• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!
  • If you're using Gesior 2012 or MyAAC, please review this thread for information about a serious security vulnerability and a fix.

OpenTibia Monsters.xml and spawns file auto-fixer

Gesior.pl

Mega Noob&LOL 2012
Senator
Premium User
Joined
Sep 18, 2007
Messages
2,742
Solutions
86
Reaction score
2,730
Location
Poland
GitHub
gesior
Got old Windows datapack and monsters do not load on Linux? [on Linux achad.xml != Achad.xml]
Downloaded map with spawns.xml, but some monster are not in your datapack?
Find all bugged names and fixes for them.
Code suggests existing file path with same lowercase file path as in monsters.xml. If there is none, it searches in all folders for file with same lowercase name.

Online version: OTS.ME - Monsters & Spawn files checker (https://ots.me/monsters-fixer/)

It generates reports like this:
1643917725747.png

Sorry for ugly variables and 20 for/if. I made it few years ago to fix one datapack. I did not plan to release it, but there are still people that try to mix datapacks and get million errors on server startup.
PHP code:
PHP:
<?php

function colorChanges($orginalString, $changedString)
{
    $original = str_split($orginalString);
    $changed = str_split($changedString);
    $offset = 1;
    $ret = '';
    while($offset <= count($original) && $offset <= count($changed)) {
        $o = $original[count($original)-$offset];
        $c = $changed[count($changed)-$offset];
        if($o == $c)
            $ret = $o . $ret;
        else
            $ret = '<span class="diff">' . $c . '</span>' . $ret;

        $offset++;
    }
    if (count($original) < count($changed)) {
        while($offset <= count($changed)) {
            $c = $changed[count($changed)-$offset];
            $ret = '<span class="diff">' . $c . '</span>' . $ret;
            
            $offset++;
        }
    }

    return $ret;
}

$monstersErrorsList = array();
$spawnsErrorsList = array();

if(isset($_FILES['monsters_file']['tmp_name']))
{
    try {
        $monstersPaths = array();
        $monstersZIP = new ZipArchive();
        if($monstersZIP->open($_FILES['monsters_file']['tmp_name']))
        {
            // create list of .xml file paths in .zip
            for($i = 0; $i < $monstersZIP->numFiles; $i++) {
                $monsterFilePath = $monstersZIP->getNameIndex($i);
                if(substr($monsterFilePath, -4, 4) == '.xml'){
                    $monstersPaths[$monsterFilePath] = $monsterFilePath;    
                }
            }

            // remove 'monster/' from file paths
            $removeMonsterDir = true;
            foreach($monstersPaths as $monsterPath)
            {
                if(substr($monsterPath, 0, 8) != 'monster/') {
                    $removeMonsterDir = false;
                    break;
                }
            }
            if($removeMonsterDir)
            {
                $monstersFilePath = 'monster/monsters.xml';
                foreach($monstersPaths as $monsterPath)
                {
                    unset($monstersPaths[$monsterPath]);
                    $newMonsterPath = substr($monsterPath, 8, strlen($monsterPath) -8);
                    $monstersPaths[$newMonsterPath] = $newMonsterPath;
                }
            } else {
                $monstersFilePath = 'monsters.xml';
            }

            ksort($monstersPaths);

            $monstersPathsLowercase = [];
            $monstersFilesLowercase = [];
            foreach($monstersPaths as $value) {
                $lowercasePath = strtolower($value);
                $lowercaseFileName = array_reverse(explode('/', str_replace('\\', '/', strtolower($value))))[0];

                $monstersPathsLowercase[$lowercasePath] = [
                    'path' => $value,
                    'lowercasePath' => $lowercasePath,
                    'lowercaseFileName' => $lowercaseFileName,
                ];

                $monstersFilesLowercase[$lowercaseFileName] = [
                    'path' => $value,
                    'lowercasePath' => $lowercasePath,
                    'lowercaseFileName' => $lowercaseFileName,
                ];
            }
        }
        else {
            throw new RuntimeException('Failed to open monsters .zip file');
        }

        $monstersXMLfileContent = $monstersZIP->getFromName($monstersFilePath);
        $monstersXML = new DOMDocument();
        $monstersXML->loadXML($monstersXMLfileContent);
        $monstersList = array();
        $monstersElements = $monstersXML->getElementsByTagName('monster');
        for($m = 0; $m < $monstersElements->length; $m++)
        {
            if($monstersElements->item($m)->hasAttributes() && $monstersElements->item($m)->attributes->getNamedItem('name') && $monstersElements->item($m)->attributes->getNamedItem('file'))
                $monstersList[$monstersElements->item($m)->attributes->getNamedItem('name')->nodeValue] = $monstersElements->item($m)->attributes->getNamedItem('file')->nodeValue;
        }
        $i = 0;
        $monsterPathId = array_values($monstersPaths);
        foreach($monstersList as $name => $file)
        {
            if(!isset($monstersPaths[$file]))
            {
                $fileName = array_reverse(explode('/', str_replace('\\', '/', strtolower($file))))[0];
                if(isset($monstersPathsLowercase[strtolower($file)])) { // same lowercase path
                    $result = [
                        'name' => $name,
                        'now' => colorChanges($monstersPathsLowercase[strtolower($file)]['path'], $file),
                        'proposition' => colorChanges($file, $monstersPathsLowercase[strtolower($file)]['path'])
                    ];
                } elseif(isset($monstersFilesLowercase[$fileName])) { // same lowercase file name
                    $result = [
                        'name' => $name,
                        'now' => colorChanges($monstersFilesLowercase[$fileName]['path'], $file),
                        'proposition' => colorChanges($file, $monstersFilesLowercase[$fileName]['path'])
                    ];
                } else {
                    $result = [
                        'name' => $name,
                        'now' => $file,
                        'proposition' => 'NONE :('
                    ];
                }

                $monstersErrorsList[] = $result;
            }
            $i++;
        }

        if(!empty($_FILES['spawns_file']['tmp_name']))
        {
            $spawnsXML = new DOMDocument();
            $spawnsXML->load($_FILES['spawns_file']['tmp_name']);
            $spawnMonstersList = array();
            $spawnsMonsters = $spawnsXML->getElementsByTagName('monster');
            for($s = 0; $s < $spawnsMonsters->length; $s++) {
                if($spawnsMonsters->item($s)->hasAttributes() && $spawnsMonsters->item($s)->attributes->getNamedItem('name')) {
                    $monsterName = $spawnsMonsters->item($s)->attributes->getNamedItem('name')->nodeValue;
                    $spawnMonstersList[] = $monsterName;
                }
            }

            foreach($spawnMonstersList as $spawnMonsterName)
                if(!isset($monstersList[$spawnMonsterName])) {
                    $result = [
                        'name' => $name,
                        'now' => $name,
                        'proposition' => 'NONE :('
                    ];
                    // try to find monster in monster.xml with same lowercase name
                    foreach($monstersList as $name => $file) {
                        if (strtolower($name) == strtolower($spawnMonsterName)) {
                            $result = [
                                'name' => $name,
                                'now' => colorChanges($spawnMonsterName, $name),
                                'proposition' => colorChanges($name, $spawnMonsterName)
                            ];
                            break;
                        }
                            
                    }

                    $spawnsErrorsList[] = $result;
                }
        }
    } catch(Exception $e) {
        echo '<div style="font-size: 20px;color: red">Errors while loading files: ' . $e->getMessage() . '</div>';
    } catch(ValueError $e) {
        echo '<div style="font-size: 20px;color: red">Errors while loading ZIP archive. Probably you uploaded wrong file.</div>';
    }
}

echo '<html><head><title>OTS.ME - Monsters & Spawn files checker</title></head><body>';
echo '<style>
table {
  text-align: left;
  border-collapse: collapse;
}
table td, table th {
  border: 1px solid #AAAAAA;
  padding: 5px 4px;
  font-size: 16px;
}
.diff {
    font-size: 18px;
    color: blue;
}
</style>';
echo '<h3>ZIP your <i>data/monster</i> directory and upload to check <i>monster.xml</i> and file names compatibility</h3>';
echo '<h3>You can also upload your <i>data/world/spawns.xml</i> to check, if all monsters from map are in <i>monster.xml</i></h3>';
echo '<form action="index.php" method="post" enctype="multipart/form-data"> 
Monsters ZIP file: <input type="file" name="monsters_file" /> [monster.zip]<br />
Spawns file: <input type="file" name="spawns_file" /> [..-spawn.xml]<br />
<input type="submit" value="Send & verify" /> 
</form>';

echo '<div style="font-size: 20px;color: red">Wrong paths to monsters in monsters.xml:</div>';
echo '<table style="font-size:16px"><tr><th>Name</th><th>Now</th><th>Proposition</th></tr>';
foreach($monstersErrorsList as $errorData) {
    echo '<tr><td>' . $errorData['name'] . '</td><td>' . $errorData['now'] . '</td><td>' . $errorData['proposition'] . '</td></tr>';
}
echo '</table>';

echo '<div style="font-size: 20px;color: red">Monsters from spawns file that can\'t be loaded:</div>';
echo '<table><tr><th>Name</th><th>Now</th><th>Proposition</th></tr>';
foreach($spawnsErrorsList as $errorData) {
    echo '<tr><td>' . $errorData['name'] . '</td><td>' . $errorData['now'] . '</td><td>' . $errorData['proposition'] . '</td></tr>';
echo '</table>';
}[/php]
 
Top