git.fiddlerwoaroof.com
Browse code

Merge remote-tracking branch 'remotes/fiddlerwoaroof/master'

Conflicts:
README.md

Jonathan Camara authored on 28/07/2012 01:36:47
Showing 12 changed files
... ...
@@ -1,6 +1,20 @@
1
+<<<<<<< HEAD
1 2
 Here it is...
2 3
 
3 4
 Requirements:
4 5
  Python 2.7
5 6
  libtcod 1.5.0 (relies on SDL and zlib)
6 7
  pyYAML 3.10 
8
+=======
9
+Homepage: http://fiddlerwoaroof.github.com/yinjar/
10
+
11
+If you want to modify the code, the files contained under data/ might be a good place to start
12
+
13
+### Prereqs:
14
+
15
+- Python (included on OS X and usually on Linux, install from http://python.org for Windows)
16
+- PyYAML
17
+- libTCOD (included but if on 64-bit linux download from here: doryen.eptalys.net/libtcod/download/)
18
+- SDL (included with libTCOD)
19
+
20
+>>>>>>> remotes/fiddlerwoaroof/master
7 21
new file mode 100644
... ...
@@ -0,0 +1 @@
1
+---
0 2
new file mode 100644
... ...
@@ -0,0 +1,3 @@
1
+---
2
+#screen_width: 155 # widescreen (1280x800)
3
+#screen_height: 90
... ...
@@ -21,51 +21,22 @@ class Cursor(object):
21 21
 		libtcod.console_put_char(self.con, self.x,self.y, ' ', libtcod.BKGND_NONE)
22 22
 
23 23
 class GameBase:
24
-	#actual size of the window
25
-	SCREEN_WIDTH, SCREEN_HEIGHT = 50,70
26
-
27
-	MAP_WIDTH, MAP_HEIGHT = SCREEN_WIDTH, SCREEN_HEIGHT - 17
28
-
29
-	INVENTORY_WIDTH = 50
30
-	BAR_WIDTH = 25
31
-
32
-	PANEL_HEIGHT = SCREEN_HEIGHT - MAP_HEIGHT - 2
33
-	PANEL_Y = SCREEN_HEIGHT - PANEL_HEIGHT
34
-
35
-	MSG_X = BAR_WIDTH + 2
36
-	MSG_WIDTH, MSG_HEIGHT = SCREEN_WIDTH - BAR_WIDTH - 2, PANEL_HEIGHT - 1
37
-
38
-	ROOM_MIN_SIZE, ROOM_MAX_SIZE = 7, 19
39
-
40
-	MAX_ROOMS = 51
41
-
42
-	MAX_ROOM_MONSTERS, MAX_ROOM_ITEMS = 9, 6
43
-
44
-	CONFUSE_NUM_TURNS = 17
45
-
46
-	LIMIT_FPS = 20	#20 frames-per-second maximum
47
-
48
-	color_dark_wall = libtcod.Color(60, 60, 60)
49
-	color_light_wall = libtcod.Color(127,127,127)
50
-	color_dark_ground = libtcod.Color(150,150,150)
51
-	color_light_ground = libtcod.Color(200,200,200)
52
-
53
-
54 24
 	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)
25
+		if color is None:
26
+			color = libtcod.white
27
+		utilities.message(self.game_msgs, self.MSG_HEIGHT, self.MSG_WIDTH, msg)
59 28
 
60
-	def __init__(self, app_name='test app', screen_width=SCREEN_WIDTH, screen_height=SCREEN_HEIGHT):
29
+	def __init__(self, app_name='test app', screen_width=None, screen_height=None):
61 30
 		print '__init__'
31
+		if screen_width is None:
32
+			screen_width, screen_height = self.SCREEN_WIDTH, self.SCREEN_HEIGHT
62 33
 		libtcod.console_init_root(
63 34
 			screen_width, screen_height, app_name, False
64 35
 		)
65 36
 
66 37
 		self.game_msgs = []
67 38
 		global message
68
-		message = functools.partial(utilities.message, self.game_msgs, self.MSG_HEIGHT, self.MSG_WIDTH)
39
+		message = self.message
69 40
 
70 41
 		self.game_state = 'playing'
71 42
 		self.player_action = 'didnt-take-turn'
... ...
@@ -136,30 +107,24 @@ class GameBase:
136 107
 		libtcod.console_set_default_background(self.panel, libtcod.black)
137 108
 		libtcod.console_clear(self.panel)
138 109
 
