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,
117 start_indented=visible_map.start_indented)
119 dijkstra_map.terrain = [n_max for i in range(dijkstra_map.size_i)]
120 for target in targets:
121 dijkstra_map[target] = 0
123 get_neighbors = self.game.map_geometry.get_neighbors
126 for pos in dijkstra_map:
127 if visible_map[pos] != '.':
129 neighbors = get_neighbors((YX(0,0), pos), dijkstra_map.size,
130 dijkstra_map.start_indented)
131 for direction in neighbors:
132 big_yx, small_yx = neighbors[direction]
133 if big_yx == YX(0,0) and \
134 dijkstra_map[small_yx] < dijkstra_map[pos] - 1:
135 dijkstra_map[pos] = dijkstra_map[small_yx] + 1
137 #print('DEBUG DIJKSTRA ---------------------', self.id_, self.position)
138 #for y, line in dijkstra_map.lines():
141 # line_to_print += ['%3s' % x]
142 # print(' '.join(line_to_print))
143 neighbors = get_neighbors((YX(0,0), own_pos), dijkstra_map.size,
144 dijkstra_map.start_indented)
146 target_direction = None
147 for direction in sorted(neighbors.keys()):
148 big_yx, small_yx = neighbors[direction]
150 n_new = dijkstra_map[small_yx]
153 target_direction = direction
154 return target_direction
156 #def hunt_player(self):
157 # visible_things = self.get_visible_things()
159 # for t in visible_things:
160 # if t.type_ == 'human':
161 # target = t.position[1] - self.view_offset
163 # if target is not None:
165 # offset_self_pos = self.position[1] - self.view_offset
166 # target_dir = self.move_on_dijkstra_map(offset_self_pos,
168 # if target_dir is not None:
169 # self.set_task('MOVE', (target_dir,))
175 def hunt_food_satisfaction(self):
176 for id_ in self.inventory:
177 t = self.game.get_thing(id_)
178 if t.type_ == 'food':
179 self.set_task('EAT', (id_,))
181 for id_ in self.get_pickable_items():
182 t = self.game.get_thing(id_)
183 if t.type_ == 'food':
184 self.set_task('PICKUP', (id_,))
186 visible_things = self.get_visible_things()
188 for t in visible_things:
189 if t.type_ == 'food':
190 food_targets += [self.game.map_geometry.pos_in_view(t.position,
193 offset_self_pos = self.game.map_geometry.pos_in_view(self.position,
196 target_dir = self.move_on_dijkstra_map(offset_self_pos,
200 self.set_task('MOVE', (target_dir,))
206 def decide_task(self):
207 #if not self.hunt_player():
208 if not self.hunt_food_satisfaction():
209 self.set_task('WAIT')
211 def set_task(self, task_name, args=()):
212 task_class = self.game.tasks[task_name]
213 self.task = task_class(self, args)
214 self.task.check() # will throw GameError if necessary
216 def proceed(self, is_AI=True):
217 """Further the thing in its tasks, decrease its health.
219 First, ensures an empty map, decrements .health and kills
220 thing if crossing zero (removes from self.game.things for AI
221 thing, or unsets self.game.player_is_alive for player thing);
222 then checks that self.task is still possible and aborts if
223 otherwise (for AI things, decides a new task).
225 Then decrements .task.todo; if it thus falls to <= 0, enacts
226 method whose name is 'task_' + self.task.name and sets .task =
227 None. If is_AI, calls .decide_task to decide a self.task.
230 self.unset_surroundings()
233 if self is self.game.player:
234 self.game.player_is_alive = False
236 del self.game.things[self.game.things.index(self)]
240 except GameError as e:
242 self._last_task_result = e
247 self.set_task('WAIT')
250 if self.task.todo <= 0:
251 self._last_task_result = self.task.do()
253 if is_AI and self.task is None:
257 self.set_task('WAIT')
259 def unset_surroundings(self):
261 self._surroundings = None
264 def view_offset(self):
265 return self.game.map_geometry.get_view_offset(self.game.map_size,
270 def surroundings(self):
271 if self._surroundings is not None:
272 return self._surroundings
273 s = self.game.map_geometry.get_view(self.game.map_size,
275 self._radius, self.view_offset)
276 self._surroundings = s
277 return self._surroundings
279 def get_stencil(self):
280 if self._stencil is not None:
282 m = Map(self.surroundings.size, ' ', self.surroundings.start_indented)
283 for pos in self.surroundings:
284 if self.surroundings[pos] in {'.', '~'}:
286 fov_center = YX((m.size.y) // 2, m.size.x // 2)
287 self._stencil = FovMapHex(m, fov_center)
290 def get_visible_map(self):
291 stencil = self.get_stencil()
292 m = Map(self.surroundings.size, ' ', self.surroundings.start_indented)
294 if stencil[pos] == '.':
295 m[pos] = self.surroundings[pos]
298 def get_visible_things(self):
299 stencil = self.get_stencil()
301 for thing in self.game.things:
302 pos = self.game.map_geometry.pos_in_view(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):