1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-20 20:23:03 +02:00

Moved hero pool logic to the separate files

This commit is contained in:
Ivan Savenko 2023-07-11 15:16:02 +03:00
parent 1c55835b8b
commit 19ace6a849
26 changed files with 535 additions and 365 deletions

View File

@ -270,17 +270,10 @@ void CCallback::recruitHero(const CGObjectInstance *townOrTavern, const CGHeroIn
{
assert(townOrTavern);
assert(hero);
ui8 i=0;
for(; i<gs->players[*player].availableHeroes.size(); i++)
{
if(gs->players[*player].availableHeroes[i] == hero)
{
HireHero pack(i, townOrTavern->id);
HireHero pack(HeroTypeID(hero->subID), townOrTavern->id);
pack.player = *player;
sendRequest(&pack);
return;
}
}
}
void CCallback::save( const std::string &fname )

View File

@ -68,6 +68,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
${MAIN_LIB_DIR}/gameState/CGameState.cpp
${MAIN_LIB_DIR}/gameState/CGameStateCampaign.cpp
${MAIN_LIB_DIR}/gameState/InfoAboutArmy.cpp
${MAIN_LIB_DIR}/gameState/TavernHeroesPool.cpp
${MAIN_LIB_DIR}/logging/CBasicLogConfigurator.cpp
${MAIN_LIB_DIR}/logging/CLogger.cpp
@ -394,6 +395,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
${MAIN_LIB_DIR}/gameState/EVictoryLossCheckResult.h
${MAIN_LIB_DIR}/gameState/InfoAboutArmy.h
${MAIN_LIB_DIR}/gameState/SThievesGuildInfo.h
${MAIN_LIB_DIR}/gameState/TavernHeroesPool.h
${MAIN_LIB_DIR}/gameState/QuestInfo.h
${MAIN_LIB_DIR}/logging/CBasicLogConfigurator.h

View File

@ -13,6 +13,7 @@
#include "gameState/CGameState.h"
#include "gameState/InfoAboutArmy.h"
#include "gameState/SThievesGuildInfo.h"
#include "gameState/TavernHeroesPool.h"
#include "CGeneralTextHandler.h"
#include "StartInfo.h" // for StartInfo
#include "battle/BattleInfo.h" // for BattleInfo
@ -99,13 +100,6 @@ const PlayerState * CGameInfoCallback::getPlayerState(PlayerColor color, bool ve
}
}
const CTown * CGameInfoCallback::getNativeTown(PlayerColor color) const
{
const PlayerSettings *ps = getPlayerSettings(color);
ERROR_RET_VAL_IF(!ps, "There is no such player!", nullptr);
return (*VLC->townh)[ps->castle]->town;
}
const CGObjectInstance * CGameInfoCallback::getObjByQuestIdentifier(int identifier) const
{
if(gs->map->questIdentifierToId.empty())
@ -486,13 +480,10 @@ std::vector<const CGHeroInstance *> CGameInfoCallback::getAvailableHeroes(const
//ERROR_RET_VAL_IF(!isOwnedOrVisited(townOrTavern), "Town or tavern must be owned or visited!", ret);
//TODO: town needs to be owned, advmap tavern needs to be visited; to be reimplemented when visit tracking is done
const CGTownInstance * town = getTown(townOrTavern->id);
if(townOrTavern->ID == Obj::TAVERN || (town && town->hasBuilt(BuildingID::TAVERN)))
{
range::copy(gs->players[*player].availableHeroes, std::back_inserter(ret));
vstd::erase_if(ret, [](const CGHeroInstance * h) {
return h == nullptr;
});
}
return gs->hpool->getHeroesFor(*player);
return ret;
}

View File

@ -108,7 +108,6 @@ public:
// std::string getTavernRumor(const CGObjectInstance * townOrTavern) const;
// EBuildingState::EBuildingState canBuildStructure(const CGTownInstance *t, BuildingID ID);//// 0 - no more than one capitol, 1 - lack of water, 2 - forbidden, 3 - Add another level to Mage Guild, 4 - already built, 5 - cannot build, 6 - cannot afford, 7 - build, 8 - lack of requirements
// virtual bool getTownInfo(const CGObjectInstance * town, InfoAboutTown & dest, const CGObjectInstance * selectedObject = nullptr) const;
// const CTown *getNativeTown(PlayerColor color) const;
//from gs
// const TeamState *getTeam(TeamID teamID) const;
@ -206,7 +205,6 @@ public:
virtual std::string getTavernRumor(const CGObjectInstance * townOrTavern) const;
virtual EBuildingState::EBuildingState canBuildStructure(const CGTownInstance *t, BuildingID ID);//// 0 - no more than one capitol, 1 - lack of water, 2 - forbidden, 3 - Add another level to Mage Guild, 4 - already built, 5 - cannot build, 6 - cannot afford, 7 - build, 8 - lack of requirements
virtual bool getTownInfo(const CGObjectInstance * town, InfoAboutTown & dest, const CGObjectInstance * selectedObject = nullptr) const;
virtual const CTown *getNativeTown(PlayerColor color) const;
//from gs
virtual const TeamState *getTeam(TeamID teamID) const;

View File

@ -35,7 +35,6 @@ PlayerState::PlayerState(PlayerState && other) noexcept:
std::swap(visitedObjects, other.visitedObjects);
std::swap(heroes, other.heroes);
std::swap(towns, other.towns);
std::swap(availableHeroes, other.availableHeroes);
std::swap(dwellings, other.dwellings);
std::swap(quests, other.quests);
}

View File

@ -33,7 +33,6 @@ public:
std::set<ObjectInstanceID> visitedObjects; // as a std::set, since most accesses here will be from visited status checks
std::vector<ConstTransitivePtr<CGHeroInstance> > heroes;
std::vector<ConstTransitivePtr<CGTownInstance> > towns;
std::vector<ConstTransitivePtr<CGHeroInstance> > availableHeroes; //heroes available in taverns
std::vector<ConstTransitivePtr<CGDwelling> > dwellings; //used for town growth
std::vector<QuestInfo> quests; //store info about all received quests
@ -74,7 +73,6 @@ public:
h & status;
h & heroes;
h & towns;
h & availableHeroes;
h & dwellings;
h & quests;
h & visitedObjects;

