1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-08-13 19:54:17 +02:00

Merge pull request #3035 from Nordsoft91/quests

Quests rework
This commit is contained in:
Nordsoft91
2023-10-14 21:38:15 +02:00
committed by GitHub
34 changed files with 1976 additions and 882 deletions

View File

@@ -38,41 +38,28 @@ TGoalVec CompleteQuest::decompose() const
logAi->debug("Trying to realize quest: %s", questToString());
switch(q.quest->missionType)
{
case CQuest::MISSION_ART:
if(!q.quest->mission.artifacts.empty())
return missionArt();
case CQuest::MISSION_HERO:
if(!q.quest->mission.heroes.empty())
return missionHero();
case CQuest::MISSION_ARMY:
if(!q.quest->mission.creatures.empty())
return missionArmy();
case CQuest::MISSION_RESOURCES:
if(q.quest->mission.resources.nonZero())
return missionResources();
case CQuest::MISSION_KILL_HERO:
case CQuest::MISSION_KILL_CREATURE:
if(q.quest->killTarget != ObjectInstanceID::NONE)
return missionDestroyObj();
case CQuest::MISSION_PRIMARY_STAT:
return missionIncreasePrimaryStat();
for(auto & s : q.quest->mission.primary)
if(s)
return missionIncreasePrimaryStat();
case CQuest::MISSION_LEVEL:
if(q.quest->mission.heroLevel > 0)
return missionLevel();
case CQuest::MISSION_PLAYER:
if(ai->playerID.getNum() != q.quest->m13489val)
logAi->debug("Can't be player of color %d", q.quest->m13489val);
break;
case CQuest::MISSION_KEYMASTER:
return missionKeymaster();
} //end of switch
return TGoalVec();
}
@@ -107,7 +94,7 @@ std::string CompleteQuest::questToString() const
return "find " + VLC->generaltexth->tentColors[q.obj->subID] + " keymaster tent";
}
if(q.quest->missionType == CQuest::MISSION_NONE)
if(q.quest->questName == CQuest::missionName(0))
return "inactive quest";
MetaString ms;
@@ -137,7 +124,7 @@ TGoalVec CompleteQuest::missionArt() const
CaptureObjectsBehavior findArts;
for(auto art : q.quest->m5arts)
for(auto art : q.quest->mission.artifacts)
{
solutions.push_back(sptr(CaptureObjectsBehavior().ofType(Obj::ARTIFACT, art)));
}
@@ -223,7 +210,7 @@ TGoalVec CompleteQuest::missionResources() const
TGoalVec CompleteQuest::missionDestroyObj() const
{
auto obj = cb->getObjByQuestIdentifier(q.quest->m13489val);
auto obj = cb->getObjByQuestIdentifier(q.quest->killTarget);
if(!obj)
return CaptureObjectsBehavior(q.obj).decompose();

View File

@@ -25,7 +25,7 @@ namespace AIPathfinding
return dynamic_cast<const IQuestObject *>(questInfo.obj)->checkQuest(node->actor->hero);
}
return questInfo.quest->progress == CQuest::NOT_ACTIVE
return questInfo.quest->activeForPlayers.count(node->actor->hero->getOwner())
|| questInfo.quest->checkQuest(node->actor->hero);
}

View File

@@ -130,7 +130,9 @@ namespace AIPathfinding
auto questInfo = QuestInfo(questObj->quest, destination.nodeObject, destination.coord);
QuestAction questAction(questInfo);
if(destination.nodeObject->ID == Obj::QUEST_GUARD && questObj->quest->missionType == CQuest::MISSION_NONE)
if(destination.nodeObject->ID == Obj::QUEST_GUARD
&& questObj->quest->mission == Rewardable::Limiter{}
&& questObj->quest->killTarget == ObjectInstanceID::NONE)
{
return false;
}

View File

