mirror of
https://github.com/vcmi/vcmi.git
synced 2025-11-25 22:42:04 +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:
@@ -173,25 +173,34 @@ 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:
|
||||
{
|
||||
const auto & bonusValue = bonus.getValue<CampaignBonusSpell>();
|
||||
picNumber = bonusValue.spell.getNum();
|
||||
desc.appendLocalString(EMetaText::GENERAL_TXT, 715);
|
||||
desc.replaceName(SpellID(bonDescs[i].info2));
|
||||
desc.replaceName(bonusValue.spell);
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::MONSTER:
|
||||
picNumber = bonDescs[i].info2 + 2;
|
||||
{
|
||||
const auto & bonusValue = bonus.getValue<CampaignBonusCreatures>();
|
||||
picNumber = bonusValue.creature.getNum() + 2;
|
||||
desc.appendLocalString(EMetaText::GENERAL_TXT, 717);
|
||||
desc.replaceNumber(bonDescs[i].info3);
|
||||
desc.replaceNamePlural(bonDescs[i].info2);
|
||||
desc.replaceNumber(bonusValue.amount);
|
||||
desc.replaceNamePlural(bonusValue.creature);
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::BUILDING:
|
||||
{
|
||||
const auto & bonusValue = bonus.getValue<CampaignBonusBuilding>();
|
||||
FactionID faction;
|
||||
for(auto & elem : GAME->server().si->playerInfos)
|
||||
{
|
||||
@@ -200,15 +209,14 @@ void CBonusSelection::createBonusesIcons()
|
||||
faction = elem.second.castle;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
assert(faction.hasValue());
|
||||
|
||||
BuildingID buildID;
|
||||
if(getCampaign()->formatVCMI())
|
||||
buildID = BuildingID(bonDescs[i].info1);
|
||||
buildID = bonusValue.building;
|
||||
else
|
||||
buildID = CBuildingHandler::campToERMU(bonDescs[i].info1, faction, std::set<BuildingID>());
|
||||
buildID = CBuildingHandler::campToERMU(bonusValue.building, faction, std::set<BuildingID>());
|
||||
picName = graphics->ERMUtoPicture[faction.getNum()][buildID.getNum()];
|
||||
picNumber = -1;
|
||||
|
||||
@@ -217,27 +225,35 @@ void CBonusSelection::createBonusesIcons()
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::ARTIFACT:
|
||||
{
|
||||
const auto & bonusValue = bonus.getValue<CampaignBonusArtifact>();
|
||||
picNumber = bonusValue.artifact;
|
||||
desc.appendLocalString(EMetaText::GENERAL_TXT, 715);
|
||||
desc.replaceName(ArtifactID(bonDescs[i].info2));
|
||||
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(SpellID(bonDescs[i].info2));
|
||||
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>
|
||||
const ui8 * ptr = reinterpret_cast<const ui8 *>(&bonDescs[i].info2);
|
||||
for(int g = 0; g < GameConstants::PRIMARY_SKILLS; ++g)
|
||||
for(int g = 0; g < bonusValue.amounts.size(); ++g)
|
||||
{
|
||||
if(leadingSkill == -1 || ptr[g] > ptr[leadingSkill])
|
||||
if(leadingSkill == -1 || bonusValue.amounts[g] > bonusValue.amounts[leadingSkill])
|
||||
{
|
||||
leadingSkill = g;
|
||||
}
|
||||
if(ptr[g] != 0)
|
||||
if(bonusValue.amounts[g] != 0)
|
||||
{
|
||||
toPrint.push_back(std::make_pair(g, ptr[g]));
|
||||
toPrint.push_back(std::make_pair(g, bonusValue.amounts[g]));
|
||||
}
|
||||
}
|
||||
picNumber = leadingSkill;
|
||||
@@ -258,17 +274,21 @@ void CBonusSelection::createBonusesIcons()
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::SECONDARY_SKILL:
|
||||
{
|
||||
const auto & bonusValue = bonus.getValue<CampaignBonusSecondarySkill>();
|
||||
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;
|
||||
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(bonDescs[i].info1)
|
||||
switch(bonusValue.resource)
|
||||
{
|
||||
case EGameResID::COMMON: //wood + ore
|
||||
{
|
||||
@@ -276,7 +296,7 @@ void CBonusSelection::createBonusesIcons()
|
||||
picNumber = 7;
|
||||
break;
|
||||
}
|
||||
case EGameResID::RARE : //mercury + sulfur + crystal + gems
|
||||
case EGameResID::RARE: //mercury + sulfur + crystal + gems
|
||||
{
|
||||
desc.replaceLocalString(EMetaText::GENERAL_TXT, 722);
|
||||
picNumber = 8;
|
||||
@@ -284,27 +304,30 @@ void CBonusSelection::createBonusesIcons()
|
||||
}
|
||||
default:
|
||||
{
|
||||
desc.replaceName(GameResID(bonDescs[i].info1));
|
||||
picNumber = bonDescs[i].info1;
|
||||
desc.replaceName(bonusValue.resource);
|
||||
picNumber = bonusValue.resource.getNum();
|
||||
}
|
||||
}
|
||||
|
||||
desc.replaceNumber(bonDescs[i].info2);
|
||||
desc.replaceNumber(bonusValue.amount);
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO:
|
||||
{
|
||||
auto superhero = getCampaign()->strongestHero(static_cast<CampaignScenarioID>(bonDescs[i].info2), PlayerColor(bonDescs[i].info1));
|
||||
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(static_cast<CampaignScenarioID>(bonDescs[i].info2)).scenarioName.toString());
|
||||
desc.replaceRawString(getCampaign()->scenario(bonusValue.scenario).scenarioName.toString());
|
||||
break;
|
||||
}
|
||||
|
||||
case CampaignBonusType::HERO:
|
||||
if(bonDescs[i].info2 == HeroTypeID::CAMP_RANDOM.getNum())
|
||||
{
|
||||
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;
|
||||
@@ -313,10 +336,12 @@ void CBonusSelection::createBonusesIcons()
|
||||
else
|
||||
{
|
||||
desc.appendLocalString(EMetaText::GENERAL_TXT, 715); // Start with %s
|
||||
desc.replaceTextID(LIBRARY->heroh->objects[bonDescs[i].info2]->getNameTextID());
|
||||
desc.replaceTextID(bonusValue.hero.toHeroType()->getNameTextID());
|
||||
picNumber = bonusValue.hero.getNum();
|
||||
}
|
||||
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](){
|
||||
if(buttonStart->isActive() && !buttonStart->isBlocked())
|
||||
|
||||
@@ -86,6 +86,7 @@ set(lib_MAIN_SRCS
|
||||
callback/CPlayerSpecificInfoCallback.cpp
|
||||
callback/GameRandomizer.cpp
|
||||
|
||||
campaign/CampaignBonus.cpp
|
||||
campaign/CampaignHandler.cpp
|
||||
campaign/CampaignState.cpp
|
||||
|
||||
@@ -480,7 +481,7 @@ set(lib_MAIN_HEADERS
|
||||
callback/IGameRandomizer.h
|
||||
callback/GameRandomizer.h
|
||||
|
||||
|
||||
campaign/CampaignBonus.h
|
||||
campaign/CampaignConstants.h
|
||||
campaign/CampaignHandler.h
|
||||
campaign/CampaignScenarioPrologEpilog.h
|
||||
|
||||
352
lib/campaign/CampaignBonus.cpp
Normal file
352
lib/campaign/CampaignBonus.cpp
Normal file
@@ -0,0 +1,352 @@
|
||||
/*
|
||||
* CampaignBonus.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "CampaignBonus.h"
|
||||
|
||||
#include "../filesystem/CBinaryReader.h"
|
||||
#include "../json/JsonNode.h"
|
||||
#include "../constants/StringConstants.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
static const std::map<std::string, CampaignBonusType> bonusTypeMap = {
|
||||
{"spell", CampaignBonusType::SPELL},
|
||||
{"creature", CampaignBonusType::MONSTER},
|
||||
{"building", CampaignBonusType::BUILDING},
|
||||
{"artifact", CampaignBonusType::ARTIFACT},
|
||||
{"scroll", CampaignBonusType::SPELL_SCROLL},
|
||||
{"primarySkill", CampaignBonusType::PRIMARY_SKILL},
|
||||
{"secondarySkill", CampaignBonusType::SECONDARY_SKILL},
|
||||
{"resource", CampaignBonusType::RESOURCE},
|
||||
{"prevHero", CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO},
|
||||
{"hero", CampaignBonusType::HERO},
|
||||
};
|
||||
|
||||
static const std::map<std::string, ui16> heroSpecialMap = {
|
||||
{"strongest", HeroTypeID::CAMP_STRONGEST},
|
||||
{"generated", HeroTypeID::CAMP_GENERATED},
|
||||
{"random", HeroTypeID::CAMP_RANDOM}
|
||||
};
|
||||
|
||||
static const std::map<std::string, ui8> resourceTypeMap = {
|
||||
{"wood", EGameResID::WOOD},
|
||||
{"mercury", EGameResID::MERCURY},
|
||||
{"ore", EGameResID::ORE},
|
||||
{"sulfur", EGameResID::SULFUR},
|
||||
{"crystal", EGameResID::CRYSTAL},
|
||||
{"gems", EGameResID::GEMS},
|
||||
{"gold", EGameResID::GOLD},
|
||||
{"common", EGameResID::COMMON},
|
||||
{"rare", EGameResID::RARE}
|
||||
};
|
||||
|
||||
CampaignBonus::CampaignBonus(CBinaryReader & reader, CampaignStartOptions mode)
|
||||
{
|
||||
switch(mode)
|
||||
{
|
||||
case CampaignStartOptions::NONE:
|
||||
// in this mode game should not attempt to create instances of bonuses
|
||||
throw std::runtime_error("Attempt to create empty campaign bonus");
|
||||
|
||||
case CampaignStartOptions::START_BONUS:
|
||||
{
|
||||
auto bonusType = static_cast<CampaignBonusType>(reader.readUInt8());
|
||||
|
||||
switch(bonusType)
|
||||
{
|
||||
case CampaignBonusType::SPELL:
|
||||
{
|
||||
HeroTypeID hero(reader.readUInt16());
|
||||
SpellID spell(reader.readUInt8());
|
||||
data = CampaignBonusSpell{hero, spell};
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::MONSTER:
|
||||
{
|
||||
HeroTypeID hero(reader.readUInt16());
|
||||
CreatureID creature(reader.readUInt16());
|
||||
int32_t amount = reader.readUInt16();
|
||||
data = CampaignBonusCreatures{hero, creature, amount};
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::BUILDING:
|
||||
{
|
||||
BuildingID building(reader.readUInt8());
|
||||
data = CampaignBonusBuilding{building};
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::ARTIFACT:
|
||||
{
|
||||
HeroTypeID hero(reader.readUInt16());
|
||||
ArtifactID artifact(reader.readUInt16());
|
||||
data = CampaignBonusArtifact{hero, artifact};
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::SPELL_SCROLL:
|
||||
{
|
||||
HeroTypeID hero(reader.readUInt16());
|
||||
SpellID spell(reader.readUInt8());
|
||||
data = CampaignBonusSpellScroll{hero, spell};
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::PRIMARY_SKILL:
|
||||
{
|
||||
HeroTypeID hero(reader.readUInt16());
|
||||
std::array<uint8_t, 4> amounts = {};
|
||||
for(auto & value : amounts)
|
||||
value = reader.readUInt8();
|
||||
|
||||
data = CampaignBonusPrimarySkill{hero, amounts};
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::SECONDARY_SKILL:
|
||||
{
|
||||
HeroTypeID hero(reader.readUInt16());
|
||||
SecondarySkill skill(reader.readUInt8());
|
||||
int32_t skillMastery(reader.readUInt8());
|
||||
data = CampaignBonusSecondarySkill{hero, skill, skillMastery};
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::RESOURCE:
|
||||
{
|
||||
GameResID resource(reader.readInt8());
|
||||
int32_t amount(reader.readInt32());
|
||||
data = CampaignBonusStartingResources{resource, amount};
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw std::runtime_error("Corrupted or unsupported h3c file");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CampaignStartOptions::HERO_CROSSOVER: //reading of players (colors / scenarios ?) player can choose
|
||||
{
|
||||
PlayerColor player(reader.readUInt8());
|
||||
CampaignScenarioID scenario(reader.readUInt8());
|
||||
data = CampaignBonusHeroesFromScenario{player, scenario};
|
||||
break;
|
||||
}
|
||||
case CampaignStartOptions::HERO_OPTIONS: //heroes player can choose between
|
||||
{
|
||||
PlayerColor player(reader.readUInt8());
|
||||
HeroTypeID hero(reader.readInt16());
|
||||
data = CampaignBonusStartingHero{player, hero};
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw std::runtime_error("Corrupted or unsupported h3c file");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CampaignBonus::CampaignBonus(const JsonNode & bjson, CampaignStartOptions mode)
|
||||
{
|
||||
CampaignBonusType bonusType;
|
||||
|
||||
if (!bjson["what"].isNull())
|
||||
{
|
||||
bonusType = bonusTypeMap.at(bjson["what"].String());
|
||||
}
|
||||
else if (mode == CampaignStartOptions::HERO_CROSSOVER)
|
||||
{
|
||||
bonusType = CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO;
|
||||
}
|
||||
else if (mode == CampaignStartOptions::HERO_OPTIONS)
|
||||
{
|
||||
bonusType = CampaignBonusType::HERO;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("Corrupted or unsupported vcmp file");
|
||||
}
|
||||
|
||||
const auto & decodeHeroTypeID = [](JsonNode heroType) -> HeroTypeID
|
||||
{
|
||||
if(vstd::contains(heroSpecialMap, heroType.String()))
|
||||
return HeroTypeID(heroSpecialMap.at(heroType.String()));
|
||||
else
|
||||
return HeroTypeID(HeroTypeID::decode(heroType.String()));
|
||||
};
|
||||
|
||||
switch(bonusType)
|
||||
{
|
||||
case CampaignBonusType::SPELL:
|
||||
{
|
||||
HeroTypeID hero(decodeHeroTypeID(bjson["hero"]));
|
||||
SpellID spell(SpellID::decode(bjson["spellType"].String()));
|
||||
data = CampaignBonusSpell{hero, spell};
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::MONSTER:
|
||||
{
|
||||
HeroTypeID hero(decodeHeroTypeID(bjson["hero"]));
|
||||
CreatureID creature(CreatureID::decode(bjson["creatureType"].String()));
|
||||
int32_t amount = bjson["creatureAmount"].Integer();
|
||||
data = CampaignBonusCreatures{hero, creature, amount};
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::BUILDING:
|
||||
{
|
||||
BuildingID building(vstd::find_pos(EBuildingType::names, bjson["buildingType"].String()));
|
||||
data = CampaignBonusBuilding{building};
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::ARTIFACT:
|
||||
{
|
||||
HeroTypeID hero(decodeHeroTypeID(bjson["hero"]));
|
||||
ArtifactID artifact(ArtifactID::decode(bjson["artifactType"].String()));
|
||||
data = CampaignBonusArtifact{hero, artifact};
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::SPELL_SCROLL:
|
||||
{
|
||||
HeroTypeID hero(decodeHeroTypeID(bjson["hero"]));
|
||||
SpellID spell(SpellID::decode(bjson["spellType"].String()));
|
||||
data = CampaignBonusSpellScroll{hero, spell};
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::PRIMARY_SKILL:
|
||||
{
|
||||
HeroTypeID hero(decodeHeroTypeID(bjson["hero"]));
|
||||
std::array<uint8_t, 4> amounts = {};
|
||||
for(size_t i = 0; i < amounts.size(); ++i)
|
||||
amounts[i] = bjson[NPrimarySkill::names[i]].Integer();
|
||||
|
||||
data = CampaignBonusPrimarySkill{hero, amounts};
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::SECONDARY_SKILL:
|
||||
{
|
||||
HeroTypeID hero(decodeHeroTypeID(bjson["hero"]));
|
||||
SecondarySkill skill(SecondarySkill::decode(bjson["skillType"].String()));
|
||||
int32_t skillMastery = bjson["skillMastery"].Integer();
|
||||
data = CampaignBonusSecondarySkill{hero, skill, skillMastery};
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::RESOURCE:
|
||||
{
|
||||
GameResID resource(resourceTypeMap.at(bjson["resourceType"].String()));
|
||||
int32_t resourceAmount(bjson["resourceAmount"].Integer());
|
||||
data = CampaignBonusStartingResources{resource, resourceAmount};
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO: //reading of players (colors / scenarios ?) player can choose
|
||||
{
|
||||
PlayerColor player(SecondarySkill::decode(bjson["playerColor"].String()));
|
||||
CampaignScenarioID scenario(bjson["scenario"].Integer());
|
||||
data = CampaignBonusHeroesFromScenario{player, scenario};
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::HERO: //heroes player can choose between
|
||||
{
|
||||
PlayerColor player(SecondarySkill::decode(bjson["playerColor"].String()));
|
||||
HeroTypeID hero(decodeHeroTypeID(bjson["hero"]));
|
||||
data = CampaignBonusStartingHero{player, hero};
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw std::runtime_error("Corrupted or unsupported h3c file");
|
||||
}
|
||||
}
|
||||
|
||||
JsonNode CampaignBonus::toJson() const
|
||||
{
|
||||
JsonNode bnode;
|
||||
|
||||
const auto & encodeHeroTypeID = [](HeroTypeID heroType) -> JsonNode
|
||||
{
|
||||
if(vstd::contains(vstd::reverseMap(heroSpecialMap), heroType))
|
||||
return JsonNode(vstd::reverseMap(heroSpecialMap)[heroType]);
|
||||
else
|
||||
return JsonNode(HeroTypeID::encode(heroType));
|
||||
};
|
||||
|
||||
bnode["what"].String() = vstd::reverseMap(bonusTypeMap).at(getType());
|
||||
|
||||
switch (getType())
|
||||
{
|
||||
case CampaignBonusType::SPELL:
|
||||
{
|
||||
const auto & bonusValue = getValue<CampaignBonusSpell>();
|
||||
bnode["hero"] = encodeHeroTypeID(bonusValue.hero);
|
||||
bnode["spellType"].String() = SpellID::encode(bonusValue.spell.getNum());
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::MONSTER:
|
||||
{
|
||||
const auto & bonusValue = getValue<CampaignBonusCreatures>();
|
||||
bnode["hero"] = encodeHeroTypeID(bonusValue.hero);
|
||||
bnode["creatureType"].String() = CreatureID::encode(bonusValue.creature.getNum());
|
||||
bnode["creatureAmount"].Integer() = bonusValue.amount;
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::BUILDING:
|
||||
{
|
||||
const auto & bonusValue = getValue<CampaignBonusBuilding>();
|
||||
bnode["buildingType"].String() = EBuildingType::names[bonusValue.building.getNum()];
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::ARTIFACT:
|
||||
{
|
||||
const auto & bonusValue = getValue<CampaignBonusArtifact>();
|
||||
bnode["hero"] = encodeHeroTypeID(bonusValue.hero);
|
||||
bnode["artifactType"].String() = ArtifactID::encode(bonusValue.artifact.getNum());
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::SPELL_SCROLL:
|
||||
{
|
||||
const auto & bonusValue = getValue<CampaignBonusSpellScroll>();
|
||||
bnode["hero"] = encodeHeroTypeID(bonusValue.hero);
|
||||
bnode["spellType"].String() = SpellID::encode(bonusValue.spell.getNum());
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::PRIMARY_SKILL:
|
||||
{
|
||||
const auto & bonusValue = getValue<CampaignBonusPrimarySkill>();
|
||||
bnode["hero"] = encodeHeroTypeID(bonusValue.hero);
|
||||
for(size_t i = 0; i < bonusValue.amounts.size(); ++i)
|
||||
bnode[NPrimarySkill::names[i]].Integer() = bonusValue.amounts[i];
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::SECONDARY_SKILL:
|
||||
{
|
||||
const auto & bonusValue = getValue<CampaignBonusSecondarySkill>();
|
||||
bnode["hero"] = encodeHeroTypeID(bonusValue.hero);
|
||||
bnode["skillType"].String() = SecondarySkill::encode(bonusValue.skill.getNum());
|
||||
bnode["skillMastery"].Integer() = bonusValue.mastery;
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::RESOURCE:
|
||||
{
|
||||
const auto & bonusValue = getValue<CampaignBonusStartingResources>();
|
||||
bnode["resourceType"].String() = vstd::reverseMap(resourceTypeMap)[bonusValue.resource.getNum()];
|
||||
bnode["resourceAmount"].Integer() = bonusValue.amount;
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO:
|
||||
{
|
||||
const auto & bonusValue = getValue<CampaignBonusHeroesFromScenario>();
|
||||
bnode["playerColor"].String() = PlayerColor::encode(bonusValue.startingPlayer);
|
||||
bnode["scenario"].Integer() = bonusValue.scenario.getNum();
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::HERO:
|
||||
{
|
||||
const auto & bonusValue = getValue<CampaignBonusStartingHero>();
|
||||
bnode["playerColor"].String() = PlayerColor::encode(bonusValue.startingPlayer);
|
||||
bnode["hero"] = encodeHeroTypeID(bonusValue.hero);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return bnode;
|
||||
}
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
220
lib/campaign/CampaignBonus.h
Normal file
220
lib/campaign/CampaignBonus.h
Normal file
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* CampaignBonus.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "CampaignConstants.h"
|
||||
#include "../constants/EntityIdentifiers.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class CBinaryReader;
|
||||
class JsonNode;
|
||||
|
||||
struct CampaignBonusSpell
|
||||
{
|
||||
HeroTypeID hero;
|
||||
SpellID spell;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
{
|
||||
h & hero;
|
||||
h & spell;
|
||||
}
|
||||
};
|
||||
|
||||
struct CampaignBonusCreatures
|
||||
{
|
||||
HeroTypeID hero;
|
||||
CreatureID creature;
|
||||
int32_t amount = 0;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
{
|
||||
h & hero;
|
||||
h & creature;
|
||||
h & amount;
|
||||
}
|
||||
};
|
||||
|
||||
struct CampaignBonusBuilding
|
||||
{
|
||||
BuildingID building;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
{
|
||||
h & building;
|
||||
}
|
||||
};
|
||||
|
||||
struct CampaignBonusArtifact
|
||||
{
|
||||
HeroTypeID hero;
|
||||
ArtifactID artifact;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
{
|
||||
h & hero;
|
||||
h & artifact;
|
||||
}
|
||||
};
|
||||
|
||||
struct CampaignBonusSpellScroll
|
||||
{
|
||||
HeroTypeID hero;
|
||||
SpellID spell;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
{
|
||||
h & hero;
|
||||
h & spell;
|
||||
}
|
||||
};
|
||||
|
||||
struct CampaignBonusPrimarySkill
|
||||
{
|
||||
HeroTypeID hero;
|
||||
std::array<uint8_t, 4> amounts = {};
|
||||
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
{
|
||||
h & hero;
|
||||
h & amounts;
|
||||
}
|
||||
};
|
||||
|
||||
struct CampaignBonusSecondarySkill
|
||||
{
|
||||
HeroTypeID hero;
|
||||
SecondarySkill skill;
|
||||
int32_t mastery = 0;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
{
|
||||
h & hero;
|
||||
h & skill;
|
||||
h & mastery;
|
||||
}
|
||||
};
|
||||
|
||||
struct CampaignBonusStartingResources
|
||||
{
|
||||
GameResID resource;
|
||||
int32_t amount = 0;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
{
|
||||
h & resource;
|
||||
h & amount;
|
||||
}
|
||||
};
|
||||
|
||||
struct CampaignBonusHeroesFromScenario
|
||||
{
|
||||
PlayerColor startingPlayer;
|
||||
CampaignScenarioID scenario;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
{
|
||||
h & startingPlayer;
|
||||
h & scenario;
|
||||
}
|
||||
};
|
||||
|
||||
struct CampaignBonusStartingHero
|
||||
{
|
||||
PlayerColor startingPlayer;
|
||||
HeroTypeID hero;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
{
|
||||
h & startingPlayer;
|
||||
h & hero;
|
||||
}
|
||||
};
|
||||
|
||||
class CampaignBonus
|
||||
{
|
||||
using Variant = std::variant<
|
||||
CampaignBonusSpell,
|
||||
CampaignBonusCreatures,
|
||||
CampaignBonusBuilding,
|
||||
CampaignBonusArtifact,
|
||||
CampaignBonusSpellScroll,
|
||||
CampaignBonusPrimarySkill,
|
||||
CampaignBonusSecondarySkill,
|
||||
CampaignBonusStartingResources,
|
||||
CampaignBonusHeroesFromScenario,
|
||||
CampaignBonusStartingHero>;
|
||||
|
||||
Variant data;
|
||||
|
||||
template<typename T, typename = void>
|
||||
struct HasHero : std::false_type { };
|
||||
|
||||
template<typename T>
|
||||
struct HasHero<T, decltype(std::declval<T>().hero, void())> : std::true_type { };
|
||||
|
||||
public:
|
||||
CampaignBonus() = default;
|
||||
|
||||
template<typename BonusType>
|
||||
CampaignBonus(const BonusType & value)
|
||||
:data(value)
|
||||
{}
|
||||
|
||||
DLL_LINKAGE CampaignBonus(CBinaryReader & reader, CampaignStartOptions mode);
|
||||
DLL_LINKAGE CampaignBonus(const JsonNode & json, CampaignStartOptions mode);
|
||||
|
||||
template<typename T>
|
||||
const T & getValue() const
|
||||
{
|
||||
return std::get<T>(data);
|
||||
}
|
||||
|
||||
HeroTypeID getTargetedHero() const
|
||||
{
|
||||
HeroTypeID result;
|
||||
|
||||
std::visit([&result](const auto & bonusValue){
|
||||
if constexpr (HasHero<decltype(bonusValue)>::value)
|
||||
{
|
||||
result = bonusValue.hero;
|
||||
}
|
||||
throw std::runtime_error("Attempt to get targeted hero on invalid type!");
|
||||
}, data);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool isBonusForHero() const
|
||||
{
|
||||
auto type = getType();
|
||||
return type == CampaignBonusType::SPELL ||
|
||||
type == CampaignBonusType::MONSTER ||
|
||||
type == CampaignBonusType::ARTIFACT ||
|
||||
type == CampaignBonusType::SPELL_SCROLL ||
|
||||
type == CampaignBonusType::PRIMARY_SKILL ||
|
||||
type == CampaignBonusType::SECONDARY_SKILL;
|
||||
}
|
||||
|
||||
CampaignBonusType getType() const
|
||||
{
|
||||
return static_cast<CampaignBonusType>(data.index());
|
||||
}
|
||||
|
||||
DLL_LINKAGE JsonNode toJson() const;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
{
|
||||
h & data;
|
||||
}
|
||||
};
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
@@ -27,7 +27,7 @@ enum class CampaignVersion : uint8_t
|
||||
VCMI_MAX = 1,
|
||||
};
|
||||
|
||||
enum class CampaignStartOptions: int8_t
|
||||
enum class CampaignStartOptions : int8_t
|
||||
{
|
||||
NONE = 0,
|
||||
START_BONUS,
|
||||
@@ -37,7 +37,6 @@ enum class CampaignStartOptions: int8_t
|
||||
|
||||
enum class CampaignBonusType : int8_t
|
||||
{
|
||||
NONE = -1,
|
||||
SPELL,
|
||||
MONSTER,
|
||||
BUILDING,
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
#include "../filesystem/CBinaryReader.h"
|
||||
#include "../filesystem/CZipLoader.h"
|
||||
#include "../GameLibrary.h"
|
||||
#include "../constants/StringConstants.h"
|
||||
#include "../mapping/CMapHeader.h"
|
||||
#include "../mapping/CMapService.h"
|
||||
#include "../modding/CModHandler.h"
|
||||
@@ -29,7 +28,7 @@
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
void CampaignHandler::readCampaign(Campaign * ret, const std::vector<ui8> & input, std::string filename, std::string modName, std::string encoding)
|
||||
void CampaignHandler::readCampaign(Campaign * ret, const std::vector<ui8> & input, const std::string & filename, const std::string & modName, const std::string & encoding)
|
||||
{
|
||||
if (input.front() < uint8_t(' ')) // binary format
|
||||
{
|
||||
@@ -77,8 +76,8 @@ std::unique_ptr<Campaign> CampaignHandler::getHeader( const std::string & name)
|
||||
std::shared_ptr<CampaignState> CampaignHandler::getCampaign( const std::string & name )
|
||||
{
|
||||
ResourcePath resourceID(name, EResType::CAMPAIGN);
|
||||
std::string modName = LIBRARY->modh->findResourceOrigin(resourceID);
|
||||
std::string encoding = LIBRARY->modh->findResourceEncoding(resourceID);
|
||||
const std::string & modName = LIBRARY->modh->findResourceOrigin(resourceID);
|
||||
const std::string & encoding = LIBRARY->modh->findResourceEncoding(resourceID);
|
||||
|
||||
auto ret = std::make_unique<CampaignState>();
|
||||
|
||||
@@ -124,14 +123,14 @@ static std::string convertMapName(std::string input)
|
||||
return input;
|
||||
}
|
||||
|
||||
std::string CampaignHandler::readLocalizedString(CampaignHeader & target, CBinaryReader & reader, std::string filename, std::string modName, std::string encoding, std::string identifier)
|
||||
std::string CampaignHandler::readLocalizedString(CampaignHeader & target, CBinaryReader & reader, const std::string & filename, const std::string & modName, const std::string & encoding, const std::string & identifier)
|
||||
{
|
||||
std::string input = TextOperations::toUnicode(reader.readBaseString(), encoding);
|
||||
const std::string & input = TextOperations::toUnicode(reader.readBaseString(), encoding);
|
||||
|
||||
return readLocalizedString(target, input, filename, modName, identifier);
|
||||
}
|
||||
|
||||
std::string CampaignHandler::readLocalizedString(CampaignHeader & target, std::string text, std::string filename, std::string modName, std::string identifier)
|
||||
std::string CampaignHandler::readLocalizedString(CampaignHeader & target, const std::string & text, const std::string & filename, const std::string & modName, const std::string & identifier)
|
||||
{
|
||||
TextIdentifier stringID( "campaign", convertMapName(filename), identifier);
|
||||
|
||||
@@ -142,7 +141,7 @@ std::string CampaignHandler::readLocalizedString(CampaignHeader & target, std::s
|
||||
return stringID.get();
|
||||
}
|
||||
|
||||
void CampaignHandler::readHeaderFromJson(CampaignHeader & ret, JsonNode & reader, std::string filename, std::string modName, std::string encoding)
|
||||
void CampaignHandler::readHeaderFromJson(CampaignHeader & ret, JsonNode & reader, const std::string & filename, const std::string & modName, const std::string & encoding)
|
||||
{
|
||||
ret.version = static_cast<CampaignVersion>(reader["version"].Integer());
|
||||
if(ret.version < CampaignVersion::VCMI_MIN || ret.version > CampaignVersion::VCMI_MAX)
|
||||
@@ -260,44 +259,6 @@ static const std::map<std::string, CampaignStartOptions> startOptionsMap = {
|
||||
{"hero", CampaignStartOptions::HERO_OPTIONS}
|
||||
};
|
||||
|
||||
static const std::map<std::string, CampaignBonusType> bonusTypeMap = {
|
||||
{"spell", CampaignBonusType::SPELL},
|
||||
{"creature", CampaignBonusType::MONSTER},
|
||||
{"building", CampaignBonusType::BUILDING},
|
||||
{"artifact", CampaignBonusType::ARTIFACT},
|
||||
{"scroll", CampaignBonusType::SPELL_SCROLL},
|
||||
{"primarySkill", CampaignBonusType::PRIMARY_SKILL},
|
||||
{"secondarySkill", CampaignBonusType::SECONDARY_SKILL},
|
||||
{"resource", CampaignBonusType::RESOURCE},
|
||||
//{"prevHero", CScenarioTravel::STravelBonus::EBonusType::HEROES_FROM_PREVIOUS_SCENARIO},
|
||||
//{"hero", CScenarioTravel::STravelBonus::EBonusType::HERO},
|
||||
};
|
||||
|
||||
static const std::map<std::string, ui32> primarySkillsMap = {
|
||||
{"attack", 0},
|
||||
{"defence", 8},
|
||||
{"spellpower", 16},
|
||||
{"knowledge", 24},
|
||||
};
|
||||
|
||||
static const std::map<std::string, ui16> heroSpecialMap = {
|
||||
{"strongest", HeroTypeID::CAMP_STRONGEST},
|
||||
{"generated", HeroTypeID::CAMP_GENERATED},
|
||||
{"random", HeroTypeID::CAMP_RANDOM}
|
||||
};
|
||||
|
||||
static const std::map<std::string, ui8> resourceTypeMap = {
|
||||
{"wood", EGameResID::WOOD},
|
||||
{"mercury", EGameResID::MERCURY},
|
||||
{"ore", EGameResID::ORE},
|
||||
{"sulfur", EGameResID::SULFUR},
|
||||
{"crystal", EGameResID::CRYSTAL},
|
||||
{"gems", EGameResID::GEMS},
|
||||
{"gold", EGameResID::GOLD},
|
||||
{"common", EGameResID::COMMON},
|
||||
{"rare", EGameResID::RARE}
|
||||
};
|
||||
|
||||
CampaignTravel CampaignHandler::readScenarioTravelFromJson(JsonNode & reader)
|
||||
{
|
||||
CampaignTravel ret;
|
||||
@@ -327,117 +288,12 @@ CampaignTravel CampaignHandler::readScenarioTravelFromJson(JsonNode & reader)
|
||||
}
|
||||
|
||||
ret.startOptions = startOptionsMap.at(reader["startOptions"].String());
|
||||
switch(ret.startOptions)
|
||||
{
|
||||
case CampaignStartOptions::NONE:
|
||||
//no bonuses. Seems to be OK
|
||||
break;
|
||||
case CampaignStartOptions::START_BONUS: //reading of bonuses player can choose
|
||||
{
|
||||
|
||||
if (!reader["playerColor"].isNull())
|
||||
ret.playerColor = PlayerColor(PlayerColor::decode(reader["playerColor"].String()));
|
||||
|
||||
for(auto & bjson : reader["bonuses"].Vector())
|
||||
{
|
||||
CampaignBonus bonus;
|
||||
bonus.type = bonusTypeMap.at(bjson["what"].String());
|
||||
switch (bonus.type)
|
||||
{
|
||||
case CampaignBonusType::RESOURCE:
|
||||
bonus.info1 = resourceTypeMap.at(bjson["type"].String());
|
||||
bonus.info2 = bjson["amount"].Integer();
|
||||
break;
|
||||
|
||||
case CampaignBonusType::BUILDING:
|
||||
bonus.info1 = vstd::find_pos(EBuildingType::names, bjson["type"].String());
|
||||
if(bonus.info1 == -1)
|
||||
logGlobal->warn("VCMP Loading: unresolved building identifier %s", bjson["type"].String());
|
||||
break;
|
||||
|
||||
default:
|
||||
auto heroIdentifier = bjson["hero"].String();
|
||||
auto it = heroSpecialMap.find(heroIdentifier);
|
||||
if(it != heroSpecialMap.end())
|
||||
bonus.info1 = it->second;
|
||||
else
|
||||
if(auto identifier = LIBRARY->identifiers()->getIdentifier(ModScope::scopeMap(), "hero", heroIdentifier))
|
||||
bonus.info1 = identifier.value();
|
||||
else
|
||||
logGlobal->warn("VCMP Loading: unresolved hero identifier %s", heroIdentifier);
|
||||
|
||||
bonus.info3 = bjson["amount"].Integer();
|
||||
|
||||
switch(bonus.type)
|
||||
{
|
||||
case CampaignBonusType::SPELL:
|
||||
case CampaignBonusType::MONSTER:
|
||||
case CampaignBonusType::SECONDARY_SKILL:
|
||||
case CampaignBonusType::ARTIFACT:
|
||||
if(auto identifier = LIBRARY->identifiers()->getIdentifier(ModScope::scopeMap(), bjson["what"].String(), bjson["type"].String()))
|
||||
bonus.info2 = identifier.value();
|
||||
else
|
||||
logGlobal->warn("VCMP Loading: unresolved %s identifier %s", bjson["what"].String(), bjson["type"].String());
|
||||
break;
|
||||
|
||||
case CampaignBonusType::SPELL_SCROLL:
|
||||
if(auto Identifier = LIBRARY->identifiers()->getIdentifier(ModScope::scopeMap(), "spell", bjson["type"].String()))
|
||||
bonus.info2 = Identifier.value();
|
||||
else
|
||||
logGlobal->warn("VCMP Loading: unresolved spell scroll identifier %s", bjson["type"].String());
|
||||
break;
|
||||
|
||||
case CampaignBonusType::PRIMARY_SKILL:
|
||||
for(auto & ps : primarySkillsMap)
|
||||
bonus.info2 |= bjson[ps.first].Integer() << ps.second;
|
||||
break;
|
||||
|
||||
default:
|
||||
bonus.info2 = bjson["type"].Integer();
|
||||
}
|
||||
break;
|
||||
}
|
||||
ret.bonusesToChoose.push_back(bonus);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CampaignStartOptions::HERO_CROSSOVER: //reading of players (colors / scenarios ?) player can choose
|
||||
{
|
||||
for(auto & bjson : reader["bonuses"].Vector())
|
||||
{
|
||||
CampaignBonus bonus;
|
||||
bonus.type = CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO;
|
||||
bonus.info1 = bjson["playerColor"].Integer(); //player color
|
||||
bonus.info2 = bjson["scenario"].Integer(); //from what scenario
|
||||
ret.bonusesToChoose.push_back(bonus);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CampaignStartOptions::HERO_OPTIONS: //heroes player can choose between
|
||||
{
|
||||
for(auto & bjson : reader["bonuses"].Vector())
|
||||
{
|
||||
CampaignBonus bonus;
|
||||
bonus.type = CampaignBonusType::HERO;
|
||||
bonus.info1 = bjson["playerColor"].Integer(); //player color
|
||||
|
||||
auto heroIdentifier = bjson["hero"].String();
|
||||
auto it = heroSpecialMap.find(heroIdentifier);
|
||||
if(it != heroSpecialMap.end())
|
||||
bonus.info2 = it->second;
|
||||
else
|
||||
if (auto identifier = LIBRARY->identifiers()->getIdentifier(ModScope::scopeMap(), "hero", heroIdentifier))
|
||||
bonus.info2 = identifier.value();
|
||||
else
|
||||
logGlobal->warn("VCMP Loading: unresolved hero identifier %s", heroIdentifier);
|
||||
|
||||
ret.bonusesToChoose.push_back(bonus);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
logGlobal->warn("VCMP Loading: Unsupported start options value");
|
||||
break;
|
||||
}
|
||||
}
|
||||
ret.bonusesToChoose.emplace_back(bjson, ret.startOptions);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -454,99 +310,19 @@ void CampaignHandler::writeScenarioTravelToJson(JsonNode & node, const CampaignT
|
||||
node["heroKeeps"].Vector().push_back(JsonNode("spells"));
|
||||
if(travel.whatHeroKeeps.artifacts)
|
||||
node["heroKeeps"].Vector().push_back(JsonNode("artifacts"));
|
||||
for(auto & c : travel.monstersKeptByHero)
|
||||
for(const auto & c : travel.monstersKeptByHero)
|
||||
node["keepCreatures"].Vector().push_back(JsonNode(CreatureID::encode(c)));
|
||||
for(auto & a : travel.artifactsKeptByHero)
|
||||
for(const auto & a : travel.artifactsKeptByHero)
|
||||
node["keepArtifacts"].Vector().push_back(JsonNode(ArtifactID::encode(a)));
|
||||
node["startOptions"].String() = vstd::reverseMap(startOptionsMap)[travel.startOptions];
|
||||
|
||||
switch(travel.startOptions)
|
||||
{
|
||||
case CampaignStartOptions::NONE:
|
||||
break;
|
||||
case CampaignStartOptions::START_BONUS:
|
||||
{
|
||||
node["playerColor"].String() = PlayerColor::encode(travel.playerColor);
|
||||
for(auto & bonus : travel.bonusesToChoose)
|
||||
{
|
||||
JsonNode bnode;
|
||||
bnode["what"].String() = vstd::reverseMap(bonusTypeMap)[bonus.type];
|
||||
switch (bonus.type)
|
||||
{
|
||||
case CampaignBonusType::RESOURCE:
|
||||
bnode["type"].String() = vstd::reverseMap(resourceTypeMap)[bonus.info1];
|
||||
bnode["amount"].Integer() = bonus.info2;
|
||||
break;
|
||||
case CampaignBonusType::BUILDING:
|
||||
bnode["type"].String() = EBuildingType::names[bonus.info1];
|
||||
break;
|
||||
default:
|
||||
if(vstd::contains(vstd::reverseMap(heroSpecialMap), bonus.info1))
|
||||
bnode["hero"].String() = vstd::reverseMap(heroSpecialMap)[bonus.info1];
|
||||
else
|
||||
bnode["hero"].String() = HeroTypeID::encode(bonus.info1);
|
||||
bnode["amount"].Integer() = bonus.info3;
|
||||
switch(bonus.type)
|
||||
{
|
||||
case CampaignBonusType::SPELL:
|
||||
bnode["type"].String() = SpellID::encode(bonus.info2);
|
||||
break;
|
||||
case CampaignBonusType::MONSTER:
|
||||
bnode["type"].String() = CreatureID::encode(bonus.info2);
|
||||
break;
|
||||
case CampaignBonusType::SECONDARY_SKILL:
|
||||
bnode["type"].String() = SecondarySkill::encode(bonus.info2);
|
||||
break;
|
||||
case CampaignBonusType::ARTIFACT:
|
||||
bnode["type"].String() = ArtifactID::encode(bonus.info2);
|
||||
break;
|
||||
case CampaignBonusType::SPELL_SCROLL:
|
||||
bnode["type"].String() = SpellID::encode(bonus.info2);
|
||||
break;
|
||||
case CampaignBonusType::PRIMARY_SKILL:
|
||||
for(auto & ps : primarySkillsMap)
|
||||
bnode[ps.first].Integer() = (bonus.info2 >> ps.second) & 0xff;
|
||||
break;
|
||||
default:
|
||||
bnode["type"].Integer() = bonus.info2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
node["bonuses"].Vector().push_back(bnode);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CampaignStartOptions::HERO_CROSSOVER:
|
||||
{
|
||||
for(auto & bonus : travel.bonusesToChoose)
|
||||
{
|
||||
JsonNode bnode;
|
||||
bnode["playerColor"].Integer() = bonus.info1;
|
||||
bnode["scenario"].Integer() = bonus.info2;
|
||||
node["bonuses"].Vector().push_back(bnode);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CampaignStartOptions::HERO_OPTIONS:
|
||||
{
|
||||
for(auto & bonus : travel.bonusesToChoose)
|
||||
{
|
||||
JsonNode bnode;
|
||||
bnode["playerColor"].Integer() = bonus.info1;
|
||||
if (travel.playerColor.isValidPlayer())
|
||||
node["playerColor"].String() = PlayerColor::encode(travel.playerColor.getNum());
|
||||
|
||||
if(vstd::contains(vstd::reverseMap(heroSpecialMap), bonus.info2))
|
||||
bnode["hero"].String() = vstd::reverseMap(heroSpecialMap)[bonus.info2];
|
||||
else
|
||||
bnode["hero"].String() = HeroTypeID::encode(bonus.info2);
|
||||
|
||||
node["bonuses"].Vector().push_back(bnode);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (const auto & bonus : travel.bonusesToChoose)
|
||||
node["bonuses"].Vector().push_back(bonus.toJson());
|
||||
}
|
||||
|
||||
void CampaignHandler::readHeaderFromMemory( CampaignHeader & ret, CBinaryReader & reader, std::string filename, std::string modName, std::string encoding )
|
||||
void CampaignHandler::readHeaderFromMemory( CampaignHeader & ret, CBinaryReader & reader, const std::string & filename, const std::string & modName, const std::string & encoding )
|
||||
{
|
||||
ret.version = static_cast<CampaignVersion>(reader.readUInt32());
|
||||
|
||||
@@ -684,114 +460,14 @@ CampaignTravel CampaignHandler::readScenarioTravelFromMemory(CBinaryReader & rea
|
||||
|
||||
ret.startOptions = static_cast<CampaignStartOptions>(reader.readUInt8());
|
||||
|
||||
switch(ret.startOptions)
|
||||
{
|
||||
case CampaignStartOptions::NONE:
|
||||
//no bonuses. Seems to be OK
|
||||
break;
|
||||
case CampaignStartOptions::START_BONUS: //reading of bonuses player can choose
|
||||
{
|
||||
if (ret.startOptions == CampaignStartOptions::START_BONUS)
|
||||
ret.playerColor.setNum(reader.readUInt8());
|
||||
ui8 numOfBonuses = reader.readUInt8();
|
||||
for (int g=0; g<numOfBonuses; ++g)
|
||||
{
|
||||
CampaignBonus bonus;
|
||||
bonus.type = static_cast<CampaignBonusType>(reader.readUInt8());
|
||||
//hero: FFFD means 'most powerful' and FFFE means 'generated'
|
||||
switch(bonus.type)
|
||||
{
|
||||
case CampaignBonusType::SPELL:
|
||||
{
|
||||
bonus.info1 = reader.readUInt16(); //hero
|
||||
bonus.info2 = reader.readUInt8(); //spell ID
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::MONSTER:
|
||||
{
|
||||
bonus.info1 = reader.readUInt16(); //hero
|
||||
bonus.info2 = reader.readUInt16(); //monster type
|
||||
bonus.info3 = reader.readUInt16(); //monster count
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::BUILDING:
|
||||
{
|
||||
bonus.info1 = reader.readUInt8(); //building ID (0 - town hall, 1 - city hall, 2 - capitol, etc)
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::ARTIFACT:
|
||||
{
|
||||
bonus.info1 = reader.readUInt16(); //hero
|
||||
bonus.info2 = reader.readUInt16(); //artifact ID
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::SPELL_SCROLL:
|
||||
{
|
||||
bonus.info1 = reader.readUInt16(); //hero
|
||||
bonus.info2 = reader.readUInt8(); //spell ID
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::PRIMARY_SKILL:
|
||||
{
|
||||
bonus.info1 = reader.readUInt16(); //hero
|
||||
bonus.info2 = reader.readUInt32(); //bonuses (4 bytes for 4 skills)
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::SECONDARY_SKILL:
|
||||
{
|
||||
bonus.info1 = reader.readUInt16(); //hero
|
||||
bonus.info2 = reader.readUInt8(); //skill ID
|
||||
bonus.info3 = reader.readUInt8(); //skill level
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::RESOURCE:
|
||||
{
|
||||
bonus.info1 = reader.readUInt8(); //type
|
||||
//FD - wood+ore
|
||||
//FE - mercury+sulfur+crystal+gem
|
||||
bonus.info2 = reader.readUInt32(); //count
|
||||
break;
|
||||
}
|
||||
default:
|
||||
logGlobal->warn("Corrupted h3c file");
|
||||
break;
|
||||
}
|
||||
ret.bonusesToChoose.push_back(bonus);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CampaignStartOptions::HERO_CROSSOVER: //reading of players (colors / scenarios ?) player can choose
|
||||
{
|
||||
ui8 numOfBonuses = reader.readUInt8();
|
||||
for (int g=0; g<numOfBonuses; ++g)
|
||||
{
|
||||
CampaignBonus bonus;
|
||||
bonus.type = CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO;
|
||||
bonus.info1 = reader.readUInt8(); //player color
|
||||
bonus.info2 = reader.readUInt8(); //from what scenario
|
||||
|
||||
ret.bonusesToChoose.push_back(bonus);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CampaignStartOptions::HERO_OPTIONS: //heroes player can choose between
|
||||
if (ret.startOptions != CampaignStartOptions::NONE)
|
||||
{
|
||||
ui8 numOfBonuses = reader.readUInt8();
|
||||
for (int g=0; g<numOfBonuses; ++g)
|
||||
{
|
||||
CampaignBonus bonus;
|
||||
bonus.type = CampaignBonusType::HERO;
|
||||
bonus.info1 = reader.readUInt8(); //player color
|
||||
bonus.info2 = reader.readUInt16(); //hero, FF FF - random
|
||||
|
||||
ret.bonusesToChoose.push_back(bonus);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
logGlobal->warn("Corrupted h3c file");
|
||||
break;
|
||||
}
|
||||
ret.bonusesToChoose.emplace_back(reader, ret.startOptions);
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
@@ -16,13 +16,13 @@ VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class DLL_LINKAGE CampaignHandler
|
||||
{
|
||||
static std::string readLocalizedString(CampaignHeader & target, CBinaryReader & reader, std::string filename, std::string modName, std::string encoding, std::string identifier);
|
||||
static std::string readLocalizedString(CampaignHeader & target, std::string text, std::string filename, std::string modName, std::string identifier);
|
||||
static std::string readLocalizedString(CampaignHeader & target, CBinaryReader & reader, const std::string & filename, const std::string & modName, const std::string & encoding, const std::string & identifier);
|
||||
static std::string readLocalizedString(CampaignHeader & target, const std::string & text, const std::string & filename, const std::string & modName, const std::string & identifier);
|
||||
|
||||
static void readCampaign(Campaign * target, const std::vector<ui8> & stream, std::string filename, std::string modName, std::string encoding);
|
||||
static void readCampaign(Campaign * target, const std::vector<ui8> & stream, const std::string & filename, const std::string & modName, const std::string & encoding);
|
||||
|
||||
//parsers for VCMI campaigns (*.vcmp)
|
||||
static void readHeaderFromJson(CampaignHeader & target, JsonNode & reader, std::string filename, std::string modName, std::string encoding);
|
||||
static void readHeaderFromJson(CampaignHeader & target, JsonNode & reader, const std::string & filename, const std::string & modName, const std::string & encoding);
|
||||
static CampaignScenario readScenarioFromJson(JsonNode & reader);
|
||||
static CampaignTravel readScenarioTravelFromJson(JsonNode & reader);
|
||||
|
||||
@@ -30,7 +30,7 @@ class DLL_LINKAGE CampaignHandler
|
||||
static void writeScenarioTravelToJson(JsonNode & node, const CampaignTravel & travel);
|
||||
|
||||
//parsers for original H3C campaigns
|
||||
static void readHeaderFromMemory(CampaignHeader & target, CBinaryReader & reader, std::string filename, std::string modName, std::string encoding);
|
||||
static void readHeaderFromMemory(CampaignHeader & target, CBinaryReader & reader, const std::string & filename, const std::string & modName, const std::string & encoding);
|
||||
static CampaignScenario readScenarioFromMemory(CBinaryReader & reader, CampaignHeader & header);
|
||||
static CampaignTravel readScenarioTravelFromMemory(CBinaryReader & reader, CampaignVersion version);
|
||||
/// returns h3c split in parts. 0 = h3c header, 1-end - maps (binary h3m)
|
||||
|
||||
@@ -80,7 +80,7 @@ JsonNode CampaignRegions::toJson(CampaignRegions cr)
|
||||
JsonNode node;
|
||||
node["prefix"].String() = cr.campPrefix;
|
||||
node["colorSuffixLength"].Float() = cr.colorSuffixLength;
|
||||
if(!cr.campSuffix.size())
|
||||
if(cr.campSuffix.empty())
|
||||
node["suffix"].clear();
|
||||
else
|
||||
node["suffix"].Vector() = JsonVector{ JsonNode(cr.campSuffix[0]), JsonNode(cr.campSuffix[1]), JsonNode(cr.campSuffix[2]) };
|
||||
@@ -127,7 +127,7 @@ std::optional<Point> CampaignRegions::getLabelPosition(CampaignScenarioID which)
|
||||
return region.labelPos;
|
||||
}
|
||||
|
||||
ImagePath CampaignRegions::getNameFor(CampaignScenarioID which, int colorIndex, std::string type) const
|
||||
ImagePath CampaignRegions::getNameFor(CampaignScenarioID which, int colorIndex, const std::string & type) const
|
||||
{
|
||||
auto const & region = regions[which.getNum()];
|
||||
|
||||
@@ -166,24 +166,13 @@ ImagePath CampaignRegions::getConqueredName(CampaignScenarioID which, int color)
|
||||
return getNameFor(which, color, campSuffix[2]);
|
||||
}
|
||||
|
||||
|
||||
bool CampaignBonus::isBonusForHero() const
|
||||
{
|
||||
return type == CampaignBonusType::SPELL ||
|
||||
type == CampaignBonusType::MONSTER ||
|
||||
type == CampaignBonusType::ARTIFACT ||
|
||||
type == CampaignBonusType::SPELL_SCROLL ||
|
||||
type == CampaignBonusType::PRIMARY_SKILL ||
|
||||
type == CampaignBonusType::SECONDARY_SKILL;
|
||||
}
|
||||
|
||||
void CampaignHeader::loadLegacyData(ui8 campId)
|
||||
{
|
||||
campaignRegions = CampaignRegions::getLegacy(campId);
|
||||
numberOfScenarios = LIBRARY->generaltexth->getCampaignLength(campId);
|
||||
}
|
||||
|
||||
void CampaignHeader::loadLegacyData(CampaignRegions regions, int numOfScenario)
|
||||
void CampaignHeader::loadLegacyData(const CampaignRegions & regions, int numOfScenario)
|
||||
{
|
||||
campaignRegions = regions;
|
||||
numberOfScenarios = numOfScenario;
|
||||
|
||||
@@ -9,11 +9,10 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "../GameConstants.h"
|
||||
#include "../filesystem/ResourcePath.h"
|
||||
#include "../serializer/Serializeable.h"
|
||||
#include "../texts/TextLocalizationContainer.h"
|
||||
#include "CampaignConstants.h"
|
||||
#include "CampaignBonus.h"
|
||||
#include "CampaignScenarioPrologEpilog.h"
|
||||
#include "../gameState/HighScore.h"
|
||||
#include "../Point.h"
|
||||
@@ -61,7 +60,7 @@ class DLL_LINKAGE CampaignRegions
|
||||
|
||||
std::vector<RegionDescription> regions;
|
||||
|
||||
ImagePath getNameFor(CampaignScenarioID which, int color, std::string type) const;
|
||||
ImagePath getNameFor(CampaignScenarioID which, int color, const std::string & type) const;
|
||||
|
||||
public:
|
||||
ImagePath getBackgroundName() const;
|
||||
@@ -116,7 +115,7 @@ class DLL_LINKAGE CampaignHeader : public boost::noncopyable
|
||||
bool difficultyChosenByPlayer = false;
|
||||
|
||||
void loadLegacyData(ui8 campId);
|
||||
void loadLegacyData(CampaignRegions regions, int numOfScenario);
|
||||
void loadLegacyData(const CampaignRegions & regions, int numOfScenario);
|
||||
|
||||
TextContainerRegistrable textContainer;
|
||||
|
||||
@@ -166,26 +165,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
struct DLL_LINKAGE CampaignBonus
|
||||
{
|
||||
CampaignBonusType type = CampaignBonusType::NONE;
|
||||
|
||||
//purpose depends on type
|
||||
int32_t info1 = 0;
|
||||
int32_t info2 = 0;
|
||||
int32_t info3 = 0;
|
||||
|
||||
bool isBonusForHero() const;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
{
|
||||
h & type;
|
||||
h & info1;
|
||||
h & info2;
|
||||
h & info3;
|
||||
}
|
||||
};
|
||||
|
||||
struct DLL_LINKAGE CampaignTravel
|
||||
{
|
||||
struct DLL_LINKAGE WhatHeroKeeps
|
||||
@@ -221,8 +200,31 @@ struct DLL_LINKAGE CampaignTravel
|
||||
h & artifactsKeptByHero;
|
||||
h & startOptions;
|
||||
h & playerColor;
|
||||
if (h.hasFeature(Handler::Version::CAMPAIGN_BONUSES))
|
||||
{
|
||||
h & bonusesToChoose;
|
||||
}
|
||||
else
|
||||
{
|
||||
struct OldBonus{
|
||||
CampaignBonusType type = {};
|
||||
int32_t info1 = 0;
|
||||
int32_t info2 = 0;
|
||||
int32_t info3 = 0;
|
||||
|
||||
void serialize(Handler &h)
|
||||
{
|
||||
h & type;
|
||||
h & info1;
|
||||
h & info2;
|
||||
h & info3;
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<OldBonus> oldBonuses;
|
||||
h & oldBonuses;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct DLL_LINKAGE CampaignScenario
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
struct CampaignBonus;
|
||||
class CampaignBonus;
|
||||
struct CampaignTravel;
|
||||
class CGHeroInstance;
|
||||
class CGameState;
|
||||
|
||||
@@ -46,8 +46,9 @@ enum class ESerializationVersion : int32_t
|
||||
SERVER_STATISTICS, // statistics now only saved on server
|
||||
OPPOSITE_SIDE_LIMITER_OWNER, // opposite side limiter no longer stores owner in itself
|
||||
UNIVERSITY_CONFIG, // town university is configurable
|
||||
CAMPAIGN_BONUSES, // new format for scenario bonuses in campaigns
|
||||
|
||||
CURRENT = UNIVERSITY_CONFIG,
|
||||
CURRENT = CAMPAIGN_BONUSES,
|
||||
};
|
||||
|
||||
static_assert(ESerializationVersion::MINIMAL <= ESerializationVersion::CURRENT, "Invalid serialization version definition!");
|
||||
|
||||
@@ -218,11 +218,13 @@ void ScenarioProperties::reloadMapRelatedUi()
|
||||
for(int i = 0; i < ui->comboBoxStartingBonusPlayerPosition->count(); ++i) // copy from player dropdown
|
||||
comboBoxPlayer->addItem(ui->comboBoxStartingBonusPlayerPosition->itemText(i), ui->comboBoxStartingBonusPlayerPosition->itemData(i));
|
||||
|
||||
const auto & bonusValue = bonus.getValue<CampaignBonusHeroesFromScenario>();
|
||||
|
||||
// set selected
|
||||
int index = comboBoxPlayer->findData(bonus.info1);
|
||||
int index = comboBoxPlayer->findData(bonusValue.startingPlayer.getNum());
|
||||
if(index != -1)
|
||||
comboBoxPlayer->setCurrentIndex(index);
|
||||
index = comboBoxOption->findData(bonus.info2);
|
||||
index = comboBoxOption->findData(bonusValue.scenario.getNum());
|
||||
if(index != -1)
|
||||
comboBoxOption->setCurrentIndex(index);
|
||||
|
||||
@@ -337,12 +339,24 @@ void ScenarioProperties::on_buttonBox_clicked(QAbstractButton * button)
|
||||
{
|
||||
for (int i = 0; i < ui->tableWidgetStartingCrossover->rowCount(); ++i)
|
||||
{
|
||||
CampaignBonus bonus;
|
||||
bonus.type = ui->radioButtonStartingOptionHeroCrossover->isChecked() ? CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO : CampaignBonusType::HERO;
|
||||
QComboBox* comboBoxOption = qobject_cast<QComboBox*>(ui->tableWidgetStartingCrossover->cellWidget(i, 0));
|
||||
QComboBox* comboBoxPlayer = qobject_cast<QComboBox*>(ui->tableWidgetStartingCrossover->cellWidget(i, 1));
|
||||
bonus.info1 = comboBoxPlayer->currentData().toInt();
|
||||
bonus.info2 = comboBoxOption->currentData().toInt();
|
||||
CampaignBonus bonus;
|
||||
|
||||
if (ui->radioButtonStartingOptionHeroCrossover->isChecked())
|
||||
{
|
||||
bonus = CampaignBonusHeroesFromScenario{
|
||||
PlayerColor(comboBoxPlayer->currentData().toInt()),
|
||||
CampaignScenarioID(comboBoxOption->currentData().toInt())
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
bonus = CampaignBonusStartingHero{
|
||||
PlayerColor(comboBoxPlayer->currentData().toInt()),
|
||||
HeroTypeID(comboBoxOption->currentData().toInt())
|
||||
};
|
||||
}
|
||||
campaignState->scenarios.at(scenario).travelOptions.bonusesToChoose.push_back(bonus);
|
||||
}
|
||||
}
|
||||
@@ -476,8 +490,8 @@ void ScenarioProperties::on_pushButtonStartingAdd_clicked()
|
||||
}
|
||||
else
|
||||
{
|
||||
CampaignBonus bonus;
|
||||
bonus.type = CampaignBonusType::SPELL;
|
||||
CampaignBonus bonus = CampaignBonusSpell{ HeroTypeID(), SpellID() };
|
||||
|
||||
if(StartingBonus::showStartingBonus(PlayerColor(ui->comboBoxStartingBonusPlayerPosition->currentData().toInt()), map, bonus))
|
||||
{
|
||||
QListWidgetItem * item = new QListWidgetItem(StartingBonus::getBonusListTitle(bonus, map));
|
||||
|
||||
@@ -139,60 +139,84 @@ void StartingBonus::loadBonus()
|
||||
comboBox->setCurrentIndex(index);
|
||||
};
|
||||
|
||||
switch (bonus.type)
|
||||
switch(bonus.getType())
|
||||
{
|
||||
case CampaignBonusType::SPELL:
|
||||
{
|
||||
const auto & bonusValue = bonus.getValue<CampaignBonusSpell>();
|
||||
ui->radioButtonSpell->setChecked(true);
|
||||
on_radioButtonSpell_toggled();
|
||||
setComboBoxValue(ui->comboBoxSpellRecipient, bonus.info1);
|
||||
setComboBoxValue(ui->comboBoxSpellSpell, bonus.info2);
|
||||
setComboBoxValue(ui->comboBoxSpellRecipient, bonusValue.hero.getNum());
|
||||
setComboBoxValue(ui->comboBoxSpellSpell, bonusValue.spell.getNum());
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::MONSTER:
|
||||
{
|
||||
const auto & bonusValue = bonus.getValue<CampaignBonusCreatures>();
|
||||
ui->radioButtonCreature->setChecked(true);
|
||||
on_radioButtonCreature_toggled();
|
||||
setComboBoxValue(ui->comboBoxCreatureRecipient, bonus.info1);
|
||||
setComboBoxValue(ui->comboBoxCreatureCreatureType, bonus.info2);
|
||||
ui->spinBoxCreatureQuantity->setValue(bonus.info3);
|
||||
setComboBoxValue(ui->comboBoxCreatureRecipient, bonusValue.hero.getNum());
|
||||
setComboBoxValue(ui->comboBoxCreatureCreatureType, bonusValue.creature.getNum());
|
||||
ui->spinBoxCreatureQuantity->setValue(bonusValue.amount);
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::BUILDING:
|
||||
{
|
||||
const auto & bonusValue = bonus.getValue<CampaignBonusBuilding>();
|
||||
ui->radioButtonBuilding->setChecked(true);
|
||||
on_radioButtonBuilding_toggled();
|
||||
setComboBoxValue(ui->comboBoxBuildingBuilding, bonus.info1);
|
||||
setComboBoxValue(ui->comboBoxBuildingBuilding, bonusValue.building.getNum());
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::ARTIFACT:
|
||||
{
|
||||
const auto & bonusValue = bonus.getValue<CampaignBonusArtifact>();
|
||||
ui->radioButtonArtifact->setChecked(true);
|
||||
on_radioButtonArtifact_toggled();
|
||||
setComboBoxValue(ui->comboBoxArtifactRecipient, bonus.info1);
|
||||
setComboBoxValue(ui->comboBoxArtifactArtifact, bonus.info2);
|
||||
setComboBoxValue(ui->comboBoxArtifactRecipient, bonusValue.hero.getNum());
|
||||
setComboBoxValue(ui->comboBoxArtifactArtifact, bonusValue.artifact.getNum());
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::SPELL_SCROLL:
|
||||
{
|
||||
const auto & bonusValue = bonus.getValue<CampaignBonusSpellScroll>();
|
||||
ui->radioButtonSpellScroll->setChecked(true);
|
||||
on_radioButtonSpellScroll_toggled();
|
||||
setComboBoxValue(ui->comboBoxSpellScrollRecipient, bonus.info1);
|
||||
setComboBoxValue(ui->comboBoxSpellScrollSpell, bonus.info2);
|
||||
setComboBoxValue(ui->comboBoxSpellScrollRecipient, bonusValue.hero.getNum());
|
||||
setComboBoxValue(ui->comboBoxSpellScrollSpell, bonusValue.spell.getNum());
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::PRIMARY_SKILL:
|
||||
{
|
||||
const auto & bonusValue = bonus.getValue<CampaignBonusPrimarySkill>();
|
||||
ui->radioButtonPrimarySkill->setChecked(true);
|
||||
on_radioButtonPrimarySkill_toggled();
|
||||
setComboBoxValue(ui->comboBoxPrimarySkillRecipient, bonus.info1);
|
||||
ui->spinBoxPrimarySkillAttack->setValue((bonus.info2 >> 0) & 0xff);
|
||||
ui->spinBoxPrimarySkillDefense->setValue((bonus.info2 >> 8) & 0xff);
|
||||
ui->spinBoxPrimarySkillSpell->setValue((bonus.info2 >> 16) & 0xff);
|
||||
ui->spinBoxPrimarySkillKnowledge->setValue((bonus.info2 >> 24) & 0xff);
|
||||
setComboBoxValue(ui->comboBoxPrimarySkillRecipient, bonusValue.hero.getNum());
|
||||
ui->spinBoxPrimarySkillAttack->setValue(bonusValue.amounts[0]);
|
||||
ui->spinBoxPrimarySkillDefense->setValue(bonusValue.amounts[0]);
|
||||
ui->spinBoxPrimarySkillSpell->setValue(bonusValue.amounts[0]);
|
||||
ui->spinBoxPrimarySkillKnowledge->setValue(bonusValue.amounts[0]);
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::SECONDARY_SKILL:
|
||||
{
|
||||
const auto & bonusValue = bonus.getValue<CampaignBonusSecondarySkill>();
|
||||
ui->radioButtonSecondarySkill->setChecked(true);
|
||||
on_radioButtonSecondarySkill_toggled();
|
||||
setComboBoxValue(ui->comboBoxSecondarySkillRecipient, bonus.info1);
|
||||
setComboBoxValue(ui->comboBoxSecondarySkillSecondarySkill, bonus.info2);
|
||||
setComboBoxValue(ui->comboBoxSecondarySkillMastery, bonus.info3);
|
||||
setComboBoxValue(ui->comboBoxSecondarySkillRecipient, bonusValue.hero.getNum());
|
||||
setComboBoxValue(ui->comboBoxSecondarySkillSecondarySkill, bonusValue.skill.getNum());
|
||||
setComboBoxValue(ui->comboBoxSecondarySkillMastery, bonusValue.mastery);
|
||||
break;
|
||||
}
|
||||
case CampaignBonusType::RESOURCE:
|
||||
{
|
||||
const auto & bonusValue = bonus.getValue<CampaignBonusStartingResources>();
|
||||
ui->radioButtonResource->setChecked(true);
|
||||
on_radioButtonResource_toggled();
|
||||
setComboBoxValue(ui->comboBoxResourceResourceType, bonus.info1);
|
||||
ui->spinBoxResourceQuantity->setValue(bonus.info2);
|
||||
setComboBoxValue(ui->comboBoxResourceResourceType, bonusValue.resource.getNum());
|
||||
ui->spinBoxResourceQuantity->setValue(bonusValue.amount);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
@@ -202,68 +226,51 @@ void StartingBonus::loadBonus()
|
||||
void StartingBonus::saveBonus()
|
||||
{
|
||||
if(ui->radioButtonSpell->isChecked())
|
||||
bonus.type = CampaignBonusType::SPELL;
|
||||
bonus = CampaignBonusSpell{
|
||||
HeroTypeID(ui->comboBoxSpellRecipient->currentData().toInt()),
|
||||
SpellID(ui->comboBoxSpellSpell->currentData().toInt())
|
||||
};
|
||||
else if(ui->radioButtonCreature->isChecked())
|
||||
bonus.type = CampaignBonusType::MONSTER;
|
||||
bonus = CampaignBonusCreatures{
|
||||
HeroTypeID(ui->comboBoxCreatureRecipient->currentData().toInt()),
|
||||
CreatureID(ui->comboBoxCreatureCreatureType->currentData().toInt()),
|
||||
int32_t(ui->spinBoxCreatureQuantity->value())
|
||||
};
|
||||
else if(ui->radioButtonBuilding->isChecked())
|
||||
bonus.type = CampaignBonusType::BUILDING;
|
||||
bonus = CampaignBonusBuilding{
|
||||
BuildingID(ui->comboBoxBuildingBuilding->currentData().toInt())
|
||||
};
|
||||
else if(ui->radioButtonArtifact->isChecked())
|
||||
bonus.type = CampaignBonusType::ARTIFACT;
|
||||
bonus = CampaignBonusArtifact{
|
||||
HeroTypeID(ui->comboBoxCreatureRecipient->currentData().toInt()),
|
||||
ArtifactID(ui->comboBoxArtifactArtifact->currentData().toInt())
|
||||
};
|
||||
else if(ui->radioButtonSpellScroll->isChecked())
|
||||
bonus.type = CampaignBonusType::SPELL_SCROLL;
|
||||
bonus = CampaignBonusSpellScroll{
|
||||
HeroTypeID(ui->comboBoxCreatureRecipient->currentData().toInt()),
|
||||
SpellID(ui->comboBoxSpellScrollSpell->currentData().toInt())
|
||||
};
|
||||
else if(ui->radioButtonPrimarySkill->isChecked())
|
||||
bonus.type = CampaignBonusType::PRIMARY_SKILL;
|
||||
else if(ui->radioButtonSecondarySkill->isChecked())
|
||||
bonus.type = CampaignBonusType::SECONDARY_SKILL;
|
||||
else if(ui->radioButtonResource->isChecked())
|
||||
bonus.type = CampaignBonusType::RESOURCE;
|
||||
|
||||
bonus.info1 = 0;
|
||||
bonus.info2 = 0;
|
||||
bonus.info3 = 0;
|
||||
|
||||
switch (bonus.type)
|
||||
bonus = CampaignBonusPrimarySkill{
|
||||
HeroTypeID(ui->comboBoxCreatureRecipient->currentData().toInt()),
|
||||
{
|
||||
case CampaignBonusType::SPELL:
|
||||
bonus.info1 = ui->comboBoxSpellRecipient->currentData().toInt();
|
||||
bonus.info2 = ui->comboBoxSpellSpell->currentData().toInt();
|
||||
break;
|
||||
case CampaignBonusType::MONSTER:
|
||||
bonus.info1 = ui->comboBoxCreatureRecipient->currentData().toInt();
|
||||
bonus.info2 = ui->comboBoxCreatureCreatureType->currentData().toInt();
|
||||
bonus.info3 = ui->spinBoxCreatureQuantity->value();
|
||||
break;
|
||||
case CampaignBonusType::BUILDING:
|
||||
bonus.info1 = ui->comboBoxBuildingBuilding->currentData().toInt();
|
||||
break;
|
||||
case CampaignBonusType::ARTIFACT:
|
||||
bonus.info1 = ui->comboBoxArtifactRecipient->currentData().toInt();
|
||||
bonus.info2 = ui->comboBoxArtifactArtifact->currentData().toInt();
|
||||
break;
|
||||
case CampaignBonusType::SPELL_SCROLL:
|
||||
bonus.info1 = ui->comboBoxSpellScrollRecipient->currentData().toInt();
|
||||
bonus.info2 = ui->comboBoxSpellScrollSpell->currentData().toInt();
|
||||
break;
|
||||
case CampaignBonusType::PRIMARY_SKILL:
|
||||
bonus.info1 = ui->comboBoxPrimarySkillRecipient->currentData().toInt();
|
||||
bonus.info2 |= ui->spinBoxPrimarySkillAttack->value() << 0;
|
||||
bonus.info2 |= ui->spinBoxPrimarySkillDefense->value() << 8;
|
||||
bonus.info2 |= ui->spinBoxPrimarySkillSpell->value() << 16;
|
||||
bonus.info2 |= ui->spinBoxPrimarySkillKnowledge->value() << 24;
|
||||
break;
|
||||
case CampaignBonusType::SECONDARY_SKILL:
|
||||
bonus.info1 = ui->comboBoxSecondarySkillRecipient->currentData().toInt();
|
||||
bonus.info2 = ui->comboBoxSecondarySkillSecondarySkill->currentData().toInt();
|
||||
bonus.info3 = ui->comboBoxSecondarySkillMastery->currentData().toInt();
|
||||
break;
|
||||
case CampaignBonusType::RESOURCE:
|
||||
bonus.info1 = ui->comboBoxResourceResourceType->currentData().toInt();
|
||||
bonus.info2 = ui->spinBoxResourceQuantity->value();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
uint8_t(ui->spinBoxPrimarySkillAttack->value()),
|
||||
uint8_t(ui->spinBoxPrimarySkillDefense->value()),
|
||||
uint8_t(ui->spinBoxPrimarySkillSpell->value()),
|
||||
uint8_t(ui->spinBoxPrimarySkillKnowledge->value()),
|
||||
}
|
||||
};
|
||||
else if(ui->radioButtonSecondarySkill->isChecked())
|
||||
bonus = CampaignBonusSecondarySkill{
|
||||
HeroTypeID(ui->comboBoxCreatureRecipient->currentData().toInt()),
|
||||
SecondarySkill(ui->comboBoxSecondarySkillSecondarySkill->currentData().toInt()),
|
||||
int32_t(ui->comboBoxSecondarySkillMastery->currentData().toInt())
|
||||
};
|
||||
else if(ui->radioButtonResource->isChecked())
|
||||
bonus = CampaignBonusStartingResources{
|
||||
GameResID(ui->comboBoxResourceResourceType->currentData().toInt()),
|
||||
int32_t(ui->spinBoxResourceQuantity->value())
|
||||
};
|
||||
}
|
||||
|
||||
void StartingBonus::on_buttonBox_clicked(QAbstractButton * button)
|
||||
@@ -309,41 +316,67 @@ QString StartingBonus::getBonusListTitle(CampaignBonus bonus, std::shared_ptr<CM
|
||||
}
|
||||
return QString::fromStdString(tmp.toString());
|
||||
};
|
||||
auto getSpellName = [](int id){
|
||||
auto getSpellName = [](SpellID id){
|
||||
MetaString tmp;
|
||||
tmp.appendName(SpellID(id));
|
||||
tmp.appendName(id);
|
||||
return QString::fromStdString(tmp.toString());
|
||||
};
|
||||
auto getMonsterName = [](int id, int amount){
|
||||
auto getMonsterName = [](CreatureID id, int amount){
|
||||
MetaString tmp;
|
||||
tmp.appendName(CreatureID(id), amount);
|
||||
tmp.appendName(id, amount);
|
||||
return QString::fromStdString(tmp.toString());
|
||||
};
|
||||
auto getArtifactName = [](int id){
|
||||
auto getArtifactName = [](ArtifactID id){
|
||||
MetaString tmp;
|
||||
tmp.appendName(ArtifactID(id));
|
||||
tmp.appendName(id);
|
||||
return QString::fromStdString(tmp.toString());
|
||||
};
|
||||
|
||||
switch (bonus.type)
|
||||
switch(bonus.getType())
|
||||
{
|
||||
case CampaignBonusType::SPELL:
|
||||
return tr("%1 spell for %2").arg(getSpellName(bonus.info2)).arg(getHeroName(bonus.info1));
|
||||
{
|
||||
const auto & bonusValue = bonus.getValue<CampaignBonusSpell>();
|
||||
return tr("%1 spell for %2").arg(getSpellName(bonusValue.spell)).arg(getHeroName(bonusValue.hero));
|
||||
}
|
||||
case CampaignBonusType::MONSTER:
|
||||
return tr("%1 %2 for %3").arg(bonus.info3).arg(getMonsterName(bonus.info2, bonus.info3)).arg(getHeroName(bonus.info1));
|
||||
{
|
||||
const auto & bonusValue = bonus.getValue<CampaignBonusCreatures>();
|
||||
return tr("%1 %2 for %3").arg(bonusValue.amount).arg(getMonsterName(bonusValue.creature, bonusValue.amount)).arg(getHeroName(bonusValue.hero));
|
||||
}
|
||||
case CampaignBonusType::BUILDING:
|
||||
{
|
||||
return tr("Building");
|
||||
}
|
||||
case CampaignBonusType::ARTIFACT:
|
||||
return tr("%1 artifact for %2").arg(getArtifactName(bonus.info2)).arg(getHeroName(bonus.info1));
|
||||
{
|
||||
const auto & bonusValue = bonus.getValue<CampaignBonusArtifact>();
|
||||
return tr("%1 artifact for %2").arg(getArtifactName(bonusValue.artifact)).arg(getHeroName(bonusValue.hero));
|
||||
}
|
||||
case CampaignBonusType::SPELL_SCROLL:
|
||||
return tr("%1 spell scroll for %2").arg(getSpellName(bonus.info2)).arg(getHeroName(bonus.info1));
|
||||
{
|
||||
const auto & bonusValue = bonus.getValue<CampaignBonusSpellScroll>();
|
||||
return tr("%1 spell scroll for %2").arg(getSpellName(bonusValue.spell)).arg(getHeroName(bonusValue.hero));
|
||||
}
|
||||
case CampaignBonusType::PRIMARY_SKILL:
|
||||
return tr("Primary skill (Attack: %1, Defense: %2, Spell: %3, Knowledge: %4) for %5").arg((bonus.info2 >> 0) & 0xff).arg((bonus.info2 >> 8) & 0xff).arg((bonus.info2 >> 16) & 0xff).arg((bonus.info2 >> 24) & 0xff).arg(getHeroName(bonus.info1));
|
||||
{
|
||||
const auto & bonusValue = bonus.getValue<CampaignBonusPrimarySkill>();
|
||||
return tr("Primary skill (Attack: %1, Defense: %2, Spell: %3, Knowledge: %4) for %5")
|
||||
.arg(bonusValue.amounts[0])
|
||||
.arg(bonusValue.amounts[1])
|
||||
.arg(bonusValue.amounts[2])
|
||||
.arg(bonusValue.amounts[3])
|
||||
.arg(getHeroName(bonusValue.hero));
|
||||
}
|
||||
case CampaignBonusType::SECONDARY_SKILL:
|
||||
{
|
||||
return tr("Secondary skill");
|
||||
}
|
||||
case CampaignBonusType::RESOURCE:
|
||||
{
|
||||
return tr("Resource");
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include "lib/constants/EntityIdentifiers.h"
|
||||
#include "lib/campaign/CampaignState.h"
|
||||
|
||||
struct CampaignBonus;
|
||||
class CampaignBonus;
|
||||
class CMap;
|
||||
|
||||
namespace Ui {
|
||||
|
||||
@@ -832,12 +832,16 @@ void CVCMIServer::setCampaignBonus(int bonusId)
|
||||
campaignBonus = bonusId;
|
||||
|
||||
const CampaignScenario & scenario = si->campState->scenario(campaignMap);
|
||||
const std::vector<CampaignBonus> & bonDescs = scenario.travelOptions.bonusesToChoose;
|
||||
if(bonDescs[bonusId].type == CampaignBonusType::HERO || bonDescs[bonusId].type == CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO)
|
||||
const CampaignBonus & bonus = scenario.travelOptions.bonusesToChoose.at(bonusId);
|
||||
if(bonus.getType() == CampaignBonusType::HERO || bonus.getType() == CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO)
|
||||
{
|
||||
PlayerColor startingPlayer = bonus.getType() == CampaignBonusType::HERO ?
|
||||
bonus.getValue<CampaignBonusStartingHero>().startingPlayer :
|
||||
bonus.getValue<CampaignBonusHeroesFromScenario>().startingPlayer;
|
||||
|
||||
for(auto & elem : si->playerInfos)
|
||||
{
|
||||
if(elem.first == PlayerColor(bonDescs[bonusId].info1))
|
||||
if(elem.first == startingPlayer)
|
||||
setPlayerConnectedId(elem.second, 1);
|
||||
else
|
||||
setPlayerConnectedId(elem.second, PlayerSettings::PLAYER_AI);
|
||||
|
||||
Reference in New Issue
Block a user