1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +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;
int3 selPos = LOCPLINT->localState->getCurrentArmy()->getSightCenter();
if(spellBeingCasted)
{
assert(shortcuts->optionSpellcasting());
if (!isInScreenRange(selPos, targetPosition))
return;
const TerrainTile *heroTile = LOCPLINT->cb->getTile(selPos);
switch(spellBeingCasted->id)
{
case SpellID::SCUTTLE_BOAT: //Scuttle Boat
if(topBlocking && topBlocking->ID == Obj::BOAT)
case SpellID::SCUTTLE_BOAT:
if(isValidAdventureSpellTarget(targetPosition, topBlocking, SpellID::SCUTTLE_BOAT))
performSpellcasting(targetPosition);
break;
case SpellID::DIMENSION_DOOR:
const TerrainTile * targetTile = LOCPLINT->cb->getTileForDimensionDoor(targetPosition, LOCPLINT->localState->getCurrentHero());
if(targetTile && targetTile->isClear(heroTile))
if(isValidAdventureSpellTarget(targetPosition, topBlocking, SpellID::DIMENSION_DOOR))
performSpellcasting(targetPosition);
break;
default:
break;
}
return;
}
//check if we can select this object
@ -610,7 +605,7 @@ void AdventureMapInterface::onTileHovered(const int3 &targetPosition)
if(!shortcuts->optionMapViewActive())
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())
return;
@ -645,35 +640,23 @@ void AdventureMapInterface::onTileHovered(const int3 &targetPosition)
if(spellBeingCasted)
{
int3 heroPosition = LOCPLINT->localState->getCurrentArmy()->getSightCenter();
if (!isInScreenRange(heroPosition, targetPosition))
{
CCS->curh->set(Cursor::Map::POINTER);
return;
}
switch(spellBeingCasted->id)
{
case SpellID::SCUTTLE_BOAT:
{
if(objAtTile && objAtTile->ID == Obj::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:
{
const TerrainTile * t = LOCPLINT->cb->getTileForDimensionDoor(targetPosition, LOCPLINT->localState->getCurrentHero());
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
if(isValidAdventureSpellTarget(targetPosition, objAtTile, SpellID::DIMENSION_DOOR))
CCS->curh->set(Cursor::Map::TELEPORT);
else
CCS->curh->set(Cursor::Map::POINTER);
return;
}
default:
break;
CCS->curh->set(Cursor::Map::POINTER);
return;
}
}
@ -949,3 +932,35 @@ void AdventureMapInterface::onScreenResize()
if (widgetActive)
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
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
void dim(Canvas & to);

View File

@ -313,7 +313,7 @@
"goodLuckDice" : [ 24, 12, 8 ],
"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,
// limit of damage increase that can be achieved by overpowering attack points
"attackPointDamageFactorCap": 4.0,
@ -391,7 +391,10 @@
"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" :

View File

@ -522,16 +522,20 @@ const TerrainTile * CGameInfoCallback::getTileForDimensionDoor(int3 tile, const
if(!allowOnlyToUncoveredTiles)
{
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
TerrainTile targetTile = gs->map->getTile(tile);
auto obfuscatedTile = std::make_shared<TerrainTile>();
obfuscatedTile->visitable = false;
obfuscatedTile->blocked = targetTile.blocked || targetTile.visitable;
obfuscatedTile->terType = (targetTile.blocked || targetTile.visitable)
? VLC->terrainTypeHandler->getById(TerrainId::ROCK)
: targetTile.isWater()
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);

View File

@ -104,6 +104,7 @@ void GameSettings::load(const JsonNode & input)
{EGameSettings::PATHFINDER_USE_WHIRLPOOL, "pathfinder", "useWhirlpool" },
{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::TOWNS_BUILDINGS_PER_TURN_CAP, "towns", "buildingsPerTurnCap" },
{EGameSettings::TOWNS_STARTING_DWELLING_CHANCES, "towns", "startingDwellingChances" },
};

View File

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

View File

@ -17,6 +17,7 @@
#include "../CGameInfoCallback.h"
#include "../CPlayerState.h"
#include "../CRandomGenerator.h"
#include "../GameSettings.h"
#include "../mapObjects/CGHeroInstance.h"
#include "../mapObjects/CGTownInstance.h"
#include "../mapObjects/MiscObjects.h"
@ -252,7 +253,20 @@ ESpellCastResult ScuttleBoatMechanics::applyAdventureEffects(SpellCastEnvironmen
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);
if(t->visitableObjects.empty() || t->visitableObjects.back()->ID != Obj::BOAT)
{
@ -287,8 +301,10 @@ ESpellCastResult DimensionDoorMechanics::applyAdventureEffects(SpellCastEnvironm
return ESpellCastResult::ERROR;
}
int3 casterPosition = parameters.caster->getHeroCaster()->getSightCenter();
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)
{
@ -308,6 +324,21 @@ ESpellCastResult DimensionDoorMechanics::applyAdventureEffects(SpellCastEnvironm
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);
@ -324,19 +355,22 @@ ESpellCastResult DimensionDoorMechanics::applyAdventureEffects(SpellCastEnvironm
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
{
InfoWindow iw;
iw.player = parameters.caster->getCasterOwner();
iw.text.appendLocalString(EMetaText::GENERAL_TXT, 70); //Dimension Door failed!
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;
smp.hid = ObjectInstanceID(parameters.caster->getCasterUnitId());