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

Merge pull request #3298 from vcmi/random_prison_distributor

Fixes to hero / prison distribution
This commit is contained in:
Ivan Savenko 2024-01-18 16:03:08 +02:00 committed by GitHub
commit b1bd44de1e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 231 additions and 55 deletions

View File

@ -156,6 +156,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
${MAIN_LIB_DIR}/rmg/modificators/ObjectDistributor.cpp
${MAIN_LIB_DIR}/rmg/modificators/RoadPlacer.cpp
${MAIN_LIB_DIR}/rmg/modificators/TreasurePlacer.cpp
${MAIN_LIB_DIR}/rmg/modificators/PrisonHeroPlacer.cpp
${MAIN_LIB_DIR}/rmg/modificators/QuestArtifactPlacer.cpp
${MAIN_LIB_DIR}/rmg/modificators/ConnectionsPlacer.cpp
${MAIN_LIB_DIR}/rmg/modificators/WaterAdopter.cpp
@ -526,6 +527,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
${MAIN_LIB_DIR}/rmg/modificators/ObjectDistributor.h
${MAIN_LIB_DIR}/rmg/modificators/RoadPlacer.h
${MAIN_LIB_DIR}/rmg/modificators/TreasurePlacer.h
${MAIN_LIB_DIR}/rmg/modificators/PrisonHeroPlacer.h
${MAIN_LIB_DIR}/rmg/modificators/QuestArtifactPlacer.h
${MAIN_LIB_DIR}/rmg/modificators/ConnectionsPlacer.h
${MAIN_LIB_DIR}/rmg/modificators/WaterAdopter.h

View File

