All map files converted to new YAML standard.

This commit is contained in:
Patrick Marsee 2019-03-09 19:08:46 -05:00
parent c5029ac0fc
commit adfb6b83f4
17 changed files with 3123 additions and 2691 deletions

1
.gitignore vendored
View file

@ -2,3 +2,4 @@ maps
saves
__pycache__
winsh.py
yamltest.py

View file

@ -3,7 +3,7 @@ import re
import heapq
import xml.etree.ElementTree as ET
import ruamel.yaml
from ruamel.yaml.comments import CommentedMap
from ruamel.yaml.comments import CommentedMap # for loading classes
class Thing(object):
@ -36,9 +36,10 @@ class Thing(object):
return self.name == other.name
class Item(Thing):
yaml_tag = u'!Item'
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 = ('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
@ -51,26 +52,43 @@ class Item(Thing):
@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] != 'clear':
if node.graphic[0] != Item.defaultGraphic[0]:
graphic['bgc'] = node.graphic[0]
if node.graphic[1] != '#00BF00':
if node.graphic[1] != Item.defaultGraphic[1]:
graphic['fgc'] = node.graphic[1]
if node.graphic[2] != '^':
if node.graphic[2] != Item.defaultGraphic[2]:
graphic['shape'] = node.graphic[2]
ret = {'name': node.name, 'location': (node.x, node.y),
'description': node.description, 'graphic': graphic,
'useFunc': node.useFunc, 'useOnFunc': node.useOnFunc,
'customValues': node.customValues, 'ranged': node.ranged}
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)
bgc = 'clear'
fgc = '#00BF00'
shape = '^'
# 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']:
@ -78,13 +96,26 @@ class Item(Thing):
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'], parts['useFunc'], parts['useOnFunc'],
dict(parts['customValues']), parts['ranged'], graphic)
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 = ('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
@ -93,6 +124,62 @@ class Useable(Thing):
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')):
@ -112,8 +199,10 @@ class NPC(Thing):
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 = ('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:
@ -146,9 +235,64 @@ class Door(Thing):
return True
return False
class MapExit(Thing):
@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)
def __init__(self, name, x: int, y: int, exitid: int, destination: str, prefix: None, graphic = ('clear', '#FF0000', 'x')):
@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'!Exit'
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)
@ -158,19 +302,54 @@ class MapExit(Thing):
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 = 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)
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)
# d: door
# x: map change
# w: wall (0)
# c: NPC (should this be a subclass of interactable?)
# i: item
# u: useable
# p: player start (up to one per level)
# regular expressions
tileRegex = re.compile(r'([a-z ])([0-9]+|[ ])')
@ -190,6 +369,106 @@ class GameMap(object):
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
@ -403,7 +682,7 @@ list of lists of tuples."""
if 'key' in node.attrib:
key = node.attrib['key']
graphic = GameMap.__loadGraphic(node)
description = node.text
description = GameMap.__cleanStr(node.text, ' ')
return Door(name, x, y, locked, description, key, graphic)
@staticmethod
@ -435,7 +714,7 @@ list of lists of tuples."""
print("Unuseable useable '{0}', ommitting.".format(name))
return None
graphic = GameMap.__loadGraphic(node)
description = node.text
description = GameMap.__cleanStr(node.text, ' ')
# get custom values
customVals = {}
@ -519,7 +798,7 @@ list of lists of tuples."""
if 'ranged' in node.attrib:
ranged = (node.attrib['ranged'].casefold() == 'true')
graphic = GameMap.__loadGraphic(node)
description = node.text
description = GameMap.__cleanStr(node.text, ' ')
# get custom values
customVals = {}

View file

@ -31,15 +31,15 @@ layout: |
w w w w w0
w w w w w w0
w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w0
things:
loadAlways:
- !Exit
id: 1
location: [5, 10]
destination: testing/test2.xml
destination: testing/test2.yml
name: upstairs
- !Exit
id: 2
location: [21, 23]
destination: testing/test4.xml
destination: testing/test4.yml
name: downstairs

62
testing/test2.yml Normal file
View file

@ -0,0 +1,62 @@
%YAML 1.2
---
layout: |
w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0
w0w0w0w0w0w0 w0 w0 w0 w0w0w0w0w0w0
w0w0w0w0 w0 w0 w0w0 w0 w0w0w0w0
w0w0w0w0 w0 w0 w0w0 w0w0
w0w0 w0 w0w0w0 w0
w0w0 w0w0w0 w0w0w0w0w0 w0
w0 w0 w0 w0 w0w0w0w0w0 w0
w0 w0 w0 w0 w0w0w0w0w0 w0
w0 w0 w0w0w0w0 w0w0w0w0w0w0w0w0w0w0
w0w0w0w0w0w0w0w0w0w0 w0w0w0 w0w0 w0w0w0w0
w0 w0w0 w0 w0
w0 w0 w0w0w0 w0 w0
w0 w0w0 w0w0w0w0 w0 w0
w0 w0w0 w0w0w0w0w0w0w0w0w0 w0 w0w0w0w0
w0w0w0w0w0 w0w0w0 w0w0w0w0w0w0w0 w0w0 w0
w0w0w0w0 w0w0w0 w0w0w0w0w0w0w0 w0w0 w0
w0 w0w0w0w0w0 w0 w0 w0w0w0w0w0w0w0w0w0w0w0w0
w0 w0w0w0w0w0 w0 w0 e0
w0w0w0w0w0w0w0w0 w0 w0 e0
w0w0w0w0w0w0 w0 w0 w0 e0
w0 w0 w0 w0 e0
w0 w0 w0 w0 e0
w0 w0w0w0w0 w0 e0
w0 w0w0w0w0 w0 w0 e0
w0 w0 w0 w0 e0
w0 w0 w0 w0 e0
w0 w0 w0w0w0w0 e0
w0w0w0w0w0w0w0w0w0w0w0w0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0
openingText: Floor 2
playerStart: [6, 10]
description: ''
floorColors: ['#9F7F5F']
wallColors: ['#7F3F0F']
loadAlways:
- !Exit
name: downstairs
location: [5, 10]
id: 1
destination: testing/test1.yml
graphic: {fgc: '#7F7F7F', shape: '#'}
- !Exit
name: upstairs
location: [7, 10]
id: 2
destination: testing/test3.yml
graphic: {fgc: '#7F7F7F', shape: '#'}
- !Exit
name: north
location: [6, 1]
id: 3
destination: testing/test3.yml
graphic: {fgc: '#7F7F7F', shape: '#'}
- !Exit
name: east
location: [25, 14]
id: 4
destination: testing/test3.yml
graphic: {fgc: '#7F7F7F', shape: '#'}
loadOnce: []

46
testing/test3.yml Normal file
View file

@ -0,0 +1,46 @@
%YAML 1.2
---
layout: |
w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0
w0 w0w0w0w0 w0w0w0w0w0w0w0w0 w0w0w0w0w0w0
w0 w0w0 w0 w0 w0w0w0w0
w0 w0w0 w0 w0 w0w0w0w0
w0w0w0 w0 w0w0 w0w0w0
w0w0w0 w0w0 w0w0 w0w0w0
w0w0w0w0w0w0w0w0w0w0 w0 w0w0 w0
w0w0w0w0w0w0w0w0w0w0 w0 w0w0 w0
w0w0w0w0w0 w0w0w0w0 w0 w0w0w0w0w0w0 w0
w0w0w0w0w0 w0w0w0 w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0
w0 w0w0 w0w0 w0w0 w0 w0
w0 w0w0 w0w0w0 w0 w0
w0 w0w0 w0w0 w0w0w0 w0w0 w0 w0
w0 w0w0 w0w0w0w0w0w0w0w0w0w0w0w0w0w0 w0w0w0w0w0w0
w0w0w0w0 w0w0w0w0 w0 w0
w0w0w0w0 w0w0w0w0 w0 w0
w0 w0w0w0w0w0w0 w0 w0
w0w0w0w0w0w0w0w0w0w0w0w0e0e0e0e0e0e0e0e0e0e0w0w0w0w0w0w0w0w0
openingText: Floor 3
playerStart: [6, 10]
description: ''
floorColors: ['#9F7F5F']
wallColors: ['#7F3F0F']
loadAlways:
- !Exit
name: downstairs
location: [7, 10]
id: 2
destination: testing/test2.yml
graphic: {fgc: '#7F7F7F', shape: '#'}
- !Exit
name: north
location: [6, 1]
id: 3
destination: testing/test2.yml
graphic: {fgc: '#7F7F7F', shape: '#'}
- !Exit
name: east
location: [25, 14]
id: 4
destination: testing/test2.yml
graphic: {fgc: '#7F7F7F', shape: '#'}
loadOnce: []

44
testing/test4.yml Normal file
View file

@ -0,0 +1,44 @@
%YAML 1.2
---
layout: |
w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0
w0w0w0w0w0w0w0w0 w0w0w0w0w0w0w0w0w0w0w0 w0
w0w0 w0 w0w0 w0 w0 w0 w0
w0w0 w0 w0w0w0 w0 w0 w0 w0
w0 w0w0 w0 w0 w0
w0 w0w0 w0w0w0 w0w0w0w0w0w0w0w0w0w0 w0w0w0w0
w0 w0
w0 w0
w0w0w0w0 w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0 w0
w0 w0 w0 w0
w0 w0 w0 w0
w0 w0w0 w0w0 w0 w0 w0
w0 w0 w0 w0
w0 w0 w0 w0
w0 w0 w0 w0
w0 w0w0 w0w0 w0 w0 w0
w0 w0 w0w0w0 w0w0w0 w0 w0
w0w0w0w0w0w0w0w0w0w0 w0w0w0 w0w0w0 w0 w0w0
w0 w0 w0 w0
w0 w0 w0 w0
w0 w0 w0w0 w0 w0
w0 w0 w0w0 w0 w0
w0 w0 w0
w0 w0 w0
w0 w0 w0 w0
w0 w0 w0 w0 w0
w0 w0 w0 w0
w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0
openingText: Basement
playerStart: [22, 22]
description: ''
floorColors: ['#9F7F5F']
wallColors: ['#7F3F0F']
loadAlways:
- !Exit
name: upstairs
location: [23, 22]
id: 2
destination: testing/test1.xml
graphic: {fgc: '#7F7F7F', shape: '#'}
loadOnce: []