diff --git a/packs/ancestryfeatures/empathic-sense.json b/packs/ancestryfeatures/empathic-sense.json index 39fd0ece1fa..57433c9bec0 100644 --- a/packs/ancestryfeatures/empathic-sense.json +++ b/packs/ancestryfeatures/empathic-sense.json @@ -42,6 +42,12 @@ "selector": "perception", "type": "circumstance", "value": 2 + }, + { + "acuity": "imprecise", + "key": "Sense", + "range": 15, + "selector": "thoughtsense" } ], "traits": { diff --git a/packs/pathfinder-monster-core/rhu-chalik.json b/packs/pathfinder-monster-core/rhu-chalik.json index e842324d24d..9b3ad3e77d8 100644 --- a/packs/pathfinder-monster-core/rhu-chalik.json +++ b/packs/pathfinder-monster-core/rhu-chalik.json @@ -710,7 +710,7 @@ { "acuity": "precise", "range": 60, - "type": "tremorsense" + "type": "thoughtsense" } ] }, diff --git a/src/module/canvas/perception/modes.ts b/src/module/canvas/perception/modes.ts index f1ff88ad268..e00df52add7 100644 --- a/src/module/canvas/perception/modes.ts +++ b/src/module/canvas/perception/modes.ts @@ -152,12 +152,60 @@ class DetectionModeTremorPF2e extends DetectionModeTremor { } } +class ThoughtsDetectionMode extends DetectionMode { + constructor() { + super({ + id: "senseThoughts", + label: "PF2E.Actor.Creature.Sense.Type.Thoughtsense", + type: DetectionMode.DETECTION_TYPES.OTHER, + angle: false, + }); + } + + static override getDetectionFilter(): OutlineOverlayFilter { + const filter = (this._detectionFilter ??= OutlineOverlayFilter.create({ + wave: true, + knockout: false, + outlineColor: [0, 1, 0, 1], + })); + filter.thickness = 1; + return filter; + } + + protected override _canDetect(visionSource: PointVisionSourcePF2e, target: PlaceableObject): boolean { + // Not if the target isn't a token + if (!(target instanceof TokenPF2e)) return false; + const token: TokenPF2e = target; + if (!token.actor) return false; + + // Not if the token is GM-hidden + if (token.document.hidden) return false; + + // Detection only works on creatures + if (!token.actor.isOfType("creature")) return false; + + // Detection cails on mindless creatures + if (token.actor.system.traits.value.includes("mindless")) return false; + + // Detection fails if target is immune to mental + if (token.actor.attributes.immunities.some((i) => i.type === "mental")) return false; + + return super._canDetect(visionSource, target); + } +} + +declare namespace ThoughtsDetectionMode { + // eslint-disable-next-line no-var + var _detectionFilter: OutlineOverlayFilter | undefined; +} + function setPerceptionModes(): void { CONFIG.Canvas.visionModes.darkvision = darkvision; CONFIG.Canvas.detectionModes.basicSight = new VisionDetectionMode(); CONFIG.Canvas.detectionModes.lightPerception = new LightPerceptionMode(); CONFIG.Canvas.detectionModes.hearing = new HearingDetectionMode(); CONFIG.Canvas.detectionModes.feelTremor = new DetectionModeTremorPF2e(); + CONFIG.Canvas.detectionModes.senseThoughts = new ThoughtsDetectionMode(); } export { setPerceptionModes }; diff --git a/src/module/scene/helpers.ts b/src/module/scene/helpers.ts index 2e3a1ecbb7d..b52c72e047a 100644 --- a/src/module/scene/helpers.ts +++ b/src/module/scene/helpers.ts @@ -97,6 +97,11 @@ function computeSightAndDetectionForRBV(token: TokenDocumentPF2e | PrototypeToke const range = scene?.flags.pf2e.hearingRange ?? null; token.detectionModes.push({ id: "hearing", enabled: true, range }); } + + const thoughtsense = actor.perception.senses.get("thoughtsense"); + if (thoughtsense && thoughtsense.acuity !== "vague") { + token.detectionModes.push({ id: "senseThoughts", enabled: true, range: thoughtsense.range }); + } } /** Returns true if this token has the default actor image or the default image for its actor type */