1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-07-13 01:20:34 +02:00

Support for objects from mods as campaign scenario bonuses

This commit is contained in:
Ivan Savenko
2025-05-29 17:24:45 +03:00
parent 87d5347e50
commit 2bf951a4cf
11 changed files with 110 additions and 20 deletions

View File

@ -46,12 +46,14 @@
#include "../../lib/GameLibrary.h" #include "../../lib/GameLibrary.h"
#include "../../lib/StartInfo.h" #include "../../lib/StartInfo.h"
#include "../../lib/campaign/CampaignState.h" #include "../../lib/campaign/CampaignState.h"
#include "../../lib/entities/artifact/CArtifact.h"
#include "../../lib/entities/building/CBuilding.h" #include "../../lib/entities/building/CBuilding.h"
#include "../../lib/entities/building/CBuildingHandler.h" #include "../../lib/entities/building/CBuildingHandler.h"
#include "../../lib/entities/faction/CFaction.h" #include "../../lib/entities/faction/CFaction.h"
#include "../../lib/entities/faction/CTown.h" #include "../../lib/entities/faction/CTown.h"
#include "../../lib/entities/faction/CTownHandler.h" #include "../../lib/entities/faction/CTownHandler.h"
#include "../../lib/entities/hero/CHeroHandler.h" #include "../../lib/entities/hero/CHeroHandler.h"
#include "../../lib/spells/CSpellHandler.h"
#include "../../lib/filesystem/Filesystem.h" #include "../../lib/filesystem/Filesystem.h"
#include "../../lib/mapObjects/CGHeroInstance.h" #include "../../lib/mapObjects/CGHeroInstance.h"
#include "../../lib/mapping/CMapHeader.h" #include "../../lib/mapping/CMapHeader.h"
@ -184,7 +186,12 @@ void CBonusSelection::createBonusesIcons()
case CampaignBonusType::SPELL: case CampaignBonusType::SPELL:
{ {
const auto & bonusValue = bonus.getValue<CampaignBonusSpell>(); const auto & bonusValue = bonus.getValue<CampaignBonusSpell>();
picNumber = bonusValue.spell.getNum(); 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.appendLocalString(EMetaText::GENERAL_TXT, 715);
desc.replaceName(bonusValue.spell); desc.replaceName(bonusValue.spell);
break; break;
@ -227,7 +234,11 @@ void CBonusSelection::createBonusesIcons()
case CampaignBonusType::ARTIFACT: case CampaignBonusType::ARTIFACT:
{ {
const auto & bonusValue = bonus.getValue<CampaignBonusArtifact>(); const auto & bonusValue = bonus.getValue<CampaignBonusArtifact>();
picNumber = bonusValue.artifact; 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.appendLocalString(EMetaText::GENERAL_TXT, 715);
desc.replaceName(bonusValue.artifact); desc.replaceName(bonusValue.artifact);
break; break;
@ -235,7 +246,12 @@ void CBonusSelection::createBonusesIcons()
case CampaignBonusType::SPELL_SCROLL: case CampaignBonusType::SPELL_SCROLL:
{ {
const auto & bonusValue = bonus.getValue<CampaignBonusSpellScroll>(); const auto & bonusValue = bonus.getValue<CampaignBonusSpellScroll>();
picNumber = bonusValue.spell; 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.appendLocalString(EMetaText::GENERAL_TXT, 716);
desc.replaceName(bonusValue.spell); desc.replaceName(bonusValue.spell);
break; break;
@ -276,11 +292,14 @@ void CBonusSelection::createBonusesIcons()
case CampaignBonusType::SECONDARY_SKILL: case CampaignBonusType::SECONDARY_SKILL:
{ {
const auto & bonusValue = bonus.getValue<CampaignBonusSecondarySkill>(); const auto & bonusValue = bonus.getValue<CampaignBonusSecondarySkill>();
const auto * skill = bonusValue.skill.toSkill();
desc.appendLocalString(EMetaText::GENERAL_TXT, 718); desc.appendLocalString(EMetaText::GENERAL_TXT, 718);
desc.replaceTextID(TextIdentifier("core", "skilllev", bonusValue.mastery - 1).get()); desc.replaceTextID(TextIdentifier("core", "skilllev", bonusValue.mastery - 1).get());
desc.replaceName(bonusValue.skill); desc.replaceName(bonusValue.skill);
picNumber = bonusValue.skill.getNum() * 3 + bonusValue.mastery - 1; if (!skill->at(bonusValue.mastery).scenarioBonus.empty())
picName = skill->at(bonusValue.mastery).scenarioBonus.empty();
else
picNumber = bonusValue.skill.getNum() * 3 + bonusValue.mastery - 1;
break; break;
} }
case CampaignBonusType::RESOURCE: case CampaignBonusType::RESOURCE:

View File

@ -91,16 +91,16 @@
"type" : "object", "type" : "object",
"additionalProperties" : false, "additionalProperties" : false,
"description" : "Graphical files associated with the artifact", "description" : "Graphical files associated with the artifact",
"required" : [ "image", "map" ], "required" : [ "image", "map", "scenarioBonus" ],
"properties" : { "properties" : {
"image" : { "image" : {
"type" : "string", "type" : "string",
"description" : "Base image for this artifact, used for example in hero screen", "description" : "Base image for this artifact, used for example in hero screen",
"format" : "imageFile" "format" : "imageFile"
}, },
"large" : { "scenarioBonus" : {
"type" : "string", "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" "format" : "imageFile"
}, },
"map" : { "map" : {

View File

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

View File

@ -119,8 +119,67 @@
"additionalProperties" : false "additionalProperties" : false
} }
}, },
"required" : ["type", "name", "school", "level", "power","gainChance","flags","levels"], "required" : ["type", "name", "school", "level", "power", "flags", "levels"],
"additionalProperties" : false, "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" : { "properties" : {
"index" : { "index" : {
"type" : "number", "type" : "number",
@ -265,7 +324,7 @@
}, },
"iconEffect" : { "iconEffect" : {
"type" : "string", "type" : "string",
"description" : "Resourse path of icon for spell effects during battle" , "description" : "Resourse path of icon for spell effects during battle",
"format" : "imageFile" "format" : "imageFile"
}, },
"iconImmune" : { "iconImmune" : {
@ -275,7 +334,7 @@
}, },
"iconScenarioBonus" : { "iconScenarioBonus" : {
"type" : "string", "type" : "string",
"description" : "Resourse path of icon for scenario bonus" , "description" : "Resourse path of 58x64 icon for scenario bonus",
"format" : "imageFile" "format" : "imageFile"
} }
} }
@ -293,7 +352,6 @@
"levels" : { "levels" : {
"type" : "object", "type" : "object",
"additionalProperties" : false, "additionalProperties" : false,
"required" : ["none", "basic", "advanced", "expert"],
"properties" : { "properties" : {
"base" : { "base" : {
"type" : "object", "type" : "object",

View File

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

View File

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

View File

@ -249,6 +249,10 @@ std::shared_ptr<CSkill> CSkillHandler::loadFromJson(const std::string & scope, c
skillAtLevel.iconSmall = levelNode["images"]["small"].String(); skillAtLevel.iconSmall = levelNode["images"]["small"].String();
skillAtLevel.iconMedium = levelNode["images"]["medium"].String(); skillAtLevel.iconMedium = levelNode["images"]["medium"].String();
skillAtLevel.iconLarge = levelNode["images"]["large"].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()) for(const auto & b : json["specialty"].Vector())

View File

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

View File

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

View File

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

View File

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