1 from plomrogue.errors import GameError
2 from plomrogue.mapping import YX, Map, FovMapHex
9 def __init__(self, game, id_=None, position=(YX(0,0), YX(0,0))):
12 self.id_ = self.game.new_thing_id()
15 self.position = position
21 def _position_set(self, pos):
22 """Set self._position to pos.
24 We use this setter as core to the @position.setter property
25 method due to property setter subclassing not yet working
26 properly, see <https://bugs.python.org/issue14965>. We will
27 therefore super() _position_set instead of @position.setter in
34 def position(self, pos):
35 self._position_set(pos)
39 class Thing(ThingBase):
43 def __init__(self, *args, **kwargs):
46 super().__init__(*args, **kwargs)
51 def _position_set(self, pos):
52 super()._position_set(pos)
53 for t_id in self.inventory:
54 t = self.game.get_thing(t_id, create_unfound=False)
55 t.position = self.position
56 if not self.id_ == self.game.player_id:
58 edge_left = self.position[1].x - self._radius
59 edge_right = self.position[1].x + self._radius
60 edge_up = self.position[1].y - self._radius
61 edge_down = self.position[1].y + self._radius
63 self.game.get_map(self.position[0] + YX(1,-1))
64 self.game.get_map(self.position[0] + YX(0,-1))
65 self.game.get_map(self.position[0] + YX(-1,-1))
66 if edge_right >= self.game.map_size.x:
67 self.game.get_map(self.position[0] + YX(1,1))
68 self.game.get_map(self.position[0] + YX(0,1))
69 self.game.get_map(self.position[0] + YX(-1,1))
71 self.game.get_map(self.position[0] + YX(-1,1))
72 self.game.get_map(self.position[0] + YX(-1,0))
73 self.game.get_map(self.position[0] + YX(-1,-1))
74 if edge_down >= self.game.map_size.y:
75 self.game.get_map(self.position[0] + YX(1,1))
76 self.game.get_map(self.position[0] + YX(1,0))
77 self.game.get_map(self.position[0] + YX(1,-1))
79 #if self.position[1].x < self._radius:
80 # self.game.get_map(self.position[0] - YX(0,1))
81 #if self.position[1].y < self._radius:
82 # self.game.get_map(self.position[0] - YX(1,0))
83 #if self.position[1].x > self.game.map_size.x - self._radius:
84 # self.game.get_map(self.position[0] + YX(0,1))
85 #if self.position[1].y > self.game.map_size.y - self._radius:
86 # self.game.get_map(self.position[0] + YX(1,0))
87 #if self.position[1].y < self._radius and \
88 # self.position[1].x <= [pos for pos in
89 # diagonal_distance_edge
90 # if pos.y == self.position[1].y][0].x:
91 # self.game.get_map(self.position[0] - YX(1,1))
95 class ThingItem(Thing):
100 class ThingFood(ThingItem):
105 class ThingAnimate(Thing):
108 def __init__(self, *args, **kwargs):
109 super().__init__(*args, **kwargs)
110 self.set_task('WAIT')
111 self._last_task_result = None
112 self.unset_surroundings()
115 def _position_set(self, pos):
116 """For player we need to update .close_maps on every move via the
117 self.surroundings property method, to keep their reality
118 bubble in sync with their movement.
121 super()._position_set(pos)
122 if self.id_ == self.game.player_id:
123 if not hasattr(self, '_surroundings'):
124 self._surroundings = None
127 def move_on_dijkstra_map(self, own_pos, targets):
128 visible_map = self.get_visible_map()
129 dijkstra_map = Map(visible_map.size,
130 start_indented=visible_map.start_indented)
132 dijkstra_map.terrain = [n_max for i in range(dijkstra_map.size_i)]
133 for target in targets:
134 dijkstra_map[target] = 0
136 get_neighbors = self.game.map_geometry.get_neighbors
139 for pos in dijkstra_map:
140 if visible_map[pos] != '.':
142 neighbors = get_neighbors((YX(0,0), pos), dijkstra_map.size,
143 dijkstra_map.start_indented)
144 for direction in neighbors:
145 big_yx, small_yx = neighbors[direction]
146 if big_yx == YX(0,0) and \
147 dijkstra_map[small_yx] < dijkstra_map[pos] - 1:
148 dijkstra_map[pos] = dijkstra_map[small_yx] + 1
150 #print('DEBUG DIJKSTRA ---------------------', self.id_, self.position)
151 #for y, line in dijkstra_map.lines():
154 # line_to_print += ['%3s' % x]
155 # print(' '.join(line_to_print))
156 neighbors = get_neighbors((YX(0,0), own_pos), dijkstra_map.size,
157 dijkstra_map.start_indented)
159 target_direction = None
160 for direction in sorted(neighbors.keys()):
161 big_yx, small_yx = neighbors[direction]
163 n_new = dijkstra_map[small_yx]
166 target_direction = direction
167 return target_direction
169 #def hunt_player(self):
170 # visible_things = self.get_visible_things()
172 # for t in visible_things:
173 # if t.type_ == 'human':
174 # target = t.position[1] - self.view_offset
176 # if target is not None:
178 # offset_self_pos = self.position[1] - self.view_offset
179 # target_dir = self.move_on_dijkstra_map(offset_self_pos,
181 # if target_dir is not None:
182 # self.set_task('MOVE', (target_dir,))
188 def hunt_food_satisfaction(self):
189 for id_ in self.inventory:
190 t = self.game.get_thing(id_, create_unfound=False)
191 if t.type_ == 'food':
192 self.set_task('EAT', (id_,))
194 for id_ in self.get_pickable_items():
195 t = self.game.get_thing(id_, create_unfound=False)
196 if t.type_ == 'food':
197 self.set_task('PICKUP', (id_,))
199 visible_things = self.get_visible_things()
201 for t in visible_things:
202 if t.type_ == 'food':
203 food_targets += [self.game.map_geometry.pos_in_view(t.position,
206 offset_self_pos = self.game.map_geometry.pos_in_view(self.position,
209 target_dir = self.move_on_dijkstra_map(offset_self_pos,
213 self.set_task('MOVE', (target_dir,))
219 def decide_task(self):
220 #if not self.hunt_player():
221 if not self.hunt_food_satisfaction():
222 self.set_task('WAIT')
224 def set_task(self, task_name, args=()):
225 task_class = self.game.tasks[task_name]
226 self.task = task_class(self, args)
227 self.task.check() # will throw GameError if necessary
229 def proceed(self, is_AI=True):
230 """Further the thing in its tasks, decrease its health.
232 First, ensures an empty map, decrements .health and kills
233 thing if crossing zero (removes from self.game.things for AI
234 thing, or unsets self.game.player_is_alive for player thing);
235 then checks that self.task is still possible and aborts if
236 otherwise (for AI things, decides a new task).
238 Then decrements .task.todo; if it thus falls to <= 0, enacts
239 method whose name is 'task_' + self.task.name and sets .task =
240 None. If is_AI, calls .decide_task to decide a self.task.
243 self.unset_surroundings()
246 if self is self.game.player:
247 self.game.player_is_alive = False
249 # TODO: Handle inventory.
250 del self.game.things[self.game.things.index(self)]
254 except GameError as e:
256 self._last_task_result = e
261 self.set_task('WAIT')
264 if self.task.todo <= 0:
265 self._last_task_result = self.task.do()
267 if is_AI and self.task is None:
271 self.set_task('WAIT')
273 def unset_surroundings(self):
275 self._surroundings = None
278 def view_offset(self):
279 return self.game.map_geometry.get_view_offset(self.game.map_size,
284 def surroundings(self):
285 if self._surroundings is not None:
286 return self._surroundings
287 s, close_maps = self.\
288 game.map_geometry.get_view_and_seen_maps(self.game.map_size,
292 self.close_maps = close_maps
293 self._surroundings = s
294 return self._surroundings
296 def get_stencil(self):
297 if self._stencil is not None:
299 m = Map(self.surroundings.size, ' ', self.surroundings.start_indented)
300 for pos in self.surroundings:
301 if self.surroundings[pos] in {'.', '~'}:
303 fov_center = YX((m.size.y) // 2, m.size.x // 2)
304 self._stencil = FovMapHex(m, fov_center)
307 def get_visible_map(self):
308 stencil = self.get_stencil()
309 m = Map(self.surroundings.size, ' ', self.surroundings.start_indented)
311 if stencil[pos] == '.':
312 m[pos] = self.surroundings[pos]
315 def get_visible_things(self):
316 stencil = self.get_stencil()
318 for thing in self.game.things:
319 pos = self.game.map_geometry.pos_in_view(thing.position,
322 if pos.y < 0 or pos.x < 0 or\
323 pos.y >= stencil.size.y or pos.x >= stencil.size.x:
325 if (not thing.in_inventory) and stencil[pos] == '.':
326 visible_things += [thing]
327 return visible_things
329 def get_pickable_items(self):
331 visible_things = self.get_visible_things()
332 neighbor_fields = self.game.map_geometry.get_neighbors(self.position,
334 for t in [t for t in visible_things
335 if isinstance(t, ThingItem) and
336 (t.position == self.position or
337 t.position in neighbor_fields.values())]:
338 pickable_ids += [t.id_]
343 class ThingHuman(ThingAnimate):
349 class ThingMonster(ThingAnimate):