1
0
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:
Ivan Savenko 2023-08-17 00:51:50 +03:00
parent 44832f3797
commit 6297140bf5
21 changed files with 601 additions and 584 deletions

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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"},

View File

@ -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,

View File

@ -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) {}

View File

@ -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

View File

@ -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);

View File

@ -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)
{
}

View File

@ -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>();

View File

@ -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);

View File

@ -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;
};
};

View File

@ -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(&parameters);
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(&parameters);
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);

View File

@ -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);
};

View File

@ -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)

View File

@ -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);
};

View File

@ -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)

View File

@ -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);

View File

@ -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);
}

View File

@ -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);

View File

@ -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)