Refactor stacks to not use method events (#4177)

This commit is contained in:
Vera Aguilera Puerto
2021-06-12 11:24:34 +02:00
committed by GitHub
parent ca4e665296
commit 0093a961bc
17 changed files with 79 additions and 248 deletions

View File

@@ -16,14 +16,14 @@ namespace Content.Server.Construction.Completions
[DataDefinition]
public class SetStackCount : IGraphAction
{
[DataField("amount")] public int Amount { get; private set; } = 1;
[DataField("amount")] public int Amount { get; } = 1;
public async Task PerformAction(IEntity entity, IEntity? user)
{
if (entity.Deleted) return;
if(!entity.HasComponent<StackComponent>()) return;
if(!entity.TryGetComponent<StackComponent>(out var stack)) return;
entity.EntityManager.EventBus.RaiseLocalEvent(entity.Uid, new StackChangeCountEvent(Amount), false);
EntitySystem.Get<StackSystem>().SetCount(entity.Uid, stack, Amount);
}
}
}

View File

@@ -29,8 +29,9 @@ namespace Content.Server.Construction.Completions
if (EntityPrototypeHelpers.HasComponent<StackComponent>(Prototype))
{
var stack = entityManager.SpawnEntity(Prototype, coordinates);
stack.EntityManager.EventBus.RaiseLocalEvent(stack.Uid, new StackChangeCountEvent(Amount), false);
var stackEnt = entityManager.SpawnEntity(Prototype, coordinates);
var stack = stackEnt.GetComponent<StackComponent>();
EntitySystem.Get<StackSystem>().SetCount(stackEnt.Uid, stack, Amount);
}
else
{

View File

@@ -269,12 +269,11 @@ namespace Content.Server.Construction.Components
if (materialStep.EntityValid(eventArgs.Using, out var stack)
&& await doAfterSystem.DoAfter(doAfterArgs) == DoAfterStatus.Finished)
{
var splitStack = new StackSplitEvent() {Amount = materialStep.Amount, SpawnPosition = eventArgs.User.Transform.Coordinates};
Owner.EntityManager.EventBus.RaiseLocalEvent(stack.Owner.Uid, splitStack);
var splitStack = EntitySystem.Get<StackSystem>().Split(eventArgs.Using.Uid, stack, materialStep.Amount, eventArgs.User.Transform.Coordinates);
if (splitStack.Result != null)
if (splitStack != null)
{
entityUsing = splitStack.Result;
entityUsing = splitStack;
valid = true;
}
}

View File

@@ -86,16 +86,12 @@ namespace Content.Server.Construction.Components
foreach (var (stackType, amount) in machineBoard.MaterialRequirements)
{
var stackSpawn = new StackTypeSpawnEvent()
{Amount = amount, StackType = stackType, SpawnPosition = Owner.Transform.Coordinates};
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, stackSpawn);
var stack = EntitySystem.Get<StackSystem>().Spawn(amount, stackType, Owner.Transform.Coordinates);
var s = stackSpawn.Result;
if (s == null)
if (stack == null)
throw new Exception($"Couldn't spawn stack of type {stackType}!");
if (!partContainer.Insert(s))
if (!partContainer.Insert(stack))
throw new Exception($"Couldn't insert machine material of type {stackType} to machine with prototype {Owner.Prototype?.ID ?? "N/A"}");
}

View File

@@ -313,14 +313,12 @@ namespace Content.Server.Construction.Components
return true;
}
var splitStack = new StackSplitEvent()
{Amount = needed, SpawnPosition = Owner.Transform.Coordinates};
Owner.EntityManager.EventBus.RaiseLocalEvent(stack.Owner.Uid, splitStack);
var splitStack = EntitySystem.Get<StackSystem>().Split(eventArgs.Using.Uid, stack, needed, Owner.Transform.Coordinates);
if (splitStack.Result == null)
if (splitStack == null)
return false;
if(!_partContainer.Insert(splitStack.Result))
if(!_partContainer.Insert(splitStack))
return false;
_materialProgress[type] += needed;

View File

