Sfoglia il codice sorgente

Merge pull request #5751 from IvanSavenko/campaign_bonuses

Campaign code refactoring & Engine support for Hota campaings
Ivan Savenko 4 mesi fa
parent
commit
66e210f54d
86 ha cambiato i file con 2855 aggiunte e 2167 eliminazioni
  1. 1 1
      AI/Nullkiller/AIGateway.cpp
  2. 1 1
      AI/VCAI/VCAI.cpp
  3. 168 119
      client/lobby/CBonusSelection.cpp
  4. 0 19
      client/render/Graphics.cpp
  5. 0 3
      client/render/Graphics.h
  6. 3 2
      client/widgets/CArtifactsOfHeroBase.cpp
  7. 0 48
      config/ERMU_to_picture.json
  8. 0 239
      config/campaignMedia.json
  9. 189 4
      config/campaignOverrides.json
  10. 238 0
      config/campaignRegions.json
  11. 0 234
      config/campaign_regions.json
  12. 36 36
      config/factions/castle.json
  13. 36 36
      config/factions/conflux.json
  14. 37 37
      config/factions/dungeon.json
  15. 35 35
      config/factions/fortress.json
  16. 38 38
      config/factions/inferno.json
  17. 37 37
      config/factions/necropolis.json
  18. 38 38
      config/factions/rampart.json
  19. 35 35
      config/factions/stronghold.json
  20. 37 37
      config/factions/tower.json
  21. 194 5
      config/gameConfig.json
  22. 3 3
      config/schemas/artifact.json
  23. 40 0
      config/schemas/campaignRegion.json
  24. 4 0
      config/schemas/mod.json
  25. 6 0
      config/schemas/skill.json
  26. 62 4
      config/schemas/spell.json
  27. 5 0
      config/schemas/townStructure.json
  28. 2 2
      docs/modders/Entities_Format/Artifact_Format.md
  29. 2 0
      docs/modders/Entities_Format/Secondary_Skill_Format.md
  30. 10 1
      docs/modders/Mod_File_Format.md
  31. 8 3
      lib/CMakeLists.txt
  32. 4 0
      lib/CSkillHandler.cpp
  33. 1 0
      lib/CSkillHandler.h
  34. 5 0
      lib/GameLibrary.cpp
  35. 4 0
      lib/GameLibrary.h
  36. 3 2
      lib/IHandlerBase.h
  37. 2 15
      lib/StartInfo.cpp
  38. 2 2
      lib/StartInfo.h
  39. 353 0
      lib/campaign/CampaignBonus.cpp
  40. 224 0
      lib/campaign/CampaignBonus.h
  41. 1 2
      lib/campaign/CampaignConstants.h
  42. 53 397
      lib/campaign/CampaignHandler.cpp
  43. 5 9
      lib/campaign/CampaignHandler.h
  44. 137 0
      lib/campaign/CampaignRegions.cpp
  45. 80 0
      lib/campaign/CampaignRegions.h
  46. 34 0
      lib/campaign/CampaignRegionsHandler.cpp
  47. 38 0
      lib/campaign/CampaignRegionsHandler.h
  48. 1 1
      lib/campaign/CampaignScenarioPrologEpilog.h
  49. 50 190
      lib/campaign/CampaignState.cpp
  50. 45 86
      lib/campaign/CampaignState.h
  51. 7 5
      lib/constants/EntityIdentifiers.cpp
  52. 10 6
      lib/constants/EntityIdentifiers.h
  53. 3 3
      lib/entities/artifact/CArtHandler.cpp
  54. 1 2
      lib/entities/artifact/CArtifact.cpp
  55. 2 1
      lib/entities/artifact/CArtifact.h
  56. 3 0
      lib/entities/artifact/CArtifactInstance.cpp
  57. 0 87
      lib/entities/building/CBuildingHandler.cpp
  58. 0 22
      lib/entities/building/CBuildingHandler.h
  59. 1 0
      lib/entities/faction/CTown.h
  60. 1 0
      lib/entities/faction/CTownHandler.cpp
  61. 49 41
      lib/gameState/CGameStateCampaign.cpp
  62. 1 1
      lib/gameState/CGameStateCampaign.h
  63. 11 4
      lib/json/JsonBonus.cpp
  64. 9 0
      lib/json/JsonValidator.cpp
  65. 2 2
      lib/mapObjectConstructors/CObjectClassesHandler.h
  66. 23 36
      lib/mapObjects/CGHeroInstance.cpp
  67. 2 1
      lib/mapObjects/CGHeroInstance.h
  68. 1 1
      lib/mapObjects/ObstacleSetHandler.h
  69. 1 0
      lib/mapping/CMapHeader.cpp
  70. 3 13
      lib/mapping/CMapService.cpp
  71. 4 2
      lib/mapping/MapFeaturesH3M.h
  72. 9 52
      lib/mapping/MapFormatH3M.cpp
  73. 96 0
      lib/mapping/MapFormatSettings.cpp
  74. 65 0
      lib/mapping/MapFormatSettings.h
  75. 31 0
      lib/mapping/MapIdentifiersH3M.cpp
  76. 7 1
      lib/mapping/MapIdentifiersH3M.h
  77. 2 0
      lib/modding/ContentTypeHandler.cpp
  78. 2 1
      lib/serializer/ESerializationVersion.h
  79. 0 12
      lib/texts/CGeneralTextHandler.cpp
  80. 0 5
      lib/texts/CGeneralTextHandler.h
  81. 2 1
      mapeditor/campaigneditor/campaigneditor.cpp
  82. 2 1
      mapeditor/campaigneditor/campaignproperties.cpp
  83. 22 8
      mapeditor/campaigneditor/scenarioproperties.cpp
  84. 168 134
      mapeditor/campaigneditor/startingbonus.cpp
  85. 1 1
      mapeditor/campaigneditor/startingbonus.h
  86. 7 3
      server/CVCMIServer.cpp

+ 1 - 1
AI/Nullkiller/AIGateway.cpp

@@ -781,7 +781,7 @@ void AIGateway::showGarrisonDialog(const CArmedInstance * up, const CGHeroInstan
 	//you can't request action from action-response thread
 	executeActionAsync("showGarrisonDialog", [this, up, down, removableUnits, queryID]()
 	{
-		if(removableUnits && up->tempOwner == down->tempOwner && nullkiller->settings->isGarrisonTroopsUsageAllowed() && !cb->getStartInfo()->isRestorationOfErathiaCampaign())
+		if(removableUnits && up->tempOwner == down->tempOwner && nullkiller->settings->isGarrisonTroopsUsageAllowed() && !cb->getStartInfo()->restrictedGarrisonsForAI())
 		{
 			pickBestCreatures(down, up);
 		}

+ 1 - 1
AI/VCAI/VCAI.cpp

@@ -762,7 +762,7 @@ void VCAI::showGarrisonDialog(const CArmedInstance * up, const CGHeroInstance *
 	//you can't request action from action-response thread
 	executeActionAsync("showGarrisonDialog", [this, down, up, removableUnits, queryID]()
 	{
-		if(removableUnits && !cb->getStartInfo()->isRestorationOfErathiaCampaign())
+		if(removableUnits && !cb->getStartInfo()->restrictedGarrisonsForAI())
 			pickBestCreatures(down, up);
 
 		answerQuery(queryID, 0);

+ 168 - 119
client/lobby/CBonusSelection.cpp

@@ -46,18 +46,20 @@
 #include "../../lib/GameLibrary.h"
 #include "../../lib/StartInfo.h"
 #include "../../lib/campaign/CampaignState.h"
+#include "../../lib/entities/artifact/CArtifact.h"
 #include "../../lib/entities/building/CBuilding.h"
-#include "../../lib/entities/building/CBuildingHandler.h"
 #include "../../lib/entities/faction/CFaction.h"
 #include "../../lib/entities/faction/CTown.h"
 #include "../../lib/entities/faction/CTownHandler.h"
 #include "../../lib/entities/hero/CHeroHandler.h"
+#include "../../lib/spells/CSpellHandler.h"
 #include "../../lib/filesystem/Filesystem.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/mapping/CMapHeader.h"
 #include "../../lib/mapping/CMapInfo.h"
 #include "../../lib/mapping/CMapService.h"
 #include "../../lib/texts/CGeneralTextHandler.h"
+#include "mapping/MapFormatSettings.h"
 
 std::shared_ptr<CampaignState> CBonusSelection::getCampaign()
 {
@@ -173,149 +175,196 @@ void CBonusSelection::createBonusesIcons()
 
 	for(int i = 0; i < bonDescs.size(); i++)
 	{
-		int bonusType = static_cast<size_t>(bonDescs[i].type);
-		std::string picName = bonusPics[bonusType];
-		size_t picNumber = bonDescs[i].info2;
+		const CampaignBonus & bonus	= bonDescs[i];
+		CampaignBonusType bonusType = bonus.getType();
+		std::string picName = bonusPics[static_cast<int>(bonusType)];
+		size_t picNumber = -1;
 
 		MetaString desc;
-		switch(bonDescs[i].type)
+		switch(bonusType)
 		{
-		case CampaignBonusType::SPELL:
-			desc.appendLocalString(EMetaText::GENERAL_TXT, 715);
-			desc.replaceName(SpellID(bonDescs[i].info2));
-			break;
-		case CampaignBonusType::MONSTER:
-			picNumber = bonDescs[i].info2 + 2;
-			desc.appendLocalString(EMetaText::GENERAL_TXT, 717);
-			desc.replaceNumber(bonDescs[i].info3);
-			desc.replaceNamePlural(bonDescs[i].info2);
-			break;
-		case CampaignBonusType::BUILDING:
-		{
-			FactionID faction;
-			for(auto & elem : GAME->server().si->playerInfos)
+			case CampaignBonusType::SPELL:
 			{
-				if(elem.second.isControlledByHuman())
-				{
-					faction = elem.second.castle;
-					break;
-				}
-
+				const auto & bonusValue = bonus.getValue<CampaignBonusSpell>();
+				const auto * spell = bonusValue.spell.toSpell();
+				if (!spell->getIconScenarioBonus().empty())
+					picName = spell->getIconScenarioBonus();
+				else
+					picNumber = bonusValue.spell.getNum();
+
+				desc.appendLocalString(EMetaText::GENERAL_TXT, 715);
+				desc.replaceName(bonusValue.spell);
+				break;
 			}
-			assert(faction.hasValue());
-
-			BuildingID buildID;
-			if(getCampaign()->formatVCMI())
-				buildID = BuildingID(bonDescs[i].info1);
-			else
-				buildID = CBuildingHandler::campToERMU(bonDescs[i].info1, faction, std::set<BuildingID>());
-			picName = graphics->ERMUtoPicture[faction.getNum()][buildID.getNum()];
-			picNumber = -1;
-
-			if(vstd::contains((*LIBRARY->townh)[faction]->town->buildings, buildID))
-				desc.appendTextID((*LIBRARY->townh)[faction]->town->buildings.find(buildID)->second->getNameTextID());
-			break;
-		}
-		case CampaignBonusType::ARTIFACT:
-			desc.appendLocalString(EMetaText::GENERAL_TXT, 715);
-			desc.replaceName(ArtifactID(bonDescs[i].info2));
-			break;
-		case CampaignBonusType::SPELL_SCROLL:
-			desc.appendLocalString(EMetaText::GENERAL_TXT, 716);
-			desc.replaceName(SpellID(bonDescs[i].info2));
-			break;
-		case CampaignBonusType::PRIMARY_SKILL:
-		{
-			int leadingSkill = -1;
-			std::vector<std::pair<int, int>> toPrint; //primary skills to be listed <num, val>
-			const ui8 * ptr = reinterpret_cast<const ui8 *>(&bonDescs[i].info2);
-			for(int g = 0; g < GameConstants::PRIMARY_SKILLS; ++g)
+			case CampaignBonusType::MONSTER:
 			{
-				if(leadingSkill == -1 || ptr[g] > ptr[leadingSkill])
-				{
-					leadingSkill = g;
-				}
-				if(ptr[g] != 0)
+				const auto & bonusValue = bonus.getValue<CampaignBonusCreatures>();
+				picNumber = bonusValue.creature.getNum() + 2;
+				desc.appendLocalString(EMetaText::GENERAL_TXT, 717);
+				desc.replaceNumber(bonusValue.amount);
+				desc.replaceNamePlural(bonusValue.creature);
+				break;
+			}
+			case CampaignBonusType::BUILDING:
+			{
+				const auto & bonusValue = bonus.getValue<CampaignBonusBuilding>();
+				FactionID faction;
+				for(auto & elem : GAME->server().si->playerInfos)
 				{
-					toPrint.push_back(std::make_pair(g, ptr[g]));
+					if(elem.second.isControlledByHuman())
+					{
+						faction = elem.second.castle;
+						break;
+					}
 				}
-			}
-			picNumber = leadingSkill;
-			desc.appendLocalString(EMetaText::GENERAL_TXT, 715);
+				assert(faction.hasValue());
 
-			std::string substitute; //text to be printed instead of %s
-			for(int v = 0; v < toPrint.size(); ++v)
-			{
-				substitute += std::to_string(toPrint[v].second);
-				substitute += " " + LIBRARY->generaltexth->primarySkillNames[toPrint[v].first];
-				if(v != toPrint.size() - 1)
+				BuildingID buildID = bonusValue.buildingDecoded;
+				if (bonusValue.buildingH3M.hasValue())
 				{
-					substitute += ", ";
+					auto mapping = LIBRARY->mapFormat->getMapping(getCampaign()->getFormat());
+					buildID = mapping.remapBuilding(faction, bonusValue.buildingH3M);
 				}
-			}
 
-			desc.replaceRawString(substitute);
-			break;
-		}
-		case CampaignBonusType::SECONDARY_SKILL:
-			desc.appendLocalString(EMetaText::GENERAL_TXT, 718);
-			desc.replaceTextID(TextIdentifier("core", "skilllev", bonDescs[i].info3 - 1).get());
-			desc.replaceName(SecondarySkill(bonDescs[i].info2));
-			picNumber = bonDescs[i].info2 * 3 + bonDescs[i].info3 - 1;
-
-			break;
-		case CampaignBonusType::RESOURCE:
-		{
-			desc.appendLocalString(EMetaText::GENERAL_TXT, 717);
+				for (const auto & townStructure : faction.toFaction()->town->clientInfo.structures)
+					if (townStructure->building && townStructure->building->bid == buildID)
+						picName = townStructure->campaignBonus.getOriginalName();
+
+				picNumber = -1;
 
-			switch(bonDescs[i].info1)
+				if(vstd::contains(faction.toFaction()->town->buildings, buildID))
+					desc.appendTextID(faction.toFaction()->town->buildings.find(buildID)->second->getNameTextID());
+				break;
+			}
+			case CampaignBonusType::ARTIFACT:
 			{
-				case EGameResID::COMMON: //wood + ore
-				{
-					desc.replaceLocalString(EMetaText::GENERAL_TXT, 721);
-					picNumber = 7;
-					break;
-				}
-				case EGameResID::RARE : //mercury + sulfur + crystal + gems
+				const auto & bonusValue = bonus.getValue<CampaignBonusArtifact>();
+				const auto * artifact = bonusValue.artifact.toArtifact();
+				if (!artifact->scenarioBonus.empty())
+					picName = artifact->scenarioBonus;
+				else
+					picNumber = bonusValue.artifact.getNum();
+				desc.appendLocalString(EMetaText::GENERAL_TXT, 715);
+				desc.replaceName(bonusValue.artifact);
+				break;
+			}
+			case CampaignBonusType::SPELL_SCROLL:
+			{
+				const auto & bonusValue = bonus.getValue<CampaignBonusSpellScroll>();
+				const auto * spell = bonusValue.spell.toSpell();
+				if (!spell->getIconScenarioBonus().empty())
+					picName = spell->getIconScenarioBonus();
+				else
+					picNumber = bonusValue.spell.getNum();
+
+				desc.appendLocalString(EMetaText::GENERAL_TXT, 716);
+				desc.replaceName(bonusValue.spell);
+				break;
+			}
+			case CampaignBonusType::PRIMARY_SKILL:
+			{
+				const auto & bonusValue = bonus.getValue<CampaignBonusPrimarySkill>();
+				int leadingSkill = -1;
+				std::vector<std::pair<int, int>> toPrint; //primary skills to be listed <num, val>
+				for(int g = 0; g < bonusValue.amounts.size(); ++g)
 				{
-					desc.replaceLocalString(EMetaText::GENERAL_TXT, 722);
-					picNumber = 8;
-					break;
+					if(leadingSkill == -1 || bonusValue.amounts[g] > bonusValue.amounts[leadingSkill])
+					{
+						leadingSkill = g;
+					}
+					if(bonusValue.amounts[g] != 0)
+					{
+						toPrint.push_back(std::make_pair(g, bonusValue.amounts[g]));
+					}
 				}
-				default:
+				picNumber = leadingSkill;
+				desc.appendLocalString(EMetaText::GENERAL_TXT, 715);
+
+				std::string substitute; //text to be printed instead of %s
+				for(int v = 0; v < toPrint.size(); ++v)
 				{
-					desc.replaceName(GameResID(bonDescs[i].info1));
-					picNumber = bonDescs[i].info1;
+					substitute += std::to_string(toPrint[v].second);
+					substitute += " " + LIBRARY->generaltexth->primarySkillNames[toPrint[v].first];
+					if(v != toPrint.size() - 1)
+					{
+						substitute += ", ";
+					}
 				}
+
+				desc.replaceRawString(substitute);
+				break;
 			}
+			case CampaignBonusType::SECONDARY_SKILL:
+			{
+				const auto & bonusValue = bonus.getValue<CampaignBonusSecondarySkill>();
+				const auto * skill = bonusValue.skill.toSkill();
+				desc.appendLocalString(EMetaText::GENERAL_TXT, 718);
+				desc.replaceTextID(TextIdentifier("core", "skilllev", bonusValue.mastery - 1).get());
+				desc.replaceName(bonusValue.skill);
+				if (!skill->at(bonusValue.mastery).scenarioBonus.empty())
+					picName = skill->at(bonusValue.mastery).scenarioBonus.empty();
+				else
+					picNumber = bonusValue.skill.getNum() * 3 + bonusValue.mastery - 1;
+				break;
+			}
+			case CampaignBonusType::RESOURCE:
+			{
+				const auto & bonusValue = bonus.getValue<CampaignBonusStartingResources>();
+				desc.appendLocalString(EMetaText::GENERAL_TXT, 717);
 
-			desc.replaceNumber(bonDescs[i].info2);
-			break;
-		}
-		case CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO:
-		{
-			auto superhero = getCampaign()->strongestHero(static_cast<CampaignScenarioID>(bonDescs[i].info2), PlayerColor(bonDescs[i].info1));
-			if(!superhero)
-				logGlobal->warn("No superhero! How could it be transferred?");
-			picNumber = superhero ? superhero->getIconIndex() : 0;
-			desc.appendLocalString(EMetaText::GENERAL_TXT, 719);
-			desc.replaceRawString(getCampaign()->scenario(static_cast<CampaignScenarioID>(bonDescs[i].info2)).scenarioName.toString());
-			break;
-		}
+				switch(bonusValue.resource)
+				{
+					case EGameResID::COMMON: //wood + ore
+					{
+						desc.replaceLocalString(EMetaText::GENERAL_TXT, 721);
+						picNumber = 7;
+						break;
+					}
+					case EGameResID::RARE: //mercury + sulfur + crystal + gems
+					{
+						desc.replaceLocalString(EMetaText::GENERAL_TXT, 722);
+						picNumber = 8;
+						break;
+					}
+					default:
+					{
+						desc.replaceName(bonusValue.resource);
+						picNumber = bonusValue.resource.getNum();
+					}
+				}
 
-		case CampaignBonusType::HERO:
-			if(bonDescs[i].info2 == HeroTypeID::CAMP_RANDOM.getNum())
+				desc.replaceNumber(bonusValue.amount);
+				break;
+			}
+			case CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO:
 			{
-				desc.appendLocalString(EMetaText::GENERAL_TXT, 720); // Start with random hero
-				picNumber = -1;
-				picName = "CBONN1A3.BMP";
+				const auto & bonusValue = bonus.getValue<CampaignBonusHeroesFromScenario>();
+				auto superhero = getCampaign()->strongestHero(bonusValue.scenario, bonusValue.startingPlayer);
+				if(!superhero)
+					logGlobal->warn("No superhero! How could it be transferred?");
+				picNumber = superhero ? superhero->getIconIndex() : 0;
+				desc.appendLocalString(EMetaText::GENERAL_TXT, 719);
+				desc.replaceRawString(getCampaign()->scenario(bonusValue.scenario).scenarioName.toString());
+				break;
 			}
-			else
+
+			case CampaignBonusType::HERO:
 			{
-				desc.appendLocalString(EMetaText::GENERAL_TXT, 715); // Start with %s
-				desc.replaceTextID(LIBRARY->heroh->objects[bonDescs[i].info2]->getNameTextID());
+				const auto & bonusValue = bonus.getValue<CampaignBonusStartingHero>();
+				if(bonusValue.hero == HeroTypeID::CAMP_RANDOM.getNum())
+				{
+					desc.appendLocalString(EMetaText::GENERAL_TXT, 720); // Start with random hero
+					picNumber = -1;
+					picName = "CBONN1A3.BMP";
+				}
+				else
+				{
+					desc.appendLocalString(EMetaText::GENERAL_TXT, 715); // Start with %s
+					desc.replaceTextID(bonusValue.hero.toHeroType()->getNameTextID());
+					picNumber = bonusValue.hero.getNum();
+				}
+				break;
 			}
-			break;
 		}
 
 		std::shared_ptr<CToggleButton> bonusButton = std::make_shared<CToggleButton>(Point(475 + i * 68, 455), AnimationPath::builtin("campaignBonusSelection"), CButton::tooltip(desc.toString(), desc.toString()), nullptr, EShortcut::NONE, false, [this](){

+ 0 - 19
client/render/Graphics.cpp

@@ -118,7 +118,6 @@ Graphics::Graphics()
 {
 	loadPaletteAndColors();
 	initializeBattleGraphics();
-	loadErmuToPicture();
 
 	//(!) do not load any CAnimation here
 }
@@ -153,21 +152,3 @@ void Graphics::setPlayerFlagColor(SDL_Palette * targetPalette, PlayerColor playe
 		SDL_SetPaletteColors(targetPalette, &color, 5, 1);
 	}
 }
-
-void Graphics::loadErmuToPicture()
-{
-	//loading ERMU to picture
-	const JsonNode config(JsonPath::builtin("config/ERMU_to_picture.json"));
-	int etp_idx = 0;
-	for(const JsonNode &etp : config["ERMU_to_picture"].Vector()) {
-		int idx = 0;
-		for(const JsonNode &n : etp.Vector()) {
-			ERMUtoPicture[idx][etp_idx] = n.String();
-			idx ++;
-		}
-		assert (idx == std::size(ERMUtoPicture));
-
-		etp_idx ++;
-	}
-	assert (etp_idx == 44);
-}

+ 0 - 3
client/render/Graphics.h

@@ -20,7 +20,6 @@ class Graphics
 {
 	void initializeBattleGraphics();
 	void loadPaletteAndColors();
-	void loadErmuToPicture();
 
 public:
 	using PlayerPalette = std::array<ColorRGBA, 32>;
@@ -32,8 +31,6 @@ public:
 	PlayerPalette neutralColorPalette;
 	ColorRGBA neutralColor;
 
-	//towns
-	std::map<int, std::string> ERMUtoPicture[GameConstants::F_NUMBER]; //maps building ID to it's picture's name for each town type
 	//for battles
 	std::map< int, std::vector < std::string > > battleACToDef; //maps AC format to vector of appropriate def names
 

+ 3 - 2
client/widgets/CArtifactsOfHeroBase.cpp

@@ -269,12 +269,13 @@ void CArtifactsOfHeroBase::setSlotData(ArtPlacePtr artPlace, const ArtifactPosit
 	artPlace->slot = slot;
 	if(auto slotInfo = curHero->getSlot(slot))
 	{
+		const auto curArt = slotInfo->getArt();
+
 		artPlace->lockSlot(slotInfo->locked);
-		artPlace->setArtifact(slotInfo->getArt()->getTypeId(), slotInfo->getArt()->getScrollSpellID());
+		artPlace->setArtifact(curArt->getTypeId(), curArt->getScrollSpellID());
 		if(slotInfo->locked)
 			return;
 
-		const auto curArt = slotInfo->getArt();
 		// If the artifact has charges, add charges information
 		if(curArt->getType()->isCharged())
 			artPlace->addChargedArtInfo(curArt->getCharges());

+ 0 - 48
config/ERMU_to_picture.json

@@ -1,48 +0,0 @@
-{
-	"ERMU_to_picture": [
-		[ "BoCsMag1.pcx", "BoRMag1.pcx", "BoTGld1.pcx", "BoIMag1.pcx", "BoNmage1.pcx", "BoDmage1.pcx", "BoSmage1.pcx", "BoFmage1.pcx", "BoEgld1.pcx" ],
-		[ "BoCsMag2.pcx", "BoRMag2.pcx", "BoTGld2.pcx", "BoIMag2.pcx", "BoNmage2.pcx", "BoDmage2.pcx", "BoSmage2.pcx", "BoFmage2.pcx", "BoEgld2.pcx" ],
-		[ "BoCsMag3.pcx", "BoRMag3.pcx", "BoTGld3.pcx", "BoIMag3.pcx", "BoNmage3.pcx", "BoDmage3.pcx", "BoSmage3.pcx", "BoFmage3.pcx", "BoEgld3.pcx" ],
-		[ "BoCsMag4.pcx", "BoRMag4.pcx", "BoTGld4.pcx", "BoIMag4.pcx", "BoNmage4.pcx", "BoDmage4.pcx", "BoSmage4.pcx", "BoFmage4.pcx", "BoEgld4.pcx" ],
-		[ "BoCsMag5.pcx", "BoRMag5.pcx", "BoTGld5.pcx", "BoIMag5.pcx", "BoNmage5.pcx", "BoDmage5.pcx", "BoSmage5.pcx", "BoFmage5.pcx", "BoEgld5.pcx" ],
-		[ "BoCsTav1.pcx", "BoRTav.pcx", "BoTTav.pcx", "BoITav.pcx", "BoNtav.pcx", "BoDtav.pcx", "BoStav1.pcx", "BoFtav.pcx", "BoEtav.pcx" ],
-		[ "BoCsDock.pcx", "", "", "", "", "", "", "BoFship.pcx", "BoEship.pcx" ],
-		[ "BoCsCas1.pcx", "BoRCas1.pcx", "BoTCas1.pcx", "BoICas1.pcx", "BoNcast1.pcx", "BoDcas1.pcx", "BoScas1.pcx", "BoFcast1.pcx", "BoEcast1.pcx" ],
-		[ "BoCsCas2.pcx", "BoRCas2.pcx", "BoTCas2.pcx", "BoICas2.pcx", "BoNcast2.pcx", "BoDcas2.pcx", "BoScas2.pcx", "BoFcast2.pcx", "BoEcast2.pcx" ],
-		[ "BoCsCas3.pcx", "BoRCas3.pcx", "BoTCas3.pcx", "BoICas3.pcx", "BoNcast3.pcx", "BoDcas3.pcx", "BoScas3.pcx", "BoFcast3.pcx", "BoEcast3.pcx" ],
-		[ "BoCsHal1.pcx", "BoRHal1.pcx", "BoTHal1.pcx", "BoIHal1.pcx", "BoNhall1.pcx", "BoDhall1.pcx", "BoShall1.pcx", "BoFhall1.pcx", "BoEhall1.pcx" ],
-		[ "BoCsHal2.pcx", "BoRHal2.pcx", "BoTHal2.pcx", "BoIHal2.pcx", "BoNhall2.pcx", "BoDhall2.pcx", "BoShall2.pcx", "BoFhall2.pcx", "BoEhall2.pcx" ],
-		[ "BoCsHal3.pcx", "BoRHal3.pcx", "BoTHal3.pcx", "BoIHal3.pcx", "BoNhall3.pcx", "BoDhall3.pcx", "BoShall3.pcx", "BoFhall3.pcx", "BoEhall3.pcx" ],
-		[ "BoCsHal4.pcx", "BoRHal4.pcx", "BoTHal4.pcx", "BoIHal4.pcx", "BoNhall4.pcx", "BoDhall4.pcx", "BoShall4.pcx", "BoFhall4.pcx", "BoEhall4.pcx" ],
-		[ "BoCsMrk1.pcx", "BoRMrk1.pcx", "BoTMark.pcx", "BoIMrk1.pcx", "BoNmark1.pcx", "BoDmark1.pcx", "BoSmrk1.pcx", "BoFmark1.pcx", "BoEmark1.pcx" ],
-		[ "BoCsMrk2.pcx", "BoRMrk2.pcx", "BoTMarkS.pcx", "BoIMrk2.pcx", "BoNmark2.pcx", "BoDmark2.pcx", "BoSmrk2.pcx", "BoFmark2.pcx", "BoEmarkS.pcx" ],
-		[ "BoCsBlak.pcx", "BoRAid.pcx", "BoTBlack.pcx", "BoIBlak.pcx", "BoNsmith.pcx", "BoDsmith.pcx", "BoSblak1.pcx", "BoFapoth.pcx", "BoEblack.pcx" ],
-		[ "BoCsLite.pcx", "BoRGar1.pcx", "BoTMarkA.pcx", "", "BoNshrod.pcx", "BoDmarkA.pcx", "BoSescap.pcx", "BoFcage.pcx", "BoEmarkA.pcx" ],
-		[ "BoCsGr1H.pcx", "BoRDwf1h.pcx", "BoTGa1H.pcx", "BoIImpH.pcx", "BoNskelH.pcx", "BoDtrogH.pcx", "BoSgob1h.pcx", "BoFgno1h.pcx", "BoDhrd1.pcx" ],
-		[ "BoCsGr2H.pcx", "BoRDwf2h.pcx", "BoTGa2h.pcx", "BoIImp2H.pcx", "", "", "BoSgob2h.pcx", "BoFgno2h.pcx", "BoDhrd2.pcx" ],
-		[ "", "", "", "", "", "", "", "", "" ],
-		[ "BoCsCv2S.pcx", "BoRGar2.pcx", "BoTCasW.pcx", "BoICasB.pcx", "BoNnecro.pcx", "BoDvort.pcx", "BoSmrk1c.pcx", "BoFcastD.pcx", "BoEuniv.pcx" ],
-		[ "BoCsTav2.pcx", "BoRDwf1t.pcx", "BoTGldL.pcx", "BoICasG.pcx", "BoNskelT.pcx", "BoDport.pcx", "BoSblak2.pcx", "BoFcastA.pcx", "" ],
-		[ "", "", "BoTGldW.pcx", "BoIMagO.pcx", "", "BoDacad.pcx", "BoSvahal.pcx", "", "" ],
-		[ "", "BoRTre1h.pcx", "", "BoIHndH.pcx", "", "", "", "", "" ],
-		[ "", "BoRTre2h.pcx", "", "BoIHnd2H.pcx", "", "", "", "", "" ],
-		[ "BoCsHoly.pcx", "BoRHoly.pcx", "BoTHoly.pcx", "BoIHoly.pcx", "BoNholyG.pcx", "BoDholy.pcx", "BoSholy.pcx", "BoFgrail.pcx", "BoEgrail.pcx" ],
-		[ "", "", "", "", "", "", "", "", "" ],
-		[ "", "", "", "", "", "", "", "", "" ],
-		[ "", "", "", "", "", "", "", "", "" ],
-		[ "BoCsPik1.pcx", "BoRCen1.pcx", "BoTGrem1.pcx", "BoIImp1.pcx", "BoNskel1.pcx", "BoDtrog1.pcx", "BoSgob1.pcx", "BoFgnol1.pcx", "BoEdn_0.pcx" ],
-		[ "BoCsCrs1.pcx", "BoRDwf1.pcx", "BoTGar1.pcx", "BoIGog1.pcx", "BoNzomb1.pcx", "BoDharp1.pcx", "BoSwolf1.pcx", "BoFlizr1.pcx", "BoEdn_1.pcx" ],
-		[ "BoCsGr1.pcx", "BoRElf1.pcx", "BoTGolm1.pcx", "BoIHnd1.pcx", "BoNwigh1.pcx", "BoDbeh1.pcx", "BoSorc1.pcx", "BoFfly1.pcx", "BoEdn_2.pcx" ],
-		[ "BoCsSwd1.pcx", "BoRPeg1.pcx", "BoTMag1.pcx", "BoIDmn1.pcx", "BoNvamp1.pcx", "BoDmedu1.pcx", "BoSogre1.pcx", "BoFbas1.pcx", "BoEdn_3.pcx" ],
-		[ "BoCsMon1.pcx", "BoRTre1.pcx", "BoTGen1.pcx", "BoIPit1.pcx", "BoNlich1.pcx", "BoDmino1.pcx", "BoSroc1.pcx", "BoFgorg1.pcx", "BoEdn_4.pcx" ],
-		[ "BoCsCv1.pcx", "BoRUni1.pcx", "BoTNaga1.pcx", "BoIEfr1.pcx", "BoNbkni1.pcx", "BoDmant1.pcx", "BoScyc1.pcx", "BoFwyvr1.pcx", "BoEdn_5.pcx" ],
-		[ "BoCsAng1.pcx", "BoRDra1.pcx", "BoTTit1.pcx", "BoIDvl1.pcx", "BoNbone1.pcx", "BoDdrag1.pcx", "BoSbeh1.pcx", "BoFhydr1.pcx", "BoEdn_6.pcx" ],
-		[ "BoCsPik2.pcx", "BoRCen2.pcx", "BoTGrem2.pcx", "BoIImp2.pcx", "BoNskel2.pcx", "BoDtrog2.pcx", "BoSgob2.pcx", "BoFgnol2.pcx", "BoEup_0.pcx" ],
-		[ "BoCsCrs2.pcx", "BoRDwf2.pcx", "BoTGar2.pcx", "BoIGog2.pcx", "BoNzomb2.pcx", "BoDharp2.pcx", "BoSwolf2.pcx", "BoFlizr2.pcx", "BoEup_1.pcx" ],
-		[ "BoCsGr2.pcx", "BoRElf2.pcx", "BoTGolm2.pcx", "BoIHnd2.pcx", "BoNwigh2.pcx", "BoDbeh2.pcx", "BoSorc2.pcx", "BoFfly2.pcx", "BoEup_2.pcx" ],
-		[ "BoCsSwd2.pcx", "BoRPeg2.pcx", "BoTMag2.pcx", "BoIDmn2.pcx", "BoNvamp2.pcx", "BoDmedu2.pcx", "BoSogre2.pcx", "BoFbas2.pcx", "BoEup_3.pcx" ],
-		[ "BoCsMon2.pcx", "BoRTre2.pcx", "BoTGen2.pcx", "BoIPit2.pcx", "BoNlich2.pcx", "BoDmino2.pcx", "BoSroc2.pcx", "BoFgorg2.pcx", "BoEup_4.pcx" ],
-		[ "BoCsCv2.pcx", "BoRUni2.pcx", "BoTNaga2.pcx", "BoIEfr2.pcx", "BoNbkni2.pcx", "BoDmant2.pcx", "BoScyc2.pcx", "BoFwyvr2.pcx", "BoEup_5.pcx" ],
-		[ "BoCsAng2.pcx", "BoRDra2.pcx", "BoTTit2.pcx", "BoIDvl2.pcx", "BoNbone2.pcx", "BoDdrag2.pcx", "BoSbeh2.pcx", "BoFhydr2.pcx", "BoEup_6.pcx" ]
-	]
-}

+ 0 - 239
config/campaignMedia.json

@@ -1,239 +0,0 @@
-{
-	"videos": [
-	//Restoration of Erathia
-		//Long live the Queen
-		"GOOD1A.SMK", //Good1_a
-		"GOOD1B.SMK", //Good1_b
-		"GOOD1C.SMK", //Good1_c
-		//Dungeons and devils
-		"EVIL1A.SMK", //Evil1_a
-		"EVIL1B.SMK", //Evil1_b
-		"EVIL1C.SMK", //Evil1_c
-		//Spoils of War
-		"NEUTRALA.SMK", //Neutral1_a
-		"NEUTRALB.SMK", //Neutral1_b
-		"NEUTRALC.SMK", //Neutral1_c
-		//Liberation
-		"GOOD2A.SMK", //Good2_a
-		"GOOD2B.SMK", //Good2_b
-		"GOOD2C.SMK", //Good2_c
-		"GOOD2D.SMK", //Good2_d
-		//Long Live the King
-		"EVIL2A.SMK", //Evil2_a
-		"EVIL2AP1.SMK", //Evil2ap1
-		"EVIL2B.SMK", //Evil2_b
-		"EVIL2C.SMK", //Evil2_c
-		"EVIL2D.SMK", //Evil2_d
-		//Song for the Father
-		"GOOD3A.SMK", //Good3_a
-		"GOOD3B.SMK", //Good3_b
-		"GOOD3C.SMK", //Good3_c
-		//Seeds Of Discontent 
-		"SECRETA.SMK", //Secret_a
-		"SECRETB.SMK", //Secret_b
-		"SECRETC.SMK", //Secret_c
-	//Armageddon's Blade
-		//Armageddon's Blade 
- 		"H3ABab1.smk", //ArmageddonsBlade_a
- 		"H3ABab2.smk", //ArmageddonsBlade_b
- 		"H3ABab3.smk", //ArmageddonsBlade_c
- 		"H3ABab4.smk", //ArmageddonsBlade_d
- 		"H3ABab5.smk", //ArmageddonsBlade_e
- 		"H3ABab6.smk", //ArmageddonsBlade_f
- 		"H3ABab7.smk", //ArmageddonsBlade_g
- 		"H3ABab8.smk", //ArmageddonsBlade_h
- 		"H3ABab9.smk", //ArmageddonsBlade_end
- 		//Dragon's Blood 
- 		"H3ABdb1.smk", //DragonsBlood_a
- 		"H3ABdb2.smk", //DragonsBlood_b
- 		"H3ABdb3.smk", //DragonsBlood_c
- 		"H3ABdb4.smk", //DragonsBlood_d
- 		"H3ABdb5.smk", //DragonsBlood_end
- 		//Dragon Slayer 
- 		"H3ABds1.smk", //DragonSlayer_a
- 		"H3ABds2.smk", //DragonSlayer_b
- 		"H3ABds3.smk", //DragonSlayer_c
- 		"H3ABds4.smk", //DragonSlayer_d
- 		"H3ABds5.smk", //DragonSlayer_end
- 		//Festival of Life 
- 		"H3ABfl1.smk", //FestivalOfLife_a
- 		"H3ABfl2.smk", //FestivalOfLife_b
-		"H3ABfl3.smk", //FestivalOfLife_c
- 		"H3ABfl4.smk", //FestivalOfLife_d
- 		"H3ABfl5.smk", //FestivalOfLife_end
- 		//Foolhardy Waywardness 
-		"H3ABfw1.smk", //FoolhardyWaywardness_a
- 		"H3ABfw2.smk", //FoolhardyWaywardness_b
-		"H3ABfw3.smk", //FoolhardyWaywardness_c
- 		"H3ABfw4.smk", //FoolhardyWaywardness_d
- 		"H3ABfw5.smk", //FoolhardyWaywardness_end
- 		//Playing with Fire 
- 		"H3ABpf1.smk", //PlayingWithFire_a
- 		"H3ABpf2.smk", //PlayingWithFire_b
- 		"H3ABpf3.smk", //PlayingWithFire_c
- 		"H3ABpf4.smk", //PlayingWithFire_end
-	//Shadow of Death Campaigns 
- 		//Birth of a Barbarian 
- 		"H3x2_BBa.smk", //BirthOfABarbarian_a
- 		"H3x2_BBb.smk", //BirthOfABarbarian_b
- 		"H3x2_BBc.smk", //BirthOfABarbarian_c
- 		"H3x2_BBd.smk", //BirthOfABarbarian_d
- 		"H3x2_BBe.smk", //BirthOfABarbarian_e
- 		"H3x2_BBf.smk", //BirthOfABarbarian_end
- 		//Elixir of Life 
- 		"H3x2_Ela.smk", //ElixirOfLife_a
- 		"H3x2_Elb.smk", //ElixirOfLife_b
- 		"H3x2_Elc.smk", //ElixirOfLife_c
- 		"H3x2_Eld.smk", //ElixirOfLife_d
- 		"H3x2_Ele.smk", //ElixirOfLife_end
- 		//Hack and Slash 
- 		"H3x2_HSa.smk", //HackAndSlash_a
- 		"EVIL2C.SMK", //HackAndSlash_b
- 		"H3x2_HSc.smk", //HackAndSlash_c
- 		"H3x2_HSd.smk", //HackAndSlash_d
- 		"H3x2_HSe.smk", //HackAndSlash_end
-		//New Beginning 
- 		"H3x2_NBa.smk", //NewBeginning_a
- 		"H3x2_NBb.smk", //NewBeginning_b
- 		"H3x2_Nbc.smk", //NewBeginning_c
- 		"H3x2_Nbd.smk", //NewBeginning_d
- 		"H3x2_Nbe.smk", //NewBeginning_end
- 		//Rise of the Necromancer 
- 		"H3x2_RNa.smk", //RiseOfTheNecromancer_a
- 		"H3x2_RNb.smk", //RiseOfTheNecromancer_b
- 		"H3x2_RNc.smk", //RiseOfTheNecromancer_c
- 		"H3x2_RNd.smk", //RiseOfTheNecromancer_d
- 		"H3x2_RNe1.smk", //RiseOfTheNecromancer_end
- 		//Spectre of Power 
-		"H3x2_SPa.smk", //SpectreOfPower_a
- 		"H3x2_SPb.smk", //SpectreOfPower_b
- 		"H3x2_SPc.smk", //SpectreOfPower_c
- 		"H3x2_SPd.smk", //SpectreOfPower_d
- 		"H3x2_SPe.smk", //SpectreOfPower_end
- 		//Unholy Alliance 
- 		"H3x2_UAa.smk", //UnholyAlliance_a
-		"H3x2_UAb.smk", //UnholyAlliance_b
-		"H3x2_UAc.smk", //UnholyAlliance_c
-		"H3x2_UAd.smk", //UnholyAlliance_d
-		"H3x2_UAe.smk", //UnholyAlliance_e
-		"H3x2_UAf.smk", //UnholyAlliance_f
-		"H3x2_UAg.smk", //UnholyAlliance_g
-		"H3x2_UAh.smk", //UnholyAlliance_h
-		"H3x2_UAi.smk", //UnholyAlliance_i
-		"H3x2_UAj.smk", //UnholyAlliance_j
-		"H3x2_UAk.smk", //UnholyAlliance_k
-		"H3x2_UAl.smk", //UnholyAlliance_l
- 		"H3x2_UAm.smk", //UnholyAlliance_end //H3x2_UAm.bik?
-	],
-
-	"music" : [
-		// Use CmpMusic.txt from H3 instead
-	],
-
-	"voice" : [
-	//Restoration of Erathia
-		"G1A", //Long live the Queen 1 
-		"G1B", //Long live the Queen 2 
-		"G1C", //Long live the Queen 3
-		"E1A.wav", //Dungeons and Devils 1 
-		"E1B.wav", //Dungeons and Devils 2 
-		"E1C.wav", //Dungeons and Devils 3
-		"N1A", //Spoils of War 1 
-		"N1B", //Spoils of War 2 
-		"N1C_D", //Spoils of War 3
-		"G2A", //Liberation 1 
-		"G2B", //Liberation 2 
-		"G2C", //Liberation 3 
-		"G2D", //Liberation 4
-		"E2A.wav", //Long live the King 1 
-		"E2AE.wav", //Long live the King 1end 
-		"E2B.wav", //Long live the King 2 
-		"E2C.wav", //Long live the King 3 
-		"E2D.wav", //Long live the King 4
-		"G3A", //Song for the Father 1 
-		"G3B", //Song for the Father 2 
-		"G3C", //Song for the Father 3
-		"S1A", //Seeds of discontent 1 
-		"S1B", //Seeds of discontent 2 
-		"S1C", //Seeds of discontent 3
-	//Armageddon's Blade
-		"ABvoAB1.wav", //Armageddon's Blade 1 
-		"ABvoAB2.wav", //Armageddon's Blade 2 
-		"ABvoAB3.wav", //Armageddon's blade 3 
-		"ABvoAB4.wav", //Armageddon's blade 4 
-		"ABvoAB5.wav", //Armageddon's blade 5 
-		"ABvoAB6.wav", //Armageddon's blade 6 
-		"ABvoAB7.wav", //Armageddon's blade 7 
-		"ABvoAB8.wav", //Armageddon's blade 8 
-		"ABvoAB9.wav", //Armageddon's blade 8end
-		"ABvoDB1.wav", //Dragon's Blood 1 
-		"ABvoDB2.wav", //Dragon's Blood 2 
-		"ABvoDB3.wav", //Dragon's Blood 3 
-		"ABvoDB4.wav", //Dragon's Blood 4 
-		"ABvoDB5.wav", //Dragon's Blood 4end
-		"ABvoDS1.wav", //Dragon Slayer 1 
-		"ABvoDS2.wav", //Dragon Slayer 2 
-		"ABvoDS3.wav", //Dragon Slayer 3 
-		"ABvoDS4.wav", //Dragon Slayer 4 
-		"ABvoDS5.wav", //Dragon Slayer 4end 
-		"ABvoFL1.wav", //Festival of Life 1 
-		"ABvoFL2.wav", //Festival of Life 2 
-		"ABvoFL3.wav", //Festival of Life 3 
-		"ABvoFL4.wav", //Festival of Life 4 
-		"ABvoFL5.wav", //Festival of Life 4end
-		"ABvoFW1.wav", //Foolhardy Waywardness 1 
-		"ABvoFW2.wav", //Foolhardy Waywardness 2 
-		"ABvoFW3.wav", //Foolhardy Waywardness 3 
-		"ABvoFW4.wav", //Foolhardy Waywardness 4 
-		"ABvoFW5.wav", //Foolhardy Waywardness 4end 
-		"ABvoPF1.wav", //Playing with Fire 1 
-		"ABvoPF2.wav", //Playing with Fire 2 
-		"ABvoPF3.wav", //Playing with Fire 3 
-		"ABvoPF4.wav", //Playing with Fire 3end
-	//Shadow of Death Campaigns 
-		"H3x2BBa", //Birth of a Barbarian 1 
-		"H3x2BBb", //Birth of a Barbarian 2 
-		"H3x2BBc", //Birth of a Barbarian 3 
-		"H3x2BBd", //Birth of a Barbarian 4 
-		"H3x2BBe", //Birth of a Barbarian 5 
-		"H3x2BBf", //Birth of a Barbarian 5end
-		"H3x2ELa", //Elixir of life 1 
-		"H3x2ELb", //Elixir of life 2 
-		"H3x2ELc", //Elixir of life 3 
-		"H3x2ELd", //Elixir of life 4 
-		"H3x2ELe", //Elixir of life 4end 
-		"H3x2HSa", //Hack and Slash 1 
-		"H3x2HSb", //Hack and Slash 2 
-		"H3x2HSc", //Hack and Slash 3 
-		"H3x2HSd", //Hack and Slash 4 
-		"H3x2HSe", //Hack and Slash 4end 
-		"H3x2NBa", //New Beginning 1 
-		"H3x2NBb", //New Beginning 2 
-		"H3x2NBc", //New Beginning 3 
-		"H3x2NBd", //New Beginning 4 
-		"H3x2NBe", //New Beginning 4end 
-		"H3x2RNa", //Rise of the Necromancer 1 
-		"H3x2RNb", //Rise of the Necromancer 2 
-		"H3x2RNc", //Rise of the Necromancer 3 
-		"H3x2RNd", //Rise of the Necromancer 4 
-		"H3x2RNe", //Rise of the Necromancer 4end 
-		"H3x2SPa", //Spectre of Power 1 
-		"H3x2Spb", //Spectre of Power 2 
-		"H3x2Spc", //Spectre of Power 3 
-		"H3x2Spd", //Spectre of Power 4 
-		"H3x2Spe", //Spectre of Power 4end 
-		"H3x2UAa", //Unholy alliance 1 
-		"H3x2UAb", //Unholy alliance 2 
-		"H3x2UAc", //Unholy alliance 3 
-		"H3x2UAd", //Unholy alliance 4 
-		"H3x2UAe", //Unholy alliance 5 
-		"H3x2UAf", //Unholy alliance 6 
-		"H3x2UAg", //Unholy alliance 7 
-		"H3x2UAh", //Unholy alliance 8 
-		"H3x2UAi", //Unholy alliance 9 
-		"H3x2UAj", //Unholy alliance 10 
-		"H3x2UAk", //Unholy alliance 11 
-		"H3x2UAl", //Unholy alliance 12 
-		"H3x2UAm" //Unholy alliance 12end
-	]
-}

+ 189 - 4
config/campaignOverrides.json

@@ -1,11 +1,196 @@
 {
-	"DATA/GOOD3" : { // RoE - "Song for the Father"
-		"outroVideo": "Endgame"
+	/// RoE CAMPAIGNS
+	
+	"DATA/GOOD1" : { //Long live the Queen
+		"restrictedGarrisonsForAI" : true,
+		"scenarios": [
+			{ "voiceProlog": "G1A" },
+			{ "voiceProlog": "G1B" },
+			{ "voiceProlog": "G1C" }
+		]
+	},
+	"DATA/EVIL1" : { // Dungeons and Devils
+		"restrictedGarrisonsForAI" : true,
+		"scenarios": [
+			{ "voiceProlog": "E1A" },
+			{ "voiceProlog": "E1B" },
+			{ "voiceProlog": "E1C" }
+		]
+	},
+	"DATA/GOOD2" : { // Liberation
+		"restrictedGarrisonsForAI" : true,
+		"scenarios": [
+			{ "voiceProlog": "G2A" },
+			{ "voiceProlog": "G2B" },
+			{ "voiceProlog": "G2C" },
+			{ "voiceProlog": "G2D" }
+		]
 	},
-	"DATA/AB" : { // AB Intro
+	"DATA/NEUTRAL1" : { // Spoils of War
+		"restrictedGarrisonsForAI" : true,
+		"scenarios": [
+			{ "voiceProlog": "N1A" },
+			{ "voiceProlog": "N1B" },
+			{ "voiceProlog": "N1C_D" }
+		]
+	},
+	"DATA/EVIL2" : { // Long live the King
+		"restrictedGarrisonsForAI" : true,
+		"scenarios": [
+			{ "voiceProlog": "E2A", "voiceEpilog": "E2AE" },
+			{ "voiceProlog": "E2B" },
+			{ "voiceProlog": "E2C" },
+			{ "voiceProlog": "E2D" }
+		]
+	},
+	
+	"DATA/GOOD3" : { // Song for the Father
+		"outroVideo": "Endgame",
+		"restrictedGarrisonsForAI" : true,
+		"scenarios": [
+			{ "voiceProlog": "G3A" },
+			{ "voiceProlog": "G3B" },
+			{ "voiceProlog": "G3C" }
+		]
+	},
+	"DATA/SECRET1" : { // Seeds of discontent
+		"restrictedGarrisonsForAI" : true,
+		"scenarios": [
+			{ "voiceProlog": "S1A" },
+			{ "voiceProlog": "S1B" },
+			{ "voiceProlog": "S1C" }
+		]
+	},
+	
+	/// AB CAMPAIGNS
+
+	"DATA/AB" : { // Armageddon's Blade
 		"introVideo": "H3X1intr",
-		"videoRim": "IntroRm2"
+		"videoRim": "IntroRm2",
+		"scenarios": [
+			{ "voiceProlog": "ABvoAB1" },
+			{ "voiceProlog": "ABvoAB2" },
+			{ "voiceProlog": "ABvoAB3" },
+			{ "voiceProlog": "ABvoAB4" },
+			{ "voiceProlog": "ABvoAB5" },
+			{ "voiceProlog": "ABvoAB6" },
+			{ "voiceProlog": "ABvoAB7" },
+			{ "voiceProlog": "ABvoAB8", "voiceEpilog": "ABvoAB9" }
+		]
+	},
+	"DATA/BLOOD" : { // Dragon's Blood
+		"scenarios": [
+			{ "voiceProlog": "ABvoDB1" },
+			{ "voiceProlog": "ABvoDB2" },
+			{ "voiceProlog": "ABvoDB3" },
+			{ "voiceProlog": "ABvoDB4", "voiceEpilog": "ABvoDB5" }
+		]
+	},
+	"DATA/SLAYER" : { // Dragon Slayer
+		"scenarios": [
+			{ "voiceProlog": "ABvoDS1" },
+			{ "voiceProlog": "ABvoDS2" },
+			{ "voiceProlog": "ABvoDS3" },
+			{ "voiceProlog": "ABvoDS4", "voiceEpilog": "ABvoDS5" }
+		]
+	},
+	"DATA/FESTIVAL" : { // Festival of Life
+		"scenarios": [
+			{ "voiceProlog": "ABvoFL1" },
+			{ "voiceProlog": "ABvoFL2" },
+			{ "voiceProlog": "ABvoFL3" },
+			{ "voiceProlog": "ABvoFL4", "voiceEpilog": "ABvoFL5" }
+		]
+	},
+	"DATA/FIRE" : { // Playing with Fire
+		"scenarios": [
+			{ "voiceProlog": "ABvoPF1" },
+			{ "voiceProlog": "ABvoPF2" },
+			{ "voiceProlog": "ABvoPF3", "voiceEpilog": "ABvoPF4" }
+		]
+	},
+	"DATA/FOOL" : { // Foolhardy Waywardness
+		"scenarios": [
+			{ "voiceProlog": "ABvoFW1" },
+			{ "voiceProlog": "ABvoFW2" },
+			{ "voiceProlog": "ABvoFW3" },
+			{ "voiceProlog": "ABvoFW4", "voiceEpilog": "ABvoFW5" }
+		]
+	},
+
+	/// SoD CAMPAIGNS
+
+	"DATA/GELU" : { // Elixir of life
+		"scenarios": [
+			{ "voiceProlog": "H3x2ELa" },
+			{ "voiceProlog": "H3x2ELb" },
+			{ "voiceProlog": "H3x2ELc" },
+			{ "voiceProlog": "H3x2ELd", "voiceEpilog": "H3x2ELe" }
+		]
+	},
+	"DATA/CRAG" : { // Hack and Slash
+		"scenarios": [
+			{ "voiceProlog": "H3x2HSa" },
+			{ "voiceProlog": "H3x2HSb" },
+			{ "voiceProlog": "H3x2HSc" },
+			{ "voiceProlog": "H3x2HSd", "voiceEpilog": "H3x2HSe" }
+		]
+	},
+	"DATA/SANDRO" : { // Rise of the Necromancer
+		"scenarios": [
+			{ "voiceProlog": "H3x2RNa" },
+			{ "voiceProlog": "H3x2RNb" },
+			{ "voiceProlog": "H3x2RNc" },
+			{ "voiceProlog": "H3x2RNd", "voiceEpilog": "H3x2RNe" }
+		]
+	},
+	"DATA/GEM" : { // New Beginning
+		"heroGemSorceress" : "gem", // Gem (Sorceress class)
+		"scenarios": [
+			{ "voiceProlog": "H3x2NBa" },
+			{ "voiceProlog": "H3x2NBb" },
+			{ "voiceProlog": "H3x2NBc" },
+			{ "voiceProlog": "H3x2NBd", "voiceEpilog": "H3x2NBe" }
+		]
+	},
+	"DATA/YOG" : { // Birth of a Barbarian
+		"heroYogWizard" : "solmyr", // Yog (based on Solmyr)
+		"scenarios": [
+			{ "voiceProlog": "H3x2BBa" },
+			{ "voiceProlog": "H3x2BBb" },
+			{ "voiceProlog": "H3x2BBc" },
+			{ "voiceProlog": "H3x2BBd" },
+			{ "voiceProlog": "H3x2BBe", "voiceEpilog": "H3x2BBf" }
+		]
+	},
+	"DATA/FINAL" : { // Unholy Alliance
+		"heroGemSorceress" : "gem", // Gem (Sorceress class)
+		"scenarios": [
+			{ "voiceProlog": "H3x2UAa" },
+			{ "voiceProlog": "H3x2UAb" },
+			{ "voiceProlog": "H3x2UAc" },
+			{ "voiceProlog": "H3x2UAd" },
+			{ "voiceProlog": "H3x2UAe" },
+			{ "voiceProlog": "H3x2UAf" },
+			{ "voiceProlog": "H3x2UAg" },
+			{ "voiceProlog": "H3x2UAh" },
+			{ "voiceProlog": "H3x2UAi" },
+			{ "voiceProlog": "H3x2UAj" },
+			{ "voiceProlog": "H3x2UAk" },
+			{ "voiceProlog": "H3x2UAl", "voiceEpilog": "H3x2UAm" }
+		]
+	},
+	"DATA/SECRET" : { // Spectre of Power
+		"scenarios": [
+			{ "voiceProlog": "H3x2SPa" },
+			{ "voiceProlog": "H3x2Spb" },
+			{ "voiceProlog": "H3x2Spc" },
+			{ "voiceProlog": "H3x2Spd", "voiceEpilog": "H3x2Spe" }
+		]
 	},
+	
+	/// CHRONICLES CAMPAIGNS
+	
 	"MAPS/CHRONICLES/HC1_MAIN" : { // Heroes Chronicles 1
 		"regions":
 		{

+ 238 - 0
config/campaignRegions.json

@@ -0,0 +1,238 @@
+{
+	// RoE
+	
+	"good1" : {
+		"prefix": "G1",
+		"colorSuffixLength": 1,
+		"desc": [
+			{ "infix": "A", "x": 57, "y": 314 },
+			{ "infix": "B", "x": 137, "y": 309 },
+			{ "infix": "C", "x": 44, "y": 163 }
+		]
+	},
+
+	"good2" : {
+		"prefix": "G2",
+		"colorSuffixLength": 1,
+		"desc": [
+			{ "infix": "A", "x": 56, "y": 90 },
+			{ "infix": "B", "x": 316, "y": 49 },
+			{ "infix": "C", "x": 54, "y": 378 },
+			{ "infix": "D", "x": 151, "y": 126 }
+		]
+	},
+
+	"good3" : {
+		"prefix": "G3",
+		"colorSuffixLength": 1,
+		"desc": [
+			{ "infix": "A", "x": 289, "y": 376 },
+			{ "infix": "B", "x": 60, "y": 147 },
+			{ "infix": "C", "x": 131, "y": 202 }
+		]
+	},
+
+	"evil1" : {
+		"prefix": "E1",
+		"colorSuffixLength": 1,
+		"desc": [
+			{ "infix": "A", "x": 270, "y": 332 },
+			{ "infix": "B", "x": 138, "y": 113 },
+			{ "infix": "C", "x": 26, "y": 70 },
+			{ "infix": "P1", "x": 256, "y": 127 },
+			{ "infix": "P2", "x": 57, "y": 314 },
+			{ "infix": "P3", "x": 137, "y": 310 },
+			{ "infix": "P4", "x": 44, "y": 163 }
+		]
+	},
+
+	"evil2" : {
+		"prefix": "E2",
+		"colorSuffixLength": 1,
+		"desc": [
+			{ "infix": "A", "x": 131, "y": 202 },
+			{ "infix": "B", "x": 60, "y": 145 },
+			{ "infix": "C", "x": 92, "y": 261 },
+			{ "infix": "D", "x": 218, "y": 307 }
+		]
+	},
+
+	"neutral1" : {
+		"prefix": "N1",
+		"colorSuffixLength": 1,
+		"desc": [
+			{ "infix": "A", "x": 42, "y": 94 },
+			{ "infix": "B", "x": 309, "y": 290 },
+			{ "infix": "CD", "x": 188, "y": 202 }
+		]
+	},
+
+	"secret1" : {
+		"prefix": "S1",
+		"colorSuffixLength": 1,
+		"desc": [
+			{ "infix": "A", "x": 263, "y": 199 },
+			{ "infix": "B", "x": 182, "y": 210 },
+			{ "infix": "C", "x": 82, "y": 152 }
+		]
+	},
+
+	// AB
+
+	"dragonSlayer" : {
+		"prefix": "BR",
+		"colorSuffixLength": 2,
+		"desc": [
+			{ "infix": "A", "x": 18, "y": 233 },
+			{ "infix": "B", "x": 125, "y": 381 },
+			{ "infix": "C", "x": 224, "y": 357 },
+			{ "infix": "D", "x": 192, "y": 320 }
+		]
+	},
+
+	"foolhardyWaywardness" : {
+		"prefix": "IS",
+		"colorSuffixLength": 2,
+		"desc": [
+			{ "infix": "A", "x": 294, "y": 399 },
+			{ "infix": "B", "x": 183, "y": 293 },
+			{ "infix": "C", "x": 40, "y": 92 },
+			{ "infix": "D", "x": 294, "y": 398 }
+		]
+	},
+
+	"festivalOfLife" : {
+		"prefix": "KR",
+		"colorSuffixLength": 2,
+		"desc": [
+			{ "infix": "A", "x": 148, "y": 323 },
+			{ "infix": "B", "x": 192, "y": 235 },
+			{ "infix": "C", "x": 136, "y": 158 },
+			{ "infix": "D", "x": 87, "y": 107 }
+		]
+	},
+
+	"dragonsBlood" : {
+		"prefix": "NI",
+		"colorSuffixLength": 2,
+		"desc": [
+			{ "infix": "A", "x": 118, "y": 111 },
+			{ "infix": "B", "x": 223, "y": 145 },
+			{ "infix": "C", "x": 320, "y": 213 },
+			{ "infix": "D", "x": 233, "y": 250 }
+		]
+	},
+
+	"playingWithFire" : {
+		"prefix": "TA",
+		"colorSuffixLength": 2,
+		"desc": [
+			{ "infix": "A", "x": 228, "y": 233 },
+			{ "infix": "B", "x": 147, "y": 194 },
+			{ "infix": "C", "x": 112, "y": 97 }
+		]
+	},
+
+	"armageddonsBlade" : {
+		"prefix": "AR",
+		"colorSuffixLength": 2,
+		"desc": [
+			{ "infix": "A", "x": 135, "y": 238 },
+			{ "infix": "B", "x": 135, "y": 121 },
+			{ "infix": "C", "x": 206, "y": 155 },
+			{ "infix": "D", "x": 105, "y": 397 },
+			{ "infix": "E", "x": 109, "y": 275 },
+			{ "infix": "F", "x": 158, "y": 188 },
+			{ "infix": "G", "x": 200, "y": 261 },
+			{ "infix": "H", "x": 232, "y": 197 }
+		]
+	},
+
+	// SoD
+
+	"hackAndSlash" : {
+		"prefix": "HS",
+		"colorSuffixLength": 2,
+		"desc": [
+			{ "infix": "A", "x": 141, "y": 326 },
+			{ "infix": "B", "x": 238, "y": 275 },
+			{ "infix": "C", "x": 22, "y": 161 },
+			{ "infix": "D", "x": 5, "y": 9 }
+		]
+	},
+
+	"birthOfBarbarian" : {
+		"prefix": "BB",
+		"colorSuffixLength": 2,
+		"desc": [
+			{ "infix": "A", "x": 167, "y": 342 },
+			{ "infix": "B", "x": 217, "y": 263 },
+			{ "infix": "C", "x": 0, "y": 71 },
+			{ "infix": "D", "x": 291, "y": 79 },
+			{ "infix": "E", "x": 316, "y": 199 }
+		]
+	},
+
+	"newBeginning" : {
+		"prefix": "NB",
+		"colorSuffixLength": 2,
+		"desc": [
+			{ "infix": "A", "x": 6, "y": 292 },
+			{ "infix": "B", "x": 161, "y": 334 },
+			{ "infix": "C", "x": 63, "y": 195 },
+			{ "infix": "D", "x": 56, "y": 46 }
+		]
+	},
+
+	"elixirOfLife" : {
+		"prefix": "EL",
+		"colorSuffixLength": 2,
+		"desc": [
+			{ "infix": "A", "x": 11, "y": 73 },
+			{ "infix": "B", "x": 0, "y": 241 },
+			{ "infix": "C", "x": 254, "y": 34 },
+			{ "infix": "D", "x": 91, "y": 144 }
+		]
+	},
+
+	"riseOfTheNecromancer" : {
+		"prefix": "RN",
+		"colorSuffixLength": 2,
+		"desc": [
+			{ "infix": "A", "x": 84, "y": 319 },
+			{ "infix": "B", "x": 194, "y": 275 },
+			{ "infix": "C", "x": 67, "y": 185 },
+			{ "infix": "D", "x": 77, "y": 30 }
+		]
+	},
+
+	"unholyAlliance" : {
+		"prefix": "UA",
+		"colorSuffixLength": 2,
+		"desc": [
+			{ "infix": "A", "x": 157, "y": 409 },
+			{ "infix": "B", "x": 62, "y": 346 },
+			{ "infix": "C", "x": 8, "y": 8 },
+			{ "infix": "D", "x": 206, "y": 1 },
+			{ "infix": "E", "x": 132, "y": 357 },
+			{ "infix": "F", "x": 184, "y": 83 },
+			{ "infix": "G", "x": 159, "y": 263 },
+			{ "infix": "H", "x": 108, "y": 173 },
+			{ "infix": "I", "x": 55, "y": 127 },
+			{ "infix": "J", "x": 9, "y": 252 },
+			{ "infix": "K", "x": 210, "y": 176 },
+			{ "infix": "L", "x": 260, "y": 210 }
+		]
+	},
+
+	"spectreOfPower" : {
+		"prefix": "SP",
+		"colorSuffixLength": 2,
+		"desc": [
+			{ "infix": "A", "x": 7, "y": 295 },
+			{ "infix": "B", "x": 44, "y": 141 },
+			{ "infix": "C", "x": 141, "y": 21 },
+			{ "infix": "D", "x": 243, "y": 156 }
+		]
+	}
+}

+ 0 - 234
config/campaign_regions.json

@@ -1,234 +0,0 @@
-{
-	"campaign_regions": [
-		{
-			"prefix": "G1",
-			"colorSuffixLength": 1,
-			"desc": [
-				{ "infix": "A", "x": 57, "y": 314 },
-				{ "infix": "B", "x": 137, "y": 309 },
-				{ "infix": "C", "x": 44, "y": 163 }
-			]
-		},
-
-		{
-			"prefix": "G2",
-			"colorSuffixLength": 1,
-			"desc": [
-				{ "infix": "A", "x": 56, "y": 90 },
-				{ "infix": "B", "x": 316, "y": 49 },
-				{ "infix": "C", "x": 54, "y": 378 },
-				{ "infix": "D", "x": 151, "y": 126 }
-			]
-		},
-
-		{
-			"prefix": "G3",
-			"colorSuffixLength": 1,
-			"desc": [
-				{ "infix": "A", "x": 289, "y": 376 },
-				{ "infix": "B", "x": 60, "y": 147 },
-				{ "infix": "C", "x": 131, "y": 202 }
-			]
-		},
-
-		{
-			"prefix": "E1",
-			"colorSuffixLength": 1,
-			"desc": [
-				{ "infix": "A", "x": 270, "y": 332 },
-				{ "infix": "B", "x": 138, "y": 113 },
-				{ "infix": "C", "x": 26, "y": 70 },
-				{ "infix": "P1", "x": 256, "y": 127 },
-				{ "infix": "P2", "x": 57, "y": 314 },
-				{ "infix": "P3", "x": 137, "y": 310 },
-				{ "infix": "P4", "x": 44, "y": 163 }
-			]
-		},
-
-		{
-			"prefix": "E2",
-			"colorSuffixLength": 1,
-			"desc": [
-				{ "infix": "A", "x": 131, "y": 202 },
-				{ "infix": "B", "x": 60, "y": 145 },
-				{ "infix": "C", "x": 92, "y": 261 },
-				{ "infix": "D", "x": 218, "y": 307 }
-			]
-		},
-
-		{
-			"prefix": "N1",
-			"colorSuffixLength": 1,
-			"desc": [
-				{ "infix": "A", "x": 42, "y": 94 },
-				{ "infix": "B", "x": 309, "y": 290 },
-				{ "infix": "CD", "x": 188, "y": 202 }
-			]
-		},
-
-		{
-			"prefix": "S1",
-			"colorSuffixLength": 1,
-			"desc": [
-				{ "infix": "A", "x": 263, "y": 199 },
-				{ "infix": "B", "x": 182, "y": 210 },
-				{ "infix": "C", "x": 82, "y": 152 }
-			]
-		},
-
-		{
-			"prefix": "BR",
-			"colorSuffixLength": 2,
-			"desc": [
-				{ "infix": "A", "x": 18, "y": 233 },
-				{ "infix": "B", "x": 125, "y": 381 },
-				{ "infix": "C", "x": 224, "y": 357 },
-				{ "infix": "D", "x": 192, "y": 320 }
-			]
-		},
-
-		{
-			"prefix": "IS",
-			"colorSuffixLength": 2,
-			"desc": [
-				{ "infix": "A", "x": 294, "y": 399 },
-				{ "infix": "B", "x": 183, "y": 293 },
-				{ "infix": "C", "x": 40, "y": 92 },
-				{ "infix": "D", "x": 294, "y": 398 }
-			]
-		},
-
-		{
-			"prefix": "KR",
-			"colorSuffixLength": 2,
-			"desc": [
-				{ "infix": "A", "x": 148, "y": 323 },
-				{ "infix": "B", "x": 192, "y": 235 },
-				{ "infix": "C", "x": 136, "y": 158 },
-				{ "infix": "D", "x": 87, "y": 107 }
-			]
-		},
-
-		{
-			"prefix": "NI",
-			"colorSuffixLength": 2,
-			"desc": [
-				{ "infix": "A", "x": 118, "y": 111 },
-				{ "infix": "B", "x": 223, "y": 145 },
-				{ "infix": "C", "x": 320, "y": 213 },
-				{ "infix": "D", "x": 233, "y": 250 }
-			]
-		},
-
-		{
-			"prefix": "TA",
-			"colorSuffixLength": 2,
-			"desc": [
-				{ "infix": "A", "x": 228, "y": 233 },
-				{ "infix": "B", "x": 147, "y": 194 },
-				{ "infix": "C", "x": 112, "y": 97 }
-			]
-		},
-
-		{
-			"prefix": "AR",
-			"colorSuffixLength": 2,
-			"desc": [
-				{ "infix": "A", "x": 135, "y": 238 },
-				{ "infix": "B", "x": 135, "y": 121 },
-				{ "infix": "C", "x": 206, "y": 155 },
-				{ "infix": "D", "x": 105, "y": 397 },
-				{ "infix": "E", "x": 109, "y": 275 },
-				{ "infix": "F", "x": 158, "y": 188 },
-				{ "infix": "G", "x": 200, "y": 261 },
-				{ "infix": "H", "x": 232, "y": 197 }
-			]
-		},
-
-		{
-			"prefix": "HS",
-			"colorSuffixLength": 2,
-			"desc": [
-				{ "infix": "A", "x": 141, "y": 326 },
-				{ "infix": "B", "x": 238, "y": 275 },
-				{ "infix": "C", "x": 22, "y": 161 },
-				{ "infix": "D", "x": 5, "y": 9 }
-			]
-		},
-
-		{
-			"prefix": "BB",
-			"colorSuffixLength": 2,
-			"desc": [
-				{ "infix": "A", "x": 167, "y": 342 },
-				{ "infix": "B", "x": 217, "y": 263 },
-				{ "infix": "C", "x": 0, "y": 71 },
-				{ "infix": "D", "x": 291, "y": 79 },
-				{ "infix": "E", "x": 316, "y": 199 }
-			]
-		},
-
-		{
-			"prefix": "NB",
-			"colorSuffixLength": 2,
-			"desc": [
-				{ "infix": "A", "x": 6, "y": 292 },
-				{ "infix": "B", "x": 161, "y": 334 },
-				{ "infix": "C", "x": 63, "y": 195 },
-				{ "infix": "D", "x": 56, "y": 46 }
-			]
-		},
-
-		{
-			"prefix": "EL",
-			"colorSuffixLength": 2,
-			"desc": [
-				{ "infix": "A", "x": 11, "y": 73 },
-				{ "infix": "B", "x": 0, "y": 241 },
-				{ "infix": "C", "x": 254, "y": 34 },
-				{ "infix": "D", "x": 91, "y": 144 }
-			]
-		},
-
-		{
-			"prefix": "RN",
-			"colorSuffixLength": 2,
-			"desc": [
-				{ "infix": "A", "x": 84, "y": 319 },
-				{ "infix": "B", "x": 194, "y": 275 },
-				{ "infix": "C", "x": 67, "y": 185 },
-				{ "infix": "D", "x": 77, "y": 30 }
-			]
-		},
-
-		{
-			"prefix": "UA",
-			"colorSuffixLength": 2,
-			"desc": [
-				{ "infix": "A", "x": 157, "y": 409 },
-				{ "infix": "B", "x": 62, "y": 346 },
-				{ "infix": "C", "x": 8, "y": 8 },
-				{ "infix": "D", "x": 206, "y": 1 },
-				{ "infix": "E", "x": 132, "y": 357 },
-				{ "infix": "F", "x": 184, "y": 83 },
-				{ "infix": "G", "x": 159, "y": 263 },
-				{ "infix": "H", "x": 108, "y": 173 },
-				{ "infix": "I", "x": 55, "y": 127 },
-				{ "infix": "J", "x": 9, "y": 252 },
-				{ "infix": "K", "x": 210, "y": 176 },
-				{ "infix": "L", "x": 260, "y": 210 }
-			]
-		},
-
-		{
-			"prefix": "SP",
-			"colorSuffixLength": 2,
-			"desc": [
-				{ "infix": "A", "x": 7, "y": 295 },
-				{ "infix": "B", "x": 44, "y": 141 },
-				{ "infix": "C", "x": 141, "y": 21 },
-				{ "infix": "D", "x": 243, "y": 156 }
-			]
-		}
-	]
-}

+ 36 - 36
config/factions/castle.json

@@ -80,43 +80,43 @@
 			"structures" :
 			{
 				"extraAnimation": { "animation" : "TBCSEXT2.def", "x" : 46,  "y" : 119 },
-				"mageGuild1":     { "animation" : "TBCSMAGE.def", "x" : 707, "y" : 166, "z" : 1, "border" : "TOCSMAG1.bmp", "area" : "TZCSMAG1.bmp" },
-				"mageGuild2":     { "animation" : "TBCSMAG2.def", "x" : 706, "y" : 135, "z" : 1, "border" : "TOCSMAG2.bmp", "area" : "TZCSMAG2.bmp" },
-				"mageGuild3":     { "animation" : "TBCSMAG3.def", "x" : 704, "y" : 107, "z" : 1, "border" : "TOCSM301.bmp", "area" : "TZCSM301.bmp" },
-				"mageGuild4":     { "animation" : "TBCSMAG4.def", "x" : 704, "y" : 76,  "z" : 1, "border" : "TOCSM401.bmp", "area" : "TZCSM401.bmp" },
-				"tavern":         { "animation" : "TBCSTVRN.def", "x" : 0,   "y" : 230, "z" : 2, "border" : "TOCSTAV1.bmp", "area" : "TZCSTAV1.bmp" },
-				"shipyard":       { "animation" : "TBCSDOCK.def", "x" : 478, "y" : 134, "z" : -3, "border" : "TOCSDKMS.bmp", "area" : "TZCSDKMS.bmp" },
-				"fort":           { "animation" : "TBCSCSTL.def", "x" : 595, "y" : 66,  "z" : -5, "border" : "TOCSCAS1.bmp", "area" : "TZCSCAS1.bmp" },
-				"citadel":        { "animation" : "TBCSCAS2.def", "x" : 478, "y" : 66,  "z" : -5, "border" : "TOCSCAS2.bmp", "area" : "TZCSCAS2.bmp" },
-				"castle":         { "animation" : "TBCSCAS3.def", "x" : 478, "y" : 37,  "z" : -5, "border" : "TOCSCAS3.bmp", "area" : "TZCSCAS3.bmp" },
-				"villageHall":    { "animation" : "TBCSHALL.def", "x" : 0,   "y" : 209, "z" : 1, "border" : "TOCSH101.bmp", "area" : "TZCSH101.bmp" },
-				"townHall":       { "animation" : "TBCSHAL2.def", "x" : 0,   "y" : 176, "z" : 1, "border" : "TOCSH201.bmp", "area" : "TZCSH201.bmp" },
-				"cityHall":       { "animation" : "TBCSHAL3.def", "x" : 0,   "y" : 164, "z" : 1, "border" : "TOCSH301.bmp", "area" : "TZCSH301.bmp" },
-				"capitol":        { "animation" : "TBCSHAL4.def", "x" : 0,   "y" : 154, "z" : 1, "border" : "TOCSH401.bmp", "area" : "TZCSH401.bmp" },
-				"marketplace":    { "animation" : "TBCSMARK.def", "x" : 413, "y" : 264, "z" : 0, "border" : "TOCSMRK1.bmp", "area" : "TZCSMRK1.bmp" },
-				"resourceSilo":   { "animation" : "TBCSSILO.def", "x" : 488, "y" : 228, "z" : 1, "border" : "TOCSMRK2.bmp", "area" : "TZCSMRK2.bmp" },
-				"blacksmith":     { "animation" : "TBCSBLAK.def", "x" : 213, "y" : 251, "z" : 0, "border" : "TOCSBLAK.bmp", "area" : "TZCSBLAK.bmp" },
-				"special1":       { "animation" : "TBCSSPEC.def", "x" : 533, "y" : 71,  "z" : -4, "border" : "TOCSLT01.bmp", "area" : "TZCSLT01.bmp" },
-				"horde1":         { "animation" : "TBCSHRD1.def", "x" : 76,  "y" : 53,  "z" : -1, "border" : "TOCSGR1H.bmp", "area" : "TZCSGR1H.bmp", "hidden" : true },
-				"horde1Upgr":     { "animation" : "TBCSHRD2.def", "x" : 76,  "y" : 35,  "z" : -1, "border" : "TOCSGR2H.bmp", "area" : "TZCSGR2H.bmp", "hidden" : true, "builds" : "horde1" },
+				"mageGuild1":     { "animation" : "TBCSMAGE.def", "x" : 707, "y" : 166, "z" : 1,  "campaignBonus" : "BoCsMag1.pcx", "border" : "TOCSMAG1.bmp", "area" : "TZCSMAG1.bmp" },
+				"mageGuild2":     { "animation" : "TBCSMAG2.def", "x" : 706, "y" : 135, "z" : 1,  "campaignBonus" : "BoCsMag2.pcx", "border" : "TOCSMAG2.bmp", "area" : "TZCSMAG2.bmp" },
+				"mageGuild3":     { "animation" : "TBCSMAG3.def", "x" : 704, "y" : 107, "z" : 1,  "campaignBonus" : "BoCsMag3.pcx", "border" : "TOCSM301.bmp", "area" : "TZCSM301.bmp" },
+				"mageGuild4":     { "animation" : "TBCSMAG4.def", "x" : 704, "y" : 76,  "z" : 1,  "campaignBonus" : "BoCsMag4.pcx", "border" : "TOCSM401.bmp", "area" : "TZCSM401.bmp" },
+				"tavern":         { "animation" : "TBCSTVRN.def", "x" : 0,   "y" : 230, "z" : 2,  "campaignBonus" : "BoCsTav1.pcx", "border" : "TOCSTAV1.bmp", "area" : "TZCSTAV1.bmp" },
+				"shipyard":       { "animation" : "TBCSDOCK.def", "x" : 478, "y" : 134, "z" : -3, "campaignBonus" : "BoCsDock.pcx", "border" : "TOCSDKMS.bmp", "area" : "TZCSDKMS.bmp" },
+				"fort":           { "animation" : "TBCSCSTL.def", "x" : 595, "y" : 66,  "z" : -5, "campaignBonus" : "BoCsCas1.pcx", "border" : "TOCSCAS1.bmp", "area" : "TZCSCAS1.bmp" },
+				"citadel":        { "animation" : "TBCSCAS2.def", "x" : 478, "y" : 66,  "z" : -5, "campaignBonus" : "BoCsCas2.pcx", "border" : "TOCSCAS2.bmp", "area" : "TZCSCAS2.bmp" },
+				"castle":         { "animation" : "TBCSCAS3.def", "x" : 478, "y" : 37,  "z" : -5, "campaignBonus" : "BoCsCas3.pcx", "border" : "TOCSCAS3.bmp", "area" : "TZCSCAS3.bmp" },
+				"villageHall":    { "animation" : "TBCSHALL.def", "x" : 0,   "y" : 209, "z" : 1,  "campaignBonus" : "BoCsHal1.pcx", "border" : "TOCSH101.bmp", "area" : "TZCSH101.bmp" },
+				"townHall":       { "animation" : "TBCSHAL2.def", "x" : 0,   "y" : 176, "z" : 1,  "campaignBonus" : "BoCsHal2.pcx", "border" : "TOCSH201.bmp", "area" : "TZCSH201.bmp" },
+				"cityHall":       { "animation" : "TBCSHAL3.def", "x" : 0,   "y" : 164, "z" : 1,  "campaignBonus" : "BoCsHal3.pcx", "border" : "TOCSH301.bmp", "area" : "TZCSH301.bmp" },
+				"capitol":        { "animation" : "TBCSHAL4.def", "x" : 0,   "y" : 154, "z" : 1,  "campaignBonus" : "BoCsHal4.pcx", "border" : "TOCSH401.bmp", "area" : "TZCSH401.bmp" },
+				"marketplace":    { "animation" : "TBCSMARK.def", "x" : 413, "y" : 264, "z" : 0,  "campaignBonus" : "BoCsMrk1.pcx", "border" : "TOCSMRK1.bmp", "area" : "TZCSMRK1.bmp" },
+				"resourceSilo":   { "animation" : "TBCSSILO.def", "x" : 488, "y" : 228, "z" : 1,  "campaignBonus" : "BoCsMrk2.pcx", "border" : "TOCSMRK2.bmp", "area" : "TZCSMRK2.bmp" },
+				"blacksmith":     { "animation" : "TBCSBLAK.def", "x" : 213, "y" : 251, "z" : 0,  "campaignBonus" : "BoCsBlak.pcx", "border" : "TOCSBLAK.bmp", "area" : "TZCSBLAK.bmp" },
+				"special1":       { "animation" : "TBCSSPEC.def", "x" : 533, "y" : 71,  "z" : -4, "campaignBonus" : "BoCsLite.pcx", "border" : "TOCSLT01.bmp", "area" : "TZCSLT01.bmp" },
+				"horde1":         { "animation" : "TBCSHRD1.def", "x" : 76,  "y" : 53,  "z" : -1, "campaignBonus" : "BoCsGr1H.pcx", "border" : "TOCSGR1H.bmp", "area" : "TZCSGR1H.bmp", "hidden" : true },
+				"horde1Upgr":     { "animation" : "TBCSHRD2.def", "x" : 76,  "y" : 35,  "z" : -1, "campaignBonus" : "BoCsGr2H.pcx", "border" : "TOCSGR2H.bmp", "area" : "TZCSGR2H.bmp", "hidden" : true, "builds" : "horde1" },
 				"ship":           { "animation" : "TBCSBOAT.def", "x" : 478, "y" : 134, "z" : -3, "border" : "TOCSDKMN.bmp", "area" : "TZCSDKMN.bmp", "hidden" : true },
-				"special2":       { "animation" : "TBCSEXT0.def", "x" : 384, "y" : 193, "z" : -2, "border" : "TOCSCAVM.bmp", "area" : "TZCSCAVM.bmp" },
-				"special3":       { "animation" : "TBCSEXT1.def", "x" : 0,   "y" : 198, "z" :  2, "border" : "TOCSTAV2.bmp", "area" : "TZCSTAV2.bmp" },
-				"grail":          { "animation" : "TBCSHOLY.def", "x" : 456, "y" : 109, "z" : -6, "border" : "TOCSHOLY.bmp", "area" : "TZCSHOLY.bmp" },
-				"dwellingLvl1":   { "animation" : "TBCSDW_0.def", "x" : 304, "y" : 92,  "z" : -1, "border" : "TOCSPIK1.bmp", "area" : "TZCSPIK1.bmp" },
-				"dwellingLvl2":   { "animation" : "TBCSDW_1.def", "x" : 360, "y" : 130, "z" : 0, "border" : "TOCSCRS1.bmp", "area" : "TZCSCRS1.bmp" },
-				"dwellingLvl3":   { "animation" : "TBCSDW_2.def", "x" : 76,  "y" : 57,  "z" : -1, "border" : "TOCSGR1N.bmp", "area" : "TZCSGR1N.bmp" },
-				"dwellingLvl4":   { "animation" : "TBCSDW_3.def", "x" : 176, "y" : 101, "z" : 0, "border" : "TOCSSWD1.bmp", "area" : "TZCSSWD1.bmp" },
-				"dwellingLvl5":   { "animation" : "TBCSDW_4.def", "x" : 563, "y" : 211, "z" : 1,  "border" : "TOCSMON1.bmp", "area" : "TZCSMON1.bmp" },
-				"dwellingLvl6":   { "animation" : "TBCSDW_5.def", "x" : 174, "y" : 190, "z" : -1, "border" : "TOCSC101.bmp", "area" : "TZCSCAV1.bmp" },
-				"dwellingLvl7":   { "animation" : "TBCSDW_6.def", "x" : 303, "y" : 0,   "z" : -2, "border" : "TOCSANG1.bmp", "area" : "TZCSANG1.bmp" },
-				"dwellingUpLvl1": { "animation" : "TBCSUP_0.def", "x" : 304, "y" : 65,  "z" : -1, "border" : "TOCSPIK2.bmp", "area" : "TZCSPIK2.bmp" },
-				"dwellingUpLvl2": { "animation" : "TBCSUP_1.def", "x" : 360, "y" : 115, "z" : 0, "border" : "TOCSCRS2.bmp", "area" : "TZCSCRS2.bmp" },
-				"dwellingUpLvl3": { "animation" : "TBCSUP_2.def", "x" : 76,  "y" : 35,  "z" : -1, "border" : "TOCSGR2N.bmp", "area" : "TZCSGR2N.bmp" },
-				"dwellingUpLvl4": { "animation" : "TBCSUP_3.def", "x" : 176, "y" : 85,  "z" : 0, "border" : "TOCSSWD2.bmp", "area" : "TZCSSWD2.bmp" },
-				"dwellingUpLvl5": { "animation" : "TBCSUP_4.def", "x" : 563, "y" : 173, "z" : 1,  "border" : "TOCSMON2.bmp", "area" : "TZCSMON2.bmp" },
-				"dwellingUpLvl6": { "animation" : "TBCSUP_5.def", "x" : 160, "y" : 190, "z" : -1, "border" : "TOCSCAV2.bmp", "area" : "TZCSCAV2.bmp" },
-				"dwellingUpLvl7": { "animation" : "TBCSUP_6.def", "x" : 303, "y" : 0,   "z" : -2, "border" : "TOCSANG2.bmp", "area" : "TZCSANG2.bmp" }
+				"special2":       { "animation" : "TBCSEXT0.def", "x" : 384, "y" : 193, "z" : -2, "campaignBonus" : "BoCsCv2S.pcx", "border" : "TOCSCAVM.bmp", "area" : "TZCSCAVM.bmp" },
+				"special3":       { "animation" : "TBCSEXT1.def", "x" : 0,   "y" : 198, "z" :  2, "campaignBonus" : "BoCsTav2.pcx", "border" : "TOCSTAV2.bmp", "area" : "TZCSTAV2.bmp" },
+				"grail":          { "animation" : "TBCSHOLY.def", "x" : 456, "y" : 109, "z" : -6, "campaignBonus" : "BoCsHoly.pcx", "border" : "TOCSHOLY.bmp", "area" : "TZCSHOLY.bmp" },
+				"dwellingLvl1":   { "animation" : "TBCSDW_0.def", "x" : 304, "y" : 92,  "z" : -1, "campaignBonus" : "BoCsPik1.pcx", "border" : "TOCSPIK1.bmp", "area" : "TZCSPIK1.bmp" },
+				"dwellingLvl2":   { "animation" : "TBCSDW_1.def", "x" : 360, "y" : 130, "z" : 0,  "campaignBonus" : "BoCsCrs1.pcx", "border" : "TOCSCRS1.bmp", "area" : "TZCSCRS1.bmp" },
+				"dwellingLvl3":   { "animation" : "TBCSDW_2.def", "x" : 76,  "y" : 57,  "z" : -1, "campaignBonus" : "BoCsGr1.pcx",  "border" : "TOCSGR1N.bmp", "area" : "TZCSGR1N.bmp" },
+				"dwellingLvl4":   { "animation" : "TBCSDW_3.def", "x" : 176, "y" : 101, "z" : 0,  "campaignBonus" : "BoCsSwd1.pcx", "border" : "TOCSSWD1.bmp", "area" : "TZCSSWD1.bmp" },
+				"dwellingLvl5":   { "animation" : "TBCSDW_4.def", "x" : 563, "y" : 211, "z" : 1,  "campaignBonus" : "BoCsMon1.pcx", "border" : "TOCSMON1.bmp", "area" : "TZCSMON1.bmp" },
+				"dwellingLvl6":   { "animation" : "TBCSDW_5.def", "x" : 174, "y" : 190, "z" : -1, "campaignBonus" : "BoCsCv1.pcx",  "border" : "TOCSC101.bmp", "area" : "TZCSCAV1.bmp" },
+				"dwellingLvl7":   { "animation" : "TBCSDW_6.def", "x" : 303, "y" : 0,   "z" : -2, "campaignBonus" : "BoCsAng1.pcx", "border" : "TOCSANG1.bmp", "area" : "TZCSANG1.bmp" },
+				"dwellingUpLvl1": { "animation" : "TBCSUP_0.def", "x" : 304, "y" : 65,  "z" : -1, "campaignBonus" : "BoCsPik2.pcx", "border" : "TOCSPIK2.bmp", "area" : "TZCSPIK2.bmp" },
+				"dwellingUpLvl2": { "animation" : "TBCSUP_1.def", "x" : 360, "y" : 115, "z" : 0,  "campaignBonus" : "BoCsCrs2.pcx", "border" : "TOCSCRS2.bmp", "area" : "TZCSCRS2.bmp" },
+				"dwellingUpLvl3": { "animation" : "TBCSUP_2.def", "x" : 76,  "y" : 35,  "z" : -1, "campaignBonus" : "BoCsGr2.pcx",  "border" : "TOCSGR2N.bmp", "area" : "TZCSGR2N.bmp" },
+				"dwellingUpLvl4": { "animation" : "TBCSUP_3.def", "x" : 176, "y" : 85,  "z" : 0,  "campaignBonus" : "BoCsSwd2.pcx", "border" : "TOCSSWD2.bmp", "area" : "TZCSSWD2.bmp" },
+				"dwellingUpLvl5": { "animation" : "TBCSUP_4.def", "x" : 563, "y" : 173, "z" : 1,  "campaignBonus" : "BoCsMon2.pcx", "border" : "TOCSMON2.bmp", "area" : "TZCSMON2.bmp" },
+				"dwellingUpLvl6": { "animation" : "TBCSUP_5.def", "x" : 160, "y" : 190, "z" : -1, "campaignBonus" : "BoCsCv2.pcx",  "border" : "TOCSCAV2.bmp", "area" : "TZCSCAV2.bmp" },
+				"dwellingUpLvl7": { "animation" : "TBCSUP_6.def", "x" : 303, "y" : 0,   "z" : -2, "campaignBonus" : "BoCsAng2.pcx", "border" : "TOCSANG2.bmp", "area" : "TZCSANG2.bmp" }
 			},
 
 			"musicTheme" : [ "music/CstleTown" ],

+ 36 - 36
config/factions/conflux.json

@@ -81,46 +81,46 @@
 			{
 				"extraAnimation2":{ "animation" : "TBELEXT5.def", "x" : 682, "y" : 183, "z" : 2 },
 				"extraAnimation": { "animation" : "TBELEXT1.def", "x" : 23,  "y" : 218, "z" : 3 },
-				"mageGuild1":     { "animation" : "TBELMAGE.def", "x" : 206, "y" : 58,  "z" : 7, "border" : "TOELMAGE.bmp", "area" : "TZELMAGE.bmp" },
-				"mageGuild2":     { "animation" : "TBELMAG2.def", "x" : 206, "y" : 58,  "z" : 7, "border" : "TOELMAG2.bmp", "area" : "TZELMAG2.bmp" },
-				"mageGuild3":     { "animation" : "TBELMAG3.def", "x" : 206, "y" : 58,  "z" : 7, "border" : "TOELMAG3.bmp", "area" : "TZELMAG3.bmp" },
-				"mageGuild4":     { "animation" : "TBELMAG4.def", "x" : 206, "y" : 58,  "z" : 7, "border" : "TOELMAG4.bmp", "area" : "TZELMAG4.bmp" },
-				"mageGuild5":     { "animation" : "TBELMAG5.def", "x" : 206, "y" : 58,  "z" : 7, "border" : "TOELMAG5.bmp", "area" : "TZELMAG5.bmp" },
-				"tavern":         { "animation" : "TBELTVRN.def", "x" : 553, "y" : 203, "z" : 7, "border" : "TOELTVRN.bmp", "area" : "TZELTVRN.bmp" },
-				"shipyard":       { "animation" : "TBELDOCK.def", "x" : 239, "y" : 215, "z" : 5, "border" : "TOELDOCK.bmp", "area" : "TZELDOCK.bmp" },
-				"fort":           { "animation" : "TBELCSTL.def", "x" : 349, "y" : 101, "z" : 1, "border" : "TOELCSTL.bmp", "area" : "TZELCSTL.bmp" },
-				"citadel":        { "animation" : "TBELCAS2.def", "x" : 349, "y" : 101, "z" : 1, "border" : "TOELCAS2.bmp", "area" : "TZELCAS2.bmp" },
-				"castle":         { "animation" : "TBELCAS3.def", "x" : 349, "y" : 101, "z" : 1, "border" : "TOELCAS3.bmp", "area" : "TZELCAS3.bmp" },
-				"villageHall":    { "animation" : "TBELHALL.def", "x" : -1,  "y" : 164, "z" : 8, "border" : "TOELHALL.bmp", "area" : "TZELHALL.bmp" },
-				"townHall":       { "animation" : "TBELHAL2.def", "x" : 0,   "y" : 165, "z" : 8, "border" : "TOELHAL2.bmp", "area" : "TZELHAL2.bmp" },
-				"cityHall":       { "animation" : "TBELHAL3.def", "x" : 0,   "y" : 165, "z" : 8, "border" : "TOELHAL3.bmp", "area" : "TZELHAL3.bmp" },
-				"capitol":        { "animation" : "TBELHAL4.def", "x" : 0,   "y" : 164, "z" : 8, "border" : "TOELHAL4.bmp", "area" : "TZELHAL4.bmp" },
-				"marketplace":    { "animation" : "TBELMARK.def", "x" : 347, "y" : 216, "z" : 8, "border" : "TOELMARK.bmp", "area" : "TZELMARK.bmp" },
-				"resourceSilo":   { "animation" : "TBELSILO.def", "x" : 372, "y" : 171, "z" : 7, "border" : "TOELSILO.bmp", "area" : "TZELSILO.bmp" },
-				"blacksmith":     { "animation" : "TBELBLAK.def", "x" : 449, "y" : 151, "z" : 5, "border" : "TOELBLAK.bmp", "area" : "TZELBLAK.bmp" },
-				"special1":       { "animation" : "TBELSPEC.def", "x" : 284, "y" : 246, "z" : 9, "border" : "TOELSPEC.bmp", "area" : "TZELSPEC.bmp" },
-				"horde1":         { "animation" : "TBELHRD1.def", "x" : 689, "y" : 250, "z" : 9, "border" : "TOELHRD1.bmp", "area" : "TZELHRD1.bmp", "hidden" : true },
-				"horde1Upgr":     { "animation" : "TBELHRD2.def", "x" : 689, "y" : 250, "z" : 9, "border" : "TOELHRD2.bmp", "area" : "TZELHRD2.bmp", "hidden" : true, "builds" : "horde1" },
+				"mageGuild1":     { "animation" : "TBELMAGE.def", "x" : 206, "y" : 58,  "z" : 7, "campaignBonus" : "BoEgld1.pcx", "border" : "TOELMAGE.bmp", "area" : "TZELMAGE.bmp" },
+				"mageGuild2":     { "animation" : "TBELMAG2.def", "x" : 206, "y" : 58,  "z" : 7, "campaignBonus" : "BoEgld2.pcx", "border" : "TOELMAG2.bmp", "area" : "TZELMAG2.bmp" },
+				"mageGuild3":     { "animation" : "TBELMAG3.def", "x" : 206, "y" : 58,  "z" : 7, "campaignBonus" : "BoEgld3.pcx", "border" : "TOELMAG3.bmp", "area" : "TZELMAG3.bmp" },
+				"mageGuild4":     { "animation" : "TBELMAG4.def", "x" : 206, "y" : 58,  "z" : 7, "campaignBonus" : "BoEgld4.pcx", "border" : "TOELMAG4.bmp", "area" : "TZELMAG4.bmp" },
+				"mageGuild5":     { "animation" : "TBELMAG5.def", "x" : 206, "y" : 58,  "z" : 7, "campaignBonus" : "BoEgld5.pcx", "border" : "TOELMAG5.bmp", "area" : "TZELMAG5.bmp" },
+				"tavern":         { "animation" : "TBELTVRN.def", "x" : 553, "y" : 203, "z" : 7, "campaignBonus" : "BoEtav.pcx",  "border" : "TOELTVRN.bmp", "area" : "TZELTVRN.bmp" },
+				"shipyard":       { "animation" : "TBELDOCK.def", "x" : 239, "y" : 215, "z" : 5, "campaignBonus" : "BoEship.pcx", "border" : "TOELDOCK.bmp", "area" : "TZELDOCK.bmp" },
+				"fort":           { "animation" : "TBELCSTL.def", "x" : 349, "y" : 101, "z" : 1, "campaignBonus" : "BoEcast1.pcx", "border" : "TOELCSTL.bmp", "area" : "TZELCSTL.bmp" },
+				"citadel":        { "animation" : "TBELCAS2.def", "x" : 349, "y" : 101, "z" : 1, "campaignBonus" : "BoEcast2.pcx", "border" : "TOELCAS2.bmp", "area" : "TZELCAS2.bmp" },
+				"castle":         { "animation" : "TBELCAS3.def", "x" : 349, "y" : 101, "z" : 1, "campaignBonus" : "BoEcast3.pcx", "border" : "TOELCAS3.bmp", "area" : "TZELCAS3.bmp" },
+				"villageHall":    { "animation" : "TBELHALL.def", "x" : -1,  "y" : 164, "z" : 8, "campaignBonus" : "BoEhall1.pcx", "border" : "TOELHALL.bmp", "area" : "TZELHALL.bmp" },
+				"townHall":       { "animation" : "TBELHAL2.def", "x" : 0,   "y" : 165, "z" : 8, "campaignBonus" : "BoEhall2.pcx", "border" : "TOELHAL2.bmp", "area" : "TZELHAL2.bmp" },
+				"cityHall":       { "animation" : "TBELHAL3.def", "x" : 0,   "y" : 165, "z" : 8, "campaignBonus" : "BoEhall3.pcx", "border" : "TOELHAL3.bmp", "area" : "TZELHAL3.bmp" },
+				"capitol":        { "animation" : "TBELHAL4.def", "x" : 0,   "y" : 164, "z" : 8, "campaignBonus" : "BoEhall4.pcx", "border" : "TOELHAL4.bmp", "area" : "TZELHAL4.bmp" },
+				"marketplace":    { "animation" : "TBELMARK.def", "x" : 347, "y" : 216, "z" : 8, "campaignBonus" : "BoEMark.pcx",  "border" : "TOELMARK.bmp", "area" : "TZELMARK.bmp" },
+				"resourceSilo":   { "animation" : "TBELSILO.def", "x" : 372, "y" : 171, "z" : 7, "campaignBonus" : "BoEMarkS.pcx", "border" : "TOELSILO.bmp", "area" : "TZELSILO.bmp" },
+				"blacksmith":     { "animation" : "TBELBLAK.def", "x" : 449, "y" : 151, "z" : 5, "campaignBonus" : "BoEblack.pcx", "border" : "TOELBLAK.bmp", "area" : "TZELBLAK.bmp" },
+				"special1":       { "animation" : "TBELSPEC.def", "x" : 284, "y" : 246, "z" : 9, "campaignBonus" : "BoEMarkA.pcx", "border" : "TOELSPEC.bmp", "area" : "TZELSPEC.bmp" },
+				"horde1":         { "animation" : "TBELHRD1.def", "x" : 689, "y" : 250, "z" : 9, "campaignBonus" : "BoEHrd1.pcx",  "border" : "TOELHRD1.bmp", "area" : "TZELHRD1.bmp", "hidden" : true },
+				"horde1Upgr":     { "animation" : "TBELHRD2.def", "x" : 689, "y" : 250, "z" : 9, "campaignBonus" : "BoEHrd2.pcx",  "border" : "TOELHRD2.bmp", "area" : "TZELHRD2.bmp", "hidden" : true, "builds" : "horde1" },
 				"ship":           { "animation" : "TBELBOAT.def", "x" : 239, "y" : 215, "z" : 5, "border" : "TOELBOAT.bmp", "area" : "TZELBOAT.bmp", "hidden" : true },
-				"special2":       { "animation" : "TBELEXT6.def", "x" : 104, "y" : 170, "z" : 6, "border" : "TOELEXT6.bmp", "area" : "TZELEXT6.bmp" },
-				"grail":          { "animation" : "TBELHOLY.def", "x" : 307, "y" : 2,   "z" : 0, "border" : "TOELHOLY.bmp", "area" : "TZELHOLY.bmp" },
+				"special2":       { "animation" : "TBELEXT6.def", "x" : 104, "y" : 170, "z" : 6, "campaignBonus" : "BoEuniv.pcx",  "border" : "TOELEXT6.bmp", "area" : "TZELEXT6.bmp" },
+				"grail":          { "animation" : "TBELHOLY.def", "x" : 307, "y" : 2,   "z" : 0, "campaignBonus" : "BoEgrail.pcx", "border" : "TOELHOLY.bmp", "area" : "TZELHOLY.bmp" },
 				"extraTownHall":  { "animation" : "TBELEXT2.def", "x" : 232, "y" : 205, "z" : 4 },
 				"extraCityHall":  { "animation" : "TBELEXT3.def", "x" : 516, "y" : 223, "z" : 6 },
 				"extraCapitol":   { "animation" : "TBELEXT4.def", "x" : 0,   "y" : 252, "z" : 9 },
-				"dwellingLvl1":   { "animation" : "TBELDW_0.def", "x" : 689, "y" : 250, "z" : 9, "border" : "TOELDW_0.bmp", "area" : "TZELDW_0.bmp" },
-				"dwellingLvl2":   { "animation" : "TBELDW_1.def", "x" : 630, "y" : 50,  "z" : 0, "border" : "TOELDW_1.bmp", "area" : "TZELDW_1.bmp" },
-				"dwellingLvl3":   { "animation" : "TBELDW_2.def", "x" : 709, "y" : 210, "z" : 5, "border" : "TOELDW_2.bmp", "area" : "TZELDW_2.bmp" },
-				"dwellingLvl4":   { "animation" : "TBELDW_3.def", "x" : 108, "y" : 131, "z" : 1, "border" : "TOELDW_3.bmp", "area" : "TZELDW_3.bmp" },
-				"dwellingLvl5":   { "animation" : "TBELDW_4.def", "x" : 264, "y" : 168, "z" : 0, "border" : "TOELDW_4.bmp", "area" : "TZELDW_4.bmp" },
-				"dwellingLvl6":   { "animation" : "TBELDW_5.def", "x" : 394, "y" : 283, "z" : 9, "border" : "TOELDW_5.bmp", "area" : "TZELDW_5.bmp" },
-				"dwellingLvl7":   { "animation" : "TBELDW_6.def", "x" : 34,  "y" : 16,  "z" : 0, "border" : "TOELDW_6.bmp", "area" : "TZELDW_6.bmp" },
-				"dwellingUpLvl1": { "animation" : "TBELUP_0.def", "x" : 689, "y" : 250, "z" : 9, "border" : "TOELUP_0.bmp", "area" : "TZELUP_0.bmp" },
-				"dwellingUpLvl2": { "animation" : "TBELUP_1.def", "x" : 630, "y" : 50,  "z" : 0, "border" : "TOELUP_1.bmp", "area" : "TZELUP_1.bmp" },
-				"dwellingUpLvl3": { "animation" : "TBELUP_2.def", "x" : 709, "y" : 210, "z" : 5, "border" : "TOELUP_2.bmp", "area" : "TZELUP_2.bmp" },
-				"dwellingUpLvl4": { "animation" : "TBELUP_3.def", "x" : 108, "y" : 131, "z" : 1, "border" : "TOELUP_3.bmp", "area" : "TZELUP_3.bmp" },
-				"dwellingUpLvl5": { "animation" : "TBELUP_4.def", "x" : 264, "y" : 168, "z" : 0, "border" : "TOELUP_4.bmp", "area" : "TZELUP_4.bmp" },
-				"dwellingUpLvl6": { "animation" : "TBELUP_5.def", "x" : 394, "y" : 283, "z" : 9, "border" : "TOELUP_5.bmp", "area" : "TZELUP_5.bmp" },
-				"dwellingUpLvl7": { "animation" : "TBELUP_6.def", "x" : 34,  "y" : 0,   "z" : 0, "border" : "TOELUP_6.bmp", "area" : "TZELUP_6.bmp" }
+				"dwellingLvl1":   { "animation" : "TBELDW_0.def", "x" : 689, "y" : 250, "z" : 9, "campaignBonus" : "BoEdn_0.pcx", "border" : "TOELDW_0.bmp", "area" : "TZELDW_0.bmp" },
+				"dwellingLvl2":   { "animation" : "TBELDW_1.def", "x" : 630, "y" : 50,  "z" : 0, "campaignBonus" : "BoEdn_1.pcx", "border" : "TOELDW_1.bmp", "area" : "TZELDW_1.bmp" },
+				"dwellingLvl3":   { "animation" : "TBELDW_2.def", "x" : 709, "y" : 210, "z" : 5, "campaignBonus" : "BoEdn_2.pcx", "border" : "TOELDW_2.bmp", "area" : "TZELDW_2.bmp" },
+				"dwellingLvl4":   { "animation" : "TBELDW_3.def", "x" : 108, "y" : 131, "z" : 1, "campaignBonus" : "BoEdn_3.pcx", "border" : "TOELDW_3.bmp", "area" : "TZELDW_3.bmp" },
+				"dwellingLvl5":   { "animation" : "TBELDW_4.def", "x" : 264, "y" : 168, "z" : 0, "campaignBonus" : "BoEdn_4.pcx", "border" : "TOELDW_4.bmp", "area" : "TZELDW_4.bmp" },
+				"dwellingLvl6":   { "animation" : "TBELDW_5.def", "x" : 394, "y" : 283, "z" : 9, "campaignBonus" : "BoEdn_5.pcx", "border" : "TOELDW_5.bmp", "area" : "TZELDW_5.bmp" },
+				"dwellingLvl7":   { "animation" : "TBELDW_6.def", "x" : 34,  "y" : 16,  "z" : 0, "campaignBonus" : "BoEdn_6.pcx", "border" : "TOELDW_6.bmp", "area" : "TZELDW_6.bmp" },
+				"dwellingUpLvl1": { "animation" : "TBELUP_0.def", "x" : 689, "y" : 250, "z" : 9, "campaignBonus" : "BoEup_0.pcx", "border" : "TOELUP_0.bmp", "area" : "TZELUP_0.bmp" },
+				"dwellingUpLvl2": { "animation" : "TBELUP_1.def", "x" : 630, "y" : 50,  "z" : 0, "campaignBonus" : "BoEup_1.pcx", "border" : "TOELUP_1.bmp", "area" : "TZELUP_1.bmp" },
+				"dwellingUpLvl3": { "animation" : "TBELUP_2.def", "x" : 709, "y" : 210, "z" : 5, "campaignBonus" : "BoEup_2.pcx", "border" : "TOELUP_2.bmp", "area" : "TZELUP_2.bmp" },
+				"dwellingUpLvl4": { "animation" : "TBELUP_3.def", "x" : 108, "y" : 131, "z" : 1, "campaignBonus" : "BoEup_3.pcx", "border" : "TOELUP_3.bmp", "area" : "TZELUP_3.bmp" },
+				"dwellingUpLvl5": { "animation" : "TBELUP_4.def", "x" : 264, "y" : 168, "z" : 0, "campaignBonus" : "BoEup_4.pcx", "border" : "TOELUP_4.bmp", "area" : "TZELUP_4.bmp" },
+				"dwellingUpLvl6": { "animation" : "TBELUP_5.def", "x" : 394, "y" : 283, "z" : 9, "campaignBonus" : "BoEup_5.pcx", "border" : "TOELUP_5.bmp", "area" : "TZELUP_5.bmp" },
+				"dwellingUpLvl7": { "animation" : "TBELUP_6.def", "x" : 34,  "y" : 0,   "z" : 0, "campaignBonus" : "BoEup_6.pcx", "border" : "TOELUP_6.bmp", "area" : "TZELUP_6.bmp" }
 			},
 
 			"musicTheme" : [ "music/ElemTown" ],

+ 37 - 37
config/factions/dungeon.json

@@ -80,43 +80,43 @@
 			},
 			"structures" :
 			{
-				"mageGuild1":     { "animation" : "TBDNMAGE.def", "x" : 164, "y" : 119, "z" : -1, "border" : "TODMAG1.bmp",  "area" : "TZDMAG1.bmp" },
-				"mageGuild2":     { "animation" : "TBDNMAG2.def", "x" : 164, "y" : 97,  "z" : -1, "border" : "TODMAG2.bmp",  "area" : "TZDMAG2.bmp" },
-				"mageGuild3":     { "animation" : "TBDNMAG3.def", "x" : 164, "y" : 77,  "z" : -1, "border" : "TODMAG3.bmp",  "area" : "TZDMAG3.bmp" },
-				"mageGuild4":     { "animation" : "TBDNMAG4.def", "x" : 164, "y" : 61,  "z" : -1, "border" : "TODMAG4.bmp",  "area" : "TZDMAG4.bmp" },
-				"mageGuild5":     { "animation" : "TBDNMAG5.def", "x" : 164, "y" : 15,  "z" : -1, "border" : "TODMAG5.bmp",  "area" : "TZDMAG5.bmp" },
-				"tavern":         { "animation" : "TBDNTVRN.def", "x" : 211, "y" : 297, "border" : "TODTAV.bmp",   "area" : "TZDTAV.bmp" },
-				"fort":           { "animation" : "TBDNCSTL.def", "x" : 363, "y" : 87,  "z" : 2,  "border" : "TODCAS1.bmp",  "area" : "TZDCAS1.bmp" },
-				"citadel":        { "animation" : "TBDNCAS2.def", "x" : 363, "y" : 87,  "z" : 2,  "border" : "TODCAS2.bmp",  "area" : "TZDCAS2.bmp" },
-				"castle":         { "animation" : "TBDNCAS3.def", "x" : 363, "y" : 87,  "z" : 2,  "border" : "TODCAS3.bmp",  "area" : "TZDCAS3.bmp" },
-				"villageHall":    { "animation" : "TBDNHALL.def", "x" : 0,   "y" : 234, "border" : "TODHALL1.bmp", "area" : "TZDHALL1.bmp" },
-				"townHall":       { "animation" : "TBDNHAL2.def", "x" : 0,   "y" : 223, "border" : "TODHALL2.bmp", "area" : "TZDHALL2.bmp" },
-				"cityHall":       { "animation" : "TBDNHAL3.def", "x" : 0,   "y" : 223, "border" : "TODHALL3.bmp", "area" : "TZDHALL3.bmp" },
-				"capitol":        { "animation" : "TBDNHAL4.def", "x" : 0,   "y" : 203, "border" : "TODHALL4.bmp", "area" : "TZDHALL4.bmp" },
-				"marketplace":    { "animation" : "TBDNMARK.def", "x" : 590, "y" : 318, "z" : -2, "border" : "TODMARK.bmp",  "area" : "TZDMARK.bmp" },
-				"resourceSilo":   { "animation" : "TBDNSILO.def", "x" : 624, "y" : 335, "z" : 1,  "border" : "TODSILO.bmp",  "area" : "TZDSILO.bmp" },
-				"blacksmith":     { "animation" : "TBDNBLAK.def", "x" : 544, "y" : 248, "z" : -3, "border" : "TODSMITH.bmp", "area" : "TZDSMITH.bmp" },
-				"special1":       { "animation" : "TBDNSPEC.def", "x" : 746, "y" : 294, "z" : 1,  "border" : "TODART.bmp",   "area" : "TZDART.bmp" },
-				"horde1":         { "animation" : "TBDNHRD1.def", "x" : 0,   "y" : 326, "z" : 2,  "border" : "TODTR1HA.bmp", "area" : "TZDTR1HA.bmp", "hidden" : true },
-				"horde1Upgr":     { "animation" : "TBDNHRD2.def", "x" : 0,   "y" : 300, "z" : 2,  "border" : "TODTR2HA.bmp", "area" : "TZDTR2HA.bmp", "hidden" : true, "builds" : "horde1" },
-				"special2":       { "animation" : "TBDNEXT0.def", "x" : 131, "y" : 26 , "border" : "TODVOR1A.bmp", "area" : "TZDVOR1A.bmp" },
-				"special3":       { "animation" : "TBDNEXT1.def", "x" : 687, "y" : 177, "border" : "TODPORTA.bmp", "area" : "TZDPORTA.bmp" },
-				"special4":       { "animation" : "TBDNEXT2.def", "x" : 313, "y" : 298, "border" : "TODACAD.bmp",  "area" : "TZDACAD.bmp" },
-				"grail":          { "animation" : "TBDNHOLY.def", "x" : 562, "y" : 24,  "z" : 1,  "border" : "TODHOLY.bmp",  "area" : "TZDHOLY.bmp" },
-				"dwellingLvl1":   { "animation" : "TBDNDW_0.def", "x" : 0,   "y" : 326, "z" : 2,  "border" : "TODTRG1A.bmp", "area" : "TZDTRG1A.bmp" },
-				"dwellingLvl2":   { "animation" : "TBDNDW_1.def", "x" : 0,   "y" : 26,  "border" : "TODHAR1.bmp",  "area" : "TZDHAR1.bmp" },
-				"dwellingLvl3":   { "animation" : "TBDNDW_2.def", "x" : 118, "y" : 308, "z" : 1,  "border" : "TODBEH1A.bmp", "area" : "TZDBEH1A.bmp" },
-				"dwellingLvl4":   { "animation" : "TBDNDW_3.def", "x" : 300, "y" : 29,  "z" : -1, "border" : "TODMED1.bmp",  "area" : "TZDMED1.bmp" },
-				"dwellingLvl5":   { "animation" : "TBDNDW_4.def", "x" : 551, "y" : 186, "z" : 1,  "border" : "TODMIN1.bmp",  "area" : "TZDMIN1.bmp" },
-				"dwellingLvl6":   { "animation" : "TBDNDW_5.def", "x" : 270, "y" : 253, "z" : -1, "border" : "TODMAN1.bmp",  "area" : "TZDMAN1.bmp" },
-				"dwellingLvl7":   { "animation" : "TBDNDW_6.def", "x" : 550, "y" : 0,   "z" : -1, "border" : "TODDRA1A.bmp", "area" : "TZDDRA1A.bmp" },
-				"dwellingUpLvl1": { "animation" : "TBDNUP_0.def", "x" : 0,   "y" : 300, "z" : 2,  "border" : "TODTRG2A.bmp", "area" : "TZDTRG2A.bmp" },
-				"dwellingUpLvl2": { "animation" : "TBDNUP_1.def", "x" : 0,   "y" : 26,  "border" : "TODHAR2.bmp",  "area" : "TZDHAR2.bmp" },
-				"dwellingUpLvl3": { "animation" : "TBDNUP_2.def", "x" : 118, "y" : 256, "z" : 1,  "border" : "TODBEH2A.bmp", "area" : "TZDBEH2A.bmp" },
-				"dwellingUpLvl4": { "animation" : "TBDNUP_3.def", "x" : 300, "y" : 29,  "z" : -1, "border" : "TODMED2.bmp",  "area" : "TZDMED2.bmp" },
-				"dwellingUpLvl5": { "animation" : "TBDNUP_4.def", "x" : 519, "y" : 172, "z" : 1,  "border" : "TODMIN2.bmp",  "area" : "TZDMIN2.bmp" },
-				"dwellingUpLvl6": { "animation" : "TBDNUP_5.def", "x" : 270, "y" : 253, "z" : -1, "border" : "TODMAN2.bmp",  "area" : "TZDMAN2.bmp" },
-				"dwellingUpLvl7": { "animation" : "TBDNUP_6.def", "x" : 550, "y" : 0,   "z" : -1, "border" : "TODDRA2A.bmp", "area" : "TZDDRA2A.bmp" }
+				"mageGuild1":     { "animation" : "TBDNMAGE.def", "x" : 164, "y" : 119, "z" : -1, "campaignBonus" : "BoDmage1.pcx", "border" : "TODMAG1.bmp",  "area" : "TZDMAG1.bmp" },
+				"mageGuild2":     { "animation" : "TBDNMAG2.def", "x" : 164, "y" : 97,  "z" : -1, "campaignBonus" : "BoDmage2.pcx", "border" : "TODMAG2.bmp",  "area" : "TZDMAG2.bmp" },
+				"mageGuild3":     { "animation" : "TBDNMAG3.def", "x" : 164, "y" : 77,  "z" : -1, "campaignBonus" : "BoDmage3.pcx", "border" : "TODMAG3.bmp",  "area" : "TZDMAG3.bmp" },
+				"mageGuild4":     { "animation" : "TBDNMAG4.def", "x" : 164, "y" : 61,  "z" : -1, "campaignBonus" : "BoDmage4.pcx", "border" : "TODMAG4.bmp",  "area" : "TZDMAG4.bmp" },
+				"mageGuild5":     { "animation" : "TBDNMAG5.def", "x" : 164, "y" : 15,  "z" : -1, "campaignBonus" : "BoDmage5.pcx", "border" : "TODMAG5.bmp",  "area" : "TZDMAG5.bmp" },
+				"tavern":         { "animation" : "TBDNTVRN.def", "x" : 211, "y" : 297,           "campaignBonus" : "BoDtav.pcx",   "border" : "TODTAV.bmp",   "area" : "TZDTAV.bmp" },
+				"fort":           { "animation" : "TBDNCSTL.def", "x" : 363, "y" : 87,  "z" : 2,  "campaignBonus" : "BoDcas1.pcx",  "border" : "TODCAS1.bmp",  "area" : "TZDCAS1.bmp" },
+				"citadel":        { "animation" : "TBDNCAS2.def", "x" : 363, "y" : 87,  "z" : 2,  "campaignBonus" : "BoDcas2.pcx",  "border" : "TODCAS2.bmp",  "area" : "TZDCAS2.bmp" },
+				"castle":         { "animation" : "TBDNCAS3.def", "x" : 363, "y" : 87,  "z" : 2,  "campaignBonus" : "BoDcas3.pcx",  "border" : "TODCAS3.bmp",  "area" : "TZDCAS3.bmp" },
+				"villageHall":    { "animation" : "TBDNHALL.def", "x" : 0,   "y" : 234,           "campaignBonus" : "BoDhall1.pcx", "border" : "TODHALL1.bmp", "area" : "TZDHALL1.bmp" },
+				"townHall":       { "animation" : "TBDNHAL2.def", "x" : 0,   "y" : 223,           "campaignBonus" : "BoDhall2.pcx", "border" : "TODHALL2.bmp", "area" : "TZDHALL2.bmp" },
+				"cityHall":       { "animation" : "TBDNHAL3.def", "x" : 0,   "y" : 223,           "campaignBonus" : "BoDhall3.pcx", "border" : "TODHALL3.bmp", "area" : "TZDHALL3.bmp" },
+				"capitol":        { "animation" : "TBDNHAL4.def", "x" : 0,   "y" : 203,           "campaignBonus" : "BoDhall4.pcx", "border" : "TODHALL4.bmp", "area" : "TZDHALL4.bmp" },
+				"marketplace":    { "animation" : "TBDNMARK.def", "x" : 590, "y" : 318, "z" : -2, "campaignBonus" : "BoDmark1.pcx", "border" : "TODMARK.bmp",  "area" : "TZDMARK.bmp" },
+				"resourceSilo":   { "animation" : "TBDNSILO.def", "x" : 624, "y" : 335, "z" : 1,  "campaignBonus" : "BoDmark2.pcx", "border" : "TODSILO.bmp",  "area" : "TZDSILO.bmp" },
+				"blacksmith":     { "animation" : "TBDNBLAK.def", "x" : 544, "y" : 248, "z" : -3, "campaignBonus" : "BoDsmith.pcx", "border" : "TODSMITH.bmp", "area" : "TZDSMITH.bmp" },
+				"special1":       { "animation" : "TBDNSPEC.def", "x" : 746, "y" : 294, "z" : 1,  "campaignBonus" : "BoDmarkA.pcx", "border" : "TODART.bmp",   "area" : "TZDART.bmp" },
+				"horde1":         { "animation" : "TBDNHRD1.def", "x" : 0,   "y" : 326, "z" : 2,  "campaignBonus" : "BoDtrogH.pcx", "border" : "TODTR1HA.bmp", "area" : "TZDTR1HA.bmp", "hidden" : true },
+				"horde1Upgr":     { "animation" : "TBDNHRD2.def", "x" : 0,   "y" : 300, "z" : 2,  "campaignBonus" : "BoDtrogH.pcx", "border" : "TODTR2HA.bmp", "area" : "TZDTR2HA.bmp", "hidden" : true, "builds" : "horde1" },
+				"special2":       { "animation" : "TBDNEXT0.def", "x" : 131, "y" : 26 ,           "campaignBonus" : "BoDvort.pcx",  "border" : "TODVOR1A.bmp", "area" : "TZDVOR1A.bmp" },
+				"special3":       { "animation" : "TBDNEXT1.def", "x" : 687, "y" : 177,           "campaignBonus" : "BoDport.pcx",  "border" : "TODPORTA.bmp", "area" : "TZDPORTA.bmp" },
+				"special4":       { "animation" : "TBDNEXT2.def", "x" : 313, "y" : 298,           "campaignBonus" : "BoDacad.pcx",  "border" : "TODACAD.bmp",  "area" : "TZDACAD.bmp" },
+				"grail":          { "animation" : "TBDNHOLY.def", "x" : 562, "y" : 24,  "z" : 1,  "campaignBonus" : "BoDholy.pcx",  "border" : "TODHOLY.bmp",  "area" : "TZDHOLY.bmp" },
+				"dwellingLvl1":   { "animation" : "TBDNDW_0.def", "x" : 0,   "y" : 326, "z" : 2,  "campaignBonus" : "BoDtrog1.pcx", "border" : "TODTRG1A.bmp", "area" : "TZDTRG1A.bmp" },
+				"dwellingLvl2":   { "animation" : "TBDNDW_1.def", "x" : 0,   "y" : 26,            "campaignBonus" : "BoDharp1.pcx", "border" : "TODHAR1.bmp",  "area" : "TZDHAR1.bmp" },
+				"dwellingLvl3":   { "animation" : "TBDNDW_2.def", "x" : 118, "y" : 308, "z" : 1,  "campaignBonus" : "BoDbeh1.pcx",  "border" : "TODBEH1A.bmp", "area" : "TZDBEH1A.bmp" },
+				"dwellingLvl4":   { "animation" : "TBDNDW_3.def", "x" : 300, "y" : 29,  "z" : -1, "campaignBonus" : "BoDmedu1.pcx", "border" : "TODMED1.bmp",  "area" : "TZDMED1.bmp" },
+				"dwellingLvl5":   { "animation" : "TBDNDW_4.def", "x" : 551, "y" : 186, "z" : 1,  "campaignBonus" : "BoDmino1.pcx", "border" : "TODMIN1.bmp",  "area" : "TZDMIN1.bmp" },
+				"dwellingLvl6":   { "animation" : "TBDNDW_5.def", "x" : 270, "y" : 253, "z" : -1, "campaignBonus" : "BoDmant1.pcx", "border" : "TODMAN1.bmp",  "area" : "TZDMAN1.bmp" },
+				"dwellingLvl7":   { "animation" : "TBDNDW_6.def", "x" : 550, "y" : 0,   "z" : -1, "campaignBonus" : "BoDdrag1.pcx", "border" : "TODDRA1A.bmp", "area" : "TZDDRA1A.bmp" },
+				"dwellingUpLvl1": { "animation" : "TBDNUP_0.def", "x" : 0,   "y" : 300, "z" : 2,  "campaignBonus" : "BoDtrog2.pcx", "border" : "TODTRG2A.bmp", "area" : "TZDTRG2A.bmp" },
+				"dwellingUpLvl2": { "animation" : "TBDNUP_1.def", "x" : 0,   "y" : 26,            "campaignBonus" : "BoDharp2.pcx", "border" : "TODHAR2.bmp",  "area" : "TZDHAR2.bmp" },
+				"dwellingUpLvl3": { "animation" : "TBDNUP_2.def", "x" : 118, "y" : 256, "z" : 1,  "campaignBonus" : "BoDbeh2.pcx",  "border" : "TODBEH2A.bmp", "area" : "TZDBEH2A.bmp" },
+				"dwellingUpLvl4": { "animation" : "TBDNUP_3.def", "x" : 300, "y" : 29,  "z" : -1, "campaignBonus" : "BoDmedu2.pcx", "border" : "TODMED2.bmp",  "area" : "TZDMED2.bmp" },
+				"dwellingUpLvl5": { "animation" : "TBDNUP_4.def", "x" : 519, "y" : 172, "z" : 1,  "campaignBonus" : "BoDmino2.pcx", "border" : "TODMIN2.bmp",  "area" : "TZDMIN2.bmp" },
+				"dwellingUpLvl6": { "animation" : "TBDNUP_5.def", "x" : 270, "y" : 253, "z" : -1, "campaignBonus" : "BoDmant2.pcx", "border" : "TODMAN2.bmp",  "area" : "TZDMAN2.bmp" },
+				"dwellingUpLvl7": { "animation" : "TBDNUP_6.def", "x" : 550, "y" : 0,   "z" : -1, "campaignBonus" : "BoDdrag2.pcx", "border" : "TODDRA2A.bmp", "area" : "TZDDRA2A.bmp" }
 			},
 
 			"musicTheme" : [ "music/Dungeon" ],

+ 35 - 35
config/factions/fortress.json

@@ -80,43 +80,43 @@
 			"structures" :
 			{
 				"extraAnimation": { "animation" : "TBFREXT2.def", "x" : 372, "y" : 227, "z" : -1 },
-				"mageGuild1":     { "animation" : "TBFRMAGE.def", "x" : 0,   "y" : 200, "z" : -1, "border" : "TOFMAG1A.bmp", "area" : "TZFMAG1A.bmp" },
-				"mageGuild2":     { "animation" : "TBFRMAG2.def", "x" : 0,   "y" : 177, "z" : -1, "border" : "TOFMAG2A.bmp", "area" : "TZFMAG2A.bmp" },
-				"mageGuild3":     { "animation" : "TBFRMAG3.def", "x" : 0,   "y" : 135, "z" : -1, "border" : "TOFMAG3A.bmp", "area" : "TZFMAG3A.bmp" },
-				"tavern":         { "animation" : "TBFRTVRN.def", "x" : 634, "y" : 219, "z" : 3,  "border" : "TOFTAVA.bmp",  "area" : "TZFTAVA.bmp" },
-				"shipyard":       { "animation" : "TBFRDOCK.def", "x" : 197, "y" : 294, "z" : 1,  "border" : "TOFDCK2.bmp",  "area" : "TZFDCK2.bmp" },
-				"fort":           { "animation" : "TBFRCSTL.def", "x" : 368, "y" : 118, "z" : -2, "border" : "TOFCAS1.bmp",  "area" : "TZFCAS1.bmp" },
-				"citadel":        { "animation" : "TBFRCAS2.def", "x" : 368, "y" : 98,  "z" : -2, "border" : "TOFCAS2.bmp",  "area" : "TZFCAS2.bmp" },
-				"castle":         { "animation" : "TBFRCAS3.def", "x" : 368, "y" : 55,  "z" : -2, "border" : "TOFCAS3.bmp",  "area" : "TZFCAS3.bmp" },
-				"villageHall":    { "animation" : "TBFRHALL.def", "x" : 166, "y" : 128, "z" : 3,  "border" : "TOFHAL1.bmp",  "area" : "TZFHAL1.bmp" },
-				"townHall":       { "animation" : "TBFRHAL2.def", "x" : 166, "y" : 97,  "z" : 3,  "border" : "TOFHAL2.bmp",  "area" : "TZFHAL2.bmp" },
-				"cityHall":       { "animation" : "TBFRHAL3.def", "x" : 166, "y" : 51,  "z" : 3,  "border" : "TOFHAL3.bmp",  "area" : "TZFHAL3.bmp" },
-				"capitol":        { "animation" : "TBFRHAL4.def", "x" : 166, "y" : 2,   "z" : 3,  "border" : "TOFHAL4.bmp",  "area" : "TZFHAL4.bmp" },
-				"marketplace":    { "animation" : "TBFRMARK.def", "x" : 382, "y" : 219, "z" : 4,  "border" : "TOFMRKAA.bmp", "area" : "TZFMRKAA.bmp" },
-				"resourceSilo":   { "animation" : "TBFRSILO.def", "x" : 448, "y" : 210, "z" : 2,  "border" : "TOFMRK2A.bmp", "area" : "TZFMRK2A.bmp" },
-				"blacksmith":     { "animation" : "TBFRBLAK.def", "x" : 360, "y" : 160, "z" : 1, "border" : "TOFAIDA.bmp",  "area" : "TZFAIDA.bmp" },
-				"special1":       { "animation" : "TBFRSPEC.def", "x" : 703, "y" : 36,  "border" : "TOFCAGE.bmp",  "area" : "TZFCAGE.bmp" },
-				"horde1":         { "animation" : "TBFRHRD1.def", "x" : 641, "y" : 121, "z" : 1,  "border" : "TOFGNL1H.bmp", "area" : "TZFGNL1H.bmp", "hidden" : true },
-				"horde1Upgr":     { "animation" : "TBFRHRD2.def", "x" : 641, "y" : 68,  "z" : 1,  "border" : "TOFGNL2H.bmp", "area" : "TZFGNL2H.bmp", "hidden" : true, "builds" : "horde1" },
+				"mageGuild1":     { "animation" : "TBFRMAGE.def", "x" : 0,   "y" : 200, "z" : -1, "campaignBonus" : "BoFmage1.pcx", "border" : "TOFMAG1A.bmp", "area" : "TZFMAG1A.bmp" },
+				"mageGuild2":     { "animation" : "TBFRMAG2.def", "x" : 0,   "y" : 177, "z" : -1, "campaignBonus" : "BoFmage2.pcx", "border" : "TOFMAG2A.bmp", "area" : "TZFMAG2A.bmp" },
+				"mageGuild3":     { "animation" : "TBFRMAG3.def", "x" : 0,   "y" : 135, "z" : -1, "campaignBonus" : "BoFmage3.pcx", "border" : "TOFMAG3A.bmp", "area" : "TZFMAG3A.bmp" },
+				"tavern":         { "animation" : "TBFRTVRN.def", "x" : 634, "y" : 219, "z" : 3,  "campaignBonus" : "BoFtav.pcx",   "border" : "TOFTAVA.bmp",  "area" : "TZFTAVA.bmp" },
+				"shipyard":       { "animation" : "TBFRDOCK.def", "x" : 197, "y" : 294, "z" : 1,  "campaignBonus" : "BoFship.pcx",  "border" : "TOFDCK2.bmp",  "area" : "TZFDCK2.bmp" },
+				"fort":           { "animation" : "TBFRCSTL.def", "x" : 368, "y" : 118, "z" : -2, "campaignBonus" : "BoFcast1.pcx", "border" : "TOFCAS1.bmp",  "area" : "TZFCAS1.bmp" },
+				"citadel":        { "animation" : "TBFRCAS2.def", "x" : 368, "y" : 98,  "z" : -2, "campaignBonus" : "BoFcast2.pcx", "border" : "TOFCAS2.bmp",  "area" : "TZFCAS2.bmp" },
+				"castle":         { "animation" : "TBFRCAS3.def", "x" : 368, "y" : 55,  "z" : -2, "campaignBonus" : "BoFcast3.pcx", "border" : "TOFCAS3.bmp",  "area" : "TZFCAS3.bmp" },
+				"villageHall":    { "animation" : "TBFRHALL.def", "x" : 166, "y" : 128, "z" : 3,  "campaignBonus" : "BoFhall1.pcx", "border" : "TOFHAL1.bmp",  "area" : "TZFHAL1.bmp" },
+				"townHall":       { "animation" : "TBFRHAL2.def", "x" : 166, "y" : 97,  "z" : 3,  "campaignBonus" : "BoFhall2.pcx", "border" : "TOFHAL2.bmp",  "area" : "TZFHAL2.bmp" },
+				"cityHall":       { "animation" : "TBFRHAL3.def", "x" : 166, "y" : 51,  "z" : 3,  "campaignBonus" : "BoFhall3.pcx", "border" : "TOFHAL3.bmp",  "area" : "TZFHAL3.bmp" },
+				"capitol":        { "animation" : "TBFRHAL4.def", "x" : 166, "y" : 2,   "z" : 3,  "campaignBonus" : "BoFhall4.pcx", "border" : "TOFHAL4.bmp",  "area" : "TZFHAL4.bmp" },
+				"marketplace":    { "animation" : "TBFRMARK.def", "x" : 382, "y" : 219, "z" : 4,  "campaignBonus" : "BoFmark1.pcx", "border" : "TOFMRKAA.bmp", "area" : "TZFMRKAA.bmp" },
+				"resourceSilo":   { "animation" : "TBFRSILO.def", "x" : 448, "y" : 210, "z" : 2,  "campaignBonus" : "BoFmark2.pcx", "border" : "TOFMRK2A.bmp", "area" : "TZFMRK2A.bmp" },
+				"blacksmith":     { "animation" : "TBFRBLAK.def", "x" : 360, "y" : 160, "z" : 1,  "campaignBonus" : "BoFapoth.pcx", "border" : "TOFAIDA.bmp",  "area" : "TZFAIDA.bmp" },
+				"special1":       { "animation" : "TBFRSPEC.def", "x" : 703, "y" : 36,            "campaignBonus" : "BoFcage.pcx",  "border" : "TOFCAGE.bmp",  "area" : "TZFCAGE.bmp" },
+				"horde1":         { "animation" : "TBFRHRD1.def", "x" : 641, "y" : 121, "z" : 1,  "campaignBonus" : "BoFgno1h.pcx", "border" : "TOFGNL1H.bmp", "area" : "TZFGNL1H.bmp", "hidden" : true },
+				"horde1Upgr":     { "animation" : "TBFRHRD2.def", "x" : 641, "y" : 68,  "z" : 1,  "campaignBonus" : "BoFgno2h.pcx", "border" : "TOFGNL2H.bmp", "area" : "TZFGNL2H.bmp", "hidden" : true, "builds" : "horde1" },
 				"ship":           { "animation" : "TBFRBOAT.def", "x" : 197, "y" : 294, "z" : 1,  "border" : "TOFDCK1.bmp",  "area" : "TZFDCK1.bmp", "hidden" : true },
-				"special2":       { "animation" : "TBFREXT0.def", "x" : 341, "y" : 174, "z" : 0, "border" : "TOFCASD.bmp",  "area" : "TZFCASD.bmp" },
-				"special3":       { "animation" : "TBFREXT1.def", "x" : 349, "y" : 79,  "z" : -3, "border" : "TOFCASA.bmp",  "area" : "TZFCASA.bmp" },
-				"grail":          { "animation" : "TBFRHOLY.def", "x" : 468, "y" : 260, "z" : 5,  "border" : "TOFHLYAA.bmp", "area" : "TZFHLYAA.bmp" },
+				"special2":       { "animation" : "TBFREXT0.def", "x" : 341, "y" : 174, "z" : 0,  "campaignBonus" : "BoFcastD.pcx", "border" : "TOFCASD.bmp",  "area" : "TZFCASD.bmp" },
+				"special3":       { "animation" : "TBFREXT1.def", "x" : 349, "y" : 79,  "z" : -3, "campaignBonus" : "BoFcastA.pcx", "border" : "TOFCASA.bmp",  "area" : "TZFCASA.bmp" },
+				"grail":          { "animation" : "TBFRHOLY.def", "x" : 468, "y" : 260, "z" : 5,  "campaignBonus" : "BoFgrail.pcx", "border" : "TOFHLYAA.bmp", "area" : "TZFHLYAA.bmp" },
 				"extraCapitol":   { "animation" : "TBFRWTRW.def", "x" : 320, "y" : 141, "z" : 2 },
-				"dwellingLvl1":   { "animation" : "TBFRDW_0.def", "x" : 641, "y" : 168, "z" : 1,  "border" : "TOFGNL1.bmp",  "area" : "TZFGNL1.bmp" },
-				"dwellingLvl2":   { "animation" : "TBFRDW_1.def", "x" : 141, "y" : 178, "border" : "TOFLIZ1.bmp",  "area" : "TZFLIZ1.bmp" },
-				"dwellingLvl3":   { "animation" : "TBFRDW_3.def", "x" : 192, "y" : 85,  "border" : "TOFFLY1A.bmp", "area" : "TZFFLY1A.bmp" },
-				"dwellingLvl4":   { "animation" : "TBFRDW_4.def", "x" : 0,   "y" : 292, "z" : 1, "border" : "TOFBAS1.bmp",  "area" : "TZFBAS1.bmp" },
-				"dwellingLvl5":   { "animation" : "TBFRDW_2.def", "x" : 15,  "y" : 127, "z" : -2, "border" : "TOFGOR1.bmp",  "area" : "TZFGOR1.bmp" },
-				"dwellingLvl6":   { "animation" : "TBFRDW_5.def", "x" : 0,   "y" : 4,   "border" : "TOFWYV1.bmp",  "area" : "TZFWYV1.bmp" },
-				"dwellingLvl7":   { "animation" : "TBFRDW_6.def", "x" : 612, "y" : 291, "z" : 5,  "border" : "TOFHYD1A.bmp", "area" : "TZFHYD1A.bmp" },
-				"dwellingUpLvl1": { "animation" : "TBFRUP_0.def", "x" : 641, "y" : 107, "z" : 1,  "border" : "TOFGNL2.bmp",  "area" : "TZFGNL2.bmp" },
-				"dwellingUpLvl2": { "animation" : "TBFRUP_1.def", "x" : 125, "y" : 163, "border" : "TOFLIZ2.bmp",  "area" : "TZFLIZ2.bmp" },
-				"dwellingUpLvl3": { "animation" : "TBFRUP_3.def", "x" : 159, "y" : 19,  "border" : "TOFFLY2A.bmp", "area" : "TZFFLY2A.bmp" },
-				"dwellingUpLvl4": { "animation" : "TBFRUP_4.def", "x" : 0,   "y" : 257, "z" : 1, "border" : "TOFBAS2.bmp",  "area" : "TZFBAS2.bmp" },
-				"dwellingUpLvl5": { "animation" : "TBFRUP_2.def", "x" : 15,  "y" : 69,  "z" : -2, "border" : "TOFGOR2.bmp",  "area" : "TZFGOR2.bmp" },
-				"dwellingUpLvl6": { "animation" : "TBFRUP_5.def", "x" : 0,   "y" : 4,   "border" : "TOFWYV2.bmp",  "area" : "TZFWYV2.bmp" },
-				"dwellingUpLvl7": { "animation" : "TBFRUP_6.def", "x" : 587, "y" : 263, "z" : 5,  "border" : "TOFHYD2A.bmp", "area" : "TZFHYD2A.bmp" }
+				"dwellingLvl1":   { "animation" : "TBFRDW_0.def", "x" : 641, "y" : 168, "z" : 1,  "campaignBonus" : "BoFgnol1.pcx", "border" : "TOFGNL1.bmp",  "area" : "TZFGNL1.bmp" },
+				"dwellingLvl2":   { "animation" : "TBFRDW_1.def", "x" : 141, "y" : 178,           "campaignBonus" : "BoFlizr1.pcx", "border" : "TOFLIZ1.bmp",  "area" : "TZFLIZ1.bmp" },
+				"dwellingLvl3":   { "animation" : "TBFRDW_3.def", "x" : 192, "y" : 85,            "campaignBonus" : "BoFfly1.pcx",  "border" : "TOFFLY1A.bmp", "area" : "TZFFLY1A.bmp" },
+				"dwellingLvl4":   { "animation" : "TBFRDW_4.def", "x" : 0,   "y" : 292, "z" : 1,  "campaignBonus" : "BoFbas1.pcx",  "border" : "TOFBAS1.bmp",  "area" : "TZFBAS1.bmp" },
+				"dwellingLvl5":   { "animation" : "TBFRDW_2.def", "x" : 15,  "y" : 127, "z" : -2, "campaignBonus" : "BoFgorg1.pcx", "border" : "TOFGOR1.bmp",  "area" : "TZFGOR1.bmp" },
+				"dwellingLvl6":   { "animation" : "TBFRDW_5.def", "x" : 0,   "y" : 4,             "campaignBonus" : "BoFwyvr1.pcx", "border" : "TOFWYV1.bmp",  "area" : "TZFWYV1.bmp" },
+				"dwellingLvl7":   { "animation" : "TBFRDW_6.def", "x" : 612, "y" : 291, "z" : 5,  "campaignBonus" : "BoFhydr1.pcx", "border" : "TOFHYD1A.bmp", "area" : "TZFHYD1A.bmp" },
+				"dwellingUpLvl1": { "animation" : "TBFRUP_0.def", "x" : 641, "y" : 107, "z" : 1,  "campaignBonus" : "BoFgnol2.pcx", "border" : "TOFGNL2.bmp",  "area" : "TZFGNL2.bmp" },
+				"dwellingUpLvl2": { "animation" : "TBFRUP_1.def", "x" : 125, "y" : 163,           "campaignBonus" : "BoFlizr2.pcx", "border" : "TOFLIZ2.bmp",  "area" : "TZFLIZ2.bmp" },
+				"dwellingUpLvl3": { "animation" : "TBFRUP_3.def", "x" : 159, "y" : 19,            "campaignBonus" : "BoFfly2.pcx",  "border" : "TOFFLY2A.bmp", "area" : "TZFFLY2A.bmp" },
+				"dwellingUpLvl4": { "animation" : "TBFRUP_4.def", "x" : 0,   "y" : 257, "z" : 1,  "campaignBonus" : "BoFbas2.pcx",  "border" : "TOFBAS2.bmp",  "area" : "TZFBAS2.bmp" },
+				"dwellingUpLvl5": { "animation" : "TBFRUP_2.def", "x" : 15,  "y" : 69,  "z" : -2, "campaignBonus" : "BoFgorg2.pcx", "border" : "TOFGOR2.bmp",  "area" : "TZFGOR2.bmp" },
+				"dwellingUpLvl6": { "animation" : "TBFRUP_5.def", "x" : 0,   "y" : 4,             "campaignBonus" : "BoFwyvr2.pcx", "border" : "TOFWYV2.bmp",  "area" : "TZFWYV2.bmp" },
+				"dwellingUpLvl7": { "animation" : "TBFRUP_6.def", "x" : 587, "y" : 263, "z" : 5,  "campaignBonus" : "BoFhydr2.pcx", "border" : "TOFHYD2A.bmp", "area" : "TZFHYD2A.bmp" }
 			},
 
 			"musicTheme" : [ "music/FortressTown" ],

+ 38 - 38
config/factions/inferno.json

@@ -80,44 +80,44 @@
 			},
 			"structures" :
 			{
-				"mageGuild1":     { "animation" : "TBINMAGE.def", "x" : 667, "y" : 127, "border" : "TOIMAG1A.bmp", "area" : "TZIMAG1A.bmp" },
-				"mageGuild2":     { "animation" : "TBINMAG2.def", "x" : 667, "y" : 101, "border" : "TOIMAG2A.bmp", "area" : "TZIMAG2A.bmp" },
-				"mageGuild3":     { "animation" : "TBINMAG3.def", "x" : 667, "y" : 83,  "border" : "TOIMAG3A.bmp", "area" : "TZIMAG3A.bmp" },
-				"mageGuild4":     { "animation" : "TBINMAG4.def", "x" : 667, "y" : 56,  "border" : "TOIMAG4A.bmp", "area" : "TZIMAG4A.bmp" },
-				"mageGuild5":     { "animation" : "TBINMAG5.def", "x" : 667, "y" : 35,  "border" : "TOIMAG5A.bmp", "area" : "TZIMAG5A.bmp" },
-				"tavern":         { "animation" : "TBINTVRN.def", "x" : 105, "y" : 219, "z" : 1,  "border" : "TOITAV.bmp",   "area" : "TZITAV.bmp" },
-				"fort":           { "animation" : "TBINCSTL.def", "x" : 222, "y" : 44,  "z" : -2, "border" : "TOICAS2A.bmp", "area" : "TZICAS2A.bmp" },
-				"citadel":        { "animation" : "TBINCAS2.def", "x" : 222, "y" : 44,  "z" : -2, "border" : "TOICAS1A.bmp", "area" : "TZICAS1A.bmp" },
-				"castle":         { "animation" : "TBINCAS3.def", "x" : 222, "y" : 18,  "z" : -2, "border" : "TOICAS3A.bmp", "area" : "TZICAS3A.bmp" },
-				"villageHall":    { "animation" : "TBINHALL.def", "x" : 0,   "y" : 174, "border" : "TOIHAL1.bmp",  "area" : "TZIHAL1.bmp"  },
-				"townHall":       { "animation" : "TBINHAL2.def", "x" : 0,   "y" : 174, "border" : "TOIHAL2.bmp",  "area" : "TZIHAL2.bmp"  },
-				"cityHall":       { "animation" : "TBINHAL3.def", "x" : 0,   "y" : 174, "border" : "TOIHAL3.bmp",  "area" : "TZIHAL3.bmp"  },
-				"capitol":        { "animation" : "TBINHAL4.def", "x" : 0,   "y" : 131, "border" : "TOIHAL4.bmp",  "area" : "TZIHAL4.bmp"  },
-				"marketplace":    { "animation" : "TBINMARK.def", "x" : 511, "y" : 301, "z" : 4,  "border" : "TOIMAR1.bmp",  "area" : "TZIMAR1.bmp" },
-				"resourceSilo":   { "animation" : "TBINSILO.def", "x" : 497, "y" : 337, "z" : 5,  "border" : "TOIMAR2.bmp",  "area" : "TZIMAR2.bmp" },
-				"blacksmith":     { "animation" : "TBINBLAK.def", "x" : 684, "y" : 253, "z" : 1,  "border" : "TOIBLKA.bmp",  "area" : "TZIBLKA.bmp" },
-				"horde1":         { "animation" : "TBINHRD1.def", "x" : 614, "y" : 256, "border" : "TOIMP1HA.bmp", "area" : "TZIMP1HA.bmp", "hidden" : true },
-				"horde1Upgr":     { "animation" : "TBINHRD2.def", "x" : 614, "y" : 221, "border" : "TOIMP2HA.bmp", "area" : "TZIMP2HA.bmp", "hidden" : true, "builds" : "horde1" },
-				"special2":       { "animation" : "TBINEXT0.def", "x" : 297, "y" : 0,   "z" : -4, "border" : "TOICAB1A.bmp", "area" : "TZICAB1A.bmp" },
-				"special3":       { "animation" : "TBINEXT1.def", "x" : 227, "y" : 174, "z" : -1,  "border" : "TOICASGA.bmp", "area" : "TZICASGA.bmp" },
-				"special4":       { "animation" : "TBINEXT2.def", "x" : 593, "y" : 104, "z" : -1, "border" : "TOIPAIN.bmp",  "area" : "TZIPAIN.bmp"  },
-				"horde2":         { "animation" : "TBINHRD3.def", "x" : 10,  "y" : 301, "border" : "TOIHND1H.bmp", "area" : "TZIHND1H.bmp", "hidden" : true },
-				"horde2Upgr":     { "animation" : "TBINHRD4.def", "x" : 9,   "y" : 273, "border" : "TOIHND2H.bmp", "area" : "TZIHND2H.bmp", "hidden" : true, "builds" : "horde2" },
-				"grail":          { "animation" : "TBINHOLY.def", "x" : 24,  "y" : 10,  "z" : -1, "border" : "TOIHOLY.bmp",  "area" : "TZIHOLY.bmp" },
-				"dwellingLvl1":   { "animation" : "TBINDW_0.def", "x" : 614, "y" : 256, "border" : "TOIMP1A.bmp",  "area" : "TZIMP1A.bmp"  },
-				"dwellingLvl2":   { "animation" : "TBINDW_1.def", "x" : 187, "y" : 248, "z" : 4,  "border" : "TOIGOG1A.bmp", "area" : "TZIGOG1A.bmp" },
-				"dwellingLvl3":   { "animation" : "TBINDW_2.def", "x" : 9,   "y" : 325, "border" : "TOIHND1.bmp",  "area" : "TZIHND1.bmp"  },
-				"dwellingLvl4":   { "animation" : "TBINDW_3.def", "x" : 414, "y" : 204, "z" : 2,  "border" : "TOIDMN1.bmp",  "area" : "TZIDMN1.bmp" },
-				"dwellingLvl5":   { "animation" : "TBINDW_4.def", "x" : 359, "y" : 296, "z" : 3,  "border" : "TOIPIT1.bmp",  "area" : "TZIPIT1.bmp" },
-				"dwellingLvl6":   { "animation" : "TBINDW_5.def", "x" : 220, "y" : 350, "z" : 5,  "border" : "TOIEFR1.bmp",  "area" : "TZIEFR1.bmp" },
-				"dwellingLvl7":   { "animation" : "TBINDW_6.def", "x" : 420, "y" : 153, "z" : -3, "border" : "TOIDVL1.bmp",  "area" : "TZIDVL1.bmp" },
-				"dwellingUpLvl1": { "animation" : "TBINUP_0.def", "x" : 614, "y" : 221, "border" : "TOIMP2A.bmp",  "area" : "TZIMP2A.bmp"  },
-				"dwellingUpLvl2": { "animation" : "TBINUP_1.def", "x" : 187, "y" : 212, "z" : 4,  "border" : "TOIGOG2A.bmp", "area" : "TZIGOG2A.bmp" },
-				"dwellingUpLvl3": { "animation" : "TBINUP_2.def", "x" : 9,   "y" : 273, "border" : "TOIHND2.bmp",  "area" : "TZIHND2.bmp"  },
-				"dwellingUpLvl4": { "animation" : "TBINUP_3.def", "x" : 412, "y" : 197, "z" : 2,  "border" : "TOIDMN2.bmp",  "area" : "TZIDMN2.bmp" },
-				"dwellingUpLvl5": { "animation" : "TBINUP_4.def", "x" : 359, "y" : 244, "z" : 3,  "border" : "TOIPIT2.bmp",  "area" : "TZIPIT2.bmp" },
-				"dwellingUpLvl6": { "animation" : "TBINUP_5.def", "x" : 220, "y" : 282, "z" : 5,  "border" : "TOIEFR2.bmp",  "area" : "TZIEFR2.bmp" },
-				"dwellingUpLvl7": { "animation" : "TBINUP_6.def", "x" : 420, "y" : 105, "z" : -3, "border" : "TOIDVL2.bmp",  "area" : "TZIDVL2.bmp" }
+				"mageGuild1":     { "animation" : "TBINMAGE.def", "x" : 667, "y" : 127,           "campaignBonus" : "BoIMag1.pcx",  "border" : "TOIMAG1A.bmp", "area" : "TZIMAG1A.bmp" },
+				"mageGuild2":     { "animation" : "TBINMAG2.def", "x" : 667, "y" : 101,           "campaignBonus" : "BoIMag2.pcx",  "border" : "TOIMAG2A.bmp", "area" : "TZIMAG2A.bmp" },
+				"mageGuild3":     { "animation" : "TBINMAG3.def", "x" : 667, "y" : 83,            "campaignBonus" : "BoIMag3.pcx",  "border" : "TOIMAG3A.bmp", "area" : "TZIMAG3A.bmp" },
+				"mageGuild4":     { "animation" : "TBINMAG4.def", "x" : 667, "y" : 56,            "campaignBonus" : "BoIMag4.pcx",  "border" : "TOIMAG4A.bmp", "area" : "TZIMAG4A.bmp" },
+				"mageGuild5":     { "animation" : "TBINMAG5.def", "x" : 667, "y" : 35,            "campaignBonus" : "BoIMag5.pcx",  "border" : "TOIMAG5A.bmp", "area" : "TZIMAG5A.bmp" },
+				"tavern":         { "animation" : "TBINTVRN.def", "x" : 105, "y" : 219, "z" : 1,  "campaignBonus" : "BoITav.pcx",   "border" : "TOITAV.bmp",   "area" : "TZITAV.bmp" },
+				"fort":           { "animation" : "TBINCSTL.def", "x" : 222, "y" : 44,  "z" : -2, "campaignBonus" : "BoICas1.pcx",  "border" : "TOICAS2A.bmp", "area" : "TZICAS2A.bmp" },
+				"citadel":        { "animation" : "TBINCAS2.def", "x" : 222, "y" : 44,  "z" : -2, "campaignBonus" : "BoICas2.pcx",  "border" : "TOICAS1A.bmp", "area" : "TZICAS1A.bmp" },
+				"castle":         { "animation" : "TBINCAS3.def", "x" : 222, "y" : 18,  "z" : -2, "campaignBonus" : "BoICas3.pcx",  "border" : "TOICAS3A.bmp", "area" : "TZICAS3A.bmp" },
+				"villageHall":    { "animation" : "TBINHALL.def", "x" : 0,   "y" : 174,           "campaignBonus" : "BoIHal1.pcx",  "border" : "TOIHAL1.bmp",  "area" : "TZIHAL1.bmp"  },
+				"townHall":       { "animation" : "TBINHAL2.def", "x" : 0,   "y" : 174,           "campaignBonus" : "BoIHal2.pcx",  "border" : "TOIHAL2.bmp",  "area" : "TZIHAL2.bmp"  },
+				"cityHall":       { "animation" : "TBINHAL3.def", "x" : 0,   "y" : 174,           "campaignBonus" : "BoIHal3.pcx",  "border" : "TOIHAL3.bmp",  "area" : "TZIHAL3.bmp"  },
+				"capitol":        { "animation" : "TBINHAL4.def", "x" : 0,   "y" : 131,           "campaignBonus" : "BoIHal4.pcx",  "border" : "TOIHAL4.bmp",  "area" : "TZIHAL4.bmp"  },
+				"marketplace":    { "animation" : "TBINMARK.def", "x" : 511, "y" : 301, "z" : 4,  "campaignBonus" : "BoIMrk1.pcx",  "border" : "TOIMAR1.bmp",  "area" : "TZIMAR1.bmp" },
+				"resourceSilo":   { "animation" : "TBINSILO.def", "x" : 497, "y" : 337, "z" : 5,  "campaignBonus" : "BoIMrk2.pcx",  "border" : "TOIMAR2.bmp",  "area" : "TZIMAR2.bmp" },
+				"blacksmith":     { "animation" : "TBINBLAK.def", "x" : 684, "y" : 253, "z" : 1,  "campaignBonus" : "BoIBlak.pcx",  "border" : "TOIBLKA.bmp",  "area" : "TZIBLKA.bmp" },
+				"horde1":         { "animation" : "TBINHRD1.def", "x" : 614, "y" : 256,           "campaignBonus" : "BoIImpH.pcx",  "border" : "TOIMP1HA.bmp", "area" : "TZIMP1HA.bmp", "hidden" : true },
+				"horde1Upgr":     { "animation" : "TBINHRD2.def", "x" : 614, "y" : 221,           "campaignBonus" : "BoIImp2H.pcx", "border" : "TOIMP2HA.bmp", "area" : "TZIMP2HA.bmp", "hidden" : true, "builds" : "horde1" },
+				"special2":       { "animation" : "TBINEXT0.def", "x" : 297, "y" : 0,   "z" : -4, "campaignBonus" : "BoICasB.pcx",  "border" : "TOICAB1A.bmp", "area" : "TZICAB1A.bmp" },
+				"special3":       { "animation" : "TBINEXT1.def", "x" : 227, "y" : 174, "z" : -1, "campaignBonus" : "BoICasG.pcx",  "border" : "TOICASGA.bmp", "area" : "TZICASGA.bmp" },
+				"special4":       { "animation" : "TBINEXT2.def", "x" : 593, "y" : 104, "z" : -1, "campaignBonus" : "BoIMagO.pcx",  "border" : "TOIPAIN.bmp",  "area" : "TZIPAIN.bmp"  },
+				"horde2":         { "animation" : "TBINHRD3.def", "x" : 10,  "y" : 301,           "campaignBonus" : "BoIHndH.pcx",  "border" : "TOIHND1H.bmp", "area" : "TZIHND1H.bmp", "hidden" : true },
+				"horde2Upgr":     { "animation" : "TBINHRD4.def", "x" : 9,   "y" : 273,           "campaignBonus" : "BoIHnd2H.pcx", "border" : "TOIHND2H.bmp", "area" : "TZIHND2H.bmp", "hidden" : true, "builds" : "horde2" },
+				"grail":          { "animation" : "TBINHOLY.def", "x" : 24,  "y" : 10,  "z" : -1, "campaignBonus" : "BoIHoly.pcx",  "border" : "TOIHOLY.bmp",  "area" : "TZIHOLY.bmp" },
+				"dwellingLvl1":   { "animation" : "TBINDW_0.def", "x" : 614, "y" : 256,           "campaignBonus" : "BoIImp1.pcx",  "border" : "TOIMP1A.bmp",  "area" : "TZIMP1A.bmp"  },
+				"dwellingLvl2":   { "animation" : "TBINDW_1.def", "x" : 187, "y" : 248, "z" : 4,  "campaignBonus" : "BoIGog1.pcx",  "border" : "TOIGOG1A.bmp", "area" : "TZIGOG1A.bmp" },
+				"dwellingLvl3":   { "animation" : "TBINDW_2.def", "x" : 9,   "y" : 325,           "campaignBonus" : "BoIHnd1.pcx",  "border" : "TOIHND1.bmp",  "area" : "TZIHND1.bmp"  },
+				"dwellingLvl4":   { "animation" : "TBINDW_3.def", "x" : 414, "y" : 204, "z" : 2,  "campaignBonus" : "BoIDmn1.pcx",  "border" : "TOIDMN1.bmp",  "area" : "TZIDMN1.bmp" },
+				"dwellingLvl5":   { "animation" : "TBINDW_4.def", "x" : 359, "y" : 296, "z" : 3,  "campaignBonus" : "BoIPit1.pcx",  "border" : "TOIPIT1.bmp",  "area" : "TZIPIT1.bmp" },
+				"dwellingLvl6":   { "animation" : "TBINDW_5.def", "x" : 220, "y" : 350, "z" : 5,  "campaignBonus" : "BoIEfr1.pcx",  "border" : "TOIEFR1.bmp",  "area" : "TZIEFR1.bmp" },
+				"dwellingLvl7":   { "animation" : "TBINDW_6.def", "x" : 420, "y" : 153, "z" : -3, "campaignBonus" : "BoIDvl1.pcx",  "border" : "TOIDVL1.bmp",  "area" : "TZIDVL1.bmp" },
+				"dwellingUpLvl1": { "animation" : "TBINUP_0.def", "x" : 614, "y" : 221,           "campaignBonus" : "BoIImp2.pcx",  "border" : "TOIMP2A.bmp",  "area" : "TZIMP2A.bmp"  },
+				"dwellingUpLvl2": { "animation" : "TBINUP_1.def", "x" : 187, "y" : 212, "z" : 4,  "campaignBonus" : "BoIGog2.pcx",  "border" : "TOIGOG2A.bmp", "area" : "TZIGOG2A.bmp" },
+				"dwellingUpLvl3": { "animation" : "TBINUP_2.def", "x" : 9,   "y" : 273,           "campaignBonus" : "BoIHnd2.pcx",  "border" : "TOIHND2.bmp",  "area" : "TZIHND2.bmp"  },
+				"dwellingUpLvl4": { "animation" : "TBINUP_3.def", "x" : 412, "y" : 197, "z" : 2,  "campaignBonus" : "BoIDmn2.pcx",  "border" : "TOIDMN2.bmp",  "area" : "TZIDMN2.bmp" },
+				"dwellingUpLvl5": { "animation" : "TBINUP_4.def", "x" : 359, "y" : 244, "z" : 3,  "campaignBonus" : "BoIPit2.pcx",  "border" : "TOIPIT2.bmp",  "area" : "TZIPIT2.bmp" },
+				"dwellingUpLvl6": { "animation" : "TBINUP_5.def", "x" : 220, "y" : 282, "z" : 5,  "campaignBonus" : "BoIEfr2.pcx",  "border" : "TOIEFR2.bmp",  "area" : "TZIEFR2.bmp" },
+				"dwellingUpLvl7": { "animation" : "TBINUP_6.def", "x" : 420, "y" : 105, "z" : -3, "campaignBonus" : "BoIDvl2.pcx",  "border" : "TOIDVL2.bmp",  "area" : "TZIDVL2.bmp" }
 			},
 
 			"musicTheme" : [ "music/InfernoTown" ],

+ 37 - 37
config/factions/necropolis.json

@@ -81,47 +81,47 @@
 			"structures" :
 			{
 				"extraAnimation": { "animation" : "TBNCEXT2.def", "x" : 25,  "y" : 279, "z" : 9 },
-				"mageGuild1":     { "animation" : "TBNCMAGE.def", "x" : 341, "y" : 116, "z" : -1, "border" : "TONMAG1.bmp",  "area" : "TZNMAG1.bmp" },
-				"mageGuild2":     { "animation" : "TBNCMAG2.def", "x" : 341, "y" : 97,  "z" : -1, "border" : "TONMAG2.bmp",  "area" : "TZNMAG2.bmp" },
-				"mageGuild3":     { "animation" : "TBNCMAG3.def", "x" : 341, "y" : 78,  "z" : -1, "border" : "TONMAG3.bmp",  "area" : "TZNMAG3.bmp" },
-				"mageGuild4":     { "animation" : "TBNCMAG4.def", "x" : 340, "y" : 62,  "z" : -1, "border" : "TONMAG4.bmp",  "area" : "TZNMAG4.bmp" },
-				"mageGuild5":     { "animation" : "TBNCMAG5.def", "x" : 343, "y" : 35,  "z" : -1, "border" : "TONMAG5.bmp",  "area" : "TZNMAG5.bmp" },
-				"tavern":         { "animation" : "TBNCTVRN.def", "x" : 508, "y" : 189, "border" : "TONTAV.bmp",   "area" : "TZNTAV.bmp"  },
-				"shipyard":       { "animation" : "TBNCDOCK.def", "x" : 617, "y" : 265, "z" : -2, "border" : "TONSHPBA.bmp", "area" : "TZNSHPBA.bmp" },
-				"fort":           { "animation" : "TBNCCSTL.def", "x" : 138, "y" : 66,  "border" : "TONCAS1.bmp",  "area" : "TZNCAS1.bmp" },
-				"citadel":        { "animation" : "TBNCCAS2.def", "x" : 139, "y" : 66,  "border" : "TONCAS2.bmp",  "area" : "TZNCAS2.bmp" },
-				"castle":         { "animation" : "TBNCCAS3.def", "x" : 34,  "y" : 18,  "border" : "TONCAS3.bmp",  "area" : "TZNCAS3.bmp" },
-				"villageHall":    { "animation" : "TBNCHALL.def", "x" : 468, "y" : 76,  "z" : -2, "border" : "TONHAL1.bmp",  "area" : "TZNHAL1.bmp" },
-				"townHall":       { "animation" : "TBNCHAL2.def", "x" : 482, "y" : 56,  "z" : -2, "border" : "TONHAL2.bmp",  "area" : "TZNHAL2.bmp" },
-				"cityHall":       { "animation" : "TBNCHAL3.def", "x" : 478, "y" : 26,  "z" : -2, "border" : "TONHAL3.bmp",  "area" : "TZNHAL3.bmp" },
-				"capitol":        { "animation" : "TBNCHAL4.def", "x" : 481, "y" : 26,  "z" : -2, "border" : "TONHAL4.bmp",  "area" : "TZNHAL4.bmp" },
-				"marketplace":    { "animation" : "TBNCMARK.def", "x" : 347, "y" : 215, "z" : 2,  "border" : "TONMRK1.bmp",  "area" : "TZNMRK1.bmp" },
-				"resourceSilo":   { "animation" : "TBNCSILO.def", "x" : 276, "y" : 185, "z" : 1,  "border" : "TONMRK2.bmp",  "area" : "TZNMRK2.bmp" },
-				"blacksmith":     { "animation" : "TBNCBLAK.def", "x" : 382, "y" : 252, "z" : 7,  "border" : "TONSMITA.bmp", "area" : "TZNSMITA.bmp" },
-				"special1":       { "animation" : "TBNCSPEC.def", "x" : 18,  "y" : 0,   "z" : -1, "border" : "TONSHRDA.bmp", "area" : "TZNSHRDA.bmp" },
-				"horde1":         { "animation" : "TBNCHRD1.def", "x" : 80,  "y" : 222, "z" : 5, "border" : "TONSKE1H.bmp", "area" : "TZNSKE1H.bmp", "hidden" : true },
-				"horde1Upgr":     { "animation" : "TBNCHRD2.def", "x" : 64,  "y" : 222, "z" : 5, "border" : "TONSKE2H.bmp", "area" : "TZNSKE2H.bmp", "hidden" : true, "builds" : "horde1" },
+				"mageGuild1":     { "animation" : "TBNCMAGE.def", "x" : 341, "y" : 116, "z" : -1, "campaignBonus" : "BoNmage1.pcx", "border" : "TONMAG1.bmp",  "area" : "TZNMAG1.bmp" },
+				"mageGuild2":     { "animation" : "TBNCMAG2.def", "x" : 341, "y" : 97,  "z" : -1, "campaignBonus" : "BoNmage2.pcx", "border" : "TONMAG2.bmp",  "area" : "TZNMAG2.bmp" },
+				"mageGuild3":     { "animation" : "TBNCMAG3.def", "x" : 341, "y" : 78,  "z" : -1, "campaignBonus" : "BoNmage3.pcx", "border" : "TONMAG3.bmp",  "area" : "TZNMAG3.bmp" },
+				"mageGuild4":     { "animation" : "TBNCMAG4.def", "x" : 340, "y" : 62,  "z" : -1, "campaignBonus" : "BoNmage4.pcx", "border" : "TONMAG4.bmp",  "area" : "TZNMAG4.bmp" },
+				"mageGuild5":     { "animation" : "TBNCMAG5.def", "x" : 343, "y" : 35,  "z" : -1, "campaignBonus" : "BoNmage5.pcx", "border" : "TONMAG5.bmp",  "area" : "TZNMAG5.bmp" },
+				"tavern":         { "animation" : "TBNCTVRN.def", "x" : 508, "y" : 189,           "campaignBonus" : "BoNtav.pcx", "border" : "TONTAV.bmp",   "area" : "TZNTAV.bmp"  },
+				"shipyard":       { "animation" : "TBNCDOCK.def", "x" : 617, "y" : 265, "z" : -2, "campaignBonus" : "BoNship.pcx", "border" : "TONSHPBA.bmp", "area" : "TZNSHPBA.bmp" },
+				"fort":           { "animation" : "TBNCCSTL.def", "x" : 138, "y" : 66,            "campaignBonus" : "BoNcast1.pcx", "border" : "TONCAS1.bmp",  "area" : "TZNCAS1.bmp" },
+				"citadel":        { "animation" : "TBNCCAS2.def", "x" : 139, "y" : 66,            "campaignBonus" : "BoNcast2.pcx", "border" : "TONCAS2.bmp",  "area" : "TZNCAS2.bmp" },
+				"castle":         { "animation" : "TBNCCAS3.def", "x" : 34,  "y" : 18,            "campaignBonus" : "BoNcast3.pcx", "border" : "TONCAS3.bmp",  "area" : "TZNCAS3.bmp" },
+				"villageHall":    { "animation" : "TBNCHALL.def", "x" : 468, "y" : 76,  "z" : -2, "campaignBonus" : "BoNhall1.pcx", "border" : "TONHAL1.bmp",  "area" : "TZNHAL1.bmp" },
+				"townHall":       { "animation" : "TBNCHAL2.def", "x" : 482, "y" : 56,  "z" : -2, "campaignBonus" : "BoNhall2.pcx", "border" : "TONHAL2.bmp",  "area" : "TZNHAL2.bmp" },
+				"cityHall":       { "animation" : "TBNCHAL3.def", "x" : 478, "y" : 26,  "z" : -2, "campaignBonus" : "BoNhall3.pcx", "border" : "TONHAL3.bmp",  "area" : "TZNHAL3.bmp" },
+				"capitol":        { "animation" : "TBNCHAL4.def", "x" : 481, "y" : 26,  "z" : -2, "campaignBonus" : "BoNhall4.pcx", "border" : "TONHAL4.bmp",  "area" : "TZNHAL4.bmp" },
+				"marketplace":    { "animation" : "TBNCMARK.def", "x" : 347, "y" : 215, "z" : 2,  "campaignBonus" : "BoNmark1.pcx", "border" : "TONMRK1.bmp",  "area" : "TZNMRK1.bmp" },
+				"resourceSilo":   { "animation" : "TBNCSILO.def", "x" : 276, "y" : 185, "z" : 1,  "campaignBonus" : "BoNmark2.pcx", "border" : "TONMRK2.bmp",  "area" : "TZNMRK2.bmp" },
+				"blacksmith":     { "animation" : "TBNCBLAK.def", "x" : 382, "y" : 252, "z" : 7,  "campaignBonus" : "BoNsmith.pcx", "border" : "TONSMITA.bmp", "area" : "TZNSMITA.bmp" },
+				"special1":       { "animation" : "TBNCSPEC.def", "x" : 18,  "y" : 0,   "z" : -1, "campaignBonus" : "BoNshrod.pcx", "border" : "TONSHRDA.bmp", "area" : "TZNSHRDA.bmp" },
+				"horde1":         { "animation" : "TBNCHRD1.def", "x" : 80,  "y" : 222, "z" : 5,  "campaignBonus" : "BoNskelH.pcx", "border" : "TONSKE1H.bmp", "area" : "TZNSKE1H.bmp", "hidden" : true },
+				"horde1Upgr":     { "animation" : "TBNCHRD2.def", "x" : 64,  "y" : 222, "z" : 5,  "campaignBonus" : "BoNnecro.pcx", "border" : "TONSKE2H.bmp", "area" : "TZNSKE2H.bmp", "hidden" : true, "builds" : "horde1" },
 				"ship":           { "animation" : "TBNCBOAT.def", "x" : 617, "y" : 265, "z" : -2, "border" : "TONSHPNA.bmp", "area" : "TZNSHPNA.bmp", "hidden" : true },
-				"special2":       { "animation" : "TBNCEXT0.def", "x" : 307, "y" : 61,  "z" : -2, "border" : "TONNECRA.bmp", "area" : "TZNNECRA.bmp" },
-				"special3":       { "animation" : "TBNCEXT1.def", "x" : 247, "y" : 275, "z" : 4,  "border" : "TONSKELT.bmp", "area" : "TZNSKELT.bmp" },
-				"grail":          { "animation" : "TBNCHOLY.def", "x" : 410, "y" : 88,  "border" : "TONHOLYA.bmp", "area" : "TZNHOLYA.bmp" },
+				"special2":       { "animation" : "TBNCEXT0.def", "x" : 307, "y" : 61,  "z" : -2, "campaignBonus" : "BoNnecro.pcx", "border" : "TONNECRA.bmp", "area" : "TZNNECRA.bmp" },
+				"special3":       { "animation" : "TBNCEXT1.def", "x" : 247, "y" : 275, "z" : 4,  "campaignBonus" : "BoNskelT.pcx", "border" : "TONSKELT.bmp", "area" : "TZNSKELT.bmp" },
+				"grail":          { "animation" : "TBNCHOLY.def", "x" : 410, "y" : 88,            "campaignBonus" : "BoNholyG.pcx", "border" : "TONHOLYA.bmp", "area" : "TZNHOLYA.bmp" },
 				"extraTownHall":  { "animation" : "TBNCEXT3.def", "x" : 0,   "y" : 241, "z" : 6 },
 				"extraCityHall":  { "animation" : "TBNCEXT4.def", "x" : 321, "y" : 255, "z" : 6 },
 				"extraCapitol":   { "animation" : "TBNCEXT5.def", "x" : 475, "y" : 257, "z" : 6 },
-				"dwellingLvl1":   { "animation" : "TBNCDW_0.def", "x" : 80,  "y" : 222, "z" : 5, "border" : "TONSKEL1.bmp", "area" : "TZNSKEL1.bmp" },
-				"dwellingLvl2":   { "animation" : "TBNCDW_1.def", "x" : 502, "y" : 223, "z" : 1, "border" : "TONZOMB1.bmp", "area" : "TZNZOMB1.bmp" },
-				"dwellingLvl3":   { "animation" : "TBNCDW_2.def", "x" : 0,   "y" : 187, "z" : 8, "border" : "TONWIGH1.bmp", "area" : "TZNWIGH1.bmp" },
-				"dwellingLvl4":   { "animation" : "TBNCDW_3.def", "x" : 607, "y" : 212, "z" : 2, "border" : "TONVAM1.bmp",  "area" : "TZNVAM1.bmp" },
-				"dwellingLvl5":   { "animation" : "TBNCDW_4.def", "x" : 206, "y" : 207, "z" : 3, "border" : "TONLICH1.bmp", "area" : "TZNLICH1.bmp" },
-				"dwellingLvl6":   { "animation" : "TBNCDW_5.def", "x" : 0,   "y" : 31,  "z" : -2, "border" : "TONBKN1.bmp",  "area" : "TZNBKN1.bmp" },
-				"dwellingLvl7":   { "animation" : "TBNCDW_6.def", "x" : 663, "y" : 25,  "border" : "TONBON1.bmp",  "area" : "TZNBON1.bmp" },
-				"dwellingUpLvl1": { "animation" : "TBNCUP_0.def", "x" : 64,  "y" : 222, "z" : 5, "border" : "TONSKEL2.bmp", "area" : "TZNSKEL2.bmp" },
-				"dwellingUpLvl2": { "animation" : "TBNCUP_1.def", "x" : 498, "y" : 224, "z" : 1, "border" : "TONZOMB2.bmp", "area" : "TZNZOMB2.bmp" },
-				"dwellingUpLvl3": { "animation" : "TBNCUP_2.def", "x" : 0,   "y" : 179, "z" : 8, "border" : "TONWIGH2.bmp", "area" : "TZNWIGH2.bmp" },
-				"dwellingUpLvl4": { "animation" : "TBNCUP_3.def", "x" : 615, "y" : 193, "z" : 2, "border" : "TONVAM2.bmp",  "area" : "TZNVAM2.bmp" },
-				"dwellingUpLvl5": { "animation" : "TBNCUP_4.def", "x" : 222, "y" : 171, "z" : 3, "border" : "TONLICH2.bmp", "area" : "TZNLICH2.bmp" },
-				"dwellingUpLvl6": { "animation" : "TBNCUP_5.def", "x" : 0,   "y" : 30,  "z" : -2, "border" : "TONBKN2.bmp",  "area" : "TZNBKN2.bmp" },
-				"dwellingUpLvl7": { "animation" : "TBNCUP_6.def", "x" : 662, "y" : 23,  "border" : "TONBON2.bmp",  "area" : "TZNBON2.bmp" }
+				"dwellingLvl1":   { "animation" : "TBNCDW_0.def", "x" : 80,  "y" : 222, "z" : 5,  "campaignBonus" : "BoNskel1.pcx", "border" : "TONSKEL1.bmp", "area" : "TZNSKEL1.bmp" },
+				"dwellingLvl2":   { "animation" : "TBNCDW_1.def", "x" : 502, "y" : 223, "z" : 1,  "campaignBonus" : "BoNzomb1.pcx", "border" : "TONZOMB1.bmp", "area" : "TZNZOMB1.bmp" },
+				"dwellingLvl3":   { "animation" : "TBNCDW_2.def", "x" : 0,   "y" : 187, "z" : 8,  "campaignBonus" : "BoNwigh1.pcx", "border" : "TONWIGH1.bmp", "area" : "TZNWIGH1.bmp" },
+				"dwellingLvl4":   { "animation" : "TBNCDW_3.def", "x" : 607, "y" : 212, "z" : 2,  "campaignBonus" : "BoNvamp1.pcx", "border" : "TONVAM1.bmp",  "area" : "TZNVAM1.bmp" },
+				"dwellingLvl5":   { "animation" : "TBNCDW_4.def", "x" : 206, "y" : 207, "z" : 3,  "campaignBonus" : "BoNlich1.pcx", "border" : "TONLICH1.bmp", "area" : "TZNLICH1.bmp" },
+				"dwellingLvl6":   { "animation" : "TBNCDW_5.def", "x" : 0,   "y" : 31,  "z" : -2, "campaignBonus" : "BoNbkni1.pcx", "border" : "TONBKN1.bmp",  "area" : "TZNBKN1.bmp" },
+				"dwellingLvl7":   { "animation" : "TBNCDW_6.def", "x" : 663, "y" : 25,            "campaignBonus" : "BoNbone1.pcx", "border" : "TONBON1.bmp",  "area" : "TZNBON1.bmp" },
+				"dwellingUpLvl1": { "animation" : "TBNCUP_0.def", "x" : 64,  "y" : 222, "z" : 5,  "campaignBonus" : "BoNskel2.pcx", "border" : "TONSKEL2.bmp", "area" : "TZNSKEL2.bmp" },
+				"dwellingUpLvl2": { "animation" : "TBNCUP_1.def", "x" : 498, "y" : 224, "z" : 1,  "campaignBonus" : "BoNzomb2.pcx", "border" : "TONZOMB2.bmp", "area" : "TZNZOMB2.bmp" },
+				"dwellingUpLvl3": { "animation" : "TBNCUP_2.def", "x" : 0,   "y" : 179, "z" : 8,  "campaignBonus" : "BoNwigh2.pcx", "border" : "TONWIGH2.bmp", "area" : "TZNWIGH2.bmp" },
+				"dwellingUpLvl4": { "animation" : "TBNCUP_3.def", "x" : 615, "y" : 193, "z" : 2,  "campaignBonus" : "BoNvamp2.pcx", "border" : "TONVAM2.bmp",  "area" : "TZNVAM2.bmp" },
+				"dwellingUpLvl5": { "animation" : "TBNCUP_4.def", "x" : 222, "y" : 171, "z" : 3,  "campaignBonus" : "BoNlich2.pcx", "border" : "TONLICH2.bmp", "area" : "TZNLICH2.bmp" },
+				"dwellingUpLvl6": { "animation" : "TBNCUP_5.def", "x" : 0,   "y" : 30,  "z" : -2, "campaignBonus" : "BoNbkni2.pcx", "border" : "TONBKN2.bmp",  "area" : "TZNBKN2.bmp" },
+				"dwellingUpLvl7": { "animation" : "TBNCUP_6.def", "x" : 662, "y" : 23,            "campaignBonus" : "BoNbone2.pcx", "border" : "TONBON2.bmp",  "area" : "TZNBON2.bmp" }
 			},
 
 			"musicTheme" : [ "music/NecroTown" ],

+ 38 - 38
config/factions/rampart.json

@@ -80,47 +80,47 @@
 			"structures" :
 			{
 				"extraAnimation": { "animation" : "TBRMEXT2.def", "x" : 327, "y" : 236, "z" : 1 },
-				"mageGuild1":     { "animation" : "TBRMMAGE.def", "x" : 454, "y" : 200, "z" : -1, "border" : "TORMAG1.bmp",  "area" : "TZRMAG1.bmp" },
-				"mageGuild2":     { "animation" : "TBRMMAG2.def", "x" : 438, "y" : 178, "z" : -1, "border" : "TORMAG2.bmp",  "area" : "TZRMAG2.bmp" },
-				"mageGuild3":     { "animation" : "TBRMMAG3.def", "x" : 418, "y" : 153, "z" : -1, "border" : "TORMAG3.bmp",  "area" : "TZRMAG3.bmp" },
-				"mageGuild4":     { "animation" : "TBRMMAG4.def", "x" : 406, "y" : 129, "z" : -1, "border" : "TORMAG4.bmp",  "area" : "TZRMAG4.bmp" },
-				"mageGuild5":     { "animation" : "TBRMMAG5.def", "x" : 384, "y" : 104, "z" : -1, "border" : "TORMAG5.bmp",  "area" : "TZRMAG5.bmp" },
-				"tavern":         { "animation" : "TBRMTVRN.def", "x" : 181, "y" : 229, "z" : 1,  "border" : "TORTAV.bmp",   "area" : "TZRTAV.bmp" },
-				"fort":           { "animation" : "TBRMCSTL.def", "x" : 63,  "y" : 25,  "z" : -4, "border" : "TORCAS1.bmp",  "area" : "TZRCAS1.bmp" },
-				"citadel":        { "animation" : "TBRMCAS2.def", "x" : 79,  "y" : 18,  "z" : -4, "border" : "TORCAS3.bmp",  "area" : "TZRCAS3.bmp" },
-				"castle":         { "animation" : "TBRMCAS3.def", "x" : 79,  "y" : 18,  "z" : -4, "border" : "TORCAS2.bmp",  "area" : "TZRCAS2.bmp" },
-				"villageHall":    { "animation" : "TBRMHALL.def", "x" : 565, "y" : 216, "border" : "TORHAL1.bmp",  "area" : "TZRHAL1.bmp" },
-				"townHall":       { "animation" : "TBRMHAL2.def", "x" : 538, "y" : 187, "border" : "TORHAL2.bmp",  "area" : "TZRHAL2.bmp" },
-				"cityHall":       { "animation" : "TBRMHAL3.def", "x" : 538, "y" : 187, "border" : "TORHAL3.bmp",  "area" : "TZRHAL3.bmp" },
-				"capitol":        { "animation" : "TBRMHAL4.def", "x" : 534, "y" : 187, "border" : "TORHAL4.bmp",  "area" : "TZRHAL4.bmp" },
-				"marketplace":    { "animation" : "TBRMMARK.def", "x" : 129, "y" : 301, "z" : 3,  "border" : "TORMRK1.bmp",  "area" : "TZRMRK1.bmp" },
-				"resourceSilo":   { "animation" : "TBRMSILO.def", "x" : 245, "y" : 324, "z" : 4,  "border" : "TORMRK2.bmp",  "area" : "TZRMRK2.bmp" },
-				"blacksmith":     { "animation" : "TBRMBLAK.def", "x" : 558, "y" : 105, "z" : -3, "border" : "TORAID.bmp",   "area" : "TZRAID.bmp" },
-				"special1":       { "animation" : "TBRMSPEC.def", "x" : 555, "y" : 297, "z" : 2, "border" : "TORGAR1A.bmp", "area" : "TZRGAR1A.bmp" },
-				"horde1":         { "animation" : "TBRMHRD1.def", "x" : 0,   "y" : 154, "z" : -2, "border" : "TORDWF1H.bmp", "area" : "TZRDWF1H.bmp", "hidden" : true },
-				"horde1Upgr":     { "animation" : "TBRMHRD2.def", "x" : 0,   "y" : 143, "z" : -2, "border" : "TORDWF2H.bmp", "area" : "TZRDWF2H.bmp", "hidden" : true, "builds" : "horde1" },
-				"special2":       { "animation" : "TBRMEXT0.def", "x" : 555, "y" : 297, "z" : 2, "border" : "TORGAR2A.bmp", "area" : "TZRGAR2A.bmp" },
-				"special3":       { "animation" : "TBRMEXT1.def", "x" : 0,   "y" : 181, "z" : 1, "border" : "TORDWFT.bmp",  "area" : "TZRDWFT.bmp" },
-				"horde2":         { "animation" : "TBRMHRD3.def", "x" : 47,  "y" : 142, "z" : -1, "border" : "TORTRE1H.bmp", "area" : "TZRTRE1H.bmp", "hidden" : true },
-				"horde2Upgr":     { "animation" : "TBRMHRD4.def", "x" : 47,  "y" : 142, "z" : -1, "border" : "TORTRE2H.bmp", "area" : "TZRTRE2H.bmp", "hidden" : true, "builds" : "horde2" },
-				"grail":          { "animation" : "TBRMHOLY.def", "x" : 0,   "y" : 54,  "z" : -3, "border" : "TORHOLY.bmp",  "area" : "TZRHOLY.bmp" },
+				"mageGuild1":     { "animation" : "TBRMMAGE.def", "x" : 454, "y" : 200, "z" : -1, "campaignBonus" : "BoRMag1.pcx", "border" : "TORMAG1.bmp",  "area" : "TZRMAG1.bmp" },
+				"mageGuild2":     { "animation" : "TBRMMAG2.def", "x" : 438, "y" : 178, "z" : -1, "campaignBonus" : "BoRMag2.pcx", "border" : "TORMAG2.bmp",  "area" : "TZRMAG2.bmp" },
+				"mageGuild3":     { "animation" : "TBRMMAG3.def", "x" : 418, "y" : 153, "z" : -1, "campaignBonus" : "BoRMag3.pcx", "border" : "TORMAG3.bmp",  "area" : "TZRMAG3.bmp" },
+				"mageGuild4":     { "animation" : "TBRMMAG4.def", "x" : 406, "y" : 129, "z" : -1, "campaignBonus" : "BoRMag4.pcx", "border" : "TORMAG4.bmp",  "area" : "TZRMAG4.bmp" },
+				"mageGuild5":     { "animation" : "TBRMMAG5.def", "x" : 384, "y" : 104, "z" : -1, "campaignBonus" : "BoRMag5.pcx", "border" : "TORMAG5.bmp",  "area" : "TZRMAG5.bmp" },
+				"tavern":         { "animation" : "TBRMTVRN.def", "x" : 181, "y" : 229, "z" : 1,  "campaignBonus" : "BoRTav.pcx", "border" : "TORTAV.bmp",   "area" : "TZRTAV.bmp" },
+				"fort":           { "animation" : "TBRMCSTL.def", "x" : 63,  "y" : 25,  "z" : -4, "campaignBonus" : "BoRCas1.pcx", "border" : "TORCAS1.bmp",  "area" : "TZRCAS1.bmp" },
+				"citadel":        { "animation" : "TBRMCAS2.def", "x" : 79,  "y" : 18,  "z" : -4, "campaignBonus" : "BoRCas2.pcx", "border" : "TORCAS3.bmp",  "area" : "TZRCAS3.bmp" },
+				"castle":         { "animation" : "TBRMCAS3.def", "x" : 79,  "y" : 18,  "z" : -4, "campaignBonus" : "BoRCas3.pcx", "border" : "TORCAS2.bmp",  "area" : "TZRCAS2.bmp" },
+				"villageHall":    { "animation" : "TBRMHALL.def", "x" : 565, "y" : 216,           "campaignBonus" : "BoRHal1.pcx", "border" : "TORHAL1.bmp",  "area" : "TZRHAL1.bmp" },
+				"townHall":       { "animation" : "TBRMHAL2.def", "x" : 538, "y" : 187,           "campaignBonus" : "BoRHal2.pcx", "border" : "TORHAL2.bmp",  "area" : "TZRHAL2.bmp" },
+				"cityHall":       { "animation" : "TBRMHAL3.def", "x" : 538, "y" : 187,           "campaignBonus" : "BoRHal3.pcx", "border" : "TORHAL3.bmp",  "area" : "TZRHAL3.bmp" },
+				"capitol":        { "animation" : "TBRMHAL4.def", "x" : 534, "y" : 187,           "campaignBonus" : "BoRHal4.pcx", "border" : "TORHAL4.bmp",  "area" : "TZRHAL4.bmp" },
+				"marketplace":    { "animation" : "TBRMMARK.def", "x" : 129, "y" : 301, "z" : 3,  "campaignBonus" : "BoRMrk1.pcx", "border" : "TORMRK1.bmp",  "area" : "TZRMRK1.bmp" },
+				"resourceSilo":   { "animation" : "TBRMSILO.def", "x" : 245, "y" : 324, "z" : 4,  "campaignBonus" : "BoRMrk2.pcx", "border" : "TORMRK2.bmp",  "area" : "TZRMRK2.bmp" },
+				"blacksmith":     { "animation" : "TBRMBLAK.def", "x" : 558, "y" : 105, "z" : -3, "campaignBonus" : "BoRAid.pcx", "border" : "TORAID.bmp",   "area" : "TZRAID.bmp" },
+				"special1":       { "animation" : "TBRMSPEC.def", "x" : 555, "y" : 297, "z" : 2,  "campaignBonus" : "BoRGar1.pcx", "border" : "TORGAR1A.bmp", "area" : "TZRGAR1A.bmp" },
+				"horde1":         { "animation" : "TBRMHRD1.def", "x" : 0,   "y" : 154, "z" : -2, "campaignBonus" : "BoRDwf1h.pcx", "border" : "TORDWF1H.bmp", "area" : "TZRDWF1H.bmp", "hidden" : true },
+				"horde1Upgr":     { "animation" : "TBRMHRD2.def", "x" : 0,   "y" : 143, "z" : -2, "campaignBonus" : "BoRDwf2h.pcx", "border" : "TORDWF2H.bmp", "area" : "TZRDWF2H.bmp", "hidden" : true, "builds" : "horde1" },
+				"special2":       { "animation" : "TBRMEXT0.def", "x" : 555, "y" : 297, "z" : 2,  "campaignBonus" : "BoRGar2.pcx", "border" : "TORGAR2A.bmp", "area" : "TZRGAR2A.bmp" },
+				"special3":       { "animation" : "TBRMEXT1.def", "x" : 0,   "y" : 181, "z" : 1,  "campaignBonus" : "BoRDwf1t.pcx", "border" : "TORDWFT.bmp",  "area" : "TZRDWFT.bmp" },
+				"horde2":         { "animation" : "TBRMHRD3.def", "x" : 47,  "y" : 142, "z" : -1, "campaignBonus" : "BoRTre1h.pcx", "border" : "TORTRE1H.bmp", "area" : "TZRTRE1H.bmp", "hidden" : true },
+				"horde2Upgr":     { "animation" : "TBRMHRD4.def", "x" : 47,  "y" : 142, "z" : -1, "campaignBonus" : "BoRTre2h.pcx", "border" : "TORTRE2H.bmp", "area" : "TZRTRE2H.bmp", "hidden" : true, "builds" : "horde2" },
+				"grail":          { "animation" : "TBRMHOLY.def", "x" : 0,   "y" : 54,  "z" : -3, "campaignBonus" : "BoRHoly.pcx", "border" : "TORHOLY.bmp",  "area" : "TZRHOLY.bmp" },
 				"extraTownHall":  { "animation" : "TBRMEXT3.def", "x" : 293, "y" : 235, "z" : 2 },
 				"extraCityHall":  { "animation" : "TBRMEXT4.def", "x" : 295, "y" : 191, "z" : 0 },
 				"extraCapitol":   { "animation" : "TBRMEXT5.def", "x" : 260, "y" : 171, "z" : 3 },
-				"dwellingLvl1":   { "animation" : "TBRMDW_0.def", "x" : 0,   "y" : 236, "z" : 2,  "border" : "TORCEN1A.bmp", "area" : "TZRCEN1A.bmp" },
-				"dwellingLvl2":   { "animation" : "TBRMDW_1.def", "x" : 0,   "y" : 154, "z" : -2, "border" : "TORDWF1.bmp",  "area" : "TZRDWF1.bmp" },
-				"dwellingLvl3":   { "animation" : "TBRMDW_2.def", "x" : 668, "y" : 101, "z" : -1, "border" : "TORELF1.bmp",  "area" : "TZRELF1.bmp" },
-				"dwellingLvl4":   { "animation" : "TBRMDW_3.def", "x" : 287, "y" : 73,  "z" : -1, "border" : "TORPEG1A.bmp", "area" : "TZRPEG1A.bmp" },
-				"dwellingLvl5":   { "animation" : "TBRMDW_4.def", "x" : 68,  "y" : 146, "z" : -1, "border" : "TORTRE1.bmp",  "area" : "TZRTRE1.bmp" },
-				"dwellingLvl6":   { "animation" : "TBRMDW_5.def", "x" : 362, "y" : 90,  "z" : -2, "border" : "TORUNI1.bmp",  "area" : "TZRUNI1.bmp" },
-				"dwellingLvl7":   { "animation" : "TBRMDW_6.def", "x" : 502, "y" : 27,  "z" : -5, "border" : "TORDR1AA.bmp", "area" : "TZRDR1AA.bmp" },
-				"dwellingUpLvl1": { "animation" : "TBRMUP_0.def", "x" : 0,   "y" : 236, "z" : 2,  "border" : "TORCEN2A.bmp", "area" : "TZRCEN2A.bmp" },
-				"dwellingUpLvl2": { "animation" : "TBRMUP_1.def", "x" : 0,   "y" : 143, "z" : -2, "border" : "TORDWF2.bmp",  "area" : "TZRDWF2.bmp" },
-				"dwellingUpLvl3": { "animation" : "TBRMUP_2.def", "x" : 665, "y" : 101, "z" : -1, "border" : "TORELF2.bmp",  "area" : "TZRELF2.bmp" },
-				"dwellingUpLvl4": { "animation" : "TBRMUP_3.def", "x" : 287, "y" : 28,  "z" : -1, "border" : "TORPEG2A.bmp", "area" : "TZRPEG2A.bmp" },
-				"dwellingUpLvl5": { "animation" : "TBRMUP_4.def", "x" : 63,  "y" : 146, "z" : -1, "border" : "TORTRE2.bmp",  "area" : "TZRTRE2.bmp" },
-				"dwellingUpLvl6": { "animation" : "TBRMUP_5.def", "x" : 362, "y" : 90,  "z" : -2, "border" : "TORUNI2.bmp",  "area" : "TZRUNI2.bmp" },
-				"dwellingUpLvl7": { "animation" : "TBRMUP_6.def", "x" : 502, "y" : 5,   "z" : -5, "border" : "TORDR2AA.bmp", "area" : "TZRDR2AA.bmp" }
+				"dwellingLvl1":   { "animation" : "TBRMDW_0.def", "x" : 0,   "y" : 236, "z" : 2,  "campaignBonus" : "BoRCen1.pcx", "border" : "TORCEN1A.bmp", "area" : "TZRCEN1A.bmp" },
+				"dwellingLvl2":   { "animation" : "TBRMDW_1.def", "x" : 0,   "y" : 154, "z" : -2, "campaignBonus" : "BoRDwf1.pcx", "border" : "TORDWF1.bmp",  "area" : "TZRDWF1.bmp" },
+				"dwellingLvl3":   { "animation" : "TBRMDW_2.def", "x" : 668, "y" : 101, "z" : -1, "campaignBonus" : "BoRElf1.pcx", "border" : "TORELF1.bmp",  "area" : "TZRELF1.bmp" },
+				"dwellingLvl4":   { "animation" : "TBRMDW_3.def", "x" : 287, "y" : 73,  "z" : -1, "campaignBonus" : "BoRPeg1.pcx", "border" : "TORPEG1A.bmp", "area" : "TZRPEG1A.bmp" },
+				"dwellingLvl5":   { "animation" : "TBRMDW_4.def", "x" : 68,  "y" : 146, "z" : -1, "campaignBonus" : "BoRTre1.pcx", "border" : "TORTRE1.bmp",  "area" : "TZRTRE1.bmp" },
+				"dwellingLvl6":   { "animation" : "TBRMDW_5.def", "x" : 362, "y" : 90,  "z" : -2, "campaignBonus" : "BoRUni1.pcx", "border" : "TORUNI1.bmp",  "area" : "TZRUNI1.bmp" },
+				"dwellingLvl7":   { "animation" : "TBRMDW_6.def", "x" : 502, "y" : 27,  "z" : -5, "campaignBonus" : "BoRDra1.pcx", "border" : "TORDR1AA.bmp", "area" : "TZRDR1AA.bmp" },
+				"dwellingUpLvl1": { "animation" : "TBRMUP_0.def", "x" : 0,   "y" : 236, "z" : 2,  "campaignBonus" : "BoRCen2.pcx", "border" : "TORCEN2A.bmp", "area" : "TZRCEN2A.bmp" },
+				"dwellingUpLvl2": { "animation" : "TBRMUP_1.def", "x" : 0,   "y" : 143, "z" : -2, "campaignBonus" : "BoRDwf2.pcx", "border" : "TORDWF2.bmp",  "area" : "TZRDWF2.bmp" },
+				"dwellingUpLvl3": { "animation" : "TBRMUP_2.def", "x" : 665, "y" : 101, "z" : -1, "campaignBonus" : "BoRElf2.pcx", "border" : "TORELF2.bmp",  "area" : "TZRELF2.bmp" },
+				"dwellingUpLvl4": { "animation" : "TBRMUP_3.def", "x" : 287, "y" : 28,  "z" : -1, "campaignBonus" : "BoRPeg2.pcx", "border" : "TORPEG2A.bmp", "area" : "TZRPEG2A.bmp" },
+				"dwellingUpLvl5": { "animation" : "TBRMUP_4.def", "x" : 63,  "y" : 146, "z" : -1, "campaignBonus" : "BoRTre2.pcx", "border" : "TORTRE2.bmp",  "area" : "TZRTRE2.bmp" },
+				"dwellingUpLvl6": { "animation" : "TBRMUP_5.def", "x" : 362, "y" : 90,  "z" : -2, "campaignBonus" : "BoRUni2.pcx", "border" : "TORUNI2.bmp",  "area" : "TZRUNI2.bmp" },
+				"dwellingUpLvl7": { "animation" : "TBRMUP_6.def", "x" : 502, "y" : 5,   "z" : -5, "campaignBonus" : "BoRDra2.pcx", "border" : "TORDR2AA.bmp", "area" : "TZRDR2AA.bmp" }
 			},
 
 			"musicTheme" : [ "music/Rampart" ],

+ 35 - 35
config/factions/stronghold.json

@@ -80,41 +80,41 @@
 			"structures" :
 			{
 				"extraAnimation": { "animation" : "TBSTEXT3.def", "x" : 23,  "y" : 20 },
-				"mageGuild1":     { "animation" : "TBSTMAGE.def", "x" : 473, "y" : 67,  "z" : -2, "border" : "TOSMAG1.bmp",  "area" : "TZSMAG1.bmp" },
-				"mageGuild2":     { "animation" : "TBSTMAG2.def", "x" : 473, "y" : 37,  "z" : -2, "border" : "TOSMAG2.bmp",  "area" : "TZSMAG2.bmp" },
-				"mageGuild3":     { "animation" : "TBSTMAG3.def", "x" : 473, "y" : 1,   "z" : -2, "border" : "TOSMAG3.bmp",  "area" : "TZSMAG3.bmp" },
-				"tavern":         { "animation" : "TBSTTVRN.def", "x" : 170, "y" : 280, "z" : 2,  "border" : "TOSTAV.bmp",   "area" : "TZSTAV.bmp" },
-				"fort":           { "animation" : "TBSTCSTL.def", "x" : 402, "y" : 148, "z" : -1, "border" : "TOSCA1.bmp",   "area" : "TZSCA1.bmp" },
-				"citadel":        { "animation" : "TBSTCAS2.def", "x" : 402, "y" : 114, "z" : -1, "border" : "TOSCA2.bmp",   "area" : "TZSCA2.bmp" },
-				"castle":         { "animation" : "TBSTCAS3.def", "x" : 402, "y" : 114, "z" : -1, "border" : "TOSCA3.bmp",   "area" : "TZSCA3.bmp" },
-				"villageHall":    { "animation" : "TBSTHALL.def", "x" : 0,   "y" : 259, "border" : "TOSHAL1A.bmp", "area" : "TZSHAL1A.bmp" },
-				"townHall":       { "animation" : "TBSTHAL2.def", "x" : 0,   "y" : 225, "border" : "TOSHAL2A.bmp", "area" : "TZSHAL2A.bmp" },
-				"cityHall":       { "animation" : "TBSTHAL3.def", "x" : 0,   "y" : 201, "border" : "TOSHAL3A.bmp", "area" : "TZSHAL3A.bmp" },
-				"capitol":        { "animation" : "TBSTHAL4.def", "x" : 0,   "y" : 148, "border" : "TOSHAL4A.bmp", "area" : "TZSHAL4A.bmp" },
-				"marketplace":    { "animation" : "TBSTMARK.def", "x" : 397, "y" : 308, "z" : 1,  "border" : "TOSMRK1.bmp",  "area" : "TZSMRK1.bmp" },
-				"resourceSilo":   { "animation" : "TBSTSILO.def", "x" : 458, "y" : 248, "z" : 2,  "border" : "TOSMRK2.bmp",  "area" : "TZSMRK2.bmp" },
-				"blacksmith":     { "animation" : "TBSTBLAK.def", "x" : 660, "y" : 286, "border" : "TOSBLK1.bmp",  "area" : "TZSBLK1.bmp" },
-				"special1":       { "animation" : "TBSTSPEC.def", "x" : 550, "y" : 229, "border" : "TOSCA1EA.bmp", "area" : "TZSCA1EA.bmp" },
-				"horde1":         { "animation" : "TBSTHRD1.def", "x" : 373, "y" : 239, "border" : "TOSGOB1H.bmp", "area" : "TZSGOB1H.bmp", "hidden" : true },
-				"horde1Upgr":     { "animation" : "TBSTHRD2.def", "x" : 373, "y" : 220, "border" : "TOSGOB2H.bmp", "area" : "TZSGOB2H.bmp", "hidden" : true, "builds" : "horde1" },
-				"special2":       { "animation" : "TBSTEXT0.def", "x" : 473, "y" : 282, "z" : 3,  "border" : "TOSMRK1C.bmp", "area" : "TZSMRK1C.bmp" },
-				"special3":       { "animation" : "TBSTEXT1.def", "x" : 617, "y" : 286, "z" : 1,  "border" : "TOSBLK2.bmp",  "area" : "TZSBLK2.bmp" },
-				"special4":       { "animation" : "TBSTEXT2.def", "x" : 313, "y" : 13,  "z" : -1, "border" : "TOSVAH.bmp",   "area" : "TZSVAH.bmp" },
-				"grail":          { "animation" : "TBSTHOLY.def", "x" : 321, "y" : 105, "z" : 3,  "border" : "TOSHOLYA.bmp", "area" : "TZSHOLYA.bmp" },
-				"dwellingLvl1":   { "animation" : "TBSTDW_0.def", "x" : 373, "y" : 239, "border" : "TOSGOB1.bmp",  "area" : "TZSGOB1.bmp" },
-				"dwellingLvl2":   { "animation" : "TBSTDW_1.def", "x" : 266, "y" : 246, "z" : 1,  "border" : "TOSWOL1.bmp",  "area" : "TZSWOL1.bmp" },
-				"dwellingLvl3":   { "animation" : "TBSTDW_2.def", "x" : 566, "y" : 232, "z" : 2,  "border" : "TOSORC1.bmp",  "area" : "TZSORC1.bmp" },
-				"dwellingLvl4":   { "animation" : "TBSTDW_3.def", "x" : 197, "y" : 204, "border" : "TOSOGR1.bmp",  "area" : "TZSOGR1.bmp" },
-				"dwellingLvl5":   { "animation" : "TBSTDW_4.def", "x" : 137, "y" : 30,  "z" : -1, "border" : "TOSROC1.bmp",  "area" : "TZSROC1.bmp" },
-				"dwellingLvl6":   { "animation" : "TBSTDW_5.def", "x" : 622, "y" : 160, "z" : -2, "border" : "TOSCYC1.bmp",  "area" : "TZSCYC1.bmp" },
-				"dwellingLvl7":   { "animation" : "TBSTDW_6.def", "x" : 604, "y" : 0,   "border" : "TOSBEH1A.bmp", "area" : "TZSBEH1A.bmp" },
-				"dwellingUpLvl1": { "animation" : "TBSTUP_0.def", "x" : 373, "y" : 220, "border" : "TOSGOB2.bmp",  "area" : "TZSGOB2.bmp" },
-				"dwellingUpLvl2": { "animation" : "TBSTUP_1.def", "x" : 266, "y" : 225, "z" : 1,  "border" : "TOSWOL2.bmp",  "area" : "TZSWOL2.bmp" },
-				"dwellingUpLvl3": { "animation" : "TBSTUP_2.def", "x" : 566, "y" : 158, "z" : 2,  "border" : "TOSORC2.bmp",  "area" : "TZSORC2.bmp" },
-				"dwellingUpLvl4": { "animation" : "TBSTUP_3.def", "x" : 197, "y" : 137, "border" : "TOSOGR2.bmp",  "area" : "TZSOGR2.bmp" },
-				"dwellingUpLvl5": { "animation" : "TBSTUP_4.def", "x" : 129, "y" : 15,  "z" : -1, "border" : "TOSROC2.bmp",  "area" : "TZSROC2.bmp" },
-				"dwellingUpLvl6": { "animation" : "TBSTUP_5.def", "x" : 616, "y" : 93,  "z" : -2, "border" : "TOSCYC2A.bmp", "area" : "TZSCYC2A.bmp" },
-				"dwellingUpLvl7": { "animation" : "TBSTUP_6.def", "x" : 604, "y" : 0,   "border" : "TOSBEH2A.bmp", "area" : "TZSBEH2A.bmp" }
+				"mageGuild1":     { "animation" : "TBSTMAGE.def", "x" : 473, "y" : 67,  "z" : -2, "campaignBonus" : "BoSmage1.pcx", "border" : "TOSMAG1.bmp",  "area" : "TZSMAG1.bmp" },
+				"mageGuild2":     { "animation" : "TBSTMAG2.def", "x" : 473, "y" : 37,  "z" : -2, "campaignBonus" : "BoSmage2.pcx", "border" : "TOSMAG2.bmp",  "area" : "TZSMAG2.bmp" },
+				"mageGuild3":     { "animation" : "TBSTMAG3.def", "x" : 473, "y" : 1,   "z" : -2, "campaignBonus" : "BoSmage3.pcx", "border" : "TOSMAG3.bmp",  "area" : "TZSMAG3.bmp" },
+				"tavern":         { "animation" : "TBSTTVRN.def", "x" : 170, "y" : 280, "z" : 2,  "campaignBonus" : "BoStav1.pcx",  "border" : "TOSTAV.bmp",   "area" : "TZSTAV.bmp" },
+				"fort":           { "animation" : "TBSTCSTL.def", "x" : 402, "y" : 148, "z" : -1, "campaignBonus" : "BoScas1.pcx",  "border" : "TOSCA1.bmp",   "area" : "TZSCA1.bmp" },
+				"citadel":        { "animation" : "TBSTCAS2.def", "x" : 402, "y" : 114, "z" : -1, "campaignBonus" : "BoScas2.pcx",  "border" : "TOSCA2.bmp",   "area" : "TZSCA2.bmp" },
+				"castle":         { "animation" : "TBSTCAS3.def", "x" : 402, "y" : 114, "z" : -1, "campaignBonus" : "BoScas3.pcx",  "border" : "TOSCA3.bmp",   "area" : "TZSCA3.bmp" },
+				"villageHall":    { "animation" : "TBSTHALL.def", "x" : 0,   "y" : 259,           "campaignBonus" : "BoShall1.pcx", "border" : "TOSHAL1A.bmp", "area" : "TZSHAL1A.bmp" },
+				"townHall":       { "animation" : "TBSTHAL2.def", "x" : 0,   "y" : 225,           "campaignBonus" : "BoShall2.pcx", "border" : "TOSHAL2A.bmp", "area" : "TZSHAL2A.bmp" },
+				"cityHall":       { "animation" : "TBSTHAL3.def", "x" : 0,   "y" : 201,           "campaignBonus" : "BoShall3.pcx", "border" : "TOSHAL3A.bmp", "area" : "TZSHAL3A.bmp" },
+				"capitol":        { "animation" : "TBSTHAL4.def", "x" : 0,   "y" : 148,           "campaignBonus" : "BoShall4.pcx", "border" : "TOSHAL4A.bmp", "area" : "TZSHAL4A.bmp" },
+				"marketplace":    { "animation" : "TBSTMARK.def", "x" : 397, "y" : 308, "z" : 1,  "campaignBonus" : "BoSmrk1.pcx",  "border" : "TOSMRK1.bmp",  "area" : "TZSMRK1.bmp" },
+				"resourceSilo":   { "animation" : "TBSTSILO.def", "x" : 458, "y" : 248, "z" : 2,  "campaignBonus" : "BoSmrk2.pcx",  "border" : "TOSMRK2.bmp",  "area" : "TZSMRK2.bmp" },
+				"blacksmith":     { "animation" : "TBSTBLAK.def", "x" : 660, "y" : 286,           "campaignBonus" : "BoSblak1.pcx", "border" : "TOSBLK1.bmp",  "area" : "TZSBLK1.bmp" },
+				"special1":       { "animation" : "TBSTSPEC.def", "x" : 550, "y" : 229,           "campaignBonus" : "BoSescap.pcx", "border" : "TOSCA1EA.bmp", "area" : "TZSCA1EA.bmp" },
+				"horde1":         { "animation" : "TBSTHRD1.def", "x" : 373, "y" : 239,           "campaignBonus" : "BoSgob1h.pcx", "border" : "TOSGOB1H.bmp", "area" : "TZSGOB1H.bmp", "hidden" : true },
+				"horde1Upgr":     { "animation" : "TBSTHRD2.def", "x" : 373, "y" : 220,           "campaignBonus" : "BoSgob2h.pcx", "border" : "TOSGOB2H.bmp", "area" : "TZSGOB2H.bmp", "hidden" : true, "builds" : "horde1" },
+				"special2":       { "animation" : "TBSTEXT0.def", "x" : 473, "y" : 282, "z" : 3,  "campaignBonus" : "BoSmrk1c.pcx", "border" : "TOSMRK1C.bmp", "area" : "TZSMRK1C.bmp" },
+				"special3":       { "animation" : "TBSTEXT1.def", "x" : 617, "y" : 286, "z" : 1,  "campaignBonus" : "BoSblak2.pcx", "border" : "TOSBLK2.bmp",  "area" : "TZSBLK2.bmp" },
+				"special4":       { "animation" : "TBSTEXT2.def", "x" : 313, "y" : 13,  "z" : -1, "campaignBonus" : "BoSvahal.pcx", "border" : "TOSVAH.bmp",   "area" : "TZSVAH.bmp" },
+				"grail":          { "animation" : "TBSTHOLY.def", "x" : 321, "y" : 105, "z" : 3,  "campaignBonus" : "BoSholy.pcx",  "border" : "TOSHOLYA.bmp", "area" : "TZSHOLYA.bmp" },
+				"dwellingLvl1":   { "animation" : "TBSTDW_0.def", "x" : 373, "y" : 239,           "campaignBonus" : "BoSgob1.pcx",  "border" : "TOSGOB1.bmp",  "area" : "TZSGOB1.bmp" },
+				"dwellingLvl2":   { "animation" : "TBSTDW_1.def", "x" : 266, "y" : 246, "z" : 1,  "campaignBonus" : "BoSwolf1.pcx", "border" : "TOSWOL1.bmp",  "area" : "TZSWOL1.bmp" },
+				"dwellingLvl3":   { "animation" : "TBSTDW_2.def", "x" : 566, "y" : 232, "z" : 2,  "campaignBonus" : "BoSorc1.pcx",  "border" : "TOSORC1.bmp",  "area" : "TZSORC1.bmp" },
+				"dwellingLvl4":   { "animation" : "TBSTDW_3.def", "x" : 197, "y" : 204,           "campaignBonus" : "BoSogre1.pcx", "border" : "TOSOGR1.bmp",  "area" : "TZSOGR1.bmp" },
+				"dwellingLvl5":   { "animation" : "TBSTDW_4.def", "x" : 137, "y" : 30,  "z" : -1, "campaignBonus" : "BoSroc1.pcx",  "border" : "TOSROC1.bmp",  "area" : "TZSROC1.bmp" },
+				"dwellingLvl6":   { "animation" : "TBSTDW_5.def", "x" : 622, "y" : 160, "z" : -2, "campaignBonus" : "BoScyc1.pcx",  "border" : "TOSCYC1.bmp",  "area" : "TZSCYC1.bmp" },
+				"dwellingLvl7":   { "animation" : "TBSTDW_6.def", "x" : 604, "y" : 0,             "campaignBonus" : "BoSbeh1.pcx",  "border" : "TOSBEH1A.bmp", "area" : "TZSBEH1A.bmp" },
+				"dwellingUpLvl1": { "animation" : "TBSTUP_0.def", "x" : 373, "y" : 220,           "campaignBonus" : "BoSgob2.pcx",  "border" : "TOSGOB2.bmp",  "area" : "TZSGOB2.bmp" },
+				"dwellingUpLvl2": { "animation" : "TBSTUP_1.def", "x" : 266, "y" : 225, "z" : 1,  "campaignBonus" : "BoSwolf2.pcx", "border" : "TOSWOL2.bmp",  "area" : "TZSWOL2.bmp" },
+				"dwellingUpLvl3": { "animation" : "TBSTUP_2.def", "x" : 566, "y" : 158, "z" : 2,  "campaignBonus" : "BoSorc2.pcx",  "border" : "TOSORC2.bmp",  "area" : "TZSORC2.bmp" },
+				"dwellingUpLvl4": { "animation" : "TBSTUP_3.def", "x" : 197, "y" : 137,           "campaignBonus" : "BoSogre2.pcx", "border" : "TOSOGR2.bmp",  "area" : "TZSOGR2.bmp" },
+				"dwellingUpLvl5": { "animation" : "TBSTUP_4.def", "x" : 129, "y" : 15,  "z" : -1, "campaignBonus" : "BoSroc2.pcx",  "border" : "TOSROC2.bmp",  "area" : "TZSROC2.bmp" },
+				"dwellingUpLvl6": { "animation" : "TBSTUP_5.def", "x" : 616, "y" : 93,  "z" : -2, "campaignBonus" : "BoScyc2.pcx",  "border" : "TOSCYC2A.bmp", "area" : "TZSCYC2A.bmp" },
+				"dwellingUpLvl7": { "animation" : "TBSTUP_6.def", "x" : 604, "y" : 0,             "campaignBonus" : "BoSbeh2.pcx",  "border" : "TOSBEH2A.bmp", "area" : "TZSBEH2A.bmp" }
 			},
 
 			"musicTheme" : [ "music/Stronghold" ],

+ 37 - 37
config/factions/tower.json

@@ -79,43 +79,43 @@
 			},
 			"structures" :
 			{
-				"mageGuild1":     { "animation" : "TBTWMAGE.def", "x" : 597, "y" : 82,  "z" : -1, "border" : "TOTGLD1.bmp",  "area" : "TZTGLD1.bmp" },
-				"mageGuild2":     { "animation" : "TBTWMAG2.def", "x" : 593, "y" : 65,  "z" : -1, "border" : "TOTGLD2.bmp",  "area" : "TZTGLD2.bmp" },
-				"mageGuild3":     { "animation" : "TBTWMAG3.def", "x" : 593, "y" : 48,  "z" : -1, "border" : "TOTGLD3.bmp",  "area" : "TZTGLD3.bmp" },
-				"mageGuild4":     { "animation" : "TBTWMAG4.def", "x" : 593, "y" : 31,  "z" : -1, "border" : "TOTGLD4.bmp",  "area" : "TZTGLD4.bmp" },
-				"mageGuild5":     { "animation" : "TBTWMAG5.def", "x" : 593, "y" : 14,  "z" : -1, "border" : "TOTGLD5.bmp",  "area" : "TZTGLD5.bmp" },
-				"tavern":         { "animation" : "TBTWTVRN.def", "x" : 375, "y" : 278, "z" : 1, "border" : "TOTTAV.bmp",   "area" : "TZTTAV.bmp" },
-				"fort":           { "animation" : "TBTWCSTL.def", "x" : 304, "y" : 0,   "z" : -1, "border" : "TOTCAS1.bmp",  "area" : "TZTCAS1.bmp" },
-				"citadel":        { "animation" : "TBTWCAS2.def", "x" : 301, "y" : 0,   "z" : -1, "border" : "TOTCAS2.bmp",  "area" : "TZTCAS2.bmp" },
-				"castle":         { "animation" : "TBTWCAS3.def", "x" : 301, "y" : 0,   "z" : -1, "border" : "TOTCAS3.bmp",  "area" : "TZTCAS3.bmp" },
-				"villageHall":    { "animation" : "TBTWHALL.def", "x" : 0,   "y" : 259, "z" : 2,  "border" : "TOTHAL1.bmp",  "area" : "TZTHAL1.bmp" },
-				"townHall":       { "animation" : "TBTWHAL2.def", "x" : 0,   "y" : 220, "z" : 2,  "border" : "TOTHAL2.bmp",  "area" : "TZTHAL2.bmp" },
-				"cityHall":       { "animation" : "TBTWHAL3.def", "x" : 0,   "y" : 82,  "z" : 2,  "border" : "TOTHAL3.bmp",  "area" : "TZTHAL3.bmp" },
-				"capitol":        { "animation" : "TBTWHAL4.def", "x" : 0,   "y" : 82,  "z" : 2,  "border" : "TOTHAL4.bmp",  "area" : "TZTHAL4.bmp" },
-				"marketplace":    { "animation" : "TBTWMARK.def", "x" : 614, "y" : 292, "border" : "TOTMRK.bmp",   "area" : "TZTMRK.bmp" },
-				"resourceSilo":   { "animation" : "TBTWSILO.def", "x" : 763, "y" : 214, "z" : 3, "border" : "TOTMRKS.bmp",  "area" : "TZTMRKS.bmp" },
-				"blacksmith":     { "animation" : "TBTWBLAK.def", "x" : 478, "y" : 211, "border" : "TOTBLKA.bmp",  "area" : "TZTBLKA.bmp" },
-				"special1":       { "animation" : "TBTWSPEC.def", "x" : 674, "y" : 276, "z" : 2, "border" : "TOTMRKA.bmp",  "area" : "TZTMRKA.bmp" },
-				"horde1":         { "animation" : "TBTWHRD1.def", "x" : 0,   "y" : 47,  "border" : "TOTGAR1H.bmp", "area" : "TZTGAR1H.bmp", "hidden" : true },
-				"horde1Upgr":     { "animation" : "TBTWHRD2.def", "x" : 0,   "y" : 28,  "border" : "TOTGAR2H.bmp", "area" : "TZTGAR2H.bmp", "hidden" : true, "builds" : "horde1" },
-				"special2":       { "animation" : "TBTWEXT0.def", "x" : 409, "y" : 82,  "border" : "TOTCASW.bmp",  "area" : "TZTCASW.bmp" },
-				"special3":       { "animation" : "TBTWEXT1.def", "x" : 702, "y" : 115, "border" : "TOTGLDL.bmp",  "area" : "TZTGLDL.bmp" },
-				"special4":       { "animation" : "TBTWEXT2.def", "x" : 592, "y" : 189, "z" : 1,  "border" : "TOTGLDW.bmp",  "area" : "TZTGLDW.bmp" },
-				"grail":          { "animation" : "TBTWHOLY.def", "x" : 237, "y" : 14,  "z" : -2, "border" : "TOTHOLYA.bmp", "area" : "TZTHOLYA.bmp" },
-				"dwellingLvl1":   { "animation" : "TBTWDW_0.def", "x" : 453, "y" : 221, "z" : 1,  "border" : "TOTGRM1A.bmp", "area" : "TZTGRM1A.bmp" },
-				"dwellingLvl2":   { "animation" : "TBTWDW_1.def", "x" : 4,   "y" : 46,  "border" : "TOTGAR1.bmp",  "area" : "TZTGAR1.bmp" },
-				"dwellingLvl3":   { "animation" : "TBTWDW_2.def", "x" : 209, "y" : 177, "z" : 1,  "border" : "TOTGOL1A.bmp", "area" : "TZTGOL1A.bmp" },
-				"dwellingLvl4":   { "animation" : "TBTWDW_3.def", "x" : 613, "y" : 95,  "border" : "TOTMAG1.bmp",  "area" : "TZTMAG1.bmp" },
-				"dwellingLvl5":   { "animation" : "TBTWDW_4.def", "x" : 511, "y" : 75,  "border" : "TOTGEN1.bmp",  "area" : "TZTGEN1.bmp" },
-				"dwellingLvl6":   { "animation" : "TBTWDW_5.def", "x" : 681, "y" : 208, "z" : 2,  "border" : "TOTNAG1.bmp",  "area" : "TZTNAG1.bmp" },
-				"dwellingLvl7":   { "animation" : "TBTWDW_6.def", "x" : 75,  "y" : 144, "z" : -1, "border" : "TOTTIT1.bmp",  "area" : "TZTTIT1.bmp" },
-				"dwellingUpLvl1": { "animation" : "TBTWUP_0.def", "x" : 446, "y" : 221, "z" : 1,  "border" : "TOTGRM2A.bmp", "area" : "TZTGRM2A.bmp" },
-				"dwellingUpLvl2": { "animation" : "TBTWUP_1.def", "x" : 4,   "y" : 28,  "border" : "TOTGAR2.bmp",  "area" : "TZTGAR2.bmp" },
-				"dwellingUpLvl3": { "animation" : "TBTWUP_2.def", "x" : 209, "y" : 177, "z" : 1,  "border" : "TOTGOL2A.bmp", "area" : "TZTGOL2A.bmp" },
-				"dwellingUpLvl4": { "animation" : "TBTWUP_3.def", "x" : 613, "y" : 74,  "border" : "TOTMAG2.bmp",  "area" : "TZTMAG2.bmp" },
-				"dwellingUpLvl5": { "animation" : "TBTWUP_4.def", "x" : 511, "y" : 8,   "border" : "TOTGEN2.bmp",  "area" : "TZTGEN2.bmp" },
-				"dwellingUpLvl6": { "animation" : "TBTWUP_5.def", "x" : 681, "y" : 157, "z" : 2,  "border" : "TOTNAG2.bmp",  "area" : "TZTNAG2.bmp" },
-				"dwellingUpLvl7": { "animation" : "TBTWUP_6.def", "x" : 75,  "y" : 91,  "z" : -1, "border" : "TOTTIT2.bmp",  "area" : "TZTTIT2.bmp" }
+				"mageGuild1":     { "animation" : "TBTWMAGE.def", "x" : 597, "y" : 82,  "z" : -1, "campaignBonus" : "BoTGld1.pcx",  "border" : "TOTGLD1.bmp",  "area" : "TZTGLD1.bmp" },
+				"mageGuild2":     { "animation" : "TBTWMAG2.def", "x" : 593, "y" : 65,  "z" : -1, "campaignBonus" : "BoTGld2.pcx",  "border" : "TOTGLD2.bmp",  "area" : "TZTGLD2.bmp" },
+				"mageGuild3":     { "animation" : "TBTWMAG3.def", "x" : 593, "y" : 48,  "z" : -1, "campaignBonus" : "BoTGld3.pcx",  "border" : "TOTGLD3.bmp",  "area" : "TZTGLD3.bmp" },
+				"mageGuild4":     { "animation" : "TBTWMAG4.def", "x" : 593, "y" : 31,  "z" : -1, "campaignBonus" : "BoTGld4.pcx",  "border" : "TOTGLD4.bmp",  "area" : "TZTGLD4.bmp" },
+				"mageGuild5":     { "animation" : "TBTWMAG5.def", "x" : 593, "y" : 14,  "z" : -1, "campaignBonus" : "BoTGld5.pcx",  "border" : "TOTGLD5.bmp",  "area" : "TZTGLD5.bmp" },
+				"tavern":         { "animation" : "TBTWTVRN.def", "x" : 375, "y" : 278, "z" : 1,  "campaignBonus" : "BoTTav.pcx",   "border" : "TOTTAV.bmp",   "area" : "TZTTAV.bmp" },
+				"fort":           { "animation" : "TBTWCSTL.def", "x" : 304, "y" : 0,   "z" : -1, "campaignBonus" : "BoTCast1.pcx", "border" : "TOTCAS1.bmp",  "area" : "TZTCAS1.bmp" },
+				"citadel":        { "animation" : "TBTWCAS2.def", "x" : 301, "y" : 0,   "z" : -1, "campaignBonus" : "BoTCast2.pcx", "border" : "TOTCAS2.bmp",  "area" : "TZTCAS2.bmp" },
+				"castle":         { "animation" : "TBTWCAS3.def", "x" : 301, "y" : 0,   "z" : -1, "campaignBonus" : "BoTCast3.pcx", "border" : "TOTCAS3.bmp",  "area" : "TZTCAS3.bmp" },
+				"villageHall":    { "animation" : "TBTWHALL.def", "x" : 0,   "y" : 259, "z" : 2,  "campaignBonus" : "BoTHall1.pcx", "border" : "TOTHAL1.bmp",  "area" : "TZTHAL1.bmp" },
+				"townHall":       { "animation" : "TBTWHAL2.def", "x" : 0,   "y" : 220, "z" : 2,  "campaignBonus" : "BoTHall2.pcx", "border" : "TOTHAL2.bmp",  "area" : "TZTHAL2.bmp" },
+				"cityHall":       { "animation" : "TBTWHAL3.def", "x" : 0,   "y" : 82,  "z" : 2,  "campaignBonus" : "BoTHall3.pcx", "border" : "TOTHAL3.bmp",  "area" : "TZTHAL3.bmp" },
+				"capitol":        { "animation" : "TBTWHAL4.def", "x" : 0,   "y" : 82,  "z" : 2,  "campaignBonus" : "BoTHall4.pcx", "border" : "TOTHAL4.bmp",  "area" : "TZTHAL4.bmp" },
+				"marketplace":    { "animation" : "TBTWMARK.def", "x" : 614, "y" : 292,           "campaignBonus" : "BoTMark.pcx",  "border" : "TOTMRK.bmp",   "area" : "TZTMRK.bmp" },
+				"resourceSilo":   { "animation" : "TBTWSILO.def", "x" : 763, "y" : 214, "z" : 3,  "campaignBonus" : "BoTMarkS.pcx", "border" : "TOTMRKS.bmp",  "area" : "TZTMRKS.bmp" },
+				"blacksmith":     { "animation" : "TBTWBLAK.def", "x" : 478, "y" : 211,           "campaignBonus" : "BoTBlack.pcx", "border" : "TOTBLKA.bmp",  "area" : "TZTBLKA.bmp" },
+				"special1":       { "animation" : "TBTWSPEC.def", "x" : 674, "y" : 276, "z" : 2,  "campaignBonus" : "BoTMarkA.pcx", "border" : "TOTMRKA.bmp",  "area" : "TZTMRKA.bmp" },
+				"horde1":         { "animation" : "TBTWHRD1.def", "x" : 0,   "y" : 47,            "campaignBonus" : "BoTGa1H.pcx",  "border" : "TOTGAR1H.bmp", "area" : "TZTGAR1H.bmp", "hidden" : true },
+				"horde1Upgr":     { "animation" : "TBTWHRD2.def", "x" : 0,   "y" : 28,            "campaignBonus" : "BoTGa2h.pcx",  "border" : "TOTGAR2H.bmp", "area" : "TZTGAR2H.bmp", "hidden" : true, "builds" : "horde1" },
+				"special2":       { "animation" : "TBTWEXT0.def", "x" : 409, "y" : 82,            "campaignBonus" : "BoTCastW.pcx", "border" : "TOTCASW.bmp",  "area" : "TZTCASW.bmp" },
+				"special3":       { "animation" : "TBTWEXT1.def", "x" : 702, "y" : 115,           "campaignBonus" : "BoTGldL.pcx",  "border" : "TOTGLDL.bmp",  "area" : "TZTGLDL.bmp" },
+				"special4":       { "animation" : "TBTWEXT2.def", "x" : 592, "y" : 189, "z" : 1,  "campaignBonus" : "BoTGldW.pcx",  "border" : "TOTGLDW.bmp",  "area" : "TZTGLDW.bmp" },
+				"grail":          { "animation" : "TBTWHOLY.def", "x" : 237, "y" : 14,  "z" : -2, "campaignBonus" : "BoTGrail.pcx", "border" : "TOTHOLYA.bmp", "area" : "TZTHOLYA.bmp" },
+				"dwellingLvl1":   { "animation" : "TBTWDW_0.def", "x" : 453, "y" : 221, "z" : 1,  "campaignBonus" : "BoTGrem1.pcx", "border" : "TOTGRM1A.bmp", "area" : "TZTGRM1A.bmp" },
+				"dwellingLvl2":   { "animation" : "TBTWDW_1.def", "x" : 4,   "y" : 46,            "campaignBonus" : "BoTGar1.pcx",  "border" : "TOTGAR1.bmp",  "area" : "TZTGAR1.bmp" },
+				"dwellingLvl3":   { "animation" : "TBTWDW_2.def", "x" : 209, "y" : 177, "z" : 1,  "campaignBonus" : "BoTGolm1.pcx", "border" : "TOTGOL1A.bmp", "area" : "TZTGOL1A.bmp" },
+				"dwellingLvl4":   { "animation" : "TBTWDW_3.def", "x" : 613, "y" : 95,            "campaignBonus" : "BoTMag1.pcx",  "border" : "TOTMAG1.bmp",  "area" : "TZTMAG1.bmp" },
+				"dwellingLvl5":   { "animation" : "TBTWDW_4.def", "x" : 511, "y" : 75,            "campaignBonus" : "BoTGen1.pcx",  "border" : "TOTGEN1.bmp",  "area" : "TZTGEN1.bmp" },
+				"dwellingLvl6":   { "animation" : "TBTWDW_5.def", "x" : 681, "y" : 208, "z" : 2,  "campaignBonus" : "BoTNaga1.pcx", "border" : "TOTNAG1.bmp",  "area" : "TZTNAG1.bmp" },
+				"dwellingLvl7":   { "animation" : "TBTWDW_6.def", "x" : 75,  "y" : 144, "z" : -1, "campaignBonus" : "BoTTit1.pcx",  "border" : "TOTTIT1.bmp",  "area" : "TZTTIT1.bmp" },
+				"dwellingUpLvl1": { "animation" : "TBTWUP_0.def", "x" : 446, "y" : 221, "z" : 1,  "campaignBonus" : "BoTGrem2.pcx", "border" : "TOTGRM2A.bmp", "area" : "TZTGRM2A.bmp" },
+				"dwellingUpLvl2": { "animation" : "TBTWUP_1.def", "x" : 4,   "y" : 28,            "campaignBonus" : "BoTGar2.pcx",  "border" : "TOTGAR2.bmp",  "area" : "TZTGAR2.bmp" },
+				"dwellingUpLvl3": { "animation" : "TBTWUP_2.def", "x" : 209, "y" : 177, "z" : 1,  "campaignBonus" : "BoTGolm2.pcx", "border" : "TOTGOL2A.bmp", "area" : "TZTGOL2A.bmp" },
+				"dwellingUpLvl4": { "animation" : "TBTWUP_3.def", "x" : 613, "y" : 74,            "campaignBonus" : "BoTMag2.pcx",  "border" : "TOTMAG2.bmp",  "area" : "TZTMAG2.bmp" },
+				"dwellingUpLvl5": { "animation" : "TBTWUP_4.def", "x" : 511, "y" : 8,             "campaignBonus" : "BoTGen2.pcx",  "border" : "TOTGEN2.bmp",  "area" : "TZTGEN2.bmp" },
+				"dwellingUpLvl6": { "animation" : "TBTWUP_5.def", "x" : 681, "y" : 157, "z" : 2,  "campaignBonus" : "BoTNaga2.pcx", "border" : "TOTNAG2.bmp",  "area" : "TZTNAG2.bmp" },
+				"dwellingUpLvl7": { "animation" : "TBTWUP_6.def", "x" : 75,  "y" : 91,  "z" : -1, "campaignBonus" : "BoTTit2.pcx",  "border" : "TOTTIT2.bmp",  "area" : "TZTTIT2.bmp" }
 			},
 
 			"musicTheme" : [ "music/TowerTown" ],

+ 194 - 5
config/gameConfig.json

@@ -127,6 +127,11 @@
 	[
 		"config/obstacles.json"
 	],
+	"campaignRegions" :
+	[
+		"config/campaignRegions.json"
+	],
+	
 	
 	"settings":
 	{
@@ -149,7 +154,6 @@
 			"restorationOfErathia" : { 
 				"supported" : true,
 				"iconIndex" : 0,
-
 				"buildingsCommon": {
 					"townHall"       : 0,
 					"cityHall"       : 1,
@@ -194,7 +198,6 @@
 					"dwellingLvl7"   : 39,
 					"dwellingUpLvl7" : 40
 				},
-
 				"buildings" : {
 					"castle" : {
 						"special1" : 18, // lighthouse
@@ -240,10 +243,88 @@
 						"special3" : 18  // glyphsOfFear
 					}
 				},
-
 				"portraits" : {
 					"catherine" : 128, // In "RoE" Catherine only has portrait
 					"portraitGeneralKendal" : 129
+				},
+				"campaignRegions" : {
+					
+					"good1" : 1,    // Long Live the Queen
+					"good2" : 2,    // Liberation
+					"good3" : 3,    // Song for the Father
+					"evil1" : 4,    // Dungeons and devils
+					"evil2" : 5,    // Long Live the King
+					"neutral1" : 6, // Spoils of War
+					"secret1" : 7   // Seeds Of Discontent 
+				},
+				"campaignMusic" : {
+					"CampainMusic01" : 0,
+					"CampainMusic02" : 1,
+					"CampainMusic03" : 2,
+					"CampainMusic04" : 3,
+					"CampainMusic05" : 4,
+					"CampainMusic06" : 5,
+					"CampainMusic07" : 6,
+					"CampainMusic08" : 7,
+					"CampainMusic09" : 8,
+					"AiTheme0" : 9,
+					"AiTheme1" : 10,
+					"AiTheme2" : 11,
+					"Combat01" : 12,
+					"Combat02" : 13,
+					"Combat03" : 14,
+					"Combat04" : 15,
+					"CstleTown" : 16,
+					"TowerTown" : 17,
+					"Rampart" : 18,
+					"InfernoTown" : 19,
+					"NecroTown" : 20,
+					"Dungeon" : 21,
+					"Stronghold" : 22,
+					"FortressTown" : 23,
+					"ElemTown" : 24,
+					"Dirt" : 25,
+					"Sand" : 26,
+					"Grass" : 27,
+					"Snow" : 28,
+					"Swamp" : 29,
+					"Rough" : 30,
+					"Underground" : 31,
+					"Lava" : 32,
+					"Water" : 33,
+					"GoodTheme" : 34,
+					"NeutralTheme" : 35,
+					"EvilTheme" : 36,
+					"SecretTheme" : 37,
+					"LoopLepr" : 38,
+					"MainMenu" : 39,
+					"Win Scenario" : 40
+				},
+				"campaignVideo" : {
+					"GOOD1A.SMK" : 0,
+					"GOOD1B.SMK" : 1,
+					"GOOD1C.SMK" : 2,
+					"EVIL1A.SMK" : 3,
+					"EVIL1B.SMK" : 4,
+					"EVIL1C.SMK" : 5,
+					"NEUTRALA.SMK" : 6,
+					"NEUTRALB.SMK" : 7,
+					"NEUTRALC.SMK" : 8,
+					"GOOD2A.SMK" : 9,
+					"GOOD2B.SMK" : 10,
+					"GOOD2C.SMK" : 11,
+					"GOOD2D.SMK" : 12,
+					"EVIL2A.SMK" : 13,
+					"EVIL2AP1.SMK" : 14,
+					"EVIL2B.SMK" : 15,
+					"EVIL2C.SMK" : 16,
+					"EVIL2D.SMK" : 17,
+					"GOOD3A.SMK" : 18,
+					"GOOD3B.SMK" : 19,
+					"GOOD3C.SMK" : 20,
+					"SECRETA.SMK" : 21,
+					"SECRETB.SMK" : 22,
+					"SECRETC.SMK" : 23
 				}
 			},
 			"armageddonsBlade" : {
@@ -255,19 +336,69 @@
 						"special2" : 18  // magicUniversity
 					}
 				},
-				
 				"portraits" : {
 					"pasis" : 128,
 					"thunar" : 129,
 					"portraitGeneralKendal" : 156,
 					"portraitYoungCristian" : 157,
 					"portraitOrdwald" : 158
+				},
+				"campaignRegions" : {
+					"dragonSlayer" : 8,
+					"foolhardyWaywardness" : 9,
+					"festivalOfLife" : 10,
+					"dragonsBlood" : 11,
+					"playingWithFire" : 12,
+					"armageddonsBlade" : 13
+				},
+				"campaignMusic" : {
+					"CampainMusic10" : 41,
+					"BladeABCampaign" : 42,
+					"BladeDBCampaign" : 43,
+					"BladeDSCampaign" : 44,
+					"BladeFLCampaign" : 45,
+					"BladeFWCampaign" : 46,
+					"BladePFCampaign" : 47
+				},
+				"campaignVideo" : {
+					"H3ABab1.smk" : 24,
+					"H3ABab2.smk" : 25,
+					"H3ABab3.smk" : 26,
+					"H3ABab4.smk" : 27,
+					"H3ABab5.smk" : 28,
+					"H3ABab6.smk" : 29,
+					"H3ABab7.smk" : 30,
+					"H3ABab8.smk" : 31,
+					"H3ABab9.smk" : 32,
+					"H3ABdb1.smk" : 33,
+					"H3ABdb2.smk" : 34,
+					"H3ABdb3.smk" : 35,
+					"H3ABdb4.smk" : 36,
+					"H3ABdb5.smk" : 37,
+					"H3ABds1.smk" : 38,
+					"H3ABds2.smk" : 39,
+					"H3ABds3.smk" : 40,
+					"H3ABds4.smk" : 41,
+					"H3ABds5.smk" : 42,
+					"H3ABfl1.smk" : 43,
+					"H3ABfl2.smk" : 44,
+					"H3ABfl3.smk" : 45,
+					"H3ABfl4.smk" : 46,
+					"H3ABfl5.smk" : 47,
+					"H3ABfw1.smk" : 48,
+					"H3ABfw2.smk" : 49,
+					"H3ABfw3.smk" : 50,
+					"H3ABfw4.smk" : 51,
+					"H3ABfw5.smk" : 52,
+					"H3ABpf1.smk" : 53,
+					"H3ABpf2.smk" : 54,
+					"H3ABpf3.smk" : 55,
+					"H3ABpf4.smk" : 56
 				}
 			},
 			"shadowOfDeath" : { 
 				"supported" : true,
 				"iconIndex" : 2,
-				
 				"portraits" : {
 					"portraitGeneralKendal" : 156,
 					"portraitYoungCristian" : 157,
@@ -276,6 +407,64 @@
 					"portraitYoungGem" : 160,
 					"portraitYoungSandro" : 161,
 					"portraitYoungYog" : 162
+				},
+				"campaignRegions" : {
+					"hackAndSlash" : 14,
+					"birthOfBarbarian" : 15,
+					"newBeginning" : 16,
+					"elixirOfLife" : 17,
+					"riseOfTheNecromancer" : 18,
+					"unholyAlliance" : 19,
+					"spectreOfPower" : 20
+				},
+				"campaignMusic" : {
+					"CampainMusic11" : 48
+				},
+				"campaignVideo" : {
+			 		"H3x2_BBa.smk" : 57,
+					"H3x2_BBb.smk" : 58,
+					"H3x2_BBc.smk" : 59,
+					"H3x2_BBd.smk" : 60,
+					"H3x2_BBe.smk" : 61,
+					"H3x2_BBf.smk" : 62,
+					"H3x2_Ela.smk" : 63,
+					"H3x2_Elb.smk" : 64,
+					"H3x2_Elc.smk" : 65,
+					"H3x2_Eld.smk" : 66,
+					"H3x2_Ele.smk" : 67,
+					"H3x2_HSa.smk" : 68,
+					"EVIL2C.SMK"   : 69,
+					"H3x2_HSc.smk" : 70,
+					"H3x2_HSd.smk" : 71,
+					"H3x2_HSe.smk" : 72,
+					"H3x2_NBa.smk" : 73,
+					"H3x2_NBb.smk" : 74,
+					"H3x2_Nbc.smk" : 75,
+					"H3x2_Nbd.smk" : 76,
+					"H3x2_Nbe.smk" : 77,
+					"H3x2_RNa.smk" : 78,
+					"H3x2_RNb.smk" : 79,
+					"H3x2_RNc.smk" : 80,
+					"H3x2_RNd.smk" : 81,
+					"H3x2_RNe1.smk": 82,
+					"H3x2_SPa.smk" : 83,
+					"H3x2_SPb.smk" : 84,
+					"H3x2_SPc.smk" : 85,
+					"H3x2_SPd.smk" : 86,
+					"H3x2_SPe.smk" : 87,
+					"H3x2_UAa.smk" : 88,
+					"H3x2_UAb.smk" : 89,
+					"H3x2_UAc.smk" : 90,
+					"H3x2_UAd.smk" : 91,
+					"H3x2_UAe.smk" : 92,
+					"H3x2_UAf.smk" : 93,
+					"H3x2_UAg.smk" : 94,
+					"H3x2_UAh.smk" : 95,
+					"H3x2_UAi.smk" : 96,
+					"H3x2_UAj.smk" : 97,
+					"H3x2_UAk.smk" : 98,
+					"H3x2_UAl.smk" : 99,
+					"H3x2_UAm.smk" : 100 //H3x2_UAm.bik?
 				}
 			},
 			"chronicles" : { 

+ 3 - 3
config/schemas/artifact.json

@@ -91,16 +91,16 @@
 			"type" : "object",
 			"additionalProperties" : false,
 			"description" : "Graphical files associated with the artifact",
-			"required" : [ "image", "map" ],
+			"required" : [ "image", "map", "scenarioBonus" ],
 			"properties" : {
 				"image" : {
 					"type" : "string",
 					"description" : "Base image for this artifact, used for example in hero screen",
 					"format" : "imageFile"
 				},
-				"large" : {
+				"scenarioBonus" : {
 					"type" : "string",
-					"description" : "Large image, used for drag-and-drop and popup messages",
+					"description" : "Image 58x64 in size, for use as campaign scenario starting bonus selection",
 					"format" : "imageFile"
 				},
 				"map" : {

+ 40 - 0
config/schemas/campaignRegion.json

@@ -0,0 +1,40 @@
+{
+	"type" : "object",
+	"$schema" : "http://json-schema.org/draft-04/schema",
+	"title" : "VCMI campaign region format",
+	"description" : "Format used to define campaign regions for h3c maps in VCMI",
+	"required" : [ "prefix", "colorSuffixLength", "desc" ],
+	"additionalProperties" : false,
+	"properties" : {
+		"prefix" :
+		{
+			"type" : "string",
+			"description" : "Prefix for all images from this region"
+		},
+		"colorSuffixLength" :
+		{
+			"type" : "number",
+			"description" : "Number of symbols used to encode color, 1 or 2"
+		},
+		"desc" :
+		{
+			"type" : "array",
+			"description" : "List of regions in this campaign",
+				"items" : {
+				"type" : "object",
+				"additionalProperties" : false,
+				"properties" : {
+					"infix" : {
+						"type" : "string"
+					},
+					"x" : {
+						"type" : "number"
+					},
+					"y" : {
+						"type" : "number"
+					}
+				}
+			}
+		}
+	}
+}

+ 4 - 0
config/schemas/mod.json

@@ -270,6 +270,10 @@
 			"description" : "List of configuration files for creatures",
 			"$ref" : "#/definitions/fileListOrObject"
 		},
+		"campaignRegions" : {
+			"description" : "List of configuration files for campaign regions",
+			"$ref" : "#/definitions/fileListOrObject"
+		},
 		"artifacts" : {
 			"description" : "List of configuration files for artifacts",
 			"$ref" : "#/definitions/fileListOrObject"

+ 6 - 0
config/schemas/skill.json

@@ -16,6 +16,7 @@
 				"images" : {
 					"type" : "object",
 					"description" : "Skill icons of varying size",
+					"required" : [ "small", "medium", "large", "scenarioBonus"],
 					"properties" : {
 						"small" : {
 							"type" : "string",
@@ -31,6 +32,11 @@
 							"type" : "string",
 							"description" : "82x93 skill icon",
 							"format" : "imageFile"
+						},
+						"scenarioBonus" : {
+							"type" : "string",
+							"description" : "58x64 skill icon for use as campaign scenario bonus",
+							"format" : "imageFile"
 						}
 					}
 				},

+ 62 - 4
config/schemas/spell.json

@@ -119,8 +119,67 @@
 			"additionalProperties" : false
 		}
 	},
-	"required" : ["type", "name", "school", "level", "power","gainChance","flags","levels"],
+	"required" : ["type", "name", "school", "level", "power", "flags", "levels"],
 	"additionalProperties" : false,
+	
+	"allOf": [
+		{
+			"if": {
+				"not" : {
+					"properties": {
+						"index" : {
+							"type" : "number"
+						}
+					}
+				},
+				"properties": {
+					"type": {
+						"enum" : ["adventure", "combat"],
+					},
+					
+				}
+			},
+			"then": {
+				"required" : ["school", "gainChance" ],
+				"properties": {
+					"levels": {
+						"required" : ["none", "basic", "advanced", "expert"]
+					},
+					"graphics" : {
+						"required" : ["iconBook", "iconScroll", "iconEffect", "iconImmune", "iconScenarioBonus"]
+					},
+				}
+			}
+		},
+		{
+			"if": {
+				"not" : {
+					"properties": {
+						"index" : {
+							"type" : "number"
+						}
+					}
+				},
+				"properties": {
+					"type": {
+						"const" : "ability"
+					}
+				}
+			},
+			"then": {
+				"required" : ["school", "gainChance" ],
+				"properties": {
+					"levels": {
+						"required" : ["none"]
+					},
+					"graphics" : {
+						"required" : ["iconEffect", "iconImmune"]
+					},
+				}
+			}
+		}
+	],
+	
 	"properties" : {
 		"index" : {
 			"type" : "number",
@@ -265,7 +324,7 @@
 				},
 				"iconEffect" : {
 					"type" : "string",
-					"description" : "Resourse path of icon for spell effects during battle" ,
+					"description" : "Resourse path of icon for spell effects during battle",
 					"format" : "imageFile"
 				},
 				"iconImmune" : {
@@ -275,7 +334,7 @@
 				},
 				"iconScenarioBonus" : {
 					"type" : "string",
-					"description" : "Resourse path of icon for scenario bonus" ,
+					"description" : "Resourse path of 58x64 icon for scenario bonus",
 					"format" : "imageFile"
 				}
 			}
@@ -293,7 +352,6 @@
 		"levels" : {
 			 "type" : "object",
 			 "additionalProperties" : false,
-			 "required" : ["none", "basic", "advanced", "expert"],
 			 "properties" : {
 				"base" : {
 					"type" : "object",

+ 5 - 0
config/schemas/townStructure.json

@@ -29,6 +29,11 @@
 			"description" : "Golden border around building, displayed when building is selected",
 			"format" : "imageFile"
 		},
+		"campaignBonus" : {
+			"type" : "string",
+			"description" : "Town building icon for campaigns",
+			"format" : "imageFile"
+		},
 		"x" : {
 			"type" : "number",
 			"description" : "Position on screen"

+ 2 - 2
docs/modders/Entities_Format/Artifact_Format.md

@@ -46,8 +46,8 @@ In order to make functional artifact you also need:
 		// Base image for this artifact, used for example in hero screen
 		"image": "BigSword.png",
 
-		// Large image, used for drag-and-drop and popup messages
-		"large": "BigSword_large.png",
+		// Large 58x64 image, used for campaign scenario bonus selection
+		"scenarioBonus": "BigSword_large.png",
 
 		//def file for adventure map
 		"map": "BigSword.def"

+ 2 - 0
docs/modders/Entities_Format/Secondary_Skill_Format.md

@@ -83,6 +83,8 @@ level fields become optional if they equal "base" configuration.
 		"medium" : "",
 		// 82x93 skill icon
 		"large" : "",
+		// 58x64 skill icon for campaign scenario bonus
+		"scenarioBonus" : ""
 	}
 }
 ```

+ 10 - 1
docs/modders/Mod_File_Format.md

@@ -119,7 +119,16 @@ These are fields that are present only in local mod.json file
 	],
 	
 	// List of configuration files for skills
-	skills
+	"skills" :
+	[
+		"config/skills.json"
+	],
+
+	// List of configuration files for campaign regions for h3c campaigns
+	"campaignRegions" :
+	[
+		"config/campaignRegions.json"
+	],
 
 	// list of creature configuration files
 	"creatures" :

+ 8 - 3
lib/CMakeLists.txt

@@ -86,8 +86,11 @@ set(lib_MAIN_SRCS
 	callback/CPlayerSpecificInfoCallback.cpp
 	callback/GameRandomizer.cpp
 
+	campaign/CampaignBonus.cpp
 	campaign/CampaignHandler.cpp
 	campaign/CampaignState.cpp
+	campaign/CampaignRegions.cpp
+	campaign/CampaignRegionsHandler.cpp
 
 	constants/EntityIdentifiers.cpp
 
@@ -99,7 +102,6 @@ set(lib_MAIN_SRCS
 	entities/artifact/CArtifactInstance.cpp
 	entities/artifact/CArtifactSet.cpp
 	entities/building/CBuilding.cpp
-	entities/building/CBuildingHandler.cpp
 	entities/faction/CFaction.cpp
 	entities/faction/CTown.cpp
 	entities/faction/CTownHandler.cpp
@@ -171,6 +173,7 @@ set(lib_MAIN_SRCS
 	mapping/MapFormatH3M.cpp
 	mapping/MapReaderH3M.cpp
 	mapping/MapFormatJson.cpp
+	mapping/MapFormatSettings.cpp
 	mapping/ObstacleProxy.cpp
 
 	modding/ActiveModsInSaveList.cpp
@@ -480,9 +483,11 @@ set(lib_MAIN_HEADERS
 	callback/IGameRandomizer.h
 	callback/GameRandomizer.h
 
-
+	campaign/CampaignBonus.h
 	campaign/CampaignConstants.h
 	campaign/CampaignHandler.h
+	campaign/CampaignRegions.h
+	campaign/CampaignRegionsHandler.h
 	campaign/CampaignScenarioPrologEpilog.h
 	campaign/CampaignState.h
 
@@ -503,7 +508,6 @@ set(lib_MAIN_HEADERS
 	entities/artifact/CArtifactSet.h
 	entities/artifact/EArtifactClass.h
 	entities/building/CBuilding.h
-	entities/building/CBuildingHandler.h
 	entities/building/TownFortifications.h
 	entities/faction/CFaction.h
 	entities/faction/CTown.h
@@ -586,6 +590,7 @@ set(lib_MAIN_HEADERS
 	mapping/MapFeaturesH3M.h
 	mapping/MapFormatH3M.h
 	mapping/MapFormat.h
+	mapping/MapFormatSettings.h
 	mapping/MapReaderH3M.h
 	mapping/MapFormatJson.h
 	mapping/ObstacleProxy.h

+ 4 - 0
lib/CSkillHandler.cpp

@@ -249,6 +249,10 @@ std::shared_ptr<CSkill> CSkillHandler::loadFromJson(const std::string & scope, c
 		skillAtLevel.iconSmall = levelNode["images"]["small"].String();
 		skillAtLevel.iconMedium = levelNode["images"]["medium"].String();
 		skillAtLevel.iconLarge = levelNode["images"]["large"].String();
+		if (!levelNode["images"]["scenarioBonus"].isNull())
+			skillAtLevel.scenarioBonus = levelNode["images"]["scenarioBonus"].String();
+		else
+			skillAtLevel.scenarioBonus = skillAtLevel.iconMedium; // MOD COMPATIBILITY fallback for pre-1.7 mods
 	}
 
 	for(const auto & b : json["specialty"].Vector())

+ 1 - 0
lib/CSkillHandler.h

@@ -28,6 +28,7 @@ public:
 		std::string iconSmall;
 		std::string iconMedium;
 		std::string iconLarge;
+		std::string scenarioBonus;
 		std::vector<std::shared_ptr<Bonus>> effects;
 	};
 

+ 5 - 0
lib/GameLibrary.cpp

@@ -26,6 +26,8 @@
 #include "entities/hero/CHeroClassHandler.h"
 #include "entities/hero/CHeroHandler.h"
 #include "texts/CGeneralTextHandler.h"
+#include "campaign/CampaignRegionsHandler.h"
+#include "mapping/MapFormatSettings.h"
 #include "modding/CModHandler.h"
 #include "modding/IdentifierStorage.h"
 #include "modding/CModVersion.h"
@@ -183,6 +185,7 @@ void GameLibrary::initializeLibrary()
 	createHandler(spellh);
 	createHandler(skillh);
 	createHandler(terviewh);
+	createHandler(campaignRegions);
 	createHandler(tplh); //templates need already resolved identifiers (refactor?)
 #if SCRIPTING_ENABLED
 	createHandler(scriptHandler);
@@ -192,6 +195,8 @@ void GameLibrary::initializeLibrary()
 
 	modh->load();
 	modh->afterLoad();
+
+	createHandler(mapFormat);
 }
 
 #if SCRIPTING_ENABLED

+ 4 - 0
lib/GameLibrary.h

@@ -41,6 +41,8 @@ class IGameSettings;
 class GameSettings;
 class CIdentifierStorage;
 class SpellSchoolHandler;
+class MapFormatSettings;
+class CampaignRegionsHandler;
 
 #if SCRIPTING_ENABLED
 namespace scripting
@@ -97,6 +99,8 @@ public:
 	std::unique_ptr<ObstacleHandler> obstacleHandler;
 	std::unique_ptr<GameSettings> settingsHandler;
 	std::unique_ptr<ObstacleSetHandler> biomeHandler;
+	std::unique_ptr<MapFormatSettings> mapFormat;
+	std::unique_ptr<CampaignRegionsHandler> campaignRegions;
 
 #if SCRIPTING_ENABLED
 	std::unique_ptr<scripting::ScriptHandler> scriptHandler;

+ 3 - 2
lib/IHandlerBase.h

@@ -15,7 +15,7 @@ class JsonNode;
 class Entity;
 
 /// base class for all handlers that can be accessed from mod system
-class DLL_LINKAGE IHandlerBase
+class DLL_LINKAGE IHandlerBase : boost::noncopyable
 {
 protected:
 	static std::string getScopeBuiltin();
@@ -44,7 +44,8 @@ public:
 	virtual ~IHandlerBase() = default;
 };
 
-template <class _ObjectID, class _ObjectBase, class _Object, class _ServiceBase> class CHandlerBase : public _ServiceBase, public IHandlerBase
+template <class _ObjectID, class _ObjectBase, class _Object, class _ServiceBase>
+class CHandlerBase : public _ServiceBase, public IHandlerBase
 {
 	const _Object * getObjectImpl(const int32_t index) const
 	{

+ 2 - 15
lib/StartInfo.cpp

@@ -90,22 +90,9 @@ std::string StartInfo::getCampaignName() const
 		return LIBRARY->generaltexth->allTexts[508];
 }
 
-bool StartInfo::isRestorationOfErathiaCampaign() const
+bool StartInfo::restrictedGarrisonsForAI() const
 {
-	constexpr std::array roeCampaigns = {
-		"DATA/GOOD1",
-		"DATA/EVIL1",
-		"DATA/GOOD2",
-		"DATA/NEUTRAL1",
-		"DATA/EVIL2",
-		"DATA/GOOD3",
-		"DATA/SECRET1",
-	};
-
-	if (!campState)
-		return false;
-
-	return vstd::contains(roeCampaigns, campState->getFilename());
+	return campState && campState->restrictedGarrisonsForAI();
 }
 
 void LobbyInfo::verifyStateBeforeStart(bool ignoreNoHuman) const

+ 2 - 2
lib/StartInfo.h

@@ -153,8 +153,8 @@ struct DLL_LINKAGE StartInfo : public Serializeable
 	// TODO: Must be client-side
 	std::string getCampaignName() const;
 
-	/// Controls hardcoded check for handling of garrisons by AI in Restoration of Erathia campaigns to match H3 behavior
-	bool isRestorationOfErathiaCampaign() const;
+	/// Controls check for handling of garrisons by AI in Restoration of Erathia campaigns to match H3 behavior
+	bool restrictedGarrisonsForAI() const;
 
 	template <typename Handler>
 	void serialize(Handler &h)

+ 353 - 0
lib/campaign/CampaignBonus.cpp

@@ -0,0 +1,353 @@
+/*
+ * CampaignBonus.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+#include "CampaignBonus.h"
+
+#include "../filesystem/CBinaryReader.h"
+#include "../mapping/MapIdentifiersH3M.h"
+#include "../json/JsonNode.h"
+#include "../constants/StringConstants.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+static const std::map<std::string, CampaignBonusType> bonusTypeMap = {
+	{"spell", CampaignBonusType::SPELL},
+	{"creature", CampaignBonusType::MONSTER},
+	{"building", CampaignBonusType::BUILDING},
+	{"artifact", CampaignBonusType::ARTIFACT},
+	{"scroll", CampaignBonusType::SPELL_SCROLL},
+	{"primarySkill", CampaignBonusType::PRIMARY_SKILL},
+	{"secondarySkill", CampaignBonusType::SECONDARY_SKILL},
+	{"resource", CampaignBonusType::RESOURCE},
+	{"prevHero", CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO},
+	{"hero", CampaignBonusType::HERO},
+};
+
+static const std::map<std::string, ui16> heroSpecialMap = {
+	{"strongest", HeroTypeID::CAMP_STRONGEST},
+	{"generated", HeroTypeID::CAMP_GENERATED},
+	{"random", HeroTypeID::CAMP_RANDOM}
+};
+
+static const std::map<std::string, ui8> resourceTypeMap = {
+	{"wood", EGameResID::WOOD},
+	{"mercury", EGameResID::MERCURY},
+	{"ore", EGameResID::ORE},
+	{"sulfur", EGameResID::SULFUR},
+	{"crystal", EGameResID::CRYSTAL},
+	{"gems", EGameResID::GEMS},
+	{"gold", EGameResID::GOLD},
+	{"common", EGameResID::COMMON},
+	{"rare", EGameResID::RARE}
+};
+
+CampaignBonus::CampaignBonus(CBinaryReader & reader, const MapIdentifiersH3M & remapper, CampaignStartOptions mode)
+{
+	switch(mode)
+	{
+		case CampaignStartOptions::NONE:
+			// in this mode game should not attempt to create instances of bonuses
+			throw std::runtime_error("Attempt to create empty campaign bonus");
+
+		case CampaignStartOptions::START_BONUS:
+		{
+			auto bonusType = static_cast<CampaignBonusType>(reader.readUInt8());
+
+			switch(bonusType)
+			{
+				case CampaignBonusType::SPELL:
+				{
+					HeroTypeID hero(reader.readInt16());
+					SpellID spell(reader.readUInt8());
+					data = CampaignBonusSpell{remapper.remap(hero), spell};
+					break;
+				}
+				case CampaignBonusType::MONSTER:
+				{
+					HeroTypeID hero(reader.readInt16());
+					CreatureID creature(reader.readUInt16());
+					int32_t amount = reader.readUInt16();
+					data = CampaignBonusCreatures{remapper.remap(hero), remapper.remap(creature), amount};
+					break;
+				}
+				case CampaignBonusType::BUILDING:
+				{
+					BuildingID building(reader.readUInt8());
+					data = CampaignBonusBuilding{building, remapper.remapBuilding(std::nullopt, building)};
+					break;
+				}
+				case CampaignBonusType::ARTIFACT:
+				{
+					HeroTypeID hero(reader.readInt16());
+					ArtifactID artifact(reader.readUInt16());
+					data = CampaignBonusArtifact{remapper.remap(hero), remapper.remap(artifact)};
+					break;
+				}
+				case CampaignBonusType::SPELL_SCROLL:
+				{
+					HeroTypeID hero(reader.readInt16());
+					SpellID spell(reader.readUInt8());
+					data = CampaignBonusSpellScroll{remapper.remap(hero), spell};
+					break;
+				}
+				case CampaignBonusType::PRIMARY_SKILL:
+				{
+					HeroTypeID hero(reader.readInt16());
+					std::array<uint8_t, 4> amounts = {};
+					for(auto & value : amounts)
+						value = reader.readUInt8();
+
+					data = CampaignBonusPrimarySkill{remapper.remap(hero), amounts};
+					break;
+				}
+				case CampaignBonusType::SECONDARY_SKILL:
+				{
+					HeroTypeID hero(reader.readInt16());
+					SecondarySkill skill(reader.readUInt8());
+					int32_t skillMastery(reader.readUInt8());
+					data = CampaignBonusSecondarySkill{remapper.remap(hero), remapper.remap(skill), skillMastery};
+					break;
+				}
+				case CampaignBonusType::RESOURCE:
+				{
+					GameResID resource(reader.readInt8());
+					int32_t amount(reader.readInt32());
+					data = CampaignBonusStartingResources{resource, amount};
+					break;
+				}
+				default:
+					throw std::runtime_error("Corrupted or unsupported h3c file");
+			}
+			break;
+		}
+		case CampaignStartOptions::HERO_CROSSOVER: //reading of players (colors / scenarios ?) player can choose
+		{
+			PlayerColor player(reader.readUInt8());
+			CampaignScenarioID scenario(reader.readUInt8());
+			data = CampaignBonusHeroesFromScenario{player, scenario};
+			break;
+		}
+		case CampaignStartOptions::HERO_OPTIONS: //heroes player can choose between
+		{
+			PlayerColor player(reader.readUInt8());
+			HeroTypeID hero(reader.readInt16());
+			data = CampaignBonusStartingHero{player, remapper.remap(hero)};
+			break;
+		}
+		default:
+		{
+			throw std::runtime_error("Corrupted or unsupported h3c file");
+		}
+	}
+}
+
+CampaignBonus::CampaignBonus(const JsonNode & bjson, CampaignStartOptions mode)
+{
+	CampaignBonusType bonusType;
+
+	if (!bjson["what"].isNull())
+	{
+		bonusType = bonusTypeMap.at(bjson["what"].String());
+	}
+	else if (mode == CampaignStartOptions::HERO_CROSSOVER)
+	{
+		bonusType = CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO;
+	}
+	else if (mode == CampaignStartOptions::HERO_OPTIONS)
+	{
+		bonusType = CampaignBonusType::HERO;
+	}
+	else
+	{
+		throw std::runtime_error("Corrupted or unsupported vcmp file");
+	}
+
+	const auto & decodeHeroTypeID = [](JsonNode heroType) -> HeroTypeID
+	{
+		if(vstd::contains(heroSpecialMap, heroType.String()))
+			return HeroTypeID(heroSpecialMap.at(heroType.String()));
+		else
+			return HeroTypeID(HeroTypeID::decode(heroType.String()));
+	};
+
+	switch(bonusType)
+	{
+		case CampaignBonusType::SPELL:
+		{
+			HeroTypeID hero(decodeHeroTypeID(bjson["hero"]));
+			SpellID spell(SpellID::decode(bjson["spellType"].String()));
+			data = CampaignBonusSpell{hero, spell};
+			break;
+		}
+		case CampaignBonusType::MONSTER:
+		{
+			HeroTypeID hero(decodeHeroTypeID(bjson["hero"]));
+			CreatureID creature(CreatureID::decode(bjson["creatureType"].String()));
+			int32_t amount = bjson["creatureAmount"].Integer();
+			data = CampaignBonusCreatures{hero, creature, amount};
+			break;
+		}
+		case CampaignBonusType::BUILDING:
+		{
+			BuildingID building(vstd::find_pos(EBuildingType::names, bjson["buildingType"].String()));
+			data = CampaignBonusBuilding{building, building};
+			break;
+		}
+		case CampaignBonusType::ARTIFACT:
+		{
+			HeroTypeID hero(decodeHeroTypeID(bjson["hero"]));
+			ArtifactID artifact(ArtifactID::decode(bjson["artifactType"].String()));
+			data = CampaignBonusArtifact{hero, artifact};
+			break;
+		}
+		case CampaignBonusType::SPELL_SCROLL:
+		{
+			HeroTypeID hero(decodeHeroTypeID(bjson["hero"]));
+			SpellID spell(SpellID::decode(bjson["spellType"].String()));
+			data = CampaignBonusSpellScroll{hero, spell};
+			break;
+		}
+		case CampaignBonusType::PRIMARY_SKILL:
+		{
+			HeroTypeID hero(decodeHeroTypeID(bjson["hero"]));
+			std::array<uint8_t, 4> amounts = {};
+			for(size_t i = 0; i < amounts.size(); ++i)
+				amounts[i] = bjson[NPrimarySkill::names[i]].Integer();
+
+			data = CampaignBonusPrimarySkill{hero, amounts};
+			break;
+		}
+		case CampaignBonusType::SECONDARY_SKILL:
+		{
+			HeroTypeID hero(decodeHeroTypeID(bjson["hero"]));
+			SecondarySkill skill(SecondarySkill::decode(bjson["skillType"].String()));
+			int32_t skillMastery = bjson["skillMastery"].Integer();
+			data = CampaignBonusSecondarySkill{hero, skill, skillMastery};
+			break;
+		}
+		case CampaignBonusType::RESOURCE:
+		{
+			GameResID resource(resourceTypeMap.at(bjson["resourceType"].String()));
+			int32_t resourceAmount(bjson["resourceAmount"].Integer());
+			data = CampaignBonusStartingResources{resource, resourceAmount};
+			break;
+		}
+		case CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO: //reading of players (colors / scenarios ?) player can choose
+		{
+			PlayerColor player(SecondarySkill::decode(bjson["playerColor"].String()));
+			CampaignScenarioID scenario(bjson["scenario"].Integer());
+			data = CampaignBonusHeroesFromScenario{player, scenario};
+			break;
+		}
+		case CampaignBonusType::HERO: //heroes player can choose between
+		{
+			PlayerColor player(SecondarySkill::decode(bjson["playerColor"].String()));
+			HeroTypeID hero(decodeHeroTypeID(bjson["hero"]));
+			data = CampaignBonusStartingHero{player, hero};
+			break;
+		}
+		default:
+			throw std::runtime_error("Corrupted or unsupported h3c file");
+	}
+}
+
+JsonNode CampaignBonus::toJson() const
+{
+	JsonNode bnode;
+
+	const auto & encodeHeroTypeID = [](HeroTypeID heroType) -> JsonNode
+	{
+		if(vstd::contains(vstd::reverseMap(heroSpecialMap), heroType))
+			return JsonNode(vstd::reverseMap(heroSpecialMap)[heroType]);
+		else
+			return JsonNode(HeroTypeID::encode(heroType));
+	};
+
+	bnode["what"].String() = vstd::reverseMap(bonusTypeMap).at(getType());
+
+	switch (getType())
+	{
+		case CampaignBonusType::SPELL:
+		{
+			const auto & bonusValue = getValue<CampaignBonusSpell>();
+			bnode["hero"] = encodeHeroTypeID(bonusValue.hero);
+			bnode["spellType"].String() = SpellID::encode(bonusValue.spell.getNum());
+			break;
+		}
+		case CampaignBonusType::MONSTER:
+		{
+			const auto & bonusValue = getValue<CampaignBonusCreatures>();
+			bnode["hero"] = encodeHeroTypeID(bonusValue.hero);
+			bnode["creatureType"].String() = CreatureID::encode(bonusValue.creature.getNum());
+			bnode["creatureAmount"].Integer() = bonusValue.amount;
+			break;
+		}
+		case CampaignBonusType::BUILDING:
+		{
+			const auto & bonusValue = getValue<CampaignBonusBuilding>();
+			bnode["buildingType"].String() = EBuildingType::names[bonusValue.buildingDecoded.getNum()];
+			break;
+		}
+		case CampaignBonusType::ARTIFACT:
+		{
+			const auto & bonusValue = getValue<CampaignBonusArtifact>();
+			bnode["hero"] = encodeHeroTypeID(bonusValue.hero);
+			bnode["artifactType"].String() = ArtifactID::encode(bonusValue.artifact.getNum());
+			break;
+		}
+		case CampaignBonusType::SPELL_SCROLL:
+		{
+			const auto & bonusValue = getValue<CampaignBonusSpellScroll>();
+			bnode["hero"] = encodeHeroTypeID(bonusValue.hero);
+			bnode["spellType"].String() = SpellID::encode(bonusValue.spell.getNum());
+			break;
+		}
+		case CampaignBonusType::PRIMARY_SKILL:
+		{
+			const auto & bonusValue = getValue<CampaignBonusPrimarySkill>();
+			bnode["hero"] = encodeHeroTypeID(bonusValue.hero);
+			for(size_t i = 0; i < bonusValue.amounts.size(); ++i)
+				bnode[NPrimarySkill::names[i]].Integer() = bonusValue.amounts[i];
+			break;
+		}
+		case CampaignBonusType::SECONDARY_SKILL:
+		{
+			const auto & bonusValue = getValue<CampaignBonusSecondarySkill>();
+			bnode["hero"] = encodeHeroTypeID(bonusValue.hero);
+			bnode["skillType"].String() = SecondarySkill::encode(bonusValue.skill.getNum());
+			bnode["skillMastery"].Integer() = bonusValue.mastery;
+			break;
+		}
+		case CampaignBonusType::RESOURCE:
+		{
+			const auto & bonusValue = getValue<CampaignBonusStartingResources>();
+			bnode["resourceType"].String() = vstd::reverseMap(resourceTypeMap)[bonusValue.resource.getNum()];
+			bnode["resourceAmount"].Integer() = bonusValue.amount;
+			break;
+		}
+		case CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO:
+		{
+			const auto & bonusValue = getValue<CampaignBonusHeroesFromScenario>();
+			bnode["playerColor"].String() = PlayerColor::encode(bonusValue.startingPlayer);
+			bnode["scenario"].Integer() = bonusValue.scenario.getNum();
+			break;
+		}
+		case CampaignBonusType::HERO:
+		{
+			const auto & bonusValue = getValue<CampaignBonusStartingHero>();
+			bnode["playerColor"].String() = PlayerColor::encode(bonusValue.startingPlayer);
+			bnode["hero"] = encodeHeroTypeID(bonusValue.hero);
+			break;
+		}
+	}
+	return bnode;
+}
+
+VCMI_LIB_NAMESPACE_END

+ 224 - 0
lib/campaign/CampaignBonus.h

@@ -0,0 +1,224 @@
+/*
+ * CampaignBonus.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "CampaignConstants.h"
+#include "../constants/EntityIdentifiers.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class CBinaryReader;
+class MapIdentifiersH3M;
+class JsonNode;
+
+struct CampaignBonusSpell
+{
+	HeroTypeID hero;
+	SpellID spell;
+
+	template <typename Handler> void serialize(Handler &h)
+	{
+		h & hero;
+		h & spell;
+	}
+};
+
+struct CampaignBonusCreatures
+{
+	HeroTypeID hero;
+	CreatureID creature;
+	int32_t amount = 0;
+
+	template <typename Handler> void serialize(Handler &h)
+	{
+		h & hero;
+		h & creature;
+		h & amount;
+	}
+};
+
+struct CampaignBonusBuilding
+{
+	BuildingID buildingH3M;
+	BuildingID buildingDecoded;
+
+	template <typename Handler> void serialize(Handler &h)
+	{
+		h & buildingH3M;
+		h & buildingDecoded;
+	}
+};
+
+struct CampaignBonusArtifact
+{
+	HeroTypeID hero;
+	ArtifactID artifact;
+
+	template <typename Handler> void serialize(Handler &h)
+	{
+		h & hero;
+		h & artifact;
+	}
+};
+
+struct CampaignBonusSpellScroll
+{
+	HeroTypeID hero;
+	SpellID spell;
+
+	template <typename Handler> void serialize(Handler &h)
+	{
+		h & hero;
+		h & spell;
+	}
+};
+
+struct CampaignBonusPrimarySkill
+{
+	HeroTypeID hero;
+	std::array<uint8_t, 4> amounts = {};
+
+	template <typename Handler> void serialize(Handler &h)
+	{
+		h & hero;
+		h & amounts;
+	}
+};
+
+struct CampaignBonusSecondarySkill
+{
+	HeroTypeID hero;
+	SecondarySkill skill;
+	int32_t mastery = 0;
+
+	template <typename Handler> void serialize(Handler &h)
+	{
+		h & hero;
+		h & skill;
+		h & mastery;
+	}
+};
+
+struct CampaignBonusStartingResources
+{
+	GameResID resource;
+	int32_t amount = 0;
+
+	template <typename Handler> void serialize(Handler &h)
+	{
+		h & resource;
+		h & amount;
+	}
+};
+
+struct CampaignBonusHeroesFromScenario
+{
+	PlayerColor startingPlayer;
+	CampaignScenarioID scenario;
+
+	template <typename Handler> void serialize(Handler &h)
+	{
+		h & startingPlayer;
+		h & scenario;
+	}
+};
+
+struct CampaignBonusStartingHero
+{
+	PlayerColor startingPlayer;
+	HeroTypeID hero;
+
+	template <typename Handler> void serialize(Handler &h)
+	{
+		h & startingPlayer;
+		h & hero;
+	}
+};
+
+class CampaignBonus
+{
+	using Variant = std::variant<
+		CampaignBonusSpell, // UNTESTED
+		CampaignBonusCreatures,
+		CampaignBonusBuilding, // UNTESTED - broken, Long Live the King, Liberation last
+		CampaignBonusArtifact,
+		CampaignBonusSpellScroll,
+		CampaignBonusPrimarySkill,
+		CampaignBonusSecondarySkill,
+		CampaignBonusStartingResources,
+		CampaignBonusHeroesFromScenario,
+		CampaignBonusStartingHero>;
+
+	Variant data;
+
+	template<typename T, typename = void>
+	struct HasHero : std::false_type { };
+
+	template<typename T>
+	struct HasHero<T, decltype(std::declval<T>().hero, void())> : std::true_type { };
+
+public:
+	CampaignBonus() = default;
+
+	template<typename BonusType>
+	CampaignBonus(const BonusType & value)
+		:data(value)
+	{}
+
+	DLL_LINKAGE CampaignBonus(CBinaryReader & reader, const MapIdentifiersH3M & remapper, CampaignStartOptions mode);
+	DLL_LINKAGE CampaignBonus(const JsonNode & json, CampaignStartOptions mode);
+
+	template<typename T>
+	const T & getValue() const
+	{
+		return std::get<T>(data);
+	}
+
+	HeroTypeID getTargetedHero() const
+	{
+		HeroTypeID result;
+
+		std::visit([&result](const auto & bonusValue){
+			if constexpr (HasHero<decltype(bonusValue)>::value)
+			{
+				result = bonusValue.hero;
+			}
+			else
+				throw std::runtime_error("Attempt to get targeted hero on invalid type!");
+		}, data);
+
+		return result;
+	}
+
+	bool isBonusForHero() const
+	{
+		auto type = getType();
+		return type == CampaignBonusType::SPELL ||
+			   type == CampaignBonusType::MONSTER ||
+			   type == CampaignBonusType::ARTIFACT ||
+			   type == CampaignBonusType::SPELL_SCROLL ||
+			   type == CampaignBonusType::PRIMARY_SKILL ||
+			   type == CampaignBonusType::SECONDARY_SKILL;
+	}
+
+	CampaignBonusType getType() const
+	{
+		return static_cast<CampaignBonusType>(data.index());
+	}
+
+	DLL_LINKAGE JsonNode toJson() const;
+
+	template <typename Handler> void serialize(Handler &h)
+	{
+		h & data;
+	}
+};
+
+VCMI_LIB_NAMESPACE_END

+ 1 - 2
lib/campaign/CampaignConstants.h

@@ -27,7 +27,7 @@ enum class CampaignVersion : uint8_t
 	VCMI_MAX = 1,
 };
 
-enum class CampaignStartOptions: int8_t
+enum class CampaignStartOptions : int8_t
 {
 	NONE = 0,
 	START_BONUS,
@@ -37,7 +37,6 @@ enum class CampaignStartOptions: int8_t
 
 enum class CampaignBonusType : int8_t
 {
-	NONE = -1,
 	SPELL,
 	MONSTER,
 	BUILDING,

+ 53 - 397
lib/campaign/CampaignHandler.cpp

@@ -11,6 +11,7 @@
 #include "CampaignHandler.h"
 
 #include "CampaignState.h"
+#include "CampaignRegionsHandler.h"
 
 #include "../filesystem/Filesystem.h"
 #include "../filesystem/CCompressedStream.h"
@@ -18,9 +19,9 @@
 #include "../filesystem/CBinaryReader.h"
 #include "../filesystem/CZipLoader.h"
 #include "../GameLibrary.h"
-#include "../constants/StringConstants.h"
 #include "../mapping/CMapHeader.h"
 #include "../mapping/CMapService.h"
+#include "../mapping/MapFormatSettings.h"
 #include "../modding/CModHandler.h"
 #include "../modding/IdentifierStorage.h"
 #include "../modding/ModScope.h"
@@ -29,7 +30,7 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-void CampaignHandler::readCampaign(Campaign * ret, const std::vector<ui8> & input, std::string filename, std::string modName, std::string encoding)
+void CampaignHandler::readCampaign(Campaign * ret, const std::vector<ui8> & input, const std::string & filename, const std::string & modName, const std::string & encoding)
 {
 	if (input.front() < uint8_t(' ')) // binary format
 	{
@@ -77,8 +78,8 @@ std::unique_ptr<Campaign> CampaignHandler::getHeader( const std::string & name)
 std::shared_ptr<CampaignState> CampaignHandler::getCampaign( const std::string & name )
 {
 	ResourcePath resourceID(name, EResType::CAMPAIGN);
-	std::string modName = LIBRARY->modh->findResourceOrigin(resourceID);
-	std::string encoding = LIBRARY->modh->findResourceEncoding(resourceID);
+	const std::string & modName = LIBRARY->modh->findResourceOrigin(resourceID);
+	const std::string & encoding = LIBRARY->modh->findResourceEncoding(resourceID);
 	
 	auto ret = std::make_unique<CampaignState>();
 	
@@ -124,14 +125,14 @@ static std::string convertMapName(std::string input)
 	return input;
 }
 
-std::string CampaignHandler::readLocalizedString(CampaignHeader & target, CBinaryReader & reader, std::string filename, std::string modName, std::string encoding, std::string identifier)
+std::string CampaignHandler::readLocalizedString(CampaignHeader & target, CBinaryReader & reader, const std::string & filename, const std::string & modName, const std::string & encoding, const std::string & identifier)
 {
-	std::string input = TextOperations::toUnicode(reader.readBaseString(), encoding);
+	const std::string & input = TextOperations::toUnicode(reader.readBaseString(), encoding);
 
 	return readLocalizedString(target, input, filename, modName, identifier);
 }
 
-std::string CampaignHandler::readLocalizedString(CampaignHeader & target, std::string text, std::string filename, std::string modName, std::string identifier)
+std::string CampaignHandler::readLocalizedString(CampaignHeader & target, const std::string & text, const std::string & filename, const std::string & modName, const std::string & identifier)
 {
 	TextIdentifier stringID( "campaign", convertMapName(filename), identifier);
 
@@ -142,7 +143,7 @@ std::string CampaignHandler::readLocalizedString(CampaignHeader & target, std::s
 	return stringID.get();
 }
 
-void CampaignHandler::readHeaderFromJson(CampaignHeader & ret, JsonNode & reader, std::string filename, std::string modName, std::string encoding)
+void CampaignHandler::readHeaderFromJson(CampaignHeader & ret, JsonNode & reader, const std::string & filename, const std::string & modName, const std::string & encoding)
 {
 	ret.version = static_cast<CampaignVersion>(reader["version"].Integer());
 	if(ret.version < CampaignVersion::VCMI_MIN || ret.version > CampaignVersion::VCMI_MAX)
@@ -152,7 +153,7 @@ void CampaignHandler::readHeaderFromJson(CampaignHeader & ret, JsonNode & reader
 	}
 	
 	ret.version = CampaignVersion::VCMI;
-	ret.campaignRegions = CampaignRegions::fromJson(reader["regions"]);
+	ret.campaignRegions = CampaignRegions(reader["regions"]);
 	ret.numberOfScenarios = reader["scenarios"].Vector().size();
 	ret.name.appendTextID(readLocalizedString(ret, reader["name"].String(), filename, modName, "name"));
 	ret.description.appendTextID(readLocalizedString(ret, reader["description"].String(), filename, modName, "description"));
@@ -175,7 +176,7 @@ JsonNode CampaignHandler::writeHeaderToJson(CampaignHeader & header)
 {
 	JsonNode node;
 	node["version"].Integer() = static_cast<ui64>(CampaignVersion::VCMI);
-	node["regions"] = CampaignRegions::toJson(header.campaignRegions);
+	node["regions"] = header.campaignRegions.toJson();
 	node["name"].String() = header.name.toString();
 	node["description"].String() = header.description.toString();
 	node["author"].String() = header.author.toString();
@@ -260,44 +261,6 @@ static const std::map<std::string, CampaignStartOptions> startOptionsMap = {
 	{"hero", CampaignStartOptions::HERO_OPTIONS}
 };
 
-static const std::map<std::string, CampaignBonusType> bonusTypeMap = {
-	{"spell", CampaignBonusType::SPELL},
-	{"creature", CampaignBonusType::MONSTER},
-	{"building", CampaignBonusType::BUILDING},
-	{"artifact", CampaignBonusType::ARTIFACT},
-	{"scroll", CampaignBonusType::SPELL_SCROLL},
-	{"primarySkill", CampaignBonusType::PRIMARY_SKILL},
-	{"secondarySkill", CampaignBonusType::SECONDARY_SKILL},
-	{"resource", CampaignBonusType::RESOURCE},
-	//{"prevHero", CScenarioTravel::STravelBonus::EBonusType::HEROES_FROM_PREVIOUS_SCENARIO},
-	//{"hero", CScenarioTravel::STravelBonus::EBonusType::HERO},
-};
-
-static const std::map<std::string, ui32> primarySkillsMap = {
-	{"attack", 0},
-	{"defence", 8},
-	{"spellpower", 16},
-	{"knowledge", 24},
-};
-
-static const std::map<std::string, ui16> heroSpecialMap = {
-	{"strongest", HeroTypeID::CAMP_STRONGEST},
-	{"generated", HeroTypeID::CAMP_GENERATED},
-	{"random", HeroTypeID::CAMP_RANDOM}
-};
-
-static const std::map<std::string, ui8> resourceTypeMap = {
-	{"wood", EGameResID::WOOD},
-	{"mercury", EGameResID::MERCURY},
-	{"ore", EGameResID::ORE},
-	{"sulfur", EGameResID::SULFUR},
-	{"crystal", EGameResID::CRYSTAL},
-	{"gems", EGameResID::GEMS},
-	{"gold", EGameResID::GOLD},
-	{"common", EGameResID::COMMON},
-	{"rare", EGameResID::RARE}
-};
-
 CampaignTravel CampaignHandler::readScenarioTravelFromJson(JsonNode & reader)
 {
 	CampaignTravel ret;
@@ -327,117 +290,12 @@ CampaignTravel CampaignHandler::readScenarioTravelFromJson(JsonNode & reader)
 	}
 
 	ret.startOptions = startOptionsMap.at(reader["startOptions"].String());
-	switch(ret.startOptions)
-	{
-	case CampaignStartOptions::NONE:
-		//no bonuses. Seems to be OK
-		break;
-	case CampaignStartOptions::START_BONUS: //reading of bonuses player can choose
-		{
-			ret.playerColor = PlayerColor(PlayerColor::decode(reader["playerColor"].String()));
-			for(auto & bjson : reader["bonuses"].Vector())
-			{
-				CampaignBonus bonus;
-				bonus.type = bonusTypeMap.at(bjson["what"].String());
-				switch (bonus.type)
-				{
-					case CampaignBonusType::RESOURCE:
-						bonus.info1 = resourceTypeMap.at(bjson["type"].String());
-						bonus.info2 = bjson["amount"].Integer();
-						break;
-						
-					case CampaignBonusType::BUILDING:
-						bonus.info1 = vstd::find_pos(EBuildingType::names, bjson["type"].String());
-						if(bonus.info1 == -1)
-							logGlobal->warn("VCMP Loading: unresolved building identifier %s", bjson["type"].String());
-						break;
-						
-					default:
-						auto heroIdentifier = bjson["hero"].String();
-						auto it = heroSpecialMap.find(heroIdentifier);
-						if(it != heroSpecialMap.end())
-							bonus.info1 = it->second;
-						else
-							if(auto identifier = LIBRARY->identifiers()->getIdentifier(ModScope::scopeMap(), "hero", heroIdentifier))
-								bonus.info1 = identifier.value();
-							else
-								logGlobal->warn("VCMP Loading: unresolved hero identifier %s", heroIdentifier);
-	
-						bonus.info3 = bjson["amount"].Integer();
-						
-						switch(bonus.type)
-						{
-							case CampaignBonusType::SPELL:
-							case CampaignBonusType::MONSTER:
-							case CampaignBonusType::SECONDARY_SKILL:
-							case CampaignBonusType::ARTIFACT:
-								if(auto identifier  = LIBRARY->identifiers()->getIdentifier(ModScope::scopeMap(), bjson["what"].String(), bjson["type"].String()))
-									bonus.info2 = identifier.value();
-								else
-									logGlobal->warn("VCMP Loading: unresolved %s identifier %s", bjson["what"].String(), bjson["type"].String());
-								break;
-								
-							case CampaignBonusType::SPELL_SCROLL:
-								if(auto Identifier = LIBRARY->identifiers()->getIdentifier(ModScope::scopeMap(), "spell", bjson["type"].String()))
-									bonus.info2 = Identifier.value();
-								else
-									logGlobal->warn("VCMP Loading: unresolved spell scroll identifier %s", bjson["type"].String());
-								break;
-								
-							case CampaignBonusType::PRIMARY_SKILL:
-								for(auto & ps : primarySkillsMap)
-									bonus.info2 |= bjson[ps.first].Integer() << ps.second;
-								break;
-								
-							default:
-								bonus.info2 = bjson["type"].Integer();
-						}
-						break;
-				}
-				ret.bonusesToChoose.push_back(bonus);
-			}
-			break;
-		}
-	case CampaignStartOptions::HERO_CROSSOVER: //reading of players (colors / scenarios ?) player can choose
-		{
-			for(auto & bjson : reader["bonuses"].Vector())
-			{
-				CampaignBonus bonus;
-				bonus.type = CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO;
-				bonus.info1 = bjson["playerColor"].Integer(); //player color
-				bonus.info2 = bjson["scenario"].Integer(); //from what scenario
-				ret.bonusesToChoose.push_back(bonus);
-			}
-			break;
-		}
-	case CampaignStartOptions::HERO_OPTIONS: //heroes player can choose between
-		{
-			for(auto & bjson : reader["bonuses"].Vector())
-			{
-				CampaignBonus bonus;
-				bonus.type = CampaignBonusType::HERO;
-				bonus.info1 = bjson["playerColor"].Integer(); //player color
-
-				auto heroIdentifier = bjson["hero"].String();
-				auto it = heroSpecialMap.find(heroIdentifier);
-				if(it != heroSpecialMap.end())
-					bonus.info2 = it->second;
-				else
-					if (auto identifier = LIBRARY->identifiers()->getIdentifier(ModScope::scopeMap(), "hero", heroIdentifier))
-						bonus.info2 = identifier.value();
-					else
-						logGlobal->warn("VCMP Loading: unresolved hero identifier %s", heroIdentifier);
-			
-				ret.bonusesToChoose.push_back(bonus);
-			}
-			break;
-		}
-	default:
-		{
-			logGlobal->warn("VCMP Loading: Unsupported start options value");
-			break;
-		}
-	}
+
+	if (!reader["playerColor"].isNull())
+		ret.playerColor = PlayerColor(PlayerColor::decode(reader["playerColor"].String()));
+
+	for(auto & bjson : reader["bonuses"].Vector())
+		ret.bonusesToChoose.emplace_back(bjson, ret.startOptions);
 
 	return ret;
 }
@@ -454,99 +312,19 @@ void CampaignHandler::writeScenarioTravelToJson(JsonNode & node, const CampaignT
 		node["heroKeeps"].Vector().push_back(JsonNode("spells"));
 	if(travel.whatHeroKeeps.artifacts)
 		node["heroKeeps"].Vector().push_back(JsonNode("artifacts"));
-	for(auto & c : travel.monstersKeptByHero)
+	for(const auto & c : travel.monstersKeptByHero)
 		node["keepCreatures"].Vector().push_back(JsonNode(CreatureID::encode(c)));
-	for(auto & a : travel.artifactsKeptByHero)
+	for(const auto & a : travel.artifactsKeptByHero)
 		node["keepArtifacts"].Vector().push_back(JsonNode(ArtifactID::encode(a)));
-	node["startOptions"].String() = vstd::reverseMap(startOptionsMap)[travel.startOptions];
 
-	switch(travel.startOptions)
-	{
-	case CampaignStartOptions::NONE:
-		break;
-	case CampaignStartOptions::START_BONUS:
-		{
-			node["playerColor"].String() = PlayerColor::encode(travel.playerColor);
-			for(auto & bonus : travel.bonusesToChoose)
-			{
-				JsonNode bnode;
-				bnode["what"].String() = vstd::reverseMap(bonusTypeMap)[bonus.type];
-				switch (bonus.type)
-				{
-					case CampaignBonusType::RESOURCE:
-						bnode["type"].String() = vstd::reverseMap(resourceTypeMap)[bonus.info1];
-						bnode["amount"].Integer() = bonus.info2;
-						break;
-					case CampaignBonusType::BUILDING:
-						bnode["type"].String() = EBuildingType::names[bonus.info1];
-						break;
-					default:
-						if(vstd::contains(vstd::reverseMap(heroSpecialMap), bonus.info1))
-							bnode["hero"].String() = vstd::reverseMap(heroSpecialMap)[bonus.info1];
-						else
-							bnode["hero"].String() = HeroTypeID::encode(bonus.info1);
-						bnode["amount"].Integer() = bonus.info3;
-						switch(bonus.type)
-						{
-							case CampaignBonusType::SPELL:
-								bnode["type"].String() = SpellID::encode(bonus.info2);
-								break;
-							case CampaignBonusType::MONSTER:
-								bnode["type"].String() = CreatureID::encode(bonus.info2);
-								break;
-							case CampaignBonusType::SECONDARY_SKILL:
-								bnode["type"].String() = SecondarySkill::encode(bonus.info2);
-								break;
-							case CampaignBonusType::ARTIFACT:
-								bnode["type"].String() = ArtifactID::encode(bonus.info2);
-								break;
-							case CampaignBonusType::SPELL_SCROLL:
-								bnode["type"].String() = SpellID::encode(bonus.info2);
-								break;
-							case CampaignBonusType::PRIMARY_SKILL:
-								for(auto & ps : primarySkillsMap)
-									bnode[ps.first].Integer() = (bonus.info2 >> ps.second) & 0xff;
-								break;
-							default:
-								bnode["type"].Integer() = bonus.info2;
-						}
-						break;
-				}
-				node["bonuses"].Vector().push_back(bnode);
-			}
-			break;
-		}
-	case CampaignStartOptions::HERO_CROSSOVER:
-		{
-			for(auto & bonus : travel.bonusesToChoose)
-			{
-				JsonNode bnode;
-				bnode["playerColor"].Integer() = bonus.info1;
-				bnode["scenario"].Integer() = bonus.info2;
-				node["bonuses"].Vector().push_back(bnode);
-			}
-			break;
-		}
-	case CampaignStartOptions::HERO_OPTIONS:
-		{
-			for(auto & bonus : travel.bonusesToChoose)
-			{
-				JsonNode bnode;
-				bnode["playerColor"].Integer() = bonus.info1;
-
-				if(vstd::contains(vstd::reverseMap(heroSpecialMap), bonus.info2))
-					bnode["hero"].String() = vstd::reverseMap(heroSpecialMap)[bonus.info2];
-				else
-					bnode["hero"].String() = HeroTypeID::encode(bonus.info2);
-				
-				node["bonuses"].Vector().push_back(bnode);
-			}
-			break;
-		}
-	}
+	if (travel.playerColor.isValidPlayer())
+		node["playerColor"].String() = PlayerColor::encode(travel.playerColor.getNum());
+
+	for (const auto & bonus : travel.bonusesToChoose)
+		node["bonuses"].Vector().push_back(bonus.toJson());
 }
 
-void CampaignHandler::readHeaderFromMemory( CampaignHeader & ret, CBinaryReader & reader, std::string filename, std::string modName, std::string encoding )
+void CampaignHandler::readHeaderFromMemory( CampaignHeader & ret, CBinaryReader & reader, const std::string & filename, const std::string & modName, const std::string & encoding )
 {
 	ret.version = static_cast<CampaignVersion>(reader.readUInt32());
 
@@ -573,14 +351,14 @@ void CampaignHandler::readHeaderFromMemory( CampaignHeader & ret, CBinaryReader
 		assert(unknownB == 1);
 		assert(unknownC == 0);
 		assert(ret.numberOfScenarios <= 8);
-
-		// TODO. Or they are hardcoded in this hota version?
-		// ret.campaignRegions = ???;
 	}
 
-	ui8 campId = reader.readUInt8() - 1;//change range of it from [1, 20] to [0, 19]
-	if(ret.version < CampaignVersion::Chr) // For chronicles: Will be overridden later; Chronicles uses own logic (reusing OH3 ID's)
-		ret.loadLegacyData(campId);
+	const auto & mapping = LIBRARY->mapFormat->getMapping(ret.version);
+
+	CampaignRegionID campaignMapId(reader.readUInt8());
+	ret.campaignRegions = *LIBRARY->campaignRegions->getByIndex(mapping.remap(campaignMapId));
+	if(ret.version != CampaignVersion::HotA)
+		ret.numberOfScenarios = ret.campaignRegions.regionsCount();
 	ret.name.appendTextID(readLocalizedString(ret, reader, filename, modName, encoding, "name"));
 	ret.description.appendTextID(readLocalizedString(ret, reader, filename, modName, encoding, "description"));
 	ret.author.appendRawString("");
@@ -592,7 +370,8 @@ void CampaignHandler::readHeaderFromMemory( CampaignHeader & ret, CBinaryReader
 	else
 		ret.difficultyChosenByPlayer = false;
 
-	ret.music = prologMusicName(reader.readInt8());
+	ret.music = mapping.remapCampaignMusic(reader.readUInt8());
+	logGlobal->trace("Campaign %s: map %d (%d scenarios), music theme: %s", filename, campaignMapId.getNum(), ret.numberOfScenarios, ret.music.getOriginalName());
 	ret.filename = filename;
 	ret.modName = modName;
 	ret.encoding = encoding;
@@ -600,18 +379,17 @@ void CampaignHandler::readHeaderFromMemory( CampaignHeader & ret, CBinaryReader
 
 CampaignScenario CampaignHandler::readScenarioFromMemory( CBinaryReader & reader, CampaignHeader & header)
 {
+	const auto & mapping = LIBRARY->mapFormat->getMapping(header.version);
+
 	auto prologEpilogReader = [&](const std::string & identifier) -> CampaignScenarioPrologEpilog
 	{
 		CampaignScenarioPrologEpilog ret;
 		ret.hasPrologEpilog = reader.readBool();
 		if(ret.hasPrologEpilog)
 		{
-			bool isOriginalCampaign = boost::starts_with(header.getFilename(), "DATA/");
-
-			ui8 index = reader.readUInt8();
-			ret.prologVideo = CampaignHandler::prologVideoName(index);
-			ret.prologMusic = CampaignHandler::prologMusicName(reader.readUInt8());
-			ret.prologVoice = isOriginalCampaign ? CampaignHandler::prologVoiceName(index) : AudioPath();
+			ret.prologVideo = mapping.remapCampaignVideo(reader.readUInt8());
+			ret.prologMusic = mapping.remapCampaignMusic(reader.readUInt8());
+			logGlobal->trace("Campaign %s, scenario %s: music theme: %s, video: %s", header.filename, identifier, ret.prologMusic.getOriginalName(), ret.prologVideo.getOriginalName());
 			ret.prologText.appendTextID(readLocalizedString(header, reader, header.filename, header.modName, header.encoding, identifier));
 		}
 		return ret;
@@ -649,14 +427,14 @@ CampaignScenario CampaignHandler::readScenarioFromMemory( CBinaryReader & reader
 }
 
 template<typename Identifier>
-static void readContainer(std::set<Identifier> & container, CBinaryReader & reader, int sizeBytes)
+static void readContainer(std::set<Identifier> & container, CBinaryReader & reader, const MapIdentifiersH3M & remapper, int sizeBytes)
 {
 	for(int iId = 0, byte = 0; iId < sizeBytes * 8; ++iId)
 	{
 		if(iId % 8 == 0)
 			byte = reader.readUInt8();
 		if(byte & (1 << iId % 8))
-			container.insert(Identifier(iId));
+			container.insert(remapper.remap(Identifier(iId)));
 	}
 }
 
@@ -670,128 +448,30 @@ CampaignTravel CampaignHandler::readScenarioTravelFromMemory(CBinaryReader & rea
 	ret.whatHeroKeeps.secondarySkills = whatHeroKeeps & 4;
 	ret.whatHeroKeeps.spells = whatHeroKeeps & 8;
 	ret.whatHeroKeeps.artifacts = whatHeroKeeps & 16;
+
+	const auto & mapping = LIBRARY->mapFormat->getMapping(version);
 	
 	if (version == CampaignVersion::HotA)
 	{
-		readContainer(ret.monstersKeptByHero, reader, 24);
-		readContainer(ret.artifactsKeptByHero, reader, 21);
+		readContainer(ret.monstersKeptByHero, reader, mapping, 24);
+		readContainer(ret.artifactsKeptByHero, reader, mapping, 21);
 	}
 	else
 	{
-		readContainer(ret.monstersKeptByHero, reader, 19);
-		readContainer(ret.artifactsKeptByHero, reader, version < CampaignVersion::SoD ? 17 : 18);
+		readContainer(ret.monstersKeptByHero, reader, mapping, 19);
+		readContainer(ret.artifactsKeptByHero, reader, mapping, version < CampaignVersion::SoD ? 17 : 18);
 	}
 
 	ret.startOptions = static_cast<CampaignStartOptions>(reader.readUInt8());
 
-	switch(ret.startOptions)
-	{
-	case CampaignStartOptions::NONE:
-		//no bonuses. Seems to be OK
-		break;
-	case CampaignStartOptions::START_BONUS: //reading of bonuses player can choose
-		{
-			ret.playerColor.setNum(reader.readUInt8());
-			ui8 numOfBonuses = reader.readUInt8();
-			for (int g=0; g<numOfBonuses; ++g)
-			{
-				CampaignBonus bonus;
-				bonus.type = static_cast<CampaignBonusType>(reader.readUInt8());
-				//hero: FFFD means 'most powerful' and FFFE means 'generated'
-				switch(bonus.type)
-				{
-				case CampaignBonusType::SPELL:
-					{
-						bonus.info1 = reader.readUInt16(); //hero
-						bonus.info2 = reader.readUInt8(); //spell ID
-						break;
-					}
-				case CampaignBonusType::MONSTER:
-					{
-						bonus.info1 = reader.readUInt16(); //hero
-						bonus.info2 = reader.readUInt16(); //monster type
-						bonus.info3 = reader.readUInt16(); //monster count
-						break;
-					}
-				case CampaignBonusType::BUILDING:
-					{
-						bonus.info1 = reader.readUInt8(); //building ID (0 - town hall, 1 - city hall, 2 - capitol, etc)
-						break;
-					}
-				case CampaignBonusType::ARTIFACT:
-					{
-						bonus.info1 = reader.readUInt16(); //hero
-						bonus.info2 = reader.readUInt16(); //artifact ID
-						break;
-					}
-				case CampaignBonusType::SPELL_SCROLL:
-					{
-						bonus.info1 = reader.readUInt16(); //hero
-						bonus.info2 = reader.readUInt8(); //spell ID
-						break;
-					}
-				case CampaignBonusType::PRIMARY_SKILL:
-					{
-						bonus.info1 = reader.readUInt16(); //hero
-						bonus.info2 = reader.readUInt32(); //bonuses (4 bytes for 4 skills)
-						break;
-					}
-				case CampaignBonusType::SECONDARY_SKILL:
-					{
-						bonus.info1 = reader.readUInt16(); //hero
-						bonus.info2 = reader.readUInt8(); //skill ID
-						bonus.info3 = reader.readUInt8(); //skill level
-						break;
-					}
-				case CampaignBonusType::RESOURCE:
-					{
-						bonus.info1 = reader.readUInt8(); //type
-						//FD - wood+ore
-						//FE - mercury+sulfur+crystal+gem
-						bonus.info2 = reader.readUInt32(); //count
-						break;
-					}
-				default:
-					logGlobal->warn("Corrupted h3c file");
-					break;
-				}
-				ret.bonusesToChoose.push_back(bonus);
-			}
-			break;
-		}
-	case CampaignStartOptions::HERO_CROSSOVER: //reading of players (colors / scenarios ?) player can choose
-		{
-			ui8 numOfBonuses = reader.readUInt8();
-			for (int g=0; g<numOfBonuses; ++g)
-			{
-				CampaignBonus bonus;
-				bonus.type = CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO;
-				bonus.info1 = reader.readUInt8(); //player color
-				bonus.info2 = reader.readUInt8(); //from what scenario
+	if (ret.startOptions == CampaignStartOptions::START_BONUS)
+		ret.playerColor.setNum(reader.readUInt8());
 
-				ret.bonusesToChoose.push_back(bonus);
-			}
-			break;
-		}
-	case CampaignStartOptions::HERO_OPTIONS: //heroes player can choose between
-		{
-			ui8 numOfBonuses = reader.readUInt8();
-			for (int g=0; g<numOfBonuses; ++g)
-			{
-				CampaignBonus bonus;
-				bonus.type = CampaignBonusType::HERO;
-				bonus.info1 = reader.readUInt8(); //player color
-				bonus.info2 = reader.readUInt16(); //hero, FF FF - random
-
-				ret.bonusesToChoose.push_back(bonus);
-			}
-			break;
-		}
-	default:
-		{
-			logGlobal->warn("Corrupted h3c file");
-			break;
-		}
+	if (ret.startOptions != CampaignStartOptions::NONE)
+	{
+		ui8 numOfBonuses = reader.readUInt8();
+		for (int g=0; g<numOfBonuses; ++g)
+			ret.bonusesToChoose.emplace_back(reader, mapping, ret.startOptions);
 	}
 
 	return ret;
@@ -864,28 +544,4 @@ std::vector< std::vector<ui8> > CampaignHandler::getFile(std::unique_ptr<CInputS
 	}
 }
 
-VideoPath CampaignHandler::prologVideoName(ui8 index)
-{
-	JsonNode config(JsonPath::builtin("CONFIG/campaignMedia"));
-	auto vids = config["videos"].Vector();
-	if(index < vids.size())
-		return VideoPath::fromJson(vids[index]);
-	return VideoPath();
-}
-
-AudioPath CampaignHandler::prologMusicName(ui8 index)
-{
-	std::vector<std::string> music;
-	return AudioPath::builtinTODO(LIBRARY->generaltexth->translate("core.cmpmusic." + std::to_string(static_cast<int>(index))));
-}
-
-AudioPath CampaignHandler::prologVoiceName(ui8 index)
-{
-	JsonNode config(JsonPath::builtin("CONFIG/campaignMedia"));
-	auto audio = config["voice"].Vector();
-	if(index < audio.size())
-		return AudioPath::fromJson(audio[index]);
-	return AudioPath();
-}
-
 VCMI_LIB_NAMESPACE_END

+ 5 - 9
lib/campaign/CampaignHandler.h

@@ -16,13 +16,13 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 class DLL_LINKAGE CampaignHandler
 {
-	static std::string readLocalizedString(CampaignHeader & target, CBinaryReader & reader, std::string filename, std::string modName, std::string encoding, std::string identifier);
-	static std::string readLocalizedString(CampaignHeader & target, std::string text, std::string filename, std::string modName, std::string identifier);
+	static std::string readLocalizedString(CampaignHeader & target, CBinaryReader & reader, const std::string & filename, const std::string & modName, const std::string & encoding, const std::string & identifier);
+	static std::string readLocalizedString(CampaignHeader & target, const std::string & text, const std::string & filename, const std::string & modName, const std::string & identifier);
 
-	static void readCampaign(Campaign * target, const std::vector<ui8> & stream, std::string filename, std::string modName, std::string encoding);
+	static void readCampaign(Campaign * target, const std::vector<ui8> & stream, const std::string & filename, const std::string & modName, const std::string & encoding);
 
 	//parsers for VCMI campaigns (*.vcmp)
-	static void readHeaderFromJson(CampaignHeader & target, JsonNode & reader, std::string filename, std::string modName, std::string encoding);
+	static void readHeaderFromJson(CampaignHeader & target, JsonNode & reader, const std::string & filename, const std::string & modName, const std::string & encoding);
 	static CampaignScenario readScenarioFromJson(JsonNode & reader);
 	static CampaignTravel readScenarioTravelFromJson(JsonNode & reader);
 
@@ -30,17 +30,13 @@ class DLL_LINKAGE CampaignHandler
 	static void writeScenarioTravelToJson(JsonNode & node, const CampaignTravel & travel);
 
 	//parsers for original H3C campaigns
-	static void readHeaderFromMemory(CampaignHeader & target, CBinaryReader & reader, std::string filename, std::string modName, std::string encoding);
+	static void readHeaderFromMemory(CampaignHeader & target, CBinaryReader & reader, const std::string & filename, const std::string & modName, const std::string & encoding);
 	static CampaignScenario readScenarioFromMemory(CBinaryReader & reader, CampaignHeader & header);
 	static CampaignTravel readScenarioTravelFromMemory(CBinaryReader & reader, CampaignVersion version);
 	/// returns h3c split in parts. 0 = h3c header, 1-end - maps (binary h3m)
 	/// headerOnly - only header will be decompressed, returned vector wont have any maps
 	static std::vector<std::vector<ui8>> getFile(std::unique_ptr<CInputStream> file, const std::string & filename, bool headerOnly);
 
-	static VideoPath prologVideoName(ui8 index);
-	static AudioPath prologMusicName(ui8 index);
-	static AudioPath prologVoiceName(ui8 index);
-
 	static constexpr auto VCMP_HEADER_FILE_NAME = "header.json";
 public:
 	static std::unique_ptr<Campaign> getHeader( const std::string & name); //name - name of appropriate file

+ 137 - 0
lib/campaign/CampaignRegions.cpp

@@ -0,0 +1,137 @@
+/*
+ * CampaignRegions.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+#include "CampaignRegions.h"
+
+#include "../json/JsonNode.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+CampaignRegions::RegionDescription::RegionDescription(const JsonNode & node)
+{
+	infix = node["infix"].String();
+	pos = Point(static_cast<int>(node["x"].Float()), static_cast<int>(node["y"].Float()));
+	if(!node["labelPos"].isNull())
+		labelPos = Point(static_cast<int>(node["labelPos"]["x"].Float()), static_cast<int>(node["labelPos"]["y"].Float()));
+	else
+		labelPos = std::nullopt;
+}
+
+JsonNode CampaignRegions::RegionDescription::toJson() const
+{
+	JsonNode node;
+	node["infix"].String() = infix;
+	node["x"].Float() = pos.x;
+	node["y"].Float() = pos.y;
+	if(labelPos != std::nullopt)
+	{
+		node["labelPos"]["x"].Float() = (*labelPos).x;
+		node["labelPos"]["y"].Float() = (*labelPos).y;
+	}
+	else
+		node["labelPos"].clear();
+	return node;
+}
+
+CampaignRegions::CampaignRegions(const JsonNode & node)
+{
+	campPrefix = node["prefix"].String();
+	colorSuffixLength = static_cast<int>(node["colorSuffixLength"].Float());
+	campSuffix = node["suffix"].isNull() ? std::vector<std::string>() : std::vector<std::string>{node["suffix"].Vector()[0].String(), node["suffix"].Vector()[1].String(), node["suffix"].Vector()[2].String()};
+	campBackground = node["background"].isNull() ? "" : node["background"].String();
+
+	for(const JsonNode & desc : node["desc"].Vector())
+		regions.push_back(CampaignRegions::RegionDescription(desc));
+}
+
+JsonNode CampaignRegions::toJson() const
+{
+	JsonNode node;
+	node["prefix"].String() = campPrefix;
+	node["colorSuffixLength"].Float() = colorSuffixLength;
+	if(campSuffix.empty())
+		node["suffix"].clear();
+	else
+		node["suffix"].Vector() = JsonVector{ JsonNode(campSuffix[0]), JsonNode(campSuffix[1]), JsonNode(campSuffix[2]) };
+	if(campBackground.empty())
+		node["background"].clear();
+	else
+		node["background"].String() = campBackground;
+	node["desc"].Vector() = JsonVector();
+	for(const auto & region : regions)
+		node["desc"].Vector().push_back(region.toJson());
+	return node;
+}
+
+ImagePath CampaignRegions::getBackgroundName() const
+{
+	if(campBackground.empty())
+		return ImagePath::builtin(campPrefix + "_BG.BMP");
+	else
+		return ImagePath::builtin(campBackground);
+}
+
+Point CampaignRegions::getPosition(CampaignScenarioID which) const
+{
+	const auto & region = regions[which.getNum()];
+	return region.pos;
+}
+
+std::optional<Point> CampaignRegions::getLabelPosition(CampaignScenarioID which) const
+{
+	const auto & region = regions[which.getNum()];
+	return region.labelPos;
+}
+
+ImagePath CampaignRegions::getNameFor(CampaignScenarioID which, int colorIndex, const std::string & type) const
+{
+	const auto & region = regions[which.getNum()];
+
+	static const std::array<std::array<std::string, 8>, 3> colors = {{
+			{ "", "", "", "", "", "", "", "" },
+			{ "R", "B", "N", "G", "O", "V", "T", "P" },
+			{ "Re", "Bl", "Br", "Gr", "Or", "Vi", "Te", "Pi" }
+		}};
+
+	std::string color = colors[colorSuffixLength][colorIndex];
+
+	return ImagePath::builtin(campPrefix + region.infix + "_" + type + color + ".BMP");
+}
+
+ImagePath CampaignRegions::getAvailableName(CampaignScenarioID which, int color) const
+{
+	if(campSuffix.empty())
+		return getNameFor(which, color, "En");
+	else
+		return getNameFor(which, color, campSuffix[0]);
+}
+
+ImagePath CampaignRegions::getSelectedName(CampaignScenarioID which, int color) const
+{
+	if(campSuffix.empty())
+		return getNameFor(which, color, "Se");
+	else
+		return getNameFor(which, color, campSuffix[1]);
+}
+
+ImagePath CampaignRegions::getConqueredName(CampaignScenarioID which, int color) const
+{
+	if(campSuffix.empty())
+		return getNameFor(which, color, "Co");
+	else
+		return getNameFor(which, color, campSuffix[2]);
+}
+
+int CampaignRegions::regionsCount() const
+{
+	return regions.size();
+}
+
+VCMI_LIB_NAMESPACE_END

+ 80 - 0
lib/campaign/CampaignRegions.h

@@ -0,0 +1,80 @@
+/*
+ * CampaignRegions.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "../Point.h"
+#include "../filesystem/ResourcePath.h"
+#include "../constants/EntityIdentifiers.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class DLL_LINKAGE CampaignRegions
+{
+	// Campaign editor
+	friend class CampaignEditor;
+	friend class CampaignProperties;
+	friend class ScenarioProperties;
+
+	/// Shared prefix for all campaign images
+	std::string campPrefix;
+	/// Suffix for enabled/selected/completed campaign region maps
+	std::vector<std::string> campSuffix;
+	/// Custom background name for campaign
+	std::string campBackground;
+	/// Lookup scheme for colored campaign images location
+	int colorSuffixLength = 0;
+
+	struct DLL_LINKAGE RegionDescription
+	{
+		std::string infix;
+		Point pos;
+		std::optional<Point> labelPos;
+
+		template <typename Handler> void serialize(Handler &h)
+		{
+			h & infix;
+			h & pos;
+			h & labelPos;
+		}
+
+		RegionDescription() = default;
+		explicit RegionDescription(const JsonNode & node);
+		JsonNode toJson() const;
+	};
+
+	std::vector<RegionDescription> regions;
+
+	ImagePath getNameFor(CampaignScenarioID which, int color, const std::string & type) const;
+
+public:
+	CampaignRegions() = default;
+	explicit CampaignRegions(const JsonNode & node);
+
+	ImagePath getBackgroundName() const;
+	Point getPosition(CampaignScenarioID which) const;
+	std::optional<Point> getLabelPosition(CampaignScenarioID which) const;
+	ImagePath getAvailableName(CampaignScenarioID which, int color) const;
+	ImagePath getSelectedName(CampaignScenarioID which, int color) const;
+	ImagePath getConqueredName(CampaignScenarioID which, int color) const;
+	int regionsCount() const;
+
+	template <typename Handler> void serialize(Handler &h)
+	{
+		h & campPrefix;
+		h & colorSuffixLength;
+		h & regions;
+		h & campSuffix;
+		h & campBackground;
+	}
+
+	JsonNode toJson() const;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 34 - 0
lib/campaign/CampaignRegionsHandler.cpp

@@ -0,0 +1,34 @@
+/*
+ * CampaignRegionsHandler.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+#include "CampaignRegionsHandler.h"
+
+#include "../json/JsonNode.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+std::vector<JsonNode> CampaignRegionsHandler::loadLegacyData()
+{
+	return {};
+}
+
+void CampaignRegionsHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
+{
+	auto object = std::make_shared<CampaignRegions>(data);
+	registerObject(scope, "campaignRegion", name, objects.size());
+	objects.push_back(object);
+}
+
+void CampaignRegionsHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
+{
+	throw std::runtime_error("CampaignRegionsHandler::loadObject - load by index is not supported!");
+}
+
+VCMI_LIB_NAMESPACE_END

+ 38 - 0
lib/campaign/CampaignRegionsHandler.h

@@ -0,0 +1,38 @@
+/*
+ * CampaignRegionsHandler.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "CampaignRegions.h"
+
+#include "../IHandlerBase.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+/// Managed campaign region sets - "map" of campaign locations, with selectable scenarios
+/// Used only for .h3c campaigns. .vmap's embed campaign regions layout in its format
+class DLL_LINKAGE CampaignRegionsHandler : public IHandlerBase
+{
+public:
+	std::vector<JsonNode> loadLegacyData() override;
+
+	/// loads single object into game. Scope is namespace of this object, same as name of source mod
+	void loadObject(std::string scope, std::string name, const JsonNode & data) override;
+	void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override;
+
+	const CampaignRegions * getByIndex(int index) const
+	{
+		return objects.at(index).get();
+	}
+
+private:
+	std::vector<std::shared_ptr<CampaignRegions>> objects;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 1 - 1
lib/campaign/CampaignScenarioPrologEpilog.h

@@ -18,7 +18,7 @@ struct DLL_LINKAGE CampaignScenarioPrologEpilog
 {
 	bool hasPrologEpilog = false;
 	VideoPath prologVideo;
-	AudioPath prologMusic; // from CmpMusic.txt
+	AudioPath prologMusic;
 	AudioPath prologVoice;
 	MetaString prologText;
 

+ 50 - 190
lib/campaign/CampaignState.cpp

@@ -17,6 +17,7 @@
 #include "../mapping/CMapService.h"
 #include "../mapping/CMapInfo.h"
 #include "../mapping/CMap.h"
+#include "../mapping/MapFormatSettings.h"
 #include "../mapObjects/CGHeroInstance.h"
 #include "../serializer/JsonDeserializer.h"
 #include "../serializer/JsonSerializer.h"
@@ -33,170 +34,14 @@ void CampaignScenario::loadPreconditionRegions(ui32 regions)
 	}
 }
 
-CampaignRegions::RegionDescription CampaignRegions::RegionDescription::fromJson(const JsonNode & node)
-{
-	CampaignRegions::RegionDescription rd;
-	rd.infix = node["infix"].String();
-	rd.pos = Point(static_cast<int>(node["x"].Float()), static_cast<int>(node["y"].Float()));
-	if(!node["labelPos"].isNull())
-		rd.labelPos = Point(static_cast<int>(node["labelPos"]["x"].Float()), static_cast<int>(node["labelPos"]["y"].Float()));
-	else
-		rd.labelPos = std::nullopt;
-	return rd;
-}
-
-JsonNode CampaignRegions::RegionDescription::toJson(CampaignRegions::RegionDescription & rd)
-{
-	JsonNode node;
-	node["infix"].String() = rd.infix;
-	node["x"].Float() = rd.pos.x;
-	node["y"].Float() = rd.pos.y;
-	if(rd.labelPos != std::nullopt)
-	{
-		node["labelPos"]["x"].Float() = (*rd.labelPos).x;
-		node["labelPos"]["y"].Float() = (*rd.labelPos).y;
-	}
-	else
-		node["labelPos"].clear();
-	return node;
-}
-
-CampaignRegions CampaignRegions::fromJson(const JsonNode & node)
-{
-	CampaignRegions cr;
-	cr.campPrefix = node["prefix"].String();
-	cr.colorSuffixLength = static_cast<int>(node["colorSuffixLength"].Float());
-	cr.campSuffix = node["suffix"].isNull() ? std::vector<std::string>() : std::vector<std::string>{node["suffix"].Vector()[0].String(), node["suffix"].Vector()[1].String(), node["suffix"].Vector()[2].String()};
-	cr.campBackground = node["background"].isNull() ? "" : node["background"].String();
-
-	for(const JsonNode & desc : node["desc"].Vector())
-		cr.regions.push_back(CampaignRegions::RegionDescription::fromJson(desc));
-
-	return cr;
-}
-
-JsonNode CampaignRegions::toJson(CampaignRegions cr)
-{
-	JsonNode node;
-	node["prefix"].String() = cr.campPrefix;
-	node["colorSuffixLength"].Float() = cr.colorSuffixLength;
-	if(!cr.campSuffix.size())
-		node["suffix"].clear();
-	else
-		node["suffix"].Vector() = JsonVector{ JsonNode(cr.campSuffix[0]), JsonNode(cr.campSuffix[1]), JsonNode(cr.campSuffix[2]) };
-	if(cr.campBackground.empty())
-		node["background"].clear();
-	else
-		node["background"].String() = cr.campBackground;
-	node["desc"].Vector() = JsonVector();
-	for(auto & region : cr.regions)
-		node["desc"].Vector().push_back(CampaignRegions::RegionDescription::toJson(region));
-	return node;
-}
-
-CampaignRegions CampaignRegions::getLegacy(int campId)
-{
-	static std::vector<CampaignRegions> campDescriptions;
-	if(campDescriptions.empty()) //read once
-	{
-		const JsonNode config(JsonPath::builtin("config/campaign_regions.json"));
-		for(const JsonNode & campaign : config["campaign_regions"].Vector())
-			campDescriptions.push_back(CampaignRegions::fromJson(campaign));
-	}
-
-	return campDescriptions.at(campId);
-}
-
-ImagePath CampaignRegions::getBackgroundName() const
-{
-	if(campBackground.empty())
-		return ImagePath::builtin(campPrefix + "_BG.BMP");
-	else
-		return ImagePath::builtin(campBackground);
-}
-
-Point CampaignRegions::getPosition(CampaignScenarioID which) const
-{
-	auto const & region = regions[which.getNum()];
-	return region.pos;
-}
-
-std::optional<Point> CampaignRegions::getLabelPosition(CampaignScenarioID which) const
-{
-	auto const & region = regions[which.getNum()];
-	return region.labelPos;
-}
-
-ImagePath CampaignRegions::getNameFor(CampaignScenarioID which, int colorIndex, std::string type) const
-{
-	auto const & region = regions[which.getNum()];
-
-	static const std::array<std::array<std::string, 8>, 3> colors = {{
-		{ "", "", "", "", "", "", "", "" },
-		{ "R", "B", "N", "G", "O", "V", "T", "P" },
-		{ "Re", "Bl", "Br", "Gr", "Or", "Vi", "Te", "Pi" }
-	}};
-
-	std::string color = colors[colorSuffixLength][colorIndex];
-
-	return ImagePath::builtin(campPrefix + region.infix + "_" + type + color + ".BMP");
-}
-
-ImagePath CampaignRegions::getAvailableName(CampaignScenarioID which, int color) const
-{
-	if(campSuffix.empty())
-		return getNameFor(which, color, "En");
-	else
-		return getNameFor(which, color, campSuffix[0]);
-}
-
-ImagePath CampaignRegions::getSelectedName(CampaignScenarioID which, int color) const
-{
-	if(campSuffix.empty())
-		return getNameFor(which, color, "Se");
-	else
-		return getNameFor(which, color, campSuffix[1]);
-}
-
-ImagePath CampaignRegions::getConqueredName(CampaignScenarioID which, int color) const
-{
-	if(campSuffix.empty())
-		return getNameFor(which, color, "Co");
-	else
-		return getNameFor(which, color, campSuffix[2]);
-}
-
-
-bool CampaignBonus::isBonusForHero() const
-{
-	return type == CampaignBonusType::SPELL ||
-		   type == CampaignBonusType::MONSTER ||
-		   type == CampaignBonusType::ARTIFACT ||
-		   type == CampaignBonusType::SPELL_SCROLL ||
-		   type == CampaignBonusType::PRIMARY_SKILL ||
-		   type == CampaignBonusType::SECONDARY_SKILL;
-}
-
-void CampaignHeader::loadLegacyData(ui8 campId)
-{
-	campaignRegions = CampaignRegions::getLegacy(campId);
-	numberOfScenarios = LIBRARY->generaltexth->getCampaignLength(campId);
-}
-
-void CampaignHeader::loadLegacyData(CampaignRegions regions, int numOfScenario)
-{
-	campaignRegions = regions;
-	numberOfScenarios = numOfScenario;
-}
-
 bool CampaignHeader::playerSelectedDifficulty() const
 {
 	return difficultyChosenByPlayer;
 }
 
-bool CampaignHeader::formatVCMI() const
+CampaignVersion CampaignHeader::getFormat() const
 {
-	return version == CampaignVersion::VCMI;
+	return version;
 }
 
 std::string CampaignHeader::getDescriptionTranslated() const
@@ -373,7 +218,7 @@ void CampaignState::setCurrentMapAsConquered(std::vector<CGHeroInstance *> heroe
 {
 	boost::range::sort(heroes, [](const CGHeroInstance * a, const CGHeroInstance * b)
 	{
-		return a->getValueForCampaign() > b->getValueForCampaign();
+		return CGHeroInstance::compareCampaignValue(a, b);
 	});
 
 	logGlobal->info("Scenario %d of campaign %s (%s) has been completed", currentMap->getNum(), getFilename(), getNameTranslated());
@@ -533,43 +378,45 @@ std::set<CampaignScenarioID> Campaign::allScenarios() const
 
 void Campaign::overrideCampaign()
 {
-	const JsonNode node = JsonUtils::assembleFromFiles("config/campaignOverrides.json");
-	for (auto & entry : node.Struct())
-		if(filename == entry.first)
-		{
-			if(!entry.second["regions"].isNull() && !entry.second["scenarioCount"].isNull())
-				loadLegacyData(CampaignRegions::fromJson(entry.second["regions"]), entry.second["scenarioCount"].Integer());
-			if(!entry.second["loadingBackground"].isNull())
-				loadingBackground = ImagePath::builtin(entry.second["loadingBackground"].String());
-			if(!entry.second["videoRim"].isNull())
-				videoRim = ImagePath::builtin(entry.second["videoRim"].String());
-			if(!entry.second["introVideo"].isNull())
-				introVideo = VideoPath::builtin(entry.second["introVideo"].String());
-			if(!entry.second["outroVideo"].isNull())
-				outroVideo = VideoPath::builtin(entry.second["outroVideo"].String());
-		}
+	const JsonNode & overrides = LIBRARY->mapFormat->campaignOverrides(filename);
+
+	if(!overrides["regions"].isNull())
+		campaignRegions = CampaignRegions(overrides["regions"]);
+	if (!overrides["scenarioCount"].isNull())
+		numberOfScenarios = overrides["scenarioCount"].Integer();
+	if(!overrides["loadingBackground"].isNull())
+		loadingBackground = ImagePath::builtin(overrides["loadingBackground"].String());
+	if(!overrides["videoRim"].isNull())
+		videoRim = ImagePath::builtin(overrides["videoRim"].String());
+	if(!overrides["introVideo"].isNull())
+		introVideo = VideoPath::builtin(overrides["introVideo"].String());
+	if(!overrides["outroVideo"].isNull())
+		outroVideo = VideoPath::builtin(overrides["outroVideo"].String());
+	if(!overrides["heroGemSorceress"].isNull())
+		gemSorceressID = HeroTypeID(*LIBRARY->identifiersHandler->getIdentifier("hero", overrides["heroGemSorceress"]));
+	if(!overrides["heroYogWizard"].isNull())
+		yogWizardID = HeroTypeID(*LIBRARY->identifiersHandler->getIdentifier("hero", overrides["heroYogWizard"]));
+
+	restrictGarrisonsAI	= overrides["restrictedGarrisonsForAI"].Bool();
 }
 
 void Campaign::overrideCampaignScenarios()
 {
-	const JsonNode node = JsonUtils::assembleFromFiles("config/campaignOverrides.json");
-	for (auto & entry : node.Struct())
-		if(filename == entry.first)
+	const JsonNode & overrides = LIBRARY->mapFormat->campaignOverrides(filename);
+
+	if(!overrides["scenarios"].isNull())
+	{
+		auto sc = overrides["scenarios"].Vector();
+		for(int i = 0; i < sc.size(); i++)
 		{
-			if(!entry.second["scenarios"].isNull())
-			{
-				auto sc = entry.second["scenarios"].Vector();
-				for(int i = 0; i < sc.size(); i++)
-				{
-					auto it = scenarios.begin();
-					std::advance(it, i);
-					if(!sc.at(i)["voiceProlog"].isNull())
-						it->second.prolog.prologVoice = AudioPath::builtin(sc.at(i)["voiceProlog"].String());
-					if(!sc.at(i)["voiceEpilog"].isNull())
-						it->second.epilog.prologVoice = AudioPath::builtin(sc.at(i)["voiceEpilog"].String());
-				}
-			}
+			auto it = scenarios.begin();
+			std::advance(it, i);
+			if(!sc.at(i)["voiceProlog"].isNull())
+				it->second.prolog.prologVoice = AudioPath::builtin(sc.at(i)["voiceProlog"].String());
+			if(!sc.at(i)["voiceEpilog"].isNull())
+				it->second.epilog.prologVoice = AudioPath::builtin(sc.at(i)["voiceEpilog"].String());
 		}
+	}
 }
 
 int Campaign::scenariosCount() const
@@ -590,4 +437,17 @@ bool CampaignState::isCampaignFinished() const
 	return conqueredScenarios() == allScenarios();
 }
 
+HeroTypeID CampaignHeader::getYogWizardID() const
+{
+	return yogWizardID;
+}
+HeroTypeID CampaignHeader::getGemSorceressID() const
+{
+	return gemSorceressID;
+}
+bool CampaignHeader::restrictedGarrisonsForAI() const
+{
+	return restrictGarrisonsAI;
+}
+
 VCMI_LIB_NAMESPACE_END

+ 45 - 86
lib/campaign/CampaignState.h

@@ -9,14 +9,14 @@
  */
 #pragma once
 
-#include "../GameConstants.h"
+#include "CampaignBonus.h"
+#include "CampaignRegions.h"
+#include "CampaignScenarioPrologEpilog.h"
+
 #include "../filesystem/ResourcePath.h"
+#include "../gameState/HighScore.h"
 #include "../serializer/Serializeable.h"
 #include "../texts/TextLocalizationContainer.h"
-#include "CampaignConstants.h"
-#include "CampaignScenarioPrologEpilog.h"
-#include "../gameState/HighScore.h"
-#include "../Point.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -30,61 +30,6 @@ class CMapInfo;
 class JsonNode;
 class IGameInfoCallback;
 
-class DLL_LINKAGE CampaignRegions
-{
-	// Campaign editor
-	friend class CampaignEditor;
-	friend class CampaignProperties;
-	friend class ScenarioProperties;
-
-	std::string campPrefix;
-	std::vector<std::string> campSuffix;
-	std::string campBackground;
-	int colorSuffixLength;
-
-	struct DLL_LINKAGE RegionDescription
-	{
-		std::string infix;
-		Point pos;
-		std::optional<Point> labelPos;
-
-		template <typename Handler> void serialize(Handler &h)
-		{
-			h & infix;
-			h & pos;
-			h & labelPos;
-		}
-
-		static CampaignRegions::RegionDescription fromJson(const JsonNode & node);
-		static JsonNode toJson(CampaignRegions::RegionDescription & rd);
-	};
-
-	std::vector<RegionDescription> regions;
-
-	ImagePath getNameFor(CampaignScenarioID which, int color, std::string type) const;
-
-public:
-	ImagePath getBackgroundName() const;
-	Point getPosition(CampaignScenarioID which) const;
-	std::optional<Point> getLabelPosition(CampaignScenarioID which) const;
-	ImagePath getAvailableName(CampaignScenarioID which, int color) const;
-	ImagePath getSelectedName(CampaignScenarioID which, int color) const;
-	ImagePath getConqueredName(CampaignScenarioID which, int color) const;
-
-	template <typename Handler> void serialize(Handler &h)
-	{
-		h & campPrefix;
-		h & colorSuffixLength;
-		h & regions;
-		h & campSuffix;
-		h & campBackground;
-	}
-
-	static CampaignRegions fromJson(const JsonNode & node);
-	static JsonNode toJson(CampaignRegions cr);
-	static CampaignRegions getLegacy(int campId);
-};
-
 class DLL_LINKAGE CampaignHeader : public boost::noncopyable
 {
 	friend class CampaignHandler;
@@ -112,17 +57,17 @@ class DLL_LINKAGE CampaignHeader : public boost::noncopyable
 	VideoPath introVideo;
 	VideoPath outroVideo;
 
+	HeroTypeID yogWizardID;
+	HeroTypeID gemSorceressID;
+
 	int numberOfScenarios = 0;
 	bool difficultyChosenByPlayer = false;
-
-	void loadLegacyData(ui8 campId);
-	void loadLegacyData(CampaignRegions regions, int numOfScenario);
+	bool restrictGarrisonsAI = false;
 
 	TextContainerRegistrable textContainer;
-
 public:
 	bool playerSelectedDifficulty() const;
-	bool formatVCMI() const;
+	CampaignVersion getFormat() const;
 
 	std::string getDescriptionTranslated() const;
 	std::string getNameTranslated() const;
@@ -139,6 +84,10 @@ public:
 	VideoPath getIntroVideo() const;
 	VideoPath getOutroVideo() const;
 
+	HeroTypeID getYogWizardID() const;
+	HeroTypeID getGemSorceressID() const;
+	bool restrictedGarrisonsForAI() const;
+
 	const CampaignRegions & getRegions() const;
 	TextContainerRegistrable & getTexts();
 
@@ -154,6 +103,8 @@ public:
 		h & campaignVersion;
 		h & creationDateTime;
 		h & difficultyChosenByPlayer;
+		if (h.hasFeature(Handler::Version::CAMPAIGN_BONUSES))
+			h & restrictGarrisonsAI;
 		h & filename;
 		h & modName;
 		h & music;
@@ -163,26 +114,11 @@ public:
 		h & videoRim;
 		h & introVideo;
 		h & outroVideo;
-	}
-};
-
-struct DLL_LINKAGE CampaignBonus
-{
-	CampaignBonusType type = CampaignBonusType::NONE;
-
-	//purpose depends on type
-	int32_t info1 = 0;
-	int32_t info2 = 0;
-	int32_t info3 = 0;
-
-	bool isBonusForHero() const;
-
-	template <typename Handler> void serialize(Handler &h)
-	{
-		h & type;
-		h & info1;
-		h & info2;
-		h & info3;
+		if (h.hasFeature(Handler::Version::CAMPAIGN_BONUSES))
+		{
+			h & yogWizardID;
+			h & gemSorceressID;
+		}
 	}
 };
 
@@ -221,7 +157,30 @@ struct DLL_LINKAGE CampaignTravel
 		h & artifactsKeptByHero;
 		h & startOptions;
 		h & playerColor;
-		h & bonusesToChoose;
+		if (h.hasFeature(Handler::Version::CAMPAIGN_BONUSES))
+		{
+			h & bonusesToChoose;
+		}
+		else
+		{
+			struct OldBonus{
+				CampaignBonusType type = {};
+				int32_t info1 = 0;
+				int32_t info2 = 0;
+				int32_t info3 = 0;
+
+				void serialize(Handler &h)
+				{
+					h & type;
+					h & info1;
+					h & info2;
+					h & info3;
+				}
+			};
+
+			std::vector<OldBonus> oldBonuses;
+			h & oldBonuses;
+		}
 	}
 };
 

+ 7 - 5
lib/constants/EntityIdentifiers.cpp

@@ -55,11 +55,9 @@ const QueryID QueryID::NONE(-1);
 const QueryID QueryID::CLIENT(-2);
 const HeroTypeID HeroTypeID::NONE(-1);
 const HeroTypeID HeroTypeID::RANDOM(-2);
-const HeroTypeID HeroTypeID::GEM(27);
-const HeroTypeID HeroTypeID::SOLMYR(45);
-const HeroTypeID HeroTypeID::CAMP_STRONGEST(0xFFFD);
-const HeroTypeID HeroTypeID::CAMP_GENERATED(0xFFFE);
-const HeroTypeID HeroTypeID::CAMP_RANDOM(0xFFFF);
+const HeroTypeID HeroTypeID::CAMP_STRONGEST(-3);
+const HeroTypeID HeroTypeID::CAMP_GENERATED(-2);
+const HeroTypeID HeroTypeID::CAMP_RANDOM(-1);
 
 const ObjectInstanceID ObjectInstanceID::NONE(-1);
 
@@ -258,6 +256,8 @@ si32 HeroTypeID::decode(const std::string & identifier)
 {
 	if (identifier == "random")
 		return -2;
+	if (identifier == "strongest")
+		return -3;
 	return resolveIdentifier("hero", identifier);
 }
 
@@ -267,6 +267,8 @@ std::string HeroTypeID::encode(const si32 index)
 		return "";
 	if (index == -2)
 		return "random";
+	if (index == -3)
+		return "strongest";
 	return LIBRARY->heroTypes()->getByIndex(index)->getJsonKey();
 }
 

+ 10 - 6
lib/constants/EntityIdentifiers.h

@@ -115,9 +115,6 @@ public:
 
 	static const HeroTypeID NONE;
 	static const HeroTypeID RANDOM;
-	static const HeroTypeID GEM; // aka Gem, Sorceress in campaign
-	static const HeroTypeID SOLMYR; // aka Young Yog in campaigns
-
 	static const HeroTypeID CAMP_STRONGEST;
 	static const HeroTypeID CAMP_GENERATED;
 	static const HeroTypeID CAMP_RANDOM;
@@ -1065,9 +1062,9 @@ public:
 		MITHRIL,
 		COUNT,
 
-		WOOD_AND_ORE = 127,  // special case for town bonus resource
-		COMMON = 0xFD, // campaign bonus
-		RARE = 0xFE, // campaign bonus
+		WOOD_AND_ORE = -4,  // special case for town bonus resource
+		COMMON = -3, // campaign bonus
+		RARE = -2, // campaign bonus
 		NONE = -1
 	};
 };
@@ -1122,6 +1119,13 @@ public:
 	static const CampaignScenarioID NONE;
 };
 
+class DLL_LINKAGE CampaignRegionID : public StaticIdentifier<CampaignRegionID>
+{
+public:
+	using StaticIdentifier<CampaignRegionID>::StaticIdentifier;
+};
+
+
 // Deprecated
 // TODO: remove
 using Obj = MapObjectID;

+ 3 - 3
lib/entities/artifact/CArtHandler.cpp

@@ -166,10 +166,10 @@ std::shared_ptr<CArtifact> CArtHandler::loadFromJson(const std::string & scope,
 	const JsonNode & graphics = node["graphics"];
 	art->image = graphics["image"].String();
 
-	if(!graphics["large"].isNull())
-		art->large = graphics["large"].String();
+	if(!graphics["scenarioBonus"].isNull())
+		art->scenarioBonus = graphics["scenarioBonus"].String();
 	else
-		art->large = art->image;
+		art->scenarioBonus = art->image; // MOD COMPATIBILITY fallback for pre-1.7 mods
 
 	art->advMapDef = graphics["map"].String();
 

+ 1 - 2
lib/entities/artifact/CArtifact.cpp

@@ -102,7 +102,6 @@ std::string CArtifact::getModScope() const
 void CArtifact::registerIcons(const IconRegistar & cb) const
 {
 	cb(getIconIndex(), 0, "ARTIFACT", image);
-	cb(getIconIndex(), 0, "ARTIFACTLARGE", large);
 }
 
 ArtifactID CArtifact::getId() const
@@ -359,7 +358,7 @@ void CArtifact::setImage(int32_t newIconIndex, const std::string & newImage, con
 {
 	iconIndex = newIconIndex;
 	image = newImage;
-	large = newLargeImage;
+	scenarioBonus = newLargeImage;
 }
 
 

+ 2 - 1
lib/entities/artifact/CArtifact.h

@@ -91,7 +91,6 @@ class DLL_LINKAGE CArtifact final : public Artifact, public CBonusSystemNode,
 {
 	ArtifactID id;
 	std::string image;
-	std::string large; // big image for custom artifacts, used in drag & drop
 	std::string advMapDef; // used for adventure map object
 	std::string modScope;
 	std::string identifier;
@@ -105,6 +104,8 @@ public:
 	/// Bonuses that are created for each instance of artifact
 	std::vector<std::shared_ptr<Bonus>> instanceBonuses;
 
+	std::string scenarioBonus;
+
 	EArtifactClass aClass = EArtifactClass::ART_SPECIAL;
 	bool onlyOnWaterMap;
 

+ 3 - 0
lib/entities/artifact/CArtifactInstance.cpp

@@ -131,6 +131,9 @@ void CChargedArtifactInstance::onChargesChanged()
 	auto artInst = static_cast<CArtifactInstance*>(this);
 	const auto artType = artInst->getType();
 
+	if(!artType->isCharged())
+		return;
+
 	const auto bonusSelector = artType->getDischargeCondition() == DischargeArtifactCondition::SPELLCAST ?
 		Selector::type()(BonusType::SPELL) : Selector::all;
 

+ 0 - 87
lib/entities/building/CBuildingHandler.cpp

@@ -1,87 +0,0 @@
-/*
- * CBuildingHandler.cpp, part of VCMI engine
- *
- * Authors: listed in file AUTHORS in main folder
- *
- * License: GNU General Public License v2.0 or later
- * Full text of license available in license.txt file, in main folder
- *
- */
-#include "StdInc.h"
-#include "CBuildingHandler.h"
-#include "GameLibrary.h"
-#include "../faction/CTown.h"
-#include "../faction/CTownHandler.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-BuildingID CBuildingHandler::campToERMU(int camp, FactionID townType, const std::set<BuildingID> & builtBuildings)
-{
-	static const std::vector<BuildingID> campToERMU = 
-	{
-		BuildingID::TOWN_HALL, BuildingID::CITY_HALL,
-		BuildingID::CAPITOL, BuildingID::FORT, BuildingID::CITADEL, BuildingID::CASTLE, BuildingID::TAVERN,
-		BuildingID::BLACKSMITH, BuildingID::MARKETPLACE, BuildingID::RESOURCE_SILO, BuildingID::NONE,
-		BuildingID::MAGES_GUILD_1, BuildingID::MAGES_GUILD_2, BuildingID::MAGES_GUILD_3, BuildingID::MAGES_GUILD_4,
-		BuildingID::MAGES_GUILD_5,
-		BuildingID::SHIPYARD, BuildingID::GRAIL,
-		BuildingID::SPECIAL_1, BuildingID::SPECIAL_2, BuildingID::SPECIAL_3, BuildingID::SPECIAL_4	
-	}; //creature generators with banks - handled separately
-	
-	if (camp < campToERMU.size())
-	{
-		return campToERMU[camp];
-	}
-
-	static const std::vector<int> hordeLvlsPerTType[GameConstants::F_NUMBER] = 
-	{
-		{2}, {1}, {1,4}, {0,2},	{0}, {0}, {0}, {0}, {0}
-	};
-
-	int curPos = static_cast<int>(campToERMU.size());
-	for (int i=0; i<(*LIBRARY->townh)[townType]->town->creatures.size(); ++i)
-	{
-		if(camp == curPos) //non-upgraded
-			return BuildingID(30 + i);
-		curPos++;
-		if(camp == curPos) //upgraded
-			return BuildingID(37 + i);
-		curPos++;
-
-		if (i < 5) // last two levels don't have reserved horde ID. Yet another H3C weirdeness
-		{
-			if (vstd::contains(hordeLvlsPerTType[townType.getNum()], i))
-			{
-				if (camp == curPos)
-				{
-					if (hordeLvlsPerTType[townType.getNum()][0] == i)
-					{
-						BuildingID dwellingID(BuildingID::getDwellingFromLevel(hordeLvlsPerTType[townType.getNum()][0], 1));
-
-						if(vstd::contains(builtBuildings, dwellingID)) //if upgraded dwelling is built
-							return BuildingID::HORDE_1_UPGR;
-						else //upgraded dwelling not presents
-							return BuildingID::HORDE_1;
-					}
-					else
-					{
-						if(hordeLvlsPerTType[townType.getNum()].size() > 1)
-						{
-							BuildingID dwellingID(BuildingID::getDwellingFromLevel(hordeLvlsPerTType[townType.getNum()][1], 1));
-
-							if(vstd::contains(builtBuildings, dwellingID)) //if upgraded dwelling is built
-								return BuildingID::HORDE_2_UPGR;
-							else //upgraded dwelling not presents
-								return BuildingID::HORDE_2;
-						}
-					}
-				}
-			}
-			curPos++;
-		}
-	}
-	assert(0);
-	return BuildingID::NONE; //not found
-}
-
-VCMI_LIB_NAMESPACE_END

+ 0 - 22
lib/entities/building/CBuildingHandler.h

@@ -1,22 +0,0 @@
-/*
- * CBuildingHandler.h, part of VCMI engine
- *
- * Authors: listed in file AUTHORS in main folder
- *
- * License: GNU General Public License v2.0 or later
- * Full text of license available in license.txt file, in main folder
- *
- */
-#pragma once
-
-#include "../../constants/EntityIdentifiers.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-class DLL_LINKAGE CBuildingHandler
-{
-public:
-	static BuildingID campToERMU(int camp, FactionID townType, const std::set<BuildingID> & builtBuildings);
-};
-
-VCMI_LIB_NAMESPACE_END

+ 1 - 0
lib/entities/faction/CTown.h

@@ -31,6 +31,7 @@ struct DLL_LINKAGE CStructure
 	int3 pos;
 	AnimationPath defName;
 	ImagePath borderName;
+	ImagePath campaignBonus;
 	ImagePath areaName;
 	std::string identifier;
 

+ 1 - 0
lib/entities/faction/CTownHandler.cpp

@@ -450,6 +450,7 @@ void CTownHandler::loadStructure(CTown &town, const std::string & stringID, cons
 	ret->hiddenUpgrade = source["hidden"].Bool();
 	ret->defName = AnimationPath::fromJson(source["animation"]);
 	ret->borderName = ImagePath::fromJson(source["border"]);
+	ret->campaignBonus = ImagePath::fromJson(source["campaignBonus"]);
 	ret->areaName = ImagePath::fromJson(source["area"]);
 
 	town.clientInfo.structures.emplace_back(ret);

+ 49 - 41
lib/gameState/CGameStateCampaign.cpp

@@ -17,7 +17,6 @@
 #include "../entities/artifact/ArtifactUtils.h"
 #include "../entities/artifact/CArtifact.h"
 #include "../entities/building/CBuilding.h"
-#include "../entities/building/CBuildingHandler.h"
 #include "../entities/hero/CHeroClass.h"
 #include "../entities/hero/CHero.h"
 #include "../mapping/CMapEditManager.h"
@@ -29,6 +28,7 @@
 #include "../StartInfo.h"
 #include "../mapping/CMap.h"
 #include "../CPlayerState.h"
+#include "mapping/MapFormatSettings.h"
 
 #include <vstd/RNG.h>
 #include <vcmi/HeroTypeService.h>
@@ -66,8 +66,8 @@ std::optional<CampaignScenarioID> CGameStateCampaign::getHeroesSourceScenario()
 	auto campaignState = gameState->scenarioOps->campState;
 	auto bonus = currentBonus();
 
-	if(bonus && bonus->type == CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO)
-		return static_cast<CampaignScenarioID>(bonus->info2);
+	if(bonus && bonus->getType() == CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO)
+		return bonus->getValue<CampaignBonusHeroesFromScenario>().scenario;
 
 	return campaignState->lastScenario();
 }
@@ -211,17 +211,18 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(vstd::RNG & randomGenerat
 void CGameStateCampaign::placeCampaignHeroes(vstd::RNG & randomGenerator)
 {
 	// place bonus hero
-	auto campaignState = gameState->scenarioOps->campState;
-	auto campaignBonus = campaignState->getBonus(*campaignState->currentScenario());
-	bool campaignGiveHero = campaignBonus && campaignBonus->type == CampaignBonusType::HERO;
+	const auto & campaignState = gameState->scenarioOps->campState;
+	const auto & campaignBonus = campaignState->getBonus(*campaignState->currentScenario());
+	bool campaignGiveHero = campaignBonus && campaignBonus->getType() == CampaignBonusType::HERO;
 
 	if(campaignGiveHero)
 	{
-		auto playerColor = PlayerColor(campaignBonus->info1);
-		auto it = gameState->scenarioOps->playerInfos.find(playerColor);
+		const auto & campaignBonusValue = campaignBonus->getValue<CampaignBonusStartingHero>();
+		const auto & playerColor = campaignBonusValue.startingPlayer;
+		const auto & it = gameState->scenarioOps->playerInfos.find(playerColor);
 		if(it != gameState->scenarioOps->playerInfos.end())
 		{
-			HeroTypeID heroTypeId = HeroTypeID(campaignBonus->info2);
+			HeroTypeID heroTypeId = campaignBonusValue.hero;
 			if(heroTypeId == HeroTypeID::CAMP_RANDOM) // random bonus hero
 			{
 				heroTypeId = gameState->pickUnusedHeroTypeRandomly(randomGenerator, playerColor);
@@ -309,20 +310,22 @@ void CGameStateCampaign::giveCampaignBonusToHero(CGHeroInstance * hero)
 	assert(curBonus->isBonusForHero());
 
 	//apply bonus
-	switch(curBonus->type)
+	switch(curBonus->getType())
 	{
 		case CampaignBonusType::SPELL:
 		{
-			hero->addSpellToSpellbook(SpellID(curBonus->info2));
+			const auto & bonusValue = curBonus->getValue<CampaignBonusSpell>();
+			hero->addSpellToSpellbook(bonusValue.spell);
 			break;
 		}
 		case CampaignBonusType::MONSTER:
 		{
+			const auto & bonusValue = curBonus->getValue<CampaignBonusCreatures>();
 			for(int i = 0; i < GameConstants::ARMY_SIZE; i++)
 			{
 				if(hero->slotEmpty(SlotID(i)))
 				{
-					hero->addToSlot(SlotID(i), CreatureID(curBonus->info2), curBonus->info3);
+					hero->addToSlot(SlotID(i), bonusValue.creature, bonusValue.amount);
 					break;
 				}
 			}
@@ -330,13 +333,15 @@ void CGameStateCampaign::giveCampaignBonusToHero(CGHeroInstance * hero)
 		}
 		case CampaignBonusType::ARTIFACT:
 		{
-			if(!gameState->giveHeroArtifact(hero, static_cast<ArtifactID>(curBonus->info2)))
+			const auto & bonusValue = curBonus->getValue<CampaignBonusArtifact>();
+			if(!gameState->giveHeroArtifact(hero, bonusValue.artifact))
 				logGlobal->error("Cannot give starting artifact - no free slots!");
 			break;
 		}
 		case CampaignBonusType::SPELL_SCROLL:
 		{
-			const auto scroll = gameState->createScroll(SpellID(curBonus->info2));
+			const auto & bonusValue = curBonus->getValue<CampaignBonusSpellScroll>();
+			const auto scroll = gameState->createScroll(bonusValue.spell);
 			const auto slot = ArtifactUtils::getArtAnyPosition(hero, scroll->getTypeId());
 			if(ArtifactUtils::isSlotEquipment(slot) || ArtifactUtils::isSlotBackpack(slot))
 				gameState->map->putArtifactInstance(*hero, scroll->getId(), slot);
@@ -346,10 +351,10 @@ void CGameStateCampaign::giveCampaignBonusToHero(CGHeroInstance * hero)
 		}
 		case CampaignBonusType::PRIMARY_SKILL:
 		{
-			const ui8 * ptr = reinterpret_cast<const ui8 *>(&curBonus->info2);
+			const auto & bonusValue = curBonus->getValue<CampaignBonusPrimarySkill>();
 			for(auto skill : PrimarySkill::ALL_SKILLS())
 			{
-				int val = ptr[skill.getNum()];
+				int val = bonusValue.amounts[skill.getNum()];
 				if(val == 0)
 					continue;
 
@@ -361,7 +366,8 @@ void CGameStateCampaign::giveCampaignBonusToHero(CGHeroInstance * hero)
 		}
 		case CampaignBonusType::SECONDARY_SKILL:
 		{
-			hero->setSecSkillLevel(SecondarySkill(curBonus->info2), curBonus->info3, ChangeValueMode::ABSOLUTE);
+			const auto & bonusValue = curBonus->getValue<CampaignBonusSecondarySkill>();
+			hero->setSecSkillLevel(bonusValue.skill, bonusValue.mastery, ChangeValueMode::ABSOLUTE);
 			break;
 		}
 	}
@@ -526,7 +532,7 @@ void CGameStateCampaign::generateCampaignHeroesToReplace()
 void CGameStateCampaign::initHeroes()
 {
 	auto chosenBonus = currentBonus();
-	if (chosenBonus && chosenBonus->isBonusForHero() && chosenBonus->info1 != HeroTypeID::CAMP_GENERATED.getNum()) //exclude generated heroes
+	if (chosenBonus && chosenBonus->isBonusForHero() && chosenBonus->getTargetedHero() != HeroTypeID::CAMP_GENERATED.getNum()) //exclude generated heroes
 	{
 		//find human player
 		PlayerColor humanPlayer=PlayerColor::NEUTRAL;
@@ -542,12 +548,12 @@ void CGameStateCampaign::initHeroes()
 
 		const auto & heroes = gameState->players.at(humanPlayer).getHeroes();
 
-		if (chosenBonus->info1 == HeroTypeID::CAMP_STRONGEST.getNum()) //most powerful
+		if (chosenBonus->getTargetedHero() == HeroTypeID::CAMP_STRONGEST.getNum()) //most powerful
 		{
 			int maxB = -1;
 			for (int b=0; b<heroes.size(); ++b)
 			{
-				if (maxB == -1 || heroes[b]->getValueForCampaign() > heroes[maxB]->getValueForCampaign())
+				if(maxB == -1 || CGHeroInstance::compareCampaignValue(heroes[b], heroes[maxB]))
 				{
 					maxB = b;
 				}
@@ -561,7 +567,7 @@ void CGameStateCampaign::initHeroes()
 		{
 			for (auto & hero : heroes)
 			{
-				if (hero->getHeroTypeID().getNum() == chosenBonus->info1)
+				if (hero->getHeroTypeID().getNum() == chosenBonus->getTargetedHero())
 				{
 					giveCampaignBonusToHero(hero);
 					break;
@@ -571,9 +577,10 @@ void CGameStateCampaign::initHeroes()
 	}
 
 	auto campaignState = gameState->scenarioOps->campState;
-	auto * yog = gameState->getUsedHero(HeroTypeID::SOLMYR);
-	if (yog && boost::starts_with(campaignState->getFilename(), "DATA/YOG") && campaignState->currentScenario()->getNum() == 2)
+	if (campaignState->getYogWizardID().hasValue() && boost::starts_with(campaignState->getFilename(), "DATA/YOG") && campaignState->currentScenario()->getNum() == 2)
 	{
+		auto * yog = gameState->getUsedHero(campaignState->getYogWizardID());
+		assert(yog);
 		assert(yog->isCampaignYog());
 		gameState->giveHeroArtifact(yog, ArtifactID::ANGELIC_ALLIANCE);
 	}
@@ -595,18 +602,17 @@ void CGameStateCampaign::initStartingResources()
 		return ret;
 	};
 
-	auto chosenBonus = currentBonus();
-	if(chosenBonus && chosenBonus->type == CampaignBonusType::RESOURCE)
+	const auto & chosenBonus = currentBonus();
+	if(chosenBonus && chosenBonus->getType() == CampaignBonusType::RESOURCE)
 	{
+		const auto & bonusValue = chosenBonus->getValue<CampaignBonusStartingResources>();
+
 		std::vector<const PlayerSettings *> people = getHumanPlayerInfo(); //players we will give resource bonus
 		for(const PlayerSettings *ps : people)
 		{
 			std::vector<GameResID> res; //resources we will give
-			switch (chosenBonus->info1)
+			switch (bonusValue.resource.toEnum())
 			{
-				case 0: case 1: case 2: case 3: case 4: case 5: case 6:
-					res.push_back(chosenBonus->info1);
-					break;
 				case EGameResID::COMMON: //wood+ore
 					res.push_back(GameResID(EGameResID::WOOD));
 					res.push_back(GameResID(EGameResID::ORE));
@@ -618,14 +624,12 @@ void CGameStateCampaign::initStartingResources()
 					res.push_back(GameResID(EGameResID::GEMS));
 					break;
 				default:
-					assert(0);
+					res.push_back(bonusValue.resource);
 					break;
 			}
-			//increasing resource quantity
+
 			for (auto & re : res)
-			{
-				gameState->players.at(ps->color).resources[re] += chosenBonus->info2;
-			}
+				gameState->players.at(ps->color).resources[re] += bonusValue.amount;
 		}
 	}
 }
@@ -637,9 +641,11 @@ void CGameStateCampaign::initTowns()
 	if (!chosenBonus)
 		return;
 
-	if (chosenBonus->type != CampaignBonusType::BUILDING)
+	if (chosenBonus->getType() != CampaignBonusType::BUILDING)
 		return;
 
+	const auto & bonusValue = chosenBonus->getValue<CampaignBonusBuilding>();
+
 	for (const auto & townID : gameState->map->getAllTowns())
 	{
 		auto town = gameState->getTown(townID);
@@ -656,11 +662,13 @@ void CGameStateCampaign::initTowns()
 		if (town->anchorPos() != pi.posOfMainTown)
 			continue;
 
-		BuildingID newBuilding;
-		if(gameState->scenarioOps->campState->formatVCMI())
-			newBuilding = BuildingID(chosenBonus->info1);
-		else
-			newBuilding = CBuildingHandler::campToERMU(chosenBonus->info1, town->getFactionID(), town->getBuildings());
+		BuildingID newBuilding = bonusValue.buildingDecoded;
+
+		if (bonusValue.buildingH3M.hasValue())
+		{
+			auto mapping = LIBRARY->mapFormat->getMapping(gameState->scenarioOps->campState->getFormat());
+			newBuilding = mapping.remapBuilding(town->getFactionID(), bonusValue.buildingH3M);
+		}
 
 		// Build granted building & all prerequisites - e.g. Mages Guild Lvl 3 should also give Mages Guild Lvl 1 & 2
 		while(true)
@@ -687,7 +695,7 @@ bool CGameStateCampaign::playerHasStartingHero(PlayerColor playerColor) const
 	if (!campaignBonus)
 		return false;
 
-	if(campaignBonus->type == CampaignBonusType::HERO && playerColor == PlayerColor(campaignBonus->info1))
+	if(campaignBonus->getType() == CampaignBonusType::HERO && playerColor == PlayerColor(campaignBonus->getValue<CampaignBonusStartingHero>().startingPlayer))
 		return true;
 	return false;
 }

+ 1 - 1
lib/gameState/CGameStateCampaign.h

@@ -15,7 +15,7 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-struct CampaignBonus;
+class CampaignBonus;
 struct CampaignTravel;
 class CGHeroInstance;
 class CGameState;

+ 11 - 4
lib/json/JsonBonus.cpp

@@ -239,10 +239,17 @@ static void loadBonusAddInfo(CAddInfo & var, BonusType type, const JsonNode & va
 		case BonusType::SPELL_BEFORE_ATTACK:
 		case BonusType::SPELL_AFTER_ATTACK:
 			// 3 numbers
-			var.resize(3);
-			var[0] = value[0].Integer();
-			var[1] = value[1].Integer();
-			var[2] = value[2].Integer();
+			if (value.isNumber())
+			{
+				var = getFirstValue(value).Integer();
+			}
+			else
+			{
+				var.resize(3);
+				var[0] = value[0].Integer();
+				var[1] = value[1].Integer();
+				var[2] = value[2].Integer();
+			}
 			break;
 		case BonusType::MULTIHEX_UNIT_ATTACK:
 		case BonusType::MULTIHEX_ENEMY_ATTACK:

+ 9 - 0
lib/json/JsonValidator.cpp

@@ -119,6 +119,13 @@ static std::string notCheck(JsonValidator & validator, const JsonNode & baseSche
 	return "";
 }
 
+static std::string ifCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	if (validator.check(schema, data).empty())
+		return validator.check(baseSchema["then"], data);
+	return "";
+}
+
 static std::string enumCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
 {
 	for(const auto & enumEntry : schema.Vector())
@@ -544,6 +551,8 @@ JsonValidator::TValidatorMap createCommonFields()
 	ret["type"]  = typeCheck;
 	ret["not"]   = notCheck;
 	ret["$ref"]  = refCheck;
+	ret["if"]  = ifCheck;
+	ret["then"]  = emptyCheck; // implemented as part of "if check"
 
 	// fields that don't need implementation
 	ret["title"] = emptyCheck;

+ 2 - 2
lib/mapObjectConstructors/CObjectClassesHandler.h

@@ -45,7 +45,7 @@ public:
 };
 
 /// Main class responsible for creation of all adventure map objects
-class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase, boost::noncopyable
+class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase
 {
 	/// list of object handlers, each of them handles only one type
 	std::vector< std::unique_ptr<ObjectClass> > mapObjectTypes;
@@ -106,4 +106,4 @@ public:
 	std::string getJsonKey(MapObjectID type) const;
 };
 
-VCMI_LIB_NAMESPACE_END
+VCMI_LIB_NAMESPACE_END

+ 23 - 36
lib/mapObjects/CGHeroInstance.cpp

@@ -748,19 +748,30 @@ uint64_t CGHeroInstance::getValueForDiplomacy() const
 	return heroStrength * armyStrength;
 }
 
-uint32_t CGHeroInstance::getValueForCampaign() const
+bool CGHeroInstance::compareCampaignValue(const CGHeroInstance * left, const CGHeroInstance * right)
 {
-	/// Determined by testing H3: hero is preferred for transfer in campaigns if total sum of his primary skills and his secondary skill levels is greatest
-	uint32_t score = 0;
-	score += getPrimSkillLevel(PrimarySkill::ATTACK);
-	score += getPrimSkillLevel(PrimarySkill::DEFENSE);
-	score += getPrimSkillLevel(PrimarySkill::SPELL_POWER);
-	score += getPrimSkillLevel(PrimarySkill::DEFENSE);
+	// https://heroes.thelazy.net/index.php/Power_rating
 
-	for (const auto& secondary : secSkills)
-		score += secondary.second;
+	uint32_t leftLevel = left->level;
+	uint64_t leftExperience = left->exp;
+	uint32_t leftPrimary = left->getPrimSkillLevel(PrimarySkill::ATTACK) + left->getPrimSkillLevel(PrimarySkill::DEFENSE) + left->getPrimSkillLevel(PrimarySkill::SPELL_POWER) + left->getPrimSkillLevel(PrimarySkill::DEFENSE);
+	uint32_t leftPrimaryAndLevel = leftPrimary + leftLevel;
 
-	return score;
+	uint32_t rightLevel = right->level;
+	uint64_t rightExperience = right->exp;
+	uint32_t rightPrimary = right->getPrimSkillLevel(PrimarySkill::ATTACK) + right->getPrimSkillLevel(PrimarySkill::DEFENSE) + right->getPrimSkillLevel(PrimarySkill::SPELL_POWER) + right->getPrimSkillLevel(PrimarySkill::DEFENSE);
+	uint32_t rightPrimaryAndLevel = rightPrimary + rightLevel;
+
+	if (leftPrimaryAndLevel != rightPrimaryAndLevel)
+		return leftPrimaryAndLevel > rightPrimaryAndLevel;
+
+	if (leftLevel != rightLevel)
+		return leftLevel > rightLevel;
+
+	if (leftExperience != rightExperience)
+		return leftExperience > rightExperience;
+
+	return left->getHeroTypeID() > right->getHeroTypeID();
 }
 
 ui64 CGHeroInstance::getTotalStrength() const
@@ -1804,37 +1815,13 @@ void CGHeroInstance::fillUpgradeInfo(UpgradeInfo & info, const CStackInstance &
 bool CGHeroInstance::isCampaignYog() const
 {
 	const StartInfo *si = cb->getStartInfo();
-
-	// it would be nice to find a way to move this hack to config/mapOverrides.json
-	if(!si || !si->campState)
-		return false;
-
-	std::string campaign = si->campState->getFilename();
-	if (!boost::starts_with(campaign, "DATA/YOG")) // "Birth of a Barbarian"
-		return false;
-
-	if (getHeroTypeID() != HeroTypeID::SOLMYR) // Yog (based on Solmyr)
-		return false;
-
-	return true;
+	return si && si->campState &&si->campState->getYogWizardID() == getHeroTypeID();
 }
 
 bool CGHeroInstance::isCampaignGem() const
 {
 	const StartInfo *si = cb->getStartInfo();
-
-	// it would be nice to find a way to move this hack to config/mapOverrides.json
-	if(!si || !si->campState)
-		return false;
-
-	std::string campaign = si->campState->getFilename();
-	if (!boost::starts_with(campaign, "DATA/GEM") &&  !boost::starts_with(campaign, "DATA/FINAL")) // "New Beginning" and "Unholy Alliance"
-		return false;
-
-	if (getHeroTypeID() != HeroTypeID::GEM) // Yog (based on Solmyr)
-		return false;
-
-	return true;
+	return si && si->campState &&si->campState->getGemSorceressID() == getHeroTypeID();
 }
 
 ResourceSet CGHeroInstance::dailyIncome() const

+ 2 - 1
lib/mapObjects/CGHeroInstance.h

@@ -212,7 +212,8 @@ public:
 	double getMagicStrength() const; // takes knowledge / spell power skill but also current mana, whether the hero owns a spell-book and whether that books contains anything into account
 	double getHeroStrength() const; // includes fighting and magic strength
 
-	uint32_t getValueForCampaign() const;
+	/// Returns true if 'left' hero is stronger than 'right' when considering campaign transfer priority
+	static bool compareCampaignValue(const CGHeroInstance * left, const CGHeroInstance * right);
 	uint64_t getValueForDiplomacy() const;
 	
 	ui64 getTotalStrength() const; // includes fighting strength and army strength

+ 1 - 1
lib/mapObjects/ObstacleSetHandler.h

@@ -102,7 +102,7 @@ private:
 };
 
 // TODO: Instantiate ObstacleSetHandler
-class DLL_LINKAGE ObstacleSetHandler : public IHandlerBase, boost::noncopyable
+class DLL_LINKAGE ObstacleSetHandler : public IHandlerBase
 {
 public:
 

+ 1 - 0
lib/mapping/CMapHeader.cpp

@@ -39,6 +39,7 @@ FactionID PlayerInfo::defaultCastle() const
 	if(isFactionRandom)
 		return FactionID::RANDOM;
 
+	assert(!allowedFactions.empty());
 	if(!allowedFactions.empty())
 		return *allowedFactions.begin();
 

+ 3 - 13
lib/mapping/CMapService.cpp

@@ -10,6 +10,8 @@
 #include "StdInc.h"
 #include "CMapService.h"
 
+#include "MapFormatSettings.h"
+
 #include "../json/JsonUtils.h"
 #include "../filesystem/Filesystem.h"
 #include "../filesystem/CBinaryReader.h"
@@ -163,23 +165,11 @@ std::unique_ptr<IMapLoader> CMapService::getMapLoader(std::unique_ptr<CInputStre
 	}
 }
 
-static JsonNode loadPatches(const std::string & path)
-{
-	JsonNode node = JsonUtils::assembleFromFiles(path);
-	for (auto & entry : node.Struct())
-		JsonUtils::validate(entry.second, "vcmi:mapHeader", "patch for " + entry.first);
-
-	node.setModScope(ModScope::scopeMap());
-	return node;
-}
-
 std::unique_ptr<IMapPatcher> CMapService::getMapPatcher(std::string scenarioName)
 {
-	static const JsonNode node = loadPatches("config/mapOverrides.json");
-
 	boost::to_lower(scenarioName);
 	logGlobal->debug("Request to patch map %s", scenarioName);
-	return std::unique_ptr<IMapPatcher>(new CMapPatcher(node[scenarioName]));
+	return std::make_unique<CMapPatcher>(LIBRARY->mapFormat->mapOverrides(scenarioName));
 }
 
 VCMI_LIB_NAMESPACE_END

+ 4 - 2
lib/mapping/MapFeaturesH3M.h

@@ -16,8 +16,7 @@ enum class EMapFormat : uint8_t;
 
 struct MapFormatFeaturesH3M
 {
-public:
-	static MapFormatFeaturesH3M find(EMapFormat format, uint32_t hotaVersion);
+private:
 	static MapFormatFeaturesH3M getFeaturesROE();
 	static MapFormatFeaturesH3M getFeaturesAB();
 	static MapFormatFeaturesH3M getFeaturesSOD();
@@ -25,8 +24,11 @@ public:
 	static MapFormatFeaturesH3M getFeaturesWOG();
 	static MapFormatFeaturesH3M getFeaturesHOTA(uint32_t hotaVersion);
 
+public:
 	MapFormatFeaturesH3M() = default;
 
+	static MapFormatFeaturesH3M find(EMapFormat format, uint32_t hotaVersion);
+
 	// number of bytes in bitmask of appropriate type
 
 	int factionsBytes;

+ 9 - 52
lib/mapping/MapFormatH3M.cpp

@@ -13,7 +13,7 @@
 
 #include "CMap.h"
 #include "MapReaderH3M.h"
-#include "MapFormat.h"
+#include "MapFormatSettings.h"
 
 #include "../CCreatureHandler.h"
 #include "../texts/CGeneralTextHandler.h"
@@ -111,52 +111,6 @@ void CMapLoaderH3M::init()
 	//map->banWaterContent(); //Not sure if force this for custom scenarios
 }
 
-static MapIdentifiersH3M generateMapping(EMapFormat format)
-{
-	auto features = MapFormatFeaturesH3M::find(format, 0);
-	MapIdentifiersH3M identifierMapper;
-
-	if(features.levelROE)
-		identifierMapper.loadMapping(LIBRARY->engineSettings()->getValue(EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA));
-	if(features.levelAB)
-		identifierMapper.loadMapping(LIBRARY->engineSettings()->getValue(EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE));
-	if(features.levelSOD)
-		identifierMapper.loadMapping(LIBRARY->engineSettings()->getValue(EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH));
-	if(features.levelCHR)
-		identifierMapper.loadMapping(LIBRARY->engineSettings()->getValue(EGameSettings::MAP_FORMAT_CHRONICLES));
-	if(features.levelWOG)
-		identifierMapper.loadMapping(LIBRARY->engineSettings()->getValue(EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS));
-	if(features.levelHOTA0)
-		identifierMapper.loadMapping(LIBRARY->engineSettings()->getValue(EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS));
-
-	return identifierMapper;
-}
-
-static std::map<EMapFormat, MapIdentifiersH3M> generateMappings()
-{
-	std::map<EMapFormat, MapIdentifiersH3M> result;
-	auto addMapping = [&result](EMapFormat format)
-	{
-		try
-		{
-			result[format] = generateMapping(format);
-		}
-		catch(const std::runtime_error &)
-		{
-			// unsupported map format - skip
-		}
-	};
-
-	addMapping(EMapFormat::ROE);
-	addMapping(EMapFormat::AB);
-	addMapping(EMapFormat::SOD);
-	addMapping(EMapFormat::CHR);
-	addMapping(EMapFormat::HOTA);
-	addMapping(EMapFormat::WOG);
-
-	return result;
-}
-
 void CMapLoaderH3M::readHeader()
 {
 	// Map version
@@ -233,12 +187,10 @@ void CMapLoaderH3M::readHeader()
 		reader->setFormatLevel(features);
 	}
 
-	// optimization - load mappings only once to avoid slow parsing of map headers for map list
-	static const std::map<EMapFormat, MapIdentifiersH3M> identifierMappers = generateMappings();
-	if (!identifierMappers.count(mapHeader->version))
+	if (!LIBRARY->mapFormat->isSupported(mapHeader->version))
 		throw std::runtime_error("Unsupported map format! Format ID " + std::to_string(static_cast<int>(mapHeader->version)));
 
-	const MapIdentifiersH3M & identifierMapper = identifierMappers.at(mapHeader->version);
+	const MapIdentifiersH3M & identifierMapper = LIBRARY->mapFormat->getMapping(mapHeader->version);
 
 	reader->setIdentifierRemapper(identifierMapper);
 
@@ -300,7 +252,12 @@ void CMapLoaderH3M::readPlayerInfo()
 		const bool allFactionsAllowed = playerInfo.isFactionRandom && allowedFactions.size() == features.factionsCount;
 
 		if(!allFactionsAllowed)
-			playerInfo.allowedFactions = allowedFactions;
+		{
+			if (!allowedFactions.empty())
+				playerInfo.allowedFactions = allowedFactions;
+			else
+				logGlobal->warn("Map '%s': Player %d has no allowed factions to play! Ignoring.", mapName, i);
+		}
 
 		playerInfo.hasMainTown = reader->readBool();
 		if(playerInfo.hasMainTown)

+ 96 - 0
lib/mapping/MapFormatSettings.cpp

@@ -0,0 +1,96 @@
+/*
+ * MapFormatSettings.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+#include "MapFormatSettings.h"
+
+#include "MapFeaturesH3M.h"
+
+#include "../GameLibrary.h"
+#include "../IGameSettings.h"
+#include "../json/JsonUtils.h"
+#include "../modding/ModScope.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+MapIdentifiersH3M MapFormatSettings::generateMapping(EMapFormat format)
+{
+	auto features = MapFormatFeaturesH3M::find(format, 0);
+	MapIdentifiersH3M identifierMapper;
+
+	if(features.levelROE)
+		identifierMapper.loadMapping(LIBRARY->engineSettings()->getValue(EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA));
+	if(features.levelAB)
+		identifierMapper.loadMapping(LIBRARY->engineSettings()->getValue(EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE));
+	if(features.levelSOD)
+		identifierMapper.loadMapping(LIBRARY->engineSettings()->getValue(EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH));
+	if(features.levelCHR)
+		identifierMapper.loadMapping(LIBRARY->engineSettings()->getValue(EGameSettings::MAP_FORMAT_CHRONICLES));
+	if(features.levelWOG)
+		identifierMapper.loadMapping(LIBRARY->engineSettings()->getValue(EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS));
+	if(features.levelHOTA0)
+		identifierMapper.loadMapping(LIBRARY->engineSettings()->getValue(EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS));
+
+	return identifierMapper;
+}
+
+std::map<CampaignVersion, EMapFormat> MapFormatSettings::generateCampaignMapping()
+{
+	return {
+		{CampaignVersion::RoE,  EMapFormat::ROE },
+		{CampaignVersion::AB,   EMapFormat::AB  },
+		{CampaignVersion::SoD,  EMapFormat::SOD },
+		{CampaignVersion::WoG,  EMapFormat::WOG },
+		{CampaignVersion::Chr,  EMapFormat::CHR },
+		{CampaignVersion::HotA, EMapFormat::HOTA}
+	};
+}
+
+std::map<EMapFormat, MapIdentifiersH3M> MapFormatSettings::generateMappings()
+{
+	std::map<EMapFormat, MapIdentifiersH3M> result;
+	auto addMapping = [&result](EMapFormat format)
+	{
+		try
+		{
+			result[format] = generateMapping(format);
+			logMod->trace("Loaded map format support for %d", static_cast<int>(format));
+		}
+		catch(const std::runtime_error &)
+		{
+			// unsupported map format - skip
+			logMod->debug("Failed to load map format support for %d", static_cast<int>(format));
+		}
+	};
+
+	addMapping(EMapFormat::ROE);
+	addMapping(EMapFormat::AB);
+	addMapping(EMapFormat::SOD);
+	addMapping(EMapFormat::CHR);
+	addMapping(EMapFormat::HOTA);
+	addMapping(EMapFormat::WOG);
+
+	return result;
+}
+
+MapFormatSettings::MapFormatSettings()
+	: mapping(generateMappings())
+	, campaignToMap(generateCampaignMapping())
+	, campaignOverridesConfig(JsonUtils::assembleFromFiles("config/campaignOverrides.json"))
+	, mapOverridesConfig(JsonUtils::assembleFromFiles("config/mapOverrides.json"))
+{
+	for (auto & entry : mapOverridesConfig.Struct())
+		JsonUtils::validate(entry.second, "vcmi:mapHeader", "patch for " + entry.first);
+
+	campaignOverridesConfig.setModScope(ModScope::scopeMap());
+	mapOverridesConfig.setModScope(ModScope::scopeMap());
+}
+
+VCMI_LIB_NAMESPACE_END

+ 65 - 0
lib/mapping/MapFormatSettings.h

@@ -0,0 +1,65 @@
+/*
+ * MapFormatSettings.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+
+#include "MapIdentifiersH3M.h"
+#include "MapFormat.h"
+#include "../campaign/CampaignConstants.h"
+#include "../json/JsonNode.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class MapFormatSettings : boost::noncopyable
+{
+	static MapIdentifiersH3M generateMapping(EMapFormat format);
+	static std::map<EMapFormat, MapIdentifiersH3M> generateMappings();
+	static std::map<CampaignVersion, EMapFormat> generateCampaignMapping();
+
+	std::map<EMapFormat, MapIdentifiersH3M> mapping;
+	std::map<CampaignVersion, EMapFormat> campaignToMap;
+
+	JsonNode campaignOverridesConfig;
+	JsonNode mapOverridesConfig;
+public:
+	MapFormatSettings();
+
+	bool isSupported(EMapFormat format) const
+	{
+		return mapping.count(format) != 0;
+	}
+
+	bool isSupported(CampaignVersion format) const
+	{
+		return isSupported(campaignToMap.at(format));
+	}
+
+	const MapIdentifiersH3M & getMapping(EMapFormat format) const
+	{
+		return mapping.at(format);
+	}
+
+	const MapIdentifiersH3M & getMapping(CampaignVersion format) const
+	{
+		return mapping.at(campaignToMap.at(format));
+	}
+
+	const JsonNode & campaignOverrides(const std::string & campaignName)
+	{
+		return campaignOverridesConfig[campaignName];
+	}
+
+	const JsonNode & mapOverrides(const std::string & mapName)
+	{
+		return mapOverridesConfig[mapName];
+	}
+};
+
+VCMI_LIB_NAMESPACE_END

+ 31 - 0
lib/mapping/MapIdentifiersH3M.cpp

@@ -89,6 +89,12 @@ void MapIdentifiersH3M::loadMapping(const JsonNode & mapping)
 		}
 	}
 
+	for (auto entry : mapping["campaignVideo"].Struct())
+		mappingCampaignVideo[entry.second.Integer()] = VideoPath::builtinTODO(entry.first);
+
+	for (auto entry : mapping["campaignMusic"].Struct())
+		mappingCampaignMusic[entry.second.Integer()] = AudioPath::builtinTODO(entry.first);
+
 	loadMapping(mappingHeroPortrait, mapping["portraits"], "hero");
 	loadMapping(mappingBuilding, mapping["buildingsCommon"], "building.core:random");
 	loadMapping(mappingFaction, mapping["factions"], "faction");
@@ -98,6 +104,7 @@ void MapIdentifiersH3M::loadMapping(const JsonNode & mapping)
 	loadMapping(mappingTerrain, mapping["terrains"], "terrain");
 	loadMapping(mappingArtifact, mapping["artifacts"], "artifact");
 	loadMapping(mappingSecondarySkill, mapping["skills"], "skill");
+	loadMapping(mappingCampaignRegions, mapping["campaignRegions"], "campaignRegion");
 }
 
 void MapIdentifiersH3M::remapTemplate(ObjectTemplate & objectTemplate)
@@ -213,4 +220,28 @@ SecondarySkill MapIdentifiersH3M::remap(SecondarySkill input) const
 	return input;
 }
 
+CampaignRegionID MapIdentifiersH3M::remap(CampaignRegionID input) const
+{
+	if (!mappingCampaignRegions.count(input))
+		throw std::out_of_range("Campaign region with ID " + std::to_string(input.getNum()) + " is not defined");
+
+	return mappingCampaignRegions.at(input);
+}
+
+VideoPath MapIdentifiersH3M::remapCampaignVideo(int input) const
+{
+	if (!mappingCampaignVideo.count(input))
+		throw std::out_of_range("Campaign video with ID " + std::to_string(input) + " is not defined");
+
+	return mappingCampaignVideo.at(input);
+}
+
+AudioPath MapIdentifiersH3M::remapCampaignMusic(int input) const
+{
+	if (!mappingCampaignMusic.count(input))
+		throw std::out_of_range("Campaign music with ID " + std::to_string(input) + " is not defined");
+
+	return mappingCampaignMusic.at(input);
+}
+
 VCMI_LIB_NAMESPACE_END

+ 7 - 1
lib/mapping/MapIdentifiersH3M.h

@@ -31,7 +31,7 @@ struct ObjectTypeIdentifier
 	}
 };
 
-class MapIdentifiersH3M
+class DLL_LINKAGE MapIdentifiersH3M
 {
 	std::map<BuildingID, BuildingID> mappingBuilding;
 	std::map<FactionID, std::map<BuildingID, BuildingID>> mappingFactionBuilding;
@@ -43,6 +43,9 @@ class MapIdentifiersH3M
 	std::map<TerrainId, TerrainId> mappingTerrain;
 	std::map<ArtifactID, ArtifactID> mappingArtifact;
 	std::map<SecondarySkill, SecondarySkill> mappingSecondarySkill;
+	std::map<CampaignRegionID, CampaignRegionID> mappingCampaignRegions;
+	std::map<int, VideoPath> mappingCampaignVideo;
+	std::map<int, AudioPath> mappingCampaignMusic;
 
 	std::map<AnimationPath, AnimationPath> mappingObjectTemplate;
 	std::map<ObjectTypeIdentifier, ObjectTypeIdentifier> mappingObjectIndex;
@@ -54,6 +57,8 @@ public:
 
 	void remapTemplate(ObjectTemplate & objectTemplate);
 
+	AudioPath remapCampaignMusic(int index) const;
+	VideoPath remapCampaignVideo(int index) const;
 	BuildingID remapBuilding(std::optional<FactionID> owner, BuildingID input) const;
 	HeroTypeID remapPortrait(HeroTypeID input) const;
 	FactionID remap(FactionID input) const;
@@ -63,6 +68,7 @@ public:
 	TerrainId remap(TerrainId input) const;
 	ArtifactID remap(ArtifactID input) const;
 	SecondarySkill remap(SecondarySkill input) const;
+	CampaignRegionID remap(CampaignRegionID input) const;
 
 };
 

+ 2 - 0
lib/modding/ContentTypeHandler.cpp

@@ -18,6 +18,7 @@
 #include "../BattleFieldHandler.h"
 #include "../CCreatureHandler.h"
 #include "../CConfigHandler.h"
+#include "../campaign/CampaignRegionsHandler.h"
 #include "../entities/artifact/CArtHandler.h"
 #include "../entities/faction/CTownHandler.h"
 #include "../entities/hero/CHeroClassHandler.h"
@@ -245,6 +246,7 @@ void CContentHandler::init()
 	handlers.insert(std::make_pair("artifacts", ContentTypeHandler(LIBRARY->arth.get(), "artifact")));
 	handlers.insert(std::make_pair("bonuses", ContentTypeHandler(LIBRARY->bth.get(), "bonus")));
 	handlers.insert(std::make_pair("creatures", ContentTypeHandler(LIBRARY->creh.get(), "creature")));
+	handlers.insert(std::make_pair("campaignRegions", ContentTypeHandler(LIBRARY->campaignRegions.get(), "campaignRegion")));
 	handlers.insert(std::make_pair("factions", ContentTypeHandler(LIBRARY->townh.get(), "faction")));
 	handlers.insert(std::make_pair("objects", ContentTypeHandler(LIBRARY->objtypeh.get(), "object")));
 	handlers.insert(std::make_pair("heroes", ContentTypeHandler(LIBRARY->heroh.get(), "hero")));

+ 2 - 1
lib/serializer/ESerializationVersion.h

@@ -46,8 +46,9 @@ enum class ESerializationVersion : int32_t
 	SERVER_STATISTICS, // statistics now only saved on server
 	OPPOSITE_SIDE_LIMITER_OWNER, // opposite side limiter no longer stores owner in itself
 	UNIVERSITY_CONFIG, // town university is configurable
+	CAMPAIGN_BONUSES, // new format for scenario bonuses in campaigns
 
-	CURRENT = UNIVERSITY_CONFIG,
+	CURRENT = CAMPAIGN_BONUSES,
 };
 
 static_assert(ESerializationVersion::MINIMAL <= ESerializationVersion::CURRENT, "Invalid serialization version definition!");

+ 0 - 12
lib/texts/CGeneralTextHandler.cpp

@@ -159,7 +159,6 @@ CGeneralTextHandler::CGeneralTextHandler():
 	readToVector("core.heroscrn", "DATA/HEROSCRN.TXT" );
 	readToVector("core.tentcolr", "DATA/TENTCOLR.TXT" );
 	readToVector("core.skilllev", "DATA/SKILLLEV.TXT" );
-	readToVector("core.cmpmusic", "DATA/CMPMUSIC.TXT" );
 	readToVector("core.minename", "DATA/MINENAME.TXT" );
 	readToVector("core.mineevnt", "DATA/MINEEVNT.TXT" );
 	readToVector("core.xtrainfo", "DATA/XTRAINFO.TXT" );
@@ -288,8 +287,6 @@ CGeneralTextHandler::CGeneralTextHandler():
 				}
 			}
 			while (parser.endLine() && !text.empty());
-
-			scenariosCountPerCampaign.push_back(region);
 		}
 	}
 }
@@ -306,15 +303,6 @@ int32_t CGeneralTextHandler::pluralText(const int32_t textIndex, const int32_t c
 	return textIndex + 1;
 }
 
-size_t CGeneralTextHandler::getCampaignLength(size_t campaignID) const
-{
-	assert(campaignID < scenariosCountPerCampaign.size());
-
-	if(campaignID < scenariosCountPerCampaign.size())
-		return scenariosCountPerCampaign[campaignID];
-	return 0;
-}
-
 std::string CGeneralTextHandler::getPreferredLanguage()
 {
 	assert(!settings["general"]["language"].String().empty());

+ 0 - 5
lib/texts/CGeneralTextHandler.h

@@ -42,9 +42,6 @@ class DLL_LINKAGE CGeneralTextHandler: public TextLocalizationContainer
 {
 	void readToVector(const std::string & sourceID, const std::string & sourceName);
 
-	/// number of scenarios in specific campaign. TODO: move to a better location
-	std::vector<size_t> scenariosCountPerCampaign;
-
 public:
 	LegacyTextContainer allTexts;
 
@@ -78,8 +75,6 @@ public:
 
 	int32_t pluralText(int32_t textIndex, int32_t count) const;
 
-	size_t getCampaignLength(size_t campaignID) const;
-
 	CGeneralTextHandler();
 	CGeneralTextHandler(const CGeneralTextHandler&) = delete;
 	CGeneralTextHandler operator=(const CGeneralTextHandler&) = delete;

+ 2 - 1
mapeditor/campaigneditor/campaigneditor.cpp

@@ -19,6 +19,7 @@
 
 #include "../../lib/VCMIDirs.h"
 #include "../../lib/json/JsonNode.h"
+#include "../../lib/campaign/CampaignRegionsHandler.h"
 #include "../../lib/campaign/CampaignState.h"
 #include "../../lib/mapping/CMap.h"
 
@@ -200,7 +201,7 @@ void CampaignEditor::on_actionNew_triggered()
 		return;
 	
 	campaignState = std::make_unique<CampaignState>();
-	campaignState->campaignRegions = CampaignRegions::getLegacy(0);
+	campaignState->campaignRegions = *LIBRARY->campaignRegions->getByIndex(0);
 	for (int i = 0; i < campaignState->campaignRegions.regions.size(); i++)
 	{
 		CampaignScenario s;

+ 2 - 1
mapeditor/campaigneditor/campaignproperties.cpp

@@ -13,6 +13,7 @@
 
 #include "../../lib/GameLibrary.h"
 #include "../../lib/texts/CGeneralTextHandler.h"
+#include "../../lib/campaign/CampaignRegionsHandler.h"
 #include "../../lib/campaign/CampaignState.h"
 #include "../../lib/constants/StringConstants.h"
 #include "../../lib/json/JsonNode.h"
@@ -89,7 +90,7 @@ void CampaignProperties::on_buttonBox_clicked(QAbstractButton * button)
 void CampaignProperties::on_comboBoxRegionPreset_currentIndexChanged(int index)
 {
 	if(ui->comboBoxRegionPreset->count() == 21 && ui->comboBoxRegionPreset->currentIndex() != 20)
-		regions = CampaignRegions::getLegacy(ui->comboBoxRegionPreset->currentIndex());
+		regions = *LIBRARY->campaignRegions->getByIndex(index);
 	
 	loadRegion();
 }

+ 22 - 8
mapeditor/campaigneditor/scenarioproperties.cpp

@@ -218,11 +218,13 @@ void ScenarioProperties::reloadMapRelatedUi()
 			for(int i = 0; i < ui->comboBoxStartingBonusPlayerPosition->count(); ++i) // copy from player dropdown
 				comboBoxPlayer->addItem(ui->comboBoxStartingBonusPlayerPosition->itemText(i), ui->comboBoxStartingBonusPlayerPosition->itemData(i));
 
+			const auto & bonusValue = bonus.getValue<CampaignBonusHeroesFromScenario>();
+
 			// set selected
-			int index = comboBoxPlayer->findData(bonus.info1);
+			int index = comboBoxPlayer->findData(bonusValue.startingPlayer.getNum());
 			if(index != -1)
 				comboBoxPlayer->setCurrentIndex(index);
-			index = comboBoxOption->findData(bonus.info2);
+			index = comboBoxOption->findData(bonusValue.scenario.getNum());
 			if(index != -1)
 				comboBoxOption->setCurrentIndex(index);
 
@@ -337,12 +339,24 @@ void ScenarioProperties::on_buttonBox_clicked(QAbstractButton * button)
 		{
 			for (int i = 0; i < ui->tableWidgetStartingCrossover->rowCount(); ++i)
 			{
-				CampaignBonus bonus;
-				bonus.type = ui->radioButtonStartingOptionHeroCrossover->isChecked() ? CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO : CampaignBonusType::HERO;
 				QComboBox* comboBoxOption = qobject_cast<QComboBox*>(ui->tableWidgetStartingCrossover->cellWidget(i, 0));
 				QComboBox* comboBoxPlayer = qobject_cast<QComboBox*>(ui->tableWidgetStartingCrossover->cellWidget(i, 1));
-				bonus.info1 = comboBoxPlayer->currentData().toInt();
-				bonus.info2 = comboBoxOption->currentData().toInt();
+				CampaignBonus bonus;
+
+				if (ui->radioButtonStartingOptionHeroCrossover->isChecked())
+				{
+					bonus = CampaignBonusHeroesFromScenario{
+						PlayerColor(comboBoxPlayer->currentData().toInt()),
+						CampaignScenarioID(comboBoxOption->currentData().toInt())
+					};
+				}
+				else
+				{
+					bonus = CampaignBonusStartingHero{
+						PlayerColor(comboBoxPlayer->currentData().toInt()),
+						HeroTypeID(comboBoxOption->currentData().toInt())
+					};
+				}
 				campaignState->scenarios.at(scenario).travelOptions.bonusesToChoose.push_back(bonus);
 			}
 		}
@@ -476,8 +490,8 @@ void ScenarioProperties::on_pushButtonStartingAdd_clicked()
 	}
 	else
 	{
-		CampaignBonus bonus;
-		bonus.type = CampaignBonusType::SPELL;
+		CampaignBonus bonus = CampaignBonusSpell{ HeroTypeID(), SpellID() };
+
 		if(StartingBonus::showStartingBonus(PlayerColor(ui->comboBoxStartingBonusPlayerPosition->currentData().toInt()), map, bonus))
 		{
 			QListWidgetItem * item = new QListWidgetItem(StartingBonus::getBonusListTitle(bonus, map));

+ 168 - 134
mapeditor/campaigneditor/startingbonus.cpp

@@ -139,131 +139,139 @@ void StartingBonus::loadBonus()
 			comboBox->setCurrentIndex(index);
 	};
 
-	switch (bonus.type)
+	switch(bonus.getType())
 	{
-	case CampaignBonusType::SPELL:
-		ui->radioButtonSpell->setChecked(true);
-		on_radioButtonSpell_toggled();
-		setComboBoxValue(ui->comboBoxSpellRecipient, bonus.info1);
-		setComboBoxValue(ui->comboBoxSpellSpell, bonus.info2);
-		break;
-	case CampaignBonusType::MONSTER:
-		ui->radioButtonCreature->setChecked(true);
-		on_radioButtonCreature_toggled();
-		setComboBoxValue(ui->comboBoxCreatureRecipient, bonus.info1);
-		setComboBoxValue(ui->comboBoxCreatureCreatureType, bonus.info2);
-		ui->spinBoxCreatureQuantity->setValue(bonus.info3);
-		break;
-	case CampaignBonusType::BUILDING:
-		ui->radioButtonBuilding->setChecked(true);
-		on_radioButtonBuilding_toggled();
-		setComboBoxValue(ui->comboBoxBuildingBuilding, bonus.info1);
-		break;
-	case CampaignBonusType::ARTIFACT:
-		ui->radioButtonArtifact->setChecked(true);
-		on_radioButtonArtifact_toggled();
-		setComboBoxValue(ui->comboBoxArtifactRecipient, bonus.info1);
-		setComboBoxValue(ui->comboBoxArtifactArtifact, bonus.info2);
-		break;
-	case CampaignBonusType::SPELL_SCROLL:
-		ui->radioButtonSpellScroll->setChecked(true);
-		on_radioButtonSpellScroll_toggled();
-		setComboBoxValue(ui->comboBoxSpellScrollRecipient, bonus.info1);
-		setComboBoxValue(ui->comboBoxSpellScrollSpell, bonus.info2);
-		break;
-	case CampaignBonusType::PRIMARY_SKILL:
-		ui->radioButtonPrimarySkill->setChecked(true);
-		on_radioButtonPrimarySkill_toggled();
-		setComboBoxValue(ui->comboBoxPrimarySkillRecipient, bonus.info1);
-		ui->spinBoxPrimarySkillAttack->setValue((bonus.info2 >> 0) & 0xff);
-		ui->spinBoxPrimarySkillDefense->setValue((bonus.info2 >> 8) & 0xff);
-		ui->spinBoxPrimarySkillSpell->setValue((bonus.info2 >> 16) & 0xff);
-		ui->spinBoxPrimarySkillKnowledge->setValue((bonus.info2 >> 24) & 0xff);
-		break;
-	case CampaignBonusType::SECONDARY_SKILL:
-		ui->radioButtonSecondarySkill->setChecked(true);
-		on_radioButtonSecondarySkill_toggled();
-		setComboBoxValue(ui->comboBoxSecondarySkillRecipient, bonus.info1);
-		setComboBoxValue(ui->comboBoxSecondarySkillSecondarySkill, bonus.info2);
-		setComboBoxValue(ui->comboBoxSecondarySkillMastery, bonus.info3);
-		break;
-	case CampaignBonusType::RESOURCE:
-		ui->radioButtonResource->setChecked(true);
-		on_radioButtonResource_toggled();
-		setComboBoxValue(ui->comboBoxResourceResourceType, bonus.info1);
-		ui->spinBoxResourceQuantity->setValue(bonus.info2);
-		break;
-	
-	default:
-		break;
+		case CampaignBonusType::SPELL:
+		{
+			const auto & bonusValue = bonus.getValue<CampaignBonusSpell>();
+			ui->radioButtonSpell->setChecked(true);
+			on_radioButtonSpell_toggled();
+			setComboBoxValue(ui->comboBoxSpellRecipient, bonusValue.hero.getNum());
+			setComboBoxValue(ui->comboBoxSpellSpell, bonusValue.spell.getNum());
+			break;
+		}
+		case CampaignBonusType::MONSTER:
+		{
+			const auto & bonusValue = bonus.getValue<CampaignBonusCreatures>();
+			ui->radioButtonCreature->setChecked(true);
+			on_radioButtonCreature_toggled();
+			setComboBoxValue(ui->comboBoxCreatureRecipient, bonusValue.hero.getNum());
+			setComboBoxValue(ui->comboBoxCreatureCreatureType, bonusValue.creature.getNum());
+			ui->spinBoxCreatureQuantity->setValue(bonusValue.amount);
+			break;
+		}
+		case CampaignBonusType::BUILDING:
+		{
+			const auto & bonusValue = bonus.getValue<CampaignBonusBuilding>();
+			ui->radioButtonBuilding->setChecked(true);
+			on_radioButtonBuilding_toggled();
+			setComboBoxValue(ui->comboBoxBuildingBuilding, bonusValue.buildingDecoded.getNum());
+			break;
+		}
+		case CampaignBonusType::ARTIFACT:
+		{
+			const auto & bonusValue = bonus.getValue<CampaignBonusArtifact>();
+			ui->radioButtonArtifact->setChecked(true);
+			on_radioButtonArtifact_toggled();
+			setComboBoxValue(ui->comboBoxArtifactRecipient, bonusValue.hero.getNum());
+			setComboBoxValue(ui->comboBoxArtifactArtifact, bonusValue.artifact.getNum());
+			break;
+		}
+		case CampaignBonusType::SPELL_SCROLL:
+		{
+			const auto & bonusValue = bonus.getValue<CampaignBonusSpellScroll>();
+			ui->radioButtonSpellScroll->setChecked(true);
+			on_radioButtonSpellScroll_toggled();
+			setComboBoxValue(ui->comboBoxSpellScrollRecipient, bonusValue.hero.getNum());
+			setComboBoxValue(ui->comboBoxSpellScrollSpell, bonusValue.spell.getNum());
+			break;
+		}
+		case CampaignBonusType::PRIMARY_SKILL:
+		{
+			const auto & bonusValue = bonus.getValue<CampaignBonusPrimarySkill>();
+			ui->radioButtonPrimarySkill->setChecked(true);
+			on_radioButtonPrimarySkill_toggled();
+			setComboBoxValue(ui->comboBoxPrimarySkillRecipient, bonusValue.hero.getNum());
+			ui->spinBoxPrimarySkillAttack->setValue(bonusValue.amounts[0]);
+			ui->spinBoxPrimarySkillDefense->setValue(bonusValue.amounts[0]);
+			ui->spinBoxPrimarySkillSpell->setValue(bonusValue.amounts[0]);
+			ui->spinBoxPrimarySkillKnowledge->setValue(bonusValue.amounts[0]);
+			break;
+		}
+		case CampaignBonusType::SECONDARY_SKILL:
+		{
+			const auto & bonusValue = bonus.getValue<CampaignBonusSecondarySkill>();
+			ui->radioButtonSecondarySkill->setChecked(true);
+			on_radioButtonSecondarySkill_toggled();
+			setComboBoxValue(ui->comboBoxSecondarySkillRecipient, bonusValue.hero.getNum());
+			setComboBoxValue(ui->comboBoxSecondarySkillSecondarySkill, bonusValue.skill.getNum());
+			setComboBoxValue(ui->comboBoxSecondarySkillMastery, bonusValue.mastery);
+			break;
+		}
+		case CampaignBonusType::RESOURCE:
+		{
+			const auto & bonusValue = bonus.getValue<CampaignBonusStartingResources>();
+			ui->radioButtonResource->setChecked(true);
+			on_radioButtonResource_toggled();
+			setComboBoxValue(ui->comboBoxResourceResourceType, bonusValue.resource.getNum());
+			ui->spinBoxResourceQuantity->setValue(bonusValue.amount);
+			break;
+		}
+
+		default:
+			break;
 	}
 }
 
 void StartingBonus::saveBonus()
 {
 	if(ui->radioButtonSpell->isChecked())
-		bonus.type = CampaignBonusType::SPELL;
+		bonus = CampaignBonusSpell{
+			HeroTypeID(ui->comboBoxSpellRecipient->currentData().toInt()),
+			SpellID(ui->comboBoxSpellSpell->currentData().toInt())
+		};
 	else if(ui->radioButtonCreature->isChecked())
-		bonus.type = CampaignBonusType::MONSTER;
+		bonus = CampaignBonusCreatures{
+			HeroTypeID(ui->comboBoxCreatureRecipient->currentData().toInt()),
+			CreatureID(ui->comboBoxCreatureCreatureType->currentData().toInt()),
+			int32_t(ui->spinBoxCreatureQuantity->value())
+		};
 	else if(ui->radioButtonBuilding->isChecked())
-		bonus.type = CampaignBonusType::BUILDING;
+		bonus = CampaignBonusBuilding{
+			BuildingID{},
+			BuildingID(ui->comboBoxBuildingBuilding->currentData().toInt())
+		};
 	else if(ui->radioButtonArtifact->isChecked())
-		bonus.type = CampaignBonusType::ARTIFACT;
+		bonus = CampaignBonusArtifact{
+			HeroTypeID(ui->comboBoxCreatureRecipient->currentData().toInt()),
+			ArtifactID(ui->comboBoxArtifactArtifact->currentData().toInt())
+		};
 	else if(ui->radioButtonSpellScroll->isChecked())
-		bonus.type = CampaignBonusType::SPELL_SCROLL;
+		bonus = CampaignBonusSpellScroll{
+			HeroTypeID(ui->comboBoxCreatureRecipient->currentData().toInt()),
+			SpellID(ui->comboBoxSpellScrollSpell->currentData().toInt())
+		};
 	else if(ui->radioButtonPrimarySkill->isChecked())
-		bonus.type = CampaignBonusType::PRIMARY_SKILL;
+		bonus = CampaignBonusPrimarySkill{
+			HeroTypeID(ui->comboBoxCreatureRecipient->currentData().toInt()),
+			{
+				uint8_t(ui->spinBoxPrimarySkillAttack->value()),
+				uint8_t(ui->spinBoxPrimarySkillDefense->value()),
+				uint8_t(ui->spinBoxPrimarySkillSpell->value()),
+				uint8_t(ui->spinBoxPrimarySkillKnowledge->value()),
+			}
+		};
 	else if(ui->radioButtonSecondarySkill->isChecked())
-		bonus.type = CampaignBonusType::SECONDARY_SKILL;
+		bonus = CampaignBonusSecondarySkill{
+			HeroTypeID(ui->comboBoxCreatureRecipient->currentData().toInt()),
+			SecondarySkill(ui->comboBoxSecondarySkillSecondarySkill->currentData().toInt()),
+			int32_t(ui->comboBoxSecondarySkillMastery->currentData().toInt())
+		};
 	else if(ui->radioButtonResource->isChecked())
-		bonus.type = CampaignBonusType::RESOURCE;
-
-	bonus.info1 = 0;
-	bonus.info2 = 0;
-	bonus.info3 = 0;
-
-	switch (bonus.type)
-	{
-	case CampaignBonusType::SPELL:
-		bonus.info1 = ui->comboBoxSpellRecipient->currentData().toInt();
-		bonus.info2 = ui->comboBoxSpellSpell->currentData().toInt();
-		break;
-	case CampaignBonusType::MONSTER:
-		bonus.info1 = ui->comboBoxCreatureRecipient->currentData().toInt();
-		bonus.info2 = ui->comboBoxCreatureCreatureType->currentData().toInt();
-		bonus.info3 = ui->spinBoxCreatureQuantity->value();
-		break;
-	case CampaignBonusType::BUILDING:
-		bonus.info1 = ui->comboBoxBuildingBuilding->currentData().toInt();
-		break;
-	case CampaignBonusType::ARTIFACT:
-		bonus.info1 = ui->comboBoxArtifactRecipient->currentData().toInt();
-		bonus.info2 = ui->comboBoxArtifactArtifact->currentData().toInt();
-		break;
-	case CampaignBonusType::SPELL_SCROLL:
-		bonus.info1 = ui->comboBoxSpellScrollRecipient->currentData().toInt();
-		bonus.info2 = ui->comboBoxSpellScrollSpell->currentData().toInt();
-		break;
-	case CampaignBonusType::PRIMARY_SKILL:
-		bonus.info1 = ui->comboBoxPrimarySkillRecipient->currentData().toInt();
-		bonus.info2 |= ui->spinBoxPrimarySkillAttack->value() << 0;
-		bonus.info2 |= ui->spinBoxPrimarySkillDefense->value() << 8;
-		bonus.info2 |= ui->spinBoxPrimarySkillSpell->value() << 16;
-		bonus.info2 |= ui->spinBoxPrimarySkillKnowledge->value() << 24;
-		break;
-	case CampaignBonusType::SECONDARY_SKILL:
-		bonus.info1 = ui->comboBoxSecondarySkillRecipient->currentData().toInt();
-		bonus.info2 = ui->comboBoxSecondarySkillSecondarySkill->currentData().toInt();
-		bonus.info3 = ui->comboBoxSecondarySkillMastery->currentData().toInt();
-		break;
-	case CampaignBonusType::RESOURCE:
-		bonus.info1 = ui->comboBoxResourceResourceType->currentData().toInt();
-		bonus.info2 = ui->spinBoxResourceQuantity->value();
-		break;
-	
-	default:
-		break;
-	}
+		bonus = CampaignBonusStartingResources{
+			GameResID(ui->comboBoxResourceResourceType->currentData().toInt()),
+			int32_t(ui->spinBoxResourceQuantity->value())
+		};
 }
 
 void StartingBonus::on_buttonBox_clicked(QAbstractButton * button)
@@ -309,40 +317,66 @@ QString StartingBonus::getBonusListTitle(CampaignBonus bonus, std::shared_ptr<CM
 		}
 		return QString::fromStdString(tmp.toString());
 	};
-	auto getSpellName = [](int id){
+	auto getSpellName = [](SpellID id){
 		MetaString tmp;
-		tmp.appendName(SpellID(id));
+		tmp.appendName(id);
 		return QString::fromStdString(tmp.toString());
 	};
-	auto getMonsterName = [](int id, int amount){
+	auto getMonsterName = [](CreatureID id, int amount){
 		MetaString tmp;
-		tmp.appendName(CreatureID(id), amount);
+		tmp.appendName(id, amount);
 		return QString::fromStdString(tmp.toString());
 	};
-	auto getArtifactName = [](int id){
+	auto getArtifactName = [](ArtifactID id){
 		MetaString tmp;
-		tmp.appendName(ArtifactID(id));
+		tmp.appendName(id);
 		return QString::fromStdString(tmp.toString());
 	};
 
-	switch (bonus.type)
+	switch(bonus.getType())
 	{
-	case CampaignBonusType::SPELL:
-		return tr("%1 spell for %2").arg(getSpellName(bonus.info2)).arg(getHeroName(bonus.info1));
-	case CampaignBonusType::MONSTER:
-		return tr("%1 %2 for %3").arg(bonus.info3).arg(getMonsterName(bonus.info2, bonus.info3)).arg(getHeroName(bonus.info1));
-	case CampaignBonusType::BUILDING:
-		return tr("Building");
-	case CampaignBonusType::ARTIFACT:
-		return tr("%1 artifact for %2").arg(getArtifactName(bonus.info2)).arg(getHeroName(bonus.info1));
-	case CampaignBonusType::SPELL_SCROLL:
-		return tr("%1 spell scroll for %2").arg(getSpellName(bonus.info2)).arg(getHeroName(bonus.info1));
-	case CampaignBonusType::PRIMARY_SKILL:
-		return tr("Primary skill (Attack: %1, Defense: %2, Spell: %3, Knowledge: %4) for %5").arg((bonus.info2 >> 0) & 0xff).arg((bonus.info2 >> 8) & 0xff).arg((bonus.info2 >> 16) & 0xff).arg((bonus.info2 >> 24) & 0xff).arg(getHeroName(bonus.info1));
-	case CampaignBonusType::SECONDARY_SKILL:
-		return tr("Secondary skill");
-	case CampaignBonusType::RESOURCE:
-		return tr("Resource");
+		case CampaignBonusType::SPELL:
+		{
+			const auto & bonusValue = bonus.getValue<CampaignBonusSpell>();
+			return tr("%1 spell for %2").arg(getSpellName(bonusValue.spell)).arg(getHeroName(bonusValue.hero));
+		}
+		case CampaignBonusType::MONSTER:
+		{
+			const auto & bonusValue = bonus.getValue<CampaignBonusCreatures>();
+			return tr("%1 %2 for %3").arg(bonusValue.amount).arg(getMonsterName(bonusValue.creature, bonusValue.amount)).arg(getHeroName(bonusValue.hero));
+		}
+		case CampaignBonusType::BUILDING:
+		{
+			return tr("Building");
+		}
+		case CampaignBonusType::ARTIFACT:
+		{
+			const auto & bonusValue = bonus.getValue<CampaignBonusArtifact>();
+			return tr("%1 artifact for %2").arg(getArtifactName(bonusValue.artifact)).arg(getHeroName(bonusValue.hero));
+		}
+		case CampaignBonusType::SPELL_SCROLL:
+		{
+			const auto & bonusValue = bonus.getValue<CampaignBonusSpellScroll>();
+			return tr("%1 spell scroll for %2").arg(getSpellName(bonusValue.spell)).arg(getHeroName(bonusValue.hero));
+		}
+		case CampaignBonusType::PRIMARY_SKILL:
+		{
+			const auto & bonusValue = bonus.getValue<CampaignBonusPrimarySkill>();
+			return tr("Primary skill (Attack: %1, Defense: %2, Spell: %3, Knowledge: %4) for %5")
+				.arg(bonusValue.amounts[0])
+				.arg(bonusValue.amounts[1])
+				.arg(bonusValue.amounts[2])
+				.arg(bonusValue.amounts[3])
+				.arg(getHeroName(bonusValue.hero));
+		}
+		case CampaignBonusType::SECONDARY_SKILL:
+		{
+			return tr("Secondary skill");
+		}
+		case CampaignBonusType::RESOURCE:
+		{
+			return tr("Resource");
+		}
 	}
 	return {};
 }

+ 1 - 1
mapeditor/campaigneditor/startingbonus.h

@@ -13,7 +13,7 @@
 #include "lib/constants/EntityIdentifiers.h"
 #include "lib/campaign/CampaignState.h"
 
-struct CampaignBonus;
+class CampaignBonus;
 class CMap;
 
 namespace Ui {

+ 7 - 3
server/CVCMIServer.cpp

@@ -832,12 +832,16 @@ void CVCMIServer::setCampaignBonus(int bonusId)
 	campaignBonus = bonusId;
 
 	const CampaignScenario & scenario = si->campState->scenario(campaignMap);
-	const std::vector<CampaignBonus> & bonDescs = scenario.travelOptions.bonusesToChoose;
-	if(bonDescs[bonusId].type == CampaignBonusType::HERO || bonDescs[bonusId].type == CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO)
+	const CampaignBonus & bonus = scenario.travelOptions.bonusesToChoose.at(bonusId);
+	if(bonus.getType() == CampaignBonusType::HERO || bonus.getType() == CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO)
 	{
+		PlayerColor startingPlayer = bonus.getType() == CampaignBonusType::HERO ?
+			bonus.getValue<CampaignBonusStartingHero>().startingPlayer :
+			bonus.getValue<CampaignBonusHeroesFromScenario>().startingPlayer;
+
 		for(auto & elem : si->playerInfos)
 		{
-			if(elem.first == PlayerColor(bonDescs[bonusId].info1))
+			if(elem.first == startingPlayer)
 				setPlayerConnectedId(elem.second, 1);
 			else
 				setPlayerConnectedId(elem.second, PlayerSettings::PLAYER_AI);