git.fiddlerwoaroof.com
Browse code

implemented item stacks

Ed L authored on 26/07/2012 04:23:38
Showing 8 changed files
... ...
@@ -51,8 +51,11 @@ class GameBase:
51 51
 	color_light_ground = libtcod.Color(200,200,200)
52 52
 
53 53
 
54
-	def message(self, msg, color):
55
-		utilities.message(self.game_msgs, self.MSG_HEIGHT, self.MSG_WIDTH, msg, color)
54
+	def message(self, msg, color=None):
55
+		if color is not None:
56
+			utilities.message(self.game_msgs, self.MSG_HEIGHT, self.MSG_WIDTH, msg, color)
57
+		else:
58
+			utilities.message(self.game_msgs, self.MSG_HEIGHT, self.MSG_WIDTH, msg)
56 59
 
57 60
 	def __init__(self, app_name='test app', screen_width=SCREEN_WIDTH, screen_height=SCREEN_HEIGHT):
58 61
 		print '__init__'
... ...
@@ -150,10 +153,12 @@ class GameBase:
150 153
 		libtcod.console_blit(self.panel, 0,0, self.SCREEN_WIDTH,self.PANEL_HEIGHT, 0,0, self.PANEL_Y)
151 154
 
152 155
 	def inventory_menu(self, header):
153
-		index = self.menu(self.con, header, self.player.get_item_names(), self.INVENTORY_WIDTH)
156
+		data = [(item.display_name, item.ident) for item in self.player.get_items()]
157
+		display = [x[0] for x in data]
158
+		index = self.menu(self.con, header, display, self.INVENTORY_WIDTH)
154 159
 
155 160
 		if index is not None:
156
-			return self.player.get_item(index)
161
+			return self.player.get_item(data[index][1])
157 162
 
158 163
 	def get_names_under_mouse(self):
159 164
 		x,y = self.mouse.cx, self.mouse.cy
... ...
@@ -1,4 +1,7 @@
1 1
 from __future__ import division
2
+import os.path
3
+import glob
4
+import yaml
2 5
 
3 6
 import libtcodpy as libtcod
4 7
 import game
... ...
@@ -6,13 +9,59 @@ import objects
6 9
 import utilities
7 10
 import monsters
8 11
 import random
9
-from main import game_instance, Game
12
+from main import Game
10 13
 
11 14
 
12 15
 class Item(object):
16
+	def __init__(self, stackable=False):
17
+		self.stackable = stackable
18
+		self.stacks_with = []
19
+		self.stack_limit = 5
20
+
13 21
 	def __new__(*args):
14 22
 		res = object.__new__(*args)
15 23
 		return res
24
+	def bind_game(self, game):
25
+		self.game = game
26
+	def bind_user(self, user):
27
+		self.user = user
28
+		return self.owner
29
+	def free_user(self):
30
+		self.user = None
31
+		return self.owner
32
+
33
+class ItemLoader(object):
34
+	def __init__(self, dir):
35
+		self.dir = dir
36
+
37
+	def load_items(self):
38
+		for fn in glob.glob(os.path.join(self.dir,'*.yml')):
39
+			print 'fn', fn
40
+			for doc in yaml.safe_load_all(file(fn)):
41
+				self.load_item(doc)
42
+
43
+	def load_item(self, doc):
44
+		_color = doc.get('color', None)
45
+		if _color is None:
46
+			_color = libtcod.green
47
+		elif hasattr(_color, 'upper'):
48
+			_color = getattr(libtcod, _color)
49
+		else:
50
+			_color = libtcod.Color(*_color)
51
+
52
+		item_class = doc['item_class']
53
+		module, clas = item_class.rsplit('.',1)
54
+		module = __import__(module)
55
+		item_class = getattr(module, clas)
56
+		print 'item class:', item_class
57
+
58
+		print 'loading', doc
59
+		@Game.register_item_type(doc['spawn_chance'])
60
+		class LoadedItem(item_class):
61
+			name = doc.get('item_description')
62
+			char = doc.get('char', '!')
63
+			color = _color
64
+
16 65
 
17 66
 @Game.register_item_type(5)
18 67
 class HealingPotion(Item):
