1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-20 20:23:03 +02:00
vcmi/lib/spells/effects/Obstacle.cpp
AlexVinS ecaa9f5d0b Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2021-02-14 19:05:43 +03:00

343 lines
8.4 KiB
C++

/*
* Obstacle.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "Obstacle.h"
#include "Registry.h"
#include "../ISpellMechanics.h"
#include "../../NetPacks.h"
#include "../../battle/IBattleState.h"
#include "../../battle/CBattleInfoCallback.h"
#include "../../serializer/JsonSerializeFormat.h"
static const std::string EFFECT_NAME = "core:obstacle";
namespace spells
{
namespace effects
{
VCMI_REGISTER_SPELL_EFFECT(Obstacle, EFFECT_NAME);
ObstacleSideOptions::ObstacleSideOptions()
: shape(),
range()
{
}
void ObstacleSideOptions::serializeJson(JsonSerializeFormat & handler)
{
serializeRelativeShape(handler, "shape", shape);
serializeRelativeShape(handler, "range", range);
handler.serializeString("appearAnimation", appearAnimation);
handler.serializeString("animation", animation);
handler.serializeInt("offsetY", offsetY);
}
void ObstacleSideOptions::serializeRelativeShape(JsonSerializeFormat & handler, const std::string & fieldName, RelativeShape & value)
{
static const std::vector<std::string> EDirMap =
{
"TL",
"TR",
"R",
"BR",
"BL",
"L",
""
};
{
JsonArraySerializer outer = handler.enterArray(fieldName);
outer.syncSize(value, JsonNode::JsonType::DATA_VECTOR);
for(size_t outerIndex = 0; outerIndex < outer.size(); outerIndex++)
{
JsonArraySerializer inner = outer.enterArray(outerIndex);
inner.syncSize(value.at(outerIndex), JsonNode::JsonType::DATA_STRING);
for(size_t innerIndex = 0; innerIndex < inner.size(); innerIndex++)
{
std::string temp;
if(handler.saving)
{
temp = EDirMap.at(value.at(outerIndex).at(innerIndex));
}
inner.serializeString(innerIndex, temp);
if(!handler.saving)
{
value.at(outerIndex).at(innerIndex) = (BattleHex::EDir) vstd::find_pos(EDirMap, temp);
}
}
}
}
if(!handler.saving)
{
if(value.empty())
value.emplace_back();
if(value.back().empty())
value.back().emplace_back(BattleHex::EDir::NONE);
}
}
Obstacle::Obstacle()
: LocationEffect(),
hidden(false),
passable(false),
trigger(false),
trap(false),
removeOnTrigger(false),
patchCount(1),
turnsRemaining(-1)
{
}
Obstacle::~Obstacle() = default;
void Obstacle::adjustAffectedHexes(std::set<BattleHex> & hexes, const Mechanics * m, const Target & spellTarget) const
{
EffectTarget effectTarget = transformTarget(m, spellTarget, spellTarget);
const ObstacleSideOptions & options = sideOptions.at(m->casterSide);
for(auto & destination : effectTarget)
{
for(auto & trasformation : options.shape)
{
BattleHex hex = destination.hexValue;
for(auto direction : trasformation)
hex.moveInDirection(direction, false);
if(hex.isValid())
hexes.insert(hex);
}
}
}
bool Obstacle::applicable(Problem & problem, const Mechanics * m) const
{
return LocationEffect::applicable(problem, m);
}
bool Obstacle::applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const
{
if(!m->isMassive())
{
const bool requiresClearTiles = m->requiresClearTiles();
const ObstacleSideOptions & options = sideOptions.at(m->casterSide);
if(target.empty())
return noRoomToPlace(problem, m);
for(const auto & destination : target)
{
for(auto & trasformation : options.shape)
{
BattleHex hex = destination.hexValue;
for(auto direction : trasformation)
hex.moveInDirection(direction, false);
if(!isHexAvailable(m->battle(), hex, requiresClearTiles))
return noRoomToPlace(problem, m);
}
}
}
return LocationEffect::applicable(problem, m, target);
}
EffectTarget Obstacle::transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const
{
const ObstacleSideOptions & options = sideOptions.at(m->casterSide);
EffectTarget ret;
if(!m->isMassive())
{
for(auto & spellDestination : spellTarget)
{
for(auto & rangeShape : options.range)
{
BattleHex hex = spellDestination.hexValue;
for(auto direction : rangeShape)
hex.moveInDirection(direction, false);
ret.emplace_back(hex);
}
}
}
return ret;
}
void Obstacle::apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const
{
if(m->isMassive())
{
std::vector<BattleHex> availableTiles;
for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
{
BattleHex hex = i;
if(isHexAvailable(m->battle(), hex, true))
availableTiles.push_back(hex);
}
RandomGeneratorUtil::randomShuffle(availableTiles, *server->getRNG());
const int patchesToPut = std::min(patchCount, (int)availableTiles.size());
EffectTarget randomTarget;
randomTarget.reserve(patchesToPut);
for(int i = 0; i < patchesToPut; i++)
randomTarget.emplace_back(availableTiles.at(i));
placeObstacles(server, m, randomTarget);
}
else
{
placeObstacles(server, m, target);
}
}
void Obstacle::serializeJsonEffect(JsonSerializeFormat & handler)
{
handler.serializeBool("hidden", hidden);
handler.serializeBool("passable", passable);
handler.serializeBool("trigger", trigger);
handler.serializeBool("trap", trap);
handler.serializeBool("removeOnTrigger", removeOnTrigger);
handler.serializeInt("patchCount", patchCount);
handler.serializeInt("turnsRemaining", turnsRemaining, -1);
handler.serializeStruct("attacker", sideOptions.at(BattleSide::ATTACKER));
handler.serializeStruct("defender", sideOptions.at(BattleSide::DEFENDER));
}
bool Obstacle::isHexAvailable(const CBattleInfoCallback * cb, const BattleHex & hex, const bool mustBeClear)
{
if(!hex.isAvailable())
return false;
if(!mustBeClear)
return true;
if(cb->battleGetUnitByPos(hex, true))
return false;
auto obst = cb->battleGetAllObstaclesOnPos(hex, false);
for(auto & i : obst)
if(i->obstacleType != CObstacleInstance::MOAT)
return false;
if(cb->battleGetSiegeLevel() != 0)
{
EWallPart::EWallPart part = cb->battleHexToWallPart(hex);
if(part == EWallPart::INVALID || part == EWallPart::INDESTRUCTIBLE_PART_OF_GATE)
return true;//no fortification here
else if(static_cast<int>(part) < 0)
return false;//indestructible part (cant be checked by battleGetWallState)
else if(part == EWallPart::BOTTOM_TOWER || part == EWallPart::UPPER_TOWER)
return false;//destructible, but should not be available
else if(cb->battleGetWallState(part) != EWallState::DESTROYED && cb->battleGetWallState(part) != EWallState::NONE)
return false;
}
return true;
}
bool Obstacle::noRoomToPlace(Problem & problem, const Mechanics * m)
{
MetaString text;
text.addTxt(MetaString::GENERAL_TXT, 181);//No room to place %s here
text.addReplacement(m->getSpellName());
problem.add(std::move(text));
return false;
}
void Obstacle::placeObstacles(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const
{
const ObstacleSideOptions & options = sideOptions.at(m->casterSide);
BattleObstaclesChanged pack;
boost::optional<BattlePerspective::BattlePerspective> perspective;
if(!m->battle()->getPlayerID())
perspective = boost::make_optional(BattlePerspective::ALL_KNOWING);
auto all = m->battle()->battleGetAllObstacles(perspective);
int obstacleIdToGive = 1;
for(auto & one : all)
if(one->uniqueID >= obstacleIdToGive)
obstacleIdToGive = one->uniqueID + 1;
for(const Destination & destination : target)
{
SpellCreatedObstacle obstacle;
obstacle.uniqueID = obstacleIdToGive++;
obstacle.pos = destination.hexValue;
obstacle.obstacleType = CObstacleInstance::USUAL;
obstacle.ID = m->getSpellIndex();
obstacle.turnsRemaining = turnsRemaining;
obstacle.casterSpellPower = m->getEffectPower();
obstacle.spellLevel = m->getEffectLevel();//todo: level of indirect effect should be also configurable
obstacle.casterSide = m->casterSide;
obstacle.hidden = hidden;
obstacle.passable = passable;
obstacle.trigger = trigger;
obstacle.trap = trap;
obstacle.removeOnTrigger = removeOnTrigger;
obstacle.appearAnimation = options.appearAnimation;
obstacle.animation = options.animation;
obstacle.animationYOffset = options.offsetY;
obstacle.customSize.clear();
obstacle.customSize.reserve(options.shape.size());
for(auto & shape : options.shape)
{
BattleHex hex = destination.hexValue;
for(auto direction : shape)
hex.moveInDirection(direction, false);
obstacle.customSize.emplace_back(hex);
}
pack.changes.emplace_back();
obstacle.toInfo(pack.changes.back());
}
if(!pack.changes.empty())
server->apply(&pack);
}
}
}