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._surroundings = None
252 def view_offset(self):
253 return self.game.map_geometry.get_view_offset(self.game.map_size,
258 def surroundings(self):
259 if self._surroundings is not None:
260 return self._surroundings
261 s = self.game.map_geometry.get_view(self.game.map_size,
263 self._radius, self.view_offset)
264 self._surroundings = s
265 return self._surroundings
267 def get_stencil(self):
268 if self._stencil is not None:
270 m = Map(self.surroundings.size, ' ')
271 for pos in self.surroundings:
272 if self.surroundings[pos] in {'.', '~'}:
274 fov_center = YX((m.size.y) // 2, m.size.x // 2)
275 self._stencil = FovMapHex(m, fov_center)
278 def get_visible_map(self):
279 stencil = self.get_stencil()
280 m = Map(self.surroundings.size, ' ')
282 if stencil[pos] == '.':
283 m[pos] = self.surroundings[pos]
286 def get_visible_things(self):
287 stencil = self.get_stencil()
289 for thing in self.game.things:
290 pos = self.game.map_geometry.pos_in_view(thing.position,
293 if pos.y < 0 or pos.x < 0 or\
294 pos.y >= stencil.size.y or pos.x >= stencil.size.x:
296 if (not thing.in_inventory) and stencil[pos] == '.':
297 visible_things += [thing]
298 return visible_things
300 def get_pickable_items(self):
302 visible_things = self.get_visible_things()
303 neighbor_fields = self.game.map_geometry.get_neighbors(self.position,
305 for t in [t for t in visible_things
306 if isinstance(t, ThingItem) and
307 (t.position == self.position or
308 t.position in neighbor_fields.values())]:
309 pickable_ids += [t.id_]
314 class ThingHuman(ThingAnimate):
320 class ThingMonster(ThingAnimate):