Jelajahi Sumber

- generic string ID -> numeric ID resolution system
- - hero army and creature upgrade names are resolved using new system
- - faction names and creatures in towns are resolved using new system
- (linux) replaced build_data.sh with hopefully better vcmibuilder script
- minor fixes

Ivan Savenko 13 tahun lalu
induk
melakukan
85a23e298c

+ 2 - 0
CMakeLists.txt

@@ -113,6 +113,8 @@ if (NOT APPLE)
 	install(DIRECTORY Mods/vcmi DESTINATION ${DATA_DIR}/Mods PATTERN ".svn" EXCLUDE)
 	install(DIRECTORY Mods/vcmi DESTINATION ${DATA_DIR}/Mods PATTERN ".svn" EXCLUDE)
 	# copy only fs.json for WoG
 	# copy only fs.json for WoG
 	install(FILES Mods/WoG/filesystem.json DESTINATION ${DATA_DIR}/Mods/WoG)
 	install(FILES Mods/WoG/filesystem.json DESTINATION ${DATA_DIR}/Mods/WoG)
+
+	install(FILES vcmibuilder DESTINATION ${BIN_DIR})
 endif()
 endif()
 
 
 if(WIN32)
 if(WIN32)

+ 0 - 160
build_data.sh

@@ -1,160 +0,0 @@
-#!/bin/sh
-
-# Copyright (c) 2009,2010,2011 Frank Zago
-#
-# 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.
-
-
-# Extract game data from various archives and create a tree with the right files
-
-# Tools needed:
-#   unshield: sudo apt-get install unshield
-#   rename (from perl)
-#   unrar: sudo apt-get install unrar
-
-# Data files needed:
-#   data1.cab and data1.hdr from the original 1st CDROM
-#   Heroes3.snd from the original 2nd CDROM
-#   the WoG release v3.58f: allinone_358f.zip
-#   the VCMI distribution: vcmi_089.zip
-#   the menu graphic pack: vcmi32menu.zip
-
-# Usage: put this script and the 4 data files into the same directory
-# and run the script.
-
-
-DESTDIR=`pwd`/vcmi
-rm -rf $DESTDIR
-mkdir $DESTDIR
-
-# Extract Data from original CD-ROM
-# 376917499 2002-06-14 14:32 _setup/data1.cab
-# 27308     2002-06-14 14:32 _setup/data1.hdr
-
-rm -rf temp
-mkdir temp
-cd temp
-
-echo Extracting data1.cab
-unshield x ../data1.cab || exit 1
-echo Done extracting data1.cab
-
-rm -rf CommonFiles
-rm -rf GameUpdate
-rm -rf Support
-rm -rf Program_Files/mplayer
-rm -rf Program_Files/ONLINE
-
-find . -name "*.DLL" | xargs rm -f
-find . -name "*.dll" | xargs rm -f
-find . -name "*.pdf" -print0  | xargs -0 rm -f
-find . -name "*.HLP" | xargs rm -f
-find . -name "*.TXT" | xargs rm -f
-find . -name "*.cnt" | xargs rm -f
-find . -name "*.exe" | xargs rm -f
-find . -name "*.EXE" | xargs rm -f
-find . -name "*.ASI" | xargs rm -f
-
-# Tree is clean. Move extracted files to their final destination
-mv Program_Files/* $DESTDIR
-cd ..
-
-# Copy Heroes3.snd from 2nd CDROM and rename it to avoid a name conflict.
-cp Heroes3.snd $DESTDIR/Data/Heroes3-cd2.snd
-
-# Extract Data from WoG
-# 39753248 allinone_358f.zip
-
-rm -rf temp
-mkdir temp
-cd temp
-
-unzip ../allinone_358f.zip
-cd WoG_Install
-mkdir temp
-cd temp
-unrar x -o+ ../main1.wog
-unrar x -o+ ../main2.wog
-unrar x -o+ ../main3.wog
-unrar x -o+ ../main4.wog
-#unrar x -o+ ../main5.wog
-#unrar x -o+ ../main6_optional.wog
-#unrar x -o+ ../main7_optional.wog
-#unrar x -o+ ../main8_optional.wog
-#unrar x -o+ ../main9_optional.wog
-
-mkdir -p $DESTDIR/Data/zvs/
-mv data/zvs/Lib1.res $DESTDIR/Data/zvs/
-
-rm -rf picsall Documentation Data data update
-rm -f action.txt h3bitmap.txt H3sprite.txt InstMult.txt
-rm -f ACTION.TXT H3BITMAP.TXT H3SPRITE.TXT INSTMULT.TXT
-rm -f *.DLL *.dll *.fnt readme.txt *.exe *.EXE *.lod *.vid h3ab_ahd.snd
-
-rename 'y/a-z/A-Z/' *
-
-# Tree is clean. Move extracted files to their final destination
-
-mv MAPS/* $DESTDIR/Maps
-rmdir MAPS
-mkdir $DESTDIR/Sprites
-mv *.MSK *.DEF $DESTDIR/Sprites
-mv * $DESTDIR/Data/
-
-cd ../../..
-
-
-# Extract Data from VCMI release
-
-rm -rf temp
-mkdir temp
-cd temp
-
-#7zr x ../vcmi_088b.7z
-unzip ../vcmi_089.zip
-
-find . -name "*.dll" | xargs rm -f
-find . -name "*.DLL" | xargs rm -f
-find . -name "*.exe" | xargs rm -f
-rm -rf AI
-rm -f AUTHORS ChangeLog license.txt Microsoft.VC90.CRT.manifest
-rm -rf MP3
-rm -rf Games
-
-# Tree is clean. Move extracted files to their final destination
-
-cp -a . $DESTDIR
-
-cd ..
-
-
-# Extract graphics package
-
-rm -rf temp
-mkdir temp
-cd temp
-
-unzip ../vcmi32menu.zip
-
-# Tree is already clean. Move extracted files to their final destination
-
-cp -a . $DESTDIR
-
-cd ..
-
-
-# Done
-echo
-echo The complete game data is at $DESTDIR
-echo You could move it to /usr/local/share/games/
-echo and configure vcmi with:
-echo   ./configure --datadir=/usr/local/share/games/ --bindir=\`pwd\` --libdir=\`pwd\`
-echo
-echo If you want to run from your VCMI tree, create those links
-echo at the root of your VCMI tree:
-echo   ln -s client/vcmiclient .
-echo   ln -s server/vcmiserver .
-echo   ln -s AI/GeniusAI/.libs/GeniusAI.so .

+ 1 - 1
client/AdventureMapClasses.cpp

@@ -294,7 +294,7 @@ void CTownList::CTownItem::update()
 {
 {
 	size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->builded >= CGI->modh->settings.MAX_BUILDING_PER_TURN];
 	size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->builded >= CGI->modh->settings.MAX_BUILDING_PER_TURN];
 
 
-	picture->setFrame(iconIndex);
+	picture->setFrame(iconIndex + 2);
 	redraw();
 	redraw();
 }
 }
 
 

+ 1 - 1
client/CCastleInterface.cpp

@@ -1072,7 +1072,7 @@ void CCreaInfo::clickRight(tribool down, bool previousState)
 			CInfoPopup *mess = new CInfoPopup();//creating popup
 			CInfoPopup *mess = new CInfoPopup();//creating popup
 			mess->free = true;
 			mess->free = true;
 			mess->bitmap = CMessage::drawBoxTextBitmapSub
 			mess->bitmap = CMessage::drawBoxTextBitmapSub
-			(LOCPLINT->playerID, descr,graphics->bigImgs[creature->idNumber],"");
+			(LOCPLINT->playerID, descr,graphics->bigImgs[creature->iconIndex],"");
 			mess->pos.x = screen->w/2 - mess->bitmap->w/2;
 			mess->pos.x = screen->w/2 - mess->bitmap->w/2;
 			mess->pos.y = screen->h/2 - mess->bitmap->h/2;
 			mess->pos.y = screen->h/2 - mess->bitmap->h/2;
 			GH.pushInt(mess);
 			GH.pushInt(mess);

+ 1 - 1
client/CVideoHandler.cpp

@@ -837,7 +837,7 @@ bool CVideoPlayer::open(std::string fname, bool loop, bool useOverlay)
 	{
 	{
 
 
 		PixelFormat screenFormat = PIX_FMT_NONE;
 		PixelFormat screenFormat = PIX_FMT_NONE;
-		if (screen->format->Bshift < screen->format->Rshift)
+		if (screen->format->Bshift > screen->format->Rshift)
 		{
 		{
 			// this a BGR surface
 			// this a BGR surface
 			switch (screen->format->BytesPerPixel)
 			switch (screen->format->BytesPerPixel)

+ 90 - 9
config/buildings.json

@@ -134,7 +134,16 @@
 				[ [ 30, 37 ], [ 31, 38 ], [ 32, 39 ], [ 33, 40 ] ],
 				[ [ 30, 37 ], [ 31, 38 ], [ 32, 39 ], [ 33, 40 ] ],
 				[ [ 34, 41 ], [ 35, 42 ], [ 36, 43 ] ]
 				[ [ 34, 41 ], [ 35, 42 ], [ 36, 43 ] ]
 			],
 			],
-			"creatures" : [ [0, 1], [2, 3], [4, 5], [6, 7], [8, 9], [10, 11], [12, 13] ],
+			"creatures" :
+			[
+				["Pikeman", "Halberdier"],
+				["Archer", "HeavyCrossbowman"],
+				["Griffin", "RoyalGriffin"],
+				["Swordsman", "Crusader"],
+				["Monk", "Zealot"],
+				["Cavalier", "Champion"],
+				["Angel", "Archangel"]
+			],
 			"horde" : [ 2, -1 ],
 			"horde" : [ 2, -1 ],
 			"primaryResource" : 127,
 			"primaryResource" : 127,
 			"mageGuild" : 4,
 			"mageGuild" : 4,
@@ -375,7 +384,16 @@
 				[ [ 30, 37 ], [ 31, 38 ], [ 32, 39 ], [ 33, 40 ] ],
 				[ [ 30, 37 ], [ 31, 38 ], [ 32, 39 ], [ 33, 40 ] ],
 				[ [ 34, 41 ], [ 35, 42 ], [ 36, 43 ] ]
 				[ [ 34, 41 ], [ 35, 42 ], [ 36, 43 ] ]
 			],
 			],
-			"creatures" : [ [14, 15], [16, 17], [18, 19], [20, 21], [22, 23], [24, 25], [26, 27] ],
+			"creatures" :
+			[
+				["Centaur", "EliteCentaur"],
+				["Dwarf", "BattleDwarf"],
+				["WoodElf", "GrandElf"],
+				["Pegasus", "SilverPegasus"],
+				["Treefolk", "BriarTreefolk"],
+				["Unicorn", "WarUnicorn"],
+				["GreenDragon", "GoldDragon"]
+			],
 			"horde" : [ 1, 4 ],
 			"horde" : [ 1, 4 ],
 			"mageGuild" : 5,
 			"mageGuild" : 5,
 			"primaryResource" : 4,
 			"primaryResource" : 4,
@@ -612,7 +630,16 @@
 				[ [ 30, 37 ], [ 31, 38 ], [ 32, 39 ], [ 33, 40 ] ],
 				[ [ 30, 37 ], [ 31, 38 ], [ 32, 39 ], [ 33, 40 ] ],
 				[ [ 34, 41 ], [ 35, 42 ], [ 36, 43 ] ]
 				[ [ 34, 41 ], [ 35, 42 ], [ 36, 43 ] ]
 			],
 			],
-			"creatures" : [ [28, 29], [30, 31], [32, 33], [34, 35], [36, 37], [38, 39], [40, 41] ],
+			"creatures" :
+			[
+				["ApprenticeGremlin", "MasterGremlin"],
+				["StoneGargoyle", "ObsidianGargoyle"],
+				["IronGolem", "StoneGolem"],
+				["Mage", "ArchMage"],
+				["Genie", "Caliph"],
+				["NagaSentinel", "NagaGuardian"],
+				["LesserTitan", "GreaterTitan"]
+			],
 			"horde" : [ 1, -1 ],
 			"horde" : [ 1, -1 ],
 			"primaryResource" : 5,
 			"primaryResource" : 5,
 			"mageGuild" : 5,
 			"mageGuild" : 5,
@@ -847,7 +874,16 @@
 				[ [ 30, 37 ], [ 31, 38 ], [ 32, 39 ], [ 33, 40 ] ],
 				[ [ 30, 37 ], [ 31, 38 ], [ 32, 39 ], [ 33, 40 ] ],
 				[ [ 34, 41 ], [ 35, 42 ], [ 36, 43 ] ]
 				[ [ 34, 41 ], [ 35, 42 ], [ 36, 43 ] ]
 			],
 			],
-			"creatures" : [ [42, 43], [44, 45], [46, 47], [48, 49], [50, 51], [52, 53], [54, 55] ],
+			"creatures" :
+			[
+				["Imp", "Familiar"],
+				["Gog", "Magog"],
+				["HellHound", "Cerberus"],
+				["Single-HornedDemon", "Dual-HornedDemon"],
+				["PitFiend", "PitFoe"],
+				["Efreet", "EfreetSultan"],
+				["Devil", "ArchDevil"]
+			],
 			"horde" : [ 0, 2 ],
 			"horde" : [ 0, 2 ],
 			"mageGuild" : 5,
 			"mageGuild" : 5,
 			"primaryResource" : 1,
 			"primaryResource" : 1,
@@ -1088,7 +1124,16 @@
 				[ [ 30, 37 ], [ 31, 38 ], [ 32, 39 ], [ 33, 40 ] ],
 				[ [ 30, 37 ], [ 31, 38 ], [ 32, 39 ], [ 33, 40 ] ],
 				[ [ 34, 41 ], [ 35, 42 ], [ 36, 43 ] ]
 				[ [ 34, 41 ], [ 35, 42 ], [ 36, 43 ] ]
 			],
 			],
-			"creatures" : [ [56, 57], [58, 59], [60, 61], [62, 63], [64, 65], [66, 67], [68, 69] ],
+			"creatures" :
+			[
+				["Skeleton", "SkeletonWarrior"],
+				["Zombie", "ZombieLord"],
+				["Wight", "Wraith"],
+				["Vampire", "Nosferatu"],
+				["Lich", "PowerLich"],
+				["BlackKnight", "BlackLord"],
+				["BoneDragon", "GhostDragon"]
+			],
 			"horde" : [ 0, -1 ],
 			"horde" : [ 0, -1 ],
 			"mageGuild" : 5,
 			"mageGuild" : 5,
 			"primaryResource" : 127,
 			"primaryResource" : 127,
@@ -1323,7 +1368,16 @@
 				[ [ 30, 37 ], [ 31, 38 ], [ 32, 39 ], [ 33, 40 ] ],
 				[ [ 30, 37 ], [ 31, 38 ], [ 32, 39 ], [ 33, 40 ] ],
 				[ [ 34, 41 ], [ 35, 42 ], [ 36, 43 ] ]
 				[ [ 34, 41 ], [ 35, 42 ], [ 36, 43 ] ]
 			],
 			],
-			"creatures" : [ [70, 71], [72, 73], [74, 75], [76, 77], [78, 79], [80, 81], [82, 83] ],
+			"creatures" :
+			[
+				["Troglodyte", "InfernalTroglodyte"],
+				["Harpy", "HarpyHag"],
+				["Beholder", "EvilEye"],
+				["Medusa", "MedusaQueen"],
+				["Minotaur", "MinotaurKing"],
+				["Manticore", "Scorpicore"],
+				["RedDragon", "BlackDragon"]
+			],
 			"horde" : [ 0, -1 ],
 			"horde" : [ 0, -1 ],
 			"mageGuild" : 5,
 			"mageGuild" : 5,
 			"primaryResource" : 3,
 			"primaryResource" : 3,
@@ -1556,7 +1610,16 @@
 				[ [ 30, 37 ], [ 31, 38 ], [ 32, 39 ], [ 33, 40 ] ],
 				[ [ 30, 37 ], [ 31, 38 ], [ 32, 39 ], [ 33, 40 ] ],
 				[ [ 34, 41 ], [ 35, 42 ], [ 36, 43 ] ]
 				[ [ 34, 41 ], [ 35, 42 ], [ 36, 43 ] ]
 			],
 			],
-			"creatures" : [ [84, 85], [86, 87], [88, 89], [90, 91], [92, 93], [94, 95], [96, 97] ],
+			"creatures" :
+			[
+				["Goblin", "Hobgoblin"],
+				["GoblinWolfRider", "HobgoblinWolfRider"],
+				["Orc", "OrcChieftain"],
+				["Ogre", "OgreMage"],
+				["Roc", "Thunderbird"],
+				["Cyclops", "CyclopsLord"],
+				["YoungBehemoth", "AncientBehemoth"]
+			],
 			"horde" : [ 0, -1 ],
 			"horde" : [ 0, -1 ],
 			"mageGuild" : 3,
 			"mageGuild" : 3,
 			"primaryResource" : 127,
 			"primaryResource" : 127,
@@ -1791,7 +1854,16 @@
 				[ [ 30, 37 ], [ 31, 38 ], [ 32, 39 ], [ 33, 40 ] ],
 				[ [ 30, 37 ], [ 31, 38 ], [ 32, 39 ], [ 33, 40 ] ],
 				[ [ 34, 41 ], [ 35, 42 ], [ 36, 43 ] ]
 				[ [ 34, 41 ], [ 35, 42 ], [ 36, 43 ] ]
 			],
 			],
-			"creatures" : [ [98, 99], [100, 101], [104, 105], [106, 107], [102, 103], [108, 109], [110, 111] ],
+			"creatures" :
+			[
+				["Gnoll", "GnollMarauder"],
+				["PrimitiveLizardman", "AdvancedLizardman"],
+				["Dragonflies", "FireDragonFly"],
+				["Basilisk", "GreaterBasilisk"],
+				["CopperGorgon", "BronzeGorgon"],
+				["Wyvern", "WyvernMonarch"],
+				["Hydra", "ChaosHydra"]
+			],
 			"horde" : [ 0, -1 ],
 			"horde" : [ 0, -1 ],
 			"mageGuild" : 3,
 			"mageGuild" : 3,
 			"primaryResource" : 127,
 			"primaryResource" : 127,
@@ -2031,7 +2103,16 @@
 				[ [ 30, 37 ], [ 31, 38 ], [ 32, 39 ], [ 33, 40 ] ],
 				[ [ 30, 37 ], [ 31, 38 ], [ 32, 39 ], [ 33, 40 ] ],
 				[ [ 34, 41 ], [ 35, 42 ], [ 36, 43 ] ]
 				[ [ 34, 41 ], [ 35, 42 ], [ 36, 43 ] ]
 			],
 			],
-			"creatures" : [ [118, 119], [112, 127], [115, 123], [114, 129], [113, 125], [120, 121], [130, 131] ],
+			"creatures" :
+			[
+				["Pixie", "Sprite"],
+				["AirElemental", "StormElemental"],
+				["WaterElemental", "IceElemental"],
+				["FireElemental", "ElectricityElemental"],
+				["EarthElemental", "StoneElemental"],
+				["PsiElemental", "MagicElemental"],
+				["Firebird", "Pheonix"]
+			],
 			"horde" : [ 0, -1 ],
 			"horde" : [ 0, -1 ],
 			"mageGuild" : 5,
 			"mageGuild" : 5,
 			"primaryResource" : 1,
 			"primaryResource" : 1,

File diff ditekan karena terlalu besar
+ 113 - 113
config/creatures.json


+ 49 - 39
lib/CCreatureHandler.cpp

@@ -318,21 +318,23 @@ void CCreatureHandler::loadCreatures()
 		int creatureID = creature["id"].Float();
 		int creatureID = creature["id"].Float();
 		const JsonNode *value;
 		const JsonNode *value;
 
 
-		/* A creature can have several names. */
-		BOOST_FOREACH(const JsonNode &name, creature["name"].Vector())
-		{
-			boost::assign::insert(nameToID)(name.String(), creatureID);
-		}
-
 		// Set various creature properties
 		// Set various creature properties
 		CCreature *c = creatures[creatureID];
 		CCreature *c = creatures[creatureID];
 		c->level = creature["level"].Float();
 		c->level = creature["level"].Float();
