c809654fd53e73cd25f17c5b11ab7edd2332d130
[roguelike.git] / loc.py
1 import curses
2 import random
3 import math
4
5 import level
6 import ui
7
8 class Location (object):
9     def __init__(self, container = None):
10         self._items = []      # in order from top to bottom
11         self._creatures = []
12         self._scenery = []
13         self._parent = container
14     def num_items(self):
15         return len(self._items)
16     def num_creatures(self):
17         return len(self._creatures)
18     def num_scenery(self):
19         return len(self._scenery)
20     def remove(self, obj):
21         if obj.is_item():
22             self._items.remove(obj)
23         elif obj.is_creature():
24             self._creatures.remove(obj)
25         elif obj.is_scenery():
26             self._scenery.remove(obj)
27     def add(self, obj):
28         if obj.is_item():
29             self._items[:0] = [obj]
30         elif obj.is_creature():
31             self._creatures[:0] = [obj]
32         elif obj.is_scenery():
33             self._scenery[:0] = [obj]
34     def top_item(self):
35         return self._items[0] if len(self._items) > 0 else None
36     def top_creature(self):
37         return self._creatures[0] if len(self._creatures) > 0 else None
38     def top_scenery(self):
39         return self._scenery[0] if len(self._scenery) > 0 else None
40     def container(self):
41         return self._parent
42     def level(self):
43         return self.container().level()
44     def can_hold(self, thing):
45         return False
46
47 class Inventory (Location):
48     def __init__(self, creature):
49         Location.__init__(self, creature)
50     def __getitem__(self, k):
51         return self._items[k]
52     def can_hold(self, thing):
53         return thing.is_item()
54
55 class Tile (Location):
56     "A square big enough for a player; the smallest unit of space."
57     def __init__(self, lvl, y, x):
58         Location.__init__(self, lvl)
59         (self.y, self.x) = (y, x)
60     def passable_by(self, thing):
61         return None
62     def transparent(self):
63         return True
64     def can_hold(self, thing):
65         return self.passable_by(thing)
66     def affect(self, thing):
67         pass
68     def close(self):
69         return False
70     def open(self):
71         return False
72     def invalidate(self, important=False):
73         self._parent.invalidate(self, important)
74     def add(self, obj):
75         Location.add(self, obj)
76         obj.invalidate()
77     def remove(self, obj):
78         obj.invalidate()
79         Location.remove(self,obj)
80     def target(self):
81         return None
82     def traverse(self):
83         return self.target()
84     def is_active(self):
85         return False
86     def affect_all(self):
87         for i in self._items:
88             self.affect(i)
89         for i in self._creatures:
90             self.affect(i)
91         for i in self._scenery:
92             self.affect(i)
93     def is_adjacent(self, tile):
94         return (self.level() == tile.level()
95                 and abs(self.y - tile.y) <= 1
96                 and abs(self.x - tile.x) <= 1)
97     def setattr(self, attr, value):
98         raise AttributeError("unknown attribute %s for %s" % (attr, self))
99     def open(self):
100         return False
101     def north(self, n=1):
102         newy = self.y - n
103         if newy >= 0:
104             return self.level().loc(newy, self.x)
105         else:
106             return None
107     def south(self, n=1):
108         newy = self.y + n
109         if newy < self.level().h:
110             return self.level().loc(newy, self.x)
111         else:
112             return None
113     def west(self, n=1):
114         newx = self.x - n
115         if newx >= 0:
116             return self.level().loc(self.y, newx)
117         else:
118             return None
119     def east(self, n=1):
120         newx = self.x + n
121         if newx < self.level().w:
122             return self.level().loc(self.y, newx)
123         else:
124             return None
125     def dir(self, key, n=1):
126         if key == curses.KEY_UP:
127             return self.north(n)
128         if key == curses.KEY_DOWN:
129             return self.south(n)
130         if key == curses.KEY_LEFT:
131             return self.west(n)
132         if key == curses.KEY_RIGHT:
133             return self.east(n)
134     def throwdest(self, key, obj, maxdist=6):
135         for i in range(maxdist+1):
136             loc = self.dir(key, i)
137             if loc == None or not loc.passable_by(obj):
138                 if i>0:
139                     return self.dir(key, i-1)
140                 else:
141                     return None
142         return self.dir(key, maxdist)
143     def render_bg(self):
144         return self.pic
145     def render(self):
146         pic = self.render_bg()
147         if self.num_scenery() > 0:
148             pic = self.top_scenery().render(pic)
149         if self.num_items() > 0:
150             pic = self.top_item().render(pic)
151         if self.num_creatures() > 0:
152             pic = self.top_creature().render(pic)
153         return pic
154     def as_loc(self):
155         return level.Loc(self.level().lid, self.y, self.x)
156
157 class Floor(Tile):
158     "A passable Tile."
159     def __init__(self, lvl, y, x, objlist=[]):
160         Tile.__init__(self, lvl, y, x)
161         self.pic = random.choice("  ."), random.choice((
162             ui.Color('BrightBlack').cp, ui.Color('Brown').cp,
163             ui.Color('BrightGreen').cp, ui.Color('Green').cp,
164             ))
165         for o in objlist:
166             o.place(self)
167     def passable_by(self, thing):
168         return True
169     def export(self):
170         return (" ", {})
171
172 class Stair(Floor):
173     "A tile that leads to another level."
174     tgt = None
175     oneway = False
176     def __init__(self, lvl, y, x, tgt = None, oneway = False, objlist=[]):
177         Floor.__init__(self, lvl, y, x, objlist)
178         self.pic = ">", ui.Color('White').cp
179         if tgt:
180             self.tgt = tgt
181         if oneway:
182             self.oneway = oneway
183
184     def setattr(self, attr, value):
185         was_available = not (self.tgt or self.oneway)
186         if not attr in ("oneway", "tgt"):
187             raise Exception("Unknown attr %s for %s" % (attr, self.__class__))
188
189         self.__setattr__(attr, value)
190
191         is_available = not (self.tgt or self.oneway)
192
193         if was_available and not is_available:
194             self.level().stair_unavailable(self)
195         elif not was_available and is_available:
196             self.level().stair_available(self)
197
198     def target(self):
199         return self.tgt
200
201     def traverse(self):
202         oldlvl = self.level()
203         tgt = self.target()
204
205         if isinstance(tgt, Location):
206             return tgt
207
208         if tgt == None:
209             tgt = level.Level(None, height=40, width=90)
210         elif isinstance(tgt, level.LevelProxy):
211             tgt = tgt.level()
212
213         if isinstance(tgt, level.Level):
214             tgt = tgt.get_target(self.oneway)
215             if not self.oneway:
216                 tgt.setattr("tgt", self)
217         elif isinstance(tgt, level.LocationProxy):
218             tgt = tgt.location()
219             if not self.oneway:
220                 tgt.setattr("tgt", self)
221
222         assert isinstance(tgt, Location), (
223                 "Target of %s is not a Location: %s" % (self, tgt))
224         self.tgt = tgt
225         return tgt
226
227     def export(self):
228         extras = { 'oneway': self.oneway }
229         if (isinstance(self.tgt, Location) or
230                 isinstance(self.tgt, level.LocationProxy)):
231             extras['tgt'] = self.tgt.as_loc()
232         return (">", extras)
233
234 class Door(Tile):
235     "A Tile that may be opened and closed."
236     def __init__(self, lvl, y, x, opened = False, hidden = False):
237         Tile.__init__(self, lvl, y, x)
238         self.opened = opened
239         self.hidden = hidden
240         self.pic = self.getpic()
241     def transparent(self):
242         return self.opened
243     def getpic(self):
244         if self.hidden:
245             return "=" if self.opened else "#", ui.Color('Default').cp
246         else:
247             return "-" if self.opened else "+", ui.Color('Brown').cp
248     def open(self):
249         if self.opened:
250             return False
251         else:
252             self.opened = True
253             self.pic = self.getpic()
254             self.invalidate()
255             return True
256     def close(self):
257         if self.opened:
258             self.opened = False
259             self.pic = self.getpic()
260             self.invalidate()
261             return True
262         else:
263             return False
264     def hide(self):
265         if self.hidden:
266             return False
267         else:
268             self.hidden = True
269             self.pic = self.getpic()
270             self.invalidate()
271             return True
272     def unhide(self):
273         if self.hidden:
274             self.hidden = False
275             self.pic = self.getpic()
276             self.invalidate()
277             return True
278         else:
279             return False
280     def setattr(self, attr, value):
281         if attr == 'hidden':
282             self.hide() if value else self.unhide()
283         elif attr == 'opened':
284             self.open() if value else self.hide()
285         else:
286             raise Exception("Unknown attr %s for %s" % (attr, self.__class__))
287     def passable_by(self, thing):
288         return self.opened
289     def export(self):
290         return "-" if self.opened else "+", {'hidden': self.hidden, 'opened': self.opened}
291
292 class Wall(Tile):
293     "An impassable Tile."
294     def __init__(self, lvl, y, x):
295         Tile.__init__(self, lvl, y, x)
296         self.pic = "#", ui.Color('Default').cp
297     def transparent(self):
298         return False
299     def passable_by(self, thing):
300         return False
301     def export(self):
302         return ("#", {})
303
304 class Water(Tile):
305     "A wet, mostly impassable, Tile."
306     def __init__(self, lvl, y, x, flowy=0.0, flowx=0.0):
307         Tile.__init__(self, lvl, y, x)
308         self.flowy = flowy
309         self.flowx = flowx
310         pic = ('~', ui.Color('Blue').cp)
311
312     def set_flow(self, flowy, flowx):
313         self.flowy = flowy
314         self.flowx = flowx
315
316     def render_bg(self):
317         return ('~', random.choice((
318             ui.Color('Blue').cp, ui.Color('Blue').cp, ui.Color('Blue').cp,
319             ui.Color('Cyan').cp, ui.Color('BrightBlue').cp, ui.Color('BrightBlue').cp,
320             ui.Color('BrightCyan').cp, ui.Color('White').cp,
321             )))
322
323     def setattr(self, attr, value):
324         if not attr in ("flowy", "flowx"):
325             raise Exception("Unknown attr %s for %s" % (attr, self.__class__))
326         self.__setattr__(attr, value)
327
328     def passable_by(self, thing):
329         return thing.is_item()  or  thing.is_creature() and thing.can_swim()
330     
331     def is_active(self):
332         return True
333    
334     def affect(self, thing):
335         if random.random() < 0.5:
336             (fxf,fxw) = math.modf(self.flowx)
337             (fyf,fyw) = math.modf(self.flowy)
338             xs = 1 if self.flowx > 0.0 else -1
339             ys = 1 if self.flowy > 0.0 else -1
340             
341
342             newx = self.x + int(fxw)
343             newy = self.y + int(fyw)
344             if random.random() < abs(fxf):
345                 newx += xs
346             if random.random() < abs(fyf):
347                 newy += ys
348             (newy,newx) = self.level().clip(newy,newx)
349             thing.try_move(self.level().loc(newy,newx))
350
351     def export(self):
352         return ("~", { 'flowx' : self.flowx, 'flowy' : self.flowy })
353