Browse Source

Update docs, fix inconsistencies between config & code

Ivan Savenko 3 months ago
parent
commit
246010c13e

+ 5 - 4
client/windows/CSpellWindow.cpp

@@ -42,6 +42,7 @@
 #include "../../lib/callback/CCallback.h"
 #include "../../lib/callback/CCallback.h"
 #include "../../lib/spells/CSpellHandler.h"
 #include "../../lib/spells/CSpellHandler.h"
 #include "../../lib/spells/ISpellMechanics.h"
 #include "../../lib/spells/ISpellMechanics.h"
+#include "../../lib/spells/adventure/AdventureSpellEffect.h"
 #include "../../lib/spells/Problem.h"
 #include "../../lib/spells/Problem.h"
 #include "../../lib/spells/SpellSchoolHandler.h"
 #include "../../lib/spells/SpellSchoolHandler.h"
 #include "../../lib/texts/CGeneralTextHandler.h"
 #include "../../lib/texts/CGeneralTextHandler.h"
@@ -752,12 +753,12 @@ void CSpellWindow::SpellArea::clickPressed(const Point & cursorPosition)
 			spells::detail::ProblemImpl problem;
 			spells::detail::ProblemImpl problem;
 			if (mySpell->getAdventureMechanics().canBeCast(problem, GAME->interface()->cb.get(), owner->myHero))
 			if (mySpell->getAdventureMechanics().canBeCast(problem, GAME->interface()->cb.get(), owner->myHero))
 			{
 			{
-				if(mySpell->getTargetType() == spells::AimType::LOCATION)
+				const auto * rangeEffect = mySpell->getAdventureMechanics().getEffectAs<AdventureSpellRangedEffect>(owner->myHero);
+
+				if(rangeEffect != nullptr)
 					adventureInt->enterCastingMode(mySpell);
 					adventureInt->enterCastingMode(mySpell);
-				else if(mySpell->getTargetType() == spells::AimType::NO_TARGET)
-					owner->myInt->cb->castSpell(h, mySpell->id);
 				else
 				else
-					logGlobal->error("Invalid spell target type");
+					owner->myInt->cb->castSpell(h, mySpell->id);
 			}
 			}
 			else
 			else
 			{
 			{

+ 8 - 1
config/cursors.json

@@ -1,3 +1,11 @@
+/// This file can be modified via mods by creating file config/cursors.json
+/// File in mod only needs to contain new or modified entries
+/// Format:
+/// - "image" - path to image (e.g. png file) with image for this cursor. Overrides "animation" key.
+/// - "animation" - path to animation (.def or .json) with image for this cursor. Also requires "frame" or "animated" key
+/// - "frame" - index of frame inside animation file that should be used for this cursor
+/// - "animated" - if set to true, cursor will be animated using entire animation file
+/// - "pivotX" and "pivotY" - position inside image that should act as pointer tip. Mouse clicks would generate click event on pixel below this point
 {
 {
 	"mapPointer"          : { "pivotX" :  0, "pivotY" :  0, "animation" : "CRADVNTR", "frame" :  0 },
 	"mapPointer"          : { "pivotX" :  0, "pivotY" :  0, "animation" : "CRADVNTR", "frame" :  0 },
 	"mapHourglass"        : { "pivotX" :  0, "pivotY" :  0, "animation" : "CRADVNTR", "frame" :  1 },
 	"mapHourglass"        : { "pivotX" :  0, "pivotY" :  0, "animation" : "CRADVNTR", "frame" :  1 },
@@ -40,7 +48,6 @@
 	"mapScrollWest"       : { "pivotX" :  1, "pivotY" :  5, "animation" : "CRADVNTR", "frame" : 38 },
 	"mapScrollWest"       : { "pivotX" :  1, "pivotY" :  5, "animation" : "CRADVNTR", "frame" : 38 },
 	"mapScrollNorthWest"  : { "pivotX" :  2, "pivotY" :  1, "animation" : "CRADVNTR", "frame" : 39 },
 	"mapScrollNorthWest"  : { "pivotX" :  2, "pivotY" :  1, "animation" : "CRADVNTR", "frame" : 39 },
 	"mapDimensionDoor"    : { "pivotX" : 14, "pivotY" : 16, "animation" : "CRADVNTR", "frame" : 41 },
 	"mapDimensionDoor"    : { "pivotX" : 14, "pivotY" : 16, "animation" : "CRADVNTR", "frame" : 41 },
-	"mapDimensionDoorGuarded" : { "pivotX" : 14, "pivotY" : 16, "image" : "CursrA51" },
 	"mapScuttleBoat"      : { "pivotX" : 20, "pivotY" : 20, "animation" : "CRADVNTR", "frame" : 42 },
 	"mapScuttleBoat"      : { "pivotX" : 20, "pivotY" : 20, "animation" : "CRADVNTR", "frame" : 42 },
 	"combatBlocked"       : { "pivotX" : 12, "pivotY" : 12, "animation" : "CRCOMBAT", "frame" :  0},
 	"combatBlocked"       : { "pivotX" : 12, "pivotY" : 12, "animation" : "CRCOMBAT", "frame" :  0},
 	"combatMove"          : { "pivotX" : 10, "pivotY" : 14, "animation" : "CRCOMBAT", "frame" :  1},
 	"combatMove"          : { "pivotX" : 10, "pivotY" : 14, "animation" : "CRCOMBAT", "frame" :  1},

+ 4 - 5
config/spells/adventure.json

@@ -12,18 +12,17 @@
 				"adventureEffect" : {
 				"adventureEffect" : {
 					"type" : "summonBoat",
 					"type" : "summonBoat",
 					"castsPerDay" : 0,
 					"castsPerDay" : 0,
-					"useExistingBoat" : true,
-					"createNewBoat" : false
+					"useExistingBoat" : true
 				}
 				}
 			},
 			},
 			"advanced":{
 			"advanced":{
 				"adventureEffect" : {
 				"adventureEffect" : {
-					"createNewBoat" : true
+					"createdBoat" : "boatNecropolis"
 				}
 				}
 			},
 			},
 			"expert":{
 			"expert":{
 				"adventureEffect" : {
 				"adventureEffect" : {
-					"createNewBoat" : true
+					"createdBoat" : "boatNecropolis"
 				}
 				}
 			}
 			}
 		},
 		},
