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