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)
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_)
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_)
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 del self.game.things[self.game.things.index(self)]
253 except GameError as e:
255 self._last_task_result = e
260 self.set_task('WAIT')
263 if self.task.todo <= 0:
264 self._last_task_result = self.task.do()
266 if is_AI and self.task is None:
270 self.set_task('WAIT')
272 def unset_surroundings(self):
274 self._surroundings = None
277 def view_offset(self):
278 return self.game.map_geometry.get_view_offset(self.game.map_size,
283 def surroundings(self):
284 if self._surroundings is not None:
285 return self._surroundings
286 s, close_maps = self.\
287 game.map_geometry.get_view_and_seen_maps(self.game.map_size,
291 self.close_maps = close_maps
292 self._surroundings = s
293 return self._surroundings
295 def get_stencil(self):
296 if self._stencil is not None:
298 m = Map(self.surroundings.size, ' ', self.surroundings.start_indented)
299 for pos in self.surroundings:
300 if self.surroundings[pos] in {'.', '~'}:
302 fov_center = YX((m.size.y) // 2, m.size.x // 2)
303 self._stencil = FovMapHex(m, fov_center)
306 def get_visible_map(self):
307 stencil = self.get_stencil()
308 m = Map(self.surroundings.size, ' ', self.surroundings.start_indented)
310 if stencil[pos] == '.':
311 m[pos] = self.surroundings[pos]
314 def get_visible_things(self):
315 stencil = self.get_stencil()
317 for thing in self.game.things:
318 pos = self.game.map_geometry.pos_in_view(thing.position,
321 if pos.y < 0 or pos.x < 0 or\
322 pos.y >= stencil.size.y or pos.x >= stencil.size.x:
324 if (not thing.in_inventory) and stencil[pos] == '.':
325 visible_things += [thing]
326 return visible_things
328 def get_pickable_items(self):
330 visible_things = self.get_visible_things()
331 neighbor_fields = self.game.map_geometry.get_neighbors(self.position,
333 for t in [t for t in visible_things
334 if isinstance(t, ThingItem) and
335 (t.position == self.position or
336 t.position in neighbor_fields.values())]:
337 pickable_ids += [t.id_]
342 class ThingHuman(ThingAnimate):
348 class ThingMonster(ThingAnimate):