Separate destructible component threshold into behaviors (#2818)

* WIP changes, add behaviors

* Fix behavior typing, namespace and test

* NO SPACES
This commit is contained in:
DrSmugleaf
2020-12-23 13:34:57 +01:00
committed by GitHub
parent ce029f461e
commit 764465f60c
57 changed files with 819 additions and 471 deletions

View File

@@ -1,4 +0,0 @@
namespace Content.Server.GameObjects.Components.Destructible
{
public sealed class ActsFlags { }
}

View File

@@ -1,12 +1,11 @@
#nullable enable
using System.Collections.Generic;
using Content.Server.GameObjects.Components.Destructible.Thresholds;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Components.Damage;
using Content.Shared.GameObjects.EntitySystems;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.IoC;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
@@ -19,9 +18,7 @@ namespace Content.Server.GameObjects.Components.Destructible
[RegisterComponent]
public class DestructibleComponent : Component
{
[Dependency] private readonly IRobustRandom _random = default!;
private ActSystem _actSystem = default!;
private DestructibleSystem _destructibleSystem = default!;
public override string Name => "Destructible";
@@ -47,7 +44,7 @@ namespace Content.Server.GameObjects.Components.Destructible
{
base.Initialize();
_actSystem = EntitySystem.Get<ActSystem>();
_destructibleSystem = EntitySystem.Get<DestructibleSystem>();
}
public override void HandleMessage(ComponentMessage message, IComponent? component)
@@ -83,7 +80,7 @@ namespace Content.Server.GameObjects.Components.Destructible
var thresholdMessage = new DestructibleThresholdReachedMessage(this, threshold, msg.Damageable.TotalDamage, damage);
SendMessage(thresholdMessage);
threshold.Trigger(Owner, _random, _actSystem);
threshold.Trigger(Owner, _destructibleSystem);
}
}

View File

@@ -1,4 +1,5 @@
using Robust.Shared.GameObjects;
using Content.Server.GameObjects.Components.Destructible.Thresholds;
using Robust.Shared.GameObjects;
namespace Content.Server.GameObjects.Components.Destructible
{

View File

@@ -1,148 +0,0 @@
#nullable enable
using System.Collections.Generic;
using Content.Server.GameObjects.Components.Stack;
using Content.Shared.Audio;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Utility;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Destructible
{
public class Threshold : IExposeData
{
/// <summary>
/// Entities spawned on reaching this threshold, from a min to a max.
/// </summary>
[ViewVariables] public Dictionary<string, MinMax>? Spawn;
/// <summary>
/// Sound played upon destruction.
/// </summary>
[ViewVariables] public string Sound = string.Empty;
/// <summary>
/// Used instead of <see cref="Sound"/> if specified.
/// </summary>
[ViewVariables] public string SoundCollection = string.Empty;
/// <summary>
/// What acts this threshold should trigger upon activation.
/// See <see cref="ActSystem"/>.
/// </summary>
[ViewVariables] public int Acts;
/// <summary>
/// Whether or not this threshold has already been triggered.
/// </summary>
[ViewVariables] public bool Triggered;
/// <summary>
/// Whether or not this threshold only triggers once.
/// If false, it will trigger again once the entity is healed
/// and then damaged to reach this threshold once again.
/// It will not repeatedly trigger as damage rises beyond that.
/// </summary>
[ViewVariables] public bool TriggersOnce;
public void ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref Spawn, "spawn", null);
serializer.DataField(ref Sound, "sound", string.Empty);
serializer.DataField(ref SoundCollection, "soundCollection", string.Empty);
serializer.DataField(ref Acts, "acts", 0, WithFormat.Flags<ActsFlags>());
serializer.DataField(ref Triggered, "triggered", false);
serializer.DataField(ref TriggersOnce, "triggersOnce", false);
}
/// <summary>
/// Triggers this threshold.
/// </summary>
/// <param name="owner">The entity that owns this threshold.</param>
/// <param name="random">
/// An instance of <see cref="IRobustRandom"/> to get randomness from, if relevant.
/// </param>
/// <param name="actSystem">
/// An instance of <see cref="ActSystem"/> to call acts on, if relevant.
/// </param>
public void Trigger(IEntity owner, IRobustRandom random, ActSystem actSystem)
{
Triggered = true;
PlaySound(owner);
DoSpawn(owner, random);
DoActs(owner, actSystem);
}
private void PlaySound(IEntity owner)
{
var pos = owner.Transform.Coordinates;
var actualSound = string.Empty;
if (SoundCollection != string.Empty)
{
actualSound = AudioHelpers.GetRandomFileFromSoundCollection(SoundCollection);
}
else if (Sound != string.Empty)
{
actualSound = Sound;
}
if (actualSound != string.Empty)
{
EntitySystem.Get<AudioSystem>().PlayAtCoords(actualSound, pos, AudioHelpers.WithVariation(0.125f));
}
}
private void DoSpawn(IEntity owner, IRobustRandom random)
{
if (Spawn == null)
{
return;
}
foreach (var (key, value) in Spawn)
{
var count = value.Min >= value.Max
? value.Min
: random.Next(value.Min, value.Max + 1);
if (count == 0) continue;
if (EntityPrototypeHelpers.HasComponent<StackComponent>(key))
{
var spawned = owner.EntityManager.SpawnEntity(key, owner.Transform.Coordinates);
var stack = spawned.GetComponent<StackComponent>();
stack.Count = count;
spawned.RandomOffset(0.5f);
}
else
{
for (var i = 0; i < count; i++)
{
var spawned = owner.EntityManager.SpawnEntity(key, owner.Transform.Coordinates);
spawned.RandomOffset(0.5f);
}
}
}
}
private void DoActs(IEntity owner, ActSystem acts)
{
if ((Acts & (int) ThresholdActs.Breakage) != 0)
{
acts.HandleBreakage(owner);
}
if ((Acts & (int) ThresholdActs.Destruction) != 0)
{
acts.HandleDestruction(owner);
}
}
}
}

