1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-11-27 22:49:25 +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

@@ -66,8 +66,8 @@ std::optional<CampaignScenarioID> CGameStateCampaign::getHeroesSourceScenario()
auto campaignState = gameState->scenarioOps->campState;
auto bonus = currentBonus();
if(bonus && bonus->type == CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO)
return static_cast<CampaignScenarioID>(bonus->info2);
if(bonus && bonus->getType() == CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO)
return bonus->getValue<CampaignBonusHeroesFromScenario>().scenario;
return campaignState->lastScenario();
}
@@ -211,17 +211,18 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(vstd::RNG & randomGenerat
void CGameStateCampaign::placeCampaignHeroes(vstd::RNG & randomGenerator)
{
// place bonus hero
auto campaignState = gameState->scenarioOps->campState;
auto campaignBonus = campaignState->getBonus(*campaignState->currentScenario());
bool campaignGiveHero = campaignBonus && campaignBonus->type == CampaignBonusType::HERO;
const auto & campaignState = gameState->scenarioOps->campState;
const auto & campaignBonus = campaignState->getBonus(*campaignState->currentScenario());
bool campaignGiveHero = campaignBonus && campaignBonus->getType() == CampaignBonusType::HERO;
if(campaignGiveHero)
{
auto playerColor = PlayerColor(campaignBonus->info1);
auto it = gameState->scenarioOps->playerInfos.find(playerColor);
const auto & campaignBonusValue = campaignBonus->getValue<CampaignBonusStartingHero>();
const auto & playerColor = campaignBonusValue.startingPlayer;
const auto & it = gameState->scenarioOps->playerInfos.find(playerColor);
if(it != gameState->scenarioOps->playerInfos.end())
{
HeroTypeID heroTypeId = HeroTypeID(campaignBonus->info2);
HeroTypeID heroTypeId = campaignBonusValue.hero;
if(heroTypeId == HeroTypeID::CAMP_RANDOM) // random bonus hero
{
heroTypeId = gameState->pickUnusedHeroTypeRandomly(randomGenerator, playerColor);
@@ -309,20 +310,22 @@ void CGameStateCampaign::giveCampaignBonusToHero(CGHeroInstance * hero)
assert(curBonus->isBonusForHero());
//apply bonus
switch(curBonus->type)
switch(curBonus->getType())
{
case CampaignBonusType::SPELL:
{
hero->addSpellToSpellbook(SpellID(curBonus->info2));
const auto & bonusValue = curBonus->getValue<CampaignBonusSpell>();
hero->addSpellToSpellbook(bonusValue.spell);
break;
}
case CampaignBonusType::MONSTER:
{
const auto & bonusValue = curBonus->getValue<CampaignBonusCreatures>();
for(int i = 0; i < GameConstants::ARMY_SIZE; i++)
{
if(hero->slotEmpty(SlotID(i)))
{
hero->addToSlot(SlotID(i), CreatureID(curBonus->info2), curBonus->info3);
hero->addToSlot(SlotID(i), bonusValue.creature, bonusValue.amount);
break;
}
}
@@ -330,13 +333,15 @@ void CGameStateCampaign::giveCampaignBonusToHero(CGHeroInstance * hero)
}
case CampaignBonusType::ARTIFACT:
{
if(!gameState->giveHeroArtifact(hero, static_cast<ArtifactID>(curBonus->info2)))
const auto & bonusValue = curBonus->getValue<CampaignBonusArtifact>();
if(!gameState->giveHeroArtifact(hero, bonusValue.artifact))
logGlobal->error("Cannot give starting artifact - no free slots!");
break;
}
case CampaignBonusType::SPELL_SCROLL:
{
const auto scroll = gameState->createScroll(SpellID(curBonus->info2));
const auto & bonusValue = curBonus->getValue<CampaignBonusSpellScroll>();
const auto scroll = gameState->createScroll(bonusValue.spell);
const auto slot = ArtifactUtils::getArtAnyPosition(hero, scroll->getTypeId());
if(ArtifactUtils::isSlotEquipment(slot) || ArtifactUtils::isSlotBackpack(slot))
gameState->map->putArtifactInstance(*hero, scroll->getId(), slot);
@@ -346,10 +351,10 @@ void CGameStateCampaign::giveCampaignBonusToHero(CGHeroInstance * hero)
}
case CampaignBonusType::PRIMARY_SKILL:
{
const ui8 * ptr = reinterpret_cast<const ui8 *>(&curBonus->info2);
const auto & bonusValue = curBonus->getValue<CampaignBonusPrimarySkill>();
for(auto skill : PrimarySkill::ALL_SKILLS())
{
int val = ptr[skill.getNum()];
int val = bonusValue.amounts[skill.getNum()];
if(val == 0)
continue;
@@ -361,7 +366,8 @@ void CGameStateCampaign::giveCampaignBonusToHero(CGHeroInstance * hero)
}
case CampaignBonusType::SECONDARY_SKILL:
{
hero->setSecSkillLevel(SecondarySkill(curBonus->info2), curBonus->info3, ChangeValueMode::ABSOLUTE);
const auto & bonusValue = curBonus->getValue<CampaignBonusSecondarySkill>();
hero->setSecSkillLevel(bonusValue.skill, bonusValue.mastery, ChangeValueMode::ABSOLUTE);
break;
}
}
@@ -526,7 +532,7 @@ void CGameStateCampaign::generateCampaignHeroesToReplace()
void CGameStateCampaign::initHeroes()
{
auto chosenBonus = currentBonus();
if (chosenBonus && chosenBonus->isBonusForHero() && chosenBonus->info1 != HeroTypeID::CAMP_GENERATED.getNum()) //exclude generated heroes
if (chosenBonus && chosenBonus->isBonusForHero() && chosenBonus->getTargetedHero() != HeroTypeID::CAMP_GENERATED.getNum()) //exclude generated heroes
{
//find human player
PlayerColor humanPlayer=PlayerColor::NEUTRAL;
@@ -542,7 +548,7 @@ void CGameStateCampaign::initHeroes()
const auto & heroes = gameState->players.at(humanPlayer).getHeroes();
if (chosenBonus->info1 == HeroTypeID::CAMP_STRONGEST.getNum()) //most powerful
if (chosenBonus->getTargetedHero() == HeroTypeID::CAMP_STRONGEST.getNum()) //most powerful
{
int maxB = -1;
for (int b=0; b<heroes.size(); ++b)
@@ -561,7 +567,7 @@ void CGameStateCampaign::initHeroes()
{
for (auto & hero : heroes)
{
if (hero->getHeroTypeID().getNum() == chosenBonus->info1)
if (hero->getHeroTypeID().getNum() == chosenBonus->getTargetedHero())
{
giveCampaignBonusToHero(hero);
break;
@@ -595,18 +601,17 @@ void CGameStateCampaign::initStartingResources()
return ret;
};
auto chosenBonus = currentBonus();
if(chosenBonus && chosenBonus->type == CampaignBonusType::RESOURCE)
const auto & chosenBonus = currentBonus();
if(chosenBonus && chosenBonus->getType() == CampaignBonusType::RESOURCE)
{
const auto & bonusValue = chosenBonus->getValue<CampaignBonusStartingResources>();
std::vector<const PlayerSettings *> people = getHumanPlayerInfo(); //players we will give resource bonus
for(const PlayerSettings *ps : people)
{
std::vector<GameResID> res; //resources we will give
switch (chosenBonus->info1)
switch (bonusValue.resource.toEnum())
{
case 0: case 1: case 2: case 3: case 4: case 5: case 6:
res.push_back(chosenBonus->info1);
break;
case EGameResID::COMMON: //wood+ore
res.push_back(GameResID(EGameResID::WOOD));
res.push_back(GameResID(EGameResID::ORE));
@@ -618,14 +623,12 @@ void CGameStateCampaign::initStartingResources()
res.push_back(GameResID(EGameResID::GEMS));
break;
default:
assert(0);
res.push_back(bonusValue.resource);
break;
}
//increasing resource quantity
for (auto & re : res)
{
gameState->players.at(ps->color).resources[re] += chosenBonus->info2;
}
gameState->players.at(ps->color).resources[re] += bonusValue.amount;
}
}
}
@@ -637,9 +640,11 @@ void CGameStateCampaign::initTowns()
if (!chosenBonus)
return;
if (chosenBonus->type != CampaignBonusType::BUILDING)
if (chosenBonus->getType() != CampaignBonusType::BUILDING)
return;
const auto & bonusValue = chosenBonus->getValue<CampaignBonusBuilding>();
for (const auto & townID : gameState->map->getAllTowns())
{
auto town = gameState->getTown(townID);
@@ -658,9 +663,9 @@ void CGameStateCampaign::initTowns()
BuildingID newBuilding;
if(gameState->scenarioOps->campState->formatVCMI())
newBuilding = BuildingID(chosenBonus->info1);
newBuilding = bonusValue.building;
else
newBuilding = CBuildingHandler::campToERMU(chosenBonus->info1, town->getFactionID(), town->getBuildings());
newBuilding = CBuildingHandler::campToERMU(bonusValue.building, town->getFactionID(), town->getBuildings());
// Build granted building & all prerequisites - e.g. Mages Guild Lvl 3 should also give Mages Guild Lvl 1 & 2
while(true)
@@ -687,7 +692,7 @@ bool CGameStateCampaign::playerHasStartingHero(PlayerColor playerColor) const
if (!campaignBonus)
return false;
if(campaignBonus->type == CampaignBonusType::HERO && playerColor == PlayerColor(campaignBonus->info1))
if(campaignBonus->getType() == CampaignBonusType::HERO && playerColor == PlayerColor(campaignBonus->getValue<CampaignBonusStartingHero>().startingPlayer))
return true;
return false;
}