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