View File

@@ -0,0 +1,4 @@
namespace Content.Server.GameObjects.Components.Destructible.Thresholds
{
public sealed class ActsFlags { }
}

View File

@@ -0,0 +1,45 @@
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects.EntitySystems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior
{
public class DoActsBehavior : IThresholdBehavior
{
private int _acts;
/// <summary>
/// What acts should be triggered upon activation.
/// See <see cref="ActSystem"/>.
/// </summary>
public ThresholdActs Acts
{
get => (ThresholdActs) _acts;
set => _acts = (int) value;
}
public void ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref _acts, "acts", 0, WithFormat.Flags<ActsFlags>());
}
public bool HasAct(ThresholdActs act)
{
return (_acts & (int) act) != 0;
}
public void Trigger(IEntity owner, DestructibleSystem system)
{
if (HasAct(ThresholdActs.Breakage))
{
system.ActSystem.HandleBreakage(owner);
}
if (HasAct(ThresholdActs.Destruction))
{
system.ActSystem.HandleDestruction(owner);
}
}
}
}

View File

@@ -0,0 +1,11 @@
using Content.Server.GameObjects.EntitySystems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Serialization;
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior
{
public interface IThresholdBehavior : IExposeData
{
void Trigger(IEntity owner, DestructibleSystem system);
}
}

View File

@@ -0,0 +1,32 @@
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Audio;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior
{
public class PlaySoundBehavior : IThresholdBehavior
{
/// <summary>
/// Sound played upon destruction.
/// </summary>
public string Sound { get; set; }
public void ExposeData(ObjectSerializer serializer)
{
serializer.DataField(this, x => x.Sound, "sound", string.Empty);
}
public void Trigger(IEntity owner, DestructibleSystem system)
{
if (string.IsNullOrEmpty(Sound))
{
return;
}
var pos = owner.Transform.Coordinates;
system.AudioSystem.PlayAtCoords(Sound, pos, AudioHelpers.WithVariation(0.125f));
}
}
}

View File