139
-		#utilities.render_bar(self.panel, 1,1, self.BAR_WIDTH, 'HP',
140
-		#	self.player.fighter.hp,
141
-		#	self.player.fighter.max_hp,
142
-		#	libtcod.red,
143
-		#	libtcod.darker_red
144
-		#)
145
-
146
-		#y = 1
147
-		#for line, color in self.game_msgs:
148
-		#	libtcod.console_set_default_foreground(self.panel, color)
149
-		#	libtcod.console_print_ex(self.panel, self.MSG_X, y, libtcod.BKGND_NONE, libtcod.LEFT, line)
150
-		#	y += 1
151
-
152
-
153 110
 		libtcod.console_blit(self.panel, 0,0, self.SCREEN_WIDTH,self.PANEL_HEIGHT, 0,0, self.PANEL_Y)
154 111
 
155
-	def inventory_menu(self, header):
156
-		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]
157 114
 		display = [x[0] for x in data]
158
-		index = self.menu(self.con, header, display, self.INVENTORY_WIDTH)
115
+		index = self.menu(header, display, self.INVENTORY_WIDTH)
116
+		return index, data
159 117
 
118
+	def inventory_menu(self, header):
119
+		index, data = self.Inventory_menu(header, self.player.get_items())
160 120
 		if index is not None:
161 121
 			return self.player.get_item(data[index][1])
162 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
+
163 128
 	def get_names_under_mouse(self):
164 129
 		x,y = self.mouse.cx, self.mouse.cy
165 130
 		names = ', '.join(
... ...
@@ -174,16 +139,25 @@ class GameBase:
174 139
 
175 140
 
176 141
 
177
-	def menu(self, con, header, options, width):
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
+
149
+		if self.con is None: self.con = 0
178 150
 		if len(options) > 26: raise ValueError('too many items')
179 151
 
152
+		con = self.con
180 153
 
181
-		print con
182 154
 		header_height = libtcod.console_get_height_rect(con, 0,0, width, self.SCREEN_HEIGHT, header)
183 155
 		height = len(options) + header_height
184 156
 		window = libtcod.console_new(width, height)
157
+		print 'window id is:', window
158
+		print
185 159
 
186
-		libtcod.console_set_default_foreground(window, libtcod.white)
160
+		libtcod.console_set_default_foreground(window, fore_color)
187 161
 		libtcod.console_print_rect(window, 0,0, width,height, header)
188 162
 
189 163
 		y = header_height
... ...
@@ -194,13 +168,18 @@ class GameBase:
194 168
 
195 169
 		x = self.SCREEN_WIDTH/2 - width/2
196 170
 		y = self.SCREEN_HEIGHT/2 - height/2
197
-		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)
198 172
 
199 173
 		key = libtcod.Key()
200 174
 		mouse = libtcod.Mouse()
201 175
 		libtcod.console_flush()
202 176
 		libtcod.sys_wait_for_event(libtcod.KEY_PRESSED, key, mouse, True)
203 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
+
204 183
 		index = key.c - ord('a')
205 184
 		if index >= 0 and index < len(options): return index
206 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
... ...
@@ -13,10 +14,14 @@ from main import Game
13 14
 
14 15
 
15 16
 class Item(object):
17
+	stack_limit = 5
18
+	potency = None
19
+	item_class = None
20
+	distance = None
21
+	probability = 1
22
+
16 23
 	def __init__(self, stackable=False):
17
-		self.stackable = stackable
18
-		self.stacks_with = []
19
-		self.stack_limit = 5
24
+		self.mods = collections.defaultdict(set)
20 25
 
21 26
 	def __new__(*args):
22 27
 		res = object.__new__(*args)
... ...
@@ -30,6 +35,24 @@ class Item(object):
30 35
 		self.user = None
31 36
 		return self.owner
32 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
+
33 56
 class ItemLoader(object):
34 57
 	def __init__(self, dir):
35 58
 		self.dir = dir
... ...
@@ -41,6 +64,8 @@ class ItemLoader(object):
41 64
 				self.load_item(doc)
42 65
 
43 66
 	def load_item(self, doc):
67
+		if doc is None: return
68
+
44 69
 		_color = doc.get('color', None)
45 70
 		if _color is None:
46 71
 			_color = libtcod.green
... ...
@@ -61,6 +86,9 @@ class ItemLoader(object):
61 86
 			name = doc.get('item_description')
62 87
 			char = doc.get('char', '!')
63 88
 			color = _color