@@ -52,15 +52,15 @@ namespace Content.Server.Construction.Components
var resultPosition = Owner.Transform.Coordinates;
Owner.Delete();
// spawn each result afrer refine
// spawn each result after refine
foreach (var result in _refineResult!)
{
var droppedEnt = Owner.EntityManager.SpawnEntity(result, resultPosition);
// TODO: If something has a stack... Just use a prototype with a single thing in the stack.
// This is not a good way to do it.
if (droppedEnt.HasComponent<StackComponent>())
Owner.EntityManager.EventBus.RaiseLocalEvent(droppedEnt.Uid, new StackChangeCountEvent(1), false);
if (droppedEnt.TryGetComponent<StackComponent>(out var stack))
EntitySystem.Get<StackSystem>().SetCount(droppedEnt.Uid, stack, 1);
}
return true;

View File

@@ -170,19 +170,17 @@ namespace Content.Server.Construction
if (!materialStep.EntityValid(entity, out var stack))
continue;
var splitStack = new StackSplitEvent()
{Amount = materialStep.Amount, SpawnPosition = user.ToCoordinates()};
RaiseLocalEvent(entity.Uid, splitStack);
var splitStack = Get<StackSystem>().Split(entity.Uid, stack, materialStep.Amount, user.ToCoordinates());
if (splitStack.Result == null)
if (splitStack == null)
continue;
if (string.IsNullOrEmpty(materialStep.Store))
{
if (!container.Insert(splitStack.Result))
if (!container.Insert(splitStack))
continue;
}
else if (!GetContainer(materialStep.Store).Insert(splitStack.Result))
else if (!GetContainer(materialStep.Store).Insert(splitStack))
continue;
handled = true;

View File

@@ -33,7 +33,8 @@ namespace Content.Server.Destructible.Thresholds.Behaviors
if (EntityPrototypeHelpers.HasComponent<StackComponent>(entityId))
{
var spawned = owner.EntityManager.SpawnEntity(entityId, owner.Transform.MapPosition);
owner.EntityManager.EventBus.RaiseLocalEvent(spawned.Uid, new StackChangeCountEvent(count), false);
var stack = spawned.GetComponent<StackComponent>();
EntitySystem.Get<StackSystem>().SetCount(spawned.Uid, stack, count);
spawned.RandomOffset(0.5f);
}
else

View File

@@ -5,6 +5,7 @@ using Content.Server.Engineering.Components;
using Content.Server.Stack;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Helpers;
using Content.Shared.Stacks;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
@@ -65,20 +66,15 @@ namespace Content.Server.Engineering.EntitySystems
if (component.Deleted || component.Owner.Deleted)
return;
var hasStack = component.Owner.HasComponent<StackComponent>();
if (hasStack && component.RemoveOnInteract)
if (component.Owner.TryGetComponent<SharedStackComponent>(out var stackComp)
&& component.RemoveOnInteract && !Get<StackSystem>().Use(uid, stackComp, 1))
{
var stackUse = new StackUseEvent() {Amount = 1};
RaiseLocalEvent(component.Owner.Uid, stackUse);
if (!stackUse.Result)
return;
}
EntityManager.SpawnEntity(component.Prototype, args.ClickLocation.SnapToGrid(grid));
if (component.RemoveOnInteract && !hasStack && !component.Owner.Deleted)
if (component.RemoveOnInteract && stackComp == null && !component.Owner.Deleted)
component.Owner.Delete();
}
}

View File

@@ -188,13 +188,12 @@ namespace Content.Server.Hands
}
else
{
var splitStack = new StackSplitEvent() { Amount = 1, SpawnPosition = playerEnt.Transform.Coordinates };
RaiseLocalEvent(throwEnt.Uid, splitStack);
var splitStack = Get<StackSystem>().Split(throwEnt.Uid, stackComp, 1, playerEnt.Transform.Coordinates);
if (splitStack.Result == null)
if (splitStack == null)
return false;
throwEnt = splitStack.Result;
throwEnt = splitStack;
}
var direction = coords.ToMapPos(EntityManager) - playerEnt.Transform.WorldPosition;

View File

@@ -6,6 +6,7 @@ using Content.Shared.Damage;
using Content.Shared.Damage.Components;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Helpers;
using Content.Shared.Stacks;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
@@ -41,12 +42,8 @@ namespace Content.Server.Medical.Components
return true;
}
if (Owner.HasComponent<StackComponent>())
if (Owner.TryGetComponent<SharedStackComponent>(out var stack) && !EntitySystem.Get<StackSystem>().Use(Owner.Uid, stack, 1))
{
var stackUse = new StackUseEvent() {Amount = 1};
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, stackUse);
if(!stackUse.Result)
return true;
}

