diff --git a/Content.Client/Content.Client.csproj b/Content.Client/Content.Client.csproj
index 01eac19f9b..2228239cdd 100644
--- a/Content.Client/Content.Client.csproj
+++ b/Content.Client/Content.Client.csproj
@@ -72,8 +72,12 @@
+
+
+
+
+
-
@@ -81,6 +85,8 @@
+
+
diff --git a/Content.Client/EntryPoint.cs b/Content.Client/EntryPoint.cs
index 556f6149bb..d3df4a3c74 100644
--- a/Content.Client/EntryPoint.cs
+++ b/Content.Client/EntryPoint.cs
@@ -1,4 +1,5 @@
using Content.Client.GameObjects;
+using Content.Client.GameObjects.Components.Actor;
using Content.Client.GameObjects.Components.Clothing;
using Content.Client.GameObjects.Components.Construction;
using Content.Client.GameObjects.Components.Power;
@@ -15,11 +16,13 @@ using SS14.Client;
using SS14.Client.Interfaces;
using SS14.Client.Interfaces.Graphics.Overlays;
using SS14.Client.Interfaces.Input;
+using SS14.Client.Player;
using SS14.Client.Utility;
using SS14.Shared.ContentPack;
using SS14.Shared.Interfaces.GameObjects;
using SS14.Shared.IoC;
using SS14.Shared.Prototypes;
+using System;
namespace Content.Client
{
@@ -34,7 +37,6 @@ namespace Content.Client
var prototypes = IoCManager.Resolve();
factory.RegisterIgnore("Interactable");
- factory.RegisterIgnore("Damageable");
factory.RegisterIgnore("Destructible");
factory.RegisterIgnore("Temperature");
factory.RegisterIgnore("PowerTransfer");
@@ -68,15 +70,22 @@ namespace Content.Client
factory.Register();
factory.Register();
factory.Register();
+ factory.Register();
factory.Register();
factory.Register();
+
factory.RegisterReference();
+ factory.Register();
+ factory.Register();
+
factory.RegisterIgnore("Construction");
factory.RegisterIgnore("Apc");
factory.RegisterIgnore("Door");
factory.RegisterIgnore("PoweredLight");
factory.RegisterIgnore("Smes");
+ factory.RegisterIgnore("Powercell");
+ factory.RegisterIgnore("HandheldLight");
prototypes.RegisterIgnore("material");
@@ -87,6 +96,43 @@ namespace Content.Client
IoCManager.BuildGraph();
IoCManager.Resolve().LoadParallax();
+ IoCManager.Resolve().PlayerJoinedServer += SubscribePlayerAttachmentEvents;
+ }
+
+ ///
+ /// Subscribe events to the player manager after the player manager is set up
+ ///
+ ///
+ ///
+ public void SubscribePlayerAttachmentEvents(object sender, EventArgs args)
+ {
+ IoCManager.Resolve().LocalPlayer.EntityAttached += AttachPlayerToEntity;
+ IoCManager.Resolve().LocalPlayer.EntityDetached += DetachPlayerFromEntity;
+ AttachPlayerToEntity(IoCManager.Resolve().LocalPlayer, EventArgs.Empty);
+ }
+
+ ///
+ /// Add the character interface master which combines all character interfaces into one window
+ ///
+ ///
+ ///
+ public void AttachPlayerToEntity(object sender, EventArgs args)
+ {
+ var localplayer = (LocalPlayer)sender;
+
+ localplayer.ControlledEntity?.AddComponent();
+ }
+
+ ///
+ /// Remove the character interface master from this entity now that we have detached ourselves from it
+ ///
+ ///
+ ///
+ public void DetachPlayerFromEntity(object sender, EventArgs args)
+ {
+ var localplayer = (LocalPlayer)sender;
+ //Wont work atm, controlled entity gets nulled before this event fires
+ localplayer.ControlledEntity?.RemoveComponent();
}
public override void PostInit()
diff --git a/Content.Client/GameObjects/Components/Actor/CharacterInterface.cs b/Content.Client/GameObjects/Components/Actor/CharacterInterface.cs
new file mode 100644
index 0000000000..90c3303c5a
--- /dev/null
+++ b/Content.Client/GameObjects/Components/Actor/CharacterInterface.cs
@@ -0,0 +1,108 @@
+using Content.Client.GameObjects.Components.Mobs;
+using Content.Shared.Input;
+using SS14.Client.Interfaces.Input;
+using SS14.Client.UserInterface.CustomControls;
+using SS14.Shared.GameObjects;
+using SS14.Shared.Input;
+using SS14.Shared.IoC;
+using SS14.Shared.Utility;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Content.Client.GameObjects.Components.Actor
+{
+ ///
+ /// A semi-abstract component which gets added to entities upon attachment and collects all character
+ /// user interfaces into a single window and keybind for the user
+ ///
+ public class CharacterInterface : Component
+ {
+ public override string Name => "Character Interface Component";
+
+ ///
+ /// Stored keybind to open the menu on keypress
+ ///
+ private InputCmdHandler _openMenuCmdHandler;
+
+ ///
+ /// Window to hold each of the character interfaces
+ ///
+ private SS14Window _window;
+
+ ///
+ /// Create the window with all character UIs and bind it to a keypress
+ ///
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ //Use all the character ui interfaced components to create the character window
+ var UIcomponents = Owner.GetAllComponents();
+ _window = new CharacterWindow(UIcomponents);
+
+ //Add to screen the window and hide it
+ _window.AddToScreen();
+ _window.Close();
+
+ //Toggle window visible/invisible on keypress
+ _openMenuCmdHandler = InputCmdHandler.FromDelegate(session => {
+ if (_window.Visible)
+ {
+ _window.Close();
+ }
+ else
+ {
+ _window.Open();
+ }
+ });
+
+ //Set keybind to open character menu
+ var inputMgr = IoCManager.Resolve();
+ inputMgr.SetInputCommand(ContentKeyFunctions.OpenCharacterMenu, _openMenuCmdHandler);
+ }
+
+ ///
+ /// Dispose of window and the keypress binding
+ ///
+ public override void OnRemove()
+ {
+ base.OnRemove();
+
+ _window.Dispose();
+ _window = null;
+
+ var inputMgr = IoCManager.Resolve();
+ inputMgr.SetInputCommand(ContentKeyFunctions.OpenCharacterMenu, null);
+ }
+
+ ///
+ /// A window that collects and shows all the individual character user interfaces
+ ///
+ public class CharacterWindow : SS14Window
+ {
+ protected override ResourcePath ScenePath => new ResourcePath("/Scenes/Mobs/CharacterWindow.tscn");
+
+ public CharacterWindow(IEnumerable windowcomponents)
+ {
+ //TODO: sort window components by priority of window component
+ foreach(var element in windowcomponents.OrderByDescending(x => x.Priority))
+ {
+ Contents.AddChild(element.Scene);
+ }
+
+ HideOnClose = true;
+ }
+ }
+ }
+
+ ///
+ /// Determines ordering of the character user interface, small values come sooner
+ ///
+ public enum UIPriority
+ {
+ First = 0,
+ Species = 100,
+ Inventory = 200,
+ Last = 99999
+ }
+}
diff --git a/Content.Client/GameObjects/Components/DamageableComponent.cs b/Content.Client/GameObjects/Components/DamageableComponent.cs
new file mode 100644
index 0000000000..70ab4811ce
--- /dev/null
+++ b/Content.Client/GameObjects/Components/DamageableComponent.cs
@@ -0,0 +1,31 @@
+using Content.Shared.GameObjects;
+using SS14.Shared.GameObjects;
+using SS14.Shared.Interfaces.GameObjects;
+using SS14.Shared.Interfaces.Network;
+using System.Collections.Generic;
+
+namespace Content.Client.GameObjects
+{
+ ///
+ /// Fuck I really hate doing this
+ /// TODO: make sure the client only gets damageable component on the clientside entity for its player mob
+ ///
+ public class DamageableComponent : SharedDamageableComponent
+ {
+ ///
+ public override string Name => "Damageable";
+
+ public Dictionary CurrentDamage = new Dictionary();
+
+ public override void HandleComponentState(ComponentState state)
+ {
+ base.HandleComponentState(state);
+
+ if(state is DamageComponentState)
+ {
+ var damagestate = (DamageComponentState)state;
+ CurrentDamage = damagestate.CurrentDamage;
+ }
+ }
+ }
+}
diff --git a/Content.Client/GameObjects/Components/Inventory/ClientInventoryComponent.cs b/Content.Client/GameObjects/Components/HUD/Inventory/ClientInventoryComponent.cs
similarity index 87%
rename from Content.Client/GameObjects/Components/Inventory/ClientInventoryComponent.cs
rename to Content.Client/GameObjects/Components/HUD/Inventory/ClientInventoryComponent.cs
index ab66cbee0b..1655160e98 100644
--- a/Content.Client/GameObjects/Components/Inventory/ClientInventoryComponent.cs
+++ b/Content.Client/GameObjects/Components/HUD/Inventory/ClientInventoryComponent.cs
@@ -1,15 +1,9 @@
using Content.Shared.GameObjects;
-using Content.Shared.Input;
-using SS14.Client.GameObjects;
using SS14.Client.Interfaces.GameObjects.Components;
-using SS14.Client.Interfaces.Input;
using SS14.Client.UserInterface;
using SS14.Client.UserInterface.Controls;
-using SS14.Client.UserInterface.CustomControls;
using SS14.Shared.GameObjects;
-using SS14.Shared.Input;
using SS14.Shared.Interfaces.GameObjects;
-using SS14.Shared.Interfaces.Network;
using SS14.Shared.IoC;
using SS14.Shared.Serialization;
using SS14.Shared.Utility;
@@ -20,21 +14,36 @@ using Content.Client.GameObjects.Components.Clothing;
using SS14.Shared.Interfaces.Reflection;
using static Content.Shared.GameObjects.Components.Inventory.EquipmentSlotDefines;
using static Content.Shared.GameObjects.SharedInventoryComponent.ClientInventoryMessage;
+using Content.Client.GameObjects.Components.Mobs;
+using Content.Client.GameObjects.Components.Actor;
namespace Content.Client.GameObjects
{
- public class ClientInventoryComponent : SharedInventoryComponent
+ ///
+ /// A character UI which shows items the user has equipped within his inventory
+ ///
+ public class ClientInventoryComponent : SharedInventoryComponent, ICharacterUI
{
private Dictionary _slots = new Dictionary();
+ ///
+ /// Holds the godot control for the inventory window
+ ///
private InventoryWindow _window;
+
private string _templateName = "HumanInventory"; //stored for serialization purposes
- private InputCmdHandler _openMenuCmdHandler;
+ ///
+ /// Inventory template after being loaded from instance creator and string name
+ ///
private Inventory _inventory;
private ISpriteComponent _sprite;
+ //Relevant interface implementation for the character UI controller
+ public Control Scene => _window;
+ public UIPriority Priority => UIPriority.Inventory;
+
public override void OnRemove()
{
base.OnRemove();
@@ -42,22 +51,17 @@ namespace Content.Client.GameObjects
_window.Dispose();
}
- public override void OnAdd()
- {
- base.OnAdd();
-
- _openMenuCmdHandler = InputCmdHandler.FromDelegate(session => { _window.AddToScreen(); _window.Open(); });
- }
-
public override void Initialize()
{
base.Initialize();
+ //Loads inventory template
var reflectionManager = IoCManager.Resolve();
var type = reflectionManager.LooseGetType(_templateName);
DebugTools.Assert(type != null);
_inventory = (Inventory)Activator.CreateInstance(type);
+ //Creates godot control class for inventory
_window = new InventoryWindow(this);
_window.CreateInventory(_inventory);
@@ -120,21 +124,6 @@ namespace Content.Client.GameObjects
}
}
- public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null, IComponent component = null)
- {
- var inputMgr = IoCManager.Resolve();
- switch (message)
- {
- case PlayerAttachedMsg _:
- inputMgr.SetInputCommand(ContentKeyFunctions.OpenCharacterMenu, _openMenuCmdHandler);
- break;
-
- case PlayerDetachedMsg _:
- inputMgr.SetInputCommand(ContentKeyFunctions.OpenCharacterMenu, null);
- break;
- }
- }
-
private void _setSlot(Slots slot, IEntity entity)
{
if (_sprite != null && entity.TryGetComponent(out ClothingComponent clothing))
@@ -178,7 +167,7 @@ namespace Content.Client.GameObjects
///
/// Temporary window to hold the basis for inventory hud
///
- private class InventoryWindow : SS14Window
+ private class InventoryWindow : Control
{
private int elements_x;
@@ -187,13 +176,11 @@ namespace Content.Client.GameObjects
private Dictionary InventorySlots = new Dictionary(); //ordered dictionary?
private ClientInventoryComponent InventoryComponent;
- protected override ResourcePath ScenePath => new ResourcePath("/Scenes/Inventory/HumanInventory.tscn");
+ protected override ResourcePath ScenePath => new ResourcePath("/Scenes/Mobs/HumanInventory.tscn");
public InventoryWindow(ClientInventoryComponent inventory)
{
InventoryComponent = inventory;
-
- HideOnClose = true;
}
///
@@ -203,7 +190,7 @@ namespace Content.Client.GameObjects
{
elements_x = inventory.Columns;
- GridContainer = (GridContainer)Contents.GetChild("PanelContainer").GetChild("CenterContainer").GetChild("GridContainer");
+ GridContainer = (GridContainer)GetChild("CenterContainer").GetChild("GridContainer");
GridContainer.Columns = elements_x;
IndexedSlots = new List(inventory.SlotMasks);
@@ -304,7 +291,7 @@ namespace Content.Client.GameObjects
public Slots Slot;
public EntityUid EntityUid;
- protected override ResourcePath ScenePath => new ResourcePath("/Scenes/Inventory/StorageSlot.tscn");
+ protected override ResourcePath ScenePath => new ResourcePath("/Scenes/Mobs/StorageSlot.tscn");
public InventoryButton(Slots slot)
{
diff --git a/Content.Client/GameObjects/Components/Mobs/ICharacterUI.cs b/Content.Client/GameObjects/Components/Mobs/ICharacterUI.cs
new file mode 100644
index 0000000000..e65e448110
--- /dev/null
+++ b/Content.Client/GameObjects/Components/Mobs/ICharacterUI.cs
@@ -0,0 +1,21 @@
+using Content.Client.GameObjects.Components.Actor;
+using SS14.Client.UserInterface;
+
+namespace Content.Client.GameObjects.Components.Mobs
+{
+ ///
+ /// An interface which is gathered to assemble the character window from multiple components
+ ///
+ public interface ICharacterUI
+ {
+ ///
+ /// The godot control which holds the character user interface to be included in the window
+ ///
+ Control Scene { get; }
+
+ ///
+ /// The order it will appear in the character UI, higher is lower
+ ///
+ UIPriority Priority { get; }
+ }
+}
diff --git a/Content.Client/GameObjects/Components/Mobs/SpeciesUI.cs b/Content.Client/GameObjects/Components/Mobs/SpeciesUI.cs
new file mode 100644
index 0000000000..a8f498a168
--- /dev/null
+++ b/Content.Client/GameObjects/Components/Mobs/SpeciesUI.cs
@@ -0,0 +1,160 @@
+using Content.Client.GameObjects.Components.Actor;
+using Content.Client.GameObjects.Components.Mobs;
+using Content.Client.Graphics.Overlays;
+using Content.Shared.GameObjects;
+using SS14.Client.GameObjects;
+using SS14.Client.Interfaces.Graphics.Overlays;
+using SS14.Client.Interfaces.ResourceManagement;
+using SS14.Client.Player;
+using SS14.Client.ResourceManagement;
+using SS14.Client.UserInterface;
+using SS14.Client.UserInterface.Controls;
+using SS14.Shared.GameObjects;
+using SS14.Shared.Interfaces.GameObjects;
+using SS14.Shared.Interfaces.Network;
+using SS14.Shared.IoC;
+using SS14.Shared.Log;
+using SS14.Shared.Utility;
+using System.Collections.Generic;
+
+namespace Content.Client.GameObjects
+{
+ ///
+ /// A character UI component which shows the current damage state of the mob (living/dead)
+ ///
+ public class SpeciesUI : Component, ICharacterUI
+ {
+ public override string Name => "Species";
+
+ public override uint? NetID => ContentNetIDs.SPECIES;
+
+ ///
+ /// Holds the godot control for the species window
+ ///
+ private SpeciesWindow _window;
+
+ ///
+ /// An enum representing the current state being applied to the user
+ ///
+ private ScreenEffects _currentEffect = ScreenEffects.None;
+
+ // Required dependencies
+ [Dependency] private readonly IOverlayManager _overlayManager;
+ [Dependency] private readonly IPlayerManager _playerManager;
+
+ //Relevant interface implementation for the character UI controller
+ public Control Scene => _window;
+ public UIPriority Priority => UIPriority.Species;
+
+ ///
+ /// Allows calculating if we need to act due to this component being controlled by the current mob
+ ///
+ private bool CurrentlyControlled => _playerManager.LocalPlayer.ControlledEntity == Owner;
+
+ ///
+ /// Holds the screen effects that can be applied mapped ot their relevant overlay
+ ///
+ private Dictionary EffectsDictionary;
+
+ public override void OnRemove()
+ {
+ base.OnRemove();
+
+ _window.Dispose();
+ }
+
+ public override void OnAdd()
+ {
+ base.OnAdd();
+
+ IoCManager.InjectDependencies(this);
+ _window = new SpeciesWindow();
+
+ EffectsDictionary = new Dictionary()
+ {
+ { ScreenEffects.CircleMask, new CircleMaskOverlay() },
+ { ScreenEffects.GradientCircleMask, new GradientCircleMask() }
+ };
+ }
+
+ public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null, IComponent component = null)
+ {
+ switch (message)
+ {
+ case HudStateChange msg:
+ if(CurrentlyControlled)
+ {
+ ChangeHudIcon(msg);
+ }
+ break;
+
+ case PlayerAttachedMsg _:
+ ApplyOverlay();
+ break;
+
+ case PlayerDetachedMsg _:
+ RemoveOverlay();
+ break;
+ }
+ }
+
+ private void ChangeHudIcon(HudStateChange changemessage)
+ {
+ _window.SetIcon(changemessage);
+ SetOverlay(changemessage);
+ }
+
+ private void SetOverlay(HudStateChange message)
+ {
+ RemoveOverlay();
+
+ _currentEffect = message.effect;
+
+ ApplyOverlay();
+ }
+
+ private void RemoveOverlay()
+ {
+ if (_currentEffect != ScreenEffects.None)
+ {
+ var appliedeffect = EffectsDictionary[_currentEffect];
+ _overlayManager.RemoveOverlay(nameof(appliedeffect));
+ }
+
+ _currentEffect = ScreenEffects.None;
+ }
+
+ private void ApplyOverlay()
+ {
+ if (_currentEffect != ScreenEffects.None)
+ {
+ _overlayManager.AddOverlay(EffectsDictionary[_currentEffect]);
+ }
+ }
+
+ private class SpeciesWindow : Control
+ {
+ private TextureRect _textureRect;
+
+ protected override ResourcePath ScenePath => new ResourcePath("/Scenes/Mobs/Species.tscn");
+
+ protected override void Initialize()
+ {
+ base.Initialize();
+
+ _textureRect = (TextureRect)GetChild("TextureRect");
+ }
+
+ public void SetIcon(HudStateChange changemessage)
+ {
+ if (!IoCManager.Resolve().TryGetResource(new ResourcePath("/Textures") / changemessage.StateSprite, out var newtexture))
+ {
+ Logger.Info("The Species Health Sprite {0} Does Not Exist", new ResourcePath("/Textures") / changemessage.StateSprite);
+ return;
+ }
+
+ _textureRect.Texture = newtexture;
+ }
+ }
+ }
+}
diff --git a/Content.Client/Graphics/Overlays/CircleMaskOverlay.cs b/Content.Client/Graphics/Overlays/CircleMaskOverlay.cs
new file mode 100644
index 0000000000..df36c995ce
--- /dev/null
+++ b/Content.Client/Graphics/Overlays/CircleMaskOverlay.cs
@@ -0,0 +1,32 @@
+using SS14.Client.Graphics.Drawing;
+using SS14.Client.Graphics.Overlays;
+using SS14.Client.Graphics.Shaders;
+using SS14.Client.Interfaces.Graphics.ClientEye;
+using SS14.Client.Interfaces.Graphics.Overlays;
+using SS14.Shared.IoC;
+using SS14.Shared.Maths;
+using SS14.Shared.Prototypes;
+
+namespace Content.Client.Graphics.Overlays
+{
+ public class CircleMaskOverlay : Overlay
+ {
+ [Dependency] private readonly IPrototypeManager _prototypeManager;
+ [Dependency] private readonly IEyeManager _eyeManager;
+
+ public override OverlaySpace Space => OverlaySpace.WorldSpace;
+
+ public CircleMaskOverlay() : base(nameof(CircleMaskOverlay))
+ {
+ IoCManager.InjectDependencies(this);
+ Shader = _prototypeManager.Index("circlemask").Instance();
+ }
+
+ protected override void Draw(DrawingHandle handle)
+ {
+ var worldHandle = (DrawingHandleWorld)handle;
+ var viewport = _eyeManager.GetWorldViewport();
+ worldHandle.DrawRect(viewport, Color.White);
+ }
+ }
+}
diff --git a/Content.Client/Graphics/Overlays/GradientCircleMask.cs b/Content.Client/Graphics/Overlays/GradientCircleMask.cs
new file mode 100644
index 0000000000..9897f06cbd
--- /dev/null
+++ b/Content.Client/Graphics/Overlays/GradientCircleMask.cs
@@ -0,0 +1,32 @@
+using SS14.Client.Graphics.Drawing;
+using SS14.Client.Graphics.Overlays;
+using SS14.Client.Graphics.Shaders;
+using SS14.Client.Interfaces.Graphics.ClientEye;
+using SS14.Client.Interfaces.Graphics.Overlays;
+using SS14.Shared.IoC;
+using SS14.Shared.Maths;
+using SS14.Shared.Prototypes;
+
+namespace Content.Client.Graphics.Overlays
+{
+ public class GradientCircleMask : Overlay
+ {
+ [Dependency] private readonly IPrototypeManager _prototypeManager;
+ [Dependency] private readonly IEyeManager _eyeManager;
+
+ public override OverlaySpace Space => OverlaySpace.WorldSpace;
+
+ public GradientCircleMask() : base(nameof(GradientCircleMask))
+ {
+ IoCManager.InjectDependencies(this);
+ Shader = _prototypeManager.Index("gradientcirclemask").Instance();
+ }
+
+ protected override void Draw(DrawingHandle handle)
+ {
+ var worldHandle = (DrawingHandleWorld)handle;
+ var viewport = _eyeManager.GetWorldViewport();
+ worldHandle.DrawRect(viewport, Color.White);
+ }
+ }
+}
diff --git a/Content.Server/Content.Server.csproj b/Content.Server/Content.Server.csproj
index 95f3b9189b..b51ba01b2f 100644
--- a/Content.Server/Content.Server.csproj
+++ b/Content.Server/Content.Server.csproj
@@ -66,6 +66,7 @@
+
@@ -79,7 +80,11 @@
+
+
+
+
@@ -96,6 +101,7 @@
+
diff --git a/Content.Server/EntryPoint.cs b/Content.Server/EntryPoint.cs
index 91e858cb8c..75ba452a70 100644
--- a/Content.Server/EntryPoint.cs
+++ b/Content.Server/EntryPoint.cs
@@ -115,6 +115,7 @@ namespace Content.Server
factory.RegisterIgnore("ConstructionGhost");
factory.Register();
+ factory.Register();
IoCManager.Register();
IoCManager.Register();
diff --git a/Content.Server/GameObjects/Components/Damage/DamageThreshold.cs b/Content.Server/GameObjects/Components/Damage/DamageThreshold.cs
new file mode 100644
index 0000000000..46e8a94a78
--- /dev/null
+++ b/Content.Server/GameObjects/Components/Damage/DamageThreshold.cs
@@ -0,0 +1,61 @@
+using Content.Shared.GameObjects;
+using System;
+
+namespace Content.Server.GameObjects
+{
+ ///
+ /// Triggers an event when values rise above or drop below this threshold
+ ///
+ 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 && this == (DamageThreshold)obj;
+ }
+ 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
+ }
+
+ public class DamageThresholdPassedEventArgs : EventArgs
+ {
+ public DamageThreshold DamageThreshold { get; }
+ public bool Passed { get; }
+
+ public DamageThresholdPassedEventArgs(DamageThreshold threshold, bool passed)
+ {
+ DamageThreshold = threshold;
+ Passed = passed;
+ }
+ }
+}
+
diff --git a/Content.Server/GameObjects/Components/Damage/DamageableComponent.cs b/Content.Server/GameObjects/Components/Damage/DamageableComponent.cs
index cc8d76d428..b65f77c4e2 100644
--- a/Content.Server/GameObjects/Components/Damage/DamageableComponent.cs
+++ b/Content.Server/GameObjects/Components/Damage/DamageableComponent.cs
@@ -18,14 +18,11 @@ namespace Content.Server.GameObjects
/// A component that handles receiving damage and healing,
/// as well as informing other components of it.
///
- public class DamageableComponent : Component, IDamageableComponent
+ public class DamageableComponent : SharedDamageableComponent, IDamageableComponent
{
///
public override string Name => "Damageable";
- ///
- public override uint? NetID => ContentNetIDs.DAMAGEABLE;
-
///
/// The resistance set of this object.
/// Affects receiving damage of various types.
@@ -33,11 +30,17 @@ namespace Content.Server.GameObjects
[ViewVariables]
public ResistanceSet Resistances { get; private set; }
- Dictionary CurrentDamage = new Dictionary();
- Dictionary> Thresholds = new Dictionary>();
+ public IReadOnlyDictionary CurrentDamage => _currentDamage;
+ private Dictionary _currentDamage = new Dictionary();
+
+ Dictionary> Thresholds = new Dictionary>();
public event EventHandler DamageThresholdPassed;
+ public override ComponentState GetComponentState()
+ {
+ return new DamageComponentState(_currentDamage);
+ }
public override void ExposeData(ObjectSerializer serializer)
{
@@ -55,9 +58,10 @@ namespace Content.Server.GameObjects
{
base.Initialize();
InitializeDamageType(DamageType.Total);
- if (Owner is IOnDamageBehavior damageBehavior)
+
+ foreach (var damagebehavior in Owner.GetAllComponents())
{
- AddThresholdsFrom(damageBehavior);
+ AddThresholdsFrom(damagebehavior);
}
RecalculateComponentThresholds();
@@ -72,7 +76,7 @@ namespace Content.Server.GameObjects
}
InitializeDamageType(damageType);
- int oldValue = CurrentDamage[damageType];
+ int oldValue = _currentDamage[damageType];
int oldTotalValue = -1;
if (amount == 0)
@@ -81,13 +85,13 @@ namespace Content.Server.GameObjects
}
amount = Resistances.CalculateDamage(damageType, amount);
- CurrentDamage[damageType] = Math.Max(0, CurrentDamage[damageType] + amount);
+ _currentDamage[damageType] = Math.Max(0, _currentDamage[damageType] + amount);
UpdateForDamageType(damageType, oldValue);
if (Resistances.AppliesToTotal(damageType))
{
- oldTotalValue = CurrentDamage[DamageType.Total];
- CurrentDamage[DamageType.Total] = Math.Max(0, CurrentDamage[DamageType.Total] + amount);
+ oldTotalValue = _currentDamage[DamageType.Total];
+ _currentDamage[DamageType.Total] = Math.Max(0, _currentDamage[DamageType.Total] + amount);
UpdateForDamageType(DamageType.Total, oldTotalValue);
}
}
@@ -104,7 +108,7 @@ namespace Content.Server.GameObjects
void UpdateForDamageType(DamageType damageType, int oldValue)
{
- int change = CurrentDamage[damageType] - oldValue;
+ int change = _currentDamage[damageType] - oldValue;
if (change == 0)
{
@@ -113,11 +117,12 @@ namespace Content.Server.GameObjects
int changeSign = Math.Sign(change);
- foreach (int value in Thresholds[damageType])
+ foreach (var threshold in Thresholds[damageType])
{
- if (((value * changeSign) > (oldValue * changeSign)) && ((value * changeSign) <= (CurrentDamage[damageType] * changeSign)))
+ var value = threshold.Value;
+ if (((value * changeSign) > (oldValue * changeSign)) && ((value * changeSign) <= (_currentDamage[damageType] * changeSign)))
{
- var args = new DamageThresholdPassedEventArgs(new DamageThreshold(damageType, value), (changeSign > 0));
+ var args = new DamageThresholdPassedEventArgs(threshold, (changeSign > 0));
DamageThresholdPassed?.Invoke(this, args);
}
}
@@ -142,62 +147,23 @@ namespace Content.Server.GameObjects
foreach (DamageThreshold threshold in thresholds)
{
- if (!Thresholds[threshold.DamageType].Contains(threshold.Value))
+ if (!Thresholds[threshold.DamageType].Contains(threshold))
{
- Thresholds[threshold.DamageType].Add(threshold.Value);
+ Thresholds[threshold.DamageType].Add(threshold);
}
}
+
+ DamageThresholdPassed += onDamageBehavior.OnDamageThresholdPassed;
}
void InitializeDamageType(DamageType damageType)
{
- if (!CurrentDamage.ContainsKey(damageType))
+ if (!_currentDamage.ContainsKey(damageType))
{
- CurrentDamage.Add(damageType, 0);
- Thresholds.Add(damageType, new List());
+ _currentDamage.Add(damageType, 0);
+ Thresholds.Add(damageType, new List());
}
}
}
-
- public struct DamageThreshold
- {
- public DamageType DamageType { get; }
- public int Value { get; }
-
- public DamageThreshold(DamageType damageType, int value)
- {
- DamageType = damageType;
- Value = value;
- }
-
- public override bool Equals(Object obj)
- {
- return obj is DamageThreshold && this == (DamageThreshold)obj;
- }
- 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 class DamageThresholdPassedEventArgs : EventArgs
- {
- public DamageThreshold DamageThreshold { get; }
- public bool Passed { get; }
-
- public DamageThresholdPassedEventArgs(DamageThreshold threshold, bool passed)
- {
- DamageThreshold = threshold;
- Passed = passed;
- }
- }
}
diff --git a/Content.Server/GameObjects/Components/Damage/DestructibleComponent.cs b/Content.Server/GameObjects/Components/Damage/DestructibleComponent.cs
index 7eef3e39f8..4045c4589c 100644
--- a/Content.Server/GameObjects/Components/Damage/DestructibleComponent.cs
+++ b/Content.Server/GameObjects/Components/Damage/DestructibleComponent.cs
@@ -43,18 +43,7 @@ namespace Content.Server.GameObjects
serializer.DataReadFunction("thresholdtype", DamageType.Total, type => damageType = type);
serializer.DataReadFunction("thresholdvalue", 0, val => damageValue = val);
- Threshold = new DamageThreshold(damageType, damageValue);
- }
- }
-
- ///
- public override void Initialize()
- {
- base.Initialize();
-
- if (Owner.TryGetComponent(out DamageableComponent damageable))
- {
- damageable.DamageThresholdPassed += OnDamageThresholdPassed;
+ Threshold = new DamageThreshold(damageType, damageValue, ThresholdType.Destruction);
}
}
diff --git a/Content.Server/GameObjects/Components/Damage/ResistanceSet.cs b/Content.Server/GameObjects/Components/Damage/ResistanceSet.cs
index dd4a860f66..f344c84cca 100644
--- a/Content.Server/GameObjects/Components/Damage/ResistanceSet.cs
+++ b/Content.Server/GameObjects/Components/Damage/ResistanceSet.cs
@@ -1,23 +1,9 @@
-using System;
+using Content.Shared.GameObjects;
+using System;
using System.Collections.Generic;
namespace Content.Server.GameObjects
{
- ///
- /// Damage types used in-game.
- /// Total should never be used directly - it's a derived value.
- ///
- public enum DamageType
- {
- Total,
- Brute,
- Heat,
- Cold,
- Acid,
- Toxic,
- Electric
- }
-
///
/// Resistance set used by damageable objects.
/// For each damage type, has a coefficient, damage reduction and "included in total" value.
diff --git a/Content.Server/GameObjects/Components/Mobs/DamageStates.cs b/Content.Server/GameObjects/Components/Mobs/DamageStates.cs
new file mode 100644
index 0000000000..3dc711531d
--- /dev/null
+++ b/Content.Server/GameObjects/Components/Mobs/DamageStates.cs
@@ -0,0 +1,104 @@
+using Content.Server.GameObjects.EntitySystems;
+using SS14.Server.GameObjects;
+using SS14.Shared.Interfaces.GameObjects;
+using SS14.Shared.Maths;
+
+namespace Content.Server.GameObjects
+{
+ ///
+ /// Defines the blocking effect of each damage state, and what effects to apply upon entering or exiting the state
+ ///
+ public interface DamageState : IActionBlocker
+ {
+ void EnterState(IEntity entity);
+
+ void ExitState(IEntity entity);
+ }
+
+ ///
+ /// Standard state that a species is at with no damage or negative effect
+ ///
+ public struct NormalState : DamageState
+ {
+ public void EnterState(IEntity entity){}
+
+ public void ExitState(IEntity entity){}
+
+ bool IActionBlocker.CanInteract()
+ {
+ return true;
+ }
+
+ bool IActionBlocker.CanMove()
+ {
+ return true;
+ }
+
+ bool IActionBlocker.CanUse()
+ {
+ return true;
+ }
+ }
+
+ ///
+ /// A state in which you are disabled from acting due to damage
+ ///
+ public struct CriticalState : DamageState
+ {
+ public void EnterState(IEntity entity) { }
+
+ public void ExitState(IEntity entity) { }
+
+ bool IActionBlocker.CanInteract()
+ {
+ return false;
+ }
+
+ bool IActionBlocker.CanMove()
+ {
+ return false;
+ }
+
+ bool IActionBlocker.CanUse()
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// A damage state which will allow ghosting out of mobs
+ ///
+ public struct DeadState : DamageState
+ {
+ public void EnterState(IEntity entity)
+ {
+ if(entity.TryGetComponent(out SpriteComponent sprite))
+ {
+ sprite.Rotation = sprite.Rotation + Angle.FromDegrees(90);
+ }
+ }
+
+ public void ExitState(IEntity entity)
+ {
+ if (entity.TryGetComponent(out SpriteComponent sprite))
+ {
+ sprite.Rotation = sprite.Rotation - Angle.FromDegrees(90);
+ }
+ }
+
+ bool IActionBlocker.CanInteract()
+ {
+ return false;
+ }
+
+ bool IActionBlocker.CanMove()
+ {
+ return false;
+ }
+
+ bool IActionBlocker.CanUse()
+ {
+ return false;
+ }
+ }
+}
diff --git a/Content.Server/GameObjects/Components/Mobs/DamageThresholdTemplates/DamageThresholdTemplates.cs b/Content.Server/GameObjects/Components/Mobs/DamageThresholdTemplates/DamageThresholdTemplates.cs
new file mode 100644
index 0000000000..b31e1edc77
--- /dev/null
+++ b/Content.Server/GameObjects/Components/Mobs/DamageThresholdTemplates/DamageThresholdTemplates.cs
@@ -0,0 +1,65 @@
+using System.Collections.Generic;
+using Content.Shared.GameObjects;
+
+namespace Content.Server.GameObjects
+{
+ ///
+ /// Defines the threshold values for each damage state for any kind of species
+ ///
+ public abstract class DamageTemplates
+ {
+ public abstract List HealthHudThresholds { get; }
+
+ ///
+ /// Changes the hud state when a threshold is reached
+ ///
+ ///
+ ///
+ ///
+ public abstract HudStateChange ChangeHudState(DamageableComponent damage);
+
+ //public abstract ResistanceSet resistanceset { get; }
+
+ ///
+ /// Shows allowed states, ordered by priority, closest to last value to have threshold reached is preferred
+ ///
+ public abstract List<(DamageType, int, ThresholdType)> AllowedStates { get; }
+
+ ///
+ /// 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
+ ///
+ public static Dictionary StateThresholdMap = new Dictionary()
+ {
+ { ThresholdType.None, new NormalState() },
+ { ThresholdType.Critical, new CriticalState() },
+ { ThresholdType.Death, new DeadState() }
+ };
+
+ public List DamageThresholds
+ {
+ get
+ {
+ List thresholds = new List();
+ 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;
+ }
+ }
+}
diff --git a/Content.Server/GameObjects/Components/Mobs/DamageThresholdTemplates/HumanTemplate.cs b/Content.Server/GameObjects/Components/Mobs/DamageThresholdTemplates/HumanTemplate.cs
new file mode 100644
index 0000000000..e1d903dd19
--- /dev/null
+++ b/Content.Server/GameObjects/Components/Mobs/DamageThresholdTemplates/HumanTemplate.cs
@@ -0,0 +1,66 @@
+using Content.Shared.GameObjects;
+using System.Collections.Generic;
+
+namespace Content.Server.GameObjects
+{
+ 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, ThresholdType.Critical),
+ (DamageType.Total, 300, ThresholdType.Death),
+ };
+
+ public override List HealthHudThresholds
+ {
+ get
+ {
+ List thresholds = new List();
+ 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
+ }
+ }
+
+ public override HudStateChange ChangeHudState(DamageableComponent damage)
+ {
+ ThresholdType healthstate = CalculateDamageState(damage);
+ switch (healthstate)
+ {
+ case ThresholdType.None:
+ var totaldamage = damage.CurrentDamage[DamageType.Total];
+ if (totaldamage > critvalue)
+ {
+ throw new System.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
+ return new HudStateChange()
+ {
+ StateSprite = "Mob/UI/Human/human" + modifier.ToString() + ".png",
+ effect = ScreenEffects.None
+ };
+ case ThresholdType.Critical:
+ return new HudStateChange()
+ {
+ StateSprite = "Mob/UI/Human/humancrit-0.png", //TODO: display as gif or alternate with -0 and -1 as frames
+ effect = ScreenEffects.GradientCircleMask
+ };
+ case ThresholdType.Death:
+ return new HudStateChange()
+ {
+ StateSprite = "Mob/UI/Human/humandead.png",
+ effect = ScreenEffects.CircleMask
+ };
+ default:
+ throw new System.InvalidOperationException();
+ }
+ }
+ }
+}
diff --git a/Content.Server/GameObjects/Components/Mobs/SpeciesComponent.cs b/Content.Server/GameObjects/Components/Mobs/SpeciesComponent.cs
new file mode 100644
index 0000000000..106f3e514c
--- /dev/null
+++ b/Content.Server/GameObjects/Components/Mobs/SpeciesComponent.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Collections.Generic;
+using Content.Server.GameObjects.EntitySystems;
+using Content.Server.Interfaces;
+using Content.Shared.GameObjects;
+using SS14.Server.GameObjects;
+using SS14.Shared.ContentPack;
+using SS14.Shared.GameObjects;
+using SS14.Shared.Interfaces.GameObjects;
+using SS14.Shared.Interfaces.Network;
+using SS14.Shared.Serialization;
+
+namespace Content.Server.GameObjects
+{
+ public class SpeciesComponent : Component, IActionBlocker, IOnDamageBehavior
+ {
+ public override string Name => "Species";
+
+ public override uint? NetID => ContentNetIDs.SPECIES;
+
+ ///
+ /// Damagestates are reached by reaching a certain damage threshold, they will block actions after being reached
+ ///
+ public DamageState CurrentDamageState { get; private set; } = new NormalState();
+
+ ///
+ /// Damage state enum for current health, set only via change damage state //TODO: SETTER
+ ///
+ private ThresholdType currentstate = ThresholdType.None;
+
+ ///
+ /// Holds the damage template which controls the threshold and resistance settings for this species type
+ ///
+ private DamageTemplates DamageTemplate;
+
+ ///
+ /// Variable for serialization
+ ///
+ private string templatename;
+
+ public override void ExposeData(ObjectSerializer serializer)
+ {
+ base.ExposeData(serializer);
+
+ serializer.DataField(ref templatename, "Template", "Human");
+
+ Type type = AppDomain.CurrentDomain.GetAssemblyByName("Content.Server").GetType("Content.Server.GameObjects." + templatename);
+ DamageTemplate = (DamageTemplates)Activator.CreateInstance(type);
+ }
+
+ public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null, IComponent component = null)
+ {
+ switch (message)
+ {
+ case PlayerAttachedMsg _:
+ var hudstatechange = DamageTemplate.ChangeHudState(Owner.GetComponent());
+ SendNetworkMessage(hudstatechange);
+ break;
+ }
+ }
+
+ bool IActionBlocker.CanMove()
+ {
+ return CurrentDamageState.CanMove();
+ }
+
+ bool IActionBlocker.CanInteract()
+ {
+ return CurrentDamageState.CanInteract();
+ }
+
+ bool IActionBlocker.CanUse()
+ {
+ return CurrentDamageState.CanUse();
+ }
+
+ List 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));
+ }
+
+ if (Owner.TryGetComponent(out BasicActorComponent actor)) //specifies if we have a client to update the hud for
+ {
+ var hudstatechange = DamageTemplate.ChangeHudState(damage);
+ SendNetworkMessage(hudstatechange);
+ }
+ }
+
+ private void ChangeDamageState(ThresholdType threshold)
+ {
+ if(threshold == currentstate)
+ {
+ return;
+ }
+
+ CurrentDamageState.ExitState(Owner);
+ CurrentDamageState = DamageTemplates.StateThresholdMap[threshold];
+ CurrentDamageState.EnterState(Owner);
+
+ currentstate = threshold;
+ }
+ }
+}
diff --git a/Content.Server/GameObjects/Components/Projectiles/ProjectileComponent.cs b/Content.Server/GameObjects/Components/Projectiles/ProjectileComponent.cs
index 77034b14a5..a1d752b018 100644
--- a/Content.Server/GameObjects/Components/Projectiles/ProjectileComponent.cs
+++ b/Content.Server/GameObjects/Components/Projectiles/ProjectileComponent.cs
@@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using YamlDotNet.RepresentationModel;
using SS14.Shared.Utility;
+using Content.Shared.GameObjects;
namespace Content.Server.GameObjects.Components.Projectiles
{
diff --git a/Content.Server/GameObjects/Components/Projectiles/ThrownItemComponent.cs b/Content.Server/GameObjects/Components/Projectiles/ThrownItemComponent.cs
index 54005c96cc..54d01e30c3 100644
--- a/Content.Server/GameObjects/Components/Projectiles/ThrownItemComponent.cs
+++ b/Content.Server/GameObjects/Components/Projectiles/ThrownItemComponent.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using Content.Server.GameObjects.Components.Projectiles;
+using Content.Shared.GameObjects;
using Content.Shared.Physics;
using SS14.Shared.Interfaces.GameObjects;
using SS14.Shared.Interfaces.GameObjects.Components;
diff --git a/Content.Server/GameObjects/Components/Weapon/Melee/MeleeWeaponComponent.cs b/Content.Server/GameObjects/Components/Weapon/Melee/MeleeWeaponComponent.cs
index a756c9cbf3..bfb20f080d 100644
--- a/Content.Server/GameObjects/Components/Weapon/Melee/MeleeWeaponComponent.cs
+++ b/Content.Server/GameObjects/Components/Weapon/Melee/MeleeWeaponComponent.cs
@@ -11,6 +11,7 @@ using SS14.Shared.Interfaces.Timing;
using SS14.Shared.GameObjects.EntitySystemMessages;
using SS14.Shared.Serialization;
using SS14.Shared.Interfaces.GameObjects.Components;
+using Content.Shared.GameObjects;
namespace Content.Server.GameObjects.Components.Weapon.Melee
{
diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Hitscan/HitscanWeaponComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Hitscan/HitscanWeaponComponent.cs
index 1d9025356d..1606f9eaa4 100644
--- a/Content.Server/GameObjects/Components/Weapon/Ranged/Hitscan/HitscanWeaponComponent.cs
+++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Hitscan/HitscanWeaponComponent.cs
@@ -1,4 +1,5 @@
-using SS14.Server.GameObjects.EntitySystems;
+using Content.Shared.GameObjects;
+using SS14.Server.GameObjects.EntitySystems;
using SS14.Shared.Audio;
using SS14.Shared.GameObjects.EntitySystemMessages;
using SS14.Shared.Interfaces.GameObjects;
@@ -9,6 +10,7 @@ using SS14.Shared.IoC;
using SS14.Shared.Map;
using SS14.Shared.Maths;
using SS14.Shared.Physics;
+using SS14.Shared.Serialization;
using System;
namespace Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan
@@ -18,7 +20,16 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan
private const float MaxLength = 20;
public override string Name => "HitscanWeapon";
- private const string SpriteName = "Objects/laser.png";
+ string Spritename = "Objects/laser.png";
+ int Damage = 10;
+
+ public override void ExposeData(ObjectSerializer serializer)
+ {
+ base.ExposeData(serializer);
+
+ serializer.DataField(ref Spritename, "sprite", "Objects/laser.png");
+ serializer.DataField(ref Damage, "damage", 10);
+ }
protected override void Fire(IEntity user, GridLocalCoordinates clicklocation)
{
@@ -37,7 +48,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan
{
if (ray.HitEntity != null && ray.HitEntity.TryGetComponent(out DamageableComponent damage))
{
- damage.TakeDamage(DamageType.Heat, 10);
+ damage.TakeDamage(DamageType.Heat, Damage);
}
}
@@ -48,7 +59,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan
var offset = angle.ToVec() * dist / 2;
var message = new EffectSystemMessage
{
- EffectSprite = SpriteName,
+ EffectSprite = Spritename,
Born = time,
DeathTime = time + TimeSpan.FromSeconds(1),
Size = new Vector2(dist, 1f),
diff --git a/Content.Server/GameObjects/EntitySystems/ActionBlockerSystem.cs b/Content.Server/GameObjects/EntitySystems/ActionBlockerSystem.cs
new file mode 100644
index 0000000000..087bc864ca
--- /dev/null
+++ b/Content.Server/GameObjects/EntitySystems/ActionBlockerSystem.cs
@@ -0,0 +1,47 @@
+using SS14.Shared.GameObjects.Systems;
+using SS14.Shared.Interfaces.GameObjects;
+
+namespace Content.Server.GameObjects.EntitySystems
+{
+ public interface IActionBlocker
+ {
+ bool CanMove();
+
+ bool CanInteract();
+
+ bool CanUse();
+ }
+
+ public class ActionBlockerSystem : EntitySystem
+ {
+ public static bool CanMove(IEntity entity)
+ {
+ bool canmove = true;
+ foreach(var actionblockercomponents in entity.GetAllComponents())
+ {
+ canmove &= actionblockercomponents.CanMove(); //sets var to false if false
+ }
+ return canmove;
+ }
+
+ public static bool CanInteract(IEntity entity)
+ {
+ bool caninteract = true;
+ foreach (var actionblockercomponents in entity.GetAllComponents())
+ {
+ caninteract &= actionblockercomponents.CanInteract();
+ }
+ return caninteract;
+ }
+
+ public static bool CanUse(IEntity entity)
+ {
+ bool canuse = true;
+ foreach (var actionblockercomponents in entity.GetAllComponents())
+ {
+ canuse &= actionblockercomponents.CanUse();
+ }
+ return canuse;
+ }
+ }
+}
diff --git a/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs b/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs
index 9a8b1faccf..8db802801e 100644
--- a/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs
+++ b/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs
@@ -178,10 +178,9 @@ namespace Content.Server.GameObjects.EntitySystems
var item = hands.GetActiveHand?.Owner;
- if (!MobCanInteract(player))
+ if (!ActionBlockerSystem.CanInteract(player))
return;
- //TODO: Mob status code that allows or rejects interactions based on current mob status
- //Check if client should be able to see that object to click on it in the first place, prevent using locaters by firing a laser or something
+ //TODO: Check if client should be able to see that object to click on it in the first place, prevent using locaters by firing a laser or something
//Clicked on empty space behavior, try using ranged attack
@@ -239,15 +238,6 @@ namespace Content.Server.GameObjects.EntitySystems
}
}
- ///
- /// TODO function for blocking activity based on mob status
- ///
- ///
- private static bool MobCanInteract(IEntity user)
- {
- return true; //Hook into future planned mob status system
- }
-
///
/// We didn't click on any entity, try doing an afterattack on the click location
///
@@ -324,7 +314,7 @@ namespace Content.Server.GameObjects.EntitySystems
///
public static void TryUseInteraction(IEntity user, IEntity used)
{
- if (user != null && used != null && MobCanInteract(user))
+ if (user != null && used != null && ActionBlockerSystem.CanUse(user))
{
UseInteraction(user, used);
}
diff --git a/Content.Server/Interfaces/GameObjects/Components/Damage/IDamageableComponent.cs b/Content.Server/Interfaces/GameObjects/Components/Damage/IDamageableComponent.cs
index a9021feb77..14f929f291 100644
--- a/Content.Server/Interfaces/GameObjects/Components/Damage/IDamageableComponent.cs
+++ b/Content.Server/Interfaces/GameObjects/Components/Damage/IDamageableComponent.cs
@@ -1,4 +1,5 @@
-using Content.Server.GameObjects;
+using Content.Server.GameObjects;
+using Content.Shared.GameObjects;
using SS14.Shared.Interfaces.GameObjects;
using System;
diff --git a/Content.Shared/Content.Shared.csproj b/Content.Shared/Content.Shared.csproj
index 9d2d3b124f..4f86c7a7fb 100644
--- a/Content.Shared/Content.Shared.csproj
+++ b/Content.Shared/Content.Shared.csproj
@@ -63,6 +63,7 @@
+
@@ -71,6 +72,7 @@
+
@@ -117,4 +119,4 @@
-
\ No newline at end of file
+
diff --git a/Content.Shared/GameObjects/Components/Damage/DamageableComponent.cs b/Content.Shared/GameObjects/Components/Damage/DamageableComponent.cs
new file mode 100644
index 0000000000..41ed371db9
--- /dev/null
+++ b/Content.Shared/GameObjects/Components/Damage/DamageableComponent.cs
@@ -0,0 +1,41 @@
+using SS14.Shared.GameObjects;
+using SS14.Shared.Serialization;
+using System;
+using System.Collections.Generic;
+
+namespace Content.Shared.GameObjects
+{
+ public abstract class SharedDamageableComponent : Component
+ {
+ public override string Name => "Damageable";
+ public sealed override uint? NetID => ContentNetIDs.DAMAGEABLE;
+ public sealed override Type StateType => typeof(DamageComponentState);
+ }
+
+ // The IDs of the items get synced over the network.
+ [Serializable, NetSerializable]
+ public class DamageComponentState : ComponentState
+ {
+ public Dictionary CurrentDamage = new Dictionary();
+
+ public DamageComponentState(Dictionary damage) : base(ContentNetIDs.DAMAGEABLE)
+ {
+ CurrentDamage = damage;
+ }
+ }
+
+ ///
+ /// Damage types used in-game.
+ /// Total should never be used directly - it's a derived value.
+ ///
+ public enum DamageType
+ {
+ Total,
+ Brute,
+ Heat,
+ Cold,
+ Acid,
+ Toxic,
+ Electric
+ }
+}
diff --git a/Content.Shared/GameObjects/ContentNetIDs.cs b/Content.Shared/GameObjects/ContentNetIDs.cs
index a0dfdeab31..bd544e218b 100644
--- a/Content.Shared/GameObjects/ContentNetIDs.cs
+++ b/Content.Shared/GameObjects/ContentNetIDs.cs
@@ -11,5 +11,6 @@
public const uint INVENTORY = 1006;
public const uint POWER_DEBUG_TOOL = 1007;
public const uint CONSTRUCTOR = 1008;
+ public const uint SPECIES = 1009;
}
}
diff --git a/Content.Shared/GameObjects/Messages/Mob/HealthHud.cs b/Content.Shared/GameObjects/Messages/Mob/HealthHud.cs
new file mode 100644
index 0000000000..b477d53a9e
--- /dev/null
+++ b/Content.Shared/GameObjects/Messages/Mob/HealthHud.cs
@@ -0,0 +1,28 @@
+using SS14.Shared.GameObjects;
+using SS14.Shared.Serialization;
+using System;
+
+namespace Content.Shared.GameObjects
+{
+ ///
+ /// Sends updates to the standard species health hud with the sprite to change the hud to
+ ///
+ [Serializable, NetSerializable]
+ public class HudStateChange : ComponentMessage
+ {
+ public string StateSprite;
+ public ScreenEffects effect;
+
+ public HudStateChange()
+ {
+ Directed = true;
+ }
+ }
+
+ public enum ScreenEffects
+ {
+ None,
+ CircleMask,
+ GradientCircleMask,
+ }
+}
diff --git a/Resources/Prototypes/Entities/Mobs.yml b/Resources/Prototypes/Entities/Mobs.yml
index 0b97100ae1..76641d5435 100644
--- a/Resources/Prototypes/Entities/Mobs.yml
+++ b/Resources/Prototypes/Entities/Mobs.yml
@@ -47,6 +47,10 @@
- type: Input
context: "human"
+
+ - type: Species
+ Template: Human
+ - type: Damageable
- type: Eye
zoom: 0.5, 0.5
diff --git a/Resources/Prototypes/Entities/Weapons.yml b/Resources/Prototypes/Entities/Weapons.yml
index 21ca07414d..0321e95a6b 100644
--- a/Resources/Prototypes/Entities/Weapons.yml
+++ b/Resources/Prototypes/Entities/Weapons.yml
@@ -11,6 +11,8 @@
sprite: Objects/laser_retro.rsi
state: 100
- type: HitscanWeapon
+ damage: 30
+ sprite: "Objects/laser.png"
- type: Item
Size: 24
sprite: Objects/laser_retro.rsi
diff --git a/Resources/Prototypes/Shaders/shaders.yml b/Resources/Prototypes/Shaders/shaders.yml
new file mode 100644
index 0000000000..f7920f1cd5
--- /dev/null
+++ b/Resources/Prototypes/Shaders/shaders.yml
@@ -0,0 +1,9 @@
+- type: shader
+ id: circlemask
+ kind: source
+ path: "/Shaders/circlemask.gdsl"
+
+- type: shader
+ id: gradientcirclemask
+ kind: source
+ path: "/Shaders/gradientcirclemask.gdsl"
\ No newline at end of file
diff --git a/Resources/Scenes/Mobs/CharacterWindow.tscn b/Resources/Scenes/Mobs/CharacterWindow.tscn
new file mode 100644
index 0000000000..a5020062fd
--- /dev/null
+++ b/Resources/Scenes/Mobs/CharacterWindow.tscn
@@ -0,0 +1,32 @@
+[gd_scene load_steps=2 format=2]
+
+[ext_resource path="res://Scenes/SS14Window/SS14Window.tscn" type="PackedScene" id=1]
+
+[node name="SS14Window" index="0" instance=ExtResource( 1 )]
+
+anchor_bottom = 1.0
+margin_left = 0.0
+margin_top = 0.0
+margin_right = 240.0
+margin_bottom = 0.0
+rect_clip_content = false
+_sections_unfolded = [ "Anchor", "Margin" ]
+
+[node name="Contents" parent="." index="0"]
+
+rect_clip_content = false
+
+[node name="Header" parent="." index="1"]
+
+rect_clip_content = false
+
+[node name="Header Text" parent="Header" index="0"]
+
+rect_clip_content = false
+text = "Character Info"
+
+[node name="CloseButton" parent="Header" index="1"]
+
+rect_clip_content = false
+
+
diff --git a/Resources/Scenes/Inventory/HumanInventory.tscn b/Resources/Scenes/Mobs/HumanInventory.tscn
similarity index 53%
rename from Resources/Scenes/Inventory/HumanInventory.tscn
rename to Resources/Scenes/Mobs/HumanInventory.tscn
index 03adb5012f..53b8f6ad8b 100644
--- a/Resources/Scenes/Inventory/HumanInventory.tscn
+++ b/Resources/Scenes/Mobs/HumanInventory.tscn
@@ -1,22 +1,12 @@
-[gd_scene load_steps=2 format=2]
+[gd_scene format=2]
-[ext_resource path="res://Scenes/SS14Window/SS14Window.tscn" type="PackedScene" id=1]
-
-[node name="SS14Window" index="0" instance=ExtResource( 1 )]
-
-margin_right = 403.0
-rect_clip_content = false
-
-[node name="Contents" parent="." index="0"]
-
-rect_clip_content = false
-
-[node name="PanelContainer" type="PanelContainer" parent="Contents" index="0"]
+[node name="PanelContainer" type="PanelContainer"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 1.0
-anchor_bottom = 1.0
+anchor_bottom = 0.0
+margin_bottom = 400.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 0
@@ -25,7 +15,7 @@ size_flags_horizontal = 3
size_flags_vertical = 3
_sections_unfolded = [ "Anchor", "Grow Direction", "Margin", "Mouse", "Rect", "Size Flags", "Theme" ]
-[node name="CenterContainer" type="CenterContainer" parent="Contents/PanelContainer" index="0"]
+[node name="CenterContainer" type="CenterContainer" parent="." index="0"]
anchor_left = 0.0
anchor_top = 0.0
@@ -33,8 +23,8 @@ anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 7.0
margin_top = 7.0
-margin_right = 276.0
-margin_bottom = 424.0
+margin_right = 1017.0
+margin_bottom = 393.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 0
@@ -44,16 +34,16 @@ size_flags_vertical = 1
use_top_left = false
_sections_unfolded = [ "Anchor", "Focus", "Grow Direction", "Hint", "Margin", "Mouse", "Rect", "Size Flags", "Theme" ]
-[node name="GridContainer" type="GridContainer" parent="Contents/PanelContainer/CenterContainer" index="0"]
+[node name="GridContainer" type="GridContainer" parent="CenterContainer" index="0"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
-margin_left = 134.0
-margin_top = 208.0
-margin_right = 134.0
-margin_bottom = 208.0
+margin_left = 505.0
+margin_top = 193.0
+margin_right = 505.0
+margin_bottom = 193.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 0
@@ -63,17 +53,4 @@ size_flags_vertical = 1
columns = 3
_sections_unfolded = [ "Anchor", "Focus", "Grow Direction", "Hint", "Margin", "Material", "Mouse", "Rect", "Size Flags", "Theme", "custom_constants" ]
-[node name="Header" parent="." index="1"]
-
-rect_clip_content = false
-
-[node name="Header Text" parent="Header" index="0"]
-
-rect_clip_content = false
-text = "I dont how to not use a window"
-
-[node name="CloseButton" parent="Header" index="1"]
-
-rect_clip_content = false
-
diff --git a/Resources/Scenes/Mobs/Species.tscn b/Resources/Scenes/Mobs/Species.tscn
new file mode 100644
index 0000000000..7f7b70f0d6
--- /dev/null
+++ b/Resources/Scenes/Mobs/Species.tscn
@@ -0,0 +1,34 @@
+[gd_scene format=2]
+
+[node name="Control" type="Control" index="0"]
+
+anchor_left = 0.0
+anchor_top = 0.0
+anchor_right = 1.0
+anchor_bottom = 0.0
+margin_bottom = 64.0
+rect_pivot_offset = Vector2( 0, 0 )
+rect_clip_content = false
+mouse_filter = 0
+mouse_default_cursor_shape = 0
+size_flags_horizontal = 1
+size_flags_vertical = 1
+_sections_unfolded = [ "Anchor", "Grow Direction", "Margin", "Rect" ]
+
+[node name="TextureRect" type="TextureRect" parent="." index="0"]
+
+anchor_left = 0.0
+anchor_top = 0.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+rect_pivot_offset = Vector2( 0, 0 )
+rect_clip_content = false
+mouse_filter = 1
+mouse_default_cursor_shape = 0
+size_flags_horizontal = 1
+size_flags_vertical = 1
+expand = true
+stretch_mode = 6
+_sections_unfolded = [ "Anchor", "Grow Direction", "Margin" ]
+
+
diff --git a/Resources/Scenes/Inventory/StorageSlot.tres b/Resources/Scenes/Mobs/StorageSlot.tres
similarity index 100%
rename from Resources/Scenes/Inventory/StorageSlot.tres
rename to Resources/Scenes/Mobs/StorageSlot.tres
diff --git a/Resources/Scenes/Inventory/StorageSlot.tscn b/Resources/Scenes/Mobs/StorageSlot.tscn
similarity index 100%
rename from Resources/Scenes/Inventory/StorageSlot.tscn
rename to Resources/Scenes/Mobs/StorageSlot.tscn
diff --git a/Resources/Shaders/circlemask.gdsl b/Resources/Shaders/circlemask.gdsl
new file mode 100644
index 0000000000..c29fd7657c
--- /dev/null
+++ b/Resources/Shaders/circlemask.gdsl
@@ -0,0 +1,16 @@
+shader_type canvas_item;
+
+float circle(in vec2 _st, in float _radius){
+ vec2 dist = _st-vec2(0.5);
+ return smoothstep(_radius-(_radius*0.01),
+ _radius+(_radius*0.01),
+ dot(dist,dist)*4.0);
+}
+
+void fragment(){
+ vec2 st = FRAGCOORD.xy*SCREEN_PIXEL_SIZE.xy;
+
+ COLOR = texture(TEXTURE, UV);
+ COLOR.rgb = vec3(0);
+ COLOR.a = circle(st,0.05);
+}
diff --git a/Resources/Shaders/gradientcirclemask.gdsl b/Resources/Shaders/gradientcirclemask.gdsl
new file mode 100644
index 0000000000..e5c662ff06
--- /dev/null
+++ b/Resources/Shaders/gradientcirclemask.gdsl
@@ -0,0 +1,10 @@
+shader_type canvas_item;
+uniform float percentagedistanceshow = 0.1;
+uniform float gradientfalloffwidth = 0.2;
+
+void fragment() {
+ float dist = distance(UV, vec2(0.5, 0.5));
+ float fromdiskdist = dist - percentagedistanceshow;
+ COLOR.a = max(0, min(1.0, 2.0*dist/percentagedistanceshow - 2.0));
+ COLOR.rgb = vec3(1,1,1) - vec3(fromdiskdist,fromdiskdist,fromdiskdist)/gradientfalloffwidth;
+}
diff --git a/Resources/Textures/Mob/UI/Human/human0.png b/Resources/Textures/Mob/UI/Human/human0.png
new file mode 100644
index 0000000000..ca94b41e6e
Binary files /dev/null and b/Resources/Textures/Mob/UI/Human/human0.png differ
diff --git a/Resources/Textures/Mob/UI/Human/human1.png b/Resources/Textures/Mob/UI/Human/human1.png
new file mode 100644
index 0000000000..68b189bc6a
Binary files /dev/null and b/Resources/Textures/Mob/UI/Human/human1.png differ
diff --git a/Resources/Textures/Mob/UI/Human/human2.png b/Resources/Textures/Mob/UI/Human/human2.png
new file mode 100644
index 0000000000..ad2acb4ad0
Binary files /dev/null and b/Resources/Textures/Mob/UI/Human/human2.png differ
diff --git a/Resources/Textures/Mob/UI/Human/human3.png b/Resources/Textures/Mob/UI/Human/human3.png
new file mode 100644
index 0000000000..0888dd79a7
Binary files /dev/null and b/Resources/Textures/Mob/UI/Human/human3.png differ
diff --git a/Resources/Textures/Mob/UI/Human/human4.png b/Resources/Textures/Mob/UI/Human/human4.png
new file mode 100644
index 0000000000..8eed659e97
Binary files /dev/null and b/Resources/Textures/Mob/UI/Human/human4.png differ
diff --git a/Resources/Textures/Mob/UI/Human/human5.png b/Resources/Textures/Mob/UI/Human/human5.png
new file mode 100644
index 0000000000..8b4e8a4b9e
Binary files /dev/null and b/Resources/Textures/Mob/UI/Human/human5.png differ
diff --git a/Resources/Textures/Mob/UI/Human/human6-0.png b/Resources/Textures/Mob/UI/Human/human6-0.png
new file mode 100644
index 0000000000..05f0233a73
Binary files /dev/null and b/Resources/Textures/Mob/UI/Human/human6-0.png differ
diff --git a/Resources/Textures/Mob/UI/Human/human6-1.png b/Resources/Textures/Mob/UI/Human/human6-1.png
new file mode 100644
index 0000000000..3eda7aaf1d
Binary files /dev/null and b/Resources/Textures/Mob/UI/Human/human6-1.png differ
diff --git a/Resources/Textures/Mob/UI/Human/humancrit-0.png b/Resources/Textures/Mob/UI/Human/humancrit-0.png
new file mode 100644
index 0000000000..252d22c750
Binary files /dev/null and b/Resources/Textures/Mob/UI/Human/humancrit-0.png differ
diff --git a/Resources/Textures/Mob/UI/Human/humancrit-1.png b/Resources/Textures/Mob/UI/Human/humancrit-1.png
new file mode 100644
index 0000000000..c3102a638c
Binary files /dev/null and b/Resources/Textures/Mob/UI/Human/humancrit-1.png differ
diff --git a/Resources/Textures/Mob/UI/Human/humandead.png b/Resources/Textures/Mob/UI/Human/humandead.png
new file mode 100644
index 0000000000..75e6fb07af
Binary files /dev/null and b/Resources/Textures/Mob/UI/Human/humandead.png differ
diff --git a/engine b/engine
index c76f791799..4d9a243685 160000
--- a/engine
+++ b/engine
@@ -1 +1 @@
-Subproject commit c76f7917996891cbb9f0ded168beac3e0eb2a619
+Subproject commit 4d9a24368561e2e26c12cc1bb7fad7f543818d98