Explorar o código

Merge pull request #5772 from IvanSavenko/modding_docs

Modding docs improvements
Ivan Savenko hai 4 meses
pai
achega
c5cc4a130d

+ 1 - 9
config/schemas/spell.json

@@ -58,7 +58,7 @@
 		},
 		"levelInfo" : {
 			"type" : "object",
-			"required" :["range", "description", "cost", "power", "aiValue"],
+			"required" :["range", "description", "cost", "power"],
 
 			"additionalProperties" : false,
 			"properties" : {
@@ -77,9 +77,6 @@
 					"type" : "string",
 					"description" : "spell range description in SRSL"
 				},
-				"aiValue" : {
-					"type" : "number"
-				},
 				"effects" : {
 					"type" : "object",
 					"description" : "Timed effects (updated by prolongation)",
@@ -108,11 +105,6 @@
 							"type" : "boolean",
 							"description" : "true: friendly/hostile based on positiveness; false: all targets"
 						},
-						"clearTarget" :
-						{
-							"type" : "boolean",
-							"description" : "LOCATION target only. Target hex/tile must be clear"
-						},
 						"clearAffected" :
 						{
 							"type" : "boolean",

+ 9 - 7
docs/modders/Bonus/Bonus_Types.md

@@ -78,9 +78,9 @@ Allows flying movement for affected heroes
 
 Eliminates terrain penalty on certain terrain types for affected heroes (Nomads ability).
 
-Note: to eliminate all terrain penalties see ROUGH_TERRAIN_DISCOUNT bonus
+Note: to eliminate all terrain penalties see [ROUGH_TERRAIN_DISCOUNT](#rough_terrain_discount) bonus
 
-- subtype: type of terrain, eg `terrain.sand`
+- subtype: type of terrain, eg `sand`
 
 ### TERRAIN_NATIVE
 
@@ -182,7 +182,7 @@ Allows affected heroes to position army before start of battle (Tactics)
 
 ### BEFORE_BATTLE_REPOSITION_BLOCK
 
-Reduces distance in which enemy hero can reposition. Counters BEFORE_BATTLE_REPOSITION bonus
+Reduces distance in which enemy hero can reposition. Counters [BEFORE_BATTLE_REPOSITION](#before_battle_reposition) bonus
 
 - val: distance within which hero can reposition his troops
 
@@ -200,7 +200,7 @@ Increases experience gain from combat by affected units. No effect if stack expe
 
 ### UNDEAD_RAISE_PERCENTAGE
 
-Defines percentage of enemy troops that will be raised after battle into own army (Necromancy). Raised unit is determined by IMPROVED_NECROMANCY bonus
+Defines percentage of enemy troops that will be raised after battle into own army (Necromancy). Raised unit is determined by [IMPROVED_NECROMANCY](#improved_necromancy) bonus
 
 - val: percentage of raised troops
 
@@ -255,7 +255,7 @@ Gives additional bonus to effect of all spells of selected school
 
 ### SPECIFIC_SPELL_DAMAGE
 
-Gives additional bonus to effect of specific spell
+For `damage`, `heal` and `demonSummon` spell effects, increases spell power by specific percentage
 
 - subtype: identifier of affected spell
 - val: bonus to spell effect, percentage
@@ -546,10 +546,12 @@ Examples:
 - Prism Breath (mods): `[ "FL", "FF", "FR" ]` — a more powerful version of Dragon Breath; all units behind the target are attacked.
 
 This is how all tiles can be referenced in the event of a frontal attack (green is the attacker and red is the defender). The hex on which defender is located is always included unconditionally.
-![MULTIHEX_UNIT_ATTACK frontal attack hexes indexing](../images/Bonus_Multihex_Attack_Horizontal.svg)
+
+![MULTIHEX_UNIT_ATTACK frontal attack hexes indexing](../../images/Bonus_Multihex_Attack_Horizontal.svg)
 
 In the case of a double-wide unit that can attack hexes to the left and right (e.g. Cerberi), the left or right hex may end up inside the attacker in certain attack configurations. To avoid this, the hex that ends up inside the unit will be 'pushed' one hex forward. This does not affect single-wide units. See below for reference:
-![MULTIHEX_UNIT_ATTACK vertical attack hexes indexing](../images/Bonus_Multihex_Attack_Vertical.svg)
+
+![MULTIHEX_UNIT_ATTACK vertical attack hexes indexing](../../images/Bonus_Multihex_Attack_Vertical.svg)
 
 ### MULTIHEX_ENEMY_ATTACK
 

+ 1 - 1
docs/modders/Entities_Format/Battle_Obstacle_Format.md

@@ -36,7 +36,7 @@ How blocked tiles are defined depends on whether obstacle is `absolute` or not:
 
 Non-absolute obstacles specify their coordinates relative to bottom-left corner of obstacle. If you wish to have obstacle that takes multiple rows, substracting 17 from hex number would block tile directly above bottom-left corner of your obstacle.
 
-For example, obstacle that blocks tiles `[1, 2, 3, -15, -16, -31]` would result in following layout on the battlefield:
+For example, obstacle that blocks tiles `[1, 2, 3, -14, -15, -31]` would result in following layout on the battlefield:
 
 ![Battlefield Relative Obstacle Example](../../images/Battle_Field_Relative_Obstacle.svg)
 

+ 363 - 98
docs/modders/Entities_Format/Spell_Format.md

@@ -6,8 +6,11 @@
 {
 	"spellName":
 	{	
-		// Mandatory. Spell type 
 		// Allowed values: "adventure", "combat", "ability"
+		// adventure spells can only be cast by hero on adventure map
+		// combat spells can be cast by hero or by creatures during combat
+		// ability-type spells can not be rolled in town mage guild 
+		// learned by hero and can only be used by creatures
 		"type": "adventure",
 		
 		// Mandatory. Spell target type
@@ -20,16 +23,17 @@
 		// Localizable name of this spell
 		"name": "Localizable name",
 	
-		// Mandatory. List of spell schools this spell belongs to
+		// List of spell schools this spell belongs to. Require for spells other than abilities
 		"school": {"air":true, "earth":true, "fire":true, "water":true},
 	
-		// Mandatory. Spell level, value in range 1-5, or 0 for abilities
+		// Spell level, value in range 1-5, or 0 for abilities
 		"level": 1,
 
-		// Mandatory. Base power of the spell
+		// Base power of the spell. To see how it affects spell, 
+		// see description of corresponding battle effect(s)
 		"power": 10,
 	
-		// Mandatory. Default chance for this spell to appear in Mage Guilds
+		// Default chance for this spell to appear in Mage Guilds
 		// Used only if chance for a faction is not set in gainChance field
 		"defaultGainChance": 0, 
 	
@@ -43,20 +47,23 @@
 		"animation":{<Animation format>},
 			
 		// List of spells that will be countered by this spell
+		// If unit is affected by any spells from this list, 
+		// then casting this spell will remove effect of countered spell
 		"counters": {
 			"spellID" : true, 
 			...
 		},
 
-		//Mandatory. List of flags that describe this spell
-		// positive - this spell is positive to target (buff)
-		// negative - this spell is negative to target (debuff)
+		// List of flags that describe this spell
+		// positive - this spell is positive to target (buff) and can target allies
+		// negative - this spell is negative to target (debuff) and can target enemies
 		// indifferent - spell is neither positive, nor negative
-		// damage - spell does damage (direct or indirect)
-		// offensive - direct damage (implicitly sets damage and negative)
-		// rising - rising spell (implicitly sets positive)
-		// special - this spell is normally unavailable and can only be received explicitly, e.g. from bonus SPELL
-		// nonMagical - this spell is not affected by Sorcery or magic resistance. School resistances apply.
+		// damage - spell does damage (direct or indirect). 
+		// If set, AI will avoid obstacles with such effect, and spellbook popup will also list damage of the spell
+		// offensive - (Deprecated?) direct damage (implicitly sets damage and negative)
+		// rising - (Deprecated?) rising spell (implicitly sets positive)
+		// special - this spell can not be present in mage guild, or leared by hero, and can only be received explicitly, e.g. from bonus SPELL
+		// nonMagical - this spell is not affected by Sorcery or magic resistance. School resistances (if any) apply.
 		"flags" : {
 			"positive": true,
 		},
@@ -195,36 +202,40 @@ TODO
 ```json
 
 {
-	//Mandatory, localizable description. Use {xxx} for formatting
+	//Localizable description. Use {xxx} for formatting
 	"description": "",
 
-	//Mandatory, cost in mana points
+	//Cost in mana points
 	"cost": 1,
 
-	//Mandatory, number
+	// Base power of the spell. To see how it affects spell, 
+	// see description of corresponding battle effect(s)
 	"power": 10,
 
-	//Mandatory, number
-	"aiValue": 20,
-
 	//Mandatory, flags structure //TODO
 	// modifiers make sense for creature target
 	"targetModifier":
 	{
-		"smart": false,	//true: friendly/hostile based on positiveness; false: all targets
-		"clearTarget": false,
+		// If true, then this spell will not affect units if:
+		// - target is friendly and spell is negative
+		// - target is enemy, and spell is positive
+		// Othervice, all units in affected area will be hit by a spell, provided they are not immune
+		"smart": false,
+		// LOCATION target only. All affected hexes must be empty with no obstacles or units on them
 		"clearAffected": false,
 	},
 	
-	//Mandatory
-	//spell range description in SRSL
-	// range "X" + smart modifier = enchanter casting, expert massive spells
-	// range "X" + no smart modifier = armageddon, death ripple, destroy undead
-
+	// spell range description. Only for combat spells
+	// Describes distances from spell cast point that will be affected.
+	// For example, "range" : "0" will only affect hex on which this spell was cast (e.g. Magic Arrow)
+	// "range" : "0,1" will affect hex on which spell was cast, as well as all hexes around it (e.g. Fireball)
+	// "range" : "1" will only affect hexes around target, without affecting target itself (Frost Ring)
+	// "range" : "0,1,2," or "range" : "0-2" will affect all tiles 0,1 and 2 hexes away from the target (Inferno)
+	// Special case: range "X" implies massive spells that affect all units (armageddon, death ripple, destroy undead)
 	"range": "X",
 
-	//DEPRECATED, Optional, arbitrary name - bonus format map
-	//timed effects, overriding by name
+	// DEPRECATED, please use "battleEffects" with timed effect instead
+	// List of bonuses that will affect targets for duration of the spell
 	"effects":
 	{
 		"firstEffect": {[bonus format]},
@@ -233,162 +244,413 @@ TODO
 
 	
 	},
-	//DEPRECATED, cumulative effects that stack while active
+
+	// DEPRECATED, please use "battleEffects" with timed effect and "cumulative" set to true instead
+	// List of bonuses that will affect targets for duration of the spell. Casting spell repeatetly will cumulate effect (Disrupting Ray)
 	"cumulativeEffects":
 	{
 		"firstCumulativeEffect": {[bonus format]}
 		//...
-
 	},
+
+	/// See Configurable battle effects section below for detailed description
 	"battleEffects":
 	{
 		"mod:firstEffect": {[effect format]},
 		"mod:secondEffect": {[effect format]}
 		//...
-
 	}
 }
 ```
 
-## Configurable battle effects
+## Spell power
 
-**If spell have at least one special effect it become configurable spell and spell configuration processed different way**
+Most of battle effects are scaled based on spell effect value. This value is same across all effects and calculated as:
 
-### Configurable spell
+```text
+caster spell power * base spell power + spell mastery power(caster spell school)
+```
 
-Configurable spells ignore *offensive* flag, *effects* and *cumulativeEffects*. For backward compatibility *offensive* flag define Damage effect, *effects* and *cumulativeEffects* define Timed effect.
+Where:
 
-### Special effect common format
+- `caster spell school` is assumed spell school level for the spell. For unit this is value of [SPELLCASTER](../bonus/Bonus_Types.md#spellcaster) bonus. For hero this is value of [MAGIC_SCHOOL_SKILL](../bonus/Bonus_Types.md#magic_school_skill) or [SPELL](../bonus/Bonus_Types.md#spell) bonus, whichever is greater
+- `spell mastery power` is `power` parameter defined in config of corresponding mastery level of the spell
+- `base spell power` is `power` parameter, as defined in config of spell itself
+- `caster spell power` is spellpower of the hero, or [CREATURE_SPELL_POWER](../bonus/Bonus_Types.md#creature_spell_power) bonus for units
 
-TODO
+If unit has [SPECIFIC_SPELL_POWER](../bonus/Bonus_Types.md#specific_spell_power) bonus for corresponding spell, game will use value of the bonus instead
+
+Power of `damage`, `heal`, `summon`, and `demonSummon` effects cast by hero can also be affected by following bonuses:
+
+- [SPECIAL_SPELL_LEV](../bonus/Bonus_Types.md#special_spell_lev) bonus for the spell, scaled down by target level (Solmyr / Deemer)
+
+Following bonuses will only affect `damage`, `heal` and `demonSummon` effects
+
+- [SPELL_DAMAGE](../bonus/Bonus_Types.md#spell_damage) for specific spell school (Sorcery)
+- [SPECIFIC_SPELL_DAMAGE](../bonus/Bonus_Types.md#specific_spell_damage) for the spell (Luna / Ciele)
+
+## Smart target modifier
+
+To restrict spell from casting it on "wrong" side in combat, you can use `smart` target modifier. If this flag is set, and spell has `positive` flag, it can only affect friendly units. Similarly, spells with `negative` flag and `smart` target modifier can only affect enemies. This affects both primary targets and any secondary targets in case of area of effect or massive spells.
+
+## Configurable battle effects
+
+### Common format
 
 ```json
 
-"mod:effectId":{
+"firstSpellEffect":{
+	// identifier of effect type. See type-specific documentation below for possible values
+	"type":"core:effectType", 
 
-"type":"mod:effectType", //identifier of effect type
-"indirect": false, // effect will be deferred (f.e. land mine damage) 
-"optional": false // you can cast spell even if this effect in not applicable
+	// effect will be deferred (f.e. land mine damage) TODO: clarify. Only dispell uses this flag!
+	"indirect": false,
+	
+	// spell can be cast even if this effect in not applicable, for example due to immunity
+	// Can be used for secondary effects, to allow casting spell if only main effect is applicable
+	"optional": false 
 
-//for unit target effects
-"ignoreImmunity" : false,
-"chainFactor" : 0.5,
-"chainLength" : 4
+	/// following fields are only applicable to effects that are cast on units (and not locations or summon)
+	
+	/// Ignore immunity unless unit has SPELL_IMMUNITY bonus for this spell with addInfo set to 1
+	"ignoreImmunity" : false,
 
-//other fields depending on type
+	/// Specifies number of additional targets to hit in chain, similar to Chain Lightning
+	"chainLength" : 4
+	
+	// Only applicable for damage spells and only if chain length is non-zero.
+	// Multiplier for damage for each chained target
+	"chainFactor" : 0.5,
 }
 ```
 
 ### Catapult
 
-TODO
+This spell can only be used when attacking a town with existing, non-destroyed walls. Can be also cast by defender, unless spell uses "smart" targeting
+
+Casting the spell on location with wall will deal 0-2 damage to the walls or towers, depending on spell configuration.
+
+Casting the spell with "massive" target will randomly pick selected number of target using logic similar to H3
 
 ```json
 
-"mod:effectId":{
+"firstSpellEffect":{
+	"type": "core:catapult"
+
+	// How many targets will be attacked by the spell
+	"targetsToAttack": 1, 
+
+	// If it is a targeted spell, probability to hit keep
+	"chanceToHitKeep" : 5, 
 
-"type": "core:catapult"
-	"targetsToAttack": 1, //How many targets will be attacked by this
-	"chanceToHitKeep" : 5, //If it is a targeted spell, chances to hit keep
-	"chanceToHitGate" : 25, //If it is a targeted spell, chances to hit gate
-	"chanceToHitTower" : 10, //If it is a targeted spell, chances to hit tower
-	"chanceToHitWall" : 50, //If it is a targeted spell, chances to hit wall
-	"chanceToNormalHit" : 60, //Chance to have 1 damage to wall, used for both targeted and massive
-	"chanceToCrit" : 30 //Chance to have 2 damage to wall, used for both targeted and massive
+	// If it is a targeted spell, probability to hit gate
+	"chanceToHitGate" : 25, 
+
+	// If it is a targeted spell, probability to hit tower
+	"chanceToHitTower" : 10,
+
+	// If it is a targeted spell, probability to hit wall 
+	"chanceToHitWall" : 50, 
+
+	// probability to deal 1 damage to wall, used for both targeted and massive
+	"chanceToNormalHit" : 60, 
+
+	// probability to deal 2 damage to wall, used for both targeted and massive
+	// chance to miss is defined implicitly, as remainer of 100% chance of normal and critical hits
+	"chanceToCrit" : 30 
 }
 ```
 
 ### Clone
 
-TODO
+Configurable version of Clone spell. Casting the spell will create clone of targeted unit that belongs to side of spell caster.
 
-Configurable version of Clone spell.
+```json
+"firstSpellEffect":{
+	"type": "core:clone"
+
+	// Maximal tier of unit on which this spell can be cast
+	"maxTier" : 3
+}
+```
+
+### Damage
+
+Deals specified damage to all affected targets based on spell effect value:
+
+- if `killByPercentage` is set, spell will deal damage equal to unit total health * [spell effect power](#spell-power) / 100
+- if `killByCount` is set, spell will deal damage equal to single creature health * [spell effect power](#spell-power)
+- if neither flag is set, spell will deal damage equal to [spell effect power](#spell-power)
+
+If spell has chain effect, damage dealt to chained target will be multiplied by specified `chainFactor`
+
+Target with [SPELL_DAMAGE_REDUCTION](../bonus/Bonus_Types.md#spell_damage_reduction) bonus with value greater than 100% for any of spell school of the spell are immune to this effect
 
 ```json
+"firstSpellEffect":{
+	"type": "core:damage",
+	"killByCount": false, 
+	"killByPercentage" : false,
+}
+```
 
-"mod:effectId":{
+### Dispel
 
-"type": "core:clone"
+Dispells all bonuses provided by any other spells from this unit. Following spells can not be dispelled
 
-"maxTier" : 3//unit tier
+- Disrupting ray
+- Acid Breath
+- any effects from adventure spells
+- any effects that comes from this spell, including effects from previous casts of the spell
+
+Only bonuses from spells with specified positiveness(es) will be dispelled. See configuration example.
+
+```json
+"firstSpellEffect":{
+	"type": "core:dispel",
+	
+	/// if set, spell will dispell other spells with "positive" flag
+	"dispelPositive": false, 
+
+	/// if set, spell will dispell other spells with "negative" flag
+	"dispelNegative" : false,
+
+	/// if set, spell will dispell other spells with "indifferent" flag
+	"dispelNeutral" : false,
 }
 ```
 
-### Damage effect
+### Heal
 
-TODO
+Effect restores [spell effect power](#spell-power) health points of affected unit. Can only be cast on unit that is not a clone and have lost some health points in the battle.
 
-If effect is automatic, spell behave like offensive spell (uses power, levelPower etc)
+If parameter `minFullUnits` is non-zero, spell can only be cast if it will at least heal enough health points to fully restore health of specified number of units. For example, a single Archangel can only use Resurrection on units with 100 health points or lower
+
+Spell can be used on dead units, but only if corpse is not blocked by a living unit.
 
 ```json
+"firstSpellEffect":{
+	"type": "core:heal",
+	
+	/// Minimal amount of health points that this spell can restore, based on target creature health
+	"minFullUnits" : 1,
+	
+	/// "heal" - only heals the unit, without resurrecting any creatures
+	/// "resurrect" - heals, resurrecting any dead units from stack until running out of power
+	/// "overHeal" - similar to resurrect, however it may also increase unit stack size over its initial size
+	"healLevel" : "heal",
+	
+	/// "oneBattle" - any resurrected unit will only stay alive till end of battle
+	/// "permanent" - any resurrected units will stay permanently after the combat
+	"healPower" : "permanent"
+}
+```
 
-"mod:effectId":{
+### Sacrifice
 
-"type": "core:damage",
-"killByCount": false, //if true works like Death Stare
-"killByPercentage" : false, //if true works like DESTRUCTION ability
+Sacrifice spell. Allows to destroy first target, while healing the second one. Destroyed unit is completely removed from the game.
 
-//TODO: options override
+Effect configuration is identical to [Heal effect](#heal).
 
+```json
+"firstSpellEffect":{
+	"type": "core:sacrifice"
+	"minFullUnits" : 1,
+	"healLevel" : "heal",
+	"healPower" : "permanent"
 }
 ```
 
-### Dispel
+### Obstacle
 
 TODO
 
-### Heal
+```json
+"firstSpellEffect":{
+	"type": "core:obstacle"
+	
+	"hidden" : false,
+	"passable" : false,
+	"trap" : false,
+	"removeOnTrigger" : false,
+	"hideNative" : false,
+
+	"patchCount" : 1,
+	"turnsRemaining" : 1,
+	"triggerAbility" : "obstacleTriggerAbility",
+	
+	"attacker" : {
+		"shape" : [],
+		"range" : [],
+		"appearSound" : {},
+		"appearAnimation" : {},
+		"animation" : {},
+		"offsetY" : 0
+	},
+	
+	"defender" : {
+		"shape" : [],
+		"range" : [],
+		"appearSound" : {},
+		"appearAnimation" : {},
+		"animation" : {},
+		"offsetY" : 0
+	}
+}
+```
+
+### Moat
 
 TODO
 
-### Obstacle
+```json
+"firstSpellEffect":{
+	"type": "core:moat"
+	
+	"hidden" : false,
+	"trap" : false,
+	"removeOnTrigger" : false,
+	"dispellable" : false,
 
-TODO
+	"moatDamage" : 90,
+	"moatHexes" : [],
+
+	"triggerAbility" : "obstacleTriggerAbility",
+	
+	"defender" : {
+		"shape" : [],
+		"range" : [],
+		"appearSound" : {},
+		"appearAnimation" : {},
+		"animation" : {},
+		"offsetY" : 0
+	}
+}
+```
 
 ### Remove obstacle
 
-TODO
+Effect removes an obstacle from targeted hex
 
-### Sacrifice
+```json
+"firstSpellEffect":{
+	"type": "core:removeObstacle",
+	
+	/// If set to true, spell can remove large ("absolute") obstacles
+	"removeAbsolute" : false,
 
-TODO
+	/// If set to true, spell can remove small obstacles (H3 behavior)
+	"removeUsual" : true,
+	
+	// If set to true, spell can remove any obstacle that was created by spell
+	"removeAllSpells" : true,
+	
+	// If set to true, spell can remove obstacles that were created with specific spell
+	"removeSpells" : [ "spellA", "spellB" ],
+}
+```
 
 ### Summon
 
-TODO
-
-### Teleport
+Effect summons additional units to the battlefield.
 
-TODO
+If `exclusive` flag is set, attempt to summon a different creature by the same side in combat will fail (even if previous summon was non-exclusive)
 
-### Timed
+Amount of summoned units is equal to [spell effect power](#spell-power).  Summoned units will disappear after combat, unless `permanent` flag in effect config is set
 
-TODO
+If `summonByHealth` option is set, then number of summoned units will be equal to [spell effect power](#spell-power) / unit health. Hero need to be able to summon at least one full unit for spell to work
 
-If effect is automatic, spell behave like \[de\]buff spell (effect and
-cumulativeEffects ignored)
+if `summonSameUnit` flag is set, and same creature was already summoned before, spell will instead heal unit in "overheal" mode, using same [spell effect power](#spell-power).
 
 ```json
+"firstSpellEffect":{
+	"type": "core:summon",
+	
+	/// Unit to summon
+	"id" : "airElemental",
+	
+	"permanent" : false,
+	"exclusive" : false,
+	"summonByHealth" : false,
+	"summonSameUnit" : false,
+}
+```
 
-"mod:effectId":{
+### Demon Summon
 
-"type": "core:timed",
-"cumulative": false
-"bonus":
-{
-"firstBonus":{[bonus format]}
-//...
+Implements Pit Lord's ability with the same name. Raises targeted dead unit as unit specified in spell parameters on casters side. New unit will be placed on the same position as corpse, and corpse will be removed from the battlefield
+
+Raised amount of units is limited by (rounded down):
+
+- total HP of summoned unit can not be larger than [spell effect power](#spell-power) of caster
+- total HP of summoned unit can not be larger than total HP of dead unit
+- total stack size of summoned unit can not be greater than stack size of dead unit
+
+```json
+"firstSpellEffect":{
+	"type": "core:demonSummon",
+	
+	/// Unit to summon
+	"id" : "demon",
+	
+	/// If true, unit will remain after combat
+	"permanent" : false
 }
+```
+
+### Teleport
+
+Effect instantly moves unit from its current location to targeted tile
+
+```json
+"firstSpellEffect":{
+	"type": "core:teleport",
+	
+	/// If true, unit will trigger obstacles on destination location
+	"triggerObstacles" : false,
+	
+	/// If true, unit can be teleported across moat during town siege
+	"isMoatPassable" : false,
+
+	/// If true, unit can be teleported across walls during town siege
+	"isWallPassable" : false,
 }
 ```
 
-## Additional documentation
+### Timed
 
-### Targets, ranges, modifiers
+Timed effect gives affected units specified bonuses for duration of the spell.
 
-TODO
+Duration of effect is:
+
+- Hero: Spellpower + value of [SPELL_DURATION](../bonus/Bonus_Types.md#spell_duration) + [SPELL_DURATION](../bonus/Bonus_Types.md#spell_duration) for specific spell
+- Units: value of [CREATURE_ENCHANT_POWER](../bonus/Bonus_Types.md#creature_enchant_power), or 3 if no such bonus
+
+Value of all bonuses can be affected by following bonuses:
+
+- [SPECIAL_PECULIAR_ENCHANT](../bonus/Bonus_Types.md#special_peculiar_enchant): value modified by 1-3 according to level of target unit
+- [SPECIAL_ADD_VALUE_ENCHANT](../bonus/Bonus_Types.md#special_add_value_enchant): value from addInfo is added to val of bonus
+- [SPECIAL_FIXED_VALUE_ENCHANT](../bonus/Bonus_Types.md#special_fixed_value_enchant): value from addInfo replaces val of bonus
+
+```json
+"firstSpellEffect" : {
+	"type": "core:timed",
+
+	// if set to true, recasting same spell will accumulate (and prolong) effects of previous spellcast
+	"cumulative" : false
+	
+	// List of bonuses granted by this spell
+	"bonus" : {
+		"firstBonus" : {[bonus format]}
+		//...
+	}
+}
+```
+
+## Target types
+
+### CREATURE
 
-- CREATURE target (only battle spells)
 - range 0: smart assumed single creature target
 - range "X" + smart modifier = enchanter casting, expert massive spells
 - range "X" + no smart modifier = armageddon, death ripple, destroy undead
@@ -396,14 +658,17 @@ TODO
 - smart modifier: smth like cloud of confusion in H4 (if I remember correctly :) )
 - no smart modifier: like inferno, fireball etc. but target only creature
 
-- NO_TARGET
+### NO_TARGET
+
 - no target selection,(abilities, most adventure spells)
 
-- LOCATION
+### LOCATION
+
 - any tile on map/battlefield (inferno, fireball etc.), DD also here but with special handling
 - clearTarget - destination hex must be clear (unused so far)
 - clearAfffected - all affected hexes must be clear (forceField, fireWall)
 
-- OBSTACLE target
+### OBSTACLE
+
 - range 0: any single obstacle
 - range X: all obstacles

+ 208 - 0
docs/modders/Guides/Bonus_System.md

@@ -0,0 +1,208 @@
+# Bonus System Guide
+
+Bonuses are effects that can be given to various game entities. A lot of game mechanics in VCMI are implemented as bonuses. Most notably, but not limited to:
+
+- All artifact effects
+- All hero specialties
+- All secondary skill effects
+- All creature abilities
+- Large number of spells
+- Some of town building
+
+While they don't provide same level of flexibility as ERM scripting from WoG, they are way easier to use and generally can be undestood by AI. List of supported effects is rather long, and covers all H3 mechanics, as well as some additions to support WoG creature abilities, HotA, and extensions requested by modders for VCMI.
+
+## Basic Usage
+
+See also: [List of Bonus Types](../Bonus/Bonus_Types.md)
+
+### Bonuses without parameters
+
+Some of the simplest bonuses don't require any parameters, so all you need to do is specify the bonus type:
+
+```json
+"bonuses" : {
+	"noPenalty" : {
+		"type" : "NO_DISTANCE_PENALTY"
+	}
+}
+```
+
+With this bonus, all ranged units in the army of the hero will not have a distance penalty when firing at distances larger than 10 hexes.
+
+### Bonuses with value
+
+Although bonuses without parameters do exist, the majority of bonuses require some configuration to suit your needs. For example:
+
+```json
+"bonuses" : {
+	"scouting" : {
+		"type" : "SIGHT_RADIUS",
+		"val" : 3
+	}
+}
+```
+
+This bonus increases the hero's sight (scouting radius) by three adventure map tiles. If the hero has multiple sources of this bonus (such as a secondary skill, specialty or other artifacts) all the bonuses will stack and the hero's actual scouting range will be equal to the sum of the values of all the bonuses.
+
+### Bonuses with subtypes
+
+In addition to value, many bonuses support so-called 'subtypes', which allow you to specify exactly what should be affected by the bonus. For example:
+
+```json
+"bonuses" : {
+	"noSandPenalty" : {
+		"type" : "NO_TERRAIN_PENALTY",
+		"subtype" : "sand"
+	}
+}
+```
+
+This bonus would eliminate the terrain penalty for your army when moving across sand terrain. Such subtypes can also be used to target objects added by mods without the need for additional bonus types in the game engine.
+
+It is also possible, and in fact required for many bonuses, to use both subtypes with a value. In this scenario, only bonuses of the same type and subtype will stack:
+
+```json
+"bonuses" : {
+	"attack" : {
+		"type" : "PRIMARY_SKILL",
+		"subtype" : "attack",
+		"val" : 3
+	}
+}
+```
+
+### Bonuses with additional info
+
+In addition to the `type`, `subtype` and `val` parameters, some bonuses may require or support an additional parameter called 'addInfo'. This is used by some bonuses to provide additional parameters that are not suitable for subtypes or values. For example:
+
+```json
+"bonuses" : {
+	"upgradeMages" : {
+		"type" : "SPECIAL_UPGRADE",
+		"subtype" : "creature.mage",
+		"addInfo" : "creature.enchanter"
+	}
+}
+```
+
+This bonus allows a hero with such an artefact to upgrade any mage in their army to an enchanter. For information on how to configure addInfo for a particular bonus, please refer to the [bonus types documentation](../Bonus/Bonus_Types.md).
+
+## Advanced Usage
+
+### Bonus Limiters
+
+Generally, a bonus affects the entity that has the bonus, as well as all entities located 'below' (or 'inside') the affected entity.
+
+For example, a bonus given to a player would affect all their heroes, towns and other owned objects, as well as their armies. However, this is undesirable in some scenarios. Heroes specialising in a specific creature, for instance, should only affect that creature and not their entire army. To support such a scenario, it is possible to use 'limiters', which enable bonuses to be applied only to certain affected entities:
+
+```json
+"specialty" : {
+	"bonuses" : {
+		"attack" : {
+			"type" : "PRIMARY_SKILL",
+			"subtype" : "primarySkill.attack"
+			"val" : 3
+			"limiters" : {
+				// Type of limiter. See bonus system reference for details
+				"type" : "CREATURE_TYPE_LIMITER", 
+				// Type-specific parameters of the limiter
+				"parameters" : [
+					"pixie", // affected unit
+					true // whether upgrades of affected unit should also be affected
+				],
+			}
+		}
+	}
+}
+```
+
+This speciality increases the attack of all Pixies in the army by 3, but does not affect any other units or the hero himself. The game supports multiple other limiters for various other use cases. Please refer to the [bonus limiters documentation](../Bonus/Bonus_Limiters.md). for details.
+
+### Bonus Propagators
+
+In some cases, it is preferable to extend the effect of bonuses instead. A typical example is a creature ability that affects the entire battlefield. For example, Angels increase the morale of all units in their hero's army. However, simply giving the Angels a morale bonus would only affect the Angels themselves. In order to affect all units in the army, such an ability would require the bonus to be 'propagated' upwards (i.e. outside of the affected entity). For such scenarios, it is possible to use `propagator`:
+
+```json
+"abilities":
+{
+	"raisesMorale" : {
+		"type" : "MORALE",
+		"val" : 1,
+		"propagator" : "HERO",
+	}
+}
+```
+
+This propagator extends the ability to all units in the hero's army, including the unit from which it originates. It is possible to propagate the bonus to most entities that form part of the bonus system. Please refer to the [bonus propagators documentation](../Bonus/Bonus_Propagators.md) for details.
+
+### Bonus Updaters
+
+Unlike propagators and limiters, updaters do not modify the entities affected by the bonus; instead, they modify the bonus itself. This is primarily used for H3 hero specialties, which are often scaled according to the level of the hero or the level of the affected unit. However, it is possible to use updaters in other areas if desired. Example:
+
+```json
+"specialty" : {
+	"bonuses" : {
+		"attack" : {
+			"type" : "PRIMARY_SKILL",
+			"subtype" : "primarySkill.attack",
+			"val" : 1,
+			"updater" : "TIMES_HERO_LEVEL"
+		}
+	}
+}
+```
+
+This speciality increases the hero's attack by 1, multiplied by their level. For example, a level 20 hero would have an attack of +20.
+
+Full list of supported bonus updaters can be found in [bonus updaters documentation](../Bonus/Bonus_Updaters.md)
+
+### Only enemy side bonus
+
+When creating a battle-wide bonus, you can use the 'BATTLE_WIDE' propagator to achieve the desired effect. Similarly, when creating a bonus that only affects allied units, use the 'HERO' propagator instead. However, due to the implementation details of the game's bonus system, bonuses that only affect the enemy side require specific configuration.
+
+For example, to implement the morale-reducing ability of Ghost Dragons, you can use the following form:
+
+```json
+"abilities":
+{
+	"decreaseMorale" : {
+		"type" : "MORALE",
+		"val" : -1,
+		"propagator": "BATTLE_WIDE",
+		"propagationUpdater" : "BONUS_OWNER_UPDATER",
+		"limiters" : [ "OPPOSITE_SIDE" ]
+	}
+}
+```
+
+As can be seen from the example, such bonuses must perform the following operations to work:
+
+- The `BATTLE_WIDE` propagator extends the effect of the bonus to the entire battlefield.
+- a `BONUS_OWNER_UPDATER` propagation updater – to indicate which side of the battlefield the bonus originates from
+- an `OPPOSITE_SIDE` limiter to restrict the bonus to units (or heroes) belonging to the other side of the battle.
+
+## Expert Usage
+
+### Full Bonus Tree Layout
+
+As mentioned in previous parts, the propagator allows bonuses to be propagated 'upwards', and bonuses only affect entities 'downwards' by default. Generally, it is clear which entities lie 'upwards' or 'downwards' – for example, creatures belong to an army, which belongs to a hero, who belongs to a player. Some cases might not be so clear, but you can consult the diagram below for help.
+
+In this diagram, all entities connected to an entity above it are considered to be 'below', and vice versa:
+
+![Bonus System Nodes Diagram](../../images/Bonus_System_Nodes.svg)
+
+### Combining updaters, propagators and limiters
+
+When the game evaluates bonuses, the following order of operations is performed:
+
+- If the bonus has a propagator, the game will attempt to look upwards through the bonus tree to find the entity to which the bonus should be propagated.
+- If such an entity is found and the bonus has a propagation updater, the updater is executed using the context of the bonus source.
+- The bonus is then moved to the entity to which it was propagated.
+- The game then collects all bonuses located upwards from the entity for which the bonus is being evaluated. Each time a bonus with an updater passes through a node, the updater is applied to the bonus using the context of the entity it passes through, including the original entity that holds the bonus and the current entity.
+- Once all bonuses have been collected, the game executes the limiter on each bonus and drops the bonus on a negative result.
+
+As a result, there are some considerations you should bear in mind.
+
+- The bonus updater is executed on every entity between the bonus source (or the bonus propagation target if a propagator is used). For example, a bonus propagated to a hero from a creature can use updaters that require either the hero or the creature.
+- The bonus propagation updater, however, can only be used with updaters that require a creature as context in the case of a creature ability.
+- A bonus limiter can only be used on the final entity through which the game accesses the bonus system for this particular bonus. For example, the SIGHT_RADIUS bonus is checked from the hero's perspective and can only be used with limiters that are valid for heroes.

+ 4 - 8
lib/spells/CSpellHandler.cpp

@@ -574,15 +574,13 @@ CSpell::TargetInfo::TargetInfo(const CSpell * spell, const int level, spells::Mo
 	: type(spell->getTargetType()),
 	smart(false),
 	massive(false),
-	clearAffected(false),
-	clearTarget(false)
+	clearAffected(false)
 {
 	const auto & levelInfo = spell->getLevelInfo(level);
 
 	smart = levelInfo.smartTarget;
 	massive = levelInfo.range.empty();
 	clearAffected = levelInfo.clearAffected;
-	clearTarget = levelInfo.clearTarget;
 }
 
 bool DLL_LINKAGE isInScreenRange(const int3 & center, const int3 & pos)
@@ -647,7 +645,8 @@ std::vector<JsonNode> CSpellHandler::loadLegacyData()
 			for(const auto & name : NFaction::names)
 				chances[name].Integer() = static_cast<si64>(parser.readNumber());
 
-			auto AIVals = parser.readNumArray<si32>(GameConstants::SPELL_SCHOOL_LEVELS);
+			// Unused, AI values
+			parser.readNumArray<si32>(GameConstants::SPELL_SCHOOL_LEVELS);
 
 			std::vector<std::string> descriptions;
 			for(size_t i = 0; i < GameConstants::SPELL_SCHOOL_LEVELS; i++)
@@ -662,7 +661,6 @@ std::vector<JsonNode> CSpellHandler::loadLegacyData()
 				level["description"].String() = descriptions[i];
 				level["cost"].Integer() = costs[i];
 				level["power"].Integer() = powers[i];
-				level["aiValue"].Integer() = AIVals[i];
 			}
 
 			legacyData.push_back(lineNode);
@@ -1022,9 +1020,7 @@ std::shared_ptr<CSpell> CSpellHandler::loadFromJson(const std::string & scope, c
 			LIBRARY->generaltexth->registerString(scope, spell->getDescriptionTextID(levelIndex), levelNode["description"]);
 
 		levelObject.cost          = static_cast<si32>(levelNode["cost"].Integer());
-		levelObject.AIValue       = static_cast<si32>(levelNode["aiValue"].Integer());
 		levelObject.smartTarget   = levelNode["targetModifier"]["smart"].Bool();
-		levelObject.clearTarget   = levelNode["targetModifier"]["clearTarget"].Bool();
 		levelObject.clearAffected = levelNode["targetModifier"]["clearAffected"].Bool();
 		levelObject.range         = spellRangeInHexes(levelNode["range"].String());
 
@@ -1058,7 +1054,7 @@ std::shared_ptr<CSpell> CSpellHandler::loadFromJson(const std::string & scope, c
 			levelObject.cumulativeEffects.push_back(b);
 		}
 
-		if(levelNode["battleEffects"].getType() == JsonNode::JsonType::DATA_STRUCT && !levelNode["battleEffects"].Struct().empty())
+		if(!levelNode["battleEffects"].Struct().empty())
 		{
 			levelObject.battleEffects = levelNode["battleEffects"];
 

+ 0 - 2
lib/spells/CSpellHandler.h

@@ -103,7 +103,6 @@ public:
 	{
 		si32 cost = 0;
 		si32 power = 0;
-		si32 AIValue = 0;
 
 		bool smartTarget = true;
 		bool clearTarget = false;
@@ -142,7 +141,6 @@ public:
 		bool smart;
 		bool massive;
 		bool clearAffected;
-		bool clearTarget;
 
 		TargetInfo(const CSpell * spell, const int32_t level, spells::Mode mode);
 	};