1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-26 22:57:00 +02:00

Serverside validation, setting for terrain compatibility before cast etc

This commit is contained in:
Dydzio 2024-04-01 18:12:38 +02:00
parent e69c096f94
commit 3bb66de551
7 changed files with 113 additions and 52 deletions

View File

@ -516,29 +516,24 @@ void AdventureMapInterface::onTileLeftClicked(const int3 &targetPosition)
const CGObjectInstance *topBlocking = LOCPLINT->cb->isVisible(targetPosition) ? getActiveObject(targetPosition) : nullptr; const CGObjectInstance *topBlocking = LOCPLINT->cb->isVisible(targetPosition) ? getActiveObject(targetPosition) : nullptr;
int3 selPos = LOCPLINT->localState->getCurrentArmy()->getSightCenter();
if(spellBeingCasted) if(spellBeingCasted)
{ {
assert(shortcuts->optionSpellcasting()); assert(shortcuts->optionSpellcasting());
if (!isInScreenRange(selPos, targetPosition))
return;
const TerrainTile *heroTile = LOCPLINT->cb->getTile(selPos);
switch(spellBeingCasted->id) switch(spellBeingCasted->id)
{ {
case SpellID::SCUTTLE_BOAT: //Scuttle Boat case SpellID::SCUTTLE_BOAT:
if(topBlocking && topBlocking->ID == Obj::BOAT) if(isValidAdventureSpellTarget(targetPosition, topBlocking, SpellID::SCUTTLE_BOAT))
performSpellcasting(targetPosition); performSpellcasting(targetPosition);
break; break;
case SpellID::DIMENSION_DOOR: case SpellID::DIMENSION_DOOR:
const TerrainTile * targetTile = LOCPLINT->cb->getTileForDimensionDoor(targetPosition, LOCPLINT->localState->getCurrentHero()); if(isValidAdventureSpellTarget(targetPosition, topBlocking, SpellID::DIMENSION_DOOR))
if(targetTile && targetTile->isClear(heroTile))
performSpellcasting(targetPosition); performSpellcasting(targetPosition);
break; break;
default:
break;
} }
return; return;
} }
//check if we can select this object //check if we can select this object
@ -610,7 +605,7 @@ void AdventureMapInterface::onTileHovered(const int3 &targetPosition)
if(!shortcuts->optionMapViewActive()) if(!shortcuts->optionMapViewActive())
return; return;
//may occur just at the start of game (fake move before full intiialization) //may occur just at the start of game (fake move before full initialization)
if(!LOCPLINT->localState->getCurrentArmy()) if(!LOCPLINT->localState->getCurrentArmy())
return; return;
@ -645,35 +640,23 @@ void AdventureMapInterface::onTileHovered(const int3 &targetPosition)
if(spellBeingCasted) if(spellBeingCasted)
{ {
int3 heroPosition = LOCPLINT->localState->getCurrentArmy()->getSightCenter();
if (!isInScreenRange(heroPosition, targetPosition))
{
CCS->curh->set(Cursor::Map::POINTER);
return;
}
switch(spellBeingCasted->id) switch(spellBeingCasted->id)
{ {
case SpellID::SCUTTLE_BOAT: case SpellID::SCUTTLE_BOAT:
{ if(isValidAdventureSpellTarget(targetPosition, objAtTile, SpellID::SCUTTLE_BOAT))
if(objAtTile && objAtTile->ID == Obj::BOAT)
CCS->curh->set(Cursor::Map::SCUTTLE_BOAT); CCS->curh->set(Cursor::Map::SCUTTLE_BOAT);
else else
CCS->curh->set(Cursor::Map::POINTER); CCS->curh->set(Cursor::Map::POINTER);
return; return;
}
case SpellID::DIMENSION_DOOR: case SpellID::DIMENSION_DOOR:
{ if(isValidAdventureSpellTarget(targetPosition, objAtTile, SpellID::DIMENSION_DOOR))
const TerrainTile * t = LOCPLINT->cb->getTileForDimensionDoor(targetPosition, LOCPLINT->localState->getCurrentHero()); CCS->curh->set(Cursor::Map::TELEPORT);
if(t && t->isClear(LOCPLINT->cb->getTile(heroPosition))/* && isInScreenRange(hpos, mapPos)*/)
CCS->curh->set(Cursor::Map::TELEPORT); //TODO: something wrong with beyond east spell range border cursor on arrogance after TP-ing near underground portal on previous day
else else
CCS->curh->set(Cursor::Map::POINTER); CCS->curh->set(Cursor::Map::POINTER);
return; return;
}
default: default:
break; CCS->curh->set(Cursor::Map::POINTER);
return;
} }
} }
@ -949,3 +932,35 @@ void AdventureMapInterface::onScreenResize()
if (widgetActive) if (widgetActive)
activate(); activate();
} }
bool AdventureMapInterface::isValidAdventureSpellTarget(int3 targetPosition, const CGObjectInstance * topObjectAtTarget, SpellID spellId)
{
int3 heroPosition = LOCPLINT->localState->getCurrentArmy()->getSightCenter();
if (!isInScreenRange(heroPosition, targetPosition))
{
return false;
}
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;
}
}

