Skip to content

Commit

Permalink
Merge pull request #77568 from RedMisao/RedMisao-patch-31
Browse files Browse the repository at this point in the history
Update JSON_INHERITANCE.md
  • Loading branch information
Night-Pryanik authored Nov 11, 2024
2 parents 053284c + da05222 commit fd61a22
Showing 1 changed file with 118 additions and 66 deletions.
184 changes: 118 additions & 66 deletions doc/JSON_INHERITANCE.md
Original file line number Diff line number Diff line change
@@ -1,143 +1,195 @@
# JSON Inheritance
To reduce duplication in the JSON data it is possible for some types to inherit from an existing type. Some restraint should be used, see guidelines section below.

To reduce duplication in the JSON data, it is *possible* for some JSON `type`s to inherit from existing objects. Some restraint should be used, see [Guidelines](#guidelines) section below.

Additionally, there is more than a single implementation of inheritance for different `type`s. See the [Behavior](#behavior) section for further details.

## Examples
In the following condensed example ```556``` ammo is derived from ```223``` ammo via ```copy-from```:

In the following condensed example, `556` ammo is derived from `223` ammo via `copy-from`:

```json
{
"id": "223",
"type": "AMMO",
"name": { "str_sp": ".223 Remington" },
"description": ".223 Remington ammunition with...",
"weight": "12 g",
"volume": "194 ml",
"price": "2 USD 80 cent",
"price_postapoc": "9 USD",
"flags": [ "IRREPLACEABLE_CONSUMABLE" ],
"material": [ "brass", "lead", "powder" ],
"damage": {
"damage_type": "bullet",
"amount": 39,
"armor_penetration": 2,
"barrels": [
{ "barrel_length": "28 mm", "amount": 13 }
]
},
"recoil": 1500
},
{
"id": "556",
"copy-from": "223",
"type": "AMMO",
"name": "5.56 NATO M855A1",
"name": { "str_sp": "5.56 NATO M855" },
"description": "5.56x45mm ammunition with a 62gr FMJ bullet...",
"price": "35 USD",
"price": "2 USD 90 cent",
"relative": {
"damage": -2,
"pierce": 4,
"damage": { "damage_type": "bullet", "amount": -3, "armor_penetration": 10 },
"dispersion": 20
},
"proportional": { "recoil": 1.1 },
"extend": { "effects": [ "NEVER_MISFIRES" ] }
}
```
In monsters it would look slightly different and has a few options while still using ```copy-from```:
```
"relative": { "melee_dice": 1, "melee_dice_sides": 5, "melee_damage": 2 },
"//": "Relative usage",
"relative": { "melee_damage": [ { "damage_type": "cut", "amount": 2 } ] }
"//": "or",
"relative": { "melee_damage": 2 },
},
```

The following rules apply to the above example:
For `"type": "AMMO"`, the following rules apply:

* Missing fields have the same value as the parent
* Missing fields, such as `weight`, `volume`, `material` and so on have the same value as the parent.
* Fields explicitly specified replace those of the parent type. The above example replaces `name`, `description` and `price`.
* Numeric values may be specified `relative` to the parent. For example `556` has less `damage` but more `pierce` than `223` and will maintain this relationship if the definition for `223` is changed.
* Note the syntax for fields that supports objects: either the whole or parts are defined inside, with missing fields having the same value as the parent.
* Flags can be added via `extend`. For example `556` is military ammo and gains the `NEVER_MISFIRES` ammo effect. Any existing flags specified from `223` are preserved.
* The entry you copied from must be of the same `type` as the item you added or changed. Not all `type`s are supported, and not all are supported in the same way. See [Support](#support) and [Behavior](#behavior) below).

* Fields explicitly specified replace those of the parent type. The above example replaces ```name```, ```description``` and ```price```.

* Numeric values may be specified ```relative``` to the parent. For example ```556``` has less ```damage``` but more ```pierce``` than ```223``` and will maintain this relationship if the definition for ```223``` is changed.

* Flags can be added via ```extend```. For example ```556``` is military ammo and gains the ```NEVER_MISFIRES``` ammo effect. Any existing flags specified from ```223``` are preserved.

* The entry you copied from must be of the same ```type``` as the item you added or changed (not all types are supported, see 'support' below)

Reloaded ammo is derived from the factory equivalent but with a 10% penalty to ```damage``` and ```dispersion``` and a chance to misfire:
Another example. Reloaded ammo is derived from the factory equivalent but with a 10% penalty to `damage` and `dispersion` and a chance to misfire. Additional rules apply:

```json
{
"id": "reloaded_556",
"copy-from": "556",
"type": "AMMO",
"name": "reloaded 5.56 NATO",
"proportional": {
"damage": 0.9,
"dispersion": 1.1
},
"name": { "str_sp": "5.56 NATO, reloaded" },
"proportional": { "price": 0.7, "damage": { "damage_type": "bullet", "amount": 0.9 }, "dispersion": 1.1 },
"extend": { "effects": [ "RECYCLED" ] },
"delete": { "effects": [ "NEVER_MISFIRES" ] }
}
"delete": { "effects": [ "NEVER_MISFIRES" ], "flags": [ "IRREPLACEABLE_CONSUMABLE" ] }
},
```
The following additional rules apply to the above example:

Chained inheritance is possible; for example ```reloaded_556``` inherits from ```556``` which is itself derived from ```223```
* Chained inheritance is possible. For example, `reloaded_556` inherits from `556`, which is itself derived from `223`.
* Numeric values may be specified as `proportional` to the parent by via a decimal factor. Here `reloaded_556` deals 90% of the damage of the factory equivalent with 110% dispersion.
* Flags can be deleted via `delete`. It is not an error if the deleted flag does not exist in the parent.


Numeric values may be specified ```proportional``` to the parent by via a decimal factor where ```0.5``` is 50% and ```2.0``` is 200%.
Not all `type`s work the same. For `"type": "MONSTER"`, `relative` would look slightly different and can be defined in two ways:

Flags can be deleted via ```delete```. It is not an error if the deleted flag does not exist in the parent.
```json
"//": "base monster",
"relative": { "melee_dice": 1, "melee_dice_sides": 5, "melee_damage": 2 },

As with relative in monsters it would look slightly different and has two options while still using ```copy-from```:
"//2": "first case",
"relative": { "melee_damage": [ { "damage_type": "cut", "amount": 2 } ] }
"//3": "second case",
"relative": { "melee_damage": 2 },
```
"proportional": { "hp": 1.5, "speed": 1.5, "attack_cost": 1.5, "melee_damage": 0.8 },

Same as above, now with `proportional`:

```json
"//": "base monster",
"proportional": { "hp": 1.5, "speed": 1.5, "attack_cost": 1.5, "melee_damage": 0.8 },

"//": "Proportional usage",
"proportional": { "melee_damage": [ { "damage_type": "cut", "amount": 0.8 } ] },
"//": "or",
"proportional": { "melee_damage": 0.8 },
"//2": "first case, applies to cut damage",
"proportional": { "melee_damage": [ { "damage_type": "cut", "amount": 0.8 } ] },
"//3": "second case, applies to *all* melee damage",
"proportional": { "melee_damage": 0.8 },
```

It is possible to define an ```abstract``` type that exists only for other types to inherit from and cannot itself be used in game. In the following condensed example ```magazine_belt``` provides values common to all implemented ammo belts:

It is possible to define an `abstract` ID that exists only for other `type`s to inherit from and cannot itself be used in game. This is done to facilitate maintenance and reduce line count. In the following condensed example `magazine_belt` provides values common to all implemented ammo belts:

```json
{
"abstract": "magazine_belt",
"type": "MAGAZINE",
"name": "Ammo belt",
"name": { "str": "ammo belt" },
"description": "An ammo belt consisting of metal linkages which disintegrate upon firing.",
"rigid": false,
"armor_data": {
"covers": [ "TORSO" ],
...
"armor": [
{
"material": [ { "type": "steel", "covered_by_mat": 100, "thickness": 0.1 } ],
"encumbrance": 10,
"coverage": 10,
"covers": [ "torso" ],
"specifically_covers": [ "torso_upper" ]
}
]
},
"flags": [ "MAG_BELT", "MAG_DESTROY" ]
"flags": [ "MAG_BELT", "MAG_DESTROY", "BELTED", "OVERSIZE", "WATER_FRIENDLY", "ZERO_WEIGHT" ]
}
```
The following additional rules apply to the above example:

Missing mandatory fields do not result in errors as the ```abstract``` type is discarded after JSON loading completes
The following additional rules apply:

* Missing mandatory fields, such as `volume` do not result in errors as the `abstract` type is discarded after JSON loading completes.
* Missing optional fields are set to the usual defaults for that type.

Missing optional fields are set to the usual defaults for that type

## Support
The following types currently support inheritance:

The following `type`s currently support inheritance (non-exhaustive list):

```
GENERIC
AMMO
ARMOR
BOOK
COMESTIBLE
effect_on_condition
ENGINE
furniture
GUN
GUNMOD
harvest
item_group
MAGAZINE
MATERIAL
material
MONSTER
MONSTER_FACTION
monstergroup
mutation
overmap_terrain
recipe
scenario
SPELL
terrain
TOOL
TOOL_ARMOR
uncraft
vehicle_part
```

To find out if a type supports copy-from, you need to know if it has implemented generic_factory. To find out if this is the case, do the following:
To find out if a type supports `copy-from`, you need to know if it has implemented generic_factory. To find out if this is the case, do the following:
* Open [init.cpp](https://github.com/CleverRaven/Cataclysm-DDA/tree/master/src/init.cpp)
* Find the line that mentions your type, for example `add( "gate", &gates::load );`
* Copy the load function, in this case it would be *gates::load*
* Use this in [the search bar on github](https://github.com/CleverRaven/Cataclysm-DDA/search?q=%22gates%3A%3Aload%22&unscoped_q=%22gates%3A%3Aload%22&type=Code) to find the file that contains *gates::load* (Note, you cannot search for ":" in file finder. The search will simply ignore this symbol.)
* Find the line that mentions your type, for example `add( "gate", &gates::load );`.
* Copy the load function, in this case it would be *gates::load*.
* Use this in [the search bar on github](https://github.com/CleverRaven/Cataclysm-DDA/search?q=%22gates%3A%3Aload%22&unscoped_q=%22gates%3A%3Aload%22&type=Code) to find the file that contains *gates::load* (Note, you cannot search for ":" in file finder. The search will simply ignore this symbol.).
* In the search results you find [gates.cpp](https://github.com/CleverRaven/Cataclysm-DDA/tree/master/src/gates.cpp). open it.
* In gates.cpp, find the generic_factory line, it looks like this: `generic_factory<gate_data> gates_data( "gate type", "handle", "other_handles" );`
* Since the generic_factory line is present, you can now conclude that it supports copy-from.
* If you don't find generic_factory present, it does not support copy-from, as is the case for type vitamin (repeat the above steps and find that [vitamin.cpp](https://github.com/CleverRaven/Cataclysm-DDA/tree/master/src/vitamin.cpp) does not contain generic_factory)
* In gates.cpp, find the generic_factory line, it looks like this: `generic_factory<gate_data> gates_data( "gate type", "handle", "other_handles" );`.
* Since the generic_factory line is present, you can now conclude that it supports `copy-from`.
* If you don't find generic_factory present, it does not support copy-from, as is the case for type vitamin (repeat the above steps and find that [vitamin.cpp](https://github.com/CleverRaven/Cataclysm-DDA/tree/master/src/vitamin.cpp) does not contain generic_factory).


## Behavior

A common misconception is that every `type` that supports `copy-from` will also support `extend`, `delete`, `proportional` and `relative`, in the same way as one's most commonly seen object: `GENERIC`. As explained above, this is not the case, and the function has to be manually implemented each time. Furthermore, given different `type`s are handled differently by the game, it may not be possible for it to be the case.

Therefore, there is no "default" JSON inheritance, only shared degrees of support. For example, a given `type` could support copying, but not extending, or support extending but not deleting. Of note are the following cases (non-exhaustive list):
* `monstergroup` extends by default. This prevents mods copying monstergroup definitions from replacing vanilla monstergroup lists. To replace the *entire* list, add `"override": true` to the monstergroup object.
* `SPELL` has limited support, given their behavior is governed by its spell `effect`. Flags are not always inherited. Testing is required to guarantee an inherited spell will behave as intended, use at your own risk.
* The `vitamins` field from `material` extends by default.


## Guidelines

Contributors are encouraged to not overuse copy-from, as it can decrease the human readability of the JSON. Chained inheritance is especially likely to become unwieldy, essentially recreating the level of redundancy we'd like to eliminate.
Contributors are encouraged to not overuse `copy-from`, as it can decrease the human readability of the JSON. Chained inheritance is especially likely to become unwieldy, essentially recreating the level of redundancy we'd like to eliminate.

In general, there are two situations where copy-from should be used in the core game:
In general, there are two situations where `copy-from` should be used in the core game:

- Two things are nearly identical variants of each other.
- A group of entities always (not almost always, always) shares some set of properties, then one or two levels of abstracts can set up a very shallow and narrow hierarchy.
* Two things are nearly identical variants of each other.
* If they're pretty much identical (e.g. everything except for their descriptions can be kept, there are only decorative differences, there are no mechanical differences, or in the case of [guns](GUN_NAMING_AND_INCLUSION.md#difference-threshold) minor value differences), they can be handled as [variants](JSON_INFO.md#snippets) (scroll down to the variant example).
* A group of entities always (not almost always, always) shares some set of properties, then one or two levels of abstracts can set up a very shallow and narrow hierarchy.

0 comments on commit fd61a22

Please sign in to comment.