import argparse
import json
import math
import itertools
import os
def main():
parser = argparse.ArgumentParser()
parser.add_argument("input", help="input DPS file")
parser.add_argument("-o", dest="output", help="output xml file")
parser.add_argument("-f", dest="Folder", type=str,
help="output folder name. Just use the same basename")
parser.add_argument("-r", dest="RATIO", default=100, type=int,
help="set the number of pixels per square")
parser.add_argument("-t", dest="THICC", default=1, type=float,
help="change ratio of wall thickness. 0 is for just a line. 1 is for default thickness")
parser.add_argument("-p", dest="PP", action='store_const', default=False,
const=True, help="Just pretty print json (for debugging)")
parser.add_argument("--trees", dest="TREES", action='store_const', default=False,
const=True, help="Find and create terrain around trees")
parser.add_argument("--terrain", dest="TERRAIN", action='store_const', default=False,
const=True, help="Find and create terrain (incomplete - only works with polygons)")
parser.add_argument("-x", dest="SHIFT_X", default=0, type=int,
help="Shift the placement of items in the x direction by n pixels")
parser.add_argument("-y", dest="SHIFT_Y", default=0, type=int,
help="Shift the placement of items in the y direction by n pixels")
global args
args = parser.parse_args()
if args.output is None and args.Folder is None:
raise Exception("Need either -o or -f option")
if args.THICC < 0:
raise Exception("thickness needs to be a positive number")
readDPS(args.input)
if args.output is not None:
createXML(args.output)
if args.Folder is not None:
#unclear why a the srting ends with a quote sometimes
if args.Folder.endswith("\""):
args.Folder = args.Folder[:-1]
dropped_extention = os.path.basename(args.input).split(".")[0]
output = os.path.join(args.Folder, dropped_extention)+".xml"
createXML(output)
def readDPS(filename):
with open(filename) as file:
global data
data = json.loads("".join(file))
if args.PP:
prettyPrint(data)
exit(0)
tables = data["tables"]
#TODO Remove getEdges Function and getRelativePoints
#function. Do all space transformations in convertPoint
#get edges of map
getEdges(tables)
for layer in viableLayers(tables):
Wall.check(layer, tables)
Door.check(layer, tables)
Secret.check(layer, tables)
if args.TREES:
TreeTerrain.check(layer, tables)
Column.check(layer, tables)
if args.TERRAIN:
Terrain.check(layer, tables)
def viableLayers(tables):
#Genarate a sequence of layers that are not orphaned...
#1. find Root
try:
root = next(layer for layer in tables["Bunch"] if layer["name"] == "root" and layer["id"] == 1)
except StopIteration:
raise Exception("Could not find a root layer!!")
#recursively iterate on layers and bunches
for layer in processLayer(root["layers"], tables):
yield layer
def processLayer(layers, tables):
for layer in layers:
bunch = findFirst(layer, tables["Bunch"])
sub = findFirst(layer, tables["Layer"])
if sub is not None:
if bunch is not None:
raise Exception("layer and bunch both exists!")
else:
yield sub
else:
if bunch is None:
raise Exception("Could not find layer")
else:
for l in processLayer(bunch["layers"], tables):
yield l
def findFirst(id, table):
try:
return next(x for x in table if x['id'] == id)
except StopIteration:
return None
def createXML(output):
xmlString = xmlStart()
for wall in Wall.walls:
xmlString += wall.getXML()
for door in Door.doors:
xmlString += door.getXML()
for secret in Secret.secrets:
xmlString += secret.getXML()
if args.TREES:
for tree in TreeTerrain.trees:
xmlString += tree.getXML()
for column in Column.columns:
xmlString += column.getXML()
if args.TERRAIN:
for ter in Terrain.terrain:
xmlString += ter.getXML()
xmlString += xmlEnd()
with open(output, "w") as file:
file.write(xmlString)
def xmlStart():
return "\n\n"
def xmlEnd():
return "\n\n"
class Terrain(object):
terrain = []
"""docstring for Terrain"""
def __init__(self, layer, data):
super(Terrain, self).__init__()
self.points = list(map(getRelativePoints, data["points"]))
def getXML(self):
occ = Occluder(self.points, terrain=True)
return occ.get()
@staticmethod
def check(layer, tables):
if layer["name"].startswith("terrain"):
Terrain.terrain.append(Terrain(layer, getFigure(layer["data"], tables)))
def getFigure(id, tables):
res = findData(id, tables["Polygon"])["figures"]
if len(res) != 1:
raise Exception("Not exactly 1 figure")
return findData(res[0], tables["Figure"])
class Wall(object):
walls = []
"""docstring for Wall"""
def __init__(self, data):
super(Wall, self).__init__()
self.points = list(map(getRelativePoints, data["points"]))
self.thickness = data["thickness"]
def getXML(self):
if args.THICC == 0:
return self.getSimpleXML()
else:
return self.getComplexXML()
def getComplexXML(self):
result = ""
for a,b in pairwise(self.points):
box = makeBox(a, b, self.thickness / 10 * args.THICC)
if box:
occ = Occluder(box)
result += occ.get()
return result
def getSimpleXML(self):
occ = Occluder(self.points)
return occ.get()
@staticmethod
def check(layer, tables):
if layer["name"].startswith("wall"):
Wall.walls.append(Wall(findData(layer["data"], tables["Wall"])))
def findData(id, table):
out = [x for x in table if x['id'] == id]
if len(out) != 1:
raise Exception("Not exactly 1 object! "+str(len(out)))
return out[0]
def getRelativePoints(point):
return {'x': point['x'] - minX, 'y': maxY - point['y']}
def convertPoint(pnt):
#move 0,0 to middle of the image and adjust from cell based to pixel based quardinates
middleX = int((maxX - minX) / 2)
middleY = int((maxY - minY) / 2)
return "{:.2f},{:.2f}".format((pnt['x'] - middleX) * args.RATIO + args.SHIFT_X, (pnt['y'] - middleY) * args.RATIO + args.SHIFT_Y)
class Door(object):
doors=[]
"""docstring for Door"""
def __init__(self, layer, data):
super(Door, self).__init__()
#is it a double door
self.double = "double" in layer["name"]
#get angle in radians
self.angle = -data["angle"] / 180 * math.pi
self.scale = data["scale"]
self.position = addVector(getRelativePoints(data["begin"]), self.scale/2*math.sin(self.angle), -self.scale/2*math.cos(self.angle))
def getPoints(self):
unit = self.scale / 2
a = unit * math.cos(self.angle)
b = unit * math.sin(self.angle)
if not self.double:
x = addVector(self.position, a, b)
else:
#double doors just extend out in one direction
#since vector (a,b) is a half the lenth of a
#single door, triple it
x = addVector(self.position, a*3, b*3)
box = makeBox(addVector(self.position, -a, -b), x, 0.1 * self.scale * args.THICC)
return box
def getXML(self):
occ = Occluder(self.getPoints(), door=True)
return occ.get()
@staticmethod
def check(layer, tables):
if "door" in layer["name"]:
Door.doors.append(Door(layer, findData(layer["data"], tables["Obstacle"])))\
class Secret(Wall):
secrets=[]
"""docstring for Secret"""
def __init__(self, arg):
super(Secret, self).__init__(arg)
def getXML(self):
result = ""
for a,b in pairwise(self.points):
occ = Occluder(makeBox(a, b, self.thickness / 10 * args.THICC), secret = True)
result += occ.get()
return result
@staticmethod
def isSecret(layer):
return layer["name"].startswith("secret") or layer["name"].startswith("Secret")
@staticmethod
def check(layer, tables):
if Secret.isSecret(layer):
Secret.secrets.append(Secret(findData(layer["data"], tables["Wall"])))
class TreeTerrain(object):
trees = []
"""docstring for TreeTerrain"""
def __init__(self, layer, data):
self.small = "small" in layer["name"]
self.mid = "mid" in layer["name"]
self.big = "big" in layer["name"]
self.angle = data["angle"] / 180 * math.pi
self.scale = data["scale"]
if self.mid:
#middle sized trees have their center point offset from the
#image's center
v = {'x': self.scale / 2, 'y': self.scale / 2}
v = rotateVector(v, self.angle)
self.position = getRelativePoints(addVector((data["begin"]), v['x'], v['y']))
else:
self.position = getRelativePoints(data["begin"])
def getXML(self):
occ = Occluder(self.makeShape(), terrain = True)
return occ.get()
def makeShape(self):
dist = self.scale / 2
if self.small:
dist /= 2
if self.mid:
dist /= 1.5
return drawCircle(self.position, dist/2)
@staticmethod
def check(layer, tables):
if "tree" in layer["name"]:
TreeTerrain.trees.append(TreeTerrain(layer, findData(layer["data"], tables["Obstacle"])))
class Column(object):
columns = []
"""docstring for Column"""
def __init__(self, layer, data):
self.scale = data["scale"]
self.position = getRelativePoints(data["begin"])
def getXML(self):
occ = Occluder(self.makeShape())
return occ.get()
def makeShape(self):
return drawCircle(self.position, self.scale / 2, close=True)
@staticmethod
def check(layer, tables):
if "column" in layer["name"]:
Column.columns.append(Column(layer, findData(layer["data"], tables["Obstacle"])))
def drawCircle(point, radius, steps=8, close=False):
vect = {'x':radius, 'y':0}
points = []
for step in range(steps):
angle = 2 * math.pi / steps * step
v = rotateVector(vect, angle)
points.append(addVector(point, v['x'], v['y']))
if close:
points.append(addVector(point, vect['x'], vect['y']))
return points
def rotateVector(vect, angle):
return {
'x': math.cos(angle) * vect['x'] - math.sin(angle) * vect['y'],
'y': math.sin(angle) * vect['x'] + math.cos(angle) * vect['y']
}
class Occluder(object):
ID = 1
"""docstring for Occluder"""
def __init__(self, points, terrain=False, door=False, secret=False):
super(Occluder, self).__init__()
self.points = points
self.terrain = terrain
self.door = door
self.secret = secret
self.id = Occluder.ID
Occluder.ID += 1
def get(self):
return self.getXMLStart() + self.pointsToXML() + self.extraTag() + self.getXMLEnd()
def getXMLStart(self):
return f"\n{self.id}\n"
def pointsToXML(self):
return ""+",".join(map(convertPoint, self.points))+"\n"
def extraTag(self):
#there should be at most one extra tag right?
if self.terrain:
return "true\n"
if self.door:
return "true\n"
if self.secret:
return "true\n"
return ""
def getXMLEnd(self):
return "\n"
def makeBox(pointA, pointB, thickness=0.1):
#make a box from a vector
#https://math.stackexchange.com/questions/60336/how-to-find-a-rectangle-which-is-formed-from-the-lines
dist = math.sqrt(math.pow(pointB['x'] - pointA['x'], 2) + math.pow(pointB['y'] - pointA['y'], 2))
if dist == 0:
return []
ajustmentX = (pointB['y'] - pointA['y']) / dist * thickness
ajustmentY = (pointA['x'] - pointB['x']) / dist * thickness
return [
addVector(pointA, ajustmentX, ajustmentY),
addVector(pointA, -ajustmentX, -ajustmentY),
addVector(pointB, -ajustmentX, -ajustmentY),
addVector(pointB, ajustmentX, ajustmentY),
addVector(pointA, ajustmentX, ajustmentY)
]
def addVector(point, x, y):
return {'x':point['x'] + x, 'y':point['y'] + y}
def pairwise(iterable):
#s -> (s0,s1), (s1,s2), (s2, s3), ...
a, b = itertools.tee(iterable)
next(b, None)
return zip(a, b)
def getEdges(tables):
#get a of figures that define polygons
availibleFigures = set()
for poly in tables["Polygon"]:
availibleFigures |= set(poly["figures"])
for objectTypes in tables:
#some items in figure table are no longer
#in use. Ignore them
if objectTypes == "Figure":
for obj in tables[objectTypes]:
if obj["id"] in availibleFigures:
checkPoints(obj)
else:
for obj in tables[objectTypes]:
checkPoints(obj)
minX = None
maxX = None
minY = None
maxY = None
def checkPoints(obj):
if "points" in obj:
for point in obj["points"]:
updatePoints(point)
def updatePoints(point):
global minX, maxX, minY, maxY
minX = minSpecial(minX, point['x'])
minY = minSpecial(minY, point['y'])
maxX = maxSpecial(maxX, point['x'])
maxY = maxSpecial(maxY, point['y'])
def minSpecial(a, b):
if a is None:
return b
else:
return min(a, b)
def maxSpecial(a, b):
if a is None:
return b
else:
return max(a, b)
def prettyPrint(data):
print(json.dumps(data, sort_keys=True, indent=4, separators=(',', ': ')))
if __name__ == "__main__":
main()