Skip to content

Commit

Permalink
core
Browse files Browse the repository at this point in the history
  • Loading branch information
rhailrake committed Nov 4, 2024
1 parent 9e38ce3 commit 70fa1a8
Show file tree
Hide file tree
Showing 36 changed files with 778 additions and 1 deletion.
148 changes: 148 additions & 0 deletions Content.Client/_Sunrise/Footprints/FootPrintsVisualizerSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
using Content.Shared._Sunrise.Footprints;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Random;

namespace Content.Client._Sunrise.Footprints;

/// <summary>
/// Handles the visual appearance and updates of footprint entities on the client
/// </summary>
public sealed class FootprintVisualizerSystem : VisualizerSystem<FootprintComponent>
{
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;

/// <inheritdoc/>
public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<FootprintComponent, ComponentInit>(OnFootprintInitialized);
SubscribeLocalEvent<FootprintComponent, ComponentShutdown>(OnFootprintShutdown);
}

/// <summary>
/// Initializes the visual appearance of a new footprint
/// </summary>
private void OnFootprintInitialized(EntityUid uid, FootprintComponent component, ComponentInit args)
{
if (!TryComp<SpriteComponent>(uid, out var sprite))
return;

InitializeSpriteLayers(sprite);
UpdateFootprintVisuals(uid, component, sprite);
}

/// <summary>
/// Cleans up the visual elements when a footprint is removed
/// </summary>
private void OnFootprintShutdown(EntityUid uid, FootprintComponent component, ComponentShutdown args)
{
if (!TryComp<SpriteComponent>(uid, out var sprite))
return;

RemoveSpriteLayers(sprite);
}

/// <summary>
/// Sets up the initial sprite layers for the footprint
/// </summary>
private void InitializeSpriteLayers(SpriteComponent sprite)
{
sprite.LayerMapReserveBlank(FootprintSpriteLayer.MainLayer);
}

/// <summary>
/// Removes sprite layers when cleaning up footprint
/// </summary>
private void RemoveSpriteLayers(SpriteComponent sprite)
{
if (sprite.LayerMapTryGet(FootprintSpriteLayer.MainLayer, out var layer))
{
sprite.RemoveLayer(layer);
}
}

/// <summary>
/// Updates the visual appearance of a footprint based on its current state
/// </summary>
private void UpdateFootprintVisuals(EntityUid uid, FootprintComponent footprint, SpriteComponent sprite)
{
if (!sprite.LayerMapTryGet(FootprintSpriteLayer.MainLayer, out var layer)
|| !TryComp<FootprintEmitterComponent>(footprint.CreatorEntity, out var emitterComponent)
|| !TryComp<AppearanceComponent>(uid, out var appearance))
return;

if (!_appearanceSystem.TryGetData<FootprintVisualType>(
uid,
FootprintVisualParameter.VisualState,
out var visualType,
appearance))
return;

UpdateSpriteState(sprite, layer, visualType, emitterComponent);
UpdateSpriteColor(sprite, layer, uid, appearance);
}

/// <summary>
/// Updates the sprite state based on the footprint type
/// </summary>
private void UpdateSpriteState(
SpriteComponent sprite,
int layer,
FootprintVisualType visualType,
FootprintEmitterComponent emitter)
{
var stateId = new RSI.StateId(GetStateId(visualType, emitter));
sprite.LayerSetState(layer, stateId, emitter.SpritePath);
}

/// <summary>
/// Determines the appropriate state ID for the footprint based on its type
/// </summary>
private string GetStateId(FootprintVisualType visualType, FootprintEmitterComponent emitter)
{
return visualType switch
{
FootprintVisualType.BareFootprint => emitter.IsRightStep
? emitter.RightBareFootState
: emitter.LeftBareFootState,
FootprintVisualType.ShoeFootprint => emitter.ShoeFootState,
FootprintVisualType.SuitFootprint => emitter.PressureSuitFootState,
FootprintVisualType.DragMark => _random.Pick(emitter.DraggingStates),
_ => throw new ArgumentOutOfRangeException(
$"Unknown footprint visual type: {visualType}")
};
}

/// <summary>
/// Updates the sprite color based on appearance data
/// </summary>
private void UpdateSpriteColor(
SpriteComponent sprite,
int layer,
EntityUid uid,
AppearanceComponent appearance)
{
if (_appearanceSystem.TryGetData<Color>(uid,
FootprintVisualParameter.TrackColor,
out var color,
appearance))
{
sprite.LayerSetColor(layer, color);
}
}

/// <inheritdoc/>
protected override void OnAppearanceChange(
EntityUid uid,
FootprintComponent component,
ref AppearanceChangeEvent args)
{
if (args.Sprite is not { } sprite)
return;

UpdateFootprintVisuals(uid, component, sprite);
}
}
224 changes: 224 additions & 0 deletions Content.Server/_Sunrise/Footprints/FootPrintsSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
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;

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

#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);
}

/// <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 _))
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;

emitter.IsRightStep = !emitter.IsRightStep;

// Create new footprint entity.
var footprintEntity = SpawnFootprint(gridUid, emitter, uid, transform, isBeingDragged);

// Update footprint position and transfer reagents if applicable.
UpdateFootprint(footprintEntity, emitter, transform, isBeingDragged);

// Update emitter state.
UpdateEmitterState(emitter, transform);
}
#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
}
Loading

0 comments on commit 70fa1a8

Please sign in to comment.