diff --git a/Content.Client/Atmos/UI/GasPressurePumpBoundUserInterface.cs b/Content.Client/Atmos/UI/GasPressurePumpBoundUserInterface.cs index a80959b7b4..3c3d8f1509 100644 --- a/Content.Client/Atmos/UI/GasPressurePumpBoundUserInterface.cs +++ b/Content.Client/Atmos/UI/GasPressurePumpBoundUserInterface.cs @@ -1,8 +1,7 @@ -using Content.Shared.Atmos; +using Content.Shared.Atmos; using Content.Shared.Atmos.Components; using Content.Shared.Atmos.Piping.Binary.Components; using Content.Shared.IdentityManagement; -using Content.Shared.Localizations; using JetBrains.Annotations; using Robust.Client.UserInterface; diff --git a/Content.Client/Cargo/Systems/CargoSystem.Telepad.cs b/Content.Client/Cargo/Systems/CargoSystem.Telepad.cs index 50d079737d..312c4e8019 100644 --- a/Content.Client/Cargo/Systems/CargoSystem.Telepad.cs +++ b/Content.Client/Cargo/Systems/CargoSystem.Telepad.cs @@ -67,8 +67,10 @@ public sealed partial class CargoSystem if (!Resolve(uid, ref sprite)) return; + if (!TryComp(uid, out var player)) + return; + _appearance.TryGetData(uid, CargoTelepadVisuals.State, out var state); - AnimationPlayerComponent? player = null; switch (state) { @@ -76,7 +78,7 @@ public sealed partial class CargoSystem if (_player.HasRunningAnimation(uid, TelepadBeamKey)) return; _player.Stop(uid, player, TelepadIdleKey); - _player.Play(uid, player, CargoTelepadBeamAnimation, TelepadBeamKey); + _player.Play((uid, player), CargoTelepadBeamAnimation, TelepadBeamKey); break; case CargoTelepadState.Unpowered: sprite.LayerSetVisible(CargoTelepadLayers.Beam, false); @@ -90,7 +92,7 @@ public sealed partial class CargoSystem _player.HasRunningAnimation(uid, player, TelepadBeamKey)) return; - _player.Play(uid, player, CargoTelepadIdleAnimation, TelepadIdleKey); + _player.Play((uid, player), CargoTelepadIdleAnimation, TelepadIdleKey); break; } } diff --git a/Content.Client/Chat/UI/EmotesMenu.xaml b/Content.Client/Chat/UI/EmotesMenu.xaml deleted file mode 100644 index 845b631617..0000000000 --- a/Content.Client/Chat/UI/EmotesMenu.xaml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Content.Client/Chat/UI/EmotesMenu.xaml.cs b/Content.Client/Chat/UI/EmotesMenu.xaml.cs index 90832fac58..e69de29bb2 100644 --- a/Content.Client/Chat/UI/EmotesMenu.xaml.cs +++ b/Content.Client/Chat/UI/EmotesMenu.xaml.cs @@ -1,111 +0,0 @@ -using System.Numerics; -using Content.Client.UserInterface.Controls; -using Content.Shared.Chat.Prototypes; -using Content.Shared.Speech; -using Content.Shared.Whitelist; -using Robust.Client.AutoGenerated; -using Robust.Client.GameObjects; -using Robust.Client.UserInterface.Controls; -using Robust.Client.UserInterface.XAML; -using Robust.Shared.Player; -using Robust.Shared.Prototypes; - -namespace Content.Client.Chat.UI; - -[GenerateTypedNameReferences] -public sealed partial class EmotesMenu : RadialMenu -{ - [Dependency] private readonly EntityManager _entManager = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly ISharedPlayerManager _playerManager = default!; - - public event Action>? OnPlayEmote; - - public EmotesMenu() - { - IoCManager.InjectDependencies(this); - RobustXamlLoader.Load(this); - - var spriteSystem = _entManager.System(); - var whitelistSystem = _entManager.System(); - - var main = FindControl("Main"); - - var emotes = _prototypeManager.EnumeratePrototypes(); - foreach (var emote in emotes) - { - var player = _playerManager.LocalSession?.AttachedEntity; - if (emote.Category == EmoteCategory.Invalid || emote.Category == EmoteCategory.Verb || - emote.ChatTriggers.Count == 0 || - !(player.HasValue && whitelistSystem.IsWhitelistPassOrNull(emote.Whitelist, player.Value)) || - whitelistSystem.IsBlacklistPass(emote.Blacklist, player.Value)) - continue; - - if (!emote.Available && - _entManager.TryGetComponent(player.Value, out var speech) && - !speech.AllowedEmotes.Contains(emote.ID)) - continue; - - var parent = FindControl(emote.Category.ToString()); - - var button = new EmoteMenuButton - { - SetSize = new Vector2(64f, 64f), - ToolTip = Loc.GetString(emote.Name), - ProtoId = emote.ID, - }; - - var tex = new TextureRect - { - VerticalAlignment = VAlignment.Center, - HorizontalAlignment = HAlignment.Center, - Texture = spriteSystem.Frame0(emote.Icon), - TextureScale = new Vector2(2f, 2f), - }; - - button.AddChild(tex); - parent.AddChild(button); - foreach (var child in main.Children) - { - if (child is not RadialMenuTextureButton castChild) - continue; - - if (castChild.TargetLayer == emote.Category.ToString()) - { - castChild.Visible = true; - break; - } - } - } - - - // Set up menu actions - foreach (var child in Children) - { - if (child is not RadialContainer container) - continue; - AddEmoteClickAction(container); - } - } - - private void AddEmoteClickAction(RadialContainer container) - { - foreach (var child in container.Children) - { - if (child is not EmoteMenuButton castChild) - continue; - - castChild.OnButtonUp += _ => - { - OnPlayEmote?.Invoke(castChild.ProtoId); - Close(); - }; - } - } -} - - -public sealed class EmoteMenuButton : RadialMenuTextureButtonWithSector -{ - public ProtoId ProtoId { get; set; } -} diff --git a/Content.Client/Chemistry/UI/ChemMasterWindow.xaml.cs b/Content.Client/Chemistry/UI/ChemMasterWindow.xaml.cs index 807ec4c1e7..e264b859a0 100644 --- a/Content.Client/Chemistry/UI/ChemMasterWindow.xaml.cs +++ b/Content.Client/Chemistry/UI/ChemMasterWindow.xaml.cs @@ -6,7 +6,6 @@ using Robust.Client.AutoGenerated; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; -using Robust.Client.Utility; using Robust.Shared.Prototypes; using Robust.Shared.Utility; using System.Linq; @@ -14,6 +13,7 @@ using System.Numerics; using Content.Shared.FixedPoint; using Robust.Client.Graphics; using static Robust.Client.UserInterface.Controls.BoxContainer; +using Robust.Client.GameObjects; namespace Content.Client.Chemistry.UI { @@ -24,6 +24,10 @@ namespace Content.Client.Chemistry.UI public sealed partial class ChemMasterWindow : FancyWindow { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + + private readonly SpriteSystem _sprite; + public event Action? OnReagentButtonPressed; public readonly Button[] PillTypeButtons; @@ -38,6 +42,8 @@ namespace Content.Client.Chemistry.UI RobustXamlLoader.Load(this); IoCManager.InjectDependencies(this); + _sprite = _entityManager.System(); + // Pill type selection buttons, in total there are 20 pills. // Pill rsi file should have states named as pill1, pill2, and so on. var resourcePath = new ResPath(PillsRsiPath); @@ -69,7 +75,7 @@ namespace Content.Client.Chemistry.UI var specifier = new SpriteSpecifier.Rsi(resourcePath, "pill" + (i + 1)); TextureRect pillTypeTexture = new TextureRect { - Texture = specifier.Frame0(), + Texture = _sprite.Frame0(specifier), TextureScale = new Vector2(1.75f, 1.75f), Stretch = TextureRect.StretchMode.KeepCentered, }; diff --git a/Content.Client/Chemistry/Visualizers/FoamVisualizerSystem.cs b/Content.Client/Chemistry/Visualizers/FoamVisualizerSystem.cs index 2ee88956ff..2f895718c7 100644 --- a/Content.Client/Chemistry/Visualizers/FoamVisualizerSystem.cs +++ b/Content.Client/Chemistry/Visualizers/FoamVisualizerSystem.cs @@ -1,4 +1,4 @@ -using Content.Shared.Chemistry.Components; +using Content.Shared.Chemistry.Components; using Robust.Client.Animations; using Robust.Client.GameObjects; using Robust.Shared.Timing; @@ -37,7 +37,7 @@ public sealed class FoamVisualizerSystem : VisualizerSystem(uid, out var animPlayer) && !AnimationSystem.HasRunningAnimation(uid, animPlayer, VaporVisualsComponent.AnimationKey)) { - AnimationSystem.Play(uid, animPlayer, comp.VaporFlick, VaporVisualsComponent.AnimationKey); + AnimationSystem.Play((uid, animPlayer), comp.VaporFlick, VaporVisualsComponent.AnimationKey); } } diff --git a/Content.Client/Examine/ExamineButton.cs b/Content.Client/Examine/ExamineButton.cs index 839e08f3d4..3d5ac211e4 100644 --- a/Content.Client/Examine/ExamineButton.cs +++ b/Content.Client/Examine/ExamineButton.cs @@ -1,11 +1,7 @@ -using Content.Client.ContextMenu.UI; -using Content.Client.Stylesheets; using Content.Shared.Verbs; -using Robust.Client.AutoGenerated; -using Robust.Client.Graphics; +using Robust.Client.GameObjects; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; -using Robust.Client.UserInterface.XAML; using Robust.Client.Utility; using Robust.Shared.Utility; @@ -27,14 +23,16 @@ public sealed class ExamineButton : ContainerButton public TextureRect Icon; public ExamineVerb Verb; + private SpriteSystem _sprite; - public ExamineButton(ExamineVerb verb) + public ExamineButton(ExamineVerb verb, SpriteSystem spriteSystem) { Margin = new Thickness(Thickness, Thickness, Thickness, Thickness); SetOnlyStyleClass(StyleClassExamineButton); Verb = verb; + _sprite = spriteSystem; if (verb.Disabled) { @@ -61,7 +59,7 @@ public sealed class ExamineButton : ContainerButton if (verb.Icon != null) { - Icon.Texture = verb.Icon.Frame0(); + Icon.Texture = _sprite.Frame0(verb.Icon); Icon.Stretch = TextureRect.StretchMode.KeepAspectCentered; AddChild(Icon); diff --git a/Content.Client/Examine/ExamineSystem.cs b/Content.Client/Examine/ExamineSystem.cs index 07694ac24a..2e8d95c978 100644 --- a/Content.Client/Examine/ExamineSystem.cs +++ b/Content.Client/Examine/ExamineSystem.cs @@ -30,6 +30,7 @@ namespace Content.Client.Examine [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IEyeManager _eyeManager = default!; [Dependency] private readonly VerbSystem _verbSystem = default!; + [Dependency] private readonly SpriteSystem _sprite = default!; public const string StyleClassEntityTooltip = "entity-tooltip"; @@ -332,7 +333,7 @@ namespace Content.Client.Examine if (!examine.ShowOnExamineTooltip) continue; - var button = new ExamineButton(examine); + var button = new ExamineButton(examine, _sprite); if (examine.HoverVerb) { diff --git a/Content.Client/Light/EntitySystems/RotatingLightSystem.cs b/Content.Client/Light/EntitySystems/RotatingLightSystem.cs index 5c2c4e4c87..1e20d7041a 100644 --- a/Content.Client/Light/EntitySystems/RotatingLightSystem.cs +++ b/Content.Client/Light/EntitySystems/RotatingLightSystem.cs @@ -85,7 +85,7 @@ public sealed class RotatingLightSystem : SharedRotatingLightSystem if (!_animations.HasRunningAnimation(uid, player, AnimKey)) { - _animations.Play(uid, player, GetAnimation(comp.Speed), AnimKey); + _animations.Play((uid, player), GetAnimation(comp.Speed), AnimKey); } } } diff --git a/Content.Client/Light/Visualizers/PoweredLightVisualizerSystem.cs b/Content.Client/Light/Visualizers/PoweredLightVisualizerSystem.cs index ee81641d26..c07742462b 100644 --- a/Content.Client/Light/Visualizers/PoweredLightVisualizerSystem.cs +++ b/Content.Client/Light/Visualizers/PoweredLightVisualizerSystem.cs @@ -2,7 +2,6 @@ using Content.Shared.Light; using Robust.Client.Animations; using Robust.Client.GameObjects; using Robust.Shared.Animations; -using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Random; @@ -53,13 +52,14 @@ public sealed class PoweredLightVisualizerSystem : VisualizerSystem private void OnAnimationCompleted(EntityUid uid, PoweredLightVisualsComponent comp, AnimationCompletedEvent args) { + if (!TryComp(uid, out var animationPlayer)) + return; if (args.Key != PoweredLightVisualsComponent.BlinkingAnimationKey) return; - if(!comp.IsBlinking) return; - AnimationSystem.Play(uid, Comp(uid), BlinkingAnimation(comp), PoweredLightVisualsComponent.BlinkingAnimationKey); + AnimationSystem.Play((uid, animationPlayer), BlinkingAnimation(comp), PoweredLightVisualsComponent.BlinkingAnimationKey); } /// @@ -76,7 +76,7 @@ public sealed class PoweredLightVisualizerSystem : VisualizerSystem(uid); if (shouldBeBlinking) { - AnimationSystem.Play(uid, animationPlayer, BlinkingAnimation(comp), PoweredLightVisualsComponent.BlinkingAnimationKey); + AnimationSystem.Play((uid, animationPlayer), BlinkingAnimation(comp), PoweredLightVisualsComponent.BlinkingAnimationKey); } else if (AnimationSystem.HasRunningAnimation(uid, animationPlayer, PoweredLightVisualsComponent.BlinkingAnimationKey)) { diff --git a/Content.Client/Mapping/MappingState.cs b/Content.Client/Mapping/MappingState.cs index bcc739fe4f..57b45036e3 100644 --- a/Content.Client/Mapping/MappingState.cs +++ b/Content.Client/Mapping/MappingState.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using System.Numerics; using Content.Client.Administration.Managers; using Content.Client.ContextMenu.UI; @@ -149,7 +149,7 @@ public sealed class MappingState : GameplayStateBase { Deselect(); - var coords = args.Coordinates.ToMap(_entityManager, _transform); + var coords = _transform.ToMapCoordinates(args.Coordinates); if (_verbs.TryGetEntityMenuEntities(coords, out var entities)) _entityMenuController.OpenRootMenu(entities); diff --git a/Content.Client/Movement/Systems/ClientSpriteMovementSystem.cs b/Content.Client/Movement/Systems/ClientSpriteMovementSystem.cs index 1700796ede..a6265204b7 100644 --- a/Content.Client/Movement/Systems/ClientSpriteMovementSystem.cs +++ b/Content.Client/Movement/Systems/ClientSpriteMovementSystem.cs @@ -1,7 +1,6 @@ using Content.Shared.Movement.Components; using Content.Shared.Movement.Systems; using Robust.Client.GameObjects; -using Robust.Shared.Timing; namespace Content.Client.Movement.Systems; @@ -10,8 +9,6 @@ namespace Content.Client.Movement.Systems; /// public sealed class ClientSpriteMovementSystem : SharedSpriteMovementSystem { - [Dependency] private readonly IGameTiming _timing = default!; - private EntityQuery _spriteQuery; public override void Initialize() diff --git a/Content.Client/Movement/Systems/EyeCursorOffsetSystem.cs b/Content.Client/Movement/Systems/EyeCursorOffsetSystem.cs index 9e8ca9a9c9..eb524cf4ee 100644 --- a/Content.Client/Movement/Systems/EyeCursorOffsetSystem.cs +++ b/Content.Client/Movement/Systems/EyeCursorOffsetSystem.cs @@ -1,8 +1,6 @@ using System.Numerics; using Content.Client.Movement.Components; using Content.Shared.Camera; -using Content.Shared.Inventory; -using Content.Shared.Movement.Systems; using Robust.Client.Graphics; using Robust.Client.Input; using Robust.Shared.Map; @@ -16,8 +14,6 @@ public sealed partial class EyeCursorOffsetSystem : EntitySystem [Dependency] private readonly IInputManager _inputManager = default!; [Dependency] private readonly IPlayerManager _player = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; - [Dependency] private readonly SharedContentEyeSystem _contentEye = default!; - [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IClyde _clyde = default!; // This value is here to make sure the user doesn't have to move their mouse @@ -42,7 +38,7 @@ public sealed partial class EyeCursorOffsetSystem : EntitySystem public Vector2? OffsetAfterMouse(EntityUid uid, EyeCursorOffsetComponent? component) { - var localPlayer = _player.LocalPlayer?.ControlledEntity; + var localPlayer = _player.LocalEntity; var mousePos = _inputManager.MouseScreenPosition; var screenSize = _clyde.MainWindow.Size; var minValue = MathF.Min(screenSize.X / 2, screenSize.Y / 2) * _edgeOffset; diff --git a/Content.Client/Movement/Systems/JetpackSystem.cs b/Content.Client/Movement/Systems/JetpackSystem.cs index 2954140d79..804736ab7a 100644 --- a/Content.Client/Movement/Systems/JetpackSystem.cs +++ b/Content.Client/Movement/Systems/JetpackSystem.cs @@ -49,13 +49,17 @@ public sealed class JetpackSystem : SharedJetpackSystem // TODO: Please don't copy-paste this I beg // make a generic particle emitter system / actual particles instead. - var query = EntityQueryEnumerator(); + var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var comp)) + while (query.MoveNext(out var uid, out var comp, out var xform)) { - if (_timing.CurTime < comp.TargetTime) - continue; + if (_transform.InRange(xform.Coordinates, comp.LastCoordinates, comp.MaxDistance)) + { + if (_timing.CurTime < comp.TargetTime) + continue; + } + comp.LastCoordinates = _transform.GetMoverCoordinates(xform.Coordinates); comp.TargetTime = _timing.CurTime + TimeSpan.FromSeconds(comp.EffectCooldown); CreateParticles(uid); diff --git a/Content.Client/RCD/RCDMenu.xaml b/Content.Client/RCD/RCDMenu.xaml deleted file mode 100644 index d8ab0ac8f4..0000000000 --- a/Content.Client/RCD/RCDMenu.xaml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Content.Client/RCD/RCDMenu.xaml.cs b/Content.Client/RCD/RCDMenu.xaml.cs deleted file mode 100644 index 7ea9894e41..0000000000 --- a/Content.Client/RCD/RCDMenu.xaml.cs +++ /dev/null @@ -1,172 +0,0 @@ -using Content.Client.UserInterface.Controls; -using Content.Shared.Popups; -using Content.Shared.RCD; -using Content.Shared.RCD.Components; -using Robust.Client.AutoGenerated; -using Robust.Client.GameObjects; -using Robust.Client.Player; -using Robust.Client.UserInterface; -using Robust.Client.UserInterface.Controls; -using Robust.Client.UserInterface.XAML; -using Robust.Shared.Prototypes; -using System.Numerics; - -namespace Content.Client.RCD; - -[GenerateTypedNameReferences] -public sealed partial class RCDMenu : RadialMenu -{ - [Dependency] private readonly EntityManager _entManager = default!; - [Dependency] private readonly IPrototypeManager _protoManager = default!; - [Dependency] private readonly IPlayerManager _playerManager = default!; - - private SharedPopupSystem _popup; - private SpriteSystem _sprites; - - public event Action>? SendRCDSystemMessageAction; - - private EntityUid _owner; - - public RCDMenu() - { - IoCManager.InjectDependencies(this); - RobustXamlLoader.Load(this); - - _popup = _entManager.System(); - _sprites = _entManager.System(); - - OnChildAdded += AddRCDMenuButtonOnClickActions; - } - - public void SetEntity(EntityUid uid) - { - _owner = uid; - Refresh(); - } - - public void Refresh() - { - // Find the main radial container - var main = FindControl("Main"); - - // Populate secondary radial containers - if (!_entManager.TryGetComponent(_owner, out var rcd)) - return; - - foreach (var protoId in rcd.AvailablePrototypes) - { - if (!_protoManager.TryIndex(protoId, out var proto)) - continue; - - if (proto.Mode == RcdMode.Invalid) - continue; - - var parent = FindControl(proto.Category); - var tooltip = Loc.GetString(proto.SetName); - - if ((proto.Mode == RcdMode.ConstructTile || proto.Mode == RcdMode.ConstructObject) && - proto.Prototype != null && _protoManager.TryIndex(proto.Prototype, out var entProto, logError: false)) - { - tooltip = Loc.GetString(entProto.Name); - } - - tooltip = OopsConcat(char.ToUpper(tooltip[0]).ToString(), tooltip.Remove(0, 1)); - - var button = new RCDMenuButton() - { - SetSize = new Vector2(64f, 64f), - ToolTip = tooltip, - ProtoId = protoId, - }; - - if (proto.Sprite != null) - { - var tex = new TextureRect() - { - VerticalAlignment = VAlignment.Center, - HorizontalAlignment = HAlignment.Center, - Texture = _sprites.Frame0(proto.Sprite), - TextureScale = new Vector2(2f, 2f), - }; - - button.AddChild(tex); - } - - parent.AddChild(button); - - // Ensure that the button that transitions the menu to the associated category layer - // is visible in the main radial container (as these all start with Visible = false) - foreach (var child in main.Children) - { - if (child is not RadialMenuTextureButton castChild) - continue; - - if (castChild.TargetLayer == proto.Category) - { - castChild.Visible = true; - break; - } - } - } - - // Set up menu actions - foreach (var child in Children) - { - AddRCDMenuButtonOnClickActions(child); - } - } - - private static string OopsConcat(string a, string b) - { - // This exists to prevent Roslyn being clever and compiling something that fails sandbox checks. - return a + b; - } - - private void AddRCDMenuButtonOnClickActions(Control control) - { - var radialContainer = control as RadialContainer; - - if (radialContainer == null) - return; - - foreach (var child in radialContainer.Children) - { - var castChild = child as RCDMenuButton; - - if (castChild == null) - continue; - - castChild.OnButtonUp += _ => - { - SendRCDSystemMessageAction?.Invoke(castChild.ProtoId); - - if (_playerManager.LocalSession?.AttachedEntity != null && - _protoManager.TryIndex(castChild.ProtoId, out var proto)) - { - var msg = Loc.GetString("rcd-component-change-mode", ("mode", Loc.GetString(proto.SetName))); - - if (proto.Mode == RcdMode.ConstructTile || proto.Mode == RcdMode.ConstructObject) - { - var name = Loc.GetString(proto.SetName); - - if (proto.Prototype != null && - _protoManager.TryIndex(proto.Prototype, out var entProto, logError: false)) - name = entProto.Name; - - msg = Loc.GetString("rcd-component-change-build-mode", ("name", name)); - } - - // Popup message - _popup.PopupClient(msg, _owner, _playerManager.LocalSession.AttachedEntity); - } - - Close(); - }; - } - } -} - -public sealed class RCDMenuButton : RadialMenuTextureButtonWithSector -{ - public ProtoId ProtoId { get; set; } -} diff --git a/Content.Client/RCD/RCDMenuBoundUserInterface.cs b/Content.Client/RCD/RCDMenuBoundUserInterface.cs index 1dd03626ae..d599c324e1 100644 --- a/Content.Client/RCD/RCDMenuBoundUserInterface.cs +++ b/Content.Client/RCD/RCDMenuBoundUserInterface.cs @@ -1,20 +1,32 @@ +using Content.Client.Popups; +using Content.Client.UserInterface.Controls; using Content.Shared.RCD; using Content.Shared.RCD.Components; using JetBrains.Annotations; -using Robust.Client.Graphics; -using Robust.Client.Input; using Robust.Client.UserInterface; +using Robust.Shared.Player; using Robust.Shared.Prototypes; +using Robust.Shared.Utility; namespace Content.Client.RCD; [UsedImplicitly] public sealed class RCDMenuBoundUserInterface : BoundUserInterface { - [Dependency] private readonly IClyde _displayManager = default!; - [Dependency] private readonly IInputManager _inputManager = default!; + private static readonly Dictionary PrototypesGroupingInfo + = new Dictionary + { + ["WallsAndFlooring"] = ("rcd-component-walls-and-flooring", new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/Radial/RCD/walls_and_flooring.png"))), + ["WindowsAndGrilles"] = ("rcd-component-windows-and-grilles", new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/Radial/RCD/windows_and_grilles.png"))), + ["Airlocks"] = ("rcd-component-airlocks", new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/Radial/RCD/airlocks.png"))), + ["Electrical"] = ("rcd-component-electrical", new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/Radial/RCD/multicoil.png"))), + ["Lighting"] = ("rcd-component-lighting", new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/Radial/RCD/lighting.png"))), + }; - private RCDMenu? _menu; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly ISharedPlayerManager _playerManager = default!; + + private SimpleRadialMenu? _menu; public RCDMenuBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) { @@ -25,19 +37,107 @@ public sealed class RCDMenuBoundUserInterface : BoundUserInterface { base.Open(); - _menu = this.CreateWindow(); - _menu.SetEntity(Owner); - _menu.SendRCDSystemMessageAction += SendRCDSystemMessage; + if (!EntMan.TryGetComponent(Owner, out var rcd)) + return; - // Open the menu, centered on the mouse - var vpSize = _displayManager.ScreenSize; - _menu.OpenCenteredAt(_inputManager.MouseScreenPosition.Position / vpSize); + _menu = this.CreateWindow(); + _menu.Track(Owner); + var models = ConvertToButtons(rcd.AvailablePrototypes); + _menu.SetButtons(models); + + _menu.OpenOverMouseScreenPosition(); } - public void SendRCDSystemMessage(ProtoId protoId) + private IEnumerable ConvertToButtons(HashSet> prototypes) + { + Dictionary> buttonsByCategory = new(); + foreach (var protoId in prototypes) + { + var prototype = _prototypeManager.Index(protoId); + if (!PrototypesGroupingInfo.TryGetValue(prototype.Category, out var groupInfo)) + continue; + + if (!buttonsByCategory.TryGetValue(prototype.Category, out var list)) + { + list = new List(); + buttonsByCategory.Add(prototype.Category, list); + } + + var actionOption = new RadialMenuActionOption(HandleMenuOptionClick, prototype) + { + Sprite = prototype.Sprite, + ToolTip = GetTooltip(prototype) + }; + list.Add(actionOption); + } + + var models = new RadialMenuNestedLayerOption[buttonsByCategory.Count]; + var i = 0; + foreach (var (key, list) in buttonsByCategory) + { + var groupInfo = PrototypesGroupingInfo[key]; + models[i] = new RadialMenuNestedLayerOption(list) + { + Sprite = groupInfo.Sprite, + ToolTip = Loc.GetString(groupInfo.Tooltip) + }; + i++; + } + + return models; + } + + private void HandleMenuOptionClick(RCDPrototype proto) { // A predicted message cannot be used here as the RCD UI is closed immediately // after this message is sent, which will stop the server from receiving it - SendMessage(new RCDSystemMessage(protoId)); + SendMessage(new RCDSystemMessage(proto.ID)); + + + if (_playerManager.LocalSession?.AttachedEntity == null) + return; + + var msg = Loc.GetString("rcd-component-change-mode", ("mode", Loc.GetString(proto.SetName))); + + if (proto.Mode is RcdMode.ConstructTile or RcdMode.ConstructObject) + { + var name = Loc.GetString(proto.SetName); + + if (proto.Prototype != null && + _prototypeManager.TryIndex(proto.Prototype, out var entProto, logError: false)) + name = entProto.Name; + + msg = Loc.GetString("rcd-component-change-build-mode", ("name", name)); + } + + // Popup message + var popup = EntMan.System(); + popup.PopupClient(msg, Owner, _playerManager.LocalSession.AttachedEntity); + } + + private string GetTooltip(RCDPrototype proto) + { + string tooltip; + + if (proto.Mode is RcdMode.ConstructTile or RcdMode.ConstructObject + && proto.Prototype != null + && _prototypeManager.TryIndex(proto.Prototype, out var entProto, logError: false)) + { + tooltip = Loc.GetString(entProto.Name); + } + else + { + tooltip = Loc.GetString(proto.SetName); + } + + tooltip = OopsConcat(char.ToUpper(tooltip[0]).ToString(), tooltip.Remove(0, 1)); + + return tooltip; + } + + private static string OopsConcat(string a, string b) + { + // This exists to prevent Roslyn being clever and compiling something that fails sandbox checks. + return a + b; } } diff --git a/Content.Client/Rotation/RotationVisualizerSystem.cs b/Content.Client/Rotation/RotationVisualizerSystem.cs index 6d3be4d1c0..8dbcf97320 100644 --- a/Content.Client/Rotation/RotationVisualizerSystem.cs +++ b/Content.Client/Rotation/RotationVisualizerSystem.cs @@ -52,7 +52,7 @@ public sealed class RotationVisualizerSystem : SharedRotationVisualsSystem // Stop the current rotate animation and then start a new one if (_animation.HasRunningAnimation(animationComp, animationKey)) { - _animation.Stop(animationComp, animationKey); + _animation.Stop((uid, animationComp), animationKey); } var animation = new Animation diff --git a/Content.Client/Silicons/StationAi/StationAiBoundUserInterface.cs b/Content.Client/Silicons/StationAi/StationAiBoundUserInterface.cs index 68318305a0..77ac13c972 100644 --- a/Content.Client/Silicons/StationAi/StationAiBoundUserInterface.cs +++ b/Content.Client/Silicons/StationAi/StationAiBoundUserInterface.cs @@ -1,28 +1,46 @@ +using Content.Client.UserInterface.Controls; using Content.Shared.Silicons.StationAi; using Robust.Client.UserInterface; namespace Content.Client.Silicons.StationAi; -public sealed class StationAiBoundUserInterface : BoundUserInterface +public sealed class StationAiBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey) { - private StationAiMenu? _menu; - - public StationAiBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) - { - } + private SimpleRadialMenu? _menu; protected override void Open() { base.Open(); - _menu = this.CreateWindow(); - _menu.Track(Owner); - _menu.OnAiRadial += args => + var ev = new GetStationAiRadialEvent(); + EntMan.EventBus.RaiseLocalEvent(Owner, ref ev); + + _menu = this.CreateWindow(); + _menu.Track(Owner); + var buttonModels = ConvertToButtons(ev.Actions); + _menu.SetButtons(buttonModels); + + _menu.Open(); + } + + private IEnumerable ConvertToButtons(IReadOnlyList actions) + { + var models = new RadialMenuActionOption[actions.Count]; + for (int i = 0; i < actions.Count; i++) { - SendPredictedMessage(new StationAiRadialMessage() + var action = actions[i]; + models[i] = new RadialMenuActionOption(HandleRadialMenuClick, action.Event) { - Event = args, - }); - }; + Sprite = action.Sprite, + ToolTip = action.Tooltip + }; + } + + return models; + } + + private void HandleRadialMenuClick(BaseStationAiAction p) + { + SendPredictedMessage(new StationAiRadialMessage { Event = p }); } } diff --git a/Content.Client/Silicons/StationAi/StationAiMenu.xaml b/Content.Client/Silicons/StationAi/StationAiMenu.xaml deleted file mode 100644 index cfa0b93234..0000000000 --- a/Content.Client/Silicons/StationAi/StationAiMenu.xaml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - diff --git a/Content.Client/Silicons/StationAi/StationAiMenu.xaml.cs b/Content.Client/Silicons/StationAi/StationAiMenu.xaml.cs deleted file mode 100644 index a536d911f3..0000000000 --- a/Content.Client/Silicons/StationAi/StationAiMenu.xaml.cs +++ /dev/null @@ -1,126 +0,0 @@ -using System.Numerics; -using Content.Client.UserInterface.Controls; -using Content.Shared.Silicons.StationAi; -using Robust.Client.AutoGenerated; -using Robust.Client.GameObjects; -using Robust.Client.Graphics; -using Robust.Client.UserInterface.Controls; -using Robust.Client.UserInterface.XAML; -using Robust.Shared.Timing; - -namespace Content.Client.Silicons.StationAi; - -[GenerateTypedNameReferences] -public sealed partial class StationAiMenu : RadialMenu -{ - [Dependency] private readonly IClyde _clyde = default!; - [Dependency] private readonly IEntityManager _entManager = default!; - - public event Action? OnAiRadial; - - private EntityUid _tracked; - - public StationAiMenu() - { - IoCManager.InjectDependencies(this); - RobustXamlLoader.Load(this); - } - - public void Track(EntityUid owner) - { - _tracked = owner; - - if (!_entManager.EntityExists(_tracked)) - { - Close(); - return; - } - - BuildButtons(); - UpdatePosition(); - } - - private void BuildButtons() - { - var ev = new GetStationAiRadialEvent(); - _entManager.EventBus.RaiseLocalEvent(_tracked, ref ev); - - var main = FindControl("Main"); - main.DisposeAllChildren(); - var sprites = _entManager.System(); - - foreach (var action in ev.Actions) - { - // TODO: This radial boilerplate is quite annoying - var button = new StationAiMenuButton(action.Event) - { - SetSize = new Vector2(64f, 64f), - ToolTip = action.Tooltip != null ? Loc.GetString(action.Tooltip) : null, - }; - - if (action.Sprite != null) - { - var texture = sprites.Frame0(action.Sprite); - var scale = Vector2.One; - - if (texture.Width <= 32) - { - scale *= 2; - } - - var tex = new TextureRect - { - VerticalAlignment = VAlignment.Center, - HorizontalAlignment = HAlignment.Center, - Texture = texture, - TextureScale = scale, - }; - - button.AddChild(tex); - } - - button.OnPressed += args => - { - OnAiRadial?.Invoke(action.Event); - Close(); - }; - main.AddChild(button); - } - } - - protected override void FrameUpdate(FrameEventArgs args) - { - base.FrameUpdate(args); - UpdatePosition(); - } - - private void UpdatePosition() - { - if (!_entManager.TryGetComponent(_tracked, out TransformComponent? xform)) - { - Close(); - return; - } - - if (!xform.Coordinates.IsValid(_entManager)) - { - Close(); - return; - } - - var coords = _entManager.System().GetSpriteScreenCoordinates((_tracked, null, xform)); - - if (!coords.IsValid) - { - Close(); - return; - } - - OpenScreenAt(coords.Position, _clyde); - } -} - -public sealed class StationAiMenuButton(BaseStationAiAction action) : RadialMenuTextureButtonWithSector -{ - public BaseStationAiAction Action = action; -} diff --git a/Content.Client/Temperature/Systems/EntityHeaterSystem.cs b/Content.Client/Temperature/Systems/EntityHeaterSystem.cs new file mode 100644 index 0000000000..300cfa3d44 --- /dev/null +++ b/Content.Client/Temperature/Systems/EntityHeaterSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared.Temperature.Systems; + +namespace Content.Client.Temperature.Systems; + +public sealed partial class EntityHeaterSystem : SharedEntityHeaterSystem; diff --git a/Content.Client/UserInterface/Controls/RadialMenu.cs b/Content.Client/UserInterface/Controls/RadialMenu.cs index 628943fa26..9734cf2960 100644 --- a/Content.Client/UserInterface/Controls/RadialMenu.cs +++ b/Content.Client/UserInterface/Controls/RadialMenu.cs @@ -12,53 +12,6 @@ namespace Content.Client.UserInterface.Controls; [Virtual] public class RadialMenu : BaseWindow { - private readonly List _path = new(); - private string? _backButtonStyleClass; - private string? _closeButtonStyleClass; - - /// - /// A free floating menu which enables the quick display of one or more radial containers - /// - /// - /// Only one radial container is visible at a time (each container forming a separate 'layer' within - /// the menu), along with a contextual button at the menu center, which will either return the user - /// to the previous layer or close the menu if there are no previous layers left to traverse. - /// To create a functional radial menu, simply parent one or more named radial containers to it, - /// and populate the radial containers with RadialMenuButtons. Setting the TargetLayer field of these - /// buttons to the name of a radial conatiner will display the container in question to the user - /// whenever it is clicked in additon to any other actions assigned to the button - /// - public RadialMenu() - { - // Hide all starting children (if any) except the first (this is the active layer) - if (ChildCount > 1) - { - for (int i = 1; i < ChildCount; i++) - GetChild(i).Visible = false; - } - - // Auto generate a contextual button for moving back through visited layers - ContextualButton = new RadialMenuContextualCentralTextureButton - { - HorizontalAlignment = HAlignment.Center, - VerticalAlignment = VAlignment.Center, - SetSize = new Vector2(64f, 64f), - }; - MenuOuterAreaButton = new RadialMenuOuterAreaButton(); - - ContextualButton.OnButtonUp += _ => ReturnToPreviousLayer(); - MenuOuterAreaButton.OnButtonUp += _ => Close(); - AddChild(ContextualButton); - AddChild(MenuOuterAreaButton); - - // Hide any further add children, unless its promoted to the active layer - OnChildAdded += child => - { - child.Visible = GetCurrentActiveLayer() == child; - SetupContextualButtonData(child); - }; - } - /// /// Contextual button used to traverse through previous layers of the radial menu /// @@ -107,6 +60,53 @@ public class RadialMenu : BaseWindow } } + private readonly List _path = new(); + private string? _backButtonStyleClass; + private string? _closeButtonStyleClass; + + /// + /// A free floating menu which enables the quick display of one or more radial containers + /// + /// + /// Only one radial container is visible at a time (each container forming a separate 'layer' within + /// the menu), along with a contextual button at the menu center, which will either return the user + /// to the previous layer or close the menu if there are no previous layers left to traverse. + /// To create a functional radial menu, simply parent one or more named radial containers to it, + /// and populate the radial containers with RadialMenuButtons. Setting the TargetLayer field of these + /// buttons to the name of a radial conatiner will display the container in question to the user + /// whenever it is clicked in additon to any other actions assigned to the button + /// + public RadialMenu() + { + // Hide all starting children (if any) except the first (this is the active layer) + if (ChildCount > 1) + { + for (int i = 1; i < ChildCount; i++) + GetChild(i).Visible = false; + } + + // Auto generate a contextual button for moving back through visited layers + ContextualButton = new RadialMenuContextualCentralTextureButton + { + HorizontalAlignment = HAlignment.Center, + VerticalAlignment = VAlignment.Center, + SetSize = new Vector2(64f, 64f), + }; + MenuOuterAreaButton = new RadialMenuOuterAreaButton(); + + ContextualButton.OnButtonUp += _ => ReturnToPreviousLayer(); + MenuOuterAreaButton.OnButtonUp += _ => Close(); + AddChild(ContextualButton); + AddChild(MenuOuterAreaButton); + + // Hide any further add children, unless its promoted to the active layer + OnChildAdded += child => + { + child.Visible = GetCurrentActiveLayer() == child; + SetupContextualButtonData(child); + }; + } + private void SetupContextualButtonData(Control child) { if (child is RadialContainer { Visible: true } container) @@ -143,11 +143,8 @@ public class RadialMenu : BaseWindow return children.First(x => x.Visible); } - public bool TryToMoveToNewLayer(string newLayer) + public bool TryToMoveToNewLayer(Control newLayer) { - if (newLayer == string.Empty) - return false; - var currentLayer = GetCurrentActiveLayer(); if (currentLayer == null) @@ -161,7 +158,7 @@ public class RadialMenu : BaseWindow continue; // Hide layers which are not of interest - if (result == true || child.Name != newLayer) + if (result == true || child != newLayer) { child.Visible = false; } @@ -186,6 +183,19 @@ public class RadialMenu : BaseWindow return result; } + public bool TryToMoveToNewLayer(string targetLayerControlName) + { + foreach (var child in Children) + { + if (child.Name == targetLayerControlName && child is RadialContainer) + { + return TryToMoveToNewLayer(child); + } + } + + return false; + } + public void ReturnToPreviousLayer() { // Close the menu if the traversal path is empty @@ -295,6 +305,17 @@ public sealed class RadialMenuOuterAreaButton : RadialMenuTextureButtonBase [Virtual] public class RadialMenuTextureButton : RadialMenuTextureButtonBase { + /// + /// Upon clicking this button the radial menu will be moved to the layer of this control. + /// + public Control? TargetLayer { get; set; } + + /// + /// Other way to set navigation to other container, as , + /// but using property of target . + /// + public string? TargetLayerControlName { get; set; } + /// /// A simple texture button that can move the user to a different layer within a radial menu /// @@ -304,14 +325,9 @@ public class RadialMenuTextureButton : RadialMenuTextureButtonBase OnButtonUp += OnClicked; } - /// - /// Upon clicking this button the radial menu will be moved to the named layer - /// - public string TargetLayer { get; set; } = string.Empty; - private void OnClicked(ButtonEventArgs args) { - if (TargetLayer == string.Empty) + if (TargetLayer == null && TargetLayerControlName == null) return; var parent = FindParentMultiLayerContainer(this); @@ -319,7 +335,14 @@ public class RadialMenuTextureButton : RadialMenuTextureButtonBase if (parent == null) return; - parent.TryToMoveToNewLayer(TargetLayer); + if (TargetLayer != null) + { + parent.TryToMoveToNewLayer(TargetLayer); + } + else + { + parent.TryToMoveToNewLayer(TargetLayerControlName!); + } } private RadialMenu? FindParentMultiLayerContainer(Control control) @@ -370,31 +393,24 @@ public interface IRadialMenuItemWithSector [Virtual] public class RadialMenuTextureButtonWithSector : RadialMenuTextureButton, IRadialMenuItemWithSector { - private float _angleOffset; + private Vector2[]? _sectorPointsForDrawing; private float _angleSectorFrom; private float _angleSectorTo; - - private Color _backgroundColorSrgb = Color.ToSrgb(new Color(70, 73, 102, 128)); - private Color _borderColorSrgb = Color.ToSrgb(new Color(173, 216, 230, 70)); - private Color _hoverBackgroundColorSrgb = Color.ToSrgb(new Color(87, 91, 127, 128)); - private Color _hoverBorderColorSrgb = Color.ToSrgb(new Color(87, 91, 127, 128)); + private float _outerRadius; private float _innerRadius; + private float _angleOffset; private bool _isWholeCircle; - private float _outerRadius; private Vector2? _parentCenter; - private Vector2[]? _sectorPointsForDrawing; + + private Color _backgroundColorSrgb = Color.ToSrgb(new Color(70, 73, 102, 128)); + private Color _hoverBackgroundColorSrgb = Color.ToSrgb(new Color(87, 91, 127, 128)); + private Color _borderColorSrgb = Color.ToSrgb(new Color(173, 216, 230, 70)); + private Color _hoverBorderColorSrgb = Color.ToSrgb(new Color(87, 91, 127, 128)); /// - /// A simple texture button that can move the user to a different layer within a radial menu - /// - public RadialMenuTextureButtonWithSector() - { - } - - /// - /// Marker, that control should render border of segment. Is false by default. + /// Marker, that controls if border of segment should be rendered. Is false by default. /// /// /// By default color of border is same as color of background. Use @@ -407,13 +423,6 @@ public class RadialMenuTextureButtonWithSector : RadialMenuTextureButton, IRadia /// public bool DrawBackground { get; set; } = true; - /// - /// Marker, that control should render separator lines. - /// Separator lines are used to visually separate sector of radial menu items. - /// Is true by default - /// - public bool DrawSeparators { get; set; } = true; - /// /// Color of background in non-hovered state. Accepts RGB color, works with sRGB for DrawPrimitive internally. /// @@ -488,6 +497,13 @@ public class RadialMenuTextureButtonWithSector : RadialMenuTextureButton, IRadia /// Vector2 IRadialMenuItemWithSector.ParentCenter { set => _parentCenter = value; } + /// + /// A simple texture button that can move the user to a different layer within a radial menu + /// + public RadialMenuTextureButtonWithSector() + { + } + /// protected override void Draw(DrawingHandleScreen handle) { @@ -509,13 +525,7 @@ public class RadialMenuTextureButtonWithSector : RadialMenuTextureButton, IRadia ? _hoverBackgroundColorSrgb : _backgroundColorSrgb; - DrawAnnulusSector(handle, - containerCenter, - _innerRadius * UIScale, - _outerRadius * UIScale, - angleFrom, - angleTo, - segmentColor); + DrawAnnulusSector(handle, containerCenter, _innerRadius * UIScale, _outerRadius * UIScale, angleFrom, angleTo, segmentColor); } if (DrawBorder) @@ -523,25 +533,12 @@ public class RadialMenuTextureButtonWithSector : RadialMenuTextureButton, IRadia var borderColor = DrawMode == DrawModeEnum.Hover ? _hoverBorderColorSrgb : _borderColorSrgb; - DrawAnnulusSector(handle, - containerCenter, - _innerRadius * UIScale, - _outerRadius * UIScale, - angleFrom, - angleTo, - borderColor, - false); + DrawAnnulusSector(handle, containerCenter, _innerRadius * UIScale, _outerRadius * UIScale, angleFrom, angleTo, borderColor, false); } - if (!_isWholeCircle && DrawSeparators) + if (!_isWholeCircle && DrawBorder) { - DrawSeparatorLines(handle, - containerCenter, - _innerRadius * UIScale, - _outerRadius * UIScale, - angleFrom, - angleTo, - SeparatorColor); + DrawSeparatorLines(handle, containerCenter, _innerRadius * UIScale, _outerRadius * UIScale, angleFrom, angleTo, SeparatorColor); } } diff --git a/Content.Client/UserInterface/Controls/SimpleRadialMenu.xaml b/Content.Client/UserInterface/Controls/SimpleRadialMenu.xaml new file mode 100644 index 0000000000..307064334d --- /dev/null +++ b/Content.Client/UserInterface/Controls/SimpleRadialMenu.xaml @@ -0,0 +1,8 @@ + + diff --git a/Content.Client/UserInterface/Controls/SimpleRadialMenu.xaml.cs b/Content.Client/UserInterface/Controls/SimpleRadialMenu.xaml.cs new file mode 100644 index 0000000000..15c8065a44 --- /dev/null +++ b/Content.Client/UserInterface/Controls/SimpleRadialMenu.xaml.cs @@ -0,0 +1,279 @@ +using Robust.Client.UserInterface; +using System.Numerics; +using Robust.Client.AutoGenerated; +using Robust.Client.Graphics; +using Robust.Shared.Utility; +using Robust.Client.GameObjects; +using Robust.Shared.Timing; +using Robust.Client.UserInterface.XAML; +using Robust.Client.Input; + +namespace Content.Client.UserInterface.Controls; + +[GenerateTypedNameReferences] +public partial class SimpleRadialMenu : RadialMenu +{ + private EntityUid? _attachMenuToEntity; + + [Dependency] private readonly IClyde _clyde = default!; + [Dependency] private readonly IEntityManager _entManager = default!; + [Dependency] private readonly IInputManager _inputManager = default!; + + public SimpleRadialMenu() + { + IoCManager.InjectDependencies(this); + RobustXamlLoader.Load(this); + } + + public void Track(EntityUid owner) + { + _attachMenuToEntity = owner; + } + + public void SetButtons(IEnumerable models, SimpleRadialMenuSettings? settings = null) + { + ClearExistingChildrenRadialButtons(); + + var sprites = _entManager.System(); + Fill(models, sprites, Children, settings ?? new SimpleRadialMenuSettings()); + } + + public void OpenOverMouseScreenPosition() + { + var vpSize = _clyde.ScreenSize; + OpenCenteredAt(_inputManager.MouseScreenPosition.Position / vpSize); + } + + private void Fill( + IEnumerable models, + SpriteSystem sprites, + ICollection rootControlChildren, + SimpleRadialMenuSettings settings + ) + { + var rootContainer = new RadialContainer + { + HorizontalExpand = true, + VerticalExpand = true, + InitialRadius = settings.DefaultContainerRadius, + ReserveSpaceForHiddenChildren = false, + Visible = true + }; + rootControlChildren.Add(rootContainer); + + foreach (var model in models) + { + if (model is RadialMenuNestedLayerOption nestedMenuModel) + { + var linkButton = RecursiveContainerExtraction(sprites, rootControlChildren, nestedMenuModel, settings); + linkButton.Visible = true; + rootContainer.AddChild(linkButton); + } + else + { + var rootButtons = ConvertToButton(model, sprites, settings, false); + rootContainer.AddChild(rootButtons); + } + } + } + + private RadialMenuTextureButton RecursiveContainerExtraction( + SpriteSystem sprites, + ICollection rootControlChildren, + RadialMenuNestedLayerOption model, + SimpleRadialMenuSettings settings + ) + { + var container = new RadialContainer + { + HorizontalExpand = true, + VerticalExpand = true, + InitialRadius = model.ContainerRadius!.Value, + ReserveSpaceForHiddenChildren = false, + Visible = false + }; + foreach (var nested in model.Nested) + { + if (nested is RadialMenuNestedLayerOption nestedMenuModel) + { + var linkButton = RecursiveContainerExtraction(sprites, rootControlChildren, nestedMenuModel, settings); + container.AddChild(linkButton); + } + else + { + var button = ConvertToButton(nested, sprites, settings, false); + container.AddChild(button); + } + } + rootControlChildren.Add(container); + + var thisLayerLinkButton = ConvertToButton(model, sprites, settings, true); + thisLayerLinkButton.TargetLayer = container; + return thisLayerLinkButton; + } + + private RadialMenuTextureButton ConvertToButton( + RadialMenuOption model, + SpriteSystem sprites, + SimpleRadialMenuSettings settings, + bool haveNested + ) + { + var button = settings.UseSectors + ? ConvertToButtonWithSector(model, settings) + : new RadialMenuTextureButton(); + button.SetSize = new Vector2(64f, 64f); + button.ToolTip = model.ToolTip; + if (model.Sprite != null) + { + var scale = Vector2.One; + + var texture = sprites.Frame0(model.Sprite); + if (texture.Width <= 32) + { + scale *= 2; + } + + button.TextureNormal = texture; + button.Scale = scale; + } + + if (model is RadialMenuActionOption actionOption) + { + button.OnPressed += _ => + { + actionOption.OnPressed?.Invoke(); + if(!haveNested) + Close(); + }; + } + + return button; + } + + private static RadialMenuTextureButtonWithSector ConvertToButtonWithSector(RadialMenuOption model, SimpleRadialMenuSettings settings) + { + var button = new RadialMenuTextureButtonWithSector + { + DrawBorder = settings.DisplayBorders, + DrawBackground = !settings.NoBackground + }; + if (model.BackgroundColor.HasValue) + { + button.BackgroundColor = model.BackgroundColor.Value; + } + + if (model.HoverBackgroundColor.HasValue) + { + button.HoverBackgroundColor = model.HoverBackgroundColor.Value; + } + + return button; + } + + private void ClearExistingChildrenRadialButtons() + { + var toRemove = new List(ChildCount); + foreach (var child in Children) + { + if (child != ContextualButton && child != MenuOuterAreaButton) + { + toRemove.Add(child); + } + } + + foreach (var control in toRemove) + { + Children.Remove(control); + } + } + + #region target entity tracking + + protected override void FrameUpdate(FrameEventArgs args) + { + base.FrameUpdate(args); + if (_attachMenuToEntity != null) + { + UpdatePosition(); + } + } + + private void UpdatePosition() + { + if (!_entManager.TryGetComponent(_attachMenuToEntity, out TransformComponent? xform)) + { + Close(); + return; + } + + if (!xform.Coordinates.IsValid(_entManager)) + { + Close(); + return; + } + + var coords = _entManager.System().GetSpriteScreenCoordinates((_attachMenuToEntity.Value, null, xform)); + + if (!coords.IsValid) + { + Close(); + return; + } + + OpenScreenAt(coords.Position, _clyde); + } + + #endregion + +} + + +public abstract class RadialMenuOption +{ + public string? ToolTip { get; init; } + + public SpriteSpecifier? Sprite { get; init; } + public Color? BackgroundColor { get; set; } + public Color? HoverBackgroundColor { get; set; } +} + +public class RadialMenuActionOption(Action onPressed) : RadialMenuOption +{ + public Action OnPressed { get; } = onPressed; +} + +public class RadialMenuActionOption(Action onPressed, T data) + : RadialMenuActionOption(onPressed: () => onPressed(data)); + +public class RadialMenuNestedLayerOption(IReadOnlyCollection nested, float containerRadius = 100) + : RadialMenuOption +{ + public float? ContainerRadius { get; } = containerRadius; + + public IReadOnlyCollection Nested { get; } = nested; +} + +public class SimpleRadialMenuSettings +{ + /// + /// Default container draw radius. Is going to be further affected by per sector increment. + /// + public int DefaultContainerRadius = 100; + + /// + /// Marker, if sector-buttons should be used. + /// + public bool UseSectors = true; + + /// + /// Marker, if border of buttons should be rendered. Can only be used when = true. + /// + public bool DisplayBorders = true; + + /// + /// Marker, if sector background should not be rendered. Can only be used when = true. + /// + public bool NoBackground = false; +} + diff --git a/Content.Client/UserInterface/Systems/Emotes/EmotesUIController.cs b/Content.Client/UserInterface/Systems/Emotes/EmotesUIController.cs index ab946f524a..e82b168d56 100644 --- a/Content.Client/UserInterface/Systems/Emotes/EmotesUIController.cs +++ b/Content.Client/UserInterface/Systems/Emotes/EmotesUIController.cs @@ -1,16 +1,17 @@ -using Content.Client.Chat.UI; using Content.Client.Gameplay; using Content.Client.UserInterface.Controls; using Content.Shared.Chat; using Content.Shared.Chat.Prototypes; using Content.Shared.Input; +using Content.Shared.Speech; +using Content.Shared.Whitelist; using JetBrains.Annotations; -using Robust.Client.Graphics; -using Robust.Client.Input; +using Robust.Client.Player; using Robust.Client.UserInterface.Controllers; using Robust.Client.UserInterface.Controls; using Robust.Shared.Input.Binding; using Robust.Shared.Prototypes; +using Robust.Shared.Utility; namespace Content.Client.UserInterface.Systems.Emotes; @@ -18,11 +19,19 @@ namespace Content.Client.UserInterface.Systems.Emotes; public sealed class EmotesUIController : UIController, IOnStateChanged { [Dependency] private readonly IEntityManager _entityManager = default!; - [Dependency] private readonly IClyde _displayManager = default!; - [Dependency] private readonly IInputManager _inputManager = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; - private MenuButton? EmotesButton => null; - private EmotesMenu? _menu; + private MenuButton? EmotesButton => UIManager.GetActiveUIWidgetOrNull()?.EmotesButton; + private SimpleRadialMenu? _menu; + + private static readonly Dictionary EmoteGroupingInfo + = new Dictionary + { + [EmoteCategory.General] = ("emote-menu-category-general", new SpriteSpecifier.Texture(new ResPath("/Textures/Clothing/Head/Soft/mimesoft.rsi/icon.png"))), + [EmoteCategory.Hands] = ("emote-menu-category-hands", new SpriteSpecifier.Texture(new ResPath("/Textures/Clothing/Hands/Gloves/latex.rsi/icon.png"))), + [EmoteCategory.Vocal] = ("emote-menu-category-vocal", new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/Emotes/vocal.png"))), + }; public void OnStateEntered(GameplayState state) { @@ -42,10 +51,16 @@ public sealed class EmotesUIController : UIController, IOnStateChanged(); + var prototypes = _prototypeManager.EnumeratePrototypes(); + var models = ConvertToButtons(prototypes); + + _menu = new SimpleRadialMenu(); + _menu.SetButtons(models); + + _menu.Open(); + _menu.OnClose += OnWindowClosed; _menu.OnOpen += OnWindowOpen; - _menu.OnPlayEmote += OnPlayEmote; if (EmotesButton != null) EmotesButton.SetClickPressed(true); @@ -56,16 +71,13 @@ public sealed class EmotesUIController : UIController, IOnStateChanged protoId) + private IEnumerable ConvertToButtons(IEnumerable emotePrototypes) { - _entityManager.RaisePredictiveEvent(new PlayEmoteMessage(protoId)); + var whitelistSystem = EntitySystemManager.GetEntitySystem(); + var player = _playerManager.LocalSession?.AttachedEntity; + + Dictionary> emotesByCategory = new(); + foreach (var emote in emotePrototypes) + { + if(emote.Category == EmoteCategory.Invalid) + continue; + + // only valid emotes that have ways to be triggered by chat and player have access / no restriction on + if (emote.Category == EmoteCategory.Invalid + || emote.ChatTriggers.Count == 0 + || !(player.HasValue && whitelistSystem.IsWhitelistPassOrNull(emote.Whitelist, player.Value)) + || whitelistSystem.IsBlacklistPass(emote.Blacklist, player.Value)) + continue; + + if (!emote.Available + && EntityManager.TryGetComponent(player.Value, out var speech) + && !speech.AllowedEmotes.Contains(emote.ID)) + continue; + + if (!emotesByCategory.TryGetValue(emote.Category, out var list)) + { + list = new List(); + emotesByCategory.Add(emote.Category, list); + } + + var actionOption = new RadialMenuActionOption(HandleRadialButtonClick, emote) + { + Sprite = emote.Icon, + ToolTip = Loc.GetString(emote.Name) + }; + list.Add(actionOption); + } + + var models = new RadialMenuOption[emotesByCategory.Count]; + var i = 0; + foreach (var (key, list) in emotesByCategory) + { + var tuple = EmoteGroupingInfo[key]; + + models[i] = new RadialMenuNestedLayerOption(list) + { + Sprite = tuple.Sprite, + ToolTip = Loc.GetString(tuple.Tooltip) + }; + i++; + } + + return models; + } + + private void HandleRadialButtonClick(EmotePrototype prototype) + { + _entityManager.RaisePredictiveEvent(new PlayEmoteMessage(prototype.ID)); } } diff --git a/Content.Client/UserInterface/Systems/Storage/Controls/StorageWindow.cs b/Content.Client/UserInterface/Systems/Storage/Controls/StorageWindow.cs index a4afebc217..39ffd883bb 100644 --- a/Content.Client/UserInterface/Systems/Storage/Controls/StorageWindow.cs +++ b/Content.Client/UserInterface/Systems/Storage/Controls/StorageWindow.cs @@ -42,6 +42,9 @@ public sealed class StorageWindow : BaseWindow private ValueList _contained = new(); private ValueList _toRemove = new(); + // Manually store this because you can't have a 0x0 GridContainer but we still need to add child controls for 1x1 containers. + private Vector2i _pieceGridSize; + private TextureButton? _backButton; private bool _isDirty; @@ -408,11 +411,14 @@ public sealed class StorageWindow : BaseWindow _contained.Clear(); _contained.AddRange(storageComp.Container.ContainedEntities.Reverse()); + var width = boundingGrid.Width + 1; + var height = boundingGrid.Height + 1; + // Build the grid representation - if (_pieceGrid.Rows - 1 != boundingGrid.Height || _pieceGrid.Columns - 1 != boundingGrid.Width) + if (_pieceGrid.Rows != _pieceGridSize.Y || _pieceGrid.Columns != _pieceGridSize.X) { - _pieceGrid.Rows = boundingGrid.Height + 1; - _pieceGrid.Columns = boundingGrid.Width + 1; + _pieceGrid.Rows = height; + _pieceGrid.Columns = width; _controlGrid.Clear(); for (var y = boundingGrid.Bottom; y <= boundingGrid.Top; y++) @@ -430,6 +436,7 @@ public sealed class StorageWindow : BaseWindow } } + _pieceGridSize = new(width, height); _toRemove.Clear(); // Remove entities no longer relevant / Update existing ones diff --git a/Content.IntegrationTests/Tests/UserInterface/UiControlTest.cs b/Content.IntegrationTests/Tests/UserInterface/UiControlTest.cs index c8378bb661..5efa009ca7 100644 --- a/Content.IntegrationTests/Tests/UserInterface/UiControlTest.cs +++ b/Content.IntegrationTests/Tests/UserInterface/UiControlTest.cs @@ -1,5 +1,4 @@ using System.Linq; -using Content.Client.Chat.UI; using Content.Client.LateJoin; using Robust.Client.UserInterface.CustomControls; using Robust.Shared.ContentPack; @@ -14,7 +13,6 @@ public sealed class UiControlTest // You should not be adding to this. private Type[] _ignored = new Type[] { - typeof(EmotesMenu), typeof(LateJoinGui), }; diff --git a/Content.Server/Anomaly/Effects/ProjectileAnomalySystem.cs b/Content.Server/Anomaly/Effects/ProjectileAnomalySystem.cs index 23e0e472f0..7983493961 100644 --- a/Content.Server/Anomaly/Effects/ProjectileAnomalySystem.cs +++ b/Content.Server/Anomaly/Effects/ProjectileAnomalySystem.cs @@ -81,14 +81,14 @@ public sealed class ProjectileAnomalySystem : EntitySystem EntityCoordinates targetCoords, float severity) { - var mapPos = coords.ToMap(EntityManager, _xform); + var mapPos = _xform.ToMapCoordinates(coords); var spawnCoords = _mapManager.TryFindGridAt(mapPos, out var gridUid, out _) - ? coords.WithEntityId(gridUid, EntityManager) + ? _xform.WithEntityId(coords, gridUid) : new(_mapManager.GetMapEntityId(mapPos.MapId), mapPos.Position); var ent = Spawn(component.ProjectilePrototype, spawnCoords); - var direction = targetCoords.ToMapPos(EntityManager, _xform) - mapPos.Position; + var direction = _xform.ToMapCoordinates(targetCoords).Position - mapPos.Position; if (!TryComp(ent, out var comp)) return; diff --git a/Content.Server/Anomaly/Effects/TechAnomalySystem.cs b/Content.Server/Anomaly/Effects/TechAnomalySystem.cs index 983cf2c8f4..1f3a6520d4 100644 --- a/Content.Server/Anomaly/Effects/TechAnomalySystem.cs +++ b/Content.Server/Anomaly/Effects/TechAnomalySystem.cs @@ -16,7 +16,6 @@ public sealed class TechAnomalySystem : EntitySystem [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly BeamSystem _beam = default!; [Dependency] private readonly IGameTiming _timing = default!; - [Dependency] private readonly EmagSystem _emag = default!; public override void Initialize() { diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasPortableSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasPortableSystem.cs index 128754bbf8..c277352b6f 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasPortableSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasPortableSystem.cs @@ -1,10 +1,8 @@ using System.Diagnostics.CodeAnalysis; using Content.Server.Atmos.Piping.Binary.Components; using Content.Server.Atmos.Piping.Unary.Components; -using Content.Server.NodeContainer; using Content.Server.NodeContainer.EntitySystems; using Content.Server.NodeContainer.Nodes; -using Content.Shared.Atmos.Piping.Unary.Components; using Content.Shared.Construction.Components; using JetBrains.Annotations; using Robust.Shared.Map; @@ -16,7 +14,6 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems public sealed class GasPortableSystem : EntitySystem { [Dependency] private readonly SharedMapSystem _mapSystem = default!; - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly NodeContainerSystem _nodeContainer = default!; public override void Initialize() diff --git a/Content.Server/Chat/Managers/ChatManager.cs b/Content.Server/Chat/Managers/ChatManager.cs index 05c04ed0e0..ea490a2069 100644 --- a/Content.Server/Chat/Managers/ChatManager.cs +++ b/Content.Server/Chat/Managers/ChatManager.cs @@ -252,7 +252,7 @@ internal sealed partial class ChatManager : IChatManager Color? colorOverride = null; var wrappedMessage = Loc.GetString("chat-manager-send-ooc-wrap-message", ("playerName",player.Name), ("message", FormattedMessage.EscapeText(message))); - if (_adminManager.HasAdminFlag(player, AdminFlags.Admin)) + if (_adminManager.HasAdminFlag(player, AdminFlags.NameColor)) { var prefs = _preferencesManager.GetPreferences(player.UserId); colorOverride = prefs.AdminOOCColor; diff --git a/Content.Server/Dragon/DragonRiftSystem.cs b/Content.Server/Dragon/DragonRiftSystem.cs index d51721edc6..66e4b813d5 100644 --- a/Content.Server/Dragon/DragonRiftSystem.cs +++ b/Content.Server/Dragon/DragonRiftSystem.cs @@ -13,6 +13,7 @@ using Robust.Shared.Serialization.Manager; using System.Numerics; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; +using Robust.Shared.GameStates; using Robust.Shared.Utility; namespace Content.Server.Dragon; @@ -33,11 +34,20 @@ public sealed class DragonRiftSystem : EntitySystem { base.Initialize(); + SubscribeLocalEvent(OnGetState); SubscribeLocalEvent(OnExamined); SubscribeLocalEvent(OnAnchorChange); SubscribeLocalEvent(OnShutdown); } + private void OnGetState(Entity ent, ref ComponentGetState args) + { + args.State = new DragonRiftComponentState + { + State = ent.Comp.State, + }; + } + public override void Update(float frameTime) { base.Update(frameTime); diff --git a/Content.Server/GameTicking/GameTicker.Player.cs b/Content.Server/GameTicking/GameTicker.Player.cs index db459fa36e..f2635c6a9a 100644 --- a/Content.Server/GameTicking/GameTicker.Player.cs +++ b/Content.Server/GameTicking/GameTicker.Player.cs @@ -38,7 +38,7 @@ namespace Content.Server.GameTicking if (args.NewStatus != SessionStatus.Disconnected) { mind.Session = session; - _pvsOverride.AddSessionOverride(GetNetEntity(mindId.Value), session); + _pvsOverride.AddSessionOverride(mindId.Value, session); } DebugTools.Assert(mind.Session == session); diff --git a/Content.Server/GameTicking/GameTicker.RoundFlow.cs b/Content.Server/GameTicking/GameTicker.RoundFlow.cs index c90a24d6a8..96036f279b 100644 --- a/Content.Server/GameTicking/GameTicker.RoundFlow.cs +++ b/Content.Server/GameTicking/GameTicker.RoundFlow.cs @@ -200,7 +200,7 @@ namespace Content.Server.GameTicking if (ev.GameMap.IsGrid) { - var mapUid = _map.CreateMap(out mapId); + var mapUid = _map.CreateMap(out mapId, runMapInit: options?.InitializeMaps ?? false); if (!_loader.TryLoadGrid(mapId, ev.GameMap.MapPath, out var grid, @@ -562,7 +562,7 @@ namespace Content.Server.GameTicking if (TryGetEntity(mind.OriginalOwnedEntity, out var entity) && pvsOverride) { - _pvsOverride.AddGlobalOverride(GetNetEntity(entity.Value), recursive: true); + _pvsOverride.AddGlobalOverride(entity.Value); } var roles = _roles.MindGetAllRoleInfo(mindId); diff --git a/Content.Server/GameTicking/GameTicker.Spawning.cs b/Content.Server/GameTicking/GameTicker.Spawning.cs index e8c2c1a625..9862e665a3 100644 --- a/Content.Server/GameTicking/GameTicker.Spawning.cs +++ b/Content.Server/GameTicking/GameTicker.Spawning.cs @@ -458,7 +458,7 @@ namespace Content.Server.GameTicking // Ideally engine would just spawn them on grid directly I guess? Right now grid traversal is handling it during // update which means we need to add a hack somewhere around it. var spawn = _robustRandom.Pick(_possiblePositions); - var toMap = spawn.ToMap(EntityManager, _transform); + var toMap = _transform.ToMapCoordinates(spawn); if (_mapManager.TryFindGridAt(toMap, out var gridUid, out _)) { diff --git a/Content.Server/Guardian/GuardianSystem.cs b/Content.Server/Guardian/GuardianSystem.cs index 341993ce2f..e8c3fe7028 100644 --- a/Content.Server/Guardian/GuardianSystem.cs +++ b/Content.Server/Guardian/GuardianSystem.cs @@ -8,6 +8,7 @@ using Content.Shared.Examine; using Content.Shared.Guardian; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; +using Content.Shared.IdentityManagement; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Mobs; @@ -188,7 +189,9 @@ namespace Content.Server.Guardian // Can only inject things with the component... if (!HasComp(target)) { - _popupSystem.PopupEntity(Loc.GetString("guardian-activator-invalid-target"), user, user); + var msg = Loc.GetString("guardian-activator-invalid-target", ("entity", Identity.Entity(target, EntityManager, user))); + + _popupSystem.PopupEntity(msg, user, user); return; } diff --git a/Content.Server/Hands/Systems/HandsSystem.cs b/Content.Server/Hands/Systems/HandsSystem.cs index 41f582cde8..1e8e012c52 100644 --- a/Content.Server/Hands/Systems/HandsSystem.cs +++ b/Content.Server/Hands/Systems/HandsSystem.cs @@ -39,6 +39,15 @@ namespace Content.Server.Hands.Systems [Dependency] private readonly PullingSystem _pullingSystem = default!; [Dependency] private readonly ThrowingSystem _throwingSystem = default!; + private EntityQuery _physicsQuery; + + /// + /// Items dropped when the holder falls down will be launched in + /// a direction offset by up to this many degrees from the holder's + /// movement direction. + /// + private const float DropHeldItemsSpread = 45; + public override void Initialize() { base.Initialize(); @@ -60,6 +69,8 @@ namespace Content.Server.Hands.Systems CommandBinds.Builder .Bind(ContentKeyFunctions.ThrowItemInHand, new PointerInputCmdHandler(HandleThrowItem)) .Register(); + + _physicsQuery = GetEntityQuery(); } public override void Shutdown() @@ -234,13 +245,13 @@ namespace Content.Server.Hands.Systems private void OnDropHandItems(Entity entity, ref DropHandItemsEvent args) { - var direction = EntityManager.TryGetComponent(entity, out PhysicsComponent? comp) ? comp.LinearVelocity / 50 : Vector2.Zero; - var dropAngle = _random.NextFloat(0.8f, 1.2f); + // If the holder doesn't have a physics component, they ain't moving + var holderVelocity = _physicsQuery.TryComp(entity, out var physics) ? physics.LinearVelocity : Vector2.Zero; + var spreadMaxAngle = Angle.FromDegrees(DropHeldItemsSpread); var fellEvent = new FellDownEvent(entity); RaiseLocalEvent(entity, fellEvent, false); - var worldRotation = TransformSystem.GetWorldRotation(entity).ToVec(); foreach (var hand in entity.Comp.Hands.Values) { if (hand.HeldEntity is not EntityUid held) @@ -255,10 +266,26 @@ namespace Content.Server.Hands.Systems if (!TryDrop(entity, hand, null, checkActionBlocker: false, handsComp: entity.Comp)) continue; + // Rotate the item's throw vector a bit for each item + var angleOffset = _random.NextAngle(-spreadMaxAngle, spreadMaxAngle); + // Rotate the holder's velocity vector by the angle offset to get the item's velocity vector + var itemVelocity = angleOffset.RotateVec(holderVelocity); + // Decrease the distance of the throw by a random amount + itemVelocity *= _random.NextFloat(1f); + // Heavier objects don't get thrown as far + // If the item doesn't have a physics component, it isn't going to get thrown anyway, but we'll assume infinite mass + itemVelocity *= _physicsQuery.TryComp(held, out var heldPhysics) ? heldPhysics.InvMass : 0; + // Throw at half the holder's intentional throw speed and + // vary the speed a little to make it look more interesting + var throwSpeed = entity.Comp.BaseThrowspeed * _random.NextFloat(0.45f, 0.55f); + _throwingSystem.TryThrow(held, - _random.NextAngle().RotateVec(direction / dropAngle + worldRotation / 50), - 0.5f * dropAngle * _random.NextFloat(-0.9f, 1.1f), - entity, 0); + itemVelocity, + throwSpeed, + entity, + pushbackRatio: 0, + compensateFriction: false + ); } } diff --git a/Content.Server/Mapping/MappingCommand.cs b/Content.Server/Mapping/MappingCommand.cs index 12a7af4484..b44a09869e 100644 --- a/Content.Server/Mapping/MappingCommand.cs +++ b/Content.Server/Mapping/MappingCommand.cs @@ -2,8 +2,6 @@ using System.Linq; using Content.Server.Administration; using Content.Server.GameTicking; using Content.Shared.Administration; -using Content.Shared.CCVar; -using Robust.Shared.Configuration; using Robust.Shared.Console; using Robust.Shared.ContentPack; using Robust.Shared.EntitySerialization; @@ -19,7 +17,6 @@ namespace Content.Server.Mapping { [Dependency] private readonly IEntityManager _entities = default!; [Dependency] private readonly IMapManager _map = default!; - [Dependency] private readonly IConfigurationManager _cfg = default!; public string Command => "mapping"; public string Description => Loc.GetString("cmd-mapping-desc"); diff --git a/Content.Server/Mapping/MappingManager.cs b/Content.Server/Mapping/MappingManager.cs index 0097df2e55..3a46b301e8 100644 --- a/Content.Server/Mapping/MappingManager.cs +++ b/Content.Server/Mapping/MappingManager.cs @@ -1,12 +1,9 @@ -using System.IO; +using System.IO; using Content.Server.Administration.Managers; using Content.Shared.Administration; using Content.Shared.Mapping; -using Robust.Server.GameObjects; using Robust.Server.Player; -using Robust.Shared.EntitySerialization; using Robust.Shared.EntitySerialization.Systems; -using Robust.Shared.Map; using Robust.Shared.Network; using Robust.Shared.Serialization; using Robust.Shared.Utility; @@ -19,7 +16,6 @@ public sealed class MappingManager : IPostInjectInit { [Dependency] private readonly IAdminManager _admin = default!; [Dependency] private readonly ILogManager _log = default!; - [Dependency] private readonly IMapManager _map = default!; [Dependency] private readonly IServerNetManager _net = default!; [Dependency] private readonly IPlayerManager _players = default!; [Dependency] private readonly IEntitySystemManager _systems = default!; diff --git a/Content.Server/Medical/Stethoscope/Components/StethoscopeComponent.cs b/Content.Server/Medical/Stethoscope/Components/StethoscopeComponent.cs deleted file mode 100644 index d7e971e953..0000000000 --- a/Content.Server/Medical/Stethoscope/Components/StethoscopeComponent.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Server.Medical.Stethoscope.Components -{ - /// - /// Adds an innate verb when equipped to use a stethoscope. - /// - [RegisterComponent] - public sealed partial class StethoscopeComponent : Component - { - public bool IsActive = false; - - [DataField("delay")] - public float Delay = 2.5f; - - [DataField("action", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string Action = "ActionStethoscope"; - - [DataField("actionEntity")] public EntityUid? ActionEntity; - } -} diff --git a/Content.Server/Medical/Stethoscope/Components/WearingStethoscopeComponent.cs b/Content.Server/Medical/Stethoscope/Components/WearingStethoscopeComponent.cs deleted file mode 100644 index dfce294a73..0000000000 --- a/Content.Server/Medical/Stethoscope/Components/WearingStethoscopeComponent.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Threading; - -namespace Content.Server.Medical.Components -{ - /// - /// Used to let doctors use the stethoscope on people. - /// - [RegisterComponent] - public sealed partial class WearingStethoscopeComponent : Component - { - public CancellationTokenSource? CancelToken; - - [DataField("delay")] - public float Delay = 2.5f; - - public EntityUid Stethoscope = default!; - } -} diff --git a/Content.Server/Medical/Stethoscope/StethoscopeSystem.cs b/Content.Server/Medical/Stethoscope/StethoscopeSystem.cs deleted file mode 100644 index b8304c562a..0000000000 --- a/Content.Server/Medical/Stethoscope/StethoscopeSystem.cs +++ /dev/null @@ -1,153 +0,0 @@ -using Content.Server.Body.Components; -using Content.Server.Medical.Components; -using Content.Server.Medical.Stethoscope.Components; -using Content.Server.Popups; -using Content.Shared.Actions; -using Content.Shared.Clothing; -using Content.Shared.Damage; -using Content.Shared.DoAfter; -using Content.Shared.FixedPoint; -using Content.Shared.Medical; -using Content.Shared.Medical.Stethoscope; -using Content.Shared.Mobs.Components; -using Content.Shared.Mobs.Systems; -using Content.Shared.Verbs; -using Robust.Shared.Utility; - -namespace Content.Server.Medical.Stethoscope -{ - public sealed class StethoscopeSystem : EntitySystem - { - [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; - [Dependency] private readonly MobStateSystem _mobStateSystem = default!; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnEquipped); - SubscribeLocalEvent(OnUnequipped); - SubscribeLocalEvent>(AddStethoscopeVerb); - SubscribeLocalEvent(OnGetActions); - SubscribeLocalEvent(OnStethoscopeAction); - SubscribeLocalEvent(OnDoAfter); - } - - /// - /// Add the component the verb event subs to if the equippee is wearing the stethoscope. - /// - private void OnEquipped(EntityUid uid, StethoscopeComponent component, ref ClothingGotEquippedEvent args) - { - component.IsActive = true; - - var wearingComp = EnsureComp(args.Wearer); - wearingComp.Stethoscope = uid; - } - - private void OnUnequipped(EntityUid uid, StethoscopeComponent component, ref ClothingGotUnequippedEvent args) - { - if (!component.IsActive) - return; - - RemComp(args.Wearer); - component.IsActive = false; - } - - /// - /// This is raised when someone with WearingStethoscopeComponent requests verbs on an item. - /// It returns if the target is not a mob. - /// - private void AddStethoscopeVerb(EntityUid uid, WearingStethoscopeComponent component, GetVerbsEvent args) - { - if (!args.CanInteract || !args.CanAccess) - return; - - if (!HasComp(args.Target)) - return; - - if (component.CancelToken != null) - return; - - if (!TryComp(component.Stethoscope, out var stetho)) - return; - - InnateVerb verb = new() - { - Act = () => - { - StartListening(component.Stethoscope, uid, args.Target, stetho); // start doafter - }, - Text = Loc.GetString("stethoscope-verb"), - Icon = new SpriteSpecifier.Rsi(new ("Clothing/Neck/Misc/stethoscope.rsi"), "icon"), - Priority = 2 - }; - args.Verbs.Add(verb); - } - - - private void OnStethoscopeAction(EntityUid uid, StethoscopeComponent component, StethoscopeActionEvent args) - { - StartListening(uid, args.Performer, args.Target, component); - } - - private void OnGetActions(EntityUid uid, StethoscopeComponent component, GetItemActionsEvent args) - { - args.AddAction(ref component.ActionEntity, component.Action); - } - - // construct the doafter and start it - private void StartListening(EntityUid scope, EntityUid user, EntityUid target, StethoscopeComponent comp) - { - _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, user, comp.Delay, new StethoscopeDoAfterEvent(), scope, target: target, used: scope) - { - NeedHand = true, - BreakOnMove = true, - }); - } - - private void OnDoAfter(EntityUid uid, StethoscopeComponent component, DoAfterEvent args) - { - if (args.Handled || args.Cancelled || args.Args.Target == null) - return; - - ExamineWithStethoscope(args.Args.User, args.Args.Target.Value); - } - - /// - /// Return a value based on the total oxyloss of the target. - /// Could be expanded in the future with reagent effects etc. - /// The loc lines are taken from the goon wiki. - /// - public void ExamineWithStethoscope(EntityUid user, EntityUid target) - { - // The mob check seems a bit redundant but (1) they could conceivably have lost it since when the doafter started and (2) I need it for .IsDead() - if (!HasComp(target) || !TryComp(target, out var mobState) || _mobStateSystem.IsDead(target, mobState)) - { - _popupSystem.PopupEntity(Loc.GetString("stethoscope-dead"), target, user); - return; - } - - if (!TryComp(target, out var damage)) - return; - // these should probably get loc'd at some point before a non-english fork accidentally breaks a bunch of stuff that does this - if (!damage.Damage.DamageDict.TryGetValue("Asphyxiation", out var value)) - return; - - var message = GetDamageMessage(value); - - _popupSystem.PopupEntity(Loc.GetString(message), target, user); - } - - private string GetDamageMessage(FixedPoint2 totalOxyloss) - { - var msg = (int) totalOxyloss switch - { - < 20 => "stethoscope-normal", - < 60 => "stethoscope-hyper", - < 80 => "stethoscope-irregular", - _ => "stethoscope-fucked" - }; - return msg; - } - } -} diff --git a/Content.Server/Mind/MindSystem.cs b/Content.Server/Mind/MindSystem.cs index 2447d88641..1b55a533e3 100644 --- a/Content.Server/Mind/MindSystem.cs +++ b/Content.Server/Mind/MindSystem.cs @@ -85,11 +85,11 @@ public sealed class MindSystem : SharedMindSystem { if (base.TryGetMind(user, out mindId, out mind)) { - DebugTools.Assert(_players.GetPlayerData(user).ContentData() is not { } data || data.Mind == mindId); + DebugTools.Assert(!_players.TryGetPlayerData(user, out var playerData) || playerData.ContentData() is not { } data || data.Mind == mindId); return true; } - DebugTools.Assert(_players.GetPlayerData(user).ContentData()?.Mind == null); + DebugTools.Assert(!_players.TryGetPlayerData(user, out var pData) || pData.ContentData()?.Mind == null); return false; } diff --git a/Content.Server/Movement/RotateEyesCommand.cs b/Content.Server/Movement/RotateEyesCommand.cs index 6395b93cab..733d341820 100644 --- a/Content.Server/Movement/RotateEyesCommand.cs +++ b/Content.Server/Movement/RotateEyesCommand.cs @@ -28,14 +28,15 @@ public sealed class RotateEyesCommand : IConsoleCommand } var count = 0; - - foreach (var mover in entManager.EntityQuery(true)) + var query = entManager.EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var mover)) { if (mover.TargetRelativeRotation.Equals(rotation)) continue; mover.TargetRelativeRotation = rotation; - entManager.Dirty(mover); + + entManager.Dirty(uid, mover); count++; } diff --git a/Content.Server/Movement/Systems/BoundarySystem.cs b/Content.Server/Movement/Systems/BoundarySystem.cs index a798f1052d..a82348926e 100644 --- a/Content.Server/Movement/Systems/BoundarySystem.cs +++ b/Content.Server/Movement/Systems/BoundarySystem.cs @@ -27,6 +27,6 @@ public sealed class BoundarySystem : EntitySystem // If for whatever reason you want to yeet them to the other side. // offset = new Angle(MathF.PI).RotateVec(offset); - _xform.SetWorldPosition(otherXform, center + offset); + _xform.SetWorldPosition((args.OtherEntity, otherXform), center + offset); } } diff --git a/Content.Server/Movement/Systems/PullController.cs b/Content.Server/Movement/Systems/PullController.cs index f28ea952c8..40345a5867 100644 --- a/Content.Server/Movement/Systems/PullController.cs +++ b/Content.Server/Movement/Systems/PullController.cs @@ -139,7 +139,7 @@ public sealed class PullController : VirtualController // Cap the distance var range = 2f; - var fromUserCoords = coords.WithEntityId(player, EntityManager); + var fromUserCoords = _transformSystem.WithEntityId(coords, player); var userCoords = new EntityCoordinates(player, Vector2.Zero); if (!_transformSystem.InRange(coords, userCoords, range)) @@ -157,7 +157,7 @@ public sealed class PullController : VirtualController } fromUserCoords = new EntityCoordinates(player, direction.Normalized() * (range - 0.01f)); - coords = fromUserCoords.WithEntityId(coords.EntityId); + coords = _transformSystem.WithEntityId(fromUserCoords, coords.EntityId); } var moving = EnsureComp(pulled!.Value); @@ -248,7 +248,7 @@ public sealed class PullController : VirtualController var pullerXform = _xformQuery.Get(puller); var pullerPosition = TransformSystem.GetMapCoordinates(pullerXform); - var movingTo = mover.MovingTo.ToMap(EntityManager, TransformSystem); + var movingTo = TransformSystem.ToMapCoordinates(mover.MovingTo); if (movingTo.MapId != pullerPosition.MapId) { diff --git a/Content.Server/Polymorph/Components/PolymorphOnCollideComponent.cs b/Content.Server/Polymorph/Components/PolymorphOnCollideComponent.cs deleted file mode 100644 index 577dadb5c8..0000000000 --- a/Content.Server/Polymorph/Components/PolymorphOnCollideComponent.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Content.Server.Polymorph.Systems; -using Content.Shared.Polymorph; -using Content.Shared.Whitelist; -using Robust.Shared.Audio; -using Robust.Shared.Prototypes; - -namespace Content.Server.Polymorph.Components; - -[RegisterComponent] -[Access(typeof(PolymorphSystem))] -public sealed partial class PolymorphOnCollideComponent : Component -{ - [DataField(required: true)] - public ProtoId Polymorph; - - [DataField(required: true)] - public EntityWhitelist Whitelist = default!; - - [DataField] - public EntityWhitelist? Blacklist; - - [DataField] - public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Magic/forcewall.ogg"); -} diff --git a/Content.Server/Rotatable/RotatableSystem.cs b/Content.Server/Rotatable/RotatableSystem.cs index 63b5e44c3d..85681535ca 100644 --- a/Content.Server/Rotatable/RotatableSystem.cs +++ b/Content.Server/Rotatable/RotatableSystem.cs @@ -21,6 +21,7 @@ namespace Content.Server.Rotatable [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; [Dependency] private readonly SharedInteractionSystem _interaction = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; public override void Initialize() { @@ -112,7 +113,7 @@ namespace Content.Server.Rotatable var entity = EntityManager.SpawnEntity(component.MirrorEntity, oldTransform.Coordinates); var newTransform = EntityManager.GetComponent(entity); newTransform.LocalRotation = oldTransform.LocalRotation; - newTransform.Anchored = false; + _transform.Unanchor(entity, newTransform); EntityManager.DeleteEntity(uid); } diff --git a/Content.Server/Teleportation/HandTeleporterSystem.cs b/Content.Server/Teleportation/HandTeleporterSystem.cs index 5c9baf1854..aa4f7eec82 100644 --- a/Content.Server/Teleportation/HandTeleporterSystem.cs +++ b/Content.Server/Teleportation/HandTeleporterSystem.cs @@ -95,6 +95,10 @@ public sealed class HandTeleporterSystem : EntitySystem var timeout = EnsureComp(user); timeout.EnteredPortal = null; component.FirstPortal = Spawn(component.FirstPortalPrototype, Transform(user).Coordinates); + + if (component.AllowPortalsOnDifferentMaps && TryComp(component.FirstPortal, out var portal)) + portal.CanTeleportToOtherMaps = true; + _adminLogger.Add(LogType.EntitySpawn, LogImpact.High, $"{ToPrettyString(user):player} opened {ToPrettyString(component.FirstPortal.Value)} at {Transform(component.FirstPortal.Value).Coordinates} using {ToPrettyString(uid)}"); _audio.PlayPvs(component.NewPortalSound, uid); } @@ -113,6 +117,10 @@ public sealed class HandTeleporterSystem : EntitySystem var timeout = EnsureComp(user); timeout.EnteredPortal = null; component.SecondPortal = Spawn(component.SecondPortalPrototype, Transform(user).Coordinates); + + if (component.AllowPortalsOnDifferentMaps && TryComp(component.SecondPortal, out var portal)) + portal.CanTeleportToOtherMaps = true; + _adminLogger.Add(LogType.EntitySpawn, LogImpact.High, $"{ToPrettyString(user):player} opened {ToPrettyString(component.SecondPortal.Value)} at {Transform(component.SecondPortal.Value).Coordinates} linked to {ToPrettyString(component.FirstPortal!.Value)} using {ToPrettyString(uid)}"); _link.TryLink(component.FirstPortal!.Value, component.SecondPortal.Value, true); _audio.PlayPvs(component.NewPortalSound, uid); diff --git a/Content.Server/Temperature/Systems/EntityHeaterSystem.cs b/Content.Server/Temperature/Systems/EntityHeaterSystem.cs index c4b5b72a9c..8452edf8e9 100644 --- a/Content.Server/Temperature/Systems/EntityHeaterSystem.cs +++ b/Content.Server/Temperature/Systems/EntityHeaterSystem.cs @@ -1,45 +1,41 @@ using Content.Server.Power.Components; -using Content.Server.Temperature.Components; -using Content.Shared.Examine; using Content.Shared.Placeable; -using Content.Shared.Popups; -using Content.Shared.Power; using Content.Shared.Temperature; -using Content.Shared.Verbs; -using Robust.Server.Audio; +using Content.Shared.Temperature.Components; +using Content.Shared.Temperature.Systems; namespace Content.Server.Temperature.Systems; /// -/// Handles updating and events. +/// Handles the server-only parts of /// -public sealed class EntityHeaterSystem : EntitySystem +public sealed class EntityHeaterSystem : SharedEntityHeaterSystem { - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly TemperatureSystem _temperature = default!; - [Dependency] private readonly AudioSystem _audio = default!; - - private readonly int SettingCount = Enum.GetValues(typeof(EntityHeaterSetting)).Length; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnExamined); - SubscribeLocalEvent>(OnGetVerbs); - SubscribeLocalEvent(OnPowerChanged); + SubscribeLocalEvent(OnMapInit); + } + + private void OnMapInit(Entity ent, ref MapInitEvent args) + { + // Set initial power level + if (TryComp(ent, out var power)) + power.Load = SettingPower(ent.Comp.Setting, ent.Comp.Power); } public override void Update(float deltaTime) { var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var comp, out var placer, out var power)) + while (query.MoveNext(out _, out _, out var placer, out var power)) { if (!power.Powered) continue; - // don't divide by total entities since its a big grill + // don't divide by total entities since it's a big grill // excess would just be wasted in the air but that's not worth simulating // if you want a heater thermomachine just use that... var energy = power.PowerReceived * deltaTime; @@ -50,66 +46,17 @@ public sealed class EntityHeaterSystem : EntitySystem } } - private void OnExamined(EntityUid uid, EntityHeaterComponent comp, ExaminedEvent args) + /// + /// doesn't exist on the client, so we need + /// this server-only override to handle setting the network load. + /// + protected override void ChangeSetting(Entity ent, EntityHeaterSetting setting, EntityUid? user = null) { - if (!args.IsInDetailsRange) + base.ChangeSetting(ent, setting, user); + + if (!TryComp(ent, out var power)) return; - args.PushMarkup(Loc.GetString("entity-heater-examined", ("setting", comp.Setting))); - } - - private void OnGetVerbs(EntityUid uid, EntityHeaterComponent comp, GetVerbsEvent args) - { - if (!args.CanAccess || !args.CanInteract) - return; - - var setting = (int) comp.Setting; - setting++; - setting %= SettingCount; - var nextSetting = (EntityHeaterSetting) setting; - - args.Verbs.Add(new AlternativeVerb() - { - Text = Loc.GetString("entity-heater-switch-setting", ("setting", nextSetting)), - Act = () => - { - ChangeSetting(uid, nextSetting, comp); - _popup.PopupEntity(Loc.GetString("entity-heater-switched-setting", ("setting", nextSetting)), uid, args.User); - } - }); - } - - private void OnPowerChanged(EntityUid uid, EntityHeaterComponent comp, ref PowerChangedEvent args) - { - // disable heating element glowing layer if theres no power - // doesn't actually turn it off since that would be annoying - var setting = args.Powered ? comp.Setting : EntityHeaterSetting.Off; - _appearance.SetData(uid, EntityHeaterVisuals.Setting, setting); - } - - private void ChangeSetting(EntityUid uid, EntityHeaterSetting setting, EntityHeaterComponent? comp = null, ApcPowerReceiverComponent? power = null) - { - if (!Resolve(uid, ref comp, ref power)) - return; - - comp.Setting = setting; - power.Load = SettingPower(setting, comp.Power); - _appearance.SetData(uid, EntityHeaterVisuals.Setting, setting); - _audio.PlayPvs(comp.SettingSound, uid); - } - - private float SettingPower(EntityHeaterSetting setting, float max) - { - switch (setting) - { - case EntityHeaterSetting.Low: - return max / 3f; - case EntityHeaterSetting.Medium: - return max * 2f / 3f; - case EntityHeaterSetting.High: - return max; - default: - return 0f; - } + power.Load = SettingPower(setting, ent.Comp.Power); } } diff --git a/Content.Server/Thief/Components/ThiefUndeterminedBackpackComponent.cs b/Content.Server/Thief/Components/ThiefUndeterminedBackpackComponent.cs index 64f88df657..9080caa245 100644 --- a/Content.Server/Thief/Components/ThiefUndeterminedBackpackComponent.cs +++ b/Content.Server/Thief/Components/ThiefUndeterminedBackpackComponent.cs @@ -22,11 +22,18 @@ public sealed partial class ThiefUndeterminedBackpackComponent : Component public List SelectedSets = new(); [DataField] - public SoundSpecifier ApproveSound = new SoundPathSpecifier("/Audio/Effects/rustle1.ogg"); + public SoundCollectionSpecifier ApproveSound = new SoundCollectionSpecifier("storageRustle"); /// /// Max number of sets you can select. /// [DataField] public int MaxSelectedSets = 2; + + /// + /// What entity all the spawned items will appear inside of + /// If null, will instead drop on the ground. + /// + [DataField] + public EntProtoId? SpawnedStoragePrototype; } diff --git a/Content.Server/Thief/Systems/ThiefUndeterminedBackpackSystem.cs b/Content.Server/Thief/Systems/ThiefUndeterminedBackpackSystem.cs index 3248a6b9c8..23f845a2e7 100644 --- a/Content.Server/Thief/Systems/ThiefUndeterminedBackpackSystem.cs +++ b/Content.Server/Thief/Systems/ThiefUndeterminedBackpackSystem.cs @@ -1,5 +1,7 @@ using Content.Server.Thief.Components; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Item; +using Content.Shared.Storage.EntitySystems; using Content.Shared.Thief; using Robust.Server.GameObjects; using Robust.Server.Audio; @@ -17,6 +19,8 @@ public sealed class ThiefUndeterminedBackpackSystem : EntitySystem [Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly UserInterfaceSystem _ui = default!; + [Dependency] private readonly SharedStorageSystem _storage = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; public override void Initialize() { @@ -37,6 +41,10 @@ public sealed class ThiefUndeterminedBackpackSystem : EntitySystem if (backpack.Comp.SelectedSets.Count != backpack.Comp.MaxSelectedSets) return; + EntityUid? spawnedStorage = null; + if (backpack.Comp.SpawnedStoragePrototype != null) + spawnedStorage = Spawn(backpack.Comp.SpawnedStoragePrototype, _transform.GetMapCoordinates(backpack.Owner)); + foreach (var i in backpack.Comp.SelectedSets) { var set = _proto.Index(backpack.Comp.PossibleSets[i]); @@ -44,10 +52,20 @@ public sealed class ThiefUndeterminedBackpackSystem : EntitySystem { var ent = Spawn(item, _transform.GetMapCoordinates(backpack.Owner)); if (TryComp(ent, out var itemComponent)) - _transform.DropNextTo(ent, backpack.Owner); + { + if (spawnedStorage != null) + _storage.Insert(spawnedStorage.Value, ent, out _, playSound: false); + else + _transform.DropNextTo(ent, backpack.Owner); + } } } - _audio.PlayPvs(backpack.Comp.ApproveSound, backpack.Owner); + + if (spawnedStorage != null) + _hands.TryPickupAnyHand(args.Actor, spawnedStorage.Value); + + // Play the sound on coordinates of the backpack/toolbox. The reason being, since we immediately delete it, the sound gets deleted alongside it. + _audio.PlayPvs(backpack.Comp.ApproveSound, Transform(backpack.Owner).Coordinates); QueueDel(backpack); } private void OnChangeSet(Entity backpack, ref ThiefBackpackChangeSetMessage args) diff --git a/Content.Server/Zombies/ZombieSystem.Transform.cs b/Content.Server/Zombies/ZombieSystem.Transform.cs index 5c64d3dae5..246d8656ee 100644 --- a/Content.Server/Zombies/ZombieSystem.Transform.cs +++ b/Content.Server/Zombies/ZombieSystem.Transform.cs @@ -139,6 +139,16 @@ public sealed partial class ZombieSystem melee.Angle = 0.0f; melee.HitSound = zombiecomp.BiteSound; + DirtyFields(target, melee, null, fields: + [ + nameof(MeleeWeaponComponent.Animation), + nameof(MeleeWeaponComponent.WideAnimation), + nameof(MeleeWeaponComponent.AltDisarm), + nameof(MeleeWeaponComponent.Range), + nameof(MeleeWeaponComponent.Angle), + nameof(MeleeWeaponComponent.HitSound), + ]); + // Sunrise-Start RemComp(target); diff --git a/Content.Shared/CCVar/CCVars.Vote.cs b/Content.Shared/CCVar/CCVars.Vote.cs index 4803fee9c6..d0006f3a1f 100644 --- a/Content.Shared/CCVar/CCVars.Vote.cs +++ b/Content.Shared/CCVar/CCVars.Vote.cs @@ -146,13 +146,13 @@ public sealed partial class CCVars /// The delay for which two votekicks are allowed to be made by separate people, in seconds. /// public static readonly CVarDef VotekickTimeout = - CVarDef.Create("votekick.timeout", 120f, CVar.SERVERONLY); + CVarDef.Create("votekick.timeout", 60f, CVar.SERVERONLY); /// /// Sets the duration of the votekick vote timer. /// public static readonly CVarDef - VotekickTimer = CVarDef.Create("votekick.timer", 60, CVar.SERVERONLY); + VotekickTimer = CVarDef.Create("votekick.timer", 45, CVar.SERVERONLY); /// /// Config for how many hours playtime a player must have to get protection from the Raider votekick type when playing as an antag. diff --git a/Content.Shared/Cuffs/SharedCuffableSystem.cs b/Content.Shared/Cuffs/SharedCuffableSystem.cs index 0006ec2c5f..9f5f1f1217 100644 --- a/Content.Shared/Cuffs/SharedCuffableSystem.cs +++ b/Content.Shared/Cuffs/SharedCuffableSystem.cs @@ -5,6 +5,7 @@ using Content.Shared.Administration.Components; using Content.Shared.Administration.Logs; using Content.Shared.Alert; using Content.Shared.Buckle.Components; +using Content.Shared.CombatMode; using Content.Shared.Cuffs.Components; using Content.Shared.Database; using Content.Shared.DoAfter; @@ -54,6 +55,7 @@ namespace Content.Shared.Cuffs [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly UseDelaySystem _delay = default!; + [Dependency] private readonly SharedCombatModeSystem _combatMode = default!; public override void Initialize() { @@ -746,10 +748,31 @@ namespace Content.Shared.Cuffs } } + var shoved = false; + // if combat mode is on, shove the person. + if (_combatMode.IsInCombatMode(user) && target != user && user != null) + { + var eventArgs = new DisarmedEvent { Target = target, Source = user.Value, PushProbability = 1}; + RaiseLocalEvent(target, eventArgs); + shoved = true; + } + if (cuffable.CuffedHandCount == 0) { if (user != null) - _popup.PopupClient(Loc.GetString("cuffable-component-remove-cuffs-success-message"), user.Value, user.Value); + { + if (shoved) + { + _popup.PopupClient(Loc.GetString("cuffable-component-remove-cuffs-push-success-message", + ("otherName", Identity.Name(user.Value, EntityManager, user))), + user.Value, + user.Value); + } + else + { + _popup.PopupClient(Loc.GetString("cuffable-component-remove-cuffs-success-message"), user.Value, user.Value); + } + } if (target != user && user != null) { diff --git a/Content.Shared/Inventory/InventorySystem.Relay.cs b/Content.Shared/Inventory/InventorySystem.Relay.cs index abe4eecd66..bc93f76317 100644 --- a/Content.Shared/Inventory/InventorySystem.Relay.cs +++ b/Content.Shared/Inventory/InventorySystem.Relay.cs @@ -73,6 +73,8 @@ public partial class InventorySystem SubscribeLocalEvent>(RefRelayInventoryEvent); SubscribeLocalEvent>(OnGetEquipmentVerbs); + SubscribeLocalEvent>(OnGetInnateVerbs); + } protected void RefRelayInventoryEvent(EntityUid uid, InventoryComponent component, ref T args) where T : IInventoryRelayEvent @@ -127,6 +129,17 @@ public partial class InventorySystem } } + private void OnGetInnateVerbs(EntityUid uid, InventoryComponent component, GetVerbsEvent args) + { + // Automatically relay stripping related verbs to all equipped clothing. + var ev = new InventoryRelayedEvent>(args); + var enumerator = new InventorySlotEnumerator(component, SlotFlags.WITHOUT_POCKET); + while (enumerator.NextItem(out var item)) + { + RaiseLocalEvent(item, ev); + } + } + } /// diff --git a/Content.Shared/Maps/TurfHelpers.cs b/Content.Shared/Maps/TurfHelpers.cs index 71bbb35db7..dfa12f3d8f 100644 --- a/Content.Shared/Maps/TurfHelpers.cs +++ b/Content.Shared/Maps/TurfHelpers.cs @@ -23,7 +23,7 @@ namespace Content.Shared.Maps return null; mapManager ??= IoCManager.Resolve(); - var pos = coordinates.ToMap(entityManager, entityManager.System()); + var pos = entityManager.System().ToMapCoordinates(coordinates); if (!mapManager.TryFindGridAt(pos, out _, out var grid)) return null; diff --git a/Content.Shared/Medical/Stethoscope/Components/StethoscopeComponent.cs b/Content.Shared/Medical/Stethoscope/Components/StethoscopeComponent.cs new file mode 100644 index 0000000000..7f740ef39c --- /dev/null +++ b/Content.Shared/Medical/Stethoscope/Components/StethoscopeComponent.cs @@ -0,0 +1,31 @@ +using Content.Shared.FixedPoint; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Medical.Stethoscope.Components; + +/// +/// Adds a verb and action that allows the user to listen to the entity's breathing. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class StethoscopeComponent : Component +{ + /// + /// Time between each use of the stethoscope. + /// + [DataField] + public TimeSpan Delay = TimeSpan.FromSeconds(1.75); + + /// + /// Last damage that was measured. Used to indicate if breathing is improving or getting worse. + /// + [DataField] + public FixedPoint2? LastMeasuredDamage; + + [DataField] + public EntProtoId Action = "ActionStethoscope"; + + [DataField] + public EntityUid? ActionEntity; +} + diff --git a/Content.Shared/Medical/Stethoscope/StethoscopeActionEvent.cs b/Content.Shared/Medical/Stethoscope/StethoscopeActionEvent.cs deleted file mode 100644 index 11ac8a2684..0000000000 --- a/Content.Shared/Medical/Stethoscope/StethoscopeActionEvent.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Content.Shared.Actions; - -namespace Content.Shared.Medical.Stethoscope; - -public sealed partial class StethoscopeActionEvent : EntityTargetActionEvent -{ -} diff --git a/Content.Shared/Medical/Stethoscope/StethoscopeSystem.cs b/Content.Shared/Medical/Stethoscope/StethoscopeSystem.cs new file mode 100644 index 0000000000..01d61aa06e --- /dev/null +++ b/Content.Shared/Medical/Stethoscope/StethoscopeSystem.cs @@ -0,0 +1,148 @@ +using Content.Shared.Actions; +using Content.Shared.Damage; +using Content.Shared.DoAfter; +using Content.Shared.FixedPoint; +using Content.Shared.Inventory; +using Content.Shared.Medical.Stethoscope.Components; +using Content.Shared.Mobs.Components; +using Content.Shared.Mobs.Systems; +using Content.Shared.Popups; +using Content.Shared.Verbs; +using Robust.Shared.Containers; + +namespace Content.Shared.Medical.Stethoscope; + +public sealed class StethoscopeSystem : EntitySystem +{ + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly SharedContainerSystem _container = default!; + + // The damage type to "listen" for with the stethoscope. + private const string DamageToListenFor = "Asphyxiation"; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent>>(AddStethoscopeVerb); + SubscribeLocalEvent(OnGetActions); + SubscribeLocalEvent(OnStethoscopeAction); + SubscribeLocalEvent(OnDoAfter); + } + + private void OnGetActions(Entity ent, ref GetItemActionsEvent args) + { + args.AddAction(ref ent.Comp.ActionEntity, ent.Comp.Action); + } + + private void OnStethoscopeAction(Entity ent, ref StethoscopeActionEvent args) + { + StartListening(ent, args.Target); + } + + private void AddStethoscopeVerb(Entity ent, ref InventoryRelayedEvent> args) + { + if (!args.Args.CanInteract || !args.Args.CanAccess) + return; + + if (!HasComp(args.Args.Target)) + return; + + var target = args.Args.Target; + + InnateVerb verb = new() + { + Act = () => StartListening(ent, target), + Text = Loc.GetString("stethoscope-verb"), + IconEntity = GetNetEntity(ent), + Priority = 2, + }; + args.Args.Verbs.Add(verb); + } + + private void StartListening(Entity ent, EntityUid target) + { + if (!_container.TryGetContainingContainer((ent, null, null), out var container)) + return; + + _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, container.Owner, ent.Comp.Delay, new StethoscopeDoAfterEvent(), ent, target: target, used: ent) + { + DuplicateCondition = DuplicateConditions.SameEvent, + BreakOnMove = true, + Hidden = true, + BreakOnHandChange = false, + }); + } + + private void OnDoAfter(Entity ent, ref StethoscopeDoAfterEvent args) + { + var target = args.Target; + + if (args.Handled || target == null || args.Cancelled) + { + ent.Comp.LastMeasuredDamage = null; + return; + } + + ExamineWithStethoscope(ent, args.Args.User, target.Value); + + args.Repeat = true; + } + + private void ExamineWithStethoscope(Entity stethoscope, EntityUid user, EntityUid target) + { + // TODO: Add check for respirator component when it gets moved to shared. + // If the mob is dead or cannot asphyxiation damage, the popup shows nothing. + if (!TryComp(target, out var mobState) || + !TryComp(target, out var damageComp) || + _mobState.IsDead(target, mobState) || + !damageComp.Damage.DamageDict.TryGetValue(DamageToListenFor, out var asphyxDmg)) + { + _popup.PopupPredicted(Loc.GetString("stethoscope-nothing"), target, user); + stethoscope.Comp.LastMeasuredDamage = null; + return; + } + + var absString = GetAbsoluteDamageString(asphyxDmg); + + // Don't show the change if this is the first time listening. + if (stethoscope.Comp.LastMeasuredDamage == null) + { + _popup.PopupPredicted(absString, target, user); + } + else + { + var deltaString = GetDeltaDamageString(stethoscope.Comp.LastMeasuredDamage.Value, asphyxDmg); + _popup.PopupPredicted(Loc.GetString("stethoscope-combined-status", ("absolute", absString), ("delta", deltaString)), target, user); + } + + stethoscope.Comp.LastMeasuredDamage = asphyxDmg; + } + + private string GetAbsoluteDamageString(FixedPoint2 asphyxDmg) + { + var msg = (int) asphyxDmg switch + { + < 10 => "stethoscope-normal", + < 30 => "stethoscope-raggedy", + < 60 => "stethoscope-hyper", + < 80 => "stethoscope-irregular", + _ => "stethoscope-fucked", + }; + return Loc.GetString(msg); + } + + private string GetDeltaDamageString(FixedPoint2 lastDamage, FixedPoint2 currentDamage) + { + if (lastDamage > currentDamage) + return Loc.GetString("stethoscope-delta-improving"); + if (lastDamage < currentDamage) + return Loc.GetString("stethoscope-delta-worsening"); + return Loc.GetString("stethoscope-delta-steady"); + } + +} + +public sealed partial class StethoscopeActionEvent : EntityTargetActionEvent; diff --git a/Content.Shared/Medical/StethoscopeDoAfterEvent.cs b/Content.Shared/Medical/StethoscopeDoAfterEvent.cs index aeb1c133cf..d3f3962958 100644 --- a/Content.Shared/Medical/StethoscopeDoAfterEvent.cs +++ b/Content.Shared/Medical/StethoscopeDoAfterEvent.cs @@ -4,6 +4,4 @@ using Robust.Shared.Serialization; namespace Content.Shared.Medical; [Serializable, NetSerializable] -public sealed partial class StethoscopeDoAfterEvent : SimpleDoAfterEvent -{ -} +public sealed partial class StethoscopeDoAfterEvent : SimpleDoAfterEvent; diff --git a/Content.Shared/Movement/Components/ActiveJetpackComponent.cs b/Content.Shared/Movement/Components/ActiveJetpackComponent.cs index 615dc3aee4..03c2a8345d 100644 --- a/Content.Shared/Movement/Components/ActiveJetpackComponent.cs +++ b/Content.Shared/Movement/Components/ActiveJetpackComponent.cs @@ -1,4 +1,5 @@ using Robust.Shared.GameStates; +using Robust.Shared.Map; namespace Content.Shared.Movement.Components; @@ -9,5 +10,10 @@ namespace Content.Shared.Movement.Components; public sealed partial class ActiveJetpackComponent : Component { public float EffectCooldown = 0.3f; + + public float MaxDistance = 0.7f; + + public EntityCoordinates LastCoordinates; + public TimeSpan TargetTime = TimeSpan.Zero; } diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs index 8acfb56376..afdf9c2b6d 100644 --- a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs @@ -1,4 +1,3 @@ -using System.Diagnostics.CodeAnalysis; using Content.Shared.Actions.Events; using Content.Shared.IdentityManagement; using Content.Shared.Interaction.Events; @@ -122,6 +121,14 @@ public abstract partial class SharedStationAiSystem if (ev.Actor == ev.Target) return; + // no need to show menu if device is not powered. + if (!PowerReceiver.IsPowered(ev.Target)) + { + ShowDeviceNotRespondingPopup(ev.Actor); + ev.Cancel(); + return; + } + if (TryComp(ev.Actor, out StationAiHeldComponent? aiComp) && (!TryComp(ev.Target, out StationAiWhitelistComponent? whitelistComponent) || !ValidateAi((ev.Actor, aiComp)))) @@ -150,7 +157,8 @@ public abstract partial class SharedStationAiSystem private void OnTargetVerbs(Entity ent, ref GetVerbsEvent args) { if (!args.CanComplexInteract - || !HasComp(args.User)) + || !HasComp(args.User) + || !args.CanInteract) { return; } @@ -166,13 +174,6 @@ public abstract partial class SharedStationAiSystem Text = isOpen ? Loc.GetString("ai-close") : Loc.GetString("ai-open"), Act = () => { - // no need to show menu if device is not powered. - if (!PowerReceiver.IsPowered(ent.Owner)) - { - ShowDeviceNotRespondingPopup(user); - return; - } - if (isOpen) { _uiSystem.CloseUi(ent.Owner, AiUi.Key, user); diff --git a/Content.Shared/Sound/Components/BaseEmitSoundComponent.cs b/Content.Shared/Sound/Components/BaseEmitSoundComponent.cs index 870d20457e..7011f72ef0 100644 --- a/Content.Shared/Sound/Components/BaseEmitSoundComponent.cs +++ b/Content.Shared/Sound/Components/BaseEmitSoundComponent.cs @@ -1,4 +1,6 @@ using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; namespace Content.Shared.Sound.Components; @@ -8,10 +10,9 @@ namespace Content.Shared.Sound.Components; /// public abstract partial class BaseEmitSoundComponent : Component { - public static readonly AudioParams DefaultParams = AudioParams.Default.WithVolume(-2f); - - [AutoNetworkedField] - [ViewVariables(VVAccess.ReadWrite)] + /// + /// The to play. + /// [DataField(required: true)] public SoundSpecifier? Sound; @@ -22,3 +23,15 @@ public abstract partial class BaseEmitSoundComponent : Component [DataField] public bool Positional; } + +/// +/// Represents the state of . +/// +/// This is obviously very cursed, but since the BaseEmitSoundComponent is abstract, we cannot network it. +/// AutoGenerateComponentState attribute won't work here, and since everything revolves around inheritance for some fucking reason, +/// there's no better way of doing this. +[Serializable, NetSerializable] +public struct EmitSoundComponentState(SoundSpecifier? sound) : IComponentState +{ + public SoundSpecifier? Sound { get; } = sound; +} diff --git a/Content.Shared/Sound/Components/EmitSoundOnActivateComponent.cs b/Content.Shared/Sound/Components/EmitSoundOnActivateComponent.cs index 810f132d83..d6aa42177e 100644 --- a/Content.Shared/Sound/Components/EmitSoundOnActivateComponent.cs +++ b/Content.Shared/Sound/Components/EmitSoundOnActivateComponent.cs @@ -17,6 +17,6 @@ public sealed partial class EmitSoundOnActivateComponent : BaseEmitSoundComponen /// otherwise this might enable sound spamming, as use-delays are only initiated if the interaction was /// handled. /// - [DataField("handle")] + [DataField] public bool Handle = true; } diff --git a/Content.Shared/Sound/Components/EmitSoundOnCollideComponent.cs b/Content.Shared/Sound/Components/EmitSoundOnCollideComponent.cs index a2cdd63ab7..4cdea05220 100644 --- a/Content.Shared/Sound/Components/EmitSoundOnCollideComponent.cs +++ b/Content.Shared/Sound/Components/EmitSoundOnCollideComponent.cs @@ -11,13 +11,12 @@ public sealed partial class EmitSoundOnCollideComponent : BaseEmitSoundComponent /// /// Minimum velocity required for the sound to play. /// - [ViewVariables(VVAccess.ReadWrite), DataField("minVelocity")] + [DataField("minVelocity")] public float MinimumVelocity = 3f; /// /// To avoid sound spam add a cooldown to it. /// - [ViewVariables(VVAccess.ReadWrite), DataField("nextSound", customTypeSerializer: typeof(TimeOffsetSerializer))] - [AutoPausedField] + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField] public TimeSpan NextSound; } diff --git a/Content.Shared/Sound/Components/EmitSoundOnDropComponent.cs b/Content.Shared/Sound/Components/EmitSoundOnDropComponent.cs index 5e04295607..64ed5e60dc 100644 --- a/Content.Shared/Sound/Components/EmitSoundOnDropComponent.cs +++ b/Content.Shared/Sound/Components/EmitSoundOnDropComponent.cs @@ -6,6 +6,4 @@ namespace Content.Shared.Sound.Components; /// Simple sound emitter that emits sound on entity drop /// [RegisterComponent, NetworkedComponent] -public sealed partial class EmitSoundOnDropComponent : BaseEmitSoundComponent -{ -} +public sealed partial class EmitSoundOnDropComponent : BaseEmitSoundComponent; diff --git a/Content.Shared/Sound/Components/EmitSoundOnInteractUsingComponent.cs b/Content.Shared/Sound/Components/EmitSoundOnInteractUsingComponent.cs index 49118d9799..d0b16fcec8 100644 --- a/Content.Shared/Sound/Components/EmitSoundOnInteractUsingComponent.cs +++ b/Content.Shared/Sound/Components/EmitSoundOnInteractUsingComponent.cs @@ -1,5 +1,4 @@ using Content.Shared.Whitelist; -using Robust.Shared.Prototypes; using Robust.Shared.GameStates; namespace Content.Shared.Sound.Components; @@ -10,6 +9,9 @@ namespace Content.Shared.Sound.Components; [RegisterComponent, NetworkedComponent] public sealed partial class EmitSoundOnInteractUsingComponent : BaseEmitSoundComponent { + /// + /// The for the entities that can use this item. + /// [DataField(required: true)] public EntityWhitelist Whitelist = new(); } diff --git a/Content.Shared/Sound/Components/EmitSoundOnLandComponent.cs b/Content.Shared/Sound/Components/EmitSoundOnLandComponent.cs index 2d33a7f5f2..d3fceb85dd 100644 --- a/Content.Shared/Sound/Components/EmitSoundOnLandComponent.cs +++ b/Content.Shared/Sound/Components/EmitSoundOnLandComponent.cs @@ -6,6 +6,4 @@ namespace Content.Shared.Sound.Components; /// Simple sound emitter that emits sound on LandEvent /// [RegisterComponent, NetworkedComponent] -public sealed partial class EmitSoundOnLandComponent : BaseEmitSoundComponent -{ -} +public sealed partial class EmitSoundOnLandComponent : BaseEmitSoundComponent; diff --git a/Content.Shared/Sound/Components/EmitSoundOnPickupComponent.cs b/Content.Shared/Sound/Components/EmitSoundOnPickupComponent.cs index ee4b4b1688..dcf73b7ac2 100644 --- a/Content.Shared/Sound/Components/EmitSoundOnPickupComponent.cs +++ b/Content.Shared/Sound/Components/EmitSoundOnPickupComponent.cs @@ -6,6 +6,4 @@ namespace Content.Shared.Sound.Components; /// Simple sound emitter that emits sound on entity pickup /// [RegisterComponent, NetworkedComponent] -public sealed partial class EmitSoundOnPickupComponent : BaseEmitSoundComponent -{ -} +public sealed partial class EmitSoundOnPickupComponent : BaseEmitSoundComponent; diff --git a/Content.Shared/Sound/Components/EmitSoundOnSpawnComponent.cs b/Content.Shared/Sound/Components/EmitSoundOnSpawnComponent.cs index 49d40ce185..20d39b3460 100644 --- a/Content.Shared/Sound/Components/EmitSoundOnSpawnComponent.cs +++ b/Content.Shared/Sound/Components/EmitSoundOnSpawnComponent.cs @@ -6,6 +6,4 @@ namespace Content.Shared.Sound.Components; /// Simple sound emitter that emits sound on entity spawn. /// [RegisterComponent, NetworkedComponent] -public sealed partial class EmitSoundOnSpawnComponent : BaseEmitSoundComponent -{ -} +public sealed partial class EmitSoundOnSpawnComponent : BaseEmitSoundComponent; diff --git a/Content.Shared/Sound/Components/EmitSoundOnThrowComponent.cs b/Content.Shared/Sound/Components/EmitSoundOnThrowComponent.cs index 5e3650a4a3..f8c0d1181b 100644 --- a/Content.Shared/Sound/Components/EmitSoundOnThrowComponent.cs +++ b/Content.Shared/Sound/Components/EmitSoundOnThrowComponent.cs @@ -6,6 +6,4 @@ namespace Content.Shared.Sound.Components; /// Simple sound emitter that emits sound on ThrowEvent /// [RegisterComponent, NetworkedComponent] -public sealed partial class EmitSoundOnThrowComponent : BaseEmitSoundComponent -{ -} +public sealed partial class EmitSoundOnThrowComponent : BaseEmitSoundComponent; diff --git a/Content.Shared/Sound/Components/EmitSoundOnUseComponent.cs b/Content.Shared/Sound/Components/EmitSoundOnUseComponent.cs index a99a01cec4..ec7a277e92 100644 --- a/Content.Shared/Sound/Components/EmitSoundOnUseComponent.cs +++ b/Content.Shared/Sound/Components/EmitSoundOnUseComponent.cs @@ -5,7 +5,7 @@ namespace Content.Shared.Sound.Components; /// /// Simple sound emitter that emits sound on UseInHand /// -[RegisterComponent] +[RegisterComponent, NetworkedComponent] public sealed partial class EmitSoundOnUseComponent : BaseEmitSoundComponent { /// @@ -17,6 +17,6 @@ public sealed partial class EmitSoundOnUseComponent : BaseEmitSoundComponent /// otherwise this might enable sound spamming, as use-delays are only initiated if the interaction was /// handled. /// - [DataField("handle")] + [DataField] public bool Handle = true; } diff --git a/Content.Shared/Sound/Components/SpamEmitSoundComponent.cs b/Content.Shared/Sound/Components/SpamEmitSoundComponent.cs index 149728a5ba..7c1428798c 100644 --- a/Content.Shared/Sound/Components/SpamEmitSoundComponent.cs +++ b/Content.Shared/Sound/Components/SpamEmitSoundComponent.cs @@ -1,4 +1,5 @@ using Robust.Shared.GameStates; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; namespace Content.Shared.Sound.Components; @@ -12,7 +13,7 @@ public sealed partial class SpamEmitSoundComponent : BaseEmitSoundComponent /// /// The time at which the next sound will play. /// - [DataField, AutoPausedField, AutoNetworkedField] + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField, AutoNetworkedField] public TimeSpan NextSound; /// diff --git a/Content.Shared/Sound/Components/SpamEmitSoundRequirePowerComponent.cs b/Content.Shared/Sound/Components/SpamEmitSoundRequirePowerComponent.cs index b0547ea398..bf5e925e0d 100644 --- a/Content.Shared/Sound/Components/SpamEmitSoundRequirePowerComponent.cs +++ b/Content.Shared/Sound/Components/SpamEmitSoundRequirePowerComponent.cs @@ -5,6 +5,4 @@ namespace Content.Shared.Sound.Components; /// on the powered state of the entity. /// [RegisterComponent] -public sealed partial class SpamEmitSoundRequirePowerComponent : Component -{ -} +public sealed partial class SpamEmitSoundRequirePowerComponent : Component; diff --git a/Content.Shared/Sound/SharedEmitSoundSystem.cs b/Content.Shared/Sound/SharedEmitSoundSystem.cs index 67aabbb74d..58d541e363 100644 --- a/Content.Shared/Sound/SharedEmitSoundSystem.cs +++ b/Content.Shared/Sound/SharedEmitSoundSystem.cs @@ -12,6 +12,7 @@ using Content.Shared.Whitelist; using JetBrains.Annotations; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; +using Robust.Shared.GameStates; using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Network; @@ -54,6 +55,47 @@ public abstract class SharedEmitSoundSystem : EntitySystem SubscribeLocalEvent(OnEmitSoundOnCollide); SubscribeLocalEvent(OnMobState); + + // We need to handle state manually here + // BaseEmitSoundComponent isn't registered so we have to subscribe to each one + // TODO: Make it use autonetworking instead of relying on inheritance + SubscribeEmitComponent(); + SubscribeEmitComponent(); + SubscribeEmitComponent(); + SubscribeEmitComponent(); + SubscribeEmitComponent(); + SubscribeEmitComponent(); + SubscribeEmitComponent(); + SubscribeEmitComponent(); + SubscribeEmitComponent(); + SubscribeEmitComponent(); + + // Helper method so it's a little less ugly + void SubscribeEmitComponent() where T : BaseEmitSoundComponent + { + SubscribeLocalEvent(GetBaseEmitState); + SubscribeLocalEvent(HandleBaseEmitState); + } + } + + private static void GetBaseEmitState(Entity ent, ref ComponentGetState args) where T : BaseEmitSoundComponent + { + args.State = new EmitSoundComponentState(ent.Comp.Sound); + } + + private static void HandleBaseEmitState(Entity ent, ref ComponentHandleState args) where T : BaseEmitSoundComponent + { + if (args.Current is not EmitSoundComponentState state) + return; + + ent.Comp.Sound = state.Sound switch + { + SoundPathSpecifier pathSpec => new SoundPathSpecifier(pathSpec.Path, pathSpec.Params), + SoundCollectionSpecifier collectionSpec => collectionSpec.Collection != null + ? new SoundCollectionSpecifier(collectionSpec.Collection, collectionSpec.Params) + : null, + _ => null, + }; } private void HandleEmitSoundOnUIOpen(EntityUid uid, EmitSoundOnUIOpenComponent component, AfterActivatableUIOpenEvent args) diff --git a/Content.Shared/Teleportation/Components/HandTeleporterComponent.cs b/Content.Shared/Teleportation/Components/HandTeleporterComponent.cs index 6ea29d3fd6..ea1aa492f3 100644 --- a/Content.Shared/Teleportation/Components/HandTeleporterComponent.cs +++ b/Content.Shared/Teleportation/Components/HandTeleporterComponent.cs @@ -21,11 +21,17 @@ public sealed partial class HandTeleporterComponent : Component public EntityUid? SecondPortal = null; /// - /// Portals can't be placed on different grids? + /// Should the portals be able to be placed across grids? /// [DataField] public bool AllowPortalsOnDifferentGrids; + /// + /// Should the portals work across maps? + /// + [DataField] + public bool AllowPortalsOnDifferentMaps; + [DataField("firstPortalPrototype", customTypeSerializer: typeof(PrototypeIdSerializer))] public string FirstPortalPrototype = "PortalRed"; diff --git a/Content.Server/Temperature/Components/EntityHeaterComponent.cs b/Content.Shared/Temperature/Components/EntityHeaterComponent.cs similarity index 73% rename from Content.Server/Temperature/Components/EntityHeaterComponent.cs rename to Content.Shared/Temperature/Components/EntityHeaterComponent.cs index 0b5acb421a..6cf97a0534 100644 --- a/Content.Server/Temperature/Components/EntityHeaterComponent.cs +++ b/Content.Shared/Temperature/Components/EntityHeaterComponent.cs @@ -1,26 +1,27 @@ -using Content.Server.Temperature.Systems; -using Content.Shared.Temperature; +using Content.Shared.Temperature.Systems; using Robust.Shared.Audio; +using Robust.Shared.GameStates; -namespace Content.Server.Temperature.Components; +namespace Content.Shared.Temperature.Components; /// /// Adds thermal energy to entities with placed on it. /// -[RegisterComponent, Access(typeof(EntityHeaterSystem))] +[RegisterComponent, Access(typeof(SharedEntityHeaterSystem))] +[NetworkedComponent, AutoGenerateComponentState] public sealed partial class EntityHeaterComponent : Component { /// /// Power used when heating at the high setting. /// Low and medium are 33% and 66% respectively. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public float Power = 2400f; /// /// Current setting of the heater. If it is off or unpowered it won't heat anything. /// - [DataField] + [DataField, AutoNetworkedField] public EntityHeaterSetting Setting = EntityHeaterSetting.Off; /// diff --git a/Content.Shared/Temperature/Systems/SharedEntityHeaterSystem.cs b/Content.Shared/Temperature/Systems/SharedEntityHeaterSystem.cs new file mode 100644 index 0000000000..887047bfa1 --- /dev/null +++ b/Content.Shared/Temperature/Systems/SharedEntityHeaterSystem.cs @@ -0,0 +1,97 @@ +using Content.Shared.Examine; +using Content.Shared.Popups; +using Content.Shared.Power; +using Content.Shared.Power.EntitySystems; +using Content.Shared.Temperature.Components; +using Content.Shared.Verbs; +using Robust.Shared.Audio.Systems; + +namespace Content.Shared.Temperature.Systems; + +/// +/// Handles events. +/// +public abstract partial class SharedEntityHeaterSystem : EntitySystem +{ + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedPowerReceiverSystem _receiver = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + + private readonly int _settingCount = Enum.GetValues().Length; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent>(OnGetVerbs); + SubscribeLocalEvent(OnPowerChanged); + } + + private void OnExamined(Entity ent, ref ExaminedEvent args) + { + if (!args.IsInDetailsRange) + return; + + args.PushMarkup(Loc.GetString("entity-heater-examined", ("setting", ent.Comp.Setting))); + } + + private void OnGetVerbs(Entity ent, ref GetVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract) + return; + + var nextSettingIndex = ((int)ent.Comp.Setting + 1) % _settingCount; + var nextSetting = (EntityHeaterSetting)nextSettingIndex; + + var user = args.User; + args.Verbs.Add(new AlternativeVerb() + { + Text = Loc.GetString("entity-heater-switch-setting", ("setting", nextSetting)), + Act = () => + { + ChangeSetting(ent, nextSetting, user); + } + }); + } + + private void OnPowerChanged(Entity ent, ref PowerChangedEvent args) + { + // disable heating element glowing layer if theres no power + // doesn't actually change the setting since that would be annoying + var setting = args.Powered ? ent.Comp.Setting : EntityHeaterSetting.Off; + _appearance.SetData(ent, EntityHeaterVisuals.Setting, setting); + } + + protected virtual void ChangeSetting(Entity ent, EntityHeaterSetting setting, EntityUid? user = null) + { + // Still allow changing the setting without power + ent.Comp.Setting = setting; + _audio.PlayPredicted(ent.Comp.SettingSound, ent, user); + _popup.PopupClient(Loc.GetString("entity-heater-switched-setting", ("setting", setting)), ent, user); + Dirty(ent); + + // Only show the glowing heating element layer if there's power + if (_receiver.IsPowered(ent.Owner)) + _appearance.SetData(ent, EntityHeaterVisuals.Setting, setting); + } + + protected float SettingPower(EntityHeaterSetting setting, float max) + { + // Power use while off needs to be non-zero so powernet doesn't consider the device powered + // by an unpowered network while in the off state. Otherwise, when we increase the load, + // the clientside APC receiver will think the device is powered until it gets the next + // update from the server, which will cause the heating element to glow for a moment. + // I spent several hours trying to figure out a better way to do this using PowerDisabled + // or something, but nothing worked as well as this. + // Just think of the load as a little LED, or bad wiring, or something. + return setting switch + { + EntityHeaterSetting.Low => max / 3f, + EntityHeaterSetting.Medium => max * 2f / 3f, + EntityHeaterSetting.High => max, + _ => 0.01f, + }; + } +} diff --git a/Content.Shared/Verbs/Verb.cs b/Content.Shared/Verbs/Verb.cs index 5faca9bb06..207c739466 100644 --- a/Content.Shared/Verbs/Verb.cs +++ b/Content.Shared/Verbs/Verb.cs @@ -281,12 +281,11 @@ namespace Content.Shared.Verbs } /// - /// This is for verbs facilitated by components on the user. + /// This is for verbs facilitated by components on the user or their clothing. /// Verbs from clothing, species, etc. rather than a held item. /// /// - /// Add a component to the user's entity and sub to the get verbs event - /// and it'll appear in the verbs menu on any target. + /// This will get relayed to all clothing (Not pockets) through an inventory relay event. /// [Serializable, NetSerializable] public sealed class InnateVerb : Verb diff --git a/Content.Shared/Weapons/Melee/MeleeWeaponComponent.cs b/Content.Shared/Weapons/Melee/MeleeWeaponComponent.cs index da6c303c5b..84e88156ad 100644 --- a/Content.Shared/Weapons/Melee/MeleeWeaponComponent.cs +++ b/Content.Shared/Weapons/Melee/MeleeWeaponComponent.cs @@ -10,7 +10,7 @@ namespace Content.Shared.Weapons.Melee; /// /// When given to a mob lets them do unarmed attacks, or when given to an item lets someone wield it to do attacks. /// -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(fieldDeltas: true), AutoGenerateComponentPause] public sealed partial class MeleeWeaponComponent : Component { // TODO: This is becoming bloated as shit. @@ -18,28 +18,26 @@ public sealed partial class MeleeWeaponComponent : Component /// /// Does this entity do a disarm on alt attack. /// - [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + [DataField, AutoNetworkedField] public bool AltDisarm = true; /// /// Should the melee weapon's damage stats be examinable. /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField] + [DataField, AutoNetworkedField] public bool Hidden; /// /// Next time this component is allowed to light attack. Heavy attacks are wound up and never have a cooldown. /// [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField] - [ViewVariables(VVAccess.ReadWrite)] [AutoPausedField] public TimeSpan NextAttack; /// /// Starts attack cooldown when equipped if true. /// - [ViewVariables(VVAccess.ReadWrite), DataField] + [DataField, AutoNetworkedField] public bool ResetOnHandSelected = true; /* @@ -51,72 +49,70 @@ public sealed partial class MeleeWeaponComponent : Component /// /// How many times we can attack per second. /// - [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField] + [DataField, AutoNetworkedField] public float AttackRate = 1f; /// /// Are we currently holding down the mouse for an attack. /// Used so we can't just hold the mouse button and attack constantly. /// - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + [AutoNetworkedField] public bool Attacking = false; /// /// If true, attacks will be repeated automatically without requiring the mouse button to be lifted. /// - [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + [DataField, AutoNetworkedField] public bool AutoAttack; /// /// If true, attacks will bypass armor resistances. /// - [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + [DataField, AutoNetworkedField] public bool ResistanceBypass = false; /// /// Base damage for this weapon. Can be modified via heavy damage or other means. /// - [DataField(required: true)] - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + [DataField(required: true), AutoNetworkedField] public DamageSpecifier Damage = default!; - [DataField] - [ViewVariables(VVAccess.ReadWrite)] + [DataField, AutoNetworkedField] public FixedPoint2 BluntStaminaDamageFactor = FixedPoint2.New(0.5f); /// /// Multiplies damage by this amount for single-target attacks. /// - [ViewVariables(VVAccess.ReadWrite), DataField] + [DataField, AutoNetworkedField] public FixedPoint2 ClickDamageModifier = FixedPoint2.New(1); // TODO: Temporarily 1.5 until interactionoutline is adjusted to use melee, then probably drop to 1.2 /// /// Nearest edge range to hit an entity. /// - [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField] + [DataField, AutoNetworkedField] public float Range = 1.5f; /// /// Total width of the angle for wide attacks. /// - [ViewVariables(VVAccess.ReadWrite), DataField] + [DataField, AutoNetworkedField] public Angle Angle = Angle.FromDegrees(60); - [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField] + [DataField, AutoNetworkedField] public EntProtoId Animation = "WeaponArcPunch"; - [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField] + [DataField, AutoNetworkedField] public EntProtoId WideAnimation = "WeaponArcSlash"; /// /// Rotation of the animation. /// 0 degrees means the top faces the attacker. /// - [ViewVariables(VVAccess.ReadWrite), DataField] + [DataField, AutoNetworkedField] public Angle WideAnimationRotation = Angle.Zero; - [ViewVariables(VVAccess.ReadWrite), DataField] + [DataField, AutoNetworkedField] public bool SwingLeft; diff --git a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs index 1f2ffbaddb..44c0fb92dd 100644 --- a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs +++ b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs @@ -109,7 +109,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem if (gun.NextFire > component.NextAttack) { component.NextAttack = gun.NextFire; - Dirty(uid, component); + DirtyField(uid, component, nameof(MeleeWeaponComponent.NextAttack)); } } @@ -133,7 +133,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem return; component.NextAttack = minimum; - Dirty(uid, component); + DirtyField(uid, component, nameof(MeleeWeaponComponent.NextAttack)); } private void OnGetBonusMeleeDamage(EntityUid uid, BonusMeleeDamageComponent component, ref GetMeleeDamageEvent args) @@ -173,7 +173,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem return; weapon.Attacking = false; - Dirty(weaponUid, weapon); + DirtyField(weaponUid, weapon, nameof(MeleeWeaponComponent.Attacking)); } private void OnLightAttack(LightAttackEvent msg, EntitySessionEventArgs args) @@ -394,7 +394,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem swings++; } - Dirty(weaponUid, weapon); + DirtyField(weaponUid, weapon, nameof(MeleeWeaponComponent.NextAttack)); // Do this AFTER attack so it doesn't spam every tick var ev = new AttemptMeleeEvent(); @@ -444,6 +444,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem RaiseLocalEvent(user, ref attackEv); weapon.Attacking = true; + DirtyField(weaponUid, weapon, nameof(MeleeWeaponComponent.Attacking)); return true; } @@ -846,15 +847,21 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem //Setting deactivated damage to the weapon's regular value before changing it. itemToggleMelee.DeactivatedDamage ??= meleeWeapon.Damage; meleeWeapon.Damage = itemToggleMelee.ActivatedDamage; + DirtyField(uid, meleeWeapon, nameof(MeleeWeaponComponent.Damage)); } - meleeWeapon.HitSound = itemToggleMelee.ActivatedSoundOnHit; + if (meleeWeapon.HitSound?.Equals(itemToggleMelee.ActivatedSoundOnHit) != true) + { + meleeWeapon.HitSound = itemToggleMelee.ActivatedSoundOnHit; + DirtyField(uid, meleeWeapon, nameof(MeleeWeaponComponent.HitSound)); + } if (itemToggleMelee.ActivatedSoundOnHitNoDamage != null) { //Setting the deactivated sound on no damage hit to the weapon's regular value before changing it. itemToggleMelee.DeactivatedSoundOnHitNoDamage ??= meleeWeapon.NoDamageSound; meleeWeapon.NoDamageSound = itemToggleMelee.ActivatedSoundOnHitNoDamage; + DirtyField(uid, meleeWeapon, nameof(MeleeWeaponComponent.NoDamageSound)); } if (itemToggleMelee.ActivatedSoundOnSwing != null) @@ -862,28 +869,41 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem //Setting the deactivated sound on no damage hit to the weapon's regular value before changing it. itemToggleMelee.DeactivatedSoundOnSwing ??= meleeWeapon.SwingSound; meleeWeapon.SwingSound = itemToggleMelee.ActivatedSoundOnSwing; + DirtyField(uid, meleeWeapon, nameof(MeleeWeaponComponent.SwingSound)); } if (itemToggleMelee.DeactivatedSecret) + { meleeWeapon.Hidden = false; + } } else { if (itemToggleMelee.DeactivatedDamage != null) + { meleeWeapon.Damage = itemToggleMelee.DeactivatedDamage; + DirtyField(uid, meleeWeapon, nameof(MeleeWeaponComponent.Damage)); + } meleeWeapon.HitSound = itemToggleMelee.DeactivatedSoundOnHit; + DirtyField(uid, meleeWeapon, nameof(MeleeWeaponComponent.HitSound)); if (itemToggleMelee.DeactivatedSoundOnHitNoDamage != null) + { meleeWeapon.NoDamageSound = itemToggleMelee.DeactivatedSoundOnHitNoDamage; + DirtyField(uid, meleeWeapon, nameof(MeleeWeaponComponent.NoDamageSound)); + } if (itemToggleMelee.DeactivatedSoundOnSwing != null) + { meleeWeapon.SwingSound = itemToggleMelee.DeactivatedSoundOnSwing; + DirtyField(uid, meleeWeapon, nameof(MeleeWeaponComponent.SwingSound)); + } if (itemToggleMelee.DeactivatedSecret) + { meleeWeapon.Hidden = true; + } } - - Dirty(uid, meleeWeapon); } } diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index f2b2652a54..17ef3994ca 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,71 +1,4 @@ Entries: -- author: SaphireLattice - changes: - - message: Utensils can finally go into disposals - type: Fix - id: 7616 - time: '2024-11-16T03:39:19.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/33326 -- author: K-Dynamic - changes: - - message: Solar assembly crate now comes with 10 flatpacks and 20 glass to make - expansion and repairs easier, as well as increasing in price from 525 to 1250 - spesos. - type: Tweak - id: 7617 - time: '2024-11-16T04:30:48.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/33019 -- author: Aquif - changes: - - message: There is now a button to view your admin remarks in the character editor, - right next to the stats button. - type: Tweak - id: 7618 - time: '2024-11-16T05:09:29.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31761 -- author: SpaceRox1244 - changes: - - message: Closets and lockers now have visuals for being labeled with papers. - type: Add - id: 7619 - time: '2024-11-17T03:27:29.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/33318 -- author: Ubaser - changes: - - message: You can now craft dim light bulbs at an autolathe. - type: Add - id: 7620 - time: '2024-11-18T06:32:08.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/33383 -- author: Ilya246 - changes: - - message: Multiple people using one shuttle console will no longer cause the shuttle - to slow down. - type: Fix - id: 7621 - time: '2024-11-19T02:59:42.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/32381 -- author: ScarKy0 - changes: - - message: Secret doors no longer tell you if they're welded shut on examine. - type: Tweak - id: 7622 - time: '2024-11-19T05:07:02.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/33365 -- author: ArZarLordOfMango - changes: - - message: Most toggleable clothing must now be equipped to toggle their actions. - type: Fix - id: 7623 - time: '2024-11-19T20:31:38.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/32826 -- author: Plykiya - changes: - - message: The SWAT crate from cargo now requires armory access to open. - type: Fix - id: 7624 - time: '2024-11-20T00:57:01.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/33415 - author: SlamBamActionman changes: - message: It's no longer possible to drag an item out of a container's UI to drop @@ -3886,3 +3819,84 @@ id: 8115 time: '2025-03-29T17:55:59.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/35123 +- author: Killerqu00 + changes: + - message: Uncuffing someone with combat mode on will shove them down. + type: Add + id: 8116 + time: '2025-03-29T20:09:34.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/35193 +- author: metalgearsloth + changes: + - message: Jetpacks emit particles more frequently. + type: Tweak + id: 8117 + time: '2025-03-30T04:06:01.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/36093 +- author: beck-thompson + changes: + - message: Stethoscopes now automatically start doafters and also can tell if a + patient is losing oxygen damage or gaining it. + type: Add + - message: Moths can no longer eat stethoscopes. + type: Fix + - message: Stethoscopes action button now works properly. + type: Fix + id: 8118 + time: '2025-03-31T02:27:08.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/36210 +- author: Tayrtahn + changes: + - message: Items thrown when someone slips now tend to scatter in the direction + they are moving, and respect the item's mass. + type: Tweak + id: 8119 + time: '2025-03-31T22:00:04.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/36232 +- author: ScarKy0 + changes: + - message: Thieves now start with the thieving satchel instead of their toolbox. + The satchel will get all the selected kits spawned inside of it. + type: Add + - message: Thief Chameleon kit now comes with a backpack and a bonus pair of chameleon + gloves. Be careful, they aren't thieving gloves and can be tough to tell apart! + type: Tweak + - message: Updated smuggler stachel's description to reflect what it's used for. + type: Tweak + - message: Thieving satchel and toolbox now correctly play a sound when their kits + are selected. + type: Fix + id: 8120 + time: '2025-03-31T22:32:31.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/36201 +- author: Tayrtahn + changes: + - message: Electric grills no longer appear powered when cycled while disconnected + from power. + type: Fix + - message: Interactions with electric grills are now predicted. + type: Tweak + id: 8121 + time: '2025-04-01T16:43:19.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/36241 +- author: MisterImp + changes: + - message: A new recipe has been added for pizza made with world peas, world peazza. + type: Add + id: 8122 + time: '2025-04-01T23:26:53.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/35191 +- author: Fildrance + changes: + - message: fixed missing deconstruct on RCD + type: Fix + id: 8123 + time: '2025-04-02T16:11:35.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/36255 +- author: qwerltaz + changes: + - message: Dragon rifts now shine a different color depending on charge progress. + type: Add + id: 8124 + time: '2025-04-02T18:37:35.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/36216 diff --git a/Resources/Credits/GitHub.txt b/Resources/Credits/GitHub.txt index a6d5b82974..8d8296eeec 100644 --- a/Resources/Credits/GitHub.txt +++ b/Resources/Credits/GitHub.txt @@ -1 +1 @@ -0tito, 0x6273, 12rabbits, 1337dakota, 13spacemen, 2013HORSEMEATSCANDAL, 20kdc, 21Melkuu, 3nderall, 4310v343k, 4dplanner, 612git, 778b, Ablankmann, abregado, Absolute-Potato, Absotively, achookh, Acruid, ActiveMammmoth, actually-reb, ada-please, adamsong, Adeinitas, Admiral-Obvious-001, adrian, Adrian16199, Ady4ik, Aerocrux, Aeshus, Aexolott, Aexxie, africalimedrop, afrokada, AftrLite, AgentSmithRadio, Agoichi, Ahion, aiden, Aisu9, ajcm, AJCM-git, AjexRose, Alekshhh, alexkar598, AlexMorgan3817, alexum418, alexumandxgabriel08x, Alithsko, alliephante, ALMv1, Alpaccalypse, Alpha-Two, AlphaQwerty, Altoids1, amatwiedle, amylizzle, Andre19926, AndrewEyeke, AndreyCamper, Anzarot121, ApolloVector, Appiah, ar4ill, ArchPigeon, ArchRBX, areitpog, Arendian, arimah, Arkanic, ArkiveDev, armoks, Arteben, ArthurMousatov, ArtisticRoomba, artur, AruMoon, ArZarLordOfMango, as334, AsikKEsel, AsnDen, asperger-sind, aspiringLich, astriloqua, august-sun, AutoOtter, AverageNotDoingAnythingEnjoyer, avghdev, Awlod, AzzyIsNotHere, baa14453, BackeTako, BananaFlambe, Baptr0b0t, BarryNorfolk, BasedUser, beck-thompson, bellwetherlogic, ben, benev0, benjamin-burges, BGare, bhespiritu, bibbly, BIGZi0348, bingojohnson, BismarckShuffle, Bixkitts, Blackern5000, Blazeror, BlitzTheSquishy, bloodrizer, Bloody2372, blueDev2, Boaz1111, BobdaBiscuit, BobTheSleder, boiled-water-tsar, Booblesnoot42, Boolean-Buckeye, botanySupremist, brainfood1183, BramvanZijp, Brandon-Huu, BriBrooo, Bright0, brndd, bryce0110, BubblegumBlue, buletsponge, buntobaggins, bvelliquette, byondfuckery, c0rigin, c4llv07e, CaasGit, Caconym27, Calecute, Callmore, capnsockless, CaptainMaru, CaptainSqrBeard, Carbonhell, Carolyn3114, Carou02, carteblanche4me, catdotjs, Catofquestionableethics, CatTheSystem, centcomofficer24, Centronias, Chaboricks, chairbender, Charlese2, charlie, ChaseFlorom, chavonadelal, Cheackraze, CheddaCheez, cheesePizza2, CheesePlated, Chief-Engineer, chillyconmor, christhirtle, chromiumboy, Chronophylos, Chubbicous, Chubbygummibear, Ciac32, civilCornball, claustro305, Clement-O, clyf, Clyybber, CMDR-Piboy314, cohanna, Cohnway, Cojoke-dot, ColdAutumnRain, Colin-Tel, collinlunn, ComicIronic, Compilatron144, CookieMasterT, coolboy911, coolmankid12345, Coolsurf6, cooperwallace, corentt, CormosLemming, CrafterKolyan, crazybrain23, creadth, CrigCrag, croilbird, Crotalus, CrudeWax, CrzyPotato, 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, dexlerxd, dffdff2423, DieselMohawk, digitalic, Dimastra, dinnercalzone, DinoWattz, DisposableCrewmember42, DjfjdfofdjfjD, doc-michael, docnite, Doctor-Cpu, DoctorBeard, DogZeroX, dolgovmi, dontbetank, Doomsdrayk, Doru991, DoubleRiceEddiedd, DoutorWhite, DR-DOCTOR-EVIL-EVIL, 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, eoineoineoin, 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, FluffiestFloof, FluffMe, FluidRock, flymo5678, foboscheshir, FoLoKe, fooberticus, ForestNoises, forgotmyotheraccount, forkeyboards, forthbridge, Fortune117, Fouin, foxhorn, freeman2651, freeze2222, frobnic8, Froffy025, Fromoriss, froozigiusz, FrostMando, FungiFellow, FunTust, Futuristic-OK, GalacticChimp, gamer3107, gansulalan, GaussiArson, Gaxeer, gbasood, gcoremans, Geekyhobo, genderGeometries, GeneralGaws, Genkail, geraeumig, Ghagliiarghii, Git-Nivrak, githubuser508, gituhabu, GlassEclipse, GNF54, godisdeadLOL, goet, GoldenCan, Goldminermac, Golinth, GoodWheatley, Gorox221, gradientvera, graevy, GraniteSidewalk, GreaseMonk, greenrock64, GreyMario, GrownSamoyedDog, GTRsound, gusxyz, Gyrandola, h3half, hamurlik, Hanzdegloker, HappyRoach, Hardly3D, harikattar, he1acdvv, Hebi, Helm4142, Henry, HerCoyote23, HighTechPuddle, hitomishirichan, hiucko, hivehum, Hmeister-fake, Hmeister-real, Hobbitmax, hobnob, HoidC, Holinka4ever, holyssss, HoofedEar, Hoolny, hord-brayden, Hreno, 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, IMCB, impubbi, imrenq, imweax, indeano, Injazz, Insineer, IntegerTempest, Interrobang01, Intoxicating-Innocence, IProduceWidgets, itsmethom, Itzbenz, iztokbajcar, Jackal298, Jackrost, jacksonzck, Jackw2As, jacob, jamessimo, janekvap, Jark255, Jarmer123, Jaskanbe, JasperJRoth, jbox144, JerryImMouse, jerryimmouse, Jessetriesagain, jessicamaybe, Jezithyr, jicksaw, JiimBob, JimGamemaster, jimmy12or, JIPDawg, jjtParadox, jmcb, JohnGinnane, johnku1, Jophire, joshepvodka, Jrpl, jukereise, juliangiebel, JustArt1m, JustCone14, justdie12, justin, justintether, JustinTrotter, JustinWinningham, justtne, K-Dynamic, k3yw, Kadeo64, Kaga-404, KaiShibaa, kalane15, kalanosh, Kanashi-Panda, katzenminer, kbailey-git, Keelin, Keer-Sar, KEEYNy, keikiru, Kelrak, kerisargit, keronshb, KIBORG04, KieueCaprie, Killerqu00, Kimpes, KingFroozy, kira-er, Kirillcas, Kirus59, Kistras, Kit0vras, KittenColony, klaypexx, Kmc2000, Ko4ergaPunk, kognise, kokoc9n, komunre, KonstantinAngelov, kosticia, koteq, KrasnoshchekovPavel, Krunklehorn, Kupie, kxvvv, kyupolaris, kzhanik, LaCumbiaDelCoronavirus, lajolico, Lamrr, LankLTE, laok233, lapatison, larryrussian, lawdog4817, Lazzi0706, leander-0, leonardo-dabepis, leonidussaks, leonsfriedrich, LeoSantich, LetterN, lettern, Level10Cybermancer, LEVELcat, lever1209, LevitatingTree, Lgibb18, lgruthes, LightVillet, liltenhead, LinkUyx, Litraxx, LittleBuilderJane, LittleNorthStar, LittleNyanCat, lizelive, lmsnoise, localcc, lokachop, Lomcastar, LordCarve, LordEclipse, lucas, LucasTheDrgn, luckyshotpictures, LudwigVonChesterfield, luizwritescode, Lukasz825700516, luminight, lunarcomets, luringens, Lusatia, lvvova1, Lyndomen, lyroth001, lzimann, lzk228, M3739, mac6na6na, MACMAN2003, Macoron, magicalus, magmodius, MagnusCrowe, malchanceux, MaloTV, ManelNavola, manelnavola, Mangohydra, marboww, Markek1, 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, MjrLandWhale, mkanke-real, MLGTASTICa, moderatelyaware, modern-nm, mokiros, momo, Moneyl, monotheonist, Moomoobeef, moony, Morb0, MossyGreySlope, mr-bo-jangles, Mr0maks, MrFippik, mrrobdemo, muburu, MureixloI, musicmanvr, MWKane, Myakot, Myctai, N3X15, nails-n-tape, Nairodian, Naive817, NakataRin, namespace-Memory, Nannek, NazrinNya, neutrino-laser, NickPowers43, nikthechampiongr, Nimfar11, ninruB, Nirnael, NIXC, 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, och-och, OctoRocket, OldDanceJacket, OliverOtter, onesch, OnyxTheBrave, Orange-Winds, OrangeMoronage9622, osjarw, Ostaf, othymer, OttoMaticode, Owai-Seek, packmore, paige404, paigemaeforrest, pali6, Palladinium, Pangogie, panzer-iv1, paolordls, partyaddict, patrikturi, PaulRitter, peccneck, Peptide90, peptron1, PeterFuto, PetMudstone, pewter-wiz, Pgriha, Phantom-Lily, pheenty, Phill101, phunnyguy, 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, ProfanedBane, PROG-MohamedDwidar, prole0, Pronana, ProPandaBear, PrPleGoo, ps3moira, Pspritechologist, Psychpsyo, psykana, psykzz, PuceTint, pumkin69, PuroSlavKing, PursuitInAshes, Putnam3145, qrtDaniil, Quantum-cross, quatre, QueerNB, QuietlyWhisper, qwerltaz, 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, rich-dunne, RieBi, riggleprime, RIKELOLDABOSS, rinary1, Rinkashikachi, riolume, RobbyTheFish, robinthedragon, Rockdtben, Rohesie, rok-povsic, rokudara-sen, rolfero, RomanNovo, rosieposieeee, Roudenn, router, RumiTiger, Ruzihm, S1rFl0, S1ss3l, Saakra, Sadie-silly, saga3152, saintmuntzer, Salex08, sam, samgithubaccount, 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, SignalWalker, siigiil, Simyon264, sirdragooon, Sirionaut, Sk1tch, SkaldetSkaeg, Skarletto, Skrauz, Skyedra, SlamBamActionman, slarticodefast, Slava0135, sleepyyapril, slimmslamm, Slyfox333, snebl, snicket, sniperchance, Snowni, snowsignal, SolidusSnek, SonicHDC, SoulFN, SoulSloth, Soundwavesghost, southbridge-fur, sowelipililimute, Soydium, spacelizard, SpaceLizardSky, SpaceManiac, SpaceRox1244, SpaceyLady, spanky-spanky, Sparlight, spartak, SpartanKadence, spderman3333, SpeltIncorrectyl, Spessmann, SphiraI, SplinterGP, spoogemonster, sporekto, sporkyz, ssdaniel24, stalengd, stanberytrask, Stanislav4ix, StanTheCarpenter, starbuckss14, Stealthbomber16, stellar-novas, stewie523, stomf, stopbreaking, stopka-html, StrawberryMoses, Stray-Pyramid, strO0pwafel, Strol20, StStevens, Subversionary, sunbear-dev, superjj18, Supernorn, SweptWasTaken, 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, thecopbennet, TheCze, TheDarkElites, thedraccx, TheEmber, TheIntoxicatedCat, thekilk, themias, theomund, TherapyGoth, 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, tosatur, TotallyLemon, ToxicSonicFan04, Tr1bute, tropicalhibi, truepaintgit, Truoizys, Tryded, TsjipTsjip, Tunguso4ka, TurboTrackerss14, tyashley, Tyler-IN, TytosB, Tyzemol, UbaserB, ubis1, UBlueberry, 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, VigersRay, violet754, Visne, vlados1408, VMSolidus, voidnull000, volotomite, volundr-, Voomra, Vordenburg, vorkathbruh, Vortebo, vulppine, wafehling, Warentan, WarMechanic, Watermelon914, weaversam8, wertanchik, whateverusername0, whatston3, widgetbeck, Willhelm53, WilliamECrew, willicassi, Winkarst-cpu, wirdal, wixoaGit, WlarusFromDaSpace, wrexbe, WTCWR68, xkreksx, xprospero, xRiriq, YanehCheck, yathxyz, Ygg01, YotaXP, youarereadingthis, YoungThugSS14, Yousifb26, youtissoum, yunii, YuriyKiss, yuriykiss, zach-hill, Zadeon, zamp, Zandario, Zap527, Zealith-Gamer, ZelteHonor, zero, ZeroDiamond, ZeWaka, zHonys, zionnBE, ZNixian, Zokkie, ZoldorfTheWizard, zonespace27, Zylofan, Zymem, zzylex +0tito, 0x6273, 12rabbits, 1337dakota, 13spacemen, 2013HORSEMEATSCANDAL, 20kdc, 21Melkuu, 3nderall, 4310v343k, 4dplanner, 612git, 778b, Ablankmann, abregado, Absolute-Potato, Absotively, achookh, Acruid, ActiveMammmoth, actually-reb, ada-please, adamsong, Adeinitas, Admiral-Obvious-001, adrian, Adrian16199, Ady4ik, Aerocrux, Aeshus, Aexolott, Aexxie, africalimedrop, afrokada, AftrLite, AgentSmithRadio, Agoichi, Ahion, aiden, Aisu9, ajcm, AJCM-git, AjexRose, Alekshhh, alexkar598, AlexMorgan3817, alexum418, alexumandxgabriel08x, Alithsko, alliephante, ALMv1, Alpaccalypse, Alpha-Two, AlphaQwerty, Altoids1, amatwiedle, amylizzle, Andre19926, AndrewEyeke, AndreyCamper, Anzarot121, ApolloVector, Appiah, ar4ill, ArchPigeon, ArchRBX, areitpog, Arendian, arimah, Arkanic, ArkiveDev, armoks, Arteben, ArthurMousatov, ArtisticRoomba, artur, AruMoon, ArZarLordOfMango, as334, AsikKEsel, AsnDen, asperger-sind, aspiringLich, astriloqua, august-sun, AutoOtter, AverageNotDoingAnythingEnjoyer, avghdev, Awlod, AzzyIsNotHere, baa14453, BackeTako, BananaFlambe, Baptr0b0t, BarryNorfolk, BasedUser, beck-thompson, bellwetherlogic, ben, benev0, benjamin-burges, BGare, bhespiritu, bibbly, BIGZi0348, bingojohnson, BismarckShuffle, Bixkitts, Blackern5000, Blazeror, BlitzTheSquishy, bloodrizer, Bloody2372, blueDev2, Boaz1111, BobdaBiscuit, BobTheSleder, boiled-water-tsar, Booblesnoot42, Boolean-Buckeye, botanySupremist, brainfood1183, BramvanZijp, Brandon-Huu, BriBrooo, Bright0, brndd, bryce0110, BubblegumBlue, buletsponge, buntobaggins, bvelliquette, BWTCK, byondfuckery, c0rigin, c4llv07e, CaasGit, Caconym27, Calecute, Callmore, capnsockless, CaptainMaru, CaptainSqrBeard, Carbonhell, Carolyn3114, Carou02, carteblanche4me, catdotjs, Catofquestionableethics, CatTheSystem, centcomofficer24, Centronias, Chaboricks, chairbender, Chaoticaa, Charlese2, charlie, ChaseFlorom, chavonadelal, Cheackraze, CheddaCheez, cheesePizza2, CheesePlated, Chief-Engineer, chillyconmor, christhirtle, chromiumboy, Chronophylos, Chubbicous, Chubbygummibear, Ciac32, civilCornball, claustro305, Clement-O, clyf, Clyybber, CMDR-Piboy314, cohanna, Cohnway, Cojoke-dot, ColdAutumnRain, Colin-Tel, collinlunn, ComicIronic, Compilatron144, CookieMasterT, coolboy911, coolmankid12345, Coolsurf6, cooperwallace, corentt, CormosLemming, CrafterKolyan, crazybrain23, creadth, CrigCrag, croilbird, Crotalus, CrudeWax, CrzyPotato, 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, dexlerxd, dffdff2423, DieselMohawk, digitalic, Dimastra, dinnercalzone, DinoWattz, DisposableCrewmember42, DjfjdfofdjfjD, doc-michael, docnite, Doctor-Cpu, DoctorBeard, DogZeroX, dolgovmi, dontbetank, Doomsdrayk, Doru991, DoubleRiceEddiedd, DoutorWhite, DR-DOCTOR-EVIL-EVIL, 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, eoineoineoin, 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, FluffiestFloof, FluffMe, FluidRock, flymo5678, foboscheshir, FoLoKe, fooberticus, ForestNoises, forgotmyotheraccount, forkeyboards, forthbridge, Fortune117, Fouin, foxhorn, freeman2651, freeze2222, frobnic8, Froffy025, Fromoriss, froozigiusz, FrostMando, FungiFellow, FunTust, Futuristic-OK, GalacticChimp, gamer3107, gansulalan, GaussiArson, Gaxeer, gbasood, gcoremans, Geekyhobo, genderGeometries, GeneralGaws, Genkail, geraeumig, Ghagliiarghii, Git-Nivrak, githubuser508, gituhabu, GlassEclipse, GNF54, godisdeadLOL, goet, GoldenCan, Goldminermac, Golinth, GoodWheatley, Gorox221, gradientvera, graevy, GraniteSidewalk, GreaseMonk, greenrock64, GreyMario, GrownSamoyedDog, GTRsound, gusxyz, Gyrandola, h3half, hamurlik, Hanzdegloker, HappyRoach, Hardly3D, harikattar, he1acdvv, Hebi, Helm4142, Henry, HerCoyote23, HighTechPuddle, hitomishirichan, hiucko, hivehum, Hmeister-fake, Hmeister-real, Hobbitmax, hobnob, HoidC, Holinka4ever, holyssss, HoofedEar, Hoolny, hord-brayden, Hreno, 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, IMCB, impubbi, imrenq, imweax, indeano, Injazz, Insineer, IntegerTempest, Interrobang01, Intoxicating-Innocence, IProduceWidgets, itsmethom, Itzbenz, iztokbajcar, Jackal298, Jackrost, jacksonzck, Jackw2As, jacob, jamessimo, janekvap, Jark255, Jarmer123, Jaskanbe, JasperJRoth, jbox144, jerryimmouse, JerryImMouse, Jessetriesagain, jessicamaybe, Jezithyr, jicksaw, JiimBob, JimGamemaster, jimmy12or, JIPDawg, jjtParadox, jmcb, JohnGinnane, johnku1, Jophire, joshepvodka, Jrpl, jukereise, juliangiebel, JustArt1m, JustCone14, justdie12, justin, justintether, JustinTrotter, JustinWinningham, justtne, K-Dynamic, k3yw, Kadeo64, Kaga-404, KaiShibaa, kalane15, kalanosh, Kanashi-Panda, katzenminer, kbailey-git, Keelin, Keer-Sar, KEEYNy, keikiru, Kelrak, kerisargit, keronshb, KIBORG04, KieueCaprie, Killerqu00, Kimpes, KingFroozy, kira-er, Kirillcas, Kirus59, Kistras, Kit0vras, KittenColony, klaypexx, Kmc2000, Ko4ergaPunk, kognise, kokoc9n, komunre, KonstantinAngelov, kosticia, koteq, KrasnoshchekovPavel, Krunklehorn, Kupie, kxvvv, kyupolaris, kzhanik, LaCumbiaDelCoronavirus, lajolico, Lamrr, LankLTE, laok233, lapatison, larryrussian, lawdog4817, Lazzi0706, leander-0, leonardo-dabepis, leonidussaks, leonsfriedrich, LeoSantich, LetterN, lettern, Level10Cybermancer, LEVELcat, lever1209, LevitatingTree, Lgibb18, lgruthes, LightVillet, liltenhead, LinkUyx, Litraxx, LittleBuilderJane, LittleNorthStar, LittleNyanCat, lizelive, lmsnoise, localcc, lokachop, Lomcastar, LordCarve, LordEclipse, lucas, LucasTheDrgn, luckyshotpictures, LudwigVonChesterfield, luizwritescode, Lukasz825700516, luminight, lunarcomets, luringens, Lusatia, lvvova1, Lyndomen, lyroth001, lzimann, lzk228, M3739, mac6na6na, MACMAN2003, Macoron, magicalus, magmodius, MagnusCrowe, malchanceux, MaloTV, ManelNavola, manelnavola, Mangohydra, marboww, Markek1, 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, MjrLandWhale, mkanke-real, MLGTASTICa, moderatelyaware, modern-nm, mokiros, momo, Moneyl, monotheonist, Moomoobeef, moony, Morb0, MossyGreySlope, mr-bo-jangles, Mr0maks, MrFippik, mrrobdemo, muburu, MureixloI, musicmanvr, MWKane, Myakot, Myctai, N3X15, nails-n-tape, Nairodian, Naive817, NakataRin, namespace-Memory, Nannek, NazrinNya, neutrino-laser, NickPowers43, nikthechampiongr, Nimfar11, ninruB, Nirnael, NIXC, 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, och-och, OctoRocket, OldDanceJacket, OliverOtter, onesch, OnyxTheBrave, Orange-Winds, OrangeMoronage9622, osjarw, Ostaf, othymer, OttoMaticode, Owai-Seek, packmore, paige404, paigemaeforrest, pali6, Palladinium, Pangogie, panzer-iv1, paolordls, partyaddict, patrikturi, PaulRitter, peccneck, Peptide90, peptron1, PeterFuto, PetMudstone, pewter-wiz, Pgriha, Phantom-Lily, pheenty, Phill101, phunnyguy, 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, ProfanedBane, PROG-MohamedDwidar, prole0, Pronana, ProPandaBear, PrPleGoo, ps3moira, Pspritechologist, Psychpsyo, psykana, psykzz, PuceTint, pumkin69, PuroSlavKing, PursuitInAshes, Putnam3145, qrtDaniil, Quantum-cross, quatre, QueerNB, QuietlyWhisper, qwerltaz, 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, rich-dunne, RieBi, riggleprime, RIKELOLDABOSS, rinary1, Rinkashikachi, riolume, RobbyTheFish, robinthedragon, Rockdtben, Rohesie, rok-povsic, rokudara-sen, rolfero, RomanNovo, rosieposieeee, Roudenn, router, RumiTiger, Ruzihm, S1rFl0, S1ss3l, Saakra, Sadie-silly, saga3152, saintmuntzer, Salex08, sam, samgithubaccount, 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, SignalWalker, siigiil, Simyon264, sirdragooon, Sirionaut, Sk1tch, SkaldetSkaeg, Skarletto, Skrauz, Skyedra, SlamBamActionman, slarticodefast, Slava0135, sleepyyapril, slimmslamm, Slyfox333, snebl, snicket, sniperchance, Snowni, snowsignal, SolidusSnek, SonicHDC, SoulFN, SoulSloth, Soundwavesghost, southbridge-fur, sowelipililimute, Soydium, spacelizard, SpaceLizardSky, SpaceManiac, SpaceRox1244, SpaceyLady, spanky-spanky, Sparlight, spartak, SpartanKadence, spderman3333, SpeltIncorrectyl, Spessmann, SphiraI, SplinterGP, spoogemonster, sporekto, sporkyz, ssdaniel24, stalengd, stanberytrask, Stanislav4ix, StanTheCarpenter, starbuckss14, Stealthbomber16, stellar-novas, stewie523, stomf, stopbreaking, stopka-html, StrawberryMoses, Stray-Pyramid, strO0pwafel, Strol20, StStevens, Subversionary, sunbear-dev, superjj18, Supernorn, SweptWasTaken, 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, thecopbennet, TheCze, TheDarkElites, thedraccx, TheEmber, TheIntoxicatedCat, thekilk, themias, theomund, TheProNoob678, TherapyGoth, 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, tosatur, TotallyLemon, ToxicSonicFan04, Tr1bute, tropicalhibi, truepaintgit, Truoizys, Tryded, TsjipTsjip, Tunguso4ka, TurboTrackerss14, tyashley, Tyler-IN, TytosB, Tyzemol, UbaserB, ubis1, UBlueberry, 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, vlados1408, VMSolidus, voidnull000, volotomite, volundr-, Voomra, Vordenburg, vorkathbruh, Vortebo, vulppine, wafehling, Warentan, WarMechanic, Watermelon914, weaversam8, wertanchik, whateverusername0, whatston3, widgetbeck, Willhelm53, WilliamECrew, willicassi, Winkarst-cpu, wirdal, wixoaGit, WlarusFromDaSpace, wrexbe, WTCWR68, xkreksx, xprospero, xRiriq, YanehCheck, yathxyz, Ygg01, YotaXP, youarereadingthis, YoungThugSS14, Yousifb26, youtissoum, yunii, yuriykiss, YuriyKiss, zach-hill, Zadeon, 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/cuffs/components/cuffable-component.ftl b/Resources/Locale/en-US/_strings/cuffs/components/cuffable-component.ftl index a2cb6ed658..092f1d6620 100644 --- a/Resources/Locale/en-US/_strings/cuffs/components/cuffable-component.ftl +++ b/Resources/Locale/en-US/_strings/cuffs/components/cuffable-component.ftl @@ -8,6 +8,7 @@ cuffable-component-start-uncuffing-target-message = You start unrestraining {$ta cuffable-component-start-uncuffing-by-other-message = {$otherName} starts unrestraining you! cuffable-component-remove-cuffs-success-message = You successfully remove the restraints. +cuffable-component-remove-cuffs-push-success-message = You successfully remove the restraints and push {$otherName} down. cuffable-component-remove-cuffs-by-other-success-message = {$otherName} unrestrains your hands. cuffable-component-remove-cuffs-to-other-partial-success-message = You successfully remove the restraints. {$cuffedHandCount} of {$otherName}'s hands remain restrained. cuffable-component-remove-cuffs-by-other-partial-success-message = {$otherName} removes your restraints. {$cuffedHandCount} of your hands remain restrained. diff --git a/Resources/Locale/en-US/_strings/entity-systems/pointing/pointing-system.ftl b/Resources/Locale/en-US/_strings/entity-systems/pointing/pointing-system.ftl index 29f0fa27e2..be7b6196b2 100644 --- a/Resources/Locale/en-US/_strings/entity-systems/pointing/pointing-system.ftl +++ b/Resources/Locale/en-US/_strings/entity-systems/pointing/pointing-system.ftl @@ -5,6 +5,6 @@ pointing-system-point-at-self = You point at yourself. pointing-system-point-at-other = You point at {THE($other)}. pointing-system-point-at-self-others = {CAPITALIZE(THE($otherName))} points at {REFLEXIVE($other)}. pointing-system-point-at-other-others = {CAPITALIZE(THE($otherName))} points at {THE($other)}. -pointing-system-point-at-you-other = {$otherName} points at you. +pointing-system-point-at-you-other = {CAPITALIZE(THE($otherName))} points at you. pointing-system-point-at-tile = You point at the {$tileName}. pointing-system-other-point-at-tile = {CAPITALIZE(THE($otherName))} points at the {$tileName}. diff --git a/Resources/Locale/en-US/_strings/game-ticking/game-presets/preset-thief.ftl b/Resources/Locale/en-US/_strings/game-ticking/game-presets/preset-thief.ftl index ab2b8f88d7..46eab5fee3 100644 --- a/Resources/Locale/en-US/_strings/game-ticking/game-presets/preset-thief.ftl +++ b/Resources/Locale/en-US/_strings/game-ticking/game-presets/preset-thief.ftl @@ -10,7 +10,7 @@ thief-role-greeting-animal = Steal things that you like. thief-role-greeting-equipment = - You have a toolbox of thieves' + You have a satchel of thieves' tools and chameleon thieves' gloves. Choose your starting equipment, and do your work stealthily. diff --git a/Resources/Locale/en-US/_strings/guardian/guardian.ftl b/Resources/Locale/en-US/_strings/guardian/guardian.ftl index 141646087d..13cb9ad9da 100644 --- a/Resources/Locale/en-US/_strings/guardian/guardian.ftl +++ b/Resources/Locale/en-US/_strings/guardian/guardian.ftl @@ -6,8 +6,7 @@ guardian-already-present-invalid-creation = You are NOT re-living that haunting guardian-no-actions-invalid-creation = You don't have the ability to host a guardian! guardian-activator-empty-invalid-creation = The injector is spent. guardian-activator-empty-examine = [color=#ba1919]The injector is spent.[/color] -# TODO: Change this once other species can inject it? -guardian-activator-invalid-target = Only humans can be injected! +guardian-activator-invalid-target = {CAPITALIZE(THE($entity))} cannot be injected! guardian-no-soul = Your guardian has no soul. guardian-available = Your guardian now has a soul. guardian-inside-container = There's no room to release your guardian! diff --git a/Resources/Locale/en-US/_strings/health-examinable/stethoscope.ftl b/Resources/Locale/en-US/_strings/health-examinable/stethoscope.ftl index decfd7795b..d4baf4cc93 100644 --- a/Resources/Locale/en-US/_strings/health-examinable/stethoscope.ftl +++ b/Resources/Locale/en-US/_strings/health-examinable/stethoscope.ftl @@ -1,6 +1,15 @@ stethoscope-verb = Listen with stethoscope -stethoscope-dead = You hear nothing. + +stethoscope-nothing = You don't hear anything. + stethoscope-normal = You hear normal breathing. +stethoscope-raggedy = You hear raggedy breathing. stethoscope-hyper = You hear hyperventilation. stethoscope-irregular = You hear hyperventilation with an irregular pattern. stethoscope-fucked = You hear twitchy, labored breathing interspersed with short gasps. + +stethoscope-delta-steady = It's steady. +stethoscope-delta-improving = It's improving. +stethoscope-delta-worsening = It's getting worse. + +stethoscope-combined-status = {$absolute} {$delta} diff --git a/Resources/Locale/en-US/_strings/temperature/entity-heater.ftl b/Resources/Locale/en-US/_strings/temperature/entity-heater.ftl index a809d508e7..391e84e512 100644 --- a/Resources/Locale/en-US/_strings/temperature/entity-heater.ftl +++ b/Resources/Locale/en-US/_strings/temperature/entity-heater.ftl @@ -1,3 +1,18 @@ -entity-heater-examined = It is set to [color=gray]{$setting}[/color] -entity-heater-switch-setting = Switch to {$setting} -entity-heater-switched-setting = Switched to {$setting} +-entity-heater-setting-name = + { $setting -> + [off] off + [low] low + [medium] medium + [high] high + *[other] unknown + } + +entity-heater-examined = It is set to { $setting -> + [off] [color=gray]{ -entity-heater-setting-name(setting: "off") }[/color] + [low] [color=yellow]{ -entity-heater-setting-name(setting: "low") }[/color] + [medium] [color=orange]{ -entity-heater-setting-name(setting: "medium") }[/color] + [high] [color=red]{ -entity-heater-setting-name(setting: "high") }[/color] + *[other] [color=purple]{ -entity-heater-setting-name(setting: "other") }[/color] +}. +entity-heater-switch-setting = Switch to { -entity-heater-setting-name(setting: $setting) } +entity-heater-switched-setting = Switched to { -entity-heater-setting-name(setting: $setting) }. diff --git a/Resources/Locale/en-US/_strings/thief/backpack.ftl b/Resources/Locale/en-US/_strings/thief/backpack.ftl index 6d3baa2c0d..962480e2e2 100644 --- a/Resources/Locale/en-US/_strings/thief/backpack.ftl +++ b/Resources/Locale/en-US/_strings/thief/backpack.ftl @@ -1,4 +1,4 @@ -thief-backpack-window-title = thief toolbox +thief-backpack-window-title = thieving kit thief-backpack-window-description = Inside are your tools of the trade, which will dissolve when you're ready. diff --git a/Resources/Locale/en-US/_strings/tips.ftl b/Resources/Locale/en-US/_strings/tips.ftl index e0e71d66da..ae43ea094a 100644 --- a/Resources/Locale/en-US/_strings/tips.ftl +++ b/Resources/Locale/en-US/_strings/tips.ftl @@ -135,3 +135,4 @@ tips-dataset-134 = You can tell if an area with firelocks up is spaced by lookin tips-dataset-135 = Instead of picking it up, you can alt-click food to eat it. This also works for mice and other creatures without hands. tips-dataset-136 = If you're trapped behind an electrified door, disable the APC or throw your ID at the door to avoid getting shocked! tips-dataset-137 = If the AI electrifies a door and you have insulated gloves, snip and mend the power wire to reset their electrification! +tips-dataset-138 = If you want to stop your prisoner from escaping from the cell right after being uncuffed, turn on combat mode while uncuffing - this will shove the prisoner down. diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/medical.yml b/Resources/Prototypes/Catalog/Fills/Lockers/medical.yml index 8b0b4c837d..2479d65ded 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/medical.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/medical.yml @@ -1,74 +1,79 @@ -- type: entity - id: LockerMedicineFilled - suffix: Filled - parent: LockerMedicine - components: - - type: StorageFill - contents: - - id: BoxSyringe - - id: ChemistryBottleEpinephrine - amount: 1 - - id: Brutepack - amount: 2 - - id: Ointment - amount: 2 - - id: Bloodpack - amount: 2 - - id: Gauze +- type: entityTable + id: LockerFillMedicine + table: !type:AllSelector + children: + - id: BoxSyringe + - id: ChemistryBottleEpinephrine + - id: Brutepack + amount: !type:ConstantNumberSelector + value: 2 + - id: Ointment + amount: !type:ConstantNumberSelector + value: 2 + - id: Bloodpack + amount: !type:ConstantNumberSelector + value: 2 + - id: Gauze - type: entity + parent: LockerMedicine + id: LockerMedicineFilled + suffix: Filled + components: + - type: EntityTableContainerFill + containers: + entity_storage: !type:NestedSelector + tableId: LockerFillMedicine + +- type: entity + parent: LockerWallMedical id: LockerWallMedicalFilled name: medicine wall locker suffix: Filled - parent: LockerWallMedical components: - - type: StorageFill - contents: - - id: BoxSyringe - - id: ChemistryBottleEpinephrine - amount: 1 - - id: Brutepack - amount: 2 - - id: Ointment - amount: 2 - - id: Bloodpack - amount: 2 - - id: Gauze + - type: EntityTableContainerFill + containers: + entity_storage: !type:NestedSelector + tableId: LockerFillMedicine +- type: entityTable + id: LockerFillMedicalDoctor + table: !type:AllSelector + children: + - id: HandheldHealthAnalyzer + prob: 0.6 + - id: ClothingHeadMirror + prob: 0.1 + - id: ClothingHandsGlovesLatex + - id: ClothingHeadsetMedical + - id: ClothingEyesHudMedical + - !type:GroupSelector + children: + - id: ClothingHeadHatSurgcapGreen + weight: 0.1 + - id: ClothingHeadHatSurgcapPurple + weight: 0.05 + - id: ClothingHeadHatSurgcapBlue + weight: 0.90 + - !type:GroupSelector + children: + - id: UniformScrubsColorBlue + weight: 0.5 + - id: UniformScrubsColorGreen + weight: 0.1 + - id: UniformScrubsColorPurple + weight: 0.05 + - id: ClothingMaskSterile - type: entity + parent: LockerMedical id: LockerMedicalFilled suffix: Filled - parent: LockerMedical components: - - type: StorageFill - contents: - - id: HandheldHealthAnalyzer - prob: 0.6 - - id: ClothingHeadMirror - prob: 0.1 - - id: ClothingHandsGlovesLatex - - id: ClothingHeadsetMedical - - id: ClothingEyesHudMedical - - id: ClothingHeadHatSurgcapGreen - prob: 0.1 - orGroup: Surgcaps - - id: ClothingHeadHatSurgcapPurple - prob: 0.05 - orGroup: Surgcaps - - id: ClothingHeadHatSurgcapBlue - prob: 0.90 - orGroup: Surgcaps - - id: UniformScrubsColorBlue - prob: 0.5 - orGroup: Surgshrubs - - id: UniformScrubsColorGreen - prob: 0.1 - orGroup: Surgshrubs - - id: UniformScrubsColorPurple - prob: 0.05 - orGroup: Surgshrubs - - id: ClothingMaskSterile + - type: EntityTableContainerFill + containers: + entity_storage: !type:NestedSelector + tableId: LockerFillMedicalDoctor - type: entity parent: LockerWallMedical @@ -76,81 +81,73 @@ name: medical doctor's wall locker suffix: Filled components: - - type: StorageFill - contents: - - id: HandheldHealthAnalyzer - prob: 0.6 - - id: ClothingHandsGlovesLatex - - id: ClothingHeadsetMedical - - id: ClothingEyesHudMedical - - id: ClothingHeadHatSurgcapGreen - prob: 0.1 - orGroup: Surgcaps - - id: ClothingHeadHatSurgcapPurple - prob: 0.05 - orGroup: Surgcaps - - id: ClothingHeadHatSurgcapBlue - prob: 0.90 - orGroup: Surgcaps - - id: UniformScrubsColorBlue - prob: 0.5 - orGroup: Surgshrubs - - id: UniformScrubsColorGreen - prob: 0.1 - orGroup: Surgshrubs - - id: UniformScrubsColorPurple - prob: 0.05 - orGroup: Surgshrubs - - id: ClothingMaskSterile + - type: EntityTableContainerFill + containers: + entity_storage: !type:NestedSelector + tableId: LockerFillMedicalDoctor + +- type: entityTable + id: LockerFillChemistry + table: !type:AllSelector + children: + #Sunrise-start + - id: LauncherSyringeMed + prob: 0.3 + - id: BoxMiniSyringe + prob: 0.2 + - id: BoxMiniSyringe + prob: 0.7 + #Sunrise-end + - id: BoxSyringe + - id: BoxBeaker + - id: BoxBeaker + prob: 0.3 + - id: BoxPillCanister + - id: BoxBottle + - id: BoxVial + - id: PlasmaChemistryVial + - id: ChemBag + - id: ClothingHandsGlovesLatex + - id: ClothingHeadsetMedical + - id: ClothingMaskSterile + - id: HandLabeler + prob: 0.5 - type: entity + parent: LockerChemistry id: LockerChemistryFilled suffix: Filled - parent: LockerChemistry components: - - type: StorageFill - contents: - #Sunrise-start - - id: LauncherSyringeMed - prob: 0.3 - - id: BoxMiniSyringe - prob: 0.2 - - id: BoxMiniSyringe - prob: 0.7 - #Sunrise-end - - id: BoxSyringe - - id: BoxBeaker - - id: BoxBeaker - prob: 0.3 - - id: BoxPillCanister - - id: BoxBottle - - id: BoxVial - - id: PlasmaChemistryVial - - id: ChemBag - - id: ClothingHandsGlovesLatex - - id: ClothingHeadsetMedical - - id: ClothingMaskSterile - - id: HandLabeler - prob: 0.5 + - type: EntityTableContainerFill + containers: + entity_storage: !type:NestedSelector + tableId: LockerFillChemistry + +- type: entityTable + id: LockerFillParamedic + table: !type:AllSelector + children: + - id: HandheldCrewMonitor # Sunrise-Edit + - id: ClothingOuterHardsuitVoidParamed + - id: ClothingOuterCoatParamedicWB + - id: ClothingHeadHatParamedicsoft + - id: ClothingOuterWinterPara + - id: ClothingUniformJumpsuitParamedic + - id: ClothingUniformJumpskirtParamedic + - id: ClothingEyesHudMedical + - id: ClothingHandsGlovesLatex + - id: ClothingHeadsetMedical + - id: ClothingMaskSterile + - id: HandheldGPSBasic + - id: MedkitFilled + prob: 0.3 - type: entity + parent: LockerParamedic id: LockerParamedicFilled suffix: Filled - parent: LockerParamedic components: - - type: StorageFill - contents: - - id: ClothingOuterHardsuitVoidParamed - - id: ClothingOuterCoatParamedicWB - - id: ClothingHeadHatParamedicsoft - - id: ClothingOuterWinterPara - - id: ClothingUniformJumpsuitParamedic - - id: ClothingUniformJumpskirtParamedic - - id: ClothingEyesHudMedical - - id: ClothingHandsGlovesLatex - - id: ClothingHeadsetMedical - - id: ClothingMaskSterile - - id: HandheldGPSBasic - - id: HandheldCrewMonitor # Sunrise-Edit - - id: MedkitFilled - prob: 0.3 + - type: EntityTableContainerFill + containers: + entity_storage: !type:NestedSelector + tableId: LockerFillParamedic diff --git a/Resources/Prototypes/Catalog/thief_toolbox_sets.yml b/Resources/Prototypes/Catalog/thief_toolbox_sets.yml index 8ad6d98889..2eb48c621f 100644 --- a/Resources/Prototypes/Catalog/thief_toolbox_sets.yml +++ b/Resources/Prototypes/Catalog/thief_toolbox_sets.yml @@ -6,16 +6,7 @@ sprite: /Textures/Clothing/OuterClothing/Misc/black_hoodie.rsi state: icon content: - - ChameleonPDA - - ClothingUniformJumpsuitChameleon - - ClothingOuterChameleon - - ClothingNeckChameleon - - ClothingMaskGasChameleon - - ClothingHeadHatChameleon - - ClothingEyesChameleon - - ClothingHeadsetChameleon - - ClothingShoesChameleon - - BarberScissors + - ClothingBackpackChameleonFill - ChameleonProjector - FakeMindShieldImplanter - AgentIDCard diff --git a/Resources/Prototypes/Entities/Clothing/Back/smuggler.yml b/Resources/Prototypes/Entities/Clothing/Back/smuggler.yml index c9d7f61890..f5ec4fcd3a 100644 --- a/Resources/Prototypes/Entities/Clothing/Back/smuggler.yml +++ b/Resources/Prototypes/Entities/Clothing/Back/smuggler.yml @@ -37,7 +37,7 @@ id: ClothingBackpackSatchelSmuggler name: smuggler's satchel suffix: Empty - description: A dingy, suspicious looking satchel. + description: A handy, suspicious looking satchel. Just flat enough to fit underneath floor tiles. components: - type: Sprite sprite: Clothing/Back/Satchels/smuggler.rsi @@ -48,7 +48,7 @@ id: ClothingBackpackSatchelSmugglerUnanchored name: smuggler's satchel suffix: Empty, Unanchored - description: A dingy, suspicious looking satchel. + description: A handy, suspicious looking satchel. Just flat enough to fit underneath floor tiles. components: - type: Sprite sprite: Clothing/Back/Satchels/smuggler.rsi diff --git a/Resources/Prototypes/Entities/Clothing/Neck/misc.yml b/Resources/Prototypes/Entities/Clothing/Neck/misc.yml index f712ec1b1d..26071b5146 100644 --- a/Resources/Prototypes/Entities/Clothing/Neck/misc.yml +++ b/Resources/Prototypes/Entities/Clothing/Neck/misc.yml @@ -32,17 +32,36 @@ path: /Audio/Items/flashlight_off.ogg - type: entity - parent: ClothingNeckBase + parent: Clothing id: ClothingNeckStethoscope name: stethoscope description: An outdated medical apparatus for listening to the sounds of the human body. It also makes you look like you know what you're doing. components: + - type: Item + size: Small - type: Sprite sprite: Clothing/Neck/Misc/stethoscope.rsi + state: icon - type: Clothing sprite: Clothing/Neck/Misc/stethoscope.rsi + quickEquip: true + slots: + - neck - type: Stethoscope +- type: entity + id: ActionStethoscope + name: Listen with stethoscope + components: + - type: EntityTargetAction + icon: + sprite: Clothing/Neck/Misc/stethoscope.rsi + state: icon + event: !type:StethoscopeActionEvent + checkCanInteract: false + priority: -1 + itemIconStyle: BigAction + - type: entity parent: ClothingNeckBase id: ClothingNeckBling @@ -69,18 +88,6 @@ - type: TypingIndicatorClothing proto: lawyer -- type: entity - id: ActionStethoscope - name: Listen with stethoscope - components: - - type: EntityTargetAction - icon: - sprite: Clothing/Neck/Misc/stethoscope.rsi - state: icon - event: !type:StethoscopeActionEvent - checkCanInteract: false - priority: -1 - - type: entity parent: ClothingNeckBase id: Dinkystar diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/food_single.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/food_single.yml index 4b7805c3d3..fda7b85b75 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/food_single.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/food_single.yml @@ -67,4 +67,5 @@ - FoodBurgerCrazy - FoodPizzaArnoldSlice - FoodPizzaUraniumSlice + - FoodPizzaWorldpeasSlice rareChance: 0.05 diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pizza.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pizza.yml index 8f476f9263..9fe96a18a1 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pizza.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pizza.yml @@ -681,3 +681,55 @@ Quantity: 0.8 - ReagentId: Fiber Quantity: 1.5 + +- type: entity + name: world peazza + parent: FoodPizzaBase + id: FoodPizzaWorldpeas + description: Modern diplomacy in the shape of a disc. + components: + - type: FlavorProfile + flavors: + - bread + - numbingtranquility + - type: Sprite + layers: + - state: worldpeas + - type: SliceableFood + slice: FoodPizzaWorldpeasSlice + - type: SolutionContainerManager + solutions: + food: + maxVol: 45 + reagents: + - ReagentId: Nutriment + Quantity: 20 + - ReagentId: Happiness + Quantity: 12 + - ReagentId: Pax + Quantity: 8 + +- type: entity + name: slice of world peazza + parent: FoodPizzaSliceBase + id: FoodPizzaWorldpeasSlice + description: Dividing the world up is a small price to pay for harmony. + components: + - type: FlavorProfile + flavors: + - bread + - numbingtranquility + - type: Sprite + layers: + - state: worldpeas-slice + - type: SolutionContainerManager + solutions: + food: + maxVol: 10 + reagents: + - ReagentId: Nutriment + Quantity: 3.5 + - ReagentId: Happiness + Quantity: 2 + - ReagentId: Pax + Quantity: 1.5 diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/box.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/box.yml index fe690d8bd1..7dd402ae6d 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/box.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/box.yml @@ -273,6 +273,9 @@ - id: FoodPizzaCotton prob: 0.10 orGroup: Pizza + - id: FoodPizzaWorldpeas + prob: 0.05 + orGroup: Pizza - id: KnifePlastic - type: entity diff --git a/Resources/Prototypes/Entities/Objects/Devices/hand_teleporter.yml b/Resources/Prototypes/Entities/Objects/Devices/hand_teleporter.yml index 192aca65fc..f6e30d1e97 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/hand_teleporter.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/hand_teleporter.yml @@ -28,5 +28,7 @@ - state: icon color: green - type: HandTeleporter + allowPortalsOnDifferentGrids: true + allowPortalsOnDifferentMaps: true firstPortalPrototype: PortalGatewayBlue secondPortalPrototype: PortalGatewayOrange diff --git a/Resources/Prototypes/Entities/Objects/Tools/light_replacer.yml b/Resources/Prototypes/Entities/Objects/Tools/light_replacer.yml index 646f6a6378..34dcd66b71 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/light_replacer.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/light_replacer.yml @@ -2,7 +2,7 @@ parent: BaseItem name: light replacer id: LightReplacer - description: An item which uses magnets to easily replace broken lights. Refill By adding more lights into the replacer. + description: An item which uses magnets to easily replace broken lights. Refill by adding more lights into the replacer. components: - type: Sprite sprite: Objects/Specific/Janitorial/light_replacer.rsi diff --git a/Resources/Prototypes/Entities/Objects/Tools/thief.yml b/Resources/Prototypes/Entities/Objects/Tools/thief.yml new file mode 100644 index 0000000000..7200c8c06d --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Tools/thief.yml @@ -0,0 +1,80 @@ +- type: entity + parent: BaseMinorContraband + id: ThiefBeacon + name: thieving beacon + description: A device that will teleport everything around it to the thief's vault at the end of the shift. + components: + - type: ThiefBeacon + - type: StealArea + range: 2 # Slightly larger than fulton beacon's random offset + - type: Item + size: Normal + - type: Physics + bodyType: Dynamic + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.25,-0.4,0.25,0.1" + density: 20 + mask: + - Impassable + - type: Foldable + folded: true + - type: Clickable + - type: InteractionOutline + - type: Appearance + - type: GenericVisualizer + visuals: + enum.FoldedVisuals.State: + foldedLayer: + True: { state: folded_extraction } + False: { state: extraction_point } + - type: Sprite + sprite: Objects/Tools/thief_beacon.rsi + drawdepth: SmallObjects + noRot: true + layers: + - state: extraction_point + map: [ "foldedLayer" ] + +- type: entity + id: ToolboxThief + name: undetermined thieving toolbox + description: This is where your favorite thief's supplies lie. Try to remember which ones. + parent: [ BaseItem, BaseMinorContraband ] + components: + - type: Sprite + sprite: Objects/Tools/Toolboxes/toolbox_thief.rsi + state: icon + - type: ThiefUndeterminedBackpack + possibleSets: + # TODO Thief pinpointer needed + - ChemistrySet + - ToolsSet + - ChameleonSet # TODO Chameleon stump PR needed + - SyndieSet + - SleeperSet + - CommunicatorSet + - SmugglerSet + - type: ActivatableUI + key: enum.ThiefBackpackUIKey.Key + - type: UserInterface + interfaces: + enum.ThiefBackpackUIKey.Key: + type: ThiefBackpackBoundUserInterface + +- type: entity + id: SatchelThief + name: undetermined thieving satchel + description: This is where your favorite thief's supplies lie. Folded for your convenience. + parent: ToolboxThief + components: + - type: Sprite + sprite: Clothing/Back/Satchels/smuggler.rsi + state: folded + - type: Item + storedRotation: 90 + - type: ThiefUndeterminedBackpack + spawnedStoragePrototype: ClothingBackpackSatchelSmugglerUnanchored diff --git a/Resources/Prototypes/Entities/Objects/Tools/thief_beacon.yml b/Resources/Prototypes/Entities/Objects/Tools/thief_beacon.yml deleted file mode 100644 index f0f3737417..0000000000 --- a/Resources/Prototypes/Entities/Objects/Tools/thief_beacon.yml +++ /dev/null @@ -1,40 +0,0 @@ -- type: entity - parent: BaseMinorContraband - id: ThiefBeacon - name: thieving beacon - description: A device that will teleport everything around it to the thief's vault at the end of the shift. - components: - - type: ThiefBeacon - - type: StealArea - range: 2 # Slightly larger than fulton beacon's random offset - - type: Item - size: Normal - - type: Physics - bodyType: Dynamic - - type: Fixtures - fixtures: - fix1: - shape: - !type:PhysShapeAabb - bounds: "-0.25,-0.4,0.25,0.1" - density: 20 - mask: - - Impassable - - type: Foldable - folded: true - - type: Clickable - - type: InteractionOutline - - type: Appearance - - type: GenericVisualizer - visuals: - enum.FoldedVisuals.State: - foldedLayer: - True: { state: folded_extraction } - False: { state: extraction_point } - - type: Sprite - sprite: Objects/Tools/thief_beacon.rsi - drawdepth: SmallObjects - noRot: true - layers: - - state: extraction_point - map: [ "foldedLayer" ] diff --git a/Resources/Prototypes/Entities/Objects/Tools/toolbox.yml b/Resources/Prototypes/Entities/Objects/Tools/toolbox.yml index 7cf20a3b24..d3a0c458e2 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/toolbox.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/toolbox.yml @@ -151,29 +151,3 @@ state: icon - type: Item sprite: Objects/Tools/Toolboxes/toolbox_gold.rsi - -- type: entity - id: ToolboxThief - name: thief undetermined toolbox - description: This is where your favorite thief's supplies lie. Try to remember which ones. - parent: [ BaseItem, BaseMinorContraband ] - components: - - type: Sprite - sprite: Objects/Tools/Toolboxes/toolbox_thief.rsi - state: icon - - type: ThiefUndeterminedBackpack - possibleSets: - # TODO Thief pinpointer needed - - ChemistrySet - - ToolsSet - - ChameleonSet # TODO Chameleon stump PR needed - - SyndieSet - - SleeperSet - - CommunicatorSet - - SmugglerSet - - type: ActivatableUI - key: enum.ThiefBackpackUIKey.Key - - type: UserInterface - interfaces: - enum.ThiefBackpackUIKey.Key: - type: ThiefBackpackBoundUserInterface diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Turrets/turrets_energy.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Turrets/turrets_energy.yml index 2a37ba09c5..66860ae98c 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Turrets/turrets_energy.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Turrets/turrets_energy.yml @@ -29,7 +29,7 @@ # Sprites and appearance - type: Sprite sprite: Objects/Weapons/Guns/Turrets/sentry_turret.rsi - drawdepth: FloorObjects + drawdepth: HighFloorObjects granularLayersRendering: true layers: - state: support diff --git a/Resources/Prototypes/Entities/Structures/cargo_telepad.yml b/Resources/Prototypes/Entities/Structures/cargo_telepad.yml index a99f35a7d9..a3198b58ea 100644 --- a/Resources/Prototypes/Entities/Structures/cargo_telepad.yml +++ b/Resources/Prototypes/Entities/Structures/cargo_telepad.yml @@ -1,4 +1,4 @@ -- type: entity +- type: entity id: CargoTelepad parent: [ BaseMachinePowered, ConstructibleMachine ] name: cargo telepad @@ -49,3 +49,4 @@ - type: CollideOnAnchor - type: NameIdentifier group: CargoTelepads + - type: AnimationPlayer diff --git a/Resources/Prototypes/RCD/rcd.yml b/Resources/Prototypes/RCD/rcd.yml index 7761193763..58180fa30c 100644 --- a/Resources/Prototypes/RCD/rcd.yml +++ b/Resources/Prototypes/RCD/rcd.yml @@ -2,25 +2,25 @@ - type: rcd id: Invalid # Hidden prototype - do not add to RCDs mode: Invalid - + - type: rcd id: Deconstruct name: rcd-component-deconstruct - category: Main + category: WallsAndFlooring sprite: /Textures/Interface/Radial/RCD/deconstruct.png mode: Deconstruct prototype: EffectRCDDeconstructPreview rotation: Camera - type: rcd - id: DeconstructLattice # Hidden prototype - do not add to RCDs + id: DeconstructLattice # Hidden prototype - do not add to RCDs name: rcd-component-deconstruct mode: Deconstruct cost: 2 delay: 0 rotation: Camera fx: EffectRCDConstruct0 - + - type: rcd id: DeconstructTile # Hidden prototype - do not add to RCDs name: rcd-component-deconstruct @@ -30,7 +30,7 @@ rotation: Camera fx: EffectRCDDeconstruct4 -# Flooring +# Flooring - type: rcd id: Plating name: rcd-component-plating @@ -44,7 +44,7 @@ rules: - CanBuildOnEmptyTile fx: EffectRCDConstruct1 - + - type: rcd id: FloorSteel name: rcd-component-floor-steel @@ -80,7 +80,7 @@ category: WallsAndFlooring sprite: /Textures/Interface/Radial/RCD/solid_wall.png mode: ConstructObject - prototype: WallSolid + prototype: WallSolid cost: 4 delay: 2 collisionMask: FullTileMask @@ -113,7 +113,7 @@ - IsWindow rotation: Fixed fx: EffectRCDConstruct2 - + - type: rcd id: WindowDirectional category: WindowsAndGrilles @@ -128,7 +128,7 @@ - IsWindow rotation: User fx: EffectRCDConstruct1 - + - type: rcd id: ReinforcedWindow category: WindowsAndGrilles @@ -142,7 +142,7 @@ - IsWindow rotation: User fx: EffectRCDConstruct3 - + - type: rcd id: WindowReinforcedDirectional category: WindowsAndGrilles @@ -170,7 +170,7 @@ collisionMask: FullTileMask rotation: User fx: EffectRCDConstruct4 - + - type: rcd id: AirlockGlass category: Airlocks @@ -182,7 +182,7 @@ collisionMask: FullTileMask rotation: User fx: EffectRCDConstruct4 - + - type: rcd id: Firelock category: Airlocks @@ -208,7 +208,7 @@ collisionBounds: "-0.23,-0.49,0.23,-0.36" rotation: User fx: EffectRCDConstruct1 - + - type: rcd id: BulbLight category: Lighting @@ -235,7 +235,7 @@ - MustBuildOnSubfloor rotation: Fixed fx: EffectRCDConstruct0 - + - type: rcd id: MVCable category: Electrical @@ -248,7 +248,7 @@ - MustBuildOnSubfloor rotation: Fixed fx: EffectRCDConstruct0 - + - type: rcd id: HVCable category: Electrical @@ -261,7 +261,7 @@ - MustBuildOnSubfloor rotation: Fixed fx: EffectRCDConstruct0 - + - type: rcd id: CableTerminal category: Electrical diff --git a/Resources/Prototypes/Recipes/Cooking/meal_recipes.yml b/Resources/Prototypes/Recipes/Cooking/meal_recipes.yml index 2eeb3c392b..28a47fdc21 100644 --- a/Resources/Prototypes/Recipes/Cooking/meal_recipes.yml +++ b/Resources/Prototypes/Recipes/Cooking/meal_recipes.yml @@ -718,6 +718,15 @@ FoodDoughCottonFlat: 1 CottonBol: 4 +- type: microwaveMealRecipe + id: RecipeWorldpeasPizza + name: world peazza recipe + result: FoodPizzaWorldpeas + time: 30 + solids: + FoodDoughFlat: 1 + FoodWorldPeas: 3 + #Italian - type: microwaveMealRecipe id: RecipeBoiledSpaghetti diff --git a/Resources/Prototypes/Roles/Antags/thief.yml b/Resources/Prototypes/Roles/Antags/thief.yml index c5e5fabe7a..2f3b569a22 100644 --- a/Resources/Prototypes/Roles/Antags/thief.yml +++ b/Resources/Prototypes/Roles/Antags/thief.yml @@ -16,5 +16,5 @@ storage: back: - ThiefBeacon - - ToolboxThief + - SatchelThief - ClothingHandsChameleonThief diff --git a/Resources/ServerInfo/Guidebook/Antagonist/Thieves.xml b/Resources/ServerInfo/Guidebook/Antagonist/Thieves.xml index 4c11fc0d99..ec871851c6 100644 --- a/Resources/ServerInfo/Guidebook/Antagonist/Thieves.xml +++ b/Resources/ServerInfo/Guidebook/Antagonist/Thieves.xml @@ -1,6 +1,6 @@ # Thieves - + [color=#999999][italic]"Yoink! I'll be taking that! And that! Ooh, don't mind if I do!"[/italic][/color] @@ -25,7 +25,7 @@ ## Tools of the Trade - You've got two more aces up your stolen sleeves: your [color=cyan]beacon[/color] and your [color=cyan]toolbox.[/color] + You've got two more aces up your stolen sleeves: your [color=cyan]beacon[/color] and your [color=cyan]satchel.[/color] Your [color=cyan]beacon[/color] provides safe passage home for trinkets that may not be easy to carry with you on the evac shuttle. Simply find a secluded part of the station to unfold the beacon, then set its coordinates to your hideout. Any shinies near it will be [bold]teleported to your vault when the shift ends,[/bold] fulfilling your objectives. @@ -36,11 +36,10 @@ - Your [color=cyan]toolbox[/color] contains... well, whatever you remembered to pack. [bold]You can select two pre-made kits[/bold] to help you complete grander heists. - Approve your choices in a safe place, as the toolbox will dissolve and the gear will drop at your feet. + Your [color=cyan]satchel[/color] contains... well, whatever you remembered to pack. [bold]You can select two pre-made kits[/bold] to help you complete grander heists. - + @@ -56,7 +55,7 @@ ## Centerpiece of the Collection Your kleptomania will take you places. One day, you'll feel like stealing a few figurines. Another day, you'll feel like stealing an industrial machine. - + No matter. They'll all be a part of your collection within a matter of time. You can steal items by [bold]having them on your person[/bold] when you get to CentComm. Failing this, you can steal larger items by [bold]leaving them by your beacon.[/bold] @@ -64,12 +63,12 @@ Some of the more [italic]animate[/italic] objectives may not cooperate with you. Make sure they're alive and with you or your beacon when the shift ends. Things that you may desire include but are not limited to: - + - + diff --git a/Resources/ServerInfo/Guidebook/Security/Security.xml b/Resources/ServerInfo/Guidebook/Security/Security.xml index 888a2d83b6..3df94c6b9b 100644 --- a/Resources/ServerInfo/Guidebook/Security/Security.xml +++ b/Resources/ServerInfo/Guidebook/Security/Security.xml @@ -1,25 +1,43 @@ - # Служба безопасности - Обеспечение безопасности станции и экипажа является задачей номер один для Службы безопасности. Независимо от того, кто это - агенты Синдиката, Ядерные оперативники, инфицированные, или просто буйные сотрудники. Служба безопасности готова поддерживать порядок и контролировать толпу. +# Security +Ensuring the safety of both station and crew is number one for [color=#cb0000]Security[/color]. They are ready for any situation, and [color=#cb0000]Security[/color] are on hand to maintain order and control crowds. - ## Экипировка - Во-первых, у нас есть нелетальные средства, которые находятся на ступень выше простого приказа кому-либо подчиниться. И дубинка-шокер, и станнер способны остановить преступника, в то время как наручники помогут лишить его возможности двигаться делать что-либо руками. +They face [textlink="Syndicate Agents" link="Traitors"], [textlink="Nuclear Operatives" link="NuclearOperatives"], [textlink="Zombies" link="Zombies"] and unruly staff. + +## Personnel +[color=#cb0000]Security[/color]'s staff is made up of Security Cadets, Security Officers, and the Detective. [color=#cb0000]Security[/color] is run by the Warden and Head of Security. + + + + + + + + +## Gear +First we have non-lethals a step above simply telling someone to cooperate with instructions. Both the stunbaton and disabler are capable of limiting the movement of an assailant, whereas handcuffs can be applied to deny a criminal free movement and access to their hands. Cuffs will also shove the person down if your [color=red]harm mode[/color] is active during uncuff. - + - ## Вспышки - Вспышки являются эффективным средством для разгона толпы и задержания особо ловких целей, при этом большая площадь воздействия ослепит всех, кто не имеет соответствующей защиты. - Стоит отметить, что вспышки можно использовать как на большом расстоянии (горячая клавиша [color=yellow]Z[/color]), так и при атаке кого-либо при активном [color=red]режиме вреда[/color]. Важно отметить, что локальные вспышки оглушают в [color=yellow]2,5[/color] раза дольше, чем вспышки на большой площади. - Служба безопасности и [color=#a4885c]некоторые[/color] другие сотрудники имеют средства защиты глаз от вспышек и светошумовых гранат. +## Flashes +[color=#a4885c]Flashes[/color] are an effective tool for dispersing crowds and apprehending particularly evasive targets, with a large area of effect blinding all those without protection. +It is worth noting that flashes can be both used in a large area ([color=yellow]Z[/color] hotkey) and if you attack somebody with it while [color=red]harm mode[/color] is active. Important to note that localized flashes last [color=yellow]2.5[/color] times as long as large area ones. +[color=#cb0000]Security[/color] and [color=#a4885c]some[/color] other members of staff have eye protection from flashbangs and flashes. - ## Летальные боеприпасы - Если того потребует ситуация, служба безопасности имеет доступ к лазерным винтовкам, дробовикам, пистолетам и автоматам, способным вести огонь на поражение по любому, кто решит выступит против станции. +## Lethals +Should the situation dictate, [color=#cb0000]Security[/color] have access to laser rifles, shotguns, handguns and automatic rifles able to put down substancial fire against any who would stand against the station. + + + + + + diff --git a/Resources/Textures/Clothing/Back/Satchels/smuggler.rsi/folded.png b/Resources/Textures/Clothing/Back/Satchels/smuggler.rsi/folded.png new file mode 100644 index 0000000000..0773c0b94d Binary files /dev/null and b/Resources/Textures/Clothing/Back/Satchels/smuggler.rsi/folded.png differ diff --git a/Resources/Textures/Clothing/Back/Satchels/smuggler.rsi/meta.json b/Resources/Textures/Clothing/Back/Satchels/smuggler.rsi/meta.json index d3b44ffaa2..66c0f6df16 100644 --- a/Resources/Textures/Clothing/Back/Satchels/smuggler.rsi/meta.json +++ b/Resources/Textures/Clothing/Back/Satchels/smuggler.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/a8056c6ba7f5367934ef829116e57d743226e1f0", + "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/a8056c6ba7f5367934ef829116e57d743226e1f0, folded by princesscheeseballs (Discord)(https://github.com/Pronana).", "size": { "x": 32, "y": 32 @@ -10,6 +10,9 @@ { "name": "icon" }, + { + "name": "folded" + }, { "name": "equipped-BACKPACK", "directions": 4 diff --git a/Resources/Textures/Objects/Consumable/Food/Baked/pizza.rsi/meta.json b/Resources/Textures/Objects/Consumable/Food/Baked/pizza.rsi/meta.json index 607a9cf8f3..1d7e8a01a5 100644 --- a/Resources/Textures/Objects/Consumable/Food/Baked/pizza.rsi/meta.json +++ b/Resources/Textures/Objects/Consumable/Food/Baked/pizza.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation and modified by Swept at https://github.com/tgstation/tgstation/commit/40d75cc340c63582fb66ce15bf75a36115f6bdaa, Spicy Rock Pizza modified from margherita pizza by mkanke, cotton made by mlexf (discord 1143460554963427380)", + "copyright": "Taken from tgstation and modified by Swept at https://github.com/tgstation/tgstation/commit/40d75cc340c63582fb66ce15bf75a36115f6bdaa, Spicy Rock Pizza modified from margherita pizza by mkanke, cotton made by mlexf (discord 1143460554963427380), world peazza modified from margherita by MisterImp (GitHub)", "size": { "x": 32, "y": 32 @@ -149,6 +149,12 @@ }, { "name": "uranium-slice" + }, + { + "name": "worldpeas" + }, + { + "name": "worldpeas-slice" } ] } diff --git a/Resources/Textures/Objects/Consumable/Food/Baked/pizza.rsi/worldpeas-slice.png b/Resources/Textures/Objects/Consumable/Food/Baked/pizza.rsi/worldpeas-slice.png new file mode 100644 index 0000000000..52216714bf Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Food/Baked/pizza.rsi/worldpeas-slice.png differ diff --git a/Resources/Textures/Objects/Consumable/Food/Baked/pizza.rsi/worldpeas.png b/Resources/Textures/Objects/Consumable/Food/Baked/pizza.rsi/worldpeas.png new file mode 100644 index 0000000000..d521aa5c49 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Food/Baked/pizza.rsi/worldpeas.png differ