@@ -170,7 +169,7 @@
 						"mine" : true,
 						"mine" : true,
 						"abandonedMine" : true
 						"abandonedMine" : true
 					},
 					},
-					"terrain" : true
+					"showTerrain" : true
 				}
 				}
 			}
 			}
 		},
 		},

+ 180 - 2
docs/modders/Entities_Format/Spell_Format.md

@@ -241,8 +241,6 @@ TODO
 		"firstEffect": {[bonus format]},
 		"firstEffect": {[bonus format]},
 		"secondEffect": {[bonus format]}
 		"secondEffect": {[bonus format]}
 		//...
 		//...
-
-	
 	},
 	},
 
 
 	// DEPRECATED, please use "battleEffects" with timed effect and "cumulative" set to true instead
 	// DEPRECATED, please use "battleEffects" with timed effect and "cumulative" set to true instead
@@ -260,6 +258,11 @@ TODO
 		"mod:secondEffect": {[effect format]}
 		"mod:secondEffect": {[effect format]}
 		//...
 		//...
 	}
 	}
+	
+	/// See Configurable adventure map effects section below for detailed description
+	"adventureEffect" : {
+		[effect format]
+	}
 }
 }
 ```
 ```
 
 
@@ -672,3 +675,178 @@ Value of all bonuses can be affected by following bonuses:
 
 
 - range 0: any single obstacle
 - range 0: any single obstacle
 - range X: all obstacles
 - range X: all obstacles
