-
Notifications
You must be signed in to change notification settings - Fork 93
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
36 changed files
with
778 additions
and
1 deletion.
There are no files selected for viewing
148 changes: 148 additions & 0 deletions
148
Content.Client/_Sunrise/Footprints/FootPrintsVisualizerSystem.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.