git.fiddlerwoaroof.com
monsters.py
76408e86
 import random
42741360
 from main import Game
76408e86
 import game
 import objects
 import libtcodpy as libtcod
539d5ff4
 import items
76408e86
 
42741360
 from main import game_instance
76408e86
 
 class Monster(object):
160f4887
 	def init(self,*a): pass
76408e86
 	def take_turn(self): pass
92d5ef55
 	def load_data(self, data):
 		for k,v in data.items():
 			setattr(self, k,v)
 		return self
76408e86
 
 class BasicMonster(Monster):
 	def take_turn(self):
 		monster = self.owner
 		if game_instance.player.can_see(monster.x, monster.y):
 			if monster.distance_to(game_instance.player) > 1:
 				dx,dy = monster.move_towards(game_instance.player.x, game_instance.player.y)
 				counter = 0
539d5ff4
 				while (dx,dy) == (0,0) and counter < 10: # wiggle around if stuck
76408e86
 					counter += 1
 					dx,dy = monster.move(random.randrange(-1,2,2), random.randrange(-1,2,2))
160f4887
 				#print 'wiggled %s times' % counter
76408e86
 			elif game_instance.player.fighter.hp > 0:
 				monster.fighter.attack(game_instance.player)
 
92d5ef55
 class Thief(BasicMonster):
 	def init(self, level):
 		self.level = level
 		self.player = level.player
 		self.inventory = []
 		self.skill = self.skill / 100.0
 
 	def take_turn(self):
 		if self.player.distance(self.owner.x, self.owner.y) < 2 and random.random() < .7:
 			self.steal()
 		else:
 			BasicMonster.take_turn(self)
 
 	def steal(self):
2dda4cb6
 		if self.player.inventory.keys():
 			print self.player.inventory.keys()
92d5ef55
 			game_instance.message( ('%s can\'t find anything to steal'%self.owner.name).capitalize(), libtcod.orange )
2dda4cb6
 			obj = random.choice(self.player.inventory.keys())
 			game_instance.message( ('%s tries to steal %s'%(self.owner.name,obj)).capitalize(), libtcod.red)
 			if random.random() < self.skill:
 				game_instance.message( ('%s successfully steals %s'%(self.owner.name,obj)).capitalize(), libtcod.orange)
 				obj = self.player.inventory[obj]
 				self.inventory.append(obj)
 				del self.player.inventory[obj.name]
92d5ef55
 
 	def death(self):
 		monster_death(self.owner)
 		for item in self.inventory:
 			self.drop(item)
 
 	def drop(self, item):
58ab2b1d
 		print 'drop'
92d5ef55
 		item.x, item.y = self.owner.pos
 		self.level.add_object(item)
 		self.inventory.remove(item)
 
 
160f4887
 class DjikstraMonster(Monster):
 	maps = {}
 
 	def init(self, level):
 		self.level = level
2dda4cb6
 		self.owner.always_visible = True
160f4887
 
 		self.opos = self.owner.x, self.owner.y
 		self.ppos = None
 
 		map = level.map
 
 	def take_turn(self):
 		pos = self.owner.x, self.owner.y
 
 		dx,dy = 0,0
58ab2b1d
 		player_room = self.level.player.get_room()
 
160f4887
 		if self.level.is_visible(*pos):
 			if self.level.player.distance(*pos) < 2:
 				self.owner.fighter.attack(game_instance.player)
 			else:
58ab2b1d
 				dx, dy = self.owner.get_step_towards(*self.level.player.pos)
160f4887
 
 
67ab39f4
 		else:
 			dj = self.level.get_djikstra(*self.owner.pos)
 			path = libtcod.dijkstra_path_set(dj, *self.level.player.pos)
 			x,y = libtcod.dijkstra_path_walk(dj)
 
 			if x is not None:
 				dx = x - self.owner.x
 				dy = y - self.owner.y
 			else:
 				print '!'
160f4887
 
 		self.owner.move(dx,dy)
 
 
 
 
539d5ff4
 class AdvancedMonster(Monster):
 	def perimeter(self, rect):
 		for dx,row in enumerate(rect, -1):
 			for dy, cell in enumerate(row, -1):
 				if (dx in {-1,1}) or (dy in {-1,1}):
 					yield dx,dy, cell
 	def take_turn(self):
 		monster = self.owner
 		if not game_instance.player.can_see(monster.x, monster.y):
 			return
 		elif monster.distance_to(game_instance.player) > 1:
 			x,y = monster.x, monster.y
 			player_x, player_y = game_instance.player.pos
 			neighborhood = [ [0,0,0], [0,0,0], [0,0,0] ]
 			for dx in range(-1,2):
 				for dy in range(-1,2):
 					new_x = x+dx
 					new_y = y+dy
 					neighborhood[dx+1][dy+1] += int(monster.level.is_blocked(x+dx, y+dy))
 			dx, dy = monster.get_step_towards(player_x, player_y)
 			if neighborhood[dx+1][dy+1]:
 				open = []
 				for dx,dy, cell in self.perimeter(neighborhood):
 					if not cell:
 						open.append( (dx,dy) )
 				open = sorted(open, key=lambda (a,b): abs(a-dx)+abs(b-dy))[:3]
 				dx,dy = random.choice(open)
 			monster.move(dx,dy)
 		else:
 			monster.fighter.attack(game_instance.player)
 
