1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-11-06 09:09:40 +02:00

Implemented option to emulate H3 seer hut full-army quest bug

This commit is contained in:
Ivan Savenko
2025-05-07 19:16:58 +03:00
parent 69de14a42f
commit 0e2ea99283
12 changed files with 53 additions and 23 deletions

View File

@@ -188,7 +188,7 @@ public:
void giveResources(PlayerColor player, TResources resources) override {};
void giveCreatures(const CArmedInstance * objid, const CGHeroInstance * h, const CCreatureSet & creatures, bool remove) override {};
void takeCreatures(ObjectInstanceID objid, const std::vector<CStackBasicDescriptor> & creatures) override {};
void takeCreatures(ObjectInstanceID objid, const std::vector<CStackBasicDescriptor> & creatures, bool forceRemoval) override {};
bool changeStackType(const StackLocation & sl, const CCreature * c) override {return false;};
bool changeStackCount(const StackLocation & sl, TQuantity count, bool absoluteValue = false) override {return false;};
bool insertNewStack(const StackLocation & sl, const CCreature * c, TQuantity count) override {return false;};

View File

@@ -463,6 +463,14 @@
"mergeOnRecruit" : true
},
"mapObjects" :
{
// Allow behavior that emulates h3 bug where quest from Seer Hut or Border Guard can take entire army from hero
// WARNING: handling of heroes without armies is not tested and may lead to bugs or crashes! Use at own risk!
// If this option is off, quests will only allow taking entire army if quest reward also gives creatures
"h3BugQuestTakesEntireArmy" : false
},
"markets" :
{
// period between restocking of "Black Market" object found on adventure map

View File

@@ -90,6 +90,7 @@ const std::vector<GameSettings::SettingOption> GameSettings::settingProperties =
{EGameSettings::MAP_FORMAT_JSON_VCMI, "mapFormat", "jsonVCMI" },
{EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA, "mapFormat", "restorationOfErathia" },
{EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH, "mapFormat", "shadowOfDeath" },
{EGameSettings::MAP_OBJECTS_H3_BUG_QUEST_TAKES_ENTIRE_ARMY, "mapObjects","h3BugQuestTakesEntireArmy" },
{EGameSettings::MARKETS_BLACK_MARKET_RESTOCK_PERIOD, "markets", "blackMarketRestockPeriod" },
{EGameSettings::MODULE_COMMANDERS, "modules", "commanders" },
{EGameSettings::MODULE_STACK_ARTIFACT, "modules", "stackArtifact" },

View File

@@ -103,7 +103,7 @@ public:
virtual void giveResources(PlayerColor player, TResources resources)=0;
virtual void giveCreatures(const CArmedInstance *objid, const CGHeroInstance * h, const CCreatureSet &creatures, bool remove) =0;
virtual void takeCreatures(ObjectInstanceID objid, const std::vector<CStackBasicDescriptor> &creatures) =0;
virtual void takeCreatures(ObjectInstanceID objid, const std::vector<CStackBasicDescriptor> &creatures, bool forceRemoval = false) =0;
virtual bool changeStackCount(const StackLocation &sl, TQuantity count, bool absoluteValue = false) =0;
virtual bool changeStackType(const StackLocation &sl, const CCreature *c) =0;
virtual bool insertNewStack(const StackLocation &sl, const CCreature *c, TQuantity count = -1) =0; //count -1 => moves whole stack

View File

@@ -63,6 +63,7 @@ enum class EGameSettings
MAP_FORMAT_JSON_VCMI,
MAP_FORMAT_RESTORATION_OF_ERATHIA,
MAP_FORMAT_SHADOW_OF_DEATH,
MAP_OBJECTS_H3_BUG_QUEST_TAKES_ENTIRE_ARMY,
MARKETS_BLACK_MARKET_RESTOCK_PERIOD,
MODULE_COMMANDERS,
MODULE_STACK_ARTIFACT,

View File

@@ -17,6 +17,7 @@
#include "../texts/CGeneralTextHandler.h"
#include "CGCreature.h"
#include "../IGameCallback.h"
#include "../IGameSettings.h"
#include "../entities/artifact/CArtifact.h"
#include "../entities/hero/CHeroHandler.h"
#include "../mapObjectConstructors/CObjectClassesHandler.h"
@@ -118,7 +119,7 @@ bool CQuest::checkQuest(const CGHeroInstance * h) const
return true;
}
void CQuest::completeQuest(IGameCallback * cb, const CGHeroInstance *h) const
void CQuest::completeQuest(IGameCallback * cb, const CGHeroInstance *h, bool allowFullArmyRemoval) const
{
// FIXME: this should be part of 'reward', and not hacking into limiter state that should only limit access to such reward
@@ -152,7 +153,7 @@ void CQuest::completeQuest(IGameCallback * cb, const CGHeroInstance *h) const
logGlobal->error("Failed to find artifact %s in inventory of hero %s", elem.toEntity(LIBRARY)->getJsonKey(), h->getHeroTypeID());
}
cb->takeCreatures(h->id, mission.creatures);
cb->takeCreatures(h->id, mission.creatures, allowFullArmyRemoval);
cb->giveResources(h->getOwner(), -mission.resources);
}
@@ -435,7 +436,8 @@ void CGSeerHut::init(vstd::RNG & rand)
seerName = LIBRARY->generaltexth->translate(seerNameID);
getQuest().textOption = rand.nextInt(2);
getQuest().completedOption = rand.nextInt(1, 3);
getQuest().mission.hasExtraCreatures = !allowsFullArmyRemoval();
configuration.canRefuse = true;
configuration.visitMode = Rewardable::EVisitMode::VISIT_ONCE;
configuration.selectMode = Rewardable::ESelectMode::SELECT_PLAYER;
@@ -645,14 +647,21 @@ const CGCreature * CGSeerHut::getCreatureToKill(bool allowNull) const
return dynamic_cast<const CGCreature *>(o);
}
bool CGSeerHut::allowsFullArmyRemoval() const
{
bool seerGivesUnits = !configuration.info.empty() && !configuration.info.back().reward.creatures.empty();
bool h3BugSettingEnabled = cb->getSettings().getBoolean(EGameSettings::MAP_OBJECTS_H3_BUG_QUEST_TAKES_ENTIRE_ARMY);
return seerGivesUnits || h3BugSettingEnabled;
}
void CGSeerHut::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const
{
CRewardableObject::blockingDialogAnswered(hero, answer);
if(answer)
{
getQuest().completeQuest(cb, hero);
getQuest().completeQuest(cb, hero, allowsFullArmyRemoval());
cb->setObjPropertyValue(id, ObjProperty::SEERHUT_COMPLETE, !getQuest().repeatedQuest); //mission complete
}
CRewardableObject::blockingDialogAnswered(hero, answer);
}
void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler)
@@ -736,6 +745,7 @@ void CGQuestGuard::init(vstd::RNG & rand)
blockVisit = true;
getQuest().textOption = rand.nextInt(3, 5);
getQuest().completedOption = rand.nextInt(4, 5);
getQuest().mission.hasExtraCreatures = !allowsFullArmyRemoval();
configuration.info.push_back({});
configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;

