Merge remote-tracking branch 'upstream/master' into 20-10-30-admins

This commit is contained in:
Pieter-Jan Briers
2020-11-10 16:59:17 +01:00
473 changed files with 5588 additions and 3584 deletions

View File

@@ -0,0 +1,124 @@
using System.Collections.Generic;
using System.Linq;
using Content.Shared.Prototypes.Kitchen;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Prototypes;
namespace Content.Shared.Alert
{
/// <summary>
/// Provides access to all configured alerts. Ability to encode/decode a given state
/// to an int.
/// </summary>
public class AlertManager
{
[Dependency]
private readonly IPrototypeManager _prototypeManager = default!;
private AlertPrototype[] _orderedAlerts;
private Dictionary<AlertType, byte> _typeToIndex;
public void Initialize()
{
// order by type value so we can map between the id and an integer index and use
// the index for compact alert change messages
_orderedAlerts =
_prototypeManager.EnumeratePrototypes<AlertPrototype>()
.OrderBy(prototype => prototype.AlertType).ToArray();
_typeToIndex = new Dictionary<AlertType, byte>();
for (var i = 0; i < _orderedAlerts.Length; i++)
{
if (i > byte.MaxValue)
{
Logger.ErrorS("alert", "too many alerts for byte encoding ({0})! encoding will need" +
" to be changed to use a ushort rather than byte", _typeToIndex.Count);
break;
}
if (!_typeToIndex.TryAdd(_orderedAlerts[i].AlertType, (byte) i))
{
Logger.ErrorS("alert",
"Found alert with duplicate id {0}", _orderedAlerts[i].AlertType);
}
}
}
/// <summary>
/// Tries to get the alert of the indicated type
/// </summary>
/// <returns>true if found</returns>
public bool TryGet(AlertType alertType, out AlertPrototype alert)
{
if (_typeToIndex.TryGetValue(alertType, out var idx))
{
alert = _orderedAlerts[idx];
return true;
}
alert = null;
return false;
}
/// <summary>
/// Tries to get the alert of the indicated type along with its encoding
/// </summary>
/// <returns>true if found</returns>
public bool TryGetWithEncoded(AlertType alertType, out AlertPrototype alert, out byte encoded)
{
if (_typeToIndex.TryGetValue(alertType, out var idx))
{
alert = _orderedAlerts[idx];
encoded = (byte) idx;
return true;
}
alert = null;
encoded = 0;
return false;
}
/// <summary>
/// Tries to get the compact encoded representation of this alert
/// </summary>
/// <returns>true if successful</returns>
public bool TryEncode(AlertPrototype alert, out byte encoded)
{
return TryEncode(alert.AlertType, out encoded);
}
/// <summary>
/// Tries to get the compact encoded representation of the alert with
/// the indicated id
/// </summary>
/// <returns>true if successful</returns>
public bool TryEncode(AlertType alertType, out byte encoded)
{
if (_typeToIndex.TryGetValue(alertType, out var idx))
{
encoded = idx;
return true;
}
encoded = 0;
return false;
}
/// <summary>
/// Tries to get the alert from the encoded representation
/// </summary>
/// <returns>true if successful</returns>
public bool TryDecode(byte encodedAlert, out AlertPrototype alert)
{
if (encodedAlert >= _orderedAlerts.Length)
{
alert = null;
return false;
}
alert = _orderedAlerts[encodedAlert];
return true;
}
}
}

View File

@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
namespace Content.Shared.Alert
{
/// <summary>
/// Defines the order of alerts so they show up in a consistent order.
/// </summary>
[Prototype("alertOrder")]
public class AlertOrderPrototype : IPrototype, IComparer<AlertPrototype>
{
private Dictionary<AlertType, int> _typeToIdx = new Dictionary<AlertType, int>();
private Dictionary<AlertCategory, int> _categoryToIdx = new Dictionary<AlertCategory, int>();
public void LoadFrom(YamlMappingNode mapping)
{
if (!mapping.TryGetNode("order", out YamlSequenceNode orderMapping)) return;
int i = 0;
foreach (var entryYaml in orderMapping)
{
var orderEntry = (YamlMappingNode) entryYaml;
var serializer = YamlObjectSerializer.NewReader(orderEntry);
if (serializer.TryReadDataField("category", out AlertCategory alertCategory))
{
_categoryToIdx[alertCategory] = i++;
}
else if (serializer.TryReadDataField("alertType", out AlertType alertType))
{
_typeToIdx[alertType] = i++;
}
}
}
private int GetOrderIndex(AlertPrototype alert)
{
if (_typeToIdx.TryGetValue(alert.AlertType, out var idx))
{
return idx;
}
if (alert.Category != null &&
_categoryToIdx.TryGetValue((AlertCategory) alert.Category, out idx))
{
return idx;
}
return -1;
}
public int Compare(AlertPrototype x, AlertPrototype y)
{
if ((x == null) && (y == null)) return 0;
if (x == null) return 1;
if (y == null) return -1;
var idx = GetOrderIndex(x);
var idy = GetOrderIndex(y);
if (idx == -1 && idy == -1)
{
// break ties by type value
return x.AlertType - y.AlertType;
}
if (idx == -1) return 1;
if (idy == -1) return -1;
var result = idx - idy;
// not strictly necessary (we don't care about ones that go at the same index)
// but it makes the sort stable
if (result == 0)
{
// break ties by type value
return x.AlertType - y.AlertType;
}
return result;
}
}
}

View File