... ...
@@ -24,10 +73,10 @@ class HealingPotion(Item):
24 73
 
25 74
 		result = True
26 75
 		if fighter.hp == fighter.max_hp:
27
-			game.message('You\'re full, can\'t heal', libtcod.red)
76
+			self.game.message('You\'re full, can\'t heal', libtcod.red)
28 77
 			result = False
29 78
 		else:
30
-			game.message('Healing...')
79
+			self.game.message('Healing...')
31 80
 			fighter.heal(10)
32 81
 
33 82
 		return result
... ...
@@ -54,7 +103,7 @@ class Confusion(Item):
54 103
 
55 104
 		result = False
56 105
 		if monster is not None:
57
-			game.message('%s becomes confused' % monster.name)
106
+			self.game.message('%s becomes confused' % monster.name)
58 107
 			monsters.ConfusedMonster(random.randrange(10,18)).attach(
59 108
 				monster
60 109
 			)
... ...
@@ -69,13 +118,13 @@ class Strengthen(Item):
69 118
 	color = libtcod.chartreuse
70 119
 	def use(self):
71 120
 		if self.user.fighter:
72
-			game.message('%s feels a surge of strength' % self.user.name)
121
+			self.game.message('%s feels a surge of strength' % self.user.name)
73 122
 			self.user.fighter.stat_adjust(20, self.adj)
74 123
 		return True
75 124
 
76 125
 	def adj(self, owner):
77 126
 		return (
78
-			lambda _: game.message('The surge of strength has subsided'),
127
+			lambda _: self.game.message('The surge of strength has subsided'),
79 128
 			owner.fighter.defense,
80 129
 			owner.fighter.power+3
81 130
 		)
... ...
@@ -87,13 +136,13 @@ class Protect(Item):
87 136
 	color = libtcod.chartreuse
88 137
 	def use(self):
89 138
 		if self.user.fighter:
90
-			game.message('%s is surrounded by a protecting aura' % self.user.name)
91
-			self.user.fighter.stat_adjust(10, self.adj)
139
+			self.game.message('%s is surrounded by a protecting aura' % self.user.name)
140
+			self.user.fighter.stat_adjust(15, self.adj)
92 141
 		return True
93 142
 
94 143
 	def adj(self, owner):
95 144
 		return (
96
-			lambda _: game.message('The protecting aura dissipates'),
145
+			lambda _: self.game.message('The protecting aura dissipates'),
97 146
 			owner.fighter.defense+6,
98 147
 			owner.fighter.power
99 148
 		)
... ...
@@ -107,11 +156,11 @@ class LightningBolt(Item):
107 156
 		monster = monsters.get_closest_monster(self.user)
108 157
 		result = False
109 158
 		if monster and self.user.can_see(monster.x, monster.y):
110
-			game.message('Monster %s has been struck by lightning' % monster.name)
159
+			self.game.message('Monster %s has been struck by lightning' % monster.name)
111 160
 			monster.fighter.take_damage(13)
112 161
 			result = True
113 162
 		else:
114
-			game.message('No target')
163
+			self.game.message('No target')
115 164
 		return result
116 165
 
117 166
 @Game.register_item_type(5)
... ...
@@ -121,19 +170,19 @@ class Jump(Item):
121 170
 	color= libtcod.dark_green
122 171
 	jump_distance = 3
123 172
 	def use(self):
124
-		game_instance.select(self.jump)
173
+		self.game.select(self.jump)
125 174
 		return True
126 175
 	def jump(self, x,y):
127 176
 		dist = self.user.distance(x,y)
128 177
 
129 178
 		if dist <= self.jump_distance:
130 179
 			self.user.x, self.user.y = x,y
131
-			game.message('you are transported to a new place')
180
+			self.game.message('you are transported to a new place')
132 181
 		elif random.random() < self.jump_distance/dist:
133 182
 			self.user.x, self.user.y = x,y
134
-			game.message('you strain all your power to move %d squares' % int(dist))
183
+			self.game.message('you strain all your power to move %d squares' % int(dist))
135 184
 		else:
136
-			game.message('you didn\'t make it')
185
+			self.game.message('you didn\'t make it')
137 186
 			self.user.fighter.take_damage( int(round(2 * dist/self.jump_distance)) )
