Support for intrinsics.
[roguelike.git] / thing.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 import re
22
23 import cacher
24 import loc
25 import ui
26
27 class Intrinsic:
28     __metaclass__ = cacher.FirstArg
29     
30     def __init__(self, name, description, abbrev, gain_msg, lose_msg):
31         self.name = name
32         self.description = description
33         self.abbrev = abbrev
34         self.gain_msg = gain_msg
35         self.lose_msg = lose_msg
36
37 intrinsics = set((
38         Intrinsic("waterbreathing", "water breathing", "WB",
39             "You feel gilly", "You feel lungy"),
40         ))
41
42 class Material(object):
43     __metaclass__ = cacher.FirstArg
44
45     def __init__(self, name, adj, attrs):
46         self.name = name
47         self.adj = adj or name
48         self.attrs = attrs
49     
50     @classmethod
51     def load(cls, fh):
52         for line in fh:
53             line = line.rstrip('\n')
54             name = None
55             adj = None
56             color = None
57             if re.match(r"#|\s*$", line):
58                 continue
59             mat = re.match(
60                     r'\s*(?:(\w+)|"([^"]*)")(?:\s+\(([^()]*)\))?\s*:\s+(.*?)\s*$',
61                     line)
62             assert mat, "Material line '%s' did not match" % (line,)
63
64             (name, qname, adj, val) = mat.groups()
65
66             name = name or qname
67             assert name, "Material '%s' does not have a name" % (line)
68
69             while val:
70                 vm = re.match(r'\s*(\w+)=(?:(\w+)|"([^"]*)")\s*', val)
71                 assert vm, "Material value '%s' is bad" % (val,)
72
73                 (vname, vval, qvval) = vm.groups()
74                 vval = vval or qvval
75
76                 if vname == 'color':
77                     color = vval
78                 else:
79                     raise Exception("Unknown attribute '%s'" % (vname,))
80
81                 val = val[vm.end():]
82
83             # Now we have the info we need
84             yield (Material(name, adj, color))
85
86     @classmethod
87     def loadall(cls, fh):
88         return list(cls.load(fh))
89
90     def attr(self):
91         if callable(self.attrs):
92             return self.attrs()
93         elif self.attrs is None:
94             self.attrs = ui.Color('Default').cp
95         elif isinstance(self.attrs, str):
96             self.attrs = ui.Color(self.attrs).cp
97
98         return self.attrs
99
100 class ItemClass (type):
101     __metaclass__ = cacher.FirstArg
102     def __new__(cls, name, properties):
103         base = Item
104         props = properties.copy()
105         props['name'] = name
106         if 'material' in props and isinstance(props['material'], str):
107             props['material'] = Material(props['material'])
108         if 'isa' in props:
109             base = ItemClass(props['isa'])
110             del props['isa']
111         return type.__new__(cls, name + " item", (base,), props)
112
113     @classmethod 
114     def load(cls, fh):
115         import pyparsing
116         from pyparsing import Word, Suppress, Optional, Dict, Group
117         ident = Word( pyparsing.alphas, pyparsing.alphanums )
118         string = ident | pyparsing.QuotedString(
119                 quoteChar = '"', escChar = '\\', unquoteResults = True)
120         value = string
121         attrib = Group(ident + Suppress(':') + string)
122         block = Suppress('{') + pyparsing.delimitedList(attrib, delim=',') + \
123                 Suppress(Optional(',') + '}')
124         item = Group(string.setResultsName('name') +
125                 Dict(block).setResultsName('props'))
126
127         itemlist = pyparsing.Dict(
128                 pyparsing.delimitedList(item, delim=',') +
129                 Suppress(Optional(',')))
130
131         for item in itemlist.parseFile(fh):
132             yield ( cls(item.name, item.props.asDict()) )
133     
134
135     @classmethod
136     def loadall(cls, fh):
137         return list(cls.load(fh))
138
139 class Thing (object):
140     "An item, creature, or piece of scenery."
141     def __init__(self, location=None):
142         self.location = None
143         self.place(location)
144     def invalidate(self):
145         self.location.invalidate(important = False)
146     def is_creature(self):
147         return False
148     def is_item(self):
149         return False
150     def is_scenery(self):
151         return False
152     def level(self):
153         try:
154             return self.location.level()
155         except AttributeError:
156             return None
157     def place(self, location):
158         if self.location is not None:
159             self.location.remove(self)
160         self.location = location
161         if location is not None:
162             self.location.add(self)
163         return True
164     def try_move(self, newloc):
165         if newloc and newloc.can_hold(self):
166             self.place(newloc)
167             return True
168         else:
169             return False
170
171 class Item(Thing):
172     wielded = None
173     slot = None
174     def intrinsics(self):
175         return [ Intrinsic(i) for i in self.intrinsic.split(" ") if i ]
176     def is_item(self):
177         return True
178     def render(self, pic=None):
179         return (self.ascii, self.material.attr())
180     def wieldable(self):
181         return self.slot is not None
182     def place(self, location):
183         if self.wielded:
184             if not self.unwield():
185                 return False
186         return super(Item, self).place(location)
187     def unwield(self):
188         if self.wielded:
189             # Have the holder unwield it
190             return self.wielded[0].unwield(self)
191