-		c->faction = creature["faction"].Float();
+
 		c->animDefName = creature["defname"].String();
 		c->animDefName = creature["defname"].String();
 
 
+		VLC->modh->identifiers.requestIdentifier(std::string("faction.") + creature["faction"].String(), [=](si32 faction)
+		{
+			c->faction = faction;
+		});
+
 		BOOST_FOREACH(const JsonNode &value, creature["upgrades"].Vector())
 		BOOST_FOREACH(const JsonNode &value, creature["upgrades"].Vector())
 		{
 		{
-			c->upgradeNames.insert(value.String());
+			VLC->modh->identifiers.requestIdentifier(std::string("creature.") + value.String(), [=](si32 identifier)
+			{
+				c->upgrades.insert(identifier);
+			});
 		}
 		}
 
 
 		value = &creature["projectile_defname"];
 		value = &creature["projectile_defname"];
@@ -360,6 +362,12 @@ void CCreatureHandler::loadCreatures()
 				AddAbility(c, ability.Vector());
 				AddAbility(c, ability.Vector());
 			}
 			}
 		}
 		}
+
+		/* A creature can have several names. */
+		BOOST_FOREACH(const JsonNode &name, creature["name"].Vector())
+		{
+			VLC->modh->identifiers.registerObject(std::string("creature.") + name.String(), c->idNumber);
+		}
 	}
 	}
 
 
 	BOOST_FOREACH(const JsonNode &creature, config["unused_creatures"].Vector())
 	BOOST_FOREACH(const JsonNode &creature, config["unused_creatures"].Vector())