138 187
 
139 188
 @Game.register_item_type(3)
... ...
@@ -143,8 +192,8 @@ class Acquire(Item):
143 192
 	color= libtcod.dark_green
144 193
 	effect_distance = 5
145 194
 	def use(self):
146
-		game.message('what do you want?')
147
-		game_instance.select(self.get)
195
+		self.game.message('what do you want?')
196
+		self.game.select(self.get)
148 197
 		return True
149 198
 	def get(self, x,y):
150 199
 		if self.user.distance(x,y) < self.effect_distance:
... ...
@@ -157,7 +206,7 @@ class Smite(Item):
157 206
 	char = '\x0f'
158 207
 	color = libtcod.red
159 208
 	def use(self):
160
-		game_instance.select(self.smite)
209
+		self.game.select(self.smite)
161 210
 		return True
162 211
 
163 212
 	def smite(self, x,y):
... ...
@@ -165,9 +214,9 @@ class Smite(Item):
165 214
 		if monster:
166 215
 			monster.fighter.take_damage(10)
167 216
 			if monster.fighter:
168
-				game.message('%s is smitten, he only retains %s hp' % (monster.name, monster.fighter.hp))
217
+				self.game.message('%s is smitten, he only retains %s hp' % (monster.name, monster.fighter.hp))
169 218
 			else:
170
-				game.message('%s thought it better to go elsewhere' % monster.name)
219
+				self.game.message('%s thought it better to go elsewhere' % monster.name)
171 220
 
172 221
 
173 222
 @Game.register_item_type(2)
... ...
@@ -178,7 +227,7 @@ class Fireball(Item):
178 227
 	effect_radius = 3
179 228
 
180 229
 	def use(self):
181
-		game_instance.select(self.smite)
230
+		self.game.select(self.smite)
182 231
 		return True
183 232
 
184 233
 	def smite(self, x,y):
... ...
@@ -189,7 +238,7 @@ class Fireball(Item):
189 238
 		for obj in self.owner.level.objects:
190 239
 			if obj.fighter and obj is not self.user:
191 240
 				if (obj.x, obj.y) == (x,y):
192
-					game.message('%s takes a direct hit from the fireball' % obj.name)
241
+					self.game.message('%s takes a direct hit from the fireball' % obj.name)
193 242
 					obj.fighter.take_damage(20)
194 243
 				elif obj.distance(x,y) < self.effect_radius:
195 244
 					obj.fighter.take_damage(6)
... ...
@@ -197,5 +246,5 @@ class Fireball(Item):
197 246
 						strikes.append('%s %s' % (obj.name, obj.fighter.hp))
198 247
 					else:
199 248
 						strikes.append('%s dead' % obj.name)
200
-		game.message('The names of those who were to close for comfort: %s' % ', '.join(strikes))
249
+		self.game.message('The names of those who were to close for comfort: %s' % ', '.join(strikes))
201 250
 
... ...
@@ -86,8 +86,8 @@ class Level(object):
86 86
 				cell.explored = True
87 87
 
88 88
 			color = libtcod.black
89
-			#if True:
90
-			if cell.explored:
89
+			if True:
90
+			#if cell.explored:
91 91
 				wall = cell.block_sight
92 92
 				walkable = not cell.blocked
93 93
 
... ...
@@ -57,7 +57,7 @@ if __name__ == 'main':
57 57
 
58 58
 
59 59
 		def player_death(self, player):
60
-			utilities.message(self.game_msgs, self.MSG_HEIGHT, self.MSG_WIDTH, 'You died!')
60
+			self.message('You died!', libtcod.red)
61 61
 			self.game_state = 'dead'
62 62
 			player.char = '%'
63 63
 			player.color = libtcod.dark_red
... ...
@@ -82,7 +82,7 @@ if __name__ == 'main':
82 82
 		def register_item_type(cls, chance):
83 83
 			def _inner(typ):
84 84
 				cls.item_types[typ] = chance
85
-				return cls
85
+				return typ
86 86
 			return _inner
87 87
 
88 88
 
... ...
@@ -90,6 +90,7 @@ if __name__ == 'main':
90 90
 		@classmethod
91 91
 		def register_monster_type(cls, typ, chance):
