mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-24 22:14:36 +02:00
Small refactoring of adventure map spell casting:
- Removed duplicated checks for DD possibility - Moved most of spell-specific code from AdventureMapInterface to library code - AdventureSpellMechanics class now provides methods to check whether spellcast is possible, similarly to battle spells - If it is not possible to cast adventure map spell (e.g. no mana or no move points) game will show infowindow immediately on clicking spellbook instead of on cast attempt - If hero does not have movement points for a DD, game will show correct error message - Added game settings 'dimensionDoorFailureSpendsPoints' due to discovered H3 logic
This commit is contained in:
parent
bcd4a8c961
commit
8353bca34f
@ -118,6 +118,8 @@
|
||||
"vcmi.server.errors.modConflict" : "Failed to load mod {'%s'}!\n Conflicts with active mod {'%s'}!\n",
|
||||
"vcmi.server.errors.unknownEntity" : "Failed to load save! Unknown entity '%s' found in saved game! Save may not be compatible with currently installed version of mods!",
|
||||
|
||||
"vcmi.dimensionDoor.seaToLandError" : "It's not possible to teleport from sea to land or vice versa with a Dimension Door.",
|
||||
|
||||
"vcmi.settingsMainWindow.generalTab.hover" : "General",
|
||||
"vcmi.settingsMainWindow.generalTab.help" : "Switches to General Options tab, which contains settings related to general game client behavior.",
|
||||
"vcmi.settingsMainWindow.battleTab.hover" : "Battle",
|
||||
|
@ -113,6 +113,8 @@
|
||||
"vcmi.server.errors.modConflict" : "Не вдалося увімкнути мод {'%s'}!\n Конфліктує з активним модом {'%s'}!\n",
|
||||
"vcmi.server.errors.unknownEntity" : "Не вдалося завантажити гру! У збереженій грі знайдено невідомий об'єкт '%s'! Це збереження може бути несумісним зі встановленою версією модифікацій!",
|
||||
|
||||
"vcmi.dimensionDoor.seaToLandError" : "Неможливо телепортуватися з моря на сушу або навпаки за допомогою просторової брами",
|
||||
|
||||
"vcmi.settingsMainWindow.generalTab.hover" : "Загальні",
|
||||
"vcmi.settingsMainWindow.generalTab.help" : "Перемикає на вкладку загальних параметрів, яка містить налаштування, пов'язані із загальною поведінкою ігрового клієнта",
|
||||
"vcmi.settingsMainWindow.battleTab.hover" : "Бої",
|
||||
|
@ -45,6 +45,8 @@
|
||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||
#include "../../lib/mapping/CMapDefines.h"
|
||||
#include "../../lib/pathfinder/CGPathNode.h"
|
||||
#include "../../lib/spells/ISpellMechanics.h"
|
||||
#include "../../lib/spells/Problem.h"
|
||||
|
||||
std::shared_ptr<AdventureMapInterface> adventureInt;
|
||||
|
||||
@ -506,11 +508,6 @@ void AdventureMapInterface::onTileLeftClicked(const int3 &targetPosition)
|
||||
if(!shortcuts->optionMapViewActive())
|
||||
return;
|
||||
|
||||
if(!LOCPLINT->cb->isVisible(targetPosition))
|
||||
{
|
||||
if(!spellBeingCasted || spellBeingCasted->id != SpellID::DIMENSION_DOOR)
|
||||
return;
|
||||
}
|
||||
if(!LOCPLINT->makingTurn)
|
||||
return;
|
||||
|
||||
@ -519,23 +516,16 @@ void AdventureMapInterface::onTileLeftClicked(const int3 &targetPosition)
|
||||
if(spellBeingCasted)
|
||||
{
|
||||
assert(shortcuts->optionSpellcasting());
|
||||
assert(spellBeingCasted->id == SpellID::SCUTTLE_BOAT || spellBeingCasted->id == SpellID::DIMENSION_DOOR);
|
||||
|
||||
switch(spellBeingCasted->id)
|
||||
{
|
||||
case SpellID::SCUTTLE_BOAT:
|
||||
if(isValidAdventureSpellTarget(targetPosition, topBlocking, SpellID::SCUTTLE_BOAT))
|
||||
if(isValidAdventureSpellTarget(targetPosition))
|
||||
performSpellcasting(targetPosition);
|
||||
break;
|
||||
case SpellID::DIMENSION_DOOR:
|
||||
if(isValidAdventureSpellTarget(targetPosition, topBlocking, SpellID::DIMENSION_DOOR))
|
||||
performSpellcasting(targetPosition);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(!LOCPLINT->cb->isVisible(targetPosition))
|
||||
return;
|
||||
|
||||
//check if we can select this object
|
||||
bool canSelect = topBlocking && topBlocking->ID == Obj::HERO && topBlocking->tempOwner == LOCPLINT->playerID;
|
||||
canSelect |= topBlocking && topBlocking->ID == Obj::TOWN && LOCPLINT->cb->getPlayerRelations(LOCPLINT->playerID, topBlocking->tempOwner) != PlayerRelations::ENEMIES;
|
||||
@ -610,21 +600,45 @@ void AdventureMapInterface::onTileHovered(const int3 &targetPosition)
|
||||
return;
|
||||
|
||||
bool isTargetPositionVisible = LOCPLINT->cb->isVisible(targetPosition);
|
||||
const CGObjectInstance *objAtTile = isTargetPositionVisible ? getActiveObject(targetPosition) : nullptr;
|
||||
|
||||
if(!isTargetPositionVisible)
|
||||
if(spellBeingCasted)
|
||||
{
|
||||
GH.statusbar()->clear();
|
||||
switch(spellBeingCasted->id)
|
||||
{
|
||||
case SpellID::SCUTTLE_BOAT:
|
||||
if(isValidAdventureSpellTarget(targetPosition))
|
||||
CCS->curh->set(Cursor::Map::SCUTTLE_BOAT);
|
||||
else
|
||||
CCS->curh->set(Cursor::Map::POINTER);
|
||||
return;
|
||||
|
||||
if(!spellBeingCasted || spellBeingCasted->id != SpellID::DIMENSION_DOOR)
|
||||
case SpellID::DIMENSION_DOOR:
|
||||
if(isValidAdventureSpellTarget(targetPosition))
|
||||
{
|
||||
if(VLC->settings()->getBoolean(EGameSettings::DIMENSION_DOOR_TRIGGERS_GUARDS) && LOCPLINT->cb->isTileGuardedUnchecked(targetPosition))
|
||||
CCS->curh->set(Cursor::Map::T1_ATTACK);
|
||||
else
|
||||
CCS->curh->set(Cursor::Map::TELEPORT);
|
||||
return;
|
||||
}
|
||||
else
|
||||
CCS->curh->set(Cursor::Map::POINTER);
|
||||
return;
|
||||
default:
|
||||
CCS->curh->set(Cursor::Map::POINTER);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(!isTargetPositionVisible)
|
||||
{
|
||||
CCS->curh->set(Cursor::Map::POINTER);
|
||||
return;
|
||||
}
|
||||
|
||||
auto objRelations = PlayerRelations::ALLIES;
|
||||
|
||||
const CGObjectInstance *objAtTile = isTargetPositionVisible ? getActiveObject(targetPosition) : nullptr;
|
||||
if(objAtTile)
|
||||
{
|
||||
objRelations = LOCPLINT->cb->getPlayerRelations(LOCPLINT->playerID, objAtTile->tempOwner);
|
||||
@ -638,40 +652,6 @@ void AdventureMapInterface::onTileHovered(const int3 &targetPosition)
|
||||
GH.statusbar()->write(tileTooltipText);
|
||||
}
|
||||
|
||||
if(spellBeingCasted)
|
||||
{
|
||||
switch(spellBeingCasted->id)
|
||||
{
|
||||
case SpellID::SCUTTLE_BOAT:
|
||||
if(isValidAdventureSpellTarget(targetPosition, objAtTile, SpellID::SCUTTLE_BOAT))
|
||||
CCS->curh->set(Cursor::Map::SCUTTLE_BOAT);
|
||||
else
|
||||
CCS->curh->set(Cursor::Map::POINTER);
|
||||
return;
|
||||
case SpellID::DIMENSION_DOOR:
|
||||
if(isValidAdventureSpellTarget(targetPosition, objAtTile, SpellID::DIMENSION_DOOR))
|
||||
{
|
||||
if(VLC->settings()->getBoolean(EGameSettings::DIMENSION_DOOR_TRIGGERS_GUARDS))
|
||||
{
|
||||
auto isGuarded = LOCPLINT->cb->isTileGuardedAfterDimensionDoorUse(targetPosition, LOCPLINT->localState->getCurrentHero());
|
||||
if(isGuarded)
|
||||
{
|
||||
CCS->curh->set(Cursor::Map::T1_ATTACK);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
CCS->curh->set(Cursor::Map::TELEPORT);
|
||||
}
|
||||
else
|
||||
CCS->curh->set(Cursor::Map::POINTER);
|
||||
return;
|
||||
default:
|
||||
CCS->curh->set(Cursor::Map::POINTER);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(LOCPLINT->localState->getCurrentArmy()->ID == Obj::TOWN || GH.isKeyboardCtrlDown())
|
||||
{
|
||||
if(objAtTile)
|
||||
@ -945,34 +925,9 @@ void AdventureMapInterface::onScreenResize()
|
||||
activate();
|
||||
}
|
||||
|
||||
bool AdventureMapInterface::isValidAdventureSpellTarget(int3 targetPosition, const CGObjectInstance * topObjectAtTarget, SpellID spellId)
|
||||
bool AdventureMapInterface::isValidAdventureSpellTarget(int3 targetPosition)
|
||||
{
|
||||
int3 heroPosition = LOCPLINT->localState->getCurrentArmy()->getSightCenter();
|
||||
if (!isInScreenRange(heroPosition, targetPosition))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
spells::detail::ProblemImpl problem;
|
||||
|
||||
switch(spellId)
|
||||
{
|
||||
case SpellID::SCUTTLE_BOAT:
|
||||
{
|
||||
if(topObjectAtTarget && topObjectAtTarget->ID == Obj::BOAT)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
case SpellID::DIMENSION_DOOR:
|
||||
{
|
||||
const TerrainTile * t = LOCPLINT->cb->getTileForDimensionDoor(targetPosition, LOCPLINT->localState->getCurrentHero());
|
||||
|
||||
if(t && t->isClear(LOCPLINT->cb->getTile(heroPosition)))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
logGlobal->warn("Called AdventureMapInterface::isValidAdventureSpellTarget with unknown Spell ID!");
|
||||
return false;
|
||||
}
|
||||
return spellBeingCasted->getAdventureMechanics().canBeCastAt(problem, LOCPLINT->cb.get(), LOCPLINT->localState->getCurrentHero(), targetPosition);
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ private:
|
||||
void performSpellcasting(const int3 & castTarget);
|
||||
|
||||
/// performs clientside validation of valid targets for adventure spells
|
||||
bool isValidAdventureSpellTarget(int3 targetPosition, const CGObjectInstance * topObjectAtTarget, SpellID spellId);
|
||||
bool isValidAdventureSpellTarget(int3 targetPosition);
|
||||
|
||||
/// dim interface if some windows opened
|
||||
void dim(Canvas & to);
|
||||
|
@ -40,6 +40,7 @@
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/CGeneralTextHandler.h"
|
||||
#include "../../lib/spells/CSpellHandler.h"
|
||||
#include "../../lib/spells/ISpellMechanics.h"
|
||||
#include "../../lib/spells/Problem.h"
|
||||
#include "../../lib/GameConstants.h"
|
||||
|
||||
@ -653,6 +654,9 @@ void CSpellWindow::SpellArea::clickPressed(const Point & cursorPosition)
|
||||
owner->myInt->localState->spellbookSettings.spellbookLastPageAdvmap = owner->currentPage;
|
||||
});
|
||||
|
||||
spells::detail::ProblemImpl problem;
|
||||
if (mySpell->getAdventureMechanics().canBeCast(problem, LOCPLINT->cb.get(), owner->myHero))
|
||||
{
|
||||
if(mySpell->getTargetType() == spells::AimType::LOCATION)
|
||||
adventureInt->enterCastingMode(mySpell);
|
||||
else if(mySpell->getTargetType() == spells::AimType::NO_TARGET)
|
||||
@ -660,6 +664,16 @@ void CSpellWindow::SpellArea::clickPressed(const Point & cursorPosition)
|
||||
else
|
||||
logGlobal->error("Invalid spell target type");
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<std::string> texts;
|
||||
problem.getAll(texts);
|
||||
if(!texts.empty())
|
||||
LOCPLINT->showInfoDialog(texts.front());
|
||||
else
|
||||
LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.spellUnknownProblem"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -395,8 +395,10 @@
|
||||
"dimensionDoorOnlyToUncoveredTiles" : false,
|
||||
// if enabled, dimension door will hint regarding tile being incompatible terrain type, unlike H3 (water/land)
|
||||
"dimensionDoorExposesTerrainType" : false,
|
||||
// if enabled, attempt to use dimension door on incompatible terrain (water/land) will result in spending of mana, movement and casts per day (H3 behavior)
|
||||
"dimensionDoorFailureSpendsPoints" : true,
|
||||
// if enabled, dimension door will initiate a fight upon landing on tile adjacent to neutral creature
|
||||
"dimensionDoorTriggersGuards" : false,
|
||||
"dimensionDoorTriggersGuards" : true,
|
||||
// if enabled, dimension door can be used 1x per day, exception being 2x per day for XL+U or bigger maps (41472 tiles) + hero having expert air magic
|
||||
"dimensionDoorTournamentRulesLimit" : false
|
||||
},
|
||||
|
@ -287,22 +287,9 @@ std::vector<const CGObjectInstance*> CGameInfoCallback::getGuardingCreatures (in
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool CGameInfoCallback::isTileGuardedAfterDimensionDoorUse(int3 tile, const CGHeroInstance * castingHero) const
|
||||
bool CGameInfoCallback::isTileGuardedUnchecked(int3 tile) const
|
||||
{
|
||||
//for known tiles this is just potential convenience info, for tiles behind fog of war this info matches HotA but not H3 so make it accessible only with proper setting on
|
||||
bool canAccessInfo = false;
|
||||
|
||||
if(isVisible(tile))
|
||||
canAccessInfo = true;
|
||||
else if(VLC->settings()->getBoolean(EGameSettings::DIMENSION_DOOR_TRIGGERS_GUARDS)
|
||||
&& isInScreenRange(castingHero->getSightCenter(), tile)
|
||||
&& castingHero->canCastThisSpell(static_cast<SpellID>(SpellID::DIMENSION_DOOR).toSpell()))
|
||||
canAccessInfo = true; //TODO: check if available casts > 0, before adding that check make dimension door daily limit popup trigger on spell pick
|
||||
|
||||
if(canAccessInfo)
|
||||
return !gs->guardingCreatures(tile).empty();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CGameInfoCallback::getHeroInfo(const CGObjectInstance * hero, InfoAboutHero & dest, const CGObjectInstance * selectedObject) const
|
||||
@ -528,40 +515,12 @@ const TerrainTile * CGameInfoCallback::getTile(int3 tile, bool verbose) const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const TerrainTile * CGameInfoCallback::getTileForDimensionDoor(int3 tile, const CGHeroInstance * castingHero) const
|
||||
const TerrainTile * CGameInfoCallback::getTileUnchecked(int3 tile) const
|
||||
{
|
||||
auto outputTile = getTile(tile, false);
|
||||
if (isInTheMap(tile))
|
||||
return &gs->map->getTile(tile);
|
||||
|
||||
if(outputTile != nullptr)
|
||||
return outputTile;
|
||||
|
||||
bool allowOnlyToUncoveredTiles = VLC->settings()->getBoolean(EGameSettings::DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES);
|
||||
|
||||
if(!allowOnlyToUncoveredTiles)
|
||||
{
|
||||
if(castingHero->canCastThisSpell(static_cast<SpellID>(SpellID::DIMENSION_DOOR).toSpell())
|
||||
&& isInScreenRange(castingHero->getSightCenter(), tile))
|
||||
{ //TODO: check if available casts > 0, before adding that check make dimension door daily limit popup trigger on spell pick
|
||||
//we are allowed to get basic blocked/water invisible nearby tile date when casting DD spell
|
||||
TerrainTile targetTile = gs->map->getTile(tile);
|
||||
auto obfuscatedTile = std::make_shared<TerrainTile>();
|
||||
obfuscatedTile->visitable = false;
|
||||
obfuscatedTile->blocked = targetTile.blocked || targetTile.visitable;
|
||||
|
||||
if(targetTile.blocked || targetTile.visitable)
|
||||
obfuscatedTile->terType = VLC->terrainTypeHandler->getById(TerrainId::ROCK);
|
||||
else if(!VLC->settings()->getBoolean(EGameSettings::DIMENSION_DOOR_EXPOSES_TERRAIN_TYPE))
|
||||
obfuscatedTile->terType = gs->map->getTile(castingHero->getSightCenter()).terType;
|
||||
else
|
||||
obfuscatedTile->terType = targetTile.isWater()
|
||||
? VLC->terrainTypeHandler->getById(TerrainId::WATER)
|
||||
: VLC->terrainTypeHandler->getById(TerrainId::GRASS);
|
||||
|
||||
outputTile = obfuscatedTile.get();
|
||||
}
|
||||
}
|
||||
|
||||
return outputTile;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
EDiggingStatus CGameInfoCallback::getTileDigStatus(int3 tile, bool verbose) const
|
||||
|
@ -193,11 +193,11 @@ public:
|
||||
//map
|
||||
virtual int3 guardingCreaturePosition (int3 pos) const;
|
||||
virtual std::vector<const CGObjectInstance*> getGuardingCreatures (int3 pos) const;
|
||||
virtual bool isTileGuardedAfterDimensionDoorUse(int3 tile, const CGHeroInstance * castingHero) const;
|
||||
virtual bool isTileGuardedUnchecked(int3 tile) const;
|
||||
virtual const CMapHeader * getMapHeader()const;
|
||||
virtual int3 getMapSize() const; //returns size of map - z is 1 for one - level map and 2 for two level map
|
||||
virtual const TerrainTile * getTile(int3 tile, bool verbose = true) const;
|
||||
virtual const TerrainTile * getTileForDimensionDoor(int3 tile, const CGHeroInstance * castingHero) const;
|
||||
virtual const TerrainTile * getTileUnchecked(int3 tile) const;
|
||||
virtual std::shared_ptr<const boost::multi_array<TerrainTile*, 3>> getAllVisibleTiles() const;
|
||||
virtual bool isInTheMap(const int3 &pos) const;
|
||||
virtual void getVisibleTilesInRange(std::unordered_set<int3> &tiles, int3 pos, int radious, int3::EDistanceFormula distanceFormula = int3::DIST_2D) const;
|
||||
|
@ -105,6 +105,7 @@ void GameSettings::load(const JsonNode & input)
|
||||
{EGameSettings::PATHFINDER_ORIGINAL_FLY_RULES, "pathfinder", "originalFlyRules" },
|
||||
{EGameSettings::DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES, "spells", "dimensionDoorOnlyToUncoveredTiles"},
|
||||
{EGameSettings::DIMENSION_DOOR_EXPOSES_TERRAIN_TYPE, "spells", "dimensionDoorExposesTerrainType" },
|
||||
{EGameSettings::DIMENSION_DOOR_FAILURE_SPENDS_POINTS, "spells", "dimensionDoorFailureSpendsPoints" },
|
||||
{EGameSettings::DIMENSION_DOOR_TRIGGERS_GUARDS, "spells", "dimensionDoorTriggersGuards" },
|
||||
{EGameSettings::DIMENSION_DOOR_TOURNAMENT_RULES_LIMIT, "spells", "dimensionDoorTournamentRulesLimit"},
|
||||
{EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP, "towns", "buildingsPerTurnCap" },
|
||||
|
@ -72,6 +72,7 @@ enum class EGameSettings
|
||||
COMBAT_ONE_HEX_TRIGGERS_OBSTACLES,
|
||||
DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES,
|
||||
DIMENSION_DOOR_EXPOSES_TERRAIN_TYPE,
|
||||
DIMENSION_DOOR_FAILURE_SPENDS_POINTS,
|
||||
DIMENSION_DOOR_TRIGGERS_GUARDS,
|
||||
DIMENSION_DOOR_TOURNAMENT_RULES_LIMIT,
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "AdventureSpellMechanics.h"
|
||||
|
||||
#include "CSpellHandler.h"
|
||||
#include "Problem.h"
|
||||
|
||||
#include "../CGameInfoCallback.h"
|
||||
#include "../CPlayerState.h"
|
||||
@ -32,37 +33,52 @@ AdventureSpellMechanics::AdventureSpellMechanics(const CSpell * s):
|
||||
{
|
||||
}
|
||||
|
||||
bool AdventureSpellMechanics::adventureCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
|
||||
bool AdventureSpellMechanics::canBeCast(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster) const
|
||||
{
|
||||
if(!owner->isAdventure())
|
||||
{
|
||||
env->complain("Attempt to cast non adventure spell in adventure mode");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(const CGHeroInstance * heroCaster = dynamic_cast<const CGHeroInstance *>(parameters.caster))
|
||||
const auto * heroCaster = dynamic_cast<const CGHeroInstance *>(caster);
|
||||
|
||||
if (heroCaster)
|
||||
{
|
||||
if(heroCaster->inTownGarrison)
|
||||
{
|
||||
env->complain("Attempt to cast an adventure spell in town garrison");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto level = heroCaster->getSpellSchoolLevel(owner);
|
||||
const auto cost = owner->getCost(level);
|
||||
|
||||
if(!heroCaster->canCastThisSpell(owner))
|
||||
{
|
||||
env->complain("Hero cannot cast this spell!");
|
||||
return false;
|
||||
|
||||
if(heroCaster->mana < cost)
|
||||
return false;
|
||||
}
|
||||
|
||||
if(heroCaster->mana < cost)
|
||||
{
|
||||
env->complain("Hero doesn't have enough spell points to cast this spell!");
|
||||
return canBeCastImpl(problem, cb, caster);
|
||||
}
|
||||
|
||||
bool AdventureSpellMechanics::canBeCastAt(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const
|
||||
{
|
||||
return canBeCast(problem, cb, caster) && canBeCastAtImpl(problem, cb, caster, pos);
|
||||
}
|
||||
|
||||
bool AdventureSpellMechanics::canBeCastImpl(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AdventureSpellMechanics::canBeCastAtImpl(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AdventureSpellMechanics::adventureCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
|
||||
{
|
||||
spells::detail::ProblemImpl problem;
|
||||
|
||||
if (!canBeCastAt(problem, env->getCb(), parameters.caster, parameters.pos))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ESpellCastResult result = beginCast(env, parameters);
|
||||
|
||||
@ -138,35 +154,34 @@ SummonBoatMechanics::SummonBoatMechanics(const CSpell * s):
|
||||
{
|
||||
}
|
||||
|
||||
ESpellCastResult SummonBoatMechanics::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
|
||||
bool SummonBoatMechanics::canBeCastImpl(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster) const
|
||||
{
|
||||
if(!parameters.caster->getHeroCaster())
|
||||
if(!caster->getHeroCaster())
|
||||
return false;
|
||||
|
||||
if(caster->getHeroCaster()->boat)
|
||||
{
|
||||
env->complain("Not a hero caster!");
|
||||
return ESpellCastResult::ERROR;
|
||||
MetaString message = MetaString::createFromTextID("core.genrltxt.333");
|
||||
caster->getCasterName(message);
|
||||
problem.add(std::move(message));
|
||||
return false;
|
||||
}
|
||||
|
||||
if(parameters.caster->getHeroCaster()->boat)
|
||||
{
|
||||
InfoWindow iw;
|
||||
iw.player = parameters.caster->getCasterOwner();
|
||||
iw.text.appendLocalString(EMetaText::GENERAL_TXT, 333);//%s is already in boat
|
||||
parameters.caster->getCasterName(iw.text);
|
||||
env->apply(&iw);
|
||||
return ESpellCastResult::CANCEL;
|
||||
}
|
||||
|
||||
int3 summonPos = parameters.caster->getHeroCaster()->bestLocation();
|
||||
int3 summonPos = caster->getHeroCaster()->bestLocation();
|
||||
|
||||
if(summonPos.x < 0)
|
||||
{
|
||||
InfoWindow iw;
|
||||
iw.player = parameters.caster->getCasterOwner();
|
||||
iw.text.appendLocalString(EMetaText::GENERAL_TXT, 334);//There is no place to put the boat.
|
||||
env->apply(&iw);
|
||||
return ESpellCastResult::CANCEL;
|
||||
MetaString message = MetaString::createFromTextID("core.genrltxt.334");
|
||||
caster->getCasterName(message);
|
||||
problem.add(std::move(message));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ESpellCastResult SummonBoatMechanics::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
|
||||
{
|
||||
const auto schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
|
||||
|
||||
//check if spell works at all
|
||||
@ -200,6 +215,8 @@ ESpellCastResult SummonBoatMechanics::applyAdventureEffects(SpellCastEnvironment
|
||||
}
|
||||
}
|
||||
|
||||
int3 summonPos = parameters.caster->getHeroCaster()->bestLocation();
|
||||
|
||||
if(nullptr != nearest) //we found boat to summon
|
||||
{
|
||||
ChangeObjPos cop;
|
||||
@ -214,6 +231,7 @@ ESpellCastResult SummonBoatMechanics::applyAdventureEffects(SpellCastEnvironment
|
||||
iw.player = parameters.caster->getCasterOwner();
|
||||
iw.text.appendLocalString(EMetaText::GENERAL_TXT, 335); //There are no boats to summon.
|
||||
env->apply(&iw);
|
||||
return ESpellCastResult::ERROR;
|
||||
}
|
||||
else //create boat
|
||||
{
|
||||
@ -233,6 +251,29 @@ ScuttleBoatMechanics::ScuttleBoatMechanics(const CSpell * s):
|
||||
{
|
||||
}
|
||||
|
||||
bool ScuttleBoatMechanics::canBeCastAtImpl(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const
|
||||
{
|
||||
if(!cb->isInTheMap(pos))
|
||||
return false;
|
||||
|
||||
if (caster->getHeroCaster())
|
||||
{
|
||||
int3 casterPosition = caster->getHeroCaster()->getSightCenter();
|
||||
|
||||
if(!isInScreenRange(casterPosition, pos))
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!cb->isVisible(pos, caster->getCasterOwner()))
|
||||
return false;
|
||||
|
||||
const TerrainTile * t = cb->getTile(pos);
|
||||
if(!t || t->visitableObjects.empty() || t->visitableObjects.back()->ID != Obj::BOAT)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ESpellCastResult ScuttleBoatMechanics::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
|
||||
{
|
||||
const auto schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
|
||||
@ -247,36 +288,11 @@ ESpellCastResult ScuttleBoatMechanics::applyAdventureEffects(SpellCastEnvironmen
|
||||
return ESpellCastResult::OK;
|
||||
}
|
||||
|
||||
if(!env->getMap()->isInTheMap(parameters.pos))
|
||||
{
|
||||
env->complain("Invalid dst tile for scuttle!");
|
||||
return ESpellCastResult::ERROR;
|
||||
}
|
||||
|
||||
int3 casterPosition = parameters.caster->getHeroCaster()->getSightCenter();
|
||||
|
||||
if(!isInScreenRange(casterPosition, parameters.pos))
|
||||
{
|
||||
env->complain("Attempting to cast Scuttle Boat outside screen range!");
|
||||
return ESpellCastResult::ERROR;
|
||||
}
|
||||
|
||||
if(!env->getCb()->isVisible(parameters.pos, parameters.caster->getCasterOwner()))
|
||||
{
|
||||
env->complain("Attempting to cast Scuttle Boat at invisible tile!");
|
||||
return ESpellCastResult::ERROR;
|
||||
}
|
||||
|
||||
const TerrainTile *t = &env->getMap()->getTile(parameters.pos);
|
||||
if(t->visitableObjects.empty() || t->visitableObjects.back()->ID != Obj::BOAT)
|
||||
{
|
||||
env->complain("There is no boat to scuttle!");
|
||||
return ESpellCastResult::ERROR;
|
||||
}
|
||||
const TerrainTile & t = env->getMap()->getTile(parameters.pos);
|
||||
|
||||
RemoveObject ro;
|
||||
ro.initiator = parameters.caster->getCasterOwner();
|
||||
ro.objectID = t->visitableObjects.back()->id;
|
||||
ro.objectID = t.visitableObjects.back()->id;
|
||||
env->apply(&ro);
|
||||
return ESpellCastResult::OK;
|
||||
}
|
||||
@ -287,71 +303,31 @@ DimensionDoorMechanics::DimensionDoorMechanics(const CSpell * s):
|
||||
{
|
||||
}
|
||||
|
||||
ESpellCastResult DimensionDoorMechanics::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
|
||||
bool DimensionDoorMechanics::canBeCastImpl(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster) const
|
||||
{
|
||||
if(!env->getMap()->isInTheMap(parameters.pos))
|
||||
if(!caster->getHeroCaster())
|
||||
return false;
|
||||
|
||||
if(caster->getHeroCaster()->movementPointsRemaining() <= 0) //unlike town portal non-zero MP is enough
|
||||
{
|
||||
env->complain("Destination is out of map!");
|
||||
return ESpellCastResult::ERROR;
|
||||
problem.add(MetaString::createFromTextID("core.genrltxt.125"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!parameters.caster->getHeroCaster())
|
||||
{
|
||||
env->complain("Not a hero caster!");
|
||||
return ESpellCastResult::ERROR;
|
||||
}
|
||||
|
||||
int3 casterPosition = parameters.caster->getHeroCaster()->getSightCenter();
|
||||
|
||||
const TerrainTile * dest = env->getCb()->getTile(parameters.pos);
|
||||
const TerrainTile * curr = env->getCb()->getTile(casterPosition);
|
||||
|
||||
if(nullptr == dest)
|
||||
{
|
||||
env->complain("Destination tile doesn't exist!");
|
||||
return ESpellCastResult::ERROR;
|
||||
}
|
||||
|
||||
if(nullptr == curr)
|
||||
{
|
||||
env->complain("Source tile doesn't exist!");
|
||||
return ESpellCastResult::ERROR;
|
||||
}
|
||||
|
||||
if(parameters.caster->getHeroCaster()->movementPointsRemaining() <= 0) //unlike town portal non-zero MP is enough
|
||||
{
|
||||
env->complain("Hero needs movement points to cast Dimension Door!");
|
||||
return ESpellCastResult::ERROR;
|
||||
}
|
||||
|
||||
if(!isInScreenRange(casterPosition, parameters.pos))
|
||||
{
|
||||
env->complain("Attempting to cast Dimension Door outside screen range!");
|
||||
return ESpellCastResult::ERROR;
|
||||
}
|
||||
|
||||
if(VLC->settings()->getBoolean(EGameSettings::DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES))
|
||||
{
|
||||
if(!env->getCb()->isVisible(parameters.pos, parameters.caster->getCasterOwner()))
|
||||
{
|
||||
env->complain("Attempting to cast Dimension Door inside Fog of War with limitation toggled on!");
|
||||
return ESpellCastResult::ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
const auto schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
|
||||
const int movementCost = GameConstants::BASE_MOVEMENT_COST * ((schoolLevel >= 3) ? 2 : 3);
|
||||
const auto schoolLevel = caster->getSpellSchoolLevel(owner);
|
||||
|
||||
std::stringstream cachingStr;
|
||||
cachingStr << "source_" << vstd::to_underlying(BonusSource::SPELL_EFFECT) << "id_" << owner->id.num;
|
||||
|
||||
int castsAlreadyPerformedThisTurn = parameters.caster->getHeroCaster()->getBonuses(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(owner->id)), Selector::all, cachingStr.str())->size();
|
||||
int castsAlreadyPerformedThisTurn = caster->getHeroCaster()->getBonuses(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(owner->id)), Selector::all, cachingStr.str())->size();
|
||||
int castsLimit = owner->getLevelPower(schoolLevel);
|
||||
|
||||
bool isTournamentRulesLimitEnabled = VLC->settings()->getBoolean(EGameSettings::DIMENSION_DOOR_TOURNAMENT_RULES_LIMIT);
|
||||
if(isTournamentRulesLimitEnabled)
|
||||
{
|
||||
bool meetsTournamentRulesTwoCastsRequirements = env->getMap()->width * env->getMap()->height * env->getMap()->levels() >= GameConstants::TOURNAMENT_RULES_DD_MAP_TILES_THRESHOLD
|
||||
int3 mapSize = cb->getMapSize();
|
||||
|
||||
bool meetsTournamentRulesTwoCastsRequirements = mapSize.x * mapSize.y * mapSize.z >= GameConstants::TOURNAMENT_RULES_DD_MAP_TILES_THRESHOLD
|
||||
&& schoolLevel == MasteryLevel::EXPERT;
|
||||
|
||||
castsLimit = meetsTournamentRulesTwoCastsRequirements ? 2 : 1;
|
||||
@ -359,31 +335,90 @@ ESpellCastResult DimensionDoorMechanics::applyAdventureEffects(SpellCastEnvironm
|
||||
|
||||
if(castsAlreadyPerformedThisTurn >= castsLimit) //limit casts per turn
|
||||
{
|
||||
InfoWindow iw;
|
||||
iw.player = parameters.caster->getCasterOwner();
|
||||
iw.text.appendLocalString(EMetaText::GENERAL_TXT, 338); //%s is not skilled enough to cast this spell again today.
|
||||
parameters.caster->getCasterName(iw.text);
|
||||
env->apply(&iw);
|
||||
return ESpellCastResult::CANCEL;
|
||||
MetaString message = MetaString::createFromTextID("core.genrltxt.338");
|
||||
caster->getCasterName(message);
|
||||
problem.add(std::move(message));
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!dest->isClear(curr)) //wrong dest tile
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DimensionDoorMechanics::canBeCastAtImpl(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const
|
||||
{
|
||||
if(!cb->isInTheMap(pos))
|
||||
return false;
|
||||
|
||||
if(VLC->settings()->getBoolean(EGameSettings::DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES))
|
||||
{
|
||||
if(!cb->isVisible(pos, caster->getCasterOwner()))
|
||||
return false;
|
||||
}
|
||||
|
||||
int3 casterPosition = caster->getHeroCaster()->getSightCenter();
|
||||
|
||||
const TerrainTile * dest = cb->getTileUnchecked(pos);
|
||||
const TerrainTile * curr = cb->getTileUnchecked(casterPosition);
|
||||
|
||||
if(!dest)
|
||||
return false;
|
||||
|
||||
if(!curr)
|
||||
return false;
|
||||
|
||||
if(!isInScreenRange(casterPosition, pos))
|
||||
return false;
|
||||
|
||||
if(VLC->settings()->getBoolean(EGameSettings::DIMENSION_DOOR_EXPOSES_TERRAIN_TYPE))
|
||||
{
|
||||
if(!dest->isClear(curr))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dest->blocked)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ESpellCastResult DimensionDoorMechanics::applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
|
||||
{
|
||||
const auto schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
|
||||
const int movementCost = GameConstants::BASE_MOVEMENT_COST * ((schoolLevel >= 3) ? 2 : 3);
|
||||
|
||||
int3 casterPosition = parameters.caster->getHeroCaster()->getSightCenter();
|
||||
const TerrainTile * dest = env->getCb()->getTile(parameters.pos);
|
||||
const TerrainTile * curr = env->getCb()->getTile(casterPosition);
|
||||
|
||||
if(!dest->isClear(curr))
|
||||
{
|
||||
InfoWindow iw;
|
||||
iw.player = parameters.caster->getCasterOwner();
|
||||
iw.text.appendLocalString(EMetaText::GENERAL_TXT, 70); //Dimension Door failed!
|
||||
|
||||
// tile is either blocked or not possible to move (e.g. water <-> land)
|
||||
if(VLC->settings()->getBoolean(EGameSettings::DIMENSION_DOOR_FAILURE_SPENDS_POINTS))
|
||||
{
|
||||
// SOD: DD to such "wrong" terrain results in mana and move points spending, but fails to move hero
|
||||
iw.text = MetaString::createFromTextID("core.genrltxt.70"); // Dimension Door failed!
|
||||
env->apply(&iw);
|
||||
// no return - resources will be spent
|
||||
}
|
||||
else
|
||||
{
|
||||
// HotA: game will show error message without taking mana or move points, even when DD into terra incognita
|
||||
iw.text = MetaString::createFromTextID("vcmi.dimensionDoor.seaToLandError");
|
||||
env->apply(&iw);
|
||||
return ESpellCastResult::CANCEL;
|
||||
}
|
||||
}
|
||||
|
||||
GiveBonus gb;
|
||||
gb.id = ObjectInstanceID(parameters.caster->getCasterUnitId());
|
||||
gb.bonus = Bonus(BonusDuration::ONE_DAY, BonusType::NONE, BonusSource::SPELL_EFFECT, 0, BonusSourceID(owner->id));
|
||||
env->apply(&gb);
|
||||
|
||||
|
||||
if(env->moveHero(ObjectInstanceID(parameters.caster->getCasterUnitId()), parameters.caster->getHeroCaster()->convertFromVisitablePos(parameters.pos), true))
|
||||
{
|
||||
SetMovePoints smp;
|
||||
smp.hid = ObjectInstanceID(parameters.caster->getCasterUnitId());
|
||||
if(movementCost < static_cast<int>(parameters.caster->getHeroCaster()->movementPointsRemaining()))
|
||||
@ -391,7 +426,10 @@ ESpellCastResult DimensionDoorMechanics::applyAdventureEffects(SpellCastEnvironm
|
||||
else
|
||||
smp.val = 0;
|
||||
env->apply(&smp);
|
||||
}
|
||||
|
||||
if(dest->isClear(curr))
|
||||
env->moveHero(ObjectInstanceID(parameters.caster->getCasterUnitId()), parameters.caster->getHeroCaster()->convertFromVisitablePos(parameters.pos), true);
|
||||
|
||||
return ESpellCastResult::OK;
|
||||
}
|
||||
|
||||
|
@ -24,45 +24,58 @@ enum class ESpellCastResult
|
||||
ERROR//internal error occurred
|
||||
};
|
||||
|
||||
class DLL_LINKAGE AdventureSpellMechanics : public IAdventureSpellMechanics
|
||||
class AdventureSpellMechanics : public IAdventureSpellMechanics
|
||||
{
|
||||
public:
|
||||
AdventureSpellMechanics(const CSpell * s);
|
||||
|
||||
bool canBeCast(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster) const override final;
|
||||
bool canBeCastAt(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const override final;
|
||||
|
||||
bool adventureCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override final;
|
||||
protected:
|
||||
///actual adventure cast implementation
|
||||
virtual ESpellCastResult applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const;
|
||||
virtual ESpellCastResult beginCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const;
|
||||
virtual bool canBeCastImpl(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster) const;
|
||||
virtual bool canBeCastAtImpl(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const;
|
||||
|
||||
void performCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const;
|
||||
void endCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters, const ESpellCastResult result) const;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE SummonBoatMechanics : public AdventureSpellMechanics
|
||||
class SummonBoatMechanics final : public AdventureSpellMechanics
|
||||
{
|
||||
public:
|
||||
SummonBoatMechanics(const CSpell * s);
|
||||
protected:
|
||||
bool canBeCastImpl(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster) const override;
|
||||
|
||||
ESpellCastResult applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE ScuttleBoatMechanics : public AdventureSpellMechanics
|
||||
class ScuttleBoatMechanics final : public AdventureSpellMechanics
|
||||
{
|
||||
public:
|
||||
ScuttleBoatMechanics(const CSpell * s);
|
||||
protected:
|
||||
bool canBeCastAtImpl(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const override;
|
||||
|
||||
ESpellCastResult applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE DimensionDoorMechanics : public AdventureSpellMechanics
|
||||
class DimensionDoorMechanics final : public AdventureSpellMechanics
|
||||
{
|
||||
public:
|
||||
DimensionDoorMechanics(const CSpell * s);
|
||||
protected:
|
||||
bool canBeCastImpl(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster) const override;
|
||||
bool canBeCastAtImpl(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const override;
|
||||
|
||||
ESpellCastResult applyAdventureEffects(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE TownPortalMechanics : public AdventureSpellMechanics
|
||||
class TownPortalMechanics final : public AdventureSpellMechanics
|
||||
{
|
||||
public:
|
||||
TownPortalMechanics(const CSpell * s);
|
||||
@ -75,7 +88,7 @@ private:
|
||||
std::vector <const CGTownInstance*> getPossibleTowns(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE ViewMechanics : public AdventureSpellMechanics
|
||||
class ViewMechanics : public AdventureSpellMechanics
|
||||
{
|
||||
public:
|
||||
ViewMechanics(const CSpell * s);
|
||||
@ -85,7 +98,7 @@ protected:
|
||||
virtual bool showTerrain(const int32_t spellLevel) const = 0;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE ViewAirMechanics : public ViewMechanics
|
||||
class ViewAirMechanics final : public ViewMechanics
|
||||
{
|
||||
public:
|
||||
ViewAirMechanics(const CSpell * s);
|
||||
@ -94,7 +107,7 @@ protected:
|
||||
bool showTerrain(const int32_t spellLevel) const override;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE ViewEarthMechanics : public ViewMechanics
|
||||
class ViewEarthMechanics final : public ViewMechanics
|
||||
{
|
||||
public:
|
||||
ViewEarthMechanics(const CSpell * s);
|
||||
|
@ -491,6 +491,11 @@ void CSpell::setupMechanics()
|
||||
adventureMechanics = IAdventureSpellMechanics::createMechanics(this);
|
||||
}
|
||||
|
||||
const IAdventureSpellMechanics & CSpell::getAdventureMechanics() const
|
||||
{
|
||||
return *adventureMechanics;
|
||||
}
|
||||
|
||||
std::unique_ptr<spells::Mechanics> CSpell::battleMechanics(const spells::IBattleCast * event) const
|
||||
{
|
||||
return mechanics->create(event);
|
||||
|
@ -290,6 +290,7 @@ public://internal, for use only by Mechanics classes
|
||||
///returns raw damage or healed HP
|
||||
int64_t calculateRawEffectValue(int32_t effectLevel, int32_t basePowerMultiplier, int32_t levelPowerMultiplier) const;
|
||||
|
||||
const IAdventureSpellMechanics & getAdventureMechanics() const;
|
||||
std::unique_ptr<spells::Mechanics> battleMechanics(const spells::IBattleCast * event) const;
|
||||
private:
|
||||
void setIsOffensive(const bool val);
|
||||
|
@ -355,6 +355,9 @@ public:
|
||||
IAdventureSpellMechanics(const CSpell * s);
|
||||
virtual ~IAdventureSpellMechanics() = default;
|
||||
|
||||
virtual bool canBeCast(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster) const = 0;
|
||||
virtual bool canBeCastAt(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const = 0;
|
||||
|
||||
virtual bool adventureCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const = 0;
|
||||
|
||||
static std::unique_ptr<IAdventureSpellMechanics> createMechanics(const CSpell * s);
|
||||
|
Loading…
Reference in New Issue
Block a user