feat: display opponent defense in attack/spell rolls and make it adjustable via effects

This makes it so that the Talents “Verletzen” and “Verheerer” can sort of be automated.
Compendium packs have been updated accordingly.
This commit is contained in:
Johannes Loher 2022-11-09 03:02:27 +01:00
parent e55da9a0e6
commit 1e094691ff
20 changed files with 2801 additions and 1670 deletions

View file

@ -28,6 +28,8 @@
"DS4.OpponentDefense": "Gegnerabwehr",
"DS4.OpponentDefenseAbbr": "GA",
"DS4.SortByOpponentDefense": "Nach Gegnerabwehr sortieren",
"DS4.OpponentDefenseMelee": "Gegnerabwehr für Schlagen",
"DS4.OpponentDefenseRanged": "Gegnerabwehr für Schießen",
"DS4.AttackTypeMelee": "Schlagen",
"DS4.AttackTypeRanged": "Schießen",
"DS4.AttackTypeMeleeRanged": "Schlagen + Schießen",
@ -74,7 +76,9 @@
"DS4.ItemTypeSpecialCreatureAbility": "Besondere Kreaturenfähigkeit",
"DS4.ItemTypeSpecialCreatureAbilityPlural": "Besondere Kreaturenfähigkeiten",
"DS4.ItemWeaponCheckFlavor": "{actor} greift mit {weapon} an.",
"DS4.ItemWeaponCheckFlavorWithOpponentDefense": "{actor} greift mit {weapon} an.<br>Gegnerabwehr: {opponentDefense}",
"DS4.ItemSpellCheckFlavor": "{actor} wirkt {spell}.",
"DS4.ItemSpellCheckFlavorWithOpponentDefense": "{actor} wirkt {spell}.<br>Gegnerabwehr: {opponentDefense}",
"DS4.ItemPropertiesArmor": "Panzerungseigenschaften",
"DS4.ItemPropertiesEquipable": "Ausrüstungseigenschaften",
"DS4.ItemPropertiesPhysical": "Physische Eigenschaften",
@ -160,6 +164,8 @@
"DS4.CooldownDuration100R": "100 Kampfrunden",
"DS4.CooldownDuration1D": "1 Tag",
"DS4.CooldownDurationD20D": "W20 Tage",
"DS4.SpellAllowsDefense": "Erlaubt Abwehr",
"DS4.SpellAllowsDefenseDescription": "Ist eine Abwehren-Probe gegen diesen Zauber erlaubt?",
"DS4.SpellMinimumLevel": "Zugangsstufe",
"DS4.SpellMinimumLevelDescription": "Die minimale Stufe, ab der ein Zauberwirker den Zauberspruch erlernen kann.",
"DS4.SpellCasterClassHealer": "Heiler",

View file

@ -28,6 +28,8 @@
"DS4.OpponentDefense": "Opponent Defense",
"DS4.OpponentDefenseAbbr": "OD",
"DS4.SortByOpponentDefense": "Sort by Opponent Defense",
"DS4.OpponentDefenseMelee": "Opponent Defense for melee attacks",
"DS4.OpponentDefenseRanged": "Opponent Defense for ranged attacks",
"DS4.AttackTypeMelee": "Melee",
"DS4.AttackTypeRanged": "Ranged",
"DS4.AttackTypeMeleeRanged": "Melee / Ranged",
@ -74,7 +76,9 @@
"DS4.ItemTypeSpecialCreatureAbility": "Special Creature Ability",
"DS4.ItemTypeSpecialCreatureAbilityPlural": "Special Creature Abilities",
"DS4.ItemWeaponCheckFlavor": "{actor} attacks with {weapon}.",
"DS4.ItemWeaponCheckFlavorWithOpponentDefense": "{actor} attacks with {weapon}<br>Opponent defense: {opponentDefense}",
"DS4.ItemSpellCheckFlavor": "{actor} casts {spell}.",
"DS4.ItemSpellCheckFlavorWithOpponentDefense": "{actor} casts {spell}.<br>Opponent Defense: {opponentDefense}",
"DS4.ItemPropertiesArmor": "Armor Properties",
"DS4.ItemPropertiesEquipable": "Equipment Properties",
"DS4.ItemPropertiesPhysical": "Physical Properties",
@ -160,6 +164,8 @@
"DS4.CooldownDuration100R": "100 Rounds",
"DS4.CooldownDuration1D": "1 Day",
"DS4.CooldownDurationD20D": "D20 Days",
"DS4.SpellAllowsDefense": "Allows Defense",
"DS4.SpellAllowsDefenseDescription": "Ist it alowed to perform a defense check against this spell?",
"DS4.SpellMinimumLevel": "Minimum Level",
"DS4.SpellMinimumLevelDescription": "The minimum level at which a spell caster may learn the spell.",
"DS4.SpellCasterClassHealer": "Healer",

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -93,7 +93,7 @@
"startTime": null
},
"icon": "icons/svg/aura.svg",
"label": "Proben mit Agilität -1",
"label": "Proben mit Agilität -4",
"transfer": true,
"flags": {},
"tint": null

View file

