diff --git a/build/duplicates.json b/build/duplicates.json index 0eac7b09896..4cf88890fb9 100644 --- a/build/duplicates.json +++ b/build/duplicates.json @@ -210,7 +210,8 @@ ], "equipment-effects": [ "Effect: Parry", - "Effect: Raise a Shield" + "Effect: Raise a Shield", + "Effect: Boost" ], "feats": [ "Adapted Cantrip", diff --git a/packs/pf2e/equipment-effects/effect-boost.json b/packs/pf2e/equipment-effects/effect-boost.json new file mode 100644 index 00000000000..c2c3a233421 --- /dev/null +++ b/packs/pf2e/equipment-effects/effect-boost.json @@ -0,0 +1,78 @@ +{ + "_id": "YVm3rVSAYxoSrOvb", + "img": "systems/pf2e/icons/abilities/blue-battery.webp", + "name": "Effect: Boost", + "system": { + "description": { + "value": "

The weapon is currently boosted.

" + }, + "duration": { + "expiry": "turn-end", + "sustained": false, + "unit": "rounds", + "value": 1 + }, + "fromSpell": false, + "level": { + "value": 1 + }, + "publication": { + "license": "ORC", + "remaster": true, + "title": "Starfinder Player Core" + }, + "rules": [ + { + "choices": { + "ownedItems": true, + "predicate": [ + { + "or": [ + { + "and": [ + "self:type:npc", + "item:type:melee" + ] + }, + { + "and": [ + "self:type:character", + "item:type:weapon" + ] + } + ] + }, + "item:trait:boost" + ], + "types": [ + "melee", + "weapon" + ] + }, + "flag": "weapon", + "rollOption": "item:boosted", + "key": "ChoiceSet", + "prompt": "PF2E.SpecificRule.Prompt.Weapon", + "predicate": [ + { + "or": [ + "self:type:npc", + "self:type:character" + ] + } + ] + } + ], + "start": { + "initiative": null, + "value": 0 + }, + "tokenIcon": { + "show": true + }, + "traits": { + "value": [] + } + }, + "type": "effect" +} diff --git a/src/module/actor/character/auxiliary.ts b/src/module/actor/character/auxiliary.ts index f06dde9b24c..793bd89242f 100644 --- a/src/module/actor/character/auxiliary.ts +++ b/src/module/actor/character/auxiliary.ts @@ -9,11 +9,12 @@ import { getActionGlyph, localizeList, sluggify } from "@util"; import { traitSlugToObject } from "@util/tags.ts"; import * as R from "remeda"; import { CharacterPF2e } from "./document.ts"; +import { ChoiceSetSource } from "@module/rules/rule-element/choice-set/data.ts"; interface AuxiliaryInteractParams { weapon: WeaponPF2e; action: "interact"; - annotation: "draw" | "grip" | "modular" | "pick-up" | "retrieve" | "sheathe"; + annotation: "draw" | "grip" | "modular" | "pick-up" | "retrieve" | "sheathe" | "boost"; hands?: ZeroToTwo; } @@ -85,6 +86,8 @@ class WeaponAuxiliaryAction { return [1, null, annotation]; case "drop": return [0, "dropped", annotation]; + case "boost": + return [1, null, annotation]; case "tower-shield": { const cost = this.action === "take-cover" ? 1 : 0; return [cost, null, null]; @@ -157,6 +160,21 @@ class WeaponAuxiliaryAction { selected: Number(selection), }); if (!updated) return; + } else if (this.annotation === "boost") { + const isBoosted = actor.itemTypes.effect.some( + (e) => e.slug === "effect-boost" && e.flags[SYSTEM_ID].grantedBy?.id === weapon.id, + ); + // No op if the weapon is already boosted + if (isBoosted) return; + const effect = await fromUuid(`Compendium.${SYSTEM_ID}.equipment-effects.Item.YVm3rVSAYxoSrOvb`); + if (effect instanceof EffectPF2e) { + const data = { ...effect.toObject(), _id: null }; + data.flags[SYSTEM_ID] = { grantedBy: { id: weapon.id, onDelete: "cascade" } }; + // Change rule to affect the specific weapon. + (data.system.rules[0] as ChoiceSetSource).selection = weapon.id; + data.system.description.value += `\n@UUID[Actor.${actor.id}.Item.${weapon.id}]{${weapon.name}}`; + await actor.createEmbeddedDocuments("Item", [data]); + } } else if (this.action === "raise-a-shield") { // Apply Effect: Raise a Shield const alreadyRaised = actor.itemTypes.effect.some((e) => e.slug === "raise-a-shield"); diff --git a/src/module/actor/character/helpers.ts b/src/module/actor/character/helpers.ts index d02fffea22c..c8d40ba7d49 100644 --- a/src/module/actor/character/helpers.ts +++ b/src/module/actor/character/helpers.ts @@ -247,6 +247,12 @@ function getWeaponAuxiliaryActions(weapon: WeaponPF2e): WeaponAux } } + if (weapon.system.traits.config.boost) { + auxiliaryActions.push( + new WeaponAuxiliaryAction({ weapon, action: "interact", annotation: "boost" }), + ); + } + if (weapon.handsHeld === 2) { auxiliaryActions.push( new WeaponAuxiliaryAction({ weapon, action: "release", annotation: "grip", hands: 1 }), diff --git a/src/module/system/damage/weapon.ts b/src/module/system/damage/weapon.ts index 4392b63be58..6b8d92492b5 100644 --- a/src/module/system/damage/weapon.ts +++ b/src/module/system/damage/weapon.ts @@ -359,6 +359,28 @@ class WeaponDamagePF2e { modifiers.push(modifier); } + // Boost trait + if (weapon.system.traits.config.boost) { + const boostConfig = weapon.system.traits.config.boost; + const slug = `boost-${boostConfig}`; + + const dieSize = boostConfig.substring(boostConfig.indexOf("d")) as DamageDieSize; + const baseNumber = Number(/(\d)d\d{1,2}$/.exec(boostConfig)?.at(1)) || 1; + const diceNumber = strikingDice > 0 ? baseNumber + strikingDice : baseNumber; + + damageDice.push( + new DamageDicePF2e({ + selector: `${weapon.id}-damage`, + slug, + label: traitLabels[slug], + diceNumber: diceNumber, + dieSize, + critical: false, + enabled: options.has(`item:boosted:${weapon.id}`), + }), + ); + } + // Add roll notes to the context const runeNotes = propertyRunes.flatMap((r) => { const data = RUNE_DATA.weapon.property[r].damage?.notes ?? []; diff --git a/src/scripts/config/traits.ts b/src/scripts/config/traits.ts index 8ee5cdf74cc..a31be91dd31 100644 --- a/src/scripts/config/traits.ts +++ b/src/scripts/config/traits.ts @@ -492,13 +492,11 @@ const weaponTraits = { backstabber: "PF2E.TraitBackstabber", backswing: "PF2E.TraitBackswing", bomb: "PF2E.TraitBomb", - "boost-1": "PF2E.TraitBoost1", "boost-1d10": "PF2E.TraitBoost1d10", "boost-1d12": "PF2E.TraitBoost1d12", "boost-1d4": "PF2E.TraitBoost1d4", "boost-1d6": "PF2E.TraitBoost1d6", "boost-1d8": "PF2E.TraitBoost1d8", - "boost-2d10": "PF2E.TraitBoost2d10", brace: "PF2E.TraitBrace", breakdown: "PF2E.TraitBreakdown", brutal: "PF2E.TraitBrutal", @@ -703,6 +701,7 @@ const npcAttackTraits = { ...weaponTraits, ...preciousMaterials, area: "PF2E.TraitArea", + "boost-2d10": "PF2E.TraitBoost2d10", concentrate: "PF2E.TraitConcentrate", curse: "PF2E.TraitCurse", "deadly-2d4": "PF2E.TraitDeadly2D4", @@ -1347,7 +1346,6 @@ const traitDescriptions = { blight: "PF2E.TraitDescriptionBlight", boggard: "PF2E.TraitDescriptionBoggard", bomb: "PF2E.TraitDescriptionBomb", - "boost-1": "PF2E.TraitDescriptionBoost", "boost-1d4": "PF2E.TraitDescriptionBoost", "boost-1d6": "PF2E.TraitDescriptionBoost", "boost-1d8": "PF2E.TraitDescriptionBoost", diff --git a/static/lang/action-en.json b/static/lang/action-en.json index 966cef2f41b..b6dbfbf50f0 100644 --- a/static/lang/action-en.json +++ b/static/lang/action-en.json @@ -595,6 +595,10 @@ "Description": "{actor} sheathes their {weapon}.", "Title": "Sheathe" }, + "Boost": { + "Description": "{actor} boosts their {weapon}.", + "Title": "Boost" + }, "Title": "Interact" }, "Leap": {