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..0c3cbf8eefb 100644 --- a/src/module/rules/rule-element/base-speed.ts +++ b/src/module/rules/rule-element/base-speed.ts @@ -53,9 +53,14 @@ 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 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"); + 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 };