@@ -0,0 +1,189 @@
using System;
using Robust.Shared.Log;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using YamlDotNet.RepresentationModel;
namespace Content.Shared.Alert
{
/// <summary>
/// An alert popup with associated icon, tooltip, and other data.
/// </summary>
[Prototype("alert")]
public class AlertPrototype : IPrototype
{
/// <summary>
/// Type of alert, no 2 alert prototypes should have the same one.
/// </summary>
public AlertType AlertType { get; private set; }
/// <summary>
/// Path to the icon (png) to show in alert bar. If severity levels are supported,
/// this should be the path to the icon without the severity number
/// (i.e. hot.png if there is hot1.png and hot2.png). Use <see cref="GetIconPath"/>
/// to get the correct icon path for a particular severity level.
/// </summary>
[ViewVariables]
public string IconPath { get; private set; }
/// <summary>
/// Name to show in tooltip window. Accepts formatting.
/// </summary>
public FormattedMessage Name { get; private set; }
/// <summary>
/// Description to show in tooltip window. Accepts formatting.
/// </summary>
public FormattedMessage Description { get; private set; }
/// <summary>
/// Category the alert belongs to. Only one alert of a given category
/// can be shown at a time. If one is shown while another is already being shown,
/// it will be replaced. This can be useful for categories of alerts which should naturally
/// replace each other and are mutually exclusive, for example lowpressure / highpressure,
/// hot / cold. If left unspecified, the alert will not replace or be replaced by any other alerts.
/// </summary>
public AlertCategory? Category { get; private set; }
/// <summary>
/// Key which is unique w.r.t category semantics (alerts with same category have equal keys,
/// alerts with no category have different keys).
/// </summary>
public AlertKey AlertKey { get; private set; }
/// <summary>
/// -1 (no effect) unless MaxSeverity is specified. Defaults to 1. Minimum severity level supported by this state.
/// </summary>
public short MinSeverity => MaxSeverity == -1 ? (short) -1 : _minSeverity;
private short _minSeverity;
/// <summary>
/// Maximum severity level supported by this state. -1 (default) indicates
/// no severity levels are supported by the state.
/// </summary>
public short MaxSeverity { get; private set; }
/// <summary>
/// Indicates whether this state support severity levels
/// </summary>
public bool SupportsSeverity => MaxSeverity != -1;
public void LoadFrom(YamlMappingNode mapping)
{
var serializer = YamlObjectSerializer.NewReader(mapping);
serializer.DataField(this, x => x.IconPath, "icon", string.Empty);
serializer.DataField(this, x => x.MaxSeverity, "maxSeverity", (short) -1);
serializer.DataField(ref _minSeverity, "minSeverity", (short) 1);
serializer.DataReadFunction("name", string.Empty,
s => Name = FormattedMessage.FromMarkup(s));
serializer.DataReadFunction("description", string.Empty,
s => Description = FormattedMessage.FromMarkup(s));
serializer.DataField(this, x => x.AlertType, "alertType", AlertType.Error);
if (AlertType == AlertType.Error)
{
Logger.ErrorS("alert", "missing or invalid alertType for alert with name {0}", Name);
}
if (serializer.TryReadDataField("category", out AlertCategory alertCategory))
{
Category = alertCategory;
}
AlertKey = new AlertKey(AlertType, Category);
}
/// <param name="severity">severity level, if supported by this alert</param>
/// <returns>the icon path to the texture for the provided severity level</returns>
public string GetIconPath(short? severity = null)
{
if (!SupportsSeverity && severity != null)
{
Logger.WarningS("alert", "attempted to get icon path for severity level for alert {0}, but" +
" this alert does not support severity levels", AlertType);
}
if (!SupportsSeverity) return IconPath;
if (severity == null)
{
Logger.WarningS("alert", "attempted to get icon path without severity level for alert {0}," +
" but this alert requires a severity level. Using lowest" +
" valid severity level instead...", AlertType);
severity = MinSeverity;
}
if (severity < MinSeverity)
{
Logger.WarningS("alert", "attempted to get icon path with severity level {0} for alert {1}," +
" but the minimum severity level for this alert is {2}. Using" +
" lowest valid severity level instead...", severity, AlertType, MinSeverity);
severity = MinSeverity;
}
if (severity > MaxSeverity)
{
Logger.WarningS("alert", "attempted to get icon path with severity level {0} for alert {1}," +
" but the max severity level for this alert is {2}. Using" +
" highest valid severity level instead...", severity, AlertType, MaxSeverity);
severity = MaxSeverity;
}
// split and add the severity number to the path
var ext = IconPath.LastIndexOf('.');
return IconPath.Substring(0, ext) + severity + IconPath.Substring(ext, IconPath.Length - ext);
}
}
/// <summary>
/// Key for an alert which is unique (for equality and hashcode purposes) w.r.t category semantics.
/// I.e., entirely defined by the category, if a category was specified, otherwise
/// falls back to the id.
/// </summary>
[Serializable, NetSerializable]
public struct AlertKey
{
private readonly AlertType? _alertType;
private readonly AlertCategory? _alertCategory;
/// NOTE: if the alert has a category you must pass the category for this to work
/// properly as a key. I.e. if the alert has a category and you pass only the ID, and you
/// compare this to another AlertKey that has both the category and the same ID, it will not consider them equal.
public AlertKey(AlertType? alertType, AlertCategory? alertCategory)
{
// if there is a category, ignore the alerttype.
if (alertCategory != null)
{
_alertCategory = alertCategory;
_alertType = null;
}
else
{
_alertCategory = null;
_alertType = alertType;
}
}
public bool Equals(AlertKey other)
{
return _alertType == other._alertType && _alertCategory == other._alertCategory;
}
public override bool Equals(object obj)
{
return obj is AlertKey other && Equals(other);
}
public override int GetHashCode()
{
return HashCode.Combine(_alertType, _alertCategory);
}
/// <param name="category">alert category, must not be null</param>
/// <returns>An alert key for the provided alert category</returns>
public static AlertKey ForCategory(AlertCategory category)
{
return new AlertKey(null, category);
}
}
}

View File

@@ -0,0 +1,52 @@
namespace Content.Shared.Alert
{
/// <summary>
/// Every category of alert. Corresponds to category field in alert prototypes defined in YML
/// </summary>
public enum AlertCategory
{
Pressure,
Temperature,
Buckled,
Health,
Piloting,
Hunger,
Thirst
}
/// <summary>
/// Every kind of alert. Corresponds to alertType field in alert prototypes defined in YML
/// </summary>
public enum AlertType
{
Error,
LowPressure,
HighPressure,
Fire,
Cold,
Hot,
Weightless,
Stun,
Handcuffed,
Buckled,
HumanCrit,
HumanDead,
HumanHealth,
PilotingShuttle,
Overfed,
Peckish,
Starving,
Overhydrated,
Thirsty,
Parched,
Pulled,
Pulling,
Debug1,
Debug2,
Debug3,
Debug4,
Debug5,
Debug6
}
}

View File

