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": {