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

Remove quests from CMap, now solely owned by quest objects

This commit is contained in:
Ivan Savenko
2025-03-18 19:54:09 +00:00
parent d9aabb47e6
commit ab11d2b075
27 changed files with 305 additions and 314 deletions

View File

@@ -657,7 +657,7 @@ bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObject
{
for(auto q : ai->cb->getMyQuests())
{
if(q.obj == obj)
if(q.obj == obj->id)
{
return false; // do not visit guards or gates when wandering
}
@@ -670,9 +670,9 @@ bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObject
{
for(auto q : ai->cb->getMyQuests())
{
if(q.obj == obj)
if(q.obj == obj->id)
{
if(q.quest->checkQuest(h))
if(q.getQuest(cb)->checkQuest(h))
return true; //we completed the quest
else
return false; //we can't complete this quest

View File

@@ -22,7 +22,8 @@ using namespace Goals;
bool isKeyMaster(const QuestInfo & q)
{
return q.obj && (q.obj->ID == Obj::BORDER_GATE || q.obj->ID == Obj::BORDERGUARD);
auto object = q.getObject(cb);
return object && (object->ID == Obj::BORDER_GATE || object->ID == Obj::BORDERGUARD);
}
std::string CompleteQuest::toString() const
@@ -38,27 +39,28 @@ TGoalVec CompleteQuest::decompose(const Nullkiller * ai) const
}
logAi->debug("Trying to realize quest: %s", questToString());
if(!q.quest->mission.artifacts.empty())
auto quest = q.getQuest(cb);
if(!quest->mission.artifacts.empty())
return missionArt(ai);
if(!q.quest->mission.heroes.empty())
if(!quest->mission.heroes.empty())
return missionHero(ai);
if(!q.quest->mission.creatures.empty())
if(!quest->mission.creatures.empty())
return missionArmy(ai);
if(q.quest->mission.resources.nonZero())
if(quest->mission.resources.nonZero())
return missionResources(ai);
if(q.quest->killTarget != ObjectInstanceID::NONE)
if(quest->killTarget != ObjectInstanceID::NONE)
return missionDestroyObj(ai);
for(auto & s : q.quest->mission.primary)
for(auto & s : quest->mission.primary)
if(s)
return missionIncreasePrimaryStat(ai);
if(q.quest->mission.heroLevel > 0)
if(quest->mission.heroLevel > 0)
return missionLevel(ai);
return TGoalVec();
@@ -68,52 +70,52 @@ bool CompleteQuest::operator==(const CompleteQuest & other) const
{
if(isKeyMaster(q))
{
return isKeyMaster(other.q) && q.obj->subID == other.q.obj->subID;
return isKeyMaster(other.q) && q.getObject(cb)->subID == other.q.getObject(cb)->subID;
}
else if(isKeyMaster(other.q))
{
return false;
}
return q.quest->qid == other.q.quest->qid;
return q.getQuest(cb) == other.q.getQuest(cb);
}
uint64_t CompleteQuest::getHash() const
{
if(isKeyMaster(q))
{
return q.obj->subID;
return q.getObject(cb)->subID;
}
return q.quest->qid;
return q.getObject(cb)->id;
}
std::string CompleteQuest::questToString() const
{
if(isKeyMaster(q))
{
return "find " + LIBRARY->generaltexth->tentColors[q.obj->subID] + " keymaster tent";
return "find " + LIBRARY->generaltexth->tentColors[q.getObject(cb)->subID] + " keymaster tent";
}
if(q.quest->questName == CQuest::missionName(EQuestMission::NONE))
if(q.getQuest(cb)->questName == CQuest::missionName(EQuestMission::NONE))
return "inactive quest";
MetaString ms;
q.quest->getRolloverText(q.obj->cb, ms, false);
q.getQuest(cb)->getRolloverText(cb, ms, false);
return ms.toString();
}
TGoalVec CompleteQuest::tryCompleteQuest(const Nullkiller * ai) const
{
auto paths = ai->pathfinder->getPathInfo(q.obj->visitablePos());
auto paths = ai->pathfinder->getPathInfo(q.getObject(cb)->visitablePos());
vstd::erase_if(paths, [&](const AIPath & path) -> bool
{
return !q.quest->checkQuest(path.targetHero);
return !q.getQuest(cb)->checkQuest(path.targetHero);
});
return CaptureObjectsBehavior::getVisitGoals(paths, ai, q.obj);
return CaptureObjectsBehavior::getVisitGoals(paths, ai, q.getObject(cb));
}
TGoalVec CompleteQuest::missionArt(const Nullkiller * ai) const
@@ -125,7 +127,7 @@ TGoalVec CompleteQuest::missionArt(const Nullkiller * ai) const
CaptureObjectsBehavior findArts;
for(auto art : q.quest->mission.artifacts)
for(auto art : q.getQuest(cb)->mission.artifacts)
{
solutions.push_back(sptr(CaptureObjectsBehavior().ofType(Obj::ARTIFACT, art)));
}
@@ -148,14 +150,14 @@ TGoalVec CompleteQuest::missionHero(const Nullkiller * ai) const
TGoalVec CompleteQuest::missionArmy(const Nullkiller * ai) const
{
auto paths = ai->pathfinder->getPathInfo(q.obj->visitablePos());
auto paths = ai->pathfinder->getPathInfo(q.getObject(cb)->visitablePos());
vstd::erase_if(paths, [&](const AIPath & path) -> bool
{
return !CQuest::checkMissionArmy(q.quest, path.heroArmy);
return !CQuest::checkMissionArmy(q.getQuest(cb), path.heroArmy);
});
return CaptureObjectsBehavior::getVisitGoals(paths, ai, q.obj);
return CaptureObjectsBehavior::getVisitGoals(paths, ai, q.getObject(cb));
}
TGoalVec CompleteQuest::missionIncreasePrimaryStat(const Nullkiller * ai) const
@@ -170,13 +172,13 @@ TGoalVec CompleteQuest::missionLevel(const Nullkiller * ai) const
TGoalVec CompleteQuest::missionKeymaster(const Nullkiller * ai) const
{
if(isObjectPassable(ai, q.obj))
if(isObjectPassable(ai, q.getObject(cb)))
{
return CaptureObjectsBehavior(q.obj).decompose(ai);
return CaptureObjectsBehavior(q.getObject(cb)).decompose(ai);
}
else
{
return CaptureObjectsBehavior().ofType(Obj::KEYMASTER, q.obj->subID).decompose(ai);
return CaptureObjectsBehavior().ofType(Obj::KEYMASTER, q.getObject(cb)->subID).decompose(ai);
}
}
@@ -188,16 +190,16 @@ TGoalVec CompleteQuest::missionResources(const Nullkiller * ai) const
if(heroes.size())
{
if(q.quest->checkQuest(heroes.front())) //it doesn't matter which hero it is
if(q.getQuest(cb)->checkQuest(heroes.front())) //it doesn't matter which hero it is
{
return solutions;// ai->ah->howToVisitObj(q.obj);
return solutions;// ai->ah->howToVisitObj(q.getObject(cb));
}
else
{
for(int i = 0; i < q.quest->m7resources.size(); ++i)
for(int i = 0; i < q.getQuest(cb)->m7resources.size(); ++i)
{
if(q.quest->m7resources[i])
solutions.push_back(sptr(CollectRes(static_cast<EGameResID>(i), q.quest->m7resources[i])));
if(q.getQuest(cb)->m7resources[i])
solutions.push_back(sptr(CollectRes(static_cast<EGameResID>(i), q.getQuest(cb)->m7resources[i])));
}
}
}
@@ -211,10 +213,10 @@ TGoalVec CompleteQuest::missionResources(const Nullkiller * ai) const
TGoalVec CompleteQuest::missionDestroyObj(const Nullkiller * ai) const
{
auto obj = ai->cb->getObj(q.quest->killTarget);
auto obj = ai->cb->getObj(q.getQuest(cb)->killTarget);
if(!obj)
return CaptureObjectsBehavior(q.obj).decompose(ai);
return CaptureObjectsBehavior(q.getObject(cb)).decompose(ai);
auto relations = ai->cb->getPlayerRelations(ai->playerID, obj->tempOwner);

View File

@@ -31,16 +31,18 @@ namespace AIPathfinding
bool QuestAction::canAct(const Nullkiller * ai, const CGHeroInstance * hero) const
{
if(questInfo.obj->ID == Obj::BORDER_GATE || questInfo.obj->ID == Obj::BORDERGUARD)
auto object = questInfo.getObject(cb);
auto quest = questInfo.getQuest(cb);
if(object->ID == Obj::BORDER_GATE || object->ID == Obj::BORDERGUARD)
{
return dynamic_cast<const IQuestObject *>(questInfo.obj)->checkQuest(hero);
return dynamic_cast<const IQuestObject *>(object)->checkQuest(hero);
}
auto notActivated = !questInfo.obj->wasVisited(ai->playerID)
&& !questInfo.quest->activeForPlayers.count(hero->getOwner());
auto notActivated = !object->wasVisited(ai->playerID)
&& !quest->activeForPlayers.count(hero->getOwner());
return notActivated
|| questInfo.quest->checkQuest(hero);
|| quest->checkQuest(hero);
}
Goals::TSubgoal QuestAction::decompose(const Nullkiller * ai, const CGHeroInstance * hero) const
@@ -50,7 +52,7 @@ namespace AIPathfinding
void QuestAction::execute(AIGateway * ai, const CGHeroInstance * hero) const
{
ai->moveHeroToTile(questInfo.obj->visitablePos(), hero);
ai->moveHeroToTile(questInfo.getObject(cb)->visitablePos(), hero);
}
std::string QuestAction::toString() const

View File

@@ -83,11 +83,11 @@ void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkil
|| node.obj->ID == Obj::BORDER_GATE)
{
auto questObj = dynamic_cast<const IQuestObject *>(node.obj);
auto questInfo = QuestInfo(questObj->getQuest(), node.obj, pos.coord);
auto questInfo = QuestInfo(node.obj->id);
if(node.obj->ID == Obj::QUEST_GUARD
&& questObj->getQuest()->mission == Rewardable::Limiter{}
&& questObj->getQuest()->killTarget == ObjectInstanceID::NONE)
&& questObj->getQuest().mission == Rewardable::Limiter{}
&& questObj->getQuest().killTarget == ObjectInstanceID::NONE)
{
continue;
}

View File

@@ -166,12 +166,12 @@ namespace AIPathfinding
{
const AIPathNode * destinationNode = nodeStorage->getAINode(destination.node);
auto questObj = dynamic_cast<const IQuestObject *>(destination.nodeObject);
auto questInfo = QuestInfo(questObj->getQuest(), destination.nodeObject, destination.coord);
auto questInfo = QuestInfo(destination.nodeObject->id);
QuestAction questAction(questInfo);
if(destination.nodeObject->ID == Obj::QUEST_GUARD
&& questObj->getQuest()->mission == Rewardable::Limiter{}
&& questObj->getQuest()->killTarget == ObjectInstanceID::NONE)
&& questObj->getQuest().mission == Rewardable::Limiter{}
&& questObj->getQuest().killTarget == ObjectInstanceID::NONE)
{
return false;
}

View File

@@ -11,6 +11,7 @@
#include "FuzzyHelper.h"
#include "Goals/Goals.h"
#include "Goals/CompleteQuest.h"
#include "VCAI.h"
#include "../../lib/mapObjectConstructors/AObjectTypeHandler.h"

View File

@@ -8,7 +8,15 @@
*
*/
#include "StdInc.h"
#include "Goals.h"
#include "CompleteQuest.h"
#include "CollectRes.h"
#include "FindObj.h"
#include "GatherArmy.h"
#include "GatherTroops.h"
#include "GetArtOfType.h"
#include "RecruitHero.h"
#include "../VCAI.h"
#include "../FuzzyHelper.h"
#include "../AIhelper.h"
@@ -18,45 +26,47 @@ using namespace Goals;
bool CompleteQuest::operator==(const CompleteQuest & other) const
{
return q.quest->qid == other.q.quest->qid;
return q.getQuest(cb) == other.q.getQuest(cb);
}
bool isKeyMaster(const QuestInfo & q)
{
return q.obj && (q.obj->ID == Obj::BORDER_GATE || q.obj->ID == Obj::BORDERGUARD);
auto object = q.getObject(cb);
return object && (object->ID == Obj::BORDER_GATE || object->ID == Obj::BORDERGUARD);
}
TGoalVec CompleteQuest::getAllPossibleSubgoals()
{
TGoalVec solutions;
auto quest = q.getQuest(cb);
if(!q.quest->isCompleted)
if(!quest->isCompleted)
{
logAi->debug("Trying to realize quest: %s", questToString());
if(isKeyMaster(q))
return missionKeymaster();
if(!q.quest->mission.artifacts.empty())
if(!quest->mission.artifacts.empty())
return missionArt();
if(!q.quest->mission.heroes.empty())
if(!quest->mission.heroes.empty())
return missionHero();
if(!q.quest->mission.creatures.empty())
if(!quest->mission.creatures.empty())
return missionArmy();
if(q.quest->mission.resources.nonZero())
if(quest->mission.resources.nonZero())
return missionResources();
if(q.quest->killTarget != ObjectInstanceID::NONE)
if(quest->killTarget != ObjectInstanceID::NONE)
return missionDestroyObj();
for(auto & s : q.quest->mission.primary)
for(auto & s : quest->mission.primary)
if(s)
return missionIncreasePrimaryStat();
if(q.quest->mission.heroLevel > 0)
if(quest->mission.heroLevel > 0)
return missionLevel();
}
@@ -65,7 +75,7 @@ TGoalVec CompleteQuest::getAllPossibleSubgoals()
TSubgoal CompleteQuest::whatToDoToAchieve()
{
if(q.quest->mission == Rewardable::Limiter{})
if(q.getQuest(cb)->mission == Rewardable::Limiter{})
{
throw cannotFulfillGoalException("Can not complete inactive quest");
}
@@ -99,11 +109,13 @@ std::string CompleteQuest::completeMessage() const
std::string CompleteQuest::questToString() const
{
if(q.quest->questName == CQuest::missionName(EQuestMission::NONE))
auto quest = q.getQuest(cb);
if(quest->questName == CQuest::missionName(EQuestMission::NONE))
return "inactive quest";
MetaString ms;
q.quest->getRolloverText(q.obj->cb, ms, false);
quest->getRolloverText(cb, ms, false);
return ms.toString();
}
@@ -116,9 +128,9 @@ TGoalVec CompleteQuest::tryCompleteQuest() const
for(auto hero : heroes)
{
if(q.quest->checkQuest(hero))
if(q.getQuest(cb)->checkQuest(hero))
{
vstd::concatenate(solutions, ai->ah->howToVisitObj(hero, ObjectIdRef(q.obj->id)));
vstd::concatenate(solutions, ai->ah->howToVisitObj(hero, ObjectIdRef(q.getObject(cb)->id)));
}
}
@@ -132,7 +144,7 @@ TGoalVec CompleteQuest::missionArt() const
if(!solutions.empty())
return solutions;
for(auto art : q.quest->mission.artifacts)
for(auto art : q.getQuest(cb)->mission.artifacts)
{
solutions.push_back(sptr(GetArtOfType(art))); //TODO: transport?
}
@@ -160,7 +172,7 @@ TGoalVec CompleteQuest::missionArmy() const
if(!solutions.empty())
return solutions;
for(auto creature : q.quest->mission.creatures)
for(auto creature : q.getQuest(cb)->mission.creatures)
{
solutions.push_back(sptr(GatherTroops(creature.getId(), creature.count)));
}
@@ -174,7 +186,7 @@ TGoalVec CompleteQuest::missionIncreasePrimaryStat() const
if(solutions.empty())
{
for(int i = 0; i < q.quest->mission.primary.size(); ++i)
for(int i = 0; i < q.getQuest(cb)->mission.primary.size(); ++i)
{
// TODO: library, school and other boost objects
logAi->debug("Don't know how to increase primary stat %d", i);
@@ -190,7 +202,7 @@ TGoalVec CompleteQuest::missionLevel() const
if(solutions.empty())
{
logAi->debug("Don't know how to reach hero level %d", q.quest->mission.heroLevel);
logAi->debug("Don't know how to reach hero level %d", q.getQuest(cb)->mission.heroLevel);
}
return solutions;
@@ -202,7 +214,7 @@ TGoalVec CompleteQuest::missionKeymaster() const
if(solutions.empty())
{
solutions.push_back(sptr(Goals::FindObj(Obj::KEYMASTER, q.obj->subID)));
solutions.push_back(sptr(Goals::FindObj(Obj::KEYMASTER, q.getObject(cb)->subID)));
}
return solutions;
@@ -216,16 +228,16 @@ TGoalVec CompleteQuest::missionResources() const
if(heroes.size())
{
if(q.quest->checkQuest(heroes.front())) //it doesn't matter which hero it is
if(q.getQuest(cb)->checkQuest(heroes.front())) //it doesn't matter which hero it is
{
return ai->ah->howToVisitObj(q.obj);
return ai->ah->howToVisitObj(q.getObject(cb));
}
else
{
for(int i = 0; i < q.quest->mission.resources.size(); ++i)
for(int i = 0; i < q.getQuest(cb)->mission.resources.size(); ++i)
{
if(q.quest->mission.resources[i])
solutions.push_back(sptr(CollectRes(static_cast<EGameResID>(i), q.quest->mission.resources[i])));
if(q.getQuest(cb)->mission.resources[i])
solutions.push_back(sptr(CollectRes(static_cast<EGameResID>(i), q.getQuest(cb)->mission.resources[i])));
}
}
}
@@ -241,10 +253,10 @@ TGoalVec CompleteQuest::missionDestroyObj() const
{
TGoalVec solutions;
auto obj = cb->getObj(q.quest->killTarget);
auto obj = cb->getObj(q.getQuest(cb)->killTarget);
if(!obj)
return ai->ah->howToVisitObj(q.obj);
return ai->ah->howToVisitObj(q.getObject(cb));
if(obj->ID == Obj::HERO)
{

View File

@@ -30,5 +30,4 @@
#include "ClearWayTo.h"
#include "DigAtTile.h"
#include "FindObj.h"
#include "CompleteQuest.h"
#include "AdventureSpellCast.h"
#include "AdventureSpellCast.h"

View File

@@ -12,7 +12,9 @@
#include "AIPathfinder.h"
#include "AIPathfinderConfig.h"
#include "../Goals/Goals.h"
#include "../Goals/CompleteQuest.h"
#include "../../../lib/CGameInfoCallback.h"
#include "../../../lib/gameState/QuestInfo.h"
#include "../../../lib/mapping/CMapDefines.h"
#include "../../../lib/mapObjects/CQuest.h"
@@ -224,7 +226,7 @@ Goals::TSubgoal PathfindingManager::clearWayTo(HeroPtr hero, int3 firstTileToGet
if(questObj)
{
auto questInfo = QuestInfo(questObj->getQuest(), topObj, topObj->visitablePos());
auto questInfo = QuestInfo(topObj->id);
return sptr(Goals::CompleteQuest(questInfo));
}

View File

@@ -13,6 +13,7 @@
#include "ResourceManager.h"
#include "BuildingManager.h"
#include "Goals/Goals.h"
#include "Goals/CompleteQuest.h"
#include "../../lib/ArtifactUtils.h"
#include "../../lib/AsyncRunner.h"
@@ -2809,7 +2810,7 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj)
{
for(auto q : ai->myCb->getMyQuests())
{
if(q.obj == obj)
if(q.getObject(cb) == obj)
{
return false; // do not visit guards or gates when wandering
}
@@ -2823,9 +2824,9 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj)
{
for(auto q : ai->myCb->getMyQuests())
{
if(q.obj == obj)
if(q.getObject(cb) == obj)
{
if(q.quest->checkQuest(h.h))
if(q.getQuest(cb)->checkQuest(h.h))
return true; //we completed the quest
else
return false; //we can't complete this quest

View File

@@ -13,6 +13,8 @@
#include "../CPlayerInterface.h"
#include "../GameEngine.h"
#include "../GameInstance.h"
#include "../CPlayerInterface.h"
#include "../gui/Shortcut.h"
#include "../widgets/Buttons.h"
#include "../widgets/CComponent.h"
@@ -74,11 +76,7 @@ void CQuestMinimap::addQuestMarks (const QuestInfo * q)
OBJECT_CONSTRUCTION;
icons.clear();
int3 tile;
if (q->obj)
tile = q->obj->visitablePos();
else
tile = q->tile;
int3 tile = q->getPosition(GAME->interface()->cb.get());
Point offset = tileToPixels(tile);
@@ -102,7 +100,7 @@ void CQuestMinimap::update()
void CQuestMinimap::iconClicked()
{
if(currentQuest->obj)
adventureInt->centerOnTile(currentQuest->obj->visitablePos());
adventureInt->centerOnTile(currentQuest->getObject(GAME->interface()->cb.get())->visitablePos());
//moveAdvMapSelection();
}
@@ -145,11 +143,14 @@ void CQuestLog::recreateLabelList()
int currentLabel = 0;
for (int i = 0; i < quests.size(); ++i)
{
auto questPtr = quests[i].getQuest(GAME->interface()->cb.get());
auto questObject = quests[i].getObject(GAME->interface()->cb.get());
// Quests without mision don't have text for them and can't be displayed
if (quests[i].quest->mission == Rewardable::Limiter{})
if (quests[i].getQuest(GAME->interface()->cb.get())->mission == Rewardable::Limiter{})
continue;
if (quests[i].quest->isCompleted)
if (questPtr->isCompleted)
{
completeMissing = false;
if (hideComplete)
@@ -157,10 +158,10 @@ void CQuestLog::recreateLabelList()
}
MetaString text;
quests[i].quest->getRolloverText (quests[i].obj->cb, text, false);
questPtr->getRolloverText(GAME->interface()->cb.get(), text, false);
if (quests[i].obj)
{
if (auto seersHut = dynamic_cast<const CGSeerHut *>(quests[i].obj))
if (auto seersHut = dynamic_cast<const CGSeerHut *>(questObject))
{
MetaString toSeer;
toSeer.appendRawString(LIBRARY->generaltexth->allTexts[347]);
@@ -168,7 +169,7 @@ void CQuestLog::recreateLabelList()
text.replaceRawString(toSeer.toString());
}
else
text.replaceRawString(quests[i].obj->getObjectName()); //get name of the object
text.replaceRawString(questObject->getObjectName()); //get name of the object
}
auto label = std::make_shared<CQuestLabel>(Rect(13, 195, 149,31), FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, text.toString());
label->disable();
@@ -177,7 +178,7 @@ void CQuestLog::recreateLabelList()
labels.push_back(label);
// Select latest active quest
if(!quests[i].quest->isCompleted)
if(!questPtr->isCompleted)
selectQuest(i, currentLabel);
currentLabel = static_cast<int>(labels.size());
@@ -233,7 +234,7 @@ void CQuestLog::selectQuest(int which, int labelId)
MetaString text;
std::vector<Component> components;
currentQuest->quest->getVisitText(currentQuest->obj->cb, text, components, true);
currentQuest->getQuest(GAME->interface()->cb.get())->getVisitText(GAME->interface()->cb.get(), text, components, true);
if(description->slider)
description->slider->scrollToMin(); // scroll text to start position
description->setText(text.toString()); //TODO: use special log entry text

View File

@@ -107,6 +107,7 @@ set(lib_MAIN_SRCS
gameState/CGameStateCampaign.cpp
gameState/HighScore.cpp
gameState/InfoAboutArmy.cpp
gameState/QuestInfo.cpp
gameState/RumorState.cpp
gameState/TavernHeroesPool.cpp
gameState/GameStatistics.cpp

View File

@@ -78,8 +78,8 @@ public:
void pickAllowedArtsSet(std::vector<ArtifactID> & out, vstd::RNG & rand);
void getAllowedSpells(std::vector<SpellID> &out, std::optional<ui16> level = std::nullopt);
void saveCommonState(CSaveFile &out) const; //stores GS and LIBRARY
void loadCommonState(CLoadFile &in); //loads GS and LIBRARY
void saveCommonState(CSaveFile &out) const; //stores GS
void loadCommonState(CLoadFile &in); //loads GS
};
class DLL_LINKAGE IGameEventCallback

View File

@@ -66,6 +66,7 @@ public:
using StaticIdentifier<BattleID>::StaticIdentifier;
DLL_LINKAGE static const BattleID NONE;
};
class DLL_LINKAGE ObjectInstanceID : public StaticIdentifier<ObjectInstanceID>
{
public:
@@ -76,6 +77,16 @@ public:
static std::string encode(const si32 index);
};
class DLL_LINKAGE QuestInstanceID : public StaticIdentifier<QuestInstanceID>
{
public:
using StaticIdentifier<QuestInstanceID>::StaticIdentifier;
static const QuestInstanceID NONE;
static si32 decode(const std::string & identifier);
static std::string encode(const si32 index);
};
class HeroClassID : public EntityIdentifier<HeroClassID>
{
public:

View File

@@ -0,0 +1,36 @@
/*
* QuestInfo.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "QuestInfo.h"
#include "../mapObjects/CQuest.h"
#include "../CGameInfoCallback.h"
VCMI_LIB_NAMESPACE_BEGIN
const CQuest * QuestInfo::getQuest(CGameInfoCallback *cb) const
{
auto questObject = dynamic_cast<const IQuestObject*>(getObject(cb));
assert(questObject);
return &questObject->getQuest();
}
const CGObjectInstance * QuestInfo::getObject(CGameInfoCallback *cb) const
{
return cb->getObjInstance(obj);
}
int3 QuestInfo::getPosition(CGameInfoCallback *cb) const
{
return getObject(cb)->visitablePos();
}
VCMI_LIB_NAMESPACE_END

View File

@@ -10,45 +10,37 @@
#pragma once
#include "int3.h"
#include "../constants/EntityIdentifiers.h"
VCMI_LIB_NAMESPACE_BEGIN
class CQuest;
class CGObjectInstance;
class CGameInfoCallback;
struct DLL_LINKAGE QuestInfo //universal interface for human and AI
{
const CQuest * quest;
const CGObjectInstance * obj; //related object, most likely Seer Hut
int3 tile;
ObjectInstanceID obj; //related object, Seer Hut or Border Guard
QuestInfo()
: quest(nullptr), obj(nullptr), tile(-1,-1,-1)
{};
QuestInfo (const CQuest * Quest, const CGObjectInstance * Obj, int3 Tile) :
quest (Quest), obj (Obj), tile (Tile){};
QuestInfo() = default;
explicit QuestInfo(ObjectInstanceID Obj)
: obj(Obj)
{}
QuestInfo (const QuestInfo &qi) : quest(qi.quest), obj(qi.obj), tile(qi.tile)
{};
const QuestInfo& operator= (const QuestInfo &qi)
{
quest = qi.quest;
obj = qi.obj;
tile = qi.tile;
return *this;
}
const CQuest * getQuest(CGameInfoCallback *cb) const;
const CGObjectInstance * getObject(CGameInfoCallback *cb) const;
int3 getPosition(CGameInfoCallback *cb) const;
bool operator== (const QuestInfo & qi) const
{
return (quest == qi.quest && obj == qi.obj);
return obj == qi.obj;
}
template <typename Handler> void serialize(Handler &h)
{
h & quest;
//h & quest;
h & obj;
h & tile;
//h & tile;
}
};

View File

@@ -178,7 +178,7 @@ void CQuest::completeQuest(IGameCallback * cb, const CGHeroInstance *h) const
cb->giveResources(h->getOwner(), -mission.resources);
}
void CQuest::addTextReplacements(IGameCallback * cb, MetaString & text, std::vector<Component> & components) const
void CQuest::addTextReplacements(const CGameInfoCallback * cb, MetaString & text, std::vector<Component> & components) const
{
if(mission.heroLevel > 0)
text.replaceNumber(mission.heroLevel);
@@ -277,7 +277,7 @@ void CQuest::addTextReplacements(IGameCallback * cb, MetaString & text, std::vec
text.replaceNumber(lastDay - cb->getDate(Date::DAY));
}
void CQuest::getVisitText(IGameCallback * cb, MetaString &iwText, std::vector<Component> &components, bool firstVisit, const CGHeroInstance * h) const
void CQuest::getVisitText(const CGameInfoCallback * cb, MetaString &iwText, std::vector<Component> &components, bool firstVisit, const CGHeroInstance * h) const
{
bool failRequirements = (h ? !checkQuest(h) : true);
mission.loadComponents(components, h);
@@ -293,7 +293,7 @@ void CQuest::getVisitText(IGameCallback * cb, MetaString &iwText, std::vector<Co
addTextReplacements(cb, iwText, components);
}
void CQuest::getRolloverText(IGameCallback * cb, MetaString &ms, bool onHover) const
void CQuest::getRolloverText(const CGameInfoCallback * cb, MetaString &ms, bool onHover) const
{
if(onHover)
ms.appendRawString("\n\n");
@@ -306,7 +306,7 @@ void CQuest::getRolloverText(IGameCallback * cb, MetaString &ms, bool onHover) c
addTextReplacements(cb, ms, components);
}
void CQuest::getCompletionText(IGameCallback * cb, MetaString &iwText) const
void CQuest::getCompletionText(const CGameInfoCallback * cb, MetaString &iwText) const
{
iwText.appendRawString(completedText.toString());
@@ -415,36 +415,37 @@ void CQuest::serializeJson(JsonSerializeFormat & handler, const std::string & fi
}
IQuestObject::IQuestObject()
:quest(std::make_unique<CQuest>())
{}
IQuestObject::~IQuestObject() = default;
bool IQuestObject::checkQuest(const CGHeroInstance* h) const
{
return quest->checkQuest(h);
return getQuest().checkQuest(h);
}
void CGSeerHut::getVisitText(MetaString &text, std::vector<Component> &components, bool FirstVisit, const CGHeroInstance * h) const
{
quest->getVisitText(cb, text, components, FirstVisit, h);
}
void IQuestObject::afterAddToMapCommon(CMap * map) const
{
map->addNewQuestInstance(quest);
getQuest().getVisitText(cb, text, components, FirstVisit, h);
}
void CGSeerHut::setObjToKill()
{
if(quest->killTarget == ObjectInstanceID::NONE)
if(getQuest().killTarget == ObjectInstanceID::NONE)
return;
if(getCreatureToKill(true))
{
quest->stackToKill = getCreatureToKill(false)->getCreatureID();
assert(quest->stackToKill != CreatureID::NONE);
quest->stackDirection = checkDirection();
getQuest().stackToKill = getCreatureToKill(false)->getCreatureID();
assert(getQuest().stackToKill != CreatureID::NONE);
getQuest().stackDirection = checkDirection();
}
else if(getHeroToKill(true))
{
quest->heroName = getHeroToKill(false)->getNameTranslated();
quest->heroPortrait = getHeroToKill(false)->getPortraitSource();
getQuest().heroName = getHeroToKill(false)->getNameTranslated();
getQuest().heroPortrait = getHeroToKill(false)->getPortraitSource();
}
}
@@ -454,8 +455,8 @@ void CGSeerHut::init(vstd::RNG & rand)
auto seerNameID = *RandomGeneratorUtil::nextItem(names, rand);
seerName = LIBRARY->generaltexth->translate(seerNameID);
quest->textOption = rand.nextInt(2);
quest->completedOption = rand.nextInt(1, 3);
getQuest().textOption = rand.nextInt(2);
getQuest().completedOption = rand.nextInt(1, 3);
configuration.canRefuse = true;
configuration.visitMode = Rewardable::EVisitMode::VISIT_ONCE;
@@ -469,33 +470,33 @@ void CGSeerHut::initObj(vstd::RNG & rand)
CRewardableObject::initObj(rand);
setObjToKill();
quest->defineQuestName();
getQuest().defineQuestName();
if(quest->mission == Rewardable::Limiter{} && quest->killTarget == ObjectInstanceID::NONE)
quest->isCompleted = true;
if(getQuest().mission == Rewardable::Limiter{} && getQuest().killTarget == ObjectInstanceID::NONE)
getQuest().isCompleted = true;
if(quest->questName == quest->missionName(EQuestMission::NONE))
if(getQuest().questName == getQuest().missionName(EQuestMission::NONE))
{
quest->firstVisitText.appendTextID(TextIdentifier("core", "seehut", "empty", quest->completedOption).get());
getQuest().firstVisitText.appendTextID(TextIdentifier("core", "seehut", "empty", getQuest().completedOption).get());
}
else
{
if(!quest->isCustomFirst)
quest->firstVisitText.appendTextID(TextIdentifier("core", "seerhut", "quest", quest->questName, quest->missionState(0), quest->textOption).get());
if(!quest->isCustomNext)
quest->nextVisitText.appendTextID(TextIdentifier("core", "seerhut", "quest", quest->questName, quest->missionState(1), quest->textOption).get());
if(!quest->isCustomComplete)
quest->completedText.appendTextID(TextIdentifier("core", "seerhut", "quest", quest-> questName, quest->missionState(2), quest->textOption).get());
if(!getQuest().isCustomFirst)
getQuest().firstVisitText.appendTextID(TextIdentifier("core", "seerhut", "quest", getQuest().questName, getQuest().missionState(0), getQuest().textOption).get());
if(!getQuest().isCustomNext)
getQuest().nextVisitText.appendTextID(TextIdentifier("core", "seerhut", "quest", getQuest().questName, getQuest().missionState(1), getQuest().textOption).get());
if(!getQuest().isCustomComplete)
getQuest().completedText.appendTextID(TextIdentifier("core", "seerhut", "quest", getQuest(). questName, getQuest().missionState(2), getQuest().textOption).get());
}
quest->getCompletionText(cb, configuration.onSelect);
getQuest().getCompletionText(cb, configuration.onSelect);
for(auto & i : configuration.info)
quest->getCompletionText(cb, i.message);
getQuest().getCompletionText(cb, i.message);
}
void CGSeerHut::getRolloverText(MetaString &text, bool onHover) const
{
quest->getRolloverText(cb, text, onHover);//TODO: simplify?
getQuest().getRolloverText(cb, text, onHover);//TODO: simplify?
if(!onHover)
text.replaceRawString(seerName);
}
@@ -503,15 +504,15 @@ void CGSeerHut::getRolloverText(MetaString &text, bool onHover) const
std::string CGSeerHut::getHoverText(PlayerColor player) const
{
std::string hoverName = getObjectName();
if(ID == Obj::SEER_HUT && quest->activeForPlayers.count(player))
if(ID == Obj::SEER_HUT && getQuest().activeForPlayers.count(player))
{
hoverName = LIBRARY->generaltexth->allTexts[347];
boost::algorithm::replace_first(hoverName, "%s", seerName);
}
if(quest->activeForPlayers.count(player)
&& (quest->mission != Rewardable::Limiter{}
|| quest->killTarget != ObjectInstanceID::NONE)) //rollover when the quest is active
if(getQuest().activeForPlayers.count(player)
&& (getQuest().mission != Rewardable::Limiter{}
|| getQuest().killTarget != ObjectInstanceID::NONE)) //rollover when the quest is active
{
MetaString ms;
getRolloverText (ms, true);
@@ -538,16 +539,16 @@ std::string CGSeerHut::getPopupText(const CGHeroInstance * hero) const
std::vector<Component> CGSeerHut::getPopupComponents(PlayerColor player) const
{
std::vector<Component> result;
if (quest->activeForPlayers.count(player))
quest->mission.loadComponents(result, nullptr);
if (getQuest().activeForPlayers.count(player))
getQuest().mission.loadComponents(result, nullptr);
return result;
}
std::vector<Component> CGSeerHut::getPopupComponents(const CGHeroInstance * hero) const
{
std::vector<Component> result;
if (quest->activeForPlayers.count(hero->getOwner()))
quest->mission.loadComponents(result, hero);
if (getQuest().activeForPlayers.count(hero->getOwner()))
getQuest().mission.loadComponents(result, hero);
return result;
}
@@ -557,13 +558,13 @@ void CGSeerHut::setPropertyDer(ObjProperty what, ObjPropertyID identifier)
{
case ObjProperty::SEERHUT_VISITED:
{
quest->activeForPlayers.emplace(identifier.as<PlayerColor>());
getQuest().activeForPlayers.emplace(identifier.as<PlayerColor>());
break;
}
case ObjProperty::SEERHUT_COMPLETE:
{
quest->isCompleted = identifier.getNum();
quest->activeForPlayers.clear();
getQuest().isCompleted = identifier.getNum();
getQuest().activeForPlayers.clear();
break;
}
}
@@ -572,7 +573,7 @@ void CGSeerHut::setPropertyDer(ObjProperty what, ObjPropertyID identifier)
void CGSeerHut::newTurn(vstd::RNG & rand) const
{
CRewardableObject::newTurn(rand);
if(quest->lastDay >= 0 && quest->lastDay <= cb->getDate() - 1) //time is up
if(getQuest().lastDay >= 0 && getQuest().lastDay <= cb->getDate() - 1) //time is up
{
cb->setObjPropertyValue(id, ObjProperty::SEERHUT_COMPLETE, true);
}
@@ -582,9 +583,9 @@ void CGSeerHut::onHeroVisit(const CGHeroInstance * h) const
{
InfoWindow iw;
iw.player = h->getOwner();
if(!quest->isCompleted)
if(!getQuest().isCompleted)
{
bool firstVisit = !quest->activeForPlayers.count(h->getOwner());
bool firstVisit = !getQuest().activeForPlayers.count(h->getOwner());
bool failRequirements = !checkQuest(h);
if(firstVisit)
@@ -592,7 +593,7 @@ void CGSeerHut::onHeroVisit(const CGHeroInstance * h) const
cb->setObjPropertyID(id, ObjProperty::SEERHUT_VISITED, h->getOwner());
AddQuest aq;
aq.quest = QuestInfo(quest.get(), this, visitablePos());
aq.quest = QuestInfo(id);
aq.player = h->tempOwner;
cb->sendAndApply(aq); //TODO: merge with setObjProperty?
}
@@ -611,7 +612,7 @@ void CGSeerHut::onHeroVisit(const CGHeroInstance * h) const
}
else
{
iw.text.appendRawString(LIBRARY->generaltexth->seerEmpty[quest->completedOption]);
iw.text.appendRawString(LIBRARY->generaltexth->seerEmpty[getQuest().completedOption]);
if (ID == Obj::SEER_HUT)
iw.text.replaceRawString(seerName);
cb->showInfoDialog(&iw);
@@ -652,7 +653,7 @@ int CGSeerHut::checkDirection() const
const CGHeroInstance * CGSeerHut::getHeroToKill(bool allowNull) const
{
const CGObjectInstance *o = cb->getObj(quest->killTarget);
const CGObjectInstance *o = cb->getObj(getQuest().killTarget);
if(allowNull && !o)
return nullptr;
return dynamic_cast<const CGHeroInstance *>(o);
@@ -660,7 +661,7 @@ const CGHeroInstance * CGSeerHut::getHeroToKill(bool allowNull) const
const CGCreature * CGSeerHut::getCreatureToKill(bool allowNull) const
{
const CGObjectInstance *o = cb->getObj(quest->killTarget);
const CGObjectInstance *o = cb->getObj(getQuest().killTarget);
if(allowNull && !o)
return nullptr;
return dynamic_cast<const CGCreature *>(o);
@@ -671,21 +672,16 @@ void CGSeerHut::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answe
CRewardableObject::blockingDialogAnswered(hero, answer);
if(answer)
{
quest->completeQuest(cb, hero);
cb->setObjPropertyValue(id, ObjProperty::SEERHUT_COMPLETE, !quest->repeatedQuest); //mission complete
getQuest().completeQuest(cb, hero);
cb->setObjPropertyValue(id, ObjProperty::SEERHUT_COMPLETE, !getQuest().repeatedQuest); //mission complete
}
}
void CGSeerHut::afterAddToMap(CMap* map)
{
IQuestObject::afterAddToMapCommon(map);
}
void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler)
{
//quest and reward
CRewardableObject::serializeJsonOptions(handler);
quest->serializeJson(handler, "quest");
getQuest().serializeJson(handler, "quest");
if(!handler.saving)
{
@@ -760,8 +756,8 @@ void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler)
void CGQuestGuard::init(vstd::RNG & rand)
{
blockVisit = true;
quest->textOption = rand.nextInt(3, 5);
quest->completedOption = rand.nextInt(4, 5);
getQuest().textOption = rand.nextInt(3, 5);
getQuest().completedOption = rand.nextInt(4, 5);
configuration.info.push_back({});
configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
@@ -771,7 +767,7 @@ void CGQuestGuard::init(vstd::RNG & rand)
void CGQuestGuard::onHeroVisit(const CGHeroInstance * h) const
{
if(!quest->isCompleted)
if(!getQuest().isCompleted)
CGSeerHut::onHeroVisit(h);
else
cb->setObjPropertyValue(id, ObjProperty::SEERHUT_COMPLETE, false);
@@ -779,13 +775,13 @@ void CGQuestGuard::onHeroVisit(const CGHeroInstance * h) const
bool CGQuestGuard::passableFor(PlayerColor color) const
{
return quest->isCompleted;
return getQuest().isCompleted;
}
void CGQuestGuard::serializeJsonOptions(JsonSerializeFormat & handler)
{
//quest only, do not call base class
quest->serializeJson(handler, "quest");
getQuest().serializeJson(handler, "quest");
}
bool CGKeys::wasMyColorVisited(const PlayerColor & player) const
@@ -869,7 +865,7 @@ void CGBorderGuard::onHeroVisit(const CGHeroInstance * h) const
h->showInfoDialog(18);
AddQuest aq;
aq.quest = QuestInfo (quest.get(), this, visitablePos());
aq.quest = QuestInfo(id);
aq.player = h->tempOwner;
cb->sendAndApply(aq);
//TODO: add this quest only once OR check for multiple instances later
@@ -882,11 +878,6 @@ void CGBorderGuard::blockingDialogAnswered(const CGHeroInstance *hero, int32_t a
cb->removeObject(this, hero->getOwner());
}
void CGBorderGuard::afterAddToMap(CMap * map)
{
IQuestObject::afterAddToMapCommon(map);
}
void CGBorderGate::onHeroVisit(const CGHeroInstance * h) const //TODO: passability
{
if (!wasMyColorVisited (h->getOwner()) )
@@ -894,7 +885,7 @@ void CGBorderGate::onHeroVisit(const CGHeroInstance * h) const //TODO: passabili
h->showInfoDialog(18);
AddQuest aq;
aq.quest = QuestInfo (quest.get(), this, visitablePos());
aq.quest = QuestInfo(id);
aq.player = h->tempOwner;
cb->sendAndApply(aq);
}

View File

@@ -47,7 +47,7 @@ public:
std::string questName;
si32 qid; //unique quest id for serialization / identification
QuestInstanceID qid;
si32 lastDay; //after this day (first day is 0) mission cannot be completed; if -1 - no limit
ObjectInstanceID killTarget;
@@ -77,11 +77,11 @@ public:
static bool checkMissionArmy(const CQuest * q, const CCreatureSet * army);
bool checkQuest(const CGHeroInstance * h) const; //determines whether the quest is complete or not
void getVisitText(IGameCallback * cb, MetaString &text, std::vector<Component> & components, bool FirstVisit, const CGHeroInstance * h = nullptr) const;
void getCompletionText(IGameCallback * cb, MetaString &text) const;
void getRolloverText (IGameCallback * cb, MetaString &text, bool onHover) const; //hover or quest log entry
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 addTextReplacements(IGameCallback * cb, MetaString &out, std::vector<Component> & components) const;
void addTextReplacements(const CGameInfoCallback * cb, MetaString &out, std::vector<Component> & components) const;
void addKillTargetReplacements(MetaString &out) const;
void defineQuestName();
@@ -116,34 +116,21 @@ public:
void serializeJson(JsonSerializeFormat & handler, const std::string & fieldName);
};
class DLL_LINKAGE IQuestObject : public virtual Serializeable
class DLL_LINKAGE IQuestObject
{
friend class CMapLoaderH3M;
friend class TreasurePlacer; // RMG
friend class Inspector; // Map editor
protected:
std::shared_ptr<CQuest> quest;
std::unique_ptr<CQuest> quest;
public:
IQuestObject()
:quest(std::make_shared<CQuest>())
{}
virtual ~IQuestObject() = default;
IQuestObject();
virtual ~IQuestObject();
virtual void getVisitText (MetaString &text, std::vector<Component> &components, bool FirstVisit, const CGHeroInstance * h = nullptr) const = 0;
virtual bool checkQuest (const CGHeroInstance * h) const;
const CQuest * getQuest() const
{
return quest.get();
}
virtual const CQuest & getQuest() const { return *quest; }
virtual CQuest & getQuest() { return *quest; }
template <typename Handler> void serialize(Handler &h)
{
h & quest;
}
protected:
void afterAddToMapCommon(CMap * map) const;
};
class DLL_LINKAGE CGSeerHut : public CRewardableObject, public IQuestObject
@@ -172,8 +159,6 @@ public:
const CGCreature *getCreatureToKill(bool allowNull) const;
void getRolloverText (MetaString &text, bool onHover) const;
void afterAddToMap(CMap * map) override;
template <typename Handler> void serialize(Handler &h)
{
h & static_cast<CRewardableObject&>(*this);
@@ -237,6 +222,7 @@ public:
class DLL_LINKAGE CGBorderGuard : public CGKeys, public IQuestObject
{
QuestInstanceID qid;
public:
using CGKeys::CGKeys;
@@ -248,8 +234,6 @@ public:
void getRolloverText (MetaString &text, bool onHover) const;
bool checkQuest (const CGHeroInstance * h) const override;
void afterAddToMap(CMap * map) override;
template <typename Handler> void serialize(Handler &h)
{
h & static_cast<IQuestObject&>(*this);
@@ -265,11 +249,6 @@ public:
void onHeroVisit(const CGHeroInstance * h) const override;
bool passableFor(PlayerColor color) const override;
template <typename Handler> void serialize(Handler &h)
{
h & static_cast<CGBorderGuard&>(*this); //need to serialize or object will be empty
}
};
VCMI_LIB_NAMESPACE_END

View File

@@ -497,19 +497,6 @@ void CMap::removeArtifactInstance(CArtifactSet & set, const ArtifactPosition & s
artifact->addPlacementMap(partsMap);
}
void CMap::addNewQuestInstance(std::shared_ptr<CQuest> quest)
{
quest->qid = static_cast<si32>(quests.size());
quests.emplace_back(quest);
}
void CMap::clearQuestInstance(const CQuest * quest)
{
assert(quests.at(quest->qid).get() == quest);
quests.at(quest->qid) = nullptr;
}
void CMap::generateUniqueInstanceName(CGObjectInstance * target)
{
//this gives object unique name even if objects are removed later
@@ -727,17 +714,6 @@ CMapEditManager * CMap::getEditManager()
return editManager.get();
}
void CMap::resolveQuestIdentifiers()
{
//FIXME: move to CMapLoaderH3M
for (auto & quest : quests)
{
if (quest && quest->killTarget != ObjectInstanceID::NONE)
quest->killTarget = questIdentifierToId[quest->killTarget.getNum()];
}
questIdentifierToId.clear();
}
void CMap::reindexObjects()
{
// Only reindex at editor / RMG operations

View File

@@ -62,8 +62,6 @@ class DLL_LINKAGE CMap : public CMapHeader, public GameCallbackHolder
std::unique_ptr<GameSettings> gameSettings;
/// All quests that are currently present on map
std::vector<std::shared_ptr<CQuest>> quests;
/// All artifacts that exists on map, whether on map, in hero inventory, or stored in some object
std::vector<std::shared_ptr<CArtifactInstance>> artInstances;
/// All heroes that are currently free for recruitment in taverns and are not present on map
@@ -78,7 +76,7 @@ class DLL_LINKAGE CMap : public CMapHeader, public GameCallbackHolder
public:
/// Central lists of items in game. Position of item in the vectors below is their (instance) id.
/// TODO: make private
std::vector< std::shared_ptr<CGObjectInstance> > objects;
std::vector<std::shared_ptr<CGObjectInstance>> objects;
explicit CMap(IGameCallback *cb);
~CMap();
@@ -110,9 +108,6 @@ public:
void putArtifactInstance(CArtifactSet & set, const ArtifactInstanceID art, const ArtifactPosition & slot);
void removeArtifactInstance(CArtifactSet & set, const ArtifactPosition & slot);
void addNewQuestInstance(std::shared_ptr<CQuest> quest);
void clearQuestInstance(const CQuest * quest);
void generateUniqueInstanceName(CGObjectInstance * target);
///Use only this method when creating new map object instances
@@ -213,8 +208,6 @@ public:
//Helper lists
std::map<TeleportChannelID, std::shared_ptr<TeleportChannel> > teleportChannels;
/// associative list to identify which hero/creature id belongs to which object id(index for objects)
std::map<si32, ObjectInstanceID> questIdentifierToId;
std::unique_ptr<CMapEditManager> editManager;
boost::multi_array<int3, 3> guardingCreaturePositions;
@@ -254,7 +247,6 @@ public:
h & events;
h & grailPos;
h & artInstances;
h & quests;
//TODO: viccondetails
h & terrain;

View File

@@ -1186,7 +1186,7 @@ std::shared_ptr<CGObjectInstance> CMapLoaderH3M::readMonster(const int3 & mapPos
if(features.levelAB)
{
object->identifier = reader->readUInt32();
map->questIdentifierToId[object->identifier] = objectInstanceID;
questIdentifierToId[object->identifier] = objectInstanceID;
}
auto hlp = std::make_unique<CStackInstance>();
@@ -2035,7 +2035,7 @@ std::shared_ptr<CGObjectInstance> CMapLoaderH3M::readHero(const int3 & mapPositi
if(features.levelAB)
{
unsigned int identifier = reader->readUInt32();
map->questIdentifierToId[identifier] = objectInstanceID;
questIdentifierToId[identifier] = objectInstanceID;
}
PlayerColor owner = reader->readPlayer();
@@ -2224,7 +2224,7 @@ std::shared_ptr<CGObjectInstance> CMapLoaderH3M::readSeerHut(const int3 & positi
if(features.levelHOTA3)
{
uint32_t repeateableQuestsCount = reader->readUInt32();
hut->quest->repeatedQuest = repeateableQuestsCount != 0;
hut->getQuest().repeatedQuest = repeateableQuestsCount != 0;
if(repeateableQuestsCount != 0)
logGlobal->warn("Map '%s': Seer Hut at %s - %d repeatable quests are not implemented!", mapName, position.toString(), repeateableQuestsCount);
@@ -2267,13 +2267,13 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, con
if(artID != ArtifactID::NONE)
{
//not none quest
hut->quest->mission.artifacts.push_back(artID);
hut->getQuest().mission.artifacts.push_back(artID);
missionType = EQuestMission::ARTIFACT;
}
hut->quest->lastDay = -1; //no timeout
hut->quest->isCustomFirst = false;
hut->quest->isCustomNext = false;
hut->quest->isCustomComplete = false;
hut->getQuest().lastDay = -1; //no timeout
hut->getQuest().isCustomFirst = false;
hut->getQuest().isCustomNext = false;
hut->getQuest().isCustomComplete = false;
}
if(missionType != EQuestMission::NONE)
@@ -2386,19 +2386,20 @@ EQuestMission CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & positi
{
for(int x = 0; x < 4; ++x)
{
guard->quest->mission.primary[x] = reader->readUInt8();
guard->getQuest().mission.primary[x] = reader->readUInt8();
}
break;
}
case EQuestMission::LEVEL:
{
guard->quest->mission.heroLevel = reader->readUInt32();
guard->getQuest().mission.heroLevel = reader->readUInt32();
break;
}
case EQuestMission::KILL_HERO:
case EQuestMission::KILL_CREATURE:
{
guard->quest->killTarget = ObjectInstanceID(reader->readUInt32());
assert(questsToResolve.count(guard) == 0);
questsToResolve[guard] = reader->readUInt32();
break;
}
case EQuestMission::ARTIFACT:
@@ -2414,7 +2415,7 @@ EQuestMission CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & positi
logGlobal->warn("Map '%s': Seer Hut at %s: Quest to find scroll '%s' is not implemented!", mapName, position.toString(), scrollSpell.toEntity(LIBRARY)->getJsonKey());
}
guard->quest->mission.artifacts.push_back(artid);
guard->getQuest().mission.artifacts.push_back(artid);
map->allowedArtifact.erase(artid); //these are unavailable for random generation
}
break;
@@ -2422,29 +2423,29 @@ EQuestMission CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & positi
case EQuestMission::ARMY:
{
size_t typeNumber = reader->readUInt8();
guard->quest->mission.creatures.resize(typeNumber);
guard->getQuest().mission.creatures.resize(typeNumber);
for(size_t hh = 0; hh < typeNumber; ++hh)
{
guard->quest->mission.creatures[hh].setType(reader->readCreature().toCreature());
guard->quest->mission.creatures[hh].count = reader->readUInt16();
guard->getQuest().mission.creatures[hh].setType(reader->readCreature().toCreature());
guard->getQuest().mission.creatures[hh].count = reader->readUInt16();
}
break;
}
case EQuestMission::RESOURCES:
{
for(int x = 0; x < 7; ++x)
guard->quest->mission.resources[x] = reader->readUInt32();
guard->getQuest().mission.resources[x] = reader->readUInt32();
break;
}
case EQuestMission::HERO:
{
guard->quest->mission.heroes.push_back(reader->readHero());
guard->getQuest().mission.heroes.push_back(reader->readHero());
break;
}
case EQuestMission::PLAYER:
{
guard->quest->mission.players.push_back(reader->readPlayer());
guard->getQuest().mission.players.push_back(reader->readPlayer());
break;
}
case EQuestMission::HOTA_MULTI:
@@ -2458,13 +2459,13 @@ EQuestMission CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & positi
std::set<HeroClassID> heroClasses;
reader->readBitmaskHeroClassesSized(heroClasses, false);
for(auto & hc : heroClasses)
guard->quest->mission.heroClasses.push_back(hc);
guard->getQuest().mission.heroClasses.push_back(hc);
break;
}
if(missionSubID == 1)
{
missionId = EQuestMission::HOTA_REACH_DATE;
guard->quest->mission.daysPassed = reader->readUInt32() + 1;
guard->getQuest().mission.daysPassed = reader->readUInt32() + 1;
break;
}
if(missionSubID == 2)
@@ -2483,13 +2484,13 @@ EQuestMission CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & positi
}
}
guard->quest->lastDay = reader->readInt32();
guard->quest->firstVisitText.appendTextID(readLocalizedString(TextIdentifier("quest", position.x, position.y, position.z, "firstVisit")));
guard->quest->nextVisitText.appendTextID(readLocalizedString(TextIdentifier("quest", position.x, position.y, position.z, "nextVisit")));
guard->quest->completedText.appendTextID(readLocalizedString(TextIdentifier("quest", position.x, position.y, position.z, "completed")));
guard->quest->isCustomFirst = !guard->quest->firstVisitText.empty();
guard->quest->isCustomNext = !guard->quest->nextVisitText.empty();
guard->quest->isCustomComplete = !guard->quest->completedText.empty();
guard->getQuest().lastDay = reader->readInt32();
guard->getQuest().firstVisitText.appendTextID(readLocalizedString(TextIdentifier("quest", position.x, position.y, position.z, "firstVisit")));
guard->getQuest().nextVisitText.appendTextID(readLocalizedString(TextIdentifier("quest", position.x, position.y, position.z, "nextVisit")));
guard->getQuest().completedText.appendTextID(readLocalizedString(TextIdentifier("quest", position.x, position.y, position.z, "completed")));
guard->getQuest().isCustomFirst = !guard->getQuest().firstVisitText.empty();
guard->getQuest().isCustomNext = !guard->getQuest().nextVisitText.empty();
guard->getQuest().isCustomComplete = !guard->getQuest().completedText.empty();
return missionId;
}
@@ -2756,7 +2757,8 @@ void CMapLoaderH3M::afterRead()
}
}
map->resolveQuestIdentifiers();
for (auto & quest : questsToResolve)
quest.first->getQuest().killTarget = questIdentifierToId.at(quest.second);
}
VCMI_LIB_NAMESPACE_END

View File

@@ -268,6 +268,10 @@ private:
std::vector<std::shared_ptr<ObjectTemplate>> originalTemplates;
std::vector<std::shared_ptr<ObjectTemplate>> remappedTemplates;
/// associative list to identify which hero/creature id belongs to which object id(index for objects)
std::map<si32, ObjectInstanceID> questIdentifierToId;
std::map<IQuestObject*, si32> questsToResolve;
/** ptr to the map object which gets filled by data from the buffer */
CMap * map;

View File

@@ -77,17 +77,7 @@ si32 MapObjectResolver::decode(const std::string & identifier) const
std::string MapObjectResolver::encode(si32 identifier) const
{
ObjectInstanceID id;
//use h3m questIdentifiers if they are present
if(owner->map->questIdentifierToId.empty())
{
id = ObjectInstanceID(identifier);
}
else
{
id = owner->map->questIdentifierToId[identifier];
}
ObjectInstanceID id(identifier);
auto object = owner->map->getObject(id);

View File

@@ -1238,11 +1238,10 @@ void RemoveObject::applyGs(CGameState *gs)
const auto * quest = dynamic_cast<const IQuestObject *>(obj);
if (quest)
{
gs->getMap().clearQuestInstance(quest->getQuest());
for (auto &player : gs->players)
{
vstd::erase_if(player.second.quests, [obj](const QuestInfo & q){
return q.obj == obj;
return q.obj == obj->id;
});
}
}

View File

@@ -515,14 +515,14 @@ void TreasurePlacer::addSeerHuts()
auto setRandomArtifact = [qap](CGSeerHut * obj)
{
ArtifactID artid = qap->drawRandomArtifact();
obj->quest->mission.artifacts.push_back(artid);
obj->getQuest().mission.artifacts.push_back(artid);
qap->addQuestArtifact(artid);
};
auto destroyObject = [qap](CGObjectInstance & obj)
{
auto & seer = dynamic_cast<CGSeerHut &>(obj);
// Artifact can be used again
ArtifactID artid = seer.getQuest()->mission.artifacts.front();
ArtifactID artid = seer.getQuest().mission.artifacts.front();
qap->addRandomArtifact(artid);
qap->removeQuestArtifact(artid);
};

View File

@@ -27,8 +27,6 @@ void CSerializer::addStdVecItems(CGameState *gs, GameLibrary *lib)
[](const CGObjectInstance &obj){ return obj.id; });
registerVectoredType<CArtifactInstance, ArtifactInstanceID>(&gs->getMap().artInstances,
[](const CArtifactInstance &artInst){ return artInst.getId(); });
registerVectoredType<CQuest, si32>(&gs->getMap().quests,
[](const CQuest &q){ return q.qid; });
smartVectorMembersSerialization = true;
}

View File

@@ -456,26 +456,26 @@ void Inspector::updateProperties(CGEvent * o)
void Inspector::updateProperties(CGSeerHut * o)
{
if(!o || !o->getQuest()) return;
if(!o) return;
addProperty(QObject::tr("First visit text"), o->getQuest()->firstVisitText, new MessageDelegate, false);
addProperty(QObject::tr("Next visit text"), o->getQuest()->nextVisitText, new MessageDelegate, false);
addProperty(QObject::tr("Completed text"), o->getQuest()->completedText, new MessageDelegate, false);
addProperty(QObject::tr("Repeat quest"), o->getQuest()->repeatedQuest, false);
addProperty(QObject::tr("Time limit"), o->getQuest()->lastDay, false);
addProperty(QObject::tr("First visit text"), o->getQuest().firstVisitText, new MessageDelegate, false);
addProperty(QObject::tr("Next visit text"), o->getQuest().nextVisitText, new MessageDelegate, false);
addProperty(QObject::tr("Completed text"), o->getQuest().completedText, new MessageDelegate, false);
addProperty(QObject::tr("Repeat quest"), o->getQuest().repeatedQuest, false);
addProperty(QObject::tr("Time limit"), o->getQuest().lastDay, false);
{ //Quest
auto * delegate = new QuestDelegate(controller, *o->quest);
auto * delegate = new QuestDelegate(controller, o->getQuest());
addProperty(QObject::tr("Quest"), PropertyEditorPlaceholder(), delegate, false);
}
}
void Inspector::updateProperties(CGQuestGuard * o)
{
if(!o || !o->getQuest()) return;
if(!o) return;
addProperty(QObject::tr("Reward"), PropertyEditorPlaceholder(), nullptr, true);
addProperty(QObject::tr("Repeat quest"), o->getQuest()->repeatedQuest, true);
addProperty(QObject::tr("Repeat quest"), o->getQuest().repeatedQuest, true);
}
void Inspector::updateProperties()
@@ -773,18 +773,18 @@ void Inspector::setProperty(CGSeerHut * o, const QString & key, const QVariant &
if(!o) return;
if(key == QObject::tr("First visit text"))
o->quest->firstVisitText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(),
o->getQuest().firstVisitText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(),
TextIdentifier("quest", o->instanceName, "firstVisit"), value.toString().toStdString()));
if(key == QObject::tr("Next visit text"))
o->quest->nextVisitText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(),
o->getQuest().nextVisitText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(),
TextIdentifier("quest", o->instanceName, "nextVisit"), value.toString().toStdString()));
if(key == QObject::tr("Completed text"))
o->quest->completedText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(),
o->getQuest().completedText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(),
TextIdentifier("quest", o->instanceName, "completed"), value.toString().toStdString()));
if(key == QObject::tr("Repeat quest"))
o->quest->repeatedQuest = value.toBool();
o->getQuest().repeatedQuest = value.toBool();
if(key == QObject::tr("Time limit"))
o->quest->lastDay = value.toString().toInt();
o->getQuest().lastDay = value.toString().toInt();
}
void Inspector::setProperty(CGQuestGuard * o, const QString & key, const QVariant & value)