@@ -7,6 +7,24 @@ namespace Content.Shared
[CVarDefs]
public sealed class CCVars : CVars
{
/*
* Status
*/
public static readonly CVarDef<string> StatusMoMMIUrl =
CVarDef.Create<string>("status.mommiurl", null);
public static readonly CVarDef<string> StatusMoMMIPassword =
CVarDef.Create<string>("status.mommipassword", null);
/*
* Game
*/
public static readonly CVarDef<bool>
EventsEnabled = CVarDef.Create("events.enabled", false, CVar.ARCHIVE | CVar.SERVERONLY);
public static readonly CVarDef<bool>
GameLobbyEnabled = CVarDef.Create("game.lobbyenabled", false, CVar.ARCHIVE);
@@ -34,6 +52,25 @@ namespace Content.Shared
public static readonly CVarDef<bool>
GamePersistGuests = CVarDef.Create("game.persistguests", true, CVar.ARCHIVE | CVar.SERVERONLY);
public static readonly CVarDef<int> GameSuspicionMinPlayers =
CVarDef.Create("game.suspicion_min_players", 5);
public static readonly CVarDef<int> GameSuspicionMinTraitors =
CVarDef.Create("game.suspicion_min_traitors", 2);
public static readonly CVarDef<int> GameSuspicionPlayersPerTraitor =
CVarDef.Create("game.suspicion_players_per_traitor", 5);
public static readonly CVarDef<int> GameSuspicionStartingBalance =
CVarDef.Create("game.suspicion_starting_balance", 20);
public static readonly CVarDef<bool> GameDiagonalMovement =
CVarDef.Create("game.diagonalmovement", true, CVar.ARCHIVE);
/*
* Console
*/
public static readonly CVarDef<bool>
ConsoleLoginLocal = CVarDef.Create("console.loginlocal", true, CVar.ARCHIVE | CVar.SERVERONLY);
@@ -63,6 +100,44 @@ namespace Content.Shared
public static readonly CVarDef<string> DatabasePgPassword =
CVarDef.Create("database.pg_password", "", CVar.SERVERONLY);
/*
* Outline
*/
public static readonly CVarDef<bool> OutlineEnabled =
CVarDef.Create("outline.enabled", true, CVar.CLIENTONLY);
/*
* Parallax
*/
public static readonly CVarDef<bool> ParallaxEnabled =
CVarDef.Create("parallax.enabled", true);
public static readonly CVarDef<bool> ParallaxDebug =
CVarDef.Create("parallax.debug", true);
/*
* AI
*/
public static readonly CVarDef<int> AIMaxUpdates =
CVarDef.Create("ai.maxupdates", 64);
/*
* Net
*/
public static readonly CVarDef<float> NetAtmosDebugOverlayTickRate =
CVarDef.Create("net.atmosdbgoverlaytickrate", 3.0f);
public static readonly CVarDef<float> NetGasOverlayTickRate =
CVarDef.Create("net.gasoverlaytickrate", 3.0f);
/*
* Admin stuff
*/

View File

@@ -2,10 +2,11 @@
using Content.Shared.GameObjects.Components.Body.Mechanism;
using Content.Shared.GameObjects.Components.Body.Part;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Serialization;
namespace Content.Shared.GameObjects.Components.Body.Behavior
{
public interface IMechanismBehavior : IComponent
public interface IMechanismBehavior : IExposeData
{
IBody? Body { get; }
@@ -15,7 +16,20 @@ namespace Content.Shared.GameObjects.Components.Body.Behavior
/// Upward reference to the parent <see cref="IMechanism"/> that this
/// behavior is attached to.
/// </summary>
IMechanism? Mechanism { get; }
IMechanism Parent { get; }
/// <summary>
/// The entity that owns <see cref="Parent"/>.
/// For the entity owning the body that this mechanism may be in,
/// see <see cref="IBody.Owner"/>
/// </summary>
IEntity Owner { get; }
void Initialize(IMechanism parent);
void Startup();
void Update(float frameTime);
/// <summary>
/// Called when the containing <see cref="IBodyPart"/> is attached to a

View File

@@ -1,102 +0,0 @@
#nullable enable
using Content.Shared.GameObjects.Components.Body.Mechanism;
using Content.Shared.GameObjects.Components.Body.Part;
using Robust.Shared.GameObjects;
using Robust.Shared.Utility;
namespace Content.Shared.GameObjects.Components.Body.Behavior
{
public abstract class MechanismBehaviorComponent : Component, IMechanismBehavior
{
public IBody? Body => Part?.Body;
public IBodyPart? Part => Mechanism?.Part;
public IMechanism? Mechanism => Owner.GetComponentOrNull<IMechanism>();
protected override void Startup()
{
base.Startup();
if (Part == null)
{
return;
}
if (Body == null)
{
AddedToPart(Part);
}
else
{
AddedToPartInBody(Body, Part);
}
}
public abstract void Update(float frameTime);
public void AddedToBody(IBody body)
{
DebugTools.AssertNotNull(Body);
DebugTools.AssertNotNull(body);
OnAddedToBody(body);
}
public void AddedToPart(IBodyPart part)
{
DebugTools.AssertNotNull(Part);
DebugTools.AssertNotNull(part);
OnAddedToPart(part);
}
public void AddedToPartInBody(IBody body, IBodyPart part)
{
DebugTools.AssertNotNull(Body);
DebugTools.AssertNotNull(body);
DebugTools.AssertNotNull(Part);
DebugTools.AssertNotNull(part);
OnAddedToPartInBody(body, part);
}
public void RemovedFromBody(IBody old)
{
DebugTools.AssertNull(Body);
DebugTools.AssertNotNull(old);
OnRemovedFromBody(old);
}
public void RemovedFromPart(IBodyPart old)
{
DebugTools.AssertNull(Part);
DebugTools.AssertNotNull(old);
OnRemovedFromPart(old);
}
public void RemovedFromPartInBody(IBody oldBody, IBodyPart oldPart)
{
DebugTools.AssertNull(Body);
DebugTools.AssertNull(Part);
DebugTools.AssertNotNull(oldBody);
DebugTools.AssertNotNull(oldPart);
OnRemovedFromPartInBody(oldBody, oldPart);
}
protected virtual void OnAddedToBody(IBody body) { }
protected virtual void OnAddedToPart(IBodyPart part) { }
protected virtual void OnAddedToPartInBody(IBody body, IBodyPart part) { }
protected virtual void OnRemovedFromBody(IBody old) { }
protected virtual void OnRemovedFromPart(IBodyPart old) { }
protected virtual void OnRemovedFromPartInBody(IBody oldBody, IBodyPart oldPart) { }
}
}

View File

@@ -1,8 +0,0 @@
#nullable enable
namespace Content.Shared.GameObjects.Components.Body.Behavior
{
public abstract class SharedHeartBehaviorComponent : MechanismBehaviorComponent
{
public override string Name => "Heart";
}
}

View File

@@ -1,39 +0,0 @@
#nullable enable
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Shared.GameObjects.Components.Body.Behavior
{
public abstract class SharedLungBehaviorComponent : MechanismBehaviorComponent
{
public override string Name => "Lung";
[ViewVariables] public abstract float Temperature { get; }
[ViewVariables] public abstract float Volume { get; }
[ViewVariables] public LungStatus Status { get; set; }
[ViewVariables] public float CycleDelay { get; set; }
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(this, l => l.CycleDelay, "cycleDelay", 2);
}
public abstract void Inhale(float frameTime);
public abstract void Exhale(float frameTime);
public abstract void Gasp();
}
public enum LungStatus
{
None = 0,
Inhaling,
Exhaling
}
}

View File

@@ -1,171 +0,0 @@
#nullable enable
using System.Collections.Generic;
using System.Linq;
using Content.Shared.Chemistry;
using Content.Shared.GameObjects.Components.Body.Networks;
using Content.Shared.GameObjects.Components.Chemistry;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Shared.GameObjects.Components.Body.Behavior
{
/// <summary>
/// Where reagents go when ingested. Tracks ingested reagents over time, and
/// eventually transfers them to <see cref="SharedBloodstreamComponent"/> once digested.
/// </summary>
public abstract class SharedStomachBehaviorComponent : MechanismBehaviorComponent
{
public override string Name => "Stomach";
private float _accumulatedFrameTime;
/// <summary>
/// Updates digestion status of ingested reagents.
/// Once reagents surpass _digestionDelay they are moved to the
/// bloodstream, where they are then metabolized.
/// </summary>
/// <param name="frameTime">
/// The time since the last update in seconds.
/// </param>
public override void Update(float frameTime)
{
if (Body == null)
{
return;
}
_accumulatedFrameTime += frameTime;
// Update at most once per second
if (_accumulatedFrameTime < 1)
{
return;
}
_accumulatedFrameTime -= 1;
if (!Body.Owner.TryGetComponent(out SharedSolutionContainerComponent? solution) ||
!Body.Owner.TryGetComponent(out SharedBloodstreamComponent? bloodstream))
{
return;
}
// Add reagents ready for transfer to bloodstream to transferSolution
var transferSolution = new Solution();
// Use ToList here to remove entries while iterating
foreach (var delta in _reagentDeltas.ToList())
{
//Increment lifetime of reagents
delta.Increment(frameTime);
if (delta.Lifetime > _digestionDelay)
{
solution.TryRemoveReagent(delta.ReagentId, delta.Quantity);
transferSolution.AddReagent(delta.ReagentId, delta.Quantity);
_reagentDeltas.Remove(delta);
}
}
// Transfer digested reagents to bloodstream
bloodstream.TryTransferSolution(transferSolution);
}
/// <summary>
/// Max volume of internal solution storage
/// </summary>
public ReagentUnit MaxVolume
{
get => Owner.TryGetComponent(out SharedSolutionContainerComponent? solution) ? solution.MaxVolume : ReagentUnit.Zero;
set
{
if (Owner.TryGetComponent(out SharedSolutionContainerComponent? solution))
{
solution.MaxVolume = value;
}
}
}
/// <summary>
/// Initial internal solution storage volume
/// </summary>
[ViewVariables]
protected ReagentUnit InitialMaxVolume { get; private set; }
/// <summary>
/// Time in seconds between reagents being ingested and them being
/// transferred to <see cref="SharedBloodstreamComponent"/>
/// </summary>
[ViewVariables]
private float _digestionDelay;
/// <summary>
/// Used to track how long each reagent has been in the stomach
/// </summary>
[ViewVariables]
private readonly List<ReagentDelta> _reagentDeltas = new List<ReagentDelta>();
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(this, s => s.InitialMaxVolume, "maxVolume", ReagentUnit.New(100));
serializer.DataField(ref _digestionDelay, "digestionDelay", 20);
}
public bool CanTransferSolution(Solution solution)
{
if (!Owner.TryGetComponent(out SharedSolutionContainerComponent? solutionComponent))
{
return false;
}
// TODO: For now no partial transfers. Potentially change by design
if (!solutionComponent.CanAddSolution(solution))
{
return false;
}
return true;
}
public bool TryTransferSolution(Solution solution)
{
if (!CanTransferSolution(solution))
return false;
if (!Owner.TryGetComponent(out SharedSolutionContainerComponent? solutionComponent))
{
return false;
}
// Add solution to _stomachContents
solutionComponent.TryAddSolution(solution, false, true);
// Add each reagent to _reagentDeltas. Used to track how long each reagent has been in the stomach
foreach (var reagent in solution.Contents)
{
_reagentDeltas.Add(new ReagentDelta(reagent.ReagentId, reagent.Quantity));
}
return true;
}
/// <summary>
/// Used to track quantity changes when ingesting & digesting reagents
/// </summary>
protected class ReagentDelta
{
public readonly string ReagentId;
public readonly ReagentUnit Quantity;
public float Lifetime { get; private set; }
public ReagentDelta(string reagentId, ReagentUnit quantity)
{
ReagentId = reagentId;
Quantity = quantity;
Lifetime = 0.0f;
}
public void Increment(float delta) => Lifetime += delta;
}
}
}

View File

@@ -1,4 +1,7 @@
#nullable enable
using System;
using System.Collections.Generic;
using Content.Shared.GameObjects.Components.Body.Behavior;
using Content.Shared.GameObjects.Components.Body.Part;
using Robust.Shared.Interfaces.GameObjects;
@@ -10,6 +13,8 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
IBodyPart? Part { get; set; }
IReadOnlyDictionary<Type, IMechanismBehavior> Behaviors { get; }
/// <summary>
/// Professional description of the <see cref="IMechanism"/>.
/// </summary>
@@ -55,6 +60,21 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
/// </summary>
BodyPartCompatibility Compatibility { get; set; }
/// <summary>
/// Adds a behavior if it does not exist already.
/// </summary>
/// <typeparam name="T">The behavior type to add.</typeparam>
/// <returns>
/// True if the behavior already existed, false if it had to be created.
/// </returns>
bool EnsureBehavior<T>(out T behavior) where T : IMechanismBehavior, new();
bool HasBehavior<T>() where T : IMechanismBehavior;
bool TryRemoveBehavior<T>() where T : IMechanismBehavior;
void Update(float frameTime);
// TODO BODY Turn these into event listeners so they dont need to be exposed
/// <summary>
/// Called when the containing <see cref="IBodyPart"/> is attached to a

View File

@@ -1,77 +0,0 @@
#nullable enable
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Shared.GameObjects.Components.Body.Behavior;
using Content.Shared.GameObjects.Components.Body.Part;
namespace Content.Shared.GameObjects.Components.Body.Mechanism
{
public static class MechanismExtensions
{
public static bool HasMechanismBehavior<T>(this IBody body)
{
return body.Parts.Values.Any(p => p.HasMechanismBehavior<T>());
}
public static bool HasMechanismBehavior<T>(this IBodyPart part)
{
return part.Mechanisms.Any(m => m.Owner.HasComponent<T>());
}
public static bool HasMechanismBehavior<T>(this IMechanism mechanism)
{
return mechanism.Owner.HasComponent<T>();
}
public static IEnumerable<IMechanismBehavior> GetMechanismBehaviors(this IBody body)
{
foreach (var part in body.Parts.Values)
foreach (var mechanism in part.Mechanisms)
foreach (var behavior in mechanism.Owner.GetAllComponents<IMechanismBehavior>())
{
yield return behavior;
}
}
public static bool TryGetMechanismBehaviors(this IBody body,
[NotNullWhen(true)] out List<IMechanismBehavior>? behaviors)
{
behaviors = body.GetMechanismBehaviors().ToList();
if (behaviors.Count == 0)
{
behaviors = null;
return false;
}
return true;
}
public static IEnumerable<T> GetMechanismBehaviors<T>(this IBody body) where T : class, IMechanismBehavior
{
foreach (var part in body.Parts.Values)
foreach (var mechanism in part.Mechanisms)
{
if (mechanism.Owner.TryGetComponent(out T? behavior))
{
yield return behavior;
}
}
}
public static bool TryGetMechanismBehaviors<T>(this IBody entity, [NotNullWhen(true)] out List<T>? behaviors)
where T : class, IMechanismBehavior
{
behaviors = entity.GetMechanismBehaviors<T>().ToList();
if (behaviors.Count == 0)
{
behaviors = null;
return false;
}
return true;
}
}
}

View File

@@ -1,10 +1,14 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Shared.GameObjects.Components.Body.Behavior;
using Content.Shared.GameObjects.Components.Body.Part;
using Content.Shared.Interfaces;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
@@ -14,11 +18,12 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
{
public override string Name => "Mechanism";
private IBodyPart? _part;
protected readonly Dictionary<int, object> OptionsCache = new Dictionary<int, object>();
protected IBody? BodyCache;
protected int IdHash;
protected IEntity? PerformerCache;
private IBodyPart? _part;
private readonly Dictionary<Type, IMechanismBehavior> _behaviors = new Dictionary<Type, IMechanismBehavior>();
public IBody? Body => Part?.Body;
@@ -61,6 +66,8 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
}
}
public IReadOnlyDictionary<Type, IMechanismBehavior> Behaviors => _behaviors;
public string Description { get; set; } = string.Empty;
public string ExamineMessage { get; set; } = string.Empty;
@@ -98,6 +105,91 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
serializer.DataField(this, m => m.Size, "size", 1);
serializer.DataField(this, m => m.Compatibility, "compatibility", BodyPartCompatibility.Universal);
var moduleManager = IoCManager.Resolve<IModuleManager>();
if (moduleManager.IsServerModule)
{
serializer.DataReadWriteFunction(
"behaviors",
null!,
behaviors =>
{
if (behaviors == null)
{
return;
}
foreach (var behavior in behaviors)
{
var type = behavior.GetType();
if (!_behaviors.TryAdd(type, behavior))
{
Logger.Warning($"Duplicate behavior in {nameof(SharedMechanismComponent)} for entity {Owner.Name}: {type}.");
continue;
}
IoCManager.InjectDependencies(behavior);
}
},
() => _behaviors.Values.ToList());
}
}
public override void Initialize()
{
base.Initialize();
foreach (var behavior in _behaviors.Values)
{
behavior.Initialize(this);
}
}
protected override void Startup()
{
base.Startup();
foreach (var behavior in _behaviors.Values)
{
behavior.Startup();
}
}
public bool EnsureBehavior<T>(out T behavior) where T : IMechanismBehavior, new()
{
if (_behaviors.TryGetValue(typeof(T), out var rawBehavior))
{
behavior = (T) rawBehavior;
return true;
}
behavior = new T();
IoCManager.InjectDependencies(behavior);
_behaviors.Add(typeof(T), behavior);
behavior.Initialize(this);
behavior.Startup();
return false;
}
public bool HasBehavior<T>() where T : IMechanismBehavior
{
return _behaviors.ContainsKey(typeof(T));
}
public bool TryRemoveBehavior<T>() where T : IMechanismBehavior
{
return _behaviors.Remove(typeof(T));
}
public void Update(float frameTime)
{
foreach (var behavior in _behaviors.Values)
{
behavior.Update(frameTime);
}
}
public void AddedToBody(IBody body)
@@ -105,9 +197,7 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
DebugTools.AssertNotNull(Body);
DebugTools.AssertNotNull(body);
OnAddedToBody(body);
foreach (var behavior in Owner.GetAllComponents<IMechanismBehavior>())
foreach (var behavior in _behaviors.Values)
{
behavior.AddedToBody(body);
}
@@ -119,9 +209,8 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
DebugTools.AssertNotNull(part);
Owner.Transform.AttachParent(part.Owner);
OnAddedToPart(part);
foreach (var behavior in Owner.GetAllComponents<IMechanismBehavior>().ToArray())
foreach (var behavior in _behaviors.Values)
{
behavior.AddedToPart(part);
}
@@ -135,9 +224,8 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
DebugTools.AssertNotNull(part);
Owner.Transform.AttachParent(part.Owner);
OnAddedToPartInBody(body, part);
foreach (var behavior in Owner.GetAllComponents<IMechanismBehavior>())
foreach (var behavior in _behaviors.Values)
{
behavior.AddedToPartInBody(body, part);
}
@@ -148,9 +236,7 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
DebugTools.AssertNull(Body);
DebugTools.AssertNotNull(old);
OnRemovedFromBody(old);
foreach (var behavior in Owner.GetAllComponents<IMechanismBehavior>())
foreach (var behavior in _behaviors.Values)
{
behavior.RemovedFromBody(old);
}
@@ -162,9 +248,8 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
DebugTools.AssertNotNull(old);
Owner.Transform.AttachToGridOrMap();
OnRemovedFromPart(old);
foreach (var behavior in Owner.GetAllComponents<IMechanismBehavior>())
foreach (var behavior in _behaviors.Values)
{
behavior.RemovedFromPart(old);
}
@@ -178,24 +263,11 @@ namespace Content.Shared.GameObjects.Components.Body.Mechanism
DebugTools.AssertNotNull(oldPart);
Owner.Transform.AttachToGridOrMap();
OnRemovedFromPartInBody(oldBody, oldPart);
foreach (var behavior in Owner.GetAllComponents<IMechanismBehavior>())
foreach (var behavior in _behaviors.Values)
{
behavior.RemovedFromPartInBody(oldBody, oldPart);
}
}
protected virtual void OnAddedToBody(IBody body) { }
protected virtual void OnAddedToPart(IBodyPart part) { }
protected virtual void OnAddedToPartInBody(IBody body, IBodyPart part) { }
protected virtual void OnRemovedFromBody(IBody old) { }
protected virtual void OnRemovedFromPart(IBodyPart old) { }
protected virtual void OnRemovedFromPartInBody(IBody oldBody, IBodyPart oldPart) { }
}
}

