git.fiddlerwoaroof.com
src/map.py
54cb1995
 import numpy as np
 import libtcodpy as tc
d7409888
 import yaml
291e96ff
 import libs.bresenham
def7cb88
 import libs.cache
54cb1995
 
 def squeeze(val, low, high):
 	return min(max(val, low), high)
 
d7409888
 class TerrainGen(object):
 	def __init__(self, map):
 		self.width, self.height = map.dim
 		self.terrain_registry = map.terrain_registry
 
 	def generate(self):
 		w,h = self.width, self.height
ef35ec9b
 		terrains, probs = self.terrain_registry.get_terrain_types()
 		map = np.random.choice(terrains, (w,h), p=probs)
d7409888
 		chars = np.zeros((w,h)).astype('int')
 		fgcolors = np.zeros((w,h,3))
 		bgcolors = np.zeros((w,h,3))
 
 		for x, row in enumerate(map):
 			for y, cell in enumerate(row):
ef35ec9b
 				char, fgcolor, bgcolor = self.terrain_registry.get_display(cell)
d7409888
 				chars[x,y] = char
 				fgcolors[x,y] = fgcolor
 				bgcolors[x,y] = bgcolor
 		return map, chars, fgcolors, bgcolors
 
 
54cb1995
 class Map(object):
d7409888
 	def __init__(self, w, h, terrain_registry):
 		self.pov = None
def7cb88
 		self.povcache = libs.cache.Cache()
 
54cb1995
 		self.width = w
 		self.height = h
d7409888
 		self.terrain_registry = terrain_registry
54cb1995
 		self.overlays = {} # (x,y): { set( object, ... ) }
 
d7409888
 		self.ids, self.map, self.fgcolors, self.bgcolors = TerrainGen(self).generate()
 
 		self.fov = FovCache(self, self.terrain_registry)
 
54cb1995
 	def add(self, object):
 		self.overlays.setdefault(object.pos,[]).append(object)
 
5f75058b
 	def check_and_execute_bump(self, object, x,y):
 		if (x,y) in self.overlays:
 			for other in self.overlays[x,y]:
 				object.bump(other)
 
b8f124a1
 	def interact(self, a, bs):
 		print 'interacting with:', bs
 		for b in bs:
 			a.interact(b)
7daa7f78
 
b8f124a1
 	def remove(self, object):
 		pos = object.pos
 		if pos in self.overlays and object in self.overlays[pos]:
 			self.overlays[pos].remove(object)
 
 	def move(self, object, dx,dy, update_cb):
 		ox,oy = object.pos
5f75058b
 
291e96ff
 		if abs(dx) < 2 and abs(dy) < 2:
b8f124a1
 			self.overlays[object.pos].remove(object)
 
 			collide_x, collide_y = None, None
291e96ff
 			x = squeeze(ox+dx, 0, self.width-1)
 			y = squeeze(oy+dy, 0, self.height-1)
3d5a515d
 			if not self.is_passable((x,y)):
5f75058b
 				collide_x, collide_y = x,y
291e96ff
 				x,y = ox,oy
b8f124a1
 			update_cb(x-ox, y-oy)
 
 			if collide_x is not None:
 				self.check_and_execute_bump(object, collide_x, collide_y)
 			if object.pos in self.overlays:
 				self.interact(object, self.overlays[object.pos])
291e96ff
 
b8f124a1
 			self.overlays.setdefault((x,y), []).append(object)
 			self.update_overlay(ox,oy)
291e96ff
 		else:
b8f124a1
 			# calculate the deltas for each step
 			line = list(libs.bresenham.line(0,0, dx,dy, 0))
 			line = [(bx-ax, by-ay) for (ax,ay), (bx,by) in zip(line,line[1:])]
 			print line
 			for x,y in line:
 				if self.move(object, x,y, update_cb) != (x,y):
5f75058b
 					break
b8f124a1
 			x,y = object.pos
291e96ff
 