+
+## Configurable adventure map effects
+
+Currently, VCMI does not allow completely new spell effects for adventure maps. However, it is possible to:
+- modify the parameters of all H3 spells.
+- create spells with similar effects to H3 spells
+- create a spell that gives bonuses to the hero who cast the spell.
+
+Unlike combat effects, adventure map spells can only have one special effect, such as the Dimension Door or Town Portal effect. The number of bonuses granted by an adventure map spell is unlimited.
+
+The AI has a limited understanding of adventure map spells and may use the following spells:
+- Spells that give WATER_WALKING or FLYING_MOVEMENT bonuses
+- Spells with the Summon Boat effect, provided the spell can create new boats with a 100% success chance.
+- Any spells with the Town Portal effect.
+### Common format
+
+All properties in this section can be used for all non-generic adventure map spell effects.
+
+Parameters:
+
+- `type` - the type of spell effect used for this spell, or `generic` if a custom mechanic is not used.
+- `castsPerDay` - Optional. Defines how many times a hero can cast this spell per day; set to zero or omitted for unlimited use.
+- `castsPerDayXL` - Optional. An alternative cast-per-day limit that is only active on maps that are at least XL+U in size. If this value is not set or is set to zero, the game will use the value of the `castsPerDay` variable.
+- `bonuses` - A list of bonuses that will be given to the hero when this spell is cast successfully. When used with effects that can fail (e.g. Summon Boat), the bonuses will only apply to a successful cast.
+
+Example:
+
+```json
+"adventureEffect" : {
+	"type" : "generic",
+	"castsPerDay" : 0,
+	"castsPerDayXL" : 0,
+	"bonuses" : {
+		"fly" : {
+			"type" : "FLYING_MOVEMENT",
+			"duration" : "ONE_DAY",
+			"val" : 40,
+			"valueType" : "INDEPENDENT_MIN"
+		}
+	}
+}
+```
+
+### Dimension Door
+
+The effect instantly teleports the hero to the selected location.
+
+Parameters:
+
+The effect instantly teleports the hero to the selected location.
+
+
+- `movementPointsRequired` - The amount of movement points the hero must have to cast this spell.
+- `movementPointsTaken` - The amount of movement points that will be taken if the spell is cast successfully. If the hero does not have enough movement points, they will be reduced to zero after casting.
+- `waterLandFailureTakesPoints` - If set to true, mana and movement points will be spent on an attempt to teleport to an inaccessible location (e.g. teleporting to land while in a boat).
+- `cursor` - Identifier of the cursor that will be shown when hovering over a valid destination tile. See `config/cursors.json` for more details.
+- `cursorGuarded` - alternative cursor that appears if using the teleport spell on a target would result in combat. This is only used if the game rule 'dimensionDoorTriggersGuards' is active.
+- `exposeFow` - If this is set to true, using this spell will reveal information behind fog of war, such as whether teleportation is possible or if the location is guarded.
+- `ignoreFow` - If this is set to true, it is possible to use the spell to teleport into terra incognita.
+- `rangeX` - maximum distance to teleport in the X dimension (left-right axis).
+- `rangeY` - maximum distance to teleport in the Y dimension (top-bottom axis).
+
+Example:
+
+```json
+"adventureEffect" : {
+	"type" : "dimensionDoor",
+	"movementPointsRequired" : 0,
+	"movementPointsTaken" : 300,
+	"waterLandFailureTakesPoints" : true,
+	"cursor" : "mapDimensionDoor",
+	"cursorGuarded" : "mapTurn1Attack",
+	"castsPerDay" : 2,
+	"rangeX" : 9,
+	"rangeY" : 8,
+	"ignoreFow" : true,
+	"exposeFow" : true
+}
+```
+
+### Remove Object
+
+The effect completely removes the targeted object from the map. The Scuttle Boat spell is an example of this effect.
+
+Parameters:
+
+- `objects` - a list of map objects that can be removed by this spell.
+- `cursor` - identifier of the cursor that will be displayed when hovering over a valid target object.  See `config/cursors.json` for more details.
+- `ignoreFow` - If set to true, it is possible to use this spell to remove objects behind terra incognita.
+- `rangeX` - maximum distance to remove objects in the X dimension (left-right axis).
+- `rangeY` - maximum distance to remove objects in the Y dimension (top-bottom axis).
+
+Example:
+
+```json
+"adventureEffect" : {
+	"type" : "removeObject",
+	"castsPerDay" : 0,
+	"cursor" : "mapScuttleBoat",
+	"rangeX" : 9,
+	"rangeY" : 8,
+	"ignoreFow" : false,
+	"objects" : {
+		"boat" : true
+	}
+}
+```
+
+### Summon Boat
+
+The effect moves or creates a boat next to the hero who cast the spell. The success chance is defined as [spell effect power](#spell-power).
+
+Parameters:
+
+- `useExistingBoat` - If this is set to true, the spell can move existing boats to the hero's location.
+- `createdBoat` - Optional identifier of the boat type that can be created by this spell. If this is not set, the spell cannot create new boats.
+
+
+Note that if the spell can both create new boats and use existing ones, it would prefer to move existing boats and only create new ones if there are no suitable ones to move.
+Example:
+
+```json
+"adventureEffect" : {
+	"type" : "summonBoat",
+	"castsPerDay" : 0,
+	"useExistingBoat" : true,
+	"createdBoat" : "boatNecropolis"
+}
+```
+
+### Town Portal
+
+Effect moves hero to a location of owned or allied town.
+
+Parameters:
+
+- `movementPointsRequired` - amount of movement points that hero must have to cast this spell
+- `movementPointsTaken` - amount of movement points that will be taken on sucessful cast of the spell. If hero does not have enough movement points, they will be reduced to zero after cast
+- `allowTownSelection` - if set to true, player will be able to select town to teleport to among all friendly non-occupied towns.
+- `skipOccupiedTowns` - if set to true, hero will teleport to nearest non-occupied town, ignoring any closer towns that are occupied by a visiting hero. No effect if `allowTownSelection` is set.
+
+Example:
+
+```json
+"adventureEffect" : {
+	"type" : "townPortal",
+	"castsPerDay" : 2,
+	"allowTownSelection" : false,
+	"skipOccupiedTowns" : false,
+	"movementPointsRequired" : 300,
+	"movementPointsTaken" : 300
+}
+```
+
+### View World
+
+Effect shows World View menu with specified objects behind FoW revealed to the player
+
+Parameters:
+
+- `objects` - list of object types that will be revealed on World View. Note that only following objects have assotiated icon, any objects not from this list will not be visible: `resource`, `mine`, `abandonedMine`, `artifact`, `hero`, `town`.
+- `showTerrain` - if set to true, terrain of the entire map (but not objects on it) will be revealed to the player.
+
+Example:
+
+```json
+"adventureEffect" : {
+	"objects" : {
+		"resource" : true,
+		"mine" : true,
+		"abandonedMine" : true
+	},
+	"showTerrain" : true
+}
+```

+ 1 - 1
lib/spells/adventure/RemoveObjectEffect.cpp

@@ -49,7 +49,7 @@ bool RemoveObjectEffect::canBeCastAtImpl(spells::Problem & problem, const IGameI
 	if (!isTargetInRange(cb, caster, pos))
 	if (!isTargetInRange(cb, caster, pos))
 		return false;
 		return false;
 
 
-	const TerrainTile * t = cb->getTile(pos);
+	const TerrainTile * t = cb->getTileUnchecked(pos);
 	if(!t || t->visitableObjects.empty())
 	if(!t || t->visitableObjects.empty())
 		return false;
 		return false;
 
 

+ 12 - 4
lib/spells/adventure/SummonBoatEffect.cpp

@@ -17,6 +17,7 @@
 #include "../../mapObjects/CGHeroInstance.h"
 #include "../../mapObjects/CGHeroInstance.h"
 #include "../../mapObjects/MiscObjects.h"
 #include "../../mapObjects/MiscObjects.h"
 #include "../../mapping/CMap.h"
 #include "../../mapping/CMap.h"
+#include "../../modding/IdentifierStorage.h"
 #include "../../networkPacks/PacksForClient.h"
 #include "../../networkPacks/PacksForClient.h"
 
 
 VCMI_LIB_NAMESPACE_BEGIN
 VCMI_LIB_NAMESPACE_BEGIN
@@ -24,13 +25,20 @@ VCMI_LIB_NAMESPACE_BEGIN
 SummonBoatEffect::SummonBoatEffect(const CSpell * s, const JsonNode & config)
 SummonBoatEffect::SummonBoatEffect(const CSpell * s, const JsonNode & config)
 	: owner(s)
 	: owner(s)
 	, useExistingBoat(config["useExistingBoat"].Bool())
 	, useExistingBoat(config["useExistingBoat"].Bool())
-	, createNewBoat(config["createNewBoat"].Bool())
 {
 {
+	if (!config["createdBoat"].isNull())
+	{
+		LIBRARY->identifiers()->requestIdentifier("core:boat", config["createdBoat"], [=](int32_t boatTypeID)
+		{
+			createdBoat = BoatId(boatTypeID);
+		});
+	}
+
 }
 }
 
 
 bool SummonBoatEffect::canCreateNewBoat() const
 bool SummonBoatEffect::canCreateNewBoat() const
 {
 {
-	return createNewBoat;
+	return createdBoat != BoatId::NONE;
 }
 }
 
 
 int SummonBoatEffect::getSuccessChance(const spells::Caster * caster) const
 int SummonBoatEffect::getSuccessChance(const spells::Caster * caster) const
@@ -108,7 +116,7 @@ ESpellCastResult SummonBoatEffect::applyAdventureEffects(SpellCastEnvironment *
 		cop.initiator = parameters.caster->getCasterOwner();
 		cop.initiator = parameters.caster->getCasterOwner();
 		env->apply(cop);
 		env->apply(cop);
 	}
 	}
-	else if(!createNewBoat) //none or basic level -> cannot create boat :(
+	else if(!canCreateNewBoat()) //none or basic level -> cannot create boat :(
 	{
 	{
 		InfoWindow iw;
 		InfoWindow iw;
 		iw.player = parameters.caster->getCasterOwner();
 		iw.player = parameters.caster->getCasterOwner();
@@ -118,7 +126,7 @@ ESpellCastResult SummonBoatEffect::applyAdventureEffects(SpellCastEnvironment *
 	}
 	}
 	else //create boat
 	else //create boat
 	{
 	{
-		env->createBoat(summonPos, BoatId::NECROPOLIS, parameters.caster->getCasterOwner());
+		env->createBoat(summonPos, createdBoat, parameters.caster->getCasterOwner());
 	}
 	}
 	return ESpellCastResult::OK;
 	return ESpellCastResult::OK;
 }
 }

+ 1 - 1
lib/spells/adventure/SummonBoatEffect.h

@@ -17,8 +17,8 @@ VCMI_LIB_NAMESPACE_BEGIN
 class DLL_LINKAGE SummonBoatEffect final : public IAdventureSpellEffect
 class DLL_LINKAGE SummonBoatEffect final : public IAdventureSpellEffect
 {
 {
 	const CSpell * owner;
 	const CSpell * owner;
+	BoatId createdBoat = BoatId::NONE;
 	bool useExistingBoat;
 	bool useExistingBoat;
-	bool createNewBoat;
 
 
 public:
 public:
 	SummonBoatEffect(const CSpell * s, const JsonNode & config);
 	SummonBoatEffect(const CSpell * s, const JsonNode & config);