@@ -568,34 +576,12 @@ void CCreatureHandler::loadUnitAnimInfo(CCreature & unit, CLegacyConfigParser &
 	parser.endLine();
 	parser.endLine();
 }
 }
 
 
-void CCreatureHandler::loadSoundsInfo()
+void CCreatureHandler::loadCreatureSounds(JsonNode node, si32 creaID) // passing node by value to get clearer binding code
 {
 {
-	tlog5 << "\t\tReading config/cr_sounds.json" << std::endl;
-	const JsonNode config(ResourceID("config/cr_sounds.json"));
-
-	if (!config["creature_sounds"].isNull())
-	{
-
-		BOOST_FOREACH(const JsonNode &node, config["creature_sounds"].Vector())
-		{
-			const JsonNode *value;
-			int id;
-
-			value = &node["name"];
-
-			bmap<std::string,int>::const_iterator i = nameToID.find(value->String());
-			if (i != nameToID.end())
-				id = i->second;
-			else
-			{
-				tlog1 << "Sound info for an unknown creature: " << value->String() << std::endl;
-				continue;
-			}
-
-			/* This is a bit ugly. Maybe we should use an array for
-			 * sound ids instead of separate variables and define
-			 * attack/defend/killed/... as indexes. */
-#define GET_SOUND_VALUE(value_name) do { value = &node[#value_name]; if (!value->isNull()) creatures[id]->sounds.value_name = value->String(); } while(0)
+	/* This is a bit ugly. Maybe we should use an array for
+	 * sound ids instead of separate variables and define
+	 * attack/defend/killed/... as indexes. */
+#define GET_SOUND_VALUE(value_name) do { creatures[creaID]->sounds.value_name = node[#value_name].String(); } while(0)
 			GET_SOUND_VALUE(attack);
 			GET_SOUND_VALUE(attack);
 			GET_SOUND_VALUE(defend);
 			GET_SOUND_VALUE(defend);
 			GET_SOUND_VALUE(killed);
 			GET_SOUND_VALUE(killed);
@@ -607,6 +593,20 @@ void CCreatureHandler::loadSoundsInfo()
 			GET_SOUND_VALUE(startMoving);
 			GET_SOUND_VALUE(startMoving);
 			GET_SOUND_VALUE(endMoving);
 			GET_SOUND_VALUE(endMoving);
 #undef GET_SOUND_VALUE
 #undef GET_SOUND_VALUE
+}
+
+void CCreatureHandler::loadSoundsInfo()
+{
+	tlog5 << "\t\tReading config/cr_sounds.json" << std::endl;
+	const JsonNode config(ResourceID("config/cr_sounds.json"));
+
+	if (!config["creature_sounds"].isNull())
+	{
+
+		BOOST_FOREACH(const JsonNode &node, config["creature_sounds"].Vector())
+		{
+			VLC->modh->identifiers.requestIdentifier(std::string("creature.") + node["name"].String(),
+			        boost::bind(&CCreatureHandler::loadCreatureSounds, this, node, _1));
 		}
 		}
 	}
 	}
 }
 }