89
+			stack_limit = doc.get('stack_limit', Item.stack_limit)
90
+			potency = doc.get('potency')
91
+			distance = doc.get('distance')
64 92
 
65 93
 
66 94
 @Game.register_item_type(5)
... ...
@@ -68,16 +96,18 @@ class HealingPotion(Item):
68 96
 	name = 'Healing potion'
69 97
 	char = '\x03'
70 98
 	color = libtcod.violet
99
+	potency = 10
100
+	item_class = 'healing'
71 101
 	def use(self):
72 102
 		fighter = self.user.fighter
73 103
 
74 104
 		result = True
75 105
 		if fighter.hp == fighter.max_hp:
76
-			self.game.message('You\'re full, can\'t heal', libtcod.red)
106
+			self.game.message('Full health, can\'t heal', libtcod.red)
77 107
 			result = False
78 108
 		else:
79 109
 			self.game.message('Healing...')
80
-			fighter.heal(10)
110
+			fighter.heal(self.potency)
81 111
 
82 112
 		return result
83 113
 
... ...
@@ -86,11 +116,14 @@ class SuperHealingPotion(Item):
86 116
 	name = 'Super healing potion'
87 117
 	char = '\x03'
88 118
 	color = libtcod.yellow
119
+	probability = .5
120
+	potency = 10
121
+	item_class = 'healing'
89 122
 	def use(self):
90 123
 		fighter = self.user.fighter
91
-		if random.random() < .75:
92
-			fighter.max_hp += 10
93
-			fighter.heal(10)
124
+		if random.random() < self.probability:
125
+			fighter.max_hp += self.potency
126
+		fighter.heal(self.potency)
94 127
 		return True
95 128
 
96 129
 @Game.register_item_type(1)
... ...
@@ -98,6 +131,7 @@ class Confusion(Item):
98 131
 	name = 'Confusion'
99 132
 	char = 'c'
100 133
 	color=libtcod.dark_chartreuse
134
+	item_class = 'monster defense'
101 135
 	def use(self):
102 136
 		monster = monsters.get_closest_monster(self.user)
103 137
 
... ...
@@ -116,10 +150,12 @@ class Strengthen(Item):
116 150
 	name = 'Strengthen'
117 151
 	char = 's'
118 152
 	color = libtcod.chartreuse
153
+	item_class = 'attack'
154
+	potency = 20
119 155
 	def use(self):
120 156
 		if self.user.fighter:
121 157
 			self.game.message('%s feels a surge of strength' % self.user.name)
122
-			self.user.fighter.stat_adjust(20, self.adj)
158
+			self.user.fighter.stat_adjust(self.potency, self.adj)
123 159
 		return True
124 160
 
125 161
 	def adj(self, owner):
... ...
@@ -134,10 +170,12 @@ class Protect(Item):
134 170
 	name = 'Protect'
135 171
 	char = 'p'
136 172
 	color = libtcod.chartreuse
173
+	item_class = 'defense'
174
+	potency = 15
137 175
 	def use(self):
138 176
 		if self.user.fighter:
139 177
 			self.game.message('%s is surrounded by a protecting aura' % self.user.name)
140
-			self.user.fighter.stat_adjust(15, self.adj)
178
+			self.user.fighter.stat_adjust(self.potency, self.adj)
141 179
 		return True
142 180
 
143 181
 	def adj(self, owner):
... ...
@@ -152,12 +190,14 @@ class LightningBolt(Item):
152 190
 	name = 'Lightning Bolt'
153 191
 	char = 'z'
154 192
 	color = libtcod.darkest_red
193
+	item_class = 'attack'
194
+	potency = 13
155 195
 	def use(self):
156 196
 		monster = monsters.get_closest_monster(self.user)
157 197
 		result = False
158 198
 		if monster and self.user.can_see(monster.x, monster.y):
159 199
 			self.game.message('Monster %s has been struck by lightning' % monster.name)
160
-			monster.fighter.take_damage(13)
200
+			monster.fighter.take_damage(self.potency)
161 201
 			result = True
162 202
 		else:
163 203
 			self.game.message('No target')
... ...
@@ -168,22 +208,23 @@ class Jump(Item):
168 208
 	name = 'Jump'
169 209
 	char = 'j'
170 210
 	color= libtcod.dark_green
171
-	jump_distance = 3
211
+	distance = 3
212
+	item_class = 'movement'
172 213
 	def use(self):
173 214
 		self.game.select(self.jump)
174 215
 		return True
175 216
 	def jump(self, x,y):
