From 946e47ee226f4efa7c11a4b47ece414cfa43fce9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Wed, 5 Mar 2025 21:31:33 +0100 Subject: [PATCH] Add townHints to random template, define logic --- config/schemas/template.json | 12 ++++ lib/rmg/CRmgTemplate.cpp | 90 ++++++++++++++++++++++++++++- lib/rmg/CRmgTemplate.h | 39 ++++++++++--- lib/rmg/modificators/TownPlacer.cpp | 57 +++++++++++++++--- lib/rmg/modificators/TownPlacer.h | 1 + 5 files changed, 184 insertions(+), 15 deletions(-) diff --git a/config/schemas/template.json b/config/schemas/template.json index 4ed948449..241461719 100644 --- a/config/schemas/template.json +++ b/config/schemas/template.json @@ -19,6 +19,18 @@ "playerTowns" : {"$ref" : "#/definitions/towns"}, "neutralTowns" : {"$ref" : "#/definitions/towns"}, "matchTerrainToTown" : { "type" : "boolean"}, + "townHints": { + "type": "array", + "items": { + "type": "object", + "properties": { + "likeZone": { "type": "number" }, + "notLikeZone": { "type": "number" }, + "relatedToZoneTerrain": { "type": "number" } + } + } + }, + "townsLikeZone" : { "type" : "number" }, "minesLikeZone" : { "type" : "number" }, "terrainTypeLikeZone" : { "type" : "number" }, "treasureLikeZone" : { "type" : "number" }, diff --git a/lib/rmg/CRmgTemplate.cpp b/lib/rmg/CRmgTemplate.cpp index 520c2eb91..19db9b952 100644 --- a/lib/rmg/CRmgTemplate.cpp +++ b/lib/rmg/CRmgTemplate.cpp @@ -102,7 +102,24 @@ void ZoneOptions::CTownInfo::serializeJson(JsonSerializeFormat & handler) handler.serializeInt("castles", castleCount, 0); handler.serializeInt("townDensity", townDensity, 0); handler.serializeInt("castleDensity", castleDensity, 0); - handler.serializeInt("sourceZone", sourceZone, NO_ZONE); + handler.serializeInt("townTypesLikeZone", townTypesLikeZone, NO_ZONE); + handler.serializeInt("townTypesNotLikeZone", townTypesNotLikeZone, NO_ZONE); + handler.serializeInt("townTypesRelatedToZoneTerrain", townTypesRelatedToZoneTerrain, NO_ZONE); +} + +ZoneOptions::CTownHints::CTownHints() + : likeZone(NO_ZONE), + notLikeZone(NO_ZONE), + relatedToZoneTerrain(NO_ZONE) +{ + +} + +void ZoneOptions::CTownHints::serializeJson(JsonSerializeFormat & handler) +{ + handler.serializeInt("likeZone", likeZone, NO_ZONE); + handler.serializeInt("notLikeZone", notLikeZone, NO_ZONE); + handler.serializeInt("relatedToZoneTerrain", relatedToZoneTerrain, NO_ZONE); } ZoneOptions::ZoneOptions(): @@ -114,6 +131,7 @@ ZoneOptions::ZoneOptions(): matchTerrainToTown(true), townsAreSameType(false), monsterStrength(EMonsterStrength::ZONE_NORMAL), + townsLikeZone(NO_ZONE), minesLikeZone(NO_ZONE), terrainTypeLikeZone(NO_ZONE), treasureLikeZone(NO_ZONE) @@ -291,6 +309,11 @@ TRmgTemplateZoneId ZoneOptions::getCustomObjectsLikeZone() const return customObjectsLikeZone; } +TRmgTemplateZoneId ZoneOptions::getTownsLikeZone() const +{ + return townsLikeZone; +} + void ZoneOptions::addConnection(const ZoneConnection & connection) { connectedZoneIds.push_back(connection.getOtherZoneId(getId())); @@ -317,16 +340,51 @@ bool ZoneOptions::isMatchTerrainToTown() const return matchTerrainToTown; } +void ZoneOptions::setMatchTerrainToTown(bool value) +{ + matchTerrainToTown = value; +} + const ZoneOptions::CTownInfo & ZoneOptions::getPlayerTowns() const { return playerTowns; } +void ZoneOptions::setPlayerTowns(const CTownInfo & value) +{ + playerTowns = value; +} + const ZoneOptions::CTownInfo & ZoneOptions::getNeutralTowns() const { return neutralTowns; } +void ZoneOptions::setNeutralTowns(const CTownInfo & value) +{ + neutralTowns = value; +} + +const std::vector & ZoneOptions::getTownHints() const +{ + return townHints; +} + +void ZoneOptions::setTownHints(const std::vector & value) +{ + townHints = value; +} + +std::set ZoneOptions::getBannedTownTypes() const +{ + return bannedTownTypes; +} + +void ZoneOptions::setBannedTownTypes(const std::set & value) +{ + bannedTownTypes = value; +} + void ZoneOptions::serializeJson(JsonSerializeFormat & handler) { static const std::vector zoneTypes = @@ -348,6 +406,7 @@ void ZoneOptions::serializeJson(JsonSerializeFormat & handler) #define SERIALIZE_ZONE_LINK(fieldName) handler.serializeInt(#fieldName, fieldName, NO_ZONE); + SERIALIZE_ZONE_LINK(townsLikeZone); SERIALIZE_ZONE_LINK(minesLikeZone); SERIALIZE_ZONE_LINK(terrainTypeLikeZone); SERIALIZE_ZONE_LINK(treasureLikeZone); @@ -367,6 +426,8 @@ void ZoneOptions::serializeJson(JsonSerializeFormat & handler) handler.serializeIdArray("allowedTowns", townTypes); handler.serializeIdArray("bannedTowns", bannedTownTypes); + handler.enterArray("townHints").serializeStruct(townHints); + { //TODO: add support for std::map to serializeEnum static const std::vector zoneMonsterStrengths = @@ -829,6 +890,8 @@ void CRmgTemplate::afterLoad() auto zone = idAndZone.second; // Inherit properties recursively + inheritTownProperties(zone); + inheritZoneProperty(zone, &rmg::ZoneOptions::getTerrainTypes, &rmg::ZoneOptions::setTerrainTypes, @@ -882,6 +945,31 @@ void CRmgTemplate::afterLoad() allowedWaterContent.erase(EWaterContent::RANDOM); } +void CRmgTemplate::inheritTownProperties(std::shared_ptr zone, uint32_t iteration) +{ + if (iteration >= 50) + { + logGlobal->error("Infinite recursion for town properties detected in template %s", name); + return; + } + + if (zone->getTownsLikeZone() != rmg::ZoneOptions::NO_ZONE) + { + const auto otherZone = zones.at(zone->getTownsLikeZone()); + + // Recursively inherit from the source zone first + inheritTownProperties(otherZone, iteration + 1); + + // Now copy all town-related properties from the source zone + zone->setPlayerTowns(otherZone->getPlayerTowns()); + zone->setNeutralTowns(otherZone->getNeutralTowns()); + zone->setMatchTerrainToTown(otherZone->isMatchTerrainToTown()); + zone->setTownHints(otherZone->getTownHints()); + zone->setTownTypes(otherZone->getTownTypes()); + zone->setBannedTownTypes(otherZone->getBannedTownTypes()); + } +} + // TODO: Allow any integer size which does not match enum, as well void CRmgTemplate::serializeSize(JsonSerializeFormat & handler, int3 & value, const std::string & fieldName) { diff --git a/lib/rmg/CRmgTemplate.h b/lib/rmg/CRmgTemplate.h index 649f4878d..ac761d049 100644 --- a/lib/rmg/CRmgTemplate.h +++ b/lib/rmg/CRmgTemplate.h @@ -142,7 +142,22 @@ public: int castleDensity; // TODO: Copy from another zone once its randomized - TRmgTemplateZoneId sourceZone = NO_ZONE; + + TRmgTemplateZoneId townTypesLikeZone = NO_ZONE; + TRmgTemplateZoneId townTypesNotLikeZone = NO_ZONE; + TRmgTemplateZoneId townTypesRelatedToZoneTerrain = NO_ZONE; + }; + + class DLL_LINKAGE CTownHints + { + public: + CTownHints(); + // TODO: Make private + TRmgTemplateZoneId likeZone = NO_ZONE; + TRmgTemplateZoneId notLikeZone = NO_ZONE; + TRmgTemplateZoneId relatedToZoneTerrain = NO_ZONE; + + void serializeJson(JsonSerializeFormat & handler); }; ZoneOptions(); @@ -162,12 +177,21 @@ public: std::set getDefaultTerrainTypes() const; const CTownInfo & getPlayerTowns() const; + void setPlayerTowns(const CTownInfo & value); const CTownInfo & getNeutralTowns() const; - std::set getDefaultTownTypes() const; + void setNeutralTowns(const CTownInfo & value); + bool isMatchTerrainToTown() const; + void setMatchTerrainToTown(bool value); + const std::vector & getTownHints() const; + void setTownHints(const std::vector & value); std::set getTownTypes() const; + void setTownTypes(const std::set & value); + std::set getBannedTownTypes() const; + void setBannedTownTypes(const std::set & value); + + std::set getDefaultTownTypes() const; std::set getMonsterTypes() const; - void setTownTypes(const std::set & value); void setMonsterTypes(const std::set & value); void setMinesInfo(const std::map & value); @@ -192,7 +216,6 @@ public: EMonsterStrength::EMonsterStrength monsterStrength; bool areTownsSameType() const; - bool isMatchTerrainToTown() const; // Get a group of configured objects const std::vector & getBannedObjects() const; @@ -202,7 +225,8 @@ public: // Copy whole custom object config from another zone ObjectConfig getCustomObjects() const; void setCustomObjects(const ObjectConfig & value); - TRmgTemplateZoneId getCustomObjectsLikeZone() const; + TRmgTemplateZoneId getCustomObjectsLikeZone() const; + TRmgTemplateZoneId getTownsLikeZone() const; protected: TRmgTemplateZoneId id; @@ -218,6 +242,7 @@ protected: std::set terrainTypes; std::set bannedTerrains; bool townsAreSameType; + std::vector townHints; // For every town present on map std::set townTypes; std::set bannedTownTypes; @@ -231,6 +256,7 @@ protected: std::vector connectedZoneIds; //list of adjacent zone ids std::vector connectionDetails; //list of connections linked to that zone + TRmgTemplateZoneId townsLikeZone; TRmgTemplateZoneId minesLikeZone; TRmgTemplateZoneId terrainTypeLikeZone; TRmgTemplateZoneId treasureLikeZone; @@ -305,8 +331,7 @@ private: std::map inheritMineTypes(std::shared_ptr zone, uint32_t iteration = 0); std::vector inheritTreasureInfo(std::shared_ptr zone, uint32_t iteration = 0); - // TODO: Copy custom object settings - // TODO: Copy town type after source town is actually randomized + void inheritTownProperties(std::shared_ptr zone, uint32_t iteration = 0); void serializeSize(JsonSerializeFormat & handler, int3 & value, const std::string & fieldName); void serializePlayers(JsonSerializeFormat & handler, CPlayerCountRange & value, const std::string & fieldName); diff --git a/lib/rmg/modificators/TownPlacer.cpp b/lib/rmg/modificators/TownPlacer.cpp index 7322382c2..04c67c2d5 100644 --- a/lib/rmg/modificators/TownPlacer.cpp +++ b/lib/rmg/modificators/TownPlacer.cpp @@ -46,12 +46,16 @@ void TownPlacer::process() void TownPlacer::init() { + // TODO: Depend on other zones POSTFUNCTION(MinePlacer); POSTFUNCTION(RoadPlacer); } void TownPlacer::placeTowns(ObjectManager & manager) { + // TODO: Configurew each subseqquent town based on townHints + // TODO: First town should be set to type chosen by player + if(zone.getOwner() && ((zone.getType() == ETemplateZoneType::CPU_START) || (zone.getType() == ETemplateZoneType::PLAYER_START))) { //set zone types to player faction, generate main town @@ -72,7 +76,7 @@ void TownPlacer::placeTowns(ObjectManager & manager) else //no player - randomize town { player = PlayerColor::NEUTRAL; - zone.setTownType(getRandomTownType()); + zone.setTownType(getTownTypeFromHint(0)); } auto townFactory = LIBRARY->objtypeh->getHandlerFor(Obj::TOWN, zone.getTownType()); @@ -114,7 +118,7 @@ void TownPlacer::placeTowns(ObjectManager & manager) addNewTowns(zone.getPlayerTowns().getTownCount(), false, PlayerColor::NEUTRAL, manager); } } - else //randomize town types for any other zones as well + else //randomize town types for non-player zones { zone.setTownType(getRandomTownType()); } @@ -180,20 +184,59 @@ void TownPlacer::cleanupBoundaries(const rmg::Object & rmgObject) } } +FactionID TownPlacer::getTownTypeFromHint(size_t hintIndex) +{ + const auto & hints = zone.getTownHints(); + if(hints.size() <= hintIndex) + return zone.getTownType(); + + const auto & townHints = hints[hintIndex]; + FactionID subType = zone.getTownType(); + + if(townHints.likeZone != rmg::ZoneOptions::NO_ZONE) + { + // Copy directly from other zone + subType = map.getZones().at(townHints.likeZone)->getTownType(); + } + else if(townHints.notLikeZone != rmg::ZoneOptions::NO_ZONE) + { + // Exclude type rolled for other zone + auto townTypes = zone.getTownTypes(); + townTypes.erase(map.getZones().at(townHints.notLikeZone)->getTownType()); + zone.setTownTypes(townTypes); + + if(!townTypes.empty()) + subType = *RandomGeneratorUtil::nextItem(townTypes, zone.getRand()); + } + else if(townHints.relatedToZoneTerrain != rmg::ZoneOptions::NO_ZONE) + { + auto townTerrain = map.getZones().at(townHints.relatedToZoneTerrain)->getTerrainType(); + + auto townTypesAllowed = zone.getTownTypes(); + vstd::erase_if(townTypesAllowed, [townTerrain](FactionID type) + { + return (*LIBRARY->townh)[type]->getNativeTerrain() != townTerrain; + }); + zone.setTownTypes(townTypesAllowed); + + if(!townTypesAllowed.empty()) + subType = *RandomGeneratorUtil::nextItem(townTypesAllowed, zone.getRand()); + } + + return subType; +} + void TownPlacer::addNewTowns(int count, bool hasFort, const PlayerColor & player, ObjectManager & manager) { for(int i = 0; i < count; i++) { FactionID subType = zone.getTownType(); - if(totalTowns>0) + if(totalTowns > 0) { if(!zone.areTownsSameType()) { - if(!zone.getTownTypes().empty()) - subType = *RandomGeneratorUtil::nextItem(zone.getTownTypes(), zone.getRand()); - else - subType = *RandomGeneratorUtil::nextItem(zone.getDefaultTownTypes(), zone.getRand()); //it is possible to have zone with no towns allowed + subType = getTownTypeFromHint(totalTowns); } } diff --git a/lib/rmg/modificators/TownPlacer.h b/lib/rmg/modificators/TownPlacer.h index 75028093b..bdce46df3 100644 --- a/lib/rmg/modificators/TownPlacer.h +++ b/lib/rmg/modificators/TownPlacer.h @@ -29,6 +29,7 @@ protected: void cleanupBoundaries(const rmg::Object & rmgObject); void addNewTowns(int count, bool hasFort, const PlayerColor & player, ObjectManager & manager); FactionID getRandomTownType(bool matchUndergroundType = false); + FactionID getTownTypeFromHint(size_t hintIndex); void placeTowns(ObjectManager & manager); bool placeMines(ObjectManager & manager); int3 placeMainTown(ObjectManager & manager, CGTownInstance & town);