1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-24 08:32:34 +02:00

Distribute limited objects evenly in zones with matching terrain

This commit is contained in:
Tomasz Zieliński 2023-03-27 09:09:58 +02:00
parent a88e1dc1f6
commit 18a87d1ec0
9 changed files with 206 additions and 31 deletions

View File

@ -101,6 +101,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
${MAIN_LIB_DIR}/rmg/Zone.cpp
${MAIN_LIB_DIR}/rmg/Functions.cpp
${MAIN_LIB_DIR}/rmg/ObjectManager.cpp
${MAIN_LIB_DIR}/rmg/ObjectDistributor.cpp
${MAIN_LIB_DIR}/rmg/RoadPlacer.cpp
${MAIN_LIB_DIR}/rmg/TreasurePlacer.cpp
${MAIN_LIB_DIR}/rmg/RmgMap.cpp
@ -338,6 +339,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
${MAIN_LIB_DIR}/rmg/Zone.h
${MAIN_LIB_DIR}/rmg/Functions.h
${MAIN_LIB_DIR}/rmg/ObjectManager.h
${MAIN_LIB_DIR}/rmg/ObjectDistributor.h
${MAIN_LIB_DIR}/rmg/RoadPlacer.h
${MAIN_LIB_DIR}/rmg/TreasurePlacer.h
${MAIN_LIB_DIR}/rmg/RmgMap.h

View File

@ -508,7 +508,11 @@ void AObjectTypeHandler::init(const JsonNode & input)
if (!input["rmg"].isNull())
{
rmgInfo.value = static_cast<ui32>(input["rmg"]["value"].Float());
rmgInfo.mapLimit = loadJsonOrMax(input["rmg"]["mapLimit"]);
const JsonNode & mapLimit = input["rmg"]["mapLimit"];
if (!mapLimit.isNull())
rmgInfo.mapLimit.reset(static_cast<ui32>(mapLimit.Float()));
rmgInfo.zoneLimit = loadJsonOrMax(input["rmg"]["zoneLimit"]);
rmgInfo.rarity = static_cast<ui32>(input["rmg"]["rarity"].Float());
} // else block is not needed - set in constructor

View File

