1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-26 08:41:13 +02:00
vcmi/server/CGameHandler.cpp
Arseniy Shestakov 9fd1cff090 Refactoring: always use std prefix for shared_ptr, unique_ptr and make_shared
Long time ago it's was used without prefix to make future switch from boost to std version easier.
I discusses this with Ivan and decide to drop these using from Global.h now.

This change wouldn't break anything because there was already code with prefix for each of three cases.
2015-12-29 05:43:33 +03:00

5958 lines
175 KiB
C++

#include "StdInc.h"
#include "../lib/filesystem/Filesystem.h"
#include "../lib/filesystem/CFileInfo.h"
#include "../lib/int3.h"
#include "../lib/mapping/CCampaignHandler.h"
#include "../lib/StartInfo.h"
#include "../lib/CModHandler.h"
#include "../lib/CArtHandler.h"
#include "../lib/CBuildingHandler.h"
#include "../lib/CHeroHandler.h"
#include "../lib/spells/CSpellHandler.h"
#include "../lib/spells/ISpellMechanics.h"
#include "../lib/CGeneralTextHandler.h"
#include "../lib/CTownHandler.h"
#include "../lib/CCreatureHandler.h"
#include "../lib/CGameState.h"
#include "../lib/BattleState.h"
#include "../lib/CondSh.h"
#include "../lib/NetPacks.h"
#include "../lib/VCMI_Lib.h"
#include "../lib/mapping/CMap.h"
#include "../lib/VCMIDirs.h"
#include "../lib/ScopeGuard.h"
#include "../lib/CSoundBase.h"
#include "CGameHandler.h"
#include "CVCMIServer.h"
#include "../lib/CCreatureSet.h"
#include "../lib/CThreadHelper.h"
#include "../lib/GameConstants.h"
#include "../lib/registerTypes/RegisterTypes.h"
/*
* CGameHandler.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
*
*/
#ifndef _MSC_VER
#include <boost/thread/xtime.hpp>
#endif
extern bool end2;
#ifdef min
#undef min
#endif
#ifdef max
#undef max
#endif
#define COMPLAIN_RET_IF(cond, txt) do {if(cond){complain(txt); return;}} while(0)
#define COMPLAIN_RET_FALSE_IF(cond, txt) do {if(cond){complain(txt); return false;}} while(0)
#define COMPLAIN_RET(txt) {complain(txt); return false;}
#define COMPLAIN_RETF(txt, FORMAT) {complain(boost::str(boost::format(txt) % FORMAT)); return false;}
#define NEW_ROUND BattleNextRound bnr;\
bnr.round = gs->curB->round + 1;\
sendAndApply(&bnr);
class ServerSpellCastEnvironment: public SpellCastEnvironment
{
public:
ServerSpellCastEnvironment(CGameHandler * gh);
~ServerSpellCastEnvironment(){};
void sendAndApply(CPackForClient * info) const override;
CRandomGenerator & getRandomGenerator() const override;
void complain(const std::string & problem) const override;
const CMap * getMap() const override;
const CGameInfoCallback * getCb() const override;
bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, PlayerColor asker = PlayerColor::NEUTRAL) const override;
private:
mutable CGameHandler * gh;
};
CondSh<bool> battleMadeAction;
CondSh<BattleResult *> battleResult(nullptr);
template <typename T> class CApplyOnGH;
class CBaseForGHApply
{
public:
virtual bool applyOnGH(CGameHandler *gh, CConnection *c, void *pack, PlayerColor player) const =0;
virtual ~CBaseForGHApply(){}
template<typename U> static CBaseForGHApply *getApplier(const U * t=nullptr)
{
return new CApplyOnGH<U>;
}
};
template <typename T> class CApplyOnGH : public CBaseForGHApply
{
public:
bool applyOnGH(CGameHandler *gh, CConnection *c, void *pack, PlayerColor player) const
{
T *ptr = static_cast<T*>(pack);
ptr->c = c;
ptr->player = player;
return ptr->applyGh(gh);
}
};
template <>
class CApplyOnGH<CPack> : public CBaseForGHApply
{
public:
bool applyOnGH(CGameHandler *gh, CConnection *c, void *pack, PlayerColor player) const
{
logGlobal->errorStream() << "Cannot apply on GH plain CPack!";
assert(0);
return false;
}
};
static CApplier<CBaseForGHApply> *applier = nullptr;
CMP_stack cmpst ;
static inline double distance(int3 a, int3 b)
{
return std::sqrt( (double)(a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y) );
}
static void giveExp(BattleResult &r)
{
r.exp[0] = 0;
r.exp[1] = 0;
for(auto i = r.casualties[!r.winner].begin(); i!=r.casualties[!r.winner].end(); i++)
{
r.exp[r.winner] += VLC->creh->creatures.at(i->first)->valOfBonuses(Bonus::STACK_HEALTH) * i->second;
}
}
PlayerStatus PlayerStatuses::operator[](PlayerColor player)
{
boost::unique_lock<boost::mutex> l(mx);
if(players.find(player) != players.end())
{
return players.at(player);
}
else
{
throw std::runtime_error("No such player!");
}
}
void PlayerStatuses::addPlayer(PlayerColor player)
{
boost::unique_lock<boost::mutex> l(mx);
players[player];
}
bool PlayerStatuses::checkFlag(PlayerColor player, bool PlayerStatus::*flag)
{
boost::unique_lock<boost::mutex> l(mx);
if(players.find(player) != players.end())
{
return players[player].*flag;
}
else
{
throw std::runtime_error("No such player!");
}
}
void PlayerStatuses::setFlag(PlayerColor player, bool PlayerStatus::*flag, bool val)
{
boost::unique_lock<boost::mutex> l(mx);
if(players.find(player) != players.end())
{
players[player].*flag = val;
}
else
{
throw std::runtime_error("No such player!");
}
cv.notify_all();
}
template <typename T>
void callWith(std::vector<T> args, std::function<void(T)> fun, ui32 which)
{
fun(args[which]);
}
void CGameHandler::levelUpHero(const CGHeroInstance * hero, SecondarySkill skill)
{
changeSecSkill(hero, skill, 1, 0);
expGiven(hero);
}
void CGameHandler::levelUpHero(const CGHeroInstance * hero)
{
// required exp for at least 1 lvl-up hasn't been reached
if(!hero->gainsLevel())
{
return;
}
// give primary skill
logGlobal->traceStream() << hero->name << " got level "<< hero->level;
auto primarySkill = hero->nextPrimarySkill();
SetPrimSkill sps;
sps.id = hero->id;
sps.which = primarySkill;
sps.abs = false;
sps.val = 1;
sendAndApply(&sps);
HeroLevelUp hlu;
hlu.hero = hero;
hlu.primskill = primarySkill;
hlu.skills = hero->getLevelUpProposedSecondarySkills();
if(hlu.skills.size() == 0)
{
sendAndApply(&hlu);
levelUpHero(hero);
}
else if(hlu.skills.size() == 1 || hero->tempOwner == PlayerColor::NEUTRAL) //choose skill automatically
{
sendAndApply(&hlu);
levelUpHero(hero, *RandomGeneratorUtil::nextItem(hlu.skills, hero->skillsInfo.rand));
}
else if(hlu.skills.size() > 1)
{
auto levelUpQuery = std::make_shared<CHeroLevelUpDialogQuery>(hlu);
hlu.queryID = levelUpQuery->queryID;
queries.addQuery(levelUpQuery);
sendAndApply(&hlu);
//level up will be called on query reply
}
}
void CGameHandler::levelUpCommander (const CCommanderInstance * c, int skill)
{
SetCommanderProperty scp;
auto hero = dynamic_cast<const CGHeroInstance *>(c->armyObj);
if (hero)
scp.heroid = hero->id;
else
{
complain ("Commander is not led by hero!");
return;
}
scp.accumulatedBonus.subtype = 0;
scp.accumulatedBonus.additionalInfo = 0;
scp.accumulatedBonus.duration = Bonus::PERMANENT;
scp.accumulatedBonus.turnsRemain = 0;
scp.accumulatedBonus.source = Bonus::COMMANDER;
scp.accumulatedBonus.valType = Bonus::BASE_NUMBER;
if (skill <= ECommander::SPELL_POWER)
{
scp.which = SetCommanderProperty::BONUS;
auto difference = [](std::vector< std::vector <ui8> > skillLevels, std::vector <ui8> secondarySkills, int skill)->int
{
int s = std::min (skill, (int)ECommander::SPELL_POWER); //spell power level controls also casts and resistance
return skillLevels.at(skill).at(secondarySkills.at(s)) - (secondarySkills.at(s) ? skillLevels.at(skill).at(secondarySkills.at(s)-1) : 0);
};
switch (skill)
{
case ECommander::ATTACK:
scp.accumulatedBonus.type = Bonus::PRIMARY_SKILL;
scp.accumulatedBonus.subtype = PrimarySkill::ATTACK;
break;
case ECommander::DEFENSE:
scp.accumulatedBonus.type = Bonus::PRIMARY_SKILL;
scp.accumulatedBonus.subtype = PrimarySkill::DEFENSE;
break;
case ECommander::HEALTH:
scp.accumulatedBonus.type = Bonus::STACK_HEALTH;
scp.accumulatedBonus.valType = Bonus::PERCENT_TO_BASE;
break;
case ECommander::DAMAGE:
scp.accumulatedBonus.type = Bonus::CREATURE_DAMAGE;
scp.accumulatedBonus.subtype = 0;
scp.accumulatedBonus.valType = Bonus::PERCENT_TO_BASE;
break;
case ECommander::SPEED:
scp.accumulatedBonus.type = Bonus::STACKS_SPEED;
break;
case ECommander::SPELL_POWER:
scp.accumulatedBonus.type = Bonus::MAGIC_RESISTANCE;
scp.accumulatedBonus.val = difference (VLC->creh->skillLevels, c->secondarySkills, ECommander::RESISTANCE);
sendAndApply (&scp); //additional pack
scp.accumulatedBonus.type = Bonus::CREATURE_SPELL_POWER;
scp.accumulatedBonus.val = difference (VLC->creh->skillLevels, c->secondarySkills, ECommander::SPELL_POWER) * 100; //like hero with spellpower = ability level
sendAndApply (&scp); //additional pack
scp.accumulatedBonus.type = Bonus::CASTS;
scp.accumulatedBonus.val = difference (VLC->creh->skillLevels, c->secondarySkills, ECommander::CASTS);
sendAndApply (&scp); //additional pack
scp.accumulatedBonus.type = Bonus::CREATURE_ENCHANT_POWER; //send normally
break;
}
scp.accumulatedBonus.val = difference (VLC->creh->skillLevels, c->secondarySkills, skill);
sendAndApply (&scp);
scp.which = SetCommanderProperty::SECONDARY_SKILL;
scp.additionalInfo = skill;
scp.amount = c->secondarySkills.at(skill) + 1;
sendAndApply (&scp);
}
else if (skill >= 100)
{
scp.which = SetCommanderProperty::SPECIAL_SKILL;
scp.accumulatedBonus = *VLC->creh->skillRequirements.at(skill-100).first;
scp.additionalInfo = skill; //unnormalized
sendAndApply (&scp);
}
expGiven(hero);
}
void CGameHandler::levelUpCommander(const CCommanderInstance * c)
{
if (!c->gainsLevel())
{
return;
}
CommanderLevelUp clu;
auto hero = dynamic_cast<const CGHeroInstance *>(c->armyObj);
if (hero)
clu.hero = hero;
else
{
complain ("Commander is not led by hero!");
return;
}
//picking sec. skills for choice
for (int i = 0; i <= ECommander::SPELL_POWER; ++i)
{
if (c->secondarySkills.at(i) < ECommander::MAX_SKILL_LEVEL)
clu.skills.push_back(i);
}
int i = 100;
for (auto specialSkill : VLC->creh->skillRequirements)
{
if (c->secondarySkills.at(specialSkill.second.first) == ECommander::MAX_SKILL_LEVEL
&& c->secondarySkills.at(specialSkill.second.second) == ECommander::MAX_SKILL_LEVEL
&& !vstd::contains (c->specialSKills, i))
clu.skills.push_back (i);
++i;
}
int skillAmount = clu.skills.size();
if(!skillAmount)
{
sendAndApply(&clu);
levelUpCommander(c);
}
else if(skillAmount == 1 || hero->tempOwner == PlayerColor::NEUTRAL) //choose skill automatically
{
sendAndApply(&clu);
levelUpCommander(c, *RandomGeneratorUtil::nextItem(clu.skills, gs->getRandomGenerator()));
}
else if(skillAmount > 1) //apply and ask for secondary skill
{
auto commanderLevelUp = std::make_shared<CCommanderLevelUpDialogQuery>(clu);
clu.queryID = commanderLevelUp->queryID;
queries.addQuery(commanderLevelUp);
sendAndApply(&clu);
}
}
void CGameHandler::expGiven(const CGHeroInstance *hero)
{
if(hero->gainsLevel())
levelUpHero(hero);
else if(hero->commander && hero->commander->gainsLevel())
levelUpCommander(hero->commander);
//if(hero->commander && hero->level > hero->commander->level && hero->commander->gainsLevel())
// levelUpCommander(hero->commander);
// else
// levelUpHero(hero);
}
void CGameHandler::changePrimSkill(const CGHeroInstance * hero, PrimarySkill::PrimarySkill which, si64 val, bool abs)
{
if (which == PrimarySkill::EXPERIENCE) // Check if scenario limit reached
{
if (gs->map->levelLimit != 0)
{
TExpType expLimit = VLC->heroh->reqExp(gs->map->levelLimit);
TExpType resultingExp = abs ? val : hero->exp + val;
if (resultingExp > expLimit)
{
// set given experience to max possible, but don't decrease if hero already over top
abs = true;
val = std::max(expLimit, hero->exp);
InfoWindow iw;
iw.player = hero->tempOwner;
iw.text.addTxt(MetaString::GENERAL_TXT, 1); //can gain no more XP
iw.text.addReplacement(hero->name);
sendAndApply(&iw);
}
}
}
SetPrimSkill sps;
sps.id = hero->id;
sps.which = which;
sps.abs = abs;
sps.val = val;
sendAndApply(&sps);
//only for exp - hero may level up
if (which == PrimarySkill::EXPERIENCE)
{
if(hero->commander && hero->commander->alive)
{
//FIXME: trim experience according to map limit?
SetCommanderProperty scp;
scp.heroid = hero->id;
scp.which = SetCommanderProperty::EXPERIENCE;
scp.amount = val;
sendAndApply (&scp);
CBonusSystemNode::treeHasChanged();
}
expGiven(hero);
}
}
void CGameHandler::changeSecSkill( const CGHeroInstance * hero, SecondarySkill which, int val, bool abs/*=false*/ )
{
SetSecSkill sss;
sss.id = hero->id;
sss.which = which;
sss.val = val;
sss.abs = abs;
sendAndApply(&sss);
if(which == SecondarySkill::WISDOM)
{
if(hero && hero->visitedTown)
giveSpells(hero->visitedTown, hero);
}
}
void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2)
{
LOG_TRACE(logGlobal);
//Fill BattleResult structure with exp info
giveExp(*battleResult.data);
if (battleResult.get()->result == BattleResult::NORMAL) // give 500 exp for defeating hero, unless he escaped
{
if (hero1)
battleResult.data->exp[1] += 500;
if (hero2)
battleResult.data->exp[0] += 500;
}
if (hero1)
battleResult.data->exp[0] = hero1->calculateXp(battleResult.data->exp[0]);//scholar skill
if (hero2)
battleResult.data->exp[1] = hero2->calculateXp(battleResult.data->exp[1]);
const CArmedInstance *bEndArmy1 = gs->curB->sides.at(0).armyObject;
const CArmedInstance *bEndArmy2 = gs->curB->sides.at(1).armyObject;
const BattleResult::EResult result = battleResult.get()->result;
auto findBattleQuery = [this] () -> std::shared_ptr<CBattleQuery>
{
for(auto &q : queries.allQueries())
{
if(auto bq = std::dynamic_pointer_cast<CBattleQuery>(q))
if(bq->bi == gs->curB)
return bq;
}
return std::shared_ptr<CBattleQuery>();
};
auto battleQuery = findBattleQuery();
if(!battleQuery)
{
logGlobal->errorStream() << "Cannot find battle query!";
if(gs->initialOpts->mode == StartInfo::DUEL)
{
battleQuery = std::make_shared<CBattleQuery>(gs->curB);
}
}
if(battleQuery != queries.topQuery(gs->curB->sides[0].color))
complain("Player " + boost::lexical_cast<std::string>(gs->curB->sides[0].color) + " although in battle has no battle query at the top!");
battleQuery->result = *battleResult.data;
//Check how many battle queries were created (number of players blocked by battle)
const int queriedPlayers = battleQuery ? boost::count(queries.allQueries(), battleQuery) : 0;
finishingBattle = make_unique<FinishingBattleHelper>(battleQuery, gs->initialOpts->mode == StartInfo::DUEL, queriedPlayers);
CasualtiesAfterBattle cab1(bEndArmy1, gs->curB), cab2(bEndArmy2, gs->curB); //calculate casualties before deleting battle
if(finishingBattle->duel)
{
duelFinished();
sendAndApply(battleResult.data); //after this point casualties objects are destroyed
return;
}
ChangeSpells cs; //for Eagle Eye
if(finishingBattle->winnerHero)
{
if(int eagleEyeLevel = finishingBattle->winnerHero->getSecSkillLevel(SecondarySkill::EAGLE_EYE))
{
int maxLevel = eagleEyeLevel + 1;
double eagleEyeChance = finishingBattle->winnerHero->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::EAGLE_EYE);
for(const CSpell *sp : gs->curB->sides.at(!battleResult.data->winner).usedSpellsHistory)
if(sp->level <= maxLevel && !vstd::contains(finishingBattle->winnerHero->spells, sp->id) && gs->getRandomGenerator().nextInt(99) < eagleEyeChance)
cs.spells.insert(sp->id);
}
}
std::vector<ui32> arts; //display them in window
if (result == BattleResult::NORMAL && finishingBattle->winnerHero)
{
if (finishingBattle->loserHero)
{
auto artifactsWorn = finishingBattle->loserHero->artifactsWorn; //TODO: wrap it into a function, somehow (boost::variant -_-)
for (auto artSlot : artifactsWorn)
{
MoveArtifact ma;
ma.src = ArtifactLocation (finishingBattle->loserHero, artSlot.first);
const CArtifactInstance * art = ma.src.getArt();
if (art && !art->artType->isBig() && art->artType->id != ArtifactID::SPELLBOOK) // don't move war machines or locked arts (spellbook)
{
arts.push_back (art->artType->id);
ma.dst = ArtifactLocation (finishingBattle->winnerHero, art->firstAvailableSlot(finishingBattle->winnerHero));
sendAndApply(&ma);
}
}
while (!finishingBattle->loserHero->artifactsInBackpack.empty())
{
//we assume that no big artifacts can be found
MoveArtifact ma;
ma.src = ArtifactLocation (finishingBattle->loserHero,
ArtifactPosition(GameConstants::BACKPACK_START)); //backpack automatically shifts arts to beginning
const CArtifactInstance * art = ma.src.getArt();
arts.push_back (art->artType->id);
ma.dst = ArtifactLocation (finishingBattle->winnerHero, art->firstAvailableSlot(finishingBattle->winnerHero));
sendAndApply(&ma);
}
if (finishingBattle->loserHero->commander) //TODO: what if commanders belong to no hero?
{
artifactsWorn = finishingBattle->loserHero->commander->artifactsWorn;
for (auto artSlot : artifactsWorn)
{
MoveArtifact ma;
ma.src = ArtifactLocation (finishingBattle->loserHero->commander.get(), artSlot.first);
const CArtifactInstance * art = ma.src.getArt();
if (art && !art->artType->isBig())
{
arts.push_back (art->artType->id);
ma.dst = ArtifactLocation (finishingBattle->winnerHero, art->firstAvailableSlot(finishingBattle->winnerHero));
sendAndApply(&ma);
}
}
}
}
for (auto armySlot : gs->curB->battleGetArmyObject(!battleResult.data->winner)->stacks)
{
auto artifactsWorn = armySlot.second->artifactsWorn;
for (auto artSlot : artifactsWorn)
{
MoveArtifact ma;
ma.src = ArtifactLocation (armySlot.second, artSlot.first);
const CArtifactInstance * art = ma.src.getArt();
if (art && !art->artType->isBig())
{
arts.push_back (art->artType->id);
ma.dst = ArtifactLocation (finishingBattle->winnerHero, art->firstAvailableSlot(finishingBattle->winnerHero));
sendAndApply(&ma);
}
}
}
}
sendAndApply(battleResult.data); //after this point casualties objects are destroyed
if (arts.size()) //display loot
{
InfoWindow iw;
iw.player = finishingBattle->winnerHero->tempOwner;
iw.text.addTxt (MetaString::GENERAL_TXT, 30); //You have captured enemy artifact
for (auto id : arts) //TODO; separate function to display loot for various ojects?
{
iw.components.push_back (Component (Component::ARTIFACT, id, 0, 0));
if(iw.components.size() >= 14)
{
sendAndApply(&iw);
iw.components.clear();
}
}
if (iw.components.size())
{
sendAndApply(&iw);
}
}
//Eagle Eye secondary skill handling
if(!cs.spells.empty())
{
cs.learn = 1;
cs.hid = finishingBattle->winnerHero->id;
InfoWindow iw;
iw.player = finishingBattle->winnerHero->tempOwner;
iw.text.addTxt(MetaString::GENERAL_TXT, 221); //Through eagle-eyed observation, %s is able to learn %s
iw.text.addReplacement(finishingBattle->winnerHero->name);
std::ostringstream names;
for(int i = 0; i < cs.spells.size(); i++)
{
names << "%s";
if(i < cs.spells.size() - 2)
names << ", ";
else if(i < cs.spells.size() - 1)
names << "%s";
}
names << ".";
iw.text.addReplacement(names.str());
auto it = cs.spells.begin();
for(int i = 0; i < cs.spells.size(); i++, it++)
{
iw.text.addReplacement(MetaString::SPELL_NAME, it->toEnum());
if(i == cs.spells.size() - 2) //we just added pre-last name
iw.text.addReplacement(MetaString::GENERAL_TXT, 141); // " and "
iw.components.push_back(Component(Component::SPELL, *it, 0, 0));
}
sendAndApply(&iw);
sendAndApply(&cs);
}
cab1.takeFromArmy(this);
cab2.takeFromArmy(this); //take casualties after battle is deleted
//if one hero has lost we will erase him
if(battleResult.data->winner!=0 && hero1)
{
RemoveObject ro(hero1->id);
sendAndApply(&ro);
}
if(battleResult.data->winner!=1 && hero2)
{
RemoveObject ro(hero2->id);
sendAndApply(&ro);
}
//give exp
if (battleResult.data->exp[0] && hero1 && battleResult.get()->winner == 0)
changePrimSkill(hero1, PrimarySkill::EXPERIENCE, battleResult.data->exp[0]);
else if (battleResult.data->exp[1] && hero2 && battleResult.get()->winner == 1)
changePrimSkill(hero2, PrimarySkill::EXPERIENCE, battleResult.data->exp[1]);
queries.popIfTop(battleQuery);
//--> continuation (battleAfterLevelUp) occurs after level-up queries are handled or on removing query (above)
}
void CGameHandler::battleAfterLevelUp( const BattleResult &result )
{
LOG_TRACE(logGlobal);
finishingBattle->remainingBattleQueriesCount--;
logGlobal->traceStream() << "Decremented queries count to " << finishingBattle->remainingBattleQueriesCount;
if(finishingBattle->remainingBattleQueriesCount > 0)
//Battle results will be handled when all battle queries are closed
return;
//TODO consider if we really want it to work like above. ATM each player as unblocked as soon as possible
// but the battle consequences are applied after final player is unblocked. Hard to abuse...
// Still, it looks like a hole.
// Necromancy if applicable.
const CStackBasicDescriptor raisedStack = finishingBattle->winnerHero ? finishingBattle->winnerHero->calculateNecromancy(*battleResult.data) : CStackBasicDescriptor();
// Give raised units to winner and show dialog, if any were raised,
// units will be given after casualties are taken
const SlotID necroSlot = raisedStack.type ? finishingBattle->winnerHero->getSlotFor(raisedStack.type) : SlotID();
if (necroSlot != SlotID())
{
finishingBattle->winnerHero->showNecromancyDialog(raisedStack);
addToSlot(StackLocation(finishingBattle->winnerHero, necroSlot), raisedStack.type, raisedStack.count);
}
BattleResultsApplied resultsApplied;
resultsApplied.player1 = finishingBattle->victor;
resultsApplied.player2 = finishingBattle->loser;
sendAndApply(&resultsApplied);
setBattle(nullptr);
if(visitObjectAfterVictory && result.winner==0)
{
logGlobal->traceStream() << "post-victory visit";
visitObjectOnTile(*getTile(finishingBattle->winnerHero->getPosition()), finishingBattle->winnerHero);
}
visitObjectAfterVictory = false;
//handle victory/loss of engaged players
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(const CGHeroInstance *another = getPlayer(finishingBattle->loser)->availableHeroes.at(1))
sah.hid[1] = another->subID;
else
sah.hid[1] = -1;
sendAndApply(&sah);
}
}
void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CStack *def, int distance, int targetHex)
{
bat.bsa.clear();
bat.stackAttacking = att->ID;
const int attackerLuck = att->LuckVal();
auto sideHeroBlocksLuck = [](const SideInBattle &side){ return NBonus::hasOfType(side.hero, Bonus::BLOCK_LUCK); };
if(!vstd::contains_if(gs->curB->sides, sideHeroBlocksLuck))
{
if(attackerLuck > 0 && gs->getRandomGenerator().nextInt(23) < attackerLuck)
{
bat.flags |= BattleAttack::LUCKY;
}
if (VLC->modh->settings.data["hardcodedFeatures"]["NEGATIVE_LUCK"].Bool()) // negative luck enabled
{
if (attackerLuck < 0 && gs->getRandomGenerator().nextInt(23) < abs(attackerLuck))
{
bat.flags |= BattleAttack::UNLUCKY;
}
}
}
if(gs->getRandomGenerator().nextInt(99) < att->valOfBonuses(Bonus::DOUBLE_DAMAGE_CHANCE))
{
bat.flags |= BattleAttack::DEATH_BLOW;
}
if(att->getCreature()->idNumber == CreatureID::BALLISTA)
{
static const int artilleryLvlToChance[] = {0, 50, 75, 100};
const CGHeroInstance * owner = gs->curB->getHero(att->owner);
int chance = artilleryLvlToChance[owner->getSecSkillLevel(SecondarySkill::ARTILLERY)];
if(chance > gs->getRandomGenerator().nextInt(99))
{
bat.flags |= BattleAttack::BALLISTA_DOUBLE_DMG;
}
}
// only primary target
applyBattleEffects(bat, att, def, distance, false);
if (!bat.shot()) //multiple-hex attack - only in meele
{
std::set<const CStack*> attackedCreatures = gs->curB->getAttackedCreatures(att, targetHex); //creatures other than primary target
for(const CStack * stack : attackedCreatures)
{
if (stack != def) //do not hit same stack twice
{
applyBattleEffects(bat, att, stack, distance, true);
}
}
}
const Bonus * bonus = att->getBonusLocalFirst(Selector::type(Bonus::SPELL_LIKE_ATTACK));
if (bonus && (bat.shot())) //TODO: make it work in melee?
{
//this is need for displaying hit animation
bat.flags |= BattleAttack::SPELL_LIKE;
bat.spellID = SpellID(bonus->subtype);
//TODO: should spell override creature`s projectile?
std::set<const CStack*> attackedCreatures = SpellID(bonus->subtype).toSpell()->getAffectedStacks(gs->curB, ECastingMode::SPELL_LIKE_ATTACK, att->owner, bonus->val, targetHex, att);
//TODO: get exact attacked hex for defender
for(const CStack * stack : attackedCreatures)
{
if (stack != def) //do not hit same stack twice
{
applyBattleEffects(bat, att, stack, distance, true);
}
}
//now add effect info for all attacked stacks
for(BattleStackAttacked & bsa : bat.bsa)
{
if(bsa.attackerID == att->ID) //this is our attack and not f.e. fire shield
{
//this is need for displaying affect animation
bsa.flags |= BattleStackAttacked::SPELL_EFFECT;
bsa.spellID = SpellID(bonus->subtype);
}
}
}
}
void CGameHandler::applyBattleEffects(BattleAttack &bat, const CStack *att, const CStack *def, int distance, bool secondary) //helper function for prepareAttack
{
BattleStackAttacked bsa;
if (secondary)
bsa.flags |= BattleStackAttacked::SECONDARY; //all other targets do not suffer from spells & spell-like abilities
bsa.attackerID = att->ID;
bsa.stackAttacked = def->ID;
bsa.damageAmount = gs->curB->calculateDmg(att, def, gs->curB->battleGetOwner(att), gs->curB->battleGetOwner(def),
bat.shot(), distance, bat.lucky(), bat.unlucky(), bat.deathBlow(), bat.ballistaDoubleDmg(), gs->getRandomGenerator());
def->prepareAttacked(bsa, gs->getRandomGenerator()); //calculate casualties
//life drain handling
if (att->hasBonusOfType(Bonus::LIFE_DRAIN) && def->isLiving())
{
StacksHealedOrResurrected shi;
shi.lifeDrain = true;
shi.tentHealing = false;
shi.drainedFrom = def->ID;
StacksHealedOrResurrected::HealInfo hi;
hi.stackID = att->ID;
hi.healedHP = att->calculateHealedHealthPoints(bsa.damageAmount * att->valOfBonuses (Bonus::LIFE_DRAIN) / 100, true);
hi.lowLevelResurrection = false;
shi.healedStacks.push_back(hi);
if (hi.healedHP > 0)
{
bsa.healedStacks.push_back(shi);
}
}
bat.bsa.push_back(bsa); //add this stack to the list of victims after drain life has been calculated
//fire shield handling
if(!bat.shot() && def->hasBonusOfType(Bonus::FIRE_SHIELD) && !att->hasBonusOfType (Bonus::FIRE_IMMUNITY))
{
BattleStackAttacked bsa2;
bsa2.stackAttacked = att->ID; //invert
bsa2.attackerID = def->ID;
bsa2.flags |= BattleStackAttacked::EFFECT; //FIXME: play animation upon efreet and not attacker
bsa2.effect = 11;
bsa2.damageAmount = (bsa.damageAmount * def->valOfBonuses(Bonus::FIRE_SHIELD)) / 100; //TODO: scale with attack/defense
att->prepareAttacked(bsa2, gameState()->getRandomGenerator());
bat.bsa.push_back(bsa2);
}
}
void CGameHandler::handleConnection(std::set<PlayerColor> players, CConnection &c)
{
setThreadName("CGameHandler::handleConnection");
try
{
while(1)//server should never shut connection first //was: while(!end2)
{
CPack *pack = nullptr;
PlayerColor player = PlayerColor::NEUTRAL;
si32 requestID = -999;
int packType = 0;
{
boost::unique_lock<boost::mutex> lock(*c.rmx);
c >> player >> requestID >> pack; //get the package
if(!pack)
{
logGlobal ->errorStream() << boost::format("Received a null package marked as request %d from player %d") % requestID % player;
}
packType = typeList.getTypeID(pack); //get the id of type
logGlobal->traceStream() << boost::format("Received client message (request %d by player %d) of type with ID=%d (%s).\n")
% requestID % player.getNum() % packType % typeid(*pack).name();
}
//prepare struct informing that action was applied
auto sendPackageResponse = [&](bool succesfullyApplied)
{
PackageApplied applied;
applied.player = player;
applied.result = succesfullyApplied;
applied.packType = packType;
applied.requestID = requestID;
boost::unique_lock<boost::mutex> lock(*c.wmx);
c << &applied;
};
CBaseForGHApply *apply = applier->apps[packType]; //and appropriate applier object
if(isBlockedByQueries(pack, player))
{
sendPackageResponse(false);
}
else if(apply)
{
const bool result = apply->applyOnGH(this,&c,pack, player);
if(!result)
complain("Got false in applying... that request must have been fishy!");
logGlobal->traceStream() << "Message successfully applied (result=" << result << ")!";
sendPackageResponse(true);
}
else
{
logGlobal->errorStream() << "Message cannot be applied, cannot find applier (unregistered type)!";
sendPackageResponse(false);
}
vstd::clear_pointer(pack);
}
}
catch(boost::system::system_error &e) //for boost errors just log, not crash - probably client shut down connection
{
assert(!c.connected); //make sure that connection has been marked as broken
logGlobal->errorStream() << e.what();
end2 = true;
}
catch(...)
{
end2 = true;
handleException();
throw;
}
logGlobal->errorStream() << "Ended handling connection";
}
int CGameHandler::moveStack(int stack, BattleHex dest)
{
int ret = 0;
const CStack *curStack = gs->curB->battleGetStackByID(stack),
*stackAtEnd = gs->curB->battleGetStackByPos(dest);
assert(curStack);
assert(dest < GameConstants::BFIELD_SIZE);
if (gs->curB->tacticDistance)
{
assert(gs->curB->isInTacticRange(dest));
}
if(curStack->position == dest)
return 0;
//initing necessary tables
auto accessibility = getAccesibility(curStack);
//shifting destination (if we have double wide stack and we can occupy dest but not be exactly there)
if(!stackAtEnd && curStack->doubleWide() && !accessibility.accessible(dest, curStack))
{
if(curStack->attackerOwned)
{
if(accessibility.accessible(dest+1, curStack))
dest += BattleHex::RIGHT;
}
else
{
if(accessibility.accessible(dest-1, curStack))
dest += BattleHex::LEFT;
}
}
if((stackAtEnd && stackAtEnd!=curStack && stackAtEnd->alive()) || !accessibility.accessible(dest, curStack))
{
complain("Given destination is not accessible!");
return 0;
}
std::pair< std::vector<BattleHex>, int > path = gs->curB->getPath(curStack->position, dest, curStack);
ret = path.second;
int creSpeed = gs->curB->tacticDistance ? GameConstants::BFIELD_SIZE : curStack->Speed();
if(curStack->hasBonusOfType(Bonus::FLYING))
{
if(path.second <= creSpeed && path.first.size() > 0)
{
//inform clients about move
BattleStackMoved sm;
sm.stack = curStack->ID;
std::vector<BattleHex> tiles;
tiles.push_back(path.first[0]);
sm.tilesToMove = tiles;
sm.distance = path.second;
sm.teleporting = false;
sendAndApply(&sm);
}
}
else //for non-flying creatures
{
std::shared_ptr<const CObstacleInstance> obstacle, obstacle2; //obstacle that interrupted movement
std::vector<BattleHex> tiles;
const int tilesToMove = std::max((int)(path.first.size() - creSpeed), 0);
int v = path.first.size()-1;
bool stackIsMoving = true;
while(stackIsMoving)
{
if(v<tilesToMove)
{
logGlobal->error("Movement terminated abnormally");
break;
}
for(bool obstacleHit = false; (!obstacleHit) && (v >= tilesToMove); --v)
{
BattleHex hex = path.first[v];
tiles.push_back(hex);
//if we walked onto something, finalize this portion of stack movement check into obstacle
if((obstacle = battleGetObstacleOnPos(hex, false)))
obstacleHit = true;
if(curStack->doubleWide())
{
BattleHex otherHex = curStack->occupiedHex(hex);
//two hex creature hit obstacle by backside
if(otherHex.isValid() && ((obstacle2 = battleGetObstacleOnPos(otherHex, false))))
obstacleHit = true;
}
}
if(tiles.size() > 0)
{
//commit movement
BattleStackMoved sm;
sm.stack = curStack->ID;
sm.distance = path.second;
sm.teleporting = false;
sm.tilesToMove = tiles;
sendAndApply(&sm);
tiles.clear();
}
//we don't handle obstacle at the destination tile -> it's handled separately in the if at the end
if(curStack->position != dest)
{
auto processObstacle = [&](std::shared_ptr<const CObstacleInstance> & obs)
{
if(obs)
{
handleDamageFromObstacle(*obs, curStack);
//if stack die in explosion or interrupted by obstacle, abort movement
if(obs->stopsMovement() || !curStack->alive())
stackIsMoving = false;
obs.reset();
}
};
processObstacle(obstacle);
if(curStack->alive())
processObstacle(obstacle2);
}
else
//movement finished normally: we reached destination
stackIsMoving = false;
}
}
//handling obstacle on the final field (separate, because it affects both flying and walking stacks)
if(curStack->alive())
{
if(auto theLastObstacle = battleGetObstacleOnPos(curStack->position, false))
{
handleDamageFromObstacle(*theLastObstacle, curStack);
}
}
if(curStack->alive() && curStack->doubleWide())
{
BattleHex otherHex = curStack->occupiedHex(curStack->position);
if(otherHex.isValid())
if(auto theLastObstacle = battleGetObstacleOnPos(otherHex, false))
{
//two hex creature hit obstacle by backside
handleDamageFromObstacle(*theLastObstacle, curStack);
}
}
return ret;
}
CGameHandler::CGameHandler(void)
{
QID = 1;
//gs = nullptr;
IObjectInterface::cb = this;
applier = new CApplier<CBaseForGHApply>;
registerTypesServerPacks(*applier);
visitObjectAfterVictory = false;
queries.gh = this;
spellEnv = new ServerSpellCastEnvironment(this);
}
CGameHandler::~CGameHandler(void)
{
delete spellEnv;
delete applier;
applier = nullptr;
delete gs;
}
void CGameHandler::init(StartInfo *si)
{
if(si->seedToBeUsed == 0)
{
si->seedToBeUsed = std::time(nullptr);
}
gs = new CGameState();
logGlobal->infoStream() << "Gamestate created!";
gs->init(si);
logGlobal->infoStream() << "Gamestate initialized!";
// reset seed, so that clients can't predict any following random values
gs->getRandomGenerator().resetSeed();
for(auto & elem : gs->players)
{
states.addPlayer(elem.first);
}
}
static bool evntCmp(const CMapEvent &a, const CMapEvent &b)
{
return a.earlierThan(b);
}
void CGameHandler::setPortalDwelling(const CGTownInstance * town, bool forced=false, bool clear = false)
{// bool forced = true - if creature should be replaced, if false - only if no creature was set
const PlayerState *p = gs->getPlayer(town->tempOwner);
if(!p)
{
logGlobal->warnStream() << "There is no player owner of town " << town->name << " at " << town->pos;
return;
}
if (forced || town->creatures.at(GameConstants::CREATURES_PER_TOWN).second.empty())//we need to change creature
{
SetAvailableCreatures ssi;
ssi.tid = town->id;
ssi.creatures = town->creatures;
ssi.creatures[GameConstants::CREATURES_PER_TOWN].second.clear();//remove old one
const std::vector<ConstTransitivePtr<CGDwelling> > &dwellings = p->dwellings;
if (dwellings.empty())//no dwellings - just remove
{
sendAndApply(&ssi);
return;
}
auto dwelling = *RandomGeneratorUtil::nextItem(dwellings, gs->getRandomGenerator());
// for multi-creature dwellings like Golem Factory
auto creatureId = RandomGeneratorUtil::nextItem(dwelling->creatures, gs->getRandomGenerator())->second[0];
if(clear)
{
ssi.creatures[GameConstants::CREATURES_PER_TOWN].first = std::max((ui32)1, (VLC->creh->creatures.at(creatureId)->growth)/2);
}
else
{
ssi.creatures[GameConstants::CREATURES_PER_TOWN].first = VLC->creh->creatures.at(creatureId)->growth;
}
ssi.creatures[GameConstants::CREATURES_PER_TOWN].second.push_back(creatureId);
sendAndApply(&ssi);
}
}
void CGameHandler::newTurn()
{
logGlobal->traceStream() << "Turn " << gs->day+1;
NewTurn n;
n.specialWeek = NewTurn::NO_ACTION;
n.creatureid = CreatureID::NONE;
n.day = gs->day + 1;
bool firstTurn = !getDate(Date::DAY);
bool newWeek = getDate(Date::DAY_OF_WEEK) == 7; //day numbers are confusing, as day was not yet switched
bool newMonth = getDate(Date::DAY_OF_MONTH) == 28;
std::map<PlayerColor, si32> hadGold;//starting gold - for buildings like dwarven treasury
if (firstTurn)
{
for (auto obj : gs->map->objects)
{
if (obj && obj->ID == Obj::PRISON) //give imprisoned hero 0 exp to level him up. easiest to do at this point
{
changePrimSkill (getHero(obj->id), PrimarySkill::EXPERIENCE, 0);
}
}
}
if (newWeek && !firstTurn)
{
n.specialWeek = NewTurn::NORMAL;
bool deityOfFireBuilt = false;
for(const CGTownInstance *t : gs->map->towns)
{
if(t->hasBuilt(BuildingID::GRAIL, ETownType::INFERNO))
{
deityOfFireBuilt = true;
break;
}
}
if(deityOfFireBuilt)
{
n.specialWeek = NewTurn::DEITYOFFIRE;
n.creatureid = CreatureID::IMP;
}
else
{
int monthType = gs->getRandomGenerator().nextInt(99);
if(newMonth) //new month
{
if (monthType < 40) //double growth
{
n.specialWeek = NewTurn::DOUBLE_GROWTH;
if (VLC->modh->settings.ALL_CREATURES_GET_DOUBLE_MONTHS)
{
std::pair<int, CreatureID> newMonster(54, VLC->creh->pickRandomMonster(gs->getRandomGenerator()));
n.creatureid = newMonster.second;
}
else if(VLC->creh->doubledCreatures.size())
{
const std::vector<CreatureID> doubledCreatures (VLC->creh->doubledCreatures.begin(), VLC->creh->doubledCreatures.end());
n.creatureid = *RandomGeneratorUtil::nextItem(doubledCreatures, gs->getRandomGenerator());
}
else
{
complain("Cannot find creature that can be spawned!");
n.specialWeek = NewTurn::NORMAL;
}
}
else if (monthType < 50)
n.specialWeek = NewTurn::PLAGUE;
}
else //it's a week, but not full month
{
if (monthType < 25)
{
n.specialWeek = NewTurn::BONUS_GROWTH; //+5
std::pair<int, CreatureID> newMonster(54, VLC->creh->pickRandomMonster(gs->getRandomGenerator()));
//TODO do not pick neutrals
n.creatureid = newMonster.second;
}
}
}
}
std::map<ui32, ConstTransitivePtr<CGHeroInstance> > pool = gs->hpool.heroesPool;
for (auto & elem : gs->players)
{
if(elem.first == PlayerColor::NEUTRAL)
continue;
else if(elem.first >= PlayerColor::PLAYER_LIMIT)
assert(0); //illegal player number!
std::pair<PlayerColor, si32> playerGold(elem.first, elem.second.resources.at(Res::GOLD));
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, gs->getRandomGenerator(), banned))
{
sah.hid[j] = h->subID;
h->initArmy(&sah.army[j]);
banned = h->type->heroClass;
}
else
sah.hid[j] = -1;
}
sendAndApply(&sah);
}
n.res[elem.first] = elem.second.resources;
for(CGHeroInstance *h : (elem).second.heroes)
{
if(h->visitedTown)
giveSpells(h->visitedTown, h);
NewTurn::Hero hth;
hth.id = h->id;
// TODO: this code executed when bonuses of previous day not yet updated (this happen in NewTurn::applyGs). See issue 2356
hth.move = h->maxMovePoints(gs->map->getTile(h->getPosition(false)).terType != ETerrainType::WATER, new TurnInfo(h, 1));
if(h->visitedTown && h->visitedTown->hasBuilt(BuildingID::MAGES_GUILD_1)) //if hero starts turn in town with mage guild
hth.mana = std::max(h->mana, h->manaLimit()); //restore all mana
else
hth.mana = std::max((si32)(0), std::max(h->mana, std::min((si32)(h->mana + h->manaRegain()), h->manaLimit())));
n.heroes.insert(hth);
if(!firstTurn) //not first day
{
n.res[elem.first][Res::GOLD] += h->valOfBonuses(Selector::typeSubtype(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::ESTATES)); //estates
for (int k = 0; k < GameConstants::RESOURCE_QUANTITY; k++)
{
n.res[elem.first][k] += h->valOfBonuses(Bonus::GENERATE_RESOURCE, k);
}
}
}
}
for(CGTownInstance *t : gs->map->towns)
{
PlayerColor player = t->tempOwner;
handleTownEvents(t, n);
if(newWeek) //first day of week
{
if(t->hasBuilt(BuildingID::PORTAL_OF_SUMMON, ETownType::DUNGEON))
setPortalDwelling(t, true, (n.specialWeek == NewTurn::PLAGUE ? true : false)); //set creatures for Portal of Summoning
if(!firstTurn)
if (t->hasBuilt(BuildingID::TREASURY, ETownType::RAMPART) && player < PlayerColor::PLAYER_LIMIT)
n.res[player][Res::GOLD] += hadGold.at(player)/10; //give 10% of starting gold
if (!vstd::contains(n.cres, t->id))
{
n.cres[t->id].tid = t->id;
n.cres[t->id].creatures = t->creatures;
}
auto & sac = n.cres.at(t->id);
for (int k=0; k < GameConstants::CREATURES_PER_TOWN; k++) //creature growths
{
if (!t->creatures.at(k).second.empty()) // there are creatures at this level
{
ui32 &availableCount = sac.creatures.at(k).first;
const CCreature *cre = VLC->creh->creatures.at(t->creatures.at(k).second.back());
if (n.specialWeek == NewTurn::PLAGUE)
availableCount = t->creatures.at(k).first / 2; //halve their number, no growth
else
{
if(firstTurn) //first day of game: use only basic growths
availableCount = cre->growth;
else
availableCount += t->creatureGrowth(k);
//Deity of fire week - upgrade both imps and upgrades
if (n.specialWeek == NewTurn::DEITYOFFIRE && vstd::contains(t->creatures.at(k).second, n.creatureid))
availableCount += 15;
if( cre->idNumber == n.creatureid ) //bonus week, effect applies only to identical creatures
{
if(n.specialWeek == NewTurn::DOUBLE_GROWTH)
availableCount *= 2;
else if(n.specialWeek == NewTurn::BONUS_GROWTH)
availableCount += 5;
}
}
}
}
}
if(!firstTurn && player < PlayerColor::PLAYER_LIMIT)//not the first day and town not neutral
{
n.res[player] = n.res[player] + t->dailyIncome();
}
if(t->hasBuilt(BuildingID::GRAIL, ETownType::TOWER))
{
// Skyship, probably easier to handle same as Veil of darkness
//do it every new day after veils apply
if (player != PlayerColor::NEUTRAL) //do not reveal fow for neutral player
{
FoWChange fw;
fw.mode = 1;
fw.player = player;
// find all hidden tiles
const auto & fow = gs->getPlayerTeam(player)->fogOfWarMap;
for (size_t i=0; i<fow.size(); i++)
for (size_t j=0; j<fow.at(i).size(); j++)
for (size_t k=0; k<fow.at(i).at(j).size(); k++)
if (!fow.at(i).at(j).at(k))
fw.tiles.insert(int3(i,j,k));
sendAndApply (&fw);
}
}
if (t->hasBonusOfType (Bonus::DARKNESS))
{
for (auto & player : gameState()->players)
{
if (getPlayerStatus(player.first) == EPlayerStatus::INGAME &&
getPlayerRelations(player.first, t->tempOwner) == PlayerRelations::ENEMIES)
changeFogOfWar(t->visitablePos(), t->getBonusLocalFirst(Selector::type(Bonus::DARKNESS))->val, player.first, true);
}
}
}
if(newMonth)
{
SetAvailableArtifacts saa;
saa.id = -1;
pickAllowedArtsSet(saa.arts);
sendAndApply(&saa);
}
sendAndApply(&n);
if(newWeek)
{
//spawn wandering monsters
if (newMonth && (n.specialWeek == NewTurn::DOUBLE_GROWTH || n.specialWeek == NewTurn::DEITYOFFIRE))
{
spawnWanderingMonsters(n.creatureid);
}
//new week info popup
if(!firstTurn)
{
InfoWindow iw;
switch (n.specialWeek)
{
case NewTurn::DOUBLE_GROWTH:
iw.text.addTxt(MetaString::ARRAY_TXT, 131);
iw.text.addReplacement(MetaString::CRE_SING_NAMES, n.creatureid);
iw.text.addReplacement(MetaString::CRE_SING_NAMES, n.creatureid);
break;
case NewTurn::PLAGUE:
iw.text.addTxt(MetaString::ARRAY_TXT, 132);
break;
case NewTurn::BONUS_GROWTH:
iw.text.addTxt(MetaString::ARRAY_TXT, 134);
iw.text.addReplacement(MetaString::CRE_SING_NAMES, n.creatureid);
iw.text.addReplacement(MetaString::CRE_SING_NAMES, n.creatureid);
break;
case NewTurn::DEITYOFFIRE:
iw.text.addTxt(MetaString::ARRAY_TXT, 135);
iw.text.addReplacement(MetaString::CRE_SING_NAMES, 42); //%s imp
iw.text.addReplacement(MetaString::CRE_SING_NAMES, 42); //%s imp
iw.text.addReplacement2(15); //%+d 15
iw.text.addReplacement(MetaString::CRE_SING_NAMES, 43); //%s familiar
iw.text.addReplacement2(15); //%+d 15
break;
default:
if (newMonth)
{
iw.text.addTxt(MetaString::ARRAY_TXT, (130));
iw.text.addReplacement(MetaString::ARRAY_TXT, gs->getRandomGenerator().nextInt(32, 41));
}
else
{
iw.text.addTxt(MetaString::ARRAY_TXT, (133));
iw.text.addReplacement(MetaString::ARRAY_TXT, gs->getRandomGenerator().nextInt(43, 57));
}
}
for (auto & elem : gs->players)
{
iw.player = elem.first;
sendAndApply(&iw);
}
}
}
logGlobal->traceStream() << "Info about turn " << n.day << "has been sent!";
handleTimeEvents();
//call objects
for(auto & elem : gs->map->objects)
{
if(elem)
elem->newTurn();
}
//count days without town for all players, regardless of their turn order
for (auto &p : gs->players)
{
PlayerState & playerState = p.second;
if (playerState.status == EPlayerStatus::INGAME)
{
if (playerState.towns.empty())
{
if (playerState.daysWithoutCastle)
++(*playerState.daysWithoutCastle);
else playerState.daysWithoutCastle = 0;
}
else
{
playerState.daysWithoutCastle = boost::none;
}
}
}
synchronizeArtifactHandlerLists(); //new day events may have changed them. TODO better of managing that
}
void CGameHandler::run(bool resume)
{
LOG_TRACE_PARAMS(logGlobal, "resume=%d", resume);
using namespace boost::posix_time;
for(CConnection *cc : conns)
{
if(!resume)
{
(*cc) << gs->initialOpts; // gs->scenarioOps
}
std::set<PlayerColor> players;
(*cc) >> players; //how many players will be handled at that client
std::stringstream sbuffer;
sbuffer << "Connection " << cc->connectionID << " will handle " << players.size() << " player: ";
for(PlayerColor color : players)
{
sbuffer << color << " ";
{
boost::unique_lock<boost::recursive_mutex> lock(gsm);
connections[color] = cc;
}
}
logGlobal->infoStream() << sbuffer.str();
cc->addStdVecItems(gs);
cc->enableStackSendingByID();
cc->disableSmartPointerSerialization();
}
for(auto & elem : conns)
{
std::set<PlayerColor> pom;
for(auto j = connections.cbegin(); j!=connections.cend();j++)
if(j->second == elem)
pom.insert(j->first);
boost::thread(std::bind(&CGameHandler::handleConnection,this,pom,std::ref(*elem)));
}
if(gs->scenarioOps->mode == StartInfo::DUEL)
{
runBattle();
end2 = true;
while(conns.size() && (*conns.begin())->isOpen())
boost::this_thread::sleep(boost::posix_time::milliseconds(5)); //give time client to close socket
return;
}
auto playerTurnOrder = generatePlayerTurnOrder();
while(!end2)
{
if(!resume) newTurn();
std::list<PlayerColor>::iterator it;
if(resume)
{
it = std::find(playerTurnOrder.begin(), playerTurnOrder.end(), gs->currentPlayer);
}
else
{
it = playerTurnOrder.begin();
}
resume = false;
for (; it != playerTurnOrder.end(); it++)
{
auto playerColor = *it;
PlayerState * playerState = &gs->players[playerColor]; //can't copy CBonusSystemNode by value
if (playerState->status == EPlayerStatus::INGAME)
{
//if player runs out of time, he shouldn't get the turn (especially AI)
checkVictoryLossConditionsForAll();
if (gs->players[playerColor].status != EPlayerStatus::INGAME)
{ //player lost at the beginning of his turn
continue;
}
else //give normal turn
{
states.setFlag(playerColor, &PlayerStatus::makingTurn, true);
YourTurn yt;
yt.player = playerColor;
//Change local daysWithoutCastle counter for local interface message //TODO: needed?
yt.daysWithoutCastle = playerState->daysWithoutCastle;
applyAndSend(&yt);
//wait till turn is done
boost::unique_lock<boost::mutex> lock(states.mx);
while (states.players.at(playerColor).makingTurn && !end2)
{
static time_duration p = milliseconds(100);
states.cv.timed_wait(lock, p);
}
}
}
}
//additional check that game is not finished
bool activePlayer = false;
for(auto player : playerTurnOrder)
{
if(gs->players[player].status == EPlayerStatus::INGAME)
activePlayer = true;
}
if(!activePlayer)
end2 = true;
}
while(conns.size() && (*conns.begin())->isOpen())
boost::this_thread::sleep(boost::posix_time::milliseconds(5)); //give time client to close socket
}
std::list<PlayerColor> CGameHandler::generatePlayerTurnOrder() const
{
// Generate player turn order
std::list<PlayerColor> playerTurnOrder;
for(const auto & player : gs->players) // add human players first
{
if(player.second.human)
playerTurnOrder.push_back(player.first);
}
for(const auto & player : gs->players) // then add non-human players
{
if(!player.second.human)
playerTurnOrder.push_back(player.first);
}
return playerTurnOrder;
}
void CGameHandler::setupBattle( int3 tile, const CArmedInstance *armies[2], const CGHeroInstance *heroes[2], bool creatureBank, const CGTownInstance *town )
{
battleResult.set(nullptr);
//send info about battles
BattleStart bs;
bs.info = gs->setupBattle(tile, armies, heroes, creatureBank, town);
sendAndApply(&bs);
}
void CGameHandler::checkForBattleEnd()
{
if(auto result = battleIsFinished())
{
setBattleResult(BattleResult::NORMAL, *result);
}
}
void CGameHandler::giveSpells( const CGTownInstance *t, const CGHeroInstance *h )
{
if(!h->hasSpellbook())
return; //hero hasn't spellbook
ChangeSpells cs;
cs.hid = h->id;
cs.learn = true;
for(int i=0; i<std::min(t->mageGuildLevel(),h->getSecSkillLevel(SecondarySkill::WISDOM)+2);i++)
{
if (t->hasBuilt(BuildingID::GRAIL, ETownType::CONFLUX)) //Aurora Borealis
{
std::vector<SpellID> spells;
getAllowedSpells(spells, i);
for (auto & spell : spells)
cs.spells.insert(spell);
}
else
{
for(int j=0; j<t->spellsAtLevel(i+1,true) && j<t->spells.at(i).size(); j++)
{
if(!vstd::contains(h->spells,t->spells.at(i).at(j)))
cs.spells.insert(t->spells.at(i).at(j));
}
}
}
if(!cs.spells.empty())
sendAndApply(&cs);
}
void CGameHandler::setBlockVis(ObjectInstanceID objid, bool bv)
{
SetObjectProperty sop(objid,2,bv);
sendAndApply(&sop);
}
bool CGameHandler::removeObject( const CGObjectInstance * obj )
{
if(!obj || !getObj(obj->id))
{
logGlobal->errorStream() << "Something wrong, that object already has been removed or hasn't existed!";
return false;
}
RemoveObject ro;
ro.id = obj->id;
sendAndApply(&ro);
checkVictoryLossConditionsForAll(); //eg if monster escaped (removing objs after battle is done dircetly by endBattle, not this function)
return true;
}
void CGameHandler::setAmount(ObjectInstanceID objid, ui32 val)
{
SetObjectProperty sop(objid,3,val);
sendAndApply(&sop);
}
bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bool transit, PlayerColor asker /*= 255*/ )
{
const CGHeroInstance *h = getHero(hid);
if(!h || (asker != PlayerColor::NEUTRAL && (teleporting || h->getOwner() != gs->currentPlayer)) //not turn of that hero or player can't simply teleport hero (at least not with this function)
)
{
logGlobal->errorStream() << "Illegal call to move hero!";
return false;
}
logGlobal->traceStream() << "Player " << asker << " wants to move hero "<< hid.getNum() << " from "<< h->pos << " to " << dst;
const int3 hmpos = CGHeroInstance::convertPosition(dst, false);
if(!gs->map->isInTheMap(hmpos))
{
logGlobal->errorStream() << "Destination tile is outside the map!";
return false;
}
const TerrainTile t = *gs->getTile(hmpos);
const int3 guardPos = gs->guardingCreaturePosition(hmpos);
const bool embarking = !h->boat && !t.visitableObjects.empty() && t.visitableObjects.back()->ID == Obj::BOAT;
const bool disembarking = h->boat && t.terType != ETerrainType::WATER && !t.blocked;
//result structure for start - movement failed, no move points used
TryMoveHero tmh;
tmh.id = hid;
tmh.start = h->pos;
tmh.end = dst;
tmh.result = TryMoveHero::FAILED;
tmh.movePoints = h->movement;
//check if destination tile is available
auto ti = new TurnInfo(h);
const bool canFly = ti->hasBonusOfType(Bonus::FLYING_MOVEMENT);
const bool canWalkOnSea = ti->hasBonusOfType(Bonus::WATER_WALKING);
const int cost = CPathfinderHelper::getMovementCost(h, h->getPosition(), hmpos, nullptr, nullptr, h->movement, ti);
//it's a rock or blocked and not visitable tile
//OR hero is on land and dest is water and (there is not present only one object - boat)
if(((t.terType == ETerrainType::ROCK || (t.blocked && !t.visitable && !canFly))
&& complain("Cannot move hero, destination tile is blocked!"))
|| ((!h->boat && !canWalkOnSea && !canFly && t.terType == ETerrainType::WATER && (t.visitableObjects.size() < 1 || (t.visitableObjects.back()->ID != Obj::BOAT && t.visitableObjects.back()->ID != Obj::HERO))) //hero is not on boat/water walking and dst water tile doesn't contain boat/hero (objs visitable from land) -> we test back cause boat may be on top of another object (#276)
&& complain("Cannot move hero, destination tile is on water!"))
|| ((h->boat && t.terType != ETerrainType::WATER && t.blocked)
&& complain("Cannot disembark hero, tile is blocked!"))
|| ( (distance(h->pos, dst) >= 1.5 && !teleporting)
&& complain("Tiles are not neighboring!"))
|| ( (h->inTownGarrison)
&& complain("Can not move garrisoned hero!"))
|| ((h->movement < cost && dst != h->pos && !teleporting)
&& complain("Hero doesn't have any movement points left!"))
|| ((transit && !canFly && !CGTeleport::isTeleport(t.topVisitableObj()))
&& complain("Hero cannot transit over this tile!"))
/*|| (states.checkFlag(h->tempOwner, &PlayerStatus::engagedIntoBattle)
&& complain("Cannot move hero during the battle"))*/)
{
//send info about movement failure
sendAndApply(&tmh);
return false;
}
//several generic blocks of code
// should be called if hero changes tile but before applying TryMoveHero package
auto leaveTile = [&]()
{
for(CGObjectInstance *obj : gs->map->getTile(int3(h->pos.x-1, h->pos.y, h->pos.z)).visitableObjects)
{
obj->onHeroLeave(h);
}
this->getTilesInRange(tmh.fowRevealed, h->getSightCenter()+(tmh.end-tmh.start), h->getSightRadious(), h->tempOwner, 1);
};
auto doMove = [&](TryMoveHero::EResult result, EGuardLook lookForGuards,
EVisitDest visitDest, ELEaveTile leavingTile) -> bool
{
LOG_TRACE_PARAMS(logGlobal, "Hero %s starts movement from %s to %s", h->name % tmh.start % tmh.end);
auto moveQuery = std::make_shared<CHeroMovementQuery>(tmh, h);
queries.addQuery(moveQuery);
if(leavingTile == LEAVING_TILE)
leaveTile();
tmh.result = result;
sendAndApply(&tmh);
if (visitDest == VISIT_DEST && t.topVisitableObj() && t.topVisitableObj()->id == h->id)
{ // Hero should be always able to visit any object he staying on even if there guards around
visitObjectOnTile(t, h);
}
else if(lookForGuards == CHECK_FOR_GUARDS && this->isInTheMap(guardPos))
{
tmh.attackedFrom = guardPos;
const TerrainTile &guardTile = *gs->getTile(guardPos);
objectVisited(guardTile.visitableObjects.back(), h);
moveQuery->visitDestAfterVictory = visitDest==VISIT_DEST;
}
else if(visitDest == VISIT_DEST)
{
visitObjectOnTile(t, h);
}
queries.popIfTop(moveQuery);
logGlobal->traceStream() << "Hero " << h->name << " ends movement";
return result != TryMoveHero::FAILED;
};
//interaction with blocking object (like resources)
auto blockingVisit = [&]() -> bool
{
for(CGObjectInstance *obj : t.visitableObjects)
{
if(obj != h && obj->blockVisit && !obj->passableFor(h->tempOwner))
{
return doMove(TryMoveHero::BLOCKING_VISIT, this->IGNORE_GUARDS, VISIT_DEST, REMAINING_ON_TILE);
//this-> is needed for MVS2010 to recognize scope (?)
}
}
return false;
};
if(!transit && embarking)
{
tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, false, ti);
return doMove(TryMoveHero::EMBARK, IGNORE_GUARDS, DONT_VISIT_DEST, LEAVING_TILE);
// In H3 embark ignore guards
}
if(disembarking)
{
tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, true, ti);
return doMove(TryMoveHero::DISEMBARK, CHECK_FOR_GUARDS, VISIT_DEST, LEAVING_TILE);
}
if(teleporting)
{
if(blockingVisit()) // e.g. hero on the other side of teleporter
return true;
doMove(TryMoveHero::TELEPORTATION, IGNORE_GUARDS, DONT_VISIT_DEST, LEAVING_TILE);
// visit town for town portal \ castle gates
// do not use generic visitObjectOnTile to avoid double-teleporting
// if this moveHero call was triggered by teleporter
if (!t.visitableObjects.empty())
{
if (CGTownInstance * town = dynamic_cast<CGTownInstance *>(t.visitableObjects.back()))
town->onHeroVisit(h);
}
return true;
}
//still here? it is standard movement!
{
tmh.movePoints = h->movement >= cost
? h->movement - cost
: 0;
EGuardLook lookForGuards = CHECK_FOR_GUARDS;
EVisitDest visitDest = VISIT_DEST;
if(transit)
{
if(CGTeleport::isTeleport(t.topVisitableObj()))
visitDest = DONT_VISIT_DEST;
if(canFly)
{
lookForGuards = IGNORE_GUARDS;
visitDest = DONT_VISIT_DEST;
}
}
else if(blockingVisit())
return true;
doMove(TryMoveHero::SUCCESS, lookForGuards, visitDest, LEAVING_TILE);
return true;
}
}
bool CGameHandler::teleportHero(ObjectInstanceID hid, ObjectInstanceID dstid, ui8 source, PlayerColor asker/* = 255*/)
{
const CGHeroInstance *h = getHero(hid);
const CGTownInstance *t = getTown(dstid);
if ( !h || !t || h->getOwner() != gs->currentPlayer )
logGlobal->errorStream()<<"Invalid call to teleportHero!";
const CGTownInstance *from = h->visitedTown;
if(((h->getOwner() != t->getOwner())
&& complain("Cannot teleport hero to another player"))
|| ((!from || !from->hasBuilt(BuildingID::CASTLE_GATE, ETownType::INFERNO))
&& complain("Hero must be in town with Castle gate for teleporting"))
|| (!t->hasBuilt(BuildingID::CASTLE_GATE, ETownType::INFERNO)
&& complain("Cannot teleport hero to town without Castle gate in it")))
return false;
int3 pos = t->visitablePos();
pos += h->getVisitableOffset();
moveHero(hid,pos,1);
return true;
}
void CGameHandler::setOwner(const CGObjectInstance * obj, PlayerColor owner)
{
PlayerColor oldOwner = getOwner(obj->id);
SetObjectProperty sop(obj->id, 1, owner.getNum());
sendAndApply(&sop);
std::set<PlayerColor> playerColors = {owner, oldOwner};
checkVictoryLossConditions(playerColors);
if(dynamic_cast<const CGTownInstance *>(obj)) //town captured
{
if (owner < PlayerColor::PLAYER_LIMIT) //new owner is real player
{
const CGTownInstance * town = dynamic_cast<const CGTownInstance *>(obj);
if (town->hasBuilt(BuildingID::PORTAL_OF_SUMMON, ETownType::DUNGEON))
setPortalDwelling(town, true, false);
gs->getPlayer(owner)->daysWithoutCastle = boost::none; // reset counter
}
if (oldOwner < PlayerColor::PLAYER_LIMIT) //old owner is real player
{
if (gs->getPlayer(oldOwner)->towns.empty())//previous player lost last last town
{
InfoWindow iw;
iw.player = oldOwner;
iw.text.addTxt(MetaString::GENERAL_TXT, 6); //%s, you have lost your last town. If you do not conquer another town in the next week, you will be eliminated.
iw.text.addReplacement(MetaString::COLOR, oldOwner.getNum());
sendAndApply(&iw);
}
}
}
const PlayerState * p = gs->getPlayer(owner);
if((obj->ID == Obj::CREATURE_GENERATOR1 || obj->ID == Obj::CREATURE_GENERATOR4 ) && p && p->dwellings.size()==1)//first dwelling captured
{
for(const CGTownInstance *t : gs->getPlayer(owner)->towns)
{
if (t->hasBuilt(BuildingID::PORTAL_OF_SUMMON, ETownType::DUNGEON))
setPortalDwelling(t);//set initial creatures for all portals of summoning
}
}
}
void CGameHandler::showBlockingDialog( BlockingDialog *iw )
{
auto dialogQuery = std::make_shared<CBlockingDialogQuery>(*iw);
queries.addQuery(dialogQuery);
iw->queryID = dialogQuery->queryID;
sendToAllClients(iw);
}
void CGameHandler::showTeleportDialog( TeleportDialog *iw )
{
auto dialogQuery = std::make_shared<CTeleportDialogQuery>(*iw);
queries.addQuery(dialogQuery);
iw->queryID = dialogQuery->queryID;
sendToAllClients(iw);
}
void CGameHandler::giveResource(PlayerColor player, Res::ERes which, int val) //TODO: cap according to Bersy's suggestion
{
if(!val) return; //don't waste time on empty call
SetResource sr;
sr.player = player;
sr.resid = which;
sr.val = gs->players.find(player)->second.resources.at(which) + val;
sendAndApply(&sr);
}
void CGameHandler::giveResources(PlayerColor player, TResources resources)
{
for(TResources::nziterator i(resources); i.valid(); i++)
giveResource(player, i->resType, i->resVal);
}
void CGameHandler::giveCreatures(const CArmedInstance *obj, const CGHeroInstance * h, const CCreatureSet &creatures, bool remove)
{
COMPLAIN_RET_IF(!creatures.stacksCount(), "Strange, giveCreatures called without args!");
COMPLAIN_RET_IF(obj->stacksCount(), "Cannot give creatures from not-cleared object!");
COMPLAIN_RET_IF(creatures.stacksCount() > GameConstants::ARMY_SIZE, "Too many stacks to give!");
//first we move creatures to give to make them army of object-source
for (auto & elem : creatures.Slots())
{
addToSlot(StackLocation(obj, obj->getSlotFor(elem.second->type)), elem.second->type, elem.second->count);
}
tryJoiningArmy(obj, h, remove, true);
}
void CGameHandler::takeCreatures(ObjectInstanceID objid, const std::vector<CStackBasicDescriptor> &creatures)
{
std::vector<CStackBasicDescriptor> cres = creatures;
if (cres.size() <= 0)
return;
const CArmedInstance* obj = static_cast<const CArmedInstance*>(getObj(objid));
for(CStackBasicDescriptor &sbd : cres)
{
TQuantity collected = 0;
while(collected < sbd.count)
{
bool foundSth = false;
for(auto i = obj->Slots().begin(); i != obj->Slots().end(); i++)
{
if(i->second->type == sbd.type)
{
TQuantity take = std::min(sbd.count - collected, i->second->count); //collect as much cres as we can
changeStackCount(StackLocation(obj, i->first), -take, false);
collected += take;
foundSth = true;
break;
}
}
if(!foundSth) //we went through the whole loop and haven't found appropriate cres
{
complain("Unexpected failure during taking creatures!");
return;
}
}
}
}
void CGameHandler::showCompInfo(ShowInInfobox * comp)
{
sendToAllClients(comp);
}
void CGameHandler::heroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero)
{
HeroVisitCastle vc;
vc.hid = hero->id;
vc.tid = obj->id;
vc.flags |= 1;
sendAndApply(&vc);
vistiCastleObjects (obj, hero);
giveSpells (obj, hero);
checkVictoryLossConditionsForPlayer(hero->tempOwner); //transported artifact?
}
void CGameHandler::vistiCastleObjects (const CGTownInstance *t, const CGHeroInstance *h)
{
std::vector<CGTownBuilding*>::const_iterator i;
for (i = t->bonusingBuildings.begin(); i != t->bonusingBuildings.end(); i++)
(*i)->onHeroVisit (h);
}
void CGameHandler::stopHeroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero)
{
HeroVisitCastle vc;
vc.hid = hero->id;
vc.tid = obj->id;
sendAndApply(&vc);
}
void CGameHandler::removeArtifact(const ArtifactLocation &al)
{
assert(al.getArt());
EraseArtifact ea;
ea.al = al;
sendAndApply(&ea);
}
void CGameHandler::startBattlePrimary(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile,
const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank,
const CGTownInstance *town) //use hero=nullptr for no hero
{
engageIntoBattle(army1->tempOwner);
engageIntoBattle(army2->tempOwner);
static const CArmedInstance *armies[2];
armies[0] = army1;
armies[1] = army2;
static const CGHeroInstance*heroes[2];
heroes[0] = hero1;
heroes[1] = hero2;
setupBattle(tile, armies, heroes, creatureBank, town); //initializes stacks, places creatures on battlefield, blocks and informs player interfaces
auto battleQuery = std::make_shared<CBattleQuery>(gs->curB);
queries.addQuery(battleQuery);
boost::thread(&CGameHandler::runBattle, this);
}
void CGameHandler::startBattleI( const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank )
{
startBattlePrimary(army1, army2, tile,
army1->ID == Obj::HERO ? static_cast<const CGHeroInstance*>(army1) : nullptr,
army2->ID == Obj::HERO ? static_cast<const CGHeroInstance*>(army2) : nullptr,
creatureBank);
}
void CGameHandler::startBattleI( const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank)
{
startBattleI(army1, army2, army2->visitablePos(), creatureBank);
}
void CGameHandler::changeSpells( const CGHeroInstance * hero, bool give, const std::set<SpellID> &spells )
{
ChangeSpells cs;
cs.hid = hero->id;
cs.spells = spells;
cs.learn = give;
sendAndApply(&cs);
}
void CGameHandler::sendMessageTo( CConnection &c, const std::string &message )
{
SystemMessage sm;
sm.text = message;
boost::unique_lock<boost::mutex> lock(*c.wmx);
c << &sm;
}
void CGameHandler::giveHeroBonus( GiveBonus * bonus )
{
sendAndApply(bonus);
}
void CGameHandler::setMovePoints( SetMovePoints * smp )
{
sendAndApply(smp);
}
void CGameHandler::setManaPoints( ObjectInstanceID hid, int val )
{
SetMana sm;
sm.hid = hid;
sm.val = val;
sm.absolute = true;
sendAndApply(&sm);
}
void CGameHandler::giveHero( ObjectInstanceID id, PlayerColor player )
{
GiveHero gh;
gh.id = id;
gh.player = player;
sendAndApply(&gh);
}
void CGameHandler::changeObjPos( ObjectInstanceID objid, int3 newPos, ui8 flags )
{
ChangeObjPos cop;
cop.objid = objid;
cop.nPos = newPos;
cop.flags = flags;
sendAndApply(&cop);
}
void CGameHandler::useScholarSkill(ObjectInstanceID fromHero, ObjectInstanceID toHero)
{
const CGHeroInstance * h1 = getHero(fromHero);
const CGHeroInstance * h2 = getHero(toHero);
if ( h1->getSecSkillLevel(SecondarySkill::SCHOLAR) < h2->getSecSkillLevel(SecondarySkill::SCHOLAR) )
{
std::swap (h1,h2);//1st hero need to have higher scholar level for correct message
std::swap(fromHero, toHero);
}
int ScholarLevel = h1->getSecSkillLevel(SecondarySkill::SCHOLAR);//heroes can trade up to this level
if (!ScholarLevel || !h1->hasSpellbook() || !h2->hasSpellbook() )
return;//no scholar skill or no spellbook
int h1Lvl = std::min(ScholarLevel+1, h1->getSecSkillLevel(SecondarySkill::WISDOM)+2),
h2Lvl = std::min(ScholarLevel+1, h2->getSecSkillLevel(SecondarySkill::WISDOM)+2);//heroes can receive this levels
ChangeSpells cs1;
cs1.learn = true;
cs1.hid = toHero;//giving spells to first hero
for(auto it : h1->spells)
if ( h2Lvl >= it.toSpell()->level && !vstd::contains(h2->spells, it))//hero can learn it and don't have it yet
cs1.spells.insert(it);//spell to learn
ChangeSpells cs2;
cs2.learn = true;
cs2.hid = fromHero;
for(auto it : h2->spells)
if ( h1Lvl >= it.toSpell()->level && !vstd::contains(h1->spells, it))
cs2.spells.insert(it);
if (!cs1.spells.empty() || !cs2.spells.empty())//create a message
{
InfoWindow iw;
iw.player = h1->tempOwner;
iw.components.push_back(Component(Component::SEC_SKILL, 18, ScholarLevel, 0));
iw.text.addTxt(MetaString::GENERAL_TXT, 139);//"%s, who has studied magic extensively,
iw.text.addReplacement(h1->name);
if (!cs2.spells.empty())//if found new spell - apply
{
iw.text.addTxt(MetaString::GENERAL_TXT, 140);//learns
int size = cs2.spells.size();
for(auto it : cs2.spells)
{
iw.components.push_back(Component(Component::SPELL, it, 1, 0));
iw.text.addTxt(MetaString::SPELL_NAME, it.toEnum());
switch (size--)
{
case 2: iw.text.addTxt(MetaString::GENERAL_TXT, 141);
case 1: break;
default: iw.text << ", ";
}
}
iw.text.addTxt(MetaString::GENERAL_TXT, 142);//from %s
iw.text.addReplacement(h2->name);
sendAndApply(&cs2);
}
if (!cs1.spells.empty() && !cs2.spells.empty() )
{
iw.text.addTxt(MetaString::GENERAL_TXT, 141);//and
}
if (!cs1.spells.empty())
{
iw.text.addTxt(MetaString::GENERAL_TXT, 147);//teaches
int size = cs1.spells.size();
for(auto it : cs1.spells)
{
iw.components.push_back(Component(Component::SPELL, it, 1, 0));
iw.text.addTxt(MetaString::SPELL_NAME, it.toEnum());
switch (size--)
{
case 2: iw.text.addTxt(MetaString::GENERAL_TXT, 141);
case 1: break;
default: iw.text << ", ";
} }
iw.text.addTxt(MetaString::GENERAL_TXT, 148);//from %s
iw.text.addReplacement(h2->name);
sendAndApply(&cs1);
}
sendAndApply(&iw);
}
}
void CGameHandler::heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2)
{
auto h1 = getHero(hero1), h2 = getHero(hero2);
if( gameState()->getPlayerRelations(h1->getOwner(), h2->getOwner()))
{
auto exchange = std::make_shared<CGarrisonDialogQuery>(h1, h2);
ExchangeDialog hex;
hex.queryID = exchange->queryID;
hex.heroes[0] = getHero(hero1);
hex.heroes[1] = getHero(hero2);
sendAndApply(&hex);
useScholarSkill(hero1,hero2);
queries.addQuery(exchange);
}
}
void CGameHandler::sendToAllClients( CPackForClient * info )
{
logGlobal->traceStream() << "Sending to all clients a package of type " << typeid(*info).name();
for(auto & elem : conns)
{
boost::unique_lock<boost::mutex> lock(*(elem)->wmx);
*elem << info;
}
}
void CGameHandler::sendAndApply(CPackForClient * info)
{
sendToAllClients(info);
gs->apply(info);
}
void CGameHandler::applyAndSend(CPackForClient * info)
{
gs->apply(info);
sendToAllClients(info);
}
void CGameHandler::sendAndApply(CGarrisonOperationPack * info)
{
sendAndApply(static_cast<CPackForClient*>(info));
checkVictoryLossConditionsForAll();
}
void CGameHandler::sendAndApply( SetResource * info )
{
sendAndApply(static_cast<CPackForClient*>(info));
checkVictoryLossConditionsForPlayer(info->player);
}
void CGameHandler::sendAndApply( SetResources * info )
{
sendAndApply(static_cast<CPackForClient*>(info));
checkVictoryLossConditionsForPlayer(info->player);
}
void CGameHandler::sendAndApply( NewStructures * info )
{
sendAndApply(static_cast<CPackForClient*>(info));
checkVictoryLossConditionsForPlayer(getTown(info->tid)->tempOwner);
}
void CGameHandler::save(const std::string & filename )
{
logGlobal->infoStream() << "Saving to " << filename;
CFileInfo info(filename);
//CResourceHandler::get("local")->createResource(info.getStem() + ".vlgm1");
CResourceHandler::get("local")->createResource(info.getStem() + ".vsgm1");
{
logGlobal->infoStream() << "Ordering clients to serialize...";
SaveGame sg(info.getStem() + ".vcgm1");
sendToAllClients(&sg);
}
try
{
// {
// logGlobal->infoStream() << "Serializing game info...";
// CSaveFile save(CResourceHandler::get("local")->getResourceName(ResourceID(info.getStem(), EResType::LIB_SAVEGAME)));
// // char hlp[8] = "VCMISVG";
// // save << hlp;
// saveCommonState(save);
// }
{
CSaveFile save(*CResourceHandler::get("local")->getResourceName(ResourceID(info.getStem(), EResType::SERVER_SAVEGAME)));
saveCommonState(save);
logGlobal->infoStream() << "Saving server state";
save << *this;
}
logGlobal->infoStream() << "Game has been successfully saved!";
}
catch(std::exception &e)
{
logGlobal->errorStream() << "Failed to save game: " << e.what();
}
}
void CGameHandler::close()
{
logGlobal->infoStream() << "We have been requested to close.";
if(gs->initialOpts->mode == StartInfo::DUEL)
{
exit(0);
}
//for(CConnection *cc : conns)
// if(cc && cc->socket && cc->socket->is_open())
// cc->socket->close();
//exit(0);
}
bool CGameHandler::arrangeStacks( ObjectInstanceID id1, ObjectInstanceID id2, ui8 what, SlotID p1, SlotID p2, si32 val, PlayerColor player )
{
const CArmedInstance *s1 = static_cast<CArmedInstance*>(gs->getObjInstance(id1)),
*s2 = static_cast<CArmedInstance*>(gs->getObjInstance(id2));
const CCreatureSet &S1 = *s1, &S2 = *s2;
StackLocation sl1(s1, p1), sl2(s2, p2);
if(!sl1.slot.validSlot() || !sl2.slot.validSlot())
{
complain("Invalid slot accessed!");
return false;
}
if(!isAllowedExchange(id1,id2))
{
complain("Cannot exchange stacks between these two objects!\n");
return false;
}
if(what==1) //swap
{
if ( ((s1->tempOwner != player && s1->tempOwner != PlayerColor::UNFLAGGABLE) && s1->getStackCount(p1))
|| ((s2->tempOwner != player && s2->tempOwner != PlayerColor::UNFLAGGABLE) && s2->getStackCount(p2)))
{
complain("Can't take troops from another player!");
return false;
}
if (sl1.army == sl2.army && sl1.slot == sl2.slot)
{
complain("Cannot swap stacks - slots are the same!");
return false;
}
swapStacks(sl1, sl2);
}
else if(what==2)//merge
{
if (( s1->getCreature(p1) != s2->getCreature(p2) && complain("Cannot merge different creatures stacks!"))
|| (((s1->tempOwner != player && s1->tempOwner != PlayerColor::UNFLAGGABLE) && s2->getStackCount(p2)) && complain("Can't take troops from another player!")))
return false;
moveStack(sl1, sl2);
}
else if(what==3) //split
{
const int countToMove = val - s2->getStackCount(p2);
const int countLeftOnSrc = s1->getStackCount(p1) - countToMove;
if ( (s1->tempOwner != player && countLeftOnSrc < s1->getStackCount(p1) )
|| (s2->tempOwner != player && val < s2->getStackCount(p2) ) )
{
complain("Can't move troops of another player!");
return false;
}
//general conditions checking
if((!vstd::contains(S1.stacks,p1) && complain("no creatures to split"))
|| (val<1 && complain("no creatures to split")) )
{
return false;
}
if(vstd::contains(S2.stacks,p2)) //dest. slot not free - it must be "rebalancing"...
{
int total = s1->getStackCount(p1) + s2->getStackCount(p2);
if( (total < val && complain("Cannot split that stack, not enough creatures!"))
|| (s1->getCreature(p1) != s2->getCreature(p2) && complain("Cannot rebalance different creatures stacks!"))
)
{
return false;
}
moveStack(sl1, sl2, countToMove);
//S2.slots[p2]->count = val;
//S1.slots[p1]->count = total - val;
}
else //split one stack to the two
{
if(s1->getStackCount(p1) < val)//not enough creatures
{
complain("Cannot split that stack, not enough creatures!");
return false;
}
moveStack(sl1, sl2, val);
}
}
return true;
}
PlayerColor CGameHandler::getPlayerAt( CConnection *c ) const
{
std::set<PlayerColor> all;
for(auto i=connections.cbegin(); i!=connections.cend(); i++)
if(i->second == c)
all.insert(i->first);
switch(all.size())
{
case 0:
return PlayerColor::NEUTRAL;
case 1:
return *all.begin();
default:
{
//if we have more than one player at this connection, try to pick active one
if(vstd::contains(all, gs->currentPlayer))
return gs->currentPlayer;
else
return PlayerColor::CANNOT_DETERMINE; //cannot say which player is it
}
}
}
bool CGameHandler::disbandCreature( ObjectInstanceID id, SlotID pos )
{
CArmedInstance *s1 = static_cast<CArmedInstance*>(gs->getObjInstance(id));
if(!vstd::contains(s1->stacks,pos))
{
complain("Illegal call to disbandCreature - no such stack in army!");
return false;
}
eraseStack(StackLocation(s1, pos));
return true;
}
bool CGameHandler::buildStructure( ObjectInstanceID tid, BuildingID requestedID, bool force /*=false*/ )
{
const CGTownInstance * t = getTown(tid);
if(!t)
COMPLAIN_RETF("No such town (ID=%s)!", tid);
if(!t->town->buildings.count(requestedID))
COMPLAIN_RETF("Town of faction %s does not have info about building ID=%s!", t->town->faction->name % tid);
if (t->hasBuilt(requestedID))
COMPLAIN_RETF("Building %s is already built in %s", t->town->buildings.at(requestedID)->Name() % t->name);
const CBuilding * requestedBuilding = t->town->buildings.at(requestedID);
//Vector with future list of built building and buildings in auto-mode that are not yet built.
std::vector<const CBuilding*> remainingAutoBuildings;
std::set<BuildingID> buildingsThatWillBe;
//Check validity of request
if(!force)
{
switch (requestedBuilding->mode)
{
case CBuilding::BUILD_NORMAL :
if (gs->canBuildStructure(t, requestedID) != EBuildingState::ALLOWED)
COMPLAIN_RET("Cannot build that building!");
break;
case CBuilding::BUILD_AUTO :
case CBuilding::BUILD_SPECIAL:
COMPLAIN_RET("This building can not be constructed normally!");
case CBuilding::BUILD_GRAIL :
if(requestedBuilding->mode == CBuilding::BUILD_GRAIL) //needs grail
{
if(!t->visitingHero || !t->visitingHero->hasArt(ArtifactID::GRAIL))
COMPLAIN_RET("Cannot build this without grail!")
else
removeArtifact(ArtifactLocation(t->visitingHero, t->visitingHero->getArtPos(ArtifactID::GRAIL, false)));
}
break;
}
}
//Performs stuff that has to be done after new building is built
auto processBuiltStructure = [t, this](const BuildingID buildingID)
{
if(buildingID >= BuildingID::DWELL_FIRST) //dwelling
{
int level = (buildingID - BuildingID::DWELL_FIRST) % GameConstants::CREATURES_PER_TOWN;
int upgradeNumber = (buildingID - BuildingID::DWELL_FIRST) / GameConstants::CREATURES_PER_TOWN;
if (upgradeNumber >= t->town->creatures.at(level).size())
{
complain(boost::str(boost::format("Error ecountered when building dwelling (bid=%s):"
"no creature found (upgrade number %d, level %d!")
% buildingID % upgradeNumber % level));
return;
}
CCreature * crea = VLC->creh->creatures.at(t->town->creatures.at(level).at(upgradeNumber));
SetAvailableCreatures ssi;
ssi.tid = t->id;
ssi.creatures = t->creatures;
if (ssi.creatures[level].second.empty()) // first creature in a dwelling
ssi.creatures[level].first = crea->growth;
ssi.creatures[level].second.push_back(crea->idNumber);
sendAndApply(&ssi);
}
if ( t->subID == ETownType::DUNGEON && buildingID == BuildingID::PORTAL_OF_SUMMON )
{
setPortalDwelling(t);
}
if(buildingID <= BuildingID::MAGES_GUILD_5) //it's mage guild
{
if(t->visitingHero)
giveSpells(t,t->visitingHero);
if(t->garrisonHero)
giveSpells(t,t->garrisonHero);
}
};
//Checks if all requirements will be met with expected building list "buildingsThatWillBe"
auto areRequirementsFullfilled = [&](const BuildingID & buildID)
{
return buildingsThatWillBe.count(buildID);
};
//Init the vectors
for(auto & build : t->town->buildings)
{
if(t->hasBuilt(build.first))
buildingsThatWillBe.insert(build.first);
else
{
if(build.second->mode == CBuilding::BUILD_AUTO) //not built auto building
remainingAutoBuildings.push_back(build.second);
}
}
//Prepare structure (list of building ids will be filled later)
NewStructures ns;
ns.tid = tid;
ns.builded = force ? t->builded : (t->builded+1);
std::queue<const CBuilding*> buildingsToAdd;
buildingsToAdd.push(requestedBuilding);
while(!buildingsToAdd.empty())
{
auto b = buildingsToAdd.front();
buildingsToAdd.pop();
ns.bid.insert(b->bid);
buildingsThatWillBe.insert(b->bid);
remainingAutoBuildings -= b;
for(auto autoBuilding : remainingAutoBuildings)
{
if (autoBuilding->requirements.test(areRequirementsFullfilled))
buildingsToAdd.push(autoBuilding);
}
}
//Other post-built events
for(auto builtID : ns.bid)
processBuiltStructure(builtID);
//Take cost
if (!force)
{
SetResources sr;
sr.player = t->tempOwner;
sr.res = gs->getPlayer(t->tempOwner)->resources - requestedBuilding->resources;
sendAndApply(&sr);
}
//We know what has been built, appluy changes. Do this as final step to properly update town window
sendAndApply(&ns);
// now when everything is built - reveal tiles for lookout tower
FoWChange fw;
fw.player = t->tempOwner;
fw.mode = 1;
getTilesInRange(fw.tiles, t->getSightCenter(), t->getSightRadious(), t->tempOwner, 1);
sendAndApply(&fw);
if(t->visitingHero)
vistiCastleObjects (t, t->visitingHero);
if(t->garrisonHero)
vistiCastleObjects (t, t->garrisonHero);
checkVictoryLossConditionsForPlayer(t->tempOwner);
return true;
}
bool CGameHandler::razeStructure (ObjectInstanceID tid, BuildingID bid)
{
///incomplete, simply erases target building
const CGTownInstance * t = getTown(tid);
if (!vstd::contains(t->builtBuildings, bid))
return false;
RazeStructures rs;
rs.tid = tid;
rs.bid.insert(bid);
rs.destroyed = t->destroyed + 1;
sendAndApply(&rs);
//TODO: Remove dwellers
// if (t->subID == 4 && bid == 17) //Veil of Darkness
// {
// RemoveBonus rb(RemoveBonus::TOWN);
// rb.whoID = t->id;
// rb.source = Bonus::TOWN_STRUCTURE;
// rb.id = 17;
// sendAndApply(&rb);
// }
return true;
}
void CGameHandler::sendMessageToAll( const std::string &message )
{
SystemMessage sm;
sm.text = message;
sendToAllClients(&sm);
}
bool CGameHandler::recruitCreatures(ObjectInstanceID objid, ObjectInstanceID dstid, CreatureID crid, ui32 cram, si32 fromLvl )
{
const CGDwelling *dw = static_cast<const CGDwelling*>(gs->getObj(objid));
const CArmedInstance *dst = nullptr;
const CCreature *c = VLC->creh->creatures.at(crid);
bool warMachine = c->hasBonusOfType(Bonus::SIEGE_WEAPON);
//TODO: test for owning
//TODO: check if dst can recruit objects (e.g. hero is actually visiting object, town and source are same, etc)
dst = dynamic_cast<const CArmedInstance*>(getObj(dstid));
assert(dw && dst);
//verify
bool found = false;
int level = 0;
for(; level < dw->creatures.size(); level++) //iterate through all levels
{
if ( (fromLvl != -1) && ( level !=fromLvl ) )
continue;
const auto &cur = dw->creatures.at(level); //current level info <amount, list of cr. ids>
int i = 0;
for(; i < cur.second.size(); i++) //look for crid among available creatures list on current level
if(cur.second.at(i) == crid)
break;
if(i < cur.second.size())
{
found = true;
cram = std::min(cram, cur.first); //reduce recruited amount up to available amount
break;
}
}
SlotID slot = dst->getSlotFor(crid);
if( (!found && complain("Cannot recruit: no such creatures!"))
|| (cram > VLC->creh->creatures.at(crid)->maxAmount(gs->getPlayer(dst->tempOwner)->resources) && complain("Cannot recruit: lack of resources!"))
|| (cram<=0 && complain("Cannot recruit: cram <= 0!"))
|| (!slot.validSlot() && !warMachine && complain("Cannot recruit: no available slot!")))
{
return false;
}
//recruit
SetResources sr;
sr.player = dst->tempOwner;
sr.res = gs->getPlayer(dst->tempOwner)->resources - (c->cost * cram);
SetAvailableCreatures sac;
sac.tid = objid;
sac.creatures = dw->creatures;
sac.creatures[level].first -= cram;
sendAndApply(&sr);
sendAndApply(&sac);
if(warMachine)
{
const CGHeroInstance *h = dynamic_cast<const CGHeroInstance*>(dst);
if(!h)
COMPLAIN_RET("Only hero can buy war machines");
switch(crid)
{
case CreatureID::BALLISTA:
giveHeroNewArtifact(h, VLC->arth->artifacts[ArtifactID::BALLISTA], ArtifactPosition::MACH1);
break;
case CreatureID::FIRST_AID_TENT:
giveHeroNewArtifact(h, VLC->arth->artifacts[ArtifactID::FIRST_AID_TENT], ArtifactPosition::MACH3);
break;
case CreatureID::AMMO_CART:
giveHeroNewArtifact(h, VLC->arth->artifacts[ArtifactID::AMMO_CART], ArtifactPosition::MACH2);
break;
default:
complain("This war machine cannot be recruited!");
return false;
}
}
else
{
addToSlot(StackLocation(dst, slot), c, cram);
}
return true;
}
bool CGameHandler::upgradeCreature( ObjectInstanceID objid, SlotID pos, CreatureID upgID )
{
CArmedInstance *obj = static_cast<CArmedInstance*>(gs->getObjInstance(objid));
if (!obj->hasStackAtSlot(pos))
{
COMPLAIN_RET("Cannot upgrade, no stack at slot " + boost::to_string(pos));
}
UpgradeInfo ui = gs->getUpgradeInfo(obj->getStack(pos));
PlayerColor player = obj->tempOwner;
const PlayerState *p = getPlayer(player);
int crQuantity = obj->stacks.at(pos)->count;
int newIDpos= vstd::find_pos(ui.newID, upgID);//get position of new id in UpgradeInfo
//check if upgrade is possible
if( (ui.oldID<0 || newIDpos == -1 ) && complain("That upgrade is not possible!"))
{
return false;
}
TResources totalCost = ui.cost.at(newIDpos) * crQuantity;
//check if player has enough resources
if(!p->resources.canAfford(totalCost))
COMPLAIN_RET("Cannot upgrade, not enough resources!");
//take resources
SetResources sr;
sr.player = player;
sr.res = p->resources - totalCost;
sendAndApply(&sr);
//upgrade creature
changeStackType(StackLocation(obj, pos), VLC->creh->creatures.at(upgID));
return true;
}
bool CGameHandler::changeStackType(const StackLocation &sl, const CCreature *c)
{
if(!sl.army->hasStackAtSlot(sl.slot))
COMPLAIN_RET("Cannot find a stack to change type");
SetStackType sst;
sst.sl = sl;
sst.type = c;
sendAndApply(&sst);
return true;
}
void CGameHandler::moveArmy(const CArmedInstance *src, const CArmedInstance *dst, bool allowMerging)
{
assert(src->canBeMergedWith(*dst, allowMerging));
while(src->stacksCount())//while there are unmoved creatures
{
auto i = src->Slots().begin(); //iterator to stack to move
StackLocation sl(src, i->first); //location of stack to move
SlotID pos = dst->getSlotFor(i->second->type);
if(!pos.validSlot())
{
//try to merge two other stacks to make place
std::pair<SlotID, SlotID> toMerge;
if(dst->mergableStacks(toMerge, i->first) && allowMerging)
{
moveStack(StackLocation(dst, toMerge.first), StackLocation(dst, toMerge.second)); //merge toMerge.first into toMerge.second
assert(!dst->hasStackAtSlot(toMerge.first)); //we have now a new free slot
moveStack(sl, StackLocation(dst, toMerge.first)); //move stack to freed slot
}
else
{
complain("Unexpected failure during an attempt to move army from " + src->nodeName() + " to " + dst->nodeName() + "!");
return;
}
}
else
{
moveStack(sl, StackLocation(dst, pos));
}
}
}
bool CGameHandler::garrisonSwap( ObjectInstanceID tid )
{
CGTownInstance *town = gs->getTown(tid);
if(!town->garrisonHero && town->visitingHero) //visiting => garrison, merge armies: town army => hero army
{
if(!town->visitingHero->canBeMergedWith(*town))
{
complain("Cannot make garrison swap, not enough free slots!");
return false;
}
moveArmy(town, town->visitingHero, true);
SetHeroesInTown intown;
intown.tid = tid;
intown.visiting = ObjectInstanceID();
intown.garrison = town->visitingHero->id;
sendAndApply(&intown);
return true;
}
else if (town->garrisonHero && !town->visitingHero) //move hero out of the garrison
{
//check if moving hero out of town will break 8 wandering heroes limit
if(getHeroCount(town->garrisonHero->tempOwner,false) >= 8)
{
complain("Cannot move hero out of the garrison, there are already 8 wandering heroes!");
return false;
}
SetHeroesInTown intown;
intown.tid = tid;
intown.garrison = ObjectInstanceID();
intown.visiting = town->garrisonHero->id;
sendAndApply(&intown);
return true;
}
else if(!!town->garrisonHero && town->visitingHero) //swap visiting and garrison hero
{
SetHeroesInTown intown;
intown.tid = tid;
intown.garrison = town->visitingHero->id;
intown.visiting = town->garrisonHero->id;
sendAndApply(&intown);
return true;
}
else
{
complain("Cannot swap garrison hero!");
return false;
}
}
// With the amount of changes done to the function, it's more like transferArtifacts.
// Function moves artifact from src to dst. If dst is not a backpack and is already occupied, old dst art goes to backpack and is replaced.
bool CGameHandler::moveArtifact(const ArtifactLocation &al1, const ArtifactLocation &al2)
{
ArtifactLocation src = al1, dst = al2;
const PlayerColor srcPlayer = src.owningPlayer(), dstPlayer = dst.owningPlayer();
const CArmedInstance *srcObj = src.relatedObj(), *dstObj = dst.relatedObj();
// Make sure exchange is even possible between the two heroes.
if(!isAllowedExchange(srcObj->id, dstObj->id))
COMPLAIN_RET("That heroes cannot make any exchange!");
const CArtifactInstance *srcArtifact = src.getArt();
const CArtifactInstance *destArtifact = dst.getArt();
if (srcArtifact == nullptr)
COMPLAIN_RET("No artifact to move!");
if (destArtifact && srcPlayer != dstPlayer)
COMPLAIN_RET("Can't touch artifact on hero of another player!");
// Check if src/dest slots are appropriate for the artifacts exchanged.
// Moving to the backpack is always allowed.
if ((!srcArtifact || dst.slot < GameConstants::BACKPACK_START)
&& srcArtifact && !srcArtifact->canBePutAt(dst, true))
COMPLAIN_RET("Cannot move artifact!");
if ((srcArtifact && srcArtifact->artType->id == ArtifactID::ART_LOCK) || (destArtifact && destArtifact->artType->id == ArtifactID::ART_LOCK))
COMPLAIN_RET("Cannot move artifact locks.");
if (dst.slot >= GameConstants::BACKPACK_START && srcArtifact->artType->isBig())
COMPLAIN_RET("Cannot put big artifacts in backpack!");
if (src.slot == ArtifactPosition::MACH4 || dst.slot == ArtifactPosition::MACH4)
COMPLAIN_RET("Cannot move catapult!");
if(dst.slot >= GameConstants::BACKPACK_START)
vstd::amin(dst.slot, ArtifactPosition(GameConstants::BACKPACK_START + dst.getHolderArtSet()->artifactsInBackpack.size()));
if (src.slot == dst.slot && src.artHolder == dst.artHolder)
COMPLAIN_RET("Won't move artifact: Dest same as source!");
if(dst.slot < GameConstants::BACKPACK_START && destArtifact) //moving art to another slot
{
//old artifact must be removed first
moveArtifact(dst, ArtifactLocation(dst.artHolder, ArtifactPosition(
dst.getHolderArtSet()->artifactsInBackpack.size() + GameConstants::BACKPACK_START)));
}
MoveArtifact ma;
ma.src = src;
ma.dst = dst;
sendAndApply(&ma);
return true;
}
/**
* Assembles or disassembles a combination artifact.
* @param heroID ID of hero holding the artifact(s).
* @param artifactSlot The worn slot ID of the combination- or constituent artifact.
* @param assemble True for assembly operation, false for disassembly.
* @param assembleTo If assemble is true, this represents the artifact ID of the combination
* artifact to assemble to. Otherwise it's not used.
*/
bool CGameHandler::assembleArtifacts (ObjectInstanceID heroID, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)
{
CGHeroInstance *hero = gs->getHero(heroID);
const CArtifactInstance *destArtifact = hero->getArt(artifactSlot);
if(!destArtifact)
COMPLAIN_RET("assembleArtifacts: there is no such artifact instance!");
if(assemble)
{
CArtifact *combinedArt = VLC->arth->artifacts[assembleTo];
if(!combinedArt->constituents)
COMPLAIN_RET("assembleArtifacts: Artifact being attempted to assemble is not a combined artifacts!");
if(!vstd::contains(destArtifact->assemblyPossibilities(hero), combinedArt))
COMPLAIN_RET("assembleArtifacts: It's impossible to assemble requested artifact!");
AssembledArtifact aa;
aa.al = ArtifactLocation(hero, artifactSlot);
aa.builtArt = combinedArt;
sendAndApply(&aa);
}
else
{
if(!destArtifact->artType->constituents)
COMPLAIN_RET("assembleArtifacts: Artifact being attempted to disassemble is not a combined artifact!");
DisassembledArtifact da;
da.al = ArtifactLocation(hero, artifactSlot);
sendAndApply(&da);
}
return false;
}
bool CGameHandler::buyArtifact( ObjectInstanceID hid, ArtifactID aid )
{
CGHeroInstance *hero = gs->getHero(hid);
CGTownInstance *town = hero->visitedTown;
if(aid==ArtifactID::SPELLBOOK)
{
if((!town->hasBuilt(BuildingID::MAGES_GUILD_1) && complain("Cannot buy a spellbook, no mage guild in the town!"))
|| (getResource(hero->getOwner(), Res::GOLD) < GameConstants::SPELLBOOK_GOLD_COST && complain("Cannot buy a spellbook, not enough gold!") )
|| (hero->getArt(ArtifactPosition::SPELLBOOK) && complain("Cannot buy a spellbook, hero already has a one!"))
)
return false;
giveResource(hero->getOwner(),Res::GOLD,-GameConstants::SPELLBOOK_GOLD_COST);
giveHeroNewArtifact(hero, VLC->arth->artifacts[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK);
assert(hero->getArt(ArtifactPosition::SPELLBOOK));
giveSpells(town,hero);
return true;
}
else if(aid < 7 && aid > 3) //war machine
{
int price = VLC->arth->artifacts[aid]->price;
if(( hero->getArt(ArtifactPosition(9+aid)) && complain("Hero already has this machine!"))
|| (gs->getPlayer(hero->getOwner())->resources.at(Res::GOLD) < price && complain("Not enough gold!")))
{
return false;
}
if ((town->hasBuilt(BuildingID::BLACKSMITH) && town->town->warMachine == aid )
|| ((town->hasBuilt(BuildingID::BALLISTA_YARD, ETownType::STRONGHOLD)) && aid == ArtifactID::BALLISTA))
{
giveResource(hero->getOwner(),Res::GOLD,-price);
giveHeroNewArtifact(hero, VLC->arth->artifacts[aid], ArtifactPosition(9+aid));
return true;
}
else
COMPLAIN_RET("This machine is unavailable here!");
}
return false;
}
bool CGameHandler::buyArtifact(const IMarket *m, const CGHeroInstance *h, Res::ERes rid, ArtifactID aid)
{
if(!vstd::contains(m->availableItemsIds(EMarketMode::RESOURCE_ARTIFACT), aid))
COMPLAIN_RET("That artifact is unavailable!");
int b1, b2;
m->getOffer(rid, aid, b1, b2, EMarketMode::RESOURCE_ARTIFACT);
if(getResource(h->tempOwner, rid) < b1)
COMPLAIN_RET("You can't afford to buy this artifact!");
SetResource sr;
sr.player = h->tempOwner;
sr.resid = rid;
sr.val = getResource(h->tempOwner, rid) - b1;
sendAndApply(&sr);
SetAvailableArtifacts saa;
if(m->o->ID == Obj::TOWN)
{
saa.id = -1;
saa.arts = CGTownInstance::merchantArtifacts;
}
else if(const CGBlackMarket *bm = dynamic_cast<const CGBlackMarket *>(m->o)) //black market
{
saa.id = bm->id.getNum();
saa.arts = bm->artifacts;
}
else
COMPLAIN_RET("Wrong marktet...");
bool found = false;
for(const CArtifact *&art : saa.arts)
{
if(art && art->id == aid)
{
art = nullptr;
found = true;
break;
}
}
if(!found)
COMPLAIN_RET("Cannot find selected artifact on the list");
sendAndApply(&saa);
giveHeroNewArtifact(h, VLC->arth->artifacts[aid], ArtifactPosition::FIRST_AVAILABLE);
return true;
}
bool CGameHandler::sellArtifact( const IMarket *m, const CGHeroInstance *h, ArtifactInstanceID aid, Res::ERes rid )
{
const CArtifactInstance *art = h->getArtByInstanceId(aid);
if(!art)
COMPLAIN_RET("There is no artifact to sell!");
if(!art->artType->isTradable())
COMPLAIN_RET("Cannot sell a war machine or spellbook!");
int resVal = 0, dump = 1;
m->getOffer(art->artType->id, rid, dump, resVal, EMarketMode::ARTIFACT_RESOURCE);
removeArtifact(ArtifactLocation(h, h->getArtPos(art)));
giveResource(h->tempOwner, rid, resVal);
return true;
}
//void CGameHandler::lootArtifacts (TArtHolder source, TArtHolder dest, std::vector<ui32> &arts)
//{
// //const CGHeroInstance * h1 = dynamic_cast<CGHeroInstance *> source;
// //auto s = boost::apply_visitor(GetArtifactSetPtr(), source);
// {
// }
//}
bool CGameHandler::buySecSkill( const IMarket *m, const CGHeroInstance *h, SecondarySkill skill)
{
if (!h)
COMPLAIN_RET("You need hero to buy a skill!");
if (h->getSecSkillLevel(SecondarySkill(skill)))
COMPLAIN_RET("Hero already know this skill");
if (!h->canLearnSkill())
COMPLAIN_RET("Hero can't learn any more skills");
if (h->type->heroClass->secSkillProbability.at(skill)==0)//can't learn this skill (like necromancy for most of non-necros)
COMPLAIN_RET("The hero can't learn this skill!");
if(!vstd::contains(m->availableItemsIds(EMarketMode::RESOURCE_SKILL), skill))
COMPLAIN_RET("That skill is unavailable!");
if(getResource(h->tempOwner, Res::GOLD) < 2000)//TODO: remove hardcoded resource\summ?
COMPLAIN_RET("You can't afford to buy this skill");
SetResource sr;
sr.player = h->tempOwner;
sr.resid = Res::GOLD;
sr.val = getResource(h->tempOwner, Res::GOLD) - 2000;
sendAndApply(&sr);
changeSecSkill(h, skill, 1, true);
return true;
}
bool CGameHandler::tradeResources(const IMarket *market, ui32 val, PlayerColor player, ui32 id1, ui32 id2)
{
int r1 = gs->getPlayer(player)->resources.at(id1),
r2 = gs->getPlayer(player)->resources.at(id2);
vstd::amin(val, r1); //can't trade more resources than have
int b1, b2; //base quantities for trade
market->getOffer(id1, id2, b1, b2, EMarketMode::RESOURCE_RESOURCE);
int units = val / b1; //how many base quantities we trade
if(val%b1) //all offered units of resource should be used, if not -> somewhere in calculations must be an error
{
//TODO: complain?
assert(0);
}
SetResource sr;
sr.player = player;
sr.resid = static_cast<Res::ERes>(id1);
sr.val = r1 - b1 * units;
sendAndApply(&sr);
sr.resid = static_cast<Res::ERes>(id2);
sr.val = r2 + b2 * units;
sendAndApply(&sr);
return true;
}
bool CGameHandler::sellCreatures(ui32 count, const IMarket *market, const CGHeroInstance * hero, SlotID slot, Res::ERes resourceID)
{
if(!vstd::contains(hero->Slots(), slot))
COMPLAIN_RET("Hero doesn't have any creature in that slot!");
const CStackInstance &s = hero->getStack(slot);
if( s.count < count //can't sell more creatures than have
|| (hero->stacksCount() == 1 && hero->needsLastStack() && s.count == count)) //can't sell last stack
{
COMPLAIN_RET("Not enough creatures in army!");
}
int b1, b2; //base quantities for trade
market->getOffer(s.type->idNumber, resourceID, b1, b2, EMarketMode::CREATURE_RESOURCE);
int units = count / b1; //how many base quantities we trade
if(count%b1) //all offered units of resource should be used, if not -> somewhere in calculations must be an error
{
//TODO: complain?
assert(0);
}
changeStackCount(StackLocation(hero, slot), -count);
SetResource sr;
sr.player = hero->tempOwner;
sr.resid = resourceID;
sr.val = getResource(hero->tempOwner, resourceID) + b2 * units;
sendAndApply(&sr);
return true;
}
bool CGameHandler::transformInUndead(const IMarket *market, const CGHeroInstance * hero, SlotID slot)
{
const CArmedInstance *army = nullptr;
if (hero)
army = hero;
else
army = dynamic_cast<const CGTownInstance *>(market->o);
if (!army)
COMPLAIN_RET("Incorrect call to transform in undead!");
if(!army->hasStackAtSlot(slot))
COMPLAIN_RET("Army doesn't have any creature in that slot!");
const CStackInstance &s = army->getStack(slot);
//resulting creature - bone dragons or skeletons
CreatureID resCreature = CreatureID::SKELETON;
if(s.hasBonusOfType(Bonus::DRAGON_NATURE)
|| (s.getCreatureID() == CreatureID::HYDRA)
|| (s.getCreatureID() == CreatureID::CHAOS_HYDRA))
resCreature = CreatureID::BONE_DRAGON;
changeStackType(StackLocation(army, slot), resCreature.toCreature());
return true;
}
bool CGameHandler::sendResources(ui32 val, PlayerColor player, Res::ERes r1, PlayerColor r2)
{
const PlayerState *p2 = gs->getPlayer(r2, false);
if(!p2 || p2->status != EPlayerStatus::INGAME)
{
complain("Dest player must be in game!");
return false;
}
si32 curRes1 = gs->getPlayer(player)->resources.at(r1),
curRes2 = gs->getPlayer(r2)->resources.at(r1);
val = std::min(si32(val),curRes1);
SetResource sr;
sr.player = player;
sr.resid = r1;
sr.val = curRes1 - val;
sendAndApply(&sr);
sr.player = r2;
sr.val = curRes2 + val;
sendAndApply(&sr);
return true;
}
bool CGameHandler::setFormation( ObjectInstanceID hid, ui8 formation )
{
gs->getHero(hid)-> formation = formation;
return true;
}
bool CGameHandler::hireHero(const CGObjectInstance *obj, ui8 hid, PlayerColor player)
{
const PlayerState *p = gs->getPlayer(player);
const CGTownInstance *t = gs->getTown(obj->id);
//common preconditions
// if( (p->resources.at(Res::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.at(Res::GOLD) < GameConstants::HERO_GOLD_COST && complain("Not enough gold for buying hero!"))
|| ((!t) && (getHeroCount(player, false) >= VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER && complain("Cannot hire hero, too many wandering heroes already!")))
|| ((t) && (getHeroCount(player, true) >= VLC->modh->settings.MAX_HEROES_AVAILABLE_PER_PLAYER && 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 = obj->visitablePos() + nh->getVisitableOffset();
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, gs->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);
SetResource sr;
sr.player = player;
sr.resid = Res::GOLD;
sr.val = p->resources.at(Res::GOLD) - GameConstants::HERO_GOLD_COST;
sendAndApply(&sr);
if(t)
{
vistiCastleObjects (t, nh);
giveSpells (t,nh);
}
return true;
}
bool CGameHandler::queryReply(QueryID qid, ui32 answer, PlayerColor player)
{
boost::unique_lock<boost::recursive_mutex> lock(gsm);
logGlobal->traceStream() << boost::format("Player %s attempts answering query %d with answer %d") % player % qid % answer;
auto topQuery = queries.topQuery(player);
COMPLAIN_RET_FALSE_IF(!topQuery, "This player doesn't have any queries!");
COMPLAIN_RET_FALSE_IF(topQuery->queryID != qid, "This player top query has different ID!");
COMPLAIN_RET_FALSE_IF(!topQuery->endsByPlayerAnswer(), "This query cannot be ended by player's answer!");
if(auto dialogQuery = std::dynamic_pointer_cast<CDialogQuery>(topQuery))
dialogQuery->answer = answer;
queries.popQuery(topQuery);
return true;
}
static EndAction end_action;
bool CGameHandler::makeBattleAction( BattleAction &ba )
{
bool ok = true;
const CStack *stack = battleGetStackByID(ba.stackNumber); //may be nullptr if action is not about stack
const CStack *destinationStack = ba.actionType == Battle::WALK_AND_ATTACK ? gs->curB->battleGetStackByPos(ba.additionalInfo)
: ba.actionType == Battle::SHOOT ? gs->curB->battleGetStackByPos(ba.destinationTile)
: nullptr;
const bool isAboutActiveStack = stack && (stack == battleActiveStack());
logGlobal->traceStream() << boost::format(
"Making action: type=%d; side=%d; stack=%s; dst=%s; additionalInfo=%d; stackAtDst=%s")
% ba.actionType % (int)ba.side % (stack ? stack->getName() : std::string("none"))
% ba.destinationTile % ba.additionalInfo % (destinationStack ? destinationStack->getName() : std::string("none"));
switch(ba.actionType)
{
case Battle::WALK: //walk
case Battle::DEFEND: //defend
case Battle::WAIT: //wait
case Battle::WALK_AND_ATTACK: //walk or attack
case Battle::SHOOT: //shoot
case Battle::CATAPULT: //catapult
case Battle::STACK_HEAL: //healing with First Aid Tent
case Battle::DAEMON_SUMMONING:
case Battle::MONSTER_SPELL:
if(!stack)
{
complain("No such stack!");
return false;
}
if(!stack->alive())
{
complain("This stack is dead: " + stack->nodeName());
return false;
}
if(battleTacticDist())
{
if(stack && !stack->attackerOwned != battleGetTacticsSide())
{
complain("This is not a stack of side that has tactics!");
return false;
}
}
else if(!isAboutActiveStack)
{
complain("Action has to be about active stack!");
return false;
}
}
switch(ba.actionType)
{
case Battle::END_TACTIC_PHASE: //wait
case Battle::BAD_MORALE:
case Battle::NO_ACTION:
{
StartAction start_action(ba);
sendAndApply(&start_action);
sendAndApply(&end_action);
break;
}
case Battle::WALK:
{
StartAction start_action(ba);
sendAndApply(&start_action); //start movement
int walkedTiles = moveStack(ba.stackNumber,ba.destinationTile); //move
if(!walkedTiles)
complain("Stack failed movement!");
sendAndApply(&end_action);
break;
}
case Battle::DEFEND:
{
//defensive stance //TODO: remove this bonus when stack becomes active
SetStackEffect sse;
sse.effect.push_back( Bonus(Bonus::STACK_GETS_TURN, Bonus::PRIMARY_SKILL, Bonus::OTHER, 20, -1, PrimarySkill::DEFENSE, Bonus::PERCENT_TO_ALL) );
sse.effect.push_back( Bonus(Bonus::STACK_GETS_TURN, Bonus::PRIMARY_SKILL, Bonus::OTHER, gs->curB->battleGetStackByID(ba.stackNumber)->valOfBonuses(Bonus::DEFENSIVE_STANCE),
-1, PrimarySkill::DEFENSE, Bonus::ADDITIVE_VALUE));
sse.stacks.push_back(ba.stackNumber);
sendAndApply(&sse);
//don't break - we share code with next case
}
case Battle::WAIT:
{
StartAction start_action(ba);
sendAndApply(&start_action);
sendAndApply(&end_action);
break;
}
case Battle::RETREAT: //retreat/flee
{
if(!gs->curB->battleCanFlee(gs->curB->sides.at(ba.side).color))
complain("Cannot retreat!");
else
setBattleResult(BattleResult::ESCAPE, !ba.side); //surrendering side loses
break;
}
case Battle::SURRENDER:
{
PlayerColor player = gs->curB->sides.at(ba.side).color;
int cost = gs->curB->battleGetSurrenderCost(player);
if(cost < 0)
complain("Cannot surrender!");
else if(getResource(player, Res::GOLD) < cost)
complain("Not enough gold to surrender!");
else
{
giveResource(player, Res::GOLD, -cost);
setBattleResult(BattleResult::SURRENDER, !ba.side); //surrendering side loses
}
break;
}
case Battle::WALK_AND_ATTACK: //walk or attack
{
StartAction start_action(ba);
sendAndApply(&start_action); //start movement and attack
if(!stack || !destinationStack)
{
sendAndApply(&end_action);
break;
}
BattleHex startingPos = stack->position;
int distance = moveStack(ba.stackNumber, ba.destinationTile);
logGlobal->traceStream() << stack->nodeName() << " will attack " << destinationStack->nodeName();
if(stack->position != ba.destinationTile //we wasn't able to reach destination tile
&& !(stack->doubleWide()
&& ( stack->position == ba.destinationTile + (stack->attackerOwned ? +1 : -1 ) )
) //nor occupy specified hex
)
{
std::string problem = "We cannot move this stack to its destination " + stack->getCreature()->namePl;
logGlobal->warnStream() << problem;
complain(problem);
ok = false;
sendAndApply(&end_action);
break;
}
if(destinationStack && stack && stack->ID == destinationStack->ID) //we should just move, it will be handled by following check
{
destinationStack = nullptr;
}
if(!destinationStack)
{
complain(boost::str(boost::format("walk and attack error: no stack at additionalInfo tile (%d)!\n") % ba.additionalInfo));
ok = false;
sendAndApply(&end_action);
break;
}
if( !CStack::isMeleeAttackPossible(stack, destinationStack) )
{
complain("Attack cannot be performed!");
sendAndApply(&end_action);
ok = false;
break;
}
//attack
int totalAttacks = 1 + stack->getBonuses(Selector::type (Bonus::ADDITIONAL_ATTACK),
(Selector::effectRange(Bonus::NO_LIMIT).Or(Selector::effectRange(Bonus::ONLY_MELEE_FIGHT))))->totalValue(); //all unspicified attacks + melee attacks
for (int i = 0; i < totalAttacks; ++i)
{
if (stack &&
stack->alive() && //move can cause death, eg. by walking into the moat
destinationStack->alive())
{
BattleAttack bat;
prepareAttack(bat, stack, destinationStack, (i ? 0 : distance), ba.additionalInfo); //no distance travelled on second attack
//prepareAttack(bat, stack, stackAtEnd, 0, ba.additionalInfo);
handleAttackBeforeCasting(bat); //only before first attack
sendAndApply(&bat);
handleAfterAttackCasting(bat);
}
//counterattack
if (destinationStack
&& !stack->hasBonusOfType(Bonus::BLOCKS_RETALIATION)
&& destinationStack->ableToRetaliate()
&& stack->alive()) //attacker may have died (fire shield)
{
BattleAttack bat;
prepareAttack(bat, destinationStack, stack, 0, stack->position);
bat.flags |= BattleAttack::COUNTER;
sendAndApply(&bat);
handleAfterAttackCasting(bat);
}
}
//return
if(stack->hasBonusOfType(Bonus::RETURN_AFTER_STRIKE) && startingPos != stack->position && stack->alive())
{
moveStack(ba.stackNumber, startingPos);
//NOTE: curStack->ID == ba.stackNumber (rev 1431)
}
sendAndApply(&end_action);
break;
}
case Battle::SHOOT:
{
if( !gs->curB->battleCanShoot(stack, ba.destinationTile) )
{
complain("Cannot shoot!");
break;
}
StartAction start_action(ba);
sendAndApply(&start_action); //start shooting
{
BattleAttack bat;
bat.flags |= BattleAttack::SHOT;
prepareAttack(bat, stack, destinationStack, 0, ba.destinationTile);
handleAttackBeforeCasting(bat);
sendAndApply(&bat);
handleAfterAttackCasting(bat);
}
//second shot for ballista, only if hero has advanced artillery
const CGHeroInstance * attackingHero = gs->curB->battleGetFightingHero(ba.side);
if( destinationStack->alive()
&& (stack->getCreature()->idNumber == CreatureID::BALLISTA)
&& (attackingHero->getSecSkillLevel(SecondarySkill::ARTILLERY) >= SecSkillLevel::ADVANCED)
)
{
BattleAttack bat2;
bat2.flags |= BattleAttack::SHOT;
prepareAttack(bat2, stack, destinationStack, 0, ba.destinationTile);
sendAndApply(&bat2);
}
//allow more than one additional attack
int additionalAttacks = stack->getBonuses(Selector::type (Bonus::ADDITIONAL_ATTACK),
(Selector::effectRange(Bonus::NO_LIMIT).Or(Selector::effectRange(Bonus::ONLY_DISTANCE_FIGHT))))->totalValue();
for (int i = 0; i < additionalAttacks; ++i)
{
if(
stack->alive()
&& destinationStack->alive()
&& stack->shots
)
{
BattleAttack bat;
bat.flags |= BattleAttack::SHOT;
prepareAttack(bat, stack, destinationStack, 0, ba.destinationTile);
sendAndApply(&bat);
handleAfterAttackCasting(bat);
}
}
sendAndApply(&end_action);
break;
}
case Battle::CATAPULT:
{
auto getCatapultHitChance = [&](EWallPart::EWallPart part, const CHeroHandler::SBallisticsLevelInfo & sbi) -> int
{
switch(part)
{
case EWallPart::GATE:
return sbi.gate;
case EWallPart::KEEP:
return sbi.keep;
case EWallPart::BOTTOM_TOWER:
case EWallPart::UPPER_TOWER:
return sbi.tower;
case EWallPart::BOTTOM_WALL:
case EWallPart::BELOW_GATE:
case EWallPart::OVER_GATE:
case EWallPart::UPPER_WALL:
return sbi.wall;
default:
return 0;
}
};
StartAction start_action(ba);
sendAndApply(&start_action);
auto onExit = vstd::makeScopeGuard([&]{ sendAndApply(&end_action); }); //if we started than we have to finish
const CGHeroInstance * attackingHero = gs->curB->battleGetFightingHero(ba.side);
CHeroHandler::SBallisticsLevelInfo sbi = VLC->heroh->ballistics.at(attackingHero->getSecSkillLevel(SecondarySkill::BALLISTICS));
auto wallPart = gs->curB->battleHexToWallPart(ba.destinationTile);
if(!gs->curB->isWallPartPotentiallyAttackable(wallPart))
{
complain("catapult tried to attack non-catapultable hex!");
break;
}
//in successive iterations damage is dealt but not yet subtracted from wall's HPs
auto &currentHP = gs->curB->si.wallState;
if (currentHP.at(wallPart) == EWallState::DESTROYED || currentHP.at(wallPart) == EWallState::NONE)
{
complain("catapult tried to attack already destroyed wall part!");
break;
}
for(int g=0; g<sbi.shots; ++g)
{
bool hitSuccessfull = false;
auto attackedPart = wallPart;
do // catapult has chance to attack desired target. Othervice - attacks randomly
{
if(currentHP.at(attackedPart) != EWallState::DESTROYED && // this part can be hit
currentHP.at(attackedPart) != EWallState::NONE &&
gs->getRandomGenerator().nextInt(99) < getCatapultHitChance(attackedPart, sbi))//hit is successful
{
hitSuccessfull = true;
}
else // select new target
{
std::vector<EWallPart::EWallPart> allowedTargets;
for (size_t i=0; i< currentHP.size(); i++)
{
if (currentHP.at(i) != EWallState::DESTROYED &&
currentHP.at(i) != EWallState::NONE)
allowedTargets.push_back(EWallPart::EWallPart(i));
}
if (allowedTargets.empty())
break;
attackedPart = *RandomGeneratorUtil::nextItem(allowedTargets, gs->getRandomGenerator());
}
}
while (!hitSuccessfull);
if (!hitSuccessfull) // break triggered - no target to shoot at
break;
CatapultAttack ca; //package for clients
CatapultAttack::AttackInfo attack;
attack.attackedPart = attackedPart;
attack.destinationTile = ba.destinationTile;
attack.damageDealt = 0;
int dmgChance[] = { sbi.noDmg, sbi.oneDmg, sbi.twoDmg }; //dmgChance[i] - chance for doing i dmg when hit is successful
int dmgRand = gs->getRandomGenerator().nextInt(99);
//accumulating dmgChance
dmgChance[1] += dmgChance[0];
dmgChance[2] += dmgChance[1];
//calculating dealt damage
for(int damage = 0; damage < ARRAY_COUNT(dmgChance); ++damage)
{
if(dmgRand <= dmgChance[damage])
{
attack.damageDealt = damage;
break;
}
}
// attacked tile may have changed - update destination
attack.destinationTile = gs->curB->wallPartToBattleHex(EWallPart::EWallPart(attack.attackedPart));
logGlobal->traceStream() << "Catapult attacks " << (int)attack.attackedPart
<< " dealing " << (int)attack.damageDealt << " damage";
//removing creatures in turrets / keep if one is destroyed
if(attack.damageDealt > 0 && (attackedPart == EWallPart::KEEP ||
attackedPart == EWallPart::BOTTOM_TOWER || attackedPart == EWallPart::UPPER_TOWER))
{
int posRemove = -1;
switch(attackedPart)
{
case EWallPart::KEEP:
posRemove = -2;
break;
case EWallPart::BOTTOM_TOWER:
posRemove = -3;
break;
case EWallPart::UPPER_TOWER:
posRemove = -4;
break;
}
BattleStacksRemoved bsr;
for(auto & elem : gs->curB->stacks)
{
if(elem->position == posRemove)
{
bsr.stackIDs.insert( elem->ID );
break;
}
}
sendAndApply(&bsr);
}
ca.attacker = ba.stackNumber;
ca.attackedParts.push_back(attack);
sendAndApply(&ca);
}
//finish by scope guard
break;
}
case Battle::STACK_HEAL: //healing with First Aid Tent
{
StartAction start_action(ba);
sendAndApply(&start_action);
const CGHeroInstance * attackingHero = gs->curB->battleGetFightingHero(ba.side);
const CStack *healer = gs->curB->battleGetStackByID(ba.stackNumber),
*destStack = gs->curB->battleGetStackByPos(ba.destinationTile);
ui32 healed = 0;
if(healer == nullptr || destStack == nullptr || !healer->hasBonusOfType(Bonus::HEALER))
{
complain("There is either no healer, no destination, or healer cannot heal :P");
}
else
{
ui32 maxiumHeal = healer->count * std::max(10, attackingHero->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::FIRST_AID));
healed = destStack->calculateHealedHealthPoints(maxiumHeal, false);
}
if(healed == 0)
{
//nothing to heal.. should we complain?
}
else
{
StacksHealedOrResurrected shr;
shr.lifeDrain = false;
shr.tentHealing = true;
shr.drainedFrom = ba.stackNumber;
StacksHealedOrResurrected::HealInfo hi;
hi.healedHP = healed;
hi.lowLevelResurrection = false;
hi.stackID = destStack->ID;
shr.healedStacks.push_back(hi);
sendAndApply(&shr);
}
sendAndApply(&end_action);
break;
}
case Battle::DAEMON_SUMMONING:
//TODO: From Strategija:
//Summon Demon is a level 2 spell.
{
StartAction start_action(ba);
sendAndApply(&start_action);
const CStack *summoner = gs->curB->battleGetStackByID(ba.stackNumber),
*destStack = gs->curB->battleGetStackByPos(ba.destinationTile, false);
BattleStackAdded bsa;
bsa.attacker = summoner->attackerOwned;
bsa.creID = CreatureID(summoner->getBonusLocalFirst(Selector::type(Bonus::DAEMON_SUMMONING))->subtype); //in case summoner can summon more than one type of monsters... scream!
ui64 risedHp = summoner->count * summoner->valOfBonuses(Bonus::DAEMON_SUMMONING, bsa.creID.toEnum());
ui64 targetHealth = destStack->getCreature()->MaxHealth() * destStack->baseAmount;
ui64 canRiseHp = std::min(targetHealth, risedHp);
ui32 canRiseAmount = canRiseHp / VLC->creh->creatures.at(bsa.creID)->MaxHealth();
bsa.amount = std::min(canRiseAmount, destStack->baseAmount);
bsa.pos = gs->curB->getAvaliableHex(bsa.creID, bsa.attacker, destStack->position);
bsa.summoned = false;
if (bsa.amount) //there's rare possibility single creature cannot rise desired type
{
BattleStacksRemoved bsr; //remove body
bsr.stackIDs.insert(destStack->ID);
sendAndApply(&bsr);
sendAndApply(&bsa);
BattleSetStackProperty ssp;
ssp.stackID = ba.stackNumber;
ssp.which = BattleSetStackProperty::CASTS; //reduce number of casts
ssp.val = -1;
ssp.absolute = false;
sendAndApply(&ssp);
}
sendAndApply(&end_action);
break;
}
case Battle::MONSTER_SPELL:
{
StartAction start_action(ba);
sendAndApply(&start_action);
const CStack * stack = gs->curB->battleGetStackByID(ba.stackNumber);
SpellID spellID = SpellID(ba.additionalInfo);
BattleHex destination(ba.destinationTile);
const Bonus *randSpellcaster = stack->getBonusLocalFirst(Selector::type(Bonus::RANDOM_SPELLCASTER));
const Bonus * spellcaster = stack->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPELLCASTER, spellID));
//TODO special bonus for genies ability
if(randSpellcaster && battleGetRandomStackSpell(stack, CBattleInfoCallback::RANDOM_AIMED) < 0)
spellID = battleGetRandomStackSpell(stack, CBattleInfoCallback::RANDOM_GENIE);
if(spellID < 0)
complain("That stack can't cast spells!");
else
{
const CSpell * spell = SpellID(spellID).toSpell();
BattleSpellCastParameters parameters(gs->curB, stack, spell);
parameters.spellLvl = 0;
if (spellcaster)
vstd::amax(parameters.spellLvl, spellcaster->val);
if (randSpellcaster)
vstd::amax(parameters.spellLvl, randSpellcaster->val);
vstd::amin (parameters.spellLvl, 3);
parameters.effectLevel = parameters.spellLvl;
parameters.mode = ECastingMode::CREATURE_ACTIVE_CASTING;
parameters.aimToHex(destination);//todo: allow multiple destinations
parameters.selectedStack = nullptr;
spell->battleCast(spellEnv, parameters);
}
sendAndApply(&end_action);
break;
}
}
if(ba.stackNumber == gs->curB->activeStack || battleResult.get()) //active stack has moved or battle has finished
battleMadeAction.setn(true);
return ok;
}
void CGameHandler::playerMessage( PlayerColor player, const std::string &message, ObjectInstanceID currObj )
{
bool cheated=true;
PlayerMessage temp_message(player, message, ObjectInstanceID(-1)); // don't inform other client on selected object
sendAndApply(&temp_message);
if(message == "vcmiistari") //give all spells and 999 mana
{
SetMana sm;
GiveBonus giveBonus(GiveBonus::HERO);
CGHeroInstance *h = gs->getHero(currObj);
if(!h && complain("Cannot realize cheat, no hero selected!")) return;
sm.hid = h->id;
giveBonus.id = h->id.getNum();
//give all spells with bonus (to allow banned spells)
giveBonus.bonus = Bonus(Bonus::PERMANENT, Bonus::SPELLS_OF_LEVEL, Bonus::OTHER, 0, 0);
//start with level 0 to skip abilities
for(int level = 1; level <= GameConstants::SPELL_LEVELS; level++)
{
giveBonus.bonus.subtype = level;
sendAndApply(&giveBonus);
}
//give mana
sm.val = 999;
sm.absolute = true;
if(!h->hasSpellbook()) //hero doesn't have spellbook
giveHeroNewArtifact(h, VLC->arth->artifacts[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK); //give spellbook
sendAndApply(&sm);
}
else if (message == "vcmiarmenelos") //build all buildings in selected town
{
CGHeroInstance *hero = gs->getHero(currObj);
CGTownInstance *town;
if (hero)
town = hero->visitedTown;
else
town = gs->getTown(currObj);
if (town)
{
for (auto & build : town->town->buildings)
{
if (!town->hasBuilt(build.first)
&& !build.second->Name().empty()
&& build.first != BuildingID::SHIP)
{
buildStructure(town->id, build.first, true);
}
}
}
}
else if(message == "vcmiainur") //gives 5 archangels into each slot
{
CGHeroInstance *hero = gs->getHero(currObj);
const CCreature *archangel = VLC->creh->creatures.at(13);
if(!hero) return;
for(int i = 0; i < GameConstants::ARMY_SIZE; i++)
if(!hero->hasStackAtSlot(SlotID(i)))
insertNewStack(StackLocation(hero, SlotID(i)), archangel, 5);
}
else if(message == "vcmiangband") //gives 10 black knight into each slot
{
CGHeroInstance *hero = gs->getHero(currObj);
const CCreature *blackKnight = VLC->creh->creatures.at(66);
if(!hero) return;
for(int i = 0; i < GameConstants::ARMY_SIZE; i++)
if(!hero->hasStackAtSlot(SlotID(i)))
insertNewStack(StackLocation(hero, SlotID(i)), blackKnight, 10);
}
else if(message == "vcminoldor") //all war machines
{
CGHeroInstance *hero = gs->getHero(currObj);
if(!hero) return;
if(!hero->getArt(ArtifactPosition::MACH1))
giveHeroNewArtifact(hero, VLC->arth->artifacts[ArtifactID::BALLISTA], ArtifactPosition::MACH1);
if(!hero->getArt(ArtifactPosition::MACH2))
giveHeroNewArtifact(hero, VLC->arth->artifacts[ArtifactID::AMMO_CART], ArtifactPosition::MACH2);
if(!hero->getArt(ArtifactPosition::MACH3))
giveHeroNewArtifact(hero, VLC->arth->artifacts[ArtifactID::FIRST_AID_TENT], ArtifactPosition::MACH3);
}
else if (message == "vcmiforgeofnoldorking") //hero gets all artifacts except war machines, spell scrolls and spell book
{
CGHeroInstance *hero = gs->getHero(currObj);
if(!hero) return;
for (int g = 7; g < VLC->arth->artifacts.size(); ++g) //including artifacts from mods
giveHeroNewArtifact(hero, VLC->arth->artifacts[g], ArtifactPosition::PRE_FIRST);
}
else if(message == "vcmiglorfindel") //selected hero gains a new level
{
CGHeroInstance *hero = gs->getHero(currObj);
changePrimSkill(hero, PrimarySkill::EXPERIENCE, VLC->heroh->reqExp(hero->level+1) - VLC->heroh->reqExp(hero->level));
}
else if(message == "vcminahar") //1000000 movement points
{
CGHeroInstance *hero = gs->getHero(currObj);
if(!hero) return;
SetMovePoints smp;
smp.hid = hero->id;
smp.val = 1000000;
sendAndApply(&smp);
}
else if(message == "vcmiformenos") //give resources
{
SetResources sr;
sr.player = player;
sr.res = gs->getPlayer(player)->resources;
for(int i=0;i<Res::GOLD;i++)
sr.res[i] += 100;
sr.res[Res::GOLD] += 100000; //100k
sendAndApply(&sr);
}
else if(message == "vcmieagles") //reveal FoW
{
FoWChange fc;
fc.mode = 1;
fc.player = player;
auto hlp_tab = new int3[gs->map->width * gs->map->height * (gs->map->twoLevel ? 2 : 1)];
int lastUnc = 0;
for(int i=0;i<gs->map->width;i++)
for(int j=0;j<gs->map->height;j++)
for(int k = 0; k < (gs->map->twoLevel ? 2 : 1); k++)
if(!gs->getPlayerTeam(fc.player)->fogOfWarMap.at(i).at(j).at(k))
hlp_tab[lastUnc++] = int3(i,j,k);
fc.tiles.insert(hlp_tab, hlp_tab + lastUnc);
delete [] hlp_tab;
sendAndApply(&fc);
}
else if(message == "vcmisilmaril") //player wins
{
gs->getPlayer(player)->enteredWinningCheatCode = 1;
}
else if(message == "vcmimelkor") //player looses
{
gs->getPlayer(player)->enteredLosingCheatCode = 1;
}
else
cheated = false;
if(cheated)
{
SystemMessage temp_message(VLC->generaltexth->allTexts.at(260));
sendAndApply(&temp_message);
checkVictoryLossConditionsForPlayer(player);//Player enter win code or got required art\creature
}
}
bool CGameHandler::makeCustomAction( BattleAction &ba )
{
switch(ba.actionType)
{
case Battle::HERO_SPELL:
{
COMPLAIN_RET_FALSE_IF(ba.side > 1, "Side must be 0 or 1!");
const CGHeroInstance *h = gs->curB->battleGetFightingHero(ba.side);
if(!h)
{
logGlobal->warnStream() << "Wrong caster!";
return false;
}
if(ba.additionalInfo >= VLC->spellh->objects.size())
{
logGlobal->warnStream() << "Wrong spell id (" << ba.additionalInfo << ")!";
return false;
}
const CSpell * s = SpellID(ba.additionalInfo).toSpell();
BattleSpellCastParameters parameters(gs->curB, h, s);
parameters.aimToHex(ba.destinationTile);//todo: allow multiple destinations
parameters.mode = ECastingMode::HERO_CASTING;
parameters.selectedStack = gs->curB->battleGetStackByID(ba.selectedStack, false);
ESpellCastProblem::ESpellCastProblem escp = gs->curB->battleCanCastThisSpell(h, s, ECastingMode::HERO_CASTING);//todo: should we check aimed cast(battleCanCastThisSpellHere)?
if(escp != ESpellCastProblem::OK)
{
logGlobal->warnStream() << "Spell cannot be cast!";
logGlobal->warnStream() << "Problem : " << escp;
return false;
}
StartAction start_action(ba);
sendAndApply(&start_action); //start spell casting
s->battleCast(spellEnv, parameters);
sendAndApply(&end_action);
if( !gs->curB->battleGetStackByID(gs->curB->activeStack, true))
{
battleMadeAction.setn(true);
}
checkForBattleEnd();
if(battleResult.get())
{
battleMadeAction.setn(true);
//battle will be ended by startBattle function
//endBattle(gs->curB->tile, gs->curB->heroes[0], gs->curB->heroes[1]);
}
return true;
}
}
return false;
}
void CGameHandler::stackTurnTrigger(const CStack * st)
{
BattleTriggerEffect bte;
bte.stackID = st->ID;
bte.effect = -1;
bte.val = 0;
bte.additionalInfo = 0;
if (st->alive())
{
//unbind
if (st->getEffect (SpellID::BIND))
{
bool unbind = true;
BonusList bl = *(st->getBonuses(Selector::type(Bonus::BIND_EFFECT)));
std::set<const CStack*> stacks = gs->curB-> batteAdjacentCreatures(st);
for(Bonus * b : bl)
{
const CStack * stack = gs->curB->battleGetStackByID(b->additionalInfo); //binding stack must be alive and adjacent
if (stack)
{
if (vstd::contains(stacks, stack)) //binding stack is still present
{
unbind = false;
}
}
}
if (unbind)
{
BattleSetStackProperty ssp;
ssp.which = BattleSetStackProperty::UNBIND;
ssp.stackID = st->ID;
sendAndApply(&ssp);
}
}
//regeneration
if(st->hasBonusOfType(Bonus::HP_REGENERATION))
{
bte.effect = Bonus::HP_REGENERATION;
bte.val = std::min((int)(st->MaxHealth() - st->firstHPleft), st->valOfBonuses(Bonus::HP_REGENERATION));
}
if(st->hasBonusOfType(Bonus::FULL_HP_REGENERATION))
{
bte.effect = Bonus::HP_REGENERATION;
bte.val = st->MaxHealth() - st->firstHPleft;
}
if (bte.val) //anything to heal
sendAndApply(&bte);
if(st->hasBonusOfType(Bonus::POISON))
{
const Bonus * b = st->getBonusLocalFirst(Selector::source(Bonus::SPELL_EFFECT, SpellID::POISON).And(Selector::type(Bonus::STACK_HEALTH)));
if (b) //TODO: what if not?...
{
bte.val = std::max (b->val - 10, -(st->valOfBonuses(Bonus::POISON)));
if (bte.val < b->val) //(negative) poison effect increases - update it
{
bte.effect = Bonus::POISON;
sendAndApply(&bte);
}
}
}
if (st->hasBonusOfType(Bonus::MANA_DRAIN) && !vstd::contains(st->state, EBattleStackState::DRAINED_MANA))
{
const PlayerColor opponent = gs->curB->theOtherPlayer(st->owner);
const CGHeroInstance * opponentHero = gs->curB->getHero(opponent);
if (opponentHero)
{
ui32 manaDrained = st->valOfBonuses(Bonus::MANA_DRAIN);
vstd::amin(manaDrained, opponentHero->mana);
if (manaDrained)
{
bte.effect = Bonus::MANA_DRAIN;
bte.val = manaDrained;
bte.additionalInfo = opponentHero->id.getNum(); //for sanity
sendAndApply(&bte);
}
}
}
if (st->isLiving() && !st->hasBonusOfType(Bonus::FEARLESS))
{
bool fearsomeCreature = false;
for(CStack * stack : gs->curB->stacks)
{
if (stack->owner != st->owner && stack->alive() && stack->hasBonusOfType(Bonus::FEAR))
{
fearsomeCreature = true;
break;
}
}
if (fearsomeCreature)
{
if (gs->getRandomGenerator().nextInt(99) < 10) //fixed 10%
{
bte.effect = Bonus::FEAR;
sendAndApply(&bte);
}
}
}
BonusList bl = *(st->getBonuses(Selector::type(Bonus::ENCHANTER)));
int side = gs->curB->whatSide(st->owner);
if (st->casts && !gs->curB->sides.at(side).enchanterCounter)
{
bool cast = false;
while (!bl.empty() && !cast)
{
auto bonus = *RandomGeneratorUtil::nextItem(bl, gs->getRandomGenerator());
auto spellID = SpellID(bonus->subtype);
const CSpell * spell = SpellID(spellID).toSpell();
bl.remove_if([&bonus](Bonus * b){return b==bonus;});
if (gs->curB->battleCanCastThisSpell(st, spell, ECastingMode::ENCHANTER_CASTING) == ESpellCastProblem::OK)
{
BattleSpellCastParameters parameters(gs->curB, st, spell);
parameters.spellLvl = bonus->val;
parameters.effectLevel = bonus->val;//todo: recheck
parameters.aimToHex(BattleHex::INVALID);
parameters.mode = ECastingMode::ENCHANTER_CASTING;
parameters.selectedStack = nullptr;
spell->battleCast(spellEnv, parameters);
//todo: move to mechanics
BattleSetStackProperty ssp;
ssp.which = BattleSetStackProperty::ENCHANTER_COUNTER;
ssp.absolute = false;
ssp.val = bonus->additionalInfo; //increase cooldown counter
ssp.stackID = st->ID;
sendAndApply(&ssp);
cast = true;
}
};
}
bl = *(st->getBonuses(Selector::type(Bonus::ENCHANTED)));
for (auto b : bl)
{
SetStackEffect sse;
int val = bl.valOfBonuses (Selector::typeSubtype(b->type, b->subtype));
if (val > 3)
{
for (auto s : gs->curB->battleGetAllStacks())
{
if (st->owner == s->owner && s->isValidTarget()) //all allied
sse.stacks.push_back (s->ID);
}
}
else
sse.stacks.push_back (st->ID);
Bonus pseudoBonus;
pseudoBonus.sid = b->subtype;
pseudoBonus.val = ((val > 3) ? (val - 3) : val);
pseudoBonus.turnsRemain = 50;
st->stackEffectToFeature (sse.effect, pseudoBonus);
if (sse.effect.size())
sendAndApply (&sse);
}
}
}
void CGameHandler::handleDamageFromObstacle(const CObstacleInstance &obstacle, const CStack * curStack)
{
//we want to determine following vars depending on obstacle type
int damage = -1;
int effect = -1;
bool oneTimeObstacle = false;
//helper info
const SpellCreatedObstacle *spellObstacle = dynamic_cast<const SpellCreatedObstacle*>(&obstacle); //not nice but we may need spell params
const ui8 side = !curStack->attackerOwned; //if enemy is defending (false = 0), side of enemy hero is 1 (true)
const CGHeroInstance *hero = gs->curB->battleGetFightingHero(side);//FIXME: there may be no hero - landmines in Tower
if(obstacle.obstacleType == CObstacleInstance::MOAT)
{
damage = battleGetMoatDmg();
}
else if(obstacle.obstacleType == CObstacleInstance::LAND_MINE)
{
//You don't get hit by a Mine you can see.
if(gs->curB->battleIsObstacleVisibleForSide(obstacle, (BattlePerspective::BattlePerspective)side))
return;
oneTimeObstacle = true;
effect = 82; //makes
const CSpell * sp = SpellID(SpellID::LAND_MINE).toSpell();
if(sp->isImmuneByStack(hero, curStack))
return;
damage = sp->calculateDamage(hero, curStack,
spellObstacle->spellLevel, spellObstacle->casterSpellPower);
//TODO even if obstacle wasn't created by hero (Tower "moat") it should deal dmg as if cast by hero,
//if it is bigger than default dmg. Or is it just irrelevant H3 implementation quirk
}
else if(obstacle.obstacleType == CObstacleInstance::FIRE_WALL)
{
const CSpell * sp = SpellID(SpellID::FIRE_WALL).toSpell();
if(sp->isImmuneByStack(hero, curStack))
return;
damage = sp->calculateDamage(hero, curStack,
spellObstacle->spellLevel, spellObstacle->casterSpellPower);
}
else
{
//no other obstacle does damage to stack
return;
}
BattleStackAttacked bsa;
if(effect >= 0)
{
bsa.flags |= BattleStackAttacked::EFFECT;
bsa.effect = effect; //makes POOF
}
bsa.damageAmount = damage;
bsa.stackAttacked = curStack->ID;
bsa.attackerID = -1;
curStack->prepareAttacked(bsa, gameState()->getRandomGenerator());
StacksInjured si;
si.stacks.push_back(bsa);
sendAndApply(&si);
if(oneTimeObstacle)
removeObstacle(obstacle);
}
void CGameHandler::handleTimeEvents()
{
gs->map->events.sort(evntCmp);
while(gs->map->events.size() && gs->map->events.front().firstOccurence+1 == gs->day)
{
CMapEvent ev = gs->map->events.front();
for (int player = 0; player < PlayerColor::PLAYER_LIMIT_I; player++)
{
auto color = PlayerColor(player);
PlayerState *pinfo = gs->getPlayer(color, false); //do not output error if player does not exist
if( pinfo //player exists
&& (ev.players & 1<<player) //event is enabled to this player
&& ((ev.computerAffected && !pinfo->human)
|| (ev.humanAffected && pinfo->human)
)
)
{
//give resources
SetResources sr;
sr.player = color;
sr.res = pinfo->resources + ev.resources;
//prepare dialog
InfoWindow iw;
iw.player = color;
iw.text << ev.message;
for (int i=0; i<ev.resources.size(); i++)
{
if(ev.resources.at(i)) //if resource is changed, we add it to the dialog
iw.components.push_back(Component(Component::RESOURCE,i,ev.resources.at(i),0));
}
if (iw.components.size())
{
sr.res.amax(0); // If removing too much resources, adjust the amount so the total doesn't become negative.
sendAndApply(&sr); //update player resources if changed
}
sendAndApply(&iw); //show dialog
}
} //PLAYERS LOOP
if(ev.nextOccurence)
{
gs->map->events.pop_front();
ev.firstOccurence += ev.nextOccurence;
auto it = gs->map->events.begin();
while ( it !=gs->map->events.end() && it->earlierThanOrEqual(ev))
it++;
gs->map->events.insert(it, ev);
}
else
{
gs->map->events.pop_front();
}
}
//TODO send only if changed
UpdateMapEvents ume;
ume.events = gs->map->events;
sendAndApply(&ume);
}
void CGameHandler::handleTownEvents(CGTownInstance * town, NewTurn &n)
{
town->events.sort(evntCmp);
while(town->events.size() && town->events.front().firstOccurence == gs->day)
{
PlayerColor player = town->tempOwner;
CCastleEvent ev = town->events.front();
PlayerState *pinfo = gs->getPlayer(player, false);
if( pinfo //player exists
&& (ev.players & 1<<player.getNum()) //event is enabled to this player
&& ((ev.computerAffected && !pinfo->human)
|| (ev.humanAffected && pinfo->human) ) )
{
// dialog
InfoWindow iw;
iw.player = player;
iw.text << ev.message;
if(ev.resources.nonZero())
{
TResources was = n.res[player];
n.res[player] += ev.resources;
n.res[player].amax(0);
for (int i=0; i<ev.resources.size(); i++)
if(ev.resources.at(i) && pinfo->resources.at(i) != n.res.at(player).at(i)) //if resource had changed, we add it to the dialog
iw.components.push_back(Component(Component::RESOURCE,i,n.res.at(player).at(i)-was.at(i),0));
}
for(auto & i : ev.buildings)
{
if(!town->hasBuilt(i))
{
buildStructure(town->id, i, true);
iw.components.push_back(Component(Component::BUILDING, town->subID, i, 0));
}
}
if (!ev.creatures.empty() && !vstd::contains(n.cres, town->id))
{
n.cres[town->id].tid = town->id;
n.cres[town->id].creatures = town->creatures;
}
auto & sac = n.cres[town->id];
for(si32 i=0;i<ev.creatures.size();i++) //creature growths
{
if(!town->creatures.at(i).second.empty() && ev.creatures.at(i) > 0)//there is dwelling
{
sac.creatures[i].first += ev.creatures.at(i);
iw.components.push_back(Component(Component::CREATURE,
town->creatures.at(i).second.back(), ev.creatures.at(i), 0));
}
}
sendAndApply(&iw); //show dialog
}
if(ev.nextOccurence)
{
town->events.pop_front();
ev.firstOccurence += ev.nextOccurence;
auto it = town->events.begin();
while ( it != town->events.end() && it->earlierThanOrEqual(ev))
it++;
town->events.insert(it, ev);
}
else
{
town->events.pop_front();
}
}
//TODO send only if changed
UpdateCastleEvents uce;
uce.town = town->id;
uce.events = town->events;
sendAndApply(&uce);
}
bool CGameHandler::complain( const std::string &problem )
{
sendMessageToAll("Server encountered a problem: " + problem);
logGlobal->errorStream() << problem;
return true;
}
void CGameHandler::showGarrisonDialog( ObjectInstanceID upobj, ObjectInstanceID hid, bool removableUnits)
{
//PlayerColor player = getOwner(hid);
auto upperArmy = dynamic_cast<const CArmedInstance*>(getObj(upobj));
auto lowerArmy = dynamic_cast<const CArmedInstance*>(getObj(hid));
assert(lowerArmy);
assert(upperArmy);
auto garrisonQuery = std::make_shared<CGarrisonDialogQuery>(upperArmy, lowerArmy);
queries.addQuery(garrisonQuery);
GarrisonDialog gd;
gd.hid = hid;
gd.objid = upobj;
gd.removableUnits = removableUnits;
gd.queryID = garrisonQuery->queryID;
sendAndApply(&gd);
}
void CGameHandler::showThievesGuildWindow(PlayerColor player, ObjectInstanceID requestingObjId)
{
OpenWindow ow;
ow.window = OpenWindow::THIEVES_GUILD;
ow.id1 = player.getNum();
ow.id2 = requestingObjId.getNum();
sendAndApply(&ow);
}
bool CGameHandler::isAllowedExchange( ObjectInstanceID id1, ObjectInstanceID id2 )
{
if(id1 == id2)
return true;
const CGObjectInstance *o1 = getObj(id1), *o2 = getObj(id2);
if(!o1 || !o2)
return true; //arranging stacks within an object should be always allowed
if (o1 && o2)
{
if(o1->ID == Obj::TOWN)
{
const CGTownInstance *t = static_cast<const CGTownInstance*>(o1);
if(t->visitingHero == o2 || t->garrisonHero == o2)
return true;
}
if(o2->ID == Obj::TOWN)
{
const CGTownInstance *t = static_cast<const CGTownInstance*>(o2);
if(t->visitingHero == o1 || t->garrisonHero == o1)
return true;
}
if (o1->ID == Obj::HERO && o2->ID == Obj::HERO)
{
const CGHeroInstance *h1 = static_cast<const CGHeroInstance*>(o1);
const CGHeroInstance *h2 = static_cast<const CGHeroInstance*>(o2);
// two heroes in same town (garrisoned and visiting)
if (h1->visitedTown != nullptr && h2->visitedTown != nullptr && h1->visitedTown == h2->visitedTown)
return true;
}
//Ongoing garrison exchange
if(auto dialog = std::dynamic_pointer_cast<CGarrisonDialogQuery>(queries.topQuery(o1->tempOwner)))
{
if(dialog->exchangingArmies.at(0) == o1 && dialog->exchangingArmies.at(1) == o2)
return true;
if(dialog->exchangingArmies.at(1) == o1 && dialog->exchangingArmies.at(0) == o2)
return true;
}
}
return false;
}
void CGameHandler::objectVisited( const CGObjectInstance * obj, const CGHeroInstance * h )
{
logGlobal->debugStream() << h->nodeName() << " visits " << obj->getObjectName() << "(" << obj->ID << ":" << obj->subID << ")";
auto visitQuery = std::make_shared<CObjectVisitQuery>(obj, h, obj->visitablePos());
queries.addQuery(visitQuery); //TODO real visit pos
HeroVisit hv;
hv.obj = obj;
hv.hero = h;
hv.player = h->tempOwner;
hv.starting = true;
sendAndApply(&hv);
obj->onHeroVisit(h);
queries.popIfTop(visitQuery); //visit ends here if no queries were created
}
void CGameHandler::objectVisitEnded(const CObjectVisitQuery &query)
{
logGlobal->traceStream() << query.visitingHero->nodeName() << " visit ends.\n";
HeroVisit hv;
hv.player = query.players.front();
hv.obj = nullptr; //not necessary, moreover may have been deleted in the meantime
hv.hero = query.visitingHero;
assert(hv.hero);
hv.starting = false;
sendAndApply(&hv);
}
bool CGameHandler::buildBoat( ObjectInstanceID objid )
{
const IShipyard *obj = IShipyard::castFrom(getObj(objid));
if(obj->shipyardStatus() != IBoatGenerator::GOOD)
{
complain("Cannot build boat in this shipyard!");
return false;
}
else if(obj->o->ID == Obj::TOWN
&& !static_cast<const CGTownInstance*>(obj)->hasBuilt(BuildingID::SHIPYARD))
{
complain("Cannot build boat in the town - no shipyard!");
return false;
}
const PlayerColor playerID = obj->o->tempOwner;
TResources boatCost;
obj->getBoatCost(boatCost);
TResources aviable = gs->getPlayer(playerID)->resources;
if (!aviable.canAfford(boatCost))
{
complain("Not enough resources to build a boat!");
return false;
}
int3 tile = obj->bestLocation();
if(!gs->map->isInTheMap(tile))
{
complain("Cannot find appropriate tile for a boat!");
return false;
}
//take boat cost
SetResources sr;
sr.player = playerID;
sr.res = (aviable - boatCost);
sendAndApply(&sr);
//create boat
NewObject no;
no.ID = Obj::BOAT;
no.subID = obj->getBoatType();
no.pos = tile + int3(1,0,0);
sendAndApply(&no);
return true;
}
void CGameHandler::engageIntoBattle( PlayerColor player )
{
//notify interfaces
PlayerBlocked pb;
pb.player = player;
pb.reason = PlayerBlocked::UPCOMING_BATTLE;
pb.startOrEnd = PlayerBlocked::BLOCKADE_STARTED;
sendAndApply(&pb);
}
void CGameHandler::checkVictoryLossConditions(const std::set<PlayerColor> & playerColors)
{
for(auto playerColor : playerColors)
{
if(gs->getPlayer(playerColor, false))
checkVictoryLossConditionsForPlayer(playerColor);
}
}
void CGameHandler::checkVictoryLossConditionsForAll()
{
std::set<PlayerColor> playerColors;
for(int i = 0; i < PlayerColor::PLAYER_LIMIT_I; ++i)
{
playerColors.insert(PlayerColor(i));
}
checkVictoryLossConditions(playerColors);
}
void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player)
{
const PlayerState *p = gs->getPlayer(player);
if(p->status != EPlayerStatus::INGAME) return;
auto victoryLossCheckResult = gs->checkForVictoryAndLoss(player);
if(victoryLossCheckResult.victory() || victoryLossCheckResult.loss())
{
InfoWindow iw;
getVictoryLossMessage(player, victoryLossCheckResult, iw);
sendAndApply(&iw);
PlayerEndsGame peg;
peg.player = player;
peg.victoryLossCheckResult = victoryLossCheckResult;
sendAndApply(&peg);
if(victoryLossCheckResult.victory())
{
//one player won -> all enemies lost
for (auto i = gs->players.cbegin(); i!=gs->players.cend(); i++)
{
if(i->first != player && gs->getPlayer(i->first)->status == EPlayerStatus::INGAME)
{
peg.player = i->first;
peg.victoryLossCheckResult = gameState()->getPlayerRelations(player, i->first) == PlayerRelations::ALLIES ?
victoryLossCheckResult : victoryLossCheckResult.invert(); // ally of winner
InfoWindow iw;
getVictoryLossMessage(player, peg.victoryLossCheckResult, iw);
iw.player = i->first;
sendAndApply(&iw);
sendAndApply(&peg);
}
}
if(p->human)
{
end2 = true;
if(gs->scenarioOps->campState)
{
std::vector<CGHeroInstance *> crossoverHeroes;
for(CGHeroInstance * hero : gs->map->heroesOnMap)
{
if(hero->tempOwner == player)
{
// keep all heroes from the winning player
crossoverHeroes.push_back(hero);
}
else if(vstd::contains(gs->scenarioOps->campState->getCurrentScenario().keepHeroes, HeroTypeID(hero->subID)))
{
// keep hero whether lost or won (like Xeron in AB campaign)
crossoverHeroes.push_back(hero);
}
}
// keep lost heroes which are in heroes pool
for(auto & heroPair : gs->hpool.heroesPool)
{
if(vstd::contains(gs->scenarioOps->campState->getCurrentScenario().keepHeroes, HeroTypeID(heroPair.first)))
{
crossoverHeroes.push_back(heroPair.second.get());
}
}
gs->scenarioOps->campState->setCurrentMapAsConquered(crossoverHeroes);
//Request clients to change connection mode
PrepareForAdvancingCampaign pfac;
sendAndApply(&pfac);
//Change connection mode
if(getPlayer(player)->human && getStartInfo()->campState)
{
for(auto connection : conns)
connection->prepareForSendingHeroes();
}
UpdateCampaignState ucs;
ucs.camp = gs->scenarioOps->campState;
sendAndApply(&ucs);
}
}
}
else
{
//copy heroes vector to avoid iterator invalidation as removal change PlayerState
auto hlp = p->heroes;
for(auto h : hlp) //eliminate heroes
{
if(h.get())
removeObject(h);
}
//player lost -> all his objects become unflagged (neutral)
for (auto obj : gs->map->objects) //unflag objs
{
if(obj.get() && obj->tempOwner == player)
setOwner(obj, PlayerColor::NEUTRAL);
}
//eliminating one player may cause victory of another:
std::set<PlayerColor> playerColors;
//do not copy player state (CBonusSystemNode) by value
for (auto &p : gs->players) //players may have different colors, iterate over players and not integers
{
if (p.first != player)
playerColors.insert(p.first);
}
//notify all players
for (auto pc : playerColors)
{
if (gs->getPlayer(pc)->status == EPlayerStatus::INGAME)
{
InfoWindow iw;
getVictoryLossMessage(player, victoryLossCheckResult.invert(), iw);
iw.player = pc;
sendAndApply(&iw);
}
}
checkVictoryLossConditions(playerColors);
}
auto playerInfo = gs->getPlayer(gs->currentPlayer, false);
// If we are called before the actual game start, there might be no current player
if (playerInfo && playerInfo->status != EPlayerStatus::INGAME)
{
// If player making turn has lost his turn must be over as well
states.setFlag(gs->currentPlayer, &PlayerStatus::makingTurn, false);
}
}
}
void CGameHandler::getVictoryLossMessage(PlayerColor player, const EVictoryLossCheckResult & victoryLossCheckResult, InfoWindow & out) const
{
out.player = player;
out.text.clear();
out.text << victoryLossCheckResult.messageToSelf;
// hackish, insert one player-specific string, if applicable
if (victoryLossCheckResult.messageToSelf.find("%s") != std::string::npos)
out.text.addReplacement(MetaString::COLOR, player.getNum());
out.components.push_back(Component(Component::FLAG, player.getNum(), 0, 0));
}
bool CGameHandler::dig( const CGHeroInstance *h )
{
for (auto i = gs->map->objects.cbegin(); i != gs->map->objects.cend(); i++) //unflag objs
{
if(*i && (*i)->ID == Obj::HOLE && (*i)->pos == h->getPosition())
{
complain("Cannot dig - there is already a hole under the hero!");
return false;
}
}
if(h->diggingStatus() != EDiggingStatus::CAN_DIG) //checks for terrain and movement
COMPLAIN_RETF("Hero cannot dig (error code %d)!", h->diggingStatus());
//create a hole
NewObject no;
no.ID = Obj::HOLE;
no.pos = h->getPosition();
no.subID = 0;
sendAndApply(&no);
//take MPs
SetMovePoints smp;
smp.hid = h->id;
smp.val = 0;
sendAndApply(&smp);
InfoWindow iw;
iw.player = h->tempOwner;
if(gs->map->grailPos == h->getPosition())
{
iw.text.addTxt(MetaString::GENERAL_TXT, 58); //"Congratulations! After spending many hours digging here, your hero has uncovered the "
iw.text.addTxt(MetaString::ART_NAMES, ArtifactID::GRAIL);
iw.soundID = soundBase::ULTIMATEARTIFACT;
giveHeroNewArtifact(h, VLC->arth->artifacts[ArtifactID::GRAIL], ArtifactPosition::PRE_FIRST); //give grail
sendAndApply(&iw);
iw.soundID = soundBase::invalid;
iw.text.clear();
iw.text.addTxt(MetaString::ART_DESCR, ArtifactID::GRAIL);
sendAndApply(&iw);
}
else
{
iw.text.addTxt(MetaString::GENERAL_TXT, 59); //"Nothing here. \n Where could it be?"
iw.soundID = soundBase::Dig;
sendAndApply(&iw);
}
return true;
}
void CGameHandler::attackCasting(const BattleAttack & bat, Bonus::BonusType attackMode, const CStack * attacker)
{
if(attacker->hasBonusOfType(attackMode))
{
std::set<SpellID> spellsToCast;
TBonusListPtr spells = attacker->getBonuses(Selector::type(attackMode));
for(const Bonus *sf : *spells)
{
spellsToCast.insert (SpellID(sf->subtype));
}
for(SpellID spellID : spellsToCast)
{
const CStack * oneOfAttacked = nullptr;
for (auto & elem : bat.bsa)
{
if (elem.newAmount > 0 && !elem.isSecondary()) //apply effects only to first target stack if it's alive
{
oneOfAttacked = gs->curB->battleGetStackByID(elem.stackAttacked);
break;
}
}
bool castMe = false;
if(oneOfAttacked == nullptr) //all attacked creatures have been killed
return;
int spellLevel = 0;
TBonusListPtr spellsByType = attacker->getBonuses(Selector::typeSubtype(attackMode, spellID));
for(const Bonus *sf : *spellsByType)
{
vstd::amax(spellLevel, sf->additionalInfo % 1000); //pick highest level
int meleeRanged = sf->additionalInfo / 1000;
if (meleeRanged == 0 || (meleeRanged == 1 && bat.shot()) || (meleeRanged == 2 && !bat.shot()))
castMe = true;
}
int chance = attacker->valOfBonuses((Selector::typeSubtype(attackMode, spellID)));
vstd::amin (chance, 100);
const CSpell * spell = SpellID(spellID).toSpell();
if(gs->curB->battleCanCastThisSpellHere(attacker, spell, ECastingMode::AFTER_ATTACK_CASTING, oneOfAttacked->position) != ESpellCastProblem::OK)
continue;
//check if spell should be cast (probability handling)
if(gs->getRandomGenerator().nextInt(99) >= chance)
continue;
//casting
if (castMe) //stacks use 0 spell power. If needed, default = 3 or custom value is used
{
BattleSpellCastParameters parameters(gs->curB, attacker, spell);
parameters.spellLvl = spellLevel;
parameters.effectLevel = spellLevel;
parameters.aimToStack(oneOfAttacked);
parameters.mode = ECastingMode::AFTER_ATTACK_CASTING;
parameters.selectedStack = nullptr;
spell->battleCast(spellEnv, parameters);
}
}
}
}
void CGameHandler::handleAttackBeforeCasting (const BattleAttack & bat)
{
const CStack * attacker = gs->curB->battleGetStackByID(bat.stackAttacking);
attackCasting(bat, Bonus::SPELL_BEFORE_ATTACK, attacker); //no death stare / acid breath needed?
}
void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat )
{
const CStack * attacker = gs->curB->battleGetStackByID(bat.stackAttacking);
if (!attacker) //could be already dead
return;
auto cast = [=](SpellID spellID, int power)
{
const CSpell * spell = SpellID(spellID).toSpell();
BattleSpellCastParameters parameters(gs->curB, attacker, spell);
parameters.spellLvl = 0;
parameters.effectLevel = 0;
parameters.aimToStack(gs->curB->battleGetStackByID(bat.bsa.at(0).stackAttacked));
parameters.effectPower = power;
parameters.mode = ECastingMode::AFTER_ATTACK_CASTING;
parameters.selectedStack = nullptr;
spell->battleCast(this->spellEnv, parameters);
};
attackCasting(bat, Bonus::SPELL_AFTER_ATTACK, attacker);
if(bat.bsa.at(0).newAmount <= 0)
{
//don't try death stare or acid breath on dead stack (crash!)
return;
}
if (attacker->hasBonusOfType(Bonus::DEATH_STARE) && bat.bsa.size())
{
// mechanics of Death Stare as in H3:
// each gorgon have 10% chance to kill (counted separately in H3) -> binomial distribution
//original formula x = min(x, (gorgons_count + 9)/10);
double chanceToKill = attacker->valOfBonuses(Bonus::DEATH_STARE, 0) / 100.0f;
vstd::amin(chanceToKill, 1); //cap at 100%
std::binomial_distribution<> distribution(attacker->count, chanceToKill);
std::mt19937 rng(std::time(nullptr));
int staredCreatures = distribution(rng);
double cap = 1 / std::max(chanceToKill, (double)(0.01));//don't divide by 0
int maxToKill = (attacker->count + cap - 1) / cap; //not much more than chance * count
vstd::amin(staredCreatures, maxToKill);
staredCreatures += (attacker->level() * attacker->valOfBonuses(Bonus::DEATH_STARE, 1)) / gs->curB->battleGetStackByID(bat.bsa.at(0).stackAttacked)->level();
if (staredCreatures)
{
if (bat.bsa.at(0).newAmount > 0) //TODO: death stare was not originally available for multiple-hex attacks, but...
cast(SpellID::DEATH_STARE, staredCreatures);
}
}
int acidDamage = 0;
TBonusListPtr acidBreath = attacker->getBonuses(Selector::type(Bonus::ACID_BREATH));
for(const Bonus *b : *acidBreath)
{
if (b->additionalInfo > gs->getRandomGenerator().nextInt(99))
acidDamage += b->val;
}
if (acidDamage)
{
cast(SpellID::ACID_BREATH_DAMAGE, acidDamage * attacker->count);
}
}
bool CGameHandler::castSpell(const CGHeroInstance *h, SpellID spellID, const int3 &pos)
{
const CSpell *s = spellID.toSpell();
AdventureSpellCastParameters p;
p.caster = h;
p.pos = pos;
return s->adventureCast(spellEnv, p);
}
void CGameHandler::visitObjectOnTile(const TerrainTile &t, const CGHeroInstance * h)
{
if (!t.visitableObjects.empty())
{
//to prevent self-visiting heroes on space press
if(t.visitableObjects.back() != h)
objectVisited(t.visitableObjects.back(), h);
else if(t.visitableObjects.size() > 1)
objectVisited(*(t.visitableObjects.end()-2),h);
}
}
bool CGameHandler::sacrificeCreatures(const IMarket *market, const CGHeroInstance *hero, SlotID slot, ui32 count)
{
int oldCount = hero->getStackCount(slot);
if(oldCount < count)
COMPLAIN_RET("Not enough creatures to sacrifice!")
else if(oldCount == count && hero->stacksCount() == 1 && hero->needsLastStack())
COMPLAIN_RET("Cannot sacrifice last creature!");
int crid = hero->getStack(slot).type->idNumber;
changeStackCount(StackLocation(hero, slot), -count);
int dump, exp;
market->getOffer(crid, 0, dump, exp, EMarketMode::CREATURE_EXP);
exp *= count;
changePrimSkill(hero, PrimarySkill::EXPERIENCE, hero->calculateXp(exp));
return true;
}
bool CGameHandler::sacrificeArtifact(const IMarket * m, const CGHeroInstance * hero, ArtifactPosition slot)
{
ArtifactLocation al(hero, slot);
const CArtifactInstance *a = al.getArt();
if(!a)
COMPLAIN_RET("Cannot find artifact to sacrifice!");
int dmp, expToGive;
m->getOffer(hero->getArtTypeId(slot), 0, dmp, expToGive, EMarketMode::ARTIFACT_EXP);
removeArtifact(al);
changePrimSkill(hero, PrimarySkill::EXPERIENCE, expToGive);
return true;
}
void CGameHandler::makeStackDoNothing(const CStack * next)
{
BattleAction doNothing;
doNothing.actionType = Battle::NO_ACTION;
doNothing.additionalInfo = 0;
doNothing.destinationTile = -1;
doNothing.side = !next->attackerOwned;
doNothing.stackNumber = next->ID;
makeAutomaticAction(next, doNothing);
}
bool CGameHandler::insertNewStack(const StackLocation &sl, const CCreature *c, TQuantity count)
{
if(sl.army->hasStackAtSlot(sl.slot))
COMPLAIN_RET("Slot is already taken!");
if(!sl.slot.validSlot())
COMPLAIN_RET("Cannot insert stack to that slot!");
InsertNewStack ins;
ins.sl = sl;
ins.stack = CStackBasicDescriptor(c, count);
sendAndApply(&ins);
return true;
}
bool CGameHandler::eraseStack(const StackLocation &sl, bool forceRemoval/* = false*/)
{
if(!sl.army->hasStackAtSlot(sl.slot))
COMPLAIN_RET("Cannot find a stack to erase");
if(sl.army->stacksCount() == 1 //from the last stack
&& sl.army->needsLastStack() //that must be left
&& !forceRemoval) //ignore above conditions if we are forcing removal
{
COMPLAIN_RET("Cannot erase the last stack!");
}
EraseStack es;
es.sl = sl;
sendAndApply(&es);
return true;
}
bool CGameHandler::changeStackCount(const StackLocation &sl, TQuantity count, bool absoluteValue /*= false*/)
{
TQuantity currentCount = sl.army->getStackCount(sl.slot);
if((absoluteValue && count < 0)
|| (!absoluteValue && -count > currentCount))
{
COMPLAIN_RET("Cannot take more stacks than present!");
}
if((currentCount == -count && !absoluteValue)
|| (!count && absoluteValue))
{
eraseStack(sl);
}
else
{
ChangeStackCount csc;
csc.sl = sl;
csc.count = count;
csc.absoluteValue = absoluteValue;
sendAndApply(&csc);
}
return true;
}
bool CGameHandler::addToSlot(const StackLocation &sl, const CCreature *c, TQuantity count)
{
const CCreature *slotC = sl.army->getCreature(sl.slot);
if(!slotC) //slot is empty
insertNewStack(sl, c, count);
else if(c == slotC)
changeStackCount(sl, count);
else
{
COMPLAIN_RET("Cannot add " + c->namePl + " to slot " + boost::lexical_cast<std::string>(sl.slot) + "!");
}
return true;
}
void CGameHandler::tryJoiningArmy(const CArmedInstance *src, const CArmedInstance *dst, bool removeObjWhenFinished, bool allowMerging)
{
if(removeObjWhenFinished)
removeAfterVisit(src);
if(!src->canBeMergedWith(*dst, allowMerging))
{
if (allowMerging) //do that, add all matching creatures.
{
bool cont = true;
while (cont)
{
for(auto i = src->stacks.begin(); i != src->stacks.end(); i++)//while there are unmoved creatures
{
SlotID pos = dst->getSlotFor(i->second->type);
if(pos.validSlot())
{
moveStack(StackLocation(src, i->first), StackLocation(dst, pos));
cont = true;
break; //or iterator crashes
}
cont = false;
}
}
}
showGarrisonDialog(src->id, dst->id, true); //show garrison window and optionally remove ourselves from map when player ends
}
else //merge
{
moveArmy(src, dst, allowMerging);
}
}
bool CGameHandler::moveStack(const StackLocation &src, const StackLocation &dst, TQuantity count)
{
if(!src.army->hasStackAtSlot(src.slot))
COMPLAIN_RET("No stack to move!");
if(dst.army->hasStackAtSlot(dst.slot) && dst.army->getCreature(dst.slot) != src.army->getCreature(src.slot))
COMPLAIN_RET("Cannot move: stack of different type at destination pos!");
if(!dst.slot.validSlot())
COMPLAIN_RET("Cannot move stack to that slot!");
if(count == -1)
{
count = src.army->getStackCount(src.slot);
}
if(src.army != dst.army //moving away
&& count == src.army->getStackCount(src.slot) //all creatures
&& src.army->stacksCount() == 1 //from the last stack
&& src.army->needsLastStack()) //that must be left
{
COMPLAIN_RET("Cannot move away the last creature!");
}
RebalanceStacks rs;
rs.src = src;
rs.dst = dst;
rs.count = count;
sendAndApply(&rs);
return true;
}
bool CGameHandler::swapStacks(const StackLocation &sl1, const StackLocation &sl2)
{
if(!sl1.army->hasStackAtSlot(sl1.slot))
return moveStack(sl2, sl1);
else if(!sl2.army->hasStackAtSlot(sl2.slot))
return moveStack(sl1, sl2);
else
{
SwapStacks ss;
ss.sl1 = sl1;
ss.sl2 = sl2;
sendAndApply(&ss);
return true;
}
}
void CGameHandler::runBattle()
{
setBattle(gs->curB);
assert(gs->curB);
//TODO: pre-tactic stuff, call scripts etc.
//tactic round
{
while(gs->curB->tacticDistance && !battleResult.get())
boost::this_thread::sleep(boost::posix_time::milliseconds(50));
}
//spells opening battle
for(int i = 0; i < 2; ++i)
{
auto h = gs->curB->battleGetFightingHero(i);
if(h && h->hasBonusOfType(Bonus::OPENING_BATTLE_SPELL))
{
TBonusListPtr bl = h->getBonuses(Selector::type(Bonus::OPENING_BATTLE_SPELL));
for (Bonus *b : *bl)
{
const CSpell * spell = SpellID(b->subtype).toSpell();
BattleSpellCastParameters parameters(gs->curB, h, spell);
parameters.spellLvl = 3;
parameters.effectLevel = 3;
parameters.aimToHex(BattleHex::INVALID);
parameters.mode = ECastingMode::PASSIVE_CASTING;
parameters.selectedStack = nullptr;
parameters.enchantPower = b->val;
spell->battleCast(spellEnv, parameters);
}
}
}
//main loop
while(!battleResult.get()) //till the end of the battle ;]
{
NEW_ROUND;
auto obstacles = gs->curB->obstacles; //we copy container, because we're going to modify it
for(auto &obstPtr : obstacles)
{
if(const SpellCreatedObstacle *sco = dynamic_cast<const SpellCreatedObstacle *>(obstPtr.get()))
if(sco->turnsRemaining == 0)
removeObstacle(*obstPtr);
}
const BattleInfo & curB = *gs->curB;
//remove clones after all mechanics and animations are handled!
std::set <const CStack*> stacksToRemove;
for (auto stack : curB.stacks)
{
if (stack->idDeadClone())
stacksToRemove.insert(stack);
}
for (auto stack : stacksToRemove)
{
BattleStacksRemoved bsr;
bsr.stackIDs.insert(stack->ID);
sendAndApply(&bsr);
}
//stack loop
const CStack *next;
while(!battleResult.get() && (next = curB.getNextStack()) && next->willMove())
{
//check for bad morale => freeze
int nextStackMorale = next->MoraleVal();
if( nextStackMorale < 0 &&
!(NBonus::hasOfType(gs->curB->battleGetFightingHero(0), Bonus::BLOCK_MORALE)
|| NBonus::hasOfType(gs->curB->battleGetFightingHero(1), Bonus::BLOCK_MORALE)) //checking if gs->curB->heroes have (or don't have) morale blocking bonuses)
)
{
if(gs->getRandomGenerator().nextInt(23) < -2 * nextStackMorale)
{
//unit loses its turn - empty freeze action
BattleAction ba;
ba.actionType = Battle::BAD_MORALE;
ba.additionalInfo = 1;
ba.side = !next->attackerOwned;
ba.stackNumber = next->ID;
makeAutomaticAction(next, ba);
continue;
}
}
if(next->hasBonusOfType(Bonus::ATTACKS_NEAREST_CREATURE)) //while in berserk
{ //fixme: stack should not attack itself
std::pair<const CStack *, int> attackInfo = curB.getNearestStack(next, boost::logic::indeterminate);
if(attackInfo.first != nullptr)
{
BattleAction attack;
attack.actionType = Battle::WALK_AND_ATTACK;
attack.side = !next->attackerOwned;
attack.stackNumber = next->ID;
attack.additionalInfo = attackInfo.first->position;
attack.destinationTile = attackInfo.second;
makeAutomaticAction(next, attack);
}
else
{
makeStackDoNothing(next);
}
continue;
}
const CGHeroInstance * curOwner = gs->curB->battleGetOwner(next);
if( (next->position < 0 || next->getCreature()->idNumber == CreatureID::BALLISTA) //arrow turret or ballista
&& (!curOwner || curOwner->getSecSkillLevel(SecondarySkill::ARTILLERY) == 0)) //hero has no artillery
{
BattleAction attack;
attack.actionType = Battle::SHOOT;
attack.side = !next->attackerOwned;
attack.stackNumber = next->ID;
for(auto & elem : gs->curB->stacks)
{
if(elem->owner != next->owner && elem->isValidTarget())
{
attack.destinationTile = elem->position;
break;
}
}
makeAutomaticAction(next, attack);
continue;
}
if(next->getCreature()->idNumber == CreatureID::CATAPULT)
{
const auto & attackableBattleHexes = curB.getAttackableBattleHexes();
if(attackableBattleHexes.empty())
{
makeStackDoNothing(next);
continue;
}
if(!curOwner || curOwner->getSecSkillLevel(SecondarySkill::BALLISTICS) == 0)
{
BattleAction attack;
attack.destinationTile = *RandomGeneratorUtil::nextItem(attackableBattleHexes,
gs->getRandomGenerator());
attack.actionType = Battle::CATAPULT;
attack.additionalInfo = 0;
attack.side = !next->attackerOwned;
attack.stackNumber = next->ID;
makeAutomaticAction(next, attack);
continue;
}
}
if(next->getCreature()->idNumber == CreatureID::FIRST_AID_TENT)
{
TStacks possibleStacks = battleGetStacksIf([&](const CStack * s){
return s->owner == next->owner && s->canBeHealed();
});
if(!possibleStacks.size())
{
makeStackDoNothing(next);
continue;
}
if(!curOwner || curOwner->getSecSkillLevel(SecondarySkill::FIRST_AID) == 0) //no hero or hero has no first aid
{
range::random_shuffle(possibleStacks);
const CStack * toBeHealed = possibleStacks.front();
BattleAction heal;
heal.actionType = Battle::STACK_HEAL;
heal.additionalInfo = 0;
heal.destinationTile = toBeHealed->position;
heal.side = !next->attackerOwned;
heal.stackNumber = next->ID;
makeAutomaticAction(next, heal);
continue;
}
}
int numberOfAsks = 1;
bool breakOuter = false;
do
{//ask interface and wait for answer
if(!battleResult.get())
{
stackTurnTrigger(next); //various effects
if (vstd::contains(next->state, EBattleStackState::FEAR))
{
makeStackDoNothing(next); //end immediately if stack was affected by fear
}
else
{
logGlobal->traceStream() << "Activating " << next->nodeName();
auto nextId = next->ID;
BattleSetActiveStack sas;
sas.stack = nextId;
sendAndApply(&sas);
auto actionWasMade = [&]() -> bool
{
if(battleMadeAction.data)//active stack has made its action
return true;
if(battleResult.get())// battle is finished
return true;
if(next == nullptr)//active stack was been removed
return true;
return !next->alive();//active stack is dead
};
boost::unique_lock<boost::mutex> lock(battleMadeAction.mx);
battleMadeAction.data = false;
while(!actionWasMade())
{
battleMadeAction.cond.wait(lock);
if(battleGetStackByID(nextId, false) != next)
next = nullptr; //it may be removed, while we wait
}
}
}
if(battleResult.get()) //don't touch it, battle could be finished while waiting got action
{
breakOuter = true;
break;
}
//we're after action, all results applied
checkForBattleEnd(); //check if this action ended the battle
if(next != nullptr)
{
//check for good morale
nextStackMorale = next->MoraleVal();
if(!vstd::contains(next->state,EBattleStackState::HAD_MORALE) //only one extra move per turn possible
&& !vstd::contains(next->state,EBattleStackState::DEFENDING)
&& !next->waited()
&& !vstd::contains(next->state, EBattleStackState::FEAR)
&& next->alive()
&& nextStackMorale > 0
&& !(NBonus::hasOfType(gs->curB->battleGetFightingHero(0), Bonus::BLOCK_MORALE)
|| NBonus::hasOfType(gs->curB->battleGetFightingHero(1), Bonus::BLOCK_MORALE)) //checking if gs->curB->heroes have (or don't have) morale blocking bonuses
)
{
if(gs->getRandomGenerator().nextInt(23) < nextStackMorale) //this stack hasn't got morale this turn
{
BattleTriggerEffect bte;
bte.stackID = next->ID;
bte.effect = Bonus::MORALE;
bte.val = 1;
bte.additionalInfo = 0;
sendAndApply(&bte); //play animation
++numberOfAsks; //move this stack once more
}
}
}
--numberOfAsks;
} while (numberOfAsks > 0);
if (breakOuter)
{
break;
}
}
}
endBattle(gs->curB->tile, gs->curB->battleGetFightingHero(0), gs->curB->battleGetFightingHero(1));
}
bool CGameHandler::makeAutomaticAction(const CStack *stack, BattleAction &ba)
{
BattleSetActiveStack bsa;
bsa.stack = stack->ID;
bsa.askPlayerInterface = false;
sendAndApply(&bsa);
bool ret = makeBattleAction(ba);
checkForBattleEnd();
return ret;
}
void CGameHandler::giveHeroArtifact(const CGHeroInstance *h, const CArtifactInstance *a, ArtifactPosition pos)
{
assert(a->artType);
ArtifactLocation al;
al.artHolder = const_cast<CGHeroInstance*>(h);
ArtifactPosition slot = ArtifactPosition::PRE_FIRST;
if(pos < 0)
{
if(pos == ArtifactPosition::FIRST_AVAILABLE)
slot = a->firstAvailableSlot(h);
else
slot = a->firstBackpackSlot(h);
}
else
{
slot = pos;
}
al.slot = slot;
if(slot < 0 || !a->canBePutAt(al))
{
complain("Cannot put artifact in that slot!");
return;
}
putArtifact(al, a);
}
void CGameHandler::putArtifact(const ArtifactLocation &al, const CArtifactInstance *a)
{
PutArtifact pa;
pa.art = a;
pa.al = al;
sendAndApply(&pa);
}
void CGameHandler::giveHeroNewArtifact(const CGHeroInstance *h, const CArtifact *artType, ArtifactPosition pos)
{
CArtifactInstance *a = nullptr;
if(!artType->constituents)
{
a = new CArtifactInstance();
}
else
{
a = new CCombinedArtifactInstance();
}
a->artType = artType; //*NOT* via settype -> all bonus-related stuff must be done by NewArtifact apply
NewArtifact na;
na.art = a;
sendAndApply(&na); // -> updates a!!!, will create a on other machines
giveHeroArtifact(h, a, pos);
}
void CGameHandler::setBattleResult(BattleResult::EResult resultType, int victoriusSide)
{
if(battleResult.get())
{
complain("There is already set result?");
return;
}
auto br = new BattleResult;
br->result = resultType;
br->winner = victoriusSide; //surrendering side loses
gs->curB->calculateCasualties(br->casualties);
battleResult.set(br);
}
void CGameHandler::commitPackage( CPackForClient *pack )
{
sendAndApply(pack);
}
void CGameHandler::spawnWanderingMonsters(CreatureID creatureID)
{
std::vector<int3>::iterator tile;
std::vector<int3> tiles;
getFreeTiles(tiles);
ui32 amount = tiles.size() / 200; //Chance is 0.5% for each tile
std::random_shuffle(tiles.begin(), tiles.end());
logGlobal->traceStream() << "Spawning wandering monsters. Found " << tiles.size() << " free tiles. Creature type: " << creatureID;
const CCreature *cre = VLC->creh->creatures.at(creatureID);
for (int i = 0; i < amount; ++i)
{
tile = tiles.begin();
logGlobal->traceStream() << "\tSpawning monster at " << *tile;
putNewMonster(creatureID, cre->getRandomAmount(std::rand), *tile);
tiles.erase(tile); //not use it again
}
}
void CGameHandler::removeObstacle(const CObstacleInstance &obstacle)
{
ObstaclesRemoved obsRem;
obsRem.obstacles.insert(obstacle.uniqueID);
sendAndApply(&obsRem);
}
void CGameHandler::synchronizeArtifactHandlerLists()
{
UpdateArtHandlerLists uahl;
uahl.treasures = VLC->arth->treasures;
uahl.minors = VLC->arth->minors;
uahl.majors = VLC->arth->majors;
uahl.relics = VLC->arth->relics;
sendAndApply(&uahl);
}
bool CGameHandler::isValidObject(const CGObjectInstance *obj) const
{
return vstd::contains(gs->map->objects, obj);
}
bool CGameHandler::isBlockedByQueries(const CPack *pack, PlayerColor player)
{
if(!strcmp(typeid(*pack).name(), typeid(PlayerMessage).name()))
return false;
auto query = queries.topQuery(player);
if(query && query->blocksPack(pack))
{
complain(boost::str(boost::format("Player %s has to answer queries before attempting any further actions. Top query is %s!") % player % query->toString()));
return true;
}
return false;
}
void CGameHandler::removeAfterVisit(const CGObjectInstance *object)
{
//If the object is being visited, there must be a matching query
for(const auto &query : queries.allQueries())
{
if(auto someVistQuery = std::dynamic_pointer_cast<CObjectVisitQuery>(query))
{
if(someVistQuery->visitedObject == object)
{
someVistQuery->removeObjectAfterVisit = true;
return;
}
}
};
//If we haven't returned so far, there is no query and no visit, call was wrong
assert("This function needs to be called during the object visit!");
}
void CGameHandler::changeFogOfWar(int3 center, ui32 radius, PlayerColor player, bool hide)
{
std::unordered_set<int3, ShashInt3> tiles;
getTilesInRange(tiles, center, radius, player, hide? -1 : 1);
if (hide)
{
std::unordered_set<int3, ShashInt3> observedTiles; //do not hide tiles observed by heroes. May lead to disastrous AI problems
auto p = gs->getPlayer(player);
for (auto h : p->heroes)
{
getTilesInRange(observedTiles, h->getSightCenter(), h->getSightRadious(), h->tempOwner, -1);
}
for (auto t : p->towns)
{
getTilesInRange(observedTiles, t->getSightCenter(), t->getSightRadious(), t->tempOwner, -1);
}
for (auto tile : observedTiles)
vstd::erase_if_present (tiles, tile);
}
changeFogOfWar(tiles, player, hide);
}
void CGameHandler::changeFogOfWar(std::unordered_set<int3, ShashInt3> &tiles, PlayerColor player, bool hide)
{
FoWChange fow;
fow.tiles = tiles;
fow.player = player;
fow.mode = hide? 0 : 1;
sendAndApply(&fow);
}
bool CGameHandler::isVisitCoveredByAnotherQuery(const CGObjectInstance *obj, const CGHeroInstance *hero)
{
if(auto topQuery = queries.topQuery(hero->getOwner()))
if(auto visit = std::dynamic_pointer_cast<const CObjectVisitQuery>(topQuery))
return !(visit->visitedObject == obj && visit->visitingHero == hero);
return true;
}
void CGameHandler::duelFinished()
{
auto si = getStartInfo();
auto getName = [&](int i){ return si->getIthPlayersSettings(gs->curB->sides.at(i).color).name; };
int casualtiesPoints = 0;
logGlobal->debugStream() << boost::format("Winner side %d\nWinner casualties:")
% (int)battleResult.data->winner;
for(auto & elem : battleResult.data->casualties[battleResult.data->winner])
{
const CCreature *c = VLC->creh->creatures[elem.first];
logGlobal->debugStream() << boost::format("\t* %d of %s") % elem.second % c->namePl;
casualtiesPoints += c->AIValue * elem.second;
}
logGlobal->debugStream() << boost::format("Total casualties points: %d") % casualtiesPoints;
time_t timeNow;
time(&timeNow);
std::ofstream out(cmdLineOptions["resultsFile"].as<std::string>(), std::ios::app);
if(out)
{
out << boost::format("%s\t%s\t%s\t%d\t%d\t%d\t%s\n") % si->mapname % getName(0) % getName(1)
% battleResult.data->winner % battleResult.data->result % casualtiesPoints
% asctime(localtime(&timeNow));
}
else
{
logGlobal->errorStream() << "Cannot open to write " << cmdLineOptions["resultsFile"].as<std::string>();
}
CSaveFile resultFile("result.vdrst");
resultFile << *battleResult.data;
BattleResultsApplied resultsApplied;
resultsApplied.player1 = finishingBattle->victor;
resultsApplied.player2 = finishingBattle->loser;
sendAndApply(&resultsApplied);
return;
}
CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance *army, BattleInfo *bat)
{
heroWithDeadCommander = ObjectInstanceID();
PlayerColor color = army->tempOwner;
if(color == PlayerColor::UNFLAGGABLE)
color = PlayerColor::NEUTRAL;
for(CStack *st : bat->stacks)
{
if(vstd::contains(st->state, EBattleStackState::SUMMONED)) //don't take into account summoned stacks
continue;
if (st->owner != color) //remove only our stacks
continue;
//FIXME: this info is also used in BattleInfo::calculateCasualties, refactor
st->count = std::max (0, st->count - st->resurrected);
if (!st->count && !st->base) //we can imagine stacks of war mahcines that are not spawned by artifacts?
{
auto warMachine = VLC->arth->creatureToMachineID(st->type->idNumber);
//catapult artifact remain even if "creature" killed in siege
if(warMachine != ArtifactID::NONE && warMachine != ArtifactID::CATAPULT)
{
auto hero = dynamic_ptr_cast<CGHeroInstance> (army);
if (hero)
removedWarMachines.push_back (ArtifactLocation(hero, hero->getArtPos(warMachine, true)));
}
}
if(!army->slotEmpty(st->slot) && st->count < army->getStackCount(st->slot))
{
StackLocation sl(army, st->slot);
if(st->alive())
newStackCounts.push_back(std::pair<StackLocation, int>(sl, st->count));
else
newStackCounts.push_back(std::pair<StackLocation, int>(sl, 0));
}
if (st->base && !st->count)
{
auto c = dynamic_cast <const CCommanderInstance *>(st->base);
if (c) //switch commander status to dead
{
auto h = dynamic_cast <const CGHeroInstance *>(army);
if (h && h->commander == c)
heroWithDeadCommander = army->id; //TODO: unify commander handling
}
}
}
}
void CasualtiesAfterBattle::takeFromArmy(CGameHandler *gh)
{
for(TStackAndItsNewCount &ncount : newStackCounts)
{
if(ncount.second > 0)
gh->changeStackCount(ncount.first, ncount.second, true);
else
gh->eraseStack(ncount.first, true);
}
for (auto al : removedWarMachines)
{
gh->removeArtifact(al);
}
if (heroWithDeadCommander != ObjectInstanceID())
{
SetCommanderProperty scp;
scp.heroid = heroWithDeadCommander;
scp.which = SetCommanderProperty::ALIVE;
scp.amount = 0;
gh->sendAndApply (&scp);
}
}
CGameHandler::FinishingBattleHelper::FinishingBattleHelper(std::shared_ptr<const CBattleQuery> Query, bool Duel, int RemainingBattleQueriesCount)
{
assert(Query->result);
assert(Query->bi);
auto &result = *Query->result;
auto &info = *Query->bi;
winnerHero = result.winner != 0 ? info.sides[1].hero : info.sides[0].hero;
loserHero = result.winner != 0 ? info.sides[0].hero : info.sides[1].hero;
victor = info.sides[result.winner].color;
loser = info.sides[!result.winner].color;
duel = Duel;
remainingBattleQueriesCount = RemainingBattleQueriesCount;
}
CGameHandler::FinishingBattleHelper::FinishingBattleHelper()
{
winnerHero = loserHero = nullptr;
}
///ServerSpellCastEnvironment
ServerSpellCastEnvironment::ServerSpellCastEnvironment(CGameHandler * gh): gh(gh)
{
}
void ServerSpellCastEnvironment::sendAndApply(CPackForClient * info) const
{
gh->sendAndApply(info);
}
CRandomGenerator & ServerSpellCastEnvironment::getRandomGenerator() const
{
return gh->gameState()->getRandomGenerator();
}
void ServerSpellCastEnvironment::complain(const std::string& problem) const
{
gh->complain(problem);
}
const CGameInfoCallback * ServerSpellCastEnvironment::getCb() const
{
return gh;
}
const CMap * ServerSpellCastEnvironment::getMap() const
{
return gh->gameState()->map;
}
bool ServerSpellCastEnvironment::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, PlayerColor asker) const
{
return gh->moveHero(hid, dst, teleporting, false, asker);
}