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

@@ -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;
}
}