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:
parent
e69c096f94
commit
3bb66de551
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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" :
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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" },
|
||||
};
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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());
|
||||
|
Loading…
Reference in New Issue
Block a user