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 saves
__pycache__ __pycache__
winsh.py winsh.py
yamltest.py

View file

@ -3,7 +3,7 @@ import re
import heapq import heapq
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
import ruamel.yaml import ruamel.yaml
from ruamel.yaml.comments import CommentedMap from ruamel.yaml.comments import CommentedMap # for loading classes
class Thing(object): class Thing(object):
@ -36,9 +36,10 @@ class Thing(object):
return self.name == other.name return self.name == other.name
class Item(Thing): 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) super(Item, self).__init__('i', name, x, y, description, 13)
self.useFunc = useFunc self.useFunc = useFunc
self.useOnFunc = useOnFunc self.useOnFunc = useOnFunc
@ -51,40 +52,70 @@ class Item(Thing):
@classmethod @classmethod
def to_yaml(cls, representer, node): def to_yaml(cls, representer, node):
# save usual things
ret = {'name': node.name, 'location': (node.x, node.y), 'description': node.description}
# save graphic
graphic = {} graphic = {}
if node.graphic[0] != 'clear': if node.graphic[0] != Item.defaultGraphic[0]:
graphic['bgc'] = node.graphic[0] graphic['bgc'] = node.graphic[0]
if node.graphic[1] != '#00BF00': if node.graphic[1] != Item.defaultGraphic[1]:
graphic['fgc'] = node.graphic[1] graphic['fgc'] = node.graphic[1]
if node.graphic[2] != '^': if node.graphic[2] != Item.defaultGraphic[2]:
graphic['shape'] = node.graphic[2] graphic['shape'] = node.graphic[2]
ret = {'name': node.name, 'location': (node.x, node.y), if len(graphic) > 0:
'description': node.description, 'graphic': graphic, ret['graphic'] = graphic
'useFunc': node.useFunc, 'useOnFunc': node.useOnFunc, # save use functions
'customValues': node.customValues, 'ranged': node.ranged} 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) return representer.represent_mapping(cls.yaml_flag, ret)
@classmethod @classmethod
def from_yaml(cls, constructor, node): def from_yaml(cls, constructor, node):
parts = CommentedMap() parts = CommentedMap()
constructor.construct_mapping(node, parts, True) constructor.construct_mapping(node, parts, True)
bgc = 'clear' # set default values for optional arguments
fgc = '#00BF00' useFunc = ''
shape = '^' useOnFunc = ''
if 'bgc' in parts['graphic']: customValues = {}
bgc = parts['graphic']['bgc'] ranged = False
if 'fgc' in parts['graphic']: bgc = Item.defaultGraphic[0]
fgc = parts['graphic']['fgc'] fgc = Item.defaultGraphic[1]
if 'shape' in parts['graphic']: shape = Item.defaultGraphic[2]
shape = parts['graphic']['shape'] # 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) 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], return cls(parts['name'], parts['location'][0], parts['location'][1],
parts['description'], parts['useFunc'], parts['useOnFunc'], parts['description'], useFunc, useOnFunc, customValues, ranged, graphic)
dict(parts['customValues']), parts['ranged'], graphic)
class Useable(Thing): 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) super(Useable, self).__init__('u', name, x, y, description, 16, playerx, playery)
self.useFunc = useFunc self.useFunc = useFunc
self.customValues = customValues self.customValues = customValues
@ -93,6 +124,62 @@ class Useable(Thing):
def use(self): def use(self):
pass 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): class NPC(Thing):
def __init__(self, name, x: int, y: int, description: str, friendly = True, following = False, graphic = ('clear', '#000000', 'o')): 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 pass
class Door(Thing): 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 self.descBase = description
if description == None: if description == None:
if locked: if locked:
@ -146,9 +235,64 @@ class Door(Thing):
return True return True
return False 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 description = name
if prefix: if prefix:
description = "{0} {1}".format(prefix, name) description = "{0} {1}".format(prefix, name)
@ -158,19 +302,54 @@ class MapExit(Thing):
self.prefix = prefix self.prefix = prefix
self.graphic = graphic 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): class GameMap(object):
# Matrix tile codes: # Matrix tile codes:
# e: empty (0) # e: empty (0)
# d: door
# x: map change
# w: wall (0) # 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 # regular expressions
tileRegex = re.compile(r'([a-z ])([0-9]+|[ ])') tileRegex = re.compile(r'([a-z ])([0-9]+|[ ])')
@ -190,6 +369,106 @@ class GameMap(object):
self.wallColors = [] self.wallColors = []
self.persistent = [] 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 @staticmethod
def read(infile = None, prevMap = None, preLoaded = False): def read(infile = None, prevMap = None, preLoaded = False):
"""Read map data and return a Map object. If infile is not provided, then """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: if 'key' in node.attrib:
key = node.attrib['key'] key = node.attrib['key']
graphic = GameMap.__loadGraphic(node) graphic = GameMap.__loadGraphic(node)
description = node.text description = GameMap.__cleanStr(node.text, ' ')
return Door(name, x, y, locked, description, key, graphic) return Door(name, x, y, locked, description, key, graphic)
@staticmethod @staticmethod
@ -435,7 +714,7 @@ list of lists of tuples."""
print("Unuseable useable '{0}', ommitting.".format(name)) print("Unuseable useable '{0}', ommitting.".format(name))
return None return None
graphic = GameMap.__loadGraphic(node) graphic = GameMap.__loadGraphic(node)
description = node.text description = GameMap.__cleanStr(node.text, ' ')
# get custom values # get custom values
customVals = {} customVals = {}
@ -519,7 +798,7 @@ list of lists of tuples."""
if 'ranged' in node.attrib: if 'ranged' in node.attrib:
ranged = (node.attrib['ranged'].casefold() == 'true') ranged = (node.attrib['ranged'].casefold() == 'true')
graphic = GameMap.__loadGraphic(node) graphic = GameMap.__loadGraphic(node)
description = node.text description = GameMap.__cleanStr(node.text, ' ')
# get custom values # get custom values
customVals = {} customVals = {}

View file

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