summaryrefslogblamecommitdiff
path: root/CI/testxml/testxml.py
blob: 5d28c843524317be11dcb2df806a7f436124207c (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
                      










                                      

                        




















































                                                                                 
                              



                                            
                                                                                              



                                              
                                                                                             


                               
                                         


                               
                                        



















































                                                  
                                                                                             

                 


                                                                              



                    


                                        





                                     
                                              












                                                                
                                                  

                                                           
                                                                                                        






                                          
                                                        












                                                                                            

                                                                        



                                                            

                                                                      






                                                              

                                                                        



                                                         

                                                                      
















                                                                      
                                          
























                                                                 
                  



















































































                                                                                    

                                                                      




























































































                                                                                                              
                                                                        







                                                                                
                             


























































































                                                                                                                      
                    






















































































































































































                                                                                                                       
                                         





































































































































































                                                                                                                              
                         





































                                                                                                                                      
                                  





                                                                    

                                                         
























































                                                                                 
                                     


                                                       

                                                   









                                                    
                                                                        


                                       
                                                                    










                                                  
                                                                 
                 
                                                                                        



                               
                                                                 














                                                    
                                                                           

















































                                                                      
                                                                          

                                   
                                                                                    







                                                             
                                                                          

                                   
                                                                                 







                                                                                      
                                                                                     







                                                                              
                                                                                                                 





                                                                              
                                                                           
                                                
                                                                                                                 


                                           
                                                                                          

                                       
                                                                                         












                                                                   

                                                                                              







































                                                                                     
                                                                                     











                                                    
                                                                          



















                                                                                          
                                                                               





                                   
                                     







                                                    
                                                                        


                                   
                                                                    




                                                
                                                                   



                                   
                                                                    






                                                    
                                                                      





























                                                                                            



                                                    
                                                           




                                                                                          
                                                                                    


                                                                        
                                                                                    
                                   





                                                       
                                 







                                                    
                                                                    


                                   
                                                                






                                            
                                                       



                               
                                                                














                                                          
                  










































































































































































































                                                                                                            

                                                  






























































































































                                                                                                  
                                                                                                                               

















































                                                                                                            

                                                                                                      








                                             
                     

















                                                                                       
                     
































                                                                                              
                                                        








                                                                           
                                                   










                            
                                                                                                   















































































































































                                                                                                
                        
                             
                            




                                                                                
                                    

                                                                   
                        















                                                        
                            







                                                                                
                        














                                                                                  
                            































































































                                                                                           
                          
                                                                   
                                                  










                                                     
                                                       

                                     
                                                                   



                           
                                   




















                                                                            
                              

                                                                   
                                             











































































                                                                                       
                                                         













                                                   
                                    











                                                       
                                                      


                               
                                                         










                                                            
                                                           




                                                              
                                                             




                                                              
                                                              

                                   
                                                                                      



                                   
                                                                                              
















































                                                                                                                
                                                              


                           
                                              















                                 
                                 
                                                                
                                 









                                                        
                            
                               
                           
                           
                             
                  
                               
                              
                         
                         
                           




                               
#!/usr/bin/env python3
# -*- 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 mutagen.oggvorbis
import io
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 = int(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 <end/> 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:
        mutagen.oggvorbis.OggVorbis(fullPath)
    except mutagen.oggvorbis.OggVorbisHeaderError() 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
    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

        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 in mapToAtlas:
        showMsgFile(file, "map have atlas", False)

    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 = io.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)