mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-24 22:14:36 +02:00
Merge pull request #1932 from rilian-la-te/proper-teleport
VCMI: teleport redesign
This commit is contained in:
commit
e3ed728193
@ -569,10 +569,10 @@ bool BattleActionsController::actionIsLegal(PossiblePlayerBattleAction action, B
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
case PossiblePlayerBattleAction::ANY_LOCATION:
|
case PossiblePlayerBattleAction::ANY_LOCATION:
|
||||||
return isCastingPossibleHere(action.spell().toSpell(), owner.stacksController->getActiveStack(), targetStack, targetHex);
|
return isCastingPossibleHere(action.spell().toSpell(), targetStack, targetHex);
|
||||||
|
|
||||||
case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
|
case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
|
||||||
return !selectedStack && targetStack && isCastingPossibleHere(action.spell().toSpell(), owner.stacksController->getActiveStack(), targetStack, targetHex);
|
return !selectedStack && targetStack && isCastingPossibleHere(action.spell().toSpell(), targetStack, targetHex);
|
||||||
|
|
||||||
case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
|
case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
|
||||||
if(targetStack && targetStackOwned && targetStack != owner.stacksController->getActiveStack() && targetStack->alive()) //only positive spells for other allied creatures
|
if(targetStack && targetStackOwned && targetStack != owner.stacksController->getActiveStack() && targetStack->alive()) //only positive spells for other allied creatures
|
||||||
@ -583,18 +583,14 @@ bool BattleActionsController::actionIsLegal(PossiblePlayerBattleAction action, B
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
case PossiblePlayerBattleAction::TELEPORT:
|
case PossiblePlayerBattleAction::TELEPORT:
|
||||||
{
|
return selectedStack && isCastingPossibleHere(action.spell().toSpell(), selectedStack, targetHex);
|
||||||
ui8 skill = getCurrentSpellcaster()->getEffectLevel(SpellID(SpellID::TELEPORT).toSpell());
|
|
||||||
return owner.curInt->cb->battleCanTeleportTo(selectedStack, targetHex, skill);
|
|
||||||
}
|
|
||||||
|
|
||||||
case PossiblePlayerBattleAction::SACRIFICE: //choose our living stack to sacrifice
|
case PossiblePlayerBattleAction::SACRIFICE: //choose our living stack to sacrifice
|
||||||
return targetStack && targetStack != selectedStack && targetStackOwned && targetStack->alive();
|
return targetStack && targetStack != selectedStack && targetStackOwned && targetStack->alive();
|
||||||
|
|
||||||
case PossiblePlayerBattleAction::OBSTACLE:
|
case PossiblePlayerBattleAction::OBSTACLE:
|
||||||
case PossiblePlayerBattleAction::FREE_LOCATION:
|
case PossiblePlayerBattleAction::FREE_LOCATION:
|
||||||
return isCastingPossibleHere(action.spell().toSpell(), owner.stacksController->getActiveStack(), targetStack, targetHex);
|
return isCastingPossibleHere(action.spell().toSpell(), targetStack, targetHex);
|
||||||
return isCastingPossibleHere(action.spell().toSpell(), owner.stacksController->getActiveStack(), targetStack, targetHex);
|
|
||||||
|
|
||||||
case PossiblePlayerBattleAction::CATAPULT:
|
case PossiblePlayerBattleAction::CATAPULT:
|
||||||
return owner.siegeController && owner.siegeController->isAttackableByCatapult(targetHex);
|
return owner.siegeController && owner.siegeController->isAttackableByCatapult(targetHex);
|
||||||
@ -904,7 +900,7 @@ spells::Mode BattleActionsController::getCurrentCastMode() const
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BattleActionsController::isCastingPossibleHere(const CSpell * currentSpell, const CStack *casterStack, const CStack *targetStack, BattleHex targetHex)
|
bool BattleActionsController::isCastingPossibleHere(const CSpell * currentSpell, const CStack *targetStack, BattleHex targetHex)
|
||||||
{
|
{
|
||||||
assert(currentSpell);
|
assert(currentSpell);
|
||||||
if (!currentSpell)
|
if (!currentSpell)
|
||||||
@ -915,6 +911,8 @@ bool BattleActionsController::isCastingPossibleHere(const CSpell * currentSpell,
|
|||||||
const spells::Mode mode = heroSpellToCast ? spells::Mode::HERO : spells::Mode::CREATURE_ACTIVE;
|
const spells::Mode mode = heroSpellToCast ? spells::Mode::HERO : spells::Mode::CREATURE_ACTIVE;
|
||||||
|
|
||||||
spells::Target target;
|
spells::Target target;
|
||||||
|
if(targetStack)
|
||||||
|
target.emplace_back(targetStack);
|
||||||
target.emplace_back(targetHex);
|
target.emplace_back(targetHex);
|
||||||
|
|
||||||
spells::BattleCast cast(owner.curInt->cb.get(), caster, mode, currentSpell);
|
spells::BattleCast cast(owner.curInt->cb.get(), caster, mode, currentSpell);
|
||||||
|
@ -53,7 +53,7 @@ class BattleActionsController
|
|||||||
/// stack that has been selected as first target for multi-target spells (Teleport & Sacrifice)
|
/// stack that has been selected as first target for multi-target spells (Teleport & Sacrifice)
|
||||||
const CStack * selectedStack;
|
const CStack * selectedStack;
|
||||||
|
|
||||||
bool isCastingPossibleHere (const CSpell * spell, const CStack *sactive, const CStack *shere, BattleHex myNumber);
|
bool isCastingPossibleHere (const CSpell * spell, const CStack *shere, BattleHex myNumber);
|
||||||
bool canStackMoveHere (const CStack *sactive, BattleHex MyNumber) const; //TODO: move to BattleState / callback
|
bool canStackMoveHere (const CStack *sactive, BattleHex MyNumber) const; //TODO: move to BattleState / callback
|
||||||
std::vector<PossiblePlayerBattleAction> getPossibleActionsForStack (const CStack *stack) const; //called when stack gets its turn
|
std::vector<PossiblePlayerBattleAction> getPossibleActionsForStack (const CStack *stack) const; //called when stack gets its turn
|
||||||
void reorderPossibleActionsPriority(const CStack * stack, MouseHoveredHexContext context);
|
void reorderPossibleActionsPriority(const CStack * stack, MouseHoveredHexContext context);
|
||||||
|
@ -158,10 +158,10 @@ ECreatureAnimType AttackAnimation::findValidGroup( const std::vector<ECreatureAn
|
|||||||
|
|
||||||
const CCreature * AttackAnimation::getCreature() const
|
const CCreature * AttackAnimation::getCreature() const
|
||||||
{
|
{
|
||||||
if (attackingStack->getCreature()->getId() == CreatureID::ARROW_TOWERS)
|
if (attackingStack->unitType()->getId() == CreatureID::ARROW_TOWERS)
|
||||||
return owner.siegeController->getTurretCreature();
|
return owner.siegeController->getTurretCreature();
|
||||||
else
|
else
|
||||||
return attackingStack->getCreature();
|
return attackingStack->unitType();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -179,7 +179,7 @@ HittedAnimation::HittedAnimation(BattleInterface & owner, const CStack * stack)
|
|||||||
: StackActionAnimation(owner, stack)
|
: StackActionAnimation(owner, stack)
|
||||||
{
|
{
|
||||||
setGroup(ECreatureAnimType::HITTED);
|
setGroup(ECreatureAnimType::HITTED);
|
||||||
setSound(battle_sound(stack->getCreature(), wince));
|
setSound(battle_sound(stack->unitType(), wince));
|
||||||
logAnim->debug("Created HittedAnimation for %s", stack->getName());
|
logAnim->debug("Created HittedAnimation for %s", stack->getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,14 +187,14 @@ DefenceAnimation::DefenceAnimation(BattleInterface & owner, const CStack * stack
|
|||||||
: StackActionAnimation(owner, stack)
|
: StackActionAnimation(owner, stack)
|
||||||
{
|
{
|
||||||
setGroup(ECreatureAnimType::DEFENCE);
|
setGroup(ECreatureAnimType::DEFENCE);
|
||||||
setSound(battle_sound(stack->getCreature(), defend));
|
setSound(battle_sound(stack->unitType(), defend));
|
||||||
logAnim->debug("Created DefenceAnimation for %s", stack->getName());
|
logAnim->debug("Created DefenceAnimation for %s", stack->getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
DeathAnimation::DeathAnimation(BattleInterface & owner, const CStack * stack, bool ranged):
|
DeathAnimation::DeathAnimation(BattleInterface & owner, const CStack * stack, bool ranged):
|
||||||
StackActionAnimation(owner, stack)
|
StackActionAnimation(owner, stack)
|
||||||
{
|
{
|
||||||
setSound(battle_sound(stack->getCreature(), killed));
|
setSound(battle_sound(stack->unitType(), killed));
|
||||||
|
|
||||||
if(ranged && myAnim->framesInGroup(ECreatureAnimType::DEATH_RANGED) > 0)
|
if(ranged && myAnim->framesInGroup(ECreatureAnimType::DEATH_RANGED) > 0)
|
||||||
setGroup(ECreatureAnimType::DEATH_RANGED);
|
setGroup(ECreatureAnimType::DEATH_RANGED);
|
||||||
@ -356,13 +356,13 @@ bool MovementAnimation::init()
|
|||||||
|
|
||||||
if (moveSoundHander == -1)
|
if (moveSoundHander == -1)
|
||||||
{
|
{
|
||||||
moveSoundHander = CCS->soundh->playSound(battle_sound(stack->getCreature(), move), -1);
|
moveSoundHander = CCS->soundh->playSound(battle_sound(stack->unitType(), move), -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
Point begPosition = owner.stacksController->getStackPositionAtHex(prevHex, stack);
|
Point begPosition = owner.stacksController->getStackPositionAtHex(prevHex, stack);
|
||||||
Point endPosition = owner.stacksController->getStackPositionAtHex(nextHex, stack);
|
Point endPosition = owner.stacksController->getStackPositionAtHex(nextHex, stack);
|
||||||
|
|
||||||
progressPerSecond = AnimationControls::getMovementDistance(stack->getCreature());
|
progressPerSecond = AnimationControls::getMovementDistance(stack->unitType());
|
||||||
|
|
||||||
begX = begPosition.x;
|
begX = begPosition.x;
|
||||||
begY = begPosition.y;
|
begY = begPosition.y;
|
||||||
@ -373,7 +373,7 @@ bool MovementAnimation::init()
|
|||||||
if (stack->hasBonus(Selector::type()(Bonus::FLYING)))
|
if (stack->hasBonus(Selector::type()(Bonus::FLYING)))
|
||||||
{
|
{
|
||||||
float distance = static_cast<float>(sqrt(distanceX * distanceX + distanceY * distanceY));
|
float distance = static_cast<float>(sqrt(distanceX * distanceX + distanceY * distanceY));
|
||||||
progressPerSecond = AnimationControls::getFlightDistance(stack->getCreature()) / distance;
|
progressPerSecond = AnimationControls::getFlightDistance(stack->unitType()) / distance;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -453,7 +453,7 @@ bool MovementEndAnimation::init()
|
|||||||
logAnim->debug("CMovementEndAnimation::init: stack %s", stack->getName());
|
logAnim->debug("CMovementEndAnimation::init: stack %s", stack->getName());
|
||||||
myAnim->pos.moveTo(owner.stacksController->getStackPositionAtHex(nextHex, stack));
|
myAnim->pos.moveTo(owner.stacksController->getStackPositionAtHex(nextHex, stack));
|
||||||
|
|
||||||
CCS->soundh->playSound(battle_sound(stack->getCreature(), endMoving));
|
CCS->soundh->playSound(battle_sound(stack->unitType(), endMoving));
|
||||||
|
|
||||||
if(!myAnim->framesInGroup(ECreatureAnimType::MOVE_END))
|
if(!myAnim->framesInGroup(ECreatureAnimType::MOVE_END))
|
||||||
{
|
{
|
||||||
@ -494,7 +494,7 @@ bool MovementStartAnimation::init()
|
|||||||
}
|
}
|
||||||
|
|
||||||
logAnim->debug("CMovementStartAnimation::init: stack %s", stack->getName());
|
logAnim->debug("CMovementStartAnimation::init: stack %s", stack->getName());
|
||||||
CCS->soundh->playSound(battle_sound(stack->getCreature(), startMoving));
|
CCS->soundh->playSound(battle_sound(stack->unitType(), startMoving));
|
||||||
|
|
||||||
if(!myAnim->framesInGroup(ECreatureAnimType::MOVE_START))
|
if(!myAnim->framesInGroup(ECreatureAnimType::MOVE_START))
|
||||||
{
|
{
|
||||||
|
@ -146,7 +146,7 @@ BattleProjectileController::BattleProjectileController(BattleInterface & owner):
|
|||||||
|
|
||||||
const CCreature & BattleProjectileController::getShooter(const CStack * stack) const
|
const CCreature & BattleProjectileController::getShooter(const CStack * stack) const
|
||||||
{
|
{
|
||||||
const CCreature * creature = stack->getCreature();
|
const CCreature * creature = stack->unitType();
|
||||||
|
|
||||||
if(creature->getId() == CreatureID::ARROW_TOWERS)
|
if(creature->getId() == CreatureID::ARROW_TOWERS)
|
||||||
creature = owner.siegeController->getTurretCreature();
|
creature = owner.siegeController->getTurretCreature();
|
||||||
|
@ -49,7 +49,7 @@ static void onAnimationFinished(const CStack *stack, std::weak_ptr<CreatureAnima
|
|||||||
|
|
||||||
if (animation->isIdle())
|
if (animation->isIdle())
|
||||||
{
|
{
|
||||||
const CCreature *creature = stack->getCreature();
|
const CCreature *creature = stack->unitType();
|
||||||
|
|
||||||
if (stack->isFrozen())
|
if (stack->isFrozen())
|
||||||
animation->setType(ECreatureAnimType::FROZEN);
|
animation->setType(ECreatureAnimType::FROZEN);
|
||||||
@ -207,7 +207,7 @@ void BattleStacksController::stackAdded(const CStack * stack, bool instant)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
stackAnimation[stack->ID] = AnimationControls::getAnimation(stack->getCreature());
|
stackAnimation[stack->ID] = AnimationControls::getAnimation(stack->unitType());
|
||||||
stackAnimation[stack->ID]->onAnimationReset += std::bind(&onAnimationFinished, stack, stackAnimation[stack->ID]);
|
stackAnimation[stack->ID]->onAnimationReset += std::bind(&onAnimationFinished, stack, stackAnimation[stack->ID]);
|
||||||
stackAnimation[stack->ID]->pos.h = stackAnimation[stack->ID]->getHeight();
|
stackAnimation[stack->ID]->pos.h = stackAnimation[stack->ID]->getHeight();
|
||||||
stackAnimation[stack->ID]->pos.w = stackAnimation[stack->ID]->getWidth();
|
stackAnimation[stack->ID]->pos.w = stackAnimation[stack->ID]->getWidth();
|
||||||
|
@ -140,11 +140,6 @@
|
|||||||
"appearAnimation" : "C09SPF0",
|
"appearAnimation" : "C09SPF0",
|
||||||
"appearSound" : "LANDMINE"
|
"appearSound" : "LANDMINE"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"damage":{
|
|
||||||
"type":"core:damage",
|
|
||||||
"optional":false,
|
|
||||||
"indirect":true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -641,6 +636,21 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"targetModifier":{"smart":true}
|
"targetModifier":{"smart":true}
|
||||||
|
},
|
||||||
|
"advanced" :{
|
||||||
|
"battleEffects":{
|
||||||
|
"teleport":{
|
||||||
|
"isMoatPassable" : true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"expert" : {
|
||||||
|
"battleEffects":{
|
||||||
|
"teleport":{
|
||||||
|
"isWallPassable" : true,
|
||||||
|
"isMoatPassable" : true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flags" : {
|
"flags" : {
|
||||||
|
@ -53,12 +53,8 @@ public:
|
|||||||
*/
|
*/
|
||||||
virtual int32_t getLevelPower(const int32_t skillLevel) const = 0;
|
virtual int32_t getLevelPower(const int32_t skillLevel) const = 0;
|
||||||
|
|
||||||
virtual std::string getNameTextID() const = 0;
|
|
||||||
virtual std::string getNameTranslated() const = 0;
|
|
||||||
|
|
||||||
virtual std::string getDescriptionTextID(int32_t level) const = 0;
|
virtual std::string getDescriptionTextID(int32_t level) const = 0;
|
||||||
virtual std::string getDescriptionTranslated(int32_t level) const = 0;
|
virtual std::string getDescriptionTranslated(int32_t level) const = 0;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -56,11 +56,6 @@ CStack::CStack(const CStackBasicDescriptor * stack, const PlayerColor & O, int I
|
|||||||
health.init(); //???
|
health.init(); //???
|
||||||
}
|
}
|
||||||
|
|
||||||
const CCreature * CStack::getCreature() const
|
|
||||||
{
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CStack::localInit(BattleInfo * battleInfo)
|
void CStack::localInit(BattleInfo * battleInfo)
|
||||||
{
|
{
|
||||||
battle = battleInfo;
|
battle = battleInfo;
|
||||||
@ -88,7 +83,7 @@ ui32 CStack::level() const
|
|||||||
if(base)
|
if(base)
|
||||||
return base->getLevel(); //creature or commander
|
return base->getLevel(); //creature or commander
|
||||||
else
|
else
|
||||||
return std::max(1, static_cast<int>(getCreature()->getLevel())); //war machine, clone etc
|
return std::max(1, static_cast<int>(unitType()->getLevel())); //war machine, clone etc
|
||||||
}
|
}
|
||||||
|
|
||||||
si32 CStack::magicResistance() const
|
si32 CStack::magicResistance() const
|
||||||
@ -346,7 +341,7 @@ bool CStack::unitHasAmmoCart(const battle::Unit * unit) const
|
|||||||
{
|
{
|
||||||
for(const CStack * st : battle->stacks)
|
for(const CStack * st : battle->stacks)
|
||||||
{
|
{
|
||||||
if(battle->battleMatchOwner(st, unit, true) && st->getCreature()->getId() == CreatureID::AMMO_CART)
|
if(battle->battleMatchOwner(st, unit, true) && st->unitType()->getId() == CreatureID::AMMO_CART)
|
||||||
{
|
{
|
||||||
return st->alive();
|
return st->alive();
|
||||||
}
|
}
|
||||||
|
@ -43,8 +43,6 @@ public:
|
|||||||
CStack();
|
CStack();
|
||||||
~CStack();
|
~CStack();
|
||||||
|
|
||||||
const CCreature * getCreature() const; //deprecated
|
|
||||||
|
|
||||||
std::string nodeName() const override;
|
std::string nodeName() const override;
|
||||||
|
|
||||||
void localInit(BattleInfo * battleInfo);
|
void localInit(BattleInfo * battleInfo);
|
||||||
|
@ -52,7 +52,7 @@ void BattleInfo::calculateCasualties(std::map<ui32,si32> * casualties) const
|
|||||||
const CStack * const st = elem;
|
const CStack * const st = elem;
|
||||||
si32 killed = st->getKilled();
|
si32 killed = st->getKilled();
|
||||||
if(killed > 0)
|
if(killed > 0)
|
||||||
casualties[st->side][st->getCreature()->getId()] += killed;
|
casualties[st->side][st->unitType()->getId()] += killed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,7 +210,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
|
|||||||
if(town)
|
if(town)
|
||||||
{
|
{
|
||||||
curB->town = town;
|
curB->town = town;
|
||||||
curB->terrainType = (*VLC->townh)[town->subID]->nativeTerrain;
|
curB->terrainType = town->getNativeTerrain();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -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"
|
||||||
@ -133,11 +137,13 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastSpell(con
|
|||||||
return ESpellCastProblem::OK;
|
return ESpellCastProblem::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CBattleInfoCallback::battleHasWallPenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const
|
bool CBattleInfoCallback::battleHasPenaltyOnLine(BattleHex from, BattleHex dest, bool checkWall, bool checkMoat) const
|
||||||
{
|
{
|
||||||
auto isTileBlocked = [&](BattleHex tile)
|
auto isTileBlocked = [&](BattleHex tile)
|
||||||
{
|
{
|
||||||
EWallPart wallPart = battleHexToWallPart(tile);
|
EWallPart wallPart = battleHexToWallPart(tile);
|
||||||
|
if (wallPart == EWallPart::INVALID)
|
||||||
|
return false; // there is no wall here
|
||||||
if (wallPart == EWallPart::INDESTRUCTIBLE_PART_OF_GATE)
|
if (wallPart == EWallPart::INDESTRUCTIBLE_PART_OF_GATE)
|
||||||
return false; // does not blocks ranged attacks
|
return false; // does not blocks ranged attacks
|
||||||
if (wallPart == EWallPart::INDESTRUCTIBLE_PART)
|
if (wallPart == EWallPart::INDESTRUCTIBLE_PART)
|
||||||
@ -145,35 +151,54 @@ bool CBattleInfoCallback::battleHasWallPenalty(const IBonusBearer * shooter, Bat
|
|||||||
|
|
||||||
return isWallPartAttackable(wallPart);
|
return isWallPartAttackable(wallPart);
|
||||||
};
|
};
|
||||||
|
// Count wall penalty requirement by shortest path, not by arbitrary line, to avoid various OH3 bugs
|
||||||
auto needWallPenalty = [&](BattleHex from, BattleHex dest)
|
auto getShortestPath = [](BattleHex from, BattleHex dest) -> std::vector<BattleHex>
|
||||||
{
|
{
|
||||||
// arbitrary selected cell size for virtual grid
|
//Out early
|
||||||
// any even number can be selected (for division by two)
|
if(from == dest)
|
||||||
static const int cellSize = 10;
|
return {};
|
||||||
|
|
||||||
// create line that goes from center of shooter cell to center of target cell
|
std::vector<BattleHex> ret;
|
||||||
Point line1{ from.getX()*cellSize+cellSize/2, from.getY()*cellSize+cellSize/2};
|
auto next = from;
|
||||||
Point line2{ dest.getX()*cellSize+cellSize/2, dest.getY()*cellSize+cellSize/2};
|
//Not a real direction, only to indicate to which side we should search closest tile
|
||||||
|
auto direction = from.getX() > dest.getX() ? BattleSide::DEFENDER : BattleSide::ATTACKER;
|
||||||
|
|
||||||
for (int y = 0; y < GameConstants::BFIELD_HEIGHT; ++y)
|
while (next != dest)
|
||||||
{
|
{
|
||||||
BattleHex obstacle = lineToWallHex(y);
|
auto tiles = next.neighbouringTiles();
|
||||||
if (!isTileBlocked(obstacle))
|
std::set<BattleHex> possibilities = {tiles.begin(), tiles.end()};
|
||||||
|
next = BattleHex::getClosestTile(direction, dest, possibilities);
|
||||||
|
ret.push_back(next);
|
||||||
|
}
|
||||||
|
assert(!ret.empty());
|
||||||
|
ret.pop_back(); //Remove destination hex
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
RETURN_IF_NOT_BATTLE(false);
|
||||||
|
auto checkNeeded = !sameSideOfWall(from, dest);
|
||||||
|
bool pathHasWall = false;
|
||||||
|
bool pathHasMoat = false;
|
||||||
|
|
||||||
|
for(const auto & hex : getShortestPath(from, dest))
|
||||||
|
{
|
||||||
|
pathHasWall |= isTileBlocked(hex);
|
||||||
|
if(!checkMoat)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// create rect around cell with an obstacle
|
auto obstacles = battleGetAllObstaclesOnPos(hex, false);
|
||||||
Rect rect {
|
|
||||||
Point(obstacle.getX(), obstacle.getY()) * cellSize,
|
|
||||||
Point( cellSize, cellSize)
|
|
||||||
};
|
|
||||||
|
|
||||||
if ( rect.intersectionTest(line1, line2))
|
if(hex != ESiegeHex::GATE_BRIDGE || (battleIsGatePassable()))
|
||||||
return true;
|
for(const auto & obst : obstacles)
|
||||||
|
if(obst->obstacleType == CObstacleInstance::MOAT)
|
||||||
|
pathHasMoat |= true;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
return checkNeeded && ( (checkWall && pathHasWall) || (checkMoat && pathHasMoat) );
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CBattleInfoCallback::battleHasWallPenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const
|
||||||
|
{
|
||||||
RETURN_IF_NOT_BATTLE(false);
|
RETURN_IF_NOT_BATTLE(false);
|
||||||
if(!battleGetSiegeLevel())
|
if(!battleGetSiegeLevel())
|
||||||
return false;
|
return false;
|
||||||
@ -184,26 +209,9 @@ bool CBattleInfoCallback::battleHasWallPenalty(const IBonusBearer * shooter, Bat
|
|||||||
if(shooter->hasBonus(selectorNoWallPenalty, cachingStrNoWallPenalty))
|
if(shooter->hasBonus(selectorNoWallPenalty, cachingStrNoWallPenalty))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const int wallInStackLine = lineToWallHex(shooterPosition.getY());
|
const auto shooterOutsideWalls = shooterPosition < lineToWallHex(shooterPosition.getY());
|
||||||
const bool shooterOutsideWalls = shooterPosition < wallInStackLine;
|
|
||||||
|
|
||||||
return shooterOutsideWalls && needWallPenalty(shooterPosition, destHex);
|
return shooterOutsideWalls && battleHasPenaltyOnLine(shooterPosition, destHex, true, false);
|
||||||
}
|
|
||||||
|
|
||||||
si8 CBattleInfoCallback::battleCanTeleportTo(const battle::Unit * stack, BattleHex destHex, int telportLevel) const
|
|
||||||
{
|
|
||||||
RETURN_IF_NOT_BATTLE(false);
|
|
||||||
if (!getAccesibility(stack).accessible(destHex, stack))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const ui8 siegeLevel = battleGetSiegeLevel();
|
|
||||||
|
|
||||||
//check for wall
|
|
||||||
//advanced teleport can pass wall of fort|citadel, expert - of castle
|
|
||||||
if ((siegeLevel > CGTownInstance::NONE && telportLevel < 2) || (siegeLevel >= CGTownInstance::CASTLE && telportLevel < 3))
|
|
||||||
return sameSideOfWall(stack->getPosition(), destHex);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<PossiblePlayerBattleAction> CBattleInfoCallback::getClientActionsForStack(const CStack * stack, const BattleClientInterfaceData & data)
|
std::vector<PossiblePlayerBattleAction> CBattleInfoCallback::getClientActionsForStack(const CStack * stack, const BattleClientInterfaceData & data)
|
||||||
@ -271,7 +279,7 @@ PossiblePlayerBattleAction CBattleInfoCallback::getCasterAction(const CSpell * s
|
|||||||
return PossiblePlayerBattleAction(spellSelMode, spell->id);
|
return PossiblePlayerBattleAction(spellSelMode, spell->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::set<BattleHex> CBattleInfoCallback::battleGetAttackedHexes(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos) const
|
std::set<BattleHex> CBattleInfoCallback::battleGetAttackedHexes(const battle::Unit * attacker, BattleHex destinationTile, BattleHex attackerPos) const
|
||||||
{
|
{
|
||||||
std::set<BattleHex> attackedHexes;
|
std::set<BattleHex> attackedHexes;
|
||||||
RETURN_IF_NOT_BATTLE(attackedHexes);
|
RETURN_IF_NOT_BATTLE(attackedHexes);
|
||||||
@ -645,7 +653,7 @@ std::vector<BattleHex> CBattleInfoCallback::battleGetAvailableHexes(const battle
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CBattleInfoCallback::battleCanAttack(const CStack * stack, const CStack * target, BattleHex dest) const
|
bool CBattleInfoCallback::battleCanAttack(const battle::Unit * stack, const battle::Unit * target, BattleHex dest) const
|
||||||
{
|
{
|
||||||
RETURN_IF_NOT_BATTLE(false);
|
RETURN_IF_NOT_BATTLE(false);
|
||||||
|
|
||||||
@ -658,7 +666,7 @@ bool CBattleInfoCallback::battleCanAttack(const CStack * stack, const CStack * t
|
|||||||
if(!battleMatchOwner(stack, target))
|
if(!battleMatchOwner(stack, target))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
auto id = stack->getCreature()->getId();
|
auto id = stack->unitType()->getId();
|
||||||
if (id == CreatureID::FIRST_AID_TENT || id == CreatureID::CATAPULT)
|
if (id == CreatureID::FIRST_AID_TENT || id == CreatureID::CATAPULT)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@ -814,8 +822,7 @@ std::vector<std::shared_ptr<const CObstacleInstance>> CBattleInfoCallback::getAl
|
|||||||
affectedObstacles.push_back(i);
|
affectedObstacles.push_back(i);
|
||||||
}
|
}
|
||||||
for(auto hex : unit->getHexes())
|
for(auto hex : unit->getHexes())
|
||||||
if(hex == ESiegeHex::GATE_BRIDGE)
|
if(hex == ESiegeHex::GATE_BRIDGE && battleIsGatePassable())
|
||||||
if(battleGetGateState() == EGateState::OPENED || battleGetGateState() == EGateState::DESTROYED)
|
|
||||||
for(int i=0; i<affectedObstacles.size(); i++)
|
for(int i=0; i<affectedObstacles.size(); i++)
|
||||||
if(affectedObstacles.at(i)->obstacleType == CObstacleInstance::MOAT)
|
if(affectedObstacles.at(i)->obstacleType == CObstacleInstance::MOAT)
|
||||||
affectedObstacles.erase(affectedObstacles.begin()+i);
|
affectedObstacles.erase(affectedObstacles.begin()+i);
|
||||||
@ -823,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;
|
||||||
|
@ -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;
|
||||||
|
|
||||||
@ -83,10 +86,10 @@ public:
|
|||||||
|
|
||||||
int battleGetSurrenderCost(const PlayerColor & Player) const; //returns cost of surrendering battle, -1 if surrendering is not possible
|
int battleGetSurrenderCost(const PlayerColor & Player) const; //returns cost of surrendering battle, -1 if surrendering is not possible
|
||||||
ReachabilityInfo::TDistances battleGetDistances(const battle::Unit * unit, BattleHex assumedPosition) const;
|
ReachabilityInfo::TDistances battleGetDistances(const battle::Unit * unit, BattleHex assumedPosition) const;
|
||||||
std::set<BattleHex> battleGetAttackedHexes(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos = BattleHex::INVALID) const;
|
std::set<BattleHex> battleGetAttackedHexes(const battle::Unit * attacker, BattleHex destinationTile, BattleHex attackerPos = BattleHex::INVALID) const;
|
||||||
bool isEnemyUnitWithinSpecifiedRange(BattleHex attackerPosition, const battle::Unit * defenderUnit, unsigned int range) const;
|
bool isEnemyUnitWithinSpecifiedRange(BattleHex attackerPosition, const battle::Unit * defenderUnit, unsigned int range) const;
|
||||||
|
|
||||||
bool battleCanAttack(const CStack * stack, const CStack * target, BattleHex dest) const; //determines if stack with given ID can attack target at the selected destination
|
bool battleCanAttack(const battle::Unit * stack, const battle::Unit * target, BattleHex dest) const; //determines if stack with given ID can attack target at the selected destination
|
||||||
bool battleCanShoot(const battle::Unit * attacker, BattleHex dest) const; //determines if stack with given ID shoot at the selected destination
|
bool battleCanShoot(const battle::Unit * attacker, BattleHex dest) const; //determines if stack with given ID shoot at the selected destination
|
||||||
bool battleCanShoot(const battle::Unit * attacker) const; //determines if stack with given ID shoot in principle
|
bool battleCanShoot(const battle::Unit * attacker) const; //determines if stack with given ID shoot in principle
|
||||||
bool battleIsUnitBlocked(const battle::Unit * unit) const; //returns true if there is neighboring enemy stack
|
bool battleIsUnitBlocked(const battle::Unit * unit) const; //returns true if there is neighboring enemy stack
|
||||||
@ -101,6 +104,7 @@ public:
|
|||||||
DamageEstimation battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPosition, DamageEstimation * retaliationDmg = nullptr) const;
|
DamageEstimation battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPosition, DamageEstimation * retaliationDmg = nullptr) const;
|
||||||
DamageEstimation battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, int movementDistance, DamageEstimation * retaliationDmg = nullptr) const;
|
DamageEstimation battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, int movementDistance, DamageEstimation * retaliationDmg = nullptr) const;
|
||||||
|
|
||||||
|
bool battleHasPenaltyOnLine(BattleHex from, BattleHex dest, bool checkWall, bool checkMoat) const;
|
||||||
bool battleHasDistancePenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const;
|
bool battleHasDistancePenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const;
|
||||||
bool battleHasWallPenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const;
|
bool battleHasWallPenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const;
|
||||||
bool battleHasShootingPenalty(const battle::Unit * shooter, BattleHex destHex) const;
|
bool battleHasShootingPenalty(const battle::Unit * shooter, BattleHex destHex) const;
|
||||||
@ -120,7 +124,6 @@ public:
|
|||||||
SpellID getRandomBeneficialSpell(CRandomGenerator & rand, const CStack * subject) const;
|
SpellID getRandomBeneficialSpell(CRandomGenerator & rand, const CStack * subject) const;
|
||||||
SpellID getRandomCastedSpell(CRandomGenerator & rand, const CStack * caster) const; //called at the beginning of turn for Faerie Dragon
|
SpellID getRandomCastedSpell(CRandomGenerator & rand, const CStack * caster) const; //called at the beginning of turn for Faerie Dragon
|
||||||
|
|
||||||
si8 battleCanTeleportTo(const battle::Unit * stack, BattleHex destHex, int telportLevel) const; //checks if teleportation of given stack to given position can take place
|
|
||||||
std::vector<PossiblePlayerBattleAction> getClientActionsForStack(const CStack * stack, const BattleClientInterfaceData & data);
|
std::vector<PossiblePlayerBattleAction> getClientActionsForStack(const CStack * stack, const BattleClientInterfaceData & data);
|
||||||
PossiblePlayerBattleAction getCasterAction(const CSpell * spell, const spells::Caster * caster, spells::Mode mode) const;
|
PossiblePlayerBattleAction getCasterAction(const CSpell * spell, const spells::Caster * caster, spells::Mode mode) const;
|
||||||
|
|
||||||
|
@ -379,6 +379,15 @@ EGateState CBattleInfoEssentials::battleGetGateState() const
|
|||||||
return getBattle()->getGateState();
|
return getBattle()->getGateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CBattleInfoEssentials::battleIsGatePassable() const
|
||||||
|
{
|
||||||
|
RETURN_IF_NOT_BATTLE(true);
|
||||||
|
if(battleGetSiegeLevel() == CGTownInstance::NONE)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return battleGetGateState() == EGateState::OPENED || battleGetGateState() == EGateState::DESTROYED;
|
||||||
|
}
|
||||||
|
|
||||||
PlayerColor CBattleInfoEssentials::battleGetOwner(const battle::Unit * unit) const
|
PlayerColor CBattleInfoEssentials::battleGetOwner(const battle::Unit * unit) const
|
||||||
{
|
{
|
||||||
RETURN_IF_NOT_BATTLE(PlayerColor::CANNOT_DETERMINE);
|
RETURN_IF_NOT_BATTLE(PlayerColor::CANNOT_DETERMINE);
|
||||||
|
@ -96,6 +96,7 @@ public:
|
|||||||
// [3] - below gate, [4] - over gate, [5] - upper wall, [6] - uppert tower, [7] - gate; returned value: 1 - intact, 2 - damaged, 3 - destroyed; 0 - no battle
|
// [3] - below gate, [4] - over gate, [5] - upper wall, [6] - uppert tower, [7] - gate; returned value: 1 - intact, 2 - damaged, 3 - destroyed; 0 - no battle
|
||||||
EWallState battleGetWallState(EWallPart partOfWall) const;
|
EWallState battleGetWallState(EWallPart partOfWall) const;
|
||||||
EGateState battleGetGateState() const;
|
EGateState battleGetGateState() const;
|
||||||
|
bool battleIsGatePassable() const;
|
||||||
|
|
||||||
//helpers
|
//helpers
|
||||||
///returns all stacks, alive or dead or undead or mechanical :)
|
///returns all stacks, alive or dead or undead or mechanical :)
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
#include "../ISpellMechanics.h"
|
#include "../ISpellMechanics.h"
|
||||||
#include "../../NetPacks.h"
|
#include "../../NetPacks.h"
|
||||||
#include "../../battle/CBattleInfoCallback.h"
|
#include "../../battle/CBattleInfoCallback.h"
|
||||||
|
#include "../../serializer/JsonSerializeFormat.h"
|
||||||
#include "../../battle/Unit.h"
|
#include "../../battle/Unit.h"
|
||||||
|
|
||||||
VCMI_LIB_NAMESPACE_BEGIN
|
VCMI_LIB_NAMESPACE_BEGIN
|
||||||
@ -45,39 +46,34 @@ void Teleport::adjustTargetTypes(std::vector<TargetType> & types) const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Teleport::applicable(Problem & problem, const Mechanics * m) const
|
bool Teleport::applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const
|
||||||
{
|
{
|
||||||
|
if(target.size() == 1) //Assume, this is check only for selecting a unit
|
||||||
return UnitEffect::applicable(problem, m);
|
return UnitEffect::applicable(problem, m);
|
||||||
|
|
||||||
|
if(target.size() != 2)
|
||||||
|
return m->adaptProblem(ESpellCastProblem::WRONG_SPELL_TARGET, problem);
|
||||||
|
|
||||||
|
const auto *targetUnit = target[0].unitValue;
|
||||||
|
const auto & targetHex = target[1].hexValue;
|
||||||
|
|
||||||
|
if(!targetUnit)
|
||||||
|
return m->adaptProblem(ESpellCastProblem::WRONG_SPELL_TARGET, problem);
|
||||||
|
|
||||||
|
if(!targetHex.isValid() || !m->battle()->getAccesibility(targetUnit).accessible(targetHex, targetUnit))
|
||||||
|
return m->adaptProblem(ESpellCastProblem::WRONG_SPELL_TARGET, problem);
|
||||||
|
|
||||||
|
if(m->battle()->battleGetSiegeLevel() && !(isWallPassable && isMoatPassable))
|
||||||
|
{
|
||||||
|
return !m->battle()->battleHasPenaltyOnLine(target[0].hexValue, target[1].hexValue, !isWallPassable, !isMoatPassable);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Teleport::apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const
|
void Teleport::apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const
|
||||||
{
|
{
|
||||||
if(target.size() != 2)
|
|
||||||
{
|
|
||||||
server->complain("Teleport requires 2 destinations.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto *targetUnit = target[0].unitValue;
|
const auto *targetUnit = target[0].unitValue;
|
||||||
if(nullptr == targetUnit)
|
const auto destination = target[1].hexValue;
|
||||||
{
|
|
||||||
server->complain("No unit to teleport");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const BattleHex destination = target[1].hexValue;
|
|
||||||
if(!destination.isValid())
|
|
||||||
{
|
|
||||||
server->complain("Invalid teleport destination");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: move here all teleport checks
|
|
||||||
if(!m->battle()->battleCanTeleportTo(targetUnit, destination, m->getEffectLevel()))
|
|
||||||
{
|
|
||||||
server->complain("Forbidden teleport.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
BattleStackMoved pack;
|
BattleStackMoved pack;
|
||||||
pack.distance = 0;
|
pack.distance = 0;
|
||||||
@ -87,11 +83,19 @@ 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)
|
||||||
{
|
{
|
||||||
//TODO: teleport options
|
handler.serializeBool("triggerObstacles", triggerObstacles);
|
||||||
|
handler.serializeBool("isWallPassable", isWallPassable);
|
||||||
|
handler.serializeBool("isMoatPassable", isMoatPassable);
|
||||||
}
|
}
|
||||||
|
|
||||||
EffectTarget Teleport::transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const
|
EffectTarget Teleport::transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const
|
||||||
|
@ -26,7 +26,7 @@ class Teleport : public UnitEffect
|
|||||||
public:
|
public:
|
||||||
void adjustTargetTypes(std::vector<TargetType> & types) const override;
|
void adjustTargetTypes(std::vector<TargetType> & types) const override;
|
||||||
|
|
||||||
bool applicable(Problem & problem, const Mechanics * m) const override;
|
bool applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const override;
|
||||||
|
|
||||||
void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override;
|
void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override;
|
||||||
|
|
||||||
@ -34,6 +34,11 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
void serializeJsonUnitEffect(JsonSerializeFormat & handler) override;
|
void serializeJsonUnitEffect(JsonSerializeFormat & handler) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool triggerObstacles;
|
||||||
|
bool isWallPassable;
|
||||||
|
bool isMoatPassable;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1208,11 +1208,11 @@ void CGameHandler::addGenericKilledLog(BattleLogMessage & blm, const CStack * de
|
|||||||
boost::format txt(formatString);
|
boost::format txt(formatString);
|
||||||
if(killed > 1)
|
if(killed > 1)
|
||||||
{
|
{
|
||||||
txt % killed % (multiple ? VLC->generaltexth->allTexts[43] : defender->getCreature()->getNamePluralTranslated()); // creatures perish
|
txt % killed % (multiple ? VLC->generaltexth->allTexts[43] : defender->unitType()->getNamePluralTranslated()); // creatures perish
|
||||||
}
|
}
|
||||||
else //killed == 1
|
else //killed == 1
|
||||||
{
|
{
|
||||||
txt % (multiple ? VLC->generaltexth->allTexts[42] : defender->getCreature()->getNameSingularTranslated()); // creature perishes
|
txt % (multiple ? VLC->generaltexth->allTexts[42] : defender->unitType()->getNameSingularTranslated()); // creature perishes
|
||||||
}
|
}
|
||||||
MetaString line;
|
MetaString line;
|
||||||
line << txt.str();
|
line << txt.str();
|
||||||
@ -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;
|
||||||
}
|
}
|
||||||
@ -4478,9 +4478,9 @@ void CGameHandler::updateGateState()
|
|||||||
// - deals moat damage to attacker if bridge is closed (fortress only)
|
// - deals moat damage to attacker if bridge is closed (fortress only)
|
||||||
|
|
||||||
bool hasForceFieldOnBridge = !battleGetAllObstaclesOnPos(BattleHex(ESiegeHex::GATE_BRIDGE), true).empty();
|
bool hasForceFieldOnBridge = !battleGetAllObstaclesOnPos(BattleHex(ESiegeHex::GATE_BRIDGE), true).empty();
|
||||||
bool hasStackAtGateInner = gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_INNER), false) != nullptr;
|
bool hasStackAtGateInner = gs->curB->battleGetUnitByPos(BattleHex(ESiegeHex::GATE_INNER), false) != nullptr;
|
||||||
bool hasStackAtGateOuter = gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_OUTER), false) != nullptr;
|
bool hasStackAtGateOuter = gs->curB->battleGetUnitByPos(BattleHex(ESiegeHex::GATE_OUTER), false) != nullptr;
|
||||||
bool hasStackAtGateBridge = gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_BRIDGE), false) != nullptr;
|
bool hasStackAtGateBridge = gs->curB->battleGetUnitByPos(BattleHex(ESiegeHex::GATE_BRIDGE), false) != nullptr;
|
||||||
bool hasWideMoat = vstd::contains_if(battleGetAllObstaclesOnPos(BattleHex(ESiegeHex::GATE_BRIDGE), false), [](const std::shared_ptr<const CObstacleInstance> & obst)
|
bool hasWideMoat = vstd::contains_if(battleGetAllObstaclesOnPos(BattleHex(ESiegeHex::GATE_BRIDGE), false), [](const std::shared_ptr<const CObstacleInstance> & obst)
|
||||||
{
|
{
|
||||||
return obst->obstacleType == CObstacleInstance::MOAT;
|
return obst->obstacleType == CObstacleInstance::MOAT;
|
||||||
@ -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);
|
||||||
@ -6031,8 +5971,8 @@ void CGameHandler::handleAfterAttackCasting(bool ranged, const CStack * attacker
|
|||||||
|
|
||||||
int bonusAdditionalInfo = attacker->getBonus(Selector::type()(Bonus::TRANSMUTATION))->additionalInfo[0];
|
int bonusAdditionalInfo = attacker->getBonus(Selector::type()(Bonus::TRANSMUTATION))->additionalInfo[0];
|
||||||
|
|
||||||
if(defender->getCreature()->getId() == bonusAdditionalInfo ||
|
if(defender->unitType()->getId() == bonusAdditionalInfo ||
|
||||||
(bonusAdditionalInfo == CAddInfo::NONE && defender->getCreature()->getId() == attacker->getCreature()->getId()))
|
(bonusAdditionalInfo == CAddInfo::NONE && defender->unitType()->getId() == attacker->unitType()->getId()))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
battle::UnitInfo resurrectInfo;
|
battle::UnitInfo resurrectInfo;
|
||||||
@ -6421,7 +6361,7 @@ void CGameHandler::runBattle()
|
|||||||
auto accessibility = getAccesibility();
|
auto accessibility = getAccesibility();
|
||||||
CreatureID creatureData = CreatureID(summonInfo->subtype);
|
CreatureID creatureData = CreatureID(summonInfo->subtype);
|
||||||
std::vector<BattleHex> targetHexes;
|
std::vector<BattleHex> targetHexes;
|
||||||
const bool targetIsBig = stack->getCreature()->isDoubleWide(); //target = creature to guard
|
const bool targetIsBig = stack->unitType()->isDoubleWide(); //target = creature to guard
|
||||||
const bool guardianIsBig = creatureData.toCreature()->isDoubleWide();
|
const bool guardianIsBig = creatureData.toCreature()->isDoubleWide();
|
||||||
|
|
||||||
/*Chosen idea for two hex units was to cover all possible surrounding hexes of target unit with as small number of stacks as possible.
|
/*Chosen idea for two hex units was to cover all possible surrounding hexes of target unit with as small number of stacks as possible.
|
||||||
@ -6606,7 +6546,7 @@ void CGameHandler::runBattle()
|
|||||||
}
|
}
|
||||||
|
|
||||||
const CGHeroInstance * curOwner = battleGetOwnerHero(next);
|
const CGHeroInstance * curOwner = battleGetOwnerHero(next);
|
||||||
const int stackCreatureId = next->getCreature()->getId();
|
const int stackCreatureId = next->unitType()->getId();
|
||||||
|
|
||||||
if ((stackCreatureId == CreatureID::ARROW_TOWERS || stackCreatureId == CreatureID::BALLISTA)
|
if ((stackCreatureId == CreatureID::ARROW_TOWERS || stackCreatureId == CreatureID::BALLISTA)
|
||||||
&& (!curOwner || getRandomGenerator().nextInt(99) >= curOwner->valOfBonuses(Bonus::MANUAL_CONTROL, stackCreatureId)))
|
&& (!curOwner || getRandomGenerator().nextInt(99) >= curOwner->valOfBonuses(Bonus::MANUAL_CONTROL, stackCreatureId)))
|
||||||
@ -6622,7 +6562,7 @@ void CGameHandler::runBattle()
|
|||||||
|
|
||||||
for(auto & elem : gs->curB->stacks)
|
for(auto & elem : gs->curB->stacks)
|
||||||
{
|
{
|
||||||
if(elem->getCreature()->getId() != CreatureID::CATAPULT
|
if(elem->unitType()->getId() != CreatureID::CATAPULT
|
||||||
&& elem->owner != next->owner
|
&& elem->owner != next->owner
|
||||||
&& elem->isValidTarget()
|
&& elem->isValidTarget()
|
||||||
&& gs->curB->battleCanShoot(next, elem->getPosition()))
|
&& gs->curB->battleCanShoot(next, elem->getPosition()))
|
||||||
@ -6644,7 +6584,7 @@ void CGameHandler::runBattle()
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (next->getCreature()->getId() == CreatureID::CATAPULT)
|
if (next->unitType()->getId() == CreatureID::CATAPULT)
|
||||||
{
|
{
|
||||||
const auto & attackableBattleHexes = curB.getAttackableBattleHexes();
|
const auto & attackableBattleHexes = curB.getAttackableBattleHexes();
|
||||||
|
|
||||||
@ -6666,7 +6606,7 @@ void CGameHandler::runBattle()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (next->getCreature()->getId() == CreatureID::FIRST_AID_TENT)
|
if (next->unitType()->getId() == CreatureID::FIRST_AID_TENT)
|
||||||
{
|
{
|
||||||
TStacks possibleStacks = battleGetStacksIf([=](const CStack * s)
|
TStacks possibleStacks = battleGetStacksIf([=](const CStack * s)
|
||||||
{
|
{
|
||||||
|
@ -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 );
|
||||||
|
Loading…
Reference in New Issue
Block a user