1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-16 10:19:47 +02:00
vcmi/server/CGameHandler.cpp
Michał W. Urbańczyk a9af0da0ab Rewritten handling mouse movement over hex and l-clicking hex into one procedure. That way the tooltip and cursor are always accurate, because they're set by the same routing that selects action. Having that logic duplicated in two methods was unmaintainable. [though the new one is still monstrous...] By the way fixed numerous issues, including:
* #785 and parts of #760
* first aid tent can heal only creatures that suffered damage
* war machines can't be healed by tent
* creatures casting spells won't try to cast them during tactic phase
* console tooltips for first aid tent
* console tooltips for teleport spell
* cursor is reset to pointer when action is requested
* fixed a few other missing or wrong tooltips/cursors

Implemented opening creature window by l-clicking on stack. Master Genie's spell is picked by server, not client.
Minor changes.
2012-03-30 21:36:07 +00:00

5693 lines
172 KiB
C++

#include "StdInc.h"
#include "../lib/int3.h"
#include "../lib/CCampaignHandler.h"
#include "../lib/StartInfo.h"
#include "../lib/CArtHandler.h"
#include "../lib/CBuildingHandler.h"
#include "../lib/CDefObjInfoHandler.h"
#include "../lib/CHeroHandler.h"
#include "../lib/CObjectHandler.h"
#include "../lib/CSpellHandler.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/map.h"
#include "../lib/VCMIDirs.h"
#include "../client/CSoundBase.h"
#include "CGameHandler.h"
#include "../lib/CCreatureSet.h"
#include "../lib/CThreadHelper.h"
#include "../lib/GameConstants.h"
#include "../lib/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
#include <boost/random/linear_congruential.hpp>
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(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);
CondSh<bool> battleMadeAction;
CondSh<BattleResult *> battleResult(NULL);
template <typename T> class CApplyOnGH;
class CBaseForGHApply
{
public:
virtual bool applyOnGH(CGameHandler *gh, CConnection *c, void *pack, ui8 player) const =0;
virtual ~CBaseForGHApply(){}
template<typename U> static CBaseForGHApply *getApplier(const U * t=NULL)
{
return new CApplyOnGH<U>;
}
};
template <typename T> class CApplyOnGH : public CBaseForGHApply
{
public:
bool applyOnGH(CGameHandler *gh, CConnection *c, void *pack, ui8 player) const
{
T *ptr = static_cast<T*>(pack);
ptr->c = c;
ptr->player = player;
return ptr->applyGh(gh);
}
};
static CApplier<CBaseForGHApply> *applier = NULL;
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(std::map<ui32,si32>::iterator i = r.casualties[!r.winner].begin(); i!=r.casualties[!r.winner].end(); i++)
{
r.exp[r.winner] += VLC->creh->creatures[i->first]->valOfBonuses(Bonus::STACK_HEALTH) * i->second;
}
}
PlayerStatus PlayerStatuses::operator[](ui8 player)
{
boost::unique_lock<boost::mutex> l(mx);
if(players.find(player) != players.end())
{
return players[player];
}
else
{
throw std::string("No such player!");
}
}
void PlayerStatuses::addPlayer(ui8 player)
{
boost::unique_lock<boost::mutex> l(mx);
players[player];
}
int PlayerStatuses::getQueriesCount(ui8 player)
{
boost::unique_lock<boost::mutex> l(mx);
if(players.find(player) != players.end())
{
return players[player].queries.size();
}
else
{
return 0;
}
}
bool PlayerStatuses::checkFlag(ui8 player, bool PlayerStatus::*flag)
{
boost::unique_lock<boost::mutex> l(mx);
if(players.find(player) != players.end())
{
return players[player].*flag;
}
else
{
throw std::string("No such player!");
}
}
void PlayerStatuses::setFlag(ui8 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::string("No such player!");
}
cv.notify_all();
}
void PlayerStatuses::addQuery(ui8 player, ui32 id)
{
boost::unique_lock<boost::mutex> l(mx);
if(players.find(player) != players.end())
{
players[player].queries.insert(id);
}
else
{
throw std::string("No such player!");
}
cv.notify_all();
}
void PlayerStatuses::removeQuery(ui8 player, ui32 id)
{
boost::unique_lock<boost::mutex> l(mx);
if(players.find(player) != players.end())
{
players[player].queries.erase(id);
}
else
{
throw std::string("No such player!");
}
cv.notify_all();
}
template <typename T>
void callWith(std::vector<T> args, boost::function<void(T)> fun, ui32 which)
{
fun(args[which]);
}
void CGameHandler::levelUpHero(int ID, int skill)
{
changeSecSkill(ID, skill, 1, 0);
levelUpHero(ID);
}
void CGameHandler::levelUpHero(int ID)
{
CGHeroInstance *hero = static_cast<CGHeroInstance *>(gs->map->objects[ID].get());
// required exp for at least 1 lvl-up hasn't been reached
if (hero->exp < VLC->heroh->reqExp(hero->level+1))
{
afterBattleCallback();
return;
}
//give prim skill
tlog5 << hero->name <<" got level "<<hero->level<<std::endl;
int r = rand()%100, pom=0, x=0;
int std::pair<int,int>::*g = (hero->level>9) ? (&std::pair<int,int>::second) : (&std::pair<int,int>::first);
for(;x<GameConstants::PRIMARY_SKILLS;x++)
{
pom += hero->type->heroClass->primChance[x].*g;
if(r<pom)
break;
}
tlog5 << "The hero gets the primary skill with the no. " << x << " with a probability of " << r << "%." << std::endl;
SetPrimSkill sps;
sps.id = ID;
sps.which = x;
sps.abs = false;
sps.val = 1;
sendAndApply(&sps);
HeroLevelUp hlu;
hlu.heroid = ID;
hlu.primskill = x;
hlu.level = hero->level+1;
//picking sec. skills for choice
std::set<int> basicAndAdv, expert, none;
for(int i=0;i<GameConstants::SKILL_QUANTITY;i++)
if (isAllowed(2,i))
none.insert(i);
for(unsigned i=0;i<hero->secSkills.size();i++)
{
if(hero->secSkills[i].second < 3)
basicAndAdv.insert(hero->secSkills[i].first);
else
expert.insert(hero->secSkills[i].first);
none.erase(hero->secSkills[i].first);
}
//first offered skill
if(basicAndAdv.size())
{
int s = hero->type->heroClass->chooseSecSkill(basicAndAdv);//upgrade existing
hlu.skills.push_back(s);
basicAndAdv.erase(s);
}
else if(none.size() && hero->secSkills.size() < hero->type->heroClass->skillLimit)
{
hlu.skills.push_back(hero->type->heroClass->chooseSecSkill(none)); //give new skill
none.erase(hlu.skills.back());
}
//second offered skill
if(none.size() && hero->secSkills.size() < hero->type->heroClass->skillLimit) //hero have free skill slot
{
hlu.skills.push_back(hero->type->heroClass->chooseSecSkill(none)); //new skill
}
else if(basicAndAdv.size())
{
hlu.skills.push_back(hero->type->heroClass->chooseSecSkill(basicAndAdv)); //upgrade existing
}
if(hlu.skills.size() > 1) //apply and ask for secondary skill
{
boost::function<void(ui32)> callback = boost::function<void(ui32)>(boost::bind(callWith<ui16>,hlu.skills,boost::function<void(ui16)>(boost::bind(&CGameHandler::levelUpHero,this,ID,_1)),_1));
applyAndAsk(&hlu,hero->tempOwner,callback); //call levelUpHero when client responds
}
else if(hlu.skills.size() == 1) //apply, give only possible skill and send info
{
sendAndApply(&hlu);
levelUpHero(ID, hlu.skills.back());
}
else //apply and send info
{
sendAndApply(&hlu);
levelUpHero(ID);
}
}
void CGameHandler::changePrimSkill(int ID, int which, si64 val, bool abs)
{
SetPrimSkill sps;
sps.id = ID;
sps.which = which;
sps.abs = abs;
sps.val = val;
sendAndApply(&sps);
//only for exp - hero may level up
if(which==4)
{
levelUpHero(ID);
//TODO: Commander
//TODO: Stack Experience only after battle
}
}
void CGameHandler::changeSecSkill( int ID, int which, int val, bool abs/*=false*/ )
{
SetSecSkill sss;
sss.id = ID;
sss.which = which;
sss.val = val;
sss.abs = abs;
sendAndApply(&sss);
if(which == 7) //Wisdom
{
const CGHeroInstance *h = getHero(ID);
if(h && h->visitedTown)
giveSpells(h->visitedTown, h);
}
}
void CGameHandler::startBattle( const CArmedInstance *armies[2], int3 tile, const CGHeroInstance *heroes[2], bool creatureBank, boost::function<void(BattleResult*)> cb, const CGTownInstance *town /*= NULL*/ )
{
battleEndCallback = new boost::function<void(BattleResult*)>(cb);
{
setupBattle(tile, armies, heroes, creatureBank, town); //initializes stacks, places creatures on battlefield, blocks and informs player interfaces
}
runBattle();
}
void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2)
{
bool duel = gs->initialOpts->mode == StartInfo::DUEL;
BattleResultsApplied resultsApplied;
const CArmedInstance *bEndArmy1 = gs->curB->belligerents[0];
const CArmedInstance *bEndArmy2 = gs->curB->belligerents[1];
resultsApplied.player1 = bEndArmy1->tempOwner;
resultsApplied.player2 = bEndArmy2->tempOwner;
const CGHeroInstance *victoriousHero = gs->curB->heroes[battleResult.data->winner];
if(!duel)
{
//unblock engaged players
if(bEndArmy1->tempOwner<GameConstants::PLAYER_LIMIT)
states.setFlag(bEndArmy1->tempOwner, &PlayerStatus::engagedIntoBattle, false);
if(bEndArmy2 && bEndArmy2->tempOwner<GameConstants::PLAYER_LIMIT)
states.setFlag(bEndArmy2->tempOwner, &PlayerStatus::engagedIntoBattle, false);
}
//end battle, remove all info, free memory
giveExp(*battleResult.data);
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]);
ui8 sides[2];
for(int i=0; i<2; ++i)
{
sides[i] = gs->curB->sides[i];
}
ui8 loser = sides[!battleResult.data->winner];
CasualtiesAfterBattle cab1(bEndArmy1, gs->curB), cab2(bEndArmy2, gs->curB); //calculate casualties before deleting battle
ChangeSpells cs; //for Eagle Eye
if(victoriousHero)
{
if(int eagleEyeLevel = victoriousHero->getSecSkillLevel(CGHeroInstance::EAGLE_EYE))
{
int maxLevel = eagleEyeLevel + 1;
double eagleEyeChance = victoriousHero->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, CGHeroInstance::EAGLE_EYE);
BOOST_FOREACH(const CSpell *sp, gs->curB->usedSpellsHistory[!battleResult.data->winner])
if(sp->level <= maxLevel && !vstd::contains(victoriousHero->spells, sp->id) && rand() % 100 < eagleEyeChance)
cs.spells.insert(sp->id);
}
}
sendAndApply(battleResult.data);
//Eagle Eye secondary skill handling
if(cs.spells.size())
{
cs.learn = 1;
cs.hid = victoriousHero->id;
InfoWindow iw;
iw.player = victoriousHero->tempOwner;
iw.text.addTxt(MetaString::GENERAL_TXT, 221); //Through eagle-eyed observation, %s is able to learn %s
iw.text.addReplacement(victoriousHero->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());
std::set<ui32>::iterator it = cs.spells.begin();
for(int i = 0; i < cs.spells.size(); i++, it++)
{
iw.text.addReplacement(MetaString::SPELL_NAME, *it);
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);
}
// Necromancy if applicable.
const CGHeroInstance *winnerHero = battleResult.data->winner != 0 ? hero2 : hero1;
const CGHeroInstance *loserHero = battleResult.data->winner != 0 ? hero1 : hero2;
const CStackBasicDescriptor raisedStack = winnerHero ? winnerHero->calculateNecromancy(*battleResult.data) : CStackBasicDescriptor();
// Give raised units to winner and show dialog, if any were raised,
// units will be given after casualities are taken
const TSlot necroSlot = raisedStack.type ? winnerHero->getSlotFor(raisedStack.type) : -1;
if(!duel)
{
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)
changePrimSkill(hero1->id,4,battleResult.data->exp[0]);
else if (battleResult.data->exp[1] && hero2)
changePrimSkill(hero2->id,4,battleResult.data->exp[1]);
else
afterBattleCallback();
}
if (necroSlot != -1)
{
winnerHero->showNecromancyDialog(raisedStack);
addToSlot(StackLocation(winnerHero, necroSlot), raisedStack.type, raisedStack.count);
}
sendAndApply(&resultsApplied);
if(duel)
{
CSaveFile resultFile("result.vdrst");
resultFile << *battleResult.data;
return;
}
if(visitObjectAfterVictory && winnerHero == hero1)
{
visitObjectOnTile(*getTile(winnerHero->getPosition()), winnerHero);
}
visitObjectAfterVictory = false;
winLoseHandle(1<<sides[0] | 1<<sides[1]); //handle victory/loss of engaged players
int result = battleResult.get()->result;
if(result == 1 || result == 2) //loser has escaped or surrendered
{
SetAvailableHeroes sah;
sah.player = loser;
sah.hid[0] = loserHero->subID;
if(result == 1) //retreat
{
sah.army[0].clear();
sah.army[0].setCreature(0, VLC->creh->nameToID[loserHero->type->refTypeStack[0]],1);
}
if(const CGHeroInstance *another = getPlayer(loser)->availableHeroes[1])
sah.hid[1] = another->subID;
else
sah.hid[1] = -1;
sendAndApply(&sah);
}
}
void CGameHandler::afterBattleCallback() //object interaction after leveling up is done
{
if(battleEndCallback && *battleEndCallback)
{
boost::function<void(BattleResult*)> *backup = battleEndCallback;
battleEndCallback = NULL; //we need to set it to NULL or else we have infinite recursion when after battle callback gives exp (eg Pandora Box with exp)
(*backup)(battleResult.data);
delete backup;
}
}
void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CStack *def, int distance, int targetHex)
{
bat.bsa.clear();
bat.stackAttacking = att->ID;
int attackerLuck = att->LuckVal();
const CGHeroInstance * h0 = gs->curB->heroes[0],
* h1 = gs->curB->heroes[1];
bool noLuck = false;
if((h0 && NBonus::hasOfType(h0, Bonus::BLOCK_LUCK)) ||
(h1 && NBonus::hasOfType(h1, Bonus::BLOCK_LUCK)))
{
noLuck = true;
}
if(!noLuck && attackerLuck > 0 && rand()%24 < attackerLuck) //TODO?: negative luck option?
{
bat.flags |= BattleAttack::LUCKY;
}
if (rand()%100 < att->valOfBonuses(Bonus::DOUBLE_DAMAGE_CHANCE))
{
bat.flags |= BattleAttack::DEATH_BLOW;
}
if(att->getCreature()->idNumber == 146)
{
static const int artilleryLvlToChance[] = {0, 50, 75, 100};
const CGHeroInstance * owner = gs->curB->getHero(att->owner);
int chance = artilleryLvlToChance[owner->getSecSkillLevel(CGHeroInstance::ARTILLERY)];
if(chance > rand() % 100)
{
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<CStack*> attackedCreatures = gs->curB->getAttackedCreatures(att, targetHex);
//TODO: get exact attacked hex for defender
BOOST_FOREACH(CStack * stack, attackedCreatures)
{
if (stack != def) //do not hit same stack twice
{
applyBattleEffects(bat, att, stack, distance, true);
}
}
}
const Bonus * bonus = att->getBonus(Selector::type(Bonus::SPELL_LIKE_ATTACK));
if (bonus && (bat.shot())) //TODO: make it work in meele?
{
bat.bsa.front().flags |= BattleStackAttacked::EFFECT;
bat.bsa.front().effect = VLC->spellh->spells[bonus->subtype]->mainEffectAnim; //hopefully it does not interfere with any other effect?
std::set<CStack*> attackedCreatures = gs->curB->getAttackedCreatures(VLC->spellh->spells[bonus->subtype], bonus->val, att->owner, targetHex);
//TODO: get exact attacked hex for defender
BOOST_FOREACH(CStack * stack, attackedCreatures)
{
if (stack != def) //do not hit same stack twice
{
applyBattleEffects(bat, att, stack, distance, true);
}
}
}
}
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.deathBlow(), bat.ballistaDoubleDmg());
def->prepareAttacked(bsa); //calculate casualties
//life drain handling
if (att->hasBonusOfType(Bonus::LIFE_DRAIN) && def->isLiving())
{
StacksHealedOrResurrected shi;
shi.lifeDrain = (ui8)true;
shi.tentHealing = (ui8)false;
shi.drainedFrom = def->ID;
StacksHealedOrResurrected::HealInfo hi;
hi.stackID = att->ID;
hi.healedHP = std::min<int>(bsa.damageAmount, att->MaxHealth() - att->firstHPleft + att->MaxHealth() * (att->baseAmount - att->count) );
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) && !bsa.killed() )
{
BattleStackAttacked bsa;
bsa.stackAttacked = att->ID; //invert
bsa.attackerID = def->ID;
bsa.flags |= BattleStackAttacked::EFFECT;
bsa.effect = 11;
bsa.damageAmount = (bsa.damageAmount * def->valOfBonuses(Bonus::FIRE_SHIELD)) / 100;
att->prepareAttacked(bsa);
bat.bsa.push_back(bsa);
}
}
void CGameHandler::handleConnection(std::set<int> players, CConnection &c)
{
setThreadName(-1, "CGameHandler::handleConnection");
srand(time(NULL));
try
{
while(1)//server should never shut connection first //was: while(!end2)
{
CPack *pack = NULL;
ui8 player = 255;
si32 requestID = -999;
int packType = 0;
{
boost::unique_lock<boost::mutex> lock(*c.rmx);
c >> player >> requestID >> pack; //get the package
packType = typeList.getTypeID(pack); //get the id of type
tlog5 << boost::format("Received client message (request %d by player %d) of type with ID=%d (%s).\n")
% requestID % player % packType % typeid(*pack).name();
}
//prepare struct informing that action was applied
PackageApplied applied;
applied.player = player;
applied.result = false;
applied.packType = packType;
applied.requestID = requestID;
CBaseForGHApply *apply = applier->apps[packType]; //and appropriae applier object
if(isBlockedByQueries(pack, packType, player))
{
complain(boost::str(boost::format("Player %d has to answer queries before attempting any further actions (count=%d)!") % (int)player % states.getQueriesCount(player)));
{
boost::unique_lock<boost::mutex> lock(*c.wmx);
c << &applied;
}
}
else if(apply)
{
bool result = apply->applyOnGH(this,&c,pack, player);
tlog5 << "Message successfully applied (result=" << result << ")!\n";
//send confirmation that we've applied the package
applied.result = result;
{
boost::unique_lock<boost::mutex> lock(*c.wmx);
c << &applied;
}
}
else
{
tlog1 << "Message cannot be applied, cannot find applier (unregistered type)!\n";
}
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
tlog1 << e.what() << std::endl;
end2 = true;
}
HANDLE_EXCEPTION(end2 = true);
tlog1 << "Ended handling connection\n";
}
int CGameHandler::moveStack(int stack, BattleHex dest)
{
int ret = 0;
CStack *curStack = gs->curB->getStack(stack),
*stackAtEnd = gs->curB->getStackT(dest);
assert(curStack);
assert(dest < GameConstants::BFIELD_SIZE);
if (gs->curB->tacticDistance)
{
assert(gs->curB->isInTacticRange(dest));
}
//initing necessary tables
bool accessibility[GameConstants::BFIELD_SIZE];
std::vector<BattleHex> accessible = gs->curB->getAccessibility(curStack, false);
for(int b=0; b<GameConstants::BFIELD_SIZE; ++b)
{
accessibility[b] = false;
}
for(int g=0; g<accessible.size(); ++g)
{
accessibility[accessible[g]] = true;
}
//shifting destination (if we have double wide stack and we can occupy dest but not be exactly there)
if(!stackAtEnd && curStack->doubleWide() && !accessibility[dest])
{
if(curStack->attackerOwned)
{
if(accessibility[dest+1])
dest += BattleHex::RIGHT;
}
else
{
if(accessibility[dest-1])
dest += BattleHex::LEFT;
}
}
if((stackAtEnd && stackAtEnd!=curStack && stackAtEnd->alive()) || !accessibility[dest])
return 0;
bool accessibilityWithOccupyable[GameConstants::BFIELD_SIZE];
std::vector<BattleHex> accOc = gs->curB->getAccessibility(curStack, true);
for(int b=0; b<GameConstants::BFIELD_SIZE; ++b)
{
accessibilityWithOccupyable[b] = false;
}
for(int g=0; g<accOc.size(); ++g)
{
accessibilityWithOccupyable[accOc[g]] = true;
}
//if(dists[dest] > curStack->creature->speed && !(stackAtEnd && dists[dest] == curStack->creature->speed+1)) //we can attack a stack if we can go to adjacent hex
// return false;
std::pair< std::vector<BattleHex>, int > path = gs->curB->getPath(curStack->position, dest, accessibilityWithOccupyable, curStack->hasBonusOfType(Bonus::FLYING), curStack->doubleWide(), curStack->attackerOwned);
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
{
// send one package with the creature path information
std::vector<BattleHex> tiles;
int tilesToMove = std::max((int)(path.first.size() - creSpeed), 0);
for(int v=path.first.size()-1; v>=tilesToMove; --v)
{
tiles.push_back(path.first[v]);
}
if (tiles.size() > 0)
{
BattleStackMoved sm;
sm.stack = curStack->ID;
sm.distance = path.second;
sm.teleporting = false;
sm.tilesToMove = tiles;
sendAndApply(&sm);
}
}
return ret;
}
CGameHandler::CGameHandler(void)
{
seedInitial = seedPostInit = -1;
QID = 1;
//gs = NULL;
IObjectInterface::cb = this;
applier = new CApplier<CBaseForGHApply>;
registerTypes3(*applier);
visitObjectAfterVictory = false;
battleEndCallback = NULL;
}
CGameHandler::~CGameHandler(void)
{
delete applier;
applier = NULL;
delete gs;
}
void CGameHandler::init(StartInfo *si)
{
extern DLL_LINKAGE boost::rand48 ran;
if(seedInitial < 0)
seedInitial = std::time(NULL);
gs = new CGameState();
tlog0 << "Gamestate created!" << std::endl;
gs->init(si, 0, seedInitial);
seedPostInit = ran();
tlog0 << "Gamestate initialized!" << std::endl;
for(std::map<ui8,PlayerState>::iterator i = gs->players.begin(); i != gs->players.end(); i++)
states.addPlayer(i->first);
}
static bool evntCmp(const CMapEvent *a, const CMapEvent *b)
{
return *a < *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)
{
tlog3 << "There is no player owner of town " << town->name << " at " << town->pos << std::endl;
return;
}
if (forced || town->creatures[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;
}
ui32 dwellpos = rand()%dwellings.size();//take random dwelling
ui32 creapos = rand()%dwellings[dwellpos]->creatures.size();//for multi-creature dwellings like Golem Factory
ui32 creature = dwellings[dwellpos]->creatures[creapos].second[0];
if (clear)
ssi.creatures[GameConstants::CREATURES_PER_TOWN].first = std::max((ui32)1, (VLC->creh->creatures[creature]->growth)/2);
else
ssi.creatures[GameConstants::CREATURES_PER_TOWN].first = VLC->creh->creatures[creature]->growth;
ssi.creatures[GameConstants::CREATURES_PER_TOWN].second.push_back(creature);
sendAndApply(&ssi);
}
}
void CGameHandler::newTurn()
{
tlog5 << "Turn " << gs->day+1 << std::endl;
NewTurn n;
n.specialWeek = NewTurn::NO_ACTION;
n.creatureid = -1;
n.day = gs->day + 1;
bool firstTurn = !getDate(0);
bool newWeek = getDate(1) == 7; //day numbers are confusing, as day was not yet switched
bool newMonth = getDate(4) == 28;
std::map<ui8, si32> hadGold;//starting gold - for buildings like dwarven treasury
srand(time(NULL));
if (newWeek && !firstTurn)
{
n.specialWeek = NewTurn::NORMAL;
bool deityOfFireBuilt = false;
BOOST_FOREACH(const CGTownInstance *t, gs->map->towns)
{
if(t->subID == 3 && vstd::contains(t->builtBuildings, EBuilding::GRAIL))
{
deityOfFireBuilt = true;
break;
}
}
if(deityOfFireBuilt)
{
n.specialWeek = NewTurn::DEITYOFFIRE;
n.creatureid = 42;
}
else
{
int monthType = rand()%100;
if(newMonth) //new month
{
if (monthType < 40) //double growth
{
n.specialWeek = NewTurn::DOUBLE_GROWTH;
if (ALLCREATURESGETDOUBLEMONTHS)
{
std::pair<int,int> newMonster(54, VLC->creh->pickRandomMonster(boost::ref(rand)));
n.creatureid = newMonster.second;
}
else
{
std::set<TCreature>::const_iterator it = VLC->creh->doubledCreatures.begin();
std::advance (it, rand() % VLC->creh->doubledCreatures.size()); //picking random element of set is tiring
n.creatureid = *it;
}
}
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,int> newMonster (54, VLC->creh->pickRandomMonster(boost::ref(rand)));
//TODO do not pick neutrals
n.creatureid = newMonster.second;
}
}
}
}
bmap<ui32, ConstTransitivePtr<CGHeroInstance> > pool = gs->hpool.heroesPool;
for ( std::map<ui8, PlayerState>::iterator i=gs->players.begin() ; i!=gs->players.end();i++)
{
if(i->first == 255)
continue;
else if(i->first >= GameConstants::PLAYER_LIMIT)
assert(0); //illegal player number!
std::pair<ui8,si32> playerGold(i->first,i->second.resources[Res::GOLD]);
hadGold.insert(playerGold);
if(newWeek) //new heroes in tavern
{
SetAvailableHeroes sah;
sah.player = i->first;
//pick heroes and their armies
CHeroClass *banned = NULL;
for (int j = 0; j < GameConstants::AVAILABLE_HEROES_PER_PLAYER; j++)
{
if(CGHeroInstance *h = gs->hpool.pickHeroFor(j == 0, i->first, getNativeTown(i->first), pool, banned)) //first hero - native if possible, second hero -> any other class
{
sah.hid[j] = h->subID;
h->initArmy(&sah.army[j]);
banned = h->type->heroClass;
}
else
sah.hid[j] = -1;
}
sendAndApply(&sah);
}
n.res[i->first] = i->second.resources;
BOOST_FOREACH(CGHeroInstance *h, (*i).second.heroes)
{
if(h->visitedTown)
giveSpells(h->visitedTown, h);
NewTurn::Hero hth;
hth.id = h->id;
hth.move = h->maxMovePoints(gs->map->getTile(h->getPosition(false)).tertype != TerrainTile::water);
if(h->visitedTown && vstd::contains(h->visitedTown->builtBuildings,0)) //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[i->first][Res::GOLD] += h->valOfBonuses(Selector::typeSubtype(Bonus::SECONDARY_SKILL_PREMY, CGHeroInstance::ESTATES)); //estates
for (int k = 0; k < GameConstants::RESOURCE_QUANTITY; k++)
{
n.res[i->first][k] += h->valOfBonuses(Bonus::GENERATE_RESOURCE, k);
}
}
}
}
// townID, creatureID, amount
std::map<si32, std::map<si32, si32> > newCreas;//creatures that needs to be added by town events
BOOST_FOREACH(CGTownInstance *t, gs->map->towns)
{
ui8 player = t->tempOwner;
handleTownEvents(t, n, newCreas);
if(newWeek) //first day of week
{
if(t->subID == 5 && vstd::contains(t->builtBuildings, EBuilding::SPECIAL_3))
setPortalDwelling(t, true, (n.specialWeek == NewTurn::PLAGUE ? true : false)); //set creatures for Portal of Summoning
if(!firstTurn)
if (t->subID == 1 && player < GameConstants::PLAYER_LIMIT && vstd::contains(t->builtBuildings, EBuilding::SPECIAL_3))//dwarven treasury
n.res[player][Res::GOLD] += hadGold[player]/10; //give 10% of starting gold
SetAvailableCreatures sac;
sac.tid = t->id;
sac.creatures = t->creatures;
for (int k=0; k < GameConstants::CREATURES_PER_TOWN; k++) //creature growths
{
if(t->creatureDwelling(k))//there is dwelling (k-level)
{
ui32 &availableCount = sac.creatures[k].first;
const CCreature *cre = VLC->creh->creatures[t->creatures[k].second.back()];
if (n.specialWeek == NewTurn::PLAGUE)
availableCount = t->creatures[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[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;
}
}
}
}
//add creatures from town events
if (vstd::contains(newCreas, t->id))
for(std::map<si32, si32>::iterator i=newCreas[t->id].begin() ; i!=newCreas[t->id].end(); i++)
sac.creatures[i->first].first += i->second;
n.cres.push_back(sac);
}
if(!firstTurn && player < GameConstants::PLAYER_LIMIT)//not the first day and town not neutral
{
if(vstd::contains(t->builtBuildings, EBuilding::RESOURCE_SILO)) //there is resource silo
{
if(t->town->primaryRes == 127) //we'll give wood and ore
{
n.res[player][Res::WOOD] ++;
n.res[player][Res::ORE] ++;
}
else
{
n.res[player][t->town->primaryRes] ++;
}
}
n.res[player][Res::GOLD] += t->dailyIncome();
}
if(vstd::contains(t->builtBuildings, EBuilding::GRAIL) && t->subID == 2)
{
// Skyship, probably easier to handle same as Veil of darkness
//do it every new day after veils apply
FoWChange fw;
fw.mode = 1;
fw.player = player;
getAllTiles(fw.tiles, player, -1, 0);
sendAndApply (&fw);
}
if (t->hasBonusOfType (Bonus::DARKNESS))
{
t->hideTiles(t->getOwner(), t->getBonus(Selector::type(Bonus::DARKNESS))->val);
}
//unhiding what shouldn't be hidden? //that's handled in netpacks client
}
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, 32 + rand()%10);
}
else
{
iw.text.addTxt(MetaString::ARRAY_TXT, (133));
iw.text.addReplacement(MetaString::ARRAY_TXT, 43 + rand()%15);
}
}
for (std::map<ui8, PlayerState>::iterator i=gs->players.begin() ; i!=gs->players.end(); i++)
{
iw.player = i->first;
sendAndApply(&iw);
}
}
}
tlog5 << "Info about turn " << n.day << "has been sent!" << std::endl;
handleTimeEvents();
//call objects
for(size_t i = 0; i<gs->map->objects.size(); i++)
{
if(gs->map->objects[i])
gs->map->objects[i]->newTurn();
}
winLoseHandle(0xff);
//warn players without town
if(gs->day)
{
for (std::map<ui8, PlayerState>::iterator i=gs->players.begin() ; i!=gs->players.end();i++)
{
if(i->second.status || i->second.towns.size() || i->second.color >= GameConstants::PLAYER_LIMIT)
continue;
InfoWindow iw;
iw.player = i->first;
iw.components.push_back(Component(Component::FLAG,i->first,0,0));
if(!i->second.daysWithoutCastle)
{
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, i->first);
}
else if(i->second.daysWithoutCastle == 6)
{
iw.text.addTxt(MetaString::ARRAY_TXT,129); //%s, this is your last day to capture a town or you will be banished from this land.
iw.text.addReplacement(MetaString::COLOR, i->first);
}
else
{
iw.text.addTxt(MetaString::ARRAY_TXT,128); //%s, you only have %d days left to capture a town or you will be banished from this land.
iw.text.addReplacement(MetaString::COLOR, i->first);
iw.text.addReplacement(7 - i->second.daysWithoutCastle);
}
sendAndApply(&iw);
}
}
}
void CGameHandler::run(bool resume)
{
using namespace boost::posix_time;
BOOST_FOREACH(CConnection *cc, conns)
{//init conn.
ui32 quantity;
ui8 pom;
//ui32 seed;
if(!resume)
{
ui32 sum = gs->map ? gs->map->checksum : 612;
(*cc) << gs->initialOpts << sum << gs->seed << seedPostInit; // gs->scenarioOps
}
(*cc) >> quantity; //how many players will be handled at that client
tlog0 << "Connection " << cc->connectionID << " will handle " << quantity << " player: ";
for(int i=0;i<quantity;i++)
{
(*cc) >> pom; //read player color
tlog0 << (int)pom << " ";
{
boost::unique_lock<boost::recursive_mutex> lock(gsm);
connections[pom] = cc;
}
}
tlog0 << std::endl;
}
for(std::set<CConnection*>::iterator i = conns.begin(); i!=conns.end();i++)
{
std::set<int> pom;
for(std::map<int,CConnection*>::iterator j = connections.begin(); j!=connections.end();j++)
if(j->second == *i)
pom.insert(j->first);
boost::thread(boost::bind(&CGameHandler::handleConnection,this,pom,boost::ref(**i)));
}
if(gs->scenarioOps->mode == StartInfo::DUEL)
{
runBattle();
return;
}
while (!end2)
{
if(!resume)
newTurn();
std::map<ui8,PlayerState>::iterator i;
if(!resume)
i = gs->players.begin();
else
i = gs->players.find(gs->currentPlayer);
resume = false;
for(; i != gs->players.end(); i++)
{
if((i->second.towns.size()==0 && i->second.heroes.size()==0)
|| i->second.color<0
|| i->first>=GameConstants::PLAYER_LIMIT
|| i->second.status)
{
continue;
}
states.setFlag(i->first,&PlayerStatus::makingTurn,true);
{
YourTurn yt;
yt.player = i->first;
applyAndSend(&yt);
}
//wait till turn is done
boost::unique_lock<boost::mutex> lock(states.mx);
while(states.players[i->first].makingTurn && !end2)
{
static time_duration p = milliseconds(200);
states.cv.timed_wait(lock,p);
}
}
}
while(conns.size() && (*conns.begin())->isOpen())
boost::this_thread::sleep(boost::posix_time::milliseconds(5)); //give time client to close socket
}
void CGameHandler::setupBattle( int3 tile, const CArmedInstance *armies[2], const CGHeroInstance *heroes[2], bool creatureBank, const CGTownInstance *town )
{
battleResult.set(NULL);
//send info about battles
BattleStart bs;
bs.info = gs->setupBattle(tile, armies, heroes, creatureBank, town);
sendAndApply(&bs);
}
void CGameHandler::checkForBattleEnd( std::vector<CStack*> &stacks )
{
//checking winning condition
bool hasStack[2]; //hasStack[0] - true if attacker has a living stack; defender similarly
hasStack[0] = hasStack[1] = false;
for(int b = 0; b<stacks.size(); ++b)
{
if(stacks[b]->alive() && !stacks[b]->hasBonusOfType(Bonus::SIEGE_WEAPON))
{
hasStack[1-stacks[b]->attackerOwned] = true;
}
}
if(!hasStack[0] || !hasStack[1]) //somebody has won
{
setBattleResult(0, hasStack[1]);
}
}
void CGameHandler::giveSpells( const CGTownInstance *t, const CGHeroInstance *h )
{
if(!h->hasSpellbook())
return; //hero hasn't spellbok
ChangeSpells cs;
cs.hid = h->id;
cs.learn = true;
for(int i=0; i<std::min(t->mageGuildLevel(),h->getSecSkillLevel(CGHeroInstance::WISDOM)+2);i++)
{
if (t->subID == 8 && vstd::contains(t->builtBuildings, EBuilding::GRAIL)) //Aurora Borealis
{
std::vector<ui16> spells;
getAllowedSpells(spells, i);
for (int j = 0; j < spells.size(); ++j)
cs.spells.insert(spells[j]);
}
else
{
for(int j=0; j<t->spellsAtLevel(i+1,true) && j<t->spells[i].size(); j++)
{
if(!vstd::contains(h->spells,t->spells[i][j]))
cs.spells.insert(t->spells[i][j]);
}
}
}
if(cs.spells.size())
sendAndApply(&cs);
}
void CGameHandler::setBlockVis(int objid, bool bv)
{
SetObjectProperty sop(objid,2,bv);
sendAndApply(&sop);
}
bool CGameHandler::removeObject( int objid )
{
if(!getObj(objid))
{
tlog1 << "Something wrong, that object already has been removed or hasn't existed!\n";
return false;
}
RemoveObject ro;
ro.id = objid;
sendAndApply(&ro);
winLoseHandle(255); //eg if monster escaped (removing objs after battle is done dircetly by endBattle, not this function)
return true;
}
void CGameHandler::setAmount(int objid, ui32 val)
{
SetObjectProperty sop(objid,3,val);
sendAndApply(&sop);
}
bool CGameHandler::moveHero( si32 hid, int3 dst, ui8 instant, ui8 asker /*= 255*/ )
{
bool blockvis = false;
const CGHeroInstance *h = getHero(hid);
if(!h || (asker != 255 && (instant || h->getOwner() != gs->currentPlayer)) //not turn of that hero or player can't simply teleport hero (at least not with this function)
)
{
tlog1 << "Illegal call to move hero!\n";
return false;
}
tlog5 << "Player " <<int(asker) << " wants to move hero "<< hid << " from "<< h->pos << " to " << dst << std::endl;
int3 hmpos = dst + int3(-1,0,0);
if(!gs->map->isInTheMap(hmpos))
{
tlog1 << "Destination tile is outside the map!\n";
return false;
}
TerrainTile t = gs->map->terrain[hmpos.x][hmpos.y][hmpos.z];
int cost = gs->getMovementCost(h, h->getPosition(false), CGHeroInstance::convertPosition(dst,false),h->movement);
int3 guardPos = gs->guardingCreaturePosition(hmpos);
//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
//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 == TerrainTile::rock || (t.blocked && !t.visitable && !h->hasBonusOfType(Bonus::FLYING_MOVEMENT) ))
&& complain("Cannot move hero, destination tile is blocked!"))
|| ((!h->boat && !h->canWalkOnSea() && t.tertype == TerrainTile::water && (t.visitableObjects.size() < 1 || (t.visitableObjects.back()->ID != 8 && t.visitableObjects.back()->ID != GameConstants::HEROI_TYPE))) //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 != TerrainTile::water && t.blocked)
&& complain("Cannot disembark hero, tile is blocked!"))
|| ((h->movement < cost && dst != h->pos && !instant)
&& complain("Hero doesn't have any movement points left!"))
|| (states.checkFlag(h->tempOwner, &PlayerStatus::engagedIntoBattle)
&& complain("Cannot move hero during the battle")))
{
//send info about movement failure
sendAndApply(&tmh);
return false;
}
//hero enters the boat
if(!h->boat && t.visitableObjects.size() && t.visitableObjects.back()->ID == 8)
{
tmh.result = TryMoveHero::EMBARK;
tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, false);
getTilesInRange(tmh.fowRevealed,h->getSightCenter()+(tmh.end-tmh.start),h->getSightRadious(),h->tempOwner,1);
sendAndApply(&tmh);
return true;
}
//hero leaves the boat
else if(h->boat && t.tertype != TerrainTile::water && !t.blocked)
{
//TODO? code similarity with the block above
tmh.result = TryMoveHero::DISEMBARK;
tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, true);
getTilesInRange(tmh.fowRevealed,h->getSightCenter()+(tmh.end-tmh.start),h->getSightRadious(),h->tempOwner,1);
sendAndApply(&tmh);
tryAttackingGuard(guardPos, h);
return true;
}
//checks for standard movement
if(!instant)
{
if( (distance(h->pos,dst) >= 1.5 && complain("Tiles are not neighboring!"))
|| (h->movement < cost && h->movement < 100 && complain("Not enough move points!")))
{
sendAndApply(&tmh);
return false;
}
//check if there is blocking visitable object
blockvis = false;
tmh.movePoints = std::max((ui32)(0),h->movement-cost); //take move points
BOOST_FOREACH(CGObjectInstance *obj, t.visitableObjects)
{
if(obj != h && obj->blockVisit && !(obj->getPassableness() & 1<<h->tempOwner))
{
blockvis = true;
break;
}
}
//we start moving
if(blockvis)//interaction with blocking object (like resources)
{
tmh.result = TryMoveHero::BLOCKING_VISIT;
sendAndApply(&tmh);
//failed to move to that tile but we visit object
if(t.visitableObjects.size())
objectVisited(t.visitableObjects.back(), h);
tlog5 << "Blocking visit at " << hmpos << std::endl;
return true;
}
else //normal move
{
BOOST_FOREACH(CGObjectInstance *obj, gs->map->terrain[h->pos.x-1][h->pos.y][h->pos.z].visitableObjects)
{
obj->onHeroLeave(h);
}
getTilesInRange(tmh.fowRevealed,h->getSightCenter()+(tmh.end-tmh.start),h->getSightRadious(),h->tempOwner,1);
tmh.result = TryMoveHero::SUCCESS;
tmh.attackedFrom = guardPos;
sendAndApply(&tmh);
tlog5 << "Moved to " <<tmh.end<<std::endl;
// If a creature guards the tile, block visit.
const bool fightingGuard = tryAttackingGuard(guardPos, h);
if(!fightingGuard && t.visitableObjects.size()) //call objects if they are visited
{
visitObjectOnTile(t, h);
}
tlog5 << "Movement end!\n";
return true;
}
}
else //instant move - teleportation
{
BOOST_FOREACH(CGObjectInstance* obj, t.blockingObjects)
{
if(obj->ID==GameConstants::HEROI_TYPE)
{
CGHeroInstance *dh = static_cast<CGHeroInstance *>(obj);
if( gameState()->getPlayerRelations(dh->tempOwner, h->tempOwner))
{
heroExchange(h->id, dh->id);
return true;
}
startBattleI(h, dh);
return true;
}
}
tmh.result = TryMoveHero::TELEPORTATION;
getTilesInRange(tmh.fowRevealed,h->getSightCenter()+(tmh.end-tmh.start),h->getSightRadious(),h->tempOwner,1);
sendAndApply(&tmh);
return true;
}
}
bool CGameHandler::teleportHero(si32 hid, si32 dstid, ui8 source, ui8 asker/* = 255*/)
{
const CGHeroInstance *h = getHero(hid);
const CGTownInstance *t = getTown(dstid);
if ( !h || !t || h->getOwner() != gs->currentPlayer )
tlog1<<"Invalid call to teleportHero!";
const CGTownInstance *from = h->visitedTown;
if(((h->getOwner() != t->getOwner())
&& complain("Cannot teleport hero to another player"))
|| ((!from || from->subID!=3 || !vstd::contains(from->builtBuildings, EBuilding::SPECIAL_3))
&& complain("Hero must be in town with Castle gate for teleporting"))
|| ((t->subID!=3 || !vstd::contains(t->builtBuildings, EBuilding::SPECIAL_3))
&& complain("Cannot teleport hero to town without Castle gate in it")))
return false;
int3 pos = t->visitablePos();
pos += h->getVisitableOffset();
stopHeroVisitCastle(from->id, hid);
moveHero(hid,pos,1);
heroVisitCastle(dstid, hid);
return true;
}
void CGameHandler::setOwner(int objid, ui8 owner)
{
ui8 oldOwner = getOwner(objid);
SetObjectProperty sop(objid,1,owner);
sendAndApply(&sop);
winLoseHandle(1<<owner | 1<<oldOwner);
if(owner < GameConstants::PLAYER_LIMIT && getTown(objid)) //town captured
{
const CGTownInstance * town = getTown(objid);
if (town->subID == 5 && vstd::contains(town->builtBuildings, 22))
setPortalDwelling(town, true, false);
if (!gs->getPlayer(owner)->towns.size())//player lost 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.
sendAndApply(&iw);
}
}
const CGObjectInstance * obj = getObj(objid);
const PlayerState * p = gs->getPlayer(owner);
if((obj->ID == 17 || obj->ID == 20 ) && p && p->dwellings.size()==1)//first dwelling captured
{
BOOST_FOREACH(const CGTownInstance *t, gs->getPlayer(owner)->towns)
{
if (t->subID == 5 && vstd::contains(t->builtBuildings, 22))
setPortalDwelling(t);//set initial creatures for all portals of summoning
}
}
}
void CGameHandler::setHoverName(int objid, MetaString* name)
{
SetHoverName shn(objid, *name);
sendAndApply(&shn);
}
void CGameHandler::showBlockingDialog( BlockingDialog *iw, const CFunctionList<void(ui32)> &callback )
{
ask(iw,iw->player,callback);
}
ui32 CGameHandler::showBlockingDialog( BlockingDialog *iw )
{
//TODO
//gsm.lock();
//int query = QID++;
//states.addQuery(player,query);
//sendToAllClients(iw);
//gsm.unlock();
//ui32 ret = getQueryResult(iw->player, query);
//gsm.lock();
//states.removeQuery(player, query);
//gsm.unlock();
return 0;
}
void CGameHandler::giveResource(int player, int 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[which] + val;
sendAndApply(&sr);
}
void CGameHandler::giveCreatures(const CArmedInstance *obj, const CGHeroInstance * h, const CCreatureSet &creatures, bool remove)
{
boost::function<void()> removeOrNot = 0;
if(remove)
removeOrNot = boost::bind(&CGameHandler::removeObject, this, obj->id);
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 (TSlots::const_iterator stack = creatures.Slots().begin(); stack != creatures.Slots().end(); stack++)
{
addToSlot(StackLocation(obj, obj->getSlotFor(stack->second->type)), stack->second->type, stack->second->count);
}
tryJoiningArmy(obj, h, remove, true);
}
void CGameHandler::takeCreatures(int 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));
BOOST_FOREACH(CStackBasicDescriptor &sbd, cres)
{
TQuantity collected = 0;
while(collected < sbd.count)
{
TSlots::const_iterator i = obj->Slots().begin();
for(; 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;
break;
}
}
if(i == obj->Slots().end()) //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(int obj, int heroID)
{
HeroVisitCastle vc;
vc.hid = heroID;
vc.tid = obj;
vc.flags |= 1;
sendAndApply(&vc);
const CGHeroInstance *h = getHero(heroID);
vistiCastleObjects (getTown(obj), h);
giveSpells (getTown(obj), getHero(heroID));
if(gs->map->victoryCondition.condition == EVictoryConditionType::TRANSPORTITEM)
checkLossVictory(h->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(int obj, int heroID)
{
HeroVisitCastle vc;
vc.hid = heroID;
vc.tid = obj;
sendAndApply(&vc);
}
void CGameHandler::removeArtifact(const ArtifactLocation &al)
{
assert(al.getArt());
EraseArtifact ea;
ea.al = al;
sendAndApply(&ea);
}
void CGameHandler::startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank, boost::function<void(BattleResult*)> cb, const CGTownInstance *town) //use hero=NULL for no hero
{
engageIntoBattle(army1->tempOwner);
engageIntoBattle(army2->tempOwner);
//block engaged players
if(army2->tempOwner < GameConstants::PLAYER_LIMIT)
states.setFlag(army2->tempOwner,&PlayerStatus::engagedIntoBattle,true);
static const CArmedInstance *armies[2];
armies[0] = army1;
armies[1] = army2;
static const CGHeroInstance*heroes[2];
heroes[0] = hero1;
heroes[1] = hero2;
boost::thread(boost::bind(&CGameHandler::startBattle, this, armies, tile, heroes, creatureBank, cb, town));
}
void CGameHandler::startBattleI( const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, boost::function<void(BattleResult*)> cb, bool creatureBank )
{
startBattleI(army1, army2, tile,
army1->ID == GameConstants::HEROI_TYPE ? static_cast<const CGHeroInstance*>(army1) : NULL,
army2->ID == GameConstants::HEROI_TYPE ? static_cast<const CGHeroInstance*>(army2) : NULL,
creatureBank, cb);
}
void CGameHandler::startBattleI( const CArmedInstance *army1, const CArmedInstance *army2, boost::function<void(BattleResult*)> cb, bool creatureBank)
{
startBattleI(army1, army2, army2->visitablePos(), cb, creatureBank);
}
void CGameHandler::changeSpells( int hid, bool give, const std::set<ui32> &spells )
{
ChangeSpells cs;
cs.hid = hid;
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( int hid, int val )
{
SetMana sm;
sm.hid = hid;
sm.val = val;
sendAndApply(&sm);
}
void CGameHandler::giveHero( int id, int player )
{
GiveHero gh;
gh.id = id;
gh.player = player;
sendAndApply(&gh);
}
void CGameHandler::changeObjPos( int objid, int3 newPos, ui8 flags )
{
ChangeObjPos cop;
cop.objid = objid;
cop.nPos = newPos;
cop.flags = flags;
sendAndApply(&cop);
}
void CGameHandler::useScholarSkill(si32 fromHero, si32 toHero)
{
const CGHeroInstance * h1 = getHero(fromHero);
const CGHeroInstance * h2 = getHero(toHero);
if ( h1->getSecSkillLevel(CGHeroInstance::SCHOLAR) < h2->getSecSkillLevel(CGHeroInstance::SCHOLAR) )
{
std::swap (h1,h2);//1st hero need to have higher scholar level for correct message
std::swap(fromHero, toHero);
}
int ScholarLevel = h1->getSecSkillLevel(CGHeroInstance::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(CGHeroInstance::WISDOM)+2),
h2Lvl = std::min(ScholarLevel+1, h2->getSecSkillLevel(CGHeroInstance::WISDOM)+2);//heroes can receive this levels
ChangeSpells cs1;
cs1.learn = true;
cs1.hid = toHero;//giving spells to first hero
for(std::set<ui32>::const_iterator it=h1->spells.begin(); it!=h1->spells.end();it++)
if ( h2Lvl >= VLC->spellh->spells[*it]->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(std::set<ui32>::const_iterator it=h2->spells.begin(); it!=h2->spells.end();it++)
if ( h1Lvl >= VLC->spellh->spells[*it]->level && !vstd::contains(h1->spells, *it))
cs2.spells.insert(*it);
if (cs1.spells.size() || cs2.spells.size())//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.size())//if found new spell - apply
{
iw.text.addTxt(MetaString::GENERAL_TXT, 140);//learns
int size = cs2.spells.size();
for(std::set<ui32>::const_iterator it=cs2.spells.begin(); it!=cs2.spells.end();it++)
{
iw.components.push_back(Component(Component::SPELL, (*it), 1, 0));
iw.text.addTxt(MetaString::SPELL_NAME, (*it));
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.size() && cs2.spells.size() )
{
iw.text.addTxt(MetaString::GENERAL_TXT, 141);//and
}
if (cs1.spells.size())
{
iw.text.addTxt(MetaString::GENERAL_TXT, 147);//teaches
int size = cs1.spells.size();
for(std::set<ui32>::const_iterator it=cs1.spells.begin(); it!=cs1.spells.end();it++)
{
iw.components.push_back(Component(Component::SPELL, (*it), 1, 0));
iw.text.addTxt(MetaString::SPELL_NAME, (*it));
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(si32 hero1, si32 hero2)
{
ui8 player1 = getHero(hero1)->tempOwner;
ui8 player2 = getHero(hero2)->tempOwner;
if( gameState()->getPlayerRelations( player1, player2))
{
OpenWindow hex;
hex.window = OpenWindow::EXCHANGE_WINDOW;
hex.id1 = hero1;
hex.id2 = hero2;
sendAndApply(&hex);
useScholarSkill(hero1,hero2);
}
}
void CGameHandler::applyAndAsk( Query * sel, ui8 player, boost::function<void(ui32)> &callback )
{
boost::unique_lock<boost::recursive_mutex> lock(gsm);
sel->id = QID;
callbacks[QID] = callback;
states.addQuery(player,QID);
QID++;
sendAndApply(sel);
}
void CGameHandler::ask( Query * sel, ui8 player, const CFunctionList<void(ui32)> &callback )
{
boost::unique_lock<boost::recursive_mutex> lock(gsm);
sel->id = QID;
callbacks[QID] = callback;
states.addQuery(player,QID);
sendToAllClients(sel);
QID++;
}
void CGameHandler::sendToAllClients( CPackForClient * info )
{
tlog5 << "Sending to all clients a package of type " << typeid(*info).name() << std::endl;
for(std::set<CConnection*>::iterator i=conns.begin(); i!=conns.end();i++)
{
boost::unique_lock<boost::mutex> lock(*(*i)->wmx);
**i << 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((CPackForClient*)info);
if(gs->map->victoryCondition.condition == EVictoryConditionType::GATHERTROOP)
winLoseHandle();
}
// void CGameHandler::sendAndApply( SetGarrisons * info )
// {
// sendAndApply((CPackForClient*)info);
// if(gs->map->victoryCondition.condition == gatherTroop)
// for(std::map<ui32,CCreatureSet>::const_iterator i = info->garrs.begin(); i != info->garrs.end(); i++)
// checkLossVictory(getObj(i->first)->tempOwner);
// }
void CGameHandler::sendAndApply( SetResource * info )
{
sendAndApply((CPackForClient*)info);
if(gs->map->victoryCondition.condition == EVictoryConditionType::GATHERRESOURCE)
checkLossVictory(info->player);
}
void CGameHandler::sendAndApply( SetResources * info )
{
sendAndApply((CPackForClient*)info);
if(gs->map->victoryCondition.condition == EVictoryConditionType::GATHERRESOURCE)
checkLossVictory(info->player);
}
void CGameHandler::sendAndApply( NewStructures * info )
{
sendAndApply((CPackForClient*)info);
if(gs->map->victoryCondition.condition == EVictoryConditionType::BUILDCITY)
checkLossVictory(getTown(info->tid)->tempOwner);
}
void CGameHandler::save( const std::string &fname )
{
{
tlog0 << "Ordering clients to serialize...\n";
SaveGame sg(fname);
sendToAllClients(&sg);
}
{
tlog0 << "Serializing game info...\n";
CSaveFile save(GVCMIDirs.UserPath + "/Games/" + fname + ".vlgm1");
char hlp[8] = "VCMISVG";
save << hlp << static_cast<CMapHeader&>(*gs->map) << gs->scenarioOps << *VLC << gs;
}
{
tlog0 << "Serializing server info...\n";
CSaveFile save(GVCMIDirs.UserPath + "/Games/" + fname + ".vsgm1");
save << *this;
}
tlog0 << "Game has been successfully saved!\n";
}
void CGameHandler::close()
{
tlog0 << "We have been requested to close.\n";
if(gs->initialOpts->mode == StartInfo::DUEL)
{
exit(0);
}
//BOOST_FOREACH(CConnection *cc, conns)
// if(cc && cc->socket && cc->socket->is_open())
// cc->socket->close();
//exit(0);
}
bool CGameHandler::arrangeStacks( si32 id1, si32 id2, ui8 what, ui8 p1, ui8 p2, si32 val, ui8 player )
{
const CArmedInstance *s1 = static_cast<CArmedInstance*>(gs->map->objects[id1].get()),
*s2 = static_cast<CArmedInstance*>(gs->map->objects[id2].get());
const CCreatureSet &S1 = *s1, &S2 = *s2;
StackLocation sl1(s1, p1), sl2(s2, p2);
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 != 254) && s1->getStackCount(p1)) //why 254??
|| ((s2->tempOwner != player && s2->tempOwner != 254) && s2->getStackCount(p2)))
{
complain("Can't take troops from another player!");
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 != 254) && s2->getStackCount(p2)) && complain("Can't take troops from another player!")))
return false;
moveStack(sl1, sl2);
}
else if(what==3) //split
{
if ( (s1->tempOwner != player && s1->getStackCount(p1) < s1->getStackCount(p1) )
|| (s2->tempOwner != player && s2->getStackCount(p2) < 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, val - s2->getStackCount(p2));
//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;
}
int CGameHandler::getPlayerAt( CConnection *c ) const
{
std::set<int> all;
for(std::map<int,CConnection*>::const_iterator i=connections.begin(); i!=connections.end(); i++)
if(i->second == c)
all.insert(i->first);
switch(all.size())
{
case 0:
return 255;
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,int(gs->currentPlayer)))
return gs->currentPlayer;
else
return 253; //cannot say which player is it
}
}
}
bool CGameHandler::disbandCreature( si32 id, ui8 pos )
{
CArmedInstance *s1 = static_cast<CArmedInstance*>(gs->map->objects[id].get());
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( si32 tid, si32 bid, bool force /*=false*/ )
{
CGTownInstance * t = static_cast<CGTownInstance*>(gs->map->objects[tid].get());
CBuilding * b = VLC->buildh->buildings[t->subID][bid];
if( !force && gs->canBuildStructure(t,bid) != 7)
{
complain("Cannot build that building!");
return false;
}
if( !force && bid == 26) //grail
{
if(!t->visitingHero || !t->visitingHero->hasArt(2))
{
complain("Cannot build grail - hero doesn't have it");
return false;
}
//remove grail
removeArtifact(ArtifactLocation(t->visitingHero, t->visitingHero->getArtPos(2, false)));
}
NewStructures ns;
ns.tid = tid;
if ( (bid == 18) && (vstd::contains(t->builtBuildings,(t->town->hordeLvl[0]+37))) )
ns.bid.insert(19);//we have upgr. dwelling, upgr. horde will be builded as well
else if ( (bid == 24) && (vstd::contains(t->builtBuildings,(t->town->hordeLvl[1]+37))) )
ns.bid.insert(25);
else if(bid>36) //upg dwelling
{
if ( (bid-37 == t->town->hordeLvl[0]) && (vstd::contains(t->builtBuildings,18)) )
ns.bid.insert(19);//we have horde, will be upgraded as well as dwelling
if ( (bid-37 == t->town->hordeLvl[1]) && (vstd::contains(t->builtBuildings,24)) )
ns.bid.insert(25);
SetAvailableCreatures ssi;
ssi.tid = tid;
ssi.creatures = t->creatures;
ssi.creatures[bid-37].second.push_back(t->town->upgradedCreatures[bid-37]);
//Test for 2nd upgrade - add sharpshooters if grand elves dwelling was constructed
//if (t->subID == 1 && bid == 39)
// ssi.creatures[bid-37].second.push_back(137);
sendAndApply(&ssi);
}
else if(bid >= 30) //bas. dwelling
{
int crid = t->town->basicCreatures[bid-30];
SetAvailableCreatures ssi;
ssi.tid = tid;
ssi.creatures = t->creatures;
ssi.creatures[bid-30].first = VLC->creh->creatures[crid]->growth;
ssi.creatures[bid-30].second.push_back(crid);
sendAndApply(&ssi);
}
else if(bid == 11)
ns.bid.insert(27);
else if(bid == 12)
ns.bid.insert(28);
else if(bid == 13)
ns.bid.insert(29);
else if (t->subID == 4 && bid == 17) //veil of darkness
{
//handled via town->reacreateBonuses in apply
// GiveBonus gb(GiveBonus::TOWN);
// gb.bonus.type = Bonus::DARKNESS;
// gb.bonus.val = 20;
// gb.id = t->id;
// gb.bonus.duration = Bonus::PERMANENT;
// gb.bonus.source = Bonus::TOWN_STRUCTURE;
// gb.bonus.id = 17;
// sendAndApply(&gb);
}
else if ( t->subID == 5 && bid == 22 )
{
setPortalDwelling(t);
}
ns.bid.insert(bid);
ns.builded = force?t->builded:(t->builded+1);
sendAndApply(&ns);
//reveal ground for lookout tower
FoWChange fw;
fw.player = t->tempOwner;
fw.mode = 1;
getTilesInRange(fw.tiles,t->pos,t->getSightRadious(),t->tempOwner,1);
sendAndApply(&fw);
if (!force)
{
SetResources sr;
sr.player = t->tempOwner;
sr.res = gs->getPlayer(t->tempOwner)->resources - b->resources;
sendAndApply(&sr);
}
if(bid<5) //it's mage guild
{
if(t->visitingHero)
giveSpells(t,t->visitingHero);
if(t->garrisonHero)
giveSpells(t,t->garrisonHero);
}
if(t->visitingHero)
vistiCastleObjects (t, t->visitingHero);
if(t->garrisonHero)
vistiCastleObjects (t, t->garrisonHero);
checkLossVictory(t->tempOwner);
return true;
}
bool CGameHandler::razeStructure (si32 tid, si32 bid)
{
///incomplete, simply erases target building
CGTownInstance * t = static_cast<CGTownInstance*>(gs->map->objects[tid].get());
if (t->builtBuildings.find(bid) == t->builtBuildings.end())
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( si32 objid, ui32 crid, ui32 cram, si32 fromLvl )
{
const CGDwelling *dw = static_cast<CGDwelling*>(gs->map->objects[objid].get());
const CArmedInstance *dst = NULL;
const CCreature *c = VLC->creh->creatures[crid];
bool warMachine = c->hasBonusOfType(Bonus::SIEGE_WEAPON);
//TODO: test for owning
if(dw->ID == GameConstants::TOWNI_TYPE)
dst = (static_cast<const CGTownInstance *>(dw))->getUpperArmy();
else if(dw->ID == 17 || dw->ID == 20 || dw->ID == 78) //advmap dwelling
dst = getHero(gs->getPlayer(dw->tempOwner)->currentSelection); //TODO: check if current hero is really visiting dwelling
else if(dw->ID == 106)
dst = dynamic_cast<const CGHeroInstance *>(getTile(dw->visitablePos())->visitableObjects.back());
assert(dw && dst);
//verify
bool found = false;
int level = 0;
typedef std::pair<const int,int> Parka;
for(; level < dw->creatures.size(); level++) //iterate through all levels
{
if ( (fromLvl != -1) && ( level !=fromLvl ) )
continue;
const std::pair<ui32, std::vector<ui32> > &cur = dw->creatures[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[i] == crid)
break;
if(i < cur.second.size())
{
found = true;
cram = std::min(cram, cur.first); //reduce recruited amount up to available amount
break;
}
}
int slot = dst->getSlotFor(crid);
if( (!found && complain("Cannot recruit: no such creatures!"))
|| (cram > VLC->creh->creatures[crid]->maxAmount(gs->getPlayer(dst->tempOwner)->resources) && complain("Cannot recruit: lack of resources!"))
|| (cram<=0 && complain("Cannot recruit: cram <= 0!"))
|| (slot<0 && !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 146:
giveHeroNewArtifact(h, VLC->arth->artifacts[4], ArtifactPosition::MACH1);
break;
case 147:
giveHeroNewArtifact(h, VLC->arth->artifacts[6], ArtifactPosition::MACH3);
break;
case 148:
giveHeroNewArtifact(h, VLC->arth->artifacts[5], 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( ui32 objid, ui8 pos, ui32 upgID )
{
CArmedInstance *obj = static_cast<CArmedInstance*>(gs->map->objects[objid].get());
assert(obj->hasStackAtSlot(pos));
UpgradeInfo ui = gs->getUpgradeInfo(obj->getStack(pos));
int player = obj->tempOwner;
const PlayerState *p = getPlayer(player);
int crQuantity = obj->stacks[pos]->count;
int newIDpos= vstd::find_pos(ui.newID, upgID);//get position of new id in UpgradeInfo
TResources totalCost = ui.cost[newIDpos] * crQuantity;
//check if upgrade is possible
if( (ui.oldID<0 || newIDpos == -1 ) && complain("That upgrade is not possible!"))
{
return false;
}
//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[upgID]);
return true;
}
bool CGameHandler::changeStackType(const StackLocation &sl, 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
{
TSlots::const_iterator i = src->Slots().begin(); //iterator to stack to move
StackLocation sl(src, i->first); //location of stack to move
TSlot pos = dst->getSlotFor(i->second->type);
if(pos < 0)
{
//try to merge two other stacks to make place
std::pair<TSlot, TSlot> 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( si32 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 = -1;
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 = -1;
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(si32 srcHeroID, si32 destHeroID, ui16 srcSlot, ui16 destSlot)
{
const CGHeroInstance *srcHero = getHero(srcHeroID);
const CGHeroInstance *destHero = getHero(destHeroID);
ArtifactLocation src(srcHero, srcSlot), dst(destHero, destSlot);
// Make sure exchange is even possible between the two heroes.
if(!isAllowedExchange(srcHeroID, destHeroID))
COMPLAIN_RET("That heroes cannot make any exchange!");
const CArtifactInstance *srcArtifact = src.getArt();
const CArtifactInstance *destArtifact = dst.getArt();
if (srcArtifact == NULL)
COMPLAIN_RET("No artifact to move!");
if (destArtifact && srcHero->tempOwner != destHero->tempOwner)
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 || destSlot < GameConstants::BACKPACK_START)
&& srcArtifact && !srcArtifact->canBePutAt(dst, true))
COMPLAIN_RET("Cannot move artifact!");
if ((srcArtifact && srcArtifact->artType->id == GameConstants::ID_LOCK) || (destArtifact && destArtifact->artType->id == GameConstants::ID_LOCK))
COMPLAIN_RET("Cannot move artifact locks.");
if (destSlot >= GameConstants::BACKPACK_START && srcArtifact->artType->isBig())
COMPLAIN_RET("Cannot put big artifacts in backpack!");
if (srcSlot == ArtifactPosition::MACH4 || destSlot == ArtifactPosition::MACH4)
COMPLAIN_RET("Cannot move catapult!");
if(dst.slot >= GameConstants::BACKPACK_START)
vstd::amin(dst.slot, GameConstants::BACKPACK_START + dst.hero->artifactsInBackpack.size());
if (src.slot == dst.slot && src.hero == dst.hero)
COMPLAIN_RET("Won't move artifact: Dest same as source!");
//moving art to backpack is always allowed (we've ruled out exceptions)
if(destSlot >= GameConstants::BACKPACK_START)
{
moveArtifact(src, dst);
}
else //moving art to another slot
{
if(destArtifact) //old artifact must be removed first
{
moveArtifact(dst, ArtifactLocation(destHero, destHero->artifactsInBackpack.size() + GameConstants::BACKPACK_START));
}
moveArtifact(src, dst);
}
return true;
}
bool CGameHandler::moveArtifact(StackLocation s1, StackLocation s2, ui16 srcSlot, ui16 destSlot)
{
ArtifactLocation src(s1.getStack(), srcSlot), dst(s2.getStack(), destSlot);
moveArtifact(src, dst);
return true;
}
bool CGameHandler::moveArtifact(si32 srcHeroID, StackLocation s2, ui16 srcSlot, ui16 destSlot)
{
ArtifactLocation src(getHero(srcHeroID), srcSlot);
ArtifactLocation dst(s2.getStack(), destSlot);
moveArtifact(src, dst);
return true;
}
bool CGameHandler::moveArtifact(StackLocation s1, si32 destHeroID, ui16 srcSlot, ui16 destSlot)
{
ArtifactLocation src(s1.getStack(), srcSlot);
ArtifactLocation dst(getHero(destHeroID), destSlot);
//hero should not wear stack artifact
vstd::amin(dst.slot, GameConstants::BACKPACK_START + dst.hero->artifactsInBackpack.size()); //put on first free position
moveArtifact(src, dst);
return true;
}
void CGameHandler::moveArtifact(const ArtifactLocation &al1, const ArtifactLocation &al2)
{
MoveArtifact ma;
ma.src = al1;
ma.dst = al2;
sendAndApply(&ma);
}
/**
* 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 (si32 heroID, ui16 artifactSlot, bool assemble, ui32 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( ui32 hid, si32 aid )
{
CGHeroInstance *hero = gs->getHero(hid);
CGTownInstance *town = hero->visitedTown;
if(aid==0) //spellbook
{
if((!vstd::contains(town->builtBuildings,si32(EBuilding::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[0], 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(9+aid) && complain("Hero already has this machine!"))
|| (!vstd::contains(town->builtBuildings,si32(EBuilding::BLACKSMITH)) && complain("No blackismith!"))
|| (gs->getPlayer(hero->getOwner())->resources[Res::GOLD] < price && complain("Not enough gold!")) //no gold
|| ((!(town->subID == 6 && vstd::contains(town->builtBuildings,si32(EBuilding::SPECIAL_3) ) )
&& town->town->warMachine!= aid ) && complain("This machine is unavailable here!")))
{
return false;
}
giveResource(hero->getOwner(),Res::GOLD,-price);
giveHeroNewArtifact(hero, VLC->arth->artifacts[aid], 9+aid);
return true;
}
return false;
}
bool CGameHandler::buyArtifact(const IMarket *m, const CGHeroInstance *h, int rid, int 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 == GameConstants::TOWNI_TYPE)
{
saa.id = -1;
saa.arts = CGTownInstance::merchantArtifacts;
}
else if(const CGBlackMarket *bm = dynamic_cast<const CGBlackMarket *>(m->o)) //black market
{
saa.id = bm->id;
saa.arts = bm->artifacts;
}
else
COMPLAIN_RET("Wrong marktet...");
bool found = false;
BOOST_FOREACH(const CArtifact *&art, saa.arts)
{
if(art && art->id == aid)
{
art = NULL;
found = true;
break;
}
}
if(!found)
COMPLAIN_RET("Cannot find selected artifact on the list");
sendAndApply(&saa);
giveHeroNewArtifact(h, VLC->arth->artifacts[aid], -2);
return true;
}
bool CGameHandler::sellArtifact(const IMarket *m, const CGHeroInstance *h, int aid, int rid)
{
const CArtifactInstance *art = h->getArtByInstanceId(aid);
if(!art)
COMPLAIN_RET("There is no artifact to sell!");
if(art->artType->id < 7)
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;
}
bool CGameHandler::buySecSkill( const IMarket *m, const CGHeroInstance *h, int skill)
{
if (!h)
COMPLAIN_RET("You need hero to buy a skill!");
if (h->getSecSkillLevel(static_cast<CGHeroInstance::SecondarySkill>(skill)))
COMPLAIN_RET("Hero already know this skill");
if (h->secSkills.size() >= GameConstants::SKILL_PER_HERO)//can't learn more skills
COMPLAIN_RET("Hero can't learn any more skills");
if (h->type->heroClass->proSec[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->id, skill, 1, true);
return true;
}
bool CGameHandler::tradeResources(const IMarket *market, ui32 val, ui8 player, ui32 id1, ui32 id2)
{
int r1 = gs->getPlayer(player)->resources[id1],
r2 = gs->getPlayer(player)->resources[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 = id1;
sr.val = r1 - b1 * units;
sendAndApply(&sr);
sr.resid = id2;
sr.val = r2 + b2 * units;
sendAndApply(&sr);
return true;
}
bool CGameHandler::sellCreatures(ui32 count, const IMarket *market, const CGHeroInstance * hero, ui32 slot, ui32 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->Slots().size() == 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, ui32 slot)
{
const CArmedInstance *army = NULL;
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);
int resCreature;//resulting creature - bone dragons or skeletons
if (s.hasBonusOfType(Bonus::DRAGON_NATURE))
resCreature = 68;
else
resCreature = 56;
changeStackType(StackLocation(army, slot), VLC->creh->creatures[resCreature]);
return true;
}
bool CGameHandler::sendResources(ui32 val, ui8 player, ui32 r1, ui32 r2)
{
const PlayerState *p2 = gs->getPlayer(r2, false);
if(!p2 || p2->status != PlayerState::INGAME)
{
complain("Dest player must be in game!");
return false;
}
si32 curRes1 = gs->getPlayer(player)->resources[r1], curRes2 = gs->getPlayer(r2)->resources[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( si32 hid, ui8 formation )
{
gs->getHero(hid)-> formation = formation;
return true;
}
bool CGameHandler::hireHero(const CGObjectInstance *obj, ui8 hid, ui8 player)
{
const PlayerState *p = gs->getPlayer(player);
const CGTownInstance *t = gs->getTown(obj->id);
//common preconditions
if( (p->resources[Res::GOLD]<2500 && 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!")))
return false;
if(t) //tavern in town
{
if( (!vstd::contains(t->builtBuildings,EBuilding::TAVERN) && complain("No tavern!"))
|| (t->visitingHero && complain("There is visiting hero - no place!")))
return false;
}
else if(obj->ID == 95) //Tavern on adv map
{
if(getTile(obj->visitablePos())->visitableObjects.back() != obj && complain("Tavern entry must be unoccupied!"))
return false;
}
const CGHeroInstance *nh = p->availableHeroes[hid];
assert(nh);
HeroRecruited hr;
hr.tid = obj->id;
hr.hid = nh->subID;
hr.player = player;
hr.tile = obj->visitablePos() + nh->getVisitableOffset();
sendAndApply(&hr);
bmap<ui32, ConstTransitivePtr<CGHeroInstance> > pool = gs->unusedHeroesFromPool();
const CGHeroInstance *theOtherHero = p->availableHeroes[!hid];
const CGHeroInstance *newHero = gs->hpool.pickHeroFor(false, player, getNativeTown(player), pool, theOtherHero->type->heroClass);
SetAvailableHeroes sah;
sah.player = player;
if(newHero)
{
sah.hid[hid] = newHero->subID;
sah.army[hid].clear();
sah.army[hid].setCreature(0, VLC->creh->nameToID[newHero->type->refTypeStack[0]],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[Res::GOLD] - 2500;
sendAndApply(&sr);
if(t)
{
vistiCastleObjects (t, nh);
giveSpells (t,nh);
}
return true;
}
bool CGameHandler::queryReply(ui32 qid, ui32 answer, ui8 player)
{
boost::unique_lock<boost::recursive_mutex> lock(gsm);
states.removeQuery(player, qid);
if(vstd::contains(callbacks,qid))
{
CFunctionList<void(ui32)> callb = callbacks[qid];
callbacks.erase(qid);
if(callb)
callb(answer);
}
else if(vstd::contains(garrisonCallbacks,qid))
{
if(garrisonCallbacks[qid])
garrisonCallbacks[qid]();
garrisonCallbacks.erase(qid);
allowedExchanges.erase(qid);
}
else
{
tlog1 << "Unknown query reply...\n";
return false;
}
return true;
}
static EndAction end_action;
bool CGameHandler::makeBattleAction( BattleAction &ba )
{
tlog1 << "\tMaking action of type " << ba.actionType << std::endl;
bool ok = true;
switch(ba.actionType)
{
case BattleAction::END_TACTIC_PHASE: //wait
{
StartAction start_action(ba);
sendAndApply(&start_action);
sendAndApply(&end_action);
break;
}
case BattleAction::WALK: //walk
{
StartAction start_action(ba);
sendAndApply(&start_action); //start movement
moveStack(ba.stackNumber,ba.destinationTile); //move
sendAndApply(&end_action);
break;
}
case BattleAction::DEFEND: //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->stacks[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 BattleAction::WAIT: //wait
{
StartAction start_action(ba);
sendAndApply(&start_action);
sendAndApply(&end_action);
break;
}
case BattleAction::RETREAT: //retreat/flee
{
if(!gs->curB->battleCanFlee(gs->curB->sides[ba.side]))
complain("Cannot retreat!");
else
setBattleResult(1, !ba.side); //surrendering side loses
break;
}
case BattleAction::SURRENDER:
{
int player = gs->curB->sides[ba.side];
int cost = gs->curB->getSurrenderingCost(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(2, !ba.side); //surrendering side loses
}
break;
}
break;
case BattleAction::WALK_AND_ATTACK: //walk or attack
{
StartAction start_action(ba);
sendAndApply(&start_action); //start movement and attack
int startingPos = gs->curB->getStack(ba.stackNumber)->position;
int distance = moveStack(ba.stackNumber, ba.destinationTile);
CStack *curStack = gs->curB->getStack(ba.stackNumber),
*stackAtEnd = gs->curB->getStackT(ba.additionalInfo);
if(curStack->position != ba.destinationTile //we wasn't able to reach destination tile
&& !(curStack->doubleWide()
&& ( curStack->position == ba.destinationTile + (curStack->attackerOwned ? +1 : -1 ) )
) //nor occupy specified hex
)
{
std::string problem = "We cannot move this stack to its destination " + curStack->getCreature()->namePl;
tlog3 << problem << std::endl;
complain(problem);
ok = false;
sendAndApply(&end_action);
break;
}
if(stackAtEnd && curStack->ID == stackAtEnd->ID) //we should just move, it will be handled by following check
{
stackAtEnd = NULL;
}
if(!stackAtEnd)
{
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(curStack, stackAtEnd) )
{
complain("Attack cannot be performed!");
sendAndApply(&end_action);
ok = false;
break;
}
//attack
{
BattleAttack bat;
prepareAttack(bat, curStack, stackAtEnd, distance, ba.additionalInfo);
handleAttackBeforeCasting(bat); //only before first attack
sendAndApply(&bat);
handleAfterAttackCasting(bat);
}
//counterattack
if(!curStack->hasBonusOfType(Bonus::BLOCKS_RETALIATION)
&& stackAtEnd->ableToRetaliate())
{
BattleAttack bat;
prepareAttack(bat, stackAtEnd, curStack, 0, curStack->position);
bat.flags |= BattleAttack::COUNTER;
sendAndApply(&bat);
handleAfterAttackCasting(bat);
}
//second attack
if(curStack->valOfBonuses(Bonus::ADDITIONAL_ATTACK) > 0
&& !curStack->hasBonusOfType(Bonus::SHOOTER)
&& curStack->alive()
&& stackAtEnd->alive() )
{
BattleAttack bat;
prepareAttack(bat, curStack, stackAtEnd, 0, ba.additionalInfo);
sendAndApply(&bat);
handleAfterAttackCasting(bat);
}
//return
if(curStack->hasBonusOfType(Bonus::RETURN_AFTER_STRIKE) && startingPos != curStack->position && curStack->alive())
{
moveStack(ba.stackNumber, startingPos);
//NOTE: curStack->ID == ba.stackNumber (rev 1431)
}
sendAndApply(&end_action);
break;
}
case BattleAction::SHOOT: //shoot
{
CStack *curStack = gs->curB->getStack(ba.stackNumber),
*destStack= gs->curB->getStackT(ba.destinationTile);
if( !gs->curB->battleCanShoot(curStack, ba.destinationTile) )
break;
StartAction start_action(ba);
sendAndApply(&start_action); //start shooting
{
BattleAttack bat;
bat.flags |= BattleAttack::SHOT;
prepareAttack(bat, curStack, destStack, 0, ba.destinationTile);
handleAttackBeforeCasting(bat);
sendAndApply(&bat);
handleAfterAttackCasting(bat);
}
//ballista & artillery handling
if(destStack->alive() && curStack->getCreature()->idNumber == 146)
{
BattleAttack bat2;
bat2.flags |= BattleAttack::SHOT;
prepareAttack(bat2, curStack, destStack, 0, ba.destinationTile);
sendAndApply(&bat2);
}
//TODO: allow more than one additional attack
if(curStack->valOfBonuses(Bonus::ADDITIONAL_ATTACK) > 0 //if unit shots twice let's make another shot
&& curStack->alive()
&& destStack->alive()
&& curStack->shots
)
{
BattleAttack bat;
bat.flags |= BattleAttack::SHOT;
prepareAttack(bat, curStack, destStack, 0, ba.destinationTile);
sendAndApply(&bat);
handleAfterAttackCasting(bat);
}
sendAndApply(&end_action);
break;
}
case BattleAction::CATAPULT: //catapult
{
StartAction start_action(ba);
sendAndApply(&start_action);
const CGHeroInstance * attackingHero = gs->curB->heroes[ba.side];
CHeroHandler::SBallisticsLevelInfo sbi = VLC->heroh->ballistics[attackingHero->getSecSkillLevel(CGHeroInstance::BALLISTICS)];
int attackedPart = gs->curB->hexToWallPart(ba.destinationTile);
if(attackedPart < 0)
{
complain("catapult tried to attack non-catapultable hex!");
break;
}
int wallInitHP = gs->curB->si.wallState[attackedPart];
int dmgAlreadyDealt = 0; //in successive iterations damage is dealt but not yet subtracted from wall's HPs
for(int g=0; g<sbi.shots; ++g)
{
if(wallInitHP + dmgAlreadyDealt == 3) //it's not destroyed
continue;
CatapultAttack ca; //package for clients
std::pair< std::pair< ui8, si16 >, ui8> attack; //<< attackedPart , destination tile >, damageDealt >
attack.first.first = attackedPart;
attack.first.second = ba.destinationTile;
attack.second = 0;
int chanceForHit = 0;
int dmgChance[] = { sbi.noDmg, sbi.oneDmg, sbi.twoDmg }; //dmgChance[i] - chance for doing i dmg when hit is successful
switch(attackedPart)
{
case 0: //keep
chanceForHit = sbi.keep;
break;
case 1: //bottom tower
case 6: //upper tower
chanceForHit = sbi.tower;
break;
case 2: //bottom wall
case 3: //below gate
case 4: //over gate
case 5: //upper wall
chanceForHit = sbi.wall;
break;
case 7: //gate
chanceForHit = sbi.gate;
break;
}
if(rand()%100 <= chanceForHit) //hit is successful
{
int dmgRand = rand()%100;
//accumulating dmgChance
dmgChance[1] += dmgChance[0];
dmgChance[2] += dmgChance[1];
//calculating dealt damage
for(int v = 0; v < ARRAY_COUNT(dmgChance); ++v)
{
if(dmgRand <= dmgChance[v])
{
attack.second = std::min(3 - dmgAlreadyDealt - wallInitHP, v);
dmgAlreadyDealt += attack.second;
break;
}
}
//removing creatures in turrets / keep if one is destroyed
if(attack.second > 0 && (attackedPart == 0 || attackedPart == 1 || attackedPart == 6))
{
int posRemove = -1;
switch(attackedPart)
{
case 0: //keep
posRemove = -2;
break;
case 1: //bottom tower
posRemove = -3;
break;
case 6: //upper tower
posRemove = -4;
break;
}
BattleStacksRemoved bsr;
for(int g=0; g<gs->curB->stacks.size(); ++g)
{
if(gs->curB->stacks[g]->position == posRemove)
{
bsr.stackIDs.insert( gs->curB->stacks[g]->ID );
break;
}
}
sendAndApply(&bsr);
}
}
ca.attacker = ba.stackNumber;
ca.attackedParts.insert(attack);
sendAndApply(&ca);
}
sendAndApply(&end_action);
break;
}
case BattleAction::STACK_HEAL: //healing with First Aid Tent
{
StartAction start_action(ba);
sendAndApply(&start_action);
const CGHeroInstance * attackingHero = gs->curB->heroes[ba.side];
CStack *healer = gs->curB->getStack(ba.stackNumber),
*destStack = gs->curB->getStackT(ba.destinationTile);
if(healer == NULL || destStack == NULL || !healer->hasBonusOfType(Bonus::HEALER))
{
complain("There is either no healer, no destination, or healer cannot heal :P");
}
int maxHealable = destStack->MaxHealth() - destStack->firstHPleft;
int maxiumHeal = healer->count * std::max(10, attackingHero->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, 27));
int healed = std::min(maxHealable, maxiumHeal);
if(healed == 0)
{
//nothing to heal.. should we complain?
}
else
{
StacksHealedOrResurrected shr;
shr.lifeDrain = (ui8)false;
shr.tentHealing = (ui8)true;
shr.drainedFrom = ba.stackNumber;
StacksHealedOrResurrected::HealInfo hi;
hi.healedHP = healed;
hi.lowLevelResurrection = 0;
hi.stackID = destStack->ID;
shr.healedStacks.push_back(hi);
sendAndApply(&shr);
}
sendAndApply(&end_action);
break;
}
case BattleAction::DAEMON_SUMMONING:
//TODO: From Strategija:
//Summon Demon is a level 2 spell.
{
StartAction start_action(ba);
sendAndApply(&start_action);
CStack *summoner = gs->curB->getStack(ba.stackNumber),
*destStack = gs->curB->getStackT(ba.destinationTile, false);
BattleStackAdded bsa;
bsa.attacker = summoner->attackerOwned;
bsa.creID = summoner->getBonus(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);
bsa.amount = std::min ((ui32)(risedHp / VLC->creh->creatures[bsa.creID]->MaxHealth()), 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 BattleAction::MONSTER_SPELL:
{
StartAction start_action(ba);
sendAndApply(&start_action);
CStack * stack = gs->curB->getStack(ba.stackNumber);
int spellID = ba.additionalInfo;
BattleHex destination(ba.destinationTile);
const Bonus *randSpellcaster = stack->getBonus(Selector::type(Bonus::RANDOM_SPELLCASTER));
const Bonus * spellcaster = stack->getBonus(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
{
int spellLvl = 0;
if (spellcaster)
vstd::amax(spellLvl, spellcaster->val);
if (randSpellcaster)
vstd::amax(spellLvl, randSpellcaster->val);
vstd::amin (spellLvl, 3);
int casterSide = gs->curB->whatSide(stack->owner);
const CGHeroInstance * secHero = gs->curB->getHero(gs->curB->theOtherPlayer(stack->owner));
handleSpellCasting(spellID, spellLvl, destination, casterSide, stack->owner, NULL, secHero, 0, ECastingMode::CREATURE_ACTIVE_CASTING, stack);
}
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( ui8 player, const std::string &message )
{
bool cheated=true;
PlayerMessage temp_message(player,message);
sendAndApply(&temp_message);
if(message == "vcmiistari") //give all spells and 999 mana
{
SetMana sm;
ChangeSpells cs;
CGHeroInstance *h = gs->getHero(gs->getPlayer(player)->currentSelection);
if(!h && complain("Cannot realize cheat, no hero selected!")) return;
sm.hid = cs.hid = h->id;
//give all spells
cs.learn = 1;
for(int i=0;i<VLC->spellh->spells.size();i++)
{
if(!VLC->spellh->spells[i]->creatureAbility)
cs.spells.insert(i);
}
//give mana
sm.val = 999;
if(!h->hasSpellbook()) //hero doesn't have spellbook
giveHeroNewArtifact(h, VLC->arth->artifacts[0], ArtifactPosition::SPELLBOOK); //give spellbook
sendAndApply(&cs);
sendAndApply(&sm);
}
else if (message == "vcmiarmenelos") //build all buildings in selected town
{
CGTownInstance *town = gs->getTown(gs->getPlayer(player)->currentSelection);
if (town)
{
BOOST_FOREACH (CBuildingHandler::TBuildingsMap::value_type &build, VLC->buildh->buildings[town->subID])
{
if (!vstd::contains(town->builtBuildings, build.first)
&& !build.second->Name().empty())
{
buildStructure(town->id, build.first, true);
}
}
}
}
else if(message == "vcmiainur") //gives 5 archangels into each slot
{
CGHeroInstance *hero = gs->getHero(gs->getPlayer(player)->currentSelection);
const CCreature *archangel = VLC->creh->creatures[13];
if(!hero) return;
for(int i = 0; i < GameConstants::ARMY_SIZE; i++)
if(!hero->hasStackAtSlot(i))
insertNewStack(StackLocation(hero, i), archangel, 5);
}
else if(message == "vcmiangband") //gives 10 black knight into each slot
{
CGHeroInstance *hero = gs->getHero(gs->getPlayer(player)->currentSelection);
const CCreature *blackKnight = VLC->creh->creatures[66];
if(!hero) return;
for(int i = 0; i < GameConstants::ARMY_SIZE; i++)
if(!hero->hasStackAtSlot(i))
insertNewStack(StackLocation(hero, i), blackKnight, 10);
}
else if(message == "vcminoldor") //all war machines
{
CGHeroInstance *hero = gs->getHero(gs->getPlayer(player)->currentSelection);
if(!hero) return;
if(!hero->getArt(ArtifactPosition::MACH1))
giveHeroNewArtifact(hero, VLC->arth->artifacts[4], ArtifactPosition::MACH1);
if(!hero->getArt(ArtifactPosition::MACH2))
giveHeroNewArtifact(hero, VLC->arth->artifacts[5], ArtifactPosition::MACH2);
if(!hero->getArt(ArtifactPosition::MACH3))
giveHeroNewArtifact(hero, VLC->arth->artifacts[6], ArtifactPosition::MACH3);
}
else if(message == "vcminahar") //1000000 movement points
{
CGHeroInstance *hero = gs->getHero(gs->getPlayer(player)->currentSelection);
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<7;i++)
sr.res[i] += 100;
sr.res[6] += 19900;
sendAndApply(&sr);
}
else if(message == "vcmieagles") //reveal FoW
{
FoWChange fc;
fc.mode = 1;
fc.player = player;
int3 * hlp_tab = new int3[gs->map->width * gs->map->height * (gs->map->twoLevel + 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+1;k++)
if(!gs->getPlayerTeam(fc.player)->fogOfWarMap[i][j][k])
hlp_tab[lastUnc++] = int3(i,j,k);
fc.tiles.insert(hlp_tab, hlp_tab + lastUnc);
delete [] hlp_tab;
sendAndApply(&fc);
}
else if(message == "vcmiglorfindel") //selected hero gains a new level
{
CGHeroInstance *hero = gs->getHero(gs->getPlayer(player)->currentSelection);
changePrimSkill(hero->id,4,VLC->heroh->reqExp(hero->level+1) - VLC->heroh->reqExp(hero->level));
}
else if(message == "vcmisilmaril") //player wins
{
gs->getPlayer(player)->enteredWinningCheatCode = 1;
}
else if(message == "vcmimelkor") //player looses
{
gs->getPlayer(player)->enteredLosingCheatCode = 1;
}
else if (message == "vcmiforgeofnoldorking") //hero gets all artifacts except war machines, spell scrolls and spell book
{
CGHeroInstance *hero = gs->getHero(gs->getPlayer(player)->currentSelection);
if(!hero) return;
for (int g=7; g<=140; ++g)
giveHeroNewArtifact(hero, VLC->arth->artifacts[g], -1);
}
else
cheated = false;
if(cheated)
{
SystemMessage temp_message(VLC->generaltexth->allTexts[260]);
sendAndApply(&temp_message);
checkLossVictory(player);//Player enter win code or got required art\creature
}
}
void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex destination, ui8 casterSide, ui8 casterColor, const CGHeroInstance * caster, const CGHeroInstance * secHero, int usedSpellPower, ECastingMode::ECastingMode mode, const CStack * stack)
{
const CSpell *spell = VLC->spellh->spells[spellID];
BattleSpellCast sc;
sc.side = casterSide;
sc.id = spellID;
sc.skill = spellLvl;
sc.tile = destination;
sc.dmgToDisplay = 0;
sc.castedByHero = (bool)caster;
sc.attackerType = (stack ? stack->type->idNumber : -1);
sc.manaGained = 0;
sc.spellCost = 0;
if (caster) //calculate spell cost
{
sc.spellCost = gs->curB->getSpellCost(VLC->spellh->spells[spellID], caster);
if (secHero && mode == ECastingMode::HERO_CASTING) //handle mana channel
{
int manaChannel = 0;
BOOST_FOREACH(CStack * stack, gs->curB->stacks) //TODO: shouldn't bonus system handle it somehow?
{
if (stack->owner == secHero->tempOwner)
{
vstd::amax(manaChannel, stack->valOfBonuses(Bonus::MANA_CHANNELING));
}
}
sc.manaGained = (manaChannel * sc.spellCost) / 100;
}
}
//calculating affected creatures for all spells
std::set<CStack*> attackedCres;
if (mode != ECastingMode::ENCHANTER_CASTING)
{
attackedCres = gs->curB->getAttackedCreatures(spell, spellLvl, casterColor, destination);
for(std::set<CStack*>::const_iterator it = attackedCres.begin(); it != attackedCres.end(); ++it)
{
sc.affectedCres.insert((*it)->ID);
}
}
else //enchanter - hit all possible stacks
{
BOOST_FOREACH (CStack * stack, gs->curB->stacks)
{
/*if it's non negative spell and our unit or non positive spell and hostile unit */
if((!spell->isNegative() && stack->owner == casterColor)
|| (!spell->isPositive() && stack->owner != casterColor))
{
if(stack->isValidTarget()) //TODO: allow dead targets somewhere in the future
attackedCres.insert(stack);
}
}
}
//checking if creatures resist
sc.resisted = gs->curB->calculateResistedStacks(spell, caster, secHero, attackedCres, casterColor, mode, usedSpellPower, spellLvl);
//calculating dmg to display
for(std::set<CStack*>::iterator it = attackedCres.begin(); it != attackedCres.end(); ++it)
{
if(vstd::contains(sc.resisted, (*it)->ID)) //this creature resisted the spell
continue;
sc.dmgToDisplay += gs->curB->calculateSpellDmg(spell, caster, *it, spellLvl, usedSpellPower);
}
if (spellID == Spells::DEATH_STARE || spellID == Spells::ACID_BREATH_DAMAGE)
{
sc.dmgToDisplay = usedSpellPower;
if (spellID == Spells::DEATH_STARE)
vstd::amin(sc.dmgToDisplay, (*attackedCres.begin())->count); //stack is already reduced after attack
}
StacksInjured si;
//applying effects
switch (spellID)
{ //damage spells
case Spells::MAGIC_ARROW:
case Spells::ICE_BOLT:
case Spells::LIGHTNING_BOLT:
case Spells::IMPLOSION:
case Spells::FROST_RING:
case Spells::FIREBALL:
case Spells::INFERNO:
case Spells::METEOR_SHOWER:
case Spells::DEATH_RIPPLE:
case Spells::DESTROY_UNDEAD:
case Spells::ARMAGEDDON:
case Spells::TITANS_LIGHTNING_BOLT:
case Spells::THUNDERBOLT: //(thunderbirds)
{
int spellDamage = 0;
if (stack && mode != ECastingMode::MAGIC_MIRROR)
{
int unitSpellPower = stack->valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, spellID);
if (unitSpellPower)
sc.dmgToDisplay = spellDamage = stack->count * unitSpellPower; //TODO: handle immunities
else //Faerie Dragon
{
usedSpellPower = stack->valOfBonuses(Bonus::CREATURE_SPELL_POWER) * stack->count / 100;
sc.dmgToDisplay = 0;
}
}
for(std::set<CStack*>::iterator it = attackedCres.begin(); it != attackedCres.end(); ++it)
{
if(vstd::contains(sc.resisted, (*it)->ID)) //this creature resisted the spell
continue;
BattleStackAttacked bsa;
if ((destination > -1 && (*it)->coversPos(destination)) || spell->range[spellLvl] == "X") //display effect only upon primary target of area spell
{
bsa.flags |= BattleStackAttacked::EFFECT;
bsa.effect = spell->mainEffectAnim;
}
if (spellDamage)
bsa.damageAmount = spellDamage;
else
{
bsa.damageAmount = gs->curB->calculateSpellDmg(spell, caster, *it, spellLvl, usedSpellPower);
sc.dmgToDisplay += bsa.damageAmount;
}
bsa.stackAttacked = (*it)->ID;
bsa.attackerID = -1;
(*it)->prepareAttacked(bsa);
si.stacks.push_back(bsa);
}
break;
}
// permanent effects
case Spells::SHIELD:
case Spells::AIR_SHIELD:
case Spells::FIRE_SHIELD:
case Spells::PROTECTION_FROM_AIR:
case Spells::PROTECTION_FROM_FIRE:
case Spells::PROTECTION_FROM_WATER:
case Spells::PROTECTION_FROM_EARTH:
case Spells::ANTI_MAGIC:
case Spells::MAGIC_MIRROR:
case Spells::BLESS:
case Spells::CURSE:
case Spells::BLOODLUST:
case Spells::PRECISION:
case Spells::WEAKNESS:
case Spells::STONE_SKIN:
case Spells::DISRUPTING_RAY:
case Spells::PRAYER:
case Spells::MIRTH:
case Spells::SORROW:
case Spells::FORTUNE:
case Spells::MISFORTUNE:
case Spells::HASTE:
case Spells::SLOW:
case Spells::SLAYER:
case Spells::FRENZY:
case Spells::COUNTERSTRIKE:
case Spells::BERSERK:
case Spells::HYPNOTIZE:
case Spells::FORGETFULNESS:
case Spells::BLIND:
case Spells::STONE_GAZE:
case Spells::POISON:
case Spells::BIND:
case Spells::DISEASE:
case Spells::PARALYZE:
case Spells::AGE:
case Spells::ACID_BREATH_DEFENSE:
{
int stackSpellPower = 0;
if (stack && mode != ECastingMode::MAGIC_MIRROR)
{
stackSpellPower = stack->valOfBonuses(Bonus::CREATURE_ENCHANT_POWER);
}
SetStackEffect sse;
Bonus pseudoBonus;
pseudoBonus.sid = spellID;
pseudoBonus.val = spellLvl;
pseudoBonus.turnsRemain = gs->curB->calculateSpellDuration(spell, caster, stackSpellPower ? stackSpellPower : usedSpellPower);
CStack::stackEffectToFeature(sse.effect, pseudoBonus);
if (spellID == 72 && stack)//bind
{
sse.effect.back().additionalInfo = stack->ID; //we need to know who casted Bind
}
const Bonus * bonus = NULL;
if (caster)
bonus = caster->getBonus(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, spellID));
si32 power = 0;
for(std::set<CStack*>::iterator it = attackedCres.begin(); it != attackedCres.end(); ++it)
{
if(vstd::contains(sc.resisted, (*it)->ID)) //this creature resisted the spell
continue;
sse.stacks.push_back((*it)->ID);
//Apply hero specials - peculiar enchants
if ((*it)->base) // no war machines - TODO: make it work
{
ui8 tier = (*it)->base->type->level;
if (bonus)
{
switch(bonus->additionalInfo)
{
case 0: //normal
{
switch(tier)
{
case 1: case 2:
power = 3;
break;
case 3: case 4:
power = 2;
break;
case 5: case 6:
power = 1;
break;
}
Bonus specialBonus(sse.effect.back());
specialBonus.val = power; //it doesn't necessarily make sense for some spells, use it wisely
sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> ((*it)->ID, specialBonus)); //additional premy to given effect
}
break;
case 1: //only Coronius as yet
{
power = std::max(5 - tier, 0);
Bonus specialBonus = CStack::featureGenerator(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK, power, pseudoBonus.turnsRemain);
specialBonus.sid = spellID;
sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> ((*it)->ID, specialBonus)); //additional attack to Slayer effect
}
break;
}
}
if (caster && caster->hasBonusOfType(Bonus::SPECIAL_BLESS_DAMAGE, spellID)) //TODO: better handling of bonus percentages
{
int damagePercent = caster->level * caster->valOfBonuses(Bonus::SPECIAL_BLESS_DAMAGE, spellID) / tier;
Bonus specialBonus = CStack::featureGenerator(Bonus::CREATURE_DAMAGE, 0, damagePercent, pseudoBonus.turnsRemain);
specialBonus.valType = Bonus::PERCENT_TO_ALL;
specialBonus.sid = spellID;
sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> ((*it)->ID, specialBonus));
}
}
}
if(!sse.stacks.empty())
sendAndApply(&sse);
break;
}
case Spells::TELEPORT:
{
BattleStackMoved bsm;
bsm.distance = -1;
bsm.stack = gs->curB->activeStack;
std::vector<BattleHex> tiles;
tiles.push_back(destination);
bsm.tilesToMove = tiles;
bsm.teleporting = true;
sendAndApply(&bsm);
break;
}
case Spells::CURE:
case Spells::RESURRECTION:
case Spells::ANIMATE_DEAD:
{
int hpGained = 0;
if (stack)
{
int unitSpellPower = stack->valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, spellID);
if (unitSpellPower)
hpGained = stack->count * unitSpellPower; //Archangel
else //Faerie Dragon-like effect - unused fo far
usedSpellPower = stack->valOfBonuses(Bonus::CREATURE_SPELL_POWER) * stack->count / 100;
}
StacksHealedOrResurrected shr;
shr.lifeDrain = (ui8)false;
shr.tentHealing = (ui8)false;
for(std::set<CStack*>::iterator it = attackedCres.begin(); it != attackedCres.end(); ++it)
{
if(vstd::contains(sc.resisted, (*it)->ID) //this creature resisted the spell
|| (spellID == Spells::ANIMATE_DEAD && !(*it)->hasBonusOfType(Bonus::UNDEAD)) //we try to cast animate dead on living stack
)
continue;
StacksHealedOrResurrected::HealInfo hi;
hi.stackID = (*it)->ID;
if (stack)
{
if (hpGained)
{
hi.healedHP = gs->curB->calculateHealedHP(hpGained, spell, *it);
}
else
hi.healedHP = gs->curB->calculateHealedHP(spell, usedSpellPower, spellLvl, *it);
}
else
hi.healedHP = gs->curB->calculateHealedHP(caster, spell, *it);
hi.lowLevelResurrection = spellLvl <= 1;
shr.healedStacks.push_back(hi);
}
if(!shr.healedStacks.empty())
sendAndApply(&shr);
break;
}
case Spells::SUMMON_FIRE_ELEMENTAL:
case Spells::SUMMON_EARTH_ELEMENTAL:
case Spells::SUMMON_WATER_ELEMENTAL:
case Spells::SUMMON_AIR_ELEMENTAL:
{ //elemental summoning
int creID;
switch(spellID)
{
case Spells::SUMMON_FIRE_ELEMENTAL:
creID = 114;
break;
case Spells::SUMMON_EARTH_ELEMENTAL:
creID = 113;
break;
case Spells::SUMMON_WATER_ELEMENTAL:
creID = 115;
break;
case Spells::SUMMON_AIR_ELEMENTAL:
creID = 112;
break;
}
BattleStackAdded bsa;
bsa.creID = creID;
bsa.attacker = !(bool)casterSide;
bsa.summoned = true;
bsa.pos = gs->curB->getAvaliableHex(creID, !(bool)casterSide); //TODO: unify it
//TODO stack casting -> probably power will be zero; set the proper number of creatures manually
int percentBonus = caster ? caster->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, spellID) : 0;
bsa.amount = usedSpellPower * VLC->spellh->spells[spellID]->powers[spellLvl] *
(100 + percentBonus) / 100.0; //new feature - percentage bonus
if(bsa.amount)
sendAndApply(&bsa);
else
complain("Summoning elementals didn't summon any!");
}
break;
case Spells::CLONE:
{
CStack * clonedStack = NULL;
if (attackedCres.size())
clonedStack = *attackedCres.begin();
if (!clonedStack)
{
complain ("No target stack to clone!");
return;
}
BattleStackAdded bsa;
bsa.creID = clonedStack->type->idNumber;
bsa.attacker = !(bool)casterSide;
bsa.summoned = true;
bsa.pos = gs->curB->getAvaliableHex(bsa.creID, !(bool)casterSide); //TODO: unify it
bsa.amount = clonedStack->count;
sendAndApply (&bsa);
BattleSetStackProperty ssp;
ssp.stackID = gs->curB->stacks.back()->ID; //how to get recent stack?
ssp.which = BattleSetStackProperty::CLONED; //using enum values
ssp.val = 0;
ssp.absolute = 1;
sendAndApply(&ssp);
}
break;
case Spells::REMOVE_OBSTACLE:
{
ObstaclesRemoved obr;
for(int g=0; g<gs->curB->obstacles.size(); ++g)
{
std::vector<BattleHex> blockedHexes = VLC->heroh->obstacles[gs->curB->obstacles[g].ID].getBlocked(gs->curB->obstacles[g].pos);
if(vstd::contains(blockedHexes, destination)) //this obstacle covers given hex
{
obr.obstacles.insert(gs->curB->obstacles[g].uniqueID);
}
}
if(!obr.obstacles.empty())
sendAndApply(&obr);
break;
}
break;
case Spells::DEATH_STARE: //handled in a bit different way
{
for(std::set<CStack*>::iterator it = attackedCres.begin(); it != attackedCres.end(); ++it)
{
if((*it)->hasBonusOfType(Bonus::UNDEAD) || (*it)->hasBonusOfType(Bonus::NON_LIVING)) //this creature is immune
{
sc.dmgToDisplay = 0; //TODO: handle Death Stare for multiple targets (?)
continue;
}
BattleStackAttacked bsa;
bsa.flags |= BattleStackAttacked::EFFECT;
bsa.effect = spell->mainEffectAnim; //from config\spell-Info.txt
bsa.damageAmount = usedSpellPower * (*it)->valOfBonuses(Bonus::STACK_HEALTH);
bsa.stackAttacked = (*it)->ID;
bsa.attackerID = -1;
(*it)->prepareAttacked(bsa);
si.stacks.push_back(bsa);
}
}
break;
case Spells::ACID_BREATH_DAMAGE: //new effect, separate from acid breath defense reduction
{
for(std::set<CStack*>::iterator it = attackedCres.begin(); it != attackedCres.end(); ++it) //no immunities
{
BattleStackAttacked bsa;
bsa.flags |= BattleStackAttacked::EFFECT;
bsa.effect = VLC->spellh->spells[80]->mainEffectAnim; //use acid breath
bsa.damageAmount = usedSpellPower; //damage times the number of attackers
bsa.stackAttacked = (*it)->ID;
bsa.attackerID = -1;
(*it)->prepareAttacked(bsa);
si.stacks.push_back(bsa);
}
}
break;
}
sendAndApply(&sc);
if(!si.stacks.empty()) //after spellcast info shows
sendAndApply(&si);
if (mode == ECastingMode::CREATURE_ACTIVE_CASTING || mode == ECastingMode::ENCHANTER_CASTING) //reduce number of casts remaining
{
BattleSetStackProperty ssp;
ssp.stackID = stack->ID;
ssp.which = BattleSetStackProperty::CASTS;
ssp.val = -1;
ssp.absolute = false;
sendAndApply(&ssp);
}
//Magic Mirror effect
if (spell->isNegative() && mode != ECastingMode::MAGIC_MIRROR && spell->level && spell->range[0] == "0") //it is actual spell and can be reflected to single target, no recurrence
{
for(std::set<CStack*>::iterator it = attackedCres.begin(); it != attackedCres.end(); ++it)
{
int mirrorChance = (*it)->valOfBonuses(Bonus::MAGIC_MIRROR);
if(mirrorChance > rand()%100)
{
std::vector<CStack *> mirrorTargets;
std::vector<CStack *> & battleStacks = gs->curB->stacks;
for (size_t j = 0; j < battleStacks.size(); ++j)
{
if(battleStacks[j]->owner == casterSide) //get enemy stacks which can be affected by this spell
{
if (!gs->curB->battleIsImmune(NULL, spell, ECastingMode::MAGIC_MIRROR, battleStacks[j]->position))
mirrorTargets.push_back(battleStacks[j]);
}
}
if (mirrorTargets.size())
{
int targetHex = mirrorTargets[rand() % mirrorTargets.size()]->position;
handleSpellCasting(spellID, 0, targetHex, 1 - casterSide, (*it)->owner, NULL, (caster ? caster : NULL), usedSpellPower, ECastingMode::MAGIC_MIRROR, (*it));
}
}
}
}
}
bool CGameHandler::makeCustomAction( BattleAction &ba )
{
switch(ba.actionType)
{
case BattleAction::HERO_SPELL: //hero casts spell
{
const CGHeroInstance *h = gs->curB->heroes[ba.side];
const CGHeroInstance *secondHero = gs->curB->heroes[!ba.side];
if(!h)
{
tlog2 << "Wrong caster!\n";
return false;
}
if(ba.additionalInfo >= VLC->spellh->spells.size())
{
tlog2 << "Wrong spell id (" << ba.additionalInfo << ")!\n";
return false;
}
const CSpell *s = VLC->spellh->spells[ba.additionalInfo];
if (s->mainEffectAnim > -1 || (s->id >= 66 || s->id <= 69) || s->id == Spells::CLONE) //allow summon elementals
//TODO: special effects, like Clone
{
ui8 skill = h->getSpellSchoolLevel(s); //skill level
ESpellCastProblem::ESpellCastProblem escp = gs->curB->battleCanCastThisSpell(h->tempOwner, s, ECastingMode::HERO_CASTING);
if(escp != ESpellCastProblem::OK)
{
tlog2 << "Spell cannot be cast!\n";
tlog2 << "Problem : " << escp << std::endl;
return false;
}
StartAction start_action(ba);
sendAndApply(&start_action); //start spell casting
handleSpellCasting (ba.additionalInfo, skill, ba.destinationTile, ba.side, h->tempOwner, h, secondHero, h->getPrimSkillLevel(2), ECastingMode::HERO_CASTING, NULL);
sendAndApply(&end_action);
if( !gs->curB->getStack(gs->curB->activeStack, false)->alive() )
{
battleMadeAction.setn(true);
}
checkForBattleEnd(gs->curB->stacks);
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;
}
else
{
tlog2 << "Spell " << s->name << " is not yet supported!\n";
return false;
}
}
}
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(72))
{
bool unbind = true;
BonusList bl = *(st->getBonuses(Selector::type(Bonus::BIND_EFFECT)));
std::set<CStack*> stacks = gs->curB->getAdjacentCreatures(st);
BOOST_FOREACH(Bonus * b, bl)
{
const CStack * stack = gs->curB->getStack(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->getBonus(Selector::source(Bonus::SPELL_EFFECT, 71) && 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))
{
const CGHeroInstance * enemy = gs->curB->getHero(gs->curB->theOtherPlayer(st->owner));
if (enemy)
{
ui32 manaDrained = st->valOfBonuses(Bonus::MANA_DRAIN);
vstd::amin (manaDrained, gs->curB->heroes[0]->mana);
if (manaDrained)
{
bte.effect = Bonus::MANA_DRAIN;
bte.val = manaDrained;
bte.additionalInfo = enemy->id; //for sanity
sendAndApply(&bte);
}
}
}
if (!st->hasBonusOfType(Bonus::FEARLESS))
{
bool fearsomeCreature = false;
BOOST_FOREACH(CStack * stack, gs->curB->stacks)
{
if (stack->owner != st->owner && stack->hasBonusOfType(Bonus::FEAR))
{
fearsomeCreature = true;
break;
}
}
if (fearsomeCreature)
{
if (rand() % 100 < 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 (bl.size() && st->casts && !gs->curB->enchanterCounter[side])
{
int index = rand() % bl.size();
int spellID = bl[index]->subtype; //spell ID
if (gs->curB->battleCanCastThisSpell(st->owner, VLC->spellh->spells[spellID], ECastingMode::ENCHANTER_CASTING)); //TODO: select another?
{
int spellLeveL = bl[index]->val; //spell level
const CGHeroInstance * enemyHero = gs->curB->getHero(gs->curB->theOtherPlayer(st->owner));
handleSpellCasting(spellID, spellLeveL, -1, side, st->owner, NULL, enemyHero, 0, ECastingMode::ENCHANTER_CASTING, st);
BattleSetStackProperty ssp;
ssp.which = BattleSetStackProperty::ENCHANTER_COUNTER;
ssp.absolute = false;
ssp.val = bl[index]->additionalInfo; //increase cooldown counter
ssp.stackID = st->ID;
sendAndApply(&ssp);
}
}
}
}
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 < GameConstants::PLAYER_LIMIT; player++)
{
PlayerState *pinfo = gs->getPlayer(player);
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 = player;
sr.res = pinfo->resources + ev->resources;
//prepare dialog
InfoWindow iw;
iw.player = player;
iw.text << ev->message;
for (int i=0; i<ev->resources.size(); i++)
{
if(ev->resources[i]) //if resource is changed, we add it to the dialog
iw.components.push_back(Component(Component::RESOURCE,i,ev->resources[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;
std::list<ConstTransitivePtr<CMapEvent> >::iterator it = gs->map->events.begin();
while ( it !=gs->map->events.end() && **it <= *ev )
it++;
gs->map->events.insert(it, ev);
}
else
{
delete ev;
gs->map->events.pop_front();
}
}
}
void CGameHandler::handleTownEvents(CGTownInstance * town, NewTurn &n, std::map<si32, std::map<si32, si32> > &newCreas)
{
//TODO event removing desync!!!
town->events.sort(evntCmp);
while(town->events.size() && town->events.front()->firstOccurence == gs->day)
{
ui8 player = town->tempOwner;
CCastleEvent *ev = town->events.front();
PlayerState *pinfo = gs->getPlayer(player);
if( pinfo //player exists
&& (ev->players & 1<<player) //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[i] && pinfo->resources[i] != n.res[player][i]) //if resource had changed, we add it to the dialog
iw.components.push_back(Component(Component::RESOURCE,i,n.res[player][i]-was[i],0));
}
for(std::set<si32>::iterator i = ev->buildings.begin(); i!=ev->buildings.end();i++)
if ( !vstd::contains(town->builtBuildings, *i))
{
buildStructure(town->id, *i, true);
iw.components.push_back(Component(Component::BUILDING, town->subID, *i, 0));
}
for(si32 i=0;i<ev->creatures.size();i++) //creature growths
{
if(town->creatureDwelling(i) && ev->creatures[i])//there is dwelling
{
newCreas[town->id][i] += ev->creatures[i];
iw.components.push_back(Component(Component::CREATURE,
town->creatures[i].second.back(), ev->creatures[i], 0));
}
}
sendAndApply(&iw); //show dialog
}
if(ev->nextOccurence)
{
town->events.pop_front();
ev->firstOccurence += ev->nextOccurence;
std::list<CCastleEvent*>::iterator it = town->events.begin();
while ( it !=town->events.end() && **it <= *ev )
it++;
town->events.insert(it, ev);
}
else
{
delete ev;
town->events.pop_front();
}
}
}
bool CGameHandler::complain( const std::string &problem )
{
sendMessageToAll("Server encountered a problem: " + problem);
tlog1 << problem << std::endl;
return true;
}
ui32 CGameHandler::getQueryResult( ui8 player, int queryID )
{
//TODO: write
return 0;
}
void CGameHandler::showGarrisonDialog( int upobj, int hid, bool removableUnits, const boost::function<void()> &cb )
{
ui8 player = getOwner(hid);
GarrisonDialog gd;
gd.hid = hid;
gd.objid = upobj;
{
boost::unique_lock<boost::recursive_mutex> lock(gsm);
gd.id = QID;
garrisonCallbacks[QID] = cb;
allowedExchanges[QID] = std::pair<si32,si32>(upobj,hid);
states.addQuery(player,QID);
QID++;
gd.removableUnits = removableUnits;
sendAndApply(&gd);
}
}
void CGameHandler::showThievesGuildWindow(int player, int requestingObjId)
{
OpenWindow ow;
ow.window = OpenWindow::THIEVES_GUILD;
ow.id1 = player;
ow.id2 = requestingObjId;
sendAndApply(&ow);
}
bool CGameHandler::isAllowedArrangePack(const ArrangeStacks *pack)
{
return isAllowedExchangeForQuery(pack->id1, pack->id2);
}
bool CGameHandler::isAllowedExchangeForQuery(int id1, int id2) {
boost::unique_lock<boost::recursive_mutex> lock(gsm);
for(std::map<ui32, std::pair<si32,si32> >::const_iterator i = allowedExchanges.begin(); i!=allowedExchanges.end(); i++)
if((id1 == i->second.first && id2 == i->second.second) ||
(id2 == i->second.first && id1 == i->second.second))
return true;
return false;
}
bool CGameHandler::isAllowedExchange( int id1, int id2 )
{
if(id1 == id2)
return true;
if (isAllowedExchangeForQuery(id1, id2))
return true;
const CGObjectInstance *o1 = getObj(id1), *o2 = getObj(id2);
if (o1 && o2)
{
if(o1->ID == GameConstants::TOWNI_TYPE)
{
const CGTownInstance *t = static_cast<const CGTownInstance*>(o1);
if(t->visitingHero == o2 || t->garrisonHero == o2)
return true;
}
if(o2->ID == GameConstants::TOWNI_TYPE)
{
const CGTownInstance *t = static_cast<const CGTownInstance*>(o2);
if(t->visitingHero == o1 || t->garrisonHero == o1)
return true;
}
if(o1->ID == GameConstants::HEROI_TYPE && o2->ID == GameConstants::HEROI_TYPE
&& distance(o1->pos, o2->pos) < 2) //hero stands on the same tile or on the neighbouring tiles
{
//TODO: it's workaround, we should check if first hero visited second and player hasn't closed exchange window
//(to block moving stacks for free [without visiting] between heroes)
return true;
}
}
else //not exchanging between heroes, TODO: more sophisticated logic
{
return true;
}
return false;
}
void CGameHandler::objectVisited( const CGObjectInstance * obj, const CGHeroInstance * h )
{
HeroVisit hv;
hv.obj = obj;
hv.hero = h;
hv.starting = true;
sendAndApply(&hv);
obj->onHeroVisit(h);
hv.obj = NULL; //not necessary, moreover may have been deleted in the meantime
hv.starting = false;
sendAndApply(&hv);
}
bool CGameHandler::buildBoat( ui32 objid )
{
const IShipyard *obj = IShipyard::castFrom(getObj(objid));
if(obj->state())
{
complain("Cannot build boat in this shipyard!");
return false;
}
else if(obj->o->ID == GameConstants::TOWNI_TYPE
&& !vstd::contains((static_cast<const CGTownInstance*>(obj))->builtBuildings,6))
{
complain("Cannot build boat in the town - no shipyard!");
return false;
}
//TODO use "real" cost via obj->getBoatCost
if(getResource(obj->o->tempOwner, 6) < 1000 || getResource(obj->o->tempOwner, 0) < 10)
{
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 = obj->o->tempOwner;
sr.res = gs->getPlayer(obj->o->tempOwner)->resources;
sr.res[Res::WOOD] -= 10;
sr.res[Res::GOLD] -= 1000;
sendAndApply(&sr);
//create boat
NewObject no;
no.ID = 8;
no.subID = obj->getBoatType();
no.pos = tile + int3(1,0,0);
sendAndApply(&no);
return true;
}
void CGameHandler::engageIntoBattle( ui8 player )
{
if(vstd::contains(states.players, player))
states.setFlag(player,&PlayerStatus::engagedIntoBattle,true);
//notify interfaces
PlayerBlocked pb;
pb.player = player;
pb.reason = PlayerBlocked::UPCOMING_BATTLE;
sendAndApply(&pb);
}
void CGameHandler::winLoseHandle(ui8 players )
{
for(size_t i = 0; i < GameConstants::PLAYER_LIMIT; i++)
{
if(players & 1<<i && gs->getPlayer(i))
{
checkLossVictory(i);
}
}
}
void CGameHandler::checkLossVictory( ui8 player )
{
const PlayerState *p = gs->getPlayer(player);
if(p->status) //player already won / lost
return;
int loss = gs->lossCheck(player);
int vic = gs->victoryCheck(player);
if(!loss && !vic)
return;
InfoWindow iw;
getLossVicMessage(player, vic ? vic : loss , vic, iw);
sendAndApply(&iw);
PlayerEndsGame peg;
peg.player = player;
peg.victory = vic;
sendAndApply(&peg);
if(vic > 0) //one player won -> all enemies lost
{
iw.text.localStrings.front().second++; //message about losing because enemy won first is just after victory message
for (bmap<ui8,PlayerState>::const_iterator i = gs->players.begin(); i!=gs->players.end(); i++)
{
if(i->first < GameConstants::PLAYER_LIMIT && i->first != player)//FIXME: skip already eliminated players?
{
iw.player = i->first;
sendAndApply(&iw);
peg.player = i->first;
peg.victory = gameState()->getPlayerRelations(player, i->first) == 1; // ally of winner
sendAndApply(&peg);
}
}
}
else //player lost -> all his objects become unflagged (neutral)
{
std::vector<ConstTransitivePtr<CGHeroInstance> > hlp = p->heroes;
for (std::vector<ConstTransitivePtr<CGHeroInstance> >::const_iterator i = hlp.begin(); i != hlp.end(); i++) //eliminate heroes
removeObject((*i)->id);
for (std::vector<ConstTransitivePtr<CGObjectInstance> >::const_iterator i = gs->map->objects.begin(); i != gs->map->objects.end(); i++) //unflag objs
{
if(*i && (*i)->tempOwner == player)
setOwner((**i).id,GameConstants::NEUTRAL_PLAYER);
}
//eliminating one player may cause victory of another:
winLoseHandle(GameConstants::ALL_PLAYERS & ~(1<<player));
}
if(vic)
{
end2 = true;
if(gs->campaign)
{
std::vector<CGHeroInstance *> hes;
BOOST_FOREACH(CGHeroInstance * ghi, gs->map->heroes)
{
if (ghi->tempOwner == vic)
{
hes.push_back(ghi);
}
}
gs->campaign->mapConquered(hes);
UpdateCampaignState ucs;
ucs.camp = gs->campaign;
sendAndApply(&ucs);
}
}
}
void CGameHandler::getLossVicMessage( ui8 player, ui8 standard, bool victory, InfoWindow &out ) const
{
// const PlayerState *p = gs->getPlayer(player);
// if(!p->human)
// return; //AI doesn't need text info of loss
out.player = player;
if(victory)
{
if(standard < 0) //not std loss
{
switch(gs->map->victoryCondition.condition)
{
case EVictoryConditionType::ARTIFACT:
out.text.addTxt(MetaString::GENERAL_TXT, 280); //Congratulations! You have found the %s, and can claim victory!
out.text.addReplacement(MetaString::ART_NAMES,gs->map->victoryCondition.ID); //artifact name
break;
case EVictoryConditionType::GATHERTROOP:
out.text.addTxt(MetaString::GENERAL_TXT, 276); //Congratulations! You have over %d %s in your armies. Your enemies have no choice but to bow down before your power!
out.text.addReplacement(gs->map->victoryCondition.count);
out.text.addReplacement(MetaString::CRE_PL_NAMES, gs->map->victoryCondition.ID);
break;
case EVictoryConditionType::GATHERRESOURCE:
out.text.addTxt(MetaString::GENERAL_TXT, 278); //Congratulations! You have collected over %d %s in your treasury. Victory is yours!
out.text.addReplacement(gs->map->victoryCondition.count);
out.text.addReplacement(MetaString::RES_NAMES, gs->map->victoryCondition.ID);
break;
case EVictoryConditionType::BUILDCITY:
out.text.addTxt(MetaString::GENERAL_TXT, 282); //Congratulations! You have successfully upgraded your town, and can claim victory!
break;
case EVictoryConditionType::BUILDGRAIL:
out.text.addTxt(MetaString::GENERAL_TXT, 284); //Congratulations! You have constructed a permanent home for the Grail, and can claim victory!
break;
case EVictoryConditionType::BEATHERO:
{
out.text.addTxt(MetaString::GENERAL_TXT, 252); //Congratulations! You have completed your quest to defeat the enemy hero %s. Victory is yours!
const CGHeroInstance *h = dynamic_cast<const CGHeroInstance*>(gs->map->victoryCondition.obj);
assert(h);
out.text.addReplacement(h->name);
}
break;
case EVictoryConditionType::CAPTURECITY:
{
out.text.addTxt(MetaString::GENERAL_TXT, 249); //Congratulations! You captured %s, and are victorious!
const CGTownInstance *t = dynamic_cast<const CGTownInstance*>(gs->map->victoryCondition.obj);
assert(t);
out.text.addReplacement(t->name);
}
break;
case EVictoryConditionType::BEATMONSTER:
out.text.addTxt(MetaString::GENERAL_TXT, 286); //Congratulations! You have completed your quest to kill the fearsome beast, and can claim victory!
break;
case EVictoryConditionType::TAKEDWELLINGS:
out.text.addTxt(MetaString::GENERAL_TXT, 288); //Congratulations! Your flag flies on the dwelling of every creature. Victory is yours!
break;
case EVictoryConditionType::TAKEMINES:
out.text.addTxt(MetaString::GENERAL_TXT, 290); //Congratulations! Your flag flies on every mine. Victory is yours!
break;
case EVictoryConditionType::TRANSPORTITEM:
out.text.addTxt(MetaString::GENERAL_TXT, 292); //Congratulations! You have reached your destination, precious cargo intact, and can claim victory!
break;
}
}
else
{
out.text.addTxt(MetaString::GENERAL_TXT, 659); //Congratulations! You have reached your destination, precious cargo intact, and can claim victory!
}
}
else
{
if(standard < 0) //not std loss
{
switch(gs->map->lossCondition.typeOfLossCon)
{
case ELossConditionType::LOSSCASTLE:
{
out.text.addTxt(MetaString::GENERAL_TXT, 251); //The town of %s has fallen - all is lost!
const CGTownInstance *t = dynamic_cast<const CGTownInstance*>(gs->map->lossCondition.obj);
assert(t);
out.text.addReplacement(t->name);
}
break;
case ELossConditionType::LOSSHERO:
{
out.text.addTxt(MetaString::GENERAL_TXT, 253); //The hero, %s, has suffered defeat - your quest is over!
const CGHeroInstance *h = dynamic_cast<const CGHeroInstance*>(gs->map->lossCondition.obj);
assert(h);
out.text.addReplacement(h->name);
}
break;
case ELossConditionType::TIMEEXPIRES:
out.text.addTxt(MetaString::GENERAL_TXT, 254); //Alas, time has run out on your quest. All is lost.
break;
}
}
else if(standard == 2)
{
out.text.addTxt(MetaString::GENERAL_TXT, 7);//%s, your heroes abandon you, and you are banished from this land.
out.text.addReplacement(MetaString::COLOR, player);
out.components.push_back(Component(Component::FLAG,player,0,0));
}
else //lost all towns and heroes
{
out.text.addTxt(MetaString::GENERAL_TXT, 660); //All your forces have been defeated, and you are banished from this land!
}
}
}
bool CGameHandler::dig( const CGHeroInstance *h )
{
for (std::vector<ConstTransitivePtr<CGObjectInstance> >::const_iterator i = gs->map->objects.begin(); i != gs->map->objects.end(); i++) //unflag objs
{
if(*i && (*i)->ID == 124 && (*i)->pos == h->getPosition())
{
complain("Cannot dig - there is already a hole under the hero!");
return false;
}
}
if(h->diggingStatus() != CGHeroInstance::CAN_DIG) //checks for terrain and movement
COMPLAIN_RETF("Hero cannot dig (error code %d)!", h->diggingStatus());
//create a hole
NewObject no;
no.ID = 124;
no.pos = h->getPosition();
no.subID = getTile(no.pos)->tertype;
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, 2);
iw.soundID = soundBase::ULTIMATEARTIFACT;
giveHeroNewArtifact(h, VLC->arth->artifacts[2], -1); //give grail
sendAndApply(&iw);
iw.soundID = soundBase::invalid;
iw.text.clear();
iw.text.addTxt(MetaString::ART_DESCR, 2);
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<ui32> spellsToCast;
TBonusListPtr spells = attacker->getBonuses(Selector::type(attackMode));
BOOST_FOREACH(const Bonus *sf, *spells)
{
spellsToCast.insert (sf->subtype);
}
BOOST_FOREACH(ui32 spellID, spellsToCast)
{
const CStack * oneOfAttacked = NULL;
for (int g=0; g<bat.bsa.size(); ++g)
{
if (bat.bsa[g].newAmount > 0 && !bat.bsa[g].isSecondary()) //apply effects only to first target stack if it's alive
{
oneOfAttacked = gs->curB->getStack(bat.bsa[g].stackAttacked);
break;
}
}
bool castMe = false;
int meleeRanged;
if(oneOfAttacked == NULL) //all attacked creatures have been killed
return;
int spellLevel = 0;
TBonusListPtr spellsByType = attacker->getBonuses(Selector::typeSubtype(attackMode, spellID));
BOOST_FOREACH(const Bonus *sf, *spellsByType)
{
vstd::amax(spellLevel, sf->additionalInfo % 1000); //pick highest level
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);
int destination = oneOfAttacked->position;
const CSpell * spell = VLC->spellh->spells[spellID];
if(gs->curB->battleCanCastThisSpellHere(attacker->owner, spell, ECastingMode::AFTER_ATTACK_CASTING, oneOfAttacked->position) != ESpellCastProblem::OK)
continue;
//check if spell should be casted (probability handling)
if(rand()%100 >= chance)
continue;
//casting //TODO: check if spell can be blocked or target is immune
if (castMe) //stacks use 0 spell power. If needed, default = 3 or custom value is used
handleSpellCasting(spellID, spellLevel, destination, !attacker->attackerOwned, attacker->owner, NULL, NULL, 0, ECastingMode::AFTER_ATTACK_CASTING, attacker);
}
}
}
void CGameHandler::handleAttackBeforeCasting (const BattleAttack & bat)
{
const CStack * attacker = gs->curB->getStack(bat.stackAttacking);
attackCasting(bat, Bonus::SPELL_BEFORE_ATTACK, attacker); //no detah stare / acid bretah needed?
}
void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat )
{
const CStack * attacker = gs->curB->getStack(bat.stackAttacking);
attackCasting(bat, Bonus::SPELL_AFTER_ATTACK, attacker);
if(bat.bsa[0].newAmount <= 0)
{
//don't try death stare or acid breath on dead stack (crash!)
return;
}
if (attacker->hasBonusOfType(Bonus::DEATH_STARE)) // spell id 79
{
int staredCreatures = 0;
double mean = attacker->count * attacker->valOfBonuses(Bonus::DEATH_STARE, 0) / 100;
if (mean >= 1)
{
boost::poisson_distribution<int, double> p((int)mean);
boost::mt19937 rng;
boost::variate_generator<boost::mt19937&, boost::poisson_distribution<int, double> > dice (rng, p);
staredCreatures += dice();
}
if (((int)(mean * 100)) < rand() % 100) //fractional chance for one last kill
++staredCreatures;
staredCreatures += attacker->type->level * attacker->valOfBonuses(Bonus::DEATH_STARE, 1);
if (staredCreatures)
{
if (bat.bsa.size() && bat.bsa[0].newAmount > 0) //TODO: death stare was not originally available for multiple-hex attacks, but...
handleSpellCasting(79, 0, gs->curB->getStack(bat.bsa[0].stackAttacked)->position,
!attacker->attackerOwned, attacker->owner, NULL, NULL, staredCreatures, ECastingMode::AFTER_ATTACK_CASTING, attacker);
}
}
int acidDamage = 0;
TBonusListPtr acidBreath = attacker->getBonuses(Selector::type(Bonus::ACID_BREATH));
BOOST_FOREACH(const Bonus *b, *acidBreath)
{
if (b->additionalInfo > rand()%100)
acidDamage += b->val;
}
if (acidDamage)
{
handleSpellCasting(81, 0, gs->curB->getStack(bat.bsa[0].stackAttacked)->position,
!attacker->attackerOwned, attacker->owner, NULL, NULL,
acidDamage * attacker->count, ECastingMode::AFTER_ATTACK_CASTING, attacker);
}
}
bool CGameHandler::castSpell(const CGHeroInstance *h, int spellID, const int3 &pos)
{
const CSpell *s = VLC->spellh->spells[spellID];
int cost = h->getSpellCost(s);
int schoolLevel = h->getSpellSchoolLevel(s);
if(!h->canCastThisSpell(s))
COMPLAIN_RET("Hero cannot cast this spell!");
if(h->mana < cost)
COMPLAIN_RET("Hero doesn't have enough spell points to cast this spell!");
if(s->combatSpell)
COMPLAIN_RET("This function can be used only for adventure map spells!");
AdvmapSpellCast asc;
asc.caster = h;
asc.spellID = spellID;
sendAndApply(&asc);
using namespace Spells;
switch(spellID)
{
case SUMMON_BOAT: //Summon Boat
{
//check if spell works at all
if(rand() % 100 >= s->powers[schoolLevel]) //power is % chance of success
{
InfoWindow iw;
iw.player = h->tempOwner;
iw.text.addTxt(MetaString::GENERAL_TXT, 336); //%s tried to summon a boat, but failed.
iw.text.addReplacement(h->name);
sendAndApply(&iw);
return true; //TODO? or should it be false? request was correct and realized, but spell failed...
}
//try to find unoccupied boat to summon
const CGBoat *nearest = NULL;
double dist = 0;
int3 summonPos = h->bestLocation();
if(summonPos.x < 0)
COMPLAIN_RET("There is no water tile available!");
BOOST_FOREACH(const CGObjectInstance *obj, gs->map->objects)
{
if(obj && obj->ID == 8)
{
const CGBoat *b = static_cast<const CGBoat*>(obj);
if(b->hero) continue; //we're looking for unoccupied boat
double nDist = distance(b->pos, h->getPosition());
if(!nearest || nDist < dist) //it's first boat or closer than previous
{
nearest = b;
dist = nDist;
}
}
}
if(nearest) //we found boat to summon
{
ChangeObjPos cop;
cop.objid = nearest->id;
cop.nPos = summonPos + int3(1,0,0);;
cop.flags = 1;
sendAndApply(&cop);
}
else if(schoolLevel < 2) //none or basic level -> cannot create boat :(
{
InfoWindow iw;
iw.player = h->tempOwner;
iw.text.addTxt(MetaString::GENERAL_TXT, 335); //There are no boats to summon.
sendAndApply(&iw);
}
else //create boat
{
NewObject no;
no.ID = 8;
no.subID = h->getBoatType();
no.pos = summonPos + int3(1,0,0);;
sendAndApply(&no);
}
break;
}
case SCUTTLE_BOAT: //Scuttle Boat
{
//check if spell works at all
if(rand() % 100 >= s->powers[schoolLevel]) //power is % chance of success
{
InfoWindow iw;
iw.player = h->tempOwner;
iw.text.addTxt(MetaString::GENERAL_TXT, 337); //%s tried to scuttle the boat, but failed
iw.text.addReplacement(h->name);
sendAndApply(&iw);
return true; //TODO? or should it be false? request was correct and realized, but spell failed...
}
if(!gs->map->isInTheMap(pos))
COMPLAIN_RET("Invalid dst tile for scuttle!");
//TODO: test range, visibility
const TerrainTile *t = &gs->map->getTile(pos);
if(!t->visitableObjects.size() || t->visitableObjects.back()->ID != 8)
COMPLAIN_RET("There is no boat to scuttle!");
RemoveObject ro;
ro.id = t->visitableObjects.back()->id;
sendAndApply(&ro);
break;
}
case DIMENSION_DOOR: //Dimension Door
{
const TerrainTile *dest = getTile(pos);
const TerrainTile *curr = getTile(h->getSightCenter());
if(!dest)
COMPLAIN_RET("Destination tile doesn't exist!");
if(!h->movement)
COMPLAIN_RET("Hero needs movement points to cast Dimension Door!");
if(h->getBonusesCount(Bonus::SPELL_EFFECT, Spells::DIMENSION_DOOR) >= s->powers[schoolLevel]) //limit casts per turn
{
InfoWindow iw;
iw.player = h->tempOwner;
iw.text.addTxt(MetaString::GENERAL_TXT, 338); //%s is not skilled enough to cast this spell again today.
iw.text.addReplacement(h->name);
sendAndApply(&iw);
break;
}
GiveBonus gb;
gb.id = h->id;
gb.bonus = Bonus(Bonus::ONE_DAY, Bonus::NONE, Bonus::SPELL_EFFECT, 0, Spells::DIMENSION_DOOR);
sendAndApply(&gb);
if(!dest->isClear(curr)) //wrong dest tile
{
InfoWindow iw;
iw.player = h->tempOwner;
iw.text.addTxt(MetaString::GENERAL_TXT, 70); //Dimension Door failed!
sendAndApply(&iw);
break;
}
//we need obtain guard pos before moving hero, otherwise we get nothing, because tile will be "unguarded" by hero
int3 guardPos = gs->guardingCreaturePosition(pos);
TryMoveHero tmh;
tmh.id = h->id;
tmh.movePoints = std::max<int>(0, h->movement - 300);
tmh.result = TryMoveHero::TELEPORTATION;
tmh.start = h->pos;
tmh.end = pos + h->getVisitableOffset();
getTilesInRange(tmh.fowRevealed, pos, h->getSightRadious(), h->tempOwner,1);
sendAndApply(&tmh);
tryAttackingGuard(guardPos, h);
}
break;
case FLY: //Fly
{
int subtype = schoolLevel >= 2 ? 1 : 2; //adv or expert
GiveBonus gb;
gb.id = h->id;
gb.bonus = Bonus(Bonus::ONE_DAY, Bonus::FLYING_MOVEMENT, Bonus::SPELL_EFFECT, 0, Spells::FLY, subtype);
sendAndApply(&gb);
}
break;
case WATER_WALK: //Water Walk
{
int subtype = schoolLevel >= 2 ? 1 : 2; //adv or expert
GiveBonus gb;
gb.id = h->id;
gb.bonus = Bonus(Bonus::ONE_DAY, Bonus::WATER_WALKING, Bonus::SPELL_EFFECT, 0, Spells::FLY, subtype);
sendAndApply(&gb);
}
break;
case TOWN_PORTAL: //Town Portal
{
if (!gs->map->isInTheMap(pos))
COMPLAIN_RET("Destination tile not present!")
TerrainTile tile = gs->map->getTile(pos);
if (tile.visitableObjects.empty() || tile.visitableObjects.back()->ID != GameConstants::TOWNI_TYPE )
COMPLAIN_RET("Town not found for Town Portal!");
CGTownInstance * town = static_cast<CGTownInstance*>(tile.visitableObjects.back());
if (town->tempOwner != h->tempOwner)
COMPLAIN_RET("Can't teleport to another player!");
if (town->visitingHero)
COMPLAIN_RET("Can't teleport to occupied town!");
if (h->getSpellSchoolLevel(s) < 2)
{
double dist = town->pos.dist2d(h->pos);
int nearest = town->id; //nearest town's ID
BOOST_FOREACH(const CGTownInstance * currTown, gs->getPlayer(h->tempOwner)->towns)
{
double curDist = currTown->pos.dist2d(h->pos);
if (nearest == -1 || curDist < dist)
{
nearest = town->id;
dist = curDist;
}
}
if (town->id != nearest)
COMPLAIN_RET("This hero can only teleport to nearest town!")
}
if (h->visitedTown)
stopHeroVisitCastle(h->visitedTown->id, h->id);
if (moveHero(h->id, town->visitablePos() + h->getVisitableOffset() ,1))
heroVisitCastle(town->id, h->id);
}
break;
case VISIONS: //Visions
case VIEW_EARTH: //View Earth
case DISGUISE: //Disguise
case VIEW_AIR: //View Air
default:
COMPLAIN_RET("This spell is not implemented yet!");
break;
}
SetMana sm;
sm.hid = h->id;
sm.val = h->mana - cost;
sendAndApply(&sm);
return true;
}
void CGameHandler::visitObjectOnTile(const TerrainTile &t, const CGHeroInstance * h)
{
//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::tryAttackingGuard(const int3 &guardPos, const CGHeroInstance * h)
{
if(!gs->map->isInTheMap(guardPos))
return false;
const TerrainTile &guardTile = gs->map->terrain[guardPos.x][guardPos.y][guardPos.z];
objectVisited(guardTile.visitableObjects.back(), h);
visitObjectAfterVictory = true;
return true;
}
bool CGameHandler::sacrificeCreatures(const IMarket *market, const CGHeroInstance *hero, TSlot slot, ui32 count)
{
int oldCount = hero->getStackCount(slot);
if(oldCount < count)
COMPLAIN_RET("Not enough creatures to sacrifice!")
else if(oldCount == count && hero->Slots().size() == 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->id, 4, hero->calculateXp(exp));
return true;
}
bool CGameHandler::sacrificeArtifact(const IMarket * m, const CGHeroInstance * hero, int 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->id, 4, expToGive);
return true;
}
void CGameHandler::makeStackDoNothing(const CStack * next)
{
BattleAction doNothing;
doNothing.actionType = 0;
doNothing.additionalInfo = 0;
doNothing.destinationTile = -1;
doNothing.side = !next->attackerOwned;
doNothing.stackNumber = next->ID;
StartAction start_action(doNothing);
sendAndApply(&start_action);
sendAndApply(&end_action);
}
bool CGameHandler::insertNewStack(const StackLocation &sl, const CCreature *c, TQuantity count)
{
if(sl.army->hasStackAtSlot(sl.slot))
COMPLAIN_RET("Slot is already taken!");
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->Slots().size() == 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(!src->canBeMergedWith(*dst, allowMerging))
{
if (allowMerging) //do that, add all matching creatures.
{
bool cont = true;
while (cont)
{
for(TSlots::const_iterator i = src->stacks.begin(); i != src->stacks.end(); i++)//while there are unmoved creatures
{
TSlot pos = dst->getSlotFor(i->second->type);
if(pos > -1)
{
moveStack(StackLocation(src, i->first), StackLocation(dst, pos));
cont = true;
break; //or iterator crashes
}
cont = false;
}
}
}
boost::function<void()> removeOrNot = 0;
if(removeObjWhenFinished)
removeOrNot = boost::bind(&IGameCallback::removeObject,this,src->id);
showGarrisonDialog(src->id, dst->id, true, removeOrNot); //show garrison window and optionally remove ourselves from map when player ends
}
else //merge
{
moveArmy(src, dst, allowMerging);
if(removeObjWhenFinished)
removeObject(src->id);
}
}
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(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->Slots().size() == 1 //from the last stack
&& src.army->needsLastStack()) //that must be left
{
COMPLAIN_RET("Cannot move away the alst 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()
{
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<ARRAY_COUNT(gs->curB->heroes); ++i)
{
if(gs->curB->heroes[i] && gs->curB->heroes[i]->hasBonusOfType(Bonus::OPENING_BATTLE_SPELL))
{
TBonusListPtr bl = gs->curB->heroes[i]->getBonuses(Selector::type(Bonus::OPENING_BATTLE_SPELL));
BOOST_FOREACH (Bonus *b, *bl)
{
handleSpellCasting(b->subtype, 3, -1, 0, gs->curB->heroes[i]->tempOwner, NULL, gs->curB->heroes[1-i], b->val, ECastingMode::HERO_CASTING, NULL);
}
}
}
//main loop
while(!battleResult.get()) //till the end of the battle ;]
{
NEW_ROUND;
std::vector<CStack*> & stacks = (gs->curB->stacks);
const BattleInfo & curB = *gs->curB;
//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->heroes[0], Bonus::BLOCK_MORALE) || NBonus::hasOfType(gs->curB->heroes[1], Bonus::BLOCK_MORALE)) //checking if gs->curB->heroes have (or don't have) morale blocking bonuses)
)
{
if( rand()%24 < -2 * nextStackMorale)
{
//unit loses its turn - empty freeze action
BattleAction ba;
ba.actionType = BattleAction::BAD_MORALE;
ba.additionalInfo = 1;
ba.side = !next->attackerOwned;
ba.stackNumber = next->ID;
StartAction start_action(ba);
sendAndApply(&start_action);
sendAndApply(&end_action);
checkForBattleEnd(stacks); //check if this "action" ended the battle (not likely but who knows...)
continue;
}
}
if(next->hasBonusOfType(Bonus::ATTACKS_NEAREST_CREATURE)) //while in berserk
{
std::pair<const CStack *, int> attackInfo = curB.getNearestStack(next, boost::logic::indeterminate);
if(attackInfo.first != NULL)
{
BattleAction attack;
attack.actionType = BattleAction::WALK_AND_ATTACK;
attack.side = !next->attackerOwned;
attack.stackNumber = next->ID;
attack.additionalInfo = attackInfo.first->position;
attack.destinationTile = attackInfo.second;
makeBattleAction(attack);
checkForBattleEnd(stacks);
}
else
{
makeStackDoNothing(next);
}
continue;
}
const CGHeroInstance * curOwner = gs->curB->battleGetOwner(next);
if( (next->position < 0 || next->getCreature()->idNumber == 146) //arrow turret or ballista
&& (!curOwner || curOwner->getSecSkillLevel(CGHeroInstance::ARTILLERY) == 0)) //hero has no artillery
{
BattleAction attack;
attack.actionType = BattleAction::SHOOT;
attack.side = !next->attackerOwned;
attack.stackNumber = next->ID;
for(int g=0; g<gs->curB->stacks.size(); ++g)
{
if(gs->curB->stacks[g]->owner != next->owner && gs->curB->stacks[g]->isValidTarget())
{
attack.destinationTile = gs->curB->stacks[g]->position;
break;
}
}
makeBattleAction(attack);
checkForBattleEnd(stacks);
continue;
}
if(next->getCreature()->idNumber == 145 && (!curOwner || curOwner->getSecSkillLevel(CGHeroInstance::BALLISTICS) == 0)) //catapult, hero has no ballistics
{
BattleAction attack;
static const int wallHexes[] = {50, 183, 182, 130, 62, 29, 12, 95};
attack.destinationTile = wallHexes[ rand()%ARRAY_COUNT(wallHexes) ];
attack.actionType = BattleAction::CATAPULT;
attack.additionalInfo = 0;
attack.side = !next->attackerOwned;
attack.stackNumber = next->ID;
makeBattleAction(attack);
continue;
}
if(next->getCreature()->idNumber == 147 && (!curOwner || curOwner->getSecSkillLevel(CGHeroInstance::FIRST_AID) == 0)) //first aid tent, hero has no first aid
{
BattleAction heal;
std::vector< const CStack * > possibleStacks, secondPriority;
for (int v=0; v<gs->curB->stacks.size(); ++v)
{
const CStack * cstack = gs->curB->stacks[v];
if (cstack->owner == next->owner && cstack->firstHPleft < cstack->MaxHealth() && cstack->isValidTarget()) //it's friendly and not fully healthy
{
if (cstack->hasBonusOfType(Bonus::SIEGE_WEAPON))
secondPriority.push_back(cstack);
else
possibleStacks.push_back(cstack);
}
}
if(possibleStacks.size() == 0 && secondPriority.size() == 0)
{
//nothing to heal
makeStackDoNothing(next);
continue;
}
else
{
//heal random creature
const CStack * toBeHealed = NULL;
if (possibleStacks.size() > 0)
toBeHealed = possibleStacks[ rand()%possibleStacks.size() ];
else
toBeHealed = secondPriority[ rand()%secondPriority.size() ];
heal.actionType = BattleAction::STACK_HEAL;
heal.additionalInfo = 0;
heal.destinationTile = toBeHealed->position;
heal.side = !next->attackerOwned;
heal.stackNumber = next->ID;
makeBattleAction(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
{
BattleSetActiveStack sas;
sas.stack = next->ID;
sendAndApply(&sas);
boost::unique_lock<boost::mutex> lock(battleMadeAction.mx);
battleMadeAction.data = false;
while (next->alive() &&
(!battleMadeAction.data && !battleResult.get())) //active stack hasn't made its action and battle is still going
battleMadeAction.cond.wait(lock);
}
}
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(stacks); //check if this action ended the battle
//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)
&& !vstd::contains(next->state,EBattleStackState::WAITING)
&& !vstd::contains(next->state, EBattleStackState::FEAR)
&& next->alive()
&& nextStackMorale > 0
&& !(NBonus::hasOfType(gs->curB->heroes[0], Bonus::BLOCK_MORALE) || NBonus::hasOfType(gs->curB->heroes[1], Bonus::BLOCK_MORALE)) //checking if gs->curB->heroes have (or don't have) morale blocking bonuses
)
{
if(rand()%24 < 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->heroes[0], gs->curB->heroes[1]);
}
void CGameHandler::giveHeroArtifact(const CGHeroInstance *h, const CArtifactInstance *a, int pos)
{
assert(a->artType);
ArtifactLocation al;
al.hero = h;
int slot = -1;
if(pos < 0)
{
if(pos == -2)
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, int pos)
{
CArtifactInstance *a = NULL;
if(!artType->constituents)
{
if (vstd::contains(VLC->arth->creatureArtifacts, artType->id))
a = new CCreatureArtifactInstance();
else
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(int resultType, int victoriusSide)
{
if(battleResult.get())
{
complain("There is already set result?");
return;
}
BattleResult *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(int 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());
tlog5 << "Spawning wandering monsters. Found " << tiles.size() << " free tiles. Creature type: " << creatureID << std::endl;
const CCreature *cre = VLC->creh->creatures[creatureID];
for (int i = 0; i < amount; ++i)
{
tile = tiles.begin();
tlog5 << "\tSpawning monster at " << *tile << std::endl;
putNewMonster(creatureID, cre->getRandomAmount(std::rand), *tile);
tiles.erase(tile); //not use it again
}
}
bool CGameHandler::isBlockedByQueries(const CPack *pack, int packType, ui8 player)
{
//it's always legal to send query reply (we'll check later if it makes sense)
if(packType == typeList.getTypeID<QueryReply>())
return false;
if(packType == typeList.getTypeID<ArrangeStacks>() && isAllowedArrangePack((const ArrangeStacks*)pack))
return false;
//if there are no queries, nothing is blocking
if(states.getQueriesCount(player) == 0)
return false;
return true; //block package
}
CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance *army, BattleInfo *bat)
{
int color = army->tempOwner;
if(color == 254)
color = GameConstants::NEUTRAL_PLAYER;
BOOST_FOREACH(CStack *st, bat->stacks)
{
if(vstd::contains(st->state, EBattleStackState::SUMMONED)) //don't take into account summoned stacks
continue;
if(st->owner==color && !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));
}
}
}
void CasualtiesAfterBattle::takeFromArmy(CGameHandler *gh)
{
BOOST_FOREACH(TStackAndItsNewCount &ncount, newStackCounts)
{
if(ncount.second > 0)
gh->changeStackCount(ncount.first, ncount.second, true);
else
gh->eraseStack(ncount.first, true);
}
}