176 217
 		dist = self.user.distance(x,y)
177 218
 
178
-		if dist <= self.jump_distance:
219
+		if dist <= self.distance:
179 220
 			self.user.x, self.user.y = x,y
180 221
 			self.game.message('you are transported to a new place')
181
-		elif random.random() < self.jump_distance/dist:
222
+		elif random.random() < self.distance/dist:
182 223
 			self.user.x, self.user.y = x,y
183 224
 			self.game.message('you strain all your power to move %d squares' % int(dist))
184 225
 		else:
185 226
 			self.game.message('you didn\'t make it')
186
-			self.user.fighter.take_damage( int(round(2 * dist/self.jump_distance)) )
227
+			self.user.fighter.take_damage( int(round(2 * dist/self.distance)) )
187 228
 
188 229
 @Game.register_item_type(3)
189 230
 class Acquire(Item):
... ...
@@ -191,6 +232,7 @@ class Acquire(Item):
191 232
 	char = 'a'
192 233
 	color= libtcod.dark_green
193 234
 	effect_distance = 5
235
+	item_class = 'pickup'
194 236
 	def use(self):
195 237
 		self.game.message('what do you want?')
196 238
 		self.game.select(self.get)
... ...
@@ -205,6 +247,8 @@ class Smite(Item):
205 247
 	name = 'Smite'
206 248
 	char = '\x0f'
207 249
 	color = libtcod.red
250
+	item_class = 'attack'
251
+	potency = 10
208 252
 	def use(self):
209 253
 		self.game.select(self.smite)
210 254
 		return True
... ...
@@ -212,7 +256,7 @@ class Smite(Item):
212 256
 	def smite(self, x,y):
213 257
 		monster = monsters.monster_at(x,y)
214 258
 		if monster:
215
-			monster.fighter.take_damage(10)
259
+			monster.fighter.take_damage(self.potency)
216 260
 			if monster.fighter:
217 261
 				self.game.message('%s is smitten, he only retains %s hp' % (monster.name, monster.fighter.hp))
218 262
 			else:
... ...
@@ -224,7 +268,9 @@ class Fireball(Item):
224 268
 	name = 'Fireball'
225 269
 	char = '*'
226 270
 	color = libtcod.darker_red
227
-	effect_radius = 3
271
+	effect_radius = 5
272
+	potency = (20,6)
273
+	item_class = 'splash attack'
228 274
 
229 275
 	def use(self):
230 276
 		self.game.select(self.smite)
... ...
@@ -232,16 +278,18 @@ class Fireball(Item):
232 278
 
233 279
 	def smite(self, x,y):
234 280
 		if random.random() < .1:
281
+			self.game.message('the fireball is amazingly effective', libtcod.green)
235 282
 			self.effect_radius *= 2
236 283
 
284
+		direct_damage, splash_damage = self.potency
237 285
 		strikes = []
238 286
 		for obj in self.owner.level.objects:
239 287
 			if obj.fighter and obj is not self.user:
240 288
 				if (obj.x, obj.y) == (x,y):
241 289
 					self.game.message('%s takes a direct hit from the fireball' % obj.name)
242
-					obj.fighter.take_damage(20)
290
+					obj.fighter.take_damage(direct_damage)
243 291
 				elif obj.distance(x,y) < self.effect_radius:
244
-					obj.fighter.take_damage(6)
292
+					obj.fighter.take_damage(splash_damage)
245 293
 					if obj.fighter:
246 294
 						strikes.append('%s %s' % (obj.name, obj.fighter.hp))
247 295
 					else:
... ...
@@ -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
 
... ...
@@ -1,43 +1,96 @@
1 1
 import os.path
2
+import yaml
2 3
 import textwrap
3 4
 import math
4 5
 import libtcodpy as libtcod
5 6
 import glob
6 7
 libtcod.console_set_keyboard_repeat(500, 50)
7
-for file in glob.glob('./data/namegen/*.cfg'):
8
-	libtcod.namegen_parse(file)
8
+for fil in glob.glob('./data/namegen/*.cfg'):
9
+	libtcod.namegen_parse(fil)
10
+
11
+help = '''
12
+ 'i': Inventory
13
+ 'd': Drop
14
+ 'g': Get item (Pick up)
15
+ '?': Help
16
+ Alt+Escape: Exit
17
+
18
+ Arrow Keys for movement / selecting
19
+ Name of item under the mouse shown
20
+ above the health bar
21
+'''
9 22
 
