1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-11-06 09:09:40 +02:00

Merge pull request #5414 from IvanSavenko/bonus_icons

[1.7] Configurable icons for bonuses
This commit is contained in:
Ivan Savenko
2025-02-28 17:58:02 +02:00
committed by GitHub
9 changed files with 64 additions and 465 deletions

View File

@@ -310,7 +310,8 @@ CStackWindow::BonusLineSection::BonusLineSection(CStackWindow * owner, size_t li
if(parent->activeBonuses.size() > bonusIndex)
{
BonusInfo & bi = parent->activeBonuses[bonusIndex];
icon[leftRight] = std::make_shared<CPicture>(bi.imagePath, position.x, position.y);
if (!bi.imagePath.empty())
icon[leftRight] = std::make_shared<CPicture>(bi.imagePath, position.x, position.y);
name[leftRight] = std::make_shared<CLabel>(position.x + 60, position.y + 2, FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, bi.name, 137);
description[leftRight] = std::make_shared<CMultiLineLabel>(Rect(position.x + 60, position.y + 20, 137, 26), FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, bi.description);
drawBonusSource(leftRight, Point(position.x - 1, position.y - 1), bi);

View File

@@ -1,54 +1,26 @@
//TODO: selector-based config
// school immunities
// LEVEL_SPELL_IMMUNITY
{
"ADDITIONAL_ATTACK":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_DOUBLE"
}
},
"ADDITIONAL_RETALIATION":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_RETAIL1"
}
},
"ATTACKS_ALL_ADJACENT":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_ROUND"
}
},
"BLOCKS_RANGED_RETALIATION":
{
"graphics":
{
"icon": "zvs/Lib1.res/RANGEDBLOCK"
}
},
"BLOCKS_RETALIATION":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_RETAIL"
}
},
"CATAPULT":
{
"graphics":
{
"icon": "zvs/Lib1.res/Catapult"
}
},
"CATAPULT_EXTRA_SHOTS":
@@ -58,26 +30,14 @@
"CHANGES_SPELL_COST_FOR_ALLY":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_MANA"
}
},
"CHANGES_SPELL_COST_FOR_ENEMY":
{
"graphics":
{
"icon": "zvs/Lib1.res/MagicDamper"
}
},
"CHARGE_IMMUNITY":
{
"graphics":
{
"icon": "zvs/Lib1.res/ChargeImmune"
}
},
"DARKNESS":
@@ -87,42 +47,22 @@
"DEATH_STARE":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_DEATH"
}
},
"DEFENSIVE_STANCE":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_DEFBON"
}
},
"DESTRUCTION":
{
"graphics":
{
"icon": "zvs/Lib1.res/DESTROYER"
}
},
"DOUBLE_DAMAGE_CHANCE":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_DBLOW"
}
},
"DRAGON_NATURE":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_DRAGON"
}
},
"DISGUISED":
@@ -132,148 +72,74 @@
"ENCHANTER":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_CAST1"
}
},
"ENCHANTED":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_BLESS"
}
},
"ENEMY_ATTACK_REDUCTION":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_RATT"
}
},
"ENEMY_DEFENCE_REDUCTION":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_RDEF"
}
},
"FIRE_SHIELD":
{
"graphics":
{
"icon": "zvs/Lib1.res/FireShield"
}
},
"FIRST_STRIKE":
{
"graphics":
{
"icon": "zvs/Lib1.res/FIRSTSTRIKE"
}
},
"FEAR":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_FEAR"
}
},
"FEARLESS":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_FEARL"
}
},
"FEROCITY":
{
"graphics":
{
"icon": "zvs/Lib1.res/Ferocity"
}
},
"FLYING":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_FLY"
}
},
"FREE_SHOOTING":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_SHOOTA"
}
},
"GARGOYLE":
{
"graphics":
{
"icon": "zvs/Lib1.res/NonLiving" // Just use the NonLiving icon for now
}
},
"GENERAL_DAMAGE_REDUCTION":
{
"graphics":
{
"icon": "zvs/Lib1.res/DamageReductionMelee"
}
},
"HATE":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_HATE"
}
},
"HEALER":
{
"graphics":
{
"icon": "zvs/Lib1.res/Healer"
}
},
"HP_REGENERATION":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_TROLL"
}
},
"JOUSTING":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_CHAMP"
}
},
"KING":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_KING3"
}
},
"LEARN_BATTLE_SPELL_CHANCE":
@@ -288,66 +154,34 @@
"LEVEL_SPELL_IMMUNITY":
{
"graphics":
{
"icon": ""
}
},
"LIFE_DRAIN":
{
"graphics":
{
"icon": "zvs/Lib1.res/DrainLife"
}
},
"LIMITED_SHOOTING_RANGE":
{
"graphics":
{
"icon": "zvs/Lib1.res/LIM_SHOOT"
}
},
"MANA_CHANNELING":
{
"graphics":
{
"icon": "zvs/Lib1.res/ManaChannel"
}
},
"MANA_DRAIN":
{
"graphics":
{
"icon": "zvs/Lib1.res/ManaDrain"
}
},
"MAGIC_MIRROR":
{
"graphics":
{
"icon": "zvs/Lib1.res/MagicMirror"
}
},
"MAGIC_RESISTANCE":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_DWARF"
}
},
"MIND_IMMUNITY":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_MIND"
}
},
"NONE":
@@ -357,34 +191,18 @@
"NO_DISTANCE_PENALTY":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_DIST"
}
},
"NO_MELEE_PENALTY":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_MELEE"
}
},
"NO_MORALE":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_MORAL"
}
},
"NO_WALL_PENALTY":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_OBST"
}
},
"NO_TERRAIN_PENALTY":
@@ -394,34 +212,18 @@
"NON_LIVING":
{
"graphics":
{
"icon": "zvs/Lib1.res/NonLiving"
}
},
"MECHANICAL":
{
"graphics":
{
"icon": "zvs/Lib1.res/Mechanical"
}
},
"OPENING_BATTLE_SPELL":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_SPDFIRE"
}
},
"RANDOM_SPELLCASTER":
{
"graphics":
{
"icon": "zvs/Lib1.res/RandomBoost"
}
},
"PERCENTAGE_DAMAGE_BOOST":
@@ -431,114 +233,58 @@
"RANGED_RETALIATION":
{
"graphics":
{
"icon": "zvs/Lib1.res/RANGEDCOUNTER"
}
},
"RECEPTIVE":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_NOFRIM"
}
},
"REBIRTH":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_REBIRTH"
}
},
"RETURN_AFTER_STRIKE":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_HARPY"
}
},
"REVENGE":
{
"graphics":
{
"icon": "zvs/Lib1.res/Revenge"
}
},
"SHOOTER":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_SHOOT"
}
},
"SHOOTS_ALL_ADJACENT":
{
"graphics":
{
"icon": "zvs/Lib1.res/AREASHOT"
}
},
"SOUL_STEAL":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_SUMMON2"
}
},
"SPELLCASTER":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_CASTER"
}
},
"SPELL_AFTER_ATTACK":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_CAST"
}
},
"SPELL_BEFORE_ATTACK":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_CAST2"
}
},
"SPELL_DAMAGE_REDUCTION":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_GOLEM"
}
},
"SPELL_IMMUNITY":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_SPDISB" //todo: configurable use from spell handler
}
},
"SPELL_LIKE_ATTACK":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_SPDFIRE"
}
},
"SPELL_SCHOOL_IMMUNITY":
@@ -547,66 +293,34 @@
"SPELL_RESISTANCE_AURA":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_UNIC"
}
},
"SUMMON_GUARDIANS":
{
"graphics":
{
"icon": "zvs/Lib1.res/SUMMONGUARDS"
}
},
"TWO_HEX_ATTACK_BREATH":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_BREATH"
}
},
"PRISM_HEX_ATTACK_BREATH":
{
"graphics":
{
"icon": "zvs/Lib1.res/PrismBreath"
}
},
"THREE_HEADED_ATTACK":
{
"graphics":
{
"icon": "zvs/Lib1.res/ThreeHeaded"
}
},
"TRANSMUTATION":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_SGTYPE"
}
},
"UNDEAD":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_UNDEAD"
}
},
"UNLIMITED_RETALIATIONS":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_RETAIL1"
}
},
"VISIONS":
@@ -616,28 +330,14 @@
"WIDE_BREATH":
{
"graphics":
{
"icon": "zvs/Lib1.res/MEGABREATH"
}
},
"DISINTEGRATE":
{
"graphics":
{
"icon": "zvs/Lib1.res/DISINTEGRATE"
}
},
"INVINCIBLE":
{
"graphics":
{
"icon": "zvs/Lib1.res/INVINCIBLE"
}
}
}

