Add monsters and AIs.
[roguelike.git] / creature.py
1 #! /usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 # Copyright © 2008 Neil Moore <neil@s-z.org>.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #  
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
20 import random
21
22 import cacher
23 import ai
24 import loc
25 import thing
26 import ui
27
28 slotabbrevs = { 'hand': 'W', 'arm': 'A', 'body': 'B', 'head': 'H' }
29
30 class Creature(thing.Thing):
31     def __init__(self, location=None):
32         thing.Thing.__init__(self, location)
33
34         self.dead = False
35         self.hpmax = 10
36         self.hp = self.hpmax
37         self.oxygen = 10
38         
39         self.intrinsics = set()
40         self.extrinsics = set()
41
42         self.inv = loc.Inventory(self)
43         self.slots = { 'hand': None, 'arm': None, 'body': None, 'head': None }
44
45     def has(self, intrinsic):
46         if isinstance(intrinsic, basestring):
47             intrinsic = thing.Intrinsic(intrinsic)
48         return intrinsic in self.intrinsics or intrinsic in self.extrinsics
49
50     def holding(self, name):
51         return self.slots[name]
52     def is_wielding(self, item):
53         return item in self.slots
54     def vis_radius(self):
55         return 5
56     def can_see(self, y, x):
57         loc = self.location
58         if (y - loc.y)**2 + (x - loc.x)**2 <= self.vis_radius()**2:
59             return self.level().visible_path(loc.y, loc.x, y, x)
60         return False
61     def visibles(self):
62         vis = []
63         py = self.location.y
64         px = self.location.x
65         rad = self.vis_radius()
66         (ymin,xmin) = self.level().clip(py - rad, px - rad)
67         (ymax,xmax) = self.level().clip(py + rad, px + rad)
68         for y in range(ymin, ymax+1):
69             for x in range(xmin, xmax+1):
70                 if self.can_see(y,x):
71                     vis.append((y,x))
72         return vis
73
74     def is_creature(self):
75         return True
76
77     def can_breathe(self):
78         if isinstance(self.location, loc.Water):
79             return self.has("waterbreathing")
80         else:
81             return True
82
83     def breathe(self):
84         if self.can_breathe():
85             if self.oxygen < 10:
86                 self.oxygen += 1
87         else:
88             if self.oxygen > 0:
89                 self.oxygen -= 1
90             else:
91                 self.losehp(1, "asphyxia")
92
93     def tick(self):
94         self.breathe()
95
96     def losehp(self, n, cause):
97         if n >= self.hp:
98             self.die(cause)
99         else:
100             self.hp -= n
101
102     def die(self, cause):
103         self.dead = cause
104         loc = self.location
105         self.place(None)
106         for item in self.inv:
107             item.place(loc)
108         thing.ItemClass("corpse")().place(loc)
109
110     def can_swim(self):
111         return random.random() < .5
112     def render(self, pic=None):
113         return ( "?", ui.Color('BrightRed').cp )
114     def move_north(self):
115         return self.try_move(self.location.north())
116     def move_south(self):
117         return self.try_move(self.location.south())
118     def move_west(self):
119         return self.try_move(self.location.west())
120     def move_east(self):
121         return self.try_move(self.location.east())
122     def pick_up(self):
123         it = self.location.top_item()
124         if it:
125             it.try_move(self.inv)
126             return True
127         else:
128             return False
129     def wield(self, item):
130         assert item in self.inv, (
131                 "%s is wielding item %s not in inventory" % (self, item))
132         assert item.wieldable(), (
133                 "%s trying to wield the unwieldable %s" % (self, item))
134         assert not item.wielded, (
135                 "%s trying to wield %s, already wielded by %s"
136                 % (self, item, item.wielded))
137         slot = item.slot
138         if self.holding(slot):
139             self.unwield(self.holding(slot))
140         self.slots[slot] = item
141         item.wielded = (self, slot)
142
143         if item.intrinsics:
144             self.extrinsics |= item.intrinsics
145             
146         return True
147
148     def unwield(self, item):
149         assert item in self.inv, (
150                 "%s is unwielding item %s not in inventory" % (self, item))
151         assert item.wielded, (
152                 "%s trying to unwield already unwielded %s" % (self, item))
153
154         (creat, slot) = item.wielded
155         assert creat is self, "%s unwielding %s's %s" % (self, creat, item)
156         assert self.holding(slot) is item, (
157                 "%s unwielding %s from wrong hand %s?" % (self, item, slot))
158
159         item.wielded = None
160         self.slots[slot] = None
161         if item.intrinsics:
162             self.recompute_extrinsics()
163         return True
164
165     def recompute_extrinsics(self):
166         self.extrinsics.clear()
167         for equip in self.slots.values():
168             if equip and equip.intrinsics:
169                 self.extrinsics |= equip.intrinsics
170
171 class Monster (Creature):
172     def __init__(self, location=None):
173         super(Monster,self).__init__(location)
174         self.ai = ai.AI(self)
175     def tick(self):
176         super(Monster, self).tick()
177         if self.dead:
178             self.ai = None
179         else:
180             self.ai.act()
181     def die(self, cause):
182         self.ai.die(cause)
183         super(Monster,self).die(cause)
184
185
186 class Player (Creature):
187     def __init__(self, location=None):
188         super(Player,self).__init__(location)
189         self.active = False
190     def render(self, pic=None):
191         return ("@", ui.Color(('Bright' if self.active else '') + 'Magenta').cp)
192     def can_swim(self):
193         return random.random() < .5
194     def format_intrinsics(self):
195         return " ".join([ intr.abbrev for intr in self.intrinsics ]
196                 + [ "/" ] + [ extr.abbrev for extr in self.extrinsics ])
197     def invalidate(self):
198         if self.location is not None:
199             self.location.invalidate(important = True)