From ef3f0174ddac5ef5cac9c4ce4d8878f70e4c8a96 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Wed, 13 Sep 2023 01:40:07 +0200 Subject: [PATCH 01/18] Rewardable seer hut and quest gate --- lib/mapObjects/CQuest.cpp | 180 ++++-------------------- lib/mapObjects/CQuest.h | 21 ++- lib/mapObjects/CRewardableObject.h | 2 +- lib/mapping/MapFormatH3M.cpp | 132 +++++++++-------- lib/rmg/modificators/TreasurePlacer.cpp | 28 ++-- 5 files changed, 127 insertions(+), 236 deletions(-) diff --git a/lib/mapObjects/CQuest.cpp b/lib/mapObjects/CQuest.cpp index 22cfe4df6..0e44e999f 100644 --- a/lib/mapObjects/CQuest.cpp +++ b/lib/mapObjects/CQuest.cpp @@ -378,7 +378,7 @@ void CQuest::getRolloverText(MetaString &ms, bool onHover) const } } -void CQuest::getCompletionText(MetaString &iwText, std::vector &components, bool isCustom, const CGHeroInstance * h) const +void CQuest::getCompletionText(MetaString &iwText) const { iwText.appendRawString(completedText); switch(missionType) @@ -567,11 +567,17 @@ void CGSeerHut::init(CRandomGenerator & rand) seerName = VLC->generaltexth->translate(seerNameID); quest->textOption = rand.nextInt(2); quest->completedOption = rand.nextInt(1, 3); + + configuration.canRefuse = true; + configuration.visitMode = Rewardable::EVisitMode::VISIT_ONCE; + configuration.selectMode = Rewardable::ESelectMode::SELECT_PLAYER; } void CGSeerHut::initObj(CRandomGenerator & rand) { init(rand); + + CRewardableObject::initObj(rand); quest->progress = CQuest::NOT_ACTIVE; if(quest->missionType) @@ -590,6 +596,10 @@ void CGSeerHut::initObj(CRandomGenerator & rand) quest->progress = CQuest::COMPLETE; quest->firstVisitText = VLC->generaltexth->seerEmpty[quest->completedOption]; } + + quest->getCompletionText(configuration.onSelect); + for(auto & i : configuration.info) + quest->getCompletionText(i.message); } void CGSeerHut::getRolloverText(MetaString &text, bool onHover) const @@ -649,44 +659,6 @@ void IQuestObject::afterAddToMapCommon(CMap * map) const map->addNewQuestInstance(quest); } -void CGSeerHut::getCompletionText(MetaString &text, std::vector &components, bool isCustom, const CGHeroInstance * h) const -{ - quest->getCompletionText (text, components, isCustom, h); - switch(rewardType) - { - case EXPERIENCE: - components.emplace_back(Component::EComponentType::EXPERIENCE, 0, static_cast(h->calculateXp(rVal)), 0); - break; - case MANA_POINTS: - components.emplace_back(Component::EComponentType::PRIM_SKILL, 5, rVal, 0); - break; - case MORALE_BONUS: - components.emplace_back(Component::EComponentType::MORALE, 0, rVal, 0); - break; - case LUCK_BONUS: - components.emplace_back(Component::EComponentType::LUCK, 0, rVal, 0); - break; - case RESOURCES: - components.emplace_back(Component::EComponentType::RESOURCE, rID, rVal, 0); - break; - case PRIMARY_SKILL: - components.emplace_back(Component::EComponentType::PRIM_SKILL, rID, rVal, 0); - break; - case SECONDARY_SKILL: - components.emplace_back(Component::EComponentType::SEC_SKILL, rID, rVal, 0); - break; - case ARTIFACT: - components.emplace_back(Component::EComponentType::ARTIFACT, rID, 0, 0); - break; - case SPELL: - components.emplace_back(Component::EComponentType::SPELL, rID, 0, 0); - break; - case CREATURE: - components.emplace_back(Component::EComponentType::CREATURE, rID, rVal, 0); - break; - } -} - void CGSeerHut::setPropertyDer (ui8 what, ui32 val) { switch(what) @@ -699,6 +671,7 @@ void CGSeerHut::setPropertyDer (ui8 what, ui32 val) void CGSeerHut::newTurn(CRandomGenerator & rand) const { + CRewardableObject::newTurn(rand); if(quest->lastDay >= 0 && quest->lastDay <= cb->getDate() - 1) //time is up { cb->setObjProperty (id, CGSeerHut::OBJPROP_VISITED, CQuest::COMPLETE); @@ -738,12 +711,7 @@ void CGSeerHut::onHeroVisit(const CGHeroInstance * h) const } if(!failRequirements) // propose completion, also on first visit { - BlockingDialog bd (true, false); - bd.player = h->getOwner(); - - getCompletionText (bd.text, bd.components, isCustom, h); - - cb->showBlockingDialog (&bd); + CRewardableObject::onHeroVisit(h); return; } } @@ -788,108 +756,9 @@ int CGSeerHut::checkDirection() const } } -void CGSeerHut::finishQuest(const CGHeroInstance * h, ui32 accept) const +void CGSeerHut::completeQuest() const //reward { - if (accept) - { - switch (quest->missionType) - { - case CQuest::MISSION_ART: - for(auto & elem : quest->m5arts) - { - if(h->hasArt(elem)) - { - cb->removeArtifact(ArtifactLocation(h, h->getArtPos(elem, false))); - } - else - { - const auto * assembly = h->getAssemblyByConstituent(elem); - assert(assembly); - auto parts = assembly->getPartsInfo(); - - // Remove the assembly - cb->removeArtifact(ArtifactLocation(h, h->getArtPos(assembly))); - - // Disassemble this backpack artifact - for(const auto & ci : parts) - { - if(ci.art->getTypeId() != elem) - cb->giveHeroNewArtifact(h, ci.art->artType, ArtifactPosition::BACKPACK_START); - } - } - } - break; - case CQuest::MISSION_ARMY: - cb->takeCreatures(h->id, quest->m6creatures); - break; - case CQuest::MISSION_RESOURCES: - for (int i = 0; i < 7; ++i) - { - cb->giveResource(h->getOwner(), static_cast(i), -static_cast(quest->m7resources[i])); - } - break; - default: - break; - } - cb->setObjProperty (id, CGSeerHut::OBJPROP_VISITED, CQuest::COMPLETE); //mission complete - completeQuest(h); //make sure to remove QuestGuard at the very end - } -} - -void CGSeerHut::completeQuest (const CGHeroInstance * h) const //reward -{ - switch (rewardType) - { - case EXPERIENCE: - { - TExpType expVal = h->calculateXp(rVal); - cb->changePrimSkill(h, PrimarySkill::EXPERIENCE, expVal, false); - break; - } - case MANA_POINTS: - { - cb->setManaPoints(h->id, h->mana+rVal); - break; - } - case MORALE_BONUS: case LUCK_BONUS: - { - Bonus hb(BonusDuration::ONE_WEEK, (rewardType == 3 ? BonusType::MORALE : BonusType::LUCK), - BonusSource::OBJECT, rVal, h->id.getNum(), "", -1); - GiveBonus gb; - gb.id = h->id.getNum(); - gb.bonus = hb; - cb->giveHeroBonus(&gb); - } - break; - case RESOURCES: - cb->giveResource(h->getOwner(), static_cast(rID), rVal); - break; - case PRIMARY_SKILL: - cb->changePrimSkill(h, static_cast(rID), rVal, false); - break; - case SECONDARY_SKILL: - cb->changeSecSkill(h, SecondarySkill(rID), rVal, false); - break; - case ARTIFACT: - cb->giveHeroNewArtifact(h, VLC->arth->objects[rID],ArtifactPosition::FIRST_AVAILABLE); - break; - case SPELL: - { - std::set spell; - spell.insert (SpellID(rID)); - cb->changeSpells(h, true, spell); - } - break; - case CREATURE: - { - CCreatureSet creatures; - creatures.setCreature(SlotID(0), CreatureID(rID), rVal); - cb->giveCreatures(this, h, creatures, false); - } - break; - default: - break; - } + cb->setObjProperty(id, CGSeerHut::OBJPROP_VISITED, CQuest::COMPLETE); //mission complete } const CGHeroInstance * CGSeerHut::getHeroToKill(bool allowNull) const @@ -912,7 +781,9 @@ const CGCreature * CGSeerHut::getCreatureToKill(bool allowNull) const void CGSeerHut::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const { - finishQuest(hero, answer); + CRewardableObject::blockingDialogAnswered(hero, answer); + if(answer) + completeQuest(); } void CGSeerHut::afterAddToMap(CMap* map) @@ -922,7 +793,7 @@ void CGSeerHut::afterAddToMap(CMap* map) void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler) { - static const std::map REWARD_MAP = + /*static const std::map REWARD_MAP = { {NOTHING, ""}, {EXPERIENCE, "experience"}, @@ -949,15 +820,16 @@ void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler) {"artifact", ARTIFACT}, {"spell", SPELL}, {"creature", CREATURE} - }; + };*/ //quest and reward quest->serializeJson(handler, "quest"); + CRewardableObject::serializeJsonOptions(handler); //only one reward is supported //todo: full reward format support after CRewardInfo integration - auto s = handler.enterStruct("reward"); + /*auto s = handler.enterStruct("reward"); std::string fullIdentifier; std::string metaTypeName; std::string scope; @@ -1079,7 +951,7 @@ void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler) } } handler.serializeInt(fullIdentifier, rVal); - } + }*/ } void CGQuestGuard::init(CRandomGenerator & rand) @@ -1087,9 +959,13 @@ void CGQuestGuard::init(CRandomGenerator & rand) blockVisit = true; quest->textOption = rand.nextInt(3, 5); quest->completedOption = rand.nextInt(4, 5); + + configuration.info.push_back({}); + configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; + configuration.canRefuse = true; } -void CGQuestGuard::completeQuest(const CGHeroInstance *h) const +void CGQuestGuard::completeQuest() const { cb->removeObject(this); } diff --git a/lib/mapObjects/CQuest.h b/lib/mapObjects/CQuest.h index e60135c56..963f29499 100644 --- a/lib/mapObjects/CQuest.h +++ b/lib/mapObjects/CQuest.h @@ -9,7 +9,7 @@ */ #pragma once -#include "CArmedInstance.h" +#include "CRewardableObject.h" #include "../ResourceSet.h" VCMI_LIB_NAMESPACE_BEGIN @@ -80,7 +80,7 @@ public: static bool checkMissionArmy(const CQuest * q, const CCreatureSet * army); virtual bool checkQuest (const CGHeroInstance * h) const; //determines whether the quest is complete or not virtual void getVisitText (MetaString &text, std::vector &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h = nullptr) const; - virtual void getCompletionText (MetaString &text, std::vector &components, bool isCustom, const CGHeroInstance * h = nullptr) const; + virtual void getCompletionText(MetaString &text) const; virtual void getRolloverText (MetaString &text, bool onHover) const; //hover or quest log entry virtual void completeQuest (const CGHeroInstance * h) const {}; virtual void addReplacements(MetaString &out, const std::string &base) const; @@ -138,13 +138,9 @@ protected: void afterAddToMapCommon(CMap * map) const; }; -class DLL_LINKAGE CGSeerHut : public CArmedInstance, public IQuestObject //army is used when giving reward +class DLL_LINKAGE CGSeerHut : public CRewardableObject, public IQuestObject //army is used when giving reward { public: - enum ERewardType {NOTHING, EXPERIENCE, MANA_POINTS, MORALE_BONUS, LUCK_BONUS, RESOURCES, PRIMARY_SKILL, SECONDARY_SKILL, ARTIFACT, SPELL, CREATURE}; - ERewardType rewardType = ERewardType::NOTHING; - si32 rID = -1; //reward ID - si32 rVal = -1; //reward value std::string seerName; void initObj(CRandomGenerator & rand) override; @@ -159,9 +155,8 @@ public: const CGHeroInstance *getHeroToKill(bool allowNull = false) const; const CGCreature *getCreatureToKill(bool allowNull = false) const; void getRolloverText (MetaString &text, bool onHover) const; - void getCompletionText(MetaString &text, std::vector &components, bool isCustom, const CGHeroInstance * h = nullptr) const; void finishQuest (const CGHeroInstance * h, ui32 accept) const; //common for both objects - virtual void completeQuest (const CGHeroInstance * h) const; + virtual void completeQuest() const; void afterAddToMap(CMap * map) override; @@ -169,9 +164,9 @@ public: { h & static_cast(*this); h & static_cast(*this); - h & rewardType; - h & rID; - h & rVal; + //h & rewardType; + //h & rID; + //h & rVal; h & seerName; } protected: @@ -186,7 +181,7 @@ class DLL_LINKAGE CGQuestGuard : public CGSeerHut { public: void init(CRandomGenerator & rand) override; - void completeQuest (const CGHeroInstance * h) const override; + void completeQuest() const override; template void serialize(Handler &h, const int version) { diff --git a/lib/mapObjects/CRewardableObject.h b/lib/mapObjects/CRewardableObject.h index ca8f918db..47ce27932 100644 --- a/lib/mapObjects/CRewardableObject.h +++ b/lib/mapObjects/CRewardableObject.h @@ -9,7 +9,7 @@ */ #pragma once -#include "../mapObjects/CArmedInstance.h" +#include "CArmedInstance.h" #include "../rewardable/Interface.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 0b241c68c..a3b4b16df 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -1842,77 +1842,89 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position) if(hut->quest->missionType) { - auto rewardType = static_cast(reader->readUInt8()); - hut->rewardType = rewardType; + auto rewardType = reader->readUInt8(); + Rewardable::Reward reward; switch(rewardType) { - case CGSeerHut::EXPERIENCE: - { - hut->rVal = reader->readUInt32(); - break; - } - case CGSeerHut::MANA_POINTS: - { - hut->rVal = reader->readUInt32(); - break; - } - case CGSeerHut::MORALE_BONUS: - { - hut->rVal = reader->readUInt8(); - break; - } - case CGSeerHut::LUCK_BONUS: - { - hut->rVal = reader->readUInt8(); - break; - } - case CGSeerHut::RESOURCES: - { - hut->rID = reader->readUInt8(); - hut->rVal = reader->readUInt32(); - - assert(hut->rID < features.resourcesCount); - assert((hut->rVal & 0x00ffffff) == hut->rVal); - break; - } - case CGSeerHut::PRIMARY_SKILL: - { - hut->rID = reader->readUInt8(); - hut->rVal = reader->readUInt8(); - break; - } - case CGSeerHut::SECONDARY_SKILL: - { - hut->rID = reader->readSkill(); - hut->rVal = reader->readUInt8(); - break; - } - case CGSeerHut::ARTIFACT: - { - hut->rID = reader->readArtifact(); - break; - } - case CGSeerHut::SPELL: - { - hut->rID = reader->readSpell(); - break; - } - case CGSeerHut::CREATURE: - { - hut->rID = reader->readCreature(); - hut->rVal = reader->readUInt16(); - break; - } - case CGSeerHut::NOTHING: + case 0: //NOTHING { // no-op break; } + case 1: //EXPERIENCE + { + reward.heroExperience = reader->readUInt32(); + break; + } + case 2: //MANA POINTS: + { + reward.manaDiff = reader->readUInt32(); + break; + } + case 3: //MORALE_BONUS + { + reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT, reader->readUInt8(), hut->id); + break; + } + case 4: //LUCK_BONUS + { + reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT, reader->readUInt8(), hut->id); + break; + } + case 5: //RESOURCES + { + auto rId = reader->readUInt8(); + auto rVal = reader->readUInt32(); + + assert(rId < features.resourcesCount); + assert((rVal & 0x00ffffff) == rVal); + + reward.resources[rId] = rVal; + break; + } + case 6: //PRIMARY_SKILL + { + auto rId = reader->readUInt8(); + auto rVal = reader->readUInt8(); + + reward.primary.at(rId) = rVal; + break; + } + case 7: //SECONDARY_SKILL + { + auto rId = reader->readSkill(); + auto rVal = reader->readUInt8(); + + reward.secondary[rId] = rVal; + break; + } + case 8: //ARTIFACT + { + reward.artifacts.push_back(reader->readArtifact()); + break; + } + case 9: //SPELL + { + reward.spells.push_back(reader->readSpell()); + break; + } + case 10: //CREATURE + { + auto rId = reader->readCreature(); + auto rVal = reader->readUInt16(); + + reward.creatures.emplace_back(rId, rVal); + break; + } default: { assert(0); } } + + hut->configuration.info.push_back({}); + hut->configuration.info.back().reward = reward; + hut->configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; } else { diff --git a/lib/rmg/modificators/TreasurePlacer.cpp b/lib/rmg/modificators/TreasurePlacer.cpp index 3ba86e19f..0addaa481 100644 --- a/lib/rmg/modificators/TreasurePlacer.cpp +++ b/lib/rmg/modificators/TreasurePlacer.cpp @@ -441,9 +441,12 @@ void TreasurePlacer::addAllPossibleObjects() { auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance); auto * obj = dynamic_cast(factory->create()); - obj->rewardType = CGSeerHut::CREATURE; - obj->rID = creature->getId(); - obj->rVal = creaturesAmount; + + Rewardable::Reward reward; + reward.creatures.emplace_back(creature->getId(), creaturesAmount); + obj->configuration.info.push_back({}); + obj->configuration.info.back().reward = reward; + obj->configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; obj->quest->missionType = CQuest::MISSION_ART; @@ -490,10 +493,12 @@ void TreasurePlacer::addAllPossibleObjects() { auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance); auto * obj = dynamic_cast(factory->create()); - - obj->rewardType = CGSeerHut::EXPERIENCE; - obj->rID = 0; //unitialized? - obj->rVal = generator.getConfig().questRewardValues[i]; + + Rewardable::Reward reward; + reward.heroExperience = generator.getConfig().questRewardValues[i]; + obj->configuration.info.push_back({}); + obj->configuration.info.back().reward = reward; + obj->configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; obj->quest->missionType = CQuest::MISSION_ART; ArtifactID artid = qap->drawRandomArtifact(); @@ -513,9 +518,12 @@ void TreasurePlacer::addAllPossibleObjects() { auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance); auto * obj = dynamic_cast(factory->create()); - obj->rewardType = CGSeerHut::RESOURCES; - obj->rID = GameResID(EGameResID::GOLD); - obj->rVal = generator.getConfig().questRewardValues[i]; + + Rewardable::Reward reward; + reward.resources[EGameResID::GOLD] = generator.getConfig().questRewardValues[i]; + obj->configuration.info.push_back({}); + obj->configuration.info.back().reward = reward; + obj->configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; obj->quest->missionType = CQuest::MISSION_ART; ArtifactID artid = qap->drawRandomArtifact(); From 96d6a48f010918787c96567486eb90b9b8af8866 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Wed, 13 Sep 2023 02:14:35 +0200 Subject: [PATCH 02/18] Backward compatible json serialization --- lib/mapObjects/CQuest.cpp | 184 ++++++++++++-------------------------- 1 file changed, 55 insertions(+), 129 deletions(-) diff --git a/lib/mapObjects/CQuest.cpp b/lib/mapObjects/CQuest.cpp index 0e44e999f..6485c447a 100644 --- a/lib/mapObjects/CQuest.cpp +++ b/lib/mapObjects/CQuest.cpp @@ -793,103 +793,30 @@ void CGSeerHut::afterAddToMap(CMap* map) void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler) { - /*static const std::map REWARD_MAP = - { - {NOTHING, ""}, - {EXPERIENCE, "experience"}, - {MANA_POINTS, "mana"}, - {MORALE_BONUS, "morale"}, - {LUCK_BONUS, "luck"}, - {RESOURCES, "resource"}, - {PRIMARY_SKILL, "primarySkill"}, - {SECONDARY_SKILL,"secondarySkill"}, - {ARTIFACT, "artifact"}, - {SPELL, "spell"}, - {CREATURE, "creature"} - }; - - static const std::map REWARD_RMAP = - { - {"experience", EXPERIENCE}, - {"mana", MANA_POINTS}, - {"morale", MORALE_BONUS}, - {"luck", LUCK_BONUS}, - {"resource", RESOURCES}, - {"primarySkill", PRIMARY_SKILL}, - {"secondarySkill",SECONDARY_SKILL}, - {"artifact", ARTIFACT}, - {"spell", SPELL}, - {"creature", CREATURE} - };*/ - //quest and reward quest->serializeJson(handler, "quest"); - CRewardableObject::serializeJsonOptions(handler); - - //only one reward is supported - //todo: full reward format support after CRewardInfo integration - - /*auto s = handler.enterStruct("reward"); - std::string fullIdentifier; - std::string metaTypeName; - std::string scope; - std::string identifier; if(handler.saving) { - si32 amount = rVal; - - metaTypeName = REWARD_MAP.at(rewardType); - switch (rewardType) - { - case NOTHING: - break; - case EXPERIENCE: - case MANA_POINTS: - case MORALE_BONUS: - case LUCK_BONUS: - identifier.clear(); - break; - case RESOURCES: - identifier = GameConstants::RESOURCE_NAMES[rID]; - break; - case PRIMARY_SKILL: - identifier = NPrimarySkill::names[rID]; - break; - case SECONDARY_SKILL: - identifier = CSkillHandler::encodeSkill(rID); - break; - case ARTIFACT: - identifier = ArtifactID(rID).toArtifact(VLC->artifacts())->getJsonKey(); - amount = 1; - break; - case SPELL: - identifier = SpellID(rID).toSpell(VLC->spells())->getJsonKey(); - amount = 1; - break; - case CREATURE: - identifier = CreatureID(rID).toCreature(VLC->creatures())->getJsonKey(); - break; - default: - assert(false); - break; - } - if(rewardType != NOTHING) - { - fullIdentifier = ModUtility::makeFullIdentifier(scope, metaTypeName, identifier); - handler.serializeInt(fullIdentifier, amount); - } + CRewardableObject::serializeJsonOptions(handler); } else { - rewardType = NOTHING; - + auto s = handler.enterStruct("reward"); + std::string fullIdentifier; + std::string metaTypeName; + std::string scope; + std::string identifier; + const JsonNode & rewardsJson = handler.getCurrent(); fullIdentifier.clear(); if(rewardsJson.Struct().empty()) + { + CRewardableObject::serializeJsonOptions(handler); return; + } else { auto iter = rewardsJson.Struct().begin(); @@ -897,61 +824,60 @@ void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler) } ModUtility::parseIdentifier(fullIdentifier, scope, metaTypeName, identifier); - - auto it = REWARD_RMAP.find(metaTypeName); - - if(it == REWARD_RMAP.end()) + if(!std::set{"resource", "primarySkill", "secondarySkill", "artifact", "spell", "creature", "experience", "mana", "morale", "luck"}.count(metaTypeName)) { - logGlobal->error("%s: invalid metatype in reward item %s", instanceName, fullIdentifier); + CRewardableObject::serializeJsonOptions(handler); return; } - else + + //backward compatibility + int val = 0; + handler.serializeInt(fullIdentifier, val); + + Rewardable::Reward reward; + if(metaTypeName == "experience") + reward.heroExperience = val; + if(metaTypeName == "mana") + reward.manaDiff = val; + if(metaTypeName == "morale") + reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT, val, id); + if(metaTypeName == "luck") + reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT, val, id); + if(metaTypeName == "resource") { - rewardType = it->second; + auto rawId = *VLC->identifiers()->getIdentifier(ModScope::scopeMap(), fullIdentifier, false); + reward.resources[rawId] = val; } - - bool doRequest = false; - - switch (rewardType) + if(metaTypeName == "primarySkill") { - case NOTHING: - return; - case EXPERIENCE: - case MANA_POINTS: - case MORALE_BONUS: - case LUCK_BONUS: - break; - case PRIMARY_SKILL: - doRequest = true; - break; - case RESOURCES: - case SECONDARY_SKILL: - case ARTIFACT: - case SPELL: - case CREATURE: - doRequest = true; - break; - default: - assert(false); - break; + auto rawId = *VLC->identifiers()->getIdentifier(ModScope::scopeMap(), fullIdentifier, false); + reward.primary.at(rawId) = val; } - - if(doRequest) + if(metaTypeName == "secondarySkill") { - auto rawId = VLC->identifiers()->getIdentifier(ModScope::scopeMap(), fullIdentifier, false); - - if(rawId) - { - rID = rawId.value(); - } - else - { - rewardType = NOTHING;//fallback in case of error - return; - } + auto rawId = *VLC->identifiers()->getIdentifier(ModScope::scopeMap(), fullIdentifier, false); + reward.secondary[rawId] = val; } - handler.serializeInt(fullIdentifier, rVal); - }*/ + if(metaTypeName == "artifact") + { + auto rawId = *VLC->identifiers()->getIdentifier(ModScope::scopeMap(), fullIdentifier, false); + reward.artifacts.push_back(rawId); + } + if(metaTypeName == "spell") + { + auto rawId = *VLC->identifiers()->getIdentifier(ModScope::scopeMap(), fullIdentifier, false); + reward.spells.push_back(rawId); + } + if(metaTypeName == "creature") + { + auto rawId = *VLC->identifiers()->getIdentifier(ModScope::scopeMap(), fullIdentifier, false); + reward.creatures.emplace_back(rawId, val); + } + + configuration.info.push_back({}); + configuration.info.back().reward = reward; + configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; + } } void CGQuestGuard::init(CRandomGenerator & rand) From 574047c55c5265f06b514aa3b4e3b4b14abb8f6c Mon Sep 17 00:00:00 2001 From: nordsoft Date: Wed, 13 Sep 2023 09:18:46 +0200 Subject: [PATCH 03/18] Start making new serialization for rewardables --- lib/mapObjects/CRewardableObject.cpp | 5 ++++ lib/mapObjects/CRewardableObject.h | 2 ++ mapeditor/inspector/rewardswidget.cpp | 39 ++++++++++++++++++++++----- 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp index 5d21cf278..8e0849f00 100644 --- a/lib/mapObjects/CRewardableObject.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -276,4 +276,9 @@ void CRewardableObject::initObj(CRandomGenerator & rand) CRewardableObject::CRewardableObject() {} +void CRewardableObject::serializeJsonOptions(JsonSerializeFormat & handler) +{ + +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/CRewardableObject.h b/lib/mapObjects/CRewardableObject.h index 47ce27932..761e93702 100644 --- a/lib/mapObjects/CRewardableObject.h +++ b/lib/mapObjects/CRewardableObject.h @@ -31,6 +31,8 @@ protected: /// return true if this object was "cleared" before and no longer has rewards applicable to selected hero /// unlike wasVisited, this method uses information not available to player owner, for example, if object was cleared by another player before bool wasVisitedBefore(const CGHeroInstance * contextHero) const; + + void serializeJsonOptions(JsonSerializeFormat & handler) override; public: /// Visitability checks. Note that hero check includes check for hero owner (returns true if object was visited by player) diff --git a/mapeditor/inspector/rewardswidget.cpp b/mapeditor/inspector/rewardswidget.cpp index 54774da11..a20fd50c1 100644 --- a/mapeditor/inspector/rewardswidget.cpp +++ b/mapeditor/inspector/rewardswidget.cpp @@ -163,10 +163,21 @@ void RewardsWidget::obtainData() if(seerhut) { - switch(seerhut->rewardType) + for(auto & i : seerhut->configuration.info) + { + if(i.reward.heroExperience) + addReward(RewardType::EXPERIENCE, 0, i.reward.heroExperience); + if(i.reward.manaDiff) + addReward(RewardType::MANA, 0, i.reward.manaDiff); + for(auto & a : i.reward.artifacts) + addReward(RewardType::ARTIFACT, a.getNum(), 0); + for(auto & a : i.reward.creatures) + addReward(RewardType::CREATURE, a.getType()->getId().getNum(), a.getCount()); + } + /*switch(seerhut->rewardType) { case CGSeerHut::ERewardType::EXPERIENCE: - addReward(RewardType::EXPERIENCE, 0, seerhut->rVal); + break; case CGSeerHut::ERewardType::MANA_POINTS: @@ -182,7 +193,7 @@ void RewardsWidget::obtainData() break; case CGSeerHut::ERewardType::RESOURCES: - addReward(RewardType::RESOURCE, seerhut->rID, seerhut->rVal); + break; case CGSeerHut::ERewardType::PRIMARY_SKILL: @@ -207,7 +218,7 @@ void RewardsWidget::obtainData() default: break; - } + }*/ } } @@ -279,15 +290,29 @@ bool RewardsWidget::commitChanges() } if(seerhut) { + seerhut->configuration.info.clear(); for(int row = 0; row < rewards; ++row) { + Rewardable::Reward reward; haveRewards = true; int typeId = ui->rewardsTable->item(row, 0)->data(Qt::UserRole).toInt(); int listId = ui->rewardsTable->item(row, 1) ? ui->rewardsTable->item(row, 1)->data(Qt::UserRole).toInt() : 0; int amount = ui->rewardsTable->item(row, 2)->data(Qt::UserRole).toInt(); - seerhut->rewardType = CGSeerHut::ERewardType(typeId + 1); - seerhut->rID = listId; - seerhut->rVal = amount; + switch (typeId) { + case RewardType::EXPERIENCE: + reward.heroExperience = amount; + break; + + case RewardType::ARTIFACT: + reward.artifacts.push_back(listId); + break; + + case RewardType::CREATURE: + reward.creatures.emplace_back(listId, amount); + + default: + break; + } } } return haveRewards; From 06f01c3b825937f0797de4ca14ba2ad971cd9792 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Fri, 15 Sep 2023 10:06:06 +0200 Subject: [PATCH 04/18] Seerhut works as rewardable object --- lib/MetaString.cpp | 11 +++ lib/MetaString.h | 3 + lib/mapObjects/CQuest.cpp | 39 +++++------ lib/mapObjects/CRewardableObject.cpp | 4 +- lib/rewardable/Configuration.cpp | 27 ++++++++ lib/rewardable/Configuration.h | 8 ++- lib/rewardable/Interface.cpp | 5 ++ lib/rewardable/Interface.h | 2 + lib/rewardable/Limiter.cpp | 6 ++ lib/rewardable/Limiter.h | 2 + lib/rewardable/Reward.cpp | 96 +++++++++++++++++++++++++++ lib/rewardable/Reward.h | 2 + mapeditor/inspector/rewardswidget.cpp | 4 +- 13 files changed, 183 insertions(+), 26 deletions(-) diff --git a/lib/MetaString.cpp b/lib/MetaString.cpp index 78329a899..86a1badfe 100644 --- a/lib/MetaString.cpp +++ b/lib/MetaString.cpp @@ -19,6 +19,7 @@ #include "VCMI_Lib.h" #include "mapObjectConstructors/CObjectClassesHandler.h" #include "spells/CSpellHandler.h" +#include "serializer/JsonSerializeFormat.h" VCMI_LIB_NAMESPACE_BEGIN @@ -385,4 +386,14 @@ void MetaString::jsonDeserialize(const JsonNode & source) numbers.push_back(entry.Integer()); } +void MetaString::serializeJson(JsonSerializeFormat & handler) +{ + JsonNode attr; + if(handler.saving) + jsonSerialize(attr); + handler.serializeRaw("attributes", attr, std::nullopt); + if(!handler.saving) + jsonDeserialize(attr); +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/MetaString.h b/lib/MetaString.h index c7329f629..2c6da6961 100644 --- a/lib/MetaString.h +++ b/lib/MetaString.h @@ -14,6 +14,7 @@ VCMI_LIB_NAMESPACE_BEGIN class JsonNode; class CreatureID; class CStackBasicDescriptor; +class JsonSerializeFormat; using TQuantity = si32; /// Strings classes that can be used as replacement in MetaString @@ -113,6 +114,8 @@ public: void jsonSerialize(JsonNode & dest) const; void jsonDeserialize(const JsonNode & dest); + + void serializeJson(JsonSerializeFormat & handler); template void serialize(Handler & h, const int version) { diff --git a/lib/mapObjects/CQuest.cpp b/lib/mapObjects/CQuest.cpp index 6485c447a..01d71bf19 100644 --- a/lib/mapObjects/CQuest.cpp +++ b/lib/mapObjects/CQuest.cpp @@ -802,35 +802,28 @@ void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler) } else { - auto s = handler.enterStruct("reward"); - std::string fullIdentifier; - std::string metaTypeName; - std::string scope; - std::string identifier; - - const JsonNode & rewardsJson = handler.getCurrent(); - - fullIdentifier.clear(); - - if(rewardsJson.Struct().empty()) - { - CRewardableObject::serializeJsonOptions(handler); - return; - } - else - { - auto iter = rewardsJson.Struct().begin(); - fullIdentifier = iter->first; - } - - ModUtility::parseIdentifier(fullIdentifier, scope, metaTypeName, identifier); - if(!std::set{"resource", "primarySkill", "secondarySkill", "artifact", "spell", "creature", "experience", "mana", "morale", "luck"}.count(metaTypeName)) + if(handler.getCurrent()["reward"].isNull() || handler.getCurrent()["reward"].Struct().empty()) { CRewardableObject::serializeJsonOptions(handler); return; } //backward compatibility + auto s = handler.enterStruct("reward"); + const JsonNode & rewardsJson = handler.getCurrent(); + + std::string fullIdentifier; + std::string metaTypeName; + std::string scope; + std::string identifier; + + auto iter = rewardsJson.Struct().begin(); + fullIdentifier = iter->first; + + ModUtility::parseIdentifier(fullIdentifier, scope, metaTypeName, identifier); + if(!std::set{"resource", "primarySkill", "secondarySkill", "artifact", "spell", "creature", "experience", "mana", "morale", "luck"}.count(metaTypeName)) + return; + int val = 0; handler.serializeInt(fullIdentifier, val); diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp index 8e0849f00..31c17e6e7 100644 --- a/lib/mapObjects/CRewardableObject.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -17,6 +17,7 @@ #include "../NetPacks.h" #include "../mapObjectConstructors/AObjectTypeHandler.h" #include "../mapObjectConstructors/CObjectClassesHandler.h" +#include "../serializer/JsonSerializeFormat.h" VCMI_LIB_NAMESPACE_BEGIN @@ -278,7 +279,8 @@ CRewardableObject::CRewardableObject() void CRewardableObject::serializeJsonOptions(JsonSerializeFormat & handler) { - + CArmedInstance::serializeJsonOptions(handler); + handler.serializeStruct("rewardable", static_cast(*this)); } VCMI_LIB_NAMESPACE_END diff --git a/lib/rewardable/Configuration.cpp b/lib/rewardable/Configuration.cpp index 386506e59..5e2430e62 100644 --- a/lib/rewardable/Configuration.cpp +++ b/lib/rewardable/Configuration.cpp @@ -10,6 +10,7 @@ #include "StdInc.h" #include "Configuration.h" +#include "../serializer/JsonSerializeFormat.h" VCMI_LIB_NAMESPACE_BEGIN @@ -23,4 +24,30 @@ ui16 Rewardable::Configuration::getResetDuration() const return resetParameters.period; } +void Rewardable::ResetInfo::serializeJson(JsonSerializeFormat & handler) +{ + handler.serializeInt("period", period); + handler.serializeBool("visitors", visitors); + handler.serializeBool("rewards", rewards); +} + +void Rewardable::VisitInfo::serializeJson(JsonSerializeFormat & handler) +{ + handler.serializeStruct("limiter", limiter); + handler.serializeStruct("reward", reward); + handler.serializeStruct("message", message); + handler.serializeInt("visitType", visitType); +} + +void Rewardable::Configuration::serializeJson(JsonSerializeFormat & handler) +{ + handler.serializeStruct("onSelect", onSelect); + handler.enterArray("info").serializeStruct(info); + handler.serializeEnum("selectMode", selectMode, std::vector{SelectModeString.begin(), SelectModeString.end()}); + handler.serializeEnum("visitMode", visitMode, std::vector{VisitModeString.begin(), VisitModeString.end()}); + handler.serializeStruct("resetParameters", resetParameters); + handler.serializeBool("canRefuse", canRefuse); + handler.serializeInt("infoWindowType", infoWindowType); +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/rewardable/Configuration.h b/lib/rewardable/Configuration.h index a2069fc71..72377da04 100644 --- a/lib/rewardable/Configuration.h +++ b/lib/rewardable/Configuration.h @@ -65,7 +65,9 @@ struct DLL_LINKAGE ResetInfo /// if true - re-randomize rewards on a new week bool rewards; - + + void serializeJson(JsonSerializeFormat & handler); + template void serialize(Handler &h, const int version) { h & period; @@ -84,6 +86,8 @@ struct DLL_LINKAGE VisitInfo /// Event to which this reward is assigned EEventType visitType; + + void serializeJson(JsonSerializeFormat & handler); template void serialize(Handler &h, const int version) { @@ -121,6 +125,8 @@ struct DLL_LINKAGE Configuration EVisitMode getVisitMode() const; ui16 getResetDuration() const; + void serializeJson(JsonSerializeFormat & handler); + template void serialize(Handler &h, const int version) { h & info; diff --git a/lib/rewardable/Interface.cpp b/lib/rewardable/Interface.cpp index 2ddedff3e..84cfdc8e3 100644 --- a/lib/rewardable/Interface.cpp +++ b/lib/rewardable/Interface.cpp @@ -147,4 +147,9 @@ void Rewardable::Interface::grantRewardAfterLevelup(IGameCallback * cb, const Re cb->removeAfterVisit(instance); } +void Rewardable::Interface::serializeJson(JsonSerializeFormat & handler) +{ + configuration.serializeJson(handler); +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/rewardable/Interface.h b/lib/rewardable/Interface.h index 74b798d69..c675f82bc 100644 --- a/lib/rewardable/Interface.h +++ b/lib/rewardable/Interface.h @@ -44,6 +44,8 @@ public: Rewardable::Configuration configuration; + void serializeJson(JsonSerializeFormat & handler); + template void serialize(Handler &h, const int version) { h & configuration; diff --git a/lib/rewardable/Limiter.cpp b/lib/rewardable/Limiter.cpp index bf3195edb..ef2d1d5c5 100644 --- a/lib/rewardable/Limiter.cpp +++ b/lib/rewardable/Limiter.cpp @@ -14,6 +14,7 @@ #include "../IGameCallback.h" #include "../CPlayerState.h" #include "../mapObjects/CGHeroInstance.h" +#include "../serializer/JsonSerializeFormat.h" VCMI_LIB_NAMESPACE_BEGIN @@ -119,4 +120,9 @@ bool Rewardable::Limiter::heroAllowed(const CGHeroInstance * hero) const return false; } +void Rewardable::Limiter::serializeJson(JsonSerializeFormat & handler) +{ + +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/rewardable/Limiter.h b/lib/rewardable/Limiter.h index a443d1e2c..68639bb68 100644 --- a/lib/rewardable/Limiter.h +++ b/lib/rewardable/Limiter.h @@ -92,6 +92,8 @@ struct DLL_LINKAGE Limiter h & anyOf; h & noneOf; } + + void serializeJson(JsonSerializeFormat & handler); }; } diff --git a/lib/rewardable/Reward.cpp b/lib/rewardable/Reward.cpp index 48caa35e5..186f4ba16 100644 --- a/lib/rewardable/Reward.cpp +++ b/lib/rewardable/Reward.cpp @@ -12,6 +12,9 @@ #include "Reward.h" #include "../mapObjects/CGHeroInstance.h" +#include "../serializer/JsonSerializeFormat.h" +#include "../constants/StringConstants.h" +#include "../CSkillHandler.h" VCMI_LIB_NAMESPACE_BEGIN @@ -94,4 +97,97 @@ void Rewardable::Reward::loadComponents(std::vector & comps, } } +void Rewardable::Reward::serializeJson(JsonSerializeFormat & handler) +{ + resources.serializeJson(handler, "resources"); + handler.serializeBool("removeObject", removeObject); + handler.serializeInt("manaPercentage", manaPercentage); + handler.serializeInt("movePercentage", movePercentage); + handler.serializeInt("heroExperience", heroExperience); + handler.serializeInt("heroLevel", heroLevel); + handler.serializeInt("manaDiff", manaDiff); + handler.serializeInt("manaOverflowFactor", manaOverflowFactor); + handler.serializeInt("movePoints", movePoints); + handler.serializeIdArray("artifacts", artifacts); + handler.serializeIdArray("spells", spells); + handler.enterArray("creatures").serializeStruct(creatures); + { + auto a = handler.enterArray("primary"); + a.syncSize(primary); + for(int i = 0; i < primary.size(); ++i) + a.serializeInt(i, primary[i]); + } + + { + auto a = handler.enterArray("secondary"); + std::vector> fieldValue; + if(handler.saving) + { + for(auto & i : secondary) + { + auto key = VLC->skillh->encodeSkill(i.first); + auto value = NSecondarySkill::levels.at(i.second); + fieldValue.emplace_back(key, value); + } + } + a.syncSize(fieldValue); + for(int i = 0; i < fieldValue.size(); ++i) + { + auto e = a.enterStruct(i); + e->serializeString("skill", fieldValue[i].first); + e->serializeString("level", fieldValue[i].second); + } + if(!handler.saving) + { + for(auto & i : fieldValue) + { + const int skillId = VLC->skillh->decodeSkill(i.first); + if(skillId < 0) + { + logGlobal->error("Invalid secondary skill %s", i.first); + continue; + } + + const int level = vstd::find_pos(NSecondarySkill::levels, i.second); + if(level < 0) + { + logGlobal->error("Invalid secondary skill level%s", i.second); + continue; + } + + secondary[SecondarySkill(skillId)] = level; + } + + } + } + + { + auto a = handler.enterArray("creaturesChange"); + std::vector> fieldValue; + if(handler.saving) + { + for(auto & i : creaturesChange) + fieldValue.push_back(i); + } + a.syncSize(fieldValue); + for(int i = 0; i < fieldValue.size(); ++i) + { + auto e = a.enterStruct(i); + e->serializeId("creature", fieldValue[i].first, CreatureID{}); + e->serializeId("amount", fieldValue[i].second, CreatureID{}); + } + if(!handler.saving) + { + for(auto & i : fieldValue) + creaturesChange[i.first] = i.second; + } + } + + { + auto a = handler.enterStruct("spellCast"); + a->serializeId("spell", spellCast.first, SpellID{}); + a->serializeInt("level", spellCast.second); + } +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/rewardable/Reward.h b/lib/rewardable/Reward.h index 16f93bb8d..3167a3546 100644 --- a/lib/rewardable/Reward.h +++ b/lib/rewardable/Reward.h @@ -110,6 +110,8 @@ struct DLL_LINKAGE Reward if(version >= 821) h & spellCast; } + + void serializeJson(JsonSerializeFormat & handler); }; } diff --git a/mapeditor/inspector/rewardswidget.cpp b/mapeditor/inspector/rewardswidget.cpp index a20fd50c1..79f3028d8 100644 --- a/mapeditor/inspector/rewardswidget.cpp +++ b/mapeditor/inspector/rewardswidget.cpp @@ -293,7 +293,9 @@ bool RewardsWidget::commitChanges() seerhut->configuration.info.clear(); for(int row = 0; row < rewards; ++row) { - Rewardable::Reward reward; + seerhut->configuration.info.emplace_back(); + seerhut->configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; + Rewardable::Reward & reward = seerhut->configuration.info.back().reward; haveRewards = true; int typeId = ui->rewardsTable->item(row, 0)->data(Qt::UserRole).toInt(); int listId = ui->rewardsTable->item(row, 1) ? ui->rewardsTable->item(row, 1)->data(Qt::UserRole).toInt() : 0; From c1c13cfafbae674aa3f09fae8b7a844ce5332f71 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Fri, 15 Sep 2023 15:29:41 +0200 Subject: [PATCH 05/18] Pandora and events work as rewardable object --- lib/mapObjects/CGPandoraBox.cpp | 520 +++++++----------------- lib/mapObjects/CGPandoraBox.h | 42 +- lib/mapObjects/CQuest.cpp | 30 +- lib/mapObjects/CQuest.h | 6 +- lib/mapObjects/CRewardableObject.cpp | 1 - lib/mapping/MapFormatH3M.cpp | 48 ++- lib/rmg/modificators/TreasurePlacer.cpp | 64 ++- 7 files changed, 236 insertions(+), 475 deletions(-) diff --git a/lib/mapObjects/CGPandoraBox.cpp b/lib/mapObjects/CGPandoraBox.cpp index 9a4dd89e8..d4787f306 100644 --- a/lib/mapObjects/CGPandoraBox.cpp +++ b/lib/mapObjects/CGPandoraBox.cpp @@ -25,297 +25,117 @@ VCMI_LIB_NAMESPACE_BEGIN +void CGPandoraBox::init() +{ + blockVisit = true; + + for(auto & i : configuration.info) + i.reward.removeObject = true; +} + void CGPandoraBox::initObj(CRandomGenerator & rand) { - blockVisit = (ID==Obj::PANDORAS_BOX); //block only if it's really pandora's box (events also derive from that class) - hasGuardians = stacks.size(); + init(); + + CRewardableObject::initObj(rand); } void CGPandoraBox::onHeroVisit(const CGHeroInstance * h) const { - BlockingDialog bd (true, false); - bd.player = h->getOwner(); - bd.text.appendLocalString (EMetaText::ADVOB_TXT, 14); - cb->showBlockingDialog (&bd); -} - -void CGPandoraBox::giveContentsUpToExp(const CGHeroInstance *h) const -{ - afterSuccessfulVisit(); - - InfoWindow iw; - iw.type = EInfoWindowMode::AUTO; - iw.player = h->getOwner(); - - bool changesPrimSkill = false; - for(const auto & elem : primskills) + auto setText = [](MetaString & text, int tId, const CGHeroInstance * h) { - if(elem) + text.appendLocalString(EMetaText::ADVOB_TXT, tId); + text.replaceRawString(h->getNameTranslated()); + }; + + for(auto i : getAvailableRewards(h, Rewardable::EEventType::EVENT_FIRST_VISIT)) + { + MetaString txt; + auto & r = configuration.info[i]; + + if(r.reward.spells.size() == 1) + setText(txt, 184, h); + else if(!r.reward.spells.empty()) + setText(txt, 188, h); + + if(r.reward.heroExperience || !r.reward.secondary.empty()) + setText(txt, 175, h); + + for(int ps : r.reward.primary) { - changesPrimSkill = true; - break; - } - } - - std::vector> unpossessedAbilities; //ability + ability level - int abilitiesRequiringSlot = 0; - - //filter out unnecessary secondary skills - for (int i = 0; i < abilities.size(); i++) - { - int curLev = h->getSecSkillLevel(abilities[i]); - bool abilityCanUseSlot = !curLev && ((h->secSkills.size() + abilitiesRequiringSlot) < GameConstants::SKILL_PER_HERO); //limit new abilities to number of slots - - if (abilityCanUseSlot) - abilitiesRequiringSlot++; - - if ((curLev && curLev < abilityLevels[i]) || abilityCanUseSlot) - { - unpossessedAbilities.emplace_back(abilities[i], abilityLevels[i]); - } - } - - if(gainedExp || changesPrimSkill || !unpossessedAbilities.empty()) - { - TExpType expVal = h->calculateXp(gainedExp); - //getText(iw,afterBattle,175,h); //wtf? - iw.text.appendLocalString(EMetaText::ADVOB_TXT, 175); //%s learns something - iw.text.replaceRawString(h->getNameTranslated()); - - if(expVal) - iw.components.emplace_back(Component::EComponentType::EXPERIENCE, 0, static_cast(expVal), 0); - - for(int i=0; ishowInfoDialog(&iw); - - //give sec skills - for(const auto & abilityData : unpossessedAbilities) - cb->changeSecSkill(h, abilityData.first, abilityData.second, true); - - assert(h->secSkills.size() <= GameConstants::SKILL_PER_HERO); - - //give prim skills - for(int i=0; ichangePrimSkill(h,static_cast(i),primskills[i],false); - - assert(!cb->isVisitCoveredByAnotherQuery(this, h)); - - //give exp - if(expVal) - cb->changePrimSkill(h, PrimarySkill::EXPERIENCE, expVal, false); - } - //else { } //TODO:Create information that box was empty for now, and deliver to CGPandoraBox::giveContentsAfterExp or refactor - - if(!cb->isVisitCoveredByAnotherQuery(this, h)) - giveContentsAfterExp(h); - //Otherwise continuation occurs via post-level-up callback. -} - -void CGPandoraBox::giveContentsAfterExp(const CGHeroInstance *h) const -{ - bool hadGuardians = hasGuardians; //copy, because flag will be emptied after issuing first post-battle message - - std::string msg = message; //in case box is removed in the meantime - InfoWindow iw; - iw.type = EInfoWindowMode::AUTO; - iw.player = h->getOwner(); - - //TODO: reuse this code for Scholar skill - if(!spells.empty()) - { - std::set spellsToGive; - - auto i = spells.cbegin(); - while (i != spells.cend()) - { - iw.components.clear(); - iw.text.clear(); - spellsToGive.clear(); - - for (; i != spells.cend(); i++) + if(ps) { - const auto * spell = (*i).toSpell(VLC->spells()); - if(h->canLearnSpell(spell)) - { - iw.components.emplace_back(Component::EComponentType::SPELL, *i, 0, 0); - spellsToGive.insert(*i); - } - if(spellsToGive.size() == 8) //display up to 8 spells at once - { - break; - } - } - if (!spellsToGive.empty()) - { - if (spellsToGive.size() > 1) - { - iw.text.appendLocalString(EMetaText::ADVOB_TXT, 188); //%s learns spells - } - else - { - iw.text.appendLocalString(EMetaText::ADVOB_TXT, 184); //%s learns a spell - } - iw.text.replaceRawString(h->getNameTranslated()); - cb->changeSpells(h, true, spellsToGive); - cb->showInfoDialog(&iw); + setText(txt, 175, h); + break; } } - } - - if(manaDiff) - { - getText(iw,hadGuardians,manaDiff,176,177,h); - iw.components.emplace_back(Component::EComponentType::PRIM_SKILL, 5, manaDiff, 0); - cb->showInfoDialog(&iw); - cb->setManaPoints(h->id, h->mana + manaDiff); - } - - if(moraleDiff) - { - getText(iw,hadGuardians,moraleDiff,178,179,h); - iw.components.emplace_back(Component::EComponentType::MORALE, 0, moraleDiff, 0); - cb->showInfoDialog(&iw); - GiveBonus gb; - gb.bonus = Bonus(BonusDuration::ONE_BATTLE,BonusType::MORALE,BonusSource::OBJECT,moraleDiff,id.getNum(),""); - gb.id = h->id.getNum(); - cb->giveHeroBonus(&gb); - } - - if(luckDiff) - { - getText(iw,hadGuardians,luckDiff,180,181,h); - iw.components.emplace_back(Component::EComponentType::LUCK, 0, luckDiff, 0); - cb->showInfoDialog(&iw); - GiveBonus gb; - gb.bonus = Bonus(BonusDuration::ONE_BATTLE,BonusType::LUCK,BonusSource::OBJECT,luckDiff,id.getNum(),""); - gb.id = h->id.getNum(); - cb->giveHeroBonus(&gb); - } - - iw.components.clear(); - iw.text.clear(); - for(int i=0; ishowInfoDialog(&iw); - } - - iw.components.clear(); - iw.text.clear(); - for(int i=0; i 0) - iw.components.emplace_back(Component::EComponentType::RESOURCE, i, resources[i], 0); - } - if(!iw.components.empty()) - { - getText(iw,hadGuardians,183,h); - cb->showInfoDialog(&iw); - } - - iw.components.clear(); - // getText(iw,afterBattle,183,h); - iw.text.appendLocalString(EMetaText::ADVOB_TXT, 183); //% has found treasure - iw.text.replaceRawString(h->getNameTranslated()); - for(const auto & elem : artifacts) - { - iw.components.emplace_back(Component::EComponentType::ARTIFACT, elem, 0, 0); - if(iw.components.size() >= 14) + + if(r.reward.manaDiff < 0) + setText(txt, 176, h); + if(r.reward.manaDiff > 0) + setText(txt, 177, h); + + for(auto b : r.reward.bonuses) { - cb->showInfoDialog(&iw); - iw.components.clear(); - iw.text.appendLocalString(EMetaText::ADVOB_TXT, 183); //% has found treasure - once more? - iw.text.replaceRawString(h->getNameTranslated()); + if(b.type == BonusType::MORALE) + { + if(b.val < 0) + setText(txt, 178, h); + if(b.val > 0) + setText(txt, 179, h); + } + if(b.type == BonusType::LUCK) + { + if(b.val < 0) + setText(txt, 180, h); + if(b.val > 0) + setText(txt, 181, h); + } } - } - if(!iw.components.empty()) - { - cb->showInfoDialog(&iw); - } - - cb->giveResources(h->getOwner(), resources); - - for(const auto & elem : artifacts) - cb->giveHeroNewArtifact(h, VLC->arth->objects[elem],ArtifactPosition::FIRST_AVAILABLE); - - iw.components.clear(); - iw.text.clear(); - - if(creatures.stacksCount()) - { //this part is taken straight from creature bank - MetaString loot; - for(const auto & elem : creatures.Slots()) - { //build list of joined creatures - iw.components.emplace_back(*elem.second); - loot.appendRawString("%s"); - loot.replaceCreatureName(*elem.second); + + for(auto res : r.reward.resources) + { + if(res < 0) + setText(txt, 182, h); + if(res > 0) + setText(txt, 183, h); } - - if(creatures.stacksCount() == 1 && creatures.Slots().begin()->second->count == 1) - iw.text.appendLocalString(EMetaText::ADVOB_TXT, 185); - else - iw.text.appendLocalString(EMetaText::ADVOB_TXT, 186); - - iw.text.replaceRawString(loot.buildList()); - iw.text.replaceRawString(h->getNameTranslated()); - - cb->showInfoDialog(&iw); - cb->giveCreatures(this, h, creatures, false); - } - if(!hasGuardians && !msg.empty()) - { - iw.text.appendRawString(msg); - cb->showInfoDialog(&iw); - } -} - -void CGPandoraBox::getText( InfoWindow &iw, bool &afterBattle, int text, const CGHeroInstance * h ) const -{ - if(afterBattle || message.empty()) - { - iw.text.appendLocalString(EMetaText::ADVOB_TXT,text);//%s has lost treasure. - iw.text.replaceRawString(h->getNameTranslated()); - } - else - { - iw.text.appendRawString(message); - afterBattle = true; - } -} - -void CGPandoraBox::getText( InfoWindow &iw, bool &afterBattle, int val, int negative, int positive, const CGHeroInstance * h ) const -{ - iw.components.clear(); - iw.text.clear(); - if(afterBattle || message.empty()) - { - iw.text.appendLocalString(EMetaText::ADVOB_TXT,val < 0 ? negative : positive); //%s's luck takes a turn for the worse / %s's luck increases - iw.text.replaceRawString(h->getNameTranslated()); - } - else - { - iw.text.appendRawString(message); - afterBattle = true; + + if(!r.reward.artifacts.empty()) + setText(txt, 183, h); + + if(!r.reward.creatures.empty()) + { + MetaString loot; + for(auto c : r.reward.creatures) + { + loot.appendRawString("%s"); + loot.replaceCreatureName(c); + } + + if(r.reward.creatures.size() == 1 && r.reward.creatures[0].count == 1) + txt.appendLocalString(EMetaText::ADVOB_TXT, 185); + else + txt.appendLocalString(EMetaText::ADVOB_TXT, 186); + + txt.replaceRawString(loot.buildList()); + txt.replaceRawString(h->getNameTranslated()); + } + + const_cast(r.message) = txt; } + + BlockingDialog bd (true, false); + bd.player = h->getOwner(); + bd.text.appendLocalString(EMetaText::ADVOB_TXT, 14); + cb->showBlockingDialog(&bd); } void CGPandoraBox::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const { if(result.winner == 0) { - giveContentsUpToExp(hero); + CRewardableObject::onHeroVisit(hero); } } @@ -328,117 +148,87 @@ void CGPandoraBox::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answe hero->showInfoDialog(16, 0, EInfoWindowMode::MODAL); cb->startBattleI(hero, this); //grants things after battle } - else if(message.empty() && resources.empty() - && primskills.empty() && abilities.empty() - && abilityLevels.empty() && artifacts.empty() - && spells.empty() && creatures.stacksCount() == 0 - && gainedExp == 0 && manaDiff == 0 && moraleDiff == 0 && luckDiff == 0) //if it gives nothing without battle + else if(getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT).empty()) { hero->showInfoDialog(15); cb->removeObject(this); } else //if it gives something without battle { - giveContentsUpToExp(hero); + CRewardableObject::onHeroVisit(hero); } } } -void CGPandoraBox::heroLevelUpDone(const CGHeroInstance *hero) const -{ - giveContentsAfterExp(hero); -} - -void CGPandoraBox::afterSuccessfulVisit() const -{ - cb->removeAfterVisit(this); -} - void CGPandoraBox::serializeJsonOptions(JsonSerializeFormat & handler) { - CCreatureSet::serializeJson(handler, "guards", 7); + CRewardableObject::serializeJsonOptions(handler); handler.serializeString("guardMessage", message); - - handler.serializeInt("experience", gainedExp, 0); - handler.serializeInt("mana", manaDiff, 0); - handler.serializeInt("morale", moraleDiff, 0); - handler.serializeInt("luck", luckDiff, 0); - - resources.serializeJson(handler, "resources"); - + + if(!handler.saving) { - bool haveSkills = false; - - if(handler.saving) - { - for(int primskill : primskills) - if(primskill != 0) - haveSkills = true; - } - else - { - primskills.resize(GameConstants::PRIMARY_SKILLS,0); - haveSkills = true; - } - - if(haveSkills) + //backward compatibility + CCreatureSet::serializeJson(handler, "guards", 7); + + Rewardable::VisitInfo vinfo; + vinfo.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; + auto & reward = vinfo.reward; + int val; + handler.serializeInt("experience", reward.heroExperience, 0); + handler.serializeInt("mana", reward.manaDiff, 0); + handler.serializeInt("morale", val, 0); + if(val) + reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT, val, id); + handler.serializeInt("luck", val, 0); + if(val) + reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT, val, id); + + reward.resources.serializeJson(handler, "resources"); { auto s = handler.enterStruct("primarySkills"); - for(int idx = 0; idx < primskills.size(); idx ++) - handler.serializeInt(NPrimarySkill::names[idx], primskills[idx], 0); + for(int idx = 0; idx < reward.primary.size(); idx ++) + handler.serializeInt(NPrimarySkill::names[idx], reward.primary[idx], 0); } - } + + handler.serializeIdArray("artifacts", reward.artifacts); + handler.serializeIdArray("spells", reward.spells); - if(handler.saving) - { - if(!abilities.empty()) + handler.enterArray("creatures").serializeStruct(reward.creatures); + { auto s = handler.enterStruct("secondarySkills"); - - for(size_t idx = 0; idx < abilities.size(); idx++) + for(const auto & p : handler.getCurrent().Struct()) { - handler.serializeEnum(CSkillHandler::encodeSkill(abilities[idx]), abilityLevels[idx], NSecondarySkill::levels); + const std::string skillName = p.first; + const std::string levelId = p.second.String(); + + const int rawId = CSkillHandler::decodeSkill(skillName); + if(rawId < 0) + { + logGlobal->error("Invalid secondary skill %s", skillName); + continue; + } + + const int level = vstd::find_pos(NSecondarySkill::levels, levelId); + if(level < 0) + { + logGlobal->error("Invalid secondary skill level %s", levelId); + continue; + } + + reward.secondary[rawId] = level; } } + configuration.info.push_back(vinfo); } - else - { - auto s = handler.enterStruct("secondarySkills"); +} - const JsonNode & skillMap = handler.getCurrent(); - - abilities.clear(); - abilityLevels.clear(); - - for(const auto & p : skillMap.Struct()) - { - const std::string skillName = p.first; - const std::string levelId = p.second.String(); - - const int rawId = CSkillHandler::decodeSkill(skillName); - if(rawId < 0) - { - logGlobal->error("Invalid secondary skill %s", skillName); - continue; - } - - const int level = vstd::find_pos(NSecondarySkill::levels, levelId); - if(level < 0) - { - logGlobal->error("Invalid secondary skill level %s", levelId); - continue; - } - - abilities.emplace_back(rawId); - abilityLevels.push_back(level); - } - } - - - handler.serializeIdArray("artifacts", artifacts); - handler.serializeIdArray("spells", spells); - - creatures.serializeJson(handler, "creatures"); +void CGEvent::init() +{ + blockVisit = false; + + for(auto & i : configuration.info) + i.reward.removeObject = removeAfterVisit; } void CGEvent::onHeroVisit( const CGHeroInstance * h ) const @@ -470,27 +260,17 @@ void CGEvent::activated( const CGHeroInstance * h ) const } else { - giveContentsUpToExp(h); + CRewardableObject::onHeroVisit(h); } } -void CGEvent::afterSuccessfulVisit() const -{ - if(removeAfterVisit) - { - cb->removeAfterVisit(this); - } - else if(hasGuardians) - hasGuardians = false; -} - void CGEvent::serializeJsonOptions(JsonSerializeFormat & handler) { CGPandoraBox::serializeJsonOptions(handler); - handler.serializeBool("aIActivable", computerActivate, true, false, false); - handler.serializeBool("humanActivable", humanActivate, true, false, true); - handler.serializeBool("removeAfterVisit", removeAfterVisit, true, false, false); + handler.serializeBool("aIActivable", computerActivate, false); + handler.serializeBool("humanActivable", humanActivate, true); + handler.serializeBool("removeAfterVisit", removeAfterVisit, false); handler.serializeIdArray("availableFor", availableFor); } diff --git a/lib/mapObjects/CGPandoraBox.h b/lib/mapObjects/CGPandoraBox.h index 6cef83266..240e9e68d 100644 --- a/lib/mapObjects/CGPandoraBox.h +++ b/lib/mapObjects/CGPandoraBox.h @@ -9,63 +9,31 @@ */ #pragma once -#include "CArmedInstance.h" +#include "CRewardableObject.h" #include "../ResourceSet.h" VCMI_LIB_NAMESPACE_BEGIN struct InfoWindow; -class DLL_LINKAGE CGPandoraBox : public CArmedInstance +class DLL_LINKAGE CGPandoraBox : public CRewardableObject { public: std::string message; - mutable bool hasGuardians = false; //helper - after battle even though we have no stacks, allows us to know that there was battle - - //gained things: - ui32 gainedExp = 0; - si32 manaDiff = 0; //amount of gained / lost mana - si32 moraleDiff = 0; //morale modifier - si32 luckDiff = 0; //luck modifier - TResources resources;//gained / lost resources - std::vector primskills;//gained / lost prim skills - std::vector abilities; //gained abilities - std::vector abilityLevels; //levels of gained abilities - std::vector artifacts; //gained artifacts - std::vector spells; //gained spells - CCreatureSet creatures; //gained creatures void initObj(CRandomGenerator & rand) override; void onHeroVisit(const CGHeroInstance * h) const override; void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; - void heroLevelUpDone(const CGHeroInstance *hero) const override; template void serialize(Handler &h, const int version) { - h & static_cast(*this); + h & static_cast(*this); h & message; - h & hasGuardians; - h & gainedExp; - h & manaDiff; - h & moraleDiff; - h & luckDiff; - h & resources; - h & primskills; - h & abilities; - h & abilityLevels; - h & artifacts; - h & spells; - h & creatures; } protected: - void giveContentsUpToExp(const CGHeroInstance *h) const; - void giveContentsAfterExp(const CGHeroInstance *h) const; + virtual void init(); void serializeJsonOptions(JsonSerializeFormat & handler) override; -private: - void getText( InfoWindow &iw, bool &afterBattle, int val, int negative, int positive, const CGHeroInstance * h ) const; - void getText( InfoWindow &iw, bool &afterBattle, int text, const CGHeroInstance * h ) const; - virtual void afterSuccessfulVisit() const; }; class DLL_LINKAGE CGEvent : public CGPandoraBox //event objects @@ -87,10 +55,10 @@ public: void onHeroVisit(const CGHeroInstance * h) const override; protected: + void init() override; void serializeJsonOptions(JsonSerializeFormat & handler) override; private: void activated(const CGHeroInstance * h) const; - void afterSuccessfulVisit() const override; }; VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/CQuest.cpp b/lib/mapObjects/CQuest.cpp index 01d71bf19..3dae6194b 100644 --- a/lib/mapObjects/CQuest.cpp +++ b/lib/mapObjects/CQuest.cpp @@ -794,20 +794,11 @@ void CGSeerHut::afterAddToMap(CMap* map) void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler) { //quest and reward + CRewardableObject::serializeJsonOptions(handler); quest->serializeJson(handler, "quest"); - - if(handler.saving) + + if(!handler.saving) { - CRewardableObject::serializeJsonOptions(handler); - } - else - { - if(handler.getCurrent()["reward"].isNull() || handler.getCurrent()["reward"].Struct().empty()) - { - CRewardableObject::serializeJsonOptions(handler); - return; - } - //backward compatibility auto s = handler.enterStruct("reward"); const JsonNode & rewardsJson = handler.getCurrent(); @@ -827,7 +818,8 @@ void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler) int val = 0; handler.serializeInt(fullIdentifier, val); - Rewardable::Reward reward; + Rewardable::VisitInfo vinfo; + auto & reward = vinfo.reward; if(metaTypeName == "experience") reward.heroExperience = val; if(metaTypeName == "mana") @@ -867,9 +859,8 @@ void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler) reward.creatures.emplace_back(rawId, val); } - configuration.info.push_back({}); - configuration.info.back().reward = reward; - configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; + vinfo.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; + configuration.info.push_back(vinfo); } } @@ -881,14 +872,10 @@ void CGQuestGuard::init(CRandomGenerator & rand) configuration.info.push_back({}); configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; + configuration.info.back().reward.removeObject = true; configuration.canRefuse = true; } -void CGQuestGuard::completeQuest() const -{ - cb->removeObject(this); -} - void CGQuestGuard::serializeJsonOptions(JsonSerializeFormat & handler) { //quest only, do not call base class @@ -946,7 +933,6 @@ void CGKeymasterTent::onHeroVisit( const CGHeroInstance * h ) const void CGBorderGuard::initObj(CRandomGenerator & rand) { - //ui32 m13489val = subID; //store color as quest info blockVisit = true; } diff --git a/lib/mapObjects/CQuest.h b/lib/mapObjects/CQuest.h index 963f29499..7cb0df2bc 100644 --- a/lib/mapObjects/CQuest.h +++ b/lib/mapObjects/CQuest.h @@ -162,11 +162,8 @@ public: template void serialize(Handler &h, const int version) { - h & static_cast(*this); + h & static_cast(*this); h & static_cast(*this); - //h & rewardType; - //h & rID; - //h & rVal; h & seerName; } protected: @@ -181,7 +178,6 @@ class DLL_LINKAGE CGQuestGuard : public CGSeerHut { public: void init(CRandomGenerator & rand) override; - void completeQuest() const override; template void serialize(Handler &h, const int version) { diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp index 31c17e6e7..c07b6da44 100644 --- a/lib/mapObjects/CRewardableObject.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -271,7 +271,6 @@ void CRewardableObject::newTurn(CRandomGenerator & rand) const void CRewardableObject::initObj(CRandomGenerator & rand) { VLC->objtypeh->getHandlerFor(ID, subID)->configureObject(this, rand); - assert(!configuration.info.empty()); } CRewardableObject::CRewardableObject() diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index a3b4b16df..4d062659b 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -1019,34 +1019,46 @@ CGObjectInstance * CMapLoaderH3M::readPandora(const int3 & mapPosition) void CMapLoaderH3M::readBoxContent(CGPandoraBox * object, const int3 & mapPosition) { readMessageAndGuards(object->message, object, mapPosition); + Rewardable::VisitInfo vinfo; + auto & reward = vinfo.reward; + + reward.heroExperience = reader->readUInt32(); + reward.manaDiff = reader->readInt32(); + reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT, reader->readUInt8(), object->id); + reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT, reader->readUInt8(), object->id); - object->gainedExp = reader->readUInt32(); - object->manaDiff = reader->readInt32(); - object->moraleDiff = reader->readInt8(); - object->luckDiff = reader->readInt8(); - - reader->readResourses(object->resources); - - object->primskills.resize(GameConstants::PRIMARY_SKILLS); + reader->readResourses(reward.resources); for(int x = 0; x < GameConstants::PRIMARY_SKILLS; ++x) - object->primskills[x] = reader->readUInt8(); + reward.primary.at(x) = reader->readUInt8(); int gabn = reader->readUInt8(); //number of gained abilities for(int oo = 0; oo < gabn; ++oo) { - object->abilities.emplace_back(reader->readSkill()); - object->abilityLevels.push_back(reader->readUInt8()); + auto rId = reader->readSkill(); + auto rVal = reader->readUInt8(); + + reward.secondary[rId] = rVal; } int gart = reader->readUInt8(); //number of gained artifacts for(int oo = 0; oo < gart; ++oo) - object->artifacts.emplace_back(reader->readArtifact()); + reward.artifacts.push_back(reader->readArtifact()); int gspel = reader->readUInt8(); //number of gained spells for(int oo = 0; oo < gspel; ++oo) - object->spells.emplace_back(reader->readSpell()); + reward.spells.push_back(reader->readSpell()); int gcre = reader->readUInt8(); //number of gained creatures - readCreatureSet(&object->creatures, gcre); + for(int oo = 0; oo < gcre; ++oo) + { + auto rId = reader->readCreature(); + auto rVal = reader->readUInt16(); + + reward.creatures.emplace_back(rId, rVal); + } + + vinfo.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; + object->configuration.info.push_back(vinfo); + reader->skipZero(8); } @@ -1843,7 +1855,8 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position) if(hut->quest->missionType) { auto rewardType = reader->readUInt8(); - Rewardable::Reward reward; + Rewardable::VisitInfo vinfo; + auto & reward = vinfo.reward; switch(rewardType) { case 0: //NOTHING @@ -1922,9 +1935,8 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position) } } - hut->configuration.info.push_back({}); - hut->configuration.info.back().reward = reward; - hut->configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; + vinfo.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; + hut->configuration.info.push_back(vinfo); } else { diff --git a/lib/rmg/modificators/TreasurePlacer.cpp b/lib/rmg/modificators/TreasurePlacer.cpp index 0addaa481..c92637aea 100644 --- a/lib/rmg/modificators/TreasurePlacer.cpp +++ b/lib/rmg/modificators/TreasurePlacer.cpp @@ -235,7 +235,12 @@ void TreasurePlacer::addAllPossibleObjects() { auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0); auto * obj = dynamic_cast(factory->create()); - obj->resources[EGameResID::GOLD] = i * 5000; + + Rewardable::VisitInfo reward; + reward.reward.resources[EGameResID::GOLD] = i * 5000; + reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; + obj->configuration.info.push_back(reward); + return obj; }; oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType()); @@ -251,7 +256,12 @@ void TreasurePlacer::addAllPossibleObjects() { auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0); auto * obj = dynamic_cast(factory->create()); - obj->gainedExp = i * 5000; + + Rewardable::VisitInfo reward; + reward.reward.heroExperience = i * 5000; + reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; + obj->configuration.info.push_back(reward); + return obj; }; oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType()); @@ -307,8 +317,12 @@ void TreasurePlacer::addAllPossibleObjects() { auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0); auto * obj = dynamic_cast(factory->create()); - auto * stack = new CStackInstance(creature, creaturesAmount); - obj->creatures.putStack(SlotID(0), stack); + + Rewardable::VisitInfo reward; + reward.reward.creatures.emplace_back(creature, creaturesAmount); + reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; + obj->configuration.info.push_back(reward); + return obj; }; oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType()); @@ -333,10 +347,13 @@ void TreasurePlacer::addAllPossibleObjects() } RandomGeneratorUtil::randomShuffle(spells, zone.getRand()); + Rewardable::VisitInfo reward; for(int j = 0; j < std::min(12, static_cast(spells.size())); j++) { - obj->spells.push_back(spells[j]->id); + reward.reward.spells.push_back(spells[j]->id); } + reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; + obj->configuration.info.push_back(reward); return obj; }; @@ -362,10 +379,13 @@ void TreasurePlacer::addAllPossibleObjects() } RandomGeneratorUtil::randomShuffle(spells, zone.getRand()); + Rewardable::VisitInfo reward; for(int j = 0; j < std::min(15, static_cast(spells.size())); j++) { - obj->spells.push_back(spells[j]->id); + reward.reward.spells.push_back(spells[j]->id); } + reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; + obj->configuration.info.push_back(reward); return obj; }; @@ -390,10 +410,13 @@ void TreasurePlacer::addAllPossibleObjects() } RandomGeneratorUtil::randomShuffle(spells, zone.getRand()); + Rewardable::VisitInfo reward; for(int j = 0; j < std::min(60, static_cast(spells.size())); j++) { - obj->spells.push_back(spells[j]->id); + reward.reward.spells.push_back(spells[j]->id); } + reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; + obj->configuration.info.push_back(reward); return obj; }; @@ -442,11 +465,10 @@ void TreasurePlacer::addAllPossibleObjects() auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance); auto * obj = dynamic_cast(factory->create()); - Rewardable::Reward reward; - reward.creatures.emplace_back(creature->getId(), creaturesAmount); - obj->configuration.info.push_back({}); - obj->configuration.info.back().reward = reward; - obj->configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; + Rewardable::VisitInfo reward; + reward.reward.creatures.emplace_back(creature->getId(), creaturesAmount); + reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; + obj->configuration.info.push_back(reward); obj->quest->missionType = CQuest::MISSION_ART; @@ -494,11 +516,10 @@ void TreasurePlacer::addAllPossibleObjects() auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance); auto * obj = dynamic_cast(factory->create()); - Rewardable::Reward reward; - reward.heroExperience = generator.getConfig().questRewardValues[i]; - obj->configuration.info.push_back({}); - obj->configuration.info.back().reward = reward; - obj->configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; + Rewardable::VisitInfo reward; + reward.reward.heroExperience = generator.getConfig().questRewardValues[i]; + reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; + obj->configuration.info.push_back(reward); obj->quest->missionType = CQuest::MISSION_ART; ArtifactID artid = qap->drawRandomArtifact(); @@ -519,11 +540,10 @@ void TreasurePlacer::addAllPossibleObjects() auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance); auto * obj = dynamic_cast(factory->create()); - Rewardable::Reward reward; - reward.resources[EGameResID::GOLD] = generator.getConfig().questRewardValues[i]; - obj->configuration.info.push_back({}); - obj->configuration.info.back().reward = reward; - obj->configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; + Rewardable::VisitInfo reward; + reward.reward.resources[EGameResID::GOLD] = generator.getConfig().questRewardValues[i]; + reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; + obj->configuration.info.push_back(reward); obj->quest->missionType = CQuest::MISSION_ART; ArtifactID artid = qap->drawRandomArtifact(); From 68c61797f84e731c6d42eabf38caacd1650bbf9e Mon Sep 17 00:00:00 2001 From: nordsoft Date: Fri, 15 Sep 2023 21:08:14 +0200 Subject: [PATCH 06/18] Implement select all option for rewardable objects --- lib/mapObjects/CGPandoraBox.cpp | 40 +++++++++++++++++++++++++--- lib/mapObjects/CRewardableObject.cpp | 3 +++ lib/rewardable/Configuration.h | 3 ++- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/lib/mapObjects/CGPandoraBox.cpp b/lib/mapObjects/CGPandoraBox.cpp index d4787f306..fc87a763c 100644 --- a/lib/mapObjects/CGPandoraBox.cpp +++ b/lib/mapObjects/CGPandoraBox.cpp @@ -28,6 +28,7 @@ VCMI_LIB_NAMESPACE_BEGIN void CGPandoraBox::init() { blockVisit = true; + configuration.selectMode = Rewardable::SELECT_ALL; for(auto & i : configuration.info) i.reward.removeObject = true; @@ -169,33 +170,62 @@ void CGPandoraBox::serializeJsonOptions(JsonSerializeFormat & handler) { //backward compatibility CCreatureSet::serializeJson(handler, "guards", 7); + Rewardable::Reward reward; + + auto addReward = [this, &reward](bool condition) + { + if(condition) + { + configuration.info.emplace_back(); + configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; + configuration.info.back().reward = reward; + } + }; + + addReward(true); - Rewardable::VisitInfo vinfo; - vinfo.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; - auto & reward = vinfo.reward; int val; handler.serializeInt("experience", reward.heroExperience, 0); + addReward(reward.heroExperience); + handler.serializeInt("mana", reward.manaDiff, 0); + addReward(reward.manaDiff); + handler.serializeInt("morale", val, 0); if(val) reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT, val, id); + addReward(val); + handler.serializeInt("luck", val, 0); if(val) reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT, val, id); + addReward(val); reward.resources.serializeJson(handler, "resources"); + addReward(reward.resources.nonZero()); + { + bool updateReward = false; auto s = handler.enterStruct("primarySkills"); for(int idx = 0; idx < reward.primary.size(); idx ++) + { handler.serializeInt(NPrimarySkill::names[idx], reward.primary[idx], 0); + updateReward |= reward.primary[idx]; + } + addReward(updateReward); } handler.serializeIdArray("artifacts", reward.artifacts); + addReward(!reward.artifacts.empty()); + handler.serializeIdArray("spells", reward.spells); + addReward(!reward.spells.empty()); handler.enterArray("creatures").serializeStruct(reward.creatures); + addReward(!reward.creatures.empty()); { + bool updateReward = false; auto s = handler.enterStruct("secondarySkills"); for(const auto & p : handler.getCurrent().Struct()) { @@ -217,15 +247,17 @@ void CGPandoraBox::serializeJsonOptions(JsonSerializeFormat & handler) } reward.secondary[rawId] = level; + updateReward = true; } + addReward(updateReward); } - configuration.info.push_back(vinfo); } } void CGEvent::init() { blockVisit = false; + configuration.selectMode = Rewardable::SELECT_ALL; for(auto & i : configuration.info) i.reward.removeObject = removeAfterVisit; diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp index c07b6da44..e4e3932c8 100644 --- a/lib/mapObjects/CRewardableObject.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -108,6 +108,9 @@ void CRewardableObject::onHeroVisit(const CGHeroInstance *h) const case Rewardable::SELECT_RANDOM: // give random grantRewardWithMessage(*RandomGeneratorUtil::nextItem(rewards, cb->gameState()->getRandomGenerator()), true); break; + case Rewardable::SELECT_ALL: // give all rewards + for(auto i : rewards) + grantRewardWithMessage(i, i == rewards.size() - 1); } break; } diff --git a/lib/rewardable/Configuration.h b/lib/rewardable/Configuration.h index 72377da04..de8764f86 100644 --- a/lib/rewardable/Configuration.h +++ b/lib/rewardable/Configuration.h @@ -35,6 +35,7 @@ enum ESelectMode SELECT_FIRST, // first reward that matches limiters SELECT_PLAYER, // player can select from all allowed rewards SELECT_RANDOM, // one random reward from all mathing limiters + SELECT_ALL, // provides all allowed rewards matching limiters }; enum class EEventType @@ -45,7 +46,7 @@ enum class EEventType EVENT_NOT_AVAILABLE }; -const std::array SelectModeString{"selectFirst", "selectPlayer", "selectRandom"}; +const std::array SelectModeString{"selectFirst", "selectPlayer", "selectRandom", "selectAll"}; const std::array VisitModeString{"unlimited", "once", "hero", "bonus", "player"}; struct DLL_LINKAGE ResetInfo From 4af2d917c0439b97e97c15c08a842f7ed3f8a959 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Fri, 15 Sep 2023 21:13:07 +0200 Subject: [PATCH 07/18] Fix messages for pandoras --- lib/mapObjects/CGPandoraBox.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/mapObjects/CGPandoraBox.cpp b/lib/mapObjects/CGPandoraBox.cpp index fc87a763c..d6b9503be 100644 --- a/lib/mapObjects/CGPandoraBox.cpp +++ b/lib/mapObjects/CGPandoraBox.cpp @@ -45,6 +45,7 @@ void CGPandoraBox::onHeroVisit(const CGHeroInstance * h) const { auto setText = [](MetaString & text, int tId, const CGHeroInstance * h) { + text.clear(); text.appendLocalString(EMetaText::ADVOB_TXT, tId); text.replaceRawString(h->getNameTranslated()); }; @@ -179,6 +180,7 @@ void CGPandoraBox::serializeJsonOptions(JsonSerializeFormat & handler) configuration.info.emplace_back(); configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; configuration.info.back().reward = reward; + reward = Rewardable::Reward{}; } }; From 4f76eb3fce5c62d8be956f36a2d289fd4c249d4e Mon Sep 17 00:00:00 2001 From: nordsoft Date: Fri, 15 Sep 2023 23:00:33 +0200 Subject: [PATCH 08/18] Add limiter serialization --- lib/mapObjects/CGPandoraBox.cpp | 4 +- lib/mapObjects/CRewardableObject.cpp | 1 + lib/rewardable/Limiter.cpp | 76 ++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/lib/mapObjects/CGPandoraBox.cpp b/lib/mapObjects/CGPandoraBox.cpp index d6b9503be..1ddf2d997 100644 --- a/lib/mapObjects/CGPandoraBox.cpp +++ b/lib/mapObjects/CGPandoraBox.cpp @@ -45,7 +45,6 @@ void CGPandoraBox::onHeroVisit(const CGHeroInstance * h) const { auto setText = [](MetaString & text, int tId, const CGHeroInstance * h) { - text.clear(); text.appendLocalString(EMetaText::ADVOB_TXT, tId); text.replaceRawString(h->getNameTranslated()); }; @@ -124,7 +123,8 @@ void CGPandoraBox::onHeroVisit(const CGHeroInstance * h) const txt.replaceRawString(h->getNameTranslated()); } - const_cast(r.message) = txt; + if(r.message.empty()) + const_cast(r.message) = txt; } BlockingDialog bd (true, false); diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp index e4e3932c8..3b96dbe68 100644 --- a/lib/mapObjects/CRewardableObject.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -111,6 +111,7 @@ void CRewardableObject::onHeroVisit(const CGHeroInstance *h) const case Rewardable::SELECT_ALL: // give all rewards for(auto i : rewards) grantRewardWithMessage(i, i == rewards.size() - 1); + break; } break; } diff --git a/lib/rewardable/Limiter.cpp b/lib/rewardable/Limiter.cpp index ef2d1d5c5..9d837ccb8 100644 --- a/lib/rewardable/Limiter.cpp +++ b/lib/rewardable/Limiter.cpp @@ -15,6 +15,8 @@ #include "../CPlayerState.h" #include "../mapObjects/CGHeroInstance.h" #include "../serializer/JsonSerializeFormat.h" +#include "../constants/StringConstants.h" +#include "../CSkillHandler.h" VCMI_LIB_NAMESPACE_BEGIN @@ -122,7 +124,81 @@ bool Rewardable::Limiter::heroAllowed(const CGHeroInstance * hero) const void Rewardable::Limiter::serializeJson(JsonSerializeFormat & handler) { + handler.serializeInt("dayOfWeek", dayOfWeek); + handler.serializeInt("daysPassed", daysPassed); + resources.serializeJson(handler, "resources"); + handler.serializeInt("manaPercentage", manaPercentage); + handler.serializeInt("heroExperience", heroExperience); + handler.serializeInt("heroLevel", heroLevel); + handler.serializeInt("manaPoints", manaPoints); + handler.serializeIdArray("artifacts", artifacts); + handler.enterArray("creatures").serializeStruct(creatures); + { + auto a = handler.enterArray("primary"); + a.syncSize(primary); + for(int i = 0; i < primary.size(); ++i) + a.serializeInt(i, primary[i]); + } + { + auto a = handler.enterArray("secondary"); + std::vector> fieldValue; + if(handler.saving) + { + for(auto & i : secondary) + { + auto key = VLC->skillh->encodeSkill(i.first); + auto value = NSecondarySkill::levels.at(i.second); + fieldValue.emplace_back(key, value); + } + } + a.syncSize(fieldValue); + for(int i = 0; i < fieldValue.size(); ++i) + { + auto e = a.enterStruct(i); + e->serializeString("skill", fieldValue[i].first); + e->serializeString("level", fieldValue[i].second); + } + if(!handler.saving) + { + for(auto & i : fieldValue) + { + const int skillId = VLC->skillh->decodeSkill(i.first); + if(skillId < 0) + { + logGlobal->error("Invalid secondary skill %s", i.first); + continue; + } + + const int level = vstd::find_pos(NSecondarySkill::levels, i.second); + if(level < 0) + { + logGlobal->error("Invalid secondary skill level%s", i.second); + continue; + } + + secondary[SecondarySkill(skillId)] = level; + } + + } + } + + //sublimiters + auto serializeSublimitersList = [&handler](const std::string & field, LimitersList & container) + { + auto a = handler.enterArray(field); + a.syncSize(container); + for(int i = 0; i < container.size(); ++i) + { + if(!handler.saving) + container[i] = std::make_shared(); + auto e = a.enterStruct(i); + container[i]->serializeJson(handler); + } + }; + serializeSublimitersList("allOf", allOf); + serializeSublimitersList("anyOf", anyOf); + serializeSublimitersList("noneOf", noneOf); } VCMI_LIB_NAMESPACE_END From 991a755a1f57c382f6a2028d4ab68e072211479d Mon Sep 17 00:00:00 2001 From: nordsoft Date: Sat, 16 Sep 2023 05:34:27 +0200 Subject: [PATCH 09/18] Added new rewardable widget --- mapeditor/inspector/inspector.cpp | 24 +- mapeditor/inspector/inspector.h | 3 + mapeditor/inspector/rewardswidget.cpp | 889 +++++++++------ mapeditor/inspector/rewardswidget.h | 75 +- mapeditor/inspector/rewardswidget.ui | 1488 +++++++++++++++++++++++-- 5 files changed, 2016 insertions(+), 463 deletions(-) diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index 30e755caf..9955d207b 100644 --- a/mapeditor/inspector/inspector.cpp +++ b/mapeditor/inspector/inspector.cpp @@ -67,6 +67,7 @@ Initializer::Initializer(CGObjectInstance * o, const PlayerColor & pl) : default INIT_OBJ_TYPE(CGHeroInstance); INIT_OBJ_TYPE(CGSignBottle); INIT_OBJ_TYPE(CGLighthouse); + //INIT_OBJ_TYPE(CRewardableObject); //INIT_OBJ_TYPE(CGPandoraBox); //INIT_OBJ_TYPE(CGEvent); //INIT_OBJ_TYPE(CGSeerHut); @@ -375,14 +376,19 @@ void Inspector::updateProperties(CGCreature * o) //addProperty("Resources reward", o->resources); //TODO: implement in setProperty } +void Inspector::updateProperties(CRewardableObject * o) +{ + if(!o) return; + + auto * delegate = new RewardsDelegate(*map, *o); + addProperty("Reward", PropertyEditorPlaceholder(), delegate, false); +} + void Inspector::updateProperties(CGPandoraBox * o) { if(!o) return; addProperty("Message", o->message, new MessageDelegate, false); - - auto * delegate = new RewardsPandoraDelegate(*map, *o); - addProperty("Reward", PropertyEditorPlaceholder(), delegate, false); } void Inspector::updateProperties(CGEvent * o) @@ -413,11 +419,6 @@ void Inspector::updateProperties(CGSeerHut * o) auto * delegate = new QuestDelegate(*map, *o); addProperty("Quest", PropertyEditorPlaceholder(), delegate, false); } - - { //Reward - auto * delegate = new RewardsSeerhutDelegate(*map, *o); - addProperty("Reward", PropertyEditorPlaceholder(), delegate, false); - } } void Inspector::updateProperties() @@ -458,6 +459,7 @@ void Inspector::updateProperties() UPDATE_OBJ_PROPERTIES(CGHeroInstance); UPDATE_OBJ_PROPERTIES(CGSignBottle); UPDATE_OBJ_PROPERTIES(CGLighthouse); + UPDATE_OBJ_PROPERTIES(CRewardableObject); UPDATE_OBJ_PROPERTIES(CGPandoraBox); UPDATE_OBJ_PROPERTIES(CGEvent); UPDATE_OBJ_PROPERTIES(CGSeerHut); @@ -503,6 +505,7 @@ void Inspector::setProperty(const QString & key, const QVariant & value) SET_PROPERTIES(CGShipyard); SET_PROPERTIES(CGSignBottle); SET_PROPERTIES(CGLighthouse); + SET_PROPERTIES(CRewardableObject); SET_PROPERTIES(CGPandoraBox); SET_PROPERTIES(CGEvent); SET_PROPERTIES(CGSeerHut); @@ -518,6 +521,11 @@ void Inspector::setProperty(CGLighthouse * o, const QString & key, const QVarian if(!o) return; } +void Inspector::setProperty(CRewardableObject * o, const QString & key, const QVariant & value) +{ + if(!o) return; +} + void Inspector::setProperty(CGPandoraBox * o, const QString & key, const QVariant & value) { if(!o) return; diff --git a/mapeditor/inspector/inspector.h b/mapeditor/inspector/inspector.h index ad47282b7..1e44964ca 100644 --- a/mapeditor/inspector/inspector.h +++ b/mapeditor/inspector/inspector.h @@ -17,6 +17,7 @@ #include "../lib/GameConstants.h" #include "../lib/mapObjects/CGCreature.h" #include "../lib/mapObjects/MapObjects.h" +#include "../lib/mapObjects/CRewardableObject.h" #include "../lib/ResourceSet.h" #define DECLARE_OBJ_TYPE(x) void initialize(x*); @@ -45,6 +46,7 @@ public: DECLARE_OBJ_TYPE(CGCreature); DECLARE_OBJ_TYPE(CGSignBottle); DECLARE_OBJ_TYPE(CGLighthouse); + //DECLARE_OBJ_TYPE(CRewardableObject); //DECLARE_OBJ_TYPE(CGEvent); //DECLARE_OBJ_TYPE(CGPandoraBox); //DECLARE_OBJ_TYPE(CGSeerHut); @@ -73,6 +75,7 @@ protected: DECLARE_OBJ_PROPERTY_METHODS(CGCreature); DECLARE_OBJ_PROPERTY_METHODS(CGSignBottle); DECLARE_OBJ_PROPERTY_METHODS(CGLighthouse); + DECLARE_OBJ_PROPERTY_METHODS(CRewardableObject); DECLARE_OBJ_PROPERTY_METHODS(CGPandoraBox); DECLARE_OBJ_PROPERTY_METHODS(CGEvent); DECLARE_OBJ_PROPERTY_METHODS(CGSeerHut); diff --git a/mapeditor/inspector/rewardswidget.cpp b/mapeditor/inspector/rewardswidget.cpp index 79f3028d8..0d0d23136 100644 --- a/mapeditor/inspector/rewardswidget.cpp +++ b/mapeditor/inspector/rewardswidget.cpp @@ -17,31 +17,153 @@ #include "../lib/CCreatureHandler.h" #include "../lib/constants/StringConstants.h" #include "../lib/mapping/CMap.h" +#include "../lib/rewardable/Configuration.h" +#include "../lib/rewardable/Limiter.h" +#include "../lib/rewardable/Reward.h" +#include "../lib/mapObjects/CGPandoraBox.h" +#include "../lib/mapObjects/CQuest.h" -RewardsWidget::RewardsWidget(const CMap & m, CGPandoraBox & p, QWidget *parent) : +RewardsWidget::RewardsWidget(const CMap & m, CRewardableObject & p, QWidget *parent) : QDialog(parent), map(m), - pandora(&p), - seerhut(nullptr), + object(p), ui(new Ui::RewardsWidget) { ui->setupUi(this); - for(auto & type : rewardTypes) - ui->rewardType->addItem(QString::fromStdString(type)); -} - -RewardsWidget::RewardsWidget(const CMap & m, CGSeerHut & p, QWidget *parent) : - QDialog(parent), - map(m), - pandora(nullptr), - seerhut(&p), - ui(new Ui::RewardsWidget) -{ - ui->setupUi(this); + //fill core elements + for(const auto & s : Rewardable::VisitModeString) + ui->visitMode->addItem(QString::fromStdString(s)); - for(auto & type : rewardTypes) - ui->rewardType->addItem(QString::fromStdString(type)); + for(const auto & s : Rewardable::SelectModeString) + ui->selectMode->addItem(QString::fromStdString(s)); + + for(const std::string & s : {"AUTO", "MODAL", "INFO"}) + ui->windowMode->addItem(QString::fromStdString(s)); + + ui->lDayOfWeek->addItem(tr("None")); + for(int i = 1; i <= 7; ++i) + ui->lDayOfWeek->addItem(tr("Day %1").arg(i)); + + //fill resources + ui->rResources->setRowCount(GameConstants::RESOURCE_QUANTITY - 1); + ui->lResources->setRowCount(GameConstants::RESOURCE_QUANTITY - 1); + for(int i = 0; i < GameConstants::RESOURCE_QUANTITY - 1; ++i) + { + for(auto * w : {ui->rResources, ui->lResources}) + { + auto * item = new QTableWidgetItem(QString::fromStdString(GameConstants::RESOURCE_NAMES[i])); + item->setData(Qt::UserRole, QVariant::fromValue(i)); + w->setItem(i, 0, item); + w->setCellWidget(i, 1, new QSpinBox); + } + } + + //fill artifacts + for(int i = 0; i < map.allowedArtifact.size(); ++i) + { + for(auto * w : {ui->rArtifacts, ui->lArtifacts}) + { + auto * item = new QListWidgetItem(QString::fromStdString(VLC->artifacts()->getByIndex(i)->getNameTranslated())); + item->setData(Qt::UserRole, QVariant::fromValue(i)); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(Qt::Unchecked); + if(!map.allowedArtifact[i]) + item->setFlags(item->flags() & ~Qt::ItemIsEnabled); + w->addItem(item); + } + } + + //fill spells + for(int i = 0; i < map.allowedSpells.size(); ++i) + { + for(auto * w : {ui->rSpells, ui->lSpells}) + { + auto * item = new QListWidgetItem(QString::fromStdString(VLC->spells()->getByIndex(i)->getNameTranslated())); + item->setData(Qt::UserRole, QVariant::fromValue(i)); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(Qt::Unchecked); + if(!map.allowedSpells[i]) + item->setFlags(item->flags() & ~Qt::ItemIsEnabled); + w->addItem(item); + } + + //spell cast + if(VLC->spells()->getByIndex(i)->isAdventure()) + { + ui->castSpell->addItem(QString::fromStdString(VLC->spells()->getByIndex(i)->getNameTranslated())); + ui->castSpell->setItemData(ui->castSpell->count() - 1, QVariant::fromValue(i)); + } + } + + //fill skills + ui->rSkills->setRowCount(map.allowedAbilities.size()); + ui->lSkills->setRowCount(map.allowedAbilities.size()); + for(int i = 0; i < map.allowedAbilities.size(); ++i) + { + for(auto * w : {ui->rSkills, ui->lSkills}) + { + auto * item = new QTableWidgetItem(QString::fromStdString(VLC->skills()->getByIndex(i)->getNameTranslated())); + item->setData(Qt::UserRole, QVariant::fromValue(i)); + + auto * widget = new QComboBox; + for(auto & s : NSecondarySkill::levels) + widget->addItem(QString::fromStdString(s)); + + if(!map.allowedAbilities[i]) + { + item->setFlags(item->flags() & ~Qt::ItemIsEnabled); + widget->setEnabled(false); + } + + w->setItem(i, 0, item); + w->setCellWidget(i, 1, widget); + } + } + + //fill creatures + for(auto & creature : VLC->creh->objects) + { + for(auto * w : {ui->rCreatureId, ui->lCreatureId}) + { + w->addItem(QString::fromStdString(creature->getNameSingularTranslated())); + w->setItemData(w->count() - 1, creature->getIndex()); + } + } + + //fill spell cast + for(auto & s : NSecondarySkill::levels) + ui->castLevel->addItem(QString::fromStdString(s)); + on_castSpellCheck_toggled(false); + + //fill bonuses + for(auto & s : bonusDurationMap) + ui->bonusDuration->addItem(QString::fromStdString(s.first)); + for(auto & s : bonusNameMap) + ui->bonusType->addItem(QString::fromStdString(s.first)); + + //set default values + if(dynamic_cast(&object)) + { + ui->visitMode->setCurrentIndex(vstd::find_pos(Rewardable::VisitModeString, "once")); + ui->visitMode->setEnabled(false); + ui->selectMode->setCurrentIndex(vstd::find_pos(Rewardable::SelectModeString, "selectAll")); + ui->selectMode->setEnabled(false); + ui->windowMode->setEnabled(false); + ui->canRefuse->setEnabled(false); + } + + if(dynamic_cast(&object)) + { + ui->visitMode->setCurrentIndex(vstd::find_pos(Rewardable::VisitModeString, "once")); + ui->visitMode->setEnabled(false); + ui->windowMode->setEnabled(false); + ui->canRefuse->setChecked(true); + ui->canRefuse->setEnabled(false); + } + + //hide elements + ui->eventInfoGroup->hide(); } RewardsWidget::~RewardsWidget() @@ -49,364 +171,440 @@ RewardsWidget::~RewardsWidget() delete ui; } -QList RewardsWidget::getListForType(RewardType typeId) -{ - assert(typeId < rewardTypes.size()); - QList result; - - switch (typeId) { - case RewardType::RESOURCE: - //to convert string to index WOOD = 0, MERCURY, ORE, SULFUR, CRYSTAL, GEMS, GOLD, MITHRIL, - result.append("Wood"); - result.append("Mercury"); - result.append("Ore"); - result.append("Sulfur"); - result.append("Crystals"); - result.append("Gems"); - result.append("Gold"); - break; - - case RewardType::PRIMARY_SKILL: - for(auto s : NPrimarySkill::names) - result.append(QString::fromStdString(s)); - break; - - case RewardType::SECONDARY_SKILL: - for(int i = 0; i < map.allowedAbilities.size(); ++i) - { - if(map.allowedAbilities[i]) - result.append(QString::fromStdString(VLC->skills()->getByIndex(i)->getNameTranslated())); - } - break; - - case RewardType::ARTIFACT: - for(int i = 0; i < map.allowedArtifact.size(); ++i) - { - if(map.allowedArtifact[i]) - result.append(QString::fromStdString(VLC->artifacts()->getByIndex(i)->getNameTranslated())); - } - break; - - case RewardType::SPELL: - for(int i = 0; i < map.allowedSpells.size(); ++i) - { - if(map.allowedSpells[i]) - result.append(QString::fromStdString(VLC->spells()->getByIndex(i)->getNameTranslated())); - } - break; - - case RewardType::CREATURE: - for(auto creature : VLC->creh->objects) - { - result.append(QString::fromStdString(creature->getNameSingularTranslated())); - } - break; - } - return result; -} - -void RewardsWidget::on_rewardType_activated(int index) -{ - ui->rewardList->clear(); - ui->rewardList->setEnabled(true); - assert(index < rewardTypes.size()); - - auto l = getListForType(RewardType(index)); - if(l.empty()) - ui->rewardList->setEnabled(false); - - for(auto & s : l) - ui->rewardList->addItem(s); -} void RewardsWidget::obtainData() { - if(pandora) - { - if(pandora->gainedExp > 0) - addReward(RewardType::EXPERIENCE, 0, pandora->gainedExp); - if(pandora->manaDiff) - addReward(RewardType::MANA, 0, pandora->manaDiff); - if(pandora->moraleDiff) - addReward(RewardType::MORALE, 0, pandora->moraleDiff); - if(pandora->luckDiff) - addReward(RewardType::LUCK, 0, pandora->luckDiff); - if(pandora->resources.nonZero()) - { - for(ResourceSet::nziterator resiter(pandora->resources); resiter.valid(); ++resiter) - addReward(RewardType::RESOURCE, resiter->resType, resiter->resVal); - } - for(int idx = 0; idx < pandora->primskills.size(); ++idx) - { - if(pandora->primskills[idx]) - addReward(RewardType::PRIMARY_SKILL, idx, pandora->primskills[idx]); - } - assert(pandora->abilities.size() == pandora->abilityLevels.size()); - for(int idx = 0; idx < pandora->abilities.size(); ++idx) - { - addReward(RewardType::SECONDARY_SKILL, pandora->abilities[idx].getNum(), pandora->abilityLevels[idx]); - } - for(auto art : pandora->artifacts) - { - addReward(RewardType::ARTIFACT, art.getNum(), 1); - } - for(auto spell : pandora->spells) - { - addReward(RewardType::SPELL, spell.getNum(), 1); - } - for(int i = 0; i < pandora->creatures.Slots().size(); ++i) - { - if(auto c = pandora->creatures.getCreature(SlotID(i))) - addReward(RewardType::CREATURE, c->getId(), pandora->creatures.getStackCount(SlotID(i))); - } - } + //common parameters + ui->visitMode->setCurrentIndex(object.configuration.visitMode); + ui->selectMode->setCurrentIndex(object.configuration.selectMode); + ui->windowMode->setCurrentIndex(int(object.configuration.infoWindowType)); + ui->onSelectText->setText(QString::fromStdString(object.configuration.onSelect.toString())); + ui->canRefuse->setChecked(object.configuration.canRefuse); - if(seerhut) - { - for(auto & i : seerhut->configuration.info) - { - if(i.reward.heroExperience) - addReward(RewardType::EXPERIENCE, 0, i.reward.heroExperience); - if(i.reward.manaDiff) - addReward(RewardType::MANA, 0, i.reward.manaDiff); - for(auto & a : i.reward.artifacts) - addReward(RewardType::ARTIFACT, a.getNum(), 0); - for(auto & a : i.reward.creatures) - addReward(RewardType::CREATURE, a.getType()->getId().getNum(), a.getCount()); - } - /*switch(seerhut->rewardType) - { - case CGSeerHut::ERewardType::EXPERIENCE: - - break; - - case CGSeerHut::ERewardType::MANA_POINTS: - addReward(RewardType::MANA, 0, seerhut->rVal); - break; - - case CGSeerHut::ERewardType::MORALE_BONUS: - addReward(RewardType::MORALE, 0, seerhut->rVal); - break; - - case CGSeerHut::ERewardType::LUCK_BONUS: - addReward(RewardType::LUCK, 0, seerhut->rVal); - break; - - case CGSeerHut::ERewardType::RESOURCES: - - break; - - case CGSeerHut::ERewardType::PRIMARY_SKILL: - addReward(RewardType::PRIMARY_SKILL, seerhut->rID, seerhut->rVal); - break; - - case CGSeerHut::ERewardType::SECONDARY_SKILL: - addReward(RewardType::SECONDARY_SKILL, seerhut->rID, seerhut->rVal); - break; - - case CGSeerHut::ERewardType::ARTIFACT: - addReward(RewardType::ARTIFACT, seerhut->rID, seerhut->rVal); - break; - - case CGSeerHut::ERewardType::SPELL: - addReward(RewardType::SPELL, seerhut->rID, seerhut->rVal); - break; - - case CGSeerHut::ERewardType::CREATURE: - addReward(RewardType::CREATURE, seerhut->rID, seerhut->rVal); - break; - - default: - break; - }*/ - } + //reset parameters + ui->resetPeriod->setValue(object.configuration.resetParameters.period); + ui->resetVisitors->setChecked(object.configuration.resetParameters.visitors); + ui->resetRewards->setChecked(object.configuration.resetParameters.rewards); + + ui->visitInfoList->clear(); + + for([[maybe_unused]] auto & a : object.configuration.info) + ui->visitInfoList->addItem(tr("Reward %1").arg(ui->visitInfoList->count() + 1)); + + if(ui->visitInfoList->currentItem()) + loadCurrentVisitInfo(ui->visitInfoList->currentRow()); } bool RewardsWidget::commitChanges() { - bool haveRewards = false; - if(pandora) + //common parameters + object.configuration.visitMode = ui->visitMode->currentIndex(); + object.configuration.selectMode = ui->selectMode->currentIndex(); + object.configuration.infoWindowType = EInfoWindowMode(ui->windowMode->currentIndex()); + object.configuration.onSelect = MetaString::createFromRawString(ui->onSelectText->text().toStdString()); + object.configuration.canRefuse = ui->canRefuse->isChecked(); + + //reset parameters + object.configuration.resetParameters.period = ui->resetPeriod->value(); + object.configuration.resetParameters.visitors = ui->resetVisitors->isChecked(); + object.configuration.resetParameters.rewards = ui->resetRewards->isChecked(); + + if(ui->visitInfoList->currentItem()) + saveCurrentVisitInfo(ui->visitInfoList->currentRow()); + + return true; +} + +void RewardsWidget::saveCurrentVisitInfo(int index) +{ + auto & vinfo = object.configuration.info.at(index); + vinfo.message = MetaString::createFromRawString(ui->rewardMessage->text().toStdString()); + + vinfo.reward.heroLevel = ui->rHeroLevel->value(); + vinfo.reward.heroExperience = ui->rHeroExperience->value(); + vinfo.reward.manaDiff = ui->rManaDiff->value(); + vinfo.reward.manaPercentage = ui->rManaPercentage->value(); + vinfo.reward.manaOverflowFactor = ui->rOverflowFactor->value(); + vinfo.reward.movePoints = ui->rMovePoints->value(); + vinfo.reward.movePercentage = ui->rMovePercentage->value(); + vinfo.reward.removeObject = ui->removeObject->isChecked(); + vinfo.reward.primary[0] = ui->rAttack->value(); + vinfo.reward.primary[1] = ui->rDefence->value(); + vinfo.reward.primary[2] = ui->rPower->value(); + vinfo.reward.primary[3] = ui->rKnowledge->value(); + for(int i = 0; i < ui->rResources->rowCount(); ++i) { - pandora->abilities.clear(); - pandora->abilityLevels.clear(); - pandora->primskills.resize(GameConstants::PRIMARY_SKILLS, 0); - pandora->resources = ResourceSet(); - pandora->artifacts.clear(); - pandora->spells.clear(); - pandora->creatures.clearSlots(); - - for(int row = 0; row < rewards; ++row) + if(auto * widget = qobject_cast(ui->rResources->cellWidget(i, 1))) + vinfo.reward.resources[i] = widget->value(); + } + + vinfo.reward.artifacts.clear(); + for(int i = 0; i < ui->rArtifacts->count(); ++i) + { + if(ui->rArtifacts->item(i)->checkState() == Qt::Checked) + vinfo.reward.artifacts.push_back(VLC->artifacts()->getByIndex(i)->getId()); + } + vinfo.reward.spells.clear(); + for(int i = 0; i < ui->rSpells->count(); ++i) + { + if(ui->rSpells->item(i)->checkState() == Qt::Checked) + vinfo.reward.spells.push_back(VLC->spells()->getByIndex(i)->getId()); + } + + vinfo.reward.secondary.clear(); + for(int i = 0; i < ui->rSkills->rowCount(); ++i) + { + if(auto * widget = qobject_cast(ui->rSkills->cellWidget(i, 1))) { - haveRewards = true; - int typeId = ui->rewardsTable->item(row, 0)->data(Qt::UserRole).toInt(); - int listId = ui->rewardsTable->item(row, 1) ? ui->rewardsTable->item(row, 1)->data(Qt::UserRole).toInt() : 0; - int amount = ui->rewardsTable->item(row, 2)->data(Qt::UserRole).toInt(); - switch(typeId) + if(widget->currentIndex() > 0) + vinfo.reward.secondary[VLC->skills()->getByIndex(i)->getId()] = widget->currentIndex(); + } + } + + vinfo.reward.creatures.clear(); + for(int i = 0; i < ui->rCreatures->rowCount(); ++i) + { + int index = ui->rCreatures->item(i, 0)->data(Qt::UserRole).toInt(); + if(auto * widget = qobject_cast(ui->rCreatures->cellWidget(i, 1))) + if(widget->value()) + vinfo.reward.creatures.emplace_back(VLC->creatures()->getByIndex(index)->getId(), widget->value()); + } + + vinfo.reward.spellCast.first = SpellID::NONE; + if(ui->castSpellCheck->isChecked()) + { + vinfo.reward.spellCast.first = VLC->spells()->getByIndex(ui->castSpell->itemData(ui->castSpell->currentIndex()).toInt())->getId(); + vinfo.reward.spellCast.second = ui->castLevel->currentIndex(); + } + + vinfo.reward.bonuses.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, val, object.id); + } + + vinfo.limiter.dayOfWeek = ui->lDayOfWeek->currentIndex(); + vinfo.limiter.daysPassed = ui->lDaysPassed->value(); + vinfo.limiter.heroLevel = ui->lHeroLevel->value(); + vinfo.limiter.heroExperience = ui->lHeroExperience->value(); + vinfo.limiter.manaPoints = ui->lManaPoints->value(); + vinfo.limiter.manaPercentage = ui->lManaPercentage->value(); + vinfo.limiter.primary[0] = ui->lAttack->value(); + vinfo.limiter.primary[1] = ui->lDefence->value(); + vinfo.limiter.primary[2] = ui->lPower->value(); + vinfo.limiter.primary[3] = ui->lKnowledge->value(); + for(int i = 0; i < ui->lResources->rowCount(); ++i) + { + if(auto * widget = qobject_cast(ui->lResources->cellWidget(i, 1))) + vinfo.limiter.resources[i] = widget->value(); + } + + vinfo.limiter.artifacts.clear(); + for(int i = 0; i < ui->lArtifacts->count(); ++i) + { + if(ui->lArtifacts->item(i)->checkState() == Qt::Checked) + vinfo.limiter.artifacts.push_back(VLC->artifacts()->getByIndex(i)->getId()); + } + vinfo.limiter.spells.clear(); + for(int i = 0; i < ui->lSpells->count(); ++i) + { + if(ui->lSpells->item(i)->checkState() == Qt::Checked) + vinfo.limiter.spells.push_back(VLC->spells()->getByIndex(i)->getId()); + } + + vinfo.limiter.secondary.clear(); + for(int i = 0; i < ui->lSkills->rowCount(); ++i) + { + if(auto * widget = qobject_cast(ui->lSkills->cellWidget(i, 1))) + { + if(widget->currentIndex() > 0) + vinfo.limiter.secondary[VLC->skills()->getByIndex(i)->getId()] = widget->currentIndex(); + } + } + + vinfo.limiter.creatures.clear(); + for(int i = 0; i < ui->lCreatures->rowCount(); ++i) + { + int index = ui->lCreatures->item(i, 0)->data(Qt::UserRole).toInt(); + if(auto * widget = qobject_cast(ui->lCreatures->cellWidget(i, 1))) + if(widget->value()) + vinfo.reward.creatures.emplace_back(VLC->creatures()->getByIndex(index)->getId(), widget->value()); + } +} + +void RewardsWidget::loadCurrentVisitInfo(int index) +{ + for(auto * w : {ui->rArtifacts, ui->rSpells, ui->lArtifacts, ui->lSpells}) + for(int i = 0; i < w->count(); ++i) + w->item(i)->setCheckState(Qt::Unchecked); + + for(auto * w : {ui->rSkills, ui->lSkills}) + for(int i = 0; i < w->rowCount(); ++i) + if(auto * widget = qobject_cast(ui->rSkills->cellWidget(i, 1))) + widget->setCurrentIndex(0); + + ui->rCreatures->setRowCount(0); + ui->lCreatures->setRowCount(0); + ui->bonuses->setRowCount(0); + + const auto & vinfo = object.configuration.info.at(index); + ui->rewardMessage->setText(QString::fromStdString(vinfo.message.toString())); + + ui->rHeroLevel->setValue(vinfo.reward.heroLevel); + ui->rHeroExperience->setValue(vinfo.reward.heroExperience); + ui->rManaDiff->setValue(vinfo.reward.manaDiff); + ui->rManaPercentage->setValue(vinfo.reward.manaPercentage); + ui->rOverflowFactor->setValue(vinfo.reward.manaOverflowFactor); + ui->rMovePoints->setValue(vinfo.reward.movePoints); + ui->rMovePercentage->setValue(vinfo.reward.movePercentage); + ui->removeObject->setChecked(vinfo.reward.removeObject); + ui->rAttack->setValue(vinfo.reward.primary[0]); + ui->rDefence->setValue(vinfo.reward.primary[1]); + ui->rPower->setValue(vinfo.reward.primary[2]); + ui->rKnowledge->setValue(vinfo.reward.primary[3]); + for(int i = 0; i < ui->rResources->rowCount(); ++i) + { + if(auto * widget = qobject_cast(ui->rResources->cellWidget(i, 1))) + widget->setValue(vinfo.reward.resources[i]); + } + + for(auto i : vinfo.reward.artifacts) + ui->rArtifacts->item(VLC->artifacts()->getById(i)->getIndex())->setCheckState(Qt::Checked); + for(auto i : vinfo.reward.spells) + ui->rArtifacts->item(VLC->spells()->getById(i)->getIndex())->setCheckState(Qt::Checked); + for(auto & i : vinfo.reward.secondary) + { + int index = VLC->skills()->getById(i.first)->getIndex(); + if(auto * widget = qobject_cast(ui->rSkills->cellWidget(index, 1))) + widget->setCurrentIndex(i.second); + } + for(auto & i : vinfo.reward.creatures) + { + int index = i.type->getIndex(); + ui->rCreatureId->setCurrentIndex(index); + ui->rCreatureAmount->setValue(i.count); + onCreatureAdd(ui->rCreatures, ui->rCreatureId, ui->rCreatureAmount); + } + + ui->castSpellCheck->setChecked(vinfo.reward.spellCast.first != SpellID::NONE); + if(ui->castSpellCheck->isChecked()) + { + int index = VLC->spells()->getById(vinfo.reward.spellCast.first)->getIndex(); + ui->castSpell->setCurrentIndex(index); + ui->castLevel->setCurrentIndex(vinfo.reward.spellCast.second); + } + + for(auto & i : vinfo.reward.bonuses) + { + auto dur = vstd::findKey(bonusDurationMap, i.duration); + for(int i = 0; i < ui->bonusDuration->count(); ++i) + { + if(ui->bonusDuration->itemText(i) == QString::fromStdString(dur)) { - case RewardType::EXPERIENCE: - pandora->gainedExp = amount; - break; - - case RewardType::MANA: - pandora->manaDiff = amount; - break; - - case RewardType::MORALE: - pandora->moraleDiff = amount; - break; - - case RewardType::LUCK: - pandora->luckDiff = amount; - break; - - case RewardType::RESOURCE: - pandora->resources[listId] = amount; - break; - - case RewardType::PRIMARY_SKILL: - pandora->primskills[listId] = amount; - break; - - case RewardType::SECONDARY_SKILL: - pandora->abilities.push_back(SecondarySkill(listId)); - pandora->abilityLevels.push_back(amount); - break; - - case RewardType::ARTIFACT: - pandora->artifacts.push_back(ArtifactID(listId)); - break; - - case RewardType::SPELL: - pandora->spells.push_back(SpellID(listId)); - break; - - case RewardType::CREATURE: - auto slot = pandora->creatures.getFreeSlot(); - if(slot != SlotID() && amount > 0) - pandora->creatures.addToSlot(slot, CreatureID(listId), amount); - break; + ui->bonusDuration->setCurrentIndex(i); + break; } } - } - if(seerhut) - { - seerhut->configuration.info.clear(); - for(int row = 0; row < rewards; ++row) + + auto typ = vstd::findKey(bonusNameMap, i.type); + for(int i = 0; i < ui->bonusType->count(); ++i) { - seerhut->configuration.info.emplace_back(); - seerhut->configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; - Rewardable::Reward & reward = seerhut->configuration.info.back().reward; - haveRewards = true; - int typeId = ui->rewardsTable->item(row, 0)->data(Qt::UserRole).toInt(); - int listId = ui->rewardsTable->item(row, 1) ? ui->rewardsTable->item(row, 1)->data(Qt::UserRole).toInt() : 0; - int amount = ui->rewardsTable->item(row, 2)->data(Qt::UserRole).toInt(); - switch (typeId) { - case RewardType::EXPERIENCE: - reward.heroExperience = amount; - break; - - case RewardType::ARTIFACT: - reward.artifacts.push_back(listId); - break; - - case RewardType::CREATURE: - reward.creatures.emplace_back(listId, amount); - - default: - break; + if(ui->bonusType->itemText(i) == QString::fromStdString(typ)) + { + ui->bonusType->setCurrentIndex(i); + break; + } + } + + ui->bonusValue->setValue(i.val); + on_bonusAdd_clicked(); + } + + ui->lDayOfWeek->setCurrentIndex(vinfo.limiter.dayOfWeek); + ui->lDaysPassed->setValue(vinfo.limiter.daysPassed); + ui->lHeroLevel->setValue(vinfo.limiter.heroLevel); + ui->lHeroExperience->setValue(vinfo.limiter.heroExperience); + ui->lManaPoints->setValue(vinfo.limiter.manaPoints); + ui->lManaPercentage->setValue(vinfo.limiter.manaPercentage); + ui->lAttack->setValue(vinfo.reward.primary[0]); + ui->lDefence->setValue(vinfo.reward.primary[1]); + ui->lPower->setValue(vinfo.reward.primary[2]); + ui->lKnowledge->setValue(vinfo.reward.primary[3]); + for(int i = 0; i < ui->lResources->rowCount(); ++i) + { + if(auto * widget = qobject_cast(ui->lResources->cellWidget(i, 1))) + widget->setValue(vinfo.limiter.resources[i]); + } + + for(auto i : vinfo.limiter.artifacts) + ui->lArtifacts->item(VLC->artifacts()->getById(i)->getIndex())->setCheckState(Qt::Checked); + for(auto i : vinfo.limiter.spells) + ui->lArtifacts->item(VLC->spells()->getById(i)->getIndex())->setCheckState(Qt::Checked); + for(auto & i : vinfo.limiter.secondary) + { + int index = VLC->skills()->getById(i.first)->getIndex(); + if(auto * widget = qobject_cast(ui->lSkills->cellWidget(index, 1))) + widget->setCurrentIndex(i.second); + } + for(auto & i : vinfo.limiter.creatures) + { + int index = i.type->getIndex(); + ui->lCreatureId->setCurrentIndex(index); + ui->lCreatureAmount->setValue(i.count); + onCreatureAdd(ui->lCreatures, ui->lCreatureId, ui->lCreatureAmount); + } +} + +void RewardsWidget::onCreatureAdd(QTableWidget * listWidget, QComboBox * comboWidget, QSpinBox * spinWidget) +{ + QTableWidgetItem * item = nullptr; + QSpinBox * widget = nullptr; + for(int i = 0; i < listWidget->rowCount(); ++i) + { + if(auto * cname = listWidget->item(i, 0)) + { + if(cname->data(Qt::UserRole).toInt() == comboWidget->currentData().toInt()) + { + item = cname; + widget = qobject_cast(listWidget->cellWidget(i, 1)); + break; } } } - return haveRewards; + + if(!item) + { + listWidget->setRowCount(listWidget->rowCount() + 1); + item = new QTableWidgetItem(comboWidget->currentText()); + listWidget->setItem(listWidget->rowCount() - 1, 0, item); + } + + item->setData(Qt::UserRole, comboWidget->currentData()); + + if(!widget) + { + widget = new QSpinBox; + widget->setRange(spinWidget->minimum(), spinWidget->maximum()); + listWidget->setCellWidget(listWidget->rowCount() - 1, 1, widget); + } + + widget->setValue(spinWidget->value()); } -void RewardsWidget::on_rewardList_activated(int index) +void RewardsWidget::on_addVisitInfo_clicked() { - ui->rewardAmount->setText(QStringLiteral("1")); + ui->visitInfoList->addItem(tr("Reward %1").arg(ui->visitInfoList->count() + 1)); + object.configuration.info.emplace_back(); } -void RewardsWidget::addReward(RewardsWidget::RewardType typeId, int listId, int amount) + +void RewardsWidget::on_removeVisitInfo_clicked() { - //for seerhut there could be the only one reward - if(!pandora && seerhut && rewards) + int index = ui->visitInfoList->currentRow(); + object.configuration.info.erase(std::next(object.configuration.info.begin(), index)); + ui->visitInfoList->blockSignals(true); + delete ui->visitInfoList->currentItem(); + ui->visitInfoList->blockSignals(false); + on_visitInfoList_itemSelectionChanged(); +} + +void RewardsWidget::on_selectMode_currentIndexChanged(int index) +{ + ui->onSelectText->setEnabled(index == vstd::find_pos(Rewardable::SelectModeString, "selectPlayer")); +} + +void RewardsWidget::on_resetPeriod_valueChanged(int arg1) +{ + ui->resetRewards->setEnabled(arg1); + ui->resetVisitors->setEnabled(arg1); +} + + +void RewardsWidget::on_visitInfoList_itemSelectionChanged() +{ + if(ui->visitInfoList->currentItem() == nullptr) + { + ui->eventInfoGroup->hide(); return; - - ui->rewardsTable->setRowCount(++rewards); - - auto itemType = new QTableWidgetItem(QString::fromStdString(rewardTypes[typeId])); - itemType->setData(Qt::UserRole, typeId); - ui->rewardsTable->setItem(rewards - 1, 0, itemType); - - auto l = getListForType(typeId); - if(!l.empty()) - { - auto itemCurr = new QTableWidgetItem(getListForType(typeId)[listId]); - itemCurr->setData(Qt::UserRole, listId); - ui->rewardsTable->setItem(rewards - 1, 1, itemCurr); } - QString am = QString::number(amount); - switch(ui->rewardType->currentIndex()) - { - case 6: - if(amount <= 1) - am = "Basic"; - if(amount == 2) - am = "Advanced"; - if(amount >= 3) - am = "Expert"; - break; - - case 7: - case 8: - am = ""; - amount = 1; - break; - } - auto itemCount = new QTableWidgetItem(am); - itemCount->setData(Qt::UserRole, amount); - ui->rewardsTable->setItem(rewards - 1, 2, itemCount); + ui->eventInfoGroup->show(); + loadCurrentVisitInfo(ui->visitInfoList->currentRow()); +} + +void RewardsWidget::on_visitInfoList_currentItemChanged(QListWidgetItem * current, QListWidgetItem * previous) +{ + if(previous) + saveCurrentVisitInfo(ui->visitInfoList->row(previous)); } -void RewardsWidget::on_buttonAdd_clicked() +void RewardsWidget::on_rCreatureAdd_clicked() { - addReward(RewardType(ui->rewardType->currentIndex()), ui->rewardList->currentIndex(), ui->rewardAmount->text().toInt()); + onCreatureAdd(ui->rCreatures, ui->rCreatureId, ui->rCreatureAmount); } -void RewardsWidget::on_buttonRemove_clicked() +void RewardsWidget::on_rCreatureRemove_clicked() { - auto currentRow = ui->rewardsTable->currentRow(); - if(currentRow != -1) - { - ui->rewardsTable->removeRow(currentRow); - --rewards; - } -} - - -void RewardsWidget::on_buttonClear_clicked() -{ - ui->rewardsTable->clear(); - rewards = 0; -} - - -void RewardsWidget::on_rewardsTable_itemSelectionChanged() -{ - /*auto type = ui->rewardsTable->item(ui->rewardsTable->currentRow(), 0); - ui->rewardType->setCurrentIndex(type->data(Qt::UserRole).toInt()); - ui->rewardType->activated(ui->rewardType->currentIndex()); + std::set> rowsToRemove; + for(auto * i : ui->rCreatures->selectedItems()) + rowsToRemove.insert(i->row()); - type = ui->rewardsTable->item(ui->rewardsTable->currentRow(), 1); - ui->rewardList->setCurrentIndex(type->data(Qt::UserRole).toInt()); - ui->rewardList->activated(ui->rewardList->currentIndex()); - - type = ui->rewardsTable->item(ui->rewardsTable->currentRow(), 2); - ui->rewardAmount->setText(QString::number(type->data(Qt::UserRole).toInt()));*/ + for(auto i : rowsToRemove) + ui->rCreatures->removeRow(i); } + +void RewardsWidget::on_lCreatureAdd_clicked() +{ + onCreatureAdd(ui->lCreatures, ui->lCreatureId, ui->lCreatureAmount); +} + + +void RewardsWidget::on_lCreatureRemove_clicked() +{ + std::set> rowsToRemove; + for(auto * i : ui->lCreatures->selectedItems()) + rowsToRemove.insert(i->row()); + + for(auto i : rowsToRemove) + ui->lCreatures->removeRow(i); +} + +void RewardsWidget::on_castSpellCheck_toggled(bool checked) +{ + ui->castSpell->setEnabled(checked); + ui->castLevel->setEnabled(checked); +} + +void RewardsWidget::on_bonusAdd_clicked() +{ + auto * itemType = new QTableWidgetItem(ui->bonusType->currentText()); + auto * itemDur = new QTableWidgetItem(ui->bonusDuration->currentText()); + auto * itemVal = new QTableWidgetItem(QString::number(ui->bonusValue->value())); + itemVal->setData(Qt::UserRole, ui->bonusValue->value()); + + ui->bonuses->setRowCount(ui->bonuses->rowCount() + 1); + ui->bonuses->setItem(ui->bonuses->rowCount() - 1, 0, itemDur); + ui->bonuses->setItem(ui->bonuses->rowCount() - 1, 1, itemType); + ui->bonuses->setItem(ui->bonuses->rowCount() - 1, 2, itemVal); +} + +void RewardsWidget::on_bonusRemove_clicked() +{ + std::set> rowsToRemove; + for(auto * i : ui->bonuses->selectedItems()) + rowsToRemove.insert(i->row()); + + for(auto i : rowsToRemove) + ui->bonuses->removeRow(i); +} + + void RewardsDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { if(auto * ed = qobject_cast(editor)) @@ -431,20 +629,11 @@ void RewardsDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, c } } -RewardsPandoraDelegate::RewardsPandoraDelegate(const CMap & m, CGPandoraBox & t): map(m), pandora(t), RewardsDelegate() +RewardsDelegate::RewardsDelegate(const CMap & m, CRewardableObject & t): map(m), object(t) { } -QWidget * RewardsPandoraDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const +QWidget * RewardsDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { - return new RewardsWidget(map, pandora, parent); -} - -RewardsSeerhutDelegate::RewardsSeerhutDelegate(const CMap & m, CGSeerHut & t): map(m), seerhut(t), RewardsDelegate() -{ -} - -QWidget * RewardsSeerhutDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const -{ - return new RewardsWidget(map, seerhut, parent); + return new RewardsWidget(map, object, parent); } diff --git a/mapeditor/inspector/rewardswidget.h b/mapeditor/inspector/rewardswidget.h index 422c31768..2a5958d9e 100644 --- a/mapeditor/inspector/rewardswidget.h +++ b/mapeditor/inspector/rewardswidget.h @@ -10,88 +10,77 @@ #pragma once #include "../StdInc.h" #include -#include "../lib/mapObjects/CGPandoraBox.h" -#include "../lib/mapObjects/CQuest.h" +#include "../lib/mapObjects/CRewardableObject.h" namespace Ui { class RewardsWidget; } -const std::array rewardTypes{"Experience", "Mana", "Morale", "Luck", "Resource", "Primary skill", "Secondary skill", "Artifact", "Spell", "Creature"}; - class RewardsWidget : public QDialog { Q_OBJECT public: - enum RewardType - { - EXPERIENCE = 0, MANA, MORALE, LUCK, RESOURCE, PRIMARY_SKILL, SECONDARY_SKILL, ARTIFACT, SPELL, CREATURE - }; - explicit RewardsWidget(const CMap &, CGPandoraBox &, QWidget *parent = nullptr); - explicit RewardsWidget(const CMap &, CGSeerHut &, QWidget *parent = nullptr); + explicit RewardsWidget(const CMap &, CRewardableObject &, QWidget *parent = nullptr); ~RewardsWidget(); void obtainData(); bool commitChanges(); private slots: - void on_rewardType_activated(int index); + void on_addVisitInfo_clicked(); - void on_rewardList_activated(int index); + void on_removeVisitInfo_clicked(); - void on_buttonAdd_clicked(); + void on_selectMode_currentIndexChanged(int index); - void on_buttonRemove_clicked(); + void on_resetPeriod_valueChanged(int arg1); - void on_buttonClear_clicked(); + void on_visitInfoList_itemSelectionChanged(); - void on_rewardsTable_itemSelectionChanged(); + void on_visitInfoList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); + + void on_rCreatureAdd_clicked(); + + void on_rCreatureRemove_clicked(); + + void on_lCreatureAdd_clicked(); + + void on_lCreatureRemove_clicked(); + + void on_castSpellCheck_toggled(bool checked); + + void on_bonusAdd_clicked(); + + void on_bonusRemove_clicked(); private: - void addReward(RewardType typeId, int listId, int amount); - QList getListForType(RewardType typeId); + + void saveCurrentVisitInfo(int index); + void loadCurrentVisitInfo(int index); + + void onCreatureAdd(QTableWidget * listWidget, QComboBox * comboWidget, QSpinBox * spinWidget); Ui::RewardsWidget *ui; - CGPandoraBox * pandora; - CGSeerHut * seerhut; + CRewardableObject & object; const CMap & map; - int rewards = 0; }; class RewardsDelegate : public QStyledItemDelegate { Q_OBJECT public: + RewardsDelegate(const CMap &, CRewardableObject &); + using QStyledItemDelegate::QStyledItemDelegate; void setEditorData(QWidget *editor, const QModelIndex &index) const override; void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; -}; -class RewardsPandoraDelegate : public RewardsDelegate -{ - Q_OBJECT -public: - RewardsPandoraDelegate(const CMap &, CGPandoraBox &); - QWidget * createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; - + private: - CGPandoraBox & pandora; - const CMap & map; -}; - -class RewardsSeerhutDelegate : public RewardsDelegate -{ - Q_OBJECT -public: - RewardsSeerhutDelegate(const CMap &, CGSeerHut &); - - QWidget * createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; - -private: - CGSeerHut & seerhut; + CRewardableObject & object; const CMap & map; }; diff --git a/mapeditor/inspector/rewardswidget.ui b/mapeditor/inspector/rewardswidget.ui index e8bb6d41a..544eee0ff 100644 --- a/mapeditor/inspector/rewardswidget.ui +++ b/mapeditor/inspector/rewardswidget.ui @@ -9,8 +9,8 @@ 0 0 - 645 - 335 + 806 + 561 @@ -19,69 +19,1433 @@ true - - - - - Remove selected + + + 3 + + + + + + + + + Add + + + + + + + Remove + + + + + + + + + QAbstractItemView::NoEditTriggers + + + -3 + + + + + + + + + Visit mode + + + + + + + + 1 + 0 + + + + + + + + + + + + Select mode + + + + + + + + 1 + 0 + + + + + + + + + + + + + On select text + + + + + + + Can refuse + + + + + + + Reset parameters + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + + Period + + + + + + + days + + + 99 + + + + + + + + + Reset visitors + + + + + + + Reset rewards + + + + + + + + + + + + Window type + + + + + + + + + + + + + + + 0 + 0 + + + Event info + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + Message to be displayed on granting of this reward + + + + + + + 0 + + + + Reward + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + + Hero level + + + + + + + + 40 + 0 + + + + + + + + Hero experience + + + + + + + + 80 + 0 + + + + 100000 + + + 100 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Spell points + + + + + + + + 60 + 0 + + + + -999 + + + 999 + + + + + + + % + + + -100 + + + 1000 + + + + + + + Overflow + + + + + + + % + + + 100 + + + 100 + + + + + + + + + + + Movement + + + + + + + -999 + + + 999 + + + + + + + % + + + -100 + + + 1000 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Remove object + + + + + + + + + Primary skills + + + + 12 + + + 3 + + + 3 + + + + + Attack + + + + + + + -99 + + + + + + + Defence + + + + + + + -99 + + + + + + + Spell power + + + + + + + -99 + + + + + + + Knowledge + + + + + + + -99 + + + + + + + + + + 0 + + + true + + + + Resources + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + 2 + + + false + + + 180 + + + true + + + false + + + 24 + + + + + + + + + + Artifacts + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + true + + + + + + + + Spells + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + true + + + + + + + + Skills + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + 2 + + + false + + + 180 + + + false + + + 24 + + + + + + + + + + Creatures + + + + 0 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + + + 0 + 0 + + + + + + + + + 60 + 0 + + + + 9999 + + + QAbstractSpinBox::AdaptiveDecimalStepType + + + + + + + Add + + + + + + + Remove + + + + + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::MultiSelection + + + QAbstractItemView::SelectRows + + + 2 + + + false + + + 180 + + + false + + + + + + + + + + Bonuses + + + + 3 + + + 3 + + + 3 + + + + + + + Duration + + + + + + + + + + Type + + + + + + + + + + Value + + + + + + + -999 + + + 999 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Add + + + + + + + Remove + + + + + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::MultiSelection + + + QAbstractItemView::SelectRows + + + 150 + + + false + + + false + + + 21 + + + + Duration + + + + + Type + + + + + Value + + + + + + + + + Cast + + + + + + Cast an adventure map spell + + + + + + + + + Spell + + + + + + + + 0 + 0 + + + + + + + + + + + + Magic school level + + + + + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + Limiter + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + + Day of week + + + + + + + + 120 + 0 + + + + + + + + Days passed + + + + + + + + 20 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Hero level + + + + + + + + 40 + 0 + + + + + + + + Hero experience + + + + + + + + 80 + 0 + + + + 100000 + + + 100 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Spell points + + + + + + + + 60 + 0 + + + + 999 + + + + + + + % + + + 100 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Primary skills + + + + 12 + + + 3 + + + 3 + + + + + Attack + + + + + + + + + + Defence + + + + + + + + + + Spell power + + + + + + + + + + Knowledge + + + + + + + + + + + + + 0 + + + true + + + + Resources + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + 2 + + + false + + + 180 + + + true + + + false + + + 21 + + + + + + + + + + Artifacts + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + true + + + + + + + + Spells + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + true + + + + + + + + Skills + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + 2 + + + false + + + 180 + + + false + + + 21 + + + + + + + + + + Creatures + + + + 0 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + + + 0 + 0 + + + + + + + + + 60 + 0 + + + + 9999 + + + QAbstractSpinBox::AdaptiveDecimalStepType + + + + + + + Add + + + + + + + Remove + + + + + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::MultiSelection + + + QAbstractItemView::SelectRows + + + 2 + + + false + + + 180 + + + false + + + 21 + + + + + + + + + + + + + + - - - - - 80 - 16777215 - - - - Qt::ImhDigitsOnly - - - - - - - Delete all - - - - - - - Add or change - - - - - - - QAbstractItemView::NoEditTriggers - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - 3 - - - false - - - - - - - - - - - - From 033e7010cadbc7827511ff6bbce1f406797874e3 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Sat, 16 Sep 2023 05:52:23 +0200 Subject: [PATCH 10/18] Fix for empty primary skills array --- mapeditor/inspector/rewardswidget.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mapeditor/inspector/rewardswidget.cpp b/mapeditor/inspector/rewardswidget.cpp index 0d0d23136..f0ec0b756 100644 --- a/mapeditor/inspector/rewardswidget.cpp +++ b/mapeditor/inspector/rewardswidget.cpp @@ -228,6 +228,7 @@ void RewardsWidget::saveCurrentVisitInfo(int index) vinfo.reward.movePoints = ui->rMovePoints->value(); vinfo.reward.movePercentage = ui->rMovePercentage->value(); vinfo.reward.removeObject = ui->removeObject->isChecked(); + vinfo.reward.primary.resize(4); vinfo.reward.primary[0] = ui->rAttack->value(); vinfo.reward.primary[1] = ui->rDefence->value(); vinfo.reward.primary[2] = ui->rPower->value(); @@ -292,6 +293,7 @@ void RewardsWidget::saveCurrentVisitInfo(int index) vinfo.limiter.heroExperience = ui->lHeroExperience->value(); vinfo.limiter.manaPoints = ui->lManaPoints->value(); vinfo.limiter.manaPercentage = ui->lManaPercentage->value(); + vinfo.limiter.primary.resize(4); vinfo.limiter.primary[0] = ui->lAttack->value(); vinfo.limiter.primary[1] = ui->lDefence->value(); vinfo.limiter.primary[2] = ui->lPower->value(); From 10d783d8eebb1fa73ec7e06bba2da69b82eea1b4 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Sat, 16 Sep 2023 05:59:16 +0200 Subject: [PATCH 11/18] Undefined behavior fix --- mapeditor/inspector/rewardswidget.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mapeditor/inspector/rewardswidget.cpp b/mapeditor/inspector/rewardswidget.cpp index f0ec0b756..24d5353f2 100644 --- a/mapeditor/inspector/rewardswidget.cpp +++ b/mapeditor/inspector/rewardswidget.cpp @@ -511,6 +511,7 @@ void RewardsWidget::on_removeVisitInfo_clicked() delete ui->visitInfoList->currentItem(); ui->visitInfoList->blockSignals(false); on_visitInfoList_itemSelectionChanged(); + loadCurrentVisitInfo(ui->visitInfoList->currentRow()); } void RewardsWidget::on_selectMode_currentIndexChanged(int index) @@ -534,13 +535,15 @@ void RewardsWidget::on_visitInfoList_itemSelectionChanged() } ui->eventInfoGroup->show(); - loadCurrentVisitInfo(ui->visitInfoList->currentRow()); } void RewardsWidget::on_visitInfoList_currentItemChanged(QListWidgetItem * current, QListWidgetItem * previous) { if(previous) saveCurrentVisitInfo(ui->visitInfoList->row(previous)); + + if(current) + loadCurrentVisitInfo(ui->visitInfoList->currentRow()); } From a3b60bf829079b6f5e4e7a6fffed1b4af7aaefa5 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Sat, 16 Sep 2023 14:30:25 +0200 Subject: [PATCH 12/18] Fix warning --- lib/mapObjects/CGPandoraBox.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mapObjects/CGPandoraBox.cpp b/lib/mapObjects/CGPandoraBox.cpp index 1ddf2d997..4fa3e50df 100644 --- a/lib/mapObjects/CGPandoraBox.cpp +++ b/lib/mapObjects/CGPandoraBox.cpp @@ -212,7 +212,7 @@ void CGPandoraBox::serializeJsonOptions(JsonSerializeFormat & handler) for(int idx = 0; idx < reward.primary.size(); idx ++) { handler.serializeInt(NPrimarySkill::names[idx], reward.primary[idx], 0); - updateReward |= reward.primary[idx]; + updateReward |= bool(reward.primary[idx]); } addReward(updateReward); } From 7b22aefb12b156b55938b40b09c09f963226d3af Mon Sep 17 00:00:00 2001 From: nordsoft Date: Sun, 17 Sep 2023 13:03:42 +0200 Subject: [PATCH 13/18] Some minor fixes --- lib/mapObjects/CGPandoraBox.cpp | 52 +++++++++++++-------------- mapeditor/inspector/rewardswidget.cpp | 1 + mapeditor/inspector/rewardswidget.ui | 2 +- 3 files changed, 27 insertions(+), 28 deletions(-) diff --git a/lib/mapObjects/CGPandoraBox.cpp b/lib/mapObjects/CGPandoraBox.cpp index 4fa3e50df..68187441c 100644 --- a/lib/mapObjects/CGPandoraBox.cpp +++ b/lib/mapObjects/CGPandoraBox.cpp @@ -171,60 +171,58 @@ void CGPandoraBox::serializeJsonOptions(JsonSerializeFormat & handler) { //backward compatibility CCreatureSet::serializeJson(handler, "guards", 7); - Rewardable::Reward reward; + Rewardable::VisitInfo vinfo; + vinfo.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; - auto addReward = [this, &reward](bool condition) + auto addReward = [this, &vinfo](bool condition) { if(condition) { - configuration.info.emplace_back(); - configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; - configuration.info.back().reward = reward; - reward = Rewardable::Reward{}; + configuration.info.push_back(vinfo); + vinfo = Rewardable::VisitInfo{}; + vinfo.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; } }; - - addReward(true); - + int val; - handler.serializeInt("experience", reward.heroExperience, 0); - addReward(reward.heroExperience); + handler.serializeInt("experience", vinfo.reward.heroExperience, 0); + addReward(vinfo.reward.heroExperience); - handler.serializeInt("mana", reward.manaDiff, 0); - addReward(reward.manaDiff); + handler.serializeInt("mana", vinfo.reward.manaDiff, 0); + addReward(vinfo.reward.manaDiff); handler.serializeInt("morale", val, 0); if(val) - reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT, val, id); + vinfo.reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT, val, id); addReward(val); handler.serializeInt("luck", val, 0); if(val) - reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT, val, id); + vinfo.reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT, val, id); addReward(val); - reward.resources.serializeJson(handler, "resources"); - addReward(reward.resources.nonZero()); + vinfo.reward.resources.serializeJson(handler, "resources"); + addReward(vinfo.reward.resources.nonZero()); { bool updateReward = false; auto s = handler.enterStruct("primarySkills"); - for(int idx = 0; idx < reward.primary.size(); idx ++) + for(int idx = 0; idx < vinfo.reward.primary.size(); idx ++) { - handler.serializeInt(NPrimarySkill::names[idx], reward.primary[idx], 0); - updateReward |= bool(reward.primary[idx]); + handler.serializeInt(NPrimarySkill::names[idx], vinfo.reward.primary[idx], 0); + updateReward |= bool(vinfo.reward.primary[idx]); } addReward(updateReward); } - handler.serializeIdArray("artifacts", reward.artifacts); - addReward(!reward.artifacts.empty()); + handler.serializeIdArray("artifacts", vinfo.reward.artifacts); + addReward(!vinfo.reward.artifacts.empty()); - handler.serializeIdArray("spells", reward.spells); - addReward(!reward.spells.empty()); + handler.serializeIdArray("spells", vinfo.reward.spells); + addReward(!vinfo.reward.spells.empty()); - handler.enterArray("creatures").serializeStruct(reward.creatures); - addReward(!reward.creatures.empty()); + handler.enterArray("creatures").serializeStruct(vinfo.reward.creatures); + addReward(!vinfo.reward.creatures.empty()); { bool updateReward = false; @@ -248,7 +246,7 @@ void CGPandoraBox::serializeJsonOptions(JsonSerializeFormat & handler) continue; } - reward.secondary[rawId] = level; + vinfo.reward.secondary[rawId] = level; updateReward = true; } addReward(updateReward); diff --git a/mapeditor/inspector/rewardswidget.cpp b/mapeditor/inspector/rewardswidget.cpp index 24d5353f2..36813172f 100644 --- a/mapeditor/inspector/rewardswidget.cpp +++ b/mapeditor/inspector/rewardswidget.cpp @@ -218,6 +218,7 @@ bool RewardsWidget::commitChanges() void RewardsWidget::saveCurrentVisitInfo(int index) { auto & vinfo = object.configuration.info.at(index); + vinfo.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; vinfo.message = MetaString::createFromRawString(ui->rewardMessage->text().toStdString()); vinfo.reward.heroLevel = ui->rHeroLevel->value(); diff --git a/mapeditor/inspector/rewardswidget.ui b/mapeditor/inspector/rewardswidget.ui index 544eee0ff..c3681e258 100644 --- a/mapeditor/inspector/rewardswidget.ui +++ b/mapeditor/inspector/rewardswidget.ui @@ -49,7 +49,7 @@ QAbstractItemView::NoEditTriggers - -3 + 0 From 44d72dce517b3e122ed07b2235cea7e0e26cc1c8 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Sun, 17 Sep 2023 15:06:49 +0200 Subject: [PATCH 14/18] Use enum in h3m map parser --- lib/mapping/MapFormatH3M.cpp | 71 ++++++++++++++++++++++-------------- lib/mapping/MapFormatH3M.h | 10 ++--- 2 files changed, 48 insertions(+), 33 deletions(-) diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index b2bdf69bc..58c181178 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -989,11 +989,11 @@ void CMapLoaderH3M::readObjectTemplates() } } -CGObjectInstance * CMapLoaderH3M::readEvent(const int3 & mapPosition) +CGObjectInstance * CMapLoaderH3M::readEvent(const int3 & mapPosition, const ObjectInstanceID & idToBeGiven) { auto * object = new CGEvent(); - readBoxContent(object, mapPosition); + readBoxContent(object, mapPosition, idToBeGiven); reader->readBitmaskPlayers(object->availableFor, false); object->computerActivate = reader->readBool(); @@ -1009,14 +1009,14 @@ CGObjectInstance * CMapLoaderH3M::readEvent(const int3 & mapPosition) return object; } -CGObjectInstance * CMapLoaderH3M::readPandora(const int3 & mapPosition) +CGObjectInstance * CMapLoaderH3M::readPandora(const int3 & mapPosition, const ObjectInstanceID & idToBeGiven) { auto * object = new CGPandoraBox(); - readBoxContent(object, mapPosition); + readBoxContent(object, mapPosition, idToBeGiven); return object; } -void CMapLoaderH3M::readBoxContent(CGPandoraBox * object, const int3 & mapPosition) +void CMapLoaderH3M::readBoxContent(CGPandoraBox * object, const int3 & mapPosition, const ObjectInstanceID & idToBeGiven) { readMessageAndGuards(object->message, object, mapPosition); Rewardable::VisitInfo vinfo; @@ -1024,8 +1024,8 @@ void CMapLoaderH3M::readBoxContent(CGPandoraBox * object, const int3 & mapPositi reward.heroExperience = reader->readUInt32(); reward.manaDiff = reader->readInt32(); - reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT, reader->readUInt8(), object->id); - reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT, reader->readUInt8(), object->id); + reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT, reader->readUInt8(), idToBeGiven); + reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT, reader->readUInt8(), idToBeGiven); reader->readResourses(reward.resources); for(int x = 0; x < GameConstants::PRIMARY_SKILLS; ++x) @@ -1417,7 +1417,7 @@ CGObjectInstance * CMapLoaderH3M::readObject(std::shared_ptrid) { case Obj::EVENT: - return readEvent(mapPosition); + return readEvent(mapPosition, objectInstanceID); case Obj::HERO: case Obj::RANDOM_HERO: @@ -1440,7 +1440,7 @@ CGObjectInstance * CMapLoaderH3M::readObject(std::shared_ptrwarn("Map '%s': Seer Hut at %s - %d quests are not implemented!", mapName, position.toString(), questsCount); for(size_t i = 0; i < questsCount; ++i) - readSeerHutQuest(hut, position); + readSeerHutQuest(hut, position, idToBeGiven); if(features.levelHOTA3) { @@ -1818,7 +1818,7 @@ CGObjectInstance * CMapLoaderH3M::readSeerHut(const int3 & position) logGlobal->warn("Map '%s': Seer Hut at %s - %d repeatable quests are not implemented!", mapName, position.toString(), repeateableQuestsCount); for(size_t i = 0; i < repeateableQuestsCount; ++i) - readSeerHutQuest(hut, position); + readSeerHutQuest(hut, position, idToBeGiven); } reader->skipZero(2); @@ -1826,7 +1826,22 @@ CGObjectInstance * CMapLoaderH3M::readSeerHut(const int3 & position) return hut; } -void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position) +enum class ESeerHutRewardType : uint8_t +{ + NOTHING = 0, + EXPERIENCE = 1, + MANA_POINTS = 2, + MORALE = 3, + LUCK = 4, + RESOURCES = 5, + PRIMARY_SKILL = 6, + SECONDARY_SKILL = 7, + ARTIFACT = 8, + SPELL = 9, + CREATURE = 10, +}; + +void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, const ObjectInstanceID & idToBeGiven) { if(features.levelAB) { @@ -1854,37 +1869,37 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position) if(hut->quest->missionType) { - auto rewardType = reader->readUInt8(); + auto rewardType = static_cast(reader->readUInt8()); Rewardable::VisitInfo vinfo; auto & reward = vinfo.reward; switch(rewardType) { - case 0: //NOTHING + case ESeerHutRewardType::NOTHING: { // no-op break; } - case 1: //EXPERIENCE + case ESeerHutRewardType::EXPERIENCE: { reward.heroExperience = reader->readUInt32(); break; } - case 2: //MANA POINTS: + case ESeerHutRewardType::MANA_POINTS: { reward.manaDiff = reader->readUInt32(); break; } - case 3: //MORALE_BONUS + case ESeerHutRewardType::MORALE: { - reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT, reader->readUInt8(), hut->id); + reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT, reader->readUInt8(), idToBeGiven); break; } - case 4: //LUCK_BONUS + case ESeerHutRewardType::LUCK: { - reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT, reader->readUInt8(), hut->id); + reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT, reader->readUInt8(), idToBeGiven); break; } - case 5: //RESOURCES + case ESeerHutRewardType::RESOURCES: { auto rId = reader->readUInt8(); auto rVal = reader->readUInt32(); @@ -1895,7 +1910,7 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position) reward.resources[rId] = rVal; break; } - case 6: //PRIMARY_SKILL + case ESeerHutRewardType::PRIMARY_SKILL: { auto rId = reader->readUInt8(); auto rVal = reader->readUInt8(); @@ -1903,7 +1918,7 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position) reward.primary.at(rId) = rVal; break; } - case 7: //SECONDARY_SKILL + case ESeerHutRewardType::SECONDARY_SKILL: { auto rId = reader->readSkill(); auto rVal = reader->readUInt8(); @@ -1911,17 +1926,17 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position) reward.secondary[rId] = rVal; break; } - case 8: //ARTIFACT + case ESeerHutRewardType::ARTIFACT: { reward.artifacts.push_back(reader->readArtifact()); break; } - case 9: //SPELL + case ESeerHutRewardType::SPELL: { reward.spells.push_back(reader->readSpell()); break; } - case 10: //CREATURE + case ESeerHutRewardType::CREATURE: { auto rId = reader->readCreature(); auto rVal = reader->readUInt16(); diff --git a/lib/mapping/MapFormatH3M.h b/lib/mapping/MapFormatH3M.h index c0407a35a..8b7a10a08 100644 --- a/lib/mapping/MapFormatH3M.h +++ b/lib/mapping/MapFormatH3M.h @@ -158,10 +158,10 @@ private: /// Reads single object from input stream based on template CGObjectInstance * readObject(std::shared_ptr objectTemplate, const int3 & objectPosition, const ObjectInstanceID & idToBeGiven); - CGObjectInstance * readEvent(const int3 & objectPosition); + CGObjectInstance * readEvent(const int3 & objectPosition, const ObjectInstanceID & idToBeGiven); CGObjectInstance * readMonster(const int3 & objectPosition, const ObjectInstanceID & idToBeGiven); CGObjectInstance * readHero(const int3 & initialPos, const ObjectInstanceID & idToBeGiven); - CGObjectInstance * readSeerHut(const int3 & initialPos); + CGObjectInstance * readSeerHut(const int3 & initialPos, const ObjectInstanceID & idToBeGiven); CGObjectInstance * readTown(const int3 & position, std::shared_ptr objTempl); CGObjectInstance * readSign(const int3 & position); CGObjectInstance * readWitchHut(); @@ -170,7 +170,7 @@ private: CGObjectInstance * readArtifact(const int3 & position, std::shared_ptr objTempl); CGObjectInstance * readResource(const int3 & position, std::shared_ptr objTempl); CGObjectInstance * readMine(const int3 & position, std::shared_ptr objTempl); - CGObjectInstance * readPandora(const int3 & position); + CGObjectInstance * readPandora(const int3 & position, const ObjectInstanceID & idToBeGiven); CGObjectInstance * readDwelling(const int3 & position); CGObjectInstance * readDwellingRandom(const int3 & position, std::shared_ptr objTempl); CGObjectInstance * readShrine(); @@ -196,7 +196,7 @@ private: * * @param guard the quest guard where that quest should be applied to */ - void readBoxContent(CGPandoraBox * object, const int3 & position); + void readBoxContent(CGPandoraBox * object, const int3 & position, const ObjectInstanceID & idToBeGiven); /** * Reads a quest for the given quest guard. @@ -205,7 +205,7 @@ private: */ void readQuest(IQuestObject * guard, const int3 & position); - void readSeerHutQuest(CGSeerHut * hut, const int3 & position); + void readSeerHutQuest(CGSeerHut * hut, const int3 & position, const ObjectInstanceID & idToBeGiven); /** * Reads events. From e3538f24abd2897373a4613ddc78a236f5219b0d Mon Sep 17 00:00:00 2001 From: nordsoft Date: Sun, 17 Sep 2023 16:04:34 +0200 Subject: [PATCH 15/18] Fix components problem --- lib/mapObjectConstructors/CRewardableConstructor.cpp | 5 ----- lib/mapObjects/CGTownBuilding.cpp | 4 ---- lib/mapping/MapFormatH3M.cpp | 6 ++++-- lib/rewardable/Reward.cpp | 10 +++++++++- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/lib/mapObjectConstructors/CRewardableConstructor.cpp b/lib/mapObjectConstructors/CRewardableConstructor.cpp index 8d3bec191..ad87c06fe 100644 --- a/lib/mapObjectConstructors/CRewardableConstructor.cpp +++ b/lib/mapObjectConstructors/CRewardableConstructor.cpp @@ -51,11 +51,6 @@ void CRewardableConstructor::configureObject(CGObjectInstance * object, CRandomG { bonus.source = BonusSource::OBJECT; bonus.sid = rewardableObject->ID; - //TODO: bonus.description = object->getObjectName(); - if (bonus.type == BonusType::MORALE) - rewardInfo.reward.extraComponents.emplace_back(Component::EComponentType::MORALE, 0, bonus.val, 0); - if (bonus.type == BonusType::LUCK) - rewardInfo.reward.extraComponents.emplace_back(Component::EComponentType::LUCK, 0, bonus.val, 0); } } assert(!rewardableObject->configuration.info.empty()); diff --git a/lib/mapObjects/CGTownBuilding.cpp b/lib/mapObjects/CGTownBuilding.cpp index 098b519d5..0a8f952a6 100644 --- a/lib/mapObjects/CGTownBuilding.cpp +++ b/lib/mapObjects/CGTownBuilding.cpp @@ -307,10 +307,6 @@ void CTownRewardableBuilding::initObj(CRandomGenerator & rand) { bonus.source = BonusSource::TOWN_STRUCTURE; bonus.sid = bID; - if (bonus.type == BonusType::MORALE) - rewardInfo.reward.extraComponents.emplace_back(Component::EComponentType::MORALE, 0, bonus.val, 0); - if (bonus.type == BonusType::LUCK) - rewardInfo.reward.extraComponents.emplace_back(Component::EComponentType::LUCK, 0, bonus.val, 0); } } } diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 58c181178..5c95fffae 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -1024,8 +1024,10 @@ void CMapLoaderH3M::readBoxContent(CGPandoraBox * object, const int3 & mapPositi reward.heroExperience = reader->readUInt32(); reward.manaDiff = reader->readInt32(); - reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT, reader->readUInt8(), idToBeGiven); - reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT, reader->readUInt8(), idToBeGiven); + if(auto val = reader->readUInt8()) + reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT, val, idToBeGiven); + if(auto val = reader->readUInt8()) + reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT, val, idToBeGiven); reader->readResourses(reward.resources); for(int x = 0; x < GameConstants::PRIMARY_SKILLS; ++x) diff --git a/lib/rewardable/Reward.cpp b/lib/rewardable/Reward.cpp index 186f4ba16..07da516cd 100644 --- a/lib/rewardable/Reward.cpp +++ b/lib/rewardable/Reward.cpp @@ -61,7 +61,15 @@ void Rewardable::Reward::loadComponents(std::vector & comps, { for (auto comp : extraComponents) comps.push_back(comp); - + + for (auto & bonus : bonuses) + { + if (bonus.type == BonusType::MORALE) + comps.emplace_back(Component::EComponentType::MORALE, 0, bonus.val, 0); + if (bonus.type == BonusType::LUCK) + comps.emplace_back(Component::EComponentType::LUCK, 0, bonus.val, 0); + } + if (heroExperience) { comps.emplace_back(Component::EComponentType::EXPERIENCE, 0, static_cast(h->calculateXp(heroExperience)), 0); From a517e6ad8efd4732ec3cdcb72cd62037806c6701 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Sun, 17 Sep 2023 18:02:24 +0200 Subject: [PATCH 16/18] Message split for pandora box --- lib/mapObjects/CGPandoraBox.cpp | 246 ++++++++++++++------------ lib/mapObjects/CGPandoraBox.h | 4 + lib/mapObjects/CRewardableObject.cpp | 91 +++++----- lib/mapObjects/CRewardableObject.h | 3 + lib/rewardable/Configuration.h | 3 +- mapeditor/inspector/rewardswidget.cpp | 12 +- 6 files changed, 194 insertions(+), 165 deletions(-) diff --git a/lib/mapObjects/CGPandoraBox.cpp b/lib/mapObjects/CGPandoraBox.cpp index 68187441c..b7538195a 100644 --- a/lib/mapObjects/CGPandoraBox.cpp +++ b/lib/mapObjects/CGPandoraBox.cpp @@ -28,7 +28,6 @@ VCMI_LIB_NAMESPACE_BEGIN void CGPandoraBox::init() { blockVisit = true; - configuration.selectMode = Rewardable::SELECT_ALL; for(auto & i : configuration.info) i.reward.removeObject = true; @@ -41,92 +40,130 @@ void CGPandoraBox::initObj(CRandomGenerator & rand) CRewardableObject::initObj(rand); } -void CGPandoraBox::onHeroVisit(const CGHeroInstance * h) const +void CGPandoraBox::grantRewardWithMessage(const CGHeroInstance * h, int index, bool markAsVisit) const { - auto setText = [](MetaString & text, int tId, const CGHeroInstance * h) + auto vi = configuration.info.at(index); + if(!vi.message.empty()) { - text.appendLocalString(EMetaText::ADVOB_TXT, tId); - text.replaceRawString(h->getNameTranslated()); - }; - - for(auto i : getAvailableRewards(h, Rewardable::EEventType::EVENT_FIRST_VISIT)) - { - MetaString txt; - auto & r = configuration.info[i]; - - if(r.reward.spells.size() == 1) - setText(txt, 184, h); - else if(!r.reward.spells.empty()) - setText(txt, 188, h); - - if(r.reward.heroExperience || !r.reward.secondary.empty()) - setText(txt, 175, h); - - for(int ps : r.reward.primary) - { - if(ps) - { - setText(txt, 175, h); - break; - } - } - - if(r.reward.manaDiff < 0) - setText(txt, 176, h); - if(r.reward.manaDiff > 0) - setText(txt, 177, h); - - for(auto b : r.reward.bonuses) - { - if(b.type == BonusType::MORALE) - { - if(b.val < 0) - setText(txt, 178, h); - if(b.val > 0) - setText(txt, 179, h); - } - if(b.type == BonusType::LUCK) - { - if(b.val < 0) - setText(txt, 180, h); - if(b.val > 0) - setText(txt, 181, h); - } - } - - for(auto res : r.reward.resources) - { - if(res < 0) - setText(txt, 182, h); - if(res > 0) - setText(txt, 183, h); - } - - if(!r.reward.artifacts.empty()) - setText(txt, 183, h); - - if(!r.reward.creatures.empty()) - { - MetaString loot; - for(auto c : r.reward.creatures) - { - loot.appendRawString("%s"); - loot.replaceCreatureName(c); - } - - if(r.reward.creatures.size() == 1 && r.reward.creatures[0].count == 1) - txt.appendLocalString(EMetaText::ADVOB_TXT, 185); - else - txt.appendLocalString(EMetaText::ADVOB_TXT, 186); - - txt.replaceRawString(loot.buildList()); - txt.replaceRawString(h->getNameTranslated()); - } - - if(r.message.empty()) - const_cast(r.message) = txt; + CRewardableObject::grantRewardWithMessage(h, index, markAsVisit); + return; } + //split reward message for pandora box + auto setText = [](bool cond, int posId, int negId, const CGHeroInstance * h) + { + MetaString text; + text.appendLocalString(EMetaText::ADVOB_TXT, cond ? posId : negId); + text.replaceRawString(h->getNameTranslated()); + return text; + }; + + auto sendInfoWindow = [h](const MetaString & text, const Rewardable::Reward & reward) + { + InfoWindow iw; + iw.player = h->tempOwner; + iw.text = text; + reward.loadComponents(iw.components, h); + iw.type = EInfoWindowMode::MODAL; + if(!iw.components.empty()) + cb->showInfoDialog(&iw); + }; + + Rewardable::Reward temp; + temp.spells = vi.reward.spells; + temp.heroExperience = vi.reward.heroExperience; + temp.heroLevel = vi.reward.heroLevel; + temp.primary = vi.reward.primary; + temp.secondary = vi.reward.secondary; + temp.bonuses = vi.reward.bonuses; + temp.manaDiff = vi.reward.manaDiff; + temp.manaPercentage = vi.reward.manaPercentage; + + MetaString txt; + if(!vi.reward.spells.empty()) + txt = setText(temp.spells.size() == 1, 184, 188, h); + + if(vi.reward.heroExperience || vi.reward.heroLevel || !vi.reward.secondary.empty()) + txt = setText(true, 175, 175, h); + + for(int i : vi.reward.primary) + { + if(i) + { + txt = setText(true, 175, 175, h); + break; + } + } + + if(vi.reward.manaDiff || vi.reward.manaPercentage) + txt = setText(temp.manaDiff > 0, 177, 176, h); + + for(auto b : vi.reward.bonuses) + { + if(b.val && b.type == BonusType::MORALE) + txt = setText(b.val > 0, 179, 178, h); + if(b.val && b.type == BonusType::LUCK) + txt = setText(b.val > 0, 181, 180, h); + } + sendInfoWindow(txt, temp); + + //resource message + temp = Rewardable::Reward{}; + temp.resources = vi.reward.resources; + sendInfoWindow(setText(vi.reward.resources.marketValue() > 0, 183, 182, h), temp); + + //artifacts message + temp = Rewardable::Reward{}; + temp.artifacts = vi.reward.artifacts; + sendInfoWindow(setText(true, 183, 183, h), temp); + + //creatures message + temp = Rewardable::Reward{}; + temp.creatures = vi.reward.creatures; + txt.clear(); + if(!vi.reward.creatures.empty()) + { + MetaString loot; + for(auto c : vi.reward.creatures) + { + loot.appendRawString("%s"); + loot.replaceCreatureName(c); + } + + if(vi.reward.creatures.size() == 1 && vi.reward.creatures[0].count == 1) + txt.appendLocalString(EMetaText::ADVOB_TXT, 185); + else + txt.appendLocalString(EMetaText::ADVOB_TXT, 186); + + txt.replaceRawString(loot.buildList()); + txt.replaceRawString(h->getNameTranslated()); + } + sendInfoWindow(txt, temp); + + //everything else + temp = vi.reward; + temp.heroExperience = 0; + temp.heroLevel = 0; + temp.secondary.clear(); + temp.primary.clear(); + temp.resources.amin(0); + temp.resources.amax(0); + temp.manaDiff = 0; + temp.manaPercentage = 0; + temp.spells.clear(); + temp.creatures.clear(); + temp.bonuses.clear(); + temp.artifacts.clear(); + sendInfoWindow(setText(true, 175, 175, h), temp); + + // grant reward afterwards. Note that it may remove object + if(markAsVisit) + markAsVisited(h); + grantReward(index, h); +} + +void CGPandoraBox::onHeroVisit(const CGHeroInstance * h) const +{ BlockingDialog bd (true, false); bd.player = h->getOwner(); bd.text.appendLocalString(EMetaText::ADVOB_TXT, 14); @@ -171,61 +208,36 @@ void CGPandoraBox::serializeJsonOptions(JsonSerializeFormat & handler) { //backward compatibility CCreatureSet::serializeJson(handler, "guards", 7); - Rewardable::VisitInfo vinfo; + configuration.info.emplace_back(); + Rewardable::VisitInfo & vinfo = configuration.info.back(); vinfo.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; - - auto addReward = [this, &vinfo](bool condition) - { - if(condition) - { - configuration.info.push_back(vinfo); - vinfo = Rewardable::VisitInfo{}; - vinfo.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; - } - }; - int val; handler.serializeInt("experience", vinfo.reward.heroExperience, 0); - addReward(vinfo.reward.heroExperience); - handler.serializeInt("mana", vinfo.reward.manaDiff, 0); - addReward(vinfo.reward.manaDiff); + int val; handler.serializeInt("morale", val, 0); if(val) vinfo.reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT, val, id); - addReward(val); handler.serializeInt("luck", val, 0); if(val) vinfo.reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT, val, id); - addReward(val); vinfo.reward.resources.serializeJson(handler, "resources"); - addReward(vinfo.reward.resources.nonZero()); - { - bool updateReward = false; auto s = handler.enterStruct("primarySkills"); for(int idx = 0; idx < vinfo.reward.primary.size(); idx ++) { handler.serializeInt(NPrimarySkill::names[idx], vinfo.reward.primary[idx], 0); - updateReward |= bool(vinfo.reward.primary[idx]); } - addReward(updateReward); } handler.serializeIdArray("artifacts", vinfo.reward.artifacts); - addReward(!vinfo.reward.artifacts.empty()); - handler.serializeIdArray("spells", vinfo.reward.spells); - addReward(!vinfo.reward.spells.empty()); - handler.enterArray("creatures").serializeStruct(vinfo.reward.creatures); - addReward(!vinfo.reward.creatures.empty()); { - bool updateReward = false; auto s = handler.enterStruct("secondarySkills"); for(const auto & p : handler.getCurrent().Struct()) { @@ -247,9 +259,7 @@ void CGPandoraBox::serializeJsonOptions(JsonSerializeFormat & handler) } vinfo.reward.secondary[rawId] = level; - updateReward = true; } - addReward(updateReward); } } } @@ -257,10 +267,18 @@ void CGPandoraBox::serializeJsonOptions(JsonSerializeFormat & handler) void CGEvent::init() { blockVisit = false; - configuration.selectMode = Rewardable::SELECT_ALL; for(auto & i : configuration.info) + { i.reward.removeObject = removeAfterVisit; + if(!message.empty() && i.message.empty()) + i.message = MetaString::createFromRawString(message); + } +} + +void CGEvent::grantRewardWithMessage(const CGHeroInstance * contextHero, int rewardIndex, bool markAsVisit) const +{ + CRewardableObject::grantRewardWithMessage(contextHero, rewardIndex, markAsVisit); } void CGEvent::onHeroVisit( const CGHeroInstance * h ) const diff --git a/lib/mapObjects/CGPandoraBox.h b/lib/mapObjects/CGPandoraBox.h index 240e9e68d..3d4cf3540 100644 --- a/lib/mapObjects/CGPandoraBox.h +++ b/lib/mapObjects/CGPandoraBox.h @@ -32,6 +32,8 @@ public: h & message; } protected: + void grantRewardWithMessage(const CGHeroInstance * contextHero, int rewardIndex, bool markAsVisit) const override; + virtual void init(); void serializeJsonOptions(JsonSerializeFormat & handler) override; }; @@ -55,6 +57,8 @@ public: void onHeroVisit(const CGHeroInstance * h) const override; protected: + void grantRewardWithMessage(const CGHeroInstance * contextHero, int rewardIndex, bool markAsVisit) const override; + void init() override; void serializeJsonOptions(JsonSerializeFormat & handler) override; private: diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp index 3b96dbe68..8df14fd7d 100644 --- a/lib/mapObjects/CRewardableObject.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -28,44 +28,45 @@ static std::string visitedTxt(const bool visited) return VLC->generaltexth->allTexts[id]; } +void CRewardableObject::grantRewardWithMessage(const CGHeroInstance * contextHero, int index, bool markAsVisit) const +{ + auto vi = configuration.info.at(index); + logGlobal->debug("Granting reward %d. Message says: %s", index, vi.message.toString()); + // show message only if it is not empty or in infobox + if (configuration.infoWindowType != EInfoWindowMode::MODAL || !vi.message.toString().empty()) + { + InfoWindow iw; + iw.player = contextHero->tempOwner; + iw.text = vi.message; + vi.reward.loadComponents(iw.components, contextHero); + iw.type = configuration.infoWindowType; + if(!iw.components.empty() || !iw.text.toString().empty()) + cb->showInfoDialog(&iw); + } + // grant reward afterwards. Note that it may remove object + if(markAsVisit) + markAsVisited(contextHero); + grantReward(index, contextHero); +} + +void CRewardableObject::selectRewardWthMessage(const CGHeroInstance * contextHero, const std::vector & rewardIndeces, const MetaString & dialog) const +{ + BlockingDialog sd(configuration.canRefuse, rewardIndeces.size() > 1); + sd.player = contextHero->tempOwner; + sd.text = dialog; + + if (rewardIndeces.size() > 1) + for (auto index : rewardIndeces) + sd.components.push_back(configuration.info.at(index).reward.getDisplayedComponent(contextHero)); + + if (rewardIndeces.size() == 1) + configuration.info.at(rewardIndeces.front()).reward.loadComponents(sd.components, contextHero); + + cb->showBlockingDialog(&sd); +} + void CRewardableObject::onHeroVisit(const CGHeroInstance *h) const { - auto grantRewardWithMessage = [&](int index, bool markAsVisit) -> void - { - auto vi = configuration.info.at(index); - logGlobal->debug("Granting reward %d. Message says: %s", index, vi.message.toString()); - // show message only if it is not empty or in infobox - if (configuration.infoWindowType != EInfoWindowMode::MODAL || !vi.message.toString().empty()) - { - InfoWindow iw; - iw.player = h->tempOwner; - iw.text = vi.message; - vi.reward.loadComponents(iw.components, h); - iw.type = configuration.infoWindowType; - if(!iw.components.empty() || !iw.text.toString().empty()) - cb->showInfoDialog(&iw); - } - // grant reward afterwards. Note that it may remove object - if(markAsVisit) - markAsVisited(h); - grantReward(index, h); - }; - auto selectRewardsMessage = [&](const std::vector & rewards, const MetaString & dialog) -> void - { - BlockingDialog sd(configuration.canRefuse, rewards.size() > 1); - sd.player = h->tempOwner; - sd.text = dialog; - - if (rewards.size() > 1) - for (auto index : rewards) - sd.components.push_back(configuration.info.at(index).reward.getDisplayedComponent(h)); - - if (rewards.size() == 1) - configuration.info.at(rewards.front()).reward.loadComponents(sd.components, h); - - cb->showBlockingDialog(&sd); - }; - if(!wasVisitedBefore(h)) { auto rewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_FIRST_VISIT); @@ -83,7 +84,7 @@ void CRewardableObject::onHeroVisit(const CGHeroInstance *h) const { auto emptyRewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_NOT_AVAILABLE); if (!emptyRewards.empty()) - grantRewardWithMessage(emptyRewards[0], false); + grantRewardWithMessage(h, emptyRewards[0], false); else logMod->warn("No applicable message for visiting empty object!"); break; @@ -91,26 +92,22 @@ void CRewardableObject::onHeroVisit(const CGHeroInstance *h) const case 1: // one reward. Just give it with message { if (configuration.canRefuse) - selectRewardsMessage(rewards, configuration.info.at(rewards.front()).message); + selectRewardWthMessage(h, rewards, configuration.info.at(rewards.front()).message); else - grantRewardWithMessage(rewards.front(), true); + grantRewardWithMessage(h, rewards.front(), true); break; } default: // multiple rewards. Act according to select mode { switch (configuration.selectMode) { case Rewardable::SELECT_PLAYER: // player must select - selectRewardsMessage(rewards, configuration.onSelect); + selectRewardWthMessage(h, rewards, configuration.onSelect); break; case Rewardable::SELECT_FIRST: // give first available - grantRewardWithMessage(rewards.front(), true); + grantRewardWithMessage(h, rewards.front(), true); break; case Rewardable::SELECT_RANDOM: // give random - grantRewardWithMessage(*RandomGeneratorUtil::nextItem(rewards, cb->gameState()->getRandomGenerator()), true); - break; - case Rewardable::SELECT_ALL: // give all rewards - for(auto i : rewards) - grantRewardWithMessage(i, i == rewards.size() - 1); + grantRewardWithMessage(h, *RandomGeneratorUtil::nextItem(rewards, cb->gameState()->getRandomGenerator()), true); break; } break; @@ -129,7 +126,7 @@ void CRewardableObject::onHeroVisit(const CGHeroInstance *h) const auto visitedRewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_ALREADY_VISITED); if (!visitedRewards.empty()) - grantRewardWithMessage(visitedRewards[0], false); + grantRewardWithMessage(h, visitedRewards[0], false); else logMod->warn("No applicable message for visiting already visited object!"); } diff --git a/lib/mapObjects/CRewardableObject.h b/lib/mapObjects/CRewardableObject.h index 761e93702..1c8e97c05 100644 --- a/lib/mapObjects/CRewardableObject.h +++ b/lib/mapObjects/CRewardableObject.h @@ -33,6 +33,9 @@ protected: bool wasVisitedBefore(const CGHeroInstance * contextHero) const; void serializeJsonOptions(JsonSerializeFormat & handler) override; + + virtual void grantRewardWithMessage(const CGHeroInstance * contextHero, int rewardIndex, bool markAsVisit) const; + virtual void selectRewardWthMessage(const CGHeroInstance * contextHero, const std::vector & rewardIndeces, const MetaString & dialog) const; public: /// Visitability checks. Note that hero check includes check for hero owner (returns true if object was visited by player) diff --git a/lib/rewardable/Configuration.h b/lib/rewardable/Configuration.h index de8764f86..72377da04 100644 --- a/lib/rewardable/Configuration.h +++ b/lib/rewardable/Configuration.h @@ -35,7 +35,6 @@ enum ESelectMode SELECT_FIRST, // first reward that matches limiters SELECT_PLAYER, // player can select from all allowed rewards SELECT_RANDOM, // one random reward from all mathing limiters - SELECT_ALL, // provides all allowed rewards matching limiters }; enum class EEventType @@ -46,7 +45,7 @@ enum class EEventType EVENT_NOT_AVAILABLE }; -const std::array SelectModeString{"selectFirst", "selectPlayer", "selectRandom", "selectAll"}; +const std::array SelectModeString{"selectFirst", "selectPlayer", "selectRandom"}; const std::array VisitModeString{"unlimited", "once", "hero", "bonus", "player"}; struct DLL_LINKAGE ResetInfo diff --git a/mapeditor/inspector/rewardswidget.cpp b/mapeditor/inspector/rewardswidget.cpp index 36813172f..84dd7e09b 100644 --- a/mapeditor/inspector/rewardswidget.cpp +++ b/mapeditor/inspector/rewardswidget.cpp @@ -147,12 +147,19 @@ RewardsWidget::RewardsWidget(const CMap & m, CRewardableObject & p, QWidget *par { ui->visitMode->setCurrentIndex(vstd::find_pos(Rewardable::VisitModeString, "once")); ui->visitMode->setEnabled(false); - ui->selectMode->setCurrentIndex(vstd::find_pos(Rewardable::SelectModeString, "selectAll")); + ui->selectMode->setCurrentIndex(vstd::find_pos(Rewardable::SelectModeString, "selectFirst")); ui->selectMode->setEnabled(false); ui->windowMode->setEnabled(false); ui->canRefuse->setEnabled(false); } + if(auto * e = dynamic_cast(&object)) + { + ui->selectMode->setEnabled(true); + if(!e->removeAfterVisit) + ui->visitMode->setCurrentIndex(vstd::find_pos(Rewardable::VisitModeString, "unlimited")); + } + if(dynamic_cast(&object)) { ui->visitMode->setCurrentIndex(vstd::find_pos(Rewardable::VisitModeString, "once")); @@ -512,7 +519,8 @@ void RewardsWidget::on_removeVisitInfo_clicked() delete ui->visitInfoList->currentItem(); ui->visitInfoList->blockSignals(false); on_visitInfoList_itemSelectionChanged(); - loadCurrentVisitInfo(ui->visitInfoList->currentRow()); + if(ui->visitInfoList->currentItem()) + loadCurrentVisitInfo(ui->visitInfoList->currentRow()); } void RewardsWidget::on_selectMode_currentIndexChanged(int index) From 2960895041c4789b99134a44021118e0893b573d Mon Sep 17 00:00:00 2001 From: nordsoft Date: Sun, 17 Sep 2023 22:19:45 +0200 Subject: [PATCH 17/18] Issues fixed --- lib/CCreatureSet.cpp | 5 +- lib/CCreatureSet.h | 7 ++- lib/mapObjects/CArmedInstance.cpp | 6 +++ lib/mapObjects/CArmedInstance.h | 3 ++ lib/mapObjects/CGHeroInstance.cpp | 5 +- lib/mapObjects/CGPandoraBox.cpp | 32 ++++++++++-- lib/mapObjects/CGTownInstance.cpp | 7 ++- lib/mapObjects/CQuest.cpp | 2 +- lib/mapObjects/CQuest.h | 2 +- lib/mapObjects/MiscObjects.cpp | 12 +++-- lib/rewardable/Limiter.cpp | 52 +++---------------- lib/rewardable/Reward.cpp | 74 +++++---------------------- lib/serializer/JsonSerializeFormat.h | 32 ++++++++++-- mapeditor/inspector/rewardswidget.cpp | 10 +++- 14 files changed, 117 insertions(+), 132 deletions(-) diff --git a/lib/CCreatureSet.cpp b/lib/CCreatureSet.cpp index ec09548c0..5bc12977f 100644 --- a/lib/CCreatureSet.cpp +++ b/lib/CCreatureSet.cpp @@ -624,12 +624,13 @@ void CCreatureSet::armyChanged() } -void CCreatureSet::serializeJson(JsonSerializeFormat & handler, const std::string & fieldName, const std::optional fixedSize) +void CCreatureSet::serializeJson(JsonSerializeFormat & handler, const std::string & armyFieldName, const std::optional fixedSize) { if(handler.saving && stacks.empty()) return; - auto a = handler.enterArray(fieldName); + handler.serializeEnum("formation", formation, NArmyFormation::names); + auto a = handler.enterArray(armyFieldName); if(handler.saving) diff --git a/lib/CCreatureSet.h b/lib/CCreatureSet.h index e72d0eb14..2c4944f05 100644 --- a/lib/CCreatureSet.h +++ b/lib/CCreatureSet.h @@ -206,6 +206,11 @@ enum class EArmyFormation : uint8_t TIGHT }; +namespace NArmyFormation +{ + static const std::vector names{ "wide", "tight" }; +} + class DLL_LINKAGE CCreatureSet : public IArmyDescriptor //seven combined creatures { CCreatureSet(const CCreatureSet &) = delete; @@ -284,7 +289,7 @@ public: h & formation; } - void serializeJson(JsonSerializeFormat & handler, const std::string & fieldName, const std::optional fixedSize = std::nullopt); + void serializeJson(JsonSerializeFormat & handler, const std::string & armyFieldName, const std::optional fixedSize = std::nullopt); operator bool() const { diff --git a/lib/mapObjects/CArmedInstance.cpp b/lib/mapObjects/CArmedInstance.cpp index 37f7fd950..bc13a206d 100644 --- a/lib/mapObjects/CArmedInstance.cpp +++ b/lib/mapObjects/CArmedInstance.cpp @@ -160,4 +160,10 @@ const IBonusBearer* CArmedInstance::getBonusBearer() const return this; } +void CArmedInstance::serializeJsonOptions(JsonSerializeFormat & handler) +{ + CGObjectInstance::serializeJsonOptions(handler); + CCreatureSet::serializeJson(handler, "army", 7); +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/CArmedInstance.h b/lib/mapObjects/CArmedInstance.h index e14447c55..378dcf96c 100644 --- a/lib/mapObjects/CArmedInstance.h +++ b/lib/mapObjects/CArmedInstance.h @@ -18,6 +18,7 @@ VCMI_LIB_NAMESPACE_BEGIN class BattleInfo; class CGameState; +class JsonSerializeFormat; class DLL_LINKAGE CArmedInstance: public CGObjectInstance, public CBonusSystemNode, public CCreatureSet, public IConstBonusProvider { @@ -48,6 +49,8 @@ public: { return this->tempOwner; } + + void serializeJsonOptions(JsonSerializeFormat & handler) override; template void serialize(Handler &h, const int version) { diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index e8cde5e39..569ae1701 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -1709,10 +1709,7 @@ void CGHeroInstance::serializeJsonOptions(JsonSerializeFormat & handler) setHeroTypeName(typeName); } - static const std::vector FORMATIONS = { "wide", "tight" }; - - CCreatureSet::serializeJson(handler, "army", 7); - handler.serializeEnum("formation", formation, FORMATIONS); + CArmedInstance::serializeJsonOptions(handler); { static constexpr int NO_PATROLING = -1; diff --git a/lib/mapObjects/CGPandoraBox.cpp b/lib/mapObjects/CGPandoraBox.cpp index b7538195a..48910fa20 100644 --- a/lib/mapObjects/CGPandoraBox.cpp +++ b/lib/mapObjects/CGPandoraBox.cpp @@ -28,9 +28,15 @@ VCMI_LIB_NAMESPACE_BEGIN void CGPandoraBox::init() { blockVisit = true; + configuration.info.emplace_back(); + configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; for(auto & i : configuration.info) + { i.reward.removeObject = true; + if(!message.empty() && i.message.empty()) + i.message = MetaString::createFromRawString(message); + } } void CGPandoraBox::initObj(CRandomGenerator & rand) @@ -202,14 +208,17 @@ void CGPandoraBox::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answe void CGPandoraBox::serializeJsonOptions(JsonSerializeFormat & handler) { CRewardableObject::serializeJsonOptions(handler); + handler.serializeString("guardMessage", message); if(!handler.saving) { - //backward compatibility - CCreatureSet::serializeJson(handler, "guards", 7); - configuration.info.emplace_back(); - Rewardable::VisitInfo & vinfo = configuration.info.back(); + //backward compatibility for VCMI maps that use old Pandora Box format + if(!handler.getCurrent()["guards"].Vector().empty()) + CCreatureSet::serializeJson(handler, "guards", 7); + + bool hasSomething = false; + Rewardable::VisitInfo vinfo; vinfo.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; handler.serializeInt("experience", vinfo.reward.heroExperience, 0); @@ -230,6 +239,8 @@ void CGPandoraBox::serializeJsonOptions(JsonSerializeFormat & handler) for(int idx = 0; idx < vinfo.reward.primary.size(); idx ++) { handler.serializeInt(NPrimarySkill::names[idx], vinfo.reward.primary[idx], 0); + if(vinfo.reward.primary[idx]) + hasSomething = true; } } @@ -261,6 +272,19 @@ void CGPandoraBox::serializeJsonOptions(JsonSerializeFormat & handler) vinfo.reward.secondary[rawId] = level; } } + + hasSomething = hasSomething + || vinfo.reward.heroExperience + || vinfo.reward.manaDiff + || vinfo.reward.resources.nonZero() + || !vinfo.reward.bonuses.empty() + || !vinfo.reward.artifacts.empty() + || !vinfo.reward.secondary.empty() + || !vinfo.reward.artifacts.empty() + || !vinfo.reward.creatures.empty(); + + if(hasSomething) + configuration.info.push_back(vinfo); } } diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 9363a29cb..b82fe8d68 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -1093,11 +1093,10 @@ void CGTownInstance::reset() void CGTownInstance::serializeJsonOptions(JsonSerializeFormat & handler) { - static const std::vector FORMATIONS = { "wide", "tight" }; - CGObjectInstance::serializeJsonOwner(handler); - CCreatureSet::serializeJson(handler, "army", 7); - handler.serializeEnum("tightFormation", formation, FORMATIONS); + if(!handler.saving) + handler.serializeEnum("tightFormation", formation, NArmyFormation::names); //for old format + CArmedInstance::serializeJsonOptions(handler); handler.serializeString("name", name); { diff --git a/lib/mapObjects/CQuest.cpp b/lib/mapObjects/CQuest.cpp index 3dae6194b..13872ea81 100644 --- a/lib/mapObjects/CQuest.cpp +++ b/lib/mapObjects/CQuest.cpp @@ -799,7 +799,7 @@ void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler) if(!handler.saving) { - //backward compatibility + //backward compatibility for VCMI maps that use old SeerHut format auto s = handler.enterStruct("reward"); const JsonNode & rewardsJson = handler.getCurrent(); diff --git a/lib/mapObjects/CQuest.h b/lib/mapObjects/CQuest.h index 7cb0df2bc..3885229dd 100644 --- a/lib/mapObjects/CQuest.h +++ b/lib/mapObjects/CQuest.h @@ -138,7 +138,7 @@ protected: void afterAddToMapCommon(CMap * map) const; }; -class DLL_LINKAGE CGSeerHut : public CRewardableObject, public IQuestObject //army is used when giving reward +class DLL_LINKAGE CGSeerHut : public CRewardableObject, public IQuestObject { public: std::string seerName; diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index 24c32a4e4..fe2b70f5e 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -196,7 +196,7 @@ void CGMine::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) con void CGMine::serializeJsonOptions(JsonSerializeFormat & handler) { - CCreatureSet::serializeJson(handler, "army", 7); + CArmedInstance::serializeJsonOptions(handler); if(isAbandoned()) { @@ -316,7 +316,9 @@ void CGResource::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) void CGResource::serializeJsonOptions(JsonSerializeFormat & handler) { - CCreatureSet::serializeJson(handler, "guards", 7); + CArmedInstance::serializeJsonOptions(handler); + if(!handler.saving && !handler.getCurrent()["guards"].Vector().empty()) + CCreatureSet::serializeJson(handler, "guards", 7); handler.serializeInt("amount", amount, 0); handler.serializeString("guardMessage", message); } @@ -827,7 +829,9 @@ void CGArtifact::afterAddToMap(CMap * map) void CGArtifact::serializeJsonOptions(JsonSerializeFormat& handler) { handler.serializeString("guardMessage", message); - CCreatureSet::serializeJson(handler, "guards" ,7); + CArmedInstance::serializeJsonOptions(handler); + if(!handler.saving && !handler.getCurrent()["guards"].Vector().empty()) + CCreatureSet::serializeJson(handler, "guards", 7); if(handler.saving && ID == Obj::SPELL_SCROLL) { @@ -1233,7 +1237,7 @@ void CGGarrison::serializeJsonOptions(JsonSerializeFormat& handler) { handler.serializeBool("removableUnits", removableUnits); serializeJsonOwner(handler); - CCreatureSet::serializeJson(handler, "army", 7); + CArmedInstance::serializeJsonOptions(handler); } void CGMagi::reset() diff --git a/lib/rewardable/Limiter.cpp b/lib/rewardable/Limiter.cpp index 9d837ccb8..bf3fb2911 100644 --- a/lib/rewardable/Limiter.cpp +++ b/lib/rewardable/Limiter.cpp @@ -133,56 +133,18 @@ void Rewardable::Limiter::serializeJson(JsonSerializeFormat & handler) handler.serializeInt("manaPoints", manaPoints); handler.serializeIdArray("artifacts", artifacts); handler.enterArray("creatures").serializeStruct(creatures); - { - auto a = handler.enterArray("primary"); - a.syncSize(primary); - for(int i = 0; i < primary.size(); ++i) - a.serializeInt(i, primary[i]); - } - + handler.enterArray("primary").serializeArray(primary); { auto a = handler.enterArray("secondary"); - std::vector> fieldValue; - if(handler.saving) + std::vector> fieldValue(secondary.begin(), secondary.end()); + a.serializeStruct>(fieldValue, [](JsonSerializeFormat & h, std::pair & e) { - for(auto & i : secondary) - { - auto key = VLC->skillh->encodeSkill(i.first); - auto value = NSecondarySkill::levels.at(i.second); - fieldValue.emplace_back(key, value); - } - } + h.serializeId("skill", e.first, SecondarySkill{}, VLC->skillh->decodeSkill, VLC->skillh->encodeSkill); + h.serializeId("level", e.second, 0, [](const std::string & i){return vstd::find_pos(NSecondarySkill::levels, i);}, [](si32 i){return NSecondarySkill::levels.at(i);}); + }); a.syncSize(fieldValue); - for(int i = 0; i < fieldValue.size(); ++i) - { - auto e = a.enterStruct(i); - e->serializeString("skill", fieldValue[i].first); - e->serializeString("level", fieldValue[i].second); - } - if(!handler.saving) - { - for(auto & i : fieldValue) - { - const int skillId = VLC->skillh->decodeSkill(i.first); - if(skillId < 0) - { - logGlobal->error("Invalid secondary skill %s", i.first); - continue; - } - - const int level = vstd::find_pos(NSecondarySkill::levels, i.second); - if(level < 0) - { - logGlobal->error("Invalid secondary skill level%s", i.second); - continue; - } - - secondary[SecondarySkill(skillId)] = level; - } - - } + secondary = std::map(fieldValue.begin(), fieldValue.end()); } - //sublimiters auto serializeSublimitersList = [&handler](const std::string & field, LimitersList & container) { diff --git a/lib/rewardable/Reward.cpp b/lib/rewardable/Reward.cpp index 07da516cd..bbdbf3cdc 100644 --- a/lib/rewardable/Reward.cpp +++ b/lib/rewardable/Reward.cpp @@ -119,76 +119,28 @@ void Rewardable::Reward::serializeJson(JsonSerializeFormat & handler) handler.serializeIdArray("artifacts", artifacts); handler.serializeIdArray("spells", spells); handler.enterArray("creatures").serializeStruct(creatures); - { - auto a = handler.enterArray("primary"); - a.syncSize(primary); - for(int i = 0; i < primary.size(); ++i) - a.serializeInt(i, primary[i]); - } - + handler.enterArray("primary").serializeArray(primary); { auto a = handler.enterArray("secondary"); - std::vector> fieldValue; - if(handler.saving) + std::vector> fieldValue(secondary.begin(), secondary.end()); + a.serializeStruct>(fieldValue, [](JsonSerializeFormat & h, std::pair & e) { - for(auto & i : secondary) - { - auto key = VLC->skillh->encodeSkill(i.first); - auto value = NSecondarySkill::levels.at(i.second); - fieldValue.emplace_back(key, value); - } - } + h.serializeId("skill", e.first, SecondarySkill{}, VLC->skillh->decodeSkill, VLC->skillh->encodeSkill); + h.serializeId("level", e.second, 0, [](const std::string & i){return vstd::find_pos(NSecondarySkill::levels, i);}, [](si32 i){return NSecondarySkill::levels.at(i);}); + }); a.syncSize(fieldValue); - for(int i = 0; i < fieldValue.size(); ++i) - { - auto e = a.enterStruct(i); - e->serializeString("skill", fieldValue[i].first); - e->serializeString("level", fieldValue[i].second); - } - if(!handler.saving) - { - for(auto & i : fieldValue) - { - const int skillId = VLC->skillh->decodeSkill(i.first); - if(skillId < 0) - { - logGlobal->error("Invalid secondary skill %s", i.first); - continue; - } - - const int level = vstd::find_pos(NSecondarySkill::levels, i.second); - if(level < 0) - { - logGlobal->error("Invalid secondary skill level%s", i.second); - continue; - } - - secondary[SecondarySkill(skillId)] = level; - } - - } + secondary = std::map(fieldValue.begin(), fieldValue.end()); } { auto a = handler.enterArray("creaturesChange"); - std::vector> fieldValue; - if(handler.saving) + std::vector> fieldValue(creaturesChange.begin(), creaturesChange.end()); + a.serializeStruct>(fieldValue, [](JsonSerializeFormat & h, std::pair & e) { - for(auto & i : creaturesChange) - fieldValue.push_back(i); - } - a.syncSize(fieldValue); - for(int i = 0; i < fieldValue.size(); ++i) - { - auto e = a.enterStruct(i); - e->serializeId("creature", fieldValue[i].first, CreatureID{}); - e->serializeId("amount", fieldValue[i].second, CreatureID{}); - } - if(!handler.saving) - { - for(auto & i : fieldValue) - creaturesChange[i.first] = i.second; - } + h.serializeId("creature", e.first, CreatureID{}); + h.serializeId("amount", e.second, CreatureID{}); + }); + creaturesChange = std::map(fieldValue.begin(), fieldValue.end()); } { diff --git a/lib/serializer/JsonSerializeFormat.h b/lib/serializer/JsonSerializeFormat.h index 68533203a..c5b274005 100644 --- a/lib/serializer/JsonSerializeFormat.h +++ b/lib/serializer/JsonSerializeFormat.h @@ -74,18 +74,44 @@ public: ///String <-> Json string void serializeString(const size_t index, std::string & value); - ///vector of serializable <-> Json vector of structs + ///vector of anything int-convertible <-> Json vector of integers + template + void serializeArray(std::vector & value) + { + syncSize(value, JsonNode::JsonType::DATA_STRUCT); + + for(size_t idx = 0; idx < size(); idx++) + serializeInt(idx, value[idx]); + } + + ///vector of strings <-> Json vector of strings + void serializeArray(std::vector & value) + { + syncSize(value, JsonNode::JsonType::DATA_STRUCT); + + for(size_t idx = 0; idx < size(); idx++) + serializeString(idx, value[idx]); + } + + ///vector of anything with custom serializing function <-> Json vector of structs template - void serializeStruct(std::vector & value) + void serializeStruct(std::vector & value, std::function serializer) { syncSize(value, JsonNode::JsonType::DATA_STRUCT); for(size_t idx = 0; idx < size(); idx++) { auto s = enterStruct(idx); - value[idx].serializeJson(*owner); + serializer(*owner, value[idx]); } } + + ///vector of serializable <-> Json vector of structs + template + void serializeStruct(std::vector & value) + { + serializeStruct(value, [](JsonSerializeFormat & h, Element & e){e.serializeJson(h);}); + } void resize(const size_t newSize); void resize(const size_t newSize, JsonNode::JsonType type); diff --git a/mapeditor/inspector/rewardswidget.cpp b/mapeditor/inspector/rewardswidget.cpp index 84dd7e09b..60d70d0c1 100644 --- a/mapeditor/inspector/rewardswidget.cpp +++ b/mapeditor/inspector/rewardswidget.cpp @@ -208,7 +208,10 @@ bool RewardsWidget::commitChanges() object.configuration.visitMode = ui->visitMode->currentIndex(); object.configuration.selectMode = ui->selectMode->currentIndex(); object.configuration.infoWindowType = EInfoWindowMode(ui->windowMode->currentIndex()); - object.configuration.onSelect = MetaString::createFromRawString(ui->onSelectText->text().toStdString()); + if(ui->onSelectText->text().isEmpty()) + object.configuration.onSelect.clear(); + else + object.configuration.onSelect = MetaString::createFromRawString(ui->onSelectText->text().toStdString()); object.configuration.canRefuse = ui->canRefuse->isChecked(); //reset parameters @@ -226,7 +229,10 @@ void RewardsWidget::saveCurrentVisitInfo(int index) { auto & vinfo = object.configuration.info.at(index); vinfo.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT; - vinfo.message = MetaString::createFromRawString(ui->rewardMessage->text().toStdString()); + if(ui->rewardMessage->text().isEmpty()) + vinfo.message.clear(); + else + vinfo.message = MetaString::createFromRawString(ui->rewardMessage->text().toStdString()); vinfo.reward.heroLevel = ui->rHeroLevel->value(); vinfo.reward.heroExperience = ui->rHeroExperience->value(); From 326791886d65ee65f1325f6846e84305a2c7a4f9 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Tue, 19 Sep 2023 17:11:03 +0200 Subject: [PATCH 18/18] Rename and events modal mode always --- lib/mapObjects/CGPandoraBox.cpp | 1 + lib/mapObjects/CRewardableObject.cpp | 12 ++++++------ lib/mapObjects/CRewardableObject.h | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/mapObjects/CGPandoraBox.cpp b/lib/mapObjects/CGPandoraBox.cpp index 48910fa20..5a4551347 100644 --- a/lib/mapObjects/CGPandoraBox.cpp +++ b/lib/mapObjects/CGPandoraBox.cpp @@ -291,6 +291,7 @@ void CGPandoraBox::serializeJsonOptions(JsonSerializeFormat & handler) void CGEvent::init() { blockVisit = false; + configuration.infoWindowType = EInfoWindowMode::MODAL; for(auto & i : configuration.info) { diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp index 8df14fd7d..ad3ebc5c6 100644 --- a/lib/mapObjects/CRewardableObject.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -49,18 +49,18 @@ void CRewardableObject::grantRewardWithMessage(const CGHeroInstance * contextHer grantReward(index, contextHero); } -void CRewardableObject::selectRewardWthMessage(const CGHeroInstance * contextHero, const std::vector & rewardIndeces, const MetaString & dialog) const +void CRewardableObject::selectRewardWthMessage(const CGHeroInstance * contextHero, const std::vector & rewardIndices, const MetaString & dialog) const { - BlockingDialog sd(configuration.canRefuse, rewardIndeces.size() > 1); + BlockingDialog sd(configuration.canRefuse, rewardIndices.size() > 1); sd.player = contextHero->tempOwner; sd.text = dialog; - if (rewardIndeces.size() > 1) - for (auto index : rewardIndeces) + if (rewardIndices.size() > 1) + for (auto index : rewardIndices) sd.components.push_back(configuration.info.at(index).reward.getDisplayedComponent(contextHero)); - if (rewardIndeces.size() == 1) - configuration.info.at(rewardIndeces.front()).reward.loadComponents(sd.components, contextHero); + if (rewardIndices.size() == 1) + configuration.info.at(rewardIndices.front()).reward.loadComponents(sd.components, contextHero); cb->showBlockingDialog(&sd); } diff --git a/lib/mapObjects/CRewardableObject.h b/lib/mapObjects/CRewardableObject.h index 1c8e97c05..86040a89b 100644 --- a/lib/mapObjects/CRewardableObject.h +++ b/lib/mapObjects/CRewardableObject.h @@ -35,7 +35,7 @@ protected: void serializeJsonOptions(JsonSerializeFormat & handler) override; virtual void grantRewardWithMessage(const CGHeroInstance * contextHero, int rewardIndex, bool markAsVisit) const; - virtual void selectRewardWthMessage(const CGHeroInstance * contextHero, const std::vector & rewardIndeces, const MetaString & dialog) const; + virtual void selectRewardWthMessage(const CGHeroInstance * contextHero, const std::vector & rewardIndices, const MetaString & dialog) const; public: /// Visitability checks. Note that hero check includes check for hero owner (returns true if object was visited by player)