54cb1995
 		return x-ox, y-oy
 
 	def update_overlay(self, x=None, y=None):
 		if x is None or y is None: pass
 		else:
 			if (x,y) in self.overlays and self.overlays[x,y] == []:
 				self.overlays.pop((x,y))
 
3d5a515d
 	def set_pov(self, pov):
 		self.pov = pov
 
 	def get_visible_objects(self):
 		o,r = self.pov
 		results = set()
 		for x,y in self.overlays:
 			if self.fov.is_visible(o,r, (x,y)):
 				results.update(self.overlays[x,y])
 		return results
 
 
 
def7cb88
 	def get_rgb(self, colors, fg=True,slices=(slice(0),slice(0))):
b8f124a1
 		result = np.rollaxis(colors, 2)
54cb1995
 		return [x.transpose() for x in result]
 
 	def draw(self, con, tl=(0,0)):
b8f124a1
 		def mv(x,y, (lx,ty)):
 			return x-lx, y-ty
54cb1995
 		br = tl[0]+con.width, tl[1]+con.height
b8f124a1
 		slices = slice(tl[0], br[0]), slice(tl[1], br[1])
def7cb88
 
 		fgcolors = self.fgcolors.astype('int')
 		bgcolors = self.bgcolors.astype('int')
 		color_mask = np.ones( (con.width, con.height, 3) )
 		char_mask = np.ones( (con.width, con.height) ).astype('bool')
b8f124a1
 
df36dce5
 		self.pov = None
def7cb88
 		if self.pov is not None:
 			origin, radius = self.pov
b8f124a1
 			ox, oy = origin
 			if ox+radius >= br[0] or oy+radius >= br[1]:
 				xmod, ymod = 0,0
 				if ox + radius >= br[0]:
 					xmod = min(ox + radius - br[0], self.width - br[0])
 				if oy + radius >= br[1]:
 					ymod =  min(oy + radius - br[1], self.height - br[1])
 				br = br[0] + xmod, br[1] + ymod
 				tl = tl[0] + xmod, tl[1] + ymod
 				slices = slice(tl[0], br[0]), slice(tl[1], br[1])
def7cb88
 			def calc_mask():
 				for x in range(tl[0], br[0]):
 					for y in range(tl[1], br[1]):
 						if not self.fov.is_visible(origin, radius, (x,y)):
b8f124a1
 							color_mask[x-tl[0], y-tl[1]] = (0.25, 0.5, 0.5)
 							char_mask[x-tl[0], y-tl[1]] = False
def7cb88
 				return color_mask, char_mask
b8f124a1
 			color_mask, char_mask = self.povcache.get( (origin,radius,tl), calc_mask )
 			fgcolors = (fgcolors[slices] * color_mask).astype('int')
 			bgcolors = (bgcolors[slices] * color_mask).astype('int')
 		else:
 			fgcolors = fgcolors[slices]
 			bgcolors = bgcolors[slices]
def7cb88
 
b8f124a1
 		tc.console_fill_foreground(con.con, *self.get_rgb(fgcolors))
 		tc.console_fill_background(con.con, *self.get_rgb(bgcolors, False))
54cb1995
 
 		chars = np.copy(self.map[slices])
 		for x,y in self.overlays:
 			screen_x = x-tl[0]
 			screen_y = y-tl[1]
b8f124a1
 			if (not (tl[0] <= x < br[0])) or (not (tl[1] <= y < br[1])): continue
 			elif not char_mask[screen_x,screen_y]: continue
54cb1995
 			if 0 <= screen_x < con.width and 0 <= screen_y < con.height:
 				obj = self.overlays[x,y][-1]
 				chars[screen_x,screen_y] = obj.char
def7cb88
 				tc.console_set_char_background(con.con, screen_x,screen_y, tc.Color(*bgcolors[screen_x,screen_y]))
 				tc.console_set_char_foreground(con.con, screen_x,screen_y, tc.Color(*obj.color))
 		chars[np.logical_not(char_mask)] = ord(' ')
54cb1995
 		tc.console_fill_char(con.con, chars.transpose())
