1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-11-23 22:37:55 +02:00

Merge pull request #1229 from IvanSavenko/battle_improvements

Battle: Fixing bugs & Implementation of missing features
This commit is contained in:
Ivan Savenko
2023-01-09 22:07:13 +02:00
committed by GitHub
117 changed files with 4284 additions and 3295 deletions

View File

@@ -1012,6 +1012,7 @@ void CGameHandler::makeAttack(const CStack * attacker, const CStack * defender,
BattleAttack bat;
BattleLogMessage blm;
bat.stackAttacking = attacker->unitId();
bat.tile = targetHex;
std::shared_ptr<battle::CUnitState> attackerState = attacker->acquireState();
@@ -1117,6 +1118,9 @@ void CGameHandler::makeAttack(const CStack * attacker, const CStack * defender,
bat.attackerChanges.changedStacks.push_back(info);
}
if (drainedLife > 0)
bat.flags |= BattleAttack::LIFE_DRAIN;
sendAndApply(&bat);
{
@@ -1145,17 +1149,6 @@ void CGameHandler::makeAttack(const CStack * attacker, const CStack * defender,
// drain life effect (as well as log entry) must be applied after the attack
if(drainedLife > 0)
{
BattleAttack bat;
bat.stackAttacking = attacker->unitId();
{
CustomEffectInfo customEffect;
customEffect.sound = soundBase::DRAINLIF;
customEffect.effect = 52;
customEffect.stack = attackerState->unitId();
bat.customEffects.push_back(std::move(customEffect));
}
sendAndApply(&bat);
MetaString text;
attackerState->addText(text, MetaString::GENERAL_TXT, 361);
attackerState->addNameReplacement(text, false);
@@ -1190,28 +1183,30 @@ void CGameHandler::makeAttack(const CStack * attacker, const CStack * defender,
//FIXME: add custom effect on actor
}
BattleStackAttacked bsa;
bsa.stackAttacked = attacker->ID; //invert
bsa.attackerID = uint32_t(-1);
bsa.flags |= BattleStackAttacked::EFFECT;
bsa.effect = 11;
bsa.damageAmount = totalDamage;
attacker->prepareAttacked(bsa, getRandomGenerator());
StacksInjured pack;
pack.stacks.push_back(bsa);
sendAndApply(&pack);
// TODO: this is already implemented in Damage::describeEffect()
if (totalDamage > 0)
{
MetaString text;
text.addTxt(MetaString::GENERAL_TXT, 376);
text.addReplacement(MetaString::SPELL_NAME, SpellID::FIRE_SHIELD);
text.addReplacement(totalDamage);
blm.lines.push_back(std::move(text));
BattleStackAttacked bsa;
bsa.flags |= BattleStackAttacked::FIRE_SHIELD;
bsa.stackAttacked = attacker->ID; //invert
bsa.attackerID = defender->ID;
bsa.damageAmount = totalDamage;
attacker->prepareAttacked(bsa, getRandomGenerator());
StacksInjured pack;
pack.stacks.push_back(bsa);
sendAndApply(&pack);
// TODO: this is already implemented in Damage::describeEffect()
{
MetaString text;
text.addTxt(MetaString::GENERAL_TXT, 376);
text.addReplacement(MetaString::SPELL_NAME, SpellID::FIRE_SHIELD);
text.addReplacement(totalDamage);
blm.lines.push_back(std::move(text));
}
addGenericKilledLog(blm, attacker, bsa.killedAmount, false);
}
addGenericKilledLog(blm, attacker, bsa.killedAmount, false);
}
sendAndApply(&blm);
@@ -1224,6 +1219,7 @@ int64_t CGameHandler::applyBattleEffects(BattleAttack & bat, std::shared_ptr<bat
BattleStackAttacked bsa;
if(secondary)
bsa.flags |= BattleStackAttacked::SECONDARY; //all other targets do not suffer from spells & spell-like abilities
bsa.attackerID = attackerState->unitId();
bsa.stackAttacked = def->unitId();
{
@@ -1277,8 +1273,12 @@ int64_t CGameHandler::applyBattleEffects(BattleAttack & bat, std::shared_ptr<bat
bat.bsa.push_back(bsa); //add this stack to the list of victims after drain life has been calculated
//fire shield handling
if(!bat.shot() && !def->isClone() &&
def->hasBonusOfType(Bonus::FIRE_SHIELD) && !attackerState->hasBonusOfType(Bonus::FIRE_IMMUNITY))
if(!bat.shot() &&
!def->isClone() &&
def->hasBonusOfType(Bonus::FIRE_SHIELD) &&
!attackerState->hasBonusOfType(Bonus::FIRE_IMMUNITY) &&
CStack::isMeleeAttackPossible(attackerState.get(), def) // attacked needs to be adjacent to defender for fire shield to trigger (e.g. Dragon Breath attack)
)
{
//TODO: use damage with bonus but without penalties
auto fireShieldDamage = (std::min<int64_t>(def->getAvailableHealth(), bsa.damageAmount) * def->valOfBonuses(Bonus::FIRE_SHIELD)) / 100;
@@ -4478,7 +4478,6 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
case EActionType::SHOOT: //shoot
case EActionType::CATAPULT: //catapult
case EActionType::STACK_HEAL: //healing with First Aid Tent
case EActionType::DAEMON_SUMMONING:
case EActionType::MONSTER_SPELL:
if (!stack)
@@ -4648,12 +4647,12 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
logGlobal->trace("%s will attack %s", stack->nodeName(), destinationStack->nodeName());
if(stack->getPosition() != attackPos //we wasn't able to reach destination tile
&& !(stack->doubleWide() && (stack->getPosition() == attackPos.cloneInDirection(stack->destShiftDir(), false))) //nor occupy specified hex
if(stack->getPosition() != attackPos
&& !(stack->doubleWide() && (stack->getPosition() == attackPos.cloneInDirection(stack->destShiftDir(), false)))
)
{
complain("We cannot move this stack to its destination " + stack->getCreature()->namePl);
ok = 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;
}
@@ -4689,8 +4688,8 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
makeAttack(destinationStack, stack, 0, stack->getPosition(), true, false, true);
}
//move can cause death, eg. by walking into the moat, first strike can cause death as well
if(stack->alive() && destinationStack->alive())
//move can cause death, eg. by walking into the moat, first strike can cause death or paralysis/petrification
if(stack->alive() && !stack->hasBonusOfType(Bonus::NOT_ACTIVE) && destinationStack->alive())
{
makeAttack(stack, destinationStack, (i ? 0 : distance), destinationTile, i==0, false, false);//no distance travelled on second attack
}
@@ -4923,13 +4922,13 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
switch(attackedPart)
{
case EWallPart::KEEP:
posRemove = -2;
posRemove = BattleHex::CASTLE_CENTRAL_TOWER;
break;
case EWallPart::BOTTOM_TOWER:
posRemove = -3;
posRemove = BattleHex::CASTLE_BOTTOM_TOWER;
break;
case EWallPart::UPPER_TOWER:
posRemove = -4;
posRemove = BattleHex::CASTLE_UPPER_TOWER;
break;
}
@@ -5012,58 +5011,6 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
}
break;
}
case EActionType::DAEMON_SUMMONING:
//TODO: From Strategija:
//Summon Demon is a level 2 spell.
{
if(target.size() < 1)
{
complain("Destination required for summon action.");
ok = false;
break;
}
const CStack * summoner = gs->curB->battleGetStackByID(ba.stackNumber);
const CStack * destStack = gs->curB->battleGetStackByPos(target.at(0).hexValue, false);
CreatureID summonedType(summoner->getBonusLocalFirst(Selector::type()(Bonus::DAEMON_SUMMONING))->subtype);//in case summoner can summon more than one type of monsters... scream!
ui64 risedHp = summoner->getCount() * summoner->valOfBonuses(Bonus::DAEMON_SUMMONING, summonedType.toEnum());
ui64 targetHealth = destStack->getCreature()->MaxHealth() * destStack->baseAmount;
ui64 canRiseHp = std::min(targetHealth, risedHp);
ui32 canRiseAmount = static_cast<ui32>(canRiseHp / summonedType.toCreature()->MaxHealth());
battle::UnitInfo info;
info.id = gs->curB->battleNextUnitId();
info.count = std::min(canRiseAmount, destStack->baseAmount);
info.type = summonedType;
info.side = summoner->side;
info.position = gs->curB->getAvaliableHex(summonedType, summoner->side, destStack->getPosition());
info.summoned = false;
BattleUnitsChanged addUnits;
addUnits.changedStacks.emplace_back(info.id, UnitChanges::EOperation::ADD);
info.save(addUnits.changedStacks.back().data);
if(info.count > 0) //there's rare possibility single creature cannot rise desired type
{
auto wrapper = wrapAction(ba);
BattleUnitsChanged removeUnits;
removeUnits.changedStacks.emplace_back(destStack->unitId(), UnitChanges::EOperation::REMOVE);
sendAndApply(&removeUnits);
sendAndApply(&addUnits);
BattleSetStackProperty ssp;
ssp.stackID = ba.stackNumber;
ssp.which = BattleSetStackProperty::CASTS; //reduce number of casts
ssp.val = -1;
ssp.absolute = false;
sendAndApply(&ssp);
}
break;
}
case EActionType::MONSTER_SPELL:
{
auto wrapper = wrapAction(ba);
@@ -5095,7 +5042,7 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
break;
}
}
if(ba.actionType == EActionType::DAEMON_SUMMONING || ba.actionType == EActionType::WAIT || ba.actionType == EActionType::DEFEND
if(ba.actionType == EActionType::WAIT || ba.actionType == EActionType::DEFEND
|| ba.actionType == EActionType::SHOOT || ba.actionType == EActionType::MONSTER_SPELL)
handleDamageFromObstacle(stack);
if(ba.stackNumber == gs->curB->activeStack || battleResult.get()) //active stack has moved or battle has finished
@@ -5469,33 +5416,29 @@ bool CGameHandler::handleDamageFromObstacle(const CStack * curStack, bool stackI
if(!sp)
COMPLAIN_RET("Invalid obstacle instance");
// For the hidden spell created obstacles, e.g. QuickSand, it should be revealed after taking damage
ObstacleChanges changeInfo;
changeInfo.id = spellObstacle->uniqueID;
if (oneTimeObstacle)
changeInfo.operation = ObstacleChanges::EOperation::ACTIVATE_AND_REMOVE;
else
changeInfo.operation = ObstacleChanges::EOperation::ACTIVATE_AND_UPDATE;
SpellCreatedObstacle changedObstacle;
changedObstacle.uniqueID = spellObstacle->uniqueID;
changedObstacle.revealed = true;
changeInfo.data.clear();
JsonSerializer ser(nullptr, changeInfo.data);
ser.serializeStruct("obstacle", changedObstacle);
BattleObstaclesChanged bocp;
bocp.changes.emplace_back(changeInfo);
sendAndApply(&bocp);
spells::BattleCast battleCast(gs->curB, &caster, spells::Mode::HERO, sp);
battleCast.applyEffects(spellEnv, spells::Target(1, spells::Destination(curStack)), true);
if(oneTimeObstacle)
{
removeObstacle(*obstacle);
}
else
{
// For the hidden spell created obstacles, e.g. QuickSand, it should be revealed after taking damage
ObstacleChanges changeInfo;
changeInfo.id = spellObstacle->uniqueID;
changeInfo.operation = ObstacleChanges::EOperation::UPDATE;
SpellCreatedObstacle changedObstacle;
changedObstacle.uniqueID = spellObstacle->uniqueID;
changedObstacle.revealed = true;
changeInfo.data.clear();
JsonSerializer ser(nullptr, changeInfo.data);
ser.serializeStruct("obstacle", changedObstacle);
BattleObstaclesChanged bocp;
bocp.changes.emplace_back(changeInfo);
sendAndApply(&bocp);
}
}
}
}
else if(obstacle->obstacleType == CObstacleInstance::MOAT)
@@ -7237,7 +7180,7 @@ void CGameHandler::handleCheatCode(std::string & cheat, PlayerColor player, cons
void CGameHandler::removeObstacle(const CObstacleInstance & obstacle)
{
BattleObstaclesChanged obsRem;
obsRem.changes.emplace_back(obstacle.uniqueID, BattleChanges::EOperation::REMOVE);
obsRem.changes.emplace_back(obstacle.uniqueID, ObstacleChanges::EOperation::REMOVE);
sendAndApply(&obsRem);
}