1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-08-10 22:31:40 +02:00

Add new rewards for configurable objects

This commit is contained in:
Ivan Savenko
2025-04-30 21:51:14 +03:00
parent 9022566c2b
commit 62e774c91e
17 changed files with 144 additions and 71 deletions

View File

@@ -265,9 +265,9 @@ uint64_t RewardEvaluator::getArmyReward(
auto rewardValue = 0; auto rewardValue = 0;
if(!info.reward.artifacts.empty()) if(!info.reward.grantedArtifacts.empty())
{ {
for(auto artID : info.reward.artifacts) for(auto artID : info.reward.grantedArtifacts)
{ {
const auto * art = artID.toArtifact(); const auto * art = artID.toArtifact();
@@ -283,7 +283,7 @@ uint64_t RewardEvaluator::getArmyReward(
} }
} }
totalValue += rewardValue > 0 ? rewardValue / (info.reward.artifacts.size() + info.reward.creatures.size()) : 0; totalValue += rewardValue > 0 ? rewardValue / (info.reward.grantedArtifacts.size() + info.reward.creatures.size()) : 0;
} }
return totalValue; return totalValue;

View File

@@ -47,6 +47,15 @@ std::shared_ptr<CGObjectInstance> CRewardableConstructor::create(IGameCallback *
return ret; return ret;
} }
void CRewardableConstructor::assignBonuses(std::vector<Bonus> & bonuses, MapObjectID objectID) const
{
for (auto & bonus : bonuses)
{
bonus.source = BonusSource::OBJECT_TYPE;
bonus.sid = BonusSourceID(objectID);
}
}
Rewardable::Configuration CRewardableConstructor::generateConfiguration(IGameCallback * cb, vstd::RNG & rand, MapObjectID objectID, const std::map<std::string, JsonNode> & presetVariables) const Rewardable::Configuration CRewardableConstructor::generateConfiguration(IGameCallback * cb, vstd::RNG & rand, MapObjectID objectID, const std::map<std::string, JsonNode> & presetVariables) const
{ {
Rewardable::Configuration result; Rewardable::Configuration result;
@@ -62,11 +71,8 @@ Rewardable::Configuration CRewardableConstructor::generateConfiguration(IGameCal
for(auto & rewardInfo : result.info) for(auto & rewardInfo : result.info)
{ {
for (auto & bonus : rewardInfo.reward.bonuses) assignBonuses(rewardInfo.reward.heroBonuses, objectID);
{ assignBonuses(rewardInfo.reward.playerBonuses, objectID);
bonus.source = BonusSource::OBJECT_TYPE;
bonus.sid = BonusSourceID(objectID);
}
} }
return result; return result;

View File

@@ -14,10 +14,13 @@
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
struct Bonus;
class DLL_LINKAGE CRewardableConstructor : public AObjectTypeHandler class DLL_LINKAGE CRewardableConstructor : public AObjectTypeHandler
{ {
Rewardable::Info objectInfo; Rewardable::Info objectInfo;
void assignBonuses(std::vector<Bonus> & bonuses, MapObjectID objectID) const;
void initTypeData(const JsonNode & config) override; void initTypeData(const JsonNode & config) override;
bool blockVisit = false; bool blockVisit = false;

View File

@@ -83,7 +83,7 @@ void CGPandoraBox::grantRewardWithMessage(const CGHeroInstance * h, int index, b
temp.heroLevel = vi.reward.heroLevel; temp.heroLevel = vi.reward.heroLevel;
temp.primary = vi.reward.primary; temp.primary = vi.reward.primary;
temp.secondary = vi.reward.secondary; temp.secondary = vi.reward.secondary;
temp.bonuses = vi.reward.bonuses; temp.heroBonuses = vi.reward.heroBonuses;
temp.manaDiff = vi.reward.manaDiff; temp.manaDiff = vi.reward.manaDiff;
temp.manaPercentage = vi.reward.manaPercentage; temp.manaPercentage = vi.reward.manaPercentage;
@@ -106,7 +106,7 @@ void CGPandoraBox::grantRewardWithMessage(const CGHeroInstance * h, int index, b
if(vi.reward.manaDiff || vi.reward.manaPercentage >= 0) if(vi.reward.manaDiff || vi.reward.manaPercentage >= 0)
txt = setText(temp.manaDiff > 0, 177, 176, h); txt = setText(temp.manaDiff > 0, 177, 176, h);
for(auto b : vi.reward.bonuses) for(auto b : vi.reward.heroBonuses)
{ {
if(b.val && b.type == BonusType::MORALE) if(b.val && b.type == BonusType::MORALE)
txt = setText(b.val > 0, 179, 178, h); txt = setText(b.val > 0, 179, 178, h);
@@ -122,7 +122,7 @@ void CGPandoraBox::grantRewardWithMessage(const CGHeroInstance * h, int index, b
//artifacts message //artifacts message
temp = Rewardable::Reward{}; temp = Rewardable::Reward{};
temp.artifacts = vi.reward.artifacts; temp.grantedArtifacts = vi.reward.grantedArtifacts;
sendInfoWindow(setText(true, 183, 183, h), temp); sendInfoWindow(setText(true, 183, 183, h), temp);
//creatures message //creatures message
@@ -160,8 +160,8 @@ void CGPandoraBox::grantRewardWithMessage(const CGHeroInstance * h, int index, b
temp.manaPercentage = -1; temp.manaPercentage = -1;
temp.spells.clear(); temp.spells.clear();
temp.creatures.clear(); temp.creatures.clear();
temp.bonuses.clear(); temp.heroBonuses.clear();
temp.artifacts.clear(); temp.grantedArtifacts.clear();
sendInfoWindow(setText(true, 175, 175, h), temp); sendInfoWindow(setText(true, 175, 175, h), temp);
// grant reward afterwards. Note that it may remove object // grant reward afterwards. Note that it may remove object
@@ -229,11 +229,11 @@ void CGPandoraBox::serializeJsonOptions(JsonSerializeFormat & handler)
int val = 0; int val = 0;
handler.serializeInt("morale", val, 0); handler.serializeInt("morale", val, 0);
if(val) if(val)
vinfo.reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(id)); vinfo.reward.heroBonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(id));
handler.serializeInt("luck", val, 0); handler.serializeInt("luck", val, 0);
if(val) if(val)
vinfo.reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(id)); vinfo.reward.heroBonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(id));
vinfo.reward.resources.serializeJson(handler, "resources"); vinfo.reward.resources.serializeJson(handler, "resources");
{ {
@@ -246,7 +246,7 @@ void CGPandoraBox::serializeJsonOptions(JsonSerializeFormat & handler)
} }
} }
handler.serializeIdArray("artifacts", vinfo.reward.artifacts); handler.serializeIdArray("artifacts", vinfo.reward.grantedArtifacts);
handler.serializeIdArray("spells", vinfo.reward.spells); handler.serializeIdArray("spells", vinfo.reward.spells);
handler.enterArray("creatures").serializeStruct(vinfo.reward.creatures); handler.enterArray("creatures").serializeStruct(vinfo.reward.creatures);
@@ -279,8 +279,8 @@ void CGPandoraBox::serializeJsonOptions(JsonSerializeFormat & handler)
|| vinfo.reward.heroExperience || vinfo.reward.heroExperience
|| vinfo.reward.manaDiff || vinfo.reward.manaDiff
|| vinfo.reward.resources.nonZero() || vinfo.reward.resources.nonZero()
|| !vinfo.reward.artifacts.empty() || !vinfo.reward.grantedArtifacts.empty()
|| !vinfo.reward.bonuses.empty() || !vinfo.reward.heroBonuses.empty()
|| !vinfo.reward.creatures.empty() || !vinfo.reward.creatures.empty()
|| !vinfo.reward.secondary.empty(); || !vinfo.reward.secondary.empty();

View File

@@ -714,9 +714,9 @@ void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler)
if(metaTypeName == "mana") if(metaTypeName == "mana")
reward.manaDiff = val; reward.manaDiff = val;
if(metaTypeName == "morale") if(metaTypeName == "morale")
reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(id)); reward.heroBonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(id));
if(metaTypeName == "luck") if(metaTypeName == "luck")
reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(id)); reward.heroBonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(id));
if(metaTypeName == "resource") if(metaTypeName == "resource")
{ {
auto rawId = *LIBRARY->identifiers()->getIdentifier(ModScope::scopeMap(), fullIdentifier, false); auto rawId = *LIBRARY->identifiers()->getIdentifier(ModScope::scopeMap(), fullIdentifier, false);
@@ -735,7 +735,7 @@ void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler)
if(metaTypeName == "artifact") if(metaTypeName == "artifact")
{ {
auto rawId = *LIBRARY->identifiers()->getIdentifier(ModScope::scopeMap(), fullIdentifier, false); auto rawId = *LIBRARY->identifiers()->getIdentifier(ModScope::scopeMap(), fullIdentifier, false);
reward.artifacts.push_back(rawId); reward.grantedArtifacts.push_back(rawId);
} }
if(metaTypeName == "spell") if(metaTypeName == "spell")
{ {

View File

@@ -72,6 +72,25 @@ TownRewardableBuildingInstance::TownRewardableBuildingInstance(CGTownInstance *
configuration = generateConfiguration(rand); configuration = generateConfiguration(rand);
} }
void TownRewardableBuildingInstance::assignBonuses(std::vector<Bonus> & bonuses) const
{
const auto & building = town->getTown()->buildings.at(getBuildingType());
for (auto & bonus : bonuses)
{
if (building->mapObjectLikeBonuses.hasValue())
{
bonus.source = BonusSource::OBJECT_TYPE;
bonus.sid = BonusSourceID(building->mapObjectLikeBonuses);
}
else
{
bonus.source = BonusSource::TOWN_STRUCTURE;
bonus.sid = BonusSourceID(building->getUniqueTypeID());
}
}
}
Rewardable::Configuration TownRewardableBuildingInstance::generateConfiguration(vstd::RNG & rand) const Rewardable::Configuration TownRewardableBuildingInstance::generateConfiguration(vstd::RNG & rand) const
{ {
Rewardable::Configuration result; Rewardable::Configuration result;
@@ -82,19 +101,8 @@ Rewardable::Configuration TownRewardableBuildingInstance::generateConfiguration(
building->rewardableObjectInfo.configureObject(result, rand, cb); building->rewardableObjectInfo.configureObject(result, rand, cb);
for(auto & rewardInfo : result.info) for(auto & rewardInfo : result.info)
{ {
for (auto & bonus : rewardInfo.reward.bonuses) assignBonuses(rewardInfo.reward.heroBonuses);
{ assignBonuses(rewardInfo.reward.playerBonuses);
if (building->mapObjectLikeBonuses.hasValue())
{
bonus.source = BonusSource::OBJECT_TYPE;
bonus.sid = BonusSourceID(building->mapObjectLikeBonuses);
}
else
{
bonus.source = BonusSource::TOWN_STRUCTURE;
bonus.sid = BonusSourceID(building->getUniqueTypeID());
}
}
} }
return result; return result;
} }

View File

@@ -58,6 +58,7 @@ class DLL_LINKAGE TownRewardableBuildingInstance : public TownBuildingInstance,
bool wasVisitedBefore(const CGHeroInstance * contextHero) const override; bool wasVisitedBefore(const CGHeroInstance * contextHero) const override;
void grantReward(ui32 rewardID, const CGHeroInstance * hero) const override; void grantReward(ui32 rewardID, const CGHeroInstance * hero) const override;
Rewardable::Configuration generateConfiguration(vstd::RNG & rand) const; Rewardable::Configuration generateConfiguration(vstd::RNG & rand) const;
void assignBonuses(std::vector<Bonus> & bonuses) const;
const IObjectInterface * getObject() const override; const IObjectInterface * getObject() const override;
bool wasVisited(PlayerColor player) const override; bool wasVisited(PlayerColor player) const override;

View File

@@ -1111,9 +1111,9 @@ void CMapLoaderH3M::readBoxContent(CGPandoraBox * object, const int3 & mapPositi
reward.heroExperience = reader->readUInt32(); reward.heroExperience = reader->readUInt32();
reward.manaDiff = reader->readInt32(); reward.manaDiff = reader->readInt32();
if(auto val = reader->readInt8Checked(-3, 3)) if(auto val = reader->readInt8Checked(-3, 3))
reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(idToBeGiven)); reward.heroBonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(idToBeGiven));
if(auto val = reader->readInt8Checked(-3, 3)) if(auto val = reader->readInt8Checked(-3, 3))
reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(idToBeGiven)); reward.heroBonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(idToBeGiven));
reader->readResources(reward.resources); reader->readResources(reward.resources);
for(int x = 0; x < GameConstants::PRIMARY_SKILLS; ++x) for(int x = 0; x < GameConstants::PRIMARY_SKILLS; ++x)
@@ -1130,11 +1130,11 @@ void CMapLoaderH3M::readBoxContent(CGPandoraBox * object, const int3 & mapPositi
size_t gart = reader->readUInt8(); //number of gained artifacts size_t gart = reader->readUInt8(); //number of gained artifacts
for(size_t oo = 0; oo < gart; ++oo) for(size_t oo = 0; oo < gart; ++oo)
{ {
reward.artifacts.push_back(reader->readArtifact()); reward.grantedArtifacts.push_back(reader->readArtifact());
if (features.levelHOTA5) if (features.levelHOTA5)
{ {
SpellID scrollSpell = reader->readSpell16(); SpellID scrollSpell = reader->readSpell16();
if (reward.artifacts.back() == ArtifactID::SPELL_SCROLL) if (reward.grantedArtifacts.back() == ArtifactID::SPELL_SCROLL)
logGlobal->warn("Map '%s': Pandora/Event at %s Option to give spell scroll (%s) via event or pandora is not implemented!", mapName, mapPosition.toString(), scrollSpell.toEntity(LIBRARY)->getJsonKey()); logGlobal->warn("Map '%s': Pandora/Event at %s Option to give spell scroll (%s) via event or pandora is not implemented!", mapName, mapPosition.toString(), scrollSpell.toEntity(LIBRARY)->getJsonKey());
} }
} }
@@ -2300,12 +2300,12 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, con
} }
case ESeerHutRewardType::MORALE: case ESeerHutRewardType::MORALE:
{ {
reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT_INSTANCE, reader->readInt8Checked(-3, 3), BonusSourceID(idToBeGiven)); reward.heroBonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT_INSTANCE, reader->readInt8Checked(-3, 3), BonusSourceID(idToBeGiven));
break; break;
} }
case ESeerHutRewardType::LUCK: case ESeerHutRewardType::LUCK:
{ {
reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT_INSTANCE, reader->readInt8Checked(-3, 3), BonusSourceID(idToBeGiven)); reward.heroBonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT_INSTANCE, reader->readInt8Checked(-3, 3), BonusSourceID(idToBeGiven));
break; break;
} }
case ESeerHutRewardType::RESOURCES: case ESeerHutRewardType::RESOURCES:
@@ -2334,11 +2334,11 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, con
} }
case ESeerHutRewardType::ARTIFACT: case ESeerHutRewardType::ARTIFACT:
{ {
reward.artifacts.push_back(reader->readArtifact()); reward.grantedArtifacts.push_back(reader->readArtifact());
if (features.levelHOTA5) if (features.levelHOTA5)
{ {
SpellID scrollSpell = reader->readSpell16(); SpellID scrollSpell = reader->readSpell16();
if (reward.artifacts.back() == ArtifactID::SPELL_SCROLL) if (reward.grantedArtifacts.back() == ArtifactID::SPELL_SCROLL)
logGlobal->warn("Map '%s': Seer Hut at %s: Option to give spell scroll (%s) as a reward is not implemented!", mapName, position.toString(), scrollSpell.toEntity(LIBRARY)->getJsonKey()); logGlobal->warn("Map '%s': Seer Hut at %s: Option to give spell scroll (%s) as a reward is not implemented!", mapName, position.toString(), scrollSpell.toEntity(LIBRARY)->getJsonKey());
} }

View File

@@ -408,6 +408,7 @@ struct DLL_LINKAGE SetAvailableHero : public CPackForClient
struct DLL_LINKAGE GiveBonus : public CPackForClient struct DLL_LINKAGE GiveBonus : public CPackForClient
{ {
using VariantType = VariantIdentifier<ObjectInstanceID, PlayerColor, BattleID>;
enum class ETarget : int8_t { OBJECT, PLAYER, BATTLE }; enum class ETarget : int8_t { OBJECT, PLAYER, BATTLE };
explicit GiveBonus(ETarget Who = ETarget::OBJECT) explicit GiveBonus(ETarget Who = ETarget::OBJECT)
@@ -415,10 +416,17 @@ struct DLL_LINKAGE GiveBonus : public CPackForClient
{ {
} }
GiveBonus(ETarget who, const VariantType & id, const Bonus & bonus)
: who(who)
, id(id)
, bonus(bonus)
{
}
void applyGs(CGameState * gs) override; void applyGs(CGameState * gs) override;
ETarget who = ETarget::OBJECT; ETarget who = ETarget::OBJECT;
VariantIdentifier<ObjectInstanceID, PlayerColor, BattleID> id; VariantType id;
Bonus bonus; Bonus bonus;
void visitTyped(ICPackVisitor & visitor) override; void visitTyped(ICPackVisitor & visitor) override;

View File

@@ -29,7 +29,8 @@ enum EVisitMode
VISIT_HERO, // every hero can visit object once VISIT_HERO, // every hero can visit object once
VISIT_BONUS, // can be visited by any hero that don't have bonus from this object VISIT_BONUS, // can be visited by any hero that don't have bonus from this object
VISIT_LIMITER, // can be visited by heroes that don't fulfill provided limiter VISIT_LIMITER, // can be visited by heroes that don't fulfill provided limiter
VISIT_PLAYER // every player can visit object once VISIT_PLAYER, // every player can visit object once
VISIT_PLAYER_GLOBAL // every player can visit object once. All objects of the same type will be considered as visited
}; };
/// controls selection of reward granted to player /// controls selection of reward granted to player
@@ -70,6 +71,9 @@ struct DLL_LINKAGE ResetInfo
/// if true - re-randomize rewards on a new week /// if true - re-randomize rewards on a new week
bool rewards; bool rewards;
/// Reset object after visit by a hero, whether hero accepted reward or not
bool resetAfterVisit = false;
void serializeJson(JsonSerializeFormat & handler); void serializeJson(JsonSerializeFormat & handler);
template <typename Handler> void serialize(Handler &h) template <typename Handler> void serialize(Handler &h)
@@ -77,6 +81,8 @@ struct DLL_LINKAGE ResetInfo
h & period; h & period;
h & visitors; h & visitors;
h & rewards; h & rewards;
if (h.version >= Handler::Version::REWARDABLE_EXTENSIONS)
h & resetAfterVisit;
} }
}; };

