git.fiddlerwoaroof.com
Browse code

beginning of mod system

Ed L authored on 27/07/2012 03:34:01
Showing 5 changed files
... ...
@@ -109,14 +109,22 @@ class GameBase:
109 109
 
110 110
 		libtcod.console_blit(self.panel, 0,0, self.SCREEN_WIDTH,self.PANEL_HEIGHT, 0,0, self.PANEL_Y)
111 111
 
112
-	def inventory_menu(self, header):
113
-		data = [(item.display_name, item.ident) for item in self.player.get_items()]
112
+	def Inventory_menu(self, header, items):
113
+		data = [(item.display_name, item.ident) for item in items]
114 114
 		display = [x[0] for x in data]
115 115
 		index = self.menu(header, display, self.INVENTORY_WIDTH)
116
+		return index, data
116 117
 
118
+	def inventory_menu(self, header):
119
+		index, data = self.Inventory_menu(header, self.player.get_items())
117 120
 		if index is not None:
118 121
 			return self.player.get_item(data[index][1])
119 122
 
123
+	def mod_menu(self, header):
124
+		index, data = self.Inventory_menu(header, self.player.get_mods())
125
+		if index is not None:
126
+			return self.player.get_mod(data[index][1])
127
+
120 128
 	def get_names_under_mouse(self):
121 129
 		x,y = self.mouse.cx, self.mouse.cy
