diff --git a/docs/developers/BattleField.png b/docs/developers/BattleField.png deleted file mode 100644 index 90022a324..000000000 Binary files a/docs/developers/BattleField.png and /dev/null differ diff --git a/docs/developers/Battlefield.md b/docs/developers/Battlefield.md new file mode 100644 index 000000000..587d856bc --- /dev/null +++ b/docs/developers/Battlefield.md @@ -0,0 +1,18 @@ +# Battlefield + +## Battlefield Layout + +![Battlefield Hexes Layout](../images/Battle_Field_Hexes.svg) + +Legend: + +- gray (0/16/17...): inaccessible hexes located on both sides of the battlefield. Units can't normally move onto them, and they can't be targeted via spells. These hexes are used as back tile for war machines +- green (1/15/35...): starting locations for units. Defined in `config/gameConfig.json` +- yellow (18/32/52...): starting locations for war machines. Defined in `config/gameConfig.json` +- dark red (12/45/62...): non-destroyable parts of walls during siege. Hardcoded. +- light red (29/78/130/182): parts of walls that can be targeted by catapult during siege. Hardcoded +- dark blue (11/28/44...): default position of moat during sieges. Defined in a special spell that is casted on start of siege battle. +- light blue (10/27/43...): additional locations covered by "wide" moat in Fortress. Defined in a special spell that is casted on start of siege battle. +- pink (94): location of drawbridge. Unit located on this tile will block drawbridge and prevent it from opening. In Fortress this hex also acts as moat when drawbridge is raised. Hardcoded +- purple (95/96): gatehouse. Unless drawbridge is down, these tiles can only be entered by defender. When defender unit is on this tile, drawbridge is forced to open. Blocked drawbridge would block these tiles. Hardcoded +- not depicted (12/50/183): locations of towers that can be targeted by catapult. Hardcoded diff --git a/docs/developers/Bonus_System.md b/docs/developers/Bonus_System.md index c3b86a646..7cc99c332 100644 --- a/docs/developers/Bonus_System.md +++ b/docs/developers/Bonus_System.md @@ -2,6 +2,16 @@ The bonus system of VCMI is a set of mechanisms that make handling of different bonuses for heroes, towns, players and units easier. The system consists of a set of nodes representing objects that can be a source or a subject of a bonus and two directed acyclic graphs (DAGs) representing inheritance and propagation of bonuses. Core of bonus system is defined in HeroBonus.h file. +## Bonus System Nodes + +![Bonus System Nodes Diagram](../images/Bonus_System_Nodes.svg) + +Legend: + +- brown: actual nodes, and members of bonus system graph +- cyan: constant nodes that act only as source, and can not receive bonuses +- gray: virtual nodes to clarify graph layout. Actual node is located below. For example, there is no entity for "Visiting Hero", instead visiting hero is hero that is attached to player node only via Town node. + ## Propagation and inheritance Each bonus originates from some node in the bonus system, and may have propagator and limiter objects attached to it. Bonuses are shared around as follows: diff --git a/docs/images/Battle_Field_Hexes.svg b/docs/images/Battle_Field_Hexes.svg new file mode 100644 index 000000000..d1be3992d --- /dev/null +++ b/docs/images/Battle_Field_Hexes.svg @@ -0,0 +1,200 @@ + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + + 17 + 18 + 19 + 20 + 21 + 22 + 23 + 24 + 25 + 26 + 27 + 28 + 29 + 30 + 31 + 32 + 33 + + 34 + 35 + 36 + 37 + 38 + 39 + 40 + 41 + 42 + 43 + 44 + 45 + 46 + 47 + 48 + 49 + 50 + + 51 + 52 + 53 + 54 + 55 + 56 + 57 + 58 + 59 + 60 + 61 + 62 + 63 + 64 + 65 + 66 + 67 + + 68 + 69 + 70 + 71 + 72 + 73 + 74 + 75 + 76 + 77 + 78 + 79 + 80 + 81 + 82 + 83 + 84 + + 85 + 86 + 87 + 88 + 89 + 90 + 91 + 92 + 93 + 94 + 95 + 96 + 97 + 98 + 99 + 100 + 101 + + 102 + 103 + 104 + 105 + 106 + 107 + 108 + 109 + 110 + 111 + 112 + 113 + 114 + 115 + 116 + 117 + 118 + + 119 + 120 + 121 + 122 + 123 + 124 + 125 + 126 + 127 + 128 + 129 + 130 + 131 + 132 + 133 + 134 + 135 + + 136 + 137 + 138 + 139 + 140 + 141 + 142 + 143 + 144 + 145 + 146 + 147 + 148 + 149 + 150 + 151 + 152 + + 153 + 154 + 155 + 156 + 157 + 158 + 159 + 160 + 161 + 162 + 163 + 164 + 165 + 166 + 167 + 168 + 169 + + 170 + 171 + 172 + 173 + 174 + 175 + 176 + 177 + 178 + 179 + 180 + 181 + 182 + 183 + 184 + 185 + 186 + + diff --git a/docs/images/Battle_Field_Relative_Obstacle.svg b/docs/images/Battle_Field_Relative_Obstacle.svg new file mode 100644 index 000000000..f1a264f8c --- /dev/null +++ b/docs/images/Battle_Field_Relative_Obstacle.svg @@ -0,0 +1,19 @@ + + -34 + -33 + -32 + -31 + -30 + + -17 + -16 + -15 + -14 + -13 + + 0 + 1 + 2 + 3 + 4 + diff --git a/docs/images/Bonus_System_Nodes.gv b/docs/images/Bonus_System_Nodes.gv new file mode 100644 index 000000000..f135e656c --- /dev/null +++ b/docs/images/Bonus_System_Nodes.gv @@ -0,0 +1,237 @@ +digraph mygraph { + fontname="monospace" + edge [fontname="Noto Serif"] + node [ + fontname="Noto Serif" + style=filled + shape=plain + fillcolor="#60200080" + pencolor="#00000080" // frames color + ] + + subgraph rankedTop { + "Global" [ + label =< + + + + + +
Global
Propagator: GLOBAL_EFFECT
C++ Class: CGameState
Global node to which
all map entities are connected
Note: Not recruited heroes (such as in tavern)
are not attached to any node
> + ] + "Team" [ + label =< + + + + +
Team
Propagator: TEAM_PROPAGATOR
C++ Class: TeamState
Per-team node.
Game will put players without team
into a team with a single player
> + ] + "Player" [ + label =< + + + + +
Player
Propagator: PLAYER_PROPAGATOR
C++ Class: CPlayerState
Per-player team.
All objects owned by a player
belong to such node
> + ] + }; + + subgraph rankedArmies { + rank="same" + "Hero" [ + fillcolor="#80808080" + label =< + + + + +
Hero
Propagator: HERO
C++ Class: CGHeroInstance
Represents a hero, either owned by player or in prison.
Bonuses from specialty and secondary skills
are attached directly to this node
> + ] + "Combat" [ + label =< + + + +
Combat
Propagator: BATTLE_WIDE
Node that contains both sides of a combat
Anything propagated to this node will affect both sides in combat
> + ] + }; + + subgraph rankedHeroes { + rank="same" + "Visiting Hero" [ + fillcolor="#80808080" + label =< + + +
Visiting Hero
Hero that is currently
visiting owned or allied town
> + ] + "Garrisoned Hero" [ + fillcolor="#80808080" + label =< + + +
Garrisoned Hero
Hero that is currently
placed in a garrison of owned town
> + ] + "Wandering Hero" [ + fillcolor="#80808080" + label =< + + +
Wandering Hero
Hero that is currently
moving on map, outside of towns
> + ] + "Neutral Army" [ + fillcolor="#80808080" + label =< + + +
Neutral Army
Any army that is not owned by a player
Wandering monsters, Banks, Events, etc
> + ] + "Owned Army" [ + fillcolor="#80808080" + label =< + + +
Owned Army
Army owned by a player.
Mines, Garrisons, Dwellings
> + ] + }; + + subgraph rankedTopHero { + rank="same" + "Town" [ + fillcolor="#80808080" + label =< + + + +
Town
C++ Class: CGTownInstance
Represents a town on map.
Town buildings can provide bonuses to this node
(or propagate them upward)
> + ] + "Artifact Instance" [ + fillcolor="#00FFFF80" + label =< + + + +
Artifact Instance
C++ Class: CArtifact
Represents a particular instance of an artifact
that hero can equip or trade
> + ] + "Boat" [ + fillcolor="#00FFFF80" + label =< + + + +
Boat
C++ Class: CGBoat
Represents a boat or other type of transport.
Can provide bonuses to boarded hero
> + ] + }; + + subgraph rankedMisc { + "Town and visiting hero" [ + label =< + + + + + + +
Town and Visiting Hero
Propagator: VISITED_TOWN_AND_VISITOR
C++ Class: CTownAndVisitingHero
Helper node that exists solely
to propagate bonuses to both town and visiting hero
Note: Neutral towns are attached to global node instead
> + ] + + "Combat" [ + label =< + + + + +
Combat
Propagator: BATTLE_WIDE
C++ Class: BattleInfo
Node that contains both sides of a combat
Anything propagated to this node will affect both sides in combat
> + ] + + "Creature Type" [ + fillcolor="#00FFFF80" + label =< + + + +
Creature Type
C++ Class: CCreature
Represents a creature type, such as Pikeman or Archer
> + ] + + "Artifact Type" [ + fillcolor="#00FFFF80" + label =< + + + +
Artifact Type
C++ Class: CArtifact
Represents an artifact type, for example Ring of Life
> + ] + + "Artifact Component" [ + fillcolor="#80808080" + label =< + + + +
Artifact Component
C++ Class: CArtifact
For combined, non-fused artifacts,
instances of components are attached to instance of combined artifact
> + ] + + "Army" [ + label =< + + + +
Army
C++ Class: CArmedInstance
Represents any object that can hold army,
such as town, hero, mines, garrisons, wandering monsters
> + ] + + "Unit in Army" [ + label =< + + + +
Unit in Army
C++ Class: CStackInstance
Represents a unit that is part of a army
A unit always has a creature type,
belongs to an army and has stack size
> + ] + + "Unit in Combat" [ + label =< + + + +
Unit in Combat
C++ Class: CStack
Represents current state of a unit during combat,
can be affected by spells or receive damage
> + ] + + "Summon in Combat" [ + label =< + + + +
Summon in Combat
C++ Class: CStack
Represents any unit that was added in combat,
and may not remain after combat
> + ] + }; + + "Global" -> "Team" + "Global" -> "Neutral Army" + "Team" -> "Player" + "Player" -> "Town and visiting hero" + "Player" -> "Wandering Hero" + "Player" -> "Owned Army" + "Town and visiting hero" -> "Town" + "Town and visiting hero" -> "Visiting Hero" + "Boat" -> "Hero" + "Combat" -> "Army" + "Army" -> "Unit in Army" + "Army" -> "Summon in Combat" + "Unit in Army" -> "Unit in Combat" + "Artifact Type" -> "Artifact Instance" + "Artifact Component" -> "Artifact Instance" + "Artifact Instance" -> "Hero" + + "Creature Type" -> "Summon in Combat" + "Creature Type" -> "Unit in Army" + + "Town" -> "Garrisoned Hero" + "Town" -> "Army" + "Neutral Army" -> "Army" + "Owned Army" -> "Army" + + "Visiting Hero" -> "Hero" + "Garrisoned Hero" -> "Hero" + "Wandering Hero" -> "Hero" + "Hero" -> "Army" +} diff --git a/docs/images/Bonus_System_Nodes.svg b/docs/images/Bonus_System_Nodes.svg new file mode 100644 index 000000000..d9e9febfb --- /dev/null +++ b/docs/images/Bonus_System_Nodes.svg @@ -0,0 +1,474 @@ + + + + + + +mygraph + + + +Global + + +Global + +Propagator: +GLOBAL_EFFECT + +C++ Class: +CGameState + +Global node to which +all map entities are connected + +Note: Not recruited heroes (such as in tavern) +are not attached to any node + + + + +Team + + +Team + +Propagator: +TEAM_PROPAGATOR + +C++ Class: +TeamState + +Per-team node. +Game will put players without team +into a team with a single player + + + + +Global->Team + + + + + +Neutral Army + + +Neutral Army + +Any army that is not owned by a player +Wandering monsters, Banks, Events, etc + + + + +Global->Neutral Army + + + + + +Player + + +Player + +Propagator: +PLAYER_PROPAGATOR + +C++ Class: +CPlayerState + +Per-player team. +All objects owned by a player +belong to such node + + + + +Team->Player + + + + + +Wandering Hero + + +Wandering Hero + +Hero that is currently +moving on map, outside of towns + + + + +Player->Wandering Hero + + + + + +Owned Army + + +Owned Army + +Army owned by a player. +Mines, Garrisons, Dwellings + + + + +Player->Owned Army + + + + + +Town and visiting hero + + +Town and Visiting Hero + +Propagator: +VISITED_TOWN_AND_VISITOR + +C++ Class: +CTownAndVisitingHero + +Helper node that exists solely +to propagate bonuses to both town and visiting hero + +Note: Neutral towns are attached to global node instead + + + + +Player->Town and visiting hero + + + + + +Hero + + +Hero + +Propagator: +HERO + +C++ Class: +CGHeroInstance + +Represents a hero, either owned by player or in prison. +Bonuses from specialty and secondary skills +are attached directly to this node + + + + +Army + + +Army + +C++ Class: +CArmedInstance + +Represents any object that can hold army, +such as town, hero, mines, garrisons, wandering monsters + + + + +Hero->Army + + + + + +Combat + + +Combat + +Propagator: +BATTLE_WIDE + +C++ Class: +BattleInfo + +Node that contains both sides of a combat +Anything propagated to this node will affect both sides in combat + + + + +Combat->Army + + + + + +Visiting Hero + + +Visiting Hero + +Hero that is currently +visiting owned or allied town + + + + +Visiting Hero->Hero + + + + + +Garrisoned Hero + + +Garrisoned Hero + +Hero that is currently +placed in a garrison of owned town + + + + +Garrisoned Hero->Hero + + + + + +Wandering Hero->Hero + + + + + +Neutral Army->Army + + + + + +Owned Army->Army + + + + + +Town + + +Town + +C++ Class: +CGTownInstance + +Represents a town on map. +Town buildings can provide bonuses to this node +(or propagate them upward) + + + + +Town->Garrisoned Hero + + + + + +Town->Army + + + + + +Artifact Instance + + +Artifact Instance + +C++ Class: +CArtifact + +Represents a particular instance of an artifact + that hero can equip or trade + + + + +Artifact Instance->Hero + + + + + +Boat + + +Boat + +C++ Class: +CGBoat + +Represents a boat or other type of transport. +Can provide bonuses to boarded hero + + + + +Boat->Hero + + + + + +Town and visiting hero->Visiting Hero + + + + + +Town and visiting hero->Town + + + + + +Creature Type + + +Creature Type + +C++ Class: +CCreature + +Represents a creature type, such as Pikeman or Archer + + + + +Unit in Army + + +Unit in Army + +C++ Class: +CStackInstance + +Represents a unit that is part of a army +A unit always has a creature type, +belongs to an army and has stack size + + + + +Creature Type->Unit in Army + + + + + +Summon in Combat + + +Summon in Combat + +C++ Class: +CStack + +Represents any unit that was added in combat, +and may not remain after combat + + + + +Creature Type->Summon in Combat + + + + + +Artifact Type + + +Artifact Type + +C++ Class: +CArtifact + +Represents an artifact type, for example Ring of Life + + + + +Artifact Type->Artifact Instance + + + + + +Artifact Component + + +Artifact Component + +C++ Class: +CArtifact + +For combined, non-fused artifacts, +instances of components are attached to instance of combined artifact + + + + +Artifact Component->Artifact Instance + + + + + +Army->Unit in Army + + + + + +Army->Summon in Combat + + + + + +Unit in Combat + + +Unit in Combat + +C++ Class: +CStack + +Represents current state of a unit during combat, +can be affected by spells or receive damage + + + + +Unit in Army->Unit in Combat + + + + + diff --git a/docs/modders/Bonus/Bonus_Limiters.md b/docs/modders/Bonus/Bonus_Limiters.md index 304f6c043..283eefb39 100644 --- a/docs/modders/Bonus/Bonus_Limiters.md +++ b/docs/modders/Bonus/Bonus_Limiters.md @@ -23,20 +23,27 @@ Example: ### HAS_ANOTHER_BONUS_LIMITER +Bonus is only active if affected entity has another bonus that meets conditions + Parameters: - Bonus type -- (optional) bonus subtype -- (optional) bonus sourceType and sourceId in struct -- example: (from Adele's bless): +- bonus subtype +- bonus sourceType and sourceId in struct + +All parameters are optional. Values that don't need checking can be replaces with `null` + +Examples: + +- Adele specialty: active if unit has any bonus from Bless spell ```json "limiters" : [ { "type" : "HAS_ANOTHER_BONUS_LIMITER", "parameters" : [ - "GENERAL_DAMAGE_PREMY", - 1, + null, + null, { "type" : "SPELL_EFFECT", "id" : "spell.bless" @@ -46,18 +53,42 @@ Parameters: ], ``` +- Mutare specialty: active if unit has `DRAGON_NATURE` bonus + +```json + "limiters" : [ + { + "parameters" : [ "DRAGON_NATURE" ], + "type" : "HAS_ANOTHER_BONUS_LIMITER" + } + ], +``` + ### CREATURE_TYPE_LIMITER +Bonus is only active on creatures of specified type + Parameters: - Creature id (string) -- (optional) include upgrades - default is false +- (optional) include upgrades - default is false. If creature has multiple upgrades, or upgrades have their own upgrades, all such creatures will be affected. Special upgrades such as upgrades via specialties (Dragon, Gelu) are not affected + +Example: + +```json +"limiters": [ { + "type":"CREATURE_TYPE_LIMITER", + "parameters": [ "angel", true ] +} ], +``` ### CREATURE_ALIGNMENT_LIMITER +Bonus is only active on creatures of factions of specified alignment + Parameters: -- Alignment identifier +- Alignment identifier, `good`, `evil`, or `neutral` ### CREATURE_LEVEL_LIMITER @@ -82,13 +113,6 @@ Parameters: Example: -```json -"limiters": [ { - "type":"CREATURE_TYPE_LIMITER", - "parameters": [ "angel", true ] -} ], -``` - ```json "limiters" : [ { "type" : "CREATURE_TERRAIN_LIMITER", @@ -102,6 +126,10 @@ Parameters: - List of affected battlefield hexes +For reference on tiles indexes see image below: + +![Battlefield Hexes Layout](../../images/Battle_Field_Hexes.svg) + ## Aggregate Limiters The following limiters must be specified as the first element of a list, diff --git a/docs/modders/Bonus/Bonus_Updaters.md b/docs/modders/Bonus/Bonus_Updaters.md index e030cde9a..fef6f5346 100644 --- a/docs/modders/Bonus/Bonus_Updaters.md +++ b/docs/modders/Bonus/Bonus_Updaters.md @@ -1,12 +1,10 @@ # Bonus Updaters -TODO: this page may be incorrect or outdated - Updaters come in two forms: simple and complex. Simple updaters take no parameters and are specified as strings. Complex updaters do take parameters (sometimes optional), and are specified as structs. -Check the files in *config/heroes/* for additional usage examples. +Check the files in `config/heroes/` for additional usage examples. ## GROWS_WITH_LEVEL @@ -14,8 +12,7 @@ Check the files in *config/heroes/* for additional usage examples. - Parameters: valPer20, stepSize=1 - Effect: Updates val to `ceil(valPer20 * floor(heroLevel / stepSize) / 20)` -Example: The following updater will cause a bonus to grow by 6 for every -40 levels. At first level, rounding will cause the bonus to be 0. +Example: The following updater will cause a bonus to grow by 6 for every 40 levels. At first level, rounding will cause the bonus to be 0. ```json "updater" : { @@ -24,8 +21,7 @@ Example: The following updater will cause a bonus to grow by 6 for every } ``` -Example: The following updater will cause a bonus to grow by 3 for every -20 levels. At first level, rounding will cause the bonus to be 1. +Example: The following updater will cause a bonus to grow by 3 for every 20 levels. At first level, rounding will cause the bonus to be 1. ```json "updater" : { @@ -36,10 +32,8 @@ Example: The following updater will cause a bonus to grow by 3 for every Remarks: -- The rounding rules are designed to match the attack/defense bonus - progression for heroes with creature specialties in HMM3. -- There is no point in specifying val for a bonus with a - GROWS_WITH_LEVEL updater. +- The rounding rules are designed to match the attack/defense bonus progression for heroes with creature specialties in HMM3. +- There is no point in specifying val for a bonus with a GROWS_WITH_LEVEL updater. ## TIMES_HERO_LEVEL @@ -48,14 +42,10 @@ Remarks: Usage: `"updater" : "TIMES_HERO_LEVEL"` -Remark: This updater is redundant, in the sense that GROWS_WITH_LEVEL -can also express the desired scaling by setting valPer20 to 20\*val. It -has been added for convenience. - ## TIMES_STACK_LEVEL - Type: Simple -- Effect: Updates val to `val * stackLevel` +- Effect: Updates val to `val * stackLevel`, where `stackLevel` is level of stack (Pikeman is level 1, Angel is level 7) Usage: @@ -66,7 +56,7 @@ Remark: The stack level for war machines is 0. ## DIVIDE_STACK_LEVEL - Type: Simple -- Effect: Updates val to `val / stackLevel` +- Effect: Updates val to `val / stackLevel`, where `stackLevel` is level of stack (Pikeman is level 1, Angel is level 7) Usage: @@ -87,4 +77,4 @@ Usage: ## BONUS_OWNER_UPDATER -TODO: document me +Helper updater for proper functionality of `OPPOSITE_SIDE` limiter diff --git a/docs/modders/Entities_Format/Battle_Obstacle_Format.md b/docs/modders/Entities_Format/Battle_Obstacle_Format.md index 84b8184ce..f491b2900 100644 --- a/docs/modders/Entities_Format/Battle_Obstacle_Format.md +++ b/docs/modders/Entities_Format/Battle_Obstacle_Format.md @@ -1,5 +1,7 @@ # Battle Obstacle Format +## Configuration reference + ```json // List of terrains on which this obstacle can be used "allowedTerrains" : [] @@ -16,7 +18,7 @@ // Height of an obstacle, in hexes "height" : 1 - // List of tiles blocked by an obstacles. For non-absolute obstacles uses relative hex indices + // List of tiles blocked by an obstacles. See below for description "blockedTiles" : [ 0, 20, 50 ] // For absolute obstacle - image with static obstacle. For non-absolute - animation with an obstacle @@ -25,3 +27,21 @@ // If set to true, obstacle will appear in front of units or other battlefield objects "foreground" : false ``` + +## Blocked tiles definition + +How blocked tiles are defined depends on whether obstacle is `absolute` or not: + +### Non-absolute obstacles + +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: + +![Battlefield Relative Obstacle Example](../../images/Battle_Field_Relative_Obstacle.svg) + +### Absolute obstacles + +Absolute obstacles operate in absolute coordinates. Because of that, blocked tiles contains list of indexes of blocked tiles. For reference on tiles indexes see image below: + +![Battlefield Hexes Layout](../../images/Battle_Field_Hexes.svg) diff --git a/docs/modders/Entities_Format/Battlefield_Format.md b/docs/modders/Entities_Format/Battlefield_Format.md index 7797b02ec..06d3376f3 100644 --- a/docs/modders/Entities_Format/Battlefield_Format.md +++ b/docs/modders/Entities_Format/Battlefield_Format.md @@ -23,3 +23,9 @@ // List of battle hexes that will be always blocked on this battlefield (e.g. ship to ship battles) "impassableHexes" : [ 10, 20, 50 ], ``` + +### Impassable Hexes + +Impassable hexes operate in absolute coordinates. For reference on tiles indexes see image below: + +![Battlefield Hexes Layout](../../images/Battle_Field_Hexes.svg) diff --git a/docs/modders/Entities_Format/Creature_Help.md b/docs/modders/Guides/Creature_Help.md similarity index 100% rename from docs/modders/Entities_Format/Creature_Help.md rename to docs/modders/Guides/Creature_Help.md diff --git a/docs/modders/Entities_Format/Faction_Help.md b/docs/modders/Guides/Faction_Help.md similarity index 100% rename from docs/modders/Entities_Format/Faction_Help.md rename to docs/modders/Guides/Faction_Help.md diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index 1cd7398c0..62f74a4a6 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -337,12 +337,40 @@ void CCreature::addBonus(int val, BonusType type, BonusSubtypeID subtype) } } -bool CCreature::isMyUpgrade(const CCreature *anotherCre) const +bool CCreature::isMyDirectUpgrade(const CCreature *anotherCre) const { - //TODO upgrade of upgrade? return vstd::contains(upgrades, anotherCre->getId()); } +bool CCreature::isMyDirectOrIndirectUpgrade(const CCreature *anotherCre) const +{ + std::set foundUpgrades; + std::vector upgradesToTest; + + upgradesToTest.push_back(getId()); + + while (!upgradesToTest.empty()) + { + CreatureID testedID = upgradesToTest.back(); + const CCreature * testedPtr = testedID.toCreature(); + + upgradesToTest.pop_back(); + + for (const auto & upgrade : testedPtr->upgrades) + { + if (upgrade == anotherCre->getId()) + return true; + + if (foundUpgrades.count(upgrade)) + continue; + + upgradesToTest.push_back(upgrade); + foundUpgrades.insert(upgrade); + } + } + return false; +} + std::string CCreature::nodeName() const { return "\"" + getNamePluralTextID() + "\""; diff --git a/lib/CCreatureHandler.h b/lib/CCreatureHandler.h index 0ee69617d..bee674f9e 100644 --- a/lib/CCreatureHandler.h +++ b/lib/CCreatureHandler.h @@ -163,7 +163,13 @@ public: static CCreature::CreatureQuantityId getQuantityID(const int & quantity); static std::string getQuantityRangeStringForId(const CCreature::CreatureQuantityId & quantityId); static int estimateCreatureCount(ui32 countID); //reverse version of above function, returns middle of range - bool isMyUpgrade(const CCreature *anotherCre) const; + + /// Returns true if this creature can be directly upgraded to target + bool isMyDirectUpgrade(const CCreature * target) const; + + /// Returns true if this creature can be upgraded to target + /// Performs full search through potential upgrades of upgrades + bool isMyDirectOrIndirectUpgrade(const CCreature *target) const; void addBonus(int val, BonusType type); void addBonus(int val, BonusType type, BonusSubtypeID subtype); diff --git a/lib/bonuses/Limiters.cpp b/lib/bonuses/Limiters.cpp index 6eedfb909..3a08ba01f 100644 --- a/lib/bonuses/Limiters.cpp +++ b/lib/bonuses/Limiters.cpp @@ -110,7 +110,7 @@ ILimiter::EDecision CCreatureTypeLimiter::limit(const BonusLimitationContext &co if(!c) return ILimiter::EDecision::NOT_APPLICABLE; - auto accept = c->getId() == creatureID || (includeUpgrades && creatureID.toCreature()->isMyUpgrade(c)); + auto accept = c->getId() == creatureID || (includeUpgrades && creatureID.toCreature()->isMyDirectOrIndirectUpgrade(c)); return accept ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD; //drop bonus if it's not our creature and (we don`t check upgrades or its not our upgrade) } diff --git a/lib/entities/hero/CHeroHandler.cpp b/lib/entities/hero/CHeroHandler.cpp index 307017c43..c97725372 100644 --- a/lib/entities/hero/CHeroHandler.cpp +++ b/lib/entities/hero/CHeroHandler.cpp @@ -140,59 +140,39 @@ void CHeroHandler::loadHeroSkills(CHero * hero, const JsonNode & node) const } /// creates standard H3 hero specialty for creatures -static std::vector> createCreatureSpecialty(CreatureID baseCreatureID) +static std::vector> createCreatureSpecialty(CreatureID cid) { std::vector> result; - std::set targets; - targets.insert(baseCreatureID); - // go through entire upgrade chain and collect all creatures to which baseCreatureID can be upgraded - for (;;) + const auto & specCreature = *cid.toCreature(); + int stepSize = specCreature.getLevel() ? specCreature.getLevel() : 5; + { - std::set oldTargets = targets; - - for(const auto & upgradeSourceID : oldTargets) - { - const CCreature * upgradeSource = upgradeSourceID.toCreature(); - targets.insert(upgradeSource->upgrades.begin(), upgradeSource->upgrades.end()); - } - - if (oldTargets.size() == targets.size()) - break; + auto bonus = std::make_shared(); + bonus->limiter.reset(new CCreatureTypeLimiter(specCreature, true)); + bonus->type = BonusType::STACKS_SPEED; + bonus->val = 1; + result.push_back(bonus); } - for(CreatureID cid : targets) { - const auto & specCreature = *cid.toCreature(); - int stepSize = specCreature.getLevel() ? specCreature.getLevel() : 5; + auto bonus = std::make_shared(); + bonus->type = BonusType::PRIMARY_SKILL; + bonus->subtype = BonusSubtypeID(PrimarySkill::ATTACK); + bonus->val = 0; + bonus->limiter.reset(new CCreatureTypeLimiter(specCreature, true)); + bonus->updater.reset(new GrowsWithLevelUpdater(specCreature.getAttack(false), stepSize)); + result.push_back(bonus); + } - { - auto bonus = std::make_shared(); - bonus->limiter.reset(new CCreatureTypeLimiter(specCreature, false)); - bonus->type = BonusType::STACKS_SPEED; - bonus->val = 1; - result.push_back(bonus); - } - - { - auto bonus = std::make_shared(); - bonus->type = BonusType::PRIMARY_SKILL; - bonus->subtype = BonusSubtypeID(PrimarySkill::ATTACK); - bonus->val = 0; - bonus->limiter.reset(new CCreatureTypeLimiter(specCreature, false)); - bonus->updater.reset(new GrowsWithLevelUpdater(specCreature.getAttack(false), stepSize)); - result.push_back(bonus); - } - - { - auto bonus = std::make_shared(); - bonus->type = BonusType::PRIMARY_SKILL; - bonus->subtype = BonusSubtypeID(PrimarySkill::DEFENSE); - bonus->val = 0; - bonus->limiter.reset(new CCreatureTypeLimiter(specCreature, false)); - bonus->updater.reset(new GrowsWithLevelUpdater(specCreature.getDefense(false), stepSize)); - result.push_back(bonus); - } + { + auto bonus = std::make_shared(); + bonus->type = BonusType::PRIMARY_SKILL; + bonus->subtype = BonusSubtypeID(PrimarySkill::DEFENSE); + bonus->val = 0; + bonus->limiter.reset(new CCreatureTypeLimiter(specCreature, true)); + bonus->updater.reset(new GrowsWithLevelUpdater(specCreature.getDefense(false), stepSize)); + result.push_back(bonus); } return result; diff --git a/lib/mapObjects/CGCreature.cpp b/lib/mapObjects/CGCreature.cpp index 4e1e76d2e..e9f16f15c 100644 --- a/lib/mapObjects/CGCreature.cpp +++ b/lib/mapObjects/CGCreature.cpp @@ -529,7 +529,7 @@ void CGCreature::battleFinished(const CGHeroInstance *hero, const BattleResult & const CCreature * cre = getCreature(); for(i = stacks.begin(); i != stacks.end(); i++) { - if(cre->isMyUpgrade(i->second->getCreature())) + if(cre->isMyDirectUpgrade(i->second->getCreature())) { cb->changeStackType(StackLocation(id, i->first), cre); //un-upgrade creatures }