footprints prediction (#5111)
* works * shuffle stuff around * no more fucky wuckies, fucky wuckies are GONE * maybe this will just fix my problems? * usings * thats not how flying works * review
This commit is contained in:
parent
3a051b65a3
commit
0d0d5a17de
|
|
@ -1,127 +0,0 @@
|
|||
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<TransformComponent> _transformQuery;
|
||||
private EntityQuery<MobThresholdsComponent> _mobThresholdQuery;
|
||||
private EntityQuery<AppearanceComponent> _appearanceQuery;
|
||||
|
||||
// private EntityQuery<LayingDownComponent> _layingQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_transformQuery = GetEntityQuery<TransformComponent>();
|
||||
_mobThresholdQuery = GetEntityQuery<MobThresholdsComponent>();
|
||||
_appearanceQuery = GetEntityQuery<AppearanceComponent>();
|
||||
// _layingQuery = GetEntityQuery<LayingDownComponent>();
|
||||
|
||||
SubscribeLocalEvent<FootPrintsComponent, ComponentStartup>(OnStartupComponent);
|
||||
SubscribeLocalEvent<FootPrintsComponent, MoveEvent>(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<FootPrintComponent>(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<SolutionContainerManagerComponent>(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<PressureProtectionComponent>(suit, out _))
|
||||
state = FootPrintVisuals.SuitPrint;
|
||||
|
||||
if (dragging)
|
||||
state = FootPrintVisuals.Dragging;
|
||||
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
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<PuddleFootPrintsComponent, EndCollideEvent>(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<AppearanceComponent>(uid, out var appearance)
|
||||
|| !TryComp<PuddleComponent>(uid, out var puddle)
|
||||
|| !TryComp<FootPrintsComponent>(args.OtherEntity, out var tripper)
|
||||
|| !TryComp<SolutionContainerManagerComponent>(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;
|
||||
}
|
||||
}
|
||||
|
|
@ -112,6 +112,27 @@ public sealed partial class DCCVars
|
|||
public static readonly CVarDef<int> YearOffset =
|
||||
CVarDef.Create("game.current_year_offset", 550, CVar.SERVERONLY);
|
||||
|
||||
/*
|
||||
* Footprints
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> MaxFootPrintsPerTile =
|
||||
CVarDef.Create("footprints.max_per_tile", 2, CVar.REPLICATED);
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> MaxFootPrintsPerGrid =
|
||||
CVarDef.Create("footprints.max_per_grid", 1000, CVar.REPLICATED);
|
||||
|
||||
|
||||
/*
|
||||
* Feedback webhook
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,23 +1,30 @@
|
|||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared._EE.FootPrint.Systems;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._EE.FootPrint;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for marking footsteps, handling footprint drawing.
|
||||
/// Component attached to individual footprint entities spawned on the ground.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(FootPrintsSystem))]
|
||||
public sealed partial class FootPrintComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Owner (with <see cref="FootPrintsComponent"/>) of a print (this component).
|
||||
/// The entity that created this footprint (must have <see cref="FootPrintsComponent"/>).
|
||||
/// </summary>
|
||||
[AutoNetworkedField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid PrintOwner;
|
||||
|
||||
/// <summary>
|
||||
/// Name of the solution container for reagent residue left in the footprint.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string SolutionName = "step";
|
||||
|
||||
/// <summary>
|
||||
/// The solution container for this footprint's reagent residue.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Entity<SolutionComponent>? Solution;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,24 +2,58 @@
|
|||
|
||||
namespace Content.Shared._EE.FootPrint;
|
||||
|
||||
/// <summary>
|
||||
/// Visual states for different types of footprints.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public enum FootPrintVisuals : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Bare foot footprints (no shoes).
|
||||
/// </summary>
|
||||
BareFootPrint,
|
||||
|
||||
/// <summary>
|
||||
/// Regular shoe footprints.
|
||||
/// </summary>
|
||||
ShoesPrint,
|
||||
|
||||
/// <summary>
|
||||
/// Hardsuit footprints.
|
||||
/// </summary>
|
||||
SuitPrint,
|
||||
|
||||
/// <summary>
|
||||
/// Drag marks from being pulled/dragged.
|
||||
/// </summary>
|
||||
Dragging
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appearance data keys for footprint visuals.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public enum FootPrintVisualState : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// The current visual state (type of print).
|
||||
/// </summary>
|
||||
State,
|
||||
|
||||
/// <summary>
|
||||
/// The color of the footprint.
|
||||
/// </summary>
|
||||
Color
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sprite layers for footprint rendering.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public enum FootPrintVisualLayers : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// The footprint sprite layer.
|
||||
/// </summary>
|
||||
Print
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,42 @@
|
|||
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;
|
||||
|
||||
[RegisterComponent]
|
||||
/// <summary>
|
||||
/// Component for entities that can leave footprints as they move.
|
||||
/// Tracks color state, position, and configuration for footprint generation.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(FootPrintsSystem), typeof(PuddleFootPrintsSystem))]
|
||||
[AutoGenerateComponentState(fieldDeltas: true)]
|
||||
public sealed partial class FootPrintsComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadOnly), DataField]
|
||||
public ResPath RsiPath = new("/Textures/_EE/Effects/footprints.rsi"); // DeltaV moved to its own space
|
||||
/// <summary>
|
||||
/// Path to the RSI containing footprint sprites.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ResPath RsiPath = new("/Textures/_EE/Effects/footprints.rsi");
|
||||
|
||||
// all of those are set as a layer
|
||||
[ViewVariables(VVAccess.ReadOnly), DataField]
|
||||
#region Sprite State Names
|
||||
|
||||
[DataField]
|
||||
public string LeftBarePrint = "footprint-left-bare-human";
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly), DataField]
|
||||
[DataField]
|
||||
public string RightBarePrint = "footprint-right-bare-human";
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly), DataField]
|
||||
[DataField]
|
||||
public string ShoesPrint = "footprint-shoes";
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly), DataField]
|
||||
[DataField]
|
||||
public string SuitPrint = "footprint-suit";
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly), DataField]
|
||||
[DataField]
|
||||
public string[] DraggingPrint =
|
||||
[
|
||||
"dragging-1",
|
||||
|
|
@ -32,57 +45,83 @@ public sealed partial class FootPrintsComponent : Component
|
|||
"dragging-4",
|
||||
"dragging-5",
|
||||
];
|
||||
// yea, those
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly), DataField]
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Prototype ID for spawned footprint entities.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntProtoId<FootPrintComponent> StepProtoId = "Footstep";
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly), DataField]
|
||||
/// <summary>
|
||||
/// The amount of solution to transfer with each footprint when stepping into a puddle.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public FixedPoint2 AmountToTransfer = 0.01;
|
||||
|
||||
/// <summary>
|
||||
/// Current color of footprints being left. Alpha channel determines visibility.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public Color PrintsColor = Color.FromHex("#00000000");
|
||||
|
||||
/// <summary>
|
||||
/// The size scaling factor for footprint steps. Must be positive.
|
||||
/// The size scaling factor for footprint steps. Must be positive.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public float StepSize = 0.7f;
|
||||
|
||||
/// <summary>
|
||||
/// The size scaling factor for drag marks. Must be positive.
|
||||
/// The size scaling factor for drag marks. Must be positive.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public float DragSize = 0.5f;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of color to transfer from the source (e.g., puddle) to the footprint.
|
||||
/// The total amount of color accumulated from stepping in puddles.
|
||||
/// Used to determine when color should start fading.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public float ColorQuantity;
|
||||
|
||||
/// <summary>
|
||||
/// The factor by which the alpha channel is reduced in subsequent footprints.
|
||||
/// The factor by which the alpha channel is reduced with each footprint.
|
||||
/// Higher values = faster fading.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float ColorReduceAlpha = 0.1f;
|
||||
|
||||
[DataField]
|
||||
public string? ReagentToTransfer;
|
||||
/// <summary>
|
||||
/// The reagent ID to transfer to footprint entities, set when stepping in puddles.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public ProtoId<ReagentPrototype>? ReagentToTransfer;
|
||||
|
||||
/// <summary>
|
||||
/// Offset applied to footprints perpendicular to movement direction.
|
||||
/// Creates the left/right alternating pattern.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Vector2 OffsetPrint = new(0.1f, 0f);
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool RightStep = true;
|
||||
|
||||
/// <summary>
|
||||
/// The position of the last footprint in world coordinates.
|
||||
/// The position of the last footprint in local coordinates.
|
||||
/// Used to determine when enough distance has been traveled for the next print.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public Vector2 StepPos = Vector2.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Controls how quickly the footprint color transitions between steps.
|
||||
/// Value between 0 and 1, where higher values mean faster color changes.
|
||||
/// Controls how quickly the footprint color transitions when stepping in new puddles.
|
||||
/// Value between 0 and 1, where higher values mean faster color changes.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float ColorInterpolationFactor = 0.2f;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
using Content.Shared._EE.FootPrint.Systems;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._EE.FootPrint;
|
||||
|
||||
/// <summary>
|
||||
/// Component attached to grids to track footprints per tile.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(FootPrintsSystem))]
|
||||
public sealed partial class GridFootPrintsComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Tracks all footprints on this grid, organized by tile position.
|
||||
/// NOTE: Not auto-networked - we use custom delta states instead
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Dictionary<Vector2i, List<NetEntity>> FootPrintsByTile = new();
|
||||
|
||||
/// <summary>
|
||||
/// Total count of all footprints on this grid.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int TotalFootPrints;
|
||||
|
||||
/// <summary>
|
||||
/// Tracks which tiles have been modified since the last state send.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public HashSet<Vector2i> DirtyTiles = new();
|
||||
|
||||
/// <summary>
|
||||
/// Tracks which tiles have been completely removed since the last state send.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public HashSet<Vector2i> RemovedTiles = new();
|
||||
|
||||
/// <summary>
|
||||
/// If true, the next state will be a full state instead of delta.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool NeedFullState = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom component state that supports delta compression.
|
||||
/// Only sends changed tiles instead of the entire dictionary.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class GridFootPrintsComponentState(
|
||||
bool fullState,
|
||||
Dictionary<Vector2i, List<NetEntity>>? modified,
|
||||
HashSet<Vector2i>? removed,
|
||||
int totalFootPrints)
|
||||
: IComponentState
|
||||
{
|
||||
/// <summary>
|
||||
/// If true, this is a full state sync. Client should replace entire dictionary.
|
||||
/// If false, this is a delta - client should apply changes.
|
||||
/// </summary>
|
||||
public bool FullState = fullState;
|
||||
|
||||
/// <summary>
|
||||
/// Modified or added tiles and their footprint lists.
|
||||
/// For full state: contains all tiles.
|
||||
/// For delta: contains only changed tiles.
|
||||
/// </summary>
|
||||
public Dictionary<Vector2i, List<NetEntity>>? Modified = modified;
|
||||
|
||||
/// <summary>
|
||||
/// Tiles that have been completely cleared (no footprints remaining).
|
||||
/// Only relevant for delta states.
|
||||
/// </summary>
|
||||
public HashSet<Vector2i>? Removed = removed;
|
||||
|
||||
/// <summary>
|
||||
/// Total count of footprints on the grid.
|
||||
/// </summary>
|
||||
public int TotalFootPrints = totalFootPrints;
|
||||
}
|
||||
|
|
@ -1,11 +1,24 @@
|
|||
using Content.Shared._EE.FootPrint.Systems;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._EE.FootPrint;
|
||||
|
||||
[RegisterComponent]
|
||||
/// <summary>
|
||||
/// Component for puddles that can leave footprints when stepped on.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(PuddleFootPrintsSystem))]
|
||||
public sealed partial class PuddleFootPrintsComponent : Component
|
||||
{
|
||||
[ViewVariables()]
|
||||
/// <summary>
|
||||
/// The ratio of the puddle's volume that determines color intensity transferred to footprints.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public float SizeRatio = 0.2f;
|
||||
|
||||
[ViewVariables()]
|
||||
/// <summary>
|
||||
/// Minimum percentage of water content required before the puddle will transfer footprints.
|
||||
/// Prevents pure water puddles from leaving colored footprints.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public float OffPercent = 80f;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,140 @@
|
|||
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<GridFootPrintsComponent, ComponentGetState>(OnGridFootPrintsGetState);
|
||||
SubscribeLocalEvent<GridFootPrintsComponent, ComponentHandleState>(OnGridFootPrintsHandleState);
|
||||
}
|
||||
|
||||
private void OnGridFootPrintsGetState(Entity<GridFootPrintsComponent> 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<Vector2i, List<NetEntity>>(ent.Comp.FootPrintsByTile.Count);
|
||||
foreach (var (tile, footprints) in ent.Comp.FootPrintsByTile)
|
||||
{
|
||||
// Deep copy the list to avoid reference sharing
|
||||
fullData[tile] = new List<NetEntity>(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<Vector2i, List<NetEntity>>? modifiedTiles = null;
|
||||
HashSet<Vector2i>? removedTiles = null;
|
||||
|
||||
if (ent.Comp.DirtyTiles.Count > 0)
|
||||
{
|
||||
modifiedTiles = new Dictionary<Vector2i, List<NetEntity>>(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<NetEntity>(footprints);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ent.Comp.RemovedTiles.Count > 0)
|
||||
{
|
||||
removedTiles = new HashSet<Vector2i>(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<GridFootPrintsComponent> 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<NetEntity>(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<NetEntity>(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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,330 @@
|
|||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Handles creation of footprints as entities move.
|
||||
/// </summary>
|
||||
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<AppearanceComponent> _appearanceQuery;
|
||||
private EntityQuery<FlightComponent> _flightQuery;
|
||||
private EntityQuery<GridFootPrintsComponent> _gridFootPrintsQuery;
|
||||
private EntityQuery<MobThresholdsComponent> _mobThresholdQuery;
|
||||
private EntityQuery<StandingStateComponent> _standingQuery;
|
||||
private EntityQuery<TransformComponent> _transformQuery;
|
||||
|
||||
private const string HardsuitTag = "Hardsuit";
|
||||
|
||||
private int _maxPerTile;
|
||||
private int _maxPerGrid;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_appearanceQuery = GetEntityQuery<AppearanceComponent>();
|
||||
_flightQuery = GetEntityQuery<FlightComponent>();
|
||||
_gridFootPrintsQuery = GetEntityQuery<GridFootPrintsComponent>();
|
||||
_mobThresholdQuery = GetEntityQuery<MobThresholdsComponent>();
|
||||
_standingQuery = GetEntityQuery<StandingStateComponent>();
|
||||
_transformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
SubscribeLocalEvent<FootPrintsComponent, ComponentStartup>(OnStartupComponent);
|
||||
SubscribeLocalEvent<FootPrintsComponent, MoveEvent>(OnMove);
|
||||
SubscribeLocalEvent<FootPrintComponent, ComponentRemove>(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<FootPrintsComponent> 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<FootPrintsComponent> 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 entity = EntityManager.PredictedSpawnAtPosition(ent.Comp.StepProtoId.Id, coords);
|
||||
|
||||
// Update appearance with current color and state
|
||||
if (_appearanceQuery.TryComp(entity, out var appearance))
|
||||
{
|
||||
_appearance.SetData(entity, FootPrintVisualState.State, PickState(ent, dragging), appearance);
|
||||
_appearance.SetData(entity, FootPrintVisualState.Color, ent.Comp.PrintsColor, appearance);
|
||||
}
|
||||
|
||||
// Set rotation
|
||||
if (_transformQuery.TryComp(entity, out var stepTransform))
|
||||
{
|
||||
stepTransform.LocalRotation = dragging
|
||||
? (transform.LocalPosition - ent.Comp.StepPos).ToAngle() + Angle.FromDegrees(-90f)
|
||||
: transform.LocalRotation + Angle.FromDegrees(180f);
|
||||
}
|
||||
|
||||
if (!TryComp<FootPrintComponent>(entity, out var footPrintComponent))
|
||||
return;
|
||||
|
||||
// Set the owner reference
|
||||
footPrintComponent.PrintOwner = ent;
|
||||
Dirty(entity, footPrintComponent);
|
||||
|
||||
// Track the footprint on the grid
|
||||
TrackFootPrint(gridUid, GetNetEntity(ent), 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((entity, footPrintComponent), ent, reagent);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFootPrintRemoved(Entity<FootPrintComponent> 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<GridFootPrintsComponent>(gridUid);
|
||||
|
||||
// Add to tile tracking
|
||||
if (!gridFootPrints.FootPrintsByTile.TryGetValue(tile, out var tileFootPrints))
|
||||
{
|
||||
tileFootPrints = new List<NetEntity>();
|
||||
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<FootPrintComponent> ent, Entity<FootPrintsComponent> tripper, ProtoId<ReagentPrototype> reagentId)
|
||||
{
|
||||
if (!TryComp<SolutionContainerManagerComponent>(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<FootPrintsComponent> 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Handles transferring puddle colors and reagents to entities with footprints when they step in puddles.
|
||||
/// </summary>
|
||||
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<FlightComponent> _flightQuery;
|
||||
|
||||
private static readonly ProtoId<ReagentPrototype> WaterPrototype = "Water";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_flightQuery = GetEntityQuery<FlightComponent>();
|
||||
|
||||
SubscribeLocalEvent<PuddleFootPrintsComponent, EndCollideEvent>(OnEndCollide);
|
||||
}
|
||||
|
||||
private void OnEndCollide(Entity<PuddleFootPrintsComponent> 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<FootPrintsComponent>(tripper, out var footPrints))
|
||||
return;
|
||||
|
||||
// Get puddle appearance and solution data
|
||||
if (!TryComp<AppearanceComponent>(ent, out var appearance))
|
||||
return;
|
||||
|
||||
if (!TryComp<PuddleComponent>(ent, out var puddleComp))
|
||||
return;
|
||||
|
||||
if (!TryComp<SolutionContainerManagerComponent>(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;
|
||||
}
|
||||
}
|
||||
|
|
@ -215,47 +215,4 @@
|
|||
- type: Tag
|
||||
tags:
|
||||
- DNASolutionScannable
|
||||
- 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: PuddleFootPrints # DeltaV
|
||||
|
|
|
|||
|
|
@ -195,3 +195,48 @@
|
|||
- 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
|
||||
|
|
|
|||
Loading…
Reference in New Issue