76408e86
 
 class ConfusedMonster(Monster):
 	def __init__(self, num_turns=game_instance.CONFUSE_NUM_TURNS):
 		self.num_turns = num_turns
 
 	def attach(self, object):
 		self.old_ai = object.ai
 		self.owner = object
 		object.ai = self
 
 	def take_turn(self):
 		if self.num_turns > 0:
 			game.message('%s is confused' % self.owner.name)
 			op = get_closest_monster(self.owner, game_instance.player)
 			if self.owner.distance_to(op) >= 2:
 				self.owner.move_towards(op.x, op.y)
 			else:
 				game.message('%s attacks %s in his confusion' % (self.owner.name, op.name))
 				if self.owner.fighter:
 					self.owner.fighter.attack(op)
 				if op.fighter:
 					op.fighter.attack(self.owner)
 
 			self.num_turns -= 1
 		else:
 			self.owner.ai = self.old_ai
 			game.message('%s is no longer confused' % self.owner.name)
 
 def monster_death(monster):
 	monster.char = '\x09'
 	monster.color = libtcod.dark_red
 	monster.blocks = False
 	monster.fighter = None
 	monster.ai = None
 	monster.name = 'remains of %s' % monster.name
 	monster.send_to_back()
 
 
 import functools
 monster_at = functools.partial(game_instance.player.object_at,
 	filter=lambda obj: obj.fighter and obj is not game_instance.player
 )
 
 get_closest_monster = functools.partial(game_instance.player.get_closest_object,
 	filter=lambda obj: obj.fighter
 )
 
 get_visible_monsters = functools.partial(game_instance.player.get_visible_objects,
 	filter=lambda obj: obj.fighter
 )
 
 
 #####
 
42741360
 import yaml
 import os.path
 import glob
 import monsters
 class MonsterLoader(object):
 	def __init__(self, dir):
 		self.dir = dir
 
 	def load_monsters(self):
 		for fn in glob.glob(os.path.join(self.dir,'*.yml')):
 			print 'fn', fn
 			for doc in yaml.safe_load_all(file(fn)):
 				self.load_monster(doc)
 
 	def load_monster(self, doc):
 		color = doc.get('color', None)
 		if color is None:
 			color = libtcod.red
 		elif hasattr(color, 'upper'):
 			color = getattr(libtcod, color)
 		else:
 			color = libtcod.Color(*color)
 
 		ai_class = doc.get('ai_class', BasicMonster)
92d5ef55
 		cls_data = {}
42741360
 		if ai_class is not BasicMonster:
92d5ef55
 			cls_data = {}
 			if hasattr(ai_class, 'items'):
 				nm = ai_class.pop('class_name', 'monsters.BasicMonster')
 				cls_data.update(ai_class)
 				ai_class = nm
 
42741360
 			module, clas = ai_class.rsplit('.',1)
 			module = __import__(module)
 			ai_class = getattr(module, clas)
 
92d5ef55
 		death_func = getattr(ai_class, 'death', monster_death)
 
42741360
 		print 'loading', doc
 		Game.register_monster_type(
 			(lambda doc:
 				lambda map,level,con,x,y: objects.Object( map, con, x,y,
 					doc['char'],
1d2d26d2
 					doc.get('name_fmt', '%s the %s') % (
 						libtcod.namegen_generate(doc['namegen_class']).capitalize(),
 						doc['race_name'].capitalize()
 					),
42741360
 					color,
 					True,
 					fighter=objects.Fighter(
 						hp=doc['hp'],
 						defense=doc['defense'],
 						power=doc['power'],
58ab2b1d
 						death_function=death_func
42741360
 					),
92d5ef55
 					ai=ai_class().load_data(cls_data),
42741360
 					level=level
 				)
 			)(doc), doc['spawn_chance'])
 
92d5ef55
 Game.register_monster_type(
 	lambda map,level, con,x,y: objects.Object(map, con,
 		x,y, '\x02', '%s the Orc' % libtcod.namegen_generate('Fantasy male'),
 			libtcod.blue, True,
 
 		fighter=objects.Fighter(hp=10, defense=2, power=3, death_function=monster_death),
 		ai=AdvancedMonster(),
 		level=level
 ), 8)
 
 Game.register_monster_type(
 	lambda map,level, con,x,y: objects.Object(map, con,
 		x,y, '\x01', '%s the Troll' % libtcod.namegen_generate('Norse male'),
 			libtcod.orange, True,
 
 		fighter=objects.Fighter(hp=16, defense=1, power=4, death_function=monster_death),
 		ai=AdvancedMonster(),
 		level=level
 ), 2)
76408e86
 
 Game.register_monster_type(
 	lambda map,level, con,x,y: objects.Object(map, con,
 		x,y, '\x01', '%s the Olog-Hai' % libtcod.namegen_generate('Norse male'),
 			libtcod.amber, True,
 
 		fighter=objects.Fighter(hp=16, defense=1, power=7, death_function=monster_death),
92d5ef55
 		ai=BasicMonster(),
76408e86
 		level=level
 ), 1)
539d5ff4
 Game.register_monster_type(None, 7)
76408e86