@@ -620,11 +620,10 @@ void CCreatureHandler::load(const JsonNode & node)
 			CCreature * creature = loadCreature(entry.second);
 			CCreature * creature = loadCreature(entry.second);
 			creature->nameRef = entry.first;
 			creature->nameRef = entry.first;
 			creature->idNumber = creatures.size();
 			creature->idNumber = creatures.size();
-			nameToID[entry.first] = creatures.size();
 
 
 			creatures.push_back(creature);
 			creatures.push_back(creature);
 			tlog3 << "Added creature: " << entry.first << "\n";
 			tlog3 << "Added creature: " << entry.first << "\n";
-			//TODO: notify modHandler that this refName can be resolved to ID
+			VLC->modh->identifiers.registerObject(std::string("creature.") + creature->nameRef, creature->idNumber);
 		}
 		}
 	}
 	}
 }
 }
@@ -640,7 +639,6 @@ CCreature * CCreatureHandler::loadCreature(const JsonNode & node)
 	cre->cost = Res::ResourceSet(node["cost"]);
 	cre->cost = Res::ResourceSet(node["cost"]);
 
 
 	cre->level = node["level"].Float();
 	cre->level = node["level"].Float();
-	cre->faction = node["faction"].Float(); //TODO: replaced by string -> id conversion
 	cre->fightValue = node["fightValue"].Float();
 	cre->fightValue = node["fightValue"].Float();
 	cre->AIValue = node["aiValue"].Float();
 	cre->AIValue = node["aiValue"].Float();
 	cre->growth = node["growth"].Float();
 	cre->growth = node["growth"].Float();
@@ -658,10 +656,22 @@ CCreature * CCreatureHandler::loadCreature(const JsonNode & node)
 	cre->ammMin = amounts["min"].Float();
 	cre->ammMin = amounts["min"].Float();
 	cre->ammMax = amounts["max"].Float();
 	cre->ammMax = amounts["max"].Float();
 
 
+	std::string factionStr = node["faction"].String();
+	if (factionStr.empty())
+		factionStr = "neutral"; //TODO: should be done in schema
+
+	VLC->modh->identifiers.requestIdentifier(std::string("faction.") + factionStr, [=](si32 faction)
+	{
+		cre->faction = faction;
+	});
+
 	//optional
 	//optional
 	BOOST_FOREACH (auto & str, node["upgrades"].Vector())
 	BOOST_FOREACH (auto & str, node["upgrades"].Vector())
 	{
 	{
-		cre->upgradeNames.insert (str.String());
+		VLC->modh->identifiers.requestIdentifier(std::string("creature.") + str.String(), [=](si32 identifier)
+		{
+			cre->upgrades.insert(identifier);
+		});
 	}
 	}
 
 
 	if (!node["shots"].isNull())
 	if (!node["shots"].isNull())

+ 4 - 4
lib/CCreatureHandler.h

@@ -86,7 +86,7 @@ public:
 	bool valid() const;
 	bool valid() const;
 
 
 	void addBonus(int val, int type, int subtype = -1);
 	void addBonus(int val, int type, int subtype = -1);