92 92
 			cls.monster_types[typ] = chance
93
+			return typ
93 94
 
94 95
 		@property
95 96
 		def level(self):
... ...
@@ -198,6 +199,7 @@ if __name__ == 'main':
198 199
 		def mvkeyhandler(self):
199 200
 			item = self.inventory_menu('choose item\n')
200 201
 			if item is not None:
202
+				item.bind_game(self)
201 203
 				self.player.use(item)
202 204
 
203 205
 		@mvkeyhandler.handle('d')
... ...
@@ -298,8 +300,11 @@ if __name__ == '__main__':
298 300
 	from functools import partial
299 301
 	render_bar = partial(render_bar, game_instance.panel)
300 302
 
301
-	a = MonsterLoader(os.path.join('.','data','monsters'))
302
-	a.load_monsters()
303
+	ml = MonsterLoader(os.path.join('.','data','monsters'))
304
+	ml.load_monsters()
305
+
306
+	il = ItemLoader(os.path.join('.','data','items'))
307
+	il.load_items()
303 308
 
304 309
 	game_instance.setup_map()
305 310
 	game_instance.main()
... ...
@@ -216,6 +216,14 @@ class Smoother(AutomataEngine):
216 216
 		else:
217 217
 			return cell
218 218
 
219
+class NewSmoother(AutomataEngine):
220
+	def rule(self, x,y, cell):
221
+		avg = self.sum_area( (x,y), 2 ) / 16
222
+		if avg < .5:
223
+			return 0
224
+		else:
225
+			return 1
226
+
219 227
 import collections
220 228
 class Map(collections.MutableSequence):
221 229
 	def __init__(self, width, height, con, level):
... ...
@@ -258,7 +266,7 @@ class Map(collections.MutableSequence):
258 266
 	def __getitem__(self, k):
259 267
 		return self.data[k]
260 268
 	def __setitem__(self, k,v):
261
-		return self.data[k][v]
269
+		self.data[k] = v
262 270
 	def __delitem__(self, k):
263 271
 		del self.data[k]
264 272
 