View File

@@ -0,0 +1,279 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Shared.Alert;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Content.Shared.GameObjects.Components.Mobs
{
/// <summary>
/// Handles the icons on the right side of the screen.
/// Should only be used for player-controlled entities.
/// </summary>
public abstract class SharedAlertsComponent : Component
{
private static readonly AlertState[] NO_ALERTS = new AlertState[0];
[Dependency]
protected readonly AlertManager AlertManager = default!;
public override string Name => "AlertsUI";
public override uint? NetID => ContentNetIDs.ALERTS;
[ViewVariables]
private Dictionary<AlertKey, ClickableAlertState> _alerts = new Dictionary<AlertKey, ClickableAlertState>();
/// <returns>true iff an alert of the indicated alert category is currently showing</returns>
public bool IsShowingAlertCategory(AlertCategory alertCategory)
{
return IsShowingAlert(AlertKey.ForCategory(alertCategory));
}
/// <returns>true iff an alert of the indicated id is currently showing</returns>
public bool IsShowingAlert(AlertType alertType)
{
if (AlertManager.TryGet(alertType, out var alert))
{
return IsShowingAlert(alert.AlertKey);
}
Logger.DebugS("alert", "unknown alert type {0}", alertType);
return false;
}
/// <returns>true iff an alert of the indicated key is currently showing</returns>
protected bool IsShowingAlert(AlertKey alertKey)
{
return _alerts.ContainsKey(alertKey);
}
protected IEnumerable<AlertState> EnumerateAlertStates()
{
return _alerts.Values.Select(alertData => alertData.AlertState);
}
/// <summary>
/// Invokes the alert's specified callback if there is one.
/// Not intended to be used on clientside.
/// </summary>
protected void PerformAlertClickCallback(AlertPrototype alert, IEntity owner)
{
if (_alerts.TryGetValue(alert.AlertKey, out var alertStateCallback))
{
alertStateCallback.OnClickAlert?.Invoke(new ClickAlertEventArgs(owner, alert));
}
else
{
Logger.DebugS("alert", "player {0} attempted to invoke" +
" alert click for {1} but that alert is not currently" +
" showing", owner.Name, alert.AlertType);
}
}
/// <summary>
/// Creates a new array containing all of the current alert states.
/// </summary>
/// <returns></returns>
protected AlertState[] CreateAlertStatesArray()
{
if (_alerts.Count == 0) return NO_ALERTS;
var states = new AlertState[_alerts.Count];
// because I don't trust LINQ
var idx = 0;
foreach (var alertData in _alerts.Values)
{
states[idx++] = alertData.AlertState;
}
return states;
}
protected bool TryGetAlertState(AlertKey key, out AlertState alertState)
{
if (_alerts.TryGetValue(key, out var alertData))
{
alertState = alertData.AlertState;
return true;
}
alertState = default;
return false;
}
/// <summary>
/// Replace the current active alerts with the specified alerts. Any
/// OnClickAlert callbacks on the active alerts will be erased.
/// </summary>
protected void SetAlerts(AlertState[] alerts)
{
var newAlerts = new Dictionary<AlertKey, ClickableAlertState>();
foreach (var alertState in alerts)
{
if (AlertManager.TryDecode(alertState.AlertEncoded, out var alert))
{
newAlerts[alert.AlertKey] = new ClickableAlertState
{
AlertState = alertState
};
}
else
{
Logger.ErrorS("alert", "unrecognized encoded alert {0}", alertState.AlertEncoded);
}
}
_alerts = newAlerts;
}
/// <summary>
/// Shows the alert. If the alert or another alert of the same category is already showing,
/// it will be updated / replaced with the specified values.
/// </summary>
/// <param name="alertType">type of the alert to set</param>
/// <param name="onClickAlert">callback to invoke when ClickAlertMessage is received by the server
/// after being clicked by client. Has no effect when specified on the clientside.</param>
/// <param name="severity">severity, if supported by the alert</param>
/// <param name="cooldown">cooldown start and end, if null there will be no cooldown (and it will
/// be erased if there is currently a cooldown for the alert)</param>
public void ShowAlert(AlertType alertType, short? severity = null, OnClickAlert onClickAlert = null,
ValueTuple<TimeSpan, TimeSpan>? cooldown = null)
{
if (AlertManager.TryGetWithEncoded(alertType, out var alert, out var encoded))
{
if (_alerts.TryGetValue(alert.AlertKey, out var alertStateCallback) &&
alertStateCallback.AlertState.AlertEncoded == encoded &&
alertStateCallback.AlertState.Severity == severity && alertStateCallback.AlertState.Cooldown == cooldown)
{
alertStateCallback.OnClickAlert = onClickAlert;
return;
}
_alerts[alert.AlertKey] = new ClickableAlertState
{
AlertState = new AlertState
{Cooldown = cooldown, AlertEncoded = encoded, Severity = severity},
OnClickAlert = onClickAlert
};
Dirty();
}
else
{
Logger.ErrorS("alert", "Unable to show alert {0}, please ensure this alertType has" +
" a corresponding YML alert prototype",
alertType);
}
}
/// <summary>
/// Clear the alert with the given category, if one is currently showing.
/// </summary>
public void ClearAlertCategory(AlertCategory category)
{
var key = AlertKey.ForCategory(category);
if (!_alerts.Remove(key))
{
return;
}
AfterClearAlert();
Dirty();
}
/// <summary>
/// Clear the alert of the given type if it is currently showing.
/// </summary>
public void ClearAlert(AlertType alertType)
{
if (AlertManager.TryGet(alertType, out var alert))
{
if (!_alerts.Remove(alert.AlertKey))
{
return;
}
AfterClearAlert();
Dirty();
}
else
{
Logger.ErrorS("alert", "unable to clear alert, unknown alertType {0}", alertType);
}
}
/// <summary>
/// Invoked after clearing an alert prior to dirtying the control
/// </summary>
protected virtual void AfterClearAlert() { }
}
[Serializable, NetSerializable]
public class AlertsComponentState : ComponentState
{
public AlertState[] Alerts;
public AlertsComponentState(AlertState[] alerts) : base(ContentNetIDs.ALERTS)
{
Alerts = alerts;
}
}
/// <summary>
/// A message that calls the click interaction on a alert
/// </summary>
[Serializable, NetSerializable]
public class ClickAlertMessage : ComponentMessage
{
public readonly byte EncodedAlert;
public ClickAlertMessage(byte encodedAlert)
{
Directed = true;
EncodedAlert = encodedAlert;
}
}
[Serializable, NetSerializable]
public struct AlertState
{
public byte AlertEncoded;
public short? Severity;
public ValueTuple<TimeSpan, TimeSpan>? Cooldown;
}
public struct ClickableAlertState
{
public AlertState AlertState;
public OnClickAlert OnClickAlert;
}
public delegate void OnClickAlert(ClickAlertEventArgs args);
public class ClickAlertEventArgs : EventArgs
{
/// <summary>
/// Player clicking the alert
/// </summary>
public readonly IEntity Player;
/// <summary>
/// Alert that was clicked
/// </summary>
public readonly AlertPrototype Alert;
public ClickAlertEventArgs(IEntity player, AlertPrototype alert)
{
Player = player;
Alert = alert;
}
}
}