-	std::string nodeName() const OVERRIDE;
+	std::string nodeName() const override;
 	//void getParents(TCNodes &out, const CBonusSystemNode *root /*= NULL*/) const;
 	//void getParents(TCNodes &out, const CBonusSystemNode *root /*= NULL*/) const;
 
 
 	template<typename RanGen>
 	template<typename RanGen>
@@ -102,7 +102,7 @@ public:
 	{
 	{
 		h & static_cast<CBonusSystemNode&>(*this);
 		h & static_cast<CBonusSystemNode&>(*this);
 		h & namePl & nameSing & nameRef
 		h & namePl & nameSing & nameRef
-			& cost & upgradeNames & upgrades 
+			& cost & upgrades
 			& fightValue & AIValue & growth & hordeGrowth
 			& fightValue & AIValue & growth & hordeGrowth
 			& ammMin & ammMax & level
 			& ammMin & ammMax & level
 			& abilityText & abilityRefs & animDefName & advMapDef;
 			& abilityText & abilityRefs & animDefName & advMapDef;
@@ -131,7 +131,6 @@ public:
 	std::set<int> notUsedMonsters;
 	std::set<int> notUsedMonsters;
 	std::set<TCreature> doubledCreatures; //they get double week
 	std::set<TCreature> doubledCreatures; //they get double week
 	std::vector<ConstTransitivePtr<CCreature> > creatures; //creature ID -> creature info
 	std::vector<ConstTransitivePtr<CCreature> > creatures; //creature ID -> creature info
-	bmap<std::string,int> nameToID;
 
 
 	//stack exp
 	//stack exp
 	std::map<TBonusType, std::pair<std::string, std::string> > stackBonuses; // bonus => name, description
 	std::map<TBonusType, std::pair<std::string, std::string> > stackBonuses; // bonus => name, description
@@ -160,6 +159,7 @@ public:
 	/// read one line from cranim.txt
 	/// read one line from cranim.txt
 	void loadUnitAnimInfo(CCreature & unit, CLegacyConfigParser &parser);
 	void loadUnitAnimInfo(CCreature & unit, CLegacyConfigParser &parser);
 	/// load cr_sounds.json config
 	/// load cr_sounds.json config
+	void loadCreatureSounds(JsonNode node, si32 creaID);
 	void loadSoundsInfo();
 	void loadSoundsInfo();
 	/// parse crexpbon.txt file from H3
 	/// parse crexpbon.txt file from H3
 	void loadStackExp(Bonus & b, BonusList & bl, CLegacyConfigParser &parser);
 	void loadStackExp(Bonus & b, BonusList & bl, CLegacyConfigParser &parser);
@@ -177,7 +177,7 @@ public:
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
 		//TODO: should be optimized, not all these informations needs to be serialized (same for ccreature)
 		//TODO: should be optimized, not all these informations needs to be serialized (same for ccreature)
-		h & notUsedMonsters & creatures & nameToID;
+		h & notUsedMonsters & creatures;
 		h & stackBonuses & expRanks & maxExpPerBattle & expAfterUpgrade;
 		h & stackBonuses & expRanks & maxExpPerBattle & expAfterUpgrade;
 		h & factionCommanders & skillLevels & skillRequirements & commanderLevelPremy;
 		h & factionCommanders & skillLevels & skillRequirements & commanderLevelPremy;
 		h & allCreatures;
 		h & allCreatures;

+ 10 - 4
lib/CHeroHandler.cpp

@@ -7,6 +7,7 @@
 #include "JsonNode.h"
 #include "JsonNode.h"
 #include "GameConstants.h"
 #include "GameConstants.h"
 #include "BattleHex.h"
 #include "BattleHex.h"
+#include "CModHandler.h"
 
 
 /*
 /*
  * CHeroHandler.cpp, part of VCMI engine
  * CHeroHandler.cpp, part of VCMI engine
@@ -134,10 +135,15 @@ void CHeroHandler::loadHeroes()
 
 
 		for(int x=0;x<3;x++)
 		for(int x=0;x<3;x++)
 		{
 		{
-			hero->lowStack[x] = parser.readNumber();
-			hero->highStack[x] = parser.readNumber();
-			hero->refTypeStack[x] = parser.readString();
-			boost::algorithm::replace_all(hero->refTypeStack[x], " ", ""); //remove spaces
+			hero->initialArmy[x].minAmount = parser.readNumber();
+			hero->initialArmy[x].maxAmount = parser.readNumber();
+
+			std::string refName = parser.readString();
+			boost::algorithm::replace_all(refName, " ", ""); //remove spaces
+			VLC->modh->identifiers.requestIdentifier(std::string("creature.") + refName, [=](si32 creature)
+			{
+				hero->initialArmy[x].creature = creature;
+			});
 		}
 		}
 		parser.endLine();
 		parser.endLine();
 
 

+ 16 - 3
lib/CHeroHandler.h

@@ -33,14 +33,27 @@ struct SSpecialtyInfo
 class DLL_LINKAGE CHero
 class DLL_LINKAGE CHero
 {
 {
 public:
 public:
+	struct InitialArmyStack
+	{
+		ui32 minAmount;
+		ui32 maxAmount;
+		TCreature creature;
+
+		template <typename Handler> void serialize(Handler &h, const int version)
+		{
+			h & minAmount & maxAmount & creature;
+		}
+	};
+
 	enum EHeroClasses {KNIGHT, CLERIC, RANGER, DRUID, ALCHEMIST, WIZARD,
 	enum EHeroClasses {KNIGHT, CLERIC, RANGER, DRUID, ALCHEMIST, WIZARD,
 		DEMONIAC, HERETIC, DEATHKNIGHT, NECROMANCER, WARLOCK, OVERLORD,
 		DEMONIAC, HERETIC, DEATHKNIGHT, NECROMANCER, WARLOCK, OVERLORD,
 		BARBARIAN, BATTLEMAGE, BEASTMASTER, WITCH, PLANESWALKER, ELEMENTALIST};
 		BARBARIAN, BATTLEMAGE, BEASTMASTER, WITCH, PLANESWALKER, ELEMENTALIST};
 
 
 	std::string name; //name of hero
 	std::string name; //name of hero
 	si32 ID;
 	si32 ID;
-	ui32 lowStack[3], highStack[3]; //amount of units; described below
-	std::string refTypeStack[3]; //reference names of units appearing in hero's army if he is recruited in tavern
+
+	InitialArmyStack initialArmy[3];
+
 	CHeroClass * heroClass;
 	CHeroClass * heroClass;
 	EHeroClasses heroType; //hero class
 	EHeroClasses heroType; //hero class
 	std::vector<std::pair<ui8,ui8> > secSkillsInit; //initial secondary skills; first - ID of skill, second - level of skill (1 - basic, 2 - adv., 3 - expert)
 	std::vector<std::pair<ui8,ui8> > secSkillsInit; //initial secondary skills; first - ID of skill, second - level of skill (1 - basic, 2 - adv., 3 - expert)
@@ -54,7 +67,7 @@ public:
 
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
-		h & name & ID & lowStack & highStack & refTypeStack	& heroClass & heroType & secSkillsInit & spec & startingSpell & sex;
+		h & name & ID & initialArmy & heroClass & heroType & secSkillsInit & spec & startingSpell & sex;
 	}
 	}
 };
 };
 
 

+ 42 - 10
lib/CModHandler.cpp

@@ -4,6 +4,7 @@
 #include "JsonNode.h"
 #include "JsonNode.h"
 #include "Filesystem/CResourceLoader.h"
 #include "Filesystem/CResourceLoader.h"
 #include "Filesystem/ISimpleResourceLoader.h"
 #include "Filesystem/ISimpleResourceLoader.h"
+
 /*
 /*
  * CModHandler.h, part of VCMI engine
  * CModHandler.h, part of VCMI engine
  *
  *
@@ -25,6 +26,46 @@ class CTownHandler;
 class CGeneralTextHandler;
 class CGeneralTextHandler;
 class ResourceLocator;
 class ResourceLocator;
 
 
+void CIdentifierStorage::requestIdentifier(std::string name, const boost::function<void(si32)> & callback)
+{
+	auto iter = registeredObjects.find(name);
+
+	if (iter != registeredObjects.end())
+		callback(iter->second); //already registered - trigger callback immediately
+	else
+		missingObjects[name].push_back(callback); // queue callback
+}
+
+void CIdentifierStorage::registerObject(std::string name, si32 identifier)
+{
+	// do not allow to register same object twice
+	assert(registeredObjects.find(name) == registeredObjects.end());
+
+	registeredObjects[name] = identifier;
+
+	auto iter = missingObjects.find(name);
+	if (iter != missingObjects.end())
+	{
+		//call all awaiting callbacks
+		BOOST_FOREACH(auto function, iter->second)
+		{
+			function(identifier);
+		}
+		missingObjects.erase(iter);
+	}
+}
+
+void CIdentifierStorage::finalize() const
+{
+	// print list of missing objects and crash
+	// in future should try to do some cleanup (like returning all id's as 0)
+	BOOST_FOREACH(auto object, missingObjects)
+	{
+		tlog1 << "Error: object " << object.first << " was not found!\n";
+	}
+	assert(missingObjects.empty());
+}
+
 CModHandler::CModHandler()
 CModHandler::CModHandler()
 {
 {
 	VLC->modh = this;
 	VLC->modh = this;
@@ -99,6 +140,7 @@ void CModHandler::loadActiveMods()
 		VLC->creh->load(config["creatures"]);
 		VLC->creh->load(config["creatures"]);
 	}
 	}
 	VLC->creh->buildBonusTreeForTiers(); //do that after all new creatures are loaded
 	VLC->creh->buildBonusTreeForTiers(); //do that after all new creatures are loaded
+	identifiers.finalize();
 }
 }
 
 
 void CModHandler::reload()
 void CModHandler::reload()
@@ -118,16 +160,6 @@ void CModHandler::reload()
 
 
 				VLC->dobjinfo->gobjs[Obj::MONSTER][crea->idNumber] = info;
 				VLC->dobjinfo->gobjs[Obj::MONSTER][crea->idNumber] = info;
 			}
 			}
-			BOOST_FOREACH(auto up, crea->upgradeNames)
-			{
-				auto it = VLC->creh->nameToID.find(up);
-				if (it != VLC->creh->nameToID.end())
-				{
-					crea->upgrades.insert (it->second);
-				}
-				else
-					tlog2 << "Not found upgrade with name " << up << "\n";
-			}
 		}
 		}
 	}
 	}
 
 

+ 21 - 1
lib/CModHandler.h

@@ -22,6 +22,24 @@ class CModIndentifier;
 class CModInfo;
 class CModInfo;
 class JsonNode;
 class JsonNode;
 
 
+/// class that stores all object identifiers strings and maps them to numeric ID's
+/// if possible, objects ID's should be in format <type>.<name>, camelCase e.g. "creature.grandElf"
+class CIdentifierStorage
+{
+	std::map<std::string, si32 > registeredObjects;
+	std::map<std::string, std::vector<boost::function<void(si32)> > > missingObjects;
+
+public:
+	/// request identifier for specific object name. If ID is not yet resolved callback will be queued
+	/// and will be called later
+	void requestIdentifier(std::string name, const boost::function<void(si32)> & callback);
+	/// registers new object, calls all associated callbacks
+	void registerObject(std::string name, si32 identifier);
+
+	/// called at the very end of loading to check for any missing ID's
+	void finalize() const;
+};
+
 typedef si32 TModID;
 typedef si32 TModID;
 
 
 class DLL_LINKAGE CModInfo
 class DLL_LINKAGE CModInfo
@@ -41,12 +59,14 @@ public:
 
 
 class DLL_LINKAGE CModHandler
 class DLL_LINKAGE CModHandler
 {
 {
-public:
 	//std::string currentConfig; //save settings in this file
 	//std::string currentConfig; //save settings in this file
 
 
 	std::map <TModID, CModInfo> allMods;
 	std::map <TModID, CModInfo> allMods;
 	std::set <TModID> activeMods;//TODO: use me
 	std::set <TModID> activeMods;//TODO: use me
 
 
+public:
+	CIdentifierStorage identifiers;
+
 	/// management of game settings config
 	/// management of game settings config
 	void loadConfigFromFile (std::string name);	
 	void loadConfigFromFile (std::string name);	
 	void saveConfigToFile (std::string name);
 	void saveConfigToFile (std::string name);

+ 8 - 19
lib/CObjectHandler.cpp

@@ -783,38 +783,27 @@ void CGHeroInstance::initArmy(IArmyDescriptor *dst /*= NULL*/)
 
 
 	for(int stackNo=0; stackNo < howManyStacks; stackNo++)
 	for(int stackNo=0; stackNo < howManyStacks; stackNo++)
 	{
 	{
-		int creID = 0;
-		auto creItr = VLC->creh->nameToID.find(type->refTypeStack[stackNo]);
-		if(creItr == VLC->creh->nameToID.end())
-		{
-			tlog1 << "Cannot find a creature named " << type->refTypeStack[stackNo] << std::endl;
-			tlog1 << "Available creatures: \n";
-			BOOST_FOREACH(auto i, VLC->creh->nameToID)
-			{
-				tlog1 << boost::format("\t%s => %d\n") % i.first % i.second;
-			}
-		}
-		else
-			creID = creItr->second;
+		auto & stack = type->initialArmy[stackNo];
 
 
-		int range = type->highStack[stackNo] - type->lowStack[stackNo];
-		int count = ran()%(range+1) + type->lowStack[stackNo];
+		int range = stack.maxAmount - stack.minAmount;
+		int count = ran()%(range+1) + stack.minAmount;
 
 
-		if(creID>=145 && creID<=149) //war machine
+		if(stack.creature >= 145 &&
+		   stack.creature <= 149) //war machine
 		{
 		{
 			warMachinesGiven++;
 			warMachinesGiven++;
 			if(dst != this)
 			if(dst != this)
 				continue;
 				continue;
 
 
 			int slot = -1, aid = -1;
 			int slot = -1, aid = -1;
-			switch (creID)
+			switch (stack.creature)
 			{
 			{
 			case 145: //catapult
 			case 145: //catapult
 				slot = ArtifactPosition::MACH4;
 				slot = ArtifactPosition::MACH4;
 				aid = 3;
 				aid = 3;
 				break;
 				break;
 			default:
 			default:
-				aid = CArtHandler::convertMachineID(creID,true);
+				aid = CArtHandler::convertMachineID(stack.creature, true);
 				slot = 9 + aid;
 				slot = 9 + aid;
 				break;
 				break;
 			}
 			}
@@ -825,7 +814,7 @@ void CGHeroInstance::initArmy(IArmyDescriptor *dst /*= NULL*/)
 				tlog3 << "Hero " << name << " already has artifact at " << slot << ", omitting giving " << aid << std::endl;
 				tlog3 << "Hero " << name << " already has artifact at " << slot << ", omitting giving " << aid << std::endl;
 		}
 		}
 		else
 		else
-			dst->setCreature(stackNo-warMachinesGiven, creID, count);
+			dst->setCreature(stackNo-warMachinesGiven, stack.creature, count);
 	}
 	}
 }
 }
 void CGHeroInstance::initHeroDefInfo()
 void CGHeroInstance::initHeroDefInfo()