@ -649,9 +649,18 @@ void CMap::banWaterHeroes()
void CMap::banHero(const HeroTypeID & id)
{
if (!vstd::contains(allowedHeroes, id))
logGlobal->warn("Attempt to ban hero %s, who is already not allowed", id.encode(id));
allowedHeroes.erase(id);
}
void CMap::unbanHero(const HeroTypeID & id)
{
if (vstd::contains(allowedHeroes, id))
logGlobal->warn("Attempt to unban hero %s, who is already allowed", id.encode(id));
allowedHeroes.insert(id);
}
void CMap::initTerrain()
{
terrain.resize(boost::extents[levels()][width][height]);

View File

@ -112,6 +112,7 @@ public:
void banWaterArtifacts();
void banWaterHeroes();
void banHero(const HeroTypeID& id);
void unbanHero(const HeroTypeID & id);
void banWaterSpells();
void banWaterSkills();
void banWaterContent();

View File

@ -35,7 +35,7 @@ VCMI_LIB_NAMESPACE_BEGIN
CMapGenerator::CMapGenerator(CMapGenOptions& mapGenOptions, int RandomSeed) :
mapGenOptions(mapGenOptions), randomSeed(RandomSeed),
allowedPrisons(0), monolithIndex(0)
monolithIndex(0)
{
loadConfig();
rand.setSeed(this->randomSeed);
@ -96,12 +96,6 @@ const CMapGenOptions& CMapGenerator::getMapGenOptions() const
return mapGenOptions;
}
void CMapGenerator::initPrisonsRemaining()
{
allowedPrisons = map->getMap(this).allowedHeroes.size();
allowedPrisons = std::max<int> (0, allowedPrisons - 16 * mapGenOptions.getHumanOrCpuPlayerCount()); //so at least 16 heroes will be available for every player
}
void CMapGenerator::initQuestArtsRemaining()
{
//TODO: Move to QuestArtifactPlacer?
@ -122,7 +116,6 @@ std::unique_ptr<CMap> CMapGenerator::generate()
addHeaderInfo();
map->initTiles(*this, rand);
Load::Progress::step();
initPrisonsRemaining();
initQuestArtsRemaining();
genZones();
Load::Progress::step();
@ -468,11 +461,6 @@ int CMapGenerator::getNextMonlithIndex()
}
}
int CMapGenerator::getPrisonsRemaning() const
{
return allowedPrisons;
}
std::shared_ptr<CZonePlacer> CMapGenerator::getZonePlacer() const
{
return placer;
@ -488,6 +476,7 @@ const std::vector<HeroTypeID> CMapGenerator::getAllPossibleHeroes() const
auto isWaterMap = map->getMap(this).isWaterMap();
//Skip heroes that were banned, including the ones placed in prisons
std::vector<HeroTypeID> ret;
for (HeroTypeID hero : map->getMap(this).allowedHeroes)
{
auto * h = dynamic_cast<const CHero*>(VLC->heroTypes()->getById(hero));
@ -517,14 +506,12 @@ const std::vector<HeroTypeID> CMapGenerator::getAllPossibleHeroes() const
void CMapGenerator::banQuestArt(const ArtifactID & id)
{
//TODO: Protect with mutex
map->getMap(this).allowedArtifact.erase(id);
}
void CMapGenerator::banHero(const HeroTypeID & id)
void CMapGenerator::unbanQuestArt(const ArtifactID & id)
{
//TODO: Protect with mutex
map->getMap(this).banHero(id);
map->getMap(this).allowedArtifact.insert(id);
}
Zone * CMapGenerator::getZoneWater() const

View File

@ -65,8 +65,7 @@ public:
const std::vector<ArtifactID> & getAllPossibleQuestArtifacts() const;
const std::vector<HeroTypeID> getAllPossibleHeroes() const;
void banQuestArt(const ArtifactID & id);
void banHero(const HeroTypeID& id);
void unbanQuestArt(const ArtifactID & id);
Zone * getZoneWater() const;
void addWaterTreasuresInfo();
@ -82,7 +81,6 @@ private:
std::vector<rmg::ZoneConnection> connectionsLeft;
int allowedPrisons;
int monolithIndex;
std::vector<ArtifactID> questArtifacts;

View File

@ -19,6 +19,7 @@
#include "modificators/ObjectManager.h"
#include "modificators/RoadPlacer.h"
#include "modificators/TreasurePlacer.h"
#include "modificators/PrisonHeroPlacer.h"
#include "modificators/QuestArtifactPlacer.h"
#include "modificators/ConnectionsPlacer.h"
#include "modificators/TownPlacer.h"
@ -127,6 +128,7 @@ void RmgMap::initTiles(CMapGenerator & generator, CRandomGenerator & rand)
void RmgMap::addModificators()
{
bool hasObjectDistributor = false;
bool hasHeroPlacer = false;
bool hasRockFiller = false;
for(auto & z : getZones())
@ -139,6 +141,11 @@ void RmgMap::addModificators()
zone->addModificator<ObjectDistributor>();
hasObjectDistributor = true;
}
if (!hasHeroPlacer)
{
zone->addModificator<PrisonHeroPlacer>();
hasHeroPlacer = true;
}
zone->addModificator<TreasurePlacer>();
zone->addModificator<ObstaclePlacer>();
zone->addModificator<TerrainPainter>();

View File

@ -15,6 +15,7 @@
#include "../RmgMap.h"
#include "../CMapGenerator.h"
#include "TreasurePlacer.h"
#include "PrisonHeroPlacer.h"
#include "QuestArtifactPlacer.h"
#include "TownPlacer.h"
#include "TerrainPainter.h"
@ -75,7 +76,6 @@ void ObjectDistributor::distributeLimitedObjects()
auto rmgInfo = handler->getRMGInfo();
// FIXME: Random order of distribution
RandomGeneratorUtil::randomShuffle(matchingZones, zone.getRand());
for (auto& zone : matchingZones)
{
@ -146,7 +146,18 @@ void ObjectDistributor::distributePrisons()
RandomGeneratorUtil::randomShuffle(zones, zone.getRand());
size_t allowedPrisons = generator.getPrisonsRemaning();
// TODO: Some shorthand for unique Modificator
PrisonHeroPlacer * prisonHeroPlacer = nullptr;
for(auto & z : map.getZones())
{
prisonHeroPlacer = z.second->getModificator<PrisonHeroPlacer>();
if (prisonHeroPlacer)
{
break;
}
}
size_t allowedPrisons = prisonHeroPlacer->getPrisonsRemaning();
for (int i = zones.size() - 1; i >= 0; i--)
{
auto zone = zones[i].second;

View File

@ -0,0 +1,73 @@
/*
* PrisonHeroPlacer.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 "PrisonHeroPlacer.h"
#include "../CMapGenerator.h"
#include "../RmgMap.h"
#include "TreasurePlacer.h"
#include "../CZonePlacer.h"
#include "../../VCMI_Lib.h"
#include "../../mapObjectConstructors/AObjectTypeHandler.h"
#include "../../mapObjectConstructors/CObjectClassesHandler.h"
#include "../../mapObjects/MapObjects.h"
VCMI_LIB_NAMESPACE_BEGIN
void PrisonHeroPlacer::process()
{
getAllowedHeroes();
}
void PrisonHeroPlacer::init()
{
// Reserve at least 16 heroes for each player
reservedHeroes = 16 * generator.getMapGenOptions().getHumanOrCpuPlayerCount();
}
void PrisonHeroPlacer::getAllowedHeroes()
{
// TODO: Give each zone unique HeroPlacer with private hero list?
// Call that only once
if (allowedHeroes.empty())
{
allowedHeroes = generator.getAllPossibleHeroes();
}
}
int PrisonHeroPlacer::getPrisonsRemaning() const
{
return std::max<int>(allowedHeroes.size() - reservedHeroes, 0);
}
HeroTypeID PrisonHeroPlacer::drawRandomHero()
{
RecursiveLock lock(externalAccessMutex);
if (getPrisonsRemaning() > 0)
{
RandomGeneratorUtil::randomShuffle(allowedHeroes, zone.getRand());
HeroTypeID ret = allowedHeroes.back();
allowedHeroes.pop_back();
return ret;
}
else
{
throw rmgException("No unused heroes left for prisons!");
}
}
void PrisonHeroPlacer::restoreDrawnHero(const HeroTypeID & hid)
{
RecursiveLock lock(externalAccessMutex);
allowedHeroes.push_back(hid);
}
VCMI_LIB_NAMESPACE_END

View File

@ -0,0 +1,41 @@
/*
* PrisonHeroPlacer, 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 "../Functions.h"
#include "../../mapObjects/ObjectTemplate.h"
VCMI_LIB_NAMESPACE_BEGIN
class CRandomGenerator;
class PrisonHeroPlacer : public Modificator
{
public:
MODIFICATOR(PrisonHeroPlacer);
void process() override;
void init() override;
int getPrisonsRemaning() const;
[[nodiscard]] HeroTypeID drawRandomHero();
void restoreDrawnHero(const HeroTypeID & hid);
private:
void getAllowedHeroes();
size_t reservedHeroes;
protected:
std::vector<HeroTypeID> allowedHeroes;
};
VCMI_LIB_NAMESPACE_END

View File

@ -40,11 +40,18 @@ void QuestArtifactPlacer::addQuestArtZone(std::shared_ptr<Zone> otherZone)
void QuestArtifactPlacer::addQuestArtifact(const ArtifactID& id)
{
logGlobal->info("Need to place quest artifact %s", VLC->artifacts()->getById(id)->getNameTranslated());
RecursiveLock lock(externalAccessMutex);
logGlobal->info("Need to place quest artifact artifact %s", VLC->artifacts()->getById(id)->getNameTranslated());
questArtifactsToPlace.emplace_back(id);
}
void QuestArtifactPlacer::removeQuestArtifact(const ArtifactID& id)
{
logGlobal->info("Will not try to place quest artifact %s", VLC->artifacts()->getById(id)->getNameTranslated());
RecursiveLock lock(externalAccessMutex);
vstd::erase_if_present(questArtifactsToPlace, id);
}
void QuestArtifactPlacer::rememberPotentialArtifactToReplace(CGObjectInstance* obj)
{
RecursiveLock lock(externalAccessMutex);
@ -131,9 +138,10 @@ ArtifactID QuestArtifactPlacer::drawRandomArtifact()
RecursiveLock lock(externalAccessMutex);
if (!questArtifacts.empty())
{
RandomGeneratorUtil::randomShuffle(questArtifacts, zone.getRand());
ArtifactID ret = questArtifacts.back();
questArtifacts.pop_back();
RandomGeneratorUtil::randomShuffle(questArtifacts, zone.getRand());
generator.banQuestArt(ret);
return ret;
}
else
@ -142,10 +150,11 @@ ArtifactID QuestArtifactPlacer::drawRandomArtifact()
}
}
void QuestArtifactPlacer::addRandomArtifact(ArtifactID artid)
void QuestArtifactPlacer::addRandomArtifact(const ArtifactID & artid)
{
RecursiveLock lock(externalAccessMutex);
questArtifacts.push_back(artid);
generator.unbanQuestArt(artid);
}
VCMI_LIB_NAMESPACE_END

View File

@ -29,14 +29,15 @@ public:
void findZonesForQuestArts();
void addQuestArtifact(const ArtifactID& id);
void removeQuestArtifact(const ArtifactID& id);
void rememberPotentialArtifactToReplace(CGObjectInstance* obj);
std::vector<CGObjectInstance*> getPossibleArtifactsToReplace() const;
void placeQuestArtifacts(CRandomGenerator & rand);
void dropReplacedArtifact(CGObjectInstance* obj);
size_t getMaxQuestArtifactCount() const;
ArtifactID drawRandomArtifact();
void addRandomArtifact(ArtifactID artid);
[[nodiscard]] ArtifactID drawRandomArtifact();
void addRandomArtifact(const ArtifactID & artid);
protected:

View File

@ -18,6 +18,7 @@
#include "../RmgMap.h"
#include "../TileInfo.h"
#include "../CZonePlacer.h"
#include "PrisonHeroPlacer.h"
#include "QuestArtifactPlacer.h"
#include "../../ArtifactUtils.h"
#include "../../mapObjectConstructors/AObjectTypeHandler.h"
@ -32,6 +33,12 @@
VCMI_LIB_NAMESPACE_BEGIN
ObjectInfo::ObjectInfo():
destroyObject([](){})
{
}
void TreasurePlacer::process()
{
addAllPossibleObjects();
@ -45,6 +52,7 @@ void TreasurePlacer::init()
maxPrisons = 0; //Should be in the constructor, but we use macro for that
DEPENDENCY(ObjectManager);
DEPENDENCY(ConnectionsPlacer);
DEPENDENCY_ALL(PrisonHeroPlacer);
POSTFUNCTION(RoadPlacer);
}
@ -90,6 +98,16 @@ void TreasurePlacer::addAllPossibleObjects()
auto prisonTemplates = VLC->objtypeh->getHandlerFor(Obj::PRISON, 0)->getTemplates(zone.getTerrainType());
if (!prisonTemplates.empty())
{
PrisonHeroPlacer * prisonHeroPlacer = nullptr;
for(auto & z : map.getZones())
{
prisonHeroPlacer = z.second->getModificator<PrisonHeroPlacer>();
if (prisonHeroPlacer)
{
break;
}
}
//prisons
//levels 1, 5, 10, 20, 30
static int prisonsLevels = std::min(generator.getConfig().prisonExperience.size(), generator.getConfig().prisonValues.size());
@ -97,16 +115,22 @@ 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
oi.value = generator.getConfig().prisonValues[i];
if (oi.value > zone.getMaxTreasureValue())
{
continue;
}
oi.generateObject = [i, this]() -> CGObjectInstance*
oi.generateObject = [i, this, prisonHeroPlacer, &oi]() -> CGObjectInstance*
{
auto possibleHeroes = generator.getAllPossibleHeroes();
HeroTypeID hid = *RandomGeneratorUtil::nextItem(possibleHeroes, zone.getRand());
HeroTypeID hid = prisonHeroPlacer->drawRandomHero();
oi.destroyObject = [hid, prisonHeroPlacer]()
{
// Hero can be used again
prisonHeroPlacer->restoreDrawnHero(hid);
};
auto factory = VLC->objtypeh->getHandlerFor(Obj::PRISON, 0);
auto* obj = dynamic_cast<CGHeroInstance*>(factory->create());
@ -114,7 +138,6 @@ void TreasurePlacer::addAllPossibleObjects()
obj->setHeroType(hid); //will be initialized later
obj->exp = generator.getConfig().prisonExperience[i];
obj->setOwner(PlayerColor::NEUTRAL);
generator.banHero(hid);
return obj;
};
@ -441,6 +464,19 @@ void TreasurePlacer::addAllPossibleObjects()
RandomGeneratorUtil::randomShuffle(creatures, zone.getRand());
auto setRandomArtifact = [qap, &oi](CGSeerHut * obj)
{
ArtifactID artid = qap->drawRandomArtifact();
oi.destroyObject = [artid, qap]()
{
// Artifact can be used again
qap->addRandomArtifact(artid);
qap->removeQuestArtifact(artid);
};
obj->quest->mission.artifacts.push_back(artid);
qap->addQuestArtifact(artid);
};
for(int i = 0; i < static_cast<int>(creatures.size()); i++)
{
auto * creature = creatures[i];
@ -451,7 +487,8 @@ void TreasurePlacer::addAllPossibleObjects()
int randomAppearance = chooseRandomAppearance(zone.getRand(), Obj::SEER_HUT, zone.getTerrainType());
oi.generateObject = [creature, creaturesAmount, randomAppearance, this, qap]() -> CGObjectInstance *
// FIXME: Remove duplicated code for gold, exp and creaure reward
oi.generateObject = [creature, creaturesAmount, randomAppearance, setRandomArtifact]() -> CGObjectInstance *
{
auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance);
auto * obj = dynamic_cast<CGSeerHut *>(factory->create());
@ -461,11 +498,7 @@ void TreasurePlacer::addAllPossibleObjects()
reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
obj->configuration.info.push_back(reward);
ArtifactID artid = qap->drawRandomArtifact();
obj->quest->mission.artifacts.push_back(artid);
generator.banQuestArt(artid);
zone.getModificator<QuestArtifactPlacer>()->addQuestArtifact(artid);
setRandomArtifact(obj);
return obj;
};
@ -499,7 +532,7 @@ void TreasurePlacer::addAllPossibleObjects()
oi.probability = 10;
oi.maxPerZone = 1;
oi.generateObject = [i, randomAppearance, this, qap]() -> CGObjectInstance *
oi.generateObject = [i, randomAppearance, this, setRandomArtifact]() -> CGObjectInstance *
{
auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance);
auto * obj = dynamic_cast<CGSeerHut *>(factory->create());
@ -508,20 +541,16 @@ void TreasurePlacer::addAllPossibleObjects()
reward.reward.heroExperience = generator.getConfig().questRewardValues[i];
reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
obj->configuration.info.push_back(reward);
ArtifactID artid = qap->drawRandomArtifact();
obj->quest->mission.artifacts.push_back(artid);
generator.banQuestArt(artid);
zone.getModificator<QuestArtifactPlacer>()->addQuestArtifact(artid);
setRandomArtifact(obj);
return obj;
};
if(!oi.templates.empty())
possibleSeerHuts.push_back(oi);
oi.generateObject = [i, randomAppearance, this, qap]() -> CGObjectInstance *
oi.generateObject = [i, randomAppearance, this, setRandomArtifact]() -> CGObjectInstance *
{
auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance);
auto * obj = dynamic_cast<CGSeerHut *>(factory->create());
@ -531,11 +560,7 @@ void TreasurePlacer::addAllPossibleObjects()
reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
obj->configuration.info.push_back(reward);
ArtifactID artid = qap->drawRandomArtifact();
obj->quest->mission.artifacts.push_back(artid);
generator.banQuestArt(artid);
zone.getModificator<QuestArtifactPlacer>()->addQuestArtifact(artid);
setRandomArtifact(obj);
return obj;
};
@ -641,8 +666,14 @@ rmg::Object TreasurePlacer::constructTreasurePile(const std::vector<ObjectInfo*>
}
auto * object = oi->generateObject();
if(oi->templates.empty())
{
logGlobal->warn("Deleting randomized object with no templates: %s", object->getObjectName());
oi->destroyObject();
delete object;
continue;
}
auto templates = object->getObjectHandler()->getMostSpecificTemplates(zone.getTerrainType());
@ -721,7 +752,7 @@ rmg::Object TreasurePlacer::constructTreasurePile(const std::vector<ObjectInfo*>
instanceAccessibleArea.add(instance.getVisitablePosition());
}
//first object is good
//Do not clean up after first object
if(rmgObject.instances().size() == 1)
break;
@ -800,10 +831,10 @@ void TreasurePlacer::createTreasures(ObjectManager& manager)
{
for (auto* oi : treasurePile)
{
oi->destroyObject();
oi->maxPerZone++;
}
};
//place biggest treasures first at large distance, place smaller ones inbetween
auto treasureInfo = zone.getTreasureInfo();
boost::sort(treasureInfo, valueComparator);

View File

@ -22,12 +22,15 @@ class CRandomGenerator;
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()> destroyObject;
void setTemplates(MapObjectID type, MapObjectSubID subtype, TerrainId terrain);
};

View File

@ -131,7 +131,12 @@ void MapController::repairMap(CMap * map) const
//fix hero instance
if(auto * nih = dynamic_cast<CGHeroInstance*>(obj.get()))
{
// All heroes present on map or in prisons need to be allowed to rehire them after they are defeated
// FIXME: How about custom scenarios where defeated hero cannot be hired again?
map->allowedHeroes.insert(nih->getHeroType());
auto type = VLC->heroh->objects[nih->subID];
assert(type->heroClass);
//TODO: find a way to get proper type name
@ -198,8 +203,6 @@ void MapController::repairMap(CMap * map) const
auto a = ArtifactUtils::createScroll(*RandomGeneratorUtil::nextItem(out, CRandomGenerator::getDefault()));
art->storedArtifact = a;
}
else
map->allowedArtifact.insert(art->getArtifact());
}
}
}