1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-28 08:48:48 +02:00
vcmi/lib/gameState/CGameState.cpp
Ivan Savenko 7ffe014d6b Remove allowed artifacts list from arthandler
1. Handlers should not contain non-const game state data
2. This field was duplicating same field in CMap
3. Due to removal of VLC serialization, this field is not updated on map
load leading to issues with artifact randomization
2023-12-11 15:06:04 +02:00

2019 lines
55 KiB
C++

/*
* CGameState.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 "CGameState.h"
#include "EVictoryLossCheckResult.h"
#include "InfoAboutArmy.h"
#include "TavernHeroesPool.h"
#include "CGameStateCampaign.h"
#include "SThievesGuildInfo.h"
#include "../ArtifactUtils.h"
#include "../CBuildingHandler.h"
#include "../CGeneralTextHandler.h"
#include "../CHeroHandler.h"
#include "../CPlayerState.h"
#include "../CStopWatch.h"
#include "../GameSettings.h"
#include "../StartInfo.h"
#include "../TerrainHandler.h"
#include "../VCMIDirs.h"
#include "../VCMI_Lib.h"
#include "../battle/BattleInfo.h"
#include "../campaign/CampaignState.h"
#include "../constants/StringConstants.h"
#include "../filesystem/ResourcePath.h"
#include "../mapObjectConstructors/AObjectTypeHandler.h"
#include "../mapObjectConstructors/CObjectClassesHandler.h"
#include "../mapObjectConstructors/DwellingInstanceConstructor.h"
#include "../mapObjects/CGHeroInstance.h"
#include "../mapObjects/CGTownInstance.h"
#include "../mapping/CMap.h"
#include "../mapping/CMapEditManager.h"
#include "../mapping/CMapService.h"
#include "../modding/IdentifierStorage.h"
#include "../pathfinder/CPathfinder.h"
#include "../pathfinder/PathfinderOptions.h"
#include "../registerTypes/RegisterTypesClientPacks.h"
#include "../rmg/CMapGenerator.h"
#include "../serializer/CMemorySerializer.h"
#include "../serializer/CTypeList.h"
#include "../spells/CSpellHandler.h"
VCMI_LIB_NAMESPACE_BEGIN
boost::shared_mutex CGameState::mutex;
template <typename T> class CApplyOnGS;
class CBaseForGSApply
{
public:
virtual void applyOnGS(CGameState *gs, void *pack) const =0;
virtual ~CBaseForGSApply() = default;
template<typename U> static CBaseForGSApply *getApplier(const U * t=nullptr)
{
return new CApplyOnGS<U>();
}
};
template <typename T> class CApplyOnGS : public CBaseForGSApply
{
public:
void applyOnGS(CGameState *gs, void *pack) const override
{
T *ptr = static_cast<T*>(pack);
boost::unique_lock<boost::shared_mutex> lock(CGameState::mutex);
ptr->applyGs(gs);
}
};
HeroTypeID CGameState::pickNextHeroType(const PlayerColor & owner)
{
const PlayerSettings &ps = scenarioOps->getIthPlayersSettings(owner);
if(ps.hero >= HeroTypeID(0) && !isUsedHero(HeroTypeID(ps.hero))) //we haven't used selected hero
{
return HeroTypeID(ps.hero);
}
return pickUnusedHeroTypeRandomly(owner);
}
HeroTypeID CGameState::pickUnusedHeroTypeRandomly(const PlayerColor & owner)
{
//list of available heroes for this faction and others
std::vector<HeroTypeID> factionHeroes;
std::vector<HeroTypeID> otherHeroes;
const PlayerSettings &ps = scenarioOps->getIthPlayersSettings(owner);
for(const HeroTypeID & hid : getUnusedAllowedHeroes())
{
if(VLC->heroh->objects[hid.getNum()]->heroClass->faction == ps.castle)
factionHeroes.push_back(hid);
else
otherHeroes.push_back(hid);
}
// select random hero native to "our" faction
if(!factionHeroes.empty())
{
return *RandomGeneratorUtil::nextItem(factionHeroes, getRandomGenerator());
}
logGlobal->warn("Cannot find free hero of appropriate faction for player %s - trying to get first available...", owner.toString());
if(!otherHeroes.empty())
{
return *RandomGeneratorUtil::nextItem(otherHeroes, getRandomGenerator());
}
logGlobal->error("No free allowed heroes!");
auto notAllowedHeroesButStillBetterThanCrash = getUnusedAllowedHeroes(true);
if(!notAllowedHeroesButStillBetterThanCrash.empty())
return *notAllowedHeroesButStillBetterThanCrash.begin();
logGlobal->error("No free heroes at all!");
throw std::runtime_error("Can not allocate hero. All heroes are already used.");
}
int CGameState::getDate(Date mode) const
{
int temp;
switch (mode)
{
case Date::DAY:
return day;
case Date::DAY_OF_WEEK: //day of week
temp = (day)%7; // 1 - Monday, 7 - Sunday
return temp ? temp : 7;
case Date::WEEK: //current week
temp = ((day-1)/7)+1;
if (!(temp%4))
return 4;
else
return (temp%4);
case Date::MONTH: //current month
return ((day-1)/28)+1;
case Date::DAY_OF_MONTH: //day of month
temp = (day)%28;
if (temp)
return temp;
else return 28;
}
return 0;
}
CGameState::CGameState()
{
gs = this;
heroesPool = std::make_unique<TavernHeroesPool>();
applier = std::make_shared<CApplier<CBaseForGSApply>>();
registerTypesClientPacks(*applier);
globalEffects.setNodeType(CBonusSystemNode::GLOBAL_EFFECTS);
}
CGameState::~CGameState()
{
// explicitly delete all ongoing battles first - BattleInfo destructor requires valid CGameState
currentBattles.clear();
map.dellNull();
scenarioOps.dellNull();
initialOpts.dellNull();
}
void CGameState::preInit(Services * services)
{
this->services = services;
}
void CGameState::init(const IMapService * mapService, StartInfo * si, Load::ProgressAccumulator & progressTracking, bool allowSavingRandomMap)
{
preInitAuto();
logGlobal->info("\tUsing random seed: %d", si->seedToBeUsed);
getRandomGenerator().setSeed(si->seedToBeUsed);
scenarioOps = CMemorySerializer::deepCopy(*si).release();
initialOpts = CMemorySerializer::deepCopy(*si).release();
si = nullptr;
switch(scenarioOps->mode)
{
case StartInfo::NEW_GAME:
initNewGame(mapService, allowSavingRandomMap, progressTracking);
break;
case StartInfo::CAMPAIGN:
initCampaign();
break;
default:
logGlobal->error("Wrong mode: %d", static_cast<int>(scenarioOps->mode));
return;
}
logGlobal->info("Map loaded!");
checkMapChecksum();
day = 0;
logGlobal->debug("Initialization:");
initGlobalBonuses();
initPlayerStates();
if (campaign)
campaign->placeCampaignHeroes();
removeHeroPlaceholders();
initGrailPosition();
initRandomFactionsForPlayers();
randomizeMapObjects();
placeStartingHeroes();
initDifficulty();
initHeroes();
initStartingBonus();
initTowns();
placeHeroesInTowns();
initMapObjects();
buildBonusSystemTree();
initVisitingAndGarrisonedHeroes();
initFogOfWar();
for(auto & elem : teams)
{
CGObelisk::visited[elem.first] = 0;
}
logGlobal->debug("\tChecking objectives");
map->checkForObjectives(); //needs to be run when all objects are properly placed
auto seedAfterInit = getRandomGenerator().nextInt();
logGlobal->info("Seed after init is %d (before was %d)", seedAfterInit, scenarioOps->seedToBeUsed);
if(scenarioOps->seedPostInit > 0)
{
//RNG must be in the same state on all machines when initialization is done (otherwise we have desync)
assert(scenarioOps->seedPostInit == seedAfterInit);
}
else
{
scenarioOps->seedPostInit = seedAfterInit; //store the post init "seed"
}
}
void CGameState::updateEntity(Metatype metatype, int32_t index, const JsonNode & data)
{
switch(metatype)
{
case Metatype::ARTIFACT_INSTANCE:
logGlobal->error("Artifact instance update is not implemented");
break;
case Metatype::CREATURE_INSTANCE:
logGlobal->error("Creature instance update is not implemented");
break;
case Metatype::HERO_INSTANCE:
//index is hero type
if(index >= 0 && index < map->allHeroes.size())
{
CGHeroInstance * hero = map->allHeroes.at(index);
hero->updateFrom(data);
}
else
{
logGlobal->error("Update entity: hero index %s is out of range [%d,%d]", index, 0, map->allHeroes.size());
}
break;
case Metatype::MAP_OBJECT_INSTANCE:
if(index >= 0 && index < map->objects.size())
{
CGObjectInstance * obj = getObjInstance(ObjectInstanceID(index));
obj->updateFrom(data);
}
else
{
logGlobal->error("Update entity: object index %s is out of range [%d,%d]", index, 0, map->objects.size());
}
break;
default:
services->updateEntity(metatype, index, data);
break;
}
}
void CGameState::updateOnLoad(StartInfo * si)
{
preInitAuto();
scenarioOps->playerInfos = si->playerInfos;
for(auto & i : si->playerInfos)
gs->players[i.first].human = i.second.isControlledByHuman();
}
void CGameState::preInitAuto()
{
if(services == nullptr)
{
logGlobal->error("Game state preinit missing");
preInit(VLC);
}
}
void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRandomMap, Load::ProgressAccumulator & progressTracking)
{
if(scenarioOps->createRandomMap())
{
logGlobal->info("Create random map.");
CStopWatch sw;
// Gen map
CMapGenerator mapGenerator(*scenarioOps->mapGenOptions, scenarioOps->seedToBeUsed);
progressTracking.include(mapGenerator);
std::unique_ptr<CMap> randomMap = mapGenerator.generate();
progressTracking.exclude(mapGenerator);
if(allowSavingRandomMap)
{
try
{
auto path = VCMIDirs::get().userDataPath() / "Maps" / "RandomMaps";
boost::filesystem::create_directories(path);
std::shared_ptr<CMapGenOptions> options = scenarioOps->mapGenOptions;
const std::string templateName = options->getMapTemplate()->getName();
const ui32 seed = scenarioOps->seedToBeUsed;
const std::string dt = vstd::getDateTimeISO8601Basic(std::time(nullptr));
const std::string fileName = boost::str(boost::format("%s_%s_%d.vmap") % dt % templateName % seed );
const auto fullPath = path / fileName;
randomMap->name.appendRawString(boost::str(boost::format(" %s") % dt));
mapService->saveMap(randomMap, fullPath);
logGlobal->info("Random map has been saved to:");
logGlobal->info(fullPath.string());
}
catch(...)
{
logGlobal->error("Saving random map failed with exception");
}
}
map = randomMap.release();
// Update starting options
for(int i = 0; i < map->players.size(); ++i)
{
const auto & playerInfo = map->players[i];
if(playerInfo.canAnyonePlay())
{
PlayerSettings & playerSettings = scenarioOps->playerInfos[PlayerColor(i)];
playerSettings.compOnly = !playerInfo.canHumanPlay;
playerSettings.castle = playerInfo.defaultCastle();
if(playerSettings.isControlledByAI() && playerSettings.name.empty())
{
playerSettings.name = VLC->generaltexth->allTexts[468];
}
playerSettings.color = PlayerColor(i);
}
else
{
scenarioOps->playerInfos.erase(PlayerColor(i));
}
}
logGlobal->info("Generated random map in %i ms.", sw.getDiff());
}
else
{
logGlobal->info("Open map file: %s", scenarioOps->mapname);
const ResourcePath mapURI(scenarioOps->mapname, EResType::MAP);
map = mapService->loadMap(mapURI).release();
}
}
void CGameState::initCampaign()
{
campaign = std::make_unique<CGameStateCampaign>(this);
map = campaign->getCurrentMap().release();
}
void CGameState::checkMapChecksum()
{
logGlobal->info("\tOur checksum for the map: %d", map->checksum);
if(scenarioOps->mapfileChecksum)
{
logGlobal->info("\tServer checksum for %s: %d", scenarioOps->mapname, scenarioOps->mapfileChecksum);
if(map->checksum != scenarioOps->mapfileChecksum)
{
logGlobal->error("Wrong map checksum!!!");
throw std::runtime_error("Wrong checksum");
}
}
else
{
scenarioOps->mapfileChecksum = map->checksum;
}
}
void CGameState::initGlobalBonuses()
{
const JsonNode & baseBonuses = VLC->settings()->getValue(EGameSettings::BONUSES_GLOBAL);
logGlobal->debug("\tLoading global bonuses");
for(const auto & b : baseBonuses.Struct())
{
auto bonus = JsonUtils::parseBonus(b.second);
bonus->source = BonusSource::GLOBAL;//for all
bonus->sid = BonusSourceID(); //there is one global object
globalEffects.addNewBonus(bonus);
}
VLC->creh->loadCrExpBon(globalEffects);
}
void CGameState::initDifficulty()
{
logGlobal->debug("\tLoading difficulty settings");
const JsonNode config = JsonUtils::assembleFromFiles("config/difficulty.json");
const JsonNode & difficultyAI(config["ai"][GameConstants::DIFFICULTY_NAMES[scenarioOps->difficulty]]);
const JsonNode & difficultyHuman(config["human"][GameConstants::DIFFICULTY_NAMES[scenarioOps->difficulty]]);
auto setDifficulty = [](PlayerState & state, const JsonNode & json)
{
//set starting resources
state.resources = TResources(json["resources"]);
//set global bonuses
for(auto & jsonBonus : json["globalBonuses"].Vector())
if(auto bonus = JsonUtils::parseBonus(jsonBonus))
state.addNewBonus(bonus);
//set battle bonuses
for(auto & jsonBonus : json["battleBonuses"].Vector())
if(auto bonus = JsonUtils::parseBonus(jsonBonus))
state.battleBonuses.push_back(*bonus);
};
for (auto & elem : players)
{
PlayerState &p = elem.second;
setDifficulty(p, p.human ? difficultyHuman : difficultyAI);
}
if (campaign)
campaign->initStartingResources();
}
void CGameState::initGrailPosition()
{
logGlobal->debug("\tPicking grail position");
//pick grail location
if(map->grailPos.x < 0 || map->grailRadius) //grail not set or set within a radius around some place
{
if(!map->grailRadius) //radius not given -> anywhere on map
map->grailRadius = map->width * 2;
std::vector<int3> allowedPos;
static const int BORDER_WIDTH = 9; // grail must be at least 9 tiles away from border
// add all not blocked tiles in range
for (int z = 0; z < map->levels(); z++)
{
for(int x = BORDER_WIDTH; x < map->width - BORDER_WIDTH ; x++)
{
for(int y = BORDER_WIDTH; y < map->height - BORDER_WIDTH; y++)
{
const TerrainTile &t = map->getTile(int3(x, y, z));
if(!t.blocked
&& !t.visitable
&& t.terType->isLand()
&& t.terType->isPassable()
&& (int)map->grailPos.dist2dSQ(int3(x, y, z)) <= (map->grailRadius * map->grailRadius))
allowedPos.emplace_back(x, y, z);
}
}
}
//remove tiles with holes
for(auto & elem : map->objects)
if(elem && elem->ID == Obj::HOLE)
allowedPos -= elem->pos;
if(!allowedPos.empty())
{
map->grailPos = *RandomGeneratorUtil::nextItem(allowedPos, getRandomGenerator());
}
else
{
logGlobal->warn("Grail cannot be placed, no appropriate tile found!");
}
}
}
void CGameState::initRandomFactionsForPlayers()
{
logGlobal->debug("\tPicking random factions for players");
for(auto & elem : scenarioOps->playerInfos)
{
if(elem.second.castle==FactionID::RANDOM)
{
auto randomID = getRandomGenerator().nextInt((int)map->players[elem.first.getNum()].allowedFactions.size() - 1);
auto iter = map->players[elem.first.getNum()].allowedFactions.begin();
std::advance(iter, randomID);
elem.second.castle = *iter;
}
}
}
void CGameState::randomizeMapObjects()
{
logGlobal->debug("\tRandomizing objects");
for(CGObjectInstance *object : map->objects)
{
if(!object)
continue;
object->pickRandomObject(getRandomGenerator());
//handle Favouring Winds - mark tiles under it
if(object->ID == Obj::FAVORABLE_WINDS)
{
for (int i = 0; i < object->getWidth() ; i++)
{
for (int j = 0; j < object->getHeight() ; j++)
{
int3 pos = object->pos - int3(i,j,0);
if(map->isInTheMap(pos)) map->getTile(pos).extTileFlags |= 128;
}
}
}
}
}
void CGameState::initPlayerStates()
{
logGlobal->debug("\tCreating player entries in gs");
for(auto & elem : scenarioOps->playerInfos)
{
PlayerState & p = players[elem.first];
p.color=elem.first;
p.human = elem.second.isControlledByHuman();
p.team = map->players[elem.first.getNum()].team;
teams[p.team].id = p.team;//init team
teams[p.team].players.insert(elem.first);//add player to team
}
}
void CGameState::placeStartingHero(const PlayerColor & playerColor, const HeroTypeID & heroTypeId, int3 townPos)
{
for(auto town : map->towns)
{
if(town->getPosition() == townPos)
{
townPos = town->visitablePos();
break;
}
}
auto handler = VLC->objtypeh->getHandlerFor(Obj::HERO, heroTypeId.toHeroType()->heroClass->getIndex());
CGObjectInstance * obj = handler->create(handler->getTemplates().front());
CGHeroInstance * hero = dynamic_cast<CGHeroInstance *>(obj);
hero->ID = Obj::HERO;
hero->setHeroType(heroTypeId);
hero->tempOwner = playerColor;
hero->pos = townPos;
hero->pos += hero->getVisitableOffset();
map->getEditManager()->insertObject(hero);
}
void CGameState::placeStartingHeroes()
{
logGlobal->debug("\tGiving starting hero");
for(auto & playerSettingPair : scenarioOps->playerInfos)
{
auto playerColor = playerSettingPair.first;
auto & playerInfo = map->players[playerColor.getNum()];
if(playerInfo.generateHeroAtMainTown && playerInfo.hasMainTown)
{
// Do not place a starting hero if the hero was already placed due to a campaign bonus
if (campaign && campaign->playerHasStartingHero(playerColor))
continue;
HeroTypeID heroTypeId = pickNextHeroType(playerColor);
if(playerSettingPair.second.hero == HeroTypeID::NONE)
playerSettingPair.second.hero = heroTypeId;
placeStartingHero(playerColor, HeroTypeID(heroTypeId), playerInfo.posOfMainTown);
}
}
}
void CGameState::removeHeroPlaceholders()
{
// remove any hero placeholders that remain on map after (potential) campaign heroes placement
for(auto obj : map->objects)
{
if(obj && obj->ID == Obj::HERO_PLACEHOLDER)
{
auto heroPlaceholder = dynamic_cast<CGHeroPlaceholder *>(obj.get());
map->removeBlockVisTiles(heroPlaceholder, true);
map->instanceNames.erase(obj->instanceName);
map->objects[heroPlaceholder->id.getNum()] = nullptr;
delete heroPlaceholder;
}
}
}
void CGameState::initHeroes()
{
for(auto hero : map->heroesOnMap) //heroes instances initialization
{
if (hero->getOwner() == PlayerColor::UNFLAGGABLE)
{
logGlobal->warn("Hero with uninitialized owner!");
continue;
}
hero->initHero(getRandomGenerator());
getPlayerState(hero->getOwner())->heroes.push_back(hero);
map->allHeroes[hero->getHeroType().getNum()] = hero;
}
// generate boats for all heroes on water
for(auto hero : map->heroesOnMap)
{
assert(map->isInTheMap(hero->visitablePos()));
const auto & tile = map->getTile(hero->visitablePos());
if (tile.terType->isWater())
{
auto handler = VLC->objtypeh->getHandlerFor(Obj::BOAT, hero->getBoatType().getNum());
CGBoat * boat = dynamic_cast<CGBoat*>(handler->create());
handler->configureObject(boat, gs->getRandomGenerator());
boat->pos = hero->pos;
boat->appearance = handler->getTemplates().front();
boat->id = ObjectInstanceID(static_cast<si32>(gs->map->objects.size()));
map->objects.emplace_back(boat);
map->addBlockVisTiles(boat);
hero->attachToBoat(boat);
}
}
for(auto obj : map->objects) //prisons
{
if(obj && obj->ID == Obj::PRISON)
{
auto * hero = dynamic_cast<CGHeroInstance*>(obj.get());
hero->initHero(getRandomGenerator());
map->allHeroes[hero->getHeroType().getNum()] = hero;
}
}
std::set<HeroTypeID> heroesToCreate = getUnusedAllowedHeroes(); //ids of heroes to be created and put into the pool
for(auto ph : map->predefinedHeroes)
{
if(!vstd::contains(heroesToCreate, ph->getHeroType()))
continue;
ph->initHero(getRandomGenerator());
heroesPool->addHeroToPool(ph);
heroesToCreate.erase(ph->type->getId());
map->allHeroes[ph->getHeroType().getNum()] = ph;
}
for(const HeroTypeID & htype : heroesToCreate) //all not used allowed heroes go with default state into the pool
{
auto * vhi = new CGHeroInstance();
vhi->initHero(getRandomGenerator(), htype);
int typeID = htype.getNum();
map->allHeroes[typeID] = vhi;
heroesPool->addHeroToPool(vhi);
}
for(auto & elem : map->disposedHeroes)
heroesPool->setAvailability(elem.heroId, elem.players);
if (campaign)
campaign->initHeroes();
}
void CGameState::initFogOfWar()
{
logGlobal->debug("\tFog of war"); //FIXME: should be initialized after all bonuses are set
int layers = map->levels();
for(auto & elem : teams)
{
auto & fow = elem.second.fogOfWarMap;
fow->resize(boost::extents[layers][map->width][map->height]);
std::fill(fow->data(), fow->data() + fow->num_elements(), 0);
for(CGObjectInstance *obj : map->objects)
{
if(!obj || !vstd::contains(elem.second.players, obj->tempOwner)) continue; //not a flagged object
std::unordered_set<int3> tiles;
getTilesInRange(tiles, obj->getSightCenter(), obj->getSightRadius(), ETileVisibility::HIDDEN, obj->tempOwner);
for(const int3 & tile : tiles)
{
(*elem.second.fogOfWarMap)[tile.z][tile.x][tile.y] = 1;
}
}
}
}
void CGameState::initStartingBonus()
{
if (scenarioOps->mode == StartInfo::CAMPAIGN)
return;
// These are the single scenario bonuses; predefined
// campaign bonuses are spread out over other init* functions.
logGlobal->debug("\tStarting bonuses");
for(auto & elem : players)
{
//starting bonus
if(scenarioOps->playerInfos[elem.first].bonus == PlayerStartingBonus::RANDOM)
scenarioOps->playerInfos[elem.first].bonus = static_cast<PlayerStartingBonus>(getRandomGenerator().nextInt(2));
switch(scenarioOps->playerInfos[elem.first].bonus)
{
case PlayerStartingBonus::GOLD:
elem.second.resources[EGameResID::GOLD] += getRandomGenerator().nextInt(5, 10) * 100;
break;
case PlayerStartingBonus::RESOURCE:
{
auto res = (*VLC->townh)[scenarioOps->playerInfos[elem.first].castle]->town->primaryRes;
if(res == EGameResID::WOOD_AND_ORE)
{
int amount = getRandomGenerator().nextInt(5, 10);
elem.second.resources[EGameResID::WOOD] += amount;
elem.second.resources[EGameResID::ORE] += amount;
}
else
{
elem.second.resources[res] += getRandomGenerator().nextInt(3, 6);
}
break;
}
case PlayerStartingBonus::ARTIFACT:
{
if(elem.second.heroes.empty())
{
logGlobal->error("Cannot give starting artifact - no heroes!");
break;
}
const Artifact * toGive = pickRandomArtifact(getRandomGenerator(), CArtifact::ART_TREASURE).toEntity(VLC);
CGHeroInstance *hero = elem.second.heroes[0];
if(!giveHeroArtifact(hero, toGive->getId()))
logGlobal->error("Cannot give starting artifact - no free slots!");
}
break;
}
}
}
void CGameState::initTowns()
{
logGlobal->debug("\tTowns");
if (campaign)
campaign->initTowns();
CGTownInstance::universitySkills.clear();
CGTownInstance::universitySkills.push_back(SecondarySkill(SecondarySkill::FIRE_MAGIC));
CGTownInstance::universitySkills.push_back(SecondarySkill(SecondarySkill::AIR_MAGIC));
CGTownInstance::universitySkills.push_back(SecondarySkill(SecondarySkill::WATER_MAGIC));
CGTownInstance::universitySkills.push_back(SecondarySkill(SecondarySkill::EARTH_MAGIC));
for (auto & elem : map->towns)
{
CGTownInstance * vti =(elem);
assert(vti->town);
if(vti->getNameTextID().empty())
{
size_t nameID = getRandomGenerator().nextInt(vti->getTown()->getRandomNamesCount() - 1);
vti->setNameTextId(vti->getTown()->getRandomNameTextID(nameID));
}
static const BuildingID basicDwellings[] = { BuildingID::DWELL_FIRST, BuildingID::DWELL_LVL_2, BuildingID::DWELL_LVL_3, BuildingID::DWELL_LVL_4, BuildingID::DWELL_LVL_5, BuildingID::DWELL_LVL_6, BuildingID::DWELL_LVL_7 };
static const BuildingID upgradedDwellings[] = { BuildingID::DWELL_UP_FIRST, BuildingID::DWELL_LVL_2_UP, BuildingID::DWELL_LVL_3_UP, BuildingID::DWELL_LVL_4_UP, BuildingID::DWELL_LVL_5_UP, BuildingID::DWELL_LVL_6_UP, BuildingID::DWELL_LVL_7_UP };
static const BuildingID hordes[] = { BuildingID::HORDE_PLACEHOLDER1, BuildingID::HORDE_PLACEHOLDER2, BuildingID::HORDE_PLACEHOLDER3, BuildingID::HORDE_PLACEHOLDER4, BuildingID::HORDE_PLACEHOLDER5, BuildingID::HORDE_PLACEHOLDER6, BuildingID::HORDE_PLACEHOLDER7 };
//init buildings
if(vstd::contains(vti->builtBuildings, BuildingID::DEFAULT)) //give standard set of buildings
{
vti->builtBuildings.erase(BuildingID::DEFAULT);
vti->builtBuildings.insert(BuildingID::VILLAGE_HALL);
if(vti->tempOwner != PlayerColor::NEUTRAL)
vti->builtBuildings.insert(BuildingID::TAVERN);
auto definesBuildingsChances = VLC->settings()->getVector(EGameSettings::TOWNS_STARTING_DWELLING_CHANCES);
for(int i = 0; i < definesBuildingsChances.size(); i++)
{
if((getRandomGenerator().nextInt(1,100) <= definesBuildingsChances[i]))
{
vti->builtBuildings.insert(basicDwellings[i]);
}
}
}
// village hall must always exist
vti->builtBuildings.insert(BuildingID::VILLAGE_HALL);
//init hordes
for (int i = 0; i < GameConstants::CREATURES_PER_TOWN; i++)
{
if (vstd::contains(vti->builtBuildings, hordes[i])) //if we have horde for this level
{
vti->builtBuildings.erase(hordes[i]);//remove old ID
if (vti->getTown()->hordeLvl.at(0) == i)//if town first horde is this one
{
vti->builtBuildings.insert(BuildingID::HORDE_1);//add it
//if we have upgraded dwelling as well
if (vstd::contains(vti->builtBuildings, upgradedDwellings[i]))
vti->builtBuildings.insert(BuildingID::HORDE_1_UPGR);//add it as well
}
if (vti->getTown()->hordeLvl.at(1) == i)//if town second horde is this one
{
vti->builtBuildings.insert(BuildingID::HORDE_2);
if (vstd::contains(vti->builtBuildings, upgradedDwellings[i]))
vti->builtBuildings.insert(BuildingID::HORDE_2_UPGR);
}
}
}
//#1444 - remove entries that don't have buildings defined (like some unused extra town hall buildings)
//But DO NOT remove horde placeholders before they are replaced
vstd::erase_if(vti->builtBuildings, [vti](const BuildingID & bid)
{
return !vti->getTown()->buildings.count(bid) || !vti->getTown()->buildings.at(bid);
});
if (vstd::contains(vti->builtBuildings, BuildingID::SHIPYARD) && vti->shipyardStatus()==IBoatGenerator::TILE_BLOCKED)
vti->builtBuildings.erase(BuildingID::SHIPYARD);//if we have harbor without water - erase it (this is H3 behaviour)
//Early check for #1444-like problems
for([[maybe_unused]] const auto & building : vti->builtBuildings)
{
assert(vti->getTown()->buildings.at(building) != nullptr);
}
//town events
for(CCastleEvent &ev : vti->events)
{
for (int i = 0; i<GameConstants::CREATURES_PER_TOWN; i++)
if (vstd::contains(ev.buildings,hordes[i])) //if we have horde for this level
{
ev.buildings.erase(hordes[i]);
if (vti->getTown()->hordeLvl.at(0) == i)
ev.buildings.insert(BuildingID::HORDE_1);
if (vti->getTown()->hordeLvl.at(1) == i)
ev.buildings.insert(BuildingID::HORDE_2);
}
}
//init spells
vti->spells.resize(GameConstants::SPELL_LEVELS);
for(ui32 z=0; z<vti->obligatorySpells.size();z++)
{
const auto * s = vti->obligatorySpells[z].toSpell();
vti->spells[s->getLevel()-1].push_back(s->id);
vti->possibleSpells -= s->id;
}
while(!vti->possibleSpells.empty())
{
ui32 total=0;
int sel = -1;
for(ui32 ps=0;ps<vti->possibleSpells.size();ps++)
total += vti->possibleSpells[ps].toSpell()->getProbability(vti->getFaction());
if (total == 0) // remaining spells have 0 probability
break;
auto r = getRandomGenerator().nextInt(total - 1);
for(ui32 ps=0; ps<vti->possibleSpells.size();ps++)
{
r -= vti->possibleSpells[ps].toSpell()->getProbability(vti->getFaction());
if(r<0)
{
sel = ps;
break;
}
}
if(sel<0)
sel=0;
const auto * s = vti->possibleSpells[sel].toSpell();
vti->spells[s->getLevel()-1].push_back(s->id);
vti->possibleSpells -= s->id;
}
vti->possibleSpells.clear();
if(vti->getOwner() != PlayerColor::NEUTRAL)
getPlayerState(vti->getOwner())->towns.emplace_back(vti);
}
}
void CGameState::initMapObjects()
{
logGlobal->debug("\tObject initialization");
// objCaller->preInit();
for(CGObjectInstance *obj : map->objects)
{
if(obj)
obj->initObj(getRandomGenerator());
}
logGlobal->debug("\tObject initialization done");
for(CGObjectInstance *obj : map->objects)
{
if(!obj)
continue;
switch(obj->ID.toEnum())
{
case Obj::QUEST_GUARD:
case Obj::SEER_HUT:
{
auto * q = dynamic_cast<CGSeerHut *>(obj);
assert (q);
q->setObjToKill();
}
}
}
CGSubterraneanGate::postInit(); //pairing subterranean gates
map->calculateGuardingGreaturePositions(); //calculate once again when all the guards are placed and initialized
}
void CGameState::placeHeroesInTowns()
{
for(auto & player : players)
{
if(player.first == PlayerColor::NEUTRAL)
continue;
for(CGHeroInstance * h : player.second.heroes)
{
for(CGTownInstance * t : player.second.towns)
{
if(h->visitablePos().z != t->visitablePos().z)
continue;
bool heroOnTownBlockableTile = t->blockingAt(h->visitablePos().x, h->visitablePos().y);
// current hero position is at one of blocking tiles of current town
// assume that this hero should be visiting the town (H3M format quirk) and move hero to correct position
if (heroOnTownBlockableTile)
{
int3 correctedPos = h->convertFromVisitablePos(t->visitablePos());
map->removeBlockVisTiles(h);
h->pos = correctedPos;
map->addBlockVisTiles(h);
assert(t->visitableAt(h->visitablePos().x, h->visitablePos().y));
}
}
}
}
}
void CGameState::initVisitingAndGarrisonedHeroes()
{
for(auto & player : players)
{
if(player.first == PlayerColor::NEUTRAL)
continue;
//init visiting and garrisoned heroes
for(CGHeroInstance * h : player.second.heroes)
{
for(CGTownInstance * t : player.second.towns)
{
if(h->visitablePos().z != t->visitablePos().z)
continue;
if (t->visitableAt(h->visitablePos().x, h->visitablePos().y))
{
assert(t->visitingHero == nullptr);
t->setVisitingHero(h);
}
}
}
}
for (auto hero : map->heroesOnMap)
{
if (hero->visitedTown)
{
assert (hero->visitedTown->visitingHero == hero);
}
}
}
const BattleInfo * CGameState::getBattle(const PlayerColor & player) const
{
if (!player.isValidPlayer())
return nullptr;
for (const auto & battlePtr : currentBattles)
if (battlePtr->sides[0].color == player || battlePtr->sides[1].color == player)
return battlePtr.get();
return nullptr;
}
const BattleInfo * CGameState::getBattle(const BattleID & battle) const
{
for (const auto & battlePtr : currentBattles)
if (battlePtr->battleID == battle)
return battlePtr.get();
return nullptr;
}
BattleInfo * CGameState::getBattle(const BattleID & battle)
{
for (const auto & battlePtr : currentBattles)
if (battlePtr->battleID == battle)
return battlePtr.get();
return nullptr;
}
BattleField CGameState::battleGetBattlefieldType(int3 tile, CRandomGenerator & rand)
{
assert(tile.valid());
if(!tile.valid())
return BattleField::NONE;
const TerrainTile &t = map->getTile(tile);
auto * topObject = t.visitableObjects.front();
if(topObject && topObject->getBattlefield() != BattleField::NONE)
{
return topObject->getBattlefield();
}
for(auto &obj : map->objects)
{
//look only for objects covering given tile
if( !obj || obj->pos.z != tile.z || !obj->coveringAt(tile.x, tile.y))
continue;
auto customBattlefield = obj->getBattlefield();
if(customBattlefield != BattleField::NONE)
return customBattlefield;
}
if(map->isCoastalTile(tile)) //coastal tile is always ground
return BattleField(*VLC->identifiers()->getIdentifier("core", "battlefield.sand_shore"));
return BattleField(*RandomGeneratorUtil::nextItem(t.terType->battleFields, rand));
}
void CGameState::fillUpgradeInfo(const CArmedInstance *obj, SlotID stackPos, UpgradeInfo &out) const
{
assert(obj);
assert(obj->hasStackAtSlot(stackPos));
out = fillUpgradeInfo(obj->getStack(stackPos));
}
UpgradeInfo CGameState::fillUpgradeInfo(const CStackInstance &stack) const
{
UpgradeInfo ret;
const CCreature *base = stack.type;
if (stack.armyObj->ID == Obj::HERO)
{
auto hero = dynamic_cast<const CGHeroInstance *>(stack.armyObj);
hero->fillUpgradeInfo(ret, stack);
if (hero->visitedTown)
{
hero->visitedTown->fillUpgradeInfo(ret, stack);
}
else
{
auto object = vstd::frontOrNull(getVisitableObjs(hero->visitablePos()));
auto upgradeSource = dynamic_cast<const ICreatureUpgrader*>(object);
if (object != hero && upgradeSource != nullptr)
upgradeSource->fillUpgradeInfo(ret, stack);
}
}
if (stack.armyObj->ID == Obj::TOWN)
{
auto town = dynamic_cast<const CGTownInstance *>(stack.armyObj);
town->fillUpgradeInfo(ret, stack);
}
if(!ret.newID.empty())
ret.oldID = base->getId();
for (ResourceSet &cost : ret.cost)
cost.positive(); //upgrade cost can't be negative, ignore missing resources
return ret;
}
PlayerRelations CGameState::getPlayerRelations( PlayerColor color1, PlayerColor color2 ) const
{
if ( color1 == color2 )
return PlayerRelations::SAME_PLAYER;
if(color1 == PlayerColor::NEUTRAL || color2 == PlayerColor::NEUTRAL) //neutral player has no friends
return PlayerRelations::ENEMIES;
const TeamState * ts = getPlayerTeam(color1);
if (ts && vstd::contains(ts->players, color2))
return PlayerRelations::ALLIES;
return PlayerRelations::ENEMIES;
}
void CGameState::apply(CPack *pack)
{
ui16 typ = CTypeList::getInstance().getTypeID(pack);
applier->getApplier(typ)->applyOnGS(this, pack);
}
void CGameState::calculatePaths(const CGHeroInstance *hero, CPathsInfo &out)
{
calculatePaths(std::make_shared<SingleHeroPathfinderConfig>(out, this, hero));
}
void CGameState::calculatePaths(const std::shared_ptr<PathfinderConfig> & config)
{
//FIXME: creating pathfinder is costly, maybe reset / clear is enough?
CPathfinder pathfinder(this, config);
pathfinder.calculatePaths();
}
/**
* Tells if the tile is guarded by a monster as well as the position
* of the monster that will attack on it.
*
* @return int3(-1, -1, -1) if the tile is unguarded, or the position of
* the monster guarding the tile.
*/
std::vector<CGObjectInstance*> CGameState::guardingCreatures (int3 pos) const
{
std::vector<CGObjectInstance*> guards;
const int3 originalPos = pos;
if (!map->isInTheMap(pos))
return guards;
const TerrainTile &posTile = map->getTile(pos);
if (posTile.visitable)
{
for (CGObjectInstance* obj : posTile.visitableObjects)
{
if(obj->isBlockedVisitable())
{
if (obj->ID == Obj::MONSTER) // Monster
guards.push_back(obj);
}
}
}
pos -= int3(1, 1, 0); // Start with top left.
for (int dx = 0; dx < 3; dx++)
{
for (int dy = 0; dy < 3; dy++)
{
if (map->isInTheMap(pos))
{
const auto & tile = map->getTile(pos);
if (tile.visitable && (tile.isWater() == posTile.isWater()))
{
for (CGObjectInstance* obj : tile.visitableObjects)
{
if (obj->ID == Obj::MONSTER && map->checkForVisitableDir(pos, &map->getTile(originalPos), originalPos)) // Monster being able to attack investigated tile
{
guards.push_back(obj);
}
}
}
}
pos.y++;
}
pos.y -= 3;
pos.x++;
}
return guards;
}
int3 CGameState::guardingCreaturePosition (int3 pos) const
{
return gs->map->guardingCreaturePositions[pos.z][pos.x][pos.y];
}
void CGameState::updateRumor()
{
static std::vector<RumorState::ERumorType> rumorTypes = {RumorState::TYPE_MAP, RumorState::TYPE_SPECIAL, RumorState::TYPE_RAND, RumorState::TYPE_RAND};
std::vector<RumorState::ERumorTypeSpecial> sRumorTypes = {
RumorState::RUMOR_OBELISKS, RumorState::RUMOR_ARTIFACTS, RumorState::RUMOR_ARMY, RumorState::RUMOR_INCOME};
if(map->grailPos.valid()) // Grail should always be on map, but I had related crash I didn't manage to reproduce
sRumorTypes.push_back(RumorState::RUMOR_GRAIL);
int rumorId = -1;
int rumorExtra = -1;
auto & rand = getRandomGenerator();
rumor.type = *RandomGeneratorUtil::nextItem(rumorTypes, rand);
do
{
switch(rumor.type)
{
case RumorState::TYPE_SPECIAL:
{
SThievesGuildInfo tgi;
obtainPlayersStats(tgi, 20);
rumorId = *RandomGeneratorUtil::nextItem(sRumorTypes, rand);
if(rumorId == RumorState::RUMOR_GRAIL)
{
rumorExtra = getTile(map->grailPos)->terType->getIndex();
break;
}
std::vector<PlayerColor> players = {};
switch(rumorId)
{
case RumorState::RUMOR_OBELISKS:
players = tgi.obelisks[0];
break;
case RumorState::RUMOR_ARTIFACTS:
players = tgi.artifacts[0];
break;
case RumorState::RUMOR_ARMY:
players = tgi.army[0];
break;
case RumorState::RUMOR_INCOME:
players = tgi.income[0];
break;
}
rumorExtra = RandomGeneratorUtil::nextItem(players, rand)->getNum();
break;
}
case RumorState::TYPE_MAP:
// Makes sure that map rumors only used if there enough rumors too choose from
if(!map->rumors.empty() && (map->rumors.size() > 1 || !rumor.last.count(RumorState::TYPE_MAP)))
{
rumorId = rand.nextInt((int)map->rumors.size() - 1);
break;
}
else
rumor.type = RumorState::TYPE_RAND;
[[fallthrough]];
case RumorState::TYPE_RAND:
auto vector = VLC->generaltexth->findStringsWithPrefix("core.randtvrn");
rumorId = rand.nextInt((int)vector.size() - 1);
break;
}
}
while(!rumor.update(rumorId, rumorExtra));
}
bool CGameState::isVisible(int3 pos, const std::optional<PlayerColor> & player) const
{
if (!map->isInTheMap(pos))
return false;
if (!player)
return true;
if(player == PlayerColor::NEUTRAL)
return false;
if(player->isSpectator())
return true;
return (*getPlayerTeam(*player)->fogOfWarMap)[pos.z][pos.x][pos.y];
}
bool CGameState::isVisible(const CGObjectInstance * obj, const std::optional<PlayerColor> & player) const
{
if(!player)
return true;
//we should always see our own heroes - but sometimes not visible heroes cause crash :?
if (player == obj->tempOwner)
return true;
if(*player == PlayerColor::NEUTRAL) //-> TODO ??? needed?
return false;
//object is visible when at least one blocked tile is visible
for(int fy=0; fy < obj->getHeight(); ++fy)
{
for(int fx=0; fx < obj->getWidth(); ++fx)
{
int3 pos = obj->pos + int3(-fx, -fy, 0);
if ( map->isInTheMap(pos) &&
obj->coveringAt(pos.x, pos.y) &&
isVisible(pos, *player))
return true;
}
}
return false;
}
bool CGameState::checkForVisitableDir(const int3 & src, const int3 & dst) const
{
const TerrainTile * pom = &map->getTile(dst);
return map->checkForVisitableDir(src, pom, dst);
}
EVictoryLossCheckResult CGameState::checkForVictoryAndLoss(const PlayerColor & player) const
{
const MetaString messageWonSelf = MetaString::createFromTextID("core.genrltxt.659");
const MetaString messageWonOther = MetaString::createFromTextID("core.genrltxt.5");
const MetaString messageLostSelf = MetaString::createFromTextID("core.genrltxt.7");
const MetaString messageLostOther = MetaString::createFromTextID("core.genrltxt.8");
auto evaluateEvent = [=](const EventCondition & condition)
{
return this->checkForVictory(player, condition);
};
const PlayerState *p = CGameInfoCallback::getPlayerState(player);
//cheater or tester, but has entered the code...
if (p->enteredWinningCheatCode)
return EVictoryLossCheckResult::victory(messageWonSelf, messageWonOther);
if (p->enteredLosingCheatCode)
return EVictoryLossCheckResult::defeat(messageLostSelf, messageLostOther);
for (const TriggeredEvent & event : map->triggeredEvents)
{
if (event.trigger.test(evaluateEvent))
{
if (event.effect.type == EventEffect::VICTORY)
return EVictoryLossCheckResult::victory(event.onFulfill, event.effect.toOtherMessage);
if (event.effect.type == EventEffect::DEFEAT)
return EVictoryLossCheckResult::defeat(event.onFulfill, event.effect.toOtherMessage);
}
}
if (checkForStandardLoss(player))
{
return EVictoryLossCheckResult::defeat(messageLostSelf, messageLostOther);
}
return EVictoryLossCheckResult();
}
bool CGameState::checkForVictory(const PlayerColor & player, const EventCondition & condition) const
{
const PlayerState *p = CGameInfoCallback::getPlayerState(player);
switch (condition.condition)
{
case EventCondition::STANDARD_WIN:
{
return player == checkForStandardWin();
}
case EventCondition::HAVE_ARTIFACT: //check if any hero has winning artifact
{
for(const auto & elem : p->heroes)
if(elem->hasArt(condition.objectType.as<ArtifactID>()))
return true;
return false;
}
case EventCondition::HAVE_CREATURES:
{
//check if in players armies there is enough creatures
int total = 0; //creature counter
for(auto object : map->objects)
{
const CArmedInstance *ai = nullptr;
if(object
&& object->tempOwner == player //object controlled by player
&& (ai = dynamic_cast<const CArmedInstance *>(object.get()))) //contains army
{
for(const auto & elem : ai->Slots()) //iterate through army
if(elem.second->getId() == condition.objectType.as<CreatureID>()) //it's searched creature
total += elem.second->count;
}
}
return total >= condition.value;
}
case EventCondition::HAVE_RESOURCES:
{
return p->resources[condition.objectType.as<GameResID>()] >= condition.value;
}
case EventCondition::HAVE_BUILDING:
{
if (condition.objectID != ObjectInstanceID::NONE) // specific town
{
const auto * t = getTown(condition.objectID);
return (t->tempOwner == player && t->hasBuilt(condition.objectType.as<BuildingID>()));
}
else // any town
{
for (const CGTownInstance * t : p->towns)
{
if (t->hasBuilt(condition.objectType.as<BuildingID>()))
return true;
}
return false;
}
}
case EventCondition::DESTROY:
{
if (condition.objectID != ObjectInstanceID::NONE) // mode A - destroy specific object of this type
{
if(const auto * hero = getHero(condition.objectID))
return boost::range::find(gs->map->heroesOnMap, hero) == gs->map->heroesOnMap.end();
else
return getObj(condition.objectID) == nullptr;
}
else
{
for(const auto & elem : map->objects) // mode B - destroy all objects of this type
{
if(elem && elem->ID == condition.objectType.as<MapObjectID>())
return false;
}
return true;
}
}
case EventCondition::CONTROL:
{
// list of players that need to control object to fulfull condition
// NOTE: cgameinfocallback specified explicitly in order to get const version
const auto * team = CGameInfoCallback::getPlayerTeam(player);
if (condition.objectID != ObjectInstanceID::NONE) // mode A - flag one specific object, like town
{
const auto * object = getObjInstance(condition.objectID);
if (!object)
return false;
return team->players.count(object->getOwner()) != 0;
}
else
{
for(const auto & elem : map->objects) // mode B - flag all objects of this type
{
//check not flagged objs
if ( elem && elem->ID == condition.objectType.as<MapObjectID>() && team->players.count(elem->getOwner()) == 0 )
return false;
}
return true;
}
}
case EventCondition::TRANSPORT:
{
const auto * t = getTown(condition.objectID);
return (t->visitingHero && t->visitingHero->hasArt(condition.objectType.as<ArtifactID>())) ||
(t->garrisonHero && t->garrisonHero->hasArt(condition.objectType.as<ArtifactID>()));
}
case EventCondition::DAYS_PASSED:
{
return (si32)gs->day > condition.value;
}
case EventCondition::IS_HUMAN:
{
return p->human ? condition.value == 1 : condition.value == 0;
}
case EventCondition::DAYS_WITHOUT_TOWN:
{
if (p->daysWithoutCastle)
return p->daysWithoutCastle >= condition.value;
else
return false;
}
case EventCondition::CONST_VALUE:
{
return condition.value; // just convert to bool
}
default:
logGlobal->error("Invalid event condition type: %d", (int)condition.condition);
return false;
}
}
PlayerColor CGameState::checkForStandardWin() const
{
//std victory condition is:
//all enemies lost
PlayerColor supposedWinner = PlayerColor::NEUTRAL;
TeamID winnerTeam = TeamID::NO_TEAM;
for(const auto & elem : players)
{
if(elem.second.status == EPlayerStatus::INGAME && elem.first.isValidPlayer())
{
if(supposedWinner == PlayerColor::NEUTRAL)
{
//first player remaining ingame - candidate for victory
supposedWinner = elem.second.color;
winnerTeam = elem.second.team;
}
else if(winnerTeam != elem.second.team)
{
//current candidate has enemy remaining in game -> no vicotry
return PlayerColor::NEUTRAL;
}
}
}
return supposedWinner;
}
bool CGameState::checkForStandardLoss(const PlayerColor & player) const
{
//std loss condition is: player lost all towns and heroes
const PlayerState & pState = *CGameInfoCallback::getPlayerState(player);
return pState.checkVanquished();
}
struct statsHLP
{
using TStat = std::pair<PlayerColor, si64>;
//converts [<player's color, value>] to vec[place] -> platers
static std::vector< std::vector< PlayerColor > > getRank( std::vector<TStat> stats )
{
std::sort(stats.begin(), stats.end(), statsHLP());
//put first element
std::vector< std::vector<PlayerColor> > ret;
std::vector<PlayerColor> tmp;
tmp.push_back( stats[0].first );
ret.push_back( tmp );
//the rest of elements
for(int g=1; g<stats.size(); ++g)
{
if(stats[g].second == stats[g-1].second)
{
(ret.end()-1)->push_back( stats[g].first );
}
else
{
//create next occupied rank
std::vector<PlayerColor> tmp;
tmp.push_back(stats[g].first);
ret.push_back(tmp);
}
}
return ret;
}
bool operator()(const TStat & a, const TStat & b) const
{
return a.second > b.second;
}
static const CGHeroInstance * findBestHero(CGameState * gs, const PlayerColor & color)
{
std::vector<ConstTransitivePtr<CGHeroInstance> > &h = gs->players[color].heroes;
if(h.empty())
return nullptr;
//best hero will be that with highest exp
int best = 0;
for(int b=1; b<h.size(); ++b)
{
if(h[b]->exp > h[best]->exp)
{
best = b;
}
}
return h[best];
}
//calculates total number of artifacts that belong to given player
static int getNumberOfArts(const PlayerState * ps)
{
int ret = 0;
for(auto h : ps->heroes)
{
ret += (int)h->artifactsInBackpack.size() + (int)h->artifactsWorn.size();
}
return ret;
}
// get total strength of player army
static si64 getArmyStrength(const PlayerState * ps)
{
si64 str = 0;
for(auto h : ps->heroes)
{
if(!h->inTownGarrison) //original h3 behavior
str += h->getArmyStrength();
}
return str;
}
// get total gold income
static int getIncome(const PlayerState * ps)
{
int totalIncome = 0;
const CGObjectInstance * heroOrTown = nullptr;
//Heroes can produce gold as well - skill, specialty or arts
for(const auto & h : ps->heroes)
{
totalIncome += h->valOfBonuses(Selector::typeSubtype(BonusType::GENERATE_RESOURCE, BonusSubtypeID(GameResID(GameResID::GOLD))));
if(!heroOrTown)
heroOrTown = h;
}
//Add town income of all towns
for(const auto & t : ps->towns)
{
totalIncome += t->dailyIncome()[EGameResID::GOLD];
if(!heroOrTown)
heroOrTown = t;
}
/// FIXME: Dirty dirty hack
/// Stats helper need some access to gamestate.
std::vector<const CGObjectInstance *> ownedObjects;
for(const CGObjectInstance * obj : heroOrTown->cb->gameState()->map->objects)
{
if(obj && obj->tempOwner == ps->color)
ownedObjects.push_back(obj);
}
/// This is code from CPlayerSpecificInfoCallback::getMyObjects
/// I'm really need to find out about callback interface design...
for(const auto * object : ownedObjects)
{
//Mines
if ( object->ID == Obj::MINE )
{
const auto * mine = dynamic_cast<const CGMine *>(object);
assert(mine);
if (mine->producedResource == EGameResID::GOLD)
totalIncome += mine->producedQuantity;
}
}
return totalIncome;
}
};
void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level)
{
auto playerInactive = [&](const PlayerColor & color)
{
return color == PlayerColor::NEUTRAL || players.at(color).status != EPlayerStatus::INGAME;
};
#define FILL_FIELD(FIELD, VAL_GETTER) \
{ \
std::vector< std::pair< PlayerColor, si64 > > stats; \
for(auto g = players.begin(); g != players.end(); ++g) \
{ \
if(playerInactive(g->second.color)) \
continue; \
std::pair< PlayerColor, si64 > stat; \
stat.first = g->second.color; \
stat.second = VAL_GETTER; \
stats.push_back(stat); \
} \
tgi.FIELD = statsHLP::getRank(stats); \
}
for(auto & elem : players)
{
if(!playerInactive(elem.second.color))
tgi.playerColors.push_back(elem.second.color);
}
if(level >= 0) //num of towns & num of heroes
{
//num of towns
FILL_FIELD(numOfTowns, g->second.towns.size())
//num of heroes
FILL_FIELD(numOfHeroes, g->second.heroes.size())
}
if(level >= 1) //best hero's portrait
{
for(const auto & player : players)
{
if(playerInactive(player.second.color))
continue;
const CGHeroInstance * best = statsHLP::findBestHero(this, player.second.color);
InfoAboutHero iah;
iah.initFromHero(best, (level >= 2) ? InfoAboutHero::EInfoLevel::DETAILED : InfoAboutHero::EInfoLevel::BASIC);
iah.army.clear();
tgi.colorToBestHero[player.second.color] = iah;
}
}
if(level >= 2) //gold
{
FILL_FIELD(gold, g->second.resources[EGameResID::GOLD])
}
if(level >= 2) //wood & ore
{
FILL_FIELD(woodOre, g->second.resources[EGameResID::WOOD] + g->second.resources[EGameResID::ORE])
}
if(level >= 3) //mercury, sulfur, crystal, gems
{
FILL_FIELD(mercSulfCrystGems, g->second.resources[EGameResID::MERCURY] + g->second.resources[EGameResID::SULFUR] + g->second.resources[EGameResID::CRYSTAL] + g->second.resources[EGameResID::GEMS])
}
if(level >= 3) //obelisks found
{
auto getObeliskVisited = [](const TeamID & t)
{
if(CGObelisk::visited.count(t))
return CGObelisk::visited[t];
else
return ui8(0);
};
FILL_FIELD(obelisks, getObeliskVisited(gs->getPlayerTeam(g->second.color)->id))
}
if(level >= 4) //artifacts
{
FILL_FIELD(artifacts, statsHLP::getNumberOfArts(&g->second))
}
if(level >= 4) //army strength
{
FILL_FIELD(army, statsHLP::getArmyStrength(&g->second))
}
if(level >= 5) //income
{
FILL_FIELD(income, statsHLP::getIncome(&g->second))
}
if(level >= 2) //best hero's stats
{
//already set in lvl 1 handling
}
if(level >= 3) //personality
{
for(const auto & player : players)
{
if(playerInactive(player.second.color)) //do nothing for neutral player
continue;
if(player.second.human)
{
tgi.personality[player.second.color] = EAiTactic::NONE;
}
else //AI
{
tgi.personality[player.second.color] = map->players[player.second.color.getNum()].aiTactic;
}
}
}
if(level >= 4) //best creature
{
//best creatures belonging to player (highest AI value)
for(const auto & player : players)
{
if(playerInactive(player.second.color)) //do nothing for neutral player
continue;
CreatureID bestCre; //best creature's ID
for(const auto & elem : player.second.heroes)
{
for(const auto & it : elem->Slots())
{
CreatureID toCmp = it.second->type->getId(); //ID of creature we should compare with the best one
if(bestCre == CreatureID::NONE || bestCre.toEntity(VLC)->getAIValue() < toCmp.toEntity(VLC)->getAIValue())
{
bestCre = toCmp;
}
}
}
tgi.bestCreature[player.second.color] = bestCre;
}
}
#undef FILL_FIELD
}
void CGameState::buildBonusSystemTree()
{
buildGlobalTeamPlayerTree();
attachArmedObjects();
for(CGTownInstance *t : map->towns)
{
t->deserializationFix();
}
}
void CGameState::deserializationFix()
{
buildGlobalTeamPlayerTree();
attachArmedObjects();
}
void CGameState::buildGlobalTeamPlayerTree()
{
for(auto & team : teams)
{
TeamState * t = &team.second;
t->attachTo(globalEffects);
for(const PlayerColor & teamMember : team.second.players)
{
PlayerState *p = getPlayerState(teamMember);
assert(p);
p->attachTo(*t);
}
}
}
void CGameState::attachArmedObjects()
{
for(CGObjectInstance *obj : map->objects)
{
if(auto * armed = dynamic_cast<CArmedInstance *>(obj))
{
armed->whatShouldBeAttached().attachTo(armed->whereShouldBeAttached(this));
}
}
}
bool CGameState::giveHeroArtifact(CGHeroInstance * h, const ArtifactID & aid)
{
CArtifactInstance * ai = ArtifactUtils::createNewArtifactInstance(aid);
map->addNewArtifactInstance(ai);
auto slot = ArtifactUtils::getArtAnyPosition(h, aid);
if(ArtifactUtils::isSlotEquipment(slot) || ArtifactUtils::isSlotBackpack(slot))
{
ai->putAt(*h, slot);
return true;
}
else
{
return false;
}
}
std::set<HeroTypeID> CGameState::getUnusedAllowedHeroes(bool alsoIncludeNotAllowed) const
{
std::set<HeroTypeID> ret = map->allowedHeroes;
for(const auto & playerSettingPair : scenarioOps->playerInfos) //remove uninitialized yet heroes picked for start by other players
{
if(playerSettingPair.second.hero != HeroTypeID::RANDOM)
ret -= HeroTypeID(playerSettingPair.second.hero);
}
for(auto hero : map->heroesOnMap) //heroes instances initialization
{
if(hero->type)
ret -= hero->type->getId();
else
ret -= hero->getHeroType();
}
for(auto obj : map->objects) //prisons
{
auto * hero = dynamic_cast<const CGHeroInstance *>(obj.get());
if(hero && hero->ID == Obj::PRISON)
ret -= hero->getHeroType();
}
return ret;
}
bool CGameState::isUsedHero(const HeroTypeID & hid) const
{
return getUsedHero(hid);
}
CGHeroInstance * CGameState::getUsedHero(const HeroTypeID & hid) const
{
for(auto obj : map->objects) //prisons
{
if (!obj)
continue;
if ( obj->ID !=Obj::PRISON && obj->ID != Obj::HERO)
continue;
auto * hero = dynamic_cast<CGHeroInstance *>(obj.get());
assert(hero);
if (hero->getHeroType() == hid)
return hero;
}
return nullptr;
}
bool RumorState::update(int id, int extra)
{
if(vstd::contains(last, type))
{
if(last[type].first != id)
{
last[type].first = id;
last[type].second = extra;
}
else
return false;
}
else
last[type] = std::make_pair(id, extra);
return true;
}
TeamState::TeamState()
{
setNodeType(TEAM);
fogOfWarMap = std::make_unique<boost::multi_array<ui8, 3>>();
}
TeamState::TeamState(TeamState && other) noexcept:
CBonusSystemNode(std::move(other)),
id(other.id)
{
std::swap(players, other.players);
std::swap(fogOfWarMap, other.fogOfWarMap);
}
CRandomGenerator & CGameState::getRandomGenerator()
{
return rand;
}
ArtifactID CGameState::pickRandomArtifact(CRandomGenerator & rand, int flags, std::function<bool(ArtifactID)> accepts)
{
std::set<ArtifactID> potentialPicks;
// Select artifacts that satisfy provided criterias
for (auto const & artifactID : map->allowedArtifact)
{
if (!VLC->arth->legalArtifact(artifactID))
continue;
auto const * artifact = artifactID.toArtifact();
assert(artifact->aClass != CArtifact::ART_SPECIAL); // should be filtered out when allowedArtifacts is initialized
if ((flags & CArtifact::ART_TREASURE) == 0 && artifact->aClass == CArtifact::ART_TREASURE)
continue;
if ((flags & CArtifact::ART_MINOR) == 0 && artifact->aClass == CArtifact::ART_MINOR)
continue;
if ((flags & CArtifact::ART_MAJOR) == 0 && artifact->aClass == CArtifact::ART_MAJOR)
continue;
if ((flags & CArtifact::ART_RELIC) == 0 && artifact->aClass == CArtifact::ART_RELIC)
continue;
if (!accepts(artifact->getId()))
continue;
potentialPicks.insert(artifact->getId());
}
return pickRandomArtifact(rand, potentialPicks);
}
ArtifactID CGameState::pickRandomArtifact(CRandomGenerator & rand, std::set<ArtifactID> potentialPicks)
{
// No allowed artifacts at all - give Grail - this can't be banned (hopefully)
// FIXME: investigate how such cases are handled by H3 - some heavily customized user-made maps likely rely on H3 behavior
if (potentialPicks.empty())
{
logGlobal->warn("Failed to find artifact that matches requested parameters!");
return ArtifactID::GRAIL;
}
// Find how many times least used artifacts were picked by randomizer
int leastUsedTimes = std::numeric_limits<int>::max();
for (auto const & artifact : potentialPicks)
if (allocatedArtifacts[artifact] < leastUsedTimes)
leastUsedTimes = allocatedArtifacts[artifact];
// Pick all artifacts that were used least number of times
std::set<ArtifactID> preferredPicks;
for (auto const & artifact : potentialPicks)
if (allocatedArtifacts[artifact] == leastUsedTimes)
preferredPicks.insert(artifact);
assert(!preferredPicks.empty());
ArtifactID artID = *RandomGeneratorUtil::nextItem(preferredPicks, rand);
allocatedArtifacts[artID] += 1; // record +1 more usage
return artID;
}
ArtifactID CGameState::pickRandomArtifact(CRandomGenerator & rand, std::function<bool(ArtifactID)> accepts)
{
return pickRandomArtifact(rand, 0xff, std::move(accepts));
}
ArtifactID CGameState::pickRandomArtifact(CRandomGenerator & rand, int flags)
{
return pickRandomArtifact(rand, flags, [](const ArtifactID &) { return true; });
}
VCMI_LIB_NAMESPACE_END