From dcb8f4fc7b0798729cec3d5e81b954393250c6ab Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 25 Oct 2023 13:50:11 +0300 Subject: [PATCH] Moved object type randomization to object class --- lib/constants/EntityIdentifiers.h | 2 +- lib/gameState/CGameState.cpp | 245 +++------------------------- lib/gameState/CGameState.h | 2 - lib/mapObjects/CGCreature.cpp | 32 ++++ lib/mapObjects/CGCreature.h | 1 + lib/mapObjects/CGDwelling.cpp | 176 +++++++++++++------- lib/mapObjects/CGDwelling.h | 49 ++---- lib/mapObjects/CGHeroInstance.cpp | 11 ++ lib/mapObjects/CGHeroInstance.h | 1 + lib/mapObjects/CGObjectInstance.cpp | 5 + lib/mapObjects/CGObjectInstance.h | 1 + lib/mapObjects/CGTownInstance.cpp | 29 ++++ lib/mapObjects/CGTownInstance.h | 2 + lib/mapObjects/IObjectInterface.h | 1 + lib/mapObjects/MiscObjects.cpp | 36 ++++ lib/mapObjects/MiscObjects.h | 2 + lib/mapping/MapFormatH3M.cpp | 59 ++----- 17 files changed, 288 insertions(+), 366 deletions(-) diff --git a/lib/constants/EntityIdentifiers.h b/lib/constants/EntityIdentifiers.h index c9b10fd83..1ff20c597 100644 --- a/lib/constants/EntityIdentifiers.h +++ b/lib/constants/EntityIdentifiers.h @@ -567,7 +567,7 @@ public: constexpr MapObjectSubID(const IdentifierBase & value): Identifier(value.getNum()) {} - constexpr MapObjectSubID(int32_t value): + constexpr MapObjectSubID(int32_t value = -1): Identifier(value) {} diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index 69a2fbed6..bd88d0b03 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -153,223 +153,6 @@ HeroTypeID CGameState::pickUnusedHeroTypeRandomly(const PlayerColor & owner) return HeroTypeID::NONE; // no available heroes at all } -std::pair CGameState::pickObject (CGObjectInstance *obj) -{ - switch(obj->ID) - { - case Obj::RANDOM_ART: - return std::make_pair(Obj::ARTIFACT, VLC->arth->pickRandomArtifact(getRandomGenerator(), CArtifact::ART_TREASURE | CArtifact::ART_MINOR | CArtifact::ART_MAJOR | CArtifact::ART_RELIC)); - case Obj::RANDOM_TREASURE_ART: - return std::make_pair(Obj::ARTIFACT, VLC->arth->pickRandomArtifact(getRandomGenerator(), CArtifact::ART_TREASURE)); - case Obj::RANDOM_MINOR_ART: - return std::make_pair(Obj::ARTIFACT, VLC->arth->pickRandomArtifact(getRandomGenerator(), CArtifact::ART_MINOR)); - case Obj::RANDOM_MAJOR_ART: - return std::make_pair(Obj::ARTIFACT, VLC->arth->pickRandomArtifact(getRandomGenerator(), CArtifact::ART_MAJOR)); - case Obj::RANDOM_RELIC_ART: - return std::make_pair(Obj::ARTIFACT, VLC->arth->pickRandomArtifact(getRandomGenerator(), CArtifact::ART_RELIC)); - case Obj::RANDOM_HERO: - return std::make_pair(Obj::HERO, pickNextHeroType(obj->tempOwner)); - case Obj::RANDOM_MONSTER: - return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(getRandomGenerator())); - case Obj::RANDOM_MONSTER_L1: - return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(getRandomGenerator(), 1)); - case Obj::RANDOM_MONSTER_L2: - return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(getRandomGenerator(), 2)); - case Obj::RANDOM_MONSTER_L3: - return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(getRandomGenerator(), 3)); - case Obj::RANDOM_MONSTER_L4: - return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(getRandomGenerator(), 4)); - case Obj::RANDOM_RESOURCE: - return std::make_pair(Obj::RESOURCE,getRandomGenerator().nextInt(6)); //now it's OH3 style, use %8 for mithril - case Obj::RANDOM_TOWN: - { - PlayerColor align = (dynamic_cast(obj))->alignmentToPlayer; - si32 f; // can be negative (for random) - if(!align.isValidPlayer()) //same as owner / random - { - if(!obj->tempOwner.isValidPlayer()) - f = -1; //random - else - f = scenarioOps->getIthPlayersSettings(obj->tempOwner).castle; - } - else - { - f = scenarioOps->getIthPlayersSettings(align).castle; - } - if(f<0) - { - do - { - f = getRandomGenerator().nextInt((int)VLC->townh->size() - 1); - } - while ((*VLC->townh)[f]->town == nullptr); // find playable faction - } - return std::make_pair(Obj::TOWN,f); - } - case Obj::RANDOM_MONSTER_L5: - return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(getRandomGenerator(), 5)); - case Obj::RANDOM_MONSTER_L6: - return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(getRandomGenerator(), 6)); - case Obj::RANDOM_MONSTER_L7: - return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(getRandomGenerator(), 7)); - case Obj::RANDOM_DWELLING: - case Obj::RANDOM_DWELLING_LVL: - case Obj::RANDOM_DWELLING_FACTION: - { - auto * dwl = dynamic_cast(obj); - int faction; - - //if castle alignment available - if(auto * info = dynamic_cast(dwl->info)) - { - faction = getRandomGenerator().nextInt((int)VLC->townh->size() - 1); - if(info->asCastle && !info->instanceId.empty()) - { - auto iter = map->instanceNames.find(info->instanceId); - - if(iter == map->instanceNames.end()) - logGlobal->error("Map object not found: %s", info->instanceId); - else - { - auto elem = iter->second; - if(elem->ID==Obj::RANDOM_TOWN) - { - randomizeObject(elem.get()); //we have to randomize the castle first - faction = elem->subID; - } - else if(elem->ID==Obj::TOWN) - faction = elem->subID; - else - logGlobal->error("Map object must be town: %s", info->instanceId); - } - } - else if(info->asCastle) - { - - for(auto & elem : map->objects) - { - if(!elem) - continue; - - if(elem->ID==Obj::RANDOM_TOWN - && dynamic_cast(elem.get())->identifier == info->identifier) - { - randomizeObject(elem); //we have to randomize the castle first - faction = elem->subID; - break; - } - else if(elem->ID==Obj::TOWN - && dynamic_cast(elem.get())->identifier == info->identifier) - { - faction = elem->subID; - break; - } - } - } - else - { - std::set temp; - - for(int i = 0; i < info->allowedFactions.size(); i++) - if(info->allowedFactions[i]) - temp.insert(i); - - if(temp.empty()) - logGlobal->error("Random faction selection failed. Nothing is allowed. Fall back to random."); - else - faction = *RandomGeneratorUtil::nextItem(temp, getRandomGenerator()); - } - } - else // castle alignment fixed - faction = obj->subID; - - int level; - - //if level set to range - if(auto * info = dynamic_cast(dwl->info)) - { - level = getRandomGenerator().nextInt(info->minLevel, info->maxLevel) - 1; - } - else // fixed level - { - level = obj->subID; - } - - delete dwl->info; - dwl->info = nullptr; - - std::pair result(Obj::NO_OBJ, -1); - CreatureID cid; - if((*VLC->townh)[faction]->town) - cid = (*VLC->townh)[faction]->town->creatures[level][0]; - else - { - //neutral faction - std::vector possibleCreatures; - std::copy_if(VLC->creh->objects.begin(), VLC->creh->objects.end(), std::back_inserter(possibleCreatures), [faction](const CCreature * c) - { - return c->getFaction().getNum() == faction; - }); - assert(!possibleCreatures.empty()); - cid = (*RandomGeneratorUtil::nextItem(possibleCreatures, getRandomGenerator()))->getId(); - } - - //NOTE: this will pick last dwelling with this creature (Mantis #900) - //check for block map equality is better but more complex solution - auto testID = [&](const Obj & primaryID) -> void - { - auto dwellingIDs = VLC->objtypeh->knownSubObjects(primaryID); - for (si32 entry : dwellingIDs) - { - const auto * handler = dynamic_cast(VLC->objtypeh->getHandlerFor(primaryID, entry).get()); - - if (handler->producesCreature(VLC->creh->objects[cid])) - result = std::make_pair(primaryID, entry); - } - }; - - testID(Obj::CREATURE_GENERATOR1); - if (result.first == Obj::NO_OBJ) - testID(Obj::CREATURE_GENERATOR4); - - if (result.first == Obj::NO_OBJ) - { - logGlobal->error("Error: failed to find dwelling for %s of level %d", (*VLC->townh)[faction]->getNameTranslated(), int(level)); - result = std::make_pair(Obj::CREATURE_GENERATOR1, *RandomGeneratorUtil::nextItem(VLC->objtypeh->knownSubObjects(Obj::CREATURE_GENERATOR1), getRandomGenerator())); - } - - return result; - } - } - return std::make_pair(Obj::NO_OBJ,-1); -} - -void CGameState::randomizeObject(CGObjectInstance *cur) -{ - std::pair ran = pickObject(cur); - if(ran.first == Obj::NO_OBJ || ran.second<0) //this is not a random object, or we couldn't find anything - { - if(cur->ID==Obj::TOWN || cur->ID==Obj::MONSTER) - cur->setType(cur->ID, cur->subID); // update def, if necessary - } - else if(ran.first==Obj::HERO)//special code for hero - { - auto * h = dynamic_cast(cur); - cur->setType(ran.first, ran.second); - map->heroesOnMap.emplace_back(h); - } - else if(ran.first==Obj::TOWN)//special code for town - { - auto * t = dynamic_cast(cur); - cur->setType(ran.first, ran.second); - map->towns.emplace_back(t); - } - else - { - cur->setType(ran.first, ran.second); - } -} - int CGameState::getDate(Date mode) const { int temp; @@ -766,9 +549,33 @@ void CGameState::randomizeMapObjects() logGlobal->debug("\tRandomizing objects"); for(CGObjectInstance *obj : map->objects) { - if(!obj) continue; + if(!obj) + continue; - randomizeObject(obj); + { + std::pair ran = pickObject(obj); + if(ran.first == Obj::NO_OBJ || ran.second<0) //this is not a random object, or we couldn't find anything + { + if(obj->ID==Obj::TOWN || obj->ID==Obj::MONSTER) + obj->setType(obj->ID, obj->subID); // update def, if necessary + } + else if(ran.first==Obj::HERO)//special code for hero + { + auto * h = dynamic_cast(obj); + obj->setType(ran.first, ran.second); + map->heroesOnMap.emplace_back(h); + } + else if(ran.first==Obj::TOWN)//special code for town + { + auto * t = dynamic_cast(obj); + obj->setType(ran.first, ran.second); + map->towns.emplace_back(t); + } + else + { + obj->setType(ran.first, ran.second); + } + } //handle Favouring Winds - mark tiles under it if(obj->ID == Obj::FAVORABLE_WINDS) diff --git a/lib/gameState/CGameState.h b/lib/gameState/CGameState.h index 94db3c60e..a99076230 100644 --- a/lib/gameState/CGameState.h +++ b/lib/gameState/CGameState.h @@ -187,7 +187,6 @@ private: void initGrailPosition(); void initRandomFactionsForPlayers(); void randomizeMapObjects(); - void randomizeObject(CGObjectInstance *cur); void initPlayerStates(); void placeStartingHeroes(); void placeStartingHero(const PlayerColor & playerColor, const HeroTypeID & heroTypeId, int3 townPos); @@ -214,7 +213,6 @@ private: CGHeroInstance * getUsedHero(const HeroTypeID & hid) const; bool isUsedHero(const HeroTypeID & hid) const; //looks in heroes and prisons std::set getUnusedAllowedHeroes(bool alsoIncludeNotAllowed = false) const; - std::pair pickObject(CGObjectInstance *obj); //chooses type of object to be randomized, returns HeroTypeID pickUnusedHeroTypeRandomly(const PlayerColor & owner); // picks a unused hero type randomly HeroTypeID pickNextHeroType(const PlayerColor & owner); // picks next free hero type of the H3 hero init sequence -> chosen starting hero, then unused hero type randomly UpgradeInfo fillUpgradeInfo(const CStackInstance &stack) const; diff --git a/lib/mapObjects/CGCreature.cpp b/lib/mapObjects/CGCreature.cpp index dff3ed95d..a4f359b3e 100644 --- a/lib/mapObjects/CGCreature.cpp +++ b/lib/mapObjects/CGCreature.cpp @@ -160,6 +160,38 @@ CreatureID CGCreature::getCreature() const return CreatureID(getObjTypeIndex().getNum()); } +void CGCreature::pickRandomObject(CRandomGenerator & rand) +{ + switch(ID) + { + case MapObjectID::RANDOM_MONSTER: + subID = VLC->creh->pickRandomMonster(rand); + break; + case MapObjectID::RANDOM_MONSTER_L1: + subID = VLC->creh->pickRandomMonster(rand, 1); + break; + case MapObjectID::RANDOM_MONSTER_L2: + subID = VLC->creh->pickRandomMonster(rand, 2); + break; + case MapObjectID::RANDOM_MONSTER_L3: + subID = VLC->creh->pickRandomMonster(rand, 3); + break; + case MapObjectID::RANDOM_MONSTER_L4: + subID = VLC->creh->pickRandomMonster(rand, 4); + break; + case MapObjectID::RANDOM_MONSTER_L5: + subID = VLC->creh->pickRandomMonster(rand, 5); + break; + case MapObjectID::RANDOM_MONSTER_L6: + subID = VLC->creh->pickRandomMonster(rand, 6); + break; + case MapObjectID::RANDOM_MONSTER_L7: + subID = VLC->creh->pickRandomMonster(rand, 7); + break; + } + ID = MapObjectID::MONSTER; +} + void CGCreature::initObj(CRandomGenerator & rand) { blockVisit = true; diff --git a/lib/mapObjects/CGCreature.h b/lib/mapObjects/CGCreature.h index 438e89c92..13ff02f30 100644 --- a/lib/mapObjects/CGCreature.h +++ b/lib/mapObjects/CGCreature.h @@ -41,6 +41,7 @@ public: std::string getHoverText(PlayerColor player) const override; std::string getHoverText(const CGHeroInstance * hero) const override; void initObj(CRandomGenerator & rand) override; + void pickRandomObject(CRandomGenerator & rand) override; void newTurn(CRandomGenerator & rand) const override; void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; diff --git a/lib/mapObjects/CGDwelling.cpp b/lib/mapObjects/CGDwelling.cpp index 70fc4d983..a911e3089 100644 --- a/lib/mapObjects/CGDwelling.cpp +++ b/lib/mapObjects/CGDwelling.cpp @@ -11,8 +11,10 @@ #include "StdInc.h" #include "CGDwelling.h" #include "../serializer/JsonSerializeFormat.h" +#include "../mapping/CMap.h" #include "../mapObjectConstructors/AObjectTypeHandler.h" #include "../mapObjectConstructors/CObjectClassesHandler.h" +#include "../mapObjectConstructors/DwellingInstanceConstructor.h" #include "../mapObjects/CGHeroInstance.h" #include "../networkPacks/StackLocation.h" #include "../networkPacks/PacksForClient.h" @@ -26,41 +28,10 @@ VCMI_LIB_NAMESPACE_BEGIN -CSpecObjInfo::CSpecObjInfo(): - owner(nullptr) -{ - -} - -void CCreGenAsCastleInfo::serializeJson(JsonSerializeFormat & handler) +void CGDwellingRandomizationInfo::serializeJson(JsonSerializeFormat & handler) { handler.serializeString("sameAsTown", instanceId); - - if(!handler.saving) - { - asCastle = !instanceId.empty(); - allowedFactions.clear(); - } - - if(!asCastle) - { - std::vector standard; - standard.resize(VLC->townh->size(), true); - - JsonSerializeFormat::LIC allowedLIC(standard, &FactionID::decode, &FactionID::encode); - allowedLIC.any = allowedFactions; - - handler.serializeLIC("allowedFactions", allowedLIC); - - if(!handler.saving) - { - allowedFactions = allowedLIC.any; - } - } -} - -void CCreGenLeveledInfo::serializeJson(JsonSerializeFormat & handler) -{ + handler.serializeIdArray("allowedFactions", allowedFactions); handler.serializeInt("minLevel", minLevel, static_cast(1)); handler.serializeInt("maxLevel", maxLevel, static_cast(7)); @@ -72,20 +43,119 @@ void CCreGenLeveledInfo::serializeJson(JsonSerializeFormat & handler) } } -void CCreGenLeveledCastleInfo::serializeJson(JsonSerializeFormat & handler) +CGDwelling::CGDwelling() = default; +CGDwelling::~CGDwelling() = default; + +FactionID CGDwelling::randomizeFaction(CRandomGenerator & rand) { - CCreGenAsCastleInfo::serializeJson(handler); - CCreGenLeveledInfo::serializeJson(handler); + assert(randomizationInfo.has_value()); + if (!randomizationInfo) + return FactionID::CASTLE; + + CGTownInstance * linkedTown = nullptr; + + if (!randomizationInfo->instanceId.empty()) + { + auto iter = cb->gameState()->map->instanceNames.find(randomizationInfo->instanceId); + + if(iter == cb->gameState()->map->instanceNames.end()) + logGlobal->error("Map object not found: %s", randomizationInfo->instanceId); + linkedTown = dynamic_cast(iter->second.get()); + } + + if (randomizationInfo->identifier != 0) + { + for(auto & elem : cb->gameState()->map->objects) + { + auto town = dynamic_cast(elem.get()); + if(town && town->identifier == randomizationInfo->identifier) + { + linkedTown = town; + break; + } + } + } + + if (linkedTown) + { + if(linkedTown->ID==Obj::RANDOM_TOWN) + linkedTown->pickRandomObject(rand); //we have to randomize the castle first + + assert(linkedTown->ID == Obj::TOWN); + if(linkedTown->ID==Obj::TOWN) + return linkedTown->getFaction(); + } + + if(!randomizationInfo->allowedFactions.empty()) + return *RandomGeneratorUtil::nextItem(randomizationInfo->allowedFactions, rand); + + + std::vector potentialPicks; + + for (FactionID faction(0); faction < VLC->townh->size(); ++faction) + if (VLC->factions()->getById(faction)->hasTown()) + potentialPicks.push_back(faction); + + assert(!potentialPicks.empty()); + return *RandomGeneratorUtil::nextItem(potentialPicks, rand); } -CGDwelling::CGDwelling() - : info(nullptr) +int CGDwelling::randomizeLevel(CRandomGenerator & rand) { + assert(randomizationInfo.has_value()); + + if (!randomizationInfo) + return rand.nextInt(1, 7) - 1; + + if(randomizationInfo->minLevel == randomizationInfo->maxLevel) + return randomizationInfo->minLevel - 1; + + return rand.nextInt(randomizationInfo->minLevel, randomizationInfo->maxLevel) - 1; } -CGDwelling::~CGDwelling() +void CGDwelling::pickRandomObject(CRandomGenerator & rand) { - vstd::clear_pointer(info); + if (ID == Obj::RANDOM_DWELLING || ID == Obj::RANDOM_DWELLING_LVL || ID == Obj::RANDOM_DWELLING_FACTION) + { + FactionID faction = randomizeFaction(rand); + int level = randomizeLevel(rand); + assert(faction != FactionID::NONE && faction != FactionID::NEUTRAL); + assert(level >= 1 && level <= 7); + randomizationInfo.reset(); + + CreatureID cid = (*VLC->townh)[faction]->town->creatures[level][0]; + + //NOTE: this will pick last dwelling with this creature (Mantis #900) + //check for block map equality is better but more complex solution + auto testID = [&](const Obj & primaryID) -> MapObjectSubID + { + auto dwellingIDs = VLC->objtypeh->knownSubObjects(primaryID); + for (si32 entry : dwellingIDs) + { + const auto * handler = dynamic_cast(VLC->objtypeh->getHandlerFor(primaryID, entry).get()); + + if (handler->producesCreature(VLC->creh->objects[cid])) + return MapObjectSubID(entry); + } + return MapObjectSubID(); + }; + + ID = Obj::CREATURE_GENERATOR1; + subID = testID(Obj::CREATURE_GENERATOR1); + + if (subID == MapObjectSubID()) + { + ID = Obj::CREATURE_GENERATOR4; + subID = testID(Obj::CREATURE_GENERATOR4); + } + + if (subID == MapObjectSubID()) + { + logGlobal->error("Error: failed to find dwelling for %s of level %d", (*VLC->townh)[faction]->getNameTranslated(), int(level)); + ID = Obj::CREATURE_GENERATOR4; + subID = *RandomGeneratorUtil::nextItem(VLC->objtypeh->knownSubObjects(Obj::CREATURE_GENERATOR1), rand); + } + } } void CGDwelling::initObj(CRandomGenerator & rand) @@ -121,23 +191,6 @@ void CGDwelling::initObj(CRandomGenerator & rand) } } -void CGDwelling::initRandomObjectInfo() -{ - vstd::clear_pointer(info); - switch(ID) - { - case Obj::RANDOM_DWELLING: info = new CCreGenLeveledCastleInfo(); - break; - case Obj::RANDOM_DWELLING_LVL: info = new CCreGenAsCastleInfo(); - break; - case Obj::RANDOM_DWELLING_FACTION: info = new CCreGenLeveledInfo(); - break; - } - - if(info) - info->owner = this; -} - void CGDwelling::setPropertyDer(ui8 what, ui32 val) { switch (what) @@ -425,9 +478,6 @@ void CGDwelling::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) void CGDwelling::serializeJsonOptions(JsonSerializeFormat & handler) { - if(!handler.saving) - initRandomObjectInfo(); - switch (ID) { case Obj::WAR_MACHINE_FACTORY: @@ -437,8 +487,10 @@ void CGDwelling::serializeJsonOptions(JsonSerializeFormat & handler) case Obj::RANDOM_DWELLING: case Obj::RANDOM_DWELLING_LVL: case Obj::RANDOM_DWELLING_FACTION: - info->serializeJson(handler); - //fall through + if (!handler.saving) + randomizationInfo = CGDwellingRandomizationInfo(); + randomizationInfo->serializeJson(handler); + [[fallthrough]]; default: serializeJsonOwner(handler); break; diff --git a/lib/mapObjects/CGDwelling.h b/lib/mapObjects/CGDwelling.h index f27cf44e2..bc63dcd85 100644 --- a/lib/mapObjects/CGDwelling.h +++ b/lib/mapObjects/CGDwelling.h @@ -16,62 +16,39 @@ VCMI_LIB_NAMESPACE_BEGIN class CGDwelling; -class DLL_LINKAGE CSpecObjInfo +class DLL_LINKAGE CGDwellingRandomizationInfo { public: - CSpecObjInfo(); - virtual ~CSpecObjInfo() = default; - - virtual void serializeJson(JsonSerializeFormat & handler) = 0; - - const CGDwelling * owner; -}; - -class DLL_LINKAGE CCreGenAsCastleInfo : public virtual CSpecObjInfo -{ -public: - bool asCastle = false; - ui32 identifier = 0;//h3m internal identifier - - std::vector allowedFactions; + std::set allowedFactions; std::string instanceId;//vcmi map instance identifier - void serializeJson(JsonSerializeFormat & handler) override; + int32_t identifier = 0;//h3m internal identifier + + uint8_t minLevel = 1; + uint8_t maxLevel = 7; //minimal and maximal level of creature in dwelling: <1, 7> + + void serializeJson(JsonSerializeFormat & handler); }; -class DLL_LINKAGE CCreGenLeveledInfo : public virtual CSpecObjInfo -{ -public: - ui8 minLevel = 1; - ui8 maxLevel = 7; //minimal and maximal level of creature in dwelling: <1, 7> - - void serializeJson(JsonSerializeFormat & handler) override; -}; - -class DLL_LINKAGE CCreGenLeveledCastleInfo : public CCreGenAsCastleInfo, public CCreGenLeveledInfo -{ -public: - CCreGenLeveledCastleInfo() = default; - void serializeJson(JsonSerializeFormat & handler) override; -}; - - class DLL_LINKAGE CGDwelling : public CArmedInstance { public: typedef std::vector > > TCreaturesSet; - CSpecObjInfo * info; //random dwelling options; not serialized + std::optional randomizationInfo; //random dwelling options; not serialized TCreaturesSet creatures; //creatures[level] -> CGDwelling(); ~CGDwelling() override; - void initRandomObjectInfo(); protected: void serializeJsonOptions(JsonSerializeFormat & handler) override; private: + FactionID randomizeFaction(CRandomGenerator & rand); + int randomizeLevel(CRandomGenerator & rand); + + void pickRandomObject(CRandomGenerator & rand) override; void initObj(CRandomGenerator & rand) override; void onHeroVisit(const CGHeroInstance * h) const override; void newTurn(CRandomGenerator & rand) const override; diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index c122af7ab..673d91684 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -571,6 +571,17 @@ void CGHeroInstance::SecondarySkillsInfo::resetWisdomCounter() wisdomCounter = 1; } +void CGHeroInstance::pickRandomObject(CRandomGenerator & rand) +{ + assert(ID == Obj::HERO || ID == Obj::PRISON || ID == Obj::RANDOM_HERO); + + if (ID == Obj::RANDOM_HERO) + { + ID = Obj::HERO; + subID = cb->gameState()->pickNextHeroType(getOwner()); + } +} + void CGHeroInstance::initObj(CRandomGenerator & rand) { if(!type) diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index a742f2d7b..492495d42 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -295,6 +295,7 @@ public: void deserializationFix(); void initObj(CRandomGenerator & rand) override; + void pickRandomObject(CRandomGenerator & rand) override; void onHeroVisit(const CGHeroInstance * h) const override; std::string getObjectName() const override; diff --git a/lib/mapObjects/CGObjectInstance.cpp b/lib/mapObjects/CGObjectInstance.cpp index 1f153a75f..435e37c6c 100644 --- a/lib/mapObjects/CGObjectInstance.cpp +++ b/lib/mapObjects/CGObjectInstance.cpp @@ -166,6 +166,11 @@ void CGObjectInstance::setType(si32 newID, si32 newSubID) cb->gameState()->map->addBlockVisTiles(this); } +void CGObjectInstance::pickRandomObject(CRandomGenerator & rand) +{ + // no-op +} + void CGObjectInstance::initObj(CRandomGenerator & rand) { switch(ID) diff --git a/lib/mapObjects/CGObjectInstance.h b/lib/mapObjects/CGObjectInstance.h index 4d1fbc9fd..d56427e3b 100644 --- a/lib/mapObjects/CGObjectInstance.h +++ b/lib/mapObjects/CGObjectInstance.h @@ -128,6 +128,7 @@ public: /** OVERRIDES OF IObjectInterface **/ void initObj(CRandomGenerator & rand) override; + void pickRandomObject(CRandomGenerator & rand) override; void onHeroVisit(const CGHeroInstance * h) const override; /// method for synchronous update. Note: For new properties classes should override setPropertyDer instead void setProperty(ui8 what, ui32 val) final; diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index a4841a3cd..5b66236d1 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -20,6 +20,7 @@ #include "../gameState/CGameState.h" #include "../mapping/CMap.h" #include "../CPlayerState.h" +#include "../StartInfo.h" #include "../TerrainHandler.h" #include "../mapObjectConstructors/AObjectTypeHandler.h" #include "../mapObjectConstructors/CObjectClassesHandler.h" @@ -459,6 +460,34 @@ void CGTownInstance::deleteTownBonus(BuildingID bid) delete freeIt; } +FactionID CGTownInstance::randomizeFaction(CRandomGenerator & rand) +{ + if(getOwner().isValidPlayer()) + return cb->gameState()->scenarioOps->getIthPlayersSettings(getOwner()).castle; + + if(alignmentToPlayer.isValidPlayer()) + return cb->gameState()->scenarioOps->getIthPlayersSettings(alignmentToPlayer).castle; + + std::vector potentialPicks; + + for (FactionID faction(0); faction < VLC->townh->size(); ++faction) + if (VLC->factions()->getById(faction)->hasTown()) + potentialPicks.push_back(faction); + + assert(!potentialPicks.empty()); + return *RandomGeneratorUtil::nextItem(potentialPicks, rand); +} + +void CGTownInstance::pickRandomObject(CRandomGenerator & rand) +{ + assert(ID == MapObjectID::TOWN || ID == MapObjectID::RANDOM_TOWN); + if (ID == MapObjectID::RANDOM_TOWN) + { + ID = MapObjectID::TOWN; + subID = randomizeFaction(rand); + } +} + void CGTownInstance::initObj(CRandomGenerator & rand) ///initialize town structures { blockVisit = true; diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index 07da57644..18a6e8eb2 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -197,6 +197,7 @@ public: void onHeroVisit(const CGHeroInstance * h) const override; void onHeroLeave(const CGHeroInstance * h) const override; void initObj(CRandomGenerator & rand) override; + void pickRandomObject(CRandomGenerator & rand) override; void battleFinished(const CGHeroInstance * hero, const BattleResult & result) const override; std::string getObjectName() const override; @@ -216,6 +217,7 @@ protected: void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; private: + FactionID randomizeFaction(CRandomGenerator & rand); void setOwner(const PlayerColor & owner) const; void onTownCaptured(const PlayerColor & winner) const; int getDwellingBonus(const std::vector& creatureIds, const std::vector >& dwellings) const; diff --git a/lib/mapObjects/IObjectInterface.h b/lib/mapObjects/IObjectInterface.h index 6a6ca40b7..8dab1a59d 100644 --- a/lib/mapObjects/IObjectInterface.h +++ b/lib/mapObjects/IObjectInterface.h @@ -45,6 +45,7 @@ public: virtual void onHeroLeave(const CGHeroInstance * h) const; virtual void newTurn(CRandomGenerator & rand) const; virtual void initObj(CRandomGenerator & rand); //synchr + virtual void pickRandomObject(CRandomGenerator & rand); virtual void setProperty(ui8 what, ui32 val);//synchr //Called when queries created DURING HERO VISIT are resolved diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index 5e688e4d9..fe6367aaf 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -247,6 +247,17 @@ std::string CGResource::getHoverText(PlayerColor player) const return VLC->generaltexth->restypes[resourceID()]; } +void CGResource::pickRandomObject(CRandomGenerator & rand) +{ + assert(ID == Obj::RESOURCE || ID == Obj::RANDOM_RESOURCE); + + if (ID == Obj::RANDOM_RESOURCE) + { + ID = Obj::RESOURCE; + subID = rand.nextInt(EGameResID::WOOD, EGameResID::GOLD); + } +} + void CGResource::initObj(CRandomGenerator & rand) { blockVisit = true; @@ -701,6 +712,31 @@ ArtifactID CGArtifact::getArtifact() const return getObjTypeIndex().getNum(); } +void CGArtifact::pickRandomObject(CRandomGenerator & rand) +{ + switch(ID) + { + case MapObjectID::RANDOM_ART: + subID = VLC->arth->pickRandomArtifact(rand, CArtifact::ART_TREASURE | CArtifact::ART_MINOR | CArtifact::ART_MAJOR | CArtifact::ART_RELIC); + break; + case MapObjectID::RANDOM_TREASURE_ART: + subID = VLC->arth->pickRandomArtifact(rand, CArtifact::ART_TREASURE); + break; + case MapObjectID::RANDOM_MINOR_ART: + subID = VLC->arth->pickRandomArtifact(rand, CArtifact::ART_MINOR); + break; + case MapObjectID::RANDOM_MAJOR_ART: + subID = VLC->arth->pickRandomArtifact(rand, CArtifact::ART_MAJOR); + break; + case MapObjectID::RANDOM_RELIC_ART: + subID = VLC->arth->pickRandomArtifact(rand, CArtifact::ART_RELIC); + break; + } + + if (ID != Obj::SPELL_SCROLL) + ID = MapObjectID::ARTIFACT; +} + void CGArtifact::initObj(CRandomGenerator & rand) { blockVisit = true; diff --git a/lib/mapObjects/MiscObjects.h b/lib/mapObjects/MiscObjects.h index c40256dc1..3d0e4264f 100644 --- a/lib/mapObjects/MiscObjects.h +++ b/lib/mapObjects/MiscObjects.h @@ -89,6 +89,7 @@ public: void pick( const CGHeroInstance * h ) const; void initObj(CRandomGenerator & rand) override; + void pickRandomObject(CRandomGenerator & rand) override; void afterAddToMap(CMap * map) override; BattleField getBattlefield() const override; @@ -115,6 +116,7 @@ public: void onHeroVisit(const CGHeroInstance * h) const override; void initObj(CRandomGenerator & rand) override; + void pickRandomObject(CRandomGenerator & rand) override; void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; std::string getHoverText(PlayerColor player) const override; diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 06c3e951a..a94fbc262 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -1322,60 +1322,27 @@ CGObjectInstance * CMapLoaderH3M::readDwellingRandom(const int3 & mapPosition, s { auto * object = new CGDwelling(); - CSpecObjInfo * spec = nullptr; - switch(objectTemplate->id) - { - case Obj::RANDOM_DWELLING: - spec = new CCreGenLeveledCastleInfo(); - break; - case Obj::RANDOM_DWELLING_LVL: - spec = new CCreGenAsCastleInfo(); - break; - case Obj::RANDOM_DWELLING_FACTION: - spec = new CCreGenLeveledInfo(); - break; - default: - throw std::runtime_error("Invalid random dwelling format"); - } - spec->owner = object; - setOwnerAndValidate(mapPosition, object, reader->readPlayer32()); - //216 and 217 - if(auto * castleSpec = dynamic_cast(spec)) + object->randomizationInfo = CGDwellingRandomizationInfo(); + + bool hasFactionInfo = objectTemplate->id == Obj::RANDOM_DWELLING || objectTemplate->id == Obj::RANDOM_DWELLING_FACTION; + bool hasLevelInfo = objectTemplate->id == Obj::RANDOM_DWELLING || objectTemplate->id == Obj::RANDOM_DWELLING_LVL; + + if (hasFactionInfo) { - castleSpec->instanceId = ""; - castleSpec->identifier = reader->readUInt32(); - if(!castleSpec->identifier) - { - castleSpec->asCastle = false; - const int MASK_SIZE = 8; - ui8 mask[2]; - mask[0] = reader->readUInt8(); - mask[1] = reader->readUInt8(); + object->randomizationInfo->identifier = reader->readUInt32(); - castleSpec->allowedFactions.clear(); - castleSpec->allowedFactions.resize(VLC->townh->size(), false); - - for(int i = 0; i < MASK_SIZE; i++) - castleSpec->allowedFactions[i] = ((mask[0] & (1 << i)) > 0); - - for(int i = 0; i < (GameConstants::F_NUMBER - MASK_SIZE); i++) - castleSpec->allowedFactions[i + MASK_SIZE] = ((mask[1] & (1 << i)) > 0); - } - else - { - castleSpec->asCastle = true; - } + if(object->randomizationInfo->identifier != 0) + reader->readBitmaskFactions(object->randomizationInfo->allowedFactions, false); } - //216 and 218 - if(auto * lvlSpec = dynamic_cast(spec)) + if(hasLevelInfo) { - lvlSpec->minLevel = std::max(reader->readUInt8(), static_cast(0)) + 1; - lvlSpec->maxLevel = std::min(reader->readUInt8(), static_cast(6)) + 1; + object->randomizationInfo->minLevel = std::max(reader->readUInt8(), static_cast(0)) + 1; + object->randomizationInfo->maxLevel = std::min(reader->readUInt8(), static_cast(6)) + 1; } - object->info = spec; + return object; }