10 23
 from game import GameBase
11 24
 import levels
12 25
 import objects
13 26
 import utilities
14 27
 if __name__ == 'main':
28
+	class Null: pass
29
+	class SettingsObject(object):
30
+		def __init__(self, setting_name, default=Null):
31
+			self.setting_name = setting_name
32
+			self.default = default
33
+
34
+		def __get__(self, instance, owner):
35
+			result = instance.settings.get(self.setting_name, self.default)
36
+			if result is Null:
37
+				raise KeyError('%s is not specified in the configuration' % self.setting_name)
38
+			return result
39
+
15 40
 	class Game(GameBase):
16 41
 		#actual size of the window
17
-		SCREEN_WIDTH, SCREEN_HEIGHT = 155, 90
42
+		def load_settings(self):
43
+			self.settings = yaml.safe_load(
44
+				file(os.path.join('./data/main.yml'))
45
+			)
46
+			if self.settings == None:
47
+				self.settings = {}
48
+			print self.settings
18 49
 
19
-		MAP_WIDTH, MAP_HEIGHT = SCREEN_WIDTH, SCREEN_HEIGHT - 17
50
+		SCREEN_WIDTH = SettingsObject('screen_width', 80)
51
+		SCREEN_HEIGHT = SettingsObject('screen_height', 50)
52
+
53
+		PANEL_HEIGHT = 15
20 54
 
21 55
 		INVENTORY_WIDTH = 50
22
-		BAR_WIDTH = 25
23 56
 
24
-		PANEL_HEIGHT = SCREEN_HEIGHT - MAP_HEIGHT - 2
25
-		PANEL_Y = SCREEN_HEIGHT - PANEL_HEIGHT
57
+		@property
58
+		def MAP_WIDTH(self):
59
+			return self.SCREEN_WIDTH
60
+		@property
61
+		def MAP_HEIGHT(self):
62
+			return self.SCREEN_HEIGHT - (self.PANEL_HEIGHT + 2)
63
+
26 64
 
65
+		BAR_WIDTH = 25
27 66
 		MSG_X = BAR_WIDTH + 2
28
-		MSG_WIDTH, MSG_HEIGHT = SCREEN_WIDTH - BAR_WIDTH - 2, PANEL_HEIGHT - 1
67
+		MSG_HEIGHT = PANEL_HEIGHT
68
+
69
+		@property
70
+		def PANEL_Y(self):
71
+			return self.SCREEN_HEIGHT - self.PANEL_HEIGHT
72
+
73
+		@property
74
+		def MSG_WIDTH(self):
75
+			return self.SCREEN_WIDTH - self.MSG_X
29 76
 
30
-		ROOM_MIN_SIZE, ROOM_MAX_SIZE = 7, 19
77
+		@property
78
+		def MSG_HEIGHT(self):
79
+			return self.PANEL_HEIGHT - 1
31 80
 
32
-		MAX_ROOMS = 51
81
+		ROOM_MIN_SIZE = SettingsObject('room_min_wall_length', 4)
82
+		ROOM_MAX_SIZE = SettingsObject('room_max_wall_length', 7)
33 83
 
34
-		MAX_ROOM_MONSTERS, MAX_ROOM_ITEMS = 9, 6
84
+		MAX_ROOMS = SettingsObject('max_number_rooms', 10)
85
+		MAX_ROOM_MONSTERS = SettingsObject('max_number_room_monsters', 6)
86
+		MAX_ROOM_ITEMS = SettingsObject('max_number_room_items', 3)
35 87
 
36 88
 		CONFUSE_NUM_TURNS = 17
37 89
 
38 90
 		LIMIT_FPS = 20	#20 frames-per-second maximum
39 91
 
40 92
 		def __init__(self):
93
+			self.load_settings()
41 94
 			GameBase.__init__(self, 'caer flinding', self.SCREEN_WIDTH, self.SCREEN_HEIGHT)
42 95
 
43 96
 			self.select_cb = None
... ...
@@ -197,17 +250,34 @@ if __name__ == 'main':
197 250
 
198 251
 		@mvkeyhandler.handle('i')
199 252
 		def mvkeyhandler(self):
200
-			item = self.inventory_menu('choose item\n')
253
+			item = self.inventory_menu('Choose item to use\n')
201 254
 			if item is not None:
202 255
 				item.bind_game(self)
203 256
 				self.player.use(item)
204 257
 
205 258
 		@mvkeyhandler.handle('d')
206 259
 		def mvkeyhandler(self):
