fish-station/Content.Server/_Sunrise/Footprints/FootPrintsSystem.cs
2024-11-15 17:19:37 +03:00

255 lines
9.4 KiB
C#

using Content.Server.Atmos.Components;
using Content.Shared._Sunrise.Footprints;
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 Robust.Shared.Map;
using Robust.Shared.Random;
using System.Linq;
using Content.Shared.GameTicking;
namespace Content.Server._Sunrise.Footprints;
/// <summary>
/// Handles creation and management of footprints left by entities as they move.
/// </summary>
public sealed class FootprintSystem : EntitySystem
{
#region Dependencies
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
#endregion
#region Entity Queries
private EntityQuery<TransformComponent> _transformQuery;
private EntityQuery<MobThresholdsComponent> _mobStateQuery;
private EntityQuery<AppearanceComponent> _appearanceQuery;
#endregion
// Dictionary to track footprints per tile to prevent overcrowding
private readonly Dictionary<(EntityUid GridId, Vector2i TilePosition), HashSet<EntityUid>> _tileFootprints = new();
private const int MaxFootprintsPerTile = 2; // Maximum footprints allowed per tile
#region Initialization
/// <summary>
/// Initializes the footprint system and sets up required queries and subscriptions.
/// </summary>
public override void Initialize()
{
base.Initialize();
_transformQuery = GetEntityQuery<TransformComponent>();
_mobStateQuery = GetEntityQuery<MobThresholdsComponent>();
_appearanceQuery = GetEntityQuery<AppearanceComponent>();
SubscribeLocalEvent<FootprintEmitterComponent, ComponentStartup>(OnEmitterStartup);
SubscribeLocalEvent<FootprintEmitterComponent, MoveEvent>(OnEntityMove);
SubscribeNetworkEvent<RoundRestartCleanupEvent>(Reset);
}
/// <summary>
/// Handles initialization of footprint emitter components.
/// </summary>
private void OnEmitterStartup(EntityUid uid, FootprintEmitterComponent component, ComponentStartup args)
{
// Add small random variation to step interval
component.WalkStepInterval = Math.Max(0f, component.WalkStepInterval + _random.NextFloat(-0.05f, 0.05f));
}
#endregion
#region Event Handlers
/// <summary>
/// Handles entity movement and creates footprints when appropriate.
/// </summary>
private void OnEntityMove(EntityUid uid, FootprintEmitterComponent emitter, ref MoveEvent args)
{
// Check if footprints should be created
if (emitter.TrackColor.A <= 0f
|| !_transformQuery.TryComp(uid, out var transform)
|| !_mobStateQuery.TryComp(uid, out var mobState)
|| !_mapManager.TryFindGridAt(_transformSystem.GetMapCoordinates((uid, transform)), out var gridUid, out var grid))
return;
var isBeingDragged =
mobState.CurrentThresholdState is MobState.Critical or MobState.Dead ||
(TryComp<StandingStateComponent>(uid, out var stateComponent) &&
stateComponent.CurrentState is StandingState.Lying);
var distanceMoved = (transform.LocalPosition - emitter.LastStepPosition).Length();
var requiredDistance = isBeingDragged ? emitter.DragMarkInterval : emitter.WalkStepInterval;
if (!(distanceMoved > requiredDistance))
return;
var tilePos = grid.TileIndicesFor(transform.Coordinates);
var tileKey = (gridUid, tilePos);
if (_tileFootprints.TryGetValue(tileKey, out var existingPrints) &&
existingPrints.Count >= MaxFootprintsPerTile)
{
if (existingPrints.Count > 0)
{
var oldestPrint = existingPrints.First();
existingPrints.Remove(oldestPrint);
QueueDel(oldestPrint);
}
}
emitter.IsRightStep = !emitter.IsRightStep;
// Create new footprint entity
var footprintEntity = SpawnFootprint(gridUid, emitter, uid, transform, isBeingDragged);
// Add the new footprint to tile tracking
if (!_tileFootprints.ContainsKey(tileKey))
_tileFootprints[tileKey] = new HashSet<EntityUid>();
_tileFootprints[tileKey].Add(footprintEntity);
// Update footprint and emitter state
UpdateFootprint(footprintEntity, emitter, transform, isBeingDragged);
UpdateEmitterState(emitter, transform);
}
private void Reset(RoundRestartCleanupEvent msg)
{
_tileFootprints.Clear();
}
#endregion
#region Footprint Creation and Management
/// <summary>
/// Creates a new footprint entity at the calculated position.
/// </summary>
private EntityUid SpawnFootprint(
EntityUid gridUid,
FootprintEmitterComponent emitter,
EntityUid emitterOwner,
TransformComponent transform,
bool isDragging)
{
var coords = CalculateFootprintPosition(gridUid, emitter, transform, isDragging);
var entity = Spawn(emitter.FootprintPrototype, coords);
var footprint = EnsureComp<FootprintComponent>(entity);
footprint.CreatorEntity = emitterOwner;
Dirty(entity, footprint);
if (_appearanceQuery.TryComp(entity, out var appearance))
{
_appearanceSystem.SetData(entity,
FootprintVisualParameter.VisualState,
DetermineVisualState(emitterOwner, isDragging),
appearance);
_appearanceSystem.SetData(entity,
FootprintVisualParameter.TrackColor,
emitter.TrackColor,
appearance);
}
return entity;
}
/// <summary>
/// Updates footprint rotation and reagent transfer.
/// </summary>
private void UpdateFootprint(
EntityUid footprintEntity,
FootprintEmitterComponent emitter,
TransformComponent transform,
bool isDragging)
{
if (!_transformQuery.TryComp(footprintEntity, out var footprintTransform))
return;
footprintTransform.LocalRotation = isDragging
? (transform.LocalPosition - emitter.LastStepPosition).ToAngle() + Angle.FromDegrees(-90f)
: transform.LocalRotation + Angle.FromDegrees(180f);
TransferReagents(footprintEntity, emitter);
}
#endregion
#region State Management
/// <summary>
/// Updates emitter state after creating a footprint.
/// </summary>
private void UpdateEmitterState(FootprintEmitterComponent emitter, TransformComponent transform)
{
emitter.TrackColor = emitter.TrackColor.WithAlpha(Math.Max(0f, emitter.TrackColor.A - emitter.ColorFadeRate));
emitter.LastStepPosition = transform.LocalPosition;
}
/// <summary>
/// Transfers reagents from emitter to footprint if applicable.
/// </summary>
private void TransferReagents(EntityUid footprintEntity, FootprintEmitterComponent emitter)
{
if (!TryComp<SolutionContainerManagerComponent>(footprintEntity, out var container)
|| !TryComp<FootprintComponent>(footprintEntity, out var footprint)
|| !_solutionSystem.ResolveSolution((footprintEntity, container),
footprint.ContainerName,
ref footprint.SolutionContainer,
out var solution)
|| string.IsNullOrWhiteSpace(emitter.CurrentReagent)
|| solution.Volume >= 1)
return;
_solutionSystem.TryAddReagent(footprint.SolutionContainer.Value,
emitter.CurrentReagent,
1,
out _);
}
#endregion
#region Utility Methods
/// <summary>
/// Calculates the position where a footprint should be placed.
/// </summary>
private EntityCoordinates CalculateFootprintPosition(
EntityUid uid,
FootprintEmitterComponent emitter,
TransformComponent transform,
bool isDragging)
{
if (isDragging)
return new EntityCoordinates(uid, transform.LocalPosition);
var offset = emitter.IsRightStep
? new Angle(Angle.FromDegrees(180f) + transform.LocalRotation)
.RotateVec(emitter.PlacementOffset)
: new Angle(transform.LocalRotation).RotateVec(emitter.PlacementOffset);
return new EntityCoordinates(uid, transform.LocalPosition + offset);
}
/// <summary>
/// Determines the visual state for a footprint based on entity equipment.
/// </summary>
private FootprintVisualType DetermineVisualState(EntityUid uid, bool isDragging)
{
if (isDragging)
return FootprintVisualType.DragMark;
var state = FootprintVisualType.BareFootprint;
if (_inventory.TryGetSlotEntity(uid, "shoes", out _))
state = FootprintVisualType.ShoeFootprint;
if (_inventory.TryGetSlotEntity(uid, "outerClothing", out var suit)
&& TryComp<PressureProtectionComponent>(suit, out _))
state = FootprintVisualType.SuitFootprint;
return state;
}
#endregion
}