View File

@ -92,6 +92,9 @@ private:
/// casts current spell at specified location /// casts current spell at specified location
void performSpellcasting(const int3 & castTarget); void performSpellcasting(const int3 & castTarget);
/// performs clientside validation of valid targets for adventure spells
bool isValidAdventureSpellTarget(int3 targetPosition, const CGObjectInstance * topObjectAtTarget, SpellID spellId);
/// dim interface if some windows opened /// dim interface if some windows opened
void dim(Canvas & to); void dim(Canvas & to);

View File

@ -313,7 +313,7 @@
"goodLuckDice" : [ 24, 12, 8 ], "goodLuckDice" : [ 24, 12, 8 ],
"badLuckDice" : [], "badLuckDice" : [],
// every 1 attack point damage influence in battle when attack points > defense points during creature attack // every 1 attack point damage influence in battle whe //TODO: test range, visibilityn attack points > defense points during creature attack
"attackPointDamageFactor": 0.05, "attackPointDamageFactor": 0.05,
// limit of damage increase that can be achieved by overpowering attack points // limit of damage increase that can be achieved by overpowering attack points
"attackPointDamageFactorCap": 4.0, "attackPointDamageFactorCap": 4.0,
@ -391,7 +391,10 @@
"spells": "spells":
{ {
"dimensionDoorOnlyToUncoveredTiles" : false // if enabled, dimension work doesn't work into tiles under Fog of War
"dimensionDoorOnlyToUncoveredTiles" : false,
// if enabled, dimension door will hint regarding tile being incompatible terrain type, unlike H3 (water/land)
"dimensionDoorExposesTerrainType" : false
}, },
"bonuses" : "bonuses" :

View File