@@ -21,48 +21,43 @@ bool CompleteQuest::operator==(const CompleteQuest & other) const
return q.quest->qid == other.q.quest->qid;
}
bool isKeyMaster(const QuestInfo & q)
{
return q.obj && (q.obj->ID == Obj::BORDER_GATE || q.obj->ID == Obj::BORDERGUARD);
}
TGoalVec CompleteQuest::getAllPossibleSubgoals()
{
TGoalVec solutions;
if(q.quest->missionType && q.quest->progress != CQuest::COMPLETE)
if(!q.quest->isCompleted)
{
logAi->debug("Trying to realize quest: %s", questToString());
switch(q.quest->missionType)
{
case CQuest::MISSION_ART:
return missionArt();
case CQuest::MISSION_HERO:
return missionHero();
case CQuest::MISSION_ARMY:
return missionArmy();
case CQuest::MISSION_RESOURCES:
return missionResources();
case CQuest::MISSION_KILL_HERO:
case CQuest::MISSION_KILL_CREATURE:
return missionDestroyObj();
case CQuest::MISSION_PRIMARY_STAT:
return missionIncreasePrimaryStat();
case CQuest::MISSION_LEVEL:
return missionLevel();
case CQuest::MISSION_PLAYER:
if(ai->playerID.getNum() != q.quest->m13489val)
logAi->debug("Can't be player of color %d", q.quest->m13489val);
break;
case CQuest::MISSION_KEYMASTER:
if(isKeyMaster(q))
return missionKeymaster();
} //end of switch
if(!q.quest->mission.artifacts.empty())
return missionArt();
if(!q.quest->mission.heroes.empty())
return missionHero();
if(!q.quest->mission.creatures.empty())
return missionArmy();
if(q.quest->mission.resources.nonZero())
return missionResources();
if(q.quest->killTarget != ObjectInstanceID::NONE)
return missionDestroyObj();
for(auto & s : q.quest->mission.primary)
if(s)
return missionIncreasePrimaryStat();
if(q.quest->mission.heroLevel > 0)
return missionLevel();
}
return TGoalVec();
@@ -70,7 +65,7 @@ TGoalVec CompleteQuest::getAllPossibleSubgoals()
TSubgoal CompleteQuest::whatToDoToAchieve()
{
if(q.quest->missionType == CQuest::MISSION_NONE)
if(q.quest->mission == Rewardable::Limiter{})
{
throw cannotFulfillGoalException("Can not complete inactive quest");
}
@@ -104,7 +99,7 @@ std::string CompleteQuest::completeMessage() const
std::string CompleteQuest::questToString() const
{
if(q.quest->missionType == CQuest::MISSION_NONE)
if(q.quest->questName == CQuest::missionName(0))
return "inactive quest";
MetaString ms;
@@ -137,7 +132,7 @@ TGoalVec CompleteQuest::missionArt() const
if(!solutions.empty())
return solutions;
for(auto art : q.quest->m5arts)
for(auto art : q.quest->mission.artifacts)
{
solutions.push_back(sptr(GetArtOfType(art))); //TODO: transport?
}
@@ -165,7 +160,7 @@ TGoalVec CompleteQuest::missionArmy() const
if(!solutions.empty())
return solutions;
for(auto creature : q.quest->m6creatures)
for(auto creature : q.quest->mission.creatures)
{
solutions.push_back(sptr(GatherTroops(creature.type->getId(), creature.count)));
}
@@ -179,7 +174,7 @@ TGoalVec CompleteQuest::missionIncreasePrimaryStat() const
if(solutions.empty())
{
for(int i = 0; i < q.quest->m2stats.size(); ++i)
for(int i = 0; i < q.quest->mission.primary.size(); ++i)
{
// TODO: library, school and other boost objects
logAi->debug("Don't know how to increase primary stat %d", i);
@@ -195,7 +190,7 @@ TGoalVec CompleteQuest::missionLevel() const
if(solutions.empty())
{
logAi->debug("Don't know how to reach hero level %d", q.quest->m13489val);
logAi->debug("Don't know how to reach hero level %d", q.quest->mission.heroLevel);
}
return solutions;
@@ -227,10 +222,10 @@ TGoalVec CompleteQuest::missionResources() const
}
else
{
for(int i = 0; i < q.quest->m7resources.size(); ++i)
for(int i = 0; i < q.quest->mission.resources.size(); ++i)
{
if(q.quest->m7resources[i])
solutions.push_back(sptr(CollectRes(static_cast<EGameResID>(i), q.quest->m7resources[i])));
if(q.quest->mission.resources[i])
solutions.push_back(sptr(CollectRes(static_cast<EGameResID>(i), q.quest->mission.resources[i])));
}
}
}
@@ -246,7 +241,7 @@ TGoalVec CompleteQuest::missionDestroyObj() const
{
TGoalVec solutions;
auto obj = cb->getObjByQuestIdentifier(q.quest->m13489val);
auto obj = cb->getObjByQuestIdentifier(q.quest->killTarget);
if(!obj)
return ai->ah->howToVisitObj(q.obj);

View File

@@ -148,11 +148,11 @@ void CQuestLog::recreateLabelList()
int currentLabel = 0;
for (int i = 0; i < quests.size(); ++i)
{
// Quests with MISSION_NONE type don't have text for them and can't be displayed
if (quests[i].quest->missionType == CQuest::MISSION_NONE)
// Quests without mision don't have text for them and can't be displayed
if (quests[i].quest->mission == Rewardable::Limiter{})
continue;
if (quests[i].quest->progress == CQuest::COMPLETE)
if (quests[i].quest->isCompleted)
{
completeMissing = false;
if (hideComplete)
@@ -180,7 +180,7 @@ void CQuestLog::recreateLabelList()
labels.push_back(label);
// Select latest active quest
if (quests[i].quest->progress != CQuest::COMPLETE)
if(!quests[i].quest->isCompleted)
selectQuest(i, currentLabel);
currentLabel = static_cast<int>(labels.size());
@@ -236,7 +236,7 @@ void CQuestLog::selectQuest(int which, int labelId)
MetaString text;
std::vector<Component> components;
currentQuest->quest->getVisitText (text, components, currentQuest->quest->isCustomFirst, true);
currentQuest->quest->getVisitText(text, components, true);
if(description->slider)
description->slider->scrollToMin(); // scroll text to start position
description->setText(text.toString()); //TODO: use special log entry text
@@ -247,9 +247,15 @@ void CQuestLog::selectQuest(int which, int labelId)
int descriptionHeight = DESCRIPTION_HEIGHT_MAX;
if(componentsSize)
{
descriptionHeight -= 15;
CComponent::ESize imageSize = CComponent::large;
switch (currentQuest->quest->missionType)
if (componentsSize > 4)
{
imageSize = CComponent::small; // Only small icons can be used for resources as 4+ icons take too much space
descriptionHeight -= 155;
}
else
descriptionHeight -= 130;
/*switch (currentQuest->quest->missionType)
{
case CQuest::MISSION_ARMY:
{
@@ -285,7 +291,7 @@ void CQuestLog::selectQuest(int which, int labelId)
default:
descriptionHeight -= 115;
break;
}
}*/
OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);

View File

@@ -72,7 +72,7 @@ Rewardable object is defined similarly to other objects, with key difference bei
// additional list of conditions. Limiter will be valid if any of these conditions are true
"anyOf" : [
{
// See "Configurable Properties" section for additiona parameters
// See "Configurable Properties" section for additional parameters
<additional properties>
}
]
@@ -80,12 +80,12 @@ Rewardable object is defined similarly to other objects, with key difference bei
// additional list of conditions. Limiter will be valid only if none of these conditions are true
"noneOf" : [
{
// See "Configurable Properties" section for additiona parameters
// See "Configurable Properties" section for additional parameters
<additional properties>
}
]
// See "Configurable Properties" section for additiona parameters
// See "Configurable Properties" section for additional parameters
<additional properties>
}
@@ -95,7 +95,7 @@ Rewardable object is defined similarly to other objects, with key difference bei
// object will be disappeared after taking reward is set to true
"removeObject": false
// See "Configurable Properties" section for additiona parameters
// See "Configurable Properties" section for additional parameters
<additional properties>
}
],
@@ -451,3 +451,30 @@ Keep in mind, that all randomization is performed on map load and on object rese
"schoolLevel": 3
}
```
### Player color
- Can be used as limiter
- Can NOT be used as reward
- Only players with specific color can pass the limiter
```jsonc
"colors" : [ "red", "blue", "tan", "green", "orange", "purple", "teal", "pink" ]
```
### Hero types
- Can be used as limiter
- Can NOT be used as reward
- Only specific heroes can pass the limiter
```jsonc
"heroes" : [ "orrin" ]
```
### Hero classes
- Can be used as limiter
- Can NOT be used as reward
- Only heroes belonging to specific classes can pass the limiter
```jsonc
"heroClasses" : [ "battlemage" ]
```

View File

@@ -1029,6 +1029,14 @@ void CStackBasicDescriptor::setType(const CCreature * c)
type = c;
}
bool operator== (const CStackBasicDescriptor & l, const CStackBasicDescriptor & r)
{
return (!l.type && !r.type)
|| (l.type && r.type
&& l.type->getId() == r.type->getId()
&& l.count == r.count);
}
void CStackBasicDescriptor::serializeJson(JsonSerializeFormat & handler)
{
handler.serializeInt("amount", count);

View File

@@ -43,6 +43,8 @@ public:
virtual void setType(const CCreature * c);
friend bool operator== (const CStackBasicDescriptor & l, const CStackBasicDescriptor & r);
template <typename Handler> void serialize(Handler &h, const int version)
{
if(h.saving)

View File

@@ -662,9 +662,9 @@ std::string CGameInfoCallback::getTavernRumor(const CGObjectInstance * townOrTav
case RumorState::TYPE_SPECIAL:
text.replaceLocalString(EMetaText::GENERAL_TXT, rumor.first);
if(rumor.first == RumorState::RUMOR_GRAIL)
text.replaceTextID(TextIdentifier("core", "genrltxt", "arraytxt", 158 + rumor.second).get());
text.replaceTextID(TextIdentifier("core", "arraytxt", 158 + rumor.second).get());
else
text.replaceTextID(TextIdentifier("core", "genrltxt", "capitalColors", rumor.second).get());
text.replaceTextID(TextIdentifier("core", "plcolors", rumor.second).get());
break;
case RumorState::TYPE_MAP:
@@ -672,7 +672,7 @@ std::string CGameInfoCallback::getTavernRumor(const CGObjectInstance * townOrTav
break;
case RumorState::TYPE_RAND:
text.replaceTextID(TextIdentifier("core", "genrltxt", "randtvrn", rumor.first).get());
text.replaceTextID(TextIdentifier("core", "randtvrn", rumor.first).get());
break;
}

View File

@@ -264,21 +264,21 @@ void TextLocalizationContainer::registerStringOverride(const std::string & modCo
void TextLocalizationContainer::addSubContainer(const TextLocalizationContainer & container)
{
subContainers.insert(&container);
subContainers.push_back(&container);
}
void TextLocalizationContainer::removeSubContainer(const TextLocalizationContainer & container)
{
subContainers.erase(&container);
subContainers.erase(std::remove(subContainers.begin(), subContainers.end(), &container), subContainers.end());
}
const std::string & TextLocalizationContainer::deserialize(const TextIdentifier & identifier) const
{
if(stringsLocalizations.count(identifier.get()) == 0)
{
for(const auto * container : subContainers)
if(container->identifierExists(identifier))
return container->deserialize(identifier);
for(auto containerIter = subContainers.rbegin(); containerIter != subContainers.rend(); ++containerIter)
if((*containerIter)->identifierExists(identifier))
return (*containerIter)->deserialize(identifier);
logGlobal->error("Unable to find localization for string '%s'", identifier.get());
return identifier.get();
@@ -547,7 +547,7 @@ CGeneralTextHandler::CGeneralTextHandler():
for (size_t i = 0; i < 9; ++i) //9 types of quests
{
std::string questName = CQuest::missionName(static_cast<CQuest::Emission>(1+i));
std::string questName = CQuest::missionName(1+i);
for (size_t j = 0; j < 5; ++j)
{

View File

@@ -146,7 +146,7 @@ protected:
/// map identifier -> localization
std::unordered_map<std::string, StringState> stringsLocalizations;
std::set<const TextLocalizationContainer *> subContainers;
std::vector<const TextLocalizationContainer *> subContainers;
/// add selected string to internal storage as high-priority strings
void registerStringOverride(const std::string & modContext, const std::string & language, const TextIdentifier & UID, const std::string & localized);

View File

@@ -22,6 +22,7 @@
#include "CCreatureSet.h"
#include "spells/CSpellHandler.h"
#include "CSkillHandler.h"
#include "CHeroHandler.h"
#include "IGameCallback.h"
#include "mapObjects/IObjectInterface.h"
#include "modding/IdentifierStorage.h"
@@ -282,6 +283,46 @@ namespace JsonRandom
return ret;
}
std::vector<PlayerColor> loadColors(const JsonNode & value, CRandomGenerator & rng)
{
std::vector<PlayerColor> ret;
std::set<std::string> def;
for(auto & color : GameConstants::PLAYER_COLOR_NAMES)
def.insert(color);
for(auto & entry : value.Vector())
{
auto key = loadKey(entry, rng, def);
auto pos = vstd::find_pos(GameConstants::PLAYER_COLOR_NAMES, key);
if(pos < 0)
logMod->warn("Unable to determine player color %s", key);
else
ret.emplace_back(pos);
}
return ret;
}
std::vector<HeroTypeID> loadHeroes(const JsonNode & value, CRandomGenerator & rng)
{
std::vector<HeroTypeID> ret;
for(auto & entry : value.Vector())
{
ret.push_back(VLC->heroTypes()->getByIndex(VLC->identifiers()->getIdentifier("hero", entry.String()).value())->getId());
}
return ret;
}
std::vector<HeroClassID> loadHeroClasses(const JsonNode & value, CRandomGenerator & rng)
{
std::vector<HeroClassID> ret;
for(auto & entry : value.Vector())
{
ret.push_back(VLC->heroClasses()->getByIndex(VLC->identifiers()->getIdentifier("heroClass", entry.String()).value())->getId());
}
return ret;
}
CStackBasicDescriptor loadCreature(const JsonNode & value, CRandomGenerator & rng)
{
CStackBasicDescriptor stack;

View File

@@ -48,6 +48,10 @@ namespace JsonRandom
DLL_LINKAGE std::vector<CStackBasicDescriptor> loadCreatures(const JsonNode & value, CRandomGenerator & rng);
DLL_LINKAGE std::vector<RandomStackInfo> evaluateCreatures(const JsonNode & value);
DLL_LINKAGE std::vector<PlayerColor> loadColors(const JsonNode & value, CRandomGenerator & rng);
DLL_LINKAGE std::vector<HeroTypeID> loadHeroes(const JsonNode & value, CRandomGenerator & rng);
DLL_LINKAGE std::vector<HeroClassID> loadHeroClasses(const JsonNode & value, CRandomGenerator & rng);
DLL_LINKAGE std::vector<Bonus> loadBonuses(const JsonNode & value);
//DLL_LINKAGE std::vector<Component> loadComponents(const JsonNode & value);
}

View File

@@ -19,6 +19,8 @@
#include <vcmi/FactionService.h>
#include <vcmi/HeroType.h>
#include <vcmi/HeroTypeService.h>
#include <vcmi/HeroClass.h>
#include <vcmi/HeroClassService.h>
#include <vcmi/spells/Spell.h>
#include <vcmi/spells/Service.h>
@@ -103,6 +105,25 @@ namespace GameConstants
#endif
}
si32 HeroClassID::decode(const std::string & identifier)
{
auto rawId = VLC->identifiers()->getIdentifier(ModScope::scopeMap(), "heroClass", identifier);
if(rawId)
return rawId.value();
else
return -1;
}
std::string HeroClassID::encode(const si32 index)
{
return VLC->heroClasses()->getByIndex(index)->getJsonKey();
}
std::string HeroClassID::entityType()
{
return "heroClass";
}
si32 HeroTypeID::decode(const std::string & identifier)
{
auto rawId = VLC->identifiers()->getIdentifier(ModScope::scopeMap(), "hero", identifier);

View File

@@ -223,6 +223,10 @@ class HeroClassID : public Identifier<HeroClassID>
{
public:
using Identifier<HeroClassID>::Identifier;
///json serialization helpers
DLL_LINKAGE static si32 decode(const std::string & identifier);
DLL_LINKAGE static std::string encode(const si32 index);
static std::string entityType();
};
class HeroTypeID : public Identifier<HeroTypeID>

View File

@@ -28,6 +28,7 @@
#include "../mapping/CMap.h"
#include "../modding/ModScope.h"
#include "../modding/ModUtility.h"
#include "../spells/CSpellHandler.h"
VCMI_LIB_NAMESPACE_BEGIN
@@ -37,16 +38,17 @@ std::map <PlayerColor, std::set <ui8> > CGKeys::playerKeyMap;
//TODO: Remove constructor
CQuest::CQuest():
qid(-1),
missionType(MISSION_NONE),
progress(NOT_ACTIVE),
isCompleted(false),
lastDay(-1),
m13489val(0),
killTarget(ObjectInstanceID::NONE),
textOption(0),
completedOption(0),
stackDirection(0),
isCustomFirst(false),
isCustomNext(false),
isCustomComplete(false)
isCustomComplete(false),
repeatedQuest(false),
questName(CQuest::missionName(0))
{
}
@@ -56,9 +58,9 @@ static std::string visitedTxt(const bool visited)
return VLC->generaltexth->allTexts[id];
}
const std::string & CQuest::missionName(CQuest::Emission mission)
const std::string & CQuest::missionName(int mission)
{
static const std::array<std::string, 11> names = {
static const std::array<std::string, 13> names = {
"empty",
"heroLevel",
"primarySkill",
@@ -69,7 +71,9 @@ const std::string & CQuest::missionName(CQuest::Emission mission)
"bringResources",
"bringHero",
"bringPlayer",
"keymaster"
"keymaster",
"hota",
"other"
};
if(static_cast<size_t>(mission) < names.size())
@@ -99,7 +103,7 @@ bool CQuest::checkMissionArmy(const CQuest * q, const CCreatureSet * army)
ui32 count = 0;
ui32 slotsCount = 0;
bool hasExtraCreatures = false;
for(cre = q->m6creatures.begin(); cre != q->m6creatures.end(); ++cre)
for(cre = q->mission.creatures.begin(); cre != q->mission.creatures.end(); ++cre)
{
for(count = 0, it = army->Slots().begin(); it != army->Slots().end(); ++it)
{
@@ -121,348 +125,212 @@ bool CQuest::checkMissionArmy(const CQuest * q, const CCreatureSet * army)
bool CQuest::checkQuest(const CGHeroInstance * h) const
{
switch (missionType)
{
case MISSION_NONE:
return true;
case MISSION_LEVEL:
return m13489val <= h->level;
case MISSION_PRIMARY_STAT:
for(int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i)
{
if(h->getPrimSkillLevel(static_cast<PrimarySkill>(i)) < static_cast<int>(m2stats[i]))
return false;
}
return true;
case MISSION_KILL_HERO:
case MISSION_KILL_CREATURE:
if(!CGHeroInstance::cb->getObjByQuestIdentifier(m13489val))
return true;
return false;
case MISSION_ART:
{
// if the object was deserialized
if(artifactsRequirements.empty())
for(const auto & id : m5arts)
++artifactsRequirements[id];
if(!mission.heroAllowed(h))
return false;
size_t reqSlots = 0;
for(const auto & elem : artifactsRequirements)
{
// check required amount of artifacts
if(h->getArtPosCount(elem.first, false, true, true) < elem.second)
return false;
if(!h->hasArt(elem.first))
reqSlots += h->getAssemblyByConstituent(elem.first)->getPartsInfo().size() - 2;
}
if(ArtifactUtils::isBackpackFreeSlots(h, reqSlots))
return true;
else
return false;
}
case MISSION_ARMY:
return checkMissionArmy(this, h);
case MISSION_RESOURCES:
for(GameResID i = EGameResID::WOOD; i <= EGameResID::GOLD; ++i) //including Mithril ?
{ //Quest has no direct access to callback
if(CGHeroInstance::cb->getResource(h->tempOwner, i) < static_cast<int>(m7resources[i]))
return false;
}
return true;
case MISSION_HERO:
return m13489val == h->type->getIndex();
case MISSION_PLAYER:
return m13489val == h->getOwner().getNum();
default:
if(killTarget != ObjectInstanceID::NONE)
{
if(CGHeroInstance::cb->getObjByQuestIdentifier(killTarget))
return false;
}
return true;
}
void CQuest::getVisitText(MetaString &iwText, std::vector<Component> &components, bool isCustom, bool firstVisit, const CGHeroInstance * h) const
void CQuest::completeQuest(IGameCallback * cb, const CGHeroInstance *h) const
{
for(auto & elem : mission.artifacts)
{
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);
}
}
}
cb->takeCreatures(h->id, mission.creatures);
cb->giveResources(h->getOwner(), mission.resources);
}
void CQuest::addTextReplacements(MetaString & text, std::vector<Component> & components) const
{
if(mission.heroLevel > 0)
text.replaceNumber(mission.heroLevel);
if(mission.heroExperience > 0)
text.replaceNumber(mission.heroExperience);
{ //primary skills
MetaString loot;
for(int i = 0; i < 4; ++i)
{
if(mission.primary[i])
{
loot.appendRawString("%d %s");
loot.replaceNumber(mission.primary[i]);
loot.replaceRawString(VLC->generaltexth->primarySkillNames[i]);
}
}
for(auto & skill : mission.secondary)
{
loot.appendTextID(VLC->skillh->getById(skill.first)->getNameTextID());
}
for(auto & spell : mission.spells)
{
loot.appendTextID(VLC->spellh->getById(spell)->getNameTextID());
}
if(!loot.empty())
text.replaceRawString(loot.buildList());
}
if(killTarget != ObjectInstanceID::NONE && !heroName.empty())
{
components.emplace_back(Component::EComponentType::HERO_PORTRAIT, heroPortrait, 0, 0);
addKillTargetReplacements(text);
}
if(killTarget != ObjectInstanceID::NONE && stackToKill.type)
{
components.emplace_back(stackToKill);
addKillTargetReplacements(text);
}
if(!mission.heroes.empty())
text.replaceRawString(VLC->heroh->getById(mission.heroes.front())->getNameTranslated());
if(!mission.artifacts.empty())
{
MetaString loot;
for(const auto & elem : mission.artifacts)
{
loot.appendRawString("%s");
loot.replaceLocalString(EMetaText::ART_NAMES, elem);
}
text.replaceRawString(loot.buildList());
}
if(!mission.creatures.empty())
{
MetaString loot;
for(const auto & elem : mission.creatures)
{
loot.appendRawString("%s");
loot.replaceCreatureName(elem);
}
text.replaceRawString(loot.buildList());
}
if(mission.resources.nonZero())
{
MetaString loot;
for(int i = 0; i < 7; ++i)
{
if(mission.resources[i])
{
loot.appendRawString("%d %s");
loot.replaceNumber(mission.resources[i]);
loot.replaceLocalString(EMetaText::RES_NAMES, i);
}
}
text.replaceRawString(loot.buildList());
}
if(!mission.players.empty())
{
MetaString loot;
for(auto & p : mission.players)
loot.appendLocalString(EMetaText::COLOR, p);
text.replaceRawString(loot.buildList());
}
if(lastDay >= 0)
text.replaceNumber(lastDay - IObjectInterface::cb->getDate(Date::DAY));
}
void CQuest::getVisitText(MetaString &iwText, std::vector<Component> &components, bool firstVisit, const CGHeroInstance * h) const
{
MetaString text;
bool failRequirements = (h ? !checkQuest(h) : true);
mission.loadComponents(components, h);
if(firstVisit)
{
isCustom = isCustomFirst;
text = firstVisitText;
iwText.appendRawString(text.toString());
}
iwText.appendRawString(firstVisitText.toString());
else if(failRequirements)
{
isCustom = isCustomNext;
text = nextVisitText;
iwText.appendRawString(text.toString());
}
switch (missionType)
{
case MISSION_LEVEL:
components.emplace_back(Component::EComponentType::EXPERIENCE, 0, m13489val, 0);
if(!isCustom)
iwText.replaceNumber(m13489val);
break;
case MISSION_PRIMARY_STAT:
{
MetaString loot;
for(int i = 0; i < 4; ++i)
{
if(m2stats[i])
{
components.emplace_back(Component::EComponentType::PRIM_SKILL, i, m2stats[i], 0);
loot.appendRawString("%d %s");
loot.replaceNumber(m2stats[i]);
loot.replaceRawString(VLC->generaltexth->primarySkillNames[i]);
}
}
if (!isCustom)
iwText.replaceRawString(loot.buildList());
}
break;
case MISSION_KILL_HERO:
components.emplace_back(Component::EComponentType::HERO_PORTRAIT, heroPortrait, 0, 0);
if(!isCustom)
addReplacements(iwText, text.toString());
break;
case MISSION_HERO:
//FIXME: portrait may not match hero, if custom portrait was set in map editor
components.emplace_back(Component::EComponentType::HERO_PORTRAIT, VLC->heroh->objects[m13489val]->imageIndex, 0, 0);
if(!isCustom)
iwText.replaceRawString(VLC->heroh->objects[m13489val]->getNameTranslated());
break;
case MISSION_KILL_CREATURE:
{
components.emplace_back(stackToKill);
if(!isCustom)
{
addReplacements(iwText, text.toString());
}
}
break;
case MISSION_ART:
{
MetaString loot;
for(const auto & elem : m5arts)
{
components.emplace_back(Component::EComponentType::ARTIFACT, elem, 0, 0);
loot.appendRawString("%s");
loot.replaceLocalString(EMetaText::ART_NAMES, elem);
}
if(!isCustom)
iwText.replaceRawString(loot.buildList());
}
break;
case MISSION_ARMY:
{
MetaString loot;
for(const auto & elem : m6creatures)
{
components.emplace_back(elem);
loot.appendRawString("%s");
loot.replaceCreatureName(elem);
}
if(!isCustom)
iwText.replaceRawString(loot.buildList());
}
break;
case MISSION_RESOURCES:
{
MetaString loot;
for(int i = 0; i < 7; ++i)
{
if(m7resources[i])
{
components.emplace_back(Component::EComponentType::RESOURCE, i, m7resources[i], 0);
loot.appendRawString("%d %s");
loot.replaceNumber(m7resources[i]);
loot.replaceLocalString(EMetaText::RES_NAMES, i);
}
}
if(!isCustom)
iwText.replaceRawString(loot.buildList());
}
break;
case MISSION_PLAYER:
components.emplace_back(Component::EComponentType::FLAG, m13489val, 0, 0);
if(!isCustom)
iwText.replaceLocalString(EMetaText::COLOR, m13489val);
break;
}
iwText.appendRawString(nextVisitText.toString());
if(lastDay >= 0)
iwText.appendTextID(TextIdentifier("core", "seerhut", "time", textOption).get());
addTextReplacements(iwText, components);
}
void CQuest::getRolloverText(MetaString &ms, bool onHover) const
{
// Quests with MISSION_NONE type don't have a text for them
assert(missionType != MISSION_NONE);
if(onHover)
ms.appendRawString("\n\n");
std::string questName = missionName(missionType);
std::string questState = missionState(onHover ? 3 : 4);
ms.appendRawString(VLC->generaltexth->translate("core.seerhut.quest", questName, questState,textOption));
ms.appendTextID(TextIdentifier("core", "seerhut", "quest", questName, questState, textOption).get());
switch(missionType)
{
case MISSION_LEVEL:
ms.replaceNumber(m13489val);
break;
case MISSION_PRIMARY_STAT:
{
MetaString loot;
for (int i = 0; i < 4; ++i)
{
if (m2stats[i])
{
loot.appendRawString("%d %s");
loot.replaceNumber(m2stats[i]);
loot.replaceRawString(VLC->generaltexth->primarySkillNames[i]);
}
}
ms.replaceRawString(loot.buildList());
}
break;
case MISSION_KILL_HERO:
ms.replaceRawString(heroName);
break;
case MISSION_KILL_CREATURE:
ms.replaceCreatureName(stackToKill);
break;
case MISSION_ART:
{
MetaString loot;
for(const auto & elem : m5arts)
{
loot.appendRawString("%s");
loot.replaceLocalString(EMetaText::ART_NAMES, elem);
}
ms.replaceRawString(loot.buildList());
}
break;
case MISSION_ARMY:
{
MetaString loot;
for(const auto & elem : m6creatures)
{
loot.appendRawString("%s");
loot.replaceCreatureName(elem);
}
ms.replaceRawString(loot.buildList());
}
break;
case MISSION_RESOURCES:
{
MetaString loot;
for (int i = 0; i < 7; ++i)
{
if (m7resources[i])
{
loot.appendRawString("%d %s");
loot.replaceNumber(m7resources[i]);
loot.replaceLocalString(EMetaText::RES_NAMES, i);
}
}
ms.replaceRawString(loot.buildList());
}
break;
case MISSION_HERO:
ms.replaceRawString(VLC->heroh->objects[m13489val]->getNameTranslated());
break;
case MISSION_PLAYER:
ms.replaceRawString(VLC->generaltexth->colors[m13489val]);
break;
default:
break;
}
std::vector<Component> components;
addTextReplacements(ms, components);
}
void CQuest::getCompletionText(MetaString &iwText) const
{
iwText.appendRawString(completedText.toString());
switch(missionType)
{
case CQuest::MISSION_LEVEL:
if (!isCustomComplete)
iwText.replaceNumber(m13489val);
break;
case CQuest::MISSION_PRIMARY_STAT:
{
MetaString loot;
assert(m2stats.size() <= 4);
for (int i = 0; i < m2stats.size(); ++i)
{
if (m2stats[i])
{
loot.appendRawString("%d %s");
loot.replaceNumber(m2stats[i]);
loot.replaceRawString(VLC->generaltexth->primarySkillNames[i]);
}
}
if (!isCustomComplete)
iwText.replaceRawString(loot.buildList());
break;
}
case CQuest::MISSION_ART:
{
MetaString loot;
for(const auto & elem : m5arts)
{
loot.appendRawString("%s");
loot.replaceLocalString(EMetaText::ART_NAMES, elem);
}
if (!isCustomComplete)
iwText.replaceRawString(loot.buildList());
}
break;
case CQuest::MISSION_ARMY:
{
MetaString loot;
for(const auto & elem : m6creatures)
{
loot.appendRawString("%s");
loot.replaceCreatureName(elem);
}
if (!isCustomComplete)
iwText.replaceRawString(loot.buildList());
}
break;
case CQuest::MISSION_RESOURCES:
{
MetaString loot;
for (int i = 0; i < 7; ++i)
{
if (m7resources[i])
{
loot.appendRawString("%d %s");
loot.replaceNumber(m7resources[i]);
loot.replaceLocalString(EMetaText::RES_NAMES, i);
}
}
if (!isCustomComplete)
iwText.replaceRawString(loot.buildList());
}
break;
case MISSION_KILL_HERO:
case MISSION_KILL_CREATURE:
if (!isCustomComplete)
addReplacements(iwText, completedText.toString());
break;
case MISSION_HERO:
if (!isCustomComplete)
iwText.replaceRawString(VLC->heroh->objects[m13489val]->getNameTranslated());
break;
case MISSION_PLAYER:
if (!isCustomComplete)
iwText.replaceRawString(VLC->generaltexth->colors[m13489val]);
break;
}
std::vector<Component> components;
addTextReplacements(iwText, components);
}
void CQuest::addArtifactID(const ArtifactID & id)
void CQuest::defineQuestName()
{
m5arts.push_back(id);
++artifactsRequirements[id];
//standard quests
questName = CQuest::missionName(0);
if(mission != Rewardable::Limiter{}) questName = CQuest::missionName(12);
if(mission.heroLevel > 0) questName = CQuest::missionName(1);
for(auto & s : mission.primary) if(s) questName = CQuest::missionName(2);
if(!mission.spells.empty()) questName = CQuest::missionName(2);
if(!mission.secondary.empty()) questName = CQuest::missionName(2);
if(killTarget != ObjectInstanceID::NONE && !heroName.empty()) questName = CQuest::missionName(3);
if(killTarget != ObjectInstanceID::NONE && stackToKill.getType()) questName = CQuest::missionName(4);
if(!mission.artifacts.empty()) questName = CQuest::missionName(5);
if(!mission.creatures.empty()) questName = CQuest::missionName(6);
if(mission.resources.nonZero()) questName = CQuest::missionName(7);
if(!mission.heroes.empty()) questName = CQuest::missionName(8);
if(!mission.players.empty()) questName = CQuest::missionName(9);
if(mission.daysPassed > 0 || !mission.heroClasses.empty()) questName = CQuest::missionName(11);
}
void CQuest::addKillTargetReplacements(MetaString &out) const
{
if(!heroName.empty())
out.replaceTextID(heroName);
if(stackToKill.type)
{
out.replaceCreatureName(stackToKill);
out.replaceRawString(VLC->generaltexth->arraytxt[147+stackDirection]);
}
}
void CQuest::serializeJson(JsonSerializeFormat & handler, const std::string & fieldName)
@@ -472,6 +340,7 @@ void CQuest::serializeJson(JsonSerializeFormat & handler, const std::string & fi
handler.serializeStruct("firstVisitText", firstVisitText);
handler.serializeStruct("nextVisitText", nextVisitText);
handler.serializeStruct("completedText", completedText);
handler.serializeBool("repeatedQuest", repeatedQuest, false);
if(!handler.saving)
{
@@ -480,86 +349,92 @@ void CQuest::serializeJson(JsonSerializeFormat & handler, const std::string & fi
isCustomComplete = !completedText.empty();
}
static const std::vector<std::string> MISSION_TYPE_JSON =
{
"None", "Level", "PrimaryStat", "KillHero", "KillCreature", "Artifact", "Army", "Resources", "Hero", "Player"
};
handler.serializeEnum("missionType", missionType, Emission::MISSION_NONE, MISSION_TYPE_JSON);
handler.serializeInt("timeLimit", lastDay, -1);
handler.serializeStruct("limiter", mission);
handler.serializeInstance("killTarget", killTarget, ObjectInstanceID::NONE);
switch (missionType)
if(!handler.saving) //compatibility with legacy vmaps
{
case MISSION_NONE:
break;
case MISSION_LEVEL:
handler.serializeInt("heroLevel", m13489val, -1);
break;
case MISSION_PRIMARY_STAT:
std::string missionType = "None";
handler.serializeString("missionType", missionType);
if(missionType == "None")
return;
if(missionType == "Level")
handler.serializeInt("heroLevel", mission.heroLevel);
if(missionType == "PrimaryStat")
{
auto primarySkills = handler.enterStruct("primarySkills");
if(!handler.saving)
m2stats.resize(GameConstants::PRIMARY_SKILLS);
for(int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i)
handler.serializeInt(NPrimarySkill::names[i], m2stats[i], 0);
handler.serializeInt(NPrimarySkill::names[i], mission.primary[i], 0);
}
break;
case MISSION_KILL_HERO:
case MISSION_KILL_CREATURE:
handler.serializeInstance<ui32>("killTarget", m13489val, static_cast<ui32>(-1));
break;
case MISSION_ART:
//todo: ban artifacts
handler.serializeIdArray<ArtifactID>("artifacts", m5arts);
break;
case MISSION_ARMY:
if(missionType == "Artifact")
handler.serializeIdArray<ArtifactID>("artifacts", mission.artifacts);
if(missionType == "Army")
{
auto a = handler.enterArray("creatures");
a.serializeStruct(m6creatures);
a.serializeStruct(mission.creatures);
}
break;
case MISSION_RESOURCES:
if(missionType == "Resources")
{
auto r = handler.enterStruct("resources");
for(size_t idx = 0; idx < (GameConstants::RESOURCE_QUANTITY - 1); idx++)
{
handler.serializeInt(GameConstants::RESOURCE_NAMES[idx], m7resources[idx], 0);
handler.serializeInt(GameConstants::RESOURCE_NAMES[idx], mission.resources[idx], 0);
}
}
break;
case MISSION_HERO:
handler.serializeId<ui32, ui32, HeroTypeID>("hero", m13489val, 0);
break;
case MISSION_PLAYER:
handler.serializeId<ui32, ui32, PlayerColor>("player", m13489val, PlayerColor::NEUTRAL);
break;
default:
logGlobal->error("Invalid quest mission type");
break;
if(missionType == "Hero")
{
ui32 temp;
handler.serializeId<ui32, ui32, HeroTypeID>("hero", temp, 0);
mission.heroes.emplace_back(temp);
}
if(missionType == "Player")
{
ui32 temp;
handler.serializeId<ui32, ui32, PlayerColor>("player", temp, PlayerColor::NEUTRAL);
mission.players.emplace_back(temp);
}
}
}
bool IQuestObject::checkQuest(const CGHeroInstance* h) const
{
return quest->checkQuest(h);
}
void IQuestObject::getVisitText(MetaString &text, std::vector<Component> &components, bool FirstVisit, const CGHeroInstance * h) const
{
quest->getVisitText(text, components, FirstVisit, h);
}
void IQuestObject::afterAddToMapCommon(CMap * map) const
{
map->addNewQuestInstance(quest);
}
void CGSeerHut::setObjToKill()
{
if(quest->missionType == CQuest::MISSION_KILL_CREATURE)
if(getCreatureToKill(true))
{
quest->stackToKill = getCreatureToKill(false)->getStack(SlotID(0)); //FIXME: stacks tend to disappear (desync?) on server :?
assert(quest->stackToKill.type);
quest->stackToKill.count = 0; //no count in info window
quest->stackDirection = checkDirection();
}
else if(quest->missionType == CQuest::MISSION_KILL_HERO)
else if(getHeroToKill(true))
{
quest->heroName = getHeroToKill(false)->getNameTranslated();
quest->heroPortrait = getHeroToKill(false)->getPortraitSource();
}
quest->getCompletionText(configuration.onSelect);
for(auto & i : configuration.info)
quest->getCompletionText(i.message);
}
void CGSeerHut::init(CRandomGenerator & rand)
@@ -582,28 +457,34 @@ void CGSeerHut::initObj(CRandomGenerator & rand)
CRewardableObject::initObj(rand);
quest->progress = CQuest::NOT_ACTIVE;
if(quest->missionType)
{
std::string questName = quest->missionName(quest->missionType);
setObjToKill();
quest->defineQuestName();
if(!quest->isCustomFirst)
quest->firstVisitText.appendTextID(TextIdentifier("core", "seerhut", "quest", questName, quest->missionState(0), quest->textOption).get());
if(!quest->isCustomNext)
quest->firstVisitText.appendTextID(TextIdentifier("core", "seerhut", "quest", questName, quest->missionState(1), quest->textOption).get());
if(!quest->isCustomComplete)
quest->firstVisitText.appendTextID(TextIdentifier("core", "seerhut", "quest", questName, quest->missionState(2), quest->textOption).get());
if(quest->mission == Rewardable::Limiter{} && quest->killTarget == ObjectInstanceID::NONE)
quest->isCompleted = true;
if(quest->questName == quest->missionName(0))
{
quest->firstVisitText.appendTextID(TextIdentifier("core", "seehut", "empty", quest->completedOption).get());
}
else
{
quest->progress = CQuest::COMPLETE;
quest->firstVisitText.appendTextID(TextIdentifier("core", "seehut", "empty", quest->completedOption).get());
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());
}
quest->getCompletionText(configuration.onSelect);
for(auto & i : configuration.info)
quest->getCompletionText(i.message);
}
void CGSeerHut::getRolloverText(MetaString &text, bool onHover) const
{
quest->getRolloverText (text, onHover);//TODO: simplify?
quest->getRolloverText(text, onHover);//TODO: simplify?
if(!onHover)
text.replaceRawString(seerName);
}
@@ -611,13 +492,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->progress != CQuest::NOT_ACTIVE)
if(ID == Obj::SEER_HUT && quest->activeForPlayers.count(player))
{
hoverName = VLC->generaltexth->allTexts[347];
boost::algorithm::replace_first(hoverName, "%s", seerName);
}
if(quest->progress & quest->missionType) //rollover when the quest is active
if(quest->activeForPlayers.count(player)
&& (quest->mission != Rewardable::Limiter{}
|| quest->killTarget != ObjectInstanceID::NONE)) //rollover when the quest is active
{
MetaString ms;
getRolloverText (ms, true);
@@ -626,48 +509,21 @@ std::string CGSeerHut::getHoverText(PlayerColor player) const
return hoverName;
}
void CQuest::addReplacements(MetaString &out, const std::string &base) const
{
switch(missionType)
{
case MISSION_KILL_CREATURE:
if(stackToKill.type)
{
out.replaceCreatureName(stackToKill);
if (std::count(base.begin(), base.end(), '%') == 2) //say where is placed monster
{
out.replaceRawString(VLC->generaltexth->arraytxt[147+stackDirection]);
}
}
break;
case MISSION_KILL_HERO:
out.replaceTextID(heroName);
break;
}
}
bool IQuestObject::checkQuest(const CGHeroInstance* h) const
{
return quest->checkQuest(h);
}
void IQuestObject::getVisitText (MetaString &text, std::vector<Component> &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h) const
{
quest->getVisitText (text,components, isCustom, FirstVisit, h);
}
void IQuestObject::afterAddToMapCommon(CMap * map) const
{
map->addNewQuestInstance(quest);
}
void CGSeerHut::setPropertyDer (ui8 what, ui32 val)
void CGSeerHut::setPropertyDer(ui8 what, ui32 val)
{
switch(what)
{
case 10:
quest->progress = static_cast<CQuest::Eprogress>(val);
case CGSeerHut::SEERHUT_VISITED:
{
quest->activeForPlayers.emplace(val);
break;
}
case CGSeerHut::SEERHUT_COMPLETE:
{
quest->isCompleted = val;
quest->activeForPlayers.clear();
break;
}
}
}
@@ -676,7 +532,7 @@ 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);
cb->setObjProperty (id, CGSeerHut::SEERHUT_COMPLETE, true);
}
}
@@ -684,30 +540,24 @@ void CGSeerHut::onHeroVisit(const CGHeroInstance * h) const
{
InfoWindow iw;
iw.player = h->getOwner();
if(quest->progress < CQuest::COMPLETE)
if(!quest->isCompleted)
{
bool firstVisit = !quest->progress;
bool firstVisit = !quest->activeForPlayers.count(h->getOwner());
bool failRequirements = !checkQuest(h);
bool isCustom = false;
if(firstVisit)
{
isCustom = quest->isCustomFirst;
cb->setObjProperty(id, CGSeerHut::OBJPROP_VISITED, CQuest::IN_PROGRESS);
cb->setObjProperty(id, CGSeerHut::SEERHUT_VISITED, h->getOwner());
AddQuest aq;
aq.quest = QuestInfo (quest, this, visitablePos());
aq.player = h->tempOwner;
cb->sendAndApply(&aq); //TODO: merge with setObjProperty?
}
else if(failRequirements)
{
isCustom = quest->isCustomNext;
}
if(firstVisit || failRequirements)
{
getVisitText (iw.text, iw.components, isCustom, firstVisit, h);
getVisitText (iw.text, iw.components, firstVisit, h);
cb->showInfoDialog(&iw);
}
@@ -758,26 +608,19 @@ int CGSeerHut::checkDirection() const
}
}
void CGSeerHut::completeQuest() const //reward
{
cb->setObjProperty(id, CGSeerHut::OBJPROP_VISITED, CQuest::COMPLETE); //mission complete
}
const CGHeroInstance * CGSeerHut::getHeroToKill(bool allowNull) const
{
const CGObjectInstance *o = cb->getObjByQuestIdentifier(quest->m13489val);
const CGObjectInstance *o = cb->getObjByQuestIdentifier(quest->killTarget);
if(allowNull && !o)
return nullptr;
assert(o && (o->ID == Obj::HERO || o->ID == Obj::PRISON));
return dynamic_cast<const CGHeroInstance *>(o);
}
const CGCreature * CGSeerHut::getCreatureToKill(bool allowNull) const
{
const CGObjectInstance *o = cb->getObjByQuestIdentifier(quest->m13489val);
const CGObjectInstance *o = cb->getObjByQuestIdentifier(quest->killTarget);
if(allowNull && !o)
return nullptr;
assert(o && o->ID == Obj::MONSTER);
return dynamic_cast<const CGCreature *>(o);
}
@@ -785,7 +628,10 @@ void CGSeerHut::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer)
{
CRewardableObject::blockingDialogAnswered(hero, answer);
if(answer)
completeQuest();
{
quest->completeQuest(cb, hero);
cb->setObjProperty(id, CGSeerHut::SEERHUT_COMPLETE, !quest->repeatedQuest); //mission complete
}
}
void CGSeerHut::afterAddToMap(CMap* map)
@@ -874,10 +720,23 @@ 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.info.back().reward.removeObject = subID == 0 ? true : false;
configuration.canRefuse = true;
}
void CGQuestGuard::onHeroVisit(const CGHeroInstance * h) const
{
if(!quest->isCompleted)
CGSeerHut::onHeroVisit(h);
else
cb->setObjProperty(id, CGSeerHut::SEERHUT_COMPLETE, false);
}
bool CGQuestGuard::passableFor(PlayerColor color) const
{
return quest->isCompleted;
}
void CGQuestGuard::serializeJsonOptions(JsonSerializeFormat & handler)
{
//quest only, do not call base class
@@ -938,12 +797,12 @@ void CGBorderGuard::initObj(CRandomGenerator & rand)
blockVisit = true;
}
void CGBorderGuard::getVisitText (MetaString &text, std::vector<Component> &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h) const
void CGBorderGuard::getVisitText(MetaString &text, std::vector<Component> &components, bool FirstVisit, const CGHeroInstance * h) const
{
text.appendLocalString(EMetaText::ADVOB_TXT,18);
text.appendLocalString(EMetaText::ADVOB_TXT, 18);
}
void CGBorderGuard::getRolloverText (MetaString &text, bool onHover) const
void CGBorderGuard::getRolloverText(MetaString &text, bool onHover) const
{
if (!onHover)
{

View File

@@ -19,47 +19,21 @@ class CGCreature;
class DLL_LINKAGE CQuest final
{
mutable std::unordered_map<ArtifactID, unsigned, ArtifactID::hash> artifactsRequirements; // artifact ID -> required count
public:
enum Emission {
MISSION_NONE = 0,
MISSION_LEVEL = 1,
MISSION_PRIMARY_STAT = 2,
MISSION_KILL_HERO = 3,
MISSION_KILL_CREATURE = 4,
MISSION_ART = 5,
MISSION_ARMY = 6,
MISSION_RESOURCES = 7,
MISSION_HERO = 8,
MISSION_PLAYER = 9,
MISSION_HOTA_MULTI = 10,
// end of H3 missions
MISSION_KEYMASTER = 100,
MISSION_HOTA_HERO_CLASS = 101,
MISSION_HOTA_REACH_DATE = 102
};
enum Eprogress {
NOT_ACTIVE,
IN_PROGRESS,
COMPLETE
};
static const std::string & missionName(int index);
static const std::string & missionState(int index);
static const std::string & missionName(Emission mission);
static const std::string & missionState(int index);
std::string questName;
si32 qid; //unique quest id for serialization / identification
Emission missionType;
Eprogress progress;
si32 lastDay; //after this day (first day is 0) mission cannot be completed; if -1 - no limit
ui32 m13489val;
std::vector<ui32> m2stats;
std::vector<ArtifactID> m5arts; // artifact IDs. Add IDs through addArtifactID(), not directly to the field.
std::vector<CStackBasicDescriptor> m6creatures; //pair[cre id, cre count], CreatureSet info irrelevant
TResources m7resources;
ObjectInstanceID killTarget;
Rewardable::Limiter mission;
bool repeatedQuest;
bool isCompleted;
std::set<PlayerColor> activeForPlayers;
// following fields are used only for kill creature/hero missions, the original
// objects became inaccessible after their removal, so we need to store info
@@ -79,13 +53,14 @@ public:
CQuest(); //TODO: Remove constructor
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<Component> &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h = nullptr) const;
virtual bool checkQuest(const CGHeroInstance * h) const; //determines whether the quest is complete or not
virtual void getVisitText(MetaString &text, std::vector<Component> & components, bool FirstVisit, 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;
void addArtifactID(const ArtifactID & id);
virtual void completeQuest(IGameCallback *, const CGHeroInstance * h) const;
virtual void addTextReplacements(MetaString &out, std::vector<Component> & components) const;
virtual void addKillTargetReplacements(MetaString &out) const;
void defineQuestName();
bool operator== (const CQuest & quest) const
{
@@ -95,14 +70,9 @@ public:
template <typename Handler> void serialize(Handler &h, const int version)
{
h & qid;
h & missionType;
h & progress;
h & isCompleted;
h & activeForPlayers;
h & lastDay;
h & m13489val;
h & m2stats;
h & m5arts;
h & m6creatures;
h & m7resources;
h & textOption;
h & stackToKill;
h & stackDirection;
@@ -115,6 +85,9 @@ public:
h & isCustomNext;
h & isCustomComplete;
h & completedOption;
h & questName;
h & mission;
h & killTarget;
}
void serializeJson(JsonSerializeFormat & handler, const std::string & fieldName);
@@ -128,7 +101,7 @@ public:
///Information about quest should remain accessible even if IQuestObject removed from map
///All CQuest objects are freed in CMap destructor
virtual ~IQuestObject() = default;
virtual void getVisitText (MetaString &text, std::vector<Component> &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h = nullptr) const;
virtual void getVisitText (MetaString &text, std::vector<Component> &components, bool FirstVisit, const CGHeroInstance * h = nullptr) const;
virtual bool checkQuest (const CGHeroInstance * h) const;
template <typename Handler> void serialize(Handler &h, const int version)
@@ -156,8 +129,6 @@ public:
const CGHeroInstance *getHeroToKill(bool allowNull = false) const;
const CGCreature *getCreatureToKill(bool allowNull = false) const;
void getRolloverText (MetaString &text, bool onHover) const;
void finishQuest (const CGHeroInstance * h, ui32 accept) const; //common for both objects
virtual void completeQuest() const;
void afterAddToMap(CMap * map) override;
@@ -168,7 +139,8 @@ public:
h & seerName;
}
protected:
static constexpr int OBJPROP_VISITED = 10;
static constexpr int SEERHUT_VISITED = 10;
static constexpr int SEERHUT_COMPLETE = 11;
void setPropertyDer(ui8 what, ui32 val) override;
@@ -180,6 +152,9 @@ class DLL_LINKAGE CGQuestGuard : public CGSeerHut
public:
void init(CRandomGenerator & rand) override;
void onHeroVisit(const CGHeroInstance * h) const override;
bool passableFor(PlayerColor color) const override;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & static_cast<CGSeerHut&>(*this);
@@ -228,7 +203,7 @@ public:
void onHeroVisit(const CGHeroInstance * h) const override;
void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
void getVisitText (MetaString &text, std::vector<Component> &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h = nullptr) const override;
void getVisitText (MetaString &text, std::vector<Component> &components, bool FirstVisit, const CGHeroInstance * h = nullptr) const override;
void getRolloverText (MetaString &text, bool onHover) const;
bool checkQuest (const CGHeroInstance * h) const override;

View File

@@ -144,6 +144,9 @@ ui8 CMapHeader::levels() const
void CMapHeader::registerMapStrings()
{
VLC->generaltexth->removeSubContainer(*this);
VLC->generaltexth->addSubContainer(*this);
//get supported languages. Assuming that translation containing most strings is the base language
std::set<std::string> mapLanguages, mapBaseLanguages;
int maxStrings = 0;

View File

@@ -105,7 +105,10 @@ std::string CMapInfo::getNameTranslated() const
if(campaign && !campaign->getNameTranslated().empty())
return campaign->getNameTranslated();
else if(mapHeader && !mapHeader->name.empty())
{
mapHeader->registerMapStrings();
return mapHeader->name.toString();
}
else
return VLC->generaltexth->allTexts[508];
}

View File

@@ -1830,6 +1830,7 @@ CGObjectInstance * CMapLoaderH3M::readSeerHut(const int3 & position, const Objec
if(features.levelHOTA3)
{
uint32_t repeateableQuestsCount = reader->readUInt32();
hut->quest->repeatedQuest = repeateableQuestsCount != 0;
if(repeateableQuestsCount != 0)
logGlobal->warn("Map '%s': Seer Hut at %s - %d repeatable quests are not implemented!", mapName, position.toString(), repeateableQuestsCount);
@@ -1858,11 +1859,30 @@ enum class ESeerHutRewardType : uint8_t
CREATURE = 10,
};
enum class EQuestMission {
NONE = 0,
LEVEL = 1,
PRIMARY_SKILL = 2,
KILL_HERO = 3,
KILL_CREATURE = 4,
ARTIFACT = 5,
ARMY = 6,
RESOURCES = 7,
HERO = 8,
PLAYER = 9,
HOTA_MULTI = 10,
// end of H3 missions
KEYMASTER = 100,
HOTA_HERO_CLASS = 101,
HOTA_REACH_DATE = 102
};
void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, const ObjectInstanceID & idToBeGiven)
{
EQuestMission missionType = EQuestMission::NONE;
if(features.levelAB)
{
readQuest(hut, position);
missionType = static_cast<EQuestMission>(readQuest(hut, position));
}
else
{
@@ -1871,12 +1891,8 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, con
if(artID != ArtifactID::NONE)
{
//not none quest
hut->quest->addArtifactID(artID);
hut->quest->missionType = CQuest::MISSION_ART;
}
else
{
hut->quest->missionType = CQuest::MISSION_NONE;
hut->quest->mission.artifacts.push_back(artID);
missionType = EQuestMission::ARTIFACT;
}
hut->quest->lastDay = -1; //no timeout
hut->quest->isCustomFirst = false;
@@ -1884,7 +1900,7 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, con
hut->quest->isCustomComplete = false;
}
if(hut->quest->missionType)
if(missionType != EQuestMission::NONE)
{
auto rewardType = static_cast<ESeerHutRewardType>(reader->readUInt8());
Rewardable::VisitInfo vinfo;
@@ -1976,91 +1992,91 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, con
}
}
void CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & position)
int CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & position)
{
guard->quest->missionType = static_cast<CQuest::Emission>(reader->readUInt8());
auto missionId = reader->readUInt8();
switch(guard->quest->missionType)
switch(static_cast<EQuestMission>(missionId))
{
case CQuest::MISSION_NONE:
return;
case CQuest::MISSION_PRIMARY_STAT:
case EQuestMission::NONE:
return missionId;
case EQuestMission::PRIMARY_SKILL:
{
guard->quest->m2stats.resize(4);
for(int x = 0; x < 4; ++x)
{
guard->quest->m2stats[x] = reader->readUInt8();
guard->quest->mission.primary[x] = reader->readUInt8();
}
}
break;
case CQuest::MISSION_LEVEL:
case CQuest::MISSION_KILL_HERO:
case CQuest::MISSION_KILL_CREATURE:
{
guard->quest->m13489val = reader->readUInt32();
break;
}
case CQuest::MISSION_ART:
case EQuestMission::LEVEL:
{
guard->quest->mission.heroLevel = reader->readUInt32();
break;
}
case EQuestMission::KILL_HERO:
case EQuestMission::KILL_CREATURE:
{
guard->quest->killTarget = ObjectInstanceID(reader->readUInt32());
break;
}
case EQuestMission::ARTIFACT:
{
int artNumber = reader->readUInt8();
for(int yy = 0; yy < artNumber; ++yy)
{
auto artid = reader->readArtifact();
guard->quest->addArtifactID(artid);
guard->quest->mission.artifacts.push_back(artid);
map->allowedArtifact[artid] = false; //these are unavailable for random generation
}
break;
}
case CQuest::MISSION_ARMY:
case EQuestMission::ARMY:
{
int typeNumber = reader->readUInt8();
guard->quest->m6creatures.resize(typeNumber);
guard->quest->mission.creatures.resize(typeNumber);
for(int hh = 0; hh < typeNumber; ++hh)
{
guard->quest->m6creatures[hh].type = VLC->creh->objects[reader->readCreature()];
guard->quest->m6creatures[hh].count = reader->readUInt16();
guard->quest->mission.creatures[hh].type = VLC->creh->objects[reader->readCreature()];
guard->quest->mission.creatures[hh].count = reader->readUInt16();
}
break;
}
case CQuest::MISSION_RESOURCES:
case EQuestMission::RESOURCES:
{
for(int x = 0; x < 7; ++x)
guard->quest->m7resources[x] = reader->readUInt32();
guard->quest->mission.resources[x] = reader->readUInt32();
break;
}
case CQuest::MISSION_HERO:
case EQuestMission::HERO:
{
guard->quest->m13489val = reader->readHero().getNum();
guard->quest->mission.heroes.push_back(reader->readHero());
break;
}
case CQuest::MISSION_PLAYER:
case EQuestMission::PLAYER:
{
guard->quest->m13489val = reader->readPlayer().getNum();
guard->quest->mission.players.push_back(reader->readPlayer());
break;
}
case CQuest::MISSION_HOTA_MULTI:
case EQuestMission::HOTA_MULTI:
{
uint32_t missionSubID = reader->readUInt32();
if(missionSubID == 0)
{
guard->quest->missionType = CQuest::MISSION_NONE; //TODO: CQuest::MISSION_HOTA_HERO_CLASS;
missionId = int(EQuestMission::HOTA_HERO_CLASS);
std::set<HeroClassID> heroClasses;
reader->readBitmaskHeroClassesSized(heroClasses, false);
logGlobal->warn("Map '%s': Quest at %s 'Belong to one of %d classes' is not implemented!", mapName, position.toString(), heroClasses.size());
for(auto & hc : heroClasses)
guard->quest->mission.heroClasses.push_back(hc);
break;
}
if(missionSubID == 1)
{
guard->quest->missionType = CQuest::MISSION_NONE; //TODO: CQuest::MISSION_HOTA_REACH_DATE;
uint32_t daysPassed = reader->readUInt32();
logGlobal->warn("Map '%s': Quest at %s 'Wait till %d days passed' is not implemented!", mapName, position.toString(), daysPassed);
missionId = int(EQuestMission::HOTA_REACH_DATE);
guard->quest->mission.daysPassed = reader->readUInt32() + 1;
break;
}
assert(0);
break;
}
default:
@@ -2076,6 +2092,7 @@ void CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & position)
guard->quest->isCustomFirst = !guard->quest->firstVisitText.empty();
guard->quest->isCustomNext = !guard->quest->nextVisitText.empty();
guard->quest->isCustomComplete = !guard->quest->completedText.empty();
return missionId;
}
CGObjectInstance * CMapLoaderH3M::readTown(const int3 & position, std::shared_ptr<const ObjectTemplate> objectTemplate)

View File

@@ -204,7 +204,7 @@ private:
*
* @param guard the quest guard where that quest should be applied to
*/
void readQuest(IQuestObject * guard, const int3 & position);
int readQuest(IQuestObject * guard, const int3 & position);
void readSeerHutQuest(CGSeerHut * hut, const int3 & position, const ObjectInstanceID & idToBeGiven);

View File

@@ -123,6 +123,10 @@ void Rewardable::Info::configureLimiter(Rewardable::Configuration & object, CRan
limiter.spells = JsonRandom::loadSpells(source["spells"], rng, spells);
limiter.creatures = JsonRandom::loadCreatures(source["creatures"], rng);
limiter.players = JsonRandom::loadColors(source["colors"], rng);
limiter.heroes = JsonRandom::loadHeroes(source["heroes"], rng);
limiter.heroClasses = JsonRandom::loadHeroClasses(source["heroClasses"], rng);
limiter.allOf = configureSublimiters(object, rng, source["allOf"] );
limiter.anyOf = configureSublimiters(object, rng, source["anyOf"] );
limiter.noneOf = configureSublimiters(object, rng, source["noneOf"] );

View File

@@ -16,7 +16,9 @@
#include "../mapObjects/CGHeroInstance.h"
#include "../serializer/JsonSerializeFormat.h"
#include "../constants/StringConstants.h"
#include "../CHeroHandler.h"
#include "../CSkillHandler.h"
#include "../ArtifactUtils.h"
VCMI_LIB_NAMESPACE_BEGIN
@@ -24,7 +26,7 @@ Rewardable::Limiter::Limiter()
: dayOfWeek(0)
, daysPassed(0)
, heroExperience(0)
, heroLevel(0)
, heroLevel(-1)
, manaPercentage(0)
, manaPoints(0)
, primary(GameConstants::PRIMARY_SKILLS, 0)
@@ -33,6 +35,33 @@ Rewardable::Limiter::Limiter()
Rewardable::Limiter::~Limiter() = default;
bool operator==(const Rewardable::Limiter & l, const Rewardable::Limiter & r)
{
return l.dayOfWeek == r.dayOfWeek
&& l.daysPassed == r.daysPassed
&& l.heroLevel == r.heroLevel
&& l.heroExperience == r.heroExperience
&& l.manaPoints == r.manaPoints
&& l.manaPercentage == r.manaPercentage
&& l.secondary == r.secondary
&& l.creatures == r.creatures
&& l.spells == r.spells
&& l.artifacts == r.artifacts
&& l.players == r.players
&& l.heroes == r.heroes
&& l.heroClasses == r.heroClasses
&& l.resources == r.resources
&& l.primary == r.primary
&& l.noneOf == r.noneOf
&& l.allOf == r.allOf
&& l.anyOf == r.anyOf;
}
bool operator!=(const Rewardable::Limiter & l, const Rewardable::Limiter & r)
{
return !(l == r);
}
bool Rewardable::Limiter::heroAllowed(const CGHeroInstance * hero) const
{
if(dayOfWeek != 0)
@@ -93,12 +122,34 @@ bool Rewardable::Limiter::heroAllowed(const CGHeroInstance * hero) const
return false;
}
for(const auto & art : artifacts)
{
if (!hero->hasArt(art))
std::unordered_map<ArtifactID, unsigned int, ArtifactID::hash> artifactsRequirements; // artifact ID -> required count
for(const auto & art : artifacts)
++artifactsRequirements[art];
size_t reqSlots = 0;
for(const auto & elem : artifactsRequirements)
{
// check required amount of artifacts
if(hero->getArtPosCount(elem.first, false, true, true) < elem.second)
return false;
if(!hero->hasArt(elem.first))
reqSlots += hero->getAssemblyByConstituent(elem.first)->getPartsInfo().size() - 2;
}
if(!ArtifactUtils::isBackpackFreeSlots(hero, reqSlots))
return false;
}
if(!players.empty() && !vstd::contains(players, hero->getOwner()))
return false;
if(!heroes.empty() && !vstd::contains(heroes, hero->type->getId()))
return false;
if(!heroClasses.empty() && !vstd::contains(heroClasses, hero->type->heroClass->getId()))
return false;
for(const auto & sublimiter : noneOf)
{
if (sublimiter->heroAllowed(hero))
@@ -122,6 +173,56 @@ bool Rewardable::Limiter::heroAllowed(const CGHeroInstance * hero) const
return false;
}
void Rewardable::Limiter::loadComponents(std::vector<Component> & comps,
const CGHeroInstance * h) const
{
if (heroExperience)
comps.emplace_back(Component::EComponentType::EXPERIENCE, 0, static_cast<si32>(h->calculateXp(heroExperience)), 0);
if (heroLevel > 0)
comps.emplace_back(Component::EComponentType::EXPERIENCE, 1, heroLevel, 0);
if (manaPoints || manaPercentage > 0)
{
int absoluteMana = h->manaLimit() ? (manaPercentage * h->mana / h->manaLimit() / 100) : 0;
comps.emplace_back(Component::EComponentType::PRIM_SKILL, 5, absoluteMana + manaPoints, 0);
}
for (size_t i=0; i<primary.size(); i++)
{
if (primary[i] != 0)
comps.emplace_back(Component::EComponentType::PRIM_SKILL, static_cast<ui16>(i), primary[i], 0);
}
for(const auto & entry : secondary)
comps.emplace_back(Component::EComponentType::SEC_SKILL, entry.first, entry.second, 0);
for(const auto & entry : artifacts)
comps.emplace_back(Component::EComponentType::ARTIFACT, entry, 1, 0);
for(const auto & entry : spells)
comps.emplace_back(Component::EComponentType::SPELL, entry, 1, 0);
for(const auto & entry : creatures)
comps.emplace_back(Component::EComponentType::CREATURE, entry.type->getId(), entry.count, 0);
for(const auto & entry : players)
comps.emplace_back(Component::EComponentType::FLAG, entry, 0, 0);
//FIXME: portrait may not match hero, if custom portrait was set in map editor
for(const auto & entry : heroes)
comps.emplace_back(Component::EComponentType::HERO_PORTRAIT, VLC->heroTypes()->getById(entry)->getIconIndex(), 0, 0);
for(const auto & entry : heroClasses)
comps.emplace_back(Component::EComponentType::HERO_PORTRAIT, VLC->heroClasses()->getById(entry)->getIconIndex(), 0, 0);
for (size_t i=0; i<resources.size(); i++)
{
if (resources[i] !=0)
comps.emplace_back(Component::EComponentType::RESOURCE, static_cast<ui16>(i), resources[i], 0);
}
}
void Rewardable::Limiter::serializeJson(JsonSerializeFormat & handler)
{
handler.serializeInt("dayOfWeek", dayOfWeek);
@@ -130,6 +231,9 @@ void Rewardable::Limiter::serializeJson(JsonSerializeFormat & handler)
handler.serializeInt("manaPercentage", manaPercentage);
handler.serializeInt("heroExperience", heroExperience);
handler.serializeInt("heroLevel", heroLevel);
handler.serializeIdArray("heroes", heroes);
handler.serializeIdArray("heroClasses", heroClasses);
handler.serializeIdArray("colors", players);
handler.serializeInt("manaPoints", manaPoints);
handler.serializeIdArray("artifacts", artifacts);
handler.enterArray("creatures").serializeStruct(creatures);

View File

@@ -17,6 +17,7 @@ VCMI_LIB_NAMESPACE_BEGIN
class CGHeroInstance;
class CStackBasicDescriptor;
struct Component;
namespace Rewardable {
@@ -25,8 +26,7 @@ using LimitersList = std::vector<std::shared_ptr<Rewardable::Limiter>>;
/// Limiters of rewards. Rewards will be granted to hero only if he satisfies requirements
/// Note: for this is only a test - it won't remove anything from hero (e.g. artifacts or creatures)
/// NOTE: in future should (partially) replace seer hut/quest guard quests checks
struct DLL_LINKAGE Limiter
struct DLL_LINKAGE Limiter final
{
/// day of week, unused if 0, 1-7 will test for current day of week
si32 dayOfWeek;
@@ -52,7 +52,7 @@ struct DLL_LINKAGE Limiter
std::map<SecondarySkill, si32> secondary;
/// artifacts that hero needs to have (equipped or in backpack) to trigger this
/// Note: does not checks for multiple copies of the same arts
/// checks for artifacts copies if same artifact id is included multiple times
std::vector<ArtifactID> artifacts;
/// Spells that hero must have in the spellbook
@@ -61,6 +61,13 @@ struct DLL_LINKAGE Limiter
/// creatures that hero needs to have
std::vector<CStackBasicDescriptor> creatures;
/// only heroes/hero classes from list could pass limiter
std::vector<HeroTypeID> heroes;
std::vector<HeroClassID> heroClasses;
/// only player colors can pass limiter
std::vector<PlayerColor> players;
/// sub-limiters, all must pass for this limiter to pass
LimitersList allOf;
@@ -75,6 +82,10 @@ struct DLL_LINKAGE Limiter
bool heroAllowed(const CGHeroInstance * hero) const;
/// Generates list of components that describes reward for a specific hero
void loadComponents(std::vector<Component> & comps,
const CGHeroInstance * h) const;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & dayOfWeek;
@@ -88,6 +99,9 @@ struct DLL_LINKAGE Limiter
h & secondary;
h & artifacts;
h & creatures;
h & heroes;
h & heroClasses;
h & players;
h & allOf;
h & anyOf;
h & noneOf;
@@ -98,4 +112,7 @@ struct DLL_LINKAGE Limiter
}
bool DLL_LINKAGE operator== (const Rewardable::Limiter & l, const Rewardable::Limiter & r);
bool DLL_LINKAGE operator!= (const Rewardable::Limiter & l, const Rewardable::Limiter & r);
VCMI_LIB_NAMESPACE_END

View File

@@ -29,7 +29,7 @@ using RewardsList = std::vector<std::shared_ptr<Rewardable::Reward>>;
/// Reward that can be granted to a hero
/// NOTE: eventually should replace seer hut rewards and events/pandoras
struct DLL_LINKAGE Reward
struct DLL_LINKAGE Reward final
{
/// resources that will be given to player
TResources resources;
@@ -78,7 +78,7 @@ struct DLL_LINKAGE Reward
bool removeObject;
/// Generates list of components that describes reward for a specific hero
virtual void loadComponents(std::vector<Component> & comps,
void loadComponents(std::vector<Component> & comps,
const CGHeroInstance * h) const;
Component getDisplayedComponent(const CGHeroInstance * h) const;

View File

@@ -461,12 +461,8 @@ void TreasurePlacer::addAllPossibleObjects()
reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
obj->configuration.info.push_back(reward);
obj->quest->missionType = CQuest::MISSION_ART;
ArtifactID artid = qap->drawRandomArtifact();
obj->quest->addArtifactID(artid);
obj->quest->lastDay = -1;
obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false;
obj->quest->mission.artifacts.push_back(artid);
generator.banQuestArt(artid);
zone.getModificator<QuestArtifactPlacer>()->addQuestArtifact(artid);
@@ -513,11 +509,8 @@ void TreasurePlacer::addAllPossibleObjects()
reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
obj->configuration.info.push_back(reward);
obj->quest->missionType = CQuest::MISSION_ART;
ArtifactID artid = qap->drawRandomArtifact();
obj->quest->addArtifactID(artid);
obj->quest->lastDay = -1;
obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false;
obj->quest->mission.artifacts.push_back(artid);
generator.banQuestArt(artid);
zone.getModificator<QuestArtifactPlacer>()->addQuestArtifact(artid);
@@ -538,11 +531,8 @@ void TreasurePlacer::addAllPossibleObjects()
reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
obj->configuration.info.push_back(reward);
obj->quest->missionType = CQuest::MISSION_ART;
ArtifactID artid = qap->drawRandomArtifact();
obj->quest->addArtifactID(artid);
obj->quest->lastDay = -1;
obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false;
obj->quest->mission.artifacts.push_back(artid);
generator.banQuestArt(artid);
zone.getModificator<QuestArtifactPlacer>()->addQuestArtifact(artid);

View File

@@ -29,20 +29,6 @@
#include "PickObjectDelegate.h"
#include "../mapcontroller.h"
static QList<std::pair<QString, QVariant>> MissionIdentifiers
{
{QObject::tr("None"), QVariant::fromValue(int(CQuest::Emission::MISSION_NONE))},
{QObject::tr("Reach level"), QVariant::fromValue(int(CQuest::Emission::MISSION_LEVEL))},
{QObject::tr("Stats"), QVariant::fromValue(int(CQuest::Emission::MISSION_PRIMARY_STAT))},
{QObject::tr("Kill hero"), QVariant::fromValue(int(CQuest::Emission::MISSION_KILL_HERO))},
{QObject::tr("Kill monster"), QVariant::fromValue(int(CQuest::Emission::MISSION_KILL_CREATURE))},
{QObject::tr("Artifact"), QVariant::fromValue(int(CQuest::Emission::MISSION_ART))},
{QObject::tr("Army"), QVariant::fromValue(int(CQuest::Emission::MISSION_ARMY))},
{QObject::tr("Resources"), QVariant::fromValue(int(CQuest::Emission::MISSION_RESOURCES))},
{QObject::tr("Hero"), QVariant::fromValue(int(CQuest::Emission::MISSION_HERO))},
{QObject::tr("Player"), QVariant::fromValue(int(CQuest::Emission::MISSION_PLAYER))},
};
static QList<std::pair<QString, QVariant>> CharacterIdentifiers
{
{QObject::tr("Compliant"), QVariant::fromValue(int(CGCreature::Character::COMPLIANT))},
@@ -410,24 +396,28 @@ void Inspector::updateProperties(CGEvent * o)
void Inspector::updateProperties(CGSeerHut * o)
{
if(!o) return;
{ //Mission type
auto * delegate = new InspectorDelegate;
delegate->options = MissionIdentifiers;
addProperty<CQuest::Emission>("Mission type", o->quest->missionType, delegate, false);
}
if(!o || !o->quest) return;
addProperty("First visit text", o->quest->firstVisitText, new MessageDelegate, false);
addProperty("Next visit text", o->quest->nextVisitText, new MessageDelegate, false);
addProperty("Completed text", o->quest->completedText, new MessageDelegate, false);
addProperty("Repeat quest", o->quest->repeatedQuest, false);
addProperty("Time limit", o->quest->lastDay, false);
{ //Quest
auto * delegate = new QuestDelegate(*controller.map(), *o);
auto * delegate = new QuestDelegate(controller, *o->quest);
addProperty("Quest", PropertyEditorPlaceholder(), delegate, false);
}
}
void Inspector::updateProperties(CGQuestGuard * o)
{
if(!o || !o->quest) return;
addProperty("Reward", PropertyEditorPlaceholder(), nullptr, true);
addProperty("Repeat quest", o->quest->repeatedQuest, true);
}
void Inspector::updateProperties()
{
if(!obj)
@@ -470,6 +460,7 @@ void Inspector::updateProperties()
UPDATE_OBJ_PROPERTIES(CGPandoraBox);
UPDATE_OBJ_PROPERTIES(CGEvent);
UPDATE_OBJ_PROPERTIES(CGSeerHut);
UPDATE_OBJ_PROPERTIES(CGQuestGuard);
table->show();
}
@@ -516,6 +507,7 @@ void Inspector::setProperty(const QString & key, const QVariant & value)
SET_PROPERTIES(CGPandoraBox);
SET_PROPERTIES(CGEvent);
SET_PROPERTIES(CGSeerHut);
SET_PROPERTIES(CGQuestGuard);
}
void Inspector::setProperty(CArmedInstance * o, const QString & key, const QVariant & value)
@@ -538,7 +530,8 @@ void Inspector::setProperty(CGPandoraBox * o, const QString & key, const QVarian
if(!o) return;
if(key == "Message")
o->message = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(), TextIdentifier("guards", o->instanceName, "message"), value.toString().toStdString()));
o->message = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(),
TextIdentifier("guards", o->instanceName, "message"), value.toString().toStdString()));
}
void Inspector::setProperty(CGEvent * o, const QString & key, const QVariant & value)
@@ -560,7 +553,8 @@ void Inspector::setProperty(CGTownInstance * o, const QString & key, const QVari
if(!o) return;
if(key == "Town name")
o->setNameTextId(mapRegisterLocalizedString("map", *controller.map(), TextIdentifier("town", o->instanceName, "name"), value.toString().toStdString()));
o->setNameTextId(mapRegisterLocalizedString("map", *controller.map(),
TextIdentifier("town", o->instanceName, "name"), value.toString().toStdString()));
}
void Inspector::setProperty(CGSignBottle * o, const QString & key, const QVariant & value)
@@ -568,7 +562,8 @@ void Inspector::setProperty(CGSignBottle * o, const QString & key, const QVarian
if(!o) return;
if(key == "Message")
o->message = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(), TextIdentifier("sign", o->instanceName, "message"), value.toString().toStdString()));
o->message = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(),
TextIdentifier("sign", o->instanceName, "message"), value.toString().toStdString()));
}
void Inspector::setProperty(CGMine * o, const QString & key, const QVariant & value)
@@ -584,7 +579,8 @@ void Inspector::setProperty(CGArtifact * o, const QString & key, const QVariant
if(!o) return;
if(key == "Message")
o->message = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(), TextIdentifier("guards", o->instanceName, "message"), value.toString().toStdString()));
o->message = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(),
TextIdentifier("guards", o->instanceName, "message"), value.toString().toStdString()));
if(o->storedArtifact && key == "Spell")
{
@@ -623,10 +619,12 @@ void Inspector::setProperty(CGHeroInstance * o, const QString & key, const QVari
o->gender = EHeroGender(value.toInt());
if(key == "Name")
o->nameCustomTextId = mapRegisterLocalizedString("map", *controller.map(), TextIdentifier("hero", o->instanceName, "name"), value.toString().toStdString());
o->nameCustomTextId = mapRegisterLocalizedString("map", *controller.map(),
TextIdentifier("hero", o->instanceName, "name"), value.toString().toStdString());
if(key == "Biography")
o->biographyCustomTextId = mapRegisterLocalizedString("map", *controller.map(), TextIdentifier("hero", o->instanceName, "biography"), value.toString().toStdString());
o->biographyCustomTextId = mapRegisterLocalizedString("map", *controller.map(),
TextIdentifier("hero", o->instanceName, "biography"), value.toString().toStdString());
if(key == "Experience")
o->exp = value.toString().toInt();
@@ -662,7 +660,8 @@ void Inspector::setProperty(CGCreature * o, const QString & key, const QVariant
if(!o) return;
if(key == "Message")
o->message = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(), TextIdentifier("monster", o->instanceName, "message"), value.toString().toStdString()));
o->message = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(),
TextIdentifier("monster", o->instanceName, "message"), value.toString().toStdString()));
if(key == "Character")
o->character = CGCreature::Character(value.toInt());
if(key == "Never flees")
@@ -677,14 +676,24 @@ void Inspector::setProperty(CGSeerHut * o, const QString & key, const QVariant &
{
if(!o) return;
if(key == "Mission type")
o->quest->missionType = CQuest::Emission(value.toInt());
if(key == "First visit text")
o->quest->firstVisitText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(), TextIdentifier("quest", o->instanceName, "firstVisit"), value.toString().toStdString()));
o->quest->firstVisitText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(),
TextIdentifier("quest", o->instanceName, "firstVisit"), value.toString().toStdString()));
if(key == "Next visit text")
o->quest->nextVisitText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(), TextIdentifier("quest", o->instanceName, "nextVisit"), value.toString().toStdString()));
o->quest->nextVisitText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(),
TextIdentifier("quest", o->instanceName, "nextVisit"), value.toString().toStdString()));
if(key == "Completed text")
o->quest->completedText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(), TextIdentifier("quest", o->instanceName, "completed"), value.toString().toStdString()));
o->quest->completedText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller.map(),
TextIdentifier("quest", o->instanceName, "completed"), value.toString().toStdString()));
if(key == "Repeat quest")
o->quest->repeatedQuest = value.toBool();
if(key == "Time limit")
o->quest->lastDay = value.toString().toInt();
}
void Inspector::setProperty(CGQuestGuard * o, const QString & key, const QVariant & value)
{
if(!o) return;
}
@@ -797,24 +806,6 @@ QTableWidgetItem * Inspector::addProperty(CGCreature::Character value)
return item;
}
QTableWidgetItem * Inspector::addProperty(CQuest::Emission value)
{
auto * item = new QTableWidgetItem;
item->setFlags(Qt::NoItemFlags);
item->setData(Qt::UserRole, QVariant::fromValue(int(value)));
for(auto & i : MissionIdentifiers)
{
if(i.second.toInt() == value)
{
item->setText(i.first);
break;
}
}
return item;
}
//========================================================================
Inspector::Inspector(MapController & c, CGObjectInstance * o, QTableWidget * t): obj(o), table(t), controller(c)

View File

@@ -82,6 +82,7 @@ protected:
DECLARE_OBJ_PROPERTY_METHODS(CGPandoraBox);
DECLARE_OBJ_PROPERTY_METHODS(CGEvent);
DECLARE_OBJ_PROPERTY_METHODS(CGSeerHut);
DECLARE_OBJ_PROPERTY_METHODS(CGQuestGuard);
//===============DECLARE PROPERTY VALUE TYPE==============================
QTableWidgetItem * addProperty(unsigned int value);
@@ -96,7 +97,6 @@ protected:
QTableWidgetItem * addProperty(bool value);
QTableWidgetItem * addProperty(CGObjectInstance * value);
QTableWidgetItem * addProperty(CGCreature::Character value);
QTableWidgetItem * addProperty(CQuest::Emission value);
QTableWidgetItem * addProperty(PropertyEditorPlaceholder value);
//===============END OF DECLARATION=======================================

View File

@@ -10,21 +10,124 @@
#include "StdInc.h"
#include "questwidget.h"
#include "ui_questwidget.h"
#include "../mapcontroller.h"
#include "../lib/VCMI_Lib.h"
#include "../lib/CSkillHandler.h"
#include "../lib/spells/CSpellHandler.h"
#include "../lib/CArtHandler.h"
#include "../lib/CCreatureHandler.h"
#include "../lib/CHeroHandler.h"
#include "../lib/constants/StringConstants.h"
#include "../lib/mapping/CMap.h"
#include "../lib/mapObjects/CGHeroInstance.h"
#include "../lib/mapObjects/CGCreature.h"
QuestWidget::QuestWidget(const CMap & _map, CGSeerHut & _sh, QWidget *parent) :
QuestWidget::QuestWidget(MapController & _controller, CQuest & _sh, QWidget *parent) :
QDialog(parent),
map(_map),
seerhut(_sh),
controller(_controller),
quest(_sh),
ui(new Ui::QuestWidget)
{
setAttribute(Qt::WA_DeleteOnClose, true);
ui->setupUi(this);
ui->lDayOfWeek->addItem(tr("None"));
for(int i = 1; i <= 7; ++i)
ui->lDayOfWeek->addItem(tr("Day %1").arg(i));
//fill resources
ui->lResources->setRowCount(GameConstants::RESOURCE_QUANTITY - 1);
for(int i = 0; i < GameConstants::RESOURCE_QUANTITY - 1; ++i)
{
auto * item = new QTableWidgetItem(QString::fromStdString(GameConstants::RESOURCE_NAMES[i]));
item->setData(Qt::UserRole, QVariant::fromValue(i));
ui->lResources->setItem(i, 0, item);
auto * spinBox = new QSpinBox;
spinBox->setMaximum(i == GameResID::GOLD ? 999999 : 999);
ui->lResources->setCellWidget(i, 1, spinBox);
}
//fill artifacts
for(int i = 0; i < controller.map()->allowedArtifact.size(); ++i)
{
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(!controller.map()->allowedArtifact[i])
item->setFlags(item->flags() & ~Qt::ItemIsEnabled);
ui->lArtifacts->addItem(item);
}
//fill spells
for(int i = 0; i < controller.map()->allowedSpells.size(); ++i)
{
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(!controller.map()->allowedSpells[i])
item->setFlags(item->flags() & ~Qt::ItemIsEnabled);
ui->lSpells->addItem(item);
}
//fill skills
ui->lSkills->setRowCount(controller.map()->allowedAbilities.size());
for(int i = 0; i < controller.map()->allowedAbilities.size(); ++i)
{
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(!controller.map()->allowedAbilities[i])
{
item->setFlags(item->flags() & ~Qt::ItemIsEnabled);
widget->setEnabled(false);
}
ui->lSkills->setItem(i, 0, item);
ui->lSkills->setCellWidget(i, 1, widget);
}
//fill creatures
for(auto & creature : VLC->creh->objects)
{
ui->lCreatureId->addItem(QString::fromStdString(creature->getNameSingularTranslated()));
ui->lCreatureId->setItemData(ui->lCreatureId->count() - 1, creature->getIndex());
}
//fill heroes
VLC->heroTypes()->forEach([this](const HeroType * hero, bool &)
{
auto * item = new QListWidgetItem(QString::fromStdString(hero->getNameTranslated()));
item->setData(Qt::UserRole, QVariant::fromValue(hero->getId().getNum()));
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
item->setCheckState(Qt::Unchecked);
ui->lHeroes->addItem(item);
});
//fill hero classes
VLC->heroClasses()->forEach([this](const HeroClass * heroClass, bool &)
{
auto * item = new QListWidgetItem(QString::fromStdString(heroClass->getNameTranslated()));
item->setData(Qt::UserRole, QVariant::fromValue(heroClass->getId().getNum()));
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
item->setCheckState(Qt::Unchecked);
ui->lHeroClasses->addItem(item);
});
//fill players
for(auto color = PlayerColor(0); color < PlayerColor::PLAYER_LIMIT; ++color)
{
auto * item = new QListWidgetItem(QString::fromStdString(GameConstants::PLAYER_COLOR_NAMES[color.getNum()]));
item->setData(Qt::UserRole, QVariant::fromValue(color.getNum()));
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
item->setCheckState(Qt::Unchecked);
ui->lPlayers->addItem(item);
}
}
QuestWidget::~QuestWidget()
@@ -34,137 +137,259 @@ QuestWidget::~QuestWidget()
void QuestWidget::obtainData()
{
assert(seerhut.quest);
bool activeId = false;
bool activeAmount = false;
switch(seerhut.quest->missionType) {
case CQuest::Emission::MISSION_LEVEL:
activeAmount = true;
ui->targetId->addItem("Reach level");
ui->targetAmount->setText(QString::number(seerhut.quest->m13489val));
break;
case CQuest::Emission::MISSION_PRIMARY_STAT:
activeId = true;
activeAmount = true;
for(auto s : NPrimarySkill::names)
ui->targetId->addItem(QString::fromStdString(s));
for(int i = 0; i < seerhut.quest->m2stats.size(); ++i)
{
if(seerhut.quest->m2stats[i] > 0)
{
ui->targetId->setCurrentIndex(i);
ui->targetAmount->setText(QString::number(seerhut.quest->m2stats[i]));
break; //TODO: support multiple stats
}
}
break;
case CQuest::Emission::MISSION_KILL_HERO:
activeId = true;
//TODO: implement
break;
case CQuest::Emission::MISSION_KILL_CREATURE:
activeId = true;
//TODO: implement
break;
case CQuest::Emission::MISSION_ART:
activeId = true;
for(int i = 0; i < map.allowedArtifact.size(); ++i)
ui->targetId->addItem(QString::fromStdString(VLC->arth->objects.at(i)->getNameTranslated()));
if(!seerhut.quest->m5arts.empty())
ui->targetId->setCurrentIndex(seerhut.quest->m5arts.front());
//TODO: support multiple artifacts
break;
case CQuest::Emission::MISSION_ARMY:
activeId = true;
activeAmount = true;
break;
case CQuest::Emission::MISSION_RESOURCES:
activeId = true;
activeAmount = true;
for(auto s : GameConstants::RESOURCE_NAMES)
ui->targetId->addItem(QString::fromStdString(s));
for(int i = 0; i < seerhut.quest->m7resources.size(); ++i)
{
if(seerhut.quest->m7resources[i] > 0)
{
ui->targetId->setCurrentIndex(i);
ui->targetAmount->setText(QString::number(seerhut.quest->m7resources[i]));
break; //TODO: support multiple resources
}
}
break;
case CQuest::Emission::MISSION_HERO:
activeId = true;
for(int i = 0; i < map.allowedHeroes.size(); ++i)
ui->targetId->addItem(QString::fromStdString(VLC->heroh->objects.at(i)->getNameTranslated()));
ui->targetId->setCurrentIndex(seerhut.quest->m13489val);
break;
case CQuest::Emission::MISSION_PLAYER:
activeId = true;
for(auto s : GameConstants::PLAYER_COLOR_NAMES)
ui->targetId->addItem(QString::fromStdString(s));
ui->targetId->setCurrentIndex(seerhut.quest->m13489val);
break;
case CQuest::Emission::MISSION_KEYMASTER:
break;
default:
break;
ui->lDayOfWeek->setCurrentIndex(quest.mission.dayOfWeek);
ui->lDaysPassed->setValue(quest.mission.daysPassed);
ui->lHeroLevel->setValue(quest.mission.heroLevel);
ui->lHeroExperience->setValue(quest.mission.heroExperience);
ui->lManaPoints->setValue(quest.mission.manaPoints);
ui->lManaPercentage->setValue(quest.mission.manaPercentage);
ui->lAttack->setValue(quest.mission.primary[0]);
ui->lDefence->setValue(quest.mission.primary[1]);
ui->lPower->setValue(quest.mission.primary[2]);
ui->lKnowledge->setValue(quest.mission.primary[3]);
for(int i = 0; i < ui->lResources->rowCount(); ++i)
{
if(auto * widget = qobject_cast<QSpinBox*>(ui->lResources->cellWidget(i, 1)))
widget->setValue(quest.mission.resources[i]);
}
ui->targetId->setEnabled(activeId);
ui->targetAmount->setEnabled(activeAmount);
for(auto i : quest.mission.artifacts)
ui->lArtifacts->item(VLC->artifacts()->getById(i)->getIndex())->setCheckState(Qt::Checked);
for(auto i : quest.mission.spells)
ui->lArtifacts->item(VLC->spells()->getById(i)->getIndex())->setCheckState(Qt::Checked);
for(auto & i : quest.mission.secondary)
{
int index = VLC->skills()->getById(i.first)->getIndex();
if(auto * widget = qobject_cast<QComboBox*>(ui->lSkills->cellWidget(index, 1)))
widget->setCurrentIndex(i.second);
}
for(auto & i : quest.mission.creatures)
{
int index = i.type->getIndex();
ui->lCreatureId->setCurrentIndex(index);
ui->lCreatureAmount->setValue(i.count);
onCreatureAdd(ui->lCreatures, ui->lCreatureId, ui->lCreatureAmount);
}
for(auto & i : quest.mission.heroes)
{
for(int e = 0; e < ui->lHeroes->count(); ++e)
{
if(ui->lHeroes->item(e)->data(Qt::UserRole).toInt() == i.getNum())
{
ui->lHeroes->item(e)->setCheckState(Qt::Checked);
break;
}
}
}
for(auto & i : quest.mission.heroClasses)
{
for(int e = 0; e < ui->lHeroClasses->count(); ++e)
{
if(ui->lHeroClasses->item(e)->data(Qt::UserRole).toInt() == i.getNum())
{
ui->lHeroClasses->item(e)->setCheckState(Qt::Checked);
break;
}
}
}
for(auto & i : quest.mission.players)
{
for(int e = 0; e < ui->lPlayers->count(); ++e)
{
if(ui->lPlayers->item(e)->data(Qt::UserRole).toInt() == i.getNum())
{
ui->lPlayers->item(e)->setCheckState(Qt::Checked);
break;
}
}
}
if(quest.killTarget != ObjectInstanceID::NONE && quest.killTarget < controller.map()->objects.size())
ui->lKillTarget->setText(QString::fromStdString(controller.map()->objects[quest.killTarget]->instanceName));
else
quest.killTarget = ObjectInstanceID::NONE;
}
QString QuestWidget::commitChanges()
bool QuestWidget::commitChanges()
{
assert(seerhut.quest);
switch(seerhut.quest->missionType) {
case CQuest::Emission::MISSION_LEVEL:
seerhut.quest->m13489val = ui->targetAmount->text().toInt();
return QString("Reach lvl ").append(ui->targetAmount->text());
case CQuest::Emission::MISSION_PRIMARY_STAT:
seerhut.quest->m2stats.resize(sizeof(NPrimarySkill::names), 0);
seerhut.quest->m2stats[ui->targetId->currentIndex()] = ui->targetAmount->text().toInt();
//TODO: support multiple stats
return ui->targetId->currentText().append(ui->targetAmount->text());
case CQuest::Emission::MISSION_KILL_HERO:
//TODO: implement
return QString("N/A");
case CQuest::Emission::MISSION_KILL_CREATURE:
//TODO: implement
return QString("N/A");
case CQuest::Emission::MISSION_ART:
seerhut.quest->m5arts.clear();
seerhut.quest->m5arts.push_back(ArtifactID(ui->targetId->currentIndex()));
//TODO: support multiple artifacts
return ui->targetId->currentText();
case CQuest::Emission::MISSION_ARMY:
//TODO: implement
return QString("N/A");
case CQuest::Emission::MISSION_RESOURCES:
seerhut.quest->m7resources[ui->targetId->currentIndex()] = ui->targetAmount->text().toInt();
//TODO: support resources
return ui->targetId->currentText().append(ui->targetAmount->text());
case CQuest::Emission::MISSION_HERO:
seerhut.quest->m13489val = ui->targetId->currentIndex();
return ui->targetId->currentText();
case CQuest::Emission::MISSION_PLAYER:
seerhut.quest->m13489val = ui->targetId->currentIndex();
return ui->targetId->currentText();
case CQuest::Emission::MISSION_KEYMASTER:
return QString("N/A");
default:
return QString("N/A");
quest.mission.dayOfWeek = ui->lDayOfWeek->currentIndex();
quest.mission.daysPassed = ui->lDaysPassed->value();
quest.mission.heroLevel = ui->lHeroLevel->value();
quest.mission.heroExperience = ui->lHeroExperience->value();
quest.mission.manaPoints = ui->lManaPoints->value();
quest.mission.manaPercentage = ui->lManaPercentage->value();
quest.mission.primary[0] = ui->lAttack->value();
quest.mission.primary[1] = ui->lDefence->value();
quest.mission.primary[2] = ui->lPower->value();
quest.mission.primary[3] = ui->lKnowledge->value();
for(int i = 0; i < ui->lResources->rowCount(); ++i)
{
if(auto * widget = qobject_cast<QSpinBox*>(ui->lResources->cellWidget(i, 1)))
quest.mission.resources[i] = widget->value();
}
quest.mission.artifacts.clear();
for(int i = 0; i < ui->lArtifacts->count(); ++i)
{
if(ui->lArtifacts->item(i)->checkState() == Qt::Checked)
quest.mission.artifacts.push_back(VLC->artifacts()->getByIndex(i)->getId());
}
quest.mission.spells.clear();
for(int i = 0; i < ui->lSpells->count(); ++i)
{
if(ui->lSpells->item(i)->checkState() == Qt::Checked)
quest.mission.spells.push_back(VLC->spells()->getByIndex(i)->getId());
}
quest.mission.secondary.clear();
for(int i = 0; i < ui->lSkills->rowCount(); ++i)
{
if(auto * widget = qobject_cast<QComboBox*>(ui->lSkills->cellWidget(i, 1)))
{
if(widget->currentIndex() > 0)
quest.mission.secondary[VLC->skills()->getByIndex(i)->getId()] = widget->currentIndex();
}
}
quest.mission.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<QSpinBox*>(ui->lCreatures->cellWidget(i, 1)))
if(widget->value())
quest.mission.creatures.emplace_back(VLC->creatures()->getByIndex(index)->getId(), widget->value());
}
quest.mission.heroes.clear();
for(int i = 0; i < ui->lHeroes->count(); ++i)
{
if(ui->lHeroes->item(i)->checkState() == Qt::Checked)
quest.mission.heroes.emplace_back(ui->lHeroes->item(i)->data(Qt::UserRole).toInt());
}
quest.mission.heroClasses.clear();
for(int i = 0; i < ui->lHeroClasses->count(); ++i)
{
if(ui->lHeroClasses->item(i)->checkState() == Qt::Checked)
quest.mission.heroClasses.emplace_back(ui->lHeroClasses->item(i)->data(Qt::UserRole).toInt());
}
quest.mission.players.clear();
for(int i = 0; i < ui->lPlayers->count(); ++i)
{
if(ui->lPlayers->item(i)->checkState() == Qt::Checked)
quest.mission.players.emplace_back(ui->lPlayers->item(i)->data(Qt::UserRole).toInt());
}
//quest.killTarget is set directly in object picking
return true;
}
QuestDelegate::QuestDelegate(const CMap & m, CGSeerHut & t): map(m), seerhut(t), QStyledItemDelegate()
void QuestWidget::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<QSpinBox*>(listWidget->cellWidget(i, 1));
break;
}
}
}
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 QuestWidget::on_lKillTargetSelect_clicked()
{
auto pred = [](const CGObjectInstance * obj) -> bool
{
if(auto * o = dynamic_cast<const CGHeroInstance*>(obj))
return o->ID != Obj::PRISON;
if(dynamic_cast<const CGCreature*>(obj))
return true;
return false;
};
for(int lvl : {0, 1})
{
auto & l = controller.scene(lvl)->objectPickerView;
l.highlight(pred);
l.update();
QObject::connect(&l, &ObjectPickerLayer::selectionMade, this, &QuestWidget::onTargetPicked);
}
hide();
}
void QuestWidget::onTargetPicked(const CGObjectInstance * obj)
{
show();
for(int lvl : {0, 1})
{
auto & l = controller.scene(lvl)->objectPickerView;
l.clear();
l.update();
QObject::disconnect(&l, &ObjectPickerLayer::selectionMade, this, &QuestWidget::onTargetPicked);
}
if(!obj) //discarded
{
quest.killTarget = ObjectInstanceID::NONE;
ui->lKillTarget->setText("");
return;
}
ui->lKillTarget->setText(QString::fromStdString(obj->instanceName));
quest.killTarget = obj->id;
}
void QuestWidget::on_lCreatureAdd_clicked()
{
onCreatureAdd(ui->lCreatures, ui->lCreatureId, ui->lCreatureAmount);
}
void QuestWidget::on_lCreatureRemove_clicked()
{
std::set<int, std::greater<int>> rowsToRemove;
for(auto * i : ui->lCreatures->selectedItems())
rowsToRemove.insert(i->row());
for(auto i : rowsToRemove)
ui->lCreatures->removeRow(i);
}
QuestDelegate::QuestDelegate(MapController & c, CQuest & t): controller(c), quest(t), QStyledItemDelegate()
{
}
QWidget * QuestDelegate::createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
return new QuestWidget(map, seerhut, parent);
return new QuestWidget(controller, quest, parent);
}
void QuestDelegate::setEditorData(QWidget * editor, const QModelIndex & index) const
@@ -183,11 +408,26 @@ void QuestDelegate::setModelData(QWidget * editor, QAbstractItemModel * model, c
{
if(auto *ed = qobject_cast<QuestWidget *>(editor))
{
auto quest = ed->commitChanges();
model->setData(index, quest);
ed->commitChanges();
}
else
{
QStyledItemDelegate::setModelData(editor, model, index);
}
}
bool QuestDelegate::eventFilter(QObject * object, QEvent * event)
{
if(auto * ed = qobject_cast<QuestWidget *>(object))
{
if(event->type() == QEvent::Hide || event->type() == QEvent::FocusOut)
return false;
if(event->type() == QEvent::Close)
{
emit commitData(ed);
emit closeEditor(ed);
return true;
}
}
return QStyledItemDelegate::eventFilter(object, event);
}

View File

@@ -16,20 +16,33 @@ namespace Ui {
class QuestWidget;
}
class MapController;
class QuestWidget : public QDialog
{
Q_OBJECT
public:
explicit QuestWidget(const CMap &, CGSeerHut &, QWidget *parent = nullptr);
explicit QuestWidget(MapController &, CQuest &, QWidget *parent = nullptr);
~QuestWidget();
void obtainData();
QString commitChanges();
bool commitChanges();
private slots:
void onTargetPicked(const CGObjectInstance *);
void on_lKillTargetSelect_clicked();
void on_lCreatureAdd_clicked();
void on_lCreatureRemove_clicked();
private:
CGSeerHut & seerhut;
const CMap & map;
void onCreatureAdd(QTableWidget * listWidget, QComboBox * comboWidget, QSpinBox * spinWidget);
CQuest & quest;
MapController & controller;
Ui::QuestWidget *ui;
};
@@ -39,13 +52,16 @@ class QuestDelegate : public QStyledItemDelegate
public:
using QStyledItemDelegate::QStyledItemDelegate;
QuestDelegate(const CMap &, CGSeerHut &);
QuestDelegate(MapController &, CQuest &);
QWidget * createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const override;
void setEditorData(QWidget * editor, const QModelIndex & index) const override;
void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override;
protected:
bool eventFilter(QObject * object, QEvent * event) override;
private:
CGSeerHut & seerhut;
const CMap & map;
CQuest & quest;
MapController & controller;
};

View File

@@ -9,8 +9,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>429</width>
<height>89</height>
<width>531</width>
<height>495</height>
</rect>
</property>
<property name="windowTitle">
@@ -19,34 +19,616 @@
<property name="modal">
<bool>true</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QComboBox" name="targetId">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<item>
<widget class="QLabel" name="label_7">
<property name="text">
<string>Day of week</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="lDayOfWeek">
<property name="minimumSize">
<size>
<width>120</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_8">
<property name="text">
<string>Days passed</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="lDaysPassed">
<property name="minimumSize">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_10">
<item>
<widget class="QLabel" name="label_14">
<property name="text">
<string>Hero level</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="lHeroLevel">
<property name="minimumSize">
<size>
<width>40</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_15">
<property name="text">
<string>Hero experience</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="lHeroExperience">
<property name="minimumSize">
<size>
<width>80</width>
<height>0</height>
</size>
</property>
<property name="maximum">
<number>100000</number>
</property>
<property name="singleStep">
<number>100</number>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_9">
<item>
<widget class="QLabel" name="label_9">
<property name="text">
<string>Spell points</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="lManaPoints">
<property name="minimumSize">
<size>
<width>60</width>
<height>0</height>
</size>
</property>
<property name="maximum">
<number>999</number>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="lManaPercentage">
<property name="suffix">
<string>%</string>
</property>
<property name="maximum">
<number>100</number>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_14">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Kill hero/monster</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lKillTarget">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="lKillTargetSelect">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Primary skills</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>12</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QLabel" name="label_10">
<property name="text">
<string>Attack</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="lAttack"/>
</item>
<item>
<widget class="QLabel" name="label_11">
<property name="text">
<string>Defence</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="lDefence"/>
</item>
<item>
<widget class="QLabel" name="label_12">
<property name="text">
<string>Spell power</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="lPower"/>
</item>
<item>
<widget class="QLabel" name="label_13">
<property name="text">
<string>Knowledge</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="lKnowledge"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLineEdit" name="targetAmount">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
<widget class="QTabWidget" name="tabWidget_2">
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="maximumSize">
<size>
<width>60</width>
<height>16777215</height>
</size>
<property name="tabPosition">
<enum>QTabWidget::North</enum>
</property>
<property name="inputMethodHints">
<set>Qt::ImhDigitsOnly</set>
<property name="tabShape">
<enum>QTabWidget::Rounded</enum>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<property name="elideMode">
<enum>Qt::ElideNone</enum>
</property>
<property name="usesScrollButtons">
<bool>true</bool>
</property>
<property name="documentMode">
<bool>false</bool>
</property>
<property name="tabBarAutoHide">
<bool>true</bool>
</property>
<widget class="QWidget" name="tab_3">
<attribute name="title">
<string>Resources</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QTableWidget" name="lResources">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<property name="columnCount">
<number>2</number>
</property>
<attribute name="horizontalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>180</number>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderDefaultSectionSize">
<number>24</number>
</attribute>
<column/>
<column/>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_4">
<attribute name="title">
<string>Artifacts</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QListWidget" name="lArtifacts">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<property name="isWrapping" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_5">
<attribute name="title">
<string>Spells</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QListWidget" name="lSpells">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<property name="isWrapping" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_7">
<attribute name="title">
<string>Skills</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QTableWidget" name="lSkills">
<property name="columnCount">
<number>2</number>
</property>
<attribute name="horizontalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>180</number>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderDefaultSectionSize">
<number>24</number>
</attribute>
<column/>
<column/>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_6">
<attribute name="title">
<string>Creatures</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QComboBox" name="lCreatureId">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="lCreatureAmount">
<property name="minimumSize">
<size>
<width>60</width>
<height>0</height>
</size>
</property>
<property name="maximum">
<number>9999</number>
</property>
<property name="stepType">
<enum>QAbstractSpinBox::AdaptiveDecimalStepType</enum>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="lCreatureAdd">
<property name="text">
<string>Add</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="lCreatureRemove">
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTableWidget" name="lCreatures">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::MultiSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="columnCount">
<number>2</number>
</property>
<attribute name="horizontalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>180</number>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderDefaultSectionSize">
<number>24</number>
</attribute>
<column/>
<column/>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Heroes</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_12">
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QListWidget" name="lHeroes">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<property name="isWrapping" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>Hero classes</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_11">
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QListWidget" name="lHeroClasses">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<property name="isWrapping" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_8">
<attribute name="title">
<string>Players</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_13">
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QListWidget" name="lPlayers">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<property name="isWrapping" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>

View File

@@ -13,6 +13,7 @@
#include "../lib/VCMI_Lib.h"
#include "../lib/CSkillHandler.h"
#include "../lib/spells/CSpellHandler.h"
#include "../lib/CHeroHandler.h"
#include "../lib/CArtHandler.h"
#include "../lib/CCreatureHandler.h"
#include "../lib/constants/StringConstants.h"
@@ -55,7 +56,11 @@ RewardsWidget::RewardsWidget(CMap & m, CRewardableObject & p, QWidget *parent) :
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);
auto * spinBox = new QSpinBox;
spinBox->setMaximum(i == GameResID::GOLD ? 999999 : 999);
if(w == ui->rResources)
spinBox->setMinimum(i == GameResID::GOLD ? -999999 : -999);
w->setCellWidget(i, 1, spinBox);
}
}
@@ -131,6 +136,36 @@ RewardsWidget::RewardsWidget(CMap & m, CRewardableObject & p, QWidget *parent) :
}
}
//fill heroes
VLC->heroTypes()->forEach([this](const HeroType * hero, bool &)
{
auto * item = new QListWidgetItem(QString::fromStdString(hero->getNameTranslated()));
item->setData(Qt::UserRole, QVariant::fromValue(hero->getId().getNum()));
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
item->setCheckState(Qt::Unchecked);
ui->lHeroes->addItem(item);
});
//fill hero classes
VLC->heroClasses()->forEach([this](const HeroClass * heroClass, bool &)
{
auto * item = new QListWidgetItem(QString::fromStdString(heroClass->getNameTranslated()));
item->setData(Qt::UserRole, QVariant::fromValue(heroClass->getId().getNum()));
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
item->setCheckState(Qt::Unchecked);
ui->lHeroClasses->addItem(item);
});
//fill players
for(auto color = PlayerColor(0); color < PlayerColor::PLAYER_LIMIT; ++color)
{
auto * item = new QListWidgetItem(QString::fromStdString(GameConstants::PLAYER_COLOR_NAMES[color.getNum()]));
item->setData(Qt::UserRole, QVariant::fromValue(color.getNum()));
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
item->setCheckState(Qt::Unchecked);
ui->lPlayers->addItem(item);
}
//fill spell cast
for(auto & s : NSecondarySkill::levels)
ui->castLevel->addItem(QString::fromStdString(s));
@@ -347,7 +382,28 @@ void RewardsWidget::saveCurrentVisitInfo(int index)
int index = ui->lCreatures->item(i, 0)->data(Qt::UserRole).toInt();
if(auto * widget = qobject_cast<QSpinBox*>(ui->lCreatures->cellWidget(i, 1)))
if(widget->value())
vinfo.reward.creatures.emplace_back(VLC->creatures()->getByIndex(index)->getId(), widget->value());
vinfo.limiter.creatures.emplace_back(VLC->creatures()->getByIndex(index)->getId(), widget->value());
}
vinfo.limiter.heroes.clear();
for(int i = 0; i < ui->lHeroes->count(); ++i)
{
if(ui->lHeroes->item(i)->checkState() == Qt::Checked)
vinfo.limiter.heroes.emplace_back(ui->lHeroes->item(i)->data(Qt::UserRole).toInt());
}
vinfo.limiter.heroClasses.clear();
for(int i = 0; i < ui->lHeroClasses->count(); ++i)
{
if(ui->lHeroClasses->item(i)->checkState() == Qt::Checked)
vinfo.limiter.heroClasses.emplace_back(ui->lHeroClasses->item(i)->data(Qt::UserRole).toInt());
}
vinfo.limiter.players.clear();
for(int i = 0; i < ui->lPlayers->count(); ++i)
{
if(ui->lPlayers->item(i)->checkState() == Qt::Checked)
vinfo.limiter.players.emplace_back(ui->lPlayers->item(i)->data(Qt::UserRole).toInt());
}
}
@@ -445,10 +501,10 @@ void RewardsWidget::loadCurrentVisitInfo(int index)
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]);
ui->lAttack->setValue(vinfo.limiter.primary[0]);
ui->lDefence->setValue(vinfo.limiter.primary[1]);
ui->lPower->setValue(vinfo.limiter.primary[2]);
ui->lKnowledge->setValue(vinfo.limiter.primary[3]);
for(int i = 0; i < ui->lResources->rowCount(); ++i)
{
if(auto * widget = qobject_cast<QSpinBox*>(ui->lResources->cellWidget(i, 1)))
@@ -472,6 +528,40 @@ void RewardsWidget::loadCurrentVisitInfo(int index)
ui->lCreatureAmount->setValue(i.count);
onCreatureAdd(ui->lCreatures, ui->lCreatureId, ui->lCreatureAmount);
}
for(auto & i : vinfo.limiter.heroes)
{
for(int e = 0; e < ui->lHeroes->count(); ++e)
{
if(ui->lHeroes->item(e)->data(Qt::UserRole).toInt() == i.getNum())
{
ui->lHeroes->item(e)->setCheckState(Qt::Checked);
break;
}
}
}
for(auto & i : vinfo.limiter.heroClasses)
{
for(int e = 0; e < ui->lHeroClasses->count(); ++e)
{
if(ui->lHeroClasses->item(e)->data(Qt::UserRole).toInt() == i.getNum())
{
ui->lHeroClasses->item(e)->setCheckState(Qt::Checked);
break;
}
}
}
for(auto & i : vinfo.limiter.players)
{
for(int e = 0; e < ui->lPlayers->count(); ++e)
{
if(ui->lPlayers->item(e)->data(Qt::UserRole).toInt() == i.getNum())
{
ui->lPlayers->item(e)->setCheckState(Qt::Checked);
break;
}
}
}
}
void RewardsWidget::onCreatureAdd(QTableWidget * listWidget, QComboBox * comboWidget, QSpinBox * spinWidget)

View File

@@ -487,6 +487,12 @@
<property name="currentIndex">
<number>0</number>
</property>
<property name="elideMode">
<enum>Qt::ElideNone</enum>
</property>
<property name="usesScrollButtons">
<bool>true</bool>
</property>
<property name="tabBarAutoHide">
<bool>true</bool>
</property>
@@ -843,7 +849,7 @@
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderDefaultSectionSize">
<number>21</number>
<number>24</number>
</attribute>
<column>
<property name="text">
@@ -1185,6 +1191,12 @@
<property name="currentIndex">
<number>0</number>
</property>
<property name="elideMode">
<enum>Qt::ElideNone</enum>
</property>
<property name="usesScrollButtons">
<bool>true</bool>
</property>
<property name="tabBarAutoHide">
<bool>true</bool>
</property>
@@ -1229,7 +1241,7 @@
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderDefaultSectionSize">
<number>21</number>
<number>24</number>
</attribute>
<column/>
<column/>
@@ -1333,7 +1345,7 @@
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderDefaultSectionSize">
<number>21</number>
<number>24</number>
</attribute>
<column/>
<column/>
@@ -1429,7 +1441,7 @@
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderDefaultSectionSize">
<number>21</number>
<number>24</number>
</attribute>
<column/>
<column/>
@@ -1437,6 +1449,102 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Heroes</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_29">
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QListWidget" name="lHeroes">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<property name="isWrapping" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>Hero classes</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_30">
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QListWidget" name="lHeroClasses">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<property name="isWrapping" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_15">
<attribute name="title">
<string>Players</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_31">
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QListWidget" name="lPlayers">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<property name="isWrapping" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>

View File

@@ -1095,8 +1095,6 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
if (isInTheMap(guardPos))
guardian = getTile(guardPos)->visitableObjects.back();
assert(guardian == nullptr || dynamic_cast<CGCreature*>(guardian) != nullptr);
const bool embarking = !h->boat && objectToVisit && objectToVisit->ID == Obj::BOAT;
const bool disembarking = h->boat
&& t.terType->isLand()