#! /usr/bin/env python2.6
# -*- coding: utf8 -*-
import array
import base64
import gzip
import os
import re
import datetime
import xml
import ogg.vorbis
from xml.dom import minidom
from PIL import Image
import zlib
filt = re.compile(".+[.](xml|tmx)", re.IGNORECASE)
filtmaps = re.compile(".+[.]tmx", re.IGNORECASE)
dyesplit1 = re.compile(";")
dyesplit2 = re.compile(",")
parentDir = "../../clientdata"
iconsDir = "graphics/items/"
spritesDir = "graphics/sprites/"
particlesDir = "graphics/particles/"
sfxDir = "sfx/"
mapsDir = "maps/"
errors = 0
warnings = 0
errDict = set()
safeDye = False
borderSize = 20
class Tileset:
None
class Layer:
None
def printErr(err):
errDict.add(err)
print err
def showFileErrorById(id, rootDir, fileDir):
print "error: id=" + id + ", file not found: " + fileDir + " (" + rootDir + fileDir + ")"
def showFileWarningById(id, rootDir, fileDir):
print "warn: id=" + id + ", file not found: " + fileDir + " (" + rootDir + fileDir + ")"
def showError(id, text):
print "error: id=" + id + " " + text
def showWarning(id, text):
print "warn: id=" + id + " " + text
def showMsg(id, text, src, iserr):
global errors, warnings
if text != "":
text = text + ", " + src
if iserr == True:
if text not in errDict:
showError(id, text)
errDict.add(text)
errors = errors + 1
else:
if text not in errDict:
showWarning(id, text)
errDict.add(text)
warnings = warnings + 1
def showMsgSprite(file, text, iserr):
global errors, warnings
if iserr == True:
err = "error: sprite=" + file + " " + text
if err not in errDict:
printErr(err)
errors = errors + 1
else:
err = "warn: sprite=" + file + " " + text
if err not in errDict:
printErr(err)
warnings = warnings + 1
def showMsgFile(file, text, iserr):
global errors, warnings
if iserr == True:
err = "error: file=" + file + " " + text
if err not in errDict:
printErr(err)
errors = errors + 1
else:
err = "warn: file=" + file + " " + text
if err not in errDict:
printErr(err)
warnings = warnings + 1
def showFileMsgById(id, rootDir, fileDir, iserr):
global errors, warnings
if iserr == True:
showFileErrorById(id, rootDir, fileDir)
errors = errors + 1
else:
showFileWarningById(id, rootDir, fileDir)
warnings = warnings + 1
def printSeparator():
print "--------------------------------------------------------------------------------"
def showHeader():
print "Evol client data validator."
print "Run at: " + datetime.datetime.now().isoformat()
print "http://www.gitorious.org/evol/evol-tools/blobs/master/testxml/testxml.py"
printSeparator()
def showFooter():
printSeparator()
print "Total:"
print " Warnings: " + str(warnings)
print " Errors: " + str(errors)
def enumDirs(parentDir):
global warnings, errors
files = os.listdir(parentDir)
for file1 in files:
if file1[0] == ".":
continue
file2 = os.path.abspath(parentDir + os.path.sep + file1)
if not os.path.isfile(file2):
enumDirs(file2)
elif filt.search(file1):
try:
minidom.parse(file2)
except xml.parsers.expat.ExpatError as err:
print "error: " + file2 + ", line=" + str(err.lineno) + ", char=" + str(err.offset)
errors = errors + 1
def loadPaths():
global warnings
try:
dom = minidom.parse(parentDir + "/paths.xml")
for node in dom.getElementsByTagName("option"):
if node.attributes["name"].value == "itemIcons":
iconsDir = node.attributes["value"].value
elif node.attributes["name"].value == "sprites":
spritesDir = node.attributes["value"].value
elif node.attributes["name"].value == "sfx":
sfxDir = node.attributes["value"].value
elif node.attributes["name"].value == "particles":
particlesDir = node.attributes["value"].value
elif node.attributes["name"].value == "maps":
mapsDir = node.attributes["value"].value
except:
print "warn: paths.xml not found"
warnings = warnings + 1
def splitImage(image):
try:
idx = image.find("|")
if idx > 0:
imagecolor = image[idx + 1:]
image = image[0:idx]
else:
imagecolor = ""
except:
image = ""
imagecolor = ""
return [image, imagecolor]
def testDye(id, color, text, src, iserr):
if len(color) < 4:
showMsg(id, "dye to small size: " + text, src, iserr)
return
colors = dyesplit1.split(color)
for col in colors:
if len(col) < 4:
showMsg(id, "dye to small size: " + text, src, iserr)
continue
c = col[0];
if col[1] != ":":
showMsg(id, "incorrect dye string: " + text, src, iserr)
continue
if c != "R" and c != "G" and c != "B" and c != "Y" and c != "M" \
and c != "C" and c != "W":
showMsg(id, "incorrect dye color: " + c + " in " + text, src, iserr)
continue
if testDyeInternal(id, col[2:], text, src, iserr) == False:
continue
def testDyeInternal(id, col, text, src, iserr):
if col[0] != "#":
showMsg(id, "incorrect dye colors: " + text, src, iserr)
return False
paletes = dyesplit2.split(col[1:])
for palete in paletes:
if len(palete) != 6:
showMsg(id, "incorrect dye palete: " + text, src, iserr)
return False
for char in palete.lower():
if (char < '0' or char > '9') and (char < 'a' or char > 'f'):
showMsg(id, "incorrect dye palete: " + text, src, iserr)
return False
return True
def testDyeColors(id, color, text, src, iserr):
if len(color) < 4:
showMsg(id, "dye to small size: " + text, src, iserr)
return -1
colors = dyesplit1.split(color)
for col in colors:
if len(col) < 4:
showMsg(id, "dye to small size: " + text, src, iserr)
continue
if testDyeInternal(id, col, text, src, iserr) == False:
continue
return len(colors)
def testDyeMark(file, color, text, iserr):
if len(color) < 1:
showMsgSprite(file, "dye mark size to small:" + text, iserr)
return -1
colors = dyesplit1.split(color)
for c in colors:
if len(c) != 1:
showMsgSprite(file, "dye mark incorrect size: " + text, iserr)
continue
if c != "R" and c != "G" and c != "B" and c != "Y" and c != "M" \
and c != "C" and c != "W":
showMsgSprite(file, "dye make incorrect: " + text, iserr)
continue
return len(colors)
def testSprites(id, node, checkGender, iserr):
try:
tmp = node.getElementsByTagName("nosprite")
if tmp is not None and len(tmp) > 1:
showMsg(id, "more than one nosprite tag found", "", iserr)
nosprite = True
except:
nosprite = False
try:
sprites = node.getElementsByTagName("sprite")
except:
sprites = None
if nosprite == False:
showMsg(id, "no sprite tag found", "", iserr)
if sprites is not None:
if len(sprites) == 0:
if nosprite == False:
showMsg(id, "no sprite tags found", "", iserr)
elif len(sprites) > 3 and checkGender:
showMsg(id, "incorrect number of sprite tags", "", iserr)
elif len(sprites) == 1:
file = sprites[0].childNodes[0].data
if checkGender:
try:
gender = sprites[0].attributes["gender"].value
except:
gender = ""
if gender != "" and gender != "unisex":
showMsg(id, "gender tag in alone sprite", "", iserr)
try:
variant = int(sprites[0].attributes["variant"].value)
except:
variant = 0
testSprite(id, file, variant, iserr)
else:
male = False
female = False
for sprite in sprites:
file = sprite.childNodes[0].data
if checkGender:
try:
gender = sprite.attributes["gender"].value
except:
gender = ""
if gender == "male":
if male == True:
showMsg(id, "double male sprite tag", "", iserr)
male = True
elif gender == "female":
if female == True:
showMsg(id, "double female sprite tag", "", iserr)
female = True
elif gender == "unisex":
if female == True or male == True:
showMsg(id, "gender sprite tag with unisex tag", "", False)
male = True
female = True
try:
variant = int(sprite.attributes["variant"].value)
except:
variant = 0
testSprite(id, file, variant, iserr)
if checkGender:
if male == False:
showMsg(id, "no male sprite tag", "",iserr)
if female == False:
showMsg(id, "no female sprite tag", "", iserr)
def testSprite(id, file, variant, iserr):
tmp = splitImage(file)
color = tmp[1]
file2 = tmp[0]
if color != "":
dnum = testDyeColors(id, color, file, "", iserr)
else:
dnum = 0
fullPath = os.path.abspath(parentDir + "/" + spritesDir + file2)
if not os.path.isfile(fullPath) or os.path.exists(fullPath) == False:
showFileMsgById(id, spritesDir, file2, iserr)
else:
testSpriteFile(id, fullPath, file, spritesDir + file2, dnum, variant, iserr)
def testSpriteFile(id, fullPath, file, fileLoc, dnum, variant, iserr):
global safeDye
try:
dom = minidom.parse(fullPath)
except:
return
if len(dom.childNodes) < 1:
return
root = dom.childNodes[0];
imagesets = dom.getElementsByTagName("imageset")
if imagesets is None or len(imagesets) < 1:
showMsgSprite(fileLoc, "incorrect number of imageset tags", iserr)
imageset = imagesets[0]
try:
image = imageset.attributes["src"].value
image0 = image
img = splitImage(image)
image = img[0]
imagecolor = img[1]
except:
showMsgSprite(fileLoc, "image attribute not exist: " + image, iserr)
return
try:
width = imageset.attributes["width"].value
except:
showMsgSprite(fileLoc, "no width attribute", iserr)
return
try:
height = imageset.attributes["height"].value
except:
showMsgSprite(fileLoc, "no height attribute", iserr)
if imagecolor != "":
num = testDyeMark(fileLoc, imagecolor, image0, iserr)
if safeDye == False and dnum != num:
if dnum > num:
e = iserr
else:
e = False
showMsgSprite(fileLoc, "dye colors size not same in sprite (" + str(num) \
+ ") and in caller (" + str(dnum) + ", id=" + str(id) + ")", e)
elif safeDye == True and dnum > 0:
showMsgSprite(fileLoc, "dye set in sprite but not in caller (id=" + str(id) + ")", False)
fullPath = os.path.abspath(parentDir + "/" + image)
if not os.path.isfile(fullPath) or os.path.exists(fullPath) == False:
showMsgSprite(fileLoc, "image file not exist: " + image, iserr)
return
sizes = testImageFile(image, fullPath, 0, iserr)
s1 = int(sizes[0] / int(width)) * int(width)
if sizes[0] != s1:
showMsgSprite(fileLoc, "image width " + str(sizes[0]) + \
" (need " + str(s1) + ") is not multiply to frame size " + width + ", image:" + image, False)
s2 = int(sizes[1] / int(height)) * int(height)
if sizes[1] != s2:
showMsgSprite(fileLoc, "image height " + str(sizes[1]) + \
" (need " + str(s2) + ") is not multiply to frame size " + height + ", image:" + image, False)
num = (s1 / int(width)) * (s2 / int(height))
if variant >= num:
showMsgSprite(fileLoc, "to big variant number " + str(variant) \
+ ". Frames number " + str(num) + ", id=" + str(id), iserr)
if num < 1:
showMsgSprite(fileLoc, "image have zero frames: " + iamge, iserr)
try:
includes = dom.getElementsByTagName("include")
except:
includes = None
#todo need parse included files
try:
actions = dom.getElementsByTagName("action")
except:
actions = None
if (actions == None or len(actions) == 0) and (includes == None or len(includes) == 0):
showMsgSprite(fileLoc, "no actions in sprite file", iserr)
else:
actset = set()
frameSet = set()
for action in actions:
try:
name = action.attributes["name"].value
except:
showMsgSprite("no action name", iserr)
continue
frameSet = frameSet | testSpriteAction(fileLoc, name, action, num, iserr)
if name in actset:
showMsgSprite(fileLoc, "duplicate action: " + name, iserr)
continue
actset.add(name)
if len(frameSet) > 0:
errIds = ""
i = 0
while i < max(frameSet):
if i not in frameSet:
errIds = errIds + str(i) + ","
i = i + 1
if len(errIds) > 0:
showMsgSprite(fileLoc, "unused frames: " + errIds[0:len(errIds)-1], False)
def testSpriteAction(file, name, action, numframes, iserr):
framesid = set()
try:
animations = action.getElementsByTagName("animation")
except:
animations = None
if animations == None or len(animations) == 0:
showMsgSprite(file, "no animation tags in action: " + name, False)
aniset = set()
for animation in animations:
try:
direction = animation.attributes["direction"].value
except:
direction = "default"
if direction is aniset:
showMsgSprite(file, "duplicate direction in action: " + name, iserr)
continue
aniset.add(direction)
lastIndex1 = -1
lastIndex2 = -1
lastOffsetX = 0
lastOffsetY = 0
cnt = 0
for node2 in animation.childNodes:
if node2.nodeName == "frame" or node2.nodeName == "sequence":
try:
offsetX = int(node2.attributes["offsetX"].value)
except:
offsetX = 0
try:
offsetY = int(node2.attributes["offsetY"].value)
except:
offsetY = 0
if node2.nodeName == "frame":
frame = node2
try:
idx = int(frame.attributes["index"].value)
except:
showMsgSprite(file, "no frame index in action: " + name, iserr)
if idx >= numframes or idx < 0:
showMsgSprite(file, "incorrect frame index " + str(idx) + \
" aciton: " + name + ", direction: "\
+ direction, iserr)
else:
framesid.add(idx)
if lastIndex1 == idx and lastIndex2 == -1 and offsetX == lastOffsetX \
and offsetY == lastOffsetY:
showMsgSprite(file, "duplicate frame animation for frame index=" \
+ str(idx) + " action: " + name + \
", direction: " + direction, False)
else:
lastIndex1 = idx
lastIndex2 = -1
lastOffsetX = offsetX
lastOffsetY = offsetY
framesid.add(idx)
cnt = cnt + 1
elif node2.nodeName == "sequence":
sequence = node2
try:
i1 = int(sequence.attributes["start"].value)
i2 = int(sequence.attributes["end"].value)
except:
showMsgSprite(file, "no sequence start or end index action: " + \
name + ", direction: " + direction, iserr)
if i1 >= numframes or i1 < 0:
showMsgSprite(file, "incorrect start sequence index " + str(i1) + \
" action: " + name + ", direction: " + direction, iserr)
if i2 >= numframes or i2 < 0:
showMsgSprite(file, "incorrect end sequence index " + str(i2) + \
" action: " + name + ", direction: " + direction, iserr)
if i1 == i2:
showMsgSprite(file, "start and end sequence index is same. " \
+ "May be better use frame? action: " + \
name + ", direction: " + direction, False)
if lastIndex1 == i1 and lastIndex2 == i2 and offsetX == lastOffsetX \
and offsetY == lastOffsetY:
showMsgSprite(file, "duplicate sequence animation for start=" \
+ str(i1) + ", end=" + str(i2) + " action: " + \
name + ", direction: " + direction, False)
else:
lastIndex1 = i1
lastIndex2 = i2
lastOffsetX = offsetX
lastOffsetY = offsetY
cnt = cnt + 1
for i in range(i1,i2 + 1):
framesid.add(i)
if cnt == 0:
showMsgSprite(file, "no frames or sequences in action: " + name, iserr)
if "default" not in aniset:
if "down" not in aniset:
showMsgSprite(file, "no down direction in animation: " + name, iserr)
if "up" not in aniset:
showMsgSprite(file, "no up direction in animation: " + name, iserr)
if "left" not in aniset:
showMsgSprite(file, "no left direction in animation: " + name, iserr)
if "right" not in aniset:
showMsgSprite(file, "no right direction in animation: " + name, iserr)
if name == "dead" and len(animations) > 0:
lastani = animations[len(animations) - 1]
lastNode = None
nc = 0
for node in lastani.childNodes:
if node.nodeName == "frame":
lastNode = node
nc = nc + 1
if node.nodeName == "sequence":
lastNode = node
nc = nc + 2
if nc > 1:
try:
cont = int(lastNode.attributes["continue"].value)
except:
cont = 0;
if cont == 0:
try:
delay = int(lastNode.attributes["delay"].value)
except:
delay = 0
if delay > 0 and delay < 5000:
showMsgSprite(file, "last frame\sequence in dead animation have to low limit. Need zero or >5000: " + name, False)
return framesid
def testImageFile(file, fullPath, sz, iserr):
try:
img = Image.open(fullPath, "r")
img.load()
except:
showMsgFile(file, "incorrect image format", iserr)
return
if img.format != "PNG":
showMsgFile(file, "image format is not png", False)
sizes = img.size
if sz != 0:
if sizes[0] > sz or sizes[1] > sz:
showMsgFile(file, "image size incorrect (" + str(sizes[0]) \
+ "x" + str(sizes[1]) + ") should be (" + str(sz) + "x" \
+ str(sz) + ")", iserr)
elif sizes[0] < sz or sizes[1] < sz:
showMsgFile(file, "possible image size incorrect (" + str(sizes[0]) \
+ "x" + str(sizes[1]) + ") should be (" + str(sz) + "x" \
+ str(sz) + ")", False)
return sizes
def testSound(file):
fullPath = parentDir + "/" + sfxDir + file
if not os.path.isfile(fullPath) or os.path.exists(fullPath) == False:
showMsgFile(file, "sound file not found", True)
return
try:
snd = ogg.vorbis.VorbisFile(fullPath)
except ogg.vorbis.VorbisError as e:
showMsgFile(file, "sound file incorrect error: " + str(e), True)
def testParticle(id, file, src):
fullPath = parentDir + "/" + file
if not os.path.isfile(fullPath) or os.path.exists(fullPath) == False:
showMsgFile(file, "particle file not found", True)
return
dom = minidom.parse(fullPath)
nodes = dom.getElementsByTagName("particle")
if len(nodes) < 1:
showMsgFile(file, "missing particle tags", False)
else:
for node in nodes:
testEmitters(id, file, node, file)
def testEmitters(id, file, parentNode, src):
for node in parentNode.getElementsByTagName("property"):
try:
name = node.attributes["name"].value
except:
showMsgFile(file, "missing attribute name in emitter" \
" in particle file", True)
continue
try:
value = node.attributes["value"].value
except:
value = None
if name == "image":
if value == None:
showMsgFile(file, "missing attribute value in emitter" \
" image attribute", True)
img = splitImage(value)
image = img[0]
imagecolor = img[1]
testDye(id, imagecolor, "image=" + image, src, True)
testImageFile(image, parentDir + "/" + image, 0, True)
for node in parentNode.getElementsByTagName("emitter"):
testEmitters(id, file, node, src)
def testItems(fileName, imgDir):
global warnings, errors, safeDye
print "Checking items.xml"
dom = minidom.parse(parentDir + fileName)
idset = set()
for node in dom.getElementsByTagName("item"):
id = node.attributes["id"].value
if id in idset:
print "error: duplicated id=" + id
errors = errors + 1
else:
idset.add(id)
idI = int(id)
try:
type = node.attributes["type"].value
except:
type = ""
print "warn: no type attribute for id=" + id
warnings = warnings + 1
try:
image = node.attributes["image"].value
image0 = image
img = splitImage(image)
image = img[0]
imagecolor = img[1]
except:
image = ""
image0 = ""
imagecolor = ""
try:
floor = node.attributes["floor"].value
floor0 = floor
flr = splitImage(floor)
floor = flr[0]
floorcolor = flr[1]
except:
floor = None
floor0 = None
floorcolor = None
try:
description = node.attributes["description"].value
except:
description = ""
try:
missile = node.attributes["missile-particle"].value
except:
missile = ""
if type == "hairsprite":
if idI >= 0:
print "error: hairsprite with id=" + id
errors = errors + 1
elif idI < -100:
print "error: hairsprite override player sprites"
errors = errors + 1
safeDye = True
testSprites(id, node, True, True)
safeDye = False
elif type == "racesprite":
if idI >= 0:
print "error: racesprite with id=" + id
errors = errors + 1
elif idI > -100:
print "error: racesprite override player hair"
errors = errors + 1
elif type == "usable" or type == "unusable" or type == "generic" \
or type == "equip-necklace" or type == "equip-torso" or type == "equip-feet" \
or type == "equip-arms" or type == "equip-legs" or type == "equip-head" \
or type == "equip-shield" or type == "equip-1hand" or type == "equip-2hand" \
or type == "equip-charm" or type == "equip-ammo" or type == "equip-neck" \
or type == "equip-ring":
if image == "":
print "error: missing image attribute on id=" + id
errors = errors + 1
continue
elif len(imagecolor) > 0:
testDye(id, imagecolor, "image=" + image0, "items.xml", True)
if floorcolor != None and len(floorcolor) > 0:
testDye(id, floorcolor, "floor=" + floor0, "items.xml", True)
if description == "":
print "warn: missing description attribute on id=" + id
warnings = warnings + 1
if missile != "":
testParticle(id, missile, "items.xml")
testSounds(id, node, "item")
try:
floorSprite = node.getElementsByTagName("floor")[0]
except:
floorSprite = None
if floorSprite != None:
if floor != None:
print "error: found attribute floor and tag floor. " + \
"Should be only one tag or attribute. id=" + id
errors = errors + 1
testSprites(id, floorSprite, False, err)
fullPath = os.path.abspath(parentDir + "/" + imgDir + image)
if not os.path.isfile(fullPath) or os.path.exists(fullPath) == False:
showFileErrorById (id, imgDir, image)
errors = errors + 1
else:
testImageFile(imgDir + image, fullPath, 32, True)
if floor != None:
fullPath = os.path.abspath(parentDir + "/" + imgDir + floor)
if not os.path.isfile(fullPath) or os.path.exists(fullPath) == False:
showFileErrorById (id, imgDir, floor)
error = errors + 1
else:
testImageFile(imgDir + floor, fullPath, 0, True)
if type != "usable" and type != "unusable" and type != "generic" \
and type != "equip-necklace" and type != "equip-1hand" \
and type != "equip-2hand" and type != "equip-ammo" \
and type != "equip-charm" and type != "equip-neck":
err = type != "equip-shield"
testSprites(id, node, True, err)
elif type == "other":
None
elif type != "":
print "warn: unknown type '" + type + "' for id=" + id
warnings = warnings + 1
def testMonsters(fileName):
global warnings, errors
print "Checking monsters.xml"
dom = minidom.parse(parentDir + fileName)
idset = set()
for node in dom.getElementsByTagName("monster"):
try:
id = node.attributes["id"].value
except:
print "error: no id for monster"
errors = errors + 1
continue
if id in idset:
print "duplicate id=" + id
else:
idset.add(id)
try:
name = node.attributes["name"].value
except:
print "error: no name for id=" + id
errors = errors + 1
name = ""
testTargetCursor(id, node, fileName)
testSprites(id, node, False, True)
testSounds(id, node, "monster")
testParticles(id, node, "particlefx", fileName)
def testTargetCursor(id, node, file):
try:
targetCursor = node.attributes["targetCursor"].value
if targetCursor != "small" and targetCursor != "medium" and targetCursor != "large":
showMsgFile(id, "unknown target cursor " + targetCursor)
except:
None
def testParticles(id, node, nodeName, src):
particles = node.getElementsByTagName(nodeName)
for particle in particles:
try:
particlefx = particle.childNodes[0].data
except:
showMsgFile(id, "particle tag have incorrect data", True)
testParticle(id, particlefx, src)
def testSounds(id, node, type):
global errors
for sound in node.getElementsByTagName("sound"):
try:
event = sound.attributes["event"].value
except:
print "error: no sound event name in id=" + id
errors = errors + 1
if type == "monster":
if event != "hit" and event != "miss" and event != "hurt" and event != "die":
print "error: incorrect sound event name " + event + " in id=" + id
errors = errors + 1
elif type == "item":
if event != "hit" and event != "strike":
print "error: incorrect sound event name " + event + " in id=" + id
errors = errors + 1
testSound(sound.childNodes[0].data)
def testNpcs(file):
global warnings, errors
print "Checking npcs.xml"
dom = minidom.parse(parentDir + file)
idset = set()
for node in dom.getElementsByTagName("npc"):
try:
id = node.attributes["id"].value
except:
print "error: no id for npc"
errors = errors + 1
continue
if id in idset:
print "error: duplicate npc id=" + id
else:
idset.add(id)
testSprites(id, node, False, True)
testParticles(id, node, "particlefx", file)
def readAttrI(node, attr, dv, msg, iserr):
return int(readAttr(node, attr, dv, msg, iserr))
def readAttr(node, attr, dv, msg, iserr):
global warnings, errors
try:
return node.attributes[attr].value
except:
print msg
if iserr:
errors = errors + 1
else:
warnings = warnings + 1
return dv
def testMap(file, path):
global warnings, errors
fullPath = parentDir + "/" + path
dom = minidom.parse(fullPath)
root = dom.documentElement
mapWidth = readAttrI(root, "width", 0, "error: missing map width: " + file, True)
mapHeight = readAttrI(root, "height", 0, "error: missing map height: " + file, True)
mapTileWidth = readAttrI(root, "tilewidth", 0, "error: missing tile width: " + file, True)
mapTileHeight = readAttrI(root, "tileheight", 0, "error: missing tile height: " + file, True)
if mapWidth == 0 or mapHeight == 0 or mapTileWidth == 0 or mapTileHeight == 0:
return
if mapWidth < borderSize * 2 + 1:
showMsgFile(file, "map width to small: " + str(mapWidth), False)
if mapHeight < borderSize * 2 + 1:
showMsgFile(file, "map height to small: " + str(mapHeight), False)
tilesMap = dict()
for tileset in dom.getElementsByTagName("tileset"):
name = readAttr(tileset, "name", "", "warning: missing tile name: " + file, False)
tileWidth = readAttrI(tileset, "tilewidth", mapTileWidth, \
"error: missing tile width in tileset: " + name + ", " + file, True)
tileHeight = readAttrI(tileset, "tileheight", mapTileHeight, \
"error: missing tile height in tileset: " + name + ", " + file, True)
try:
firstGid = int(tileset.attributes["firstgid"].value)
except:
firstGid = 0
if firstGid in tilesMap:
showMsgFile(file, "tile with firstgid " + str(firstGid) + \
" already exist: " + name + ", " + file, True)
continue
if tileWidth == 0 or tileHeight == 0:
continue
tile = Tileset()
tile.name = name
tile.width = tileWidth
tile.height = tileHeight
tile.firstGid = firstGid
tile.lastGid = 0
# if tileWidth != 32:
# showMsgFile(file, "tile width " + str(tileWidth) + " != 32: " + name, False)
# if tileHeight != 32:
# showMsgFile(file, "tile height " + str(tileHeight) + " != 32: " + name, False)
images = tileset.getElementsByTagName("image")
if images == None or len(images) == 0:
showMsgFile(file, "missing image tags in tile " + name, True)
continue
elif len(images) > 1:
showMsgFile(file, "to many image tags in tile " + name, True)
continue
else:
image = images[0]
source = readAttr(image, "source", None, "error: missing source in image tag in tile " \
+ name + ": " + file, True)
if source != None:
imagePath = os.path.abspath(parentDir + "/" + mapsDir + source)
img = splitImage(imagePath)
imagePath = img[0]
imagecolor = img[1]
tile.image = imagePath
tile.color = imagecolor
if not os.path.isfile(imagePath) or os.path.exists(imagePath) == False:
showMsgFile(file, "image file not exist: " + mapsDir + source + ", " + \
name, True)
continue
if imagecolor != "":
testDye("", imagecolor, source, file, True)
sz = testImageFile(file, imagePath, 0, True)
width = sz[0]
height = sz[1]
if width == 0 or height == 0:
continue
if width < tileWidth:
showMsgFile(file, "tile width more than image width in tile: " + \
name, True)
continue
if height < tileHeight:
showMsgFile(file, "tile height more than image height in tile: " + \
name, True)
continue
s1 = int(width / int(tileWidth)) * int(tileWidth)
if width != s1:
showMsgFile(file, "image width " + str(width) + \
" (need " + str(s1) + ") is not multiply to tile size " + \
str(tileWidth) + ". " + source + ", " + name, False)
s2 = int(height / int(tileHeight)) * int(tileHeight)
tile.lastGid = tile.firstGid + (s1 * s2)
if height != s2:
showMsgFile(file, "image width " + str(height) + \
" (need " + str(s2) + ") is not multiply to tile size " + \
str(tileHeight) + ". " + source + ", " + name, False)
tilesMap[tile.firstGid] = tile
layers = dom.getElementsByTagName("layer")
if layers == None or len(layers) == 0:
showMsgFile(file, "map dont have layers", True)
return
fringe = None
collision = None
lowLayers = set()
overLayers = set()
beforeFringe = True
for layer in layers:
name = readAttr(layer, "name", None, "layer dont have name", True)
if name == None:
continue
obj = Layer()
obj.name = name
if name.lower() == "fringe":
if fringe is not None:
showMsgFile(file, "duplicate Fringe layer", True)
fringe = obj
beforeFringe = False
elif name.lower() == "collision":
if collision is not None:
showMsgFile(file, "duplicate Collision layer", True)
collision = obj
elif beforeFringe == True:
lowLayers.add(obj)
else:
overLayers.add(obj)
width = readAttrI(layer, "width", 0, "error: missing layer width: " + name + \
", " + file, True)
height = readAttrI(layer, "height", 0, "error: missing layer height: " + name + \
", " + file, True)
if width == 0 or height == 0:
continue
obj.width = width
obj.height = height
if mapWidth < width:
showMsgFile(file, "layer width " + str(width) + " more than map width " + \
str(mapWidth) + ": " + name, True)
if mapHeight < height:
showMsgFile(file, "layer height " + str(height) + " more then map height " + \
str(mapHeight) + ": " + name, True)
obj = testLayer(file, layer, name, width, height, obj, tilesMap)
if fringe == None:
showMsgFile(file, "missing fringe layer", True)
if collision == None:
showMsgFile(file, "missing collision layer", True)
else:
ids =testCollisionLayer(file, collision)
if ids != None and len(ids) > 0:
showLayerErrors(file, ids, "empty tiles in collision border", False)
if len(lowLayers) < 1:
showMsgFile(file, "missing low layers", False)
if len(overLayers) < 1:
showMsgFile(file, "missing over layers", False)
if fringe != None:
lowLayers.add(fringe)
warn1 = None
if len(overLayers) > 0:
warn1 = testLayerGroups(file, lowLayers, False)
lowLayers = lowLayers | overLayers
err1 = testLayerGroups(file, lowLayers, False)
else:
err1 = testLayerGroups(file, lowLayers, False)
if warn1 != None and err1 != None:
warn1 = warn1 - err1
if warn1 != None and len(warn1) > 0:
showLayerErrors(file, warn1, "empty tile in lower layers", False)
if err1 != None and len(err1) > 0:
showLayerErrors(file, err1, "empty tile in all layers", True)
def testCollisionLayer(file, layer):
haveTiles = False
tileset = set()
arr = layer.arr
x1 = borderSize
y1 = borderSize
x2 = layer.width - 20
y2 = layer.width - 20
if x2 < 0:
x2 = 0
if y2 < 0:
y2 = 0
for x in range(0, layer.width):
if haveTiles == True:
break
for y in range(0, layer.height):
idx = ((y * layer.width) + x) * 4
val = getLDV(arr, idx)
if val != 0:
haveTiles = True
if val == 0 and (x < x1 or x > x2 or y < y1 or y > y2):
tileset.add((x, y))
if haveTiles == False:
showMsgFile(file, "empty collision layer", False)
return tileset
def showLayerErrors(file, points, msg, iserr):
txt = ""
cnt = 0
for point in points:
txt = txt + " " + str(point) + ","
cnt = cnt + 1
if cnt > 100:
txt = txt + " ... "
break
showMsgFile(file, msg + txt[0:len(txt)-1], iserr)
def getLDV(arr, index):
return arr[index] | (arr[index + 1] << 8) | (arr[index + 2] << 16) \
| (arr[index + 3] << 24)
def testLayer(file, node, name, width, height, layer, tiles):
datas = node.getElementsByTagName("data")
if datas == None or len(datas) == 0:
showMsgFile(file, "missing data tag in layer: " + name, True)
return
layer.arr = None
for data in datas:
try:
encoding = data.attributes["encoding"].value
except:
encoding = ""
try:
compression = data.attributes["compression"].value
except:
compression = ""
if encoding == "base64":
if compression != "gzip":
showMsgFile(file, "invalid compression " + compression + \
" in layer: " + name, True)
continue
binData = data.childNodes[0].data.strip()
binData = binData.decode('base64')
dc = zlib.decompressobj(16 + zlib.MAX_WBITS)
layerData = dc.decompress(binData)
arr = array.array("B")
arr.fromstring(layerData)
layer.arr = arr
# here may be i should check is tiles correct or not, but i will trust to tiled
return layer
def testLayerGroups(file, layers, iserr):
width = 0
height = 0
errset = set()
for layer in layers:
if layer.width > width:
width = layer.width
if layer.height > height:
height = layer.height
for x in range(0, width):
for y in range(0, height):
good = False
for layer in layers:
if layer.arr != None and x < layer.width \
and y < layer.height:
arr = layer.arr
ptr = ((y * layer.width) + x) * 4
if arr[ptr] != 0 or arr[ptr + 1] != 0 or \
arr[ptr + 2] != 0 or arr[ptr + 3] != 0:
good = True
break
if good == False:
errset.add((x,y))
# showMsgFile(file, "empty tile in lower layers (" \
# + str(x) + "," + str(y) + ")", False)
# for f in range(0, len(arr), 4):
# flg = getLDV(arr, f)
# y = int(f / 4 / layer.width)
# x = int(f / 4) - (y * layer.width)
# print "addr: " + str(f) + ", flg=" + str(flg) + ", x=" + str(x) + ", y=" + str(y)
return errset
def testMaps(dir):
global warnings, errors
fullPath = parentDir + "/" + dir
print "Checking maps"
if not os.path.isdir(fullPath) or not os.path.exists(fullPath):
print "error: maps dir not found: " + dir
errors = errors + 1
return
for file in os.listdir(fullPath):
if filtmaps.search(file):
testMap(mapsDir + file, dir + file)
def haveXml(dir):
if not os.path.isdir(dir) or not os.path.exists(dir):
return False
for file in os.listdir(dir):
if filt.search(file):
return True
return False
def detectClientData(dirs):
global parentDir
for dir in dirs:
if haveXml(dir):
print "Detected client data directory in: " + dir
parentDir = dir
return True
print "Cant detect client data directory"
exit(1)
showHeader()
print "Detecting clientdata dir"
detectClientData([".", "..", parentDir])
print "Checking xml file syntax"
enumDirs(parentDir)
loadPaths()
testItems("/items.xml", iconsDir)
testMonsters("/monsters.xml")
testNpcs("/npcs.xml")
testMaps(mapsDir)
showFooter()