d7409888
 
 	def coord_iter(self):
 		return ( (x,y) for x in range(self.width) for y in range(self.height) )
 
 	@property
 	def dim(self):
 		return self.width, self.height
 
3d5a515d
 	def is_passable(self, coord):
 		if coord in self.overlays and any(x.blocks for x in self.overlays[coord]):
 			return False
 		else:
 			return self.fov.is_passable(coord)
 
d7409888
 
 class FovCache(object):
 	# TODO: get allow updates to base_map
 	def __init__(self, map, terrain_registry):
def7cb88
 		self.width, self.height = map.dim
 
d7409888
 		self.base_map = tc.map_new(*map.dim)
 
 		for x,y in map.coord_iter():
 			if (x,y) in map.overlays:
 				object = map.overlays[x,y]
 				pssble,trnsprnt = object.passable, object.transparent
 			else:
 				pssble,trnsprnt = terrain_registry.get_props(map.ids[x,y])
 			tc.map_set_properties(self.base_map, x,y, trnsprnt,pssble)
 
 		self.fovmaps = {}
 
def7cb88
 	def get_fovmap(self, origin, radius):
d7409888
 		key = origin,radius
 
 		if key in self.fovmaps: fovmap = self.fovmaps[key]
 		else:
 			fovmap = tc.map_new(self.width, self.height)
 			tc.map_copy(self.base_map, fovmap)
 			self.fovmaps[key] = fovmap
 
 			x,y = origin
b8f124a1
 			tc.map_compute_fov(fovmap, x,y, radius, algo=tc.FOV_DIAMOND)
d7409888
 
 		return fovmap
 
def7cb88
 	def is_visible(self, origin, radius, coord):
 		fovmap = self.get_fovmap(origin, radius)
 		return tc.map_is_in_fov(fovmap, *coord)
 
d7409888
 	def is_transparent(self, coord):
 		return tc.map_is_transparent(self.base_map, *coord)
 	def is_passable(self, coord):
 		return tc.map_is_walkable(self.base_map, *coord)
 
 
 class TerrainInfo(object):
 	passable = False
 	transparent = False
 	char = ord(' ')
 	fg = (255,255,255)
 	bg = (0,0,0)
ef35ec9b
 	prob = 1
d7409888
 
 	@classmethod
ef35ec9b
 	def make_terrain(cls, name, char, passable, transparent,fg,bg, prob=1):
d7409888
 		if hasattr(char, 'upper'): char = ord(char)
 		passable = bool(passable)
 		transparent = bool(transparent)
ef35ec9b
 		return type(name, (cls,), dict(char=char, passable=passable, transparent=transparent,fg=fg,bg=bg,prob=prob))
d7409888
 
 class TerrainRegistry(object):
 	def __init__(self):
 		self.id = 0
 		self.registry = {}
 		self.names = {}
 
 	def get_terrain(self, id):
 		ter = self.registry[id]
 		return ter
 
 	def get_props(self, id):
 		ter = self.get_terrain(id)
 		return ter.passable, ter.transparent
 
 	def get_display(self, char):
 		ter = self.get_terrain(char)
 		return ter.char, ter.fg, ter.bg
 
 	def get_terrain_types(self):
ef35ec9b
 		types, probabilities = zip(*[(x, self.registry[x].prob) for x in self.registry])
 		probs = [float(x) / sum(probabilities) for x in probabilities]
 		return types, probs
d7409888
 
 
 	def register(self, ter):
 		self.id += 1
 		self.registry[self.id] = ter
 		self.names[ter.__name__] = ter
 		return ter
 
 	def new_terrain(self, name, char,
ef35ec9b
 		passable=False, transparent=False, fg=(255,255,255), bg=(0,0,0), prob=1
d7409888
 	):
ef35ec9b
 		ter = TerrainInfo.make_terrain(name, char, passable, transparent, fg,bg, prob=prob)
d7409888
 		return self.register(ter)
 
 	def load_from_file(self, fn, loader=yaml.safe_load):
 		with open(fn) as f:
 			values = loader(f)
 		for name, terrain in values.viewitems():
 			self.new_terrain(name, **terrain)