Bodysystem and damagesystem rework (#1544)
* Things and stuff with grids, unfinished w/ code debug changes. * Updated submodule and also lost some progress cause I fucked it up xd * First unfinished draft of the BodySystem. Doesn't compile. * More changes to make it compile, but still just a framework. Doesn't do anything at the moment. * Many cleanup changes. * Revert "Merge branch 'master' of https://github.com/GlassEclipse/space-station-14 into body_system" This reverts commit ddd4aebbc76cf2a0b7b102f72b93d55a0816c88c, reversing changes made to 12d0dd752706bdda8879393bd8191a1199a0c978. * Commit human.yml * Updated a lot of things to be more classy, more progress overall, etc. etc. * Latest update with many changes * Minor changes * Fixed Travis build bug * Adds first draft of Body Scanner console, apparently I also forgot to tie Mechanisms into body parts so now a heart just sits in the Torso like a good boy :) * Commit rest of stuff * Latest changes * Latest changes again * 14 naked cowboys * Yay! * Latest changes (probably doesnt compile) * Surgery!!!!!!!!!~1116y * Cleaned some stuff up * More cleanup * Refactoring of code. Basic surgery path now done. * Removed readme, has been added to HackMD * Fixes typo (and thus test errors) * WIP changes, committing so I can pull latest master changes * Still working on that god awful merge * Latest changes * Latest changes!! * Beginning of refactor to BoundUserInterface * Surgery! * Latest changes - fixes pr change requests and random fixes * oops * Fixes bodypart recursion * Beginning of work on revamping the damage system. * More latest changes * Latest changes * Finished merge * Commit before removing old healthcode * Almost done with removing speciescomponent... * It compiles!!! * yahoo more work * Fixes to make it work * Merge conflict fixes * Deleting species visualizer was a mistake * IDE warnings are VERBOTEN * makes the server not kill itself on startup, some cleanup (#1) * Namespaces, comments and exception fixes * Fix conveyor and conveyor switch serialization SS14 in reactive when * Move damage, acts and body to shared Damage cleanup Comment cleanup * Rename SpeciesComponent to RotationComponent and cleanup Damage cleanup Comment cleanup * Fix nullable warnings * Address old reviews Fix off welder suicide damage type, deathmatch and suspicion * Fix new test fail with units being able to accept items when unpowered * Remove RotationComponent, change references to IBodyManagerComponent * Add a bloodstream to humans * More cleanups * Add body conduits, connections, connectors substances and valves * Revert "Add body conduits, connections, connectors substances and valves" This reverts commit 9ab0b50e6b15fe98852d7b0836c0cdbf4bd76d20. * Implement the heart mechanism behavior with the circulatory network * Added network property to mechanism behaviors * Changed human organ sprites and added missing ones * Fix tests * Add individual body part sprite rendering * Fix error where dropped mechanisms are not initialized * Implement client/server body damage * Make DamageContainer take care of raising events * Reimplement medical scanner with the new body system * Improve the medical scanner ui * Merge conflict fixes * Fix crash when colliding with something * Fix microwave suicides and eyes sprite rendering * Fix nullable reference error * Fix up surgery client side * Fix missing using from merge conflict * Add breathing *inhale * Merge conflict fixes * Fix accumulatedframetime being reset to 0 instead of decreased by the threshold https://github.com/space-wizards/space-station-14/pull/1617 * Use and add to the new AtmosHelpers * Fix feet * Add proper coloring to dropped body parts * Fix Urist's lungs being too strong * Merge conflict fixes * Merge conflict fixes * Merge conflict fixes Co-authored-by: GlassEclipse <tsymall5@gmail.com> Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com> Co-authored-by: AJCM-git <60196617+AJCM-git@users.noreply.github.com>
This commit is contained in:
@@ -1,39 +1,59 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Server.Interfaces.GameObjects;
|
||||
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.Random;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
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]
|
||||
public class BreakableComponent : Component, IOnDamageBehavior, IExAct
|
||||
[ComponentReference(typeof(IDamageableComponent))]
|
||||
public class BreakableComponent : RuinableComponent, IExAct
|
||||
{
|
||||
|
||||
#pragma warning disable 649
|
||||
#pragma warning disable 649
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager;
|
||||
#pragma warning restore 649
|
||||
/// <inheritdoc />
|
||||
public override string Name => "Breakable";
|
||||
public DamageThreshold Threshold { get; private set; }
|
||||
[Dependency] private readonly IRobustRandom _random;
|
||||
#pragma warning restore 649
|
||||
|
||||
public DamageType damageType = DamageType.Total;
|
||||
public int damageValue = 0;
|
||||
public bool broken = false;
|
||||
public override string Name => "Breakable";
|
||||
|
||||
private ActSystem _actSystem;
|
||||
private DamageState _currentDamageState;
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
public override List<DamageState> SupportedDamageStates =>
|
||||
new List<DamageState> {DamageState.Alive, DamageState.Dead};
|
||||
|
||||
public override DamageState CurrentDamageState => _currentDamageState;
|
||||
|
||||
void IExAct.OnExplosion(ExplosionEventArgs eventArgs)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
switch (eventArgs.Severity)
|
||||
{
|
||||
case ExplosionSeverity.Destruction:
|
||||
PerformDestruction();
|
||||
break;
|
||||
case ExplosionSeverity.Heavy:
|
||||
PerformDestruction();
|
||||
break;
|
||||
case ExplosionSeverity.Light:
|
||||
if (_random.Prob(0.5f))
|
||||
{
|
||||
PerformDestruction();
|
||||
}
|
||||
|
||||
serializer.DataField(ref damageValue, "thresholdvalue", 100);
|
||||
serializer.DataField(ref damageType, "thresholdtype", DamageType.Total);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
@@ -42,38 +62,21 @@ namespace Content.Server.GameObjects.Components.Damage
|
||||
_actSystem = _entitySystemManager.GetEntitySystem<ActSystem>();
|
||||
}
|
||||
|
||||
public List<DamageThreshold> GetAllDamageThresholds()
|
||||
// Might want to move this down and have a more standardized method of revival
|
||||
public void FixAllDamage()
|
||||
{
|
||||
Threshold = new DamageThreshold(damageType, damageValue, ThresholdType.Breakage);
|
||||
return new List<DamageThreshold>() {Threshold};
|
||||
Heal();
|
||||
_currentDamageState = DamageState.Alive;
|
||||
}
|
||||
|
||||
public void OnDamageThresholdPassed(object obj, DamageThresholdPassedEventArgs e)
|
||||
protected override void DestructionBehavior()
|
||||
{
|
||||
if (e.Passed && e.DamageThreshold == Threshold && broken == false)
|
||||
_actSystem.HandleBreakage(Owner);
|
||||
if (!Owner.Deleted && DestroySound != string.Empty)
|
||||
{
|
||||
broken = true;
|
||||
_actSystem.HandleBreakage(Owner);
|
||||
var pos = Owner.Transform.GridPosition;
|
||||
EntitySystem.Get<AudioSystem>().PlayAtCoords(DestroySound, pos);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnExplosion(ExplosionEventArgs eventArgs)
|
||||
{
|
||||
var prob = IoCManager.Resolve<IRobustRandom>();
|
||||
switch (eventArgs.Severity)
|
||||
{
|
||||
case ExplosionSeverity.Destruction:
|
||||
_actSystem.HandleBreakage(Owner);
|
||||
break;
|
||||
case ExplosionSeverity.Heavy:
|
||||
_actSystem.HandleBreakage(Owner);
|
||||
break;
|
||||
case ExplosionSeverity.Light:
|
||||
if(prob.Prob(0.4f))
|
||||
_actSystem.HandleBreakage(Owner);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -23,7 +24,7 @@ namespace Content.Server.GameObjects.Components.Damage
|
||||
|
||||
public override string Name => "DamageOnHighSpeedImpact";
|
||||
|
||||
public DamageType Damage { get; set; } = DamageType.Brute;
|
||||
public DamageType Damage { get; set; } = DamageType.Blunt;
|
||||
public float MinimumSpeed { get; set; } = 20f;
|
||||
public int BaseDamage { get; set; } = 5;
|
||||
public float Factor { get; set; } = 0.75f;
|
||||
@@ -38,7 +39,7 @@ namespace Content.Server.GameObjects.Components.Damage
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(this, x => Damage, "damage", DamageType.Brute);
|
||||
serializer.DataField(this, x => Damage, "damage", DamageType.Blunt);
|
||||
serializer.DataField(this, x => MinimumSpeed, "minimumSpeed", 20f);
|
||||
serializer.DataField(this, x => BaseDamage, "baseDamage", 5);
|
||||
serializer.DataField(this, x => Factor, "factor", 1f);
|
||||
@@ -51,7 +52,7 @@ namespace Content.Server.GameObjects.Components.Damage
|
||||
|
||||
public void CollideWith(IEntity collidedWith)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out ICollidableComponent collidable) || !Owner.TryGetComponent(out DamageableComponent damageable)) return;
|
||||
if (!Owner.TryGetComponent(out ICollidableComponent collidable) || !Owner.TryGetComponent(out IDamageableComponent damageable)) return;
|
||||
|
||||
var speed = collidable.LinearVelocity.Length;
|
||||
|
||||
@@ -70,7 +71,7 @@ namespace Content.Server.GameObjects.Components.Damage
|
||||
if (Owner.TryGetComponent(out StunnableComponent stun) && _robustRandom.Prob(StunChance))
|
||||
stun.Stun(StunSeconds);
|
||||
|
||||
damageable.TakeDamage(Damage, damage, collidedWith, Owner);
|
||||
damageable.ChangeDamage(Damage, damage, false, collidedWith);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects.Components.Interactable;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Interactable;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -9,7 +9,7 @@ using Robust.Shared.Serialization;
|
||||
namespace Content.Server.GameObjects.Components.Damage
|
||||
{
|
||||
[RegisterComponent]
|
||||
class DamageOnToolInteractComponent : Component, IInteractUsing
|
||||
public class DamageOnToolInteractComponent : Component, IInteractUsing
|
||||
{
|
||||
public override string Name => "DamageOnToolInteract";
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Content.Server.GameObjects.Components.Damage
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
Owner.EnsureComponent<DamageableComponent>();
|
||||
Owner.EnsureComponent<DestructibleComponent>();
|
||||
}
|
||||
|
||||
public bool InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
@@ -40,12 +40,12 @@ namespace Content.Server.GameObjects.Components.Damage
|
||||
{
|
||||
if (tool.HasQuality(ToolQuality.Welding) && toolQuality == ToolQuality.Welding)
|
||||
{
|
||||
if (eventArgs.Using.TryGetComponent<WelderComponent>(out WelderComponent welder))
|
||||
{
|
||||
if (eventArgs.Using.TryGetComponent(out WelderComponent welder))
|
||||
{
|
||||
if (welder.WelderLit) return CallDamage(eventArgs, tool);
|
||||
}
|
||||
}
|
||||
break; //If the tool quality is welding and its not lit or its not actually a welder that can be lit then its pointless to continue.
|
||||
}
|
||||
}
|
||||
|
||||
if (tool.HasQuality(toolQuality)) return CallDamage(eventArgs, tool);
|
||||
}
|
||||
@@ -55,14 +55,17 @@ namespace Content.Server.GameObjects.Components.Damage
|
||||
|
||||
protected bool CallDamage(InteractUsingEventArgs eventArgs, ToolComponent tool)
|
||||
{
|
||||
if (eventArgs.Target.TryGetComponent<DamageableComponent>(out var damageable))
|
||||
if (eventArgs.Target.TryGetComponent<DestructibleComponent>(out var damageable))
|
||||
{
|
||||
if(tool.HasQuality(ToolQuality.Welding)) damageable.TakeDamage(DamageType.Heat, Damage, eventArgs.Using, eventArgs.User);
|
||||
else
|
||||
damageable.TakeDamage(DamageType.Brute, Damage, eventArgs.Using, eventArgs.User);
|
||||
damageable.ChangeDamage(tool.HasQuality(ToolQuality.Welding)
|
||||
? DamageType.Heat
|
||||
: DamageType.Blunt,
|
||||
Damage, false, eventArgs.User);
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
using System;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Damage
|
||||
{
|
||||
/// <summary>
|
||||
/// Triggers an event when values rise above or drop below this threshold
|
||||
/// </summary>
|
||||
public struct DamageThreshold
|
||||
{
|
||||
public DamageType DamageType { get; }
|
||||
public int Value { get; }
|
||||
public ThresholdType ThresholdType { get; }
|
||||
|
||||
public DamageThreshold(DamageType damageType, int value, ThresholdType thresholdType)
|
||||
{
|
||||
DamageType = damageType;
|
||||
Value = value;
|
||||
ThresholdType = thresholdType;
|
||||
}
|
||||
|
||||
public override bool Equals(Object obj)
|
||||
{
|
||||
return obj is DamageThreshold threshold && this == threshold;
|
||||
}
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return DamageType.GetHashCode() ^ Value.GetHashCode();
|
||||
}
|
||||
public static bool operator ==(DamageThreshold x, DamageThreshold y)
|
||||
{
|
||||
return x.DamageType == y.DamageType && x.Value == y.Value;
|
||||
}
|
||||
public static bool operator !=(DamageThreshold x, DamageThreshold y)
|
||||
{
|
||||
return !(x == y);
|
||||
}
|
||||
}
|
||||
|
||||
public enum ThresholdType
|
||||
{
|
||||
None,
|
||||
Destruction,
|
||||
Death,
|
||||
Critical,
|
||||
HUDUpdate,
|
||||
Breakage,
|
||||
}
|
||||
|
||||
public class DamageThresholdPassedEventArgs : EventArgs
|
||||
{
|
||||
public DamageThreshold DamageThreshold { get; }
|
||||
public bool Passed { get; }
|
||||
public int ExcessDamage { get; }
|
||||
|
||||
public DamageThresholdPassedEventArgs(DamageThreshold threshold, bool passed, int excess)
|
||||
{
|
||||
DamageThreshold = threshold;
|
||||
Passed = passed;
|
||||
ExcessDamage = excess;
|
||||
}
|
||||
}
|
||||
|
||||
public class DamageEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of damage.
|
||||
/// </summary>
|
||||
public DamageType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Change in damage.
|
||||
/// </summary>
|
||||
public int Damage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The entity that damaged this one.
|
||||
/// Could be null.
|
||||
/// </summary>
|
||||
public IEntity Source { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The mob entity that damaged this one.
|
||||
/// Could be null.
|
||||
/// </summary>
|
||||
public IEntity SourceMob { get; }
|
||||
|
||||
public DamageEventArgs(DamageType type, int damage, IEntity source, IEntity sourceMob)
|
||||
{
|
||||
Type = type;
|
||||
Damage = damage;
|
||||
Source = source;
|
||||
SourceMob = sourceMob;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,212 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.Interfaces.GameObjects;
|
||||
using Content.Server.Interfaces.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Damage
|
||||
{
|
||||
//TODO: add support for component add/remove
|
||||
|
||||
/// <summary>
|
||||
/// A component that handles receiving damage and healing,
|
||||
/// as well as informing other components of it.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class DamageableComponent : SharedDamageableComponent, IDamageableComponent
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string Name => "Damageable";
|
||||
|
||||
/// <summary>
|
||||
/// The resistance set of this object.
|
||||
/// Affects receiving damage of various types.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public ResistanceSet Resistances { get; private set; }
|
||||
|
||||
[ViewVariables]
|
||||
public IReadOnlyDictionary<DamageType, int> CurrentDamage => _currentDamage;
|
||||
private Dictionary<DamageType, int> _currentDamage = new Dictionary<DamageType, int>();
|
||||
|
||||
[ViewVariables]
|
||||
public Dictionary<DamageType, List<DamageThreshold>> Thresholds = new Dictionary<DamageType, List<DamageThreshold>>();
|
||||
|
||||
public event EventHandler<DamageThresholdPassedEventArgs> DamageThresholdPassed;
|
||||
public event EventHandler<DamageEventArgs> Damaged;
|
||||
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
return new DamageComponentState(_currentDamage);
|
||||
}
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
serializer.DataField(this, x => Resistances, "resistances", ResistanceSet.DefaultResistanceSet);
|
||||
}
|
||||
|
||||
public bool IsDead()
|
||||
{
|
||||
var currentDamage = _currentDamage[DamageType.Total];
|
||||
foreach (var threshold in Thresholds[DamageType.Total])
|
||||
{
|
||||
if (threshold.Value <= currentDamage)
|
||||
{
|
||||
if (threshold.ThresholdType != ThresholdType.Death) continue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
InitializeDamageType(DamageType.Total);
|
||||
|
||||
foreach (var damagebehavior in Owner.GetAllComponents<IOnDamageBehavior>())
|
||||
{
|
||||
AddThresholdsFrom(damagebehavior);
|
||||
Damaged += damagebehavior.OnDamaged;
|
||||
}
|
||||
|
||||
RecalculateComponentThresholds();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void TakeDamage(DamageType damageType, int amount, IEntity source = null, IEntity sourceMob = null)
|
||||
{
|
||||
if (damageType == DamageType.Total)
|
||||
{
|
||||
foreach (DamageType e in Enum.GetValues(typeof(DamageType)))
|
||||
{
|
||||
if (e == damageType) continue;
|
||||
TakeDamage(e, amount, source, sourceMob);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
InitializeDamageType(damageType);
|
||||
|
||||
int oldValue = _currentDamage[damageType];
|
||||
int oldTotalValue = -1;
|
||||
|
||||
if (amount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
amount = Resistances.CalculateDamage(damageType, amount);
|
||||
_currentDamage[damageType] = Math.Max(0, _currentDamage[damageType] + amount);
|
||||
UpdateForDamageType(damageType, oldValue);
|
||||
|
||||
Damaged?.Invoke(this, new DamageEventArgs(damageType, amount, source, sourceMob));
|
||||
|
||||
if (Resistances.AppliesToTotal(damageType))
|
||||
{
|
||||
oldTotalValue = _currentDamage[DamageType.Total];
|
||||
_currentDamage[DamageType.Total] = Math.Max(0, _currentDamage[DamageType.Total] + amount);
|
||||
UpdateForDamageType(DamageType.Total, oldTotalValue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void TakeHealing(DamageType damageType, int amount, IEntity source = null, IEntity sourceMob = null)
|
||||
{
|
||||
if (damageType == DamageType.Total)
|
||||
{
|
||||
throw new ArgumentException("Cannot heal for DamageType.Total");
|
||||
}
|
||||
TakeDamage(damageType, -amount, source, sourceMob);
|
||||
}
|
||||
|
||||
public void HealAllDamage()
|
||||
{
|
||||
var values = Enum.GetValues(typeof(DamageType)).Cast<DamageType>();
|
||||
foreach (var damageType in values)
|
||||
{
|
||||
if (CurrentDamage.ContainsKey(damageType) && damageType != DamageType.Total)
|
||||
{
|
||||
TakeHealing(damageType, CurrentDamage[damageType]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateForDamageType(DamageType damageType, int oldValue)
|
||||
{
|
||||
int change = _currentDamage[damageType] - oldValue;
|
||||
|
||||
if (change == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int changeSign = Math.Sign(change);
|
||||
|
||||
foreach (var threshold in Thresholds[damageType])
|
||||
{
|
||||
var value = threshold.Value;
|
||||
if (((value * changeSign) > (oldValue * changeSign)) && ((value * changeSign) <= (_currentDamage[damageType] * changeSign)))
|
||||
{
|
||||
var excessDamage = change - value;
|
||||
var typeOfDamage = damageType;
|
||||
if (change - value < 0)
|
||||
{
|
||||
excessDamage = 0;
|
||||
}
|
||||
var args = new DamageThresholdPassedEventArgs(threshold, (changeSign > 0), excessDamage);
|
||||
DamageThresholdPassed?.Invoke(this, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RecalculateComponentThresholds()
|
||||
{
|
||||
foreach (IOnDamageBehavior onDamageBehaviorComponent in Owner.GetAllComponents<IOnDamageBehavior>())
|
||||
{
|
||||
AddThresholdsFrom(onDamageBehaviorComponent);
|
||||
}
|
||||
}
|
||||
|
||||
void AddThresholdsFrom(IOnDamageBehavior onDamageBehavior)
|
||||
{
|
||||
if (onDamageBehavior == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(onDamageBehavior));
|
||||
}
|
||||
|
||||
List<DamageThreshold> thresholds = onDamageBehavior.GetAllDamageThresholds();
|
||||
|
||||
if (thresholds == null)
|
||||
return;
|
||||
|
||||
foreach (DamageThreshold threshold in thresholds)
|
||||
{
|
||||
if (!Thresholds[threshold.DamageType].Contains(threshold))
|
||||
{
|
||||
Thresholds[threshold.DamageType].Add(threshold);
|
||||
}
|
||||
}
|
||||
|
||||
DamageThresholdPassed += onDamageBehavior.OnDamageThresholdPassed;
|
||||
}
|
||||
|
||||
void InitializeDamageType(DamageType damageType)
|
||||
{
|
||||
if (!_currentDamage.ContainsKey(damageType))
|
||||
{
|
||||
_currentDamage.Add(damageType, 0);
|
||||
Thresholds.Add(damageType, new List<DamageThreshold>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +1,49 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Server.Interfaces.GameObjects;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
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.Random;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Damage
|
||||
{
|
||||
/// <summary>
|
||||
/// Deletes the entity once a certain damage threshold has been reached.
|
||||
/// When attached to an <see cref="IEntity"/>, allows it to take damage and deletes it after taking enough damage.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class DestructibleComponent : Component, IOnDamageBehavior, IDestroyAct, IExAct
|
||||
[ComponentReference(typeof(IDamageableComponent))]
|
||||
public class DestructibleComponent : RuinableComponent, IDestroyAct
|
||||
{
|
||||
#pragma warning disable 649
|
||||
#pragma warning disable 649
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager;
|
||||
#pragma warning restore 649
|
||||
#pragma warning restore 649
|
||||
|
||||
protected ActSystem _actSystem;
|
||||
|
||||
protected string _spawnOnDestroy;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string Name => "Destructible";
|
||||
|
||||
/// <summary>
|
||||
/// Damage threshold calculated from the values
|
||||
/// given in the prototype declaration.
|
||||
/// Entity spawned upon destruction.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public DamageThreshold Threshold { get; private set; }
|
||||
public string SpawnOnDestroy => _spawnOnDestroy;
|
||||
|
||||
public DamageType damageType = DamageType.Total;
|
||||
public int damageValue = 0;
|
||||
public string spawnOnDestroy = "";
|
||||
public string destroySound = "";
|
||||
public bool destroyed = false;
|
||||
|
||||
ActSystem _actSystem;
|
||||
void IDestroyAct.OnDestroy(DestructionEventArgs eventArgs)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(_spawnOnDestroy) && eventArgs.IsSpawnWreck)
|
||||
{
|
||||
Owner.EntityManager.SpawnEntity(_spawnOnDestroy, Owner.Transform.GridPosition);
|
||||
}
|
||||
}
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(ref damageValue, "thresholdvalue", 100);
|
||||
serializer.DataField(ref damageType, "thresholdtype", DamageType.Total);
|
||||
serializer.DataField(ref spawnOnDestroy, "spawnondestroy", "");
|
||||
serializer.DataField(ref destroySound, "destroysound", "");
|
||||
serializer.DataField(ref _spawnOnDestroy, "spawnondestroy", string.Empty);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
@@ -58,57 +52,18 @@ namespace Content.Server.GameObjects.Components.Damage
|
||||
_actSystem = _entitySystemManager.GetEntitySystem<ActSystem>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
List<DamageThreshold> IOnDamageBehavior.GetAllDamageThresholds()
|
||||
{
|
||||
Threshold = new DamageThreshold(damageType, damageValue, ThresholdType.Destruction);
|
||||
return new List<DamageThreshold>() { Threshold };
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
void IOnDamageBehavior.OnDamageThresholdPassed(object obj, DamageThresholdPassedEventArgs e)
|
||||
protected override void DestructionBehavior()
|
||||
{
|
||||
if (e.Passed && e.DamageThreshold == Threshold && destroyed == false)
|
||||
if (!Owner.Deleted)
|
||||
{
|
||||
destroyed = true;
|
||||
var pos = Owner.Transform.GridPosition;
|
||||
_actSystem.HandleDestruction(Owner, true);
|
||||
if(destroySound != string.Empty)
|
||||
_actSystem.HandleDestruction(Owner,
|
||||
true); //This will call IDestroyAct.OnDestroy on this component (and all other components on this entity)
|
||||
if (DestroySound != string.Empty)
|
||||
{
|
||||
EntitySystem.Get<AudioSystem>().PlayAtCoords(destroySound, pos);
|
||||
EntitySystem.Get<AudioSystem>().PlayAtCoords(DestroySound, pos);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void IExAct.OnExplosion(ExplosionEventArgs eventArgs)
|
||||
{
|
||||
var prob = IoCManager.Resolve<IRobustRandom>();
|
||||
switch (eventArgs.Severity)
|
||||
{
|
||||
case ExplosionSeverity.Destruction:
|
||||
_actSystem.HandleDestruction(Owner, false);
|
||||
break;
|
||||
case ExplosionSeverity.Heavy:
|
||||
var spawnWreckOnHeavy = prob.Prob(0.5f);
|
||||
_actSystem.HandleDestruction(Owner, spawnWreckOnHeavy);
|
||||
break;
|
||||
case ExplosionSeverity.Light:
|
||||
if (prob.Prob(0.4f))
|
||||
_actSystem.HandleDestruction(Owner, true);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void IDestroyAct.OnDestroy(DestructionEventArgs eventArgs)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(spawnOnDestroy) && eventArgs.IsSpawnWreck)
|
||||
{
|
||||
Owner.EntityManager.SpawnEntity(spawnOnDestroy, Owner.Transform.GridPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Damage
|
||||
{
|
||||
/// <summary>
|
||||
/// Resistance set used by damageable objects.
|
||||
/// For each damage type, has a coefficient, damage reduction and "included in total" value.
|
||||
/// </summary>
|
||||
public class ResistanceSet : IExposeData
|
||||
{
|
||||
public static ResistanceSet DefaultResistanceSet = new ResistanceSet();
|
||||
|
||||
[ViewVariables]
|
||||
private readonly Dictionary<DamageType, ResistanceSetSettings> _resistances = new Dictionary<DamageType, ResistanceSetSettings>();
|
||||
|
||||
public ResistanceSet()
|
||||
{
|
||||
foreach (DamageType damageType in Enum.GetValues(typeof(DamageType)))
|
||||
{
|
||||
_resistances[damageType] = new ResistanceSetSettings();
|
||||
}
|
||||
}
|
||||
|
||||
public void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
foreach (DamageType damageType in Enum.GetValues(typeof(DamageType)))
|
||||
{
|
||||
var resistanceName = damageType.ToString().ToLower();
|
||||
serializer.DataReadFunction(resistanceName, new ResistanceSetSettings(), resistanceSetting =>
|
||||
{
|
||||
_resistances[damageType] = resistanceSetting;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjusts input damage with the resistance set values.
|
||||
/// </summary>
|
||||
/// <param name="damageType">Type of the damage.</param>
|
||||
/// <param name="amount">Incoming amount of the damage.</param>
|
||||
/// <returns>Damage adjusted by the resistance set.</returns>
|
||||
public int CalculateDamage(DamageType damageType, int amount)
|
||||
{
|
||||
if (amount > 0) //if it's damage, reduction applies
|
||||
{
|
||||
amount -= _resistances[damageType].DamageReduction;
|
||||
|
||||
if (amount <= 0)
|
||||
return 0;
|
||||
}
|
||||
|
||||
amount = (int)Math.Floor(amount * _resistances[damageType].Coefficient);
|
||||
|
||||
return amount;
|
||||
}
|
||||
|
||||
public bool AppliesToTotal(DamageType damageType)
|
||||
{
|
||||
//Damage that goes straight to total (for whatever reason) never applies twice
|
||||
|
||||
return damageType != DamageType.Total && _resistances[damageType].AppliesToTotal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Settings for a specific damage type in a resistance set.
|
||||
/// </summary>
|
||||
public class ResistanceSetSettings : IExposeData
|
||||
{
|
||||
public float Coefficient { get; private set; } = 1;
|
||||
public int DamageReduction { get; private set; } = 0;
|
||||
public bool AppliesToTotal { get; private set; } = true;
|
||||
|
||||
public void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
serializer.DataField(this, x => Coefficient, "coefficient", 1);
|
||||
serializer.DataField(this, x => DamageReduction, "damageReduction", 0);
|
||||
serializer.DataField(this, x => AppliesToTotal, "appliesToTotal", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using System.Collections.Generic;
|
||||
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.Serialization;
|
||||
|
||||
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
|
||||
{
|
||||
private DamageState _currentDamageState;
|
||||
|
||||
/// <summary>
|
||||
/// How much HP this component can sustain before triggering
|
||||
/// <see cref="PerformDestruction"/>.
|
||||
/// </summary>
|
||||
public int MaxHp { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sound played upon destruction.
|
||||
/// </summary>
|
||||
protected string DestroySound { get; private set; }
|
||||
|
||||
public override List<DamageState> SupportedDamageStates =>
|
||||
new List<DamageState> {DamageState.Alive, DamageState.Dead};
|
||||
|
||||
public override DamageState CurrentDamageState => _currentDamageState;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
HealthChangedEvent += OnHealthChanged;
|
||||
}
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(this, ruinable => ruinable.MaxHp, "maxHP", 100);
|
||||
serializer.DataField(this, ruinable => ruinable.DestroySound, "destroySound", string.Empty);
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
{
|
||||
base.OnRemove();
|
||||
HealthChangedEvent -= OnHealthChanged;
|
||||
}
|
||||
|
||||
private void OnHealthChanged(HealthChangedEventArgs e)
|
||||
{
|
||||
if (CurrentDamageState != DamageState.Dead && TotalDamage >= MaxHp)
|
||||
{
|
||||
PerformDestruction();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys the Owner <see cref="IEntity"/>, setting
|
||||
/// <see cref="IDamageableComponent.CurrentDamageState"/> to
|
||||
/// <see cref="DamageState.Dead"/>
|
||||
/// </summary>
|
||||
protected void PerformDestruction()
|
||||
{
|
||||
_currentDamageState = DamageState.Dead;
|
||||
|
||||
if (!Owner.Deleted && DestroySound != string.Empty)
|
||||
{
|
||||
var pos = Owner.Transform.GridPosition;
|
||||
EntitySystem.Get<AudioSystem>().PlayAtCoords(DestroySound, pos);
|
||||
}
|
||||
|
||||
DestructionBehavior();
|
||||
}
|
||||
|
||||
protected abstract void DestructionBehavior();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user