1 from plomrogue.errors import GameError
8 def __init__(self, world, id_=None, position=((0,0), (0,0))):
10 self.position = position
12 self.id_ = self.world.new_thing_id()
20 def _position_set(self, pos):
21 """Set self._position to pos.
23 We use this setter as core to the @position.setter property
24 method due to property setter subclassing not yet working
25 properly, see <https://bugs.python.org/issue14965>. We will
26 therefore super() _position_set instead of @position.setter in
33 def position(self, pos):
34 self._position_set(pos)
38 class Thing(ThingBase):
42 def __init__(self, *args, **kwargs):
44 super().__init__(*args, **kwargs)
49 def _position_set(self, pos):
50 super()._position_set(pos)
51 for t_id in self.inventory:
52 t = self.world.get_thing(t_id)
53 t.position = self.position
57 class ThingItem(Thing):
62 class ThingFood(ThingItem):
67 class ThingAnimate(Thing):
70 def __init__(self, *args, **kwargs):
71 super().__init__(*args, **kwargs)
73 self._last_task_result = None
75 self.unset_surroundings()
77 def move_on_dijkstra_map(self, own_pos, targets):
78 visible_map = self.get_visible_map()
79 dijkstra_map = self.world.game.map_type(visible_map.size)
81 dijkstra_map.terrain = [n_max for i in range(dijkstra_map.size_i)]
82 for target in targets:
83 dijkstra_map[target] = 0
87 for pos in dijkstra_map:
88 if visible_map[pos] != '.':
90 neighbors = dijkstra_map.get_neighbors(tuple(pos))
91 for direction in neighbors:
92 yx = neighbors[direction]
93 if yx is not None and dijkstra_map[yx] < dijkstra_map[pos] - 1:
94 dijkstra_map[pos] = dijkstra_map[yx] + 1
96 neighbors = dijkstra_map.get_neighbors(own_pos)
98 target_direction = None
99 for direction in sorted(neighbors.keys()):
100 yx = neighbors[direction]
102 n_new = dijkstra_map[yx]
105 target_direction = direction
106 return target_direction
108 def hunt_player(self):
109 visible_things = self.get_visible_things()
110 offset = self.get_surroundings_offset()
112 for t in visible_things:
113 if t.type_ == 'human':
114 target = (t.position[1][0] - offset[0],
115 t.position[1][1] - offset[1])
117 if target is not None:
119 offset_self_pos = (self.position[1][0] - offset[0],
120 self.position[1][1] - offset[1])
121 target_dir = self.move_on_dijkstra_map(offset_self_pos,
123 if target_dir is not None:
124 self.set_task('MOVE', (target_dir,))
130 def hunt_food_satisfaction(self):
131 for id_ in self.inventory:
132 t = self.world.get_thing(id_)
133 if t.type_ == 'food':
134 self.set_task('EAT', (id_,))
136 for id_ in self.get_pickable_items():
137 t = self.world.get_thing(id_)
138 if t.type_ == 'food':
139 self.set_task('PICKUP', (id_,))
141 visible_things = self.get_visible_things()
142 offset = self.get_surroundings_offset()
144 for t in visible_things:
145 if t.type_ == 'food':
146 food_targets += [(t.position[1][0] - offset[0],
147 t.position[1][1] - offset[1])]
148 offset_self_pos = (self.position[1][0] - offset[0],
149 self.position[1][1] - offset[1])
150 target_dir = self.move_on_dijkstra_map(offset_self_pos,
154 self.set_task('MOVE', (target_dir,))
160 def decide_task(self):
161 #if not self.hunt_player():
162 if not self.hunt_food_satisfaction():
163 self.set_task('WAIT')
165 def set_task(self, task_name, args=()):
166 task_class = self.world.game.tasks[task_name]
167 self.task = task_class(self, args)
168 self.task.check() # will throw GameError if necessary
170 def proceed(self, is_AI=True):
171 """Further the thing in its tasks, decrease its health.
173 First, ensures an empty map, decrements .health and kills
174 thing if crossing zero (removes from self.world.things for AI
175 thing, or unsets self.world.player_is_alive for player thing);
176 then checks that self.task is still possible and aborts if
177 otherwise (for AI things, decides a new task).
179 Then decrements .task.todo; if it thus falls to <= 0, enacts
180 method whose name is 'task_' + self.task.name and sets .task =
181 None. If is_AI, calls .decide_task to decide a self.task.
184 self.unset_surroundings()
187 if self is self.world.player:
188 self.world.player_is_alive = False
190 del self.world.things[self.world.things.index(self)]
194 except GameError as e:
196 self._last_task_result = e
201 self.set_task('WAIT')
204 if self.task.todo <= 0:
205 self._last_task_result = self.task.do()
207 if is_AI and self.task is None:
211 self.set_task('WAIT')
213 def unset_surroundings(self):
215 self._surrounding_map = None
216 self._surroundings_offset = None
218 def must_fix_indentation(self):
219 return self._radius % 2 != self.position[1][0] % 2
221 def get_surroundings_offset(self):
222 if self._surroundings_offset is not None:
223 return self._surroundings_offset
224 add_line = self.must_fix_indentation()
225 offset_y = self.position[1][0] - self._radius - int(add_line)
226 offset_x = self.position[1][1] - self._radius
227 self._surroundings_offset = (offset_y, offset_x)
228 return self._surroundings_offset
230 def get_surrounding_map(self):
231 if self._surrounding_map is not None:
232 return self._surrounding_map
234 def pan_and_scan(size_of_axis, pos, offset):
236 small_pos = pos + offset
239 small_pos = size_of_axis + small_pos
240 elif small_pos >= size_of_axis:
242 small_pos = small_pos - size_of_axis
243 return big_pos, small_pos
245 add_line = self.must_fix_indentation()
246 self._surrounding_map = self.world.game.\
247 map_type(size=(self._radius*2+1+int(add_line),
249 size = self.world.map_size
250 offset = self.get_surroundings_offset()
251 for pos in self._surrounding_map:
252 big_y, small_y = pan_and_scan(size[0], pos[0], offset[0])
253 big_x, small_x = pan_and_scan(size[1], pos[1], offset[1])
254 big_yx = (big_y, big_x)
255 small_yx = (small_y, small_x)
256 self._surrounding_map[pos] = self.world.maps[big_yx][small_yx]
257 return self._surrounding_map
259 def get_stencil(self):
260 if self._stencil is not None:
262 surrounding_map = self.get_surrounding_map()
263 m = surrounding_map.new_from_shape(' ')
264 for pos in surrounding_map:
265 if surrounding_map[pos] in {'.', '~'}:
267 offset = self.get_surroundings_offset()
268 fov_center = (self.position[1][0] - offset[0],
269 self.position[1][1] - offset[1])
270 self._stencil = m.get_fov_map(fov_center)
273 def get_visible_map(self):
274 stencil = self.get_stencil()
275 m = self.get_surrounding_map().new_from_shape(' ')
277 if stencil[pos] == '.':
278 m[pos] = self._surrounding_map[pos]
281 def get_visible_things(self):
283 def calc_pos_in_fov(big_pos, small_pos, offset, size_of_axis):
284 pos = small_pos - offset
286 pos = small_pos - size_of_axis - offset
288 pos = small_pos + size_of_axis - offset
291 stencil = self.get_stencil()
292 offset = self.get_surroundings_offset()
294 size = self.world.map_size
295 fov_size = self.get_surrounding_map().size
296 for thing in self.world.things:
297 big_pos = thing.position[0]
298 small_pos = thing.position[1]
299 pos_y = calc_pos_in_fov(big_pos[0], small_pos[0], offset[0], size[0])
300 pos_x = calc_pos_in_fov(big_pos[1], small_pos[1], offset[1], size[1])
301 if pos_y < 0 or pos_x < 0 or\
302 pos_y >= fov_size[0] or pos_x >= fov_size[1]:
304 if (not thing.in_inventory) and stencil[(pos_y, pos_x)] == '.':
305 visible_things += [thing]
306 return visible_things
308 def get_pickable_items(self):
310 visible_things = self.get_visible_things()
311 for t in [t for t in visible_things if
312 isinstance(t, ThingItem) and
313 (t.position == self.position or
315 self.world.maps[(0,0)].get_neighbors(self.position[1]).values())]:
316 pickable_ids += [t.id_]
321 class ThingHuman(ThingAnimate):
327 class ThingMonster(ThingAnimate):