@ -522,16 +522,20 @@ const TerrainTile * CGameInfoCallback::getTileForDimensionDoor(int3 tile, const
if(!allowOnlyToUncoveredTiles) if(!allowOnlyToUncoveredTiles)
{ {
if(castingHero->canCastThisSpell(static_cast<SpellID>(SpellID::DIMENSION_DOOR).toSpell()) if(castingHero->canCastThisSpell(static_cast<SpellID>(SpellID::DIMENSION_DOOR).toSpell())
&& isInScreenRange(castingHero->pos, tile)) //TODO: check if > 0 casts left && isInScreenRange(castingHero->getSightCenter(), tile)) //TODO: check if > 0 casts left
{ {
//we are allowed to get basic blocked/water invisible nearby tile date when casting DD spell //we are allowed to get basic blocked/water invisible nearby tile date when casting DD spell
TerrainTile targetTile = gs->map->getTile(tile); TerrainTile targetTile = gs->map->getTile(tile);
auto obfuscatedTile = std::make_shared<TerrainTile>(); auto obfuscatedTile = std::make_shared<TerrainTile>();
obfuscatedTile->visitable = false; obfuscatedTile->visitable = false;
obfuscatedTile->blocked = targetTile.blocked || targetTile.visitable; obfuscatedTile->blocked = targetTile.blocked || targetTile.visitable;
obfuscatedTile->terType = (targetTile.blocked || targetTile.visitable)
? VLC->terrainTypeHandler->getById(TerrainId::ROCK) if(targetTile.blocked || targetTile.visitable)
: targetTile.isWater() 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::WATER)
: VLC->terrainTypeHandler->getById(TerrainId::GRASS); : VLC->terrainTypeHandler->getById(TerrainId::GRASS);

View File

@ -104,6 +104,7 @@ void GameSettings::load(const JsonNode & input)
{EGameSettings::PATHFINDER_USE_WHIRLPOOL, "pathfinder", "useWhirlpool" }, {EGameSettings::PATHFINDER_USE_WHIRLPOOL, "pathfinder", "useWhirlpool" },
{EGameSettings::PATHFINDER_ORIGINAL_FLY_RULES, "pathfinder", "originalFlyRules" }, {EGameSettings::PATHFINDER_ORIGINAL_FLY_RULES, "pathfinder", "originalFlyRules" },
{EGameSettings::DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES, "spells", "dimensionDoorOnlyToUncoveredTiles"}, {EGameSettings::DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES, "spells", "dimensionDoorOnlyToUncoveredTiles"},
{EGameSettings::DIMENSION_DOOR_EXPOSES_TERRAIN_TYPE, "spells", "dimensionDoorExposesTerrainType" },
{EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP, "towns", "buildingsPerTurnCap" }, {EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP, "towns", "buildingsPerTurnCap" },
{EGameSettings::TOWNS_STARTING_DWELLING_CHANCES, "towns", "startingDwellingChances" }, {EGameSettings::TOWNS_STARTING_DWELLING_CHANCES, "towns", "startingDwellingChances" },
}; };

View File

@ -71,6 +71,7 @@ enum class EGameSettings
TOWNS_STARTING_DWELLING_CHANCES, TOWNS_STARTING_DWELLING_CHANCES,
COMBAT_ONE_HEX_TRIGGERS_OBSTACLES, COMBAT_ONE_HEX_TRIGGERS_OBSTACLES,
DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES, DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES,
DIMENSION_DOOR_EXPOSES_TERRAIN_TYPE,
OPTIONS_COUNT OPTIONS_COUNT
}; };

View File

@ -17,6 +17,7 @@
#include "../CGameInfoCallback.h" #include "../CGameInfoCallback.h"
#include "../CPlayerState.h" #include "../CPlayerState.h"
#include "../CRandomGenerator.h" #include "../CRandomGenerator.h"
#include "../GameSettings.h"
#include "../mapObjects/CGHeroInstance.h" #include "../mapObjects/CGHeroInstance.h"
#include "../mapObjects/CGTownInstance.h" #include "../mapObjects/CGTownInstance.h"
#include "../mapObjects/MiscObjects.h" #include "../mapObjects/MiscObjects.h"
@ -252,7 +253,20 @@ ESpellCastResult ScuttleBoatMechanics::applyAdventureEffects(SpellCastEnvironmen
return ESpellCastResult::ERROR; return ESpellCastResult::ERROR;
} }
//TODO: test range, visibility 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); const TerrainTile *t = &env->getMap()->getTile(parameters.pos);
if(t->visitableObjects.empty() || t->visitableObjects.back()->ID != Obj::BOAT) if(t->visitableObjects.empty() || t->visitableObjects.back()->ID != Obj::BOAT)
{ {
@ -287,8 +301,10 @@ ESpellCastResult DimensionDoorMechanics::applyAdventureEffects(SpellCastEnvironm
return ESpellCastResult::ERROR; return ESpellCastResult::ERROR;
} }
int3 casterPosition = parameters.caster->getHeroCaster()->getSightCenter();
const TerrainTile * dest = env->getCb()->getTile(parameters.pos); const TerrainTile * dest = env->getCb()->getTile(parameters.pos);
const TerrainTile * curr = env->getCb()->getTile(parameters.caster->getHeroCaster()->getSightCenter()); const TerrainTile * curr = env->getCb()->getTile(casterPosition);
if(nullptr == dest) if(nullptr == dest)
{ {
@ -308,6 +324,21 @@ ESpellCastResult DimensionDoorMechanics::applyAdventureEffects(SpellCastEnvironm
return ESpellCastResult::ERROR; 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 auto schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
const int movementCost = GameConstants::BASE_MOVEMENT_COST * ((schoolLevel >= 3) ? 2 : 3); const int movementCost = GameConstants::BASE_MOVEMENT_COST * ((schoolLevel >= 3) ? 2 : 3);
@ -324,19 +355,22 @@ ESpellCastResult DimensionDoorMechanics::applyAdventureEffects(SpellCastEnvironm
return ESpellCastResult::CANCEL; 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(!dest->isClear(curr)) //wrong dest tile if(!dest->isClear(curr)) //wrong dest tile
{ {
InfoWindow iw; InfoWindow iw;
iw.player = parameters.caster->getCasterOwner(); iw.player = parameters.caster->getCasterOwner();
iw.text.appendLocalString(EMetaText::GENERAL_TXT, 70); //Dimension Door failed! iw.text.appendLocalString(EMetaText::GENERAL_TXT, 70); //Dimension Door failed!
env->apply(&iw); env->apply(&iw);
return ESpellCastResult::CANCEL;
} }
else if(env->moveHero(ObjectInstanceID(parameters.caster->getCasterUnitId()), parameters.caster->getHeroCaster()->convertFromVisitablePos(parameters.pos), true))
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; SetMovePoints smp;
smp.hid = ObjectInstanceID(parameters.caster->getCasterUnitId()); smp.hid = ObjectInstanceID(parameters.caster->getCasterUnitId());