浏览代码

Minimize hardcoded logic for campaigns. Support for hota h3c's

Ivan Savenko 4 月之前
父节点
当前提交
c279da0798

+ 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
 	//you can't request action from action-response thread
 	executeActionAsync("showGarrisonDialog", [this, up, down, removableUnits, queryID]()
 	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);
 			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
 	//you can't request action from action-response thread
 	executeActionAsync("showGarrisonDialog", [this, down, up, removableUnits, queryID]()
 	executeActionAsync("showGarrisonDialog", [this, down, up, removableUnits, queryID]()
 	{
 	{
-		if(removableUnits && !cb->getStartInfo()->isRestorationOfErathiaCampaign())
+		if(removableUnits && !cb->getStartInfo()->restrictedGarrisonsForAI())
 			pickBestCreatures(down, up);
 			pickBestCreatures(down, up);
 
 
 		answerQuery(queryID, 0);
 		answerQuery(queryID, 0);

+ 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",
 		"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
 	"MAPS/CHRONICLES/HC1_MAIN" : { // Heroes Chronicles 1
 		"regions":
 		"regions":
 		{
 		{

+ 162 - 6
config/gameConfig.json

@@ -154,7 +154,6 @@
 			"restorationOfErathia" : { 
 			"restorationOfErathia" : { 
 				"supported" : true,
 				"supported" : true,
 				"iconIndex" : 0,
 				"iconIndex" : 0,
-
 				"buildingsCommon": {
 				"buildingsCommon": {
 					"townHall"       : 0,
 					"townHall"       : 0,
 					"cityHall"       : 1,
 					"cityHall"       : 1,
@@ -199,7 +198,6 @@
 					"dwellingLvl7"   : 39,
 					"dwellingLvl7"   : 39,
 					"dwellingUpLvl7" : 40
 					"dwellingUpLvl7" : 40
 				},
 				},
-
 				"buildings" : {
 				"buildings" : {
 					"castle" : {
 					"castle" : {
 						"special1" : 18, // lighthouse
 						"special1" : 18, // lighthouse
@@ -245,7 +243,6 @@
 						"special3" : 18  // glyphsOfFear
 						"special3" : 18  // glyphsOfFear
 					}
 					}
 				},
 				},
-
 				"portraits" : {
 				"portraits" : {
 					"catherine" : 128, // In "RoE" Catherine only has portrait
 					"catherine" : 128, // In "RoE" Catherine only has portrait
 					"portraitGeneralKendal" : 129
 					"portraitGeneralKendal" : 129
@@ -259,6 +256,75 @@
 					"evil2" : 5,    // Long Live the King
 					"evil2" : 5,    // Long Live the King
 					"neutral1" : 6, // Spoils of War
 					"neutral1" : 6, // Spoils of War
 					"secret1" : 7   // Seeds Of Discontent 
 					"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" : {
 			"armageddonsBlade" : {
@@ -270,7 +336,6 @@
 						"special2" : 18  // magicUniversity
 						"special2" : 18  // magicUniversity
 					}
 					}
 				},
 				},
-				
 				"portraits" : {
 				"portraits" : {
 					"pasis" : 128,
 					"pasis" : 128,
 					"thunar" : 129,
 					"thunar" : 129,
@@ -285,12 +350,55 @@
 					"dragonsBlood" : 11,
 					"dragonsBlood" : 11,
 					"playingWithFire" : 12,
 					"playingWithFire" : 12,
 					"armageddonsBlade" : 13
 					"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" : { 
 			"shadowOfDeath" : { 
 				"supported" : true,
 				"supported" : true,
 				"iconIndex" : 2,
 				"iconIndex" : 2,
-				
 				"portraits" : {
 				"portraits" : {
 					"portraitGeneralKendal" : 156,
 					"portraitGeneralKendal" : 156,
 					"portraitYoungCristian" : 157,
 					"portraitYoungCristian" : 157,
@@ -300,7 +408,6 @@
 					"portraitYoungSandro" : 161,
 					"portraitYoungSandro" : 161,
 					"portraitYoungYog" : 162
 					"portraitYoungYog" : 162
 				},
 				},
-				
 				"campaignRegions" : {
 				"campaignRegions" : {
 					"hackAndSlash" : 14,
 					"hackAndSlash" : 14,
 					"birthOfBarbarian" : 15,
 					"birthOfBarbarian" : 15,
@@ -309,6 +416,55 @@
 					"riseOfTheNecromancer" : 18,
 					"riseOfTheNecromancer" : 18,
 					"unholyAlliance" : 19,
 					"unholyAlliance" : 19,
 					"spectreOfPower" : 20
 					"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" : { 
 			"chronicles" : { 

+ 2 - 15
lib/StartInfo.cpp

@@ -90,22 +90,9 @@ std::string StartInfo::getCampaignName() const
 		return LIBRARY->generaltexth->allTexts[508];
 		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
 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
 	// TODO: Must be client-side
 	std::string getCampaignName() const;
 	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>
 	template <typename Handler>
 	void serialize(Handler &h)
 	void serialize(Handler &h)

+ 6 - 6
lib/campaign/CampaignBonus.cpp

@@ -64,14 +64,14 @@ CampaignBonus::CampaignBonus(CBinaryReader & reader, const MapIdentifiersH3M & r
 			{
 			{
 				case CampaignBonusType::SPELL:
 				case CampaignBonusType::SPELL:
 				{
 				{
-					HeroTypeID hero(reader.readUInt16());
+					HeroTypeID hero(reader.readInt16());
 					SpellID spell(reader.readUInt8());
 					SpellID spell(reader.readUInt8());
 					data = CampaignBonusSpell{remapper.remap(hero), spell};
 					data = CampaignBonusSpell{remapper.remap(hero), spell};
 					break;
 					break;
 				}
 				}
 				case CampaignBonusType::MONSTER:
 				case CampaignBonusType::MONSTER:
 				{
 				{
-					HeroTypeID hero(reader.readUInt16());
+					HeroTypeID hero(reader.readInt16());
 					CreatureID creature(reader.readUInt16());
 					CreatureID creature(reader.readUInt16());
 					int32_t amount = reader.readUInt16();
 					int32_t amount = reader.readUInt16();
 					data = CampaignBonusCreatures{remapper.remap(hero), remapper.remap(creature), amount};
 					data = CampaignBonusCreatures{remapper.remap(hero), remapper.remap(creature), amount};
@@ -85,21 +85,21 @@ CampaignBonus::CampaignBonus(CBinaryReader & reader, const MapIdentifiersH3M & r
 				}
 				}
 				case CampaignBonusType::ARTIFACT:
 				case CampaignBonusType::ARTIFACT:
 				{
 				{
-					HeroTypeID hero(reader.readUInt16());
+					HeroTypeID hero(reader.readInt16());
 					ArtifactID artifact(reader.readUInt16());
 					ArtifactID artifact(reader.readUInt16());
 					data = CampaignBonusArtifact{remapper.remap(hero), remapper.remap(artifact)};
 					data = CampaignBonusArtifact{remapper.remap(hero), remapper.remap(artifact)};
 					break;
 					break;
 				}
 				}
 				case CampaignBonusType::SPELL_SCROLL:
 				case CampaignBonusType::SPELL_SCROLL:
 				{
 				{
-					HeroTypeID hero(reader.readUInt16());
+					HeroTypeID hero(reader.readInt16());
 					SpellID spell(reader.readUInt8());
 					SpellID spell(reader.readUInt8());
 					data = CampaignBonusSpellScroll{remapper.remap(hero), spell};
 					data = CampaignBonusSpellScroll{remapper.remap(hero), spell};
 					break;
 					break;
 				}
 				}
 				case CampaignBonusType::PRIMARY_SKILL:
 				case CampaignBonusType::PRIMARY_SKILL:
 				{
 				{
-					HeroTypeID hero(reader.readUInt16());
+					HeroTypeID hero(reader.readInt16());
 					std::array<uint8_t, 4> amounts = {};
 					std::array<uint8_t, 4> amounts = {};
 					for(auto & value : amounts)
 					for(auto & value : amounts)
 						value = reader.readUInt8();
 						value = reader.readUInt8();
@@ -109,7 +109,7 @@ CampaignBonus::CampaignBonus(CBinaryReader & reader, const MapIdentifiersH3M & r
 				}
 				}
 				case CampaignBonusType::SECONDARY_SKILL:
 				case CampaignBonusType::SECONDARY_SKILL:
 				{
 				{
-					HeroTypeID hero(reader.readUInt16());
+					HeroTypeID hero(reader.readInt16());
 					SecondarySkill skill(reader.readUInt8());
 					SecondarySkill skill(reader.readUInt8());
 					int32_t skillMastery(reader.readUInt8());
 					int32_t skillMastery(reader.readUInt8());
 					data = CampaignBonusSecondarySkill{remapper.remap(hero), remapper.remap(skill), skillMastery};
 					data = CampaignBonusSecondarySkill{remapper.remap(hero), remapper.remap(skill), skillMastery};

+ 2 - 1
lib/campaign/CampaignBonus.h

@@ -188,7 +188,8 @@ public:
 			{
 			{
 				result = bonusValue.hero;
 				result = bonusValue.hero;
 			}
 			}
-			throw std::runtime_error("Attempt to get targeted hero on invalid type!");
+			else
+				throw std::runtime_error("Attempt to get targeted hero on invalid type!");
 		}, data);
 		}, data);
 
 
 		return result;
 		return result;

+ 7 - 31
lib/campaign/CampaignHandler.cpp

@@ -370,7 +370,8 @@ void CampaignHandler::readHeaderFromMemory( CampaignHeader & ret, CBinaryReader
 	else
 	else
 		ret.difficultyChosenByPlayer = false;
 		ret.difficultyChosenByPlayer = false;
 
 
-	ret.music = prologMusicName(reader.readInt8());
+	ret.music = mapping.remapCampaignMusic(reader.readUInt8());
+	logGlobal->info("Campaign %s: map %d (%d scenarios), music theme: %s", filename, campaignMapId.getNum(), ret.numberOfScenarios, ret.music.getOriginalName());
 	ret.filename = filename;
 	ret.filename = filename;
 	ret.modName = modName;
 	ret.modName = modName;
 	ret.encoding = encoding;
 	ret.encoding = encoding;
@@ -378,18 +379,17 @@ void CampaignHandler::readHeaderFromMemory( CampaignHeader & ret, CBinaryReader
 
 
 CampaignScenario CampaignHandler::readScenarioFromMemory( CBinaryReader & reader, CampaignHeader & header)
 CampaignScenario CampaignHandler::readScenarioFromMemory( CBinaryReader & reader, CampaignHeader & header)
 {
 {
+	const auto & mapping = LIBRARY->mapFormat->getMapping(header.version);
+
 	auto prologEpilogReader = [&](const std::string & identifier) -> CampaignScenarioPrologEpilog
 	auto prologEpilogReader = [&](const std::string & identifier) -> CampaignScenarioPrologEpilog
 	{
 	{
 		CampaignScenarioPrologEpilog ret;
 		CampaignScenarioPrologEpilog ret;
 		ret.hasPrologEpilog = reader.readBool();
 		ret.hasPrologEpilog = reader.readBool();
 		if(ret.hasPrologEpilog)
 		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->info("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));
 			ret.prologText.appendTextID(readLocalizedString(header, reader, header.filename, header.modName, header.encoding, identifier));
 		}
 		}
 		return ret;
 		return ret;
@@ -544,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
 VCMI_LIB_NAMESPACE_END

+ 0 - 4
lib/campaign/CampaignHandler.h

@@ -37,10 +37,6 @@ class DLL_LINKAGE CampaignHandler
 	/// headerOnly - only header will be decompressed, returned vector wont have any maps
 	/// 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 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";
 	static constexpr auto VCMP_HEADER_FILE_NAME = "header.json";
 public:
 public:
 	static std::unique_ptr<Campaign> getHeader( const std::string & name); //name - name of appropriate file
 	static std::unique_ptr<Campaign> getHeader( const std::string & name); //name - name of appropriate file

+ 4 - 0
lib/campaign/CampaignRegions.cpp

@@ -12,6 +12,8 @@
 
 
 #include "../json/JsonNode.h"
 #include "../json/JsonNode.h"
 
 
+VCMI_LIB_NAMESPACE_BEGIN
+
 CampaignRegions::RegionDescription CampaignRegions::RegionDescription::fromJson(const JsonNode & node)
 CampaignRegions::RegionDescription CampaignRegions::RegionDescription::fromJson(const JsonNode & node)
 {
 {
 	CampaignRegions::RegionDescription rd;
 	CampaignRegions::RegionDescription rd;
@@ -146,3 +148,5 @@ int CampaignRegions::regionsCount() const
 {
 {
 	return regions.size();
 	return regions.size();
 }
 }
+
+VCMI_LIB_NAMESPACE_END

+ 4 - 0
lib/campaign/CampaignRegionsHandler.cpp

@@ -12,6 +12,8 @@
 
 
 #include "../json/JsonNode.h"
 #include "../json/JsonNode.h"
 
 
+VCMI_LIB_NAMESPACE_BEGIN
+
 std::vector<JsonNode> CampaignRegionsHandler::loadLegacyData()
 std::vector<JsonNode> CampaignRegionsHandler::loadLegacyData()
 {
 {
 	return {};
 	return {};
@@ -28,3 +30,5 @@ void CampaignRegionsHandler::loadObject(std::string scope, std::string name, con
 {
 {
 	throw std::runtime_error("CampaignRegionsHandler::loadObject - load by index is not supported!");
 	throw std::runtime_error("CampaignRegionsHandler::loadObject - load by index is not supported!");
 }
 }
+
+VCMI_LIB_NAMESPACE_END

+ 1 - 1
lib/campaign/CampaignScenarioPrologEpilog.h

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

+ 19 - 0
lib/campaign/CampaignState.cpp

@@ -392,6 +392,12 @@ void Campaign::overrideCampaign()
 		introVideo = VideoPath::builtin(overrides["introVideo"].String());
 		introVideo = VideoPath::builtin(overrides["introVideo"].String());
 	if(!overrides["outroVideo"].isNull())
 	if(!overrides["outroVideo"].isNull())
 		outroVideo = VideoPath::builtin(overrides["outroVideo"].String());
 		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()
 void Campaign::overrideCampaignScenarios()
@@ -431,4 +437,17 @@ bool CampaignState::isCampaignFinished() const
 	return conqueredScenarios() == allScenarios();
 	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
 VCMI_LIB_NAMESPACE_END

+ 15 - 1
lib/campaign/CampaignState.h

@@ -57,11 +57,14 @@ class DLL_LINKAGE CampaignHeader : public boost::noncopyable
 	VideoPath introVideo;
 	VideoPath introVideo;
 	VideoPath outroVideo;
 	VideoPath outroVideo;
 
 
+	HeroTypeID yogWizardID;
+	HeroTypeID gemSorceressID;
+
 	int numberOfScenarios = 0;
 	int numberOfScenarios = 0;
 	bool difficultyChosenByPlayer = false;
 	bool difficultyChosenByPlayer = false;
+	bool restrictGarrisonsAI = false;
 
 
 	TextContainerRegistrable textContainer;
 	TextContainerRegistrable textContainer;
-
 public:
 public:
 	bool playerSelectedDifficulty() const;
 	bool playerSelectedDifficulty() const;
 	bool formatVCMI() const;
 	bool formatVCMI() const;
@@ -81,6 +84,10 @@ public:
 	VideoPath getIntroVideo() const;
 	VideoPath getIntroVideo() const;
 	VideoPath getOutroVideo() const;
 	VideoPath getOutroVideo() const;
 
 
+	HeroTypeID getYogWizardID() const;
+	HeroTypeID getGemSorceressID() const;
+	bool restrictedGarrisonsForAI() const;
+
 	const CampaignRegions & getRegions() const;
 	const CampaignRegions & getRegions() const;
 	TextContainerRegistrable & getTexts();
 	TextContainerRegistrable & getTexts();
 
 
@@ -96,6 +103,8 @@ public:
 		h & campaignVersion;
 		h & campaignVersion;
 		h & creationDateTime;
 		h & creationDateTime;
 		h & difficultyChosenByPlayer;
 		h & difficultyChosenByPlayer;
+		if (h.hasFeature(Handler::Version::CAMPAIGN_BONUSES))
+			h & restrictGarrisonsAI;
 		h & filename;
 		h & filename;
 		h & modName;
 		h & modName;
 		h & music;
 		h & music;
@@ -105,6 +114,11 @@ public:
 		h & videoRim;
 		h & videoRim;
 		h & introVideo;
 		h & introVideo;
 		h & outroVideo;
 		h & outroVideo;
+		if (h.hasFeature(Handler::Version::CAMPAIGN_BONUSES))
+		{
+			h & yogWizardID;
+			h & gemSorceressID;
+		}
 	}
 	}
 };
 };
 
 

+ 7 - 3
lib/constants/EntityIdentifiers.cpp

@@ -57,9 +57,9 @@ const HeroTypeID HeroTypeID::NONE(-1);
 const HeroTypeID HeroTypeID::RANDOM(-2);
 const HeroTypeID HeroTypeID::RANDOM(-2);
 const HeroTypeID HeroTypeID::GEM(27);
 const HeroTypeID HeroTypeID::GEM(27);
 const HeroTypeID HeroTypeID::SOLMYR(45);
 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);
 const ObjectInstanceID ObjectInstanceID::NONE(-1);
 
 
@@ -258,6 +258,8 @@ si32 HeroTypeID::decode(const std::string & identifier)
 {
 {
 	if (identifier == "random")
 	if (identifier == "random")
 		return -2;
 		return -2;
+	if (identifier == "strongest")
+		return -3;
 	return resolveIdentifier("hero", identifier);
 	return resolveIdentifier("hero", identifier);
 }
 }
 
 
@@ -267,6 +269,8 @@ std::string HeroTypeID::encode(const si32 index)
 		return "";
 		return "";
 	if (index == -2)
 	if (index == -2)
 		return "random";
 		return "random";
+	if (index == -3)
+		return "strongest";
 	return LIBRARY->heroTypes()->getByIndex(index)->getJsonKey();
 	return LIBRARY->heroTypes()->getByIndex(index)->getJsonKey();
 }
 }
 
 

+ 3 - 3
lib/constants/EntityIdentifiers.h

@@ -1065,9 +1065,9 @@ public:
 		MITHRIL,
 		MITHRIL,
 		COUNT,
 		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
 		NONE = -1
 	};
 	};
 };
 };

+ 11 - 6
lib/json/JsonBonus.cpp

@@ -239,12 +239,17 @@ static void loadBonusAddInfo(CAddInfo & var, BonusType type, const JsonNode & va
 		case BonusType::SPELL_BEFORE_ATTACK:
 		case BonusType::SPELL_BEFORE_ATTACK:
 		case BonusType::SPELL_AFTER_ATTACK:
 		case BonusType::SPELL_AFTER_ATTACK:
 			// 3 numbers
 			// 3 numbers
-			if (!value.isVector())
-				break;
-			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;
 			break;
 		case BonusType::MULTIHEX_UNIT_ATTACK:
 		case BonusType::MULTIHEX_UNIT_ATTACK:
 		case BonusType::MULTIHEX_ENEMY_ATTACK:
 		case BonusType::MULTIHEX_ENEMY_ATTACK:

+ 2 - 26
lib/mapObjects/CGHeroInstance.cpp

@@ -1802,37 +1802,13 @@ void CGHeroInstance::fillUpgradeInfo(UpgradeInfo & info, const CStackInstance &
 bool CGHeroInstance::isCampaignYog() const
 bool CGHeroInstance::isCampaignYog() const
 {
 {
 	const StartInfo *si = cb->getStartInfo();
 	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
 bool CGHeroInstance::isCampaignGem() const
 {
 {
 	const StartInfo *si = cb->getStartInfo();
 	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
 ResourceSet CGHeroInstance::dailyIncome() const

+ 3 - 0
lib/mapping/MapFormatSettings.cpp

@@ -18,6 +18,8 @@
 #include "../json/JsonUtils.h"
 #include "../json/JsonUtils.h"
 #include "../modding/ModScope.h"
 #include "../modding/ModScope.h"
 
 
+VCMI_LIB_NAMESPACE_BEGIN
+
 MapIdentifiersH3M MapFormatSettings::generateMapping(EMapFormat format)
 MapIdentifiersH3M MapFormatSettings::generateMapping(EMapFormat format)
 {
 {
 	auto features = MapFormatFeaturesH3M::find(format, 0);
 	auto features = MapFormatFeaturesH3M::find(format, 0);
@@ -87,6 +89,7 @@ MapFormatSettings::MapFormatSettings()
 	for (auto & entry : mapOverridesConfig.Struct())
 	for (auto & entry : mapOverridesConfig.Struct())
 		JsonUtils::validate(entry.second, "vcmi:mapHeader", "patch for " + entry.first);
 		JsonUtils::validate(entry.second, "vcmi:mapHeader", "patch for " + entry.first);
 
 
+	campaignOverridesConfig.setModScope(ModScope::scopeMap());
 	mapOverridesConfig.setModScope(ModScope::scopeMap());
 	mapOverridesConfig.setModScope(ModScope::scopeMap());
 }
 }
 
 

+ 1 - 1
lib/mapping/MapFormatSettings.h

@@ -17,7 +17,7 @@
 
 
 VCMI_LIB_NAMESPACE_BEGIN
 VCMI_LIB_NAMESPACE_BEGIN
 
 
-	class MapFormatSettings : boost::noncopyable
+class MapFormatSettings : boost::noncopyable
 {
 {
 	static MapIdentifiersH3M generateMapping(EMapFormat format);
 	static MapIdentifiersH3M generateMapping(EMapFormat format);
 	static std::map<EMapFormat, MapIdentifiersH3M> generateMappings();
 	static std::map<EMapFormat, MapIdentifiersH3M> generateMappings();

+ 26 - 3
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(mappingHeroPortrait, mapping["portraits"], "hero");
 	loadMapping(mappingBuilding, mapping["buildingsCommon"], "building.core:random");
 	loadMapping(mappingBuilding, mapping["buildingsCommon"], "building.core:random");
 	loadMapping(mappingFaction, mapping["factions"], "faction");
 	loadMapping(mappingFaction, mapping["factions"], "faction");
@@ -216,9 +222,26 @@ SecondarySkill MapIdentifiersH3M::remap(SecondarySkill input) const
 
 
 CampaignRegionID MapIdentifiersH3M::remap(CampaignRegionID input) const
 CampaignRegionID MapIdentifiersH3M::remap(CampaignRegionID input) const
 {
 {
-	if (mappingCampaignRegions.count(input))
-		return mappingCampaignRegions.at(input);
-	return input;
+	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
 VCMI_LIB_NAMESPACE_END

+ 4 - 0
lib/mapping/MapIdentifiersH3M.h

@@ -44,6 +44,8 @@ class MapIdentifiersH3M
 	std::map<ArtifactID, ArtifactID> mappingArtifact;
 	std::map<ArtifactID, ArtifactID> mappingArtifact;
 	std::map<SecondarySkill, SecondarySkill> mappingSecondarySkill;
 	std::map<SecondarySkill, SecondarySkill> mappingSecondarySkill;
 	std::map<CampaignRegionID, CampaignRegionID> mappingCampaignRegions;
 	std::map<CampaignRegionID, CampaignRegionID> mappingCampaignRegions;
+	std::map<int, VideoPath> mappingCampaignVideo;
+	std::map<int, AudioPath> mappingCampaignMusic;
 
 
 	std::map<AnimationPath, AnimationPath> mappingObjectTemplate;
 	std::map<AnimationPath, AnimationPath> mappingObjectTemplate;
 	std::map<ObjectTypeIdentifier, ObjectTypeIdentifier> mappingObjectIndex;
 	std::map<ObjectTypeIdentifier, ObjectTypeIdentifier> mappingObjectIndex;
@@ -55,6 +57,8 @@ public:
 
 
 	void remapTemplate(ObjectTemplate & objectTemplate);
 	void remapTemplate(ObjectTemplate & objectTemplate);
 
 
+	AudioPath remapCampaignMusic(int index) const;
+	VideoPath remapCampaignVideo(int index) const;
 	BuildingID remapBuilding(std::optional<FactionID> owner, BuildingID input) const;
 	BuildingID remapBuilding(std::optional<FactionID> owner, BuildingID input) const;
 	HeroTypeID remapPortrait(HeroTypeID input) const;
 	HeroTypeID remapPortrait(HeroTypeID input) const;
 	FactionID remap(FactionID input) const;
 	FactionID remap(FactionID input) const;

+ 0 - 1
lib/texts/CGeneralTextHandler.cpp

@@ -159,7 +159,6 @@ CGeneralTextHandler::CGeneralTextHandler():
 	readToVector("core.heroscrn", "DATA/HEROSCRN.TXT" );
 	readToVector("core.heroscrn", "DATA/HEROSCRN.TXT" );
 	readToVector("core.tentcolr", "DATA/TENTCOLR.TXT" );
 	readToVector("core.tentcolr", "DATA/TENTCOLR.TXT" );
 	readToVector("core.skilllev", "DATA/SKILLLEV.TXT" );
 	readToVector("core.skilllev", "DATA/SKILLLEV.TXT" );
-	readToVector("core.cmpmusic", "DATA/CMPMUSIC.TXT" );
 	readToVector("core.minename", "DATA/MINENAME.TXT" );
 	readToVector("core.minename", "DATA/MINENAME.TXT" );
 	readToVector("core.mineevnt", "DATA/MINEEVNT.TXT" );
 	readToVector("core.mineevnt", "DATA/MINEEVNT.TXT" );
 	readToVector("core.xtrainfo", "DATA/XTRAINFO.TXT" );
 	readToVector("core.xtrainfo", "DATA/XTRAINFO.TXT" );