Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Separate crate machine from market system #2681

Merged
merged 15 commits into from
Jan 31, 2025
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
using Content.Shared._NF.Market;
using Content.Shared._NF.CrateMachine;
using Content.Shared._NF.CrateMachine.Components;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Audio.Systems;
using static Content.Shared._NF.Market.Components.CrateMachineComponent;
using CrateMachineComponent = Content.Shared._NF.Market.Components.CrateMachineComponent;

namespace Content.Client._NF.Market.Systems;
namespace Content.Client._NF.CrateMachine;

public sealed class MarketSystem : SharedMarketSystem
public sealed class CrateMachineSystem: SharedCrateMachineSystem
{
Copy link
Contributor

@whatston3 whatston3 Jan 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public sealed class CrateMachineSystem: SharedCrateMachineSystem
public sealed partial class CrateMachineSystem : SharedCrateMachineSystem

per https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/partial-classes-and-methods:
All the parts must use the partial keyword

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strange my IDE is saying it can safely be removed
image

[Dependency] private readonly AppearanceSystem _appearanceSystem = default!;
[Dependency] private readonly AnimationPlayerSystem _animationSystem = default!;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ private void Return(ButtonEventArgs args)

private void PurchaseCrate(ButtonEventArgs args)
{
SendMessage(new CrateMachinePurchaseMessage());
SendMessage(new MarketPurchaseMessage());
Close();
}

Expand Down
15 changes: 15 additions & 0 deletions Content.Server/_NF/CrateMachine/CrateMachineOpenedEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Content.Server._NF.CrateMachine;

/// <summary>
/// Raised whenever a crate machine state changes.
/// Only used on server side.
/// </summary>
public sealed class CrateMachineOpenedEvent : EntityEventArgs
{
public EntityUid EntityUid { get; }

public CrateMachineOpenedEvent(EntityUid uid)
{
EntityUid = uid;
}
}
107 changes: 107 additions & 0 deletions Content.Server/_NF/CrateMachine/CrateMachineSystem.Animation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
using Content.Server.Power.Components;
using Content.Shared._NF.CrateMachine;
using AppearanceSystem = Robust.Server.GameObjects.AppearanceSystem;
using CrateMachineComponent = Content.Shared._NF.CrateMachine.Components.CrateMachineComponent;

namespace Content.Server._NF.CrateMachine;

/// <summary>
/// Handles starting the opening animation.
/// Updates the time remaining on the component.
/// </summary>
public sealed partial class CrateMachineSystem: SharedCrateMachineSystem
whatston3 marked this conversation as resolved.
Show resolved Hide resolved
{
[Dependency] private readonly AppearanceSystem _appearanceSystem = default!;

/// <summary>
/// Keep track of time in this function, in order to process the animation.
/// </summary>
/// <param name="frameTime">The current frame time</param>
public override void Update(float frameTime)
{
base.Update(frameTime);

var query = EntityQueryEnumerator<CrateMachineComponent, ApcPowerReceiverComponent>();
while (query.MoveNext(out var uid, out var crateMachine, out var receiver))
{
if (!receiver.Powered)
continue;

ProcessOpeningAnimation(uid, frameTime, crateMachine);
ProcessClosingAnimation(uid, frameTime, crateMachine);
}
}

/// <summary>
/// Updates the time remaining for the opening animation, calls the delegate when the animation finishes, and updates the visual state.
/// </summary>
/// <param name="uid">The Uid of the crate machine</param>
/// <param name="frameTime">The current frame time</param>
/// <param name="comp">The crate machine component</param>
private void ProcessOpeningAnimation(EntityUid uid, float frameTime, CrateMachineComponent comp)
{
if (comp.OpeningTimeRemaining <= 0)
return;

comp.OpeningTimeRemaining -= frameTime;

// Automatically start closing after it finishes open animation.
if (comp.OpeningTimeRemaining <= 0)
{
comp.DidTakeCrate = false;
RaiseLocalEvent(uid, new CrateMachineOpenedEvent(uid));
}

// Update at the end so the closing animation can start automatically.
UpdateVisualState(uid, comp);
}

/// <summary>
/// Updates the time remaining for the closing animation, calls the delegate when the animation finishes, and updates the visual state.
/// </summary>
/// <param name="uid">The Uid of the crate machine</param>
/// <param name="frameTime">The current frame time</param>
/// <param name="comp">The crate machine component</param>
private void ProcessClosingAnimation(EntityUid uid, float frameTime, CrateMachineComponent comp)
{
if (!comp.DidTakeCrate && !IsOccupied(uid, comp, true))
{
comp.DidTakeCrate = true;
comp.ClosingTimeRemaining = comp.ClosingTime;
}

comp.ClosingTimeRemaining -= frameTime;
UpdateVisualState(uid, comp);
}

/// <summary>
/// Updates the visual state of the crate machine by setting the visual state using the appearance system.
/// </summary>
/// <param name="uid">The Uid of the crate machine</param>
/// <param name="component">The crate machine component</param>
private void UpdateVisualState(EntityUid uid, CrateMachineComponent? component = null)
{
if (!Resolve(uid, ref component))
return;

if (component.OpeningTimeRemaining > 0)
_appearanceSystem.SetData(uid, CrateMachineVisuals.VisualState, CrateMachineVisualState.Opening);
else if (component.ClosingTimeRemaining > 0)
_appearanceSystem.SetData(uid, CrateMachineVisuals.VisualState, CrateMachineVisualState.Closing);
else if (!component.DidTakeCrate)
_appearanceSystem.SetData(uid, CrateMachineVisuals.VisualState, CrateMachineVisualState.Open);
else
_appearanceSystem.SetData(uid, CrateMachineVisuals.VisualState, CrateMachineVisualState.Closed);
}

/// <summary>
/// Starts the opening animation of the crate machine and calls the delegate when the animation finishes.
/// </summary>
/// <param name="crateMachineUid">The Uid of the crate machine</param>
/// <param name="component">The crate machine component</param>
public void OpenFor(EntityUid crateMachineUid, CrateMachineComponent component)
{
component.OpeningTimeRemaining = component.OpeningTime;
UpdateVisualState(crateMachineUid, component);
}
}
121 changes: 121 additions & 0 deletions Content.Server/_NF/CrateMachine/CrateMachineSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Server.Storage.EntitySystems;
using Content.Shared._NF.CrateMachine.Components;
using Content.Shared.Maps;
using Robust.Shared.Map;

namespace Content.Server._NF.CrateMachine;

/// <summary>
/// The crate machine system can be used to make a crate machine open and spawn crates.
/// When calling <see cref="OpenFor"/>, the machine will open the door and give a callback to the given
/// <see cref="ICrateMachineDelegate"/> when it is done opening.
/// </summary>
public sealed partial class CrateMachineSystem
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public sealed partial class CrateMachineSystem
public sealed partial class CrateMachineSystem : SharedCrateMachineSystem

Extend the shared version, add the needed import above?

Copy link
Contributor Author

@GreaseMonk GreaseMonk Jan 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will add it, however it will add another cleanup warning, but i do agree for readability is best to add it
image

{
[Dependency] private readonly IEntityManager _entityManager = default!;
GreaseMonk marked this conversation as resolved.
Show resolved Hide resolved
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly EntityStorageSystem _storage = default!;

/// <summary>
/// Checks if there is a crate on the crate machine.
/// </summary>
/// <param name="crateMachineUid">The Uid of the crate machine</param>
/// <param name="component">The crate machine component</param>
/// <param name="ignoreAnimation">Ignores animation checks</param>
/// <returns>False if not occupied, true if it is.</returns>
public bool IsOccupied(EntityUid crateMachineUid, CrateMachineComponent component, bool ignoreAnimation = false)
{
if (!_entityManager.TryGetComponent<TransformComponent>(crateMachineUid, out var crateMachineTransform))
return true;
var tileRef = crateMachineTransform.Coordinates.GetTileRef(EntityManager, _mapManager);
if (tileRef == null)
return true;

if (!ignoreAnimation && (component.OpeningTimeRemaining > 0 || component.ClosingTimeRemaining > 0f))
return true;

// Finally check if there is a crate intersecting the crate machine.
return _lookup.GetLocalEntitiesIntersecting(tileRef.Value, flags: LookupFlags.All | LookupFlags.Approximate)
.Any(entity => _entityManager.GetComponent<MetaDataComponent>(entity).EntityPrototype?.ID ==
component.CratePrototype);
}

/// <summary>
/// Calculates distance between two EntityCoordinates on the same grid.
/// Used to check for cargo pallets around the console instead of on the grid.
/// </summary>
/// <param name="point1">the first point</param>
/// <param name="point2">the second point</param>
/// <returns></returns>
private static double CalculateDistance(EntityCoordinates point1, EntityCoordinates point2)
{
var xDifference = point2.X - point1.X;
var yDifference = point2.Y - point1.Y;

return Math.Sqrt(xDifference * xDifference + yDifference * yDifference);
}
GreaseMonk marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Find the nearest unoccupied crate machine, that is anchored.
/// </summary>
/// <param name="from">The Uid of the entity to find the nearest crate machine from</param>
/// <param name="maxDistance">The maximum distance to search for a crate machine</param>
/// <param name="machineUid">The Uid of the nearest unoccupied crate machine, or null if none found</param>
public bool FindNearestUnoccupied(EntityUid from, int maxDistance, [NotNullWhen(true)] out EntityUid? machineUid)
{
machineUid = null;
if (maxDistance < 0)
return false;

var crateMachineQuery = AllEntityQuery<CrateMachineComponent, TransformComponent>();
var consoleGridUid = Transform(from).GridUid!.Value;
while (crateMachineQuery.MoveNext(out var crateMachineUid, out var comp, out var compXform))
{
// Skip crate machines that aren't mounted on a grid.
if (Transform(crateMachineUid).GridUid == null)
continue;
// Skip crate machines that are not on the same grid.
if (Transform(crateMachineUid).GridUid!.Value != consoleGridUid)
continue;
var currentDistance = CalculateDistance(compXform.Coordinates, Transform(from).Coordinates);

var isTooFarAway = currentDistance > maxDistance;
GreaseMonk marked this conversation as resolved.
Show resolved Hide resolved
var isBusy = IsOccupied(crateMachineUid, comp);

if (!compXform.Anchored || isTooFarAway || isBusy)
{
continue;
}

machineUid = crateMachineUid;
return true;
}
return false;
}


/// <summary>
/// Convenience function that simply spawns a crate and returns the uid.
/// </summary>
/// <param name="uid">The Uid of the crate machine</param>
/// <param name="component">The crate machine component</param>
/// <returns>The Uid of the spawned crate</returns>
public EntityUid SpawnCrate(EntityUid uid, CrateMachineComponent component)
{
return Spawn(component.CratePrototype, Transform(uid).Coordinates);
}

/// <summary>
/// Convenience function that simply inserts a entity into the container entity and relieve the caller of
/// using the storage system reference.
/// </summary>
/// <param name="uid">The Uid of the crate machine</param>
/// <param name="container">The Uid of the container</param>
public void InsertIntoCrate(EntityUid uid, EntityUid container)
{
_storage.Insert(uid, container);
}
}
Loading
Loading