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", "name": "40k9e",
"revision": "0.2.2", "revision": "0.2.7",
"game": "Warhammer 40,000", "game": "Warhammer 40,000",
"genre": "sci-fi", "genre": "sci-fi",
"publisher": "Games Workshop", "publisher": "Games Workshop",
@ -544,6 +544,78 @@
"tracked": true, "tracked": true,
"statType": "numeric" "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": { "model": {
"value": null, "value": null,
"statType": "term", "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": { "crusadeStats": {
"evals": [ "evals": [
{ {
@ -1879,6 +1912,45 @@
"iterations": 1 "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": { "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", "name": "Astra Cartographica",
"revision": "0.0.6", "revision": "0.0.7",
"game": "Warhammer 40,000", "game": "Warhammer 40,000",
"genre": "sci-fi", "genre": "sci-fi",
"publisher": "Games Workshop", "publisher": "Games Workshop",
"url": "https://warhammer40000.com/", "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, "wip": true,
"dependencies": [ "dependencies": [
{ {
@ -18,7 +18,7 @@
"assetTaxonomy": {}, "assetTaxonomy": {},
"assetCatalog": { "assetCatalog": {
"Ability§Agent of the Imperium": { "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": { "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)." "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" "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": { "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)" "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 "value": 3
}, },
"BS": { "BS": {
"value": null "value": "null"
}, },
"S": { "S": {
"value": 4 "value": 4
@ -121,7 +128,7 @@
"value": 4 "value": 4
}, },
"Base": { "Base": {
"value": "32mm" "value": "25mm"
} }
} }
}, },
@ -158,7 +165,7 @@
"value": 4 "value": 4
}, },
"Base": { "Base": {
"value": "32mm" "value": "25mm"
}, },
"loadout1": { "loadout1": {
"value": "Household pistol", "value": "Household pistol",
@ -384,7 +391,7 @@
"value": 5 "value": 5
}, },
"Base": { "Base": {
"value": "32mm" "value": "25mm"
}, },
"loadout1": { "loadout1": {
"value": "Dartmask", "value": "Dartmask",
@ -610,7 +617,7 @@
"value": 5 "value": 5
}, },
"Base": { "Base": {
"value": "32mm" "value": "25mm"
}, },
"loadout1": { "loadout1": {
"value": "Voltaic pistol", "value": "Voltaic pistol",
@ -773,7 +780,7 @@
"value": 4 "value": 4
}, },
"Base": { "Base": {
"value": "32mm" "value": "25mm"
}, },
"loadout1": { "loadout1": {
"value": "Navis shotgun", "value": "Navis shotgun",
@ -1167,7 +1174,7 @@
"value": 4 "value": 4
}, },
"Base": { "Base": {
"value": "32mm" "value": "25mm"
}, },
"loadout1": { "loadout1": {
"value": "Navis shotgun", "value": "Navis shotgun",
@ -1442,7 +1449,7 @@
"value": 5 "value": 5
}, },
"Base": { "Base": {
"value": "32mm" "value": "25mm"
}, },
"loadout1": { "loadout1": {
"value": "Laspistol", "value": "Laspistol",
@ -1605,7 +1612,7 @@
"value": 4 "value": 4
}, },
"Base": { "Base": {
"value": "32mm" "value": "25mm"
}, },
"loadout1": { "loadout1": {
"value": "Artificer shotgun", "value": "Artificer shotgun",
@ -1783,7 +1790,7 @@
"value": 4 "value": 4
}, },
"Base": { "Base": {
"value": "32mm" "value": "25mm"
}, },
"loadout1": { "loadout1": {
"value": "Lasgun", "value": "Lasgun",
@ -1938,6 +1945,8 @@
"value": 0, "value": 0,
"statType": "rank", "statType": "rank",
"statOrder": 10, "statOrder": 10,
"group": "Loadout",
"groupOrder": 2,
"ranks": { "ranks": {
"0": { "0": {
"order": 0, "order": 0,
@ -1973,6 +1982,8 @@
"value": 0, "value": 0,
"statType": "rank", "statType": "rank",
"statOrder": 10, "statOrder": 10,
"group": "Loadout",
"groupOrder": 2,
"ranks": { "ranks": {
"0": { "0": {
"order": 0, "order": 0,
@ -2035,7 +2046,7 @@
"Model§Navis Sergeant-at-Arms" "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": { "allowed": {
"items": [ "items": [
"Model§Navis Armsman" "Model§Navis Armsman"
@ -2057,6 +2068,8 @@
"statType": "numeric", "statType": "numeric",
"dynamic": true, "dynamic": true,
"visibility": "active", "visibility": "active",
"group": "Loadout",
"groupOrder": 2,
"value": 4, "value": 4,
"min": 4, "min": 4,
"max": 8, "max": 8,
@ -2102,10 +2115,11 @@
"Model§Voidmaster" "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": { "allowed": {
"items": [ "items": [
"Model§Voidsmen" "Model§Voidsmen",
"Model§Canid"
] ]
}, },
"meta": { "meta": {
@ -2210,7 +2224,7 @@
"Weapon§Chainfist": { "Weapon§Chainfist": {
"stats": { "stats": {
"AP": { "AP": {
"value": "-4" "value": -4
}, },
"D": { "D": {
"value": "D3" "value": "D3"
@ -2250,7 +2264,7 @@
"Weapon§Dartmask": { "Weapon§Dartmask": {
"stats": { "stats": {
"AP": { "AP": {
"value": "-2" "value": -2
}, },
"D": { "D": {
"value": 1 "value": 1
@ -2270,13 +2284,13 @@
"Weapon§Death Cult power blade": { "Weapon§Death Cult power blade": {
"stats": { "stats": {
"AP": { "AP": {
"value": "-3" "value": -3
}, },
"D": { "D": {
"value": 1 "value": 1
}, },
"S": { "S": {
"value": "+1" "value": 1
}, },
"Type": { "Type": {
"value": "Melee" "value": "Melee"
@ -2289,7 +2303,7 @@
"Weapon§Demolition charge": { "Weapon§Demolition charge": {
"stats": { "stats": {
"AP": { "AP": {
"value": "-3" "value": -3
}, },
"D": { "D": {
"value": 2 "value": 2
@ -2329,7 +2343,7 @@
"Weapon§Household pistol": { "Weapon§Household pistol": {
"stats": { "stats": {
"AP": { "AP": {
"value": "-3" "value": -3
}, },
"D": { "D": {
"value": 2 "value": 2
@ -2386,7 +2400,7 @@
"Weapon§Meltagun": { "Weapon§Meltagun": {
"stats": { "stats": {
"AP": { "AP": {
"value": "-4" "value": -4
}, },
"D": { "D": {
"value": "D6" "value": "D6"
@ -2406,13 +2420,13 @@
"Weapon§Monomolecular cane-rapier": { "Weapon§Monomolecular cane-rapier": {
"stats": { "stats": {
"AP": { "AP": {
"value": "-2" "value": -2
}, },
"D": { "D": {
"value": 1 "value": 1
}, },
"S": { "S": {
"value": "+1" "value": 1
}, },
"Type": { "Type": {
"value": "Melee" "value": "Melee"
@ -2445,7 +2459,7 @@
"Weapon§Navis las-volley": { "Weapon§Navis las-volley": {
"stats": { "stats": {
"AP": { "AP": {
"value": "-1" "value": -1
}, },
"D": { "D": {
"value": 1 "value": 1
@ -2483,13 +2497,13 @@
"Weapon§Power axe": { "Weapon§Power axe": {
"stats": { "stats": {
"AP": { "AP": {
"value": "-2" "value": -2
}, },
"D": { "D": {
"value": 1 "value": 1
}, },
"S": { "S": {
"value": "+2" "value": 2
}, },
"Type": { "Type": {
"value": "Melee" "value": "Melee"
@ -2502,13 +2516,13 @@
"Weapon§Power sword": { "Weapon§Power sword": {
"stats": { "stats": {
"AP": { "AP": {
"value": "-3" "value": -3
}, },
"D": { "D": {
"value": 1 "value": 1
}, },
"S": { "S": {
"value": "+1" "value": 1
}, },
"Type": { "Type": {
"value": "Melee" "value": "Melee"
@ -2521,7 +2535,7 @@
"Weapon§Standard": { "Weapon§Standard": {
"stats": { "stats": {
"AP": { "AP": {
"value": "-3" "value": -3
}, },
"D": { "D": {
"value": 1 "value": 1
@ -2540,7 +2554,7 @@
"Weapon§Supercharge": { "Weapon§Supercharge": {
"stats": { "stats": {
"AP": { "AP": {
"value": "-3" "value": -3
}, },
"D": { "D": {
"value": 2 "value": 2
@ -2560,7 +2574,7 @@
"Weapon§Voidsman rotor cannon": { "Weapon§Voidsman rotor cannon": {
"stats": { "stats": {
"AP": { "AP": {
"value": "-1" "value": -1
}, },
"D": { "D": {
"value": 1 "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", "name": "Officio Assassinorum",
"revision": "0.0.6", "revision": "0.0.7",
"game": "Warhammer 40,000", "game": "Warhammer 40,000",
"genre": "sci-fi", "genre": "sci-fi",
"publisher": "Games Workshop", "publisher": "Games Workshop",
"url": "https://warhammer40000.com/", "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, "wip": true,
"dependencies": [ "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." "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": { "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": { "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)." "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)." "text": "This model has a 4+ [invulnerable save](https://wahapedia.ru/wh40k9ed/the-rules/core-rules/#Invulnerable-Saves)."
}, },
"Ability§Polymorphine": { "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": { "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." "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": { "stats": {
"Power Level": { "Power Level": {
"value": 5 "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": { "keywords": {
@ -109,18 +142,10 @@
"Ability§Lightning Reflexes", "Ability§Lightning Reflexes",
"Ability§Polymorphine", "Ability§Polymorphine",
"Ability§Hit and Run", "Ability§Hit and Run",
"Ability§Reign of Confusion", "Ability§Reign of Confusion"
{
"item": "Model§Callidus Assassin",
"stats": {
"Points": {
"visibility": "hidden"
}
}
}
] ]
}, },
"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": { "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)" "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": { "stats": {
"Power Level": { "Power Level": {
"value": 5 "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": { "keywords": {
@ -158,18 +216,10 @@
"Ability§Abomination", "Ability§Abomination",
"Ability§Life Drain", "Ability§Life Drain",
"Ability§Etherium", "Ability§Etherium",
"Ability§Psychic Assassin", "Ability§Psychic Assassin"
{
"item": "Model§Culexus Assassin",
"stats": {
"Points": {
"visibility": "hidden"
}
}
}
] ]
}, },
"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": { "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)" "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": { "stats": {
"Power Level": { "Power Level": {
"value": 5 "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": { "keywords": {
@ -207,18 +290,10 @@
"Ability§Bio-meltdown", "Ability§Bio-meltdown",
"Ability§Sentinel Array", "Ability§Sentinel Array",
"Ability§Frenzon", "Ability§Frenzon",
"Ability§Killing Rampage", "Ability§Killing Rampage"
{
"item": "Model§Eversor Assassin",
"stats": {
"Points": {
"visibility": "hidden"
}
}
}
] ]
}, },
"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": { "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)" "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": { "stats": {
"Power Level": { "Power Level": {
"value": 5 "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": { "keywords": {
@ -257,175 +365,19 @@
"Ability§Faultless Aim", "Ability§Faultless Aim",
"Ability§Head Shot", "Ability§Head Shot",
"Ability§Spymask", "Ability§Spymask",
"Ability§Stealth Suit", "Ability§Stealth Suit"
{
"item": "Model§Vindicare Assassin",
"stats": {
"Points": {
"visibility": "hidden"
}
}
}
] ]
}, },
"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": { "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)" "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": {}, "Roster§Army": {},
"Weapon§Animus speculum": { "Weapon§Animus speculum": {
"stats": { "stats": {
"AP": { "AP": {
"value": "-4" "value": -4
}, },
"D": { "D": {
"value": 1 "value": 1
@ -465,7 +417,7 @@
"Weapon§Executioner pistol": { "Weapon§Executioner pistol": {
"stats": { "stats": {
"AP": { "AP": {
"value": "-1" "value": -1
}, },
"D": { "D": {
"value": 1 "value": 1
@ -485,7 +437,7 @@
"Weapon§Exitus pistol": { "Weapon§Exitus pistol": {
"stats": { "stats": {
"AP": { "AP": {
"value": "-3" "value": -3
}, },
"D": { "D": {
"value": "D3" "value": "D3"
@ -505,7 +457,7 @@
"Weapon§Exitus rifle": { "Weapon§Exitus rifle": {
"stats": { "stats": {
"AP": { "AP": {
"value": "-3" "value": -3
}, },
"D": { "D": {
"value": "D3" "value": "D3"
@ -525,7 +477,7 @@
"Weapon§Melta bombs": { "Weapon§Melta bombs": {
"stats": { "stats": {
"AP": { "AP": {
"value": "-4" "value": -4
}, },
"D": { "D": {
"value": "D6" "value": "D6"
@ -565,13 +517,13 @@
"Weapon§Neuro-gauntlet": { "Weapon§Neuro-gauntlet": {
"stats": { "stats": {
"AP": { "AP": {
"value": "-1" "value": -1
}, },
"D": { "D": {
"value": 1 "value": 1
}, },
"S": { "S": {
"value": "+1" "value": 1
}, },
"Type": { "Type": {
"value": "Melee" "value": "Melee"
@ -585,7 +537,7 @@
"Weapon§Phase sword": { "Weapon§Phase sword": {
"stats": { "stats": {
"AP": { "AP": {
"value": "-3" "value": -3
}, },
"D": { "D": {
"value": 2 "value": 2
@ -605,7 +557,7 @@
"Weapon§Poison blades": { "Weapon§Poison blades": {
"stats": { "stats": {
"AP": { "AP": {
"value": "-1" "value": -1
}, },
"D": { "D": {
"value": 1 "value": 1
@ -625,13 +577,13 @@
"Weapon§Power sword": { "Weapon§Power sword": {
"stats": { "stats": {
"AP": { "AP": {
"value": "-3" "value": -3
}, },
"D": { "D": {
"value": 1 "value": 1
}, },
"S": { "S": {
"value": "+1" "value": 1
}, },
"Type": { "Type": {
"value": "Melee" "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', genre: 'sci-fi',
publisher: 'Games Workshop', publisher: 'Games Workshop',
url: 'https://warhammer40000.com/', 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.',
revision: '0.0.6', revision: '0.0.7',
wip: true, wip: true,
dependencies: [ dependencies: [
{ {
@ -172,14 +172,14 @@ processAbilities = (data,assetCatalog) => {
if(!ability.is_other_wargear){ if(!ability.is_other_wargear){
itemKey = 'Ability§' + ability.name; itemKey = 'Ability§' + ability.name;
if(!assetCatalog[itemKey]) assetCatalog[itemKey] = tempAbility; 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{ }else{
itemKey = 'Wargear§' + ability.name; 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 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); let costMode = findMode(abilityCostArr);
if(costMode) tempAbility.stats = {Points: {value: numerize(costMode)}}; if(costMode) tempAbility.stats = {Points: {value: numerize(costMode)}};
if(!assetCatalog[itemKey]) assetCatalog[itemKey] = tempAbility; 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'); let subFactTest = new RegExp(`<${data.factCurrent}>`, 'gi');
if(subFactTest.test(assetCatalog[itemKey].text)){ if(subFactTest.test(assetCatalog[itemKey].text)){
@ -302,7 +302,7 @@ createWargearStat = (i,wargearArr,modelLoadout,assetCatalog) => {
return tempStat return tempStat
} }
processRelics = (data,assetCatalog) => { processRelics = (data,assetCatalog) => {
console.log(data.relics.relic_list) // console.log(data.relics.relic_list)
data.relics.relic_list?.forEach(relic => { 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 weapName = relic.name.replace(/(1: |2: |3: )/,'').replace(/в/g,'d').replace(/^"*(.*[^"])"*$/g,'$1').replace(/^\s*(.*[^\s])\s*$/g,'$1');
let tempWeapon = {stats:{ let tempWeapon = {stats:{
@ -402,6 +402,8 @@ formatText = (text,log = false) => {
boldPattern2: [/<span[\s]+class="kwb">((?:.(?!\<\/span\>))*.)<\/span>/g,'<b>$1</b>'], boldPattern2: [/<span[\s]+class="kwb">((?:.(?!\<\/span\>))*.)<\/span>/g,'<b>$1</b>'],
underPattern2: [/<span[\s]+class="kwbu">((?:.(?!\<\/span\>))*.)<\/span>/g,'<i>$1<i>'], underPattern2: [/<span[\s]+class="kwbu">((?:.(?!\<\/span\>))*.)<\/span>/g,'<i>$1<i>'],
ttPattern2: [/<span[\s]+class="tt">((?:.(?!\<\/span\>))*.)<\/span>/g,'<b>$1</b>'], 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>'], h_customPattern: [/<span[\s]+class="h_custom">((?:.(?!\<\/span\>))*.)<\/span>/g,'<b>$1</b>'],
redfontPattern: [/<span[\s]+class="redfont">((?:.(?!\<\/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,'**'], boldTranslationPattern: [/<\/?b>/g,'**'],
doubleBoldPattern: [/\*\*\*\*/g,''], doubleBoldPattern: [/\*\*\*\*/g,''],
doubleBoldPattern2: [/\*\*\s\*\*/g,' '], doubleBoldPattern2: [/\*\*\s\*\*/g,' '],
italicsTranslationPattern: [/<\/?i>/g,'*'] italicsTranslationPattern: [/<\/?i>/g,'*'],
doublePrime: [/\s([0-9]+)"(\.*)\s/g,' $1″$2 '],
} }
newText = text.replace(/kwb2/g,'kwb'); newText = text.replace(/kwb2/g,'kwb');
if(log) console.log(newText) if(log) console.log(newText)
@ -466,85 +469,34 @@ processUnits = (data,assetCatalog) => {
},keywords:{},assets:{}}; },keywords:{},assets:{}};
let models = data.models.filter(model => model.datasheet_id === unitId); 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('-')){ if(models[0]?.line === 1 && models[0]?.models_per_unit.includes('-')){
tempItem.stats.model = { tempItem.stats.model = {
value: data.models.filter(model => model.datasheet_id === unitId && model.line === 1)[0].itemKey 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); 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); 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 abilities = data.abilities.composed.filter(ability => ability.datasheet_id === unitId);
let abilityList = abilities.filter(ability => ability.datasheet_id === unitId && !ability.is_other_wargear); 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); 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'); tempItem.assets.traits.push('Psychic Power§Smite');
} }
const order = ['Ability§', 'Wargear§', 'Psychic Power§', 'Model§']; 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 => { Array.from(new Set(data.psychicPowers.map(power => power.type))).forEach(discipline => {
// console.log(discipline) // console.log(discipline)
@ -584,6 +536,8 @@ processUnits = (data,assetCatalog) => {
value: 0, value: 0,
statType: 'rank', statType: 'rank',
statOrder: 10, statOrder: 10,
group: 'Loadout',
groupOrder: 2,
ranks: { ranks: {
0: {order: 0,number: 0,icons: ['cancel']}, 0: {order: 0,number: 0,icons: ['cancel']},
1: {order: 1,number: 1,icons: ['confirmed'],traits: [{trait: tempWargear}]} 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]; let source = data.sources.filter(source => source.source_id == datasheet.source_id)[0];
// console.log(source) // console.log(source)
if(source){ if(source){
@ -649,26 +684,16 @@ processUnits = (data,assetCatalog) => {
let factionKeywords = data.keywords.filter(keyword => keyword.datasheet_id === unitId && keyword.is_faction_keyword); 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); 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; 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) => { generateDamageRule = (damageRows,currentRow) => {
// console.log(damageRows,currentRow) // console.log(damageRows,currentRow)