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,275 +0,0 @@
|
||||
using Content.Server.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Mobs
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the blocking effect of each damage state, and what effects to apply upon entering or exiting the state
|
||||
/// </summary>
|
||||
public interface IDamageState : IActionBlocker
|
||||
{
|
||||
void EnterState(IEntity entity);
|
||||
|
||||
void ExitState(IEntity entity);
|
||||
|
||||
bool IsConscious { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Standard state that a species is at with no damage or negative effect
|
||||
/// </summary>
|
||||
public struct NormalState : IDamageState
|
||||
{
|
||||
public void EnterState(IEntity entity)
|
||||
{
|
||||
entity.TryGetComponent(out AppearanceComponent appearanceComponent);
|
||||
appearanceComponent?.SetData(DamageStateVisuals.State, DamageStateVisualData.Normal);
|
||||
}
|
||||
|
||||
public void ExitState(IEntity entity)
|
||||
{
|
||||
}
|
||||
|
||||
public bool IsConscious => true;
|
||||
|
||||
bool IActionBlocker.CanInteract()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanMove()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUse()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanThrow()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanSpeak()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanDrop()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanPickup()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEmote()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanAttack()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEquip()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUnequip()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanChangeDirection()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A state in which you are disabled from acting due to damage
|
||||
/// </summary>
|
||||
public struct CriticalState : IDamageState
|
||||
{
|
||||
public void EnterState(IEntity entity)
|
||||
{
|
||||
if(entity.TryGetComponent(out StunnableComponent stun))
|
||||
stun.CancelAll();
|
||||
|
||||
entity.TryGetComponent(out AppearanceComponent appearanceComponent);
|
||||
appearanceComponent?.SetData(DamageStateVisuals.State, DamageStateVisualData.Crit);
|
||||
StandingStateHelper.Down(entity);
|
||||
}
|
||||
|
||||
public void ExitState(IEntity entity)
|
||||
{
|
||||
StandingStateHelper.Standing(entity);
|
||||
}
|
||||
|
||||
public bool IsConscious => false;
|
||||
|
||||
bool IActionBlocker.CanInteract()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanMove()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUse()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanThrow()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanSpeak()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanDrop()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanPickup()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEmote()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanAttack()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEquip()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUnequip()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanChangeDirection()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A damage state which will allow ghosting out of mobs
|
||||
/// </summary>
|
||||
public struct DeadState : IDamageState
|
||||
{
|
||||
public void EnterState(IEntity entity)
|
||||
{
|
||||
if(entity.TryGetComponent(out StunnableComponent stun))
|
||||
stun.CancelAll();
|
||||
|
||||
StandingStateHelper.Down(entity);
|
||||
entity.TryGetComponent(out AppearanceComponent appearanceComponent);
|
||||
appearanceComponent?.SetData(DamageStateVisuals.State, DamageStateVisualData.Dead);
|
||||
|
||||
if (entity.TryGetComponent(out ICollidableComponent collidable))
|
||||
{
|
||||
collidable.CanCollide = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void ExitState(IEntity entity)
|
||||
{
|
||||
StandingStateHelper.Standing(entity);
|
||||
|
||||
if (entity.TryGetComponent(out ICollidableComponent collidable))
|
||||
{
|
||||
collidable.CanCollide = true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsConscious => false;
|
||||
|
||||
bool IActionBlocker.CanInteract()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanMove()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUse()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanThrow()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanSpeak()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanDrop()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanPickup()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEmote()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanAttack()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEquip()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUnequip()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanChangeDirection()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Mobs.DamageThresholdTemplates
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the threshold values for each damage state for any kind of species
|
||||
/// </summary>
|
||||
public abstract class DamageTemplates
|
||||
{
|
||||
public abstract List<DamageThreshold> HealthHudThresholds { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Changes the hud state when a threshold is reached
|
||||
/// </summary>
|
||||
/// <param name="damage"></param>
|
||||
/// <returns></returns>
|
||||
public abstract void ChangeHudState(DamageableComponent damage);
|
||||
|
||||
//public abstract ResistanceSet resistanceset { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Shows allowed states, ordered by priority, closest to last value to have threshold reached is preferred
|
||||
/// </summary>
|
||||
public abstract List<(DamageType, int, ThresholdType)> AllowedStates { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Map of ALL POSSIBLE damage states to the threshold enum value that will trigger them, normal state wont be triggered by this value but is a default that is fell back onto
|
||||
/// </summary>
|
||||
public static Dictionary<ThresholdType, IDamageState> StateThresholdMap = new Dictionary<ThresholdType, IDamageState>()
|
||||
{
|
||||
{ ThresholdType.None, new NormalState() },
|
||||
{ ThresholdType.Critical, new CriticalState() },
|
||||
{ ThresholdType.Death, new DeadState() }
|
||||
};
|
||||
|
||||
public List<DamageThreshold> DamageThresholds
|
||||
{
|
||||
get
|
||||
{
|
||||
List<DamageThreshold> thresholds = new List<DamageThreshold>();
|
||||
foreach (var element in AllowedStates)
|
||||
{
|
||||
thresholds.Add(new DamageThreshold(element.Item1, element.Item2, element.Item3));
|
||||
}
|
||||
return thresholds;
|
||||
}
|
||||
}
|
||||
|
||||
public ThresholdType CalculateDamageState(DamageableComponent damage)
|
||||
{
|
||||
ThresholdType healthstate = ThresholdType.None;
|
||||
foreach(var element in AllowedStates)
|
||||
{
|
||||
if(damage.CurrentDamage[element.Item1] >= element.Item2)
|
||||
{
|
||||
healthstate = element.Item3;
|
||||
}
|
||||
}
|
||||
|
||||
return healthstate;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Mobs.DamageThresholdTemplates
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class Human : DamageTemplates
|
||||
{
|
||||
int critvalue = 200;
|
||||
int normalstates = 6;
|
||||
//string startsprite = "human0";
|
||||
|
||||
public override List<(DamageType, int, ThresholdType)> AllowedStates => new List<(DamageType, int, ThresholdType)>()
|
||||
{
|
||||
(DamageType.Total, critvalue-1, ThresholdType.None),
|
||||
(DamageType.Total, critvalue, ThresholdType.Critical),
|
||||
(DamageType.Total, 300, ThresholdType.Death),
|
||||
};
|
||||
|
||||
public override List<DamageThreshold> HealthHudThresholds
|
||||
{
|
||||
get
|
||||
{
|
||||
List<DamageThreshold> thresholds = new List<DamageThreshold>();
|
||||
thresholds.Add(new DamageThreshold(DamageType.Total, 1, ThresholdType.HUDUpdate));
|
||||
for (var i = 1; i <= normalstates; i++)
|
||||
{
|
||||
thresholds.Add(new DamageThreshold(DamageType.Total, i * critvalue / normalstates, ThresholdType.HUDUpdate));
|
||||
}
|
||||
return thresholds; //we don't need to respecify the state damage thresholds since we'll update hud on damage state changes as well
|
||||
}
|
||||
}
|
||||
|
||||
// for shared string dict, since we don't define these anywhere in content
|
||||
[UsedImplicitly]
|
||||
public static readonly string[] _humanStatusImages =
|
||||
{
|
||||
"/Textures/Interface/StatusEffects/Human/human0.png",
|
||||
"/Textures/Interface/StatusEffects/Human/human1.png",
|
||||
"/Textures/Interface/StatusEffects/Human/human2.png",
|
||||
"/Textures/Interface/StatusEffects/Human/human3.png",
|
||||
"/Textures/Interface/StatusEffects/Human/human4.png",
|
||||
"/Textures/Interface/StatusEffects/Human/human5.png",
|
||||
"/Textures/Interface/StatusEffects/Human/human6-0.png",
|
||||
"/Textures/Interface/StatusEffects/Human/human6-1.png",
|
||||
"/Textures/Interface/StatusEffects/Human/humancrit-0.png",
|
||||
"/Textures/Interface/StatusEffects/Human/humancrit-1.png",
|
||||
"/Textures/Interface/StatusEffects/Human/humandead.png",
|
||||
};
|
||||
|
||||
public override void ChangeHudState(DamageableComponent damage)
|
||||
{
|
||||
ThresholdType healthstate = CalculateDamageState(damage);
|
||||
damage.Owner.TryGetComponent(out ServerStatusEffectsComponent statusEffectsComponent);
|
||||
damage.Owner.TryGetComponent(out ServerOverlayEffectsComponent overlayComponent);
|
||||
switch (healthstate)
|
||||
{
|
||||
case ThresholdType.None:
|
||||
var totaldamage = damage.CurrentDamage[DamageType.Total];
|
||||
if (totaldamage > critvalue)
|
||||
{
|
||||
throw new InvalidOperationException(); //these should all be below the crit value, possibly going over multiple thresholds at once?
|
||||
}
|
||||
var modifier = totaldamage / (critvalue / normalstates); //integer division floors towards zero
|
||||
statusEffectsComponent?.ChangeStatusEffectIcon(StatusEffect.Health,
|
||||
"/Textures/Interface/StatusEffects/Human/human" + modifier + ".png");
|
||||
|
||||
overlayComponent?.RemoveOverlay(SharedOverlayID.GradientCircleMaskOverlay);
|
||||
overlayComponent?.RemoveOverlay(SharedOverlayID.CircleMaskOverlay);
|
||||
|
||||
return;
|
||||
case ThresholdType.Critical:
|
||||
statusEffectsComponent?.ChangeStatusEffectIcon(
|
||||
StatusEffect.Health,
|
||||
"/Textures/Interface/StatusEffects/Human/humancrit-0.png");
|
||||
overlayComponent?.AddOverlay(SharedOverlayID.GradientCircleMaskOverlay);
|
||||
overlayComponent?.RemoveOverlay(SharedOverlayID.CircleMaskOverlay);
|
||||
|
||||
return;
|
||||
case ThresholdType.Death:
|
||||
statusEffectsComponent?.ChangeStatusEffectIcon(
|
||||
StatusEffect.Health,
|
||||
"/Textures/Interface/StatusEffects/Human/humandead.png");
|
||||
overlayComponent?.RemoveOverlay(SharedOverlayID.GradientCircleMaskOverlay);
|
||||
overlayComponent?.AddOverlay(SharedOverlayID.CircleMaskOverlay);
|
||||
|
||||
return;
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,9 @@ namespace Content.Server.GameObjects.Components.Mobs
|
||||
|
||||
public int GetHeatResistance()
|
||||
{
|
||||
if (Owner.GetComponent<InventoryComponent>().TryGetSlotItem(EquipmentSlotDefines.Slots.GLOVES, itemComponent: out ClothingComponent gloves)
|
||||
| Owner.TryGetComponent(out SpeciesComponent speciesComponent))
|
||||
if (Owner.GetComponent<InventoryComponent>().TryGetSlotItem(EquipmentSlotDefines.Slots.GLOVES, itemComponent: out ClothingComponent gloves))
|
||||
{
|
||||
return Math.Max(gloves?.HeatResistance ?? int.MinValue, speciesComponent?.HeatResistance ?? int.MinValue);
|
||||
return gloves?.HeatResistance ?? int.MinValue;
|
||||
}
|
||||
return int.MinValue;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Content.Server.GameObjects.Components.Observer;
|
||||
using Content.Server.Interfaces.GameTicking;
|
||||
using Content.Server.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
@@ -126,8 +127,8 @@ namespace Content.Server.GameObjects.Components.Mobs
|
||||
}
|
||||
|
||||
var dead =
|
||||
Owner.TryGetComponent<SpeciesComponent>(out var species) &&
|
||||
species.CurrentDamageState is DeadState;
|
||||
Owner.TryGetComponent<IDamageableComponent>(out var damageable) &&
|
||||
damageable.CurrentDamageState == DamageState.Dead;
|
||||
|
||||
if (!HasMind)
|
||||
{
|
||||
|
||||
488
Content.Server/GameObjects/Components/Mobs/MobStateManager.cs
Normal file
488
Content.Server/GameObjects/Components/Mobs/MobStateManager.cs
Normal file
@@ -0,0 +1,488 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects.Components.Body;
|
||||
using Content.Server.GameObjects.Components.Damage;
|
||||
using Content.Server.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Mobs
|
||||
{
|
||||
/// <summary>
|
||||
/// When attacked to an <see cref="IDamageableComponent"/>, this component will handle critical and death behaviors
|
||||
/// for mobs.
|
||||
/// Additionally, it handles sending effects to clients (such as blur effect for unconsciousness) and managing the
|
||||
/// health HUD.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
internal class MobStateManagerComponent : Component, IOnHealthChangedBehavior, IActionBlocker
|
||||
{
|
||||
private readonly Dictionary<DamageState, IMobState> _behavior = new Dictionary<DamageState, IMobState>
|
||||
{
|
||||
{DamageState.Alive, new NormalState()},
|
||||
{DamageState.Critical, new CriticalState()},
|
||||
{DamageState.Dead, new DeadState()}
|
||||
};
|
||||
|
||||
public override string Name => "MobStateManager";
|
||||
|
||||
private DamageState _currentDamageState;
|
||||
|
||||
public IMobState CurrentMobState { get; private set; } = new NormalState();
|
||||
|
||||
bool IActionBlocker.CanInteract()
|
||||
{
|
||||
return CurrentMobState.CanInteract();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanMove()
|
||||
{
|
||||
return CurrentMobState.CanMove();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUse()
|
||||
{
|
||||
return CurrentMobState.CanUse();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanThrow()
|
||||
{
|
||||
return CurrentMobState.CanThrow();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanSpeak()
|
||||
{
|
||||
return CurrentMobState.CanSpeak();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanDrop()
|
||||
{
|
||||
return CurrentMobState.CanDrop();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanPickup()
|
||||
{
|
||||
return CurrentMobState.CanPickup();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEmote()
|
||||
{
|
||||
return CurrentMobState.CanEmote();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanAttack()
|
||||
{
|
||||
return CurrentMobState.CanAttack();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEquip()
|
||||
{
|
||||
return CurrentMobState.CanEquip();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUnequip()
|
||||
{
|
||||
return CurrentMobState.CanUnequip();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanChangeDirection()
|
||||
{
|
||||
return CurrentMobState.CanChangeDirection();
|
||||
}
|
||||
|
||||
public void OnHealthChanged(HealthChangedEventArgs e)
|
||||
{
|
||||
if (e.Damageable.CurrentDamageState != _currentDamageState)
|
||||
{
|
||||
_currentDamageState = e.Damageable.CurrentDamageState;
|
||||
CurrentMobState.ExitState(Owner);
|
||||
CurrentMobState = _behavior[_currentDamageState];
|
||||
CurrentMobState.EnterState(Owner);
|
||||
}
|
||||
|
||||
CurrentMobState.UpdateState(Owner);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_currentDamageState = DamageState.Alive;
|
||||
CurrentMobState = _behavior[_currentDamageState];
|
||||
CurrentMobState.EnterState(Owner);
|
||||
CurrentMobState.UpdateState(Owner);
|
||||
}
|
||||
|
||||
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 ServerStatusEffectsComponent status))
|
||||
{
|
||||
status.RemoveStatusEffect(StatusEffect.Health);
|
||||
}
|
||||
|
||||
if (Owner.TryGetComponent(out ServerOverlayEffectsComponent overlay))
|
||||
{
|
||||
overlay.ClearOverlays();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the blocking effects of an associated <see cref="DamageState"/>
|
||||
/// (i.e. Normal, Critical, Dead) and what effects to apply upon entering or
|
||||
/// exiting the state.
|
||||
/// </summary>
|
||||
public interface IMobState : IActionBlocker
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when this state is entered.
|
||||
/// </summary>
|
||||
void EnterState(IEntity entity);
|
||||
|
||||
/// <summary>
|
||||
/// Called when this state is left for a different state.
|
||||
/// </summary>
|
||||
void ExitState(IEntity entity);
|
||||
|
||||
/// <summary>
|
||||
/// Called when this state is updated.
|
||||
/// </summary>
|
||||
void UpdateState(IEntity entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The standard state an entity is in; no negative effects.
|
||||
/// </summary>
|
||||
public struct NormalState : IMobState
|
||||
{
|
||||
public void EnterState(IEntity entity)
|
||||
{
|
||||
UpdateState(entity);
|
||||
}
|
||||
|
||||
public void ExitState(IEntity entity) { }
|
||||
|
||||
public void UpdateState(IEntity entity)
|
||||
{
|
||||
if (!entity.TryGetComponent(out ServerStatusEffectsComponent status))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!entity.TryGetComponent(out IDamageableComponent damageable))
|
||||
{
|
||||
status.ChangeStatusEffectIcon(StatusEffect.Health,
|
||||
"/Textures/Interface/StatusEffects/Human/human0.png");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO
|
||||
switch (damageable)
|
||||
{
|
||||
case RuinableComponent ruinable:
|
||||
{
|
||||
var modifier = (int) (ruinable.TotalDamage / (ruinable.MaxHp / 7f));
|
||||
|
||||
status.ChangeStatusEffectIcon(StatusEffect.Health,
|
||||
"/Textures/Interface/StatusEffects/Human/human" + modifier + ".png");
|
||||
|
||||
break;
|
||||
}
|
||||
case BodyManagerComponent body:
|
||||
{
|
||||
// TODO: Declare body max normal damage (currently 100)
|
||||
var modifier = (int) (body.TotalDamage / (100f / 7f));
|
||||
|
||||
status.ChangeStatusEffectIcon(StatusEffect.Health,
|
||||
"/Textures/Interface/StatusEffects/Human/human" + modifier + ".png");
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
status.ChangeStatusEffectIcon(StatusEffect.Health,
|
||||
"/Textures/Interface/StatusEffects/Human/human0.png");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanInteract()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanMove()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUse()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanThrow()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanSpeak()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanDrop()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanPickup()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEmote()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanAttack()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEquip()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUnequip()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanChangeDirection()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A state in which an entity is disabled from acting due to sufficient damage (considered unconscious).
|
||||
/// </summary>
|
||||
public struct CriticalState : IMobState
|
||||
{
|
||||
public void EnterState(IEntity entity)
|
||||
{
|
||||
if (entity.TryGetComponent(out ServerStatusEffectsComponent status))
|
||||
{
|
||||
status.ChangeStatusEffectIcon(StatusEffect.Health,
|
||||
"/Textures/Interface/StatusEffects/Human/humancrit-0.png"); //Todo: combine humancrit-0 and humancrit-1 into a gif and display it
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlay))
|
||||
{
|
||||
overlay.AddOverlay(SharedOverlayID.GradientCircleMaskOverlay);
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out StunnableComponent stun))
|
||||
{
|
||||
stun.CancelAll();
|
||||
}
|
||||
|
||||
StandingStateHelper.Down(entity);
|
||||
}
|
||||
|
||||
public void ExitState(IEntity entity)
|
||||
{
|
||||
StandingStateHelper.Standing(entity);
|
||||
|
||||
if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlay))
|
||||
{
|
||||
overlay.ClearOverlays();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateState(IEntity entity)
|
||||
{
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanInteract()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanMove()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUse()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanThrow()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanSpeak()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanDrop()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanPickup()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEmote()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanAttack()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEquip()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUnequip()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanChangeDirection()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The state representing a dead entity; allows for ghosting.
|
||||
/// </summary>
|
||||
public struct DeadState : IMobState
|
||||
{
|
||||
public void EnterState(IEntity entity)
|
||||
{
|
||||
if (entity.TryGetComponent(out ServerStatusEffectsComponent status))
|
||||
{
|
||||
status.ChangeStatusEffectIcon(StatusEffect.Health,
|
||||
"/Textures/Interface/StatusEffects/Human/humandead.png");
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlayComponent))
|
||||
{
|
||||
overlayComponent.AddOverlay(SharedOverlayID.CircleMaskOverlay);
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out StunnableComponent stun))
|
||||
{
|
||||
stun.CancelAll();
|
||||
}
|
||||
|
||||
StandingStateHelper.Down(entity);
|
||||
|
||||
if (entity.TryGetComponent(out CollidableComponent collidable))
|
||||
{
|
||||
collidable.CanCollide = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void ExitState(IEntity entity)
|
||||
{
|
||||
StandingStateHelper.Standing(entity);
|
||||
|
||||
if (entity.TryGetComponent(out CollidableComponent collidable))
|
||||
{
|
||||
collidable.CanCollide = true;
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlay))
|
||||
{
|
||||
overlay.ClearOverlays();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateState(IEntity entity)
|
||||
{
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanInteract()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanMove()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUse()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanThrow()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanSpeak()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanDrop()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanPickup()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEmote()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanAttack()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEquip()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUnequip()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanChangeDirection()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,247 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects.Components.Damage;
|
||||
using Content.Server.GameObjects.Components.Mobs.DamageThresholdTemplates;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Server.Interfaces.GameObjects;
|
||||
using Content.Server.Observer;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Movement;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Mobs
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedSpeciesComponent))]
|
||||
public class SpeciesComponent : SharedSpeciesComponent, IActionBlocker, IOnDamageBehavior, IExAct, IRelayMoveInput
|
||||
{
|
||||
/// <summary>
|
||||
/// Damagestates are reached by reaching a certain damage threshold, they will block actions after being reached
|
||||
/// </summary>
|
||||
public IDamageState CurrentDamageState { get; private set; } = new NormalState();
|
||||
|
||||
/// <summary>
|
||||
/// Damage state enum for current health, set only via change damage state //TODO: SETTER
|
||||
/// </summary>
|
||||
private ThresholdType currentstate = ThresholdType.None;
|
||||
|
||||
/// <summary>
|
||||
/// Holds the damage template which controls the threshold and resistance settings for this species type
|
||||
/// </summary>
|
||||
public DamageTemplates DamageTemplate { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Variable for serialization
|
||||
/// </summary>
|
||||
private string templatename;
|
||||
|
||||
private int _heatResistance;
|
||||
public int HeatResistance => _heatResistance;
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(ref templatename, "Template", "Human");
|
||||
|
||||
var type = typeof(SpeciesComponent).Assembly.GetType("Content.Server.GameObjects.Components.Mobs.DamageThresholdTemplates." + templatename);
|
||||
DamageTemplate = (DamageTemplates) Activator.CreateInstance(type);
|
||||
serializer.DataFieldCached(ref _heatResistance, "HeatResistance", 323);
|
||||
}
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, IComponent component)
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
|
||||
switch (message)
|
||||
{
|
||||
case PlayerAttachedMsg _:
|
||||
if (CanReceiveStatusEffect(Owner)) {
|
||||
DamageableComponent damage = Owner.GetComponent<DamageableComponent>();
|
||||
DamageTemplate.ChangeHudState(damage);
|
||||
}
|
||||
break;
|
||||
case PlayerDetachedMsg _:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
{
|
||||
base.OnRemove();
|
||||
Owner.TryGetComponent(out ServerStatusEffectsComponent statusEffectsComponent);
|
||||
statusEffectsComponent?.RemoveStatusEffect(StatusEffect.Health);
|
||||
|
||||
Owner.TryGetComponent(out ServerOverlayEffectsComponent overlayEffectsComponent);
|
||||
overlayEffectsComponent?.ClearOverlays();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanMove()
|
||||
{
|
||||
return CurrentDamageState.CanMove();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanInteract()
|
||||
{
|
||||
return CurrentDamageState.CanInteract();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUse()
|
||||
{
|
||||
return CurrentDamageState.CanUse();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanThrow()
|
||||
{
|
||||
return CurrentDamageState.CanThrow();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanSpeak()
|
||||
{
|
||||
return CurrentDamageState.CanSpeak();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanDrop()
|
||||
{
|
||||
return CurrentDamageState.CanDrop();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanPickup()
|
||||
{
|
||||
return CurrentDamageState.CanPickup();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEmote()
|
||||
{
|
||||
return CurrentDamageState.CanEmote();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanAttack()
|
||||
{
|
||||
return CurrentDamageState.CanAttack();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEquip()
|
||||
{
|
||||
return CurrentDamageState.CanEquip();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUnequip()
|
||||
{
|
||||
return CurrentDamageState.CanUnequip();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanChangeDirection()
|
||||
{
|
||||
return CurrentDamageState.CanChangeDirection();
|
||||
}
|
||||
|
||||
List<DamageThreshold> IOnDamageBehavior.GetAllDamageThresholds()
|
||||
{
|
||||
var thresholdlist = DamageTemplate.DamageThresholds;
|
||||
thresholdlist.AddRange(DamageTemplate.HealthHudThresholds);
|
||||
return thresholdlist;
|
||||
}
|
||||
|
||||
void IOnDamageBehavior.OnDamageThresholdPassed(object damageable, DamageThresholdPassedEventArgs e)
|
||||
{
|
||||
DamageableComponent damage = (DamageableComponent) damageable;
|
||||
|
||||
if (e.DamageThreshold.ThresholdType != ThresholdType.HUDUpdate)
|
||||
{
|
||||
ChangeDamageState(DamageTemplate.CalculateDamageState(damage));
|
||||
}
|
||||
|
||||
//specifies if we have a client to update the hud for
|
||||
if (CanReceiveStatusEffect(Owner))
|
||||
{
|
||||
DamageTemplate.ChangeHudState(damage);
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanReceiveStatusEffect(IEntity user)
|
||||
{
|
||||
if (!user.HasComponent<ServerStatusEffectsComponent>() &&
|
||||
!user.HasComponent<ServerOverlayEffectsComponent>())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (user.HasComponent<DamageableComponent>())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ChangeDamageState(ThresholdType threshold)
|
||||
{
|
||||
if (threshold == currentstate)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentDamageState.ExitState(Owner);
|
||||
CurrentDamageState = DamageTemplates.StateThresholdMap[threshold];
|
||||
CurrentDamageState.EnterState(Owner);
|
||||
|
||||
currentstate = threshold;
|
||||
|
||||
var toRaise = new MobDamageStateChangedMessage(this);
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, toRaise);
|
||||
}
|
||||
|
||||
void IExAct.OnExplosion(ExplosionEventArgs eventArgs)
|
||||
{
|
||||
var burnDamage = 0;
|
||||
var bruteDamage = 0;
|
||||
switch(eventArgs.Severity)
|
||||
{
|
||||
case ExplosionSeverity.Destruction:
|
||||
bruteDamage += 250;
|
||||
burnDamage += 250;
|
||||
break;
|
||||
case ExplosionSeverity.Heavy:
|
||||
bruteDamage += 60;
|
||||
burnDamage += 60;
|
||||
break;
|
||||
case ExplosionSeverity.Light:
|
||||
bruteDamage += 30;
|
||||
break;
|
||||
}
|
||||
Owner.GetComponent<DamageableComponent>().TakeDamage(DamageType.Brute, bruteDamage, null);
|
||||
Owner.GetComponent<DamageableComponent>().TakeDamage(DamageType.Heat, burnDamage, null);
|
||||
}
|
||||
|
||||
void IRelayMoveInput.MoveInputPressed(ICommonSession session)
|
||||
{
|
||||
if (CurrentDamageState is DeadState)
|
||||
{
|
||||
new Ghost().Execute(null, (IPlayerSession) session, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fired when <see cref="SpeciesComponent.CurrentDamageState"/> changes.
|
||||
/// </summary>
|
||||
public sealed class MobDamageStateChangedMessage : EntitySystemMessage
|
||||
{
|
||||
public MobDamageStateChangedMessage(SpeciesComponent species)
|
||||
{
|
||||
Species = species;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The species component that was changed.
|
||||
/// </summary>
|
||||
public SpeciesComponent Species { get; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user