View File

@@ -25,77 +25,63 @@ namespace Content.Server.Stack
base.Initialize();
SubscribeLocalEvent<StackComponent, InteractUsingEvent>(OnStackInteractUsing);
// The following subscriptions are basically the "method calls" of this entity system.
SubscribeLocalEvent<StackComponent, StackUseEvent>(OnStackUse);
SubscribeLocalEvent<StackComponent, StackSplitEvent>(OnStackSplit);
SubscribeLocalEvent<StackTypeSpawnEvent>(OnStackTypeSpawn);
}
/// <summary>
/// Try to use an amount of items on this stack.
/// See <see cref="StackUseEvent"/>
/// Try to use an amount of items on this stack. Returns whether this succeeded.
/// </summary>
private void OnStackUse(EntityUid uid, StackComponent stack, StackUseEvent args)
public bool Use(EntityUid uid, SharedStackComponent stack, int amount)
{
// Check if we have enough things in the stack for this...
if (stack.Count < args.Amount)
if (stack.Count <= amount)
{
// Not enough things in the stack, so we set the output result to false.
args.Result = false;
}
else
{
// We do have enough things in the stack, so remove them and set the output result to true.
RaiseLocalEvent(uid, new StackChangeCountEvent(stack.Count - args.Amount), false);
args.Result = true;
// Not enough things in the stack, return false.
return false;
}
// We do have enough things in the stack, so remove them and change.
SetCount(uid, stack, stack.Count - amount);
return true;
}
/// <summary>
/// Try to split this stack into two.
/// See <see cref="StackSplitEvent"/>
/// Try to split this stack into two. Returns a non-null <see cref="IEntity"/> if successful.
/// </summary>
private void OnStackSplit(EntityUid uid, StackComponent stack, StackSplitEvent args)
public IEntity? Split(EntityUid uid, SharedStackComponent stack, int amount, EntityCoordinates spawnPosition)
{
// If the stack doesn't have enough things as specified in the parameters, we do nothing.
if (stack.Count < args.Amount)
return;
// Get a prototype ID to spawn the new entity. Null is also valid, although it should rarely be picked...
var prototype = _prototypeManager.TryIndex<StackPrototype>(stack.StackTypeId, out var stackType)
? stackType.Spawn
: stack.Owner.Prototype?.ID ?? null;
// Remove the amount of things we want to split from the original stack...
RaiseLocalEvent(uid, new StackChangeCountEvent(stack.Count - args.Amount), false);
// Try to remove the amount of things we want to split from the original stack...
if (!Use(uid, stack, amount))
return null;
// Set the output parameter in the event instance to the newly split stack.
args.Result = EntityManager.SpawnEntity(prototype, args.SpawnPosition);
var entity = EntityManager.SpawnEntity(prototype, spawnPosition);
if (args.Result.TryGetComponent(out StackComponent? stackComp))
if (ComponentManager.TryGetComponent(entity.Uid, out SharedStackComponent? stackComp))
{
// Set the split stack's count.
RaiseLocalEvent(args.Result.Uid, new StackChangeCountEvent(args.Amount), false);
SetCount(entity.Uid, stackComp, amount);
}
return entity;
}
/// <summary>
/// Tries to spawn a stack of a certain type.
/// See <see cref="StackTypeSpawnEvent"/>
/// Spawns a stack of a certain stack type. See <see cref="StackPrototype"/>.
/// </summary>
private void OnStackTypeSpawn(StackTypeSpawnEvent args)
public IEntity Spawn(int amount, StackPrototype prototype, EntityCoordinates spawnPosition)
{
// Can't spawn a stack for an invalid type.
if (args.StackType == null)
return;
// Set the output result parameter to the new stack entity...
args.Result = EntityManager.SpawnEntity(args.StackType.Spawn, args.SpawnPosition);
var stack = args.Result.GetComponent<StackComponent>();
var entity = EntityManager.SpawnEntity(prototype.Spawn, spawnPosition);
var stack = ComponentManager.GetComponent<StackComponent>(entity.Uid);
// And finally, set the correct amount!
RaiseLocalEvent(args.Result.Uid, new StackChangeCountEvent(args.Amount), false);
SetCount(entity.Uid, stack, amount);
return entity;
}
private void OnStackInteractUsing(EntityUid uid, StackComponent stack, InteractUsingEvent args)
@@ -107,8 +93,8 @@ namespace Content.Server.Stack
return;
var toTransfer = Math.Min(stack.Count, otherStack.AvailableSpace);
RaiseLocalEvent(uid, new StackChangeCountEvent(stack.Count - toTransfer), false);
RaiseLocalEvent(args.Used.Uid, new StackChangeCountEvent(otherStack.Count + toTransfer), false);
SetCount(uid, stack, stack.Count - toTransfer);
SetCount(args.Used.Uid, otherStack, otherStack.Count + toTransfer);
var popupPos = args.ClickLocation;
if (!popupPos.IsValid(EntityManager))
@@ -145,110 +131,4 @@ namespace Content.Server.Stack
args.Handled = true;
}
}
/*
* The following events are actually funny ECS method calls!
*
* Instead of coupling systems together into a ball of spaghetti,
* we raise events that act as method calls.
*
* So for example, instead of having an Use() method in the
* stack component or stack system, we have a StackUseEvent.
* Before raising the event, you would set the Amount property,
* which acts as a parameter or argument, and afterwards the
* entity system in charge of handling this would perform the logic
* and then set the Result on the event instance.
* Then you can access this property to see whether your Use attempt succeeded.
*
* This is very powerful, as it completely removes the coupling
* between entity systems and allows for greater flexibility.
* If you want to intercept this event with another entity system, you can.
* And you don't have to write any bad, hacky code for this!
* You could even use handled events, or cancellable events...
* The possibilities are endless.
*
* Of course, not everything needs to be directed events!
* Broadcast events also work in the same way.
* For example, we use a broadcast event to spawn a stack of a certain type.
*
* Wrapping your head around this may be difficult at first,
* but soon you'll get it, coder. Soon you'll grasp the wisdom.
* Go forth and write some beautiful and robust code!
*/
/// <summary>
/// Uses an amount of things from a stack.
/// Whether this succeeded is stored in <see cref="Result"/>.
/// </summary>
public class StackUseEvent : EntityEventArgs
{
/// <summary>
/// The amount of things to use on the stack.
/// Consider this the equivalent of a parameter for a method call.
/// </summary>
public int Amount { get; init; }
/// <summary>
/// Whether the action succeeded or not.
/// Set by the <see cref="StackSystem"/> after handling this event.
/// Consider this the equivalent of a return value for a method call.
/// </summary>
public bool Result { get; set; } = false;
}
/// <summary>
/// Tries to split a stack into two.
/// If this succeeds, <see cref="Result"/> will be the new stack.
/// </summary>
public class StackSplitEvent : EntityEventArgs
{
/// <summary>
/// The amount of things to take from the original stack.
/// Input parameter.
/// </summary>
public int Amount { get; init; }
/// <summary>
/// The position where to spawn the new stack.
/// Input parameter.
/// </summary>
public EntityCoordinates SpawnPosition { get; init; }
/// <summary>
/// The newly split stack. May be null if the split failed.
/// Output parameter.
/// </summary>
public IEntity? Result { get; set; } = null;
}
/// <summary>
/// Tries to spawn a stack of a certain type.
/// If this succeeds, <see cref="Result"/> will be the new stack.
/// </summary>
public class StackTypeSpawnEvent : EntityEventArgs
{
/// <summary>
/// The amount of things the spawned stack will have.
/// Input parameter.
/// </summary>
public int Amount { get; init; }
/// <summary>
/// The stack type to be spawned.
/// Input parameter.
/// </summary>
public StackPrototype? StackType { get; init; }
/// <summary>
/// The position where the new stack will be spawned.
/// Input parameter.
/// </summary>
public EntityCoordinates SpawnPosition { get; init; }
/// <summary>
/// The newly spawned stack, or null if this failed.
/// Output parameter.
/// </summary>
public IEntity? Result { get; set; } = null;
}
}

View File

@@ -80,10 +80,7 @@ namespace Content.Server.Tiles
if (HasBaseTurf(currentTileDefinition, baseTurf.Name))
{
var stackUse = new StackUseEvent() {Amount = 1};
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, stackUse);
if (!stackUse.Result)
if (!EntitySystem.Get<StackSystem>().Use(Owner.Uid, stack, 1))
continue;
PlaceAt(mapGrid, location, currentTileDefinition.TileId);

View File

@@ -44,8 +44,8 @@ namespace Content.Server.Wires.Components
var droppedEnt = Owner.EntityManager.SpawnEntity(_wireDroppedOnCutPrototype, eventArgs.ClickLocation);
// TODO: Literally just use a prototype that has a single thing in the stack, it's not that complicated...
if (droppedEnt.HasComponent<StackComponent>())
Owner.EntityManager.EventBus.RaiseLocalEvent(droppedEnt.Uid, new StackChangeCountEvent(1), false);
if (droppedEnt.TryGetComponent<StackComponent>(out var stack))
EntitySystem.Get<StackSystem>().SetCount(droppedEnt.Uid, stack, 1);
return true;
}

View File

@@ -47,14 +47,9 @@ namespace Content.Server.Wires.Components
}
}
if (Owner.HasComponent<StackComponent>())
{
var stackUse = new StackUseEvent(){Amount = 1};
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, stackUse);
if(!stackUse.Result)
if (Owner.TryGetComponent<StackComponent>(out var stack)
&& !EntitySystem.Get<StackSystem>().Use(Owner.Uid, stack, 1))
return true;
}
Owner.EntityManager.SpawnEntity(_wirePrototypeID, grid.GridTileToLocal(snapPos));
return true;

