Damage rework (#2525)
* Make damage work through messages and events, make destructible not inherit ruinable or reference damageable * Copy sound logic to destructible component for now * Fix typo * Fix prototype error * Remove breakable component damageable reference * Remove breakable construction reference * Remove ruinable component * Move thresholds to individual components and away from damageable * Add threshold property to damageable component code * Add thresholds to destructible component, add states to damageable, remove damage container, fix up mob states * Being alive isn't normal * Fix not reading the id * Merge fixes * YAML fixes * Grammar moment * Remove unnecessary dependency * Update thresholds doc * Change naming of thresholds to states in MobStateComponent * Being alive is once again normal * Make DamageState a byte * Bring out classes structs and enums from DestructibleComponent * Add test for destructible thresholds * Merge fixes * More merge fixes and fix rejuvenate test * Remove IMobState.IsConscious * More merge fixes someone please god review this shit already * Fix rejuvenate test * Update outdated destructible in YAML * Fix repeatedly entering the current state * Fix repeatedly entering the current state, add Threshold.TriggersOnce and expand test * Update saltern
This commit is contained in:
@@ -1,12 +1,16 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using Content.Server.Commands.Observer;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Content.Shared.GameObjects.Components.Body.Part;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Content.Shared.GameObjects.Components.Movement;
|
||||
using Robust.Server.GameObjects.Components.Container;
|
||||
using Robust.Server.Interfaces.Console;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Players;
|
||||
|
||||
@@ -76,10 +80,12 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
|
||||
void IRelayMoveInput.MoveInputPressed(ICommonSession session)
|
||||
{
|
||||
if (Owner.TryGetComponent(out IDamageableComponent? damageable) &&
|
||||
damageable.CurrentState == DamageState.Dead)
|
||||
if (Owner.TryGetComponent(out IMobStateComponent? mobState) &&
|
||||
mobState.IsDead())
|
||||
{
|
||||
new Ghost().Execute(null, (IPlayerSession) session, null);
|
||||
var shell = IoCManager.Resolve<IConsoleShell>();
|
||||
|
||||
new Ghost().Execute(shell, (IPlayerSession) session, Array.Empty<string>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
BodyPartType Part { get; }
|
||||
}
|
||||
|
||||
public class BodyHealthChangeParams : HealthChangeParams, IBodyHealthChangeParams
|
||||
public class BodyDamageChangeParams : DamageChangeParams, IBodyHealthChangeParams
|
||||
{
|
||||
public BodyHealthChangeParams(BodyPartType part)
|
||||
public BodyDamageChangeParams(BodyPartType part)
|
||||
{
|
||||
Part = part;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace Content.Server.GameObjects.Components.Buckle
|
||||
[ComponentDependency] public readonly AppearanceComponent? AppearanceComponent = null;
|
||||
[ComponentDependency] private readonly ServerAlertsComponent? _serverAlertsComponent = null;
|
||||
[ComponentDependency] private readonly StunnableComponent? _stunnableComponent = null;
|
||||
[ComponentDependency] private readonly MobStateManagerComponent? _mobStateManagerComponent = null;
|
||||
[ComponentDependency] private readonly MobStateComponent? _mobStateComponent = null;
|
||||
|
||||
private int _size;
|
||||
|
||||
@@ -351,7 +351,7 @@ namespace Content.Server.GameObjects.Components.Buckle
|
||||
EntitySystem.Get<StandingStateSystem>().Standing(Owner);
|
||||
}
|
||||
|
||||
_mobStateManagerComponent?.CurrentMobState.EnterState(Owner);
|
||||
_mobStateComponent?.CurrentState?.EnterState(Owner);
|
||||
|
||||
UpdateBuckleStatus();
|
||||
|
||||
|
||||
@@ -1,72 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Server.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.Log;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Damage
|
||||
{
|
||||
// TODO: Repair needs to set CurrentDamageState to DamageState.Alive, but it doesn't exist... should be easy enough if it's just an interface you can slap on BreakableComponent
|
||||
|
||||
/// <summary>
|
||||
/// When attached to an <see cref="IEntity"/>, allows it to take damage and sets it to a "broken state" after taking
|
||||
/// enough damage.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IDamageableComponent))]
|
||||
public class BreakableComponent : RuinableComponent, IExAct
|
||||
public class BreakableComponent : Component
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
public override string Name => "Breakable";
|
||||
|
||||
private ActSystem _actSystem;
|
||||
|
||||
public override List<DamageState> SupportedDamageStates =>
|
||||
new() {DamageState.Alive, DamageState.Dead};
|
||||
|
||||
void IExAct.OnExplosion(ExplosionEventArgs eventArgs)
|
||||
{
|
||||
switch (eventArgs.Severity)
|
||||
{
|
||||
case ExplosionSeverity.Destruction:
|
||||
case ExplosionSeverity.Heavy:
|
||||
PerformDestruction();
|
||||
break;
|
||||
case ExplosionSeverity.Light:
|
||||
if (_random.Prob(0.5f))
|
||||
{
|
||||
PerformDestruction();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_actSystem = _entitySystemManager.GetEntitySystem<ActSystem>();
|
||||
}
|
||||
|
||||
// Might want to move this down and have a more standardized method of revival
|
||||
public void FixAllDamage()
|
||||
{
|
||||
Heal();
|
||||
CurrentState = DamageState.Alive;
|
||||
}
|
||||
|
||||
protected override void DestructionBehavior()
|
||||
{
|
||||
_actSystem.HandleBreakage(Owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using Content.Server.GameObjects.Components.Construction;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
@@ -10,11 +8,8 @@ using Robust.Shared.Serialization;
|
||||
namespace Content.Server.GameObjects.Components.Damage
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IDamageableComponent))]
|
||||
public class BreakableConstructionComponent : RuinableComponent
|
||||
public class BreakableConstructionComponent : Component, IDestroyAct
|
||||
{
|
||||
private ActSystem _actSystem = default!;
|
||||
|
||||
public override string Name => "BreakableConstruction";
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
@@ -24,20 +19,16 @@ namespace Content.Server.GameObjects.Components.Damage
|
||||
serializer.DataField(this, x => x.Node, "node", string.Empty);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_actSystem = EntitySystem.Get<ActSystem>();
|
||||
}
|
||||
|
||||
public string Node { get; private set; } = string.Empty;
|
||||
|
||||
protected override async void DestructionBehavior()
|
||||
async void IDestroyAct.OnDestroy(DestructionEventArgs eventArgs)
|
||||
{
|
||||
if (Owner.Deleted || !Owner.TryGetComponent(out ConstructionComponent? construction) || string.IsNullOrEmpty(Node)) return;
|
||||
|
||||
_actSystem.HandleBreakage(Owner);
|
||||
if (Owner.Deleted ||
|
||||
!Owner.TryGetComponent(out ConstructionComponent? construction) ||
|
||||
string.IsNullOrEmpty(Node))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await construction.ChangeNode(Node);
|
||||
}
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects.Components.Stack;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Utility;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Damage
|
||||
{
|
||||
/// <summary>
|
||||
/// When attached to an <see cref="IEntity"/>, allows it to take damage and deletes it after taking enough damage.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IDamageableComponent))]
|
||||
public class DestructibleComponent : RuinableComponent, IDestroyAct
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
protected ActSystem ActSystem;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string Name => "Destructible";
|
||||
|
||||
/// <summary>
|
||||
/// Entities spawned on destruction plus the min and max amount spawned.
|
||||
/// </summary>
|
||||
public Dictionary<string, MinMax> SpawnOnDestroy { get; private set; }
|
||||
|
||||
void IDestroyAct.OnDestroy(DestructionEventArgs eventArgs)
|
||||
{
|
||||
if (SpawnOnDestroy == null || !eventArgs.IsSpawnWreck) return;
|
||||
foreach (var (key, value) in SpawnOnDestroy)
|
||||
{
|
||||
int count;
|
||||
if (value.Min >= value.Max)
|
||||
{
|
||||
count = value.Min;
|
||||
}
|
||||
else
|
||||
{
|
||||
count = _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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
|
||||
serializer.DataField(this, d => d.SpawnOnDestroy, "spawnOnDestroy", null);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
ActSystem = _entitySystemManager.GetEntitySystem<ActSystem>();
|
||||
}
|
||||
|
||||
|
||||
protected override void DestructionBehavior()
|
||||
{
|
||||
if (!Owner.Deleted)
|
||||
{
|
||||
var pos = Owner.Transform.Coordinates;
|
||||
ActSystem.HandleDestruction(Owner,
|
||||
true); //This will call IDestroyAct.OnDestroy on this component (and all other components on this entity)
|
||||
}
|
||||
}
|
||||
|
||||
public struct MinMax
|
||||
{
|
||||
public int Min;
|
||||
public int Max;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Robust.Server.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;
|
||||
using Logger = Robust.Shared.Log.Logger;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Damage
|
||||
{
|
||||
/// <summary>
|
||||
/// When attached to an <see cref="IEntity"/>, allows it to take damage and
|
||||
/// "ruins" or "destroys" it after enough damage is taken.
|
||||
/// </summary>
|
||||
[ComponentReference(typeof(IDamageableComponent))]
|
||||
public abstract class RuinableComponent : DamageableComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Sound played upon destruction.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
protected string DestroySound { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used instead of <see cref="DestroySound"/> if specified.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
protected string DestroySoundCollection { get; private set; }
|
||||
|
||||
public override List<DamageState> SupportedDamageStates =>
|
||||
new() {DamageState.Alive, DamageState.Dead};
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataReadWriteFunction(
|
||||
"deadThreshold",
|
||||
100,
|
||||
t =>
|
||||
{
|
||||
if (t == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Thresholds[DamageState.Dead] = t.Value;
|
||||
},
|
||||
() => Thresholds.TryGetValue(DamageState.Dead, out var value) ? value : (int?) null);
|
||||
|
||||
serializer.DataField(this, ruinable => ruinable.DestroySound, "destroySound", string.Empty);
|
||||
serializer.DataField(this, ruinable => ruinable.DestroySoundCollection, "destroySoundCollection", string.Empty);
|
||||
}
|
||||
|
||||
protected override void EnterState(DamageState state)
|
||||
{
|
||||
base.EnterState(state);
|
||||
|
||||
if (state == DamageState.Dead)
|
||||
{
|
||||
PerformDestruction();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys the Owner <see cref="IEntity"/>, setting
|
||||
/// <see cref="IDamageableComponent.CurrentState"/> to
|
||||
/// <see cref="Shared.GameObjects.Components.Damage.DamageState.Dead"/>
|
||||
/// </summary>
|
||||
protected void PerformDestruction()
|
||||
{
|
||||
CurrentState = DamageState.Dead;
|
||||
|
||||
if (!Owner.Deleted)
|
||||
{
|
||||
var pos = Owner.Transform.Coordinates;
|
||||
string sound = string.Empty;
|
||||
if (DestroySoundCollection != string.Empty)
|
||||
{
|
||||
sound = AudioHelpers.GetRandomFileFromSoundCollection(DestroySoundCollection);
|
||||
|
||||
}
|
||||
else if (DestroySound != string.Empty)
|
||||
{
|
||||
sound = DestroySound;
|
||||
}
|
||||
if (sound != string.Empty)
|
||||
{
|
||||
Logger.Debug("Playing destruction sound");
|
||||
EntitySystem.Get<AudioSystem>().PlayAtCoords(sound, pos, AudioHelpers.WithVariation(0.125f));
|
||||
}
|
||||
}
|
||||
|
||||
DestructionBehavior();
|
||||
}
|
||||
|
||||
protected abstract void DestructionBehavior();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace Content.Server.GameObjects.Components.Destructible
|
||||
{
|
||||
public sealed class ActsFlags { }
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
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;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Destructible
|
||||
{
|
||||
/// <summary>
|
||||
/// When attached to an <see cref="IEntity"/>, allows it to take damage
|
||||
/// and triggers thresholds when reached.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class DestructibleComponent : Component
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
private ActSystem _actSystem = default!;
|
||||
|
||||
public override string Name => "Destructible";
|
||||
|
||||
[ViewVariables]
|
||||
private SortedDictionary<int, Threshold> _lowestToHighestThresholds = new();
|
||||
|
||||
[ViewVariables] private int PreviousTotalDamage { get; set; }
|
||||
|
||||
public IReadOnlyDictionary<int, Threshold> LowestToHighestThresholds => _lowestToHighestThresholds;
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataReadWriteFunction(
|
||||
"thresholds",
|
||||
new Dictionary<int, Threshold>(),
|
||||
thresholds => _lowestToHighestThresholds = new SortedDictionary<int, Threshold>(thresholds),
|
||||
() => new Dictionary<int, Threshold>(_lowestToHighestThresholds));
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_actSystem = EntitySystem.Get<ActSystem>();
|
||||
}
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, IComponent? component)
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
|
||||
switch (message)
|
||||
{
|
||||
case DamageChangedMessage msg:
|
||||
{
|
||||
if (msg.Damageable.Owner != Owner)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (var (damage, threshold) in _lowestToHighestThresholds)
|
||||
{
|
||||
if (threshold.Triggered)
|
||||
{
|
||||
if (threshold.TriggersOnce)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (PreviousTotalDamage >= damage)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (msg.Damageable.TotalDamage >= damage)
|
||||
{
|
||||
var thresholdMessage = new DestructibleThresholdReachedMessage(this, threshold, msg.Damageable.TotalDamage, damage);
|
||||
SendMessage(thresholdMessage);
|
||||
|
||||
threshold.Trigger(Owner, _random, _actSystem);
|
||||
}
|
||||
}
|
||||
|
||||
PreviousTotalDamage = msg.Damageable.TotalDamage;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Destructible
|
||||
{
|
||||
public class DestructibleThresholdReachedMessage : ComponentMessage
|
||||
{
|
||||
public DestructibleThresholdReachedMessage(DestructibleComponent parent, Threshold threshold, int totalDamage, int thresholdAmount)
|
||||
{
|
||||
Parent = parent;
|
||||
Threshold = threshold;
|
||||
TotalDamage = totalDamage;
|
||||
ThresholdAmount = thresholdAmount;
|
||||
}
|
||||
|
||||
public DestructibleComponent Parent { get; }
|
||||
|
||||
public Threshold Threshold { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The amount of total damage currently had that triggered this threshold.
|
||||
/// </summary>
|
||||
public int TotalDamage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The amount of damage at which this threshold triggers.
|
||||
/// </summary>
|
||||
public int ThresholdAmount { get; }
|
||||
}
|
||||
}
|
||||
13
Content.Server/GameObjects/Components/Destructible/MinMax.cs
Normal file
13
Content.Server/GameObjects/Components/Destructible/MinMax.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Destructible
|
||||
{
|
||||
public struct MinMax
|
||||
{
|
||||
[ViewVariables]
|
||||
public int Min;
|
||||
|
||||
[ViewVariables]
|
||||
public int Max;
|
||||
}
|
||||
}
|
||||
148
Content.Server/GameObjects/Components/Destructible/Threshold.cs
Normal file
148
Content.Server/GameObjects/Components/Destructible/Threshold.cs
Normal file
@@ -0,0 +1,148 @@
|
||||
#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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Destructible
|
||||
{
|
||||
[Flags, FlagsFor(typeof(ActsFlags))]
|
||||
[Serializable]
|
||||
public enum ThresholdActs
|
||||
{
|
||||
Invalid = 0,
|
||||
Breakage,
|
||||
Destruction
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ using Content.Shared.GameObjects.Components.Body;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Disposal;
|
||||
using Content.Shared.GameObjects.Components.Items;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Verbs;
|
||||
using Content.Shared.Interfaces;
|
||||
@@ -139,11 +140,10 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!entity.TryGetComponent(out IPhysicsComponent? physics) ||
|
||||
!physics.CanCollide)
|
||||
{
|
||||
if (!(entity.TryGetComponent(out IDamageableComponent? damageState) && damageState.CurrentState == DamageState.Dead)) {
|
||||
if (!(entity.TryGetComponent(out IMobStateComponent? state) && state.IsDead())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Server.GameObjects.Components.Damage;
|
||||
using Content.Server.GameObjects.Components.Interactable;
|
||||
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Gravity;
|
||||
using Content.Shared.GameObjects.Components.Interactable;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
@@ -108,8 +109,11 @@ namespace Content.Server.GameObjects.Components.Gravity
|
||||
return false;
|
||||
|
||||
// Repair generator
|
||||
var breakable = Owner.GetComponent<BreakableComponent>();
|
||||
breakable.FixAllDamage();
|
||||
if (Owner.TryGetComponent(out IDamageableComponent? damageable))
|
||||
{
|
||||
damageable.Heal();
|
||||
}
|
||||
|
||||
_intact = true;
|
||||
|
||||
Owner.PopupMessage(eventArgs.User,
|
||||
|
||||
@@ -9,6 +9,8 @@ using Content.Server.Mobs;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Medical;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Content.Shared.Preferences;
|
||||
using Robust.Server.GameObjects;
|
||||
@@ -163,8 +165,8 @@ namespace Content.Server.GameObjects.Components.Medical
|
||||
}
|
||||
|
||||
var dead =
|
||||
mind.OwnedEntity.TryGetComponent<IDamageableComponent>(out var damageable) &&
|
||||
damageable.CurrentState == DamageState.Dead;
|
||||
mind.OwnedEntity.TryGetComponent<IMobStateComponent>(out var state) &&
|
||||
state.IsDead();
|
||||
if (!dead) return;
|
||||
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Medical;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Verbs;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
@@ -146,14 +147,23 @@ namespace Content.Server.GameObjects.Components.Medical
|
||||
UserInterface?.SetState(newState);
|
||||
}
|
||||
|
||||
private MedicalScannerStatus GetStatusFromDamageState(DamageState damageState)
|
||||
private MedicalScannerStatus GetStatusFromDamageState(IMobStateComponent state)
|
||||
{
|
||||
switch (damageState)
|
||||
if (state.IsAlive())
|
||||
{
|
||||
case DamageState.Alive: return MedicalScannerStatus.Green;
|
||||
case DamageState.Critical: return MedicalScannerStatus.Red;
|
||||
case DamageState.Dead: return MedicalScannerStatus.Death;
|
||||
default: throw new ArgumentException(nameof(damageState));
|
||||
return MedicalScannerStatus.Green;
|
||||
}
|
||||
else if (state.IsCritical())
|
||||
{
|
||||
return MedicalScannerStatus.Red;
|
||||
}
|
||||
else if (state.IsDead())
|
||||
{
|
||||
return MedicalScannerStatus.Death;
|
||||
}
|
||||
else
|
||||
{
|
||||
return MedicalScannerStatus.Yellow;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,9 +172,11 @@ namespace Content.Server.GameObjects.Components.Medical
|
||||
if (Powered)
|
||||
{
|
||||
var body = _bodyContainer.ContainedEntity;
|
||||
return body == null
|
||||
var state = body?.GetComponentOrNull<IMobStateComponent>();
|
||||
|
||||
return state == null
|
||||
? MedicalScannerStatus.Open
|
||||
: GetStatusFromDamageState(body.GetComponent<IDamageableComponent>().CurrentState);
|
||||
: GetStatusFromDamageState(state);
|
||||
}
|
||||
|
||||
return MedicalScannerStatus.Off;
|
||||
|
||||
@@ -12,6 +12,7 @@ using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Content.Shared.GameObjects.Components.Body.Mechanism;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Interfaces.Chemistry;
|
||||
@@ -355,8 +356,8 @@ namespace Content.Server.GameObjects.Components.Metabolism
|
||||
/// </param>
|
||||
public void Update(float frameTime)
|
||||
{
|
||||
if (!Owner.TryGetComponent<IDamageableComponent>(out var damageable) ||
|
||||
damageable.CurrentState == DamageState.Dead)
|
||||
if (!Owner.TryGetComponent<IMobStateComponent>(out var state) ||
|
||||
state.IsDead())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using Content.Server.Mobs;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Server.GameObjects.Components.UserInterface;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -174,8 +175,8 @@ namespace Content.Server.GameObjects.Components.Mobs
|
||||
}
|
||||
|
||||
var dead =
|
||||
Owner.TryGetComponent<IDamageableComponent>(out var damageable) &&
|
||||
damageable.CurrentState == DamageState.Dead;
|
||||
Owner.TryGetComponent<IMobStateComponent>(out var state) &&
|
||||
state.IsDead();
|
||||
|
||||
if (!HasMind)
|
||||
{
|
||||
|
||||
@@ -9,20 +9,17 @@ using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Mobs.State
|
||||
{
|
||||
public class CriticalState : SharedCriticalState
|
||||
public class CriticalMobState : SharedCriticalMobState
|
||||
{
|
||||
public override void EnterState(IEntity entity)
|
||||
{
|
||||
base.EnterState(entity);
|
||||
|
||||
if (entity.TryGetComponent(out AppearanceComponent appearance))
|
||||
{
|
||||
appearance.SetData(DamageStateVisuals.State, DamageState.Critical);
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out ServerAlertsComponent status))
|
||||
{
|
||||
status.ShowAlert(AlertType.HumanCrit); //Todo: combine humancrit-0 and humancrit-1 into a gif and display it
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlay))
|
||||
{
|
||||
overlay.AddOverlay(SharedOverlayID.GradientCircleMaskOverlay);
|
||||
@@ -38,12 +35,12 @@ namespace Content.Server.GameObjects.Components.Mobs.State
|
||||
|
||||
public override void ExitState(IEntity entity)
|
||||
{
|
||||
base.ExitState(entity);
|
||||
|
||||
if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlay))
|
||||
{
|
||||
overlay.ClearOverlays();
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateState(IEntity entity) { }
|
||||
}
|
||||
}
|
||||
@@ -10,10 +10,12 @@ using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Mobs.State
|
||||
{
|
||||
public class DeadState : SharedDeadState
|
||||
public class DeadMobState : SharedDeadMobState
|
||||
{
|
||||
public override void EnterState(IEntity entity)
|
||||
{
|
||||
base.EnterState(entity);
|
||||
|
||||
if (entity.TryGetComponent(out AppearanceComponent appearance))
|
||||
{
|
||||
appearance.SetData(DamageStateVisuals.State, DamageState.Dead);
|
||||
@@ -44,6 +46,8 @@ namespace Content.Server.GameObjects.Components.Mobs.State
|
||||
|
||||
public override void ExitState(IEntity entity)
|
||||
{
|
||||
base.ExitState(entity);
|
||||
|
||||
if (entity.TryGetComponent(out IPhysicsComponent physics))
|
||||
{
|
||||
physics.CanCollide = true;
|
||||
@@ -54,7 +58,5 @@ namespace Content.Server.GameObjects.Components.Mobs.State
|
||||
overlay.ClearOverlays();
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateState(IEntity entity) { }
|
||||
}
|
||||
}
|
||||
@@ -1,71 +1,22 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Mobs.State
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedMobStateManagerComponent))]
|
||||
public class MobStateManagerComponent : SharedMobStateManagerComponent
|
||||
[ComponentReference(typeof(SharedMobStateComponent))]
|
||||
[ComponentReference(typeof(IMobStateComponent))]
|
||||
public class MobStateComponent : SharedMobStateComponent
|
||||
{
|
||||
private readonly Dictionary<DamageState, IMobState> _behavior = new()
|
||||
{
|
||||
{DamageState.Alive, new NormalState()},
|
||||
{DamageState.Critical, new CriticalState()},
|
||||
{DamageState.Dead, new DeadState()}
|
||||
};
|
||||
|
||||
private DamageState _currentDamageState;
|
||||
|
||||
protected override IReadOnlyDictionary<DamageState, IMobState> Behavior => _behavior;
|
||||
|
||||
public override IMobState CurrentMobState { get; protected set; }
|
||||
|
||||
public override DamageState CurrentDamageState
|
||||
{
|
||||
get => _currentDamageState;
|
||||
protected set
|
||||
{
|
||||
if (_currentDamageState == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_currentDamageState != DamageState.Invalid)
|
||||
{
|
||||
CurrentMobState.ExitState(Owner);
|
||||
}
|
||||
|
||||
_currentDamageState = value;
|
||||
CurrentMobState = Behavior[CurrentDamageState];
|
||||
CurrentMobState.EnterState(Owner);
|
||||
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
{
|
||||
// TODO: Might want to add an OnRemove() to IMobState since those are where these components are being used
|
||||
base.OnRemove();
|
||||
|
||||
if (Owner.TryGetComponent(out ServerAlertsComponent status))
|
||||
{
|
||||
status.ClearAlert(AlertType.HumanHealth);
|
||||
}
|
||||
|
||||
if (Owner.TryGetComponent(out ServerOverlayEffectsComponent overlay))
|
||||
{
|
||||
overlay.ClearOverlays();
|
||||
}
|
||||
}
|
||||
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
return new MobStateManagerComponentState(CurrentDamageState);
|
||||
base.OnRemove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
#nullable enable
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Mobs.State
|
||||
{
|
||||
public class NormalMobState : SharedNormalMobState
|
||||
{
|
||||
public override void EnterState(IEntity entity)
|
||||
{
|
||||
base.EnterState(entity);
|
||||
|
||||
EntitySystem.Get<StandingStateSystem>().Standing(entity);
|
||||
|
||||
if (entity.TryGetComponent(out AppearanceComponent? appearance))
|
||||
{
|
||||
appearance.SetData(DamageStateVisuals.State, DamageState.Alive);
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateState(IEntity entity, int threshold)
|
||||
{
|
||||
base.UpdateState(entity, threshold);
|
||||
|
||||
if (!entity.TryGetComponent(out IDamageableComponent? damageable))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!entity.TryGetComponent(out ServerAlertsComponent? alerts))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!entity.TryGetComponent(out IMobStateComponent? stateComponent))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
short modifier = 0;
|
||||
|
||||
if (stateComponent.TryGetEarliestIncapacitatedState(threshold, out _, out var earliestThreshold))
|
||||
{
|
||||
modifier = (short) (damageable.TotalDamage / (earliestThreshold / 7f));
|
||||
}
|
||||
|
||||
alerts.ShowAlert(AlertType.HumanHealth, modifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
using Content.Server.GameObjects.Components.Damage;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Mobs.State
|
||||
{
|
||||
public class NormalState : SharedNormalState
|
||||
{
|
||||
public override void EnterState(IEntity entity)
|
||||
{
|
||||
EntitySystem.Get<StandingStateSystem>().Standing(entity);
|
||||
|
||||
if (entity.TryGetComponent(out AppearanceComponent appearance))
|
||||
{
|
||||
appearance.SetData(DamageStateVisuals.State, DamageState.Alive);
|
||||
}
|
||||
|
||||
UpdateState(entity);
|
||||
}
|
||||
|
||||
public override void ExitState(IEntity entity) { }
|
||||
|
||||
public override void UpdateState(IEntity entity)
|
||||
{
|
||||
if (!entity.TryGetComponent(out ServerAlertsComponent status))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!entity.TryGetComponent(out IDamageableComponent damageable))
|
||||
{
|
||||
status.ShowAlert(AlertType.HumanHealth, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO
|
||||
switch (damageable)
|
||||
{
|
||||
case RuinableComponent ruinable:
|
||||
{
|
||||
if (!ruinable.Thresholds.TryGetValue(DamageState.Dead, out var threshold))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var modifier = (short) (ruinable.TotalDamage / (threshold / 7f));
|
||||
|
||||
status.ShowAlert(AlertType.HumanHealth, modifier);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (!damageable.Thresholds.TryGetValue(DamageState.Critical, out var threshold))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var modifier = (short) (damageable.TotalDamage / (threshold / 7f));
|
||||
|
||||
status.ShowAlert(AlertType.HumanHealth, modifier);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
#nullable enable
|
||||
using Content.Server.AI.Utility.AiLogic;
|
||||
using Content.Server.GameObjects.EntitySystems.AI;
|
||||
using Content.Server.Interfaces.GameTicking;
|
||||
using Content.Shared.GameObjects.Components.Movement;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Server.AI;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
@@ -38,7 +38,7 @@ namespace Content.Server.GameObjects.Components.Movement
|
||||
}
|
||||
}
|
||||
|
||||
public AiLogicProcessor? Processor { get; set; }
|
||||
public UtilityAi? Processor { get; set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string? StartingGearPrototype { get; set; }
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Content.Shared.GameObjects.Components.Movement;
|
||||
using Content.Shared.GameObjects.Components.Nutrition;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -185,15 +186,19 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
HungerThresholdEffect();
|
||||
Dirty();
|
||||
}
|
||||
if (_currentHungerThreshold == HungerThreshold.Dead)
|
||||
|
||||
if (_currentHungerThreshold != HungerThreshold.Dead)
|
||||
return;
|
||||
|
||||
if (!Owner.TryGetComponent(out IDamageableComponent damageable))
|
||||
return;
|
||||
|
||||
if (!Owner.TryGetComponent(out IMobStateComponent mobState))
|
||||
return;
|
||||
|
||||
if (!mobState.IsDead())
|
||||
{
|
||||
if (Owner.TryGetComponent(out IDamageableComponent damageable))
|
||||
{
|
||||
if (damageable.CurrentState != DamageState.Dead)
|
||||
{
|
||||
damageable.ChangeDamage(DamageType.Blunt, 2, true, null);
|
||||
}
|
||||
}
|
||||
damageable.ChangeDamage(DamageType.Blunt, 2, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,6 +214,4 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
return new HungerComponentState(_currentHungerThreshold);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using Content.Shared.Alert;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Content.Shared.GameObjects.Components.Movement;
|
||||
using Content.Shared.GameObjects.Components.Nutrition;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -183,19 +184,21 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
Dirty();
|
||||
}
|
||||
|
||||
if (_currentThirstThreshold == ThirstThreshold.Dead)
|
||||
if (_currentThirstThreshold != ThirstThreshold.Dead)
|
||||
return;
|
||||
|
||||
if (!Owner.TryGetComponent(out IDamageableComponent damageable))
|
||||
return;
|
||||
|
||||
if (!Owner.TryGetComponent(out IMobStateComponent mobState))
|
||||
return;
|
||||
|
||||
if (!mobState.IsDead())
|
||||
{
|
||||
if (Owner.TryGetComponent(out IDamageableComponent damageable))
|
||||
{
|
||||
if (damageable.CurrentState != DamageState.Dead)
|
||||
{
|
||||
damageable.ChangeDamage(DamageType.Blunt, 2, true, null);
|
||||
}
|
||||
}
|
||||
damageable.ChangeDamage(DamageType.Blunt, 2, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void ResetThirst()
|
||||
{
|
||||
_currentThirstThreshold = ThirstThreshold.Okay;
|
||||
@@ -208,5 +211,4 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
return new ThirstComponentState(_currentThirstThreshold);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using Content.Server.Mobs;
|
||||
using Content.Server.Mobs.Roles;
|
||||
using Content.Server.Mobs.Roles.Suspicion;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Content.Shared.GameObjects.Components.Suspicion;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Server.GameObjects;
|
||||
@@ -60,8 +61,8 @@ namespace Content.Server.GameObjects.Components.Suspicion
|
||||
|
||||
public bool IsDead()
|
||||
{
|
||||
return Owner.TryGetComponent(out IDamageableComponent? damageable) &&
|
||||
damageable.CurrentState == DamageState.Dead;
|
||||
return Owner.TryGetComponent(out IMobStateComponent? state) &&
|
||||
state.IsDead();
|
||||
}
|
||||
|
||||
public bool IsInnocent()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
#nullable enable
|
||||
using System;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
@@ -10,7 +11,9 @@ using Robust.Server.GameObjects;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.GameObjects.Components
|
||||
@@ -19,57 +22,46 @@ namespace Content.Server.GameObjects.Components
|
||||
[ComponentReference(typeof(SharedWindowComponent))]
|
||||
public class WindowComponent : SharedWindowComponent, IExamine, IInteractHand
|
||||
{
|
||||
private int? Damage
|
||||
private int _maxDamage;
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
get
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(ref _maxDamage, "maxDamage", 100);
|
||||
}
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, IComponent? component)
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
|
||||
switch (message)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out IDamageableComponent damageableComponent)) return null;
|
||||
return damageableComponent.TotalDamage;
|
||||
case DamageChangedMessage msg:
|
||||
{
|
||||
var current = msg.Damageable.TotalDamage;
|
||||
UpdateVisuals(current);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int? MaxDamage
|
||||
private void UpdateVisuals(int currentDamage)
|
||||
{
|
||||
get
|
||||
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
|
||||
{
|
||||
if (!Owner.TryGetComponent(out IDamageableComponent damageableComponent)) return null;
|
||||
return damageableComponent.Thresholds[DamageState.Dead];
|
||||
appearance.SetData(WindowVisuals.Damage, (float) currentDamage / _maxDamage);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
if (Owner.TryGetComponent(out IDamageableComponent damageableComponent))
|
||||
{
|
||||
damageableComponent.HealthChangedEvent += OnDamage;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDamage(HealthChangedEventArgs eventArgs)
|
||||
{
|
||||
int current = eventArgs.Damageable.TotalDamage;
|
||||
int max = eventArgs.Damageable.Thresholds[DamageState.Dead];
|
||||
if (eventArgs.Damageable.CurrentState == DamageState.Dead) return;
|
||||
UpdateVisuals(current, max);
|
||||
}
|
||||
|
||||
private void UpdateVisuals(int currentDamage, int maxDamage)
|
||||
{
|
||||
if (Owner.TryGetComponent(out AppearanceComponent appearance))
|
||||
{
|
||||
appearance.SetData(WindowVisuals.Damage, (float) currentDamage / maxDamage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void IExamine.Examine(FormattedMessage message, bool inDetailsRange)
|
||||
{
|
||||
int? damage = Damage;
|
||||
int? maxDamage = MaxDamage;
|
||||
if (damage == null || maxDamage == null) return;
|
||||
float fraction = ((damage == 0 || maxDamage == 0) ? 0f : (float) damage / maxDamage) ?? 0f;
|
||||
int level = Math.Min(ContentHelpers.RoundToLevels(fraction, 1, 7), 5);
|
||||
var damage = Owner.GetComponentOrNull<IDamageableComponent>()?.TotalDamage;
|
||||
if (damage == null) return;
|
||||
var fraction = ((damage == 0 || _maxDamage == 0)
|
||||
? 0f
|
||||
: (float) damage / _maxDamage);
|
||||
var level = Math.Min(ContentHelpers.RoundToLevels(fraction, 1, 7), 5);
|
||||
switch (level)
|
||||
{
|
||||
case 0:
|
||||
|
||||
Reference in New Issue
Block a user