1017 lines
39 KiB
Python
1017 lines
39 KiB
Python
#gamemap.py
|
|
import re
|
|
import heapq
|
|
import xml.etree.ElementTree as ET
|
|
import ruamel.yaml
|
|
from ruamel.yaml.comments import CommentedMap # for loading classes
|
|
|
|
class Thing(object):
|
|
|
|
def __init__(self, thingType: str, name: str, x: int, y: int, description: str, flags: int, playerx = None, playery = None):
|
|
self.thingType = thingType
|
|
self.name = name
|
|
self.description = description
|
|
self.x = x
|
|
self.y = y
|
|
self.playerx = x
|
|
self.playery = y
|
|
if playerx:
|
|
self.playerx = playerx
|
|
if playery:
|
|
self.playery = playery
|
|
self.passable = bool(flags & 1)
|
|
self.talkable = bool(flags & 2)
|
|
self.lookable = bool(flags & 4)
|
|
self.takeable = bool(flags & 8)
|
|
self.useable = bool(flags & 16)
|
|
self.graphic = ('clear', '#7F7F7F', ' ')
|
|
|
|
def __str__(self):
|
|
"""__str__ is used for look."""
|
|
return self.description
|
|
|
|
def __eq__(self, other):
|
|
if not isinstance(other, Thing):
|
|
return False
|
|
return self.name == other.name
|
|
|
|
class Item(Thing):
|
|
yaml_flag = u'!Item'
|
|
defaultGraphic = ('clear', '#00BF00', '^')
|
|
|
|
def __init__(self, name, x: int, y: int, description: str, useFunc: str, useOnFunc: str, customValues: dict, ranged: bool, graphic = defaultGraphic):
|
|
super(Item, self).__init__('i', name, x, y, description, 13)
|
|
self.useFunc = useFunc
|
|
self.useOnFunc = useOnFunc
|
|
self.customValues = customValues
|
|
self.ranged = ranged
|
|
self.graphic = graphic
|
|
|
|
def use(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def to_yaml(cls, representer, node):
|
|
# save usual things
|
|
ret = {'name': node.name, 'location': (node.x, node.y), 'description': node.description}
|
|
# save graphic
|
|
graphic = {}
|
|
if node.graphic[0] != Item.defaultGraphic[0]:
|
|
graphic['bgc'] = node.graphic[0]
|
|
if node.graphic[1] != Item.defaultGraphic[1]:
|
|
graphic['fgc'] = node.graphic[1]
|
|
if node.graphic[2] != Item.defaultGraphic[2]:
|
|
graphic['shape'] = node.graphic[2]
|
|
if len(graphic) > 0:
|
|
ret['graphic'] = graphic
|
|
# save use functions
|
|
if node.useFunc != '':
|
|
ret['useFunc'] = node.useFunc
|
|
if node.useOnFunc != '':
|
|
ret['useOnFunc'] = node.useOnFunc
|
|
if len(node.customValues) > 0:
|
|
ret['customValues'] = node.customValues
|
|
if node.ranged:
|
|
ret['ranged'] = node.ranged
|
|
return representer.represent_mapping(cls.yaml_flag, ret)
|
|
|
|
@classmethod
|
|
def from_yaml(cls, constructor, node):
|
|
parts = CommentedMap()
|
|
constructor.construct_mapping(node, parts, True)
|
|
# set default values for optional arguments
|
|
useFunc = ''
|
|
useOnFunc = ''
|
|
customValues = {}
|
|
ranged = False
|
|
bgc = Item.defaultGraphic[0]
|
|
fgc = Item.defaultGraphic[1]
|
|
shape = Item.defaultGraphic[2]
|
|
# load graphic
|
|
if 'graphic' in parts:
|
|
if 'bgc' in parts['graphic']:
|
|
bgc = parts['graphic']['bgc']
|
|
if 'fgc' in parts['graphic']:
|
|
fgc = parts['graphic']['fgc']
|
|
if 'shape' in parts['graphic']:
|
|
shape = parts['graphic']['shape']
|
|
graphic = (bgc, fgc, shape)
|
|
# load use functions
|
|
if 'useFunc' in parts:
|
|
useFunc = parts['useFunc']
|
|
if 'useOnFunc' in parts:
|
|
useOnFunc = parts['useOnFunc']
|
|
if 'customValues' in parts:
|
|
customValues = dict(parts['customValues'])
|
|
for v in customValues:
|
|
if isinstance(customValues[v], tuple):
|
|
customValues[v] = list(customValues[v])
|
|
if 'ranged' in parts:
|
|
useOnFunc = parts['ranged']
|
|
return cls(parts['name'], parts['location'][0], parts['location'][1],
|
|
parts['description'], useFunc, useOnFunc, customValues, ranged, graphic)
|
|
|
|
class Useable(Thing):
|
|
yaml_flag = u'!Useable'
|
|
defaultGraphic = ('clear', '#0000FF', '#')
|
|
|
|
def __init__(self, name, x: int, y: int, description: str, useFunc: str, customValues: dict, playerx = None, playery = None, graphic = defaultGraphic):
|
|
super(Useable, self).__init__('u', name, x, y, description, 16, playerx, playery)
|
|
self.useFunc = useFunc
|
|
self.customValues = customValues
|
|
self.graphic = graphic
|
|
|
|
def use(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def to_yaml(cls, representer, node):
|
|
# save usual things
|
|
ret = {'name': node.name, 'location': (node.x, node.y), 'description': node.description}
|
|
# save graphic
|
|
graphic = {}
|
|
if node.graphic[0] != Useable.defaultGraphic[0]:
|
|
graphic['bgc'] = node.graphic[0]
|
|
if node.graphic[1] != Useable.defaultGraphic[1]:
|
|
graphic['fgc'] = node.graphic[1]
|
|
if node.graphic[2] != Useable.defaultGraphic[2]:
|
|
graphic['shape'] = node.graphic[2]
|
|
if len(graphic) > 0:
|
|
ret['graphic'] = graphic
|
|
# save use functions
|
|
if node.useFunc != '':
|
|
ret['useFunc'] = node.useFunc
|
|
if len(node.customValues) > 0:
|
|
ret['customValues'] = node.customValues
|
|
if node.x != node.playerx or node.y != node.playery:
|
|
ret['useLocation'] = (node.playerx, node.playery)
|
|
return representer.represent_mapping(cls.yaml_flag, ret)
|
|
|
|
@classmethod
|
|
def from_yaml(cls, constructor, node):
|
|
parts = CommentedMap()
|
|
constructor.construct_mapping(node, parts, True)
|
|
# set default values for optional arguments
|
|
useFunc = ''
|
|
customValues = {}
|
|
playerx, playery = parts['location']
|
|
bgc = Useable.defaultGraphic[0]
|
|
fgc = Useable.defaultGraphic[1]
|
|
shape = Useable.defaultGraphic[2]
|
|
# load graphic
|
|
if 'graphic' in parts:
|
|
if 'bgc' in parts['graphic']:
|
|
bgc = parts['graphic']['bgc']
|
|
if 'fgc' in parts['graphic']:
|
|
fgc = parts['graphic']['fgc']
|
|
if 'shape' in parts['graphic']:
|
|
shape = parts['graphic']['shape']
|
|
graphic = (bgc, fgc, shape)
|
|
# load use functions
|
|
if 'useFunc' in parts:
|
|
useFunc = parts['useFunc']
|
|
if 'customValues' in parts:
|
|
customValues = dict(parts['customValues'])
|
|
for v in customValues:
|
|
if isinstance(customValues[v], tuple):
|
|
customValues[v] = list(customValues[v])
|
|
if 'useLocation' in parts:
|
|
playerx, playery = parts['useLocation']
|
|
return cls(parts['name'], parts['location'][0], parts['location'][1],
|
|
parts['description'], useFunc, customValues, playerx, playery, graphic)
|
|
|
|
class NPC(Thing):
|
|
|
|
def __init__(self, name, x: int, y: int, description: str, friendly = True, following = False, graphic = ('clear', '#000000', 'o')):
|
|
super(NPC, self).__init__('c', name, x, y, description, 6)
|
|
self.following = following
|
|
self.friendly = friendly
|
|
self.inventory = []
|
|
self.graphic = graphic
|
|
|
|
def give(self, item):
|
|
self.inventory.append(item)
|
|
|
|
def drop(self, index):
|
|
return self.inventory.pop(index)
|
|
|
|
def dialog(self):
|
|
pass
|
|
|
|
class Door(Thing):
|
|
yaml_flag = u'!Door'
|
|
defaultGraphic = ('clear', '#7F3F00', '#')
|
|
|
|
def __init__(self, name, x: int, y: int, locked: bool, description = None, key = None, graphic = defaultGraphic):
|
|
self.descBase = description
|
|
if description == None:
|
|
if locked:
|
|
description = "The {0} is locked.".format(name)
|
|
else:
|
|
description = "The {0} is unlocked.".format(name)
|
|
else:
|
|
if locked:
|
|
description += " It is locked.".format(name)
|
|
else:
|
|
description += " It is unlocked.".format(name)
|
|
super(Door, self).__init__('d', name, x, y, description, 1)
|
|
self.passable = not locked
|
|
self.key = key
|
|
self.graphic = graphic
|
|
|
|
def lock(self, key = None):
|
|
if key == self.key:
|
|
self.passable = not self.passable
|
|
if self.descBase == None:
|
|
if self.passable:
|
|
self.description = "The {0} is unlocked.".format(self.name)
|
|
else:
|
|
self.description = "The {0} is locked.".format(self.name)
|
|
else:
|
|
if self.passable:
|
|
self.description += " It is unlocked.".format(self.name)
|
|
else:
|
|
self.description += " It is locked.".format(self.name)
|
|
return True
|
|
return False
|
|
|
|
@classmethod
|
|
def to_yaml(cls, representer, node):
|
|
# save usual things
|
|
ret = {'name': node.name, 'location': (node.x, node.y)}
|
|
# save graphic
|
|
graphic = {}
|
|
if node.graphic[0] != Door.defaultGraphic[0]:
|
|
graphic['bgc'] = node.graphic[0]
|
|
if node.graphic[1] != Door.defaultGraphic[1]:
|
|
graphic['fgc'] = node.graphic[1]
|
|
if node.graphic[2] != Door.defaultGraphic[2]:
|
|
graphic['shape'] = node.graphic[2]
|
|
if len(graphic) > 0:
|
|
ret['graphic'] = graphic
|
|
# save door state
|
|
if node.passable:
|
|
ret['locked'] = not node.passable
|
|
if node.descBase != None:
|
|
ret['description'] = node.descBase
|
|
if node.key != None:
|
|
ret['key'] = node.key
|
|
return representer.represent_mapping(cls.yaml_flag, ret)
|
|
|
|
@classmethod
|
|
def from_yaml(cls, constructor, node):
|
|
parts = CommentedMap()
|
|
constructor.construct_mapping(node, parts, True)
|
|
# set default values for optional arguments
|
|
description = None
|
|
locked = False
|
|
key = None
|
|
bgc = Door.defaultGraphic[0]
|
|
fgc = Door.defaultGraphic[1]
|
|
shape = Door.defaultGraphic[2]
|
|
# load graphic
|
|
if 'graphic' in parts:
|
|
if 'bgc' in parts['graphic']:
|
|
bgc = parts['graphic']['bgc']
|
|
if 'fgc' in parts['graphic']:
|
|
fgc = parts['graphic']['fgc']
|
|
if 'shape' in parts['graphic']:
|
|
shape = parts['graphic']['shape']
|
|
graphic = (bgc, fgc, shape)
|
|
# load door state
|
|
if 'description' in parts:
|
|
description = parts['description']
|
|
if 'locked' in parts:
|
|
locked = parts['locked']
|
|
if 'key' in parts:
|
|
key = parts['key']
|
|
return cls(parts['name'], parts['location'][0], parts['location'][1],
|
|
locked, description, key, graphic)
|
|
|
|
class MapExit(Thing):
|
|
yaml_flag = u'!MapExit'
|
|
defaultGraphic = ('clear', '#FF0000', 'x')
|
|
|
|
def __init__(self, name, x: int, y: int, exitid: int, destination: str, prefix: None, graphic = defaultGraphic):
|
|
description = name
|
|
if prefix:
|
|
description = "{0} {1}".format(prefix, name)
|
|
super(MapExit, self).__init__('x', name, x, y, description, 5)
|
|
self.exitid = exitid
|
|
self.destination = destination
|
|
self.prefix = prefix
|
|
self.graphic = graphic
|
|
|
|
@classmethod
|
|
def to_yaml(cls, representer, node):
|
|
# save usual things
|
|
ret = {'name': node.name, 'location': (node.x, node.y), 'id': node.exitid, 'destination': node.destination}
|
|
# save graphic
|
|
graphic = {}
|
|
if node.graphic[0] != MapExit.defaultGraphic[0]:
|
|
graphic['bgc'] = node.graphic[0]
|
|
if node.graphic[1] != MapExit.defaultGraphic[1]:
|
|
graphic['fgc'] = node.graphic[1]
|
|
if node.graphic[2] != MapExit.defaultGraphic[2]:
|
|
graphic['shape'] = node.graphic[2]
|
|
if len(graphic) > 0:
|
|
ret['graphic'] = graphic
|
|
if node.prefix != None:
|
|
ret['prefix'] = node.prefix
|
|
return representer.represent_mapping(cls.yaml_flag, ret)
|
|
|
|
@classmethod
|
|
def from_yaml(cls, constructor, node):
|
|
parts = CommentedMap()
|
|
constructor.construct_mapping(node, parts, True)
|
|
# set default values for optional arguments
|
|
prefix = None
|
|
bgc = MapExit.defaultGraphic[0]
|
|
fgc = MapExit.defaultGraphic[1]
|
|
shape = MapExit.defaultGraphic[2]
|
|
# load graphic
|
|
if 'graphic' in parts:
|
|
if 'bgc' in parts['graphic']:
|
|
bgc = parts['graphic']['bgc']
|
|
if 'fgc' in parts['graphic']:
|
|
fgc = parts['graphic']['fgc']
|
|
if 'shape' in parts['graphic']:
|
|
shape = parts['graphic']['shape']
|
|
graphic = (bgc, fgc, shape)
|
|
if 'prefix' in parts:
|
|
prefix = parts['prefix']
|
|
return cls(parts['name'], parts['location'][0], parts['location'][1],
|
|
parts['id'], parts['destination'], prefix, graphic)
|
|
|
|
class MapError(RuntimeError):
|
|
pass
|
|
|
|
class GameMap(object):
|
|
# Matrix tile codes:
|
|
# e: empty (0)
|
|
# w: wall (0)
|
|
|
|
# regular expressions
|
|
tileRegex = re.compile(r'([a-z ])([0-9]+|[ ])')
|
|
matrixRegex = re.compile(r'(?:[ \t]*(?:[a-z ](?:[0-9]+|[ ]))+(\n))+')
|
|
|
|
def __init__(self, name, graph, matrix, dimensions):
|
|
self.name = name
|
|
self.openingText = ""
|
|
self.mapGraph = graph
|
|
self.mapMatrix = matrix
|
|
self.dimensions = dimensions
|
|
self.thingPos = {} # int location : list of names
|
|
self.thingNames = {} # Things can be looked up by name.
|
|
self.playerStart = (1, 1)
|
|
self.description = "The area is completely blank."
|
|
self.floorColors = []
|
|
self.wallColors = []
|
|
self.persistent = []
|
|
|
|
@staticmethod
|
|
def __cleanStr(text: str, end = '\n'):
|
|
if text == None:
|
|
return text
|
|
text = text.strip()
|
|
i = 0
|
|
while True:
|
|
i = text.find('\n', i)
|
|
if i == -1:
|
|
break
|
|
j = i+1
|
|
if len(text) > j:
|
|
while text[j] in ' \t':
|
|
j += 1 # just find the first non-space chartacter
|
|
text = text[:i+1] + text[j:]
|
|
i += 1
|
|
else:
|
|
break
|
|
return text.replace('\n', end)
|
|
|
|
@staticmethod
|
|
def convert(infile: str):
|
|
"""Convert an XML map to a YAML one."""
|
|
data = None
|
|
with open(infile, 'r') as f:
|
|
data = f.read()
|
|
info = ET.fromstring(data)
|
|
layout = info.find('layout')
|
|
ret = {}
|
|
if layout == None:
|
|
raise MapError('No layout in {0}.'.format(infile))
|
|
|
|
# "Layout" needs some work before we convert it.
|
|
ret['layout'] = GameMap.__cleanStr(layout.text.strip())
|
|
|
|
# now the rest of the things
|
|
if 'openingText' in info.attrib:
|
|
ret['openingText'] = info.attrib['openingText']
|
|
else:
|
|
raise MapError('No opening text in {0}.'.format(infile))
|
|
if 'playerStart' in info.attrib:
|
|
ps = info.attrib['playerStart'].split(',')
|
|
ret['playerStart'] = (int(ps[0]), int(ps[1]))
|
|
else:
|
|
raise MapError('No player start position in {0}.'.format(infile))
|
|
if info.text != None:
|
|
ret['description'] = GameMap.__cleanStr(info.text, ' ')
|
|
else:
|
|
raise MapError('No description in {0}.'.format(infile))
|
|
|
|
# get map colors
|
|
floorColors = ['#9F7F5F']
|
|
floorColorsStr = info.find('floorColors')
|
|
if floorColorsStr != None:
|
|
floorColors = floorColorsStr.text.lstrip().split()
|
|
if len(floorColors) == 0:
|
|
floorColors.append('#9F7F5F')
|
|
ret['floorColors'] = floorColors
|
|
wallColors = ['#7F3F0F']
|
|
wallColorsStr = info.find('wallColors')
|
|
if wallColorsStr != None:
|
|
wallColors = wallColorsStr.text.lstrip().split()
|
|
if len(wallColors) == 0:
|
|
wallColors.append('#7F3F0F')
|
|
ret['wallColors'] = wallColors
|
|
|
|
# get things
|
|
ret['loadAlways'] = []
|
|
ret['loadOnce'] = []
|
|
for node in info:
|
|
if node.tag == 'loadOnce':
|
|
# Things in the load-once section are only loaded the first
|
|
# time that the map is loaded, and saved in the player's
|
|
# save file.
|
|
for node1 in node:
|
|
if node1.tag == 'door':
|
|
ret['loadOnce'].append(GameMap.__loadDoor(node1))
|
|
elif node1.tag == 'useable':
|
|
ret['loadOnce'].append(GameMap.__loadUseable(node1))
|
|
elif node1.tag == 'item':
|
|
ret['loadOnce'].append(GameMap.__loadItem(node1))
|
|
elif node.tag == 'exit':
|
|
ret['loadAlways'].append(GameMap.__loadExit(None, node, -1)) # weird arguments: there is no actual level
|
|
elif node.tag == 'door':
|
|
ret['loadAlways'].append(GameMap.__loadDoor(node))
|
|
elif node.tag == 'useable':
|
|
ret['loadAlways'].append(GameMap.__loadUseable(node))
|
|
|
|
#start saving
|
|
outfile = infile[:-3] + 'yml'
|
|
yaml = ruamel.yaml.YAML()
|
|
yaml.indent(mapping=4, sequence=4, offset=2)
|
|
yaml.register_class(Item)
|
|
yaml.register_class(Useable)
|
|
yaml.register_class(Door)
|
|
yaml.register_class(MapExit)
|
|
with open(outfile, 'w') as f:
|
|
f.write('%YAML 1.2\n---\n')
|
|
yaml.dump(ret, f)
|
|
|
|
@staticmethod
|
|
def read(infile = None, prevMap = None, preLoaded = False):
|
|
"""Read map data and return a Map object. If infile is not provided, then
|
|
it will read from stdin. Otherwise, it should be a valid file name.
|
|
Entering a map through stdin will be obsolete once testing is over."""
|
|
info = None
|
|
tryToRead = True
|
|
yaml = ruamel.yaml.YAML()
|
|
yaml.register_class(Item)
|
|
yaml.register_class(Useable)
|
|
yaml.register_class(Door)
|
|
yaml.register_class(MapExit)
|
|
if infile != None:
|
|
try:
|
|
with open(infile, 'r') as f:
|
|
info = yaml.load(f)
|
|
except OSError as e:
|
|
print("The file could not be read.")
|
|
return None
|
|
else:
|
|
|
|
while tryToRead:
|
|
try:
|
|
data += input()
|
|
except EOFError as e:
|
|
tryToRead = False
|
|
|
|
# Now what we do with the data
|
|
mat = None
|
|
if 'layout' not in info:
|
|
raise MapError('No layout in {0}.'.format(infile))
|
|
layout = info['layout']
|
|
if layout != None:
|
|
#print(layout.text)
|
|
match = GameMap.matrixRegex.match(layout.lstrip())
|
|
if match == None:
|
|
raise RuntimeError('Map read a file without a map matrix.')
|
|
mat = match.group()
|
|
else:
|
|
raise RuntimeError('Map read a file without a map matrix.')
|
|
|
|
# generate matrix and graph first
|
|
mapMatrix, mapGraph, dimensions = GameMap.parseMatrix(mat)
|
|
playerStart = (-1, -1)
|
|
level = GameMap(infile, mapGraph, mapMatrix, dimensions)
|
|
|
|
# Now, load other info
|
|
GameMap.loadThings(level, info, prevMap, preLoaded)
|
|
|
|
return level
|
|
|
|
@staticmethod
|
|
def parseMatrix(matrixStr):
|
|
"""Returns a map graph as an adjacency list, as well as the matrix as a
|
|
list of lists of tuples."""
|
|
# Make the matrix first
|
|
mat = [[]]
|
|
x = 0
|
|
y = 0
|
|
l = 0
|
|
while len(matrixStr) > 0:
|
|
tile = GameMap.tileRegex.match(matrixStr)
|
|
if tile != None:
|
|
tileType = tile.group(1)
|
|
tileNum = tile.group(2)
|
|
if tileType == ' ':
|
|
tileType = 'e'
|
|
if tileNum == ' ':
|
|
tileNum = '0'
|
|
mat[l].append((tileType, int(tileNum)))
|
|
#x += 1
|
|
matrixStr = matrixStr[len(tile.group()):]
|
|
elif matrixStr[0] == '\n':
|
|
if x == 0:
|
|
x = len(mat[l])
|
|
elif x != len(mat[l]):
|
|
raise RuntimeError("Map matrix has jagged edges.")
|
|
l += 1
|
|
#x = 0
|
|
mat.append([])
|
|
i = 1
|
|
while i < len(matrixStr) and matrixStr[i] in ' \t\n':
|
|
i += 1
|
|
if i == len(matrixStr):
|
|
matrixStr = ''
|
|
else:
|
|
matrixStr = matrixStr[i:]
|
|
else: # This should happen when it finishes?
|
|
raise RuntimeError("Unexpected token in map matrix: '{0}'".format(matrixStr))
|
|
y = len(mat) - 1
|
|
|
|
# Now for the graph
|
|
numTiles = x * y
|
|
dim = (x, y)
|
|
graph = [[] for j in range(numTiles)]
|
|
passable = ('e', 'd', 'x', 'p', 'i')
|
|
for j in range(y):
|
|
for i in range(x):
|
|
if mat[j][i][0] in passable:
|
|
here = GameMap.__coordsToInt(i, j, x)
|
|
if i > 0 and mat[j][i-1][0] in passable:
|
|
there = GameMap.__coordsToInt(i-1, j, x)
|
|
graph[here].append(there)
|
|
graph[there].append(here)
|
|
if j > 0 and mat[j-1][i][0] in passable:
|
|
there = GameMap.__coordsToInt(i, j-1, x)
|
|
graph[here].append(there)
|
|
graph[there].append(here)
|
|
|
|
return mat, graph, dim
|
|
|
|
@staticmethod
|
|
def loadThings(level, info, prevMap = None, preLoaded = False):
|
|
"""load the things from the xml part of the map file."""
|
|
if 'openingText' in info:
|
|
level.openingText = info['openingText']
|
|
if 'playerStart' in info:
|
|
level.playerStart = info['playerStart']
|
|
if 'description' in info:
|
|
level.description = info['description']
|
|
|
|
# get map colors
|
|
if 'floorColors' in info:
|
|
level.floorColors = info['floorColors']
|
|
if 'wallColors' in info:
|
|
level.wallColors = info['wallColors']
|
|
if len(level.floorColors) == 0:
|
|
level.floorColors.append('#9F7F5F')
|
|
if len(level.wallColors) == 0:
|
|
level.wallColors.append('#7F3F0F')
|
|
|
|
# get things
|
|
if 'loadOnce' in info and not preLoaded:
|
|
for thing in info['loadOnce']:
|
|
#print(type(thing))
|
|
level.addThing(thing, True)
|
|
if 'loadAlways' in info:
|
|
for thing in info['loadAlways']:
|
|
#print(type(thing))
|
|
level.addThing(thing)
|
|
if thing.thingType == 'x' and prevMap == thing.exitid:
|
|
level.playerStart = (thing.x, thing.y)
|
|
|
|
@staticmethod
|
|
def __loadExit(level, node, prevMap):
|
|
exitid = 0
|
|
x, y = 0, 0
|
|
destination = ''
|
|
name = ''
|
|
prefix = None
|
|
if 'name' in node.attrib:
|
|
name = node.attrib['name']
|
|
else:
|
|
print("Nameless exit, ommitting.")
|
|
return None
|
|
if 'id' in node.attrib:
|
|
exitid = int(node.attrib['id'])
|
|
else:
|
|
print("Exit '{0}' with no id, ommitting.".format(name))
|
|
return None
|
|
if 'location' in node.attrib:
|
|
loc = node.attrib['location'].split(',')
|
|
x, y = int(loc[0]), int(loc[1])
|
|
else:
|
|
print("Exit '{0}' without a location, ommitting.".format(name))
|
|
return None
|
|
if 'destination' in node.attrib:
|
|
destination = node.attrib['destination']
|
|
else:
|
|
print("Exit '{0}' with no destination, ommitting.".format(name))
|
|
return None
|
|
if 'prefix' in node.attrib:
|
|
prefix = node.attrib['prefix']
|
|
graphic = GameMap.__loadGraphic(node)
|
|
#level.addThing(MapExit(name, x, y, exitid, destination, prefix))
|
|
if prevMap == exitid:
|
|
level.playerStart = (x, y)
|
|
return MapExit(name, x, y, exitid, destination, prefix, graphic)
|
|
|
|
@staticmethod
|
|
def __loadDoor(node):
|
|
x, y = 0, 0
|
|
name = ''
|
|
locked = False
|
|
doorOpen = False
|
|
key = None
|
|
if 'name' in node.attrib:
|
|
name = node.attrib['name']
|
|
else:
|
|
print("Nameless door, ommitting.")
|
|
return None
|
|
if 'location' in node.attrib:
|
|
pos = node.attrib['location'].split(',')
|
|
x, y = int(pos[0]), int(pos[1])
|
|
else:
|
|
print("Door '{0}' without a location, ommitting.".format(name))
|
|
return None
|
|
if 'locked' in node.attrib:
|
|
if node.attrib['locked'] == 'True':
|
|
locked = True
|
|
if 'open' in node.attrib:
|
|
if node.attrib['open'] == 'True':
|
|
doorOpen = True
|
|
if 'key' in node.attrib:
|
|
key = node.attrib['key']
|
|
graphic = GameMap.__loadGraphic(node)
|
|
description = GameMap.__cleanStr(node.text, ' ')
|
|
return Door(name, x, y, locked, description, key, graphic)
|
|
|
|
@staticmethod
|
|
def __loadUseable(node):
|
|
x, y = 0, 0
|
|
playerx, playery = 0, 0
|
|
name = ''
|
|
useFunc = ''
|
|
description = ''
|
|
if 'name' in node.attrib:
|
|
name = node.attrib['name']
|
|
else:
|
|
print("Nameless useable, ommitting.")
|
|
return None
|
|
if 'location' in node.attrib:
|
|
pos = node.attrib['location'].split(',')
|
|
x, y = int(pos[0]), int(pos[1])
|
|
else:
|
|
print("Useable '{0}' without a location, ommitting.".format(name))
|
|
return None
|
|
if 'useLocation' in node.attrib:
|
|
loc = node.attrib['useLocation'].split(',')
|
|
playerx, playery = int(loc[0]), int(loc[1])
|
|
else:
|
|
playerx, playery = x, y
|
|
if 'useFunc' in node.attrib:
|
|
useFunc = node.attrib['useFunc']
|
|
else:
|
|
print("Unuseable useable '{0}', ommitting.".format(name))
|
|
return None
|
|
graphic = GameMap.__loadGraphic(node)
|
|
description = GameMap.__cleanStr(node.text, ' ')
|
|
|
|
# get custom values
|
|
customVals = {}
|
|
for val in node:
|
|
if val.tag == 'graphic':
|
|
continue # skip it
|
|
value = None
|
|
valName = ''
|
|
if 'name' in val.attrib:
|
|
valName = val.attrib['name']
|
|
else:
|
|
print("Nameless custom value in {0}, ommitting.".format(name))
|
|
continue
|
|
if 'value' in val.attrib:
|
|
if val.tag == 'int':
|
|
value = int(val.attrib['value'])
|
|
elif val.tag == 'float':
|
|
value = float(val.attrib['value'])
|
|
elif val.tag == 'str':
|
|
value = str(val.attrib['value'])
|
|
elif val.tag == 'item':
|
|
value = GameMap.__loadItem(val)
|
|
elif val.tag == 'intList':
|
|
value = [int(i) for i in val.attrib['value'].split(',')]
|
|
elif val.tag == 'floatList':
|
|
value = [float(i) for i in val.attrib['value'].split(',')]
|
|
elif val.tag == 'strList':
|
|
value = [i.lstrip() for i in val.attrib['value'].split(',')] # depricated, use 'list' of strings instead so you can use commas.
|
|
else:
|
|
print("Value {0} in {1} is of unknown type {2}, ommitting.".format(valName, name, val.tag))
|
|
continue
|
|
elif val.tag == 'list':
|
|
value = []
|
|
for i in val:
|
|
if 'value' in i.attrib:
|
|
if i.tag == 'int':
|
|
value.append(int(i.attrib['value']))
|
|
elif i.tag == 'float':
|
|
value.append(float(i.attrib['value']))
|
|
elif i.tag == 'str':
|
|
value.append(str(i.attrib['value']))
|
|
elif i.tag == 'item':
|
|
value.append(GameMap.__loadItem(i))
|
|
else:
|
|
print("Value {0} in {1} has an entry of unknown type {2}, ommitting.".format(valName, name, i.tag))
|
|
continue
|
|
else:
|
|
print("Value {0} in {1} has an entry with no value, ommitting.".format(valName, name))
|
|
continue
|
|
else:
|
|
print("Value {0} in {1} has no value, ommitting.".format(valName, name))
|
|
continue
|
|
customVals[valName] = value
|
|
|
|
return Useable(name, x, y, description, useFunc, customVals, playerx, playery, graphic)
|
|
|
|
@staticmethod
|
|
def __loadItem(node):
|
|
x, y = 0, 0
|
|
playerx, playery = 0, 0
|
|
name = ''
|
|
useFunc = ''
|
|
useOnFunc = ''
|
|
description = ''
|
|
ranged = False
|
|
if 'name' in node.attrib:
|
|
name = node.attrib['name']
|
|
else:
|
|
print("Nameless item, ommitting.")
|
|
return None
|
|
if 'location' in node.attrib:
|
|
pos = node.attrib['location'].split(',')
|
|
x, y = int(pos[0]), int(pos[1])
|
|
else:
|
|
print("Item '{0}' without a location, ommitting.".format(name))
|
|
return None
|
|
if 'useFunc' in node.attrib:
|
|
useFunc = node.attrib['useFunc']
|
|
if 'useOnFunc' in node.attrib:
|
|
useOnFunc = node.attrib['useOnFunc']
|
|
if 'ranged' in node.attrib:
|
|
ranged = (node.attrib['ranged'].casefold() == 'true')
|
|
graphic = GameMap.__loadGraphic(node)
|
|
description = GameMap.__cleanStr(node.text, ' ')
|
|
|
|
# get custom values
|
|
customVals = {}
|
|
for val in node:
|
|
if val.tag == 'graphic':
|
|
continue # skip it
|
|
value = None
|
|
valName = ''
|
|
if 'name' in val.attrib:
|
|
valName = val.attrib['name']
|
|
else:
|
|
print("Nameless custom value in {0}, ommitting.".format(name))
|
|
continue
|
|
if 'value' in val.attrib:
|
|
if val.tag == 'int':
|
|
value = int(val.attrib['value'])
|
|
elif val.tag == 'float':
|
|
value = float(val.attrib['value'])
|
|
elif val.tag == 'str':
|
|
value = str(val.attrib['value'])
|
|
elif val.tag == 'item':
|
|
value = GameMap.__loadItem(val)
|
|
elif val.tag == 'intList':
|
|
value = [int(i) for i in val.attrib['value'].split(',')]
|
|
elif val.tag == 'floatList':
|
|
value = [float(i) for i in val.attrib['value'].split(',')]
|
|
elif val.tag == 'strList':
|
|
value = [i.lstrip() for i in val.attrib['value'].split(',')] # depricated, use 'list' of strings instead so you can use commas.
|
|
else:
|
|
print("Value {0} in {1} is of unknown type {2}, ommitting.".format(valName, name, val.tag))
|
|
continue
|
|
elif val.tag == 'list':
|
|
value = []
|
|
for i in val:
|
|
if 'value' in i.attrib:
|
|
if i.tag == 'int':
|
|
value.append(int(i.attrib['value']))
|
|
elif i.tag == 'float':
|
|
value.append(float(i.attrib['value']))
|
|
elif i.tag == 'str':
|
|
value.append(str(i.attrib['value']))
|
|
elif i.tag == 'item':
|
|
value.append(GameMap.__loadItem(i))
|
|
else:
|
|
print("Value {0} in {1} has an entry of unknown type {2}, ommitting.".format(valName, name, i.tag))
|
|
continue
|
|
else:
|
|
print("Value {0} in {1} has an entry with no value, ommitting.".format(valName, name))
|
|
continue
|
|
else:
|
|
print("Value {0} in {1} has no value, ommitting.".format(valName, name))
|
|
continue
|
|
customVals[valName] = value
|
|
|
|
return Item(name, x, y, description, useFunc, useOnFunc, customVals, ranged, graphic)
|
|
|
|
@staticmethod
|
|
def __loadGraphic(node):
|
|
bgc = 'clear'
|
|
fgc = '#7F7F7F'
|
|
shape = '#'
|
|
graphicInDecl = False
|
|
if 'bgc' in node.attrib:
|
|
bgc = node.attrib['bgc']
|
|
graphicInDecl = True
|
|
if 'fgc' in node.attrib:
|
|
fgc = node.attrib['fgc']
|
|
graphicInDecl = True
|
|
if 'shape' in node.attrib:
|
|
shape = node.attrib['shape']
|
|
graphicInDecl = True
|
|
|
|
if not graphicInDecl:
|
|
#check for graphic child node
|
|
graphic = node.find('graphic')
|
|
if graphic != None:
|
|
if 'bgc' in graphic.attrib:
|
|
bgc = graphic.attrib['bgc']
|
|
if 'fgc' in graphic.attrib:
|
|
fgc = graphic.attrib['fgc']
|
|
if 'shape' in graphic.attrib:
|
|
shape = graphic.attrib['shape']
|
|
return (bgc, fgc, shape)
|
|
|
|
# stuff the gameshell itself might use
|
|
|
|
def addThing(self, thing, persist = False):
|
|
if thing == None:
|
|
return
|
|
if thing.name in self.thingNames:
|
|
raise ValueError("Cannot have two objects named {0}.".format(thing.name))
|
|
pos = self.coordsToInt(thing.x, thing.y)
|
|
if pos not in self.thingPos:
|
|
self.thingPos[pos] = [thing.name]
|
|
else:
|
|
self.thingPos[pos].append(thing.name)
|
|
self.thingNames[thing.name] = thing
|
|
if persist:
|
|
self.persistent.append(thing.name)
|
|
|
|
def removeThing(self, name):
|
|
thing = self.getThingByName(name)
|
|
if thing:
|
|
oldPos = self.coordsToInt(thing.x, thing.y)
|
|
if oldPos in self.thingPos:
|
|
self.thingPos[oldPos].remove(name)
|
|
if len(self.thingPos[oldPos]) == 0:
|
|
del self.thingPos[oldPos]
|
|
del self.thingNames[name]
|
|
|
|
def path(self, x1, y1, x2, y2, closeEnough = True):
|
|
startThing = self.getThingAtCoords(x1, y1)
|
|
if not closeEnough:
|
|
if startThing and not startThing.passable:
|
|
return -1, [] # meaning you can't get there
|
|
dist, prev = self.dijkstra(x1, y1, closeEnough)
|
|
endPoint = self.coordsToInt(x2, y2)
|
|
numVertex = self.dimensions[0] * self.dimensions[1]
|
|
if dist[endPoint] < numVertex + 1:
|
|
return dist[endPoint], prev
|
|
else:
|
|
return -1, [] # meaning you can't get there
|
|
|
|
def dijkstra(self, x1, y1, closeEnough = True):
|
|
"""Uses Dijkstra's Algorithm to find the shortest path from (x1, y1) to (x2, y2)
|
|
The closeEnough parameter will create a path that lands beside the source if necessary."""
|
|
# first test to see that the start point is passable
|
|
startThing = self.getThingAtCoords(x1, y1)
|
|
startPoint = self.coordsToInt(x1, y1)
|
|
#endPoint = self.coordsToInt(x2, y2)
|
|
numVertex = self.dimensions[0] * self.dimensions[1]
|
|
dist = [numVertex + 1 for i in range(numVertex)]
|
|
prev = [-1 for i in range(numVertex)]
|
|
dist[startPoint] = 0
|
|
if closeEnough:
|
|
if startThing and not startThing.passable:
|
|
dist[startPoint] = -1
|
|
queue = []
|
|
heapq.heappush(queue, (dist[startPoint], startPoint))
|
|
|
|
while len(queue) > 0:
|
|
u = heapq.heappop(queue)[1]
|
|
for v in self.mapGraph[u]:
|
|
thing = self.getThingAtPos(v)
|
|
if thing and not thing.passable:
|
|
continue
|
|
tempDist = dist[u] + 1
|
|
if tempDist < dist[v]:
|
|
dist[v] = tempDist
|
|
if dist[u] != -1:
|
|
prev[v] = u
|
|
heapq.heappush(queue, (dist[v], v))
|
|
|
|
return dist, prev
|
|
#if dist[endPoint] < numVertex + 1:
|
|
# return dist[endPoint], prev
|
|
#else:
|
|
# return -1, [] # meaning you can't get there
|
|
|
|
def lineOfSight(self, x1, y1, x2, y2):
|
|
pass
|
|
|
|
@staticmethod
|
|
def __coordsToInt(x, y, width):
|
|
return x + y * width
|
|
|
|
def coordsToInt(self, x, y, width = -1):
|
|
if width < 0:
|
|
return x + y * self.dimensions[0]
|
|
else:
|
|
return x + y * width
|
|
|
|
def intToCoords(self, pos):
|
|
return pos % self.dimensions[0], int(pos / self.dimensions[0])
|
|
|
|
def getThingAtCoords(self, x, y):
|
|
return self.getThingAtPos(self.coordsToInt(x, y))
|
|
|
|
def getThingsAtCoords(self, x, y):
|
|
return self.getThingsAtPos(self.coordsToInt(x, y))
|
|
|
|
def getThingAtPos(self, pos):
|
|
if pos in self.thingPos:
|
|
return self.thingNames[self.thingPos[pos][0]]
|
|
else:
|
|
return None
|
|
|
|
def getThingsAtPos(self, pos):
|
|
if pos in self.thingPos:
|
|
ret = []
|
|
for i in self.thingPos[pos]:
|
|
ret.append(self.thingNames[i])
|
|
return ret
|
|
else:
|
|
return None
|
|
|
|
def getThingByName(self, name):
|
|
if name in self.thingNames:
|
|
return self.thingNames[name]
|
|
else:
|
|
return None
|
|
|
|
def moveThing(self, name, x, y = -1):
|
|
newPos = x
|
|
if y != -1:
|
|
newPos = self.coordsToInt(x, y)
|
|
else:
|
|
x, y = self.intToCoords(x)
|
|
thing = self.getThingByName(name)
|
|
if thing:
|
|
oldPos = self.coordsToInt(thing.x, thing.y)
|
|
if oldPos in self.thingPos:
|
|
self.thingPos[oldPos].remove(name)
|
|
if len(self.thingPos[oldPos]) == 0:
|
|
del self.thingPos[oldPos]
|
|
if newPos not in self.thingPos:
|
|
self.thingPos[newPos] = [name]
|
|
else:
|
|
self.thingPos[newPos].append(name)
|
|
thing.x, thing.y = x, y
|
|
else:
|
|
raise RuntimeError("There is nothing by the name of {0}.".format(name))
|
|
|