+ 17 - 5
lib/CTownHandler.cpp

@@ -5,6 +5,7 @@
 #include "CGeneralTextHandler.h"
 #include "CGeneralTextHandler.h"
 #include "JsonNode.h"
 #include "JsonNode.h"
 #include "GameConstants.h"
 #include "GameConstants.h"
+#include "CModHandler.h"
 #include "Filesystem/CResourceLoader.h"
 #include "Filesystem/CResourceLoader.h"
 
 
 /*
 /*
@@ -402,14 +403,24 @@ void CTownHandler::loadTown(CTown &town, const JsonNode & source)
 		town.hordeLvl[town.hordeLvl.size()] = node.Float();
 		town.hordeLvl[town.hordeLvl.size()] = node.Float();
 	}
 	}
 
 
-	BOOST_FOREACH(const JsonNode &list, source["creatures"].Vector())
+	const JsonVector & creatures = source["creatures"].Vector();
+
+	town.creatures.resize(creatures.size());
+
+	for (size_t i=0; i< creatures.size(); i++)
 	{
 	{
-		std::vector<TCreature> level;
-		BOOST_FOREACH(const JsonNode &node, list.Vector())
+		const JsonVector & level = creatures[i].Vector();
+
+		town.creatures[i].resize(level.size());
+
+		for (size_t j=0; j<level.size(); j++)
 		{
 		{
-			level.push_back(node.Float());
+			VLC->modh->identifiers.requestIdentifier(std::string("creature.") + level[j].String(), [=, &town](si32 creature)
+			{
+				town.creatures[i][j] = creature;
+			});
 		}
 		}
-		town.creatures.push_back(level);
+
 	}
 	}
 
 
 	loadBuildings(town, source["buildings"]);
 	loadBuildings(town, source["buildings"]);
@@ -471,6 +482,7 @@ void CTownHandler::load(const JsonNode &source)
 			loadPuzzle(faction, node.second["puzzleMap"]);
 			loadPuzzle(faction, node.second["puzzleMap"]);
 
 
 		tlog3 << "Added faction: " << node.first << "\n";
 		tlog3 << "Added faction: " << node.first << "\n";
+		VLC->modh->identifiers.registerObject(std::string("faction.") + node.first, faction.factionID);
 	}
 	}
 }
 }
 
 

+ 2 - 2
server/CGameHandler.cpp

@@ -716,7 +716,7 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer
 		if(result == 1) //retreat
 		if(result == 1) //retreat
 		{
 		{
 			sah.army[0].clear();
 			sah.army[0].clear();
-			sah.army[0].setCreature(0, VLC->creh->nameToID[loserHero->type->refTypeStack[0]],1);
+			sah.army[0].setCreature(0, loserHero->type->initialArmy[0].creature, 1);
 		}
 		}
 
 
 		if(const CGHeroInstance *another =  getPlayer(loser)->availableHeroes[1])
 		if(const CGHeroInstance *another =  getPlayer(loser)->availableHeroes[1])
@@ -3195,7 +3195,7 @@ bool CGameHandler::hireHero(const CGObjectInstance *obj, ui8 hid, ui8 player)
 	{
 	{
 		sah.hid[hid] = newHero->subID;
 		sah.hid[hid] = newHero->subID;
 		sah.army[hid].clear();
 		sah.army[hid].clear();
-		sah.army[hid].setCreature(0, VLC->creh->nameToID[newHero->type->refTypeStack[0]],1);
+		sah.army[hid].setCreature(0, newHero->type->initialArmy[0].creature, 1);
 	}
 	}
 	else
 	else
 		sah.hid[hid] = -1;
 		sah.hid[hid] = -1;

+ 257 - 0
vcmibuilder

@@ -0,0 +1,257 @@
+#!/bin/bash
+
+#
+# VCMI data builder script
+# Extracts game data from various sources and creates a tree with the right files
+#
+# Authors: listed in file AUTHORS in main folder
+#
+# 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.
+#
+
+# no console arguments - print help
+if [ $# -eq 0 ] ; then
+	print_help=true
+fi
+
+# command line parsing
+# can't use system getopt which is not cross-platform (BSD/Mac)
+# can't use built-in getopts which can't parse long options (too difficult to avoid - e.g. CD1/CD2)
+while [ $# -gt 0 ]
+do
+	case $1 in
+		--cd1) cd1_dir=$2           ; shift 2 ;;
+		--cd2) cd2_dir=$2           ; shift 2 ;;
+		--gog) gog_file=$2          ; shift 2 ;;
+		--data) data_dir=$2         ; shift 2 ;;
+		--wog) wog_archive=$2       ; shift 2 ;;
+		--vcmi) vcmi_archive=$2     ; shift 2 ;;
+		--download) download=true   ; shift 1 ;;
+		--validate) validate=true   ; shift 1 ;;
+		*) print_help=true          ; shift 1 ;;
+	esac
+done
+
+if [[ -n "$print_help" ]]
+then
+	echo "VCMI data builder utility"
+	echo "Usage: vcmibuilder <options>"
+	echo "Options:"
+	echo " --cd1 DIRECTORY  " "Path to mounted first CD with Heroes 3 install files"
+	echo "                  " "Requires unshield"
+	echo
+	echo " --cd2 DIRECTORY  " "Path to second CD with Heroes 3 data files."
+	echo
+	echo " --gog EXECUTABLE " "Path to gog.com executable"
+	echo "                  " "Requires innoextract ( http://constexpr.org/innoextract/ )"
+	echo
+	echo " --data DIRECTORY " "Path to installed Heroes 3 data"
+	echo
+	echo " --wog ARCHIVE    " "Path to manually downloaded WoG archive"
+	echo "                  " "Requires unzip"
+	echo
+	echo " --vcmi ARCHIVE   " "Path to manually downloaded VCMI data package"
+	echo "                  " "Requires unzip"
+	echo
+	echo " --download       " "If specified vcmibuilder will download packages using wget"
+	echo "                  " "Requires wget and Internet connection"
+	echo
+	echo " --validate       " "If specified vcmibuilder will run basic validness checks"
+	exit 1
+fi
+
+# test presence of program $1, $2 will be passed as parameters to test presence
+test_utility ()
+{
+	$1 $2 > /dev/null 2>&1 || { echo "$1 was not found. Please install it" 1>&2 ; exit 1; }
+} 
+
+#print error message and exit
+fail ()
+{
+	$2
+	echo "$1" 1>&2
+	exit 1
+}
+
+# print warning to stderr.
+warning ()
+{
+	echo "$1" 1>&2
+	warn_user=true
+}
+
+# check if selected options are correct.
+
+if [[ -n "$data_dir" ]]
+then
+	if [[ -n "$gog_file" ]] || [[ -n "$cd1_dir" ]] || [[ -n "$cd2_dir" ]]
+	then
+		warning "Warning: Installed data dir was specified. Both gog and cd options will be ignored"
+		unset gog_file cd1_dir cd2_dir
+	fi
+fi
+
+if [[ -n "$gog_file" ]]
+then
+	test_utility "innoextract"
+	if [[ -n "$cd1_dir" ]] || [[ -n "$cd2_dir" ]]
+	then
+		warning "Warning: Both gog and cd options were specified. cd options will be ignored"
+		unset cd1_dir cd2_dir
+	fi
+fi
+
+if [[ -n "$cd1_dir" ]] 
+then
+	test_utility "unshield" "-V"
+fi
+
+if [[ -n "$download" ]] 
+then
+	if [[ -n "$wog_archive" ]] && [[ -n "$vcmi_archive" ]]
+	then
+		warning "Warning: Both wog and vcmi options were specified. Download option will not be used"
+		unset download
+	else
+		test_utility "wget" "-V"
+	fi
+fi
+
+if [[ -n "$download" ]] || [[ -n "$wog_archive" ]] || [[ -n "$vcmi_archive" ]]
+then
+	test_utility "unzip"
+fi
+
+if [[ -z "$gog_file" ]] && [[ -z "$data_dir" ]] && ( [[ -z "$cd1_dir" ]] || [[ -z "$cd2_dir" ]] )
+then
+	warning "Warning: Selected options will not create complete Heroes 3 data!"
+fi
+
+if [[ -z "$download" ]] && ( [[ -z "$wog_archive" ]] || [[ -z "$vcmi_archive" ]])
+then
+	warning "Warning: Selected options will not create complete VCMI data!"
+fi
+
+# if at least one warning has been printed - ask for confirmation
+if [[ -n "$warn_user" ]]
+then
+	read -p "Do you wish to continue? (y/n) " -n 1
+	echo #print eol
+	if [[ ! $REPLY =~ ^[Yy]$ ]]
+	then
+		exit 1
+	fi
+fi
+
+# start installation
+
+dest_dir="./vcmi"
+mkdir -p $dest_dir
+
+if [[ -n "$gog_file" ]]
+then
+	data_dir="./app"
+	# innoextract always reports error (iconv 84 error). Just test file for presence
+	test -f $gog_file || fail "Error: gog.com executable was not found!"
+	innoextract -s -p 1 $gog_file
+fi
+
+if [[ -n "$cd1_dir" ]]
+then
+	data_dir="./cddir"
+	mkdir -p $data_dir
+	unshield -d $data_dir x $cd1_dir/_setup/data1.cab || fail "Error: failed to extract from Install Shield installer!" "rm -rf ./cddir"
+	
+	# a bit tricky - different releases have different root directory. Move extracted files to data_dir
+	if [ -d $data_dir/"Heroes3" ]
+	then
+		mv $data_dir/Heroes3/* $data_dir
+	elif [ -d $data_dir"/Program_Files" ]
+	then
+		mv $data_dir/Program_Files/* $data_dir
+	else
+		echo "Error: failed to find extracted game files!"
+		echo "Extracted directories are: "
+		ls -la $data_dir
+		echo "Please report this on vcmi.eu"
+		exit 1;
+	fi
+fi
+
+if [[ -n "$cd2_dir" ]]
+then
+	mkdir -p $dest_dir/Data
+
+	if [ -d $cd2_dir/heroes3 ]
+	then
+		cp $cd2_dir/heroes3/Data/Heroes3.vid $dest_dir/Data/VIDEO.VID
+		cp $cd2_dir/heroes3/Data/Heroes3.snd $dest_dir/Data/Heroes3-cd2.snd
+	else
+		cp $cd2_dir/Heroes3/Data/Heroes3.vid $dest_dir/Data/VIDEO.VID
+		cp $cd2_dir/Heroes3/Data/Heroes3.snd $dest_dir/Data/Heroes3-cd2.snd
+	fi
+fi
+
+if [[ -n "$data_dir" ]]
+then
+	cp -r $data_dir/Data $dest_dir
+	cp -r $data_dir/Maps $dest_dir
+
+	# this folder is named differently from time to time
+	# vcmi can handle any case but script can't
+	if [ -d $data_dir/MP3 ]
+	then
+		cp -r $data_dir/MP3 $dest_dir
+	else
+		cp -r $data_dir/Mp3 $dest_dir
+	fi
+fi
+
+if [[ -n "$download" ]]
+then
+	if [[ -z "$wog_archive" ]]
+	then
+		wget "http://download.vcmi.eu/WoG/wog.zip" -O wog.zip || fail "Error: failed to download WoG archive!" "rm -f wog.zip"
+		wog_archive="./wog.zip"
+	fi
+	
+	if [[ -z "$vcmi_archive" ]]
+	then
+		wget "http://download.vcmi.eu/core.zip" -O core.zip || fail "Error: failed to download WoG archive!" "rm -f core.zip"
+		vcmi_archive="./core.zip"
+	fi
+fi
+
+if [[ -n "$wog_archive" ]]
+then
+	echo "decompressing $wog_archive"
+	unzip -qo $wog_archive -d $dest_dir || fail "Error: failed to extract WoG archive!"
+fi
+
+if [[ -n "$vcmi_archive" ]]
+then
+	echo "decompressing $vcmi_archive"
+	unzip -qo $vcmi_archive -d $dest_dir || fail "Error: failed to extract WoG archive!"
+fi
+
+if [[ -n "$validate" ]]
+then
+	test -f $dest_dir/Data/H3bitmap.lod || fail "Error: Heroes 3 data files are missing!"
+	test -f $dest_dir/Data/H3sprite.lod || fail "Error: Heroes 3 data files are missing!"
+	test -f $dest_dir/Data/VIDEO.VID    || fail "Error: Heroes 3 data files (CD2) are missing!"
+	test -d $dest_dir/Mods/WoG/Data     || fail "Error: WoG data files are missing!"
+	test -d $dest_dir/Mods/vcmi/Data    || fail "Error: VCMI data files are missing!"
+fi
+
+#TODO: Cleanup? How?
+
+echo
+echo "vcmibuilder finished succesfully"
+echo "resulting data was placed into $PWD/vcmi"
+echo "any other files in current directory can be removed"
+echo
+

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini