diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 991c58b50..91c72bca9 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -265,9 +265,9 @@ uint64_t RewardEvaluator::getArmyReward( 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(); @@ -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; diff --git a/lib/mapObjectConstructors/CRewardableConstructor.cpp b/lib/mapObjectConstructors/CRewardableConstructor.cpp index afeebb631..852af6470 100644 --- a/lib/mapObjectConstructors/CRewardableConstructor.cpp +++ b/lib/mapObjectConstructors/CRewardableConstructor.cpp @@ -47,6 +47,15 @@ std::shared_ptr CRewardableConstructor::create(IGameCallback * return ret; } +void CRewardableConstructor::assignBonuses(std::vector & 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 & presetVariables) const { Rewardable::Configuration result; @@ -62,11 +71,8 @@ Rewardable::Configuration CRewardableConstructor::generateConfiguration(IGameCal for(auto & rewardInfo : result.info) { - for (auto & bonus : rewardInfo.reward.bonuses) - { - bonus.source = BonusSource::OBJECT_TYPE; - bonus.sid = BonusSourceID(objectID); - } + assignBonuses(rewardInfo.reward.heroBonuses, objectID); + assignBonuses(rewardInfo.reward.playerBonuses, objectID); } return result; diff --git a/lib/mapObjectConstructors/CRewardableConstructor.h b/lib/mapObjectConstructors/CRewardableConstructor.h index 88d1b2855..b363c423b 100644 --- a/lib/mapObjectConstructors/CRewardableConstructor.h +++ b/lib/mapObjectConstructors/CRewardableConstructor.h @@ -14,10 +14,13 @@ VCMI_LIB_NAMESPACE_BEGIN +struct Bonus; + class DLL_LINKAGE CRewardableConstructor : public AObjectTypeHandler { Rewardable::Info objectInfo; + void assignBonuses(std::vector & bonuses, MapObjectID objectID) const; void initTypeData(const JsonNode & config) override; bool blockVisit = false; diff --git a/lib/mapObjects/CGPandoraBox.cpp b/lib/mapObjects/CGPandoraBox.cpp index 6696134c6..ca934c7de 100644 --- a/lib/mapObjects/CGPandoraBox.cpp +++ b/lib/mapObjects/CGPandoraBox.cpp @@ -83,7 +83,7 @@ void CGPandoraBox::grantRewardWithMessage(const CGHeroInstance * h, int index, b temp.heroLevel = vi.reward.heroLevel; temp.primary = vi.reward.primary; temp.secondary = vi.reward.secondary; - temp.bonuses = vi.reward.bonuses; + temp.heroBonuses = vi.reward.heroBonuses; temp.manaDiff = vi.reward.manaDiff; 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) 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) txt = setText(b.val > 0, 179, 178, h); @@ -122,7 +122,7 @@ void CGPandoraBox::grantRewardWithMessage(const CGHeroInstance * h, int index, b //artifacts message temp = Rewardable::Reward{}; - temp.artifacts = vi.reward.artifacts; + temp.grantedArtifacts = vi.reward.grantedArtifacts; sendInfoWindow(setText(true, 183, 183, h), temp); //creatures message @@ -160,8 +160,8 @@ void CGPandoraBox::grantRewardWithMessage(const CGHeroInstance * h, int index, b temp.manaPercentage = -1; temp.spells.clear(); temp.creatures.clear(); - temp.bonuses.clear(); - temp.artifacts.clear(); + temp.heroBonuses.clear(); + temp.grantedArtifacts.clear(); sendInfoWindow(setText(true, 175, 175, h), temp); // grant reward afterwards. Note that it may remove object @@ -229,11 +229,11 @@ void CGPandoraBox::serializeJsonOptions(JsonSerializeFormat & handler) int val = 0; handler.serializeInt("morale", val, 0); 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); 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"); { @@ -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.enterArray("creatures").serializeStruct(vinfo.reward.creatures); @@ -279,8 +279,8 @@ void CGPandoraBox::serializeJsonOptions(JsonSerializeFormat & handler) || vinfo.reward.heroExperience || vinfo.reward.manaDiff || vinfo.reward.resources.nonZero() - || !vinfo.reward.artifacts.empty() - || !vinfo.reward.bonuses.empty() + || !vinfo.reward.grantedArtifacts.empty() + || !vinfo.reward.heroBonuses.empty() || !vinfo.reward.creatures.empty() || !vinfo.reward.secondary.empty(); diff --git a/lib/mapObjects/CQuest.cpp b/lib/mapObjects/CQuest.cpp index 5ee72fa44..b3aeea0d6 100644 --- a/lib/mapObjects/CQuest.cpp +++ b/lib/mapObjects/CQuest.cpp @@ -714,9 +714,9 @@ void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler) if(metaTypeName == "mana") reward.manaDiff = val; 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") - 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") { auto rawId = *LIBRARY->identifiers()->getIdentifier(ModScope::scopeMap(), fullIdentifier, false); @@ -735,7 +735,7 @@ void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler) if(metaTypeName == "artifact") { auto rawId = *LIBRARY->identifiers()->getIdentifier(ModScope::scopeMap(), fullIdentifier, false); - reward.artifacts.push_back(rawId); + reward.grantedArtifacts.push_back(rawId); } if(metaTypeName == "spell") { diff --git a/lib/mapObjects/TownBuildingInstance.cpp b/lib/mapObjects/TownBuildingInstance.cpp index 01822cb1a..ba5865b59 100644 --- a/lib/mapObjects/TownBuildingInstance.cpp +++ b/lib/mapObjects/TownBuildingInstance.cpp @@ -72,6 +72,25 @@ TownRewardableBuildingInstance::TownRewardableBuildingInstance(CGTownInstance * configuration = generateConfiguration(rand); } +void TownRewardableBuildingInstance::assignBonuses(std::vector & 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 result; @@ -82,19 +101,8 @@ Rewardable::Configuration TownRewardableBuildingInstance::generateConfiguration( building->rewardableObjectInfo.configureObject(result, rand, cb); for(auto & rewardInfo : result.info) { - for (auto & bonus : rewardInfo.reward.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()); - } - } + assignBonuses(rewardInfo.reward.heroBonuses); + assignBonuses(rewardInfo.reward.playerBonuses); } return result; } diff --git a/lib/mapObjects/TownBuildingInstance.h b/lib/mapObjects/TownBuildingInstance.h index eace6f9b1..2cb18e70c 100644 --- a/lib/mapObjects/TownBuildingInstance.h +++ b/lib/mapObjects/TownBuildingInstance.h @@ -58,6 +58,7 @@ class DLL_LINKAGE TownRewardableBuildingInstance : public TownBuildingInstance, bool wasVisitedBefore(const CGHeroInstance * contextHero) const override; void grantReward(ui32 rewardID, const CGHeroInstance * hero) const override; Rewardable::Configuration generateConfiguration(vstd::RNG & rand) const; + void assignBonuses(std::vector & bonuses) const; const IObjectInterface * getObject() const override; bool wasVisited(PlayerColor player) const override; diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 575bcab13..4650e9d4f 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -1111,9 +1111,9 @@ void CMapLoaderH3M::readBoxContent(CGPandoraBox * object, const int3 & mapPositi reward.heroExperience = reader->readUInt32(); reward.manaDiff = reader->readInt32(); 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)) - 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); 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 for(size_t oo = 0; oo < gart; ++oo) { - reward.artifacts.push_back(reader->readArtifact()); + reward.grantedArtifacts.push_back(reader->readArtifact()); if (features.levelHOTA5) { 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()); } } @@ -2300,12 +2300,12 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, con } 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; } 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; } case ESeerHutRewardType::RESOURCES: @@ -2334,11 +2334,11 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, con } case ESeerHutRewardType::ARTIFACT: { - reward.artifacts.push_back(reader->readArtifact()); + reward.grantedArtifacts.push_back(reader->readArtifact()); if (features.levelHOTA5) { 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()); } diff --git a/lib/networkPacks/PacksForClient.h b/lib/networkPacks/PacksForClient.h index 8b38a30f5..57a4bcbc2 100644 --- a/lib/networkPacks/PacksForClient.h +++ b/lib/networkPacks/PacksForClient.h @@ -408,6 +408,7 @@ struct DLL_LINKAGE SetAvailableHero : public CPackForClient struct DLL_LINKAGE GiveBonus : public CPackForClient { + using VariantType = VariantIdentifier; enum class ETarget : int8_t { OBJECT, PLAYER, BATTLE }; 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; ETarget who = ETarget::OBJECT; - VariantIdentifier id; + VariantType id; Bonus bonus; void visitTyped(ICPackVisitor & visitor) override; diff --git a/lib/rewardable/Configuration.h b/lib/rewardable/Configuration.h index 902746916..caedf9132 100644 --- a/lib/rewardable/Configuration.h +++ b/lib/rewardable/Configuration.h @@ -29,7 +29,8 @@ enum EVisitMode 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_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 @@ -70,6 +71,9 @@ struct DLL_LINKAGE ResetInfo /// if true - re-randomize rewards on a new week bool rewards; + /// Reset object after visit by a hero, whether hero accepted reward or not + bool resetAfterVisit = false; + void serializeJson(JsonSerializeFormat & handler); template void serialize(Handler &h) @@ -77,6 +81,8 @@ struct DLL_LINKAGE ResetInfo h & period; h & visitors; h & rewards; + if (h.version >= Handler::Version::REWARDABLE_EXTENSIONS) + h & resetAfterVisit; } }; diff --git a/lib/rewardable/Info.cpp b/lib/rewardable/Info.cpp index 20b0acb2e..07a12c30f 100644 --- a/lib/rewardable/Info.cpp +++ b/lib/rewardable/Info.cpp @@ -174,14 +174,15 @@ void Rewardable::Info::configureReward(Rewardable::Configuration & object, vstd: reward.movePercentage = randomizer.loadValue(source["movePercentage"], rng, variables, -1); 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.primary = randomizer.loadPrimaries(source["primary"], 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.creatures = randomizer.loadCreatures(source["creatures"], rng, variables); 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.replaceName(artifact); @@ -315,7 +316,7 @@ void Rewardable::Info::replaceTextPlaceholders(MetaString & target, const Variab } else { - for (const auto & artifact : info.reward.artifacts ) + for (const auto & artifact : info.reward.grantedArtifacts ) target.replaceName(artifact); for (const auto & spell : info.reward.spells ) diff --git a/lib/rewardable/Interface.cpp b/lib/rewardable/Interface.cpp index c7eef6dc1..824b9d923 100644 --- a/lib/rewardable/Interface.cpp +++ b/lib/rewardable/Interface.cpp @@ -151,16 +151,19 @@ void Rewardable::Interface::grantRewardAfterLevelup(const Rewardable::VisitInfo cb->setMovePoints(&smp); } - for(const Bonus & bonus : info.reward.bonuses) + for(const Bonus & bonus : info.reward.heroBonuses) { - GiveBonus gb; - gb.who = GiveBonus::ETarget::OBJECT; - gb.bonus = bonus; - gb.id = hero->id; + GiveBonus gb(GiveBonus::ETarget::OBJECT, hero->id, bonus); 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); if(!info.reward.spells.empty()) diff --git a/lib/rewardable/Limiter.h b/lib/rewardable/Limiter.h index e95e6459a..0b99a9116 100644 --- a/lib/rewardable/Limiter.h +++ b/lib/rewardable/Limiter.h @@ -48,6 +48,9 @@ struct DLL_LINKAGE Limiter final : public Serializeable /// Number of free secondary slots that hero needs to have bool canLearnSkills; + /// Hero has commander, and commander is currently alive + bool commanderAlive; + /// resources player needs to have in order to trigger reward 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 std::vector artifacts; + /// artifact slots that hero needs to have available (not locked and without any artifact) to pass the limiter + std::vector availableSlots; + + /// Spell scrolls that hero must have in inventory (equipped or in backpack) + std::vector scrolls; + /// Spells that hero must have in the spellbook std::vector spells; @@ -102,10 +111,17 @@ struct DLL_LINKAGE Limiter final : public Serializeable h & manaPoints; h & manaPercentage; h & canLearnSkills; + if (h.version >= Handler::Version::REWARDABLE_EXTENSIONS) + h & commanderAlive; h & resources; h & primary; h & secondary; h & artifacts; + if (h.version >= Handler::Version::REWARDABLE_EXTENSIONS) + { + h & availableSlots; + h & scrolls; + } h & spells; h & canLearnSpells; h & creatures; diff --git a/lib/rewardable/Reward.cpp b/lib/rewardable/Reward.cpp index 62541a8ba..c1758f1fc 100644 --- a/lib/rewardable/Reward.cpp +++ b/lib/rewardable/Reward.cpp @@ -79,7 +79,7 @@ void Rewardable::Reward::loadComponents(std::vector & comps, const CG for (auto comp : extraComponents) comps.push_back(comp); - for (auto & bonus : bonuses) + for (auto & bonus : heroBonuses) { if (bonus.type == BonusType::MORALE) comps.emplace_back(ComponentType::MORALE, bonus.val); @@ -111,7 +111,7 @@ void Rewardable::Reward::loadComponents(std::vector & comps, const CG 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); for(const auto & entry : spells) @@ -141,7 +141,7 @@ void Rewardable::Reward::serializeJson(JsonSerializeFormat & handler) handler.serializeInt("manaDiff", manaDiff); handler.serializeInt("manaOverflowFactor", manaOverflowFactor); handler.serializeInt("movePoints", movePoints); - handler.serializeIdArray("artifacts", artifacts); + handler.serializeIdArray("artifacts", grantedArtifacts); handler.serializeIdArray("spells", spells); handler.enterArray("creatures").serializeStruct(creatures); handler.enterArray("primary").serializeArray(primary); diff --git a/lib/rewardable/Reward.h b/lib/rewardable/Reward.h index 0dd46d189..f09d42c55 100644 --- a/lib/rewardable/Reward.h +++ b/lib/rewardable/Reward.h @@ -65,6 +65,8 @@ struct DLL_LINKAGE Reward final /// received experience si32 heroExperience; + si32 commanderExperience; + si32 unitsExperience; /// received levels (converted into XP during grant) si32 heroLevel; @@ -86,7 +88,8 @@ struct DLL_LINKAGE Reward final std::vector guards; /// list of bonuses, e.g. morale/luck - std::vector bonuses; + std::vector heroBonuses; + std::vector playerBonuses; /// skills that hero may receive or lose std::vector primary; @@ -96,7 +99,10 @@ struct DLL_LINKAGE Reward final std::map creaturesChange; /// objects that hero may receive - std::vector artifacts; + std::vector grantedArtifacts; + std::vector takenArtifacts; + std::vector takenArtifactSlots; + std::vector scrolls; std::vector spells; std::vector creatures; @@ -131,14 +137,29 @@ struct DLL_LINKAGE Reward final h & movePercentage; h & guards; h & heroExperience; + if (h.version >= Handler::Version::REWARDABLE_EXTENSIONS) + { + h & commanderExperience; + h & unitsExperience; + } h & heroLevel; h & manaDiff; h & manaOverflowFactor; h & movePoints; h & primary; h & secondary; - h & bonuses; - h & artifacts; + h & heroBonuses; + 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 & creatures; h & creaturesChange; diff --git a/lib/serializer/ESerializationVersion.h b/lib/serializer/ESerializationVersion.h index 9d36ea3ae..7124a92ff 100644 --- a/lib/serializer/ESerializationVersion.h +++ b/lib/serializer/ESerializationVersion.h @@ -39,9 +39,9 @@ enum class ESerializationVersion : int32_t 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 STORE_UID_COUNTER_IN_CMAP, // fix crash caused by conflicting instanceName after loading game + REWARDABLE_EXTENSIONS, // new functionality for rewardable objects - - CURRENT = STORE_UID_COUNTER_IN_CMAP, + CURRENT = REWARDABLE_EXTENSIONS, }; static_assert(ESerializationVersion::MINIMAL <= ESerializationVersion::CURRENT, "Invalid serialization version definition!"); diff --git a/mapeditor/inspector/rewardswidget.cpp b/mapeditor/inspector/rewardswidget.cpp index c537c33bd..00d279019 100644 --- a/mapeditor/inspector/rewardswidget.cpp +++ b/mapeditor/inspector/rewardswidget.cpp @@ -297,11 +297,11 @@ void RewardsWidget::saveCurrentVisitInfo(int index) vinfo.reward.resources[i] = widget->value(); } - vinfo.reward.artifacts.clear(); + vinfo.reward.grantedArtifacts.clear(); for(int i = 0; i < ui->rArtifacts->count(); ++i) { 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(); 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.bonuses.clear(); + vinfo.reward.heroBonuses.clear(); for(int i = 0; i < ui->bonuses->rowCount(); ++i) { auto dur = bonusDurationMap.at(ui->bonuses->item(i, 0)->text().toStdString()); auto typ = bonusNameMap.at(ui->bonuses->item(i, 1)->text().toStdString()); 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(); @@ -452,7 +452,7 @@ void RewardsWidget::loadCurrentVisitInfo(int index) 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); for(auto i : vinfo.reward.spells) 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); } - for(auto & i : vinfo.reward.bonuses) + for(auto & i : vinfo.reward.heroBonuses) { auto dur = vstd::findKey(bonusDurationMap, i.duration); 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(", ")); QStringList artifactsList; - for (auto artifact : vinfo.reward.artifacts) + for (auto artifact : vinfo.reward.grantedArtifacts) { 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); } 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); }