1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-08-13 19:54:17 +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 "../CStack.h"
#include "BattleInfo.h" #include "BattleInfo.h"
#include "CObstacleInstance.h"
#include "DamageCalculator.h" #include "DamageCalculator.h"
#include "PossiblePlayerBattleAction.h" #include "PossiblePlayerBattleAction.h"
#include "../NetPacks.h" #include "../NetPacks.h"
#include "../spells/ObstacleCasterProxy.h"
#include "../spells/ISpellMechanics.h"
#include "../spells/Problem.h"
#include "../spells/CSpellHandler.h" #include "../spells/CSpellHandler.h"
#include "../mapObjects/CGTownInstance.h" #include "../mapObjects/CGTownInstance.h"
#include "../BattleFieldHandler.h" #include "../BattleFieldHandler.h"
@@ -826,6 +830,66 @@ std::vector<std::shared_ptr<const CObstacleInstance>> CBattleInfoCallback::getAl
return affectedObstacles; 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 CBattleInfoCallback::getAccesibility() const
{ {
AccessibilityInfo ret; AccessibilityInfo ret;

View File

@@ -20,6 +20,7 @@ VCMI_LIB_NAMESPACE_BEGIN
class CGHeroInstance; class CGHeroInstance;
class CStack; class CStack;
class ISpellCaster; class ISpellCaster;
class SpellCastEnvironment;
class CSpell; class CSpell;
struct CObstacleInstance; struct CObstacleInstance;
class IBonusBearer; 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>> 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; 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; 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 * battle = nullptr; //battle to which the player is engaged, nullptr if none or not applicable
const IBattleInfo * getBattle() const;
protected: protected:
boost::optional<PlayerColor> player; // not set gives access to all information, otherwise callback provides only information "visible" for player 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(boost::optional<PlayerColor> Player);
CCallbackBase() = default; CCallbackBase() = default;
const IBattleInfo * getBattle() const;
void setBattle(const IBattleInfo * B); void setBattle(const IBattleInfo * B);
bool duringBattle() const; bool duringBattle() const;

View File

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

View File

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

View File

@@ -1527,7 +1527,7 @@ int CGameHandler::moveStack(int stack, BattleHex dest)
{ {
if(stackIsMoving && start != curStack->getPosition()) if(stackIsMoving && start != curStack->getPosition())
{ {
stackIsMoving = handleDamageFromObstacle(curStack, passed); stackIsMoving = handleObstacleTriggersForUnit(*spellEnv, *curStack, passed);
passed.insert(curStack->getPosition()); passed.insert(curStack->getPosition());
if(curStack->doubleWide()) if(curStack->doubleWide())
passed.insert(curStack->occupiedHex()); passed.insert(curStack->occupiedHex());
@@ -1565,7 +1565,7 @@ int CGameHandler::moveStack(int stack, BattleHex dest)
passed.clear(); //Just empty passed, obstacles will handled automatically passed.clear(); //Just empty passed, obstacles will handled automatically
} }
//handling obstacle on the final field (separate, because it affects both flying and walking stacks) //handling obstacle on the final field (separate, because it affects both flying and walking stacks)
handleDamageFromObstacle(curStack, passed); handleObstacleTriggersForUnit(*spellEnv, *curStack, passed);
return ret; return ret;
} }
@@ -4933,7 +4933,7 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
} }
if(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) || 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 if(ba.stackNumber == gs->curB->activeStack || battleResult.get()) //active stack has moved or battle has finished
battleMadeAction.setn(true); battleMadeAction.setn(true);
return ok; 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() void CGameHandler::handleTimeEvents()
{ {
gs->map->events.sort(evntCmp); gs->map->events.sort(evntCmp);

View File

@@ -236,7 +236,6 @@ public:
bool makeCustomAction(BattleAction &ba); bool makeCustomAction(BattleAction &ba);
void stackEnchantedTrigger(const CStack * stack); void stackEnchantedTrigger(const CStack * stack);
void stackTurnTrigger(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); void removeObstacle(const CObstacleInstance &obstacle);
bool queryReply( QueryID qid, const JsonNode & answer, PlayerColor player ); bool queryReply( QueryID qid, const JsonNode & answer, PlayerColor player );