diff --git a/Content.Client/Actions/UI/ActionAlertTooltip.cs b/Content.Client/Actions/UI/ActionAlertTooltip.cs index 2425cdefb9..664a67b406 100644 --- a/Content.Client/Actions/UI/ActionAlertTooltip.cs +++ b/Content.Client/Actions/UI/ActionAlertTooltip.cs @@ -21,7 +21,7 @@ namespace Content.Client.Actions.UI /// public (TimeSpan Start, TimeSpan End)? Cooldown { get; set; } - public ActionAlertTooltip(FormattedMessage name, FormattedMessage? desc, string? requires = null, FormattedMessage? charges = null) + public ActionAlertTooltip(FormattedMessage name, FormattedMessage? desc, string? requires = null) { _gameTiming = IoCManager.Resolve(); @@ -52,17 +52,6 @@ namespace Content.Client.Actions.UI vbox.AddChild(description); } - if (charges != null && !string.IsNullOrWhiteSpace(charges.ToString())) - { - var chargesLabel = new RichTextLabel - { - MaxWidth = TooltipTextMaxWidth, - StyleClasses = { StyleNano.StyleClassTooltipActionCharges } - }; - chargesLabel.SetMessage(charges); - vbox.AddChild(chargesLabel); - } - vbox.AddChild(_cooldownLabel = new RichTextLabel { MaxWidth = TooltipTextMaxWidth, diff --git a/Content.Client/Light/EntitySystems/LightBulbSystem.cs b/Content.Client/Light/EntitySystems/LightBulbSystem.cs index c028cc64c6..a3698fc199 100644 --- a/Content.Client/Light/EntitySystems/LightBulbSystem.cs +++ b/Content.Client/Light/EntitySystems/LightBulbSystem.cs @@ -1,36 +1,46 @@ using Content.Shared.Light.Components; +using Content.Shared.Light.EntitySystems; using Robust.Client.GameObjects; -namespace Content.Client.Light.Visualizers; +namespace Content.Client.Light.EntitySystems; -public sealed class LightBulbSystem : VisualizerSystem +public sealed class LightBulbSystem : SharedLightBulbSystem { - protected override void OnAppearanceChange(EntityUid uid, LightBulbComponent comp, ref AppearanceChangeEvent args) + [Dependency] private readonly AppearanceSystem _appearance = default!; + [Dependency] private readonly SpriteSystem _sprite = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnAppearanceChange); + } + + private void OnAppearanceChange(EntityUid uid, LightBulbComponent comp, ref AppearanceChangeEvent args) { if (args.Sprite == null) return; // update sprite state - if (AppearanceSystem.TryGetData(uid, LightBulbVisuals.State, out var state, args.Component)) + if (_appearance.TryGetData(uid, LightBulbVisuals.State, out var state, args.Component)) { switch (state) { case LightBulbState.Normal: - SpriteSystem.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.NormalSpriteState); + _sprite.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.NormalSpriteState); break; case LightBulbState.Broken: - SpriteSystem.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.BrokenSpriteState); + _sprite.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.BrokenSpriteState); break; case LightBulbState.Burned: - SpriteSystem.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.BurnedSpriteState); + _sprite.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.BurnedSpriteState); break; } } // also update sprites color - if (AppearanceSystem.TryGetData(uid, LightBulbVisuals.Color, out var color, args.Component)) + if (_appearance.TryGetData(uid, LightBulbVisuals.Color, out var color, args.Component)) { - SpriteSystem.SetColor((uid, args.Sprite), color); + _sprite.SetColor((uid, args.Sprite), color); } } } diff --git a/Content.Client/Light/EntitySystems/PoweredLightSystem.cs b/Content.Client/Light/EntitySystems/PoweredLightSystem.cs new file mode 100644 index 0000000000..b8a6b16da4 --- /dev/null +++ b/Content.Client/Light/EntitySystems/PoweredLightSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared.Light.EntitySystems; + +namespace Content.Client.Light.EntitySystems; + +public sealed class PoweredLightSystem : SharedPoweredLightSystem; diff --git a/Content.Client/Medical/SuitSensors/SuitSensorSystem.cs b/Content.Client/Medical/SuitSensors/SuitSensorSystem.cs new file mode 100644 index 0000000000..75868e08d9 --- /dev/null +++ b/Content.Client/Medical/SuitSensors/SuitSensorSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared.Medical.SuitSensors; + +namespace Content.Client.Medical.SuitSensors; + +public sealed class SuitSensorSystem : SharedSuitSensorSystem; diff --git a/Content.Client/Power/Components/ApcPowerReceiverComponent.cs b/Content.Client/Power/Components/ApcPowerReceiverComponent.cs index fbebcb7cf8..ead686189e 100644 --- a/Content.Client/Power/Components/ApcPowerReceiverComponent.cs +++ b/Content.Client/Power/Components/ApcPowerReceiverComponent.cs @@ -5,4 +5,5 @@ namespace Content.Client.Power.Components; [RegisterComponent] public sealed partial class ApcPowerReceiverComponent : SharedApcPowerReceiverComponent { + public override float Load { get; set; } } diff --git a/Content.Client/Robotics/UI/RoboticsConsoleWindow.xaml.cs b/Content.Client/Robotics/UI/RoboticsConsoleWindow.xaml.cs index 06e5674d9c..24583d2776 100644 --- a/Content.Client/Robotics/UI/RoboticsConsoleWindow.xaml.cs +++ b/Content.Client/Robotics/UI/RoboticsConsoleWindow.xaml.cs @@ -28,6 +28,8 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow public EntityUid Entity; + private bool _allowBorgControl = true; + public RoboticsConsoleWindow() { RobustXamlLoader.Load(this); @@ -72,6 +74,7 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow public void UpdateState(RoboticsConsoleState state) { _cyborgs = state.Cyborgs; + _allowBorgControl = state.AllowBorgControl; // clear invalid selection if (_selected is {} selected && !_cyborgs.ContainsKey(selected)) @@ -85,8 +88,8 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow PopulateData(); var locked = _lock.IsLocked(Entity); - DangerZone.Visible = !locked; - LockedMessage.Visible = locked; + DangerZone.Visible = !locked && _allowBorgControl; + LockedMessage.Visible = locked && _allowBorgControl; // Only show if locked AND control is allowed } private void PopulateCyborgs() @@ -120,11 +123,19 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow BorgSprite.Texture = _sprite.Frame0(data.ChassisSprite!); var batteryColor = data.Charge switch { - < 0.2f => "red", - < 0.4f => "orange", - < 0.6f => "yellow", - < 0.8f => "green", - _ => "blue" + < 0.2f => "#FF6C7F", // red + < 0.4f => "#EF973C", // orange + < 0.6f => "#E8CB2D", // yellow + < 0.8f => "#30CC19", // green + _ => "#00D3B8" // cyan + }; + + var hpPercentColor = data.HpPercent switch { + < 0.2f => "#FF6C7F", // red + < 0.4f => "#EF973C", // orange + < 0.6f => "#E8CB2D", // yellow + < 0.8f => "#30CC19", // green + _ => "#00D3B8" // cyan }; var text = new FormattedMessage(); @@ -132,12 +143,14 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow text.AddMarkupOrThrow(Loc.GetString("robotics-console-designation")); text.AddText($" {data.Name}\n"); // prevent players trolling by naming borg [color=red]satan[/color] text.AddMarkupOrThrow($"{Loc.GetString("robotics-console-battery", ("charge", (int)(data.Charge * 100f)), ("color", batteryColor))}\n"); + text.AddMarkupOrThrow($"{Loc.GetString("robotics-console-hp", ("hp", (int)(data.HpPercent * 100f)), ("color", hpPercentColor))}\n"); text.AddMarkupOrThrow($"{Loc.GetString("robotics-console-brain", ("brain", data.HasBrain))}\n"); text.AddMarkupOrThrow(Loc.GetString("robotics-console-modules", ("count", data.ModuleCount))); BorgInfo.SetMessage(text); // how the turntables - DisableButton.Disabled = !(data.HasBrain && data.CanDisable); + DisableButton.Disabled = !_allowBorgControl || !(data.HasBrain && data.CanDisable); + DestroyButton.Disabled = !_allowBorgControl; } protected override void FrameUpdate(FrameEventArgs args) diff --git a/Content.Client/Shuttles/UI/ShuttleDockControl.xaml.cs b/Content.Client/Shuttles/UI/ShuttleDockControl.xaml.cs index 2b575b4805..449323c746 100644 --- a/Content.Client/Shuttles/UI/ShuttleDockControl.xaml.cs +++ b/Content.Client/Shuttles/UI/ShuttleDockControl.xaml.cs @@ -40,6 +40,8 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl private readonly HashSet _drawnDocks = new(); private readonly Dictionary _dockButtons = new(); + private readonly Color _fallbackHighlightedColor = Color.Magenta; + /// /// Store buttons for every other dock /// @@ -213,11 +215,11 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl if (HighlightedDock == dock.Entity) { - otherDockColor = Color.ToSrgb(Color.Magenta); + otherDockColor = Color.ToSrgb(dock.HighlightedColor); } else { - otherDockColor = Color.ToSrgb(Color.Purple); + otherDockColor = Color.ToSrgb(dock.Color); } /* @@ -311,7 +313,7 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl ScalePosition(Vector2.Transform(new Vector2(-0.5f, 0.5f), rotation)), ScalePosition(Vector2.Transform(new Vector2(0.5f, -0.5f), rotation))); - var dockColor = Color.Magenta; + var dockColor = _viewedState?.HighlightedColor ?? _fallbackHighlightedColor; var connectionColor = Color.Pink; handle.DrawRect(ourDockConnection, connectionColor.WithAlpha(0.2f)); diff --git a/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs b/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs index 2dcec6b44a..7899a5ef3e 100644 --- a/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs +++ b/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs @@ -308,7 +308,7 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl -dockRadius * UIScale, (Size.X + dockRadius) * UIScale, (Size.Y + dockRadius) * UIScale); - + if (_docks.TryGetValue(nent, out var docks)) { foreach (var state in docks) @@ -321,7 +321,7 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl continue; } - var color = Color.ToSrgb(Color.Magenta); + var color = Color.ToSrgb(state.HighlightedColor); var verts = new[] { diff --git a/Content.Client/Stack/StackSystem.cs b/Content.Client/Stack/StackSystem.cs index d12e9900a6..d7d1c34ae2 100644 --- a/Content.Client/Stack/StackSystem.cs +++ b/Content.Client/Stack/StackSystem.cs @@ -28,22 +28,8 @@ namespace Content.Client.Stack base.SetCount(uid, amount, component); - if (component.Lingering && - TryComp(uid, out var sprite)) - { - // tint the stack gray and make it transparent if it's lingering. - var color = component.Count == 0 && component.Lingering - ? Color.DarkGray.WithAlpha(0.65f) - : Color.White; - - for (var i = 0; i < sprite.AllLayers.Count(); i++) - { - _sprite.LayerSetColor((uid, sprite), i, color); - } - } - // TODO PREDICT ENTITY DELETION: This should really just be a normal entity deletion call. - if (component.Count <= 0 && !component.Lingering) + if (component.Count <= 0) { Xform.DetachEntity(uid, Transform(uid)); return; diff --git a/Content.Client/UserInterface/Controls/SlotControl.cs b/Content.Client/UserInterface/Controls/SlotControl.cs index a684bb05ef..2b43f2397d 100644 --- a/Content.Client/UserInterface/Controls/SlotControl.cs +++ b/Content.Client/UserInterface/Controls/SlotControl.cs @@ -1,9 +1,11 @@ using System.Numerics; using Content.Client.Cooldown; using Content.Client.UserInterface.Systems.Inventory.Controls; +using Robust.Client.GameObjects; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Shared.Input; +using Robust.Shared.Prototypes; namespace Content.Client.UserInterface.Controls { @@ -20,6 +22,7 @@ namespace Content.Client.UserInterface.Controls public CooldownGraphic CooldownDisplay { get; } private SpriteView SpriteView { get; } + private EntityPrototypeView ProtoView { get; } public EntityUid? Entity => SpriteView.Entity; @@ -141,6 +144,13 @@ namespace Content.Client.UserInterface.Controls SetSize = new Vector2(DefaultButtonSize, DefaultButtonSize), OverrideDirection = Direction.South }); + AddChild(ProtoView = new EntityPrototypeView + { + Visible = false, + Scale = new Vector2(2, 2), + SetSize = new Vector2(DefaultButtonSize, DefaultButtonSize), + OverrideDirection = Direction.South + }); AddChild(HoverSpriteView = new SpriteView { @@ -209,12 +219,35 @@ namespace Content.Client.UserInterface.Controls HoverSpriteView.SetEntity(null); } + /// + /// Causes the control to display a placeholder prototype, optionally faded + /// public void SetEntity(EntityUid? ent) { SpriteView.SetEntity(ent); + SpriteView.Visible = true; + ProtoView.Visible = false; UpdateButtonTexture(); } + /// + /// Causes the control to display a placeholder prototype, optionally faded + /// + public void SetPrototype(EntProtoId? proto, bool fade) + { + ProtoView.SetPrototype(proto); + SpriteView.Visible = false; + ProtoView.Visible = true; + + UpdateButtonTexture(); + + if (ProtoView.Entity is not { } ent || !fade) + return; + + var sprites = IoCManager.Resolve().GetEntitySystem(); + sprites.SetColor((ent.Owner, ent.Comp1), Color.DarkGray.WithAlpha(0.65f)); + } + private void UpdateButtonTexture() { var fullTexture = Theme.ResolveTextureOrNull(_fullButtonTexturePath); diff --git a/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs b/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs index cad9045fa8..be3af28b15 100644 --- a/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs +++ b/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs @@ -3,12 +3,12 @@ using Content.Client.Actions; using Content.Client.Actions.UI; using Content.Client.Cooldown; using Content.Client.Stylesheets; -using Content.Shared.Actions; using Content.Shared.Actions.Components; -using Content.Shared.Charges.Components; using Content.Shared.Charges.Systems; +using Content.Shared.Examine; using Robust.Client.GameObjects; using Robust.Client.Graphics; +using Robust.Client.Player; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Shared.Input; @@ -23,9 +23,9 @@ namespace Content.Client.UserInterface.Systems.Actions.Controls; public sealed class ActionButton : Control, IEntityControl { private IEntityManager _entities; + private IPlayerManager _player; private SpriteSystem? _spriteSys; private ActionUIController? _controller; - private SharedChargesSystem _sharedChargesSys; private bool _beingHovered; private bool _depressed; private bool _toggled; @@ -67,8 +67,8 @@ public sealed class ActionButton : Control, IEntityControl // TODO why is this constructor so slooooow. The rest of the code is fine _entities = entities; + _player = IoCManager.Resolve(); _spriteSys = spriteSys; - _sharedChargesSys = _entities.System(); _controller = controller; MouseFilter = MouseFilterMode.Pass; @@ -197,23 +197,17 @@ public sealed class ActionButton : Control, IEntityControl return null; var name = FormattedMessage.FromMarkupPermissive(Loc.GetString(metadata.EntityName)); - var decr = FormattedMessage.FromMarkupPermissive(Loc.GetString(metadata.EntityDescription)); - FormattedMessage? chargesText = null; + var desc = FormattedMessage.FromMarkupPermissive(Loc.GetString(metadata.EntityDescription)); - // TODO: Don't touch this use an event make callers able to add their own shit for actions or I kill you. - if (_entities.TryGetComponent(Action, out LimitedChargesComponent? actionCharges)) - { - var charges = _sharedChargesSys.GetCurrentCharges((Action.Value, actionCharges, null)); - chargesText = FormattedMessage.FromMarkupPermissive(Loc.GetString($"Charges: {charges.ToString()}/{actionCharges.MaxCharges}")); + if (_player.LocalEntity is null) + return null; - if (_entities.TryGetComponent(Action, out AutoRechargeComponent? autoRecharge)) - { - var chargeTimeRemaining = _sharedChargesSys.GetNextRechargeTime((Action.Value, actionCharges, autoRecharge)); - chargesText.AddText(Loc.GetString($"{Environment.NewLine}Time Til Recharge: {chargeTimeRemaining}")); - } - } + var ev = new ExaminedEvent(desc, Action.Value, _player.LocalEntity.Value, true, !desc.IsEmpty); + _entities.EventBus.RaiseLocalEvent(Action.Value.Owner, ev); - return new ActionAlertTooltip(name, decr, charges: chargesText); + var newDesc = ev.GetTotalMessage(); + + return new ActionAlertTooltip(name, newDesc); } protected override void ControlFocusExited() diff --git a/Content.Client/UserInterface/Systems/Hands/HandsUIController.cs b/Content.Client/UserInterface/Systems/Hands/HandsUIController.cs index d6573004d4..5514c6d347 100644 --- a/Content.Client/UserInterface/Systems/Hands/HandsUIController.cs +++ b/Content.Client/UserInterface/Systems/Hands/HandsUIController.cs @@ -12,6 +12,7 @@ using Robust.Client.Player; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controllers; using Robust.Shared.Input; +using Robust.Shared.Prototypes; using Robust.Shared.Timing; using Robust.Shared.Utility; @@ -73,7 +74,8 @@ public sealed class HandsUIController : UIController, IOnStateEntered entity, string name) @@ -139,7 +141,7 @@ public sealed class HandsUIController : UIController, IOnStateEntered(_entity, out var meta) || meta.Deleted) { - Update(null); + Update(null, hand); return; } diff --git a/Content.IntegrationTests/AssemblyInfo.cs b/Content.IntegrationTests/AssemblyInfo.cs index 76fc42f3a9..b8a88e2623 100644 --- a/Content.IntegrationTests/AssemblyInfo.cs +++ b/Content.IntegrationTests/AssemblyInfo.cs @@ -5,4 +5,4 @@ // https://github.com/dotnet/runtime/issues/107197 // So we can't really parallelize integration tests harder either until the runtime fixes that, // *or* we fix serv3 to not spam expression trees. -[assembly: LevelOfParallelism(3)] +[assembly: LevelOfParallelism(2)] diff --git a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs index 04d23e3f6d..d8e77b6196 100644 --- a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs @@ -11,7 +11,6 @@ using Content.Server.Mind; using Content.Server.Roles; using Content.Server.RoundEnd; using Content.Server.Shuttles.Components; -using Content.Server.Station.Components; using Content.Shared.CCVar; using Content.Shared.Damage; using Content.Shared.FixedPoint; @@ -22,6 +21,7 @@ using Content.Shared.NPC.Prototypes; using Content.Shared.NPC.Systems; using Content.Shared.NukeOps; using Content.Shared.Pinpointer; +using Content.Shared.Roles.Components; using Content.Shared.Station.Components; using Robust.Server.GameObjects; using Robust.Shared.GameObjects; diff --git a/Content.IntegrationTests/Tests/Minds/MindTests.cs b/Content.IntegrationTests/Tests/Minds/MindTests.cs index 48e11e4648..2f77519829 100644 --- a/Content.IntegrationTests/Tests/Minds/MindTests.cs +++ b/Content.IntegrationTests/Tests/Minds/MindTests.cs @@ -3,7 +3,6 @@ using System.Linq; using Content.Server.Ghost.Roles; using Content.Server.Ghost.Roles.Components; using Content.Server.Mind; -using Content.Server.Roles; using Content.Shared.Damage; using Content.Shared.Damage.Prototypes; using Content.Shared.FixedPoint; @@ -11,7 +10,7 @@ using Content.Shared.Mind; using Content.Shared.Mind.Components; using Content.Shared.Players; using Content.Shared.Roles; -using Content.Shared.Roles.Jobs; +using Content.Shared.Roles.Components; using Robust.Server.Console; using Robust.Server.GameObjects; using Robust.Server.Player; diff --git a/Content.IntegrationTests/Tests/Minds/RoleTests.cs b/Content.IntegrationTests/Tests/Minds/RoleTests.cs index 8acfff3fb9..f0a7268a3d 100644 --- a/Content.IntegrationTests/Tests/Minds/RoleTests.cs +++ b/Content.IntegrationTests/Tests/Minds/RoleTests.cs @@ -1,7 +1,5 @@ using System.Linq; -using Content.Server.Roles; -using Content.Shared.Roles; -using Content.Shared.Roles.Jobs; +using Content.Shared.Roles.Components; using Robust.Shared.GameObjects; using Robust.Shared.Reflection; diff --git a/Content.Server/Access/Systems/AgentIDCardSystem.cs b/Content.Server/Access/Systems/AgentIDCardSystem.cs index 22c5c10b19..8d19ec3d39 100644 --- a/Content.Server/Access/Systems/AgentIDCardSystem.cs +++ b/Content.Server/Access/Systems/AgentIDCardSystem.cs @@ -14,6 +14,7 @@ using Content.Shared._Sunrise.Biocode; using Content.Server.Implants; using Content.Shared.Implants; using Content.Shared.Inventory; +using Content.Shared.Lock; using Content.Shared.PDA; namespace Content.Server.Access.Systems @@ -26,6 +27,7 @@ namespace Content.Server.Access.Systems [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly ChameleonClothingSystem _chameleon = default!; [Dependency] private readonly ChameleonControllerSystem _chamController = default!; + [Dependency] private readonly LockSystem _lock = default!; [Dependency] private readonly BiocodeSystem _biocodeSystem = default!; public override void Initialize() @@ -81,7 +83,8 @@ namespace Content.Server.Access.Systems private void OnAfterInteract(EntityUid uid, AgentIDCardComponent component, AfterInteractEvent args) { - if (args.Target == null || !args.CanReach || !TryComp(args.Target, out var targetAccess) || !HasComp(args.Target)) + if (args.Target == null || !args.CanReach || _lock.IsLocked(uid) || + !TryComp(args.Target, out var targetAccess) || !HasComp(args.Target)) return; // Sunrise-Start diff --git a/Content.Server/Administration/Systems/AdminSystem.cs b/Content.Server/Administration/Systems/AdminSystem.cs index e60a6790b6..61651ab4dc 100644 --- a/Content.Server/Administration/Systems/AdminSystem.cs +++ b/Content.Server/Administration/Systems/AdminSystem.cs @@ -1,7 +1,6 @@ using System.Linq; using Content.Server.Administration.Managers; using Content.Server.Chat.Managers; -using Content.Server.Forensics; using Content.Server.GameTicking; using Content.Server.Hands.Systems; using Content.Server.Mind; @@ -21,6 +20,7 @@ using Content.Shared.PDA; using Content.Shared.Players.PlayTimeTracking; using Content.Shared.Popups; using Content.Shared.Roles; +using Content.Shared.Roles.Components; using Content.Shared.Roles.Jobs; using Content.Shared.StationRecords; using Content.Shared.Throwing; diff --git a/Content.Server/Construction/ConstructionSystem.Interactions.cs b/Content.Server/Construction/ConstructionSystem.Interactions.cs index 74c856a6f1..dd69fe4e13 100644 --- a/Content.Server/Construction/ConstructionSystem.Interactions.cs +++ b/Content.Server/Construction/ConstructionSystem.Interactions.cs @@ -276,8 +276,8 @@ namespace Content.Server.Construction if(!insertStep.EntityValid(insert, EntityManager, Factory)) return HandleResult.False; - // Unremovable items can't be inserted, unless they are a lingering stack - if(HasComp(insert) && (!TryComp(insert, out var comp) || !comp.Lingering)) + // Unremovable items can't be inserted + if(HasComp(insert)) return HandleResult.False; // If we're only testing whether this step would be handled by the given event, then we're done. diff --git a/Content.Server/Electrocution/ElectrocutionSystem.cs b/Content.Server/Electrocution/ElectrocutionSystem.cs index 05dfffa4a9..1f04421c0f 100644 --- a/Content.Server/Electrocution/ElectrocutionSystem.cs +++ b/Content.Server/Electrocution/ElectrocutionSystem.cs @@ -1,5 +1,4 @@ using Content.Server.Administration.Logs; -using Content.Server.Light.Components; using Content.Server.NodeContainer; using Content.Server.NodeContainer.EntitySystems; using Content.Server.NodeContainer.NodeGroups; @@ -16,6 +15,7 @@ using Content.Shared.IdentityManagement; using Content.Shared.Interaction; using Content.Shared.Inventory; using Content.Shared.Jittering; +using Content.Shared.Light.Components; using Content.Shared.Maps; using Content.Shared.NodeContainer; using Content.Shared.NodeContainer.NodeGroups; diff --git a/Content.Server/GameTicking/GameTicker.RoundFlow.cs b/Content.Server/GameTicking/GameTicker.RoundFlow.cs index 755a0bc0b6..3e57e62093 100644 --- a/Content.Server/GameTicking/GameTicker.RoundFlow.cs +++ b/Content.Server/GameTicking/GameTicker.RoundFlow.cs @@ -3,7 +3,6 @@ using System.Numerics; using Content.Server.Announcements; using Content.Server.Discord; using Content.Server.GameTicking.Events; -using Content.Server.Ghost; using Content.Server.Maps; using Content.Server.Roles; using Content.Server.Shuttles.Components; @@ -13,6 +12,7 @@ using Content.Shared.GameTicking; using Content.Shared.Mind; using Content.Shared.Players; using Content.Shared.Preferences; +using Content.Shared.Roles.Components; using JetBrains.Annotations; using Prometheus; using Robust.Shared.Asynchronous; @@ -20,7 +20,6 @@ using Robust.Shared.Audio; using Robust.Shared.EntitySerialization; using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.Map; -using Robust.Shared.Map.Components; using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Random; diff --git a/Content.Server/GameTicking/Rules/DragonRuleSystem.cs b/Content.Server/GameTicking/Rules/DragonRuleSystem.cs index 964b248beb..53cf3bd4b0 100644 --- a/Content.Server/GameTicking/Rules/DragonRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/DragonRuleSystem.cs @@ -4,6 +4,7 @@ using Content.Server.Mind; using Content.Server.Roles; using Content.Server.Station.Systems; using Content.Shared.Localizations; +using Content.Shared.Roles.Components; using Robust.Server.GameObjects; namespace Content.Server.GameTicking.Rules; diff --git a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs index 6549795ae1..459bd1c496 100644 --- a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs @@ -17,6 +17,7 @@ using Content.Shared.NPC.Components; using Content.Shared.NPC.Systems; using Content.Shared.Nuke; using Content.Shared.NukeOps; +using Content.Shared.Roles.Components; using Content.Shared.Store; using Content.Shared.Tag; using Content.Shared.Zombies; diff --git a/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs b/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs index 1cc1d649d6..a9cfd247e3 100644 --- a/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs @@ -25,6 +25,7 @@ using Content.Shared.Mobs.Systems; using Content.Shared.NPC.Prototypes; using Content.Shared.NPC.Systems; using Content.Shared.Revolutionary.Components; +using Content.Shared.Roles.Components; using Content.Shared.Stunnable; using Content.Shared.Zombies; using Robust.Shared.Prototypes; @@ -204,7 +205,10 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem(revMindId, out var role)) + { role.Value.Comp2.ConvertedCount++; + Dirty(role.Value.Owner, role.Value.Comp2); + } } } diff --git a/Content.Server/GameTicking/Rules/SurvivorRuleSystem.cs b/Content.Server/GameTicking/Rules/SurvivorRuleSystem.cs index 4990b98b91..d673444665 100644 --- a/Content.Server/GameTicking/Rules/SurvivorRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/SurvivorRuleSystem.cs @@ -6,6 +6,7 @@ using Content.Server.Shuttles.Systems; using Content.Shared.GameTicking.Components; using Content.Shared.Mind; using Content.Shared.Mobs.Systems; +using Content.Shared.Roles.Components; using Content.Shared.Survivor.Components; using Content.Shared.Tag; using Robust.Server.GameObjects; diff --git a/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs b/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs index 3f515d4505..a3c5f0c951 100644 --- a/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs @@ -2,6 +2,7 @@ using Content.Server.Antag; using Content.Server.GameTicking.Rules.Components; using Content.Server.Roles; using Content.Shared.Humanoid; +using Content.Shared.Roles.Components; using Content.Shared.NPC.Systems; namespace Content.Server.GameTicking.Rules; diff --git a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs index 0a2882aa3c..e72e9d5f73 100644 --- a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs @@ -1,19 +1,16 @@ -using Content.Server.Administration.Logs; using Content.Server.Antag; using Content.Server.GameTicking.Rules.Components; using Content.Server.Mind; using Content.Server.Objectives; using Content.Server.PDA.Ringer; -using Content.Server.Roles; using Content.Server.Traitor.Uplink; -using Content.Shared.Database; using Content.Shared.FixedPoint; -using Content.Shared.GameTicking.Components; using Content.Shared.Mind; using Content.Shared.NPC.Systems; using Content.Shared.PDA; using Content.Shared.Random.Helpers; using Content.Shared.Roles; +using Content.Shared.Roles.Components; using Content.Shared.Roles.Jobs; using Content.Shared.Roles.RoleCodeword; using Robust.Shared.Prototypes; diff --git a/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs b/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs index 9fab98446a..c6da622bb4 100644 --- a/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs @@ -13,6 +13,7 @@ using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Content.Shared.Roles; +using Content.Shared.Roles.Components; using Content.Shared.Zombies; using Robust.Shared.Player; using Robust.Shared.Timing; diff --git a/Content.Server/Ghost/ObserverRoleComponent.cs b/Content.Server/Ghost/ObserverRoleComponent.cs deleted file mode 100644 index 8421fb7343..0000000000 --- a/Content.Server/Ghost/ObserverRoleComponent.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Content.Shared.Roles; - -namespace Content.Server.Ghost; - -/// -/// This is used to mark Observers properly, as they get Minds -/// -[RegisterComponent] -public sealed partial class ObserverRoleComponent : BaseMindRoleComponent -{ - public string Name => Loc.GetString("observer-role-name"); -} diff --git a/Content.Server/Ghost/Roles/GhostRoleMarkerRoleComponent.cs b/Content.Server/Ghost/Roles/GhostRoleMarkerRoleComponent.cs deleted file mode 100644 index da3e89ba2b..0000000000 --- a/Content.Server/Ghost/Roles/GhostRoleMarkerRoleComponent.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Content.Shared.Roles; - -namespace Content.Server.Ghost.Roles; - -/// -/// Added to mind role entities to tag that they are a ghostrole. -/// It also holds the name for the round end display -/// -[RegisterComponent] -public sealed partial class GhostRoleMarkerRoleComponent : BaseMindRoleComponent -{ - //TODO does anything still use this? It gets populated by GhostRolesystem but I don't see anything ever reading it - [DataField] public string? Name; - -} diff --git a/Content.Server/Ghost/Roles/GhostRoleSystem.cs b/Content.Server/Ghost/Roles/GhostRoleSystem.cs index 46e5052506..652097dc96 100644 --- a/Content.Server/Ghost/Roles/GhostRoleSystem.cs +++ b/Content.Server/Ghost/Roles/GhostRoleSystem.cs @@ -565,9 +565,6 @@ public sealed class GhostRoleSystem : EntitySystem _mindSystem.TransferTo(newMind, mob); _roleSystem.MindAddRoles(newMind.Owner, role.MindRoles, newMind.Comp); - - if (_roleSystem.MindHasRole(newMind!, out var markerRole)) - markerRole.Value.Comp2.Name = role.RoleName; } /// diff --git a/Content.Server/Light/EntitySystems/LightBulbSystem.cs b/Content.Server/Light/EntitySystems/LightBulbSystem.cs index 5714bde3e5..ba5b795e2d 100644 --- a/Content.Server/Light/EntitySystems/LightBulbSystem.cs +++ b/Content.Server/Light/EntitySystems/LightBulbSystem.cs @@ -1,87 +1,5 @@ -using Content.Server.Light.Components; -using Content.Shared.Destructible; -using Content.Shared.Light.Components; -using Content.Shared.Throwing; -using Robust.Server.GameObjects; -using Robust.Shared.Audio; -using Robust.Shared.Audio.Systems; -using Robust.Shared.Player; +using Content.Shared.Light.EntitySystems; -namespace Content.Server.Light.EntitySystems -{ - public sealed class LightBulbSystem : EntitySystem - { - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; +namespace Content.Server.Light.EntitySystems; - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(HandleLand); - SubscribeLocalEvent(OnBreak); - } - - private void OnInit(EntityUid uid, LightBulbComponent bulb, ComponentInit args) - { - // update default state of bulbs - SetColor(uid, bulb.Color, bulb); - SetState(uid, bulb.State, bulb); - } - - private void HandleLand(EntityUid uid, LightBulbComponent bulb, ref LandEvent args) - { - PlayBreakSound(uid, bulb); - SetState(uid, LightBulbState.Broken, bulb); - } - - private void OnBreak(EntityUid uid, LightBulbComponent component, BreakageEventArgs args) - { - SetState(uid, LightBulbState.Broken, component); - } - - /// - /// Set a new color for a light bulb and raise event about change - /// - public void SetColor(EntityUid uid, Color color, LightBulbComponent? bulb = null) - { - if (!Resolve(uid, ref bulb)) - return; - - bulb.Color = color; - UpdateAppearance(uid, bulb); - } - - /// - /// Set a new state for a light bulb (broken, burned) and raise event about change - /// - public void SetState(EntityUid uid, LightBulbState state, LightBulbComponent? bulb = null) - { - if (!Resolve(uid, ref bulb)) - return; - - bulb.State = state; - UpdateAppearance(uid, bulb); - } - - public void PlayBreakSound(EntityUid uid, LightBulbComponent? bulb = null) - { - if (!Resolve(uid, ref bulb)) - return; - - _audio.PlayPvs(bulb.BreakSound, uid); - } - - private void UpdateAppearance(EntityUid uid, LightBulbComponent? bulb = null, - AppearanceComponent? appearance = null) - { - if (!Resolve(uid, ref bulb, ref appearance, logMissing: false)) - return; - - // try to update appearance and color - _appearance.SetData(uid, LightBulbVisuals.State, bulb.State, appearance); - _appearance.SetData(uid, LightBulbVisuals.Color, bulb.Color, appearance); - } - } -} +public sealed class LightBulbSystem : SharedLightBulbSystem; diff --git a/Content.Server/Light/EntitySystems/PoweredLightSystem.cs b/Content.Server/Light/EntitySystems/PoweredLightSystem.cs index b5b9f20432..948c44cd75 100644 --- a/Content.Server/Light/EntitySystems/PoweredLightSystem.cs +++ b/Content.Server/Light/EntitySystems/PoweredLightSystem.cs @@ -1,440 +1,64 @@ -using Content.Server.DeviceLinking.Systems; -using Content.Server.DeviceNetwork; -using Content.Server.DeviceNetwork.Systems; using Content.Server.Emp; using Content.Server.Ghost; -using Content.Server.Light.Components; -using Content.Server.Power.Components; -using Content.Shared.Audio; -using Content.Shared.Damage; -using Content.Shared.DeviceLinking.Events; -using Content.Shared.DoAfter; -using Content.Shared.Hands.EntitySystems; -using Content.Shared.Interaction; -using Content.Shared.Light; using Content.Shared.Light.Components; -using Robust.Server.GameObjects; -using Robust.Shared.Containers; -using Robust.Shared.Timing; -using Robust.Shared.Audio.Systems; -using Content.Shared.Damage.Systems; -using Content.Shared.Damage.Components; -using Content.Shared.DeviceNetwork; -using Content.Shared.DeviceNetwork.Events; -using Content.Shared.Power; +using Content.Shared.Light.EntitySystems; -namespace Content.Server.Light.EntitySystems +namespace Content.Server.Light.EntitySystems; + +/// +/// System for the PoweredLightComponents +/// +public sealed class PoweredLightSystem : SharedPoweredLightSystem { - /// - /// System for the PoweredLightComponents - /// - public sealed class PoweredLightSystem : EntitySystem + public override void Initialize() { - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly SharedAmbientSoundSystem _ambientSystem = default!; - [Dependency] private readonly LightBulbSystem _bulbSystem = default!; - [Dependency] private readonly SharedHandsSystem _handsSystem = default!; - [Dependency] private readonly DeviceLinkSystem _signalSystem = default!; - [Dependency] private readonly SharedContainerSystem _containerSystem = default!; - [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly PointLightSystem _pointLight = default!; - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly DamageOnInteractSystem _damageOnInteractSystem = default!; + base.Initialize(); + SubscribeLocalEvent(OnMapInit); - private static readonly TimeSpan ThunkDelay = TimeSpan.FromSeconds(2); - public const string LightBulbContainer = "light_bulb"; + SubscribeLocalEvent(OnGhostBoo); - public override void Initialize() + SubscribeLocalEvent(OnEmpPulse); + } + + private void OnGhostBoo(EntityUid uid, PoweredLightComponent light, GhostBooEvent args) + { + if (light.IgnoreGhostsBoo) + return; + + // check cooldown first to prevent abuse + var time = GameTiming.CurTime; + if (light.LastGhostBlink != null) { - base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnMapInit); - SubscribeLocalEvent(OnInteractUsing); - SubscribeLocalEvent(OnInteractHand); - - SubscribeLocalEvent(OnGhostBoo); - SubscribeLocalEvent(HandleLightDamaged); - - SubscribeLocalEvent(OnSignalReceived); - SubscribeLocalEvent(OnPacketReceived); - - SubscribeLocalEvent(OnPowerChanged); - - SubscribeLocalEvent(OnDoAfter); - SubscribeLocalEvent(OnEmpPulse); - } - - private void OnInit(EntityUid uid, PoweredLightComponent light, ComponentInit args) - { - light.LightBulbContainer = _containerSystem.EnsureContainer(uid, LightBulbContainer); - _signalSystem.EnsureSinkPorts(uid, light.OnPort, light.OffPort, light.TogglePort); - } - - private void OnMapInit(EntityUid uid, PoweredLightComponent light, MapInitEvent args) - { - // TODO: Use ContainerFill dog - if (light.HasLampOnSpawn != null) - { - var entity = Spawn(light.HasLampOnSpawn, Comp(uid).Coordinates); - _containerSystem.Insert(entity, light.LightBulbContainer); - } - // need this to update visualizers - UpdateLight(uid, light); - } - - private void OnInteractUsing(EntityUid uid, PoweredLightComponent component, InteractUsingEvent args) - { - if (args.Handled) + if (time <= light.LastGhostBlink + light.GhostBlinkingCooldown) return; - - args.Handled = InsertBulb(uid, args.Used, component); } - private void OnInteractHand(EntityUid uid, PoweredLightComponent light, InteractHandEvent args) + light.LastGhostBlink = time; + + ToggleBlinkingLight(uid, light, true); + uid.SpawnTimer(light.GhostBlinkingTime, () => { - if (args.Handled) - return; + ToggleBlinkingLight(uid, light, false); + }); - // check if light has bulb to eject - var bulbUid = GetBulb(uid, light); - if (bulbUid == null) - return; + args.Handled = true; + } - var userUid = args.User; - //removing a broken/burned bulb, so allow instant removal - if(TryComp(bulbUid.Value, out var bulb) && bulb.State != LightBulbState.Normal) - { - args.Handled = EjectBulb(uid, userUid, light) != null; - return; - } - - // removing a working bulb, so require a delay - _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, userUid, light.EjectBulbDelay, new PoweredLightDoAfterEvent(), uid, target: uid) - { - BreakOnMove = true, - BreakOnDamage = true, - }); - - args.Handled = true; - } - - #region Bulb Logic API - /// - /// Inserts the bulb if possible. - /// - /// True if it could insert it, false if it couldn't. - public bool InsertBulb(EntityUid uid, EntityUid bulbUid, PoweredLightComponent? light = null) + private void OnMapInit(EntityUid uid, PoweredLightComponent light, MapInitEvent args) + { + // TODO: Use ContainerFill dog + if (light.HasLampOnSpawn != null) { - if (!Resolve(uid, ref light)) - return false; - - // check if light already has bulb - if (GetBulb(uid, light) != null) - return false; - - // check if bulb fits - if (!TryComp(bulbUid, out LightBulbComponent? lightBulb)) - return false; - if (lightBulb.Type != light.BulbType) - return false; - - // try to insert bulb in container - if (!_containerSystem.Insert(bulbUid, light.LightBulbContainer)) - return false; - - UpdateLight(uid, light); - return true; + var entity = EntityManager.SpawnEntity(light.HasLampOnSpawn, EntityManager.GetComponent(uid).Coordinates); + ContainerSystem.Insert(entity, light.LightBulbContainer); } + // need this to update visualizers + UpdateLight(uid, light); + } - /// - /// Ejects the bulb to a mob's hand if possible. - /// - /// Bulb uid if it was successfully ejected, null otherwise - public EntityUid? EjectBulb(EntityUid uid, EntityUid? userUid = null, PoweredLightComponent? light = null) - { - if (!Resolve(uid, ref light)) - return null; - - // check if light has bulb - if (GetBulb(uid, light) is not { Valid: true } bulb) - return null; - - // try to remove bulb from container - if (!_containerSystem.Remove(bulb, light.LightBulbContainer)) - return null; - - // try to place bulb in hands - _handsSystem.PickupOrDrop(userUid, bulb); - - UpdateLight(uid, light); - return bulb; - } - - /// - /// Replaces the spawned prototype of a pre-mapinit powered light with a different variant. - /// - public bool ReplaceSpawnedPrototype(Entity light, string bulb) - { - if (light.Comp.LightBulbContainer.ContainedEntity != null) - return false; - - if (LifeStage(light.Owner) >= EntityLifeStage.MapInitialized) - return false; - - light.Comp.HasLampOnSpawn = bulb; - return true; - } - - /// - /// Try to replace current bulb with a new one - /// If succeed old bulb just drops on floor - /// - public bool ReplaceBulb(EntityUid uid, EntityUid bulb, PoweredLightComponent? light = null) - { - EjectBulb(uid, null, light); - return InsertBulb(uid, bulb, light); - } - - /// - /// Try to get light bulb inserted in powered light - /// - /// Bulb uid if it exist, null otherwise - public EntityUid? GetBulb(EntityUid uid, PoweredLightComponent? light = null) - { - if (!Resolve(uid, ref light)) - return null; - - return light.LightBulbContainer.ContainedEntity; - } - - /// - /// Try to break bulb inside light fixture - /// - public bool TryDestroyBulb(EntityUid uid, PoweredLightComponent? light = null) - { - if (!Resolve(uid, ref light, false)) - return false; - - // if we aren't mapinited, - // just null the spawned bulb - if (LifeStage(uid) < EntityLifeStage.MapInitialized) - { - light.HasLampOnSpawn = null; - return true; - } - - // check bulb state - var bulbUid = GetBulb(uid, light); - if (bulbUid == null || !TryComp(bulbUid.Value, out LightBulbComponent? lightBulb)) - return false; - if (lightBulb.State == LightBulbState.Broken) - return false; - - // break it - _bulbSystem.SetState(bulbUid.Value, LightBulbState.Broken, lightBulb); - _bulbSystem.PlayBreakSound(bulbUid.Value, lightBulb); - UpdateLight(uid, light); - return true; - } - #endregion - - private void UpdateLight(EntityUid uid, - PoweredLightComponent? light = null, - ApcPowerReceiverComponent? powerReceiver = null, - AppearanceComponent? appearance = null) - { - if (!Resolve(uid, ref light, ref powerReceiver, false)) - return; - - // Optional component. - Resolve(uid, ref appearance, false); - - // check if light has bulb - var bulbUid = GetBulb(uid, light); - if (bulbUid == null || !TryComp(bulbUid.Value, out LightBulbComponent? lightBulb)) - { - SetLight(uid, false, light: light); - powerReceiver.Load = 0; - _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Empty, appearance); - return; - } - - switch (lightBulb.State) - { - case LightBulbState.Normal: - if (powerReceiver.Powered && light.On) - { - SetLight(uid, true, lightBulb.Color, light, lightBulb.LightRadius, lightBulb.LightEnergy, lightBulb.LightSoftness); - _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.On, appearance); - var time = _gameTiming.CurTime; - if (time > light.LastThunk + ThunkDelay) - { - light.LastThunk = time; - _audio.PlayPvs(light.TurnOnSound, uid, light.TurnOnSound.Params.AddVolume(-10f)); - } - } - else - { - SetLight(uid, false, light: light); - _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Off, appearance); - } - break; - case LightBulbState.Broken: - SetLight(uid, false, light: light); - _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Broken, appearance); - break; - case LightBulbState.Burned: - SetLight(uid, false, light: light); - _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Burned, appearance); - break; - } - - powerReceiver.Load = (light.On && lightBulb.State == LightBulbState.Normal) ? lightBulb.PowerUse : 0; - } - - /// - /// Destroy the light bulb if the light took any damage. - /// - public void HandleLightDamaged(EntityUid uid, PoweredLightComponent component, DamageChangedEvent args) - { - // Was it being repaired, or did it take damage? - if (args.DamageIncreased) - { - // Eventually, this logic should all be done by this (or some other) system, not a component. - TryDestroyBulb(uid, component); - } - } - - private void OnGhostBoo(EntityUid uid, PoweredLightComponent light, GhostBooEvent args) - { - if (light.IgnoreGhostsBoo) - return; - - // check cooldown first to prevent abuse - var time = _gameTiming.CurTime; - if (light.LastGhostBlink != null) - { - if (time <= light.LastGhostBlink + light.GhostBlinkingCooldown) - return; - } - - light.LastGhostBlink = time; - - ToggleBlinkingLight(uid, light, true); - uid.SpawnTimer(light.GhostBlinkingTime, () => - { - ToggleBlinkingLight(uid, light, false); - }); - - args.Handled = true; - } - - private void OnPowerChanged(EntityUid uid, PoweredLightComponent component, ref PowerChangedEvent args) - { - // TODO: Power moment - var metadata = MetaData(uid); - - if (metadata.EntityPaused || TerminatingOrDeleted(uid, metadata)) - return; - - UpdateLight(uid, component); - } - - public void ToggleBlinkingLight(EntityUid uid, PoweredLightComponent light, bool isNowBlinking) - { - if (light.IsBlinking == isNowBlinking) - return; - - light.IsBlinking = isNowBlinking; - - if (!TryComp(uid, out AppearanceComponent? appearance)) - return; - - _appearance.SetData(uid, PoweredLightVisuals.Blinking, isNowBlinking, appearance); - } - - private void OnSignalReceived(EntityUid uid, PoweredLightComponent component, ref SignalReceivedEvent args) - { - if (args.Port == component.OffPort) - SetState(uid, false, component); - else if (args.Port == component.OnPort) - SetState(uid, true, component); - else if (args.Port == component.TogglePort) - ToggleLight(uid, component); - } - - /// - /// Turns the light on or of when receiving a command. - /// The light is turned on or of according to the value - /// - private void OnPacketReceived(EntityUid uid, PoweredLightComponent component, DeviceNetworkPacketEvent args) - { - if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? command) || command != DeviceNetworkConstants.CmdSetState) return; - if (!args.Data.TryGetValue(DeviceNetworkConstants.StateEnabled, out bool enabled)) return; - - SetState(uid, enabled, component); - } - - private void SetLight(EntityUid uid, bool value, Color? color = null, PoweredLightComponent? light = null, float? radius = null, float? energy = null, float? softness = null) - { - if (!Resolve(uid, ref light)) - return; - - light.CurrentLit = value; - _ambientSystem.SetAmbience(uid, value); - - if (TryComp(uid, out PointLightComponent? pointLight)) - { - _pointLight.SetEnabled(uid, value, pointLight); - - if (color != null) - _pointLight.SetColor(uid, color.Value, pointLight); - if (radius != null) - _pointLight.SetRadius(uid, (float) radius, pointLight); - if (energy != null) - _pointLight.SetEnergy(uid, (float) energy, pointLight); - if (softness != null) - _pointLight.SetSoftness(uid, (float) softness, pointLight); - } - - // light bulbs burn your hands! - if (TryComp(uid, out var damageOnInteractComp)) - _damageOnInteractSystem.SetIsDamageActiveTo((uid, damageOnInteractComp), value); - } - - public void ToggleLight(EntityUid uid, PoweredLightComponent? light = null) - { - if (!Resolve(uid, ref light)) - return; - - light.On = !light.On; - UpdateLight(uid, light); - } - - public void SetState(EntityUid uid, bool state, PoweredLightComponent? light = null) - { - if (!Resolve(uid, ref light)) - return; - - light.On = state; - UpdateLight(uid, light); - } - - private void OnDoAfter(EntityUid uid, PoweredLightComponent component, DoAfterEvent args) - { - if (args.Handled || args.Cancelled || args.Args.Target == null) - return; - - EjectBulb(args.Args.Target.Value, args.Args.User, component); - - args.Handled = true; - } - - private void OnEmpPulse(EntityUid uid, PoweredLightComponent component, ref EmpPulseEvent args) - { - if (TryDestroyBulb(uid, component)) - args.Affected = true; - } + private void OnEmpPulse(EntityUid uid, PoweredLightComponent component, ref EmpPulseEvent args) + { + if (TryDestroyBulb(uid, component)) + args.Affected = true; } } diff --git a/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs b/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs index c7874d64fe..7af093b178 100644 --- a/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs +++ b/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs @@ -1,65 +1,25 @@ -using System.Numerics; -using Content.Server.Access.Systems; using Content.Server.DeviceNetwork.Systems; using Content.Server.Emp; using Content.Server.Medical.CrewMonitoring; -using Content.Server.Popups; -using Content.Server.Station.Systems; -using Content.Shared.ActionBlocker; -using Content.Shared.Clothing; -using Content.Shared.Damage; -using Content.Shared.DeviceNetwork; -using Content.Shared.DoAfter; -using Content.Shared.Examine; -using Content.Shared.GameTicking; -using Content.Shared.Interaction; -using Content.Shared.Inventory; -using Content.Shared.Medical.SuitSensor; -using Content.Shared.Mobs; -using Content.Shared.Mobs.Components; -using Content.Shared.Mobs.Systems; -using Content.Shared.Verbs; -using Robust.Shared.Containers; -using Robust.Shared.Map; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; -using Robust.Shared.Timing; using Content.Shared.DeviceNetwork.Components; +using Content.Shared.Medical.SuitSensor; +using Content.Shared.Medical.SuitSensors; +using Robust.Shared.Timing; namespace Content.Server.Medical.SuitSensors; -public sealed class SuitSensorSystem : EntitySystem +public sealed class SuitSensorSystem : SharedSuitSensorSystem { [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!; - [Dependency] private readonly IdCardSystem _idCardSystem = default!; - [Dependency] private readonly MobStateSystem _mobStateSystem = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly SharedTransformSystem _transform = default!; - [Dependency] private readonly StationSystem _stationSystem = default!; [Dependency] private readonly SingletonDeviceNetServerSystem _singletonServerSystem = default!; - [Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!; - [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; - [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; - [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; - [Dependency] private readonly IPrototypeManager _proto = default!; - [Dependency] private readonly InventorySystem _inventory = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnPlayerSpawn); - SubscribeLocalEvent(OnMapInit); - SubscribeLocalEvent(OnEquipped); - SubscribeLocalEvent(OnUnequipped); - SubscribeLocalEvent(OnExamine); - SubscribeLocalEvent>(OnVerb); - SubscribeLocalEvent(OnInsert); - SubscribeLocalEvent(OnRemove); + SubscribeLocalEvent(OnEmpPulse); SubscribeLocalEvent(OnEmpFinished); - SubscribeLocalEvent(OnSuitSensorDoAfter); } public override void Update(float frameTime) @@ -78,14 +38,13 @@ public sealed class SuitSensorSystem : EntitySystem if (curTime < sensor.NextUpdate) continue; - if (!CheckSensorAssignedStation(uid, sensor)) + if (!CheckSensorAssignedStation((uid, sensor))) continue; - // TODO: This would cause imprecision at different tick rates. - sensor.NextUpdate = curTime + sensor.UpdateRate; + sensor.NextUpdate += sensor.UpdateRate; // get sensor status - var status = GetSensorState(uid, sensor); + var status = GetSensorState((uid, sensor)); if (status == null) continue; @@ -112,406 +71,21 @@ public sealed class SuitSensorSystem : EntitySystem } } - /// - /// Checks whether the sensor is assigned to a station or not - /// and tries to assign an unassigned sensor to a station if it's currently on a grid - /// - /// True if the sensor is assigned to a station or assigning it was successful. False otherwise. - private bool CheckSensorAssignedStation(EntityUid uid, SuitSensorComponent sensor) - { - if (!sensor.StationId.HasValue && Transform(uid).GridUid == null) - return false; - - sensor.StationId = _stationSystem.GetOwningStation(uid); - return sensor.StationId.HasValue; - } - - private void OnPlayerSpawn(PlayerSpawnCompleteEvent ev) - { - // If the player spawns in arrivals then the grid underneath them may not be appropriate. - // in which case we'll just use the station spawn code told us they are attached to and set all of their - // sensors. - var sensorQuery = GetEntityQuery(); - var xformQuery = GetEntityQuery(); - RecursiveSensor(ev.Mob, ev.Station, sensorQuery, xformQuery); - } - - private void RecursiveSensor(EntityUid uid, EntityUid stationUid, EntityQuery sensorQuery, EntityQuery xformQuery) - { - var xform = xformQuery.GetComponent(uid); - var enumerator = xform.ChildEnumerator; - - while (enumerator.MoveNext(out var child)) - { - if (sensorQuery.TryGetComponent(child, out var sensor)) - { - sensor.StationId = stationUid; - } - - RecursiveSensor(child, stationUid, sensorQuery, xformQuery); - } - } - - private void OnMapInit(EntityUid uid, SuitSensorComponent component, MapInitEvent args) - { - // Fallback - component.StationId ??= _stationSystem.GetOwningStation(uid); - - // generate random mode - if (component.RandomMode) - { - //make the sensor mode favor higher levels, except coords. - var modesDist = new[] - { - SuitSensorMode.SensorOff, - SuitSensorMode.SensorBinary, SuitSensorMode.SensorBinary, - SuitSensorMode.SensorVitals, SuitSensorMode.SensorVitals, SuitSensorMode.SensorVitals, - SuitSensorMode.SensorCords, SuitSensorMode.SensorCords - }; - component.Mode = _random.Pick(modesDist); - } - } - - private void OnEquipped(EntityUid uid, SuitSensorComponent component, ref ClothingGotEquippedEvent args) - { - component.User = args.Wearer; - } - - private void OnUnequipped(EntityUid uid, SuitSensorComponent component, ref ClothingGotUnequippedEvent args) - { - component.User = null; - } - - private void OnExamine(EntityUid uid, SuitSensorComponent component, ExaminedEvent args) - { - if (!args.IsInDetailsRange) - return; - - string msg; - switch (component.Mode) - { - case SuitSensorMode.SensorOff: - msg = "suit-sensor-examine-off"; - break; - case SuitSensorMode.SensorBinary: - msg = "suit-sensor-examine-binary"; - break; - case SuitSensorMode.SensorVitals: - msg = "suit-sensor-examine-vitals"; - break; - case SuitSensorMode.SensorCords: - msg = "suit-sensor-examine-cords"; - break; - default: - return; - } - - args.PushMarkup(Loc.GetString(msg)); - } - - private void OnVerb(EntityUid uid, SuitSensorComponent component, GetVerbsEvent args) - { - // check if user can change sensor - if (component.ControlsLocked) - return; - - // standard interaction checks - if (!args.CanInteract || args.Hands == null) - return; - - if (!_interactionSystem.InRangeUnobstructed(args.User, args.Target)) - return; - - // check if target is incapacitated (cuffed, dead, etc) - if (component.User != null && args.User != component.User && _actionBlocker.CanInteract(component.User.Value, null)) - return; - - args.Verbs.UnionWith(new[] - { - CreateVerb(uid, component, args.User, SuitSensorMode.SensorOff), - CreateVerb(uid, component, args.User, SuitSensorMode.SensorBinary), - CreateVerb(uid, component, args.User, SuitSensorMode.SensorVitals), - CreateVerb(uid, component, args.User, SuitSensorMode.SensorCords) - }); - } - - private void OnInsert(EntityUid uid, SuitSensorComponent component, EntGotInsertedIntoContainerMessage args) - { - if (args.Container.ID != component.ActivationContainer) - return; - - component.User = args.Container.Owner; - } - - private void OnRemove(EntityUid uid, SuitSensorComponent component, EntGotRemovedFromContainerMessage args) - { - if (args.Container.ID != component.ActivationContainer) - return; - - component.User = null; - } - - private void OnEmpPulse(EntityUid uid, SuitSensorComponent component, ref EmpPulseEvent args) + private void OnEmpPulse(Entity ent, ref EmpPulseEvent args) { args.Affected = true; args.Disabled = true; - component.PreviousMode = component.Mode; - SetSensor((uid, component), SuitSensorMode.SensorOff, null); + ent.Comp.PreviousMode = ent.Comp.Mode; + SetSensor(ent.AsNullable(), SuitSensorMode.SensorOff, null); - component.PreviousControlsLocked = component.ControlsLocked; - component.ControlsLocked = true; + ent.Comp.PreviousControlsLocked = ent.Comp.ControlsLocked; + ent.Comp.ControlsLocked = true; } - private void OnEmpFinished(EntityUid uid, SuitSensorComponent component, ref EmpDisabledRemoved args) + private void OnEmpFinished(Entity ent, ref EmpDisabledRemoved args) { - SetSensor((uid, component), component.PreviousMode, null); - component.ControlsLocked = component.PreviousControlsLocked; - } - - private Verb CreateVerb(EntityUid uid, SuitSensorComponent component, EntityUid userUid, SuitSensorMode mode) - { - return new Verb() - { - Text = GetModeName(mode), - Disabled = component.Mode == mode, - Priority = -(int) mode, // sort them in descending order - Category = VerbCategory.SetSensor, - Act = () => TrySetSensor((uid, component), mode, userUid) - }; - } - - private string GetModeName(SuitSensorMode mode) - { - string name; - switch (mode) - { - case SuitSensorMode.SensorOff: - name = "suit-sensor-mode-off"; - break; - case SuitSensorMode.SensorBinary: - name = "suit-sensor-mode-binary"; - break; - case SuitSensorMode.SensorVitals: - name = "suit-sensor-mode-vitals"; - break; - case SuitSensorMode.SensorCords: - name = "suit-sensor-mode-cords"; - break; - default: - return ""; - } - - return Loc.GetString(name); - } - - public void TrySetSensor(Entity sensors, SuitSensorMode mode, EntityUid userUid) - { - var comp = sensors.Comp; - - if (!Resolve(sensors, ref comp)) - return; - - if (comp.User == null || userUid == comp.User) - SetSensor(sensors, mode, userUid); - else - { - var doAfterEvent = new SuitSensorChangeDoAfterEvent(mode); - var doAfterArgs = new DoAfterArgs(EntityManager, userUid, comp.SensorsTime, doAfterEvent, sensors) - { - BreakOnMove = true, - BreakOnDamage = true - }; - - _doAfterSystem.TryStartDoAfter(doAfterArgs); - } - } - - private void OnSuitSensorDoAfter(Entity sensors, ref SuitSensorChangeDoAfterEvent args) - { - if (args.Handled || args.Cancelled) - return; - - SetSensor(sensors, args.Mode, args.User); - } - - public void SetSensor(Entity sensors, SuitSensorMode mode, EntityUid? userUid = null) - { - var comp = sensors.Comp; - - comp.Mode = mode; - - if (userUid != null) - { - var msg = Loc.GetString("suit-sensor-mode-state", ("mode", GetModeName(mode))); - _popupSystem.PopupEntity(msg, sensors, userUid.Value); - } - } - - /// - /// Set all suit sensors on the equipment someone is wearing to the specified mode. - /// - public void SetAllSensors(EntityUid target, SuitSensorMode mode, SlotFlags slots = SlotFlags.All ) - { - // iterate over all inventory slots - var slotEnumerator = _inventory.GetSlotEnumerator(target, slots); - while (slotEnumerator.NextItem(out var item, out _)) - { - if (TryComp(item, out var sensorComp)) - SetSensor((item, sensorComp), mode); - } - } - - public SuitSensorStatus? GetSensorState(EntityUid uid, SuitSensorComponent? sensor = null, TransformComponent? transform = null) - { - if (!Resolve(uid, ref sensor, ref transform)) - return null; - - // check if sensor is enabled and worn by user - if (sensor.Mode == SuitSensorMode.SensorOff || sensor.User == null || !HasComp(sensor.User) || transform.GridUid == null) - return null; - - var sensorTransform = Transform(uid); - var sensorMapId = sensorTransform.MapID; - - // try to get mobs id from ID slot - var userName = Loc.GetString("suit-sensor-component-unknown-name"); - var userJob = Loc.GetString("suit-sensor-component-unknown-job"); - var userJobIcon = "JobIconNoId"; - var userJobDepartments = new List(); - - if (_idCardSystem.TryFindIdCard(sensor.User.Value, out var card)) - { - if (card.Comp.FullName != null) - userName = card.Comp.FullName; - if (card.Comp.LocalizedJobTitle != null) - userJob = card.Comp.LocalizedJobTitle; - userJobIcon = card.Comp.JobIcon; - - foreach (var department in card.Comp.JobDepartments) - userJobDepartments.Add(Loc.GetString(_proto.Index(department).Name)); - } - - // get health mob state - var isAlive = false; - if (TryComp(sensor.User.Value, out MobStateComponent? mobState)) - isAlive = !_mobStateSystem.IsDead(sensor.User.Value, mobState); - - // get mob total damage - var totalDamage = 0; - if (TryComp(sensor.User.Value, out var damageable)) - totalDamage = damageable.TotalDamage.Int(); - - // Get mob total damage crit threshold - int? totalDamageThreshold = null; - if (_mobThresholdSystem.TryGetThresholdForState(sensor.User.Value, MobState.Critical, out var critThreshold)) - totalDamageThreshold = critThreshold.Value.Int(); - - // finally, form suit sensor status - var status = new SuitSensorStatus(GetNetEntity(sensor.User.Value), GetNetEntity(uid), userName, userJob, userJobIcon, userJobDepartments, sensorMapId); - switch (sensor.Mode) - { - case SuitSensorMode.SensorBinary: - status.IsAlive = isAlive; - break; - case SuitSensorMode.SensorVitals: - status.IsAlive = isAlive; - status.TotalDamage = totalDamage; - status.TotalDamageThreshold = totalDamageThreshold; - break; - case SuitSensorMode.SensorCords: - status.IsAlive = isAlive; - status.TotalDamage = totalDamage; - status.TotalDamageThreshold = totalDamageThreshold; - EntityCoordinates coordinates; - var xformQuery = GetEntityQuery(); - - if (transform.GridUid != null) - { - coordinates = new EntityCoordinates(transform.GridUid.Value, - Vector2.Transform(_transform.GetWorldPosition(transform, xformQuery), - _transform.GetInvWorldMatrix(xformQuery.GetComponent(transform.GridUid.Value), xformQuery))); - } - else if (transform.MapUid != null) - { - coordinates = new EntityCoordinates(transform.MapUid.Value, - _transform.GetWorldPosition(transform, xformQuery)); - } - else - { - coordinates = EntityCoordinates.Invalid; - } - - status.Coordinates = GetNetCoordinates(coordinates); - break; - } - - return status; - } - - /// - /// Serialize create a device network package from the suit sensors status. - /// - public NetworkPayload SuitSensorToPacket(SuitSensorStatus status) - { - var payload = new NetworkPayload() - { - [DeviceNetworkConstants.Command] = DeviceNetworkConstants.CmdUpdatedState, - [SuitSensorConstants.NET_NAME] = status.Name, - [SuitSensorConstants.NET_JOB] = status.Job, - [SuitSensorConstants.NET_JOB_ICON] = status.JobIcon, - [SuitSensorConstants.NET_JOB_DEPARTMENTS] = status.JobDepartments, - [SuitSensorConstants.NET_IS_ALIVE] = status.IsAlive, - [SuitSensorConstants.NET_SUIT_SENSOR_UID] = status.SuitSensorUid, - [SuitSensorConstants.NET_OWNER_UID] = status.OwnerUid, - }; - - if (status.TotalDamage != null) - payload.Add(SuitSensorConstants.NET_TOTAL_DAMAGE, status.TotalDamage); - if (status.TotalDamageThreshold != null) - payload.Add(SuitSensorConstants.NET_TOTAL_DAMAGE_THRESHOLD, status.TotalDamageThreshold); - if (status.Coordinates != null) - payload.Add(SuitSensorConstants.NET_COORDINATES, status.Coordinates); - if (status.MapId != null) - payload.Add(SuitSensorConstants.MAP_ID, status.MapId); - - return payload; - } - - /// - /// Try to create the suit sensors status from the device network message - /// - public SuitSensorStatus? PacketToSuitSensor(NetworkPayload payload) - { - // check command - if (!payload.TryGetValue(DeviceNetworkConstants.Command, out string? command)) - return null; - if (command != DeviceNetworkConstants.CmdUpdatedState) - return null; - - // check name, job and alive - if (!payload.TryGetValue(SuitSensorConstants.NET_NAME, out string? name)) return null; - if (!payload.TryGetValue(SuitSensorConstants.NET_JOB, out string? job)) return null; - if (!payload.TryGetValue(SuitSensorConstants.NET_JOB_ICON, out string? jobIcon)) return null; - if (!payload.TryGetValue(SuitSensorConstants.NET_JOB_DEPARTMENTS, out List? jobDepartments)) return null; - if (!payload.TryGetValue(SuitSensorConstants.NET_IS_ALIVE, out bool? isAlive)) return null; - if (!payload.TryGetValue(SuitSensorConstants.NET_SUIT_SENSOR_UID, out NetEntity suitSensorUid)) return null; - if (!payload.TryGetValue(SuitSensorConstants.NET_OWNER_UID, out NetEntity ownerUid)) return null; - - // try get total damage and cords (optionals) - payload.TryGetValue(SuitSensorConstants.NET_TOTAL_DAMAGE, out int? totalDamage); - payload.TryGetValue(SuitSensorConstants.NET_TOTAL_DAMAGE_THRESHOLD, out int? totalDamageThreshold); - payload.TryGetValue(SuitSensorConstants.NET_COORDINATES, out NetCoordinates? coords); - payload.TryGetValue(SuitSensorConstants.MAP_ID, out MapId? mapId); - - var status = new SuitSensorStatus(ownerUid, suitSensorUid, name, job, jobIcon, jobDepartments, mapId) - { - IsAlive = isAlive.Value, - TotalDamage = totalDamage, - TotalDamageThreshold = totalDamageThreshold, - Coordinates = coords, - MapId = mapId, - }; - return status; + SetSensor(ent.AsNullable(), ent.Comp.PreviousMode, null); + ent.Comp.ControlsLocked = ent.Comp.PreviousControlsLocked; } } diff --git a/Content.Server/Mindshield/MindShieldSystem.cs b/Content.Server/Mindshield/MindShieldSystem.cs index 18757d53e9..d55ba0aefa 100644 --- a/Content.Server/Mindshield/MindShieldSystem.cs +++ b/Content.Server/Mindshield/MindShieldSystem.cs @@ -7,6 +7,7 @@ using Content.Shared.Implants; using Content.Shared.Implants.Components; using Content.Shared.Mindshield.Components; using Content.Shared.Revolutionary.Components; +using Content.Shared.Roles.Components; using Robust.Shared.Containers; namespace Content.Server.Mindshield; diff --git a/Content.Server/Ninja/Systems/SpiderChargeSystem.cs b/Content.Server/Ninja/Systems/SpiderChargeSystem.cs index c08576a5ce..c2d9fb3f68 100644 --- a/Content.Server/Ninja/Systems/SpiderChargeSystem.cs +++ b/Content.Server/Ninja/Systems/SpiderChargeSystem.cs @@ -1,10 +1,10 @@ using Content.Server.Mind; using Content.Server.Objectives.Components; using Content.Server.Popups; -using Content.Server.Roles; using Content.Shared.Ninja.Components; using Content.Shared.Ninja.Systems; using Content.Shared.Roles; +using Content.Shared.Roles.Components; using Content.Shared.Sticky; using Content.Shared.Trigger; diff --git a/Content.Server/Objectives/Systems/NinjaConditionsSystem.cs b/Content.Server/Objectives/Systems/NinjaConditionsSystem.cs index c9e9326c1e..db78816503 100644 --- a/Content.Server/Objectives/Systems/NinjaConditionsSystem.cs +++ b/Content.Server/Objectives/Systems/NinjaConditionsSystem.cs @@ -1,8 +1,8 @@ using Content.Server.Objectives.Components; -using Content.Server.Roles; using Content.Shared.Objectives.Components; using Content.Shared.Ninja.Components; using Content.Shared.Roles; +using Content.Shared.Roles.Components; using Content.Shared.Warps; using Robust.Shared.Random; diff --git a/Content.Server/Power/Components/ApcPowerReceiverComponent.cs b/Content.Server/Power/Components/ApcPowerReceiverComponent.cs index aa8feff3e0..bfd096a253 100644 --- a/Content.Server/Power/Components/ApcPowerReceiverComponent.cs +++ b/Content.Server/Power/Components/ApcPowerReceiverComponent.cs @@ -14,9 +14,12 @@ namespace Content.Server.Power.Components /// /// Amount of charge this needs from an APC per second to function. /// - [ViewVariables(VVAccess.ReadWrite)] [DataField("powerLoad")] - public float Load { get => NetworkLoad.DesiredPower; set => NetworkLoad.DesiredPower = value; } + public override float Load + { + get => NetworkLoad.DesiredPower; + set => NetworkLoad.DesiredPower = value; + } public ApcPowerProviderComponent? Provider = null; diff --git a/Content.Server/Power/Generator/GeneratorSystem.cs b/Content.Server/Power/Generator/GeneratorSystem.cs index 72b99d59af..97491ddbf4 100644 --- a/Content.Server/Power/Generator/GeneratorSystem.cs +++ b/Content.Server/Power/Generator/GeneratorSystem.cs @@ -198,6 +198,7 @@ public sealed class GeneratorSystem : SharedGeneratorSystem generator.On = on; UpdateState(uid, generator); + Dirty(uid, generator); } public override void Update(float frameTime) diff --git a/Content.Server/Power/Generator/PortableGeneratorSystem.cs b/Content.Server/Power/Generator/PortableGeneratorSystem.cs index a2d506f6c2..d8fca4e48a 100644 --- a/Content.Server/Power/Generator/PortableGeneratorSystem.cs +++ b/Content.Server/Power/Generator/PortableGeneratorSystem.cs @@ -42,8 +42,6 @@ public sealed class PortableGeneratorSystem : SharedPortableGeneratorSystem SubscribeLocalEvent(GeneratorStartMessage); SubscribeLocalEvent(GeneratorStopMessage); SubscribeLocalEvent(GeneratorSwitchOutputMessage); - - SubscribeLocalEvent(OnSwitchPowerCheck); } private void GeneratorSwitchOutputMessage(EntityUid uid, PortableGeneratorComponent component, PortableGeneratorSwitchOutputMessage args) @@ -195,12 +193,6 @@ public sealed class PortableGeneratorSystem : SharedPortableGeneratorSystem } } - private void OnSwitchPowerCheck(EntityUid uid, FuelGeneratorComponent comp, ref SwitchPowerCheckEvent args) - { - if (comp.On) - args.DisableMessage = Loc.GetString("fuel-generator-verb-disable-on"); - } - public override void Update(float frameTime) { var query = EntityQueryEnumerator(); diff --git a/Content.Server/Power/Generator/PowerSwitchableSystem.cs b/Content.Server/Power/Generator/PowerSwitchableSystem.cs index 25de3bd293..1a89f20627 100644 --- a/Content.Server/Power/Generator/PowerSwitchableSystem.cs +++ b/Content.Server/Power/Generator/PowerSwitchableSystem.cs @@ -1,5 +1,4 @@ -using Content.Server.NodeContainer; -using Content.Server.NodeContainer.EntitySystems; +using Content.Server.NodeContainer.EntitySystems; using Content.Server.Popups; using Content.Server.Power.Components; using Content.Server.Power.Nodes; @@ -7,9 +6,7 @@ using Content.Shared.NodeContainer; using Content.Shared.Power; using Content.Shared.Power.Generator; using Content.Shared.Timing; -using Content.Shared.Verbs; using Robust.Shared.Audio.Systems; -using Robust.Shared.Utility; namespace Content.Server.Power.Generator; @@ -26,47 +23,9 @@ public sealed class PowerSwitchableSystem : SharedPowerSwitchableSystem [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly UseDelaySystem _useDelay = default!; - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent>(GetVerbs); - } - - private void GetVerbs(EntityUid uid, PowerSwitchableComponent comp, GetVerbsEvent args) - { - if (!args.CanAccess || !args.CanInteract) - return; - - var voltage = VoltageColor(GetNextVoltage(uid, comp)); - var msg = Loc.GetString("power-switchable-switch-voltage", ("voltage", voltage)); - - InteractionVerb verb = new() - { - Act = () => - { - // don't need to check it again since if its disabled server wont let the verb act - Cycle(uid, args.User, comp); - }, - Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/zap.svg.192dpi.png")), - Text = msg - }; - - var ev = new SwitchPowerCheckEvent(); - RaiseLocalEvent(uid, ref ev); - if (ev.DisableMessage != null) - { - verb.Message = ev.DisableMessage; - verb.Disabled = true; - } - - args.Verbs.Add(verb); - } - - /// - /// Cycles voltage then updates nodes and optionally power supplier to match it. - /// - public void Cycle(EntityUid uid, EntityUid user, PowerSwitchableComponent? comp = null) + // TODO: Prediction + /// + public override void Cycle(EntityUid uid, EntityUid user, PowerSwitchableComponent? comp = null) { if (!Resolve(uid, ref comp)) return; @@ -115,10 +74,3 @@ public sealed class PowerSwitchableSystem : SharedPowerSwitchableSystem _useDelay.TryResetDelay((uid, useDelay)); } } - -/// -/// Raised on a to see if its verb should work. -/// If is non-null, the verb is disabled with that as the message. -/// -[ByRefEvent] -public record struct SwitchPowerCheckEvent(string? DisableMessage = null); diff --git a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs index 41306f102c..643daece71 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs @@ -21,6 +21,7 @@ using Content.Shared.DoAfter; using Content.Shared.Emag.Systems; using Content.Shared.FixedPoint; using Content.Shared.Humanoid; +using Content.Shared.Light.Components; using Content.Shared.Maps; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; diff --git a/Content.Server/Robotics/Systems/RoboticsConsoleSystem.cs b/Content.Server/Robotics/Systems/RoboticsConsoleSystem.cs index c4554d65d6..560d8174aa 100644 --- a/Content.Server/Robotics/Systems/RoboticsConsoleSystem.cs +++ b/Content.Server/Robotics/Systems/RoboticsConsoleSystem.cs @@ -95,6 +95,9 @@ public sealed class RoboticsConsoleSystem : SharedRoboticsConsoleSystem private void OnDisable(Entity ent, ref RoboticsConsoleDisableMessage args) { + if (!ent.Comp.AllowBorgControl) + return; + if (_lock.IsLocked(ent.Owner)) return; @@ -112,6 +115,9 @@ public sealed class RoboticsConsoleSystem : SharedRoboticsConsoleSystem private void OnDestroy(Entity ent, ref RoboticsConsoleDestroyMessage args) { + if (!ent.Comp.AllowBorgControl) + return; + if (_lock.IsLocked(ent.Owner)) return; @@ -139,7 +145,7 @@ public sealed class RoboticsConsoleSystem : SharedRoboticsConsoleSystem private void UpdateUserInterface(Entity ent) { - var state = new RoboticsConsoleState(ent.Comp.Cyborgs); + var state = new RoboticsConsoleState(ent.Comp.Cyborgs, ent.Comp.AllowBorgControl); _ui.SetUiState(ent.Owner, RoboticsConsoleUiKey.Key, state); } } diff --git a/Content.Server/Roles/DragonRoleComponent.cs b/Content.Server/Roles/DragonRoleComponent.cs deleted file mode 100644 index c47455d8f6..0000000000 --- a/Content.Server/Roles/DragonRoleComponent.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Content.Server.Dragon; -using Content.Shared.Roles; - -namespace Content.Server.Roles; - -/// -/// Added to mind role entities to tag that they are a space dragon. -/// -[RegisterComponent, Access(typeof(DragonSystem))] -public sealed partial class DragonRoleComponent : BaseMindRoleComponent -{ -} diff --git a/Content.Server/Roles/InitialInfectedRoleComponent.cs b/Content.Server/Roles/InitialInfectedRoleComponent.cs deleted file mode 100644 index 475cd3ba60..0000000000 --- a/Content.Server/Roles/InitialInfectedRoleComponent.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Content.Shared.Roles; - -namespace Content.Server.Roles; - -/// -/// Added to mind role entities to tag that they are an initial infected. -/// -[RegisterComponent] -public sealed partial class InitialInfectedRoleComponent : BaseMindRoleComponent -{ - -} diff --git a/Content.Server/Roles/NinjaRoleComponent.cs b/Content.Server/Roles/NinjaRoleComponent.cs deleted file mode 100644 index 7bdffe67a3..0000000000 --- a/Content.Server/Roles/NinjaRoleComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Content.Shared.Roles; - -namespace Content.Server.Roles; - -/// -/// Added to mind role entities to tag that they are a space ninja. -/// -[RegisterComponent] -public sealed partial class NinjaRoleComponent : BaseMindRoleComponent -{ -} diff --git a/Content.Server/Roles/NukeopsRoleComponent.cs b/Content.Server/Roles/NukeopsRoleComponent.cs deleted file mode 100644 index 41561088ea..0000000000 --- a/Content.Server/Roles/NukeopsRoleComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Content.Shared.Roles; - -namespace Content.Server.Roles; - -/// -/// Added to mind role entities to tag that they are a nuke operative. -/// -[RegisterComponent] -public sealed partial class NukeopsRoleComponent : BaseMindRoleComponent -{ -} diff --git a/Content.Server/Roles/ParadoxCloneRoleComponent.cs b/Content.Server/Roles/ParadoxCloneRoleComponent.cs deleted file mode 100644 index 32ebb2fe2d..0000000000 --- a/Content.Server/Roles/ParadoxCloneRoleComponent.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Content.Shared.Roles; - -namespace Content.Server.Roles; - -/// -/// Added to mind role entities to tag that they are a paradox clone. -/// -[RegisterComponent] -public sealed partial class ParadoxCloneRoleComponent : BaseMindRoleComponent -{ - /// - /// Name modifer applied to the player when they turn into a ghost. - /// Needed to be able to keep the original and the clone apart in dead chat. - /// - [DataField] - public LocId? NameModifier = "paradox-clone-ghost-name-modifier"; -} diff --git a/Content.Server/Roles/ParadoxCloneRoleSystem.cs b/Content.Server/Roles/ParadoxCloneRoleSystem.cs index 83e23fef91..c957692b70 100644 --- a/Content.Server/Roles/ParadoxCloneRoleSystem.cs +++ b/Content.Server/Roles/ParadoxCloneRoleSystem.cs @@ -1,7 +1,7 @@ using Content.Shared.Ghost; using Content.Shared.Mind; using Content.Shared.NameModifier.EntitySystems; -using Content.Shared.Roles; +using Content.Shared.Roles.Components; namespace Content.Server.Roles; diff --git a/Content.Server/Roles/RemoveRoleCommand.cs b/Content.Server/Roles/RemoveRoleCommand.cs index 2d18415067..f3cc4a834d 100644 --- a/Content.Server/Roles/RemoveRoleCommand.cs +++ b/Content.Server/Roles/RemoveRoleCommand.cs @@ -3,6 +3,7 @@ using Content.Shared.Administration; using Content.Shared.Players; using Content.Shared.Roles; using Content.Shared.Roles.Jobs; +using Content.Shared.Roles.Components; using Robust.Server.Player; using Robust.Shared.Console; diff --git a/Content.Server/Roles/RevolutionaryRoleComponent.cs b/Content.Server/Roles/RevolutionaryRoleComponent.cs deleted file mode 100644 index dcdb131b9d..0000000000 --- a/Content.Server/Roles/RevolutionaryRoleComponent.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Content.Shared.Roles; - -namespace Content.Server.Roles; - -/// -/// Added to mind role entities to tag that they are a Revolutionary. -/// -[RegisterComponent] -public sealed partial class RevolutionaryRoleComponent : BaseMindRoleComponent -{ - /// - /// For headrevs, how many people you have converted. - /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public uint ConvertedCount = 0; -} diff --git a/Content.Server/Roles/RoleBriefingComponent.cs b/Content.Server/Roles/RoleBriefingComponent.cs deleted file mode 100644 index f4d3fe6353..0000000000 --- a/Content.Server/Roles/RoleBriefingComponent.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Content.Shared.Roles; - -namespace Content.Server.Roles; - -/// -/// Adds a briefing to the character info menu, does nothing else. -/// -[RegisterComponent] -public sealed partial class RoleBriefingComponent : BaseMindRoleComponent -{ - [DataField] - public string Briefing; -} diff --git a/Content.Server/Roles/RoleBriefingSystem.cs b/Content.Server/Roles/RoleBriefingSystem.cs index 17b62a08b3..6825fe8e10 100644 --- a/Content.Server/Roles/RoleBriefingSystem.cs +++ b/Content.Server/Roles/RoleBriefingSystem.cs @@ -1,3 +1,5 @@ +using Content.Shared.Roles.Components; + namespace Content.Server.Roles; public sealed class RoleBriefingSystem : EntitySystem diff --git a/Content.Server/Roles/SubvertedSiliconRoleComponent.cs b/Content.Server/Roles/SubvertedSiliconRoleComponent.cs deleted file mode 100644 index 55727573b9..0000000000 --- a/Content.Server/Roles/SubvertedSiliconRoleComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Content.Shared.Roles; - -namespace Content.Server.Roles; - -/// -/// Added to mind role entities to tag that they are a hacked borg. -/// -[RegisterComponent] -public sealed partial class SubvertedSiliconRoleComponent : BaseMindRoleComponent -{ -} diff --git a/Content.Server/Roles/SurvivorRoleComponent.cs b/Content.Server/Roles/SurvivorRoleComponent.cs deleted file mode 100644 index e5e6dd9f87..0000000000 --- a/Content.Server/Roles/SurvivorRoleComponent.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Content.Shared.Roles; - -namespace Content.Server.Roles; - -/// -/// Adds to a mind role ent to tag they're a Survivor -/// -[RegisterComponent] -public sealed partial class SurvivorRoleComponent : BaseMindRoleComponent; diff --git a/Content.Server/Roles/ThiefRoleComponent.cs b/Content.Server/Roles/ThiefRoleComponent.cs deleted file mode 100644 index c0ddee71a4..0000000000 --- a/Content.Server/Roles/ThiefRoleComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Content.Shared.Roles; - -namespace Content.Server.Roles; - -/// -/// Added to mind role entities to tag that they are a thief. -/// -[RegisterComponent] -public sealed partial class ThiefRoleComponent : BaseMindRoleComponent -{ -} diff --git a/Content.Server/Roles/TraitorRoleComponent.cs b/Content.Server/Roles/TraitorRoleComponent.cs deleted file mode 100644 index a8a11a8f1b..0000000000 --- a/Content.Server/Roles/TraitorRoleComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Content.Shared.Roles; - -namespace Content.Server.Roles; - -/// -/// Added to mind role entities to tag that they are a syndicate traitor. -/// -[RegisterComponent] -public sealed partial class TraitorRoleComponent : BaseMindRoleComponent -{ -} diff --git a/Content.Server/Roles/WizardRoleComponent.cs b/Content.Server/Roles/WizardRoleComponent.cs deleted file mode 100644 index 72a89ee2ca..0000000000 --- a/Content.Server/Roles/WizardRoleComponent.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Content.Server.Roles; - -/// -/// Mind role to tag entities that they're a Wizard -/// -[RegisterComponent] -public sealed partial class WizardRoleComponent : Component; diff --git a/Content.Server/Roles/ZombieRoleComponent.cs b/Content.Server/Roles/ZombieRoleComponent.cs deleted file mode 100644 index cff25e53e8..0000000000 --- a/Content.Server/Roles/ZombieRoleComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Content.Shared.Roles; - -namespace Content.Server.Roles; - -/// -/// Added to mind role entities to tag that they are a zombie. -/// -[RegisterComponent] -public sealed partial class ZombieRoleComponent : BaseMindRoleComponent -{ -} diff --git a/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs b/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs index 74ae23199e..6cc0fea32f 100644 --- a/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs +++ b/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs @@ -235,6 +235,8 @@ public sealed partial class ShuttleConsoleSystem : SharedShuttleConsoleSystem _xformQuery.TryGetComponent(comp.DockedWith, out var otherDockXform) ? GetNetEntity(otherDockXform.GridUid) : null, + Color = comp.RadarColor, + HighlightedColor = comp.HighlightedRadarColor }; gridDocks.Add(state); diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.Impact.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.Impact.cs index e78a17e180..b5adeb04db 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.Impact.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.Impact.cs @@ -249,7 +249,7 @@ public sealed partial class ShuttleSystem if (direction.LengthSquared() > minsq) { - _stuns.TryUpdateKnockdownDuration(uid, knockdownTime); + _stuns.TryCrawling(uid, knockdownTime); _throwing.TryThrow(uid, direction, physics, Transform(uid), _projQuery, direction.Length(), playSound: false); } else diff --git a/Content.Server/Silicons/Borgs/BorgSystem.MMI.cs b/Content.Server/Silicons/Borgs/BorgSystem.MMI.cs index 7435e38f5a..b41f1397ec 100644 --- a/Content.Server/Silicons/Borgs/BorgSystem.MMI.cs +++ b/Content.Server/Silicons/Borgs/BorgSystem.MMI.cs @@ -1,7 +1,7 @@ -using Content.Server.Roles; -using Content.Shared.Containers.ItemSlots; +using Content.Shared.Containers.ItemSlots; using Content.Shared.Mind.Components; using Content.Shared.Roles; +using Content.Shared.Roles.Components; using Content.Shared.Silicons.Borgs.Components; using Robust.Shared.Containers; diff --git a/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs b/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs index 07fb1cb30b..bbd62d7a01 100644 --- a/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs +++ b/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs @@ -52,7 +52,7 @@ public sealed partial class BorgSystem private void OnProvideItemStartup(EntityUid uid, ItemBorgModuleComponent component, ComponentStartup args) { - component.ProvidedContainer = Container.EnsureContainer(uid, component.ProvidedContainerId); + Container.EnsureContainer(uid, component.HoldingContainer); } private void OnSelectableInstalled(EntityUid uid, SelectableBorgModuleComponent component, ref BorgModuleInstalledEvent args) @@ -187,43 +187,43 @@ public sealed partial class BorgSystem if (!TryComp(chassis, out var hands)) return; - var xform = Transform(chassis); - foreach (var itemProto in component.Items) - { - EntityUid item; + if (!_container.TryGetContainer(uid, component.HoldingContainer, out var container)) + return; - if (!component.ItemsCreated) + var xform = Transform(chassis); + + for (var i = 0; i < component.Hands.Count; i++) + { + var hand = component.Hands[i]; + var handId = $"{uid}-hand-{i}"; + + _hands.AddHand((chassis, hands), handId, hand.Hand); + EntityUid? item = null; + + if (component.StoredItems is not null) + { + if (component.StoredItems.TryGetValue(handId, out var storedItem)) + { + item = storedItem; + _container.Remove(storedItem, container, force: true); + } + } + else if (hand.Item is { } itemProto) { item = Spawn(itemProto, xform.Coordinates); } - else + + if (item is { } pickUp) { - item = component.ProvidedContainer.ContainedEntities - .FirstOrDefault(ent => Prototype(ent)?.ID == itemProto.Id); - if (!item.IsValid()) + _hands.DoPickup(chassis, handId, pickUp, hands); + if (!hand.ForceRemovable && hand.Hand.Whitelist == null && hand.Hand.Blacklist == null) { - Log.Debug($"no items found: {component.ProvidedContainer.ContainedEntities.Count}"); - continue; + EnsureComp(pickUp); } - - _container.Remove(item, component.ProvidedContainer, force: true); } - - if (!item.IsValid()) - { - Log.Debug("no valid item"); - continue; - } - - var handId = $"{uid}-item{component.HandCounter}"; - component.HandCounter++; - _hands.AddHand((chassis, hands), handId, HandLocation.Middle); - _hands.DoPickup(chassis, handId, item, hands); - EnsureComp(item); - component.ProvidedItems.Add(handId, item); } - component.ItemsCreated = true; + Dirty(uid, component); } private void RemoveProvidedItems(EntityUid chassis, EntityUid uid, BorgChassisComponent? chassisComponent = null, ItemBorgModuleComponent? component = null) @@ -234,27 +234,33 @@ public sealed partial class BorgSystem if (!TryComp(chassis, out var hands)) return; - if (TerminatingOrDeleted(uid)) - { - foreach (var (hand, item) in component.ProvidedItems) - { - QueueDel(item); - _hands.RemoveHand(chassis, hand); - } - component.ProvidedItems.Clear(); + if (!_container.TryGetContainer(uid, component.HoldingContainer, out var container)) return; - } - foreach (var (handId, item) in component.ProvidedItems) + if (TerminatingOrDeleted(uid)) + return; + + component.StoredItems ??= new(); + + for (var i = 0; i < component.Hands.Count; i++) { - if (LifeStage(item) <= EntityLifeStage.MapInitialized) + var handId = $"{uid}-hand-{i}"; + + if (_hands.TryGetHeldItem(chassis, handId, out var held)) { - RemComp(item); - _container.Insert(item, component.ProvidedContainer); + RemComp(held.Value); + _container.Insert(held.Value, container); + component.StoredItems[handId] = held.Value; } + else + { + component.StoredItems.Remove(handId); + } + _hands.RemoveHand(chassis, handId); } - component.ProvidedItems.Clear(); + + Dirty(uid, component); } /// @@ -286,8 +292,8 @@ public sealed partial class BorgSystem if (!TryComp(containedModuleUid, out var containedItemModuleComp)) continue; - if (containedItemModuleComp.Items.Count == itemModuleComp.Items.Count && - containedItemModuleComp.Items.All(itemModuleComp.Items.Contains)) + if (containedItemModuleComp.Hands.Count == itemModuleComp.Hands.Count && + containedItemModuleComp.Hands.All(itemModuleComp.Hands.Contains)) { if (user != null) Popup.PopupEntity(Loc.GetString("borg-module-duplicate"), uid, user.Value); diff --git a/Content.Server/Silicons/Borgs/BorgSystem.Transponder.cs b/Content.Server/Silicons/Borgs/BorgSystem.Transponder.cs index 96df55f5d1..4507c778cf 100644 --- a/Content.Server/Silicons/Borgs/BorgSystem.Transponder.cs +++ b/Content.Server/Silicons/Borgs/BorgSystem.Transponder.cs @@ -1,4 +1,9 @@ +using Content.Shared.Containers.ItemSlots; using Content.Shared.DeviceNetwork; +using Content.Shared.Damage; +using Content.Shared.FixedPoint; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Systems; using Content.Shared.Movement.Components; using Content.Shared.Popups; using Content.Shared.Robotics; @@ -14,6 +19,9 @@ namespace Content.Server.Silicons.Borgs; public sealed partial class BorgSystem { [Dependency] private readonly EmagSystem _emag = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!; + [Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!; private void InitializeTransponder() { @@ -28,7 +36,7 @@ public sealed partial class BorgSystem var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var comp, out var chassis, out var device, out var meta)) { - if (comp.NextDisable is {} nextDisable && now >= nextDisable) + if (comp.NextDisable is { } nextDisable && now >= nextDisable) DoDisable((uid, comp, chassis, meta)); if (now < comp.NextBroadcast) @@ -38,13 +46,17 @@ public sealed partial class BorgSystem if (_powerCell.TryGetBatteryFromSlot(uid, out var battery)) charge = battery.CurrentCharge / battery.MaxCharge; - var hasBrain = chassis.BrainEntity != null && !comp.FakeDisabled; + var hpPercent = CalcHP(uid); + + // checks if it has a brain and if the brain is not a empty MMI (gives false anyway if the fake disable is true) + var hasBrain = CheckBrain(chassis.BrainEntity) && !comp.FakeDisabled; var canDisable = comp.NextDisable == null && !comp.FakeDisabling; var data = new CyborgControlData( comp.Sprite, comp.Name, meta.EntityName, charge, + hpPercent, chassis.ModuleCount, hasBrain, canDisable); @@ -75,7 +87,7 @@ public sealed partial class BorgSystem return; } - if (ent.Comp2.BrainEntity is not {} brain) + if (ent.Comp2.BrainEntity is not { } brain) return; var message = Loc.GetString(ent.Comp1.DisabledPopup, ("name", Name(ent, ent.Comp3))); @@ -161,4 +173,40 @@ public sealed partial class BorgSystem { ent.Comp.Name = name; } + + /// + /// Returns a ratio between 0 and 1, 1 when they have no damage and 0 whenever they are crit (or more damaged) + /// + private float CalcHP(EntityUid uid) + { + if (!TryComp(uid, out var damageable)) + return 1; + + if (!_mobState.IsAlive(uid)) + return 0; + + if (!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Critical, out var threshold)) + { + Log.Error($"Borg({ToPrettyString(uid)}), doesn't have critical threshold."); + return 1; + } + + return 1 - ((FixedPoint2)(damageable.TotalDamage / threshold)).Float(); + } + + /// + /// Returns true if the borg has a brain + /// + private bool CheckBrain(EntityUid? brainEntity) + { + if (brainEntity == null) + return false; + + // if the brainEntity.Value has the component MMIComponent then it is a MMI, + // in that case it trys to get the "brain" of the MMI, if it is null the MMI is empty and so it returns false + if (TryComp(brainEntity.Value, out var mmi) && _itemSlotsSystem.GetItemOrNull(brainEntity.Value, mmi.BrainSlotId) == null) + return false; + + return true; + } } diff --git a/Content.Server/Silicons/Laws/SiliconLawSystem.cs b/Content.Server/Silicons/Laws/SiliconLawSystem.cs index d9959b4161..425ed1c14f 100644 --- a/Content.Server/Silicons/Laws/SiliconLawSystem.cs +++ b/Content.Server/Silicons/Laws/SiliconLawSystem.cs @@ -4,7 +4,6 @@ using Content.Server.Chat.Managers; using Content.Server.Chat.Systems; using Content.Server.GameTicking; using Content.Server.Radio.Components; -using Content.Server.Roles; using Content.Server.Station.Systems; using Content.Shared.Administration; using Content.Shared.Chat; @@ -13,9 +12,9 @@ using Content.Shared.GameTicking; using Content.Shared.Mind; using Content.Shared.Mind.Components; using Content.Shared.Roles; +using Content.Shared.Roles.Components; using Content.Shared.Silicons.Laws; using Content.Shared.Silicons.Laws.Components; -using Content.Shared.Wires; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Containers; diff --git a/Content.Server/Stack/StackSystem.cs b/Content.Server/Stack/StackSystem.cs index 61153be401..a24cb2df42 100644 --- a/Content.Server/Stack/StackSystem.cs +++ b/Content.Server/Stack/StackSystem.cs @@ -33,7 +33,7 @@ namespace Content.Server.Stack base.SetCount(uid, amount, component); // Queue delete stack if count reaches zero. - if (component.Count <= 0 && !component.Lingering) + if (component.Count <= 0) QueueDel(uid); } diff --git a/Content.Server/StationEvents/Events/SolarFlareRule.cs b/Content.Server/StationEvents/Events/SolarFlareRule.cs index 19f6e393d2..b6530d867f 100644 --- a/Content.Server/StationEvents/Events/SolarFlareRule.cs +++ b/Content.Server/StationEvents/Events/SolarFlareRule.cs @@ -8,6 +8,7 @@ using Content.Shared.Radio.Components; using Content.Shared.Doors.Components; using Content.Shared.Doors.Systems; using Content.Shared.GameTicking.Components; +using Content.Shared.Light.Components; namespace Content.Server.StationEvents.Events; diff --git a/Content.Server/Stunnable/Components/StunOnCollideComponent.cs b/Content.Server/Stunnable/Components/StunOnCollideComponent.cs index 363fb78b75..cbf6b17af8 100644 --- a/Content.Server/Stunnable/Components/StunOnCollideComponent.cs +++ b/Content.Server/Stunnable/Components/StunOnCollideComponent.cs @@ -1,58 +1,66 @@ -namespace Content.Server.Stunnable.Components +using Content.Server.Stunnable.Systems; + +namespace Content.Server.Stunnable.Components; + +/// +/// Adds stun when it collides with an entity +/// +[RegisterComponent, Access(typeof(StunOnCollideSystem))] +public sealed partial class StunOnCollideComponent : Component { + // TODO: Can probably predict this. + /// - /// Adds stun when it collides with an entity + /// How long we are stunned for /// - [RegisterComponent, Access(typeof(StunOnCollideSystem))] - public sealed partial class StunOnCollideComponent : Component - { - // TODO: Can probably predict this. + [DataField] + public TimeSpan StunAmount; - /// - /// How long we are stunned for - /// - [DataField] - public TimeSpan StunAmount; + /// + /// How long we are knocked down for + /// + [DataField] + public TimeSpan KnockdownAmount; - /// - /// How long we are knocked down for - /// - [DataField] - public TimeSpan KnockdownAmount; + /// + /// How long we are slowed down for + /// + [DataField] + public TimeSpan SlowdownAmount; - /// - /// How long we are slowed down for - /// - [DataField] - public TimeSpan SlowdownAmount; + /// + /// Multiplier for a mob's walking speed + /// + [DataField] + public float WalkSpeedModifier = 1f; - /// - /// Multiplier for a mob's walking speed - /// - [DataField] - public float WalkSpeedModifier = 1f; + /// + /// Multiplier for a mob's sprinting speed + /// + [DataField] + public float SprintSpeedModifier = 1f; - /// - /// Multiplier for a mob's sprinting speed - /// - [DataField] - public float SprintSpeedModifier = 1f; + /// + /// Refresh Stun or Slowdown on hit + /// + [DataField] + public bool Refresh = true; - /// - /// Refresh Stun or Slowdown on hit - /// - [DataField] - public bool Refresh = true; + /// + /// Should the entity try and stand automatically after being knocked down? + /// + [DataField] + public bool AutoStand = true; - /// - /// Should the entity try and stand automatically after being knocked down? - /// - [DataField] - public bool AutoStand = true; + /// + /// Should the entity drop their items upon first being knocked down? + /// + [DataField] + public bool Drop = true; - /// - /// Fixture we track for the collision. - /// - [DataField("fixture")] public string FixtureID = "projectile"; - } + /// + /// Fixture we track for the collision. + /// + [DataField("fixture")] public string FixtureID = "projectile"; } + diff --git a/Content.Server/Stunnable/Systems/StunOnCollideSystem.cs b/Content.Server/Stunnable/Systems/StunOnCollideSystem.cs index 18c386d4ac..2257812da1 100644 --- a/Content.Server/Stunnable/Systems/StunOnCollideSystem.cs +++ b/Content.Server/Stunnable/Systems/StunOnCollideSystem.cs @@ -4,47 +4,60 @@ using JetBrains.Annotations; using Content.Shared.Throwing; using Robust.Shared.Physics.Events; -namespace Content.Server.Stunnable +namespace Content.Server.Stunnable.Systems; + +[UsedImplicitly] +internal sealed class StunOnCollideSystem : EntitySystem { - [UsedImplicitly] - internal sealed class StunOnCollideSystem : EntitySystem + [Dependency] private readonly StunSystem _stunSystem = default!; + [Dependency] private readonly MovementModStatusSystem _movementMod = default!; + + public override void Initialize() { - [Dependency] private readonly StunSystem _stunSystem = default!; - [Dependency] private readonly MovementModStatusSystem _movementMod = default!; + base.Initialize(); - public override void Initialize() + SubscribeLocalEvent(HandleCollide); + SubscribeLocalEvent(HandleThrow); + } + + private void TryDoCollideStun(Entity ent, EntityUid target) + { + _stunSystem.TryKnockdown(target, ent.Comp.KnockdownAmount, ent.Comp.Refresh, ent.Comp.AutoStand, ent.Comp.Drop); + + if (ent.Comp.Refresh) { - base.Initialize(); - SubscribeLocalEvent(HandleCollide); - SubscribeLocalEvent(HandleThrow); - } - - private void TryDoCollideStun(EntityUid uid, StunOnCollideComponent component, EntityUid target) - { - _stunSystem.TryUpdateStunDuration(target, component.StunAmount); - - _stunSystem.TryKnockdown(target, component.KnockdownAmount, component.Refresh, component.AutoStand, force: true); - + _stunSystem.TryUpdateStunDuration(target, ent.Comp.StunAmount); _movementMod.TryUpdateMovementSpeedModDuration( target, MovementModStatusSystem.TaserSlowdown, - component.SlowdownAmount, - component.WalkSpeedModifier, - component.SprintSpeedModifier + ent.Comp.SlowdownAmount, + ent.Comp.WalkSpeedModifier, + ent.Comp.SprintSpeedModifier ); } - - private void HandleCollide(EntityUid uid, StunOnCollideComponent component, ref StartCollideEvent args) + else { - if (args.OurFixtureId != component.FixtureID) - return; - - TryDoCollideStun(uid, component, args.OtherEntity); - } - - private void HandleThrow(EntityUid uid, StunOnCollideComponent component, ThrowDoHitEvent args) - { - TryDoCollideStun(uid, component, args.Target); + _stunSystem.TryAddStunDuration(target, ent.Comp.StunAmount); + _movementMod.TryAddMovementSpeedModDuration( + target, + MovementModStatusSystem.TaserSlowdown, + ent.Comp.SlowdownAmount, + ent.Comp.WalkSpeedModifier, + ent.Comp.SprintSpeedModifier + ); } } + + private void HandleCollide(Entity ent, ref StartCollideEvent args) + { + if (args.OurFixtureId != ent.Comp.FixtureID) + return; + + TryDoCollideStun(ent, args.OtherEntity); + } + + private void HandleThrow(Entity ent, ref ThrowDoHitEvent args) + { + TryDoCollideStun(ent, args.Target); + } } diff --git a/Content.Server/Thief/Systems/ThiefBeaconSystem.cs b/Content.Server/Thief/Systems/ThiefBeaconSystem.cs index 4c65ba5c44..069966a8a4 100644 --- a/Content.Server/Thief/Systems/ThiefBeaconSystem.cs +++ b/Content.Server/Thief/Systems/ThiefBeaconSystem.cs @@ -1,12 +1,12 @@ using Content.Server.Mind; using Content.Server.Objectives.Components; -using Content.Server.Roles; using Content.Server.Thief.Components; using Content.Shared.Examine; using Content.Shared.Foldable; using Content.Shared.Popups; using Content.Shared.Verbs; using Content.Shared.Roles; +using Content.Shared.Roles.Components; using Robust.Shared.Audio.Systems; namespace Content.Server.Thief.Systems; diff --git a/Content.Server/Trigger/Systems/FireStackOnTriggerSystem.cs b/Content.Server/Trigger/Systems/FireStackOnTriggerSystem.cs new file mode 100644 index 0000000000..af3298b865 --- /dev/null +++ b/Content.Server/Trigger/Systems/FireStackOnTriggerSystem.cs @@ -0,0 +1,53 @@ +using Content.Server.Atmos.EntitySystems; +using Content.Shared.Trigger; +using Content.Shared.Trigger.Components.Effects; + +namespace Content.Server.Trigger.Systems; + +/// +/// Trigger system for adding or removing fire stacks from an entity with . +/// +/// +public sealed class FireStackOnTriggerSystem : EntitySystem +{ + [Dependency] private readonly FlammableSystem _flame = default!; + + /// + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnTriggerFlame); + SubscribeLocalEvent(OnTriggerExtinguish); + } + + private void OnTriggerFlame(Entity ent, ref TriggerEvent args) + { + if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) + return; + + var target = ent.Comp.TargetUser ? args.User : ent.Owner; + + if (target == null) + return; + + _flame.AdjustFireStacks(target.Value, ent.Comp.FireStacks, ignite: ent.Comp.DoIgnite); + + args.Handled = true; + } + + private void OnTriggerExtinguish(Entity ent, ref TriggerEvent args) + { + if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) + return; + + var target = ent.Comp.TargetUser ? args.User : ent.Owner; + + if (target == null) + return; + + _flame.Extinguish(target.Value); + + args.Handled = true; + } +} diff --git a/Content.Server/Trigger/Systems/IgniteOnTriggerSystem.cs b/Content.Server/Trigger/Systems/IgniteOnTriggerSystem.cs index f4d88b774a..c6ae16ec78 100644 --- a/Content.Server/Trigger/Systems/IgniteOnTriggerSystem.cs +++ b/Content.Server/Trigger/Systems/IgniteOnTriggerSystem.cs @@ -8,6 +8,7 @@ namespace Content.Server.Trigger.Systems; /// /// Handles igniting when triggered and stopping ignition after the delay. /// +/// public sealed class IgniteOnTriggerSystem : EntitySystem { [Dependency] private readonly IGameTiming _timing = default!; diff --git a/Content.Server/VoiceMask/VoiceMaskSystem.cs b/Content.Server/VoiceMask/VoiceMaskSystem.cs index d7a8b4c90e..5d5abfc2cf 100644 --- a/Content.Server/VoiceMask/VoiceMaskSystem.cs +++ b/Content.Server/VoiceMask/VoiceMaskSystem.cs @@ -5,11 +5,13 @@ using Content.Shared.Chat; using Content.Shared.Clothing; using Content.Shared.Database; using Content.Shared.Inventory; +using Content.Shared.Lock; using Content.Shared.Popups; using Content.Shared.Preferences; using Content.Shared.Speech; using Content.Shared.VoiceMask; using Robust.Shared.Configuration; +using Robust.Shared.Containers; using Robust.Shared.Prototypes; namespace Content.Server.VoiceMask; @@ -22,6 +24,8 @@ public sealed partial class VoiceMaskSystem : EntitySystem [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly LockSystem _lock = default!; + [Dependency] private readonly SharedContainerSystem _container = default!; // CCVar. private int _maxNameLength; @@ -30,6 +34,7 @@ public sealed partial class VoiceMaskSystem : EntitySystem { base.Initialize(); SubscribeLocalEvent>(OnTransformSpeakerName); + SubscribeLocalEvent(OnLockToggled); SubscribeLocalEvent(OnChangeName); SubscribeLocalEvent(OnChangeVerb); SubscribeLocalEvent(OnEquip); @@ -47,6 +52,14 @@ public sealed partial class VoiceMaskSystem : EntitySystem args.Args.SpeechVerb = entity.Comp.VoiceMaskSpeechVerb ?? args.Args.SpeechVerb; } + private void OnLockToggled(Entity ent, ref LockToggledEvent args) + { + if (args.Locked) + _actions.RemoveAction(ent.Comp.ActionEntity); + else if (_container.TryGetContainingContainer(ent.Owner, out var container)) + _actions.AddAction(container.Owner, ref ent.Comp.ActionEntity, ent.Comp.Action, ent); + } + #region User inputs from UI private void OnChangeVerb(Entity entity, ref VoiceMaskChangeVerbMessage msg) { @@ -81,6 +94,9 @@ public sealed partial class VoiceMaskSystem : EntitySystem #region UI private void OnEquip(EntityUid uid, VoiceMaskComponent component, ClothingGotEquippedEvent args) { + if (_lock.IsLocked(uid)) + return; + _actions.AddAction(args.Wearer, ref component.ActionEntity, component.Action, uid); EnsureComp(args.Wearer, out var maskerComponent); maskerComponent.VoiceId = component.VoiceId; diff --git a/Content.Server/Xenoarchaeology/Artifact/XAE/XAELightFlickerSystem.cs b/Content.Server/Xenoarchaeology/Artifact/XAE/XAELightFlickerSystem.cs index 4c4073b123..6e7c4fc4ad 100644 --- a/Content.Server/Xenoarchaeology/Artifact/XAE/XAELightFlickerSystem.cs +++ b/Content.Server/Xenoarchaeology/Artifact/XAE/XAELightFlickerSystem.cs @@ -1,6 +1,6 @@ using Content.Server.Ghost; -using Content.Server.Light.Components; using Content.Server.Xenoarchaeology.Artifact.XAE.Components; +using Content.Shared.Light.Components; using Content.Shared.Xenoarchaeology.Artifact; using Content.Shared.Xenoarchaeology.Artifact.XAE; using Robust.Shared.Random; diff --git a/Content.Server/Zombies/ZombieSystem.cs b/Content.Server/Zombies/ZombieSystem.cs index 8f2d4add27..7c1b15cb84 100644 --- a/Content.Server/Zombies/ZombieSystem.cs +++ b/Content.Server/Zombies/ZombieSystem.cs @@ -9,7 +9,6 @@ using Content.Server.Emoting.Systems; using Content.Server.GameTicking.Rules.Components; using Content.Server.Pinpointer; using Content.Server.Speech.EntitySystems; -using Content.Server.Roles; using Content.Shared.Anomaly.Components; using Content.Shared.Armor; using Content.Shared.Bed.Sleep; @@ -25,6 +24,7 @@ using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Content.Shared.Popups; using Content.Shared.Roles; +using Content.Shared.Roles.Components; using Content.Shared.Stunnable; using Content.Shared.Throwing; using Content.Shared.Weapons.Melee.Events; diff --git a/Content.Shared/Chemistry/Reagent/ReagentId.cs b/Content.Shared/Chemistry/Reagent/ReagentId.cs index 798dd28db4..88c0abff2a 100644 --- a/Content.Shared/Chemistry/Reagent/ReagentId.cs +++ b/Content.Shared/Chemistry/Reagent/ReagentId.cs @@ -75,7 +75,21 @@ public partial struct ReagentId : IEquatable public override int GetHashCode() { - return HashCode.Combine(Prototype, Data); + // We need to make sure we take the hash code of Data by value in order + // for hashed key lookups to work properly + var hash = 17; + unchecked + { + if (Data?.Count != 0) + { + foreach (var data in Data ?? []) + { + hash = hash * 23 + data.GetHashCode(); + } + } + } + + return HashCode.Combine(Prototype, hash); } public string ToString(FixedPoint2 quantity) diff --git a/Content.Shared/Chemistry/Reagent/ReagentQuantity.cs b/Content.Shared/Chemistry/Reagent/ReagentQuantity.cs index 5aa2cefcfa..ebbb6c7fef 100644 --- a/Content.Shared/Chemistry/Reagent/ReagentQuantity.cs +++ b/Content.Shared/Chemistry/Reagent/ReagentQuantity.cs @@ -59,7 +59,7 @@ public partial struct ReagentQuantity : IEquatable public bool Equals(ReagentQuantity other) { - return Quantity != other.Quantity && Reagent.Equals(other.Reagent); + return Quantity == other.Quantity && Reagent.Equals(other.Reagent); } public override bool Equals(object? obj) diff --git a/Content.Shared/Clothing/EntitySystems/SharedChameleonClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/SharedChameleonClothingSystem.cs index f3fe7a97c9..c65432d379 100644 --- a/Content.Shared/Clothing/EntitySystems/SharedChameleonClothingSystem.cs +++ b/Content.Shared/Clothing/EntitySystems/SharedChameleonClothingSystem.cs @@ -5,6 +5,7 @@ using Content.Shared.Contraband; using Content.Shared.Inventory; using Content.Shared.Inventory.Events; using Content.Shared.Item; +using Content.Shared.Lock; using Content.Shared.Tag; using Content.Shared.Verbs; using Robust.Shared.Prototypes; @@ -24,6 +25,7 @@ public abstract class SharedChameleonClothingSystem : EntitySystem [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly TagSystem _tag = default!; [Dependency] protected readonly IGameTiming _timing = default!; + [Dependency] private readonly LockSystem _lock = default!; [Dependency] private readonly BiocodeSystem _biocodeSystem = default!; private static readonly SlotFlags[] IgnoredSlots = @@ -170,7 +172,7 @@ public abstract class SharedChameleonClothingSystem : EntitySystem private void OnVerb(Entity ent, ref GetVerbsEvent args) { - if (!args.CanAccess || !args.CanInteract || ent.Comp.User != args.User) + if (!args.CanAccess || !args.CanInteract || _lock.IsLocked(ent.Owner)) return; // Can't pass args from a ref event inside of lambdas diff --git a/Content.Shared/Construction/Components/MachineBoardComponent.cs b/Content.Shared/Construction/Components/MachineBoardComponent.cs index 0469b59fd1..292b17dcf1 100644 --- a/Content.Shared/Construction/Components/MachineBoardComponent.cs +++ b/Content.Shared/Construction/Components/MachineBoardComponent.cs @@ -33,6 +33,12 @@ public sealed partial class MachineBoardComponent : Component public EntProtoId Prototype; } +/// +/// Marker component for any item that's machine board-like without necessarily being a MachineBoardComponent +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class CircuitboardComponent : Component; + [DataDefinition, Serializable] public partial struct GenericPartInfo { diff --git a/Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs b/Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs index 31ff034809..97ff74f64a 100644 --- a/Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs +++ b/Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs @@ -184,22 +184,22 @@ public abstract partial class SharedDoAfterSystem : EntitySystem } } - // Whether the user and the target are too far apart. + // Whether the user and the target are too far apart or they are inaccessible. if (args.Target != null) { if (args.DistanceThreshold != null) { - if (!_interaction.InRangeUnobstructed(args.User, args.Target.Value, args.DistanceThreshold.Value)) + if (!_interaction.InRangeAndAccessible(args.User, args.Target.Value, args.DistanceThreshold.Value)) return true; } } - // Whether the distance between the tool and the user has grown too much. + // Whether the distance between the tool and the user has grown too much or they became inaccessible. if (args.Used != null) { if (args.DistanceThreshold != null) { - if (!_interaction.InRangeUnobstructed(args.User, + if (!_interaction.InRangeAndAccessible(args.User, args.Used.Value, args.DistanceThreshold.Value)) return true; diff --git a/Content.Shared/EntityEffects/EffectConditions/JobCondition.cs b/Content.Shared/EntityEffects/EffectConditions/JobCondition.cs index 7fec087d6b..0b942a3e09 100644 --- a/Content.Shared/EntityEffects/EffectConditions/JobCondition.cs +++ b/Content.Shared/EntityEffects/EffectConditions/JobCondition.cs @@ -3,7 +3,7 @@ using Content.Shared.Localizations; using Content.Shared.Mind; using Content.Shared.Mind.Components; using Content.Shared.Roles; -using Content.Shared.Roles.Jobs; +using Content.Shared.Roles.Components; using Robust.Shared.Prototypes; namespace Content.Shared.EntityEffects.EffectConditions; diff --git a/Content.Shared/Hands/Components/HandsComponent.cs b/Content.Shared/Hands/Components/HandsComponent.cs index 8241feec65..57d4d58f67 100644 --- a/Content.Shared/Hands/Components/HandsComponent.cs +++ b/Content.Shared/Hands/Components/HandsComponent.cs @@ -1,6 +1,8 @@ using Content.Shared.DisplacementMap; using Content.Shared.Hands.EntitySystems; +using Content.Shared.Whitelist; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; namespace Content.Shared.Hands.Components; @@ -125,16 +127,45 @@ public sealed partial class HandsComponent : Component public partial record struct Hand { [DataField] - public HandLocation Location = HandLocation.Right; + public HandLocation Location = HandLocation.Middle; + + /// + /// The label to be displayed for this hand when it does not contain an entity + /// + [DataField] + public LocId? EmptyLabel; + + /// + /// The prototype ID of a "representative" entity prototype for what this hand could hold, used in the UI. + /// It is not map-initted. + /// + [DataField] + public EntProtoId? EmptyRepresentative; + + /// + /// What this hand is allowed to hold + /// + [DataField] + public EntityWhitelist? Whitelist; + + /// + /// What this hand is not allowed to hold + /// + [DataField] + public EntityWhitelist? Blacklist; public Hand() { } - public Hand(HandLocation location) + public Hand(HandLocation location, LocId? emptyLabel = null, EntProtoId? emptyRepresentative = null, EntityWhitelist? whitelist = null, EntityWhitelist? blacklist = null) { Location = location; + EmptyLabel = emptyLabel; + EmptyRepresentative = emptyRepresentative; + Whitelist = whitelist; + Blacklist = blacklist; } } diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs index ed8b62e393..73aac15963 100644 --- a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs @@ -217,6 +217,9 @@ public abstract partial class SharedHandsSystem if (checkActionBlocker && !_actionBlocker.CanPickup(uid, entity)) return false; + if (!CheckWhitelists((uid, handsComp), handId, entity)) + return false; + if (ContainerSystem.TryGetContainingContainer((entity, null, null), out var container)) { if (!ContainerSystem.CanRemove(entity, container)) diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Whitelist.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Whitelist.cs new file mode 100644 index 0000000000..dbb1978ce2 --- /dev/null +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Whitelist.cs @@ -0,0 +1,15 @@ +using Content.Shared.Hands.Components; +using Content.Shared.Whitelist; + +namespace Content.Shared.Hands.EntitySystems; + +public abstract partial class SharedHandsSystem +{ + private bool CheckWhitelists(Entity ent, string handId, EntityUid toTest) + { + if (!TryGetHand(ent, handId, out var hand)) + return false; + + return _entityWhitelist.CheckBoth(toTest, hand.Value.Blacklist, hand.Value.Whitelist); + } +} diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs index af5e82f417..8431e27658 100644 --- a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs @@ -7,8 +7,10 @@ using Content.Shared.Interaction; using Content.Shared.Inventory; using Content.Shared.Inventory.VirtualItem; using Content.Shared.Storage.EntitySystems; +using Content.Shared.Whitelist; using Robust.Shared.Containers; using Robust.Shared.Input.Binding; +using Robust.Shared.Prototypes; using Robust.Shared.Utility; namespace Content.Shared.Hands.EntitySystems; @@ -23,6 +25,7 @@ public abstract partial class SharedHandsSystem [Dependency] private readonly SharedStorageSystem _storage = default!; [Dependency] protected readonly SharedTransformSystem TransformSystem = default!; [Dependency] private readonly SharedVirtualItemSystem _virtualSystem = default!; + [Dependency] private readonly EntityWhitelistSystem _entityWhitelist = default!; public event Action, string, HandLocation>? OnPlayerAddHand; public event Action, string>? OnPlayerRemoveHand; @@ -66,9 +69,9 @@ public abstract partial class SharedHandsSystem /// /// Adds a hand with the given container id and supplied location to the specified entity. /// - public void AddHand(Entity ent, string handName, HandLocation handLocation) + public void AddHand(Entity ent, string handName, HandLocation handLocation, LocId? emptyLabel = null, EntProtoId? emptyRepresentative = null, EntityWhitelist? whitelist = null, EntityWhitelist? blacklist = null) { - AddHand(ent, handName, new Hand(handLocation)); + AddHand(ent, handName, new Hand(handLocation, emptyLabel, emptyRepresentative, whitelist, blacklist)); } /// diff --git a/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs b/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs index 4090982297..7896989b40 100644 --- a/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs +++ b/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs @@ -131,7 +131,7 @@ public sealed class ItemToggleSystem : EntitySystem if (ent.Comp.Activated) { - var ev = new ItemToggleActivateAttemptEvent(args.User); + var ev = new ItemToggleDeactivateAttemptEvent(args.User); RaiseLocalEvent(ent.Owner, ref ev); if (ev.Cancelled) @@ -139,7 +139,7 @@ public sealed class ItemToggleSystem : EntitySystem } else { - var ev = new ItemToggleDeactivateAttemptEvent(args.User); + var ev = new ItemToggleActivateAttemptEvent(args.User); RaiseLocalEvent(ent.Owner, ref ev); if (ev.Cancelled) diff --git a/Content.Shared/Light/Components/LightBulbComponent.cs b/Content.Shared/Light/Components/LightBulbComponent.cs index 35b04be897..fcd3840a0c 100644 --- a/Content.Shared/Light/Components/LightBulbComponent.cs +++ b/Content.Shared/Light/Components/LightBulbComponent.cs @@ -8,69 +8,61 @@ namespace Content.Shared.Light.Components; /// Component that represents a light bulb. Can be broken, or burned, which turns them mostly useless. /// TODO: Breaking and burning should probably be moved to another component eventually. /// -[RegisterComponent, NetworkedComponent] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] public sealed partial class LightBulbComponent : Component { /// /// The color of the lightbulb and the light it produces. /// - [DataField("color")] - [ViewVariables(VVAccess.ReadWrite)] + [DataField, AutoNetworkedField] public Color Color = Color.White; /// /// The type of lightbulb. Tube/bulb/etc... /// [DataField("bulb")] - [ViewVariables(VVAccess.ReadWrite)] public LightBulbType Type = LightBulbType.Tube; /// /// The initial state of the lightbulb. /// - [DataField("startingState")] + [DataField("startingState"), AutoNetworkedField] public LightBulbState State = LightBulbState.Normal; /// /// The temperature the air around the lightbulb is exposed to when the lightbulb burns out. /// [DataField("BurningTemperature")] - [ViewVariables(VVAccess.ReadWrite)] public int BurningTemperature = 1400; /// /// Relates to how bright the light produced by the lightbulb is. /// - [DataField("lightEnergy")] - [ViewVariables(VVAccess.ReadWrite)] + [DataField] public float LightEnergy = 0.8f; /// /// The maximum radius of the point light source this light produces. /// - [DataField("lightRadius")] - [ViewVariables(VVAccess.ReadWrite)] + [DataField] public float LightRadius = 10; /// /// Relates to the falloff constant of the light produced by the lightbulb. /// - [DataField("lightSoftness")] - [ViewVariables(VVAccess.ReadWrite)] + [DataField] public float LightSoftness = 1; /// /// The amount of power used by the lightbulb when it's active. /// [DataField("PowerUse")] - [ViewVariables(VVAccess.ReadWrite)] public int PowerUse = 60; /// /// The sound produced when the lightbulb breaks. /// - [DataField("breakSound")] - [ViewVariables(VVAccess.ReadWrite)] + [DataField] public SoundSpecifier BreakSound = new SoundCollectionSpecifier("GlassBreak", AudioParams.Default.WithVolume(-6f)); #region Appearance @@ -78,22 +70,19 @@ public sealed partial class LightBulbComponent : Component /// /// The sprite state used when the lightbulb is intact. /// - [DataField("normalSpriteState")] - [ViewVariables(VVAccess.ReadWrite)] + [DataField] public string NormalSpriteState = "normal"; /// /// The sprite state used when the lightbulb is broken. /// - [DataField("brokenSpriteState")] - [ViewVariables(VVAccess.ReadWrite)] + [DataField] public string BrokenSpriteState = "broken"; /// /// The sprite state used when the lightbulb is burned. /// - [DataField("burnedSpriteState")] - [ViewVariables(VVAccess.ReadWrite)] + [DataField] public string BurnedSpriteState = "burned"; #endregion Appearance diff --git a/Content.Shared/Light/Components/LightReplacerComponent.cs b/Content.Shared/Light/Components/LightReplacerComponent.cs index 3ce9647c88..1276ff9edc 100644 --- a/Content.Shared/Light/Components/LightReplacerComponent.cs +++ b/Content.Shared/Light/Components/LightReplacerComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.Light.Components; using Content.Shared.Light.EntitySystems; using Content.Shared.Storage; using Robust.Shared.Audio; diff --git a/Content.Server/Light/Components/PoweredLightComponent.cs b/Content.Shared/Light/Components/PoweredLightComponent.cs similarity index 54% rename from Content.Server/Light/Components/PoweredLightComponent.cs rename to Content.Shared/Light/Components/PoweredLightComponent.cs index 1a6f610516..ad9a9d401c 100644 --- a/Content.Server/Light/Components/PoweredLightComponent.cs +++ b/Content.Shared/Light/Components/PoweredLightComponent.cs @@ -1,80 +1,87 @@ -using Content.Server.Light.EntitySystems; -using Content.Shared.Damage; using Content.Shared.DeviceLinking; -using Content.Shared.Light.Components; +using Content.Shared.Light.EntitySystems; using Robust.Shared.Audio; using Robust.Shared.Containers; +using Robust.Shared.GameStates; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -namespace Content.Server.Light.Components +namespace Content.Shared.Light.Components { /// /// Component that represents a wall light. It has a light bulb that can be replaced when broken. /// - [RegisterComponent, Access(typeof(PoweredLightSystem))] + [RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause, Access(typeof(SharedPoweredLightSystem))] public sealed partial class PoweredLightComponent : Component { - [DataField("burnHandSound")] + /* + * Stop adding more fields, use components or I will shed you. + */ + + [DataField] public SoundSpecifier BurnHandSound = new SoundPathSpecifier("/Audio/Effects/lightburn.ogg"); - [DataField("turnOnSound")] + [DataField] public SoundSpecifier TurnOnSound = new SoundPathSpecifier("/Audio/Machines/light_tube_on.ogg"); - [DataField("hasLampOnSpawn", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? HasLampOnSpawn = null; + // Should be using containerfill? + [DataField] + public EntProtoId? HasLampOnSpawn = null; [DataField("bulb")] public LightBulbType BulbType; - [DataField("on")] + [DataField, AutoNetworkedField] public bool On = true; - [DataField("ignoreGhostsBoo")] + [DataField] public bool IgnoreGhostsBoo; - [DataField("ghostBlinkingTime")] + [DataField] public TimeSpan GhostBlinkingTime = TimeSpan.FromSeconds(10); - [DataField("ghostBlinkingCooldown")] + [DataField] public TimeSpan GhostBlinkingCooldown = TimeSpan.FromSeconds(60); [ViewVariables] public ContainerSlot LightBulbContainer = default!; - [ViewVariables] + + [AutoNetworkedField] public bool CurrentLit; - [ViewVariables] + + [DataField, AutoNetworkedField] public bool IsBlinking; - [ViewVariables] + + [DataField, AutoNetworkedField, AutoPausedField] public TimeSpan LastThunk; - [ViewVariables] + + [DataField, AutoPausedField] public TimeSpan? LastGhostBlink; - [DataField("onPort", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string OnPort = "On"; + [DataField] + public ProtoId OnPort = "On"; - [DataField("offPort", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string OffPort = "Off"; + [DataField] + public ProtoId OffPort = "Off"; - [DataField("togglePort", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string TogglePort = "Toggle"; + [DataField] + public ProtoId TogglePort = "Toggle"; /// /// How long it takes to eject a bulb from this /// - [DataField("ejectBulbDelay")] + [DataField] public float EjectBulbDelay = 2; /// /// Shock damage done to a mob that hits the light with an unarmed attack /// - [DataField("unarmedHitShock")] + [DataField] public int UnarmedHitShock = 20; /// /// Stun duration applied to a mob that hits the light with an unarmed attack /// - [DataField("unarmedHitStun")] + [DataField] public TimeSpan UnarmedHitStun = TimeSpan.FromSeconds(5); } } diff --git a/Content.Shared/Light/EntitySystems/SharedLightBulbSystem.cs b/Content.Shared/Light/EntitySystems/SharedLightBulbSystem.cs new file mode 100644 index 0000000000..34c986b4de --- /dev/null +++ b/Content.Shared/Light/EntitySystems/SharedLightBulbSystem.cs @@ -0,0 +1,84 @@ +using Content.Shared.Destructible; +using Content.Shared.Light.Components; +using Content.Shared.Throwing; +using Robust.Shared.Audio.Systems; + +namespace Content.Shared.Light.EntitySystems; + +public abstract class SharedLightBulbSystem : EntitySystem +{ + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(HandleLand); + SubscribeLocalEvent(OnBreak); + } + + private void OnInit(EntityUid uid, LightBulbComponent bulb, ComponentInit args) + { + // update default state of bulbs + SetColor(uid, bulb.Color, bulb); + SetState(uid, bulb.State, bulb); + } + + private void HandleLand(EntityUid uid, LightBulbComponent bulb, ref LandEvent args) + { + PlayBreakSound(uid, bulb); + SetState(uid, LightBulbState.Broken, bulb); + } + + private void OnBreak(EntityUid uid, LightBulbComponent component, BreakageEventArgs args) + { + SetState(uid, LightBulbState.Broken, component); + } + + /// + /// Set a new color for a light bulb and raise event about change + /// + public void SetColor(EntityUid uid, Color color, LightBulbComponent? bulb = null) + { + if (!Resolve(uid, ref bulb) || bulb.Color.Equals(color)) + return; + + bulb.Color = color; + Dirty(uid, bulb); + UpdateAppearance(uid, bulb); + } + + /// + /// Set a new state for a light bulb (broken, burned) and raise event about change + /// + public void SetState(EntityUid uid, LightBulbState state, LightBulbComponent? bulb = null) + { + if (!Resolve(uid, ref bulb) || bulb.State == state) + return; + + bulb.State = state; + Dirty(uid, bulb); + UpdateAppearance(uid, bulb); + } + + public void PlayBreakSound(EntityUid uid, LightBulbComponent? bulb = null, EntityUid? user = null) + { + if (!Resolve(uid, ref bulb)) + return; + + _audio.PlayPredicted(bulb.BreakSound, uid, user: user); + } + + private void UpdateAppearance(EntityUid uid, LightBulbComponent? bulb = null, + AppearanceComponent? appearance = null) + { + if (!Resolve(uid, ref bulb, ref appearance, logMissing: false)) + return; + + // try to update appearance and color + _appearance.SetData(uid, LightBulbVisuals.State, bulb.State, appearance); + _appearance.SetData(uid, LightBulbVisuals.Color, bulb.Color, appearance); + } +} diff --git a/Content.Shared/Light/EntitySystems/SharedPoweredLightSystem.cs b/Content.Shared/Light/EntitySystems/SharedPoweredLightSystem.cs new file mode 100644 index 0000000000..65097f0d06 --- /dev/null +++ b/Content.Shared/Light/EntitySystems/SharedPoweredLightSystem.cs @@ -0,0 +1,425 @@ +using Content.Shared.Audio; +using Content.Shared.Damage; +using Content.Shared.Damage.Components; +using Content.Shared.Damage.Systems; +using Content.Shared.DeviceLinking; +using Content.Shared.DeviceLinking.Events; +using Content.Shared.DeviceNetwork; +using Content.Shared.DeviceNetwork.Events; +using Content.Shared.DoAfter; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Interaction; +using Content.Shared.Light.Components; +using Content.Shared.Power; +using Content.Shared.Power.Components; +using Content.Shared.Power.EntitySystems; +using Content.Shared.Storage.EntitySystems; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Containers; +using Robust.Shared.Timing; + +namespace Content.Shared.Light.EntitySystems; + +public abstract class SharedPoweredLightSystem : EntitySystem +{ + [Dependency] protected readonly IGameTiming GameTiming = default!; + [Dependency] private readonly DamageOnInteractSystem _damageOnInteractSystem = default!; + [Dependency] private readonly SharedAmbientSoundSystem _ambientSystem = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] protected readonly SharedContainerSystem ContainerSystem = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly SharedLightBulbSystem _bulbSystem = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; + [Dependency] private readonly SharedPowerReceiverSystem _receiver = default!; + [Dependency] private readonly SharedPointLightSystem _pointLight = default!; + [Dependency] private readonly SharedStorageSystem _storage = default!; + [Dependency] private readonly SharedDeviceLinkSystem _deviceLink = default!; + + private static readonly TimeSpan ThunkDelay = TimeSpan.FromSeconds(2); + public const string LightBulbContainer = "light_bulb"; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnRemoved); + SubscribeLocalEvent(OnInserted); + SubscribeLocalEvent(OnInteractUsing); + SubscribeLocalEvent(OnInteractHand); + SubscribeLocalEvent(OnSignalReceived); + SubscribeLocalEvent(OnPacketReceived); + SubscribeLocalEvent(OnPowerChanged); + SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(HandleLightDamaged); + } + + private void OnInit(EntityUid uid, PoweredLightComponent light, ComponentInit args) + { + light.LightBulbContainer = ContainerSystem.EnsureContainer(uid, LightBulbContainer); + _deviceLink.EnsureSinkPorts(uid, light.OnPort, light.OffPort, light.TogglePort); + } + + private void OnRemoved(Entity light, ref EntRemovedFromContainerMessage args) + { + if (args.Container.ID != LightBulbContainer) + return; + + UpdateLight(light, light); + } + + private void OnInserted(Entity light, ref EntInsertedIntoContainerMessage args) + { + if (args.Container.ID != LightBulbContainer) + return; + + UpdateLight(light, light); + } + + private void OnInteractUsing(EntityUid uid, PoweredLightComponent component, InteractUsingEvent args) + { + if (args.Handled) + return; + + args.Handled = InsertBulb(uid, args.Used, component, user: args.User, playAnimation: true); + } + + private void OnInteractHand(EntityUid uid, PoweredLightComponent light, InteractHandEvent args) + { + if (args.Handled) + return; + + // check if light has bulb to eject + var bulbUid = GetBulb(uid, light); + if (bulbUid == null) + return; + + var userUid = args.User; + //removing a broken/burned bulb, so allow instant removal + if (TryComp(bulbUid.Value, out var bulb) && bulb.State != LightBulbState.Normal) + { + args.Handled = EjectBulb(uid, userUid, light) != null; + return; + } + + // removing a working bulb, so require a delay + _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, userUid, light.EjectBulbDelay, new PoweredLightDoAfterEvent(), uid, target: uid) + { + BreakOnMove = true, + BreakOnDamage = true, + }); + + args.Handled = true; + } + + private void OnSignalReceived(Entity ent, ref SignalReceivedEvent args) + { + if (args.Port == ent.Comp.OffPort) + SetState(ent, false, ent.Comp); + else if (args.Port == ent.Comp.OnPort) + SetState(ent, true, ent.Comp); + else if (args.Port == ent.Comp.TogglePort) + ToggleLight(ent, ent.Comp); + } + + /// + /// Turns the light on or of when receiving a command. + /// The light is turned on or of according to the value + /// + private void OnPacketReceived(EntityUid uid, PoweredLightComponent component, DeviceNetworkPacketEvent args) + { + if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? command) || command != DeviceNetworkConstants.CmdSetState) return; + if (!args.Data.TryGetValue(DeviceNetworkConstants.StateEnabled, out bool enabled)) return; + + SetState(uid, enabled, component); + } + + /// + /// Inserts the bulb if possible. + /// + /// True if it could insert it, false if it couldn't. + public bool InsertBulb(EntityUid uid, EntityUid bulbUid, PoweredLightComponent? light = null, EntityUid? user = null, bool playAnimation = false) + { + if (!Resolve(uid, ref light)) + return false; + + // check if light already has bulb + if (GetBulb(uid, light) != null) + return false; + + // check if bulb fits + if (!TryComp(bulbUid, out var lightBulb)) + return false; + + if (lightBulb.Type != light.BulbType) + return false; + + // try to insert bulb in container + if (!ContainerSystem.Insert(bulbUid, light.LightBulbContainer)) + return false; + + if (playAnimation && TryComp(user, out TransformComponent? xform)) + { + var itemXform = Transform(uid); + _storage.PlayPickupAnimation(bulbUid, xform.Coordinates, itemXform.Coordinates, itemXform.LocalRotation, user: user); + } + + return true; + } + + /// + /// Ejects the bulb to a mob's hand if possible. + /// + /// Bulb uid if it was successfully ejected, null otherwise + public EntityUid? EjectBulb(EntityUid uid, EntityUid? userUid = null, PoweredLightComponent? light = null) + { + if (!Resolve(uid, ref light)) + return null; + + // check if light has bulb + if (GetBulb(uid, light) is not { Valid: true } bulb) + return null; + + // try to remove bulb from container + if (!ContainerSystem.Remove(bulb, light.LightBulbContainer)) + return null; + + // try to place bulb in hands + _handsSystem.PickupOrDrop(userUid, bulb); + + return bulb; + } + + /// + /// Replaces the spawned prototype of a pre-mapinit powered light with a different variant. + /// + public bool ReplaceSpawnedPrototype(Entity light, string bulb) + { + if (light.Comp.LightBulbContainer.ContainedEntity != null) + return false; + + if (LifeStage(light.Owner) >= EntityLifeStage.MapInitialized) + return false; + + light.Comp.HasLampOnSpawn = bulb; + return true; + } + + /// + /// Try to replace current bulb with a new one + /// If succeed old bulb just drops on floor + /// + public bool ReplaceBulb(EntityUid uid, EntityUid bulb, PoweredLightComponent? light = null) + { + EjectBulb(uid, null, light); + return InsertBulb(uid, bulb, light); + } + + /// + /// Try to get light bulb inserted in powered light + /// + /// Bulb uid if it exist, null otherwise + public EntityUid? GetBulb(EntityUid uid, PoweredLightComponent? light = null) + { + if (!Resolve(uid, ref light)) + return null; + + return light.LightBulbContainer?.ContainedEntity; + } + + /// + /// Try to break bulb inside light fixture + /// + public bool TryDestroyBulb(EntityUid uid, PoweredLightComponent? light = null) + { + if (!Resolve(uid, ref light, false)) + return false; + + // if we aren't mapinited, + // just null the spawned bulb + if (LifeStage(uid) < EntityLifeStage.MapInitialized) + { + light.HasLampOnSpawn = null; + return true; + } + + // check bulb state + var bulbUid = GetBulb(uid, light); + if (bulbUid == null || !EntityManager.TryGetComponent(bulbUid.Value, out LightBulbComponent? lightBulb)) + return false; + if (lightBulb.State == LightBulbState.Broken) + return false; + + // break it + _bulbSystem.SetState(bulbUid.Value, LightBulbState.Broken, lightBulb); + _bulbSystem.PlayBreakSound(bulbUid.Value, lightBulb); + UpdateLight(uid, light); + return true; + } + + protected void UpdateLight(EntityUid uid, + PoweredLightComponent? light = null, + SharedApcPowerReceiverComponent? powerReceiver = null, + AppearanceComponent? appearance = null, + EntityUid? user = null) + { + // We don't do anything during state application on the client as if + // it's due to an entity spawn, we'd have to wait for component init to + // be able to do anything, despite the server having already sent us the + // state that we need. On the other hand, we still want this to run in + // prediction so we can, well, predict lights turning on. + if (GameTiming.ApplyingState) + return; + + if (!Resolve(uid, ref light, false)) + return; + + if (!_receiver.ResolveApc(uid, ref powerReceiver)) + return; + + // Optional component. + Resolve(uid, ref appearance, false); + + // check if light has bulb + var bulbUid = GetBulb(uid, light); + if (bulbUid == null || !TryComp(bulbUid.Value, out var lightBulb)) + { + SetLight(uid, false, light: light); + powerReceiver.Load = 0; + _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Empty, appearance); + return; + } + + switch (lightBulb.State) + { + case LightBulbState.Normal: + if (powerReceiver.Powered && light.On) + { + SetLight(uid, true, lightBulb.Color, light, lightBulb.LightRadius, lightBulb.LightEnergy, lightBulb.LightSoftness); + _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.On, appearance); + var time = GameTiming.CurTime; + if (time > light.LastThunk + ThunkDelay) + { + light.LastThunk = time; + Dirty(uid, light); + _audio.PlayPredicted(light.TurnOnSound, uid, user: user, light.TurnOnSound.Params.AddVolume(-10f)); + } + } + else + { + SetLight(uid, false, light: light); + _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Off, appearance); + } + break; + case LightBulbState.Broken: + SetLight(uid, false, light: light); + _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Broken, appearance); + break; + case LightBulbState.Burned: + SetLight(uid, false, light: light); + _appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Burned, appearance); + break; + } + + powerReceiver.Load = (light.On && lightBulb.State == LightBulbState.Normal) ? lightBulb.PowerUse : 0; + } + + /// + /// Destroy the light bulb if the light took any damage. + /// + public void HandleLightDamaged(EntityUid uid, PoweredLightComponent component, DamageChangedEvent args) + { + // Was it being repaired, or did it take damage? + if (args.DamageIncreased) + { + // Eventually, this logic should all be done by this (or some other) system, not a component. + TryDestroyBulb(uid, component); + } + } + + private void OnPowerChanged(EntityUid uid, PoweredLightComponent component, ref PowerChangedEvent args) + { + // TODO: Power moment + var metadata = MetaData(uid); + + if (metadata.EntityPaused || TerminatingOrDeleted(uid, metadata)) + return; + + UpdateLight(uid, component); + } + + public void ToggleBlinkingLight(EntityUid uid, PoweredLightComponent light, bool isNowBlinking) + { + if (light.IsBlinking == isNowBlinking) + return; + + light.IsBlinking = isNowBlinking; + Dirty(uid, light); + + if (!TryComp(uid, out var appearance)) + return; + + _appearance.SetData(uid, PoweredLightVisuals.Blinking, isNowBlinking, appearance); + } + + private void SetLight(EntityUid uid, bool value, Color? color = null, PoweredLightComponent? light = null, float? radius = null, float? energy = null, float? softness = null) + { + if (!Resolve(uid, ref light)) + return; + + if (light.CurrentLit != value) + { + light.CurrentLit = value; + Dirty(uid, light); + } + + _ambientSystem.SetAmbience(uid, value); + + if (_pointLight.TryGetLight(uid, out var pointLight)) + { + _pointLight.SetEnabled(uid, value, pointLight); + + if (color != null) + _pointLight.SetColor(uid, color.Value, pointLight); + if (radius != null) + _pointLight.SetRadius(uid, (float)radius, pointLight); + if (energy != null) + _pointLight.SetEnergy(uid, (float)energy, pointLight); + if (softness != null) + _pointLight.SetSoftness(uid, (float)softness, pointLight); + } + + // light bulbs burn your hands! + if (TryComp(uid, out var damageOnInteractComp)) + _damageOnInteractSystem.SetIsDamageActiveTo((uid, damageOnInteractComp), value); + } + + public void ToggleLight(EntityUid uid, PoweredLightComponent? light = null) + { + if (!Resolve(uid, ref light)) + return; + + light.On = !light.On; + UpdateLight(uid, light); + } + + public void SetState(EntityUid uid, bool state, PoweredLightComponent? light = null) + { + if (!Resolve(uid, ref light)) + return; + + light.On = state; + Dirty(uid, light); + UpdateLight(uid, light); + } + + private void OnDoAfter(EntityUid uid, PoweredLightComponent component, DoAfterEvent args) + { + if (args.Handled || args.Cancelled || args.Args.Target == null) + return; + + EjectBulb(args.Args.Target.Value, args.Args.User, component); + + args.Handled = true; + } +} diff --git a/Content.Shared/Lock/ItemToggleRequiresLockComponent.cs b/Content.Shared/Lock/ItemToggleRequiresLockComponent.cs index 94b8729476..1f257a90c8 100644 --- a/Content.Shared/Lock/ItemToggleRequiresLockComponent.cs +++ b/Content.Shared/Lock/ItemToggleRequiresLockComponent.cs @@ -5,13 +5,19 @@ namespace Content.Shared.Lock; /// /// This is used for toggleable items that require the entity to have a lock in a certain state. /// -[RegisterComponent, NetworkedComponent, Access(typeof(LockSystem))] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(LockSystem))] public sealed partial class ItemToggleRequiresLockComponent : Component { /// /// TRUE: the lock must be locked to toggle the item. /// FALSE: the lock must be unlocked to toggle the item. /// - [DataField] + [DataField, AutoNetworkedField] public bool RequireLocked; + + /// + /// Popup text for when someone tries to toggle the item, but it's locked. If null, no popup will be shown. + /// + [DataField] + public LocId? LockedPopup = "lock-comp-generic-fail"; } diff --git a/Content.Shared/Lock/LockComponent.cs b/Content.Shared/Lock/LockComponent.cs index 5df3cb19ae..0fd1229d21 100644 --- a/Content.Shared/Lock/LockComponent.cs +++ b/Content.Shared/Lock/LockComponent.cs @@ -21,6 +21,18 @@ public sealed partial class LockComponent : Component [AutoNetworkedField] public bool Locked = true; + /// + /// If true, will show verbs to lock and unlock the item. Otherwise, it will not. + /// + [DataField, AutoNetworkedField] + public bool ShowLockVerbs = true; + + /// + /// If true will show examine text. + /// + [DataField, AutoNetworkedField] + public bool ShowExamine = true; + /// /// Whether or not the lock is locked by simply clicking. /// @@ -50,7 +62,7 @@ public sealed partial class LockComponent : Component /// The sound played when unlocked. /// [DataField("unlockingSound"), ViewVariables(VVAccess.ReadWrite)] - public SoundSpecifier UnlockSound = new SoundPathSpecifier("/Audio/Machines/door_lock_off.ogg") + public SoundSpecifier? UnlockSound = new SoundPathSpecifier("/Audio/Machines/door_lock_off.ogg") { Params = AudioParams.Default.WithVolume(-5f), }; @@ -59,7 +71,7 @@ public sealed partial class LockComponent : Component /// The sound played when locked. /// [DataField("lockingSound"), ViewVariables(VVAccess.ReadWrite)] - public SoundSpecifier LockSound = new SoundPathSpecifier("/Audio/Machines/door_lock_on.ogg") + public SoundSpecifier? LockSound = new SoundPathSpecifier("/Audio/Machines/door_lock_on.ogg") { Params = AudioParams.Default.WithVolume(-5f) }; diff --git a/Content.Shared/Lock/LockSystem.cs b/Content.Shared/Lock/LockSystem.cs index 8dc8683acc..f409148591 100644 --- a/Content.Shared/Lock/LockSystem.cs +++ b/Content.Shared/Lock/LockSystem.cs @@ -29,12 +29,12 @@ public sealed class LockSystem : EntitySystem { [Dependency] private readonly AccessReaderSystem _accessReader = default!; [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; - [Dependency] private readonly ActivatableUISystem _activatableUI = default!; [Dependency] private readonly EmagSystem _emag = default!; [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedPopupSystem _sharedPopupSystem = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedUserInterfaceSystem _ui = default!; /// public override void Initialize() @@ -55,8 +55,8 @@ public sealed class LockSystem : EntitySystem SubscribeLocalEvent(OnAttemptChangePanel); SubscribeLocalEvent(OnUnanchorAttempt); - SubscribeLocalEvent(OnUIOpenAttempt); - SubscribeLocalEvent(LockToggled); + SubscribeLocalEvent(OnUIOpenAttempt); + SubscribeLocalEvent(LockToggled); SubscribeLocalEvent(OnActivateAttempt); } @@ -97,6 +97,9 @@ public sealed class LockSystem : EntitySystem private void OnExamined(EntityUid uid, LockComponent lockComp, ExaminedEvent args) { + if (!lockComp.ShowExamine) + return; + args.PushText(Loc.GetString(lockComp.Locked ? "lock-comp-on-examined-is-locked" : "lock-comp-on-examined-is-unlocked", @@ -246,6 +249,20 @@ public sealed class LockSystem : EntitySystem return true; } + /// + /// Toggle the lock to locked if unlocked, and unlocked if locked. + /// + /// Entity to toggle the lock state of. + /// The person trying to toggle the lock + /// Entities lock comp (will be resolved) + public void ToggleLock(EntityUid uid, EntityUid? user, LockComponent? lockComp = null) + { + if (IsLocked((uid, lockComp))) + Unlock(uid, user, lockComp); + else + Lock(uid, user, lockComp); + } + /// /// Returns true if the entity is locked. /// Entities with no lock component are considered unlocked. @@ -304,11 +321,12 @@ public sealed class LockSystem : EntitySystem private void AddToggleLockVerb(EntityUid uid, LockComponent component, GetVerbsEvent args) { - if (!args.CanAccess || !args.CanInteract || !args.CanComplexInteract) + if (!args.CanAccess || !args.CanInteract || !args.CanComplexInteract || !component.ShowLockVerbs) return; AlternativeVerb verb = new() { + Disabled = !CanToggleLock(uid, args.User), Act = component.Locked ? () => TryUnlock(uid, args.User, component) : () => TryLock(uid, args.User, component), @@ -411,41 +429,54 @@ public sealed class LockSystem : EntitySystem args.Cancel(); } - private void OnUIOpenAttempt(EntityUid uid, ActivatableUIRequiresLockComponent component, ActivatableUIOpenAttemptEvent args) + private void OnUIOpenAttempt(EntityUid uid, UIRequiresLockComponent component, ActivatableUIOpenAttemptEvent args) { if (args.Cancelled) return; - if (TryComp(uid, out var lockComp) && lockComp.Locked != component.RequireLocked) - { - args.Cancel(); - if (lockComp.Locked) - { - _sharedPopupSystem.PopupClient(Loc.GetString("entity-storage-component-locked-message"), uid, args.User); - } + if (!TryComp(uid, out var lockComp) || lockComp.Locked == component.RequireLocked) + return; - _audio.PlayPredicted(component.AccessDeniedSound, uid, args.User); + args.Cancel(); + if (lockComp.Locked && component.Popup != null) + { + _sharedPopupSystem.PopupClient(Loc.GetString(component.Popup), uid, args.User); } + + _audio.PlayPredicted(component.AccessDeniedSound, uid, args.User); } - private void LockToggled(EntityUid uid, ActivatableUIRequiresLockComponent component, LockToggledEvent args) + private void LockToggled(EntityUid uid, UIRequiresLockComponent component, LockToggledEvent args) { if (!TryComp(uid, out var lockComp) || lockComp.Locked == component.RequireLocked) return; - _activatableUI.CloseAll(uid); + if (component.UserInterfaceKeys == null) + { + _ui.CloseUis(uid); + return; + } + + foreach (var key in component.UserInterfaceKeys) + { + _ui.CloseUi(uid, key); + } } + private void OnActivateAttempt(EntityUid uid, ItemToggleRequiresLockComponent component, ref ItemToggleActivateAttemptEvent args) { if (args.Cancelled) return; - if (TryComp(uid, out var lockComp) && lockComp.Locked != component.RequireLocked) + if (!TryComp(uid, out var lockComp) || lockComp.Locked == component.RequireLocked) + return; + + args.Cancelled = true; + + if (lockComp.Locked && component.LockedPopup != null) { - args.Cancelled = true; - if (lockComp.Locked) - _sharedPopupSystem.PopupClient(Loc.GetString("lock-comp-generic-fail", - ("target", Identity.Entity(uid, EntityManager))), + _sharedPopupSystem.PopupClient(Loc.GetString(component.LockedPopup, + ("target", Identity.Entity(uid, EntityManager))), uid, args.User); } diff --git a/Content.Shared/Lock/ActivatableUIRequiresLockComponent.cs b/Content.Shared/Lock/UIRequiresLockComponent.cs similarity index 67% rename from Content.Shared/Lock/ActivatableUIRequiresLockComponent.cs rename to Content.Shared/Lock/UIRequiresLockComponent.cs index a2a9d8c556..ab10526103 100644 --- a/Content.Shared/Lock/ActivatableUIRequiresLockComponent.cs +++ b/Content.Shared/Lock/UIRequiresLockComponent.cs @@ -7,8 +7,15 @@ namespace Content.Shared.Lock; /// This is used for activatable UIs that require the entity to have a lock in a certain state. /// [RegisterComponent, NetworkedComponent, Access(typeof(LockSystem))] -public sealed partial class ActivatableUIRequiresLockComponent : Component +public sealed partial class UIRequiresLockComponent : Component { + /// + /// UIs that are locked behind this component. + /// If null, will close all UIs. + /// + [DataField] + public List? UserInterfaceKeys; + /// /// TRUE: the lock must be locked to access the UI. /// FALSE: the lock must be unlocked to access the UI. @@ -21,4 +28,7 @@ public sealed partial class ActivatableUIRequiresLockComponent : Component /// [DataField] public SoundSpecifier? AccessDeniedSound = new SoundPathSpecifier("/Audio/Machines/custom_deny.ogg"); + + [DataField] + public LocId? Popup = "entity-storage-component-locked-message"; } diff --git a/Content.Shared/Medical/SuitSensor/SharedSuitSensor.cs b/Content.Shared/Medical/SuitSensors/SharedSuitSensor.cs similarity index 100% rename from Content.Shared/Medical/SuitSensor/SharedSuitSensor.cs rename to Content.Shared/Medical/SuitSensors/SharedSuitSensor.cs diff --git a/Content.Shared/Medical/SuitSensors/SharedSuitSensorSystem.cs b/Content.Shared/Medical/SuitSensors/SharedSuitSensorSystem.cs new file mode 100644 index 0000000000..2ed1089ba8 --- /dev/null +++ b/Content.Shared/Medical/SuitSensors/SharedSuitSensorSystem.cs @@ -0,0 +1,468 @@ +using System.Numerics; +using Content.Shared.Access.Systems; +using Content.Shared.ActionBlocker; +using Content.Shared.Clothing; +using Content.Shared.Damage; +using Content.Shared.DeviceNetwork; +using Content.Shared.DoAfter; +using Content.Shared.Examine; +using Content.Shared.GameTicking; +using Content.Shared.Interaction; +using Content.Shared.Inventory; +using Content.Shared.Medical.SuitSensor; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; +using Content.Shared.Mobs.Systems; +using Content.Shared.Popups; +using Content.Shared.Station; +using Content.Shared.Verbs; +using Robust.Shared.Containers; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Timing; + +namespace Content.Shared.Medical.SuitSensors; + +public abstract class SharedSuitSensorSystem : EntitySystem +{ + [Dependency] private readonly SharedStationSystem _stationSystem = default!; + [Dependency] private readonly MobStateSystem _mobStateSystem = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!; + [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly InventorySystem _inventory = default!; + [Dependency] private readonly SharedIdCardSystem _idCardSystem = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IGameTiming _timing = default!; + + private EntityQuery _sensorQuery; + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnPlayerSpawn); + SubscribeLocalEvent(OnEquipped); + SubscribeLocalEvent(OnUnequipped); + SubscribeLocalEvent(OnExamine); + SubscribeLocalEvent>(OnVerb); + SubscribeLocalEvent(OnInsert); + SubscribeLocalEvent(OnRemove); + SubscribeLocalEvent(OnSuitSensorDoAfter); + + _sensorQuery = GetEntityQuery(); + } + + /// + /// Checks whether the sensor is assigned to a station or not + /// and tries to assign an unassigned sensor to a station if it's currently on a grid. + /// + /// True if the sensor is assigned to a station or assigning it was successful. False otherwise. + public bool CheckSensorAssignedStation(Entity sensor) + { + if (!sensor.Comp.StationId.HasValue && Transform(sensor.Owner).GridUid == null) + return false; + + sensor.Comp.StationId = _stationSystem.GetOwningStation(sensor.Owner); + Dirty(sensor); + return sensor.Comp.StationId.HasValue; + } + + private void OnMapInit(Entity ent, ref MapInitEvent args) + { + // Fallback + ent.Comp.StationId ??= _stationSystem.GetOwningStation(ent.Owner); + + // generate random mode + if (ent.Comp.RandomMode) + { + //make the sensor mode favor higher levels, except coords. + var modesDist = new[] + { + SuitSensorMode.SensorOff, + SuitSensorMode.SensorBinary, SuitSensorMode.SensorBinary, + SuitSensorMode.SensorVitals, SuitSensorMode.SensorVitals, SuitSensorMode.SensorVitals, + SuitSensorMode.SensorCords, SuitSensorMode.SensorCords + }; + ent.Comp.Mode = _random.Pick(modesDist); + } + + ent.Comp.NextUpdate = _timing.CurTime; + Dirty(ent); + } + + private void OnPlayerSpawn(PlayerSpawnCompleteEvent ev) + { + // If the player spawns in arrivals then the grid underneath them may not be appropriate. + // in which case we'll just use the station spawn code told us they are attached to and set all of their + // sensors. + RecursiveSensor(ev.Mob, ev.Station); + } + + private void RecursiveSensor(EntityUid uid, EntityUid stationUid) + { + var xform = Transform(uid); + var enumerator = xform.ChildEnumerator; + + while (enumerator.MoveNext(out var child)) + { + if (_sensorQuery.TryComp(child, out var sensor)) + { + sensor.StationId = stationUid; + Dirty(child, sensor); + } + + RecursiveSensor(child, stationUid); + } + } + + private void OnEquipped(Entity ent, ref ClothingGotEquippedEvent args) + { + ent.Comp.User = args.Wearer; + Dirty(ent); + } + + private void OnUnequipped(Entity ent, ref ClothingGotUnequippedEvent args) + { + ent.Comp.User = null; + Dirty(ent); + } + + private void OnExamine(Entity ent, ref ExaminedEvent args) + { + if (!args.IsInDetailsRange) + return; + + string msg; + switch (ent.Comp.Mode) + { + case SuitSensorMode.SensorOff: + msg = "suit-sensor-examine-off"; + break; + case SuitSensorMode.SensorBinary: + msg = "suit-sensor-examine-binary"; + break; + case SuitSensorMode.SensorVitals: + msg = "suit-sensor-examine-vitals"; + break; + case SuitSensorMode.SensorCords: + msg = "suit-sensor-examine-cords"; + break; + default: + return; + } + + args.PushMarkup(Loc.GetString(msg)); + } + + private void OnVerb(Entity ent, ref GetVerbsEvent args) + { + // check if user can change sensor + if (ent.Comp.ControlsLocked) + return; + + // standard interaction checks + if (!args.CanInteract || args.Hands == null) + return; + + if (!_interactionSystem.InRangeUnobstructed(args.User, args.Target)) + return; + + // check if target is incapacitated (cuffed, dead, etc) + if (ent.Comp.User != null && args.User != ent.Comp.User && _actionBlocker.CanInteract(ent.Comp.User.Value, null)) + return; + + args.Verbs.UnionWith(new[] + { + CreateVerb(ent, args.User, SuitSensorMode.SensorOff), + CreateVerb(ent, args.User, SuitSensorMode.SensorBinary), + CreateVerb(ent, args.User, SuitSensorMode.SensorVitals), + CreateVerb(ent, args.User, SuitSensorMode.SensorCords) + }); + } + + private void OnInsert(Entity ent, ref EntGotInsertedIntoContainerMessage args) + { + if (args.Container.ID != ent.Comp.ActivationContainer) + return; + + ent.Comp.User = args.Container.Owner; + Dirty(ent); + } + + private void OnRemove(Entity ent, ref EntGotRemovedFromContainerMessage args) + { + if (args.Container.ID != ent.Comp.ActivationContainer) + return; + + ent.Comp.User = null; + Dirty(ent); + } + + private Verb CreateVerb(Entity ent, EntityUid userUid, SuitSensorMode mode) + { + return new Verb() + { + Text = GetModeName(mode), + Disabled = ent.Comp.Mode == mode, + Priority = -(int)mode, // sort them in descending order + Category = VerbCategory.SetSensor, + Act = () => TrySetSensor(ent.AsNullable(), mode, userUid) + }; + } + + public string GetModeName(SuitSensorMode mode) + { + string name; + switch (mode) + { + case SuitSensorMode.SensorOff: + name = "suit-sensor-mode-off"; + break; + case SuitSensorMode.SensorBinary: + name = "suit-sensor-mode-binary"; + break; + case SuitSensorMode.SensorVitals: + name = "suit-sensor-mode-vitals"; + break; + case SuitSensorMode.SensorCords: + name = "suit-sensor-mode-cords"; + break; + default: + return ""; + } + + return Loc.GetString(name); + } + + /// + /// Attempts to set mode of the entity to the selected in params. + /// Works instantly if the user is the player wearing the sensors and will start a DoAfter otherwise. + /// + /// Entity and its component that should be changed. + /// Selected mode + /// userUid, when not equal to the , creates doafter + public bool TrySetSensor(Entity sensors, SuitSensorMode mode, EntityUid userUid) + { + if (!Resolve(sensors, ref sensors.Comp, false)) + return false; + + if (sensors.Comp.User == null || userUid == sensors.Comp.User) + SetSensor(sensors, mode, userUid); + else + { + var doAfterEvent = new SuitSensorChangeDoAfterEvent(mode); + var doAfterArgs = new DoAfterArgs(EntityManager, userUid, sensors.Comp.SensorsTime, doAfterEvent, sensors) + { + BreakOnMove = true, + BreakOnDamage = true + }; + + _doAfterSystem.TryStartDoAfter(doAfterArgs); + } + return true; + } + + private void OnSuitSensorDoAfter(Entity sensors, ref SuitSensorChangeDoAfterEvent args) + { + if (args.Handled || args.Cancelled) + return; + + SetSensor(sensors.AsNullable(), args.Mode, args.User); + } + + /// + /// Sets mode of the of the chosen entity. + /// Makes popup when not null + /// + /// Entity and it's component that should be changed + /// Selected mode + /// uid, required for the popup + public void SetSensor(Entity sensors, SuitSensorMode mode, EntityUid? userUid = null) + { + if (!Resolve(sensors, ref sensors.Comp, false)) + return; + + sensors.Comp.Mode = mode; + Dirty(sensors); + + if (userUid != null) + { + var msg = Loc.GetString("suit-sensor-mode-state", ("mode", GetModeName(mode))); + _popupSystem.PopupClient(msg, sensors, userUid.Value); + } + } + + /// + /// Set all suit sensors on the equipment someone is wearing to the specified mode. + /// + public void SetAllSensors(EntityUid target, SuitSensorMode mode, SlotFlags slots = SlotFlags.All) + { + // iterate over all inventory slots + var slotEnumerator = _inventory.GetSlotEnumerator(target, slots); + while (slotEnumerator.NextItem(out var item, out _)) + { + if (TryComp(item, out var sensorComp)) + SetSensor((item, sensorComp), mode); + } + } + + /// + /// Attempts to get full from the + /// + /// Entity to get status + /// Full of the chosen uid + public SuitSensorStatus? GetSensorState(Entity ent) + { + if (!Resolve(ent, ref ent.Comp1, ref ent.Comp2, false)) + return null; + + var sensor = ent.Comp1; + var transform = ent.Comp2; + + // check if sensor is enabled and worn by user + if (sensor.Mode == SuitSensorMode.SensorOff || sensor.User == null || !HasComp(sensor.User) || transform.GridUid == null) + return null; + + // try to get mobs id from ID slot + var userName = Loc.GetString("suit-sensor-component-unknown-name"); + var userJob = Loc.GetString("suit-sensor-component-unknown-job"); + var userJobIcon = "JobIconNoId"; + var userJobDepartments = new List(); + + if (_idCardSystem.TryFindIdCard(sensor.User.Value, out var card)) + { + if (card.Comp.FullName != null) + userName = card.Comp.FullName; + if (card.Comp.LocalizedJobTitle != null) + userJob = card.Comp.LocalizedJobTitle; + userJobIcon = card.Comp.JobIcon; + + foreach (var department in card.Comp.JobDepartments) + userJobDepartments.Add(Loc.GetString(_proto.Index(department).Name)); + } + + // get health mob state + var isAlive = false; + if (TryComp(sensor.User.Value, out MobStateComponent? mobState)) + isAlive = !_mobStateSystem.IsDead(sensor.User.Value, mobState); + + // get mob total damage + var totalDamage = 0; + if (TryComp(sensor.User.Value, out var damageable)) + totalDamage = damageable.TotalDamage.Int(); + + // Get mob total damage crit threshold + int? totalDamageThreshold = null; + if (_mobThresholdSystem.TryGetThresholdForState(sensor.User.Value, MobState.Critical, out var critThreshold)) + totalDamageThreshold = critThreshold.Value.Int(); + + // finally, form suit sensor status + var status = new SuitSensorStatus(GetNetEntity(sensor.User.Value), GetNetEntity(ent.Owner), userName, userJob, userJobIcon, userJobDepartments); + switch (sensor.Mode) + { + case SuitSensorMode.SensorBinary: + status.IsAlive = isAlive; + break; + case SuitSensorMode.SensorVitals: + status.IsAlive = isAlive; + status.TotalDamage = totalDamage; + status.TotalDamageThreshold = totalDamageThreshold; + break; + case SuitSensorMode.SensorCords: + status.IsAlive = isAlive; + status.TotalDamage = totalDamage; + status.TotalDamageThreshold = totalDamageThreshold; + EntityCoordinates coordinates; + var xformQuery = GetEntityQuery(); + + if (transform.GridUid != null) + { + coordinates = new EntityCoordinates(transform.GridUid.Value, + Vector2.Transform(_transform.GetWorldPosition(transform, xformQuery), + _transform.GetInvWorldMatrix(xformQuery.GetComponent(transform.GridUid.Value), xformQuery))); + } + else if (transform.MapUid != null) + { + coordinates = new EntityCoordinates(transform.MapUid.Value, + _transform.GetWorldPosition(transform, xformQuery)); + } + else + { + coordinates = EntityCoordinates.Invalid; + } + + status.Coordinates = GetNetCoordinates(coordinates); + break; + } + + return status; + } + + /// + /// Create a device network package from the suit sensors status. + /// + public NetworkPayload SuitSensorToPacket(SuitSensorStatus status) + { + var payload = new NetworkPayload() + { + [DeviceNetworkConstants.Command] = DeviceNetworkConstants.CmdUpdatedState, + [SuitSensorConstants.NET_NAME] = status.Name, + [SuitSensorConstants.NET_JOB] = status.Job, + [SuitSensorConstants.NET_JOB_ICON] = status.JobIcon, + [SuitSensorConstants.NET_JOB_DEPARTMENTS] = status.JobDepartments, + [SuitSensorConstants.NET_IS_ALIVE] = status.IsAlive, + [SuitSensorConstants.NET_SUIT_SENSOR_UID] = status.SuitSensorUid, + [SuitSensorConstants.NET_OWNER_UID] = status.OwnerUid, + }; + + if (status.TotalDamage != null) + payload.Add(SuitSensorConstants.NET_TOTAL_DAMAGE, status.TotalDamage); + if (status.TotalDamageThreshold != null) + payload.Add(SuitSensorConstants.NET_TOTAL_DAMAGE_THRESHOLD, status.TotalDamageThreshold); + if (status.Coordinates != null) + payload.Add(SuitSensorConstants.NET_COORDINATES, status.Coordinates); + + return payload; + } + + /// + /// Try to create the suit sensors status from the device network message. + /// + public SuitSensorStatus? PacketToSuitSensor(NetworkPayload payload) + { + // check command + if (!payload.TryGetValue(DeviceNetworkConstants.Command, out string? command)) + return null; + if (command != DeviceNetworkConstants.CmdUpdatedState) + return null; + + // check name, job and alive + if (!payload.TryGetValue(SuitSensorConstants.NET_NAME, out string? name)) return null; + if (!payload.TryGetValue(SuitSensorConstants.NET_JOB, out string? job)) return null; + if (!payload.TryGetValue(SuitSensorConstants.NET_JOB_ICON, out string? jobIcon)) return null; + if (!payload.TryGetValue(SuitSensorConstants.NET_JOB_DEPARTMENTS, out List? jobDepartments)) return null; + if (!payload.TryGetValue(SuitSensorConstants.NET_IS_ALIVE, out bool? isAlive)) return null; + if (!payload.TryGetValue(SuitSensorConstants.NET_SUIT_SENSOR_UID, out NetEntity suitSensorUid)) return null; + if (!payload.TryGetValue(SuitSensorConstants.NET_OWNER_UID, out NetEntity ownerUid)) return null; + + // try get total damage and cords (optionals) + payload.TryGetValue(SuitSensorConstants.NET_TOTAL_DAMAGE, out int? totalDamage); + payload.TryGetValue(SuitSensorConstants.NET_TOTAL_DAMAGE_THRESHOLD, out int? totalDamageThreshold); + payload.TryGetValue(SuitSensorConstants.NET_COORDINATES, out NetCoordinates? coords); + + var status = new SuitSensorStatus(ownerUid, suitSensorUid, name, job, jobIcon, jobDepartments) + { + IsAlive = isAlive.Value, + TotalDamage = totalDamage, + TotalDamageThreshold = totalDamageThreshold, + Coordinates = coords, + }; + return status; + } +} diff --git a/Content.Server/Medical/SuitSensors/SuitSensorComponent.cs b/Content.Shared/Medical/SuitSensors/SuitSensorComponent.cs similarity index 89% rename from Content.Server/Medical/SuitSensors/SuitSensorComponent.cs rename to Content.Shared/Medical/SuitSensors/SuitSensorComponent.cs index 626ea914b4..f782f2e55d 100644 --- a/Content.Server/Medical/SuitSensors/SuitSensorComponent.cs +++ b/Content.Shared/Medical/SuitSensors/SuitSensorComponent.cs @@ -1,14 +1,16 @@ using Content.Shared.Medical.SuitSensor; +using Robust.Shared.GameStates; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; -namespace Content.Server.Medical.SuitSensors; +namespace Content.Shared.Medical.SuitSensors; /// /// Tracking device, embedded in almost all uniforms and jumpsuits. /// If enabled, will report to crew monitoring console owners position and status. /// -[RegisterComponent, AutoGenerateComponentPause] -[Access(typeof(SuitSensorSystem))] +[RegisterComponent, NetworkedComponent] +[Access(typeof(SharedSuitSensorSystem))] +[AutoGenerateComponentState, AutoGenerateComponentPause] public sealed partial class SuitSensorComponent : Component { /// @@ -20,7 +22,7 @@ public sealed partial class SuitSensorComponent : Component /// /// If true user can't change suit sensor mode /// - [DataField] + [DataField, AutoNetworkedField] public bool ControlsLocked = false; /// @@ -32,7 +34,7 @@ public sealed partial class SuitSensorComponent : Component /// /// Current sensor mode. Can be switched by user verbs. /// - [DataField] + [DataField, AutoNetworkedField] public SuitSensorMode Mode = SuitSensorMode.SensorOff; /// @@ -56,7 +58,7 @@ public sealed partial class SuitSensorComponent : Component /// /// Current user that wears suit sensor. Null if nobody wearing it. /// - [ViewVariables] + [DataField, AutoNetworkedField] public EntityUid? User = null; /// @@ -69,7 +71,7 @@ public sealed partial class SuitSensorComponent : Component /// /// The station this suit sensor belongs to. If it's null the suit didn't spawn on a station and the sensor doesn't work. /// - [DataField("station")] + [DataField("station"), AutoNetworkedField] public EntityUid? StationId = null; /// diff --git a/Content.Shared/Power/Components/SharedApcPowerReceiverComponent.cs b/Content.Shared/Power/Components/SharedApcPowerReceiverComponent.cs index 80bacd24bd..96189a92ae 100644 --- a/Content.Shared/Power/Components/SharedApcPowerReceiverComponent.cs +++ b/Content.Shared/Power/Components/SharedApcPowerReceiverComponent.cs @@ -19,4 +19,7 @@ public abstract partial class SharedApcPowerReceiverComponent : Component /// [ViewVariables(VVAccess.ReadWrite)] public virtual bool PowerDisabled { get; set; } + + // Doesn't actually do anything on the client just here for shared code. + public abstract float Load { get; set; } } diff --git a/Content.Shared/Power/Generator/SharedPortableGeneratorSystem.cs b/Content.Shared/Power/Generator/SharedPortableGeneratorSystem.cs index e8ccc85166..4ed911c296 100644 --- a/Content.Shared/Power/Generator/SharedPortableGeneratorSystem.cs +++ b/Content.Shared/Power/Generator/SharedPortableGeneratorSystem.cs @@ -9,6 +9,18 @@ namespace Content.Shared.Power.Generator; /// public abstract class SharedPortableGeneratorSystem : EntitySystem { + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnSwitchPowerCheck); + } + + private void OnSwitchPowerCheck(EntityUid uid, FuelGeneratorComponent comp, ref SwitchPowerCheckEvent args) + { + if (comp.On) + args.DisableMessage = Loc.GetString("fuel-generator-verb-disable-on"); + } } /// diff --git a/Content.Shared/Power/Generator/SharedPowerSwitchableSystem.cs b/Content.Shared/Power/Generator/SharedPowerSwitchableSystem.cs index c1787b6078..00d2b1d7a0 100644 --- a/Content.Shared/Power/Generator/SharedPowerSwitchableSystem.cs +++ b/Content.Shared/Power/Generator/SharedPowerSwitchableSystem.cs @@ -1,4 +1,6 @@ using Content.Shared.Examine; +using Content.Shared.Verbs; +using Robust.Shared.Utility; namespace Content.Shared.Power.Generator; @@ -11,6 +13,7 @@ public abstract class SharedPowerSwitchableSystem : EntitySystem public override void Initialize() { SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent>(GetVerbs); } private void OnExamined(EntityUid uid, PowerSwitchableComponent comp, ExaminedEvent args) @@ -20,6 +23,41 @@ public abstract class SharedPowerSwitchableSystem : EntitySystem args.PushMarkup(Loc.GetString(comp.ExamineText, ("voltage", voltage))); } + private void GetVerbs(EntityUid uid, PowerSwitchableComponent comp, GetVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract) + return; + + var voltage = VoltageColor(GetNextVoltage(uid, comp)); + var msg = Loc.GetString("power-switchable-switch-voltage", ("voltage", voltage)); + + InteractionVerb verb = new() + { + Act = () => + { + // don't need to check it again since if its disabled server wont let the verb act + Cycle(uid, args.User, comp); + }, + Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/zap.svg.192dpi.png")), + Text = msg + }; + + var ev = new SwitchPowerCheckEvent(); + RaiseLocalEvent(uid, ref ev); + if (ev.DisableMessage != null) + { + verb.Message = ev.DisableMessage; + verb.Disabled = true; + } + + args.Verbs.Add(verb); + } + + /// + /// Cycles voltage then updates nodes and optionally power supplier to match it. + /// + public virtual void Cycle(EntityUid uid, EntityUid user, PowerSwitchableComponent? comp = null) { } + /// /// Helper to get the colored markup string for a voltage type. /// @@ -70,3 +108,10 @@ public abstract class SharedPowerSwitchableSystem : EntitySystem return comp.Cables[NextIndex(uid, comp)].Voltage; } } + +/// +/// Raised on a to see if its verb should work. +/// If is non-null, the verb is disabled with that as the message. +/// +[ByRefEvent] +public record struct SwitchPowerCheckEvent(string? DisableMessage = null); diff --git a/Content.Shared/Robotics/Components/RoboticsConsoleComponent.cs b/Content.Shared/Robotics/Components/RoboticsConsoleComponent.cs index 9e4b51866f..eef2007f31 100644 --- a/Content.Shared/Robotics/Components/RoboticsConsoleComponent.cs +++ b/Content.Shared/Robotics/Components/RoboticsConsoleComponent.cs @@ -50,4 +50,10 @@ public sealed partial class RoboticsConsoleComponent : Component [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] [AutoNetworkedField, AutoPausedField] public TimeSpan NextDestroy = TimeSpan.Zero; + + /// + /// Controls if the console can disable or destroy any borg. + /// + [DataField] + public bool AllowBorgControl = true; } diff --git a/Content.Shared/Robotics/RoboticsConsoleUi.cs b/Content.Shared/Robotics/RoboticsConsoleUi.cs index 7a6974fb51..01ecb3f598 100644 --- a/Content.Shared/Robotics/RoboticsConsoleUi.cs +++ b/Content.Shared/Robotics/RoboticsConsoleUi.cs @@ -1,4 +1,4 @@ -using Robust.Shared.Prototypes; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; using Robust.Shared.Utility; @@ -19,9 +19,15 @@ public sealed class RoboticsConsoleState : BoundUserInterfaceState /// public Dictionary Cyborgs; - public RoboticsConsoleState(Dictionary cyborgs) + /// + /// If the UI will have the buttons to disable and destroy. + /// + public bool AllowBorgControl; + + public RoboticsConsoleState(Dictionary cyborgs, bool allowBorgControl) { Cyborgs = cyborgs; + AllowBorgControl = allowBorgControl; } } @@ -84,6 +90,12 @@ public partial record struct CyborgControlData [DataField] public float Charge; + /// + /// HP level from 0 to 1. + /// + [DataField] + public float HpPercent; // 0.0 to 1.0 + /// /// How many modules this borg has, just useful information for roboticists. /// Lets them keep track of the latejoin borgs that need new modules and stuff. @@ -111,12 +123,13 @@ public partial record struct CyborgControlData [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] public TimeSpan Timeout = TimeSpan.Zero; - public CyborgControlData(SpriteSpecifier? chassisSprite, string chassisName, string name, float charge, int moduleCount, bool hasBrain, bool canDisable) + public CyborgControlData(SpriteSpecifier? chassisSprite, string chassisName, string name, float charge, float hpPercent, int moduleCount, bool hasBrain, bool canDisable) { ChassisSprite = chassisSprite; ChassisName = chassisName; Name = name; Charge = charge; + HpPercent = hpPercent; ModuleCount = moduleCount; HasBrain = hasBrain; CanDisable = canDisable; diff --git a/Content.Shared/Roles/Components/ChangelingRoleComponent.cs b/Content.Shared/Roles/Components/ChangelingRoleComponent.cs new file mode 100644 index 0000000000..fb9bc05af3 --- /dev/null +++ b/Content.Shared/Roles/Components/ChangelingRoleComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// Added to mind role entities to tag that they are a changeling. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class ChangelingRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/Roles/Components/DragonRoleComponent.cs b/Content.Shared/Roles/Components/DragonRoleComponent.cs new file mode 100644 index 0000000000..8f5abecd24 --- /dev/null +++ b/Content.Shared/Roles/Components/DragonRoleComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// Added to mind role entities to tag that they are a space dragon. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class DragonRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/Roles/Components/GhostRoleMarkerRoleComponent.cs b/Content.Shared/Roles/Components/GhostRoleMarkerRoleComponent.cs new file mode 100644 index 0000000000..623f298474 --- /dev/null +++ b/Content.Shared/Roles/Components/GhostRoleMarkerRoleComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// Added to mind role entities to tag that they are a ghostrole. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class GhostRoleMarkerRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/Roles/Components/InitialInfectedRoleComponent.cs b/Content.Shared/Roles/Components/InitialInfectedRoleComponent.cs new file mode 100644 index 0000000000..a96c9a4e1d --- /dev/null +++ b/Content.Shared/Roles/Components/InitialInfectedRoleComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// Added to mind role entities to tag that they are an initial infected. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class InitialInfectedRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/Roles/Jobs/JobRoleComponent.cs b/Content.Shared/Roles/Components/JobRoleComponent.cs similarity index 59% rename from Content.Shared/Roles/Jobs/JobRoleComponent.cs rename to Content.Shared/Roles/Components/JobRoleComponent.cs index dbaf12beec..c62c0ea1f4 100644 --- a/Content.Shared/Roles/Jobs/JobRoleComponent.cs +++ b/Content.Shared/Roles/Components/JobRoleComponent.cs @@ -1,12 +1,9 @@ using Robust.Shared.GameStates; -namespace Content.Shared.Roles.Jobs; +namespace Content.Shared.Roles.Components; /// -/// Added to mind role entities to mark them as a job role entity. +/// Added to mind role entities to mark them as a job role entity. /// [RegisterComponent, NetworkedComponent] -public sealed partial class JobRoleComponent : BaseMindRoleComponent -{ - -} +public sealed partial class JobRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/Roles/MindRoleComponent.cs b/Content.Shared/Roles/Components/MindRoleComponent.cs similarity index 52% rename from Content.Shared/Roles/MindRoleComponent.cs rename to Content.Shared/Roles/Components/MindRoleComponent.cs index 09593c94cd..45ab808192 100644 --- a/Content.Shared/Roles/MindRoleComponent.cs +++ b/Content.Shared/Roles/Components/MindRoleComponent.cs @@ -2,7 +2,7 @@ using Content.Shared.Mind; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; -namespace Content.Shared.Roles; +namespace Content.Shared.Roles.Components; /// /// This holds data for, and indicates, a Mind Role entity @@ -11,49 +11,52 @@ namespace Content.Shared.Roles; public sealed partial class MindRoleComponent : BaseMindRoleComponent { /// - /// Marks this Mind Role as Antagonist - /// A single antag Mind Role is enough to make the owner mind count as Antagonist. + /// Marks this Mind Role as Antagonist. + /// A single antag Mind Role is enough to make the owner mind count as Antagonist. /// [DataField] public bool Antag; /// - /// The mind's current antagonist/special role, or lack thereof; + /// The mind's current antagonist/special role, or lack thereof. /// [DataField] public ProtoId? RoleType; /// - /// The role's subtype, shown only to admins to help with antag categorization + /// The role's subtype, shown only to admins to help with antag categorization. /// [DataField] public LocId? Subtype; /// - /// True if this mindrole is an exclusive antagonist. Antag setting is not checked if this is True. + /// True if this mindrole is an exclusive antagonist. Antag setting is not checked if this is True. /// [DataField] public bool ExclusiveAntag; /// - /// The Mind that this role belongs to + /// The Mind that this role belongs to. /// - public Entity Mind { get; set; } + /// + /// TODO: Make this a datafield. Also components should not store other components. + /// + public Entity Mind; /// - /// The Antagonist prototype of this role + /// The Antagonist prototype of this role. /// [DataField] - public ProtoId? AntagPrototype { get; set; } + public ProtoId? AntagPrototype; /// - /// The Job prototype of this role + /// The Job prototype of this role. /// [DataField] - public ProtoId? JobPrototype { get; set; } + public ProtoId? JobPrototype; /// - /// Used to order the characters on by role/antag status. Highest numbers are shown first. + /// Used to order the characters on by role/antag status. Highest numbers are shown first. /// [DataField] public int SortWeight; @@ -62,7 +65,4 @@ public sealed partial class MindRoleComponent : BaseMindRoleComponent // Why does this base component actually exist? It does make auto-categorization easy, but before that it was useless? // I used it for easy organisation/bookkeeping of what components are for mindroles [EntityCategory("Roles")] -public abstract partial class BaseMindRoleComponent : Component -{ - -} +public abstract partial class BaseMindRoleComponent : Component; diff --git a/Content.Shared/Roles/Components/NinjaRoleComponent.cs b/Content.Shared/Roles/Components/NinjaRoleComponent.cs new file mode 100644 index 0000000000..4aa72e1628 --- /dev/null +++ b/Content.Shared/Roles/Components/NinjaRoleComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// Added to mind role entities to tag that they are a space ninja. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class NinjaRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/Roles/Components/NukeopsRoleComponent.cs b/Content.Shared/Roles/Components/NukeopsRoleComponent.cs new file mode 100644 index 0000000000..57b90236e5 --- /dev/null +++ b/Content.Shared/Roles/Components/NukeopsRoleComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// Added to mind role entities to tag that they are a nuke operative. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class NukeopsRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/Roles/Components/ObserverRoleComponent.cs b/Content.Shared/Roles/Components/ObserverRoleComponent.cs new file mode 100644 index 0000000000..c7a451eac3 --- /dev/null +++ b/Content.Shared/Roles/Components/ObserverRoleComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// This is used to mark Observers properly, as they get Minds. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class ObserverRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/Roles/Components/ParadoxCloneRoleComponent.cs b/Content.Shared/Roles/Components/ParadoxCloneRoleComponent.cs new file mode 100644 index 0000000000..40a6e86499 --- /dev/null +++ b/Content.Shared/Roles/Components/ParadoxCloneRoleComponent.cs @@ -0,0 +1,17 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// Added to mind role entities to tag that they are a paradox clone. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class ParadoxCloneRoleComponent : BaseMindRoleComponent +{ + /// + /// Name modifer applied to the player when they turn into a ghost. + /// Needed to be able to keep the original and the clone apart in dead chat. + /// + [DataField] + public LocId? NameModifier = "paradox-clone-ghost-name-modifier"; +} diff --git a/Content.Shared/Roles/Components/RevolutionaryRoleComponent.cs b/Content.Shared/Roles/Components/RevolutionaryRoleComponent.cs new file mode 100644 index 0000000000..d4d4660814 --- /dev/null +++ b/Content.Shared/Roles/Components/RevolutionaryRoleComponent.cs @@ -0,0 +1,16 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// Added to mind role entities to tag that they are a Revolutionary. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class RevolutionaryRoleComponent : BaseMindRoleComponent +{ + /// + /// For headrevs, how many people you have converted. + /// + [DataField, AutoNetworkedField] + public uint ConvertedCount = 0; +} diff --git a/Content.Shared/Roles/Components/RoleBriefingComponent.cs b/Content.Shared/Roles/Components/RoleBriefingComponent.cs new file mode 100644 index 0000000000..99eccf8a34 --- /dev/null +++ b/Content.Shared/Roles/Components/RoleBriefingComponent.cs @@ -0,0 +1,13 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// Adds a briefing to the character info menu, does nothing else. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class RoleBriefingComponent : BaseMindRoleComponent +{ + [DataField(required: true), AutoNetworkedField] + public LocId Briefing; +} diff --git a/Content.Shared/Roles/Components/SiliconBrainRoleComponent.cs b/Content.Shared/Roles/Components/SiliconBrainRoleComponent.cs new file mode 100644 index 0000000000..d8eaebbe1f --- /dev/null +++ b/Content.Shared/Roles/Components/SiliconBrainRoleComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// Used on Silicon's minds to get the appropriate mind role +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class SiliconBrainRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/Roles/StartingMindRoleComponent.cs b/Content.Shared/Roles/Components/StartingMindRoleComponent.cs similarity index 84% rename from Content.Shared/Roles/StartingMindRoleComponent.cs rename to Content.Shared/Roles/Components/StartingMindRoleComponent.cs index 768307d391..a37ed29a23 100644 --- a/Content.Shared/Roles/StartingMindRoleComponent.cs +++ b/Content.Shared/Roles/Components/StartingMindRoleComponent.cs @@ -1,26 +1,25 @@ using Robust.Shared.GameStates; using Robust.Shared.Prototypes; -namespace Content.Shared.Roles; +namespace Content.Shared.Roles.Components; /// /// This is most likely not the component you are looking for, almost nothing should be using this. /// Consider using GhostRoleComponent or AntagSelectionComponent instead. /// /// The specified mind role will be added to the mob on spawn. -/// /// [RegisterComponent, NetworkedComponent] public sealed partial class StartingMindRoleComponent : Component { /// - /// The ID of the mind role to add + /// The ID of the mind role to add /// [DataField(required: true)] public EntProtoId MindRole; /// - /// Add the mind role silently + /// Add the mind role silently /// [DataField] public bool Silent = true; diff --git a/Content.Shared/Roles/Components/SubvertedSiliconRoleComponent.cs b/Content.Shared/Roles/Components/SubvertedSiliconRoleComponent.cs new file mode 100644 index 0000000000..6b62f458ce --- /dev/null +++ b/Content.Shared/Roles/Components/SubvertedSiliconRoleComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// Added to mind role entities to tag that they are a hacked borg. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class SubvertedSiliconRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/Roles/Components/SurvivorRoleComponent.cs b/Content.Shared/Roles/Components/SurvivorRoleComponent.cs new file mode 100644 index 0000000000..1eee04d9e5 --- /dev/null +++ b/Content.Shared/Roles/Components/SurvivorRoleComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// Added to mind role entities to tag that they are survivor. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class SurvivorRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/Roles/Components/ThiefRoleComponent.cs b/Content.Shared/Roles/Components/ThiefRoleComponent.cs new file mode 100644 index 0000000000..ee30833970 --- /dev/null +++ b/Content.Shared/Roles/Components/ThiefRoleComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// Added to mind role entities to tag that they are a thief. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class ThiefRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/Roles/Components/TraitorRoleComponent.cs b/Content.Shared/Roles/Components/TraitorRoleComponent.cs new file mode 100644 index 0000000000..7348fe0c57 --- /dev/null +++ b/Content.Shared/Roles/Components/TraitorRoleComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// Added to mind role entities to tag that they are a syndicate traitor. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class TraitorRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/Roles/Components/WizardRoleComponent.cs b/Content.Shared/Roles/Components/WizardRoleComponent.cs new file mode 100644 index 0000000000..0d24897216 --- /dev/null +++ b/Content.Shared/Roles/Components/WizardRoleComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// Added to mind role entities to tag that they are a wizard. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class WizardRoleComponent : Component; diff --git a/Content.Shared/Roles/Components/ZombieRoleComponent.cs b/Content.Shared/Roles/Components/ZombieRoleComponent.cs new file mode 100644 index 0000000000..e137fb0b0f --- /dev/null +++ b/Content.Shared/Roles/Components/ZombieRoleComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Components; + +/// +/// Added to mind role entities to tag that they are a zombie. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class ZombieRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/Roles/Jobs/SharedJobSystem.cs b/Content.Shared/Roles/Jobs/SharedJobSystem.cs index 6d82ebfd04..ab4136a690 100644 --- a/Content.Shared/Roles/Jobs/SharedJobSystem.cs +++ b/Content.Shared/Roles/Jobs/SharedJobSystem.cs @@ -2,6 +2,7 @@ using System.Linq; using Content.Shared.Players; using Content.Shared.Players.PlayTimeTracking; +using Content.Shared.Roles.Components; using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Utility; diff --git a/Content.Shared/Roles/SharedRoleSystem.cs b/Content.Shared/Roles/SharedRoleSystem.cs index b21973248f..4f06d60184 100644 --- a/Content.Shared/Roles/SharedRoleSystem.cs +++ b/Content.Shared/Roles/SharedRoleSystem.cs @@ -5,7 +5,7 @@ using Content.Shared.CCVar; using Content.Shared.Database; using Content.Shared.GameTicking; using Content.Shared.Mind; -using Content.Shared.Roles.Jobs; +using Content.Shared.Roles.Components; using Content.Shared.Whitelist; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; diff --git a/Content.Shared/Roles/SiliconBrainRoleComponent.cs b/Content.Shared/Roles/SiliconBrainRoleComponent.cs deleted file mode 100644 index 72ad0a86b6..0000000000 --- a/Content.Shared/Roles/SiliconBrainRoleComponent.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Content.Shared.Roles; - -/// -/// Used on Silicon's minds to get the appropriate mind role -/// -[RegisterComponent] -public sealed partial class SiliconBrainRoleComponent : BaseMindRoleComponent -{ -} diff --git a/Content.Shared/SecretLocks/SharedVoiceTriggerLockSystem.cs b/Content.Shared/SecretLocks/SharedVoiceTriggerLockSystem.cs new file mode 100644 index 0000000000..483b3ec251 --- /dev/null +++ b/Content.Shared/SecretLocks/SharedVoiceTriggerLockSystem.cs @@ -0,0 +1,30 @@ +using Content.Shared.Item.ItemToggle; +using Content.Shared.Lock; +using Content.Shared.Trigger.Components.Triggers; + +namespace Content.Shared.SecretLocks; + +public sealed partial class SharedVoiceTriggerLockSystem : EntitySystem +{ + [Dependency] private readonly ItemToggleSystem _toggle = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnLockToggled); + } + + private void OnLockToggled(Entity ent, ref LockToggledEvent args) + { + if (!TryComp(ent.Owner, out var triggerComp)) + return; + + triggerComp.ShowVerbs = !args.Locked; + triggerComp.ShowExamine = !args.Locked; + + _toggle.TryDeactivate(ent.Owner, null, true, false); + + Dirty(ent.Owner, triggerComp); + } +} diff --git a/Content.Shared/SecretLocks/VoiceTriggerLockComponent.cs b/Content.Shared/SecretLocks/VoiceTriggerLockComponent.cs new file mode 100644 index 0000000000..345f76226f --- /dev/null +++ b/Content.Shared/SecretLocks/VoiceTriggerLockComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.SecretLocks; + +/// +/// "Locks" items (Doesn't actually lock them but just switches various settings) so its not possible to tell +/// the item is triggered by a voice activation. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class VoiceTriggerLockComponent : Component; diff --git a/Content.Shared/Shuttles/BUIStates/DockingPortState.cs b/Content.Shared/Shuttles/BUIStates/DockingPortState.cs index a605c2ea77..a058831545 100644 --- a/Content.Shared/Shuttles/BUIStates/DockingPortState.cs +++ b/Content.Shared/Shuttles/BUIStates/DockingPortState.cs @@ -17,4 +17,14 @@ public sealed class DockingPortState public bool Connected => GridDockedWith != null; public NetEntity? GridDockedWith; + + /// + /// The default colour used to shade a dock on a radar screen + /// + public Color Color; + + /// + /// The colour used to shade a dock on a radar screen if it is highlighted (hovered over/selected on docking screen/shown in the main ship radar) + /// + public Color HighlightedColor; } diff --git a/Content.Shared/Silicons/Borgs/Components/ItemBorgModuleComponent.cs b/Content.Shared/Silicons/Borgs/Components/ItemBorgModuleComponent.cs index e86edb3476..3680a4cbde 100644 --- a/Content.Shared/Silicons/Borgs/Components/ItemBorgModuleComponent.cs +++ b/Content.Shared/Silicons/Borgs/Components/ItemBorgModuleComponent.cs @@ -1,6 +1,8 @@ -using Robust.Shared.Containers; +using Content.Shared.Hands.Components; +using Robust.Shared.Containers; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; namespace Content.Shared.Silicons.Borgs.Components; @@ -11,40 +13,40 @@ namespace Content.Shared.Silicons.Borgs.Components; public sealed partial class ItemBorgModuleComponent : Component { /// - /// The items that are provided. + /// The hands that are provided. /// [DataField(required: true)] - public List Items = new(); + public List Hands = new(); /// - /// The entities from that were spawned. + /// The items stored within the hands. Null until the first time items are stored. /// - [DataField("providedItems")] - public SortedDictionary ProvidedItems = new(); + [DataField] + public Dictionary? StoredItems; /// - /// A counter that ensures a unique + /// An ID for the container where items are stored when not in use. /// - [DataField("handCounter")] - public int HandCounter; - - /// - /// Whether or not the items have been created and stored in - /// - [DataField("itemsCrated")] - public bool ItemsCreated; - - /// - /// A container where provided items are stored when not being used. - /// This is helpful as it means that items retain state. - /// - [ViewVariables] - public Container ProvidedContainer = default!; - - /// - /// An ID for the container where provided items are stored when not used. - /// - [DataField("providedContainerId")] - public string ProvidedContainerId = "provided_container"; + [DataField] + public string HoldingContainer = "holding_container"; } +[DataDefinition, Serializable, NetSerializable] +public partial record struct BorgHand +{ + [DataField] + public EntProtoId? Item; + + [DataField] + public Hand Hand = new(); + + [DataField] + public bool ForceRemovable = false; + + public BorgHand(EntProtoId? item, Hand hand, bool forceRemovable = false) + { + Item = item; + Hand = hand; + ForceRemovable = forceRemovable; + } +} diff --git a/Content.Shared/Slippery/SlipperySystem.cs b/Content.Shared/Slippery/SlipperySystem.cs index f844a19acc..cdd6ae5a71 100644 --- a/Content.Shared/Slippery/SlipperySystem.cs +++ b/Content.Shared/Slippery/SlipperySystem.cs @@ -186,7 +186,8 @@ public sealed class SlipperySystem : EntitySystem _audio.PlayPredicted(component.SlipSound, other, other); } - _stun.TryKnockdown(other, component.SlipData.KnockdownTime, true, force: true); + // Slippery is so tied to knockdown that we really just need to force it here. + _stun.TryKnockdown(other, component.SlipData.KnockdownTime, force: true); _adminLogger.Add(LogType.Slip, LogImpact.Low, $"{ToPrettyString(other):mob} slipped on collision with {ToPrettyString(uid):entity}"); } diff --git a/Content.Shared/Species/Systems/ReformSystem.cs b/Content.Shared/Species/Systems/ReformSystem.cs index 943432522d..285d36840f 100644 --- a/Content.Shared/Species/Systems/ReformSystem.cs +++ b/Content.Shared/Species/Systems/ReformSystem.cs @@ -90,7 +90,7 @@ public sealed partial class ReformSystem : EntitySystem // Spawn a new entity // This is, to an extent, taken from polymorph. I don't use polymorph for various reasons- most notably that this is permanent. - var child = Spawn(comp.ReformPrototype, Transform(uid).Coordinates); + var child = SpawnNextToOrDrop(comp.ReformPrototype, uid); // This transfers the mind to the new entity if (_mindSystem.TryGetMind(uid, out var mindId, out var mind)) @@ -106,7 +106,7 @@ public sealed partial class ReformSystem : EntitySystem } public sealed partial class ReformEvent : InstantActionEvent { } - + [Serializable, NetSerializable] public sealed partial class ReformDoAfterEvent : SimpleDoAfterEvent { } } diff --git a/Content.Shared/Stacks/SharedStackSystem.cs b/Content.Shared/Stacks/SharedStackSystem.cs index 912089379a..60b93a8da8 100644 --- a/Content.Shared/Stacks/SharedStackSystem.cs +++ b/Content.Shared/Stacks/SharedStackSystem.cs @@ -352,10 +352,6 @@ namespace Content.Shared.Stacks private void OnStackStarted(EntityUid uid, StackComponent component, ComponentStartup args) { - // on client, lingering stacks that start at 0 need to be darkened - // on server this does nothing - SetCount(uid, component.Count, component); - if (!TryComp(uid, out AppearanceComponent? appearance)) return; @@ -366,7 +362,7 @@ namespace Content.Shared.Stacks private void OnStackGetState(EntityUid uid, StackComponent component, ref ComponentGetState args) { - args.State = new StackComponentState(component.Count, component.MaxCountOverride, component.Lingering); + args.State = new StackComponentState(component.Count, component.MaxCountOverride); } private void OnStackHandleState(EntityUid uid, StackComponent component, ref ComponentHandleState args) @@ -375,7 +371,6 @@ namespace Content.Shared.Stacks return; component.MaxCountOverride = cast.MaxCount; - component.Lingering = cast.Lingering; // This will change the count and call events. SetCount(uid, cast.Count, component); } @@ -428,7 +423,7 @@ namespace Content.Shared.Stacks return; // We haven't eaten the whole stack yet or are unable to eat it completely. - if (eaten.Comp.Count > 0 || eaten.Comp.Lingering) + if (eaten.Comp.Count > 0) { args.Refresh = true; return; diff --git a/Content.Shared/Stacks/StackComponent.cs b/Content.Shared/Stacks/StackComponent.cs index 356b888606..453c8a737d 100644 --- a/Content.Shared/Stacks/StackComponent.cs +++ b/Content.Shared/Stacks/StackComponent.cs @@ -34,13 +34,6 @@ namespace Content.Shared.Stacks [ViewVariables(VVAccess.ReadOnly)] public bool Unlimited { get; set; } - /// - /// Lingering stacks will remain present even when there are no items. - /// Instead, they will become transparent. - /// - [DataField("lingering"), ViewVariables(VVAccess.ReadWrite)] - public bool Lingering; - [DataField("throwIndividually"), ViewVariables(VVAccess.ReadWrite)] public bool ThrowIndividually { get; set; } = false; @@ -93,13 +86,10 @@ namespace Content.Shared.Stacks public int Count { get; } public int? MaxCount { get; } - public bool Lingering; - - public StackComponentState(int count, int? maxCount, bool lingering) + public StackComponentState(int count, int? maxCount) { Count = count; MaxCount = maxCount; - Lingering = lingering; } } diff --git a/Content.Shared/StatusEffectNew/StatusEffectSystem.API.cs b/Content.Shared/StatusEffectNew/StatusEffectSystem.API.cs index 2144b5a0c1..56636c9601 100644 --- a/Content.Shared/StatusEffectNew/StatusEffectSystem.API.cs +++ b/Content.Shared/StatusEffectNew/StatusEffectSystem.API.cs @@ -69,7 +69,7 @@ public sealed partial class StatusEffectsSystem if (!TryGetStatusEffect(target, effectProto, out statusEffect)) return TryAddStatusEffect(target, effectProto, out statusEffect, duration); - SetStatusEffectTime(statusEffect.Value, duration); + SetStatusEffectEndTime(statusEffect.Value, duration); return true; } @@ -291,7 +291,7 @@ public sealed partial class StatusEffectsSystem var meta = MetaData(effect); if (meta.EntityPrototype is not null && meta.EntityPrototype == effectProto) { - SetStatusEffectTime(effect, time); + SetStatusEffectEndTime(effect, time); return true; } } diff --git a/Content.Shared/StatusEffectNew/StatusEffectsSystem.cs b/Content.Shared/StatusEffectNew/StatusEffectsSystem.cs index 1ffb74570a..b385a12fb8 100644 --- a/Content.Shared/StatusEffectNew/StatusEffectsSystem.cs +++ b/Content.Shared/StatusEffectNew/StatusEffectsSystem.cs @@ -125,47 +125,6 @@ public sealed partial class StatusEffectsSystem : EntitySystem PredictedQueueDel(ent.Owner); } - private void SetStatusEffectTime(EntityUid effect, TimeSpan? duration) - { - if (!_effectQuery.TryComp(effect, out var effectComp)) - return; - - if (duration is null) - { - if(effectComp.EndEffectTime is null) - return; - - effectComp.EndEffectTime = null; - } - else - effectComp.EndEffectTime = _timing.CurTime + duration; - - Dirty(effect, effectComp); - } - - private void UpdateStatusEffectTime(EntityUid effect, TimeSpan? duration) - { - if (!_effectQuery.TryComp(effect, out var effectComp)) - return; - - // It's already infinitely long - if (effectComp.EndEffectTime is null) - return; - - if (duration is null) - effectComp.EndEffectTime = null; - else - { - var newEndTime = _timing.CurTime + duration; - if (effectComp.EndEffectTime >= newEndTime) - return; - - effectComp.EndEffectTime = newEndTime; - } - - Dirty(effect, effectComp); - } - public bool CanAddStatusEffect(EntityUid uid, EntProtoId effectProto) { if (!_proto.TryIndex(effectProto, out var effectProtoData)) @@ -227,13 +186,39 @@ public sealed partial class StatusEffectsSystem : EntitySystem return true; } - private void AddStatusEffectTime(EntityUid effect, TimeSpan delta) + private void UpdateStatusEffectTime(Entity effect, TimeSpan? duration) { - if (!_effectQuery.TryComp(effect, out var effectComp)) + if (!_effectQuery.Resolve(effect, ref effect.Comp)) return; - // If we don't have an end time set, we want to just make the status effect end in delta time from now. - SetStatusEffectEndTime((effect, effectComp), (effectComp.EndEffectTime ?? _timing.CurTime) + delta); + // It's already infinitely long + if (effect.Comp.EndEffectTime is null) + return; + + TimeSpan? newEndTime = null; + + if (duration is not null) + { + // Don't update time to a smaller timespan... + newEndTime = _timing.CurTime + duration; + if (effect.Comp.EndEffectTime >= newEndTime) + return; + } + + SetStatusEffectEndTime(effect, newEndTime); + } + + private void AddStatusEffectTime(Entity effect, TimeSpan delta) + { + if (!_effectQuery.Resolve(effect, ref effect.Comp)) + return; + + // It's already infinitely long can't add or subtract from infinity... + if (effect.Comp.EndEffectTime is null) + return; + + // Add to the current end effect time, if we're here we should have one set already, and if it's null it's probably infinite. + SetStatusEffectEndTime((effect, effect.Comp), effect.Comp.EndEffectTime.Value + delta); } private void SetStatusEffectEndTime(Entity ent, TimeSpan? endTime) diff --git a/Content.Shared/Stunnable/CrawlerComponent.cs b/Content.Shared/Stunnable/CrawlerComponent.cs new file mode 100644 index 0000000000..d969117344 --- /dev/null +++ b/Content.Shared/Stunnable/CrawlerComponent.cs @@ -0,0 +1,40 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Stunnable; + +/// +/// This is used to denote that an entity can crawl. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedStunSystem))] +public sealed partial class CrawlerComponent : Component +{ + /// + /// Default time we will be knocked down for. + /// + [DataField, AutoNetworkedField] + public TimeSpan DefaultKnockedDuration { get; set; } = TimeSpan.FromSeconds(0.5); + + /// + /// Minimum damage taken to extend our knockdown timer by the default time. + /// + [DataField, AutoNetworkedField] + public float KnockdownDamageThreshold = 5f; + + /// + /// Time it takes us to stand up + /// + [DataField, AutoNetworkedField] + public TimeSpan StandTime = TimeSpan.FromSeconds(2); + + /// + /// Base modifier to the maximum movement speed of a knocked down mover. + /// + [DataField, AutoNetworkedField] + public float SpeedModifier = 0.4f; + + /// + /// Friction modifier applied to an entity in the downed state. + /// + [DataField, AutoNetworkedField] + public float FrictionModifier = 1f; +} diff --git a/Content.Shared/Stunnable/KnockdownStatusEffectComponent.cs b/Content.Shared/Stunnable/KnockdownStatusEffectComponent.cs index 79b2fb695b..b4805511b2 100644 --- a/Content.Shared/Stunnable/KnockdownStatusEffectComponent.cs +++ b/Content.Shared/Stunnable/KnockdownStatusEffectComponent.cs @@ -6,4 +6,22 @@ namespace Content.Shared.Stunnable; /// Knockdown as a status effect. /// [RegisterComponent, NetworkedComponent, Access(typeof(SharedStunSystem))] -public sealed partial class KnockdownStatusEffectComponent : Component; +public sealed partial class KnockdownStatusEffectComponent : Component +{ + /// + /// Should this knockdown only affect crawlers? + /// + /// + /// If your status effect doesn't come paired with + /// Or if your status effect doesn't whitelist itself to only those with + /// Then you need to set this to true. + /// + [DataField] + public bool Crawl; + + /// + /// Should we drop items when we fall? + /// + [DataField] + public bool Drop = true; +} diff --git a/Content.Shared/Stunnable/SharedStunSystem.Knockdown.cs b/Content.Shared/Stunnable/SharedStunSystem.Knockdown.cs index 19441bade3..c8cbae2f68 100644 --- a/Content.Shared/Stunnable/SharedStunSystem.Knockdown.cs +++ b/Content.Shared/Stunnable/SharedStunSystem.Knockdown.cs @@ -25,13 +25,7 @@ namespace Content.Shared.Stunnable; /// public abstract partial class SharedStunSystem { - // TODO: Both of these constants need to be moved to a component somewhere, and need to be tweaked for balance... - // We don't always have standing state available when these are called so it can't go there - // Maybe I can pass the values to KnockedDownComponent from Standing state on Component init? - // Default knockdown timer - public static readonly TimeSpan DefaultKnockedDuration = TimeSpan.FromSeconds(0.5f); - // Minimum damage taken to refresh our knockdown timer to the default duration - public static readonly float KnockdownDamageThreshold = 5f; + private EntityQuery _crawlerQuery; [Dependency] private readonly EntityLookupSystem _entityLookup = default!; [Dependency] private readonly SharedHandsSystem _hands = default!; @@ -42,6 +36,8 @@ public abstract partial class SharedStunSystem private void InitializeKnockdown() { + _crawlerQuery = GetEntityQuery(); + SubscribeLocalEvent(OnRejuvenate); // Startup and Shutdown @@ -76,6 +72,7 @@ public abstract partial class SharedStunSystem while (query.MoveNext(out var uid, out var knockedDown)) { + // If it's null then we don't want to stand up if (!knockedDown.AutoStand || knockedDown.DoAfterId.HasValue || knockedDown.NextUpdate > GameTiming.CurTime) continue; @@ -152,7 +149,7 @@ public abstract partial class SharedStunSystem /// Entity who's knockdown time we're updating. /// The time we're updating with. /// Whether we're resetting the timer or adding to the current timer. - public void UpdateKnockdownTime(Entity entity, TimeSpan time, bool refresh = true) + public void UpdateKnockdownTime(Entity entity, TimeSpan time, bool refresh = true) { if (refresh) RefreshKnockdownTime(entity, time); @@ -169,6 +166,7 @@ public abstract partial class SharedStunSystem { entity.Comp.NextUpdate = time; DirtyField(entity, entity.Comp, nameof(KnockedDownComponent.NextUpdate)); + Alerts.ShowAlert(entity, KnockdownAlert, null, (GameTiming.CurTime, entity.Comp.NextUpdate)); } /// @@ -177,11 +175,14 @@ public abstract partial class SharedStunSystem /// /// Entity whose timer we're updating /// The time we want them to be knocked down for. - public void RefreshKnockdownTime(Entity entity, TimeSpan time) + public void RefreshKnockdownTime(Entity entity, TimeSpan time) { + if (!Resolve(entity, ref entity.Comp, false)) + return; + var knockedTime = GameTiming.CurTime + time; if (entity.Comp.NextUpdate < knockedTime) - SetKnockdownTime(entity, knockedTime); + SetKnockdownTime((entity, entity.Comp), knockedTime); } /// @@ -189,35 +190,20 @@ public abstract partial class SharedStunSystem /// /// Entity whose timer we're updating /// The time we want to add to their knocked down timer. - public void AddKnockdownTime(Entity entity, TimeSpan time) + public void AddKnockdownTime(Entity entity, TimeSpan time) { + if (!Resolve(entity, ref entity.Comp, false)) + return; + if (entity.Comp.NextUpdate < GameTiming.CurTime) { - SetKnockdownTime(entity, GameTiming.CurTime + time); + SetKnockdownTime((entity, entity.Comp), GameTiming.CurTime + time); return; } entity.Comp.NextUpdate += time; DirtyField(entity, entity.Comp, nameof(KnockedDownComponent.NextUpdate)); - } - - /// - /// Checks if an entity is able to stand, returns true if it can, returns false if it cannot. - /// - /// Entity we're checking - /// Returns whether the entity is able to stand - public bool CanStand(Entity entity) - { - if (entity.Comp.NextUpdate > GameTiming.CurTime) - return false; - - if (!Blocker.CanMove(entity)) - return false; - - var ev = new StandUpAttemptEvent(); - RaiseLocalEvent(entity, ref ev); - - return !ev.Cancelled; + Alerts.ShowAlert(entity, KnockdownAlert, null, (GameTiming.CurTime, entity.Comp.NextUpdate)); } #endregion @@ -232,17 +218,30 @@ public abstract partial class SharedStunSystem if (playerSession.AttachedEntity is not { Valid: true } playerEnt || !Exists(playerEnt)) return; - if (!TryComp(playerEnt, out var component)) + ToggleKnockdown(playerEnt); + } + + /// + /// Handles an entity trying to make itself fall down. + /// + /// Entity who is trying to fall down + private void ToggleKnockdown(Entity entity) + { + // We resolve here instead of using TryCrawling to be extra sure someone without crawler can't stand up early. + if (!Resolve(entity, ref entity.Comp1, false)) + return; + + if (!Resolve(entity, ref entity.Comp2, false)) { - TryKnockdown(playerEnt, DefaultKnockedDuration, true, false, false); // TODO: Unhardcode these numbers + TryKnockdown(entity.Owner, entity.Comp1.DefaultKnockedDuration, true, false, false); return; } - var stand = !component.DoAfterId.HasValue; - SetAutoStand(playerEnt, stand); + var stand = !entity.Comp2.DoAfterId.HasValue; + SetAutoStand((entity, entity.Comp2), stand); - if (!stand || !_standing.TryStandUp(playerEnt)) - CancelKnockdownDoAfter((playerEnt, component)); + if (!stand || !_standing.TryStandUp((entity, entity.Comp2))) + CancelKnockdownDoAfter((entity, entity.Comp2)); } /// @@ -253,10 +252,7 @@ public abstract partial class SharedStunSystem /// Returns whether the entity is able to stand public bool TryStand(Entity entity) { - if (entity.Comp.NextUpdate > GameTiming.CurTime) - return false; - - if (!Blocker.CanMove(entity)) + if (!KnockdownOver(entity)) return false; var ev = new StandUpAttemptEvent(entity.Comp.AutoStand); @@ -283,16 +279,22 @@ public abstract partial class SharedStunSystem #endregion - #region Knockdown Extenders + #region Crawling - private void OnDamaged(Entity entity, ref DamageChangedEvent args) + private void OnDamaged(Entity entity, ref DamageChangedEvent args) { // We only want to extend our knockdown timer if it would've prevented us from standing up if (!args.InterruptsDoAfters || !args.DamageIncreased || args.DamageDelta == null || GameTiming.ApplyingState) return; - if (args.DamageDelta.GetTotal() >= KnockdownDamageThreshold) // TODO: Unhardcode this - SetKnockdownTime(entity, GameTiming.CurTime + DefaultKnockedDuration); + if (args.DamageDelta.GetTotal() >= entity.Comp.KnockdownDamageThreshold) + RefreshKnockdownTime(entity.Owner, entity.Comp.DefaultKnockedDuration); + } + + private void OnKnockdownRefresh(Entity entity, ref KnockedDownRefreshEvent args) + { + args.FrictionModifier *= entity.Comp.FrictionModifier; + args.SpeedModifier *= entity.Comp.SpeedModifier; } #endregion diff --git a/Content.Shared/Stunnable/SharedStunSystem.cs b/Content.Shared/Stunnable/SharedStunSystem.cs index f27bae9ee4..0eb027958f 100644 --- a/Content.Shared/Stunnable/SharedStunSystem.cs +++ b/Content.Shared/Stunnable/SharedStunSystem.cs @@ -26,7 +26,6 @@ namespace Content.Shared.Stunnable; public abstract partial class SharedStunSystem : EntitySystem { public static readonly EntProtoId StunId = "StatusEffectStunned"; - public static readonly EntProtoId KnockdownId = "StatusEffectKnockdown"; [Dependency] protected readonly IGameTiming GameTiming = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; @@ -62,10 +61,11 @@ public abstract partial class SharedStunSystem : EntitySystem SubscribeLocalEvent(OnMobStateChanged); // New Status Effect subscriptions - SubscribeLocalEvent(OnStunEffectApplied); + SubscribeLocalEvent(OnStunStatusApplied); SubscribeLocalEvent(OnStunStatusRemoved); SubscribeLocalEvent>(OnStunEndAttempt); + SubscribeLocalEvent(OnKnockdownStatusApplied); SubscribeLocalEvent>(OnStandUpAttempt); // Stun Appearance Data @@ -124,7 +124,7 @@ public abstract partial class SharedStunSystem : EntitySystem return; TryUpdateStunDuration(args.OtherEntity, ent.Comp.Duration); - TryKnockdown(args.OtherEntity, ent.Comp.Duration, true, force: true); + TryKnockdown(args.OtherEntity, ent.Comp.Duration, force: true); } // TODO STUN: Make events for different things. (Getting modifiers, attempt events, informative events...) @@ -157,29 +157,54 @@ public abstract partial class SharedStunSystem : EntitySystem _adminLogger.Add(LogType.Stamina, LogImpact.Medium, $"{ToPrettyString(uid):user} stunned for {timeForLogs} seconds"); } - public bool TryAddKnockdownDuration(EntityUid uid, TimeSpan duration) + /// + /// Tries to knock an entity to the ground, but will fail if they aren't able to crawl. + /// Useful if you don't want to paralyze an entity that can't crawl, but still want to knockdown + /// entities that can. + /// + /// Entity we're trying to knockdown. + /// Time of the knockdown. + /// Do we refresh their timer, or add to it if one exists? + /// Whether we should automatically stand when knockdown ends. + /// Should we drop what we're holding? + /// Should we force crawling? Even if something tried to block it? + /// Returns true if the entity is able to crawl, and was able to be knocked down. + public bool TryCrawling(Entity entity, + TimeSpan? time, + bool refresh = true, + bool autoStand = true, + bool drop = true, + bool force = false) { - if (!_status.TryAddStatusEffectDuration(uid, KnockdownId, duration)) + if (!Resolve(entity, ref entity.Comp, false)) return false; - TryKnockdown(uid, duration, true, force: true); - - return true; - + return TryKnockdown(entity, time, refresh, autoStand, drop, force); } - public bool TryUpdateKnockdownDuration(EntityUid uid, TimeSpan? duration) + /// + /// An overload of TryCrawling which uses the default crawling time from the CrawlerComponent as its timespan. + public bool TryCrawling(Entity entity, + bool refresh = true, + bool autoStand = true, + bool drop = true, + bool force = false) { - if (!_status.TryUpdateStatusEffectDuration(uid, KnockdownId, duration)) + if (!Resolve(entity, ref entity.Comp, false)) return false; - return TryKnockdown(uid, duration, true, force: true); + return TryKnockdown(entity, entity.Comp.DefaultKnockedDuration, refresh, autoStand, drop, force); } /// - /// Knocks down the entity, making it fall to the ground. + /// Checks if we can knock down an entity to the ground... /// - public bool TryKnockdown(Entity entity, TimeSpan? time, bool refresh, bool autoStand = true, bool drop = true, bool force = false) + /// The entity we're trying to knock down + /// The time of the knockdown + /// Whether we want to automatically stand when knockdown ends. + /// Whether we should drop items. + /// Should we force the status effect? + public bool CanKnockdown(Entity entity, ref TimeSpan? time, ref bool autoStand, ref bool drop, bool force = false) { if (time <= TimeSpan.Zero) return false; @@ -188,30 +213,53 @@ public abstract partial class SharedStunSystem : EntitySystem if (!Resolve(entity, ref entity.Comp, false)) return false; - if (!force) - { - var evAttempt = new KnockDownAttemptEvent(autoStand, drop); - RaiseLocalEvent(entity, ref evAttempt); + var evAttempt = new KnockDownAttemptEvent(autoStand, drop, time); + RaiseLocalEvent(entity, ref evAttempt); - if (evAttempt.Cancelled) - return false; + autoStand = evAttempt.AutoStand; + drop = evAttempt.Drop; - autoStand = evAttempt.AutoStand; - drop = evAttempt.Drop; - } + return force || !evAttempt.Cancelled; + } - Knockdown(entity!, time, refresh, autoStand, drop); + /// + /// Knocks down the entity, making it fall to the ground. + /// + /// The entity we're trying to knock down + /// The time of the knockdown + /// Whether we should refresh a running timer or add to it, if one exists. + /// Whether we want to automatically stand when knockdown ends. + /// Whether we should drop items. + /// Should we force the status effect? + public bool TryKnockdown(Entity entity, TimeSpan? time, bool refresh = true, bool autoStand = true, bool drop = true, bool force = false) + { + if (!CanKnockdown(entity.Owner, ref time, ref autoStand, ref drop, force)) + return false; + // If the entity can't crawl they also need to be stunned, and therefore we should be using paralysis status effect. + // Also time shouldn't be null if we're and trying to add time but, we check just in case anyways. + if (!Resolve(entity, ref entity.Comp, false)) + return refresh || time == null ? TryUpdateParalyzeDuration(entity, time) : TryAddParalyzeDuration(entity, time.Value); + + Knockdown(entity, time, refresh, autoStand, drop); return true; } - private void Knockdown(Entity entity, TimeSpan? time, bool refresh, bool autoStand, bool drop) + private void Crawl(Entity entity, TimeSpan? time, bool refresh, bool autoStand, bool drop) + { + if (!Resolve(entity, ref entity.Comp, false)) + return; + + Knockdown(entity, time, refresh, autoStand, drop); + } + + private void Knockdown(EntityUid uid, TimeSpan? time, bool refresh, bool autoStand, bool drop) { // Initialize our component with the relevant data we need if we don't have it - if (EnsureComp(entity, out var component)) + if (EnsureComp(uid, out var component)) { - RefreshKnockedMovement((entity, component)); - CancelKnockdownDoAfter((entity, component)); + RefreshKnockedMovement((uid, component)); + CancelKnockdownDoAfter((uid, component)); } else { @@ -219,41 +267,50 @@ public abstract partial class SharedStunSystem : EntitySystem if (drop) { var ev = new DropHandItemsEvent(); - RaiseLocalEvent(entity, ev); + RaiseLocalEvent(uid, ref ev); } // Only update Autostand value if it's our first time being knocked down... - SetAutoStand((entity, component), autoStand); + SetAutoStand((uid, component), autoStand); } - var knockedEv = new KnockedDownEvent(time); - RaiseLocalEvent(entity, ref knockedEv); + var knockedEv = new KnockedDownEvent(); + RaiseLocalEvent(uid, ref knockedEv); if (time != null) { - UpdateKnockdownTime((entity, component), time.Value, refresh); - _adminLogger.Add(LogType.Stamina, LogImpact.Medium, $"{ToPrettyString(entity):user} knocked down for {time.Value.Seconds} seconds"); + UpdateKnockdownTime((uid, component), time.Value, refresh); + _adminLogger.Add(LogType.Stamina, LogImpact.Medium, $"{ToPrettyString(uid):user} was knocked down for {time.Value.Seconds} seconds"); } else - _adminLogger.Add(LogType.Stamina, LogImpact.Medium, $"{ToPrettyString(entity):user} knocked down for an indefinite amount of time"); - - Alerts.ShowAlert(entity, KnockdownAlert, null, (GameTiming.CurTime, component.NextUpdate)); + { + Alerts.ShowAlert(uid, KnockdownAlert); + _adminLogger.Add(LogType.Stamina, LogImpact.Medium, $"{ToPrettyString(uid):user} was knocked down"); + } } public bool TryAddParalyzeDuration(EntityUid uid, TimeSpan duration) { - var knockdown = TryAddKnockdownDuration(uid, duration); - var stunned = TryAddStunDuration(uid, duration); + if (!_status.TryAddStatusEffectDuration(uid, StunId, duration)) + return false; - return knockdown || stunned; + // We can't exit knockdown when we're stunned, so this prevents knockdown lasting longer than the stun. + Knockdown(uid, null, false, true, true); + OnStunnedSuccessfully(uid, duration); + + return true; } public bool TryUpdateParalyzeDuration(EntityUid uid, TimeSpan? duration) { - var knockdown = TryUpdateKnockdownDuration(uid, duration); - var stunned = TryUpdateStunDuration(uid, duration); + if (!_status.TryUpdateStatusEffectDuration(uid, StunId, duration)) + return false; - return knockdown || stunned; + // We can't exit knockdown when we're stunned, so this prevents knockdown lasting longer than the stun. + Knockdown(uid, null, false, true, true); + OnStunnedSuccessfully(uid, duration); + + return true; } public bool TryUnstun(Entity entity) @@ -267,7 +324,7 @@ public abstract partial class SharedStunSystem : EntitySystem return !ev.Cancelled && RemComp(entity); } - private void OnStunEffectApplied(Entity entity, ref StatusEffectAppliedEvent args) + private void OnStunStatusApplied(Entity entity, ref StatusEffectAppliedEvent args) { if (GameTiming.ApplyingState) return; @@ -290,6 +347,18 @@ public abstract partial class SharedStunSystem : EntitySystem args.Args = ev; } + private void OnKnockdownStatusApplied(Entity entity, ref StatusEffectAppliedEvent args) + { + if (GameTiming.ApplyingState) + return; + + // If you make something that shouldn't crawl, crawl, that's your own fault. + if (entity.Comp.Crawl) + Crawl(args.Target, null, true, true, drop: entity.Comp.Drop); + else + Knockdown(args.Target, null, true, true, drop: entity.Comp.Drop); + } + private void OnStandUpAttempt(Entity entity, ref StatusEffectRelayedEvent args) { if (args.Args.Cancelled) diff --git a/Content.Shared/Stunnable/StunnableEvents.cs b/Content.Shared/Stunnable/StunnableEvents.cs index f4a0191c92..f0c08f6136 100644 --- a/Content.Shared/Stunnable/StunnableEvents.cs +++ b/Content.Shared/Stunnable/StunnableEvents.cs @@ -26,7 +26,7 @@ public record struct StunEndAttemptEvent(bool Cancelled); /// knocked down arguments. /// [ByRefEvent] -public record struct KnockDownAttemptEvent(bool AutoStand, bool Drop) +public record struct KnockDownAttemptEvent(bool AutoStand, bool Drop, TimeSpan? Time) { public bool Cancelled; } @@ -35,7 +35,7 @@ public record struct KnockDownAttemptEvent(bool AutoStand, bool Drop) /// Raised directed on an entity when it is knocked down. /// [ByRefEvent] -public record struct KnockedDownEvent(TimeSpan? Time); +public record struct KnockedDownEvent; /// /// Raised on an entity that needs to refresh its knockdown modifiers diff --git a/Content.Shared/Trigger/Components/Conditions/RandomChanceTriggerConditionComponent.cs b/Content.Shared/Trigger/Components/Conditions/RandomChanceTriggerConditionComponent.cs new file mode 100644 index 0000000000..2612f16007 --- /dev/null +++ b/Content.Shared/Trigger/Components/Conditions/RandomChanceTriggerConditionComponent.cs @@ -0,0 +1,16 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Conditions; + +/// +/// This condition will cancel triggers based on random chance. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class RandomChanceTriggerConditionComponent : BaseTriggerConditionComponent +{ + /// + /// Chance for the trigger to succeed. + /// + [DataField, AutoNetworkedField] + public float SuccessChance = .9f; +} diff --git a/Content.Shared/Trigger/Components/Effects/ExtinguishOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/ExtinguishOnTriggerComponent.cs new file mode 100644 index 0000000000..43208a9971 --- /dev/null +++ b/Content.Shared/Trigger/Components/Effects/ExtinguishOnTriggerComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Effects; + +/// +/// This trigger removes all the fire stacks on a target with . +/// If TargetUser is true, the entity that caused this trigger will be extinguished instead. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class ExtinguishOnTriggerComponent : BaseXOnTriggerComponent; diff --git a/Content.Shared/Trigger/Components/Effects/FireStackOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/FireStackOnTriggerComponent.cs new file mode 100644 index 0000000000..cde5075e9b --- /dev/null +++ b/Content.Shared/Trigger/Components/Effects/FireStackOnTriggerComponent.cs @@ -0,0 +1,26 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Effects; + +/// +/// Adjusts fire stacks on trigger, optionally setting them on fire as well. +/// Requires to ignite the target. +/// If TargetUser is true they will have their firestacks adjusted instead. +/// +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class FireStackOnTriggerComponent : BaseXOnTriggerComponent +{ + /// + /// How many fire stacks to add or remove. + /// + [DataField, AutoNetworkedField] + public float FireStacks; + + /// + /// If true, the target will be set on fire if it isn't already. + /// If false does nothing. + /// + [DataField, AutoNetworkedField] + public bool DoIgnite = true; +} diff --git a/Content.Shared/Trigger/Components/Effects/IgniteOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/IgniteOnTriggerComponent.cs index 36273ef1b2..3e3db526e4 100644 --- a/Content.Shared/Trigger/Components/Effects/IgniteOnTriggerComponent.cs +++ b/Content.Shared/Trigger/Components/Effects/IgniteOnTriggerComponent.cs @@ -8,6 +8,7 @@ namespace Content.Shared.Trigger.Components.Effects; /// Requires along with triggering components. /// The if TargetUser is true they will be ignited instead (they need IgnitionSourceComponent as well). /// +/// [RegisterComponent, NetworkedComponent] [AutoGenerateComponentState, AutoGenerateComponentPause] public sealed partial class IgniteOnTriggerComponent : BaseXOnTriggerComponent diff --git a/Content.Shared/Trigger/Components/Effects/LockOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/LockOnTriggerComponent.cs new file mode 100644 index 0000000000..38eea0a461 --- /dev/null +++ b/Content.Shared/Trigger/Components/Effects/LockOnTriggerComponent.cs @@ -0,0 +1,19 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; + +namespace Content.Shared.Trigger.Components.Effects; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class LockOnTriggerComponent : BaseXOnTriggerComponent +{ + [DataField, AutoNetworkedField] + public LockAction LockOnTrigger = LockAction.Toggle; +} + +[Serializable, NetSerializable] +public enum LockAction +{ + Lock = 0, + Unlock = 1, + Toggle = 2, +} diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnInteractHandComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnInteractHandComponent.cs new file mode 100644 index 0000000000..ca7e96be74 --- /dev/null +++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnInteractHandComponent.cs @@ -0,0 +1,11 @@ +using Content.Shared.Interaction; +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Triggers; + +/// +/// Trigger on , aka clicking on an entity with an empty hand. +/// User is the player with the hand doing the clicking. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class TriggerOnInteractHandComponent : BaseTriggerOnXComponent; diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnRoundEndComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnRoundEndComponent.cs new file mode 100644 index 0000000000..29a9643ca7 --- /dev/null +++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnRoundEndComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Triggers; + +/// +/// Triggers the entity when the round ends, i.e. the scoreboard appears and post-round begins. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class TriggerOnRoundEndComponent : BaseTriggerOnXComponent; diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnVoiceComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnVoiceComponent.cs index a36992d7da..1fc3c1b966 100644 --- a/Content.Shared/Trigger/Components/Triggers/TriggerOnVoiceComponent.cs +++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnVoiceComponent.cs @@ -44,4 +44,52 @@ public sealed partial class TriggerOnVoiceComponent : BaseTriggerOnXComponent /// [DataField, AutoNetworkedField] public int MaxLength = 50; + + /// + /// When examining the item, should it show information about what word is recorded? + /// + [DataField, AutoNetworkedField] + public bool ShowExamine = true; + + /// + /// Should there be verbs that allow re-recording of the trigger word? + /// + [DataField, AutoNetworkedField] + public bool ShowVerbs = true; + + /// + /// The verb text that is shown when you can start recording a message. + /// + [DataField] + public LocId StartRecordingVerb = "trigger-on-voice-record"; + + /// + /// The verb text that is shown when you can stop recording a message. + /// + [DataField] + public LocId StopRecordingVerb = "trigger-on-voice-stop"; + + /// + /// Tooltip that appears when hovering over the stop or start recording verbs. + /// + [DataField] + public LocId? RecordingVerbMessage; + + /// + /// The verb text that is shown when you can clear a recording. + /// + [DataField] + public LocId ClearRecordingVerb = "trigger-on-voice-clear"; + + /// + /// The loc string that is shown when inspecting an uninitialized voice trigger. + /// + [DataField] + public LocId? InspectUninitializedLoc = "trigger-on-voice-uninitialized"; + + /// + /// The loc string to use when inspecting voice trigger. Will also include the triggering phrase + /// + [DataField] + public LocId? InspectInitializedLoc = "trigger-on-voice-examine"; } diff --git a/Content.Shared/Trigger/Systems/LockOnTriggerSystem.cs b/Content.Shared/Trigger/Systems/LockOnTriggerSystem.cs new file mode 100644 index 0000000000..8726ede899 --- /dev/null +++ b/Content.Shared/Trigger/Systems/LockOnTriggerSystem.cs @@ -0,0 +1,35 @@ +using Content.Shared.Lock; +using Content.Shared.Trigger.Components.Effects; + +namespace Content.Shared.Trigger.Systems; + +public sealed class LockOnTriggerSystem : EntitySystem +{ + [Dependency] private readonly LockSystem _lock = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnTrigger); + } + + private void OnTrigger(Entity ent, ref TriggerEvent args) + { + if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) + return; + + switch (ent.Comp.LockOnTrigger) + { + case LockAction.Lock: + _lock.Lock(ent.Owner, args.User); + break; + case LockAction.Unlock: + _lock.Unlock(ent, args.User); + break; + case LockAction.Toggle: + _lock.ToggleLock(ent, args.User); + break; + } + } +} diff --git a/Content.Shared/Trigger/Systems/TriggerOnRoundEndSystem.cs b/Content.Shared/Trigger/Systems/TriggerOnRoundEndSystem.cs new file mode 100644 index 0000000000..c18fb08f3f --- /dev/null +++ b/Content.Shared/Trigger/Systems/TriggerOnRoundEndSystem.cs @@ -0,0 +1,31 @@ +using Content.Shared.GameTicking; +using Content.Shared.Trigger.Components.Triggers; + +namespace Content.Shared.Trigger.Systems; + +/// +/// System for creating a trigger when the round ends. +/// +public sealed class TriggerOnRoundEndSystem : EntitySystem +{ + [Dependency] private readonly TriggerSystem _trigger = default!; + + /// + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnRoundEnd); + } + + private void OnRoundEnd(RoundEndMessageEvent args) + { + var triggerQuery = EntityQueryEnumerator(); + + // trigger everything with the component + while (triggerQuery.MoveNext(out var uid, out var comp)) + { + _trigger.Trigger(uid, null, comp.KeyOut); + } + } +} diff --git a/Content.Shared/Trigger/Systems/TriggerSystem.Condition.cs b/Content.Shared/Trigger/Systems/TriggerSystem.Condition.cs index a917f1ad48..2d0756556a 100644 --- a/Content.Shared/Trigger/Systems/TriggerSystem.Condition.cs +++ b/Content.Shared/Trigger/Systems/TriggerSystem.Condition.cs @@ -1,5 +1,7 @@ -using Content.Shared.Trigger.Components.Conditions; +using Content.Shared.Random.Helpers; +using Content.Shared.Trigger.Components.Conditions; using Content.Shared.Verbs; +using Robust.Shared.Random; namespace Content.Shared.Trigger.Systems; @@ -13,6 +15,8 @@ public sealed partial class TriggerSystem SubscribeLocalEvent(OnToggleTriggerAttempt); SubscribeLocalEvent>(OnToggleGetAltVerbs); + + SubscribeLocalEvent(OnRandomChanceTriggerAttempt); } private void OnWhitelistTriggerAttempt(Entity ent, ref AttemptTriggerEvent args) @@ -54,4 +58,23 @@ public sealed partial class TriggerSystem ent.Comp.Enabled = !ent.Comp.Enabled; Dirty(ent); } + + private void OnRandomChanceTriggerAttempt(Entity ent, + ref AttemptTriggerEvent args) + { + if (args.Key == null || ent.Comp.Keys.Contains(args.Key)) + { + // TODO: Replace with RandomPredicted once the engine PR is merged + var hash = new List + { + (int)_timing.CurTick.Value, + GetNetEntity(ent).Id, + args.User == null ? 0 : GetNetEntity(args.User.Value).Id, + }; + var seed = SharedRandomExtensions.HashCodeCombine(hash); + var rand = new System.Random(seed); + + args.Cancelled |= !rand.Prob(ent.Comp.SuccessChance); // When not successful, Cancelled = true + } + } } diff --git a/Content.Shared/Trigger/Systems/TriggerSystem.Interaction.cs b/Content.Shared/Trigger/Systems/TriggerSystem.Interaction.cs index f506909760..035ef4ec91 100644 --- a/Content.Shared/Trigger/Systems/TriggerSystem.Interaction.cs +++ b/Content.Shared/Trigger/Systems/TriggerSystem.Interaction.cs @@ -12,6 +12,7 @@ public sealed partial class TriggerSystem { SubscribeLocalEvent(OnActivate); SubscribeLocalEvent(OnUse); + SubscribeLocalEvent(OnInteractHand); SubscribeLocalEvent(HandleItemToggleOnTrigger); SubscribeLocalEvent(HandleAnchorOnTrigger); @@ -39,6 +40,15 @@ public sealed partial class TriggerSystem args.Handled = true; } + private void OnInteractHand(Entity ent, ref InteractHandEvent args) + { + if (args.Handled) + return; + + Trigger(ent.Owner, args.User, ent.Comp.KeyOut); + args.Handled = true; + } + private void HandleItemToggleOnTrigger(Entity ent, ref TriggerEvent args) { if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) diff --git a/Content.Shared/Trigger/Systems/TriggerSystem.Voice.cs b/Content.Shared/Trigger/Systems/TriggerSystem.Voice.cs index ac67cb7ed2..c374369a7f 100644 --- a/Content.Shared/Trigger/Systems/TriggerSystem.Voice.cs +++ b/Content.Shared/Trigger/Systems/TriggerSystem.Voice.cs @@ -25,15 +25,21 @@ public sealed partial class TriggerSystem RemCompDeferred(ent); } - private void OnVoiceExamine(Entity ent, ref ExaminedEvent args) + private void OnVoiceExamine(EntityUid uid, TriggerOnVoiceComponent component, ExaminedEvent args) { - if (args.IsInDetailsRange) + if (!args.IsInDetailsRange || !component.ShowExamine) + return; + + if (component.InspectUninitializedLoc != null && string.IsNullOrWhiteSpace(component.KeyPhrase)) { - args.PushText(string.IsNullOrWhiteSpace(ent.Comp.KeyPhrase) - ? Loc.GetString("trigger-on-voice-uninitialized") - : Loc.GetString("trigger-on-voice-examine", ("keyphrase", ent.Comp.KeyPhrase))); + args.PushText(Loc.GetString(component.InspectUninitializedLoc)); + } + else if (component.InspectInitializedLoc != null && !string.IsNullOrWhiteSpace(component.KeyPhrase)) + { + args.PushText(Loc.GetString(component.InspectInitializedLoc.Value, ("keyphrase", component.KeyPhrase))); } } + private void OnListen(Entity ent, ref ListenEvent args) { var component = ent.Comp; @@ -71,13 +77,13 @@ public sealed partial class TriggerSystem private void OnVoiceGetAltVerbs(Entity ent, ref GetVerbsEvent args) { - if (!args.CanInteract || !args.CanAccess) + if (!args.CanInteract || !args.CanAccess || !ent.Comp.ShowVerbs) return; var user = args.User; args.Verbs.Add(new AlternativeVerb { - Text = Loc.GetString(ent.Comp.IsRecording ? "trigger-on-voice-stop" : "trigger-on-voice-record"), + Text = Loc.GetString(ent.Comp.IsRecording ? ent.Comp.StopRecordingVerb : ent.Comp.StartRecordingVerb), Act = () => { if (ent.Comp.IsRecording) @@ -93,7 +99,7 @@ public sealed partial class TriggerSystem args.Verbs.Add(new AlternativeVerb { - Text = Loc.GetString("trigger-on-voice-clear"), + Text = Loc.GetString(ent.Comp.ClearRecordingVerb), Act = () => { ClearRecording(ent); diff --git a/Content.Shared/UserInterface/ActivatableUISystem.cs b/Content.Shared/UserInterface/ActivatableUISystem.cs index d2fb70ee76..455a4dbbf9 100644 --- a/Content.Shared/UserInterface/ActivatableUISystem.cs +++ b/Content.Shared/UserInterface/ActivatableUISystem.cs @@ -134,7 +134,7 @@ public sealed partial class ActivatableUISystem : EntitySystem } } - return args.CanInteract || HasComp(args.User) && !component.BlockSpectators; + return (args.CanInteract || HasComp(args.User) && !component.BlockSpectators) && !RaiseCanOpenEventChecks(args.User, uid); } private void OnUseInHand(EntityUid uid, ActivatableUIComponent component, UseInHandEvent args) @@ -267,11 +267,7 @@ public sealed partial class ActivatableUISystem : EntitySystem // If we've gotten this far, fire a cancellable event that indicates someone is about to activate this. // This is so that stuff can require further conditions (like power). - var oae = new ActivatableUIOpenAttemptEvent(user); - var uae = new UserOpenActivatableUIAttemptEvent(user, uiEntity); - RaiseLocalEvent(user, uae); - RaiseLocalEvent(uiEntity, oae); - if (oae.Cancelled || uae.Cancelled) + if (RaiseCanOpenEventChecks(user, uiEntity)) return false; // Give the UI an opportunity to prepare itself if it needs to do anything @@ -328,4 +324,15 @@ public sealed partial class ActivatableUISystem : EntitySystem if (ent.Comp.InHandsOnly) CloseAll(ent, ent); } + + private bool RaiseCanOpenEventChecks(EntityUid user, EntityUid uiEntity) + { + // If we've gotten this far, fire a cancellable event that indicates someone is about to activate this. + // This is so that stuff can require further conditions (like power). + var oae = new ActivatableUIOpenAttemptEvent(user); + var uae = new UserOpenActivatableUIAttemptEvent(user, uiEntity); + RaiseLocalEvent(user, uae); + RaiseLocalEvent(uiEntity, oae); + return oae.Cancelled || uae.Cancelled; + } } diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 718d0605c9..a8f2351ee5 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,74 +1,4 @@ Entries: -- author: RedBookcase - changes: - - message: Added Advanced Circular Saw as a Medical Doctor specific uplink item. - type: Add - - message: Removed Syndicate Surgery Duffel Bag from uplink. - type: Remove - id: 8328 - time: '2025-04-24T03:01:29.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/35915 -- author: AgentSmithRadio, MadeOfHeartAndStone - changes: - - message: Added cotton grilled cheese sandwich entity and recipe. - type: Add - id: 8329 - time: '2025-04-24T03:24:37.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/36135 -- author: SG6732 - changes: - - message: Added meat patties. Get the charcoal and let's start grillin. - type: Add - id: 8330 - time: '2025-04-24T03:45:27.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/34896 -- author: DrMelon, EmoGarbage404 - changes: - - message: Added turnstiles, gates able to limit the flow of foot traffic. The station's - flow is at your fingertips. - type: Add - id: 8331 - time: '2025-04-24T11:39:40.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/36313 -- author: EmoGarbage404 - changes: - - message: Added prisoner closets. These create automatic ID's which allow you to - quickly handle the transfer of prisoners in and out of genpop cells while keeping - their belongings secure. Imprison people today! - type: Add - id: 8332 - time: '2025-04-24T14:32:11.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/36392 -- author: Southbridge - changes: - - message: Colorful Light Crates have been added and can be ordered. - type: Add - id: 8333 - time: '2025-04-24T17:41:55.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/36750 -- author: ScarKy0, Jbsundown - changes: - - message: Letters and Packages can now be cut open with sharp objects. Be wary, - this makes cargo lose money! - type: Add - id: 8334 - time: '2025-04-24T19:47:46.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/36815 -- author: EmoGarbage404 - changes: - - message: Fixed situations where using the QSI would send the player into an esoteric - space realm. - type: Fix - id: 8335 - time: '2025-04-24T21:18:26.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/36834 -- author: thetolbean - changes: - - message: Specific outer clothing now correctly blocks your identity. - type: Fix - id: 8336 - time: '2025-04-24T23:42:27.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/33085 - author: SlamBamActionman changes: - message: Changed 2 posters to be rules-compliant. @@ -3900,3 +3830,119 @@ id: 8840 time: '2025-08-08T19:27:46.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/39481 +- author: Flareguy + changes: + - message: Updated moth displacement maps to fix missing pixels, among other various + issues. + type: Fix + id: 8841 + time: '2025-08-09T21:52:06.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/39174 +- author: Princess-Cheeseballs + changes: + - message: Mobs such as borgs, dragons, etc can no longer crawl. + type: Tweak + id: 8842 + time: '2025-08-10T17:49:29.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/39084 +- author: beck-thompson + changes: + - message: Various syndicate items can now be "locked" with your voice to hide their + true identity. The items are, all chameleon clothing, voice masks, no slips, + agent IDs, cane swords, and e daggers. + type: Add + - message: e dagger now has a slightly longer activation cool down (1s -> 1.5s). + type: Tweak + - message: Voice triggered items now properly display the trigger text when examined. + type: Fix + id: 8843 + time: '2025-08-10T18:10:13.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/39310 +- author: jessicamaybe + changes: + - message: 'Added a rare Hamlet variant: Fragile Hamlet' + type: Add + id: 8844 + time: '2025-08-10T22:20:26.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/39531 +- author: perryprog + changes: + - message: "Diona\_nymphs that reform inside of a locker will no longer be invisible." + type: Fix + id: 8845 + time: '2025-08-11T11:00:12.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/39505 +- author: Samuka + changes: + - message: The Robotics Console now correctly displays the brain installed information + if the borg has a empty MMI + type: Fix + - message: The Robotics Console now shows the borg physical integrity + type: Tweak + - message: Changed the colors for the battery display in the robotics console so + it easier to read the percentage + type: Tweak + id: 8846 + time: '2025-08-11T18:57:39.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/38023 +- author: metalgearsloth + changes: + - message: Inserting lights now plays an insert animation. + type: Add + id: 8847 + time: '2025-08-11T21:06:28.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/36541 +- author: sowelipililimute + changes: + - message: Borg modules can now have hands! + type: Add + - message: The generic cables module can now throw and mix and match cables in its + slots + type: Tweak + - message: The engineering construction module can now hold any material in its + materials slots, any floor tile in its floor tile slots, and now has slots for + holding any circuitboard and flatpack + type: Tweak + - message: The janitor custodial module can now throw its soap and pick up new and + better varieties of soap + type: Tweak + - message: The janitor advanced cleaning module can now throw and pick up beakers + in its slot + type: Tweak + - message: The medical topicals module can now hold any type of topicals in its + topicals slots + type: Tweak + - message: The medical chemical module can now drop and pick up its vials, and hold + other small containers + type: Tweak + - message: The medical advanced chemical module can now drop and pick up its beakers, + and hold other larger containers + type: Tweak + - message: The science artifact module can drop and pick up its vial, and hold other + small containers + type: Tweak + - message: The service service module can now drop and pick up its shaker, and pick + up other drink containers. It also has a spoon now.l containers + type: Tweak + - message: The science anomaly module can now throw its reinforced and reinforced + plasma glass + type: Tweak + - message: The service service module now has a book bag, and two slots for books + and paper + type: Tweak + - message: The service service module can now drop and pick up its shaker, and pick + up other drink containers. It also has a spoon now. + type: Tweak + - message: The service musique module can now drop and pick up instruments. + type: Tweak + id: 8848 + time: '2025-08-12T22:21:42.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/38668 +- author: perryprog + changes: + - message: Fixed lights sometimes not emitting light when spawning them or when + they enter PVS. + type: Fix + id: 8849 + time: '2025-08-12T23:00:36.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/39585 diff --git a/Resources/Changelog/Maps.yml b/Resources/Changelog/Maps.yml index b3e0833f7e..195d7b0e1a 100644 --- a/Resources/Changelog/Maps.yml +++ b/Resources/Changelog/Maps.yml @@ -503,4 +503,11 @@ id: 62 time: '2025-07-31T20:28:39.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/39295 +- author: F1restar4 + changes: + - message: On Oasis, added an atmospherics network monitor to atmos' front desk + type: Add + id: 63 + time: '2025-08-11T15:43:47.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/39331 Order: 1 diff --git a/Resources/Credits/GitHub.txt b/Resources/Credits/GitHub.txt index f1a069de14..814930806c 100644 --- a/Resources/Credits/GitHub.txt +++ b/Resources/Credits/GitHub.txt @@ -1 +1 @@ -0leshe, 0tito, 0x6273, 12rabbits, 1337dakota, 13spacemen, 154942, 2013HORSEMEATSCANDAL, 20kdc, 21Melkuu, 3nderall, 4310v343k, 4dplanner, 612git, 778b, 96flo, aaron, abadaba695, Ablankmann, abregado, Absolute-Potato, Absotively, achookh, Acruid, ActiveMammmoth, actually-reb, ada-please, adamsong, Adeinitas, adm2play, Admiral-Obvious-001, adrian, Adrian16199, Ady4ik, Aearo-Deepwater, Aerocrux, Aeshus, Aexolott, Aexxie, africalimedrop, afrokada, AftrLite, AgentSmithRadio, Agoichi, Ahion, aiden, Aidenkrz, Aisu9, ajcm, AJCM-git, AjexRose, Alekshhh, alexkar598, AlexMorgan3817, alexum418, alexumandxgabriel08x, Alice4267, Alithsko, Alkheemist, alliephante, ALMv1, Alpaccalypse, Alpha-Two, AlphaQwerty, Altoids1, amatwiedle, amylizzle, ancientpower, Andre19926, AndrewEyeke, AndreyCamper, Anzarot121, ApolloVector, Appiah, ar4ill, Arcane-Waffle, archee1, ArchPigeon, ArchRBX, areitpog, Arendian, areyouconfused, arimah, Arkanic, ArkiveDev, armoks, Arteben, ArthurMousatov, ArtisticRoomba, artur, ArZarLordOfMango, as334, AsikKEsel, AsnDen, asperger-sind, aspiringLich, astriloqua, august-sun, AutoOtter, AverageNotDoingAnythingEnjoyer, avghdev, Awlod, azzyisnothere, AzzyIsNotHere, B-Kirill, B3CKDOOR, baa14453, BackeTako, Bakke, BananaFlambe, Baptr0b0t, BarryNorfolk, BasedUser, beck-thompson, beesterman, bellwetherlogic, ben, benbryant0, benev0, benjamin-burges, BGare, bhespiritu, bibbly, BigfootBravo, BIGZi0348, bingojohnson, BismarckShuffle, Bixkitts, Blackern5000, Blazeror, blitzthesquishy, bloodrizer, Bloody2372, blueDev2, Boaz1111, BobdaBiscuit, BobTheSleder, boiled-water-tsar, Bokser815, bolantej, Booblesnoot42, Boolean-Buckeye, botanySupremist, brainfood1183, BramvanZijp, Brandon-Huu, BriBrooo, Bright0, brndd, bryce0110, BubblegumBlue, buletsponge, buntobaggins, bvelliquette, BWTCK, byondfuckery, c0rigin, c4llv07e, CaasGit, Caconym27, Calecute, Callmore, Camdot, capnsockless, CaptainMaru, captainsqrbeard, Carbonhell, Carolyn3114, Carou02, carteblanche4me, catdotjs, catlord, Catofquestionableethics, CatTheSystem, Centronias, Chaboricks, chairbender, Chaoticaa, Charlese2, charlie, chartman, ChaseFlorom, chavonadelal, Cheackraze, CheddaCheez, cheesePizza2, CheesePlated, Chief-Engineer, chillyconmor, christhirtle, chromiumboy, Chronophylos, Chubbicous, Chubbygummibear, Ciac32, ciaran, citrea, civilCornball, claustro305, Clement-O, clyf, Clyybber, CMDR-Piboy314, cnv41, coco, cohanna, Cohnway, Cojoke-dot, ColdAutumnRain, Colin-Tel, collinlunn, ComicIronic, Compilatron144, CookieMasterT, coolboy911, coolmankid12345, Coolsurf6, cooperwallace, corentt, CormosLemming, CrafterKolyan, crazybrain23, Crazydave91920, creadth, CrigCrag, CroilBird, Crotalus, CrudeWax, cryals, CrzyPotato, cubixthree, cutemoongod, Cyberboss, d34d10cc, DadeKuma, Daemon, daerSeebaer, dahnte, dakamakat, DamianX, dan, dangerrevolution, daniel-cr, DanSAussieITS, Daracke, Darkenson, DawBla, Daxxi3, dch-GH, de0rix, Deahaka, dean, DEATHB4DEFEAT, Deatherd, deathride58, DebugOk, Decappi, Decortex, Deeeeja, deepdarkdepths, DeepwaterCreations, Deerstop, degradka, Delete69, deltanedas, DenisShvalov, DerbyX, derek, dersheppard, Deserty0, Detintinto, DevilishMilk, devinschubert14, dexlerxd, dffdff2423, DieselMohawk, digitalic, Dimastra, DinnerCalzone, DinoWattz, Disp-Dev, DisposableCrewmember42, dissidentbullet, DjfjdfofdjfjD, doc-michael, docnite, Doctor-Cpu, DogZeroX, dolgovmi, dontbetank, Doomsdrayk, Doru991, DoubleRiceEddiedd, DoutorWhite, DR-DOCTOR-EVIL-EVIL, Dragonjspider, dragonryan06, drakewill-CRL, Drayff, dreamlyjack, DrEnzyme, dribblydrone, DrMelon, drongood12, DrSingh, DrSmugleaf, drteaspoon420, DTanxxx, DubiousDoggo, DuckManZach, Duddino, dukevanity, duskyjay, Dutch-VanDerLinde, dvir001, dylanstrategie, dylanwhittingham, Dynexust, Easypoller, echo, eclips_e, eden077, EEASAS, Efruit, efzapa, Ekkosangen, ElectroSR, elsie, elthundercloud, Elysium206, Emisse, emmafornash, EmoGarbage404, Endecc, EnrichedCaramel, Entvari, eoineoineoin, ephememory, eris, erohrs2, ERORR404V1, Errant-4, ertanic, esguard, estacaoespacialpirata, eugene, ewokswagger, exincore, exp111, f0x-n3rd, FacePluslll, Fahasor, FairlySadPanda, farrellka-dev, FATFSAAM2, Feluk6174, ficcialfaint, Fiftyllama, Fildrance, FillerVK, FinnishPaladin, firenamefn, Firewars763, FirinMaLazors, Fishfish458, fl-oz, Flareguy, flashgnash, FlipBrooke, FluffiestFloof, FluffMe, FluidRock, flymo5678, foboscheshir, FoLoKe, fooberticus, ForestNoises, forgotmyotheraccount, forkeyboards, forthbridge, Fortune117, foxhorn, freeman2651, freeze2222, frobnic8, Froffy025, Fromoriss, froozigiusz, FrostMando, FrostRibbon, Funce, FungiFellow, FunTust, Futuristic-OK, GalacticChimp, gamer3107, Gamewar360, gansulalan, GaussiArson, Gaxeer, gbasood, gcoremans, Geekyhobo, genderGeometries, GeneralGaws, Genkail, Gentleman-Bird, geraeumig, Ghagliiarghii, Git-Nivrak, githubuser508, gituhabu, GlassEclipse, GnarpGnarp, GNF54, godisdeadLOL, goet, GoldenCan, Goldminermac, Golinth, golubgik, GoodWheatley, Gorox221, gradientvera, graevy, GraniteSidewalk, GreaseMonk, greenrock64, GreyMario, GrownSamoyedDog, GTRsound, gusxyz, Gyrandola, h3half, hamurlik, Hanzdegloker, HappyRoach, Hardly3D, harikattar, he1acdvv, Hebi, Helix-ctrl, helm4142, Henry, HerCoyote23, HighTechPuddle, Hitlinemoss, hiucko, hivehum, Hmeister-fake, Hmeister-real, Hobbitmax, hobnob, HoidC, Holinka4ever, holyssss, HoofedEar, Hoolny, hord-brayden, Hoshizora, Hreno, Hrosts, htmlsystem, hubismal, Hugal31, Huxellberger, Hyenh, hyperb1, hyperDelegate, hyphenationc, i-justuser-i, iaada, iacore, IamVelcroboy, Ian321, icekot8, icesickleone, iczero, iglov, IgorAnt028, igorsaux, ike709, illersaver, Illiux, Ilushkins33, Ilya246, IlyaElDunaev, imatsoup, IMCB, impubbi, imrenq, imweax, indeano, Injazz, Insineer, insoPL, IntegerTempest, Interrobang01, Intoxicating-Innocence, IProduceWidgets, itsmethom, Itzbenz, iztokbajcar, Jackal298, Jackrost, jacksonzck, Jacktastic09, Jackw2As, jacob, jamessimo, janekvap, Jark255, Jarmer123, Jaskanbe, JasperJRoth, jbox144, JCGWE30, JerryImMouse, jerryimmouse, Jessetriesagain, jessicamaybe, Jezithyr, jicksaw, JiimBob, JimGamemaster, jimmy12or, JIPDawg, jjtParadox, jkwookee, jmcb, JohnGinnane, johnku1, Jophire, joshepvodka, JpegOfAFrog, jproads, JrInventor05, Jrpl, jukereise, juliangiebel, JustArt1m, JustCone14, justdie12, justin, justintether, JustinTrotter, JustinWinningham, justtne, K-Dynamic, k3yw, Kadeo64, Kaga-404, kaiserbirch, KaiShibaa, kalane15, kalanosh, KamTheSythe, Kanashi-Panda, katzenminer, kbailey-git, Keelin, Keer-Sar, KEEYNy, keikiru, Kelrak, kerisargit, keronshb, KIBORG04, KieueCaprie, Killerqu00, Kimpes, KingFroozy, kira-er, kiri-yoshikage, Kirillcas, Kirus59, Kistras, Kit0vras, KittenColony, Kittygyat, klaypexx, Kmc2000, Ko4ergaPunk, kognise, kokoc9n, komunre, KonstantinAngelov, kontakt, kosticia, koteq, kotobdev, Kowlin, KrasnoshchekovPavel, Krosus777, Krunklehorn, Kupie, kxvvv, kyupolaris, kzhanik, LaCumbiaDelCoronavirus, lajolico, Lamrr, lanedon, LankLTE, laok233, lapatison, larryrussian, lawdog4817, Lazzi0706, leander-0, leonardo-dabepis, leonidussaks, leonsfriedrich, LeoSantich, LetterN, lettern, Level10Cybermancer, LEVELcat, lever1209, LevitatingTree, Lgibb18, lgruthes, LightVillet, liltenhead, linkbro1, linkuyx, Litraxx, little-meow-meow, LittleBuilderJane, LittleNorthStar, LittleNyanCat, lizelive, ljm862, lmsnoise, localcc, lokachop, Lomcastar, LordCarve, LordEclipse, lucas, LucasTheDrgn, luckyshotpictures, LudwigVonChesterfield, luizwritescode, Lukasz825700516, luminight, lunarcomets, Lusatia, lvvova1, Lyndomen, lyroth001, lzimann, lzk228, M1tht1c, M3739, mac6na6na, MACMAN2003, Macoron, magicalus, magmodius, MagnusCrowe, maland1, malchanceux, MaloTV, ManelNavola, manelnavola, Mangohydra, marboww, Markek1, matt, Matz05, max, MaxNox7, maylokana, MehimoNemo, MeltedPixel, memeproof, MendaxxDev, Menshin, Mephisto72, MerrytheManokit, Mervill, metalgearsloth, MetalSage, MFMessage, mhamsterr, michaelcu, micheel665, mifia, MilenVolf, MilonPL, Minemoder5000, Minty642, minus1over12, Mirino97, mirrorcult, misandrie, MishaUnity, MissKay1994, MisterImp, MisterMecky, Mith-randalf, Mixelz, mjarduk, MjrLandWhale, mkanke-real, MLGTASTICa, mnva0, moderatelyaware, modern-nm, mokiros, momo, Moneyl, monotheonist, Moomoobeef, moony, Morb0, MossyGreySlope, mr-bo-jangles, Mr0maks, MrFippik, mrrobdemo, muburu, MureixloI, murolem, musicmanvr, MWKane, Myakot, Myctai, N3X15, nabegator, nails-n-tape, Nairodian, Naive817, NakataRin, namespace-Memory, Nannek, NazrinNya, neutrino-laser, NickPowers43, nikitosych, nikthechampiongr, Nimfar11, ninruB, Nirnael, NIXC, nkokic, NkoKirkto, nmajask, noctyrnal, noelkathegod, noirogen, nok-ko, NonchalantNoob, NoobyLegion, Nopey, not-gavnaed, notafet, notquitehadouken, NotSoDana, noudoit, noverd, Nox38, NuclearWinter, nukashimika, nuke-haus, NULL882, nullarmo, nyeogmi, Nylux, Nyranu, Nyxilath, och-och, OctoRocket, OldDanceJacket, OliverOtter, onesch, OneZerooo0, OnyxTheBrave, Orange-Winds, OrangeMoronage9622, Orsoniks, osjarw, Ostaf, othymer, OttoMaticode, Owai-Seek, packmore, paige404, paigemaeforrest, pali6, Palladinium, Pangogie, panzer-iv1, partyaddict, patrikturi, PaulRitter, peccneck, Peptide90, peptron1, perryprog, PeterFuto, PetMudstone, pewter-wiz, Pgriha, Phantom-Lily, pheenty, philingham, Phill101, Phooooooooooooooooooooooooooooooosphate, phunnyguy, PicklOH, PilgrimViis, Pill-U, pinkbat5, Piras314, Pireax, Pissachu, pissdemon, PixeltheAertistContrib, PixelTheKermit, PJB3005, Plasmaguy, plinyvic, Plykiya, poeMota, pofitlo, pointer-to-null, pok27, poklj, PolterTzi, PoorMansDreams, PopGamer45, portfiend, potato1234x, PotentiallyTom, PotRoastPiggy, Princess-Cheeseballs, ProfanedBane, PROG-MohamedDwidar, Prole0, ProPandaBear, PrPleGoo, ps3moira, Pspritechologist, Psychpsyo, psykana, psykzz, PuceTint, pumkin69, PuroSlavKing, PursuitInAshes, Putnam3145, py01, Pyrovi, qrtDaniil, qrwas, Quantum-cross, quatre, QueerNB, QuietlyWhisper, qwerltaz, Radezolid, RadioMull, Radosvik, Radrark, Rainbeon, Rainfey, Raitononai, Ramlik, RamZ, randy10122, Rane, Ranger6012, Rapidgame7, ravage123321, rbertoche, RedBookcase, Redfire1331, Redict, RedlineTriad, redmushie, RednoWCirabrab, ReeZer2, RemberBM, RemieRichards, RemTim, rene-descartes2021, Renlou, retequizzle, rhsvenson, rich-dunne, RieBi, riggleprime, RIKELOLDABOSS, rinary1, Rinkashikachi, riolume, rlebell33, RobbyTheFish, robinthedragon, Rockdtben, Rohesie, rok-povsic, rokudara-sen, rolfero, RomanNovo, rosieposieeee, Roudenn, router, ruddygreat, rumaks, RumiTiger, Ruzihm, S1rFl0, S1ss3l, Saakra, Sadie-silly, saga3152, saintmuntzer, Salex08, sam, samgithubaccount, Samuka-C, SaphireLattice, SapphicOverload, sarahon, sativaleanne, SaveliyM360, sBasalto, ScalyChimp, ScarKy0, schrodinger71, scrato, Scribbles0, scrivoy, scruq445, scuffedjays, ScumbagDog, SeamLesss, Segonist, semensponge, sephtasm, Serkket, sewerpig, SG6732, sh18rw, Shaddap1, ShadeAware, ShadowCommander, shadowtheprotogen546, shaeone, shampunj, shariathotpatrol, SharkSnake98, shibechef, Siginanto, SignalWalker, siigiil, silicon14wastaken, Simyon264, sirdragooon, Sirionaut, Sk1tch, SkaldetSkaeg, Skarletto, Skrauz, Skybailey-dev, Skyedra, SlamBamActionman, slarticodefast, Slava0135, sleepyyapril, slimmslamm, Slyfox333, Smugman, snebl, snicket, sniperchance, Snowni, snowsignal, SolidusSnek, solstar2, SonicHDC, SoulFN, SoulSloth, Soundwavesghost, soupkilove, southbridge-fur, sowelipililimute, Soydium, spacelizard, SpaceLizardSky, SpaceManiac, SpaceRox1244, SpaceyLady, Spangs04, spanky-spanky, Sparlight, spartak, SpartanKadence, spderman3333, SpeltIncorrectyl, Spessmann, SphiraI, SplinterGP, spoogemonster, sporekto, sporkyz, ssdaniel24, stalengd, stanberytrask, Stanislav4ix, StanTheCarpenter, starbuckss14, Stealthbomber16, stellar-novas, stewie523, stomf, Stop-Signs, stopbreaking, stopka-html, StrawberryMoses, Stray-Pyramid, strO0pwafel, Strol20, StStevens, Subversionary, sunbear-dev, supergdpwyl, superjj18, Supernorn, SweptWasTaken, SyaoranFox, Sybil, SYNCHRONIC, Szunti, t, Tainakov, takemysoult, tap, TaralGit, Taran, taurie, Tayrtahn, tday93, teamaki, TeenSarlacc, TekuNut, telyonok, TemporalOroboros, tentekal, terezi4real, Terraspark4941, texcruize, Tezzaide, TGODiamond, TGRCdev, tgrkzus, ThatGuyUSA, ThatOneGoblin25, thatrandomcanadianguy, TheArturZh, TheBlueYowie, thecopbennet, TheCze, TheDarkElites, thedraccx, TheEmber, TheFlyingSentry, TheIntoxicatedCat, thekilk, themias, theomund, TheProNoob678, TherapyGoth, ThereDrD0, TheShuEd, thetolbean, thevinter, TheWaffleJesus, thinbug0, ThunderBear2006, timothyteakettle, TimrodDX, timurjavid, tin-man-tim, TiniestShark, Titian3, tk-a369, tkdrg, tmtmtl30, ToastEnjoyer, Toby222, TokenStyle, Tollhouse, Toly65, tom-leys, tomasalves8, Tomeno, Tonydatguy, topy, tornado-technology, TornadoTechnology, tosatur, TotallyLemon, ToxicSonicFan04, Tr1bute, treytipton, trixxedbit, TrixxedHeart, tropicalhibi, truepaintgit, Truoizys, Tryded, TsjipTsjip, Tunguso4ka, TurboTrackerss14, tyashley, Tyler-IN, TytosB, Tyzemol, UbaserB, ubis1, UBlueberry, uhbg, UKNOWH, UltimateJester, Unbelievable-Salmon, underscorex5, UnicornOnLSD, Unisol, unusualcrow, Uriende, UristMcDorf, user424242420, Utmanarn, Vaaankas, valentfingerov, valquaint, Varen, Vasilis, VasilisThePikachu, veliebm, Velken, VelonacepsCalyxEggs, veprolet, VerinSenpai, veritable-calamity, Veritius, Vermidia, vero5123, verslebas, vexerot, viceemargo, VigersRay, violet754, Visne, vitusveit, vlad, vlados1408, VMSolidus, vmzd, voidnull000, volotomite, volundr-, Voomra, Vordenburg, vorkathbruh, Vortebo, vulppine, wafehling, walksanatora, Warentan, WarMechanic, Watermelon914, weaversam8, wertanchik, whateverusername0, whatston3, widgetbeck, Will-Oliver-Br, Willhelm53, WilliamECrew, willicassi, Winkarst-cpu, wirdal, wixoaGit, WlarusFromDaSpace, Wolfkey-SomeoneElseTookMyUsername, wrexbe, WTCWR68, xeri7, xkreksx, xprospero, xRiriq, xsainteer, YanehCheck, yathxyz, Ygg01, YotaXP, youarereadingthis, YoungThugSS14, Yousifb26, youtissoum, yunii, YuriyKiss, yuriykiss, zach-hill, Zadeon, Zalycon, zamp, Zandario, Zap527, Zealith-Gamer, ZelteHonor, zero, ZeroDiamond, ZeWaka, zHonys, zionnBE, ZNixian, Zokkie, ZoldorfTheWizard, zonespace27, Zylofan, Zymem, zzylex +0leshe, 0tito, 0x6273, 12rabbits, 1337dakota, 13spacemen, 154942, 2013HORSEMEATSCANDAL, 20kdc, 21Melkuu, 3nderall, 4310v343k, 4dplanner, 612git, 778b, 96flo, aaron, abadaba695, Ablankmann, abregado, Absolute-Potato, Absotively, achookh, Acruid, ActiveMammmoth, actually-reb, ada-please, adamsong, Adeinitas, adm2play, Admiral-Obvious-001, adrian, Adrian16199, Ady4ik, Aearo-Deepwater, Aerocrux, Aeshus, Aexolott, Aexxie, africalimedrop, afrokada, AftrLite, AgentSmithRadio, Agoichi, Ahion, aiden, Aidenkrz, Aisu9, ajcm, AJCM-git, AjexRose, Alekshhh, alexalexmax, alexkar598, AlexMorgan3817, alexum418, alexumandxgabriel08x, Alice4267, Alithsko, Alkheemist, alliephante, ALMv1, Alpaccalypse, Alpha-Two, AlphaQwerty, Altoids1, amatwiedle, amylizzle, ancientpower, Andre19926, AndrewEyeke, AndreyCamper, Anzarot121, ApolloVector, Appiah, ar4ill, Arcane-Waffle, archee1, ArchPigeon, ArchRBX, areitpog, Arendian, areyouconfused, arimah, Arkanic, ArkiveDev, armoks, Arteben, ArthurMousatov, ArtisticRoomba, artur, ArZarLordOfMango, as334, AsikKEsel, AsnDen, asperger-sind, aspiringLich, astriloqua, august-sun, AutoOtter, AverageNotDoingAnythingEnjoyer, avghdev, Awlod, AzzyIsNotHere, azzyisnothere, B-Kirill, B3CKDOOR, baa14453, BackeTako, Bakke, BananaFlambe, Baptr0b0t, BarryNorfolk, BasedUser, beck-thompson, beesterman, bellwetherlogic, ben, benbryant0, benev0, benjamin-burges, BGare, bhespiritu, bibbly, BigfootBravo, BIGZi0348, bingojohnson, BismarckShuffle, Bixkitts, Blackern5000, Blazeror, blitzthesquishy, bloodrizer, Bloody2372, blueDev2, Boaz1111, BobdaBiscuit, BobTheSleder, boiled-water-tsar, Bokser815, bolantej, Booblesnoot42, Boolean-Buckeye, botanySupremist, brainfood1183, BramvanZijp, Brandon-Huu, BriBrooo, Bright0, brndd, bryce0110, BubblegumBlue, buletsponge, buntobaggins, bvelliquette, BWTCK, byondfuckery, c0rigin, c4llv07e, CaasGit, Caconym27, Calecute, Callmore, Camdot, capnsockless, CaptainMaru, captainsqrbeard, Carbonhell, Carolyn3114, Carou02, carteblanche4me, catdotjs, catlord, Catofquestionableethics, CatTheSystem, Centronias, Chaboricks, chairbender, Chaoticaa, Charlese2, charlie, chartman, ChaseFlorom, chavonadelal, Cheackraze, CheddaCheez, cheesePizza2, CheesePlated, Chief-Engineer, chillyconmor, christhirtle, chromiumboy, Chronophylos, Chubbicous, Chubbygummibear, Ciac32, ciaran, citrea, civilCornball, claustro305, Clement-O, clyf, Clyybber, CMDR-Piboy314, cnv41, coco, cohanna, Cohnway, Cojoke-dot, ColdAutumnRain, Colin-Tel, collinlunn, ComicIronic, Compilatron144, CookieMasterT, coolboy911, coolmankid12345, Coolsurf6, cooperwallace, corentt, CormosLemming, CrafterKolyan, crazybrain23, Crazydave91920, creadth, CrigCrag, CroilBird, Crotalus, CrudeWax, cryals, CrzyPotato, cubixthree, cutemoongod, Cyberboss, d34d10cc, DadeKuma, Daemon, daerSeebaer, dahnte, dakamakat, DamianX, dan, dangerrevolution, daniel-cr, DanSAussieITS, Daracke, Darkenson, DawBla, Daxxi3, dch-GH, de0rix, Deahaka, dean, DEATHB4DEFEAT, Deatherd, deathride58, DebugOk, Decappi, Decortex, Deeeeja, deepdarkdepths, DeepwaterCreations, Deerstop, degradka, Delete69, deltanedas, DenisShvalov, DerbyX, derek, dersheppard, Deserty0, Detintinto, DevilishMilk, devinschubert14, dexlerxd, dffdff2423, DieselMohawk, digitalic, Dimastra, DinnerCalzone, DinoWattz, Disp-Dev, DisposableCrewmember42, dissidentbullet, DjfjdfofdjfjD, doc-michael, docnite, Doctor-Cpu, DogZeroX, dolgovmi, dontbetank, Doomsdrayk, Doru991, DoubleRiceEddiedd, DoutorWhite, DR-DOCTOR-EVIL-EVIL, Dragonjspider, dragonryan06, drakewill-CRL, Drayff, dreamlyjack, DrEnzyme, dribblydrone, DrMelon, drongood12, DrSingh, DrSmugleaf, drteaspoon420, DTanxxx, DubiousDoggo, DuckManZach, Duddino, dukevanity, duskyjay, Dutch-VanDerLinde, dvir001, dylanstrategie, dylanwhittingham, Dynexust, Easypoller, echo, eclips_e, eden077, EEASAS, Efruit, efzapa, Ekkosangen, ElectroSR, elsie, elthundercloud, Elysium206, Emisse, emmafornash, EmoGarbage404, Endecc, EnrichedCaramel, Entvari, eoineoineoin, ephememory, eris, erohrs2, ERORR404V1, Errant-4, ertanic, esguard, estacaoespacialpirata, eugene, ewokswagger, exincore, exp111, f0x-n3rd, FacePluslll, Fahasor, FairlySadPanda, farrellka-dev, FATFSAAM2, Feluk6174, ficcialfaint, Fiftyllama, Fildrance, FillerVK, FinnishPaladin, firenamefn, Firewars763, FirinMaLazors, Fishfish458, fl-oz, Flareguy, flashgnash, FlipBrooke, FluffiestFloof, FluffMe, FluidRock, flymo5678, foboscheshir, FoLoKe, fooberticus, ForestNoises, forgotmyotheraccount, forkeyboards, forthbridge, Fortune117, foxhorn, freeman2651, freeze2222, frobnic8, Froffy025, Fromoriss, froozigiusz, FrostMando, FrostRibbon, Funce, FungiFellow, FunTust, Futuristic-OK, GalacticChimp, gamer3107, Gamewar360, gansulalan, GaussiArson, Gaxeer, gbasood, gcoremans, Geekyhobo, genderGeometries, GeneralGaws, Genkail, Gentleman-Bird, geraeumig, Ghagliiarghii, Git-Nivrak, githubuser508, gituhabu, GlassEclipse, GnarpGnarp, GNF54, godisdeadLOL, goet, GoldenCan, Goldminermac, Golinth, golubgik, GoodWheatley, Gorox221, gradientvera, graevy, GraniteSidewalk, GreaseMonk, greenrock64, GreyMario, GrownSamoyedDog, GTRsound, gusxyz, Gyrandola, h3half, hamurlik, Hanzdegloker, HappyRoach, Hardly3D, harikattar, he1acdvv, Hebi, Helix-ctrl, helm4142, Henry, HerCoyote23, HighTechPuddle, Hitlinemoss, hiucko, hivehum, Hmeister-fake, Hmeister-real, Hobbitmax, hobnob, HoidC, Holinka4ever, holyssss, HoofedEar, Hoolny, hord-brayden, Hoshizora, Hreno, Hrosts, htmlsystem, hubismal, Hugal31, Huxellberger, Hyenh, hyperb1, hyperDelegate, hyphenationc, i-justuser-i, iaada, iacore, IamVelcroboy, Ian321, icekot8, icesickleone, iczero, iglov, IgorAnt028, igorsaux, ike709, illersaver, Illiux, Ilushkins33, Ilya246, IlyaElDunaev, imatsoup, IMCB, impubbi, imrenq, imweax, indeano, Injazz, Insineer, insoPL, IntegerTempest, Interrobang01, Intoxicating-Innocence, IProduceWidgets, itsmethom, Itzbenz, iztokbajcar, Jackal298, Jackrost, jacksonzck, Jacktastic09, Jackw2As, jacob, jamessimo, janekvap, Jark255, Jarmer123, Jaskanbe, JasperJRoth, jbox144, JCGWE30, jerryimmouse, JerryImMouse, Jessetriesagain, jessicamaybe, Jezithyr, jicksaw, JiimBob, JimGamemaster, jimmy12or, JIPDawg, jjtParadox, jkwookee, jmcb, JohnGinnane, johnku1, Jophire, joshepvodka, JpegOfAFrog, jproads, JrInventor05, Jrpl, jukereise, juliangiebel, JustArt1m, JustCone14, justdie12, justin, justintether, JustinTrotter, JustinWinningham, justtne, K-Dynamic, k3yw, Kadeo64, Kaga-404, kaiserbirch, KaiShibaa, kalane15, kalanosh, KamTheSythe, Kanashi-Panda, katzenminer, kbailey-git, Keelin, Keer-Sar, KEEYNy, keikiru, Kelrak, kerisargit, keronshb, KIBORG04, KieueCaprie, Killerqu00, Kimpes, KingFroozy, kira-er, kiri-yoshikage, Kirillcas, Kirus59, Kistras, Kit0vras, KittenColony, Kittygyat, klaypexx, Kmc2000, Ko4ergaPunk, kognise, kokoc9n, komunre, KonstantinAngelov, kontakt, kosticia, koteq, kotobdev, Kowlin, KrasnoshchekovPavel, Krosus777, Krunklehorn, Kupie, kxvvv, kyupolaris, kzhanik, LaCumbiaDelCoronavirus, lajolico, Lamrr, lanedon, LankLTE, laok233, lapatison, larryrussian, lawdog4817, Lazzi0706, leander-0, leonardo-dabepis, leonidussaks, leonsfriedrich, LeoSantich, lettern, LetterN, Level10Cybermancer, LEVELcat, lever1209, LevitatingTree, Lgibb18, lgruthes, LightVillet, liltenhead, linkbro1, linkuyx, Litraxx, little-meow-meow, LittleBuilderJane, LittleNorthStar, LittleNyanCat, lizelive, ljm862, lmsnoise, localcc, lokachop, lolman360, Lomcastar, LordCarve, LordEclipse, lucas, LucasTheDrgn, luckyshotpictures, LudwigVonChesterfield, luizwritescode, Lukasz825700516, luminight, lunarcomets, Lusatia, Luxeator, lvvova1, Lyndomen, lyroth001, lzimann, lzk228, M1tht1c, M3739, M87S, mac6na6na, MACMAN2003, Macoron, magicalus, magmodius, MagnusCrowe, maland1, malchanceux, MaloTV, manelnavola, ManelNavola, Mangohydra, marboww, Markek1, marlyn, matt, Matz05, max, MaxNox7, maylokana, MehimoNemo, MeltedPixel, memeproof, MendaxxDev, Menshin, Mephisto72, MerrytheManokit, Mervill, metalgearsloth, MetalSage, MFMessage, mhamsterr, michaelcu, micheel665, mifia, MilenVolf, MilonPL, Minemoder5000, Minty642, minus1over12, Mirino97, mirrorcult, misandrie, MishaUnity, MissKay1994, MisterImp, MisterMecky, Mith-randalf, Mixelz, mjarduk, MjrLandWhale, mkanke-real, MLGTASTICa, mnva0, moderatelyaware, modern-nm, mokiros, momo, Moneyl, monotheonist, Moomoobeef, moony, Morb0, MossyGreySlope, mr-bo-jangles, Mr0maks, MrFippik, mrrobdemo, muburu, MureixloI, murolem, musicmanvr, MWKane, Myakot, Myctai, N3X15, nabegator, nails-n-tape, Nairodian, Naive817, NakataRin, namespace-Memory, Nannek, NazrinNya, neutrino-laser, NickPowers43, nikitosych, nikthechampiongr, Nimfar11, ninruB, Nirnael, NIXC, nkokic, NkoKirkto, nmajask, noctyrnal, noelkathegod, noirogen, nok-ko, NonchalantNoob, NoobyLegion, Nopey, not-gavnaed, notafet, notquitehadouken, NotSoDana, noudoit, noverd, Nox38, NuclearWinter, nukashimika, nuke-haus, NULL882, nullarmo, nyeogmi, Nylux, Nyranu, Nyxilath, och-och, OctoRocket, OldDanceJacket, OliverOtter, onesch, OneZerooo0, OnyxTheBrave, Orange-Winds, OrangeMoronage9622, Orsoniks, osjarw, Ostaf, othymer, OttoMaticode, Owai-Seek, packmore, paige404, paigemaeforrest, pali6, Palladinium, Pangogie, panzer-iv1, partyaddict, patrikturi, PaulRitter, peccneck, Peptide90, peptron1, perryprog, PeterFuto, PetMudstone, pewter-wiz, pgraycs, Pgriha, Phantom-Lily, pheenty, philingham, Phill101, Phooooooooooooooooooooooooooooooosphate, phunnyguy, PicklOH, PilgrimViis, Pill-U, pinkbat5, Piras314, Pireax, Pissachu, pissdemon, PixeltheAertistContrib, PixelTheKermit, PJB3005, Plasmaguy, plinyvic, Plykiya, poeMota, pofitlo, pointer-to-null, pok27, poklj, PolterTzi, PoorMansDreams, PopGamer45, portfiend, potato1234x, PotentiallyTom, PotRoastPiggy, Princess-Cheeseballs, ProfanedBane, PROG-MohamedDwidar, Prole0, ProPandaBear, PrPleGoo, ps3moira, Pspritechologist, Psychpsyo, psykana, psykzz, PuceTint, pumkin69, PuroSlavKing, PursuitInAshes, Putnam3145, py01, Pyrovi, qrtDaniil, qrwas, Quantum-cross, quatre, QueerNB, QuietlyWhisper, qwerltaz, Radezolid, RadioMull, Radosvik, Radrark, Rainbeon, Rainfey, Raitononai, Ramlik, RamZ, randy10122, Rane, Ranger6012, Rapidgame7, ravage123321, rbertoche, RedBookcase, Redfire1331, Redict, RedlineTriad, redmushie, RednoWCirabrab, ReeZer2, RemberBM, RemieRichards, RemTim, rene-descartes2021, Renlou, retequizzle, rhsvenson, rich-dunne, RieBi, riggleprime, RIKELOLDABOSS, rinary1, Rinkashikachi, riolume, rlebell33, RobbyTheFish, robinthedragon, Rockdtben, Rohesie, rok-povsic, rokudara-sen, rolfero, RomanNovo, rosieposieeee, Roudenn, router, ruddygreat, rumaks, RumiTiger, Ruzihm, S1rFl0, S1ss3l, Saakra, Sadie-silly, saga3152, saintmuntzer, Salex08, sam, samgithubaccount, Samuka-C, SaphireLattice, SapphicOverload, sarahon, sativaleanne, SaveliyM360, sBasalto, ScalyChimp, ScarKy0, schrodinger71, scrato, Scribbles0, scrivoy, scruq445, scuffedjays, ScumbagDog, SeamLesss, Segonist, semensponge, sephtasm, ser1-1y, Serkket, sewerpig, SG6732, sh18rw, Shaddap1, ShadeAware, ShadowCommander, shadowtheprotogen546, shaeone, shampunj, shariathotpatrol, SharkSnake98, shibechef, Siginanto, SignalWalker, siigiil, silicon14wastaken, Simyon264, sirdragooon, Sirionaut, Sk1tch, SkaldetSkaeg, Skarletto, Skrauz, Skybailey-dev, Skyedra, SlamBamActionman, slarticodefast, Slava0135, sleepyyapril, slimmslamm, Slyfox333, Smugman, snebl, snicket, sniperchance, Snowni, snowsignal, SolidusSnek, solstar2, SonicHDC, SoulFN, SoulSloth, Soundwavesghost, soupkilove, southbridge-fur, sowelipililimute, Soydium, spacelizard, SpaceLizardSky, SpaceManiac, SpaceRox1244, SpaceyLady, Spangs04, spanky-spanky, Sparlight, spartak, SpartanKadence, spderman3333, SpeltIncorrectyl, Spessmann, SphiraI, SplinterGP, spoogemonster, sporekto, sporkyz, ssdaniel24, stalengd, stanberytrask, Stanislav4ix, StanTheCarpenter, starbuckss14, Stealthbomber16, stellar-novas, stewie523, stomf, Stop-Signs, stopbreaking, stopka-html, StrawberryMoses, Stray-Pyramid, strO0pwafel, Strol20, StStevens, Subversionary, sunbear-dev, supergdpwyl, superjj18, Supernorn, SweptWasTaken, SyaoranFox, Sybil, SYNCHRONIC, Szunti, t, Tainakov, takemysoult, taonewt, tap, TaralGit, Taran, taurie, Tayrtahn, tday93, teamaki, TeenSarlacc, TekuNut, telyonok, TemporalOroboros, tentekal, terezi4real, Terraspark4941, texcruize, Tezzaide, TGODiamond, TGRCdev, tgrkzus, ThatGuyUSA, ThatOneGoblin25, thatrandomcanadianguy, TheArturZh, TheBlueYowie, thecopbennet, TheCze, TheDarkElites, thedraccx, TheEmber, TheFlyingSentry, TheIntoxicatedCat, thekilk, themias, theomund, TheProNoob678, TherapyGoth, ThereDrD0, TheShuEd, thetolbean, thevinter, TheWaffleJesus, thinbug0, ThunderBear2006, timothyteakettle, TimrodDX, timurjavid, tin-man-tim, TiniestShark, Titian3, tk-a369, tkdrg, tmtmtl30, ToastEnjoyer, Toby222, TokenStyle, Tollhouse, Toly65, tom-leys, tomasalves8, Tomeno, Tonydatguy, topy, tornado-technology, TornadoTechnology, tosatur, TotallyLemon, ToxicSonicFan04, Tr1bute, treytipton, trixxedbit, TrixxedHeart, tropicalhibi, truepaintgit, Truoizys, Tryded, TsjipTsjip, Tunguso4ka, TurboTrackerss14, tyashley, Tyler-IN, TytosB, Tyzemol, UbaserB, ubis1, UBlueberry, uhbg, UKNOWH, UltimateJester, Unbelievable-Salmon, underscorex5, UnicornOnLSD, Unisol, unusualcrow, Uriende, UristMcDorf, user424242420, Utmanarn, Vaaankas, valentfingerov, valquaint, Varen, Vasilis, VasilisThePikachu, veliebm, Velken, VelonacepsCalyxEggs, veprolet, VerinSenpai, veritable-calamity, Veritius, Vermidia, vero5123, verslebas, vexerot, viceemargo, VigersRay, violet754, Visne, vitusveit, vlad, vlados1408, VMSolidus, vmzd, voidnull000, volotomite, volundr-, Voomra, Vordenburg, vorkathbruh, Vortebo, vulppine, wafehling, walksanatora, Warentan, WarMechanic, Watermelon914, weaversam8, wertanchik, whateverusername0, whatston3, widgetbeck, Will-Oliver-Br, Willhelm53, WilliamECrew, willicassi, Winkarst-cpu, wirdal, wixoaGit, WlarusFromDaSpace, Wolfkey-SomeoneElseTookMyUsername, wrexbe, WTCWR68, xeri7, xkreksx, xprospero, xRiriq, xsainteer, YanehCheck, yathxyz, Ygg01, YotaXP, youarereadingthis, YoungThugSS14, Yousifb26, youtissoum, yunii, yuriykiss, YuriyKiss, zach-hill, Zadeon, Zalycon, zamp, Zandario, Zap527, Zealith-Gamer, ZelteHonor, zero, ZeroDiamond, ZeWaka, zHonys, zionnBE, ZNixian, Zokkie, ZoldorfTheWizard, zonespace27, Zylofan, Zymem, zzylex diff --git a/Resources/Locale/en-US/_strings/access/components/agent-id-card-component.ftl b/Resources/Locale/en-US/_strings/access/components/agent-id-card-component.ftl index 5e1e3cd7cf..c645967d98 100644 --- a/Resources/Locale/en-US/_strings/access/components/agent-id-card-component.ftl +++ b/Resources/Locale/en-US/_strings/access/components/agent-id-card-component.ftl @@ -8,3 +8,5 @@ agent-id-card-current-name = Name: agent-id-card-current-job = Job: agent-id-card-job-icon-label = Job icon: agent-id-menu-title = Agent ID Card + +agent-id-open-ui-verb = Change settings diff --git a/Resources/Locale/en-US/_strings/fluids/components/spillable-component.ftl b/Resources/Locale/en-US/_strings/fluids/components/spillable-component.ftl index cfcdf86a90..6dc677d0bc 100644 --- a/Resources/Locale/en-US/_strings/fluids/components/spillable-component.ftl +++ b/Resources/Locale/en-US/_strings/fluids/components/spillable-component.ftl @@ -10,4 +10,4 @@ spill-melee-hit-others = {CAPITALIZE(THE($attacker))} spills some of {THE($spill spill-land-spilled-on-other = {CAPITALIZE(THE($spillable))} spills some of its solution onto {THE($target)}! spill-examine-is-spillable = This container looks spillable. -spill-examine-spillable-weapon = You could splash this onto someone with a melee attack. +spill-examine-spillable-weapon = You could splash this onto someone with an attack. diff --git a/Resources/Locale/en-US/_strings/ghost/observer-role.ftl b/Resources/Locale/en-US/_strings/ghost/observer-role.ftl deleted file mode 100644 index acb30b128f..0000000000 --- a/Resources/Locale/en-US/_strings/ghost/observer-role.ftl +++ /dev/null @@ -1,2 +0,0 @@ -observer-role-name = Observer - diff --git a/Resources/Locale/en-US/_strings/research/components/robotics-console.ftl b/Resources/Locale/en-US/_strings/research/components/robotics-console.ftl index a4c82bd032..d3d2bcea48 100644 --- a/Resources/Locale/en-US/_strings/research/components/robotics-console.ftl +++ b/Resources/Locale/en-US/_strings/research/components/robotics-console.ftl @@ -6,6 +6,7 @@ robotics-console-model = [color=gray]Model:[/color] {$name} # name is not formatted to prevent players trolling robotics-console-designation = [color=gray]Designation:[/color] robotics-console-battery = [color=gray]Battery charge:[/color] [color={$color}]{$charge}[/color]% +robotics-console-hp = [color=gray]Integrity:[/color] [color={$color}]{$hp}[/color]% robotics-console-modules = [color=gray]Modules installed:[/color] {$count} robotics-console-brain = [color=gray]Brain installed:[/color] [color={$brain -> [true] green]Yes diff --git a/Resources/Locale/en-US/_strings/robotics/borg_modules.ftl b/Resources/Locale/en-US/_strings/robotics/borg_modules.ftl new file mode 100644 index 0000000000..b6c55447e7 --- /dev/null +++ b/Resources/Locale/en-US/_strings/robotics/borg_modules.ftl @@ -0,0 +1,12 @@ +borg-slot-cables-empty = Cables +borg-slot-construction-empty = Construction materials +borg-slot-circuitboards-empty = Circuitboards +borg-slot-flatpacks-empty = Flatpacks +borg-slot-tiles-empty = Floor tiles +borg-slot-topicals-empty = Topicals +borg-slot-small-containers-empty = Small containers +borg-slot-chemical-containers-empty = Chemical containers +borg-slot-documents-empty = Books and papers +borg-slot-soap-empty = Soap +borg-slot-instruments-empty = Instruments +borg-slot-beakers-empty = Beakers diff --git a/Resources/Locale/en-US/changeling/changeling.ftl b/Resources/Locale/en-US/changeling/changeling.ftl index 423cb0811e..d304385848 100644 --- a/Resources/Locale/en-US/changeling/changeling.ftl +++ b/Resources/Locale/en-US/changeling/changeling.ftl @@ -1,9 +1,6 @@ roles-antag-changeling-name = Changeling roles-antag-changeling-objective = A intelligent predator that assumes the identities of its victims. -changeling-role-greeting = You are a Changeling, a highly intelligent predator. Your only goal is to escape the station alive via assuming the identities of the denizens of this station. You are hungry and will not make it long without sustenance... kill, consume, hide, survive. -changeling-briefing = You are a changeling, your goal is to survive. Consume humanoids to gain biomass and utilize it to evade termination. You are able to utilize and assume the identities of those you consume to evade a grim fate. - changeling-devour-attempt-failed-rotting = This corpse has only rotted biomass. changeling-devour-attempt-failed-protected = This victim's biomass is protected. diff --git a/Resources/Locale/en-US/locks/voice-trigger-lock.ftl b/Resources/Locale/en-US/locks/voice-trigger-lock.ftl new file mode 100644 index 0000000000..fd2dc38d23 --- /dev/null +++ b/Resources/Locale/en-US/locks/voice-trigger-lock.ftl @@ -0,0 +1,5 @@ +voice-trigger-lock-verb-record = Record lock phrase +voice-trigger-lock-verb-message = Locking the item will disable features that reveal its true nature! + +voice-trigger-lock-on-uninitialized = The display is blank +voice-trigger-lock-on-examine = The display shows the passphrase: "{$keyphrase}" diff --git a/Resources/Maps/_Sunrise/Station/oasis.yml b/Resources/Maps/_Sunrise/Station/oasis.yml index 69cef57a20..7ca774cbe7 100644 --- a/Resources/Maps/_Sunrise/Station/oasis.yml +++ b/Resources/Maps/_Sunrise/Station/oasis.yml @@ -81347,6 +81347,14 @@ entities: - type: Transform pos: 10.5,-33.5 parent: 2 +- proto: ComputerAtmosMonitoring + entities: + - uid: 8792 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -40.5,3.5 + parent: 2 - proto: computerBodyScanner entities: - uid: 8819 diff --git a/Resources/Prototypes/Catalog/Fills/Crates/npc.yml b/Resources/Prototypes/Catalog/Fills/Crates/npc.yml index 10c715bb99..403eb1a01d 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/npc.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/npc.yml @@ -233,6 +233,11 @@ - type: StorageFill contents: - id: MobHamsterHamlet + prob: 1 + orGroup: MobHamsterHamlet + - id: MobHamsterHamletSlippery + prob: 0.001 + orGroup: MobHamsterHamlet - type: entity id: CrateNPCLizard diff --git a/Resources/Prototypes/Entities/Clothing/Back/specific.yml b/Resources/Prototypes/Entities/Clothing/Back/specific.yml index bc3bbc0cdc..3864234d93 100644 --- a/Resources/Prototypes/Entities/Clothing/Back/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Back/specific.yml @@ -1,5 +1,5 @@ - type: entity - parent: ClothingBackpack + parent: [ClothingBackpack, BaseChameleon] id: ClothingBackpackChameleon name: backpack description: You wear this on your back and put items into it. diff --git a/Resources/Prototypes/Entities/Clothing/Ears/specific.yml b/Resources/Prototypes/Entities/Clothing/Ears/specific.yml index cf330d2434..e78267320d 100644 --- a/Resources/Prototypes/Entities/Clothing/Ears/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Ears/specific.yml @@ -1,5 +1,5 @@ - type: entity - parent: ClothingHeadsetGrey + parent: [ClothingHeadsetGrey, BaseChameleon] id: ClothingHeadsetChameleon name: passenger headset description: An updated, modular intercom that fits over the head. Takes encryption keys. @@ -14,10 +14,6 @@ - type: ChameleonClothing slot: [ears] default: ClothingHeadsetGrey - - type: UserInterface - interfaces: - enum.ChameleonUiKey.Key: - type: ChameleonBoundUserInterface # Sunrise-Start - type: Biocode factions: diff --git a/Resources/Prototypes/Entities/Clothing/Eyes/specific.yml b/Resources/Prototypes/Entities/Clothing/Eyes/specific.yml index e8d709e6f4..95fa467047 100644 --- a/Resources/Prototypes/Entities/Clothing/Eyes/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Eyes/specific.yml @@ -1,5 +1,5 @@ - type: entity - parent: ClothingEyesBase + parent: [ClothingEyesBase, BaseChameleon] id: ClothingEyesChameleon # no flash immunity, sorry name: sun glasses description: Useful both for security and cargonia. @@ -7,7 +7,7 @@ components: - type: Tag tags: # intentionally no WhitelistChameleon tag - - PetWearable + - PetWearable - type: Sprite sprite: Clothing/Eyes/Glasses/sunglasses.rsi - type: Clothing @@ -15,14 +15,9 @@ - type: ChameleonClothing slot: [eyes] default: ClothingEyesGlassesSunglasses - - type: UserInterface - interfaces: - enum.ChameleonUiKey.Key: - type: ChameleonBoundUserInterface # Sunrise-Start - type: Biocode factions: - Syndicate - Thief # Sunrise-End - diff --git a/Resources/Prototypes/Entities/Clothing/Hands/specific.yml b/Resources/Prototypes/Entities/Clothing/Hands/specific.yml index 68ca89cfbe..1c71c6262f 100644 --- a/Resources/Prototypes/Entities/Clothing/Hands/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Hands/specific.yml @@ -1,5 +1,5 @@ - type: entity - parent: ClothingHandsButcherable + parent: [ClothingHandsButcherable, BaseChameleon] id: ClothingHandsChameleon # doesn't protect from electricity or heat name: black gloves description: Regular black gloves that do not keep you from frying. @@ -17,10 +17,6 @@ - type: Fiber fiberMaterial: fibers-chameleon - type: FingerprintMask - - type: UserInterface - interfaces: - enum.ChameleonUiKey.Key: - type: ChameleonBoundUserInterface # Sunrise-Start - type: Biocode factions: diff --git a/Resources/Prototypes/Entities/Clothing/Head/specific.yml b/Resources/Prototypes/Entities/Clothing/Head/specific.yml index ece7d81361..c88140af66 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/specific.yml @@ -1,5 +1,5 @@ - type: entity - parent: ClothingHeadBase + parent: [ClothingHeadBase, BaseChameleon] id: ClothingHeadHatChameleon name: beret description: A beret, an artists favorite headwear. @@ -14,10 +14,6 @@ - type: ChameleonClothing slot: [HEAD] default: ClothingHeadHatBeret - - type: UserInterface - interfaces: - enum.ChameleonUiKey.Key: - type: ChameleonBoundUserInterface # Sunrise-Start - type: Biocode factions: diff --git a/Resources/Prototypes/Entities/Clothing/Masks/specific.yml b/Resources/Prototypes/Entities/Clothing/Masks/specific.yml index d1b63c4eb7..83d4c7aa66 100644 --- a/Resources/Prototypes/Entities/Clothing/Masks/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Masks/specific.yml @@ -1,5 +1,5 @@ - type: entity - parent: ClothingMaskBase + parent: [ClothingMaskBase, BaseChameleon] id: ClothingMaskGasChameleon name: gas mask description: A face-covering mask that can be connected to an air supply. @@ -16,10 +16,6 @@ default: ClothingMaskGas - type: BreathMask - type: IdentityBlocker # need that for default ClothingMaskGas - - type: UserInterface - interfaces: - enum.ChameleonUiKey.Key: - type: ChameleonBoundUserInterface - type: HideLayerClothing slots: - Snout @@ -36,6 +32,12 @@ suffix: Voice Mask, Chameleon components: - type: VoiceMask + - type: UIRequiresLock + userInterfaceKeys: + - enum.ChameleonUiKey.Key + - enum.VoiceMaskUIKey.Key + accessDeniedSound: null + popup: null - type: HideLayerClothing slots: - Snout diff --git a/Resources/Prototypes/Entities/Clothing/Neck/specific.yml b/Resources/Prototypes/Entities/Clothing/Neck/specific.yml index c2f86e57f1..06096d8608 100644 --- a/Resources/Prototypes/Entities/Clothing/Neck/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Neck/specific.yml @@ -1,5 +1,5 @@ - type: entity - parent: ClothingNeckBase + parent: [ClothingNeckBase, BaseChameleon] id: ClothingNeckChameleon name: striped red scarf description: A stylish striped red scarf. The perfect winter accessory for those with a keen fashion sense, and those who just can't handle a cold breeze on their necks. @@ -14,10 +14,6 @@ - type: ChameleonClothing slot: [NECK] default: ClothingNeckScarfStripedRed - - type: UserInterface - interfaces: - enum.ChameleonUiKey.Key: - type: ChameleonBoundUserInterface # Sunrise-Start - type: Biocode factions: diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/specific.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/specific.yml index f1017d1017..f780b73d75 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/specific.yml @@ -1,5 +1,5 @@ - type: entity - parent: ClothingOuterBase + parent: [ClothingOuterBase, BaseChameleon] id: ClothingOuterChameleon name: vest description: A thick vest with a rubbery, water-resistant shell. @@ -15,10 +15,6 @@ slot: [outerClothing] default: ClothingOuterVest requireTag: Vest # Sunrise-Edit - - type: UserInterface - interfaces: - enum.ChameleonUiKey.Key: - type: ChameleonBoundUserInterface - type: TemperatureProtection # Same as a basic winter coat. heatingCoefficient: 1.1 coolingCoefficient: 0.1 diff --git a/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml b/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml index f8faed04bc..701a9924db 100644 --- a/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml @@ -170,7 +170,7 @@ sprite: Clothing/Shoes/Specific/wizard.rsi - type: entity - parent: ClothingShoesBase + parent: [ClothingShoesBase, BaseChameleon] id: ClothingShoesChameleon name: black shoes suffix: Chameleon @@ -204,16 +204,12 @@ - type: ChameleonClothing slot: [FEET] default: ClothingShoesColorBlack - - type: UserInterface - interfaces: - enum.ChameleonUiKey.Key: - type: ChameleonBoundUserInterface - # Sunrise-Start + # Sunrise-Start - type: Biocode factions: - Syndicate - Thief - # Sunrise-End + # Sunrise-End - type: entity parent: ClothingShoesChameleon diff --git a/Resources/Prototypes/Entities/Clothing/Uniforms/specific.yml b/Resources/Prototypes/Entities/Clothing/Uniforms/specific.yml index 6af4f84c59..969d0b7f91 100644 --- a/Resources/Prototypes/Entities/Clothing/Uniforms/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Uniforms/specific.yml @@ -1,5 +1,5 @@ - type: entity - parent: ClothingUniformBase + parent: [ClothingUniformBase, BaseChameleon] id: ClothingUniformJumpsuitChameleon name: black jumpsuit description: A generic black jumpsuit with no rank markings. @@ -36,13 +36,9 @@ - type: ChameleonClothing slot: [innerclothing] default: ClothingUniformJumpsuitColorBlack - - type: UserInterface - interfaces: - enum.ChameleonUiKey.Key: - type: ChameleonBoundUserInterface - # Sunrise-Start + # Sunrise-Start - type: Biocode factions: - Syndicate - Thief - # Sunrise-End + # Sunrise-End diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml index 5ce43a2317..6981320678 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml @@ -182,7 +182,9 @@ - type: Lock locked: true unlockOnClick: false - - type: ActivatableUIRequiresLock + - type: UIRequiresLock + userInterfaceKeys: + - enum.BorgUiKey.Key - type: LockedWiresPanel - type: Damageable damageContainer: Silicon diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 2aa796e57a..4e618df7d5 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -1506,12 +1506,14 @@ methods: [ Touch ] effects: - !type:WashCreamPieReaction + - type: Crawler # Sunrise-start - type: AttackOnInteractionFail attackMemoryLength: 10 # Sunrise-end + - type: entity name: monkey id: MobMonkey @@ -2448,6 +2450,7 @@ - type: FactionException # Sunrise-End +# TODO: Make grenade penguin voice activated like the rest of the stealth items. - type: entity name: grenade penguin parent: [ MobPenguin, MobCombat, BaseSyndicateContraband ] diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml index 6541df0939..e982fb067a 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml @@ -700,6 +700,22 @@ - meaty - sadness +- type: entity + parent: MobHamsterHamlet + id: MobHamsterHamletSlippery + suffix: Slippery + components: + - type: Slippery + - type: StepTrigger + requiredTriggeredSpeed: 1 + - type: TriggerOnStepTrigger + - type: GibOnTrigger + - type: EmitSoundOnTrigger + sound: + path: "/Audio/Animals/mouse_squeak.ogg" + positional: true + predicted: true + - type: entity name: Shiva parent: MobGiantSpider diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index f60b2f5844..549fb0a505 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -172,6 +172,7 @@ - type: SleepEmitSound - type: SSDIndicator - type: StandingState + - type: Crawler - type: Dna - type: MindContainer showExamineInfo: true diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/base_machineboard.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/base_machineboard.yml index 42fe0fb2f8..110f540ad7 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/base_machineboard.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/base_machineboard.yml @@ -23,4 +23,5 @@ Glass: 230 chemicalComposition: Silicon: 20 + - type: Circuitboard diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml index 9257de049c..2cd6cc64b0 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml @@ -22,6 +22,7 @@ Glass: 230 chemicalComposition: Silicon: 20 + - type: Circuitboard - type: entity parent: BaseComputerCircuitboard diff --git a/Resources/Prototypes/Entities/Objects/Devices/Electronics/base_electronics.yml b/Resources/Prototypes/Entities/Objects/Devices/Electronics/base_electronics.yml index 9782b8fddf..3c863f22ad 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Electronics/base_electronics.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Electronics/base_electronics.yml @@ -22,3 +22,4 @@ Glass: 200 chemicalComposition: Silicon: 20 + - type: Circuitboard diff --git a/Resources/Prototypes/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Entities/Objects/Devices/pda.yml index 4fdfb2ebf9..2d27bc3be4 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/pda.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/pda.yml @@ -1521,7 +1521,7 @@ - MedTekCartridge - type: entity - parent: BasePDA + parent: [BasePDA, VoiceLock] id: ChameleonPDA name: passenger PDA description: Why isn't it gray? diff --git a/Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml b/Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml index 250fdf3ed3..7d7f9275f4 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml @@ -15,6 +15,7 @@ - type: Tag tags: - Sheet + - ConstructionMaterial - NoPaint # Sunrise-edit - type: Material - type: Damageable @@ -109,15 +110,6 @@ stackType: Glass count: 1 -- type: entity - parent: SheetGlass - id: SheetGlassLingering0 - suffix: Lingering, 0 - components: - - type: Stack - lingering: true - count: 0 - - type: entity parent: SheetGlassBase id: SheetRGlass @@ -198,15 +190,6 @@ Quantity: 0.5 canReact: false -- type: entity - parent: SheetRGlass - id: SheetRGlassLingering0 - suffix: Lingering, 0 - components: - - type: Stack - lingering: true - count: 0 - - type: entity parent: SheetGlassBase id: SheetPGlass @@ -337,15 +320,6 @@ stackType: ReinforcedPlasmaGlass count: 1 -- type: entity - parent: SheetRPGlass - id: SheetRPGlassLingering0 - suffix: Lingering, 0 - components: - - type: Stack - lingering: true - count: 0 - - type: entity parent: SheetGlassBase id: SheetUGlass diff --git a/Resources/Prototypes/Entities/Objects/Materials/Sheets/metal.yml b/Resources/Prototypes/Entities/Objects/Materials/Sheets/metal.yml index 82efb5a5ee..ce1b6d8dbe 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/Sheets/metal.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/Sheets/metal.yml @@ -15,6 +15,7 @@ tags: - Sheet - Metal + - ConstructionMaterial - NoPaint # Sunrise-edit - type: Damageable damageContainer: Inorganic @@ -99,15 +100,6 @@ stackType: Steel count: 1 -- type: entity - parent: SheetSteel - id: SheetSteelLingering0 - suffix: Lingering, 0 - components: - - type: Stack - lingering: true - count: 0 - - type: entity parent: SheetMetalBase id: SheetBrass @@ -234,12 +226,3 @@ - type: Stack stackType: Plasteel count: 1 - -- type: entity - parent: SheetPlasteel - id: SheetPlasteelLingering0 - suffix: Lingering, 0 - components: - - type: Stack - lingering: true - count: 0 diff --git a/Resources/Prototypes/Entities/Objects/Materials/Sheets/other.yml b/Resources/Prototypes/Entities/Objects/Materials/Sheets/other.yml index 4215e15679..565b2c8027 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/Sheets/other.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/Sheets/other.yml @@ -12,6 +12,7 @@ - type: Tag tags: - Sheet + - ConstructionMaterial - NoPaint # Sunrise-edit - type: Damageable damageContainer: Inorganic @@ -116,6 +117,7 @@ - type: Tag tags: - Sheet + - ConstructionMaterial - NoPaint # Sunrise-edit - type: entity @@ -139,16 +141,6 @@ - type: Stack count: 1 -- type: entity - parent: SheetPlasma - id: SheetPlasmaLingering0 - name: plasma - suffix: 0, Lingering - components: - - type: Stack - lingering: true - count: 0 - - type: entity parent: [PlasticSounds, SheetOtherBase] # Sunrise edit id: SheetPlastic @@ -159,6 +151,7 @@ tags: - Plastic - Sheet + - ConstructionMaterial - NoPaint # Sunrise-edit - type: Material - type: PhysicalComposition diff --git a/Resources/Prototypes/Entities/Objects/Materials/ingots.yml b/Resources/Prototypes/Entities/Objects/Materials/ingots.yml index 7e8e6a5b74..8d9b8259da 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/ingots.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/ingots.yml @@ -14,6 +14,7 @@ - type: Tag tags: - Ingot + - ConstructionMaterial - type: Damageable damageContainer: Inorganic damageModifierSet: Metallic diff --git a/Resources/Prototypes/Entities/Objects/Materials/materials.yml b/Resources/Prototypes/Entities/Objects/Materials/materials.yml index b119e29f41..a796ca7e13 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/materials.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/materials.yml @@ -160,6 +160,7 @@ - ClothMade - Gauze - RawMaterial + - ConstructionMaterial - type: Construction graph: WebObjects # not sure if I should either keep this here or just make another prototype. Will keep it here just in case. node: cloth @@ -236,6 +237,7 @@ tags: - ClothMade - RawMaterial + - ConstructionMaterial - type: Item heldPrefix: durathread @@ -296,6 +298,7 @@ tags: - Wooden - RawMaterial + - ConstructionMaterial - type: Extractable grindableSolutionName: wood - type: SolutionContainerManager diff --git a/Resources/Prototypes/Entities/Objects/Materials/parts.yml b/Resources/Prototypes/Entities/Objects/Materials/parts.yml index e39693c7e3..dd181fd8b1 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/parts.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/parts.yml @@ -19,6 +19,9 @@ behaviors: - !type:DoActsBehavior acts: [ "Destruction" ] + - type: Tag + tags: + - ConstructionMaterial - type: entity parent: PartBase @@ -100,6 +103,7 @@ - type: Tag tags: - RodMetal1 + - ConstructionMaterial - PartRodMetal #Sunrise-edit - type: Sprite state: rods @@ -115,44 +119,9 @@ - type: Tag tags: - RodMetal1 + - ConstructionMaterial - PartRodMetal #Sunrise-edit - type: Sprite state: rods - type: Stack count: 1 - -- type: entity - parent: PartRodMetal - id: PartRodMetalLingering0 - suffix: Lingering, 0 - components: - - type: Stack - lingering: true - count: 0 - -- type: entity - parent: FloorTileItemSteel - id: FloorTileItemSteelLingering0 - suffix: Lingering, 0 - components: - - type: Stack - lingering: true - count: 0 - -- type: entity - parent: FloorTileItemWhite - id: FloorTileItemWhiteLingering0 - suffix: Lingering, 0 - components: - - type: Stack - lingering: true - count: 0 - -- type: entity - parent: FloorTileItemDark - id: FloorTileItemDarkLingering0 - suffix: Lingering, 0 - components: - - type: Stack - lingering: true - count: 0 diff --git a/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml b/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml index 3047efd986..e9ecce206d 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml @@ -613,7 +613,7 @@ - type: entity name: passenger ID card - parent: IDCardStandard + parent: [IDCardStandard, BaseChameleon] id: AgentIDCard suffix: Agent components: @@ -630,9 +630,11 @@ - state: default - state: idpassenger - type: AgentIDCard + - type: UIRequiresLock - type: ActivatableUI key: enum.AgentIDCardUiKey.Key inHandsOnly: true + verbText: agent-id-open-ui-verb - type: Tag tags: - DoorBumpOpener diff --git a/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml b/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml index 22e3290788..aedebc640d 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml @@ -11,6 +11,9 @@ size: Tiny - type: Stack count: 1 + - type: Tag + tags: + - ConstructionMaterial - type: entity id: MicroManipulatorStockPart diff --git a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml index cb05e06105..48f6ec8384 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml @@ -240,25 +240,3 @@ - type: Residue residueAdjective: residue-slippery residueColor: residue-blue - -- type: entity - name: soap - id: SoapBorg # Intended for borg internals, not slippery or food, not a container for soap reagent - parent: BaseItem - description: A Nanotrasen brand bar of soap. Smells of plasma and machines. - components: - - type: Sprite - sprite: Objects/Specific/Janitorial/soap.rsi - layers: - - state: nt-4 - - type: Appearance - - type: Item - sprite: Objects/Specific/Janitorial/soap.rsi - storedRotation: -90 - - type: CleansForensics - - type: Residue - residueAdjective: residue-slippery - residueColor: residue-grey - - type: Tag - tags: - - Soap diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/healing.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/healing.yml index 334548e99e..c7f2fd0567 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Medical/healing.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/healing.yml @@ -63,15 +63,6 @@ stackType: Ointment count: 1 -- type: entity - id: Ointment10Lingering - parent: Ointment - suffix: 10, Lingering - components: - - type: Stack - lingering: true - count: 10 - - type: entity name: regenerative mesh description: Used to treat even the nastiest burns. Also effective against caustic burns. @@ -121,15 +112,6 @@ stackType: RegenerativeMesh count: 1 -- type: entity - parent: RegenerativeMesh - id: RegenerativeMeshLingering0 - suffix: 0, Lingering - components: - - type: Stack - lingering: true - count: 0 - - type: entity name: bruise pack description: A therapeutic gel pack and bandages designed to treat blunt-force trauma. @@ -177,15 +159,6 @@ stackType: Brutepack count: 1 -- type: entity - id: Brutepack10Lingering - parent: Brutepack - suffix: 10, Lingering - components: - - type: Stack - lingering: true - count: 10 - - type: entity name: medicated suture description: A suture soaked in medicine, treats blunt-force trauma effectively and closes wounds. @@ -235,15 +208,6 @@ stackType: MedicatedSuture count: 1 -- type: entity - parent: MedicatedSuture - id: MedicatedSutureLingering0 - suffix: 0, Lingering - components: - - type: Stack - lingering: true - count: 0 - - type: entity name: blood pack description: Contains a groundbreaking universal blood replacement created by Nanotrasen's advanced medical science. @@ -291,15 +255,6 @@ stackType: Bloodpack count: 1 -- type: entity - parent: Bloodpack - id: Bloodpack10Lingering - suffix: 10, Lingering - components: - - type: Stack - lingering: true - count: 10 - - type: entity parent: BaseHealingItem id: Tourniquet @@ -385,15 +340,6 @@ - type: Stack count: 1 -- type: entity - id: Gauze10Lingering - parent: Gauze - suffix: 10, Lingering - components: - - type: Stack - lingering: true - count: 10 - - type: entity name: aloe cream description: A topical cream for burns. diff --git a/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml b/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml index 5fb76e79b9..6b2a48ee31 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml @@ -46,7 +46,7 @@ - type: SelectableBorgModule - type: ContainerContainer containers: - provided_container: !type:Container { } + holding_container: !type:Container { } - type: entity parent: BaseAction @@ -422,12 +422,31 @@ - state: generic - state: icon-cables - type: ItemBorgModule - items: - - CableApcStackLingering10 - - CableMVStackLingering10 - - CableHVStackLingering10 - - WirecutterBorg # Sunrise-Edit - - trayScanner + hands: + - item: CableApcStack10 + hand: + emptyRepresentative: CableApcStack10 + emptyLabel: borg-slot-cables-empty + whitelist: + tags: + - CableCoil + - item: CableMVStack10 + hand: + emptyRepresentative: CableMVStack10 + emptyLabel: borg-slot-cables-empty + whitelist: + tags: + - CableCoil + - item: CableHVStack10 + hand: + emptyRepresentative: CableHVStack10 + emptyLabel: borg-slot-cables-empty + whitelist: + tags: + - CableCoil + - item: WirecutterBorg # Sunrise-Edit + - item: CrowbarBorg # Sunrise-Edit + - item: trayScanner - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: wire-module } @@ -442,11 +461,11 @@ - state: generic - state: icon-fire-extinguisher - type: ItemBorgModule - items: - - BorgFireExtinguisher - - BorgHandheldGPSBasic - - HandheldStationMapUnpowered - - HandHeldMassScannerBorg + hands: + - item: BorgFireExtinguisher + - item: BorgHandheldGPSBasic + - item: HandheldStationMapUnpowered + - item: HandHeldMassScannerBorg - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: extinguisher-module } @@ -460,13 +479,13 @@ - state: generic - state: icon-tools - type: ItemBorgModule - items: - - CrowbarBorg # Sunrise-Edit - - WrenchBorg # Sunrise-Edit - - ScrewdriverBorg # Sunrise-Edit - - WirecutterBorg # Sunrise-Edit - - Multitool - - WelderBorg # Sunrise-Edit + hands: + - item: CrowbarBorg # Sunrise-Edit + - item: WrenchBorg # Sunrise-Edit + - item: ScrewdriverBorg # Sunrise-Edit + - item: WirecutterBorg # Sunrise-Edit + - item: WelderBorg # Sunrise-Edit + - item: Multitool - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: tool-module } @@ -481,13 +500,13 @@ - state: cargo - state: icon-appraisal - type: ItemBorgModule - items: - - AppraisalTool - - Pen - - HandLabeler - - RubberStampApproved - - RubberStampDenied - - RadioHandheld + hands: + - item: AppraisalTool + - item: Pen + - item: HandLabeler + - item: RubberStampApproved + - item: RubberStampDenied + - item: RadioHandheld - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: appraisal-module } @@ -501,11 +520,11 @@ - state: cargo - state: icon-mining - type: ItemBorgModule - items: - - MiningDrill - - Shovel - - MineralScannerUnpowered - - BorgOreBag + hands: + - item: MiningDrill + - item: Shovel + - item: MineralScannerUnpowered + - item: BorgOreBag - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: mining-module } @@ -520,12 +539,12 @@ - state: cargo - state: icon-mining-adv - type: ItemBorgModule - items: - - MiningDrillDiamond - - WeaponPlasmaCutterBorg # Sunrise-Edit - - Shovel - - AdvancedMineralScannerUnpowered - - OreBagOfHolding + hands: + - item: MiningDrillDiamond + - item: Shovel + - item: AdvancedMineralScannerUnpowered + - item: OreBagOfHolding + - item: WeaponPlasmaCutterBorg # Sunrise-Edit - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: adv-mining-module } @@ -539,11 +558,11 @@ - state: cargo - state: icon-grappling-gun - type: ItemBorgModule - items: - - WeaponGrapplingGun - - BorgFireExtinguisher - - BorgHandheldGPSBasic - - HandHeldMassScannerBorg + hands: + - item: WeaponGrapplingGun + - item: BorgFireExtinguisher + - item: BorgHandheldGPSBasic + - item: HandHeldMassScannerBorg - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: grappling-module } @@ -559,16 +578,23 @@ - state: engineering - state: icon-tools-adv - type: ItemBorgModule - items: - - JawsOfLife - - PowerDrill - - Multitool - - WelderBorg # Sunrise-Edit - - Multitool - - RemoteSignallerAdvanced + hands: + - item: JawsOfLife + - item: PowerDrill + - item: WelderBorg # Sunrise-Edit + - item: Multitool + - item: RemoteSignallerAdvanced - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: adv-tools-module } +- type: entity + id: BorgModuleConstructionMaterialPlaceholder + parent: BaseItem + components: + - type: Sprite + sprite: Objects/Materials/Sheets/other.rsi + state: generic_materials + - type: entity id: BorgModuleConstruction parent: [ BaseBorgModuleEngineering, BaseProviderBorgModule ] @@ -579,16 +605,61 @@ - state: engineering - state: icon-construction - type: ItemBorgModule - items: - - SheetSteelLingering0 - - SheetGlassLingering0 - - SheetRGlassLingering0 - - SheetRPGlassLingering0 - - SheetPlasteelLingering0 - - PartRodMetalLingering0 - - FloorTileItemSteelLingering0 - - FloorTileItemWhiteLingering0 - - FloorTileItemDarkLingering0 + hands: + - hand: + emptyRepresentative: BorgModuleConstructionMaterialPlaceholder + emptyLabel: borg-slot-construction-empty + whitelist: + tags: + - ConstructionMaterial + - hand: + emptyRepresentative: BorgModuleConstructionMaterialPlaceholder + emptyLabel: borg-slot-construction-empty + whitelist: + tags: + - ConstructionMaterial + - hand: + emptyRepresentative: BorgModuleConstructionMaterialPlaceholder + emptyLabel: borg-slot-construction-empty + whitelist: + tags: + - ConstructionMaterial + - hand: + emptyRepresentative: DoorElectronics + emptyLabel: borg-slot-circuitboards-empty + whitelist: + components: + - Circuitboard + - hand: + emptyRepresentative: MicroManipulatorStockPart + emptyLabel: borg-slot-construction-empty + whitelist: + tags: + - ConstructionMaterial + - hand: + emptyRepresentative: SpaceHeaterFlatpack + emptyLabel: borg-slot-flatpacks-empty + whitelist: + components: + - Flatpack + - hand: + emptyRepresentative: FloorTileItemSteel + emptyLabel: borg-slot-tiles-empty + whitelist: + components: + - FloorTile + - hand: + emptyRepresentative: FloorTileItemWhite + emptyLabel: borg-slot-tiles-empty + whitelist: + components: + - FloorTile + - hand: + emptyRepresentative: FloorTileItemDark + emptyLabel: borg-slot-tiles-empty + whitelist: + components: + - FloorTile - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: construction-module } @@ -602,13 +673,13 @@ - state: engineering - state: icon-rcd - type: ItemBorgModule - items: - - RCDRecharging - - BorgFireExtinguisher - - BorgHandheldGPSBasic - - GasAnalyzer - - HolofanProjectorBorg - - GeigerCounter + hands: + - item: RCDRecharging + - item: BorgFireExtinguisher + - item: BorgHandheldGPSBasic + - item: GasAnalyzer + - item: HolofanProjectorBorg + - item: GeigerCounter - AtmosAlertsMonitorUnpowered - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: rcd-module } @@ -624,11 +695,17 @@ - state: janitor - state: icon-light-replacer - type: ItemBorgModule - items: - - LightReplacer - - CrowbarBorg # Sunrise-Edit - - ScrewdriverBorg # Sunrise-Edit - - SoapBorg + hands: + - item: LightReplacer + - item: BorgTrashBag + - item: Plunger + - item: SoapNT + hand: + emptyLabel: borg-slot-soap-empty + emptyRepresentative: SoapNT + whitelist: + tags: + - Soap - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: light-replacer-module } @@ -642,12 +719,11 @@ - state: janitor - state: icon-mop - type: ItemBorgModule - items: - - MopItem - - BorgBucket # Sunrise-Edit - - BorgTrashBag # Sunrise-Edit - - BorgSprayBottle - - HoloprojectorJanitorBorg # Sunrise-Edit + hands: + - item: MopItem + - item: BorgBucket + - item: BorgSprayBottle + - item: HoloprojectorJanitorBorg # Sunrise-Edit - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: cleaning-module } @@ -662,14 +738,18 @@ - state: janitor - state: icon-mop-adv - type: ItemBorgModule - items: - - AdvMopItem - - BorgBucket # Sunrise-Edit - - BorgTrashBag # Sunrise-Edit - - HoloprojectorJanitorBorg # Sunrise-Edit - - SprayBottleSpaceCleanerBorg # Sunrise-Edit - - BorgDropper - - BorgBeaker + hands: + #- item: AdvMopItem # Sunrise-Edit + - item: BorgMegaSprayBottle + - item: HoloprojectorJanitorBorg # Sunrise-Edit + - item: BorgDropper + - item: Beaker + hand: + emptyLabel: borg-slot-beakers-empty + emptyRepresentative: Beaker + whitelist: + tags: + - GlassBeaker - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: adv-cleaning-module } @@ -684,9 +764,9 @@ - state: medical - state: icon-diagnosis - type: ItemBorgModule - items: - - HandheldHealthAnalyzerUnpowered - - ClothingNeckStethoscope + hands: + - item: HandheldHealthAnalyzerUnpowered + - item: ClothingNeckStethoscope - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: diagnosis-module } @@ -700,14 +780,36 @@ - state: medical - state: icon-treatment - type: ItemBorgModule - items: - #- HandheldHealthAnalyzerUnpowered - - Gauze10Lingering - - Brutepack10Lingering - - Ointment10Lingering - - Bloodpack10Lingering - - RegenerativeMeshLingering0 - - MedicatedSutureLingering0 + hands: + #- item: HandheldHealthAnalyzerUnpowered + - item: Gauze + hand: + emptyLabel: borg-slot-topicals-empty + emptyRepresentative: Gauze + whitelist: + components: + - Healing + - item: Brutepack + hand: + emptyLabel: borg-slot-topicals-empty + emptyRepresentative: Brutepack + whitelist: + components: + - Healing + - item: Ointment + hand: + emptyLabel: borg-slot-topicals-empty + emptyRepresentative: Ointment + whitelist: + components: + - Healing + - item: Bloodpack + hand: + emptyLabel: borg-slot-topicals-empty + emptyRepresentative: Bloodpack + whitelist: + components: + - Healing - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: treatment-module } @@ -721,12 +823,12 @@ - state: medical - state: icon-defib - type: ItemBorgModule - items: - #- HandheldHealthAnalyzerUnpowered - - DefibrillatorOneHandedUnpowered - #- BorgFireExtinguisher - - BorgHandheldGPSBasic - - HandLabeler + hands: + #- item: HandheldHealthAnalyzerUnpowered + - item: DefibrillatorOneHandedUnpowered + #- item: BorgFireExtinguisher + - item: BorgHandheldGPSBasic + - item: HandLabeler - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: defib-module } @@ -740,13 +842,32 @@ - state: medical - state: icon-chem - type: ItemBorgModule - items: - - HypoBorgMedical # Sunrise-Edit - - HyposprayMedical - - Syringe - - BorgVial - - BorgVial - - BorgVial + hands: + - item: HypoBorgMedical # Sunrise-Edit + - item: HyposprayMedical # Sunrise-Edit + - item: Syringe + - item: BorgDropper + - item: BaseChemistryEmptyVial + hand: + emptyLabel: borg-slot-small-containers-empty + emptyRepresentative: BaseChemistryEmptyVial + whitelist: + components: + - FitsInDispenser + - item: BaseChemistryEmptyVial + hand: + emptyLabel: borg-slot-small-containers-empty + emptyRepresentative: BaseChemistryEmptyVial + whitelist: + components: + - FitsInDispenser + - item: BaseChemistryEmptyVial + hand: + emptyLabel: borg-slot-small-containers-empty + emptyRepresentative: BaseChemistryEmptyVial + whitelist: + components: + - FitsInDispenser - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: chem-module } @@ -761,13 +882,38 @@ - state: medical - state: icon-chemist - type: ItemBorgModule - items: - - HypoBorgMedicalAdvanced # Sunrise-Edit - - Hypospray - - SyringeBluespace - - BorgBeaker - - BorgBeaker - - BorgBeaker + hands: + - item: HypoBorgMedicalAdvanced # Sunrise-Edit + - item: BorgHypo + - item: Syringe + - item: BorgDropper + - item: Beaker + hand: + emptyLabel: borg-slot-chemical-containers-empty + emptyRepresentative: Beaker + whitelist: + components: + - FitsInDispenser + tags: + - ChemDispensable + - item: Beaker + hand: + emptyLabel: borg-slot-chemical-containers-empty + emptyRepresentative: Beaker + whitelist: + components: + - FitsInDispenser + tags: + - ChemDispensable + - item: Beaker + hand: + emptyLabel: borg-slot-chemical-containers-empty + emptyRepresentative: Beaker + whitelist: + components: + - FitsInDispenser + tags: + - ChemDispensable - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: adv-chem-module } @@ -783,13 +929,19 @@ - state: science - state: icon-artifacts - type: ItemBorgModule - items: - - NodeScanner - - SprayBottle - - GasAnalyzer - - BorgDropper - - BorgVial - - GeigerCounter + hands: + - item: NodeScanner + - item: SprayBottle + - item: GasAnalyzer + - item: BorgDropper + - item: BaseChemistryEmptyVial + hand: + emptyLabel: borg-slot-small-containers-empty + emptyRepresentative: BaseChemistryEmptyVial + whitelist: + components: + - FitsInDispenser + - item: GeigerCounter - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: node-scanner-module } @@ -803,13 +955,23 @@ - state: science - state: icon-anomalies - type: ItemBorgModule - items: - - AnomalyScanner - - AnomalyLocatorUnpowered - - AnomalyLocatorWideUnpowered - - HandLabeler - - SheetRGlassLingering0 - - SheetRPGlassLingering0 + hands: + - item: AnomalyScanner + - item: AnomalyLocatorUnpowered + - item: AnomalyLocatorWideUnpowered + - item: HandLabeler + - hand: + emptyRepresentative: SheetRGlass + emptyLabel: borg-slot-construction-empty + whitelist: + tags: + - ConstructionMaterial + - hand: + emptyRepresentative: SheetRPGlass + emptyLabel: borg-slot-construction-empty + whitelist: + tags: + - ConstructionMaterial - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: anomaly-module } @@ -824,14 +986,43 @@ - state: service - state: icon-pen - type: ItemBorgModule - items: - - Pen - #- BooksBag (Add back when hand whitelisting exists, at the moment they can only use it like an orebag.) - - HandLabeler - - RubberStampApproved - - RubberStampDenied - - BorgDropper - - BorgVial + hands: + - item: Pen + - item: BooksBag + - hand: + emptyLabel: borg-slot-documents-empty + emptyRepresentative: BookBase + whitelist: + tags: + - Book + - Dice + - Document + - Figurine + - TabletopBoard + - Write + - hand: + emptyLabel: borg-slot-documents-empty + emptyRepresentative: Paper + whitelist: + tags: + - Book + - Dice + - Document + - Figurine + - TabletopBoard + - Write + - item: HandLabeler + - item: RubberStampApproved + - item: RubberStampDenied + - item: BorgDropper + - item: DrinkShaker + hand: + emptyLabel: borg-slot-small-containers-empty + emptyRepresentative: DrinkShaker + whitelist: + components: + - FitsInDispenser + - item: BarSpoon - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: service-module } @@ -845,10 +1036,28 @@ - state: service - state: icon-musique - type: ItemBorgModule - items: - - SynthesizerInstrument - - ElectricGuitarInstrument - - SaxophoneInstrument + hands: + - item: SynthesizerInstrument + hand: + emptyLabel: borg-slot-instruments-empty + emptyRepresentative: SynthesizerInstrument + whitelist: + components: + - Instrument + - item: ElectricGuitarInstrument + hand: + emptyLabel: borg-slot-instruments-empty + emptyRepresentative: ElectricGuitarInstrument + whitelist: + components: + - Instrument + - item: SaxophoneInstrument + hand: + emptyLabel: borg-slot-instruments-empty + emptyRepresentative: SaxophoneInstrument + whitelist: + components: + - Instrument - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: musical-module } @@ -862,11 +1071,11 @@ - state: service - state: icon-gardening - type: ItemBorgModule - items: - - HydroponicsToolMiniHoe - - HydroponicsToolSpade - - HydroponicsToolClippers - - Bucket + hands: + - item: HydroponicsToolMiniHoe + - item: HydroponicsToolSpade + - item: HydroponicsToolClippers + - item: Bucket - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: gardening-module } @@ -880,10 +1089,10 @@ - state: service - state: icon-harvesting - type: ItemBorgModule - items: - - HydroponicsToolScythe - - HydroponicsToolHatchet - - PlantBag + hands: + - item: HydroponicsToolScythe + - item: HydroponicsToolHatchet + - item: PlantBag - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: harvesting-module } @@ -897,10 +1106,10 @@ - state: service - state: icon-clown - type: ItemBorgModule - items: - - BikeHorn - - ClownRecorder - - BikeHornInstrument + hands: + - item: BikeHorn + - item: ClownRecorder + - item: BikeHornInstrument - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: clowning-module } @@ -915,12 +1124,12 @@ - state: service - state: icon-clown-adv - type: ItemBorgModule - items: - - HoloprojectorClownBorg - - BorgLauncherCreamPie - - ClownRecorder - - PushHorn - - BikeHornInstrument + hands: + - item: HoloprojectorClownBorg + - item: BorgLauncherCreamPie + - item: ClownRecorder + - item: PushHorn + - item: BikeHornInstrument - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: adv-clowning-module } @@ -935,9 +1144,9 @@ - state: syndicate - state: icon-syndicate - type: ItemBorgModule - items: - - WeaponPistolEchis - - EnergyDaggerLoud + hands: + - item: WeaponPistolEchis + - item: EnergyDaggerLoud - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: syndicate-weapon-module } - type: Item @@ -972,10 +1181,11 @@ - state: syndicate - state: icon-syndicate - type: ItemBorgModule - items: - - Crowbar - - AccessBreaker - - PinpointerSyndicateNuclear + hands: + - item: CrowbarBorg # Sunrise-Edit + - item: Emag + - item: AccessBreaker + - item: PinpointerSyndicateNuclear - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: syndicate-operative-module } @@ -990,9 +1200,9 @@ - state: syndicate - state: icon-syndicate - type: ItemBorgModule - items: - - CyborgEnergySwordDouble - - PinpointerSyndicateNuclear + hands: + - item: CyborgEnergySwordDouble + - item: PinpointerSyndicateNuclear - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: syndicate-esword-module } @@ -1007,9 +1217,9 @@ - state: syndicate - state: icon-syndicate - type: ItemBorgModule - items: - - WeaponLightMachineGunL6C - - PinpointerSyndicateNuclear + hands: + - item: WeaponLightMachineGunL6C + - item: PinpointerSyndicateNuclear - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: syndicate-l6c-module } @@ -1024,8 +1234,8 @@ - state: syndicateborgbomb - state: icon-bomb - type: ItemBorgModule - items: - - SelfDestructSeq + hands: + - item: SelfDestructSeq - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: syndicate-martyr-module } - type: Item @@ -1061,10 +1271,10 @@ - state: xenoborg_generic - state: icon-xenoborg-basic - type: ItemBorgModule - items: - - MaterialBag - - PinpointerMothership - - HandheldGPSBasic + hands: + - item: MaterialBag + - item: PinpointerMothership + - item: HandheldGPSBasic - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-basic-module } @@ -1079,13 +1289,13 @@ - state: xenoborg_generic - state: icon-xenoborg-tools - type: ItemBorgModule - items: - - Crowbar - - Wrench - - Screwdriver - - Wirecutter - - Multitool - - RefuelingWelder + hands: + - item: Crowbar + - item: Wrench + - item: Screwdriver + - item: Wirecutter + - item: Multitool + - item: RefuelingWelder - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-tool-module } @@ -1100,8 +1310,8 @@ - state: xenoborg_engi - state: icon-xenoborg-access-breaker - type: ItemBorgModule - items: - - AccessBreaker + hands: + - item: AccessBreaker - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-access-breaker-module } @@ -1116,8 +1326,8 @@ - state: xenoborg_engi - state: icon-xenoborg-fire-extinguisher - type: ItemBorgModule - items: - - SelfRechargingFireExtinguisher + hands: + - item: SelfRechargingFireExtinguisher - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-extinguisher-module } @@ -1132,8 +1342,8 @@ - state: xenoborg_heavy - state: icon-xenoborg-jammer - type: ItemBorgModule - items: - - XenoborgRadioJammer + hands: + - item: XenoborgRadioJammer - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-jammer-module } @@ -1148,8 +1358,8 @@ - state: xenoborg_heavy - state: icon-xenoborg-laser - type: ItemBorgModule - items: - - XenoborgLaserGun + hands: + - item: XenoborgLaserGun - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-laser-module } @@ -1164,8 +1374,8 @@ - state: xenoborg_heavy - state: icon-xenoborg-laser2 - type: ItemBorgModule - items: - - XenoborgHeavyLaserGun + hands: + - item: XenoborgHeavyLaserGun - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-laser2-module } @@ -1180,12 +1390,12 @@ - state: xenoborg_scout - state: icon-xenoborg-space-movement - type: ItemBorgModule - items: - - HandheldGPSBasic - - HandHeldMassScannerBorg - - HandheldStationMapUnpowered - - WeaponGrapplingGun - - JetpackXenoborg + hands: + - item: HandheldGPSBasic + - item: HandHeldMassScannerBorg + - item: HandheldStationMapUnpowered + - item: WeaponGrapplingGun + - item: JetpackXenoborg - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-space-movement-module } @@ -1200,9 +1410,9 @@ - state: xenoborg_scout - state: icon-xenoborg-sword - type: ItemBorgModule - items: - - KukriKnife - - JetpackXenoborg + hands: + - item: KukriKnife + - item: JetpackXenoborg - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-sword-module } @@ -1217,9 +1427,9 @@ - state: xenoborg_scout - state: icon-xenoborg-sword2 - type: ItemBorgModule - items: - - EnergyDaggerLoudBlue - - JetpackXenoborg + hands: + - item: EnergyDaggerLoudBlue + - item: JetpackXenoborg - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-sword2-module } @@ -1234,8 +1444,8 @@ - state: xenoborg_stealth - state: icon-xenoborg-hypo - type: ItemBorgModule - items: - - NocturineHypo + hands: + - item: NocturineHypo - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-hypo-module } @@ -1250,8 +1460,8 @@ - state: xenoborg_stealth - state: icon-xenoborg-projector - type: ItemBorgModule - items: - - ChameleonProjector + hands: + - item: ChameleonProjector - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-projector-module } @@ -1266,8 +1476,8 @@ - state: xenoborg_stealth - state: icon-xenoborg-cloak - type: ItemBorgModule - items: - - CloakingDevice + hands: + - item: CloakingDevice - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-eye-module } @@ -1282,7 +1492,7 @@ - state: xenoborg_stealth - state: icon-xenoborg-cloak2 - type: ItemBorgModule - items: - - SuperCloakingDevice + hands: + - item: SuperCloakingDevice - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-eye2-module } diff --git a/Resources/Prototypes/Entities/Objects/Specific/chemistry-vials.yml b/Resources/Prototypes/Entities/Objects/Specific/chemistry-vials.yml index bc8207b673..21b3742d02 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/chemistry-vials.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/chemistry-vials.yml @@ -104,64 +104,6 @@ solution: beaker - type: DnaSubstanceTrace -- type: entity - parent: BaseItem - id: BorgVial - name: integrated vial - description: An internal compartment installed into a cyborg. Rated for 30 units of any liquid. - components: - # All this shit is here to avoid inheriting breakable, since borgs can't replace broken vials. - - type: Sprite - sprite: Objects/Specific/Chemistry/vial.rsi - layers: - - state: vial-1 - - state: vial-1-1 - map: ["enum.SolutionContainerLayers.Fill"] - visible: false - - type: Appearance - - type: SolutionContainerVisuals - maxFillLevels: 6 - fillBaseName: vial-1- - inHandsMaxFillLevels: 4 - inHandsFillBaseName: -fill- - - type: Drink - solution: beaker - - type: SolutionContainerManager - solutions: - beaker: - maxVol: 30 - - type: MixableSolution - solution: beaker - - type: RefillableSolution - solution: beaker - - type: DrainableSolution - solution: beaker - - type: ExaminableSolution - solution: beaker - exactVolume: true - - type: DrawableSolution - solution: beaker - - type: SolutionTransfer - maxTransferAmount: 30 - canChangeTransferAmount: true - - type: SolutionItemStatus - solution: beaker - - type: UserInterface - interfaces: - enum.TransferAmountUiKey.Key: - type: TransferAmountBoundUserInterface - - type: Item - size: Tiny - sprite: Objects/Specific/Chemistry/vial.rsi - shape: - - 0,0,0,0 - - type: MeleeWeapon - soundNoDamage: - path: "/Audio/Effects/Fluids/splat.ogg" - damage: - types: - Blunt: 0 - - type: entity id: VestineChemistryVial parent: BaseChemistryEmptyVial diff --git a/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml b/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml index c5d5a5206e..b52f3a7aa2 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml @@ -95,65 +95,6 @@ price: 30 - type: DnaSubstanceTrace -- type: entity - parent: BaseItem - id: BorgBeaker - name: integrated beaker - description: An internal compartment installed into a cyborg. Rated for 50 units of any liquid. - components: - # 3 morbillion components are to avoid inheriting breakable since borgs can't replace beakers. - - type: Tag - tags: - - GlassBeaker - - type: Sprite - sprite: Objects/Specific/Chemistry/beaker.rsi - layers: - - state: beaker - - state: beaker1 - map: ["enum.SolutionContainerLayers.Fill"] - visible: false - - type: Item - sprite: Objects/Specific/Chemistry/beaker.rsi - - type: MeleeWeapon - soundNoDamage: - path: "/Audio/Effects/Fluids/splat.ogg" - damage: - types: - Blunt: 0 - - type: SolutionContainerManager - solutions: - beaker: - maxVol: 50 - - type: MixableSolution - solution: beaker - - type: FitsInDispenser - solution: beaker - - type: RefillableSolution - solution: beaker - - type: DrainableSolution - solution: beaker - - type: ExaminableSolution - solution: beaker - exactVolume: true - - type: DrawableSolution - solution: beaker - - type: InjectableSolution - solution: beaker - - type: SolutionTransfer - canChangeTransferAmount: true - - type: SolutionItemStatus - solution: beaker - - type: UserInterface - interfaces: - enum.TransferAmountUiKey.Key: - type: TransferAmountBoundUserInterface - - type: Drink - solution: beaker - - type: Appearance - - type: SolutionContainerVisuals - maxFillLevels: 6 - fillBaseName: beaker - - type: entity parent: BaseItem id: BaseBeakerMetallic diff --git a/Resources/Prototypes/Entities/Objects/Specific/locks.yml b/Resources/Prototypes/Entities/Objects/Specific/locks.yml new file mode 100644 index 0000000000..296b9fd7d9 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Specific/locks.yml @@ -0,0 +1,24 @@ +- type: entity + id: VoiceLock + abstract: true + components: + - type: Lock + locked: false + showLockVerbs: false + showExamine: false + lockOnClick: false + unlockOnClick: false + useAccess: false + unlockingSound: null # TODO: Maybe add sounds but just to the user? + lockingSound: null + lockTime: 0 + unlockTime: 0 + - type: TriggerOnVoice + listenRange: 2 # more fun + startRecordingVerb: voice-trigger-lock-verb-record + recordingVerbMessage: voice-trigger-lock-verb-message + inspectUninitializedLoc: voice-trigger-lock-on-uninitialized + inspectInitializedLoc: voice-trigger-lock-on-examine + - type: LockOnTrigger + - type: ActiveListener + - type: VoiceTriggerLock diff --git a/Resources/Prototypes/Entities/Objects/Tools/cable_coils.yml b/Resources/Prototypes/Entities/Objects/Tools/cable_coils.yml index 3bed807e50..671e3a691d 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/cable_coils.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/cable_coils.yml @@ -82,15 +82,6 @@ - type: Stack count: 10 -- type: entity - parent: CableHVStack10 - id: CableHVStackLingering10 - suffix: Lingering, 10 - components: - - type: Stack - lingering: true - count: 10 - - type: entity parent: CableHVStack id: CableHVStack1 @@ -147,15 +138,6 @@ - type: Stack count: 10 -- type: entity - parent: CableMVStack10 - id: CableMVStackLingering10 - suffix: Lingering, 10 - components: - - type: Stack - lingering: true - count: 10 - - type: entity parent: CableMVStack id: CableMVStack1 @@ -223,15 +205,6 @@ - type: Stack count: 10 -- type: entity - parent: CableApcStack10 - id: CableApcStackLingering10 - suffix: Lingering, 10 - components: - - type: Stack - lingering: true - count: 10 - - type: entity parent: CableApcStack id: CableApcStack1 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/cane.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/cane.yml index cbf437d0b2..2847b723d8 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/cane.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/cane.yml @@ -53,7 +53,7 @@ - type: DisarmMalus - type: entity - parent: Cane + parent: [Cane, VoiceLock] id: CaneSheath suffix: Empty components: @@ -69,6 +69,9 @@ interfaces: enum.StorageUiKey.Key: type: StorageBoundUserInterface + - type: ItemSlotsLock + slots: + - item - type: ItemSlots slots: item: diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml index 1e37968360..0230de72c6 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml @@ -184,7 +184,7 @@ - type: entity name: pen - parent: BaseMeleeWeaponEnergy + parent: [BaseMeleeWeaponEnergy, VoiceLock] id: EnergyDagger suffix: E-Dagger description: 'A dark ink pen.' @@ -247,6 +247,16 @@ damage: types: Blunt: 1 + - type: EmitSoundOnUse + sound: + path: /Audio/Items/pen_click.ogg + params: + volume: -4 + maxDistance: 2 + - type: UseDelay + delay: 1.5 + - type: ItemToggleRequiresLock # TODO: FIX THIS VERB IS COOKED + lockedPopup: null - type: Tag tags: - Write diff --git a/Resources/Prototypes/Entities/StatusEffects/movement.yml b/Resources/Prototypes/Entities/StatusEffects/movement.yml index a6cbd20724..71142af434 100644 --- a/Resources/Prototypes/Entities/StatusEffects/movement.yml +++ b/Resources/Prototypes/Entities/StatusEffects/movement.yml @@ -58,10 +58,3 @@ - type: StatusEffectAlert alert: Stun - type: StunnedStatusEffect - -- type: entity - parent: MobStandStatusEffectBase - id: StatusEffectKnockdown - name: knocked down - components: - - type: KnockdownStatusEffect diff --git a/Resources/Prototypes/Entities/Structures/Storage/Canisters/gas_canisters.yml b/Resources/Prototypes/Entities/Structures/Storage/Canisters/gas_canisters.yml index 1983209b4a..ac2b768eb2 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Canisters/gas_canisters.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Canisters/gas_canisters.yml @@ -37,7 +37,9 @@ 3: { state: can-o3, shader: "unshaded" } - type: ActivatableUI key: enum.GasCanisterUiKey.Key - - type: ActivatableUIRequiresLock + - type: UIRequiresLock + userInterfaceKeys: + - enum.GasCanisterUiKey.Key - type: UserInterface interfaces: enum.GasCanisterUiKey.Key: diff --git a/Resources/Prototypes/GameRules/roundstart.yml b/Resources/Prototypes/GameRules/roundstart.yml index 54a2e616ad..ce9cbb4695 100644 --- a/Resources/Prototypes/GameRules/roundstart.yml +++ b/Resources/Prototypes/GameRules/roundstart.yml @@ -265,6 +265,8 @@ - type: GameRule minPlayers: 25 - type: AntagSelection + agentName: changeling-round-end-agent-name + selectionTime: IntraPlayerSpawn definitions: - prefRoles: [ Changeling ] max: 3 @@ -282,6 +284,9 @@ - ActionRetractableItemArmBlade # Temporary addition, will inevitably be a purchasable in the bio-store mindRoles: - MindRoleChangeling + - type: AntagObjectives + objectives: + - ChangelingSurviveObjective - type: entity id: Revolutionary diff --git a/Resources/Prototypes/Objectives/changeling.yml b/Resources/Prototypes/Objectives/changeling.yml new file mode 100644 index 0000000000..a1a4612217 --- /dev/null +++ b/Resources/Prototypes/Objectives/changeling.yml @@ -0,0 +1,23 @@ +- type: entity + abstract: true + parent: BaseObjective + id: BaseChangelingObjective + components: + - type: Objective + issuer: objective-issuer-changeling + difficulty: 1 + - type: RoleRequirement + roles: + - ChangelingRole + +- type: entity + parent: [BaseChangelingObjective, BaseSurviveObjective] + id: ChangelingSurviveObjective + name: Survive. + description: We must stay alive at all cost. + components: + - type: Objective + difficulty: 1 + icon: + sprite: Mobs/Species/Human/organs.rsi + state: heart-on diff --git a/Resources/Prototypes/Objectives/traitor.yml b/Resources/Prototypes/Objectives/traitor.yml index 6fa814e439..81b2651c9b 100644 --- a/Resources/Prototypes/Objectives/traitor.yml +++ b/Resources/Prototypes/Objectives/traitor.yml @@ -164,8 +164,8 @@ - !type:TargetObjectiveMindFilter blacklist: components: - - RandomTraitorAlive - - RandomTraitorProgress + - HelpProgressCondition + - KeepAliveCondition - type: entity parent: [BaseTraitorSocialObjective, BaseHelpProgressObjective] @@ -193,8 +193,8 @@ - !type:TargetObjectiveMindFilter blacklist: components: - - RandomTraitorAlive - - RandomTraitorProgress + - HelpProgressCondition + - KeepAliveCondition # steal diff --git a/Resources/Prototypes/Roles/MindRoles/mind_roles.yml b/Resources/Prototypes/Roles/MindRoles/mind_roles.yml index 95d49c1b83..d387903ec4 100644 --- a/Resources/Prototypes/Roles/MindRoles/mind_roles.yml +++ b/Resources/Prototypes/Roles/MindRoles/mind_roles.yml @@ -320,3 +320,5 @@ roleType: SoloAntagonist subtype: role-subtype-changeling - type: ChangelingRole + - type: RoleBriefing + briefing: changeling-briefing diff --git a/Resources/Prototypes/chameleon.yml b/Resources/Prototypes/chameleon.yml new file mode 100644 index 0000000000..6969380621 --- /dev/null +++ b/Resources/Prototypes/chameleon.yml @@ -0,0 +1,15 @@ +# for clothing that can be toggled, like magboots +- type: entity + parent: VoiceLock + abstract: true + id: BaseChameleon + components: + - type: UIRequiresLock + userInterfaceKeys: + - enum.ChameleonUiKey.Key + accessDeniedSound: null + popup: null + - type: UserInterface + interfaces: + enum.ChameleonUiKey.Key: + type: ChameleonBoundUserInterface diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index e7358245a3..7423987142 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -347,6 +347,9 @@ - type: Tag id: ComputerTelevisionCircuitboard +- type: Tag + id: ConstructionMaterial + - type: Tag id: ConveyorAssembly diff --git a/Resources/Textures/Mobs/Species/Moth/displacement.rsi/hand.png b/Resources/Textures/Mobs/Species/Moth/displacement.rsi/hand.png index f632f80d19..5443655da6 100644 Binary files a/Resources/Textures/Mobs/Species/Moth/displacement.rsi/hand.png and b/Resources/Textures/Mobs/Species/Moth/displacement.rsi/hand.png differ diff --git a/Resources/Textures/Mobs/Species/Moth/displacement.rsi/jumpsuit-female.png b/Resources/Textures/Mobs/Species/Moth/displacement.rsi/jumpsuit-female.png index f4cf6def03..93dada15f7 100644 Binary files a/Resources/Textures/Mobs/Species/Moth/displacement.rsi/jumpsuit-female.png and b/Resources/Textures/Mobs/Species/Moth/displacement.rsi/jumpsuit-female.png differ diff --git a/Resources/Textures/Mobs/Species/Moth/displacement.rsi/jumpsuit-male.png b/Resources/Textures/Mobs/Species/Moth/displacement.rsi/jumpsuit-male.png index c3be6e6d9d..b78c10e0c1 100644 Binary files a/Resources/Textures/Mobs/Species/Moth/displacement.rsi/jumpsuit-male.png and b/Resources/Textures/Mobs/Species/Moth/displacement.rsi/jumpsuit-male.png differ diff --git a/Resources/Textures/Mobs/Species/Moth/displacement.rsi/outerclothing.png b/Resources/Textures/Mobs/Species/Moth/displacement.rsi/outerclothing.png index e4939fdb79..db14be441c 100644 Binary files a/Resources/Textures/Mobs/Species/Moth/displacement.rsi/outerclothing.png and b/Resources/Textures/Mobs/Species/Moth/displacement.rsi/outerclothing.png differ diff --git a/Resources/Textures/Mobs/Species/Moth/displacement.rsi/shoes.png b/Resources/Textures/Mobs/Species/Moth/displacement.rsi/shoes.png index eb92ec29e3..2e576b604f 100644 Binary files a/Resources/Textures/Mobs/Species/Moth/displacement.rsi/shoes.png and b/Resources/Textures/Mobs/Species/Moth/displacement.rsi/shoes.png differ