home · contact · privacy
Don't generate objects at illegal positions. Plus, refactor.
[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)):
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._stencil = None
49
50     def move_towards_target(self, target):
51         dijkstra_map = type(self.world.map_)(self.world.map_.size)
52         n_max = 256
53         dijkstra_map.terrain = [n_max for i in range(dijkstra_map.size_i)]
54         dijkstra_map[target] = 0
55         shrunk = True
56         visible_map = self.get_visible_map()
57         while shrunk:
58             shrunk = False
59             for pos in dijkstra_map:
60                 if visible_map[pos] != '.':
61                     continue
62                 neighbors = dijkstra_map.get_neighbors(tuple(pos))
63                 for direction in neighbors:
64                     yx = neighbors[direction]
65                     if yx is not None and dijkstra_map[yx] < dijkstra_map[pos] - 1:
66                         dijkstra_map[pos] = dijkstra_map[yx] + 1
67                         shrunk = True
68         neighbors = dijkstra_map.get_neighbors(tuple(self.position))
69         n = n_max
70         target_direction = None
71         for direction in sorted(neighbors.keys()):
72             yx = neighbors[direction]
73             if yx is not None:
74                 n_new = dijkstra_map[yx]
75                 if n_new < n:
76                     n = n_new
77                     target_direction = direction
78         if target_direction:
79             self.set_task('MOVE', (target_direction,))
80
81     def decide_task(self):
82         visible_things = self.get_visible_things()
83         target = None
84         for t in visible_things:
85             if t.type_ == 'human':
86                 target = t.position
87                 break
88         if target is not None:
89             try:
90                 self.move_towards_target(target)
91                 return
92             except GameError:
93                 pass
94         self.set_task('WAIT')
95
96     def set_task(self, task_name, args=()):
97         task_class = self.world.game.tasks[task_name]
98         self.task = task_class(self, args)
99         self.task.check()  # will throw GameError if necessary
100
101     def proceed(self, is_AI=True):
102         """Further the thing in its tasks, decrease its health.
103
104         First, ensures an empty map, decrements .health and kills
105         thing if crossing zero (removes from self.world.things for AI
106         thing, or unsets self.world.player_is_alive for player thing);
107         then checks that self.task is still possible and aborts if
108         otherwise (for AI things, decides a new task).
109
110         Then decrements .task.todo; if it thus falls to <= 0, enacts
111         method whose name is 'task_' + self.task.name and sets .task =
112         None. If is_AI, calls .decide_task to decide a self.task.
113
114         """
115         self._stencil = None
116         self.health -= 1
117         if self.health <= 0:
118             if self is self.world.player:
119                 self.world.player_is_alive = False
120             else:
121                 del self.world.things[self.world.things.index(self)]
122             return
123         try:
124             self.task.check()
125         except GameError as e:
126             self.task = None
127             self._last_task_result = e
128             if is_AI:
129                 try:
130                     self.decide_task()
131                 except GameError:
132                     self.set_task('WAIT')
133             return
134         self.task.todo -= 1
135         if self.task.todo <= 0:
136             self._last_task_result = self.task.do()
137             self.task = None
138         if is_AI and self.task is None:
139             try:
140                 self.decide_task()
141             except GameError:
142                 self.set_task('WAIT')
143
144     def get_stencil(self):
145         if self._stencil is not None:
146             return self._stencil
147         self._stencil = self.world.map_.get_fov_map(self.position)
148         return self._stencil
149
150     def get_visible_map(self):
151         stencil = self.get_stencil()
152         m = self.world.map_.new_from_shape(' ')
153         for pos in m:
154             if stencil[pos] == '.':
155                 m[pos] = self.world.map_[pos]
156         return m
157
158     def get_visible_things(self):
159         stencil = self.get_stencil()
160         visible_things = []
161         for thing in self.world.things:
162             if (not thing.in_inventory) and stencil[thing.position] == '.':
163                 visible_things += [thing]
164         return visible_things
165
166     def get_pickable_items(self):
167         pickable_ids = []
168         for t in [t for t in self.get_visible_things() if
169                   isinstance(t, ThingItem) and
170                   (t.position == self.position or
171                    t.position in
172                    self.world.map_.get_neighbors(self.position).values())]:
173             pickable_ids += [t.id_]
174         return pickable_ids
175
176
177
178 class ThingHuman(ThingAnimate):
179     type_ = 'human'
180     health = 100
181
182
183
184 class ThingMonster(ThingAnimate):
185     type_ = 'monster'
186     health = 50