From c2ccb14ffff0e45398365e19dfd2874307ddb943 Mon Sep 17 00:00:00 2001
From: Jesusaves
Date: Sun, 23 Oct 2022 23:45:19 -0300
Subject: Initial commit
---
.gitignore | 89 +
Rules/castle_indoor/rule_castle_001.tmx | 176 ++
Rules/castle_indoor/rule_castle_002.tmx | 77 +
Rules/castle_indoor/rule_castle_003.tmx | 68 +
Rules/castle_indoor/rule_castle_009.tmx | 106 +
Rules/castle_indoor/rule_castle_010.tmx | 113 ++
Rules/castle_indoor/rule_castle_indoor.txt | 23 +
Rules/cave/rule_cave.txt | 41 +
Rules/cave/rule_cave_001.tmx | 169 ++
Rules/cave/rule_cave_002.tmx | 75 +
Rules/cave/rule_cave_003.tmx | 47 +
Rules/cave/rule_cave_004.tmx | 70 +
Rules/cave/rule_cave_005.tmx | 70 +
Rules/cave/rule_cave_006.tmx | 135 ++
Rules/cave/rule_cave_007.tmx | 147 ++
Rules/cave/rule_cave_008.tmx | 144 ++
Rules/cave/rule_cave_009.tmx | 75 +
Rules/cave/rule_cave_010.tmx | 92 +
Rules/cave/rule_cave_050.tmx | 129 ++
Rules/cave/rule_cave_100.tmx | 53 +
Rules/cave/rule_cave_110.tmx | 47 +
Rules/caverules.txt | 6 +
Rules/desertcliffs/rule_cave.txt | 32 +
Rules/desertcliffs/rule_cave_001.tmx | 166 ++
Rules/desertcliffs/rule_cave_002.tmx | 75 +
Rules/desertcliffs/rule_cave_003.tmx | 47 +
Rules/desertcliffs/rule_cave_004.tmx | 93 +
Rules/desertcliffs/rule_cave_005.tmx | 70 +
Rules/desertcliffs/rule_cave_006.tmx | 135 ++
Rules/desertcliffs/rule_cave_007.tmx | 147 ++
Rules/desertcliffs/rule_cave_008.tmx | 144 ++
Rules/desertcliffs/rule_cave_03.tmx | 85 +
Rules/desertcliffs/rule_cave_050.tmx | 39 +
Rules/desertcliffs/rule_cave_100.tmx | 33 +
Rules/icecave/rule_icecave.txt | 34 +
Rules/icecave/rule_icecave_001.tmx | 54 +
Rules/icecave/rule_icecave_002.tmx | 45 +
Rules/icecave/rule_icecave_003.tmx | 35 +
Rules/icecave/rule_icecave_004.tmx | 40 +
Rules/icecave/rule_icecave_005.tmx | 40 +
Rules/icecave/rule_icecave_006.tmx | 40 +
Rules/icecave/rule_icecave_007.tmx | 40 +
Rules/icecave/rule_icecave_009.tmx | 55 +
Rules/icecave/rule_icecave_010.tmx | 55 +
Rules/icecave/rule_icecave_020.tmx | 40 +
Rules/icecave/rule_icecave_100.tmx | 40 +
Rules/rule_cave.tmx | 375 ++++
Rules/rules.txt | 17 +
Rules/tilesets/set_castle_indoor.png | Bin 0 -> 36121 bytes
Rules/tilesets/set_cave.png | Bin 0 -> 27678 bytes
Rules/tilesets/set_desertcliff.png | Bin 0 -> 28082 bytes
Rules/tilesets/set_hlw.png | Bin 0 -> 13104 bytes
Rules/tilesets/set_icecave.png | Bin 0 -> 4742 bytes
Rules/tilesets/set_icemountain.png | Bin 0 -> 1632 bytes
Rules/tilesets/set_rules.png | Bin 0 -> 563 bytes
Rules/tilesets/set_woodland.png | Bin 0 -> 4636 bytes
adler32/Makefile | 16 +
adler32/adler32 | Bin 0 -> 9216 bytes
adler32/adler32.c | 68 +
client/aurora.py | 37 +
client/dailylogin.py | 86 +
client/magicacademy.py | 60 +
client/minimap-dyecmd.py | 17 +
client/minimap-dyecmd.sh | 0
client/minimap-override/none.png | Bin 0 -> 280 bytes
client/minimap-render.py | 171 ++
client/skills.py | 137 ++
client/tmxrasterizer | Bin 0 -> 1525440 bytes
client/weapons.py | 89 +
hercules/.gitignore | 12 +
hercules/code/__init__.py | 0
hercules/code/clienttoserver/__init__.py | 0
hercules/code/clienttoserver/maps.py | 190 ++
hercules/code/configutils.py | 51 +
hercules/code/fileutils.py | 86 +
hercules/code/server/__init__.py | 0
hercules/code/server/account.py | 63 +
hercules/code/server/accreg.py | 52 +
hercules/code/server/db/__init__.py | 0
hercules/code/server/db/char.py | 187 ++
hercules/code/server/db/charregnumdb.py | 34 +
hercules/code/server/db/inventory.py | 44 +
hercules/code/server/db/skill.py | 31 +
hercules/code/server/dbitem.py | 7 +
hercules/code/server/dbskill.py | 7 +
hercules/code/server/dbuser.py | 7 +
hercules/code/server/evol/__init__.py | 0
hercules/code/server/evol/athena.py | 207 ++
hercules/code/server/evol/consts.py | 49 +
hercules/code/server/evol/itemdb.py | 104 +
hercules/code/server/evol/main.py | 43 +
hercules/code/server/evol/mobdb.py | 89 +
hercules/code/server/evol/mobskilldb.py | 50 +
hercules/code/server/evol/npcs.py | 281 +++
hercules/code/server/maps.py | 43 +
hercules/code/server/party.py | 80 +
hercules/code/server/questsdb.py | 33 +
hercules/code/server/storage.py | 81 +
hercules/code/server/tmw/__init__.py | 0
hercules/code/server/tmw/athena.py | 207 ++
hercules/code/server/tmw/consts.py | 129 ++
hercules/code/server/tmw/itemdb.py | 264 +++
hercules/code/server/tmw/main.py | 49 +
hercules/code/server/tmw/mobdb.py | 170 ++
hercules/code/server/tmw/mobskilldb.py | 55 +
hercules/code/server/tmw/npcs.py | 878 ++++++++
hercules/code/server/utils.py | 12 +
hercules/code/servertoclient/__init__.py | 0
hercules/code/servertoclient/homunculuses.py | 31 +
hercules/code/servertoclient/items.py | 138 ++
hercules/code/servertoclient/luas.py | 51 +
hercules/code/servertoclient/maps.py | 48 +
hercules/code/servertoclient/mercenaries.py | 31 +
hercules/code/servertoclient/monsters.py | 43 +
hercules/code/servertoclient/npcs.py | 29 +
hercules/code/servertoclient/pets.py | 31 +
hercules/code/servertoclient/quests.py | 39 +
hercules/code/servertoclient/skills.py | 33 +
hercules/code/servertoclient/sprites.py | 467 +++++
hercules/code/serverutils.py | 12 +
hercules/code/stringutils.py | 62 +
hercules/code/tileutils.py | 48 +
hercules/convert_mapcache_to_tmx.py | 24 +
hercules/convert_tmx_to_mapcache.py | 9 +
hercules/extract_mapcache.py | 24 +
hercules/import_newdb.sh | 24 +
hercules/list_mapcache.py | 24 +
hercules/templates/acc_reg_num_db.sql | 11 +
hercules/templates/char.sql | 72 +
hercules/templates/char_reg_num_db.sql | 11 +
hercules/templates/collision.png | Bin 0 -> 275 bytes
hercules/templates/constants.tpl | 45 +
hercules/templates/group.tpl | 11 +
hercules/templates/homunculus.tpl | 3 +
hercules/templates/homunculuses.xml | 6 +
hercules/templates/inventory.sql | 23 +
hercules/templates/item.tpl | 7 +
hercules/templates/item_db.tpl | 63 +
hercules/templates/items.xml | 148 ++
hercules/templates/login.sql | 25 +
hercules/templates/mercenaries.xml | 6 +
hercules/templates/mercenary.tpl | 3 +
hercules/templates/mob_db.tpl | 101 +
hercules/templates/mob_skill_db.tpl | 56 +
hercules/templates/monster.tpl | 3 +
hercules/templates/monsters.xml | 11 +
hercules/templates/npc.tpl | 3 +
hercules/templates/npcs.xml | 11 +
hercules/templates/party.sql | 12 +
hercules/templates/pet.tpl | 3 +
hercules/templates/pets.xml | 6 +
hercules/templates/quest.tpl | 5 +
hercules/templates/quest_db.tpl | 5 +
hercules/templates/quests.xml | 6 +
hercules/templates/skill.sql | 10 +
hercules/templates/skill.tpl | 1 +
hercules/templates/skills.xml | 15 +
hercules/templates/sprite.xml | 21 +
hercules/templates/storage.sql | 22 +
hercules/templates/template.tmx | 25 +
hercules/templates/tileset.png | Bin 0 -> 3550 bytes
hercules/tmx_converter.py | 659 ++++++
herculeswrapper/char.sh | 7 +
herculeswrapper/herc-map-wrapper-config.example | 77 +
herculeswrapper/include.sh | 56 +
herculeswrapper/login.sh | 7 +
herculeswrapper/map.sh | 52 +
imagescheck/icccheck.sh | 5 +
imagescheck/icccheckfile.sh | 9 +
licensecheck/checkfile.sh | 12 +
licensecheck/clientdata.sh | 9 +
licensecheck/serverdata.py | 71 +
localserver/.gitignore | 1 +
localserver/README | 14 +
localserver/applicator.py | 45 +
localserver/build.sh | 8 +
localserver/buildasan.sh | 6 +
localserver/checktime.sh | 9 +
localserver/clean.sh | 6 +
localserver/conf/battle.conf | 32 +
localserver/conf/char-server.conf | 31 +
localserver/conf/constants.conf | 30 +
localserver/conf/inter-server.conf | 32 +
localserver/conf/login-server.conf | 32 +
localserver/conf/logs.conf | 32 +
localserver/conf/map-server.conf | 32 +
localserver/conf/msg_conf.txt | 0
localserver/conf/ports.conf | 8 +
localserver/conf/script.conf | 32 +
localserver/conf/socket.conf | 32 +
localserver/conf/sql_connection.conf | 52 +
localserver/givegm.sh | 8 +
localserver/initdb.sh | 26 +
localserver/installconfigs.sh | 20 +
localserver/npc/006-10_blackbox.txt | 12 +
localserver/npc/015-8_blackbox.txt | 27 +
localserver/npc/017-1_stranger_blackbox.txt | 40 +
localserver/npc/018-7-1_demure_blackbox.txt | 12 +
localserver/npc/023-4_blackbox.txt | 36 +
localserver/npc/025-2-4_aegis_blackbox.txt | 33 +
localserver/npc/botcheck_blackbox.txt | 35 +
localserver/npc/easteregg_blackbox.txt | 16 +
localserver/npc/magic_blackbox.txt | 13 +
localserver/reapply.patch3 | 5 +
localserver/reapply.patch4 | 47 +
localserver/rebuild.sh | 9 +
localserver/updateconfigs.sh | 30 +
localserver/updatedb.sh | 103 +
localserver/updater.sh | 44 +
manaplus/connect_local_server.sh | 13 +
manaplus/evol.manaplus | 18 +
manaplus/force_localmanaplus.sh | 5 +
manaplus/loginwallpaper_800x600.png | Bin 0 -> 988427 bytes
manaplus/tmw2.manaplus | 18 +
testxml/testxml.py | 2439 +++++++++++++++++++++++
testxml/xsd/XMLSchema.xsd | 2262 +++++++++++++++++++++
testxml/xsd/checkfile.sh | 5 +
testxml/xsd/its.xsd | 926 +++++++++
testxml/xsd/tmw.xsd | 2117 ++++++++++++++++++++
testxml/xsd/xlink.xsd | 79 +
testxml/xsd/xml.xsd | 287 +++
testxml/xsdcheck.sh | 52 +
update/TMW2org.zip | Bin 0 -> 42988466 bytes
update/addmods.sh | 35 +
update/adler32.c | 79 +
update/commit.txt | 1 +
update/commit_old.txt | 1 +
update/create_music.sh | 31 +
update/createnew.sh | 40 +
update/deploy.sh | 7 +
update/musiccommit.txt | 1 +
update/musiccommit_old.txt | 1 +
update/news.txt | 40 +
update/pseudo_update.sh | 31 +
update/update.sh | 42 +
update/update_music.sh | 42 +
web/all_news.bin | 1 +
web/backupcopy | 129 ++
web/deploy.sh | 4 +
web/feed.xml | 1 +
web/fetch.py | 62 +
web/gameinfo.sh | 22 +
web/index.bin | 1 +
web/langs.txt | 5 +
web/legacy | 51 +
web/news.html | 1 +
web/news_to_html.py | 91 +
web/newsfeed.py | 101 +
web/nf_main.xml | 1 +
web/oldupdatelang.py | 164 ++
web/original.po | 170 ++
web/template.po | 84 +
web/updatelang.py | 190 ++
wiki/elegen.py | 426 ++++
wiki/lanalysis.py | 394 ++++
wiki/redesign.py | 1050 ++++++++++
wiki/sedesign.py | 515 +++++
wiki/tmp-arm | 247 +++
wiki/tmp-dec | 8 +
wiki/tmp-etc | 25 +
wiki/tmp-wpn | 59 +
wiki/webwikigen.py | 758 +++++++
wiki/wikigen.py | 898 +++++++++
263 files changed, 26066 insertions(+)
create mode 100644 .gitignore
create mode 100644 Rules/castle_indoor/rule_castle_001.tmx
create mode 100644 Rules/castle_indoor/rule_castle_002.tmx
create mode 100644 Rules/castle_indoor/rule_castle_003.tmx
create mode 100644 Rules/castle_indoor/rule_castle_009.tmx
create mode 100644 Rules/castle_indoor/rule_castle_010.tmx
create mode 100644 Rules/castle_indoor/rule_castle_indoor.txt
create mode 100644 Rules/cave/rule_cave.txt
create mode 100644 Rules/cave/rule_cave_001.tmx
create mode 100644 Rules/cave/rule_cave_002.tmx
create mode 100644 Rules/cave/rule_cave_003.tmx
create mode 100644 Rules/cave/rule_cave_004.tmx
create mode 100644 Rules/cave/rule_cave_005.tmx
create mode 100644 Rules/cave/rule_cave_006.tmx
create mode 100644 Rules/cave/rule_cave_007.tmx
create mode 100644 Rules/cave/rule_cave_008.tmx
create mode 100644 Rules/cave/rule_cave_009.tmx
create mode 100644 Rules/cave/rule_cave_010.tmx
create mode 100644 Rules/cave/rule_cave_050.tmx
create mode 100644 Rules/cave/rule_cave_100.tmx
create mode 100644 Rules/cave/rule_cave_110.tmx
create mode 100644 Rules/caverules.txt
create mode 100644 Rules/desertcliffs/rule_cave.txt
create mode 100644 Rules/desertcliffs/rule_cave_001.tmx
create mode 100644 Rules/desertcliffs/rule_cave_002.tmx
create mode 100644 Rules/desertcliffs/rule_cave_003.tmx
create mode 100644 Rules/desertcliffs/rule_cave_004.tmx
create mode 100644 Rules/desertcliffs/rule_cave_005.tmx
create mode 100644 Rules/desertcliffs/rule_cave_006.tmx
create mode 100644 Rules/desertcliffs/rule_cave_007.tmx
create mode 100644 Rules/desertcliffs/rule_cave_008.tmx
create mode 100644 Rules/desertcliffs/rule_cave_03.tmx
create mode 100644 Rules/desertcliffs/rule_cave_050.tmx
create mode 100644 Rules/desertcliffs/rule_cave_100.tmx
create mode 100644 Rules/icecave/rule_icecave.txt
create mode 100644 Rules/icecave/rule_icecave_001.tmx
create mode 100644 Rules/icecave/rule_icecave_002.tmx
create mode 100644 Rules/icecave/rule_icecave_003.tmx
create mode 100644 Rules/icecave/rule_icecave_004.tmx
create mode 100644 Rules/icecave/rule_icecave_005.tmx
create mode 100644 Rules/icecave/rule_icecave_006.tmx
create mode 100644 Rules/icecave/rule_icecave_007.tmx
create mode 100644 Rules/icecave/rule_icecave_009.tmx
create mode 100644 Rules/icecave/rule_icecave_010.tmx
create mode 100644 Rules/icecave/rule_icecave_020.tmx
create mode 100644 Rules/icecave/rule_icecave_100.tmx
create mode 100644 Rules/rule_cave.tmx
create mode 100644 Rules/rules.txt
create mode 100644 Rules/tilesets/set_castle_indoor.png
create mode 100644 Rules/tilesets/set_cave.png
create mode 100644 Rules/tilesets/set_desertcliff.png
create mode 100644 Rules/tilesets/set_hlw.png
create mode 100644 Rules/tilesets/set_icecave.png
create mode 100644 Rules/tilesets/set_icemountain.png
create mode 100644 Rules/tilesets/set_rules.png
create mode 100644 Rules/tilesets/set_woodland.png
create mode 100644 adler32/Makefile
create mode 100755 adler32/adler32
create mode 100644 adler32/adler32.c
create mode 100755 client/aurora.py
create mode 100755 client/dailylogin.py
create mode 100755 client/magicacademy.py
create mode 100755 client/minimap-dyecmd.py
create mode 100755 client/minimap-dyecmd.sh
create mode 100644 client/minimap-override/none.png
create mode 100755 client/minimap-render.py
create mode 100755 client/skills.py
create mode 100755 client/tmxrasterizer
create mode 100755 client/weapons.py
create mode 100644 hercules/.gitignore
create mode 100644 hercules/code/__init__.py
create mode 100644 hercules/code/clienttoserver/__init__.py
create mode 100644 hercules/code/clienttoserver/maps.py
create mode 100644 hercules/code/configutils.py
create mode 100644 hercules/code/fileutils.py
create mode 100644 hercules/code/server/__init__.py
create mode 100644 hercules/code/server/account.py
create mode 100644 hercules/code/server/accreg.py
create mode 100644 hercules/code/server/db/__init__.py
create mode 100644 hercules/code/server/db/char.py
create mode 100644 hercules/code/server/db/charregnumdb.py
create mode 100644 hercules/code/server/db/inventory.py
create mode 100644 hercules/code/server/db/skill.py
create mode 100644 hercules/code/server/dbitem.py
create mode 100644 hercules/code/server/dbskill.py
create mode 100644 hercules/code/server/dbuser.py
create mode 100644 hercules/code/server/evol/__init__.py
create mode 100644 hercules/code/server/evol/athena.py
create mode 100644 hercules/code/server/evol/consts.py
create mode 100644 hercules/code/server/evol/itemdb.py
create mode 100644 hercules/code/server/evol/main.py
create mode 100644 hercules/code/server/evol/mobdb.py
create mode 100644 hercules/code/server/evol/mobskilldb.py
create mode 100644 hercules/code/server/evol/npcs.py
create mode 100644 hercules/code/server/maps.py
create mode 100644 hercules/code/server/party.py
create mode 100644 hercules/code/server/questsdb.py
create mode 100644 hercules/code/server/storage.py
create mode 100644 hercules/code/server/tmw/__init__.py
create mode 100644 hercules/code/server/tmw/athena.py
create mode 100644 hercules/code/server/tmw/consts.py
create mode 100644 hercules/code/server/tmw/itemdb.py
create mode 100644 hercules/code/server/tmw/main.py
create mode 100644 hercules/code/server/tmw/mobdb.py
create mode 100644 hercules/code/server/tmw/mobskilldb.py
create mode 100644 hercules/code/server/tmw/npcs.py
create mode 100644 hercules/code/server/utils.py
create mode 100644 hercules/code/servertoclient/__init__.py
create mode 100644 hercules/code/servertoclient/homunculuses.py
create mode 100644 hercules/code/servertoclient/items.py
create mode 100644 hercules/code/servertoclient/luas.py
create mode 100644 hercules/code/servertoclient/maps.py
create mode 100644 hercules/code/servertoclient/mercenaries.py
create mode 100644 hercules/code/servertoclient/monsters.py
create mode 100644 hercules/code/servertoclient/npcs.py
create mode 100644 hercules/code/servertoclient/pets.py
create mode 100644 hercules/code/servertoclient/quests.py
create mode 100644 hercules/code/servertoclient/skills.py
create mode 100644 hercules/code/servertoclient/sprites.py
create mode 100644 hercules/code/serverutils.py
create mode 100644 hercules/code/stringutils.py
create mode 100644 hercules/code/tileutils.py
create mode 100755 hercules/convert_mapcache_to_tmx.py
create mode 100755 hercules/convert_tmx_to_mapcache.py
create mode 100755 hercules/extract_mapcache.py
create mode 100755 hercules/import_newdb.sh
create mode 100755 hercules/list_mapcache.py
create mode 100644 hercules/templates/acc_reg_num_db.sql
create mode 100644 hercules/templates/char.sql
create mode 100644 hercules/templates/char_reg_num_db.sql
create mode 100644 hercules/templates/collision.png
create mode 100644 hercules/templates/constants.tpl
create mode 100644 hercules/templates/group.tpl
create mode 100644 hercules/templates/homunculus.tpl
create mode 100644 hercules/templates/homunculuses.xml
create mode 100644 hercules/templates/inventory.sql
create mode 100644 hercules/templates/item.tpl
create mode 100644 hercules/templates/item_db.tpl
create mode 100644 hercules/templates/items.xml
create mode 100644 hercules/templates/login.sql
create mode 100644 hercules/templates/mercenaries.xml
create mode 100644 hercules/templates/mercenary.tpl
create mode 100644 hercules/templates/mob_db.tpl
create mode 100644 hercules/templates/mob_skill_db.tpl
create mode 100644 hercules/templates/monster.tpl
create mode 100644 hercules/templates/monsters.xml
create mode 100644 hercules/templates/npc.tpl
create mode 100644 hercules/templates/npcs.xml
create mode 100644 hercules/templates/party.sql
create mode 100644 hercules/templates/pet.tpl
create mode 100644 hercules/templates/pets.xml
create mode 100644 hercules/templates/quest.tpl
create mode 100644 hercules/templates/quest_db.tpl
create mode 100644 hercules/templates/quests.xml
create mode 100644 hercules/templates/skill.sql
create mode 100644 hercules/templates/skill.tpl
create mode 100644 hercules/templates/skills.xml
create mode 100644 hercules/templates/sprite.xml
create mode 100644 hercules/templates/storage.sql
create mode 100644 hercules/templates/template.tmx
create mode 100644 hercules/templates/tileset.png
create mode 100755 hercules/tmx_converter.py
create mode 100755 herculeswrapper/char.sh
create mode 100644 herculeswrapper/herc-map-wrapper-config.example
create mode 100644 herculeswrapper/include.sh
create mode 100755 herculeswrapper/login.sh
create mode 100755 herculeswrapper/map.sh
create mode 100755 imagescheck/icccheck.sh
create mode 100755 imagescheck/icccheckfile.sh
create mode 100755 licensecheck/checkfile.sh
create mode 100755 licensecheck/clientdata.sh
create mode 100755 licensecheck/serverdata.py
create mode 100644 localserver/.gitignore
create mode 100644 localserver/README
create mode 100644 localserver/applicator.py
create mode 100755 localserver/build.sh
create mode 100755 localserver/buildasan.sh
create mode 100755 localserver/checktime.sh
create mode 100755 localserver/clean.sh
create mode 100644 localserver/conf/battle.conf
create mode 100644 localserver/conf/char-server.conf
create mode 100644 localserver/conf/constants.conf
create mode 100644 localserver/conf/inter-server.conf
create mode 100644 localserver/conf/login-server.conf
create mode 100644 localserver/conf/logs.conf
create mode 100644 localserver/conf/map-server.conf
create mode 100644 localserver/conf/msg_conf.txt
create mode 100644 localserver/conf/ports.conf
create mode 100644 localserver/conf/script.conf
create mode 100644 localserver/conf/socket.conf
create mode 100644 localserver/conf/sql_connection.conf
create mode 100755 localserver/givegm.sh
create mode 100755 localserver/initdb.sh
create mode 100755 localserver/installconfigs.sh
create mode 100644 localserver/npc/006-10_blackbox.txt
create mode 100644 localserver/npc/015-8_blackbox.txt
create mode 100644 localserver/npc/017-1_stranger_blackbox.txt
create mode 100644 localserver/npc/018-7-1_demure_blackbox.txt
create mode 100644 localserver/npc/023-4_blackbox.txt
create mode 100644 localserver/npc/025-2-4_aegis_blackbox.txt
create mode 100644 localserver/npc/botcheck_blackbox.txt
create mode 100644 localserver/npc/easteregg_blackbox.txt
create mode 100644 localserver/npc/magic_blackbox.txt
create mode 100644 localserver/reapply.patch3
create mode 100644 localserver/reapply.patch4
create mode 100755 localserver/rebuild.sh
create mode 100755 localserver/updateconfigs.sh
create mode 100755 localserver/updatedb.sh
create mode 100755 localserver/updater.sh
create mode 100755 manaplus/connect_local_server.sh
create mode 100644 manaplus/evol.manaplus
create mode 100755 manaplus/force_localmanaplus.sh
create mode 100644 manaplus/loginwallpaper_800x600.png
create mode 100644 manaplus/tmw2.manaplus
create mode 100755 testxml/testxml.py
create mode 100644 testxml/xsd/XMLSchema.xsd
create mode 100755 testxml/xsd/checkfile.sh
create mode 100644 testxml/xsd/its.xsd
create mode 100644 testxml/xsd/tmw.xsd
create mode 100644 testxml/xsd/xlink.xsd
create mode 100644 testxml/xsd/xml.xsd
create mode 100755 testxml/xsdcheck.sh
create mode 100644 update/TMW2org.zip
create mode 100755 update/addmods.sh
create mode 100644 update/adler32.c
create mode 100644 update/commit.txt
create mode 100644 update/commit_old.txt
create mode 100755 update/create_music.sh
create mode 100755 update/createnew.sh
create mode 100755 update/deploy.sh
create mode 100644 update/musiccommit.txt
create mode 100644 update/musiccommit_old.txt
create mode 100644 update/news.txt
create mode 100755 update/pseudo_update.sh
create mode 100755 update/update.sh
create mode 100755 update/update_music.sh
create mode 100644 web/all_news.bin
create mode 100755 web/backupcopy
create mode 100755 web/deploy.sh
create mode 100644 web/feed.xml
create mode 100755 web/fetch.py
create mode 100755 web/gameinfo.sh
create mode 100644 web/index.bin
create mode 100644 web/langs.txt
create mode 100755 web/legacy
create mode 100644 web/news.html
create mode 100755 web/news_to_html.py
create mode 100755 web/newsfeed.py
create mode 100644 web/nf_main.xml
create mode 100755 web/oldupdatelang.py
create mode 100644 web/original.po
create mode 100644 web/template.po
create mode 100755 web/updatelang.py
create mode 100755 wiki/elegen.py
create mode 100755 wiki/lanalysis.py
create mode 100755 wiki/redesign.py
create mode 100755 wiki/sedesign.py
create mode 100644 wiki/tmp-arm
create mode 100644 wiki/tmp-dec
create mode 100644 wiki/tmp-etc
create mode 100644 wiki/tmp-wpn
create mode 100755 wiki/webwikigen.py
create mode 100755 wiki/wikigen.py
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a1e538c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,89 @@
+# temp files
+*.xml~
+*.py~
+*.txt~
+*.conf~
+*~
+._*
+
+# updates
+update/files
+update/upload
+!update/adler32.c
+update/adler32
+update/files/Bugfix-*
+update/save*
+
+hercules_update/files
+hercules_update/upload
+!hercules_update/adler32.c
+hercules_update/adler32
+
+# saedit
+saedit/saedit
+saedit/make.log
+
+# langs
+lang/tx
+lang/new
+login.txt
+password.txt
+
+# lang client
+lang_client/tmp
+lang_client/pot
+lang_client/dict
+
+# tx
+tx
+
+# temp files
+*.o
+localserver/.~*
+
+# dyecmd
+dyecmd/dyecmd
+dyecmd/src/dyecmd
+dyecmd/src/CMakeFiles/
+dyecmd/src/Makefile
+dyecmd/src/cmake_install.cmake
+
+# testxml
+testxml/errors.txt
+
+# gitlab merger
+herculeswrapper/herc-map-wrapper-config
+
+# license check
+licensecheck/npcs.txt
+
+# wiki
+wiki/Items.md
+wiki/Monsters.md
+wiki/*.html
+wiki/*.txt
+
+# web
+1index.bin
+backup.bin
+web/po/*
+
+# Proccessed other server data
+hercules/data*
+hercules/osd*
+hercules/old*
+
+# Diffs and Patches related files
+*.diff
+*.patch
+*.orig
+*.rej
+
+# Jesusalva's personal stuff
+misc/*
+update/*back*
+lang/in/JSave
+
+# Beta Server
+localserver/beta.patch
+localserver/beta.patch2
diff --git a/Rules/castle_indoor/rule_castle_001.tmx b/Rules/castle_indoor/rule_castle_001.tmx
new file mode 100644
index 0000000..6f4af55
--- /dev/null
+++ b/Rules/castle_indoor/rule_castle_001.tmx
@@ -0,0 +1,176 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,74,0,74,0,74,0,74,0,74,0,74,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,74,0,74,0,74,0,74,0,74,0,74,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,74,0,74,0,0,0,74,0,74,0,74,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,74,0,74,0,0,0,0,0,74,0,74,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,74,0,74,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,74,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,10,0,11,0,0,0,0,0,14,0,15,0,0,0,17,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,19,0,0,0,0,0,22,0,23,0,0,0,25,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,30,0,31,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,38,0,39,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,36,0,37,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,41,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,1218,0,1186,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,1217,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,313,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,575,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,2,0,2,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
diff --git a/Rules/castle_indoor/rule_castle_002.tmx b/Rules/castle_indoor/rule_castle_002.tmx
new file mode 100644
index 0000000..a98bc77
--- /dev/null
+++ b/Rules/castle_indoor/rule_castle_002.tmx
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,74,74,0,
+0,74,0,74,0,0,0,0,
+0,74,0,74,0,74,74,0,
+0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,14,0,
+0,14,0,0,0,0,0,0,
+0,0,0,14,0,14,0,0,
+0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,14,0,0,
+0,0,0,14,0,0,0,0,
+0,14,0,0,0,0,14,0,
+0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,240,0,
+0,370,0,0,0,0,0,0,
+0,0,0,370,0,238,0,0,
+0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0
+
+
+
diff --git a/Rules/castle_indoor/rule_castle_003.tmx b/Rules/castle_indoor/rule_castle_003.tmx
new file mode 100644
index 0000000..a534afb
--- /dev/null
+++ b/Rules/castle_indoor/rule_castle_003.tmx
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+0,0,0,0,0,0,0,0,0,
+0,74,74,74,0,74,74,74,0,
+0,74,74,74,0,74,74,74,0,
+0,74,74,74,0,74,74,74,0,
+0,0,0,0,0,0,0,0,0,
+0,74,74,74,0,74,74,74,0,
+0,74,74,74,0,74,74,74,0,
+0,74,74,74,0,74,74,74,0,
+0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,
+0,0,0,14,0,14,0,0,0,
+0,0,0,0,0,0,0,0,0,
+0,0,0,14,0,14,0,0,0,
+0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,
+0,0,0,14,0,14,0,0,0,
+0,0,0,14,0,14,0,0,0,
+0,14,14,0,0,0,14,14,0,
+0,0,0,0,0,0,0,0,0,
+0,14,14,0,0,0,14,14,0,
+0,0,0,14,0,14,0,0,0,
+0,0,0,14,0,14,0,0,0,
+0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,
+0,0,0,209,0,210,0,0,0,
+0,0,0,0,0,0,0,0,0,
+0,0,0,338,0,339,0,0,0,
+0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0
+
+
+
diff --git a/Rules/castle_indoor/rule_castle_009.tmx b/Rules/castle_indoor/rule_castle_009.tmx
new file mode 100644
index 0000000..680d64c
--- /dev/null
+++ b/Rules/castle_indoor/rule_castle_009.tmx
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,
+0,74,0,74,74,0,74,0,74,74,0,
+0,74,0,0,0,0,74,0,0,0,0,
+0,0,0,0,74,0,0,0,0,74,0,
+0,74,74,0,74,0,74,74,0,74,0,
+0,0,0,0,0,0,0,0,0,0,0,
+0,74,0,74,74,0,0,0,0,0,0,
+0,74,0,0,0,0,0,0,0,0,0,
+0,0,0,0,74,0,0,0,0,0,0,
+0,74,74,0,74,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,
+0,74,0,74,74,0,0,0,0,0,0,
+0,74,0,0,0,0,0,0,0,0,0,
+0,0,0,0,74,0,0,0,0,0,0,
+0,74,74,0,74,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,
+0,10,0,42,10,0,0,0,44,0,0,
+0,42,0,0,0,0,44,0,0,0,0,
+0,0,0,0,42,0,0,0,0,44,0,
+0,10,42,0,10,0,0,44,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,
+0,11,0,42,11,0,0,0,0,0,0,
+0,42,0,0,0,0,0,0,0,0,0,
+0,0,0,0,42,0,0,0,0,0,0,
+0,11,42,0,11,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,
+0,19,0,42,19,0,0,0,0,0,0,
+0,42,0,0,0,0,0,0,0,0,0,
+0,0,0,0,42,0,0,0,0,0,0,
+0,19,42,0,19,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,10,0,0,0,0,0,0,0,
+0,10,0,0,0,0,0,0,0,0,0,
+0,0,0,0,10,0,0,0,0,0,0,
+0,0,10,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,11,0,0,0,0,0,0,0,
+0,11,0,0,0,0,0,0,0,0,0,
+0,0,0,0,11,0,0,0,0,0,0,
+0,0,11,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,19,0,0,0,0,0,0,0,
+0,19,0,0,0,0,0,0,0,0,0,
+0,0,0,0,19,0,0,0,0,0,0,
+0,0,19,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,810,0,0,0,0,304,0,0,
+0,793,0,0,0,0,287,0,0,0,0,
+0,0,0,0,825,0,0,0,0,319,0,
+0,0,808,0,0,0,0,302,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,810,0,0,0,0,0,0,0,
+0,793,0,0,0,0,0,0,0,0,0,
+0,0,0,0,825,0,0,0,0,0,0,
+0,0,808,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,810,0,0,0,0,0,0,0,
+0,793,0,0,0,0,0,0,0,0,0,
+0,0,0,0,825,0,0,0,0,0,0,
+0,0,808,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0
+
+
+
diff --git a/Rules/castle_indoor/rule_castle_010.tmx b/Rules/castle_indoor/rule_castle_010.tmx
new file mode 100644
index 0000000..5629a23
--- /dev/null
+++ b/Rules/castle_indoor/rule_castle_010.tmx
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,74,74,0,74,74,0,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,74,74,0,74,74,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,74,74,0,74,74,0,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,74,74,0,74,74,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,74,74,0,74,74,0,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,74,74,0,74,74,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,74,74,0,74,74,0,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,74,74,0,74,74,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,0,0,0,0,0,0,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,0,0,0,0,0,0,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,11,11,0,11,11,0,42,42,0,42,42,0,44,44,0,44,44,0,0,
+0,11,42,0,42,11,0,42,11,0,11,42,0,44,0,0,0,44,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,11,42,0,42,11,0,42,11,0,11,42,0,44,0,0,0,44,0,0,
+0,11,11,0,11,11,0,42,42,0,42,42,0,44,44,0,44,44,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,10,10,0,10,10,0,42,42,0,42,42,0,0,0,0,0,0,0,0,
+0,10,42,0,42,10,0,42,10,0,10,42,0,0,44,0,44,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,10,42,0,42,10,0,42,10,0,10,42,0,0,44,0,44,0,0,0,
+0,10,10,0,10,10,0,42,42,0,42,42,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,19,19,0,19,19,0,42,42,0,42,42,0,0,0,0,0,0,0,0,
+0,19,42,0,42,19,0,42,19,0,19,42,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,19,42,0,42,19,0,42,19,0,19,42,0,0,0,0,0,0,0,0,
+0,19,19,0,19,19,0,42,42,0,42,42,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,11,11,0,11,11,0,11,11,0,11,11,0,0,0,0,0,0,0,0,
+0,11,11,0,11,11,0,11,11,0,11,11,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,11,11,0,11,11,0,11,11,0,11,11,0,0,0,0,0,0,0,0,
+0,11,11,0,11,11,0,11,11,0,11,11,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,10,10,0,10,10,0,10,10,0,10,10,0,0,0,0,0,0,0,0,
+0,10,10,0,10,10,0,10,10,0,10,10,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,10,10,0,10,10,0,10,10,0,10,10,0,0,0,0,0,0,0,0,
+0,10,10,0,10,10,0,10,10,0,10,10,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,19,19,0,19,19,0,19,19,0,19,19,0,0,0,0,0,0,0,0,
+0,19,19,0,19,19,0,19,19,0,19,19,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,19,19,0,19,19,0,19,19,0,19,19,0,0,0,0,0,0,0,0,
+0,19,19,0,19,19,0,19,19,0,19,19,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,294,0,0,0,295,0,654,0,0,0,655,0,0,
+0,0,280,0,282,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,312,0,314,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,310,0,0,0,311,0,670,0,0,0,671,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,294,0,0,0,295,0,0,0,0,0,0,0,0,
+0,0,280,0,282,0,0,0,0,0,0,0,0,0,606,0,608,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,312,0,314,0,0,0,0,0,0,0,0,0,638,0,640,0,0,0,
+0,0,0,0,0,0,0,310,0,0,0,311,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,294,0,0,0,295,0,0,0,0,0,0,0,0,
+0,0,280,0,282,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,312,0,314,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,310,0,0,0,311,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
diff --git a/Rules/castle_indoor/rule_castle_indoor.txt b/Rules/castle_indoor/rule_castle_indoor.txt
new file mode 100644
index 0000000..4ce7553
--- /dev/null
+++ b/Rules/castle_indoor/rule_castle_indoor.txt
@@ -0,0 +1,23 @@
+// lines starting with // or // are comments
+// all other lines will be parsed and treated as filenames.
+
+// Rules for castles indoors
+
+// basic stuff: each rule-tile gets a default tile
+// here is a map attribute set: "DeleteTiles := true" This means to delete all
+// tiles in all touched tileslayers first.
+./rule_castle_001.tmx
+
+// straight walls:
+./rule_castle_002.tmx
+
+// corners at walls
+./rule_castle_003.tmx
+
+// Carpets are bad in overall, so do not place one near the other
+// straight carpets
+./rule_castle_009.tmx
+
+// corners in carpets
+./rule_castle_010.tmx
+
diff --git a/Rules/cave/rule_cave.txt b/Rules/cave/rule_cave.txt
new file mode 100644
index 0000000..080ae6b
--- /dev/null
+++ b/Rules/cave/rule_cave.txt
@@ -0,0 +1,41 @@
+// lines starting with // or // are comments
+// all other lines will be parsed and treated as filenames.
+
+// Rules for caves
+
+// basic stuff: each rule-tile gets a default tile
+// here is a map attribute set: "DeleteTiles := true" This means to delete all
+// tiles in all touched tileslayers first.
+./rule_cave_001.tmx
+
+// straight walls:
+./rule_cave_002.tmx
+
+// corners at walls
+./rule_cave_003.tmx
+./rule_cave_004.tmx
+./rule_cave_005.tmx
+
+// water in caves:
+./rule_cave_006.tmx
+./rule_cave_007.tmx
+
+// rules for entrances, has bad exception handling,
+// so mind where to put entrances. (So avoid putting them near corners)
+./rule_cave_008.tmx
+
+// straight carpets
+./rule_cave_009.tmx
+
+// corners in carpets
+./rule_cave_010.tmx
+
+// all tiles, which are walkable, but have unwalkable neighbors,
+// should be unwalkable
+// example: a decorative stone in a sea
+
+./rule_cave_050.tmx
+
+// error handling: some situations cannot be handled properly with this ruleset:
+// so indicate these situations with the error-tile
+./rule_cave_100.tmx
diff --git a/Rules/cave/rule_cave_001.tmx b/Rules/cave/rule_cave_001.tmx
new file mode 100644
index 0000000..065ccce
--- /dev/null
+++ b/Rules/cave/rule_cave_001.tmx
@@ -0,0 +1,169 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,74,0,74,0,74,0,74,0,74,0,74,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,74,0,74,0,74,0,74,0,74,0,74,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,74,0,74,0,0,0,74,0,74,0,74,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,74,0,74,0,0,0,0,0,74,0,74,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,74,0,74,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,10,0,11,0,12,0,13,0,14,0,15,0,0,0,17,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,18,0,19,0,20,0,21,0,22,0,23,0,0,0,25,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,26,0,27,0,0,0,29,0,30,0,31,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,34,0,35,0,0,0,0,0,38,0,39,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,42,0,43,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,36,0,37,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,41,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,82,0,198,0,188,0,188,0,98,0,75,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,201,0,196,0,188,0,188,0,75,0,75,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,184,0,82,0,0,0,188,0,75,0,75,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,82,0,82,0,0,0,0,0,75,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,105,0,105,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,177,0,0,0,0,0,136,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,152,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,103,0,0,0,168,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,200,0,206,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,103,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,2,0,2,0,0,0,0,0,2,0,2,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,2,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
diff --git a/Rules/cave/rule_cave_002.tmx b/Rules/cave/rule_cave_002.tmx
new file mode 100644
index 0000000..74feece
--- /dev/null
+++ b/Rules/cave/rule_cave_002.tmx
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+0,0,0,0,0,0,0,0,
+0,74,0,0,0,0,0,0,
+0,74,0,0,0,74,74,0,
+0,74,0,74,0,0,0,0,
+0,74,0,74,0,74,74,0,
+0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,14,0,
+0,14,0,0,0,0,0,0,
+0,0,0,14,0,14,0,0,
+0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,14,0,0,
+0,0,0,14,0,0,0,0,
+0,14,0,0,0,0,14,0,
+0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,
+0,114,0,0,0,0,0,0,
+0,130,0,0,0,0,97,0,
+0,146,0,0,0,0,0,0,
+0,0,0,0,0,99,0,0,
+0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,101,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,
+0,2,0,0,0,0,0,0,
+0,2,0,0,0,0,2,0,
+0,2,0,0,0,0,0,0,
+0,0,0,2,0,2,0,0,
+0,0,0,0,0,0,0,0
+
+
+
diff --git a/Rules/cave/rule_cave_003.tmx b/Rules/cave/rule_cave_003.tmx
new file mode 100644
index 0000000..6656ef6
--- /dev/null
+++ b/Rules/cave/rule_cave_003.tmx
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,14,0,0,0,14,0,0,0,0,0,0,0,
+0,14,14,0,14,14,0,0,14,0,14,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,14,0,14,0,0,14,14,0,14,14,0,
+0,0,0,0,0,0,0,14,0,0,0,14,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,86,0,0,0,87,0,0,100,0,102,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
diff --git a/Rules/cave/rule_cave_004.tmx b/Rules/cave/rule_cave_004.tmx
new file mode 100644
index 0000000..3238bc7
--- /dev/null
+++ b/Rules/cave/rule_cave_004.tmx
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+0,0,0,0,0,0,0,
+0,74,74,0,74,74,0,
+0,74,74,0,74,74,0,
+0,74,74,0,74,74,0,
+0,74,74,0,74,74,0,
+0,0,0,0,0,0,0,
+0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,
+0,14,14,0,14,14,0,
+0,0,14,0,14,0,0,
+0,0,0,0,0,0,0,
+0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,
+0,14,0,0,0,14,0,
+0,0,0,0,0,0,0,
+0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,
+0,0,118,0,116,0,0,
+0,0,134,0,132,0,0,
+0,0,150,0,148,0,0,
+0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,
+0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,
+0,2,2,0,2,2,0,
+0,2,2,0,2,2,0,
+0,2,2,0,2,2,0,
+0,0,2,0,2,0,0,
+0,0,0,0,0,0,0,
+0,0,0,0,0,0,0
+
+
+
diff --git a/Rules/cave/rule_cave_005.tmx b/Rules/cave/rule_cave_005.tmx
new file mode 100644
index 0000000..a325b0a
--- /dev/null
+++ b/Rules/cave/rule_cave_005.tmx
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,74,0,74,0,0,0,0,0,0,0,0,
+0,0,74,0,74,0,0,0,74,0,74,0,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,14,0,14,0,0,
+0,14,14,0,14,14,0,0,14,0,14,0,0,
+0,0,14,0,14,0,0,0,14,0,14,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,14,0,0,0,14,0,
+0,14,0,0,0,14,0,14,0,0,0,14,0,
+0,14,14,0,14,14,0,14,14,0,14,14,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,95,0,96,0,0,0,0,0,0,0,0,
+0,0,111,0,112,0,0,0,113,0,115,0,0,
+0,0,127,0,128,0,0,0,129,0,131,0,0,
+0,0,143,0,144,0,0,0,145,0,147,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,2,0,2,0,0,0,0,0,0,0,0,
+0,0,2,0,2,0,0,0,2,0,2,0,0,
+0,0,2,0,2,0,0,0,2,0,2,0,0,
+0,0,2,0,2,0,0,0,2,0,2,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
diff --git a/Rules/cave/rule_cave_006.tmx b/Rules/cave/rule_cave_006.tmx
new file mode 100644
index 0000000..622082d
--- /dev/null
+++ b/Rules/cave/rule_cave_006.tmx
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+0,0,0,0,0,0,
+0,74,0,74,74,0,
+0,74,0,0,0,0,
+0,0,0,0,74,0,
+0,74,74,0,74,0,
+0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,
+0,0,0,29,0,0,
+0,29,0,0,0,0,
+0,0,0,0,29,0,
+0,0,29,0,0,0,
+0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,
+0,0,0,21,0,0,
+0,21,0,0,0,0,
+0,0,0,0,21,0,
+0,0,21,0,0,0,
+0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,
+0,0,0,13,0,0,
+0,13,0,0,0,0,
+0,0,0,0,13,0,
+0,0,13,0,0,0,
+0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,
+0,0,0,20,0,0,
+0,20,0,0,0,0,
+0,0,0,0,20,0,
+0,0,20,0,0,0,
+0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,
+0,0,0,12,0,0,
+0,12,0,0,0,0,
+0,0,0,0,12,0,
+0,0,12,0,0,0,
+0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,
+0,29,0,0,29,0,
+0,0,0,0,0,0,
+0,0,0,0,0,0,
+0,29,0,0,29,0,
+0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,
+0,21,0,0,21,0,
+0,0,0,0,0,0,
+0,0,0,0,0,0,
+0,21,0,0,21,0,
+0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,
+0,13,0,0,13,0,
+0,0,0,0,0,0,
+0,0,0,0,0,0,
+0,13,0,0,13,0,
+0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,
+0,20,0,0,20,0,
+0,0,0,0,0,0,
+0,0,0,0,0,0,
+0,20,0,0,20,0,
+0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,
+0,12,0,0,12,0,
+0,0,0,0,0,0,
+0,0,0,0,0,0,
+0,12,0,0,12,0,
+0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,
+0,0,0,189,0,0,
+0,172,0,0,0,0,
+0,0,0,0,204,0,
+0,0,187,0,0,0,
+0,0,0,0,0,0
+
+
+
diff --git a/Rules/cave/rule_cave_007.tmx b/Rules/cave/rule_cave_007.tmx
new file mode 100644
index 0000000..aeeae3c
--- /dev/null
+++ b/Rules/cave/rule_cave_007.tmx
@@ -0,0 +1,147 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,29,29,0,29,29,0,
+0,0,29,0,29,0,0,29,0,0,0,29,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,29,0,29,0,0,29,0,0,0,29,0,
+0,0,0,0,0,0,0,29,29,0,29,29,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,21,21,0,21,21,0,
+0,0,21,0,21,0,0,21,0,0,0,21,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,21,0,21,0,0,21,0,0,0,21,0,
+0,0,0,0,0,0,0,21,21,0,21,21,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,13,13,0,13,13,0,
+0,0,13,0,13,0,0,13,0,0,0,13,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,13,0,13,0,0,13,0,0,0,13,0,
+0,0,0,0,0,0,0,13,13,0,13,13,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,20,20,0,20,20,0,
+0,0,20,0,20,0,0,20,0,0,0,20,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,20,0,20,0,0,20,0,0,0,20,0,
+0,0,0,0,0,0,0,20,20,0,20,20,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,12,12,0,12,12,0,
+0,0,12,0,12,0,0,12,0,0,0,12,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,12,0,12,0,0,12,0,0,0,12,0,
+0,0,0,0,0,0,0,12,12,0,12,12,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,29,29,0,29,29,0,0,0,0,0,0,0,
+0,29,0,0,0,29,0,0,29,0,29,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,29,0,0,0,29,0,0,29,0,29,0,0,
+0,29,29,0,29,29,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,21,21,0,21,21,0,0,0,0,0,0,0,
+0,21,0,0,0,21,0,0,21,0,21,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,21,0,0,0,21,0,0,21,0,21,0,0,
+0,21,21,0,21,21,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,13,13,0,13,13,0,0,0,0,0,0,0,
+0,13,0,0,0,13,0,0,13,0,13,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,13,0,0,0,13,0,0,13,0,13,0,0,
+0,13,13,0,13,13,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,20,20,0,20,20,0,0,0,0,0,0,0,
+0,20,0,0,0,20,0,0,20,0,20,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,20,0,0,0,20,0,0,20,0,20,0,0,
+0,20,20,0,20,20,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,12,12,0,12,12,0,0,0,0,0,0,0,
+0,12,0,0,0,12,0,0,12,0,12,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,12,0,0,0,12,0,0,12,0,12,0,0,
+0,12,12,0,12,12,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,169,0,0,0,170,0,
+0,0,171,0,173,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,203,0,205,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,185,0,0,0,186,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
diff --git a/Rules/cave/rule_cave_008.tmx b/Rules/cave/rule_cave_008.tmx
new file mode 100644
index 0000000..00a7a06
--- /dev/null
+++ b/Rules/cave/rule_cave_008.tmx
@@ -0,0 +1,144 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,74,74,74,0,74,74,74,0,74,74,74,0,
+0,74,74,74,0,74,74,74,0,74,74,74,0,
+0,74,74,74,0,74,74,74,0,74,74,74,0,
+0,74,74,74,0,74,74,74,0,74,74,74,0,
+0,74,74,74,0,74,74,74,0,74,74,74,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,74,74,74,0,0,0,0,0,
+0,0,0,0,0,74,74,74,0,0,0,0,0,
+0,0,0,0,0,74,74,74,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,14,14,14,0,14,14,14,0,14,14,14,0,
+0,14,14,14,0,14,14,14,0,14,14,14,0,
+0,14,14,14,0,14,14,14,0,14,14,14,0,
+0,14,31,14,0,14,38,14,0,14,30,14,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,14,22,14,0,0,0,0,0,
+0,0,0,0,0,14,14,14,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,23,0,0,0,15,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,14,14,14,0,14,14,14,0,14,14,14,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,14,14,14,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,98,98,98,0,98,98,98,0,98,98,98,0,
+0,114,114,114,0,114,114,114,0,114,114,114,0,
+0,164,165,166,0,139,140,141,0,91,92,93,0,
+0,180,181,182,0,155,156,157,0,107,108,109,0,
+0,0,0,0,0,0,0,0,0,123,124,125,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,82,82,82,0,0,0,0,0,
+0,0,0,0,0,82,82,82,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,101,101,101,0,0,0,0,0,
+0,0,0,0,0,195,195,195,0,0,0,0,0,
+0,0,0,0,0,210,211,212,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
diff --git a/Rules/cave/rule_cave_009.tmx b/Rules/cave/rule_cave_009.tmx
new file mode 100644
index 0000000..bb1e8ec
--- /dev/null
+++ b/Rules/cave/rule_cave_009.tmx
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+0,0,0,0,0,0,
+0,74,0,74,74,0,
+0,74,0,0,0,0,
+0,0,0,0,74,0,
+0,74,74,0,74,0,
+0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,
+0,0,0,43,0,0,
+0,43,0,0,0,0,
+0,0,0,0,43,0,
+0,0,43,0,0,0,
+0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,
+0,0,0,42,0,0,
+0,42,0,0,0,0,
+0,0,0,0,42,0,
+0,0,42,0,0,0,
+0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,
+0,43,0,0,43,0,
+0,0,0,0,0,0,
+0,0,0,0,0,0,
+0,43,0,0,43,0,
+0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,
+0,42,0,0,42,0,
+0,0,0,0,0,0,
+0,0,0,0,0,0,
+0,42,0,0,42,0,
+0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,
+0,0,0,106,0,0,
+0,89,0,0,0,0,
+0,0,0,0,121,0,
+0,0,104,0,0,0,
+0,0,0,0,0,0
+
+
+
diff --git a/Rules/cave/rule_cave_010.tmx b/Rules/cave/rule_cave_010.tmx
new file mode 100644
index 0000000..3c9256b
--- /dev/null
+++ b/Rules/cave/rule_cave_010.tmx
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,43,43,0,43,43,0,
+0,0,43,0,43,0,0,43,0,0,0,43,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,43,0,43,0,0,43,0,0,0,43,0,
+0,0,0,0,0,0,0,43,43,0,43,43,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,42,42,0,42,42,0,
+0,0,42,0,42,0,0,42,0,0,0,42,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,42,0,42,0,0,42,0,0,0,42,0,
+0,0,0,0,0,0,0,42,42,0,42,42,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,43,0,43,0,0,0,0,0,0,0,0,
+0,43,0,0,0,43,0,0,43,0,43,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,43,0,0,0,43,0,0,43,0,43,0,0,
+0,0,43,0,43,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,42,0,42,0,0,0,0,0,0,0,0,
+0,42,0,0,0,42,0,0,42,0,42,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,42,0,0,0,42,0,0,42,0,42,0,0,
+0,0,42,0,42,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,137,0,0,0,138,0,
+0,0,88,0,90,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,120,0,122,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,153,0,0,0,154,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
diff --git a/Rules/cave/rule_cave_050.tmx b/Rules/cave/rule_cave_050.tmx
new file mode 100644
index 0000000..ff04f7f
--- /dev/null
+++ b/Rules/cave/rule_cave_050.tmx
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+0,0,0,0,0,
+0,0,74,0,0,
+0,74,74,74,0,
+0,0,74,0,0,
+0,0,0,0,0
+
+
+
+
+0,0,0,0,0,
+0,0,18,0,0,
+0,18,0,18,0,
+0,0,18,0,0,
+0,0,0,0,0
+
+
+
+
+0,0,0,0,0,
+0,0,26,0,0,
+0,26,0,26,0,
+0,0,26,0,0,
+0,0,0,0,0
+
+
+
+
+0,0,0,0,0,
+0,0,27,0,0,
+0,27,0,27,0,
+0,0,27,0,0,
+0,0,0,0,0
+
+
+
+
+0,0,0,0,0,
+0,0,35,0,0,
+0,35,0,35,0,
+0,0,35,0,0,
+0,0,0,0,0
+
+
+
+
+0,0,0,0,0,
+0,0,43,0,0,
+0,43,0,43,0,
+0,0,43,0,0,
+0,0,0,0,0
+
+
+
+
+0,0,0,0,0,
+0,0,12,0,0,
+0,12,0,12,0,
+0,0,12,0,0,
+0,0,0,0,0
+
+
+
+
+0,0,0,0,0,
+0,0,14,0,0,
+0,14,0,14,0,
+0,0,14,0,0,
+0,0,0,0,0
+
+
+
+
+0,0,0,0,0,
+0,0,30,0,0,
+0,30,0,30,0,
+0,0,30,0,0,
+0,0,0,0,0
+
+
+
+
+0,0,0,0,0,
+0,0,38,0,0,
+0,38,0,38,0,
+0,0,38,0,0,
+0,0,0,0,0
+
+
+
+
+0,0,0,0,0,
+0,0,31,0,0,
+0,31,0,31,0,
+0,0,31,0,0,
+0,0,0,0,0
+
+
+
+
+0,0,0,0,0,
+0,0,39,0,0,
+0,39,0,39,0,
+0,0,39,0,0,
+0,0,0,0,0
+
+
+
+
+0,0,0,0,0,
+0,0,0,0,0,
+0,0,2,0,0,
+0,0,0,0,0,
+0,0,0,0,0
+
+
+
diff --git a/Rules/cave/rule_cave_100.tmx b/Rules/cave/rule_cave_100.tmx
new file mode 100644
index 0000000..b115cf8
--- /dev/null
+++ b/Rules/cave/rule_cave_100.tmx
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ H4sIAAAAAAAAA2NgIA84kyiOLOeMhPGJk2sPIfupbR42d1PTHlIBAHq5GFfUAQAA
+
+
+
+
+ H4sIAAAAAAAAA2NgoB9gxcFmx8HGB4hVR0ugjIM90AAA6DCU7NQBAAA=
+
+
+
+
+ H4sIAAAAAAAAA2NgoB/gxcEeqkAFB3ugAQAsLTFH1AEAAA==
+
+
+
+
+ H4sIAAAAAAAAA2NgoB9gw8EeBdQFAM2+xmvUAQAA
+
+
+
+
+ H4sIAAAAAAAAA2NgoB/gw8EeBdQFAEag6ZXUAQAA
+
+
+
+
+ H4sIAAAAAAAAA2NgoB8Qw8EeBdQFAJqF6UzUAQAA
+
+
+
+
+ H4sIAAAAAAAAA2NgoB9wIZFNrFkDBchxNz0AAJUJJNPUAQAA
+
+
+
diff --git a/Rules/cave/rule_cave_110.tmx b/Rules/cave/rule_cave_110.tmx
new file mode 100644
index 0000000..6656ef6
--- /dev/null
+++ b/Rules/cave/rule_cave_110.tmx
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,14,0,0,0,14,0,0,0,0,0,0,0,
+0,14,14,0,14,14,0,0,14,0,14,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,14,0,14,0,0,14,14,0,14,14,0,
+0,0,0,0,0,0,0,14,0,0,0,14,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,86,0,0,0,87,0,0,100,0,102,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
diff --git a/Rules/caverules.txt b/Rules/caverules.txt
new file mode 100644
index 0000000..d25355b
--- /dev/null
+++ b/Rules/caverules.txt
@@ -0,0 +1,6 @@
+rule_cave.tmx
+
+// NOTE: Map with Ground only, black - base ground - water. Automap after done.
+// AFTERMAP: Remove grey over tiles, fix narrow transistions (especially wall-water).
+// RANDOMIZE: By adding randomization to rule_cave.tmx AFTER first automap.
+// Add details manually.
diff --git a/Rules/desertcliffs/rule_cave.txt b/Rules/desertcliffs/rule_cave.txt
new file mode 100644
index 0000000..8695922
--- /dev/null
+++ b/Rules/desertcliffs/rule_cave.txt
@@ -0,0 +1,32 @@
+// lines starting with // or // are comments
+// all other lines will be parsed and treated as filenames.
+
+// Rules for desert cliffs
+
+// basic stuff: each rule-tile gets a default tile
+// here is a map attribute set: "DeleteTiles := true" This means to delete all
+// tiles in all touched tileslayers first.
+./rule_cave_001.tmx
+
+// straight walls:
+./rule_cave_002.tmx
+
+// corners at walls
+./rule_cave_003.tmx
+./rule_cave_03.tmx
+//./rule_cave_004.tmx
+//./rule_cave_005.tmx
+
+// rules for entrances, has bad exception handling,
+// so mind where to put entrances. (So avoid putting them near corners)
+//./rule_cave_008.tmx
+
+// all tiles, which are walkable, but have unwalkable neighbors,
+// should be unwalkable
+// example: a decorative stone in a sea
+
+./rule_cave_050.tmx
+
+// error handling: some situations cannot be handled properly with this ruleset:
+// so indicate these situations with the error-tile
+//./rule_cave_100.tmx
diff --git a/Rules/desertcliffs/rule_cave_001.tmx b/Rules/desertcliffs/rule_cave_001.tmx
new file mode 100644
index 0000000..403a7a4
--- /dev/null
+++ b/Rules/desertcliffs/rule_cave_001.tmx
@@ -0,0 +1,166 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,10,0,0,0,0,0,0,0,10,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,10,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,14,0,0,0,0,0,0,0,18,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,43,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,78,0,0,0,0,0,0,0,78,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
diff --git a/Rules/desertcliffs/rule_cave_002.tmx b/Rules/desertcliffs/rule_cave_002.tmx
new file mode 100644
index 0000000..e7c1140
--- /dev/null
+++ b/Rules/desertcliffs/rule_cave_002.tmx
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,74,0,0,0,74,74,0,
+0,74,0,74,0,0,0,0,
+0,0,0,74,0,74,74,0,
+0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,10,0,
+0,10,0,10,0,0,0,0,
+0,0,0,0,0,10,0,0,
+0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,10,0,0,0,10,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,10,0,0,10,0,
+0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,176,0,
+0,161,0,113,0,0,0,0,
+0,0,0,129,0,181,0,0,
+0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0
+
+
+
diff --git a/Rules/desertcliffs/rule_cave_003.tmx b/Rules/desertcliffs/rule_cave_003.tmx
new file mode 100644
index 0000000..27e52d8
--- /dev/null
+++ b/Rules/desertcliffs/rule_cave_003.tmx
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,10,0,10,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,10,0,10,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,10,0,0,0,10,0,10,10,0,10,10,0,
+0,10,10,0,10,10,0,10,0,0,0,10,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,112,0,114,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,96,0,98,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
diff --git a/Rules/desertcliffs/rule_cave_004.tmx b/Rules/desertcliffs/rule_cave_004.tmx
new file mode 100644
index 0000000..f4616ee
--- /dev/null
+++ b/Rules/desertcliffs/rule_cave_004.tmx
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,74,74,74,74,0,0,0,74,74,74,74,0,
+0,74,74,74,74,0,0,0,74,74,74,74,0,
+0,74,74,74,74,0,0,0,74,74,74,74,0,
+0,74,74,74,74,0,0,0,74,74,74,74,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,74,74,74,74,0,0,0,74,74,74,74,0,
+0,74,74,74,74,0,0,0,74,74,74,74,0,
+0,74,74,74,74,0,0,0,74,74,74,74,0,
+0,74,74,74,74,0,0,0,74,74,74,74,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,10,10,0,0,0,0,0,10,10,0,0,
+0,0,10,0,0,0,0,0,0,0,10,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,10,0,0,0,0,0,0,0,10,0,0,
+0,0,10,10,0,0,0,0,0,10,10,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,10,0,0,0,0,0,10,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,10,0,0,0,0,0,10,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,195,0,0,
+0,0,0,0,0,0,0,0,0,210,211,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
diff --git a/Rules/desertcliffs/rule_cave_005.tmx b/Rules/desertcliffs/rule_cave_005.tmx
new file mode 100644
index 0000000..97cf76d
--- /dev/null
+++ b/Rules/desertcliffs/rule_cave_005.tmx
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,74,0,74,0,0,0,0,0,0,0,0,
+0,0,74,0,74,0,0,0,74,0,74,0,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,14,0,14,0,0,
+0,14,14,0,14,14,0,0,14,0,14,0,0,
+0,0,14,0,14,0,0,0,14,0,14,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,14,0,0,0,14,0,
+0,14,0,0,0,14,0,14,0,0,0,14,0,
+0,14,14,0,14,14,0,14,14,0,14,14,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,95,0,96,0,0,0,0,0,0,0,0,
+0,0,111,0,112,0,0,0,113,0,115,0,0,
+0,0,127,0,128,0,0,0,129,0,131,0,0,
+0,0,143,0,144,0,0,0,145,0,147,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,2,0,2,0,0,0,0,0,0,0,0,
+0,0,2,0,2,0,0,0,2,0,2,0,0,
+0,0,2,0,2,0,0,0,2,0,2,0,0,
+0,0,2,0,2,0,0,0,2,0,2,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
diff --git a/Rules/desertcliffs/rule_cave_006.tmx b/Rules/desertcliffs/rule_cave_006.tmx
new file mode 100644
index 0000000..9b1cf57
--- /dev/null
+++ b/Rules/desertcliffs/rule_cave_006.tmx
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+0,0,0,0,0,0,
+0,74,0,74,74,0,
+0,74,0,0,0,0,
+0,0,0,0,74,0,
+0,74,74,0,74,0,
+0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,
+0,0,0,29,0,0,
+0,29,0,0,0,0,
+0,0,0,0,29,0,
+0,0,29,0,0,0,
+0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,
+0,0,0,21,0,0,
+0,21,0,0,0,0,
+0,0,0,0,21,0,
+0,0,21,0,0,0,
+0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,
+0,0,0,13,0,0,
+0,13,0,0,0,0,
+0,0,0,0,13,0,
+0,0,13,0,0,0,
+0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,
+0,0,0,20,0,0,
+0,20,0,0,0,0,
+0,0,0,0,20,0,
+0,0,20,0,0,0,
+0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,
+0,0,0,12,0,0,
+0,12,0,0,0,0,
+0,0,0,0,12,0,
+0,0,12,0,0,0,
+0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,
+0,29,0,0,29,0,
+0,0,0,0,0,0,
+0,0,0,0,0,0,
+0,29,0,0,29,0,
+0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,
+0,21,0,0,21,0,
+0,0,0,0,0,0,
+0,0,0,0,0,0,
+0,21,0,0,21,0,
+0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,
+0,13,0,0,13,0,
+0,0,0,0,0,0,
+0,0,0,0,0,0,
+0,13,0,0,13,0,
+0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,
+0,20,0,0,20,0,
+0,0,0,0,0,0,
+0,0,0,0,0,0,
+0,20,0,0,20,0,
+0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,
+0,12,0,0,12,0,
+0,0,0,0,0,0,
+0,0,0,0,0,0,
+0,12,0,0,12,0,
+0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,
+0,0,0,189,0,0,
+0,172,0,0,0,0,
+0,0,0,0,204,0,
+0,0,187,0,0,0,
+0,0,0,0,0,0
+
+
+
diff --git a/Rules/desertcliffs/rule_cave_007.tmx b/Rules/desertcliffs/rule_cave_007.tmx
new file mode 100644
index 0000000..9171718
--- /dev/null
+++ b/Rules/desertcliffs/rule_cave_007.tmx
@@ -0,0 +1,147 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,
+0,74,74,0,74,74,0,74,74,0,74,74,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,29,29,0,29,29,0,
+0,0,29,0,29,0,0,29,0,0,0,29,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,29,0,29,0,0,29,0,0,0,29,0,
+0,0,0,0,0,0,0,29,29,0,29,29,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,21,21,0,21,21,0,
+0,0,21,0,21,0,0,21,0,0,0,21,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,21,0,21,0,0,21,0,0,0,21,0,
+0,0,0,0,0,0,0,21,21,0,21,21,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,13,13,0,13,13,0,
+0,0,13,0,13,0,0,13,0,0,0,13,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,13,0,13,0,0,13,0,0,0,13,0,
+0,0,0,0,0,0,0,13,13,0,13,13,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,20,20,0,20,20,0,
+0,0,20,0,20,0,0,20,0,0,0,20,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,20,0,20,0,0,20,0,0,0,20,0,
+0,0,0,0,0,0,0,20,20,0,20,20,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,12,12,0,12,12,0,
+0,0,12,0,12,0,0,12,0,0,0,12,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,12,0,12,0,0,12,0,0,0,12,0,
+0,0,0,0,0,0,0,12,12,0,12,12,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,29,29,0,29,29,0,0,0,0,0,0,0,
+0,29,0,0,0,29,0,0,29,0,29,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,29,0,0,0,29,0,0,29,0,29,0,0,
+0,29,29,0,29,29,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,21,21,0,21,21,0,0,0,0,0,0,0,
+0,21,0,0,0,21,0,0,21,0,21,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,21,0,0,0,21,0,0,21,0,21,0,0,
+0,21,21,0,21,21,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,13,13,0,13,13,0,0,0,0,0,0,0,
+0,13,0,0,0,13,0,0,13,0,13,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,13,0,0,0,13,0,0,13,0,13,0,0,
+0,13,13,0,13,13,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,20,20,0,20,20,0,0,0,0,0,0,0,
+0,20,0,0,0,20,0,0,20,0,20,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,20,0,0,0,20,0,0,20,0,20,0,0,
+0,20,20,0,20,20,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,12,12,0,12,12,0,0,0,0,0,0,0,
+0,12,0,0,0,12,0,0,12,0,12,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,12,0,0,0,12,0,0,12,0,12,0,0,
+0,12,12,0,12,12,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,169,0,0,0,170,0,
+0,0,171,0,173,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,203,0,205,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,185,0,0,0,186,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
diff --git a/Rules/desertcliffs/rule_cave_008.tmx b/Rules/desertcliffs/rule_cave_008.tmx
new file mode 100644
index 0000000..14a69e3
--- /dev/null
+++ b/Rules/desertcliffs/rule_cave_008.tmx
@@ -0,0 +1,144 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,74,74,74,0,74,74,74,0,74,74,74,0,
+0,74,74,74,0,74,74,74,0,74,74,74,0,
+0,74,74,74,0,74,74,74,0,74,74,74,0,
+0,74,74,74,0,74,74,74,0,74,74,74,0,
+0,74,74,74,0,74,74,74,0,74,74,74,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,74,74,74,0,0,0,0,0,
+0,0,0,0,0,74,74,74,0,0,0,0,0,
+0,0,0,0,0,74,74,74,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,14,14,14,0,14,14,14,0,14,14,14,0,
+0,14,14,14,0,14,14,14,0,14,14,14,0,
+0,14,14,14,0,14,14,14,0,14,14,14,0,
+0,14,31,14,0,14,38,14,0,14,30,14,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,14,22,14,0,0,0,0,0,
+0,0,0,0,0,14,14,14,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,23,0,0,0,15,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,14,14,14,0,14,14,14,0,14,14,14,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,14,14,14,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,98,98,98,0,98,98,98,0,98,98,98,0,
+0,114,114,114,0,114,114,114,0,114,114,114,0,
+0,164,165,166,0,139,140,141,0,91,92,93,0,
+0,180,181,182,0,155,156,157,0,107,108,109,0,
+0,0,0,0,0,0,0,0,0,123,124,125,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,82,82,82,0,0,0,0,0,
+0,0,0,0,0,82,82,82,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,101,101,101,0,0,0,0,0,
+0,0,0,0,0,78,78,78,0,0,0,0,0,
+0,0,0,0,0,210,211,212,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
diff --git a/Rules/desertcliffs/rule_cave_03.tmx b/Rules/desertcliffs/rule_cave_03.tmx
new file mode 100644
index 0000000..b7ddb3a
--- /dev/null
+++ b/Rules/desertcliffs/rule_cave_03.tmx
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,74,0,0,74,74,0,74,74,0,0,0,0,
+0,74,0,0,74,74,0,74,74,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,14,0,0,14,0,0,0,14,0,0,0,0,
+0,14,0,0,14,0,0,0,14,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,14,0,14,0,0,0,0,0,
+0,0,0,0,0,14,0,14,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,78,0,0,0,78,0,0,0,0,
+0,0,0,0,78,0,0,0,78,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,129,0,0,130,0,0,0,128,0,0,0,0,
+0,129,0,0,130,0,0,0,128,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,2,0,0,2,0,0,0,2,0,0,0,0,
+0,2,0,0,2,0,0,0,2,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
diff --git a/Rules/desertcliffs/rule_cave_050.tmx b/Rules/desertcliffs/rule_cave_050.tmx
new file mode 100644
index 0000000..aabf7fe
--- /dev/null
+++ b/Rules/desertcliffs/rule_cave_050.tmx
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+0,0,0,0,0,
+0,0,74,0,0,
+0,74,74,74,0,
+0,0,74,0,0,
+0,0,0,0,0
+
+
+
+
+0,0,0,0,0,
+0,0,14,0,0,
+0,14,0,14,0,
+0,0,14,0,0,
+0,0,0,0,0
+
+
+
+
+0,0,0,0,0,
+0,0,0,0,0,
+0,0,2,0,0,
+0,0,0,0,0,
+0,0,0,0,0
+
+
+
diff --git a/Rules/desertcliffs/rule_cave_100.tmx b/Rules/desertcliffs/rule_cave_100.tmx
new file mode 100644
index 0000000..bd25bb0
--- /dev/null
+++ b/Rules/desertcliffs/rule_cave_100.tmx
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ H4sIAAAAAAAAA2NgoC7wokCPFxKmpT3DHQAAJSFKadQBAAA=
+
+
+
+
+ H4sIAAAAAAAAA2NgGHjAh4NNrJ5RgAoA0841FdQBAAA=
+
+
+
+
+ H4sIAAAAAAAAA2NgGHjgjYNNrJ5RgAoAKzmZutQBAAA=
+
+
+
diff --git a/Rules/icecave/rule_icecave.txt b/Rules/icecave/rule_icecave.txt
new file mode 100644
index 0000000..c4580a9
--- /dev/null
+++ b/Rules/icecave/rule_icecave.txt
@@ -0,0 +1,34 @@
+# lines starting with # or // are comments
+# all other lines will be parsed and treated as filenames.
+
+# ice caves... similar to usual caves
+
+# basic stuff: each rule-tile gets a default tile
+./rule_icecave_001.tmx
+
+# straight walls:
+./rule_icecave_002.tmx
+
+# corners at walls
+./rule_icecave_003.tmx
+./rule_icecave_004.tmx
+./rule_icecave_005.tmx
+
+# frozen water straight waterside
+./rule_icecave_006.tmx
+
+# frozen water: corners at waterside
+./rule_icecave_007.tmx
+
+# carpet straight
+./rule_icecave_009.tmx
+
+# carpet corners
+./rule_icecave_010.tmx
+
+# entrances and exits
+./rule_icecave_020.tmx
+
+# error handling: some situations cannot be handled properly with this ruleset:
+# so indicate these situations with the error-tile
+./rule_icecave_100.tmx
diff --git a/Rules/icecave/rule_icecave_001.tmx b/Rules/icecave/rule_icecave_001.tmx
new file mode 100644
index 0000000..c7c31f9
--- /dev/null
+++ b/Rules/icecave/rule_icecave_001.tmx
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ H4sIAAAAAAAAA2NgIA4IE6CJBbj0DRZzyPEXALXctVNEAQAA
+
+
+
+
+ H4sIAAAAAAAAA2NgIA4wQ2kWKM0KpdmI1A8D7FCaA02ci0RzuKE0D5o4H4nm8ENpASgtCKWFSDADALg2bV1EAQAA
+
+
+
+
+ H4sIAAAAAAAAA2NgIA5oQ2lpNLqVSP0wYASlJdDEyTVHkkrmwPwjgkYTAwCSHrZpRAEAAA==
+
+
+
+
+ H4sIAAAAAAAAA2NgIA4wEeBTCkg1D5d6cs2hxD8ARG/fIUQBAAA=
+
+
+
+
+ H4sIAAAAAAAAA2NgIA8YkKmPWubhUh9AojkBaDQ5AACqeTmWRAEAAA==
+
+
+
+
+ H4sIAAAAAAAAA2NgGAWUAgAV+RUNRAEAAA==
+
+
+
+
+ H4sIAAAAAAAAA2NgGAWUAgAV+RUNRAEAAA==
+
+
+
diff --git a/Rules/icecave/rule_icecave_002.tmx b/Rules/icecave/rule_icecave_002.tmx
new file mode 100644
index 0000000..0df731d
--- /dev/null
+++ b/Rules/icecave/rule_icecave_002.tmx
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ H4sIAAAAAAAAA2NgIA4Ik6FGGAmjq8FnHroabOYQAgAtyHRU2AAAAA==
+
+
+
+
+ H4sIAAAAAAAAA2NgIA4wk6mGGU2cGHPQ1aKbQQwAAE/ZWtvYAAAA
+
+
+
+
+ H4sIAAAAAAAAA2NgoB1gprIcPj3oAAAVANpz2AAAAA==
+
+
+
+
+ H4sIAAAAAAAAA2NgIA5YE6HGG4uYFho/mkj70IEOieoBwW8e8tgAAAA=
+
+
+
+
+ H4sIAAAAAAAAA2NgGBigR2PzAWLr5ITYAAAA
+
+
+
+
+ H4sIAAAAAAAAA2NgIA4wkamGCU2cGHPQ1aKbQQwAAFG7H+7YAAAA
+
+
+
diff --git a/Rules/icecave/rule_icecave_003.tmx b/Rules/icecave/rule_icecave_003.tmx
new file mode 100644
index 0000000..3dda319
--- /dev/null
+++ b/Rules/icecave/rule_icecave_003.tmx
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ H4sIAAAAAAAAA2NgIA8IQzGxbHL1kAoA+YlsANAAAAA=
+
+
+
+
+ H4sIAAAAAAAAA2NgIA8wE+Dj0sOMhc2AJk4JAAB87igk0AAAAA==
+
+
+
+
+ H4sIAAAAAAAAA2NgIB8wo9EwNjMWNjHm4OKTAgA8hSQr0AAAAA==
+
+
+
+
+ H4sIAAAAAAAAA2NgIA/Io/EVkNi6UFqfTLMpAQCgAAh70AAAAA==
+
+
+
diff --git a/Rules/icecave/rule_icecave_004.tmx b/Rules/icecave/rule_icecave_004.tmx
new file mode 100644
index 0000000..d7c406b
--- /dev/null
+++ b/Rules/icecave/rule_icecave_004.tmx
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ H4sIAAAAAAAAA2NgwA+EoRidTSs5XAAAY11lnagAAAA=
+
+
+
+
+ H4sIAAAAAAAAA2NgwA+YoRidTYkcA5ocMQAANSMN1qgAAAA=
+
+
+
+
+ H4sIAAAAAAAAA2NgoD9gJsDHBgB9Q1zQqAAAAA==
+
+
+
+
+ H4sIAAAAAAAAA2NgIAzsobQtFjl/KO2LRS4eSscSYQchAAB9+9lsqAAAAA==
+
+
+
+
+ H4sIAAAAAAAAA2NgwA+YoBidTYkcA5ocMQAAqilRnqgAAAA=
+
+
+
diff --git a/Rules/icecave/rule_icecave_005.tmx b/Rules/icecave/rule_icecave_005.tmx
new file mode 100644
index 0000000..b365906
--- /dev/null
+++ b/Rules/icecave/rule_icecave_005.tmx
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ H4sIAAAAAAAAA2NgIB8Io9GU6MEnLkwCm556SAUAWbn4FWwBAAA=
+
+
+
+
+ H4sIAAAAAAAAA2NgIB8wo9GU6MEnzoyFTch+Uu2hBwAAZhB9h2wBAAA=
+
+
+
+
+ H4sIAAAAAAAAA2NgGJqAGQ8flxwhPcwksMkBAHO5eppsAQAA
+
+
+
+
+ H4sIAAAAAAAAA2NgIB9oQGlNEvRYQGlLNHErKG2DRY8HlPZEE/eC0j5Y9ERA6Ug08SgoHYPHjbQCAKQ1RdVsAQAA
+
+
+
+
+ H4sIAAAAAAAAA2NgIB8wodGU6MFnFr300BoAAErWG9FsAQAA
+
+
+
diff --git a/Rules/icecave/rule_icecave_006.tmx b/Rules/icecave/rule_icecave_006.tmx
new file mode 100644
index 0000000..d796ca3
--- /dev/null
+++ b/Rules/icecave/rule_icecave_006.tmx
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ H4sIAAAAAAAAA2NgwA2EkWhhLOL4ALp6YSzi2AAALrUFg5AAAAA=
+
+
+
+
+ H4sIAAAAAAAAA2NgIAzYCPCppQcEAItPfPCQAAAA
+
+
+
+
+ H4sIAAAAAAAAA2NgIAy4CPCppQcEAOTd4SuQAAAA
+
+
+
+
+ H4sIAAAAAAAAA2NgIAz4CPCppQcEAMFTlWKQAAAA
+
+
+
+
+ H4sIAAAAAAAAA2NgIAza0PilROiZisZvIUIPCAAASt1u8JAAAAA=
+
+
+
diff --git a/Rules/icecave/rule_icecave_007.tmx b/Rules/icecave/rule_icecave_007.tmx
new file mode 100644
index 0000000..a1670d4
--- /dev/null
+++ b/Rules/icecave/rule_icecave_007.tmx
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ H4sIAAAAAAAAA2NgIA8IQzGxbHL1DCe3AQDj6TXRbAEAAA==
+
+
+
+
+ H4sIAAAAAAAAA2NgoC5gg2J0NrI8A5o4LjWUuoMa9hDyDykAANyNKXlsAQAA
+
+
+
+
+ H4sIAAAAAAAAA2NgoC7ggmJ0NrI8A5o4LjWUuoMa9hDyDykAAN2upiFsAQAA
+
+
+
+
+ H4sIAAAAAAAAA2NgoC7gg2J0NrI8A5o4LjWUuoMa9hDyDykAACKwIxZsAQAA
+
+
+
+
+ H4sIAAAAAAAAA2NgoC4oQuMXo/FLoHQZle1FB1Og9DQKzWlC4zdTYBYAacXSPGwBAAA=
+
+
+
diff --git a/Rules/icecave/rule_icecave_009.tmx b/Rules/icecave/rule_icecave_009.tmx
new file mode 100644
index 0000000..31c7132
--- /dev/null
+++ b/Rules/icecave/rule_icecave_009.tmx
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ H4sIAAAAAAAAA2NgwA2EkWhhLOL4ALp6YSzi2AAALrUFg5AAAAA=
+
+
+
+
+ H4sIAAAAAAAAA2NgIAzYCfCppQcEAJIt/ZSQAAAA
+
+
+
+
+ H4sIAAAAAAAAA2NgIAy4CfCppQcEAP2/YE+QAAAA
+
+
+
+
+ H4sIAAAAAAAAA2NgIAz4CfCppQcEANgxFAaQAAAA
+
+
+
+
+ H4sIAAAAAAAAA2NgwA3YcbBJBaSYAwA8zLY9kAAAAA==
+
+
+
+
+ H4sIAAAAAAAAA2NgwA24cbBJBaSYAwCnbhfQkAAAAA==
+
+
+
+
+ H4sIAAAAAAAAA2NgwA34cbBJBaSYAwARDac9kAAAAA==
+
+
+
+
+ H4sIAAAAAAAAA2NgIAyM0fhKROhxQuMbEqEHBABmYFlZkAAAAA==
+
+
+
diff --git a/Rules/icecave/rule_icecave_010.tmx b/Rules/icecave/rule_icecave_010.tmx
new file mode 100644
index 0000000..13b9120
--- /dev/null
+++ b/Rules/icecave/rule_icecave_010.tmx
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ H4sIAAAAAAAAA2NgIA8IQzGxbHL1DCe3AQDj6TXRbAEAAA==
+
+
+
+
+ H4sIAAAAAAAAA2NgoC5gh2J0NrI8A5o4LjWUuoMa9hDyDykAANMIrO9sAQAA
+
+
+
+
+ H4sIAAAAAAAAA2NgoC7ghmJ0NrI8A5o4LjWUuoMa9hDyDykAANIrI7dsAQAA
+
+
+
+
+ H4sIAAAAAAAAA2NgoC7gh2J0NrI8A5o4LjWUuoMa9hDyDykAAC01poBsAQAA
+
+
+
+
+ H4sIAAAAAAAAA2NgIA+wQzE6m5AeXHx2HGrIAeTYQ45/SAUAzKE5fmwBAAA=
+
+
+
+
+ H4sIAAAAAAAAA2NgIA9wQzE6m5AeXHxuHGrIAeTYQ45/SAUATchkcWwBAAA=
+
+
+
+
+ H4sIAAAAAAAAA2NgIA/wQzE6m5AeXHx+HGrIAeTYQ45/SAUADe2AwmwBAAA=
+
+
+
+
+ H4sIAAAAAAAAA2NgoC4IQuMHo/EVobQyle1FB45Q2plCc5LQ+MkUmAUA1eCGkGwBAAA=
+
+
+
diff --git a/Rules/icecave/rule_icecave_020.tmx b/Rules/icecave/rule_icecave_020.tmx
new file mode 100644
index 0000000..ff74b40
--- /dev/null
+++ b/Rules/icecave/rule_icecave_020.tmx
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ H4sIAAAAAAAAA2NgIA4II2F6q8Gmhxg1hAAAKDCfLPwAAAA=
+
+
+
+
+ H4sIAAAAAAAAA2NgIA4wI2F6qBEkoAabHiES9QAA7GlTvPwAAAA=
+
+
+
+
+ H4sIAAAAAAAAA2NgIA5oI2FcwBaI7YDYHo+aECAOBeIwPGpSgDgViNOIdBsISCNhYgEAc8NUGPwAAAA=
+
+
+
+
+ H4sIAAAAAAAAA2NgGPxAH4gTgFgXjxp5INYDYgUSzAUA1uf1CPwAAAA=
+
+
+
+
+ H4sIAAAAAAAAA2NgoC9gQsL41DAQUEMNPQBW4FMC/AAAAA==
+
+
+
diff --git a/Rules/icecave/rule_icecave_100.tmx b/Rules/icecave/rule_icecave_100.tmx
new file mode 100644
index 0000000..a7c9936
--- /dev/null
+++ b/Rules/icecave/rule_icecave_100.tmx
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ H4sIAAAAAAAAA2NgIA4IEyEujEQLo/FJMQdZDJc5hAAAgNct+9gAAAA=
+
+
+
+
+ H4sIAAAAAAAAA2NgoB5gw8FmJkIvLjXE6MUGAC5qS5vYAAAA
+
+
+
+
+ H4sIAAAAAAAAA2NgoB7gwsFmJkIvLjXE6MUGAFQuCaTYAAAA
+
+
+
+
+ H4sIAAAAAAAAA2NgoB7gw8FmJkIvLjXE6MUGAL3v5wfYAAAA
+
+
+
+
+ H4sIAAAAAAAAA2NgoB4QIYJNjF5ixAkBAOvVVsfYAAAA
+
+
+
diff --git a/Rules/rule_cave.tmx b/Rules/rule_cave.tmx
new file mode 100644
index 0000000..e134398
--- /dev/null
+++ b/Rules/rule_cave.tmx
@@ -0,0 +1,375 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+31,31,0,31,31,0,31,31,0,31,31,0,31,31,0,31,31,0,0,31,0,31,0,0,0,0,0,0,0,0,0,0,
+31,31,0,31,31,0,31,31,0,31,31,0,31,31,0,31,31,0,31,31,0,31,70,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,0,31,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,70,0,0,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+75,0,0,0,0,0,0,0,70,70,0,70,70,0,0,0,0,0,0,70,70,70,0,0,70,70,0,0,0,0,0,0,
+75,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,0,0,70,70,0,0,0,0,0,0,
+75,0,70,70,0,70,70,0,70,0,0,70,70,0,0,0,0,0,0,70,0,0,0,0,70,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,70,70,0,0,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,70,0,0,70,70,0,0,0,0,0,0,
+70,0,70,0,70,70,0,70,70,0,0,0,0,0,0,0,0,0,0,70,70,70,0,0,70,70,0,0,0,0,0,0,
+70,0,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,0,0,0,70,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+70,70,0,70,70,0,70,70,0,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+70,70,0,70,70,0,70,70,0,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+70,70,0,70,70,0,70,70,0,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+70,70,0,70,70,0,70,70,0,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,0,0,70,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+31,0,0,0,31,0,31,0,0,0,31,0,31,0,0,0,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,0,70,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+70,0,0,70,0,70,0,0,0,70,0,0,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+70,0,0,70,0,70,0,0,0,70,0,0,0,0,0,0,0,0,0,0,70,0,0,0,0,185,0,0,0,0,0,0,
+70,0,0,70,0,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+70,0,0,70,0,70,0,0,70,0,0,0,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,0,0,0,70,0,0,0,0,0,0,0,
+0,0,70,0,70,0,0,0,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,70,0,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,70,0,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+70,0,0,0,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,70,0,0,0,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,0,0,70,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+2,2,0,2,2,0,2,2,0,2,2,0,2,146,0,146,2,0,0,2,0,2,0,0,0,0,0,0,0,0,0,0,
+2,146,0,146,2,0,146,146,0,146,146,0,146,146,0,146,146,0,146,2,0,2,146,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,2,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,11,0,0,146,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+146,0,0,0,0,0,0,0,146,50,0,146,11,0,0,0,0,0,0,146,146,146,0,0,146,2,0,0,0,0,0,0,
+146,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,146,11,11,0,0,11,2,0,0,0,0,0,0,
+2,0,50,2,0,2,50,0,11,0,0,2,146,0,0,0,0,0,0,11,0,0,0,0,11,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,50,146,0,0,11,146,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,146,146,146,0,0,2,146,0,0,0,0,0,0,
+2,0,114,0,114,2,0,2,114,0,0,0,0,0,0,0,0,0,0,11,11,146,0,0,2,11,0,0,0,0,0,0,
+114,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,11,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+2,113,0,115,2,0,130,114,0,114,130,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+98,114,0,114,98,0,2,113,0,115,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+130,2,0,2,113,0,2,2,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+2,2,0,2,2,0,115,2,0,2,98,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,114,0,0,146,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+114,114,0,114,114,0,114,114,0,114,114,0,114,0,0,0,114,0,0,114,0,114,0,0,0,0,0,0,0,0,0,0,
+114,0,0,0,114,0,0,0,0,0,0,0,0,0,0,0,0,0,0,114,0,114,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,114,0,114,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,50,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,50,0,0,0,0,0,0,0,
+50,0,0,0,0,49,0,51,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+51,97,0,99,49,0,129,114,0,114,131,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+97,114,0,114,99,0,0,129,0,131,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+115,0,0,0,130,0,49,50,0,50,51,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,98,49,0,51,113,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,146,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,50,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,146,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,
+49,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,50,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,98,0,98,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,50,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,50,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+51,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+50,0,0,51,0,49,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,130,0,115,0,0,0,113,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+98,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,101,0,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,117,0,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+131,0,0,0,129,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,99,0,0,0,97,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,114,0,0,146,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,59,0,58,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+177,0,0,0,180,0,179,0,0,0,178,0,152,0,0,0,153,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,147,0,145,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+162,0,0,163,0,161,0,0,0,147,0,0,149,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,43,0,42,0,0,0,147,0,0,0,0,0,0,0,0,0,0,149,0,0,0,149,163,0,0,0,0,0,0,
+0,0,0,70,0,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,145,0,0,0,148,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,148,0,0,0,161,148,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
+
+0,0,0,0,0,0,0,0,0,0,0,0,0,514,0,514,0,0,0,514,0,514,0,0,0,0,0,0,0,0,0,0,
+0,514,0,514,0,0,514,514,0,514,514,0,514,514,0,514,514,0,514,514,0,514,514,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,514,0,514,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,514,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+514,0,0,514,0,514,0,0,0,514,0,0,514,0,0,0,0,0,0,514,514,514,0,0,514,514,0,0,0,0,0,0,
+514,0,0,514,0,514,0,0,0,0,0,0,0,0,0,0,0,0,0,514,514,514,0,0,514,514,0,0,0,0,0,0,
+514,0,514,514,0,514,514,0,514,0,0,0,0,0,0,0,0,0,0,514,0,0,0,0,514,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,514,0,0,0,514,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,514,514,514,0,0,514,514,0,0,0,0,0,0,
+0,0,0,0,516,0,0,0,516,0,0,0,0,0,0,0,0,0,0,514,514,514,0,0,514,514,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,514,0,0,0,514,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,516,0,516,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,516,0,516,516,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,516,0,0,514,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+
+
+
diff --git a/Rules/rules.txt b/Rules/rules.txt
new file mode 100644
index 0000000..4bd5986
--- /dev/null
+++ b/Rules/rules.txt
@@ -0,0 +1,17 @@
+# Comment the rules which are not in use
+
+# Micksha Cave
+#caverules.txt
+
+# Cave Rules
+cave/rule_cave.txt
+
+# Castle Rules (bad tileset)
+#castle_indoor/rule_castle_indoor.txt
+
+# Ice Cave Rules
+#icecave/rule_icecave.txt
+
+# Desert Cliffs Rules
+#desertcliffs/rule_cave.txt
+
diff --git a/Rules/tilesets/set_castle_indoor.png b/Rules/tilesets/set_castle_indoor.png
new file mode 100644
index 0000000..7f02def
Binary files /dev/null and b/Rules/tilesets/set_castle_indoor.png differ
diff --git a/Rules/tilesets/set_cave.png b/Rules/tilesets/set_cave.png
new file mode 100644
index 0000000..aa6e1bf
Binary files /dev/null and b/Rules/tilesets/set_cave.png differ
diff --git a/Rules/tilesets/set_desertcliff.png b/Rules/tilesets/set_desertcliff.png
new file mode 100644
index 0000000..3ead83d
Binary files /dev/null and b/Rules/tilesets/set_desertcliff.png differ
diff --git a/Rules/tilesets/set_hlw.png b/Rules/tilesets/set_hlw.png
new file mode 100644
index 0000000..ec17f1e
Binary files /dev/null and b/Rules/tilesets/set_hlw.png differ
diff --git a/Rules/tilesets/set_icecave.png b/Rules/tilesets/set_icecave.png
new file mode 100644
index 0000000..8a1319b
Binary files /dev/null and b/Rules/tilesets/set_icecave.png differ
diff --git a/Rules/tilesets/set_icemountain.png b/Rules/tilesets/set_icemountain.png
new file mode 100644
index 0000000..7a8613e
Binary files /dev/null and b/Rules/tilesets/set_icemountain.png differ
diff --git a/Rules/tilesets/set_rules.png b/Rules/tilesets/set_rules.png
new file mode 100644
index 0000000..bf7f93c
Binary files /dev/null and b/Rules/tilesets/set_rules.png differ
diff --git a/Rules/tilesets/set_woodland.png b/Rules/tilesets/set_woodland.png
new file mode 100644
index 0000000..9661f07
Binary files /dev/null and b/Rules/tilesets/set_woodland.png differ
diff --git a/adler32/Makefile b/adler32/Makefile
new file mode 100644
index 0000000..42f101a
--- /dev/null
+++ b/adler32/Makefile
@@ -0,0 +1,16 @@
+prefix=/usr/local
+bindir=${prefix}/bin
+
+LDLIBS=-lz
+
+all: adler32
+
+adler32: adler32.c
+
+clean:
+ rm -f adler32
+
+install:
+ install -D adler32 ${bindir}/
+
+.PHONY: clean install
diff --git a/adler32/adler32 b/adler32/adler32
new file mode 100755
index 0000000..a934ff7
Binary files /dev/null and b/adler32/adler32 differ
diff --git a/adler32/adler32.c b/adler32/adler32.c
new file mode 100644
index 0000000..5dd7e4c
--- /dev/null
+++ b/adler32/adler32.c
@@ -0,0 +1,68 @@
+/*
+ * adler32.c (c) 2006 Bjorn Lindeijer
+ * License: GPL, v2 or later
+ *
+ * Calculates Adler-32 checksums for all files passed as argument.
+ *
+ * Usage: adler32 [file]...
+ */
+
+#include
+#include
+#include
+
+/**
+ * Calculates the Adler-32 checksum for the given file.
+ */
+unsigned long fadler32(FILE *file)
+{
+ // Obtain file size
+ fseek(file, 0, SEEK_END);
+ long fileSize = ftell(file);
+ rewind(file);
+
+ // Calculate Adler-32 checksum
+ char *buffer = (char*) malloc(fileSize);
+ fread(buffer, 1, fileSize, file);
+ unsigned long adler = adler32(0L, Z_NULL, 0);
+ adler = adler32(adler, (Bytef*) buffer, fileSize);
+ free(buffer);
+
+ return adler;
+}
+
+/**
+ * Prints out usage and exists.
+ */
+void print_usage()
+{
+ printf("Usage: adler32 [file]...\n");
+ exit(0);
+}
+
+int main(int argc, char *argv[])
+{
+ int i; /**< Loops through arguments. */
+
+ if (argc == 1)
+ {
+ print_usage();
+ }
+
+ for (i = 1; i < argc; ++i)
+ {
+ FILE *file = fopen(argv[i], "r");
+
+ if (!file)
+ {
+ printf("Error while opening '%s' for reading!\n", argv[i]);
+ exit(1);
+ }
+
+ unsigned long adler = fadler32(file);
+ printf("%s %lx\n", argv[i], adler);
+ fclose(file);
+ }
+
+ return 0;
+}
diff --git a/client/aurora.py b/client/aurora.py
new file mode 100755
index 0000000..38ed174
--- /dev/null
+++ b/client/aurora.py
@@ -0,0 +1,37 @@
+#!/usr/bin/python2.7
+
+# Setup
+events=["Expo", "Fishing", "Kamelot", "Regnum", "Raid", "Tower", "Mining",
+ "Anniversary", "Christmas", "Easter", "Patrick", "Thanksgiving", "Valentine",
+ "Olympics", "Candor", "Worker"]
+
+# Functions
+def headers(val):
+ return '\n\t\n\t\t\n' % val
+
+def navigation():
+ nav=""
+ nav+='\t\t\t \n'
+ return nav
+
+def tail():
+ return '\t\t \n\t \n'
+
+def data(val):
+ bf='\t\t\t \n' % val
+ return bf
+
+# Begin
+f=open("aurora.tmp", "w")
+
+f.write('\n\n')
+
+for evtc in sorted(events):
+ f.write(headers(evtc))
+ f.write(data(evtc))
+ f.write(navigation())
+ f.write(tail())
+
+f.write('\n ')
+f.close()
+
diff --git a/client/dailylogin.py b/client/dailylogin.py
new file mode 100755
index 0000000..52c945d
--- /dev/null
+++ b/client/dailylogin.py
@@ -0,0 +1,86 @@
+#!/usr/bin/python2.7
+
+# Setup
+x=y=0
+i=j=0
+
+# Functions
+def headers(val):
+ return '\n\t\n\t\t\n\t\t\t \n \n\n' % val
+
+def tail():
+ return '\n\t\t \n\t \n'
+
+def override_check(vl, over, text1, text2):
+ global j
+ if not over:
+ j=0
+ return '\t\t\t \n' % (text1, text2)
+ else:
+ j+=1
+ if (j == vl):
+ return '\t\t\t \n' % (text1)
+ elif (j < vl):
+ return '\t\t\t \n' % (text1)
+ else:
+ return ""
+
+def spammer(val, override=False):
+ bf=""
+
+ bf+=override_check(val, override, 'x="35" y="35"', 'jexp')
+ bf+=override_check(val, override, 'x="70" y="35"', 'bexp')
+ bf+=override_check(val, override, 'x="105" y="35"', 'sc')
+ bf+=override_check(val, override, 'x="140" y="35"', 'jexp')
+ bf+=override_check(val, override, 'x="175" y="35"', 'bexp')
+
+ bf+=override_check(val, override, 'x="35" y="70"', 'gp')
+ bf+=override_check(val, override, 'x="70" y="70"', 'sc')
+ bf+=override_check(val, override, 'x="105" y="70"', 'bexp')
+ bf+=override_check(val, override, 'x="140" y="70"', 'gp')
+ bf+=override_check(val, override, 'x="175" y="70"', 'jexp')
+
+ bf+=override_check(val, override, 'x="35" y="105"', 'bexp')
+ bf+=override_check(val, override, 'x="70" y="105"', 'gp')
+ bf+=override_check(val, override, 'x="105" y="105"', 'jexp')
+ bf+=override_check(val, override, 'x="140" y="105"', 'gift')
+ bf+=override_check(val, override, 'x="175" y="105"', 'gp')
+
+ bf+=override_check(val, override, 'x="35" y="140"', 'jexp')
+ bf+=override_check(val, override, 'x="70" y="140"', 'bexp')
+ bf+=override_check(val, override, 'x="105" y="140"', 'gp')
+ bf+=override_check(val, override, 'x="140" y="140"', 'jexp')
+ bf+=override_check(val, override, 'x="175" y="140"', 'bexp')
+
+ bf+=override_check(val, override, 'x="35" y="175"', 'gift')
+ bf+=override_check(val, override, 'x="70" y="175"', 'jexp')
+ bf+=override_check(val, override, 'x="105" y="175"', 'bexp')
+ bf+=override_check(val, override, 'x="140" y="175"', 'gp')
+ bf+=override_check(val, override, 'x="175" y="175"', 'jexp')
+
+ bf+=override_check(val, override, 'x="35" y="210"', 'bexp')
+ bf+=override_check(val, override, 'x="70" y="210"', 'last')
+ bf+=override_check(val, override, 'x="105" y="210"', 'sc')
+ bf+=override_check(val, override, 'x="140" y="210"', 'sc')
+ bf+=override_check(val, override, 'x="175" y="210"', 'sc')
+
+ bf+=override_check(val, override, 'x="35" y="245"', 'sc')
+
+ return bf
+
+# Begin
+f=open("daily.tmp", "w")
+
+f.write('\n\n')
+
+while (i < 31):
+ i+=1
+ f.write(headers(i))
+ f.write(spammer(i, False))
+ f.write('\n\t\t\t \n\n\t\t\t\n')
+ f.write(spammer(i, True))
+ f.write(tail())
+
+f.write('\n ')
+f.close()
+
diff --git a/client/magicacademy.py b/client/magicacademy.py
new file mode 100755
index 0000000..c6ee44c
--- /dev/null
+++ b/client/magicacademy.py
@@ -0,0 +1,60 @@
+#!/usr/bin/python2.7
+
+# Setup
+class dlist(list):
+ def __setitem__(self, index, value):
+ size = len(self)
+ if index >= size:
+ self.extend(None for _ in range(size, index + 1))
+ list.__setitem__(self, index, value)
+
+class Skill:
+ def __init__(self, ID, NAME, DESC):
+ self.id=ID
+ self.name=NAME
+ self.desc=DESC
+
+def skill_tree(sk1=None, sk2=None, sk3=None, sk4=None, sk5=None):
+ return [sk1, sk2, sk3, sk4, sk5]
+
+academy={}
+
+# Skill shortcuts
+SK_HEAL=Skill("heal", "Healing", "Heals target")
+SK_ABHEAL=Skill("abheal", "High Healing", "Supremely heals target")
+
+# Scholarship
+academy["scholarship"]=dlist()
+academy["scholarship"].append(skill_tree(SK_HEAL, None, SK_ABHEAL, None, None))
+
+
+# Functions
+def headers(classe, val):
+ return '\n\t\n\t\t\n' % (classe, val)
+
+def navigation():
+ nav=""
+ nav+='\t\t\t \n'
+ return nav
+
+def tail():
+ return '\t\t \n\t \n'
+
+def data(val):
+ bf='\t\t\t \n' % val
+ return bf
+
+# Begin
+f=open("aurora.tmp", "w")
+
+f.write('\n\n')
+
+for evtc in sorted(events):
+ f.write(headers("none", evtc))
+ f.write(data(evtc))
+ f.write(navigation())
+ f.write(tail())
+
+f.write('\n ')
+f.close()
+
diff --git a/client/minimap-dyecmd.py b/client/minimap-dyecmd.py
new file mode 100755
index 0000000..c43a4e4
--- /dev/null
+++ b/client/minimap-dyecmd.py
@@ -0,0 +1,17 @@
+#!/usr/bin/env python
+#-*- coding:utf-8 -*-
+
+import os
+f=open("minimap-dyecmd.sh", "w");
+CLIENT_DATA_ROOT="../../clientdata"
+
+map_names = sorted([os.path.splitext(p)[0] for p in os.listdir(os.path.join(CLIENT_DATA_ROOT, u'graphics', u'minimaps'))])
+
+f.write('cd '+os.path.join(CLIENT_DATA_ROOT, u'graphics', u'minimaps')+'\n')
+for c in map_names:
+ if os.path.exists(os.path.join(CLIENT_DATA_ROOT, u'graphics', u'minimaps', c+u'.png')):
+ i=c+u'.png'
+ f.write("dyecmd %s tmp ; mv tmp %s\n" % (i, i))
+ f.write("echo \"Converted "+i+" successfully.\"\n")
+
+f.close()
diff --git a/client/minimap-dyecmd.sh b/client/minimap-dyecmd.sh
new file mode 100755
index 0000000..e69de29
diff --git a/client/minimap-override/none.png b/client/minimap-override/none.png
new file mode 100644
index 0000000..539e992
Binary files /dev/null and b/client/minimap-override/none.png differ
diff --git a/client/minimap-render.py b/client/minimap-render.py
new file mode 100755
index 0000000..9d4bac1
--- /dev/null
+++ b/client/minimap-render.py
@@ -0,0 +1,171 @@
+#!/usr/bin/env python
+#-*- coding:utf-8 -*-
+
+import sys
+import os
+import subprocess
+import tempfile
+
+CLIENT_DATA_ROOT = os.path.realpath(
+ os.path.join(
+ os.path.dirname(__file__),
+ u'../../clientdata',
+ )
+)
+
+SKIP_DOMAIN="030-"
+
+class MinimapRenderer(object):
+
+ PROGRAMS = {
+ 'default': {
+ 'tmxrasterizer': 'tmxrasterizer',
+ 'im_convert': 'convert',
+ },
+ 'win32': {
+ 'tmxrasterizer': 'tmxrasterizer.exe',
+ 'im_convert': 'convert.exe',
+ },
+ }
+
+ def __init__(self, map_name, tilesize, useAntiAliasing):
+ self.map_name = map_name
+ self.tilesize = tilesize
+ self.useAntiAliasing = useAntiAliasing
+
+ def render(self):
+ """
+ Processes a map
+ """
+ if not os.path.exists(os.path.join(CLIENT_DATA_ROOT, u'maps', self.map_name+u'.tmx')):
+ sys.stderr.write(u'Invalid map name: %s. Skipping.\n' % self.map_name)
+ return 1
+ if not self.map_name.endswith(u'.tmx'):
+ self.map_name = self.map_name+u'.tmx'
+ if SKIP_DOMAIN in self.map_name:
+ print(u'Skipping %s (regex)...\n' % self.map_name)
+ return 0
+
+ map_number = os.path.splitext(os.path.basename(self.map_name))[0]
+ tmx_file = os.path.join(CLIENT_DATA_ROOT, u'maps', self.map_name)
+ minimap_file = os.path.join(CLIENT_DATA_ROOT, u'graphics', u'minimaps', map_number+u'.png')
+
+ prefix = os.path.commonprefix((tmx_file, minimap_file))
+ sys.stdout.write(u'%s -> %s\n' % (os.path.relpath(tmx_file, prefix), os.path.relpath(minimap_file, prefix)))
+
+ try:
+ self.do_render(tmx_file, minimap_file)
+ except Exception as e:
+ sys.stderr.write(u'\x1b[31m\x1b[1mError while rendering %s: %s\x1b[0m\n' % (self.map_name, e))
+ return 1
+ else:
+ return 0
+
+ def do_render(self, tmx_file, bitmap_file):
+ """
+ The map rendering implementation
+ """
+ platform_programs = MinimapRenderer.PROGRAMS.get(sys.platform, MinimapRenderer.PROGRAMS.get('default'))
+ # tmx rasterize
+ mrf, map_raster = tempfile.mkstemp(suffix='.png')
+ tmxrasterizer_cmd = [
+ #platform_programs.get('tmxrasterizer'),
+ '../../tiled/default/install-root/usr/local/bin/tmxrasterizer',
+ '--tilesize', str(self.tilesize), '--hide-layer', 'Collision','--hide-layer', 'Height','--hide-layer', 'Collisions','--hide-layer', 'collision','--hide-layer', 'height','--hide-layer', 'Heights', '--hide-layer', 'height', '--ignore-visibility', '--hide-object-layers',
+ ]
+ if self.useAntiAliasing:
+ tmxrasterizer_cmd.append('--anti-aliasing')
+ tmxrasterizer_cmd += [tmx_file, map_raster]
+ subprocess.check_call(tmxrasterizer_cmd)
+ if os.stat(map_raster).st_size == 0:
+ raise Exception('A problem was encountered when rendering a map')
+ # add cell-shading to the minimap to improve readability
+ ebf, edges_bitmap = tempfile.mkstemp(suffix='.png')
+ subprocess.check_call([
+ platform_programs.get('im_convert'), map_raster,
+ '-set', 'option:convolve:scale', '-1!',
+ '-morphology', 'Convolve', 'Laplacian:0',
+ '-colorspace', 'gray',
+ '-auto-level',
+ '-threshold', '2.8%',
+ '-negate',
+ '-transparent', 'white',
+ edges_bitmap
+ ])
+ subprocess.check_call([
+ platform_programs.get('im_convert'), map_raster, edges_bitmap,
+ '-compose', 'Dissolve',
+ '-define', 'compose:args=35',
+ '-composite',
+ bitmap_file
+ ])
+ os.unlink(map_raster)
+ os.unlink(edges_bitmap)
+
+ @staticmethod
+ def check_programs():
+ """
+ Checks the require programs are available
+ """
+ def which(program):
+ import os
+ def is_exe(fpath):
+ return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
+ fpath, fname = os.path.split(program)
+ if fpath:
+ if is_exe(program):
+ return program
+ else:
+ for path in os.environ["PATH"].split(os.pathsep):
+ exe_file = os.path.join(path, program)
+ if is_exe(exe_file):
+ return exe_file
+ return None
+
+ platform_programs = MinimapRenderer.PROGRAMS.get(sys.platform, MinimapRenderer.PROGRAMS.get('default'))
+ for program in platform_programs.values():
+ if not which(program):
+ raise Exception('The required "%s" program is missing from your PATH.' % program)
+
+def usage():
+ sys.stderr.write(u'''Usage: %s MAP_NAME...
+
+ Example:
+ $ ./minimap-render.py 007-1
+ will render the map at maps/007-1.tmx in the graphics/minimaps directory.
+ $ ./minimap-render.py all
+ will render all existing maps found in the maps directory.
+ $ ./minimap-render.py update
+ will update all existing minimaps found in the graphics/minimaps directory.
+
+ For convenience,
+ $ ./minimap-render.py 007-1.tmx
+ is also accepted.
+ \n''' % sys.argv[0])
+
+def main():
+ if not len(sys.argv) > 1:
+ usage()
+ return 127
+ try:
+ MinimapRenderer.check_programs()
+ except Exception as e:
+ sys.stderr.write(u'%s\n' % e)
+ return 126
+
+ status = 0
+ if sys.argv[1].lower() == 'all':
+ map_names = sorted([os.path.splitext(p)[0] for p in os.listdir(os.path.join(CLIENT_DATA_ROOT, u'maps'))])
+ elif sys.argv[1].lower() == 'update':
+ map_names = sorted([os.path.splitext(p)[0] for p in os.listdir(os.path.join(CLIENT_DATA_ROOT, u'graphics', u'minimaps'))])
+ else:
+ map_names = sys.argv[1:]
+
+ for map_name in map_names:
+ # Render tiles at 1 pixel size
+ map_renderer = MinimapRenderer(map_name, 1, True)
+ status += map_renderer.render()
+ return 0
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/client/skills.py b/client/skills.py
new file mode 100755
index 0000000..bfe59f3
--- /dev/null
+++ b/client/skills.py
@@ -0,0 +1,137 @@
+#!/usr/bin/python2.7
+
+# Setup
+x=y=0
+i=j=0
+
+skills=[]
+
+class Skill:
+ def __init__(self, sid, name, icon, desc, bmp, amp, cmd, maxlv):
+ self.id=sid
+ self.lv=maxlv
+ self.name=name
+ self.icon=icon
+ self.cmd=cmd
+
+ self.desc=desc
+ self.bmp=bmp
+ self.amp=amp
+
+def fillskill(sk, lv):
+ sid=sk.id
+ name=sk.name
+ icon=sk.icon
+ desc=sk.desc
+ bmp=sk.bmp
+ amp=sk.amp
+ cmd=sk.cmd
+
+ if lv > 0:
+ lvstr='\t\t\tlevel="%d"\n' % lv
+ mpstr='%d MP. ' % int(bmp+(amp*(lv-1)))
+ cmdstr='\t\t\tinvokeCmd="@sk-%s"\n' % cmd
+ else:
+ lvstr=''
+ lv=1
+ mpstr=''
+ cmdstr=''
+
+ msg='\
+\t\t \n' % (sid, name, icon, mpstr, desc, cmdstr, lvstr)
+ return msg
+
+
+
+
+
+
+
+
+
+
+
+# Declare the skills
+
+#########################
+### Transmutation Skills
+#########################
+#skills.append(Skill(20024, "Parum", "other/parum", "Transmutate wood into stuff.",
+#50, 0, "parum", 0))
+#skills.append(Skill(20027, "Transmutation", "transmutation", "Transmute stuff into other stuff.",
+#215, -5, "trans", 10))
+
+#########################
+### Summon Skills
+#########################
+skills.append(Skill(20025, "Summon Maggots", "other/kalmurk", "2x Maggot Slime.",
+40, 5, "kalmurk", 0))
+skills.append(Skill(20029, "Summon Dragon", "none", "4x Dragon Scale.",
+50, 4, "dragokin", 0))
+skills.append(Skill(20030, "Summon Slimes", "none", "15x Maggot Slime.",
+30, 3, "limerizer", 0))
+skills.append(Skill(20043, "Summon Fluffies", "none", "1x White Fur.",
+25, 4, "cuteheart", 0))
+skills.append(Skill(20042, "Summon Spiky", "none", "1x Mushroom Spores.",
+25, 5, "kalspike", 0))
+skills.append(Skill(20041, "Summon Mouboo", "none", "1x Mouboo Figurine.",
+25, 5, "kalboo", 0))
+skills.append(Skill(20036, "Summon Snakes", "none", "1x Snake Egg.",
+35, 6, "halhiss", 0))
+skills.append(Skill(20037, "Summon Wolverns", "none", "5x White Fur.",
+45, 5, "kalwulf", 0))
+skills.append(Skill(20038, "Summon Fairies", "none", "1x Fluo Powder.",
+40, 4, "fairykingdom", 0))
+skills.append(Skill(20039, "Summon Yetis", "none", "1x Frozen Yeti Tear.",
+37, 5, "frozenheart", 0))
+skills.append(Skill(20040, "Summon Terranite", "none", "1x Terranite Ore.",
+47, 5, "stoneheart", 0))
+skills.append(Skill(20044, "Summon Plants", "none", "2x Root.",
+30, 3, "plantkingdom", 5))
+skills.append(Skill(20047, "Summon Ducks", "none", "1x Cherry Cake. Req. Rubber Ducky.",
+40, 7, "ducky", 0))
+skills.append(Skill(20049, "Summon Pixies", "none", "3x Fluo Powder.",
+40, 4, "fairyempire", 0))
+
+skills.append(Skill(20023, "Summon Cave Maggot", "none", "Req. Zarkor Scroll.",
+40, 7, "zarkor", 0))
+
+
+
+
+
+
+
+
+
+
+
+
+# Begin
+f=open("skills.tmp", "w")
+
+f.write('\n\n\n\t\n')
+
+for sk in skills:
+ i=0
+ while (i < sk.lv):
+ i+=1
+ f.write(fillskill(sk, i))
+
+ # Fill the fallback
+ if (int(sk.lv)):
+ sk.desc="MP + "+str(sk.amp)+"/lv. "+sk.desc
+ f.write(fillskill(sk, -1))
+ f.write("\n")
+
+# We're done
+f.write('\n\t \n ')
+f.close()
+
diff --git a/client/tmxrasterizer b/client/tmxrasterizer
new file mode 100755
index 0000000..f079132
Binary files /dev/null and b/client/tmxrasterizer differ
diff --git a/client/weapons.py b/client/weapons.py
new file mode 100755
index 0000000..cc45d01
--- /dev/null
+++ b/client/weapons.py
@@ -0,0 +1,89 @@
+#!/usr/bin/python2.7
+
+class Item:
+ def __init__(self, xid):
+ self.id=xid
+ self.lvl=0
+
+a=open("../../clientdata/items.xml", "r")
+
+swords=[]
+bows=[]
+shields=[]
+
+gid="0"
+rid=0
+ctx=Item(0)
+mem=[]
+
+for l in a:
+ if "- = 2700 and rid <= 2899:
+ shields.append(rid)
+ elif rid >= 3500 and rid <= 3999:
+ swords.append(rid)
+ elif rid >= 6000 and rid <= 6499:
+ bows.append(rid)
+
+a.close()
+
+#shields=sorted(shields, reverse=True)
+#bows=sorted(bows, reverse=True)
+#swords=sorted(swords, reverse=True)
+
+b=open("weapons.tmp", "w")
+
+b.write('\n\
+\n\
+\n\
+
\n')
+
+b.write(' \n')
+
+for i in swords:
+ b.write(' \n' % i)
+
+b.write(' \n \n')
+
+for i in bows:
+ b.write(' \n' % i)
+
+b.write(' \n \n')
+
+for i in shields:
+ b.write(' \n' % i)
+
+b.write(' \n ')
+
+b.close()
diff --git a/hercules/.gitignore b/hercules/.gitignore
new file mode 100644
index 0000000..7eeffc2
--- /dev/null
+++ b/hercules/.gitignore
@@ -0,0 +1,12 @@
+clientdata
+maps
+serverdata
+oldserverdata
+newserverdata
+mapcache
+olddb
+newdb
+rodata
+tools/luadec
+
+*.pyc
diff --git a/hercules/code/__init__.py b/hercules/code/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/hercules/code/clienttoserver/__init__.py b/hercules/code/clienttoserver/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/hercules/code/clienttoserver/maps.py b/hercules/code/clienttoserver/maps.py
new file mode 100644
index 0000000..69ef4ef
--- /dev/null
+++ b/hercules/code/clienttoserver/maps.py
@@ -0,0 +1,190 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+import array
+import csv
+import hashlib
+import os
+import zlib
+import struct
+import StringIO
+from xml.dom import minidom
+
+from code.fileutils import makeDir, writeInt16, writeInt32, writeData, removeDir
+
+def getTmxFiles(srcDir):
+ names = []
+ for name in os.listdir(srcDir):
+ names.append(name)
+ names.sort()
+ for name in names:
+ fileName = srcDir + name
+ if os.path.isfile(fileName) == False:
+ continue
+ if name.endswith(".tmx") == False:
+ continue
+ yield (fileName, name)
+
+def findFirstGid(tilesets, tile):
+ found = -1
+ for t in tilesets:
+ if t <= tile:
+ found = t
+ break
+ return found
+
+# client
+# 0 - walkable ground
+# 1 - non walkable wall
+# 2 - air allowed shootable too
+# 3 - water allowed water, shootable too
+# 4 - sit, walkable ground
+# 5 - player walk not allowed
+# 6 - monster walk not allowed
+
+# server
+# 0 - walkable ground
+# 1 - non walkable wall
+# 2 - air allowed shootable too
+# 3 - water allowed water, shootable too
+# 4 - sit, walkable ground
+# 5 - none
+# 6 - monster walk not allowed
+def convertTileType(tile, idx, width, height):
+ if tile == 5:
+ tile = 0;
+ if tile > 6 or tile < 0:
+ y = int(idx / width)
+ x = idx - y * width
+ print "Error: wrong tile: ({0}, {1}) = {2}".format(x, y, tile)
+ tile = 1
+ return tile
+
+# map file format
+#
+# int16 version;
+# uint8 md5_checksum[16];
+# int16 xs;
+# int16 ys;
+# int32 len;
+# ...data...
+
+def recreateMap(names):
+ collisionLayerName = "collision"
+ destDir = "../../serverdata/maps/re/"
+ tmxName = names[0]
+ mCaheName = destDir + names[1][:-3] + "mcache"
+ with open(mCaheName, "wb") as w:
+ print tmxName
+ dom = minidom.parse(tmxName)
+ root = dom.documentElement
+ tilesets = []
+ for tileset in root.getElementsByTagName("tileset"):
+ tilesets.append(int(tileset.attributes["firstgid"].value))
+ tilesets.sort(reverse = True)
+ found = False
+ for layer in root.getElementsByTagName("layer"):
+ if layer.attributes["name"].value.lower() == collisionLayerName:
+ data = layer.getElementsByTagName("data")
+ if data is None or len(data) != 1:
+ continue
+ data = data[0]
+ width = int(layer.attributes["width"].value)
+ height = int(layer.attributes["height"].value)
+ encoding = data.attributes["encoding"].value
+ try:
+ compression = data.attributes["compression"].value
+ except:
+ compression = ""
+
+ tiles = []
+ if encoding == "base64":
+ 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("I")
+ arr.fromstring(layerData)
+ idx = 0
+ for tile in arr:
+ if tile == 0:
+ tileType = 0
+ else:
+ firstgid = findFirstGid(tilesets, tile)
+ tileType = convertTileType(tile - firstgid, idx, width, height);
+ tiles.append(tileType)
+ idx = idx + 1
+ elif encoding == "csv":
+ binData = data.childNodes[0].data.strip()
+ f = StringIO.StringIO(binData)
+ arr = list(csv.reader(f, delimiter=',', quotechar='|'))
+ idx = 0
+ for row in arr:
+ for item in row:
+ if item != "":
+ tile = int(item)
+ if tile == 0:
+ tileType = 0
+ else:
+ firstgid = findFirstGid(tilesets, tile)
+ tileType = convertTileType(tile - firstgid, idx, width, height);
+ # tmx collision format
+ # 0 - walkable ground
+ # 1 - non walkable wall
+ # 2 - air allowed shootable too
+ # 3 - water allowed water, shootable too
+ # 4 - sit, walkable ground
+ # 5 - none
+
+ # server collision format
+ # 000 0 - walkable, shootable
+ # 001 1 - wall
+ # 010 2 - same with 0
+ # 011 3 - walkable, shootable, water
+ # 100 4 - same with 0
+ # 101 5 - shootable
+ # 110 6 - same with 0
+ # 111 7 - none
+ if tileType > 128 or tileType < 0:
+ tileType = 1
+ tiles.append(tileType)
+ idx = idx + 1
+ f.close()
+ else:
+ print "map format not supported: " + tmxName
+ continue
+
+ #comp = zlib.compressobj()
+ binData = struct.pack(str(len(tiles))+"B", *tiles)
+ binData = zlib.compress(binData)
+ writeInt16(w, 1)
+ writeData(w, hashlib.md5(binData).digest()) # 16 bytes
+ writeInt16(w, width)
+ writeInt16(w, height)
+ writeInt32(w, len(binData))
+ writeData(w, binData)
+ print tmxName
+ found = True
+ break
+ if found == False:
+ print "Error: missing collision layer in file: {0}".format(tmxName)
+ return
+ return
+ with open(mCaheName + ".debug", "wb") as w:
+ writeInt16(w, width)
+ writeInt16(w, height)
+ writeData(w, struct.pack(str(len(tiles))+"B", *tiles))
+
+
+def recreateMapCache():
+ destDir = "../../serverdata/maps/re/"
+ srcDir = "../../clientdata/maps/"
+ removeDir(destDir)
+ makeDir(destDir)
+ for names in getTmxFiles(srcDir):
+ recreateMap(names)
diff --git a/hercules/code/configutils.py b/hercules/code/configutils.py
new file mode 100644
index 0000000..6d9ddb2
--- /dev/null
+++ b/hercules/code/configutils.py
@@ -0,0 +1,51 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+def writeIntField(w, name, value):
+ if value == "":
+ value = "0"
+ w.write(" {0}: {1}\n".format(name, value))
+
+def writeIntField2(w, name, value):
+ if value == "":
+ value = "0"
+ w.write(" {0}: {1}\n".format(name, value))
+
+def writeStrField(w, name, value):
+ w.write(" {0}: \"{1}\"\n".format(name, value))
+
+def writeCondField2(w, cond, name):
+ if cond != 0:
+ w.write(" {0}: true\n".format(name))
+
+def writeSubField(w, name, value):
+ w.write(" {0}: {1}\n".format(name, value))
+
+def writeFieldArr(w, name, value, value2):
+ w.write(" {0}: [{1}, {2}]\n".format(name, value, value2))
+
+def writeFieldList(w, name, value, value2):
+ w.write(" {0}: ({1}, {2})\n".format(name, value, value2))
+
+def writeStartBlock(w, text):
+ w.write(" {0}: {{\n".format(text))
+
+def writeEndBlock(w):
+ w.write(" }\n")
+
+def writeStartScript(w, name):
+ w.write(" {0}: <\"\n".format(name))
+
+def writeEndScript(w):
+ w.write(" \">\n")
+
+def isHaveData(fields, start, cnt):
+ for f in range(0, cnt):
+ value = fields[start + f * 2]
+ chance = fields[start + f * 2]
+ if value == "" or value == "0" or chance == "" or chance == "0":
+ continue
+ return True
+ return False
diff --git a/hercules/code/fileutils.py b/hercules/code/fileutils.py
new file mode 100644
index 0000000..a0635d4
--- /dev/null
+++ b/hercules/code/fileutils.py
@@ -0,0 +1,86 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+import array
+import os
+import struct
+import shutil
+
+def readInt32(f):
+ data = f.read(4)
+ arr = array.array("I")
+ arr.fromstring(data)
+ return arr[0]
+
+def readInt16(f):
+ data = f.read(2)
+ arr = array.array("H")
+ arr.fromstring(data)
+ return arr[0]
+
+def readInt8(f):
+ data = f.read(1)
+ arr = array.array("B")
+ arr.fromstring(data)
+ return arr[0]
+
+def readMapName(f):
+ data = f.read(12)
+ data = str(data)
+ while data[-1] == '\x00':
+ data = data[:-1]
+ return data
+
+def skipData(f, sz):
+ f.read(sz)
+
+def readData(f, sz):
+ return f.read(sz)
+
+def readFile(path):
+ with open(path, "r") as f:
+ return f.read()
+
+def writeInt32(f, i):
+ f.write(struct.pack("I", i))
+
+def writeInt16(f, i):
+ f.write(struct.pack("H", i))
+
+def writeMapName(f, name):
+ if len(name) > 12:
+ name = name[:12]
+ while len(name) < 12:
+ name = name + '\x00'
+ f.write(struct.pack("12s", name))
+
+def writeData(f, data):
+ f.write(data)
+
+def copyFile(src, dst, name):
+ shutil.copyfile(src + name, dst + name)
+
+def saveFile(fileName, data):
+ with open(fileName, "w") as w:
+ w.write(data)
+
+def makeDir(path):
+ if not os.path.exists(path):
+ os.makedirs(path)
+
+def removeDir(path):
+ if os.path.exists(path):
+ shutil.rmtree(path)
+
+def removeAllFiles(path):
+ if os.path.exists(path):
+ shutil.rmtree(path)
+
+def findFileIn(names, dirs):
+ for name in names:
+ for path in dirs:
+ if os.path.exists(path + name):
+ return path
+ return None
diff --git a/hercules/code/server/__init__.py b/hercules/code/server/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/hercules/code/server/account.py b/hercules/code/server/account.py
new file mode 100644
index 0000000..7763766
--- /dev/null
+++ b/hercules/code/server/account.py
@@ -0,0 +1,63 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+import re
+
+from code.fileutils import readFile
+from code.stringutils import stripNewLine, escapeSqlStr
+
+def convertSex(sex):
+ if sex == "M" or sex == "F" or sex == "S":
+ return sex
+ return "M"
+
+def convertAccount():
+ srcFile = "olddb/account.txt"
+ dstFile = "newdb/login.sql"
+ fieldsSplit = re.compile("\t")
+ tpl = readFile("templates/login.sql")
+ firstLine = True
+ with open(dstFile, "w") as w:
+ w.write(tpl)
+ w.write("INSERT INTO `login` VALUES ")
+ with open(srcFile, "r") as r:
+ for line in r:
+ if line[:2] == "//":
+ continue
+ rows = fieldsSplit.split(line)
+ if len(rows) == 2:
+ continue
+ if len(rows) != 14:
+ print "wrong account.txt line: " + stripNewLine(line)
+ continue
+
+ if firstLine == False:
+ w.write(",\n")
+ else:
+ firstLine = False
+
+ w.write(("({account_id},'{userid}','{user_pass}','{sex}'," +
+ "'{email}',{group_id},{state},{unban_time}," +
+ "{expiration_time},{logincount},'{lastlogin}'," +
+ "'{last_ip}','{birthdate}',{character_slots}," +
+ "'{pincode}',{pincode_change})").format(
+ account_id = rows[0],
+ userid = escapeSqlStr(rows[1]),
+ user_pass = escapeSqlStr(rows[2]),
+ sex = convertSex(rows[4]),
+ email = escapeSqlStr(rows[7]),
+ group_id = 0,
+ state = 0,
+ unban_time = rows[12],
+ expiration_time = rows[9],
+ logincount = rows[5],
+ lastlogin = rows[3],
+ last_ip = rows[10],
+ birthdate = '0000-00-00',
+ character_slots = 0,
+ pincode = '',
+ pincode_change = 0
+ ))
+ w.write("\n")
diff --git a/hercules/code/server/accreg.py b/hercules/code/server/accreg.py
new file mode 100644
index 0000000..b69910f
--- /dev/null
+++ b/hercules/code/server/accreg.py
@@ -0,0 +1,52 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+import re
+
+from code.fileutils import readFile
+from code.stringutils import stripNewLine
+
+def convertAccReg():
+ srcFile = "olddb/accreg.txt"
+ dstFile = "newdb/acc_reg_num_db.sql"
+ fieldsSplit = re.compile("\t")
+ comaSplit = re.compile(",")
+ spaceSplit = re.compile(" ")
+ tpl = readFile("templates/acc_reg_num_db.sql")
+ firstLine = True
+ with open(dstFile, "w") as w:
+ w.write(tpl)
+ w.write("INSERT INTO `acc_reg_num_db` VALUES ")
+ with open(srcFile, "r") as r:
+ for line in r:
+ if line[:2] == "//":
+ continue
+ line = stripNewLine(line)
+ rows = fieldsSplit.split(line)
+ if len(rows) != 2:
+ print "wrong accreg.txt line: " + line
+ continue
+
+ accountId = rows[0]
+
+ data = spaceSplit.split(rows[1])
+ for varStr in data:
+ if varStr == "":
+ continue
+
+ tmp = comaSplit.split(varStr)
+
+ if firstLine == False:
+ w.write(",\n")
+ else:
+ firstLine = False
+
+ w.write(("({account_id},'{key}',{index},{value})").format(
+ account_id = accountId,
+ key = tmp[0],
+ index = "0",
+ value = tmp[1]
+ ))
+ w.write("\n")
diff --git a/hercules/code/server/db/__init__.py b/hercules/code/server/db/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/hercules/code/server/db/char.py b/hercules/code/server/db/char.py
new file mode 100644
index 0000000..cd8e0cc
--- /dev/null
+++ b/hercules/code/server/db/char.py
@@ -0,0 +1,187 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2015 Evol Online
+# Author: Andrei Karas (4144)
+
+from code.fileutils import readFile
+from code.stringutils import escapeSqlStr
+
+def saveCharTable(users):
+ dstFile = "newdb/char.sql"
+ firstLine = True
+ tpl = readFile("templates/char.sql")
+ with open(dstFile, "w") as w:
+ w.write(tpl)
+ w.write("INSERT INTO `char` VALUES ")
+ for userId in users:
+ user = users[userId]
+
+ if firstLine == False:
+ w.write(",\n")
+ else:
+ firstLine = False
+
+ w.write(("({char_id},{account_id},{char_num},'{name}',{CLASS}," +
+ "{base_level},{job_level},{base_exp},{job_exp},{zeny}," +
+ "{str},{agi},{vit},{INT},{dex},{luk},{max_hp},{hp},{max_sp}," +
+ "{sp},{status_point},{skill_point},{option},{karma},{manner}," +
+ "{party_id},{guild_id},{pet_id},{homun_id},{elemental_id}," +
+ "{hair},{hair_color},{clothes_color},{body},{weapon},{shield}," +
+ "{head_top},{head_mid},{head_bottom},{robe}," +
+ "'{last_map}',{last_x},{last_y},'{save_map}',{save_x},{save_y}," +
+ "{partner_id},{online},{father},{mother},{child},{fame}," +
+ "{rename},{delete_date},{slotchange},{char_opt},{font}," +
+ "{unban_time},{uniqueitem_counter},'{sex}',{hotkey_rowshift})").format(
+ char_id = user.char_id,
+ account_id = user.account_id,
+ char_num = user.char_num,
+ name = escapeSqlStr(user.char_name),
+ CLASS = user.char_class,
+ base_level = user.base_level,
+ job_level = user.job_level,
+ base_exp = user.base_exp,
+ job_exp = user.job_exp,
+ zeny = user.zeny,
+ str = user.stat_str,
+ agi = user.stat_agi,
+ vit = user.stat_vit,
+ INT = user.stat_int,
+ dex = user.stat_dex,
+ luk = user.stat_luk,
+ max_hp = user.max_hp,
+ hp = user.hp,
+ max_sp = user.max_sp,
+ sp = user.sp,
+ status_point = user.status_point,
+ skill_point = user.skill_point,
+ option = user.option,
+ karma = user.karma,
+ manner = user.manner,
+ party_id = user.party_id,
+ guild_id = user.guild_id,
+ pet_id = user.pet_id,
+ homun_id = "0",
+ elemental_id = "0",
+ hair = user.hair,
+ hair_color = user.hair_color,
+ clothes_color = user.clothes_color,
+ body = 0,
+ weapon = user.weapon,
+ shield = user.shield,
+ head_top = user.head_top,
+ head_mid = user.head_mid,
+ head_bottom = user.head_bottom,
+ robe = "0",
+ last_map = escapeSqlStr(user.last_map),
+ last_x = user.last_x,
+ last_y = user.last_y,
+ save_map = escapeSqlStr(user.save_map),
+ save_x = user.save_x,
+ save_y = user.save_y,
+ partner_id = user.partner_id,
+ online = "0",
+ father = "0",
+ mother = "0",
+ child = "0",
+ fame = "0",
+ rename = "0",
+ delete_date = "0",
+ slotchange = "0",
+ char_opt = "0",
+ font = "0",
+ unban_time = "0",
+ uniqueitem_counter = len(user.inventory),
+ sex = "U",
+ hotkey_rowshift = 0
+ ))
+ w.write("\n")
+
+def saveCharTableCustom(users):
+ dstFile = "newdb/char.sql"
+ firstLine = True
+ tpl = readFile("templates/char.sql")
+ with open(dstFile, "w") as w:
+ w.write(tpl)
+ w.write("INSERT INTO `char` VALUES ")
+ for userId in users:
+ user = users[userId]
+
+ if firstLine == False:
+ w.write(",\n")
+ else:
+ firstLine = False
+
+ w.write(("({char_id},{account_id},{char_num},'{name}',{CLASS}," +
+ "{base_level},{job_level},{base_exp},{job_exp},{zeny}," +
+ "{str},{agi},{vit},{INT},{dex},{luk},{max_hp},{hp},{max_sp}," +
+ "{sp},{status_point},{skill_point},{option},{karma},{manner}," +
+ "{party_id},{guild_id},{pet_id},{homun_id},{elemental_id}," +
+ "{hair},{hair_color},{clothes_color},{body},{weapon},{shield}," +
+ "{head_top},{head_mid},{head_bottom},{robe}," +
+ "'{last_map}',{last_x},{last_y},'{save_map}',{save_x},{save_y}," +
+ "{partner_id},{online},{father},{mother},{child},{fame}," +
+ "{rename},{delete_date},{slotchange},{char_opt},{font}," +
+ "{unban_time},{uniqueitem_counter},'{sex}',{hotkey_rowshift})").format(
+ char_id = user.char_id,
+ account_id = user.account_id,
+ char_num = user.char_num,
+ name = escapeSqlStr(user.char_name),
+ CLASS = 0,
+ base_level = user.base_level,
+ job_level = user.job_level,
+ base_exp = user.base_exp,
+ job_exp = user.job_exp,
+ zeny = user.zeny,
+ str = user.stat_str,
+ agi = user.stat_agi,
+ vit = user.stat_vit,
+ INT = user.stat_int,
+ dex = user.stat_dex,
+ luk = user.stat_luk,
+ max_hp = user.max_hp,
+ hp = user.hp,
+ max_sp = user.max_sp,
+ sp = user.sp,
+ status_point = user.status_point,
+ skill_point = user.skill_point,
+ option = user.option,
+ karma = user.karma,
+ manner = user.manner,
+ party_id = 0,
+ guild_id = 0,
+ pet_id = 0,
+ homun_id = "0",
+ elemental_id = "0",
+ hair = user.hair,
+ hair_color = user.hair_color,
+ clothes_color = user.clothes_color,
+ body = 0,
+ weapon = 0,
+ shield = 0,
+ head_top = 0,
+ head_mid = 0,
+ head_bottom = 0,
+ robe = "0",
+ last_map = "000-2-1",
+ last_x = 50,
+ last_y = 37,
+ save_map = "00-2-1",
+ save_x = 50,
+ save_y = 37,
+ partner_id = user.partner_id,
+ online = "0",
+ father = "0",
+ mother = "0",
+ child = "0",
+ fame = "0",
+ rename = "0",
+ delete_date = "0",
+ slotchange = "0",
+ char_opt = "0",
+ font = "0",
+ unban_time = "0",
+ uniqueitem_counter = len(user.inventory),
+ sex = "U",
+ hotkey_rowshift = 0
+ ))
+ w.write("\n")
diff --git a/hercules/code/server/db/charregnumdb.py b/hercules/code/server/db/charregnumdb.py
new file mode 100644
index 0000000..1617c13
--- /dev/null
+++ b/hercules/code/server/db/charregnumdb.py
@@ -0,0 +1,34 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2015 Evol Online
+# Author: Andrei Karas (4144)
+
+from code.fileutils import readFile
+
+def saveCharRegNumDbTable(users):
+ dstFile = "newdb/char_reg_num_db.sql"
+ firstLine = True
+ tpl = readFile("templates/char_reg_num_db.sql")
+ with open(dstFile, "w") as w:
+ w.write(tpl)
+ w.write("INSERT INTO `char_reg_num_db` VALUES ")
+ for userId in users:
+ user = users[userId]
+ for varKey in user.variables:
+ if varKey == "ShipQuests":
+ continue
+
+ varValue = user.variables[varKey]
+
+ if firstLine == False:
+ w.write(",\n")
+ else:
+ firstLine = False
+
+ w.write(("({char_id},'{key}',{index},{value})").format(
+ char_id = user.char_id,
+ key = varKey,
+ index = "0",
+ value = varValue
+ ))
+ w.write("\n")
diff --git a/hercules/code/server/db/inventory.py b/hercules/code/server/db/inventory.py
new file mode 100644
index 0000000..e5c1e6a
--- /dev/null
+++ b/hercules/code/server/db/inventory.py
@@ -0,0 +1,44 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2015 Evol Online
+# Author: Andrei Karas (4144)
+
+from code.fileutils import readFile
+
+def saveInventoryTable(users):
+ dstFile = "newdb/inventory.sql"
+ firstLine = True
+ tpl = readFile("templates/inventory.sql")
+ with open(dstFile, "w") as w:
+ w.write(tpl)
+ w.write("INSERT INTO `inventory` VALUES ")
+ for userId in users:
+ user = users[userId]
+ for item in user.inventory:
+
+ if firstLine == False:
+ w.write(",\n")
+ else:
+ firstLine = False
+
+ w.write(("({id},{char_id},{nameid},{amount},{equip},{identify}," +
+ "{refine},{attribute},{card0},{card1},{card2},{card3}," +
+ "{expire_time},{favorite},{bound},{unique_id})").format(
+ id = 0,
+ char_id = user.char_id,
+ nameid = item.itemId,
+ amount = item.amount,
+ equip = item.equip,
+ identify = "1",
+ refine = item.refine,
+ attribute = item.attribute,
+ card0 = "0",
+ card1 = "0",
+ card2 = "0",
+ card3 = "0",
+ expire_time = "0",
+ favorite = "0",
+ bound = "0",
+ unique_id = "0"
+ ))
+ w.write("\n")
diff --git a/hercules/code/server/db/skill.py b/hercules/code/server/db/skill.py
new file mode 100644
index 0000000..d21eb3d
--- /dev/null
+++ b/hercules/code/server/db/skill.py
@@ -0,0 +1,31 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2015 Evol Online
+# Author: Andrei Karas (4144)
+
+from code.fileutils import readFile
+
+def saveSkillTable(users):
+ dstFile = "newdb/skill.sql"
+ firstLine = True
+ tpl = readFile("templates/skill.sql")
+ with open(dstFile, "w") as w:
+ w.write(tpl)
+ w.write("INSERT INTO `skill` VALUES ")
+ for userId in users:
+ user = users[userId]
+ for skill in user.skills:
+
+ if firstLine == False:
+ w.write(",\n")
+ else:
+ firstLine = False
+
+ w.write(("({char_id},{id},{lv},{flag})").format(
+ char_id = user.char_id,
+ id = skill.skillId,
+ lv = skill.level,
+ flag = 0
+# flag = skill.flags
+ ))
+ w.write("\n")
diff --git a/hercules/code/server/dbitem.py b/hercules/code/server/dbitem.py
new file mode 100644
index 0000000..8534ad4
--- /dev/null
+++ b/hercules/code/server/dbitem.py
@@ -0,0 +1,7 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2015 Evol Online
+# Author: Andrei Karas (4144)
+
+class Item:
+ pass
diff --git a/hercules/code/server/dbskill.py b/hercules/code/server/dbskill.py
new file mode 100644
index 0000000..343f3af
--- /dev/null
+++ b/hercules/code/server/dbskill.py
@@ -0,0 +1,7 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2015 Evol Online
+# Author: Andrei Karas (4144)
+
+class Skill:
+ pass
diff --git a/hercules/code/server/dbuser.py b/hercules/code/server/dbuser.py
new file mode 100644
index 0000000..95ad55d
--- /dev/null
+++ b/hercules/code/server/dbuser.py
@@ -0,0 +1,7 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2015 Evol Online
+# Author: Andrei Karas (4144)
+
+class User:
+ pass
diff --git a/hercules/code/server/evol/__init__.py b/hercules/code/server/evol/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/hercules/code/server/evol/athena.py b/hercules/code/server/evol/athena.py
new file mode 100644
index 0000000..bad35da
--- /dev/null
+++ b/hercules/code/server/evol/athena.py
@@ -0,0 +1,207 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2015 Evol Online
+# Author: Andrei Karas (4144)
+
+import re
+
+from code.stringutils import stripNewLine, removeGat
+from code.server.dbitem import Item
+from code.server.dbskill import Skill
+from code.server.dbuser import User
+
+comaSplit = re.compile(",")
+spaceSplit = re.compile(" ")
+
+def parseInventory(line, data):
+ items = []
+
+ if data == "":
+ return items
+
+ rows = spaceSplit.split(data)
+ if len(rows) < 1:
+ return items
+
+ for data2 in rows:
+ if data2 == "":
+ continue
+
+ rows2 = comaSplit.split(data2)
+
+ if len(rows2) != 12:
+ print "wrong inventory in account.txt line: " + stripNewLine(line)
+ continue
+
+ item = Item()
+
+ item.uknownId = rows2[0]
+ item.itemId = rows2[1]
+ item.amount = rows2[2]
+ item.equip = rows2[3]
+ item.color = rows2[4]
+ item.refine = rows2[5]
+ item.attribute = rows2[6]
+ item.card0 = rows2[7]
+ item.card1 = rows2[8]
+ item.card2 = rows2[9]
+ item.card3 = rows2[10]
+ item.broken = rows2[11]
+
+ items.append(item)
+
+ return items
+
+def parseSkills(line, data):
+ skills = []
+
+ if data == "":
+ return skills
+
+ rows = spaceSplit.split(data)
+ if len(rows) < 1:
+ return skills
+
+ for data2 in rows:
+ if data2 == "":
+ continue
+
+ rows2 = comaSplit.split(data2)
+
+ if len(rows2) != 2:
+ print "wrong skills in account.txt line: " + stripNewLine(line)
+ continue
+
+ skill = Skill()
+ skill.skillId = rows2[0]
+ skill.level = int(rows2[1]) & 0xffff
+ skill.flags = (int(rows2[1]) * 0xffff) & 0xffff
+
+ skills.append(skill)
+
+ return skills
+
+def parseVars(line, data):
+ variables = {}
+
+ if data == "":
+ return variables
+
+ rows = spaceSplit.split(data)
+ if len(rows) < 1:
+ return variables
+
+ for data2 in rows:
+ if data2 == "":
+ continue
+
+ rows2 = comaSplit.split(data2)
+
+ if len(rows2) != 2:
+ print "wrong variables in account.txt line: " + stripNewLine(line)
+ continue
+
+ variables[rows2[0]] = rows2[1]
+
+ return variables
+
+def readAthena():
+ srcFile = "olddb/athena.txt"
+ fieldsSplit = re.compile("\t")
+
+ users = {}
+ with open(srcFile, "r") as r:
+ for line in r:
+ if line[:2] == "//":
+ continue
+ rows = fieldsSplit.split(line)
+ if len(rows) == 2:
+ continue
+ if len(rows) != 20:
+ print "wrong account.txt line: " + stripNewLine(line)
+ continue
+
+ user = User()
+
+ user.char_id = rows[0]
+ tmp = comaSplit.split(rows[1])
+ user.account_id = tmp[0]
+ user.char_num = tmp[1]
+ user.char_name = rows[2]
+
+ tmp = comaSplit.split(rows[3])
+ user.char_class = tmp[0]
+ user.base_level = tmp[1]
+ user.job_level = tmp[2]
+
+ tmp = comaSplit.split(rows[4])
+ user.base_exp = tmp[0]
+ user.job_exp = tmp[1]
+ user.zeny = tmp[2]
+
+ tmp = comaSplit.split(rows[5])
+ user.hp = tmp[0]
+ user.max_hp = tmp[1]
+ user.sp = tmp[2]
+ user.max_sp = tmp[3]
+
+ tmp = comaSplit.split(rows[6])
+ user.stat_str = tmp[0]
+ user.stat_agi = tmp[1]
+ user.stat_vit = tmp[2]
+ user.stat_int = tmp[3]
+ user.stat_dex = tmp[4]
+ user.stat_luk = tmp[5]
+
+ tmp = comaSplit.split(rows[7])
+ user.status_point = tmp[0]
+ user.skill_point = tmp[1]
+
+ tmp = comaSplit.split(rows[8])
+ user.option = tmp[0]
+ user.karma = tmp[1]
+ user.manner = tmp[2]
+
+ tmp = comaSplit.split(rows[9])
+ user.party_id = tmp[0]
+ user.guild_id = tmp[1]
+ user.pet_id = tmp[2]
+
+ tmp = comaSplit.split(rows[10])
+ user.hair = tmp[0]
+ user.hair_color = tmp[1]
+ user.clothes_color = tmp[2]
+
+ tmp = comaSplit.split(rows[11])
+ user.weapon = tmp[0]
+ user.shield = tmp[1]
+ user.head_top = tmp[2]
+ user.head_mid = tmp[3]
+ user.head_bottom = tmp[4]
+
+ tmp = comaSplit.split(rows[12])
+ user.last_map = removeGat(tmp[0])
+ user.last_x = tmp[1]
+ user.last_y = tmp[2]
+
+ tmp = comaSplit.split(rows[13])
+ user.save_map = removeGat(tmp[0])
+ user.save_x = tmp[1]
+ user.save_y = tmp[2]
+ user.partner_id = tmp[3]
+ user.language_id = tmp[4]
+
+ # skip 14
+
+ user.inventory = parseInventory(line, rows[15])
+
+ # skip 16
+
+ user.skills = parseSkills(line, rows[17])
+
+ user.variables = parseVars(line, rows[18])
+ user.variables["Lang"] = user.language_id
+
+ users[user.char_id] = user
+
+ return users
diff --git a/hercules/code/server/evol/consts.py b/hercules/code/server/evol/consts.py
new file mode 100644
index 0000000..c63f559
--- /dev/null
+++ b/hercules/code/server/evol/consts.py
@@ -0,0 +1,49 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+import re
+
+def convertConsts(quests):
+ dstFile = "newserverdata/db/const.txt"
+ fieldsSplit = re.compile("\t+")
+ fields = dict()
+ with open(dstFile, "w") as w:
+ srcFile = "serverdata/db/const.txt"
+ with open(srcFile, "r") as r:
+ for line in r:
+ if len(line) < 2 or line[0:2] == "//":
+ continue
+ line = line.replace(" ", "\t")
+ rows = fieldsSplit.split(line)
+ sz = len(rows)
+ if sz < 2 or sz > 3:
+ continue
+
+ fields[rows[0]] = rows[1][:-1]
+ if sz == 2:
+ w.write("{0}\t{1}".format(rows[0], rows[1]))
+ else:
+ w.write("{0}\t{1}\t{2}".format(rows[0], rows[1], rows[2]))
+ # build in parameters
+ w.write("ClientVersion\t10000\t1\n");
+
+ srcFile = "oldserverdata/db/const.txt"
+ w.write("// evol constants\n")
+ with open(srcFile, "r") as r:
+ for line in r:
+ if len(line) < 2 or line[0:2] == "//":
+ continue
+ line = line.replace(" ", "\t")
+ rows = fieldsSplit.split(line)
+ if len(rows) != 2:
+ continue
+
+ if rows[0] in quests:
+ rows[1] = str(quests[rows[0]]) + "\n"
+ if rows[0] in fields:
+ if fields[rows[0]] != rows[1][:-1]:
+ print "warning: different const values for {0} ({1}, {2})".format(rows[0], rows[1][:-1], fields[rows[0]])
+ else:
+ w.write("{0}\t{1}".format(rows[0], rows[1]))
diff --git a/hercules/code/server/evol/itemdb.py b/hercules/code/server/evol/itemdb.py
new file mode 100644
index 0000000..a60e57d
--- /dev/null
+++ b/hercules/code/server/evol/itemdb.py
@@ -0,0 +1,104 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+import re
+
+from code.configutils import writeIntField, writeStartBlock, writeEndBlock, writeStartScript, writeEndScript, writeStrField, writeSubField
+from code.fileutils import readFile
+
+def convertItemDb():
+ srcFile = "oldserverdata/db/item_db.txt"
+ dstFile = "newserverdata/db/re/item_db.conf"
+ constsFile = "newserverdata/db/const.txt"
+ fieldsSplit = re.compile(",")
+ scriptsSplit = re.compile("{")
+ items = dict()
+ with open(srcFile, "r") as r:
+ with open(dstFile, "w") as w:
+ with open(constsFile, "a") as c:
+ c.write("// items\n");
+ tpl = readFile("templates/item_db.tpl")
+ w.write(tpl)
+ for line in r:
+ if len(line) < 2 or line[0] == "#" or line[0:2] == "//":
+ continue
+ rows = fieldsSplit.split(line)
+ if len(rows) < 5 or rows[0] == "0":
+ continue
+
+ sz = len(rows)
+ if sz > 19:
+ sz = 19
+ for f in xrange(0, sz):
+ rows[f] = rows[f].strip()
+ if rows[4] == "2":
+ rows[4] = "0"
+ rows[3] = str(int(rows[3]) | 4)
+
+ items[rows[1]] = rows[0]
+ w.write("{\n")
+ c.write("{0}\t{1}\n".format(rows[1], rows[0]))
+ writeIntField(w, "Id", rows[0])
+ writeStrField(w, "AegisName", rows[1])
+ writeStrField(w, "Name", rows[2])
+ writeIntField(w, "Type", rows[4])
+ writeIntField(w, "Buy", rows[5])
+ writeIntField(w, "Sell", rows[6])
+ writeIntField(w, "Weight", rows[7])
+ writeIntField(w, "Atk", rows[8])
+ writeIntField(w, "Matk", "0")
+ writeIntField(w, "Def", rows[9])
+ writeIntField(w, "Range", rows[10])
+ writeIntField(w, "Slots", "0")
+ writeIntField(w, "Job", "0xFFFFFFFF")
+ writeIntField(w, "Upper", "0x3F")
+ writeIntField(w, "Gender", rows[13])
+ writeIntField(w, "Loc", rows[14])
+ writeIntField(w, "WeaponLv", rows[15])
+ writeIntField(w, "EquipLv", rows[16])
+ writeIntField(w, "Refine", "false")
+ if rows[14] == "2":
+ writeIntField(w, "View", "1")
+ else:
+ writeIntField(w, "View", rows[0])
+ writeIntField(w, "BindOnEquip", "false")
+ writeIntField(w, "BuyingStore", "false")
+ writeIntField(w, "Delay", "0")
+ trade = int(rows[3])
+ if trade != 0:
+ writeStartBlock(w, "Trade")
+ if trade & 1 == 1:
+ writeSubField(w, "nodrop", "true")
+ if trade & 2 == 2:
+ writeSubField(w, "notrade", "true")
+ if trade & 4 == 4:
+ writeSubField(w, "nodelonuse", "true")
+ if trade & 8 == 8:
+ writeSubField(w, "nostorage", "true")
+ if trade & 256 == 256:
+ writeSubField(w, "nogstorage", "true")
+ if trade & 512 == 512:
+ writeSubField(w, "noselltonpc", "true")
+ if trade != 0:
+ writeEndBlock(w)
+ writeIntField(w, "Sprite", "0")
+
+ scripts = ""
+ for f in xrange(sz, len(rows)):
+ scripts = scripts + ", " + rows[f]
+ rows = scriptsSplit.split(scripts)
+ cnt = len(rows)
+ if cnt > 1:
+ text = rows[1].strip()
+ if len(text) > 1:
+ text = text[:-2]
+ if text != "":
+ writeStartScript(w, "Script")
+ w.write(" {0}\n".format(text))
+ writeEndScript(w)
+
+ w.write("},\n")
+ w.write(")\n")
+ return items
diff --git a/hercules/code/server/evol/main.py b/hercules/code/server/evol/main.py
new file mode 100644
index 0000000..be9f0ab
--- /dev/null
+++ b/hercules/code/server/evol/main.py
@@ -0,0 +1,43 @@
+#! /usr/bin/env python
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+from code.server.account import convertAccount
+from code.server.accreg import convertAccReg
+from code.server.party import convertParty
+from code.server.storage import convertStorage
+from code.server.db.char import saveCharTable
+from code.server.db.charregnumdb import saveCharRegNumDbTable
+from code.server.db.inventory import saveInventoryTable
+from code.server.db.skill import saveSkillTable
+from code.server.evol.athena import readAthena
+from code.server.evol.consts import convertConsts
+from code.server.evol.itemdb import convertItemDb
+from code.server.evol.mobdb import convertMobDb
+from code.server.evol.mobskilldb import convertMobSkillDb
+from code.server.evol.npcs import createMainScript, convertNpcs
+from code.server.questsdb import convertQuestsDb
+from code.server.utils import cleanServerData
+
+def serverEvolMain():
+ cleanServerData()
+ createMainScript()
+ items = convertItemDb()
+ convertNpcs(items)
+ convertMobDb()
+ quests = convertQuestsDb()
+ convertConsts(quests)
+ convertMobSkillDb()
+
+def dbEvolMain():
+ convertAccount()
+ users = readAthena()
+ saveCharTable(users)
+ saveCharRegNumDbTable(users)
+ saveSkillTable(users)
+ saveInventoryTable(users)
+ convertStorage()
+ convertAccReg()
+ convertParty(users)
\ No newline at end of file
diff --git a/hercules/code/server/evol/mobdb.py b/hercules/code/server/evol/mobdb.py
new file mode 100644
index 0000000..74eda04
--- /dev/null
+++ b/hercules/code/server/evol/mobdb.py
@@ -0,0 +1,89 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+import re
+
+def convertMobDb():
+ srcFile = "oldserverdata/db/mob_db.txt"
+ dstFile = "newserverdata/db/re/mob_db.txt"
+ fieldsSplit = re.compile(",")
+ with open(srcFile, "r") as r:
+ with open(dstFile, "w") as w:
+ for line in r:
+# if len(line) < 2 or line[0] == "#":
+ if len(line) < 2:
+ w.write(line)
+ continue
+ rows = fieldsSplit.split(line)
+ for f in xrange(0, len(rows)):
+ rows[f] = rows[f].strip()
+ w.write("{0:<5} {1:<15} {2:<16} {3:<16} {4:<5} {5:<5} {6:<5} "
+ "{7:<5} {8:<5} {9:<7} {10:<5} {11:<5} {12:<5} {13:<5} "
+ "{14:<5} {15:<5} {16:<5} {17:<5} {18:<5} {19:<5} {20:<7}"
+ " {21:<7} {22:<6} {23:<5} {24:<8} {25:<8} {26:<6} "
+ "{27:<8} {28:<9} {29:<8} {30:<5} {31:<7} {32:<8} {33:<7}"
+ " {34:<8} {35:<7} {36:<8} {37:<8} {38:<9} {39:<8} "
+ "{40:<9} {41:<8} {42:<9} {43:<8} {44:<9} {45:<8} {46:<9}"
+ " {47:<8} {48:<9} {49:<8} {50:<9} {51:<8} {52:<9} "
+ "{53:<8} {54:<9} {55:<8} {56:<8} \n".format(
+ rows[0] + ",",
+ rows[1] + ",",
+ rows[2] + ",",
+ rows[2] + ",",
+ rows[3] + ",",
+ rows[4] + ",",
+ rows[5] + ",",
+ rows[6] + ",",
+ rows[7] + ",",
+ rows[8] + ",",
+ rows[9] + ",",
+ rows[10] + ",",
+ rows[11] + ",",
+ rows[12] + ",",
+ rows[13] + ",",
+ rows[14] + ",",
+ rows[15] + ",",
+ rows[16] + ",",
+ rows[17] + ",",
+ rows[18] + ",",
+ rows[19] + ",",
+ rows[20] + ",",
+ rows[21] + ",",
+ rows[22] + ",",
+ rows[23] + ",",
+ rows[24] + ",",
+ rows[25] + ",",
+ rows[26] + ",",
+ rows[27] + ",",
+ rows[28] + ",",
+ rows[45] + ",",
+ rows[47] + ",",
+ rows[48] + ",",
+ rows[49] + ",",
+ rows[50] + ",",
+ rows[51] + ",",
+ rows[52] + ",",
+ rows[29] + ",",
+ rows[30] + ",",
+ rows[31] + ",",
+ rows[32] + ",",
+ rows[33] + ",",
+ rows[34] + ",",
+ rows[35] + ",",
+ rows[36] + ",",
+ rows[37] + ",",
+ rows[38] + ",",
+ rows[39] + ",",
+ rows[40] + ",",
+ rows[41] + ",",
+ rows[42] + ",",
+ rows[43] + ",",
+ rows[44] + ",",
+ "0,",
+ "0,",
+ "0,",
+ "0"
+ ))
+
diff --git a/hercules/code/server/evol/mobskilldb.py b/hercules/code/server/evol/mobskilldb.py
new file mode 100644
index 0000000..a5a7d5f
--- /dev/null
+++ b/hercules/code/server/evol/mobskilldb.py
@@ -0,0 +1,50 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+import re
+
+from code.fileutils import readFile
+
+def convertMobSkillDb():
+ srcFile = "oldserverdata/db/mob_skill_db.txt"
+ dstFile = "newserverdata/db/re/mob_skill_db.txt"
+ fieldsSplit = re.compile(",")
+ with open(srcFile, "r") as r:
+ with open(dstFile, "w") as w:
+ tpl = readFile("templates/mob_skill_db.tpl")
+ w.write(tpl)
+ for line in r:
+ if len(line) < 2:
+ w.write(line)
+ continue
+ rows = fieldsSplit.split(line)
+ if len(rows) < 10:
+ w.write(line)
+ continue
+
+ for f in xrange(0, len(rows)):
+ rows[f] = rows[f].strip()
+
+ w.write("{0},{1},{2},{3},{4},{5},{6},"
+ "{7},{8},{9},{10},{11},{12},{13},"
+ "{14},{15},,,\n".format(
+ rows[0],
+ rows[1],
+ rows[2],
+ rows[3],
+ rows[4],
+ rows[5],
+ rows[6],
+ rows[7],
+ rows[8],
+ rows[9],
+ rows[10],
+ rows[11],
+ rows[13],
+ rows[14],
+ rows[15],
+ rows[16]
+ ))
+
diff --git a/hercules/code/server/evol/npcs.py b/hercules/code/server/evol/npcs.py
new file mode 100644
index 0000000..6efe811
--- /dev/null
+++ b/hercules/code/server/evol/npcs.py
@@ -0,0 +1,281 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+import os
+import re
+
+from code.fileutils import makeDir
+from code.stringutils import stripWindows, stripNewLine
+
+mapsConfFile = "newserverdata/conf/maps.conf"
+mapsIndexFile = "newserverdata/db/map_index.txt"
+npcMainScript = "newserverdata/npc/re/scripts_main.conf"
+mapsIndex = 1
+scriptRe = re.compile("^(?P[^/](.+))[.]gat,([ ]*)(?P[\d]+),([ ]*)(?P[\d]+),([ ]*)(?P[\d]+)(|,(?P[\d]+))" +
+ "[\t](?Pscript)[\t](?P[\w#' ]+)[\t]"
+ "(?P[\d]+)((,((?P[\d]+),(?P[\d]+)))|)(|;(?P[\d]+))(|,|;){$")
+
+shopRe = re.compile("^(?P[^/](.+))[.]gat,([ ]*)(?P[\d]+),([ ]*)(?P[\d]+),([ ]*)(?P[\d]+)(|,(?P[\d]+))" +
+ "[\t](?Pshop)[\t](?P[\w#' ]+)[\t]"
+ "(?P[\d]+),(?P(.+))$")
+
+mapFlagRe = re.compile("^(?P[^/](.+))[.]gat" +
+ "[ ](?Pmapflag)[ ](?P[\w#']+)(|[ ](?P.*))$")
+
+warpRe = re.compile("^(?P[^/](.+))[.]gat,([ ]*)(?P[\d]+),([ ]*)(?P[\d]+)[\t]"
+ "(?Pwarp)[\t](?P[^\t]+)[\t](?P[\d-]+),(?P[\d-]+),(?P[^/](.+))[.]gat,([ ]*)(?P[\d]+),([ ]*)(?P[\d]+)$")
+
+monsterRe = re.compile("^(?P[^/](.+))[.]gat,([ ]*)(?P[\d]+),([ ]*)(?P[\d]+),([ ]*)(?P[\d-]+),(?P[\d-]+)\t"
+ "(?Pmonster)[\t](?P[\w#' ]+)[\t]"
+ "(?P[\d]+),(?P[\d]+),(?P[\d-]+),(?P[\d]+),(?P[\d]+)$")
+
+setRe = re.compile("^(?P[ ]+)set[ ](?P[^,]+),([ ]*)(?P[^;]+);$");
+
+class ScriptTracker:
+ pass
+
+def createMainScript():
+ with open(npcMainScript, "w") as w:
+ w.write("npc: npc/functions/main.txt\n")
+ w.write("import: npc/scripts.conf\n")
+
+def convertNpcs(items):
+ processNpcDir("oldserverdata/npc/", "newserverdata/npc/", items)
+
+def processNpcDir(srcDir, dstDir, items):
+ makeDir(dstDir)
+ files = os.listdir(srcDir)
+ for file1 in files:
+ if file1[0] == '.':
+ continue
+ srcPath = os.path.abspath(srcDir + os.path.sep + file1)
+ dstPath = os.path.abspath(dstDir + os.path.sep + file1)
+ if not os.path.isfile(srcPath):
+ processNpcDir(srcPath, dstPath, items)
+ else:
+ if file1[-5:] == ".conf" or file1[-4:] == ".txt":
+ processNpcFile(srcPath, dstPath, items)
+
+def processNpcFile(srcFile, dstFile, items):
+# print "file: " + srcFile
+ tracker = ScriptTracker()
+ tracker.insideScript = False
+ tracker.items = items
+ with open(srcFile, "r") as r:
+ with open(dstFile, "w") as w:
+ tracker.w = w
+ for line in r:
+ tracker.line = stripWindows(line)
+ res = convertTextLine(tracker)
+ if res:
+ w.write(tracker.line)
+
+def convertTextLine(tracker):
+ line = tracker.line
+ if line[:5] == "map: ":
+ processScriptMapLine(line)
+ return False
+
+ idx = line.find("\tscript\t")
+ if idx >= 0:
+ processScript(tracker)
+ return False
+ idx = line.find("\tshop\t")
+ if idx >= 0:
+ processShop(tracker)
+ return False
+ idx = line.find("\tmonster\t")
+ if idx >= 0:
+ processMonster(tracker)
+ return False
+ idx = line.find("\twarp\t")
+ if idx >= 0:
+ processWarp(tracker)
+ return False
+ idx = line.find(".gat mapflag ")
+ if idx >= 0:
+ processMapFlag(tracker)
+ return False
+ processStrReplace(tracker)
+ return False
+
+def processScriptMapLine(line):
+ global mapsIndex
+ line = stripNewLine(line)
+ if line[-4:] == ".gat":
+ line = line[:-4]
+ with open(mapsConfFile, "a") as w:
+ w.write(line + "\n")
+
+ with open(mapsIndexFile, "a") as w:
+ w.write("{0} {1}\n".format(line[5:], mapsIndex))
+ mapsIndex = mapsIndex + 1
+
+def writeScript(w, m):
+ if m.group("gender") != None:
+ w.write("// Gender = {0}\n".format(m.group("gender")));
+
+ if m.group("x") == 0 and m.group("y") == 0: # float npc
+ w.write("-")
+ else:
+ w.write("{0},{1},{2},{3}".format(m.group("map"), m.group("x"), m.group("y"), m.group("dir")))
+ class_ = m.group("class")
+ if class_ == "0": # hidden npc
+ class_ = "32767"
+ else:
+ class_ = int(class_)
+ if class_ > 125 and class_ <= 400:
+ class_ = class_ + 100
+ w.write("\t{0}\t{1}\t{2}".format(m.group("tag"), m.group("name"), class_));
+
+def processScript(tracker):
+ line = tracker.line
+ w = tracker.w
+ if line[:9] == "function\t":
+ tracker.w.write(line)
+ return
+
+ m = scriptRe.search(line)
+ if m == None:
+ print "error in parsing: " + line
+ w.write("!!!error parsing line")
+ w.write(line)
+ return
+# print "source=" + line[:-1]
+# print "map={0} x={1} y={2} dir={3} gender={4} tag={5} name={6} class={7}, xs={8}, ys={9}, size={10}".format(
+# m.group("map"), m.group("x"), m.group("y"), m.group("dir"), m.group("gender"),
+# m.group("tag"), m.group("name"), m.group("class"), m.group("xs"), m.group("ys"), m.group("size"))
+
+ if m.group("size") != None:
+ w.write("// Size = {0}\n".format(m.group("size")));
+ writeScript(w, m)
+ if m.group("xs") != None:
+ w.write(",{0},{1}".format(m.group("xs"), m.group("ys")));
+ w.write(",{\n");
+
+
+def itemsToShop(tracker, itemsStr):
+ itemsSplit = re.compile(",")
+ itemsSplit2 = re.compile(":")
+ itemsDict = tracker.items
+ outStr = ""
+ items = itemsSplit.split(itemsStr)
+ for str2 in items:
+ parts = itemsSplit2.split(str2)
+ if outStr != "":
+ outStr = outStr + ","
+ outStr = outStr + itemsDict[parts[0].strip()] + ":" + parts[1]
+ return outStr
+
+def processShop(tracker):
+ line = tracker.line
+ w = tracker.w
+
+ m = shopRe.search(line)
+ if m == None:
+ print "error in parsing: " + line
+ w.write("!!!error parsing line")
+ w.write(line)
+ return
+# print "source=" + line[:-1]
+# print "map={0} x={1} y={2} dir={3} gender={4} tag={5} name={6} class={7} items={8}".format(
+# m.group("map"), m.group("x"), m.group("y"), m.group("dir"), m.group("gender"),
+# m.group("tag"), m.group("name"), m.group("class"), m.group("items"))
+
+ writeScript(w, m)
+ w.write("," + itemsToShop(tracker, m.group("items")) + "\n")
+
+def processMapFlag(tracker):
+ line = tracker.line
+ w = tracker.w
+
+ m = mapFlagRe.search(line)
+ if m == None:
+ print "error in parsing: " + line
+ w.write("!!!error parsing line")
+ w.write(line)
+ return
+# print "source=" + line[:-1]
+# print "map={0} tag={1} name={2} flag={3}".format(
+# m.group("map"), m.group("tag"), m.group("name"), m.group("flag"))
+
+ w.write("{0}\t{1}\t{2}".format(m.group("map"), m.group("tag"), m.group("name")))
+ if m.group("flag") == None:
+ w.write("\n")
+ else:
+ w.write("\t{0}\n".format(m.group("flag")))
+
+def processWarp(tracker):
+ line = tracker.line
+ w = tracker.w
+ m = warpRe.search(line)
+ if m == None:
+ print "error in parsing: " + line
+ w.write("!!!error parsing line")
+ w.write(line)
+ return
+
+# print "source=" + line[:-1]
+# print "map={0} xy={1},{2} tag={3} name={4} xs={5} ys={6} target: map={7} xy={8},{9}".format(
+# m.group("map"), m.group("x"), m.group("y"), m.group("tag"), m.group("name"), m.group("xs"), m.group("ys"), m.group("targetmap"), m.group("targetx"), m.group("targety"))
+
+ xs = int(m.group("xs"))
+ ys = int(m.group("ys"))
+ if xs < 0:
+ xs = 0
+ if ys < 0:
+ ys = 0
+ w.write("{0},{1},{2},{3}\t{4}\t{5}\t{6},{7},{8},{9},{10}\n".format(
+ m.group("map"), m.group("x"), m.group("y"), "0", m.group("tag"), m.group("name"),
+ xs, ys, m.group("targetmap"), m.group("targetx"), m.group("targety")))
+
+
+def processMonster(tracker):
+ line = tracker.line
+ w = tracker.w
+ m = monsterRe.search(line)
+ if m == None:
+ print "error in parsing: " + line
+ w.write("!!!error parsing line")
+ w.write(line)
+ return
+
+# print "source=" + line[:-1]
+# print ("map={0} xy={1},{2} xs={3} ys={4} tag={5} name={6} class={7} " +
+# "num={8} look={9} delays={10},{11}").format(
+# m.group("map"), m.group("x"), m.group("y"), m.group("xs"), m.group("ys"),
+# m.group("tag"), m.group("name"), m.group("class"),
+# m.group("num"), m.group("look"), m.group("delay1"), m.group("delay2"))
+ w.write("{0},{1},{2},{3},{4}\t{5}\t{6}\t{7},{8},{9},{10}\n".format(m.group("map"),
+ m.group("x"), m.group("y"), m.group("xs"), m.group("ys"),
+ m.group("tag"), m.group("name"),
+ m.group("class"), m.group("num"), m.group("delay1"), m.group("delay2")))
+
+
+def processStrReplace(tracker):
+ line = tracker.line
+ w = tracker.w
+ line = line.replace("setskill ", "addtoskill ")
+ line = line.replace("zeny", "Zeny")
+ line = line.replace("getclientversion(\"\")", "ClientVersion")
+ line = line.replace("getclientversion()", "ClientVersion")
+ line = line.replace("setlang @", "Lang = @")
+ line = re.sub("([^@^$])@([^@])", "\\1.@\\2", line)
+ line = line.replace(".@menu", "@menu")
+ idx = line.find("getmapmobs(")
+ if idx >= 0:
+ idx2 = line.find("\"", idx + len("getmapmobs(") + 1)
+ idx3 = line.find(")", idx + len("getmapmobs(") + 1)
+ if idx2 + 1 == idx3:
+ line = line[:idx2 + 1] + ",\"all\"" + line[idx2 + 1:]
+
+ line = line.replace("getmapmobs(", "mobcount(")
+
+ m = setRe.search(line);
+ if m != None:
+ line = "{0}{1} = {2};\n".format(m.group("space"), m.group("var"), m.group("val"))
+
+ w.write(line)
+
diff --git a/hercules/code/server/maps.py b/hercules/code/server/maps.py
new file mode 100644
index 0000000..2ef0173
--- /dev/null
+++ b/hercules/code/server/maps.py
@@ -0,0 +1,43 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+import array
+import struct
+import zlib
+
+from code.fileutils import readMapName, readInt16, readInt32, readData, makeDir
+
+def listMapCache(f, mapsCount):
+ print "Known maps:"
+ print "{0:12} {1:<4}x {2:<4} {3:<10}".format("Map name", "sx", "sy", "compressed size")
+ for i in xrange(0, mapsCount):
+ name = readMapName(f)
+ sx = readInt16(f)
+ sy = readInt16(f)
+ sz = readInt32(f)
+ print "{0:12} {1:<4}x {2:<4} {3:<10}".format(name, sx, sy, sz)
+ f.seek(sz, 1)
+
+def extractMaps(f, mapsCount):
+ destDir = "maps/"
+ makeDir(destDir)
+ for i in xrange(0, mapsCount):
+ name = readMapName(f)
+ sx = readInt16(f)
+ sy = readInt16(f)
+ sz = readInt32(f)
+ data = readData(f, sz)
+ dc = zlib.decompressobj()
+ data = dc.decompress(data)
+ with open(destDir + name, "wb") as w:
+ w.write(struct.pack("H", sx))
+ w.write(struct.pack("H", sy))
+ w.write(data)
+ with open(destDir + name + ".txt", "wb") as w:
+ arr = array.array("B")
+ arr.fromstring(data)
+ for x in xrange(0, sx):
+ for y in xrange(0, sy):
+ w.write("{0},{1}:{2}\n".format(x, y, arr[x + y * sx]))
diff --git a/hercules/code/server/party.py b/hercules/code/server/party.py
new file mode 100644
index 0000000..c75a541
--- /dev/null
+++ b/hercules/code/server/party.py
@@ -0,0 +1,80 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+import re
+
+from code.fileutils import readFile
+from code.stringutils import stripNewLine, escapeSqlStr
+
+def findLeaderId(name, users):
+ for userId in users:
+ user = users[userId]
+ if user.char_name == name:
+ return user.char_id
+ return 0
+
+def convertParty(users):
+ srcFile = "olddb/party.txt"
+ dstFile = "newdb/party.sql"
+ fieldsSplit = re.compile("\t")
+ comaSplit = re.compile(",")
+ tpl = readFile("templates/party.sql")
+ firstLine = True
+ with open(dstFile, "w") as w:
+ w.write(tpl)
+ w.write("INSERT INTO `party` VALUES ")
+ with open(srcFile, "r") as r:
+ for line in r:
+ if line[:2] == "//":
+ continue
+ line = stripNewLine(line)
+ rows = fieldsSplit.split(line)
+ if len(rows) == 2:
+ continue
+ if len(rows) < 3:
+ print "wrong party.txt line: " + line
+ continue
+
+ partyId = rows[0]
+ partyName = rows[1]
+
+ tmp = comaSplit.split(rows[2])
+ partyExp = tmp[0]
+ partyItem = tmp[1]
+
+ leaderId = 0
+ leaderName = ""
+ accountId = ""
+
+ for f in xrange(3, len(rows), 2):
+
+ if rows[f] == "0,0" or rows[f] == "":
+ continue
+
+ tmp = comaSplit.split(rows[f])
+ accountId = tmp[0]
+ leader = tmp[1]
+ charName = rows[f + 1]
+ if leader == "1":
+ leaderId = accountId
+ leaderName = charName
+
+ if firstLine == False:
+ w.write(",\n")
+ else:
+ firstLine = False
+
+ leaderCharId = findLeaderId(leaderName, users)
+
+ w.write(("({party_id},'{name}',{exp},{item}," +
+ "{leader_id},{leader_char})").format(
+ party_id = partyId,
+ name = escapeSqlStr(partyName),
+ exp = partyExp,
+ item = partyItem,
+ leader_id = leaderId,
+ leader_char = leaderCharId
+ ))
+ w.write("\n")
diff --git a/hercules/code/server/questsdb.py b/hercules/code/server/questsdb.py
new file mode 100644
index 0000000..880a7f9
--- /dev/null
+++ b/hercules/code/server/questsdb.py
@@ -0,0 +1,33 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+from code.fileutils import readFile
+
+def convertQuestsDb():
+ srcFile = "oldserverdata/db/questvars.txt"
+ dstFile = "newserverdata/db/quest_db.txt"
+ quests = dict()
+ with open(srcFile, "r") as r:
+ with open(dstFile, "w") as w:
+ tpl = readFile("templates/quest_db.tpl")
+ w.write(tpl)
+ cnt = 0
+ for line in r:
+ if len(line) < 2 or line[0:2] == "//":
+ continue
+
+ idx = line.find("// ")
+ if idx < 3:
+ continue
+ line = line[idx + 3:]
+ idx = line.find(" ")
+ if idx < 3:
+ continue
+ line = line[:idx]
+
+ w.write("{0},0,0,0,0,0,0,0,\"{1}\"\n".format(cnt, line))
+ quests[line] = cnt
+ cnt = cnt + 1
+ return quests
diff --git a/hercules/code/server/storage.py b/hercules/code/server/storage.py
new file mode 100644
index 0000000..6bddb52
--- /dev/null
+++ b/hercules/code/server/storage.py
@@ -0,0 +1,81 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+import re
+
+from code.fileutils import readFile
+from code.stringutils import stripNewLine
+from code.server.dbitem import Item
+
+def convertStorage():
+ srcFile = "olddb/storage.txt"
+ dstFile = "newdb/storage.sql"
+ fieldsSplit = re.compile("\t")
+ comaSplit = re.compile(",")
+ spaceSplit = re.compile(" ")
+ tpl = readFile("templates/storage.sql")
+ firstLine = True
+ with open(dstFile, "w") as w:
+ w.write(tpl)
+ w.write("INSERT INTO `storage` VALUES ")
+ with open(srcFile, "r") as r:
+ for line in r:
+ if line[:2] == "//":
+ continue
+ rows = fieldsSplit.split(line)
+ if len(rows) == 2:
+ continue
+ if len(rows) != 3:
+ print "wrong storage.txt line: " + stripNewLine(line)
+ continue
+
+ tmp = comaSplit.split(rows[0])
+ accountId = tmp[0]
+# storage_amount = tmp[1]
+
+ data = spaceSplit.split(rows[1])
+ for itemStr in data:
+ if itemStr == "":
+ continue
+
+ tmp = comaSplit.split(itemStr)
+ item = Item()
+ item.unknownId = tmp[0]
+ item.itemId = tmp[1]
+ item.amount = tmp[2]
+ item.equip = tmp[3]
+ item.color = tmp[4]
+ item.refine = tmp[5]
+ item.attribute = tmp[6]
+ item.card0 = tmp[7]
+ item.card1 = tmp[8]
+ item.card2 = tmp[9]
+ item.card3 = "0"
+
+ if firstLine == False:
+ w.write(",\n")
+ else:
+ firstLine = False
+
+ w.write(("({id},{account_id},{nameid},{amount},{equip},{identify}," +
+ "{refine},{attribute},{card0},{card1},{card2},{card3}," +
+ "{expire_time},{bound},{unique_id})").format(
+ id = 0,
+ account_id = accountId,
+ nameid = item.itemId,
+ amount = item.amount,
+ equip = item.equip,
+ identify = "1",
+ refine = item.refine,
+ attribute = item.attribute,
+ card0 = "0",
+ card1 = "0",
+ card2 = "0",
+ card3 = "0",
+ expire_time = "0",
+ bound = "0",
+ unique_id = "0"
+ ))
+ w.write("\n")
diff --git a/hercules/code/server/tmw/__init__.py b/hercules/code/server/tmw/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/hercules/code/server/tmw/athena.py b/hercules/code/server/tmw/athena.py
new file mode 100644
index 0000000..6548b2e
--- /dev/null
+++ b/hercules/code/server/tmw/athena.py
@@ -0,0 +1,207 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2015 Evol Online
+# Author: Andrei Karas (4144)
+
+import re
+
+from code.stringutils import stripNewLine
+from code.server.dbitem import Item
+from code.server.dbskill import Skill
+from code.server.dbuser import User
+
+comaSplit = re.compile(",")
+spaceSplit = re.compile(" ")
+
+def parseInventory(line, data):
+ items = []
+
+ if data == "":
+ return items
+
+ rows = spaceSplit.split(data)
+ if len(rows) < 1:
+ return items
+
+ for data2 in rows:
+ if data2 == "":
+ continue
+
+ rows2 = comaSplit.split(data2)
+
+ if len(rows2) != 12:
+ print "wrong inventory in account.txt line: " + stripNewLine(line)
+ continue
+
+ item = Item()
+
+ item.uknownId = rows2[0]
+ item.itemId = rows2[1]
+ item.amount = rows2[2]
+ item.equip = rows2[3]
+ item.color = rows2[4]
+ item.refine = rows2[5]
+ item.attribute = rows2[6]
+ item.card0 = rows2[7]
+ item.card1 = rows2[8]
+ item.card2 = rows2[9]
+ item.card3 = rows2[10]
+ item.broken = rows2[11]
+
+ items.append(item)
+
+ return items
+
+def parseSkills(line, data):
+ skills = []
+
+ if data == "":
+ return skills
+
+ rows = spaceSplit.split(data)
+ if len(rows) < 1:
+ return skills
+
+ for data2 in rows:
+ if data2 == "":
+ continue
+
+ rows2 = comaSplit.split(data2)
+
+ if len(rows2) != 2:
+ print "wrong skills in account.txt line: " + stripNewLine(line)
+ continue
+
+ skill = Skill()
+ skill.skillId = rows2[0]
+ skill.level = int(rows2[1]) & 0xffff
+ skill.flags = (int(rows2[1]) * 0xffff) & 0xffff
+
+ skills.append(skill)
+
+ return skills
+
+def parseVars(line, data):
+ variables = {}
+
+ if data == "":
+ return variables
+
+ rows = spaceSplit.split(data)
+ if len(rows) < 1:
+ return variables
+
+ for data2 in rows:
+ if data2 == "":
+ continue
+
+ rows2 = comaSplit.split(data2)
+
+ if len(rows2) != 2:
+ print "wrong variables in account.txt line: " + stripNewLine(line)
+ continue
+
+ variables[rows2[0]] = rows2[1]
+
+ return variables
+
+def readAthena():
+ srcFile = "olddb/athena.txt"
+ fieldsSplit = re.compile("\t")
+
+ users = {}
+ with open(srcFile, "r") as r:
+ for line in r:
+ if line[:2] == "//":
+ continue
+ rows = fieldsSplit.split(line)
+ if len(rows) == 2:
+ continue
+ if len(rows) != 20:
+ print "wrong account.txt line: " + stripNewLine(line)
+ continue
+
+ user = User()
+
+ user.char_id = rows[0]
+ tmp = comaSplit.split(rows[1])
+ user.account_id = tmp[0]
+ user.char_num = tmp[1]
+ user.char_name = rows[2]
+
+ tmp = comaSplit.split(rows[3])
+ user.char_class = tmp[0]
+ user.base_level = tmp[1]
+ user.job_level = tmp[2]
+
+ tmp = comaSplit.split(rows[4])
+ user.base_exp = tmp[0]
+ user.job_exp = tmp[1]
+ user.zeny = tmp[2]
+
+ tmp = comaSplit.split(rows[5])
+ user.hp = tmp[0]
+ user.max_hp = tmp[1]
+ user.sp = tmp[2]
+ user.max_sp = tmp[3]
+
+ tmp = comaSplit.split(rows[6])
+ user.stat_str = tmp[0]
+ user.stat_agi = tmp[1]
+ user.stat_vit = tmp[2]
+ user.stat_int = tmp[3]
+ user.stat_dex = tmp[4]
+ user.stat_luk = tmp[5]
+
+ tmp = comaSplit.split(rows[7])
+ user.status_point = tmp[0]
+ user.skill_point = tmp[1]
+
+ tmp = comaSplit.split(rows[8])
+ user.option = tmp[0]
+ user.karma = tmp[1]
+ user.manner = tmp[2]
+
+ tmp = comaSplit.split(rows[9])
+ user.party_id = tmp[0]
+ user.guild_id = tmp[1]
+ user.pet_id = tmp[2]
+
+ tmp = comaSplit.split(rows[10])
+ user.hair = tmp[0]
+ user.hair_color = tmp[1]
+ user.clothes_color = tmp[2]
+
+ tmp = comaSplit.split(rows[11])
+ user.weapon = tmp[0]
+ user.shield = tmp[1]
+ user.head_top = tmp[2]
+ user.head_mid = tmp[3]
+ user.head_bottom = tmp[4]
+
+ tmp = comaSplit.split(rows[12])
+ user.last_map = tmp[0]
+ user.last_x = tmp[1]
+ user.last_y = tmp[2]
+
+ tmp = comaSplit.split(rows[13])
+ user.save_map = tmp[0]
+ user.save_x = tmp[1]
+ user.save_y = tmp[2]
+
+ user.partner_id = "0"
+ user.language_id = "0"
+
+ # skip 14
+
+ user.inventory = parseInventory(line, rows[15])
+
+ # skip 16
+
+ user.skills = parseSkills(line, rows[17])
+
+ user.variables = parseVars(line, rows[18])
+
+ users[user.char_id] = user
+
+ return users
diff --git a/hercules/code/server/tmw/consts.py b/hercules/code/server/tmw/consts.py
new file mode 100644
index 0000000..5b8403e
--- /dev/null
+++ b/hercules/code/server/tmw/consts.py
@@ -0,0 +1,129 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+import os
+import re
+
+from code.fileutils import readFile
+from code.stringutils import stripNewLine
+
+fieldsSplit = re.compile(":")
+
+def getConstsFile(srcDir):
+ files = os.listdir(srcDir)
+ for srcFile in files:
+ if srcFile.find("const") == 0 and srcFile[-4:] == ".txt":
+ yield srcDir + srcFile
+
+def readOneConst(r, line):
+ key = ""
+ val = ""
+ depr = 0
+ if line.find(": {") > 0:
+ rows = fieldsSplit.split(line)
+ key = rows[0].strip()
+ line = r.next().strip()
+ rows = fieldsSplit.split(line)
+ if len(rows) != 2:
+ print "error"
+ return ("", "", 0)
+ if rows[0] == "Value":
+ val = rows[1]
+ line = r.next().strip()
+ rows = fieldsSplit.split(line)
+ if len(rows) != 2:
+ print "error"
+ return ("", "", 0)
+ rows[1] = rows[1].strip()
+ if rows[0] == "Deprecated" and rows[1].find("true") == 0:
+ depr = 1
+ else:
+ rows = fieldsSplit.split(line)
+ if len(rows) != 2:
+ return ("", "", 0)
+ key = rows[0];
+ val = rows[1]
+ return (key, val, depr)
+
+def writeConst(w, const):
+ if const[2] == 1:
+ w.write("\t{0}: {{\n\t\tValue:{1}\n\t\tDeprecated: true\n\t}}\n".format(const[0], const[1]))
+ else:
+ w.write("\t{0}:{1}\n".format(const[0], const[1]))
+
+def convertConsts(quests, npcIds):
+ dstFile = "newserverdata/db/constants.conf"
+ fields = dict()
+ vals = [("MF_NOTELEPORT", "mf_noteleport"),
+ ("MF_NORETURN", "mf_noreturn"),
+ ("MF_MONSTER_NOTELEPORT", "mf_monster_noteleport"),
+ ("MF_NOSAVE", "mf_nosave"),
+ ("MF_NOPENALTY", "mf_nopenalty"),
+ ("MF_PVP", "mf_pvp"),
+ ("MF_PVP_NOPARTY", "mf_pvp_noparty"),
+ ("MF_PVP_NOCALCRANK", "mf_pvp_nocalcrank"),
+ ("MF_NOWARP", "mf_nowarp"),
+ ("MF_NOWARPTO", "mf_nowarpto"),
+ ("MF_SNOW", "mf_snow"),
+ ("MF_FOG", "mf_fog"),
+ ("MF_SAKURA", "mf_sakura"),
+ ("MF_LEAVES", "mf_leaves"),
+ ("MF_TOWN", "mf_town"),
+ ("sc_poison", "SC_POISON"),
+ ("sc_slowpoison", "SC_SLOWPOISON"),
+ ("sc_adrenaline", "SC_ADRENALINE"),
+ ]
+ with open(dstFile, "w") as w:
+ tpl = readFile("templates/constants.tpl")
+ w.write(tpl);
+ srcFile = "serverdata/db/constants.conf"
+ with open(srcFile, "r") as r:
+ for line in r:
+ if line.find("**************************************************************************/") >= 0:
+ break
+
+ for line in r:
+ line = line.strip()
+ if len(line) < 2 or line[0:2] == "//" or line[0:2] == "/*":
+ continue
+ const = readOneConst(r, line)
+ if const[0] == "comment__":
+ continue
+ fields[const[0]] = const[1].strip()
+ writeConst(w, const)
+
+ srcDir = "oldserverdata/world/map/db/"
+ w.write("// tmw constants\n")
+
+ fieldsSplit = re.compile("\t+")
+
+ for srcFile in getConstsFile(srcDir):
+ with open(srcFile, "r") as r:
+ for line in r:
+ if len(line) < 2 or line[0:2] == "//":
+ continue
+ line = line.replace(" ", "\t")
+ rows = fieldsSplit.split(line)
+ if len(rows) != 2:
+ continue
+
+ for val in vals:
+ if rows[0] == val[0]:
+ rows[0] = val[1]
+ if rows[0] in quests:
+ rows[1] = str(quests[rows[0]]) + "\n"
+ if rows[0] in fields:
+ if fields[rows[0]] != rows[1][:-1]:
+ print "warning: different const values for {0} ({1}, {2})".format(rows[0], rows[1][:-1], fields[rows[0]])
+ else:
+ writeConst(w, (rows[0], stripNewLine(rows[1]), 0))
+ w.write("// tmw npcs\n")
+ for npc in npcIds:
+ if npc == -1:
+ key = "MINUS1"
+ else:
+ key = str(npc)
+ writeConst(w, ("NPC" + key, npc, 0))
+ w.write("}")
diff --git a/hercules/code/server/tmw/itemdb.py b/hercules/code/server/tmw/itemdb.py
new file mode 100644
index 0000000..beccd09
--- /dev/null
+++ b/hercules/code/server/tmw/itemdb.py
@@ -0,0 +1,264 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+import os
+import re
+
+from code.configutils import writeStartScript, writeEndScript, writeIntField, writeStrField
+from code.fileutils import readFile
+
+def getItemDbFile(srcDir):
+ files = os.listdir(srcDir)
+ for srcFile in files:
+ if srcFile.find("item_db") >= 0:
+ yield srcFile
+
+def replaceStr(line):
+ vals = [
+ ("setskill ", "addtoskill "),
+ ("zeny", "Zeny"),
+ ("sc_poison", "SC_POISON"),
+ ("sc_slowpoison", "SC_SLOWPOISON"),
+ ("sex", "Sex"),
+ ("SEX", "Sex"),
+
+ (".gat", ""),
+ ("Bugleg", "BugLeg"),
+ ("set BugLeg, 0;", "//set BugLeg, 0;"),
+ ("set CaveSnakeLamp, 0;", "//set CaveSnakeLamp, 0;"),
+ ("set Class, @BaseClass;", "//set Class, @BaseClass;"),
+ ("goto_Exit;", "goto L_Exit;"),
+ ("if @spants_state < 7 goto", "if(@spants_state < 7) goto"),
+ ("isdead()", "ispcdead()"),
+ ("changeSex", "changecharsex()"),
+ ("getpartnerid2()", "getpartnerid()"),
+
+ ("getmap()", "getmapname()"),
+ ("L_end", "L_End"),
+ ("gmcommand", "atcommand"),
+ ("MF_NOSAVE", "mf_nosave"),
+ ("S_update_var", "S_Update_Var"),
+ ("L_teach", "L_Teach"),
+ ("L_focus", "L_Focus"),
+ ("L_unfocus", "L_Unfocus"),
+ ("L_main", "L_Main"),
+ ("L_next", "L_Next"),
+ ("L_close", "L_Close"),
+ ("@NpcName$", "@npcname$"),
+ ("@cost", "@Cost"),
+ ("@TEMP", "@temp"),
+ ("L_Menuitems", "L_MenuItems"),
+ ("L_no_item", "L_No_Item"),
+ ("L_no_water", "L_No_Water"),
+ ("L_NOT_ENOUGH", "L_No_Water"),
+ ("L_bye", "L_Bye"),
+ ("L_NOHELP", "L_NoHelp"),
+ ("L_Eyepatch", "L_EyePatch"),
+ ("@PC_STAMINA", "@pc_stamina"),
+ ("L_magic", "L_Magic"),
+ ("L_cont", "L_Cont"),
+ ("L_accept", "L_Accept"),
+ ("L_no_event", "L_No_Event"),
+ ("L_event_done", "L_Event_Done"),
+ ("L_nobeer", "L_NoBeer"),
+ ("L_iron", "L_Iron"),
+ ("L_sulphur", "L_Sulphur"),
+ ("L_red", "L_Red"),
+ ("L_yellow", "L_Yellow"),
+ ("L_green", "L_Green"),
+ ("L_orange", "L_Orange"),
+ ("L_pink", "L_Pink"),
+ ("L_purple", "L_Purple"),
+ ("L_question", "L_Question"),
+ ("L_quest", "L_Quest"),
+ ("L_dead", "L_Dead"),
+ ("L_menu", "L_Menu"),
+ ("L_give", "L_Give"),
+ ("@Items$", "@items$"),
+ ("@menuItems$", "@menuitems$"),
+ ("L_Teach_initial", "L_Teach_Initial"),
+ ("L_finish", "L_Finish"),
+ ("L_No_ash", "L_No_Ash"),
+ ("L_No_water", "L_No_Water"),
+ ("L_cave", "L_Cave"),
+ ("L_farewell", "L_Farewell"),
+ ("@Q_forestbow_", "@Q_Forestbow_"),
+ ("L_game", "L_Game"),
+ ("L_good", "L_Good"),
+ ("L_abort", "L_Abort"),
+ ("@menuID", "@menuid"),
+ ("L_NO_ITEM", "L_No_Item"),
+ ("L_HELP", "L_Help"),
+ ("L_Noitem", "L_NoItem"),
+ ("L_No_fur", "L_No_Fur"),
+ ("@EXP", "@Exp"),
+ ("L_water", "L_Water"),
+ ("L_get", "L_Get"),
+ ("L_happy", "L_Happy"),
+ ("L_cheat", "L_Cheat"),
+ ("@Reward_EXP", "@Reward_Exp"),
+ ("@REWARD_EXP", "@Reward_Exp"),
+ ("L_no_money", "L_No_Money"),
+ ("@MinLevel", "@minLevel"),
+ ("L_return", "L_Return"),
+ ("L_intro", "L_Intro"),
+ ("L_full", "L_Full"),
+ ("@minlevel", "@minLevel"),
+ ("@MinLevel", "@minLevel"),
+ ("L_Cleanup", "L_CleanUp"),
+ ("L_Alreadystarted", "L_AlreadyStarted"),
+ ("@amount", "@Amount"),
+ ("L_again", "L_Again"),
+ ("L_no_potion", "L_No_Potion"),
+ ("L_wizard_hat", "L_Wizard_Hat"),
+ ("L_notenough", "L_NotEnough"),
+ ("L_offer", "L_Offer"),
+ ("L_giveup", "L_GiveUp"),
+ ("L_not_ready", "L_Not_Ready"),
+ ("@MobID", "@mobId"),
+ ("@mobID", "@mobId"),
+ ("L_naked", "L_Naked"),
+ ("L_shortcut", "L_Shortcut"),
+ ("L_shirt", "L_Shirt"),
+ ("L_goodjob", "L_GoodJob"),
+ ("L_kill", "L_Kill"),
+ ("L_nothing", "L_Nothing"),
+ ("L_lowlevel", "L_LowLevel"),
+ ("@mask", "@Mask"),
+ ("Foice", "FoiceItem"),
+ ("LanternaJack", "LanternaJackItem"),
+ # fix at same time usage with same name function and variable
+ ("\"DailyQuestPoints\"", "\"DailyQuestPointsFunc\""),
+ ("sc_adrenaline", "SC_ADRENALINE"),
+ ];
+
+ for val in vals:
+ line = line.replace(val[0], val[1]);
+ return line
+
+def convertItemDb(isNew):
+ srcDir = "oldserverdata/world/map/db/"
+ dstFile = "newserverdata/db/re/item_db.conf"
+ if os.path.isfile(dstFile):
+ os.remove(dstFile)
+ constsFile = "newserverdata/db/const.txt"
+ if os.path.isfile(constsFile):
+ os.remove(constsFile)
+ fieldsSplit = re.compile(",")
+ scriptsSplit = re.compile("},")
+ scriptsTextClean = re.compile('([{}])')
+ scriptsTextComma = re.compile('^,')
+ scriptsTextColon = re.compile('; ')
+ items = dict()
+
+ tpl = readFile("templates/item_db.tpl")
+ with open(dstFile, "w") as w:
+ w.write(tpl)
+ with open(constsFile, "a") as c:
+ c.write("// items\n");
+ for srcFile in getItemDbFile(srcDir):
+ with open(srcDir + srcFile, "r") as r:
+ for line in r:
+ if len(line) < 2 or line[0] == "#" or line[0:2] == "//":
+ continue
+ line = replaceStr(line)
+ rows = fieldsSplit.split(line)
+ if len(rows) < 5 or rows[0] == "0":
+ continue
+
+ sz = len(rows)
+ if sz > 19:
+ sz = 19
+ for f in xrange(0, sz):
+ rows[f] = rows[f].strip()
+
+ items[rows[1]] = {'id':rows[0],'buy':rows[4],'name':rows[1]}
+ items[rows[0]] = {'id':rows[0],'buy':rows[4],'name':rows[1]}
+ items[int(rows[0])] = {'id':rows[0],'buy':rows[4],'name':rows[1]}
+ # set all values then write
+ w.write("{\n")
+ c.write("{0}\t{1}\n".format(rows[1], rows[0]))
+ writeIntField(w, "Id", rows[0])
+ writeStrField(w, "AegisName", rows[1])
+ if isNew == True:
+ offset = -1
+ else:
+ offset = 0
+ writeStrField(w, "Name", rows[offset + 2])
+ if rows[offset + 3] == "0":
+ itemType = "2"
+ else:
+ itemType = rows[offset + 3]
+ writeIntField(w, "Type", itemType)
+
+ writeIntField(w, "Buy", rows[offset + 4])
+ if int(rows[offset + 4])*.75 <= int(rows[offset + 5])*1.24:
+ writeIntField(w, "Sell", str(int(rows[offset + 4])*.75))
+ else:
+ writeIntField(w, "Sell", rows[offset + 5])
+ writeIntField(w, "Weight", rows[offset + 6])
+ writeIntField(w, "Atk", rows[offset + 7])
+ writeIntField(w, "Matk", "0")
+ writeIntField(w, "Def", rows[offset + 8])
+ writeIntField(w, "Range", rows[offset + 9])
+ writeIntField(w, "Slots", "0")
+ writeIntField(w, "Job", "0xFFFFFFFF")
+ writeIntField(w, "Upper", "0x3F")
+ writeIntField(w, "Gender", rows[offset + 12])
+ writeIntField(w, "Loc", rows[offset + 13])
+ writeIntField(w, "WeaponLv", rows[offset + 14])
+ writeIntField(w, "EquipLv", rows[offset + 15])
+ writeIntField(w, "Refine", "false")
+ if isNew == True:
+ offset = 2
+ else:
+ offset = 0
+ if rows[offset + 13] == "2":
+ writeIntField(w, "View", "1")
+ elif rows[offset + 13] == "34":
+ writeIntField(w, "View", "11")
+ elif rows[offset + 13] == "32768":
+ writeIntField(w, "View", "1")
+ elif itemType == "4": # weapon
+ writeIntField(w, "View", "1")
+ else:
+ writeIntField(w, "View", rows[0])
+ writeIntField(w, "BindOnEquip", "false")
+ writeIntField(w, "BuyingStore", "false")
+ writeIntField(w, "Delay", "0")
+ writeIntField(w, "Sprite", "0")
+
+ scripts = ""
+ if isNew == True:
+ offset = -1
+ else:
+ offset = 0
+ for f in xrange(offset + 17, len(rows)):
+ scripts = scripts + ", " + rows[f]
+ rows = scriptsSplit.split(scripts)
+ # Needs .split(';') and \n each
+ if len(rows) > 1:
+ UseScript = scriptsTextColon.sub(';',scriptsTextComma.sub('', scriptsTextClean.sub('', rows[0]))).strip().split(';')
+ EquipScript = scriptsTextColon.sub(';',scriptsTextComma.sub('', scriptsTextClean.sub('', rows[1]))).strip().split(';')
+ else:
+ UseScript = ""
+ EquipScript = ""
+ # move to for stmt
+ if len(UseScript) > 1:
+ writeStartScript(w, "Script")
+ for uline in UseScript:
+ if len(uline) > 0:
+ w.write(" {0};\n".format(uline))
+ writeEndScript(w)
+ if len(EquipScript) > 1:
+ writeStartScript(w, "OnEquipScript")
+ for eline in EquipScript:
+ if len(eline) > 0:
+ w.write(" {0};\n".format(eline))
+ writeEndScript(w)
+
+ w.write("},\n")
+ w.write(")\n")
+ return items
diff --git a/hercules/code/server/tmw/main.py b/hercules/code/server/tmw/main.py
new file mode 100644
index 0000000..bfbf5ef
--- /dev/null
+++ b/hercules/code/server/tmw/main.py
@@ -0,0 +1,49 @@
+#! /usr/bin/env python
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+from sets import Set
+
+from code.server.account import convertAccount
+from code.server.accreg import convertAccReg
+from code.server.party import convertParty
+from code.server.storage import convertStorage
+from code.server.db.char import saveCharTableCustom
+from code.server.db.charregnumdb import saveCharRegNumDbTable
+from code.server.db.inventory import saveInventoryTable
+from code.server.db.skill import saveSkillTable
+from code.server.tmw.athena import readAthena
+from code.server.tmw.consts import convertConsts
+from code.server.tmw.itemdb import convertItemDb
+from code.server.tmw.mobdb import convertMobDb
+from code.server.tmw.mobskilldb import convertMobSkillDb
+from code.server.tmw.npcs import createMainScript, convertNpcs
+from code.server.utils import cleanServerData
+
+def serverTmwMain(isNew):
+ try:
+ cleanServerData()
+ except:
+ print "Updating server"
+ createMainScript()
+ items = convertItemDb(isNew)
+ npcIds = Set()
+ convertNpcs(items, npcIds)
+ convertMobDb(items)
+ quests = dict()
+ convertConsts(quests, npcIds)
+ convertMobSkillDb()
+
+def dbTmwMain():
+ convertAccount()
+ users = readAthena()
+# saveCharTable(users)
+ saveCharTableCustom(users)
+ saveCharRegNumDbTable(users)
+ saveSkillTable(users)
+ saveInventoryTable(users)
+ convertStorage()
+ convertAccReg()
+ convertParty(users)
diff --git a/hercules/code/server/tmw/mobdb.py b/hercules/code/server/tmw/mobdb.py
new file mode 100644
index 0000000..04d1fce
--- /dev/null
+++ b/hercules/code/server/tmw/mobdb.py
@@ -0,0 +1,170 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+import math
+import os
+import re
+
+from code.configutils import isHaveData, writeCondField2, writeStartBlock, writeEndBlock, writeIntField, writeStrField, writeFieldArr, writeIntField2, writeFieldList, writeSubField
+from code.fileutils import readFile
+
+def getMobDbFile(srcDir):
+ files = os.listdir(srcDir)
+ for srcFile in files:
+ if srcFile.find("mob_db") >= 0:
+ yield srcFile
+
+def replaceStr(line):
+ vals = [
+ ("lanternaJack", "LanternaJack"),
+ ("foice", "Foice"),
+ ("BlueFairy", "BlueFairyMob"),
+ ("RedFairy", "RedFairyMob"),
+ ("GreenFairy", "GreenFairyMob"),
+ ("Scorpion", "ScorpionMob"),
+ ("Tritan", "TritanMob"),
+ ("Ukar", "UkarMob"),
+ ];
+
+ for val in vals:
+ line = line.replace(val[0], val[1]);
+ return line
+
+def convertMobDb(items):
+ srcDir = "oldserverdata/world/map/db/"
+ dstFile = "newserverdata/db/re/mob_db.conf"
+ fieldsSplit = re.compile(",")
+ tpl = readFile("templates/mob_db.tpl")
+ with open(dstFile, "w") as w:
+ w.write(tpl)
+ for srcFile in getMobDbFile(srcDir):
+ with open(srcDir + srcFile, "r") as r:
+ for line in r:
+ if len(line) < 2 or line[:2] == "//" or line[:1] == "#":
+ w.write(line)
+ continue
+ line = replaceStr(line)
+ rows = fieldsSplit.split(line)
+ for f in xrange(0, len(rows)):
+ rows[f] = rows[f].strip()
+ try:
+ val = int(rows[23])
+ if val < 20:
+ rows[23] = "20"
+ except:
+ None
+
+ # Experience and Job experience, following *tmw-eathena*/src/map/mob.c
+ calc_exp = 0
+
+ if rows[6] == "0":
+ if int(rows[4]) <= 1:
+ calc_exp = 1
+
+ mod_def = 100 - int(rows[11])
+
+ if mod_def == 0:
+ mod_def = 1
+
+ effective_hp = ((50 - int(rows[18])) * int(rows[4]) / 50) + (2 * int(rows[18]) * int(rows[4]) / mod_def)
+ attack_factor = (int(rows[9]) + int(rows[10]) + int(rows[13]) / 3 + int(rows[17]) / 2 + int(rows[18])) * (1872 / int(rows[26])) / 4
+ dodge_factor = (int(rows[3]) + int(rows[14]) + int(rows[18]) / 2)**(4 / 3)
+ persuit_factor = (3 + int(rows[8])) * (int(rows[24]) % 2) * 1000 / int(rows[25])
+ aggression_factor = 1
+
+ if False:
+ aggression_factor = 10 / 9
+
+ base_exp_rate = 100 # From *tmw-eathena-data*/conf/battle_athena.conf
+
+ calc_exp = int(math.floor(effective_hp * (math.sqrt(attack_factor) + math.sqrt(dodge_factor) + math.sqrt(persuit_factor) + 55)**3 * aggression_factor / 2000000 * base_exp_rate / 100))
+
+ if calc_exp < 1:
+ calc_exp = 1
+ else:
+ calc_exp = rows[6]
+
+ w.write("{\n")
+ writeIntField(w, "Id", rows[0])
+ writeStrField(w, "SpriteName", rows[1])
+ writeStrField(w, "Name", rows[2])
+ writeIntField(w, "Lv", rows[3])
+ writeIntField(w, "Hp", rows[4])
+ writeIntField(w, "Sp", rows[5])
+ writeIntField(w, "Exp", calc_exp)
+ writeIntField(w, "JExp", rows[7])
+ writeIntField(w, "AttackRange", rows[8])
+ writeFieldArr(w, "Attack", rows[9], rows[10])
+ writeIntField(w, "Def", rows[11])
+ writeIntField(w, "Mdef", rows[12])
+ writeStartBlock(w, "Stats")
+ writeIntField2(w, "Str", rows[13])
+ writeIntField2(w, "Agi", rows[14])
+ writeIntField2(w, "Vit", rows[15])
+ writeIntField2(w, "Int", rows[16])
+ writeIntField2(w, "Dex", rows[17])
+ writeIntField2(w, "Luk", rows[18])
+ writeEndBlock(w)
+ writeIntField(w, "ViewRange", rows[19])
+ writeIntField(w, "ChaseRange", 10)
+ writeIntField(w, "Size", rows[21])
+ writeIntField(w, "Race", rows[22])
+ writeFieldList(w, "Element", int(rows[23]) % 10, int(rows[23]) / 20)
+ mode = int(rows[24], 0)
+ if mode != 0:
+ writeStartBlock(w, "Mode")
+ writeCondField2(w, mode & 0x0001, "CanMove")
+ writeCondField2(w, mode & 0x0002, "Looter")
+ writeCondField2(w, mode & 0x0004, "Aggressive")
+ writeCondField2(w, mode & 0x0008, "Assist")
+ writeCondField2(w, mode & 0x0010, "CastSensorIdle")
+ writeCondField2(w, mode & 0x0020, "Boss")
+ writeCondField2(w, mode & 0x0040, "Plant")
+ writeCondField2(w, mode & 0x0080, "CanAttack")
+ writeCondField2(w, mode & 0x0100, "Detector")
+ writeCondField2(w, mode & 0x0200, "CastSensorChase")
+ writeCondField2(w, mode & 0x0400, "ChangeChase")
+ writeCondField2(w, mode & 0x0800, "Angry")
+ writeCondField2(w, mode & 0x1000, "ChangeTargetMelee")
+ writeCondField2(w, mode & 0x2000, "ChangeTargetChase")
+ writeCondField2(w, mode & 0x4000, "TargetWeak")
+ writeCondField2(w, mode & 0x8000, "LiveWithoutMaster")
+ writeEndBlock(w)
+ writeIntField(w, "MoveSpeed", rows[25])
+ writeIntField(w, "AttackDelay", rows[26])
+ writeIntField(w, "AttackMotion", rows[27])
+ writeIntField(w, "DamageMotion", rows[28])
+ writeIntField(w, "MvpExp", rows[45])
+
+ if isHaveData(rows, 47, 3):
+ writeStartBlock(w, "MvpDrops")
+ for f in range(0, 3):
+ value = rows[47 + f * 2]
+ chance = rows[47 + f * 2]
+ if value == "" or value == "0" or chance == "" or chance == "0":
+ continue
+ value = int(value)
+ if value not in items:
+ w.write("// Error: mvp drop with id {0} not found in item db\n".format(value))
+ print("Error: mvp drop with id {0} not found in item db".format(value))
+ else:
+ writeSubField(w, items[value]["name"], chance)
+ writeEndBlock(w)
+ if isHaveData(rows, 29, 10):
+ writeStartBlock(w, "Drops")
+ for f in range(0, 10):
+ value = rows[29 + f * 2]
+ chance = rows[30 + f * 2]
+ if value == "" or value == "0" or chance == "" or chance == "0":
+ continue
+ value = int(value)
+ if value not in items:
+ w.write("// Error: drop with id {0} not found in item db\n".format(value))
+ print("Error: drop with id {0} not found in item db".format(value))
+ else:
+ writeSubField(w, items[value]["name"], chance)
+ writeEndBlock(w)
+ w.write("},\n")
+ w.write(")\n")
diff --git a/hercules/code/server/tmw/mobskilldb.py b/hercules/code/server/tmw/mobskilldb.py
new file mode 100644
index 0000000..7188824
--- /dev/null
+++ b/hercules/code/server/tmw/mobskilldb.py
@@ -0,0 +1,55 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+import re
+
+from code.fileutils import readFile
+
+def convertMobSkillDb():
+ srcFile = "oldserverdata/world/map/db/mob_skill_db.txt"
+ dstFile = "newserverdata/db/re/mob_skill_db.txt"
+ fieldsSplit = re.compile(",")
+ notintown = re.compile("notintown")
+ notintownSub = re.compile("notintown,0")
+ with open(srcFile, "r") as r:
+ with open(dstFile, "w") as w:
+ tpl = readFile("templates/mob_skill_db.tpl")
+ w.write(tpl)
+ for line in r:
+ if notintown.search(line):
+ if line[0:2] == "//":
+ line = ''
+ line = notintownSub.sub("myhpltmaxrate,20",line)
+ if len(line) < 2 or line[0:2] == "//":
+ w.write(line)
+ continue
+ rows = fieldsSplit.split(line)
+ if len(rows) < 10:
+ w.write(line)
+ continue
+
+ for f in xrange(0, len(rows)):
+ rows[f] = rows[f].strip()
+
+ w.write("{0},{1},{2},{3},{4},{5},{6},"
+ "{7},{8},{9},{10},{11},{12},{13},"
+ "{14},,,,\n".format(
+ rows[0],
+ rows[1],
+ rows[2],
+ rows[3],
+ rows[4],
+ rows[5],
+ rows[6],
+ rows[7],
+ rows[8],
+ rows[9],
+ rows[10],
+ rows[11],
+ rows[12],
+ rows[13],
+ rows[14]
+ ))
+
diff --git a/hercules/code/server/tmw/npcs.py b/hercules/code/server/tmw/npcs.py
new file mode 100644
index 0000000..c51da78
--- /dev/null
+++ b/hercules/code/server/tmw/npcs.py
@@ -0,0 +1,878 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+import os
+import re
+
+from code.fileutils import makeDir
+from code.stringutils import stripWindows, stripNewLine
+
+mapsConfFile = "newserverdata/conf/maps.conf"
+if os.path.isfile(mapsConfFile):
+ os.remove(mapsConfFile)
+mapsIndexFile = "newserverdata/db/map_index.txt"
+if os.path.isfile(mapsIndexFile):
+ os.remove(mapsIndexFile)
+npcMainScript = "newserverdata/npc/re/scripts_main.conf"
+mapsIndex = 1
+scriptRe = re.compile("^(((?P[^/](.+)),([ ]*)(?P[\d]+),([ ]*)(?P[\d]+),([ ]*)(?P[\d]+))|(?Pfunction)|-)" +
+ "[|](?Pscript)[|](?P[^|]+)([|]"
+ "(?P[\d-]+)((,((?P[\d]+),(?P[\d]+)))|)|)$")
+
+scriptRe2 = re.compile("^(((?P[^/](.+))[.]gat,([ ]*)(?P[\d]+),([ ]*)(?P[\d]+),([ ]*)(?P[\d]+))|(?Pfunction)|-)" +
+ "[\t](?Pscript)[\t](?P[\w#'\\[\\]_ äü.-]+)[\t]"
+ "(((?P[\d-]+)((,((?P[\d-]+),(?P[\d-]+)))|)(|,)(|[ \t]))|){(|[ ])$")
+
+shopRe = re.compile("^(?P[^/](.+)),([ ]*)(?P[\d]+),([ ]*)(?P[\d]+),([ ]*)(?P[\d]+)(|,(?P[\d]+))" +
+ "[|](?Pshop)[|](?P[^|]+)[|]"
+ "(?P[\d-]+),(?P(.+))$")
+
+shopRe2 = re.compile("^(?P[^/](.+))[.]gat,([ ]*)(?P[\d]+),([ ]*)(?P[\d]+),([ ]*)(?P[\d]+)" +
+ "[\t](?Pshop)[\t](?P[^\t]+)[\t]"
+ "(?P[\d]+),(?P(.+))$")
+
+mapFlagRe = re.compile("^(?P[^/](.+))" +
+ "[|](?Pmapflag)[|](?P[\w#']+)(|[|](?P.*))$")
+
+mapFlagRe2 = re.compile("^(?P[^/](.+))[.]gat" +
+ "[ ](?Pmapflag)[ ](?P[\w#']+)(|[ ](?P.*))$")
+
+warpRe = re.compile("^(?P[^/](.+)),([ ]*)(?P[\d]+),([ ]*)(?P[\d]+)[|]"
+ "(?Pwarp)[|](?P[^|]+)[|](?P[\d-]+),(?P[\d-]+),(?P[^/](.+)),([ ]*)(?P[\d]+),([ ]*)(?P[\d]+)$")
+warpRe2 = re.compile("^(?P[^/](.+))[.]gat,([ ]*)(?P[\d]+),([ ]*)(?P[\d]+)([\t]+)"
+ "(?Pwarp)[\t](?P[^\t]+)([\t]+)(?P[\d-]+),(?P[\d-]+),(?P[^/](.+))[.]gat,([ ]*)(?P[\d]+),([ ]*)(?P[\d]+)$")
+warpRe3 = re.compile("^(?P[^/](.+)),([ ]*)(?P[\d]+),([ ]*)(?P[\d]+)[|]"
+ "(?Pwarp)[|](?P[\d-]+),(?P[\d-]+),(?P[^/](.+)),([ ]*)(?P[\d]+),([ ]*)(?P[\d]+)$")
+
+monsterRe = re.compile("^(?P[^/](.+)),([ ]*)(?P[\d]+),([ ]*)(?P[\d]+),([ ]*)(?P[\d-]+),(?P[\d-]+)[|]"
+ "(?Pmonster)[|](?P[^|]+)[|]"
+ "(?P[\d]+),(?P[\d]+),(?P[\d]+)ms,(?P[\d]+)ms(|,(?P[\w+-:#]+))$")
+
+monsterRe2 = re.compile("^(?P[^/](.+))[.]gat,([ ]*)(?P[\d]+),([ ]*)(?P[\d]+),([ ]*)(?P[\d-]+),(?P[\d-]+)\t"
+ "(?Pmonster)[\t](?P[\w#' ]+)([\t]+)"
+ "(?P[\d]+),(?P[\d]+),(?P[\d]+),(?P[\d]+)(|,(?P[\w+-:#]+))$")
+
+setRe = re.compile("^(?P[ ]+)set[ ](?P[^,]+),([ ]*)(?P[^;]+);$");
+
+class ScriptTracker:
+ pass
+
+def createMainScript():
+ with open(npcMainScript, "w") as w:
+ w.write("npc: npc/functions/main.txt\n")
+ w.write("import: npc/scripts.conf\n")
+
+def convertNpcs(items, npcIds):
+ processNpcDir("oldserverdata/world/map/npc/", "newserverdata/npc/", items, npcIds)
+
+def processNpcDir(srcDir, dstDir, items, npcIds):
+ makeDir(dstDir)
+ files = os.listdir(srcDir)
+ for file1 in files:
+ if file1[0] == '.':
+ continue
+ srcPath = os.path.abspath(srcDir + os.path.sep + file1)
+ dstPath = os.path.abspath(dstDir + os.path.sep + file1)
+ if not os.path.isfile(srcPath):
+ processNpcDir(srcPath, dstPath, items, npcIds)
+ else:
+ if file1[-5:] == ".conf" or file1[-4:] == ".txt":
+ processNpcFile(srcPath, dstPath, items, npcIds)
+
+def processNpcFile(srcFile, dstFile, items, npcIds):
+# print "file: " + srcFile
+ tracker = ScriptTracker()
+ tracker.insideScript = False
+ tracker.items = items
+ tracker.npcIds = npcIds
+ with open(srcFile, "r") as r:
+ with open(dstFile, "w") as w:
+ tracker.w = w
+ for line in r:
+ line = stripWindows(line)
+ tracker.line = line
+ res = convertTextLine(tracker)
+ if res:
+ w.write(tracker.line)
+
+def convertTextLine(tracker):
+ line = tracker.line
+ if line[:5] == "map: ":
+ processScriptMapLine(line)
+ return False
+
+ if len(line) >= 2 and line[0:2] == "//":
+ return False
+
+ if line == "};\n":
+ tracker.w.write("}\n");
+ return False
+
+ idx = line.find("|script|")
+ if idx <= 0:
+ idx = line.find("\tscript\t")
+ if idx >= 0:
+ processScript(tracker)
+ return False
+ idx = line.find("|shop|")
+ if idx <= 0:
+ idx = line.find("\tshop\t")
+ if idx >= 0:
+ processShop(tracker)
+ return False
+ idx = line.find("|monster|")
+ if idx <= 0:
+ idx = line.find("\tmonster\t")
+ if idx >= 0:
+ processMonster(tracker)
+ return False
+ idx = line.find("|warp|")
+ if idx <= 0:
+ idx = line.find("\twarp\t")
+ if idx >= 0:
+ processWarp(tracker)
+ return False
+ idx = line.find("|mapflag|")
+ if idx <= 0:
+ idx = line.find(".gat mapflag ")
+ if idx >= 0:
+ processMapFlag(tracker)
+ return False
+ processStrReplace(tracker)
+ return False
+
+def processScriptMapLine(line):
+ global mapsIndex
+ line = stripNewLine(line)
+ if line[-4:] == ".gat":
+ line = line[:-4]
+ with open(mapsConfFile, "a") as w:
+ w.write(line + "\n")
+
+ with open(mapsIndexFile, "a") as w:
+ w.write("{0} {1}\n".format(line[5:], mapsIndex))
+ mapsIndex = mapsIndex + 1
+
+def writeScript(w, m, npcIds):
+ try:
+ if m.group("function") != None:
+ isFunction = True
+ else:
+ isFunction = False
+ except:
+ isFunction = False
+
+ if isFunction:
+ w.write("function");
+ elif m.group("x") == None or (m.group("x") == 0 and m.group("y") == 0): # float npc
+ w.write("-")
+ else:
+ w.write("{0},{1},{2},{3}".format(m.group("map"), m.group("x"), m.group("y"), m.group("dir")))
+ if isFunction:
+ funcName = m.group("name")
+ if funcName == "DailyQuestPoints":
+ funcName = "DailyQuestPointsFunc"
+ w.write("\t{0}\t{1}\t".format(m.group("tag"), funcName));
+ else:
+ class_ = m.group("class")
+ if class_ == "0": # hidden npc
+ class_ = 32767
+ elif class_ == None:
+ class_ = -1;
+ else:
+ class_ = int(class_)
+# if class_ > 125 and class_ <= 400:
+# class_ = class_ + 100
+ npcIds.add(int(class_))
+ if class_ == -1:
+ class_ = "MINUS1"
+ w.write("\t{0}\t{1}\tNPC{2}".format(m.group("tag"), m.group("name"), class_));
+
+def processScript(tracker):
+ line = tracker.line[:-1]
+ w = tracker.w
+
+ m = scriptRe.search(line)
+ if m == None:
+ m = scriptRe2.search(line)
+ if m == None:
+ print "error in parsing: " + line
+ w.write("!!!error parsing line")
+ w.write(line)
+ return
+# print "source=" + line
+# print "map={0} x={1} y={2} dir={3} tag={4} name={5} class={6}, xs={7}, ys={8}".format(
+# m.group("map"), m.group("x"), m.group("y"), m.group("dir"),
+# m.group("tag"), m.group("name"), m.group("class"), m.group("xs"), m.group("ys"))
+ # debug
+
+ try:
+ if m.group("function") != None:
+ isFunction = True
+ else:
+ isFunction = False
+ except:
+ isFunction = False
+
+ writeScript(w, m, tracker.npcIds)
+
+ if m.group("xs") != None:
+ w.write(",{0},{1}".format(m.group("xs"), m.group("ys")));
+
+ if isFunction == False:
+ w.write(",{\n");
+ else:
+ w.write("{\n");
+
+def itemsToShop(tracker, itemsStr):
+ itemsSplit = re.compile(",")
+ itemsSplit2 = re.compile(":")
+ itemsDict = tracker.items
+ outStr = ""
+ items = itemsSplit.split(itemsStr)
+ for str2 in items:
+ parts = itemsSplit2.split(str2)
+ if len(parts) != 2:
+ print "Wrong shop item name {0}: {1}".format(str2, itemsStr)
+ continue
+ if parts[1][0] == "*":
+ parts[1] = str((int(parts[1][1:]) * int(itemsDict[parts[0].strip()]['buy'])))
+ if outStr != "":
+ outStr = outStr + ","
+ itemName = parts[0].strip()
+ if itemName in itemsDict:
+ outStr = outStr + itemsDict[itemName]['id'] + ":" + parts[1]
+ else:
+ print "Wrong item name in shop: {0}".format(itemName)
+ return outStr
+
+def processShop(tracker):
+ line = tracker.line
+ w = tracker.w
+
+ m = shopRe.search(line)
+ if m == None:
+ m = shopRe2.search(line)
+ if m == None:
+ print "error in parsing: " + line
+ w.write("!!!error parsing line")
+ w.write(line)
+ return
+
+# print "source=" + line[:-1]
+# print "map={0} x={1} y={2} dir={3} tag={4} name={5} class={6} items={7}".format(
+# m.group("map"), m.group("x"), m.group("y"), m.group("dir"),
+# m.group("tag"), m.group("name"), m.group("class"), m.group("items"))
+
+ writeScript(w, m, tracker.npcIds)
+ w.write("," + itemsToShop(tracker, m.group("items")) + "\n")
+
+def processMapFlag(tracker):
+ line = tracker.line
+ w = tracker.w
+
+ m = mapFlagRe.search(line)
+ if m == None:
+ m = mapFlagRe2.search(line)
+ if m == None:
+ print "error in parsing: " + line
+ w.write("!!!error parsing line")
+ w.write(line)
+ return
+# print "source=" + line[:-1]
+# print "map={0} tag={1} name={2} flag={3}".format(
+# m.group("map"), m.group("tag"), m.group("name"), m.group("flag"))
+
+ if m.group("name") == "town" or m.group("name") == "resave":
+ w.write("//")
+ w.write("{0}\t{1}\t{2}".format(m.group("map"), m.group("tag"), m.group("name")))
+ if m.group("flag") == None:
+ w.write("\n")
+ else:
+ w.write("\t{0}\n".format(m.group("flag")))
+
+def processWarp(tracker):
+ line = tracker.line
+ w = tracker.w
+ m = warpRe.search(line)
+ noName = False
+ if m == None:
+ m = warpRe2.search(line)
+ if m == None:
+ m = warpRe3.search(line)
+ noName = True
+ if m == None:
+ print "error in parsing: " + line
+ w.write("!!!error parsing line")
+ w.write(line)
+ return
+
+ if noName == True:
+ warpName = "{0}_{1}_{2}".format(m.group("map"), m.group("x"), m.group("y"))
+ else:
+ warpName = m.group("name")
+
+# print "source=" + line[:-1]
+# print "map={0} xy={1},{2} tag={3} name={4} xs={5} ys={6} target: map={7} xy={8},{9}".format(
+# m.group("map"), m.group("x"), m.group("y"), m.group("tag"), warpName, m.group("xs"), m.group("ys"), m.group("targetmap"), m.group("targetx"), m.group("targety"))
+
+ xs = int(m.group("xs"))
+ ys = int(m.group("ys"))
+ if xs < 0:
+ xs = 0
+ if ys < 0:
+ ys = 0
+ w.write("{0},{1},{2},{3}\t{4}\t{5}\t{6},{7},{8},{9},{10}\n".format(
+ m.group("map"), m.group("x"), m.group("y"), "0", m.group("tag"), warpName,
+ xs, ys, m.group("targetmap"), m.group("targetx"), m.group("targety")))
+
+
+def processMonster(tracker):
+ line = tracker.line
+ w = tracker.w
+ m = monsterRe.search(line)
+ if m == None:
+ m = monsterRe2.search(line)
+ if m == None:
+ print "error in parsing: " + line
+ w.write("!!!error parsing line")
+ w.write(line)
+ return
+
+# print "source=" + line[:-1]
+# print ("map={0} xy={1},{2} xs={3} ys={4} tag={5} name={6} class={7} " +
+# "num={8} delays={9},{10}, label={11}").format(
+# m.group("map"), m.group("x"), m.group("y"), m.group("xs"), m.group("ys"),
+# m.group("tag"), m.group("name"), m.group("class"),
+# m.group("num"), m.group("delay1"), m.group("delay2"), m.group("label"))
+
+ if m.group("label") != None:
+ label = "," + m.group("label")
+ else:
+ label = ""
+ w.write("{0},{1},{2},{3},{4}\t{5}\t{6}\t{7},{8},{9},{10}{11}\n".format(m.group("map"),
+ m.group("x"), m.group("y"), m.group("xs"), m.group("ys"),
+ m.group("tag"), m.group("name"),
+ m.group("class"), m.group("num"), m.group("delay1"), m.group("delay2"), label))
+
+
+def processStrReplace(tracker):
+ line = tracker.line
+ w = tracker.w
+ if line == "{\n":
+ return
+ vals = [
+ ("setskill ", "addtoskill "),
+ ("zeny", "Zeny"),
+ ("sc_poison", "SC_POISON"),
+ ("sc_slowpoison", "SC_SLOWPOISON"),
+ ("sex", "Sex"),
+ ("SEX", "Sex"),
+
+ (".gat", ""),
+ ("Bugleg", "BugLeg"),
+ ("set BugLeg, 0;", "//set BugLeg, 0;"),
+ ("set CaveSnakeLamp, 0;", "//set CaveSnakeLamp, 0;"),
+ ("set Class, @BaseClass;", "//set Class, @BaseClass;"),
+ ("goto_Exit;", "goto L_Exit;"),
+ ("if @spants_state < 7 goto", "if(@spants_state < 7) goto"),
+ ("isdead()", "ispcdead()"),
+ ("changeSex", "changecharsex()"),
+ ("getpartnerid2()", "getpartnerid()"),
+ ("getpartnerid2(0)", "getpartnerid()"),
+ ("getgmlevel(0)", "getgmlevel()"),
+ ("if @beer_count > 4", "if (@beer_count > 4)"),
+ ("if !(@Q_status)", "if (!(@Q_status))"),
+ ("if isin(\"021-1\", 130, 120, 140, 125) ", "if (isin(\"021-1\", 130, 120, 140, 125)) "),
+ ("if divorce(0) goto L_", "if (divorce()) goto L_"),
+
+ ("getmap()", "getmapname()"),
+ ("L_end", "L_End"),
+ ("gmcommand", "atcommand"),
+ ("MF_NOSAVE", "mf_nosave"),
+ ("S_update_var", "S_Update_Var"),
+ ("L_teach", "L_Teach"),
+ ("L_focus", "L_Focus"),
+ ("L_unfocus", "L_Unfocus"),
+ ("L_main", "L_Main"),
+ ("L_next", "L_Next"),
+ ("L_close", "L_Close"),
+ ("@NpcName$", "@npcname$"),
+ ("@npcName$", "@npcname$"),
+ ("@cost", "@Cost"),
+ ("@TEMP", "@temp"),
+ ("L_Menuitems", "L_MenuItems"),
+ ("L_no_item", "L_No_Item"),
+ ("L_no_water", "L_No_Water"),
+ ("L_NOT_ENOUGH", "L_No_Water"),
+ ("L_bye", "L_Bye"),
+ ("L_NOHELP", "L_NoHelp"),
+ ("L_Eyepatch", "L_EyePatch"),
+ ("@PC_STAMINA", "@pc_stamina"),
+ ("L_magic", "L_Magic"),
+ ("L_cont", "L_Cont"),
+ ("L_accept", "L_Accept"),
+ ("L_no_event", "L_No_Event"),
+ ("L_event_done", "L_Event_Done"),
+ ("L_nobeer", "L_NoBeer"),
+ ("L_iron", "L_Iron"),
+ ("L_sulphur", "L_Sulphur"),
+ ("L_red", "L_Red"),
+ ("L_yellow", "L_Yellow"),
+ ("L_green", "L_Green"),
+ ("L_orange", "L_Orange"),
+ ("L_pink", "L_Pink"),
+ ("L_purple", "L_Purple"),
+ ("L_question", "L_Question"),
+ ("L_quest", "L_Quest"),
+ ("L_dead", "L_Dead"),
+ ("L_menu", "L_Menu"),
+ ("L_give", "L_Give"),
+ ("@Items$", "@items$"),
+ ("@menuItems$", "@menuitems$"),
+ ("L_Teach_initial", "L_Teach_Initial"),
+ ("L_finish", "L_Finish"),
+ ("L_No_ash", "L_No_Ash"),
+ ("L_no_ash", "L_No_Ash"),
+ ("L_No_water", "L_No_Water"),
+ ("L_cave", "L_Cave"),
+ ("L_farewell", "L_Farewell2"),
+ ("@Q_forestbow_", "@Q_Forestbow_"),
+ ("L_game", "L_Game"),
+ ("L_good", "L_Good"),
+ ("L_abort", "L_Abort"),
+ ("@menuID", "@menuid"),
+ ("L_NO_ITEM", "L_No_Item"),
+ ("L_HELP", "L_Help"),
+ ("L_Noitem", "L_NoItem"),
+ ("L_No_fur", "L_No_Fur"),
+ ("@EXP", "@Exp"),
+ ("L_water", "L_Water"),
+ ("L_get", "L_Get"),
+ ("L_happy", "L_Happy"),
+ ("L_cheat", "L_Cheat"),
+ ("@Reward_EXP", "@Reward_Exp"),
+ ("@REWARD_EXP", "@Reward_Exp"),
+ ("L_no_money", "L_No_Money"),
+ ("@MinLevel", "@minLevel"),
+ ("L_return", "L_Return"),
+ ("L_intro", "L_Intro"),
+ ("L_full", "L_Full"),
+ ("@minlevel", "@minLevel"),
+ ("@MinLevel", "@minLevel"),
+ ("L_Cleanup", "L_CleanUp"),
+ ("L_Alreadystarted", "L_AlreadyStarted"),
+ ("@amount", "@Amount"),
+ ("L_again", "L_Again"),
+ ("L_no_potion", "L_No_Potion"),
+ ("L_wizard_hat", "L_Wizard_Hat"),
+ ("L_notenough", "L_NotEnough"),
+ ("L_offer", "L_Offer"),
+ ("L_giveup", "L_GiveUp"),
+ ("L_not_ready", "L_Not_Ready"),
+ ("@MobID", "@mobId"),
+ ("@mobID", "@mobId"),
+ ("L_naked", "L_Naked"),
+ ("L_shortcut", "L_Shortcut"),
+ ("L_shirt", "L_Shirt"),
+ ("L_goodjob", "L_GoodJob"),
+ ("L_kill", "L_Kill"),
+ ("L_nothing", "L_Nothing"),
+ ("L_lowlevel", "L_LowLevel"),
+ ("L_MENU", "L_Menu"),
+ ("L_potion", "L_Potion"),
+ ("L_fertig", "L_Fertig"),
+ ("L_exit", "L_Exit"),
+ ("L_fight", "L_Fight"),
+ ("L_start", "L_Start"),
+ ("L_unvollst", "L_Unvollst"),
+ ("L_no_room_for_rings", "L_No_Room_For_Rings"),
+ ("@mask", "@Mask"),
+ ("@MAP$", "@map$"),
+ ("baselevel", "BaseLevel"),
+ ("L_Lifestones_Trade_Missing", "L_Lifestones_TM"),
+ ("L_Caretaker_first_reward", "L_Caretaker_fr"),
+ ("L_NohMask_TravelingTroupe", "L_NohMask_TravT"),
+ ("L_listen_to_a_story_first", "L_listen_to_sf"),
+ ("L_post_ironpowder_option", "L_post_ip_op"),
+ ("L_Sulphur_teach_spell_no", "L_Sulphur_tspell"),
+ ("L_Lifestones_MakeSelf_yes", "L_Lifestones_MSy"),
+ ("L_Lifestones_MakeSelf_no", "L_Lifestones_MSn"),
+ ("L_Caretaker_later_rewards", "L_Caretaker_lr"),
+ ("L_boneknife_quest_completed", "L_boneknife_qc"),
+ ("L_boneknife_quest_tooweak", "L_boneknife_qtw"),
+ ("L_tanktop_insufficient_cloth", "L_tanktop_ic"),
+ ("L_dark_green_q_toolittle", "L_dark_greenqtit"),
+ ("L_about_schools_minimenu", "L_about_sch_mm"),
+ ("L_insufficient_BaseLevel", "L_insuf_BL"),
+ ("L_Teach_Initial_nonature", "L_Teach_Ininn"),
+ ("L_Teach_CheckAdvanceTo2_fail", "L_Teach_CATo2f"),
+ ("L_Levelup2_must_practice", "L_Levelup2_mpr"),
+ ("L_Airlia_intro_mana_loss", "L_Airlia_iml"),
+ ("L_knife_quest_completecheck", "L_knife_q_cc"),
+ ("L_Magic_purify_explained", "L_Magic_pur_exp"),
+ ("L_tanktop_insufficient_Zeny", "L_tanktop_ins_Z"),
+ ("L_monster_oil_knows_recipe", "L_monsteroil_kn_r"),
+ ("L_Teach_CheckAdvanceToLOH", "L_TeachChATLOH"),
+ ("L_knife_quest_missing_stingers", "L_knife_qm_sti"),
+ ("L_Magic_train_tree_backgd", "L_Mag_tr_tr_ba"),
+ ("SUB_pick_one_of_many_items", "SUB_pick_1_m_it"),
+ ("L_about_other_prerequisites", "L_a_o_prereq"),
+ ("L_monster_oil_why_dangerous", "L_monstero_w_dang"),
+ ("L_Teach_LOH_advance_abort0", "L_Teach_LOH_a_a0"),
+ ("L_Teach_LOH_advance_abort1", "L_Teach_LOH_a_a1"),
+ ("L_knife_quest_missing_mushrooms", "L_knife_q_m_mushr"),
+ ("L_Magic_train_sagatha_fail", "L_Magic_tsag_f"),
+ ("L_Q_manaseed_touched_short", "L_Q_manas_tou_sh"),
+ ("L_monster_oil_ingredients", "L_monsoil_ingr"),
+ ("L_snakeskins_completecheck", "L_snakes_comc"),
+ ("L_Magic_train_sagatha_lvl1", "L_Mag_tr_sag_lvl1"),
+ ("L_make_mana_potion_missing", "L_make_m_p_mis"),
+ ("L_monster_oil_where_gold", "L_monoil_wg"),
+ ("L_golden_scorpion_wrestle_intro", "L_gold_scor_wr_i"),
+ ("L_component_quest_missing", "L_comp_q_mis"),
+ ("L_monster_oil_start_brew", "L_monsto_st_br"),
+ ("L_golden_requires_knife_quest_done", "L_gold_req_kn_q_d"),
+ ("LL_student_4_wrong_potion", "L_stud_4_wrong_p"),
+ ("L_monster_oil_missing_gold", "L_monsto_mis_g"),
+ ("L_golden_requires_knife_quest", "L_gold_req_kn_q"),
+ ("LL_Magic_skill_insufficient", "L_Mag_sk_insuf"),
+ ("L_monster_oil_out_of_leaves", "L_monso_oo_lea"),
+ ("L_too_lowlevel_for_stinger", "L_too_lol_f_sti"),
+ ("L_monster_oil_leaf_color", "L_monso_le_co"),
+ ("L_golden_scorpion_over_ask", "L_gold_scor_ov_a"),
+ ("S_monster_oil_random_move", "L_monso_rand_mo"),
+ ("L_golden_scorpion_ask_again", "L_gold_scor_as_ag"),
+ ("L_monster_oil_random_0_lighten", "L_monso_rand_0_li"),
+ ("L_golden_scorpion_wrestle_again", "L_gold_scorp_wre_ag"),
+ ("L_monster_oil_explode_dodge", "L_monso_expl_dod"),
+ ("L_golden_scorpion_wrestle", "L_gold_scorp_wre"),
+ ("L_monster_oil_no_gold_end", "L_monso_no_go_e"),
+ ("L_mopox_cure_", "L_moc_"),
+ ("L_mopox_failed_", "L_mof_"),
+ # fix at same time usage with same name function and variable
+ ("\"DailyQuestPoints\"", "\"DailyQuestPointsFunc\""),
+ # TMW-BR errors
+ ("@cerveja", "@Cerveja"),
+ ("@custo", "@Custo"),
+ ("@genero", "@Genero"),
+ ("@gosmaVerme", "@GosmaVerme"),
+ ("@I", "@i"),
+ ("@valor", "@Valor"),
+
+ ("@peloBranco", "@PeloBranco"),
+ ("@pelobranco", "@PeloBranco"),
+ ("@raiz", "@Raiz"),
+ ("@senha", "@Senha"),
+ ("@garrafaVazia", "@GarrafaVazia"),
+ ("@GAMBOGE", "@Gamboge"),
+ ("@FM$", "@fm$"),
+ ("L_abrir", "L_Abrir"),
+ ("L_Abrir", "L_Abrir"),
+ ("L_acabou", "L_Acabou"),
+ ("L_ACABOU", "L_Acabou"),
+ ("L_Aceita", "L_Aceita"),
+ ("L_aceita", "L_Aceita"),
+ ("L_AceitaCapaceteDeMineiro", "L_AceitaCapMineiro"),
+ ("L_AceitaShortDeAlgodao", "L_AceitaShortAlgodao"),
+ ("L_AceitoFlechasDeFerro", "L_AceitaFlechaFerro"),
+ ("L_AceitoFlechasNormais", "L_AceitaFlechaNormal"),
+ ("L_Aceitou", "L_Aceitou"),
+ ("L_aceitou", "L_Aceitou"),
+ ("L_adicionar_registro_AGE", "L_addreg_AGE"),
+ ("L_adicionar_registro_GP", "L_addreg_GP"),
+ ("L_adicionar_registro_LVL", "L_addreg_LVL"),
+ ("L_Ajuda", "L_Ajuda"),
+ ("L_ajuda", "L_Ajuda"),
+ ("L_ajudaComMaisPresentes2", "L_AjudaMaisPresentes2"),
+ ("L_ajudaComMaisPresentes", "L_AjudaMaisPresentes"),
+ ("L_Apresentacao", "L_Apresentacao"),
+ ("L_apresentacao", "L_Apresentacao"),
+ ("L_Arco", "L_Arco"),
+ ("L_arco", "L_Arco"),
+ ("L_Azul", "L_Azul"),
+ ("L_azul", "L_Azul"),
+ ("L_boneknife_quest_completed", "L_BoneKnife_Completo"),
+ ("L_boneknife_quest_tooweak", "L_BoneKnife_Fraco"),
+ ("L_buscar_e_adicionar_AGE", "L_seekadd_AGE"),
+ ("L_buscar_e_adicionar_GP", "L_seekadd_GP"),
+ ("L_buscar_e_adicionar_LVL", "L_seekadd_LVL"),
+ ("L_buscar_e_selecionar_AGE", "L_seeksel_AGE"),
+ ("L_buscar_e_selecionar_GP", "L_seeksel_GP"),
+ ("L_buscar_e_selecionar_LVL", "L_seeksel_LVL"),
+ ("L_Check", "L_Check"),
+ ("L_CHECK", "L_Check"),
+ ("L_cheio", "L_Cheio"),
+ ("L_CHEIO", "L_Cheio"),
+ ("L_Close", "L_close"),
+ ("L_close", "L_close"),
+ ("L_coelhoProcurandoOvos", "L_CoelhoProcuraOvos"),
+ ("L_ComGrana", "L_ComGrana"),
+ ("L_comgrana", "L_ComGrana"),
+ ("L_comprimenta_conhecendo", "L_CumprimentaSabe"),
+ ("L_Concluida", "L_Concluida"),
+ ("L_concluida", "L_Concluida"),
+ ("L_confirmacao_invalida", "L_Inv_Conf"),
+ ("L_Congratulacoes", "L_Congratulacao"),
+ ("L_congratulacoes", "L_Congratulacao"),
+ ("L_Continua", "L_Continua"),
+ ("L_continua", "L_Continua"),
+ ("L_continua_destransformar", "L_Continua_destransf"),
+ ("L_Continua_destransformar", "L_Continua_destransf"),
+ ("L_continuacao", "L_Continuacao"),
+ ("L_Continuacao", "L_Continuacao"),
+ ("L_controlarNacionalidade", "L_ControlaNacao"),
+ ("L_ControlarNacionalidade", "L_ControlaNacao"),
+ ("L_conversa_com_garotas", "L_ConvesaGarota"),
+ ("L_conversa_com_garotos", "L_ConversaGaroto"),
+ ("L_cor", "L_Cor"),
+ ("L_Cor", "L_Cor"),
+ ("L_Curar", "L_Curar"),
+ ("L_curar", "L_Curar"),
+ ("L_dark_green_q_explain2", "L_VerdeEscuro_qexp2"),
+ ("L_dark_green_q_explain", "L_VerdeEscuro_qexp"),
+ ("L_dark_green_q_guess_0", "L_VerdeEscuro_qguess"),
+ ("L_dark_green_q_noslime", "L_VerdeEscuro_noslim"),
+ ("L_dark_green_q_toolittle", "L_VerdeEscuro_pouco"),
+ ("L_dark_green_q_toomuch", "L_VerdeEscuro_muito"),
+ ("L_dep_tudo", "L_Dep_Tudo"),
+ ("L_dep_Tudo", "L_Dep_Tudo"),
+ ("L_DesistenteSelecionado", "L_DesistSel"),
+ ("L_Desistir", "L_Desistir"),
+ ("L_desistir", "L_Desistir"),
+ ("L_desistir_de_assinar2", "L_DesistirAssinar2"),
+ ("L_DICA", "L_Dica"),
+ ("L_Dica", "L_Dica"),
+ ("L_dica", "L_Dica"),
+ ("L_Encontrei", "L_Encontrei"),
+ ("L_encontrei", "L_Encontrei"),
+ ("L_End", "L_End"),
+ ("L_end", "L_end"),
+ ("L_engana_player_novamente", "L_Engana2"),
+ ("L_EQUIP", "L_Equip"),
+ ("L_Equip", "L_Equip"),
+ ("L_ErrouPalavrasMagicas", "L_ErrouMagia2"),
+ ("L_explicacao", "L_Explicacao"),
+ ("L_Explicacao", "L_Explicacao"),
+ ("L_ExplicacaoCausaCrise", "L_ExplicacaoCrise"),
+ ("L_Explicar", "L_Explicar"),
+ ("L_explicar", "L_Explicar"),
+ ("L_fala2", "L_Fala2"),
+ ("L_Fala2", "L_Fala2"),
+ ("L_Fala", "L_Fala"),
+ ("L_fala", "L_Fala"),
+ ("L_falasRestauracaoErro", "L_FalaRestauraErro"),
+ ("L_Falta", "L_Falta"),
+ ("L_falta", "L_Falta"),
+ ("L_falta_ovos_novamente", "L_FaltaOvos2"),
+ ("L_Fechar", "L_Fechar"),
+ ("L_fechar", "L_Fechar"),
+ ("L_Fim", "L_Fim"),
+ ("L_fim", "L_Fim"),
+ ("L_FIM", "L_Fim"),
+ ("L_FimListaDesconectados", "L_FimListaOff"),
+ ("L_Fraco", "L_Fraco"),
+ ("L_fraco", "L_Fraco"),
+ ("L_FRACO", "L_Fraco"),
+ ("L_GanhaPartyParticipar", "L_GanhaPartyP"),
+ ("L_golden_requires_knife_quest", "L_Ouroreq_Knife_q"),
+ ("L_golden_requires_knife_quest_done", "L_Ouroreq_Kinfe_ok"),
+ ("L_golden_scorpion_ask_again", "L_EscorpOuro_again"),
+ ("L_golden_scorpion_over_ask", "L_EscorpOuro_overask"),
+ ("L_golden_scorpion_wrestle", "L_EscorpOuro_Wrest"),
+ ("L_golden_scorpion_wrestle_again", "L_EscorpOuro_Wrest2"),
+ ("L_golden_scorpion_wrestle_intro", "L_EscorpOuro_WerestI"),
+ ("L_HistoriaAdagaBoneClaide", "L_HistoriaAdagaBC"),
+ ("L_Info", "L_Info"),
+ ("L_info", "L_Info"),
+ ("L_iniciaQuest", "L_IniciaQuest"),
+ ("L_IniciaQuest", "L_IniciaQuest"),
+ ("L_Inicio", "L_Inicio"),
+ ("L_inicio", "L_Inicio"),
+ ("L_Inicio_Segunda_Parte", "L_Inicio_P2"),
+ ("L_INSUF", "L_Insuf"),
+ ("L_insuf", "L_Insuf"),
+ ("L_Introfloresta", "L_IntroFloresta"),
+ ("L_introFloresta", "L_IntroFloresta"),
+ ("L_introfloresta", "L_IntroFloresta"),
+ ("L_itens", "L_Itens"),
+ ("L_Itens", "L_Itens"),
+ ("L_jaParticipandoPartida", "L_JaPartPart"),
+ ("L_knife_quest_completecheck", "L_Knife_CompCheck"),
+ ("L_knife_quest_missing_mushrooms", "L_Knife_MissMush"),
+ ("L_knife_quest_missing_mushrooms_2", "L_Knife_MissMush2"),
+ ("L_knife_quest_missing_stingers", "L_Knife_MissSting"),
+ ("L_knife_quest_missing_stingers_2", "L_Knife_MissSting2"),
+ ("L_limpar", "L_Limpar"),
+ ("L_Limpar", "L_Limpar"),
+ ("L_lista", "L_Lista2"),
+ ("L_listen_to_a_story_first", "L_ListenStory"),
+ ("L_longe", "L_Longe"),
+ ("L_LONGE", "L_Longe"),
+ ("L_loop_buscar_desconectados", "L_LoopOff"),
+ ("L_loop_buscar_lutadores2", "L_LoopLutador2"),
+ ("L_loop_buscar_lutadores", "L_LoopLutador"),
+ ("L_main_menu_post_setzer", "L_MM_PostSetzer"),
+ ("L_maisDoces //< pede o dobro de itens e dá o dobro de XP.", "L_MaisDoces //< pede o dobro de itens e dá o dobro de XP."),
+ ("L_Menu", "L_Menu"),
+ ("L_menu", "L_Menu"),
+ ("L_menuGM", "L_MenuGM"),
+ ("L_menugm", "L_MenuGM"),
+ ("L_MenuGM", "L_MenuGM"),
+ ("L_Menugm", "L_MenuGM"),
+ ("L_MenuItens", "L_MenuItens"),
+ ("L_menuItens", "L_MenuItens"),
+ ("L_missaoCogumeloCompleta", "L_MissCog_Ok"),
+ ("L_missaoCordaPescador1", "L_MissCorda_1"),
+ ("L_missaoCordaPescador2", "L_MissCorda_2"),
+ ("L_missaoEscudoMadeira1", "L_MissEscudMad_1"),
+ ("L_missaoEscudoMadeira2", "L_MissEscudMad_2"),
+ ("L_missaoMadeiraResistente", "L_MissMadeiraR"),
+ ("L_missaoMadeiraResistenteCompleta", "L_MissMadeiraR_Ok"),
+ ("L_missaoMaisPresentesCompleta", "L_MissMaisPresenteOK"),
+ ("L_missaoPocaoQueimadura", "L_MissPotQueim"),
+ ("L_missaoPresentesCompleta", "L_MissPresenteOk"),
+ ("L_MOBS_queimaduraEscorpiao", "L_MOB_QueimEscorp"),
+ ("L_nada", "L_Nada2"),
+ ("L_nao", "L_Nao2"),
+ ("L_naoPode", "L_NaoPode"),
+ ("L_naoSei", "L_NaoSei"),
+ ("L_naotenho", "L_NaoTenho"),
+ ("L_Naotenho", "L_NaoTenho"),
+ ("L_naoTrouxe", "L_NaoTrouxe"),
+ ("L_naotrouxe", "L_NaoTrouxe"),
+ ("L_Naotrouxe", "L_NaoTrouxe"),
+ ("L_novaPartida", "L_NovaPartida2"),
+ ("L_NovaPartida", "L_NovaPartida"),
+ ("L_novorecordindividual", "L_NovoRecordSeu"),
+ ("L_obrigado", "L_Obrigado2"),
+ ("L_obsidian_spork_intro", "L_ObsidianSporkIntro"),
+ ("L_OK", "L_Ok2"),
+ ("L_ok", "L_Ok3"),
+ ("L_onde", "L_Onde"),
+ ("L_opcaoFinalizarPartida", "L_opFimPart"),
+ ("L_opcaoIniciaPartidaErro", "L_opIniPartErro"),
+ ("L_opcaoParticipaJogoErro", "L_opPartJogoErro"),
+ ("L_Participante_Invalido", "L_Partc_Invalido"),
+ ("L_Participante_Perdedor", "L_Partc_Perde"),
+ ("L_Participante_Vencedor", "L_partc_Vence"),
+ ("L_pobre", "L_Pobre"),
+ ("L_porque", "L_Porque"),
+ ("L_possuiFragmento", "L_PossuiFragmento"),
+ ("L_preciso", "L_Preciso"),
+ ("L_PrecoPartyParticipar", "L_PrecoPartyPart"),
+ ("L_presente", "L_Presente"),
+ ("L_PrometeVoltarComGRana", "L_VoltoComGp"),
+ ("L_pronto", "L_Pronto"),
+ ("L_quem", "L_Quem"),
+ ("L_QueroArcoDeCurtoAlcance", "L_ArcoCurto"),
+ ("L_ReexplicaApostaParaCobrir", "L_ExpApostaCobrir"),
+ ("L_ReexplicaApostaParaIniciar", "L_ExpApostaIniciar"),
+ ("L_ret_tudo", "L_Ret_Tudo"),
+ ("L_ret_Tudo", "L_Ret_Tudo"),
+ ("L_retirar", "L_Retirar"),
+ ("L_retorno", "L_Retorno"),
+ ("L_retorno_Doces_Escondidos", "L_Retorno_DoceHid"),
+ ("L_Retorno_Segunda_Parte", "L_Retorno_Part2"),
+ ("L_return", "L_Return"),
+ ("L_rever", "L_Rever"),
+ ("L_rico", "L_Rico"),
+ ("L_saia", "L_Saia"),
+ ("L_salvar", "L_Salvar"),
+ ("L_Segunda_Parte_MaisPirulitos", "L_P2_MaisPirulito"),
+ ("L_Segunda_Parte_NaoPirulito", "L_P2_NaoPirulito"),
+ ("L_Segunda_Parte_Pirulito", "L_P2_Pirulito"),
+ ("L_Segunda_Parte_Satisfeito", "L_P2_Satisfeito"),
+ ("L_selecionar_registro_AGE", "L_selreg_AGE"),
+ ("L_selecionar_registro_GP", "L_selreg_GP"),
+ ("L_selecionar_registro_LVL", "L_selreg_LVL"),
+ ("L_semEspaco", "L_SemEspaco"),
+ ("L_semGrana", "L_SemGrana"),
+ ("L_semgrana", "L_SemGrana"),
+ ("L_semItem", "L_SemItem"),
+ ("L_semItens", "L_SemItens"),
+ ("L_semLevel", "L_SemLevel"),
+ ("L_semlevel", "L_SemLevel"),
+ ("L_semlugar", "L_SemLugar"),
+ ("L_semLugar", "L_SemLugar"),
+ ("L_semLvl", "L_SemLvl"),
+ ("L_semLVL", "L_SemLvl"),
+ ("L_SemLVL", "L_SemLvl"),
+ ("L_set", "L_Set"),
+ ("L_SET", "L_Set"),
+ ("L_ShieldNoLeatherPatch", "L_ShdNoLeathPatch"),
+ ("L_sim2", "L_Sim2"),
+ ("L_sim3", "L_Sim3"),
+ ("L_sim", "L_Sim"),
+ ("L_Nao2trouxe", "L_Nao2Trouxe"),
+ ("L_snakeskins_completecheck", "L_snakeskin_okcheck"),
+ ("L_SugerePesquisaDePreco", "L_PesquisePreco"),
+ ("L_tchau2", "L_Tchau2"),
+ ("L_tchau", "L_Tchau"),
+ ("L_This_shouldn_t_happen", "L_NaoDeviaAcontecer"),
+ ("L_too_lowlevel_for_stinger", "L_Stinger_LvlBaixo"),
+ ("L_verde", "L_Verde"),
+ ("L_verificaMaisPresentes", "L_MaisPresenteCheck"),
+ ("L_VOLTA", "L_Volta"),
+ ("L_voltaCheckIngredientes", "L_VoltaCheckItens"),
+ ("L_voltaComIngredientes", "L_VoltaComItens"),
+ ("L_warp", "L_Warp2"),
+ ("L_minissaia", "L_Minissaia"),
+ ("L_preMenu", "L_PreMenu"),
+ ("L_Regras", "L_regras"),
+ ("S_MOBS_queimaduraEscorpiao", "S_MOBS_QueimEscorp"),
+ ("S_MOBS_queimaduraTartaruga", "S_MOBS_QueimTartaruga"),
+ ("Refinamento \\+\" + (@menu", "Refinamento \" + (@menu"),
+ ("Bom! \Aqui vou eu...", "Bom! Aqui vou eu..."),
+ ("\\:\";", ":\";"),
+ ("if BaseLevel <= 10, set", "if (BaseLevel <= 10) set"),
+ ("\\: Passando", ": Passando"),
+ ("\\o/", "o/"),
+ ("(getgmlevel ==", "(getgmlevel() =="),
+ ("foice", "Foice"),
+ ("lanternaJack", "LanternaJack"),
+ ("0), set @preco, ", "0) set @preco, "),
+ ("255), set @preco, ", "255) set @preco, "),
+ ("L_pass", "L_Pass"),
+ ("Quest_threepwood1", "QUEST_threepwood1"),
+ ("L_no", "L_No"),
+ ("L_askHelp", "L_AskHelp"),
+ ("L_ask", "L_Ask"),
+ ("L_notEnough", "L_NotEnough"),
+ ("L_done", "L_Done"),
+ ("L_toomany", "L_TooMany"),
+ ("L_not_enough_money", "L_Not_enough_money"),
+ ("L_island", "L_Island"),
+ ("if @colorID == 2 ", "if (@colorID == 2) "),
+ ("L_help", "L_Help"),
+ ("L_yes", "L_Yes"),
+ ("if @opacityID == 2 ", "if (@opacityID == 2) "),
+ ("L_NohMask_Accuse_Respond", "L_NohMask_AResp"),
+ ("if @opacityID > 4 ", "if (@opacityID > 4) "),
+ ("if @tmpHairStyle > 0 ", "if (@tmpHairStyle > 0) "),
+ ("if @colorID > 6 ", "if (@colorID > 6) "),
+ ("if @opacityID < 0 ", "if (@opacityID < 0) "),
+ ("if countitem(\"MaggotSlime\") >= 10 goto", "if (countitem(\"MaggotSlime\") >= 10) goto"),
+ ("if @colorID < 0 set", "if (@colorID < 0) set"),
+ ];
+
+ for val in vals:
+ line = line.replace(val[0], val[1]);
+
+ idx = line.find("getmapmobs(")
+ if idx >= 0:
+ idx2 = line.find("\"", idx + len("getmapmobs(") + 1)
+ idx3 = line.find(")", idx + len("getmapmobs(") + 1)
+ if idx2 + 1 == idx3:
+ line = line[:idx2 + 1] + ",\"all\"" + line[idx2 + 1:]
+
+ line = line.replace("getmapmobs(", "mobcount(")
+
+ m = setRe.search(line);
+ if m != None:
+ line = "{0}{1} = {2};\n".format(m.group("space"), m.group("var"), m.group("val"))
+
+ w.write(line)
+
diff --git a/hercules/code/server/utils.py b/hercules/code/server/utils.py
new file mode 100644
index 0000000..ebbddf3
--- /dev/null
+++ b/hercules/code/server/utils.py
@@ -0,0 +1,12 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+from code.fileutils import removeAllFiles, makeDir
+
+def cleanServerData():
+ removeAllFiles("newserverdata")
+ makeDir("newserverdata/conf")
+ makeDir("newserverdata/db/re/")
+ makeDir("newserverdata/npc/re/")
diff --git a/hercules/code/servertoclient/__init__.py b/hercules/code/servertoclient/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/hercules/code/servertoclient/homunculuses.py b/hercules/code/servertoclient/homunculuses.py
new file mode 100644
index 0000000..678a96c
--- /dev/null
+++ b/hercules/code/servertoclient/homunculuses.py
@@ -0,0 +1,31 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+import re
+
+from code.fileutils import makeDir, readFile, saveFile
+
+def convertHomunculuses():
+ destDir = "clientdata/"
+ templatesDir = "templates/"
+ homunculusesDbFile = "serverdata/db/re/homunculus_db.txt"
+ fieldsSplit = re.compile(",")
+ makeDir(destDir)
+ tpl = readFile(templatesDir + "homunculus.tpl")
+ homunculuses = readFile(templatesDir + "homunculuses.xml")
+ data = ""
+ homunculusSprite = "monsters/tortuga.xml ";
+ with open(homunculusesDbFile, "r") as f:
+ for line in f:
+ if line == "" or line[0:2] == "//":
+ continue
+ rows = fieldsSplit.split(line)
+ if len(rows) < 9:
+ continue
+ data = data + tpl.format(
+ id = rows[0],
+ sprite = homunculusSprite,
+ name = rows[2])
+ saveFile(destDir + "homunculuses.xml", homunculuses.format(data))
diff --git a/hercules/code/servertoclient/items.py b/hercules/code/servertoclient/items.py
new file mode 100644
index 0000000..3fdb531
--- /dev/null
+++ b/hercules/code/servertoclient/items.py
@@ -0,0 +1,138 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+import re
+from sets import Set
+
+from code.fileutils import makeDir, readFile, saveFile
+from code.stringutils import stripQuotes2, strToXml
+
+def prepStat(val, text):
+ if val != "0" and val != "":
+ return " {0}=\"{1}\"\n".format(text, val)
+ return ""
+
+def convertItems():
+ destDir = "clientdata/"
+ templatesDir = "templates/"
+ itemsDbFile = "serverdata/sql-files/item_db_re.sql"
+ fieldsSplit = re.compile(",")
+ bracketsSplit = re.compile("[(]|[)]")
+ makeDir(destDir)
+ tpl = readFile(templatesDir + "item.tpl")
+ items = readFile(templatesDir + "items.xml")
+ data = ""
+ ids = Set()
+ with open(itemsDbFile, "r") as f:
+ for line in f:
+ if len(line) < 10 or line[0:2] == "//" or line[0:12] != "REPLACE INTO":
+ continue
+ rows = bracketsSplit.split(line)
+ if len(rows) < 2:
+ continue
+ rows = fieldsSplit.split(rows[1])
+ if len(rows) < 31:
+ continue
+ rows = stripQuotes2(rows)
+ itemId = rows[0]
+ name = rows[1]
+# name2 = rows[2]
+ itemType = rows[3]
+# priceBuy = rows[4]
+# priceSell = rows[5]
+ weight = rows[6]
+ atk = rows[7]
+ matk = rows[8]
+ defense = rows[9]
+ attackRange = rows[10]
+# slots = rows[11]
+# equipJobs = rows[12]
+# equipUpper = rows[12]
+# equipGender = rows[14]
+ equipLocations = rows[15]
+# weaponLevel = rows[16]
+# equipLevelMin = rows[17]
+# equipLevelMax = rows[18]
+# refinable = rows[19]
+ view = rows[20]
+# bindOnEquip = rows[21]
+# buyInStore = rows[22]
+ delay = rows[23]
+# tradeFlag = rows[24]
+# tradeGroup = rows[25]
+# nouseFlag = rows[26]
+# nouseGroup = rows[27]
+# stackAmount = rows[28]
+# stackFlag = rows[29]
+# sprite = rows[30]
+
+ name = name.replace("\\'", "'")
+ image = ""
+
+ statStr = prepStat(atk, "attack")
+ statStr = statStr + prepStat(matk, "mattack")
+ statStr = statStr + prepStat(defense, "defense")
+ statStr = statStr + prepStat(weight, "weight")
+ statStr = statStr + prepStat(attackRange, "range")
+ statStr = statStr + prepStat(delay, "speed")
+# print itemId + "," + equipLocations
+# typeStr = "other"
+ typeStr = "equip-legs"
+ spriteStr = "equipment/legs/trousers-male.xml"
+ image = "generic/box-fish.png"
+ if itemType == 0 or itemType == 2 or itemType == 18: # usable
+ image = "usable/bread.png"
+ typeStr = "usable"
+ spriteStr = "";
+ elif equipLocations == "0":
+ image = "usable/bread.png"
+ typeStr = "usable"
+ spriteStr = "";
+ elif equipLocations == "1":
+ image = "equipment/legs/shorts.png|S:#4d4d4d,514d47,686868,706662,919191,99917b,b6b6b6,c0b698,dfdfdf,e4dfca"
+ typeStr = "equip-legs"
+ spriteStr = "equipment/legs/shorts-male.xml|#4d4d4d,514d47,686868,706662,919191,99917b,b6b6b6,c0b698,dfdfdf,e0d5bf";
+ elif equipLocations == "2":
+ image = "equipment/weapons/knife.png"
+ typeStr = "equip-1hand"
+ spriteStr = "equipment/weapons/knife.xml";
+ elif equipLocations == "4":
+ image = "equipment/hands/armbands.png"
+ typeStr = "equip-arms"
+ spriteStr = "equipment/hands/armbands-male.xml";
+ elif equipLocations == "16":
+ image = "equipment/chest/cottonshirt.png|S:#3c3c3c,3e3c38,4d4d4d,514d47,686868,706662,919191,99917b,b6b6b6,c0b698,dfdfdf,e4dfca"
+ typeStr = "equip-torso"
+ spriteStr = "equipment/chest/cottonshirt-male.xml|#43413d,59544f,7a706c,8a8176,a69e88,d1c7a7,e0d5bf";
+ elif equipLocations == "64":
+ image = "equipment/feet/boots.png|S:#3c3c3c,40332d,4d4d4d,5e4a3d,686868,705740,919191,a1825d,b6b6b6,b59767,dfdfdf,dbbf88"
+ typeStr = "equip-feet"
+ spriteStr = "equipment/feet/boots-male.xml|#40332d,5e4a3d,705740,a1825d,b59767,dbbf88";
+ elif equipLocations == "136":
+ image = "equipment/chest/cottonshirt.png|S:#3c3c3c,3e3c38,4d4d4d,514d47,686868,706662,919191,99917b,b6b6b6,c0b698,dfdfdf,e4dfca"
+ typeStr = "equip-torso"
+ spriteStr = "equipment/chest/cottonshirt-male.xml|#43413d,59544f,7a706c,8a8176,a69e88,d1c7a7,e0d5bf";
+ elif equipLocations == "256":
+ image = "equipment/head/bandana.png"
+ typeStr = "equip-head"
+ spriteStr = "equipment/head/bandana-male.xml";
+ elif equipLocations == "512":
+ # no sprites in evol
+ image = "equipment/chest/sailorshirt.png"
+ typeStr = "equip-torso"
+ spriteStr = "equipment/chest/shirt-male.xml|#131913,1b231d,233129,35433e,4e6059,6c8279;#72571e,836737,a5854d,b18f45";
+
+ name = strToXml(name);
+
+ if itemId not in ids:
+ ids.add(itemId)
+ data = data + tpl.format(itemId, name, 0,
+ statStr, image, typeStr, spriteStr)
+ if view != "0" and view not in ids:
+ ids.add(view)
+ data = data + tpl.format(view, name, 0,
+ statStr, image, typeStr, spriteStr)
+
+ saveFile(destDir + "items.xml", items.format(data))
diff --git a/hercules/code/servertoclient/luas.py b/hercules/code/servertoclient/luas.py
new file mode 100644
index 0000000..8b401b7
--- /dev/null
+++ b/hercules/code/servertoclient/luas.py
@@ -0,0 +1,51 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2015 Evol Online
+# Author: Andrei Karas (4144)
+
+import re
+
+comaSplit = re.compile(",")
+equalSplit = re.compile("=")
+
+def extractLuaArray(fileName, arrName):
+ with open(fileName, "r") as f:
+ for line in f:
+ if line.find(arrName) == 0:
+ line = line[line.find("{") + 1:]
+ line = line[:line.find("}")]
+ return line
+ return ""
+
+def convertJobName():
+ jobs = dict()
+ jobNameFile = "rodata/decompiled/jobname.lua"
+ line = extractLuaArray(jobNameFile, "JobNameTable")
+ arr = comaSplit.split(line)
+ for itemStr in arr:
+ parts = equalSplit.split(itemStr.strip())
+ if parts[0].find("[jobtbl.") == 0:
+ key = parts[0].strip()
+ key = key[8:-1].strip()
+ val = parts[1].strip()
+ val = val[1:-1].strip()
+ jobs[key] = val
+ return jobs
+
+def convertIdentity(jobs):
+ idents = dict()
+ npcIdentityFile = "rodata/decompiled/npcidentity.lua"
+ line = extractLuaArray(npcIdentityFile, "jobtbl = ")
+ arr = comaSplit.split(line)
+ for itemStr in arr:
+ parts = equalSplit.split(itemStr.strip())
+ key = parts[0].strip()
+ val = parts[1].strip()
+ if key in jobs:
+ idents[val] = jobs[key].lower()
+ return idents
+
+def convertLuas():
+ jobs = convertJobName()
+ idtofile = convertIdentity(jobs)
+ return idtofile
diff --git a/hercules/code/servertoclient/maps.py b/hercules/code/servertoclient/maps.py
new file mode 100644
index 0000000..0f1ca5b
--- /dev/null
+++ b/hercules/code/servertoclient/maps.py
@@ -0,0 +1,48 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+import zlib
+
+from code.fileutils import readInt16, readInt32, makeDir, copyFile, readFile, readMapName, saveFile, readData
+from code.tileutils import getTileData, getTile
+
+def covertToTmx(f, mapsCount):
+ destDir = "clientdata/"
+ mapsDir = destDir + "maps/"
+ tilesetsDir = destDir + "graphics/tilesets/"
+ templatesDir = "templates/"
+ makeDir(mapsDir)
+ makeDir(tilesetsDir)
+ copyFile(templatesDir, tilesetsDir, "collision.png")
+ copyFile(templatesDir, tilesetsDir, "tileset.png")
+ tmx = readFile("templates/template.tmx")
+ for i in xrange(0, mapsCount):
+ name = readMapName(f)
+ print "converting map [{0:4}/{1:4}]: {2}".format(i, mapsCount + 1, name)
+ sx = readInt16(f)
+ sy = readInt16(f)
+ sz = readInt32(f)
+ mapData = readData(f, sz)
+ dc = zlib.decompressobj()
+ mapData = dc.decompress(mapData)
+ ground = ""
+ collision = ""
+ fringe = ""
+ for y in xrange(0, sy):
+ for x in xrange(0, sx):
+ tileData = getTileData(mapData, x, y, sx)
+ tile = getTile(tileData)
+ if x + 1 == sx and y + 1 == sy:
+ ground = ground + tile[0]
+ collision = collision + tile[1]
+ fringe = fringe + "0";
+ else:
+ ground = ground + tile[0] + ","
+ collision = collision + tile[1] + ","
+ fringe = fringe + "0,";
+ ground = ground + "\n"
+ collision = collision + "\n"
+ fringe = fringe + "\n"
+ saveFile(mapsDir + name + ".tmx", tmx.format(sx, sy, ground, collision, fringe))
diff --git a/hercules/code/servertoclient/mercenaries.py b/hercules/code/servertoclient/mercenaries.py
new file mode 100644
index 0000000..481f66c
--- /dev/null
+++ b/hercules/code/servertoclient/mercenaries.py
@@ -0,0 +1,31 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+import re
+
+from code.fileutils import makeDir, readFile, saveFile
+
+def convertMercenaries():
+ destDir = "clientdata/"
+ templatesDir = "templates/"
+ mercenariesDbFile = "serverdata/db/mercenary_db.txt"
+ fieldsSplit = re.compile(",")
+ makeDir(destDir)
+ tpl = readFile(templatesDir + "mercenary.tpl")
+ mercenaries = readFile(templatesDir + "mercenaries.xml")
+ data = ""
+ mercenarySprite = "monsters/croc.xml ";
+ with open(mercenariesDbFile, "r") as f:
+ for line in f:
+ if line == "" or line[0:2] == "//":
+ continue
+ rows = fieldsSplit.split(line)
+ if len(rows) < 9:
+ continue
+ data = data + tpl.format(
+ id = rows[0],
+ sprite = mercenarySprite,
+ name = rows[2])
+ saveFile(destDir + "mercenaries.xml", mercenaries.format(data))
diff --git a/hercules/code/servertoclient/monsters.py b/hercules/code/servertoclient/monsters.py
new file mode 100644
index 0000000..969795d
--- /dev/null
+++ b/hercules/code/servertoclient/monsters.py
@@ -0,0 +1,43 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+import re
+
+from code.fileutils import makeDir, readFile, saveFile
+from code.stringutils import strToXml, stripQuotes
+
+def convertMonsters(isNonFree = False, idtofile = None):
+ destDir = "clientdata/"
+ templatesDir = "templates/"
+ monstersDbFile = "serverdata/sql-files/mob_db_re.sql"
+ fieldsSplit = re.compile(",")
+ bracketsSplit = re.compile("[(]|[)]")
+ makeDir(destDir)
+ tpl = readFile(templatesDir + "monster.tpl")
+ monsters = readFile(templatesDir + "monsters.xml")
+ data = ""
+
+ with open(monstersDbFile, "r") as f:
+ for line in f:
+ if len(line) < 10 or line[0:2] == "//" or line[0:12] != "REPLACE INTO":
+ continue
+ rows = bracketsSplit.split(line)
+ if len(rows) < 2:
+ continue
+ rows = fieldsSplit.split(rows[1])
+ if len(rows) < 5:
+ continue
+ monsterId = rows[0]
+ if isNonFree == True and monsterId in idtofile:
+ #convertSprite("rodata/data/sprite/ёуЅєЕН/", idtofile[monsterId])
+ monsterSprite = "sprites/{0}.xml ".format(idtofile[monsterId])
+ else:
+ monsterSprite = """monsters/blub.xml
+ accessories/blub-tentacle.xml|#3e4164,3a3968,544a82,64437a,7d6db4,a26392,8f99c4,d294ab,b3cdcd,e7b8b8,d9ecd1,f0e8c5 """;
+
+ name = strToXml(stripQuotes(rows[2]))
+ data = data + tpl.format(monsterId, name, monsterSprite)
+
+ saveFile(destDir + "monsters.xml", monsters.format(data))
diff --git a/hercules/code/servertoclient/npcs.py b/hercules/code/servertoclient/npcs.py
new file mode 100644
index 0000000..5e49ffd
--- /dev/null
+++ b/hercules/code/servertoclient/npcs.py
@@ -0,0 +1,29 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2015 Evol Online
+# Author: Andrei Karas (4144)
+
+from code.fileutils import makeDir, readFile, saveFile
+
+
+def getNpcIds(idtofile):
+ for key1 in idtofile:
+ key = int(key1)
+ if 45 <= key <= 125 or 400 < key < 1000 or 10001 <= key < 10100:
+ yield key1
+
+
+def convertNpcsNonFree(idtofile):
+ destDir = "clientdata/"
+ templatesDir = "templates/"
+ makeDir(destDir)
+ tpl = readFile(templatesDir + "npc.tpl")
+ npcs = readFile(templatesDir + "npcs.xml")
+ data = ""
+
+ for key in getNpcIds(idtofile):
+ npcSprite = "sprites/{0}.xml ".format(idtofile[key])
+ data = data + tpl.format(
+ id = key,
+ sprite = npcSprite)
+ saveFile(destDir + "npcs.xml", npcs.format(data))
diff --git a/hercules/code/servertoclient/pets.py b/hercules/code/servertoclient/pets.py
new file mode 100644
index 0000000..bc6deef
--- /dev/null
+++ b/hercules/code/servertoclient/pets.py
@@ -0,0 +1,31 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+import re
+
+from code.fileutils import makeDir, readFile, saveFile
+
+def convertPets():
+ destDir = "clientdata/"
+ templatesDir = "templates/"
+ petsDbFile = "serverdata/db/re/pet_db.txt"
+ fieldsSplit = re.compile(",")
+ makeDir(destDir)
+ tpl = readFile(templatesDir + "pet.tpl")
+ pets = readFile(templatesDir + "pets.xml")
+ data = ""
+ petSprite = "monsters/tortuga.xml ";
+ with open(petsDbFile, "r") as f:
+ for line in f:
+ if line == "" or line[0:2] == "//":
+ continue
+ rows = fieldsSplit.split(line)
+ if len(rows) < 9:
+ continue
+ data = data + tpl.format(
+ id = rows[0],
+ sprite = petSprite,
+ name = rows[2])
+ saveFile(destDir + "pets.xml", pets.format(data))
diff --git a/hercules/code/servertoclient/quests.py b/hercules/code/servertoclient/quests.py
new file mode 100644
index 0000000..df47fd5
--- /dev/null
+++ b/hercules/code/servertoclient/quests.py
@@ -0,0 +1,39 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+import re
+
+from code.fileutils import makeDir, readFile, saveFile
+from code.stringutils import strToXml, stripQuotes
+
+def convertQuests():
+ print "quests disabled for now"
+ return
+ destDir = "clientdata/"
+ templatesDir = "templates/"
+ questsDbFile = "serverdata/db/quest_db.txt"
+ fieldsSplit = re.compile(",")
+ makeDir(destDir)
+ tpl = readFile(templatesDir + "quest.tpl")
+ quests = readFile(templatesDir + "quests.xml")
+ data = ""
+ with open(questsDbFile, "r") as f:
+ for line in f:
+ if line == "" or line[0:2] == "//":
+ continue
+ rows = fieldsSplit.split(line)
+ if len(rows) < 9:
+ continue
+ questId = rows[0]
+ text = rows[8]
+ if text[-1] == "\n":
+ text = text[:-1]
+ text = strToXml(stripQuotes(text))
+ name = text
+ if len(name) > 20:
+ name = name[:19]
+
+ data = data + tpl.format(questId, name, text + ": " + str(questId))
+ saveFile(destDir + "quests.xml", quests.format(data))
diff --git a/hercules/code/servertoclient/skills.py b/hercules/code/servertoclient/skills.py
new file mode 100644
index 0000000..6affb72
--- /dev/null
+++ b/hercules/code/servertoclient/skills.py
@@ -0,0 +1,33 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+import re
+
+from code.fileutils import makeDir, readFile, saveFile
+
+def convertSkillsToXml():
+ destDir = "clientdata/"
+ templatesDir = "templates/"
+ skillsDbFile = "serverdata/db/re/skill_db.txt"
+ fieldsSplit = re.compile(",")
+ makeDir(destDir)
+ tpl = readFile(templatesDir + "skill.tpl")
+ skills = readFile(templatesDir + "skills.xml")
+ data = ""
+ with open(skillsDbFile, "r") as f:
+ for line in f:
+ if line == "" or line[0:2] == "//":
+ continue
+ rows = fieldsSplit.split(line)
+ if len(rows) < 9:
+ continue
+ skillId = rows[0]
+ name = rows[15].strip()
+ description = rows[16].strip()
+ idx = description.find ("//")
+ if idx > 1:
+ description = description[:idx]
+ data = data + tpl.format(skillId, name, description)
+ saveFile(destDir + "skills.xml", skills.format(data))
diff --git a/hercules/code/servertoclient/sprites.py b/hercules/code/servertoclient/sprites.py
new file mode 100644
index 0000000..5dad9d1
--- /dev/null
+++ b/hercules/code/servertoclient/sprites.py
@@ -0,0 +1,467 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2015 Evol Online
+# Author: Andrei Karas (4144)
+
+import array
+import os
+from PIL import Image
+
+from code.fileutils import readInt8, readInt16, readInt32, makeDir, readFile, saveFile, skipData, readData, findFileIn
+
+class ActClass:
+ pass
+
+class ActAnimationClass:
+ pass
+
+class ActSpriteClass:
+ pass
+
+class ActFrameClass:
+ pass
+
+class ActPivotClass:
+ pass
+
+class ActEventClass:
+ pass
+
+class SprClass:
+ pass
+
+class SprImageClass:
+ pass
+
+def readIndexedRLEImage(f, spr):
+ spr.nimages = spr.indexedSpritesCount
+ spr.images = dict()
+ for imageN in range(0, spr.indexedSpritesCount):
+ image = SprImageClass()
+ spr.images[imageN] = image
+ image.width = readInt16(f)
+ image.height = readInt16(f)
+ image.compressed = True
+ data = array.array('B', (0 for _ in xrange(image.width * image.height)))
+ image.data = data
+
+ compressSize = readInt16(f)
+ #uncompressedSize = image.width * image.height
+ #print "uncompressed size=" + str(uncompressedSize)
+ #print "compressed size=" + str(compressSize)
+
+ idx = 0
+ readCnt = 0
+ while readCnt < compressSize:
+ c = readInt8(f)
+ readCnt = readCnt + 1
+ data[idx] = c;
+ #print "{0:4}: {1}".format(idx, c)
+ idx = idx + 1
+ if c == 0:
+ cnt = readInt8(f)
+ readCnt = readCnt + 1
+ if cnt == 0:
+ data[idx] = cnt
+ #print "{0:4}: {1}".format(idx, cnt)
+ idx = idx + 1
+ else:
+ for j in range(1, cnt):
+ data[idx] = c
+ #print "{0:4} ({1} to {2}): {3}".format(idx, j, cnt - 1, c)
+ idx = idx + 1
+ #print "read bytes: " + str(readCnt)
+
+def readIndexedImage(f, spr):
+ pass
+
+def readRgbaImage(f, spr):
+ for imageN in range(0, spr.rgbaSpritesCount):
+ image = SprImageClass()
+ spr.images[imageN + spr.indexedSpritesCount] = image
+ image.width = readInt16(f)
+ image.height = readInt16(f)
+ data = array.array('I', (0 for _ in xrange(image.width * image.height)))
+ image.compressed = False
+ image.data = data
+ for idx in range(0, image.width * image.height):
+ data[idx] = readInt32(f)
+
+
+def readPalette(f, spr):
+ palette = array.array('I', (0 for _ in xrange(256)))
+ spr.palette = palette
+ for col in range(0, 256):
+ palette[col] = readInt32(f)
+
+def getSignedNumber(number, bitLength):
+ mask = (2 ** bitLength) - 1
+ if number & (1 << (bitLength - 1)):
+ return number | ~mask
+ else:
+ return number & mask
+
+def decodeSprite(spr):
+ palette = spr.palette
+ spr.maxwidth = 0
+ spr.maxheight = 0
+ alpha = False
+ for imageN in range(0, spr.indexedSpritesCount):
+ image = spr.images[imageN]
+ indexed = image.data
+ data = array.array('I', (0 for _ in xrange(image.width * image.height)))
+ for idx in range(0, image.width * image.height):
+ col = indexed[idx]
+ if col == 0:
+ data[idx] = 0
+ else:
+ if palette[col] > 0x00ffffff:
+ data[idx] = palette[col]
+ alpha = True
+ else:
+ data[idx] = palette[col] + 0xff000000
+ image.data = data
+ if image.width > spr.maxwidth:
+ spr.maxwidth = image.width
+ if image.height > spr.maxheight:
+ spr.maxheight = image.height
+
+ # for debug
+# png = Image.new('RGBA', (image.width, image.height))
+# png.putdata(image.data)
+# png.save("test{0}.png".format(imageN))
+
+ if alpha:
+ print "detected alpha"
+
+ for imageN in range(0, spr.rgbaSpritesCount):
+ image = spr.images[imageN + spr.indexedSpritesCount]
+ if image.width > spr.maxwidth:
+ spr.maxwidth = image.width
+ if image.height > spr.maxheight:
+ spr.maxheight = image.height
+
+
+
+def saveSpriteImage(act, spr, spriteDir, spriteName):
+# for imageN in range(0, spr.rgbaSpritesCount):
+# image = spr.images[imageN + spr.indexedSpritesCount]
+# print "{0} x {1}".format(image.width, image.height)
+# png = Image.new('RGBA', (image.width, image.height))
+# png.putdata(image.data)
+# png.save("test{0}.png".format(imageN + spr.indexedSpritesCount))
+
+# numTiles = spr.indexedSpritesCount + spr.rgbaSpritesCount
+
+ counted = 0
+ for animN in range(0, act.nanimations):
+ anim = act.animations[animN]
+ for spriteN in range(0, anim.nsprites):
+ sprite = anim.sprites[spriteN]
+# key = []
+# for frameN in range(0, sprite.nframes):
+# frame = sprite.frames[frameN]
+# idf = frame.frameIndex
+# if frame.mirror > 0:
+# idf = - idf
+# key.append(idf)
+ counted = counted + 1
+
+ numTiles = counted
+ #print "max: {0}x{1}".format(spr.maxwidth, spr.maxheight)
+
+ # in row
+ rowTiles = int(2048 / spr.maxwidth)
+ colTiles = int(numTiles / rowTiles)
+ if colTiles * rowTiles < numTiles:
+ colTiles = colTiles + 1
+ tilesetWidth = 2048
+ tilesetHeight = colTiles * spr.maxheight
+# print "num {0} row {1}, col {2}".format(numTiles, rowTiles, colTiles)
+# print "size {0}x{1}".format(2048, colTiles * spr.maxheight)
+
+# tileset = array.array('I', (0 for _ in xrange(tilesetWidth * tilesetHeight)))
+ tileset = Image.new('RGBA', (tilesetWidth, tilesetHeight))
+# png.putdata(tileset)
+ #draw = ImageDraw.Draw(png)
+
+ x = 0
+ y = 0
+
+ frameToIdx = dict()
+
+ tile = 0
+ for animN in range(0, act.nanimations):
+ anim = act.animations[animN]
+ for spriteN in range(0, anim.nsprites):
+ sprite = anim.sprites[spriteN]
+ frameToIdx[str(animN) + "_" + str(spriteN)] = tile
+ for frameN in range(0, sprite.nframes):
+ frame = sprite.frames[frameN]
+ frm = frame.frameIndex
+ if frame.mirror > 0:
+ frm = - frm
+
+ if frm in frameToIdx:
+ continue
+
+ if frame.frameIndex not in spr.images:
+ print "wrong frame index: {0}".format(frame.frameIndex)
+ continue
+ image = spr.images[frame.frameIndex]
+ png = Image.new('RGBA', (image.width, image.height))
+ png.putdata(image.data)
+ if frame.mirror > 0:
+ png = png.transpose(Image.FLIP_LEFT_RIGHT)
+ offsetX = (spr.maxwidth - image.width) / 2
+ offsetY = spr.maxheight - image.height
+# offsetX = (spr.maxwidth - image.width) / 2 + getSignedNumber(frame.offsetX, 32)
+# offsetY = spr.maxheight - image.height + getSignedNumber(frame.offsetY, 32)
+ tileset.paste(png, (x + offsetX, y + offsetY))
+ tile = tile + 1
+ x = x + spr.maxwidth
+ if x + spr.maxwidth > 2048:
+ x = 0
+ y = y + spr.maxheight
+
+# for imageN in range(0, spr.rgbaSpritesCount + spr.indexedSpritesCount):
+# image = spr.images[imageN]
+# png = Image.new('RGBA', (image.width, image.height))
+# png.putdata(image.data)
+# tileset.paste(png, (x, y))
+# x = x + spr.maxwidth
+# if x + spr.maxwidth > 2048:
+# x = 0
+# y = y + spr.maxheight
+
+ spr.frameToIdx = frameToIdx
+ spr.tilesetWidth = tilesetWidth
+ spr.tilesetHeight = tilesetHeight
+ makeDir(spriteDir)
+ tileset.save(spriteDir + spriteName + ".png")
+
+def extractSpriteAnimData(act, spr, actIndex, direction):
+# delay = anim.delay
+ delay = 100
+ data = " \n"
+ if actIndex not in act.animations:
+ data = data + " \n".format(
+ 0, delay, 0, 0)
+ data = data + " \n"
+ return data
+
+ anim = act.animations[actIndex]
+ for spriteN in range(0, anim.nsprites):
+ #sprite = anim.sprites[spriteN]
+ #for frameN in range(0, sprite.nframes):
+ #frame = sprite.frames[frameN]
+ #frm = frame.frameIndex
+ #if frame.mirror > 0:
+ # frm = -frm
+ #if frm not in spr.frameToIdx:
+ # continue
+
+ idx = spr.frameToIdx[str(actIndex) + "_" + str(spriteN)]
+ offsetX = 0
+ offsetY = 0
+# offsetX = frame.offsetX
+# offsetY = frame.offsetY
+# if offsetX > 4294900000:
+# offsetX = - (4294967296 - offsetX)
+# if offsetY > 4294000000:
+# offsetY = -(4294967296 - offsetY)
+
+ data = data + " \n".format(
+ idx, delay, offsetX, offsetY)
+
+ data = data + " \n"
+ return data
+
+def extractSpriteDataAll(act, spr, actIndex, name):
+ data = extractSpriteAnimData(act, spr, actIndex, "down")
+ data = data + extractSpriteAnimData(act, spr, actIndex + 1, "downleft")
+ data = data + extractSpriteAnimData(act, spr, actIndex + 2, "left")
+ data = data + extractSpriteAnimData(act, spr, actIndex + 3, "upleft")
+ data = data + extractSpriteAnimData(act, spr, actIndex + 4, "up")
+ data = data + extractSpriteAnimData(act, spr, actIndex + 5, "upright")
+ data = data + extractSpriteAnimData(act, spr, actIndex + 6, "right")
+ data = data + extractSpriteAnimData(act, spr, actIndex + 6, "downright")
+ return data
+
+def saveSpriteXml(act, spr, spriteDir, spriteName):
+ templatesDir = "templates/"
+ dstFile = spriteDir + spriteName + ".xml"
+ tpl = readFile(templatesDir + "sprite.xml")
+ # 0, 8, 16, 24, 32, 40, 48
+ # 0 - walk or attack or ?
+ # 8 - walk
+ # 16 - attack
+ # 24 - dead
+ # 32 - dead2 ?
+ # no more
+ standData = extractSpriteDataAll(act, spr, 0, "stand")
+ walkData = extractSpriteDataAll(act, spr, 8, "walk")
+ attackData = extractSpriteDataAll(act, spr, 16, "attack")
+ deadData = extractSpriteDataAll(act, spr, 32, "dead")
+
+ data = tpl.format(
+ src = "graphics/sprites/sprites/" + spriteName + ".png",
+ width = spr.maxwidth,
+ height = spr.maxheight,
+ stand = standData,
+ walk = walkData,
+ attack = attackData,
+ dead = deadData
+ )
+ saveFile(dstFile, data)
+
+
+def readAct(actFile):
+ act = ActClass()
+ with open(actFile, "r") as f:
+ act.header = readInt16(f)
+ if act.header != 17217:
+ #print "Wrong header in file {0}".format(actFile)
+ return None
+ act.minorVersion = readInt8(f)
+ act.majorVersion = readInt8(f)
+ act.nanimations = readInt16(f)
+ print "{0}, {1}.{2}, {3}".format(actFile, act.majorVersion, act.minorVersion, act.nanimations)
+ #print " animations: " + str(act.nanimations)
+ act.animations = dict()
+ skipData(f, 10)
+ for animN in range(0, act.nanimations):
+ anim = ActAnimationClass()
+ anim.delay = 30
+ act.animations[animN] = anim
+ anim.nsprites = readInt32(f)
+ #print " sprites: " + str(anim.nsprites)
+ anim.sprites = dict()
+ for spriteN in range(0, anim.nsprites):
+ sprite = ActSpriteClass()
+ anim.sprites[spriteN] = sprite
+ sprite.left1 = readInt32(f)
+ sprite.top1 = readInt32(f)
+ sprite.right1 = readInt32(f)
+ sprite.buttom1 = readInt32(f)
+ sprite.left2 = readInt32(f)
+ sprite.top2 = readInt32(f)
+ sprite.right2 = readInt32(f)
+ sprite.buttom2 = readInt32(f)
+ sprite.nframes = readInt32(f)
+ #print "sprite {0}, frames: {1}".format(spriteN, sprite.nframes)
+ sprite.frames = dict()
+ for frameN in range(0, sprite.nframes):
+ frame = ActFrameClass()
+ sprite.frames[frameN] = frame
+ frame.offsetX = readInt32(f)
+ frame.offsetY = readInt32(f)
+ frame.frameIndex = readInt32(f)
+ #print " index: " + str(frame.frameIndex)
+ frame.mirror = readInt32(f)
+ if act.majorVersion >= 2:
+ frame.color = readInt32(f)
+ frame.scaleX = readInt32(f)
+ if act.majorVersion > 2 or (act.majorVersion == 2 and act.minorVersion >= 4):
+ frame.scaleY = readInt32(f)
+ else:
+ frame.scaleY = frame.scaleX
+ frame.angle = readInt32(f)
+ frame.spriteType = readInt32(f)
+ if act.majorVersion > 2 or (act.majorVersion == 2 and act.minorVersion >= 5):
+ frame.width = readInt32(f)
+ frame.height = readInt32(f)
+ #print "{0} x {1}".format(frame.width, frame.height)
+ if act.majorVersion >= 2:
+ sprite.eventIndex = readInt32(f)
+ if act.majorVersion > 2 or (act.majorVersion == 2 and act.minorVersion >= 3):
+ sprite.npivots = readInt32(f)
+ sprite.pivots = dict()
+ #if sprite.npivots > 0:
+ #print " pivotes: " + str(sprite.npivots)
+ for pivotN in range(0, sprite.npivots):
+ pivot = ActPivotClass()
+ sprite.pivots[pivotN] = pivot
+ pivot.unknown = readInt32(f)
+ pivot.centerX = readInt32(f)
+ pivot.centerY = readInt32(f)
+ pivot.nAttribute = readInt32(f)
+ if act.majorVersion > 2 or (act.majorVersion == 2 and act.minorVersion >= 1):
+ act.nevents = readInt32(f)
+ act.events = dict()
+ for eventN in range(0, act.nevents):
+ event = ActEventClass()
+ act.events[eventN] = event;
+ event.name = readData(f, 40)
+ if act.majorVersion > 2 or (act.majorVersion == 2 and act.minorVersion >= 2):
+ for animN in range(0, act.nanimations):
+ anim = act.animations[animN]
+ anim.delay = readInt32(f)
+ return act
+
+def readSpr(sprFile):
+ spr = SprClass()
+ with open(sprFile, "r") as f:
+ spr.header1 = readInt8(f)
+ spr.header2 = readInt8(f)
+ if spr.header1 != 0x53 or spr.header2 != 0x50:
+ return None
+ spr.minorVersion = readInt8(f)
+ spr.majorVersion = readInt8(f)
+ spr.indexedSpritesCount = readInt16(f)
+ if spr.majorVersion > 1 or (spr.majorVersion == 1 and spr.minorVersion >= 1):
+ spr.rgbaSpritesCount = readInt16(f)
+ else:
+ spr.rgbaSpritesCount = 0
+ print "{0}, {1}.{2}, {3}, {4}".format(sprFile, spr.majorVersion, spr.minorVersion, spr.indexedSpritesCount, spr.rgbaSpritesCount)
+ spr.frames = spr.indexedSpritesCount + spr.rgbaSpritesCount
+
+ if spr.majorVersion > 2 or (spr.majorVersion == 2 and spr.minorVersion >= 1):
+ readIndexedRLEImage(f, spr)
+ else:
+ readIndexedImage(f, spr)
+ readRgbaImage(f, spr)
+
+ if spr.majorVersion > 1 or (spr.majorVersion == 1 and spr.minorVersion >= 1):
+ readPalette(f, spr)
+
+ return spr
+
+def convertSprite(spritePath, spriteName):
+ actFile = "{0}.act".format(spritePath + spriteName)
+ sprFile = "{0}.spr".format(spritePath + spriteName)
+ if os.path.exists(actFile) == False or os.path.exists(sprFile) == False:
+ return None
+ act = readAct(actFile)
+ spr = readSpr(sprFile)
+ decodeSprite(spr)
+ saveSpriteImage(act, spr, "clientdata/graphics/sprites/sprites/", spriteName)
+ saveSpriteXml(act, spr, "clientdata/graphics/sprites/sprites/", spriteName)
+# if actFile.find("wolf") > 0:
+# exit(0)
+# exit(0)
+
+def findSpritePath(spriteName):
+ testName = spriteName + ".act"
+ testName2 = spriteName.upper() + ".act"
+ path = findFileIn((testName, testName2),
+ ("rodata/data/sprite/ёуЅєЕН/",
+ "rodata/data/sprite/npc/",
+ "rodata/data/sprite/homun/"))
+ return path
+
+def convertSpritesNonFree(idtofile):
+ processed = []
+ for spriteid in idtofile:
+ spriteName = idtofile[spriteid]
+ if spriteName in processed:
+ print "skipping " + spriteName
+ continue
+
+ path = findSpritePath(spriteName)
+ if path is None:
+ print "not found " + spriteName
+ continue
+ print spriteName
+ convertSprite(path, spriteName)
+ processed.append(spriteName)
diff --git a/hercules/code/serverutils.py b/hercules/code/serverutils.py
new file mode 100644
index 0000000..39a283e
--- /dev/null
+++ b/hercules/code/serverutils.py
@@ -0,0 +1,12 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+import os
+
+def detectServerType():
+ if os.path.exists("oldserverdata/world/map/news.d"):
+ return "tmw"
+ else:
+ return "evol"
diff --git a/hercules/code/stringutils.py b/hercules/code/stringutils.py
new file mode 100644
index 0000000..73db611
--- /dev/null
+++ b/hercules/code/stringutils.py
@@ -0,0 +1,62 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+import codecs
+
+def stripQuotes(data):
+ if len(data) == 0:
+ return data
+ if data[-1] == "\"":
+ data = data[:-1]
+ if data[0] == "\"":
+ data = data[1:]
+ if data[-1] == "'":
+ data = data[:-1]
+ if data[0] == "'":
+ data = data[1:]
+ return data
+
+def stripQuotes2(data):
+ for idx in xrange(0, len(data)):
+ data[idx] = stripQuotes(data[idx])
+ return data
+
+def strToXml(data):
+ data = data.replace("&", "&");
+ data = data.replace("<", "<");
+ data = data.replace(">", ">");
+ return data
+
+def stripNewLine(data):
+ if len(data) == 0:
+ return data
+ if data[-1] == "\r":
+ data = data[:-1]
+ if len(data) > 0 and data[-1] == "\n":
+ data = data[:-1]
+ return data
+
+def stripWindows(data):
+ if len(data) == 0:
+ return data
+ if data[-1] == "\r":
+ data = data[:-1]
+ if len(data) > 1 and data[-2] == "\r":
+ data = data[:-2] + data[-1]
+ data = data.replace(codecs.BOM_UTF8, "", 1)
+ return data
+
+def escapeSqlStr(data):
+ data = data.replace("\\", "\\\\");
+ data = data.replace("'", "\\'");
+ data = data.replace("`", "\\`");
+ data = data.replace("{", "\\{");
+ data = data.replace("}", "\\}");
+ return data
+
+def removeGat(data):
+ if len(data) >= 4 and data[-4:] == ".gat":
+ data = data[:-4]
+ return data
diff --git a/hercules/code/tileutils.py b/hercules/code/tileutils.py
new file mode 100644
index 0000000..42e7a84
--- /dev/null
+++ b/hercules/code/tileutils.py
@@ -0,0 +1,48 @@
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+import array
+
+def getTileData(mapData, x, y, sx):
+ data = mapData[y * sx + x]
+ arr = array.array("B")
+ arr.fromstring(data)
+ data = arr[0]
+ return data
+
+def getTile(data):
+ normal = 0
+ collision = 0
+ if data == 0: # 000 normal walkable
+ normal = 1
+ collision = 5
+ elif data == 1: # 001 non walkable
+ normal = 2
+ collision = 6
+ elif data == 2: # 010 same with 0
+ normal = 1
+ collision = 5
+ elif data == 3: # 011 same with 0, but water
+ normal = 3
+ collision = 5
+ elif data == 4: # 100 same with 0
+ normal = 1
+ collision = 5
+ elif data == 5: # 101 same with 1, but shootable (for now not supported!!!)
+ normal = 4
+ collision = 6
+ elif data == 6: # 110 same with 0
+ normal = 1
+ collision = 5
+ return (str(normal), str(collision))
+
+def getGroundTile(flag):
+ return str(flag + 1)
+
+def getCollisionTile(flag):
+ if flag == 0:
+ return "0"
+ return "4"
+
diff --git a/hercules/convert_mapcache_to_tmx.py b/hercules/convert_mapcache_to_tmx.py
new file mode 100755
index 0000000..e6e6bc9
--- /dev/null
+++ b/hercules/convert_mapcache_to_tmx.py
@@ -0,0 +1,24 @@
+#! /usr/bin/env python
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+import os
+
+from code.fileutils import readInt32, readInt16
+from code.servertoclient.maps import covertToTmx
+
+def runFunction(path):
+ with open(path, "rb") as f:
+ size = readInt32(f)
+ if os.path.getsize(path) != size:
+ print "Map cache corrupted, wrong file size."
+ exit(1)
+ mapsCount = readInt16(f)
+ print "Maps count: " + str(mapsCount)
+ readInt16(f) # padding
+ covertToTmx(f, mapsCount)
+
+
+runFunction("serverdata/db/re/map_cache.dat")
diff --git a/hercules/convert_tmx_to_mapcache.py b/hercules/convert_tmx_to_mapcache.py
new file mode 100755
index 0000000..62d0d18
--- /dev/null
+++ b/hercules/convert_tmx_to_mapcache.py
@@ -0,0 +1,9 @@
+#! /usr/bin/env python
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+from code.clienttoserver.maps import recreateMapCache
+
+recreateMapCache();
diff --git a/hercules/extract_mapcache.py b/hercules/extract_mapcache.py
new file mode 100755
index 0000000..39a63c3
--- /dev/null
+++ b/hercules/extract_mapcache.py
@@ -0,0 +1,24 @@
+#! /usr/bin/env python
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+import os
+
+from code.fileutils import readInt16, readInt32
+from code.server.maps import extractMaps
+
+
+def runFunction(path):
+ with open(path, "rb") as f:
+ size = readInt32(f)
+ if os.path.getsize(path) != size:
+ print "Map cache corrupted, wrong file size."
+ exit(1)
+ mapsCount = readInt16(f)
+ print "Maps count: " + str(mapsCount)
+ readInt16(f) # padding
+ extractMaps(f, mapsCount)
+
+runFunction("serverdata/db/re/map_cache.dat")
diff --git a/hercules/import_newdb.sh b/hercules/import_newdb.sh
new file mode 100755
index 0000000..ba81d4f
--- /dev/null
+++ b/hercules/import_newdb.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+
+export CMD="$*"
+
+if [[ -z "${CMD}" ]]; then
+ echo "Usage example: ./import_newsb.sh -u evol -pevol evol"
+ exit
+fi
+
+cd newdb
+
+function import {
+echo Importing $1
+mysql $CMD <$1
+}
+
+import acc_reg_num_db.sql
+import char.sql
+import char_reg_num_db.sql
+import inventory.sql
+import login.sql
+import party.sql
+import skill.sql
+import storage.sql
diff --git a/hercules/list_mapcache.py b/hercules/list_mapcache.py
new file mode 100755
index 0000000..2a83f53
--- /dev/null
+++ b/hercules/list_mapcache.py
@@ -0,0 +1,24 @@
+#! /usr/bin/env python
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2014 Evol Online
+# Author: Andrei Karas (4144)
+
+import os
+
+from code.fileutils import readInt16, readInt32
+from code.server.maps import listMapCache
+
+def runFunction(path):
+ with open(path, "rb") as f:
+ size = readInt32(f)
+ if os.path.getsize(path) != size:
+ print "Map cache corrupted, wrong file size."
+ exit(1)
+ mapsCount = readInt16(f)
+ print "Maps count: " + str(mapsCount)
+ readInt16(f) # padding
+ listMapCache(f, mapsCount)
+
+
+runFunction("serverdata/db/re/map_cache.dat")
diff --git a/hercules/templates/acc_reg_num_db.sql b/hercules/templates/acc_reg_num_db.sql
new file mode 100644
index 0000000..da8b061
--- /dev/null
+++ b/hercules/templates/acc_reg_num_db.sql
@@ -0,0 +1,11 @@
+DROP TABLE IF EXISTS `acc_reg_num_db`;
+
+CREATE TABLE IF NOT EXISTS `acc_reg_num_db` (
+ `account_id` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `key` VARCHAR(32) BINARY NOT NULL DEFAULT '',
+ `index` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `value` INT(11) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`account_id`,`key`,`index`),
+ KEY `account_id` (`account_id`)
+) ENGINE=InnoDB;
+
diff --git a/hercules/templates/char.sql b/hercules/templates/char.sql
new file mode 100644
index 0000000..0f75eb5
--- /dev/null
+++ b/hercules/templates/char.sql
@@ -0,0 +1,72 @@
+DROP TABLE IF EXISTS `char`;
+
+CREATE TABLE IF NOT EXISTS `char` (
+ `char_id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `account_id` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `char_num` TINYINT(1) NOT NULL DEFAULT '0',
+ `name` VARCHAR(30) NOT NULL DEFAULT '',
+ `class` SMALLINT(6) UNSIGNED NOT NULL DEFAULT '0',
+ `base_level` SMALLINT(6) UNSIGNED NOT NULL DEFAULT '1',
+ `job_level` SMALLINT(6) UNSIGNED NOT NULL DEFAULT '1',
+ `base_exp` BIGINT(20) UNSIGNED NOT NULL DEFAULT '0',
+ `job_exp` BIGINT(20) UNSIGNED NOT NULL DEFAULT '0',
+ `zeny` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `str` SMALLINT(4) UNSIGNED NOT NULL DEFAULT '0',
+ `agi` SMALLINT(4) UNSIGNED NOT NULL DEFAULT '0',
+ `vit` SMALLINT(4) UNSIGNED NOT NULL DEFAULT '0',
+ `int` SMALLINT(4) UNSIGNED NOT NULL DEFAULT '0',
+ `dex` SMALLINT(4) UNSIGNED NOT NULL DEFAULT '0',
+ `luk` SMALLINT(4) UNSIGNED NOT NULL DEFAULT '0',
+ `max_hp` INT(9) UNSIGNED NOT NULL DEFAULT '0',
+ `hp` INT(9) UNSIGNED NOT NULL DEFAULT '0',
+ `max_sp` INT(9) UNSIGNED NOT NULL DEFAULT '0',
+ `sp` INT(9) UNSIGNED NOT NULL DEFAULT '0',
+ `status_point` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `skill_point` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `option` INT(11) NOT NULL DEFAULT '0',
+ `karma` TINYINT(3) NOT NULL DEFAULT '0',
+ `manner` SMALLINT(6) NOT NULL DEFAULT '0',
+ `party_id` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `guild_id` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `pet_id` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `homun_id` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `elemental_id` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `hair` TINYINT(4) UNSIGNED NOT NULL DEFAULT '0',
+ `hair_color` SMALLINT(5) UNSIGNED NOT NULL DEFAULT '0',
+ `clothes_color` SMALLINT(5) UNSIGNED NOT NULL DEFAULT '0',
+ `body` SMALLINT(5) unsigned NOT NULL default '0',
+ `weapon` SMALLINT(6) UNSIGNED NOT NULL DEFAULT '0',
+ `shield` SMALLINT(6) UNSIGNED NOT NULL DEFAULT '0',
+ `head_top` SMALLINT(6) UNSIGNED NOT NULL DEFAULT '0',
+ `head_mid` SMALLINT(6) UNSIGNED NOT NULL DEFAULT '0',
+ `head_bottom` SMALLINT(6) UNSIGNED NOT NULL DEFAULT '0',
+ `robe` SMALLINT(6) UNSIGNED NOT NULL DEFAULT '0',
+ `last_map` VARCHAR(11) NOT NULL DEFAULT '',
+ `last_x` SMALLINT(4) UNSIGNED NOT NULL DEFAULT '53',
+ `last_y` SMALLINT(4) UNSIGNED NOT NULL DEFAULT '111',
+ `save_map` VARCHAR(11) NOT NULL DEFAULT '',
+ `save_x` SMALLINT(4) UNSIGNED NOT NULL DEFAULT '53',
+ `save_y` SMALLINT(4) UNSIGNED NOT NULL DEFAULT '111',
+ `partner_id` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `online` TINYINT(2) NOT NULL DEFAULT '0',
+ `father` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `mother` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `child` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `fame` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `rename` SMALLINT(3) UNSIGNED NOT NULL DEFAULT '0',
+ `delete_date` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `slotchange` SMALLINT(3) UNSIGNED NOT NULL DEFAULT '0',
+ `char_opt` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `font` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0',
+ `unban_time` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `uniqueitem_counter` BIGINT(20) UNSIGNED NOT NULL DEFAULT '0',
+ `sex` ENUM('M','F','U') NOT NULL DEFAULT 'U',
+ `hotkey_rowshift` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0',
+ PRIMARY KEY (`char_id`),
+ UNIQUE KEY `name_key` (`name`),
+ KEY `account_id` (`account_id`),
+ KEY `party_id` (`party_id`),
+ KEY `guild_id` (`guild_id`),
+ KEY `online` (`online`)
+) ENGINE=InnoDB AUTO_INCREMENT=150000;
+
diff --git a/hercules/templates/char_reg_num_db.sql b/hercules/templates/char_reg_num_db.sql
new file mode 100644
index 0000000..ea9429f
--- /dev/null
+++ b/hercules/templates/char_reg_num_db.sql
@@ -0,0 +1,11 @@
+DROP TABLE IF EXISTS `char_reg_num_db`;
+
+CREATE TABLE IF NOT EXISTS `char_reg_num_db` (
+ `char_id` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `key` VARCHAR(32) BINARY NOT NULL DEFAULT '',
+ `index` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `value` INT(11) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`char_id`,`key`,`index`),
+ KEY `char_id` (`char_id`)
+) ENGINE=InnoDB;
+
diff --git a/hercules/templates/collision.png b/hercules/templates/collision.png
new file mode 100644
index 0000000..f360c63
Binary files /dev/null and b/hercules/templates/collision.png differ
diff --git a/hercules/templates/constants.tpl b/hercules/templates/constants.tpl
new file mode 100644
index 0000000..b42bb18
--- /dev/null
+++ b/hercules/templates/constants.tpl
@@ -0,0 +1,45 @@
+//================= Hercules Database =====================================
+//= _ _ _
+//= | | | | | |
+//= | |_| | ___ _ __ ___ _ _| | ___ ___
+//= | _ |/ _ \ '__/ __| | | | |/ _ \/ __|
+//= | | | | __/ | | (__| |_| | | __/\__ \
+//= \_| |_/\___|_| \___|\__,_|_|\___||___/
+//================= License ===============================================
+//= This file is part of Hercules.
+//= http://herc.ws - http://github.com/HerculesWS/Hercules
+//=
+//= Copyright (C) 2016 Hercules Dev Team
+//=
+//= Hercules is free software: you can redistribute it and/or modify
+//= it under the terms of the GNU General Public License as published by
+//= the Free Software Foundation, either version 3 of the License, or
+//= (at your option) any later version.
+//=
+//= This program is distributed in the hope that it will be useful,
+//= but WITHOUT ANY WARRANTY; without even the implied warranty of
+//= MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+//= GNU General Public License for more details.
+//=
+//= You should have received a copy of the GNU General Public License
+//= along with this program. If not, see .
+//=========================================================================
+//= Script Constants Database
+//=========================================================================
+
+constants_db: {
+/************* Entry structure (short) ************************************
+ Identifier: value // (int)
+ ************* Entry structure (full) *************************************
+ Identifier: {
+ Value: value // (int)
+ Parameter: true // (boolean) Defaults to false.
+ Deprecated: true // (boolean) Defaults to false.
+ }
+**************************************************************************/
+// NOTE:
+// Parameters are special in that they retrieve certain runtime values
+// depending on the specified ID in field Value. Depending on the
+// implementation values assigned by scripts to parameters will affect
+// runtime values, such as Zeny, as well (see pc_readparam/pc_setparam).
+
diff --git a/hercules/templates/group.tpl b/hercules/templates/group.tpl
new file mode 100644
index 0000000..c93b778
--- /dev/null
+++ b/hercules/templates/group.tpl
@@ -0,0 +1,11 @@
+
+ {commands}
+
+ {permissions}
+
+
diff --git a/hercules/templates/homunculus.tpl b/hercules/templates/homunculus.tpl
new file mode 100644
index 0000000..67a76bc
--- /dev/null
+++ b/hercules/templates/homunculus.tpl
@@ -0,0 +1,3 @@
+
+ {sprite}
+
diff --git a/hercules/templates/homunculuses.xml b/hercules/templates/homunculuses.xml
new file mode 100644
index 0000000..3737d8b
--- /dev/null
+++ b/hercules/templates/homunculuses.xml
@@ -0,0 +1,6 @@
+
+
+
+{0}
+
diff --git a/hercules/templates/inventory.sql b/hercules/templates/inventory.sql
new file mode 100644
index 0000000..9fabd54
--- /dev/null
+++ b/hercules/templates/inventory.sql
@@ -0,0 +1,23 @@
+DROP TABLE IF EXISTS `inventory`;
+
+CREATE TABLE IF NOT EXISTS `inventory` (
+ `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `char_id` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `nameid` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `amount` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `equip` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `identify` SMALLINT(6) NOT NULL DEFAULT '0',
+ `refine` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0',
+ `attribute` TINYINT(4) UNSIGNED NOT NULL DEFAULT '0',
+ `card0` SMALLINT(11) NOT NULL DEFAULT '0',
+ `card1` SMALLINT(11) NOT NULL DEFAULT '0',
+ `card2` SMALLINT(11) NOT NULL DEFAULT '0',
+ `card3` SMALLINT(11) NOT NULL DEFAULT '0',
+ `expire_time` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `favorite` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0',
+ `bound` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0',
+ `unique_id` BIGINT(20) UNSIGNED NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `char_id` (`char_id`)
+) ENGINE=InnoDB;
+
diff --git a/hercules/templates/item.tpl b/hercules/templates/item.tpl
new file mode 100644
index 0000000..a7f28a9
--- /dev/null
+++ b/hercules/templates/item.tpl
@@ -0,0 +1,7 @@
+ -
+
{6}
+
diff --git a/hercules/templates/item_db.tpl b/hercules/templates/item_db.tpl
new file mode 100644
index 0000000..198343d
--- /dev/null
+++ b/hercules/templates/item_db.tpl
@@ -0,0 +1,63 @@
+item_db: (
+// Items Database
+//
+/******************************************************************************
+ ************* Entry structure ************************************************
+ ******************************************************************************
+{
+ // =================== Mandatory fields ===============================
+ Id: ID (int)
+ AegisName: "Aegis_Name" (string)
+ Name: "Item Name" (string)
+ // =================== Optional fields ================================
+ Type: Item Type (int, defaults to 3 = etc item)
+ Buy: Buy Price (int, defaults to Sell * 2)
+ Sell: Sell Price (int, defaults to Buy / 2)
+ Weight: Item Weight (int, defaults to 0)
+ Atk: Attack (int, defaults to 0)
+ Matk: Magical Attack (int, defaults to 0, ignored in pre-re)
+ Def: Defense (int, defaults to 0)
+ Range: Attack Range (int, defaults to 0)
+ Slots: Slots (int, defaults to 0)
+ Job: Job mask (int, defaults to all jobs = 0xFFFFFFFF)
+ Upper: Upper mask (int, defaults to any = 0x3f)
+ Gender: Gender (int, defaults to both = 2)
+ Loc: Equip location (int, required value for equipment)
+ WeaponLv: Weapon Level (int, defaults to 0)
+ EquipLv: Equip required level (int, defaults to 0)
+ EquipLv: [min, max] (alternative syntax with min / max level)
+ Refine: Refineable (boolean, defaults to true)
+ View: View ID (int, defaults to 0)
+ BindOnEquip: true/false (boolean, defaults to false)
+ BuyingStore: true/false (boolean, defaults to false)
+ Delay: Delay to use item (int, defaults to 0)
+ FloorLifeTime: Delay to remove item from ground (int, default flooritem_lifetime)
+ KeepAfterUse: true/false (boolean, defaults to false)
+ Trade: { (defaults to no restrictions)
+ override: GroupID (int, defaults to 100)
+ nodrop: true/false (boolean, defaults to false)
+ notrade: true/false (boolean, defaults to false)
+ partneroverride: true/false (boolean, defaults to false)
+ noselltonpc: true/false (boolean, defaults to false)
+ nocart: true/false (boolean, defaults to false)
+ nostorage: true/false (boolean, defaults to false)
+ nogstorage: true/false (boolean, defaults to false)
+ nomail: true/false (boolean, defaults to false)
+ noauction: true/false (boolean, defaults to false)
+ nodelonuse: true/false (boolean, defaults to false)
+ }
+ Nouse: { (defaults to no restrictions)
+ override: GroupID (int, defaults to 100)
+ sitting: true/false (boolean, defaults to false)
+ }
+ Stack: [amount, type] (int, defaults to 0)
+ Sprite: SpriteID (int, defaults to 0)
+ Script: <"
+ Script
+ (it can be multi-line)
+ ">
+ OnEquipScript: <" OnEquip Script (can also be multi-line) ">
+ OnUnequipScript: <" OnUnequip Script (can also be multi-line) ">
+},
+******************************************************************************/
+
diff --git a/hercules/templates/items.xml b/hercules/templates/items.xml
new file mode 100644
index 0000000..07d1cb7
--- /dev/null
+++ b/hercules/templates/items.xml
@@ -0,0 +1,148 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
hairstyles/hairstyle01.xml
+
+ -
+
hairstyles/hairstyle02.xml
+
+ -
+
hairstyles/hairstyle03.xml
+
+ -
+
hairstyles/hairstyle04.xml
+
+ -
+
hairstyles/hairstyle05.xml
+
+ -
+
hairstyles/hairstyle06.xml
+
+ -
+
hairstyles/hairstyle07.xml
+
+ -
+
hairstyles/hairstyle08.xml
+
+ -
+
hairstyles/hairstyle09.xml
+
+ -
+
hairstyles/hairstyle10.xml
+
+ -
+
hairstyles/hairstyle11.xml
+
+ -
+
hairstyles/hairstyle12.xml
+
+ -
+
hairstyles/hairstyle13.xml
+
+ -
+
hairstyles/hairstyle14.xml
+
+ -
+
hairstyles/hairstyle15.xml
+
+ -
+
hairstyles/hairstyle16.xml
+
+ -
+
hairstyles/hairstyle17.xml
+
+ -
+
hairstyles/hairstyle18.xml
+
+ -
+
hairstyles/hairstyle19.xml
+
+ -
+
hairstyles/hairstyle20.xml
+
+ -
+
hairstyles/hairstyle21.xml
+
+ -
+
hairstyles/hairstyle22.xml
+
+ -
+
hairstyles/hairstyle23.xml
+
+ -
+
hairstyles/hairstyle24.xml
+
+ -
+
hairstyles/hairstyle25.xml
+
+ -
+
hairstyles/hairstyle26.xml
+
+ -
+
hairstyles/hairstyle27.xml
+
+ -
+
hairstyles/hairstyle28.xml
+
+
+
+ -
+
races/human-male.xml
+ races/human-female.xml
+ weapons/barehands/hit1.ogg
+ weapons/barehands/miss1.ogg
+
+ -
+
races/ukar-male.xml
+ races/ukar-female.xml
+ weapons/barehands/hit1.ogg
+ weapons/barehands/miss1.ogg
+
+ -
+
races/demon-male.xml
+ races/demon-female.xml
+ weapons/barehands/hit1.ogg
+ weapons/barehands/miss1.ogg
+
+ -
+
races/elven-male.xml
+ races/elven-female.xml
+ weapons/barehands/hit1.ogg
+ weapons/barehands/miss1.ogg
+
+ -
+
races/orc-male.xml
+ races/orc-female.xml
+ weapons/barehands/hit1.ogg
+ weapons/barehands/miss1.ogg
+
+ -
+
races/raijin-male.xml
+ races/raijin-female.xml
+ weapons/barehands/hit1.ogg
+ weapons/barehands/miss1.ogg
+
+ -
+
races/tritan-male.xml
+ races/tritan-female.xml
+ weapons/barehands/hit1.ogg
+ weapons/barehands/miss1.ogg
+
+
+
+{0}
+
diff --git a/hercules/templates/login.sql b/hercules/templates/login.sql
new file mode 100644
index 0000000..e64de4a
--- /dev/null
+++ b/hercules/templates/login.sql
@@ -0,0 +1,25 @@
+DROP TABLE IF EXISTS `login`;
+
+CREATE TABLE IF NOT EXISTS `login` (
+ `account_id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `userid` VARCHAR(23) NOT NULL DEFAULT '',
+ `user_pass` VARCHAR(32) NOT NULL DEFAULT '',
+ `sex` ENUM('M','F','S') NOT NULL DEFAULT 'M',
+ `email` VARCHAR(39) NOT NULL DEFAULT '',
+ `group_id` TINYINT(3) NOT NULL DEFAULT '0',
+ `state` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `unban_time` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `expiration_time` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `logincount` MEDIUMINT(9) UNSIGNED NOT NULL DEFAULT '0',
+ `lastlogin` DATETIME NULL,
+ `last_ip` VARCHAR(100) NOT NULL DEFAULT '',
+ `birthdate` DATE NULL,
+ `character_slots` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0',
+ `pincode` VARCHAR(4) NOT NULL DEFAULT '',
+ `pincode_change` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ PRIMARY KEY (`account_id`),
+ KEY `name` (`userid`)
+) ENGINE=InnoDB AUTO_INCREMENT=2000000;
+
+INSERT INTO `login` (`account_id`, `userid`, `user_pass`, `sex`, `email`) VALUES ('1', 's1', 'p1', 'S','athena@athena.com');
+
diff --git a/hercules/templates/mercenaries.xml b/hercules/templates/mercenaries.xml
new file mode 100644
index 0000000..cfc801c
--- /dev/null
+++ b/hercules/templates/mercenaries.xml
@@ -0,0 +1,6 @@
+
+
+
+{0}
+
diff --git a/hercules/templates/mercenary.tpl b/hercules/templates/mercenary.tpl
new file mode 100644
index 0000000..c371659
--- /dev/null
+++ b/hercules/templates/mercenary.tpl
@@ -0,0 +1,3 @@
+
+ {sprite}
+
diff --git a/hercules/templates/mob_db.tpl b/hercules/templates/mob_db.tpl
new file mode 100644
index 0000000..ecf4a27
--- /dev/null
+++ b/hercules/templates/mob_db.tpl
@@ -0,0 +1,101 @@
+//================= Hercules Database =====================================
+//= _ _ _
+//= | | | | | |
+//= | |_| | ___ _ __ ___ _ _| | ___ ___
+//= | _ |/ _ \ '__/ __| | | | |/ _ \/ __|
+//= | | | | __/ | | (__| |_| | | __/\__ \
+//= \_| |_/\___|_| \___|\__,_|_|\___||___/
+//================= License ===============================================
+//= This file is part of Hercules.
+//= http://herc.ws - http://github.com/HerculesWS/Hercules
+//=
+//= Copyright (C) 2015 Hercules Dev Team
+//=
+//= Hercules is free software: you can redistribute it and/or modify
+//= it under the terms of the GNU General Public License as published by
+//= the Free Software Foundation, either version 3 of the License, or
+//= (at your option) any later version.
+//=
+//= This program is distributed in the hope that it will be useful,
+//= but WITHOUT ANY WARRANTY; without even the implied warranty of
+//= MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+//= GNU General Public License for more details.
+//=
+//= You should have received a copy of the GNU General Public License
+//= along with this program. If not, see .
+//=========================================================================
+//= Mobs Database
+//=========================================================================
+
+mob_db: (
+// Mobs Database
+//
+/******************************************************************************
+ ************* Entry structure ************************************************
+ ******************************************************************************
+{
+ // =================== Mandatory fields ===============================
+ Id: ID (int)
+ SpriteName: "SPRITE_NAME" (string)
+ Name: "Mob name" (string)
+ // =================== Optional fields ================================
+ JName: "Mob name" (string)
+ Lv: level (int, defaults to 1)
+ Hp: health (int, defaults to 1)
+ Sp: mana (int, defaults to 0)
+ Exp: basic experience (int, defaults to 0)
+ JExp: job experience (int, defaults to 0)
+ AttackRange: attack range (int, defaults to 1)
+ Attack: [attack1, attack2] (int, defaults to 0)
+ Def: defence (int, defaults to 0)
+ Mdef: magic defence (int, defaults to 0)
+ Stats: {
+ Str: strength (int, defaults to 0)
+ Agi: agility (int, defaults to 0)
+ Vit: vitality (int, defaults to 0)
+ Int: intelligence (int, defaults to 0)
+ Dex: dexterity (int, defaults to 0)
+ Luk: luck (int, defaults to 0)
+ }
+ ViewRange: view range (int, defaults to 1)
+ ChaseRange: chase range (int, defaults to 1)
+ Size: size (int, defaults to 1)
+ Race: race (int, defaults to 0)
+ Element: (type, level)
+ Mode: {
+ CanMove: true/false (bool, defaults to false)
+ Looter: true/false (bool, defaults to false)
+ Aggressive: true/false (bool, defaults to false)
+ Assist: true/false (bool, defaults to false)
+ CastSensorIdle:true/false (bool, defaults to false)
+ Boss: true/false (bool, defaults to false)
+ Plant: true/false (bool, defaults to false)
+ CanAttack: true/false (bool, defaults to false)
+ Detector: true/false (bool, defaults to false)
+ CastSensorChase: true/false (bool, defaults to false)
+ ChangeChase: true/false (bool, defaults to false)
+ Angry: true/false (bool, defaults to false)
+ ChangeTargetMelee: true/false (bool, defaults to false)
+ ChangeTargetChase: true/false (bool, defaults to false)
+ TargetWeak: true/false (bool, defaults to false)
+ NoKnockback: true/false (bool, defaults to false)
+ SurviveWithoutMaster: true/false (bool, defaults to false)
+ }
+ MoveSpeed: move speed (int, defaults to 0)
+ WalkMask: walk mask (int, defaults to 0)
+ AttackDelay: attack delay (int, defaults to 4000)
+ AttackMotion: attack motion (int, defaults to 2000)
+ DamageMotion: damage motion (int, defaults to 0)
+ MvpExp: mvp experience (int, defaults to 0)
+ MvpDrops: {
+ AegisName: chance (string: int)
+ ...
+ }
+ Drops: {
+ AegisName: chance (string: int)
+ ...
+ }
+
+},
+******************************************************************************/
+
diff --git a/hercules/templates/mob_skill_db.tpl b/hercules/templates/mob_skill_db.tpl
new file mode 100644
index 0000000..d8cc0a4
--- /dev/null
+++ b/hercules/templates/mob_skill_db.tpl
@@ -0,0 +1,56 @@
+// Mob Skill Database
+// Based on Aegis Episode 14.2
+//
+// Structure of Database:
+// MobID,Dummy value (info only),State,SkillID,SkillLv,Rate,CastTime,Delay,Cancelable,Target,Condition type,Condition value,val1,val2,val3,val4,val5,Emotion,Chat
+//
+// RATE: the chance of the skill being casted when the condition is fulfilled (10000 = 100%).
+// DELAY: the time (in milliseconds) before attempting to recast the same skill.
+//
+// STATE:
+// any (except dead) / idle (in standby) / walk (in movement) / dead (on killed) /
+// loot /attack / angry (like attack, except player has not attacked mob yet) /
+// chase (following target, after being attacked) / follow (following target,
+// without being attacked) / anytarget (attack+angry+chase+follow)
+//
+// TARGET:
+// target (current target) / self / friend / master / randomtarget (any enemy within skill's range)
+//
+// The following are for ground-skills, a random target tile is selected from the specified area:
+// around1 (3x3 area around self) / around2 (5x5 area around self) /
+// around3 (7x7 area around self) / around4 (9x9 area around self) /
+// around5 (3x3 area around target) / around6 (5x5 area around target) /
+// around7 (7x7 area around target) / around8 (9x9 area around target) /
+// around = around4
+//
+// CONDITION:
+// always Unconditional (no condition value).
+// onspawn When mob spawns/respawns (no condition value).
+// myhpltmaxrate When mob's HP drops to the specified %.
+// myhpinrate When mob's HP is in a certain % range (condition value = lower bound, val1 = upper bound).
+// mystatuson If mob has the specified abnormality in status.
+// mystatusoff If mob has ended the specified abnormality in status.
+// friendhpltmaxrate When mob's friend's HP drops to the specified %.
+// friendhpinrate When mob's friend's HP is in a certain % range (condition value = lower bound, val1 = upper bound).
+// friendstatuson If friend has the specified abnormality in status.
+// friendstatusoff If friend has ended the specified abnormality in status.
+// attackpcgt When attack PCs become greater than specified number.
+// attackpcge When attack PCs become greater than or equal to the specified number.
+// slavelt When number of slaves is less than the original specified number.
+// slavele When number of slaves is less than or equal to the original specified number.
+// closedattacked When close range melee attacked (no condition value).
+// longrangeattacked When long range attacked, ex. bows, guns, ranged skills (no condition value).
+// skillused When the specified skill is used on the mob.
+// afterskill After mob casts the specified skill.
+// casttargeted When a target is in cast range (no condition value).
+// rudeattacked When mob is rude attacked (no condition value).
+//
+// Status abnormalities specified through the statuson/statusoff system:
+// anybad (any type of state change) / stone / freeze / stun / sleep /
+// poison / curse / silence / confusion / blind / hiding / sight (unhidden)
+//
+// Note: if a negative MobID is provided, the skill will be treated as 'global':
+// -1: added for all boss types.
+// -2: added for all normal types.
+// -4: added for all mobs.
+
diff --git a/hercules/templates/monster.tpl b/hercules/templates/monster.tpl
new file mode 100644
index 0000000..3b6bf4d
--- /dev/null
+++ b/hercules/templates/monster.tpl
@@ -0,0 +1,3 @@
+
+ {2}
+
diff --git a/hercules/templates/monsters.xml b/hercules/templates/monsters.xml
new file mode 100644
index 0000000..9f8ffa0
--- /dev/null
+++ b/hercules/templates/monsters.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+{0}
+
diff --git a/hercules/templates/npc.tpl b/hercules/templates/npc.tpl
new file mode 100644
index 0000000..a201d64
--- /dev/null
+++ b/hercules/templates/npc.tpl
@@ -0,0 +1,3 @@
+
+ {sprite}
+
diff --git a/hercules/templates/npcs.xml b/hercules/templates/npcs.xml
new file mode 100644
index 0000000..72a0a4f
--- /dev/null
+++ b/hercules/templates/npcs.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+{0}
+
diff --git a/hercules/templates/party.sql b/hercules/templates/party.sql
new file mode 100644
index 0000000..0beb439
--- /dev/null
+++ b/hercules/templates/party.sql
@@ -0,0 +1,12 @@
+DROP TABLE IF EXISTS `party`;
+
+CREATE TABLE IF NOT EXISTS `party` (
+ `party_id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `name` VARCHAR(24) NOT NULL DEFAULT '',
+ `exp` TINYINT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `item` TINYINT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `leader_id` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `leader_char` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ PRIMARY KEY (`party_id`)
+) ENGINE=InnoDB;
+
diff --git a/hercules/templates/pet.tpl b/hercules/templates/pet.tpl
new file mode 100644
index 0000000..08d489c
--- /dev/null
+++ b/hercules/templates/pet.tpl
@@ -0,0 +1,3 @@
+
+ {sprite}
+
diff --git a/hercules/templates/pets.xml b/hercules/templates/pets.xml
new file mode 100644
index 0000000..6f838cf
--- /dev/null
+++ b/hercules/templates/pets.xml
@@ -0,0 +1,6 @@
+
+
+
+{0}
+
diff --git a/hercules/templates/quest.tpl b/hercules/templates/quest.tpl
new file mode 100644
index 0000000..25615f9
--- /dev/null
+++ b/hercules/templates/quest.tpl
@@ -0,0 +1,5 @@
+
+
+ {2}
+
+
diff --git a/hercules/templates/quest_db.tpl b/hercules/templates/quest_db.tpl
new file mode 100644
index 0000000..e67b40f
--- /dev/null
+++ b/hercules/templates/quest_db.tpl
@@ -0,0 +1,5 @@
+// Quest Database
+//
+// Structure of Database:
+// Quest ID,Time Limit,Target1,Val1,Target2,Val2,Target3,Val3,Quest Title
+
diff --git a/hercules/templates/quests.xml b/hercules/templates/quests.xml
new file mode 100644
index 0000000..c2828f9
--- /dev/null
+++ b/hercules/templates/quests.xml
@@ -0,0 +1,6 @@
+
+
+
+{0}
+
diff --git a/hercules/templates/skill.sql b/hercules/templates/skill.sql
new file mode 100644
index 0000000..2ee3002
--- /dev/null
+++ b/hercules/templates/skill.sql
@@ -0,0 +1,10 @@
+DROP TABLE IF EXISTS `skill`;
+
+CREATE TABLE IF NOT EXISTS `skill` (
+ `char_id` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `id` SMALLINT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `lv` TINYINT(4) UNSIGNED NOT NULL DEFAULT '0',
+ `flag` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0,
+ PRIMARY KEY (`char_id`,`id`)
+) ENGINE=InnoDB;
+
diff --git a/hercules/templates/skill.tpl b/hercules/templates/skill.tpl
new file mode 100644
index 0000000..53dc952
--- /dev/null
+++ b/hercules/templates/skill.tpl
@@ -0,0 +1 @@
+
diff --git a/hercules/templates/skills.xml b/hercules/templates/skills.xml
new file mode 100644
index 0000000..c747ed7
--- /dev/null
+++ b/hercules/templates/skills.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+{0}
+
+
diff --git a/hercules/templates/sprite.xml b/hercules/templates/sprite.xml
new file mode 100644
index 0000000..7443ba7
--- /dev/null
+++ b/hercules/templates/sprite.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+{stand}
+
+
+{walk}
+
+
+{attack}
+
+
+{dead}
+
diff --git a/hercules/templates/storage.sql b/hercules/templates/storage.sql
new file mode 100644
index 0000000..027d291
--- /dev/null
+++ b/hercules/templates/storage.sql
@@ -0,0 +1,22 @@
+DROP TABLE IF EXISTS `storage`;
+
+CREATE TABLE IF NOT EXISTS `storage` (
+ `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `account_id` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `nameid` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `amount` SMALLINT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `equip` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `identify` SMALLINT(6) UNSIGNED NOT NULL DEFAULT '0',
+ `refine` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0',
+ `attribute` TINYINT(4) UNSIGNED NOT NULL DEFAULT '0',
+ `card0` SMALLINT(11) NOT NULL DEFAULT '0',
+ `card1` SMALLINT(11) NOT NULL DEFAULT '0',
+ `card2` SMALLINT(11) NOT NULL DEFAULT '0',
+ `card3` SMALLINT(11) NOT NULL DEFAULT '0',
+ `expire_time` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+ `bound` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0',
+ `unique_id` BIGINT(20) UNSIGNED NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `account_id` (`account_id`)
+) ENGINE=InnoDB;
+
diff --git a/hercules/templates/template.tmx b/hercules/templates/template.tmx
new file mode 100644
index 0000000..ec9b214
--- /dev/null
+++ b/hercules/templates/template.tmx
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+{2}
+
+
+
+
+{4}
+
+
+
+
+{3}
+
+
+
diff --git a/hercules/templates/tileset.png b/hercules/templates/tileset.png
new file mode 100644
index 0000000..f36ccbb
Binary files /dev/null and b/hercules/templates/tileset.png differ
diff --git a/hercules/tmx_converter.py b/hercules/tmx_converter.py
new file mode 100755
index 0000000..96a37d9
--- /dev/null
+++ b/hercules/tmx_converter.py
@@ -0,0 +1,659 @@
+#!/usr/bin/env python
+# -*- encoding: utf-8 -*-
+
+## TMW2 Script
+## Modified by Jesusalva for Moubootaur Legends
+###################################################
+
+## tmx_converter.py - Extract walkmap, warp, and spawn information from maps.
+##
+## Copyright © 2012 Ben Longbons
+## Copyright © 2016-2017 The Mana World Developers
+##
+## This file is part of The Mana World
+##
+## This program is free software: you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation, either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program. If not, see .
+
+
+from __future__ import print_function
+
+import sys
+import os
+import posixpath
+import xml.sax
+import traceback
+
+dump_all = False # wall of text
+check_mobs = False # mob_db.txt
+heigherror=True
+fatalError=False
+
+# lower case versions of everything except 'spawn' and 'warp'
+other_object_types = set([
+ 'particle_effect',
+ 'npc', # not interpreted by client
+ 'script', # for ManaServ
+ 'fixme', # flag for things that didn't have a type before
+ 'music',
+])
+
+# Somebody has put ManaServ fields in our data!
+other_spawn_fields = (
+ 'spawn_rate',
+)
+other_warp_fields = (
+)
+
+TILESIZE = 32
+SEPARATOR = ''
+MESSAGE = 'This file is generated automatically. All manually added changes will be removed when running the Converter.'
+CLIENT_MAPS = 'maps'
+SERVER_WLK = 'data'
+SERVER_NPCS = 'npc'
+MOB_DB_CONF = 'db/re/mob_db.conf'
+MAP_CONF = 'conf/map/maps.conf'
+MAP_DB_CONF = 'db/map_index.txt'
+NPC_MOBS = '_mobs.txt'
+NPC_CONFIG = '_config.txt'
+NPC_WARPS = '_warps.txt'
+NPC_IMPORTS = '_import.txt'
+NPC_MASTER_IMPORTS = NPC_IMPORTS
+
+def ifte(ifs, thens, elses):
+ if ifs:
+ return thens
+ else:
+ return elses
+
+class State(object):
+ pass
+State.INITIAL = State()
+State.LAYER = State()
+State.DATA = State()
+State.FINAL = State()
+
+class Object(object):
+ __slots__ = (
+ 'name',
+ 'x', 'y',
+ 'w', 'h',
+ )
+class Mob(Object):
+ __slots__ = (
+ 'monster_id',
+ 'max_beings',
+ 'spawn',
+ 'death',
+ 'script',
+ ) + other_spawn_fields
+ def __init__(self):
+ self.max_beings = 1
+ self.spawn = 0
+ self.death = 0
+ self.script = ''
+
+class Save(Object):
+ __slots__ = (
+ 'inn',
+ )
+
+class Warp(Object):
+ __slots__ = (
+ 'dest_map',
+ 'dest_x',
+ 'dest_y',
+ 'npc_id',
+ 'trigger_x',
+ 'trigger_y',
+ 'notes',
+ ) + other_warp_fields
+ def __init__(self):
+ self.npc_id = 'WARP'
+
+# TMW2 CUSTOM OBJECTS
+####################################
+class Slide(Object):
+ __slots__ = (
+ 'dest_x',
+ 'dest_y',
+ 'npc_id',
+ 'trigger_x',
+ 'trigger_y',
+ 'notes',
+ ) + other_warp_fields
+ def __init__(self):
+ self.npc_id = 'SLIDE'
+
+class DynCollision(Object):
+ __slots__ = (
+ 'colid',
+ 'enabled',
+ )
+ def __init__(self):
+ self.colid = 1
+ self.enabled = True
+
+class DungeonSwitch(Object):
+ __slots__ = (
+ 'enabled',
+ 'distance',
+ 'callfunc',
+ 'doevent',
+ 'args',
+ )
+ def __init__(self):
+ self.enabled = False
+ self.distance = 2
+ self.callfunc = ''
+ self.doevent = ''
+ self.args = ''
+
+class FunctionTrigger(Object):
+ __slots__ = (
+ 'callfunc',
+ 'doevent',
+ 'args',
+ )
+ def __init__(self):
+ self.callfunc = ''
+ self.doevent = ''
+ self.args = ''
+
+class Trap(Object):
+ __slots__ = (
+ 'disarmtime',
+ 'stuntime',
+ 'damage',
+ 'target',
+ )
+ def __init__(self):
+ self.disarmtime = 15
+ self.stuntime = 3
+ self.damage = 80
+ self.target = 3
+
+class TreasureChest(Object):
+ __slots__ = (
+ 'distance',
+ )
+ def __init__(self):
+ self.distance = 2
+
+####################################
+class ContentHandler(xml.sax.ContentHandler):
+ __slots__ = (
+ 'locator', # keeps track of location in document
+ 'state', # state of height info
+ 'tilesets', # first gid of each tileset
+ 'buffer', # characters within a section
+ 'encoding', # encoding of layer data
+ 'compression', # compression of layer data
+ 'width', # width of the height layer
+ 'height', # height of the height layer
+ 'firstgid', # first gid of height layer
+ 'heightmap',# height map
+ 'base', # base name of current map
+ 'npc_dir', # world/map/npc/
+ 'mobs', # open file to _mobs.txt
+ 'warps', # open file to _warps.txt
+ 'imports', # open file to _import.txt
+ 'name', # name property of the current map
+ 'object', # stores properties of the latest tag
+ 'mob_ids', # set of all mob types that spawn here
+ )
+ def __init__(self, npc_dir, mobs, confs, warps, imports):
+ xml.sax.ContentHandler.__init__(self)
+ self.locator = None
+ self.state = State.INITIAL
+ self.tilesets = set([0]) # consider the null tile as its own tileset
+ self.buffer = bytearray()
+ self.encoding = None
+ self.compression = None
+ self.width = None
+ self.height = None
+ self.firstgid = 0
+ self.heightmap = ''
+ self.base = posixpath.basename(npc_dir)
+ self.npc_dir = npc_dir
+ self.mobs = mobs
+ self.confs = confs
+ self.warps = warps
+ self.imports = imports
+ self.object = None
+ self.mob_ids = set()
+ self.mob_cnt = False
+ self.save_cnt = False
+ self.warp_cnt = False
+
+ def setDocumentLocator(self, loc):
+ self.locator = loc
+
+ # this method randomly cuts in the middle of a line; thus funky logic
+ def characters(self, s):
+ if not s.strip():
+ return
+ if self.state is State.DATA:
+ self.buffer += s.encode('ascii')
+
+ def startDocument(self):
+ pass
+
+ def startElement(self, name, attr):
+ global heigherror
+ if dump_all:
+ attrs = ' '.join('%s="%s"' % (k,v) for k,v in attr.items())
+ if attrs:
+ print('<%s %s>' % (name, attrs))
+ else:
+ print('<%s>' % name)
+
+ if self.state is State.INITIAL:
+ if name == u'property' and attr[u'name'].lower() == u'name':
+ self.name = attr[u'value']
+ self.mobs.write('// %s\n' % MESSAGE)
+ self.mobs.write('// Map %s: %s mobs\n' % (self.base, self.name))
+ self.confs.write('// %s\n' % MESSAGE)
+ self.confs.write('// Map %s: %s conf\n' % (self.base, self.name))
+ self.warps.write('// %s\n' % MESSAGE)
+ self.warps.write('// Map %s: %s warps\n' % (self.base, self.name))
+
+ if name == u'tileset':
+ self.tilesets.add(int(attr[u'firstgid']))
+ if 'name' in attr.__dict__['_attrs'].keys():
+ if attr[u'name'] == u'Height Numbers':
+ self.firstgid = int(attr[u'firstgid'])
+
+ if name == u'layer' and attr[u'name'].lower().startswith(u'height'):
+ self.width = int(attr[u'width'])
+ self.height = int(attr[u'height'])
+ self.state = State.LAYER
+ heigherror=False
+ # Map width must be enough to fill the largest widescreen on market
+ if (self.width < 1920/TILESIZE):
+ print('Bad map width: %d (min. %d)' % (self.width, 1920/TILESIZE))
+ elif self.state is State.LAYER:
+ if name == u'data':
+ if attr.get(u'encoding','') not in (u'', u'csv'):
+ print('Bad encoding:', attr.get(u'encoding',''))
+ return
+ self.encoding = attr.get(u'encoding','')
+ if attr.get(u'compression','') not in (u'', u'none'):
+ print('Bad compression:', attr.get(u'compression',''))
+ return
+ self.compression = attr.get(u'compression','')
+ self.state = State.DATA
+ elif self.state is State.FINAL:
+ if name == u'object':
+ obj_type = attr[u'type'].lower()
+ x = int(int(attr[u'x']) / TILESIZE);
+ y = int(int(attr[u'y']) / TILESIZE);
+ w = int(int(attr.get(u'width', 0)) / TILESIZE);
+ h = int(int(attr.get(u'height', 0)) / TILESIZE);
+ # I'm not sure exactly what the w/h shrinking is for,
+ # I just copied it out of the old converter.
+ # I know that the x += w/2 is to get centers, though.
+ if obj_type == 'spawn':
+ self.object = Mob()
+ w = int((w - 1) / 2)
+ h = int((h - 1) / 2)
+ if w < 0:
+ w = 0
+ else:
+ x += w
+ if h < 0:
+ h = 0
+ else:
+ y += h
+ elif obj_type == 'save':
+ self.object = Save()
+ x += w/2
+ y += h/2
+ w -= 2
+ h -= 2
+ elif obj_type == 'warp':
+ self.object = Warp()
+ x += w/2
+ y += h/2
+ w -= 1
+ h -= 1
+ ### TMW2 INSTANCES
+ ##########################################################
+ elif obj_type == 'slide':
+ self.object = Slide()
+ x += w/2
+ y += h/2
+ w -= 1
+ h -= 1
+ elif obj_type == 'dyncollision':
+ self.object = DynCollision()
+ w -= 1
+ h -= 1
+ elif obj_type == 'switch':
+ self.object = DungeonSwitch()
+ x += w/2
+ y += h/2
+ w -= 1
+ h -= 1
+ elif obj_type == 'treasure':
+ self.object = TreasureChest()
+ x += w/2
+ y += h/2
+ w -= 1
+ h -= 1
+ elif obj_type == 'function':
+ self.object = FunctionTrigger()
+ x += w/2
+ y += h/2
+ w -= 1
+ h -= 1
+ elif obj_type == 'trap':
+ self.object = Trap()
+ x += w/2
+ y += h/2
+ w -= 1
+ h -= 1
+ else:
+ if obj_type not in other_object_types:
+ print('Unknown object type:', obj_type, file=sys.stderr)
+ self.object = None
+ return
+ obj = self.object
+ obj.x = x
+ obj.y = y
+ obj.w = w
+ obj.h = h
+ obj.name = attr[u'name']
+ elif name == u'property':
+ obj = self.object
+ if obj is None:
+ return
+ key = attr[u'name'].lower()
+ value = attr[u'value']
+ # Not true due to defaulting
+ #assert not hasattr(obj, key)
+ try:
+ value = int(value)
+ except ValueError:
+ pass
+ setattr(obj, key, value)
+
+
+ def add_warp_line(self, line):
+ self.warps.write(line)
+
+ def endElement(self, name):
+ if dump_all:
+ print('%s>' % name)
+
+ if name == u'object':
+ obj = self.object
+ if isinstance(obj, Mob):
+ mob_id = obj.monster_id
+ if mob_id < 1000:
+ mob_id += 1002
+ if check_mobs:
+ try:
+ name = mob_names[mob_id]
+ except KeyError:
+ print('Warning: unknown mob ID: %d (%s)' % (mob_id, obj.name))
+ else:
+ if name != obj.name:
+ print('Warning: wrong mob name: %s (!= %s)' % (obj.name, name))
+ obj.name = name
+ self.mob_ids.add(mob_id)
+ if obj.script:
+ obj.script = ",%s" % (obj.script)
+ self.mobs.write(
+ SEPARATOR.join([
+ '%s,%d,%d,%d,%d\t' % (self.base, obj.x, obj.y, obj.w, obj.h),
+ 'monster\t',
+ obj.name,
+ '\t%d,%d,%d,%d%s\n' % (mob_id, obj.max_beings, obj.spawn, obj.death, obj.script),
+ ])
+ )
+ self.mob_cnt = True
+ elif isinstance(obj, Save):
+ """
+ obj_name = "%s_%s_%s" % (self.base, obj.x, obj.y)
+ self.confs.write(
+ SEPARATOR.join([
+ '',
+ '%s,%d,%d,0\tscript\t#save_%s\tNPC_SAVE_POINT,{\n' % (self.base, obj.x, obj.y, obj_name),
+ ' savepointparticle .map$, .x, .y, %s;\n close;\n\nOnInit:\n .distance = 2;\n .sex = G_OTHER;\n end;\n}\n' % (obj.inn),
+ ])
+ )
+ self.save_cnt = True
+ """
+ print("[WARNING] Object type \"Save\" is deprecated!")
+ elif isinstance(obj, Warp):
+ if (obj.npc_id == u'WARP'):
+ obj_name = "#%s_%s_%s" % (self.base, obj.x, obj.y)
+ if (obj.dest_map.lower() in ["slide", "self"]):
+ self.warps.write(
+ SEPARATOR.join([
+ '%s,%d,%d,0\t' % (self.base, obj.x, (obj.y)),
+ 'script\t',
+ '%s\tNPC_HIDDEN,%d,%d,{\n\tend;\nOnTouch:\n\tslide %d,%d; end;\n}\n' % (obj_name, obj.w, obj.h, obj.dest_x, obj.dest_y),
+ ])
+ )
+ else:
+ self.warps.write(
+ SEPARATOR.join([
+ '%s,%d,%d,0\t' % (self.base, obj.x, obj.y),
+ 'warp\t',
+ '%s\t%s,%s,%s,%d,%d\n' % (obj_name, obj.w, obj.h, obj.dest_map, obj.dest_x, obj.dest_y),
+ ])
+ )
+ self.warp_cnt = True
+ ### TMW2 INSTANCES
+ ##############################################################
+ elif isinstance(obj, Slide):
+ if (obj.npc_id == u'SLIDE'):
+ obj_name = "#%s_%s_%s" % (self.base, obj.x, obj.y)
+ self.warps.write(
+ SEPARATOR.join([
+ '%s,%d,%d,0\t' % (self.base, obj.x, (obj.y)),
+ 'script\t',
+ '%s\tNPC_HIDDEN,%d,%d,{\n\tend;\nOnTouch:\n\tslide %d,%d; end;\n}\n' % (obj_name, obj.w, obj.h, obj.dest_x, obj.dest_y),
+ ])
+ )
+ self.warp_cnt = True
+ elif (not obj.npc_id == u'SCRIPT'):
+ obj_name = "#%s_%s_%s" % (self.base, obj.x, obj.y)
+ self.warps.write(
+ SEPARATOR.join([
+ '%s,%d,%d,0\tscript\t%s_h\tNPC_HIDDEN,0,0,{\n' % (self.base, obj.x, obj.y, obj_name),
+ 'OnTouch:\n warp "%s", %d, %d;\nclose;\n\nOnUnTouch:\n doevent "%s::OnUnTouch";\n}\n' % (obj.dest_map, obj.dest_x, obj.dest_y, obj_name),
+ '%s,%d,%d,0\tscript\t%s\t%s,%d,%d,{\n close;\nOnTouch:\n doorTouch;\n\nOnUnTouch:\n doorUnTouch;\n\nOnTimer340:\n doorTimer;\n\nOnInit:\n doorInit;\n}\n\n' % (self.base, obj.x, obj.y, obj_name, obj.npc_id, obj.trigger_x, obj.trigger_y),
+ ])
+ )
+ self.warp_cnt = True
+ elif isinstance(obj, DynCollision):
+ obj_name = "%s_%s_%s" % (self.base, obj.x, obj.y)
+ triggere = ifte(obj.enabled, "\nOnInit:", "")
+ triggerd = ifte(obj.enabled, "", "\nOnInit:")
+ self.confs.write(
+ SEPARATOR.join([
+ '\n',
+ '%s,%d,%d,0\t' % (self.base, obj.x, (obj.y)),
+ 'script\t',
+ '#%s\tNPC_HIDDEN,{\n\tend;\nOnDisable:%s\n\tdelcells "%s"; end;\n' % (obj_name, triggerd, obj_name),
+ 'OnEnable:%s\n\tsetcells "%s", %d, %d, %d, %d, %d, "%s";\n}\n' % (triggere, self.base, obj.x, obj.y, obj.x+obj.w, obj.y+obj.h, obj.colid, obj_name),
+ ])
+ )
+ self.save_cnt = True
+ elif isinstance(obj, DungeonSwitch):
+ obj_name = "%s_%s_%s" % (self.base, obj.x, obj.y)
+ status = ifte(obj.enabled, "OFFLINE", "ONLINE")
+ nstats = ifte(obj.enabled, "ONLINE", "OFFLINE")
+ func_name = ifte(obj.callfunc != "", "\tcallfunc \"%s\"%s;\n" % (obj.callfunc,
+ ifte(obj.args != "", ", %s" % obj.args, "")), "")
+ scrp_name = ifte(obj.doevent != "", "\tdoevent \"%s\";\n" % (obj.doevent), "")
+ self.confs.write(
+ SEPARATOR.join([
+ '\n',
+ '%s,%d,%d,0\t' % (self.base, obj.x, (obj.y)),
+ 'script\t',
+ '#%s\tNPC_SWITCH_%s,{\n' % (obj_name, status),
+ '\tif (getnpcclass() == NPC_SWITCH_%s)\n\t\tend;\n' % (nstats),
+ '%s%s' % (func_name, scrp_name),
+ '\tsetnpcdisplay "#%s", NPC_SWITCH_%s;\n\tend;\nOnInit:\n\t.distance=%d;\n}\n' % (obj_name, nstats, obj.distance),
+ ])
+ )
+ self.save_cnt = True
+ elif isinstance(obj, FunctionTrigger):
+ obj_name = "%s_%s_%s" % (self.base, obj.x, obj.y)
+ func_name = ifte(obj.callfunc != "", "\tcallfunc \"%s\"%s;\n" % (obj.callfunc,
+ ifte(obj.args != "", ", %s" % obj.args, "")), "")
+ scrp_name = ifte(obj.doevent != "", "\tdoevent \"%s\";\n" % (obj.doevent), "")
+ self.confs.write(
+ SEPARATOR.join([
+ '\n',
+ '%s,%d,%d,0\t' % (self.base, obj.x, (obj.y)),
+ 'script\t',
+ '#%s\tNPC_HIDDEN,%d,%d,{\n\tend;\n' % (obj_name, obj.w, obj.h),
+ 'OnTouch:\n%s%s\tend;\n}\n' % (func_name, scrp_name),
+ ])
+ )
+ self.save_cnt = True
+ elif isinstance(obj, Trap):
+ obj_name = "%s_%s_%s" % (self.base, obj.x, obj.y)
+ npcid = ifte(obj.disarmtime, "NPC_TRAP", "NPC_TRAP_ONLINE")
+ timer = ifte(obj.disarmtime, "OnTimer%d:\n\tstopnpctimer; setnpctimer 0; setnpcdisplay \"#%s\", NPC_TRAP; end;\n" % (obj.disarmtime*1000, obj_name), "")
+ self.confs.write(
+ SEPARATOR.join([
+ '\n',
+ '%s,%d,%d,0\t' % (self.base, obj.x, (obj.y)),
+ 'script\t',
+ '#%s\t%s,%d,%d,{\n\tmesn strcharinfo(0);\n\tmesq l("Something seems off with that!");\n\tclose;\n' % (obj_name, npcid, obj.w, obj.h),
+ '%s%s' % (ifte(obj.target & 1, "OnTouch:\n", ""), ifte(obj.target & 2, "OnTouchNPC:\n", "")),
+ '\tIronTrap(%d, %d, %d);\n\tend;\n%s}\n' % (obj.damage, obj.disarmtime, obj.stuntime, timer),
+ ])
+ )
+ self.save_cnt = True
+ elif isinstance(obj, TreasureChest):
+ obj_name = "%s_%s_%s" % (self.base, obj.x, obj.y)
+ self.confs.write(
+ SEPARATOR.join([
+ '\n',
+ '%s,%d,%d,0\t' % (self.base, obj.x, (obj.y)),
+ 'script\t',
+ '#%s\tNPC_CHEST,{\n\tTreasureBox();' % (obj_name),
+ '\n\tspecialeffect(.dir == 0 ? 24 : 25, AREA, getnpcid()); // closed ? opening : closing',
+ '\n\tclose;\nOnInit:\n\t.distance=%d;' % (obj.distance),
+ '\n\tend;\n}\n',
+ ])
+ )
+ self.save_cnt = True
+
+ ##############################################################
+
+ if name == u'data':
+ if self.state is State.DATA:
+ if self.encoding == u'csv':
+ for x in self.buffer.split(','):
+ if int(x) > 0:
+ self.heightmap += str((int(x) - int(self.firstgid)) + 1)
+ else:
+ self.heightmap += str(x)
+ self.state = State.FINAL
+
+ def endDocument(self):
+ if not self.mob_cnt:
+ os.remove(posixpath.join(main.this_map_npc_dir, NPC_MOBS))
+ if not self.save_cnt:
+ os.remove(posixpath.join(main.this_map_npc_dir, NPC_CONFIG))
+ if not self.warp_cnt:
+ os.remove(posixpath.join(main.this_map_npc_dir, NPC_WARPS))
+
+ imp_cnt = (len(os.walk(self.npc_dir).next()[2]))
+
+ if imp_cnt > 0:
+ self.imports.write('// Map %s: %s\n' % (self.base, self.name))
+ self.imports.write('// %s\n' % MESSAGE)
+
+ npcs = os.listdir(self.npc_dir)
+ npcs.sort()
+ for x in npcs:
+ if x == NPC_IMPORTS:
+ continue
+ if x.startswith('.'):
+ continue
+ if x.endswith('.txt') or x.endswith('.c'):
+ self.imports.write('"%s",\n' % posixpath.join(SERVER_NPCS, self.base, x))
+ else:
+ os.remove(posixpath.join(main.this_map_npc_dir, NPC_IMPORTS))
+
+def main(argv):
+ global heigherror, fatalError
+ _, client_data, server_data = argv
+ tmx_dir = posixpath.join(client_data, CLIENT_MAPS)
+ npc_dir = posixpath.join(server_data, SERVER_NPCS)
+ if check_mobs:
+ global mob_names
+ mob_names = {}
+ with open(posixpath.join(server_data, MOB_DB_CONF)) as mob_db:
+ for line in mob_db:
+ if not line.strip():
+ continue
+ if line.startswith('//'):
+ continue
+
+ npc_master = []
+ map_basenames = []
+
+ map_conf = open(posixpath.join(server_data,MAP_CONF), 'w')
+ map_db = open(posixpath.join(server_data,MAP_DB_CONF), 'w')
+ map_conf.write("map_removed: (\n)\nmap_list: (\n")
+ map_count = 1
+ for arg in sorted(os.listdir(tmx_dir)):
+ base, ext = posixpath.splitext(arg)
+
+ if ext == '.tmx':
+ map_basenames.append(base)
+ tmx = posixpath.join(tmx_dir, arg)
+ main.this_map_npc_dir = posixpath.join(npc_dir, base)
+ os.path.isdir(main.this_map_npc_dir) or os.mkdir(main.this_map_npc_dir)
+ print('Converting %s' % (tmx))
+ try:
+ with open(posixpath.join(main.this_map_npc_dir, NPC_MOBS), 'w') as mobs:
+ with open(posixpath.join(main.this_map_npc_dir, NPC_CONFIG), 'w') as confs:
+ with open(posixpath.join(main.this_map_npc_dir, NPC_WARPS), 'w') as warps:
+ with open(posixpath.join(main.this_map_npc_dir, NPC_IMPORTS), 'w') as imports:
+ xml.sax.parse(tmx, ContentHandler(main.this_map_npc_dir, mobs, confs, warps, imports))
+ except:
+ traceback.print_exc()
+ print("ERROR: MAP \"%s\" WAS NOT CONVERTED. ERROR FOUND!" % tmx)
+ fatalError=True
+ if os.path.isfile(posixpath.join(main.this_map_npc_dir, NPC_IMPORTS)):
+ npc_master.append('@include "%s"\n' % posixpath.join(SERVER_NPCS, base, NPC_IMPORTS))
+ if heigherror:
+ print("ERROR: Height layer possibly missing")
+ fatalError=True
+ heigherror=True
+
+ map_db.write('%s %d\n' % (arg.split('.')[0], map_count))
+ map_conf.write(' "%s",\n' % (arg.split('.')[0]))
+ map_count += 1
+ map_conf.write(")\n")
+ with open(posixpath.join(npc_dir, NPC_MASTER_IMPORTS), 'w') as out:
+ out.write('// %s\n\n' % MESSAGE)
+ npc_master.sort()
+ for line in npc_master:
+ out.write(line)
+ if fatalError:
+ exit(1)
+
+if __name__ == '__main__':
+ main(sys.argv)
diff --git a/herculeswrapper/char.sh b/herculeswrapper/char.sh
new file mode 100755
index 0000000..2b794d9
--- /dev/null
+++ b/herculeswrapper/char.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+export PIPE=./log/charpipe.tmp
+
+source ${dir}/include.sh
+
+server_logic ./char-server 2s
diff --git a/herculeswrapper/herc-map-wrapper-config.example b/herculeswrapper/herc-map-wrapper-config.example
new file mode 100644
index 0000000..67a70a0
--- /dev/null
+++ b/herculeswrapper/herc-map-wrapper-config.example
@@ -0,0 +1,77 @@
+## config for herc-map-wrapper
+
+echo "Copy this file to herc-map-wrapper-config" >&2
+echo "Then You need to either set the oauth secret" >&2
+echo "or use a hard-coded list instead of list_issues" >&2
+echo "Both github and gitlab methods are included.">&2
+echo "Make sure to comment out the method that isn't used" >&2
+echo "Update the with the ID number for gitlab" >&2
+echo "or the repo name if github i.e. themanaworld/server-data" >&2
+echo "Then, delete these lines" >&2
+exit 1
+
+server_data=../../server-data
+client_data=../client-data
+evol_music=../music
+tmw_tools=../tools
+
+#gitlab
+list_issues()
+{
+python -c '
+
+# replace this with one of your oauth keys from github (no permissions needed)
+oauth = "01234567890123456789"
+
+import requests
+issues = requests.get("https://git.themanaworld.org/api/v3/projects/%d/merge_requests" % '$1', params={"state": "opened"}, headers={"PRIVATE-TOKEN": "%s" % oauth, "Accept": "application/vnd.gitlab.v3+json"})
+issues.raise_for_status()
+for issue in issues.json():
+ if "test" in issue["labels"]:
+ print(issue["iid"])
+' | sort -n
+}
+
+#github
+list_issues()
+{
+python -c '
+
+# replace this with one of your oauth keys from github (no permissions needed)
+oauth = "0123456789abcdef0123456789abcdef01234567"
+
+import requests
+issues = requests.get("https://api.github.com/search/issues", params={"q": "repo:'$1' type:pr state:open label:test"}, headers={"Authorization": "token %s" % oauth, "Accept": "application/vnd.github.v3+json"})
+issues.raise_for_status()
+for issue in issues.json["items"]:
+ print(issue["number"])
+' | sort -n
+}
+
+
+server_main_branch=origin/master
+
+server_extra_branches=(
+ $(list_issues | sed 's|^|origin/merge-requests/|;')
+)
+
+client_main_branch=origin/master
+
+client_extra_branches=(
+ $(list_issues | sed 's|^|origin/merge-requests/|;')
+)
+
+music_main_branch=origin/master
+
+music_extra_branches=(
+ $(list_issues | sed 's|^|origin/merge-requests/|;')
+)
+unset list_issues
+
+motd=../server-data/npc/commands/motd-debug-text.txt
+
+expected_life=60
+trouble_sleep=60
+normal_sleep=5
+
+ulimit -c unlimited
diff --git a/herculeswrapper/include.sh b/herculeswrapper/include.sh
new file mode 100644
index 0000000..97325f8
--- /dev/null
+++ b/herculeswrapper/include.sh
@@ -0,0 +1,56 @@
+#!/usr/bin/env bash
+
+function create_pipe {
+ trap "rm -f $PIPE" EXIT
+ if [[ ! -p $PIPE ]]; then
+ echo "Making pipe $PIPE"
+ rm -f $PIPE
+ mkfifo $PIPE
+ fi
+}
+
+# $1 - text
+function send_all_pipes {
+ echo $1 >$LOGIN_PIPE
+ echo $1 >$CHAR_PIPE
+}
+
+# $1 - text
+function send_char_pipe {
+ echo $1 >$CHAR_PIPE
+}
+
+# $1 - server binary name
+# $2 - sleep time
+function server_logic {
+ create_pipe
+ $1
+
+ while true
+ do
+ if read line <$PIPE; then
+ echo pipe: $line
+ if [[ "$line" == 'exit' ]]; then
+ exit
+ fi
+ if [[ "$line" == 'restart' ]]; then
+ sleep $2
+ $1
+ fi
+ fi
+ done
+}
+
+function pull_all {
+ ls
+ # Update Server Data
+ cd ../
+ ./pull.sh force
+ # Update Client Data
+ cd testserver
+ ./pseudo_update.sh
+ cd ..
+ # Return to server data
+ cd server-data
+}
+
diff --git a/herculeswrapper/login.sh b/herculeswrapper/login.sh
new file mode 100755
index 0000000..32085dd
--- /dev/null
+++ b/herculeswrapper/login.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+export PIPE=./log/loginpipe.tmp
+
+source ${dir}/include.sh
+
+server_logic ./login-server 0s
diff --git a/herculeswrapper/map.sh b/herculeswrapper/map.sh
new file mode 100755
index 0000000..67c5147
--- /dev/null
+++ b/herculeswrapper/map.sh
@@ -0,0 +1,52 @@
+#!/usr/bin/env bash
+
+export PIPE=./log/mappipe.tmp
+export LOGIN_PIPE=./log/loginpipe.tmp
+export CHAR_PIPE=./log/charpipe.tmp
+
+source ${dir}/include.sh
+
+create_pipe
+
+while [ 1 ] ; do
+ ./map-server
+ export ret=$?
+ case "${ret}" in
+ 0)
+ echo "Returned 0. Probably ctrl+c"
+ break
+ ;;
+ 1)
+ echo "Returned 1. Probably error in server"
+ break
+ ;;
+ 100)
+ echo "Terminating server"
+ send_all_pipes "exit"
+ break
+ ;;
+ 101)
+ echo "Restarting all servers..."
+ send_all_pipes "restart"
+ sleep 7s
+ ;;
+ 102)
+ echo "Restarting char and map servers..."
+ send_char_pipe "restart"
+ sleep 5s
+ ;;
+ 103)
+ echo "Restarting map server..."
+ ;;
+ 104)
+ echo "git pull..."
+ pull_all
+ echo "Restarting all servers..."
+ send_all_pipes "restart"
+ sleep 7s
+ ;;
+ esac
+done
+
+killall logmaster.py
+
diff --git a/imagescheck/icccheck.sh b/imagescheck/icccheck.sh
new file mode 100755
index 0000000..28a7b37
--- /dev/null
+++ b/imagescheck/icccheck.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+export DIR="../../clientdata"
+
+find -H $DIR -type f -name "*.png" -exec ./icccheckfile.sh {} \;
diff --git a/imagescheck/icccheckfile.sh b/imagescheck/icccheckfile.sh
new file mode 100755
index 0000000..5b45466
--- /dev/null
+++ b/imagescheck/icccheckfile.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+identify -verbose $1 | egrep -i "profile|iCCP" >/dev/null
+
+if [ "$?" == 0 ]; then
+ export name="$1"
+ export name=${name##../../clientdata/}
+ echo "ICC or iCCP profile found for image $name"
+fi
diff --git a/licensecheck/checkfile.sh b/licensecheck/checkfile.sh
new file mode 100755
index 0000000..38835d5
--- /dev/null
+++ b/licensecheck/checkfile.sh
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+
+export name="$3"
+export name=${name##../../clientdata/}
+
+grep "$name" $1 >/dev/null
+if [ "$?" != 0 ]; then
+ grep "$name " $2 >/dev/null
+ if [ "$?" != 0 ]; then
+ echo "Missing license for $name"
+ fi
+fi
diff --git a/licensecheck/clientdata.sh b/licensecheck/clientdata.sh
new file mode 100755
index 0000000..cd63426
--- /dev/null
+++ b/licensecheck/clientdata.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+export DIR="../../clientdata"
+
+find -H $DIR -type f -name "*.png" -exec ./checkfile.sh $DIR/LICENSE $DIR/ART_LICENSE {} \;
+find -H $DIR/sfx -type f -name "*.ogg" -exec ./checkfile.sh $DIR/LICENSE $DIR/ART_LICENSE {} \;
+find -H $DIR -type f -name "*.tmx" -exec ./checkfile.sh $DIR/LICENSE $DIR/ART_LICENSE {} \;
+find -H $DIR -type f -name "*.jpg" -exec ./checkfile.sh $DIR/LICENSE $DIR/ART_LICENSE {} \;
+
diff --git a/licensecheck/serverdata.py b/licensecheck/serverdata.py
new file mode 100755
index 0000000..8a731a4
--- /dev/null
+++ b/licensecheck/serverdata.py
@@ -0,0 +1,71 @@
+#! /usr/bin/env python2.7
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2018 TMW-2
+# Author: Jesusalva
+
+# Bad command:
+# ls --recursive --hyperlink=always --format=single-column ../../serverdata/npc/|grep txt
+
+# Initialize stuff
+import subprocess
+import sys
+import os
+erp=[]
+
+# Clear previous NPC list
+try:
+ subprocess.call("rm npcs.txt", shell=True)
+except:
+ pass
+
+# Determine correct path
+PATH="../../serverdata/npc/"
+if len(sys.argv) == 2:
+ PATH=sys.argv[1]
+
+# Generate NPC list
+subprocess.call("find "+PATH+" txt > npcs.txt", shell=True)
+npcs=open("npcs.txt", "r")
+
+# Begin
+print("Checking license info for NPCs")
+print("Source is at: "+PATH)
+
+for mpa in npcs:
+ mp=mpa.replace('\n','')
+ # Skip mapflags
+ if "mapflag" in mp:
+ continue
+ # Skip bad files
+ if not '.txt' in mp:
+ continue
+ # Skip certain folders
+ if "/dev/" in mp or "/00000SAVE/" in mp or "/test/" in mp:
+ continue
+
+ a=open(mp, 'r')
+ #print("Verify %s" % mp)
+ ok=False
+ for line in a:
+ if 'tmw2 script' in line.lower() or 'tmw-2 script' in line.lower() or 'tmw 2 script' in line.lower() or 'tmw2/lof script' in line.lower() or 'This file is generated automatically' in line or 'author' in line.lower() or 'tmw2 function' in line.lower() or 'tmw-2 function' in line.lower() or 'tmw 2 function' in line.lower():
+ ok=True
+ break
+
+ a.close()
+ if not ok:
+ erp.append(mp)
+
+npcs.close()
+if len(erp) > 0:
+ print("-----------------------------------------------------------------------")
+
+for i in sorted(erp):
+ print(i)
+
+print("-----------------------------------------------------------------------")
+print("Serverdata license check result")
+print("Errors: %d" % (len(erp)))
+if len(erp):
+ os.exit(1)
+
diff --git a/localserver/.gitignore b/localserver/.gitignore
new file mode 100644
index 0000000..ff3de76
--- /dev/null
+++ b/localserver/.gitignore
@@ -0,0 +1 @@
+versions
diff --git a/localserver/README b/localserver/README
new file mode 100644
index 0000000..8d1e2a3
--- /dev/null
+++ b/localserver/README
@@ -0,0 +1,14 @@
+Here located for LOCAL server usage ONLY.
+Can be unsecure to use this files in public or production server.
+
+build.sh - build server from sources with most compilers.
+buildasan.sh - build server from sources with asan flags enabled.
+clean.sh - remove all built and temp files.
+givegm.sh - give gm rights to first account created on server.
+initdb.sh - initialize mariadb database.
+installconfigs.sh - install configurations into server data directory.
+updatedb.sh - update if need sql database
+updateconfigs.sh - update if need server data configuration
+
+See also http://wiki.evolonline.org/guidelines/installmariadb
+ http://wiki.evolonline.org/guidelines/localserver
diff --git a/localserver/applicator.py b/localserver/applicator.py
new file mode 100644
index 0000000..beb99a6
--- /dev/null
+++ b/localserver/applicator.py
@@ -0,0 +1,45 @@
+import subprocess
+
+# Open reapply.patch3
+f=open("reapply.patch3", "r")
+subprocess.call("cd ../../server-code", shell=True)
+
+for line in f:
+ if line[0] == "#" or line[0] == "\r" or line[0] == "\n":
+ continue
+ print "Downloading patch "+line.replace("\n", "")
+ subprocess.call("cd ../../server-code ; wget https://gitlab.com/evol/hercules/commit/"+line.replace("\n", "")+".diff", shell=True)
+ print "Applying patch..."
+ #subprocess.call("cd ../../server-code ; ls", shell=True)
+ subprocess.call("cd ../../server-code ; git apply --ignore-whitespace --reject "+line.replace("\n", "")+".diff", shell=True)
+ print "Patch applied"
+ subprocess.call("cd ../../server-code ; rm "+line.replace("\n", "")+".diff", shell=True)
+ print "Patch deleted (success)"
+
+
+f.close()
+print "Evol porting finished"
+
+
+###############################################
+# Open reapply.patch4 (HerculesWS Upstream)
+f=open("reapply.patch4", "r")
+subprocess.call("cd ../../server-code", shell=True)
+
+for line in f:
+ if line[0] == "#" or line[0] == "\r" or line[0] == "\n":
+ continue
+ print "Downloading patch "+line.replace("\n", "")
+ subprocess.call("cd ../../server-code ; wget https://github.com/HerculesWS/Hercules/commit/"+line.replace("\n", "")+".diff", shell=True)
+ print "Applying patch..."
+ #subprocess.call("cd ../../server-code ; ls", shell=True)
+ subprocess.call("cd ../../server-code ; git apply --ignore-whitespace --exclude=doc/ --exclude=tools/ --exclude=db/ --exclude=npc/ --exclude=conf/ --reject "+line.replace("\n", "")+".diff", shell=True)
+ print "Patch applied"
+ subprocess.call("cd ../../server-code ; rm "+line.replace("\n", "")+".diff", shell=True)
+ print "Patch deleted (success)"
+
+
+f.close()
+print "HercWS porting finished"
+subprocess.call("ls --recursive ../../server-code|grep rej", shell=True)
+
diff --git a/localserver/build.sh b/localserver/build.sh
new file mode 100755
index 0000000..e861d0e
--- /dev/null
+++ b/localserver/build.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+
+export DIR=`pwd`
+
+./checktime.sh
+./updater.sh
+source ./clean.sh
+./build.sh old > /dev/null
diff --git a/localserver/buildasan.sh b/localserver/buildasan.sh
new file mode 100755
index 0000000..6ed4c05
--- /dev/null
+++ b/localserver/buildasan.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+
+export DIR=`pwd`
+./updater.sh
+source ./clean.sh
+./build.sh
diff --git a/localserver/checktime.sh b/localserver/checktime.sh
new file mode 100755
index 0000000..9ca0c95
--- /dev/null
+++ b/localserver/checktime.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+year=$(date "+%Y")
+
+if [[ $year -lt 2017 ]]; then
+ date
+ echo "Error: Date time is wrong. Please fix time. See man date"
+ exit 1
+fi
\ No newline at end of file
diff --git a/localserver/clean.sh b/localserver/clean.sh
new file mode 100755
index 0000000..134f5cb
--- /dev/null
+++ b/localserver/clean.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+
+rm -rf ../../server-code/src/evol/build/
+mkdir ../../server-code/src/evol/build/
+cd ../../server-code/
+make clean
diff --git a/localserver/conf/battle.conf b/localserver/conf/battle.conf
new file mode 100644
index 0000000..4080eed
--- /dev/null
+++ b/localserver/conf/battle.conf
@@ -0,0 +1,32 @@
+//================= Hercules Configuration ================================
+//= _ _ _
+//= | | | | | |
+//= | |_| | ___ _ __ ___ _ _| | ___ ___
+//= | _ |/ _ \ '__/ __| | | | |/ _ \/ __|
+//= | | | | __/ | | (__| |_| | | __/\__ \
+//= \_| |_/\___|_| \___|\__,_|_|\___||___/
+//================= License ===============================================
+//= This file is part of Hercules.
+//= http://herc.ws - http://github.com/HerculesWS/Hercules
+//=
+//= Copyright (C) 2014-2016 Hercules Dev Team
+//=
+//= Hercules is free software: you can redistribute it and/or modify
+//= it under the terms of the GNU General Public License as published by
+//= the Free Software Foundation, either version 3 of the License, or
+//= (at your option) any later version.
+//=
+//= This program is distributed in the hope that it will be useful,
+//= but WITHOUT ANY WARRANTY; without even the implied warranty of
+//= MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+//= GNU General Public License for more details.
+//=
+//= You should have received a copy of the GNU General Public License
+//= along with this program. If not, see .
+//=========================================================================
+//= Battle local configuration file.
+//=========================================================================
+
+battle_configuration: {
+ // See conf/map/battle.conf for details
+}
diff --git a/localserver/conf/char-server.conf b/localserver/conf/char-server.conf
new file mode 100644
index 0000000..03b1900
--- /dev/null
+++ b/localserver/conf/char-server.conf
@@ -0,0 +1,31 @@
+//================= Hercules Configuration ================================
+//= _ _ _
+//= | | | | | |
+//= | |_| | ___ _ __ ___ _ _| | ___ ___
+//= | _ |/ _ \ '__/ __| | | | |/ _ \/ __|
+//= | | | | __/ | | (__| |_| | | __/\__ \
+//= \_| |_/\___|_| \___|\__,_|_|\___||___/
+//================= License ===============================================
+//= This file is part of Hercules.
+//= http://herc.ws - http://github.com/HerculesWS/Hercules
+//=
+//= Copyright (C) 2014-2016 Hercules Dev Team
+//=
+//= Hercules is free software: you can redistribute it and/or modify
+//= it under the terms of the GNU General Public License as published by
+//= the Free Software Foundation, either version 3 of the License, or
+//= (at your option) any later version.
+//=
+//= This program is distributed in the hope that it will be useful,
+//= but WITHOUT ANY WARRANTY; without even the implied warranty of
+//= MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+//= GNU General Public License for more details.
+//=
+//= You should have received a copy of the GNU General Public License
+//= along with this program. If not, see .
+//=========================================================================
+//= Character Server local configuration file.
+//=========================================================================
+
+min_char_class: 0
+max_char_class: 2
diff --git a/localserver/conf/constants.conf b/localserver/conf/constants.conf
new file mode 100644
index 0000000..4562d0c
--- /dev/null
+++ b/localserver/conf/constants.conf
@@ -0,0 +1,30 @@
+//================= Hercules Configuration ================================
+//= _ _ _
+//= | | | | | |
+//= | |_| | ___ _ __ ___ _ _| | ___ ___
+//= | _ |/ _ \ '__/ __| | | | |/ _ \/ __|
+//= | | | | __/ | | (__| |_| | | __/\__ \
+//= \_| |_/\___|_| \___|\__,_|_|\___||___/
+//================= License ===============================================
+//= This file is part of Hercules.
+//= http://herc.ws - http://github.com/HerculesWS/Hercules
+//=
+//= Copyright (C) 2014-2016 Hercules Dev Team
+//=
+//= Hercules is free software: you can redistribute it and/or modify
+//= it under the terms of the GNU General Public License as published by
+//= the Free Software Foundation, either version 3 of the License, or
+//= (at your option) any later version.
+//=
+//= This program is distributed in the hope that it will be useful,
+//= but WITHOUT ANY WARRANTY; without even the implied warranty of
+//= MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+//= GNU General Public License for more details.
+//=
+//= You should have received a copy of the GNU General Public License
+//= along with this program. If not, see .
+//=========================================================================
+//= Battle local configuration file.
+//=========================================================================
+
+debug: 0
diff --git a/localserver/conf/inter-server.conf b/localserver/conf/inter-server.conf
new file mode 100644
index 0000000..243dda4
--- /dev/null
+++ b/localserver/conf/inter-server.conf
@@ -0,0 +1,32 @@
+//================= Hercules Configuration ================================
+//= _ _ _
+//= | | | | | |
+//= | |_| | ___ _ __ ___ _ _| | ___ ___
+//= | _ |/ _ \ '__/ __| | | | |/ _ \/ __|
+//= | | | | __/ | | (__| |_| | | __/\__ \
+//= \_| |_/\___|_| \___|\__,_|_|\___||___/
+//================= License ===============================================
+//= This file is part of Hercules.
+//= http://herc.ws - http://github.com/HerculesWS/Hercules
+//=
+//= Copyright (C) 2014-2016 Hercules Dev Team
+//=
+//= Hercules is free software: you can redistribute it and/or modify
+//= it under the terms of the GNU General Public License as published by
+//= the Free Software Foundation, either version 3 of the License, or
+//= (at your option) any later version.
+//=
+//= This program is distributed in the hope that it will be useful,
+//= but WITHOUT ANY WARRANTY; without even the implied warranty of
+//= MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+//= GNU General Public License for more details.
+//=
+//= You should have received a copy of the GNU General Public License
+//= along with this program. If not, see .
+//=========================================================================
+//= Hercules Inter Server local configuration file.
+//=========================================================================
+
+inter_configuration: {
+ // See conf/common/inter-server.conf
+}
diff --git a/localserver/conf/login-server.conf b/localserver/conf/login-server.conf
new file mode 100644
index 0000000..feadef9
--- /dev/null
+++ b/localserver/conf/login-server.conf
@@ -0,0 +1,32 @@
+//================= Hercules Configuration ================================
+//= _ _ _
+//= | | | | | |
+//= | |_| | ___ _ __ ___ _ _| | ___ ___
+//= | _ |/ _ \ '__/ __| | | | |/ _ \/ __|
+//= | | | | __/ | | (__| |_| | | __/\__ \
+//= \_| |_/\___|_| \___|\__,_|_|\___||___/
+//================= License ===============================================
+//= This file is part of Hercules.
+//= http://herc.ws - http://github.com/HerculesWS/Hercules
+//=
+//= Copyright (C) 2014-2016 Hercules Dev Team
+//=
+//= Hercules is free software: you can redistribute it and/or modify
+//= it under the terms of the GNU General Public License as published by
+//= the Free Software Foundation, either version 3 of the License, or
+//= (at your option) any later version.
+//=
+//= This program is distributed in the hope that it will be useful,
+//= but WITHOUT ANY WARRANTY; without even the implied warranty of
+//= MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+//= GNU General Public License for more details.
+//=
+//= You should have received a copy of the GNU General Public License
+//= along with this program. If not, see .
+//=========================================================================
+//= Login Server local configuration file.
+//=========================================================================
+
+login_configuration: {
+ // See conf/login/login-server.conf for details
+}
diff --git a/localserver/conf/logs.conf b/localserver/conf/logs.conf
new file mode 100644
index 0000000..99e666d
--- /dev/null
+++ b/localserver/conf/logs.conf
@@ -0,0 +1,32 @@
+//================= Hercules Configuration ================================
+//= _ _ _
+//= | | | | | |
+//= | |_| | ___ _ __ ___ _ _| | ___ ___
+//= | _ |/ _ \ '__/ __| | | | |/ _ \/ __|
+//= | | | | __/ | | (__| |_| | | __/\__ \
+//= \_| |_/\___|_| \___|\__,_|_|\___||___/
+//================= License ===============================================
+//= This file is part of Hercules.
+//= http://herc.ws - http://github.com/HerculesWS/Hercules
+//=
+//= Copyright (C) 2014-2016 Hercules Dev Team
+//=
+//= Hercules is free software: you can redistribute it and/or modify
+//= it under the terms of the GNU General Public License as published by
+//= the Free Software Foundation, either version 3 of the License, or
+//= (at your option) any later version.
+//=
+//= This program is distributed in the hope that it will be useful,
+//= but WITHOUT ANY WARRANTY; without even the implied warranty of
+//= MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+//= GNU General Public License for more details.
+//=
+//= You should have received a copy of the GNU General Public License
+//= along with this program. If not, see .
+//=========================================================================
+//= Hercules Log local configuration file.
+//=========================================================================
+
+map_log: {
+ // See conf/map/logs.conf for details
+}
diff --git a/localserver/conf/map-server.conf b/localserver/conf/map-server.conf
new file mode 100644
index 0000000..2c9b165
--- /dev/null
+++ b/localserver/conf/map-server.conf
@@ -0,0 +1,32 @@
+//================= Hercules Configuration ================================
+//= _ _ _
+//= | | | | | |
+//= | |_| | ___ _ __ ___ _ _| | ___ ___
+//= | _ |/ _ \ '__/ __| | | | |/ _ \/ __|
+//= | | | | __/ | | (__| |_| | | __/\__ \
+//= \_| |_/\___|_| \___|\__,_|_|\___||___/
+//================= License ===============================================
+//= This file is part of Hercules.
+//= http://herc.ws - http://github.com/HerculesWS/Hercules
+//=
+//= Copyright (C) 2014-2016 Hercules Dev Team
+//=
+//= Hercules is free software: you can redistribute it and/or modify
+//= it under the terms of the GNU General Public License as published by
+//= the Free Software Foundation, either version 3 of the License, or
+//= (at your option) any later version.
+//=
+//= This program is distributed in the hope that it will be useful,
+//= but WITHOUT ANY WARRANTY; without even the implied warranty of
+//= MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+//= GNU General Public License for more details.
+//=
+//= You should have received a copy of the GNU General Public License
+//= along with this program. If not, see .
+//=========================================================================
+//= Map Server local configuration file.
+//=========================================================================
+
+map_configuration: {
+ // See conf/map/map-server.conf for details
+}
diff --git a/localserver/conf/msg_conf.txt b/localserver/conf/msg_conf.txt
new file mode 100644
index 0000000..e69de29
diff --git a/localserver/conf/ports.conf b/localserver/conf/ports.conf
new file mode 100644
index 0000000..7d5481e
--- /dev/null
+++ b/localserver/conf/ports.conf
@@ -0,0 +1,8 @@
+// Login server port
+login_port: 6901
+
+// Character server port
+char_port: 6122
+
+// Map server port
+map_port: 5122
diff --git a/localserver/conf/script.conf b/localserver/conf/script.conf
new file mode 100644
index 0000000..b354b2b
--- /dev/null
+++ b/localserver/conf/script.conf
@@ -0,0 +1,32 @@
+//================= Hercules Configuration ================================
+//= _ _ _
+//= | | | | | |
+//= | |_| | ___ _ __ ___ _ _| | ___ ___
+//= | _ |/ _ \ '__/ __| | | | |/ _ \/ __|
+//= | | | | __/ | | (__| |_| | | __/\__ \
+//= \_| |_/\___|_| \___|\__,_|_|\___||___/
+//================= License ===============================================
+//= This file is part of Hercules.
+//= http://herc.ws - http://github.com/HerculesWS/Hercules
+//=
+//= Copyright (C) 2014-2016 Hercules Dev Team
+//=
+//= Hercules is free software: you can redistribute it and/or modify
+//= it under the terms of the GNU General Public License as published by
+//= the Free Software Foundation, either version 3 of the License, or
+//= (at your option) any later version.
+//=
+//= This program is distributed in the hope that it will be useful,
+//= but WITHOUT ANY WARRANTY; without even the implied warranty of
+//= MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+//= GNU General Public License for more details.
+//=
+//= You should have received a copy of the GNU General Public License
+//= along with this program. If not, see .
+//=========================================================================
+//= Hercules Script local configuration file.
+//=========================================================================
+
+script_configuration: {
+ // See conf/map/script.conf for details
+}
diff --git a/localserver/conf/socket.conf b/localserver/conf/socket.conf
new file mode 100644
index 0000000..04d0a40
--- /dev/null
+++ b/localserver/conf/socket.conf
@@ -0,0 +1,32 @@
+//================= Hercules Configuration ================================
+//= _ _ _
+//= | | | | | |
+//= | |_| | ___ _ __ ___ _ _| | ___ ___
+//= | _ |/ _ \ '__/ __| | | | |/ _ \/ __|
+//= | | | | __/ | | (__| |_| | | __/\__ \
+//= \_| |_/\___|_| \___|\__,_|_|\___||___/
+//================= License ===============================================
+//= This file is part of Hercules.
+//= http://herc.ws - http://github.com/HerculesWS/Hercules
+//=
+//= Copyright (C) 2014-2016 Hercules Dev Team
+//=
+//= Hercules is free software: you can redistribute it and/or modify
+//= it under the terms of the GNU General Public License as published by
+//= the Free Software Foundation, either version 3 of the License, or
+//= (at your option) any later version.
+//=
+//= This program is distributed in the hope that it will be useful,
+//= but WITHOUT ANY WARRANTY; without even the implied warranty of
+//= MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+//= GNU General Public License for more details.
+//=
+//= You should have received a copy of the GNU General Public License
+//= along with this program. If not, see .
+//=========================================================================
+//= Hercules Sockets local configuration file
+//=========================================================================
+
+socket_configuration: {
+ // See conf/common/socket.conf for details.
+}
diff --git a/localserver/conf/sql_connection.conf b/localserver/conf/sql_connection.conf
new file mode 100644
index 0000000..1f9f514
--- /dev/null
+++ b/localserver/conf/sql_connection.conf
@@ -0,0 +1,52 @@
+//================= Hercules Configuration ================================
+//= _ _ _
+//= | | | | | |
+//= | |_| | ___ _ __ ___ _ _| | ___ ___
+//= | _ |/ _ \ '__/ __| | | | |/ _ \/ __|
+//= | | | | __/ | | (__| |_| | | __/\__ \
+//= \_| |_/\___|_| \___|\__,_|_|\___||___/
+//================= License ===============================================
+//= This file is part of Hercules.
+//= http://herc.ws - http://github.com/HerculesWS/Hercules
+//=
+//= Copyright (C) 2014-2016 Hercules Dev Team
+//=
+//= Hercules is free software: you can redistribute it and/or modify
+//= it under the terms of the GNU General Public License as published by
+//= the Free Software Foundation, either version 3 of the License, or
+//= (at your option) any later version.
+//=
+//= This program is distributed in the hope that it will be useful,
+//= but WITHOUT ANY WARRANTY; without even the implied warranty of
+//= MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+//= GNU General Public License for more details.
+//=
+//= You should have received a copy of the GNU General Public License
+//= along with this program. If not, see .
+//=========================================================================
+//= SQL connection configuration file.
+//=========================================================================
+// This file affects how ALL server sql connections work, unless explictly
+// defined so in the server configuration file (See
+// doc/global_configuration.txt for more information).
+//=========================================================================
+
+sql_connection: {
+ // [INTER] You can specify the codepage to use in your mySQL tables here.
+ // (Note that this feature requires MySQL 4.1+)
+ //default_codepage: ""
+
+ // [LOGIN] Is `userid` in account_db case sensitive?
+ //case_sensitive: false
+
+ // For IPs, ideally under linux, you want to use localhost instead of 127.0.0.1.
+ // Under windows, you want to use 127.0.0.1. If you see a message like
+ // "Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)"
+ // and you have localhost, switch it to 127.0.0.1
+ db_hostname: "127.0.0.1"
+ db_port: 3306
+ db_username: "evol"
+ db_password: "evol"
+ db_database: "evol"
+ //codepage:""
+}
diff --git a/localserver/givegm.sh b/localserver/givegm.sh
new file mode 100755
index 0000000..ac9d301
--- /dev/null
+++ b/localserver/givegm.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+
+ID="$1"
+if [[ -z "${ID}" ]]; then
+ mysql -u evol -pevol evol -e "update login set group_id=99 where account_id=2000000;"
+else
+ mysql -u evol -pevol evol -e "update login set group_id=99 where account_id=${ID} or userid='${ID}';"
+fi
diff --git a/localserver/initdb.sh b/localserver/initdb.sh
new file mode 100755
index 0000000..5a5db2d
--- /dev/null
+++ b/localserver/initdb.sh
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+
+export DIR="$(pwd)"
+export VER=$(cat versions/sqlver 2>/dev/null)
+
+cd ../../serverdata/sql-files
+
+echo Creating db and user...
+echo Enter mysql root password:
+mysql --force -u root -p <./init.sql
+result=$?
+if [ "$result" != 0 ]; then
+ echo Password failed. Trying with sudo...
+ sudo mysql --force -u root <./init.sql
+fi
+
+export CMD="mysql -u evol -pevol evol"
+
+echo Creating tables...
+$CMD ${DIR}/versions/sqlver
+fi
diff --git a/localserver/installconfigs.sh b/localserver/installconfigs.sh
new file mode 100755
index 0000000..2a42c3c
--- /dev/null
+++ b/localserver/installconfigs.sh
@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+
+export SD="../../serverdata"
+export CONF="$SD/conf/import"
+export NPC="$SD/npc"
+
+mkdir $CONF
+cp conf/* $CONF
+cp -f ${SD}/conf/channels.conf.base ${SD}/conf/channels.conf
+cp -f npc/006-10_* ${NPC}/006-10/
+cp -f npc/017-1_* ${NPC}/017-1/
+cp -f npc/015-8_* ${NPC}/015-8/
+cp -f npc/025-2-4_* ${NPC}/025-2-4/
+cp -f npc/018-7-1_* ${NPC}/018-7-1/
+cp -f npc/023-4_* ${NPC}/023-4/
+cp -f npc/botcheck_* ${NPC}/botcheck/
+cp -f npc/easteregg_* ${NPC}/config/
+cp -f npc/magic_* ${NPC}/magic/
+mkdir versions
+echo "4" >versions/confver
diff --git a/localserver/npc/006-10_blackbox.txt b/localserver/npc/006-10_blackbox.txt
new file mode 100644
index 0000000..924a45e
--- /dev/null
+++ b/localserver/npc/006-10_blackbox.txt
@@ -0,0 +1,12 @@
+// TMW2 Script
+// Author:
+// Jesusalva
+// Description:
+// Runestaff Blackbox
+
+// Syntax:
+// 00610_Init_BlackBox( - )
+function script 00610_Init_BlackBox {
+ return;
+}
+
diff --git a/localserver/npc/015-8_blackbox.txt b/localserver/npc/015-8_blackbox.txt
new file mode 100644
index 0000000..7ae5c57
--- /dev/null
+++ b/localserver/npc/015-8_blackbox.txt
@@ -0,0 +1,27 @@
+// TMW2 Script
+// Author:
+// Jesusalva
+// Description:
+// This is the “015-8 mapfile” black box.
+// It contains the answers to all the riddles.
+// This file is kept outside the main tree for translation purposes.
+// You thought you could cheat the answers, eh?
+
+// Syntax:
+// 0158_Riddle_BlackBox( )
+// Returns true on success, false on failure.
+function script 0158_Riddle_BlackBox {
+ .@r$="secret";
+ .@l$=l("secret");
+
+ mesc l("When I was alive, I brought people to their destiny.");
+ mesc l("One day, I was provoked. Others came to my aid,");
+ mesc l("But all I could do was a bite.");
+ mesc l("To my murders, I gave them a full meal.");
+ mesc l("And in a hut, a lone woman mourned my death.");
+ mes "";
+ input .@ans$;
+
+ return riddlevalidation(.@ans$, .@r$[.@e], .@l$[.@e]);
+}
+
diff --git a/localserver/npc/017-1_stranger_blackbox.txt b/localserver/npc/017-1_stranger_blackbox.txt
new file mode 100644
index 0000000..bdd444d
--- /dev/null
+++ b/localserver/npc/017-1_stranger_blackbox.txt
@@ -0,0 +1,40 @@
+// TMW2 Script
+// Author:
+// Jesusalva
+// Description:
+// This is the “Stranger” black box.
+// It contains the answers to all the riddles.
+// This file is kept outside the main tree for translation purposes.
+// You thought you could cheat the answers, eh?
+
+// Syntax:
+// 0171_Stranger_BlackBox( difficulty )
+// Where difficulty can be EASY, MEDIUM or HARD.
+// Returns 1 on success, 0 on failure.
+function script 0171_Stranger_BlackBox {
+ switch (getarg(0)) {
+ case EASY:
+ setarray .@q$, l("Are you cheating?"), l("Should we blame Saulc for bugs?");
+ setarray .@r$, "no", "yes";
+ setarray .@l$, l("no"), l("yes");
+ break;
+ case MEDIUM:
+ setarray .@q$, l("Are you cheating?"), l("Should we blame Saulc for bugs?");
+ setarray .@r$, "no", "yes";
+ setarray .@l$, l("no"), l("yes");
+ break;
+ case HARD:
+ setarray .@q$, l("Are you cheating?"), l("Should we blame Saulc for bugs?");
+ setarray .@r$, "no", "yes";
+ setarray .@l$, l("no"), l("yes");
+ break;
+ default: return 0;
+ }
+
+ .@e=rand(getarraysize(.@q$)-1);
+ mesq .@q$[.@e];
+ input .@ans$;
+
+ return riddlevalidation(.@ans$, .@r$[.@e], .@l$[.@e]);
+}
+
diff --git a/localserver/npc/018-7-1_demure_blackbox.txt b/localserver/npc/018-7-1_demure_blackbox.txt
new file mode 100644
index 0000000..a5e7560
--- /dev/null
+++ b/localserver/npc/018-7-1_demure_blackbox.txt
@@ -0,0 +1,12 @@
+// TMW2 Script
+// Author:
+// Jesusalva
+// Description:
+// Demure Axe Blackbox
+
+// Syntax:
+// 01871_Demure_BlackBox( - )
+function script 01871_Demure_BlackBox {
+ return;
+}
+
diff --git a/localserver/npc/023-4_blackbox.txt b/localserver/npc/023-4_blackbox.txt
new file mode 100644
index 0000000..a5687e5
--- /dev/null
+++ b/localserver/npc/023-4_blackbox.txt
@@ -0,0 +1,36 @@
+// TMW2 Script
+// Author:
+// Jesusalva
+// Description:
+// 023-4 Blackbox
+
+// Syntax:
+// 0234_Password_BlackBox( PASSWORD )
+// 0234_Init_BlackBox( - )
+function script 0234_Password_BlackBox {
+ .@message$=getarg(0, "");
+ // Only react if the message is what we want to hear
+ if (.@message$ == "PASSWORD") {
+ if (getq(FrostiaQuest_Homunculus) < 1)
+ end;
+
+ if (isat("023-3", 26, 219)) {
+ warp "023-4", 42, 69;
+ sleep2(15);
+ dispbottom l("The gates to Aethyr have opened themselves to you.");
+ }
+ }
+ return;
+}
+
+function script 0234_Init_BlackBox {
+ return;
+}
+
+- script #0234BlackBox NPC_HIDDEN,{
+ end;
+OnIter:
+ end;
+}
+
+
diff --git a/localserver/npc/025-2-4_aegis_blackbox.txt b/localserver/npc/025-2-4_aegis_blackbox.txt
new file mode 100644
index 0000000..524d704
--- /dev/null
+++ b/localserver/npc/025-2-4_aegis_blackbox.txt
@@ -0,0 +1,33 @@
+// TMW2 Script
+// Author:
+// Jesusalva
+// Description:
+// Aegis Shield & Mana Tree Blackbox
+
+// Syntax:
+// 02524_Tree_BlackBox( target name, target GID )
+// 02524_Revenge_BlackBox( - )
+// 02524_Avenge_BlackBox( deadflag )
+function script 02524_Tree_BlackBox {
+ .@prayer$=getarg(0);
+ .@gid=getarg(1);
+ .@org=getcharid(3);
+ message .@prayer$, strcharinfo(0)+" is praying for you!";
+ if (!attachrid(.@gid)) return;
+ percentheal 100, 100;
+ @purifio=max(gettimetick(2), @purifio)+60;
+ attachrid(.@org);
+ getmapxy(.@m$, .@x, .@y, 0);
+ .@mob=monster(.@m$, .@x+any(-2,-3,2,3), .@y+any(-1,0,1), "Pinkie of Doom", PinkieSuseran, 1);
+ unitattack(.@mob, .@org);
+ return;
+}
+
+function script 02524_Revenge_BlackBox {
+ return;
+}
+
+function script 02524_Avenge_BlackBox {
+ return;
+}
+
diff --git a/localserver/npc/botcheck_blackbox.txt b/localserver/npc/botcheck_blackbox.txt
new file mode 100644
index 0000000..b11050e
--- /dev/null
+++ b/localserver/npc/botcheck_blackbox.txt
@@ -0,0 +1,35 @@
+// TMW2 Script
+// Author:
+// Jesusalva
+// Description:
+// Bot check area configuration, measures, utilitaries,
+// and various configuration files to prevent bots.
+// You must fill this file manually, or don't file it at all.
+
+botcheck,37,41,0 script Bot Master NPC_ELMO,{
+ if (!is_staff())
+ end;
+ mesc l("This file must be requested due not being public."), 1;
+ mesc l("Too many evil bots out there %%e They can't learn this!"), 1;
+ close;
+
+OnCall:
+ if (getmap() ~= "botcheck") {
+ if (@bck_map$ != "")
+ warp @bck_map$, @bck_x, @bck_y;
+ else
+ warp "Save", 0, 0;
+ dispbottom l("You passed the botcheck. Thanks for playing the game %%G");
+ end;
+ }
+ getmapxy(@bck_map$, @bck_x, @bck_y, 0);
+ warp "botcheck", 38, 40;
+ dispbottom l("BOTCHECK: You are on botcheck area. DO NOT attack or collect items until given the order.");
+ end;
+
+OnInit:
+ bindatcmd "botcheck", "Bot Master::OnCall", 60, 60, 1;
+ end;
+
+}
+
diff --git a/localserver/npc/easteregg_blackbox.txt b/localserver/npc/easteregg_blackbox.txt
new file mode 100644
index 0000000..0335eeb
--- /dev/null
+++ b/localserver/npc/easteregg_blackbox.txt
@@ -0,0 +1,16 @@
+// TMW2 Script
+//
+// Contains Easter Egg data and triggers (some excluded)
+
+// Author: Saulc
+005-2-1,41,33,0 script Note#saxsocave NPC_PAPER_NOTE,{
+ mesc l("I leave this basement that start to be too dangerous!");
+
+ RegEasterEgg(EE_SAXSO, 5);
+ close;
+
+OnInit:
+ .distance = 2;
+ end;
+}
+
diff --git a/localserver/npc/magic_blackbox.txt b/localserver/npc/magic_blackbox.txt
new file mode 100644
index 0000000..63d5001
--- /dev/null
+++ b/localserver/npc/magic_blackbox.txt
@@ -0,0 +1,13 @@
+// TMW2 Script
+// Author:
+// Jesusalva
+// Description:
+// Magic Blackbox
+// Contains the internals for Scripture skill.
+
+// Syntax:
+// SK_Scripture( {@chat$} )
+function script SK_Scripture {
+ return;
+}
+
diff --git a/localserver/reapply.patch3 b/localserver/reapply.patch3
new file mode 100644
index 0000000..7d7ddaf
--- /dev/null
+++ b/localserver/reapply.patch3
@@ -0,0 +1,5 @@
+############
+## First postponed update
+
+##
+#
diff --git a/localserver/reapply.patch4 b/localserver/reapply.patch4
new file mode 100644
index 0000000..bc0f612
--- /dev/null
+++ b/localserver/reapply.patch4
@@ -0,0 +1,47 @@
+############
+# HOWTO:
+# git log v2019.08.25.. --pretty=short
+
+# TODO: Instructions on how to update our hercules repo
+# git diff v2019.09.22 v2019.10.20 > ../tmw2/server-code/x.diff
+# git apply --ignore-whitespace --exclude=doc/ --exclude=tools/ --exclude=db/ --exclude=npc/ --exclude=conf/ --reject x.diff ; git checkout -- .gitlab-ci.yml
+
+############
+## v2019.10.20
+
+############
+## v2019.11.17+1
+
+############
+## v2019.12.15
+
+############
+## v2020.01.12
+
+############
+## v2020.02.09
+
+############
+## v2020.03.08+2
+
+############
+## v2020.04.05+1
+
+############
+## v2020.05.03
+
+############
+## v2020.05.31+1
+
+############
+## v2020.06.28
+
+## Changed logic of skill_get_index
+#1042e9919f18d4aac21583a675d28758ba233e03
+
+## Added ifdef on mmo.h to allow change it with CPPFLAGS
+#8a4667a265227587dcc288f75dfe948f5df46034
+
+############
+## v2020.07.26
+
diff --git a/localserver/rebuild.sh b/localserver/rebuild.sh
new file mode 100755
index 0000000..3ec97ed
--- /dev/null
+++ b/localserver/rebuild.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+export DIR=`pwd`
+
+# Skip patches applications
+
+./checktime.sh
+source ./clean.sh
+CC="gcc-9" ./build.sh old > /dev/null
diff --git a/localserver/updateconfigs.sh b/localserver/updateconfigs.sh
new file mode 100755
index 0000000..4caf378
--- /dev/null
+++ b/localserver/updateconfigs.sh
@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+
+export VER=$(cat versions/confver 2>/dev/null)
+export CONFDIR="../../serverdata/"
+
+if [[ -z "${VER}" ]]; then
+ export VER="0"
+ mkdir versions
+fi
+
+if [ "${VER}" -lt "1" ]; then
+ cp -f ${CONFDIR}/conf/channels.conf ${CONFDIR}/conf/channels.conf.bak
+ cp -f ${CONFDIR}/conf/channels.conf.base ${CONFDIR}/conf/channels.conf
+fi
+
+if [ "${VER}" -lt "2" ]; then
+ rm ${CONFDIR}/conf/import/*.txt
+ cp conf/* ${CONFDIR}/conf/import
+ echo "2" >versions/confver
+fi
+
+if [ "${VER}" -lt "3" ]; then
+ cp conf/constants.conf ${CONFDIR}/conf/import/
+ echo "3" >versions/confver
+fi
+
+if [ "${VER}" -lt "4" ]; then
+ cp -f npc/motd-* ${CONFDIR}/npc/commands/
+ echo "4" >versions/confver
+fi
diff --git a/localserver/updatedb.sh b/localserver/updatedb.sh
new file mode 100755
index 0000000..6e777ae
--- /dev/null
+++ b/localserver/updatedb.sh
@@ -0,0 +1,103 @@
+#!/usr/bin/env bash
+
+export VER=$(cat versions/sqlver 2>/dev/null)
+export SQLDIR="../../serverdata/sql-files/upgrades"
+export CMD="mysql -u evol -pevol evol"
+
+function run {
+ echo "Running $1"
+ $CMD <${SQLDIR}/$1
+}
+
+if [[ -z "${VER}" ]]; then
+ export VER="1"
+ mkdir versions
+fi
+
+if [ "${VER}" -lt "2" ]; then
+ run "2015-07-08--13-08.sql"
+ run "2015-08-27--20-42.sql"
+fi
+
+if [ "${VER}" -lt "3" ]; then
+ run "2015-12-16--12-57.sql"
+ run "2015-12-17--15-58.sql"
+fi
+
+if [ "${VER}" -lt "4" ]; then
+ run "2016-03-10--22-18.sql"
+fi
+
+if [ "${VER}" -lt "5" ]; then
+ run "2016-07-08--02-42.sql"
+ run "2016-07-08--02-51.sql"
+fi
+
+if [ "${VER}" -lt "6" ]; then
+ run "2016-10-03--20-27.sql"
+fi
+
+if [ "${VER}" -lt "7" ]; then
+ run "2016-10-26--10-29.sql"
+fi
+
+if [ "${VER}" -lt "8" ]; then
+ run "2017-03-02--11-40.sql"
+ run "2017-03-05--08-09.sql"
+fi
+
+if [ "${VER}" -lt "9" ]; then
+ run "2017-03-15--14-29.sql"
+fi
+
+if [ "${VER}" -lt "10" ]; then
+ run "2017-11-04--10-39.sql"
+fi
+
+if [ "${VER}" -lt "11" ]; then
+ run "2017-06-04--15-04.sql"
+ run "2017-06-04--15-05.sql"
+fi
+
+if [ "${VER}" -lt "12" ]; then
+ run "2018-02-26--15-57.sql"
+fi
+
+if [ "${VER}" -lt "13" ]; then
+ run "2018-03-10--04-06.sql"
+fi
+
+if [ "${VER}" -lt "14" ]; then
+ run "2018-06-05--12-02.sql"
+fi
+
+if [ "${VER}" -lt "15" ]; then
+ run "2018-07-24--03-23.sql"
+ run "2018-08-03--11-37.sql"
+fi
+
+if [ "${VER}" -lt "16" ]; then
+ run "2018-06-03--00-10.sql"
+ run "2018-06-03--17-16.sql"
+ run "2018-09-01--05-22.sql"
+fi
+
+if [ "${VER}" -lt "17" ]; then
+ run "2018-12-14--01-02.sql"
+ run "2018-12-29--07-51.sql"
+fi
+
+if [ "${VER}" -lt "18" ]; then
+ run "2019-04-08--21-52.sql"
+ run "2019-04-25--02-12.sql"
+fi
+
+if [ "${VER}" -lt "19" ]; then
+ run "2019-05-09--18-07.sql"
+ run "2020-08-01--00-00.sql"
+fi
+
+if [ "${VER}" -lt "19" ]; then
+ echo "19" >versions/sqlver
+fi
+
diff --git a/localserver/updater.sh b/localserver/updater.sh
new file mode 100755
index 0000000..461afa5
--- /dev/null
+++ b/localserver/updater.sh
@@ -0,0 +1,44 @@
+#!/usr/bin/env bash
+
+# Apply beta.patch if it exists to serverdata
+# It will only exist on BETA SERVERS, though
+if [ -e "./beta.patch" ]
+ then
+ echo "Apply beta.patch ........"
+ cd ../../serverdata
+ git apply ../tools/localserver/beta.patch
+ cd $DIR
+ mv beta.patch .~beta.patch
+ ls
+ echo "........ Done."
+fi
+
+# Apply beta.patch2 if it exists to server-code
+# It will only exist on BETA SERVERS, though
+if [ -e "./beta.patch2" ]
+ then
+ echo "Apply server updates ........"
+ sleep 1
+ cd ../../server-code
+ git checkout -- src
+ echo "Rolling server back to correct version"
+ #git diff master ce2dbb6acdc559ec256d1f9f9a779b8283064708 > x.diff
+ #ls
+ #head -n 25 x.diff
+ #tail -n 40 x.diff
+ #git apply --reject --whitespace=nowarn x.diff
+ git status
+ ls --recursive|grep ".rej"
+ cd ../tools/localserver
+ echo "Server code clean ........"
+ sleep 1
+ python applicator.py
+ echo "Apply beta.patch2 ........"
+ cd ../../server-code
+ git apply ../tools/localserver/beta.patch2
+ cd $DIR
+ #mv beta.patch2 .~beta.patch2
+ ls
+ echo "........ Done."
+fi
+
diff --git a/manaplus/connect_local_server.sh b/manaplus/connect_local_server.sh
new file mode 100755
index 0000000..33232cd
--- /dev/null
+++ b/manaplus/connect_local_server.sh
@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+
+sleep 3
+
+export MANAPLUS="../../manaplus/src/manaplus"
+if [ -f "$MANAPLUS" ]; then
+ echo "Starting local manaplus"
+else
+ echo "Starting system manaplus"
+ export MANAPLUS="manaplus"
+fi
+
+${MANAPLUS} -u -d ../../clientdata tmw2.manaplus
diff --git a/manaplus/evol.manaplus b/manaplus/evol.manaplus
new file mode 100644
index 0000000..8062529
--- /dev/null
+++ b/manaplus/evol.manaplus
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/manaplus/force_localmanaplus.sh b/manaplus/force_localmanaplus.sh
new file mode 100755
index 0000000..1cc34cb
--- /dev/null
+++ b/manaplus/force_localmanaplus.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+export MANAPLUS="../../manaplus/src/manaplus"
+echo "Starting local manaplus"
+${MANAPLUS} -u -d ../../clientdata tmw2.manaplus
diff --git a/manaplus/loginwallpaper_800x600.png b/manaplus/loginwallpaper_800x600.png
new file mode 100644
index 0000000..4a97574
Binary files /dev/null and b/manaplus/loginwallpaper_800x600.png differ
diff --git a/manaplus/tmw2.manaplus b/manaplus/tmw2.manaplus
new file mode 100644
index 0000000..0a2cbb8
--- /dev/null
+++ b/manaplus/tmw2.manaplus
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testxml/testxml.py b/testxml/testxml.py
new file mode 100755
index 0000000..0b693cc
--- /dev/null
+++ b/testxml/testxml.py
@@ -0,0 +1,2439 @@
+#! /usr/bin/env python
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2010-2011 Evol Online
+# Author: Andrei Karas (4144)
+
+import array
+import os
+import re
+import datetime
+import xml
+import csv
+import ogg.vorbis
+import StringIO
+import sys
+from xml.dom import minidom
+from xml.etree import ElementTree
+from PIL import Image
+import zlib
+
+filt = re.compile(".+[.](xml|tmx|tsx)", re.IGNORECASE)
+filtmaps = re.compile(".+[.]tmx", re.IGNORECASE)
+filtimages = re.compile(".+[.]png", re.IGNORECASE)
+filtxmls = re.compile(".+[.]xml", re.IGNORECASE)
+filtogg = re.compile(".+[.]ogg", re.IGNORECASE)
+dyesplit1 = re.compile(";")
+dyesplit2 = re.compile(",")
+parentDir = "../../gittorious/clientdata-beta"
+iconsDir = "graphics/items/"
+spritesDir = "graphics/sprites/"
+particlesDir = "graphics/particles/"
+sfxDir = "sfx/"
+musicDir = "music/"
+mapsDir = "maps/"
+spriteErrorFile = "error.xml"
+levelUpEffectFile = "levelup.particle.xml"
+portalEffectFile = "warparea.particle.xml"
+minimapsDir = "graphics/minimaps/"
+wallpapersDir = "graphics/images/"
+wallpaperFile = "login_wallpaper.png"
+
+errors = 0
+warnings = 0
+errDict = set()
+safeDye = False
+borderSize = 14 # Required 18 # Original 14
+tiledVersion = 13 # Minimum Tiled version, advised "14" for Tiled 1.4
+colorsList = set()
+showAll = False
+silent = False
+stfu = False
+herc = False
+
+testBadCollisions = False
+# number of tiles difference. after this amount tiles can be counted as incorrect
+tileNumDiff = 3
+# max number of incorrect tiles. If more then tile not counted as error
+maxNumErrTiles = 5
+
+class Tileset:
+ None
+
+class Layer:
+ None
+
+def printErr(err):
+ errDict.add(err)
+ print err.encode("utf-8")
+
+def showFileErrorById(id, rootDir, fileDir):
+ rootDir = rootDir.encode("utf-8")
+ fileDir = fileDir.encode("utf-8")
+ print "error: id=" + id + ", file not found: " + fileDir + " (" + rootDir + fileDir + ")"
+
+def showFileWarningById(id, rootDir, fileDir):
+ rootDir = rootDir.encode("utf-8")
+ fileDir = fileDir.encode("utf-8")
+ print "warn: id=" + id + ", file not found: " + fileDir + " (" + rootDir + fileDir + ")"
+
+def showError(id, text):
+ text = text.encode("utf-8")
+ print "error: id=" + id + " " + text
+
+def showWarning(id, text):
+ text = text.encode("utf-8")
+ print "warn: id=" + id + " " + text
+
+def showMsg(id, text, src, iserr):
+ global errors, warnings
+ if text != "":
+ text = text + ", " + src
+ if iserr == True:
+ if text not in errDict:
+ showError(id, text)
+ errDict.add(text)
+ errors = errors + 1
+ else:
+ if text not in errDict:
+ showWarning(id, text)
+ errDict.add(text)
+ warnings = warnings + 1
+
+def showMsgSprite(file, text, iserr):
+ global errors, warnings
+ if iserr == True:
+ err = "error: sprite=" + file + " " + text
+ if err not in errDict:
+ printErr(err)
+ errors = errors + 1
+ else:
+ err = "warn: sprite=" + file + " " + text
+ if err not in errDict:
+ printErr(err)
+ warnings = warnings + 1
+
+def showMsgFile(file, text, iserr):
+ global errors, warnings
+ if iserr == True:
+ err = "error: file=" + file + " " + text
+ if err not in errDict:
+ printErr(err)
+ errors = errors + 1
+ else:
+ err = "warn: file=" + file + " " + text
+ if err not in errDict:
+ printErr(err)
+ warnings = warnings + 1
+
+def showFileMsgById(id, rootDir, fileDir, iserr):
+ global errors, warnings
+ if iserr == True:
+ showFileErrorById(id, rootDir, fileDir)
+ errors = errors + 1
+ else:
+ showFileWarningById(id, rootDir, fileDir)
+ warnings = warnings + 1
+
+def printSeparator():
+ print "--------------------------------------------------------------------------------"
+
+def showHeader():
+ print("Client Data Validator.")
+ print("Run at: " + datetime.datetime.now().isoformat())
+ print("https://git.themanaworld.org/ml/tools/blob/master/testxml/testxml.py")
+ printSeparator()
+
+def showFooter():
+ printSeparator()
+ print "Total:"
+ print " Warnings: " + str(warnings)
+ print " Errors: " + str(errors)
+
+def enumDirs(parentDir):
+ global warnings, errors
+ try:
+ files = os.listdir(parentDir)
+ except OSError:
+ print "Directory error: " + parentDir
+ if silent == False:
+ warnings = warnings + 1
+ return
+ for file1 in files:
+ if file1[0] == ".":
+ continue
+ file2 = os.path.abspath(parentDir + os.path.sep + file1)
+ if not os.path.isfile(file2):
+ enumDirs(file2)
+ else:
+ if filt.search(file1):
+ try:
+ if silent == True and not stfu:
+ print "Checking " + file2
+ minidom.parse(file2)
+ except xml.parsers.expat.ExpatError as err:
+ print "error: " + file2 + ", line=" + str(err.lineno) + ", char=" + str(err.offset)
+ errors = errors + 1
+ if file1 != "testxml.py":
+ checkFilePermission(file2)
+
+def checkFilePermission(fullName):
+ global warnings
+ if os.access(fullName, os.X_OK):
+ print "warn: execute flag on file: " + fullName
+ warnings = warnings + 1
+
+
+def loadPaths():
+ global warnings, iconsDir, spritesDir, sfxDir, particlesDir, mapsDir, spriteErrorFile, \
+ levelUpEffectFile, portalEffectFile, minimapsDir, wallpapersDir, walpaperFile, \
+ musicDir, wallpaperFile
+ try:
+ dom = minidom.parse(parentDir + "/paths.xml")
+ for node in dom.getElementsByTagName("option"):
+ if node.attributes["name"].value == "itemIcons":
+ iconsDir = node.attributes["value"].value
+ if iconsDir != "graphics/items/":
+ print "warn: itemIcons path has not default value."\
+ " Will be incampatible with old clients."
+ warnings = warnings + 1
+ elif node.attributes["name"].value == "sprites":
+ spritesDir = node.attributes["value"].value
+ if spritesDir != "graphics/sprites/":
+ print "warn: sprites path has not default value."\
+ " Will be incampatible with old clients."
+ warnings = warnings + 1
+ elif node.attributes["name"].value == "sfx":
+ sfxDir = node.attributes["value"].value
+
+ elif node.attributes["name"].value == "particles":
+ particlesDir = node.attributes["value"].value
+ if particlesDir != "graphics/particles/":
+ print "warn: particles path has not default value."\
+ " Will be incampatible with old clients."
+ warnings = warnings + 1
+ elif node.attributes["name"].value == "maps":
+ mapsDir = node.attributes["value"].value
+ if mapsDir != "maps/":
+ print "warn: maps path has not default value."\
+ " Will be incampatible with old clients."
+ warnings = warnings + 1
+ elif node.attributes["name"].value == "spriteErrorFile":
+ spriteErrorFile = node.attributes["value"].value
+ elif node.attributes["name"].value == "levelUpEffectFile":
+ levelUpEffectFile = node.attributes["value"].value
+ elif node.attributes["name"].value == "portalEffectFile":
+ portalEffectFile = node.attributes["value"].value
+ elif node.attributes["name"].value == "minimaps":
+ minimapsDir = node.attributes["value"].value
+ elif node.attributes["name"].value == "wallpapers":
+ wallpapersDir = node.attributes["value"].value
+ elif node.attributes["name"].value == "wallpaperFile":
+ wallpaperFile = node.attributes["value"].value
+ elif node.attributes["name"].value == "music":
+ musicDir = node.attributes["value"].value
+
+ except:
+ print "warn: paths.xml not found"
+ warnings = warnings + 1
+
+def splitImage(image):
+ try:
+ idx = image.find("|")
+ if idx > 0:
+ imagecolor = image[idx + 1:]
+ image = image[0:idx]
+ else:
+ imagecolor = ""
+ except:
+ image = ""
+ imagecolor = ""
+ return [image, imagecolor]
+
+def testDye(id, color, text, src, iserr):
+ if len(color) < 4:
+ showMsg(id, "dye to small size: " + text, src, iserr)
+ return
+ colors = dyesplit1.split(color)
+ for col in colors:
+ if len(col) < 4:
+ showMsg(id, "dye to small size: " + text, src, iserr)
+ continue
+
+ c = col[0];
+ if col[1] != ":":
+ showMsg(id, "incorrect dye string: " + text, src, iserr)
+ continue
+
+ if c != "R" and c != "G" and c != "B" and c != "Y" and c != "M" \
+ and c != "C" and c != "W" and c != "S":
+ showMsg(id, "incorrect dye color: " + c + " in " + text, src, iserr)
+ continue
+ if testDyeInternal(id, col[2:], text, src, iserr) == False:
+ continue
+
+
+def testDyeInternal(id, col, text, src, iserr):
+ oldPalette = col[0] == "#"
+ if oldPalette == False and col[0] != "@":
+ showMsg(id, "incorrect dye colors: " + text, src, iserr)
+ return False
+
+ if oldPalette:
+ paletes = dyesplit2.split(col[1:])
+ for palete in paletes:
+ if len(palete) != 6:
+ showMsg(id, "incorrect dye palete: " + text, src, iserr)
+ return False
+
+ for char in palete.lower():
+ if (char < '0' or char > '9') and (char < 'a' or char > 'f'):
+ showMsg(id, "incorrect dye palete: " + text, src, iserr)
+ return False
+ return True
+
+
+def testDyeColors(id, color, text, src, iserr):
+ if len(color) < 4:
+ showMsg(id, "dye to small size: " + text, src, iserr)
+ return -1
+ colors = dyesplit1.split(color)
+ for col in colors:
+ if len(col) < 4:
+ showMsg(id, "dye to small size: " + text, src, iserr)
+ continue
+ if testDyeInternal(id, col, text, src, iserr) == False:
+ continue
+ return len(colors)
+
+def testDyeChannel(file, color, text, iserr):
+ if len(color) < 1:
+ showMsgSprite(file, "dye channel size to small:" + text, iserr)
+ return -1
+ colors = dyesplit1.split(color)
+ for c in colors:
+ if len(c) != 1:
+ showMsgSprite(file, "dye channel incorrect size: " + text, iserr)
+ continue
+ if c != "R" and c != "G" and c != "B" and c != "Y" and c != "M" \
+ and c != "C" and c != "W" and c != "S":
+ showMsgSprite(file, "dye make incorrect: " + text, iserr)
+ continue
+ return len(colors)
+
+
+def testSprites(id, node, checkGender, isNormalDye, isMust, checkAction, iserr):
+ try:
+ tmp = node.getElementsByTagName("nosprite")
+ if tmp is not None and len(tmp) > 1:
+ showMsg(id, "more than one nosprite tag found", "", iserr)
+ nosprite = True
+ except:
+ nosprite = False
+
+ if isMust == False:
+ nosprite = True
+
+ try:
+ sprites = node.getElementsByTagName("sprite")
+ except:
+ sprites = None
+ if nosprite == False:
+ showMsg(id, "no sprite tag found", "", iserr)
+
+ if sprites is not None:
+ if len(sprites) == 0 or len(sprites[0].childNodes) == 0:
+ if nosprite == False:
+ showMsg(id, "no sprite tags found", "", iserr)
+ elif len(sprites) > 3 and checkGender:
+ showMsg(id, "incorrect number of sprite tags", "", iserr)
+ elif len(sprites) == 1:
+ file = sprites[0].childNodes[0].data
+ if checkGender:
+ try:
+ gender = sprites[0].attributes["gender"].value
+ except:
+ gender = ""
+
+ if gender != "" and gender != "unisex":
+ showMsg(id, "gender tag in alone sprite", "", iserr)
+
+ try:
+ variant = int(sprites[0].attributes["variant"].value)
+ except:
+ variant = 0
+
+ testSprite(id, file, variant, isNormalDye, checkAction, iserr)
+ else:
+ male = False
+ female = False
+ unisex = False
+ for sprite in sprites:
+ file = sprite.childNodes[0].data
+ if checkGender:
+ try:
+ gender = sprite.attributes["gender"].value
+ except:
+ gender = ""
+ if gender == "male":
+ if male == True:
+ showMsg(id, "double male sprite tag", "", iserr)
+ male = True
+ elif gender == "female":
+ if female == True:
+ showMsg(id, "double female sprite tag", "", iserr)
+ female = True
+ elif gender == "unisex":
+ unisex = True
+ try:
+ variant = int(sprite.attributes["variant"].value)
+ except:
+ variant = 0
+ testSprite(id, file, variant, isNormalDye, checkAction, iserr)
+ if checkGender:
+ if male == False and unisex == False:
+ showMsg(id, "no male sprite tag", "",iserr)
+ if female == False and unisex == False:
+ showMsg(id, "no female sprite tag", "", iserr)
+ if unisex == True and female == True and male == True:
+ showMsg(id, "gender sprite tag with unisex tag", "", iserr)
+ if unisex == False and male == False and female == False:
+ showMsg(id, "no any gender tags", "", iserr)
+
+def testSprite(id, file, variant, isNormalDye, checkAction, iserr):
+ global safeDye
+ tmp = splitImage(file)
+ color = tmp[1]
+ file2 = tmp[0]
+ if color != "":
+ dnum = testDyeColors(id, color, file, "", iserr)
+ else:
+ dnum = 0
+
+ fullPath = os.path.abspath(parentDir + "/" + spritesDir + file2)
+ if not os.path.isfile(fullPath) or os.path.exists(fullPath) == False:
+ showFileMsgById(id, spritesDir, file2, iserr)
+ else:
+ if not isNormalDye and color is not None and len(color) > 0:
+ showMsg(id, "sprite tag have dye string but it should not, because used colors dye", color, iserr)
+
+ oldSafe = safeDye
+ safeDye = True
+ testSpriteFile(id, fullPath, file, spritesDir + file2, dnum, variant, checkAction, iserr)
+ safeDye = oldSafe
+
+def powerOfTwo(num):
+ val = 1
+ while val < num:
+ val = val * 2
+ return val
+
+def testSpriteFile(id, fullPath, file, fileLoc, dnum, variant, checkAction, iserr):
+ global safeDye
+
+ try:
+ dom = minidom.parse(fullPath)
+ except:
+ return
+
+ if len(dom.childNodes) < 1:
+ return
+
+ try:
+ variants = dom.documentElement.attributes["variants"].value
+ except:
+ variants = 0
+
+# try:
+# variant_offset = dom.documentElement.attributes["variant_offset"].value
+# except:
+# variant_offset = 0
+
+# root = dom.childNodes[0];
+ imagesets = dom.getElementsByTagName("imageset")
+ if imagesets is None or len(imagesets) < 1:
+ showMsgSprite(fileLoc, "incorrect number of imageset tags", iserr)
+ return
+ isets = set()
+ imagesetnums = dict()
+ num = 0
+ for imageset in imagesets:
+ try:
+ name = imageset.attributes["name"].value
+ except:
+ showMsgSprite(fileLoc, "imageset don't have name attribute", iserr)
+ name = None
+
+ if name is not None:
+ if name in isets:
+ showMsgSprite(fileLoc, "imageset with name '" + name + "' already exists", iserr)
+ isets.add(name)
+
+ image = ""
+ try:
+ image = imageset.attributes["src"].value
+ image0 = image
+ img = splitImage(image)
+ image = img[0]
+ imagecolor = img[1]
+ except:
+ showMsgSprite(fileLoc, "image attribute not exist: " + image, iserr)
+ continue
+
+ try:
+ width = imageset.attributes["width"].value
+ except:
+ showMsgSprite(fileLoc, "no width attribute", iserr)
+ continue
+
+ try:
+ height = imageset.attributes["height"].value
+ except:
+ showMsgSprite(fileLoc, "no height attribute", iserr)
+
+ if imagecolor != "":
+ num = testDyeChannel(fileLoc, imagecolor, image0, iserr)
+ if safeDye == False and dnum != num:
+ if dnum > num:
+ e = iserr
+ else:
+ e = False
+ showMsgSprite(fileLoc, "dye colors size not same in sprite (" + str(num) \
+ + ") and in caller (" + str(dnum) + ", id=" + str(id) + ")", e)
+ elif safeDye == True and dnum > 0:
+ showMsgSprite(fileLoc, "dye set in sprite but not in caller (id=" + str(id) + ")", False)
+
+
+ fullPath = os.path.abspath(parentDir + "/" + image)
+ if not os.path.isfile(fullPath) or os.path.exists(fullPath) == False:
+ showMsgSprite(fileLoc, "image file not exist: " + image, iserr)
+ continue
+ sizes = testImageFile(image, fullPath, 0, " " + fileLoc, iserr)
+ s1 = int(sizes[0] / int(width)) * int(width)
+
+ sizesOGL = [0,1]
+ sizesOGL[0] = powerOfTwo(sizes[0])
+ sizesOGL[1] = powerOfTwo(sizes[1])
+
+ if s1 == 0:
+ tmp = int(width)
+ else:
+ tmp = s1
+ if sizes[0] != s1 and tmp != sizesOGL[0] and sizes[0] != sizesOGL[0]:
+ if silent != True:
+ showMsgSprite(fileLoc, "image width " + str(sizes[0]) + \
+ " (need " + str(tmp) + ") is not multiply to frame size " + width + ", image:" + image, False)
+
+ if sizes[0] != sizesOGL[0]:
+ if sizesOGL[0] > sizes[0]:
+ txt = str(sizesOGL[0] / 2) + " or "
+ else:
+ txt = ""
+
+ if showAll is True:
+ showMsgSprite(fileLoc, "image width should be power of two. If not image will be resized on the fly."\
+ "\nCurrent image width " + str(sizes[0]) + \
+ ". used in sprite width " + str(tmp) +
+ "\nallowed width " + txt + str(sizesOGL[0]) + " (" + image + ")", False)
+
+ s2 = int(sizes[1] / int(height)) * int(height)
+
+ if s2 == 0:
+ tmp = int(height)
+ else:
+ tmp = s2;
+
+ if sizes[1] != s2 and tmp != sizesOGL[1] and sizes[1] != sizesOGL[1]:
+ if silent != True:
+ showMsgSprite(fileLoc, "image height " + str(sizes[1]) + \
+ " (need " + str(tmp) + ") is not multiply to frame size " + height + ", image:" + image, False)
+
+ if sizes[1] != sizesOGL[1]:
+ if sizesOGL[1] > sizes[1]:
+ txt = str(sizesOGL[1] / 2) + " or "
+ else:
+ txt = ""
+
+ if showAll is True:
+ showMsgSprite(fileLoc, "image height should be power of two. If not image will be resized on the fly."\
+ "\nCurrent image height " + str(sizes[1]) + \
+ ". used in sprite height " + str(tmp) +
+ "\nallowed height " + txt + str(sizesOGL[1]) + " (" + image + ")", False)
+
+
+ num = (s1 / int(width)) * (s2 / int(height))
+ if variants == 0 and variant > 0:
+ showMsgSprite(fileLoc, "missing variants attribute in sprite", iserr)
+ if variants > 0 and variant >= variants:
+ showMsgSprite(fileLoc, "variant number more then in variants attribute", iserr)
+
+ if variant > 0 and variant >= num:
+ showMsgSprite(fileLoc, "to big variant number " + str(variant) \
+ + ". Frames number " + str(num) + ", id=" + str(id), iserr)
+ if num < 1:
+ showMsgSprite(fileLoc, "image have zero frames: " + image, iserr)
+ if name is not None and num > 0:
+ imagesetnums[name] = num
+
+ try:
+ includes = dom.getElementsByTagName("include")
+ for include in includes:
+ try:
+ incfile = include.attributes["file"].value
+ file2 = os.path.abspath(parentDir + os.path.sep + spritesDir + incfile)
+ if not os.path.isfile(file2):
+ showMsgSprite(fileLoc, "include file not exists " + incfile, True)
+ except:
+ showMsgSprite(fileLoc, "bad include", iserr)
+
+
+ except:
+ includes = None
+
+ #todo need parse included files
+
+ try:
+ actions = dom.getElementsByTagName("action")
+ except:
+ actions = None
+
+ if (actions == None or len(actions) == 0) and (includes == None or len(includes) == 0):
+ showMsgSprite(fileLoc, "no actions in sprite file", iserr)
+ else:
+ actset = set()
+ frameSet = set()
+ hpSet = set()
+ for action in actions:
+ try:
+ name = action.attributes["name"].value
+ except:
+ showMsgSprite(fileLoc, "no action name", iserr)
+ continue
+ try:
+ hp = action.attributes["hp"].value
+ except:
+ hp = "100"
+ try:
+ setname = action.attributes["imageset"].value
+ except:
+ setname = ""
+ if setname in imagesetnums:
+ num = imagesetnums[setname]
+ else:
+ num = 0
+ showMsgSprite(fileLoc, "using incorrect imageset name in action: " + name, iserr)
+ frameSet = frameSet | testSpriteAction(fileLoc, name, action, num, iserr)
+
+ if name + "|" + hp in actset:
+ showMsgSprite(fileLoc, "duplicate action: " + name, iserr)
+ continue
+ actset.add(name + "|" + hp)
+ hpSet.add(hp)
+
+ if len(frameSet) > 0:
+ errIds = ""
+ i = 0
+ while i < max(frameSet):
+ if i not in frameSet:
+ errIds = errIds + str(i) + ","
+ i = i + 1
+ if len(errIds) > 0:
+ if silent != True:
+ showMsgSprite(fileLoc, "unused frames: " + errIds[0:len(errIds)-1], False)
+
+ if checkAction != "":
+ for hp in hpSet:
+ if checkAction + "|" + hp not in actset:
+ showMsgSprite(fileLoc, "no attack action '" + checkAction + "' in sprite", iserr)
+
+
+def testSpriteAction(file, name, action, numframes, iserr):
+ framesid = set()
+
+ try:
+ animations = action.getElementsByTagName("animation")
+ except:
+ animations = None
+
+ if animations == None or len(animations) == 0:
+ if name != "default":
+ showMsgSprite(file, "no animation tags in action: " + name, False)
+ else:
+ return framesid
+
+ aniset = set()
+ delayTags = ("frame", "sequence", "pause")
+
+ for animation in animations:
+ lastAttack = None
+ try:
+ direction = animation.attributes["direction"].value
+ except:
+ direction = "default"
+
+ if direction is aniset:
+ showMsgSprite(file, "duplicate direction in action: " + name, iserr)
+ continue
+ aniset.add(direction)
+
+ lastIndex1 = -1
+ lastIndex2 = -1
+ lastOffsetX = 0
+ lastOffsetY = 0
+ cnt = 0
+ labels = set()
+
+ for node2 in animation.childNodes:
+ if name == "attack" and node2.nodeName != "#text":
+ lastAttack = node2.nodeName
+ if node2.nodeName in delayTags:
+ try:
+ delay = int(node2.attributes["delay"].value)
+ except:
+ delay = 0
+
+ if delay % 10 != 0 and showAll is True:
+ showMsgSprite(file, "delay " + str(delay) + " must be multiple of 10 in action: " + name + \
+ ", direction: " + direction, False)
+
+
+ if node2.nodeName == "frame" or node2.nodeName == "sequence":
+ try:
+ offsetX = int(node2.attributes["offsetX"].value)
+ except:
+ offsetX = 0
+ try:
+ offsetY = int(node2.attributes["offsetY"].value)
+ except:
+ offsetY = 0
+
+ if node2.nodeName == "frame":
+ frame = node2
+ try:
+ idx = int(frame.attributes["index"].value)
+ except:
+ showMsgSprite(file, "no frame index in action: " + name, iserr)
+
+ if idx >= numframes or idx < 0:
+ showMsgSprite(file, "incorrect frame index " + str(idx) + \
+ " action: " + name + ", direction: "\
+ + direction, iserr)
+ else:
+ framesid.add(idx)
+ if lastIndex1 == idx and lastIndex2 == -1 and offsetX == lastOffsetX \
+ and offsetY == lastOffsetY:
+ showMsgSprite(file, "duplicate frame animation for frame index=" \
+ + str(idx) + " action: " + name + \
+ ", direction: " + direction + "\n" + node2.toxml(), False)
+ #print node2.toxml()
+ else:
+ lastIndex1 = idx
+ lastIndex2 = -1
+ lastOffsetX = offsetX
+ lastOffsetY = offsetY
+
+ framesid.add(idx)
+ cnt = cnt + 1
+ elif node2.nodeName == "sequence":
+ sequence = node2
+ try:
+ sframes = dyesplit2.split(sequence.attributes["value"].value)
+ except:
+ sframes = None
+ if sframes is not None:
+ for frm in sframes:
+ if frm != "p":
+ k = frm.find("-")
+ if k == 0 or k == len(frm) - 1:
+ showMsgSprite(file, "incorrect sequence value " + \
+ name + ", direction: " + direction, iserr)
+ elif k == -1:
+ #same as frame
+ idx = int(frm)
+ if idx >= numframes or idx < 0:
+ showMsgSprite(file, "incorrect frame index " + str(idx) + \
+ " action: " + name + ", direction: "\
+ + direction, iserr)
+ else:
+ framesid.add(idx)
+ else:
+ #same as simple sequence
+ i1 = int(frm[:k])
+ i2 = int(frm[k + 1:])
+ if i1 >= numframes or i1 < 0:
+ showMsgSprite(file, "incorrect start sequence index " + str(i1) + \
+ " action: " + name + ", direction: " + direction, iserr)
+ if i2 >= numframes or i2 < 0:
+ showMsgSprite(file, "incorrect end sequence index " + str(i2) + \
+ " action: " + name + ", direction: " + direction, iserr)
+ if i1 == i2:
+ showMsgSprite(file, "start and end sequence index is same. " \
+ + "May be better use frame? action: " + \
+ name + ", direction: " + direction, False)
+
+ for i in range(i1,i2 + 1):
+ framesid.add(i)
+ cnt = cnt + 1
+ continue
+
+ try:
+ i1 = int(sequence.attributes["start"].value)
+ i2 = int(sequence.attributes["end"].value)
+ except:
+ showMsgSprite(file, "no sequence start or end index action: " + \
+ name + ", direction: " + direction, iserr)
+# try:
+# repeat = int(sequence.attributes["repeat"].value)
+# except:
+# repeat = 1
+
+ if i1 >= numframes or i1 < 0:
+ showMsgSprite(file, "incorrect start sequence index " + str(i1) + \
+ " action: " + name + ", direction: " + direction, iserr)
+ if i2 >= numframes or i2 < 0:
+ showMsgSprite(file, "incorrect end sequence index " + str(i2) + \
+ " action: " + name + ", direction: " + direction, iserr)
+ if i1 == i2:
+ showMsgSprite(file, "start and end sequence index is same. " \
+ + "May be better use frame? action: " + \
+ name + ", direction: " + direction, False)
+
+ if lastIndex1 == i1 and lastIndex2 == i2 and offsetX == lastOffsetX \
+ and offsetY == lastOffsetY:
+ showMsgSprite(file, "duplicate sequence animation. May be need use repeat attribue? for start=" \
+ + str(i1) + ", end=" + str(i2) + " action: " + \
+ name + ", direction: " + direction + "\n" + node2.toxml(), False)
+ else:
+ lastIndex1 = i1
+ lastIndex2 = i2
+ lastOffsetX = offsetX
+ lastOffsetY = offsetY
+
+ cnt = cnt + 1
+ for i in range(i1,i2 + 1):
+ framesid.add(i)
+ elif node2.nodeName == "end" or node2.nodeName == "jump" or node2.nodeName == "label" or node2.nodeName == "goto":
+ lastIndex1 = -1
+ lastIndex2 = -1
+ lastOffsetX = 0
+ lastOffsetY = 0
+ cnt = cnt + 1
+ elif node2.nodeName == "pause":
+ try:
+ delay = int(node2.attributes["delay"].value)
+ except:
+ delay = 0
+ if delay <= 0:
+ showMsgSprite(file, "incorrect delay in pause tag " + name, iserr)
+
+ elif node2.nodeName == "#text" or node2.nodeName == "#comment":
+ None
+ else:
+ showMsgSprite(file, "unknown animation tag: " + node2.nodeName + ", " + name, False)
+
+ if node2.nodeName == "jump":
+ try:
+ jaction = node2.attributes["action"].value
+ except:
+ jaction = ""
+ if jaction == "" or jaction is None:
+ showMsgSprite(file, "no action attribute in jump tag " + name, iserr)
+ elif node2.nodeName == "label":
+ try:
+ label = node2.attributes["name"].value
+ except:
+ label = ""
+ if label == "" or label is None:
+ showMsgSprite(file, "no name attribute in label tag " + name, iserr)
+ else:
+ if label in labels:
+ showMsgSprite(file, "duplicate label " + label + " " + name + "\n" \
+ + node2.toxml(), iserr)
+ else:
+ labels.add(label)
+ elif node2.nodeName == "goto":
+ try:
+ label = node2.attributes["label"].value
+ except:
+ label = ""
+ if label == "" or label is None:
+ showMsgSprite(file, "no label attribute in goto tag " + name, iserr)
+ if cnt == 0:
+ showMsgSprite(file, "no frames or sequences in action: " + name, iserr)
+
+ if name == "attack":
+ if lastAttack is not None and lastAttack != "end":
+ showMsgSprite(file, "last attack tag should be or attack animation "\
+ "can be infinite. direction: " + direction, False)
+
+
+ if "default" not in aniset:
+ if "down" not in aniset:
+ showMsgSprite(file, "no down direction in animation: " + name, iserr)
+ if "up" not in aniset:
+ showMsgSprite(file, "no up direction in animation: " + name, iserr)
+ if "left" not in aniset:
+ showMsgSprite(file, "no left direction in animation: " + name, iserr)
+ if "right" not in aniset:
+ showMsgSprite(file, "no right direction in animation: " + name, iserr)
+
+ if name == "dead" and len(animations) > 0:
+ lastani = animations[len(animations) - 1]
+ lastNode = None
+ nc = 0
+ for node in lastani.childNodes:
+ if node.nodeName == "frame":
+ lastNode = node
+ nc = nc + 1
+ if node.nodeName == "sequence":
+ lastNode = node
+ nc = nc + 2
+ if nc > 1:
+ try:
+ cont = int(lastNode.attributes["continue"].value)
+ except:
+ cont = 0;
+ if cont == 0:
+ try:
+ delay = int(lastNode.attributes["delay"].value)
+ except:
+ delay = 0
+ if delay > 0 and delay < 5000:
+ showMsgSprite(file, "last frame\sequence in dead animation have to low limit. Need zero or >5000: " + name, False)
+
+ return framesid
+
+
+def testImageFile(file, fullPath, sz, src, iserr):
+ try:
+ img = Image.open(fullPath, "r")
+ img.load()
+ except:
+ showMsgFile(file, "incorrect image format" + src, iserr)
+ return
+
+ if img.format != "PNG":
+ showMsgFile(file, "image format is not png" + src, False)
+
+ sizes = img.size
+ if sz != 0:
+ if sizes[0] > sz or sizes[1] > sz:
+ showMsgFile(file, "image size incorrect (" + str(sizes[0]) \
+ + "x" + str(sizes[1]) + ") should be (" + str(sz) + "x" \
+ + str(sz) + ")", iserr)
+ elif sizes[0] < sz or sizes[1] < sz:
+ showMsgFile(file, "possible image size incorrect (" + str(sizes[0]) \
+ + "x" + str(sizes[1]) + ") should be (" + str(sz) + "x" \
+ + str(sz) + ")", False)
+
+ return sizes
+
+def testSound(file, sfxDir, msg):
+ fullPath = parentDir + "/" + sfxDir + file
+ if not os.path.isfile(fullPath) or os.path.exists(fullPath) == False:
+ print "error:" + fullPath
+ if msg != "":
+ showMsgFile(file, "sound file not found: " + msg , True)
+ else:
+ showMsgFile(file, "sound file not found", True)
+ return
+ try:
+ ogg.vorbis.VorbisFile(fullPath)
+ except ogg.vorbis.VorbisError as e:
+ showMsgFile(file, "sound file incorrect error: " + str(e), True)
+
+
+def testParticle(id, file, src):
+ fullPath = parentDir + "/" + file
+ if not os.path.isfile(fullPath) or os.path.exists(fullPath) == False:
+ showMsgFile(file, "particle file not found", True)
+ return
+ try:
+ dom = minidom.parse(fullPath)
+ except:
+ showMsgFile(file, "incorrect particle xml file", True)
+ return
+
+ nodes = dom.getElementsByTagName("particle")
+ if len(nodes) < 1:
+ showMsgFile(file, "missing particle tags", False)
+ else:
+ for node in nodes:
+ testEmitters(id, file, node, file)
+
+
+def testEmitters(id, file, parentNode, src):
+ for node in parentNode.getElementsByTagName("property"):
+ try:
+ name = node.attributes["name"].value
+ except:
+ showMsgFile(file, "missing attribute name in emitter" \
+ " in particle file", True)
+ continue
+ try:
+ value = node.attributes["value"].value
+ except:
+ value = None
+
+ if name == "image":
+ if value == None:
+ showMsgFile(file, "missing attribute value in emitter" \
+ " image attribute", True)
+ img = splitImage(value)
+ image = img[0]
+ imagecolor = img[1]
+ if imagecolor != None and len(imagecolor) > 0:
+ testDye(id, imagecolor, "image=" + image, src, True)
+ fullName = parentDir + "/" + image
+ if not os.path.isfile(fullName) or os.path.exists(fullName) == False:
+ showMsgFile(file, "image file not exist: " + image, True)
+ else:
+ testImageFile(image, fullName, 0, " " + file,True)
+ for node in parentNode.getElementsByTagName("emitter"):
+ testEmitters(id, file, node, src)
+
+
+
+def testItems(fileName, imgDir):
+ global warnings, errors, safeDye
+ if not stfu:
+ print "Checking " + fileName
+ try:
+ dom = minidom.parse(parentDir + "/" + fileName)
+ except Exception as err:
+ print "error: " + fileName + ": corrupted"
+ print err
+ errors = errors + 1
+ return
+ idset = set()
+ oldId = None
+ for node in dom.documentElement.childNodes:
+ if node.nodeName == "include":
+ try:
+ name = node.attributes["name"].value
+ if name == "":
+ errors = errors + 1
+ print "error: " + fileName + ": Empty include name";
+ testItems(name, imgDir)
+ except:
+ errors = errors + 1
+ print "error: " + fileName + ": Broken include tag";
+ continue
+ if node.nodeName != "item":
+ continue
+
+ if node.parentNode != dom.documentElement:
+ continue
+
+ try:
+ id = node.attributes["id"].value
+ except:
+ if oldId is None:
+ print "error: " + fileName + ": item without id"
+ else:
+ print "error: " + fileName + ": item without id. Last id was: " + oldId
+ errors = errors + 1
+ continue
+ oldId = id
+ if id in idset:
+ print "error: " + fileName + ": duplicated id=" + id
+ errors = errors + 1
+ else:
+ idset.add(id)
+
+ idI = int(id)
+
+ try:
+ colors = node.attributes["colors"].value
+ except:
+ colors = None
+
+ try:
+ type = node.attributes["type"].value
+ except:
+ type = ""
+ print "warn: " + fileName + ": no type attribute for id=" + id
+ warnings = warnings + 1
+ try:
+ image = node.attributes["image"].value
+ image0 = image
+ img = splitImage(image)
+ image = img[0]
+ imagecolor = img[1]
+ except:
+ image = ""
+ image0 = ""
+ imagecolor = ""
+
+ try:
+ floor = node.attributes["floor"].value
+ floor0 = floor
+ flr = splitImage(floor)
+ floor = flr[0]
+ floorcolor = flr[1]
+ except:
+ floor = None
+ floor0 = None
+ floorcolor = None
+
+ try:
+ description = node.attributes["description"].value
+ except:
+ description = ""
+
+ try:
+ missile = node.attributes["missile-particle"].value
+ except:
+ missile = ""
+
+ try:
+ drawBefore = node.attributes["drawBefore"].value
+ except:
+ drawBefore = ""
+
+ try:
+ drawAfter = node.attributes["drawAfter"].value
+ except:
+ drawAfter = ""
+
+# try:
+# drawPriority = int(node.attributes["drawPriority"].value)
+# except:
+# drawPriority = 0
+
+ if type == "hairsprite":
+ if idI >= 0:
+ print "error: " + fileName + ": hairsprite with id=" + id
+ errors = errors + 1
+ elif idI < -100:
+ print "error: " + fileName + ": hairsprite override player sprites"
+ errors = errors + 1
+
+ safeDye = True
+ testSprites(id, node, True, True, True, "", True)
+ safeDye = False
+
+ elif type == "racesprite":
+ if idI >= 0:
+ print "error: " + fileName + ": racesprite with id=" + id
+ errors = errors + 1
+ elif idI > -100:
+ print "error: " + fileName + ": racesprite override player hair"
+ errors = errors + 1
+ elif type == "usable" or type == "unusable" or type == "generic" \
+ or type == "equip-necklace" or type == "equip-torso" or type == "equip-feet" \
+ or type == "equip-arms" or type == "equip-legs" or type == "equip-head" \
+ or type == "equip-shield" or type == "equip-1hand" or type == "equip-2hand" \
+ or type == "equip-charm" or type == "equip-ammo" or type == "equip-neck" \
+ or type == "equip-ring" or type == "card":
+ if image == "":
+ print "error: " + fileName + ": missing image attribute on id=" + id
+ errors = errors + 1
+ continue
+ elif len(imagecolor) > 0:
+ if colors is None:
+ testDye(id, imagecolor, "image=" + image0, fileName, True)
+ else:
+ testDyeChannel(id, imagecolor, "image=" + image0, True)
+ if colors not in colorsList:
+ print "error: " + fileName + ": colors value " + colors + " not found in itemcolors.xml"
+ errors = errors + 1
+
+ if floorcolor != None and len(floorcolor) > 0:
+ if colors is None:
+ testDye(id, floorcolor, "floor=" + floor0, fileName, True)
+ else:
+ testDyeChannel(id, imagecolor, "floor=" + floor0, True);
+ if colors not in colorsList:
+ print "error: " + fileName + ": colors value " + colors + " not found in itemcolors.xml"
+ errors = errors + 1
+
+ if description == "":
+ print "warn: " + fileName + ": missing description attribute on id=" + id
+ warnings = warnings + 1
+ elif description == ".":
+ print "warn: " + fileName + ": broken description attribute on id=" + id
+ warnings = warnings + 1
+
+ if missile != "":
+ testParticle(id, missile, fileName)
+
+ testSounds(id, node, "item")
+
+ try:
+ floorSprite = node.getElementsByTagName("floor")[0]
+ except:
+ floorSprite = None
+ if floorSprite != None:
+ if floor != None:
+ print "error: " + fileName + ": found attribute floor and tag floor. " + \
+ "Should be only one tag or attribute. id=" + id
+ errors = errors + 1
+ testSprites(id, floorSprite, False, colors is None, True, "", err)
+
+ fullPath = os.path.abspath(parentDir + "/" + imgDir + image)
+ if not os.path.isfile(fullPath) or os.path.exists(fullPath) == False:
+ showFileErrorById (id, imgDir, image)
+ errors = errors + 1
+ else:
+ testImageFile(imgDir + image, fullPath, 32, "", True)
+
+ if floor != None:
+ fullPath = os.path.abspath(parentDir + "/" + imgDir + floor)
+ if not os.path.isfile(fullPath) or os.path.exists(fullPath) == False:
+ showFileErrorById (id, imgDir, floor)
+ errors = errors + 1
+ else:
+ testImageFile(imgDir + floor, fullPath, 0, "", True)
+
+ testItemReplace(id, node, "replace")
+ if drawBefore != "":
+ checkSpriteName(id, drawBefore)
+ if drawAfter != "":
+ checkSpriteName(id, drawAfter)
+
+ try:
+ attackaction = node.attributes["attack-action"].value
+ except:
+ attackaction = ""
+
+ testSprites(id, node, True, colors is None, False, attackaction, True)
+
+ if type != "usable" and type != "unusable" and type != "generic" \
+ and type != "equip-necklace" and type != "equip-1hand" \
+ and type != "equip-2hand" and type != "equip-ammo" \
+ and type != "equip-charm" and type != "equip-neck":
+ err = type != "equip-shield"
+ testSprites(id, node, True, colors is None, True, "", err)
+ elif type == "other":
+ None
+ elif type != "":
+ print "warn: " + fileName + ": unknown type '" + type + "' for id=" + id
+ warnings = warnings + 1
+
+
+def testItemReplace(id, rootNode, name):
+ global warnings, errors
+ for node in rootNode.getElementsByTagName(name):
+ if node.parentNode != rootNode:
+ continue
+ try:
+ sprite = node.attributes["sprite"].value
+ except:
+ if len(node.attributes) != 0:
+ print "error: reading replace sprite name, id=" + str(id)
+ errors = errors + 1
+ continue
+ checkSpriteName(id, sprite)
+ for itemNode in node.getElementsByTagName("item"):
+ if itemNode.parentNode != node:
+ continue
+ #TODO here need check "from" and "to" for correct item id
+
+
+def checkSpriteName(id, name):
+ global warnings, errors
+ if name != "race" and name != "type" and name != "shoes" and name != "boot" and \
+ name != "boots" and name != "bottomclothes" \
+ and name != "bottom" and name != "pants" and name != "topclothes" and \
+ name != "top" and name != "torso" and name != "body" and name != "misc1" \
+ and name != "misc2" and name != "scarf" and name != "scarfs" and \
+ name != "hair" and name != "hat" and name != "hats" and name != "wings" \
+ and name != "glove" and name != "gloves" and name != "weapon" and \
+ name != "weapons" and name != "shield" and name != "shields" and \
+ name != "amulet" and name != "amulets" and name != "ring" and name != "rings":
+ print "error: unknown sprite name " + name + ", id=" + str(id)
+ errors = errors + 1
+
+
+def testMonsters(fileName):
+ global warnings, errors
+ if not stfu:
+ print "Checking " + fileName
+ dom = minidom.parse(parentDir + "/" + fileName)
+ idset = set()
+ for node in dom.documentElement.childNodes:
+ if node.nodeName == "include":
+ try:
+ name = node.attributes["name"].value
+ if name == "":
+ errors = errors + 1
+ print "error: " + fileName + ": Empty include name";
+ testMonsters(name)
+ except:
+ errors = errors + 1
+ print "error: " + fileName + ": Broken include tag";
+ continue
+ if node.nodeName == "monster":
+ try:
+ id = node.attributes["id"].value
+ except:
+ print "error: " + fileName + ": no id for monster"
+ errors = errors + 1
+ continue
+
+ if id in idset:
+ print "error: " + fileName + ": duplicate id=" + id
+ errors = errors + 1
+ else:
+ idset.add(id)
+
+ try:
+ name = node.attributes["name"].value
+ except:
+ print "error: " + fileName + ": no name for id=" + id
+ errors = errors + 1
+ name = ""
+
+ testTargetCursor(id, node, fileName)
+ testSprites(id, node, False, True, True, "", True)
+ testSounds(id, node, "monster")
+ testParticles(id, node, "particlefx", fileName)
+
+def testTargetCursor(id, node, file):
+ try:
+ targetCursor = node.attributes["targetCursor"].value
+ if targetCursor != "small" and targetCursor != "medium" and targetCursor != "large":
+ showMsgFile(id, "unknown target cursor " + targetCursor, True)
+ except:
+ None
+
+def testParticles(id, node, nodeName, src):
+ particles = node.getElementsByTagName(nodeName)
+ for particle in particles:
+ try:
+ particlefx = particle.childNodes[0].data
+ except:
+ showMsgFile(id, "particle tag have incorrect data", True)
+
+ testParticle(id, particlefx, src)
+
+
+
+def testSounds(id, node, type):
+ global errors
+ havemiss = False
+ for sound in node.getElementsByTagName("sound"):
+ try:
+ event = sound.attributes["event"].value
+ except:
+ print "error: no sound event name in id=" + id
+ errors = errors + 1
+
+ if type == "monster":
+ if event != "hit" and event != "miss" and event != "hurt" and event != "die" \
+ and event != "move" and event != "sit" and event != "spawn":
+ print "error: incorrect sound event name " + event + " in id=" + id
+ errors = errors + 1
+ elif type == "item":
+ if event != "hit" and event != "strike" and event != "miss":
+ print "error: incorrect sound event name " + event + " in id=" + id
+ errors = errors + 1
+ if event == "strike" or event == "miss":
+ if havemiss:
+ print "error: miss and strike attributes at same time in id=" + id
+ errors = errors + 1
+ havemiss = True
+
+ testSound(sound.childNodes[0].data, sfxDir, "")
+
+def testNpcs(file):
+ global warnings, errors
+ if not stfu:
+ print "Checking " + file
+ dom = minidom.parse(parentDir + "/" + file)
+ idset = set()
+ for node in dom.documentElement.childNodes:
+ if node.nodeName == "include":
+ try:
+ name = node.attributes["name"].value
+ if name == "":
+ errors = errors + 1
+ print "error: " + file + ": Empty include name";
+ testNpcs(name)
+ except:
+ errors = errors + 1
+ print "error: " + file + ": Broken include tag";
+ continue
+ if node.nodeName != "npc":
+ continue
+
+ try:
+ id = node.attributes["id"].value
+ except:
+ print "error: " + file + ": no id for npc"
+ errors = errors + 1
+ continue
+
+ if id in idset:
+ print "error: " + file + ": duplicate npc id=" + id
+ errors = errors + 1
+ else:
+ idset.add(id)
+
+ testSprites(id, node, False, True, True, "", True)
+ testParticles(id, node, "particlefx", file)
+
+def readAttrI(node, attr, dv, msg, iserr):
+ return int(readAttr(node, attr, dv, msg, iserr))
+
+def readAttr(node, attr, dv, msg, iserr):
+ global warnings, errors
+ try:
+ return node.attributes[attr].value
+ except:
+ print msg
+ if iserr:
+ errors = errors + 1
+ else:
+ warnings = warnings + 1
+ return dv
+
+def readAttr2(node, attr, dv):
+ try:
+ return node.attributes[attr].value
+ except:
+ return dv
+
+
+def testMap(mapName, file, path):
+ global warnings, errors
+ fullPath = parentDir + "/" + path
+ dom = minidom.parse(fullPath)
+ root = dom.documentElement
+ mapWidth = readAttrI(root, "width", 0, "error: missing map width: " + file, True)
+ mapHeight = readAttrI(root, "height", 0, "error: missing map height: " + file, True)
+ mapTileWidth = readAttrI(root, "tilewidth", 0, "error: missing tile width: " + file, True)
+ mapTileHeight = readAttrI(root, "tileheight", 0, "error: missing tile height: " + file, True)
+ mapVersion = readAttr(root, "version", "1.0", "error: missing map version: " + file, True)
+
+ if mapWidth == 0 or mapHeight == 0 or mapTileWidth == 0 or mapTileHeight == 0:
+ return
+
+ mapVersion = mapVersion.replace(".", "")
+ try:
+ mapVersion = int(mapVersion)
+ except:
+ showMsgFile(file, "Invalid map version: " + str(mapVersion), False)
+
+ if mapVersion < tiledVersion:
+ showMsgFile(file, "Outdated map version: " + str(mapVersion), False)
+
+ if mapWidth < borderSize * 2 + 1:
+ if silent == False or file.find("maps/test") != 0:
+ showMsgFile(file, "map width to small: " + str(mapWidth), False)
+ if mapHeight < borderSize * 2 + 1:
+ if silent == False or file.find("maps/test") != 0:
+ showMsgFile(file, "map height to small: " + str(mapHeight), False)
+
+ if len(dom.getElementsByTagName("properties")) < 1:
+ showMsgFile(file, "missing map properties", True)
+ return
+
+ for props in dom.getElementsByTagName("properties"):
+ for prop in props.getElementsByTagName("property"):
+ try:
+ name = prop.attributes["name"].value
+ except:
+ name = ""
+ if name == "":
+ showMsgFile(file, "wrong property", True)
+ continue
+ try:
+ value = prop.attributes["value"].value
+ except:
+ value = ""
+ if value == "" and name == "name":
+ showMsgFile(file, "empty map name property", True)
+ continue
+
+ # Total minimum required width
+ if mapWidth < 60:
+ name1=file.find("maps/test")
+ name2=file.find("maps/000-1")
+ if name1 == 0 or name2 == 0:
+ pass
+ else:
+ showMsgFile(file, "total map width to small: " + str(mapWidth), False)
+
+ tilesMap = dict()
+
+ for tileset0 in dom.getElementsByTagName("tileset"):
+ tileset = tileset0
+ try:
+ firstGid = int(tileset.attributes["firstgid"].value)
+ except:
+ firstGid = 0
+
+ try:
+ source = tileset.attributes["source"].value
+ if source is not None and source != "":
+ file2 = os.path.abspath(parentDir + os.path.sep + mapsDir + source)
+ if not os.path.isfile(file2):
+ showMsgFile(file, "missing source file in tileset " + source, True)
+ except:
+ source = ""
+
+ tile = Tileset()
+ tile.firstGid = firstGid
+ tile.lastGid = 0
+
+ if source[-4:] == ".tsx":
+ relativePath = parentDir + "/" + mapsDir + source
+ try:
+ dom2 = minidom.parse(relativePath)
+ tileset = dom2.documentElement
+ idx = relativePath.rfind("/")
+ relativePath = relativePath[:idx+1]
+ relativePath2 = source
+ idx = relativePath2.rfind("/")
+ relativePath2 = relativePath2[:idx+1]
+ except:
+ showMsgFile(file, "tsx not found: " + source, True)
+ relativePath = ""
+ relativePath2 = ""
+ else:
+ relativePath = ""
+ relativePath2 = ""
+
+ name = readAttr(tileset, "name", "", "warning: missing tile name: " + file, False)
+ tileWidth = readAttrI(tileset, "tilewidth", mapTileWidth, \
+ "error: missing tile width in tileset: " + name + ", " + file, True)
+ tileHeight = readAttrI(tileset, "tileheight", mapTileHeight, \
+ "error: missing tile height in tileset: " + name + ", " + file, True)
+ if firstGid in tilesMap:
+ showMsgFile(file, "tile with firstgid " + str(firstGid) + \
+ " already exist: " + name + ", " + file, True)
+ continue
+
+ tile.width = tileWidth
+ tile.tileWidth = tileWidth
+ tile.height = tileHeight
+ tile.tileHeight = tileHeight
+ tile.name = name
+
+ images = tileset.getElementsByTagName("image")
+ if images == None or len(images) == 0:
+ showMsgFile(file, "missing image tags in tile " + name, True)
+ continue
+ elif len(images) > 1:
+ showMsgFile(file, "to many image tags in tile " + name, True)
+ continue
+
+ image = images[0]
+ source = readAttr(image, "source", None, "error: missing source in image tag in tile " \
+ + name + ": " + file, True)
+
+ if source != None:
+ if relativePath == "":
+ imagePath = os.path.abspath(parentDir + "/" + mapsDir + source)
+ else:
+ imagePath = os.path.abspath(relativePath + source)
+
+ img = splitImage(imagePath)
+ imagePath = img[0]
+ imagecolor = img[1]
+
+ tile.image = imagePath
+ tile.color = imagecolor
+
+ if not os.path.isfile(imagePath) or os.path.exists(imagePath) == False:
+ showMsgFile(file, "image file not exist: " + mapsDir + source + ", " + \
+ name, True)
+ continue
+
+ if imagecolor != "":
+ testDye("", imagecolor, source, file, True)
+
+ sz = testImageFile(file, imagePath, 0, "", True)
+ width = sz[0]
+ height = sz[1]
+
+ if width == 0 or height == 0:
+ continue
+
+ if width < tileWidth:
+ showMsgFile(file, "tile width more than image width in tile: " + \
+ name, True)
+ continue
+ if height < tileHeight:
+ showMsgFile(file, "tile height more than image height in tile: " + \
+ name, True)
+ continue
+
+ s1 = int(width / int(tileWidth)) * int(tileWidth)
+
+ if width != s1:
+ if s1 == 0:
+ s1 = int(tileWidth)
+ showMsgFile(file, "image width " + str(width) + \
+ " (need " + str(s1) + ") is not multiply to tile size " + \
+ str(tileWidth) + ". " + source + ", " + name, False)
+
+ s2 = int(height / int(tileHeight)) * int(tileHeight)
+
+ tile.lastGid = tile.firstGid + (int(width / int(tileWidth)) * int(height / int(tileHeight))) - 1
+ if height != s2:
+ if s2 == 0:
+ s2 = int(tileHeight)
+ showMsgFile(file, "image width " + str(height) + \
+ " (need " + str(s2) + ") is not multiply to tile size " + \
+ str(tileHeight) + ". " + source + ", " + name, False)
+
+ tile.source = relativePath2 + source
+ # hack to change relative back path to normal relative path
+ if len(tile.source) > 3 and tile.source[:11] == "../graphics":
+ tile.source = tile.source[3:]
+ tilesMap[tile.firstGid] = tile
+
+ #if mapName not in mapToAtlas:
+ # showMsgFile(file, "map dont have atlas", True)
+
+ tileset = tileset0
+
+ testTiles(mapName, file, tilesMap)
+ layers = dom.getElementsByTagName("layer")
+ objects = dom.getElementsByTagName("object")
+ if layers == None or len(layers) == 0:
+ showMsgFile(file, "map dont have layers", True)
+ return
+
+ fringe = None
+ collision = None
+ lowLayers = []
+ overLayers = []
+ beforeFringe = True
+ haveHeight = False
+
+ for layer in layers:
+ name = readAttr(layer, "name", None, "layer dont have name", True)
+ if name == None:
+ continue
+ if name.lower() == "height" or name.lower() == "heights":
+ haveHeight=True
+ obj = Layer()
+ obj.name = name
+ if name.lower() == "fringe":
+ if fringe is not None:
+ showMsgFile(file, "duplicate Fringe layer", True)
+ fringe = obj
+ beforeFringe = False
+ elif name.lower() == "collision":
+ if collision is not None:
+ showMsgFile(file, "duplicate Collision layer", True)
+ collision = obj
+ elif beforeFringe == True:
+ lowLayers.append(obj)
+ else:
+ overLayers.append(obj)
+
+ width = readAttrI(layer, "width", 0, "error: missing layer width: " + name + \
+ ", " + file, True)
+ height = readAttrI(layer, "height", 0, "error: missing layer height: " + name + \
+ ", " + file, True)
+ if width == 0 or height == 0:
+ continue
+
+ obj.width = width
+ obj.height = height
+
+ if mapWidth < width:
+ showMsgFile(file, "layer width " + str(width) + " more than map width " + \
+ str(mapWidth) + ": " + name, True)
+ if mapHeight < height:
+ showMsgFile(file, "layer height " + str(height) + " more then map height " + \
+ str(mapHeight) + ": " + name, True)
+
+ obj = testLayer(file, layer, name, width, height, obj, tilesMap)
+ testOverSizedTiles(obj, tilesMap, file)
+
+ if fringe == None:
+ showMsgFile(file, "missing fringe layer", True)
+ if collision == None:
+ showMsgFile(file, "missing collision layer", True)
+ elif mapName != "test.tmx" and mapName != "testbg.tmx":
+ ids = testCollisionLayer(file, collision, tilesMap)
+ if ids[0] != None and len(ids[0]) > 0:
+ if silent == False or file.find("maps/test") != 0:
+ showLayerErrors(file, ids[0], "empty tiles in collision border", False)
+ if ids[1] != None and len(ids[1]) > 0:
+ if silent == False or file.find("maps/test") != 0:
+ showLayerErrors(file, ids[1], "incorrect tileset index in collision layer", False)
+
+ if len(lowLayers) < 1:
+ showMsgFile(file, "missing low layers", False)
+ if len(overLayers) < 1:
+ if (silent == False or file.find("maps/test") != 0) and herc == False:
+ showMsgFile(file, "missing over layers", False)
+
+ if not haveHeight:
+ showMsgFile(file, "missing height layer", False)
+
+ if fringe != None:
+ lowLayers.append(fringe)
+ warn1 = None
+
+ if len(overLayers) > 0:
+ testData = dict()
+ warn1 = testLayerGroups(file, lowLayers, collision, None, tilesMap, False)
+ lowLayers.extend(overLayers)
+ err1 = testLayerGroups(file, lowLayers, collision, testData, tilesMap, False)
+ reportAboutTiles(file, testData)
+ else:
+ testData = dict()
+ err1 = testLayerGroups(file, lowLayers, collision, testData, tilesMap, False)
+ reportAboutTiles(file, testData)
+
+ if warn1 != None and err1 != None:
+ warn1 = warn1 - err1
+ if warn1 != None and len(warn1) > 0:
+ if silent != True:
+ showLayerErrors(file, warn1, "empty tile in lower layers", False)
+ if err1 != None and len(err1) > 0:
+ showLayerErrors(file, err1, "empty tile in all layers", True)
+
+ for objx in objects:
+ x = readAttr(objx, "x", 0, "object in invalid X position", False)
+ y = readAttr(objx, "y", 0, "object in invalid Y position", False)
+ w = readAttr2(objx, "width", 0)
+ h = readAttr2(objx, "height", 0)
+ try:
+ fs=False
+ if (float(x) != int(x)):
+ showMsgFile(file, "Invalid object X pos - must be integer", False)
+ fs=1
+ if (float(y) != int(y)):
+ showMsgFile(file, "Invalid object Y pos - must be integer", False)
+ fs=1
+ if (float(w) != int(w)):
+ showMsgFile(file, "Invalid object Width - must be integer", False)
+ fs=1
+ if (float(h) != int(h)):
+ showMsgFile(file, "Invalid object Height - must be integer", False)
+ fs=1
+ if fs:
+ id1=readAttr(objx, "id", "?", "invalid object ID", False)
+ name1=readAttr(objx, "name", "?", "invalid object name", False)
+ type1=readAttr(objx, "type", "?", "invalid object type", False)
+ showMsgFile(file, "Broken object: id %s name %s (%s,%s,%s,%s) type %s" % (id1, name1, x, y, w, h, type1), True);
+ except:
+ id1=readAttr(objx, "id", "?", "invalid object ID", False)
+ showMsgFile(file, "Broken object ID %s - x/y/h/w corrupted data detected" % id1, True)
+
+def testOverSizedTiles(layer, tiles, file):
+ global warnings, errors
+
+ oversizeErrList = []
+ ignoreErrList = []
+ ignoreTilesetList = set()
+ ignoredFiles = []
+ if "ignored" in atlasToFiles:
+ ignoredFiles = atlasToFiles["ignored"]
+ for x in range(0, layer.width):
+ for y in range(0, layer.height):
+ idx = ((y * layer.width) + x) * 4
+ val = getLDV(layer.arr, idx)
+ if val == 0:
+ continue
+
+ tile, tilesetName = findTileByGid(tiles, val)
+ if layer.name.lower() not in ("collision", "heights", "height") and tilesetName in ignoredFiles:
+ ignoreErrList.append((x, y))
+ ignoreTilesetList.add(tilesetName)
+ if layer.name.lower() == "fringe":
+ continue
+ if tile is None:
+ # now ignoring, this happend because layer parser
+ # not support includes
+ None
+ elif tile.tileWidth > 32 and x + 1 < layer.width:
+ for x2 in range(x + 1, x + 1 + int(tile.width / 32), 1):
+ idx = ((y * layer.width) + x2) * 4
+ val = getLDV(layer.arr, idx)
+ tile, _ = findTileByGid(tiles, val)
+ if val > 0:
+ oversizeErrList.append((x, y))
+ if silent != True:
+ warnings = warnings + 1
+ elif tile.tileHeight > 32 and y - 1 > 0:
+ for y2 in range(y - 1, y - 1 - int(tile.height / 32), -1):
+ idx = ((y2 * layer.width) + x) * 4
+ val = getLDV(layer.arr, idx)
+ tile, _ = findTileByGid(tiles, val)
+ if val > 0:
+ oversizeErrList.append((x, y))
+ if silent != True:
+ warnings = warnings + 1
+
+ if len(oversizeErrList) > 0 and silent != True:
+ print "error: " + file + ": Oversized tile overlapped to next tile in layer " + layer.name + \
+ ". Possible incorrect map drawing"
+ errors = errors + 1
+ errStr = ""
+ k = 0
+ for err in oversizeErrList:
+ errStr = errStr + str(err) + ", "
+ k = k + 1
+ if k > 100:
+ errStr = errStr + "..."
+ break
+ print errStr
+
+ if len(ignoreErrList) > 0:
+ errStr = ""
+ for err in ignoreTilesetList:
+ if errStr != "":
+ errStr = errStr + ", "
+ errStr = errStr + err
+ print("error: {0}: Tiles from ignored atlas used in layer {1}. Tilesets: {2}. "
+ "Possible incorrect map drawing".format(file, layer.name, errStr))
+ errors = errors + 1
+ errStr = ""
+ k = 0
+ for err in ignoreErrList:
+ errStr = errStr + str(err) + ", "
+ k = k + 1
+ if k > 100:
+ errStr = errStr + "..."
+ break
+ print errStr
+
+
+def testTiles(mapName, file, tilesMap):
+ ignoredFiles = []
+ if "ignored" in atlasToFiles:
+ ignoredFiles = atlasToFiles["ignored"]
+ for firstGid in tilesMap:
+ tile1 = tilesMap[firstGid]
+ if mapName in mapToAtlas:
+ atlasName = mapToAtlas[mapName]
+ if atlasName in atlasToFiles:
+ files = atlasToFiles[atlasName]
+ """
+ if tile1.source not in files and tile1.source not in ignoredFiles:
+ showMsgFile(file, "tileset '{0} ({1})' not present in atlas '{2}'".format(
+ tile1.name,
+ tile1.source,
+ atlasName),
+ True)
+ """
+ for gid2 in tilesMap:
+ if firstGid != gid2:
+ tile2 = tilesMap[gid2]
+ if (tile1.firstGid >= tile2.firstGid and tile1.firstGid <= tile2.lastGid) or \
+ (tile1.lastGid >= tile2.firstGid and tile1.lastGid <= tile2.lastGid):
+ showMsgFile(file, "overlaping tilesets gids \"" + tile1.name \
+ + "\" and \"" + tile2.name + "\"", True)
+
+
+def reportAboutTiles(file, data):
+ if testBadCollisions == False:
+ return
+ for k in data:
+ d = data[k]
+ if d[0] != 0 and d[2] != 0:
+ #print file + ": " + str(k) + ": " + str(d)
+ testCollisionPoints(file, k, d, 1, 3, \
+ "possible tiles should be without collision: ", \
+ "because no collision: ", False)
+ testCollisionPoints(file, k, d, 3, 1, \
+ "possible tiles should be with collision: ", \
+ "because collision: ", False)
+
+
+def testCollisionPoints(file, tileId, data, idx1, idx2, msg1, msg2, iserr):
+ #print "test: " + str(idx1) + ", " + str(idx2)
+ cnt1 = 0
+ cnt2 = 0
+ for point in data[idx1]:
+ if point[2] > 0:
+ cnt1 = cnt1 + 1
+ for point in data[idx2]:
+ if point[2] > 0:
+ cnt2 = cnt2 + 1
+
+ ln1 = len(data[idx1])
+ ln2 = len(data[idx2])
+ #print "cnt1=" + str(cnt1) + ", cnt2=" + str(cnt2) + ", ln1=" + str(ln1) + ", ln2=" + str(ln2)
+ if ln1 > 0 and ln2 > 0 and cnt2 > 0 and cnt2 < cnt1 - tileNumDiff and cnt2 < maxNumErrTiles:
+ text = msg1
+ c = 0
+ for point in data[idx2]:
+ if point[2] > 0:
+ if c > 100:
+ break
+ text = text + "(" + str(point[0]) + ", " + str(point[1]) + "), "
+ c = c + 1
+ text = text[:len(text)-2] + " " + msg2
+ c = 0
+ for point in data[idx1]:
+ if c > 100:
+ break
+ text = text + "(" + str(point[0]) + ", " + str(point[1]) + "), "
+ c = c + 1
+ showMsgFile(file, text[:len(text)-2], iserr)
+
+
+def testCollisionLayer(file, layer, tiles):
+ haveTiles = False
+ tileset = set()
+ badtiles = set()
+ arr = layer.arr
+ x1 = borderSize
+ y1 = borderSize
+ x2 = layer.width - borderSize
+ y2 = layer.height - borderSize
+ if x2 < 0:
+ x2 = 0
+ if y2 < 0:
+ y2 = 0
+
+ if arr is None :
+ return (set(), set())
+
+ for x in range(0, layer.width):
+ for y in range(0, layer.height):
+ idx = ((y * layer.width) + x) * 4
+ val = getLDV(arr, idx)
+ if val != 0:
+ haveTiles = True
+ tile, tilesetName = findTileByGid(tiles, val)
+ if tile is not None:
+ idx = val - tile.firstGid
+ if idx > 6: # 6 - max collision type
+ badtiles.add(((x, y), idx))
+ else:
+ badtiles.add(((x, y), "+{0}".format(val)))
+ if val == 0 and (x < x1 or x > x2 or y < y1 or y > y2):
+ tileset.add((x, y))
+
+ if haveTiles == False:
+ if silent == False or file.find("maps/test") != 0:
+ showMsgFile(file, "empty collision layer", False)
+ return (set(), set())
+
+ return (tileset, badtiles)
+
+
+def findTileByGid(tiles, gid):
+ for firstGid in tiles:
+ if firstGid <= gid:
+ tile = tiles[firstGid]
+ if tile.lastGid >= gid:
+ return (tile, tile.source)
+ return (None, None)
+
+
+def showLayerErrors(file, points, msg, iserr):
+ txt = ""
+ cnt = 0
+ for point in points:
+ txt = txt + " " + str(point) + ","
+ cnt = cnt + 1
+ if cnt > 100:
+ txt = txt + " ... "
+ break
+ showMsgFile(file, msg + txt[0:len(txt)-1], iserr)
+
+
+def getLDV(arr, index):
+ return arr[index] | (arr[index + 1] << 8) | (arr[index + 2] << 16) \
+ | (arr[index + 3] << 24)
+
+
+def getLDV2(arr, x, y, width, height, tilesMap):
+ ptr = ((y * width) + x) * 4
+ res = getLDV(arr, ptr)
+ yend = height - 1
+ if yend - y > 5:
+ yend = y + 5
+ for y2 in range(height - 1, y, -1):
+ x0 = x - 3
+ if x0 < 0:
+ x0 = 0
+ for x2 in range(x0, x + 1):
+ ptr = ((y2 * width) + x2) * 4
+ val = getLDV(arr, ptr)
+ tile, _ = findTileByGid(tilesMap, val)
+ if tile is not None:
+ if (tile.tileHeight > 32 or y2 == y) and (tile.tileWidth > 32 or x2 == x):
+ hg = tile.tileHeight / 32
+ wg = tile.tileWidth / 32
+ if (y2 - y < hg or y2 == y) and (x2 - x < wg or x2 == x):
+ res = val
+
+ return res
+
+
+def testLayer(file, node, name, width, height, layer, tiles):
+ datas = node.getElementsByTagName("data")
+ if datas == None or len(datas) == 0:
+ showMsgFile(file, "missing data tag in layer: " + name, True)
+ return
+ layer.arr = None
+ for data in datas:
+ try:
+ encoding = data.attributes["encoding"].value
+ except:
+ encoding = ""
+ try:
+ compression = data.attributes["compression"].value
+ except:
+ compression = ""
+ if encoding == "base64":
+# if compression != "gzip":
+# if compression != "zlib":
+# showMsgFile(file, "invalid compression " + compression + \
+# " in layer: " + name, True)
+# continue
+# else:
+# showMsgFile(file, "not supported compression by old clients " \
+# + compression + " in layer: " + name, False)
+ binData = data.childNodes[0].data.strip()
+ binData = binData.decode('base64')
+ if compression == "gzip":
+ dc = zlib.decompressobj(16 + zlib.MAX_WBITS)
+ else:
+ dc = zlib.decompressobj()
+ layerData = dc.decompress(binData)
+ arr = array.array("B")
+ arr.fromstring(layerData)
+ layer.arr = arr
+# print file
+# for item in arr:
+# print item
+ elif encoding == "csv":
+ if compression != "":
+ showMsgFile(file, "not supported compression " + compression + \
+ " for csv layer format:" + name, True)
+ binData = data.childNodes[0].data.strip()
+ f = StringIO.StringIO(binData)
+ arr = list(csv.reader(f, delimiter=',', quotechar='|'))
+ layer.arr = []
+# print file
+ for row in arr:
+ try:
+ for item in row:
+ if item != "":
+ nums = splitBytes(int(item))
+ layer.arr.append(nums[0])
+ layer.arr.append(nums[1])
+ layer.arr.append(nums[2])
+ layer.arr.append(nums[3])
+ except:
+ None
+
+ f.close()
+ arr = array.array('i', (layer.arr))
+ layer.arr = arr
+# for item in arr:
+# print item
+
+ elif encoding == "":
+ if compression != "":
+ showMsgFile(file, "not supported compression " + compression + \
+ " for xml layer format:" + name, True)
+
+ layer.arr = []
+ tiles = data.getElementsByTagName("tile")
+# print file
+ for tile in tiles:
+ try:
+ gid = int(tile.attributes["gid"].value)
+ except:
+ showMsgFile(file, "incorrect xml layer format: " + name, True)
+ return layer
+ nums = splitBytes(gid)
+ layer.arr.append(nums[0])
+ layer.arr.append(nums[1])
+ layer.arr.append(nums[2])
+ layer.arr.append(nums[3])
+
+ arr = array.array('i', (layer.arr))
+ layer.arr = arr
+# for item in arr:
+# print item
+
+
+ # here may be i should check is tiles correct or not, but i will trust to tiled
+ return layer
+
+
+def splitBytes(num):
+ i1 = int(num % 256)
+ i2 = int(((num % 65536) - i1) / 256)
+ i3 = int(((num % 16777216) - i2 - i1) / 65536)
+ i4 = int(((num % 4294967296) - i3 - i2 - i1) / 16777216)
+ return (i1, i2, i3, i4)
+
+def testLayerGroups(file, layers, collision, tileInfo, tilesMap, iserr):
+ width = 0
+ height = 0
+ errset = set()
+ for layer in layers:
+ if layer.width > width:
+ width = layer.width
+ if layer.height > height:
+ height = layer.height
+
+ for x in range(0, width):
+ for y in range(0, height):
+ good = False
+ lastTileId = 0
+ for layer in layers:
+ if layer.arr != None and x < layer.width \
+ and y < layer.height:
+ arr = layer.arr
+ ptr = ((y * layer.width) + x) * 4
+ if testBadCollisions == True:
+ val = getLDV2(arr, x, y, layer.width, layer.height, tilesMap)
+ else:
+ val = 0
+ val1 = getLDV(arr, ptr)
+ if val1 != 0:
+ good = True
+ if val == val1 and testBadCollisions == True:
+ lastTileId = val
+ if good == False:
+ errset.add((x,y))
+ elif testBadCollisions == True and collision != None and tileInfo != None:
+ if lastTileId not in tileInfo:
+ tileInfo[lastTileId] = [0, set(), 0, set()]
+ ti = tileInfo[lastTileId]
+ flg = getLDV(collision.arr, ((y * collision.width) + x) * 4)
+ cnt = countCollisionsNear(collision, x, y)
+ k = 0
+ if flg > 0:
+ if cnt[1] < cnt[0] and cnt[0] - cnt[1] > 5:
+ k = 1
+ ti[2] = ti[2] + 1
+ ti[3].add((x, y, k))
+ else:
+ if cnt[0] > cnt[1] and cnt[0] - cnt[1] > 5:
+ k = 1
+ ti[0] = ti[0] + 1
+ ti[1].add((x, y, k))
+
+ return errset
+
+
+def countCollisionsNear(layer, x, y):
+ arr = layer.arr
+ x1 = x - 1
+ y1 = y - 1
+ x2 = x + 1
+ y2 = y + 1
+ col = 0
+ nor = 0
+
+ if x1 < 0:
+ x1 = 0
+ if x2 >= layer.width:
+ x2 = layer.width - 1
+ if y1 < 0:
+ y1 = 0
+ if y2 >= layer.height:
+ y2 = layer.height - 1
+
+ for f in range(x1, x2 + 1):
+ for d in range(y1, y2 + 1):
+ if f != x or d != y:
+ val = getLDV(arr, ((d * layer.width) + f) * 4)
+ if val == 0:
+ nor = nor + 1
+ else:
+ col = col + 1
+ return (nor, col)
+
+
+def testMaps(dir):
+ global warnings, errors
+ fullPath = parentDir + "/" + dir
+ print "Checking maps"
+ if not os.path.isdir(fullPath) or not os.path.exists(fullPath):
+ print "error: maps dir not found: " + dir
+ errors = errors + 1
+ return
+
+ for file in os.listdir(fullPath):
+ if filtmaps.search(file):
+ testMap(file, mapsDir + file, dir + file)
+
+def testDirExists(path):
+ global errors
+ fullName = parentDir + "/" + path
+ if not os.path.exists(fullName):
+ print "error: path '" + path + "' not exists."
+ errors = errors + 1
+ elif not os.path.isdir(fullName):
+ print "error: path '" + path + "' is incorrect directory."
+ errors = errors + 1
+
+def testDefaultFiles():
+ global warnings
+ print "Checking default files"
+ testDirExists(iconsDir)
+ testDirExists(spritesDir)
+ testDirExists(particlesDir)
+ testDirExists(minimapsDir)
+ testDirExists(mapsDir)
+ testDirExists(sfxDir)
+ if silent != True:
+ testDirExists(musicDir)
+ testDirExists(wallpapersDir)
+
+ testSprite("0", spriteErrorFile, 0, True, "", True)
+ testParticle("0", particlesDir + levelUpEffectFile, "levelUpEffectFile")
+ testParticle("0", particlesDir + portalEffectFile, "portalEffectFile")
+ fullName = parentDir + "/" + wallpapersDir + wallpaperFile
+ if not os.path.isdir(fullName) and os.path.exists(fullName):
+ testImageFile(wallpapersDir + wallpaperFile, fullName, 0, "", False)
+
+
+def testMinimapsDir():
+ global errors, warnings
+
+ print "Checking minimaps"
+ fullPath = parentDir + "/" + minimapsDir
+ if not os.path.isdir(fullPath) or not os.path.exists(fullPath):
+ print "warn: minimaps dir not exist"
+ warnings = warnings + 1
+ return
+ for file in os.listdir(fullPath):
+ if filtimages.search(file):
+ fullName = parentDir + "/" + minimapsDir + file
+ testImageFile(minimapsDir + file, fullName, 0, "", True)
+
+
+def testImagesDir(imagesDir, sz):
+ global errors, warnings
+
+ fullPath = parentDir + "/" + imagesDir
+ if not os.path.isdir(fullPath) or not os.path.exists(fullPath):
+ return
+ for file in os.listdir(fullPath):
+ file2 = fullPath + "/" + file
+ if file[0] == ".":
+ continue
+ if not os.path.isfile(file2):
+ testImagesDir(imagesDir + file + "/", sz)
+ if filtimages.search(file):
+ fullName = parentDir + "/" + imagesDir + file
+ testImageFile(imagesDir + file, fullName, sz, "", True)
+
+
+def testSpritesDir(dir):
+ global errors, warnings, safeDye
+
+ fullPath = parentDir + "/" + spritesDir + dir
+ if not os.path.isdir(fullPath) or not os.path.exists(fullPath):
+ return
+
+ for file in os.listdir(fullPath):
+ file2 = fullPath + "/" + file
+ if file[0] == ".":
+ continue
+ if not os.path.isfile(file2):
+ testSpritesDir(dir + file + "/")
+ if filtimages.search(file):
+ fullName = parentDir + "/" + spritesDir + dir + file
+ testImageFile(spritesDir + dir, fullName, 0, spritesDir + dir + file, True)
+ elif filtxmls.search(file):
+ fullName = dir + file
+ safeDye = True
+ testSprite("0", dir + file, 0, True, "", True)
+ safeDye = False
+
+
+
+def testParticlesDir(dir):
+ global errors, warnings, safeDye
+
+ fullPath = parentDir + "/" + dir
+ if not os.path.isdir(fullPath) or not os.path.exists(fullPath):
+ return
+ for file in os.listdir(fullPath):
+ file2 = fullPath + "/" + file
+ if file[0] == ".":
+ continue
+ if not os.path.isfile(file2):
+ testParticlesDir(dir + file + "/")
+ if filtimages.search(file):
+ fullName = parentDir + "/" + dir + file
+ testImageFile(dir + file, fullName, 0, "", True)
+ elif filtxmls.search(file):
+ fullName = dir + file
+ safeDye = True
+ testParticle("0", dir + file, "")
+ safeDye = False
+
+
+def testSoundsDir(dir, sfxDir):
+ global errors, warnings
+
+ fullPath = parentDir + "/" + sfxDir + dir
+ if not os.path.isdir(fullPath) or not os.path.exists(fullPath):
+ print "warn: directory " + sfxDir + " not exist"
+ warnings = warnings + 1
+ return
+ for file in os.listdir(fullPath):
+ file2 = fullPath + "/" + file
+ if file[0] == ".":
+ continue
+ if not os.path.isfile(file2):
+ testSoundsDir(dir + file + "/", sfxDir)
+ elif filtogg.search(file):
+ testSound(dir + file, sfxDir, "")
+
+
+def testItemColors(fileName):
+ global warnings, errors, safeDye, colorLists
+ print "Checking itemcolors.xml"
+ try:
+ dom = minidom.parse(parentDir + "/" + fileName)
+ except:
+ return
+
+ for node in dom.getElementsByTagName("list"):
+ if node.parentNode != dom.documentElement:
+ continue
+
+ try:
+ name = node.attributes["name"].value
+ except:
+ print "error: colors list dont have name"
+ errors = errors + 1
+ continue
+ if name in colorsList:
+ print "error: duplicate color list: " + name
+ errors = errors + 1
+ continue
+ colorsList.add(name)
+ colors = set()
+ names = set()
+ for colorNode in node.getElementsByTagName("color"):
+ if colorNode.parentNode != node:
+ continue
+ try:
+ id = colorNode.attributes["id"].value
+ except:
+ print "error: getting id in list: " + name
+ errors = errors + 1
+ continue
+ try:
+ colorName = colorNode.attributes["name"].value
+ except:
+ print "error: getting name in list: " + name
+ errors = errors + 1
+ continue
+ try:
+ colorDye = colorNode.attributes["value"].value
+ except:
+ print "error: getting color in list: " + name
+ errors = errors + 1
+ if id in colors:
+ print "error: color with id " + str(id) + " already in list: " + name
+ errors = errors + 1
+ else:
+ colors.add(id)
+ if colorName in names:
+ print "error: color with name \"" + colorName + "\" already in list: " + name
+ errors = errors + 1
+ else:
+ names.add(colorName)
+ testDyeColors(id, colorDye, colorDye, name, True)
+
+def loadMapAtlases(fileName):
+ mapToAtlas = dict()
+ atlasToFiles = dict()
+ try:
+ root = ElementTree.parse(parentDir + "/" + fileName).getroot()
+ except:
+ showMsgFile(fileName, "load xml error. Probably file missing", True)
+ return (mapToAtlas, atlasToFiles)
+
+ for node in root.findall("map"):
+ mapName = node.attrib["name"]
+ atlasNode = node.find("atlas")
+ if atlasNode == None:
+ continue
+ atlasName = atlasNode.attrib["name"]
+ mapToAtlas[mapName] = atlasName
+ for node in root.findall("atlas"):
+ atlasName = node.attrib["name"]
+ files = []
+ for fileNode in node.findall("file"):
+ fileName = fileNode.attrib["name"]
+ files.append(fileName)
+ atlasToFiles[atlasName] = files
+ for mapName in mapToAtlas:
+ atlasName = mapToAtlas[mapName]
+ if atlasName not in atlasToFiles:
+ showMsgFile(fileName, "atlas '{0}' assigned to map not present in maps.xml".format(atlasName), True)
+
+ return (mapToAtlas, atlasToFiles)
+
+def haveXml(dir):
+ if not os.path.isdir(dir) or not os.path.exists(dir):
+ return False
+ for file in os.listdir(dir):
+ if filt.search(file):
+ return True
+ return False
+
+
+def detectClientData(dirs):
+ global parentDir
+
+ for dir in dirs:
+ if haveXml(dir):
+ print "Detected client data directory in: " + dir
+ parentDir = dir
+ return True
+
+ print "Cant detect client data directory"
+ exit(1)
+
+
+if len(sys.argv) == 2:
+ if sys.argv[1] == "all":
+ showAll = True
+ elif sys.argv[1] == "silent":
+ silent = True
+ elif sys.argv[1] == "stfu":
+ silent = True
+ stfu = True
+ elif sys.argv[1] == "herc":
+ silent = True
+ herc = True
+
+showHeader()
+print "Detecting clientdata dir"
+detectClientData([".", "..", "../../clientdata", parentDir])
+print "Checking xml file syntax"
+enumDirs(parentDir)
+loadPaths()
+(mapToAtlas, atlasToFiles) = loadMapAtlases("/maps.xml")
+testDefaultFiles()
+testItemColors("/itemcolors.xml")
+testItems("/items.xml", iconsDir)
+testMonsters("/monsters.xml")
+testNpcs("/npcs.xml")
+testMaps(mapsDir)
+testMinimapsDir()
+print "Checking images dir"
+testImagesDir(wallpapersDir, 0)
+print "Checking icons dir"
+testImagesDir(iconsDir, 32)
+print "Checking sprites dir"
+testSpritesDir("")
+print "Checking particles dir"
+testParticlesDir(particlesDir)
+print "Checking sfx dir"
+testSoundsDir("", sfxDir)
+print "Checking music dir"
+if silent != True:
+ testSoundsDir("", musicDir)
+showFooter()
+if errors > 0 or warnings > 0:
+ exit(1)
diff --git a/testxml/xsd/XMLSchema.xsd b/testxml/xsd/XMLSchema.xsd
new file mode 100644
index 0000000..3711a93
--- /dev/null
+++ b/testxml/xsd/XMLSchema.xsd
@@ -0,0 +1,2262 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+]>
+
+
+
+ Part 1 version: Id: structures.xsd,v 1.2 2004/01/15 11:34:25 ht Exp
+ Part 2 version: Id: datatypes.xsd,v 1.3 2004/01/23 18:11:13 ht Exp
+
+
+
+
+ The schema corresponding to this document is normative,
+ with respect to the syntactic constraints it expresses in the
+ XML Schema language. The documentation (within <documentation> elements)
+ below, is not normative, but rather highlights important aspects of
+ the W3C Recommendation of which this is a part
+
+
+
+ The simpleType element and all of its members are defined
+ towards the end of this schema document
+
+
+
+
+ Get access to the xml: attribute groups for xml:lang
+ as declared on 'schema' and 'documentation' below
+
+
+
+
+
+
+ This type is extended by almost all schema types
+ to allow attributes from other namespaces to be
+ added to user schemas.
+
+
+
+
+
+
+
+
+
+
+
+ This type is extended by all types which allow annotation
+ other than <schema> itself
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This group is for the
+ elements which occur freely at the top level of schemas.
+ All of their types are based on the "annotated" type by extension.
+
+
+
+
+
+
+
+
+
+
+
+ This group is for the
+ elements which can self-redefine (see <redefine> below).
+
+
+
+
+
+
+
+
+
+
+
+ A utility type, not for public use
+
+
+
+
+
+
+
+
+
+ A utility type, not for public use
+
+
+
+
+
+
+
+
+
+ A utility type, not for public use
+
+ #all or (possibly empty) subset of {extension, restriction}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A utility type, not for public use
+
+
+
+
+
+
+
+
+
+
+
+ A utility type, not for public use
+
+ #all or (possibly empty) subset of {extension, restriction, list, union}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ for maxOccurs
+
+
+
+
+
+
+
+
+
+
+
+
+ for all particles
+
+
+
+
+
+
+
+ for element, group and attributeGroup,
+ which both define and reference
+
+
+
+
+
+
+
+ 'complexType' uses this
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This branch is short for
+ <complexContent>
+ <restriction base="xs:anyType">
+ ...
+ </restriction>
+ </complexContent>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Will be restricted to required or forbidden
+
+
+
+
+
+ Not allowed if simpleContent child is chosen.
+ May be overriden by setting on complexContent child.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This choice is added simply to
+ make this a valid restriction per the REC
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Overrides any setting on complexType parent.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This choice is added simply to
+ make this a valid restriction per the REC
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ No typeDefParticle group reference
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A utility type, not for public use
+
+ #all or (possibly empty) subset of {substitution, extension,
+ restriction}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The element element can be used either
+ at the top level to define an element-type binding globally,
+ or within a content model to either reference a globally-defined
+ element or type or declare an element-type binding locally.
+ The ref form is not allowed at the top level.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ group type for explicit groups, named top-level groups and
+ group references
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ group type for the three kinds of group
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This choice with min/max is here to
+ avoid a pblm with the Elt:All/Choice/Seq
+ Particle derivation constraint
+
+
+
+
+
+
+
+ restricted max/min
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Only elements allowed inside
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ simple type for the value of the 'namespace' attr of
+ 'any' and 'anyAttribute'
+
+
+
+ Value is
+ ##any - - any non-conflicting WFXML/attribute at all
+
+ ##other - - any non-conflicting WFXML/attribute from
+ namespace other than targetNS
+
+ ##local - - any unqualified non-conflicting WFXML/attribute
+
+ one or - - any non-conflicting WFXML/attribute from
+ more URI the listed namespaces
+ references
+ (space separated)
+
+ ##targetNamespace or ##local may appear in the above list, to
+ refer to the targetNamespace of the enclosing
+ schema or an absent targetNamespace respectively
+
+
+
+
+ A utility type, not for public use
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A subset of XPath expressions for use
+in selectors
+ A utility type, not for public
+use
+
+
+
+ The following pattern is intended to allow XPath
+ expressions per the following EBNF:
+ Selector ::= Path ( '|' Path )*
+ Path ::= ('.//')? Step ( '/' Step )*
+ Step ::= '.' | NameTest
+ NameTest ::= QName | '*' | NCName ':' '*'
+ child:: is also allowed
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A subset of XPath expressions for use
+in fields
+ A utility type, not for public
+use
+
+
+
+ The following pattern is intended to allow XPath
+ expressions per the same EBNF as for selector,
+ with the following change:
+ Path ::= ('.//')? ( Step '/' )* ( Step | '@' NameTest )
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The three kinds of identity constraints, all with
+ type of or derived from 'keybase'.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A utility type, not for public use
+
+ A public identifier, per ISO 8879
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ notations for use within XML Schema schemas
+
+
+
+
+
+
+ Not the real urType, but as close an approximation as we can
+ get in the XML representation
+
+
+
+
+
+
+
+
+ First the built-in primitive datatypes. These definitions are for
+ information only, the real built-in definitions are magic.
+
+
+ For each built-in datatype in this schema (both primitive and
+ derived) can be uniquely addressed via a URI constructed
+ as follows:
+ 1) the base URI is the URI of the XML Schema namespace
+ 2) the fragment identifier is the name of the datatype
+
+ For example, to address the int datatype, the URI is:
+
+ http://www.w3.org/2001/XMLSchema#int
+
+ Additionally, each facet definition element can be uniquely
+ addressed via a URI constructed as follows:
+ 1) the base URI is the URI of the XML Schema namespace
+ 2) the fragment identifier is the name of the facet
+
+ For example, to address the maxInclusive facet, the URI is:
+
+ http://www.w3.org/2001/XMLSchema#maxInclusive
+
+ Additionally, each facet usage in a built-in datatype definition
+ can be uniquely addressed via a URI constructed as follows:
+ 1) the base URI is the URI of the XML Schema namespace
+ 2) the fragment identifier is the name of the datatype, followed
+ by a period (".") followed by the name of the facet
+
+ For example, to address the usage of the maxInclusive facet in
+ the definition of int, the URI is:
+
+ http://www.w3.org/2001/XMLSchema#int.maxInclusive
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ NOTATION cannot be used directly in a schema; rather a type
+ must be derived from it by specifying at least one enumeration
+ facet whose value is the name of a NOTATION declared in the
+ schema.
+
+
+
+
+
+
+
+
+ Now the derived primitive types
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ pattern specifies the content of section 2.12 of XML 1.0e2
+ and RFC 3066 (Revised version of RFC 1766).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ pattern matches production 7 from the XML spec
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ pattern matches production 5 from the XML spec
+
+
+
+
+
+
+
+
+
+
+
+
+
+ pattern matches production 4 from the Namespaces in XML spec
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A utility type, not for public use
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ #all or (possibly empty) subset of {restriction, union, list}
+
+
+ A utility type, not for public use
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Can be restricted to required or forbidden
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Required at the top level
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Forbidden when nested
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ We should use a substitution group for facets, but
+ that's ruled out because it would allow users to
+ add their own, which we're not ready for yet.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ base attribute and simpleType child are mutually
+ exclusive, but one or other is required
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ itemType attribute and simpleType child are mutually
+ exclusive, but one or other is required
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ memberTypes attribute must be non-empty or there must be
+ at least one simpleType child
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testxml/xsd/checkfile.sh b/testxml/xsd/checkfile.sh
new file mode 100755
index 0000000..90df1f1
--- /dev/null
+++ b/testxml/xsd/checkfile.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+xmllint --format --schema tmw.xsd "${1}" 2>&1 >/dev/null | \
+ grep -v ": Skipping import of schema located at " | \
+ grep -v ".xml validates"
diff --git a/testxml/xsd/its.xsd b/testxml/xsd/its.xsd
new file mode 100644
index 0000000..9dbb432
--- /dev/null
+++ b/testxml/xsd/its.xsd
@@ -0,0 +1,926 @@
+
+
+
+
+
+
+ Container for global rules.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Version of the ITS schema.
+
+
+
+
+
+
+
+
+
+
+
+
+ XPath expression identifying the nodes to be selected.
+
+
+
+
+
+
+
+
+
+ Version of the ITS schema.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The Translate data category information to be attached to
+ the current node.
+
+
+
+
+
+ The nodes need to be translated.
+
+
+
+
+ The nodes must not be translated.
+
+
+
+
+
+
+
+
+
+ Localization note.
+
+
+
+
+
+
+ The type of localization note.
+
+
+
+
+
+ Localization note is an alert.
+
+
+
+
+ Localization note is a description.
+
+
+
+
+
+
+
+
+
+ URI referring to the location of the localization note.
+
+
+
+
+
+
+ Pointer to a resource containing
+ information about the term.
+
+
+
+
+
+
+ Indicates a term locally.
+
+
+
+
+
+ The value 'yes' means that this is a term.
+
+
+
+
+ The value 'no' means that this is not a term.
+
+
+
+
+
+
+
+
+
+ The text direction for the context.
+
+
+
+
+
+ Left-to-right text.
+
+
+
+
+ Right-to-left text.
+
+
+
+
+ Left-to-right override.
+
+
+
+
+ Right-to-left override.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The Translate data category information to be attached to
+ the current node.
+
+
+
+
+
+ The nodes need to be translated.
+
+
+
+
+ The nodes must not be translated.
+
+
+
+
+
+
+
+
+
+ Localization note.
+
+
+
+
+
+
+ The type of localization note.
+
+
+
+
+
+ Localization note is an alert.
+
+
+
+
+ Localization note is a description.
+
+
+
+
+
+
+
+
+
+ URI referring to the location of the localization note.
+
+
+
+
+
+
+ Pointer to a resource containing
+ information about the term.
+
+
+
+
+
+
+ Indicates a term locally.
+
+
+
+
+
+ The value 'yes' means that this is a term.
+
+
+
+
+ The value 'no' means that this is not a term.
+
+
+
+
+
+
+
+
+
+ The text direction for the context.
+
+
+
+
+
+ Left-to-right text.
+
+
+
+
+ Right-to-left text.
+
+
+
+
+ Left-to-right override.
+
+
+
+
+ Right-to-left override.
+
+
+
+
+
+
+
+
+ Inline element to contain ITS information.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Rule about the Translate data category.
+
+
+
+
+
+
+
+
+
+ The Translate data category information to be
+ applied to selected nodes.
+
+
+
+
+
+ The nodes need to be translated.
+
+
+
+
+ The nodes must not be translated.
+
+
+
+
+
+
+
+
+
+
+
+
+ The Translate data category information to be attached to
+ the current node.
+
+
+
+
+
+ The nodes need to be translated.
+
+
+
+
+ The nodes must not be translated.
+
+
+
+
+
+
+
+
+ Rule about the Localization Note data category.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Relative XPath expression pointing to a node that holds the localization note.
+
+
+
+
+ The type of localization note.
+
+
+
+
+
+ Localization note is an alert.
+
+
+
+
+ Localization note is a description.
+
+
+
+
+
+
+
+ URI referring to the location of the localization note.
+
+
+
+
+ Relative XPath expression pointing to a node that holds the URI referring to the location of the localization note.
+
+
+
+
+
+ Contains a localization note.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Localization note.
+
+
+
+
+
+
+ The type of localization note.
+
+
+
+
+
+ Localization note is an alert.
+
+
+
+
+ Localization note is a description.
+
+
+
+
+
+
+
+
+
+ URI referring to the location of the localization note.
+
+
+
+
+
+ Rule about the Terminology data category.
+
+
+
+
+
+
+
+
+
+ Indicates whether the selection is a term or not.
+
+
+
+
+
+ The value 'yes' means that this is a term.
+
+
+
+
+ The value 'no' means that this is not a term.
+
+
+
+
+
+
+
+ URI referring to the resource providing information about the term.
+
+
+
+
+ Relative XPath expression pointing to a node containing a URI referring to the resource providing information about the term.
+
+
+
+
+ Relative XPath expression pointing to a node containing
+ information about the term.
+
+
+
+
+
+
+
+
+
+
+ Pointer to a resource containing
+ information about the term.
+
+
+
+
+
+
+ Indicates a term locally.
+
+
+
+
+
+ The value 'yes' means that this is a term.
+
+
+
+
+ The value 'no' means that this is not a term.
+
+
+
+
+
+
+
+
+ Rule about the Directionality data category.
+
+
+
+
+
+
+
+
+
+ The text direction for the selection.
+
+
+
+
+
+ Left-to-right text.
+
+
+
+
+ Right-to-left text.
+
+
+
+
+ Left-to-right override.
+
+
+
+
+ Right-to-left override.
+
+
+
+
+
+
+
+
+
+
+
+
+ The text direction for the context.
+
+
+
+
+
+ Left-to-right text.
+
+
+
+
+ Right-to-left text.
+
+
+
+
+ Left-to-right override.
+
+
+
+
+ Right-to-left override.
+
+
+
+
+
+
+
+
+ Rule about the Ruby data category.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Relative XPath expression pointing to a node that corresponds to a ruby element
+
+
+
+
+ Relative XPath expression pointing to a node that
+ corresponds to a rt element
+
+
+
+
+ Relative XPath expression pointing to a node that
+ corresponds to a rp element
+
+
+
+
+ Relative XPath expression pointing to a node that
+ corresponds to a rbc element
+
+
+
+
+ Relative XPath expression pointing to a node that
+ corresponds to a rtc element
+
+
+
+
+ Relative XPath expression pointing to a node that corresponds to a rbspan attribute.
+
+
+
+
+
+ Ruby text.
+
+
+
+
+
+
+
+
+
+ Allows an rt element to span multiple rb elements in complex ruby markup.
+
+
+
+
+
+ Ruby markup.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Ruby base text.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Ruby text.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Allows an rt element to span multiple rb elements in complex ruby markup.
+
+
+
+
+
+ Container for rb elements in the case of complex ruby markup.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Container for rt elements in the case of complex ruby markup.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Used in the case of simple ruby markup to specify characters that can denote the beginning and end of ruby text when user agents do not have other ways to present ruby text distinctively from the base text.
+
+
+
+
+
+
+
+
+
+
+ Rule about the Language Information data category.
+
+
+
+
+
+
+
+
+
+ Relative XPath expression pointing to a node that contains language information.
+
+
+
+
+
+ Rule about the Elements Within Text data category.
+
+
+
+
+
+
+
+
+
+ States whether current context is regarded as
+ "within text".
+
+
+
+
+
+ The element and its content are part of the flow of its parent element.
+
+
+
+
+ The element splits the text flow of its parent element and its content is an independent text flow.
+
+
+
+
+ The element is part of the flow of its parent element, its content is an independent flow.
+
+
+
+
+
+
+
diff --git a/testxml/xsd/tmw.xsd b/testxml/xsd/tmw.xsd
new file mode 100644
index 0000000..a8b5cf2
--- /dev/null
+++ b/testxml/xsd/tmw.xsd
@@ -0,0 +1,2117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testxml/xsd/xlink.xsd b/testxml/xsd/xlink.xsd
new file mode 100644
index 0000000..2c53ec3
--- /dev/null
+++ b/testxml/xsd/xlink.xsd
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Type of pointer to external rules files.
+
+
+
+
+
+ Simple link.
+
+
+
+
+
+
+
+
+
+ Pointer to external rules files.
+
+
+
+
diff --git a/testxml/xsd/xml.xsd b/testxml/xsd/xml.xsd
new file mode 100644
index 0000000..aea7d0d
--- /dev/null
+++ b/testxml/xsd/xml.xsd
@@ -0,0 +1,287 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
lang (as an attribute name)
+
+ denotes an attribute whose value
+ is a language code for the natural language of the content of
+ any element; its value is inherited. This name is reserved
+ by virtue of its definition in the XML specification.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
space (as an attribute name)
+
+ denotes an attribute whose
+ value is a keyword indicating what whitespace processing
+ discipline is intended for the content of the element; its
+ value is inherited. This name is reserved by virtue of its
+ definition in the XML specification.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
base (as an attribute name)
+
+ denotes an attribute whose value
+ provides a URI to be used as the base for interpreting any
+ relative URIs in the scope of the element on which it
+ appears; its value is inherited. This name is reserved
+ by virtue of its definition in the XML Base specification.
+
+
+ See http://www.w3.org/TR/xmlbase/
+ for information about this attribute.
+
+
+
+
+
+
+
+
+
+
+
+
id (as an attribute name)
+
+ denotes an attribute whose value
+ should be interpreted as if declared to be of type ID.
+ This name is reserved by virtue of its definition in the
+ xml:id specification.
+
+
+ See http://www.w3.org/TR/xml-id/
+ for information about this attribute.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Father (in any context at all)
+
+
+
+ denotes Jon Bosak, the chair of
+ the original XML Working Group. This name is reserved by
+ the following decision of the W3C XML Plenary and
+ XML Coordination groups:
+
+
+
+ In appreciation for his vision, leadership and
+ dedication the W3C XML Plenary on this 10th day of
+ February, 2000, reserves for Jon Bosak in perpetuity
+ the XML name "xml:Father".
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This schema defines attributes and an attribute group suitable
+ for use by schemas wishing to allow xml:base
,
+ xml:lang
, xml:space
or
+ xml:id
attributes on elements they define.
+
+
+ To enable this, such a schema must import this schema for
+ the XML namespace, e.g. as follows:
+
+
+ <schema . . .>
+ . . .
+ <import namespace="http://www.w3.org/XML/1998/namespace"
+ schemaLocation="http://www.w3.org/2001/xml.xsd"/>
+
+
+ or
+
+
+ <import namespace="http://www.w3.org/XML/1998/namespace"
+ schemaLocation="http://www.w3.org/2009/01/xml.xsd"/>
+
+
+ Subsequently, qualified reference to any of the attributes or the
+ group defined below will have the desired effect, e.g.
+
+
+ <type . . .>
+ . . .
+ <attributeGroup ref="xml:specialAttrs"/>
+
+
+ will define a type which will schema-validate an instance element
+ with any of those attributes.
+
+
+
+
+
+
+
+
+
+
+
+
+ In keeping with the XML Schema WG's standard versioning
+ policy, this schema document will persist at
+
+ http://www.w3.org/2009/01/xml.xsd .
+
+
+ At the date of issue it can also be found at
+
+ http://www.w3.org/2001/xml.xsd .
+
+
+ The schema document at that URI may however change in the future,
+ in order to remain compatible with the latest version of XML
+ Schema itself, or with the XML namespace itself. In other words,
+ if the XML Schema or XML namespaces change, the version of this
+ document at
+ http://www.w3.org/2001/xml.xsd
+
+ will change accordingly; the version at
+
+ http://www.w3.org/2009/01/xml.xsd
+
+ will not change.
+
+
+ Previous dated (and unchanging) versions of this schema
+ document are at:
+
+
+
+
+
+
+
+
+
diff --git a/testxml/xsdcheck.sh b/testxml/xsdcheck.sh
new file mode 100755
index 0000000..8cd27dd
--- /dev/null
+++ b/testxml/xsdcheck.sh
@@ -0,0 +1,52 @@
+#!/usr/bin/env bash
+
+cd xsd
+DIR="$(pwd)/../../../clientdata"
+rm ../errors.txt
+
+function check {
+ xmllint --format --schema tmw.xsd "${DIR}"/"${1}" 2>&1 >/dev/null | \
+ grep -v ": Skipping import of schema located at " | \
+ grep -v ".xml validates" | \
+ grep -v ".manaplus validates" >>../errors.txt
+}
+
+xmllint --format --schema XMLSchema.xsd tmw.xsd 2>&1 >/dev/null | \
+ grep -v ": Skipping import of schema located at " | \
+ grep -v ".xsd validates" >>../errors.txt
+
+check avatars.xml
+check badges.xml
+check charcreation.xml
+check deadmessages.xml
+check effects.xml
+check elementals.xml
+check emotes.xml
+check equipmentslots.xml
+check equipmentwindow.xml
+check tmw2.manaplus
+check features.xml
+check groups.xml
+check homunculuses.xml
+check horses.xml
+check itemcolors.xml
+check itemfields.xml
+check items.xml
+check maps.xml
+check mercenaries.xml
+check mods.xml
+check monsters.xml
+check npcdialogs.xml
+check npcs.xml
+check paths.xml
+check pets.xml
+check quests.xml
+check skills.xml
+check skillunits.xml
+check sounds.xml
+check stats.xml
+check status-effects.xml
+check units.xml
+check weapons.xml
+
+find -H "${DIR}/graphics" -type f -name "*.xml" -exec ./checkfile.sh {} \; >>../errors.txt
diff --git a/update/TMW2org.zip b/update/TMW2org.zip
new file mode 100644
index 0000000..5bed7a0
Binary files /dev/null and b/update/TMW2org.zip differ
diff --git a/update/addmods.sh b/update/addmods.sh
new file mode 100755
index 0000000..109a81b
--- /dev/null
+++ b/update/addmods.sh
@@ -0,0 +1,35 @@
+#!/usr/bin/env bash
+
+# Copyright (C) 2010-2012 TMW2 Online
+# Author: Andrei Karas (4144)
+
+dir=`pwd`
+mds='../../clientdata/mods'
+
+previous=`cat commit.txt`
+rm files/xml_mods.txt
+rm files/mod-*
+
+cd ../../clientdata/mods
+
+FILES=`ls`
+
+for file in $FILES; do
+ echo "Uploading $file mod...."
+ cd $file
+ find . -type f | xargs zip -9 -r ../../../tools/update/files/mod-$file.zip
+ cd $dir/files
+ sum=`adler32 mod-$file.zip | awk '{print $2}'`
+ echo " " >> xml_mods.txt
+ cp mod-$file.zip ../upload/
+ cd ../$mds
+done
+
+cd $dir/files
+cp xml_header.txt resources.xml
+cat xml_footer.txt >>resources.xml
+cat xml_mods.txt >>resources.xml
+echo '' >>resources.xml
+cp resources2.txt ../upload/
+cp resources.xml ../upload/
+
diff --git a/update/adler32.c b/update/adler32.c
new file mode 100644
index 0000000..606c739
--- /dev/null
+++ b/update/adler32.c
@@ -0,0 +1,79 @@
+/*
+ * adler32.c (c) 2006 Bjorn Lindeijer
+ * License: GPL, v2 or later
+ *
+ * Calculates Adler-32 checksums for all files passed as argument.
+ *
+ * Usage: adler32 [file]...
+ */
+
+#include
+#include
+#include
+
+/**
+ * Calculates the Adler-32 checksum for the given file.
+ */
+unsigned long fadler32(FILE *file)
+{
+ // Obtain file size
+ fseek(file, 0, SEEK_END);
+ long fileSize = ftell(file);
+ rewind(file);
+
+ // Calculate Adler-32 checksum
+ char *buffer = (char*) malloc(fileSize);
+ fread(buffer, 1, fileSize, file);
+ unsigned long adler = adler32(0L, Z_NULL, 0);
+ adler = adler32(adler, (Bytef*) buffer, fileSize);
+ free(buffer);
+
+ return adler;
+}
+
+/**
+ * Prints out usage and exists.
+ */
+void print_usage()
+{
+ printf("Usage: adler32 mode [file]...\n");
+ exit(0);
+}
+
+int main(int argc, char *argv[])
+{
+ int i; /**< Loops through arguments. */
+
+ if (argc < 2)
+ {
+ print_usage();
+ }
+
+ int mode = atoi(argv[1]);
+
+ for (i = 2; i < argc; ++i)
+ {
+ FILE *file = fopen(argv[i], "r");
+
+ if (!file)
+ {
+ printf("Error while opening '%s' for reading!\n", argv[i]);
+ exit(1);
+ }
+
+ unsigned long adler = fadler32(file);
+ switch (mode)
+ {
+ case 0:
+ default:
+ printf("%s %lx\n", argv[i], adler);
+ break;
+ case 1:
+ printf("%lx", adler);
+ break;
+ }
+ fclose(file);
+ }
+
+ return 0;
+}
diff --git a/update/commit.txt b/update/commit.txt
new file mode 100644
index 0000000..dc221c3
--- /dev/null
+++ b/update/commit.txt
@@ -0,0 +1 @@
+a0877f34f3f73c84629bf7781e7da0b2feaa0174
diff --git a/update/commit_old.txt b/update/commit_old.txt
new file mode 100644
index 0000000..652fd1b
--- /dev/null
+++ b/update/commit_old.txt
@@ -0,0 +1 @@
+2b90b8051698ab0e962dab6e338e6894e4a7cd21
diff --git a/update/create_music.sh b/update/create_music.sh
new file mode 100755
index 0000000..70720d9
--- /dev/null
+++ b/update/create_music.sh
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+
+# Copyright (C) 2011-2012 Evol Online
+# Author: Andrei Karas (4144)
+
+dir=`pwd`
+
+rm adler32
+gcc -lz adler32.c -o adler32
+
+mkdir files
+mkdir upload
+
+rm files/music.zip
+cd ../../music
+find -iregex ".+[.]\(ogg\|mp3\)" -exec touch --date=2015-01-01 {} \;
+find -iregex ".+[.]\(ogg\|mp3\)" -printf "%P\n" | zip -X -@ ../tools/update/files/music.zip
+git log --pretty=oneline -n 1 | awk '{print $1}' >../tools/update/musiccommit.txt
+
+cd $dir/files
+sum=`adler32 music.zip | awk '{print $2}'`
+
+echo " " >> xml_footer.txt
+
+cp xml_header.txt resources.xml
+cat xml_footer.txt >>resources.xml
+echo '' >>resources.xml
+
+cp music.zip ../upload/
+cp resources.xml ../upload/
+cp ../news.txt ../upload/
diff --git a/update/createnew.sh b/update/createnew.sh
new file mode 100755
index 0000000..5ab356f
--- /dev/null
+++ b/update/createnew.sh
@@ -0,0 +1,40 @@
+#!/usr/bin/env bash
+
+# Copyright (C) 2011-2012 TMW2 Online
+# Author: Andrei Karas (4144)
+
+dir=`pwd`
+
+rm adler32
+gcc -lz adler32.c -o adler32
+
+mkdir files
+mkdir upload
+
+rm files/TMW2.zip
+cd ../../clientdata
+find -iregex ".+[.]\(xml\|png\|jpg\|tmx\|ogg\|txt\|po\|tsx\)" -exec touch --date=2015-01-01 {} \;
+find -iregex ".+[.]\(xml\|png\|jpg\|tmx\|ogg\|txt\|po\|tsx\)" -printf "%P\n" | zip -X -@ ../tools/update/files/TMW2.zip
+git log --pretty=oneline -n 1 | awk '{print $1}' >../tools/update/commit.txt
+
+cd $dir/files
+sum=`adler32 TMW2.zip | awk '{print $2}'`
+echo "TMW2.zip ${sum}" >resources2.txt
+
+echo '
+' >xml_header.txt
+touch xml_footer.txt
+touch xml_mods.txt
+
+echo " " >> xml_header.txt
+cp xml_header.txt resources.xml
+cat xml_footer.txt >>resources.xml
+cat xml_mods.txt >>resources.xml
+echo ' ' >>resources.xml
+
+cp TMW2.zip ../upload/
+cp resources2.txt ../upload/
+cp resources.xml ../upload/
+cp ../news.txt ../upload/
+
+
diff --git a/update/deploy.sh b/update/deploy.sh
new file mode 100755
index 0000000..1ae1be7
--- /dev/null
+++ b/update/deploy.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+# Deploy script
+echo "Deploying Bugfix-TMW2.zip"
+scp -4 upload/Bugfix-TMW2.zip jesusalva@updates.tmw2.org:/home/jesusalva/http/updates/
+scp -4 upload/resources.xml jesusalva@updates.tmw2.org:/home/jesusalva/http/updates/
+
diff --git a/update/musiccommit.txt b/update/musiccommit.txt
new file mode 100644
index 0000000..58369c4
--- /dev/null
+++ b/update/musiccommit.txt
@@ -0,0 +1 @@
+39e9aec960bd515ba0c458e39117edcc2f0d3079
diff --git a/update/musiccommit_old.txt b/update/musiccommit_old.txt
new file mode 100644
index 0000000..ee52604
--- /dev/null
+++ b/update/musiccommit_old.txt
@@ -0,0 +1 @@
+e65188eb11f07b09c20cfbc6e97ddd2275893a48
diff --git a/update/news.txt b/update/news.txt
new file mode 100644
index 0000000..2559f5f
--- /dev/null
+++ b/update/news.txt
@@ -0,0 +1,40 @@
+##0 Actual Release: ##1 17.2 Interlude in Red
+##0 Welcome to ##BTMW-2: Moubootaur Legends##b!
+
+##0 By playing you agree and abide to the ##1##BTerms of Service##b##0, available at:
+##1 [@@https://tmw2.org/legal|https://tmw2.org/legal@@]
+##2 Last ToS update: 2021-09-15
+
+##0 A Night in Red got delayed, and this is an interlude! Laura was only slightly
+##0 tweaked thus far. This interlude has more hints and tips, and fix bugs on Aurora
+##0 as well as rewording several stuff. ##BRealm of Drops##b skill was added: Now
+##0 you'll get warranted drops as you kill monsters, reducing the effects of PRNG.
+##0 ##BKreist Daily Quest##b was entirely reworked, and defeating a boss now plays
+##0 out a fanfare (but only some boss!) Boss Raid and Dream Towers events were
+##0 tweaked. You need less intelligence to learn magic. ##BConnor##b is more
+##0 generous giving coal. More drops for mobs above level 100. Iced Bottle now
+##0 heals a tiny bit. These Quality of Life changes makes an ##BInterlude in Red##b.
+
+##0 Ready to become a Moubootaur Legend now?
+##0 TMW2 Team
+
+##7 We want to express our gratitude here to ##BWoody##b, ##BSharli##b and ##BPovo##b, for sponsoring this server.
+##7 We would also want to kindly thank ##Bjak1##b and the GermanTMW Team for
+##7 their sponsorship and help.
+##7 And by last, our gratitude to every developer and contributor who made this
+##7 possible!
+
+----
+
+##0 [@@https://transifex.com/arctic-games/moubootaur-legends|Translators@@] are always welcome.
+##1 Our Staff will never ask for your password. You are the sole responsible for
+##1 its safety! You can contact a GM with ##B@request##b or calling one at #world.
+##0 (If you want, it's possible to skip music download under Settings > Audio)
+
+##0 Enjoy gaming, and leave feedback!
+
+##9 -- Your TMW2 Team
+##9 October 2022
+
+##0 You can check out this page for older entries:
+##9 [@@https://tmw2.org/news|https://tmw2.org/news@@]
diff --git a/update/pseudo_update.sh b/update/pseudo_update.sh
new file mode 100755
index 0000000..38678bf
--- /dev/null
+++ b/update/pseudo_update.sh
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+
+# Copyright (C) 2010-2012 TMW2 Online
+# Author: Andrei Karas (4144)
+
+dir=`pwd`
+
+previous=`cat commit.txt`
+rm upload/Bugfix-TMW2.zip
+
+cd ../../clientdata
+head=`git log --pretty=oneline -n 1 | awk '{print $1}'`
+u1=`echo ${previous} | cut -c 1-7`
+u2=`echo ${head} | cut -c 1-7`
+git diff --name-status ${previous} HEAD | awk '/^(A|M)\t/ {print $2}; /^(R...)\t/ {print $3}' | \
+grep -e "[.]\(xml\|png\|jpg\|tmx\|ogg\|txt\|po\|tsx\)" | sort | uniq | \
+xargs zip -X -9 -r ../tools/update/upload/Bugfix-TMW2.zip
+
+cd $dir/upload
+
+sum=`adler32 Bugfix-TMW2.zip | awk '{print $2}'`
+echo "Update ID: ${u1}..${u2}"
+echo "Checksum: ${sum}"
+
+echo "Bugfix-TMW2.zip ${sum}" >>resources2.txt
+cp ../files/xml_header.txt resources.xml
+echo " " >> resources.xml
+cat ../files/xml_footer.txt >>resources.xml
+cat ../files/xml_mods.txt >>resources.xml
+echo '' >>resources.xml
+
diff --git a/update/update.sh b/update/update.sh
new file mode 100755
index 0000000..5e02147
--- /dev/null
+++ b/update/update.sh
@@ -0,0 +1,42 @@
+#!/usr/bin/env bash
+
+# Copyright (C) 2010-2012 TMW2 Online
+# Author: Andrei Karas (4144)
+
+dir=`pwd`
+
+rm adler32
+gcc -lz adler32.c -o adler32
+
+mkdir files
+mkdir upload
+
+previous=`cat commit_old.txt`
+
+cd ../../clientdata
+head=`git log --pretty=oneline -n 1 | awk '{print $1}'`
+u1=`echo ${previous} | cut -c 1-7`
+u2=`echo ${head} | cut -c 1-7`
+git diff --name-status ${previous} HEAD | awk '/^(A|M)\t/ {print $2}; /^(R...)\t/ {print $3}' | \
+ grep -e "[.]\(xml\|png\|jpg\|tmx\|ogg\|txt\|po\|tsx\)" | sort | uniq | \
+ xargs zip -X -9 -r ../tools/update/files/TMW2-${u1}.zip
+
+cd $dir/files
+if [ -f TMW2-${u1}.zip ]; then
+ #mv ../commit.txt ../commit_old.txt
+ echo ${head} >../commit.txt
+ sum=`adler32 TMW2-${u1}.zip | awk '{print $2}'`
+ echo "TMW2-${u1}.zip ${sum}" >>resources2.txt
+ echo " " >> xml_header.txt
+ cp xml_header.txt resources.xml
+ cat xml_footer.txt >>resources.xml
+ cat xml_mods.txt >>resources.xml
+ echo '' >>resources.xml
+
+ cp TMW2-${u1}.zip ../upload/
+ cp resources2.txt ../upload/
+ cp resources.xml ../upload/
+ cp ../news.txt ../upload
+
+ echo "Update ID: ${u1}..${u2}"
+fi
diff --git a/update/update_music.sh b/update/update_music.sh
new file mode 100755
index 0000000..a99f5c6
--- /dev/null
+++ b/update/update_music.sh
@@ -0,0 +1,42 @@
+#!/usr/bin/env bash
+
+# Copyright (C) 2010-2012 Evol Online
+# Author: Andrei Karas (4144)
+
+dir=`pwd`
+
+rm adler32
+gcc -lz adler32.c -o adler32
+
+mkdir files
+mkdir upload
+
+previous=`cat musiccommit.txt`
+
+cd ../../music
+head=`git log --pretty=oneline -n 1 | awk '{print $1}'`
+u1=`echo ${previous} | cut -c 1-7`
+u2=`echo ${head} | cut -c 1-7`
+git log --name-status ${previous}..${head} \
+| awk '/^(A|M)\t/ {print $2}'
+ grep -e "[.]\(ogg\|mp3\)" | sort | uniq | \
+ xargs zip -X -9 -r ../../tools/update/files/music-${u1}..${u2}.zip
+#exit
+
+cd $dir/files
+if [ -f music-${u1}..${u2}.zip ]; then
+ mv ../muciscommit.txt ../muciscommit_old.txt
+ echo ${head} >../muciscommit.txt
+ sum=`adler32 music-${u1}..${u2}.zip | awk '{print $2}'`
+
+ echo " " >> xml_footer.txt
+ cp xml_header.txt resources.xml
+ cat xml_footer.txt >>resources.xml
+ cat xml_mods.txt >>resources.xml
+ echo '' >>resources.xml
+
+ cp music-${u1}..${u2}.zip ../upload/
+ cp resources2.txt ../upload/
+ cp resources.xml ../upload/
+ cp ../news.txt ../upload
+fi
diff --git a/web/all_news.bin b/web/all_news.bin
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/web/all_news.bin
@@ -0,0 +1 @@
+
diff --git a/web/backupcopy b/web/backupcopy
new file mode 100755
index 0000000..81601b0
--- /dev/null
+++ b/web/backupcopy
@@ -0,0 +1,129 @@
+#! /usr/bin/env python2.7
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2018 TMW-2
+# Author: Jesusalva
+
+import os
+
+defaultLang = "en"
+rootPath = "../../web/"
+
+langs=[]
+files={}
+originals={}
+
+# Populates the basic arrays
+def populate():
+ # Populate langs
+ o=open("langs.txt", "r")
+ for i in o:
+ langs.append(i.replace('\n',''))
+ o.close()
+
+ # Populate files
+ for i in os.listdir(rootPath):
+ if i.endswith(".php.en"):
+ print "Valid file: "+str(i)
+ files[i]={}
+ for x in langs:
+ files[i][x]={}
+
+ print str(files)
+# Write po files
+def writePoFile(nf,lg):
+ a=open("po/"+lg+".po", "a")
+ f=open(rootPath+nf, "r")
+
+ for line in f:
+ a.write("#, no-c-format\n")
+ a.write('msgid "%s"\n' % line.replace('\n', ''))
+ try:
+ if files[nf][lg][line] != line:
+ a.write('msgstr "%s"\n' % files[nf][lg][line].replace('\n',''))
+ else:
+ a.write('msgstr "%s"\n' % "")
+ except KeyError:
+ a.write('msgstr "%s"\n' % "")
+
+ a.write('\n')
+
+ a.close()
+
+# Creates/Loads stuff
+def genreadPoFile():
+ for a in langs:
+ print("Reading translations for "+a)
+ for i in files:
+ readPoFile(i,a)
+
+def generatePoFiles():
+ for a in langs:
+ print("Updating po file for "+a)
+ xrv=open("po/"+a+".po", "w")
+ # Print header
+ xrv.write('\
+# Copyright (C) 2018 TMW2\n\
+#\n\
+\n\
+msgid ""\n\
+msgstr ""\n\
+"Project-Id-Version: TMW2\\n"\n\
+"MIME-Version: 1.0\\n"\n\
+"Content-Type: text/plain; charset=UTF-8\\n"\n\
+"Content-Transfer-Encoding: 8bit\\n"\n\
+\n\
+')
+ xrv.close()
+
+ for i in files:
+ writePoFile(i,a)
+
+def generateLocal():
+ for a in langs:
+ print("generating local file for "+a)
+ for i in files:
+ writeLocal(i,a)
+
+# Reads Po Files
+def readPoFile(nf, lg):
+ try:
+ a=open("po/"+lg+".po", "r")
+ except:
+ a=open("po/"+lg+".po", "w")
+ a.close()
+ a=open("po/"+lg+".po", "r")
+
+ ctx=""
+ for line in a:
+ if "msgid " in line:
+ ctx=line.replace('\n','')
+ if "msgstr " in line and ctx != "":
+ if line != 'msgstr ""\n':
+ files[nf][lg][ctx]=line.replace('\n', '')
+ else:
+ files[nf][lg][ctx]=ctx
+ ctx=""
+ a.close()
+
+def writeLocal(nf, lg):
+ a=open(rootPath+nf[:-2]+lg, 'w')
+ b=open(rootPath+nf, 'r')
+
+ for line in b:
+ try:
+ a.write(files[nf][lg][line])
+ except KeyError:
+ a.write(line)
+
+ b.close()
+ a.close()
+
+# Mainframe: populate arrays
+populate()
+
+# Mainframe: handle PO files
+genreadPoFile()
+generatePoFiles()
+generateLocal()
+
diff --git a/web/deploy.sh b/web/deploy.sh
new file mode 100755
index 0000000..65c3bd2
--- /dev/null
+++ b/web/deploy.sh
@@ -0,0 +1,4 @@
+./news_to_html.py
+./newsfeed.py
+#cp news.html ../../web/
+cp feed.xml ../../web/tmw2.org/
diff --git a/web/feed.xml b/web/feed.xml
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/web/feed.xml
@@ -0,0 +1 @@
+
diff --git a/web/fetch.py b/web/fetch.py
new file mode 100755
index 0000000..c5fbeea
--- /dev/null
+++ b/web/fetch.py
@@ -0,0 +1,62 @@
+#! /usr/bin/env python2.7
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2018 TMW-2
+# Author: Jesusalva
+
+from transifex.api import TransifexAPI
+#import sys
+
+#if len(sys.argv) == 2:
+# if sys.argv[1] == "AllFilesVerified":
+# pass
+# else:
+# print("Rejected: Incorrect magic word.")
+# exit(1)
+#else:
+# print("Rejected: As this script is VULNERABLE TO CODE INJECTION, you cannot run this command without verifying all files at Transifex first.")
+# exit(1)
+print("\033[1mThis script is VULNERABLE TO CODE INJECTION. Git diff before deploying!\033[0m")
+
+project='moubootaur-legends'
+
+print("Loading user credentials (login.txt/password.txt) from lang/ folder...")
+
+# Load credentials from login.txt and password.txt
+login=open('../lang/login.txt', 'r')
+for i in login:
+ username=i.replace('\n', '').replace('\r', '')
+
+login.close()
+
+passw=open('../lang/password.txt', 'r')
+for i in passw:
+ password=i.replace('\n', '').replace('\r', '')
+
+passw.close()
+
+t=TransifexAPI(username, password, 'https://www.transifex.com')
+
+if (not t.ping):
+ print("ERROR: Ping failed, this may be due incorrect username/password in login.txt and password.txt. Ensure there is NO newline at the end of file.")
+ exit(1)
+
+if (not t.project_exists(project)):
+ print("ERROR: Invalid project name")
+ exit(1)
+
+# Load languages
+langs=[]
+vcx=open("langs.txt", "r")
+for i in vcx:
+ if i != "en":
+ langs.append(i.replace('\n', ''))
+vcx.close()
+
+# Fetch all translations and record them at in/
+for i in langs:
+ print("Fetching %s..." %(i))
+ t.get_translation(project, 'website', i, 'po/'+str(i)+'.po')
+
+print("All translations were retrieved.")
+print("Please sanitize files removing \"\\n\", or updatelang.py won't parse properly.")
diff --git a/web/gameinfo.sh b/web/gameinfo.sh
new file mode 100755
index 0000000..757ee96
--- /dev/null
+++ b/web/gameinfo.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+echo "Deploying news file..."
+scp -4 -P 22 feed.xml jesusalva@213.202.247.189:/home/jesusalva/http/
+scp -4 -P 22 ../update/news.txt jesusalva@213.202.247.189:/home/jesusalva/http/updates/
+
+echo "Begin - Gameinfo updater. User input required."
+cd ../../gameinfo-api
+
+cd serverdata
+git pull --rebase -X theirs
+cd ..
+
+cd clientdata
+git pull --rebase -X theirs
+cd ..
+
+git commit -a
+git push
+
+echo "Gameinfo updated! Mission complete!"
+
diff --git a/web/index.bin b/web/index.bin
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/web/index.bin
@@ -0,0 +1 @@
+
diff --git a/web/langs.txt b/web/langs.txt
new file mode 100644
index 0000000..c1cd357
--- /dev/null
+++ b/web/langs.txt
@@ -0,0 +1,5 @@
+pt-BR
+fr
+de
+es
+ru
diff --git a/web/legacy b/web/legacy
new file mode 100755
index 0000000..4629a06
--- /dev/null
+++ b/web/legacy
@@ -0,0 +1,51 @@
+#! /usr/bin/env python2.7
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2018 TMW-2
+# Author: Jesusalva
+
+import os
+import re
+
+defaultLang = "en"
+
+rootPath = "../../web/"
+
+filt = re.compile(".+[.]php", re.IGNORECASE)
+
+allFiles=[]
+langFiles = dict()
+langs = [] # Current languages
+
+# Read languages
+o=open("langs.txt", "r")
+for i in o:
+ langs.append(i.replace('\n',''))
+o.close()
+
+# Read PHP files, and create langFiles[i]["master"]
+for i in os.listdir(rootPath):
+ if i.endswith(".php"):
+ langFiles[i]={}
+ o=open(rootPath+'/'+i, 'r')
+ for line in o:
+ langFiles[i]["master"]=line
+ o.close()
+
+# Sort each language for stuff
+for a in langFiles:
+ for b in langs:
+ langFiles[a][b]=[]
+
+ cnt=0
+ pot=open("po/"+b+".po", "r")
+ for line in pot:
+ if "msgstr" in line:
+ abaco=str(line.replace('msgstr "','')[:-1])
+ if abaco != "":
+ langFiles[a][b].append(abaco)
+ else:
+ langFiles[a][b].append(langFiles[a][master][cnt])
+ cnt+=1
+
+ print str(langFiles[a][b])
diff --git a/web/news.html b/web/news.html
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/web/news.html
@@ -0,0 +1 @@
+
diff --git a/web/news_to_html.py b/web/news_to_html.py
new file mode 100755
index 0000000..4015a62
--- /dev/null
+++ b/web/news_to_html.py
@@ -0,0 +1,91 @@
+#! /usr/bin/env python
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2018 The Mana World 2
+# Author: Jonatas N. (Jesusalva)
+
+import datetime
+date=str(datetime.date.today())
+
+#date="2019-08-05"
+
+# Update the INDEX
+i1=open("index.bin", "r")
+i2=open("1index.bin", "w")
+for i in i1:
+ i2.write(i)
+i2.close()
+i1.close()
+i1=open("index.bin", "w")
+i2=open("1index.bin", "r")
+i1.write(date+'\n')
+for i in i2:
+ i1.write(i)
+i2.close()
+i1.close()
+i1=open("index.bin", "r")
+
+# Prepare to sort from newest to oldest
+dt=open("all_news.bin", "r")
+dc=open("backup.bin", "w")
+for i in dt:
+ dc.write(i)
+dc.close()
+dt.close()
+dt=open("all_news.bin", "w")
+dc=open("backup.bin", "r")
+
+# Read news and prepare header
+ns=open("../update/news.txt", "r")
+dt.write("\n\n\n")
+
+# Function to markup it, and strip new lines
+def markup(r):
+ r=r.replace('##0', ' ')
+ r=r.replace('##1', '')
+ r=r.replace('##2', '')
+ r=r.replace('##3', '')
+ r=r.replace('##4', '')
+ r=r.replace('##5', '')
+ r=r.replace('##6', '')
+ r=r.replace('##7', '')
+ r=r.replace('##8', '')
+ r=r.replace('##9', '')
+ r=r.replace('##B', '')
+ r=r.replace('##b', ' ')
+ r=r.replace('[@@', '')
+ r=r.replace('@@]', ' ')
+ return r
+def nn(r):
+ return r.replace('\n', '')
+
+# Write the news at all_news.bin, and then reload the backup. Finally, close the file
+for i in ns:
+ dt.write(markup(i)+' \n')
+dt.write(" ")
+for i in dc:
+ dt.write(i)
+
+ns.close()
+dt.close()
+dc.close()
+
+# Write the final HTML page
+wp=open("news.html", "w")
+dt=open("all_news.bin", "r")
+
+wp.write("Server News ")
+wp.write("Archive ")
+for i in i1:
+ wp.write(''+nn(i)+' ')
+wp.write('
')
+
+for line in dt:
+ wp.write(nn(line))
+wp.write("")
+
+wp.close()
+dt.close()
+i1.close()
+
diff --git a/web/newsfeed.py b/web/newsfeed.py
new file mode 100755
index 0000000..5840ca4
--- /dev/null
+++ b/web/newsfeed.py
@@ -0,0 +1,101 @@
+#! /usr/bin/env python
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2018 The Mana World 2
+# Author: Jonatas N. (Jesusalva)
+
+import datetime
+date=str(datetime.date.today())
+fulldate=str(datetime.datetime.utcnow().isoformat())
+
+#date="2020-01-31"
+#fulldate="2020-01-31T11:45:20.543750"
+
+# Open file (Dt) and export to "old". (Src) is current news
+dt=open("nf_main.xml", "r")
+old=[]
+for line in dt:
+ old.append(line)
+dt.close()
+dt=open("nf_main.xml", "w")
+src=open("../update/news.txt", "r")
+
+
+# Read news and prepare header
+ns=open("../update/news.txt", "r")
+
+
+# Function to markup it, and strip new lines
+def markup(r):
+ r=r.replace('##0', '')
+ r=r.replace('##1', '')
+ r=r.replace('##2', '')
+ r=r.replace('##3', '')
+ r=r.replace('##4', '')
+ r=r.replace('##5', '')
+ r=r.replace('##6', '')
+ r=r.replace('##7', '')
+ r=r.replace('##8', '')
+ r=r.replace('##9', '')
+ r=r.replace('##B', '')
+ r=r.replace('##b', ' ')
+ r=r.replace('[@@', '')
+ r=r.replace('@@]', ' ')
+ return r
+def nn(r):
+ return r.replace('\n', '')
+
+# Write the news, and close that file
+dt.write(' \n')
+for i in src:
+ if ('##0 Actual Release: ##1' in i):
+ dt.write('\
+ '+nn(i.replace('##0 Actual Release: ##1',''))+' \n\
+ \n\
+ '+fulldate+' \n\
+ tag:tmw2.org,'+date+' \n\
+ '+markup(i)+'
\n')
+dt.write("]]>\n ")
+
+dt.write('\n\n')
+
+for i in old:
+ dt.write(i)
+
+src.close()
+dt.close()
+
+# Open main file as (Dt) and read from (Ns)
+dt=open("feed.xml", "w")
+ns=open("nf_main.xml", "r")
+
+# headers
+dt.write('\
+\n\
+\n\
+ TMW2 \n\
+ \n\
+ \n\
+')
+dt.write(' '+fulldate+' \n')
+dt.write('\
+ https://tmw2.org \n\
+ \n\
+ TMW2 Project \n\
+ admin@tmw2.org \n\
+ \n\
+')
+
+# Write data
+for i in ns:
+ dt.write(i)
+
+# close
+dt.write('\n ')
+
+dt.close()
+ns.close()
+
diff --git a/web/nf_main.xml b/web/nf_main.xml
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/web/nf_main.xml
@@ -0,0 +1 @@
+
diff --git a/web/oldupdatelang.py b/web/oldupdatelang.py
new file mode 100755
index 0000000..e7a367a
--- /dev/null
+++ b/web/oldupdatelang.py
@@ -0,0 +1,164 @@
+#! /usr/bin/env python3
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2018 TMW-2
+# Author: Jesusalva
+
+import os
+
+defaultLang = "en"
+rootPath = "../../web/"
+
+langs=[]
+files={}
+originals={}
+tm=["","\n"]
+
+def invalidline(line, filx="none"):
+ return (line.startswith("include ") or
+ line.startswith("") or
+ "" in line or
+ "iframe" in line or
+ "header" in filx or
+ (line.startswith("
") and line.endswith("
\n")) or
+ (line.startswith("
") and line.endswith("\n")) or
+ (line.startswith("
") and line.endswith("\n")) or
+ (line.startswith("
") and line.endswith("\n")) or
+ line.replace('\n','') == "")
+
+# Populates the basic arrays
+def populate():
+ # Populate langs
+ o=open("langs.txt", "r")
+ for i in o:
+ langs.append(i.replace('\n',''))
+ o.close()
+
+ # Populate files
+ for i in os.listdir(rootPath):
+ if i.endswith(".php.en"):
+ print("Valid file: "+str(i))
+ files[i]={}
+ for x in langs:
+ files[i][x]={}
+
+ #print(str(files))
+
+
+# Creates/Loads stuff
+def genreadPoFile():
+ for a in langs:
+ print("Reading translations for "+a)
+ for i in files:
+ readPoFile(i,a)
+
+def generatePoFiles():
+ global tm
+ context=langs
+ context.append('en')
+ for a in langs:
+ print("Updating po file for "+a)
+ xrv=open("po/"+a+".po", "w")
+ # Prints header
+ xrv.write('\
+# Copyright (C) 2018 TMW2\n\
+#\n\
+\n\
+msgid ""\n\
+msgstr ""\n\
+"Project-Id-Version: TMW2\\n"\n\
+"MIME-Version: 1.0\\n"\n\
+"Content-Type: text/plain; charset=UTF-8\\n"\n\
+"Content-Transfer-Encoding: 8bit\\n"\n\
+\n\
+')
+ xrv.close()
+
+ tm=[""]
+ for i in files:
+ writePoFile(i,a)
+ context.remove('en')
+
+def generateLocal():
+ for a in langs:
+ print("generating local file for "+a)
+ for i in files:
+ writeLocal(i,a)
+
+
+
+# Write po files. TODO: WARNING: Some translations are getting overriden!
+def writePoFile(nf,lg):
+ # Translation Memory, to prevent duplicates
+ global tm
+
+ a=open("po/"+lg+".po", "a")
+ f=open(rootPath+nf, "r")
+
+ for line in f:
+ if line in tm or invalidline(line, nf):
+ continue
+ else:
+ tm.append(line)
+
+ a.write("#, no-c-format\n")
+ a.write('msgid "%s"\n' % line.replace('\n', '').replace('"','\\"'))
+ try:
+ if files[nf][lg][line.replace('\n', '')] != line.replace('\n', '') and files[nf][lg][line.replace('\n', '')] != "":
+ a.write('msgstr "%s"\n' % files[nf][lg][line.replace('\n', '')].replace('\n','').replace('"','\\"'))
+ else:
+ a.write('msgstr "%s"\n' % "")
+ except KeyError:
+ a.write('msgstr "%s"\n' % "")
+
+ a.write('\n')
+
+ a.close()
+
+
+# Reads Po Files
+def readPoFile(nf, lg):
+ try:
+ a=open("po/"+lg+".po", "r")
+ except:
+ a=open("po/"+lg+".po", "w")
+ a.close()
+ a=open("po/"+lg+".po", "r")
+
+ ctx=""
+ for line in a:
+ if "msgid " in line:
+ ctx=line.replace('"\n','').replace('\\"','"').replace('msgid "', "")
+ if "msgstr " in line and ctx != "":
+ if line != 'msgstr ""\n':
+ files[nf][lg][ctx]=line.replace('"\n', '').replace('\\"','"').replace('msgstr "', "")
+ else:
+ files[nf][lg][ctx]=ctx
+ ctx=""
+
+ a.close()
+
+def writeLocal(nf, lg):
+ a=open(rootPath+nf[:-2]+lg, 'w')
+ b=open(rootPath+nf, 'r')
+
+ for line in b:
+ try:
+ a.write(files[nf][lg][line.replace('\n', '')])
+ a.write('\n')
+ except KeyError:
+ a.write(line)
+
+ b.close()
+ a.close()
+
+# Mainframe: populate arrays
+populate()
+
+# Mainframe: handle PO files
+genreadPoFile()
+generatePoFiles()
+generateLocal()
+
diff --git a/web/original.po b/web/original.po
new file mode 100644
index 0000000..f27e280
--- /dev/null
+++ b/web/original.po
@@ -0,0 +1,170 @@
+# Copyright (C) 2018 TMW2
+#
+# Translators:
+# Jesusaves
, 2018
+msgid ""
+msgstr ""
+"Project-Id-Version: TMW2\n"
+"Last-Translator: Jesusaves , 2018\n"
+"Language-Team: Portuguese (Brazil) (https://www.transifex.com/akaras/teams/959/pt_BR/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: pt_BR\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#, no-c-format
+msgid "TMW-2: Monster World | Index Page"
+msgstr "TMW-2: Mundo dos Monstros | Página Inicial"
+
+#, no-c-format
+msgid " TMW-2: Monster Wars "
+msgstr "TMW-2: Guerra dos Monstros "
+
+#, no-c-format
+msgid ""
+" The Monster World (2) is an open source MMORPG project developed by "
+"the community and for the community. We believe that players should be "
+"rewarded for merit, and not for game time or luck. TMW2 runs in the ManaPlus"
+" client, also opensource, and our game have its code database licensed under"
+" GPL 3 and resources (GFX,SFX,etc) typically under CC-BY-SA, although GPL "
+"content is also present.
"
+msgstr ""
+"O Mundo dos Monstros (2) é um MMORPG de código aberto, desenvolvido pela "
+"comunidade e para comunidade. Acreditamos que os jogadores devem ser "
+"recompensados por mérito, e não por tempo de jogo ou sorte. TMW2 roda no "
+"cliente ManaPlus, também de código aberto, e nosso jogo tem seu código na "
+"licença GPL 3 e recursos (GFX, SFX, etc) tipicamente sobre CC-BY-SA, embora "
+"conteúdo GPL também esteja presente.
"
+
+#, no-c-format
+msgid ""
+" In this game, the player is given the opportunity to permanently "
+"change the world and its story, affecting all players forever, mainly by "
+"events and quests. The players are free to decide how they want the world to"
+" develop, and their actions change the storyline, besides always being able "
+"to join the Development Team.
"
+msgstr ""
+"Nesse jogo, o jogador recebe a oportunidade de permanentemente modificar "
+"o mundo e sua história, afetando todos os jogadores para sempre, "
+"principalmente por eventos e quests. Os jogadores são livres para decidir "
+"como querem que o mundo se desenvolva, e suas ações mudam a história, além "
+"de sempre serem capazes de se unir a Equipe de Desenvolvimento.
"
+
+#, no-c-format
+msgid "TMW-2: Monster World | Download Page"
+msgstr "TMW-2: Mundo dos Monstros | Página de Download"
+
+#, no-c-format
+msgid " Downloading the Game "
+msgstr " Baixando o Jogo "
+
+#, no-c-format
+msgid " Downloading the Client "
+msgstr " Baixando o Cliente "
+
+#, no-c-format
+msgid " "
+msgstr "
"
+
+#, no-c-format
+msgid ""
+" The first step is to download ManaPlus ,"
+msgstr ""
+" O primeiro passo é baixar o ManaPlus ,"
+
+#, no-c-format
+msgid ""
+" our game client. If you download from a repository, ensure the "
+"version is"
+msgstr ""
+"nosso cliente de jogo. Se você baixar de um repositório, certifique-se que a"
+" versão é"
+
+#, no-c-format
+msgid " 1.7.x or greater. Older versions may present bugs."
+msgstr "1.7.x ou superior. Versões mais antigas podem apresentar erros."
+
+#, no-c-format
+msgid "
"
+msgstr " "
+
+#, no-c-format
+msgid " Adding the Server "
+msgstr "Adicionando o Servidor "
+
+#, no-c-format
+msgid ""
+" Once the game opens, a server list will show. Select “Add”. Insert a"
+" server"
+msgstr ""
+"Quando o jogo abrir, uma lista de servidores será exibida. Clique em "
+"\"Adicionar\". Insira um"
+
+#, no-c-format
+msgid " name and description. The important data is as follow:"
+msgstr "nome de servidor e descrição. Os dados importantes são esses:"
+
+#, no-c-format
+msgid " "
+msgstr " "
+
+#, no-c-format
+msgid " Address: server.tmw2.org"
+msgstr " Endereço: server.tmw2.org"
+
+#, no-c-format
+msgid " Port: 6901"
+msgstr "Porta: 6901"
+
+#, no-c-format
+msgid " Type: Evol2"
+msgstr "Tipo: Evol2"
+
+#, no-c-format
+msgid "
"
+msgstr "
"
+
+#, no-c-format
+msgid ""
+" You'll need to register before playing the game. Please use a valid "
+"email"
+msgstr ""
+"Você precisará se registrar antes de jogar. Por favor utilize um endereço de"
+" email válido"
+
+#, no-c-format
+msgid ""
+" address as if we need to email your password back to you, we will do"
+" using"
+msgstr "já que se precisarmos lhe enviar a sua senha de volta, o faremos"
+
+#, no-c-format
+msgid " that email."
+msgstr "utilizando esse email."
+
+#, no-c-format
+msgid " Troubleshooting "
+msgstr "Solução de Problemas "
+
+#, no-c-format
+msgid ""
+" Game official release is 2018-03-02. If we are past this date and "
+"you're"
+msgstr ""
+"O lançamento oficial do jogo é 2018-03-02. Se essa data já passou e você"
+
+#, no-c-format
+msgid ""
+" getting a “servers unavailable” message, or is otherwise unable to "
+"connect,"
+msgstr ""
+"está recebendo uma mensagem de \"servidores indisponíveis\", ou não consegue"
+" se conectar,"
+
+#, no-c-format
+msgid " please contact us so we may fix this issue."
+msgstr ""
+"por favor entre em contato conosco para que possamos corrigir esse problema."
diff --git a/web/template.po b/web/template.po
new file mode 100644
index 0000000..16ce842
--- /dev/null
+++ b/web/template.po
@@ -0,0 +1,84 @@
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: 1.0\n"
+"Report-Msgid-Bugs-To: dev@tmw2.org\n"
+"POT-Creation-Date: 2020-02-28 05:43-0300\n"
+"PO-Revision-Date: 2020-02-28 05:43-0300\n"
+"Last-Translator: TMW2 Team \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgid "a string for testing"
+msgstr "uma linha para testar"
+
+msgid "$s Online Players"
+msgstr "$s Jogadores Online"
+
+msgid "Home"
+msgstr "Início"
+
+msgid "Community"
+msgstr "Comunidade"
+
+msgid "News"
+msgstr "Notícias"
+
+msgid "Legal Notice"
+msgstr "Nota Legal"
+
+msgid "Wiki & Info"
+msgstr "Wiki & Info"
+
+msgid "Get involved"
+msgstr "Se Involva"
+
+msgid "Making Art"
+msgstr "Pixelart"
+
+msgid "MapMaking"
+msgstr "Fazendo Mapas"
+
+msgid "Programming"
+msgstr "Programação"
+
+msgid "Game Content"
+msgstr "Conteúdo de Jogo"
+
+msgid "Music and SFX"
+msgstr "Música e SFX"
+
+msgid "Item Viewer"
+msgstr "Visualizador de Itens"
+
+msgid "Page not found"
+msgstr "Página não encontrada"
+
+msgid "Get in touch with the community!"
+msgstr "Entre em contato com a comunidade!"
+
+msgid ""
+"We have a great community that expands over many platforms with people from all over the world. If you have a question or you need help with something don't hesitate to ask us.\n"
+"Come visit us, and bring (or make) friends!"
+msgstr ""
+"We have a great community that expands over many platforms with people from all over the world. If you have a question or you need help with something don't hesitate to ask us.\n"
+"Come visit us, and bring (or make) friends!"
+
+msgid "Social Platforms"
+msgstr "Plataformas Sociais"
+
+msgid "Development"
+msgstr "Desenvolvimento"
+
+msgid "Support us"
+msgstr "Apoie-nos"
+
+msgid "You want to contribute in a way not listed above?"
+msgstr "Você quer contribuir de uma maneira não listada acima?"
+
+msgid "No problem!"
+msgstr "Sem problemas!"
+
+msgid "Reach out to us and tell us about it."
+msgstr "Entre em contato conosco e nos fale sobre."
diff --git a/web/updatelang.py b/web/updatelang.py
new file mode 100755
index 0000000..2eba12c
--- /dev/null
+++ b/web/updatelang.py
@@ -0,0 +1,190 @@
+#! /usr/bin/env python3
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2018~2020 TMW-2
+# Author: Jesusalva
+# Note: This will "force" completion to 100%. Use review system!
+
+import copy, datetime
+import polib, yaml
+
+defaultLang = "en"
+rootPath = "../../site/i18n/"
+
+langs=[]
+files={}
+originals={}
+tm={defaultLang: ["","\n"]}
+
+# Search for array[?]==search in an array of dicts
+# Returns the dictionary, or returns "ERROR"
+def dl_search(array, search):
+ for key in array.keys():
+ if array[key] == search:
+ return key
+ raise Exception("Key not found")
+
+# For headers
+def currentdate():
+ dt=datetime.datetime.now()
+
+ day=dt.timetuple()[2]
+ month=dt.timetuple()[1]
+ year=dt.timetuple()[0]
+
+ hour=dt.timetuple()[3]
+ minute=dt.timetuple()[4]
+ #second=5
+ #weekday=6
+ #yearday=7
+
+ return "%04d-%02d-%02d %02d:%02d-0300" % (year, month, day, hour, minute)
+
+
+def init():
+ global defaultLang, rootPath, langs, files, originals, tm
+ # Populate langs
+ o=open("langs.txt", "r")
+ for i in o:
+ langs.append(i.replace('\n',''))
+ o.close()
+
+ # Create the original file
+ f=open(rootPath+defaultLang+".yml", "r")
+ originals=yaml.load(f, Loader=yaml.FullLoader) # Dictionary
+ f.close()
+
+ # We don't care with metadata
+ del originals["language_meta"]
+
+ # Create each language's template
+ for i in langs:
+ files[str(i)]=copy.copy(originals)
+ tm[str(i)]=["","\n"]
+
+ """
+ # Do the reading for each language
+ for i in langs:
+ f=open(rootpath+i+".yml", "r")
+ files[str(i)]=yaml.load(f, Loader=yaml.FullLoader) # Dictionary
+ f.close()
+ """
+
+# [OK] Returns name from language code
+def lgname(lg):
+ if lg == "en":
+ return "English"
+ elif lg == "pt-BR":
+ return "Português (Brasil)"
+ elif lg == "de":
+ return "Deutsch"
+ elif lg == "fr":
+ return "François"
+ elif lg == "es":
+ return "Español"
+ elif lg == "ru":
+ return "Русский"
+ else:
+ return "Unknown %s" % lg
+
+# [OK] Entry
+def poentry(org, ttl, comments):
+ return polib.POEntry(msgid=org, msgstr=ttl, comment=comments)
+
+# Creates/Loads stuff
+def generatePoFiles():
+ global tm
+ context=copy.copy(langs)
+ context.append('en')
+ for lg in context:
+ print("Updating po file for "+lg)
+ po=polib.POFile()
+ po.metadata = {
+ 'Project-Id-Version': '1.0',
+ 'Report-Msgid-Bugs-To': 'dev@tmw2.org',
+ 'POT-Creation-Date': currentdate(),
+ 'PO-Revision-Date': currentdate(),
+ 'Last-Translator': 'TMW2 Team ',
+ 'MIME-Version': '1.0',
+ 'Content-Type': 'text/plain; charset=utf-8',
+ 'Content-Transfer-Encoding': '8bit',
+ }
+
+ for key, speech in originals.items():
+ # Translation Memory (no duplicates)
+ if speech in tm[lg]:
+ continue
+ tm[lg].append(speech)
+
+ # Add to po file
+ if lg == "en":
+ po.append(poentry(speech, "", key))
+ else:
+ po.append(poentry(speech, files[lg][key], key))
+
+ po.save("po/%s.po" % lg)
+ context.remove('en')
+
+
+
+
+
+# [OK] Reads Po Files
+def readPoFile(lg):
+ try:
+ po=polib.pofile("po/"+lg+".po")
+ except:
+ a=open("po/"+lg+".po", "w")
+ a.close()
+ print("%s.po does not exist, not reading" % lg)
+ return
+
+ print("%s Progress: %d%%" % (lgname(lg), po.percent_translated()))
+
+ for entry in po:
+ #print("%s = %s" % (entry.msgid, entry.msgstr))
+
+ try:
+ if entry.msgstr != "":
+ dest=dl_search(files[lg], entry.msgid)
+ #print("[+] %s" % dest)
+ files[lg][dest]=entry.msgstr
+ else:
+ dest=dl_search(files[lg], entry.msgid)
+ #print("%s.%s IS EMPTY" % (lg, dest))
+ try:
+ files[lg][dest]=originals[dest]
+ except:
+ print("%s - failed to obtain originals" % entry.msgid)
+ except:
+ print("%s - string was removed" % entry.msgstr)
+
+# [OK] Save the new YML file
+def writeLocal(lg):
+ f=open(rootPath+lg+".yml", 'w')
+ f.write("# THIS FILE WAS GENERATED AUTOMATICALLY\n#EDITING IT WILL HAVE NO EFFECT\n\n")
+ files[lg]["language_meta"]={"code": lg, "name": lgname(lg)}
+ yaml.dump(files[lg], f)
+ f.close()
+
+
+
+
+
+
+
+
+
+
+
+# Mainframe: setup
+init()
+
+# Save translations to their YML files
+for lg in langs:
+ readPoFile(lg)
+ writeLocal(lg)
+
+# Mainframe: handle PO files
+generatePoFiles()
+
diff --git a/wiki/elegen.py b/wiki/elegen.py
new file mode 100755
index 0000000..e8385f0
--- /dev/null
+++ b/wiki/elegen.py
@@ -0,0 +1,426 @@
+#! /usr/bin/env python3
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2010-2011 Evol Online
+# Copyright (C) 2018 TMW-2
+# Author: Andrei Karas (4144)
+# Author: Jesusalva
+
+import datetime
+
+wikib=open("EleMonsters.html", "w")
+wikib.write('EleGen File ')
+totalid=0
+bifs=False
+
+def printSeparator():
+ print("--------------------------------------------------------------------------------")
+
+def showHeader():
+ print("TMW2 Ele Generator")
+ print("This stuff analyzes and sorts mobs elementals, races and walkmasks.")
+ ##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():
+ #pass
+ #printSeparator()
+ print("Done.")
+
+
+
+Mobs0=[]
+Mobs1=[]
+Mobs2=[]
+Mobs3=[]
+Mobs4=[]
+Mobs5=[]
+Mobs6=[]
+Mobs7=[]
+Mobs8=[]
+Mobs9=[]
+MobsA=[]
+
+SysDrops=[]
+
+def fwalk(wmask):
+ if wmask == 'WATER':
+ return '%s ' % (wmask)
+ elif wmask == 'AIR':
+ return '%s ' % (wmask)
+ elif wmask == 'WALL':
+ return '%s ' % (wmask)
+ elif wmask == 'NORMAL' or wmask == 'DEFAULT':
+ return '%s ' % (wmask)
+ else:
+ print("Invalid walk mask: "+wmask)
+ exit(1)
+
+def WhatRace(rac):
+ rc=rac.race
+ if rc == 0:
+ return "Formless"
+ elif rc == 1:
+ return "Undead"
+ elif rc == 2:
+ return "Brute"
+ elif rc == 3:
+ return "Plant"
+ elif rc == 4:
+ return "Insect"
+ elif rc == 5:
+ return "Mineral"
+ elif rc == 6:
+ return "Evil"
+ elif rc == 7:
+ return "SemiHuman"
+ elif rc == 8:
+ return "Angel"
+ elif rc == 9:
+ return "Dragon"
+ elif rc == 10:
+ return "Player"
+ elif rc == 11:
+ return "Boss"
+ elif rc == 12:
+ return "NonBoss"
+ elif rc == 14:
+ return "NonSemiHuman"
+ elif rc == 15:
+ return "NonPlayer"
+ elif rc == 16:
+ return "SemiPlayer"
+ elif rc == 17:
+ return "NonSemiPlayer"
+ else:
+ print("ERROR, INVALID RACE ID: %d (ID: %s)" % (rc, rac.id))
+ exit(1)
+
+def WhatElem(rac):
+ rc=rac.elem
+ tl="ERROR"
+ cl="#F00"
+ if rc == 0:
+ tl,cl="Neutral","#000"
+ elif rc == 1:
+ tl,cl="Water","#00F"
+ elif rc == 2:
+ tl,cl="Earth","#7A0"
+ elif rc == 3:
+ tl,cl="Fire","#F00"
+ elif rc == 4:
+ tl,cl="Wind","#093"
+ elif rc == 5:
+ tl,cl="Nature","#040"
+ elif rc == 6:
+ tl,cl="Holy","#afa"
+ elif rc == 7:
+ tl,cl="Dark","#908"
+ elif rc == 8:
+ tl,cl="Ghost","#404"
+ elif rc == 9:
+ tl,cl="Undead","#440"
+ else:
+ print("ERROR, INVALID ELEM ID: %d (ID: %s)" % (rc, rac.id))
+ exit(1)
+ return "%s " % (cl, tl)
+
+class Mob:
+ def __init__(self):
+ # Basic
+ self.id="0"
+ #self.aegis="UnknownMonster" # SpriteName is not used anywhere, we are using its ID
+ self.name="Unknown Monster Name"
+ self.view="1"
+
+ # Defensive
+ self.mobpt="0" # Mob Points “Level”
+ self.hp="0"
+ self.xp="0"
+ self.jp="0"
+ self.st=""
+
+ # Offensive
+ self.atk="[0, 0]"
+ self.range="0"
+ self.move="0"
+ self.delay="0"
+ self.drops=[]
+
+ # New
+ self.race=-1
+ self.elem=-1
+ self.elel=-1
+ self.walk="NORMAL"
+
+def MobAlloc(ab):
+ try:
+ maab=int(ab.elem)
+ except:
+ maab=9901
+
+ if maab == 0:
+ Mobs0.append(ab)
+ elif maab == 1:
+ Mobs1.append(ab)
+ elif maab == 2:
+ Mobs2.append(ab)
+ elif maab == 3:
+ Mobs3.append(ab)
+ elif maab == 4:
+ Mobs4.append(ab)
+ elif maab == 5:
+ Mobs5.append(ab)
+ elif maab == 6:
+ Mobs6.append(ab)
+ elif maab == 7:
+ Mobs7.append(ab)
+ elif maab == 8:
+ Mobs8.append(ab)
+ elif maab == 9:
+ Mobs9.append(ab)
+ else:
+ MobsA.append(ab)
+
+def testMobs():
+ global totalid
+ print("Generating Elem-Mob Wiki...")
+ src=open("../../serverdata/db/re/mob_db.conf", "r")
+ wikib.write("EleGen Monster Database \n")
+ start=False
+ dropper=False
+ x=Mob() # Only for pyflakes2
+
+ for a in src:
+ if a == "{\n":
+ if start and not bifs and ((int(x.hp) <= 50) or (x.race == 3)):
+ start=False
+
+ if start:
+ MobAlloc(x)
+ else:
+ start=True
+ x=Mob()
+
+ if " Id:" in a:
+ x.id=stp(a)
+ totalid+=1
+ if x.id == "ID":
+ continue
+
+ if " Name:" in a:
+ x.name=stp(a)
+ elif " Hp:" in a:
+ x.hp=stp(a)
+ elif " Lv:" in a:
+ x.mobpt=stp(a)
+ elif " Exp:" in a:
+ x.xp=stp(a)
+ elif " JExp:" in a:
+ x.jp=stp(a)
+ elif " Attack:" in a:
+ x.atk=stp(a)
+ elif " AttackRange:" in a:
+ x.range=stp(a)
+ elif " MoveSpeed:" in a:
+ x.move=stp(a)
+ elif " ViewRange:" in a:
+ x.view=stp(a)
+ elif " AttackDelay:" in a:
+ x.delay=stp(a)
+ elif " Looter: true" in a:
+ x.st+="Lot ,"
+ elif " Assist: true" in a:
+ x.st+="Ass ,"
+ elif " Aggressive: true" in a:
+ x.st+="Agr ,"
+ elif " WalkMask:" in a:
+ x.walk=stp(a)
+ elif " Element:" in a:
+ tmp=stp(a)
+ tmp2=tmp.split(',')
+ try:
+ x.elem=int(tmp2[0])
+ x.elel=int(tmp2[1])
+ except:
+ print("Invalid Element for mob %s: %s" % (x.id, tmp))
+ exit(1)
+ elif " Race:" in a:
+ try:
+ x.race=int(stp(a))
+ except:
+ print("Invalid Race for mob %s: %s" % (x.id, a))
+ exit(1)
+ elif 'Drops: ' in a:
+ dropper=True
+ elif dropper and '}' in a:
+ dropper=False
+ elif dropper:
+ x.drops.append(stp(a).split(": "))
+ # Write last entry
+ MobAlloc(x)
+
+ writeMob()
+
+ wikib.write('\n\n|Mode|Desc|\n|----|----|\n')
+ wikib.write('|Lot|Looter|\n')
+ wikib.write('|Ass|Assist|\n')
+ wikib.write('|Agr|Aggressive|\n')
+
+ src.close()
+
+def stp(x):
+ return x.replace('\n', '').replace('|', '').replace('(int, defaults to ', '').replace(')', '').replace('basic experience', '').replace('"','').replace(" ","").replace("\t","").replace('(string', '').replace('Name: ','').replace('Element: ','').replace('Race: ','').replace('AttackDelay: ', '').replace('WalkMask: ','').replace('MoveSpeed: ', '').replace('AttackRange: ', '').replace('ViewRange: ','').replace('Attack: ','').replace('ViewRange: ','').replace('Hp: ','').replace('Id: ','').replace('Lv: ','').replace('view range','').replace('attack range','').replace('move speed','').replace('health','').replace('(int','').replace('attack delay','atk.').replace('(','').replace(')','').replace('WALK_','')
+
+
+def MonsterWrite(tbl):
+ # TODO: Check _mobs files to determine the usual monster density (a misc info to aid adding proper drop specs)
+ wikib.write("\n")
+ wikib.write("ID Name Mob Info Elegen Misc Info Rewards Drops \n")
+ for i in sorted(tbl, key=lambda tbl: int(tbl.mobpt)):
+ if i.id == 'ID':
+ continue
+ wikib.write(' ' +
+ i.id +""+
+ i.name +" "+
+ mb_core(i) +" "+
+ mb_eleg(i) +" "+
+ mbdt('misc', mb_rdmisc(i)) +" "+
+ mbdt('Exp\'s', mb_rdrw(i)) +" "+
+ mbdt('drops', mb_rddrop(i)) +" \n"
+ )
+ wikib.write("
\n")
+ wikib.write("\n(↑) Return to top \n\n")
+
+def writeMob():
+ wikib.write("Total monsters: %d " % totalid)
+ wikib.write(" ")
+
+ wikib.write("Neutral \n\n")
+ wikib.write("Total: %d Monsters \n\n" % len(Mobs0))
+ MonsterWrite(Mobs0)
+ wikib.write("Water \n\n")
+ wikib.write("Total: %d Monsters \n\n" % len(Mobs1))
+ MonsterWrite(Mobs1)
+ wikib.write("Earth \n\n")
+ wikib.write("Total: %d Monsters \n\n" % len(Mobs2))
+ MonsterWrite(Mobs2)
+ wikib.write("Fire \n\n")
+ wikib.write("Total: %d Monsters \n\n" % len(Mobs3))
+ MonsterWrite(Mobs3)
+ wikib.write("Wind \n\n")
+ wikib.write("Total: %d Monsters \n\n" % len(Mobs4))
+ MonsterWrite(Mobs4)
+ wikib.write("Nature \n\n")
+ wikib.write("Total: %d Monsters \n\n" % len(Mobs5))
+ MonsterWrite(Mobs5)
+ wikib.write("Holy \n\n")
+ wikib.write("Total: %d Monsters \n\n" % len(Mobs6))
+ MonsterWrite(Mobs6)
+ wikib.write("Dark \n\n")
+ wikib.write("Total: %d Monsters \n\n" % len(Mobs7))
+ MonsterWrite(Mobs7)
+ wikib.write("Ghost \n\n")
+ wikib.write("Total: %d Monsters \n\n" % len(Mobs8))
+ MonsterWrite(Mobs8)
+ wikib.write("Undead \n\n")
+ wikib.write("Total: %d Monsters \n\n" % len(Mobs9))
+ MonsterWrite(Mobs9)
+
+ wikib.write("Error \n\n")
+ wikib.write("Total: %d Monsters \n\n" % len(MobsA))
+ MonsterWrite(MobsA)
+
+
+def mbdt(summary, content):
+ return "%s " % (content)
+ return "\
+"+summary+" \
+"+content+" "
+
+def mb_core(mb):
+ buff=""
+ buff+="Lvl: %s \n" % (mb.mobpt)
+ buff+="HP: %s \n" % (mb.hp)
+ buff+="ATK: %s \n" % (mb.atk)
+ if mb.st != "":
+ buff+="Modes: %s " % (mb.st)
+ return buff
+
+def mb_eleg(mb):
+ buff=""
+ buff+="Race: %s \n" % (WhatRace(mb))
+ buff+="Walk: %s \n" % (fwalk(mb.walk))
+ buff+="Element: %s \n" % (WhatElem(mb))
+ return buff
+
+def mb_rdmisc(mb):
+ buff=""
+ if "agr" in mb.st.lower():
+ buff+="View Range: %s\n" % (mb.view)
+ buff+="Attack Range: %s\n" % (mb.range)
+ buff+="AtkDelay: %s ms\n" % (mb.delay)
+ buff+="Move speed: %s ms\n" % (mb.move)
+ buff+="Element Level: %d\n" % (mb.elel)
+ return buff
+
+def mb_rdrw(mb):
+ buff=""
+ try:
+ buff+="MobPoints: %d\n" % (int(mb.mobpt)*11/10)
+ except:
+ pass
+ buff+="%s\n" % (mb.xp)
+ buff+="%s\n" % (mb.jp)
+ return buff
+
+def mb_rddrop(mb):
+ buff=""
+ for ax in mb.drops:
+ # Ignore disabled drops
+ if ax[0].startswith("//"):
+ continue
+
+ # Write drop
+ try:
+ buff+=ax[0]+': ' + str(int(ax[1])/100.0) + ' %\n'
+ except IndexError:
+ print("Fatal: invalid %s mob with %s drops" % (mb.name, str(ax)))
+ exit(1)
+ except:
+ print("[Warning] %s incorrect drop: %s" % (mb.name, str(ax)))
+ buff+=ax[0]+': ' + ax[1] + ' ppm\n'
+
+ # Save to SysDrops
+ SysDrops.append([ax[0], ax[1], mb.name])
+
+ return buff
+
+
+
+showHeader()
+
+testMobs()
+print("Total monsters: %d" % totalid)
+
+wikib.write('')
+wikib.close()
+#print(str(SysDrops))
+
+showFooter()
+exit(0)
diff --git a/wiki/lanalysis.py b/wiki/lanalysis.py
new file mode 100755
index 0000000..bc2ffe2
--- /dev/null
+++ b/wiki/lanalysis.py
@@ -0,0 +1,394 @@
+#! /usr/bin/env python3
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2010-2011 Evol Online
+# Copyright (C) 2018 TMW-2
+# Author: Andrei Karas (4144)
+# Author: Jesusalva
+
+import datetime
+import sys
+
+wikib=open("EleMonsters.html", "w")
+wikib.write('EleGen File ')
+
+def printSeparator():
+ print("--------------------------------------------------------------------------------")
+
+def showHeader():
+ print("TMW2 Ele Generator")
+ print("This stuff analyzes and sorts mobs elementals, races and walkmasks.")
+ ##print "Evol client data validator."
+ print("Run at: " + datetime.datetime.now().isoformat())
+ print("Output is: EleMonsters.html")
+ ##print "https://gitlab.com/evol/evol-tools/blob/master/testxml/testxml.py"
+ printSeparator()
+
+def showFooter():
+ #pass
+ #printSeparator()
+ print("Done.")
+
+
+
+Mobs1=[]
+Mobs2=[]
+Mobs3=[]
+Mobs4=[]
+Mobs5=[]
+Mobs6=[]
+MobsA=[]
+
+SysDrops=[]
+
+def fwalk(wmask):
+ if wmask == 'WATER':
+ return '%s ' % (wmask)
+ elif wmask == 'AIR':
+ return '%s ' % (wmask)
+ elif wmask == 'WALL':
+ return '%s ' % (wmask)
+ elif wmask == 'NORMAL' or wmask == 'DEFAULT':
+ return '%s ' % (wmask)
+ else:
+ print("Invalid walk mask: "+wmask)
+ exit(1)
+
+def WhatRace(rac):
+ rc=rac.race
+ if rc == 0:
+ return "Formless"
+ elif rc == 1:
+ return "Undead"
+ elif rc == 2:
+ return "Brute"
+ elif rc == 3:
+ return "Plant"
+ elif rc == 4:
+ return "Insect"
+ elif rc == 5:
+ return "Fish"
+ elif rc == 6:
+ return "-"
+ elif rc == 7:
+ return "SemiHuman"
+ elif rc == 8:
+ return "Angel"
+ elif rc == 9:
+ return "Dragon"
+ elif rc == 10:
+ return "Player"
+ elif rc == 11:
+ return "Boss"
+ elif rc == 12:
+ return "NonBoss"
+ elif rc == 14:
+ return "NonSemiHuman"
+ elif rc == 15:
+ return "NonPlayer"
+ elif rc == 16:
+ return "SemiPlayer"
+ elif rc == 17:
+ return "NonSemiPlayer"
+ else:
+ print("ERROR, INVALID RACE ID: %d (ID: %s)" % (rc, rac.id))
+ exit(1)
+
+def WhatElem(rac):
+ rc=rac.elem
+ tl="ERROR"
+ cl="#F00"
+ if rc == 0:
+ tl,cl="Neutral","#000"
+ elif rc == 1:
+ tl,cl="Water","#00F"
+ elif rc == 2:
+ tl,cl="Earth","#7A0"
+ elif rc == 3:
+ tl,cl="Fire","#F00"
+ elif rc == 4:
+ tl,cl="Wind","#093"
+ elif rc == 5:
+ tl,cl="Nature","#040"
+ elif rc == 6:
+ tl,cl="Holy","#afa"
+ elif rc == 7:
+ tl,cl="Dark","#908"
+ elif rc == 8:
+ tl,cl="Ghost","#404"
+ elif rc == 9:
+ tl,cl="Undead","#440"
+ else:
+ print("ERROR, INVALID ELEM ID: %d (ID: %s)" % (rc, rac.id))
+ exit(1)
+ return "%s " % (cl, tl)
+
+class Mob:
+ def __init__(self):
+ # Basic
+ self.id="0"
+ #self.aegis="UnknownMonster" # SpriteName is not used anywhere, we are using its ID
+ self.name="Unknown Monster Name"
+ self.view="1"
+ self.boss=False
+
+ # Defensive
+ self.mobpt="0" # Mob Points “Level”
+ self.hp="0"
+ self.xp="Exp: 0"
+ self.jp="JExp: 0"
+ self.st=""
+
+ # Offensive
+ self.atk="[0, 0]"
+ self.range="0"
+ self.move="0"
+ self.delay="0"
+ self.drops=[]
+
+ # New
+ self.race=-1
+ self.elem=-1
+ self.elel=-1
+ self.walk="NORMAL"
+
+def MobAlloc(ab):
+ try:
+ maab=int(ab.mobpt)
+ except:
+ maab=9901
+
+ if maab <= 20:
+ Mobs1.append(ab)
+ elif maab <= 40:
+ Mobs2.append(ab)
+ elif maab <= 60:
+ Mobs3.append(ab)
+ elif maab <= 80:
+ Mobs4.append(ab)
+ elif maab <= 100:
+ Mobs5.append(ab)
+ elif maab <= 150:
+ Mobs6.append(ab)
+ else:
+ MobsA.append(ab)
+
+def testMobs():
+ print("Generating Elem-Mob Wiki...")
+ if len(sys.argv) >= 2:
+ src=open(sys.argv[1]+"/db/re/mob_db.conf", "r")
+ else:
+ src=open("../../server-data/db/re/mob_db.conf", "r")
+
+ wikib.write("EleGen Monster Database \n")
+ start=False
+ dropper=False
+ x=Mob() # Only for pyflakes2
+
+ for a in src:
+ if a == "{\n":
+ if start:
+ MobAlloc(x)
+ else:
+ start=True
+ x=Mob()
+
+ if " Id:" in a:
+ x.id=stp(a)
+ if x.id == "ID":
+ continue
+
+ if " Name:" in a:
+ x.name=stp(a)
+ elif " Hp:" in a:
+ x.hp=stp(a)
+ elif " Lv:" in a:
+ x.mobpt=stp(a)
+ elif " Exp:" in a:
+ x.xp=stp(a)
+ elif " JExp:" in a:
+ x.jp=stp(a)
+ elif " Attack:" in a:
+ x.atk=stp(a)
+ elif " AttackRange:" in a:
+ x.range=stp(a)
+ elif " MoveSpeed:" in a:
+ x.move=stp(a)
+ elif " ViewRange:" in a:
+ x.view=stp(a)
+ elif " AttackDelay:" in a:
+ x.delay=stp(a)
+ elif " Boss: true" in a:
+ x.boss=True
+ elif " Looter: true" in a:
+ x.st+="Lot ,"
+ elif " Assist: true" in a:
+ x.st+="Ass ,"
+ elif " Aggressive: true" in a:
+ x.st+="Agr ,"
+ elif " WalkMask:" in a:
+ x.walk=stp(a)
+ elif " Element:" in a:
+ tmp=stp(a)
+ tmp2=tmp.split(',')
+ try:
+ x.elem=int(tmp2[0])
+ x.elel=int(tmp2[1])
+ except:
+ print("Invalid Element for mob %s: %s" % (x.id, tmp))
+ exit(1)
+ elif " Race:" in a:
+ try:
+ x.race=int(stp(a))
+ except:
+ print("Invalid Race for mob %s: %s" % (x.id, a))
+ exit(1)
+ elif 'Drops: ' in a:
+ dropper=True
+ elif dropper and '}' in a:
+ dropper=False
+ elif dropper:
+ x.drops.append(stp(a).split(": "))
+ # Write last entry
+ MobAlloc(x)
+
+ writeMob()
+
+ wikib.write('\n\n|Mode|Desc|\n|----|----|\n')
+ wikib.write('|Lot|Looter|\n')
+ wikib.write('|Ass|Assist|\n')
+ wikib.write('|Agr|Aggressive|\n')
+
+ src.close()
+
+def stp(x):
+ return x.replace('\n', '').replace('|', '').replace('(int, defaults to ', '').replace(')', '').replace('basic experience', '').replace('"','').replace(" ","").replace("\t","").replace('(string', '').replace('Name: ','').replace('Element: ','').replace('Race: ','').replace('AttackDelay: ', '').replace('WalkMask: ','').replace('MoveSpeed: ', '').replace('AttackRange: ', '').replace('ViewRange: ','').replace('Attack: ','').replace('ViewRange: ','').replace('Hp: ','').replace('Id: ','').replace('Lv: ','').replace('view range','').replace('attack range','').replace('move speed','').replace('health','').replace('(int','').replace('attack delay','atk.').replace('(','').replace(')','').replace('WALK_','')
+
+
+def MonsterWrite(tbl):
+ # TODO: Check _mobs files to determine the usual monster density (a misc info to aid adding proper drop specs)
+ wikib.write("\n")
+ wikib.write("ID Name Mob Info Elegen Misc Info Rewards Drops \n")
+ for i in sorted(tbl, key=lambda tbl: int(tbl.mobpt)):
+ if i.id == 'ID':
+ continue
+ if i.boss:
+ i.name=""+i.name+" "
+ wikib.write(' ' +
+ i.id +""+
+ i.name +" "+
+ mb_core(i) +" "+
+ mb_eleg(i) +" "+
+ mbdt('misc', mb_rdmisc(i)) +" "+
+ mbdt('Exp\'s', mb_rdrw(i)) +" "+
+ mbdt('drops', mb_rddrop(i)) +" \n"
+ )
+ wikib.write("
\n")
+ wikib.write("\n(↑) Return to top \n\n")
+
+def writeMob():
+ wikib.write(" ")
+
+ wikib.write("Lv 0-20 \n\n")
+ MonsterWrite(Mobs1)
+ wikib.write("Lv 21-40 \n\n")
+ MonsterWrite(Mobs2)
+ wikib.write("Lv 41-60 \n\n")
+ MonsterWrite(Mobs3)
+ wikib.write("Lv 61-80 \n\n")
+ MonsterWrite(Mobs4)
+ wikib.write("Lv 81-100 \n\n")
+ MonsterWrite(Mobs5)
+ wikib.write("Lv 101-150 \n\n")
+ MonsterWrite(Mobs6)
+
+ wikib.write("Lv 101+ \n\n")
+ MonsterWrite(MobsA)
+
+
+def mbdt(summary, content):
+ return ""+summary+" "+content+" "
+ return "\
+"+summary+" \
+"+content+" "
+
+def mb_core(mb):
+ buff=""
+ buff+="Lvl: %s \n" % (mb.mobpt)
+ buff+="HP: %s \n" % (mb.hp)
+ buff+="ATK: %s \n" % (mb.atk)
+ if mb.st != "":
+ buff+="Modes: %s " % (mb.st)
+ return buff
+
+def mb_eleg(mb):
+ buff=""
+ buff+="Race: %s \n" % (WhatRace(mb))
+ buff+="Walk: %s \n" % (fwalk(mb.walk))
+ buff+="Element: %s \n" % (WhatElem(mb))
+ return buff
+
+def mb_rdmisc(mb):
+ buff=""
+ if "agr" in mb.st.lower():
+ buff+="View Range: %s\n" % (mb.view)
+ buff+="Attack Range: %s\n" % (mb.range)
+ buff+="AtkDelay: %s ms\n" % (mb.delay)
+ buff+="Move speed: %s ms\n" % (mb.move)
+ buff+="Element Level: %d\n" % (mb.elel)
+ return buff
+
+def mb_rdrw(mb):
+ buff=""
+ buff+="%s \n" % (mb.xp.replace(' ', ' '))
+ buff+="%s \n" % (mb.jp.replace(' ', ' '))
+ try:
+ buff+="MobPoints: %d\n" % (int(mb.mobpt)*11/10)
+ except:
+ pass
+ return buff
+
+def mb_rddrop(mb):
+ buff=""
+ for ax in mb.drops:
+ # Ignore disabled drops
+ if ax[0].startswith("//"):
+ continue
+
+ # Write drop
+ try:
+ buff+=ax[0]+': ' + str(int(ax[1])/100.0) + ' %\n'
+ except IndexError:
+ print("Fatal: invalid %s mob with %s drops" % (mb.name, str(ax)))
+ exit(1)
+ except:
+ print("[Warning] %s incorrect drop: %s" % (mb.name, str(ax)))
+ buff+=ax[0]+': ' + ax[1] + ' ppm\n'
+
+ # Save to SysDrops
+ SysDrops.append([ax[0], ax[1], mb.name])
+
+ return buff
+
+
+
+showHeader()
+
+testMobs()
+
+wikib.write(' ')
+wikib.write("Run at: " + datetime.datetime.now().isoformat())
+wikib.write('')
+wikib.close()
+#print(str(SysDrops))
+
+showFooter()
+exit(0)
diff --git a/wiki/redesign.py b/wiki/redesign.py
new file mode 100755
index 0000000..8e94c52
--- /dev/null
+++ b/wiki/redesign.py
@@ -0,0 +1,1050 @@
+#! /usr/bin/env python3
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2010-2011 Evol Online
+# Copyright (C) 2018 TMW-2
+# Author: Andrei Karas (4144)
+# Author: Jesusalva
+#
+# The use of the data
+
+import datetime
+import sys
+
+stgen=True
+aeros=False
+bifs=False
+skipCI=False
+
+wikib=open("EleMonsters.html", "w")
+wikib.write('EleGen File ')
+
+exp=[ 9,16,25,36,77,112,153,200,253,320,
+ 385,490,585,700,830,970,1120,1260,1420,1620,
+ 1860,1990,2240,2504,2950,3426,3934,4474,6889,7995,
+ 9174,10425,11748,13967,15775,17678,19677,21773,28543,34212,
+ 38065,42102,46323,53026,58419,64041,69892,75973,92468,115254,
+ 128692,142784,157528,178184,196300,215198,234879,255341,310188,365914,
+ 402508,442769,487051,535756,589342,648281,713112,784421,862867,949158,
+ 1044076,1148484,1263331,1389671,1528642,1681509,1849671,2034639,2238111,
+ 2461928,
+ 2708132,2978946,3276840,3604530,3964987,4361495,4797656,5277432,5805184,6095442,
+ 6400217,6720231,7056251,7409070,7779531,8168509,8576941,9005793,9456080,9928893,
+ 10425342,10946613,11493946,12068655,12672087,13305690,13970980,14669530,15403013,16173172,
+ 16981833,17830934,18722479,19658611,20641552,21673632,22757319,23895184,25089950,25591758,
+ 26103599,26625670,27158181,27701356,28255388,28820507,29396920,29984867,30584571,31196260,
+ 31820184,32456596,33105727,33767845,34443202,35132065,35834705,36551410,37282446,38028099,
+ 38788671,39564454,40355745,41162860,41986116,42825838,43682362,44556009,45447138,45901616,
+ 46360641,46824255,47292503,47765432,48243093,48725528,49212784,49704921,50201982,50704003,
+ 51211047,51723162,52240405,52762810,53290446,53823354,54361598,54905214,55454271,56008820,
+ 56568910,57134610,57705965,58283032,58865873,59454536,60049086,60649586,61256083,61868649,
+ 62487336,63112216,63743344,64380779,65024597,65674843,66331602,66994930,67664885,68341538,
+ 69024959,69715207,70412360,71116485,71827658,72545940,73271398,74004114,74744153,75491595,
+ 76246518,77008984,77779077,78556879,79342456,80135878,80937236,81746616,82564087,83389730,
+ 84223638,85065880,85916545,86775711,87643468,88519912,89405121,90299171,91202163,92114182,
+ 93035322,93965687,94905347,95854406,96812948,97781076,98758892,99746478,100743951,101751398,
+ 102768911,103796609,104834582,105882930,106941769,108011193,109091315,110182230,111284052,112396904,
+ 113520874,114656091,115802657,116960692,118130296,119311601,120504716,121709770,122926875,124156150,
+ 125397711,126651686,127918206,129197392,130489375,131794267,133112212,134443338,135787774,137145652,
+ 138517109,139902284,141301316,142714335,144141489,145582902,147038738,148509126,149994219,150894194,
+ 151799571,152710366,153626627,154548387,155475676,156408540,157346998,158291079,159240837,160196288,
+ 161157464,162124419,163097165,164075757,165060214,166050576,167046878,168049166,169057470,170071823,
+ 171092262,172118826,173151541,174190460,175235604,176287026,177344758,178408837,179479294,180556179,
+ 181639518,182729367,183825754,184928711,186038293,187154528,188277464,189407130,190543577,191686849,
+ 192836981,193994007,195157974,196328927,197506910,198691949,199884102,201083405,202289908,203503646,
+ 204724676,205953024,207188754,208431886,209682484,210940582,212206225,213479467,214760350,216048919,
+ 217345224,218649298,219961193,221280960,222608645,223944302,225287974,226639708,227999552,229367554,
+ 230743762,232128226,233520999,234922133,236331675,237749668,239176170,240611236,242054915,243507252,
+ 244968307,246438117,247916753,249404252,250900687,252406101,253920540,255444067,256976742,258518609,
+ 260069729,261630158,263199938,264779135,266367816,267966025,269573820,271191260,272818408,274455326,
+ 276102061,277758679,279425234,281101787,282788399,284485141,286192050,287909214,289636677,291374496,
+ 293122752,294881486,296650782,298430689,300221275,302022609,303834742,305657759,307491713,309336672,
+ 311192696,313059863,314938223,316827859,318728836,320641216,322565061,324500459,326447460,328406143,
+ 330376584,332358847,334353004,336359128,338377288,340407551,342449994,344504696,346571722,348651151,
+ 350743062,352847532,354964629,357094423,359236990,361392422,363560778,365742141,367936599,370144221,
+ 372365091,374599279,376846874,379107955,381382611,383670915,385972949,388288794,390618537,392962257,
+ 395320037,397691969,400078129,402478601,404893475,407322841,409766782,412225383,414698739,417186939,
+ 419690069,422208209,424741456,427289911,429853651,432432776,435027370,437637543,440263366,442024421,
+ 443792521,445567690,447349963,449139360,450935926,452739673,454550638,456368839,458194326,460027115,
+ 461867221,463714693,465569558,467431848,469301579,471178794,473063508,474955766,476855601,478763022,
+ 480678083,482600803,484531205,486469328,488415217,490368880,492330355,494299675,496276878,498261990,
+ 500255043,502256068,504265102,506282171,508307305,510340540,512381907,514431439,516489176,518555144,
+ 520629376,522711891,524802748,526901963,529009581,531125618,533250127,535383137,537524678,539674777,
+ 541833486,544000830,546176836,548361554,550555012,552757234,554968274,557188159,559416915,561654583,
+ 563901210,566156820,568421455,570695142,572977924,575269847,577570928,579881219,582200741,584529542,
+ 586867672,589215145,591572009,593938303,596314063,598699319,601094122,603498497,605912494,608336152,
+ 610769496,613212575,615665434,618128102,620600617,623083026,625575357,628077658,630589971,633112332,
+ 635644789,638187373,640740134,643303101,645876314,648459827,651053675,653657891,656272528,658897623,
+ 661533224,664179368,666836088,669503437,672181451,674870184,677569669,680279959,683001081,685733085,
+ 688476023,691229930,693994859,696770843,699557927,702356165,705165600,707986270,710818214,713661496,
+ 716516145,719382218,722259755,725148792,728049398,730961596,733885443,736820993,739768279,742727359,
+ 745698276,748681079,751675801,754682510,757701238,760732051,763774987,766830096,769897426,772977027,
+ 776068941,779173220,782289911,785419075,788560754,791714999,794881861,798061393,801253640,804458666,
+ 807676501,810907214,814150847,817407448,820677081,823959788,827255638,830564665,833886935,837222492
+ ]
+
+def printSeparator():
+ print("--------------------------------------------------------------------------------")
+
+def showHeader():
+ global stgen, aeros, bifs, skipCI
+ print("TMW2 Ele Generator")
+ print("Run at: " + datetime.datetime.now().isoformat())
+ print("Usage: ./redesign.py [default|aeros|none|update|all] []")
+ if len(sys.argv) >= 2:
+ if sys.argv[1] == "default":
+ stgen=True
+ aeros=False
+ bifs=False
+ skipCI=False
+ elif sys.argv[1] == "aeros":
+ stgen=False
+ aeros=True
+ bifs=False
+ skipCI=False
+ elif sys.argv[1] == "none":
+ stgen=False
+ aeros=False
+ bifs=False
+ skipCI=False
+ elif sys.argv[1] == "update":
+ stgen=True
+ aeros=False
+ bifs=False
+ skipCI=True
+ elif sys.argv[1] == "all":
+ stgen=True
+ aeros=True
+ bifs=True
+ skipCI=False
+ else:
+ exit(1)
+ print("This stuff analyzes and sorts monsters and then create base stats for Moubootaur Legends.")
+ print("Drops aren't calculated or taken in account, TWEAK AS NEEDED")
+ printSeparator()
+ print("Output is: EleMonsters.html")
+
+def showFooter():
+ #pass
+ #printSeparator()
+ print("Done.")
+
+
+
+Mobs1=[]
+Mobs2=[]
+Mobs3=[]
+Mobs4=[]
+Mobs5=[]
+Mobs6=[]
+MobsA=[]
+
+# [N, W, E, F, W, -, H, H, G, -]
+Ele=[0, 0, 0, 0, 0, 0, 0, 0, 0]
+
+# This is for Aeros
+Plants=[]
+Level50=[]
+Level100=[]
+Aggressive=[]
+Assistant=[]
+Looter=[]
+Boss=[]
+
+SysDrops=[]
+
+def fwalk(wmask):
+ if wmask == 'WATER':
+ return '%s ' % (wmask)
+ elif wmask == 'AIR':
+ return '%s ' % (wmask)
+ elif wmask == 'WALL':
+ return '%s ' % (wmask)
+ elif wmask == 'NORMAL' or wmask == 'DEFAULT':
+ return '%s ' % (wmask)
+ else:
+ print("Invalid walk mask: "+wmask)
+ exit(1)
+
+def WhatRace(rac):
+ rc=rac.race
+ if rc == 0:
+ return "Formless"
+ elif rc == 1:
+ return "Undead"
+ elif rc == 2:
+ return "Brute"
+ elif rc == 3:
+ return "Plant"
+ elif rc == 4:
+ return "Insect"
+ elif rc == 5:
+ return "Mineral"
+ elif rc == 6:
+ return "-"
+ elif rc == 7:
+ return "SemiHuman"
+ elif rc == 8:
+ return "Angel"
+ elif rc == 9:
+ return "Dragon"
+ elif rc == 10:
+ return "Player"
+ elif rc == 11:
+ return "Boss"
+ elif rc == 12:
+ return "NonBoss"
+ elif rc == 14:
+ return "NonSemiHuman"
+ elif rc == 15:
+ return "NonPlayer"
+ elif rc == 16:
+ return "SemiPlayer"
+ elif rc == 17:
+ return "NonSemiPlayer"
+ else:
+ print("ERROR, INVALID RACE ID: %d (ID: %s)" % (rc, rac.id))
+ exit(1)
+
+def WhatElem(rac):
+ rc=rac.elem
+ tl="ERROR"
+ cl="#F00"
+ if rc == 0:
+ tl,cl="Neutral","#000"
+ elif rc == 1:
+ tl,cl="Water","#00F"
+ elif rc == 2:
+ tl,cl="Earth","#7A0"
+ elif rc == 3:
+ tl,cl="Fire","#F00"
+ elif rc == 4:
+ tl,cl="Wind","#093"
+ elif rc == 5:
+ tl,cl="Nature","#040"
+ elif rc == 6:
+ tl,cl="Holy","#afa"
+ elif rc == 7:
+ tl,cl="Dark","#908"
+ elif rc == 8:
+ tl,cl="Ghost","#404"
+ elif rc == 9:
+ tl,cl="Undead","#440"
+ else:
+ print("ERROR, INVALID ELEM ID: %d (ID: %s)" % (rc, rac.id))
+ exit(1)
+ Ele[rc]+=1
+ return "%s " % (cl, tl)
+
+class Mob:
+ def __init__(self):
+ # Basic
+ self.id="0"
+ #self.aegis="UnknownMonster" # SpriteName is not used anywhere, we are using its ID
+ self.name="Unknown Monster Name"
+ self.view="1"
+ self.boss=False
+ self.plant=False
+
+ # Defensive
+ self.mobpt="0" # Mob Points “Level”
+ self.hp="0"
+ self.xp="Exp: 0"
+ self.jp="JExp: 0"
+ self.st=""
+ self.dfn=0
+ self.mdf=0
+
+ # Offensive
+ self.atk="[0, 0]"
+ self.range="1"
+ self.move="0"
+ self.delay="0"
+ self.drops=[]
+
+ # New
+ self.race=-1
+ self.elem=-1
+ self.elel=-1
+ self.walk="NORMAL"
+
+ # Stats
+ self.str='0'
+ self.agi='0'
+ self.vit='0'
+ self.int='0'
+ self.dex='0'
+ self.luk='0'
+
+def MobAlloc(ab):
+ try:
+ maab=int(ab.mobpt)
+ except:
+ maab=9901
+
+ if maab <= 20 or skipCI:
+ Mobs1.append(ab)
+ elif maab <= 40:
+ Mobs2.append(ab)
+ elif maab <= 60:
+ Mobs3.append(ab)
+ elif maab <= 80:
+ Mobs4.append(ab)
+ elif maab <= 100:
+ Mobs5.append(ab)
+ elif maab <= 150:
+ Mobs6.append(ab)
+ else:
+ MobsA.append(ab)
+
+ # Aeros allocation
+ """Plants=[]
+ Level50=[]
+ Level100=[]
+ Aggressive=[]
+ Assistant=[]
+ Looter=[]
+ Boss=[]"""
+ normie=True
+ if ab.plant:
+ Plants.append(ab.id)
+ normie=False
+ if ab.boss:
+ Boss.append(ab.id)
+ normie=False
+ if normie:
+ if "Agr" in ab.st:
+ Aggressive.append(ab.id)
+ normie=False
+ if "Lot" in ab.st:
+ Looter.append(ab.id)
+ normie=False
+ if "Ass" in ab.st:
+ Assistant.append(ab.id)
+ normie=False
+ if normie:
+ if maab <= 55:
+ Level50.append(ab.id)
+ else:
+ Level100.append(ab.id)
+
+def testMobs():
+ print("Generating Elem-Mob Wiki...")
+ if len(sys.argv) >= 3:
+ src=open(sys.argv[2]+"/db/re/mob_db.conf", "r", encoding="utf-8")
+ else:
+ src=open("../../serverdata/db/re/mob_db.conf", "r", encoding="utf-8")
+
+ wikib.write("EleGen Monster Database \n")
+ start=False
+ dropper=False
+ skip=0
+ x=Mob() # Only for pyflakes2
+
+ for a2 in src:
+ a=a2.replace(' ', '\t');
+ if a == "{\n":
+ if skip:
+ skip=0
+ if start:
+ MobAlloc(x)
+ else:
+ start=True
+ x=Mob()
+
+ if skip:
+ start=False
+ x=Mob()
+ continue
+ if " Id:" in a:
+ x.id=stp(a)
+ if x.id == "ID":
+ continue
+
+ if " Name:" in a:
+ x.name=stp(a)
+ elif " Hp:" in a:
+ x.hp=stp(a)
+ elif " Lv:" in a:
+ x.mobpt=stp(a)
+ elif " Exp:" in a:
+ x.xp=stp(a)
+ elif " JExp:" in a:
+ x.jp=stp(a)
+ elif " Attack:" in a:
+ x.atk=stp(a)
+ elif " AttackRange:" in a:
+ x.range=stp(a)
+ elif " MoveSpeed:" in a:
+ x.move=stp(a)
+ elif " ViewRange:" in a:
+ x.view=stp(a)
+ elif " AttackDelay:" in a:
+ x.delay=stp(a)
+
+ elif " Def:" in a:
+ x.dfn=stp(a)
+ elif " Mdef:" in a:
+ x.mdf=stp(a)
+
+ elif " Str:" in a:
+ x.str=stp(a)
+ elif " Agi:" in a:
+ x.agi=stp(a)
+ elif " Vit:" in a:
+ x.vit=stp(a)
+ elif " Int:" in a:
+ x.int=stp(a)
+ elif " Dex:" in a:
+ x.dex=stp(a)
+ elif " Luk:" in a:
+ x.luk=stp(a)
+
+
+ elif " Boss: true" in a:
+ x.boss=True
+ elif " Plant: true" in a:
+ x.plant=True
+ elif " Looter: true" in a:
+ x.st+="Lot ,"
+ elif " Assist: true" in a:
+ x.st+="Ass ,"
+ elif " Aggressive: true" in a:
+ x.st+="Agr ,"
+ elif " WalkMask:" in a:
+ x.walk=stp(a)
+ elif " Element:" in a:
+ tmp=stp(a)
+ tmp2=tmp.split(',')
+ try:
+ x.elem=int(tmp2[0])
+ x.elel=int(tmp2[1])
+ except:
+ print("Invalid Element for mob %s: %s" % (x.id, tmp))
+ exit(1)
+ elif " Race:" in a:
+ try:
+ x.race=int(stp(a))
+ except:
+ print("Invalid Race for mob %s: %s" % (x.id, a))
+ exit(1)
+ elif 'Drops: ' in a:
+ dropper=True
+ elif dropper and '}' in a:
+ dropper=False
+ elif dropper:
+ x.drops.append(stp(a).split(": "))
+ elif "Plant: true" in a:
+ skip=1
+ # Write last entry
+ MobAlloc(x)
+
+ writeMob()
+
+ wikib.write('\n\n|Mode|Desc|\n|----|----|\n')
+ wikib.write('|Lot|Looter|\n')
+ wikib.write('|Ass|Assist|\n')
+ wikib.write('|Agr|Aggressive|\n')
+
+ src.close()
+
+def stp(x):
+ return x.replace('\n', '').replace('|', '').replace('(int, defaults to ', '').replace(')', '').replace('basic experience', '').replace('"','').replace(" ","").replace("\t","").replace('(string', '').replace('Name: ','').replace('Element: ','').replace('Race: ','').replace('AttackDelay: ', '').replace('WalkMask: ','').replace('MoveSpeed: ', '').replace('AttackRange: ', '').replace('ViewRange: ','').replace('Attack: ','').replace('ViewRange: ','').replace('Hp: ','').replace('Id: ','').replace('Lv: ','').replace('view range','').replace('attack range','').replace('move speed','').replace('health','').replace('(int','').replace('attack delay','atk.').replace('(','').replace(')','').replace('WALK_','').replace('Def: ','').replace('Mdef: ','')
+
+
+def MonsterWrite(tbl):
+ global skipCI
+ # TODO: Check _mobs files to determine the usual monster density (a misc info to aid adding proper drop specs)
+ wikib.write("\n")
+ if stgen:
+ wikib.write("ID Name Mob Info Stgen Elegen Misc Info Rewards Stats Drops \n")
+ else:
+ wikib.write("ID Name Mob Info Elegen Misc Info Rewards Stats Drops \n")
+
+ if not skipCI:
+ tbl=sorted(tbl, key=lambda tbl: int(tbl.mobpt))
+
+ for i in tbl:
+ if i.id == 'ID':
+ continue
+
+ # Special skips for REDESIGN
+ #if (int(i.id) < 1187):
+ # continue
+ if not bifs and ((int(i.hp) <= 50) or (i.race == 3)):
+ continue
+
+ if i.boss:
+ i.name=""+i.name+" "
+ if stgen:
+ wikib.write(' ' +
+ i.id +""+
+ i.name +" "+
+ mb_core(i) +" "+
+ mbdt('advise',mb_stgen(i)) +" "+
+ mb_eleg(i) +" "+
+ mbdt('misc', mb_rdmisc(i)) +" "+
+ mbdt('Exp\'s', mb_rdrw(i)) +" "+
+ mbdt('stats', mb_rdstat(i)) +" "+
+ mbdt('drops', mb_rddrop(i)) +" \n"
+ )
+ else:
+ wikib.write(' ' +
+ i.id +""+
+ i.name +" "+
+ mb_core(i) +" "+
+ mb_eleg(i) +" "+
+ mbdt('misc', mb_rdmisc(i)) +" "+
+ mbdt('Exp\'s', mb_rdrw(i)) +" "+
+ mbdt('stats', mb_rdstat(i)) +" "+
+ mbdt('drops', mb_rddrop(i)) +" \n"
+ )
+ wikib.write("
\n")
+ wikib.write("Total: %02d Monsters \n" % len(tbl))
+ wikib.write("\n(↑) Return to top \n\n")
+
+def writeMob():
+ wikib.write(" ")
+
+ wikib.write("Lv 0-20 \n\n")
+ MonsterWrite(Mobs1)
+ wikib.write("Lv 21-40 \n\n")
+ MonsterWrite(Mobs2)
+ wikib.write("Lv 41-60 \n\n")
+ MonsterWrite(Mobs3)
+ wikib.write("Lv 61-80 \n\n")
+ MonsterWrite(Mobs4)
+ wikib.write("Lv 81-100 \n\n")
+ MonsterWrite(Mobs5)
+ wikib.write("Lv 101-150 \n\n")
+ MonsterWrite(Mobs6)
+
+ wikib.write("Lv 101+ \n\n")
+ MonsterWrite(MobsA)
+
+
+def mbdt(summary, content):
+ return ""+summary+" "+content+" "
+ return "\
+"+summary+" \
+"+content+" "
+
+def mb_core(mb):
+ buff=""
+ buff+="Lvl: %s \n" % (mb.mobpt)
+ buff+="HP: %s \n" % (mb.hp)
+ buff+="ATK: %s \n" % (mb.atk)
+ buff+="DEF: %s/%s \n" % (mb.dfn, mb.mdf)
+ if mb.st != "":
+ buff+="Modes: %s " % (mb.st)
+ return buff
+
+def mb_eleg(mb):
+ buff=""
+ buff+="Race: %s \n" % (WhatRace(mb))
+ buff+="Walk: %s \n" % (fwalk(mb.walk))
+ buff+="Element: %s \n" % (WhatElem(mb))
+ return buff
+
+############################################################
+def mb_stgen(mb):
+ lv=int(mb.mobpt)
+ st=int(mb.str.replace(' ', '').replace('Str:',''))
+ #ag=int(mb.agi.replace(' ', '').replace('Agi:',''))
+ vi=int(mb.vit.replace(' ', '').replace('Vit:',''))
+ #it=int(mb.int.replace(' ', '').replace('Int:',''))
+ #dx=int(mb.dex.replace(' ', '').replace('Dex:',''))
+ #lk=int(mb.luk.replace(' ', '').replace('Luk:',''))
+
+ # Attack Range vs Attack Delay
+ ar=int(mb.range)
+ ad=int(mb.delay)
+ mv=int(mb.move)
+ magr=False
+ mass=False
+ if ('Agr' in mb.st):
+ magr=True
+ if ('Ass' in mb.st):
+ mass=True
+
+ if (not ar):
+ ar=1
+
+ # Fórmula do dano:
+ # Tal que level 10 cause 150 dano
+ # Tal que level 50 cause 1000 dano
+ # Tal que a fórmula seja exponencial
+ """
+ lhp=lv**1.5+lv*(vi/100)+lv*15
+ hhp=lv**1.8+lv*(vi/100)+lv*15
+ # Casos especiais
+ if mb.boss:
+ lhp*=1.6
+ hhp*=4.2
+ if "slime" in mb.name.lower():
+ lhp*=0.4
+ hhp*=0.4
+ if ar > 1:
+ lhp-=ar**2
+ hhp-=ar**2
+
+ # Fórmula da HPTable: 400+(x*50)
+ # MASTERED
+ lat=(lv*40+lv**1.2+lv*(st/100))/ar
+ hat=(lv*40+lv**1.5+lv*(st/100))/ar
+ # Casos especiais
+ if mb.boss:
+ lat*=1.2
+ hat*=1.4
+ if "slime" in mb.name.lower():
+ lat*=0.3
+ hat*=0.3
+ # Attack is DPS. So we must adjust to be a damage per second
+ lat*=(ad/1872)
+ hat*=(ad/1872)
+ # Formula is not reliable
+ lat/=2
+ hat/=2
+ # Consider Aggressive and Assist mobs
+ if magr:
+ lat*=0.8
+ hat*=0.8
+ if mass:
+ lat*=0.9
+ hat*=0.9
+
+ # Experience
+ lxp=(lv**1.2+lat+(lhp/8)) / max(1,(12-(lv/10)))
+ hxp=(lv**1.6+hat+(hhp/8)) / max(1,(15-(lv/10)))
+ # Casos especiais
+ if mb.boss:
+ lxp*=4.2
+ hxp*=4.7
+ """
+
+ # Over100 Special Formula
+ OVER100=0
+ if lv > 100:
+ OVER100=lv-100
+ lv=100+OVER100*0.25
+
+ # Fórmula da HPTable: 400+(x*50)
+ # MASTERED
+ lat=(lv*40+lv**1.2+lv*(st/100))
+ hat=(lv*40+lv**1.5+lv*(st/100))
+ if (ar > 1):
+ lat*=max(0.5, 1.0-((ar-1)/10.0))
+ hat*=max(0.5, 1.0-((ar-1)/10.0))
+
+ # Casos especiais
+ if mb.boss:
+ lat*=1.2
+ hat*=1.4
+ if "slime" in mb.name.lower():
+ lat*=0.3
+ hat*=0.3
+ # Attack is DPS. So we must adjust to be a damage per second
+ lat*=(ad/1872)
+ hat*=(ad/1872)
+ # Formula is not reliable
+ lat*=0.55
+ hat*=0.55
+ lat*=max(0.5, 1.0-(lv/10.0))
+ hat*=max(0.5, 1.0-(lv/10.0))
+ # Consider Aggressive and Assist mobs
+ if magr:
+ lat*=0.78
+ hat*=0.78
+ if mass:
+ lat*=0.89
+ hat*=0.89
+
+
+ # Over100 Special Formula
+ if OVER100:
+ lv=100+OVER100*2
+
+ # HP: Each 10 levels give you 100 extra weapon damage
+ # I expect from 6 to 14 hits to kill
+ # You deliver in average two hits per second
+ # Mobs deliver one hit each four seconds (and that's 1hand advantage)
+ lhp=lv*20*6+lv*(vi/100)
+ hhp=lv*20*14+lv*(vi/100)
+ if mb.boss:
+ lhp*=1.4
+ hhp*=1.8
+ if "slime" in mb.name.lower():
+ lhp*=0.6
+ hhp*=0.6
+ if ar > 1 and not OVER100:
+ lhp*=0.9
+ hhp*=0.9
+
+ # Experience is way too non-linear and I prefer to do it with reference formula
+ # like I was doing before
+ # But let's use a formula based on mobs-to-lvl-up where you must kill 1580 lv 60 mobs
+ # to raise from lv 60 (you can kill lv 50 mobs easier - but then I expect 3700 kills)
+ # mobs to kill to raise level
+
+ # This is the current mob-lvl-exp-table generated by this software
+ # As you see, it is somewhat similar to TMW Org.
+ # Remember aggressive, fast, and assistant mobs give even more exp
+
+ # Level 1: total 1 mobs individual 3 xp (1.450)
+ # Level 2: total 3 mobs individual 5 xp (1.450)
+ # Level 3: total 8 mobs individual 5 xp (1.450)
+ # Level 4: total 16 mobs individual 4 xp (1.450)
+ # Level 5: total 26 mobs individual 7 xp (1.450)
+ # Level 6: total 39 mobs individual 8 xp (1.450)
+ # Level 7: total 56 mobs individual 9 xp (1.450)
+ # Level 8: total 77 mobs individual 9 xp (1.450)
+ # Level 9: total 101 mobs individual 10 xp (1.450)
+ # Level 10: total 129 mobs individual 11 xp (1.450)
+ # Level 20: total 667 mobs individual 21 xp (1.450)
+ # Level 30: total 1767 mobs individual 57 xp (1.450)
+ # Level 31: total 1912 mobs individual 63 xp (1.450)
+ # Level 32: total 2064 mobs individual 68 xp (1.450)
+ # Level 33: total 2223 mobs individual 73 xp (1.450)
+ # Level 34: total 2390 mobs individual 84 xp (1.450)
+ # Level 35: total 2563 mobs individual 91 xp (1.450)
+ # Level 36: total 2747 mobs individual 96 xp (1.455)
+ # Level 37: total 2942 mobs individual 101 xp (1.460)
+ # Level 38: total 3148 mobs individual 105 xp (1.465)
+ # Level 39: total 3366 mobs individual 130 xp (1.470)
+ # Level 40: total 3597 mobs individual 148 xp (1.475)
+ # Level 41: total 3841 mobs individual 156 xp (1.480)
+ # Level 42: total 4098 mobs individual 163 xp (1.485)
+ # Level 43: total 4369 mobs individual 170 xp (1.490)
+ # Level 44: total 4656 mobs individual 185 xp (1.495)
+ # Level 45: total 4958 mobs individual 193 xp (1.500)
+ # Level 46: total 5276 mobs individual 201 xp (1.505)
+ # Level 47: total 5611 mobs individual 208 xp (1.510)
+ # Level 48: total 5963 mobs individual 215 xp (1.515)
+ # Level 49: total 6334 mobs individual 249 xp (1.520)
+ # Level 50: total 6724 mobs individual 295 xp (1.525)
+ # Level 51: total 7133 mobs individual 314 xp (1.530)
+ # Level 52: total 7564 mobs individual 331 xp (1.535)
+ # Level 53: total 8016 mobs individual 348 xp (1.540)
+ # Level 54: total 8491 mobs individual 375 xp (1.545)
+ # Level 55: total 8990 mobs individual 393 xp (1.550)
+ # Level 56: total 9512 mobs individual 411 xp (1.555)
+ # Level 57: total 10061 mobs individual 428 xp (1.560)
+ # Level 58: total 10636 mobs individual 443 xp (1.565)
+ # Level 59: total 11239 mobs individual 514 xp (1.570)
+ # Level 60: total 11871 mobs individual 579 xp (1.575)
+ # Level 61: total 12533 mobs individual 608 xp (1.580)
+ # Level 62: total 13226 mobs individual 638 xp (1.585)
+ # Level 63: total 13952 mobs individual 670 xp (1.590)
+ # Level 64: total 14712 mobs individual 704 xp (1.595)
+ # Level 65: total 15508 mobs individual 740 xp (1.600)
+ # Level 66: total 16340 mobs individual 778 xp (1.605)
+ # Level 67: total 17211 mobs individual 818 xp (1.610)
+ # Level 68: total 18122 mobs individual 861 xp (1.615)
+ # Level 69: total 19075 mobs individual 905 xp (1.620)
+ # Level 70: total 20071 mobs individual 952 xp (1.625)
+ # Level 80: total 32389 mobs individual 1783 xp (1.650)
+ # Level 90: total 47806 mobs individual 3635 xp (1.650)
+ # Level 100: total 66308 mobs individual 4976 xp (1.650)
+
+ # Over100 Special Formula
+ if OVER100:
+ lv=100+OVER100
+
+ try:
+ mxp=exp[lv]
+ hxp=exp[lv]
+ lxp=exp[lv-1]
+ minxp=exp[lv-1]
+ except:
+ mxp=exp[len(exp)-1]
+ hxp=exp[len(exp)-1]
+ lxp=exp[len(exp)-2]
+ minxp=exp[len(exp)-2]
+ print("Warning: Invalid exp for mob \033[1m%s\033[0m" % (mb.name.replace("", "").replace(" ", "")))
+
+ if not mb.boss:
+ if lv > 35:
+ fx=1.45+((lv-35)/200.0)
+ else:
+ fx=1.45
+ if fx > 1.65:
+ fx=1.65
+
+ # Aggressive, assistant and fast mobs yield more exp
+ # Slow monsters yield less exp, because they're easier to kill on any playstyle
+ if magr:
+ fx-=0.09
+ else:
+ fx-=0.04
+ if mass:
+ fx-=0.08
+ if mv < 200:
+ fx-=0.02
+ if mv > 500:
+ fx+=0.02
+
+ # 1 ^ anything = 1, so we need a better rule
+ if lv != 1:
+ lxp=minxp/(lv**fx)
+ else:
+ lxp=3 # 3~5 is fine
+ # Boss are BOSS. It gives roughly 0.1% ~ 10.0% from lvlup xp.
+ else:
+ lxp=mxp/1000
+ hxp=mxp/10
+ del minxp, mxp
+
+ # Over100 Special Formula (kinda)
+ olv=0
+ if lv > 80:
+ olv=lv+0
+ lv=80+(lv-80)*0.25
+
+ # Defense follows the same player formula
+ dfn=((lv**1.255)*2.5)
+ dfn=dfn*350.0/810.0
+ mdf=max(0, lv-5)+(lv/10.0)
+ if not mb.boss:
+ mdf/=2
+ if ar > 3:
+ dfn/=2
+
+ # Nerf mobs def in 50% (to be more realistic to what we have)
+ dfn/=2
+
+ # Over100 Special Formula
+ if OVER100:
+ lv=100+OVER100
+ else:
+ lv=olv+0
+ del olv
+
+ # Force HP to be higher
+ # It'll only start applying from level 40 onwards
+ # It gives a bonus of 0.5% HP per mob level
+ # This means a level 100 mob got 60 times that stronger: 30%
+ if lv > 40:
+ lhp=lhp*(1.0+((lv-40)/210.0))
+ lhp=int(lhp)
+
+ # Norm
+ lhp=int(lhp)
+ hhp=int(hhp)
+ lat=int(lat)
+ hat=int(hat)
+ lxp=int(lxp)
+ hxp=int(hxp)
+ dfn=int(dfn)
+ mdf=int(mdf)
+
+ buff=""
+ buff+="HP Range: %s ~ %s \n" % (lhp, hhp)
+ buff+="ATK Range: %s ~ %s \n" % (lat, hat)
+ buff+="Maximum XP: %s ~ %s \n" % (lxp, hxp)
+ buff+="DEF: %s / %s \n" % (dfn, mdf)
+ buff+="Drop, Move, Elegen, aspd \n"
+ buff+=" "
+ return buff
+
+def mb_rdstat(mb):
+ buff=""
+ buff+="%s\n" % (mb.str)
+ buff+="%s\n" % (mb.agi)
+ buff+="%s\n" % (mb.vit)
+ buff+="%s\n" % (mb.int)
+ buff+="%s\n" % (mb.dex)
+ buff+="%s\n" % (mb.luk)
+ buff+=" "
+ return buff
+
+def mb_rdmisc(mb):
+ buff=""
+ if "agr" in mb.st.lower():
+ buff+="View Range: %s\n" % (mb.view)
+ buff+="Attack Range: %s\n" % (mb.range)
+ buff+="AtkDelay: %s ms\n" % (mb.delay)
+ buff+="Move speed: %s ms\n" % (mb.move)
+ buff+="Element Level: %d\n" % (mb.elel)
+ return buff
+
+def mb_rdrw(mb):
+ buff=""
+ buff+="%s \n" % (mb.xp.replace(' ', ' '))
+ buff+="%s \n" % (mb.jp.replace(' ', ' '))
+ try:
+ buff+="MobPoints: %d\n" % (int(mb.mobpt)*11/10)
+ except:
+ pass
+ return buff
+
+def mb_rddrop(mb):
+ buff=""
+ tdr=0
+ for ax in mb.drops:
+ # Ignore disabled drops
+ if ax[0].startswith("//"):
+ continue
+
+ # Write drop
+ try:
+ buff+=ax[0]+': ' + str(int(ax[1])/100.0) + ' %\n'
+ tdr+=int(ax[1])
+ except IndexError:
+ print("Fatal: invalid %s mob with %s drops" % (mb.name, str(ax)))
+ exit(1)
+ except:
+ print("[Warning] %s incorrect drop: %s" % (mb.name, str(ax)))
+ buff+=ax[0]+': ' + ax[1] + ' ppm\n'
+
+ # Save to SysDrops
+ SysDrops.append([ax[0], ax[1], mb.name])
+
+ if tdr < 500:
+ print("[INFO] %s has %d/500 drops. Disqualified for Realm of Drops." % (mb.name, tdr))
+ return buff
+
+
+
+showHeader()
+
+testMobs()
+
+wikib.write(' ')
+wikib.write("Run at: " + datetime.datetime.now().isoformat())
+wikib.write(' ')
+
+wikib.write("""
+Player Stats (melee warrior)
+
+Level Stats 1 Stats 2 Average
+
+00
+str: 2 agi: 5 vit: 7 int:1 dex: 6 luk: 3
+str: 2 agi: 5 vit: 7 int:1 dex: 6 luk: 3
+str: 2 agi: 5 vit: 7 int:1 dex: 6 luk: 3
+
+10
+str:15 agi:10 vit: 5 int:1 dex:10 luk: 4
+str:11 agi:11 vit: 6 int:1 dex:11 luk: 7
+str:13 agi:11 vit: 5 int:1 dex:10 luk: 6
+
+20
+str:19 agi:10 vit:10 int:1 dex:20 luk:10
+str:14 agi:19 vit: 8 int:1 dex:17 luk:11
+str:17 agi:14 vit: 9 int:1 dex:18 luk:11
+
+30
+str:25 agi:30 vit:10 int:1 dex:20 luk:10
+str:22 agi:28 vit:12 int:1 dex:21 luk:15
+str:24 agi:29 vit:11 int:1 dex:20 luk:13
+
+40
+str:38 agi:30 vit:20 int:1 dex:25 luk:17
+str:28 agi:35 vit:14 int:1 dex:32 luk:21
+str:33 agi:33 vit:17 int:1 dex:28 luk:19
+
+50
+str:50 agi:40 vit:30 int:1 dex:25 luk:18
+str:41 agi:41 vit:15 int:1 dex:41 luk:26
+str:46 agi:41 vit:22 int:1 dex:33 luk:27
+
+60
+str:54 agi:50 vit:40 int:1 dex:35 luk:20
+str:52 agi:52 vit:16 int:1 dex:45 luk:32
+str:53 agi:51 vit:28 int:1 dex:40 luk:26
+
+70
+str:60 agi:60 vit:43 int:1 dex:50 luk:20
+str:61 agi:61 vit:22 int:1 dex:51 luk:38
+str:60 agi:61 vit:33 int:1 dex:50 luk:29
+
+80
+str:80 agi:60 vit:50 int:1 dex:50 luk:25
+str:65 agi:71 vit:32 int:1 dex:60 luk:41
+str:72 agi:66 vit:41 int:1 dex:55 luk:33
+
+90
+str:80 agi:70 vit:60 int:1 dex:60 luk:31
+str:71 agi:71 vit:41 int:1 dex:71 luk:51
+str:76 agi:70 vit:50 int:1 dex:66 luk:41
+
+100
+str:90 agi:80 vit:69 int:1 dex:60 luk:31
+str:71 agi:81 vit:42 int:1 dex:81 luk:61
+str:81 agi:80 vit:56 int:1 dex:70 luk:46
+
+110
+str:90 agi:90 vit:69 int:1 dex:70 luk:35
+str:82 agi:82 vit:50 int:1 dex:82 luk:65
+str:86 agi:86 vit:60 int:1 dex:76 luk:50
+
+120
+str:99 agi:90 vit:69 int:1 dex:70 luk:45
+str:86 agi:86 vit:51 int:1 dex:86 luk:68
+str:93 agi:88 vit:60 int:1 dex:78 luk:56
+
+130
+str:99 agi:91 vit:75 int:1 dex:70 luk:55
+str:91 agi:90 vit:51 int:1 dex:90 luk:68
+str:95 agi:91 vit:63 int:1 dex:80 luk:61
+
+
+""")
+wikib.write(' ')
+wikib.write("""
+Elemental Count
+
+%s %02d
+%s %02d
+%s %02d
+%s %02d
+%s %02d
+%s %02d
+%s %02d
+%s %02d
+%s %02d
+
+""" % (
+"Neutral", Ele[0],
+"Water", Ele[1],
+"Earth", Ele[2],
+"Fire", Ele[3],
+"Wind", Ele[4],
+"Holy", Ele[6],
+"Dark", Ele[7],
+"Ghost", Ele[8],
+"Error", Ele[5]))
+
+wikib.write('')
+wikib.close()
+#print(str(SysDrops))
+
+# Aeros allocation
+if aeros:
+ print("// These arrays are filled automatically by redesign.py");
+ print("setarray .ML_Plants, "+
+ str(Plants).replace('[','').replace(']','').replace("'","")+";")
+ print("setarray .ML_Boss, "+
+ str(Boss).replace('[','').replace(']','').replace("'","")+";")
+ print("setarray .ML_Aggr, "+
+ str(Aggressive).replace('[','').replace(']','').replace("'","")+";")
+ print("setarray .ML_Asst, "+
+ str(Assistant).replace('[','').replace(']','').replace("'","")+";")
+ print("setarray .ML_Loot, "+
+ str(Looter).replace('[','').replace(']','').replace("'","")+";")
+ print("setarray .ML_Lv50, "+
+ str(Level50).replace('[','').replace(']','').replace("'","").replace('ID, ','')+
+ ";")
+ print("setarray .ML_Lv99, "+
+ str(Level100).replace('[','').replace(']','').replace("'","")+";")
+
+showFooter()
+exit(0)
diff --git a/wiki/sedesign.py b/wiki/sedesign.py
new file mode 100755
index 0000000..26d956b
--- /dev/null
+++ b/wiki/sedesign.py
@@ -0,0 +1,515 @@
+#! /usr/bin/env python3
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2010-2011 Evol Online
+# Copyright (C) 2018 TMW-2
+# Author: Andrei Karas (4144)
+# Author: Jesusalva
+
+import datetime
+import sys
+
+stgen=True
+
+wikia=open("EleItems.html", "w")
+
+# the TYPEs we use to determine where to pack things
+IT_ARMOR={ 'MISC': [], # FOR FAILURE
+ 'EQP_ACC_L': [], # ACCESSORY LEFT
+ 'EQP_ACC_R': [], # ACCESSORT RIGHT
+ 'EQP_HEAD_MID': [], # CHEST
+ 'EQP_SHOES': [], # FEET
+ 'EQP_GARMENT': [], # GLOVES
+ 'EQP_HEAD_LOW':[], # PANTS
+ '1024': [], # NECKLACES (should be EQP_COSTUME_HEAD_TOP instead of number)
+ '2048': [], # RINGS (should be EQP_COSTUME_HEAD_MID instead of number)
+ 'EQP_MOUNT':[], # MOUNTS (ie. EQP_SHADOW_SHOES)
+ 'EQP_HEAD_TOP':[], # HATS/HELMETS
+ 'EQP_HAND_L': []} # SHIELDS
+IT_WEAPON={ 'RANGED': [], # RANGED WEAPONS
+ 'MAGICAL':[], # MAGICAL WEAPONS
+ 'HAND_2':[], # TWO HAND (LR)
+ 'HAND_1':[]} # WEAPONS (R)
+
+def printSeparator():
+ print("--------------------------------------------------------------------------------")
+
+def showHeader():
+ print("TMW2 EleItems Generator")
+ print("Run at: " + datetime.datetime.now().isoformat())
+ print("Usage: ./sedesign.py [info|level|none] []")
+ print("SeDesign determines the reference defense for every item.")
+ print("Tweak as needed. Bonuses/Rarity/etc not taken in account.")
+ print("Running this without Redesign (or vice versa) will break balance")
+ printSeparator()
+ print("Output is: EleItems.html")
+
+def showFooter():
+ #pass
+ #printSeparator()
+ print("Done.")
+
+
+
+
+
+
+
+class It:
+ def __init__(self):
+ # Basic
+ self.id="0"
+ self.aegis="UnknownItem"
+ self.name="Unknown Item Name"
+ self.price="0" # Sell price, of course
+ self.weight="0"
+ self.type="IT_ETC" # default type
+ self.loc=""
+ self.fc=0.0
+
+ # Offensive/Defensive
+ self.atk="0"
+ self.matk="0"
+ self.range="0"
+ self.defs="0"
+
+ # Restrictions (EquipLv)
+ self.lvl="0"
+ self.drop=True
+ self.trade=True
+ self.sell=True
+ self.store=True
+
+ # Special settings
+ self.rare=False # DropAnnounce
+ self.script=""
+
+ # Visual
+ self.sl="0" # Slots
+ self.ac=False # Allow Cards
+
+def ItAlloc(it):
+ if (it.sl == "0" and it.ac) or (it.sl in ["1","2","3","4"] and not it.ac):
+ print("WARNING, item id "+it.id+" invalid dye/card setting!")
+ if (len(it.sl) > 1 and it.id != "ID(int"):
+ print("WARNING, item id "+it.id+" bad slots length: %d (%s)" % (len(it.sl), it.sl))
+
+ a=it.type
+ if "IT_ARMOR" in a:
+ if 'EQP_ACC_L' in it.loc:
+ IT_ARMOR['EQP_ACC_L'].append(it)
+ it.fc=0.00
+ elif 'EQP_ACC_R' in it.loc:
+ IT_ARMOR['EQP_ACC_R'].append(it)
+ it.fc=0.00
+ elif 'EQP_HEAD_MID' in it.loc:
+ IT_ARMOR['EQP_HEAD_MID'].append(it)
+ if ("bSpeedAddRate, -" in it.script or "bSpeedAddRate,-" in it.script):
+ it.fc=0.40
+ else:
+ it.fc=0.30
+ elif 'EQP_SHOES' in it.loc:
+ IT_ARMOR['EQP_SHOES'].append(it)
+ it.fc=0.08
+ elif 'EQP_GARMENT' in it.loc:
+ IT_ARMOR['EQP_GARMENT'].append(it)
+ it.fc=0.07
+ elif 'EQP_HEAD_LOW' in it.loc:
+ IT_ARMOR['EQP_HEAD_LOW'].append(it)
+ it.fc=0.10
+ elif 'EQP_HEAD_TOP' in it.loc:
+ IT_ARMOR['EQP_HEAD_TOP'].append(it)
+ if ("bSpeedAddRate, -" in it.script or "bSpeedAddRate,-" in it.script):
+ it.fc=0.15
+ else:
+ it.fc=0.10
+ elif 'EQP_HAND_L' in it.loc:
+ IT_ARMOR['EQP_HAND_L'].append(it)
+ it.fc=0.25
+ elif '1024' in it.loc:
+ IT_ARMOR['1024'].append(it)
+ it.fc=0.00
+ elif '2048' in it.loc:
+ IT_ARMOR['2048'].append(it)
+ it.fc=0.00
+ elif 'EQP_SHADOW_SHOES' in it.loc:
+ IT_ARMOR['EQP_MOUNT'].append(it)
+ it.fc=0.00
+ elif 'EQP_SHADOW_ACC_R' in it.loc:
+ IT_ARMOR['EQP_ACC_R'].append(it) # Not really
+ it.fc=0.00
+ else:
+ raise Exception("Invalid Loc for ID %s: %s" % (it.id, it.loc))
+ elif "IT_WEAPON" in a:
+ if int(it.matk) > 0:
+ IT_WEAPON["MAGICAL"].append(it)
+ elif int(it.range) > 2:
+ IT_WEAPON["RANGED"].append(it)
+ elif "HAND_L" in it.loc or "EQP_ARMS" in it.loc:
+ IT_WEAPON["HAND_2"].append(it)
+ elif "HAND_R" in it.loc:
+ IT_WEAPON["HAND_1"].append(it)
+ else:
+ raise Exception("Invalid location for weapon: %s" % it.loc)
+
+def newItemDB():
+ print("\nGenerating Item Wiki...")
+ if len(sys.argv) >= 3:
+ src=open(sys.argv[2]+"/db/re/item_db.conf", "r", encoding="utf-8")
+ else:
+ src=open("../../server-data/db/re/item_db.conf", "r", encoding="utf-8")
+
+ lg=False
+ x=It()
+ for a2 in src:
+ a=a2.replace(' ', '\t');
+ if a == "{\n":
+ ItAlloc(x)
+ x=It()
+
+ # sti() block
+ if " Id:" in a:
+ x.id=sti(a)
+ elif " AegisName:" in a:
+ x.aegis=sti(a)
+ elif " Name:" in a:
+ x.name=stin(a)
+ elif " Sell:" in a:
+ x.price=sti(a)
+ elif " Weight:" in a:
+ x.weight=sti(a)
+ elif " Type:" in a:
+ x.type=sti(a)
+ elif " Loc:" in a:
+ x.loc=sti(a)
+ elif " Atk:" in a:
+ x.atk=sti(a)
+ elif " Matk:" in a:
+ x.matk=sti(a)
+ elif " Range:" in a:
+ x.range=sti(a)
+ elif " Def:" in a:
+ x.defs=sti(a)
+ elif " EquipLv:" in a:
+ x.lvl=sti(a)
+ elif " Slots:" in a:
+ x.sl=sti(a)
+ elif " AllowCards:" in a:
+ x.ac=True
+ # Write booleans
+ elif "DropAnnounce: true" in a:
+ x.rare=True
+ elif "nodrop: true" in a:
+ x.drop=False
+ elif "notrade: true" in a:
+ x.trade=False
+ elif "noselltonpc: true" in a:
+ x.sell=False
+ elif "nostorage: true" in a:
+ x.store=False
+ elif "Script" in a:
+ lg=True
+ x.script+=""
+ elif lg and "\">" in a:
+ lg=False
+ x.script+=" "
+ elif lg:
+ if not "announce" in a and not "debugmes" in a and not "logmes" in a:
+ x.script+=str(a.replace('\t', '').replace('getiteminfo(getequipid(', 'iteminfo((').replace('Flee2','Block'))
+
+ # Write last entry
+ ItAlloc(x)
+ writeItems()
+
+ src.close()
+
+def sti(x):
+ return x.replace('\n', '').replace('|', '').replace(')', '').replace('Id: ', '').replace('"','').replace(" ","").replace("\t","").replace('AegisName: ', '').replace('Name: ','').replace('Sell: ', '').replace('Weight: ', '').replace('Type: ', '').replace('Loc: ', '').replace('Atk: ', '').replace('Matk: ', '').replace('Range: ', '').replace('Def: ', '').replace('EquipLv: ', '').replace('Slots: ','').replace(" ", "").replace('@min=','').replace('@max=','').replace('@delay=','').replace(';','')
+
+def stin(x):
+ return x.replace('\n', '').replace('|', '').replace(')', '').replace('Id: ', '').replace('"','').replace(" ","").replace("\t","").replace('Name: ','').replace(';','')
+
+
+def writeItems():
+ wikia.write("Armors \n\
+\n\n")
+ wikia.write("Restrictions Reference \n")
+ wikia.write("\n\
+ * - Rare item with drop announce. \n\
+ (dp) - This item cannot be dropped. \n\
+ (tr) - This item cannot de traded. \n\
+ (sl) - This item cannot be sold. \n\
+ (gg) - This item cannot go to storage. \n\
+ \n")
+
+ ####################################################################
+ wikia.write("Armors \n")
+
+ ArmorWrite("Left Accessory",'EQP_ACC_L')
+ ArmorWrite("Right Accessory",'EQP_ACC_R')
+ ArmorWrite("Headgear",'EQP_HEAD_TOP')
+ ArmorWrite("Chest",'EQP_HEAD_MID')
+ ArmorWrite("Pants",'EQP_HEAD_LOW')
+ ArmorWrite("Shoes",'EQP_SHOES')
+ ArmorWrite("Necklaces",'1024')
+ ArmorWrite("Rings",'2048')
+ ArmorWrite("Gloves",'EQP_GARMENT')
+ ArmorWrite("Shields",'EQP_HAND_L')
+ #for i in sorted(IT_ARMOR['EQP_HEAD_TOP'], key=lambda xcv: int(xcv.lvl)):
+ # # Name | Level | Location | Specification
+ # print("`%s`|%d| :grey_question: | " % (i.name, int(i.lvl)))
+
+ ####################################################################
+ wikia.write("Weapons \n")
+
+ # 1 Hand Items
+ wikia.write("1H Weapons \n")
+ ItemWrite(IT_WEAPON['HAND_1'], ID=True, AEGIS=True, PRICE=True, WEIGHT=True, ATK=True, LVL=True)
+
+ # 2 Hand Items
+ wikia.write("2H Weapons \n")
+ ItemWrite(IT_WEAPON['HAND_2'], ID=True, AEGIS=True, PRICE=True, WEIGHT=True, ATK=True, LVL=True, RANGE=True)
+
+ # Ranged Items
+ wikia.write("Ranged Weapons \n")
+ ItemWrite(IT_WEAPON['RANGED'], ID=True, AEGIS=True, PRICE=True, WEIGHT=True, ATK=True, LVL=True, RANGE=True)
+
+ # Magic Items
+ wikia.write("Magical Weapons \n")
+ ItemWrite(IT_WEAPON['MAGICAL'], ID=True, AEGIS=True, PRICE=True, WEIGHT=True, ATK=True, MATK=True, LVL=True, RANGE=True)
+
+# Write AegisName with restrictions
+def hl(it):
+ buff=""
+ if it.rare:
+ buff+="*"
+ buff+=it.aegis
+ buff+=" "
+ if not it.drop:
+ buff+="(dp) "
+ if not it.trade:
+ buff+="(tr) "
+ if not it.sell:
+ buff+="(sl) "
+ if not it.store:
+ buff+="(gg) "
+ return buff
+
+# wikia.write("Id|Aegis|Name|Weight|Atk|Matk|\n")
+# wikia.write("Id|Aegis|Name|Price|Weight|\n")
+
+def ItemWrite(tbl, ID=False, AEGIS=False, NAME=False, PRICE=False, WEIGHT=False, DEF=False, LVL=False, ATK=False, MATK=False, RANGE=False, HEALING=False, SCRIPT=False, DROPPER=False):
+ global stgen
+ wikia.write("\n")
+ wikia.write("")
+ if ID:
+ wikia.write("ID ")
+ if AEGIS:
+ wikia.write("Aegis ")
+ if NAME:
+ wikia.write("Name ")
+ if PRICE:
+ wikia.write("Price ")
+ if WEIGHT:
+ wikia.write("Weight ")
+ if DEF:
+ wikia.write("Def ")
+ if stgen:
+ wikia.write("Adj.Def ")
+ if LVL:
+ wikia.write("Lvl ")
+ if ATK:
+ wikia.write("Atk ")
+ if MATK:
+ wikia.write("Matk ")
+ if ATK and MATK:
+ wikia.write("+ ")
+ if ATK or MATK:
+ if stgen:
+ wikia.write("Adj. Atk. ")
+ if RANGE:
+ wikia.write("Range ")
+ if HEALING:
+ wikia.write("Min ")
+ wikia.write("Max ")
+ wikia.write("Delay ")
+ if SCRIPT:
+ wikia.write("Script ")
+ if DROPPER:
+ wikia.write("Mobs ")
+
+ wikia.write(" \n")
+
+ try:
+ if (sys.argv[1] in ["level", "info"]):
+ sort=sorted(tbl, key=lambda xcv: int(xcv.lvl))
+ elif (sys.argv[1] == "none"):
+ sort=tbl
+ else:
+ print("Syntax: ./sedesign.py [info|level|none]")
+ sort=tbl
+
+ if (sys.argv[1] == "info"):
+ stgen=False
+ except:
+ sort=tbl
+
+ for i in sort:
+ wikia.write('')
+
+ if ID:
+ wikia.write(" %s " % (i.id,i.id))
+ if AEGIS:
+ wikia.write("%s " % hl(i))
+ if NAME:
+ wikia.write("%s " % i.name)
+ if PRICE:
+ wikia.write("%s GP " % i.price)
+ if WEIGHT:
+ wikia.write("%s g " % i.weight)
+ if DEF:
+ wikia.write("Def: %s " % i.defs)
+ if stgen:
+ lv=int(i.lvl)
+
+ bb=(lv**1.255)*2.5*i.fc
+ hc=bb*350.0/810.0 # Hercules value: Capped at 350
+
+ # Magic penalty
+ if ("bMatk," in i.script):
+ hc=hc/2.0
+
+ # Precise rounding
+ if (hc % 1 >= 0.5):
+ hc+=1
+ wikia.write("Adj. Df: %d " % int(hc))
+ if LVL:
+ wikia.write("Lv: %s " % i.lvl)
+ if ATK:
+ wikia.write("Atk: %s " % i.atk)
+ if MATK:
+ wikia.write("%s " % i.matk)
+ if ATK and MATK:
+ try:
+ tmpatmat=i.atk.replace("Atk: ", "")
+ #print("`%s`+`%s`=?" % (tmpatmat, i.matk))
+ wikia.write("%s " % str(int(i.matk)+int(tmpatmat)))
+ except:
+ wikia.write("? ")
+ if ATK or MATK:
+ if stgen:
+ lv=int(i.lvl)
+ at=int(i.atk)
+ fc=7.5
+ ## Two hand swords are stronger
+ if i in IT_WEAPON['HAND_2']:
+ fc+=0.62
+ ## Calculate guns
+ if int(i.id) == 6010 or int(i.id) == 6050:
+ lv+=20
+ if int(i.id) == 6020:
+ lv=0
+ if int(i.id) == 6040:
+ fc/=2.5
+
+ ## Progression
+ if int(i.matk) <= 0 or int(at) > 0:
+ fc-=1.0
+ fc+=((100-lv)/100.0)
+
+ ## Even slower progression (note bows doesn't gets the 0.8 2hand bonus)
+ if not i in IT_WEAPON['RANGED']:
+ fc-=(lv/15.0)/10.0
+
+ ## After level 50 weapons progression is slowed down
+ if lv > 45:
+ fc-=(lv-45)/90.0
+
+ ## This is because mob HP scaling is buggy
+ if lv > 45 and not i in IT_WEAPON['RANGED']:
+ fc-=(lv-45)/100.0
+
+ ## Magic Weapons adjustment
+ if int(i.matk) > 0:
+ fc+=0.0
+
+ ## Physical and Magic Weapons adjustment
+ if int(i.matk) > 0 and int(at) > 0:
+ fc=7.0+((100-lv)/100.0)
+
+ ## HAT is for craft or rare items.
+ lat=lv*fc
+ hat=lv*(fc+max(0.01, 1.0-(lv/100.0)))
+
+ # Edge Cases
+ ## Don't recalculate noob weapon
+ if lv <= 20:
+ lat=hat=at
+
+ wikia.write("%d ~ %d (%.2f) " % (lat, hat, fc))
+ if RANGE:
+ wikia.write("%s " % i.range)
+ if HEALING:
+ wikia.write("%s " % i.minheal)
+ wikia.write("%s " % i.maxheal)
+ wikia.write("%s s " % i.delheal)
+ if SCRIPT:
+ wikia.write("%s " % i.script)
+ if DROPPER:
+ wikia.write("- ")
+
+ wikia.write(" ")
+
+ wikia.write("
\n")
+ wikia.write("\n(↑) Return to top \n\n")
+
+def ArmorWrite(name,scope):
+ wikia.write(""+name+" \n")
+ ItemWrite(IT_ARMOR[scope], ID=True, AEGIS=True, PRICE=True, WEIGHT=True, DEF=True, LVL=True, SCRIPT=True)
+
+
+
+
+
+
+
+
+
+
+
+showHeader()
+
+wikia.write("")
+wikia.write('SeDesign \n')
+wikia.write("")
+newItemDB()
+
+# Ending
+wikia.write(" ")
+wikia.write("Run at: " + datetime.datetime.now().isoformat())
+wikia.write("")
+wikia.close()
+
+# Print for reference the landmarks
+i=0
+while i < 100:
+ i+=10
+ bb=(i**1.255)*2.5
+ hc=bb*350.0/810.0 # Hercules value: Capped at 350
+ df=(100.0 - hc / (hc + 400.0) * 90.0) / 100.0 * 100
+ print("%d Level %d Defense (%.2f%% DMG)" % (i, hc, df) );
+
+showFooter()
+exit(0)
diff --git a/wiki/tmp-arm b/wiki/tmp-arm
new file mode 100644
index 0000000..53e733e
--- /dev/null
+++ b/wiki/tmp-arm
@@ -0,0 +1,247 @@
+"791": 0,
+"1151": 4,
+"1152": 8,
+"1153": 5,
+"1155": 0,
+"1156": 5,
+"1159": 2,
+"1161": 2,
+"1162": 2,
+"1163": 2,
+"1164": 8,
+"1165": 3,
+"1169": 5,
+"1170": 5,
+"1171": 5,
+"1178": 10,
+"3200": 2,
+"3201": 2,
+"3202": 0,
+"3203": 0,
+"3204": 1,
+"3205": 4,
+"3206": 0,
+"3207": 0,
+"3210": 1,
+"2900": 1,
+"2901": 0,
+"2902": 1,
+"2903": 3,
+"2904": 3,
+"2905": 3,
+"2906": 9,
+"2907": 9,
+"2908": 3,
+"2909": 5,
+"2910": 3,
+"2911": 3,
+"2913": 3,
+"2914": 4,
+"2915": 5,
+"2916": 4,
+"2917": 2,
+"2918": 2,
+"2919": 6,
+"2920": 6,
+"2921": 4,
+"2923": 1,
+"2924": 0,
+"2926": 0,
+"2927": 9,
+"2928": 3,
+"2929": 9,
+"2930": 1,
+"2932": 6,
+"2934": 0,
+"2935": 0,
+"2936": 1,
+"2937": 4,
+"2938": 0,
+"2939": 0,
+"2940": 4,
+"2941": 7,
+"2942": 8,
+"2943": 6,
+"2944": 4,
+"2945": 3,
+"2946": 1,
+"2983": 1,
+"2985": 2,
+"2986": 2,
+"2987": 3,
+"2989": 0,
+"2990": 0,
+"2991": 2,
+"2992": 1,
+"2993": 2,
+"2994": 2,
+"2995": 3,
+"2996": 15,
+"2997": 3,
+"2998": 4,
+"2999": 5,
+"3000": 2,
+"3001": 5,
+"3002": 3,
+"3003": 5,
+"3004": 8,
+"3005": 8,
+"3006": 1,
+"3007": 6,
+"3008": 7,
+"3010": 3,
+"3011": 2,
+"3012": 5,
+"3013": 3,
+"3014": 1,
+"3015": 2,
+"3017": 2,
+"3018": 6,
+"3019": 4,
+"3020": 7,
+"3021": 0,
+"3022": 1,
+"3023": 3,
+"3024": 3,
+"3028": 3,
+"3029": 3,
+"3030": 10,
+"1300": 0,
+"1301": 4,
+"1302": 0,
+"1303": 2,
+"1304": 3,
+"1305": 5,
+"1306": 7,
+"1307": 7,
+"1308": 5,
+"1309": 0,
+"1310": 4,
+"1311": 8,
+"1312": 6,
+"1313": 0,
+"1314": 2,
+"1315": 1,
+"1316": 2,
+"1317": 5,
+"1318": 4,
+"1319": 1,
+"1320": 3,
+"1321": 0,
+"1323": 8,
+"1324": 3,
+"1325": 0,
+"1326": 0,
+"1327": 0,
+"1328": 6,
+"1329": 7,
+"1330": 0,
+"1331": 10,
+"1332": 1,
+"1333": 8,
+"1334": 3,
+"1335": 0,
+"1336": 6,
+"1337": 7,
+"1338": 2,
+"1339": 5,
+"2200": 0,
+"2201": 3,
+"2202": 8,
+"2203": 7,
+"2204": 5,
+"2205": 1,
+"2206": 1,
+"2207": 3,
+"2208": 5,
+"2209": 1,
+"2210": 8,
+"2211": 6,
+"2212": 2,
+"2213": 4,
+"2214": 4,
+"2215": 7,
+"2216": 10,
+"2217": 0,
+"2218": 2,
+"2219": 2,
+"2220": 3,
+"1800": 1,
+"1801": 8,
+"1802": 5,
+"1803": 0,
+"1804": 3,
+"1805": 2,
+"1806": 2,
+"1807": 3,
+"1808": 4,
+"1809": 5,
+"1810": 6,
+"1811": 9,
+"1812": 7,
+"1813": 8,
+"1814": 7,
+"1815": 10,
+"1816": 0,
+"1817": 1,
+"1818": 2,
+"1819": 3,
+"1000": 4,
+"1001": 5,
+"1002": 4,
+"1003": 4,
+"1004": 4,
+"1005": 6,
+"1007": 4,
+"1008": 6,
+"1009": 8,
+"1010": 7,
+"1012": 2,
+"1013": 2,
+"1014": 4,
+"1015": 5,
+"1016": 5,
+"1017": 5,
+"1019": 5,
+"2500": 0,
+"2501": 4,
+"2510": 0,
+"2511": 0,
+"2512": 2,
+"2000": 3,
+"2001": 5,
+"2002": 7,
+"2003": 9,
+"2004": 0,
+"2005": 1,
+"2006": 2,
+"2007": 2,
+"2008": 4,
+"2009": 5,
+"2010": 6,
+"2011": 9,
+"2012": 9,
+"2013": 7,
+"2014": 7,
+"2015": 8,
+"2016": 0,
+"2700": 0,
+"2701": 1,
+"2702": 3,
+"2703": 7,
+"2704": 5,
+"2705": 10,
+"2706": 6,
+"2707": 0,
+"2708": 6,
+"2709": 4,
+"2710": 2,
+"2711": 8,
+"2712": 9,
+"2713": 1,
+"2714": 2,
+"2715": 2,
+"2716": 3,
+"2717": 3,
+"2718": 4,
+"2719": 4,
diff --git a/wiki/tmp-dec b/wiki/tmp-dec
new file mode 100644
index 0000000..8337ac2
--- /dev/null
+++ b/wiki/tmp-dec
@@ -0,0 +1,8 @@
+"3208": BurglarMask,
+"3209": BanditMask,
+"3211": AutumnMask,
+"2922": YetiMask,
+"2925": OperaMask,
+"2931": SkullMask,
+"3009": FafiMask,
+"3016": TerraniteMask,
diff --git a/wiki/tmp-etc b/wiki/tmp-etc
new file mode 100644
index 0000000..5e1d879
--- /dev/null
+++ b/wiki/tmp-etc
@@ -0,0 +1,25 @@
+Armor:
+ "1150": FourLeafClover,
+ "1154": Doll,
+ "1157": LeatherBall,
+ "1158": ZarkorScroll,
+ "1160": PlushMouboo,
+ "1166": Blanket,
+ "1167": EarthScroll,
+ "1168": CursedSkull,
+ "1172": LeatherQuiver,
+ "1173": IronQuiver,
+ "1174": BronzeQuiver,
+ "1175": PlatinumQuiver,
+ "1176": DragonStar,
+ "1177": MichelSoul,
+ "1179": RubberDucky,
+ "1006": LifestonePendant,
+ "1011": 1,
+ "1020": 2,
+ "1021": 1,
+ "2508": 2,
+ "2509": 3,
+ "2720": 0
+
+
diff --git a/wiki/tmp-wpn b/wiki/tmp-wpn
new file mode 100644
index 0000000..f86e7b4
--- /dev/null
+++ b/wiki/tmp-wpn
@@ -0,0 +1,59 @@
+"3500": 0,
+"3501": 2,
+"3502": 3,
+"3503": 2,
+"3504": 4,
+"3505": 1,
+"3506": 0,
+"3507": 1,
+"3508": 7,
+"3509": 9,
+"3510": 0,
+"3511": 10,
+"3512": 3,
+"3513": 8,
+"3514": 3,
+"3515": 6,
+"3516": 8,
+"3518": 2,
+"3519": 6,
+"3520": 2,
+"3527": 5,
+"3528": 3,
+"3529": 1,
+"3531": 6,
+"3536": 6,
+"3538": 6,
+"3539": 6,
+"3600": 99,
+"6040": 6,
+"3517": 4,
+"3521": 2,
+"3522": 6,
+"3523": 4,
+"3524": 5,
+"3525": 6,
+"3526": 7,
+"3530": 7,
+"3532": 9,
+"3533": 7,
+"3534": 8,
+"3537": 6,
+"3601": 99,
+"3602": 99,
+"3603": 99,
+"6000": 0,
+"6001": 1,
+"6002": 2,
+"6003": 4,
+"6004": 6,
+"6005": 8,
+"6006": 10,
+"6010": 6,
+"6030": 6,
+"6050": 7,
+"7020": 6,
+"7400": 4,
+"7401": 5,
+"7421": 10,
+"3604": 99,
diff --git a/wiki/webwikigen.py b/wiki/webwikigen.py
new file mode 100755
index 0000000..51a8d7a
--- /dev/null
+++ b/wiki/webwikigen.py
@@ -0,0 +1,758 @@
+#! /usr/bin/env python3
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2010-2011 Evol Online
+# Copyright (C) 2018 TMW-2
+# Author: Andrei Karas (4144)
+# Author: Jesusalva
+
+import datetime
+
+wikia=open("Items.md", "w")
+wikib=open("Monsters.md", "w")
+wikic=open("../../client-data/x.diff", "w")
+
+# the TYPEs we use to determine where to pack things
+IT_HEALING=[]
+IT_ETC=[]
+IT_USABLE=[]
+IT_AMMO=[]
+IT_CARD=[]
+IT_PETEGG=[]
+IT_WEAPON={ 'HAND_2': [], # TWO HAND (LR)
+ 'HAND_1':[]} # WEAPONS (R)
+IT_ARMOR={ 'MISC': [], # FOR FAILURE
+ 'EQP_ACC_L': [], # ACCESSORY LEFT
+ 'EQP_ACC_R': [], # ACCESSORT RIGHT
+ 'EQP_HEAD_MID': [], # CHEST
+ 'EQP_SHOES': [], # FEET
+ 'EQP_GARMENT': [], # GLOVES
+ 'EQP_HEAD_LOW':[], # PANTS
+ '1024': [], # NECKLACES (should be EQP_COSTUME_HEAD_TOP instead of number)
+ '2048': [], # RINGS (should be EQP_COSTUME_HEAD_MID instead of number)
+ 'EQP_HEAD_TOP':[], # HATS/HELMETS
+ 'EQP_HAND_L': []} # SHIELDS
+
+Mobs1=[]
+Mobs2=[]
+Mobs3=[]
+Mobs4=[]
+Mobs5=[]
+MobsA=[]
+
+SysDrops=[]
+
+
+def printSeparator():
+ print("--------------------------------------------------------------------------------")
+
+def showHeader():
+ print("TMW2 Wiki Generator")
+ ##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():
+ #pass
+ #printSeparator()
+ print("Done.")
+
+
+
+
+
+def fwalk(wmask):
+ if wmask == 'WATER':
+ return '%s ' % (wmask)
+ elif wmask == 'AIR':
+ return '%s ' % (wmask)
+ elif wmask == 'WALL':
+ return '%s ' % (wmask)
+ elif wmask == 'NORMAL' or wmask == 'DEFAULT':
+ return '%s ' % (wmask)
+ else:
+ print("Invalid walk mask: "+wmask)
+ exit(1)
+
+def WhatRace(rac):
+ rc=rac.race
+ if rc == 0:
+ return "Formless"
+ elif rc == 1:
+ return "Undead"
+ elif rc == 2:
+ return "Brute"
+ elif rc == 3:
+ return "Plant"
+ elif rc == 4:
+ return "Insect"
+ elif rc == 5:
+ return "Fish"
+ elif rc == 6:
+ return "-"
+ elif rc == 7:
+ return "SemiHuman"
+ elif rc == 8:
+ return "Angel"
+ elif rc == 9:
+ return "Dragon"
+ elif rc == 10:
+ return "Player"
+ elif rc == 11:
+ return "Boss"
+ elif rc == 12:
+ return "NonBoss"
+ elif rc == 14:
+ return "NonSemiHuman"
+ elif rc == 15:
+ return "NonPlayer"
+ elif rc == 16:
+ return "SemiPlayer"
+ elif rc == 17:
+ return "NonSemiPlayer"
+ else:
+ print("ERROR, INVALID RACE ID: %d (ID: %s)" % (rc, rac.id))
+ exit(1)
+
+def WhatElem(rac):
+ rc=rac.elem
+ tl="ERROR"
+ cl="#F00"
+ if rc == 0:
+ tl,cl="Neutral","#000"
+ elif rc == 1:
+ tl,cl="Water","#00F"
+ elif rc == 2:
+ tl,cl="Earth","#7A0"
+ elif rc == 3:
+ tl,cl="Fire","#F00"
+ elif rc == 4:
+ tl,cl="Wind","#093"
+ elif rc == 5:
+ tl,cl="Poison","#040"
+ elif rc == 6:
+ tl,cl="Holy","#afa"
+ elif rc == 7:
+ tl,cl="Dark","#908"
+ elif rc == 8:
+ tl,cl="Ghost","#404"
+ elif rc == 9:
+ tl,cl="Undead","#440"
+ else:
+ print("ERROR, INVALID ELEM ID: %d (ID: %s)" % (rc, rac.id))
+ exit(1)
+ return "%s " % (cl, tl)
+
+class Mob:
+ def __init__(self):
+ # Basic
+ self.id="0"
+ #self.aegis="UnknownMonster" # SpriteName is not used anywhere, we are using its ID
+ self.name="Unknown Monster Name"
+ self.view="1"
+
+ # Defensive
+ self.mobpt="0" # Mob Points “Level”
+ self.hp="0"
+ self.xp="0"
+ self.jp="0"
+ self.st=""
+
+ # Offensive
+ self.atk="[0, 0]"
+ self.range="0"
+ self.move="0"
+ self.delay="0"
+ self.drops=[]
+
+ # Elegen Info
+ self.race=-1
+ self.elem=-1
+ self.elel=-1
+ self.walk="NORMAL"
+
+def MobAlloc(ab):
+ try:
+ maab=int(ab.mobpt)
+ except:
+ maab=9901
+
+ if maab <= 20:
+ Mobs1.append(ab)
+ elif maab <= 40:
+ Mobs2.append(ab)
+ elif maab <= 60:
+ Mobs3.append(ab)
+ elif maab <= 80:
+ Mobs4.append(ab)
+ elif maab <= 100:
+ Mobs5.append(ab)
+ else:
+ MobsA.append(ab)
+
+def testMobs():
+ print("Generating Mob Wiki...")
+ src=open("../../server-data/db/re/mob_db.conf", "r")
+ wikib.write("Monster Database \n")
+ start=False
+ dropper=False
+ x=Mob() # Only for pyflakes2
+
+ for a in src:
+ if a == "{\n":
+ if start:
+ MobAlloc(x)
+ else:
+ start=True
+ x=Mob()
+
+ if " Id:" in a:
+ x.id=stp(a)
+ if x.id == "ID":
+ continue
+
+ if " Name:" in a:
+ x.name=stp(a)
+ elif " Hp:" in a:
+ x.hp=stp(a)
+ elif " Lv:" in a:
+ x.mobpt=stp(a)
+ elif " Exp:" in a:
+ x.xp=stp(a)
+ elif " JExp:" in a:
+ x.jp=stp(a)
+ elif " Attack:" in a:
+ x.atk=stp(a)
+ elif " AttackRange:" in a:
+ x.range=stp(a)
+ elif " MoveSpeed:" in a:
+ x.move=stp(a)
+ elif " ViewRange:" in a:
+ x.view=stp(a)
+ elif " AttackDelay:" in a:
+ x.delay=stp(a)
+ elif " Looter: true" in a:
+ x.st+="Lot ,"
+ elif " Assist: true" in a:
+ x.st+="Ass ,"
+ elif " Aggressive: true" in a:
+ x.st+="Agr ,"
+ elif " WalkMask:" in a:
+ x.walk=stp(a)
+ elif " Element:" in a:
+ tmp=stp(a)
+ tmp2=tmp.split(',')
+ try:
+ x.elem=int(tmp2[0])
+ x.elel=int(tmp2[1])
+ except:
+ print("Invalid Element for mob %s: %s" % (x.id, tmp))
+ exit(1)
+ elif " Race:" in a:
+ try:
+ x.race=int(stp(a))
+ except:
+ print("Invalid Race for mob %s: %s" % (x.id, a))
+ exit(1)
+ elif 'Drops: ' in a:
+ dropper=True
+ elif dropper and '}' in a:
+ dropper=False
+ elif dropper:
+ x.drops.append(stp(a).split(": "))
+ # Write last entry
+ MobAlloc(x)
+
+ writeMob()
+
+ wikib.write('\n\n|Mode|Desc|\n|----|----|\n')
+ wikib.write('|Lot|Looter|\n')
+ wikib.write('|Ass|Assist|\n')
+ wikib.write('|Agr|Aggressive|\n')
+
+ src.close()
+
+def stp(x):
+ return x.replace('\n', '').replace('|', '').replace('(int, defaults to ', '').replace(')', '').replace('basic experience', '').replace('"','').replace(" ","").replace("\t","").replace('(string', '').replace('Name: ','').replace('Element: ','').replace('Race: ','').replace('AttackDelay: ', '').replace('WalkMask: ','').replace('MoveSpeed: ', '').replace('AttackRange: ', '').replace('ViewRange: ','').replace('Attack: ','').replace('ViewRange: ','').replace('Hp: ','').replace('Id: ','').replace('Lv: ','').replace('view range','').replace('attack range','').replace('move speed','').replace('health','').replace('(int','').replace('attack delay','atk.').replace('(','').replace(')','').replace('WALK_','')
+
+
+def MonsterWrite(tbl):
+ # TODO: Check _mobs files to determine the usual monster density (a misc info to aid adding proper drop specs)
+ wikib.write("\n")
+ wikib.write("ID Name Mob Info Elegen Misc Info Rewards Drops \n")
+ for i in tbl:
+ if i.id == 'ID':
+ continue
+ wikib.write(' ' +
+ i.id +""+
+ i.name +" "+
+ mb_core(i) +" "+
+ mb_eleg(i) +" "+
+ mbdt('misc', mb_rdmisc(i)) +" "+
+ mbdt('Exp\'s', mb_rdrw(i)) +" "+
+ mbdt('drops', mb_rddrop(i)) +" \n"
+ )
+ wikib.write("
\n")
+ wikib.write("\n(↑) Return to top \n\n")
+
+def writeMob():
+ wikib.write("\
++ [Level 0-20](#starter)\n\
++ [Level 21-40](#apprentice)\n\
++ [Level 41-60](#intermediary)\n\
++ [Level 61-80](#advanced)\n\
++ [Level 81-100](#expert)\n\
++ [Level 100+](#out-of-scope)\n\n\
+ ")
+
+ wikib.write("## Starter\n\n")
+ MonsterWrite(Mobs1)
+
+ wikib.write("## Apprentice\n\n")
+ MonsterWrite(Mobs2)
+
+ wikib.write("## Intermediary\n\n")
+ MonsterWrite(Mobs3)
+
+ wikib.write("## Advanced\n\n")
+ MonsterWrite(Mobs4)
+
+ wikib.write("## Expert\n\n")
+ MonsterWrite(Mobs5)
+
+ wikib.write("## Out Of Scope\n\n")
+ MonsterWrite(MobsA)
+
+
+def mbdt(summary, content):
+ return "\
+"+summary+" \
+"+content+" "
+
+def mb_core(mb):
+ buff=""
+ buff+="Lvl: %s \n" % (mb.mobpt)
+ buff+="HP: %s \n" % (mb.hp)
+ buff+="ATK: %s \n" % (mb.atk)
+ if mb.st != "":
+ buff+="Modes: %s " % (mb.st)
+ return buff
+
+def mb_eleg(mb):
+ buff=""
+ buff+="Race: %s \n" % (WhatRace(mb))
+ buff+="Walk: %s \n" % (fwalk(mb.walk))
+ buff+="Element: %s \n" % (WhatElem(mb))
+ return buff
+
+def mb_rdmisc(mb):
+ buff=""
+ if "agr" in mb.st.lower():
+ buff+="View Range: %s\n" % (mb.view)
+ buff+="Attack Range: %s\n" % (mb.range)
+ buff+="AtkDelay: %s ms\n" % (mb.delay)
+ buff+="Move speed: %s ms\n" % (mb.move)
+ buff+="Element Level: %d\n" % (mb.elel)
+ return buff
+
+def mb_rdrw(mb):
+ buff=""
+ try:
+ buff+="MobPoints: %d\n" % (int(mb.mobpt)*11/10)
+ except:
+ pass
+ buff+="%s\n" % (mb.xp)
+ buff+="%s\n" % (mb.jp)
+ return buff
+
+def mb_rddrop(mb):
+ buff=""
+ for ax in mb.drops:
+ # Ignore disabled drops
+ if ax[0].startswith("//"):
+ continue
+
+ # Write drop
+ try:
+ buff+=ax[0]+': ' + str(int(ax[1])/100.0) + ' %\n'
+ except IndexError:
+ print("Fatal: invalid %s mob with %s drops" % (mb.name, str(ax)))
+ exit(1)
+ except:
+ print("[Warning] %s incorrect drop: %s" % (mb.name, str(ax)))
+ buff+=ax[0]+': ' + ax[1] + ' ppm\n'
+
+ # Save to SysDrops
+ SysDrops.append([ax[0], ax[1], mb.name])
+
+ return buff
+
+
+class It:
+ def __init__(self):
+ # Basic
+ self.id="0"
+ self.aegis="UnknownItem"
+ self.name="Unknown Item Name"
+ self.price="0" # Sell price, of course
+ self.weight="0"
+ self.type="IT_ETC" # default type
+ self.loc=""
+
+ # Offensive/Defensive
+ self.atk="0"
+ self.matk="0"
+ self.range="0"
+ self.defs="0"
+
+ # Restrictions (EquipLv)
+ self.lvl="0"
+ self.drop=True
+ self.trade=True
+ self.sell=True
+ self.store=True
+
+ # Special settings
+ self.rare=False # DropAnnounce
+ self.script=False
+
+ # Visual
+ self.sl="0" # Slots
+ self.ac=False # Allow Cards
+
+ # Script settings
+ self.minheal="0"
+ self.maxheal="0"
+ self.delheal="0"
+
+def ItAlloc(it):
+ if (it.sl == "0" and it.ac) or (it.sl in ["1","2","3","4"] and not it.ac):
+ print("WARNING, item id "+it.id+" invalid dye/card setting!")
+ if (len(it.sl) > 1):
+ print("WARNING, item id "+it.id+" bad slots length: %d (%s)" % (len(it.sl), it.sl))
+ if it.ac:
+ wikic.write(it.id + ": " + it.name + "\n")
+
+ a=it.type
+ if "IT_HEALING" in a:
+ IT_HEALING.append(it)
+ elif "IT_ETC" in a:
+ IT_ETC.append(it)
+ elif "IT_USABLE" in a:
+ IT_USABLE.append(it)
+ elif "IT_AMMO" in a:
+ IT_AMMO.append(it)
+ elif "IT_CARD" in a:
+ IT_CARD.append(it)
+ elif "IT_PETEGG" in a:
+ IT_PETEGG.append(it)
+
+ elif "IT_WEAPON" in a:
+ if "HAND_L" in it.loc or "EQP_ARMS" in it.loc:
+ IT_WEAPON["HAND_2"].append(it)
+ elif "HAND_R" in it.loc:
+ IT_WEAPON["HAND_1"].append(it)
+ else:
+ raise Exception("Invalid location for weapon: %s" % it.loc)
+
+ elif "IT_ARMOR" in a:
+ if 'EQP_ACC_L' in it.loc:
+ IT_ARMOR['EQP_ACC_L'].append(it)
+ elif 'EQP_ACC_R' in it.loc:
+ IT_ARMOR['EQP_ACC_R'].append(it)
+ elif 'EQP_HEAD_MID' in it.loc:
+ IT_ARMOR['EQP_HEAD_MID'].append(it)
+ elif 'EQP_SHOES' in it.loc:
+ IT_ARMOR['EQP_SHOES'].append(it)
+ elif 'EQP_GARMENT' in it.loc:
+ IT_ARMOR['EQP_GARMENT'].append(it)
+ elif 'EQP_HEAD_LOW' in it.loc:
+ IT_ARMOR['EQP_HEAD_LOW'].append(it)
+ elif 'EQP_HEAD_TOP' in it.loc:
+ IT_ARMOR['EQP_HEAD_TOP'].append(it)
+ elif 'EQP_HAND_L' in it.loc:
+ IT_ARMOR['EQP_HAND_L'].append(it)
+ elif '1024' in it.loc:
+ IT_ARMOR['1024'].append(it)
+ elif '2048' in it.loc:
+ IT_ARMOR['2048'].append(it)
+ elif 'EQP_SHADOW_ACC_R' in it.loc:
+ IT_ARMOR['EQP_ACC_R'].append(it)
+ else:
+ raise Exception("Invalid Loc for ID %s: %s" % (it.id, it.loc))
+
+def newItemDB():
+ print("Generating Item Wiki...")
+ src=open("../../server-data/db/re/item_db.conf", "r")
+
+ x=It()
+ for a in src:
+ if a == "{\n":
+ ItAlloc(x)
+ x=It()
+
+ # sti() block
+ if " Id:" in a:
+ x.id=sti(a)
+ elif " AegisName:" in a:
+ x.aegis=sti(a)
+ elif " Name:" in a:
+ x.name=stin(a)
+ elif " Sell:" in a:
+ x.price=sti(a)
+ elif " Weight:" in a:
+ x.weight=sti(a)
+ elif " Type:" in a:
+ x.type=sti(a)
+ elif " Loc:" in a:
+ x.loc=sti(a)
+ elif " Atk:" in a:
+ x.atk=sti(a)
+ elif " Matk:" in a:
+ x.matk=sti(a)
+ elif " Range:" in a:
+ x.range=sti(a)
+ elif " Def:" in a:
+ x.defs=sti(a)
+ elif " EquipLv:" in a:
+ x.lvl=sti(a)
+ elif " Slots:" in a:
+ x.sl=sti(a)
+ elif " AllowCards:" in a:
+ x.ac=True
+ # Write booleans
+ elif "DropAnnounce: true" in a:
+ x.rare=True
+ elif "nodrop: true" in a:
+ x.drop=False
+ elif "notrade: true" in a:
+ x.trade=False
+ elif "noselltonpc: true" in a:
+ x.sell=False
+ elif "nostorage: true" in a:
+ x.store=False
+ elif "Script" in a:
+ x.script=True
+ # For healing items
+ elif "@min" in a:
+ x.minheal=sti(a)
+ elif "@max" in a:
+ x.maxheal=sti(a)
+ elif "@delay" in a:
+ x.delheal=sti(a)
+
+ # Write last entry
+ ItAlloc(x)
+ writeItems()
+
+ src.close()
+
+def sti(x):
+ return x.replace('\n', '').replace('|', '').replace(')', '').replace('Id: ', '').replace('"','').replace(" ","").replace("\t","").replace('AegisName: ', '').replace('Name: ','').replace('Sell: ', '').replace('Weight: ', '').replace('Type: ', '').replace('Loc: ', '').replace('Atk: ', '').replace('Matk: ', '').replace('Range: ', '').replace('Def: ', '').replace('EquipLv: ', '').replace('Slots: ','').replace(" ", "").replace('@min=','').replace('@max=','').replace('@delay=','').replace(';','')
+
+def stin(x):
+ return x.replace('\n', '').replace('|', '').replace(')', '').replace('Id: ', '').replace('"','').replace(" ","").replace("\t","").replace('Name: ','').replace(';','')
+
+
+def writeItems():
+ wikia.write("# Items\n\
++ [Healing Items](#healing-items)\n\
++ [Usable Items](#usable-items)\n\
++ [Generic Items](#generic-items)\n\
++ [Ammo](#ammo)\n\
++ [Cards](#cards)\n\
++ [Pet Eggs](#pet-eggs)\n\
++ [Weapons](#weapons)\n\
+ + [1H Weapons](#1h-weapons)\n\
+ + [2H Weapons](#2h-weapons)\n\
++ [Armors](#armors)\n\
+ + [Left Accessory](#left-accessory)\n\
+ + [Right Accessory](#right-accessory)\n\
+ + [Headgear](#headgear)\n\
+ + [Chest](#chest)\n\
+ + [Pants](#pants)\n\
+ + [Shoes](#shoes)\n\
+ + [Necklaces](#necklaces)\n\
+ + [Rings](#rings)\n\
+ + [Gloves](#gloves)\n\
+ + [Shields](#shields)\n\
+\n\n")
+ wikia.write("#### Restrictions Reference\n")
+ wikia.write("Special Aegis Name Markers:\n\
++ * - Rare item with drop announce.\n\
++ (dp) - This item cannot be dropped.\n\
++ (tr) - This item cannot de traded.\n\
++ (sl) - This item cannot be sold.\n\
++ (gg) - This item cannot go to storage.\n\n")
+
+ # Healing Items
+ wikia.write("## Healing Items\n\n")
+ ItemWrite(IT_HEALING, ID=True, AEGIS=True, PRICE=True, WEIGHT=True, HEALING=True, DROPPER=True)
+
+ # Usable Items
+ wikia.write("## Usable Items\n")
+ ItemWrite(IT_USABLE, ID=True, AEGIS=True, NAME=True, PRICE=True, WEIGHT=True, DROPPER=True)
+
+ # Generic Items
+ wikia.write("## Generic Items\n")
+ ItemWrite(IT_ETC, ID=True, AEGIS=True, NAME=True, PRICE=True, WEIGHT=True, DROPPER=True)
+
+ # Ammo Items
+ wikia.write("## Ammo\n")
+ ItemWrite(IT_AMMO, ID=True, AEGIS=True, NAME=True, WEIGHT=True, ATK=True)
+
+ # Card Items
+ wikia.write("## Cards\n")
+ ItemWrite(IT_CARD, ID=True, AEGIS=True, NAME=True, PRICE=True, WEIGHT=True)
+
+ # Pet Egg Items
+ wikia.write("## Pet Eggs\n")
+ ItemWrite(IT_PETEGG, ID=True, AEGIS=True, NAME=True, WEIGHT=True)
+
+ ####################################################################
+ wikia.write("# Weapons\n")
+
+ # 1 Hand Items
+ wikia.write("## 1H Weapons\n")
+ ItemWrite(IT_WEAPON['HAND_1'], ID=True, AEGIS=True, PRICE=True, WEIGHT=True, ATK=True, LVL=True, DROPPER=True)
+
+ # 2 Hand Items
+ wikia.write("## 2H Weapons\n")
+ ItemWrite(IT_WEAPON['HAND_2'], ID=True, AEGIS=True, PRICE=True, WEIGHT=True, ATK=True, LVL=True, RANGE=True)
+
+
+ ####################################################################
+ wikia.write("# Armors\n")
+
+ ArmorWrite("Left Accessory",'EQP_ACC_L')
+ ArmorWrite("Right Accessory",'EQP_ACC_R')
+ ArmorWrite("Headgear",'EQP_HEAD_TOP')
+ ArmorWrite("Chest",'EQP_HEAD_MID')
+ ArmorWrite("Pants",'EQP_HEAD_LOW')
+ ArmorWrite("Shoes",'EQP_SHOES')
+ ArmorWrite("Necklaces",'1024')
+ ArmorWrite("Rings",'2048')
+ ArmorWrite("Gloves",'EQP_GARMENT')
+ ArmorWrite("Shields",'EQP_HAND_L')
+
+# Write AegisName with restrictions
+def hl(it):
+ buff=""
+ if it.rare:
+ buff+="*"
+ buff+=it.aegis
+ buff+=" "
+ if not it.drop:
+ buff+="(dp) "
+ if not it.trade:
+ buff+="(tr) "
+ if not it.sell:
+ buff+="(sl) "
+ if not it.store:
+ buff+="(gg) "
+ return buff
+
+# wikia.write("Id|Aegis|Name|Weight|Atk|Matk|\n")
+# wikia.write("Id|Aegis|Name|Price|Weight|\n")
+
+def ItemWrite(tbl, ID=False, AEGIS=False, NAME=False, PRICE=False, WEIGHT=False, DEF=False, LVL=False, ATK=False, RANGE=False, HEALING=False, SCRIPT=False, DROPPER=False):
+ wikia.write("\n")
+ wikia.write("")
+ if ID:
+ wikia.write("ID ")
+ if AEGIS:
+ wikia.write("Aegis ")
+ if NAME:
+ wikia.write("Name ")
+ if PRICE:
+ wikia.write("Price ")
+ if WEIGHT:
+ wikia.write("Weight ")
+ if DEF:
+ wikia.write("Def ")
+ if LVL:
+ wikia.write("Lvl ")
+ if ATK:
+ wikia.write("Atk ")
+ wikia.write("Matk ")
+ if RANGE:
+ wikia.write("Range ")
+ if HEALING:
+ wikia.write("Min ")
+ wikia.write("Max ")
+ wikia.write("Delay ")
+ if SCRIPT:
+ wikia.write("Script ")
+ if DROPPER:
+ wikia.write("Mobs ")
+
+ wikia.write(" \n")
+
+ for i in tbl:
+ wikia.write('')
+
+ if ID:
+ wikia.write(" %s " % (i.id,i.id))
+ if AEGIS:
+ wikia.write("%s " % hl(i))
+ if NAME:
+ wikia.write("%s " % i.name)
+ if PRICE:
+ wikia.write("%s GP " % i.price)
+ if WEIGHT:
+ wikia.write("%s g " % i.weight)
+ if DEF:
+ wikia.write("Def: %s " % i.defs)
+ if LVL:
+ wikia.write("Lv: %s " % i.lvl)
+ if ATK:
+ wikia.write("Atk: %s " % i.atk)
+ wikia.write("%s " % i.matk)
+ if RANGE:
+ wikia.write("%s " % i.range)
+ if HEALING:
+ wikia.write("%s " % i.minheal)
+ wikia.write("%s " % i.maxheal)
+ wikia.write("%s s " % i.delheal)
+ if SCRIPT:
+ wikia.write("%s " % i.script)
+ # TODO: Check for item Aegis in npc/ folder too, to determine shops and quests.
+ if DROPPER:
+ tmp_droppers=""
+ tmp_drpalign=[]
+ for ax in SysDrops:
+ if ax[0] == i.aegis:
+ tmp_drpalign.append([ax[2], ax[1]])
+ if len(tmp_drpalign) > 0:
+ for a in sorted(tmp_drpalign, key=lambda xcv: xcv[1], reverse=True):
+ try:
+ ppm=int(a[1])/100.0
+ tmp_droppers+=("%s: %.2f %% \n" % (a[0], ppm))
+ except:
+ print("[Warning] %s whodrop error: %s" % (i.name, str(a)))
+ wikia.write("%s " % mbdt("monsters", tmp_droppers))
+ else:
+ wikia.write("- ")
+
+ wikia.write(" ")
+
+ wikia.write("
\n")
+ wikia.write("\n[(↑) Return to top](#items)\n\n")
+
+def ArmorWrite(name,scope):
+ wikia.write("## "+name+"\n")
+ ItemWrite(IT_ARMOR[scope], ID=True, AEGIS=True, PRICE=True, WEIGHT=True, DEF=True, LVL=True, SCRIPT=True)
+
+showHeader()
+
+testMobs()
+newItemDB()
+
+wikia.close()
+wikib.close()
+wikic.close()
+#print(str(SysDrops))
+
+showFooter()
+exit(0)
diff --git a/wiki/wikigen.py b/wiki/wikigen.py
new file mode 100755
index 0000000..c5ce71e
--- /dev/null
+++ b/wiki/wikigen.py
@@ -0,0 +1,898 @@
+#! /usr/bin/env python3
+# -*- coding: utf8 -*-
+#
+# Copyright (C) 2010-2011 Evol Online
+# Copyright (C) 2018 TMW-2
+# Author: Andrei Karas (4144)
+# Author: Jesusalva
+
+import datetime
+import sys
+
+wikia=open("Items.md", "w")
+wikib=open("Monsters.md", "w")
+wikic=open("../../client-data/dyes.diff", "w") # Dye Report
+wikid=open("../../server-data/changechase.diff", "w") # ChangeChase Report
+
+# the TYPEs we use to determine where to pack things
+IT_HEALING=[]
+IT_ETC=[]
+IT_USABLE=[]
+IT_AMMO=[]
+IT_CARD=[]
+IT_PETEGG=[]
+IT_WEAPON={ 'HAND_2': [], # TWO HAND (LR)
+ 'HAND_1':[]} # WEAPONS (R)
+IT_ARMOR={ 'MISC': [], # FOR FAILURE
+ 'EQP_ACC_L': [], # ACCESSORY LEFT
+ 'EQP_ACC_R': [], # ACCESSORT RIGHT
+ 'EQP_HEAD_MID': [], # CHEST
+ 'EQP_SHOES': [], # FEET
+ 'EQP_GARMENT': [], # GLOVES
+ 'EQP_HEAD_LOW':[], # PANTS
+ '1024': [], # NECKLACES (should be EQP_COSTUME_HEAD_TOP instead of number)
+ '2048': [], # RINGS (should be EQP_COSTUME_HEAD_MID instead of number)
+ 'EQP_MOUNT':[], # MOUNTS (ie. EQP_SHADOW_SHOES)
+ 'EQP_HEAD_TOP':[], # HATS/HELMETS
+ 'EQP_HAND_L': []} # SHIELDS
+
+Mobs1=[]
+Mobs2=[]
+Mobs3=[]
+Mobs4=[]
+Mobs5=[]
+Mobs6=[]
+MobsA=[]
+
+SysDrops=[]
+
+
+def printSeparator():
+ print("--------------------------------------------------------------------------------")
+
+def showHeader():
+ print("TMW2 Wiki Generator")
+ ##print "Evol client data validator."
+ print("Run at: " + datetime.datetime.now().isoformat())
+ print("Usage: ./wikigen.py [ ]")
+ ##print "https://gitlab.com/evol/evol-tools/blob/master/testxml/testxml.py"
+ printSeparator()
+
+def showFooter():
+ #pass
+ #printSeparator()
+ print("Done.")
+
+
+
+
+
+
+class Mob:
+ def __init__(self):
+ # Basic
+ self.id="0"
+ #self.aegis="UnknownMonster" # SpriteName is not used anywhere, we are using its ID
+ self.name="Unknown Monster Name"
+ self.view="1"
+ self.chch=False
+ self.boss=False
+
+ # Defensive
+ self.mobpt="0" # Mob Points “Level”
+ self.hp="0"
+ self.xp="0"
+ self.jp="0"
+ self.st=""
+
+ # Offensive
+ self.atk="[0, 0]"
+ self.range="0"
+ self.move="0"
+ self.delay="0"
+ self.drops=[]
+
+def MobAlloc(ab):
+ try:
+ maab=int(ab.mobpt)
+ except:
+ maab=9901
+
+ if maab <= 20:
+ Mobs1.append(ab)
+ elif maab <= 40:
+ Mobs2.append(ab)
+ elif maab <= 60:
+ Mobs3.append(ab)
+ elif maab <= 80:
+ Mobs4.append(ab)
+ elif maab <= 100:
+ Mobs5.append(ab)
+ elif maab <= 150:
+ Mobs6.append(ab)
+ elif maab != 9901:
+ MobsA.append(ab)
+ else:
+ print("WARNING, Disregarding \"%s\" (ID: %s) as invalid mob" % (ab.name, ab.id))
+
+def testMobs():
+ print("\nGenerating Mob Wiki...")
+ if len(sys.argv) >= 2:
+ src=open(sys.argv[1]+"/db/re/mob_db.conf", "r")
+ else:
+ src=open("../../server-data/db/re/mob_db.conf", "r")
+
+ wikib.write("# Monster Database\n")
+ start=False
+ dropper=False
+ x=Mob() # Only for pyflakes2
+
+ for a in src:
+ if a == "{\n":
+ if start:
+ MobAlloc(x)
+ else:
+ start=True
+ x=Mob()
+
+ if " Id:" in a:
+ x.id=stp(a)
+ elif " Name:" in a:
+ x.name=stp(a)
+ elif " Hp:" in a:
+ x.hp=stp(a)
+ elif " Lv:" in a:
+ x.mobpt=stp(a)
+ elif " Exp:" in a:
+ x.xp=stp(a)
+ elif " JExp:" in a:
+ x.jp=stp(a)
+ elif " Attack:" in a:
+ x.atk=stp(a)
+ elif " AttackRange:" in a:
+ x.range=stp(a)
+ elif " MoveSpeed:" in a:
+ x.move=stp(a)
+ elif " ViewRange:" in a:
+ x.view=stp(a)
+ elif " AttackDelay:" in a:
+ x.delay=stp(a)
+ elif " Boss: true" in a:
+ x.boss=True
+ elif " Looter: true" in a:
+ x.st+="Lot,"
+ elif " Assist: true" in a:
+ x.st+="Ass,"
+ elif " Aggressive: true" in a:
+ x.st+="Agr,"
+ elif " ChangeChase: true" in a:
+ x.chch=True
+ elif 'Drops: ' in a:
+ dropper=True
+ elif dropper and '}' in a:
+ dropper=False
+ elif dropper:
+ x.drops.append(stp(a).split(": "))
+ # Write last entry
+ MobAlloc(x)
+
+ writeMob()
+
+ wikib.write('\n\n|Mode|Desc|\n|----|----|\n')
+ wikib.write('|Lot|Looter|\n')
+ wikib.write('|Ass|Assist|\n')
+ wikib.write('|Agr|Aggressive|\n')
+
+ src.close()
+
+def stp(x):
+ return x.replace('\n', '').replace('|', '').replace('(int, defaults to ', '').replace(')', '').replace('basic experience', '').replace('"','').replace(" ","").replace("\t","").replace('(string', '').replace('Name: ','').replace('AttackDelay: ', '').replace('MoveSpeed: ', '').replace('AttackRange: ', '').replace('ViewRange: ','').replace('Attack: ','').replace('ViewRange: ','').replace('Hp: ','').replace('Id: ','').replace('Lv: ','').replace('view range','').replace('attack range','').replace('move speed','').replace('health','').replace('(int','').replace('attack delay','atk.')
+
+
+def MonsterWrite(tbl):
+ # TODO: Check _mobs files to determine the usual monster density (a misc info to aid adding proper drop specs)
+ wikib.write("\n")
+ wikib.write("ID Name HP Atk Delay Modes Misc Info Rewards Drops \n")
+ for i in tbl:
+ if not i.chch:
+ wikid.write("%s:%s\n" % (i.id, i.name))
+ if i.boss:
+ i.name=""+i.name+" "
+ wikib.write(' ' +
+ i.id +""+
+ i.name +" HP: "+
+ i.hp +" Atk: "+
+ i.atk +" "+
+ i.delay +" ms "+
+ i.st +" "+
+ mbdt('misc', mb_rdmisc(i)) +" "+
+ mbdt('Exp\'s', mb_rdrw(i)) +" "+
+ mbdt('drops', mb_rddrop(i)) +" \n"
+ )
+ wikib.write("
\n")
+ wikib.write("\n[(↑) Return to top](#monster-database)\n\n")
+
+def writeMob():
+ wikib.write("\
++ [Level 0-20](#starter)\n\
++ [Level 21-40](#apprentice)\n\
++ [Level 41-60](#intermediary)\n\
++ [Level 61-80](#advanced)\n\
++ [Level 81-100](#expert)\n\
++ [Level 101-150](#master)\n\
++ [Level 100+](#out-of-scope)\n\n\
+ ")
+
+ wikib.write("## Starter\n\n")
+ MonsterWrite(Mobs1)
+
+ wikib.write("## Apprentice\n\n")
+ MonsterWrite(Mobs2)
+
+ wikib.write("## Intermediary\n\n")
+ MonsterWrite(Mobs3)
+
+ wikib.write("## Advanced\n\n")
+ MonsterWrite(Mobs4)
+
+ wikib.write("## Expert\n\n")
+ MonsterWrite(Mobs5)
+
+ wikib.write("## Master\n\n")
+ MonsterWrite(Mobs6)
+
+ wikib.write("## Out Of Scope\n\n")
+ MonsterWrite(MobsA)
+
+
+def mbdt(summary, content):
+ return "\
+"+summary+" \
+"+content+" "
+
+def mb_rdmisc(mb):
+ buff=""
+ if "agr" in mb.st.lower():
+ buff+="View Range: %s\n" % (mb.view)
+ buff+="Attack Range: %s\n" % (mb.range)
+ buff+="Move speed: %s ms\n" % (mb.move)
+ return buff
+
+def mb_rdrw(mb):
+ buff=""
+ buff+="MobPoints: %s\n" % (mb.mobpt)
+ buff+="%s\n" % (mb.xp)
+ buff+="%s\n" % (mb.jp)
+ return buff
+
+def mb_rddrop(mb):
+ buff=""
+ # sorted
+ for ax in sorted(mb.drops, key=lambda xcv: float(xcv[1]), reverse=True):
+ # Ignore disabled drops
+ if ax[0].startswith("//"):
+ continue
+
+ # Write drop
+ try:
+ buff+=ax[0]+': ' + str(int(ax[1])/100.0) + ' %\n'
+ except IndexError:
+ print("Fatal: invalid %s mob with %s drops" % (mb.name, str(ax)))
+ exit(1)
+ except:
+ print("[Warning] %s incorrect drop: %s" % (mb.name, str(ax)))
+ buff+=ax[0]+': ' + ax[1] + ' ppm\n'
+
+ # Save to SysDrops
+ SysDrops.append([ax[0], ax[1], mb.name])
+
+ return buff
+
+
+class It:
+ def __init__(self):
+ # Basic
+ self.id="0"
+ self.aegis="UnknownItem"
+ self.name="Unknown Item Name"
+ self.price="0" # Sell price, of course
+ self.weight="0"
+ self.type="IT_ETC" # default type
+ self.loc=""
+
+ # Offensive/Defensive
+ self.atk="0"
+ self.matk="0"
+ self.range="0"
+ self.defs="0"
+
+ # Restrictions (EquipLv)
+ self.lvl="0"
+ self.drop=True
+ self.trade=True
+ self.sell=True
+ self.store=True
+
+ # Special settings
+ self.rare=False # DropAnnounce
+ self.script=False
+
+ # Visual
+ self.sl="0" # Slots
+ self.ac=False # Allow Cards
+
+ # Script settings
+ self.minheal="0"
+ self.maxheal="0"
+ self.delheal="0"
+ self.typheal="0"
+ self.rarheal="0"
+
+def ItAlloc(it):
+ if (it.sl == "0" and it.ac) or (it.sl in ["1","2","3","4"] and not it.ac):
+ print("WARNING, item id "+it.id+" invalid dye/card setting!")
+ if (len(it.sl) > 1):
+ print("WARNING, item id "+it.id+" bad slots length: %d (%s)" % (len(it.sl), it.sl))
+ if it.ac:
+ wikic.write(it.id + ": " + it.name + "\n")
+
+ a=it.type
+ if "IT_HEALING" in a:
+ IT_HEALING.append(it)
+ elif "IT_ETC" in a:
+ IT_ETC.append(it)
+ elif "IT_USABLE" in a:
+ IT_USABLE.append(it)
+ elif "IT_AMMO" in a:
+ IT_AMMO.append(it)
+ elif "IT_CARD" in a:
+ IT_CARD.append(it)
+ elif "IT_PETEGG" in a:
+ IT_PETEGG.append(it)
+
+ elif "IT_WEAPON" in a:
+ if "HAND_L" in it.loc or "EQP_ARMS" in it.loc:
+ IT_WEAPON["HAND_2"].append(it)
+ elif "HAND_R" in it.loc:
+ IT_WEAPON["HAND_1"].append(it)
+ else:
+ raise Exception("Invalid location for weapon: %s" % it.loc)
+
+ elif "IT_ARMOR" in a:
+ if 'EQP_ACC_L' in it.loc:
+ IT_ARMOR['EQP_ACC_L'].append(it)
+ elif 'EQP_ACC_R' in it.loc:
+ IT_ARMOR['EQP_ACC_R'].append(it)
+ elif 'EQP_HEAD_MID' in it.loc:
+ IT_ARMOR['EQP_HEAD_MID'].append(it)
+ elif 'EQP_SHOES' in it.loc:
+ IT_ARMOR['EQP_SHOES'].append(it)
+ elif 'EQP_GARMENT' in it.loc:
+ IT_ARMOR['EQP_GARMENT'].append(it)
+ elif 'EQP_HEAD_LOW' in it.loc:
+ IT_ARMOR['EQP_HEAD_LOW'].append(it)
+ elif 'EQP_HEAD_TOP' in it.loc:
+ IT_ARMOR['EQP_HEAD_TOP'].append(it)
+ elif 'EQP_HAND_L' in it.loc:
+ IT_ARMOR['EQP_HAND_L'].append(it)
+ elif '1024' in it.loc:
+ IT_ARMOR['1024'].append(it)
+ elif '2048' in it.loc:
+ IT_ARMOR['2048'].append(it)
+ elif 'EQP_SHADOW_SHOES' in it.loc:
+ IT_ARMOR['EQP_MOUNT'].append(it)
+ elif 'EQP_SHADOW_ACC_R' in it.loc:
+ IT_ARMOR['EQP_ACC_R'].append(it) # Not really
+ else:
+ raise Exception("Invalid Loc for ID %s: %s" % (it.id, it.loc))
+
+def newItemDB():
+ print("\nGenerating Item Wiki...")
+ if len(sys.argv) >= 2:
+ src=open(sys.argv[1]+"/db/re/item_db.conf", "r")
+ else:
+ src=open("../../server-data/db/re/item_db.conf", "r")
+
+ x=It()
+ for a in src:
+ if a == "{\n":
+ ItAlloc(x)
+ x=It()
+
+ # sti() block
+ if " Id:" in a:
+ x.id=sti(a)
+ elif " AegisName:" in a:
+ x.aegis=sti(a)
+ elif " Name:" in a:
+ x.name=stin(a)
+ elif " Sell:" in a:
+ x.price=sti(a)
+ elif " Weight:" in a:
+ x.weight=sti(a)
+ elif " Type:" in a:
+ x.type=sti(a)
+ elif " Loc:" in a:
+ x.loc=sti(a)
+ elif " Atk:" in a:
+ x.atk=sti(a)
+ elif " Matk:" in a:
+ x.matk=sti(a)
+ elif " Range:" in a:
+ x.range=sti(a)
+ elif " Def:" in a:
+ x.defs=sti(a)
+ elif " EquipLv:" in a:
+ x.lvl=sti(a)
+ elif " Slots:" in a:
+ x.sl=sti(a)
+ elif " AllowCards:" in a:
+ x.ac=True
+ # Write booleans
+ elif "DropAnnounce: true" in a:
+ x.rare=True
+ elif "nodrop: true" in a:
+ x.drop=False
+ elif "notrade: true" in a:
+ x.trade=False
+ elif "noselltonpc: true" in a:
+ x.sell=False
+ elif "nostorage: true" in a:
+ x.store=False
+ elif "Script" in a:
+ x.script=True
+ # For healing items
+ elif "@min " in a:
+ x.minheal=sti(a)
+ elif "@max " in a:
+ x.maxheal=sti(a)
+ elif "@delay" in a:
+ x.delheal=sti(a)
+ elif "@type" in a:
+ x.typheal=sti(a)
+ try:
+ x.minheal=str(int(x.rarheal) * (int(x.typheal)*1 + 1)) + " %"
+ x.maxheal=str(int(x.rarheal) * (int(x.typheal)*2 + 1)) + " %"
+ if (x.delheal == "0"):
+ x.delheal=int(x.typheal)*2 + 1
+ except:
+ x.delheal="ERROR"
+ pass
+ elif "@rarity" in a:
+ x.rarheal=sti(a)
+ x.minheal=str(int(x.rarheal) * (int(x.typheal)*1 + 1)) + " %"
+ x.maxheal=str(int(x.rarheal) * (int(x.typheal)*2 + 1)) + " %"
+
+ # Write last entry
+ ItAlloc(x)
+ writeItems()
+
+ src.close()
+
+def sti(x):
+ return x.replace('\n', '').replace('|', '').replace(')', '').replace('Id: ', '').replace('"','').replace(" ","").replace("\t","").replace('AegisName: ', '').replace('Name: ','').replace('Sell: ', '').replace('Weight: ', '').replace('Type: ', '').replace('Loc: ', '').replace('Atk: ', '').replace('Matk: ', '').replace('Range: ', '').replace('Def: ', '').replace('EquipLv: ', '').replace('Slots: ','').replace(" ", "").replace('@min=','').replace('@max=','').replace('@delay=','').replace('@type=','').replace('@rarity=','').replace(';','')
+
+def stin(x):
+ return x.replace('\n', '').replace('|', '').replace(')', '').replace('Id: ', '').replace('"','').replace(" ","").replace("\t","").replace('Name: ','').replace(';','')
+
+
+def writeItems():
+ wikia.write("# Items\n\
++ [Healing Items](#healing-items)\n\
++ [Usable Items](#usable-items)\n\
++ [Generic Items](#generic-items)\n\
++ [Ammo](#ammo)\n\
++ [Cards](#cards)\n\
++ [Pet Eggs](#pet-eggs)\n\
++ [Mounts](#mounts)\n\
++ [Weapons](#weapons)\n\
+ + [1H Weapons](#1h-weapons)\n\
+ + [2H Weapons](#2h-weapons)\n\
++ [Armors](#armors)\n\
+ + [Left Accessory](#left-accessory)\n\
+ + [Right Accessory](#right-accessory)\n\
+ + [Headgear](#headgear)\n\
+ + [Chest](#chest)\n\
+ + [Pants](#pants)\n\
+ + [Shoes](#shoes)\n\
+ + [Necklaces](#necklaces)\n\
+ + [Rings](#rings)\n\
+ + [Gloves](#gloves)\n\
+ + [Shields](#shields)\n\
+\n\n")
+ wikia.write("#### Restrictions Reference\n")
+ wikia.write("Special Aegis Name Markers:\n\
++ * - Rare item with drop announce.\n\
++ (dp) - This item cannot be dropped.\n\
++ (tr) - This item cannot de traded.\n\
++ (sl) - This item cannot be sold.\n\
++ (gg) - This item cannot go to storage.\n\n")
+
+ # Healing Items
+ wikia.write("## Healing Items\n\n")
+ ItemWrite(IT_HEALING, ID=True, AEGIS=True, PRICE=True, WEIGHT=True, HEALING=True, DROPPER=True)
+
+ # Usable Items
+ wikia.write("## Usable Items\n")
+ ItemWrite(IT_USABLE, ID=True, AEGIS=True, NAME=True, PRICE=True, WEIGHT=True, DROPPER=True)
+
+ # Generic Items
+ wikia.write("## Generic Items\n")
+ ItemWrite(IT_ETC, ID=True, AEGIS=True, NAME=True, PRICE=True, WEIGHT=True, DROPPER=True)
+
+ # Ammo Items
+ wikia.write("## Ammo\n")
+ ItemWrite(IT_AMMO, ID=True, AEGIS=True, NAME=True, WEIGHT=True, ATK=True)
+
+ # Card Items
+ wikia.write("## Cards\n")
+ ItemWrite(IT_CARD, ID=True, AEGIS=True, NAME=True, PRICE=True, WEIGHT=True)
+
+ # Pet Egg Items
+ wikia.write("## Pet Eggs\n")
+ ItemWrite(IT_PETEGG, ID=True, AEGIS=True, NAME=True, WEIGHT=True)
+
+ # Mount Items
+ wikia.write("## Mounts\n")
+ ItemWrite(IT_ARMOR['EQP_MOUNT'], ID=True, AEGIS=True, NAME=True, WEIGHT=True)
+
+ ####################################################################
+ wikia.write("# Weapons\n")
+
+ # 1 Hand Items
+ wikia.write("## 1H Weapons\n")
+ ItemWrite(IT_WEAPON['HAND_1'], ID=True, AEGIS=True, PRICE=True, WEIGHT=True, ATK=True, LVL=True, DROPPER=True)
+
+ # 2 Hand Items
+ wikia.write("## 2H Weapons\n")
+ ItemWrite(IT_WEAPON['HAND_2'], ID=True, AEGIS=True, PRICE=True, WEIGHT=True, ATK=True, LVL=True, RANGE=True)
+
+
+ ####################################################################
+ wikia.write("# Armors\n")
+
+ ArmorWrite("Left Accessory",'EQP_ACC_L', False)
+ ArmorWrite("Right Accessory",'EQP_ACC_R', False)
+ ArmorWrite("Headgear",'EQP_HEAD_TOP')
+ ArmorWrite("Chest",'EQP_HEAD_MID')
+ ArmorWrite("Pants",'EQP_HEAD_LOW')
+ ArmorWrite("Shoes",'EQP_SHOES')
+ ArmorWrite("Necklaces",'1024', False)
+ ArmorWrite("Rings",'2048', False)
+ ArmorWrite("Gloves",'EQP_GARMENT')
+ ArmorWrite("Shields",'EQP_HAND_L')
+
+# Write AegisName with restrictions
+def hl(it):
+ buff=""
+ if it.rare:
+ buff+="*"
+ buff+=it.aegis
+ buff+=" "
+ if not it.drop:
+ buff+="(dp) "
+ if not it.trade:
+ buff+="(tr) "
+ if not it.sell:
+ buff+="(sl) "
+ if not it.store:
+ buff+="(gg) "
+ return buff
+
+# wikia.write("Id|Aegis|Name|Weight|Atk|Matk|\n")
+# wikia.write("Id|Aegis|Name|Price|Weight|\n")
+
+def ItemWrite(tbl, ID=False, AEGIS=False, NAME=False, PRICE=False, WEIGHT=False, DEF=False, LVL=False, ATK=False, RANGE=False, HEALING=False, SCRIPT=False, DROPPER=False):
+ wikia.write("\n")
+ wikia.write("")
+ if ID:
+ wikia.write("ID ")
+ if AEGIS:
+ wikia.write("Aegis ")
+ if NAME:
+ wikia.write("Name ")
+ if PRICE:
+ wikia.write("Price ")
+ if WEIGHT:
+ wikia.write("Weight ")
+ if DEF:
+ wikia.write("Def ")
+ if LVL:
+ wikia.write("Lvl ")
+ if ATK:
+ wikia.write("Atk ")
+ wikia.write("Matk ")
+ if RANGE:
+ wikia.write("Range ")
+ if HEALING:
+ wikia.write("Min ")
+ wikia.write("Max ")
+ wikia.write("Delay ")
+ if SCRIPT:
+ wikia.write("Script ")
+ if DROPPER:
+ wikia.write("Mobs ")
+
+ wikia.write(" \n")
+
+ for i in tbl:
+ """
+ # To sort by weight, for example, uncomment this block.
+ try:
+ zt=int(i.weight)
+ except:
+ tbl.remove(i)
+
+ for i in sorted(tbl, key=lambda xcv: int(xcv.weight), reverse=True):
+ """
+
+ wikia.write('')
+
+ if ID:
+ wikia.write(" %s " % (i.id,i.id))
+ if AEGIS:
+ wikia.write("%s " % hl(i))
+ if NAME:
+ wikia.write("%s " % i.name)
+ if PRICE:
+ wikia.write("%s GP " % i.price)
+ if WEIGHT:
+ wikia.write("%s g " % i.weight)
+ if DEF:
+ wikia.write("Def: %s " % i.defs)
+ if LVL:
+ wikia.write("Lv: %s " % i.lvl)
+ if ATK:
+ wikia.write("Atk: %s " % i.atk)
+ wikia.write("%s " % i.matk)
+ if RANGE:
+ wikia.write("%s " % i.range)
+ if HEALING:
+ wikia.write("%s " % i.minheal)
+ wikia.write("%s " % i.maxheal)
+ wikia.write("%s s " % i.delheal)
+ if SCRIPT:
+ wikia.write("%s " % i.script)
+ # TODO: Check for item Aegis in npc/ folder too, to determine shops and quests.
+ if DROPPER:
+ tmp_droppers=""
+ tmp_drpalign=[]
+ for ax in SysDrops:
+ if ax[0] == i.aegis:
+ tmp_drpalign.append([ax[2], ax[1]])
+ if len(tmp_drpalign) > 0:
+ for a in sorted(tmp_drpalign, key=lambda xcv: float(xcv[1]), reverse=True):
+ try:
+ ppm=int(a[1])/100.0
+ tmp_droppers+=("%s: %.2f %% \n" % (a[0], ppm))
+ except:
+ print("[Warning] %s whodrop error: %s" % (i.name, str(a)))
+ wikia.write("%s " % mbdt("monsters", tmp_droppers))
+ else:
+ wikia.write("- ")
+
+ wikia.write(" ")
+
+ wikia.write("
\n")
+ wikia.write("\n[(↑) Return to top](#items)\n\n")
+
+def ArmorWrite(name,scope,defitem=True):
+ wikia.write("## "+name+"\n")
+ ItemWrite(IT_ARMOR[scope], ID=True, AEGIS=True, PRICE=True, WEIGHT=True, DEF=defitem, LVL=True, DROPPER=True)
+
+
+
+
+
+
+
+
+
+
+
+class Quest:
+ def __init__(self, ide):
+ # Basic
+ self.id=ide
+ self.name="Unknown Quest Name"
+ self.group="Unknown"
+ self.ent=[]
+ self.level=0
+
+class QuestEntry:
+ def __init__(self):
+ # Basic
+ self.complete=False
+ self.entry=[] # collection of
+ self.giver=""
+ self.reward=""
+ self.loc=""
+
+def sortlv(val):
+ return val[1]
+
+def qnt(string):
+ return string.replace(' ','').replace('"','').replace("'","").replace('<','').replace('>','').replace('nowiki=1', '').replace('nowiki', '')
+
+def qnt2(string):
+ return string.replace('##B','**').replace('##b','**').replace('##0','*').replace('##1','*').replace('##2','*').replace('##3','*').replace('##','*')
+
+def DoQuest():
+ print("\nGenerating Quest Wiki...")
+ if len(sys.argv) >= 3:
+ src=open(sys.argv[2]+"/quests.xml", "r")
+ else:
+ src=open("../../client-data/quests.xml", "r")
+
+ qlog=[]
+ q=Quest(-1)
+ qe=QuestEntry()
+ ig=False
+ nw=False
+
+ for e in src:
+ # Handle Comments and Ignored lines
+ if '' in e:
+ continue
+ elif '' in e:
+ ig=False
+ if '' in e:
+ q.ent.append(qe)
+ elif ' tag: %s (arg was %s) (line was %s)" % (e, rc[1], l))
+ exit(1)
+ rc=[False, ""]
+
+ # Fill stuff in Quest Entry
+ if '','').replace(' ','').replace('','').replace('','').replace('','').replace('','').strip()
+ elif '','').replace(' ','').replace("@@", "text").replace('')
+ qe.loc=b[1].replace('','').replace('','').strip() )
+ if (not q.level):
+ try:
+ q.level=int(a.replace('','').replace(' ','').strip())
+ except:
+ pass
+
+ # Done reading file
+ src.close()
+ aktbl={}
+ aksort=[]
+ print("\033[32;1mTotal quests: %d\033[0m" % len(qlog))
+ for i in qlog:
+ if i.name=="Unknown Quest Name":
+ print("Warning, invalid quest: %d" % (i.id))
+ qlog.remove(i)
+ continue
+ # Total Table
+ #print(str(i.id)+": "+i.name)
+ try:
+ if (i.level):
+ aktbl[i.group].append(("[%s](q/%d) (Lv %d)" % (i.name, i.id, i.level), i.level))
+ else:
+ aktbl[i.group].append(("[%s](q/%d)" % (i.name, i.id), i.level))
+ except:
+ if (i.level):
+ aktbl[i.group]=[("[%s](q/%d) (Lv %d)" % (i.name, i.id, i.level), i.level)]
+ else:
+ aktbl[i.group]=[("[%s](q/%d)" % (i.name, i.id), i.level)]
+ aksort.append(i.group)
+
+ for key in aktbl:
+ #print(aktbl[key]);
+ aktbl[key]=sorted(aktbl[key], key=sortlv)
+
+ # Individual file
+ f=open("../../wiki/q/"+str(i.id)+'.md', "w")
+ f.write("\n\n" % (i.id, i.name))
+ f.write("# %s\n" % i.name)
+ f.write('\n')
+
+ totalcnt=0
+ for a in i.ent:
+ totalcnt+=1
+ f.write('\n### %d Stage\n\n' % totalcnt)
+ if a.complete:
+ f.write('*This completes quest*\n\n')
+
+ if a.giver != "" or a.reward != "" or a.loc != "":
+ f.write('```\n')
+ if a.giver != "":
+ f.write('Quest Giver: %s\n' % a.giver)
+ if a.reward != "":
+ f.write('Reward: %s\n' % a.reward.replace('@@', ''))
+ if a.loc != "":
+ f.write('Location: %s\n' % a.loc)
+ f.write('```\n\n')
+
+ for line in a.entry:
+ f.write('%s\n' % line)
+
+ f.write('\n\n****\nThis file is generated automatically. Editing it will have no effect.\n')
+ f.close()
+
+ # Write total table
+ f=open("Quests.txt", "w")
+ f.write("***Total quests: %d***\n" % len(qlog))
+ for key in aksort:
+ f.write('\n## %s\n\n' % key)
+ # TODO: Sort by Quest Level
+ for a in aktbl[key]:
+ f.write('+ '+a[0]+'\n')
+ f.close()
+
+showHeader()
+
+testMobs()
+newItemDB()
+DoQuest()
+
+wikia.close()
+wikib.close()
+wikic.close()
+wikid.close()
+#print(str(SysDrops))
+
+showFooter()
+exit(0)
--
cgit v1.2.3-70-g09d2