... ...
@@ -143,7 +143,10 @@ class MonsterLoader(object):
143 143
 			(lambda doc:
144 144
 				lambda map,level,con,x,y: objects.Object( map, con, x,y,
145 145
 					doc['char'],
146
-					doc.get('name_fmt', '%s the %s') % (libtcod.namegen_generate(doc['namegen_class']).capitalize(), doc['race_name'].capitalize()),
146
+					doc.get('name_fmt', '%s the %s') % (
147
+						libtcod.namegen_generate(doc['namegen_class']).capitalize(),
148
+						doc['race_name'].capitalize()
149
+					),
147 150
 					color,
148 151
 					True,
149 152
 					fighter=objects.Fighter(
... ...
@@ -11,6 +11,91 @@ def get_pos_pair(x,y):
11 11
 			x,y = x.pos
12 12
 	return x,y
13 13
 
14
+import collections
15
+class Slot(object):
16
+	def __init__(self):
17
+		self.limit = None
18
+		self.items = []
19
+
20
+	@property
21
+	def display_name(self):
22
+		if self.items != []:
23
+			return '%s (x%s)' % (self.items[0].name, len(self.items))
24
+
25
+	@property
26
+	def ident(self):
27
+		if self.items != []:
28
+			return self.items[0].name
29
+
30
+
31
+	def empty(self):
32
+		return len(self.items) == 0
33
+
34
+	def _add_item(self, item):
35
+		self.items.append(item)
36
+		return True
37
+
38
+	def add_item(self, item):
39
+		result = False
40
+		if self.limit is None or len(self.items) <= self.limit:
41
+			if self.items == []:
42
+				self.limit = item.item.stack_limit
43
+				print 'no items'
44
+				return self._add_item(item)
45
+			elif (len(self.items) < self.limit) and (self.ident == item.name):
46
+				print 'add_items %d' % len(self.items)
47
+				return self._add_item(item)
48
+			elif self.ident != item.name:
49
+				raise ValueError('Cannot stack %s with %s' % (self.ident, item.ident))
50
+		return result
51
+
52
+	def get_item(self, default=None):
53
+		result = default
54
+		if self.items != []:
55
+			result = self.items[-1]
56
+		return result
57
+
58
+	def consume(self):
59
+		self.items.pop()
60
+
61
+
62
+
63
+class Inventory(object):
64
+	def __init__(self):
65
+		self.objects = {}
66
+
67
+	def __iter__(self):
68
+		for v in self.objects.itervalues():
69
+			for i in v:
70
+				yield i
71
+
72
+	def __len__(self):
73
+		return sum(len(x) for x in self.objects.values())
74
+
75
+	def __contains__(self, it):
76
+		return it.ident in self.objects
77
+
78
+	def __getitem__(self, k):
79
+		return self.objects[k][-1].get_item()
80
+
81
+	def __setitem__(self, k,v):
82
+		if v.ident != k: raise ValueError('Inventory key must equal the item\'s name')
83
+		self.add_item(v)
84
+
85
+	def add_item(self, item):
86
+		slot = self.objects.setdefault(item.name, [Slot()])
87
+		while not slot[-1].add_item(item):
88
+			print 'add slot'
89
+			slot.append(Slot())
90
+
91
+	def __delitem__(self, k):
92
+		self.objects[k][-1].consume()
93
+		while self.objects[k] != [] and self.objects[k][-1].empty():
94
+			self.objects[k].pop()
95
+		else:
96
+			if self.objects[k] == []:
97
+				del self.objects[k]
98
+
14 99
 class Player(Object):
15 100
 	def triggers_recompute(func):
16 101
 		def _inner(self, *a, **kw):
... ...
@@ -25,7 +110,7 @@ class Player(Object):
25 110
 		)
26 111
 
27 112
 		map.player = self
28
-		self.inventory = []
113
+		self.inventory = Inventory()
29 114
 
30 115
 	def draw(self, player=None):
31 116
 		if player is None:
... ...
@@ -33,38 +118,37 @@ class Player(Object):
33 118
 		return Object.draw(self, player)
34 119
 
35 120
 	def pick_up(self, obj):
121
+		# TODO: limit number of items
36 122
 		if len(self.inventory) >= 26:
37 123
 			game.message('Your inventory is full, cannot pick up %s' % obj.name, libtcod.red)
38
-		else:
39
-			self.inventory.append(
40
-				self.level.claim_object(obj)
124
+		elif obj is not None:
125
+			self.inventory.add_item(
126
+				self.level.claim_object(obj).item.bind_user(self) # returns item.owner
41 127
 			)
42 128
 			game.message('you picked up a %s!' % obj.name, libtcod.green)
43
-			obj.item.user = self
44 129
 		return self
45 130
 
46 131
 	def drop(self, obj):
47
-		self.level.objects.insert(0, obj)
132
+		obj = self.inventory[obj.name]
48 133
 		obj.x, obj.y = self.x, self.y
49
-		game.message('you dropped a %s' % obj.name, libtcod.yellow)
134
+		self.level.add_object(obj)
135
+		del self.inventory[obj.name]
50 136
 
51 137
 	def use(self, item):
52 138
 		item.owner.enter_level(self.level)
53 139
 		success = item.use()
54 140
 
55
-		try:
56
-			index = self.inventory.index(item.owner)
57
-		except ValueError:
58
-			index = -1
141
+		if success:
142
+			del self.inventory[item.name]
59 143
 
60
-		if success and index != -1:
61
-			self.inventory.pop(index)
62 144
 
63 145
 	def get_item(self, index):
64 146
 		return self.inventory[index].item
65 147
 
66 148
 	def get_item_names(self):
67
-		return [item.name for item in self.inventory]
149
+		return [item.display_name for item in self.inventory]
150
+	def get_items(self):
151
+		return [item for item in self.inventory]
68 152
 
69 153
 	def tick(self):
70 154
 		if self.fighter:
... ...
@@ -90,6 +90,8 @@ class MovementKeyListener(object):
90 90
 			result = self.handlers[key.vk](*a, **kw) or True
91 91
 		elif key.c in self.char_handlers:
92 92
 			result = self.char_handlers[key.c](*a, **kw) or True
93
+		if result is None:
94
+			result = 'didnt-take-turn'
93 95
 		return (key, result)
94 96
 
95 97
 	def handle(self, key):