View File

@@ -665,7 +665,7 @@
},
"defaultRepositoryURL" : {
"type" : "string",
"default" : "https://raw.githubusercontent.com/vcmi/vcmi-mods-repository/develop/vcmi-1.6.json",
"default" : "https://raw.githubusercontent.com/vcmi/vcmi-mods-repository/develop/vcmi-1.7.json",
},
"extraRepositoryEnabled" : {
"type" : "boolean",

View File

@@ -19,6 +19,7 @@
#include "GameConstants.h"
#include "GameLibrary.h"
#include "modding/ModScope.h"
#include "modding/IdentifierStorage.h"
#include "spells/CSpellHandler.h"
#include "texts/CGeneralTextHandler.h"
#include "json/JsonUtils.h"
@@ -57,14 +58,9 @@ CBonusTypeHandler::CBonusTypeHandler()
BONUS_LIST;
#undef BONUS_NAME
load();
}
CBonusTypeHandler::~CBonusTypeHandler()
{
//dtor
}
CBonusTypeHandler::~CBonusTypeHandler() = default;
std::string CBonusTypeHandler::bonusToString(const std::shared_ptr<Bonus> & bonus, const IBonusBearer * bearer, bool description) const
{
@@ -98,151 +94,42 @@ std::string CBonusTypeHandler::bonusToString(const std::shared_ptr<Bonus> & bonu
ImagePath CBonusTypeHandler::bonusToGraphics(const std::shared_ptr<Bonus> & bonus) const
{
std::string fileName;
bool fullPath = false;
const CBonusType & bt = bonusTypes[vstd::to_underlying(bonus->type)];
switch(bonus->type)
{
case BonusType::SPELL_IMMUNITY:
{
fullPath = true;
if (bonus->subtype.as<SpellID>().hasValue())
{
const CSpell * sp = bonus->subtype.as<SpellID>().toSpell();
fileName = sp->getIconImmune();
}
break;
}
case BonusType::SPELL_DAMAGE_REDUCTION: //Spell damage reduction for all schools
{
if (bonus->subtype.as<SpellSchool>() == SpellSchool::ANY)
fileName = "E_GOLEM.bmp";
if (bt.subtypeIcons.count(bonus->subtype.getNum()))
return bt.subtypeIcons.at(bonus->subtype.getNum());
if (bonus->subtype.as<SpellSchool>() == SpellSchool::AIR)
fileName = "E_LIGHT.bmp";
if (bt.valueIcons.count(bonus->val))
return bt.valueIcons.at(bonus->val);
if (bonus->subtype.as<SpellSchool>() == SpellSchool::FIRE)
fileName = "E_FIRE.bmp";
if (bonus->subtype.as<SpellSchool>() == SpellSchool::WATER)
fileName = "E_COLD.bmp";
if (bonus->subtype.as<SpellSchool>() == SpellSchool::EARTH)
fileName = "E_SPEATH1.bmp"; //No separate icon for earth damage
break;
}
case BonusType::SPELL_SCHOOL_IMMUNITY: //for all school
{
if (bonus->subtype.as<SpellSchool>() == SpellSchool::AIR)
fileName = "E_SPAIR.bmp";
if (bonus->subtype.as<SpellSchool>() == SpellSchool::FIRE)
fileName = "E_SPFIRE.bmp";
if (bonus->subtype.as<SpellSchool>() == SpellSchool::WATER)
fileName = "E_SPWATER.bmp";
if (bonus->subtype.as<SpellSchool>() == SpellSchool::EARTH)
fileName = "E_SPEATH.bmp";
break;
}
case BonusType::NEGATIVE_EFFECTS_IMMUNITY:
{
if (bonus->subtype.as<SpellSchool>() == SpellSchool::AIR)
fileName = "E_SPAIR1.bmp";
if (bonus->subtype.as<SpellSchool>() == SpellSchool::FIRE)
fileName = "E_SPFIRE1.bmp";
if (bonus->subtype.as<SpellSchool>() == SpellSchool::WATER)
fileName = "E_SPWATER1.bmp";
if (bonus->subtype.as<SpellSchool>() == SpellSchool::EARTH)
fileName = "E_SPEATH1.bmp";
break;
}
case BonusType::LEVEL_SPELL_IMMUNITY:
{
if(vstd::iswithin(bonus->val, 1, 5))
{
fileName = "E_SPLVL" + std::to_string(bonus->val) + ".bmp";
}
break;
}
case BonusType::KING:
{
if(vstd::iswithin(bonus->val, 0, 3))
{
fileName = "E_KING" + std::to_string(std::max(1, bonus->val)) + ".bmp";
}
break;
}
case BonusType::GENERAL_DAMAGE_REDUCTION:
{
if (bonus->subtype == BonusCustomSubtype::damageTypeMelee)
fileName = "DamageReductionMelee.bmp";
if (bonus->subtype == BonusCustomSubtype::damageTypeRanged)
fileName = "DamageReductionRanged.bmp";
if (bonus->subtype == BonusCustomSubtype::damageTypeAll)
fileName = "DamageReductionAll.bmp";
break;
}
default:
{
const CBonusType & bt = bonusTypes[vstd::to_underlying(bonus->type)];
fileName = bt.icon;
fullPath = true;
}
break;
}
if(!fileName.empty() && !fullPath)
fileName = "zvs/Lib1.res/" + fileName;
return ImagePath::builtinTODO(fileName);
return bt.icon;
}
void CBonusTypeHandler::load()
std::vector<JsonNode> CBonusTypeHandler::loadLegacyData()
{
JsonNode gameConf(JsonPath::builtin("config/gameConfig.json"));
gameConf.setModScope(ModScope::scopeBuiltin());
JsonNode config(JsonUtils::assembleFromFiles(gameConf["bonuses"]));
config.setModScope("vcmi");
load(config);
return {};
}
void CBonusTypeHandler::load(const JsonNode & config)
void CBonusTypeHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
{
for(const auto & node : config.Struct())
auto it = bonusNameMap.find(name);
if(it == bonusNameMap.end())
{
auto it = bonusNameMap.find(node.first);
if(it == bonusNameMap.end())
{
//TODO: new bonus
// CBonusType bt;
// loadItem(node.second, bt);
//
// auto new_id = bonusTypes.size();
//
// bonusTypes.push_back(bt);
logBonus->warn("Unrecognized bonus name! (%s)", node.first);
}
else
{
CBonusType & bt = bonusTypes[vstd::to_underlying(it->second)];
loadItem(node.second, bt, node.first);
logBonus->trace("Loaded bonus type %s", node.first);
}
logBonus->warn("Unrecognized bonus name! (%s)", name);
}
else
{
CBonusType & bt = bonusTypes[vstd::to_underlying(it->second)];
loadItem(data, bt, name);
logBonus->trace("Loaded bonus type %s", name);
}
}
void CBonusTypeHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
{
assert(0);
}
void CBonusTypeHandler::loadItem(const JsonNode & source, CBonusType & dest, const std::string & name) const
@@ -259,7 +146,23 @@ void CBonusTypeHandler::loadItem(const JsonNode & source, CBonusType & dest, con
const JsonNode & graphics = source["graphics"];
if(!graphics.isNull())
dest.icon = graphics["icon"].String();
dest.icon = ImagePath::fromJson(graphics["icon"]);
for (const auto & additionalIcon : graphics["subtypeIcons"].Struct())
{
auto path = ImagePath::fromJson(additionalIcon.second);
LIBRARY->identifiers()->requestIdentifier(additionalIcon.second.getModScope(), additionalIcon.first, [&dest, path](int32_t index)
{
dest.subtypeIcons[index] = path;
});
}
for (const auto & additionalIcon : graphics["valueIcons"].Struct())
{
auto path = ImagePath::fromJson(additionalIcon.second);
int value = std::stoi(additionalIcon.first);
dest.valueIcons[value] = path;
}
}
VCMI_LIB_NAMESPACE_END

View File

@@ -27,18 +27,12 @@ public:
std::string getNameTextID() const;
std::string getDescriptionTextID() const;
template <typename Handler> void serialize(Handler & h)
{
h & icon;
h & identifier;
h & hidden;
}
private:
friend class CBonusTypeHandler;
std::string icon;
ImagePath icon;
std::map<int, ImagePath> subtypeIcons;
std::map<int, ImagePath> valueIcons;
std::string identifier;
bool hidden;
@@ -53,16 +47,11 @@ public:
std::string bonusToString(const std::shared_ptr<Bonus> & bonus, const IBonusBearer * bearer, bool description) const override;
ImagePath bonusToGraphics(const std::shared_ptr<Bonus> & bonus) const override;
template <typename Handler> void serialize(Handler & h)
{
//for now always use up to date configuration
//once modded bonus type will be implemented, serialize only them
std::vector<CBonusType> ignore;
h & ignore;
}
std::vector<JsonNode> loadLegacyData() override;
void loadObject(std::string scope, std::string name, const JsonNode & data) override;
void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override;
private:
void load();
void load(const JsonNode & config);
void loadItem(const JsonNode & source, CBonusType & dest, const std::string & name) const;
std::vector<CBonusType> bonusTypes; //index = BonusType

View File

@@ -51,7 +51,6 @@ namespace scripting
/// Loads and constructs several handlers
class DLL_LINKAGE GameLibrary final : public Services
{
std::shared_ptr<CBonusTypeHandler> bth;
std::shared_ptr<CContentHandler> getContent() const;
void setContent(std::shared_ptr<CContentHandler> content);
@@ -78,6 +77,7 @@ public:
const CIdentifierStorage * identifiers() const;
std::shared_ptr<CArtHandler> arth;
std::shared_ptr<CBonusTypeHandler> bth;
std::shared_ptr<CHeroHandler> heroh;
std::shared_ptr<CHeroClassHandler> heroclassesh;
std::shared_ptr<CCreatureHandler> creh;

View File

@@ -10,6 +10,7 @@
#pragma once
#include "filesystem/ResourcePath.h"
#include "IHandlerBase.h"
VCMI_LIB_NAMESPACE_BEGIN
@@ -18,7 +19,7 @@ struct Bonus;
///High level interface for BonusTypeHandler
class DLL_LINKAGE IBonusTypeHandler
class DLL_LINKAGE IBonusTypeHandler : public IHandlerBase
{
public:
virtual ~IBonusTypeHandler() = default;

View File

@@ -23,6 +23,7 @@
#include "../entities/hero/CHeroClassHandler.h"
#include "../entities/hero/CHeroHandler.h"
#include "../texts/CGeneralTextHandler.h"
#include "../CBonusTypeHandler.h"
#include "../CSkillHandler.h"
#include "../CStopWatch.h"
#include "../IGameSettings.h"
@@ -241,6 +242,7 @@ void CContentHandler::init()
{
handlers.insert(std::make_pair("heroClasses", ContentTypeHandler(LIBRARY->heroclassesh.get(), "heroClass")));
handlers.insert(std::make_pair("artifacts", ContentTypeHandler(LIBRARY->arth.get(), "artifact")));
handlers.insert(std::make_pair("bonuses", ContentTypeHandler(LIBRARY->bth.get(), "bonus")));
handlers.insert(std::make_pair("creatures", ContentTypeHandler(LIBRARY->creh.get(), "creature")));
handlers.insert(std::make_pair("factions", ContentTypeHandler(LIBRARY->townh.get(), "faction")));
handlers.insert(std::make_pair("objects", ContentTypeHandler(LIBRARY->objtypeh.get(), "object")));

View File

@@ -425,6 +425,9 @@ ModsStorage::ModsStorage(const std::vector<TModID> & modsToLoad, const JsonNode
coreModConfig.setModScope(ModScope::scopeBuiltin());
mods.try_emplace(ModScope::scopeBuiltin(), ModScope::scopeBuiltin(), coreModConfig, JsonNode());
// MODS COMPATIBILITY: in 1.6, repository list contains mod list directly, in 1.7 it is located in 'availableMods' node
const auto & availableRepositoryMods = repositoryList["availableMods"].isNull() ? repositoryList : repositoryList["availableMods"];
for(auto modID : modsToLoad)
{
if(ModScope::isScopeReserved(modID))
@@ -439,10 +442,10 @@ ModsStorage::ModsStorage(const std::vector<TModID> & modsToLoad, const JsonNode
continue;
}
mods.try_emplace(modID, modID, modConfig, repositoryList[modID]);
mods.try_emplace(modID, modID, modConfig, availableRepositoryMods[modID]);
}
for(const auto & mod : repositoryList.Struct())
for(const auto & mod : availableRepositoryMods.Struct())
{
if (vstd::contains(modsToLoad, mod.first))
continue;