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()
114 def move_on_dijkstra_map(self, own_pos, targets):
115 visible_map = self.get_visible_map()
116 dijkstra_map = Map(visible_map.size)
118 dijkstra_map.terrain = [n_max for i in range(dijkstra_map.size_i)]
119 for target in targets:
120 dijkstra_map[target] = 0
124 for pos in dijkstra_map:
125 if visible_map[pos] != '.':
127 neighbors = self.game.map_geometry.get_neighbors((YX(0,0), pos),
129 for direction in neighbors:
130 big_yx, small_yx = neighbors[direction]
131 if big_yx == YX(0,0) and \
132 dijkstra_map[small_yx] < dijkstra_map[pos] - 1:
133 dijkstra_map[pos] = dijkstra_map[small_yx] + 1
135 neighbors = self.game.map_geometry.get_neighbors((YX(0,0), own_pos),
138 target_direction = None
139 for direction in sorted(neighbors.keys()):
140 big_yx, small_yx = neighbors[direction]
142 n_new = dijkstra_map[small_yx]
145 target_direction = direction
146 return target_direction
148 def hunt_player(self):
149 visible_things = self.get_visible_things()
150 offset = self.get_surroundings_offset()
152 for t in visible_things:
153 if t.type_ == 'human':
154 target = t.position[1] - offset
156 if target is not None:
158 offset_self_pos = self.position[1] - offset
159 target_dir = self.move_on_dijkstra_map(offset_self_pos,
161 if target_dir is not None:
162 self.set_task('MOVE', (target_dir,))
168 def hunt_food_satisfaction(self):
169 for id_ in self.inventory:
170 t = self.game.get_thing(id_)
171 if t.type_ == 'food':
172 self.set_task('EAT', (id_,))
174 for id_ in self.get_pickable_items():
175 t = self.game.get_thing(id_)
176 if t.type_ == 'food':
177 self.set_task('PICKUP', (id_,))
179 visible_things = self.get_visible_things()
180 offset = self.get_surroundings_offset()
182 for t in visible_things:
183 if t.type_ == 'food':
184 food_targets += [t.position[1] - offset]
185 offset_self_pos = self.position[1] - offset
186 target_dir = self.move_on_dijkstra_map(offset_self_pos,
190 self.set_task('MOVE', (target_dir,))
196 def decide_task(self):
197 #if not self.hunt_player():
198 if not self.hunt_food_satisfaction():
199 self.set_task('WAIT')
201 def set_task(self, task_name, args=()):
202 task_class = self.game.tasks[task_name]
203 self.task = task_class(self, args)
204 self.task.check() # will throw GameError if necessary
206 def proceed(self, is_AI=True):
207 """Further the thing in its tasks, decrease its health.
209 First, ensures an empty map, decrements .health and kills
210 thing if crossing zero (removes from self.game.things for AI
211 thing, or unsets self.game.player_is_alive for player thing);
212 then checks that self.task is still possible and aborts if
213 otherwise (for AI things, decides a new task).
215 Then decrements .task.todo; if it thus falls to <= 0, enacts
216 method whose name is 'task_' + self.task.name and sets .task =
217 None. If is_AI, calls .decide_task to decide a self.task.
220 self.unset_surroundings()
223 if self is self.game.player:
224 self.game.player_is_alive = False
226 del self.game.things[self.game.things.index(self)]
230 except GameError as e:
232 self._last_task_result = e
237 self.set_task('WAIT')
240 if self.task.todo <= 0:
241 self._last_task_result = self.task.do()
243 if is_AI and self.task is None:
247 self.set_task('WAIT')
249 def unset_surroundings(self):
251 self._surrounding_map = None
252 self._surroundings_offset = None
254 def get_surroundings_offset(self):
255 if self._surroundings_offset is not None:
256 return self._surroundings_offset
257 y_long = self.position[0].y * self.game.map_size.y + self.position[1].y
258 x_long = self.position[0].x * self.game.map_size.x + self.position[1].x
259 yx_to_origin = YX(y_long, x_long)
260 self._surroundings_offset = yx_to_origin - YX(self._radius, self._radius)
261 return self._surroundings_offset
263 def get_surrounding_map(self):
264 if self._surrounding_map is not None:
265 return self._surrounding_map
266 self._surrounding_map = Map(size=YX(self._radius*2+1, self._radius*2+1))
267 offset = self.get_surroundings_offset()
268 for pos in self._surrounding_map:
269 correct = self.game.map_geometry.correct_double_coordinate
270 big_yx, small_yx = correct(self.game.map_size, (0,0), pos + offset)
271 map_ = self.game.get_map(big_yx, False)
273 map_ = Map(size=self.game.map_size)
274 self._surrounding_map[pos] = map_[small_yx]
275 return self._surrounding_map
277 def get_stencil(self):
278 if self._stencil is not None:
280 surrounding_map = self.get_surrounding_map()
281 m = Map(surrounding_map.size, ' ')
282 for pos in surrounding_map:
283 if surrounding_map[pos] in {'.', '~'}:
285 fov_center = YX((m.size.y) // 2, m.size.x // 2)
286 self._stencil = FovMapHex(m, fov_center)
289 def get_visible_map(self):
290 stencil = self.get_stencil()
291 m = Map(self.get_surrounding_map().size, ' ')
293 if stencil[pos] == '.':
294 m[pos] = self._surrounding_map[pos]
297 def get_visible_things(self):
298 stencil = self.get_stencil()
299 offset = self.get_surroundings_offset()
301 for thing in self.game.things:
302 pos = self.game.map_geometry.pos_in_projection(thing.position,
305 if pos.y < 0 or pos.x < 0 or\
306 pos.y >= stencil.size.y or pos.x >= stencil.size.x:
308 if (not thing.in_inventory) and stencil[pos] == '.':
309 visible_things += [thing]
310 return visible_things
312 def get_pickable_items(self):
314 visible_things = self.get_visible_things()
315 neighbor_fields = self.game.map_geometry.get_neighbors(self.position,
317 for t in [t for t in visible_things
318 if isinstance(t, ThingItem) and
319 (t.position == self.position or
320 t.position in neighbor_fields.values())]:
321 pickable_ids += [t.id_]
326 class ThingHuman(ThingAnimate):
332 class ThingMonster(ThingAnimate):