1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-24 08:32:34 +02:00

Teleport: can trigger obstacles now

This commit is contained in:
Konstantin P 2023-04-12 16:20:40 +03:00
parent 384ee99834
commit f11fa8f0c8
7 changed files with 79 additions and 66 deletions

View File

@ -14,9 +14,13 @@
#include "../CStack.h"
#include "BattleInfo.h"
#include "CObstacleInstance.h"
#include "DamageCalculator.h"
#include "PossiblePlayerBattleAction.h"
#include "../NetPacks.h"
#include "../spells/ObstacleCasterProxy.h"
#include "../spells/ISpellMechanics.h"
#include "../spells/Problem.h"
#include "../spells/CSpellHandler.h"
#include "../mapObjects/CGTownInstance.h"
#include "../BattleFieldHandler.h"
@ -826,6 +830,66 @@ std::vector<std::shared_ptr<const CObstacleInstance>> CBattleInfoCallback::getAl
return affectedObstacles;
}
bool CBattleInfoCallback::handleObstacleTriggersForUnit(SpellCastEnvironment & spellEnv, const battle::Unit & unit, const std::set<BattleHex> & passed) const
{
if(!unit.alive())
return false;
bool movementStopped = false;
for(auto & obstacle : getAllAffectedObstaclesByStack(&unit, passed))
{
//helper info
const SpellCreatedObstacle * spellObstacle = dynamic_cast<const SpellCreatedObstacle *>(obstacle.get());
if(spellObstacle)
{
auto revealObstacles = [&](const SpellCreatedObstacle & spellObstacle) -> void
{
// For the hidden spell created obstacles, e.g. QuickSand, it should be revealed after taking damage
auto operation = ObstacleChanges::EOperation::UPDATE;
if (spellObstacle.removeOnTrigger)
operation = ObstacleChanges::EOperation::REMOVE;
SpellCreatedObstacle changedObstacle;
changedObstacle.uniqueID = spellObstacle.uniqueID;
changedObstacle.revealed = true;
BattleObstaclesChanged bocp;
bocp.changes.emplace_back(spellObstacle.uniqueID, operation);
changedObstacle.toInfo(bocp.changes.back(), operation);
spellEnv.apply(&bocp);
};
const auto side = unit.unitSide();
auto shouldReveal = !spellObstacle->hidden || !battleIsObstacleVisibleForSide(*obstacle, (BattlePerspective::BattlePerspective)side);
const auto * hero = battleGetFightingHero(spellObstacle->casterSide);
auto caster = spells::ObstacleCasterProxy(getBattle()->getSidePlayer(spellObstacle->casterSide), hero, *spellObstacle);
const auto * sp = obstacle->getTrigger().toSpell();
if(obstacle->triggersEffects() && sp)
{
auto cast = spells::BattleCast(this, &caster, spells::Mode::PASSIVE, sp);
spells::detail::ProblemImpl ignored;
auto target = spells::Target(1, spells::Destination(&unit));
if(sp->battleMechanics(&cast)->canBeCastAt(target, ignored)) // Obstacles should not be revealed by immune creatures
{
if(shouldReveal) { //hidden obstacle triggers effects after revealed
revealObstacles(*spellObstacle);
cast.cast(&spellEnv, target);
}
}
}
else if(shouldReveal)
revealObstacles(*spellObstacle);
}
if(!unit.alive())
return false;
if(obstacle->stopsMovement())
movementStopped = true;
}
return unit.alive() && !movementStopped;
}
AccessibilityInfo CBattleInfoCallback::getAccesibility() const
{
AccessibilityInfo ret;

View File

@ -20,6 +20,7 @@ VCMI_LIB_NAMESPACE_BEGIN
class CGHeroInstance;
class CStack;
class ISpellCaster;
class SpellCastEnvironment;
class CSpell;
struct CObstacleInstance;
class IBonusBearer;
@ -61,6 +62,8 @@ public:
std::vector<std::shared_ptr<const CObstacleInstance>> battleGetAllObstaclesOnPos(BattleHex tile, bool onlyBlocking = true) const override;
std::vector<std::shared_ptr<const CObstacleInstance>> getAllAffectedObstaclesByStack(const battle::Unit * unit, const std::set<BattleHex> & passed) const override;
//Handle obstacle damage here, requires SpellCastEnvironment
bool handleObstacleTriggersForUnit(SpellCastEnvironment & spellEnv, const battle::Unit & unit, const std::set<BattleHex> & passed = {}) const;
const CStack * battleGetStackByPos(BattleHex pos, bool onlyAlive = true) const;

View File

@ -25,14 +25,13 @@ class DLL_LINKAGE CCallbackBase
{
const IBattleInfo * battle = nullptr; //battle to which the player is engaged, nullptr if none or not applicable
const IBattleInfo * getBattle() const;
protected:
boost::optional<PlayerColor> player; // not set gives access to all information, otherwise callback provides only information "visible" for player
CCallbackBase(boost::optional<PlayerColor> Player);
CCallbackBase() = default;
const IBattleInfo * getBattle() const;
void setBattle(const IBattleInfo * B);
bool duringBattle() const;

View File

@ -83,10 +83,17 @@ void Teleport::apply(ServerCallback * server, const Mechanics * m, const EffectT
pack.tilesToMove = tiles;
pack.teleporting = true;
server->apply(&pack);
if(triggerObstacles)
{
auto spellEnv = dynamic_cast<SpellCastEnvironment*>(server);
m->battle()->handleObstacleTriggersForUnit(*spellEnv, *targetUnit);
}
}
void Teleport::serializeJsonUnitEffect(JsonSerializeFormat & handler)
{
handler.serializeBool("triggerObstacles", triggerObstacles);
handler.serializeBool("isWallPassable", isWallPassable);
handler.serializeBool("isMoatPassable", isMoatPassable);
}

View File

@ -36,6 +36,7 @@ protected:
void serializeJsonUnitEffect(JsonSerializeFormat & handler) override;
private:
bool triggerObstacles;
bool isWallPassable;
bool isMoatPassable;
};

View File

@ -1527,7 +1527,7 @@ int CGameHandler::moveStack(int stack, BattleHex dest)
{
if(stackIsMoving && start != curStack->getPosition())
{
stackIsMoving = handleDamageFromObstacle(curStack, passed);
stackIsMoving = handleObstacleTriggersForUnit(*spellEnv, *curStack, passed);
passed.insert(curStack->getPosition());
if(curStack->doubleWide())
passed.insert(curStack->occupiedHex());
@ -1565,7 +1565,7 @@ int CGameHandler::moveStack(int stack, BattleHex dest)
passed.clear(); //Just empty passed, obstacles will handled automatically
}
//handling obstacle on the final field (separate, because it affects both flying and walking stacks)
handleDamageFromObstacle(curStack, passed);
handleObstacleTriggersForUnit(*spellEnv, *curStack, passed);
return ret;
}
@ -4933,7 +4933,7 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
}
if(ba.actionType == EActionType::WAIT || ba.actionType == EActionType::DEFEND
|| ba.actionType == EActionType::SHOOT || ba.actionType == EActionType::MONSTER_SPELL)
handleDamageFromObstacle(stack);
handleObstacleTriggersForUnit(*spellEnv, *stack);
if(ba.stackNumber == gs->curB->activeStack || battleResult.get()) //active stack has moved or battle has finished
battleMadeAction.setn(true);
return ok;
@ -5289,66 +5289,6 @@ void CGameHandler::stackTurnTrigger(const CStack *st)
}
}
bool CGameHandler::handleDamageFromObstacle(const battle::Unit * curStack, const std::set<BattleHex> & passed)
{
if(!curStack->alive())
return false;
bool movementStopped = false;
for(auto & obstacle : getAllAffectedObstaclesByStack(curStack, passed))
{
//helper info
const SpellCreatedObstacle * spellObstacle = dynamic_cast<const SpellCreatedObstacle *>(obstacle.get());
if(spellObstacle)
{
auto revealObstacles = [&](const SpellCreatedObstacle & spellObstacle) -> void
{
// For the hidden spell created obstacles, e.g. QuickSand, it should be revealed after taking damage
auto operation = ObstacleChanges::EOperation::UPDATE;
if (spellObstacle.removeOnTrigger)
operation = ObstacleChanges::EOperation::REMOVE;
SpellCreatedObstacle changedObstacle;
changedObstacle.uniqueID = spellObstacle.uniqueID;
changedObstacle.revealed = true;
BattleObstaclesChanged bocp;
bocp.changes.emplace_back(spellObstacle.uniqueID, operation);
changedObstacle.toInfo(bocp.changes.back(), operation);
sendAndApply(&bocp);
};
const auto side = curStack->unitSide();
auto shouldReveal = !spellObstacle->hidden || !gs->curB->battleIsObstacleVisibleForSide(*obstacle, (BattlePerspective::BattlePerspective)side);
const auto * hero = gs->curB->battleGetFightingHero(spellObstacle->casterSide);
auto caster = spells::ObstacleCasterProxy(gs->curB->getSidePlayer(spellObstacle->casterSide), hero, *spellObstacle);
const auto * sp = obstacle->getTrigger().toSpell();
if(obstacle->triggersEffects() && sp)
{
auto cast = spells::BattleCast(gs->curB, &caster, spells::Mode::PASSIVE, sp);
spells::detail::ProblemImpl ignored;
auto target = spells::Target(1, spells::Destination(curStack));
if(sp->battleMechanics(&cast)->canBeCastAt(target, ignored)) // Obstacles should not be revealed by immune creatures
{
if(shouldReveal) { //hidden obstacle triggers effects after revealed
revealObstacles(*spellObstacle);
cast.cast(spellEnv, target);
}
}
}
else if(shouldReveal)
revealObstacles(*spellObstacle);
}
if(!curStack->alive())
return false;
if(obstacle->stopsMovement())
movementStopped = true;
}
return curStack->alive() && !movementStopped;
}
void CGameHandler::handleTimeEvents()
{
gs->map->events.sort(evntCmp);

View File

@ -236,7 +236,6 @@ public:
bool makeCustomAction(BattleAction &ba);
void stackEnchantedTrigger(const CStack * stack);
void stackTurnTrigger(const CStack *stack);
bool handleDamageFromObstacle(const battle::Unit * curStack, const std::set<BattleHex> & passed = {}); //checks if obstacle is land mine and handles possible consequences
void removeObstacle(const CObstacleInstance &obstacle);
bool queryReply( QueryID qid, const JsonNode & answer, PlayerColor player );