@ -124,7 +124,7 @@
"type": "specialCreatureAbility",
"img": "systems/ds4/assets/icons/official/special-creature-abilities/petrification.png",
"data": {
"description": "<p>Bei einem erfolgreichen Blickangriff versteinert das Ziel, sofern diesem K&Ouml;R+AU misslingt. Eine Versteinerung kann durch den Zauber Allheilung aufgehoben werden.</p>",
"description": "<p>Bei einem erfolgreichen Blickangriff versteinert das Ziel, sofern diesem K&Ouml;R+AU misslingt. Eine Versteinerung kann durch den Zauber @Compendium[ds4.spells.pmYcjLXv1EB9bM59]{Allheilung} aufgehoben werden.</p>",
"experiencePoints": 50
},
"effects": [],
@ -158,7 +158,7 @@
"type": "specialCreatureAbility",
"img": "systems/ds4/assets/icons/official/special-creature-abilities/attribute-loss.png",
"data": {
"description": "<p>Pro schadensverursachendem Treffer wird AGI um 1 gesenkt (bei AGI Null ist das Opfer bewegungsunf&auml;hig). Pro Tag oder Anwendung des Zaubers <em>Allheilung</em> wird 1 verlorener Attributspunkt regeneriert.</p>",
"description": "<p>Pro schadensverursachendem Treffer wird AGI um 1 gesenkt (bei AGI Null ist das Opfer bewegungsunf&auml;hig). Pro Tag oder Anwendung des Zaubers @Compendium[ds4.spells.pmYcjLXv1EB9bM59]{Allheilung}&nbsp;wird 1 verlorener Attributspunkt regeneriert.</p>",
"experiencePoints": 15
},
"effects": [],
@ -294,7 +294,7 @@
"type": "specialCreatureAbility",
"img": "systems/ds4/assets/icons/official/special-creature-abilities/charm.png",
"data": {
"description": "<p>Kann Gegner mit einem &bdquo;Lockruf&ldquo; bezaubern. Dieser Zauber funktioniert wie der Zauberspruch <em>Gehorche</em>. Abklingzeit des <em>Lockrufs</em>: 10 Kampfrunden</p>",
"description": "<p>Kann Gegner mit einem &bdquo;Lockruf&ldquo; bezaubern. Dieser Zauber funktioniert wie der Zauberspruch @Compendium[ds4.spells.wZYElRaDmhqgzUvQ]{Gehorche}. Abklingzeit des <em>Lockrufs</em>: 10 Kampfrunden</p>",
"experiencePoints": 25
},
"effects": [],
@ -447,7 +447,7 @@
"type": "specialCreatureAbility",
"img": "systems/ds4/assets/icons/official/special-creature-abilities/attribute-loss.png",
"data": {
"description": "<p>Pro schadensverursachendem Treffer wird K&Ouml;R um 1 gesenkt (bei K&Ouml;R Null ist das Opfer tot). Pro Tag oder Anwendung des Zaubers <em>Allheilung</em> wird 1 verlorener Attributspunkt regeneriert.</p>",
"description": "<p>Pro schadensverursachendem Treffer wird K&Ouml;R um 1 gesenkt (bei K&Ouml;R Null ist das Opfer tot). Pro Tag oder Anwendung des Zaubers @Compendium[ds4.spells.pmYcjLXv1EB9bM59]{Allheilung}&nbsp;wird 1 verlorener Attributspunkt regeneriert.</p>",
"experiencePoints": 15
},
"effects": [],
@ -778,7 +778,7 @@
"type": "specialCreatureAbility",
"img": "systems/ds4/assets/icons/official/special-creature-abilities/attribute-loss.png",
"data": {
"description": "<p>Pro schadensverursachendem Treffer wird GEI um 1 gesenkt (bei GEI Null ist das Opfer wahnsinnig). Pro Tag oder Anwendung des Zaubers <em>Allheilung</em> wird 1 verlorener Attributspunkt regeneriert.</p>",
"description": "<p>Pro schadensverursachendem Treffer wird GEI um 1 gesenkt (bei GEI Null ist das Opfer wahnsinnig). Pro Tag oder Anwendung des Zaubers @Compendium[ds4.spells.pmYcjLXv1EB9bM59]{Allheilung}&nbsp;wird 1 verlorener Attributspunkt regeneriert.</p>",
"experiencePoints": 15
},
"effects": [],

File diff suppressed because one or more lines are too long

View file