@ -43,7 +43,7 @@ struct DLL_LINKAGE RandomMapInfo
ui32 value;
/// How many of such objects can be placed on map, 0 = object can not be placed by RMG
ui32 mapLimit;
boost::optional<ui32> mapLimit;
/// How many of such objects can be placed in one zone, 0 = unplaceable
ui32 zoneLimit;
@ -53,11 +53,12 @@ struct DLL_LINKAGE RandomMapInfo
RandomMapInfo():
value(0),
mapLimit(0),
zoneLimit(0),
rarity(0)
{}
void setMapLimit(ui32 val) { mapLimit.reset(val); }
template <typename Handler> void serialize(Handler &h, const int version)
{
h & value;

View File

@ -276,11 +276,11 @@ void CMapGenerator::genZones()
void CMapGenerator::createWaterTreasures()
{
if(!getZoneWater())
if (!getZoneWater())
return;
//add treasures on water
for(const auto & treasureInfo : getConfig().waterTreasure)
for (const auto& treasureInfo : getConfig().waterTreasure)
{
getZoneWater()->addTreasureInfo(treasureInfo);
}
@ -296,7 +296,7 @@ void CMapGenerator::fillZones()
//we need info about all town types to evaluate dwellings and pandoras with creatures properly
//place main town in the middle
Load::Progress::setupStepsTill(map->getZones().size(), 50);
for(const auto & it : map->getZones())
for (const auto& it : map->getZones())
{
it.second->initFreeTiles();
it.second->initModificators();
@ -305,7 +305,7 @@ void CMapGenerator::fillZones()
Load::Progress::setupStepsTill(map->getZones().size(), 240);
std::vector<std::shared_ptr<Zone>> treasureZones;
for(const auto & it : map->getZones())
for (const auto& it : map->getZones())
{
it.second->processModificators();
@ -316,10 +316,10 @@ void CMapGenerator::fillZones()
}
//find place for Grail
if(treasureZones.empty())
if (treasureZones.empty())
{
for(const auto & it : map->getZones())
if(it.second->getType() != ETemplateZoneType::WATER)
for (const auto& it : map->getZones())
if (it.second->getType() != ETemplateZoneType::WATER)
treasureZones.push_back(it.second);
}
auto grailZone = *RandomGeneratorUtil::nextItem(treasureZones, rand);

View File

@ -0,0 +1,116 @@
/*
* ObjectDistributor.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 "ObjectDistributor.h"
#include "../VCMI_Lib.h"
#include "RmgMap.h"
#include "CMapGenerator.h"
#include "TreasurePlacer.h"
#include "TownPlacer.h"
#include "TerrainPainter.h"
#include "../mapObjects/CObjectClassesHandler.h"
#include "../mapObjects/MapObjects.h"
#include "Functions.h"
#include "RmgObject.h"
VCMI_LIB_NAMESPACE_BEGIN
void ObjectDistributor::process()
{
//Firts call will add objects to ALL zones, once they were added skip it
if (zone.getModificator<TreasurePlacer>()->getPossibleObjectsSize() == 0)
{
ObjectDistributor::distributeLimitedObjects();
}
}
void ObjectDistributor::init()
{
DEPENDENCY(TownPlacer);
DEPENDENCY(TerrainPainter);
POSTFUNCTION(TreasurePlacer);
}
void ObjectDistributor::distributeLimitedObjects()
{
//FIXME: Must be called after TerrainPainter::process()
ObjectInfo oi;
auto zones = map.getZones();
for (auto primaryID : VLC->objtypeh->knownObjects())
{
for (auto secondaryID : VLC->objtypeh->knownSubObjects(primaryID))
{
auto handler = VLC->objtypeh->getHandlerFor(primaryID, secondaryID);
if (!handler->isStaticObject() && handler->getRMGInfo().value)
{
auto rmgInfo = handler->getRMGInfo();
//Skip objects which don't have global per-map limit here
if (rmgInfo.mapLimit)
{
//Count all zones where this object can be placed
std::vector<std::shared_ptr<Zone>> matchingZones;
//TODO: Are all terrains initialized at this point? Including water?
for (const auto& it : zones)
{
if (!handler->getTemplates(it.second->getTerrainType()).empty())
{
matchingZones.push_back(it.second);
}
}
//TODO: Also check if the object value is within zone max value
size_t numZones = matchingZones.size();
if (!numZones)
continue;
auto rmgInfo = handler->getRMGInfo();
for (auto& zone : matchingZones)
{
auto temp = handler->getTemplates(zone->getTerrainType()).front();
oi.generateObject = [temp]() -> CGObjectInstance *
{
return VLC->objtypeh->getHandlerFor(temp->id, temp->subid)->create(temp);
};
oi.value = rmgInfo.value;
oi.probability = rmgInfo.rarity;
oi.templ = temp;
//Rounding up will make sure all possible objects are exhausted
uint32_t mapLimit = rmgInfo.mapLimit.get();
uint32_t maxPerZone = std::ceil(float(mapLimit) / numZones);
//But not more than zone limit
oi.maxPerZone = std::min(maxPerZone, rmgInfo.zoneLimit);
numZones--;
rmgInfo.setMapLimit(mapLimit - oi.maxPerZone);
//Don't add objects with 0 count remaining
if (oi.maxPerZone)
{
zone->getModificator<TreasurePlacer>()->addObjectToRandomPool(oi);
}
}
}
}
}
}
}
VCMI_LIB_NAMESPACE_END

View File

@ -0,0 +1,32 @@
/*
* ObjectDistributor.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 "Zone.h"
#include "RmgObject.h"
VCMI_LIB_NAMESPACE_BEGIN
class CGObjectInstance;
class ObjectTemplate;
class ObjectDistributor : public Modificator
{
void distributeLimitedObjects();
public:
MODIFICATOR(ObjectDistributor);
void process() override;
void init() override;
};
VCMI_LIB_NAMESPACE_END

View File

@ -21,6 +21,7 @@
#include "TreasurePlacer.h"
#include "ConnectionsPlacer.h"
#include "TownPlacer.h"
#include "ObjectDistributor.h"
#include "WaterAdopter.h"
#include "WaterProxy.h"
#include "WaterRoutes.h"
@ -118,6 +119,7 @@ void RmgMap::addModificators()
auto zone = z.second;
zone->addModificator<ObjectManager>();
zone->addModificator<ObjectDistributor>();
zone->addModificator<TreasurePlacer>();
zone->addModificator<ObstaclePlacer>();
zone->addModificator<TerrainPainter>();

View File

@ -46,6 +46,11 @@ void TreasurePlacer::setQuestArtZone(Zone * otherZone)
questArtZone = otherZone;
}
void TreasurePlacer::addObjectToRandomPool(const ObjectInfo& oi)
{
possibleObjects.push_back(oi);
}
void TreasurePlacer::addAllPossibleObjects()
{
ObjectInfo oi;
@ -59,6 +64,14 @@ void TreasurePlacer::addAllPossibleObjects()
auto handler = VLC->objtypeh->getHandlerFor(primaryID, secondaryID);
if(!handler->isStaticObject() && handler->getRMGInfo().value)
{
auto rmgInfo = handler->getRMGInfo();
if (rmgInfo.mapLimit)
{
//Skip objects with per-map limit here
continue;
}
//TODO: Also check if the object value is within zone max value
for(const auto & temp : handler->getTemplates())
{
if(temp->canBePlacedAt(zone.getTerrainType()))
@ -67,15 +80,12 @@ void TreasurePlacer::addAllPossibleObjects()
{
return VLC->objtypeh->getHandlerFor(temp->id, temp->subid)->create(temp);
};
auto rmgInfo = handler->getRMGInfo();
oi.value = rmgInfo.value;
oi.probability = rmgInfo.rarity;
oi.templ = temp;
oi.maxPerZone = rmgInfo.zoneLimit;
vstd::amin(oi.maxPerZone, rmgInfo.mapLimit / numZones); //simple, but should distribute objects evenly on large maps
possibleObjects.push_back(oi);
addObjectToRandomPool(oi);
break; //Place only one template per zone
}
}
}
@ -116,7 +126,7 @@ void TreasurePlacer::addAllPossibleObjects()
oi.value = generator.getConfig().prisonValues[i];
oi.probability = 30;
oi.maxPerZone = generator.getPrisonsRemaning() / 5; //probably not perfect, but we can't generate more prisons than hereos.
possibleObjects.push_back(oi);
addObjectToRandomPool(oi);
}
//all following objects are unlimited
@ -172,7 +182,7 @@ void TreasurePlacer::addAllPossibleObjects()
};
oi.templ = tmplate;
possibleObjects.push_back(oi);
addObjectToRandomPool(oi);
}
}
}
@ -201,7 +211,7 @@ void TreasurePlacer::addAllPossibleObjects()
oi.setTemplate(Obj::SPELL_SCROLL, 0, zone.getTerrainType());
oi.value = generator.getConfig().scrollValues[i];
oi.probability = 30;
possibleObjects.push_back(oi);
addObjectToRandomPool(oi);
}
//pandora box with gold
@ -217,7 +227,7 @@ void TreasurePlacer::addAllPossibleObjects()
oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
oi.value = i * generator.getConfig().pandoraMultiplierGold;
oi.probability = 5;
possibleObjects.push_back(oi);
addObjectToRandomPool(oi);
}
//pandora box with experience
@ -233,7 +243,7 @@ void TreasurePlacer::addAllPossibleObjects()
oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
oi.value = i * generator.getConfig().pandoraMultiplierExperience;
oi.probability = 20;
possibleObjects.push_back(oi);
addObjectToRandomPool(oi);
}
//pandora box with creatures
@ -286,7 +296,7 @@ void TreasurePlacer::addAllPossibleObjects()
oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
oi.value = static_cast<ui32>((2 * (creature->AIValue) * creaturesAmount * (1 + static_cast<float>(map.getZoneCount(creature->faction)) / map.getTotalZoneCount())) / 3);
oi.probability = 3;
possibleObjects.push_back(oi);
addObjectToRandomPool(oi);
}
//Pandora with 12 spells of certain level
@ -315,7 +325,7 @@ void TreasurePlacer::addAllPossibleObjects()
oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
oi.value = (i + 1) * generator.getConfig().pandoraMultiplierSpells; //5000 - 15000
oi.probability = 2;
possibleObjects.push_back(oi);
addObjectToRandomPool(oi);
}
//Pandora with 15 spells of certain school
@ -344,7 +354,7 @@ void TreasurePlacer::addAllPossibleObjects()
oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
oi.value = generator.getConfig().pandoraSpellSchool;
oi.probability = 2;
possibleObjects.push_back(oi);
addObjectToRandomPool(oi);
}
// Pandora box with 60 random spells
@ -372,7 +382,7 @@ void TreasurePlacer::addAllPossibleObjects()
oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
oi.value = generator.getConfig().pandoraSpell60;
oi.probability = 2;
possibleObjects.push_back(oi);
addObjectToRandomPool(oi);
//seer huts with creatures or generic rewards
@ -438,14 +448,14 @@ void TreasurePlacer::addAllPossibleObjects()
generator.banQuestArt(artid);
this->questArtZone->getModificator<TreasurePlacer>()->possibleObjects.push_back(generateArtInfo(artid));
this->questArtZone->getModificator<TreasurePlacer>()->addObjectToRandomPool(generateArtInfo(artid));
return obj;
};
oi.setTemplate(Obj::SEER_HUT, randomAppearance, zone.getTerrainType());
oi.value = static_cast<ui32>(((2 * (creature->AIValue) * creaturesAmount * (1 + static_cast<float>(map.getZoneCount(creature->faction)) / map.getTotalZoneCount())) - 4000) / 3);
oi.probability = 3;
possibleObjects.push_back(oi);
addObjectToRandomPool(oi);
}
static int seerLevels = std::min(generator.getConfig().questValues.size(), generator.getConfig().questRewardValues.size());
@ -474,12 +484,12 @@ void TreasurePlacer::addAllPossibleObjects()
generator.banQuestArt(artid);
this->questArtZone->getModificator<TreasurePlacer>()->possibleObjects.push_back(generateArtInfo(artid));
this->questArtZone->getModificator<TreasurePlacer>()->addObjectToRandomPool(generateArtInfo(artid));
return obj;
};
possibleObjects.push_back(oi);
addObjectToRandomPool(oi);
oi.generateObject = [i, randomAppearance, this, generateArtInfo]() -> CGObjectInstance *
{
@ -497,16 +507,21 @@ void TreasurePlacer::addAllPossibleObjects()
generator.banQuestArt(artid);
this->questArtZone->getModificator<TreasurePlacer>()->possibleObjects.push_back(generateArtInfo(artid));
this->questArtZone->getModificator<TreasurePlacer>()->addObjectToRandomPool(generateArtInfo(artid));
return obj;
};
possibleObjects.push_back(oi);
addObjectToRandomPool(oi);
}
}
}
size_t TreasurePlacer::getPossibleObjectsSize() const
{
return possibleObjects.size();
}
bool TreasurePlacer::isGuardNeededForTreasure(int value)
{
return zone.getType() != ETemplateZoneType::WATER && value > minGuardedValue;

View File

@ -45,8 +45,11 @@ public:
void createTreasures(ObjectManager & manager);
void setQuestArtZone(Zone * otherZone);
void addObjectToRandomPool(const ObjectInfo& oi);
void addAllPossibleObjects(); //add objects, including zone-specific, to possibleObjects
size_t getPossibleObjectsSize() const;
protected:
bool isGuardNeededForTreasure(int value);