From 6a343e058a776b1050773c53da583ac044df6f0f Mon Sep 17 00:00:00 2001 From: Jesusaves Date: Wed, 12 May 2021 18:47:33 -0300 Subject: Add CI Utils. Add Updater. And add my analysis tool (wikigen) --- CI/imagescheck/icccheck.sh | 5 + CI/imagescheck/icccheckfile.sh | 9 + CI/licensecheck/checkfile.sh | 12 + CI/licensecheck/clientdata.sh | 9 + CI/licensecheck/serverdata.py | 71 ++ CI/testxml/testxml.py | 2437 ++++++++++++++++++++++++++++++++++++++++ CI/testxml/xsd/XMLSchema.xsd | 2262 +++++++++++++++++++++++++++++++++++++ CI/testxml/xsd/checkfile.sh | 5 + CI/testxml/xsd/its.xsd | 926 +++++++++++++++ CI/testxml/xsd/tmw.xsd | 2117 ++++++++++++++++++++++++++++++++++ CI/testxml/xsd/xlink.xsd | 79 ++ CI/testxml/xsd/xml.xsd | 287 +++++ CI/testxml/xsdcheck.sh | 52 + update/pseudo_update.sh | 31 + wiki/wikigen.py | 902 +++++++++++++++ 15 files changed, 9204 insertions(+) create mode 100755 CI/imagescheck/icccheck.sh create mode 100755 CI/imagescheck/icccheckfile.sh create mode 100755 CI/licensecheck/checkfile.sh create mode 100755 CI/licensecheck/clientdata.sh create mode 100755 CI/licensecheck/serverdata.py create mode 100755 CI/testxml/testxml.py create mode 100644 CI/testxml/xsd/XMLSchema.xsd create mode 100755 CI/testxml/xsd/checkfile.sh create mode 100644 CI/testxml/xsd/its.xsd create mode 100644 CI/testxml/xsd/tmw.xsd create mode 100644 CI/testxml/xsd/xlink.xsd create mode 100644 CI/testxml/xsd/xml.xsd create mode 100755 CI/testxml/xsdcheck.sh create mode 100755 update/pseudo_update.sh create mode 100755 wiki/wikigen.py diff --git a/CI/imagescheck/icccheck.sh b/CI/imagescheck/icccheck.sh new file mode 100755 index 0000000..6ca1481 --- /dev/null +++ b/CI/imagescheck/icccheck.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +export DIR="../../client-data" + +find -H $DIR -type f -name "*.png" -exec ./icccheckfile.sh {} \; diff --git a/CI/imagescheck/icccheckfile.sh b/CI/imagescheck/icccheckfile.sh new file mode 100755 index 0000000..dbdfb23 --- /dev/null +++ b/CI/imagescheck/icccheckfile.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +identify -verbose $1 | egrep -i "profile|iCCP" >/dev/null + +if [ "$?" == 0 ]; then + export name="$1" + export name=${name##../../client-data/} + echo "ICC or iCCP profile found for image $name" +fi diff --git a/CI/licensecheck/checkfile.sh b/CI/licensecheck/checkfile.sh new file mode 100755 index 0000000..2de49fe --- /dev/null +++ b/CI/licensecheck/checkfile.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +export name="$3" +export name=${name##../../client-data/} + +grep "$name" $1 >/dev/null +if [ "$?" != 0 ]; then + grep "$name " $2 >/dev/null + if [ "$?" != 0 ]; then + echo "Missing license for $name" + fi +fi diff --git a/CI/licensecheck/clientdata.sh b/CI/licensecheck/clientdata.sh new file mode 100755 index 0000000..583b197 --- /dev/null +++ b/CI/licensecheck/clientdata.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +export DIR="../../client-data" + +find -H $DIR -type f -name "*.png" -exec ./checkfile.sh $DIR/LICENSE $DIR/ART_LICENSE {} \; +find -H $DIR/sfx -type f -name "*.ogg" -exec ./checkfile.sh $DIR/LICENSE $DIR/ART_LICENSE {} \; +find -H $DIR -type f -name "*.tmx" -exec ./checkfile.sh $DIR/LICENSE $DIR/ART_LICENSE {} \; +find -H $DIR -type f -name "*.jpg" -exec ./checkfile.sh $DIR/LICENSE $DIR/ART_LICENSE {} \; + diff --git a/CI/licensecheck/serverdata.py b/CI/licensecheck/serverdata.py new file mode 100755 index 0000000..02f4aec --- /dev/null +++ b/CI/licensecheck/serverdata.py @@ -0,0 +1,71 @@ +#! /usr/bin/env python2.7 +# -*- coding: utf8 -*- +# +# Copyright (C) 2018 TMW-2 +# Author: Jesusalva + +# Bad command: +# ls --recursive --hyperlink=always --format=single-column ../../server-data/npc/|grep txt + +# Initialize stuff +import subprocess +import sys +import os +erp=[] + +# Clear previous NPC list +try: + subprocess.call("rm npcs.txt", shell=True) +except: + pass + +# Determine correct path +PATH="../../server-data/npc/" +if len(sys.argv) == 2: + PATH=sys.argv[1] + +# Generate NPC list +subprocess.call("find "+PATH+" txt > npcs.txt", shell=True) +npcs=open("npcs.txt", "r") + +# Begin +print("Checking license info for NPCs") +print("Source is at: "+PATH) + +for mpa in npcs: + mp=mpa.replace('\n','') + # Skip mapflags + if "mapflag" in mp: + continue + # Skip bad files + if not '.txt' in mp: + continue + # Skip certain folders + if "/dev/" in mp or "/00000SAVE/" in mp or "/test/" in mp: + continue + + a=open(mp, 'r') + #print("Verify %s" % mp) + ok=False + for line in a: + if 'tmw2 script' in line.lower() or 'tmw-2 script' in line.lower() or 'tmw 2 script' in line.lower() or 'tmw2/lof script' in line.lower() or 'This file is generated automatically' in line or 'author' in line.lower() or 'tmw2 function' in line.lower() or 'tmw-2 function' in line.lower() or 'tmw 2 function' in line.lower(): + ok=True + break + + a.close() + if not ok: + erp.append(mp) + +npcs.close() +if len(erp) > 0: + print("-----------------------------------------------------------------------") + +for i in sorted(erp): + print(i) + +print("-----------------------------------------------------------------------") +print("Serverdata license check result") +print("Errors: %d" % (len(erp))) +if len(erp): + os.exit(1) + diff --git a/CI/testxml/testxml.py b/CI/testxml/testxml.py new file mode 100755 index 0000000..6ae9e12 --- /dev/null +++ b/CI/testxml/testxml.py @@ -0,0 +1,2437 @@ +#! /usr/bin/env python +# -*- coding: utf8 -*- +# +# Copyright (C) 2010-2011 Evol Online +# Author: Andrei Karas (4144) + +import array +import os +import re +import datetime +import xml +import csv +import ogg.vorbis +import StringIO +import sys +from xml.dom import minidom +from xml.etree import ElementTree +from PIL import Image +import zlib + +filt = re.compile(".+[.](xml|tmx|tsx)", re.IGNORECASE) +filtmaps = re.compile(".+[.]tmx", re.IGNORECASE) +filtimages = re.compile(".+[.]png", re.IGNORECASE) +filtxmls = re.compile(".+[.]xml", re.IGNORECASE) +filtogg = re.compile(".+[.]ogg", re.IGNORECASE) +dyesplit1 = re.compile(";") +dyesplit2 = re.compile(",") +parentDir = "../../gittorious/clientdata-beta" +iconsDir = "graphics/items/" +spritesDir = "graphics/sprites/" +particlesDir = "graphics/particles/" +sfxDir = "sfx/" +musicDir = "music/" +mapsDir = "maps/" +spriteErrorFile = "error.xml" +levelUpEffectFile = "levelup.particle.xml" +portalEffectFile = "warparea.particle.xml" +minimapsDir = "graphics/minimaps/" +wallpapersDir = "graphics/images/" +wallpaperFile = "login_wallpaper.png" + +errors = 0 +warnings = 0 +errDict = set() +safeDye = False +borderSize = 14 # Required 18 # Original 14 +tiledVersion = 13 # Minimum Tiled version, advised "14" for Tiled 1.4 +colorsList = set() +showAll = False +silent = False +stfu = False +herc = False + +testBadCollisions = False +# number of tiles difference. after this amount tiles can be counted as incorrect +tileNumDiff = 3 +# max number of incorrect tiles. If more then tile not counted as error +maxNumErrTiles = 5 + +class Tileset: + None + +class Layer: + None + +def printErr(err): + errDict.add(err) + print err.encode("utf-8") + +def showFileErrorById(id, rootDir, fileDir): + rootDir = rootDir.encode("utf-8") + fileDir = fileDir.encode("utf-8") + print "error: id=" + id + ", file not found: " + fileDir + " (" + rootDir + fileDir + ")" + +def showFileWarningById(id, rootDir, fileDir): + rootDir = rootDir.encode("utf-8") + fileDir = fileDir.encode("utf-8") + print "warn: id=" + id + ", file not found: " + fileDir + " (" + rootDir + fileDir + ")" + +def showError(id, text): + text = text.encode("utf-8") + print "error: id=" + id + " " + text + +def showWarning(id, text): + text = text.encode("utf-8") + 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 "https://gitlab.com/evol/evol-tools/blob/master/testxml/testxml.py" + printSeparator() + +def showFooter(): + printSeparator() + print "Total:" + print " Warnings: " + str(warnings) + print " Errors: " + str(errors) + +def enumDirs(parentDir): + global warnings, errors + try: + files = os.listdir(parentDir) + except OSError: + print "Directory error: " + parentDir + if silent == False: + warnings = warnings + 1 + return + 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) + else: + if filt.search(file1): + try: + if silent == True and not stfu: + print "Checking " + file2 + minidom.parse(file2) + except xml.parsers.expat.ExpatError as err: + print "error: " + file2 + ", line=" + str(err.lineno) + ", char=" + str(err.offset) + errors = errors + 1 + if file1 != "testxml.py": + checkFilePermission(file2) + +def checkFilePermission(fullName): + global warnings + if os.access(fullName, os.X_OK): + print "warn: execute flag on file: " + fullName + warnings = warnings + 1 + + +def loadPaths(): + global warnings, iconsDir, spritesDir, sfxDir, particlesDir, mapsDir, spriteErrorFile, \ + levelUpEffectFile, portalEffectFile, minimapsDir, wallpapersDir, walpaperFile, \ + musicDir, wallpaperFile + try: + dom = minidom.parse(parentDir + "/paths.xml") + for node in dom.getElementsByTagName("option"): + if node.attributes["name"].value == "itemIcons": + iconsDir = node.attributes["value"].value + if iconsDir != "graphics/items/": + print "warn: itemIcons path has not default value."\ + " Will be incampatible with old clients." + warnings = warnings + 1 + elif node.attributes["name"].value == "sprites": + spritesDir = node.attributes["value"].value + if spritesDir != "graphics/sprites/": + print "warn: sprites path has not default value."\ + " Will be incampatible with old clients." + warnings = warnings + 1 + elif node.attributes["name"].value == "sfx": + sfxDir = node.attributes["value"].value + + elif node.attributes["name"].value == "particles": + particlesDir = node.attributes["value"].value + if particlesDir != "graphics/particles/": + print "warn: particles path has not default value."\ + " Will be incampatible with old clients." + warnings = warnings + 1 + elif node.attributes["name"].value == "maps": + mapsDir = node.attributes["value"].value + if mapsDir != "maps/": + print "warn: maps path has not default value."\ + " Will be incampatible with old clients." + warnings = warnings + 1 + elif node.attributes["name"].value == "spriteErrorFile": + spriteErrorFile = node.attributes["value"].value + elif node.attributes["name"].value == "levelUpEffectFile": + levelUpEffectFile = node.attributes["value"].value + elif node.attributes["name"].value == "portalEffectFile": + portalEffectFile = node.attributes["value"].value + elif node.attributes["name"].value == "minimaps": + minimapsDir = node.attributes["value"].value + elif node.attributes["name"].value == "wallpapers": + wallpapersDir = node.attributes["value"].value + elif node.attributes["name"].value == "wallpaperFile": + wallpaperFile = node.attributes["value"].value + elif node.attributes["name"].value == "music": + musicDir = 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" and c != "S": + 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): + oldPalette = col[0] == "#" + if oldPalette == False and col[0] != "@": + showMsg(id, "incorrect dye colors: " + text, src, iserr) + return False + + if oldPalette: + 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 testDyeChannel(file, color, text, iserr): + if len(color) < 1: + showMsgSprite(file, "dye channel size to small:" + text, iserr) + return -1 + colors = dyesplit1.split(color) + for c in colors: + if len(c) != 1: + showMsgSprite(file, "dye channel 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" and c != "S": + showMsgSprite(file, "dye make incorrect: " + text, iserr) + continue + return len(colors) + + +def testSprites(id, node, checkGender, isNormalDye, isMust, checkAction, 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 + + if isMust == False: + nosprite = True + + 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 or len(sprites[0].childNodes) == 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, isNormalDye, checkAction, iserr) + else: + male = False + female = False + unisex = 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": + unisex = True + try: + variant = int(sprite.attributes["variant"].value) + except: + variant = 0 + testSprite(id, file, variant, isNormalDye, checkAction, iserr) + if checkGender: + if male == False and unisex == False: + showMsg(id, "no male sprite tag", "",iserr) + if female == False and unisex == False: + showMsg(id, "no female sprite tag", "", iserr) + if unisex == True and female == True and male == True: + showMsg(id, "gender sprite tag with unisex tag", "", iserr) + if unisex == False and male == False and female == False: + showMsg(id, "no any gender tags", "", iserr) + +def testSprite(id, file, variant, isNormalDye, checkAction, iserr): + global safeDye + 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: + if not isNormalDye and color is not None and len(color) > 0: + showMsg(id, "sprite tag have dye string but it should not, because used colors dye", color, iserr) + + oldSafe = safeDye + safeDye = True + testSpriteFile(id, fullPath, file, spritesDir + file2, dnum, variant, checkAction, iserr) + safeDye = oldSafe + +def powerOfTwo(num): + val = 1 + while val < num: + val = val * 2 + return val + +def testSpriteFile(id, fullPath, file, fileLoc, dnum, variant, checkAction, iserr): + global safeDye + + try: + dom = minidom.parse(fullPath) + except: + return + + if len(dom.childNodes) < 1: + return + + try: + variants = dom.documentElement.attributes["variants"].value + except: + variants = 0 + +# try: +# variant_offset = dom.documentElement.attributes["variant_offset"].value +# except: +# variant_offset = 0 + +# root = dom.childNodes[0]; + imagesets = dom.getElementsByTagName("imageset") + if imagesets is None or len(imagesets) < 1: + showMsgSprite(fileLoc, "incorrect number of imageset tags", iserr) + return + isets = set() + imagesetnums = dict() + num = 0 + for imageset in imagesets: + try: + name = imageset.attributes["name"].value + except: + showMsgSprite(fileLoc, "imageset don't have name attribute", iserr) + name = None + + if name is not None: + if name in isets: + showMsgSprite(fileLoc, "imageset with name '" + name + "' already exists", iserr) + isets.add(name) + + image = "" + 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) + continue + + try: + width = imageset.attributes["width"].value + except: + showMsgSprite(fileLoc, "no width attribute", iserr) + continue + + try: + height = imageset.attributes["height"].value + except: + showMsgSprite(fileLoc, "no height attribute", iserr) + + if imagecolor != "": + num = testDyeChannel(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) + continue + sizes = testImageFile(image, fullPath, 0, " " + fileLoc, iserr) + s1 = int(sizes[0] / int(width)) * int(width) + + sizesOGL = [0,1] + sizesOGL[0] = powerOfTwo(sizes[0]) + sizesOGL[1] = powerOfTwo(sizes[1]) + + if s1 == 0: + tmp = int(width) + else: + tmp = s1 + if sizes[0] != s1 and tmp != sizesOGL[0] and sizes[0] != sizesOGL[0]: + if silent != True: + showMsgSprite(fileLoc, "image width " + str(sizes[0]) + \ + " (need " + str(tmp) + ") is not multiply to frame size " + width + ", image:" + image, False) + + if sizes[0] != sizesOGL[0]: + if sizesOGL[0] > sizes[0]: + txt = str(sizesOGL[0] / 2) + " or " + else: + txt = "" + + if showAll is True: + showMsgSprite(fileLoc, "image width should be power of two. If not image will be resized on the fly."\ + "\nCurrent image width " + str(sizes[0]) + \ + ". used in sprite width " + str(tmp) + + "\nallowed width " + txt + str(sizesOGL[0]) + " (" + image + ")", False) + + s2 = int(sizes[1] / int(height)) * int(height) + + if s2 == 0: + tmp = int(height) + else: + tmp = s2; + + if sizes[1] != s2 and tmp != sizesOGL[1] and sizes[1] != sizesOGL[1]: + if silent != True: + showMsgSprite(fileLoc, "image height " + str(sizes[1]) + \ + " (need " + str(tmp) + ") is not multiply to frame size " + height + ", image:" + image, False) + + if sizes[1] != sizesOGL[1]: + if sizesOGL[1] > sizes[1]: + txt = str(sizesOGL[1] / 2) + " or " + else: + txt = "" + + if showAll is True: + showMsgSprite(fileLoc, "image height should be power of two. If not image will be resized on the fly."\ + "\nCurrent image height " + str(sizes[1]) + \ + ". used in sprite height " + str(tmp) + + "\nallowed height " + txt + str(sizesOGL[1]) + " (" + image + ")", False) + + + num = (s1 / int(width)) * (s2 / int(height)) + if variants == 0 and variant > 0: + showMsgSprite(fileLoc, "missing variants attribute in sprite", iserr) + if variants > 0 and variant >= variants: + showMsgSprite(fileLoc, "variant number more then in variants attribute", iserr) + + if variant > 0 and 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: " + image, iserr) + if name is not None and num > 0: + imagesetnums[name] = num + + try: + includes = dom.getElementsByTagName("include") + for include in includes: + try: + incfile = include.attributes["file"].value + file2 = os.path.abspath(parentDir + os.path.sep + spritesDir + incfile) + if not os.path.isfile(file2): + showMsgSprite(fileLoc, "include file not exists " + incfile, True) + except: + showMsgSprite(fileLoc, "bad include", iserr) + + + 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() + hpSet = set() + for action in actions: + try: + name = action.attributes["name"].value + except: + showMsgSprite(fileLoc, "no action name", iserr) + continue + try: + hp = action.attributes["hp"].value + except: + hp = "100" + try: + setname = action.attributes["imageset"].value + except: + setname = "" + if setname in imagesetnums: + num = imagesetnums[setname] + else: + num = 0 + showMsgSprite(fileLoc, "using incorrect imageset name in action: " + name, iserr) + frameSet = frameSet | testSpriteAction(fileLoc, name, action, num, iserr) + + if name + "|" + hp in actset: + showMsgSprite(fileLoc, "duplicate action: " + name, iserr) + continue + actset.add(name + "|" + hp) + hpSet.add(hp) + + 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: + if silent != True: + showMsgSprite(fileLoc, "unused frames: " + errIds[0:len(errIds)-1], False) + + if checkAction != "": + for hp in hpSet: + if checkAction + "|" + hp not in actset: + showMsgSprite(fileLoc, "no attack action '" + checkAction + "' in sprite", iserr) + + +def testSpriteAction(file, name, action, numframes, iserr): + framesid = set() + + try: + animations = action.getElementsByTagName("animation") + except: + animations = None + + if animations == None or len(animations) == 0: + if name != "default": + showMsgSprite(file, "no animation tags in action: " + name, False) + else: + return framesid + + aniset = set() + delayTags = ("frame", "sequence", "pause") + + for animation in animations: + lastAttack = None + 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 + labels = set() + + for node2 in animation.childNodes: + if name == "attack" and node2.nodeName != "#text": + lastAttack = node2.nodeName + if node2.nodeName in delayTags: + try: + delay = int(node2.attributes["delay"].value) + except: + delay = 0 + + if delay % 10 != 0 and showAll is True: + showMsgSprite(file, "delay " + str(delay) + " must be multiple of 10 in action: " + name + \ + ", direction: " + direction, False) + + + 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) + \ + " action: " + 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 + "\n" + node2.toxml(), False) + #print node2.toxml() + else: + lastIndex1 = idx + lastIndex2 = -1 + lastOffsetX = offsetX + lastOffsetY = offsetY + + framesid.add(idx) + cnt = cnt + 1 + elif node2.nodeName == "sequence": + sequence = node2 + try: + sframes = dyesplit2.split(sequence.attributes["value"].value) + except: + sframes = None + if sframes is not None: + for frm in sframes: + if frm != "p": + k = frm.find("-") + if k == 0 or k == len(frm) - 1: + showMsgSprite(file, "incorrect sequence value " + \ + name + ", direction: " + direction, iserr) + elif k == -1: + #same as frame + idx = int(frm) + if idx >= numframes or idx < 0: + showMsgSprite(file, "incorrect frame index " + str(idx) + \ + " action: " + name + ", direction: "\ + + direction, iserr) + else: + framesid.add(idx) + else: + #same as simple sequence + i1 = int(frm[:k]) + i2 = int(frm[k + 1:]) + 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) + + for i in range(i1,i2 + 1): + framesid.add(i) + cnt = cnt + 1 + continue + + 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) +# try: +# repeat = int(sequence.attributes["repeat"].value) +# except: +# repeat = 1 + + 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. May be need use repeat attribue? for start=" \ + + str(i1) + ", end=" + str(i2) + " action: " + \ + name + ", direction: " + direction + "\n" + node2.toxml(), False) + else: + lastIndex1 = i1 + lastIndex2 = i2 + lastOffsetX = offsetX + lastOffsetY = offsetY + + cnt = cnt + 1 + for i in range(i1,i2 + 1): + framesid.add(i) + elif node2.nodeName == "end" or node2.nodeName == "jump" or node2.nodeName == "label" or node2.nodeName == "goto": + lastIndex1 = -1 + lastIndex2 = -1 + lastOffsetX = 0 + lastOffsetY = 0 + cnt = cnt + 1 + elif node2.nodeName == "pause": + try: + delay = int(node2.attributes["delay"].value) + except: + delay = 0 + if delay <= 0: + showMsgSprite(file, "incorrect delay in pause tag " + name, iserr) + + elif node2.nodeName == "#text" or node2.nodeName == "#comment": + None + else: + showMsgSprite(file, "unknown animation tag: " + node2.nodeName + ", " + name, False) + + if node2.nodeName == "jump": + try: + jaction = node2.attributes["action"].value + except: + jaction = "" + if jaction == "" or jaction is None: + showMsgSprite(file, "no action attribute in jump tag " + name, iserr) + elif node2.nodeName == "label": + try: + label = node2.attributes["name"].value + except: + label = "" + if label == "" or label is None: + showMsgSprite(file, "no name attribute in label tag " + name, iserr) + else: + if label in labels: + showMsgSprite(file, "duplicate label " + label + " " + name + "\n" \ + + node2.toxml(), iserr) + else: + labels.add(label) + elif node2.nodeName == "goto": + try: + label = node2.attributes["label"].value + except: + label = "" + if label == "" or label is None: + showMsgSprite(file, "no label attribute in goto tag " + name, iserr) + if cnt == 0: + showMsgSprite(file, "no frames or sequences in action: " + name, iserr) + + if name == "attack": + if lastAttack is not None and lastAttack != "end": + showMsgSprite(file, "last attack tag should be or attack animation "\ + "can be infinite. direction: " + direction, False) + + + 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, src, iserr): + try: + img = Image.open(fullPath, "r") + img.load() + except: + showMsgFile(file, "incorrect image format" + src, iserr) + return + + if img.format != "PNG": + showMsgFile(file, "image format is not png" + src, 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, sfxDir, msg): + fullPath = parentDir + "/" + sfxDir + file + if not os.path.isfile(fullPath) or os.path.exists(fullPath) == False: + print "error:" + fullPath + if msg != "": + showMsgFile(file, "sound file not found: " + msg , True) + else: + showMsgFile(file, "sound file not found", True) + return + try: + 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 + try: + dom = minidom.parse(fullPath) + except: + showMsgFile(file, "incorrect particle xml file", True) + return + + 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] + if imagecolor != None and len(imagecolor) > 0: + testDye(id, imagecolor, "image=" + image, src, True) + fullName = parentDir + "/" + image + if not os.path.isfile(fullName) or os.path.exists(fullName) == False: + showMsgFile(file, "image file not exist: " + image, True) + else: + testImageFile(image, fullName, 0, " " + file,True) + for node in parentNode.getElementsByTagName("emitter"): + testEmitters(id, file, node, src) + + + +def testItems(fileName, imgDir): + global warnings, errors, safeDye + if not stfu: + print "Checking " + fileName + try: + dom = minidom.parse(parentDir + "/" + fileName) + except Exception as err: + print "error: " + fileName + ": corrupted" + print err + errors = errors + 1 + return + idset = set() + oldId = None + for node in dom.documentElement.childNodes: + if node.nodeName == "include": + try: + name = node.attributes["name"].value + if name == "": + errors = errors + 1 + print "error: " + fileName + ": Empty include name"; + testItems(name, imgDir) + except: + errors = errors + 1 + print "error: " + fileName + ": Broken include tag"; + continue + if node.nodeName != "item": + continue + + if node.parentNode != dom.documentElement: + continue + + try: + id = node.attributes["id"].value + except: + if oldId is None: + print "error: " + fileName + ": item without id" + else: + print "error: " + fileName + ": item without id. Last id was: " + oldId + errors = errors + 1 + continue + oldId = id + if id in idset: + print "error: " + fileName + ": duplicated id=" + id + errors = errors + 1 + else: + idset.add(id) + + idI = int(id) + + try: + colors = node.attributes["colors"].value + except: + colors = None + + try: + type = node.attributes["type"].value + except: + type = "" + print "warn: " + fileName + ": 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 = "" + + try: + drawBefore = node.attributes["drawBefore"].value + except: + drawBefore = "" + + try: + drawAfter = node.attributes["drawAfter"].value + except: + drawAfter = "" + +# try: +# drawPriority = int(node.attributes["drawPriority"].value) +# except: +# drawPriority = 0 + + if type == "hairsprite": + if idI >= 0: + print "error: " + fileName + ": hairsprite with id=" + id + errors = errors + 1 + elif idI < -100: + print "error: " + fileName + ": hairsprite override player sprites" + errors = errors + 1 + + safeDye = True + testSprites(id, node, True, True, True, "", True) + safeDye = False + + elif type == "racesprite": + if idI >= 0: + print "error: " + fileName + ": racesprite with id=" + id + errors = errors + 1 + elif idI > -100: + print "error: " + fileName + ": 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" or type == "card": + if image == "": + print "error: " + fileName + ": missing image attribute on id=" + id + errors = errors + 1 + continue + elif len(imagecolor) > 0: + if colors is None: + testDye(id, imagecolor, "image=" + image0, fileName, True) + else: + testDyeChannel(id, imagecolor, "image=" + image0, True) + if colors not in colorsList: + print "error: " + fileName + ": colors value " + colors + " not found in itemcolors.xml" + errors = errors + 1 + + if floorcolor != None and len(floorcolor) > 0: + if colors is None: + testDye(id, floorcolor, "floor=" + floor0, fileName, True) + else: + testDyeChannel(id, imagecolor, "floor=" + floor0, True); + if colors not in colorsList: + print "error: " + fileName + ": colors value " + colors + " not found in itemcolors.xml" + errors = errors + 1 + + if description == "": + print "warn: " + fileName + ": missing description attribute on id=" + id + warnings = warnings + 1 + elif description == ".": + print "warn: " + fileName + ": broken description attribute on id=" + id + warnings = warnings + 1 + + if missile != "": + testParticle(id, missile, fileName) + + testSounds(id, node, "item") + + try: + floorSprite = node.getElementsByTagName("floor")[0] + except: + floorSprite = None + if floorSprite != None: + if floor != None: + print "error: " + fileName + ": found attribute floor and tag floor. " + \ + "Should be only one tag or attribute. id=" + id + errors = errors + 1 + testSprites(id, floorSprite, False, colors is None, True, "", 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) + errors = errors + 1 + else: + testImageFile(imgDir + floor, fullPath, 0, "", True) + + testItemReplace(id, node, "replace") + if drawBefore != "": + checkSpriteName(id, drawBefore) + if drawAfter != "": + checkSpriteName(id, drawAfter) + + try: + attackaction = node.attributes["attack-action"].value + except: + attackaction = "" + + testSprites(id, node, True, colors is None, False, attackaction, 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, colors is None, True, "", err) + elif type == "other": + None + elif type != "": + print "warn: " + fileName + ": unknown type '" + type + "' for id=" + id + warnings = warnings + 1 + + +def testItemReplace(id, rootNode, name): + global warnings, errors + for node in rootNode.getElementsByTagName(name): + if node.parentNode != rootNode: + continue + try: + sprite = node.attributes["sprite"].value + except: + if len(node.attributes) != 0: + print "error: reading replace sprite name, id=" + str(id) + errors = errors + 1 + continue + checkSpriteName(id, sprite) + for itemNode in node.getElementsByTagName("item"): + if itemNode.parentNode != node: + continue + #TODO here need check "from" and "to" for correct item id + + +def checkSpriteName(id, name): + global warnings, errors + if name != "race" and name != "type" and name != "shoes" and name != "boot" and \ + name != "boots" and name != "bottomclothes" \ + and name != "bottom" and name != "pants" and name != "topclothes" and \ + name != "top" and name != "torso" and name != "body" and name != "misc1" \ + and name != "misc2" and name != "scarf" and name != "scarfs" and \ + name != "hair" and name != "hat" and name != "hats" and name != "wings" \ + and name != "glove" and name != "gloves" and name != "weapon" and \ + name != "weapons" and name != "shield" and name != "shields" and \ + name != "amulet" and name != "amulets" and name != "ring" and name != "rings": + print "error: unknown sprite name " + name + ", id=" + str(id) + errors = errors + 1 + + +def testMonsters(fileName): + global warnings, errors + if not stfu: + print "Checking " + fileName + dom = minidom.parse(parentDir + "/" + fileName) + idset = set() + for node in dom.documentElement.childNodes: + if node.nodeName == "include": + try: + name = node.attributes["name"].value + if name == "": + errors = errors + 1 + print "error: " + fileName + ": Empty include name"; + testMonsters(name) + except: + errors = errors + 1 + print "error: " + fileName + ": Broken include tag"; + continue + if node.nodeName == "monster": + try: + id = node.attributes["id"].value + except: + print "error: " + fileName + ": no id for monster" + errors = errors + 1 + continue + + if id in idset: + print "error: " + fileName + ": duplicate id=" + id + errors = errors + 1 + else: + idset.add(id) + + try: + name = node.attributes["name"].value + except: + print "error: " + fileName + ": no name for id=" + id + errors = errors + 1 + name = "" + + testTargetCursor(id, node, fileName) + testSprites(id, node, False, True, True, "", 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, True) + 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 + havemiss = False + 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" \ + and event != "move" and event != "sit" and event != "spawn": + print "error: incorrect sound event name " + event + " in id=" + id + errors = errors + 1 + elif type == "item": + if event != "hit" and event != "strike" and event != "miss": + print "error: incorrect sound event name " + event + " in id=" + id + errors = errors + 1 + if event == "strike" or event == "miss": + if havemiss: + print "error: miss and strike attributes at same time in id=" + id + errors = errors + 1 + havemiss = True + + testSound(sound.childNodes[0].data, sfxDir, "") + +def testNpcs(file): + global warnings, errors + if not stfu: + print "Checking " + file + dom = minidom.parse(parentDir + "/" + file) + idset = set() + for node in dom.documentElement.childNodes: + if node.nodeName == "include": + try: + name = node.attributes["name"].value + if name == "": + errors = errors + 1 + print "error: " + file + ": Empty include name"; + testNpcs(name) + except: + errors = errors + 1 + print "error: " + file + ": Broken include tag"; + continue + if node.nodeName != "npc": + continue + + try: + id = node.attributes["id"].value + except: + print "error: " + file + ": no id for npc" + errors = errors + 1 + continue + + if id in idset: + print "error: " + file + ": duplicate npc id=" + id + errors = errors + 1 + else: + idset.add(id) + + testSprites(id, node, False, True, True, "", 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 readAttr2(node, attr, dv): + try: + return node.attributes[attr].value + except: + return dv + + +def testMap(mapName, 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) + mapVersion = readAttr(root, "version", "1.0", "error: missing map version: " + file, True) + + if mapWidth == 0 or mapHeight == 0 or mapTileWidth == 0 or mapTileHeight == 0: + return + + mapVersion = mapVersion.replace(".", "") + try: + mapVersion = int(mapVersion) + except: + showMsgFile(file, "Invalid map version: " + str(mapVersion), False) + + if mapVersion < tiledVersion: + showMsgFile(file, "Outdated map version: " + str(mapVersion), False) + + if mapWidth < borderSize * 2 + 1: + if silent == False or file.find("maps/test") != 0: + showMsgFile(file, "map width to small: " + str(mapWidth), False) + if mapHeight < borderSize * 2 + 1: + if silent == False or file.find("maps/test") != 0: + showMsgFile(file, "map height to small: " + str(mapHeight), False) + + if len(dom.getElementsByTagName("properties")) < 1: + showMsgFile(file, "missing map properties", True) + return + + for props in dom.getElementsByTagName("properties"): + for prop in props.getElementsByTagName("property"): + try: + name = prop.attributes["name"].value + except: + name = "" + if name == "": + showMsgFile(file, "wrong property", True) + continue + try: + value = prop.attributes["value"].value + except: + value = "" + if value == "" and name == "name": + showMsgFile(file, "empty map name property", True) + continue + + # Total minimum required width + if mapWidth < 60: + name1=file.find("maps/test") + name2=file.find("maps/000-1") + if name1 == 0 or name2 == 0: + pass + else: + showMsgFile(file, "total map width to small: " + str(mapWidth), False) + + tilesMap = dict() + + for tileset0 in dom.getElementsByTagName("tileset"): + tileset = tileset0 + try: + firstGid = int(tileset.attributes["firstgid"].value) + except: + firstGid = 0 + + try: + source = tileset.attributes["source"].value + if source is not None and source != "": + file2 = os.path.abspath(parentDir + os.path.sep + mapsDir + source) + if not os.path.isfile(file2): + showMsgFile(file, "missing source file in tileset " + source, True) + except: + source = "" + + tile = Tileset() + tile.firstGid = firstGid + tile.lastGid = 0 + + if source[-4:] == ".tsx": + relativePath = parentDir + "/" + mapsDir + source + try: + dom2 = minidom.parse(relativePath) + tileset = dom2.documentElement + idx = relativePath.rfind("/") + relativePath = relativePath[:idx+1] + relativePath2 = source + idx = relativePath2.rfind("/") + relativePath2 = relativePath2[:idx+1] + except: + showMsgFile(file, "tsx not found: " + source, True) + relativePath = "" + relativePath2 = "" + else: + relativePath = "" + relativePath2 = "" + + 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) + if firstGid in tilesMap: + showMsgFile(file, "tile with firstgid " + str(firstGid) + \ + " already exist: " + name + ", " + file, True) + continue + + tile.width = tileWidth + tile.tileWidth = tileWidth + tile.height = tileHeight + tile.tileHeight = tileHeight + tile.name = name + + 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 + + image = images[0] + source = readAttr(image, "source", None, "error: missing source in image tag in tile " \ + + name + ": " + file, True) + + if source != None: + if relativePath == "": + imagePath = os.path.abspath(parentDir + "/" + mapsDir + source) + else: + imagePath = os.path.abspath(relativePath + 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: + if s1 == 0: + s1 = int(tileWidth) + 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 + (int(width / int(tileWidth)) * int(height / int(tileHeight))) - 1 + if height != s2: + if s2 == 0: + s2 = int(tileHeight) + showMsgFile(file, "image width " + str(height) + \ + " (need " + str(s2) + ") is not multiply to tile size " + \ + str(tileHeight) + ". " + source + ", " + name, False) + + tile.source = relativePath2 + source + # hack to change relative back path to normal relative path + if len(tile.source) > 3 and tile.source[:11] == "../graphics": + tile.source = tile.source[3:] + tilesMap[tile.firstGid] = tile + + if mapName not in mapToAtlas: + showMsgFile(file, "map dont have atlas", True) + + tileset = tileset0 + + testTiles(mapName, file, tilesMap) + layers = dom.getElementsByTagName("layer") + objects = dom.getElementsByTagName("object") + if layers == None or len(layers) == 0: + showMsgFile(file, "map dont have layers", True) + return + + fringe = None + collision = None + lowLayers = [] + overLayers = [] + beforeFringe = True + haveHeight = False + + for layer in layers: + name = readAttr(layer, "name", None, "layer dont have name", True) + if name == None: + continue + if name.lower() == "height" or name.lower() == "heights": + haveHeight=True + 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.append(obj) + else: + overLayers.append(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) + testOverSizedTiles(obj, tilesMap, file) + + if fringe == None: + showMsgFile(file, "missing fringe layer", True) + if collision == None: + showMsgFile(file, "missing collision layer", True) + elif mapName != "test.tmx" and mapName != "testbg.tmx": + ids = testCollisionLayer(file, collision, tilesMap) + if ids[0] != None and len(ids[0]) > 0: + if silent == False or file.find("maps/test") != 0: + showLayerErrors(file, ids[0], "empty tiles in collision border", False) + if ids[1] != None and len(ids[1]) > 0: + if silent == False or file.find("maps/test") != 0: + showLayerErrors(file, ids[1], "incorrect tileset index in collision layer", False) + + if len(lowLayers) < 1: + showMsgFile(file, "missing low layers", False) + if len(overLayers) < 1: + if (silent == False or file.find("maps/test") != 0) and herc == False: + showMsgFile(file, "missing over layers", False) + + if not haveHeight: + showMsgFile(file, "missing height layer", False) + + if fringe != None: + lowLayers.append(fringe) + warn1 = None + + if len(overLayers) > 0: + testData = dict() + warn1 = testLayerGroups(file, lowLayers, collision, None, tilesMap, False) + lowLayers.extend(overLayers) + err1 = testLayerGroups(file, lowLayers, collision, testData, tilesMap, False) + reportAboutTiles(file, testData) + else: + testData = dict() + err1 = testLayerGroups(file, lowLayers, collision, testData, tilesMap, False) + reportAboutTiles(file, testData) + + if warn1 != None and err1 != None: + warn1 = warn1 - err1 + if warn1 != None and len(warn1) > 0: + if silent != True: + 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) + + for objx in objects: + x = readAttr(objx, "x", 0, "object in invalid X position", False) + y = readAttr(objx, "y", 0, "object in invalid Y position", False) + w = readAttr2(objx, "width", 0) + h = readAttr2(objx, "height", 0) + try: + fs=False + if (float(x) != int(x)): + showMsgFile(file, "Invalid object X pos - must be integer", False) + fs=1 + if (float(y) != int(y)): + showMsgFile(file, "Invalid object Y pos - must be integer", False) + fs=1 + if (float(w) != int(w)): + showMsgFile(file, "Invalid object Width - must be integer", False) + fs=1 + if (float(h) != int(h)): + showMsgFile(file, "Invalid object Height - must be integer", False) + fs=1 + if fs: + id1=readAttr(objx, "id", "?", "invalid object ID", False) + name1=readAttr(objx, "name", "?", "invalid object name", False) + type1=readAttr(objx, "type", "?", "invalid object type", False) + showMsgFile(file, "Broken object: id %s name %s (%s,%s,%s,%s) type %s" % (id1, name1, x, y, w, h, type1), True); + except: + id1=readAttr(objx, "id", "?", "invalid object ID", False) + showMsgFile(file, "Broken object ID %s - x/y/h/w corrupted data detected" % id1, True) + +def testOverSizedTiles(layer, tiles, file): + global warnings, errors + + oversizeErrList = [] + ignoreErrList = [] + ignoreTilesetList = set() + ignoredFiles = [] + if "ignored" in atlasToFiles: + ignoredFiles = atlasToFiles["ignored"] + for x in range(0, layer.width): + for y in range(0, layer.height): + idx = ((y * layer.width) + x) * 4 + val = getLDV(layer.arr, idx) + if val == 0: + continue + + tile, tilesetName = findTileByGid(tiles, val) + if layer.name.lower() not in ("collision", "heights", "height") and tilesetName in ignoredFiles: + ignoreErrList.append((x, y)) + ignoreTilesetList.add(tilesetName) + if layer.name.lower() == "fringe": + continue + if tile is None: + # now ignoring, this happend because layer parser + # not support includes + None + elif tile.tileWidth > 32 and x + 1 < layer.width: + for x2 in range(x + 1, x + 1 + int(tile.width / 32), 1): + idx = ((y * layer.width) + x2) * 4 + val = getLDV(layer.arr, idx) + tile, _ = findTileByGid(tiles, val) + if val > 0: + oversizeErrList.append((x, y)) + if silent != True: + warnings = warnings + 1 + elif tile.tileHeight > 32 and y - 1 > 0: + for y2 in range(y - 1, y - 1 - int(tile.height / 32), -1): + idx = ((y2 * layer.width) + x) * 4 + val = getLDV(layer.arr, idx) + tile, _ = findTileByGid(tiles, val) + if val > 0: + oversizeErrList.append((x, y)) + if silent != True: + warnings = warnings + 1 + + if len(oversizeErrList) > 0 and silent != True: + print "error: " + file + ": Oversized tile overlapped to next tile in layer " + layer.name + \ + ". Possible incorrect map drawing" + errors = errors + 1 + errStr = "" + k = 0 + for err in oversizeErrList: + errStr = errStr + str(err) + ", " + k = k + 1 + if k > 100: + errStr = errStr + "..." + break + print errStr + + if len(ignoreErrList) > 0: + errStr = "" + for err in ignoreTilesetList: + if errStr != "": + errStr = errStr + ", " + errStr = errStr + err + print("error: {0}: Tiles from ignored atlas used in layer {1}. Tilesets: {2}. " + "Possible incorrect map drawing".format(file, layer.name, errStr)) + errors = errors + 1 + errStr = "" + k = 0 + for err in ignoreErrList: + errStr = errStr + str(err) + ", " + k = k + 1 + if k > 100: + errStr = errStr + "..." + break + print errStr + + +def testTiles(mapName, file, tilesMap): + ignoredFiles = [] + if "ignored" in atlasToFiles: + ignoredFiles = atlasToFiles["ignored"] + for firstGid in tilesMap: + tile1 = tilesMap[firstGid] + if mapName in mapToAtlas: + atlasName = mapToAtlas[mapName] + if atlasName in atlasToFiles: + files = atlasToFiles[atlasName] + if tile1.source not in files and tile1.source not in ignoredFiles: + showMsgFile(file, "tileset '{0} ({1})' not present in atlas '{2}'".format( + tile1.name, + tile1.source, + atlasName), + True) + for gid2 in tilesMap: + if firstGid != gid2: + tile2 = tilesMap[gid2] + if (tile1.firstGid >= tile2.firstGid and tile1.firstGid <= tile2.lastGid) or \ + (tile1.lastGid >= tile2.firstGid and tile1.lastGid <= tile2.lastGid): + showMsgFile(file, "overlaping tilesets gids \"" + tile1.name \ + + "\" and \"" + tile2.name + "\"", True) + + +def reportAboutTiles(file, data): + if testBadCollisions == False: + return + for k in data: + d = data[k] + if d[0] != 0 and d[2] != 0: + #print file + ": " + str(k) + ": " + str(d) + testCollisionPoints(file, k, d, 1, 3, \ + "possible tiles should be without collision: ", \ + "because no collision: ", False) + testCollisionPoints(file, k, d, 3, 1, \ + "possible tiles should be with collision: ", \ + "because collision: ", False) + + +def testCollisionPoints(file, tileId, data, idx1, idx2, msg1, msg2, iserr): + #print "test: " + str(idx1) + ", " + str(idx2) + cnt1 = 0 + cnt2 = 0 + for point in data[idx1]: + if point[2] > 0: + cnt1 = cnt1 + 1 + for point in data[idx2]: + if point[2] > 0: + cnt2 = cnt2 + 1 + + ln1 = len(data[idx1]) + ln2 = len(data[idx2]) + #print "cnt1=" + str(cnt1) + ", cnt2=" + str(cnt2) + ", ln1=" + str(ln1) + ", ln2=" + str(ln2) + if ln1 > 0 and ln2 > 0 and cnt2 > 0 and cnt2 < cnt1 - tileNumDiff and cnt2 < maxNumErrTiles: + text = msg1 + c = 0 + for point in data[idx2]: + if point[2] > 0: + if c > 100: + break + text = text + "(" + str(point[0]) + ", " + str(point[1]) + "), " + c = c + 1 + text = text[:len(text)-2] + " " + msg2 + c = 0 + for point in data[idx1]: + if c > 100: + break + text = text + "(" + str(point[0]) + ", " + str(point[1]) + "), " + c = c + 1 + showMsgFile(file, text[:len(text)-2], iserr) + + +def testCollisionLayer(file, layer, tiles): + haveTiles = False + tileset = set() + badtiles = set() + arr = layer.arr + x1 = borderSize + y1 = borderSize + x2 = layer.width - borderSize + y2 = layer.height - borderSize + if x2 < 0: + x2 = 0 + if y2 < 0: + y2 = 0 + + if arr is None : + return (set(), set()) + + for x in range(0, layer.width): + for y in range(0, layer.height): + idx = ((y * layer.width) + x) * 4 + val = getLDV(arr, idx) + if val != 0: + haveTiles = True + tile, tilesetName = findTileByGid(tiles, val) + if tile is not None: + idx = val - tile.firstGid + if idx > 6: # 6 - max collision type + badtiles.add(((x, y), idx)) + else: + badtiles.add(((x, y), "+{0}".format(val))) + if val == 0 and (x < x1 or x > x2 or y < y1 or y > y2): + tileset.add((x, y)) + + if haveTiles == False: + if silent == False or file.find("maps/test") != 0: + showMsgFile(file, "empty collision layer", False) + return (set(), set()) + + return (tileset, badtiles) + + +def findTileByGid(tiles, gid): + for firstGid in tiles: + if firstGid <= gid: + tile = tiles[firstGid] + if tile.lastGid >= gid: + return (tile, tile.source) + return (None, None) + + +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 getLDV2(arr, x, y, width, height, tilesMap): + ptr = ((y * width) + x) * 4 + res = getLDV(arr, ptr) + yend = height - 1 + if yend - y > 5: + yend = y + 5 + for y2 in range(height - 1, y, -1): + x0 = x - 3 + if x0 < 0: + x0 = 0 + for x2 in range(x0, x + 1): + ptr = ((y2 * width) + x2) * 4 + val = getLDV(arr, ptr) + tile, _ = findTileByGid(tilesMap, val) + if tile is not None: + if (tile.tileHeight > 32 or y2 == y) and (tile.tileWidth > 32 or x2 == x): + hg = tile.tileHeight / 32 + wg = tile.tileWidth / 32 + if (y2 - y < hg or y2 == y) and (x2 - x < wg or x2 == x): + res = val + + return res + + +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": +# if compression != "zlib": +# showMsgFile(file, "invalid compression " + compression + \ +# " in layer: " + name, True) +# continue +# else: +# showMsgFile(file, "not supported compression by old clients " \ +# + compression + " in layer: " + name, False) + binData = data.childNodes[0].data.strip() + binData = binData.decode('base64') + if compression == "gzip": + dc = zlib.decompressobj(16 + zlib.MAX_WBITS) + else: + dc = zlib.decompressobj() + layerData = dc.decompress(binData) + arr = array.array("B") + arr.fromstring(layerData) + layer.arr = arr +# print file +# for item in arr: +# print item + elif encoding == "csv": + if compression != "": + showMsgFile(file, "not supported compression " + compression + \ + " for csv layer format:" + name, True) + binData = data.childNodes[0].data.strip() + f = StringIO.StringIO(binData) + arr = list(csv.reader(f, delimiter=',', quotechar='|')) + layer.arr = [] +# print file + for row in arr: + try: + for item in row: + if item != "": + nums = splitBytes(int(item)) + layer.arr.append(nums[0]) + layer.arr.append(nums[1]) + layer.arr.append(nums[2]) + layer.arr.append(nums[3]) + except: + None + + f.close() + arr = array.array('i', (layer.arr)) + layer.arr = arr +# for item in arr: +# print item + + elif encoding == "": + if compression != "": + showMsgFile(file, "not supported compression " + compression + \ + " for xml layer format:" + name, True) + + layer.arr = [] + tiles = data.getElementsByTagName("tile") +# print file + for tile in tiles: + try: + gid = int(tile.attributes["gid"].value) + except: + showMsgFile(file, "incorrect xml layer format: " + name, True) + return layer + nums = splitBytes(gid) + layer.arr.append(nums[0]) + layer.arr.append(nums[1]) + layer.arr.append(nums[2]) + layer.arr.append(nums[3]) + + arr = array.array('i', (layer.arr)) + layer.arr = arr +# for item in arr: +# print item + + + # here may be i should check is tiles correct or not, but i will trust to tiled + return layer + + +def splitBytes(num): + i1 = int(num % 256) + i2 = int(((num % 65536) - i1) / 256) + i3 = int(((num % 16777216) - i2 - i1) / 65536) + i4 = int(((num % 4294967296) - i3 - i2 - i1) / 16777216) + return (i1, i2, i3, i4) + +def testLayerGroups(file, layers, collision, tileInfo, tilesMap, 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 + lastTileId = 0 + 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 testBadCollisions == True: + val = getLDV2(arr, x, y, layer.width, layer.height, tilesMap) + else: + val = 0 + val1 = getLDV(arr, ptr) + if val1 != 0: + good = True + if val == val1 and testBadCollisions == True: + lastTileId = val + if good == False: + errset.add((x,y)) + elif testBadCollisions == True and collision != None and tileInfo != None: + if lastTileId not in tileInfo: + tileInfo[lastTileId] = [0, set(), 0, set()] + ti = tileInfo[lastTileId] + flg = getLDV(collision.arr, ((y * collision.width) + x) * 4) + cnt = countCollisionsNear(collision, x, y) + k = 0 + if flg > 0: + if cnt[1] < cnt[0] and cnt[0] - cnt[1] > 5: + k = 1 + ti[2] = ti[2] + 1 + ti[3].add((x, y, k)) + else: + if cnt[0] > cnt[1] and cnt[0] - cnt[1] > 5: + k = 1 + ti[0] = ti[0] + 1 + ti[1].add((x, y, k)) + + return errset + + +def countCollisionsNear(layer, x, y): + arr = layer.arr + x1 = x - 1 + y1 = y - 1 + x2 = x + 1 + y2 = y + 1 + col = 0 + nor = 0 + + if x1 < 0: + x1 = 0 + if x2 >= layer.width: + x2 = layer.width - 1 + if y1 < 0: + y1 = 0 + if y2 >= layer.height: + y2 = layer.height - 1 + + for f in range(x1, x2 + 1): + for d in range(y1, y2 + 1): + if f != x or d != y: + val = getLDV(arr, ((d * layer.width) + f) * 4) + if val == 0: + nor = nor + 1 + else: + col = col + 1 + return (nor, col) + + +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(file, mapsDir + file, dir + file) + +def testDirExists(path): + global errors + fullName = parentDir + "/" + path + if not os.path.exists(fullName): + print "error: path '" + path + "' not exists." + errors = errors + 1 + elif not os.path.isdir(fullName): + print "error: path '" + path + "' is incorrect directory." + errors = errors + 1 + +def testDefaultFiles(): + global warnings + print "Checking default files" + testDirExists(iconsDir) + testDirExists(spritesDir) + testDirExists(particlesDir) + testDirExists(minimapsDir) + testDirExists(mapsDir) + testDirExists(sfxDir) + if silent != True: + testDirExists(musicDir) + testDirExists(wallpapersDir) + + testSprite("0", spriteErrorFile, 0, True, "", True) + testParticle("0", particlesDir + levelUpEffectFile, "levelUpEffectFile") + testParticle("0", particlesDir + portalEffectFile, "portalEffectFile") + fullName = parentDir + "/" + wallpapersDir + wallpaperFile + if not os.path.isdir(fullName) and os.path.exists(fullName): + testImageFile(wallpapersDir + wallpaperFile, fullName, 0, "", False) + + +def testMinimapsDir(): + global errors, warnings + + print "Checking minimaps" + fullPath = parentDir + "/" + minimapsDir + if not os.path.isdir(fullPath) or not os.path.exists(fullPath): + print "warn: minimaps dir not exist" + warnings = warnings + 1 + return + for file in os.listdir(fullPath): + if filtimages.search(file): + fullName = parentDir + "/" + minimapsDir + file + testImageFile(minimapsDir + file, fullName, 0, "", True) + + +def testImagesDir(imagesDir, sz): + global errors, warnings + + fullPath = parentDir + "/" + imagesDir + if not os.path.isdir(fullPath) or not os.path.exists(fullPath): + return + for file in os.listdir(fullPath): + file2 = fullPath + "/" + file + if file[0] == ".": + continue + if not os.path.isfile(file2): + testImagesDir(imagesDir + file + "/", sz) + if filtimages.search(file): + fullName = parentDir + "/" + imagesDir + file + testImageFile(imagesDir + file, fullName, sz, "", True) + + +def testSpritesDir(dir): + global errors, warnings, safeDye + + fullPath = parentDir + "/" + spritesDir + dir + if not os.path.isdir(fullPath) or not os.path.exists(fullPath): + return + + for file in os.listdir(fullPath): + file2 = fullPath + "/" + file + if file[0] == ".": + continue + if not os.path.isfile(file2): + testSpritesDir(dir + file + "/") + if filtimages.search(file): + fullName = parentDir + "/" + spritesDir + dir + file + testImageFile(spritesDir + dir, fullName, 0, spritesDir + dir + file, True) + elif filtxmls.search(file): + fullName = dir + file + safeDye = True + testSprite("0", dir + file, 0, True, "", True) + safeDye = False + + + +def testParticlesDir(dir): + global errors, warnings, safeDye + + fullPath = parentDir + "/" + dir + if not os.path.isdir(fullPath) or not os.path.exists(fullPath): + return + for file in os.listdir(fullPath): + file2 = fullPath + "/" + file + if file[0] == ".": + continue + if not os.path.isfile(file2): + testParticlesDir(dir + file + "/") + if filtimages.search(file): + fullName = parentDir + "/" + dir + file + testImageFile(dir + file, fullName, 0, "", True) + elif filtxmls.search(file): + fullName = dir + file + safeDye = True + testParticle("0", dir + file, "") + safeDye = False + + +def testSoundsDir(dir, sfxDir): + global errors, warnings + + fullPath = parentDir + "/" + sfxDir + dir + if not os.path.isdir(fullPath) or not os.path.exists(fullPath): + print "warn: directory " + sfxDir + " not exist" + warnings = warnings + 1 + return + for file in os.listdir(fullPath): + file2 = fullPath + "/" + file + if file[0] == ".": + continue + if not os.path.isfile(file2): + testSoundsDir(dir + file + "/", sfxDir) + elif filtogg.search(file): + testSound(dir + file, sfxDir, "") + + +def testItemColors(fileName): + global warnings, errors, safeDye, colorLists + print "Checking itemcolors.xml" + try: + dom = minidom.parse(parentDir + "/" + fileName) + except: + return + + for node in dom.getElementsByTagName("list"): + if node.parentNode != dom.documentElement: + continue + + try: + name = node.attributes["name"].value + except: + print "error: colors list dont have name" + errors = errors + 1 + continue + if name in colorsList: + print "error: duplicate color list: " + name + errors = errors + 1 + continue + colorsList.add(name) + colors = set() + names = set() + for colorNode in node.getElementsByTagName("color"): + if colorNode.parentNode != node: + continue + try: + id = colorNode.attributes["id"].value + except: + print "error: getting id in list: " + name + errors = errors + 1 + continue + try: + colorName = colorNode.attributes["name"].value + except: + print "error: getting name in list: " + name + errors = errors + 1 + continue + try: + colorDye = colorNode.attributes["value"].value + except: + print "error: getting color in list: " + name + errors = errors + 1 + if id in colors: + print "error: color with id " + str(id) + " already in list: " + name + errors = errors + 1 + else: + colors.add(id) + if colorName in names: + print "error: color with name \"" + colorName + "\" already in list: " + name + errors = errors + 1 + else: + names.add(colorName) + testDyeColors(id, colorDye, colorDye, name, True) + +def loadMapAtlases(fileName): + mapToAtlas = dict() + atlasToFiles = dict() + try: + root = ElementTree.parse(parentDir + "/" + fileName).getroot() + except: + showMsgFile(fileName, "load xml error. Probably file missing", True) + return (mapToAtlas, atlasToFiles) + + for node in root.findall("map"): + mapName = node.attrib["name"] + atlasNode = node.find("atlas") + if atlasNode == None: + continue + atlasName = atlasNode.attrib["name"] + mapToAtlas[mapName] = atlasName + for node in root.findall("atlas"): + atlasName = node.attrib["name"] + files = [] + for fileNode in node.findall("file"): + fileName = fileNode.attrib["name"] + files.append(fileName) + atlasToFiles[atlasName] = files + for mapName in mapToAtlas: + atlasName = mapToAtlas[mapName] + if atlasName not in atlasToFiles: + showMsgFile(fileName, "atlas '{0}' assigned to map not present in maps.xml".format(atlasName), True) + + return (mapToAtlas, atlasToFiles) + +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) + + +if len(sys.argv) == 2: + if sys.argv[1] == "all": + showAll = True + elif sys.argv[1] == "silent": + silent = True + elif sys.argv[1] == "stfu": + silent = True + stfu = True + elif sys.argv[1] == "herc": + silent = True + herc = True + +showHeader() +print "Detecting clientdata dir" +detectClientData([".", "..", "../../client-data", parentDir]) +print "Checking xml file syntax" +enumDirs(parentDir) +loadPaths() +(mapToAtlas, atlasToFiles) = loadMapAtlases("/maps.xml") +testDefaultFiles() +testItemColors("/itemcolors.xml") +testItems("/items.xml", iconsDir) +testMonsters("/monsters.xml") +testNpcs("/npcs.xml") +testMaps(mapsDir) +testMinimapsDir() +print "Checking images dir" +testImagesDir(wallpapersDir, 0) +print "Checking icons dir" +testImagesDir(iconsDir, 32) +print "Checking sprites dir" +testSpritesDir("") +print "Checking particles dir" +testParticlesDir(particlesDir) +print "Checking sfx dir" +testSoundsDir("", sfxDir) +print "Checking music dir" +if silent != True: + testSoundsDir("", musicDir) +showFooter() +if errors > 0 or warnings > 0: + exit(1) diff --git a/CI/testxml/xsd/XMLSchema.xsd b/CI/testxml/xsd/XMLSchema.xsd new file mode 100644 index 0000000..3711a93 --- /dev/null +++ b/CI/testxml/xsd/XMLSchema.xsd @@ -0,0 +1,2262 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]> + + + + Part 1 version: Id: structures.xsd,v 1.2 2004/01/15 11:34:25 ht Exp + Part 2 version: Id: datatypes.xsd,v 1.3 2004/01/23 18:11:13 ht Exp + + + + + The schema corresponding to this document is normative, + with respect to the syntactic constraints it expresses in the + XML Schema language. The documentation (within <documentation> elements) + below, is not normative, but rather highlights important aspects of + the W3C Recommendation of which this is a part + + + + The simpleType element and all of its members are defined + towards the end of this schema document + + + + + Get access to the xml: attribute groups for xml:lang + as declared on 'schema' and 'documentation' below + + + + + + + This type is extended by almost all schema types + to allow attributes from other namespaces to be + added to user schemas. + + + + + + + + + + + + This type is extended by all types which allow annotation + other than <schema> itself + + + + + + + + + + + + + + + This group is for the + elements which occur freely at the top level of schemas. + All of their types are based on the "annotated" type by extension. + + + + + + + + + + + + This group is for the + elements which can self-redefine (see <redefine> below). + + + + + + + + + + + + A utility type, not for public use + + + + + + + + + + A utility type, not for public use + + + + + + + + + + A utility type, not for public use + + #all or (possibly empty) subset of {extension, restriction} + + + + + + + + + + + + + + + + A utility type, not for public use + + + + + + + + + + + + A utility type, not for public use + + #all or (possibly empty) subset of {extension, restriction, list, union} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + for maxOccurs + + + + + + + + + + + + + for all particles + + + + + + + + for element, group and attributeGroup, + which both define and reference + + + + + + + + 'complexType' uses this + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This branch is short for + <complexContent> + <restriction base="xs:anyType"> + ... + </restriction> + </complexContent> + + + + + + + + + + + + + + Will be restricted to required or forbidden + + + + + + Not allowed if simpleContent child is chosen. + May be overriden by setting on complexContent child. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This choice is added simply to + make this a valid restriction per the REC + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Overrides any setting on complexType parent. + + + + + + + + + + + + + + This choice is added simply to + make this a valid restriction per the REC + + + + + + + + + + + + + + + + No typeDefParticle group reference + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A utility type, not for public use + + #all or (possibly empty) subset of {substitution, extension, + restriction} + + + + + + + + + + + + + + + + + + + + + + + + The element element can be used either + at the top level to define an element-type binding globally, + or within a content model to either reference a globally-defined + element or type or declare an element-type binding locally. + The ref form is not allowed at the top level. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + group type for explicit groups, named top-level groups and + group references + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + group type for the three kinds of group + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This choice with min/max is here to + avoid a pblm with the Elt:All/Choice/Seq + Particle derivation constraint + + + + + + + + restricted max/min + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Only elements allowed inside + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + simple type for the value of the 'namespace' attr of + 'any' and 'anyAttribute' + + + + Value is + ##any - - any non-conflicting WFXML/attribute at all + + ##other - - any non-conflicting WFXML/attribute from + namespace other than targetNS + + ##local - - any unqualified non-conflicting WFXML/attribute + + one or - - any non-conflicting WFXML/attribute from + more URI the listed namespaces + references + (space separated) + + ##targetNamespace or ##local may appear in the above list, to + refer to the targetNamespace of the enclosing + schema or an absent targetNamespace respectively + + + + + A utility type, not for public use + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A subset of XPath expressions for use +in selectors + A utility type, not for public +use + + + + The following pattern is intended to allow XPath + expressions per the following EBNF: + Selector ::= Path ( '|' Path )* + Path ::= ('.//')? Step ( '/' Step )* + Step ::= '.' | NameTest + NameTest ::= QName | '*' | NCName ':' '*' + child:: is also allowed + + + + + + + + + + + + + + + + + + + + + + A subset of XPath expressions for use +in fields + A utility type, not for public +use + + + + The following pattern is intended to allow XPath + expressions per the same EBNF as for selector, + with the following change: + Path ::= ('.//')? ( Step '/' )* ( Step | '@' NameTest ) + + + + + + + + + + + + + + + + + + + + + + + + + The three kinds of identity constraints, all with + type of or derived from 'keybase'. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A utility type, not for public use + + A public identifier, per ISO 8879 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + notations for use within XML Schema schemas + + + + + + + Not the real urType, but as close an approximation as we can + get in the XML representation + + + + + + + + + First the built-in primitive datatypes. These definitions are for + information only, the real built-in definitions are magic. + + + For each built-in datatype in this schema (both primitive and + derived) can be uniquely addressed via a URI constructed + as follows: + 1) the base URI is the URI of the XML Schema namespace + 2) the fragment identifier is the name of the datatype + + For example, to address the int datatype, the URI is: + + http://www.w3.org/2001/XMLSchema#int + + Additionally, each facet definition element can be uniquely + addressed via a URI constructed as follows: + 1) the base URI is the URI of the XML Schema namespace + 2) the fragment identifier is the name of the facet + + For example, to address the maxInclusive facet, the URI is: + + http://www.w3.org/2001/XMLSchema#maxInclusive + + Additionally, each facet usage in a built-in datatype definition + can be uniquely addressed via a URI constructed as follows: + 1) the base URI is the URI of the XML Schema namespace + 2) the fragment identifier is the name of the datatype, followed + by a period (".") followed by the name of the facet + + For example, to address the usage of the maxInclusive facet in + the definition of int, the URI is: + + http://www.w3.org/2001/XMLSchema#int.maxInclusive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NOTATION cannot be used directly in a schema; rather a type + must be derived from it by specifying at least one enumeration + facet whose value is the name of a NOTATION declared in the + schema. + + + + + + + + + Now the derived primitive types + + + + + + + + + + + + + + + + + + + + + + + + + + + pattern specifies the content of section 2.12 of XML 1.0e2 + and RFC 3066 (Revised version of RFC 1766). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + pattern matches production 7 from the XML spec + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + pattern matches production 5 from the XML spec + + + + + + + + + + + + + + pattern matches production 4 from the Namespaces in XML spec + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A utility type, not for public use + + + + + + + + + + + + + + + + + + + + #all or (possibly empty) subset of {restriction, union, list} + + + A utility type, not for public use + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Can be restricted to required or forbidden + + + + + + + + + + + + + + + + + Required at the top level + + + + + + + + + + + + + + + + + + Forbidden when nested + + + + + + + + + + + + + + + + + We should use a substitution group for facets, but + that's ruled out because it would allow users to + add their own, which we're not ready for yet. + + + + + + + + + + + + + + + + + + + + + + + + + + + + base attribute and simpleType child are mutually + exclusive, but one or other is required + + + + + + + + + + + + + + + itemType attribute and simpleType child are mutually + exclusive, but one or other is required + + + + + + + + + + + + + + + + + memberTypes attribute must be non-empty or there must be + at least one simpleType child + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CI/testxml/xsd/checkfile.sh b/CI/testxml/xsd/checkfile.sh new file mode 100755 index 0000000..90df1f1 --- /dev/null +++ b/CI/testxml/xsd/checkfile.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +xmllint --format --schema tmw.xsd "${1}" 2>&1 >/dev/null | \ + grep -v ": Skipping import of schema located at " | \ + grep -v ".xml validates" diff --git a/CI/testxml/xsd/its.xsd b/CI/testxml/xsd/its.xsd new file mode 100644 index 0000000..9dbb432 --- /dev/null +++ b/CI/testxml/xsd/its.xsd @@ -0,0 +1,926 @@ + + + + + + + Container for global rules. + + + + + + + + + + + + + + + + + + + + + + + + Version of the ITS schema. + + + + + + + + + + + + + XPath expression identifying the nodes to be selected. + + + + + + + + + + Version of the ITS schema. + + + + + + + + + + + + + + + + The Translate data category information to be attached to + the current node. + + + + + + The nodes need to be translated. + + + + + The nodes must not be translated. + + + + + + + + + + Localization note. + + + + + + + The type of localization note. + + + + + + Localization note is an alert. + + + + + Localization note is a description. + + + + + + + + + + URI referring to the location of the localization note. + + + + + + + Pointer to a resource containing + information about the term. + + + + + + + Indicates a term locally. + + + + + + The value 'yes' means that this is a term. + + + + + The value 'no' means that this is not a term. + + + + + + + + + + The text direction for the context. + + + + + + Left-to-right text. + + + + + Right-to-left text. + + + + + Left-to-right override. + + + + + Right-to-left override. + + + + + + + + + + + + + + + + + + + The Translate data category information to be attached to + the current node. + + + + + + The nodes need to be translated. + + + + + The nodes must not be translated. + + + + + + + + + + Localization note. + + + + + + + The type of localization note. + + + + + + Localization note is an alert. + + + + + Localization note is a description. + + + + + + + + + + URI referring to the location of the localization note. + + + + + + + Pointer to a resource containing + information about the term. + + + + + + + Indicates a term locally. + + + + + + The value 'yes' means that this is a term. + + + + + The value 'no' means that this is not a term. + + + + + + + + + + The text direction for the context. + + + + + + Left-to-right text. + + + + + Right-to-left text. + + + + + Left-to-right override. + + + + + Right-to-left override. + + + + + + + + + Inline element to contain ITS information. + + + + + + + + + + + + + + + + + + + + + Rule about the Translate data category. + + + + + + + + + + The Translate data category information to be + applied to selected nodes. + + + + + + The nodes need to be translated. + + + + + The nodes must not be translated. + + + + + + + + + + + + + The Translate data category information to be attached to + the current node. + + + + + + The nodes need to be translated. + + + + + The nodes must not be translated. + + + + + + + + + Rule about the Localization Note data category. + + + + + + + + + + + + + + + + + + + Relative XPath expression pointing to a node that holds the localization note. + + + + + The type of localization note. + + + + + + Localization note is an alert. + + + + + Localization note is a description. + + + + + + + + URI referring to the location of the localization note. + + + + + Relative XPath expression pointing to a node that holds the URI referring to the location of the localization note. + + + + + + Contains a localization note. + + + + + + + + + + + + + + + + + + + + + + + + + + + Localization note. + + + + + + + The type of localization note. + + + + + + Localization note is an alert. + + + + + Localization note is a description. + + + + + + + + + + URI referring to the location of the localization note. + + + + + + Rule about the Terminology data category. + + + + + + + + + + Indicates whether the selection is a term or not. + + + + + + The value 'yes' means that this is a term. + + + + + The value 'no' means that this is not a term. + + + + + + + + URI referring to the resource providing information about the term. + + + + + Relative XPath expression pointing to a node containing a URI referring to the resource providing information about the term. + + + + + Relative XPath expression pointing to a node containing + information about the term. + + + + + + + + + + + Pointer to a resource containing + information about the term. + + + + + + + Indicates a term locally. + + + + + + The value 'yes' means that this is a term. + + + + + The value 'no' means that this is not a term. + + + + + + + + + Rule about the Directionality data category. + + + + + + + + + + The text direction for the selection. + + + + + + Left-to-right text. + + + + + Right-to-left text. + + + + + Left-to-right override. + + + + + Right-to-left override. + + + + + + + + + + + + + The text direction for the context. + + + + + + Left-to-right text. + + + + + Right-to-left text. + + + + + Left-to-right override. + + + + + Right-to-left override. + + + + + + + + + Rule about the Ruby data category. + + + + + + + + + + + + + + + + + + + Relative XPath expression pointing to a node that corresponds to a ruby element + + + + + Relative XPath expression pointing to a node that + corresponds to a rt element + + + + + Relative XPath expression pointing to a node that + corresponds to a rp element + + + + + Relative XPath expression pointing to a node that + corresponds to a rbc element + + + + + Relative XPath expression pointing to a node that + corresponds to a rtc element + + + + + Relative XPath expression pointing to a node that corresponds to a rbspan attribute. + + + + + + Ruby text. + + + + + + + + + + Allows an rt element to span multiple rb elements in complex ruby markup. + + + + + + Ruby markup. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Ruby base text. + + + + + + + + + + + + + + + + + + + + Ruby text. + + + + + + + + + + + + + + + + + + + Allows an rt element to span multiple rb elements in complex ruby markup. + + + + + + Container for rb elements in the case of complex ruby markup. + + + + + + + + + + + + + + + + + + + + Container for rt elements in the case of complex ruby markup. + + + + + + + + + + + + + + + + + + + + Used in the case of simple ruby markup to specify characters that can denote the beginning and end of ruby text when user agents do not have other ways to present ruby text distinctively from the base text. + + + + + + + + + + + Rule about the Language Information data category. + + + + + + + + + + Relative XPath expression pointing to a node that contains language information. + + + + + + Rule about the Elements Within Text data category. + + + + + + + + + + States whether current context is regarded as + "within text". + + + + + + The element and its content are part of the flow of its parent element. + + + + + The element splits the text flow of its parent element and its content is an independent text flow. + + + + + The element is part of the flow of its parent element, its content is an independent flow. + + + + + + + diff --git a/CI/testxml/xsd/tmw.xsd b/CI/testxml/xsd/tmw.xsd new file mode 100644 index 0000000..a8b5cf2 --- /dev/null +++ b/CI/testxml/xsd/tmw.xsd @@ -0,0 +1,2117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CI/testxml/xsd/xlink.xsd b/CI/testxml/xsd/xlink.xsd new file mode 100644 index 0000000..2c53ec3 --- /dev/null +++ b/CI/testxml/xsd/xlink.xsd @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Type of pointer to external rules files. + + + + + + Simple link. + + + + + + + + + + Pointer to external rules files. + + + + diff --git a/CI/testxml/xsd/xml.xsd b/CI/testxml/xsd/xml.xsd new file mode 100644 index 0000000..aea7d0d --- /dev/null +++ b/CI/testxml/xsd/xml.xsd @@ -0,0 +1,287 @@ + + + + + + +
+

About the XML namespace

+ +
+

+ This schema document describes the XML namespace, in a form + suitable for import by other schema documents. +

+

+ See + http://www.w3.org/XML/1998/namespace.html and + + http://www.w3.org/TR/REC-xml for information + about this namespace. +

+

+ Note that local names in this namespace are intended to be + defined only by the World Wide Web Consortium or its subgroups. + The names currently defined in this namespace are listed below. + They should not be used with conflicting semantics by any Working + Group, specification, or document instance. +

+

+ See further below in this document for more information about how to refer to this schema document from your own + XSD schema documents and about the + namespace-versioning policy governing this schema document. +

+
+
+
+
+ + + + +
+ +

lang (as an attribute name)

+

+ denotes an attribute whose value + is a language code for the natural language of the content of + any element; its value is inherited. This name is reserved + by virtue of its definition in the XML specification.

+ +
+
+

Notes

+

+ Attempting to install the relevant ISO 2- and 3-letter + codes as the enumerated possible values is probably never + going to be a realistic possibility. +

+

+ See BCP 47 at + http://www.rfc-editor.org/rfc/bcp/bcp47.txt + and the IANA language subtag registry at + + http://www.iana.org/assignments/language-subtag-registry + for further information. +

+

+ The union allows for the 'un-declaration' of xml:lang with + the empty string. +

+
+
+
+ + + + + + + + + +
+ + + + +
+ +

space (as an attribute name)

+

+ denotes an attribute whose + value is a keyword indicating what whitespace processing + discipline is intended for the content of the element; its + value is inherited. This name is reserved by virtue of its + definition in the XML specification.

+ +
+
+
+ + + + + + +
+ + + +
+ +

base (as an attribute name)

+

+ denotes an attribute whose value + provides a URI to be used as the base for interpreting any + relative URIs in the scope of the element on which it + appears; its value is inherited. This name is reserved + by virtue of its definition in the XML Base specification.

+ +

+ See http://www.w3.org/TR/xmlbase/ + for information about this attribute. +

+
+
+
+
+ + + + +
+ +

id (as an attribute name)

+

+ denotes an attribute whose value + should be interpreted as if declared to be of type ID. + This name is reserved by virtue of its definition in the + xml:id specification.

+ +

+ See http://www.w3.org/TR/xml-id/ + for information about this attribute. +

+
+
+
+
+ + + + + + + + + + +
+ +

Father (in any context at all)

+ +
+

+ denotes Jon Bosak, the chair of + the original XML Working Group. This name is reserved by + the following decision of the W3C XML Plenary and + XML Coordination groups: +

+
+

+ In appreciation for his vision, leadership and + dedication the W3C XML Plenary on this 10th day of + February, 2000, reserves for Jon Bosak in perpetuity + the XML name "xml:Father". +

+
+
+
+
+
+ + + +
+

About this schema document

+ +
+

+ This schema defines attributes and an attribute group suitable + for use by schemas wishing to allow xml:base, + xml:lang, xml:space or + xml:id attributes on elements they define. +

+

+ To enable this, such a schema must import this schema for + the XML namespace, e.g. as follows: +

+
+          <schema . . .>
+           . . .
+           <import namespace="http://www.w3.org/XML/1998/namespace"
+                      schemaLocation="http://www.w3.org/2001/xml.xsd"/>
+     
+

+ or +

+
+           <import namespace="http://www.w3.org/XML/1998/namespace"
+                      schemaLocation="http://www.w3.org/2009/01/xml.xsd"/>
+     
+

+ Subsequently, qualified reference to any of the attributes or the + group defined below will have the desired effect, e.g. +

+
+          <type . . .>
+           . . .
+           <attributeGroup ref="xml:specialAttrs"/>
+     
+

+ will define a type which will schema-validate an instance element + with any of those attributes. +

+
+
+
+
+ + + +
+

Versioning policy for this schema document

+
+

+ In keeping with the XML Schema WG's standard versioning + policy, this schema document will persist at + + http://www.w3.org/2009/01/xml.xsd. +

+

+ At the date of issue it can also be found at + + http://www.w3.org/2001/xml.xsd. +

+

+ The schema document at that URI may however change in the future, + in order to remain compatible with the latest version of XML + Schema itself, or with the XML namespace itself. In other words, + if the XML Schema or XML namespaces change, the version of this + document at + http://www.w3.org/2001/xml.xsd + + will change accordingly; the version at + + http://www.w3.org/2009/01/xml.xsd + + will not change. +

+

+ Previous dated (and unchanging) versions of this schema + document are at: +

+ +
+
+
+
+ +
+ diff --git a/CI/testxml/xsdcheck.sh b/CI/testxml/xsdcheck.sh new file mode 100755 index 0000000..2052a2c --- /dev/null +++ b/CI/testxml/xsdcheck.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash + +cd xsd +DIR="$(pwd)/../../../client-data" +rm ../errors.txt + +function check { + xmllint --format --schema tmw.xsd "${DIR}"/"${1}" 2>&1 >/dev/null | \ + grep -v ": Skipping import of schema located at " | \ + grep -v ".xml validates" | \ + grep -v ".manaplus validates" >>../errors.txt +} + +xmllint --format --schema XMLSchema.xsd tmw.xsd 2>&1 >/dev/null | \ + grep -v ": Skipping import of schema located at " | \ + grep -v ".xsd validates" >>../errors.txt + +check avatars.xml +check badges.xml +check charcreation.xml +check deadmessages.xml +check effects.xml +check elementals.xml +check emotes.xml +check equipmentslots.xml +check equipmentwindow.xml +check tmw2.manaplus +check features.xml +check groups.xml +check homunculuses.xml +check horses.xml +check itemcolors.xml +check itemfields.xml +check items.xml +check maps.xml +check mercenaries.xml +check mods.xml +check monsters.xml +check npcdialogs.xml +check npcs.xml +check paths.xml +check pets.xml +check quests.xml +check skills.xml +check skillunits.xml +check sounds.xml +check stats.xml +check status-effects.xml +check units.xml +check weapons.xml + +find -H "${DIR}/graphics" -type f -name "*.xml" -exec ./checkfile.sh {} \; >>../errors.txt diff --git a/update/pseudo_update.sh b/update/pseudo_update.sh new file mode 100755 index 0000000..9893b88 --- /dev/null +++ b/update/pseudo_update.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +# Copyright (C) 2010-2012 TMW2 Online +# Author: Andrei Karas (4144) + +dir=`pwd` + +previous=`cat commit.txt` +rm upload/Extra.zip + +cd ../../client-data +head=`git log --pretty=oneline -n 1 | awk '{print $1}'` +u1=`echo ${previous} | cut -c 1-7` +u2=`echo ${head} | cut -c 1-7` +git diff --name-status ${previous} HEAD | awk '/^(A|M)\t/ {print $2}; /^(R...)\t/ {print $3}' | \ +grep -e "[.]\(xml\|png\|tmx\|ogg\|txt\|po\|tsx\|md\)" | sort | uniq | \ +xargs zip -X -9 -r ../tools/update/upload/Extra.zip + +cd $dir/upload + +sum=`adler32 Extra.zip | awk '{print $2}'` +echo "Update ID: ${u1}..${u2}" +echo "Checksum: ${sum}" + +echo "Extra.zip ${sum}" >>resources2.txt +cp ../files/xml_header.txt resources.xml +echo " " >> resources.xml +#cat ../files/xml_footer.txt >>resources.xml +#cat ../files/xml_mods.txt >>resources.xml +echo '' >>resources.xml + diff --git a/wiki/wikigen.py b/wiki/wikigen.py new file mode 100755 index 0000000..7a7d2ec --- /dev/null +++ b/wiki/wikigen.py @@ -0,0 +1,902 @@ +#! /usr/bin/env python3 +# -*- coding: utf8 -*- +# +# Copyright (C) 2010-2011 Evol Online +# Copyright (C) 2018 TMW-2 +# Author: Andrei Karas (4144) +# Author: Jesusalva + +import datetime +import sys, traceback + +wikia=open("Items.md", "w") +wikib=open("Monsters.md", "w") +wikic=open("../../client-data/dyes.diff", "w") # Dye Report +wikid=open("../../server-data/changechase.diff", "w") # ChangeChase Report + +# the TYPEs we use to determine where to pack things +IT_HEALING=[] +IT_ETC=[] +IT_USABLE=[] +IT_AMMO=[] +IT_CARD=[] +IT_PETEGG=[] +IT_WEAPON={ 'HAND_2': [], # TWO HAND (LR) + 'HAND_1':[]} # WEAPONS (R) +IT_ARMOR={ 'MISC': [], # FOR FAILURE + 'EQP_ACC_L': [], # ACCESSORY LEFT + 'EQP_ACC_R': [], # ACCESSORT RIGHT + 'EQP_HEAD_MID': [], # CHEST + 'EQP_SHOES': [], # FEET + 'EQP_GARMENT': [], # GLOVES + 'EQP_HEAD_LOW':[], # PANTS + '1024': [], # NECKLACES (should be EQP_COSTUME_HEAD_TOP instead of number) + '2048': [], # RINGS (should be EQP_COSTUME_HEAD_MID instead of number) + 'EQP_MOUNT':[], # MOUNTS (ie. EQP_SHADOW_SHOES) + 'EQP_HEAD_TOP':[], # HATS/HELMETS + 'EQP_HAND_L': []} # SHIELDS + +Mobs1=[] +Mobs2=[] +Mobs3=[] +Mobs4=[] +Mobs5=[] +Mobs6=[] +MobsA=[] + +SysDrops=[] + + +def printSeparator(): + print("--------------------------------------------------------------------------------") + +def showHeader(): + print("TMW2 Wiki Generator") + ##print "Evol client data validator." + print("Run at: " + datetime.datetime.now().isoformat()) + print("Usage: ./wikigen.py [ ]") + ##print "https://gitlab.com/evol/evol-tools/blob/master/testxml/testxml.py" + printSeparator() + +def showFooter(): + #pass + #printSeparator() + print("Done.") + + + + + + +class Mob: + def __init__(self): + # Basic + self.id="0" + #self.aegis="UnknownMonster" # SpriteName is not used anywhere, we are using its ID + self.name="Unknown Monster Name" + self.view="1" + self.chch=False + self.boss=False + + # Defensive + self.mobpt="0" # Mob Points “Level” + self.hp="0" + self.xp="0" + self.jp="0" + self.st="" + + # Offensive + self.atk="[0, 0]" + self.range="0" + self.move="0" + self.delay="0" + self.drops=[] + +def MobAlloc(ab): + try: + maab=int(ab.mobpt) + except: + maab=9901 + + if maab <= 20: + Mobs1.append(ab) + elif maab <= 40: + Mobs2.append(ab) + elif maab <= 60: + Mobs3.append(ab) + elif maab <= 80: + Mobs4.append(ab) + elif maab <= 100: + Mobs5.append(ab) + elif maab <= 150: + Mobs6.append(ab) + elif maab != 9901: + MobsA.append(ab) + else: + print("WARNING, Disregarding \"%s\" (ID: %s) as invalid mob" % (ab.name, ab.id)) + +def testMobs(): + print("\nGenerating Mob Wiki...") + if len(sys.argv) >= 2: + src=open(sys.argv[1]+"/db/pre-re/mob_db.conf", "r") + else: + src=open("../../server-data/db/pre-re/mob_db.conf", "r") + + wikib.write("# Monster Database\n") + start=False + dropper=False + x=Mob() # Only for pyflakes2 + + for a in src: + if a == "{\n": + if start: + MobAlloc(x) + else: + start=True + x=Mob() + + if " Id:" in a: + x.id=stp(a) + elif " Name:" in a: + x.name=stp(a) + elif " Hp:" in a: + x.hp=stp(a) + elif " Lv:" in a: + x.mobpt=stp(a) + elif " Exp:" in a: + x.xp=stp(a) + elif " JExp:" in a: + x.jp=stp(a) + elif " Attack:" in a: + x.atk=stp(a) + elif " AttackRange:" in a: + x.range=stp(a) + elif " MoveSpeed:" in a: + x.move=stp(a) + elif " ViewRange:" in a: + x.view=stp(a) + elif " AttackDelay:" in a: + x.delay=stp(a) + elif " Boss: true" in a: + x.boss=True + elif " Looter: true" in a: + x.st+="Lot," + elif " Assist: true" in a: + x.st+="Ass," + elif " Aggressive: true" in a: + x.st+="Agr," + elif " ChangeChase: true" in a: + x.chch=True + elif 'Drops: ' in a: + dropper=True + elif dropper and '}' in a: + dropper=False + elif dropper: + x.drops.append(stp(a).split(": ")) + # Write last entry + MobAlloc(x) + + writeMob() + + wikib.write('\n\n|Mode|Desc|\n|----|----|\n') + wikib.write('|Lot|Looter|\n') + wikib.write('|Ass|Assist|\n') + wikib.write('|Agr|Aggressive|\n') + + src.close() + +def stp(x): + return x.replace('\n', '').replace('|', '').replace('(int, defaults to ', '').replace(')', '').replace('basic experience', '').replace('"','').replace(" ","").replace("\t","").replace('(string', '').replace('Name: ','').replace('AttackDelay: ', '').replace('MoveSpeed: ', '').replace('AttackRange: ', '').replace('ViewRange: ','').replace('Attack: ','').replace('ViewRange: ','').replace('Hp: ','').replace('Id: ','').replace('Lv: ','').replace('view range','').replace('attack range','').replace('move speed','').replace('health','').replace('(int','').replace('attack delay','atk.') + + +def MonsterWrite(tbl): + # TODO: Check _mobs files to determine the usual monster density (a misc info to aid adding proper drop specs) + wikib.write("\n") + wikib.write("\n") + for i in tbl: + if not i.chch: + wikid.write("%s:%s\n" % (i.id, i.name)) + if i.boss: + i.name=""+i.name+"" + wikib.write('\n" + ) + wikib.write("
IDNameHPAtkDelayModesMisc InfoRewardsDrops
' + + i.id +""+ + i.name +"HP: "+ + i.hp +"Atk: "+ + i.atk +""+ + i.delay +" ms"+ + i.st +""+ + mbdt('misc', mb_rdmisc(i)) +""+ + mbdt('Exp\'s', mb_rdrw(i)) +""+ + mbdt('drops', mb_rddrop(i)) +"
\n") + wikib.write("\n[(↑) Return to top](#monster-database)\n\n") + +def writeMob(): + wikib.write("\ ++ [Level 0-20](#starter)\n\ ++ [Level 21-40](#apprentice)\n\ ++ [Level 41-60](#intermediary)\n\ ++ [Level 61-80](#advanced)\n\ ++ [Level 81-100](#expert)\n\ ++ [Level 101-150](#master)\n\ ++ [Level 100+](#out-of-scope)\n\n\ + ") + + wikib.write("## Starter\n\n") + MonsterWrite(Mobs1) + + wikib.write("## Apprentice\n\n") + MonsterWrite(Mobs2) + + wikib.write("## Intermediary\n\n") + MonsterWrite(Mobs3) + + wikib.write("## Advanced\n\n") + MonsterWrite(Mobs4) + + wikib.write("## Expert\n\n") + MonsterWrite(Mobs5) + + wikib.write("## Master\n\n") + MonsterWrite(Mobs6) + + wikib.write("## Out Of Scope\n\n") + MonsterWrite(MobsA) + + +def mbdt(summary, content): + return "
\ +"+summary+"\ +
"+content+"
" + +def mb_rdmisc(mb): + buff="" + if "agr" in mb.st.lower(): + buff+="View Range: %s\n" % (mb.view) + buff+="Attack Range: %s\n" % (mb.range) + buff+="Move speed: %s ms\n" % (mb.move) + return buff + +def mb_rdrw(mb): + buff="" + buff+="MobPoints: %s\n" % (mb.mobpt) + buff+="%s\n" % (mb.xp) + buff+="%s\n" % (mb.jp) + return buff + +def mb_rddrop(mb): + buff="" + # sorted + try: + for ax in sorted(mb.drops, key=lambda xcv: float(xcv[1]), reverse=True): + # Ignore disabled drops + if ax[0].startswith("//"): + continue + + # Write drop + try: + buff+=ax[0]+': ' + str(int(ax[1])/100.0) + ' %\n' + except IndexError: + print("Fatal: invalid %s mob with %s drops" % (mb.name, str(ax))) + exit(1) + except: + print("[Warning] %s incorrect drop: %s" % (mb.name, str(ax))) + buff+=ax[0]+': ' + ax[1] + ' ppm\n' + + # Save to SysDrops + SysDrops.append([ax[0], ax[1], mb.name]) + except: + traceback.print_exc() + print("Offender: %s" % mb.name) + + return buff + + +class It: + def __init__(self): + # Basic + self.id="0" + self.aegis="UnknownItem" + self.name="Unknown Item Name" + self.price="0" # Sell price, of course + self.weight="0" + self.type="IT_ETC" # default type + self.loc="" + + # Offensive/Defensive + self.atk="0" + self.matk="0" + self.range="0" + self.defs="0" + + # Restrictions (EquipLv) + self.lvl="0" + self.drop=True + self.trade=True + self.sell=True + self.store=True + + # Special settings + self.rare=False # DropAnnounce + self.script=False + + # Visual + self.sl="0" # Slots + self.ac=False # Allow Cards + + # Script settings + self.minheal="0" + self.maxheal="0" + self.delheal="0" + self.typheal="0" + self.rarheal="0" + +def ItAlloc(it): + if (it.sl == "0" and it.ac) or (it.sl in ["1","2","3","4"] and not it.ac): + print("WARNING, item id "+it.id+" invalid dye/card setting!") + if (len(it.sl) > 1): + print("WARNING, item id "+it.id+" bad slots length: %d (%s)" % (len(it.sl), it.sl)) + if it.ac: + wikic.write(it.id + ": " + it.name + "\n") + + a=it.type + if "IT_HEALING" in a: + IT_HEALING.append(it) + elif "IT_ETC" in a: + IT_ETC.append(it) + elif "IT_USABLE" in a: + IT_USABLE.append(it) + elif "IT_AMMO" in a: + IT_AMMO.append(it) + elif "IT_CARD" in a: + IT_CARD.append(it) + elif "IT_PETEGG" in a: + IT_PETEGG.append(it) + + elif "IT_WEAPON" in a: + if "HAND_L" in it.loc or "EQP_ARMS" in it.loc: + IT_WEAPON["HAND_2"].append(it) + elif "HAND_R" in it.loc: + IT_WEAPON["HAND_1"].append(it) + else: + raise Exception("Invalid location for weapon: %s" % it.loc) + + elif "IT_ARMOR" in a: + if 'EQP_ACC_L' in it.loc: + IT_ARMOR['EQP_ACC_L'].append(it) + elif 'EQP_ACC_R' in it.loc: + IT_ARMOR['EQP_ACC_R'].append(it) + elif 'EQP_HEAD_MID' in it.loc: + IT_ARMOR['EQP_HEAD_MID'].append(it) + elif 'EQP_SHOES' in it.loc: + IT_ARMOR['EQP_SHOES'].append(it) + elif 'EQP_GARMENT' in it.loc: + IT_ARMOR['EQP_GARMENT'].append(it) + elif 'EQP_HEAD_LOW' in it.loc: + IT_ARMOR['EQP_HEAD_LOW'].append(it) + elif 'EQP_HEAD_TOP' in it.loc: + IT_ARMOR['EQP_HEAD_TOP'].append(it) + elif 'EQP_HAND_L' in it.loc: + IT_ARMOR['EQP_HAND_L'].append(it) + elif '1024' in it.loc: + IT_ARMOR['1024'].append(it) + elif '2048' in it.loc: + IT_ARMOR['2048'].append(it) + elif 'EQP_SHADOW_SHOES' in it.loc: + IT_ARMOR['EQP_MOUNT'].append(it) + elif 'EQP_ARMOR' in it.loc: + IT_ARMOR['EQP_ACC_R'].append(it) # Not really + else: + raise Exception("Invalid Loc for ID %s: %s" % (it.id, it.loc)) + +def newItemDB(): + print("\nGenerating Item Wiki...") + if len(sys.argv) >= 2: + src=open(sys.argv[1]+"/db/pre-re/item_db.conf", "r") + else: + src=open("../../server-data/db/pre-re/item_db.conf", "r") + + x=It() + for a in src: + if a == "{\n": + ItAlloc(x) + x=It() + + # sti() block + if " Id:" in a: + x.id=sti(a) + elif " AegisName:" in a: + x.aegis=sti(a) + elif " Name:" in a: + x.name=stin(a) + elif " Sell:" in a: + x.price=sti(a) + elif " Weight:" in a: + x.weight=sti(a) + elif " Type:" in a: + x.type=sti(a) + elif " Loc:" in a: + x.loc=sti(a) + elif " Atk:" in a: + x.atk=sti(a) + elif " Matk:" in a: + x.matk=sti(a) + elif " Range:" in a: + x.range=sti(a) + elif " Def:" in a: + x.defs=sti(a) + elif " EquipLv:" in a: + x.lvl=sti(a) + elif " Slots:" in a: + x.sl=sti(a) + elif " AllowCards:" in a: + x.ac=True + # Write booleans + elif "DropAnnounce: true" in a: + x.rare=True + elif "nodrop: true" in a: + x.drop=False + elif "notrade: true" in a: + x.trade=False + elif "noselltonpc: true" in a: + x.sell=False + elif "nostorage: true" in a: + x.store=False + elif "Script" in a: + x.script=True + # For healing items + elif "@min " in a: + x.minheal=sti(a) + elif "@max " in a: + x.maxheal=sti(a) + elif "@delay" in a: + x.delheal=sti(a) + elif "@type" in a: + x.typheal=sti(a) + try: + x.minheal=str(int(x.rarheal) * (int(x.typheal)*1 + 1)) + " %" + x.maxheal=str(int(x.rarheal) * (int(x.typheal)*2 + 1)) + " %" + if (x.delheal == "0"): + x.delheal=int(x.typheal)*2 + 1 + except: + x.delheal="ERROR" + pass + elif "@rarity" in a: + x.rarheal=sti(a) + x.minheal=str(int(x.rarheal) * (int(x.typheal)*1 + 1)) + " %" + x.maxheal=str(int(x.rarheal) * (int(x.typheal)*2 + 1)) + " %" + + # Write last entry + ItAlloc(x) + writeItems() + + src.close() + +def sti(x): + return x.replace('\n', '').replace('|', '').replace(')', '').replace('Id: ', '').replace('"','').replace(" ","").replace("\t","").replace('AegisName: ', '').replace('Name: ','').replace('Sell: ', '').replace('Weight: ', '').replace('Type: ', '').replace('Loc: ', '').replace('Atk: ', '').replace('Matk: ', '').replace('Range: ', '').replace('Def: ', '').replace('EquipLv: ', '').replace('Slots: ','').replace(" ", "").replace('@min=','').replace('@max=','').replace('@delay=','').replace('@type=','').replace('@rarity=','').replace(';','') + +def stin(x): + return x.replace('\n', '').replace('|', '').replace(')', '').replace('Id: ', '').replace('"','').replace(" ","").replace("\t","").replace('Name: ','').replace(';','') + + +def writeItems(): + wikia.write("# Items\n\ ++ [Healing Items](#healing-items)\n\ ++ [Usable Items](#usable-items)\n\ ++ [Generic Items](#generic-items)\n\ ++ [Ammo](#ammo)\n\ ++ [Cards](#cards)\n\ ++ [Pet Eggs](#pet-eggs)\n\ ++ [Mounts](#mounts)\n\ ++ [Weapons](#weapons)\n\ + + [1H Weapons](#1h-weapons)\n\ + + [2H Weapons](#2h-weapons)\n\ ++ [Armors](#armors)\n\ + + [Left Accessory](#left-accessory)\n\ + + [Right Accessory](#right-accessory)\n\ + + [Headgear](#headgear)\n\ + + [Chest](#chest)\n\ + + [Pants](#pants)\n\ + + [Shoes](#shoes)\n\ + + [Necklaces](#necklaces)\n\ + + [Rings](#rings)\n\ + + [Gloves](#gloves)\n\ + + [Shields](#shields)\n\ +\n\n") + wikia.write("#### Restrictions Reference\n") + wikia.write("Special Aegis Name Markers:\n\ ++ * - Rare item with drop announce.\n\ ++ (dp) - This item cannot be dropped.\n\ ++ (tr) - This item cannot de traded.\n\ ++ (sl) - This item cannot be sold.\n\ ++ (gg) - This item cannot go to storage.\n\n") + + # Healing Items + wikia.write("## Healing Items\n\n") + ItemWrite(IT_HEALING, ID=True, AEGIS=True, PRICE=True, WEIGHT=True, HEALING=True, DROPPER=True) + + # Usable Items + wikia.write("## Usable Items\n") + ItemWrite(IT_USABLE, ID=True, AEGIS=True, NAME=True, PRICE=True, WEIGHT=True, DROPPER=True) + + # Generic Items + wikia.write("## Generic Items\n") + ItemWrite(IT_ETC, ID=True, AEGIS=True, NAME=True, PRICE=True, WEIGHT=True, DROPPER=True) + + # Ammo Items + wikia.write("## Ammo\n") + ItemWrite(IT_AMMO, ID=True, AEGIS=True, NAME=True, WEIGHT=True, ATK=True) + + # Card Items + wikia.write("## Cards\n") + ItemWrite(IT_CARD, ID=True, AEGIS=True, NAME=True, PRICE=True, WEIGHT=True) + + # Pet Egg Items + wikia.write("## Pet Eggs\n") + ItemWrite(IT_PETEGG, ID=True, AEGIS=True, NAME=True, WEIGHT=True) + + # Mount Items + wikia.write("## Mounts\n") + ItemWrite(IT_ARMOR['EQP_MOUNT'], ID=True, AEGIS=True, NAME=True, WEIGHT=True) + + #################################################################### + wikia.write("# Weapons\n") + + # 1 Hand Items + wikia.write("## 1H Weapons\n") + ItemWrite(IT_WEAPON['HAND_1'], ID=True, AEGIS=True, PRICE=True, WEIGHT=True, ATK=True, LVL=True, DROPPER=True) + + # 2 Hand Items + wikia.write("## 2H Weapons\n") + ItemWrite(IT_WEAPON['HAND_2'], ID=True, AEGIS=True, PRICE=True, WEIGHT=True, ATK=True, LVL=True, RANGE=True) + + + #################################################################### + wikia.write("# Armors\n") + + ArmorWrite("Left Accessory",'EQP_ACC_L', False) + ArmorWrite("Right Accessory",'EQP_ACC_R', False) + ArmorWrite("Headgear",'EQP_HEAD_TOP') + ArmorWrite("Chest",'EQP_HEAD_MID') + ArmorWrite("Pants",'EQP_HEAD_LOW') + ArmorWrite("Shoes",'EQP_SHOES') + ArmorWrite("Necklaces",'1024', False) + ArmorWrite("Rings",'2048', False) + ArmorWrite("Gloves",'EQP_GARMENT') + ArmorWrite("Shields",'EQP_HAND_L') + +# Write AegisName with restrictions +def hl(it): + buff="" + if it.rare: + buff+="*" + buff+=it.aegis + buff+=" " + if not it.drop: + buff+="(dp)" + if not it.trade: + buff+="(tr)" + if not it.sell: + buff+="(sl)" + if not it.store: + buff+="(gg)" + return buff + +# wikia.write("Id|Aegis|Name|Weight|Atk|Matk|\n") +# wikia.write("Id|Aegis|Name|Price|Weight|\n") + +def ItemWrite(tbl, ID=False, AEGIS=False, NAME=False, PRICE=False, WEIGHT=False, DEF=False, LVL=False, ATK=False, RANGE=False, HEALING=False, SCRIPT=False, DROPPER=False): + wikia.write("\n") + wikia.write("") + if ID: + wikia.write("") + if AEGIS: + wikia.write("") + if NAME: + wikia.write("") + if PRICE: + wikia.write("") + if WEIGHT: + wikia.write("") + if DEF: + wikia.write("") + if LVL: + wikia.write("") + if ATK: + wikia.write("") + wikia.write("") + if RANGE: + wikia.write("") + if HEALING: + wikia.write("") + wikia.write("") + wikia.write("") + if SCRIPT: + wikia.write("") + if DROPPER: + wikia.write("") + + wikia.write("\n") + + for i in tbl: + """ + # To sort by weight, for example, uncomment this block. + try: + zt=int(i.weight) + except: + tbl.remove(i) + + for i in sorted(tbl, key=lambda xcv: int(xcv.weight), reverse=True): + """ + + wikia.write('') + + if ID: + wikia.write("" % (i.id,i.id)) + if AEGIS: + wikia.write("" % hl(i)) + if NAME: + wikia.write("" % i.name) + if PRICE: + wikia.write("" % i.price) + if WEIGHT: + wikia.write("" % i.weight) + if DEF: + wikia.write("" % i.defs) + if LVL: + wikia.write("" % i.lvl) + if ATK: + wikia.write("" % i.atk) + wikia.write("" % i.matk) + if RANGE: + wikia.write("" % i.range) + if HEALING: + wikia.write("" % i.minheal) + wikia.write("" % i.maxheal) + wikia.write("" % i.delheal) + if SCRIPT: + wikia.write("" % i.script) + # TODO: Check for item Aegis in npc/ folder too, to determine shops and quests. + if DROPPER: + tmp_droppers="" + tmp_drpalign=[] + for ax in SysDrops: + if ax[0] == i.aegis: + tmp_drpalign.append([ax[2], ax[1]]) + if len(tmp_drpalign) > 0: + for a in sorted(tmp_drpalign, key=lambda xcv: float(xcv[1]), reverse=True): + try: + ppm=int(a[1])/100.0 + tmp_droppers+=("%s: %.2f %% \n" % (a[0], ppm)) + except: + print("[Warning] %s whodrop error: %s" % (i.name, str(a))) + wikia.write("" % mbdt("monsters", tmp_droppers)) + else: + wikia.write("") + + wikia.write("") + + wikia.write("
IDAegisNamePriceWeightDefLvlAtkMatkRangeMinMaxDelayScriptMobs
%s%s%s%s GP%s gDef: %sLv: %sAtk: %s%s%s%s%s%s s%s%s-
\n") + wikia.write("\n[(↑) Return to top](#items)\n\n") + +def ArmorWrite(name,scope,defitem=True): + wikia.write("## "+name+"\n") + ItemWrite(IT_ARMOR[scope], ID=True, AEGIS=True, PRICE=True, WEIGHT=True, DEF=defitem, LVL=True, DROPPER=True) + + + + + + + + + + + +class Quest: + def __init__(self, ide): + # Basic + self.id=ide + self.name="Unknown Quest Name" + self.group="Unknown" + self.ent=[] + self.level=0 + +class QuestEntry: + def __init__(self): + # Basic + self.complete=False + self.entry=[] # collection of + self.giver="" + self.reward="" + self.loc="" + +def sortlv(val): + return val[1] + +def qnt(string): + return string.replace(' ','').replace('"','').replace("'","").replace('<','').replace('>','').replace('nowiki=1', '').replace('nowiki', '') + +def qnt2(string): + return string.replace('##B','**').replace('##b','**').replace('##0','*').replace('##1','*').replace('##2','*').replace('##3','*').replace('##','*') + +def DoQuest(): + print("\nGenerating Quest Wiki...") + if len(sys.argv) >= 3: + src=open(sys.argv[2]+"/quests.xml", "r") + else: + src=open("../../client-data/quests.xml", "r") + + qlog=[] + q=Quest(-1) + qe=QuestEntry() + ig=False + nw=False + + for e in src: + # Handle Comments and Ignored lines + if '' in e: + continue + elif '' in e: + ig=False + if '' in e: + q.ent.append(qe) + elif ' tag: %s (arg was %s) (line was %s)" % (e, rc[1], l)) + exit(1) + rc=[False, ""] + + # Fill stuff in Quest Entry + if '','').replace('','').replace('','').replace('','').replace('','').replace('','').strip() + elif '','').replace('','').replace("@@", "text").replace('') + qe.loc=b[1].replace('','').replace('','').strip() ) + if (not q.level): + try: + q.level=int(a.replace('','').replace('','').strip()) + except: + pass + + # Done reading file + src.close() + aktbl={} + aksort=[] + print("\033[32;1mTotal quests: %d\033[0m" % len(qlog)) + for i in qlog: + if i.name=="Unknown Quest Name": + print("Warning, invalid quest: %d" % (i.id)) + qlog.remove(i) + continue + # Total Table + #print(str(i.id)+": "+i.name) + try: + if (i.level): + aktbl[i.group].append(("[%s](q/%d) (Lv %d)" % (i.name, i.id, i.level), i.level)) + else: + aktbl[i.group].append(("[%s](q/%d)" % (i.name, i.id), i.level)) + except: + if (i.level): + aktbl[i.group]=[("[%s](q/%d) (Lv %d)" % (i.name, i.id, i.level), i.level)] + else: + aktbl[i.group]=[("[%s](q/%d)" % (i.name, i.id), i.level)] + aksort.append(i.group) + + for key in aktbl: + #print(aktbl[key]); + aktbl[key]=sorted(aktbl[key], key=sortlv) + + # Individual file + f=open("../../wiki/q/"+str(i.id)+'.md', "w") + f.write("\n\n" % (i.id, i.name)) + f.write("# %s\n" % i.name) + f.write('\n') + + totalcnt=0 + for a in i.ent: + totalcnt+=1 + f.write('\n### %d Stage\n\n' % totalcnt) + if a.complete: + f.write('*This completes quest*\n\n') + + if a.giver != "" or a.reward != "" or a.loc != "": + f.write('```\n') + if a.giver != "": + f.write('Quest Giver: %s\n' % a.giver) + if a.reward != "": + f.write('Reward: %s\n' % a.reward.replace('@@', '')) + if a.loc != "": + f.write('Location: %s\n' % a.loc) + f.write('```\n\n') + + for line in a.entry: + f.write('%s\n' % line) + + f.write('\n\n****\nThis file is generated automatically. Editing it will have no effect.\n') + f.close() + + # Write total table + f=open("Quests.txt", "w") + f.write("***Total quests: %d***\n" % len(qlog)) + for key in aksort: + f.write('\n## %s\n\n' % key) + # TODO: Sort by Quest Level + for a in aktbl[key]: + f.write('+ '+a[0]+'\n') + f.close() + +showHeader() + +testMobs() +newItemDB() +DoQuest() + +wikia.close() +wikib.close() +wikic.close() +wikid.close() +#print(str(SysDrops)) + +showFooter() +exit(0) -- cgit v1.2.3-70-g09d2