207
-			chosen_item = self.inventory_menu('Choose the item to drop:')
260
+			chosen_item = self.inventory_menu('Choose item to drop\n')
208 261
 			if chosen_item is not None:
209 262
 				self.player.drop(chosen_item.owner)
210 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
+
211 281
 		@mvkeyhandler.handle('g')
212 282
 		def mvkeyhandler(self):
213 283
 			for obj in self.level.iter_objects():
... ...
@@ -222,6 +292,10 @@ if __name__ == 'main':
222 292
 		def mvkeyhandler(self):
223 293
 			self.change_level(down=True)
224 294
 
295
+		@mvkeyhandler.handle('?')
296
+		def mvkeyhandler(self):
297
+			self.menu(help, [], 50)
298
+
225 299
 		selectkeyhandler = utilities.MovementKeyListener()
226 300
 		@selectkeyhandler.up
227 301
 		def selectkeyhandler(self):
... ...
@@ -284,6 +358,19 @@ if __name__ == 'main':
284 358
 
285 359
 			libtcod.console_blit(self.panel, 0,0, self.SCREEN_WIDTH,self.PANEL_HEIGHT, 0,0, self.PANEL_Y)
286 360
 
361
+		def main_menu(self):
362
+			message = (
363
+				'Welcome to YinJAR: is not Just Another Roguelike (WIP)',
364
+				'',
365
+				'Choose an option:',
366
+				'',
367
+			)
368
+
369
+			options = ['Play', 'Exit']
370
+			return options[
371
+				self.menu('\n'.join(message), options, len(message[0]))
372
+			]
373
+
287 374
 	game_instance = Game()
288 375
 	from monsters import MonsterLoader
289 376
 
... ...
@@ -306,6 +393,9 @@ if __name__ == '__main__':
306 393
 	il = ItemLoader(os.path.join('.','data','items'))
307 394
 	il.load_items()
308 395
 
309
-	game_instance.setup_map()
310
-	game_instance.main()
396
+	game_instance.load_settings()
397
+	action = game_instance.main_menu()
398
+	if action.lower() == 'play':
399
+		game_instance.setup_map()
400
+		game_instance.main()
311 401
 
... ...
@@ -151,6 +151,7 @@ class MazeGen(AutomataEngine):
151 151
 
152 152
 			cx, cy = point
153 153
 			lx, ty = cx-left_offset, cy-up_offset
154
+
154 155
 			if lx < 0:
155 156
 				max_width += lx
156 157
 				lx = 0
... ...
@@ -160,10 +161,11 @@ class MazeGen(AutomataEngine):
160 161
 			w, h = random.randrange(1,max_width+1), random.randrange(1, max_height+1)
161 162
 
162 163
 			if lx + w >= self.width:
163
-				lx -= (lx+w) - self.width
164
+				w -= (lx+w) - self.width
164 165
 			if ty + h >= self.height:
165
-				ty -= (ty+h) - self.height
166
+				h -= (ty+h) - self.height
166 167
 
168
+			print '(',lx,ty, ')', w, h
167 169
 			room = Rect(lx,ty, w,h)
168 170
 			success = True
169 171
 			for o_room in self.rooms:
170 172
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
+
... ...
@@ -4,6 +4,7 @@ import libtcodpy as libtcod
4 4
 import maps
5 5
 
6 6
 class Object(object):
7
+	# FIXME: map argument unused, remove
7 8
 	def __init__(self, map, con, x,y, char, name, color, blocks=False, level=None, fighter=None, ai=None, item=None):
8 9
 		self.name = name
9 10
 		self.x, self.y = x,y
... ...
@@ -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):
151 185
new file mode 100644
... ...
@@ -0,0 +1,26 @@
1
+
2
+class Weapon(object):
3
+	def __init__(self, power_boost=0, chance_to_hit=100):
4
+		self.power_boost = power_boost
5
+		self.chance_to_hit = chance_to_hit
6
+
7
+		self.ammo = []
8
+		self.mods = []
9
+		self.user = None
10
+
11
+	def modify(self, mod):
12
+		if mod.modify(self):
13
+			self.mods.append(mod)
14
+	def remove_mod(self, mod):
15
+		if mod.undo(self):
16
+			self.mods.remove(mod)
17
+
18
+	def load(self, ammo):
19
+		self.ammo.append(ammo)
20
+
21
+	def equip(self, user):
22
+		self.user = user
23
+
24
+	def attack(self, target):
25
+		pass
26
+