View File

@@ -1,76 +0,0 @@
using System;
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Shared.GameObjects.Components.Mobs
{
/// <summary>
/// Handles the icons on the right side of the screen.
/// Should only be used for player-controlled entities
/// </summary>
public abstract class SharedStatusEffectsComponent : Component
{
public override string Name => "StatusEffectsUI";
public override uint? NetID => ContentNetIDs.STATUSEFFECTS;
public abstract IReadOnlyDictionary<StatusEffect, StatusEffectStatus> Statuses { get; }
public abstract void ChangeStatusEffectIcon(StatusEffect effect, string icon);
public abstract void ChangeStatusEffect(StatusEffect effect, string icon, ValueTuple<TimeSpan, TimeSpan>? cooldown);
public abstract void RemoveStatusEffect(StatusEffect effect);
}
[Serializable, NetSerializable]
public class StatusEffectComponentState : ComponentState
{
public Dictionary<StatusEffect, StatusEffectStatus> StatusEffects;
public StatusEffectComponentState(Dictionary<StatusEffect, StatusEffectStatus> statusEffects) : base(ContentNetIDs.STATUSEFFECTS)
{
StatusEffects = statusEffects;
}
}
/// <summary>
/// A message that calls the click interaction on a status effect
/// </summary>
[Serializable, NetSerializable]
public class ClickStatusMessage : ComponentMessage
{
public readonly StatusEffect Effect;
public ClickStatusMessage(StatusEffect effect)
{
Directed = true;
Effect = effect;
}
}
[Serializable, NetSerializable]
public struct StatusEffectStatus
{
public string Icon;
public ValueTuple<TimeSpan, TimeSpan>? Cooldown;
}
// Each status effect is assumed to be unique
public enum StatusEffect
{
Health,
Hunger,
Thirst,
Pressure,
Fire,
Temperature,
Stun,
Cuffed,
Buckled,
Piloting,
Pulling,
Pulled,
Weightless
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Threading;
using Content.Shared.Alert;
using Content.Shared.GameObjects.Components.Movement;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces.GameObjects.Components;
@@ -41,7 +42,7 @@ namespace Content.Shared.GameObjects.Components.Mobs
protected float KnockdownTimer;
protected float SlowdownTimer;
private string _stunTexture;
private string _stunAlertId;
protected CancellationTokenSource StatusRemoveCancellation = new CancellationTokenSource();
@@ -117,7 +118,7 @@ namespace Content.Shared.GameObjects.Components.Mobs
StunnedTimer = seconds;
LastStun = _gameTiming.CurTime;
SetStatusEffect();
SetAlert();
OnStun();
Dirty();
@@ -144,7 +145,7 @@ namespace Content.Shared.GameObjects.Components.Mobs
KnockdownTimer = seconds;
LastStun = _gameTiming.CurTime;
SetStatusEffect();
SetAlert();
OnKnockdown();
Dirty();
@@ -186,18 +187,18 @@ namespace Content.Shared.GameObjects.Components.Mobs
if (Owner.TryGetComponent(out MovementSpeedModifierComponent movement))
movement.RefreshMovementSpeedModifiers();
SetStatusEffect();
SetAlert();
Dirty();
}
private void SetStatusEffect()
private void SetAlert()
{
if (!Owner.TryGetComponent(out SharedStatusEffectsComponent status))
if (!Owner.TryGetComponent(out SharedAlertsComponent status))
{
return;
}
status.ChangeStatusEffect(StatusEffect.Stun, _stunTexture,
status.ShowAlert(AlertType.Stun, cooldown:
(StunStart == null || StunEnd == null) ? default : (StunStart.Value, StunEnd.Value));
StatusRemoveCancellation.Cancel();
StatusRemoveCancellation = new CancellationTokenSource();
@@ -212,8 +213,8 @@ namespace Content.Shared.GameObjects.Components.Mobs
serializer.DataField(ref _slowdownCap, "slowdownCap", 20f);
serializer.DataField(ref _helpInterval, "helpInterval", 1f);
serializer.DataField(ref _helpKnockdownRemove, "helpKnockdownRemove", 1f);
serializer.DataField(ref _stunTexture, "stunTexture",
"/Textures/Objects/Weapons/Melee/stunbaton.rsi/stunbaton_off.png");
serializer.DataField(ref _stunAlertId, "stunAlertId",
"stun");
}
protected virtual void OnInteractHand() { }
@@ -230,7 +231,7 @@ namespace Content.Shared.GameObjects.Components.Mobs
KnockdownTimer -= _helpKnockdownRemove;
SetStatusEffect();
SetAlert();
Dirty();
return true;

View File

@@ -139,7 +139,7 @@ namespace Content.Shared.GameObjects.Components.Movement
/// Whether or not the player can move diagonally.
/// </summary>
[ViewVariables]
public bool DiagonalMovementEnabled => _configurationManager.GetCVar<bool>("game.diagonalmovement");
public bool DiagonalMovementEnabled => _configurationManager.GetCVar<bool>(CCVars.GameDiagonalMovement);
/// <inheritdoc />
public override void OnAdd()

View File

@@ -1,12 +0,0 @@
using Robust.Shared.GameObjects;
namespace Content.Shared.GameObjects.Components.Nutrition
{
/// <summary>
/// Shared class for stomach components
/// </summary>
public class SharedStomachComponent : Component
{
public override string Name => "Stomach";
}
}

View File

@@ -30,6 +30,15 @@ namespace Content.Shared.GameObjects.Components.PDA
}
}
[Serializable, NetSerializable]
public sealed class PDAEjectPenMessage : BoundUserInterfaceMessage
{
public PDAEjectPenMessage()
{
}
}
[Serializable, NetSerializable]
public class PDAUBoundUserInterfaceState : BoundUserInterfaceState
{
@@ -40,28 +49,27 @@ namespace Content.Shared.GameObjects.Components.PDA
public sealed class PDAUpdateState : PDAUBoundUserInterfaceState
{
public bool FlashlightEnabled;
public bool HasPen;
public PDAIdInfoText PDAOwnerInfo;
public UplinkAccountData Account;
public UplinkListingData[] Listings;
public PDAUpdateState(bool isFlashlightOn, PDAIdInfoText ownerInfo)
public PDAUpdateState(bool isFlashlightOn, bool hasPen, PDAIdInfoText ownerInfo)
{
FlashlightEnabled = isFlashlightOn;
HasPen = hasPen;
PDAOwnerInfo = ownerInfo;
}
public PDAUpdateState(bool isFlashlightOn, PDAIdInfoText ownerInfo, UplinkAccountData accountData)
public PDAUpdateState(bool isFlashlightOn, bool hasPen, PDAIdInfoText ownerInfo, UplinkAccountData accountData)
: this(isFlashlightOn, hasPen, ownerInfo)
{
FlashlightEnabled = isFlashlightOn;
PDAOwnerInfo = ownerInfo;
Account = accountData;
}
public PDAUpdateState(bool isFlashlightOn, PDAIdInfoText ownerInfo, UplinkAccountData accountData, UplinkListingData[] listings)
public PDAUpdateState(bool isFlashlightOn, bool hasPen, PDAIdInfoText ownerInfo, UplinkAccountData accountData, UplinkListingData[] listings)
: this(isFlashlightOn, hasPen, ownerInfo, accountData)
{
FlashlightEnabled = isFlashlightOn;
PDAOwnerInfo = ownerInfo;
Account = accountData;
Listings = listings;
}
}

View File

@@ -1,12 +1,15 @@
#nullable enable
using System;
using Content.Shared.Alert;
using Content.Shared.GameObjects.Components.Mobs;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Physics;
using Content.Shared.Physics.Pull;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.ComponentDependencies;
using Robust.Shared.GameObjects.Components;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Physics;
@@ -19,7 +22,7 @@ namespace Content.Shared.GameObjects.Components.Pulling
public override string Name => "Pullable";
public override uint? NetID => ContentNetIDs.PULLABLE;
[ComponentDependency] private IPhysicsComponent? _physics = default!;
[ComponentDependency] private readonly IPhysicsComponent? _physics = default!;
private IEntity? _puller;
@@ -36,7 +39,7 @@ namespace Content.Shared.GameObjects.Components.Pulling
_puller = value;
Dirty();
if (!Owner.TryGetComponent(out IPhysicsComponent? physics))
if (_physics == null)
{
return;
}
@@ -45,7 +48,7 @@ namespace Content.Shared.GameObjects.Components.Pulling
if (value == null)
{
if (physics.TryGetController(out controller))
if (_physics.TryGetController(out controller))
{
controller.StopPull();
}
@@ -53,7 +56,7 @@ namespace Content.Shared.GameObjects.Components.Pulling
return;
}
controller = physics.EnsureController<PullController>();
controller = _physics.EnsureController<PullController>();
controller.StartPull(value);
}
}
@@ -67,12 +70,12 @@ namespace Content.Shared.GameObjects.Components.Pulling
return false;
}
if (!puller.TryGetComponent(out IPhysicsComponent? physics))
if (_physics == null)
{
return false;
}
if (physics.Anchored)
if (_physics.Anchored)
{
return false;
}
@@ -145,12 +148,12 @@ namespace Content.Shared.GameObjects.Components.Pulling
return false;
}
if (!Owner.TryGetComponent(out IPhysicsComponent? physics))
if (_physics == null)
{
return false;
}
if (!physics.TryGetController(out PullController controller))
if (!_physics.TryGetController(out PullController controller))
{
return false;
}
@@ -204,29 +207,36 @@ namespace Content.Shared.GameObjects.Components.Pulling
private void AddPullingStatuses(IEntity puller)
{
if (Owner.TryGetComponent(out SharedStatusEffectsComponent? pulledStatus))
if (Owner.TryGetComponent(out SharedAlertsComponent? pulledStatus))
{
pulledStatus.ChangeStatusEffectIcon(StatusEffect.Pulled,
"/Textures/Interface/StatusEffects/Pull/pulled.png");
pulledStatus.ShowAlert(AlertType.Pulled);
}
if (puller.TryGetComponent(out SharedStatusEffectsComponent? ownerStatus))
if (puller.TryGetComponent(out SharedAlertsComponent? ownerStatus))
{
ownerStatus.ChangeStatusEffectIcon(StatusEffect.Pulling,
"/Textures/Interface/StatusEffects/Pull/pulling.png");
ownerStatus.ShowAlert(AlertType.Pulling, onClickAlert: OnClickAlert);
}
}
private void OnClickAlert(ClickAlertEventArgs args)
{
EntitySystem
.Get<SharedPullingSystem>()
.GetPulled(args.Player)?
.GetComponentOrNull<SharedPullableComponent>()?
.TryStopPull();
}
private void RemovePullingStatuses(IEntity puller)
{
if (Owner.TryGetComponent(out SharedStatusEffectsComponent? pulledStatus))
if (Owner.TryGetComponent(out SharedAlertsComponent? pulledStatus))
{
pulledStatus.RemoveStatusEffect(StatusEffect.Pulled);
pulledStatus.ClearAlert(AlertType.Pulled);
}
if (puller.TryGetComponent(out SharedStatusEffectsComponent? ownerStatus))
if (puller.TryGetComponent(out SharedAlertsComponent? ownerStatus))
{
ownerStatus.RemoveStatusEffect(StatusEffect.Pulling);
ownerStatus.ClearAlert(AlertType.Pulling);
}
}

