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
+
+
+
+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
+
+
+
+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 @@
+
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 @@
+
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
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 @@
+
+
+
+
+
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:
+
+
+
## 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:
+
+
+
+### 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:
+
+
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:
+
+
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
}