mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-22 22:13:35 +02:00
Merge pull request #4483 from vcmi/custom_objects_per_zone
Customizable objects in RMG zones
This commit is contained in:
commit
ffed9480e0
@ -13,6 +13,7 @@
|
||||
#include "../../lib/VCMI_Lib.h"
|
||||
#include "../../lib/CCreatureHandler.h"
|
||||
#include "../../lib/CHeroHandler.h"
|
||||
#include "../../lib/mapObjects/CompoundMapObjectID.h"
|
||||
#include "../../lib/mapObjectConstructors/AObjectTypeHandler.h"
|
||||
#include "../../lib/mapObjects/CGHeroInstance.h"
|
||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||
|
@ -22,6 +22,7 @@
|
||||
"minesLikeZone" : { "type" : "number" },
|
||||
"terrainTypeLikeZone" : { "type" : "number" },
|
||||
"treasureLikeZone" : { "type" : "number" },
|
||||
"customObjectsLikeZone" : { "type" : "number" },
|
||||
|
||||
"terrainTypes": {"$ref" : "#/definitions/stringArray"},
|
||||
"bannedTerrains": {"$ref" : "#/definitions/stringArray"},
|
||||
@ -49,6 +50,51 @@
|
||||
},
|
||||
"additionalProperties" : false
|
||||
}
|
||||
},
|
||||
"customObjects" : {
|
||||
"type" : "object",
|
||||
"properties": {
|
||||
"bannedCategories": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": ["all", "dwelling", "creatureBank", "randomArtifact", "bonus", "resource", "resourceGenerator", "spellScroll", "pandorasBox", "questArtifact", "seerHut"]
|
||||
}
|
||||
},
|
||||
"bannedObjects": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"commonObjects": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"rmg": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"value": {
|
||||
"type": "integer"
|
||||
},
|
||||
"rarity": {
|
||||
"type": "integer"
|
||||
},
|
||||
"zoneLimit": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": ["value", "rarity"]
|
||||
}
|
||||
},
|
||||
"required": ["id", "rmg"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -99,10 +99,13 @@
|
||||
"minesLikeZone" : 1,
|
||||
|
||||
// Treasures will have same configuration as in linked zone
|
||||
"treasureLikeZone" : 1
|
||||
"treasureLikeZone" : 1,
|
||||
|
||||
// Terrain type will have same configuration as in linked zone
|
||||
"terrainTypeLikeZone" : 3
|
||||
"terrainTypeLikeZone" : 3,
|
||||
|
||||
// Custom objects will have same configuration as in linked zone
|
||||
"customObjectsLikeZone" : 1,
|
||||
|
||||
// factions of monsters allowed on this zone
|
||||
"allowedMonsters" : ["inferno", "necropolis"]
|
||||
@ -130,6 +133,28 @@
|
||||
"density" : 5
|
||||
}
|
||||
...
|
||||
]
|
||||
],
|
||||
|
||||
// Objects with different configuration than default / set by mods
|
||||
"customObjects" :
|
||||
{
|
||||
// All of objects of this kind will be removed from zone
|
||||
// Possible values: "all", "none", "creatureBank", "bonus", "dwelling", "resource", "resourceGenerator", "spellScroll", "randomArtifact", "pandorasBox", "questArtifact", "seerHut", "other
|
||||
"bannedCategories" : ["all", "dwelling", "creatureBank", "other"],
|
||||
// Specify object types and subtypes
|
||||
"bannedObjects" :["core:object.randomArtifactRelic"],
|
||||
// Configure individual common objects - overrides banned objects
|
||||
"commonObjects":
|
||||
[
|
||||
{
|
||||
"id" : "core:object.creatureBank.dragonFlyHive",
|
||||
"rmg" : {
|
||||
"value" : 9000,
|
||||
"rarity" : 500,
|
||||
"zoneLimit" : 2
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
@ -184,6 +184,8 @@ set(lib_MAIN_SRCS
|
||||
rmg/TileInfo.cpp
|
||||
rmg/Zone.cpp
|
||||
rmg/Functions.cpp
|
||||
rmg/ObjectInfo.cpp
|
||||
rmg/ObjectConfig.cpp
|
||||
rmg/RmgMap.cpp
|
||||
rmg/PenroseTiling.cpp
|
||||
rmg/modificators/Modificator.cpp
|
||||
@ -510,6 +512,7 @@ set(lib_MAIN_HEADERS
|
||||
mapObjects/IOwnableObject.h
|
||||
mapObjects/MapObjects.h
|
||||
mapObjects/MiscObjects.h
|
||||
mapObjects/CompoundMapObjectID.h
|
||||
mapObjects/ObjectTemplate.h
|
||||
mapObjects/ObstacleSetHandler.h
|
||||
|
||||
@ -587,6 +590,8 @@ set(lib_MAIN_HEADERS
|
||||
rmg/RmgMap.h
|
||||
rmg/float3.h
|
||||
rmg/Functions.h
|
||||
rmg/ObjectInfo.h
|
||||
rmg/ObjectConfig.h
|
||||
rmg/PenroseTiling.h
|
||||
rmg/modificators/Modificator.h
|
||||
rmg/modificators/ObjectManager.h
|
||||
|
@ -40,6 +40,8 @@
|
||||
#include "../texts/CGeneralTextHandler.h"
|
||||
#include "../texts/CLegacyConfigParser.h"
|
||||
|
||||
#include <vstd/StringUtils.h>
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
CObjectClassesHandler::CObjectClassesHandler()
|
||||
@ -390,6 +392,62 @@ TObjectTypeHandler CObjectClassesHandler::getHandlerFor(CompoundMapObjectID comp
|
||||
return getHandlerFor(compoundIdentifier.primaryID, compoundIdentifier.secondaryID);
|
||||
}
|
||||
|
||||
CompoundMapObjectID CObjectClassesHandler::getCompoundIdentifier(const std::string & scope, const std::string & type, const std::string & subtype) const
|
||||
{
|
||||
std::optional<si32> id;
|
||||
if (scope.empty())
|
||||
{
|
||||
id = VLC->identifiers()->getIdentifier("object", type);
|
||||
}
|
||||
else
|
||||
{
|
||||
id = VLC->identifiers()->getIdentifier(scope, "object", type);
|
||||
}
|
||||
|
||||
if(id)
|
||||
{
|
||||
if (subtype.empty())
|
||||
return CompoundMapObjectID(id.value(), 0);
|
||||
|
||||
const auto & object = mapObjectTypes.at(id.value());
|
||||
std::optional<si32> subID = VLC->identifiers()->getIdentifier(scope, object->getJsonKey(), subtype);
|
||||
|
||||
if (subID)
|
||||
return CompoundMapObjectID(id.value(), subID.value());
|
||||
}
|
||||
|
||||
std::string errorString = "Failed to get id for object of type " + type + "." + subtype;
|
||||
logGlobal->error(errorString);
|
||||
throw std::runtime_error(errorString);
|
||||
}
|
||||
|
||||
CompoundMapObjectID CObjectClassesHandler::getCompoundIdentifier(const std::string & objectName) const
|
||||
{
|
||||
std::string subtype = "object"; //Default for objects with no subIds
|
||||
std::string type;
|
||||
|
||||
auto scopeAndFullName = vstd::splitStringToPair(objectName, ':');
|
||||
logGlobal->debug("scopeAndFullName: %s, %s", scopeAndFullName.first, scopeAndFullName.second);
|
||||
|
||||
auto typeAndName = vstd::splitStringToPair(scopeAndFullName.second, '.');
|
||||
logGlobal->debug("typeAndName: %s, %s", typeAndName.first, typeAndName.second);
|
||||
|
||||
auto nameAndSubtype = vstd::splitStringToPair(typeAndName.second, '.');
|
||||
logGlobal->debug("nameAndSubtype: %s, %s", nameAndSubtype.first, nameAndSubtype.second);
|
||||
|
||||
if (!nameAndSubtype.first.empty())
|
||||
{
|
||||
type = nameAndSubtype.first;
|
||||
subtype = nameAndSubtype.second;
|
||||
}
|
||||
else
|
||||
{
|
||||
type = typeAndName.second;
|
||||
}
|
||||
|
||||
return getCompoundIdentifier(boost::to_lower_copy(scopeAndFullName.first), type, subtype);
|
||||
}
|
||||
|
||||
std::set<MapObjectID> CObjectClassesHandler::knownObjects() const
|
||||
{
|
||||
std::set<MapObjectID> ret;
|
||||
@ -459,6 +517,18 @@ void CObjectClassesHandler::afterLoadFinalization()
|
||||
logGlobal->warn("No templates found for %s:%s", entry->getJsonKey(), obj->getJsonKey());
|
||||
}
|
||||
}
|
||||
|
||||
for(auto & entry : objectIdHandlers)
|
||||
{
|
||||
// Call function for each object id
|
||||
entry.second(entry.first);
|
||||
}
|
||||
}
|
||||
|
||||
void CObjectClassesHandler::resolveObjectCompoundId(const std::string & id, std::function<void(CompoundMapObjectID)> callback)
|
||||
{
|
||||
auto compoundId = getCompoundIdentifier(id);
|
||||
objectIdHandlers.push_back(std::make_pair(compoundId, callback));
|
||||
}
|
||||
|
||||
void CObjectClassesHandler::generateExtraMonolithsForRMG(ObjectClass * container)
|
||||
|
@ -9,7 +9,7 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "../constants/EntityIdentifiers.h"
|
||||
#include "../mapObjects/CompoundMapObjectID.h"
|
||||
#include "../IHandlerBase.h"
|
||||
#include "../json/JsonNode.h"
|
||||
|
||||
@ -19,27 +19,6 @@ class AObjectTypeHandler;
|
||||
class ObjectTemplate;
|
||||
struct SObjectSounds;
|
||||
|
||||
struct DLL_LINKAGE CompoundMapObjectID
|
||||
{
|
||||
si32 primaryID;
|
||||
si32 secondaryID;
|
||||
|
||||
CompoundMapObjectID(si32 primID, si32 secID) : primaryID(primID), secondaryID(secID) {};
|
||||
|
||||
bool operator<(const CompoundMapObjectID& other) const
|
||||
{
|
||||
if(this->primaryID != other.primaryID)
|
||||
return this->primaryID < other.primaryID;
|
||||
else
|
||||
return this->secondaryID < other.secondaryID;
|
||||
}
|
||||
|
||||
bool operator==(const CompoundMapObjectID& other) const
|
||||
{
|
||||
return (this->primaryID == other.primaryID) && (this->secondaryID == other.secondaryID);
|
||||
}
|
||||
};
|
||||
|
||||
class CGObjectInstance;
|
||||
|
||||
using TObjectTypeHandler = std::shared_ptr<AObjectTypeHandler>;
|
||||
@ -74,6 +53,8 @@ class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase, boost::noncopyabl
|
||||
/// map that is filled during construction with all known handlers. Not serializeable due to usage of std::function
|
||||
std::map<std::string, std::function<TObjectTypeHandler()> > handlerConstructors;
|
||||
|
||||
std::vector<std::pair<CompoundMapObjectID, std::function<void(CompoundMapObjectID)>>> objectIdHandlers;
|
||||
|
||||
/// container with H3 templates, used only during loading, no need to serialize it
|
||||
using TTemplatesContainer = std::multimap<std::pair<MapObjectID, MapObjectSubID>, std::shared_ptr<const ObjectTemplate>>;
|
||||
TTemplatesContainer legacyTemplates;
|
||||
@ -110,15 +91,19 @@ public:
|
||||
TObjectTypeHandler getHandlerFor(MapObjectID type, MapObjectSubID subtype) const;
|
||||
TObjectTypeHandler getHandlerFor(const std::string & scope, const std::string & type, const std::string & subtype) const;
|
||||
TObjectTypeHandler getHandlerFor(CompoundMapObjectID compoundIdentifier) const;
|
||||
CompoundMapObjectID getCompoundIdentifier(const std::string & scope, const std::string & type, const std::string & subtype) const;
|
||||
CompoundMapObjectID getCompoundIdentifier(const std::string & objectName) const;
|
||||
|
||||
std::string getObjectName(MapObjectID type, MapObjectSubID subtype) const;
|
||||
|
||||
SObjectSounds getObjectSounds(MapObjectID type, MapObjectSubID subtype) const;
|
||||
|
||||
void resolveObjectCompoundId(const std::string & id, std::function<void(CompoundMapObjectID)> callback);
|
||||
|
||||
/// Returns handler string describing the handler (for use in client)
|
||||
std::string getObjectHandlerName(MapObjectID type) const;
|
||||
|
||||
std::string getJsonKey(MapObjectID type) const;
|
||||
};
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
VCMI_LIB_NAMESPACE_END
|
@ -49,7 +49,10 @@ public:
|
||||
|
||||
virtual bool givesBonuses() const { return false; }
|
||||
|
||||
virtual bool hasGuards() const { return false; }
|
||||
|
||||
virtual ~IObjectInfo() = default;
|
||||
|
||||
};
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
37
lib/mapObjects/CompoundMapObjectID.h
Normal file
37
lib/mapObjects/CompoundMapObjectID.h
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* CompoundMapObjectID.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "../constants/EntityIdentifiers.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
struct DLL_LINKAGE CompoundMapObjectID
|
||||
{
|
||||
si32 primaryID;
|
||||
si32 secondaryID;
|
||||
|
||||
CompoundMapObjectID(si32 primID, si32 secID) : primaryID(primID), secondaryID(secID) {};
|
||||
|
||||
bool operator<(const CompoundMapObjectID& other) const
|
||||
{
|
||||
if(this->primaryID != other.primaryID)
|
||||
return this->primaryID < other.primaryID;
|
||||
else
|
||||
return this->secondaryID < other.secondaryID;
|
||||
}
|
||||
|
||||
bool operator==(const CompoundMapObjectID& other) const
|
||||
{
|
||||
return (this->primaryID == other.primaryID) && (this->secondaryID == other.secondaryID);
|
||||
}
|
||||
};
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
@ -508,6 +508,11 @@ bool ObjectTemplate::canBePlacedAt(TerrainId terrainID) const
|
||||
return vstd::contains(allowedTerrains, terrainID);
|
||||
}
|
||||
|
||||
CompoundMapObjectID ObjectTemplate::getCompoundID() const
|
||||
{
|
||||
return CompoundMapObjectID(id, subid);
|
||||
}
|
||||
|
||||
void ObjectTemplate::recalculate()
|
||||
{
|
||||
calculateWidth();
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "../int3.h"
|
||||
#include "../filesystem/ResourcePath.h"
|
||||
#include "../serializer/Serializeable.h"
|
||||
#include "../mapObjects/CompoundMapObjectID.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
@ -46,6 +47,7 @@ public:
|
||||
/// H3 ID/subID of this object
|
||||
MapObjectID id;
|
||||
MapObjectSubID subid;
|
||||
|
||||
/// print priority, objects with higher priority will be print first, below everything else
|
||||
si32 printPriority;
|
||||
/// animation file that should be used to display object
|
||||
@ -122,6 +124,8 @@ public:
|
||||
// Checks if object can be placed on specific terrain
|
||||
bool canBePlacedAt(TerrainId terrain) const;
|
||||
|
||||
CompoundMapObjectID getCompoundID() const;
|
||||
|
||||
ObjectTemplate();
|
||||
|
||||
void readTxt(CLegacyConfigParser & parser);
|
||||
|
@ -526,6 +526,11 @@ bool Rewardable::Info::givesBonuses() const
|
||||
return testForKey(parameters, "bonuses");
|
||||
}
|
||||
|
||||
bool Rewardable::Info::hasGuards() const
|
||||
{
|
||||
return testForKey(parameters, "guards");
|
||||
}
|
||||
|
||||
const JsonNode & Rewardable::Info::getParameters() const
|
||||
{
|
||||
return parameters;
|
||||
|
@ -68,6 +68,8 @@ public:
|
||||
|
||||
bool givesBonuses() const override;
|
||||
|
||||
bool hasGuards() const override;
|
||||
|
||||
void configureObject(Rewardable::Configuration & object, vstd::RNG & rng, IGameCallback * cb) const;
|
||||
|
||||
void init(const JsonNode & objectConfig, const std::string & objectTextID);
|
||||
|
@ -102,6 +102,7 @@ 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);
|
||||
}
|
||||
|
||||
ZoneOptions::ZoneOptions():
|
||||
@ -156,7 +157,7 @@ std::optional<int> ZoneOptions::getOwner() const
|
||||
return owner;
|
||||
}
|
||||
|
||||
const std::set<TerrainId> ZoneOptions::getTerrainTypes() const
|
||||
std::set<TerrainId> ZoneOptions::getTerrainTypes() const
|
||||
{
|
||||
if (terrainTypes.empty())
|
||||
{
|
||||
@ -191,7 +192,7 @@ std::set<FactionID> ZoneOptions::getDefaultTownTypes() const
|
||||
return VLC->townh->getDefaultAllowed();
|
||||
}
|
||||
|
||||
const std::set<FactionID> ZoneOptions::getTownTypes() const
|
||||
std::set<FactionID> ZoneOptions::getTownTypes() const
|
||||
{
|
||||
if (townTypes.empty())
|
||||
{
|
||||
@ -214,7 +215,7 @@ void ZoneOptions::setMonsterTypes(const std::set<FactionID> & value)
|
||||
monsterTypes = value;
|
||||
}
|
||||
|
||||
const std::set<FactionID> ZoneOptions::getMonsterTypes() const
|
||||
std::set<FactionID> ZoneOptions::getMonsterTypes() const
|
||||
{
|
||||
return vstd::difference(monsterTypes, bannedMonsters);
|
||||
}
|
||||
@ -250,7 +251,7 @@ void ZoneOptions::addTreasureInfo(const CTreasureInfo & value)
|
||||
vstd::amax(maxTreasureValue, value.max);
|
||||
}
|
||||
|
||||
const std::vector<CTreasureInfo> & ZoneOptions::getTreasureInfo() const
|
||||
std::vector<CTreasureInfo> ZoneOptions::getTreasureInfo() const
|
||||
{
|
||||
return treasureInfo;
|
||||
}
|
||||
@ -272,7 +273,22 @@ TRmgTemplateZoneId ZoneOptions::getTerrainTypeLikeZone() const
|
||||
|
||||
TRmgTemplateZoneId ZoneOptions::getTreasureLikeZone() const
|
||||
{
|
||||
return treasureLikeZone;
|
||||
return treasureLikeZone;
|
||||
}
|
||||
|
||||
ObjectConfig ZoneOptions::getCustomObjects() const
|
||||
{
|
||||
return objectConfig;
|
||||
}
|
||||
|
||||
void ZoneOptions::setCustomObjects(const ObjectConfig & value)
|
||||
{
|
||||
objectConfig = value;
|
||||
}
|
||||
|
||||
TRmgTemplateZoneId ZoneOptions::getCustomObjectsLikeZone() const
|
||||
{
|
||||
return customObjectsLikeZone;
|
||||
}
|
||||
|
||||
void ZoneOptions::addConnection(const ZoneConnection & connection)
|
||||
@ -334,6 +350,7 @@ void ZoneOptions::serializeJson(JsonSerializeFormat & handler)
|
||||
SERIALIZE_ZONE_LINK(minesLikeZone);
|
||||
SERIALIZE_ZONE_LINK(terrainTypeLikeZone);
|
||||
SERIALIZE_ZONE_LINK(treasureLikeZone);
|
||||
SERIALIZE_ZONE_LINK(customObjectsLikeZone);
|
||||
|
||||
#undef SERIALIZE_ZONE_LINK
|
||||
|
||||
@ -398,6 +415,8 @@ void ZoneOptions::serializeJson(JsonSerializeFormat & handler)
|
||||
handler.serializeInt(GameConstants::RESOURCE_NAMES[idx], mines[idx], 0);
|
||||
}
|
||||
}
|
||||
|
||||
handler.serializeStruct("customObjects", objectConfig);
|
||||
}
|
||||
|
||||
ZoneConnection::ZoneConnection():
|
||||
@ -759,53 +778,29 @@ const JsonNode & CRmgTemplate::getMapSettings() const
|
||||
return *mapSettings;
|
||||
}
|
||||
|
||||
std::set<TerrainId> CRmgTemplate::inheritTerrainType(std::shared_ptr<ZoneOptions> zone, uint32_t iteration /* = 0 */)
|
||||
template<typename T>
|
||||
T CRmgTemplate::inheritZoneProperty(std::shared_ptr<rmg::ZoneOptions> zone,
|
||||
T (rmg::ZoneOptions::*getter)() const,
|
||||
void (rmg::ZoneOptions::*setter)(const T&),
|
||||
TRmgTemplateZoneId (rmg::ZoneOptions::*inheritFrom)() const,
|
||||
const std::string& propertyString,
|
||||
uint32_t iteration)
|
||||
{
|
||||
if (iteration >= 50)
|
||||
{
|
||||
logGlobal->error("Infinite recursion for terrain types detected in template %s", name);
|
||||
return std::set<TerrainId>();
|
||||
logGlobal->error("Infinite recursion for %s detected in template %s", propertyString, name);
|
||||
return T();
|
||||
}
|
||||
if (zone->getTerrainTypeLikeZone() != ZoneOptions::NO_ZONE)
|
||||
|
||||
if (((*zone).*inheritFrom)() != rmg::ZoneOptions::NO_ZONE)
|
||||
{
|
||||
iteration++;
|
||||
const auto otherZone = zones.at(zone->getTerrainTypeLikeZone());
|
||||
zone->setTerrainTypes(inheritTerrainType(otherZone, iteration));
|
||||
const auto otherZone = zones.at(((*zone).*inheritFrom)());
|
||||
T inheritedValue = inheritZoneProperty(otherZone, getter, setter, inheritFrom, propertyString, iteration);
|
||||
((*zone).*setter)(inheritedValue);
|
||||
}
|
||||
//This implicitly excludes banned terrains
|
||||
return zone->getTerrainTypes();
|
||||
}
|
||||
|
||||
std::map<TResource, ui16> CRmgTemplate::inheritMineTypes(std::shared_ptr<ZoneOptions> zone, uint32_t iteration /* = 0 */)
|
||||
{
|
||||
if (iteration >= 50)
|
||||
{
|
||||
logGlobal->error("Infinite recursion for mine types detected in template %s", name);
|
||||
return std::map<TResource, ui16>();
|
||||
}
|
||||
if (zone->getMinesLikeZone() != ZoneOptions::NO_ZONE)
|
||||
{
|
||||
iteration++;
|
||||
const auto otherZone = zones.at(zone->getMinesLikeZone());
|
||||
zone->setMinesInfo(inheritMineTypes(otherZone, iteration));
|
||||
}
|
||||
return zone->getMinesInfo();
|
||||
}
|
||||
|
||||
std::vector<CTreasureInfo> CRmgTemplate::inheritTreasureInfo(std::shared_ptr<ZoneOptions> zone, uint32_t iteration /* = 0 */)
|
||||
{
|
||||
if (iteration >= 50)
|
||||
{
|
||||
logGlobal->error("Infinite recursion for treasures detected in template %s", name);
|
||||
return std::vector<CTreasureInfo>();
|
||||
}
|
||||
if (zone->getTreasureLikeZone() != ZoneOptions::NO_ZONE)
|
||||
{
|
||||
iteration++;
|
||||
const auto otherZone = zones.at(zone->getTreasureLikeZone());
|
||||
zone->setTreasureInfo(inheritTreasureInfo(otherZone, iteration));
|
||||
}
|
||||
return zone->getTreasureInfo();
|
||||
|
||||
return ((*zone).*getter)();
|
||||
}
|
||||
|
||||
void CRmgTemplate::afterLoad()
|
||||
@ -814,12 +809,32 @@ void CRmgTemplate::afterLoad()
|
||||
{
|
||||
auto zone = idAndZone.second;
|
||||
|
||||
//Inherit properties recursively.
|
||||
inheritTerrainType(zone);
|
||||
inheritMineTypes(zone);
|
||||
inheritTreasureInfo(zone);
|
||||
// Inherit properties recursively
|
||||
inheritZoneProperty(zone,
|
||||
&rmg::ZoneOptions::getTerrainTypes,
|
||||
&rmg::ZoneOptions::setTerrainTypes,
|
||||
&rmg::ZoneOptions::getTerrainTypeLikeZone,
|
||||
"terrain types");
|
||||
|
||||
inheritZoneProperty(zone,
|
||||
&rmg::ZoneOptions::getMinesInfo,
|
||||
&rmg::ZoneOptions::setMinesInfo,
|
||||
&rmg::ZoneOptions::getMinesLikeZone,
|
||||
"mine types");
|
||||
|
||||
inheritZoneProperty(zone,
|
||||
&rmg::ZoneOptions::getTreasureInfo,
|
||||
&rmg::ZoneOptions::setTreasureInfo,
|
||||
&rmg::ZoneOptions::getTreasureLikeZone,
|
||||
"treasure info");
|
||||
|
||||
//TODO: Inherit monster types as well
|
||||
inheritZoneProperty(zone,
|
||||
&rmg::ZoneOptions::getCustomObjects,
|
||||
&rmg::ZoneOptions::setCustomObjects,
|
||||
&rmg::ZoneOptions::getCustomObjectsLikeZone,
|
||||
"custom objects");
|
||||
|
||||
//TODO: Inherit monster types as well
|
||||
auto monsterTypes = zone->getMonsterTypes();
|
||||
if (monsterTypes.empty())
|
||||
{
|
||||
@ -848,6 +863,7 @@ void CRmgTemplate::afterLoad()
|
||||
allowedWaterContent.erase(EWaterContent::RANDOM);
|
||||
}
|
||||
|
||||
// TODO: Allow any integer size which does not match enum, as well
|
||||
void CRmgTemplate::serializeSize(JsonSerializeFormat & handler, int3 & value, const std::string & fieldName)
|
||||
{
|
||||
static const std::map<std::string, int3> sizeMapping =
|
||||
@ -916,5 +932,19 @@ void CRmgTemplate::serializePlayers(JsonSerializeFormat & handler, CPlayerCountR
|
||||
value.fromString(encodedValue);
|
||||
}
|
||||
|
||||
const std::vector<CompoundMapObjectID> & ZoneOptions::getBannedObjects() const
|
||||
{
|
||||
return objectConfig.getBannedObjects();
|
||||
}
|
||||
|
||||
const std::vector<ObjectConfig::EObjectCategory> & ZoneOptions::getBannedObjectCategories() const
|
||||
{
|
||||
return objectConfig.getBannedObjectCategories();
|
||||
}
|
||||
|
||||
const std::vector<ObjectInfo> & ZoneOptions::getConfiguredObjects() const
|
||||
{
|
||||
return objectConfig.getConfiguredObjects();
|
||||
}
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
@ -13,10 +13,14 @@
|
||||
#include "../int3.h"
|
||||
#include "../GameConstants.h"
|
||||
#include "../ResourceSet.h"
|
||||
#include "ObjectInfo.h"
|
||||
#include "ObjectConfig.h"
|
||||
#include "../mapObjectConstructors/CObjectClassesHandler.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class JsonSerializeFormat;
|
||||
struct CompoundMapObjectID;
|
||||
|
||||
enum class ETemplateZoneType
|
||||
{
|
||||
@ -132,6 +136,9 @@ public:
|
||||
int castleCount;
|
||||
int townDensity;
|
||||
int castleDensity;
|
||||
|
||||
// TODO: Copy from another zone once its randomized
|
||||
TRmgTemplateZoneId sourceZone = NO_ZONE;
|
||||
};
|
||||
|
||||
ZoneOptions();
|
||||
@ -146,15 +153,15 @@ public:
|
||||
void setSize(int value);
|
||||
std::optional<int> getOwner() const;
|
||||
|
||||
const std::set<TerrainId> getTerrainTypes() const;
|
||||
std::set<TerrainId> getTerrainTypes() const;
|
||||
void setTerrainTypes(const std::set<TerrainId> & value);
|
||||
std::set<TerrainId> getDefaultTerrainTypes() const;
|
||||
|
||||
const CTownInfo & getPlayerTowns() const;
|
||||
const CTownInfo & getNeutralTowns() const;
|
||||
std::set<FactionID> getDefaultTownTypes() const;
|
||||
const std::set<FactionID> getTownTypes() const;
|
||||
const std::set<FactionID> getMonsterTypes() const;
|
||||
std::set<FactionID> getTownTypes() const;
|
||||
std::set<FactionID> getMonsterTypes() const;
|
||||
|
||||
void setTownTypes(const std::set<FactionID> & value);
|
||||
void setMonsterTypes(const std::set<FactionID> & value);
|
||||
@ -164,7 +171,7 @@ public:
|
||||
|
||||
void setTreasureInfo(const std::vector<CTreasureInfo> & value);
|
||||
void addTreasureInfo(const CTreasureInfo & value);
|
||||
const std::vector<CTreasureInfo> & getTreasureInfo() const;
|
||||
std::vector<CTreasureInfo> getTreasureInfo() const;
|
||||
ui32 getMaxTreasureValue() const;
|
||||
void recalculateMaxTreasureValue();
|
||||
|
||||
@ -183,12 +190,24 @@ public:
|
||||
bool areTownsSameType() const;
|
||||
bool isMatchTerrainToTown() const;
|
||||
|
||||
// Get a group of configured objects
|
||||
const std::vector<CompoundMapObjectID> & getBannedObjects() const;
|
||||
const std::vector<ObjectConfig::EObjectCategory> & getBannedObjectCategories() const;
|
||||
const std::vector<ObjectInfo> & getConfiguredObjects() const;
|
||||
|
||||
// Copy whole custom object config from another zone
|
||||
ObjectConfig getCustomObjects() const;
|
||||
void setCustomObjects(const ObjectConfig & value);
|
||||
TRmgTemplateZoneId getCustomObjectsLikeZone() const;
|
||||
|
||||
protected:
|
||||
TRmgTemplateZoneId id;
|
||||
ETemplateZoneType type;
|
||||
int size;
|
||||
ui32 maxTreasureValue;
|
||||
std::optional<int> owner;
|
||||
|
||||
ObjectConfig objectConfig;
|
||||
CTownInfo playerTowns;
|
||||
CTownInfo neutralTowns;
|
||||
bool matchTerrainToTown;
|
||||
@ -211,6 +230,7 @@ protected:
|
||||
TRmgTemplateZoneId minesLikeZone;
|
||||
TRmgTemplateZoneId terrainTypeLikeZone;
|
||||
TRmgTemplateZoneId treasureLikeZone;
|
||||
TRmgTemplateZoneId customObjectsLikeZone;
|
||||
};
|
||||
|
||||
}
|
||||
@ -280,8 +300,21 @@ private:
|
||||
std::set<TerrainId> inheritTerrainType(std::shared_ptr<rmg::ZoneOptions> zone, uint32_t iteration = 0);
|
||||
std::map<TResource, ui16> inheritMineTypes(std::shared_ptr<rmg::ZoneOptions> zone, uint32_t iteration = 0);
|
||||
std::vector<CTreasureInfo> inheritTreasureInfo(std::shared_ptr<rmg::ZoneOptions> zone, uint32_t iteration = 0);
|
||||
|
||||
// TODO: Copy custom object settings
|
||||
// TODO: Copy town type after source town is actually randomized
|
||||
|
||||
void serializeSize(JsonSerializeFormat & handler, int3 & value, const std::string & fieldName);
|
||||
void serializePlayers(JsonSerializeFormat & handler, CPlayerCountRange & value, const std::string & fieldName);
|
||||
|
||||
template<typename T>
|
||||
T inheritZoneProperty(std::shared_ptr<rmg::ZoneOptions> zone,
|
||||
T (rmg::ZoneOptions::*getter)() const,
|
||||
void (rmg::ZoneOptions::*setter)(const T&),
|
||||
TRmgTemplateZoneId (rmg::ZoneOptions::*inheritFrom)() const,
|
||||
const std::string& propertyString,
|
||||
uint32_t iteration = 0);
|
||||
|
||||
};
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
VCMI_LIB_NAMESPACE_END
|
189
lib/rmg/ObjectConfig.cpp
Normal file
189
lib/rmg/ObjectConfig.cpp
Normal file
@ -0,0 +1,189 @@
|
||||
/*
|
||||
* ObjectConfig.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
|
||||
#include "StdInc.h"
|
||||
#include <boost/bimap.hpp>
|
||||
#include <boost/assign.hpp>
|
||||
#include "ObjectInfo.h"
|
||||
#include "ObjectConfig.h"
|
||||
|
||||
#include "../VCMI_Lib.h"
|
||||
#include "../mapObjectConstructors/CObjectClassesHandler.h"
|
||||
#include "../mapObjectConstructors/AObjectTypeHandler.h"
|
||||
#include "../serializer/JsonSerializeFormat.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
void ObjectConfig::addBannedObject(const CompoundMapObjectID & objid)
|
||||
{
|
||||
// FIXME: We do not need to store the object info, just the id
|
||||
|
||||
bannedObjects.push_back(objid);
|
||||
|
||||
logGlobal->info("Banned object of type %d.%d", objid.primaryID, objid.secondaryID);
|
||||
}
|
||||
|
||||
void ObjectConfig::addCustomObject(const ObjectInfo & object, const CompoundMapObjectID & objid)
|
||||
{
|
||||
customObjects.push_back(object);
|
||||
auto & lastObject = customObjects.back();
|
||||
lastObject.setAllTemplates(objid.primaryID, objid.secondaryID);
|
||||
|
||||
assert(lastObject.templates.size() > 0);
|
||||
logGlobal->info("Added custom object of type %d.%d", objid.primaryID, objid.secondaryID);
|
||||
}
|
||||
|
||||
void ObjectConfig::serializeJson(JsonSerializeFormat & handler)
|
||||
{
|
||||
// TODO: We need serializer utility for list of enum values
|
||||
|
||||
static const boost::bimap<EObjectCategory, std::string> OBJECT_CATEGORY_STRINGS = boost::assign::list_of<boost::bimap<EObjectCategory, std::string>::relation>
|
||||
(EObjectCategory::OTHER, "other")
|
||||
(EObjectCategory::ALL, "all")
|
||||
(EObjectCategory::NONE, "none")
|
||||
(EObjectCategory::CREATURE_BANK, "creatureBank")
|
||||
(EObjectCategory::BONUS, "bonus")
|
||||
(EObjectCategory::DWELLING, "dwelling")
|
||||
(EObjectCategory::RESOURCE, "resource")
|
||||
(EObjectCategory::RESOURCE_GENERATOR, "resourceGenerator")
|
||||
(EObjectCategory::SPELL_SCROLL, "spellScroll")
|
||||
(EObjectCategory::RANDOM_ARTIFACT, "randomArtifact")
|
||||
(EObjectCategory::PANDORAS_BOX, "pandorasBox")
|
||||
(EObjectCategory::QUEST_ARTIFACT, "questArtifact")
|
||||
(EObjectCategory::SEER_HUT, "seerHut");
|
||||
|
||||
|
||||
// TODO: Separate into individual methods to enforce RAII destruction?
|
||||
{
|
||||
auto categories = handler.enterArray("bannedCategories");
|
||||
if (handler.saving)
|
||||
{
|
||||
for (const auto& category : bannedObjectCategories)
|
||||
{
|
||||
auto str = OBJECT_CATEGORY_STRINGS.left.at(category);
|
||||
categories.serializeString(categories.size(), str);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<std::string> categoryNames;
|
||||
categories.serializeArray(categoryNames);
|
||||
|
||||
for (const auto & categoryName : categoryNames)
|
||||
{
|
||||
auto it = OBJECT_CATEGORY_STRINGS.right.find(categoryName);
|
||||
if (it != OBJECT_CATEGORY_STRINGS.right.end())
|
||||
{
|
||||
bannedObjectCategories.push_back(it->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Doesn't seem to use this field at all
|
||||
|
||||
{
|
||||
auto bannedObjectData = handler.enterArray("bannedObjects");
|
||||
if (handler.saving)
|
||||
{
|
||||
|
||||
// FIXME: Do we even need to serialize / store banned objects?
|
||||
/*
|
||||
for (const auto & object : bannedObjects)
|
||||
{
|
||||
// TODO: Translate id back to string?
|
||||
|
||||
|
||||
JsonNode node;
|
||||
node.String() = VLC->objtypeh->getHandlerFor(object.primaryID, object.secondaryID);
|
||||
// TODO: Check if AI-generated code is right
|
||||
|
||||
|
||||
}
|
||||
// handler.serializeRaw("bannedObjects", node, std::nullopt);
|
||||
|
||||
*/
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<std::string> objectNames;
|
||||
bannedObjectData.serializeArray(objectNames);
|
||||
|
||||
for (const auto & objectName : objectNames)
|
||||
{
|
||||
VLC->objtypeh->resolveObjectCompoundId(objectName,
|
||||
[this](CompoundMapObjectID objid)
|
||||
{
|
||||
addBannedObject(objid);
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto commonObjectData = handler.getCurrent()["commonObjects"].Vector();
|
||||
if (handler.saving)
|
||||
{
|
||||
|
||||
//TODO?
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto & objectConfig : commonObjectData)
|
||||
{
|
||||
auto objectName = objectConfig["id"].String();
|
||||
auto rmg = objectConfig["rmg"].Struct();
|
||||
|
||||
// TODO: Use common code with default rmg config
|
||||
auto objectValue = rmg["value"].Integer();
|
||||
auto objectProbability = rmg["rarity"].Integer();
|
||||
|
||||
auto objectMaxPerZone = rmg["zoneLimit"].Integer();
|
||||
if (objectMaxPerZone == 0)
|
||||
{
|
||||
objectMaxPerZone = std::numeric_limits<int>::max();
|
||||
}
|
||||
|
||||
VLC->objtypeh->resolveObjectCompoundId(objectName,
|
||||
|
||||
[this, objectValue, objectProbability, objectMaxPerZone](CompoundMapObjectID objid)
|
||||
{
|
||||
ObjectInfo object(objid.primaryID, objid.secondaryID);
|
||||
|
||||
// TODO: Configure basic generateObject function
|
||||
|
||||
object.value = objectValue;
|
||||
object.probability = objectProbability;
|
||||
object.maxPerZone = objectMaxPerZone;
|
||||
addCustomObject(object, objid);
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<ObjectInfo> & ObjectConfig::getConfiguredObjects() const
|
||||
{
|
||||
return customObjects;
|
||||
}
|
||||
|
||||
const std::vector<CompoundMapObjectID> & ObjectConfig::getBannedObjects() const
|
||||
{
|
||||
return bannedObjects;
|
||||
}
|
||||
|
||||
const std::vector<ObjectConfig::EObjectCategory> & ObjectConfig::getBannedObjectCategories() const
|
||||
{
|
||||
return bannedObjectCategories;
|
||||
}
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
57
lib/rmg/ObjectConfig.h
Normal file
57
lib/rmg/ObjectConfig.h
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* ObjectInfo.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../mapObjects/CompoundMapObjectID.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class DLL_LINKAGE ObjectConfig
|
||||
{
|
||||
public:
|
||||
|
||||
enum class EObjectCategory
|
||||
{
|
||||
OTHER = -2,
|
||||
ALL = -1,
|
||||
NONE = 0,
|
||||
CREATURE_BANK = 1,
|
||||
BONUS,
|
||||
DWELLING,
|
||||
RESOURCE,
|
||||
RESOURCE_GENERATOR,
|
||||
SPELL_SCROLL,
|
||||
RANDOM_ARTIFACT,
|
||||
PANDORAS_BOX,
|
||||
QUEST_ARTIFACT,
|
||||
SEER_HUT
|
||||
};
|
||||
|
||||
void addBannedObject(const CompoundMapObjectID & objid);
|
||||
void addCustomObject(const ObjectInfo & object, const CompoundMapObjectID & objid);
|
||||
void clearBannedObjects();
|
||||
void clearCustomObjects();
|
||||
const std::vector<CompoundMapObjectID> & getBannedObjects() const;
|
||||
const std::vector<EObjectCategory> & getBannedObjectCategories() const;
|
||||
const std::vector<ObjectInfo> & getConfiguredObjects() const;
|
||||
|
||||
void serializeJson(JsonSerializeFormat & handler);
|
||||
private:
|
||||
// TODO: Add convenience method for banning objects by name
|
||||
std::vector<CompoundMapObjectID> bannedObjects;
|
||||
std::vector<EObjectCategory> bannedObjectCategories;
|
||||
|
||||
// TODO: In what format should I store custom objects?
|
||||
// Need to convert map serialization format to ObjectInfo
|
||||
std::vector<ObjectInfo> customObjects;
|
||||
};
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
85
lib/rmg/ObjectInfo.cpp
Normal file
85
lib/rmg/ObjectInfo.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* ObjectInfo.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
|
||||
#include "StdInc.h"
|
||||
#include "ObjectInfo.h"
|
||||
|
||||
#include "../VCMI_Lib.h"
|
||||
#include "../mapObjectConstructors/CObjectClassesHandler.h"
|
||||
#include "../mapObjectConstructors/AObjectTypeHandler.h"
|
||||
#include "../serializer/JsonSerializeFormat.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
ObjectInfo::ObjectInfo(si32 ID, si32 subID):
|
||||
primaryID(ID),
|
||||
secondaryID(subID),
|
||||
destroyObject([](CGObjectInstance * obj){}),
|
||||
maxPerZone(std::numeric_limits<ui32>::max())
|
||||
{
|
||||
}
|
||||
|
||||
ObjectInfo::ObjectInfo(CompoundMapObjectID id):
|
||||
ObjectInfo(id.primaryID, id.secondaryID)
|
||||
{
|
||||
}
|
||||
|
||||
ObjectInfo::ObjectInfo(const ObjectInfo & other)
|
||||
{
|
||||
templates = other.templates;
|
||||
primaryID = other.primaryID;
|
||||
secondaryID = other.secondaryID;
|
||||
value = other.value;
|
||||
probability = other.probability;
|
||||
maxPerZone = other.maxPerZone;
|
||||
generateObject = other.generateObject;
|
||||
destroyObject = other.destroyObject;
|
||||
}
|
||||
|
||||
ObjectInfo & ObjectInfo::operator=(const ObjectInfo & other)
|
||||
{
|
||||
if (this == &other)
|
||||
return *this;
|
||||
|
||||
templates = other.templates;
|
||||
primaryID = other.primaryID;
|
||||
secondaryID = other.secondaryID;
|
||||
value = other.value;
|
||||
probability = other.probability;
|
||||
maxPerZone = other.maxPerZone;
|
||||
generateObject = other.generateObject;
|
||||
destroyObject = other.destroyObject;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void ObjectInfo::setAllTemplates(MapObjectID type, MapObjectSubID subtype)
|
||||
{
|
||||
auto templHandler = VLC->objtypeh->getHandlerFor(type, subtype);
|
||||
if(!templHandler)
|
||||
return;
|
||||
|
||||
templates = templHandler->getTemplates();
|
||||
}
|
||||
|
||||
void ObjectInfo::setTemplates(MapObjectID type, MapObjectSubID subtype, TerrainId terrainType)
|
||||
{
|
||||
auto templHandler = VLC->objtypeh->getHandlerFor(type, subtype);
|
||||
if(!templHandler)
|
||||
return;
|
||||
|
||||
templates = templHandler->getTemplates(terrainType);
|
||||
}
|
||||
|
||||
CompoundMapObjectID ObjectInfo::getCompoundID() const
|
||||
{
|
||||
return CompoundMapObjectID(primaryID, secondaryID);
|
||||
}
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
45
lib/rmg/ObjectInfo.h
Normal file
45
lib/rmg/ObjectInfo.h
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* ObjectInfo.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../mapObjects/ObjectTemplate.h"
|
||||
#include "../mapObjects/CompoundMapObjectID.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
struct CompoundMapObjectID;
|
||||
class CGObjectInstance;
|
||||
|
||||
struct DLL_LINKAGE ObjectInfo
|
||||
{
|
||||
ObjectInfo(si32 ID, si32 subID);
|
||||
ObjectInfo(CompoundMapObjectID id);
|
||||
ObjectInfo(const ObjectInfo & other);
|
||||
ObjectInfo & operator=(const ObjectInfo & other);
|
||||
|
||||
std::vector<std::shared_ptr<const ObjectTemplate>> templates;
|
||||
si32 primaryID;
|
||||
si32 secondaryID;
|
||||
ui32 value = 0;
|
||||
ui16 probability = 0;
|
||||
ui32 maxPerZone = 1;
|
||||
//ui32 maxPerMap; //unused
|
||||
std::function<CGObjectInstance *()> generateObject;
|
||||
std::function<void(CGObjectInstance *)> destroyObject;
|
||||
|
||||
void setAllTemplates(MapObjectID type, MapObjectSubID subtype);
|
||||
void setTemplates(MapObjectID type, MapObjectSubID subtype, TerrainId terrain);
|
||||
|
||||
CompoundMapObjectID getCompoundID() const;
|
||||
//bool matchesId(const CompoundMapObjectID & id) const;
|
||||
};
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
@ -45,7 +45,6 @@ void ObjectDistributor::init()
|
||||
|
||||
void ObjectDistributor::distributeLimitedObjects()
|
||||
{
|
||||
ObjectInfo oi;
|
||||
auto zones = map.getZones();
|
||||
|
||||
for (auto primaryID : VLC->objtypeh->knownObjects())
|
||||
@ -81,6 +80,8 @@ void ObjectDistributor::distributeLimitedObjects()
|
||||
RandomGeneratorUtil::randomShuffle(matchingZones, zone.getRand());
|
||||
for (auto& zone : matchingZones)
|
||||
{
|
||||
ObjectInfo oi(primaryID, secondaryID);
|
||||
|
||||
oi.generateObject = [cb=map.mapInstance->cb, primaryID, secondaryID]() -> CGObjectInstance *
|
||||
{
|
||||
return VLC->objtypeh->getHandlerFor(primaryID, secondaryID)->create(cb, nullptr);
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
#include "StdInc.h"
|
||||
#include "TreasurePlacer.h"
|
||||
#include "../CRmgTemplate.h"
|
||||
#include "../CMapGenerator.h"
|
||||
#include "../Functions.h"
|
||||
#include "ObjectManager.h"
|
||||
@ -24,6 +25,7 @@
|
||||
#include "../../mapObjectConstructors/AObjectTypeHandler.h"
|
||||
#include "../../mapObjectConstructors/CObjectClassesHandler.h"
|
||||
#include "../../mapObjectConstructors/DwellingInstanceConstructor.h"
|
||||
#include "../../rewardable/Info.h"
|
||||
#include "../../mapObjects/CGHeroInstance.h"
|
||||
#include "../../mapObjects/CGPandoraBox.h"
|
||||
#include "../../mapObjects/CQuest.h"
|
||||
@ -37,15 +39,29 @@
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
ObjectInfo::ObjectInfo():
|
||||
destroyObject([](CGObjectInstance * obj){})
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void TreasurePlacer::process()
|
||||
{
|
||||
if (zone.getMaxTreasureValue() == 0)
|
||||
{
|
||||
//No treasures at all
|
||||
return;
|
||||
}
|
||||
|
||||
tierValues = generator.getConfig().pandoraCreatureValues;
|
||||
// Add all native creatures
|
||||
for(auto const & cre : VLC->creh->objects)
|
||||
{
|
||||
if(!cre->special && cre->getFaction() == zone.getTownType())
|
||||
{
|
||||
creatures.push_back(cre.get());
|
||||
}
|
||||
}
|
||||
|
||||
// Get default objects
|
||||
addAllPossibleObjects();
|
||||
// Override with custom objects
|
||||
objects.patchWithZoneConfig(zone, this);
|
||||
|
||||
auto * m = zone.getModificator<ObjectManager>();
|
||||
if(m)
|
||||
createTreasures(*m);
|
||||
@ -62,14 +78,37 @@ void TreasurePlacer::init()
|
||||
|
||||
void TreasurePlacer::addObjectToRandomPool(const ObjectInfo& oi)
|
||||
{
|
||||
if (oi.templates.empty())
|
||||
{
|
||||
logGlobal->error("Attempt to add ObjectInfo with no templates! Value: %d", oi.value);
|
||||
return;
|
||||
}
|
||||
if (!oi.generateObject)
|
||||
{
|
||||
logGlobal->error("Attempt to add ObjectInfo with no generateObject function! Value: %d", oi.value);
|
||||
return;
|
||||
}
|
||||
if (!oi.maxPerZone)
|
||||
{
|
||||
logGlobal->warn("Attempt to add ObjectInfo with 0 maxPerZone! Value: %d", oi.value);
|
||||
return;
|
||||
}
|
||||
RecursiveLock lock(externalAccessMutex);
|
||||
possibleObjects.push_back(oi);
|
||||
objects.addObject(oi);
|
||||
}
|
||||
|
||||
void TreasurePlacer::addAllPossibleObjects()
|
||||
{
|
||||
ObjectInfo oi;
|
||||
|
||||
addCommonObjects();
|
||||
addDwellings();
|
||||
addPandoraBoxes();
|
||||
addSeerHuts();
|
||||
addPrisons();
|
||||
addScrolls();
|
||||
}
|
||||
|
||||
void TreasurePlacer::addCommonObjects()
|
||||
{
|
||||
for(auto primaryID : VLC->objtypeh->knownObjects())
|
||||
{
|
||||
for(auto secondaryID : VLC->objtypeh->knownSubObjects(primaryID))
|
||||
@ -83,21 +122,31 @@ void TreasurePlacer::addAllPossibleObjects()
|
||||
//Skip objects with per-map limit here
|
||||
continue;
|
||||
}
|
||||
ObjectInfo oi(primaryID, secondaryID);
|
||||
setBasicProperties(oi, CompoundMapObjectID(primaryID, secondaryID));
|
||||
|
||||
oi.generateObject = [this, primaryID, secondaryID]() -> CGObjectInstance *
|
||||
{
|
||||
return VLC->objtypeh->getHandlerFor(primaryID, secondaryID)->create(map.mapInstance->cb, nullptr);
|
||||
};
|
||||
oi.value = rmgInfo.value;
|
||||
oi.probability = rmgInfo.rarity;
|
||||
oi.setTemplates(primaryID, secondaryID, zone.getTerrainType());
|
||||
oi.maxPerZone = rmgInfo.zoneLimit;
|
||||
|
||||
if(!oi.templates.empty())
|
||||
addObjectToRandomPool(oi);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TreasurePlacer::setBasicProperties(ObjectInfo & oi, CompoundMapObjectID objid) const
|
||||
{
|
||||
oi.generateObject = [this, objid]() -> CGObjectInstance *
|
||||
{
|
||||
return VLC->objtypeh->getHandlerFor(objid)->create(map.mapInstance->cb, nullptr);
|
||||
};
|
||||
oi.setTemplates(objid.primaryID, objid.secondaryID, zone.getTerrainType());
|
||||
}
|
||||
|
||||
void TreasurePlacer::addPrisons()
|
||||
{
|
||||
//Generate Prison on water only if it has a template
|
||||
auto prisonTemplates = VLC->objtypeh->getHandlerFor(Obj::PRISON, 0)->getTemplates(zone.getTerrainType());
|
||||
if (!prisonTemplates.empty())
|
||||
@ -119,7 +168,7 @@ void TreasurePlacer::addAllPossibleObjects()
|
||||
size_t prisonsLeft = getMaxPrisons();
|
||||
for (int i = prisonsLevels - 1; i >= 0; i--)
|
||||
{
|
||||
ObjectInfo oi; // Create new instance which will hold destructor operation
|
||||
ObjectInfo oi(Obj::PRISON, 0); // Create new instance which will hold destructor operation
|
||||
|
||||
oi.value = generator.getConfig().prisonValues[i];
|
||||
if (oi.value > zone.getMaxTreasureValue())
|
||||
@ -157,22 +206,13 @@ void TreasurePlacer::addAllPossibleObjects()
|
||||
addObjectToRandomPool(oi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TreasurePlacer::addDwellings()
|
||||
{
|
||||
if(zone.getType() == ETemplateZoneType::WATER)
|
||||
return;
|
||||
|
||||
//all following objects are unlimited
|
||||
oi.maxPerZone = std::numeric_limits<ui32>::max();
|
||||
|
||||
std::vector<const CCreature *> creatures; //native creatures for this zone
|
||||
for(auto const & cre : VLC->creh->objects)
|
||||
{
|
||||
if(!cre->special && cre->getFaction() == zone.getTownType())
|
||||
{
|
||||
creatures.push_back(cre.get());
|
||||
}
|
||||
}
|
||||
|
||||
//dwellings
|
||||
auto dwellingTypes = {Obj::CREATURE_GENERATOR1, Obj::CREATURE_GENERATOR4};
|
||||
|
||||
@ -199,6 +239,9 @@ void TreasurePlacer::addAllPossibleObjects()
|
||||
if(cre->getFaction() == zone.getTownType())
|
||||
{
|
||||
auto nativeZonesCount = static_cast<float>(map.getZoneCount(cre->getFaction()));
|
||||
ObjectInfo oi(dwellingType, secondaryID);
|
||||
setBasicProperties(oi, CompoundMapObjectID(dwellingType, secondaryID));
|
||||
|
||||
oi.value = static_cast<ui32>(cre->getAIValue() * cre->getGrowth() * (1 + (nativeZonesCount / map.getTotalZoneCount()) + (nativeZonesCount / 2)));
|
||||
oi.probability = 40;
|
||||
|
||||
@ -208,13 +251,20 @@ void TreasurePlacer::addAllPossibleObjects()
|
||||
obj->tempOwner = PlayerColor::NEUTRAL;
|
||||
return obj;
|
||||
};
|
||||
oi.setTemplates(dwellingType, secondaryID, zone.getTerrainType());
|
||||
if(!oi.templates.empty())
|
||||
addObjectToRandomPool(oi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void TreasurePlacer::addScrolls()
|
||||
{
|
||||
if(zone.getType() == ETemplateZoneType::WATER)
|
||||
return;
|
||||
|
||||
ObjectInfo oi(Obj::SPELL_SCROLL, 0);
|
||||
|
||||
for(int i = 0; i < generator.getConfig().scrollValues.size(); i++)
|
||||
{
|
||||
oi.generateObject = [i, this]() -> CGObjectInstance *
|
||||
@ -239,7 +289,22 @@ void TreasurePlacer::addAllPossibleObjects()
|
||||
addObjectToRandomPool(oi);
|
||||
}
|
||||
|
||||
//pandora box with gold
|
||||
}
|
||||
|
||||
void TreasurePlacer::addPandoraBoxes()
|
||||
{
|
||||
if(zone.getType() == ETemplateZoneType::WATER)
|
||||
return;
|
||||
|
||||
addPandoraBoxesWithGold();
|
||||
addPandoraBoxesWithExperience();
|
||||
addPandoraBoxesWithCreatures();
|
||||
addPandoraBoxesWithSpells();
|
||||
}
|
||||
|
||||
void TreasurePlacer::addPandoraBoxesWithGold()
|
||||
{
|
||||
ObjectInfo oi(Obj::PANDORAS_BOX, 0);
|
||||
for(int i = 1; i < 5; i++)
|
||||
{
|
||||
oi.generateObject = [this, i]() -> CGObjectInstance *
|
||||
@ -260,8 +325,11 @@ void TreasurePlacer::addAllPossibleObjects()
|
||||
if(!oi.templates.empty())
|
||||
addObjectToRandomPool(oi);
|
||||
}
|
||||
|
||||
//pandora box with experience
|
||||
}
|
||||
|
||||
void TreasurePlacer::addPandoraBoxesWithExperience()
|
||||
{
|
||||
ObjectInfo oi(Obj::PANDORAS_BOX, 0);
|
||||
for(int i = 1; i < 5; i++)
|
||||
{
|
||||
oi.generateObject = [this, i]() -> CGObjectInstance *
|
||||
@ -282,49 +350,17 @@ void TreasurePlacer::addAllPossibleObjects()
|
||||
if(!oi.templates.empty())
|
||||
addObjectToRandomPool(oi);
|
||||
}
|
||||
|
||||
//pandora box with creatures
|
||||
const std::vector<int> & tierValues = generator.getConfig().pandoraCreatureValues;
|
||||
|
||||
auto creatureToCount = [tierValues](const CCreature * creature) -> int
|
||||
{
|
||||
if(!creature->getAIValue() || tierValues.empty()) //bug #2681
|
||||
return 0; //this box won't be generated
|
||||
|
||||
//Follow the rules from https://heroes.thelazy.net/index.php/Pandora%27s_Box
|
||||
|
||||
int actualTier = creature->getLevel() > tierValues.size() ?
|
||||
tierValues.size() - 1 :
|
||||
creature->getLevel() - 1;
|
||||
float creaturesAmount = std::floor((static_cast<float>(tierValues[actualTier])) / creature->getAIValue());
|
||||
if (creaturesAmount < 1)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else if(creaturesAmount <= 5)
|
||||
{
|
||||
//No change
|
||||
}
|
||||
else if(creaturesAmount <= 12)
|
||||
{
|
||||
creaturesAmount = std::ceil(creaturesAmount / 2) * 2;
|
||||
}
|
||||
else if(creaturesAmount <= 50)
|
||||
{
|
||||
creaturesAmount = std::round(creaturesAmount / 5) * 5;
|
||||
}
|
||||
else
|
||||
{
|
||||
creaturesAmount = std::round(creaturesAmount / 10) * 10;
|
||||
}
|
||||
return static_cast<int>(creaturesAmount);
|
||||
};
|
||||
}
|
||||
|
||||
void TreasurePlacer::addPandoraBoxesWithCreatures()
|
||||
{
|
||||
for(auto * creature : creatures)
|
||||
{
|
||||
int creaturesAmount = creatureToCount(creature);
|
||||
if(!creaturesAmount)
|
||||
continue;
|
||||
|
||||
ObjectInfo oi(Obj::PANDORAS_BOX, 0);
|
||||
|
||||
oi.generateObject = [this, creature, creaturesAmount]() -> CGObjectInstance *
|
||||
{
|
||||
@ -339,12 +375,16 @@ void TreasurePlacer::addAllPossibleObjects()
|
||||
return obj;
|
||||
};
|
||||
oi.setTemplates(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
|
||||
oi.value = static_cast<ui32>((2 * (creature->getAIValue()) * creaturesAmount * (1 + static_cast<float>(map.getZoneCount(creature->getFaction())) / map.getTotalZoneCount())) / 3);
|
||||
oi.value = static_cast<ui32>(creature->getAIValue() * creaturesAmount * (1 + static_cast<float>(map.getZoneCount(creature->getFaction())) / map.getTotalZoneCount()));
|
||||
oi.probability = 3;
|
||||
if(!oi.templates.empty())
|
||||
addObjectToRandomPool(oi);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void TreasurePlacer::addPandoraBoxesWithSpells()
|
||||
{
|
||||
ObjectInfo oi(Obj::PANDORAS_BOX, 0);
|
||||
//Pandora with 12 spells of certain level
|
||||
for(int i = 1; i <= GameConstants::SPELL_LEVELS; i++)
|
||||
{
|
||||
@ -441,9 +481,14 @@ void TreasurePlacer::addAllPossibleObjects()
|
||||
oi.probability = 2;
|
||||
if(!oi.templates.empty())
|
||||
addObjectToRandomPool(oi);
|
||||
|
||||
}
|
||||
|
||||
void TreasurePlacer::addSeerHuts()
|
||||
{
|
||||
//Seer huts with creatures or generic rewards
|
||||
|
||||
ObjectInfo oi(Obj::SEER_HUT, 0);
|
||||
|
||||
if(zone.getConnectedZoneIds().size()) //Unlikely, but...
|
||||
{
|
||||
auto * qap = zone.getModificator<QuestArtifactPlacer>();
|
||||
@ -588,12 +633,6 @@ void TreasurePlacer::addAllPossibleObjects()
|
||||
}
|
||||
}
|
||||
|
||||
size_t TreasurePlacer::getPossibleObjectsSize() const
|
||||
{
|
||||
RecursiveLock lock(externalAccessMutex);
|
||||
return possibleObjects.size();
|
||||
}
|
||||
|
||||
void TreasurePlacer::setMaxPrisons(size_t count)
|
||||
{
|
||||
RecursiveLock lock(externalAccessMutex);
|
||||
@ -606,6 +645,40 @@ size_t TreasurePlacer::getMaxPrisons() const
|
||||
return maxPrisons;
|
||||
}
|
||||
|
||||
int TreasurePlacer::creatureToCount(const CCreature * creature) const
|
||||
{
|
||||
if(!creature->getAIValue() || tierValues.empty()) //bug #2681
|
||||
return 0; //this box won't be generated
|
||||
|
||||
//Follow the rules from https://heroes.thelazy.net/index.php/Pandora%27s_Box
|
||||
|
||||
int actualTier = creature->getLevel() > tierValues.size() ?
|
||||
tierValues.size() - 1 :
|
||||
creature->getLevel() - 1;
|
||||
float creaturesAmount = std::floor((static_cast<float>(tierValues[actualTier])) / creature->getAIValue());
|
||||
if (creaturesAmount < 1)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else if(creaturesAmount <= 5)
|
||||
{
|
||||
//No change
|
||||
}
|
||||
else if(creaturesAmount <= 12)
|
||||
{
|
||||
creaturesAmount = std::ceil(creaturesAmount / 2) * 2;
|
||||
}
|
||||
else if(creaturesAmount <= 50)
|
||||
{
|
||||
creaturesAmount = std::round(creaturesAmount / 5) * 5;
|
||||
}
|
||||
else
|
||||
{
|
||||
creaturesAmount = std::round(creaturesAmount / 10) * 10;
|
||||
}
|
||||
return static_cast<int>(creaturesAmount);
|
||||
};
|
||||
|
||||
bool TreasurePlacer::isGuardNeededForTreasure(int value)
|
||||
{// no guard in a zone with "monsters: none" and for small treasures; water zones cen get monster strength ZONE_NONE elsewhere if needed
|
||||
return zone.monsterStrength != EMonsterStrength::ZONE_NONE && value > minGuardedValue;
|
||||
@ -623,6 +696,7 @@ std::vector<ObjectInfo*> TreasurePlacer::prepareTreasurePile(const CTreasureInfo
|
||||
bool hasLargeObject = false;
|
||||
while(currentValue <= static_cast<int>(desiredValue) - 100) //no objects with value below 100 are available
|
||||
{
|
||||
// FIXME: Pointer might be invalidated after this
|
||||
auto * oi = getRandomObject(desiredValue, currentValue, !hasLargeObject);
|
||||
if(!oi) //fail
|
||||
break;
|
||||
@ -674,12 +748,21 @@ rmg::Object TreasurePlacer::constructTreasurePile(const std::vector<ObjectInfo*>
|
||||
accessibleArea.add(int3());
|
||||
}
|
||||
|
||||
auto * object = oi->generateObject();
|
||||
if(oi->templates.empty())
|
||||
CGObjectInstance * object = nullptr;
|
||||
if (oi->generateObject)
|
||||
{
|
||||
logGlobal->warn("Deleting randomized object with no templates: %s", object->getObjectName());
|
||||
oi->destroyObject(object);
|
||||
delete object;
|
||||
object = oi->generateObject();
|
||||
if(oi->templates.empty())
|
||||
{
|
||||
logGlobal->warn("Deleting randomized object with no templates: %s", object->getObjectName());
|
||||
oi->destroyObject(object);
|
||||
delete object;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logGlobal->error("ObjectInfo has no generateObject function! Templates: %d", oi->templates.size());
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -785,7 +868,7 @@ ObjectInfo * TreasurePlacer::getRandomObject(ui32 desiredValue, ui32 currentValu
|
||||
ui32 maxVal = desiredValue - currentValue;
|
||||
ui32 minValue = static_cast<ui32>(0.25f * (desiredValue - currentValue));
|
||||
|
||||
for(ObjectInfo & oi : possibleObjects) //copy constructor turned out to be costly
|
||||
for(ObjectInfo & oi : objects.getPossibleObjects()) //copy constructor turned out to be costly
|
||||
{
|
||||
if(oi.value > maxVal)
|
||||
break; //this assumes values are sorted in ascending order
|
||||
@ -859,24 +942,19 @@ void TreasurePlacer::createTreasures(ObjectManager& manager)
|
||||
boost::sort(treasureInfo, valueComparator);
|
||||
|
||||
//sort treasures by ascending value so we can stop checking treasures with too high value
|
||||
boost::sort(possibleObjects, [](const ObjectInfo& oi1, const ObjectInfo& oi2) -> bool
|
||||
{
|
||||
return oi1.value < oi2.value;
|
||||
});
|
||||
objects.sortPossibleObjects();
|
||||
|
||||
const size_t size = zone.area()->getTilesVector().size();
|
||||
|
||||
int totalDensity = 0;
|
||||
|
||||
// FIXME: No need to use iterator here
|
||||
for (auto t = treasureInfo.begin(); t != treasureInfo.end(); t++)
|
||||
{
|
||||
std::vector<rmg::Object> treasures;
|
||||
|
||||
//discard objects with too high value to be ever placed
|
||||
vstd::erase_if(possibleObjects, [t](const ObjectInfo& oi) -> bool
|
||||
{
|
||||
return oi.value > t->max;
|
||||
});
|
||||
objects.discardObjectsAboveValue(t->max);
|
||||
|
||||
totalDensity += t->density;
|
||||
|
||||
@ -895,7 +973,11 @@ void TreasurePlacer::createTreasures(ObjectManager& manager)
|
||||
continue;
|
||||
}
|
||||
|
||||
int value = std::accumulate(treasurePileInfos.begin(), treasurePileInfos.end(), 0, [](int v, const ObjectInfo* oi) {return v + oi->value; });
|
||||
int value = std::accumulate(treasurePileInfos.begin(), treasurePileInfos.end(), 0,
|
||||
[](int v, const ObjectInfo* oi)
|
||||
{
|
||||
return v + oi->value;
|
||||
});
|
||||
|
||||
const ui32 maxPileGenerationAttempts = 2;
|
||||
for (ui32 attempt = 0; attempt < maxPileGenerationAttempts; attempt++)
|
||||
@ -1016,13 +1098,222 @@ char TreasurePlacer::dump(const int3 & t)
|
||||
return Modificator::dump(t);
|
||||
}
|
||||
|
||||
void ObjectInfo::setTemplates(MapObjectID type, MapObjectSubID subtype, TerrainId terrainType)
|
||||
void TreasurePlacer::ObjectPool::addObject(const ObjectInfo & info)
|
||||
{
|
||||
auto templHandler = VLC->objtypeh->getHandlerFor(type, subtype);
|
||||
if(!templHandler)
|
||||
return;
|
||||
|
||||
templates = templHandler->getTemplates(terrainType);
|
||||
possibleObjects.push_back(info);
|
||||
}
|
||||
|
||||
void TreasurePlacer::ObjectPool::updateObject(MapObjectID id, MapObjectSubID subid, ObjectInfo info)
|
||||
{
|
||||
/*
|
||||
Handle separately:
|
||||
- Dwellings
|
||||
- Prisons
|
||||
- Seer huts (quests)
|
||||
- Pandora Boxes
|
||||
*/
|
||||
// FIXME: This will drop all templates
|
||||
customObjects.insert(std::make_pair(CompoundMapObjectID(id, subid), info));
|
||||
}
|
||||
|
||||
void TreasurePlacer::ObjectPool::patchWithZoneConfig(const Zone & zone, TreasurePlacer * tp)
|
||||
{
|
||||
// FIXME: Wycina wszystkie obiekty poza pandorami i dwellami :?
|
||||
|
||||
// Copy standard objects if they are not already modified
|
||||
/*
|
||||
for (const auto & object : possibleObjects)
|
||||
{
|
||||
for (const auto & templ : object.templates)
|
||||
{
|
||||
// FIXME: Objects with same temmplates (Pandora boxes) are not added
|
||||
CompoundMapObjectID key(templ->id, templ->subid);
|
||||
if (!vstd::contains(customObjects, key))
|
||||
{
|
||||
customObjects[key] = object;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
auto bannedObjectCategories = zone.getBannedObjectCategories();
|
||||
auto categoriesSet = std::unordered_set<ObjectConfig::EObjectCategory>(bannedObjectCategories.begin(), bannedObjectCategories.end());
|
||||
|
||||
if (categoriesSet.count(ObjectConfig::EObjectCategory::ALL))
|
||||
{
|
||||
possibleObjects.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
vstd::erase_if(possibleObjects, [this, &categoriesSet](const ObjectInfo & oi) -> bool
|
||||
|
||||
{
|
||||
auto category = getObjectCategory(oi.getCompoundID());
|
||||
if (categoriesSet.count(category))
|
||||
{
|
||||
logGlobal->info("Removing object %s from possible objects", oi.templates.front()->stringID);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
auto bannedObjects = zone.getBannedObjects();
|
||||
auto bannedObjectsSet = std::set<CompoundMapObjectID>(bannedObjects.begin(), bannedObjects.end());
|
||||
vstd::erase_if(possibleObjects, [&bannedObjectsSet](const ObjectInfo & object)
|
||||
{
|
||||
for (const auto & templ : object.templates)
|
||||
{
|
||||
CompoundMapObjectID key = object.getCompoundID();
|
||||
if (bannedObjectsSet.count(key))
|
||||
{
|
||||
// FIXME: Stopped working, nothing is banned
|
||||
logGlobal->info("Banning object %s from possible objects", templ->stringID);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
auto configuredObjects = zone.getConfiguredObjects();
|
||||
|
||||
// FIXME: Access TreasurePlacer from ObjectPool
|
||||
for (auto & object : configuredObjects)
|
||||
{
|
||||
tp->setBasicProperties(object, object.getCompoundID());
|
||||
addObject(object);
|
||||
logGlobal->info("Added custom object of type %d.%d", object.primaryID, object.secondaryID);
|
||||
}
|
||||
// TODO: Overwrite or add to possibleObjects
|
||||
|
||||
// FIXME: Protect with mutex as well?
|
||||
/*
|
||||
for (const auto & customObject : customObjects)
|
||||
{
|
||||
addObject(customObject.second);
|
||||
}
|
||||
*/
|
||||
// TODO: Consider adding custom Pandora boxes with arbitrary content
|
||||
}
|
||||
|
||||
std::vector<ObjectInfo> & TreasurePlacer::ObjectPool::getPossibleObjects()
|
||||
{
|
||||
return possibleObjects;
|
||||
}
|
||||
|
||||
void TreasurePlacer::ObjectPool::sortPossibleObjects()
|
||||
{
|
||||
boost::sort(possibleObjects, [](const ObjectInfo& oi1, const ObjectInfo& oi2) -> bool
|
||||
{
|
||||
return oi1.value < oi2.value;
|
||||
});
|
||||
}
|
||||
|
||||
void TreasurePlacer::ObjectPool::discardObjectsAboveValue(ui32 value)
|
||||
{
|
||||
vstd::erase_if(possibleObjects, [value](const ObjectInfo& oi) -> bool
|
||||
{
|
||||
return oi.value > value;
|
||||
});
|
||||
}
|
||||
|
||||
ObjectConfig::EObjectCategory TreasurePlacer::ObjectPool::getObjectCategory(CompoundMapObjectID id)
|
||||
{
|
||||
auto name = VLC->objtypeh->getObjectHandlerName(id.primaryID);
|
||||
|
||||
if (name == "configurable")
|
||||
{
|
||||
auto handler = VLC->objtypeh->getHandlerFor(id.primaryID, id.secondaryID);
|
||||
if (!handler)
|
||||
{
|
||||
return ObjectConfig::EObjectCategory::NONE;
|
||||
}
|
||||
|
||||
auto temp = handler->getTemplates().front();
|
||||
auto info = handler->getObjectInfo(temp);
|
||||
|
||||
if (info->hasGuards())
|
||||
{
|
||||
return ObjectConfig::EObjectCategory::CREATURE_BANK;
|
||||
}
|
||||
else if (info->givesResources())
|
||||
{
|
||||
return ObjectConfig::EObjectCategory::RESOURCE;
|
||||
}
|
||||
else if (info->givesArtifacts())
|
||||
{
|
||||
return ObjectConfig::EObjectCategory::RANDOM_ARTIFACT;
|
||||
}
|
||||
else if (info->givesBonuses())
|
||||
{
|
||||
return ObjectConfig::EObjectCategory::BONUS;
|
||||
}
|
||||
|
||||
return ObjectConfig::EObjectCategory::OTHER;
|
||||
}
|
||||
else if (name == "dwelling" || name == "randomDwelling")
|
||||
{
|
||||
// TODO: Special handling for different tiers
|
||||
return ObjectConfig::EObjectCategory::DWELLING;
|
||||
}
|
||||
else if (name == "bank")
|
||||
return ObjectConfig::EObjectCategory::CREATURE_BANK;
|
||||
else if (name == "market")
|
||||
return ObjectConfig::EObjectCategory::OTHER;
|
||||
else if (name == "hillFort")
|
||||
return ObjectConfig::EObjectCategory::OTHER;
|
||||
else if (name == "resource" || name == "randomResource")
|
||||
return ObjectConfig::EObjectCategory::RESOURCE;
|
||||
else if (name == "randomArtifact") //"artifact"
|
||||
return ObjectConfig::EObjectCategory::RANDOM_ARTIFACT;
|
||||
else if (name == "artifact")
|
||||
{
|
||||
if (id.primaryID == Obj::SPELL_SCROLL ) // randomArtifactTreasure
|
||||
{
|
||||
return ObjectConfig::EObjectCategory::SPELL_SCROLL;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ObjectConfig::EObjectCategory::QUEST_ARTIFACT;
|
||||
}
|
||||
}
|
||||
else if (name == "denOfThieves")
|
||||
return ObjectConfig::EObjectCategory::OTHER;
|
||||
else if (name == "lighthouse")
|
||||
{
|
||||
return ObjectConfig::EObjectCategory::BONUS;
|
||||
}
|
||||
else if (name == "magi")
|
||||
{
|
||||
// TODO: By default, both eye and hut are banned in every zone
|
||||
return ObjectConfig::EObjectCategory::OTHER;
|
||||
}
|
||||
else if (name == "mine")
|
||||
return ObjectConfig::EObjectCategory::RESOURCE_GENERATOR;
|
||||
else if (name == "pandora")
|
||||
return ObjectConfig::EObjectCategory::PANDORAS_BOX;
|
||||
else if (name == "prison")
|
||||
{
|
||||
// TODO: Prisons should be configurable
|
||||
return ObjectConfig::EObjectCategory::OTHER;
|
||||
}
|
||||
else if (name == "questArtifact")
|
||||
{
|
||||
// TODO: There are no dedicated quest artifacts, needs extra logic
|
||||
return ObjectConfig::EObjectCategory::QUEST_ARTIFACT;
|
||||
}
|
||||
else if (name == "seerHut")
|
||||
{
|
||||
return ObjectConfig::EObjectCategory::SEER_HUT;
|
||||
}
|
||||
else if (name == "siren")
|
||||
return ObjectConfig::EObjectCategory::BONUS;
|
||||
else if (name == "obelisk")
|
||||
return ObjectConfig::EObjectCategory::OTHER;
|
||||
|
||||
// TODO: ObjectConfig::EObjectCategory::SPELL_SCROLL
|
||||
|
||||
// Not interesting for us
|
||||
return ObjectConfig::EObjectCategory::NONE;
|
||||
}
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
@ -9,6 +9,8 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../ObjectInfo.h"
|
||||
#include "../Zone.h"
|
||||
#include "../../mapObjects/ObjectTemplate.h"
|
||||
|
||||
@ -18,21 +20,7 @@ class CGObjectInstance;
|
||||
class ObjectManager;
|
||||
class RmgMap;
|
||||
class CMapGenerator;
|
||||
|
||||
struct ObjectInfo
|
||||
{
|
||||
ObjectInfo();
|
||||
|
||||
std::vector<std::shared_ptr<const ObjectTemplate>> templates;
|
||||
ui32 value = 0;
|
||||
ui16 probability = 0;
|
||||
ui32 maxPerZone = 1;
|
||||
//ui32 maxPerMap; //unused
|
||||
std::function<CGObjectInstance *()> generateObject;
|
||||
std::function<void(CGObjectInstance *)> destroyObject;
|
||||
|
||||
void setTemplates(MapObjectID type, MapObjectSubID subtype, TerrainId terrain);
|
||||
};
|
||||
class ObjectConfig;
|
||||
|
||||
class TreasurePlacer: public Modificator
|
||||
{
|
||||
@ -45,11 +33,27 @@ public:
|
||||
|
||||
void createTreasures(ObjectManager & manager);
|
||||
void addObjectToRandomPool(const ObjectInfo& oi);
|
||||
void addAllPossibleObjects(); //add objects, including zone-specific, to possibleObjects
|
||||
void setBasicProperties(ObjectInfo & oi, CompoundMapObjectID objid) const;
|
||||
|
||||
// TODO: Can be defaulted to addAllPossibleObjects, but then each object will need to be configured
|
||||
void addCommonObjects();
|
||||
void addDwellings();
|
||||
void addPandoraBoxes();
|
||||
void addPandoraBoxesWithGold();
|
||||
void addPandoraBoxesWithExperience();
|
||||
void addPandoraBoxesWithCreatures();
|
||||
void addPandoraBoxesWithSpells();
|
||||
void addSeerHuts();
|
||||
void addPrisons();
|
||||
void addScrolls();
|
||||
void addAllPossibleObjects(); //add objects, including zone-specific, to possibleObjects
|
||||
// TODO: Read custom object config from zone file
|
||||
|
||||
/// Get all objects for this terrain
|
||||
|
||||
size_t getPossibleObjectsSize() const;
|
||||
void setMaxPrisons(size_t count);
|
||||
size_t getMaxPrisons() const;
|
||||
int creatureToCount(const CCreature * creature) const;
|
||||
|
||||
protected:
|
||||
bool isGuardNeededForTreasure(int value);
|
||||
@ -59,7 +63,26 @@ protected:
|
||||
rmg::Object constructTreasurePile(const std::vector<ObjectInfo*> & treasureInfos, bool densePlacement = false);
|
||||
|
||||
protected:
|
||||
std::vector<ObjectInfo> possibleObjects;
|
||||
class ObjectPool
|
||||
{
|
||||
public:
|
||||
void addObject(const ObjectInfo & info);
|
||||
void updateObject(MapObjectID id, MapObjectSubID subid, ObjectInfo info);
|
||||
std::vector<ObjectInfo> & getPossibleObjects();
|
||||
void patchWithZoneConfig(const Zone & zone, TreasurePlacer * tp);
|
||||
void sortPossibleObjects();
|
||||
void discardObjectsAboveValue(ui32 value);
|
||||
|
||||
ObjectConfig::EObjectCategory getObjectCategory(CompoundMapObjectID id);
|
||||
|
||||
private:
|
||||
|
||||
std::vector<ObjectInfo> possibleObjects;
|
||||
std::map<CompoundMapObjectID, ObjectInfo> customObjects;
|
||||
|
||||
} objects;
|
||||
// TODO: Need to nagivate and update these
|
||||
|
||||
int minGuardedValue = 0;
|
||||
|
||||
rmg::Area treasureArea;
|
||||
@ -67,6 +90,9 @@ protected:
|
||||
rmg::Area guards;
|
||||
|
||||
size_t maxPrisons;
|
||||
|
||||
std::vector<const CCreature *> creatures; //native creatures for this zone
|
||||
std::vector<int> tierValues;
|
||||
};
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
Loading…
Reference in New Issue
Block a user