@ -21,6 +21,7 @@
"flags": {}
},
{
"_id": "1VAiKGCnqKfNC8AE",
"name": "Waffenloser Meister",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -38,8 +39,7 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "1VAiKGCnqKfNC8AE"
"flags": {}
},
{
"_id": "2ASdMhcx0hN3ZpPc",
@ -84,6 +84,7 @@
"flags": {}
},
{
"_id": "2zY11r1tKxBzNB0e",
"name": "Mächtige Erweckung",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -101,10 +102,10 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "2zY11r1tKxBzNB0e"
"flags": {}
},
{
"_id": "3AkPGw4beW52LIAY",
"name": "Rüstträger",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -122,8 +123,7 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "3AkPGw4beW52LIAY"
"flags": {}
},
{
"_id": "42FNsShgm1B6MClC",
@ -238,6 +238,7 @@
"flags": {}
},
{
"_id": "61Dz3XpStwlMfsbL",
"name": "Jäger",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -288,8 +289,7 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "61Dz3XpStwlMfsbL"
"flags": {}
},
{
"_id": "6YJLvjCIUmhqlaFb",
@ -313,6 +313,7 @@
"flags": {}
},
{
"_id": "6oXmRM21CLfELqKv",
"name": "Verheerer",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -324,14 +325,41 @@
"mod": 0
}
},
"effects": [],
"effects": [
{
"_id": "aqgIfpGWXYeXn1y6",
"changes": [
{
"key": "data.opponentDefense",
"mode": 2,
"value": "-1"
}
],
"disabled": false,
"duration": {
"startTime": null
},
"icon": "icons/svg/aura.svg",
"label": "Gegnerabwehr bei Zaubern/Zielzaubern -1",
"transfer": true,
"flags": {
"ds4": {
"itemEffectConfig": {
"applyToItems": true,
"itemName": "",
"condition": "'@item.type' === 'spell' && @item.data.allowsDefense"
}
}
},
"tint": null
}
],
"folder": null,
"sort": 0,
"permission": {
"default": 0
},
"flags": {},
"_id": "6oXmRM21CLfELqKv"
"flags": {}
},
{
"_id": "6z0JXGEqdzDTWQ7f",
@ -474,6 +502,7 @@
"flags": {}
},
{
"_id": "8apgKsktW4pyWmMq",
"name": "Hinterhältiger Angriff",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -491,8 +520,7 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "8apgKsktW4pyWmMq"
"flags": {}
},
{
"_id": "8nkrwGAE0HPoAAEm",
@ -537,6 +565,7 @@
"flags": {}
},
{
"_id": "8wHCsoZEQp3rScWe",
"name": "Parade",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -554,10 +583,10 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "8wHCsoZEQp3rScWe"
"flags": {}
},
{
"_id": "9hpucJC8WArBiXUR",
"name": "Tod entrinnen",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -575,8 +604,7 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "9hpucJC8WArBiXUR"
"flags": {}
},
{
"_id": "9qdc56F4XTntYoo9",
@ -622,7 +650,7 @@
}
}
},
"tint": ""
"tint": null
}
],
"folder": null,
@ -696,6 +724,7 @@
"flags": {}
},
{
"_id": "Bp7OVgurG40CR1Mw",
"name": "Knechtschaft",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -713,10 +742,10 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "Bp7OVgurG40CR1Mw"
"flags": {}
},
{
"_id": "DUexlPzqyH2xPxYP",
"name": "Meister seiner Klasse",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -734,8 +763,7 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "DUexlPzqyH2xPxYP"
"flags": {}
},
{
"_id": "DZcu8KQFWChBVPRR",
@ -759,6 +787,7 @@
"flags": {}
},
{
"_id": "FoY7VbBTatyHOrb8",
"name": "Untote Horden",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -776,10 +805,10 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "FoY7VbBTatyHOrb8"
"flags": {}
},
{
"_id": "G3fbdAorLMCa2hGu",
"name": "Meucheln",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -797,10 +826,10 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "G3fbdAorLMCa2hGu"
"flags": {}
},
{
"_id": "GVuVyP3uLw3Fkiwf",
"name": "Prügler",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -818,10 +847,10 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "GVuVyP3uLw3Fkiwf"
"flags": {}
},
{
"_id": "GWVLcfQ2fm3Hc0zP",
"name": "Schütze",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -872,8 +901,7 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "GWVLcfQ2fm3Hc0zP"
"flags": {}
},
{
"_id": "HFCY3fxIbeXapRan",
@ -918,6 +946,7 @@
"flags": {}
},
{
"_id": "IIvsBSAqFFUFqALo",
"name": "Sattelschütze",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -935,10 +964,10 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "IIvsBSAqFFUFqALo"
"flags": {}
},
{
"_id": "IfyKb7y4YoUTssTs",
"name": "Tiermeister",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -984,8 +1013,7 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "IfyKb7y4YoUTssTs"
"flags": {}
},
{
"_id": "JbGKvhxVEAdczQib",
@ -1009,6 +1037,7 @@
"flags": {}
},
{
"_id": "JldVU3O4mmDWqk8s",
"name": "Spruchmeister",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -1026,8 +1055,7 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "JldVU3O4mmDWqk8s"
"flags": {}
},
{
"_id": "KgOHPx5oQHKBuVPc",
@ -1224,6 +1252,7 @@
"flags": {}
},
{
"_id": "Mz5glQvRowlF5U8X",
"name": "Manipulator",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -1269,10 +1298,10 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "Mz5glQvRowlF5U8X"
"flags": {}
},
{
"_id": "NR3BzKbROxHjpGrs",
"name": "Schlossknacker",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -1323,10 +1352,10 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "NR3BzKbROxHjpGrs"
"flags": {}
},
{
"_id": "NSBiWy4FTIu6Y2Vv",
"name": "Präziser Schuss",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -1344,10 +1373,10 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "NSBiWy4FTIu6Y2Vv"
"flags": {}
},
{
"_id": "Nu7TKGp987s5mHA0",
"name": "Instrument",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -1365,8 +1394,7 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "Nu7TKGp987s5mHA0"
"flags": {}
},
{
"_id": "ODepf0g8Us5jBqLm",
@ -1432,6 +1460,7 @@
"flags": {}
},
{
"_id": "Q98LHOFZmKVoafp8",
"name": "Todeskraft",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -1449,10 +1478,10 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "Q98LHOFZmKVoafp8"
"flags": {}
},
{
"_id": "QAxxIGpwledNp0n1",
"name": "Panzerung zerschmettern",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -1470,10 +1499,10 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "QAxxIGpwledNp0n1"
"flags": {}
},
{
"_id": "RJauLusDDQWo77JU",
"name": "Schwimmen",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -1519,8 +1548,7 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "RJauLusDDQWo77JU"
"flags": {}
},
{
"_id": "RTLVQgPmjiPDdTFw",
@ -1544,6 +1572,7 @@
"flags": {}
},
{
"_id": "TQG9TbBb9S0nHogC",
"name": "Schnelle Reflexe",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -1589,8 +1618,7 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "TQG9TbBb9S0nHogC"
"flags": {}
},
{
"_id": "UUYS4u0DmEbGzXxI",
@ -1711,6 +1739,7 @@
"flags": {}
},
{
"_id": "WMXI5ckyEdlC29j4",
"name": "Heldenglück",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -1728,8 +1757,7 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "WMXI5ckyEdlC29j4"
"flags": {}
},
{
"_id": "Wwvj3V65hIe0JWul",
@ -1753,6 +1781,7 @@
"flags": {}
},
{
"_id": "XNjKX9xKkktkwAHk",
"name": "Kann ich mal vorbei?",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -1770,8 +1799,7 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "XNjKX9xKkktkwAHk"
"flags": {}
},
{
"_id": "XUyuomVVOxuSSKXl",
@ -1837,6 +1865,7 @@
"flags": {}
},
{
"_id": "YPFshcSE5pZS0dto",
"name": "Schmerzhafter Wechsel",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -1854,8 +1883,7 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "YPFshcSE5pZS0dto"
"flags": {}
},
{
"_id": "ZnT8LMCRqZS3zpJO",
@ -1988,6 +2016,7 @@
"flags": {}
},
{
"_id": "aojENPok9Guo3JN1",
"name": "Wahrnehmung",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -2053,10 +2082,10 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "aojENPok9Guo3JN1"
"flags": {}
},
{
"_id": "bA8wUU0bKouuxkQ5",
"name": "Reiten",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -2102,10 +2131,10 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "bA8wUU0bKouuxkQ5"
"flags": {}
},
{
"_id": "bu9alxaRfnzzTyX1",
"name": "Macht des Blutes",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -2123,8 +2152,7 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "bu9alxaRfnzzTyX1"
"flags": {}
},
{
"_id": "cLkCx5hxP7rVYUqD",
@ -2169,6 +2197,7 @@
"flags": {}
},
{
"_id": "dtynnRNkxg59Nqz4",
"name": "Verletzen",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -2180,16 +2209,44 @@
"mod": 0
}
},
"effects": [],
"effects": [
{
"_id": "x1FVRsSyjHwOfYRs",
"changes": [
{
"key": "data.opponentDefenseForAttackType.melee",
"mode": 2,
"value": "-1"
}
],
"disabled": false,
"duration": {
"startTime": null
},
"icon": "icons/svg/aura.svg",
"label": "Gegnerabwehr bei Nahkampfangriffen -1",
"transfer": true,
"flags": {
"ds4": {
"itemEffectConfig": {
"applyToItems": true,
"itemName": "",
"condition": "'@item.type' === 'weapon' && ('@item.data.attackType' === 'melee' || '@item.data.attackType' === 'meleeRanged')"
}
}
},
"tint": null
}
],
"folder": null,
"sort": 0,
"permission": {
"default": 0
},
"flags": {},
"_id": "dtynnRNkxg59Nqz4"
"flags": {}
},
{
"_id": "fIrcapAlXMqto18X",
"name": "Unersättliches Beschwören",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -2207,10 +2264,10 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "fIrcapAlXMqto18X"
"flags": {}
},
{
"_id": "g4XI9wikUdxoCFNg",
"name": "Ritual der Narben",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -2271,8 +2328,7 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "g4XI9wikUdxoCFNg"
"flags": {}
},
{
"_id": "gwCc6niwZL45wklE",
@ -2429,6 +2485,7 @@
"flags": {}
},
{
"_id": "iP5aZcqriVLjdVcd",
"name": "Tiergestalt",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -2446,10 +2503,10 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "iP5aZcqriVLjdVcd"
"flags": {}
},
{
"_id": "inRlUNgoiaHm4pf6",
"name": "Verdrücken",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -2467,10 +2524,10 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "inRlUNgoiaHm4pf6"
"flags": {}
},
{
"_id": "jmjtmMy7DnG205xR",
"name": "Schlachtruf",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -2488,8 +2545,7 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "jmjtmMy7DnG205xR"
"flags": {}
},
{
"_id": "kZti1KQIbf4UPvI7",
@ -2723,6 +2779,7 @@
"flags": {}
},
{
"_id": "ml6GkLIsqDII9Mcp",
"name": "Untote zerschmettern",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -2740,8 +2797,7 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "ml6GkLIsqDII9Mcp"
"flags": {}
},
{
"_id": "nMxDermxN1pUziUJ",
@ -2765,6 +2821,7 @@
"flags": {}
},
{
"_id": "nbDef1IPNyYcXmua",
"name": "Meister aller Klassen",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -2782,10 +2839,10 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "nbDef1IPNyYcXmua"
"flags": {}
},
{
"_id": "oxWYfqhbcsDoaaUJ",
"name": "Nekromantie",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -2831,8 +2888,7 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "oxWYfqhbcsDoaaUJ"
"flags": {}
},
{
"_id": "pAOP7wkvhtsNIPQ8",
@ -2889,6 +2945,7 @@
"flags": {}
},
{
"_id": "pC1K0VHWTpaJqwtt",
"name": "Raserei",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -2939,10 +2996,10 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "pC1K0VHWTpaJqwtt"
"flags": {}
},
{
"_id": "pDPVZpnhvlabmcvT",
"name": "Langfinger",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -2960,10 +3017,10 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "pDPVZpnhvlabmcvT"
"flags": {}
},
{
"_id": "pEH7q5M85j50f45J",
"name": "Mächtige Beschwörung",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -2981,10 +3038,10 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "pEH7q5M85j50f45J"
"flags": {}
},
{
"_id": "qnYeR3a3LNUJ329t",
"name": "Zehrender Spurt",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -3002,10 +3059,10 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "qnYeR3a3LNUJ329t"
"flags": {}
},
{
"_id": "rCRPchtSJye0K5nt",
"name": "Zauberwaffe",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -3023,10 +3080,10 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "rCRPchtSJye0K5nt"
"flags": {}
},
{
"_id": "rXXw5aS0pCr5amWa",
"name": "Zwei Waffen",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -3044,10 +3101,10 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "rXXw5aS0pCr5amWa"
"flags": {}
},
{
"_id": "rbHZFVutiQ25glBq",
"name": "Totenrufer",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -3065,10 +3122,10 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "rbHZFVutiQ25glBq"
"flags": {}
},
{
"_id": "s37iJhz4IQVhCWbe",
"name": "Sehnenschneider",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -3086,8 +3143,7 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "s37iJhz4IQVhCWbe"
"flags": {}
},
{
"_id": "sSKiZ5hdQMBnAYRA",
@ -3132,6 +3188,7 @@
"flags": {}
},
{
"_id": "soobr7uyQgDm3DoN",
"name": "Stabbindung",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -3149,10 +3206,10 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "soobr7uyQgDm3DoN"
"flags": {}
},
{
"_id": "sqGJRKlgFoD2vLCD",
"name": "Heimlichkeit",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -3213,10 +3270,10 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "sqGJRKlgFoD2vLCD"
"flags": {}
},
{
"_id": "sqWBOfkvuv7ZTrVM",
"name": "Kreiszeichner",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -3234,8 +3291,7 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "sqWBOfkvuv7ZTrVM"
"flags": {}
},
{
"_id": "srLA4jC8lsZbp3nT",
@ -3280,6 +3336,7 @@
"flags": {}
},
{
"_id": "tkLyvmSYvVslMXVE",
"name": "Vertrautenband",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -3297,10 +3354,10 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "tkLyvmSYvVslMXVE"
"flags": {}
},
{
"_id": "tmFeIA1PSVHqGGjx",
"name": "Vernichtender Schlag",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -3346,8 +3403,7 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "tmFeIA1PSVHqGGjx"
"flags": {}
},
{
"_id": "v5axYsQQ2w57Iu4p",
@ -3399,6 +3455,7 @@
"flags": {}
},
{
"_id": "v9ocoi91dKJahAe3",
"name": "Waffenkenner",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -3416,8 +3473,7 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "v9ocoi91dKJahAe3"
"flags": {}
},
{
"_id": "vnEDVqVCsZuf8NYN",
@ -3462,6 +3518,7 @@
"flags": {}
},
{
"_id": "w34myctr1EDmXSPI",
"name": "Herr der Elemente",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -3507,10 +3564,10 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "w34myctr1EDmXSPI"
"flags": {}
},
{
"_id": "wpZ1LCG8nLu4PSc9",
"name": "Mächtige Herbeirufung",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -3528,10 +3585,10 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "wpZ1LCG8nLu4PSc9"
"flags": {}
},
{
"_id": "yCHMzXoqCRrNU5Br",
"name": "Schnutz vor Elementen",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -3549,8 +3606,7 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "yCHMzXoqCRrNU5Br"
"flags": {}
},
{
"_id": "yIcgnr9Xr7Kwocaj",
@ -3574,6 +3630,7 @@
"flags": {}
},
{
"_id": "yMVciLvr77vbTw6r",
"name": "Magieresistent",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -3591,8 +3648,7 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "yMVciLvr77vbTw6r"
"flags": {}
},
{
"_id": "zhzVJz6WhSMMeTuY",
@ -3644,6 +3700,7 @@
"flags": {}
},
{
"_id": "zvZelUv5qQz3adKN",
"name": "Salve",
"type": "talent",
"img": "icons/svg/item-bag.svg",
@ -3661,7 +3718,6 @@
"permission": {
"default": 0
},
"flags": {},
"_id": "zvZelUv5qQz3adKN"
"flags": {}
}
]

