0.0.7: single-model units no longer have any "model" asssets

This commit is contained in:
GameKnave 2022-12-21 00:53:07 -08:00
parent 3ca785a753
commit e981ced75d
30 changed files with 312466 additions and 321189 deletions

View file

@ -1,6 +1,6 @@
{
"name": "40k9e",
"revision": "0.2.2",
"revision": "0.2.7",
"game": "Warhammer 40,000",
"genre": "sci-fi",
"publisher": "Games Workshop",
@ -544,6 +544,78 @@
"tracked": true,
"statType": "numeric"
},
"A": {
"value": null,
"statOrder": 7,
"group": "Stats",
"statType": "term",
"visibility": "normal"
},
"BS": {
"format": "{v}+",
"value": null,
"statOrder": 3,
"group": "Stats",
"statType": "numeric",
"visibility": "normal"
},
"Ld": {
"value": null,
"statOrder": 8,
"group": "Stats",
"statType": "numeric",
"visibility": "normal"
},
"S": {
"value": null,
"statOrder": 4,
"group": "Stats",
"statType": "numeric",
"visibility": "normal"
},
"Sv": {
"format": "{v}+",
"value": null,
"statOrder": 9,
"group": "Stats",
"statType": "numeric",
"visibility": "normal"
},
"T": {
"value": null,
"statOrder": 5,
"group": "Stats",
"statType": "numeric",
"visibility": "normal"
},
"W": {
"value": null,
"statOrder": 6,
"group": "Stats",
"statType": "numeric",
"visibility": "normal"
},
"WS": {
"format": "{v}+",
"value": null,
"statOrder": 2,
"group": "Stats",
"statType": "numeric",
"visibility": "normal"
},
"Base": {
"statType": "term",
"value": null,
"visibility": "active"
},
"M": {
"statType": "term",
"format": "{t}″",
"value": null,
"statOrder": 1,
"group": "Stats",
"visibility": "normal"
},
"model": {
"value": null,
"statType": "term",
@ -1581,45 +1653,6 @@
}
]
},
"This unit has unspent traits.": {
"evals": [
{
"paths": [
[
"{roster}",
"stats",
"Play",
"value"
]
],
"value": "Narrative",
"operator": "AND",
"not": false,
"actionable": true
},
{
"paths": [
[
"{self}",
"stats",
"Battle Honours",
"value"
]
],
"min": [
"{self}",
"stats",
"Battle Honours",
"max"
],
"operator": "AND",
"not": false,
"actionable": true
}
],
"failState": "warning",
"evaluate": "AND"
},
"crusadeStats": {
"evals": [
{
@ -1879,6 +1912,45 @@
"iterations": 1
}
]
},
"This unit has unspent traits.": {
"evals": [
{
"paths": [
[
"{roster}",
"stats",
"Play",
"value"
]
],
"value": "Narrative",
"operator": "AND",
"not": true,
"actionable": true
},
{
"paths": [
[
"{self}",
"stats",
"Battle Honours",
"value"
]
],
"min": [
"{self}",
"stats",
"Battle Honours",
"max"
],
"operator": "AND",
"not": true,
"actionable": true
}
],
"failState": "warning",
"evaluate": "OR"
}
},
"aspects": {

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -1,11 +1,11 @@
{
"name": "Astra Cartographica",
"revision": "0.0.6",
"revision": "0.0.7",
"game": "Warhammer 40,000",
"genre": "sci-fi",
"publisher": "Games Workshop",
"url": "https://warhammer40000.com/",
"notes": "This manifest is provided for the purposes of testing the features of *Rosterizer* and is not intended for distribution.\n\nThe data included herein was programatically compiled from freely-available sources on the internet and likely contains some errors. Use with caution.",
"notes": "0.0.7: single-model units no longer have any \"model\" asssets\n\n0.0.6: \"source\" keyword category\n\n0.0.5: add relics\n\nThis manifest is provided for the purposes of testing the features of *Rosterizer* and is not intended for distribution.\n\nThe data included herein was programatically compiled from freely-available sources on the internet and likely contains some errors. Use with caution.",
"wip": true,
"dependencies": [
{
@ -18,7 +18,7 @@
"assetTaxonomy": {},
"assetCatalog": {
"Ability§Agent of the Imperium": {
"text": "If your army is [Battle-forged](https://wahapedia.ru/wh40k9ed/the-rules/core-rules/#BATTLE-FORGED-ARMIES), you can include one **AGENT OF** THE** IMPERIUM** unit in each IMPERIUM** (excluding FALLEN** units) [Patrol](https://wahapedia.ru/wh40k9ed/the-rules/core-rules/#Patrol-Detachment), [Battalion](https://wahapedia.ru/wh40k9ed/the-rules/core-rules/#Battalion-Detachment) and [Brigade Detachment](https://wahapedia.ru/wh40k9ed/the-rules/core-rules/#Brigade-Detachment) in your army without those units taking up [Battlefield Role slots](https://wahapedia.ru/wh40k9ed/the-rules/core-rules/#Battlefield-Role-Slots) in those Detachments. The inclusion of an AGENT** OF** THE** IMPERIUM** unit does not prevent other units from their Detachment benefiting from [Detachment abilities](https://wahapedia.ru/wh40k9ed/the-rules/core-rules/#Detachment-Abilities) (e.g. [Chapter Tactics](https://wahapedia.ru/wh40k9ed/factions/space-marines/#Chapter-Tactics-1), [Defenders of Humanity](https://wahapedia.ru/wh40k9ed/factions/astra-militarum/#Defenders-of-Humanity), etc.), and it does not prevent other units from your army benefiting from abilities that require every model in your army to have that ability (e.g. Combat Doctrines**). An AGENT** OF** THE** IMPERIUM** unit included in a Patrol, Battalion or Brigade Detachment in this manner is ignored for any rules that state all units from that Detachment must have at least one Faction keyword in common (e.g. in a [matched play](https://wahapedia.ru/wh40k9ed/the-rules/matched-play) game), and when determining your [Army Faction](https://wahapedia.ru/wh40k9ed/the-rules/core-rules/#Army-Faction)."
"text": "If your army is [Battle-forged](https://wahapedia.ru/wh40k9ed/the-rules/core-rules/#BATTLE-FORGED-ARMIES), you can include one **AGENT OF** THE** IMPERIUM** unit in each IMPERIUM** (excluding FALLEN** units) [Patrol](https://wahapedia.ru/wh40k9ed/the-rules/core-rules/#Patrol-Detachment), [Battalion](https://wahapedia.ru/wh40k9ed/the-rules/core-rules/#Battalion-Detachment) and [Brigade Detachment](https://wahapedia.ru/wh40k9ed/the-rules/core-rules/#Brigade-Detachment) in your army without those units taking up [Battlefield Role slots](https://wahapedia.ru/wh40k9ed/the-rules/core-rules/#Battlefield-Role-Slots) in those Detachments. The inclusion of an AGENT** OF** THE** IMPERIUM** unit does not prevent other units from their Detachment benefiting from [Detachment abilities](https://wahapedia.ru/wh40k9ed/the-rules/core-rules/#Detachment-Abilities) (e.g. [Chapter Tactics](https://wahapedia.ru/wh40k9ed/factions/space-marines/#Chapter-Tactics-1), Defenders of Humanity, etc.), and it does not prevent other units from your army benefiting from abilities that require every model in your army to have that ability (e.g. Combat Doctrines**). An AGENT** OF** THE** IMPERIUM** unit included in a Patrol, Battalion or Brigade Detachment in this manner is ignored for any rules that state all units from that Detachment must have at least one Faction keyword in common (e.g. in a [matched play](https://wahapedia.ru/wh40k9ed/the-rules/matched-play) game), and when determining your [Army Faction](https://wahapedia.ru/wh40k9ed/the-rules/core-rules/#Army-Faction)."
},
"Ability§Captain on Deck": {
"text": "In your [Command phase](https://wahapedia.ru/wh40k9ed/the-rules/core-rules/#COMMAND-PHASE), select one friendly **NAVIS IMPERIALIS** CORE** unit within 9″ of this units Cartographica Rogue Trader model. Until the start of your next Command phase, each time a model in that unit makes an attack, add 1 to that attacks [hit roll](https://wahapedia.ru/wh40k9ed/the-rules/core-rules/#1.-Hit-Roll)."
@ -83,7 +83,14 @@
"Model§Cartographica Rogue Trader"
]
},
"text": "Increase this units **Power Rating** by +1** for every model it includes other than the Cartographica Rogue Trader.\n\n• The Cartographica Rogue Trader is equipped with: household pistol; monomolecular cane-rapier.\n\n• The Death Cult Executioner is equipped with: dartmask; Death Cult power blade.\n\n• The Lectro-Maester is equipped with: voltaic pistol.\n\n• The Rejuvenat Adept is equipped with: laspistol.",
"text": "Increase this units **Power Rating** by +1** for every model it includes other than the Cartographica Rogue Trader.\n\n• The Cartographica Rogue Trader is equipped with: household pistol; monomolecular cane-rapier.\n\n• The Death Cult Executioner is equipped with: dartmask; Death Cult power blade.\n\n• The Lectro-Maester is equipped with: voltaic pistol.\n\n• The Rejuvenat Adept is equipped with: laspistol.\n\n\n\nnull",
"allowed": {
"items": [
"Model§Death Cult Executioner",
"Model§Lectro-Maester",
"Model§Rejuvenat Adept"
]
},
"meta": {
"Publication": "[Kill Team: Annual 2022 Elucidian Starstriders (Datasheet) 9th ed. @2022-09-28](https://www.warhammer-community.com/wp-content/uploads/2022/09/RLuPWfAupdlNlFKd.pdf)"
}
@ -100,7 +107,7 @@
"value": 3
},
"BS": {
"value": null
"value": "null"
},
"S": {
"value": 4
@ -121,7 +128,7 @@
"value": 4
},
"Base": {
"value": "32mm"
"value": "25mm"
}
}
},
@ -158,7 +165,7 @@
"value": 4
},
"Base": {
"value": "32mm"
"value": "25mm"
},
"loadout1": {
"value": "Household pistol",
@ -384,7 +391,7 @@
"value": 5
},
"Base": {
"value": "32mm"
"value": "25mm"
},
"loadout1": {
"value": "Dartmask",
@ -610,7 +617,7 @@
"value": 5
},
"Base": {
"value": "32mm"
"value": "25mm"
},
"loadout1": {
"value": "Voltaic pistol",
@ -773,7 +780,7 @@
"value": 4
},
"Base": {
"value": "32mm"
"value": "25mm"
},
"loadout1": {
"value": "Navis shotgun",
@ -1167,7 +1174,7 @@
"value": 4
},
"Base": {
"value": "32mm"
"value": "25mm"
},
"loadout1": {
"value": "Navis shotgun",
@ -1442,7 +1449,7 @@
"value": 5
},
"Base": {
"value": "32mm"
"value": "25mm"
},
"loadout1": {
"value": "Laspistol",
@ -1605,7 +1612,7 @@
"value": 4
},
"Base": {
"value": "32mm"
"value": "25mm"
},
"loadout1": {
"value": "Artificer shotgun",
@ -1783,7 +1790,7 @@
"value": 4
},
"Base": {
"value": "32mm"
"value": "25mm"
},
"loadout1": {
"value": "Lasgun",
@ -1938,6 +1945,8 @@
"value": 0,
"statType": "rank",
"statOrder": 10,
"group": "Loadout",
"groupOrder": 2,
"ranks": {
"0": {
"order": 0,
@ -1973,6 +1982,8 @@
"value": 0,
"statType": "rank",
"statOrder": 10,
"group": "Loadout",
"groupOrder": 2,
"ranks": {
"0": {
"order": 0,
@ -2035,7 +2046,7 @@
"Model§Navis Sergeant-at-Arms"
]
},
"text": "• One Navis Armsman model is equipped with: Navis las-volley.\n\n• One Navis Armsman model is equipped with: Navis heavy shotgun; endurant shield.\n\n• Every other model is equipped with: Navis shotgun.\n\n• The Navis Sergeant-at-Arms Navis shotgun can be replaced with one of the following: 1 autopistol and 1 chainsword; 1 bolt pistol and 1 power sword.\n\n• 1 Navis Armsmans Navis las-volley can be replaced with one of the following: 1 meltagun; 1 plasma gun.\n\n• 1 Navis Armsmans Navis shotgun can be replaced with 1 autopistol and 1 power axe.\n\n• 1 Navis Armsmans Navis shotgun can be replaced with 1 autopistol and 1 chainfist.\n\n• 1 Navis Armsman can be equipped with 1 demolition charge, 1 frag grenades and 1 smoke grenades.",
"text": "• One Navis Armsman model is equipped with: Navis las-volley.\n\n• One Navis Armsman model is equipped with: Navis heavy shotgun; endurant shield.\n\n• Every other model is equipped with: Navis shotgun.\n\n• The Navis Sergeant-at-Arms Navis shotgun can be replaced with one of the following: 1 autopistol and 1 chainsword; 1 bolt pistol and 1 power sword.\n\n• 1 Navis Armsmans Navis las-volley can be replaced with one of the following: 1 meltagun; 1 plasma gun.\n\n• 1 Navis Armsmans Navis shotgun can be replaced with 1 autopistol and 1 power axe.\n\n• 1 Navis Armsmans Navis shotgun can be replaced with 1 autopistol and 1 chainfist.\n\n• 1 Navis Armsman can be equipped with 1 demolition charge, 1 frag grenades and 1 smoke grenades.\n\nnull",
"allowed": {
"items": [
"Model§Navis Armsman"
@ -2057,6 +2068,8 @@
"statType": "numeric",
"dynamic": true,
"visibility": "active",
"group": "Loadout",
"groupOrder": 2,
"value": 4,
"min": 4,
"max": 8,
@ -2102,10 +2115,11 @@
"Model§Voidmaster"
]
},
"text": "If this unit contains 6 or more models, it has **Power Rating 4**.\n\n• For every 5 models in this unit, one Voidsman model is equipped with: laspistol; Voidsman rotor cannon.\n\n• Every other Voidsman model is equipped with: lasgun; laspistol.\n\n• The Voidmaster is equipped with: artificer shotgun; laspistol.",
"text": "If this unit contains 6 or more models, it has **Power Rating 4**.\n\n• For every 5 models in this unit, one Voidsman model is equipped with: laspistol; Voidsman rotor cannon.\n\n• Every other Voidsman model is equipped with: lasgun; laspistol.\n\n• The Voidmaster is equipped with: artificer shotgun; laspistol.\n\n\n\nnull",
"allowed": {
"items": [
"Model§Voidsmen"
"Model§Voidsmen",
"Model§Canid"
]
},
"meta": {
@ -2210,7 +2224,7 @@
"Weapon§Chainfist": {
"stats": {
"AP": {
"value": "-4"
"value": -4
},
"D": {
"value": "D3"
@ -2250,7 +2264,7 @@
"Weapon§Dartmask": {
"stats": {
"AP": {
"value": "-2"
"value": -2
},
"D": {
"value": 1
@ -2270,13 +2284,13 @@
"Weapon§Death Cult power blade": {
"stats": {
"AP": {
"value": "-3"
"value": -3
},
"D": {
"value": 1
},
"S": {
"value": "+1"
"value": 1
},
"Type": {
"value": "Melee"
@ -2289,7 +2303,7 @@
"Weapon§Demolition charge": {
"stats": {
"AP": {
"value": "-3"
"value": -3
},
"D": {
"value": 2
@ -2329,7 +2343,7 @@
"Weapon§Household pistol": {
"stats": {
"AP": {
"value": "-3"
"value": -3
},
"D": {
"value": 2
@ -2386,7 +2400,7 @@
"Weapon§Meltagun": {
"stats": {
"AP": {
"value": "-4"
"value": -4
},
"D": {
"value": "D6"
@ -2406,13 +2420,13 @@
"Weapon§Monomolecular cane-rapier": {
"stats": {
"AP": {
"value": "-2"
"value": -2
},
"D": {
"value": 1
},
"S": {
"value": "+1"
"value": 1
},
"Type": {
"value": "Melee"
@ -2445,7 +2459,7 @@
"Weapon§Navis las-volley": {
"stats": {
"AP": {
"value": "-1"
"value": -1
},
"D": {
"value": 1
@ -2483,13 +2497,13 @@
"Weapon§Power axe": {
"stats": {
"AP": {
"value": "-2"
"value": -2
},
"D": {
"value": 1
},
"S": {
"value": "+2"
"value": 2
},
"Type": {
"value": "Melee"
@ -2502,13 +2516,13 @@
"Weapon§Power sword": {
"stats": {
"AP": {
"value": "-3"
"value": -3
},
"D": {
"value": 1
},
"S": {
"value": "+1"
"value": 1
},
"Type": {
"value": "Melee"
@ -2521,7 +2535,7 @@
"Weapon§Standard": {
"stats": {
"AP": {
"value": "-3"
"value": -3
},
"D": {
"value": 1
@ -2540,7 +2554,7 @@
"Weapon§Supercharge": {
"stats": {
"AP": {
"value": "-3"
"value": -3
},
"D": {
"value": 2
@ -2560,7 +2574,7 @@
"Weapon§Voidsman rotor cannon": {
"stats": {
"AP": {
"value": "-1"
"value": -1
},
"D": {
"value": 1

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -1,11 +1,11 @@
{
"name": "Officio Assassinorum",
"revision": "0.0.6",
"revision": "0.0.7",
"game": "Warhammer 40,000",
"genre": "sci-fi",
"publisher": "Games Workshop",
"url": "https://warhammer40000.com/",
"notes": "This manifest is provided for the purposes of testing the features of *Rosterizer* and is not intended for distribution.\n\nThe data included herein was programatically compiled from freely-available sources on the internet and likely contains some errors. Use with caution.",
"notes": "0.0.7: single-model units no longer have any \"model\" asssets\n\n0.0.6: \"source\" keyword category\n\n0.0.5: add relics\n\nThis manifest is provided for the purposes of testing the features of *Rosterizer* and is not intended for distribution.\n\nThe data included herein was programatically compiled from freely-available sources on the internet and likely contains some errors. Use with caution.",
"wip": true,
"dependencies": [
{
@ -51,7 +51,7 @@
"text": "This model can never have a [Warlord Trait](https://wahapedia.ru/wh40k9ed/the-rules/core-rules/#Warlord-Traits). During deployment, you can set this model up in concealment instead of placing it on the battlefield. At the end of any of your [Movement phases](https://wahapedia.ru/wh40k9ed/the-rules/core-rules/#MOVEMENT-PHASE), this model can reveal its position set it up anywhere on the battlefield that is more than 9″ away from any enemy models."
},
"Ability§Killing Rampage": {
"text": "Each time a model in an enemy unit is destroyed as the result of an attack made with a melee weapon by this model, you can immediately make one additional attack with a melee weapon this model is equipped with against the same unit. These additional attacks cannot themselves generate further attacks. In addition, this model can [consolidate](https://wahapedia.ru/wh40k9ed/the-rules/core-rules/#Consolidate) up to 6″ instead of up to 3."
"text": "Each time a model in an enemy unit is destroyed as the result of an attack made with a melee weapon by this model, you can immediately make one additional attack with a melee weapon this model is equipped with against the same unit. These additional attacks cannot themselves generate further attacks. In addition, this model can [consolidate](https://wahapedia.ru/wh40k9ed/the-rules/core-rules/#Consolidate) up to 6″ instead of up to 3\"."
},
"Ability§Life Drain": {
"text": "When resolving an attack made with a melee weapon by this model, a [saving throw](https://wahapedia.ru/wh40k9ed/the-rules/core-rules/#4.-Saving-Throw) cannot be made unless it is an [invulnerable saving throw](https://wahapedia.ru/wh40k9ed/the-rules/core-rules/#Invulnerable-Saves)."
@ -60,7 +60,7 @@
"text": "This model has a 4+ [invulnerable save](https://wahapedia.ru/wh40k9ed/the-rules/core-rules/#Invulnerable-Saves)."
},
"Ability§Polymorphine": {
"text": "During deployment, you can set up this model in disguise instead of setting it up on the battlefield. At the end of any of your [Movement phases](https://wahapedia.ru/wh40k9ed/the-rules/core-rules/#MOVEMENT-PHASE), this model can revert to its true form set it up anywhere on the battlefield that is more than D6+3 away from any enemy models. For example, if you roll a 4, the model can be set up anywhere that is more than 7″ away from any enemy model."
"text": "During deployment, you can set up this model in disguise instead of setting it up on the battlefield. At the end of any of your [Movement phases](https://wahapedia.ru/wh40k9ed/the-rules/core-rules/#MOVEMENT-PHASE), this model can revert to its true form set it up anywhere on the battlefield that is more than D6+3\" away from any enemy models. For example, if you roll a 4, the model can be set up anywhere that is more than 7″ away from any enemy model."
},
"Ability§Psychic Assassin": {
"text": "Attacks made by this model can target a [PSYKER](https://wahapedia.ru/wh40k9ed/the-rules/core-rules/#PSYCHIC-PHASE) [CHARACTER](https://wahapedia.ru/wh40k9ed/the-rules/core-rules/#Characters) even if it is not the closest enemy unit. In addition, this model can shoot with its psyk-out grenades in the same phase that it shoots with its animus speculum."
@ -81,6 +81,39 @@
"stats": {
"Power Level": {
"value": 5
},
"Points": {
"value": 100
},
"M": {
"value": 7
},
"WS": {
"value": 2
},
"BS": {
"value": 2
},
"S": {
"value": 4
},
"T": {
"value": 4
},
"W": {
"value": 5
},
"A": {
"value": 5
},
"Ld": {
"value": 9
},
"Sv": {
"value": 6
},
"Base": {
"value": "32mm"
}
},
"keywords": {
@ -109,18 +142,10 @@
"Ability§Lightning Reflexes",
"Ability§Polymorphine",
"Ability§Hit and Run",
"Ability§Reign of Confusion",
{
"item": "Model§Callidus Assassin",
"stats": {
"Points": {
"visibility": "hidden"
}
}
}
"Ability§Reign of Confusion"
]
},
"text": "A Callidus Assassin is a single model equipped with: neural shredder; phase sword; poison blades.",
"text": "A Callidus Assassin is a single model equipped with: neural shredder; phase sword; poison blades.\n\n\n\nnull",
"meta": {
"Publication": "[Psychic Awakening: War of the Spider (Expansion) 8th ed. Indomitus 1.4 @2022-02-04](https://www.warhammer-community.com/wp-content/uploads/2020/07/VXO1CdVZDRexbsb1.pdf)"
}
@ -129,6 +154,39 @@
"stats": {
"Power Level": {
"value": 5
},
"Points": {
"value": 100
},
"M": {
"value": 7
},
"WS": {
"value": 2
},
"BS": {
"value": 2
},
"S": {
"value": 4
},
"T": {
"value": 4
},
"W": {
"value": 5
},
"A": {
"value": 4
},
"Ld": {
"value": 9
},
"Sv": {
"value": 6
},
"Base": {
"value": "32mm"
}
},
"keywords": {
@ -158,18 +216,10 @@
"Ability§Abomination",
"Ability§Life Drain",
"Ability§Etherium",
"Ability§Psychic Assassin",
{
"item": "Model§Culexus Assassin",
"stats": {
"Points": {
"visibility": "hidden"
}
}
}
"Ability§Psychic Assassin"
]
},
"text": "A Culexus Assassin is a single model equipped with: animus speculum; psyk-out grenades.",
"text": "A Culexus Assassin is a single model equipped with: animus speculum; psyk-out grenades.\n\n\n\nnull",
"meta": {
"Publication": "[Psychic Awakening: War of the Spider (Expansion) 8th ed. Indomitus 1.4 @2022-02-04](https://www.warhammer-community.com/wp-content/uploads/2020/07/VXO1CdVZDRexbsb1.pdf)"
}
@ -178,6 +228,39 @@
"stats": {
"Power Level": {
"value": 5
},
"Points": {
"value": 100
},
"M": {
"value": 7
},
"WS": {
"value": 2
},
"BS": {
"value": 2
},
"S": {
"value": 4
},
"T": {
"value": 4
},
"W": {
"value": 6
},
"A": {
"value": 6
},
"Ld": {
"value": 9
},
"Sv": {
"value": 6
},
"Base": {
"value": "32mm"
}
},
"keywords": {
@ -207,18 +290,10 @@
"Ability§Bio-meltdown",
"Ability§Sentinel Array",
"Ability§Frenzon",
"Ability§Killing Rampage",
{
"item": "Model§Eversor Assassin",
"stats": {
"Points": {
"visibility": "hidden"
}
}
}
"Ability§Killing Rampage"
]
},
"text": "An Eversor Assassin is a single model equipped with: executioner pistol; neuro-gauntlet; power sword; melta bombs.",
"text": "An Eversor Assassin is a single model equipped with: executioner pistol; neuro-gauntlet; power sword; melta bombs.\n\n\n\nnull",
"meta": {
"Publication": "[Psychic Awakening: War of the Spider (Expansion) 8th ed. Indomitus 1.4 @2022-02-04](https://www.warhammer-community.com/wp-content/uploads/2020/07/VXO1CdVZDRexbsb1.pdf)"
}
@ -227,6 +302,39 @@
"stats": {
"Power Level": {
"value": 5
},
"Points": {
"value": 100
},
"M": {
"value": 7
},
"WS": {
"value": 2
},
"BS": {
"value": 2
},
"S": {
"value": 4
},
"T": {
"value": 4
},
"W": {
"value": 5
},
"A": {
"value": 5
},
"Ld": {
"value": 9
},
"Sv": {
"value": 6
},
"Base": {
"value": "32mm"
}
},
"keywords": {
@ -257,175 +365,19 @@
"Ability§Faultless Aim",
"Ability§Head Shot",
"Ability§Spymask",
"Ability§Stealth Suit",
{
"item": "Model§Vindicare Assassin",
"stats": {
"Points": {
"visibility": "hidden"
}
}
}
"Ability§Stealth Suit"
]
},
"text": "A Vindicare Assassin is a single model equipped with: exitus pistol; exitus rifle; blind grenades.",
"text": "A Vindicare Assassin is a single model equipped with: exitus pistol; exitus rifle; blind grenades.\n\n\n\nnull",
"meta": {
"Publication": "[Psychic Awakening: War of the Spider (Expansion) 8th ed. Indomitus 1.4 @2022-02-04](https://www.warhammer-community.com/wp-content/uploads/2020/07/VXO1CdVZDRexbsb1.pdf)"
}
},
"Model§Callidus Assassin": {
"stats": {
"Points": {
"value": 100
},
"M": {
"value": 7
},
"WS": {
"value": 2
},
"BS": {
"value": 2
},
"S": {
"value": 4
},
"T": {
"value": 4
},
"W": {
"value": 5
},
"A": {
"value": 5
},
"Ld": {
"value": 9
},
"Sv": {
"value": 6
},
"Base": {
"value": "32mm"
}
}
},
"Model§Culexus Assassin": {
"stats": {
"Points": {
"value": 100
},
"M": {
"value": 7
},
"WS": {
"value": 2
},
"BS": {
"value": 2
},
"S": {
"value": 4
},
"T": {
"value": 4
},
"W": {
"value": 5
},
"A": {
"value": 4
},
"Ld": {
"value": 9
},
"Sv": {
"value": 6
},
"Base": {
"value": "32mm"
}
}
},
"Model§Eversor Assassin": {
"stats": {
"Points": {
"value": 100
},
"M": {
"value": 7
},
"WS": {
"value": 2
},
"BS": {
"value": 2
},
"S": {
"value": 4
},
"T": {
"value": 4
},
"W": {
"value": 6
},
"A": {
"value": 6
},
"Ld": {
"value": 9
},
"Sv": {
"value": 6
},
"Base": {
"value": "32mm"
}
}
},
"Model§Vindicare Assassin": {
"stats": {
"Points": {
"value": 100
},
"M": {
"value": 7
},
"WS": {
"value": 2
},
"BS": {
"value": 2
},
"S": {
"value": 4
},
"T": {
"value": 4
},
"W": {
"value": 5
},
"A": {
"value": 5
},
"Ld": {
"value": 9
},
"Sv": {
"value": 6
},
"Base": {
"value": "32mm"
}
}
},
"Roster§Army": {},
"Weapon§Animus speculum": {
"stats": {
"AP": {
"value": "-4"
"value": -4
},
"D": {
"value": 1
@ -465,7 +417,7 @@
"Weapon§Executioner pistol": {
"stats": {
"AP": {
"value": "-1"
"value": -1
},
"D": {
"value": 1
@ -485,7 +437,7 @@
"Weapon§Exitus pistol": {
"stats": {
"AP": {
"value": "-3"
"value": -3
},
"D": {
"value": "D3"
@ -505,7 +457,7 @@
"Weapon§Exitus rifle": {
"stats": {
"AP": {
"value": "-3"
"value": -3
},
"D": {
"value": "D3"
@ -525,7 +477,7 @@
"Weapon§Melta bombs": {
"stats": {
"AP": {
"value": "-4"
"value": -4
},
"D": {
"value": "D6"
@ -565,13 +517,13 @@
"Weapon§Neuro-gauntlet": {
"stats": {
"AP": {
"value": "-1"
"value": -1
},
"D": {
"value": 1
},
"S": {
"value": "+1"
"value": 1
},
"Type": {
"value": "Melee"
@ -585,7 +537,7 @@
"Weapon§Phase sword": {
"stats": {
"AP": {
"value": "-3"
"value": -3
},
"D": {
"value": 2
@ -605,7 +557,7 @@
"Weapon§Poison blades": {
"stats": {
"AP": {
"value": "-1"
"value": -1
},
"D": {
"value": 1
@ -625,13 +577,13 @@
"Weapon§Power sword": {
"stats": {
"AP": {
"value": "-3"
"value": -3
},
"D": {
"value": 1
},
"S": {
"value": "+1"
"value": 1
},
"Type": {
"value": "Melee"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -29,8 +29,8 @@ processInfo = (data,factionKey) => {
genre: 'sci-fi',
publisher: 'Games Workshop',
url: 'https://warhammer40000.com/',
notes: 'This manifest is provided for the purposes of testing the features of *Rosterizer* and is not intended for distribution.\n\nThe data included herein was programatically compiled from freely-available sources on the internet and likely contains some errors. Use with caution.',
revision: '0.0.6',
notes: '0.0.7: single-model units no longer have any "model" asssets\n\n0.0.6: "source" keyword category\n\n0.0.5: add relics\n\nThis manifest is provided for the purposes of testing the features of *Rosterizer* and is not intended for distribution.\n\nThe data included herein was programatically compiled from freely-available sources on the internet and likely contains some errors. Use with caution.',
revision: '0.0.7',
wip: true,
dependencies: [
{
@ -172,14 +172,14 @@ processAbilities = (data,assetCatalog) => {
if(!ability.is_other_wargear){
itemKey = 'Ability§' + ability.name;
if(!assetCatalog[itemKey]) assetCatalog[itemKey] = tempAbility;
else assetCatalog[itemKey].text += '\n\nERROR: The following text was found on another ability with the same name.\n\n' + formatText(ability.description,shouldLog);
else assetCatalog[itemKey].text += '\n\n***ERROR***—*The following text was found on another ability with the same name:* \n' + formatText(ability.description,shouldLog);
}else{
itemKey = 'Wargear§' + ability.name;
let abilityCostArr = data.abilities.datasheets_abilities.filter(datasheets_ability => datasheets_ability.ability_id === ability.ability_id).map(datasheets_ability => datasheets_ability.cost);
let costMode = findMode(abilityCostArr);
if(costMode) tempAbility.stats = {Points: {value: numerize(costMode)}};
if(!assetCatalog[itemKey]) assetCatalog[itemKey] = tempAbility;
else assetCatalog[itemKey].text += '\n\nERROR: The following text was found on another wargear with the same name.\n\n' + formatText(ability.description,shouldLog);
else assetCatalog[itemKey].text += '\n\n***ERROR***—*The following text was found on another wargear with the same name:* \n' + formatText(ability.description,shouldLog);
}
let subFactTest = new RegExp(`<${data.factCurrent}>`, 'gi');
if(subFactTest.test(assetCatalog[itemKey].text)){
@ -302,7 +302,7 @@ createWargearStat = (i,wargearArr,modelLoadout,assetCatalog) => {
return tempStat
}
processRelics = (data,assetCatalog) => {
console.log(data.relics.relic_list)
// console.log(data.relics.relic_list)
data.relics.relic_list?.forEach(relic => {
let weapName = relic.name.replace(/(1: |2: |3: )/,'').replace(/в/g,'d').replace(/^"*(.*[^"])"*$/g,'$1').replace(/^\s*(.*[^\s])\s*$/g,'$1');
let tempWeapon = {stats:{
@ -402,6 +402,8 @@ formatText = (text,log = false) => {
boldPattern2: [/<span[\s]+class="kwb">((?:.(?!\<\/span\>))*.)<\/span>/g,'<b>$1</b>'],
underPattern2: [/<span[\s]+class="kwbu">((?:.(?!\<\/span\>))*.)<\/span>/g,'<i>$1<i>'],
ttPattern2: [/<span[\s]+class="tt">((?:.(?!\<\/span\>))*.)<\/span>/g,'<b>$1</b>'],
ttPattern3: [/<span[\s]+class="tt kwbu">((?:.(?!\<\/span\>))*.)<\/span>/g,'<b>$1</b>'],
ttPattern4: [/<span[\s]+class="tt kwbu">((?:.(?!\<\/span\>))*.)<\/span>/g,'<b>$1</b>'],
h_customPattern: [/<span[\s]+class="h_custom">((?:.(?!\<\/span\>))*.)<\/span>/g,'<b>$1</b>'],
redfontPattern: [/<span[\s]+class="redfont">((?:.(?!\<\/span\>))*.)<\/span>/g,'<b>$1</b>'],
}
@ -413,7 +415,8 @@ formatText = (text,log = false) => {
boldTranslationPattern: [/<\/?b>/g,'**'],
doubleBoldPattern: [/\*\*\*\*/g,''],
doubleBoldPattern2: [/\*\*\s\*\*/g,' '],
italicsTranslationPattern: [/<\/?i>/g,'*']
italicsTranslationPattern: [/<\/?i>/g,'*'],
doublePrime: [/\s([0-9]+)"(\.*)\s/g,' $1″$2 '],
}
newText = text.replace(/kwb2/g,'kwb');
if(log) console.log(newText)
@ -466,85 +469,34 @@ processUnits = (data,assetCatalog) => {
},keywords:{},assets:{}};
let models = data.models.filter(model => model.datasheet_id === unitId);
// console.log(unitId,models)
let singleModelUnit = models[0]?.models_per_unit == 1 && models.length == 1;
if(models[0]?.line === 1 && models[0]?.models_per_unit.includes('-')){
tempItem.stats.model = {
value: data.models.filter(model => model.datasheet_id === unitId && model.line === 1)[0].itemKey
}
}
let modelDamage = data.damage.filter(dmgLine => dmgLine.datasheet_id == unitId);
if(modelDamage.length){
let modelItemKey = models.filter(model => model.datasheet_id === unitId)[0].itemKey;
// console.log(unitId,modelItemKey)
assetCatalog[modelItemKey].stats['W'] = {
value: numerize(assetCatalog[modelItemKey].stats['W'].value),
max: numerize(assetCatalog[modelItemKey].stats['W'].value),
min: 1,
dynamic: true,
increment: {value: 1},
statType: 'numeric',
visibility: 'always',
}
assetCatalog[modelItemKey].rules = assetCatalog[modelItemKey].rules || {};
assetCatalog[modelItemKey].rules.dynamicDamageMid = generateDamageRule(modelDamage[0],modelDamage[2]);
assetCatalog[modelItemKey].rules.dynamicDamageLow = generateDamageRule(modelDamage[0],modelDamage[3]);
}
let options = data.options.filter(option => option.datasheet_id === unitId);
tempItem.text = formatText(datasheet.unit_composition + '\n\n' + options.map(option => (option.button || '') + ' ' + option.description).join('\n\n') + '\n\n' + datasheet.psyker);
if(models[0]?.models_per_unit?.includes('-')){
tempItem.stats[datasheet.name] = {
statType: 'numeric',
dynamic: true,
visibility: 'active',
};
let stat = tempItem.stats[datasheet.name];
let range = models[0].models_per_unit.split('-');
stat.value = Number(range[0]);
stat.min = Number(range[0]);
stat.max = Number(range[1]);
let basePlThresh = stat.min;
if(!(stat.max % stat.min)){
// console.log(datasheet.name,'has a clean threshold')
stat.increment = {value: numerize(stat.min)};
}
else if(!((stat.max + 1) % (stat.min + 1)) && models[1]?.models_per_unit == 1){
// console.log(datasheet.name,'has a sergeant')
stat.increment = {value: numerize(stat.min) + 1};
// console.log(stat)
basePlThresh ++;
}else tempItem.text += '\n\nERROR: there might be a problem with incrementation that will require inputting by hand.';
let PLArr = datasheet.unit_composition.split(/(\<b\>Power Rating |\<\/b\>)/).map(el => Number(el.replace('+','plus'))).filter(el => !isNaN(el));
if(PLArr.length){
let tempInc = PLArr[0] - datasheet.power_points;
// console.log(datasheet.name,basePlThresh,tempInc,PLArr)
for (let i = 0; i < PLArr.length; i++) {
if(PLArr[i] !== ((i+1) * tempInc) + Number(datasheet.power_points)){
// console.log(PLArr[i],tempInc,Number(datasheet.power_points), ((i+1) * tempInc) + Number(datasheet.power_points))
tempItem.text += '\n\nERROR: there might be a problem with Power Rating that will require a custom rule.';
tempInc = 0;
break;
}
}
if(tempInc){
tempItem.stats.poweri = {value:tempInc};
for (let i = 0; i < PLArr.length; i++) {
tempItem.stats['power'+(i+1)] = {
"value": (basePlThresh * (i + 1)) + 1
}
}
}
}else if(datasheet.unit_composition.includes('Power Rating')) tempItem.text += '\n\nERROR: there might be a problem with Power Rating that will require a custom rule.';
}
let modelList = [];
models.forEach(model => {
let [minQty,maxQty] = model.models_per_unit.split('-').map(qty => Number(qty));
if(minQty){
let tempTrait = {item: model.itemKey};
if(minQty > 1) tempTrait.quantity = minQty;
// console.log(datasheet.name,model.name,model.models_per_unit,models.length)
if(model.models_per_unit == 1 && models.length == 1){
tempTrait.stats = tempTrait.stats || {};
tempTrait.stats.Points = tempTrait.stats.Points || {};
tempTrait.stats.Points.visibility = 'hidden';
}
// console.log(tempTrait)
if(Object.keys(tempTrait).length === 1) tempTrait = model.itemKey;
tempItem.assets.traits = tempItem.assets.traits || [];
tempItem.assets.traits.push(tempTrait);
modelList.push(model)
}
if(minQty > 1 || maxQty > 1){
tempItem.allowed = tempItem.allowed || {};
tempItem.allowed.items = tempItem.allowed.items || [];
tempItem.allowed.items.push(model.itemKey)
}
});
let abilities = data.abilities.composed.filter(ability => ability.datasheet_id === unitId);
let abilityList = abilities.filter(ability => ability.datasheet_id === unitId && !ability.is_other_wargear);
let wargearList = abilities.filter(ability => ability.datasheet_id === unitId && ability.is_other_wargear);
@ -559,7 +511,7 @@ processUnits = (data,assetCatalog) => {
tempItem.assets.traits.push('Psychic Power§Smite');
}
const order = ['Ability§', 'Wargear§', 'Psychic Power§', 'Model§'];
tempItem.assets.traits.sort((a, b) => stringSimilarity.findBestMatch((a.item || a),order).bestMatchIndex - stringSimilarity.findBestMatch((b.item || b),order).bestMatchIndex);
tempItem.assets.traits?.sort((a, b) => stringSimilarity.findBestMatch((a.item || a),order).bestMatchIndex - stringSimilarity.findBestMatch((b.item || b),order).bestMatchIndex);
Array.from(new Set(data.psychicPowers.map(power => power.type))).forEach(discipline => {
// console.log(discipline)
@ -584,6 +536,8 @@ processUnits = (data,assetCatalog) => {
value: 0,
statType: 'rank',
statOrder: 10,
group: 'Loadout',
groupOrder: 2,
ranks: {
0: {order: 0,number: 0,icons: ['cancel']},
1: {order: 1,number: 1,icons: ['confirmed'],traits: [{trait: tempWargear}]}
@ -634,6 +588,87 @@ processUnits = (data,assetCatalog) => {
}
});
if(models[0]?.models_per_unit?.includes('-')){
tempItem.stats[datasheet.name] = {
statType: 'numeric',
dynamic: true,
visibility: 'active',
group: 'Loadout',
groupOrder: 2,
};
let stat = tempItem.stats[datasheet.name];
let range = models[0].models_per_unit.split('-');
stat.value = Number(range[0]);
stat.min = Number(range[0]);
stat.max = Number(range[1]);
let basePlThresh = stat.min;
if(!(stat.max % stat.min)){
// console.log(datasheet.name,'has a clean threshold')
stat.increment = {value: numerize(stat.min)};
}
else if(!((stat.max + 1) % (stat.min + 1)) && models[1]?.models_per_unit == 1){
// console.log(datasheet.name,'has a sergeant')
stat.increment = {value: numerize(stat.min) + 1};
// console.log(stat)
basePlThresh ++;
}else tempItem.text += '\n\n***ERROR***—*there might be a problem with incrementation that will require inputting by hand.*';
let PLArr = datasheet.unit_composition.split(/(\<b\>Power Rating |\<\/b\>)/).map(el => Number(el.replace('+','plus'))).filter(el => !isNaN(el));
if(PLArr.length){
let tempInc = PLArr[0] - datasheet.power_points;
// console.log(datasheet.name,basePlThresh,tempInc,PLArr)
for (let i = 0; i < PLArr.length; i++) {
if(PLArr[i] !== ((i+1) * tempInc) + Number(datasheet.power_points)){
// console.log(PLArr[i],tempInc,Number(datasheet.power_points), ((i+1) * tempInc) + Number(datasheet.power_points))
tempItem.text += '\n\n***ERROR***—*there might be a problem with Power Rating that will require a custom rule.*';
tempInc = 0;
break;
}
}
if(tempInc){
tempItem.stats.poweri = {value:tempInc};
for (let i = 0; i < PLArr.length; i++) {
tempItem.stats['power'+(i+1)] = {
"value": (basePlThresh * (i + 1)) + 1
}
}
}
}else if(datasheet.unit_composition.includes('Power Rating')) tempItem.text += '\n\n***ERROR***—*there might be a problem with Power Rating that will require a custom rule.*';
}
models.forEach(model => {
let [minQty,maxQty] = model.models_per_unit.split('-').map(qty => Number(qty));
if(minQty){
// console.log(datasheet.name,model.name,model.models_per_unit,models.length)
if(singleModelUnit){
let modelAsset = assetCatalog[model.itemKey];
// console.log(modelAsset)
tempItem = {
...tempItem,
stats: {
...tempItem.stats,
...modelAsset.stats,
},
rules: {
...tempItem.rules,
...modelAsset.rules,
}
}
if(!Object.keys(tempItem.rules).length) delete tempItem.rules;
}else{
let tempTrait = {item: model.itemKey};
if(minQty > 1) tempTrait.quantity = minQty;
// console.log(tempTrait)
if(Object.keys(tempTrait).length === 1) tempTrait = model.itemKey;
tempItem.assets.traits = tempItem.assets.traits || [];
tempItem.assets.traits.push(tempTrait);
}
}
if(minQty > 1 || maxQty > 1 || !minQty){
tempItem.allowed = tempItem.allowed || {};
tempItem.allowed.items = tempItem.allowed.items || [];
tempItem.allowed.items.push(model.itemKey)
}
});
let source = data.sources.filter(source => source.source_id == datasheet.source_id)[0];
// console.log(source)
if(source){
@ -649,26 +684,16 @@ processUnits = (data,assetCatalog) => {
let factionKeywords = data.keywords.filter(keyword => keyword.datasheet_id === unitId && keyword.is_faction_keyword);
if(factionKeywords.length) tempItem.keywords.Faction = factionKeywords.map(keyword => keyword.keyword);
let modelDamage = data.damage.filter(dmgLine => dmgLine.datasheet_id == unitId);
if(modelDamage.length){
let modelItemKey = models.filter(model => model.datasheet_id === unitId)[0].itemKey;
// console.log(unitId,modelItemKey)
assetCatalog[modelItemKey].stats['W'] = {
value: numerize(assetCatalog[modelItemKey].stats['W'].value),
max: numerize(assetCatalog[modelItemKey].stats['W'].value),
min: 1,
dynamic: true,
increment: {value: 1},
statType: 'numeric',
visibility: 'always',
}
assetCatalog[modelItemKey].rules = assetCatalog[modelItemKey].rules || {};
assetCatalog[modelItemKey].rules.dynamicDamageMid = generateDamageRule(modelDamage[0],modelDamage[2]);
assetCatalog[modelItemKey].rules.dynamicDamageLow = generateDamageRule(modelDamage[0],modelDamage[3]);
}
assetCatalog[datasheet.role + '§' + datasheet.name] = tempItem;
});
data.datasheets.forEach(datasheet => {
let unitId = datasheet.datasheet_id;
let models = data.models.filter(model => model.datasheet_id === unitId);
console.log(unitId,data.models,unitId)
let modelItemKey = models.filter(model => model.datasheet_id === unitId)[0]?.itemKey;
let singleModelUnit = models[0]?.models_per_unit == 1 && models.length == 1;
if(singleModelUnit && modelItemKey) delete assetCatalog[modelItemKey];
});
}
generateDamageRule = (damageRows,currentRow) => {
// console.log(damageRows,currentRow)