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 get_neighbors((YX(0,0), own_pos), dijkstra_map.size,
138 dijkstra_map.start_indented)
140 target_direction = None
141 for direction in sorted(neighbors.keys()):
142 big_yx, small_yx = neighbors[direction]
144 n_new = dijkstra_map[small_yx]
147 target_direction = direction
148 return target_direction
150 def hunt_player(self):
151 visible_things = self.get_visible_things()
153 for t in visible_things:
154 if t.type_ == 'human':
155 target = t.position[1] - self.view_offset
157 if target is not None:
159 offset_self_pos = self.position[1] - self.view_offset
160 target_dir = self.move_on_dijkstra_map(offset_self_pos,
162 if target_dir is not None:
163 self.set_task('MOVE', (target_dir,))
169 def hunt_food_satisfaction(self):
170 for id_ in self.inventory:
171 t = self.game.get_thing(id_)
172 if t.type_ == 'food':
173 self.set_task('EAT', (id_,))
175 for id_ in self.get_pickable_items():
176 t = self.game.get_thing(id_)
177 if t.type_ == 'food':
178 self.set_task('PICKUP', (id_,))
180 visible_things = self.get_visible_things()
182 for t in visible_things:
183 if t.type_ == 'food':
184 food_targets += [t.position[1] - self.view_offset]
185 offset_self_pos = self.position[1] - self.view_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._surroundings = None
254 def view_offset(self):
255 return self.game.map_geometry.get_view_offset(self.game.map_size,
260 def surroundings(self):
261 if self._surroundings is not None:
262 return self._surroundings
263 s = self.game.map_geometry.get_view(self.game.map_size,
265 self._radius, self.view_offset)
266 self._surroundings = s
267 return self._surroundings
269 def get_stencil(self):
270 if self._stencil is not None:
272 m = Map(self.surroundings.size, ' ', self.surroundings.start_indented)
273 for pos in self.surroundings:
274 if self.surroundings[pos] in {'.', '~'}:
276 fov_center = YX((m.size.y) // 2, m.size.x // 2)
277 self._stencil = FovMapHex(m, fov_center)
280 def get_visible_map(self):
281 stencil = self.get_stencil()
282 m = Map(self.surroundings.size, ' ')
284 if stencil[pos] == '.':
285 m[pos] = self.surroundings[pos]
288 def get_visible_things(self):
289 stencil = self.get_stencil()
291 for thing in self.game.things:
292 pos = self.game.map_geometry.pos_in_view(thing.position,
295 if pos.y < 0 or pos.x < 0 or\
296 pos.y >= stencil.size.y or pos.x >= stencil.size.x:
298 if (not thing.in_inventory) and stencil[pos] == '.':
299 visible_things += [thing]
300 return visible_things
302 def get_pickable_items(self):
304 visible_things = self.get_visible_things()
305 neighbor_fields = self.game.map_geometry.get_neighbors(self.position,
307 for t in [t for t in visible_things
308 if isinstance(t, ThingItem) and
309 (t.position == self.position or
310 t.position in neighbor_fields.values())]:
311 pickable_ids += [t.id_]
316 class ThingHuman(ThingAnimate):
322 class ThingMonster(ThingAnimate):