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)
|