View File

@@ -80,7 +80,7 @@ public:
void getVisitText(const CGameInfoCallback * cb, MetaString &text, std::vector<Component> & components, bool FirstVisit, const CGHeroInstance * h = nullptr) const;
void getCompletionText(const CGameInfoCallback * cb, MetaString &text) const;
void getRolloverText (const CGameInfoCallback * cb, MetaString &text, bool onHover) const; //hover or quest log entry
void completeQuest(IGameCallback *, const CGHeroInstance * h) const;
void completeQuest(IGameCallback *, const CGHeroInstance * h, bool allowFullArmyRemoval) const;
void addTextReplacements(const CGameInfoCallback * cb, MetaString &out, std::vector<Component> & components) const;
void addKillTargetReplacements(MetaString &out) const;
void defineQuestName();
@@ -166,6 +166,7 @@ public:
h & seerName;
}
protected:
bool allowsFullArmyRemoval() const;
void setPropertyDer(ObjProperty what, ObjPropertyID identifier) override;
void serializeJsonOptions(JsonSerializeFormat & handler) override;

View File

@@ -2429,7 +2429,6 @@ EQuestMission CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & positi
{
size_t typeNumber = reader->readUInt8();
guard->getQuest().mission.creatures.resize(typeNumber);
guard->getQuest().mission.hasExtraCreatures = true;
for(size_t hh = 0; hh < typeNumber; ++hh)
{
guard->getQuest().mission.creatures[hh].setType(reader->readCreature().toCreature());

View File

@@ -218,7 +218,7 @@ void Rewardable::Interface::grantRewardAfterLevelup(const Rewardable::VisitInfo
if (!info.reward.takenCreatures.empty())
{
cb->takeCreatures(hero->id, info.reward.takenCreatures);
cb->takeCreatures(hero->id, info.reward.takenCreatures, !info.reward.creatures.empty());
}
if(!info.reward.creaturesChange.empty())

View File

@@ -1157,26 +1157,36 @@ void CGameHandler::giveCreatures(const CArmedInstance *obj, const CGHeroInstance
tryJoiningArmy(obj, h, remove, true);
}
void CGameHandler::takeCreatures(ObjectInstanceID objid, const std::vector<CStackBasicDescriptor> &creatures)
void CGameHandler::takeCreatures(ObjectInstanceID objid, const std::vector<CStackBasicDescriptor> &creatures, bool forceRemoval)
{
std::vector<CStackBasicDescriptor> cres = creatures;
if (cres.size() <= 0)
std::vector<CStackBasicDescriptor> remainerForTaking = creatures;
if (remainerForTaking.empty())
return;
const CArmedInstance* obj = static_cast<const CArmedInstance*>(getObj(objid));
for (CStackBasicDescriptor &sbd : cres)
const CArmedInstance* army = static_cast<const CArmedInstance*>(getObj(objid));
for (const CStackBasicDescriptor &stackToTake : remainerForTaking)
{
TQuantity collected = 0;
while(collected < sbd.getCount())
while(collected < stackToTake.getCount())
{
bool foundSth = false;
for (auto i = obj->Slots().begin(); i != obj->Slots().end(); i++)
for (const auto & armySlot : army->Slots())
{
if (i->second->getType() == sbd.getType())
if (armySlot.second->getType() == stackToTake.getType())
{
TQuantity take = std::min(sbd.getCount() - collected, i->second->getCount()); //collect as much cres as we can
changeStackCount(StackLocation(obj->id, i->first), -take, false);
collected += take;
if (stackToTake.getCount() - collected >= armySlot.second->getCount())
{
// take entire stack
collected += armySlot.second->getCount();
eraseStack(StackLocation(army->id, armySlot.first), forceRemoval);
}
else
{
// take part of the stack
collected = stackToTake.getCount();
changeStackCount(StackLocation(army->id, armySlot.first), collected - stackToTake.getCount(), false);
}
foundSth = true;
break;
}

View File

@@ -125,7 +125,7 @@ public:
void giveResources(PlayerColor player, TResources resources) override;
void giveCreatures(const CArmedInstance *objid, const CGHeroInstance * h, const CCreatureSet &creatures, bool remove) override;
void takeCreatures(ObjectInstanceID objid, const std::vector<CStackBasicDescriptor> &creatures) override;
void takeCreatures(ObjectInstanceID objid, const std::vector<CStackBasicDescriptor> &creatures, bool forceRemoval) override;
bool changeStackType(const StackLocation &sl, const CCreature *c) override;
bool changeStackCount(const StackLocation &sl, TQuantity count, bool absoluteValue = false) override;
bool insertNewStack(const StackLocation &sl, const CCreature *c, TQuantity count) override;

View File

@@ -62,7 +62,7 @@ public:
void giveResources(PlayerColor player, TResources resources) override {}
void giveCreatures(const CArmedInstance *objid, const CGHeroInstance * h, const CCreatureSet &creatures, bool remove) override {}
void takeCreatures(ObjectInstanceID objid, const std::vector<CStackBasicDescriptor> &creatures) override {}
void takeCreatures(ObjectInstanceID objid, const std::vector<CStackBasicDescriptor> &creatures, bool forceRemoval) override {}
bool changeStackCount(const StackLocation &sl, TQuantity count, bool absoluteValue = false) override {return false;}
bool changeStackType(const StackLocation &sl, const CCreature *c) override {return false;}
bool insertNewStack(const StackLocation &sl, const CCreature *c, TQuantity count = -1) override {return false;} //count -1 => moves whole stack