View File

@ -357,9 +357,12 @@ class PlayerColor : public BaseForID<PlayerColor, ui8>
enum EPlayerColor
{
PLAYER_LIMIT_I = 8
PLAYER_LIMIT_I = 8,
ALL_PLAYERS_MASK = 0xff
};
using Mask = uint8_t;
DLL_LINKAGE static const PlayerColor SPECTATOR; //252
DLL_LINKAGE static const PlayerColor CANNOT_DETERMINE; //253
DLL_LINKAGE static const PlayerColor UNFLAGGABLE; //254 - neutral objects (pandora, banks)

View File

@ -36,6 +36,7 @@
#include "StartInfo.h"
#include "gameState/CGameState.h"
#include "gameState/CGameStateCampaign.h"
#include "gameState/TavernHeroesPool.h"
#include "mapping/CMap.h"
#include "CPlayerState.h"
#include "GameSettings.h"

View File

@ -36,7 +36,7 @@ public:
virtual void visitSetMana(SetMana & pack) {}
virtual void visitSetMovePoints(SetMovePoints & pack) {}
virtual void visitFoWChange(FoWChange & pack) {}
virtual void visitSetAvailableHeroes(SetAvailableHeroes & pack) {}
virtual void visitSetAvailableHeroes(SetAvailableHero & pack) {}
virtual void visitGiveBonus(GiveBonus & pack) {}
virtual void visitChangeObjPos(ChangeObjPos & pack) {}
virtual void visitPlayerEndsGame(PlayerEndsGame & pack) {}

View File

