Browse code
beginning of mod system
Ed L authored on 27/07/2012 03:34:01
Showing 5 changed files
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 |
|
|
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): |