mirror of
https://github.com/vcmi/vcmi.git
synced 2025-02-07 13:08:09 +02:00
Start of stabilization - battles now start correctly
This commit is contained in:
parent
44832f3797
commit
6297140bf5
@ -292,7 +292,7 @@ void CBattleAI::activeStack( const CStack * stack )
|
||||
//spellcast may finish battle or kill active stack
|
||||
//send special preudo-action
|
||||
BattleAction cancel;
|
||||
cancel.actionType = EActionType::CANCEL;
|
||||
cancel.actionType = EActionType::NO_ACTION;
|
||||
cb->battleMakeUnitAction(cancel);
|
||||
return;
|
||||
}
|
||||
|
@ -206,7 +206,7 @@ bool CCallback::buildBuilding(const CGTownInstance *town, BuildingID buildingID)
|
||||
void CBattleCallback::battleMakeSpellAction(const BattleAction & action)
|
||||
{
|
||||
assert(action.actionType == EActionType::HERO_SPELL);
|
||||
MakeCustomAction mca(action);
|
||||
MakeAction mca(action);
|
||||
sendRequest(&mca);
|
||||
}
|
||||
|
||||
|
@ -401,7 +401,7 @@ void BattleStacksController::stackRemoved(uint32_t stackID)
|
||||
{
|
||||
BattleAction action;
|
||||
action.side = owner.defendingHeroInstance ? (owner.curInt->playerID == owner.defendingHeroInstance->tempOwner) : false;
|
||||
action.actionType = EActionType::CANCEL;
|
||||
action.actionType = EActionType::NO_ACTION;
|
||||
action.stackNumber = getActiveStack()->unitId();
|
||||
|
||||
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 =
|
||||
{
|
||||
{EActionType::END_TACTIC_PHASE, "End tactic phase"},
|
||||
{EActionType::INVALID, "Invalid"},
|
||||
{EActionType::NO_ACTION, "No action"},
|
||||
{EActionType::HERO_SPELL, "Hero spell"},
|
||||
{EActionType::WALK, "Walk"},
|
||||
|
@ -1000,18 +1000,19 @@ namespace Date
|
||||
|
||||
enum class EActionType : int32_t
|
||||
{
|
||||
CANCEL = -3,
|
||||
END_TACTIC_PHASE = -2,
|
||||
INVALID = -1,
|
||||
NO_ACTION = 0,
|
||||
HERO_SPELL,
|
||||
WALK,
|
||||
DEFEND,
|
||||
NO_ACTION,
|
||||
|
||||
END_TACTIC_PHASE,
|
||||
RETREAT,
|
||||
SURRENDER,
|
||||
|
||||
HERO_SPELL,
|
||||
|
||||
WALK,
|
||||
WAIT,
|
||||
DEFEND,
|
||||
WALK_AND_ATTACK,
|
||||
SHOOT,
|
||||
WAIT,
|
||||
CATAPULT,
|
||||
MONSTER_SPELL,
|
||||
BAD_MORALE,
|
||||
|
@ -134,7 +134,6 @@ public:
|
||||
virtual void visitBuildBoat(BuildBoat & pack) {}
|
||||
virtual void visitQueryReply(QueryReply & pack) {}
|
||||
virtual void visitMakeAction(MakeAction & pack) {}
|
||||
virtual void visitMakeCustomAction(MakeCustomAction & pack) {}
|
||||
virtual void visitDigWithHero(DigWithHero & pack) {}
|
||||
virtual void visitCastAdvSpell(CastAdvSpell & 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
|
||||
{
|
||||
ObjectInstanceID id; //digging hero id
|
||||
|
@ -638,11 +638,6 @@ void MakeAction::visitTyped(ICPackVisitor & visitor)
|
||||
visitor.visitMakeAction(*this);
|
||||
}
|
||||
|
||||
void MakeCustomAction::visitTyped(ICPackVisitor & visitor)
|
||||
{
|
||||
visitor.visitMakeCustomAction(*this);
|
||||
}
|
||||
|
||||
void DigWithHero::visitTyped(ICPackVisitor & visitor)
|
||||
{
|
||||
visitor.visitDigWithHero(*this);
|
||||
|
@ -20,7 +20,7 @@ static const int32_t INVALID_UNIT_ID = -1000;
|
||||
BattleAction::BattleAction():
|
||||
side(-1),
|
||||
stackNumber(-1),
|
||||
actionType(EActionType::INVALID),
|
||||
actionType(EActionType::NO_ACTION),
|
||||
actionSubtype(-1)
|
||||
{
|
||||
}
|
||||
|
@ -352,7 +352,6 @@ void registerTypesServerPacks(Serializer &s)
|
||||
s.template registerType<CPackForServer, BuildBoat>();
|
||||
s.template registerType<CPackForServer, QueryReply>();
|
||||
s.template registerType<CPackForServer, MakeAction>();
|
||||
s.template registerType<CPackForServer, MakeCustomAction>();
|
||||
s.template registerType<CPackForServer, DigWithHero>();
|
||||
s.template registerType<CPackForServer, CastAdvSpell>();
|
||||
s.template registerType<CPackForServer, CastleTeleportHero>();
|
||||
|
@ -288,14 +288,6 @@ void ApplyGhNetPackVisitor::visitMakeAction(MakeAction & pack)
|
||||
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)
|
||||
{
|
||||
gh.throwOnWrongOwner(&pack, pack.id);
|
||||
|
@ -55,8 +55,7 @@ public:
|
||||
virtual void visitBuildBoat(BuildBoat & pack) override;
|
||||
virtual void visitQueryReply(QueryReply & pack) override;
|
||||
virtual void visitMakeAction(MakeAction & pack) override;
|
||||
virtual void visitMakeCustomAction(MakeCustomAction & pack) override;
|
||||
virtual void visitDigWithHero(DigWithHero & pack) override;
|
||||
virtual void visitCastAdvSpell(CastAdvSpell & pack) override;
|
||||
virtual void visitPlayerMessage(PlayerMessage & pack) override;
|
||||
};
|
||||
};
|
||||
|
@ -51,484 +51,530 @@ void BattleActionProcessor::setGameHandler(CGameHandler * 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);
|
||||
|
||||
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());
|
||||
|
||||
logGlobal->trace("Making action: %s", ba.toString());
|
||||
|
||||
switch(ba.actionType)
|
||||
if(target.size() < 1)
|
||||
{
|
||||
case EActionType::WALK: //walk
|
||||
case EActionType::DEFEND: //defend
|
||||
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:
|
||||
gameHandler->complain("Destination required for move action.");
|
||||
return false;
|
||||
}
|
||||
|
||||
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!");
|
||||
return false;
|
||||
}
|
||||
if (!stack->alive())
|
||||
{
|
||||
gameHandler->complain("This stack is dead: " + stack->nodeName());
|
||||
return false;
|
||||
makeAttack(destinationStack, stack, 0, stack->getPosition(), true, false, true);
|
||||
}
|
||||
|
||||
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())
|
||||
{
|
||||
gameHandler->complain("This is not a stack of side that has tactics!");
|
||||
return false;
|
||||
}
|
||||
makeAttack(stack, destinationStack, (i ? 0 : distance), destinationTile, i==0, false, false);//no distance travelled on second attack
|
||||
}
|
||||
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!");
|
||||
return false;
|
||||
makeAttack(destinationStack, stack, 0, stack->getPosition(), true, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
static EndAction end_action;
|
||||
auto wrapAction = [this](BattleAction &ba)
|
||||
//return
|
||||
if(stack->hasBonusOfType(BonusType::RETURN_AFTER_STRIKE)
|
||||
&& target.size() == 3
|
||||
&& startingPos != stack->getPosition()
|
||||
&& startingPos == target.at(2).hexValue
|
||||
&& stack->alive())
|
||||
{
|
||||
StartAction startAction(ba);
|
||||
gameHandler->sendAndApply(&startAction);
|
||||
moveStack(ba.stackNumber, startingPos);
|
||||
//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)
|
||||
{
|
||||
case EActionType::END_TACTIC_PHASE: //wait
|
||||
case EActionType::BAD_MORALE:
|
||||
case EActionType::NO_ACTION:
|
||||
{
|
||||
auto wrapper = wrapAction(ba);
|
||||
break;
|
||||
}
|
||||
case EActionType::WALK:
|
||||
{
|
||||
auto wrapper = wrapAction(ba);
|
||||
if(target.size() < 1)
|
||||
{
|
||||
gameHandler->complain("Destination required for move action.");
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
int walkedTiles = moveStack(ba.stackNumber, target.at(0).hexValue); //move
|
||||
if (!walkedTiles)
|
||||
gameHandler->complain("Stack failed movement!");
|
||||
break;
|
||||
}
|
||||
case EActionType::DEFEND:
|
||||
{
|
||||
//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::NO_ACTION:
|
||||
return doEmptyAction(ba);
|
||||
case EActionType::END_TACTIC_PHASE:
|
||||
return doEndTacticsAction(ba);
|
||||
case EActionType::RETREAT:
|
||||
return doRetreatAction(ba);
|
||||
case EActionType::SURRENDER:
|
||||
return doSurrenderAction(ba);
|
||||
case EActionType::HERO_SPELL:
|
||||
return doHeroSpellAction(ba);
|
||||
case EActionType::WALK:
|
||||
return doWalkAction(ba);
|
||||
case EActionType::WAIT:
|
||||
return doWaitAction(ba);
|
||||
case EActionType::DEFEND:
|
||||
return doDefendAction(ba);
|
||||
case EActionType::WALK_AND_ATTACK:
|
||||
return doAttackAction(ba);
|
||||
case EActionType::SHOOT:
|
||||
return doShootAction(ba);
|
||||
case EActionType::CATAPULT:
|
||||
return doCatapultAction(ba);
|
||||
case EActionType::MONSTER_SPELL:
|
||||
{
|
||||
auto wrapper = wrapAction(ba);
|
||||
|
||||
const CStack * stack = gameHandler->gameState()->curB->battleGetStackByID(ba.stackNumber);
|
||||
SpellID spellID = SpellID(ba.actionSubtype);
|
||||
|
||||
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;
|
||||
}
|
||||
return doUnitSpellAction(ba);
|
||||
case EActionType::BAD_MORALE:
|
||||
return doBadMoraleAction(ba);
|
||||
case EActionType::STACK_HEAL:
|
||||
return doHealAction(ba);
|
||||
}
|
||||
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)
|
||||
gameHandler->handleObstacleTriggersForUnit(*gameHandler->spellEnv, *stack);
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
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;
|
||||
return result;
|
||||
}
|
||||
|
||||
int BattleActionProcessor::moveStack(int stack, BattleHex dest)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
const CStack *curStack = gameHandler->battleGetStackByID(stack),
|
||||
*stackAtEnd = gameHandler->gameState()->curB->battleGetStackByPos(dest);
|
||||
const CStack *curStack = gameHandler->battleGetStackByID(stack);
|
||||
const CStack *stackAtEnd = gameHandler->gameState()->curB->battleGetStackByPos(dest);
|
||||
|
||||
assert(curStack);
|
||||
assert(dest < GameConstants::BFIELD_SIZE);
|
||||
|
@ -13,7 +13,6 @@ VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
struct BattleLogMessage;
|
||||
struct BattleAttack;
|
||||
class BattleProcessor;
|
||||
class BattleAction;
|
||||
struct BattleHex;
|
||||
class CStack;
|
||||
@ -27,6 +26,7 @@ class CUnitState;
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
class CGameHandler;
|
||||
class BattleProcessor;
|
||||
|
||||
class BattleActionProcessor : boost::noncopyable
|
||||
{
|
||||
@ -48,11 +48,30 @@ class BattleActionProcessor : boost::noncopyable
|
||||
void sendGenericKilledLog(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:
|
||||
BattleActionProcessor(BattleProcessor * owner);
|
||||
explicit BattleActionProcessor(BattleProcessor * owner);
|
||||
void setGameHandler(CGameHandler * newGameHandler);
|
||||
|
||||
bool makeBattleAction(BattleAction &ba);
|
||||
bool makeCustomAction(BattleAction &ba);
|
||||
bool makeBattleAction(const BattleAction &ba);
|
||||
|
||||
};
|
||||
|
||||
|
@ -290,21 +290,35 @@ void BattleFlowProcessor::activateNextStack()
|
||||
//TODO: activate next round if next == nullptr
|
||||
const auto & curB = *gameHandler->gameState()->curB;
|
||||
|
||||
const CStack * next = getNextStack();
|
||||
|
||||
if (!next)
|
||||
return;
|
||||
|
||||
BattleUnitsChanged removeGhosts;
|
||||
|
||||
for(auto stack : curB.stacks)
|
||||
// Find next stack that requires manual control
|
||||
for (;;)
|
||||
{
|
||||
if(stack->ghostPending)
|
||||
removeGhosts.changedStacks.emplace_back(stack->unitId(), UnitChanges::EOperation::REMOVE);
|
||||
}
|
||||
const CStack * next = getNextStack();
|
||||
|
||||
if(!removeGhosts.changedStacks.empty())
|
||||
gameHandler->sendAndApply(&removeGhosts);
|
||||
if (!next)
|
||||
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)
|
||||
@ -423,7 +437,7 @@ bool BattleFlowProcessor::tryMakeAutomaticAction(const CStack * next)
|
||||
return s->unitOwner() == next->unitOwner() && s->canBeHealed();
|
||||
});
|
||||
|
||||
if (!possibleStacks.size())
|
||||
if (possibleStacks.empty())
|
||||
{
|
||||
makeStackDoNothing(next);
|
||||
return true;
|
||||
@ -452,25 +466,11 @@ bool BattleFlowProcessor::tryMakeAutomaticAction(const CStack * next)
|
||||
makeStackDoNothing(next); //end immediately if stack was affected by fear
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
logGlobal->trace("Activating %s", next->nodeName());
|
||||
auto nextId = next->unitId();
|
||||
BattleSetActiveStack sas;
|
||||
sas.stack = nextId;
|
||||
gameHandler->sendAndApply(&sas);
|
||||
return false;
|
||||
}
|
||||
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
|
||||
auto nextStackMorale = next->moraleVal();
|
||||
if( !next->hadMorale
|
||||
@ -491,11 +491,38 @@ void BattleFlowProcessor::onActionMade(const CStack *next)
|
||||
bte.val = 1;
|
||||
bte.additionalInfo = 0;
|
||||
gameHandler->sendAndApply(&bte); //play animation
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (gameHandler->gameLobby()->state != EServerState::SHUTDOWN)
|
||||
owner->endBattle(gameHandler->gameState()->curB->tile, gameHandler->gameState()->curB->battleGetFightingHero(0), gameHandler->gameState()->curB->battleGetFightingHero(1));
|
||||
void BattleFlowProcessor::onActionMade(const BattleAction &ba)
|
||||
{
|
||||
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)
|
||||
|
@ -26,6 +26,7 @@ class BattleFlowProcessor : boost::noncopyable
|
||||
|
||||
const CStack * getNextStack();
|
||||
|
||||
bool rollGoodMorale(const CStack * stack);
|
||||
bool tryMakeAutomaticAction(const CStack * stack);
|
||||
|
||||
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)
|
||||
|
||||
public:
|
||||
BattleFlowProcessor(BattleProcessor * owner);
|
||||
explicit BattleFlowProcessor(BattleProcessor * owner);
|
||||
void setGameHandler(CGameHandler * newGameHandler);
|
||||
|
||||
void onBattleStarted();
|
||||
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 actionsProcessor->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);
|
||||
return makeBattleAction(ba);
|
||||
}
|
||||
|
||||
void BattleProcessor::setBattleResult(EBattleResult resultType, int 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 BattleProcessor::makeCustomAction(BattleAction &ba)
|
||||
{
|
||||
return actionsProcessor->makeCustomAction(ba);
|
||||
}
|
||||
|
||||
void BattleProcessor::endBattle(int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2)
|
||||
{
|
||||
resultProcessor->endBattle(tile, hero1, hero2);
|
||||
bool result = actionsProcessor->makeBattleAction(ba);
|
||||
flowProcessor->onActionMade(ba);
|
||||
return result;
|
||||
}
|
||||
|
||||
void BattleProcessor::endBattleConfirm(const BattleInfo * battleInfo)
|
||||
|
@ -44,8 +44,7 @@ class BattleProcessor : boost::noncopyable
|
||||
void checkBattleStateChanges();
|
||||
void setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance *heroes[2], bool creatureBank, const CGTownInstance *town);
|
||||
|
||||
bool makeBattleAction(BattleAction &ba);
|
||||
bool makeCustomAction(BattleAction &ba);
|
||||
bool makeBattleAction(const BattleAction &ba);
|
||||
|
||||
void setBattleResult(EBattleResult resultType, int victoriusSide);
|
||||
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
|
||||
|
||||
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 battleAfterLevelUp(const BattleResult &result);
|
||||
|
||||
|
@ -39,8 +39,8 @@
|
||||
#include "../../lib/spells/Problem.h"
|
||||
|
||||
BattleResultProcessor::BattleResultProcessor(BattleProcessor * owner)
|
||||
: owner(owner)
|
||||
, gameHandler(nullptr)
|
||||
// : owner(owner)
|
||||
: gameHandler(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
@ -550,5 +550,4 @@ void BattleResultProcessor::setBattleResult(EBattleResult resultType, int victor
|
||||
battleResult->result = resultType;
|
||||
battleResult->winner = victoriusSide; //surrendering side loses
|
||||
gameHandler->gameState()->curB->calculateCasualties(battleResult->casualties);
|
||||
|
||||
}
|
||||
|
@ -61,14 +61,14 @@ struct FinishingBattleHelper
|
||||
|
||||
class BattleResultProcessor : boost::noncopyable
|
||||
{
|
||||
BattleProcessor * owner;
|
||||
// BattleProcessor * owner;
|
||||
CGameHandler * gameHandler;
|
||||
|
||||
std::unique_ptr<BattleResult> battleResult;
|
||||
std::unique_ptr<FinishingBattleHelper> finishingBattle;
|
||||
|
||||
public:
|
||||
BattleResultProcessor(BattleProcessor * owner);
|
||||
explicit BattleResultProcessor(BattleProcessor * owner);
|
||||
void setGameHandler(CGameHandler * newGameHandler);
|
||||
|
||||
void setBattleResult(EBattleResult resultType, int victoriusSide);
|
||||
|
@ -43,7 +43,7 @@ CBattleQuery::CBattleQuery(CGameHandler * owner):
|
||||
bool CBattleQuery::blocksPack(const CPack * pack) const
|
||||
{
|
||||
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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user