From e73f980b3cece08f7982e4f77b5af7352d8c3322 Mon Sep 17 00:00:00 2001 From: Sparlight Date: Wed, 5 Aug 2026 21:24:14 -0600 Subject: [PATCH] Allow you to smell Scent bearers directly --- .../Scent/ScentSniffBoundUserInterface.cs | 2 +- .../_Starlight/Scent/ScentSniffMenu.xaml.cs | 24 +++++++++++- .../_Starlight/Scent/Systems/ScentSystem.cs | 37 ++++++++++++------- .../_Starlight/Scent/ScentSniffUi.cs | 7 +++- .../Locale/en-US/_Starlight/scent/scent.ftl | 3 ++ 5 files changed, 55 insertions(+), 18 deletions(-) diff --git a/Content.Client/_Starlight/Scent/ScentSniffBoundUserInterface.cs b/Content.Client/_Starlight/Scent/ScentSniffBoundUserInterface.cs index a15433bb893b..cf09faa9d600 100644 --- a/Content.Client/_Starlight/Scent/ScentSniffBoundUserInterface.cs +++ b/Content.Client/_Starlight/Scent/ScentSniffBoundUserInterface.cs @@ -31,6 +31,6 @@ protected override void UpdateState(BoundUserInterfaceState state) if (_window == null || state is not ScentSniffBoundUserInterfaceState cast) return; - _window.UpdateEntries(cast.Entries); + _window.UpdateEntries(cast.Entries, cast.OwnScentId); } } diff --git a/Content.Client/_Starlight/Scent/ScentSniffMenu.xaml.cs b/Content.Client/_Starlight/Scent/ScentSniffMenu.xaml.cs index b30d0686f1c6..c3e86f1dc2c4 100644 --- a/Content.Client/_Starlight/Scent/ScentSniffMenu.xaml.cs +++ b/Content.Client/_Starlight/Scent/ScentSniffMenu.xaml.cs @@ -1,6 +1,9 @@ +using System.Numerics; +using Content.Client.Administration.UI.CustomControls; using Content.Client.Message; using Content.Shared._Starlight.Scent; using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.XAML; @@ -19,13 +22,30 @@ public ScentSniffMenu() RobustXamlLoader.Load(this); } - public void UpdateEntries(List entries) + public void UpdateEntries(List entries, string? ownScentId) { Rows.RemoveAllChildren(); + if (ownScentId is { } ownId) + { + var ownRow = new ContainerButton + { + ToolTip = Loc.GetString("scent-sniff-window-track-own-tooltip"), + HorizontalExpand = true, + }; + ownRow.AddStyleClass(ContainerButton.StyleClassButton); + ownRow.AddChild(new Label { Text = Loc.GetString("scent-sniff-window-track-own") }); + ownRow.OnPressed += _ => OnTrackPressed?.Invoke(ownId); + Rows.AddChild(ownRow); + + Rows.AddChild(new HSeparator()); + Rows.AddChild(new Control { MinSize = new Vector2(0, 10) }); + } + if (entries.Count == 0) { - Rows.AddChild(new Label { Text = Loc.GetString("scent-sniff-window-empty") }); + var emptyKey = ownScentId != null ? "scent-sniff-window-empty-traces" : "scent-sniff-window-empty"; + Rows.AddChild(new Label { Text = Loc.GetString(emptyKey) }); return; } diff --git a/Content.Server/_Starlight/Scent/Systems/ScentSystem.cs b/Content.Server/_Starlight/Scent/Systems/ScentSystem.cs index cc736ad00378..6674ec610fdc 100644 --- a/Content.Server/_Starlight/Scent/Systems/ScentSystem.cs +++ b/Content.Server/_Starlight/Scent/Systems/ScentSystem.cs @@ -101,7 +101,9 @@ private void OnSniffObjectAction(Entity ent, ref SniffObjectAc if (trace != null) PruneExpiredTraces(trace); - if (trace == null || trace.Scents.Count == 0) + var hasOwnScent = TryComp(args.Target, out var targetScent) && targetScent.ScentId != null; + + if ((trace == null || trace.Scents.Count == 0) && !hasOwnScent) { _popup.PopupEntity(Loc.GetString("scent-sniff-no-scents", ("target", Name(args.Target))), args.Target, ent.Owner); args.Handled = true; @@ -126,23 +128,27 @@ private void OnSniffObjectDoAfter(EntityUid uid, SmellerComponent component, Sni if (args.Handled || args.Cancelled || args.Args.Target is not { } target) return; - if (!TryComp(target, out var trace)) - return; - - PruneExpiredTraces(trace); + TryComp(target, out var trace); + if (trace != null) + PruneExpiredTraces(trace); var now = _timing.CurTime; - var entries = new List(trace.Scents.Count); - foreach (var (scentId, info) in trace.Scents) + var entries = new List(trace?.Scents.Count ?? 0); + if (trace != null) { - var speciesName = Loc.GetString("scent-species-non-humanoid"); - if (info.Species != null && _prototype.TryIndex(info.Species, out var species)) - speciesName = Loc.GetString(species.Name); + foreach (var (scentId, info) in trace.Scents) + { + var speciesName = Loc.GetString("scent-species-non-humanoid"); + if (info.Species != null && _prototype.TryIndex(info.Species, out var species)) + speciesName = Loc.GetString(species.Name); - var age = (float)(now - info.LastTouched).TotalSeconds; - entries.Add(new ScentTraceEntry(scentId, GetFreshness(age, trace.TraceLifetime), speciesName)); + var age = (float)(now - info.LastTouched).TotalSeconds; + entries.Add(new ScentTraceEntry(scentId, GetFreshness(age, trace.TraceLifetime), speciesName)); + } } + var ownScentId = TryComp(target, out var targetScent) ? targetScent.ScentId : null; + if (!_ui.TryOpenUi(uid, ScentSniffUiKey.Key, uid)) { Log.Warning($"{ToPrettyString(uid)} has SmellerComponent but couldn't open ScentSniffUiKey - " + @@ -151,7 +157,7 @@ private void OnSniffObjectDoAfter(EntityUid uid, SmellerComponent component, Sni } component.SniffTarget = target; - _ui.SetUiState(uid, ScentSniffUiKey.Key, new ScentSniffBoundUserInterfaceState(entries)); + _ui.SetUiState(uid, ScentSniffUiKey.Key, new ScentSniffBoundUserInterfaceState(entries, ownScentId)); args.Handled = true; } @@ -191,7 +197,10 @@ private void OnTrackMessage(EntityUid uid, SmellerComponent component, ScentSnif if (!_transform.InRange(xform.Coordinates, targetXform.Coordinates, component.SniffRange)) return; - if (!TryComp(target, out var trace) || !trace.Scents.ContainsKey(args.ScentId)) + var isOwnScent = TryComp(target, out var targetScent) && targetScent.ScentId == args.ScentId; + var isTracedScent = TryComp(target, out var trace) && trace.Scents.ContainsKey(args.ScentId); + + if (!isOwnScent && !isTracedScent) return; SetTrackedScent((uid, component), args.ScentId, target); diff --git a/Content.Shared/_Starlight/Scent/ScentSniffUi.cs b/Content.Shared/_Starlight/Scent/ScentSniffUi.cs index ab096644c8c4..fcb1c2c3511a 100644 --- a/Content.Shared/_Starlight/Scent/ScentSniffUi.cs +++ b/Content.Shared/_Starlight/Scent/ScentSniffUi.cs @@ -45,9 +45,14 @@ public sealed class ScentSniffBoundUserInterfaceState : BoundUserInterfaceState { public readonly List Entries; - public ScentSniffBoundUserInterfaceState(List entries) + // The target's own current scent, if it has ScentComponent. Always offered as a pinned + // option above the trace list, separately from whatever traces were found on it. + public readonly string? OwnScentId; + + public ScentSniffBoundUserInterfaceState(List entries, string? ownScentId) { Entries = entries; + OwnScentId = ownScentId; } } diff --git a/Resources/Locale/en-US/_Starlight/scent/scent.ftl b/Resources/Locale/en-US/_Starlight/scent/scent.ftl index bd40b21ca19c..c25caf238a7c 100644 --- a/Resources/Locale/en-US/_Starlight/scent/scent.ftl +++ b/Resources/Locale/en-US/_Starlight/scent/scent.ftl @@ -1,9 +1,12 @@ scent-sniff-window-title = Scent Trace scent-sniff-window-empty = No scents detected. +scent-sniff-window-empty-traces = No other scent traces detected. scent-sniff-window-entry-id = [color={$color}]Scent ({$id}...)[/color] scent-sniff-window-entry-detail = [color={$color}]{$species} - {$freshness}[/color] scent-sniff-window-track-tooltip = Click to track this scent scent-sniff-window-tracking-popup = You start tracking this scent. +scent-sniff-window-track-own = Track this creature's scent directly +scent-sniff-window-track-own-tooltip = Track this individual's scent directly. scent-species-non-humanoid = Non-Humanoid