1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-09-16 09:26:28 +02:00

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

This commit is contained in:
Ivan Savenko
2025-06-02 17:24:13 +03:00
parent 59dfa4c2d7
commit c279da0798
25 changed files with 472 additions and 355 deletions

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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
]
}

View File

@@ -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/AB" : { // AB Intro
"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/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":
{

View File

@@ -154,7 +154,6 @@
"restorationOfErathia" : {
"supported" : true,
"iconIndex" : 0,
"buildingsCommon": {
"townHall" : 0,
"cityHall" : 1,
@@ -199,7 +198,6 @@
"dwellingLvl7" : 39,
"dwellingUpLvl7" : 40
},
"buildings" : {
"castle" : {
"special1" : 18, // lighthouse
@@ -245,7 +243,6 @@
"special3" : 18 // glyphsOfFear
}
},
"portraits" : {
"catherine" : 128, // In "RoE" Catherine only has portrait
"portraitGeneralKendal" : 129
@@ -259,6 +256,75 @@
"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" : {
@@ -270,7 +336,6 @@
"special2" : 18 // magicUniversity
}
},
"portraits" : {
"pasis" : 128,
"thunar" : 129,
@@ -285,12 +350,55 @@
"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,
@@ -300,7 +408,6 @@
"portraitYoungSandro" : 161,
"portraitYoungYog" : 162
},
"campaignRegions" : {
"hackAndSlash" : 14,
"birthOfBarbarian" : 15,
@@ -309,6 +416,55 @@
"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" : {

View File

@@ -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

View File

@@ -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)

View File

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

View File

@@ -188,7 +188,8 @@ public:
{
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);
return result;

View File

@@ -370,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->info("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;
@@ -378,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->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));
}
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

View File

@@ -37,10 +37,6 @@ class DLL_LINKAGE CampaignHandler
/// 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

View File

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

View File

@@ -12,6 +12,8 @@
#include "../json/JsonNode.h"
VCMI_LIB_NAMESPACE_BEGIN
std::vector<JsonNode> CampaignRegionsHandler::loadLegacyData()
{
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!");
}
VCMI_LIB_NAMESPACE_END

View File

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

View File

@@ -392,6 +392,12 @@ void Campaign::overrideCampaign()
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()
@@ -431,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

View File

@@ -57,11 +57,14 @@ class DLL_LINKAGE CampaignHeader : public boost::noncopyable
VideoPath introVideo;
VideoPath outroVideo;
HeroTypeID yogWizardID;
HeroTypeID gemSorceressID;
int numberOfScenarios = 0;
bool difficultyChosenByPlayer = false;
bool restrictGarrisonsAI = false;
TextContainerRegistrable textContainer;
public:
bool playerSelectedDifficulty() const;
bool formatVCMI() const;
@@ -81,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();
@@ -96,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;
@@ -105,6 +114,11 @@ public:
h & videoRim;
h & introVideo;
h & outroVideo;
if (h.hasFeature(Handler::Version::CAMPAIGN_BONUSES))
{
h & yogWizardID;
h & gemSorceressID;
}
}
};

View File

@@ -57,9 +57,9 @@ 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 +258,8 @@ si32 HeroTypeID::decode(const std::string & identifier)
{
if (identifier == "random")
return -2;
if (identifier == "strongest")
return -3;
return resolveIdentifier("hero", identifier);
}
@@ -267,6 +269,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();
}

View File

@@ -1065,9 +1065,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
};
};

View File

@@ -239,12 +239,17 @@ static void loadBonusAddInfo(CAddInfo & var, BonusType type, const JsonNode & va
case BonusType::SPELL_BEFORE_ATTACK:
case BonusType::SPELL_AFTER_ATTACK:
// 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;
case BonusType::MULTIHEX_UNIT_ATTACK:
case BonusType::MULTIHEX_ENEMY_ATTACK:

View File

@@ -1802,37 +1802,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

View File

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

View File

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

View File

@@ -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");
@@ -216,9 +222,26 @@ SecondarySkill MapIdentifiersH3M::remap(SecondarySkill 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

View File

@@ -44,6 +44,8 @@ class MapIdentifiersH3M
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;
@@ -55,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;

View File

@@ -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" );