home · contact · privacy
Add surrounding maps of new terrain type ~.
[plomrogue2-experiments] / new / plomrogue / things.py
1 from plomrogue.errors import GameError
2
3
4
5 class ThingBase:
6     type_ = '?'
7
8     def __init__(self, world, id_=None, position=((0,0), (0,0))):
9         self.world = world
10         self.position = position
11         if id_ is None:
12             self.id_ = self.world.new_thing_id()
13         else:
14             self.id_ = id_
15
16
17
18 class Thing(ThingBase):
19     blocking = False
20     in_inventory = False
21
22     def __init__(self, *args, **kwargs):
23         super().__init__(*args, **kwargs)
24         self.inventory = []
25
26     def proceed(self):
27         pass
28
29
30
31 class ThingItem(Thing):
32     pass
33
34
35
36 class ThingFood(ThingItem):
37     type_ = 'food'
38
39
40
41 class ThingAnimate(Thing):
42     blocking = True
43
44     def __init__(self, *args, **kwargs):
45         super().__init__(*args, **kwargs)
46         self.set_task('WAIT')
47         self._last_task_result = None
48         self._radius = 8
49         self.unset_surroundings()
50
51     def move_on_dijkstra_map(self, own_pos, targets):
52         visible_map = self.get_visible_map()
53         dijkstra_map = self.world.game.map_type(visible_map.size)
54         n_max = 256
55         dijkstra_map.terrain = [n_max for i in range(dijkstra_map.size_i)]
56         for target in targets:
57             dijkstra_map[target] = 0
58         shrunk = True
59         while shrunk:
60             shrunk = False
61             for pos in dijkstra_map:
62                 if visible_map[pos] != '.':
63                     continue
64                 neighbors = dijkstra_map.get_neighbors(tuple(pos))
65                 for direction in neighbors:
66                     yx = neighbors[direction]
67                     if yx is not None and dijkstra_map[yx] < dijkstra_map[pos] - 1:
68                         dijkstra_map[pos] = dijkstra_map[yx] + 1
69                         shrunk = True
70         neighbors = dijkstra_map.get_neighbors(own_pos)
71         n = n_max
72         target_direction = None
73         for direction in sorted(neighbors.keys()):
74             yx = neighbors[direction]
75             if yx is not None:
76                 n_new = dijkstra_map[yx]
77                 if n_new < n:
78                     n = n_new
79                     target_direction = direction
80         return target_direction
81
82     def hunt_player(self):
83         visible_things, offset = self.get_visible_things()
84         target = None
85         for t in visible_things:
86             if t.type_ == 'human':
87                 target = (t.position[1][0] - offset[0],
88                           t.position[1][1] - offset[1])
89                 break
90         if target is not None:
91             try:
92                 offset_self_pos = (self.position[1][0] - offset[0],
93                                    self.position[1][1] - offset[1])
94                 target_dir = self.move_on_dijkstra_map(offset_self_pos,
95                                                        [target])
96                 if target_dir is not None:
97                     self.set_task('MOVE', (target_dir,))
98                     return True
99             except GameError:
100                 pass
101         return False
102
103     def hunt_food_satisfaction(self):
104         for id_ in self.inventory:
105             t = self.world.get_thing(id_)
106             if t.type_ == 'food':
107                 self.set_task('EAT', (id_,))
108                 return True
109         for id_ in self.get_pickable_items():
110             t = self.world.get_thing(id_)
111             if t.type_ == 'food':
112                 self.set_task('PICKUP', (id_,))
113                 return True
114         visible_things, offset = self.get_visible_things()
115         food_targets = []
116         for t in visible_things:
117             if t.type_ == 'food':
118                 food_targets += [(t.position[1][0] - offset[0],
119                                   t.position[1][1] - offset[1])]
120         offset_self_pos = (self.position[1][0] - offset[0],
121                            self.position[1][1] - offset[1])
122         target_dir = self.move_on_dijkstra_map(offset_self_pos,
123                                                food_targets)
124         if target_dir:
125             try:
126                 self.set_task('MOVE', (target_dir,))
127                 return True
128             except GameError:
129                 pass
130         return False
131
132     def decide_task(self):
133         #if not self.hunt_player():
134         if not self.hunt_food_satisfaction():
135             self.set_task('WAIT')
136
137     def set_task(self, task_name, args=()):
138         task_class = self.world.game.tasks[task_name]
139         self.task = task_class(self, args)
140         self.task.check()  # will throw GameError if necessary
141
142     def proceed(self, is_AI=True):
143         """Further the thing in its tasks, decrease its health.
144
145         First, ensures an empty map, decrements .health and kills
146         thing if crossing zero (removes from self.world.things for AI
147         thing, or unsets self.world.player_is_alive for player thing);
148         then checks that self.task is still possible and aborts if
149         otherwise (for AI things, decides a new task).
150
151         Then decrements .task.todo; if it thus falls to <= 0, enacts
152         method whose name is 'task_' + self.task.name and sets .task =
153         None. If is_AI, calls .decide_task to decide a self.task.
154
155         """
156         self.unset_surroundings()
157         self.health -= 1
158         if self.health <= 0:
159             if self is self.world.player:
160                 self.world.player_is_alive = False
161             else:
162                 del self.world.things[self.world.things.index(self)]
163             return
164         try:
165             self.task.check()
166         except GameError as e:
167             self.task = None
168             self._last_task_result = e
169             if is_AI:
170                 try:
171                     self.decide_task()
172                 except GameError:
173                     self.set_task('WAIT')
174             return
175         self.task.todo -= 1
176         if self.task.todo <= 0:
177             self._last_task_result = self.task.do()
178             self.task = None
179         if is_AI and self.task is None:
180             try:
181                 self.decide_task()
182             except GameError:
183                 self.set_task('WAIT')
184
185     def unset_surroundings(self):
186         self._stencil = None
187         self._surrounding_map = None
188         self._surroundings_offset = None
189
190     def must_fix_indentation(self):
191         return self._radius % 2 != self.position[1][0] % 2
192
193     def get_surroundings_offset(self):
194         if self._surroundings_offset is not None:
195             return self._surroundings_offset
196         add_line = self.must_fix_indentation()
197         offset_y = self.position[1][0] - self._radius - int(add_line)
198         offset_x = self.position[1][1] - self._radius
199         self._surroundings_offset = (offset_y, offset_x)
200         return self._surroundings_offset
201
202     def get_surrounding_map(self):
203         if self._surrounding_map is not None:
204             return self._surrounding_map
205
206         def pan_and_scan(size_of_axis, pos, offset):
207             big_pos = 0
208             small_pos = pos + offset
209             if small_pos < 0:
210                 big_pos = -1
211                 small_pos = size_of_axis + small_pos
212             elif small_pos >= size_of_axis:
213                 big_pos = 1
214                 small_pos = small_pos - size_of_axis
215             return big_pos, small_pos
216
217         add_line = self.must_fix_indentation()
218         self._surrounding_map = self.world.game.\
219                                 map_type(size=(self._radius*2+1+int(add_line),
220                                                self._radius*2+1))
221         size = self.world.maps[(0,0)].size
222         offset = self.get_surroundings_offset()
223         for pos in self._surrounding_map:
224             big_y, small_y = pan_and_scan(size[0], pos[0], offset[0])
225             big_x, small_x = pan_and_scan(size[1], pos[1], offset[1])
226             big_yx = (big_y, big_x)
227             small_yx = (small_y, small_x)
228             self._surrounding_map[pos] = self.world.maps[big_yx][small_yx]
229         return self._surrounding_map
230
231     def get_stencil(self):
232         if self._stencil is not None:
233             return self._stencil
234         surrounding_map = self.get_surrounding_map()
235         m = surrounding_map.new_from_shape(' ')
236         for pos in surrounding_map:
237             if surrounding_map[pos] in {'.', '~'}:
238                 m[pos] = '.'
239         offset = self.get_surroundings_offset()
240         fov_center = (self.position[1][0] - offset[0],
241                       self.position[1][1] - offset[1])
242         self._stencil = m.get_fov_map(fov_center)
243         return self._stencil
244
245     def get_visible_map(self):
246         stencil = self.get_stencil()
247         m = self.get_surrounding_map().new_from_shape(' ')
248         for pos in m:
249             if stencil[pos] == '.':
250                 m[pos] = self._surrounding_map[pos]
251         return m
252
253     def get_visible_things(self):
254
255         def calc_pos_in_fov(big_pos, small_pos, offset, size_of_axis):
256             pos = small_pos - offset
257             if big_pos == -1:
258                 pos = small_pos - size_of_axis - offset
259             elif big_pos == 1:
260                 pos = small_pos + size_of_axis - offset
261             return pos
262
263         stencil = self.get_stencil()
264         offset = self.get_surroundings_offset()
265         visible_things = []
266         size = self.world.maps[(0,0)].size
267         fov_size = self.get_surrounding_map().size
268         for thing in self.world.things:
269             big_pos = thing.position[0]
270             small_pos = thing.position[1]
271             pos_y = calc_pos_in_fov(big_pos[0], small_pos[0], offset[0], size[0])
272             pos_x = calc_pos_in_fov(big_pos[1], small_pos[1], offset[1], size[1])
273             if pos_y < 0 or pos_x < 0 or pos_y >= fov_size[0] or pos_x >= fov_size[1]:
274                 continue
275             if (not thing.in_inventory) and stencil[(pos_y, pos_x)] == '.':
276                 visible_things += [thing]
277         return visible_things, offset
278
279     def get_pickable_items(self):
280         pickable_ids = []
281         visible_things, _ = self.get_visible_things()
282         for t in [t for t in visible_things if
283                   isinstance(t, ThingItem) and
284                   (t.position == self.position or
285                    t.position[1] in
286                    self.world.maps[(0,0)].get_neighbors(self.position[1]).values())]:
287             pickable_ids += [t.id_]
288         return pickable_ids
289
290
291
292 class ThingHuman(ThingAnimate):
293     type_ = 'human'
294     health = 100
295
296
297
298 class ThingMonster(ThingAnimate):
299     type_ = 'monster'
300     health = 50