@@ -0,0 +1,33 @@
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Audio;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior
{
public class PlaySoundCollectionBehavior : IThresholdBehavior
{
/// <summary>
/// Sound collection from which to pick a random sound to play.
/// </summary>
private string SoundCollection { get; set; }
public void ExposeData(ObjectSerializer serializer)
{
serializer.DataField(this, x => x.SoundCollection, "soundCollection", string.Empty);
}
public void Trigger(IEntity owner, DestructibleSystem system)
{
if (string.IsNullOrEmpty(SoundCollection))
{
return;
}
var sound = AudioHelpers.GetRandomFileFromSoundCollection(SoundCollection);
var pos = owner.Transform.Coordinates;
system.AudioSystem.PlayAtCoords(sound, pos, AudioHelpers.WithVariation(0.125f));
}
}
}

View File

@@ -0,0 +1,51 @@
#nullable enable
using System.Collections.Generic;
using Content.Server.GameObjects.Components.Stack;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Utility;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior
{
public class SpawnEntitiesBehavior : IThresholdBehavior
{
/// <summary>
/// Entities spawned on reaching this threshold, from a min to a max.
/// </summary>
public Dictionary<string, MinMax> Spawn { get; set; } = new();
public void ExposeData(ObjectSerializer serializer)
{
serializer.DataField(this, x => x.Spawn, "spawn", new Dictionary<string, MinMax>());
}
public void Trigger(IEntity owner, DestructibleSystem system)
{
foreach (var (entityId, minMax) in Spawn)
{
var count = minMax.Min >= minMax.Max
? minMax.Min
: system.Random.Next(minMax.Min, minMax.Max + 1);
if (count == 0) continue;
if (EntityPrototypeHelpers.HasComponent<StackComponent>(entityId))
{
var spawned = owner.EntityManager.SpawnEntity(entityId, owner.Transform.Coordinates);
var stack = spawned.GetComponent<StackComponent>();
stack.Count = count;
spawned.RandomOffset(0.5f);
}
else
{
for (var i = 0; i < count; i++)
{
var spawned = owner.EntityManager.SpawnEntity(entityId, owner.Transform.Coordinates);
spawned.RandomOffset(0.5f);
}
}
}
}
}
}

View File

@@ -2,7 +2,7 @@
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Destructible
namespace Content.Server.GameObjects.Components.Destructible.Thresholds
{
public struct MinMax : IExposeData
{

View File

@@ -0,0 +1,57 @@
#nullable enable
using System.Collections.Generic;
using Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior;
using Content.Server.GameObjects.EntitySystems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Destructible.Thresholds
{
public class Threshold : IExposeData
{
/// <summary>
/// Whether or not this threshold has already been triggered.
/// </summary>
[ViewVariables] public bool Triggered;
/// <summary>
/// Whether or not this threshold only triggers once.
/// If false, it will trigger again once the entity is healed
/// and then damaged to reach this threshold once again.
/// It will not repeatedly trigger as damage rises beyond that.
/// </summary>
[ViewVariables] public bool TriggersOnce;
/// <summary>
/// Behaviors to activate once this threshold is triggered.
/// </summary>
[ViewVariables] public List<IThresholdBehavior> Behaviors = new();
public void ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref Triggered, "triggered", false);
serializer.DataField(ref TriggersOnce, "triggersOnce", false);
serializer.DataField(ref Behaviors, "behaviors", new List<IThresholdBehavior>());
}
/// <summary>
/// Triggers this threshold.
/// </summary>
/// <param name="owner">The entity that owns this threshold.</param>
/// <param name="system">
/// An instance of <see cref="DestructibleSystem"/> to get dependency and
/// system references from, if relevant.
/// </param>
public void Trigger(IEntity owner, DestructibleSystem system)
{
Triggered = true;
foreach (var behavior in Behaviors)
{
behavior.Trigger(owner, system);
}
}
}
}

View File

@@ -1,13 +1,13 @@
using System;
using Robust.Shared.Serialization;
namespace Content.Server.GameObjects.Components.Destructible
namespace Content.Server.GameObjects.Components.Destructible.Thresholds
{
[Flags, FlagsFor(typeof(ActsFlags))]
[Serializable]
public enum ThresholdActs
{
Invalid = 0,
None = 0,
Breakage,
Destruction
}