mirror of
https://github.com/vcmi/vcmi.git
synced 2025-07-15 01:24:45 +02:00
Start of stabilization - battles now start correctly
This commit is contained in:
@ -292,7 +292,7 @@ void CBattleAI::activeStack( const CStack * stack )
|
|||||||
//spellcast may finish battle or kill active stack
|
//spellcast may finish battle or kill active stack
|
||||||
//send special preudo-action
|
//send special preudo-action
|
||||||
BattleAction cancel;
|
BattleAction cancel;
|
||||||
cancel.actionType = EActionType::CANCEL;
|
cancel.actionType = EActionType::NO_ACTION;
|
||||||
cb->battleMakeUnitAction(cancel);
|
cb->battleMakeUnitAction(cancel);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -206,7 +206,7 @@ bool CCallback::buildBuilding(const CGTownInstance *town, BuildingID buildingID)
|
|||||||
void CBattleCallback::battleMakeSpellAction(const BattleAction & action)
|
void CBattleCallback::battleMakeSpellAction(const BattleAction & action)
|
||||||
{
|
{
|
||||||
assert(action.actionType == EActionType::HERO_SPELL);
|
assert(action.actionType == EActionType::HERO_SPELL);
|
||||||
MakeCustomAction mca(action);
|
MakeAction mca(action);
|
||||||
sendRequest(&mca);
|
sendRequest(&mca);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -401,7 +401,7 @@ void BattleStacksController::stackRemoved(uint32_t stackID)
|
|||||||
{
|
{
|
||||||
BattleAction action;
|
BattleAction action;
|
||||||
action.side = owner.defendingHeroInstance ? (owner.curInt->playerID == owner.defendingHeroInstance->tempOwner) : false;
|
action.side = owner.defendingHeroInstance ? (owner.curInt->playerID == owner.defendingHeroInstance->tempOwner) : false;
|
||||||
action.actionType = EActionType::CANCEL;
|
action.actionType = EActionType::NO_ACTION;
|
||||||
action.stackNumber = getActiveStack()->unitId();
|
action.stackNumber = getActiveStack()->unitId();
|
||||||
|
|
||||||
LOCPLINT->cb->battleMakeUnitAction(action);
|
LOCPLINT->cb->battleMakeUnitAction(action);
|
||||||
|
@ -248,7 +248,6 @@ std::ostream & operator<<(std::ostream & os, const EActionType actionType)
|
|||||||
static const std::map<EActionType, std::string> actionTypeToString =
|
static const std::map<EActionType, std::string> actionTypeToString =
|
||||||
{
|
{
|
||||||
{EActionType::END_TACTIC_PHASE, "End tactic phase"},
|
{EActionType::END_TACTIC_PHASE, "End tactic phase"},
|
||||||
{EActionType::INVALID, "Invalid"},
|
|
||||||
{EActionType::NO_ACTION, "No action"},
|
{EActionType::NO_ACTION, "No action"},
|
||||||
{EActionType::HERO_SPELL, "Hero spell"},
|
{EActionType::HERO_SPELL, "Hero spell"},
|
||||||
{EActionType::WALK, "Walk"},
|
{EActionType::WALK, "Walk"},
|
||||||
|
@ -1000,18 +1000,19 @@ namespace Date
|
|||||||
|
|
||||||
enum class EActionType : int32_t
|
enum class EActionType : int32_t
|
||||||
{
|
{
|
||||||
CANCEL = -3,
|
NO_ACTION,
|
||||||
END_TACTIC_PHASE = -2,
|
|
||||||
INVALID = -1,
|
END_TACTIC_PHASE,
|
||||||
NO_ACTION = 0,
|
|
||||||
HERO_SPELL,
|
|
||||||
WALK,
|
|
||||||
DEFEND,
|
|
||||||
RETREAT,
|
RETREAT,
|
||||||
SURRENDER,
|
SURRENDER,
|
||||||
|
|
||||||
|
HERO_SPELL,
|
||||||
|
|
||||||
|
WALK,
|
||||||
|
WAIT,
|
||||||
|
DEFEND,
|
||||||
WALK_AND_ATTACK,
|
WALK_AND_ATTACK,
|
||||||
SHOOT,
|
SHOOT,
|
||||||
WAIT,
|
|
||||||
CATAPULT,
|
CATAPULT,
|
||||||
MONSTER_SPELL,
|
MONSTER_SPELL,
|
||||||
BAD_MORALE,
|
BAD_MORALE,
|
||||||
|
@ -134,7 +134,6 @@ public:
|
|||||||
virtual void visitBuildBoat(BuildBoat & pack) {}
|
virtual void visitBuildBoat(BuildBoat & pack) {}
|
||||||
virtual void visitQueryReply(QueryReply & pack) {}
|
virtual void visitQueryReply(QueryReply & pack) {}
|
||||||
virtual void visitMakeAction(MakeAction & pack) {}
|
virtual void visitMakeAction(MakeAction & pack) {}
|
||||||
virtual void visitMakeCustomAction(MakeCustomAction & pack) {}
|
|
||||||
virtual void visitDigWithHero(DigWithHero & pack) {}
|
virtual void visitDigWithHero(DigWithHero & pack) {}
|
||||||
virtual void visitCastAdvSpell(CastAdvSpell & pack) {}
|
virtual void visitCastAdvSpell(CastAdvSpell & pack) {}
|
||||||
virtual void visitSaveGame(SaveGame & pack) {}
|
virtual void visitSaveGame(SaveGame & pack) {}
|
||||||
|
@ -2513,24 +2513,6 @@ struct DLL_LINKAGE MakeAction : public CPackForServer
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DLL_LINKAGE MakeCustomAction : public CPackForServer
|
|
||||||
{
|
|
||||||
MakeCustomAction() = default;
|
|
||||||
MakeCustomAction(BattleAction BA)
|
|
||||||
: ba(std::move(BA))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
BattleAction ba;
|
|
||||||
|
|
||||||
virtual void visitTyped(ICPackVisitor & visitor) override;
|
|
||||||
|
|
||||||
template <typename Handler> void serialize(Handler & h, const int version)
|
|
||||||
{
|
|
||||||
h & static_cast<CPackForServer &>(*this);
|
|
||||||
h & ba;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DLL_LINKAGE DigWithHero : public CPackForServer
|
struct DLL_LINKAGE DigWithHero : public CPackForServer
|
||||||
{
|
{
|
||||||
ObjectInstanceID id; //digging hero id
|
ObjectInstanceID id; //digging hero id
|
||||||
|
@ -638,11 +638,6 @@ void MakeAction::visitTyped(ICPackVisitor & visitor)
|
|||||||
visitor.visitMakeAction(*this);
|
visitor.visitMakeAction(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MakeCustomAction::visitTyped(ICPackVisitor & visitor)
|
|
||||||
{
|
|
||||||
visitor.visitMakeCustomAction(*this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DigWithHero::visitTyped(ICPackVisitor & visitor)
|
void DigWithHero::visitTyped(ICPackVisitor & visitor)
|
||||||
{
|
{
|
||||||
visitor.visitDigWithHero(*this);
|
visitor.visitDigWithHero(*this);
|
||||||
|
@ -20,7 +20,7 @@ static const int32_t INVALID_UNIT_ID = -1000;
|
|||||||
BattleAction::BattleAction():
|
BattleAction::BattleAction():
|
||||||
side(-1),
|
side(-1),
|
||||||
stackNumber(-1),
|
stackNumber(-1),
|
||||||
actionType(EActionType::INVALID),
|
actionType(EActionType::NO_ACTION),
|
||||||
actionSubtype(-1)
|
actionSubtype(-1)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -352,7 +352,6 @@ void registerTypesServerPacks(Serializer &s)
|
|||||||
s.template registerType<CPackForServer, BuildBoat>();
|
s.template registerType<CPackForServer, BuildBoat>();
|
||||||
s.template registerType<CPackForServer, QueryReply>();
|
s.template registerType<CPackForServer, QueryReply>();
|
||||||
s.template registerType<CPackForServer, MakeAction>();
|
s.template registerType<CPackForServer, MakeAction>();
|
||||||
s.template registerType<CPackForServer, MakeCustomAction>();
|
|
||||||
s.template registerType<CPackForServer, DigWithHero>();
|
s.template registerType<CPackForServer, DigWithHero>();
|
||||||
s.template registerType<CPackForServer, CastAdvSpell>();
|
s.template registerType<CPackForServer, CastAdvSpell>();
|
||||||
s.template registerType<CPackForServer, CastleTeleportHero>();
|
s.template registerType<CPackForServer, CastleTeleportHero>();
|
||||||
|
@ -288,14 +288,6 @@ void ApplyGhNetPackVisitor::visitMakeAction(MakeAction & pack)
|
|||||||
result = gh.battles->makeBattleAction(pack.player, pack.ba);
|
result = gh.battles->makeBattleAction(pack.player, pack.ba);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ApplyGhNetPackVisitor::visitMakeCustomAction(MakeCustomAction & pack)
|
|
||||||
{
|
|
||||||
if (!gh.hasPlayerAt(pack.player, pack.c))
|
|
||||||
gh.throwAndComplain(&pack, "No such pack.player!");
|
|
||||||
|
|
||||||
result = gh.battles->makeCustomAction(pack.player, pack.ba);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApplyGhNetPackVisitor::visitDigWithHero(DigWithHero & pack)
|
void ApplyGhNetPackVisitor::visitDigWithHero(DigWithHero & pack)
|
||||||
{
|
{
|
||||||
gh.throwOnWrongOwner(&pack, pack.id);
|
gh.throwOnWrongOwner(&pack, pack.id);
|
||||||
|
@ -55,8 +55,7 @@ public:
|
|||||||
virtual void visitBuildBoat(BuildBoat & pack) override;
|
virtual void visitBuildBoat(BuildBoat & pack) override;
|
||||||
virtual void visitQueryReply(QueryReply & pack) override;
|
virtual void visitQueryReply(QueryReply & pack) override;
|
||||||
virtual void visitMakeAction(MakeAction & pack) override;
|
virtual void visitMakeAction(MakeAction & pack) override;
|
||||||
virtual void visitMakeCustomAction(MakeCustomAction & pack) override;
|
|
||||||
virtual void visitDigWithHero(DigWithHero & pack) override;
|
virtual void visitDigWithHero(DigWithHero & pack) override;
|
||||||
virtual void visitCastAdvSpell(CastAdvSpell & pack) override;
|
virtual void visitCastAdvSpell(CastAdvSpell & pack) override;
|
||||||
virtual void visitPlayerMessage(PlayerMessage & pack) override;
|
virtual void visitPlayerMessage(PlayerMessage & pack) override;
|
||||||
};
|
};
|
||||||
|
@ -51,484 +51,530 @@ void BattleActionProcessor::setGameHandler(CGameHandler * newGameHandler)
|
|||||||
gameHandler = newGameHandler;
|
gameHandler = newGameHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BattleActionProcessor::makeBattleAction(BattleAction &ba)
|
bool BattleActionProcessor::doEmptyAction(const BattleAction & ba)
|
||||||
{
|
{
|
||||||
bool ok = true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BattleActionProcessor::doEndTacticsAction(const BattleAction & ba)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BattleActionProcessor::doWaitAction(const BattleAction & ba)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BattleActionProcessor::doBadMoraleAction(const BattleAction & ba)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BattleActionProcessor::doRetreatAction(const BattleAction & ba)
|
||||||
|
{
|
||||||
|
if (!gameHandler->gameState()->curB->battleCanFlee(gameHandler->gameState()->curB->sides.at(ba.side).color))
|
||||||
|
{
|
||||||
|
gameHandler->complain("Cannot retreat!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
owner->setBattleResult(EBattleResult::ESCAPE, !ba.side);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BattleActionProcessor::doSurrenderAction(const BattleAction & ba)
|
||||||
|
{
|
||||||
|
PlayerColor player = gameHandler->gameState()->curB->sides.at(ba.side).color;
|
||||||
|
int cost = gameHandler->gameState()->curB->battleGetSurrenderCost(player);
|
||||||
|
if (cost < 0)
|
||||||
|
{
|
||||||
|
gameHandler->complain("Cannot surrender!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gameHandler->getResource(player, EGameResID::GOLD) < cost)
|
||||||
|
{
|
||||||
|
gameHandler->complain("Not enough gold to surrender!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
gameHandler->giveResource(player, EGameResID::GOLD, -cost);
|
||||||
|
owner->setBattleResult(EBattleResult::SURRENDER, !ba.side);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BattleActionProcessor::doHeroSpellAction(const BattleAction & ba)
|
||||||
|
{
|
||||||
|
const CGHeroInstance *h = gameHandler->gameState()->curB->battleGetFightingHero(ba.side);
|
||||||
|
if (!h)
|
||||||
|
{
|
||||||
|
logGlobal->error("Wrong caster!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CSpell * s = SpellID(ba.actionSubtype).toSpell();
|
||||||
|
if (!s)
|
||||||
|
{
|
||||||
|
logGlobal->error("Wrong spell id (%d)!", ba.actionSubtype);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
spells::BattleCast parameters(gameHandler->gameState()->curB, h, spells::Mode::HERO, s);
|
||||||
|
|
||||||
|
spells::detail::ProblemImpl problem;
|
||||||
|
|
||||||
|
auto m = s->battleMechanics(¶meters);
|
||||||
|
|
||||||
|
if(!m->canBeCast(problem))//todo: should we check aimed cast?
|
||||||
|
{
|
||||||
|
logGlobal->warn("Spell cannot be cast!");
|
||||||
|
std::vector<std::string> texts;
|
||||||
|
problem.getAll(texts);
|
||||||
|
for(auto s : texts)
|
||||||
|
logGlobal->warn(s);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
StartAction start_action(ba);
|
||||||
|
gameHandler->sendAndApply(&start_action); //start spell casting
|
||||||
|
|
||||||
|
parameters.cast(gameHandler->spellEnv, ba.getTarget(gameHandler->gameState()->curB));
|
||||||
|
|
||||||
|
EndAction end_action;
|
||||||
|
gameHandler->sendAndApply(&end_action);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BattleActionProcessor::doWalkAction(const BattleAction & ba)
|
||||||
|
{
|
||||||
|
const CStack * stack = gameHandler->battleGetStackByID(ba.stackNumber);
|
||||||
battle::Target target = ba.getTarget(gameHandler->gameState()->curB);
|
battle::Target target = ba.getTarget(gameHandler->gameState()->curB);
|
||||||
|
|
||||||
const CStack * stack = gameHandler->battleGetStackByID(ba.stackNumber); //may be nullptr if action is not about stack
|
if (!canStackAct(stack))
|
||||||
|
return false;
|
||||||
|
|
||||||
const bool isAboutActiveStack = stack && (ba.stackNumber == gameHandler->gameState()->curB->getActiveStackID());
|
if(target.size() < 1)
|
||||||
|
|
||||||
logGlobal->trace("Making action: %s", ba.toString());
|
|
||||||
|
|
||||||
switch(ba.actionType)
|
|
||||||
{
|
{
|
||||||
case EActionType::WALK: //walk
|
gameHandler->complain("Destination required for move action.");
|
||||||
case EActionType::DEFEND: //defend
|
return false;
|
||||||
case EActionType::WAIT: //wait
|
}
|
||||||
case EActionType::WALK_AND_ATTACK: //walk or attack
|
|
||||||
case EActionType::SHOOT: //shoot
|
|
||||||
case EActionType::CATAPULT: //catapult
|
|
||||||
case EActionType::STACK_HEAL: //healing with First Aid Tent
|
|
||||||
case EActionType::MONSTER_SPELL:
|
|
||||||
|
|
||||||
if (!stack)
|
int walkedTiles = moveStack(ba.stackNumber, target.at(0).hexValue); //move
|
||||||
|
if (!walkedTiles)
|
||||||
|
{
|
||||||
|
gameHandler->complain("Stack failed movement!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BattleActionProcessor::doDefendAction(const BattleAction & ba)
|
||||||
|
{
|
||||||
|
const CStack * stack = gameHandler->battleGetStackByID(ba.stackNumber);
|
||||||
|
|
||||||
|
if (!canStackAct(stack))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
//defensive stance, TODO: filter out spell boosts from bonus (stone skin etc.)
|
||||||
|
SetStackEffect sse;
|
||||||
|
Bonus defenseBonusToAdd(BonusDuration::STACK_GETS_TURN, BonusType::PRIMARY_SKILL, BonusSource::OTHER, 20, -1, PrimarySkill::DEFENSE, BonusValueType::PERCENT_TO_ALL);
|
||||||
|
Bonus bonus2(BonusDuration::STACK_GETS_TURN, BonusType::PRIMARY_SKILL, BonusSource::OTHER, stack->valOfBonuses(BonusType::DEFENSIVE_STANCE), -1, PrimarySkill::DEFENSE, BonusValueType::ADDITIVE_VALUE);
|
||||||
|
Bonus alternativeWeakCreatureBonus(BonusDuration::STACK_GETS_TURN, BonusType::PRIMARY_SKILL, BonusSource::OTHER, 1, -1, PrimarySkill::DEFENSE, BonusValueType::ADDITIVE_VALUE);
|
||||||
|
|
||||||
|
BonusList defence = *stack->getBonuses(Selector::typeSubtype(BonusType::PRIMARY_SKILL, PrimarySkill::DEFENSE));
|
||||||
|
int oldDefenceValue = defence.totalValue();
|
||||||
|
|
||||||
|
defence.push_back(std::make_shared<Bonus>(defenseBonusToAdd));
|
||||||
|
defence.push_back(std::make_shared<Bonus>(bonus2));
|
||||||
|
|
||||||
|
int difference = defence.totalValue() - oldDefenceValue;
|
||||||
|
std::vector<Bonus> buffer;
|
||||||
|
if(difference == 0) //give replacement bonus for creatures not reaching 5 defense points (20% of def becomes 0)
|
||||||
|
{
|
||||||
|
difference = 1;
|
||||||
|
buffer.push_back(alternativeWeakCreatureBonus);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
buffer.push_back(defenseBonusToAdd);
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.push_back(bonus2);
|
||||||
|
|
||||||
|
sse.toUpdate.push_back(std::make_pair(ba.stackNumber, buffer));
|
||||||
|
gameHandler->sendAndApply(&sse);
|
||||||
|
|
||||||
|
BattleLogMessage message;
|
||||||
|
|
||||||
|
MetaString text;
|
||||||
|
stack->addText(text, EMetaText::GENERAL_TXT, 120);
|
||||||
|
stack->addNameReplacement(text);
|
||||||
|
text.replaceNumber(difference);
|
||||||
|
|
||||||
|
message.lines.push_back(text);
|
||||||
|
|
||||||
|
gameHandler->sendAndApply(&message);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BattleActionProcessor::doAttackAction(const BattleAction & ba)
|
||||||
|
{
|
||||||
|
const CStack * stack = gameHandler->battleGetStackByID(ba.stackNumber);
|
||||||
|
battle::Target target = ba.getTarget(gameHandler->gameState()->curB);
|
||||||
|
|
||||||
|
if (!canStackAct(stack))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(target.size() < 2)
|
||||||
|
{
|
||||||
|
gameHandler->complain("Two destinations required for attack action.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BattleHex attackPos = target.at(0).hexValue;
|
||||||
|
BattleHex destinationTile = target.at(1).hexValue;
|
||||||
|
const CStack * destinationStack = gameHandler->gameState()->curB->battleGetStackByPos(destinationTile, true);
|
||||||
|
|
||||||
|
if(!destinationStack)
|
||||||
|
{
|
||||||
|
gameHandler->complain("Invalid target to attack");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BattleHex startingPos = stack->getPosition();
|
||||||
|
int distance = moveStack(ba.stackNumber, attackPos);
|
||||||
|
|
||||||
|
logGlobal->trace("%s will attack %s", stack->nodeName(), destinationStack->nodeName());
|
||||||
|
|
||||||
|
if(stack->getPosition() != attackPos && !(stack->doubleWide() && (stack->getPosition() == attackPos.cloneInDirection(stack->destShiftDir(), false))) )
|
||||||
|
{
|
||||||
|
// we were not able to reach destination tile, nor occupy specified hex
|
||||||
|
// abort attack attempt, but treat this case as legal - we may have stepped onto a quicksands/mine
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(destinationStack && stack->unitId() == destinationStack->unitId()) //we should just move, it will be handled by following check
|
||||||
|
{
|
||||||
|
destinationStack = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!destinationStack)
|
||||||
|
{
|
||||||
|
gameHandler->complain("Unit can not attack itself");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!CStack::isMeleeAttackPossible(stack, destinationStack))
|
||||||
|
{
|
||||||
|
gameHandler->complain("Attack cannot be performed!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//attack
|
||||||
|
int totalAttacks = stack->totalAttacks.getMeleeValue();
|
||||||
|
|
||||||
|
//TODO: move to CUnitState
|
||||||
|
const auto * attackingHero = gameHandler->gameState()->curB->battleGetFightingHero(ba.side);
|
||||||
|
if(attackingHero)
|
||||||
|
{
|
||||||
|
totalAttacks += attackingHero->valOfBonuses(BonusType::HERO_GRANTS_ATTACKS, stack->creatureIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool firstStrike = destinationStack->hasBonusOfType(BonusType::FIRST_STRIKE);
|
||||||
|
const bool retaliation = destinationStack->ableToRetaliate();
|
||||||
|
for (int i = 0; i < totalAttacks; ++i)
|
||||||
|
{
|
||||||
|
//first strike
|
||||||
|
if(i == 0 && firstStrike && retaliation)
|
||||||
{
|
{
|
||||||
gameHandler->complain("No such stack!");
|
makeAttack(destinationStack, stack, 0, stack->getPosition(), true, false, true);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!stack->alive())
|
|
||||||
{
|
|
||||||
gameHandler->complain("This stack is dead: " + stack->nodeName());
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gameHandler->battleTacticDist())
|
//move can cause death, eg. by walking into the moat, first strike can cause death or paralysis/petrification
|
||||||
|
if(stack->alive() && !stack->hasBonusOfType(BonusType::NOT_ACTIVE) && destinationStack->alive())
|
||||||
{
|
{
|
||||||
if (stack && stack->unitSide() != gameHandler->battleGetTacticsSide())
|
makeAttack(stack, destinationStack, (i ? 0 : distance), destinationTile, i==0, false, false);//no distance travelled on second attack
|
||||||
{
|
|
||||||
gameHandler->complain("This is not a stack of side that has tactics!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (!isAboutActiveStack)
|
|
||||||
|
//counterattack
|
||||||
|
//we check retaliation twice, so if it unblocked during attack it will work only on next attack
|
||||||
|
if(stack->alive()
|
||||||
|
&& !stack->hasBonusOfType(BonusType::BLOCKS_RETALIATION)
|
||||||
|
&& (i == 0 && !firstStrike)
|
||||||
|
&& retaliation && destinationStack->ableToRetaliate())
|
||||||
{
|
{
|
||||||
gameHandler->complain("Action has to be about active stack!");
|
makeAttack(destinationStack, stack, 0, stack->getPosition(), true, false, true);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static EndAction end_action;
|
//return
|
||||||
auto wrapAction = [this](BattleAction &ba)
|
if(stack->hasBonusOfType(BonusType::RETURN_AFTER_STRIKE)
|
||||||
|
&& target.size() == 3
|
||||||
|
&& startingPos != stack->getPosition()
|
||||||
|
&& startingPos == target.at(2).hexValue
|
||||||
|
&& stack->alive())
|
||||||
{
|
{
|
||||||
StartAction startAction(ba);
|
moveStack(ba.stackNumber, startingPos);
|
||||||
gameHandler->sendAndApply(&startAction);
|
//NOTE: curStack->unitId() == ba.stackNumber (rev 1431)
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return vstd::makeScopeGuard([&]()
|
bool BattleActionProcessor::doShootAction(const BattleAction & ba)
|
||||||
|
{
|
||||||
|
const CStack * stack = gameHandler->battleGetStackByID(ba.stackNumber);
|
||||||
|
battle::Target target = ba.getTarget(gameHandler->gameState()->curB);
|
||||||
|
|
||||||
|
if (!canStackAct(stack))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(target.size() < 1)
|
||||||
|
{
|
||||||
|
gameHandler->complain("Destination required for shot action.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto destination = target.at(0).hexValue;
|
||||||
|
|
||||||
|
const CStack * destinationStack = gameHandler->gameState()->curB->battleGetStackByPos(destination);
|
||||||
|
|
||||||
|
if (!gameHandler->gameState()->curB->battleCanShoot(stack, destination))
|
||||||
|
{
|
||||||
|
gameHandler->complain("Cannot shoot!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!destinationStack)
|
||||||
|
{
|
||||||
|
gameHandler->complain("No target to shoot!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
makeAttack(stack, destinationStack, 0, destination, true, true, false);
|
||||||
|
|
||||||
|
//ranged counterattack
|
||||||
|
if (destinationStack->hasBonusOfType(BonusType::RANGED_RETALIATION)
|
||||||
|
&& !stack->hasBonusOfType(BonusType::BLOCKS_RANGED_RETALIATION)
|
||||||
|
&& destinationStack->ableToRetaliate()
|
||||||
|
&& gameHandler->gameState()->curB->battleCanShoot(destinationStack, stack->getPosition())
|
||||||
|
&& stack->alive()) //attacker may have died (fire shield)
|
||||||
|
{
|
||||||
|
makeAttack(destinationStack, stack, 0, stack->getPosition(), true, true, true);
|
||||||
|
}
|
||||||
|
//allow more than one additional attack
|
||||||
|
|
||||||
|
int totalRangedAttacks = stack->totalAttacks.getRangedValue();
|
||||||
|
|
||||||
|
//TODO: move to CUnitState
|
||||||
|
const auto * attackingHero = gameHandler->gameState()->curB->battleGetFightingHero(ba.side);
|
||||||
|
if(attackingHero)
|
||||||
|
{
|
||||||
|
totalRangedAttacks += attackingHero->valOfBonuses(BonusType::HERO_GRANTS_ATTACKS, stack->creatureIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i = 1; i < totalRangedAttacks; ++i)
|
||||||
|
{
|
||||||
|
if(
|
||||||
|
stack->alive()
|
||||||
|
&& destinationStack->alive()
|
||||||
|
&& stack->shots.canUse()
|
||||||
|
)
|
||||||
{
|
{
|
||||||
gameHandler->sendAndApply(&end_action);
|
makeAttack(stack, destinationStack, 0, destination, false, true, false);
|
||||||
});
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BattleActionProcessor::doCatapultAction(const BattleAction & ba)
|
||||||
|
{
|
||||||
|
const CStack * stack = gameHandler->gameState()->curB->battleGetStackByID(ba.stackNumber);
|
||||||
|
battle::Target target = ba.getTarget(gameHandler->gameState()->curB);
|
||||||
|
|
||||||
|
if (!canStackAct(stack))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::shared_ptr<const Bonus> catapultAbility = stack->getBonusLocalFirst(Selector::type()(BonusType::CATAPULT));
|
||||||
|
if(!catapultAbility || catapultAbility->subtype < 0)
|
||||||
|
{
|
||||||
|
gameHandler->complain("We do not know how to shoot :P");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const CSpell * spell = SpellID(catapultAbility->subtype).toSpell();
|
||||||
|
spells::BattleCast parameters(gameHandler->gameState()->curB, stack, spells::Mode::SPELL_LIKE_ATTACK, spell); //We can shot infinitely by catapult
|
||||||
|
auto shotLevel = stack->valOfBonuses(Selector::typeSubtype(BonusType::CATAPULT_EXTRA_SHOTS, catapultAbility->subtype));
|
||||||
|
parameters.setSpellLevel(shotLevel);
|
||||||
|
parameters.cast(gameHandler->spellEnv, target);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BattleActionProcessor::doUnitSpellAction(const BattleAction & ba)
|
||||||
|
{
|
||||||
|
const CStack * stack = gameHandler->gameState()->curB->battleGetStackByID(ba.stackNumber);
|
||||||
|
battle::Target target = ba.getTarget(gameHandler->gameState()->curB);
|
||||||
|
SpellID spellID = SpellID(ba.actionSubtype);
|
||||||
|
|
||||||
|
if (!canStackAct(stack))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::shared_ptr<const Bonus> randSpellcaster = stack->getBonus(Selector::type()(BonusType::RANDOM_SPELLCASTER));
|
||||||
|
std::shared_ptr<const Bonus> spellcaster = stack->getBonus(Selector::typeSubtype(BonusType::SPELLCASTER, spellID));
|
||||||
|
|
||||||
|
//TODO special bonus for genies ability
|
||||||
|
if (randSpellcaster && gameHandler->battleGetRandomStackSpell(gameHandler->getRandomGenerator(), stack, CBattleInfoCallback::RANDOM_AIMED) < 0)
|
||||||
|
spellID = gameHandler->battleGetRandomStackSpell(gameHandler->getRandomGenerator(), stack, CBattleInfoCallback::RANDOM_GENIE);
|
||||||
|
|
||||||
|
if (spellID < 0)
|
||||||
|
gameHandler->complain("That stack can't cast spells!");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const CSpell * spell = SpellID(spellID).toSpell();
|
||||||
|
spells::BattleCast parameters(gameHandler->gameState()->curB, stack, spells::Mode::CREATURE_ACTIVE, spell);
|
||||||
|
int32_t spellLvl = 0;
|
||||||
|
if(spellcaster)
|
||||||
|
vstd::amax(spellLvl, spellcaster->val);
|
||||||
|
if(randSpellcaster)
|
||||||
|
vstd::amax(spellLvl, randSpellcaster->val);
|
||||||
|
parameters.setSpellLevel(spellLvl);
|
||||||
|
parameters.cast(gameHandler->spellEnv, target);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BattleActionProcessor::doHealAction(const BattleAction & ba)
|
||||||
|
{
|
||||||
|
const CStack * stack = gameHandler->gameState()->curB->battleGetStackByID(ba.stackNumber);
|
||||||
|
battle::Target target = ba.getTarget(gameHandler->gameState()->curB);
|
||||||
|
|
||||||
|
if (!canStackAct(stack))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(target.size() < 1)
|
||||||
|
{
|
||||||
|
gameHandler->complain("Destination required for heal action.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const battle::Unit * destStack = nullptr;
|
||||||
|
std::shared_ptr<const Bonus> healerAbility = stack->getBonusLocalFirst(Selector::type()(BonusType::HEALER));
|
||||||
|
|
||||||
|
if(target.at(0).unitValue)
|
||||||
|
destStack = target.at(0).unitValue;
|
||||||
|
else
|
||||||
|
destStack = gameHandler->gameState()->curB->battleGetUnitByPos(target.at(0).hexValue);
|
||||||
|
|
||||||
|
if(stack == nullptr || destStack == nullptr || !healerAbility || healerAbility->subtype < 0)
|
||||||
|
{
|
||||||
|
gameHandler->complain("There is either no healer, no destination, or healer cannot heal :P");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const CSpell * spell = SpellID(healerAbility->subtype).toSpell();
|
||||||
|
spells::BattleCast parameters(gameHandler->gameState()->curB, stack, spells::Mode::SPELL_LIKE_ATTACK, spell); //We can heal infinitely by first aid tent
|
||||||
|
auto dest = battle::Destination(destStack, target.at(0).hexValue);
|
||||||
|
parameters.setSpellLevel(0);
|
||||||
|
parameters.cast(gameHandler->spellEnv, {dest});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BattleActionProcessor::canStackAct(const CStack * stack)
|
||||||
|
{
|
||||||
|
const bool isAboutActiveStack = stack->unitId() == gameHandler->gameState()->curB->getActiveStackID();
|
||||||
|
|
||||||
|
if (!stack)
|
||||||
|
{
|
||||||
|
gameHandler->complain("No such stack!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!stack->alive())
|
||||||
|
{
|
||||||
|
gameHandler->complain("This stack is dead: " + stack->nodeName());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gameHandler->battleTacticDist())
|
||||||
|
{
|
||||||
|
if (stack && stack->unitSide() != gameHandler->battleGetTacticsSide())
|
||||||
|
{
|
||||||
|
gameHandler->complain("This is not a stack of side that has tactics!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!isAboutActiveStack)
|
||||||
|
{
|
||||||
|
gameHandler->complain("Action has to be about active stack!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BattleActionProcessor::dispatchBattleAction(const BattleAction & ba)
|
||||||
|
{
|
||||||
switch(ba.actionType)
|
switch(ba.actionType)
|
||||||
{
|
{
|
||||||
case EActionType::END_TACTIC_PHASE: //wait
|
case EActionType::NO_ACTION:
|
||||||
case EActionType::BAD_MORALE:
|
return doEmptyAction(ba);
|
||||||
case EActionType::NO_ACTION:
|
case EActionType::END_TACTIC_PHASE:
|
||||||
{
|
return doEndTacticsAction(ba);
|
||||||
auto wrapper = wrapAction(ba);
|
case EActionType::RETREAT:
|
||||||
break;
|
return doRetreatAction(ba);
|
||||||
}
|
case EActionType::SURRENDER:
|
||||||
case EActionType::WALK:
|
return doSurrenderAction(ba);
|
||||||
{
|
case EActionType::HERO_SPELL:
|
||||||
auto wrapper = wrapAction(ba);
|
return doHeroSpellAction(ba);
|
||||||
if(target.size() < 1)
|
case EActionType::WALK:
|
||||||
{
|
return doWalkAction(ba);
|
||||||
gameHandler->complain("Destination required for move action.");
|
case EActionType::WAIT:
|
||||||
ok = false;
|
return doWaitAction(ba);
|
||||||
break;
|
case EActionType::DEFEND:
|
||||||
}
|
return doDefendAction(ba);
|
||||||
int walkedTiles = moveStack(ba.stackNumber, target.at(0).hexValue); //move
|
case EActionType::WALK_AND_ATTACK:
|
||||||
if (!walkedTiles)
|
return doAttackAction(ba);
|
||||||
gameHandler->complain("Stack failed movement!");
|
case EActionType::SHOOT:
|
||||||
break;
|
return doShootAction(ba);
|
||||||
}
|
case EActionType::CATAPULT:
|
||||||
case EActionType::DEFEND:
|
return doCatapultAction(ba);
|
||||||
{
|
|
||||||
//defensive stance, TODO: filter out spell boosts from bonus (stone skin etc.)
|
|
||||||
SetStackEffect sse;
|
|
||||||
Bonus defenseBonusToAdd(BonusDuration::STACK_GETS_TURN, BonusType::PRIMARY_SKILL, BonusSource::OTHER, 20, -1, PrimarySkill::DEFENSE, BonusValueType::PERCENT_TO_ALL);
|
|
||||||
Bonus bonus2(BonusDuration::STACK_GETS_TURN, BonusType::PRIMARY_SKILL, BonusSource::OTHER, stack->valOfBonuses(BonusType::DEFENSIVE_STANCE),
|
|
||||||
-1, PrimarySkill::DEFENSE, BonusValueType::ADDITIVE_VALUE);
|
|
||||||
Bonus alternativeWeakCreatureBonus(BonusDuration::STACK_GETS_TURN, BonusType::PRIMARY_SKILL, BonusSource::OTHER, 1, -1, PrimarySkill::DEFENSE, BonusValueType::ADDITIVE_VALUE);
|
|
||||||
|
|
||||||
BonusList defence = *stack->getBonuses(Selector::typeSubtype(BonusType::PRIMARY_SKILL, PrimarySkill::DEFENSE));
|
|
||||||
int oldDefenceValue = defence.totalValue();
|
|
||||||
|
|
||||||
defence.push_back(std::make_shared<Bonus>(defenseBonusToAdd));
|
|
||||||
defence.push_back(std::make_shared<Bonus>(bonus2));
|
|
||||||
|
|
||||||
int difference = defence.totalValue() - oldDefenceValue;
|
|
||||||
std::vector<Bonus> buffer;
|
|
||||||
if(difference == 0) //give replacement bonus for creatures not reaching 5 defense points (20% of def becomes 0)
|
|
||||||
{
|
|
||||||
difference = 1;
|
|
||||||
buffer.push_back(alternativeWeakCreatureBonus);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
buffer.push_back(defenseBonusToAdd);
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.push_back(bonus2);
|
|
||||||
|
|
||||||
sse.toUpdate.push_back(std::make_pair(ba.stackNumber, buffer));
|
|
||||||
gameHandler->sendAndApply(&sse);
|
|
||||||
|
|
||||||
BattleLogMessage message;
|
|
||||||
|
|
||||||
MetaString text;
|
|
||||||
stack->addText(text, EMetaText::GENERAL_TXT, 120);
|
|
||||||
stack->addNameReplacement(text);
|
|
||||||
text.replaceNumber(difference);
|
|
||||||
|
|
||||||
message.lines.push_back(text);
|
|
||||||
|
|
||||||
gameHandler->sendAndApply(&message);
|
|
||||||
//don't break - we share code with next case
|
|
||||||
}
|
|
||||||
[[fallthrough]];
|
|
||||||
case EActionType::WAIT:
|
|
||||||
{
|
|
||||||
auto wrapper = wrapAction(ba);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case EActionType::RETREAT: //retreat/flee
|
|
||||||
{
|
|
||||||
if (!gameHandler->gameState()->curB->battleCanFlee(gameHandler->gameState()->curB->sides.at(ba.side).color))
|
|
||||||
gameHandler->complain("Cannot retreat!");
|
|
||||||
else
|
|
||||||
owner->setBattleResult(EBattleResult::ESCAPE, !ba.side); //surrendering side loses
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case EActionType::SURRENDER:
|
|
||||||
{
|
|
||||||
PlayerColor player = gameHandler->gameState()->curB->sides.at(ba.side).color;
|
|
||||||
int cost = gameHandler->gameState()->curB->battleGetSurrenderCost(player);
|
|
||||||
if (cost < 0)
|
|
||||||
gameHandler->complain("Cannot surrender!");
|
|
||||||
else if (gameHandler->getResource(player, EGameResID::GOLD) < cost)
|
|
||||||
gameHandler->complain("Not enough gold to surrender!");
|
|
||||||
else
|
|
||||||
{
|
|
||||||
gameHandler->giveResource(player, EGameResID::GOLD, -cost);
|
|
||||||
owner->setBattleResult(EBattleResult::SURRENDER, !ba.side); //surrendering side loses
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case EActionType::WALK_AND_ATTACK: //walk or attack
|
|
||||||
{
|
|
||||||
auto wrapper = wrapAction(ba);
|
|
||||||
|
|
||||||
if(!stack)
|
|
||||||
{
|
|
||||||
gameHandler->complain("No attacker");
|
|
||||||
ok = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(target.size() < 2)
|
|
||||||
{
|
|
||||||
gameHandler->complain("Two destinations required for attack action.");
|
|
||||||
ok = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
BattleHex attackPos = target.at(0).hexValue;
|
|
||||||
BattleHex destinationTile = target.at(1).hexValue;
|
|
||||||
const CStack * destinationStack = gameHandler->gameState()->curB->battleGetStackByPos(destinationTile, true);
|
|
||||||
|
|
||||||
if(!destinationStack)
|
|
||||||
{
|
|
||||||
gameHandler->complain("Invalid target to attack");
|
|
||||||
ok = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
BattleHex startingPos = stack->getPosition();
|
|
||||||
int distance = moveStack(ba.stackNumber, attackPos);
|
|
||||||
|
|
||||||
logGlobal->trace("%s will attack %s", stack->nodeName(), destinationStack->nodeName());
|
|
||||||
|
|
||||||
if(stack->getPosition() != attackPos
|
|
||||||
&& !(stack->doubleWide() && (stack->getPosition() == attackPos.cloneInDirection(stack->destShiftDir(), false)))
|
|
||||||
)
|
|
||||||
{
|
|
||||||
// we were not able to reach destination tile, nor occupy specified hex
|
|
||||||
// abort attack attempt, but treat this case as legal - we may have stepped onto a quicksands/mine
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(destinationStack && stack->unitId() == destinationStack->unitId()) //we should just move, it will be handled by following check
|
|
||||||
{
|
|
||||||
destinationStack = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!destinationStack)
|
|
||||||
{
|
|
||||||
gameHandler->complain("Unit can not attack itself");
|
|
||||||
ok = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!CStack::isMeleeAttackPossible(stack, destinationStack))
|
|
||||||
{
|
|
||||||
gameHandler->complain("Attack cannot be performed!");
|
|
||||||
ok = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
//attack
|
|
||||||
int totalAttacks = stack->totalAttacks.getMeleeValue();
|
|
||||||
|
|
||||||
//TODO: move to CUnitState
|
|
||||||
const auto * attackingHero = gameHandler->gameState()->curB->battleGetFightingHero(ba.side);
|
|
||||||
if(attackingHero)
|
|
||||||
{
|
|
||||||
totalAttacks += attackingHero->valOfBonuses(BonusType::HERO_GRANTS_ATTACKS, stack->creatureIndex());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const bool firstStrike = destinationStack->hasBonusOfType(BonusType::FIRST_STRIKE);
|
|
||||||
const bool retaliation = destinationStack->ableToRetaliate();
|
|
||||||
for (int i = 0; i < totalAttacks; ++i)
|
|
||||||
{
|
|
||||||
//first strike
|
|
||||||
if(i == 0 && firstStrike && retaliation)
|
|
||||||
{
|
|
||||||
makeAttack(destinationStack, stack, 0, stack->getPosition(), true, false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
//move can cause death, eg. by walking into the moat, first strike can cause death or paralysis/petrification
|
|
||||||
if(stack->alive() && !stack->hasBonusOfType(BonusType::NOT_ACTIVE) && destinationStack->alive())
|
|
||||||
{
|
|
||||||
makeAttack(stack, destinationStack, (i ? 0 : distance), destinationTile, i==0, false, false);//no distance travelled on second attack
|
|
||||||
}
|
|
||||||
|
|
||||||
//counterattack
|
|
||||||
//we check retaliation twice, so if it unblocked during attack it will work only on next attack
|
|
||||||
if(stack->alive()
|
|
||||||
&& !stack->hasBonusOfType(BonusType::BLOCKS_RETALIATION)
|
|
||||||
&& (i == 0 && !firstStrike)
|
|
||||||
&& retaliation && destinationStack->ableToRetaliate())
|
|
||||||
{
|
|
||||||
makeAttack(destinationStack, stack, 0, stack->getPosition(), true, false, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//return
|
|
||||||
if(stack->hasBonusOfType(BonusType::RETURN_AFTER_STRIKE)
|
|
||||||
&& target.size() == 3
|
|
||||||
&& startingPos != stack->getPosition()
|
|
||||||
&& startingPos == target.at(2).hexValue
|
|
||||||
&& stack->alive())
|
|
||||||
{
|
|
||||||
moveStack(ba.stackNumber, startingPos);
|
|
||||||
//NOTE: curStack->unitId() == ba.stackNumber (rev 1431)
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case EActionType::SHOOT:
|
|
||||||
{
|
|
||||||
if(target.size() < 1)
|
|
||||||
{
|
|
||||||
gameHandler->complain("Destination required for shot action.");
|
|
||||||
ok = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto destination = target.at(0).hexValue;
|
|
||||||
|
|
||||||
const CStack * destinationStack = gameHandler->gameState()->curB->battleGetStackByPos(destination);
|
|
||||||
|
|
||||||
if (!gameHandler->gameState()->curB->battleCanShoot(stack, destination))
|
|
||||||
{
|
|
||||||
gameHandler->complain("Cannot shoot!");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!destinationStack)
|
|
||||||
{
|
|
||||||
gameHandler->complain("No target to shoot!");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto wrapper = wrapAction(ba);
|
|
||||||
|
|
||||||
makeAttack(stack, destinationStack, 0, destination, true, true, false);
|
|
||||||
|
|
||||||
//ranged counterattack
|
|
||||||
if (destinationStack->hasBonusOfType(BonusType::RANGED_RETALIATION)
|
|
||||||
&& !stack->hasBonusOfType(BonusType::BLOCKS_RANGED_RETALIATION)
|
|
||||||
&& destinationStack->ableToRetaliate()
|
|
||||||
&& gameHandler->gameState()->curB->battleCanShoot(destinationStack, stack->getPosition())
|
|
||||||
&& stack->alive()) //attacker may have died (fire shield)
|
|
||||||
{
|
|
||||||
makeAttack(destinationStack, stack, 0, stack->getPosition(), true, true, true);
|
|
||||||
}
|
|
||||||
//allow more than one additional attack
|
|
||||||
|
|
||||||
int totalRangedAttacks = stack->totalAttacks.getRangedValue();
|
|
||||||
|
|
||||||
//TODO: move to CUnitState
|
|
||||||
const auto * attackingHero = gameHandler->gameState()->curB->battleGetFightingHero(ba.side);
|
|
||||||
if(attackingHero)
|
|
||||||
{
|
|
||||||
totalRangedAttacks += attackingHero->valOfBonuses(BonusType::HERO_GRANTS_ATTACKS, stack->creatureIndex());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
for(int i = 1; i < totalRangedAttacks; ++i)
|
|
||||||
{
|
|
||||||
if(
|
|
||||||
stack->alive()
|
|
||||||
&& destinationStack->alive()
|
|
||||||
&& stack->shots.canUse()
|
|
||||||
)
|
|
||||||
{
|
|
||||||
makeAttack(stack, destinationStack, 0, destination, false, true, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case EActionType::CATAPULT:
|
|
||||||
{
|
|
||||||
auto wrapper = wrapAction(ba);
|
|
||||||
const CStack * shooter = gameHandler->gameState()->curB->battleGetStackByID(ba.stackNumber);
|
|
||||||
std::shared_ptr<const Bonus> catapultAbility = stack->getBonusLocalFirst(Selector::type()(BonusType::CATAPULT));
|
|
||||||
if(!catapultAbility || catapultAbility->subtype < 0)
|
|
||||||
{
|
|
||||||
gameHandler->complain("We do not know how to shoot :P");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const CSpell * spell = SpellID(catapultAbility->subtype).toSpell();
|
|
||||||
spells::BattleCast parameters(gameHandler->gameState()->curB, shooter, spells::Mode::SPELL_LIKE_ATTACK, spell); //We can shot infinitely by catapult
|
|
||||||
auto shotLevel = stack->valOfBonuses(Selector::typeSubtype(BonusType::CATAPULT_EXTRA_SHOTS, catapultAbility->subtype));
|
|
||||||
parameters.setSpellLevel(shotLevel);
|
|
||||||
parameters.cast(gameHandler->spellEnv, target);
|
|
||||||
}
|
|
||||||
//finish by scope guard
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case EActionType::STACK_HEAL: //healing with First Aid Tent
|
|
||||||
{
|
|
||||||
auto wrapper = wrapAction(ba);
|
|
||||||
const CStack * healer = gameHandler->gameState()->curB->battleGetStackByID(ba.stackNumber);
|
|
||||||
|
|
||||||
if(target.size() < 1)
|
|
||||||
{
|
|
||||||
gameHandler->complain("Destination required for heal action.");
|
|
||||||
ok = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const battle::Unit * destStack = nullptr;
|
|
||||||
std::shared_ptr<const Bonus> healerAbility = stack->getBonusLocalFirst(Selector::type()(BonusType::HEALER));
|
|
||||||
|
|
||||||
if(target.at(0).unitValue)
|
|
||||||
destStack = target.at(0).unitValue;
|
|
||||||
else
|
|
||||||
destStack = gameHandler->gameState()->curB->battleGetUnitByPos(target.at(0).hexValue);
|
|
||||||
|
|
||||||
if(healer == nullptr || destStack == nullptr || !healerAbility || healerAbility->subtype < 0)
|
|
||||||
{
|
|
||||||
gameHandler->complain("There is either no healer, no destination, or healer cannot heal :P");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const CSpell * spell = SpellID(healerAbility->subtype).toSpell();
|
|
||||||
spells::BattleCast parameters(gameHandler->gameState()->curB, healer, spells::Mode::SPELL_LIKE_ATTACK, spell); //We can heal infinitely by first aid tent
|
|
||||||
auto dest = battle::Destination(destStack, target.at(0).hexValue);
|
|
||||||
parameters.setSpellLevel(0);
|
|
||||||
parameters.cast(gameHandler->spellEnv, {dest});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case EActionType::MONSTER_SPELL:
|
case EActionType::MONSTER_SPELL:
|
||||||
{
|
return doUnitSpellAction(ba);
|
||||||
auto wrapper = wrapAction(ba);
|
case EActionType::BAD_MORALE:
|
||||||
|
return doBadMoraleAction(ba);
|
||||||
const CStack * stack = gameHandler->gameState()->curB->battleGetStackByID(ba.stackNumber);
|
case EActionType::STACK_HEAL:
|
||||||
SpellID spellID = SpellID(ba.actionSubtype);
|
return doHealAction(ba);
|
||||||
|
|
||||||
std::shared_ptr<const Bonus> randSpellcaster = stack->getBonus(Selector::type()(BonusType::RANDOM_SPELLCASTER));
|
|
||||||
std::shared_ptr<const Bonus> spellcaster = stack->getBonus(Selector::typeSubtype(BonusType::SPELLCASTER, spellID));
|
|
||||||
|
|
||||||
//TODO special bonus for genies ability
|
|
||||||
if (randSpellcaster && gameHandler->battleGetRandomStackSpell(gameHandler->getRandomGenerator(), stack, CBattleInfoCallback::RANDOM_AIMED) < 0)
|
|
||||||
spellID = gameHandler->battleGetRandomStackSpell(gameHandler->getRandomGenerator(), stack, CBattleInfoCallback::RANDOM_GENIE);
|
|
||||||
|
|
||||||
if (spellID < 0)
|
|
||||||
gameHandler->complain("That stack can't cast spells!");
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const CSpell * spell = SpellID(spellID).toSpell();
|
|
||||||
spells::BattleCast parameters(gameHandler->gameState()->curB, stack, spells::Mode::CREATURE_ACTIVE, spell);
|
|
||||||
int32_t spellLvl = 0;
|
|
||||||
if(spellcaster)
|
|
||||||
vstd::amax(spellLvl, spellcaster->val);
|
|
||||||
if(randSpellcaster)
|
|
||||||
vstd::amax(spellLvl, randSpellcaster->val);
|
|
||||||
parameters.setSpellLevel(spellLvl);
|
|
||||||
parameters.cast(gameHandler->spellEnv, target);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
gameHandler->complain("Unrecognized action type received!!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BattleActionProcessor::makeBattleAction(const BattleAction &ba)
|
||||||
|
{
|
||||||
|
logGlobal->trace("Making action: %s", ba.toString());
|
||||||
|
const CStack * stack = gameHandler->gameState()->curB->battleGetStackByID(ba.stackNumber);
|
||||||
|
|
||||||
|
StartAction startAction(ba);
|
||||||
|
gameHandler->sendAndApply(&startAction);
|
||||||
|
|
||||||
|
bool result = dispatchBattleAction(ba);
|
||||||
|
|
||||||
|
EndAction endAction;
|
||||||
|
gameHandler->sendAndApply(&endAction);
|
||||||
|
|
||||||
if(ba.actionType == EActionType::WAIT || ba.actionType == EActionType::DEFEND || ba.actionType == EActionType::SHOOT || ba.actionType == EActionType::MONSTER_SPELL)
|
if(ba.actionType == EActionType::WAIT || ba.actionType == EActionType::DEFEND || ba.actionType == EActionType::SHOOT || ba.actionType == EActionType::MONSTER_SPELL)
|
||||||
gameHandler->handleObstacleTriggersForUnit(*gameHandler->spellEnv, *stack);
|
gameHandler->handleObstacleTriggersForUnit(*gameHandler->spellEnv, *stack);
|
||||||
|
|
||||||
return ok;
|
return result;
|
||||||
}
|
|
||||||
|
|
||||||
bool BattleActionProcessor::makeCustomAction(BattleAction & ba)
|
|
||||||
{
|
|
||||||
switch(ba.actionType)
|
|
||||||
{
|
|
||||||
case EActionType::HERO_SPELL:
|
|
||||||
{
|
|
||||||
const CGHeroInstance *h = gameHandler->gameState()->curB->battleGetFightingHero(ba.side);
|
|
||||||
if (!h)
|
|
||||||
{
|
|
||||||
logGlobal->error("Wrong caster!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CSpell * s = SpellID(ba.actionSubtype).toSpell();
|
|
||||||
if (!s)
|
|
||||||
{
|
|
||||||
logGlobal->error("Wrong spell id (%d)!", ba.actionSubtype);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
spells::BattleCast parameters(gameHandler->gameState()->curB, h, spells::Mode::HERO, s);
|
|
||||||
|
|
||||||
spells::detail::ProblemImpl problem;
|
|
||||||
|
|
||||||
auto m = s->battleMechanics(¶meters);
|
|
||||||
|
|
||||||
if(!m->canBeCast(problem))//todo: should we check aimed cast?
|
|
||||||
{
|
|
||||||
logGlobal->warn("Spell cannot be cast!");
|
|
||||||
std::vector<std::string> texts;
|
|
||||||
problem.getAll(texts);
|
|
||||||
for(auto s : texts)
|
|
||||||
logGlobal->warn(s);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
StartAction start_action(ba);
|
|
||||||
gameHandler->sendAndApply(&start_action); //start spell casting
|
|
||||||
|
|
||||||
parameters.cast(gameHandler->spellEnv, ba.getTarget(gameHandler->gameState()->curB));
|
|
||||||
|
|
||||||
EndAction end_action;
|
|
||||||
gameHandler->sendAndApply(&end_action);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int BattleActionProcessor::moveStack(int stack, BattleHex dest)
|
int BattleActionProcessor::moveStack(int stack, BattleHex dest)
|
||||||
{
|
{
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
const CStack *curStack = gameHandler->battleGetStackByID(stack),
|
const CStack *curStack = gameHandler->battleGetStackByID(stack);
|
||||||
*stackAtEnd = gameHandler->gameState()->curB->battleGetStackByPos(dest);
|
const CStack *stackAtEnd = gameHandler->gameState()->curB->battleGetStackByPos(dest);
|
||||||
|
|
||||||
assert(curStack);
|
assert(curStack);
|
||||||
assert(dest < GameConstants::BFIELD_SIZE);
|
assert(dest < GameConstants::BFIELD_SIZE);
|
||||||
|
@ -13,7 +13,6 @@ VCMI_LIB_NAMESPACE_BEGIN
|
|||||||
|
|
||||||
struct BattleLogMessage;
|
struct BattleLogMessage;
|
||||||
struct BattleAttack;
|
struct BattleAttack;
|
||||||
class BattleProcessor;
|
|
||||||
class BattleAction;
|
class BattleAction;
|
||||||
struct BattleHex;
|
struct BattleHex;
|
||||||
class CStack;
|
class CStack;
|
||||||
@ -27,6 +26,7 @@ class CUnitState;
|
|||||||
VCMI_LIB_NAMESPACE_END
|
VCMI_LIB_NAMESPACE_END
|
||||||
|
|
||||||
class CGameHandler;
|
class CGameHandler;
|
||||||
|
class BattleProcessor;
|
||||||
|
|
||||||
class BattleActionProcessor : boost::noncopyable
|
class BattleActionProcessor : boost::noncopyable
|
||||||
{
|
{
|
||||||
@ -48,11 +48,30 @@ class BattleActionProcessor : boost::noncopyable
|
|||||||
void sendGenericKilledLog(const CStack * defender, int32_t killed, bool multiple);
|
void sendGenericKilledLog(const CStack * defender, int32_t killed, bool multiple);
|
||||||
void addGenericKilledLog(BattleLogMessage & blm, const CStack * defender, int32_t killed, bool multiple);
|
void addGenericKilledLog(BattleLogMessage & blm, const CStack * defender, int32_t killed, bool multiple);
|
||||||
|
|
||||||
|
bool canStackAct(const CStack * stack);
|
||||||
|
|
||||||
|
bool doEmptyAction(const BattleAction & ba);
|
||||||
|
bool doEndTacticsAction(const BattleAction & ba);
|
||||||
|
bool doRetreatAction(const BattleAction & ba);
|
||||||
|
bool doSurrenderAction(const BattleAction & ba);
|
||||||
|
bool doHeroSpellAction(const BattleAction & ba);
|
||||||
|
bool doWalkAction(const BattleAction & ba);
|
||||||
|
bool doWaitAction(const BattleAction & ba);
|
||||||
|
bool doDefendAction(const BattleAction & ba);
|
||||||
|
bool doAttackAction(const BattleAction & ba);
|
||||||
|
bool doShootAction(const BattleAction & ba);
|
||||||
|
bool doCatapultAction(const BattleAction & ba);
|
||||||
|
bool doUnitSpellAction(const BattleAction & ba);
|
||||||
|
bool doBadMoraleAction(const BattleAction & ba);
|
||||||
|
bool doHealAction(const BattleAction & ba);
|
||||||
|
|
||||||
|
bool dispatchBattleAction(const BattleAction & ba);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
BattleActionProcessor(BattleProcessor * owner);
|
explicit BattleActionProcessor(BattleProcessor * owner);
|
||||||
void setGameHandler(CGameHandler * newGameHandler);
|
void setGameHandler(CGameHandler * newGameHandler);
|
||||||
|
|
||||||
bool makeBattleAction(BattleAction &ba);
|
bool makeBattleAction(const BattleAction &ba);
|
||||||
bool makeCustomAction(BattleAction &ba);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -290,21 +290,35 @@ void BattleFlowProcessor::activateNextStack()
|
|||||||
//TODO: activate next round if next == nullptr
|
//TODO: activate next round if next == nullptr
|
||||||
const auto & curB = *gameHandler->gameState()->curB;
|
const auto & curB = *gameHandler->gameState()->curB;
|
||||||
|
|
||||||
const CStack * next = getNextStack();
|
// Find next stack that requires manual control
|
||||||
|
for (;;)
|
||||||
if (!next)
|
|
||||||
return;
|
|
||||||
|
|
||||||
BattleUnitsChanged removeGhosts;
|
|
||||||
|
|
||||||
for(auto stack : curB.stacks)
|
|
||||||
{
|
{
|
||||||
if(stack->ghostPending)
|
const CStack * next = getNextStack();
|
||||||
removeGhosts.changedStacks.emplace_back(stack->unitId(), UnitChanges::EOperation::REMOVE);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!removeGhosts.changedStacks.empty())
|
if (!next)
|
||||||
gameHandler->sendAndApply(&removeGhosts);
|
return;
|
||||||
|
|
||||||
|
BattleUnitsChanged removeGhosts;
|
||||||
|
|
||||||
|
for(auto stack : curB.stacks)
|
||||||
|
{
|
||||||
|
if(stack->ghostPending)
|
||||||
|
removeGhosts.changedStacks.emplace_back(stack->unitId(), UnitChanges::EOperation::REMOVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!removeGhosts.changedStacks.empty())
|
||||||
|
gameHandler->sendAndApply(&removeGhosts);
|
||||||
|
|
||||||
|
if (!tryMakeAutomaticAction(next))
|
||||||
|
{
|
||||||
|
logGlobal->trace("Activating %s", next->nodeName());
|
||||||
|
auto nextId = next->unitId();
|
||||||
|
BattleSetActiveStack sas;
|
||||||
|
sas.stack = nextId;
|
||||||
|
gameHandler->sendAndApply(&sas);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BattleFlowProcessor::tryMakeAutomaticAction(const CStack * next)
|
bool BattleFlowProcessor::tryMakeAutomaticAction(const CStack * next)
|
||||||
@ -423,7 +437,7 @@ bool BattleFlowProcessor::tryMakeAutomaticAction(const CStack * next)
|
|||||||
return s->unitOwner() == next->unitOwner() && s->canBeHealed();
|
return s->unitOwner() == next->unitOwner() && s->canBeHealed();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!possibleStacks.size())
|
if (possibleStacks.empty())
|
||||||
{
|
{
|
||||||
makeStackDoNothing(next);
|
makeStackDoNothing(next);
|
||||||
return true;
|
return true;
|
||||||
@ -452,25 +466,11 @@ bool BattleFlowProcessor::tryMakeAutomaticAction(const CStack * next)
|
|||||||
makeStackDoNothing(next); //end immediately if stack was affected by fear
|
makeStackDoNothing(next); //end immediately if stack was affected by fear
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
return false;
|
||||||
{
|
|
||||||
logGlobal->trace("Activating %s", next->nodeName());
|
|
||||||
auto nextId = next->unitId();
|
|
||||||
BattleSetActiveStack sas;
|
|
||||||
sas.stack = nextId;
|
|
||||||
gameHandler->sendAndApply(&sas);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BattleFlowProcessor::onActionMade(const CStack *next)
|
bool BattleFlowProcessor::rollGoodMorale(const CStack * next)
|
||||||
{
|
{
|
||||||
//we're after action, all results applied
|
|
||||||
owner->checkBattleStateChanges(); //check if this action ended the battle
|
|
||||||
|
|
||||||
if(next == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
//check for good morale
|
//check for good morale
|
||||||
auto nextStackMorale = next->moraleVal();
|
auto nextStackMorale = next->moraleVal();
|
||||||
if( !next->hadMorale
|
if( !next->hadMorale
|
||||||
@ -491,11 +491,38 @@ void BattleFlowProcessor::onActionMade(const CStack *next)
|
|||||||
bte.val = 1;
|
bte.val = 1;
|
||||||
bte.additionalInfo = 0;
|
bte.additionalInfo = 0;
|
||||||
gameHandler->sendAndApply(&bte); //play animation
|
gameHandler->sendAndApply(&bte); //play animation
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (gameHandler->gameLobby()->state != EServerState::SHUTDOWN)
|
void BattleFlowProcessor::onActionMade(const BattleAction &ba)
|
||||||
owner->endBattle(gameHandler->gameState()->curB->tile, gameHandler->gameState()->curB->battleGetFightingHero(0), gameHandler->gameState()->curB->battleGetFightingHero(1));
|
{
|
||||||
|
const CStack * next = gameHandler->gameState()->curB->battleGetStackByID(ba.stackNumber);
|
||||||
|
|
||||||
|
//we're after action, all results applied
|
||||||
|
owner->checkBattleStateChanges(); //check if this action ended the battle
|
||||||
|
|
||||||
|
if(next == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool heroAction = ba.actionType == EActionType::HERO_SPELL || ba.actionType ==EActionType::SURRENDER|| ba.actionType ==EActionType::RETREAT;
|
||||||
|
|
||||||
|
if (heroAction && next->alive())
|
||||||
|
{
|
||||||
|
// this is action made by hero AND unit is alive (e.g. not killed by casted spell)
|
||||||
|
// keep current active stack for next action
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rollGoodMorale(next))
|
||||||
|
{
|
||||||
|
// Good morale - same stack makes 2nd turn
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
activateNextStack();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BattleFlowProcessor::makeStackDoNothing(const CStack * next)
|
void BattleFlowProcessor::makeStackDoNothing(const CStack * next)
|
||||||
|
@ -26,6 +26,7 @@ class BattleFlowProcessor : boost::noncopyable
|
|||||||
|
|
||||||
const CStack * getNextStack();
|
const CStack * getNextStack();
|
||||||
|
|
||||||
|
bool rollGoodMorale(const CStack * stack);
|
||||||
bool tryMakeAutomaticAction(const CStack * stack);
|
bool tryMakeAutomaticAction(const CStack * stack);
|
||||||
|
|
||||||
void summonGuardiansHelper(std::vector<BattleHex> & output, const BattleHex & targetPosition, ui8 side, bool targetIsTwoHex);
|
void summonGuardiansHelper(std::vector<BattleHex> & output, const BattleHex & targetPosition, ui8 side, bool targetIsTwoHex);
|
||||||
@ -40,10 +41,10 @@ class BattleFlowProcessor : boost::noncopyable
|
|||||||
bool makeAutomaticAction(const CStack *stack, BattleAction &ba); //used when action is taken by stack without volition of player (eg. unguided catapult attack)
|
bool makeAutomaticAction(const CStack *stack, BattleAction &ba); //used when action is taken by stack without volition of player (eg. unguided catapult attack)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
BattleFlowProcessor(BattleProcessor * owner);
|
explicit BattleFlowProcessor(BattleProcessor * owner);
|
||||||
void setGameHandler(CGameHandler * newGameHandler);
|
void setGameHandler(CGameHandler * newGameHandler);
|
||||||
|
|
||||||
void onBattleStarted();
|
void onBattleStarted();
|
||||||
void onTacticsEnded();
|
void onTacticsEnded();
|
||||||
void onActionMade(const CStack *stack);
|
void onActionMade(const BattleAction &ba);
|
||||||
};
|
};
|
||||||
|
@ -270,58 +270,20 @@ bool BattleProcessor::makeBattleAction(PlayerColor player, BattleAction &ba)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return actionsProcessor->makeBattleAction(ba);
|
return makeBattleAction(ba);
|
||||||
}
|
|
||||||
|
|
||||||
bool BattleProcessor::makeCustomAction(PlayerColor player, BattleAction &ba)
|
|
||||||
{
|
|
||||||
const BattleInfo * b = gameHandler->gameState()->curB;
|
|
||||||
|
|
||||||
if(!b && gameHandler->complain("Can not make action - there is no battle ongoing!"))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (ba.side != 0 && ba.side != 1 && gameHandler->complain("Can not make action - invalid battle side!"))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if(b->tacticDistance)
|
|
||||||
{
|
|
||||||
gameHandler->complain("Can not cast spell during tactics mode!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto active = b->battleActiveUnit();
|
|
||||||
if(!active && gameHandler->complain("No active unit in battle!"))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
auto unitOwner = b->battleGetOwner(active);
|
|
||||||
|
|
||||||
if(player != unitOwner && gameHandler->complain("Can not make actions in battles you are not part of!"))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if(ba.actionType != EActionType::HERO_SPELL && gameHandler->complain("Invalid custom action type!"))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return actionsProcessor->makeCustomAction(ba);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BattleProcessor::setBattleResult(EBattleResult resultType, int victoriusSide)
|
void BattleProcessor::setBattleResult(EBattleResult resultType, int victoriusSide)
|
||||||
{
|
{
|
||||||
resultProcessor->setBattleResult(resultType, victoriusSide);
|
resultProcessor->setBattleResult(resultType, victoriusSide);
|
||||||
|
resultProcessor->endBattle(gameHandler->gameState()->curB->tile, gameHandler->gameState()->curB->battleGetFightingHero(0), gameHandler->gameState()->curB->battleGetFightingHero(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BattleProcessor::makeBattleAction(BattleAction &ba)
|
bool BattleProcessor::makeBattleAction(const BattleAction &ba)
|
||||||
{
|
{
|
||||||
return actionsProcessor->makeBattleAction(ba);
|
bool result = actionsProcessor->makeBattleAction(ba);
|
||||||
}
|
flowProcessor->onActionMade(ba);
|
||||||
|
return result;
|
||||||
bool BattleProcessor::makeCustomAction(BattleAction &ba)
|
|
||||||
{
|
|
||||||
return actionsProcessor->makeCustomAction(ba);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BattleProcessor::endBattle(int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2)
|
|
||||||
{
|
|
||||||
resultProcessor->endBattle(tile, hero1, hero2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BattleProcessor::endBattleConfirm(const BattleInfo * battleInfo)
|
void BattleProcessor::endBattleConfirm(const BattleInfo * battleInfo)
|
||||||
|
@ -44,8 +44,7 @@ class BattleProcessor : boost::noncopyable
|
|||||||
void checkBattleStateChanges();
|
void checkBattleStateChanges();
|
||||||
void setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance *heroes[2], bool creatureBank, const CGTownInstance *town);
|
void setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance *heroes[2], bool creatureBank, const CGTownInstance *town);
|
||||||
|
|
||||||
bool makeBattleAction(BattleAction &ba);
|
bool makeBattleAction(const BattleAction &ba);
|
||||||
bool makeCustomAction(BattleAction &ba);
|
|
||||||
|
|
||||||
void setBattleResult(EBattleResult resultType, int victoriusSide);
|
void setBattleResult(EBattleResult resultType, int victoriusSide);
|
||||||
public:
|
public:
|
||||||
@ -60,9 +59,7 @@ public:
|
|||||||
void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank = false); //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle
|
void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank = false); //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle
|
||||||
|
|
||||||
bool makeBattleAction(PlayerColor player, BattleAction &ba);
|
bool makeBattleAction(PlayerColor player, BattleAction &ba);
|
||||||
bool makeCustomAction(PlayerColor player, BattleAction &ba);
|
|
||||||
|
|
||||||
void endBattle(int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2); //ends battle
|
|
||||||
void endBattleConfirm(const BattleInfo * battleInfo);
|
void endBattleConfirm(const BattleInfo * battleInfo);
|
||||||
void battleAfterLevelUp(const BattleResult &result);
|
void battleAfterLevelUp(const BattleResult &result);
|
||||||
|
|
||||||
|
@ -39,8 +39,8 @@
|
|||||||
#include "../../lib/spells/Problem.h"
|
#include "../../lib/spells/Problem.h"
|
||||||
|
|
||||||
BattleResultProcessor::BattleResultProcessor(BattleProcessor * owner)
|
BattleResultProcessor::BattleResultProcessor(BattleProcessor * owner)
|
||||||
: owner(owner)
|
// : owner(owner)
|
||||||
, gameHandler(nullptr)
|
: gameHandler(nullptr)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -550,5 +550,4 @@ void BattleResultProcessor::setBattleResult(EBattleResult resultType, int victor
|
|||||||
battleResult->result = resultType;
|
battleResult->result = resultType;
|
||||||
battleResult->winner = victoriusSide; //surrendering side loses
|
battleResult->winner = victoriusSide; //surrendering side loses
|
||||||
gameHandler->gameState()->curB->calculateCasualties(battleResult->casualties);
|
gameHandler->gameState()->curB->calculateCasualties(battleResult->casualties);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -61,14 +61,14 @@ struct FinishingBattleHelper
|
|||||||
|
|
||||||
class BattleResultProcessor : boost::noncopyable
|
class BattleResultProcessor : boost::noncopyable
|
||||||
{
|
{
|
||||||
BattleProcessor * owner;
|
// BattleProcessor * owner;
|
||||||
CGameHandler * gameHandler;
|
CGameHandler * gameHandler;
|
||||||
|
|
||||||
std::unique_ptr<BattleResult> battleResult;
|
std::unique_ptr<BattleResult> battleResult;
|
||||||
std::unique_ptr<FinishingBattleHelper> finishingBattle;
|
std::unique_ptr<FinishingBattleHelper> finishingBattle;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
BattleResultProcessor(BattleProcessor * owner);
|
explicit BattleResultProcessor(BattleProcessor * owner);
|
||||||
void setGameHandler(CGameHandler * newGameHandler);
|
void setGameHandler(CGameHandler * newGameHandler);
|
||||||
|
|
||||||
void setBattleResult(EBattleResult resultType, int victoriusSide);
|
void setBattleResult(EBattleResult resultType, int victoriusSide);
|
||||||
|
@ -43,7 +43,7 @@ CBattleQuery::CBattleQuery(CGameHandler * owner):
|
|||||||
bool CBattleQuery::blocksPack(const CPack * pack) const
|
bool CBattleQuery::blocksPack(const CPack * pack) const
|
||||||
{
|
{
|
||||||
const char * name = typeid(*pack).name();
|
const char * name = typeid(*pack).name();
|
||||||
return strcmp(name, typeid(MakeAction).name()) && strcmp(name, typeid(MakeCustomAction).name());
|
return strcmp(name, typeid(MakeAction).name()) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CBattleQuery::onRemoval(PlayerColor color)
|
void CBattleQuery::onRemoval(PlayerColor color)
|
||||||
|
Reference in New Issue
Block a user