View File

@@ -57,7 +57,7 @@ namespace Content.Shared.Stacks
return;
// This will change the count and call events.
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new StackChangeCountEvent(cast.Count));
EntitySystem.Get<SharedStackSystem>().SetCount(Owner.Uid, this, cast.Count);
MaxCount = cast.MaxCount;
}

View File

@@ -12,7 +12,6 @@ namespace Content.Shared.Stacks
base.Initialize();
SubscribeLocalEvent<SharedStackComponent, ComponentStartup>(OnStackStarted);
SubscribeLocalEvent<SharedStackComponent, StackChangeCountEvent>(OnStackCountChange);
SubscribeLocalEvent<SharedStackComponent, ExaminedEvent>(OnStackExamined);
}
@@ -26,26 +25,27 @@ namespace Content.Shared.Stacks
appearance.SetData(StackVisuals.Hide, false);
}
protected void OnStackCountChange(EntityUid uid, SharedStackComponent component, StackChangeCountEvent args)
public void SetCount(EntityUid uid, SharedStackComponent component, int amount)
{
if (args.Amount == component.Count)
// Do nothing if amount is already the same.
if (amount == component.Count)
return;
// Store old value for event-raising purposes...
var old = component.Count;
if (args.Amount > component.MaxCount)
// Clamp the value.
if (amount > component.MaxCount)
{
args.Amount = component.MaxCount;
args.Clamped = true;
amount = component.MaxCount;
}
if (args.Amount < 0)
if (amount < 0)
{
args.Amount = 0;
args.Clamped = true;
amount = 0;
}
component.Count = args.Amount;
component.Count = amount;
component.Dirty();
// Queue delete stack if count reaches zero.
@@ -74,32 +74,6 @@ namespace Content.Shared.Stacks
}
}
/// <summary>
/// Attempts to change the amount of things in a stack to a specific number.
/// If the amount had to be clamped to zero or the max amount, <see cref="Clamped"/> will be true
/// and the amount will be changed to match the value set.
/// Does nothing if the amount is the same as the stack count already.
/// </summary>
public class StackChangeCountEvent : EntityEventArgs
{
/// <summary>
/// Amount to set the stack to.
/// Input/Output parameter.
/// </summary>
public int Amount { get; set; }
/// <summary>
/// Whether the <see cref="Amount"/> had to be clamped.
/// Output parameter.
/// </summary>
public bool Clamped { get; set; }
public StackChangeCountEvent(int amount)
{
Amount = amount;
}
}
/// <summary>
/// Event raised when a stack's count has changed.
/// </summary>