@ -330,23 +330,24 @@ struct DLL_LINKAGE FoWChange : public CPackForClient
}
};
struct DLL_LINKAGE SetAvailableHeroes : public CPackForClient
struct DLL_LINKAGE SetAvailableHero : public CPackForClient
{
SetAvailableHeroes()
SetAvailableHero()
{
for(auto & i : army)
i.clear();
army.clear();
}
void applyGs(CGameState * gs);
uint8_t slotID;
PlayerColor player;
si32 hid[GameConstants::AVAILABLE_HEROES_PER_PLAYER]; //-1 if no hero
CSimpleArmy army[GameConstants::AVAILABLE_HEROES_PER_PLAYER];
HeroTypeID hid; //-1 if no hero
CSimpleArmy army;
virtual void visitTyped(ICPackVisitor & visitor) override;
template <typename Handler> void serialize(Handler & h, const int version)
{
h & slotID;
h & player;
h & hid;
h & army;
@ -692,7 +693,7 @@ struct DLL_LINKAGE HeroRecruited : public CPackForClient
{
void applyGs(CGameState * gs) const;
si32 hid = -1; //subID of hero
HeroTypeID hid; //subID of hero
ObjectInstanceID tid;
ObjectInstanceID boatId;
int3 tile;
@ -2437,12 +2438,12 @@ struct DLL_LINKAGE SetFormation : public CPackForServer
struct DLL_LINKAGE HireHero : public CPackForServer
{
HireHero() = default;
HireHero(si32 HID, const ObjectInstanceID & TID)
HireHero(HeroTypeID HID, const ObjectInstanceID & TID)
: hid(HID)
, tid(TID)
{
}
si32 hid = 0; //available hero serial
HeroTypeID hid; //available hero serial
ObjectInstanceID tid; //town (tavern) id
PlayerColor player;

View File

@ -20,6 +20,7 @@
#include "spells/CSpellHandler.h"
#include "CCreatureHandler.h"
#include "gameState/CGameState.h"
#include "gameState/TavernHeroesPool.h"
#include "CStack.h"
#include "battle/BattleInfo.h"
#include "CTownHandler.h"
@ -151,7 +152,7 @@ void FoWChange::visitTyped(ICPackVisitor & visitor)
visitor.visitFoWChange(*this);
}
void SetAvailableHeroes::visitTyped(ICPackVisitor & visitor)
void SetAvailableHero::visitTyped(ICPackVisitor & visitor)
{
visitor.visitSetAvailableHeroes(*this);
}
@ -939,18 +940,9 @@ void FoWChange::applyGs(CGameState *gs)
}
}
void SetAvailableHeroes::applyGs(CGameState *gs)
void SetAvailableHero::applyGs(CGameState *gs)
{
PlayerState *p = gs->getPlayerState(player);
p->availableHeroes.clear();
for (int i = 0; i < GameConstants::AVAILABLE_HEROES_PER_PLAYER; i++)
{
CGHeroInstance *h = (hid[i]>=0 ? gs->hpool.heroesPool[hid[i]].get() : nullptr);
if(h && army[i])
h->setToArmy(army[i]);
p->availableHeroes.emplace_back(h);
}
gs->hpool->setHeroForPlayer(player, TavernHeroSlot(slotID), hid, army);
}
void GiveBonus::applyGs(CGameState *gs)
@ -1150,11 +1142,8 @@ void RemoveObject::applyGs(CGameState *gs)
beatenHero->inTownGarrison = false;
}
//return hero to the pool, so he may reappear in tavern
gs->hpool.heroesPool[beatenHero->subID] = beatenHero;
if(!vstd::contains(gs->hpool.pavailable, beatenHero->subID))
gs->hpool.pavailable[beatenHero->subID] = 0xff;
gs->hpool->addHeroToPool(beatenHero);
gs->map->objects[id.getNum()] = nullptr;
//If hero on Boat is removed, the Boat disappears
@ -1379,8 +1368,7 @@ void SetHeroesInTown::applyGs(CGameState * gs) const
void HeroRecruited::applyGs(CGameState * gs) const
{
assert(vstd::contains(gs->hpool.heroesPool, hid));
CGHeroInstance *h = gs->hpool.heroesPool[hid];
CGHeroInstance *h = gs->hpool->takeHero(hid);
CGTownInstance *t = gs->getTown(tid);
PlayerState *p = gs->getPlayerState(player);
@ -1411,7 +1399,6 @@ void HeroRecruited::applyGs(CGameState * gs) const
}
}
gs->hpool.heroesPool.erase(hid);
if(h->id == ObjectInstanceID())
{
h->id = ObjectInstanceID(static_cast<si32>(gs->map->objects.size()));
@ -2021,26 +2008,14 @@ void NewTurn::applyGs(CGameState *gs)
{
CGHeroInstance *hero = gs->getHero(h.id);
if(!hero)
{
// retreated or surrendered hero who has not been reset yet
for(auto& hp : gs->hpool.heroesPool)
{
if(hp.second->id == h.id)
{
hero = hp.second;
break;
}
}
}
if(!hero)
{
logGlobal->error("Hero %d not found in NewTurn::applyGs", h.id.getNum());
continue;
}
hero->setMovementPoints(h.move);
hero->mana = h.mana;
}
gs->hpool->onNewDay();
for(const auto & re : res)
{
assert(re.first < PlayerColor::PLAYER_LIMIT);

View File

@ -35,9 +35,9 @@ struct DLL_LINKAGE PlayerSettings
};
Ebonus bonus;
si16 castle;
si32 hero,
heroPortrait; //-1 if default, else ID
FactionID castle;
HeroTypeID hero;
HeroTypeID heroPortrait; //-1 if default, else ID
std::string heroName;
PlayerColor color; //from 0 -

View File

@ -12,6 +12,7 @@
#include "EVictoryLossCheckResult.h"
#include "InfoAboutArmy.h"
#include "TavernHeroesPool.h"
#include "CGameStateCampaign.h"
#include "SThievesGuildInfo.h"
@ -102,81 +103,6 @@ static CGObjectInstance * createObject(const Obj & id, int subid, const int3 & p
return nobj;
}
CGHeroInstance * CGameState::HeroesPool::pickHeroFor(bool native,
const PlayerColor & player,
const CTown * town,
std::map<ui32, ConstTransitivePtr<CGHeroInstance>> & available,
CRandomGenerator & rand,
const CHeroClass * bannedClass) const
{
CGHeroInstance *ret = nullptr;
if(player>=PlayerColor::PLAYER_LIMIT)
{
logGlobal->error("Cannot pick hero for faction %s. Wrong owner!", town->faction->getJsonKey());
return nullptr;
}
std::vector<CGHeroInstance *> pool;
if(native)
{
for(auto & elem : available)
{
if(pavailable.find(elem.first)->second & 1<<player.getNum()
&& elem.second->type->heroClass->faction == town->faction->getIndex())
{
pool.push_back(elem.second); //get all available heroes
}
}
if(pool.empty())
{
logGlobal->error("Cannot pick native hero for %s. Picking any...", player.getStr());
return pickHeroFor(false, player, town, available, rand);
}
else
{
ret = *RandomGeneratorUtil::nextItem(pool, rand);
}
}
else
{
int sum = 0;
int r;
for(auto & elem : available)
{
if (pavailable.find(elem.first)->second & (1<<player.getNum()) && // hero is available
( !bannedClass || elem.second->type->heroClass != bannedClass) ) // and his class is not same as other hero
{
pool.push_back(elem.second);
sum += elem.second->type->heroClass->selectionProbability[town->faction->getId()]; //total weight
}
}
if(pool.empty() || sum == 0)
{
logGlobal->error("There are no heroes available for player %s!", player.getStr());
return nullptr;
}
r = rand.nextInt(sum - 1);
for (auto & elem : pool)
{
r -= elem->type->heroClass->selectionProbability[town->faction->getId()];
if(r < 0)
{
ret = elem;
break;
}
}
if(!ret)
ret = pool.back();
}
available.erase(ret->subID);
return ret;
}
HeroTypeID CGameState::pickNextHeroType(const PlayerColor & owner)
{
const PlayerSettings &ps = scenarioOps->getIthPlayersSettings(owner);
@ -459,6 +385,7 @@ int CGameState::getDate(Date::EDateType mode) const
CGameState::CGameState()
{
gs = this;
hpool = std::make_unique<TavernHeroesPool>(this);
applier = std::make_shared<CApplier<CBaseForGSApply>>();
registerTypesClientPacks1(*applier);
registerTypesClientPacks2(*applier);
@ -469,9 +396,6 @@ CGameState::~CGameState()
{
map.dellNull();
curB.dellNull();
for(auto ptr : hpool.heroesPool) // clean hero pool
ptr.second.dellNull();
}
void CGameState::preInit(Services * services)
@ -951,8 +875,7 @@ void CGameState::initHeroes()
if(!vstd::contains(heroesToCreate, HeroTypeID(ph->subID)))
continue;
ph->initHero(getRandomGenerator());
hpool.heroesPool[ph->subID] = ph;
hpool.pavailable[ph->subID] = 0xff;
hpool->addHeroToPool(ph);
heroesToCreate.erase(ph->type->getId());
map->allHeroes[ph->subID] = ph;
@ -965,14 +888,11 @@ void CGameState::initHeroes()
int typeID = htype.getNum();
map->allHeroes[typeID] = vhi;
hpool.heroesPool[typeID] = vhi;
hpool.pavailable[typeID] = 0xff;
hpool->addHeroToPool(vhi);
}
for(auto & elem : map->disposedHeroes)
{
hpool.pavailable[elem.heroId] = elem.players;
}
hpool->setAvailability(elem.heroId, elem.players);
if (campaign)
campaign->initHeroes();
@ -2067,17 +1987,6 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level)
#undef FILL_FIELD
}
std::map<ui32, ConstTransitivePtr<CGHeroInstance> > CGameState::unusedHeroesFromPool()
{
std::map<ui32, ConstTransitivePtr<CGHeroInstance> > pool = hpool.heroesPool;
for(const auto & player : players)
for(auto availableHero : player.second.availableHeroes)
if(availableHero)
pool.erase((*availableHero).subID);
return pool;
}
void CGameState::buildBonusSystemTree()
{
buildGlobalTeamPlayerTree();

View File

@ -29,6 +29,7 @@ struct EventCondition;
struct CampaignTravel;
class CStackInstance;
class CGameStateCampaign;
class TavernHeroesPool;
struct SThievesGuildInfo;
template<typename T> class CApplier;
@ -78,25 +79,10 @@ DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const EVictoryLossCheck
class DLL_LINKAGE CGameState : public CNonConstInfoCallback
{
friend class CGameStateCampaign;
public:
struct DLL_LINKAGE HeroesPool
{
std::map<ui32, ConstTransitivePtr<CGHeroInstance> > heroesPool; //[subID] - heroes available to buy; nullptr if not available
std::map<ui32,ui8> pavailable; // [subid] -> which players can recruit hero (binary flags)
CGHeroInstance * pickHeroFor(bool native,
const PlayerColor & player,
const CTown * town,
std::map<ui32, ConstTransitivePtr<CGHeroInstance>> & available,
CRandomGenerator & rand,
const CHeroClass * bannedClass = nullptr) const;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & heroesPool;
h & pavailable;
}
} hpool; //we have here all heroes available on this map that are not hired
//we have here all heroes available on this map that are not hired
std::unique_ptr<TavernHeroesPool> hpool;
CGameState();
virtual ~CGameState();
@ -142,7 +128,6 @@ public:
bool checkForStandardLoss(const PlayerColor & player) const; //checks if given player lost the game
void obtainPlayersStats(SThievesGuildInfo & tgi, int level); //fills tgi with info about other players that is available at given level of thieves' guild
std::map<ui32, ConstTransitivePtr<CGHeroInstance> > unusedHeroesFromPool(); //heroes pool without heroes that are available in taverns
bool isVisible(int3 pos, const std::optional<PlayerColor> & player) const override;
bool isVisible(const CGObjectInstance * obj, const std::optional<PlayerColor> & player) const override;

View File

@ -0,0 +1,176 @@
/*
* TavernHeroesPool.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 "TavernHeroesPool.h"
#include "CGameState.h"
#include "CPlayerState.h"
#include "../mapObjects/CGHeroInstance.h"
#include "../CHeroHandler.h"
TavernHeroesPool::TavernHeroesPool() = default;
TavernHeroesPool::TavernHeroesPool(CGameState * gameState)
: gameState(gameState)
{
}
TavernHeroesPool::~TavernHeroesPool()
{
for(auto ptr : heroesPool) // clean hero pool
delete ptr.second;
}
std::map<HeroTypeID, CGHeroInstance*> TavernHeroesPool::unusedHeroesFromPool()
{
std::map<HeroTypeID, CGHeroInstance*> pool = heroesPool;
for(const auto & player : currentTavern)
for(auto availableHero : player.second)
if(availableHero.second)
pool.erase(HeroTypeID(availableHero.second->subID));
return pool;
}
void TavernHeroesPool::setHeroForPlayer(PlayerColor player, TavernHeroSlot slot, HeroTypeID hero, CSimpleArmy & army)
{
currentTavern[player].erase(slot);
if (hero == HeroTypeID::NONE)
return;
CGHeroInstance * h = heroesPool[hero];
if (h && army)
h->setToArmy(army);
currentTavern[player][slot] = h;
}
bool TavernHeroesPool::isHeroAvailableFor(HeroTypeID hero, PlayerColor color) const
{
if (pavailable.count(hero))
return pavailable.at(hero) & (1 << color.getNum());
return true;
}
CGHeroInstance * TavernHeroesPool::pickHeroFor(TavernHeroSlot slot,
const PlayerColor & player,
const FactionID & factionID,
CRandomGenerator & rand,
const CHeroClass * bannedClass) const
{
if(player>=PlayerColor::PLAYER_LIMIT)
{
logGlobal->error("Cannot pick hero for player %d. Wrong owner!", player.getStr());
return nullptr;
}
if(slot == TavernHeroSlot::NATIVE)
{
std::vector<CGHeroInstance *> pool;
for(auto & elem : heroesPool)
{
//get all available heroes
bool heroAvailable = isHeroAvailableFor(elem.first, player);
bool heroClassNative = elem.second->type->heroClass->faction == factionID;
if(heroAvailable && heroClassNative)
pool.push_back(elem.second);
}
if(!pool.empty())
return *RandomGeneratorUtil::nextItem(pool, rand);
logGlobal->error("Cannot pick native hero for %s. Picking any...", player.getStr());
}
std::vector<CGHeroInstance *> pool;
int totalWeight = 0;
for(auto & elem : heroesPool)
{
bool heroAvailable = isHeroAvailableFor(elem.first, player);
bool heroClassBanned = bannedClass && elem.second->type->heroClass == bannedClass;
if ( heroAvailable && !heroClassBanned)
{
pool.push_back(elem.second);
totalWeight += elem.second->type->heroClass->selectionProbability[factionID]; //total weight
}
}
if(pool.empty() || totalWeight == 0)
{
logGlobal->error("There are no heroes available for player %s!", player.getStr());
return nullptr;
}
int roll = rand.nextInt(totalWeight - 1);
for (auto & elem : pool)
{
roll -= elem->type->heroClass->selectionProbability[factionID];
if(roll < 0)
{
return elem;
}
}
return pool.back();
}
std::vector<const CGHeroInstance *> TavernHeroesPool::getHeroesFor(PlayerColor color) const
{
std::vector<const CGHeroInstance *> result;
if(!currentTavern.count(color))
return result;
for(const auto & hero : currentTavern.at(color))
result.push_back(hero.second);
return result;
}
CGHeroInstance * TavernHeroesPool::takeHero(HeroTypeID hero)
{
assert(heroesPool.count(hero));
CGHeroInstance * result = heroesPool[hero];
heroesPool.erase(hero);
assert(result);
return result;
}
void TavernHeroesPool::onNewDay()
{
for(auto & hero : heroesPool)
{
assert(hero.second);
if(!hero.second)
continue;
hero.second->setMovementPoints(hero.second->movementPointsLimit(true));
hero.second->mana = hero.second->manaLimit();
}
}
void TavernHeroesPool::addHeroToPool(CGHeroInstance * hero)
{
heroesPool[HeroTypeID(hero->subID)] = hero;
}
void TavernHeroesPool::setAvailability(HeroTypeID hero, PlayerColor::Mask mask)
{
pavailable[hero] = mask;
}

View File

@ -0,0 +1,75 @@
/*
* TavernHeroesPool.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 "../ConstTransitivePtr.h"
#include "../GameConstants.h"
VCMI_LIB_NAMESPACE_BEGIN
class CGHeroInstance;
class CTown;
class CRandomGenerator;
class CHeroClass;
class CGameState;
class CSimpleArmy;
enum class TavernHeroSlot
{
NATIVE,
RANDOM
};
class DLL_LINKAGE TavernHeroesPool
{
CGameState * gameState;
//[subID] - heroes available to buy; nullptr if not available
std::map<HeroTypeID, CGHeroInstance* > heroesPool;
// [subid] -> which players can recruit hero (binary flags)
std::map<HeroTypeID, PlayerColor::Mask> pavailable;
std::map<HeroTypeID, CGHeroInstance* > unusedHeroesFromPool(); //heroes pool without heroes that are available in taverns
std::map<PlayerColor, std::map<TavernHeroSlot, CGHeroInstance*> > currentTavern;
bool isHeroAvailableFor(HeroTypeID hero, PlayerColor color) const;
public:
TavernHeroesPool();
TavernHeroesPool(CGameState * gameState);
~TavernHeroesPool();
CGHeroInstance * pickHeroFor(TavernHeroSlot slot,
const PlayerColor & player,
const FactionID & faction,
CRandomGenerator & rand,
const CHeroClass * bannedClass = nullptr) const;
std::vector<const CGHeroInstance *> getHeroesFor(PlayerColor color) const;
CGHeroInstance * takeHero(HeroTypeID hero);
/// reset mana and movement points for all heroes in pool
void onNewDay();
void addHeroToPool(CGHeroInstance * hero);
void setAvailability(HeroTypeID hero, PlayerColor::Mask mask);
void setHeroForPlayer(PlayerColor player, TavernHeroSlot slot, HeroTypeID hero, CSimpleArmy & army);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & gameState;
h & heroesPool;
h & pavailable;
}
};
VCMI_LIB_NAMESPACE_END

View File

@ -56,10 +56,10 @@ struct DLL_LINKAGE DisposedHero
{
DisposedHero();
ui32 heroId;
HeroTypeID heroId;
ui32 portrait; /// The portrait id of the hero, -1 is default.
std::string name;
ui8 players; /// Who can hire this hero (bitfield).
PlayerColor::Mask players; /// Who can hire this hero (bitfield).
template <typename Handler>
void serialize(Handler & h, const int version)

View File

@ -239,7 +239,7 @@ void registerTypesClientPacks1(Serializer &s)
s.template registerType<CPackForClient, SetMana>();
s.template registerType<CPackForClient, SetMovePoints>();
s.template registerType<CPackForClient, FoWChange>();
s.template registerType<CPackForClient, SetAvailableHeroes>();
s.template registerType<CPackForClient, SetAvailableHero>();
s.template registerType<CPackForClient, GiveBonus>();
s.template registerType<CPackForClient, ChangeObjPos>();
s.template registerType<CPackForClient, PlayerEndsGame>();

View File

@ -14,6 +14,7 @@
#include "../StartInfo.h"
#include "../gameState/CGameState.h"
#include "../gameState/CGameStateCampaign.h"
#include "../gameState/TavernHeroesPool.h"
#include "../mapping/CMap.h"
#include "../CModHandler.h"
#include "../mapObjects/CObjectHandler.h"

View File

@ -8,6 +8,12 @@
*
*/
#include "StdInc.h"
#include "CGameHandler.h"
#include "HeroPoolProcessor.h"
#include "ServerNetPackVisitors.h"
#include "ServerSpellCastEnvironment.h"
#include "CVCMIServer.h"
#include "../lib/filesystem/Filesystem.h"
#include "../lib/filesystem/FileInfo.h"
@ -35,7 +41,6 @@
#include "../lib/GameSettings.h"
#include "../lib/battle/BattleInfo.h"
#include "../lib/CondSh.h"
#include "ServerNetPackVisitors.h"
#include "../lib/VCMI_Lib.h"
#include "../lib/mapping/CMap.h"
#include "../lib/mapping/CMapService.h"
@ -44,9 +49,6 @@
#include "../lib/ScopeGuard.h"
#include "../lib/CSoundBase.h"
#include "../lib/TerrainHandler.h"
#include "CGameHandler.h"
#include "ServerSpellCastEnvironment.h"
#include "CVCMIServer.h"
#include "../lib/CCreatureSet.h"
#include "../lib/CThreadHelper.h"
#include "../lib/GameConstants.h"
@ -868,24 +870,12 @@ void CGameHandler::battleAfterLevelUp(const BattleResult &result)
std::set<PlayerColor> playerColors = {finishingBattle->loser, finishingBattle->victor};
checkVictoryLossConditions(playerColors);
if (result.result == BattleResult::SURRENDER || result.result == BattleResult::ESCAPE) //loser has escaped or surrendered
{
SetAvailableHeroes sah;
sah.player = finishingBattle->loser;
sah.hid[0] = finishingBattle->loserHero->subID;
if (result.result == BattleResult::ESCAPE) //retreat
{
sah.army[0].clear();
sah.army[0].setCreature(SlotID(0), finishingBattle->loserHero->type->initialArmy.at(0).creature, 1);
}
if (result.result == BattleResult::SURRENDER)
heroPool->onHeroSurrendered(finishingBattle->loser, finishingBattle->loserHero);
if (const CGHeroInstance *another = getPlayerState(finishingBattle->loser)->availableHeroes.at(0))
sah.hid[1] = another->subID;
else
sah.hid[1] = -1;
if (result.result == BattleResult::ESCAPE)
heroPool->onHeroEscaped(finishingBattle->loser, finishingBattle->loserHero);
sendAndApply(&sah);
}
if (result.winner != 2 && finishingBattle->winnerHero && finishingBattle->winnerHero->stacks.empty()
&& (!finishingBattle->winnerHero->commander || !finishingBattle->winnerHero->commander->alive))
{
@ -893,20 +883,7 @@ void CGameHandler::battleAfterLevelUp(const BattleResult &result)
sendAndApply(&ro);
if (VLC->settings()->getBoolean(EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS))
{
SetAvailableHeroes sah;
sah.player = finishingBattle->victor;
sah.hid[0] = finishingBattle->winnerHero->subID;
sah.army[0].clear();
sah.army[0].setCreature(SlotID(0), finishingBattle->winnerHero->type->initialArmy.at(0).creature, 1);
if (const CGHeroInstance *another = getPlayerState(finishingBattle->victor)->availableHeroes.at(0))
sah.hid[1] = another->subID;
else
sah.hid[1] = -1;
sendAndApply(&sah);
}
heroPool->onHeroEscaped(finishingBattle->victor, finishingBattle->winnerHero);
}
finishingBattle.reset();
@ -1576,6 +1553,7 @@ int CGameHandler::moveStack(int stack, BattleHex dest)
CGameHandler::CGameHandler(CVCMIServer * lobby)
: lobby(lobby)
, heroPool(std::make_unique<HeroPoolProcessor>(this))
, complainNoCreatures("No creatures to split")
, complainNotEnoughCreatures("Cannot split that stack, not enough creatures!")
, complainInvalidSlot("Invalid slot accessed!")
@ -1765,27 +1743,6 @@ void CGameHandler::newTurn()
}
}
std::map<ui32, ConstTransitivePtr<CGHeroInstance> > pool = gs->hpool.heroesPool;
for (auto& hp : pool)
{
auto hero = hp.second;
if (hero->isInitialized() && hero->stacks.size())
{
// reset retreated or surrendered heroes
auto maxmove = hero->movementPointsLimit(true);
// if movement is greater than maxmove, we should decrease it
if (hero->movementPointsRemaining() != maxmove || hero->mana < hero->manaLimit())
{
NewTurn::Hero hth;
hth.id = hero->id;
hth.move = maxmove;
hth.mana = hero->getManaNewTurn();
n.heroes.insert(hth);
}
}
}
for (auto & elem : gs->players)
{
if (elem.first == PlayerColor::NEUTRAL)
@ -1797,29 +1754,7 @@ void CGameHandler::newTurn()
hadGold.insert(playerGold);
if (newWeek) //new heroes in tavern
{
SetAvailableHeroes sah;
sah.player = elem.first;
//pick heroes and their armies
CHeroClass *banned = nullptr;
for (int j = 0; j < GameConstants::AVAILABLE_HEROES_PER_PLAYER; j++)
{
//first hero - native if possible, second hero -> any other class
if (CGHeroInstance *h = gs->hpool.pickHeroFor(j == 0, elem.first, getNativeTown(elem.first), pool, getRandomGenerator(), banned))
{
sah.hid[j] = h->subID;
h->initArmy(getRandomGenerator(), &sah.army[j]);
banned = h->type->heroClass;
}
else
{
sah.hid[j] = -1;
}
}
sendAndApply(&sah);
}
heroPool->onNewWeek(elem.first);
n.res[elem.first] = elem.second.resources;
@ -4383,94 +4318,6 @@ bool CGameHandler::setFormation(ObjectInstanceID hid, ui8 formation)
return true;
}
bool CGameHandler::hireHero(const CGObjectInstance *obj, ui8 hid, PlayerColor player)
{
const PlayerState * p = getPlayerState(player);
const CGTownInstance * t = getTown(obj->id);
//common preconditions
// if ((p->resources.at(EGameResID::GOLD)<GOLD_NEEDED && complain("Not enough gold for buying hero!"))
// || (getHeroCount(player, false) >= GameConstants::MAX_HEROES_PER_PLAYER && complain("Cannot hire hero, only 8 wandering heroes are allowed!")))
if ((p->resources[EGameResID::GOLD] < GameConstants::HERO_GOLD_COST && complain("Not enough gold for buying hero!"))
|| ((getHeroCount(player, false) >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP) && complain("Cannot hire hero, too many wandering heroes already!")))
|| ((getHeroCount(player, true) >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP) && complain("Cannot hire hero, too many heroes garrizoned and wandering already!"))))
{
return false;
}
if (t) //tavern in town
{
if ((!t->hasBuilt(BuildingID::TAVERN) && complain("No tavern!"))
|| (t->visitingHero && complain("There is visiting hero - no place!")))
{
return false;
}
}
else if (obj->ID == Obj::TAVERN)
{
if (getTile(obj->visitablePos())->visitableObjects.back() != obj && complain("Tavern entry must be unoccupied!"))
{
return false;
}
}
const CGHeroInstance *nh = p->availableHeroes.at(hid);
if (!nh)
{
complain ("Hero is not available for hiring!");
return false;
}
HeroRecruited hr;
hr.tid = obj->id;
hr.hid = nh->subID;
hr.player = player;
hr.tile = nh->convertFromVisitablePos(obj->visitablePos());
if (getTile(hr.tile)->isWater())
{
//Create a new boat for hero
createObject(obj->visitablePos(), Obj::BOAT, nh->getBoatType().getNum());
hr.boatId = getTopObj(hr.tile)->id;
}
sendAndApply(&hr);
std::map<ui32, ConstTransitivePtr<CGHeroInstance> > pool = gs->unusedHeroesFromPool();
const CGHeroInstance *theOtherHero = p->availableHeroes.at(!hid);
const CGHeroInstance *newHero = nullptr;
if (theOtherHero) //on XXL maps all heroes can be imprisoned :(
{
newHero = gs->hpool.pickHeroFor(false, player, getNativeTown(player), pool, getRandomGenerator(), theOtherHero->type->heroClass);
}
SetAvailableHeroes sah;
sah.player = player;
if (newHero)
{
sah.hid[hid] = newHero->subID;
sah.army[hid].clear();
sah.army[hid].setCreature(SlotID(0), newHero->type->initialArmy[0].creature, 1);
}
else
{
sah.hid[hid] = -1;
}
sah.hid[!hid] = theOtherHero ? theOtherHero->subID : -1;
sendAndApply(&sah);
giveResource(player, EGameResID::GOLD, -GameConstants::HERO_GOLD_COST);
if(t)
{
visitCastleObjects(t, nh);
giveSpells (t,nh);
}
return true;
}
bool CGameHandler::queryReply(QueryID qid, const JsonNode & answer, PlayerColor player)
{
boost::unique_lock<boost::recursive_mutex> lock(gsm);

View File

@ -46,6 +46,7 @@ template<typename T> class CApplier;
VCMI_LIB_NAMESPACE_END
class HeroPoolProcessor;
class CGameHandler;
class CVCMIServer;
class CBaseForGHApply;
@ -97,7 +98,10 @@ class CGameHandler : public IGameCallback, public CBattleInfoCallback, public En
CVCMIServer * lobby;
std::shared_ptr<CApplier<CBaseForGHApply>> applier;
std::unique_ptr<boost::thread> battleThread;
public:
std::unique_ptr<HeroPoolProcessor> heroPool;
using FireShieldInfo = std::vector<std::pair<const CStack *, int64_t>>;
//use enums as parameters, because doMove(sth, true, false, true) is not readable
enum EGuardLook {CHECK_FOR_GUARDS, IGNORE_GUARDS};
@ -145,6 +149,7 @@ public:
void setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance *heroes[2], bool creatureBank, const CGTownInstance *town);
void setBattleResult(BattleResult::EResult resultType, int victoriusSide);
CGameHandler() = default;
CGameHandler(CVCMIServer * lobby);
~CGameHandler();
@ -240,7 +245,6 @@ public:
void removeObstacle(const CObstacleInstance &obstacle);
bool queryReply( QueryID qid, const JsonNode & answer, PlayerColor player );
bool hireHero( const CGObjectInstance *obj, ui8 hid, PlayerColor player );
bool buildBoat( ObjectInstanceID objid, PlayerColor player );
bool setFormation( ObjectInstanceID hid, ui8 formation );
bool tradeResources(const IMarket *market, ui32 val, PlayerColor player, ui32 id1, ui32 id2);
@ -283,6 +287,7 @@ public:
h & QID;
h & states;
h & finishingBattle;
h & heroPool;
h & getRandomGenerator();
#if SCRIPTING_ENABLED