View File

@@ -13,21 +13,34 @@ namespace Content.Shared.GameObjects.Components
[Serializable, NetSerializable]
public class ConfigurationBoundUserInterfaceState : BoundUserInterfaceState
{
public readonly Dictionary<string, string> Config;
public Dictionary<string, string> Config { get; }
public ConfigurationBoundUserInterfaceState(Dictionary<string, string> config)
{
Config = config;
}
}
/// <summary>
/// Message sent to other components on this entity when DeviceNetwork configuration updated.
/// </summary>
public class ConfigUpdatedComponentMessage : ComponentMessage
{
public Dictionary<string, string> Config { get; }
public ConfigUpdatedComponentMessage(Dictionary<string, string> config)
{
Config = config;
}
}
/// <summary>
/// Message data sent from client to server when the device configuration is updated.
/// </summary>
[Serializable, NetSerializable]
public class ConfigurationUpdatedMessage : BoundUserInterfaceMessage
{
public readonly Dictionary<string, string> Config;
public Dictionary<string, string> Config { get; }
public ConfigurationUpdatedMessage(Dictionary<string, string> config)
{
@@ -38,7 +51,7 @@ namespace Content.Shared.GameObjects.Components
[Serializable, NetSerializable]
public class ValidationUpdateMessage : BoundUserInterfaceMessage
{
public readonly string ValidationString;
public string ValidationString { get; }
public ValidationUpdateMessage(string validationString)
{

View File

@@ -29,7 +29,7 @@
public const uint RESEARCH_CONSOLE = 1023;
public const uint WIRES = 1024;
public const uint COMBATMODE = 1025;
public const uint STATUSEFFECTS = 1026;
public const uint ALERTS = 1026;
public const uint OVERLAYEFFECTS = 1027;
public const uint STOMACH = 1028;
public const uint ITEMCOOLDOWN = 1029;

View File

@@ -0,0 +1,21 @@
using Content.Shared.GameObjects.Components.Body.Behavior;
using Content.Shared.GameObjects.Components.Body.Mechanism;
using JetBrains.Annotations;
using Robust.Shared.GameObjects.Systems;
namespace Content.Shared.GameObjects.EntitySystems
{
[UsedImplicitly]
public class MechanismSystem : EntitySystem
{
public override void Update(float frameTime)
{
base.Update(frameTime);
foreach (var mechanism in ComponentManager.EntityQuery<IMechanism>())
{
mechanism.Update(frameTime);
}
}
}
}

View File

@@ -41,8 +41,6 @@ namespace Content.Shared.GameObjects.EntitySystems
.Bind(EngineKeyFunctions.MoveDown, moveDownCmdHandler)
.Bind(EngineKeyFunctions.Walk, new WalkInputCmdHandler())
.Register<SharedMoverSystem>();
_configurationManager.RegisterCVar("game.diagonalmovement", true, CVar.ARCHIVE);
}
/// <inheritdoc />