Kaynağa Gözat

Serverside validation, setting for terrain compatibility before cast etc

Dydzio 1 yıl önce
ebeveyn
işleme
3bb66de551

+ 51 - 36
client/adventureMap/AdventureMapInterface.cpp

@@ -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)
-					CCS->curh->set(Cursor::Map::SCUTTLE_BOAT);
-				else
-					CCS->curh->set(Cursor::Map::POINTER);
-				return;
-			}
+			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
-				else
-					CCS->curh->set(Cursor::Map::POINTER);
-				return;
-			}
+			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;
+	}
+}

+ 3 - 0
client/adventureMap/AdventureMapInterface.h

@@ -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);
 

+ 5 - 2
config/gameConfig.json

@@ -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" : 

+ 10 - 6
lib/CGameInfoCallback.cpp

@@ -522,18 +522,22 @@ 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()
-                    ? VLC->terrainTypeHandler->getById(TerrainId::WATER)
-                    : VLC->terrainTypeHandler->getById(TerrainId::GRASS);
+
+			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();
         }

+ 1 - 0
lib/GameSettings.cpp

@@ -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"          },
 	};

+ 1 - 0
lib/GameSettings.h

@@ -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
 };

+ 42 - 8
lib/spells/AdventureSpellMechanics.cpp

@@ -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());