1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-11-23 22:37:55 +02:00

Rework campaign bonuses storage in type-safe form

Replaced campaign bonuses from using 3 integers to store anything with
type-safe version that uses std::variant that ensures that all bonuses
are in correct state.

Also removed "interesting" solutions like storing primary skills using
bit shifts.

Prerequirement for HotA campaign support
This commit is contained in:
Ivan Savenko
2025-05-28 18:03:17 +03:00
parent 713dbfb1f7
commit 2cd29c1893
16 changed files with 1028 additions and 707 deletions

View File

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