122 130
 		names = ', '.join(
... ...
@@ -132,6 +140,12 @@ class GameBase:
132 140
 
133 141
 
134 142
 	def menu(self, header, options, width, back_color=libtcod.black, fore_color=libtcod.white):
143
+		import debug
144
+		print '------\n'
145
+		print debug._get_last_module(100)
146
+		print 'menu(', self, header, options, width, back_color, fore_color, ')'
147
+		print '------\n'
148
+
135 149
 		if self.con is None: self.con = 0
136 150
 		if len(options) > 26: raise ValueError('too many items')
137 151
 
... ...
@@ -140,6 +154,8 @@ class GameBase:
140 154
 		header_height = libtcod.console_get_height_rect(con, 0,0, width, self.SCREEN_HEIGHT, header)
141 155
 		height = len(options) + header_height
142 156
 		window = libtcod.console_new(width, height)
157
+		print 'window id is:', window
158
+		print
143 159
 
144 160
 		libtcod.console_set_default_foreground(window, fore_color)
145 161
 		libtcod.console_print_rect(window, 0,0, width,height, header)
... ...
@@ -152,13 +168,18 @@ class GameBase:
152 168
 
153 169
 		x = self.SCREEN_WIDTH/2 - width/2
154 170
 		y = self.SCREEN_HEIGHT/2 - height/2
155
-		libtcod.console_blit(window, 0,0, width,height, 0, x,y, 1.0, 0.7)
171
+		libtcod.console_blit(window, 0,0, width,height, 0, x,y, 1.0, 0.9)
156 172
 
157 173
 		key = libtcod.Key()
158 174
 		mouse = libtcod.Mouse()
159 175
 		libtcod.console_flush()
160 176
 		libtcod.sys_wait_for_event(libtcod.KEY_PRESSED, key, mouse, True)
161 177
 
178
+		libtcod.console_clear(window)
179
+		libtcod.console_blit(window, 0,0, width,height, 0, x,y, 1.0, 0.9)
180
+		libtcod.console_delete(window)
181
+		libtcod.console_flush()
182
+
162 183
 		index = key.c - ord('a')
163 184
 		if index >= 0 and index < len(options): return index
164 185
 		return None
... ...
@@ -1,4 +1,5 @@
1 1
 from __future__ import division
2
+import collections
2 3
 import os.path
3 4
 import glob
4 5
 import yaml
... ...
@@ -14,8 +15,13 @@ from main import Game
14 15
 
15 16
 class Item(object):
16 17
 	stack_limit = 5
18
+	potency = None
19
+	item_class = None
20
+	distance = None
21
+	probability = 1
22
+
17 23
 	def __init__(self, stackable=False):
18
-		self.stacks_with = []
24
+		self.mods = collections.defaultdict(set)
19 25
 
20 26
 	def __new__(*args):
21 27
 		res = object.__new__(*args)
... ...
@@ -29,6 +35,24 @@ class Item(object):
29 35
 		self.user = None
30 36
 		return self.owner
31 37
 
38
+	def modify(self, mod):
39
+		undo = mod.modify(self)
40
+		self.mods[mod.name].add(mod)
41
+		self.owner.name = self.name
42
+
43
+	def unmodify(self, mod):
44
+		if hasattr(mod, 'upper'):
45
+			mods = self.mods[mod]
46
+		else:
47
+			mods = self.mods[mod.name]
48
+		if mods != set():
49
+			mod = mods.pop()
50
+			mod.revert(self)
51
+			self.owner.name = self.name
52
+			if mods == set():
53
+				self.mods.pop(mod.name)
54
+			print 'self.mods', self.mods
55
+
32 56
 class ItemLoader(object):
33 57
 	def __init__(self, dir):
34 58
 		self.dir = dir
... ...
@@ -40,6 +64,8 @@ class ItemLoader(object):
40 64
 				self.load_item(doc)
41 65
 
42 66
 	def load_item(self, doc):
67
+		if doc is None: return
68
+
43 69
 		_color = doc.get('color', None)
44 70
 		if _color is None:
45 71
 			_color = libtcod.green
... ...
@@ -61,7 +87,8 @@ class ItemLoader(object):
61 87
 			char = doc.get('char', '!')
62 88
 			color = _color
63 89
 			stack_limit = doc.get('stack_limit', Item.stack_limit)
64
-			stacks_with = doc.get('stacks_with', ())
90
+			potency = doc.get('potency')
91
+			distance = doc.get('distance')
65 92
 
66 93
 
67 94
 @Game.register_item_type(5)
... ...
@@ -69,16 +96,18 @@ class HealingPotion(Item):
69 96
 	name = 'Healing potion'
70 97
 	char = '\x03'
71 98
 	color = libtcod.violet
99
+	potency = 10
100
+	item_class = 'healing'
72 101
 	def use(self):
73 102
 		fighter = self.user.fighter
74 103
 
75 104
 		result = True
76 105
 		if fighter.hp == fighter.max_hp:
77
-			self.game.message('You\'re full, can\'t heal', libtcod.red)
106
+			self.game.message('Full health, can\'t heal', libtcod.red)
78 107
 			result = False
79 108
 		else:
80 109
 			self.game.message('Healing...')
81
-			fighter.heal(10)
110
+			fighter.heal(self.potency)
82 111
 
83 112
 		return result
84 113
 
... ...
@@ -87,11 +116,14 @@ class SuperHealingPotion(Item):
87 116
 	name = 'Super healing potion'
88 117
 	char = '\x03'
89 118
 	color = libtcod.yellow
119
+	probability = .5
120
+	potency = 10
121
+	item_class = 'healing'
90 122
 	def use(self):
91 123
 		fighter = self.user.fighter
92
-		if random.random() < .75:
93
-			fighter.max_hp += 10
94
-			fighter.heal(10)
124
+		if random.random() < self.probability:
125
+			fighter.max_hp += self.potency
126
+		fighter.heal(self.potency)
95 127
 		return True
96 128
 
97 129
 @Game.register_item_type(1)
... ...
@@ -99,6 +131,7 @@ class Confusion(Item):
99 131
 	name = 'Confusion'
100 132
 	char = 'c'
101 133
 	color=libtcod.dark_chartreuse
134
+	item_class = 'monster defense'
102 135
 	def use(self):
103 136
 		monster = monsters.get_closest_monster(self.user)
104 137
 
... ...
@@ -117,10 +150,12 @@ class Strengthen(Item):
117 150
 	name = 'Strengthen'
118 151
 	char = 's'
119 152
 	color = libtcod.chartreuse
153
+	item_class = 'attack'
154
+	potency = 20
120 155
 	def use(self):
121 156
 		if self.user.fighter:
122 157
 			self.game.message('%s feels a surge of strength' % self.user.name)
123
-			self.user.fighter.stat_adjust(20, self.adj)
158
+			self.user.fighter.stat_adjust(self.potency, self.adj)
124 159
 		return True
125 160
 
126 161
 	def adj(self, owner):
... ...
@@ -135,10 +170,12 @@ class Protect(Item):
135 170
 	name = 'Protect'
136 171
 	char = 'p'
137 172
 	color = libtcod.chartreuse
173
+	item_class = 'defense'
174
+	potency = 15
138 175
 	def use(self):
139 176
 		if self.user.fighter:
140 177
 			self.game.message('%s is surrounded by a protecting aura' % self.user.name)
141
-			self.user.fighter.stat_adjust(15, self.adj)
178
+			self.user.fighter.stat_adjust(self.potency, self.adj)
142 179
 		return True
143 180
 
144 181
 	def adj(self, owner):
... ...
@@ -153,12 +190,14 @@ class LightningBolt(Item):
153 190
 	name = 'Lightning Bolt'
154 191
 	char = 'z'
155 192
 	color = libtcod.darkest_red
193
+	item_class = 'attack'
194
+	potency = 13
156 195
 	def use(self):
157 196
 		monster = monsters.get_closest_monster(self.user)
158 197
 		result = False
159 198
 		if monster and self.user.can_see(monster.x, monster.y):
160 199
 			self.game.message('Monster %s has been struck by lightning' % monster.name)
161
-			monster.fighter.take_damage(13)
200
+			monster.fighter.take_damage(self.potency)
162 201
 			result = True
163 202
 		else:
164 203
 			self.game.message('No target')
... ...
@@ -169,22 +208,23 @@ class Jump(Item):
169 208
 	name = 'Jump'
170 209
 	char = 'j'
171 210
 	color= libtcod.dark_green
172
-	jump_distance = 3
211
+	distance = 3
212
+	item_class = 'movement'
173 213
 	def use(self):
174 214
 		self.game.select(self.jump)
175 215
 		return True
176 216
 	def jump(self, x,y):
177 217
 		dist = self.user.distance(x,y)
178 218
 
179
-		if dist <= self.jump_distance:
219
+		if dist <= self.distance:
180 220
 			self.user.x, self.user.y = x,y
181 221
 			self.game.message('you are transported to a new place')
182
-		elif random.random() < self.jump_distance/dist:
222
+		elif random.random() < self.distance/dist:
183 223
 			self.user.x, self.user.y = x,y
184 224
 			self.game.message('you strain all your power to move %d squares' % int(dist))
185 225
 		else:
186 226
 			self.game.message('you didn\'t make it')
187
-			self.user.fighter.take_damage( int(round(2 * dist/self.jump_distance)) )
227
+			self.user.fighter.take_damage( int(round(2 * dist/self.distance)) )
188 228
 
189 229
 @Game.register_item_type(3)
190 230
 class Acquire(Item):
... ...
@@ -192,6 +232,7 @@ class Acquire(Item):
192 232
 	char = 'a'
193 233
 	color= libtcod.dark_green
194 234
 	effect_distance = 5
235
+	item_class = 'pickup'
195 236
 	def use(self):
196 237
 		self.game.message('what do you want?')
197 238
 		self.game.select(self.get)
... ...
@@ -206,6 +247,8 @@ class Smite(Item):
206 247
 	name = 'Smite'
207 248
 	char = '\x0f'
208 249
 	color = libtcod.red
250
+	item_class = 'attack'
251
+	potency = 10
209 252
 	def use(self):
210 253
 		self.game.select(self.smite)
211 254
 		return True
... ...
@@ -213,7 +256,7 @@ class Smite(Item):
213 256
 	def smite(self, x,y):
214 257
 		monster = monsters.monster_at(x,y)
215 258
 		if monster:
216
-			monster.fighter.take_damage(10)
259
+			monster.fighter.take_damage(self.potency)
217 260
 			if monster.fighter:
218 261
 				self.game.message('%s is smitten, he only retains %s hp' % (monster.name, monster.fighter.hp))
219 262
 			else:
... ...
@@ -226,6 +269,8 @@ class Fireball(Item):
226 269
 	char = '*'
227 270
 	color = libtcod.darker_red
228 271
 	effect_radius = 5
272
+	potency = (20,6)
273
+	item_class = 'splash attack'
229 274
 
230 275
 	def use(self):
231 276
 		self.game.select(self.smite)
... ...
@@ -233,16 +278,18 @@ class Fireball(Item):
233 278
 
234 279
 	def smite(self, x,y):
235 280
 		if random.random() < .1:
281
+			self.game.message('the fireball is amazingly effective', libtcod.green)
236 282
 			self.effect_radius *= 2
237 283
 
284
+		direct_damage, splash_damage = self.potency
238 285
 		strikes = []
239 286
 		for obj in self.owner.level.objects:
240 287
 			if obj.fighter and obj is not self.user:
241 288
 				if (obj.x, obj.y) == (x,y):
242 289
 					self.game.message('%s takes a direct hit from the fireball' % obj.name)
243
-					obj.fighter.take_damage(20)
290
+					obj.fighter.take_damage(direct_damage)
244 291
 				elif obj.distance(x,y) < self.effect_radius:
245
-					obj.fighter.take_damage(6)
292
+					obj.fighter.take_damage(splash_damage)
246 293
 					if obj.fighter:
247 294
 						strikes.append('%s %s' % (obj.name, obj.fighter.hp))
248 295
 					else:
... ...
@@ -250,17 +250,34 @@ if __name__ == 'main':
250 250
 
251 251
 		@mvkeyhandler.handle('i')
252 252
 		def mvkeyhandler(self):
253
-			item = self.inventory_menu('choose item\n')
253
+			item = self.inventory_menu('Choose item to use\n')
254 254
 			if item is not None:
255 255
 				item.bind_game(self)
256 256
 				self.player.use(item)
257 257
 
258 258
 		@mvkeyhandler.handle('d')
259 259
 		def mvkeyhandler(self):
260
-			chosen_item = self.inventory_menu('Choose the item to drop:')
260
+			chosen_item = self.inventory_menu('Choose item to drop\n')
261 261
 			if chosen_item is not None:
262 262
 				self.player.drop(chosen_item.owner)
263 263
 
264
+		@mvkeyhandler.handle('n')
265
+		def mvkeyhandler(self):
266
+			chosen_item = self.inventory_menu('Choose item to unmod\n')
267
+			if chosen_item is not None:
268
+				data = chosen_item.mods.keys()
269
+				index = self.menu('Choose \nmod to \nundo\n', data, self.INVENTORY_WIDTH)
270
+				if index is not None:
271
+					self.player.unmodify(chosen_item.name, data[index])
272
+
273
+		@mvkeyhandler.handle('m')
274
+		def mvkeyhandler(self):
275
+			chosen_item = self.inventory_menu('Choose item to mod\n')
276
+			if chosen_item is not None:
277
+				chosen_mod = self.mod_menu('Choose mod to apply\n')
278
+				if chosen_mod is not None:
279
+					self.player.modify(chosen_item.name, chosen_mod.name)
280
+
264 281
 		@mvkeyhandler.handle('g')
265 282
 		def mvkeyhandler(self):
266 283
 			for obj in self.level.iter_objects():
267 284
new file mode 100644
... ...
@@ -0,0 +1,27 @@
1
+import math
2
+class Mod(object):
3
+	'''An undoable change to some object or item'''
4
+
5
+	def __init__(self):
6
+		pass
7
+
8
+	def modify(self, target):
9
+		pass
10
+
11
+	def revert(self, target):
12
+		pass
13
+
14
+class Boost(Mod):
15
+	name = 'boost'
16
+
17
+	def modify(self, target):
18
+		target.potency *= 1.5
19
+		result = math.ceil(target.potency)
20
+		target.potency = int(result)
21
+		target.name = 'boosted %s' % target.name
22
+	def revert(self, target):
23
+		target.potency /= 1.5
24
+		result = math.ceil(target.potency)
25
+		target.potency = int(result)
26
+		target.name = target.name.split(' ', 1)[1]
27
+
... ...
@@ -1,5 +1,7 @@
1
+import contextlib
1 2
 import libtcodpy as libtcod
2 3
 from objects import Object, Fighter
4
+import mods
3 5
 
4 6
 def get_pos_pair(x,y):
5 7
 	if y is None:
... ...
@@ -83,6 +85,7 @@ class Inventory(object):
83 85
 		self.add_item(v)
84 86
 
85 87
 	def add_item(self, item):
88
+		print 'add_item', item.name
86 89
 		slot = self.objects.setdefault(item.name, [Slot()])
87 90
 		while not slot[-1].add_item(item):
88 91
 			print 'add slot'
... ...
@@ -111,6 +114,13 @@ class Player(Object):
111 114
 
112 115
 		map.player = self
113 116
 		self.inventory = Inventory()
117
+		self.mods = Inventory()
118
+
119
+		class Item:
120
+			stack_limit = 5
121
+		obj = Object(None, con, None,None, 'b', 'boost', color, item=Item())
122
+		obj.mod = mods.Boost()
123
+		self.mods.add_item(obj)
114 124
 
115 125
 	def draw(self, player=None):
116 126
 		if player is None:
... ...
@@ -141,10 +151,34 @@ class Player(Object):
141 151
 		if success:
142 152
 			del self.inventory[item.name]
143 153
 
154
+	@contextlib.contextmanager
155
+	def recategorize_item(self, item_name):
156
+		item = self.inventory[item_name]
157
+		yield item
158
+		del self.inventory[item_name]
159
+		self.inventory.add_item(item)
160
+
161
+	def modify(self, item_name, mod_name):
162
+		mod = self.mods[mod_name]
163
+		with self.recategorize_item(item_name) as item:
164
+			item.item.modify(mod.mod)
165
+			del self.mods[mod_name]
166
+		print list(self.mods)
167
+
168
+	def unmodify(self, item_name, mod_name):
169
+		with self.recategorize_item(item_name) as item:
170
+			item.item.unmodify(mod_name)
171
+		print list(self.mods)
172
+
173
+	def get_mod(self, index):
174
+		return self.mods[index].mod
175
+	def get_mod_names(self):
176
+		return [item.display_name for item in self.mods]
177
+	def get_mods(self):
178
+		return [item for item in self.mods]
144 179
 
145 180
 	def get_item(self, index):
146 181
 		return self.inventory[index].item
147
-
148 182
 	def get_item_names(self):
149 183
 		return [item.display_name for item in self.inventory]
150 184
 	def get_items(self):