View file

@ -16,6 +16,7 @@ const defaultData: DS4SpellDataSourceData = {
numerical: 0,
complex: "",
},
allowsDefense: false,
spellGroups: {
lightning: false,
earth: false,

View file

@ -60,8 +60,8 @@ class CheckFactory {
}
createCheckTargetNumberModifier(): string {
const totalCheckTargetNumber = Math.max(this.checkTargetNumber + this.checkModifier, 0);
return `v${totalCheckTargetNumber}`;
const totalCheckTargetNumber = this.checkTargetNumber + this.checkModifier;
return totalCheckTargetNumber >= 0 ? `v(${this.checkTargetNumber} + ${this.checkModifier})` : "v0";
}
createCoupFumbleModifier(): string | null {

View file

@ -12,4 +12,5 @@ export interface DS4SpellDataProperties {
interface DS4SpellDataPropertiesData extends DS4SpellDataSourceData, DS4ItemDataPropertiesDataRollable {
price: number | null;
opponentDefense?: number;
}

View file

@ -16,6 +16,7 @@ export interface DS4SpellDataSourceData extends DS4ItemDataSourceDataBase, DS4It
numerical: number;
complex: string;
};
allowsDefense: boolean;
spellGroups: Record<keyof typeof DS4.i18n.spellGroups, boolean>;
maxDistance: UnitData<DistanceUnit>;
effectRadius: UnitData<DistanceUnit>;

View file

@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: MIT
import { createCheckRoll } from "../../../dice/check-factory";
import { createCheckRoll, DS4CheckFactoryOptions } from "../../../dice/check-factory";
import { notifications } from "../../../ui/notifications";
import { getGame } from "../../../utils/utils";
import { DS4Item } from "../item";
@ -12,6 +12,9 @@ export class DS4Spell extends DS4Item {
override prepareDerivedData(): void {
this.data.data.rollable = this.data.data.equipped;
this.data.data.price = calculateSpellPrice(this.data.data);
if (this.data.data.allowsDefense) {
this.data.data.opponentDefense = 0;
}
}
override async roll(options: { speaker?: { token?: TokenDocument; alias?: string } } = {}): Promise<void> {
@ -42,17 +45,29 @@ export class DS4Spell extends DS4Item {
);
}
const spellType = this.data.data.spellType;
const opponentDefense = this.data.data.opponentDefense;
const checkTargetNumber =
ownerDataData.combatValues[spellType].total +
(hasComplexModifier ? 0 : this.data.data.spellModifier.numerical);
const speaker = ChatMessage.getSpeaker({ actor: this.actor, ...options.speaker });
const flavor =
opponentDefense !== undefined && opponentDefense !== 0
? "DS4.ItemSpellCheckFlavorWithOpponentDefense"
: "DS4.ItemSpellCheckFlavor";
const flavorData: DS4CheckFactoryOptions["flavorData"] = {
actor: speaker.alias ?? this.actor.name,
spell: this.name,
};
if (opponentDefense !== undefined && opponentDefense !== 0) {
flavorData.opponentDefense = (opponentDefense < 0 ? "" : "+") + opponentDefense;
}
await createCheckRoll(checkTargetNumber, {
rollMode: game.settings.get("core", "rollMode"),
maximumCoupResult: ownerDataData.rolling.maximumCoupResult,
minimumFumbleResult: ownerDataData.rolling.minimumFumbleResult,
flavor: "DS4.ItemSpellCheckFlavor",
flavorData: { actor: speaker.alias ?? this.actor.name, spell: this.name },
flavor: flavor,
flavorData: flavorData,
speaker,
});

View file

@ -5,7 +5,12 @@
import type { DS4ItemDataPropertiesDataRollable } from "../item-data-properties-base";
import type { DS4WeaponDataSourceData } from "./weapon-data-source";
interface DS4WeaponDataPropertiesData extends DS4WeaponDataSourceData, DS4ItemDataPropertiesDataRollable {}
interface DS4WeaponDataPropertiesData extends DS4WeaponDataSourceData, DS4ItemDataPropertiesDataRollable {
opponentDefenseForAttackType: {
melee?: number;
ranged?: number;
};
}
export interface DS4WeaponDataProperties {
type: "weapon";

View file

@ -3,16 +3,22 @@
// SPDX-License-Identifier: MIT
import { DS4 } from "../../../config";
import { createCheckRoll } from "../../../dice/check-factory";
import { createCheckRoll, DS4CheckFactoryOptions } from "../../../dice/check-factory";
import { notifications } from "../../../ui/notifications";
import { getGame } from "../../../utils/utils";
import { DS4Item } from "../item";
import type { AttackType } from "./weapon-data-source";
export class DS4Weapon extends DS4Item {
override prepareDerivedData(): void {
this.data.data.rollable = this.data.data.equipped;
const data = this.data.data;
data.rollable = data.equipped;
data.opponentDefenseForAttackType = {};
if (data.attackType === "melee" || data.attackType === "meleeRanged") {
data.opponentDefenseForAttackType.melee = data.opponentDefense;
}
if (data.attackType === "ranged" || data.attackType === "meleeRanged") {
data.opponentDefenseForAttackType.ranged = data.opponentDefense;
}
}
override async roll(options: { speaker?: { token?: TokenDocument; alias?: string } } = {}): Promise<void> {
@ -33,54 +39,67 @@ export class DS4Weapon extends DS4Item {
const ownerDataData = this.actor.data.data;
const weaponBonus = this.data.data.weaponBonus;
const combatValue = await this.getCombatValueKeyForAttackType(this.data.data.attackType);
const attackType = await this.getPerformedAttackType();
const opponentDefense = this.data.data.opponentDefenseForAttackType[attackType];
const combatValue = `${attackType}Attack` as const;
const checkTargetNumber = ownerDataData.combatValues[combatValue].total + weaponBonus;
const speaker = ChatMessage.getSpeaker({ actor: this.actor, ...options.speaker });
const flavor =
opponentDefense !== undefined && opponentDefense !== 0
? "DS4.ItemWeaponCheckFlavorWithOpponentDefense"
: "DS4.ItemWeaponCheckFlavor";
const flavorData: DS4CheckFactoryOptions["flavorData"] = {
actor: speaker.alias ?? this.actor.name,
weapon: this.name,
};
if (opponentDefense !== undefined && opponentDefense !== 0) {
flavorData.opponentDefense = (opponentDefense < 0 ? "" : "+") + opponentDefense;
}
await createCheckRoll(checkTargetNumber, {
rollMode: getGame().settings.get("core", "rollMode"),
maximumCoupResult: ownerDataData.rolling.maximumCoupResult,
minimumFumbleResult: ownerDataData.rolling.minimumFumbleResult,
flavor: "DS4.ItemWeaponCheckFlavor",
flavorData: { actor: speaker.alias ?? this.actor.name, weapon: this.name },
speaker,
flavor,
flavorData,
});
Hooks.callAll("ds4.rollItem", this);
}
private async getCombatValueKeyForAttackType(attackType: AttackType): Promise<"meleeAttack" | "rangedAttack"> {
if (attackType === "meleeRanged") {
const { melee, ranged } = { ...DS4.i18n.attackTypes };
const identifier = "attack-type-selection";
return Dialog.prompt({
title: getGame().i18n.localize("DS4.DialogAttackTypeSelection"),
content: await renderTemplate("systems/ds4/templates/dialogs/simple-select-form.hbs", {
selects: [
{
label: getGame().i18n.localize("DS4.AttackType"),
identifier,
options: { melee, ranged },
},
],
}),
label: getGame().i18n.localize("DS4.GenericOkButton"),
callback: (html) => {
const selectedAttackType = html.find(`#${identifier}`).val();
if (selectedAttackType !== "melee" && selectedAttackType !== "ranged") {
throw new Error(
getGame().i18n.format("DS4.ErrorUnexpectedAttackType", {
actualType: selectedAttackType,
expectedTypes: "'melee', 'ranged'",
}),
);
}
return `${selectedAttackType}Attack` as const;
},
});
} else {
return `${attackType}Attack` as const;
private async getPerformedAttackType(): Promise<"melee" | "ranged"> {
if (this.data.data.attackType !== "meleeRanged") {
return this.data.data.attackType;
}
const { melee, ranged } = { ...DS4.i18n.attackTypes };
const identifier = `attack-type-selection-${foundry.utils.randomID()}`;
return Dialog.prompt({
title: getGame().i18n.localize("DS4.DialogAttackTypeSelection"),
content: await renderTemplate("systems/ds4/templates/dialogs/simple-select-form.hbs", {
selects: [
{
label: getGame().i18n.localize("DS4.AttackType"),
identifier,
options: { melee, ranged },
},
],
}),
label: getGame().i18n.localize("DS4.GenericOkButton"),
callback: (html) => {
const selectedAttackType = html.find(`#${identifier}`).val();
if (selectedAttackType !== "melee" && selectedAttackType !== "ranged") {
throw new Error(
getGame().i18n.format("DS4.ErrorUnexpectedAttackType", {
actualType: selectedAttackType,
expectedTypes: "'melee', 'ranged'",
}),
);
}
return selectedAttackType;
},
});
}
}

39
src/migration/007.ts Normal file
View file

@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: 2022 Johannes Loher
//
// SPDX-License-Identifier: MIT
import {
getActorUpdateDataGetter,
getCompendiumMigrator,
getSceneUpdateDataGetter,
migrateActors,
migrateCompendiums,
migrateItems,
migrateScenes,
} from "./migrationHelpers";
async function migrate(): Promise<void> {
await migrateItems(getItemUpdateData);
await migrateActors(getActorUpdateData);
await migrateScenes(getSceneUpdateData);
await migrateCompendiums(migrateCompendium);
}
function getItemUpdateData(itemData: Partial<foundry.data.ItemData["_source"]>) {
if (itemData.type !== "spell") return;
return {
data: {
allowsDefense: false,
},
};
}
const getActorUpdateData = getActorUpdateDataGetter(getItemUpdateData);
const getSceneUpdateData = getSceneUpdateDataGetter(getActorUpdateData);
const migrateCompendium = getCompendiumMigrator({ getItemUpdateData, getActorUpdateData, getSceneUpdateData });
export const migration = {
migrate,
migrateCompendium,
};

View file

@ -11,13 +11,14 @@ import { migration as migration003 } from "./003";
import { migration as migration004 } from "./004";
import { migration as migration005 } from "./005";
import { migration as migration006 } from "./006";
import { migration as migration007 } from "./007";
async function migrate(): Promise<void> {
if (!getGame().user?.isGM) {
return;
}
const oldMigrationVersion = getGame().settings.get("ds4", "systemMigrationVersion");
const oldMigrationVersion = getCurrentMigrationVersion();
const targetMigrationVersion = migrations.length;
@ -47,7 +48,7 @@ async function migrateFromTo(oldMigrationVersion: number, targetMigrationVersion
for (const [i, { migrate }] of migrationsToExecute.entries()) {
const currentMigrationVersion = oldMigrationVersion + i + 1;
logger.info("executing migration script ", currentMigrationVersion);
logger.info("executing migration script", currentMigrationVersion);
try {
await migrate();
getGame().settings.set("ds4", "systemMigrationVersion", currentMigrationVersion);
@ -127,6 +128,10 @@ async function migrateCompendiumFromTo(
}
}
function getCurrentMigrationVersion(): number {
return getGame().settings.get("ds4", "systemMigrationVersion");
}
function getTargetMigrationVersion(): number {
return migrations.length;
}
@ -136,7 +141,15 @@ interface Migration {
migrateCompendium: (pack: CompendiumCollection<CompendiumCollection.Metadata>) => Promise<void>;
}
const migrations: Migration[] = [migration001, migration002, migration003, migration004, migration005, migration006];
const migrations: Migration[] = [
migration001,
migration002,
migration003,
migration004,
migration005,
migration006,
migration007,
];
function isFirstWorldStart(migrationVersion: number): boolean {
return migrationVersion < 0;
@ -145,6 +158,7 @@ function isFirstWorldStart(migrationVersion: number): boolean {
export const migration = {
migrate,
migrateFromTo,
getCurrentMigrationVersion,
getTargetMigrationVersion,
migrateCompendiumFromTo,
};

View file

@ -177,6 +177,7 @@
"numerical": 0,
"complex": ""
},
"allowsDefense": false,
"spellGroups": {
"lightning": false,
"earth": false,

View file

@ -36,14 +36,26 @@ SPDX-License-Identifier: MIT
hasQuantity=true}}
{{!-- attack type --}}
<img class="ds4-embedded-document-list__image"
src="{{lookup ../../config.icons.attackTypes itemData.data.attackType}}"
title="{{lookup ../../config.i18n.attackTypes itemData.data.attackType}}" />
src="{{lookup @root/config.icons.attackTypes itemData.data.attackType}}"
title="{{lookup @root/config.i18n.attackTypes itemData.data.attackType}}" />
{{!-- weapon bonus --}}
<div>{{ itemData.data.weaponBonus}}</div>
{{!-- opponent defense --}}
<div>{{ itemData.data.opponentDefense}}</div>
<div>
{{#if itemData.data.opponentDefenseForAttackType.melee includeZero=true}}
{{#if itemData.data.opponentDefenseForAttackType.ranged includeZero=true}}
<span
title="{{localize 'DS4.OpponentDefenseMelee'}}">{{itemData.data.opponentDefenseForAttackType.melee}}</span>/<span
title="{{localize 'DS4.OpponentDefenseRanged'}}">{{itemData.data.opponentDefenseForAttackType.ranged}}</span>
{{else}}
{{itemData.data.opponentDefenseForAttackType.melee}}
{{/if}}
{{else}}
{{itemData.data.opponentDefenseForAttackType.ranged}}
{{/if}}
</div>
{{/systems/ds4/templates/sheets/actor/components/item-list-entry.hbs}}
{{/each}}
</ol>
@ -76,13 +88,13 @@ documentType='item' type='weapon'}}
{{#> systems/ds4/templates/sheets/actor/components/item-list-entry.hbs itemData=itemData isEquipable=true
hasQuantity=true}}
{{!-- armor material type --}}
<div title="{{lookup ../../config.i18n.armorMaterialTypes itemData.data.armorMaterialType}}">
{{lookup ../../config.i18n.armorMaterialTypesAbbr itemData.data.armorMaterialType}}
<div title="{{lookup @root/config.i18n.armorMaterialTypes itemData.data.armorMaterialType}}">
{{lookup @root/config.i18n.armorMaterialTypesAbbr itemData.data.armorMaterialType}}
</div>
{{!-- armor type --}}
<div title="{{lookup ../../config.i18n.armorTypes itemData.data.armorType}}">
{{lookup ../../config.i18n.armorTypesAbbr itemData.data.armorType}}
<div title="{{lookup @root/config.i18n.armorTypes itemData.data.armorType}}">
{{lookup @root/config.i18n.armorTypesAbbr itemData.data.armorType}}
</div>
{{!-- armor value --}}

View file

@ -105,6 +105,14 @@ SPDX-License-Identifier: MIT
{{/each}}
</div>
</div>
<div class="form-group">
<label for="data.allowsDefense-{{data._id}}" title="{{localize 'DS4.SpellAllowsDefenseDescription'}}">{{localize
"DS4.SpellAllowsDefense"}}</label>
<div class="form-fields">
<input id="data.allowsDefense-{{data._id}}" data-dtype="Boolean" type="checkbox" name="data.allowsDefense"
{{checked data.data.allowsDefense}} />
</div>
</div>
<div class="form-group slim">
<label title="{{localize 'DS4.SpellMinimumLevelDescription'}}">{{localize "DS4.SpellMinimumLevel"}}</label>
<div class="form-fields">