View File

@@ -174,14 +174,15 @@ void Rewardable::Info::configureReward(Rewardable::Configuration & object, vstd:
reward.movePercentage = randomizer.loadValue(source["movePercentage"], rng, variables, -1); reward.movePercentage = randomizer.loadValue(source["movePercentage"], rng, variables, -1);
reward.removeObject = source["removeObject"].Bool(); reward.removeObject = source["removeObject"].Bool();
reward.bonuses = randomizer.loadBonuses(source["bonuses"]); reward.heroBonuses = randomizer.loadBonuses(source["bonuses"]);
reward.playerBonuses = randomizer.loadBonuses(source["playerBonuses"]);
reward.guards = randomizer.loadCreatures(source["guards"], rng, variables); reward.guards = randomizer.loadCreatures(source["guards"], rng, variables);
reward.primary = randomizer.loadPrimaries(source["primary"], rng, variables); reward.primary = randomizer.loadPrimaries(source["primary"], rng, variables);
reward.secondary = randomizer.loadSecondaries(source["secondary"], rng, variables); reward.secondary = randomizer.loadSecondaries(source["secondary"], rng, variables);
reward.artifacts = randomizer.loadArtifacts(source["artifacts"], rng, variables); reward.grantedArtifacts = randomizer.loadArtifacts(source["artifacts"], rng, variables);
reward.spells = randomizer.loadSpells(source["spells"], rng, variables); reward.spells = randomizer.loadSpells(source["spells"], rng, variables);
reward.creatures = randomizer.loadCreatures(source["creatures"], rng, variables); reward.creatures = randomizer.loadCreatures(source["creatures"], rng, variables);
if(!source["spellCast"].isNull() && source["spellCast"].isStruct()) if(!source["spellCast"].isNull() && source["spellCast"].isStruct())
@@ -293,7 +294,7 @@ void Rewardable::Info::replaceTextPlaceholders(MetaString & target, const Variab
} }
} }
for (const auto & artifact : info.reward.artifacts ) for (const auto & artifact : info.reward.grantedArtifacts )
{ {
loot.appendRawString("%s"); loot.appendRawString("%s");
loot.replaceName(artifact); loot.replaceName(artifact);
@@ -315,7 +316,7 @@ void Rewardable::Info::replaceTextPlaceholders(MetaString & target, const Variab
} }
else else
{ {
for (const auto & artifact : info.reward.artifacts ) for (const auto & artifact : info.reward.grantedArtifacts )
target.replaceName(artifact); target.replaceName(artifact);
for (const auto & spell : info.reward.spells ) for (const auto & spell : info.reward.spells )

View File

@@ -151,16 +151,19 @@ void Rewardable::Interface::grantRewardAfterLevelup(const Rewardable::VisitInfo
cb->setMovePoints(&smp); cb->setMovePoints(&smp);
} }
for(const Bonus & bonus : info.reward.bonuses) for(const Bonus & bonus : info.reward.heroBonuses)
{ {
GiveBonus gb; GiveBonus gb(GiveBonus::ETarget::OBJECT, hero->id, bonus);
gb.who = GiveBonus::ETarget::OBJECT;
gb.bonus = bonus;
gb.id = hero->id;
cb->giveHeroBonus(&gb); cb->giveHeroBonus(&gb);
} }
for(const ArtifactID & art : info.reward.artifacts) for(const Bonus & bonus : info.reward.playerBonuses)
{
GiveBonus gb(GiveBonus::ETarget::PLAYER, hero->getOwner(), bonus);
cb->giveHeroBonus(&gb);
}
for(const ArtifactID & art : info.reward.grantedArtifacts)
cb->giveHeroNewArtifact(hero, art, ArtifactPosition::FIRST_AVAILABLE); cb->giveHeroNewArtifact(hero, art, ArtifactPosition::FIRST_AVAILABLE);
if(!info.reward.spells.empty()) if(!info.reward.spells.empty())

View File

@@ -48,6 +48,9 @@ struct DLL_LINKAGE Limiter final : public Serializeable
/// Number of free secondary slots that hero needs to have /// Number of free secondary slots that hero needs to have
bool canLearnSkills; bool canLearnSkills;
/// Hero has commander, and commander is currently alive
bool commanderAlive;
/// resources player needs to have in order to trigger reward /// resources player needs to have in order to trigger reward
TResources resources; TResources resources;
@@ -59,6 +62,12 @@ struct DLL_LINKAGE Limiter final : public Serializeable
/// checks for artifacts copies if same artifact id is included multiple times /// checks for artifacts copies if same artifact id is included multiple times
std::vector<ArtifactID> artifacts; std::vector<ArtifactID> artifacts;
/// artifact slots that hero needs to have available (not locked and without any artifact) to pass the limiter
std::vector<ArtifactPosition> availableSlots;
/// Spell scrolls that hero must have in inventory (equipped or in backpack)
std::vector<SpellID> scrolls;
/// Spells that hero must have in the spellbook /// Spells that hero must have in the spellbook
std::vector<SpellID> spells; std::vector<SpellID> spells;
@@ -102,10 +111,17 @@ struct DLL_LINKAGE Limiter final : public Serializeable
h & manaPoints; h & manaPoints;
h & manaPercentage; h & manaPercentage;
h & canLearnSkills; h & canLearnSkills;
if (h.version >= Handler::Version::REWARDABLE_EXTENSIONS)
h & commanderAlive;
h & resources; h & resources;
h & primary; h & primary;
h & secondary; h & secondary;
h & artifacts; h & artifacts;
if (h.version >= Handler::Version::REWARDABLE_EXTENSIONS)
{
h & availableSlots;
h & scrolls;
}
h & spells; h & spells;
h & canLearnSpells; h & canLearnSpells;
h & creatures; h & creatures;

View File

@@ -79,7 +79,7 @@ void Rewardable::Reward::loadComponents(std::vector<Component> & comps, const CG
for (auto comp : extraComponents) for (auto comp : extraComponents)
comps.push_back(comp); comps.push_back(comp);
for (auto & bonus : bonuses) for (auto & bonus : heroBonuses)
{ {
if (bonus.type == BonusType::MORALE) if (bonus.type == BonusType::MORALE)
comps.emplace_back(ComponentType::MORALE, bonus.val); comps.emplace_back(ComponentType::MORALE, bonus.val);
@@ -111,7 +111,7 @@ void Rewardable::Reward::loadComponents(std::vector<Component> & comps, const CG
comps.emplace_back(ComponentType::SEC_SKILL, entry.first, finalLevel); comps.emplace_back(ComponentType::SEC_SKILL, entry.first, finalLevel);
} }
for(const auto & entry : artifacts) for(const auto & entry : grantedArtifacts)
comps.emplace_back(ComponentType::ARTIFACT, entry); comps.emplace_back(ComponentType::ARTIFACT, entry);
for(const auto & entry : spells) for(const auto & entry : spells)
@@ -141,7 +141,7 @@ void Rewardable::Reward::serializeJson(JsonSerializeFormat & handler)
handler.serializeInt("manaDiff", manaDiff); handler.serializeInt("manaDiff", manaDiff);
handler.serializeInt("manaOverflowFactor", manaOverflowFactor); handler.serializeInt("manaOverflowFactor", manaOverflowFactor);
handler.serializeInt("movePoints", movePoints); handler.serializeInt("movePoints", movePoints);
handler.serializeIdArray("artifacts", artifacts); handler.serializeIdArray("artifacts", grantedArtifacts);
handler.serializeIdArray("spells", spells); handler.serializeIdArray("spells", spells);
handler.enterArray("creatures").serializeStruct(creatures); handler.enterArray("creatures").serializeStruct(creatures);
handler.enterArray("primary").serializeArray(primary); handler.enterArray("primary").serializeArray(primary);

View File

@@ -65,6 +65,8 @@ struct DLL_LINKAGE Reward final
/// received experience /// received experience
si32 heroExperience; si32 heroExperience;
si32 commanderExperience;
si32 unitsExperience;
/// received levels (converted into XP during grant) /// received levels (converted into XP during grant)
si32 heroLevel; si32 heroLevel;
@@ -86,7 +88,8 @@ struct DLL_LINKAGE Reward final
std::vector<CStackBasicDescriptor> guards; std::vector<CStackBasicDescriptor> guards;
/// list of bonuses, e.g. morale/luck /// list of bonuses, e.g. morale/luck
std::vector<Bonus> bonuses; std::vector<Bonus> heroBonuses;
std::vector<Bonus> playerBonuses;
/// skills that hero may receive or lose /// skills that hero may receive or lose
std::vector<si32> primary; std::vector<si32> primary;
@@ -96,7 +99,10 @@ struct DLL_LINKAGE Reward final
std::map<CreatureID, CreatureID> creaturesChange; std::map<CreatureID, CreatureID> creaturesChange;
/// objects that hero may receive /// objects that hero may receive
std::vector<ArtifactID> artifacts; std::vector<ArtifactID> grantedArtifacts;
std::vector<ArtifactID> takenArtifacts;
std::vector<ArtifactPosition> takenArtifactSlots;
std::vector<SpellID> scrolls;
std::vector<SpellID> spells; std::vector<SpellID> spells;
std::vector<CStackBasicDescriptor> creatures; std::vector<CStackBasicDescriptor> creatures;
@@ -131,14 +137,29 @@ struct DLL_LINKAGE Reward final
h & movePercentage; h & movePercentage;
h & guards; h & guards;
h & heroExperience; h & heroExperience;
if (h.version >= Handler::Version::REWARDABLE_EXTENSIONS)
{
h & commanderExperience;
h & unitsExperience;
}
h & heroLevel; h & heroLevel;
h & manaDiff; h & manaDiff;
h & manaOverflowFactor; h & manaOverflowFactor;
h & movePoints; h & movePoints;
h & primary; h & primary;
h & secondary; h & secondary;
h & bonuses; h & heroBonuses;
h & artifacts; if (h.version >= Handler::Version::REWARDABLE_EXTENSIONS)
{
h & playerBonuses;
}
h & grantedArtifacts;
if (h.version >= Handler::Version::REWARDABLE_EXTENSIONS)
{
h & takenArtifacts;
h & takenArtifactSlots;
h & scrolls;
}
h & spells; h & spells;
h & creatures; h & creatures;
h & creaturesChange; h & creaturesChange;

View File

@@ -39,9 +39,9 @@ enum class ESerializationVersion : int32_t
STACK_INSTANCE_EXPERIENCE_FIX, // stack experience is stored as total, not as average STACK_INSTANCE_EXPERIENCE_FIX, // stack experience is stored as total, not as average
STACK_INSTANCE_ARMY_FIX, // remove serialization of army that owns stack instance STACK_INSTANCE_ARMY_FIX, // remove serialization of army that owns stack instance
STORE_UID_COUNTER_IN_CMAP, // fix crash caused by conflicting instanceName after loading game STORE_UID_COUNTER_IN_CMAP, // fix crash caused by conflicting instanceName after loading game
REWARDABLE_EXTENSIONS, // new functionality for rewardable objects
CURRENT = REWARDABLE_EXTENSIONS,
CURRENT = STORE_UID_COUNTER_IN_CMAP,
}; };
static_assert(ESerializationVersion::MINIMAL <= ESerializationVersion::CURRENT, "Invalid serialization version definition!"); static_assert(ESerializationVersion::MINIMAL <= ESerializationVersion::CURRENT, "Invalid serialization version definition!");

View File

@@ -297,11 +297,11 @@ void RewardsWidget::saveCurrentVisitInfo(int index)
vinfo.reward.resources[i] = widget->value(); vinfo.reward.resources[i] = widget->value();
} }
vinfo.reward.artifacts.clear(); vinfo.reward.grantedArtifacts.clear();
for(int i = 0; i < ui->rArtifacts->count(); ++i) for(int i = 0; i < ui->rArtifacts->count(); ++i)
{ {
if(ui->rArtifacts->item(i)->checkState() == Qt::Checked) if(ui->rArtifacts->item(i)->checkState() == Qt::Checked)
vinfo.reward.artifacts.push_back(LIBRARY->artifacts()->getByIndex(i)->getId()); vinfo.reward.grantedArtifacts.push_back(LIBRARY->artifacts()->getByIndex(i)->getId());
} }
vinfo.reward.spells.clear(); vinfo.reward.spells.clear();
for(int i = 0; i < ui->rSpells->count(); ++i) for(int i = 0; i < ui->rSpells->count(); ++i)
@@ -336,13 +336,13 @@ void RewardsWidget::saveCurrentVisitInfo(int index)
vinfo.reward.spellCast.second = ui->castLevel->currentIndex(); vinfo.reward.spellCast.second = ui->castLevel->currentIndex();
} }
vinfo.reward.bonuses.clear(); vinfo.reward.heroBonuses.clear();
for(int i = 0; i < ui->bonuses->rowCount(); ++i) for(int i = 0; i < ui->bonuses->rowCount(); ++i)
{ {
auto dur = bonusDurationMap.at(ui->bonuses->item(i, 0)->text().toStdString()); auto dur = bonusDurationMap.at(ui->bonuses->item(i, 0)->text().toStdString());
auto typ = bonusNameMap.at(ui->bonuses->item(i, 1)->text().toStdString()); auto typ = bonusNameMap.at(ui->bonuses->item(i, 1)->text().toStdString());
auto val = ui->bonuses->item(i, 2)->data(Qt::UserRole).toInt(); auto val = ui->bonuses->item(i, 2)->data(Qt::UserRole).toInt();
vinfo.reward.bonuses.emplace_back(dur, typ, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(object.id)); vinfo.reward.heroBonuses.emplace_back(dur, typ, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(object.id));
} }
vinfo.limiter.dayOfWeek = ui->lDayOfWeek->currentIndex(); vinfo.limiter.dayOfWeek = ui->lDayOfWeek->currentIndex();
@@ -452,7 +452,7 @@ void RewardsWidget::loadCurrentVisitInfo(int index)
widget->setValue(vinfo.reward.resources[i]); widget->setValue(vinfo.reward.resources[i]);
} }
for(auto i : vinfo.reward.artifacts) for(auto i : vinfo.reward.grantedArtifacts)
ui->rArtifacts->item(LIBRARY->artifacts()->getById(i)->getIndex())->setCheckState(Qt::Checked); ui->rArtifacts->item(LIBRARY->artifacts()->getById(i)->getIndex())->setCheckState(Qt::Checked);
for(auto i : vinfo.reward.spells) for(auto i : vinfo.reward.spells)
ui->rArtifacts->item(LIBRARY->spells()->getById(i)->getIndex())->setCheckState(Qt::Checked); ui->rArtifacts->item(LIBRARY->spells()->getById(i)->getIndex())->setCheckState(Qt::Checked);
@@ -478,7 +478,7 @@ void RewardsWidget::loadCurrentVisitInfo(int index)
ui->castLevel->setCurrentIndex(vinfo.reward.spellCast.second); ui->castLevel->setCurrentIndex(vinfo.reward.spellCast.second);
} }
for(auto & i : vinfo.reward.bonuses) for(auto & i : vinfo.reward.heroBonuses)
{ {
auto dur = vstd::findKey(bonusDurationMap, i.duration); auto dur = vstd::findKey(bonusDurationMap, i.duration);
for(int i = 0; i < ui->bonusDuration->count(); ++i) for(int i = 0; i < ui->bonusDuration->count(); ++i)
@@ -786,7 +786,7 @@ void RewardsDelegate::updateModelData(QAbstractItemModel * model, const QModelIn
} }
textList += QObject::tr("Resources: %1").arg(resourcesList.join(", ")); textList += QObject::tr("Resources: %1").arg(resourcesList.join(", "));
QStringList artifactsList; QStringList artifactsList;
for (auto artifact : vinfo.reward.artifacts) for (auto artifact : vinfo.reward.grantedArtifacts)
{ {
artifactsList += QString::fromStdString(LIBRARY->artifacts()->getById(artifact)->getNameTranslated()); artifactsList += QString::fromStdString(LIBRARY->artifacts()->getById(artifact)->getNameTranslated());
} }
@@ -814,7 +814,7 @@ void RewardsDelegate::updateModelData(QAbstractItemModel * model, const QModelIn
textList += QObject::tr("Spell Cast: %1 (%2)").arg(QString::fromStdString(LIBRARY->spells()->getById(vinfo.reward.spellCast.first)->getNameTranslated())).arg(vinfo.reward.spellCast.second); textList += QObject::tr("Spell Cast: %1 (%2)").arg(QString::fromStdString(LIBRARY->spells()->getById(vinfo.reward.spellCast.first)->getNameTranslated())).arg(vinfo.reward.spellCast.second);
} }
QStringList bonusesList; QStringList bonusesList;
for (auto & bonus : vinfo.reward.bonuses) for (auto & bonus : vinfo.reward.heroBonuses)
{ {
bonusesList += QString("%1 %2 (%3)").arg(QString::fromStdString(vstd::findKey(bonusDurationMap, bonus.duration))).arg(QString::fromStdString(vstd::findKey(bonusNameMap, bonus.type))).arg(bonus.val); bonusesList += QString("%1 %2 (%3)").arg(QString::fromStdString(vstd::findKey(bonusDurationMap, bonus.duration))).arg(QString::fromStdString(vstd::findKey(bonusNameMap, bonus.type))).arg(bonus.val);
} }