From 4806f9eaf16d130c34fc62f52fb38ffb2f7cb06f Mon Sep 17 00:00:00 2001 From: iDantar <130079801+iDantar@users.noreply.github.com> Date: Tue, 28 Apr 2026 23:01:27 +0300 Subject: [PATCH 1/3] Pushing wind only gives bonus to fly speed if it wasn't derived from land speed --- packs/pf2e/feat-effects/effect-pushing-wind.json | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packs/pf2e/feat-effects/effect-pushing-wind.json b/packs/pf2e/feat-effects/effect-pushing-wind.json index e90b8f1eb29..f9748f4dc44 100644 --- a/packs/pf2e/feat-effects/effect-pushing-wind.json +++ b/packs/pf2e/feat-effects/effect-pushing-wind.json @@ -23,10 +23,18 @@ "rules": [ { "key": "FlatModifier", - "selector": [ - "land-speed", - "fly-speed" + "selector": "land-speed", + "type": "circumstance", + "value": 5 + }, + { + "key": "FlatModifier", + "predicate": [ + { + "not": "derived-from-land" + } ], + "selector": "fly-speed", "type": "circumstance", "value": 5 } From 402fabaccb838e329341d1893f6b28813f005129 Mon Sep 17 00:00:00 2001 From: iDantar <130079801+iDantar@users.noreply.github.com> Date: Sun, 3 May 2026 00:02:40 +0300 Subject: [PATCH 2/3] Systemic fix against applying bonuses twice for derived speeds --- .../feat-effects/effect-pushing-wind.json | 14 +++------- src/module/actor/creature/document.ts | 2 +- src/module/rules/rule-element/base-speed.ts | 7 ++--- .../rules/rule-element/flat-modifier.ts | 16 ++++++++++++ src/module/system/statistic/speed.ts | 26 ++++++++++++++++--- 5 files changed, 46 insertions(+), 19 deletions(-) diff --git a/packs/pf2e/feat-effects/effect-pushing-wind.json b/packs/pf2e/feat-effects/effect-pushing-wind.json index f9748f4dc44..e90b8f1eb29 100644 --- a/packs/pf2e/feat-effects/effect-pushing-wind.json +++ b/packs/pf2e/feat-effects/effect-pushing-wind.json @@ -23,18 +23,10 @@ "rules": [ { "key": "FlatModifier", - "selector": "land-speed", - "type": "circumstance", - "value": 5 - }, - { - "key": "FlatModifier", - "predicate": [ - { - "not": "derived-from-land" - } + "selector": [ + "land-speed", + "fly-speed" ], - "selector": "fly-speed", "type": "circumstance", "value": 5 } diff --git a/src/module/actor/creature/document.ts b/src/module/actor/creature/document.ts index 65aa7e65679..fb9ba3809d4 100644 --- a/src/module/actor/creature/document.ts +++ b/src/module/actor/creature/document.ts @@ -748,7 +748,7 @@ abstract class CreaturePF2e< domain["derived-from-land"] = true; } const statistic = selected.derivedFromLand - ? landSpeed.extend({ type, base: selected.value, source: selected.source }) + ? landSpeed.extend({ type, base: selected.value, source: selected.source, derivedFromLand: true }) : new SpeedStatistic(this, { type, base: selected.value, diff --git a/src/module/rules/rule-element/base-speed.ts b/src/module/rules/rule-element/base-speed.ts index 81a9aff01e2..16498e5f3ae 100644 --- a/src/module/rules/rule-element/base-speed.ts +++ b/src/module/rules/rule-element/base-speed.ts @@ -53,9 +53,10 @@ class BaseSpeedRuleElement extends RuleElement { if (!Number.isInteger(value)) this.failValidation("Failed to resolve value"); return null; } - // Whether this speed is derived from the creature's land speed - const derivedFromLand = - type !== "land" && typeof this.value === "string" && this.value.includes("movement.speeds.land.value"); + const landTotal = this.actor.system.movement?.speeds?.land?.value ?? 0; + // Whether this speed is derived from the land speed. + // If choices are between constant and land speed, only true if land speed was chosen. + const derivedFromLand = type !== "land" && typeof this.value === "string" && this.value.includes("movement.speeds.land.value") && value === landTotal; return { type: type, value, source: this.getReducedLabel(), derivedFromLand }; }; } diff --git a/src/module/rules/rule-element/flat-modifier.ts b/src/module/rules/rule-element/flat-modifier.ts index 7564637bcd8..7693e726854 100644 --- a/src/module/rules/rule-element/flat-modifier.ts +++ b/src/module/rules/rule-element/flat-modifier.ts @@ -1,5 +1,6 @@ import { DeferredValueParams, MODIFIER_TYPES, Modifier, ModifierType } from "@actor/modifiers.ts"; import { AttributeString } from "@actor/types.ts"; +import { MOVEMENT_TYPES } from "@actor/values.ts"; import { damageCategoriesUnique } from "@scripts/config/damage.ts"; import { DamageCategoryUnique } from "@system/damage/types.ts"; import { @@ -129,6 +130,21 @@ class FlatModifierRuleElement extends RuleElement { if (selector === "null") continue; const construct = (options: DeferredValueParams = {}): Modifier | null => { + // If same rule affects land- and another speed: omit non-land modifier when base is land-derived. + const testRollOptions = options.test ?? []; + const derivedFromLand = Array.isArray(testRollOptions) + ? testRollOptions.includes("derived-from-land") + : testRollOptions.has("derived-from-land"); + if ( + selectors.includes("land-speed") && + selector !== "land-speed" && + selector.endsWith("-speed") && + MOVEMENT_TYPES.includes(selector.replace(/-speed$/, "") as never) && + derivedFromLand + ) { + return null; + } + const resolvedValue = Number(this.resolveValue(this.value, 0, options)) || 0; if (this.ignored) return null; diff --git a/src/module/system/statistic/speed.ts b/src/module/system/statistic/speed.ts index 320ce333952..7ec8e10b5b2 100644 --- a/src/module/system/statistic/speed.ts +++ b/src/module/system/statistic/speed.ts @@ -4,7 +4,7 @@ import { MovementType } from "@actor/types.ts"; import { extractModifierAdjustments, extractModifiers } from "@module/rules/helpers.ts"; import { ErrorPF2e, localizer } from "@util"; import { BaseStatistic } from "./base.ts"; -import { BaseStatisticData, BaseStatisticTraceData } from "./data.ts"; +import { BaseStatisticData, BaseStatisticTraceData, StatisticData } from "./data.ts"; class SpeedStatistic extends BaseStatistic { constructor(actor: TActor, options: SpeedStatisticData) { @@ -30,6 +30,15 @@ class SpeedStatistic { + const set = super.createRollOptions(domains); + if ((this.data as StatisticData & { derivedFromLand?: boolean }).derivedFromLand) { + set.add("derived-from-land"); + } + return set; + } + /** The movement type for this statistic */ type: TType; @@ -64,8 +73,15 @@ class SpeedStatistic(options: ExtendParams): SpeedStatistic { - const { type, base = this.value, modifiers = [], source = this.source } = options; - return new SpeedStatistic(this.actor, { type, base, modifiers, domains: [`${type}-speed`], source }); + const { type, base = this.value, modifiers = [], source = this.source, derivedFromLand } = options; + return new SpeedStatistic(this.actor, { + type, + base, + modifiers, + domains: [`${type}-speed`], + source, + derivedFromLand, + }); } override getTraceData(): TType extends "land" @@ -97,6 +113,8 @@ interface SpeedStatisticData extends Omit base?: number; /** A feature, ancestry, effect, etc. from which this speed originated */ source?: string | null; + /** Is this speed based from land; used to prevent double-application of modifiers. */ + derivedFromLand?: boolean; } interface SpeedStatisticTraceData< @@ -115,7 +133,7 @@ interface LandSpeedStatisticTraceData extends SpeedStatisticTraceData<"land"> { interface ExtendParams extends Pick< SpeedStatisticData, - "type" | "base" | "modifiers" | "source" + "type" | "base" | "modifiers" | "source" | "derivedFromLand" > {} export { SpeedStatistic }; From 88703647d87a758b2a892d96d22c7edb92d8840f Mon Sep 17 00:00:00 2001 From: iDantar <130079801+iDantar@users.noreply.github.com> Date: Sun, 3 May 2026 00:07:44 +0300 Subject: [PATCH 3/3] linter --- src/module/rules/rule-element/base-speed.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/module/rules/rule-element/base-speed.ts b/src/module/rules/rule-element/base-speed.ts index 16498e5f3ae..0c3cbf8eefb 100644 --- a/src/module/rules/rule-element/base-speed.ts +++ b/src/module/rules/rule-element/base-speed.ts @@ -56,7 +56,11 @@ class BaseSpeedRuleElement extends RuleElement { const landTotal = this.actor.system.movement?.speeds?.land?.value ?? 0; // Whether this speed is derived from the land speed. // If choices are between constant and land speed, only true if land speed was chosen. - const derivedFromLand = type !== "land" && typeof this.value === "string" && this.value.includes("movement.speeds.land.value") && value === landTotal; + const derivedFromLand = + type !== "land" && + typeof this.value === "string" && + this.value.includes("movement.speeds.land.value") && + value === landTotal; return { type: type, value, source: this.getReducedLabel(), derivedFromLand }; }; }