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()
151 for t in visible_things:
152 if t.type_ == 'human':
153 target = t.position[1] - self.view_offset
155 if target is not None:
157 offset_self_pos = self.position[1] - self.view_offset
158 target_dir = self.move_on_dijkstra_map(offset_self_pos,
160 if target_dir is not None:
161 self.set_task('MOVE', (target_dir,))
167 def hunt_food_satisfaction(self):
168 for id_ in self.inventory:
169 t = self.game.get_thing(id_)
170 if t.type_ == 'food':
171 self.set_task('EAT', (id_,))
173 for id_ in self.get_pickable_items():
174 t = self.game.get_thing(id_)
175 if t.type_ == 'food':
176 self.set_task('PICKUP', (id_,))
178 visible_things = self.get_visible_things()
180 for t in visible_things:
181 if t.type_ == 'food':
182 food_targets += [t.position[1] - self.view_offset]
183 offset_self_pos = self.position[1] - self.view_offset
184 target_dir = self.move_on_dijkstra_map(offset_self_pos,
188 self.set_task('MOVE', (target_dir,))
194 def decide_task(self):
195 #if not self.hunt_player():
196 if not self.hunt_food_satisfaction():
197 self.set_task('WAIT')
199 def set_task(self, task_name, args=()):
200 task_class = self.game.tasks[task_name]
201 self.task = task_class(self, args)
202 self.task.check() # will throw GameError if necessary
204 def proceed(self, is_AI=True):
205 """Further the thing in its tasks, decrease its health.
207 First, ensures an empty map, decrements .health and kills
208 thing if crossing zero (removes from self.game.things for AI
209 thing, or unsets self.game.player_is_alive for player thing);
210 then checks that self.task is still possible and aborts if
211 otherwise (for AI things, decides a new task).
213 Then decrements .task.todo; if it thus falls to <= 0, enacts
214 method whose name is 'task_' + self.task.name and sets .task =
215 None. If is_AI, calls .decide_task to decide a self.task.
218 self.unset_surroundings()
221 if self is self.game.player:
222 self.game.player_is_alive = False
224 del self.game.things[self.game.things.index(self)]
228 except GameError as e:
230 self._last_task_result = e
235 self.set_task('WAIT')
238 if self.task.todo <= 0:
239 self._last_task_result = self.task.do()
241 if is_AI and self.task is None:
245 self.set_task('WAIT')
247 def unset_surroundings(self):
249 self._surrounding_map = None
252 def view_offset(self):
253 return self.game.map_geometry.get_view_offset(self.game.map_size,
257 def get_surrounding_map(self):
258 if self._surrounding_map is not None:
259 return self._surrounding_map
260 self._surrounding_map = Map(size=YX(self._radius*2+1, self._radius*2+1))
261 for pos in self._surrounding_map:
262 correct = self.game.map_geometry.correct_double_coordinate
263 big_yx, small_yx = correct(self.game.map_size, (0,0),
264 pos + self.view_offset)
265 map_ = self.game.get_map(big_yx, False)
267 map_ = Map(size=self.game.map_size)
268 self._surrounding_map[pos] = map_[small_yx]
269 return self._surrounding_map
271 def get_stencil(self):
272 if self._stencil is not None:
274 surrounding_map = self.get_surrounding_map()
275 m = Map(surrounding_map.size, ' ')
276 for pos in surrounding_map:
277 if surrounding_map[pos] in {'.', '~'}:
279 fov_center = YX((m.size.y) // 2, m.size.x // 2)
280 self._stencil = FovMapHex(m, fov_center)
283 def get_visible_map(self):
284 stencil = self.get_stencil()
285 m = Map(self.get_surrounding_map().size, ' ')
287 if stencil[pos] == '.':
288 m[pos] = self._surrounding_map[pos]
291 def get_visible_things(self):
292 stencil = self.get_stencil()
294 for thing in self.game.things:
295 pos = self.game.map_geometry.pos_in_view(thing.position,
298 if pos.y < 0 or pos.x < 0 or\
299 pos.y >= stencil.size.y or pos.x >= stencil.size.x:
301 if (not thing.in_inventory) and stencil[pos] == '.':
302 visible_things += [thing]
303 return visible_things
305 def get_pickable_items(self):
307 visible_things = self.get_visible_things()
308 neighbor_fields = self.game.map_geometry.get_neighbors(self.position,
310 for t in [t for t in visible_things
311 if isinstance(t, ThingItem) and
312 (t.position == self.position or
313 t.position in neighbor_fields.values())]:
314 pickable_ids += [t.id_]
319 class ThingHuman(ThingAnimate):
325 class ThingMonster(ThingAnimate):