View File

@ -2,6 +2,7 @@ set(server_SRCS
StdInc.cpp
CGameHandler.cpp
HeroPoolProcessor.cpp
ServerSpellCastEnvironment.cpp
CQuery.cpp
CVCMIServer.cpp
@ -13,6 +14,7 @@ set(server_HEADERS
StdInc.h
CGameHandler.h
HeroPoolProcessor.h
ServerSpellCastEnvironment.h
CQuery.h
CVCMIServer.h

View File

@ -822,7 +822,7 @@ void CVCMIServer::setPlayer(PlayerColor clickedColor)
void CVCMIServer::optionNextCastle(PlayerColor player, int dir)
{
PlayerSettings & s = si->playerInfos[player];
si16 & cur = s.castle;
FactionID & cur = s.castle;
auto & allowed = getPlayerInfo(player.getNum()).allowedFactions;
const bool allowRandomTown = getPlayerInfo(player.getNum()).isFactionRandom;
@ -856,7 +856,7 @@ void CVCMIServer::optionNextCastle(PlayerColor player, int dir)
else
{
assert(dir >= -1 && dir <= 1); //othervice std::advance may go out of range
auto iter = allowed.find(FactionID(cur));
auto iter = allowed.find(cur);
std::advance(iter, dir);
cur = *iter;
}

View File

@ -0,0 +1,162 @@
/*
* HeroPoolProcessor.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 "HeroPoolProcessor.h"
#include "CGameHandler.h"
#include "../lib/CHeroHandler.h"
#include "../lib/CPlayerState.h"
#include "../lib/GameSettings.h"
#include "../lib/NetPacks.h"
#include "../lib/StartInfo.h"
#include "../lib/mapObjects/CGTownInstance.h"
#include "../lib/gameState/CGameState.h"
#include "../lib/gameState/TavernHeroesPool.h"
HeroPoolProcessor::HeroPoolProcessor() = default;
HeroPoolProcessor::HeroPoolProcessor(CGameHandler * gameHandler):
gameHandler(gameHandler)
{
}
void HeroPoolProcessor::onHeroSurrendered(const PlayerColor & color, const CGHeroInstance * hero)
{
SetAvailableHero sah;
sah.slotID = 0;
sah.player = color;
sah.hid = hero->subID;
sah.army.clear();
sah.army.setCreature(SlotID(0), hero->type->initialArmy.at(0).creature, 1);
gameHandler->sendAndApply(&sah);
}
void HeroPoolProcessor::onHeroEscaped(const PlayerColor & color, const CGHeroInstance * hero)
{
SetAvailableHero sah;
sah.slotID = 0;
sah.player = color;
sah.hid = hero->subID;
gameHandler->sendAndApply(&sah);
}
void HeroPoolProcessor::clearHeroFromSlot(const PlayerColor & color, TavernHeroSlot slot)
{
SetAvailableHero sah;
sah.player = color;
sah.slotID = static_cast<int>(slot);
sah.hid = HeroTypeID::NONE;
gameHandler->sendAndApply(&sah);
}
void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHeroSlot slot)
{
SetAvailableHero sah;
sah.player = color;
sah.slotID = static_cast<int>(slot);
//first hero - native if possible, second hero -> any other class
CGHeroInstance *h = gameHandler->gameState()->hpool->pickHeroFor(slot, color, gameHandler->getPlayerSettings(color)->castle, gameHandler->getRandomGenerator());
if (h)
{
sah.hid = h->subID;
h->initArmy(gameHandler->getRandomGenerator(), &sah.army);
}
else
{
sah.hid = -1;
}
gameHandler->sendAndApply(&sah);
}
void HeroPoolProcessor::onNewWeek(const PlayerColor & color)
{
clearHeroFromSlot(color, TavernHeroSlot::NATIVE);
clearHeroFromSlot(color, TavernHeroSlot::RANDOM);
selectNewHeroForSlot(color, TavernHeroSlot::NATIVE);
selectNewHeroForSlot(color, TavernHeroSlot::RANDOM);
}
bool HeroPoolProcessor::hireHero(const CGObjectInstance *obj, const HeroTypeID & heroToRecruit, const PlayerColor & player)
{
const PlayerState * playerState = gameHandler->getPlayerState(player);
const CGTownInstance * town = gameHandler->getTown(obj->id);
if (playerState->resources[EGameResID::GOLD] < GameConstants::HERO_GOLD_COST && gameHandler->complain("Not enough gold for buying hero!"))
return false;
if (gameHandler->getHeroCount(player, false) >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP) && gameHandler->complain("Cannot hire hero, too many wandering heroes already!"))
return false;
if (gameHandler->getHeroCount(player, true) >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP) && gameHandler->complain("Cannot hire hero, too many heroes garrizoned and wandering already!"))
return false;
if (town) //tavern in town
{
if (!town->hasBuilt(BuildingID::TAVERN) && gameHandler->complain("No tavern!"))
return false;
if (town->visitingHero && gameHandler->complain("There is visiting hero - no place!"))
return false;
}
if (obj->ID == Obj::TAVERN)
{
if (gameHandler->getTile(obj->visitablePos())->visitableObjects.back() != obj && gameHandler->complain("Tavern entry must be unoccupied!"))
return false;
}
auto recruitableHeroes = gameHandler->gameState()->hpool->getHeroesFor(player);
const CGHeroInstance *recruitedHero = nullptr;;
for(const auto & hero : recruitableHeroes)
{
if (hero->subID == heroToRecruit)
recruitedHero = hero;
}
if (!recruitedHero)
{
gameHandler->complain ("Hero is not available for hiring!");
return false;
}
HeroRecruited hr;
hr.tid = obj->id;
hr.hid = recruitedHero->subID;
hr.player = player;
hr.tile = recruitedHero->convertFromVisitablePos(obj->visitablePos());
if (gameHandler->getTile(hr.tile)->isWater())
{
//Create a new boat for hero
gameHandler->createObject(obj->visitablePos(), Obj::BOAT, recruitedHero->getBoatType().getNum());
hr.boatId = gameHandler->getTopObj(hr.tile)->id;
}
gameHandler->sendAndApply(&hr);
if (recruitableHeroes[0] == recruitedHero)
selectNewHeroForSlot(player, TavernHeroSlot::NATIVE);
else
selectNewHeroForSlot(player, TavernHeroSlot::RANDOM);
gameHandler->giveResource(player, EGameResID::GOLD, -GameConstants::HERO_GOLD_COST);
if(town)
{
gameHandler->visitCastleObjects(town, recruitedHero);
gameHandler->giveSpells(town, recruitedHero);
}
return true;
}

View File

@ -0,0 +1,45 @@
/*
* HeroPoolProcessor.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
VCMI_LIB_NAMESPACE_BEGIN
enum class TavernHeroSlot;
class PlayerColor;
class CGHeroInstance;
class HeroTypeID;
class CGObjectInstance;
VCMI_LIB_NAMESPACE_END
class CGameHandler;
class HeroPoolProcessor : boost::noncopyable
{
CGameHandler * gameHandler;
void clearHeroFromSlot(const PlayerColor & color, TavernHeroSlot slot);
void selectNewHeroForSlot(const PlayerColor & color, TavernHeroSlot slot);
public:
HeroPoolProcessor();
HeroPoolProcessor(CGameHandler * gameHandler);
void onHeroSurrendered(const PlayerColor & color, const CGHeroInstance * hero);
void onHeroEscaped(const PlayerColor & color, const CGHeroInstance * hero);
void onNewWeek(const PlayerColor & color);
bool hireHero(const CGObjectInstance *obj, const HeroTypeID & hid, const PlayerColor & player);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & gameHandler;
}
};

View File

@ -11,6 +11,8 @@
#include "ServerNetPackVisitors.h"
#include "CGameHandler.h"
#include "HeroPoolProcessor.h"
#include "../lib/IGameCallback.h"
#include "../lib/mapObjects/CGTownInstance.h"
#include "../lib/gameState/CGameState.h"
@ -251,7 +253,7 @@ void ApplyGhNetPackVisitor::visitHireHero(HireHero & pack)
if(town && PlayerRelations::ENEMIES == gh.getPlayerRelations(obj->tempOwner, gh.getPlayerAt(pack.c)))
gh.throwAndComplain(&pack, "Can't buy hero in enemy town!");
result = gh.hireHero(obj, pack.hid, pack.player);
result = gh.heroPool->hireHero(obj, pack.hid, pack.player);
}
void ApplyGhNetPackVisitor::visitBuildBoat(BuildBoat & pack)