From ce5817ebd46107a196b76d03c6bd0643ea1c5d22 Mon Sep 17 00:00:00 2001 From: Tobias Berger Date: Fri, 16 Jan 2026 19:07:45 +0000 Subject: [PATCH] No more footprint prediction (#5224) * Revert "footprints prediction (#5111)" This reverts commit 0d0d5a17de0a15135c46f96376faf655f807701c. * No footprint slowdown --------- Co-authored-by: Vanessa --- .../_EE/FootPrint/FootPrintsSystem.cs | 127 +++++++ .../_EE/FootPrint/PuddleFootPrintsSystem.cs | 57 +++ Content.Shared/_DV/CCVars/DCCVars.cs | 21 -- .../_EE/Footprint/FootPrintComponent.cs | 17 +- .../_EE/Footprint/FootPrintVisuals.cs | 34 -- .../_EE/Footprint/FootPrintsComponent.cs | 89 ++--- .../_EE/Footprint/GridFootPrintsComponent.cs | 80 ----- .../Footprint/PuddleFootPrintsComponent.cs | 19 +- .../Systems/FootPrintsSystem.HandleState.cs | 140 -------- .../_EE/Footprint/Systems/FootPrintsSystem.cs | 331 +----------------- .../Systems/PuddleFootPrintsSystem.cs | 123 ------- .../Prototypes/Entities/Effects/puddle.yml | 48 ++- .../_DV/Entities/Effects/puddles.yml | 45 --- 13 files changed, 265 insertions(+), 866 deletions(-) create mode 100644 Content.Server/_EE/FootPrint/FootPrintsSystem.cs create mode 100644 Content.Server/_EE/FootPrint/PuddleFootPrintsSystem.cs delete mode 100644 Content.Shared/_EE/Footprint/GridFootPrintsComponent.cs delete mode 100644 Content.Shared/_EE/Footprint/Systems/FootPrintsSystem.HandleState.cs delete mode 100644 Content.Shared/_EE/Footprint/Systems/PuddleFootPrintsSystem.cs diff --git a/Content.Server/_EE/FootPrint/FootPrintsSystem.cs b/Content.Server/_EE/FootPrint/FootPrintsSystem.cs new file mode 100644 index 0000000000..b8fff2a5aa --- /dev/null +++ b/Content.Server/_EE/FootPrint/FootPrintsSystem.cs @@ -0,0 +1,127 @@ +using Content.Server.Atmos.Components; +using Content.Shared._EE.Flight; // DeltaV +using Content.Shared._EE.FootPrint; +using Content.Shared.Inventory; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; +// using Content.Shared.Standing; +using Content.Shared.Chemistry.Components.SolutionManager; +using Content.Shared.Chemistry.EntitySystems; +using Robust.Shared.Map; +using Robust.Shared.Random; + +namespace Content.Server._EE.FootPrint; + +public sealed class FootPrintsSystem : EntitySystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly InventorySystem _inventory = default!; + [Dependency] private readonly IMapManager _map = default!; + + [Dependency] private readonly SharedSolutionContainerSystem _solution = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly SharedFlightSystem _flight = default!; // DeltaV + + private EntityQuery _transformQuery; + private EntityQuery _mobThresholdQuery; + private EntityQuery _appearanceQuery; + +// private EntityQuery _layingQuery; + + public override void Initialize() + { + base.Initialize(); + + _transformQuery = GetEntityQuery(); + _mobThresholdQuery = GetEntityQuery(); + _appearanceQuery = GetEntityQuery(); +// _layingQuery = GetEntityQuery(); + + SubscribeLocalEvent(OnStartupComponent); + SubscribeLocalEvent(OnMove); + } + + private void OnStartupComponent(EntityUid uid, FootPrintsComponent component, ComponentStartup args) + { + component.StepSize = Math.Max(0f, component.StepSize + _random.NextFloat(-0.05f, 0.05f)); + } + + private void OnMove(EntityUid uid, FootPrintsComponent component, ref MoveEvent args) + { + if (_flight.IsFlying(uid)) // DeltaV - Flying players won't make footprints + return; + + if (component.PrintsColor.A <= 0f + || !_transformQuery.TryComp(uid, out var transform) + || !_mobThresholdQuery.TryComp(uid, out var mobThreshHolds) + || !_map.TryFindGridAt(_transform.GetMapCoordinates((uid, transform)), out var gridUid, out _)) + return; + + var dragging = mobThreshHolds.CurrentThresholdState is MobState.Critical or MobState.Dead; + var distance = (transform.LocalPosition - component.StepPos).Length(); + var stepSize = dragging ? component.DragSize : component.StepSize; + + if (!(distance > stepSize)) + return; + + component.RightStep = !component.RightStep; + + var entity = Spawn(component.StepProtoId, CalcCoords(gridUid, component, transform, dragging)); + var footPrintComponent = EnsureComp(entity); + + footPrintComponent.PrintOwner = uid; + Dirty(entity, footPrintComponent); + + if (_appearanceQuery.TryComp(entity, out var appearance)) + { + _appearance.SetData(entity, FootPrintVisualState.State, PickState(uid, dragging), appearance); + _appearance.SetData(entity, FootPrintVisualState.Color, component.PrintsColor, appearance); + } + + if (!_transformQuery.TryComp(entity, out var stepTransform)) + return; + + stepTransform.LocalRotation = dragging + ? (transform.LocalPosition - component.StepPos).ToAngle() + Angle.FromDegrees(-90f) + : transform.LocalRotation + Angle.FromDegrees(180f); + + component.PrintsColor = component.PrintsColor.WithAlpha(Math.Max(0f, component.PrintsColor.A - component.ColorReduceAlpha)); + component.StepPos = transform.LocalPosition; + + if (!TryComp(entity, out var solutionContainer) + || !_solution.ResolveSolution((entity, solutionContainer), footPrintComponent.SolutionName, ref footPrintComponent.Solution, out var solution) + || string.IsNullOrWhiteSpace(component.ReagentToTransfer) || solution.Volume >= 1) + return; + + _solution.TryAddReagent(footPrintComponent.Solution.Value, component.ReagentToTransfer, 0.01, out _); //was 1 + } + + private EntityCoordinates CalcCoords(EntityUid uid, FootPrintsComponent component, TransformComponent transform, bool state) + { + if (state) + return new EntityCoordinates(uid, transform.LocalPosition); + + var offset = component.RightStep + ? new Angle(Angle.FromDegrees(180f) + transform.LocalRotation).RotateVec(component.OffsetPrint) + : new Angle(transform.LocalRotation).RotateVec(component.OffsetPrint); + + return new EntityCoordinates(uid, transform.LocalPosition + offset); + } + + private FootPrintVisuals PickState(EntityUid uid, bool dragging) + { + var state = FootPrintVisuals.BareFootPrint; + + if (_inventory.TryGetSlotEntity(uid, "shoes", out _)) + state = FootPrintVisuals.ShoesPrint; + + if (_inventory.TryGetSlotEntity(uid, "outerClothing", out var suit) && TryComp(suit, out _)) + state = FootPrintVisuals.SuitPrint; + + if (dragging) + state = FootPrintVisuals.Dragging; + + return state; + } +} diff --git a/Content.Server/_EE/FootPrint/PuddleFootPrintsSystem.cs b/Content.Server/_EE/FootPrint/PuddleFootPrintsSystem.cs new file mode 100644 index 0000000000..765daa8e3d --- /dev/null +++ b/Content.Server/_EE/FootPrint/PuddleFootPrintsSystem.cs @@ -0,0 +1,57 @@ +using System.Linq; +using Content.Shared._EE.Flight; // DeltaV +using Content.Shared._EE.FootPrint; +using Content.Shared.Chemistry.Components.SolutionManager; +using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Fluids; +using Content.Shared.Fluids.Components; +using Robust.Shared.Physics.Events; + +namespace Content.Server._EE.FootPrint; + +public sealed class PuddleFootPrintsSystem : EntitySystem +{ + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!; + [Dependency] private readonly SharedFlightSystem _flight = default!; // DeltaV + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnStepTrigger); + } + + private void OnStepTrigger(EntityUid uid, PuddleFootPrintsComponent component, ref EndCollideEvent args) + { + if (_flight.IsFlying(uid)) // DeltaV - Flying players won't make footprints + return; + + if (!TryComp(uid, out var appearance) + || !TryComp(uid, out var puddle) + || !TryComp(args.OtherEntity, out var tripper) + || !TryComp(uid, out var solutionManager) + || !_solutionContainer.ResolveSolution((uid, solutionManager), puddle.SolutionName, ref puddle.Solution, out var solutions)) + return; + + var totalSolutionQuantity = solutions.Contents.Sum(sol => (float) sol.Quantity); + var waterQuantity = (from sol in solutions.Contents where sol.Reagent.Prototype == "Water" select (float) sol.Quantity).FirstOrDefault(); + + if (waterQuantity / (totalSolutionQuantity / 100f) > component.OffPercent || solutions.Contents.Count <= 0) + return; + + tripper.ReagentToTransfer = + solutions.Contents.Aggregate((l, r) => l.Quantity > r.Quantity ? l : r).Reagent.Prototype; + + if (_appearance.TryGetData(uid, PuddleVisuals.SolutionColor, out var color, appearance) + && _appearance.TryGetData(uid, PuddleVisuals.CurrentVolume, out var volume, appearance)) + AddColor((Color) color, (float) volume * component.SizeRatio, tripper); + + _solutionContainer.RemoveEachReagent(puddle.Solution.Value, 0.01); //was 1 + } + + private void AddColor(Color col, float quantity, FootPrintsComponent component) + { + component.PrintsColor = component.ColorQuantity == 0f ? col : Color.InterpolateBetween(component.PrintsColor, col, component.ColorInterpolationFactor); + component.ColorQuantity += quantity; + } +} diff --git a/Content.Shared/_DV/CCVars/DCCVars.cs b/Content.Shared/_DV/CCVars/DCCVars.cs index 8182273f1d..e3b7843f95 100644 --- a/Content.Shared/_DV/CCVars/DCCVars.cs +++ b/Content.Shared/_DV/CCVars/DCCVars.cs @@ -124,27 +124,6 @@ public sealed partial class DCCVars public static readonly CVarDef YearOffset = CVarDef.Create("game.current_year_offset", 550, CVar.SERVERONLY); - /* - * Footprints - */ - - /// - /// Maximum number of footprints allowed per tile. - /// Won't allow for new footprints to spawn on the tile once reached. - /// Set to 0 to disable per-tile limiting. - /// - public static readonly CVarDef MaxFootPrintsPerTile = - CVarDef.Create("footprints.max_per_tile", 2, CVar.REPLICATED); - - /// - /// Maximum total number of footprints allowed on a single grid. - /// When this limit is reached, the oldest footprint on the grid will be deleted. - /// Set to 0 to disable global limiting. - /// - public static readonly CVarDef MaxFootPrintsPerGrid = - CVarDef.Create("footprints.max_per_grid", 1000, CVar.REPLICATED); - - /* * Feedback webhook */ diff --git a/Content.Shared/_EE/Footprint/FootPrintComponent.cs b/Content.Shared/_EE/Footprint/FootPrintComponent.cs index 88945cea1c..e342275e45 100644 --- a/Content.Shared/_EE/Footprint/FootPrintComponent.cs +++ b/Content.Shared/_EE/Footprint/FootPrintComponent.cs @@ -1,30 +1,23 @@ -using Content.Shared._EE.FootPrint.Systems; -using Content.Shared.Chemistry.Components; +using Content.Shared.Chemistry.Components; using Robust.Shared.GameStates; namespace Content.Shared._EE.FootPrint; /// -/// Component attached to individual footprint entities spawned on the ground. +/// This is used for marking footsteps, handling footprint drawing. /// -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(FootPrintsSystem))] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] public sealed partial class FootPrintComponent : Component { /// - /// The entity that created this footprint (must have ). + /// Owner (with ) of a print (this component). /// - [DataField, AutoNetworkedField] + [AutoNetworkedField] public EntityUid PrintOwner; - /// - /// Name of the solution container for reagent residue left in the footprint. - /// [DataField] public string SolutionName = "step"; - /// - /// The solution container for this footprint's reagent residue. - /// [ViewVariables] public Entity? Solution; } diff --git a/Content.Shared/_EE/Footprint/FootPrintVisuals.cs b/Content.Shared/_EE/Footprint/FootPrintVisuals.cs index 6d31ac30d3..a2ca3d3191 100644 --- a/Content.Shared/_EE/Footprint/FootPrintVisuals.cs +++ b/Content.Shared/_EE/Footprint/FootPrintVisuals.cs @@ -2,58 +2,24 @@ namespace Content.Shared._EE.FootPrint; -/// -/// Visual states for different types of footprints. -/// [Serializable, NetSerializable] public enum FootPrintVisuals : byte { - /// - /// Bare foot footprints (no shoes). - /// BareFootPrint, - - /// - /// Regular shoe footprints. - /// ShoesPrint, - - /// - /// Hardsuit footprints. - /// SuitPrint, - - /// - /// Drag marks from being pulled/dragged. - /// Dragging } -/// -/// Appearance data keys for footprint visuals. -/// [Serializable, NetSerializable] public enum FootPrintVisualState : byte { - /// - /// The current visual state (type of print). - /// State, - - /// - /// The color of the footprint. - /// Color } -/// -/// Sprite layers for footprint rendering. -/// [Serializable, NetSerializable] public enum FootPrintVisualLayers : byte { - /// - /// The footprint sprite layer. - /// Print } diff --git a/Content.Shared/_EE/Footprint/FootPrintsComponent.cs b/Content.Shared/_EE/Footprint/FootPrintsComponent.cs index 32a6830f57..7c2da23b19 100644 --- a/Content.Shared/_EE/Footprint/FootPrintsComponent.cs +++ b/Content.Shared/_EE/Footprint/FootPrintsComponent.cs @@ -1,42 +1,29 @@ using System.Numerics; -using Content.Shared._EE.FootPrint.Systems; -using Content.Shared.Chemistry.Reagent; -using Content.Shared.FixedPoint; -using Robust.Shared.GameStates; using Robust.Shared.Prototypes; using Robust.Shared.Utility; namespace Content.Shared._EE.FootPrint; -/// -/// Component for entities that can leave footprints as they move. -/// Tracks color state, position, and configuration for footprint generation. -/// -[RegisterComponent, NetworkedComponent, Access(typeof(FootPrintsSystem), typeof(PuddleFootPrintsSystem))] -[AutoGenerateComponentState(fieldDeltas: true)] +[RegisterComponent] public sealed partial class FootPrintsComponent : Component { - /// - /// Path to the RSI containing footprint sprites. - /// - [DataField] - public ResPath RsiPath = new("/Textures/_EE/Effects/footprints.rsi"); + [ViewVariables(VVAccess.ReadOnly), DataField] + public ResPath RsiPath = new("/Textures/_EE/Effects/footprints.rsi"); // DeltaV moved to its own space - #region Sprite State Names - - [DataField] + // all of those are set as a layer + [ViewVariables(VVAccess.ReadOnly), DataField] public string LeftBarePrint = "footprint-left-bare-human"; - [DataField] + [ViewVariables(VVAccess.ReadOnly), DataField] public string RightBarePrint = "footprint-right-bare-human"; - [DataField] + [ViewVariables(VVAccess.ReadOnly), DataField] public string ShoesPrint = "footprint-shoes"; - [DataField] + [ViewVariables(VVAccess.ReadOnly), DataField] public string SuitPrint = "footprint-suit"; - [DataField] + [ViewVariables(VVAccess.ReadOnly), DataField] public string[] DraggingPrint = [ "dragging-1", @@ -45,83 +32,57 @@ public sealed partial class FootPrintsComponent : Component "dragging-4", "dragging-5", ]; + // yea, those - #endregion - - /// - /// Prototype ID for spawned footprint entities. - /// - [DataField] + [ViewVariables(VVAccess.ReadOnly), DataField] public EntProtoId StepProtoId = "Footstep"; - /// - /// The amount of solution to transfer with each footprint when stepping into a puddle. - /// - [DataField] - public FixedPoint2 AmountToTransfer = 0.01; - - /// - /// Current color of footprints being left. Alpha channel determines visibility. - /// - [DataField, AutoNetworkedField] + [ViewVariables(VVAccess.ReadOnly), DataField] public Color PrintsColor = Color.FromHex("#00000000"); /// - /// The size scaling factor for footprint steps. Must be positive. + /// The size scaling factor for footprint steps. Must be positive. /// - [DataField, AutoNetworkedField] + [DataField] public float StepSize = 0.7f; /// - /// The size scaling factor for drag marks. Must be positive. + /// The size scaling factor for drag marks. Must be positive. /// - [DataField, AutoNetworkedField] + [DataField] public float DragSize = 0.5f; /// - /// The total amount of color accumulated from stepping in puddles. - /// Used to determine when color should start fading. + /// The amount of color to transfer from the source (e.g., puddle) to the footprint. /// - [DataField, AutoNetworkedField] + [DataField] public float ColorQuantity; /// - /// The factor by which the alpha channel is reduced with each footprint. - /// Higher values = faster fading. + /// The factor by which the alpha channel is reduced in subsequent footprints. /// [DataField] public float ColorReduceAlpha = 0.1f; - /// - /// The reagent ID to transfer to footprint entities, set when stepping in puddles. - /// - [DataField, AutoNetworkedField] - public ProtoId? ReagentToTransfer; + [DataField] + public string? ReagentToTransfer; - /// - /// Offset applied to footprints perpendicular to movement direction. - /// Creates the left/right alternating pattern. - /// [DataField] public Vector2 OffsetPrint = new(0.1f, 0f); /// - /// Tracks which foot should make the next print. True for right foot, false for left. + /// Tracks which foot should make the next print. True for right foot, false for left. /// - [DataField, AutoNetworkedField] public bool RightStep = true; /// - /// The position of the last footprint in local coordinates. - /// Used to determine when enough distance has been traveled for the next print. + /// The position of the last footprint in world coordinates. /// - [DataField, AutoNetworkedField] public Vector2 StepPos = Vector2.Zero; /// - /// Controls how quickly the footprint color transitions when stepping in new puddles. - /// Value between 0 and 1, where higher values mean faster color changes. + /// Controls how quickly the footprint color transitions between steps. + /// Value between 0 and 1, where higher values mean faster color changes. /// - [DataField] public float ColorInterpolationFactor = 0.2f; } diff --git a/Content.Shared/_EE/Footprint/GridFootPrintsComponent.cs b/Content.Shared/_EE/Footprint/GridFootPrintsComponent.cs deleted file mode 100644 index 75690a1769..0000000000 --- a/Content.Shared/_EE/Footprint/GridFootPrintsComponent.cs +++ /dev/null @@ -1,80 +0,0 @@ -using Content.Shared._EE.FootPrint.Systems; -using Robust.Shared.GameStates; -using Robust.Shared.Serialization; - -namespace Content.Shared._EE.FootPrint; - -/// -/// Component attached to grids to track footprints per tile. -/// -[RegisterComponent, NetworkedComponent, Access(typeof(FootPrintsSystem))] -public sealed partial class GridFootPrintsComponent : Component -{ - /// - /// Tracks all footprints on this grid, organized by tile position. - /// NOTE: Not auto-networked - we use custom delta states instead - /// - [DataField] - public Dictionary> FootPrintsByTile = new(); - - /// - /// Total count of all footprints on this grid. - /// - [DataField] - public int TotalFootPrints; - - /// - /// Tracks which tiles have been modified since the last state send. - /// - [ViewVariables] - public HashSet DirtyTiles = new(); - - /// - /// Tracks which tiles have been completely removed since the last state send. - /// - [ViewVariables] - public HashSet RemovedTiles = new(); - - /// - /// If true, the next state will be a full state instead of delta. - /// - [ViewVariables] - public bool NeedFullState = true; -} - -/// -/// Custom component state that supports delta compression. -/// Only sends changed tiles instead of the entire dictionary. -/// -[Serializable, NetSerializable] -public sealed class GridFootPrintsComponentState( - bool fullState, - Dictionary>? modified, - HashSet? removed, - int totalFootPrints) - : IComponentState -{ - /// - /// If true, this is a full state sync. Client should replace entire dictionary. - /// If false, this is a delta - client should apply changes. - /// - public bool FullState = fullState; - - /// - /// Modified or added tiles and their footprint lists. - /// For full state: contains all tiles. - /// For delta: contains only changed tiles. - /// - public Dictionary>? Modified = modified; - - /// - /// Tiles that have been completely cleared (no footprints remaining). - /// Only relevant for delta states. - /// - public HashSet? Removed = removed; - - /// - /// Total count of footprints on the grid. - /// - public int TotalFootPrints = totalFootPrints; -} diff --git a/Content.Shared/_EE/Footprint/PuddleFootPrintsComponent.cs b/Content.Shared/_EE/Footprint/PuddleFootPrintsComponent.cs index cf6f2dda92..b85fe92842 100644 --- a/Content.Shared/_EE/Footprint/PuddleFootPrintsComponent.cs +++ b/Content.Shared/_EE/Footprint/PuddleFootPrintsComponent.cs @@ -1,24 +1,11 @@ -using Content.Shared._EE.FootPrint.Systems; -using Robust.Shared.GameStates; - namespace Content.Shared._EE.FootPrint; -/// -/// Component for puddles that can leave footprints when stepped on. -/// -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(PuddleFootPrintsSystem))] +[RegisterComponent] public sealed partial class PuddleFootPrintsComponent : Component { - /// - /// The ratio of the puddle's volume that determines color intensity transferred to footprints. - /// - [DataField, AutoNetworkedField] + [ViewVariables()] public float SizeRatio = 0.2f; - /// - /// Minimum percentage of water content required before the puddle will transfer footprints. - /// Prevents pure water puddles from leaving colored footprints. - /// - [DataField, AutoNetworkedField] + [ViewVariables()] public float OffPercent = 80f; } diff --git a/Content.Shared/_EE/Footprint/Systems/FootPrintsSystem.HandleState.cs b/Content.Shared/_EE/Footprint/Systems/FootPrintsSystem.HandleState.cs deleted file mode 100644 index db03679e9d..0000000000 --- a/Content.Shared/_EE/Footprint/Systems/FootPrintsSystem.HandleState.cs +++ /dev/null @@ -1,140 +0,0 @@ -using Robust.Shared.GameStates; -using Robust.Shared.Timing; - -namespace Content.Shared._EE.FootPrint.Systems; - -public sealed partial class FootPrintsSystem -{ - [Dependency] private readonly IGameTiming _timing = default!; - - private void InitializeStateHandling() - { - SubscribeLocalEvent(OnGridFootPrintsGetState); - SubscribeLocalEvent(OnGridFootPrintsHandleState); - } - - private void OnGridFootPrintsGetState(Entity ent, ref ComponentGetState args) - { - // Determine if we should send full state or delta - // Send full state if: - // - Component explicitly needs it - // - This is the first send to this player (FromTick is very old) - // - Periodically for desync recovery - var sendFullState = ent.Comp.NeedFullState || - args.FromTick == GameTick.Zero || - args.FromTick < _timing.CurTick - - (uint)(TimeSpan.FromSeconds(30).Seconds * _timing.TickRate); - - if (sendFullState) - { - // Send complete state - var fullData = new Dictionary>(ent.Comp.FootPrintsByTile.Count); - foreach (var (tile, footprints) in ent.Comp.FootPrintsByTile) - { - // Deep copy the list to avoid reference sharing - fullData[tile] = new List(footprints); - } - - args.State = new GridFootPrintsComponentState( - fullState: true, - modified: fullData, - removed: null, - totalFootPrints: ent.Comp.TotalFootPrints - ); - - // Clear dirty tracking after full state send - ent.Comp.DirtyTiles.Clear(); - ent.Comp.RemovedTiles.Clear(); - ent.Comp.NeedFullState = false; - } - else - { - // Send delta state - only changed tiles - Dictionary>? modifiedTiles = null; - HashSet? removedTiles = null; - - if (ent.Comp.DirtyTiles.Count > 0) - { - modifiedTiles = new Dictionary>(ent.Comp.DirtyTiles.Count); - foreach (var tile in ent.Comp.DirtyTiles) - { - if (ent.Comp.FootPrintsByTile.TryGetValue(tile, out var footprints)) - { - // Deep copy the list - modifiedTiles[tile] = new List(footprints); - } - } - } - - if (ent.Comp.RemovedTiles.Count > 0) - { - removedTiles = new HashSet(ent.Comp.RemovedTiles); - } - - // Only create state if there are actual changes - if (modifiedTiles != null || removedTiles != null) - { - args.State = new GridFootPrintsComponentState( - fullState: false, - modified: modifiedTiles, - removed: removedTiles, - totalFootPrints: ent.Comp.TotalFootPrints - ); - - // Clear dirty tracking after delta send - ent.Comp.DirtyTiles.Clear(); - ent.Comp.RemovedTiles.Clear(); - } - // If no changes, args.State stays null and nothing is sent - } - } - - private void OnGridFootPrintsHandleState(Entity ent, ref ComponentHandleState args) - { - if (args.Current is not GridFootPrintsComponentState state) - return; - - if (state.FullState) - { - // Full state sync - replace entire dictionary - ent.Comp.FootPrintsByTile.Clear(); - - if (state.Modified != null) - { - foreach (var (tile, footprints) in state.Modified) - { - ent.Comp.FootPrintsByTile[tile] = new List(footprints); - } - } - } - else - { - // Delta state - apply changes - - // Apply removed tiles - if (state.Removed != null) - { - foreach (var tile in state.Removed) - { - ent.Comp.FootPrintsByTile.Remove(tile); - } - } - - // Apply modified/added tiles - if (state.Modified != null) - { - foreach (var (tile, footprints) in state.Modified) - { - ent.Comp.FootPrintsByTile[tile] = new List(footprints); - } - } - } - - // Always update total count - ent.Comp.TotalFootPrints = state.TotalFootPrints; - - // Clear any local dirty tracking on client - ent.Comp.DirtyTiles.Clear(); - ent.Comp.RemovedTiles.Clear(); - } -} diff --git a/Content.Shared/_EE/Footprint/Systems/FootPrintsSystem.cs b/Content.Shared/_EE/Footprint/Systems/FootPrintsSystem.cs index 3d3538a15c..5f282702bb 100644 --- a/Content.Shared/_EE/Footprint/Systems/FootPrintsSystem.cs +++ b/Content.Shared/_EE/Footprint/Systems/FootPrintsSystem.cs @@ -1,330 +1 @@ -using Content.Shared._EE.Flight; -using Content.Shared.Chemistry.Components.SolutionManager; -using Content.Shared.Chemistry.EntitySystems; -using Content.Shared.Inventory; -using Content.Shared.Mobs; -using Content.Shared.Mobs.Components; -using Content.Shared.Standing; -using Content.Shared.Tag; -using Robust.Shared.Map; -using Robust.Shared.Random; -using Robust.Shared.Configuration; -using Content.Shared._DV.CCVars; -using Content.Shared.Chemistry.Reagent; -using Robust.Shared.Prototypes; - -namespace Content.Shared._EE.FootPrint.Systems; - -/// -/// Handles creation of footprints as entities move. -/// -public sealed partial class FootPrintsSystem : EntitySystem -{ - [Dependency] private readonly IConfigurationManager _cfg = default!; - [Dependency] private readonly IMapManager _map = default!; - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly InventorySystem _inventory = default!; - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly SharedFlightSystem _flight = default!; - [Dependency] private readonly SharedMapSystem _mapSystem = default!; - [Dependency] private readonly SharedSolutionContainerSystem _solution = default!; - [Dependency] private readonly SharedTransformSystem _transform = default!; - [Dependency] private readonly StandingStateSystem _standing = default!; - [Dependency] private readonly TagSystem _tag = default!; - - private EntityQuery _appearanceQuery; - private EntityQuery _flightQuery; - private EntityQuery _gridFootPrintsQuery; - private EntityQuery _mobThresholdQuery; - private EntityQuery _standingQuery; - private EntityQuery _transformQuery; - - private const string HardsuitTag = "Hardsuit"; - - private int _maxPerTile; - private int _maxPerGrid; - - public override void Initialize() - { - base.Initialize(); - - _appearanceQuery = GetEntityQuery(); - _flightQuery = GetEntityQuery(); - _gridFootPrintsQuery = GetEntityQuery(); - _mobThresholdQuery = GetEntityQuery(); - _standingQuery = GetEntityQuery(); - _transformQuery = GetEntityQuery(); - - SubscribeLocalEvent(OnStartupComponent); - SubscribeLocalEvent(OnMove); - SubscribeLocalEvent(OnFootPrintRemoved); - - InitializeStateHandling(); - - // Subscribe to CVar changes - Subs.CVar(_cfg, DCCVars.MaxFootPrintsPerTile, value => _maxPerTile = value, true); - Subs.CVar(_cfg, DCCVars.MaxFootPrintsPerGrid, value => _maxPerGrid = value, true); - } - - private void OnStartupComponent(Entity ent, ref ComponentStartup args) - { - // Add slight random variation to step size for more natural-looking prints - ent.Comp.StepSize = Math.Max(0f, ent.Comp.StepSize + _random.NextFloat(-0.05f, 0.05f)); - DirtyField(ent.AsNullable(), nameof(FootPrintsComponent.StepSize)); - } - - private void OnMove(Entity ent, ref MoveEvent args) - { - // Don't create footprints if flying - if (_flightQuery.TryComp(ent, out var flight) && _flight.IsFlying((ent, flight))) - return; - - // Don't create footprints if color is fully transparent - if (ent.Comp.PrintsColor.A <= 0f) - return; - - if (!_transformQuery.TryComp(ent, out var transform)) - return; - - if (!_mobThresholdQuery.TryComp(ent, out var mobThresholds)) - return; - - // Check if entity is being dragged (critical or dead) - var isCrit = mobThresholds.CurrentThresholdState is MobState.Critical or MobState.Dead; - var dragging = isCrit || _standingQuery.TryComp(ent, out var standingComp) && _standing.IsDown((ent, standingComp)); - - // Calculate distance traveled since last footprint - var distance = (transform.LocalPosition - ent.Comp.StepPos).Length(); - var stepSize = dragging ? ent.Comp.DragSize : ent.Comp.StepSize; - - // Not enough distance traveled yet - MOST COMMON EARLY RETURN - if (distance <= stepSize) - return; - - // Need to be on a grid to leave footprints - if (!_map.TryFindGridAt(_transform.GetMapCoordinates((ent, transform)), out var gridUid, out var grid)) - return; - - // Calculate spawn coordinates - var coords = CalcCoords(gridUid, ent.Comp, transform, dragging); - - // Get the tile position for limit checking - if (!_mapSystem.TryGetTileRef(gridUid, grid, coords, out var tileRef)) - return; - - var tilePos = tileRef.GridIndices; - - // Check per-tile limit BEFORE spawning - // Use TryComp instead of EnsureComp to avoid adding component in hot path - if (_maxPerTile > 0 && _gridFootPrintsQuery.TryComp(gridUid, out var gridFootPrints)) - { - if (gridFootPrints.FootPrintsByTile.TryGetValue(tilePos, out var existingFootPrints) - && existingFootPrints.Count >= _maxPerTile) - { - // Tile is full, don't spawn - return; - } - } - - // Alternate feet - ent.Comp.RightStep = !ent.Comp.RightStep; - DirtyField(ent.AsNullable(), nameof(FootPrintsComponent.RightStep)); - - // Spawn the footprint entity - var footprint = EntityManager.PredictedSpawnAtPosition(ent.Comp.StepProtoId.Id, coords); - - // Update appearance with current color and state - if (_appearanceQuery.TryComp(footprint, out var appearance)) - { - _appearance.SetData(footprint, FootPrintVisualState.State, PickState(ent, dragging), appearance); - _appearance.SetData(footprint, FootPrintVisualState.Color, ent.Comp.PrintsColor, appearance); - } - - // Set rotation - if (_transformQuery.TryComp(footprint, out var stepTransform)) - { - stepTransform.LocalRotation = dragging - ? (transform.LocalPosition - ent.Comp.StepPos).ToAngle() + Angle.FromDegrees(-90f) - : transform.LocalRotation + Angle.FromDegrees(180f); - } - - if (!TryComp(footprint, out var footPrintComponent)) - return; - - // Set the owner reference - footPrintComponent.PrintOwner = ent; - Dirty(footprint, footPrintComponent); - - // Track the footprint on the grid - TrackFootPrint(gridUid, GetNetEntity(footprint), tilePos); - - // Reduce color alpha for next footprint - ent.Comp.PrintsColor = ent.Comp.PrintsColor.WithAlpha( - Math.Max(0f, ent.Comp.PrintsColor.A - ent.Comp.ColorReduceAlpha)); - DirtyField(ent.AsNullable(), nameof(FootPrintsComponent.PrintsColor)); - - // Update last step position - ent.Comp.StepPos = transform.LocalPosition; - DirtyField(ent.AsNullable(), nameof(FootPrintsComponent.StepPos)); - - // Handle reagent transfer - if (ent.Comp.ReagentToTransfer is { } reagent) - { - TryTransferReagent((footprint, footPrintComponent), ent, reagent); - } - } - - private void OnFootPrintRemoved(Entity ent, ref ComponentRemove args) - { - // Clean up tracking when footprint is deleted - if (!_transformQuery.TryComp(ent, out var transform)) - return; - - if (transform.GridUid == null) - return; - - UntrackFootPrint(transform.GridUid.Value, GetNetEntity(ent)); - } - - private void TrackFootPrint(EntityUid gridUid, NetEntity footPrintUid, Vector2i tile) - { - var gridFootPrints = EnsureComp(gridUid); - - // Add to tile tracking - if (!gridFootPrints.FootPrintsByTile.TryGetValue(tile, out var tileFootPrints)) - { - tileFootPrints = new List(); - gridFootPrints.FootPrintsByTile[tile] = tileFootPrints; - } - - tileFootPrints.Add(footPrintUid); - gridFootPrints.TotalFootPrints++; - - // DELTA TRACKING: Mark tile as dirty - gridFootPrints.DirtyTiles.Add(tile); - // Remove from removed set if it was there - gridFootPrints.RemovedTiles.Remove(tile); - - // Enforce global limit - if (_maxPerGrid > 0 && gridFootPrints.TotalFootPrints > _maxPerGrid) - { - RemoveOldestFootPrint(gridFootPrints); - } - - Dirty(gridUid, gridFootPrints); - } - - private void UntrackFootPrint(EntityUid gridUid, NetEntity footPrintUid) - { - if (!_gridFootPrintsQuery.TryComp(gridUid, out var gridFootPrints)) - return; - - // Find and remove from tile tracking - foreach (var (tile, footPrints) in gridFootPrints.FootPrintsByTile) - { - if (footPrints.Remove(footPrintUid)) - { - gridFootPrints.TotalFootPrints--; - - // DELTA TRACKING - if (footPrints.Count == 0) - { - // Tile is now empty - gridFootPrints.FootPrintsByTile.Remove(tile); - gridFootPrints.RemovedTiles.Add(tile); - gridFootPrints.DirtyTiles.Remove(tile); - } - else - { - // Tile still has footprints, just modified - gridFootPrints.DirtyTiles.Add(tile); - } - - Dirty(gridUid, gridFootPrints); - break; - } - } - } - - private void RemoveOldestFootPrint(GridFootPrintsComponent gridFootPrints) - { - foreach (var (tile, footPrints) in gridFootPrints.FootPrintsByTile) - { - if (footPrints.Count > 0) - { - var toRemove = footPrints[0]; - footPrints.RemoveAt(0); - QueueDel(GetEntity(toRemove)); - gridFootPrints.TotalFootPrints--; - - // DELTA TRACKING - if (footPrints.Count == 0) - { - gridFootPrints.FootPrintsByTile.Remove(tile); - gridFootPrints.RemovedTiles.Add(tile); - gridFootPrints.DirtyTiles.Remove(tile); - } - else - { - gridFootPrints.DirtyTiles.Add(tile); - } - - return; - } - } - } - - private void TryTransferReagent(Entity ent, Entity tripper, ProtoId reagentId) - { - if (!TryComp(ent, out var solutionContainer)) - return; - - if (!_solution.ResolveSolution((ent, solutionContainer), - ent.Comp.SolutionName, - ref ent.Comp.Solution, - out var solution)) - return; - - // Don't overfill - if (solution.Volume >= 1) - return; - - // Transfer a small amount of reagent - _solution.TryAddReagent(ent.Comp.Solution.Value, reagentId, tripper.Comp.AmountToTransfer, out _); - } - - private EntityCoordinates CalcCoords(EntityUid gridUid, - FootPrintsComponent component, - TransformComponent transform, - bool dragging) - { - // For dragging, place footprint at center - if (dragging) - return new EntityCoordinates(gridUid, transform.LocalPosition); - - // For walking, offset left or right based on which foot - var offset = component.RightStep - ? new Angle(Angle.FromDegrees(180f) + transform.LocalRotation).RotateVec(component.OffsetPrint) - : new Angle(transform.LocalRotation).RotateVec(component.OffsetPrint); - - return new EntityCoordinates(gridUid, transform.LocalPosition + offset); - } - - private FootPrintVisuals PickState(Entity ent, bool dragging) - { - // If dragging, always use drag marks - if (dragging) - return FootPrintVisuals.Dragging; - - // Check for shoes - if (_inventory.TryGetSlotEntity(ent, "shoes", out _)) - return FootPrintVisuals.ShoesPrint; - - // Check for hardsuit - if (_inventory.TryGetSlotEntity(ent, "outerClothing", out var suit) && _tag.HasTag(suit.Value, HardsuitTag)) - return FootPrintVisuals.SuitPrint; - - // Default to bare feet - return FootPrintVisuals.BareFootPrint; - } -} + \ No newline at end of file diff --git a/Content.Shared/_EE/Footprint/Systems/PuddleFootPrintsSystem.cs b/Content.Shared/_EE/Footprint/Systems/PuddleFootPrintsSystem.cs deleted file mode 100644 index 6189f48fda..0000000000 --- a/Content.Shared/_EE/Footprint/Systems/PuddleFootPrintsSystem.cs +++ /dev/null @@ -1,123 +0,0 @@ -using System.Linq; -using Content.Shared._EE.Flight; -using Content.Shared.Chemistry.Components.SolutionManager; -using Content.Shared.Chemistry.EntitySystems; -using Content.Shared.Chemistry.Reagent; -using Content.Shared.Fluids; -using Content.Shared.Fluids.Components; -using Robust.Shared.Physics.Events; -using Robust.Shared.Prototypes; - -namespace Content.Shared._EE.FootPrint.Systems; - -/// -/// Handles transferring puddle colors and reagents to entities with footprints when they step in puddles. -/// -public sealed class PuddleFootPrintsSystem : EntitySystem -{ - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly SharedFlightSystem _flight = default!; - [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!; - - private EntityQuery _flightQuery; - - private static readonly ProtoId WaterPrototype = "Water"; - - public override void Initialize() - { - base.Initialize(); - - _flightQuery = GetEntityQuery(); - - SubscribeLocalEvent(OnEndCollide); - } - - private void OnEndCollide(Entity ent, ref EndCollideEvent args) - { - var tripper = args.OtherEntity; - - // Don't process if the tripper is flying - if (_flightQuery.TryComp(ent, out var flight) && _flight.IsFlying((ent, flight))) - return; - - // Only process entities that can leave footprints - if (!TryComp(tripper, out var footPrints)) - return; - - // Get puddle appearance and solution data - if (!TryComp(ent, out var appearance)) - return; - - if (!TryComp(ent, out var puddleComp)) - return; - - if (!TryComp(ent, out var solutionManager)) - return; - - if (!_solutionContainer.ResolveSolution((ent, solutionManager), - puddleComp.SolutionName, - ref puddleComp.Solution, - out var solution)) - return; - - // Calculate total solution quantity and water percentage - var totalSolutionQuantity = solution.Contents.Sum(sol => (float)sol.Quantity); - - if (totalSolutionQuantity <= 0 || solution.Contents.Count <= 0) - return; - - var waterQuantity = solution.Contents - .Where(sol => sol.Reagent.Prototype == WaterPrototype) - .Sum(sol => (float)sol.Quantity); - - var waterPercent = (waterQuantity / totalSolutionQuantity) * 100f; - - // If puddle is mostly water, don't transfer color - if (waterPercent > ent.Comp.OffPercent) - return; - - // Find the reagent with the highest quantity to transfer - var primaryReagent = solution.Contents - .OrderByDescending(sol => sol.Quantity) - .FirstOrDefault(); - - if (primaryReagent.Reagent.Prototype == null) - return; - - // Set the reagent to transfer - footPrints.ReagentToTransfer = primaryReagent.Reagent.Prototype; - - // Transfer color from puddle to footprints - if (_appearance.TryGetData(ent, PuddleVisuals.SolutionColor, out var colorValue, appearance) - && _appearance.TryGetData(ent, PuddleVisuals.CurrentVolume, out var volumeValue, appearance)) - { - if (colorValue is Color color && volumeValue is float volume) - { - AddColor(color, volume * ent.Comp.SizeRatio, footPrints); - Dirty(tripper, footPrints); - } - } - - // Remove small amount of reagent from puddle - _solutionContainer.RemoveEachReagent(puddleComp.Solution.Value, footPrints.AmountToTransfer); - } - - private void AddColor(Color color, float quantity, FootPrintsComponent component) - { - // If no color yet, use the puddle's color directly - if (component.ColorQuantity == 0f) - { - component.PrintsColor = color; - } - else - { - // Interpolate between current color and new color - component.PrintsColor = Color.InterpolateBetween( - component.PrintsColor, - color, - component.ColorInterpolationFactor); - } - - component.ColorQuantity += quantity; - } -} diff --git a/Resources/Prototypes/Entities/Effects/puddle.yml b/Resources/Prototypes/Entities/Effects/puddle.yml index e3b223671a..6c235e9abe 100644 --- a/Resources/Prototypes/Entities/Effects/puddle.yml +++ b/Resources/Prototypes/Entities/Effects/puddle.yml @@ -215,4 +215,50 @@ - type: Tag tags: - DNASolutionScannable - - type: PuddleFootPrints # DeltaV + - type: PuddleFootPrints # DeltaV- Begin updates from EE Blood Puddle + +- type: entity + name: footstep + id: Footstep + save: false + description: Trace of liquid + components: + - type: Clickable + - type: FootstepModifier + footstepSoundCollection: + collection: FootstepWater + params: + volume: 3 + - type: Transform + noRot: false + - type: Sprite + drawdepth: FloorObjects + color: "#FFFFFF80" + - type: Physics + bodyType: Static + - type: Fixtures + fixtures: + slipFixture: + shape: + !type:PhysShapeAabb + bounds: "-0.4,-0.4,0.4,0.4" + mask: + - ItemMask + layer: + - SlipLayer + hard: false + - type: SolutionContainerManager + solutions: + step: { maxVol: 2 } + - type: FootPrint + - type: Puddle + solution: step + - type: Appearance + - type: SpawnOnDespawn + prototype: PuddleSparkle + - type: TimedDespawn + lifetime: 60 # DeltaV End updates from EE Blood Puddle + - type: SpeedModifierContacts + ignoreWhitelist: + components: + - InputMover diff --git a/Resources/Prototypes/_DV/Entities/Effects/puddles.yml b/Resources/Prototypes/_DV/Entities/Effects/puddles.yml index 7efcfbfa61..1a34855d46 100644 --- a/Resources/Prototypes/_DV/Entities/Effects/puddles.yml +++ b/Resources/Prototypes/_DV/Entities/Effects/puddles.yml @@ -195,48 +195,3 @@ - id: PuddleRandomChems - id: PuddleRandomKitchen - id: PuddleRandomMisc - - -- type: entity - name: footstep - id: Footstep - save: false - description: Trace of liquid - components: - - type: Clickable - - type: FootstepModifier - footstepSoundCollection: - collection: FootstepWater - params: - volume: 3 - - type: Transform - noRot: false - - type: Sprite - drawdepth: FloorObjects - color: "#FFFFFF80" - - type: Physics - bodyType: Static - - type: Fixtures - fixtures: - slipFixture: - shape: - !type:PhysShapeAabb - bounds: "-0.4,-0.4,0.4,0.4" - mask: - - ItemMask - layer: - - SlipLayer - hard: false - - type: SolutionContainerManager - solutions: - step: { maxVol: 2 } - - type: FootPrint - - type: Puddle - solution: step - - type: Appearance - - type: SpawnOnDespawn - prototype: PuddleSparkle - - type: SpeedModifierContacts - ignoreWhitelist: - components: - - FootPrints