diff --git a/Content.Benchmarks/PvsBenchmark.cs b/Content.Benchmarks/PvsBenchmark.cs index 0b4dd90762..fa7f9d4542 100644 --- a/Content.Benchmarks/PvsBenchmark.cs +++ b/Content.Benchmarks/PvsBenchmark.cs @@ -1,19 +1,17 @@ #nullable enable using System; -using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using Content.IntegrationTests; using Content.IntegrationTests.Pair; +using Content.Server.Mind; using Content.Server.Warps; using Robust.Server.GameObjects; using Robust.Shared; using Robust.Shared.Analyzers; -using Robust.Shared.Enums; using Robust.Shared.GameObjects; -using Robust.Shared.GameStates; using Robust.Shared.Map; -using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Random; @@ -58,15 +56,20 @@ public class PvsBenchmark _pair.Server.CfgMan.SetCVar(CVars.NetPvsAsync, false); _sys = _entMan.System(); + SetupAsync().Wait(); + } + + private async Task SetupAsync() + { // Spawn the map _pair.Server.ResolveDependency().SetSeed(42); - _pair.Server.WaitPost(() => + await _pair.Server.WaitPost(() => { var success = _entMan.System().TryLoad(_mapId, Map, out _); if (!success) throw new Exception("Map load failed"); _pair.Server.MapMan.DoMapInitialize(_mapId); - }).Wait(); + }); // Get list of ghost warp positions _spawns = _entMan.AllComponentsList() @@ -76,17 +79,19 @@ public class PvsBenchmark Array.Resize(ref _players, PlayerCount); - // Spawn "Players". - _pair.Server.WaitPost(() => + // Spawn "Players" + _players = await _pair.Server.AddDummySessions(PlayerCount); + await _pair.Server.WaitPost(() => { + var mind = _pair.Server.System(); for (var i = 0; i < PlayerCount; i++) { var pos = _spawns[i % _spawns.Length]; var uid =_entMan.SpawnEntity("MobHuman", pos); _pair.Server.ConsoleHost.ExecuteCommand($"setoutfit {_entMan.GetNetEntity(uid)} CaptainGear"); - _players[i] = new DummySession{AttachedEntity = uid}; + mind.ControlMob(_players[i].UserId, uid); } - }).Wait(); + }); // Repeatedly move players around so that they "explore" the map and see lots of entities. // This will populate their PVS data with out-of-view entities. @@ -168,20 +173,4 @@ public class PvsBenchmark }).Wait(); _pair.Server.PvsTick(_players); } - - private sealed class DummySession : ICommonSession - { - public SessionStatus Status => SessionStatus.InGame; - public EntityUid? AttachedEntity {get; set; } - public NetUserId UserId => default; - public string Name => string.Empty; - public short Ping => default; - public INetChannel Channel { get; set; } = default!; - public LoginType AuthType => default; - public HashSet ViewSubscriptions { get; } = new(); - public DateTime ConnectedTime { get; set; } - public SessionState State => default!; - public SessionData Data => default!; - public bool ClientSide { get; set; } - } } diff --git a/Content.Client/Access/UI/AgentIDCardBoundUserInterface.cs b/Content.Client/Access/UI/AgentIDCardBoundUserInterface.cs index c3fac8cb92..761f52988a 100644 --- a/Content.Client/Access/UI/AgentIDCardBoundUserInterface.cs +++ b/Content.Client/Access/UI/AgentIDCardBoundUserInterface.cs @@ -1,5 +1,7 @@ using Content.Shared.Access.Systems; +using Content.Shared.StatusIcon; using Robust.Client.GameObjects; +using Robust.Shared.Prototypes; namespace Content.Client.Access.UI { @@ -40,7 +42,7 @@ namespace Content.Client.Access.UI SendMessage(new AgentIDCardJobChangedMessage(newJob)); } - public void OnJobIconChanged(string newJobIconId) + public void OnJobIconChanged(ProtoId newJobIconId) { SendMessage(new AgentIDCardJobIconChangedMessage(newJobIconId)); } diff --git a/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs b/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs index 9a38c0c485..6d0b2a184f 100644 --- a/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs +++ b/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs @@ -38,7 +38,7 @@ namespace Content.Client.Access.UI JobLineEdit.OnFocusExit += e => OnJobChanged?.Invoke(e.Text); } - public void SetAllowedIcons(HashSet icons, string currentJobIconId) + public void SetAllowedIcons(HashSet> icons, string currentJobIconId) { IconGrid.DisposeAllChildren(); @@ -46,10 +46,8 @@ namespace Content.Client.Access.UI var i = 0; foreach (var jobIconId in icons) { - if (!_prototypeManager.TryIndex(jobIconId, out var jobIcon)) - { + if (!_prototypeManager.TryIndex(jobIconId, out var jobIcon)) continue; - } String styleBase = StyleBase.ButtonOpenBoth; var modulo = i % JobIconColumnCount; @@ -77,7 +75,7 @@ namespace Content.Client.Access.UI }; jobIconButton.AddChild(jobIconTexture); - jobIconButton.OnPressed += _ => _bui.OnJobIconChanged(jobIcon.ID); + jobIconButton.OnPressed += _ => _bui.OnJobIconChanged(jobIconId); IconGrid.AddChild(jobIconButton); if (jobIconId.Equals(currentJobIconId)) diff --git a/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs b/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs index 298912e7d5..82f6ebd8b5 100644 --- a/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs +++ b/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs @@ -27,6 +27,9 @@ namespace Content.Client.Access.UI private string? _lastJobTitle; private string? _lastJobProto; + // The job that will be picked if the ID doesn't have a job on the station. + private static ProtoId _defaultJob = "Passenger"; + public IdCardConsoleWindow(IdCardConsoleBoundUserInterface owner, IPrototypeManager prototypeManager, List> accessLevels) { @@ -65,7 +68,6 @@ namespace Content.Client.Access.UI } JobPresetOptionButton.OnItemSelected += SelectJobPreset; - _accessButtons.Populate(accessLevels, prototypeManager); AccessLevelControlContainer.AddChild(_accessButtons); @@ -172,11 +174,15 @@ namespace Content.Client.Access.UI new List>()); var jobIndex = _jobPrototypeIds.IndexOf(state.TargetIdJobPrototype); - if (jobIndex >= 0) + // If the job index is < 0 that means they don't have a job registered in the station records. + // For example, a new ID from a box would have no job index. + if (jobIndex < 0) { - JobPresetOptionButton.SelectId(jobIndex); + jobIndex = _jobPrototypeIds.IndexOf(_defaultJob); } + JobPresetOptionButton.SelectId(jobIndex); + _lastFullName = state.TargetIdFullName; _lastJobTitle = state.TargetIdJobTitle; _lastJobProto = state.TargetIdJobPrototype; diff --git a/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs b/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs index dc263d6055..588d62e560 100644 --- a/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs +++ b/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs @@ -147,7 +147,7 @@ public sealed partial class BanPanel : DefaultWindow var prototypeManager = IoCManager.Resolve(); foreach (var proto in prototypeManager.EnumeratePrototypes()) { - CreateRoleGroup(proto.ID, proto.Roles, proto.Color); + CreateRoleGroup(proto.ID, proto.Roles.Select(p => p.Id), proto.Color); } CreateRoleGroup("Antagonist", prototypeManager.EnumeratePrototypes().Select(p => p.ID), Color.Red); diff --git a/Content.Client/Administration/UI/SpawnExplosion/ExplosionDebugOverlay.cs b/Content.Client/Administration/UI/SpawnExplosion/ExplosionDebugOverlay.cs index eede3a6217..d60094ad89 100644 --- a/Content.Client/Administration/UI/SpawnExplosion/ExplosionDebugOverlay.cs +++ b/Content.Client/Administration/UI/SpawnExplosion/ExplosionDebugOverlay.cs @@ -25,7 +25,7 @@ public sealed class ExplosionDebugOverlay : Overlay public override OverlaySpace Space => OverlaySpace.WorldSpace | OverlaySpace.ScreenSpace; - public Matrix3 SpaceMatrix; + public Matrix3x2 SpaceMatrix; public MapId Map; private readonly Font _font; @@ -78,7 +78,8 @@ public sealed class ExplosionDebugOverlay : Overlay if (SpaceTiles == null) return; - gridBounds = Matrix3.Invert(SpaceMatrix).TransformBox(args.WorldBounds); + Matrix3x2.Invert(SpaceMatrix, out var invSpace); + gridBounds = invSpace.TransformBox(args.WorldBounds); DrawText(handle, gridBounds, SpaceMatrix, SpaceTiles, SpaceTileSize); } @@ -86,7 +87,7 @@ public sealed class ExplosionDebugOverlay : Overlay private void DrawText( DrawingHandleScreen handle, Box2 gridBounds, - Matrix3 transform, + Matrix3x2 transform, Dictionary> tileSets, ushort tileSize) { @@ -103,7 +104,7 @@ public sealed class ExplosionDebugOverlay : Overlay if (!gridBounds.Contains(centre)) continue; - var worldCenter = transform.Transform(centre); + var worldCenter = Vector2.Transform(centre, transform); var screenCenter = _eyeManager.WorldToScreen(worldCenter); @@ -119,7 +120,7 @@ public sealed class ExplosionDebugOverlay : Overlay if (tileSets.TryGetValue(0, out var set)) { var epicenter = set.First(); - var worldCenter = transform.Transform((epicenter + Vector2Helpers.Half) * tileSize); + var worldCenter = Vector2.Transform((epicenter + Vector2Helpers.Half) * tileSize, transform); var screenCenter = _eyeManager.WorldToScreen(worldCenter) + new Vector2(-24, -24); var text = $"{Intensity[0]:F2}\nΣ={TotalIntensity:F1}\nΔ={Slope:F1}"; handle.DrawString(_font, screenCenter, text); @@ -148,11 +149,12 @@ public sealed class ExplosionDebugOverlay : Overlay if (SpaceTiles == null) return; - gridBounds = Matrix3.Invert(SpaceMatrix).TransformBox(args.WorldBounds).Enlarged(2); + Matrix3x2.Invert(SpaceMatrix, out var invSpace); + gridBounds = invSpace.TransformBox(args.WorldBounds).Enlarged(2); handle.SetTransform(SpaceMatrix); DrawTiles(handle, gridBounds, SpaceTiles, SpaceTileSize); - handle.SetTransform(Matrix3.Identity); + handle.SetTransform(Matrix3x2.Identity); } private void DrawTiles( diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml index fb68e6c790..ea89916ba8 100644 --- a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml +++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml @@ -1,15 +1,21 @@  + xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls" + xmlns:ot="clr-namespace:Content.Client.Administration.UI.Tabs.ObjectsTab" + xmlns:co="clr-namespace:Content.Client.UserInterface.Controls"> + + - - - - + + + + + diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs index a5c3008436..90559707f9 100644 --- a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs +++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs @@ -1,5 +1,7 @@ using Content.Client.Station; +using Content.Client.UserInterface.Controls; using Robust.Client.AutoGenerated; +using Robust.Client.Graphics; using Robust.Client.UserInterface; using Robust.Client.UserInterface.XAML; using Robust.Shared.Map.Components; @@ -10,20 +12,20 @@ namespace Content.Client.Administration.UI.Tabs.ObjectsTab; [GenerateTypedNameReferences] public sealed partial class ObjectsTab : Control { - [Dependency] private readonly EntityManager _entityManager = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IGameTiming _timing = default!; private readonly List _objects = new(); - private List _selections = new(); + private readonly List _selections = new(); + private bool _ascending = false; // Set to false for descending order by default + private ObjectsTabHeader.Header _headerClicked = ObjectsTabHeader.Header.ObjectName; + private readonly Color _altColor = Color.FromHex("#292B38"); + private readonly Color _defaultColor = Color.FromHex("#2F2F3B"); - public event Action? OnEntryKeyBindDown; + public event Action? OnEntryKeyBindDown; - // Listen I could either have like 4 different event subscribers (for map / grid / station changes) and manage their lifetimes in AdminUIController - // OR - // I can do this. - private TimeSpan _updateFrequency = TimeSpan.FromSeconds(2); - - private TimeSpan _nextUpdate = TimeSpan.FromSeconds(2); + private readonly TimeSpan _updateFrequency = TimeSpan.FromSeconds(2); + private TimeSpan _nextUpdate; public ObjectsTab() { @@ -42,6 +44,30 @@ public sealed partial class ObjectsTab : Control ObjectTypeOptions.AddItem(Enum.GetName((ObjectsTabSelection)type)!); } + ListHeader.OnHeaderClicked += HeaderClicked; + SearchList.SearchBar = SearchLineEdit; + SearchList.GenerateItem += GenerateButton; + SearchList.DataFilterCondition += DataFilterCondition; + + RefreshObjectList(); + // Set initial selection and refresh the list to apply the initial sort order + var defaultSelection = ObjectsTabSelection.Grids; + ObjectTypeOptions.SelectId((int)defaultSelection); // Set the default selection + RefreshObjectList(defaultSelection); // Refresh the list with the default selection + + // Initialize the next update time + _nextUpdate = TimeSpan.Zero; + } + + protected override void FrameUpdate(FrameEventArgs args) + { + base.FrameUpdate(args); + + if (_timing.CurTime < _nextUpdate) + return; + + _nextUpdate = _timing.CurTime + _updateFrequency; + RefreshObjectList(); } @@ -81,32 +107,72 @@ public sealed partial class ObjectsTab : Control throw new ArgumentOutOfRangeException(nameof(selection), selection, null); } - foreach (var control in _objects) + entities.Sort((a, b) => { - ObjectList.RemoveChild(control); + var valueA = GetComparableValue(a, _headerClicked); + var valueB = GetComparableValue(b, _headerClicked); + return _ascending ? Comparer.Default.Compare(valueA, valueB) : Comparer.Default.Compare(valueB, valueA); + }); + + var listData = new List(); + for (int index = 0; index < entities.Count; index++) + { + var info = entities[index]; + listData.Add(new ObjectsListData(info, $"{info.Name} {info.Entity}", index % 2 == 0 ? _altColor : _defaultColor)); } - _objects.Clear(); - - foreach (var (name, nent) in entities) - { - var ctrl = new ObjectsTabEntry(name, nent); - _objects.Add(ctrl); - ObjectList.AddChild(ctrl); - ctrl.OnKeyBindDown += args => OnEntryKeyBindDown?.Invoke(ctrl, args); - } + SearchList.PopulateList(listData); } - protected override void FrameUpdate(FrameEventArgs args) + private void GenerateButton(ListData data, ListContainerButton button) { - base.FrameUpdate(args); - - if (_timing.CurTime < _nextUpdate) + if (data is not ObjectsListData { Info: var info, BackgroundColor: var backgroundColor }) return; - // I do not care for precision. - _nextUpdate = _timing.CurTime + _updateFrequency; + var entry = new ObjectsTabEntry(info.Name, info.Entity, new StyleBoxFlat { BackgroundColor = backgroundColor }); + button.ToolTip = $"{info.Name}, {info.Entity}"; + // Add key binding event handler + entry.OnKeyBindDown += args => OnEntryKeyBindDown?.Invoke(args, data); + + button.AddChild(entry); + } + + private bool DataFilterCondition(string filter, ListData listData) + { + if (listData is not ObjectsListData { FilteringString: var filteringString }) + return false; + + // If the filter is empty, do not filter out any entries + if (string.IsNullOrEmpty(filter)) + return true; + + return filteringString.Contains(filter, StringComparison.CurrentCultureIgnoreCase); + } + + private object GetComparableValue((string Name, NetEntity Entity) entity, ObjectsTabHeader.Header header) + { + return header switch + { + ObjectsTabHeader.Header.ObjectName => entity.Name, + ObjectsTabHeader.Header.EntityID => entity.Entity.ToString(), + _ => entity.Name + }; + } + + private void HeaderClicked(ObjectsTabHeader.Header header) + { + if (_headerClicked == header) + { + _ascending = !_ascending; + } + else + { + _headerClicked = header; + _ascending = true; + } + + ListHeader.UpdateHeaderSymbols(_headerClicked, _ascending); RefreshObjectList(); } @@ -118,3 +184,4 @@ public sealed partial class ObjectsTab : Control } } +public record ObjectsListData((string Name, NetEntity Entity) Info, string FilteringString, Color BackgroundColor) : ListData; diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml index 0f6975e365..83c4cc5697 100644 --- a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml +++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml @@ -1,6 +1,6 @@ - - + @@ -14,4 +14,4 @@ HorizontalExpand="True" ClipText="True"/> - + diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml.cs b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml.cs index c9b2cd8b57..aab06c6ccd 100644 --- a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml.cs +++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml.cs @@ -1,19 +1,21 @@ using Robust.Client.AutoGenerated; +using Robust.Client.Graphics; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; namespace Content.Client.Administration.UI.Tabs.ObjectsTab; [GenerateTypedNameReferences] -public sealed partial class ObjectsTabEntry : ContainerButton +public sealed partial class ObjectsTabEntry : PanelContainer { public NetEntity AssocEntity; - public ObjectsTabEntry(string name, NetEntity nent) + public ObjectsTabEntry(string name, NetEntity nent, StyleBox styleBox) { RobustXamlLoader.Load(this); AssocEntity = nent; EIDLabel.Text = nent.ToString(); NameLabel.Text = name; + BackgroundColorPanel.PanelOverride = styleBox; } } diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml new file mode 100644 index 0000000000..71a1f5c7bc --- /dev/null +++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml @@ -0,0 +1,21 @@ + + + + + diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml.cs b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml.cs new file mode 100644 index 0000000000..3a91b5b948 --- /dev/null +++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml.cs @@ -0,0 +1,86 @@ +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Input; + +namespace Content.Client.Administration.UI.Tabs.ObjectsTab +{ + [GenerateTypedNameReferences] + public sealed partial class ObjectsTabHeader : Control + { + public event Action
? OnHeaderClicked; + + private const string ArrowUp = "↑"; + private const string ArrowDown = "↓"; + + public ObjectsTabHeader() + { + RobustXamlLoader.Load(this); + + ObjectNameLabel.OnKeyBindDown += ObjectNameClicked; + EntityIDLabel.OnKeyBindDown += EntityIDClicked; + } + + public Label GetHeader(Header header) + { + return header switch + { + Header.ObjectName => ObjectNameLabel, + Header.EntityID => EntityIDLabel, + _ => throw new ArgumentOutOfRangeException(nameof(header), header, null) + }; + } + + public void ResetHeaderText() + { + ObjectNameLabel.Text = Loc.GetString("object-tab-object-name"); + EntityIDLabel.Text = Loc.GetString("object-tab-entity-id"); + } + + public void UpdateHeaderSymbols(Header headerClicked, bool ascending) + { + ResetHeaderText(); + var arrow = ascending ? ArrowUp : ArrowDown; + GetHeader(headerClicked).Text += $" {arrow}"; + } + + private void HeaderClicked(GUIBoundKeyEventArgs args, Header header) + { + if (args.Function != EngineKeyFunctions.UIClick) + { + return; + } + + OnHeaderClicked?.Invoke(header); + args.Handle(); + } + + private void ObjectNameClicked(GUIBoundKeyEventArgs args) + { + HeaderClicked(args, Header.ObjectName); + } + + private void EntityIDClicked(GUIBoundKeyEventArgs args) + { + HeaderClicked(args, Header.EntityID); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + ObjectNameLabel.OnKeyBindDown -= ObjectNameClicked; + EntityIDLabel.OnKeyBindDown -= EntityIDClicked; + } + } + + public enum Header + { + ObjectName, + EntityID + } + } +} diff --git a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs index a8bfaddecf..826945e7cc 100644 --- a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs +++ b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs @@ -53,6 +53,7 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab SearchList.ItemKeyBindDown += (args, data) => OnEntryKeyBindDown?.Invoke(args, data); RefreshPlayerList(_adminSystem.PlayerList); + } #region Antag Overlay @@ -110,7 +111,9 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab _players = players; PlayerCount.Text = $"Players: {_playerMan.PlayerCount}"; - var sortedPlayers = new List(players); + var filteredPlayers = players.Where(info => _showDisconnected || info.Connected).ToList(); + + var sortedPlayers = new List(filteredPlayers); sortedPlayers.Sort(Compare); UpdateHeaderSymbols(); diff --git a/Content.Client/Antag/AntagStatusIconSystem.cs b/Content.Client/Antag/AntagStatusIconSystem.cs deleted file mode 100644 index 804ae21ad4..0000000000 --- a/Content.Client/Antag/AntagStatusIconSystem.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Content.Shared.Antag; -using Content.Shared.Revolutionary.Components; -using Content.Shared.StatusIcon; -using Content.Shared.StatusIcon.Components; -using Content.Shared.Zombies; -using Robust.Client.Player; -using Robust.Shared.Prototypes; - -namespace Content.Client.Antag; - -/// -/// Used for assigning specified icons for antags. -/// -public sealed class AntagStatusIconSystem : SharedStatusIconSystem -{ - [Dependency] private readonly IPrototypeManager _prototype = default!; - [Dependency] private readonly IPlayerManager _player = default!; - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(GetRevIcon); - SubscribeLocalEvent(GetIcon); - SubscribeLocalEvent(GetIcon); - SubscribeLocalEvent(GetIcon); - } - - /// - /// Adds a Status Icon on an entity if the player is supposed to see it. - /// - private void GetIcon(EntityUid uid, T comp, ref GetStatusIconsEvent ev) where T: IAntagStatusIconComponent - { - var ent = _player.LocalSession?.AttachedEntity; - - var canEv = new CanDisplayStatusIconsEvent(ent); - RaiseLocalEvent(uid, ref canEv); - - if (!canEv.Cancelled) - ev.StatusIcons.Add(_prototype.Index(comp.StatusIcon)); - } - - - /// - /// Adds the Rev Icon on an entity if the player is supposed to see it. This additional function is needed to deal - /// with a special case where if someone is a head rev we only want to display the headrev icon. - /// - private void GetRevIcon(EntityUid uid, RevolutionaryComponent comp, ref GetStatusIconsEvent ev) - { - if (HasComp(uid)) - return; - - GetIcon(uid, comp, ref ev); - - } -} diff --git a/Content.Client/Atmos/Overlays/AtmosDebugOverlay.cs b/Content.Client/Atmos/Overlays/AtmosDebugOverlay.cs index 6dfbc326ec..c85dbd2051 100644 --- a/Content.Client/Atmos/Overlays/AtmosDebugOverlay.cs +++ b/Content.Client/Atmos/Overlays/AtmosDebugOverlay.cs @@ -66,7 +66,7 @@ public sealed class AtmosDebugOverlay : Overlay DrawData(msg, handle); } - handle.SetTransform(Matrix3.Identity); + handle.SetTransform(Matrix3x2.Identity); } private void DrawData(DebugMessage msg, diff --git a/Content.Client/Atmos/Overlays/GasTileOverlay.cs b/Content.Client/Atmos/Overlays/GasTileOverlay.cs index f4dc274a4e..17027525e5 100644 --- a/Content.Client/Atmos/Overlays/GasTileOverlay.cs +++ b/Content.Client/Atmos/Overlays/GasTileOverlay.cs @@ -190,7 +190,7 @@ namespace Content.Client.Atmos.Overlays var (_, _, worldMatrix, invMatrix) = gridXform.GetWorldPositionRotationMatrixWithInv(); state.drawHandle.SetTransform(worldMatrix); - var floatBounds = invMatrix.TransformBox(in state.WorldBounds).Enlarged(grid.TileSize); + var floatBounds = invMatrix.TransformBox(state.WorldBounds).Enlarged(grid.TileSize); var localBounds = new Box2i( (int) MathF.Floor(floatBounds.Left), (int) MathF.Floor(floatBounds.Bottom), @@ -249,7 +249,7 @@ namespace Content.Client.Atmos.Overlays }); drawHandle.UseShader(null); - drawHandle.SetTransform(Matrix3.Identity); + drawHandle.SetTransform(Matrix3x2.Identity); } private void DrawMapOverlay( diff --git a/Content.Client/Chasm/ChasmFallingVisualsSystem.cs b/Content.Client/Chasm/ChasmFallingVisualsSystem.cs index 4b04aa9dd7..ddcd509cb3 100644 --- a/Content.Client/Chasm/ChasmFallingVisualsSystem.cs +++ b/Content.Client/Chasm/ChasmFallingVisualsSystem.cs @@ -24,8 +24,11 @@ public sealed class ChasmFallingVisualsSystem : EntitySystem private void OnComponentInit(EntityUid uid, ChasmFallingComponent component, ComponentInit args) { - if (!TryComp(uid, out var sprite)) + if (!TryComp(uid, out var sprite) || + TerminatingOrDeleted(uid)) + { return; + } component.OriginalScale = sprite.Scale; diff --git a/Content.Client/Chat/UI/EmotesMenu.xaml.cs b/Content.Client/Chat/UI/EmotesMenu.xaml.cs index a26d319920..3340755343 100644 --- a/Content.Client/Chat/UI/EmotesMenu.xaml.cs +++ b/Content.Client/Chat/UI/EmotesMenu.xaml.cs @@ -1,7 +1,8 @@ -using System.Numerics; +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; @@ -19,6 +20,7 @@ public sealed partial class EmotesMenu : RadialMenu [Dependency] private readonly ISharedPlayerManager _playerManager = default!; private readonly SpriteSystem _spriteSystem; + private readonly EntityWhitelistSystem _whitelistSystem; public event Action>? OnPlayEmote; @@ -28,6 +30,7 @@ public sealed partial class EmotesMenu : RadialMenu RobustXamlLoader.Load(this); _spriteSystem = _entManager.System(); + _whitelistSystem = _entManager.System(); var main = FindControl("Main"); @@ -37,8 +40,8 @@ public sealed partial class EmotesMenu : RadialMenu var player = _playerManager.LocalSession?.AttachedEntity; if (emote.Category == EmoteCategory.Invalid || emote.ChatTriggers.Count == 0 || - !(player.HasValue && (emote.Whitelist?.IsValid(player.Value, _entManager) ?? true)) || - (emote.Blacklist?.IsValid(player.Value, _entManager) ?? false)) + !(player.HasValue && _whitelistSystem.IsWhitelistPassOrNull(emote.Whitelist, player.Value)) || + _whitelistSystem.IsBlacklistPass(emote.Blacklist, player.Value)) continue; if (!emote.Available && diff --git a/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs b/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs index f1e8e8d7aa..17b88fb5a8 100644 --- a/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs +++ b/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs @@ -3,6 +3,7 @@ using Content.Shared.Chemistry; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reagent; using Content.Shared.Hands; +using Content.Shared.Item; using Content.Shared.Rounding; using Robust.Client.GameObjects; using Robust.Shared.Prototypes; @@ -150,6 +151,9 @@ public sealed class SolutionContainerVisualsSystem : VisualizerSystem(uid, out var item)) + return; + if (!AppearanceSystem.TryGetData(uid, SolutionContainerVisuals.FillFraction, out var fraction, appearance)) return; @@ -159,7 +163,8 @@ public sealed class SolutionContainerVisualsSystem : VisualizerSystem internal sealed class ConstructionMenuPresenter : IDisposable { + [Dependency] private readonly EntityManager _entManager = default!; [Dependency] private readonly IEntitySystemManager _systemManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPlacementManager _placementManager = default!; @@ -30,6 +32,7 @@ namespace Content.Client.Construction.UI [Dependency] private readonly IPlayerManager _playerManager = default!; private readonly IConstructionMenuView _constructionView; + private readonly EntityWhitelistSystem _whitelistSystem; private ConstructionSystem? _constructionSystem; private ConstructionPrototype? _selected; @@ -78,6 +81,7 @@ namespace Content.Client.Construction.UI // This is a lot easier than a factory IoCManager.InjectDependencies(this); _constructionView = new ConstructionMenu(); + _whitelistSystem = _entManager.System(); // This is required so that if we load after the system is initialized, we can bind to it immediately if (_systemManager.TryGetEntitySystem(out var constructionSystem)) @@ -157,7 +161,7 @@ namespace Content.Client.Construction.UI if (_playerManager.LocalSession == null || _playerManager.LocalEntity == null - || (recipe.EntityWhitelist != null && !recipe.EntityWhitelist.IsValid(_playerManager.LocalEntity.Value))) + || _whitelistSystem.IsWhitelistFail(recipe.EntityWhitelist, _playerManager.LocalEntity.Value)) continue; if (!string.IsNullOrEmpty(search)) diff --git a/Content.Client/Decals/Overlays/DecalOverlay.cs b/Content.Client/Decals/Overlays/DecalOverlay.cs index d9904ae80b..0de3301e58 100644 --- a/Content.Client/Decals/Overlays/DecalOverlay.cs +++ b/Content.Client/Decals/Overlays/DecalOverlay.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Shared.Decals; using Robust.Client.GameObjects; using Robust.Client.Graphics; @@ -113,7 +114,7 @@ namespace Content.Client.Decals.Overlays handle.DrawTexture(cache.Texture, decal.Coordinates, angle, decal.Color); } - handle.SetTransform(Matrix3.Identity); + handle.SetTransform(Matrix3x2.Identity); } } } diff --git a/Content.Client/Decals/Overlays/DecalPlacementOverlay.cs b/Content.Client/Decals/Overlays/DecalPlacementOverlay.cs index be277448ed..845bd7c03d 100644 --- a/Content.Client/Decals/Overlays/DecalPlacementOverlay.cs +++ b/Content.Client/Decals/Overlays/DecalPlacementOverlay.cs @@ -51,7 +51,7 @@ public sealed class DecalPlacementOverlay : Overlay var handle = args.WorldHandle; handle.SetTransform(worldMatrix); - var localPos = invMatrix.Transform(mousePos.Position); + var localPos = Vector2.Transform(mousePos.Position, invMatrix); if (snap) { @@ -63,6 +63,6 @@ public sealed class DecalPlacementOverlay : Overlay var box = new Box2Rotated(aabb, rotation, localPos); handle.DrawTextureRect(_sprite.Frame0(decal.Sprite), box, color); - handle.SetTransform(Matrix3.Identity); + handle.SetTransform(Matrix3x2.Identity); } } diff --git a/Content.Client/DeltaV/Overlays/UltraVisionOverlay.cs b/Content.Client/DeltaV/Overlays/UltraVisionOverlay.cs index 64c002b609..d2d4ced71d 100644 --- a/Content.Client/DeltaV/Overlays/UltraVisionOverlay.cs +++ b/Content.Client/DeltaV/Overlays/UltraVisionOverlay.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Robust.Client.Graphics; using Robust.Client.Player; using Robust.Shared.Enums; @@ -43,7 +44,7 @@ public sealed partial class UltraVisionOverlay : Overlay var worldHandle = args.WorldHandle; var viewport = args.WorldBounds; - worldHandle.SetTransform(Matrix3.Identity); + worldHandle.SetTransform(Matrix3x2.Identity); worldHandle.UseShader(_ultraVisionShader); worldHandle.DrawRect(viewport, Color.White); worldHandle.UseShader(null); // important - as of writing, construction overlay breaks without this diff --git a/Content.Client/DeltaV/Shipyard/UI/ShipyardBoundUserInterface.cs b/Content.Client/DeltaV/Shipyard/UI/ShipyardBoundUserInterface.cs index 4a3def491e..a0c72b6ff7 100644 --- a/Content.Client/DeltaV/Shipyard/UI/ShipyardBoundUserInterface.cs +++ b/Content.Client/DeltaV/Shipyard/UI/ShipyardBoundUserInterface.cs @@ -1,5 +1,6 @@ using Content.Shared.Access.Systems; using Content.Shared.Shipyard; +using Content.Shared.Whitelist; using Robust.Client.GameObjects; using Robust.Client.Player; using Robust.Shared.Prototypes; @@ -10,6 +11,7 @@ public sealed class ShipyardConsoleBoundUserInterface : BoundUserInterface { [Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; private readonly AccessReaderSystem _access; @@ -25,7 +27,7 @@ public sealed class ShipyardConsoleBoundUserInterface : BoundUserInterface { base.Open(); - _menu = new ShipyardConsoleMenu(Owner, _proto, EntMan, _player, _access); + _menu = new ShipyardConsoleMenu(Owner, _proto, EntMan, _player, _access, _whitelistSystem); _menu.OpenCentered(); _menu.OnClose += Close; _menu.OnPurchased += Purchase; diff --git a/Content.Client/DeltaV/Shipyard/UI/ShipyardConsoleMenu.xaml.cs b/Content.Client/DeltaV/Shipyard/UI/ShipyardConsoleMenu.xaml.cs index 6821b066ff..3620289b7d 100644 --- a/Content.Client/DeltaV/Shipyard/UI/ShipyardConsoleMenu.xaml.cs +++ b/Content.Client/DeltaV/Shipyard/UI/ShipyardConsoleMenu.xaml.cs @@ -2,6 +2,7 @@ using Content.Client.UserInterface.Controls; using Content.Shared.Access.Systems; using Content.Shared.Shipyard; using Content.Shared.Shipyard.Prototypes; +using Content.Shared.Whitelist; using Robust.Client.AutoGenerated; using Robust.Client.GameObjects; using Robust.Client.Player; @@ -25,7 +26,7 @@ public sealed partial class ShipyardConsoleMenu : FancyWindow public Entity Console; private string? _category; - public ShipyardConsoleMenu(EntityUid console, IPrototypeManager proto, IEntityManager entMan, IPlayerManager player, AccessReaderSystem access) + public ShipyardConsoleMenu(EntityUid console, IPrototypeManager proto, IEntityManager entMan, IPlayerManager player, AccessReaderSystem access, EntityWhitelistSystem whitelist) { RobustXamlLoader.Load(this); IoCManager.InjectDependencies(this); @@ -37,7 +38,7 @@ public sealed partial class ShipyardConsoleMenu : FancyWindow // don't include ships that aren't allowed by whitelist, server won't accept them anyway foreach (var vessel in proto.EnumeratePrototypes()) { - if (vessel.Whitelist?.IsValid(console, entMan) != false) + if(whitelist.IsWhitelistPass(vessel.Whitelist, console)) _vessels.Add(vessel); } _vessels.Sort((x, y) => string.Compare(x.Name, y.Name, StringComparison.CurrentCultureIgnoreCase)); diff --git a/Content.Client/DoAfter/DoAfterOverlay.cs b/Content.Client/DoAfter/DoAfterOverlay.cs index 45981159f0..dfbbf10891 100644 --- a/Content.Client/DoAfter/DoAfterOverlay.cs +++ b/Content.Client/DoAfter/DoAfterOverlay.cs @@ -56,8 +56,8 @@ public sealed class DoAfterOverlay : Overlay // If you use the display UI scale then need to set max(1f, displayscale) because 0 is valid. const float scale = 1f; - var scaleMatrix = Matrix3.CreateScale(new Vector2(scale, scale)); - var rotationMatrix = Matrix3.CreateRotation(-rotation); + var scaleMatrix = Matrix3Helpers.CreateScale(new Vector2(scale, scale)); + var rotationMatrix = Matrix3Helpers.CreateRotation(-rotation); var curTime = _timing.CurTime; @@ -91,9 +91,9 @@ public sealed class DoAfterOverlay : Overlay ? curTime - _meta.GetPauseTime(uid, meta) : curTime; - var worldMatrix = Matrix3.CreateTranslation(worldPosition); - Matrix3.Multiply(scaleMatrix, worldMatrix, out var scaledWorld); - Matrix3.Multiply(rotationMatrix, scaledWorld, out var matty); + var worldMatrix = Matrix3Helpers.CreateTranslation(worldPosition); + var scaledWorld = Matrix3x2.Multiply(scaleMatrix, worldMatrix); + var matty = Matrix3x2.Multiply(rotationMatrix, scaledWorld); handle.SetTransform(matty); var offset = 0f; @@ -151,7 +151,7 @@ public sealed class DoAfterOverlay : Overlay } handle.UseShader(null); - handle.SetTransform(Matrix3.Identity); + handle.SetTransform(Matrix3x2.Identity); } public Color GetProgressColor(float progress, float alpha = 1f) diff --git a/Content.Client/Drugs/RainbowOverlay.cs b/Content.Client/Drugs/RainbowOverlay.cs index e62b0dfa66..fb48c91010 100644 --- a/Content.Client/Drugs/RainbowOverlay.cs +++ b/Content.Client/Drugs/RainbowOverlay.cs @@ -1,7 +1,9 @@ +using Content.Shared.CCVar; using Content.Shared.Drugs; using Content.Shared.StatusEffect; using Robust.Client.Graphics; using Robust.Client.Player; +using Robust.Shared.Configuration; using Robust.Shared.Enums; using Robust.Shared.Prototypes; using Robust.Shared.Timing; @@ -10,6 +12,7 @@ namespace Content.Client.Drugs; public sealed class RainbowOverlay : Overlay { + [Dependency] private readonly IConfigurationManager _config = default!; [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; @@ -75,6 +78,10 @@ public sealed class RainbowOverlay : Overlay protected override void Draw(in OverlayDrawArgs args) { + // TODO disable only the motion part or ike's idea (single static frame of the overlay) + if (_config.GetCVar(CCVars.ReducedMotion)) + return; + if (ScreenTexture == null) return; diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs index 4446256daf..7b07a9ac1e 100644 --- a/Content.Client/Entry/EntryPoint.cs +++ b/Content.Client/Entry/EntryPoint.cs @@ -153,7 +153,6 @@ namespace Content.Client.Entry _parallaxManager.LoadDefaultParallax(); _overlayManager.AddOverlay(new SingularityOverlay()); - _overlayManager.AddOverlay(new FlashOverlay()); _overlayManager.AddOverlay(new RadiationPulseOverlay()); _chatManager.Initialize(); _clientPreferencesManager.Initialize(); diff --git a/Content.Client/Explosion/ExplosionOverlay.cs b/Content.Client/Explosion/ExplosionOverlay.cs index 2d8c15f1b9..8cf7447a5d 100644 --- a/Content.Client/Explosion/ExplosionOverlay.cs +++ b/Content.Client/Explosion/ExplosionOverlay.cs @@ -48,7 +48,7 @@ public sealed class ExplosionOverlay : Overlay DrawExplosion(drawHandle, args.WorldBounds, visuals, index, xforms, textures); } - drawHandle.SetTransform(Matrix3.Identity); + drawHandle.SetTransform(Matrix3x2.Identity); drawHandle.UseShader(null); } @@ -78,7 +78,8 @@ public sealed class ExplosionOverlay : Overlay if (visuals.SpaceTiles == null) return; - gridBounds = Matrix3.Invert(visuals.SpaceMatrix).TransformBox(worldBounds).Enlarged(2); + Matrix3x2.Invert(visuals.SpaceMatrix, out var invSpace); + gridBounds = invSpace.TransformBox(worldBounds).Enlarged(2); drawHandle.SetTransform(visuals.SpaceMatrix); DrawTiles(drawHandle, gridBounds, index, visuals.SpaceTiles, visuals, visuals.SpaceTileSize, textures); diff --git a/Content.Client/Flash/FlashOverlay.cs b/Content.Client/Flash/FlashOverlay.cs index fe9c888227..9ea00275e8 100644 --- a/Content.Client/Flash/FlashOverlay.cs +++ b/Content.Client/Flash/FlashOverlay.cs @@ -1,12 +1,11 @@ -using System.Numerics; +using Content.Shared.Flash; +using Content.Shared.Flash.Components; +using Content.Shared.StatusEffect; using Content.Client.Viewport; using Robust.Client.Graphics; using Robust.Client.State; using Robust.Client.Player; using Robust.Shared.Enums; -using Robust.Shared.Graphics; -using Robust.Shared.IoC; -using Robust.Shared.Maths; using Robust.Shared.Prototypes; using Robust.Shared.Timing; using SixLabors.ImageSharp.PixelFormats; @@ -17,66 +16,87 @@ namespace Content.Client.Flash { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IClyde _displayManager = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IStateManager _stateManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IGameTiming _timing = default!; + + private readonly StatusEffectsSystem _statusSys; public override OverlaySpace Space => OverlaySpace.WorldSpace; private readonly ShaderInstance _shader; - private double _startTime = -1; - private double _lastsFor = 1; - private Texture? _screenshotTexture; + public float PercentComplete = 0.0f; + public Texture? ScreenshotTexture; public FlashOverlay() { IoCManager.InjectDependencies(this); - _shader = _prototypeManager.Index("FlashedEffect").Instance().Duplicate(); + _shader = _prototypeManager.Index("FlashedEffect").InstanceUnique(); + _statusSys = _entityManager.System(); } - public void ReceiveFlash(double duration) + protected override void FrameUpdate(FrameEventArgs args) + { + var playerEntity = _playerManager.LocalEntity; + + if (playerEntity == null) + return; + + if (!_entityManager.HasComponent(playerEntity) + || !_entityManager.TryGetComponent(playerEntity, out var status)) + return; + + if (!_statusSys.TryGetTime(playerEntity.Value, SharedFlashSystem.FlashedKey, out var time, status)) + return; + + var curTime = _timing.CurTime; + var lastsFor = (float) (time.Value.Item2 - time.Value.Item1).TotalSeconds; + var timeDone = (float) (curTime - time.Value.Item1).TotalSeconds; + + PercentComplete = timeDone / lastsFor; + } + + public void ReceiveFlash() { if (_stateManager.CurrentState is IMainViewportState state) { + // take a screenshot + // note that the callback takes a while and ScreenshotTexture will be null the first few Draws state.Viewport.Viewport.Screenshot(image => { var rgba32Image = image.CloneAs(SixLabors.ImageSharp.Configuration.Default); - _screenshotTexture = _displayManager.LoadTextureFromImage(rgba32Image); + ScreenshotTexture = _displayManager.LoadTextureFromImage(rgba32Image); }); } + } - _startTime = _gameTiming.CurTime.TotalSeconds; - _lastsFor = duration; + protected override bool BeforeDraw(in OverlayDrawArgs args) + { + if (!_entityManager.TryGetComponent(_playerManager.LocalEntity, out EyeComponent? eyeComp)) + return false; + if (args.Viewport.Eye != eyeComp.Eye) + return false; + + return PercentComplete < 1.0f; } protected override void Draw(in OverlayDrawArgs args) { - if (!_entityManager.TryGetComponent(_playerManager.LocalEntity, out EyeComponent? eyeComp)) - return; - - if (args.Viewport.Eye != eyeComp.Eye) - return; - - var percentComplete = (float) ((_gameTiming.CurTime.TotalSeconds - _startTime) / _lastsFor); - if (percentComplete >= 1.0f) + if (ScreenshotTexture == null) return; var worldHandle = args.WorldHandle; + _shader.SetParameter("percentComplete", PercentComplete); worldHandle.UseShader(_shader); - _shader.SetParameter("percentComplete", percentComplete); - - if (_screenshotTexture != null) - { - worldHandle.DrawTextureRectRegion(_screenshotTexture, args.WorldBounds); - } - + worldHandle.DrawTextureRectRegion(ScreenshotTexture, args.WorldBounds); worldHandle.UseShader(null); } protected override void DisposeBehavior() { base.DisposeBehavior(); - _screenshotTexture = null; + ScreenshotTexture = null; + PercentComplete = 1.0f; } } } diff --git a/Content.Client/Flash/FlashSystem.cs b/Content.Client/Flash/FlashSystem.cs index ad8f8b0b82..9a0579f6aa 100644 --- a/Content.Client/Flash/FlashSystem.cs +++ b/Content.Client/Flash/FlashSystem.cs @@ -1,62 +1,67 @@ using Content.Shared.Flash; +using Content.Shared.Flash.Components; +using Content.Shared.StatusEffect; using Robust.Client.Graphics; using Robust.Client.Player; -using Robust.Shared.GameStates; -using Robust.Shared.Timing; +using Robust.Shared.Player; -namespace Content.Client.Flash +namespace Content.Client.Flash; + +public sealed class FlashSystem : SharedFlashSystem { - public sealed class FlashSystem : SharedFlashSystem + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IOverlayManager _overlayMan = default!; + + private FlashOverlay _overlay = default!; + + public override void Initialize() { - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly IPlayerManager _playerManager = default!; - [Dependency] private readonly IOverlayManager _overlayManager = default!; + base.Initialize(); - public override void Initialize() + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); + SubscribeLocalEvent(OnStatusAdded); + + _overlay = new(); + } + + private void OnPlayerAttached(EntityUid uid, FlashedComponent component, LocalPlayerAttachedEvent args) + { + _overlayMan.AddOverlay(_overlay); + } + + private void OnPlayerDetached(EntityUid uid, FlashedComponent component, LocalPlayerDetachedEvent args) + { + _overlay.PercentComplete = 1.0f; + _overlay.ScreenshotTexture = null; + _overlayMan.RemoveOverlay(_overlay); + } + + private void OnInit(EntityUid uid, FlashedComponent component, ComponentInit args) + { + if (_player.LocalEntity == uid) { - base.Initialize(); - - SubscribeLocalEvent(OnFlashableHandleState); + _overlayMan.AddOverlay(_overlay); } + } - private void OnFlashableHandleState(EntityUid uid, FlashableComponent component, ref ComponentHandleState args) + private void OnShutdown(EntityUid uid, FlashedComponent component, ComponentShutdown args) + { + if (_player.LocalEntity == uid) { - if (args.Current is not FlashableComponentState state) - return; + _overlay.PercentComplete = 1.0f; + _overlay.ScreenshotTexture = null; + _overlayMan.RemoveOverlay(_overlay); + } + } - // Yes, this code is awful. I'm just porting it to an entity system so don't blame me. - if (_playerManager.LocalEntity != uid) - { - return; - } - - if (state.Time == default) - { - return; - } - - // Few things here: - // 1. If a shorter duration flash is applied then don't do anything - // 2. If the client-side time is later than when the flash should've ended don't do anything - var currentTime = _gameTiming.CurTime.TotalSeconds; - var newEndTime = state.Time.TotalSeconds + state.Duration; - var currentEndTime = component.LastFlash.TotalSeconds + component.Duration; - - if (currentEndTime > newEndTime) - { - return; - } - - if (currentTime > newEndTime) - { - return; - } - - component.LastFlash = state.Time; - component.Duration = state.Duration; - - var overlay = _overlayManager.GetOverlay(); - overlay.ReceiveFlash(component.Duration); + private void OnStatusAdded(EntityUid uid, FlashedComponent component, StatusEffectAddedEvent args) + { + if (_player.LocalEntity == uid && args.Key == FlashedKey) + { + _overlay.ReceiveFlash(); } } } diff --git a/Content.Client/Fluids/PuddleOverlay.cs b/Content.Client/Fluids/PuddleOverlay.cs index ac6661cfdd..a8c1d35510 100644 --- a/Content.Client/Fluids/PuddleOverlay.cs +++ b/Content.Client/Fluids/PuddleOverlay.cs @@ -1,4 +1,5 @@ -using Content.Shared.FixedPoint; +using System.Numerics; +using Content.Shared.FixedPoint; using Robust.Client.Graphics; using Robust.Client.ResourceManagement; using Robust.Shared.Enums; @@ -73,7 +74,7 @@ public sealed class PuddleOverlay : Overlay } } - drawHandle.SetTransform(Matrix3.Identity); + drawHandle.SetTransform(Matrix3x2.Identity); } private void DrawScreen(in OverlayDrawArgs args) @@ -99,7 +100,7 @@ public sealed class PuddleOverlay : Overlay if (!gridBounds.Contains(centre)) continue; - var screenCenter = _eyeManager.WorldToScreen(matrix.Transform(centre)); + var screenCenter = _eyeManager.WorldToScreen(Vector2.Transform(centre, matrix)); drawHandle.DrawString(_font, screenCenter, debugOverlayData.CurrentVolume.ToString(), Color.White); } diff --git a/Content.Client/GameTicking/Managers/ClientGameTicker.cs b/Content.Client/GameTicking/Managers/ClientGameTicker.cs index 309db2eb4e..fcf5ae91a4 100644 --- a/Content.Client/GameTicking/Managers/ClientGameTicker.cs +++ b/Content.Client/GameTicking/Managers/ClientGameTicker.cs @@ -4,10 +4,12 @@ using Content.Client.Lobby; using Content.Client.RoundEnd; using Content.Shared.GameTicking; using Content.Shared.GameWindow; +using Content.Shared.Roles; using JetBrains.Annotations; using Robust.Client.Graphics; using Robust.Client.State; using Robust.Client.UserInterface; +using Robust.Shared.Prototypes; namespace Content.Client.GameTicking.Managers { @@ -17,10 +19,9 @@ namespace Content.Client.GameTicking.Managers [Dependency] private readonly IStateManager _stateManager = default!; [Dependency] private readonly IClientAdminManager _admin = default!; [Dependency] private readonly IClyde _clyde = default!; - [Dependency] private readonly SharedMapSystem _map = default!; [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; - private Dictionary> _jobsAvailable = new(); + private Dictionary, int?>> _jobsAvailable = new(); private Dictionary _stationNames = new(); [ViewVariables] public bool AreWeReady { get; private set; } @@ -32,13 +33,13 @@ namespace Content.Client.GameTicking.Managers [ViewVariables] public TimeSpan StartTime { get; private set; } [ViewVariables] public new bool Paused { get; private set; } - [ViewVariables] public IReadOnlyDictionary> JobsAvailable => _jobsAvailable; + [ViewVariables] public IReadOnlyDictionary, int?>> JobsAvailable => _jobsAvailable; [ViewVariables] public IReadOnlyDictionary StationNames => _stationNames; public event Action? InfoBlobUpdated; public event Action? LobbyStatusUpdated; public event Action? LobbyLateJoinStatusUpdated; - public event Action>>? LobbyJobsAvailableUpdated; + public event Action, int?>>>? LobbyJobsAvailableUpdated; public override void Initialize() { @@ -69,7 +70,7 @@ namespace Content.Client.GameTicking.Managers // reading the console. E.g., logs like this one could leak the nuke station/grid: // > Grid NT-Arrivals 1101 (122/n25896) changed parent. Old parent: map 10 (121/n25895). New parent: FTL (123/n26470) #if !DEBUG - _map.Log.Level = _admin.IsAdmin() ? LogLevel.Info : LogLevel.Warning; + EntityManager.System().Log.Level = _admin.IsAdmin() ? LogLevel.Info : LogLevel.Warning; #endif } diff --git a/Content.Client/Guidebook/Richtext/Box.cs b/Content.Client/Guidebook/Richtext/Box.cs index ecf6cb21f7..6e18ad9c57 100644 --- a/Content.Client/Guidebook/Richtext/Box.cs +++ b/Content.Client/Guidebook/Richtext/Box.cs @@ -11,6 +11,9 @@ public sealed class Box : BoxContainer, IDocumentTag HorizontalExpand = true; control = this; + if (args.TryGetValue("Margin", out var margin)) + Margin = new Thickness(float.Parse(margin)); + if (args.TryGetValue("Orientation", out var orientation)) Orientation = Enum.Parse(orientation); else diff --git a/Content.Client/Guidebook/Richtext/ColorBox.cs b/Content.Client/Guidebook/Richtext/ColorBox.cs new file mode 100644 index 0000000000..84de300d6e --- /dev/null +++ b/Content.Client/Guidebook/Richtext/ColorBox.cs @@ -0,0 +1,49 @@ +using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; +using Robust.Client.Graphics; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; + +namespace Content.Client.Guidebook.Richtext; + +[UsedImplicitly] +public sealed class ColorBox : PanelContainer, IDocumentTag +{ + public bool TryParseTag(Dictionary args, [NotNullWhen(true)] out Control? control) + { + HorizontalExpand = true; + VerticalExpand = true; + control = this; + + if (args.TryGetValue("Margin", out var margin)) + Margin = new Thickness(float.Parse(margin)); + + if (args.TryGetValue("HorizontalAlignment", out var halign)) + HorizontalAlignment = Enum.Parse(halign); + else + HorizontalAlignment = HAlignment.Stretch; + + if (args.TryGetValue("VerticalAlignment", out var valign)) + VerticalAlignment = Enum.Parse(valign); + else + VerticalAlignment = VAlignment.Stretch; + + var styleBox = new StyleBoxFlat(); + if (args.TryGetValue("Color", out var color)) + styleBox.BackgroundColor = Color.FromHex(color); + + if (args.TryGetValue("OutlineThickness", out var outlineThickness)) + styleBox.BorderThickness = new Thickness(float.Parse(outlineThickness)); + else + styleBox.BorderThickness = new Thickness(1); + + if (args.TryGetValue("OutlineColor", out var outlineColor)) + styleBox.BorderColor = Color.FromHex(outlineColor); + else + styleBox.BorderColor = Color.White; + + PanelOverride = styleBox; + + return true; + } +} diff --git a/Content.Client/Guidebook/Richtext/Table.cs b/Content.Client/Guidebook/Richtext/Table.cs new file mode 100644 index 0000000000..b6923c3698 --- /dev/null +++ b/Content.Client/Guidebook/Richtext/Table.cs @@ -0,0 +1,27 @@ +using System.Diagnostics.CodeAnalysis; +using Content.Client.UserInterface.Controls; +using JetBrains.Annotations; +using Robust.Client.UserInterface; + +namespace Content.Client.Guidebook.Richtext; + +[UsedImplicitly] +public sealed class Table : TableContainer, IDocumentTag +{ + public bool TryParseTag(Dictionary args, [NotNullWhen(true)] out Control? control) + { + HorizontalExpand = true; + control = this; + + if (!args.TryGetValue("Columns", out var columns) || !int.TryParse(columns, out var columnsCount)) + { + Logger.Error("Guidebook tag \"Table\" does not specify required property \"Columns.\""); + control = null; + return false; + } + + Columns = columnsCount; + + return true; + } +} diff --git a/Content.Client/LateJoin/LateJoinGui.cs b/Content.Client/LateJoin/LateJoinGui.cs index ba9351d674..62a06629f2 100644 --- a/Content.Client/LateJoin/LateJoinGui.cs +++ b/Content.Client/LateJoin/LateJoinGui.cs @@ -244,7 +244,7 @@ namespace Content.Client.LateJoin VerticalAlignment = VAlignment.Center }; - var jobIcon = _prototypeManager.Index(prototype.Icon); + var jobIcon = _prototypeManager.Index(prototype.Icon); icon.Texture = _sprites.Frame0(jobIcon.Icon); jobSelector.AddChild(icon); @@ -290,7 +290,7 @@ namespace Content.Client.LateJoin } } - private void JobsAvailableUpdated(IReadOnlyDictionary> updatedJobs) + private void JobsAvailableUpdated(IReadOnlyDictionary, int?>> updatedJobs) { foreach (var stationEntries in updatedJobs) { @@ -337,10 +337,10 @@ namespace Content.Client.LateJoin public Label JobLabel { get; } public string JobId { get; } public string JobLocalisedName { get; } - public uint? Amount { get; private set; } + public int? Amount { get; private set; } private bool _initialised = false; - public JobButton(Label jobLabel, string jobId, string jobLocalisedName, uint? amount) + public JobButton(Label jobLabel, ProtoId jobId, string jobLocalisedName, int? amount) { JobLabel = jobLabel; JobId = jobId; @@ -350,7 +350,7 @@ namespace Content.Client.LateJoin _initialised = true; } - public void RefreshLabel(uint? amount) + public void RefreshLabel(int? amount) { if (Amount == amount && _initialised) { diff --git a/Content.Client/Light/RgbLightControllerSystem.cs b/Content.Client/Light/RgbLightControllerSystem.cs index 7d55bcebf1..85b6114830 100644 --- a/Content.Client/Light/RgbLightControllerSystem.cs +++ b/Content.Client/Light/RgbLightControllerSystem.cs @@ -207,8 +207,11 @@ namespace Content.Client.Light public static Color GetCurrentRgbColor(TimeSpan curTime, TimeSpan offset, Entity rgb) { + var delta = (float)(curTime - offset).TotalSeconds; + var entOffset = Math.Abs(rgb.Owner.Id * 0.09817f); + var hue = (delta * rgb.Comp.CycleRate + entOffset) % 1; return Color.FromHsv(new Vector4( - (float) (((curTime.TotalSeconds - offset.TotalSeconds) * rgb.Comp.CycleRate + Math.Abs(rgb.Owner.Id * 0.1)) % 1), + MathF.Abs(hue), 1.0f, 1.0f, 1.0f diff --git a/Content.Client/Lobby/LobbyUIController.cs b/Content.Client/Lobby/LobbyUIController.cs index f6a3eed962..05b98606ab 100644 --- a/Content.Client/Lobby/LobbyUIController.cs +++ b/Content.Client/Lobby/LobbyUIController.cs @@ -302,7 +302,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered p.Value == JobPriority.High).Key; // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract (what is resharper smoking?) - return _prototypeManager.Index(highPriorityJob ?? SharedGameTicker.FallbackOverflowJob); + return _prototypeManager.Index(highPriorityJob.Id ?? SharedGameTicker.FallbackOverflowJob); } public void GiveDummyLoadout(EntityUid uid, RoleLoadout? roleLoadout) diff --git a/Content.Client/Lobby/UI/CharacterPickerButton.xaml.cs b/Content.Client/Lobby/UI/CharacterPickerButton.xaml.cs index 2ad8de7445..7efd1c594f 100644 --- a/Content.Client/Lobby/UI/CharacterPickerButton.xaml.cs +++ b/Content.Client/Lobby/UI/CharacterPickerButton.xaml.cs @@ -53,9 +53,9 @@ public sealed partial class CharacterPickerButton : ContainerButton .LoadProfileEntity(humanoid, null, true); var highPriorityJob = humanoid.JobPriorities.SingleOrDefault(p => p.Value == JobPriority.High).Key; - if (highPriorityJob != null) + if (highPriorityJob != default) { - var jobName = prototypeManager.Index(highPriorityJob).LocalizedName; + var jobName = prototypeManager.Index(highPriorityJob).LocalizedName; description = $"{description}\n{jobName}"; } } diff --git a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs index 9da9ca080b..ec4701dbe3 100644 --- a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs +++ b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs @@ -7,6 +7,7 @@ using Content.Client.Lobby.UI.Loadouts; using Content.Client.Lobby.UI.Roles; using Content.Client.Message; using Content.Client.Players.PlayTimeTracking; +using Content.Client.Stylesheets; using Content.Client.UserInterface.Systems.Guidebook; using Content.Shared.CCVar; using Content.Shared.Clothing; @@ -466,38 +467,96 @@ namespace Content.Client.Lobby.UI var traits = _prototypeManager.EnumeratePrototypes().OrderBy(t => Loc.GetString(t.Name)).ToList(); TabContainer.SetTabTitle(3, Loc.GetString("humanoid-profile-editor-traits-tab")); - if (traits.Count > 0) - { - foreach (var trait in traits) - { - var selector = new TraitPreferenceSelector(trait); - - if (Profile?.TraitPreferences.Contains(trait.ID) == true) - { - selector.Preference = true; - } - else - { - selector.Preference = false; - } - - selector.PreferenceChanged += preference => - { - Profile = Profile?.WithTraitPreference(trait.ID, preference); - SetDirty(); - }; - - TraitsList.AddChild(selector); - } - } - else + if (traits.Count < 1) { TraitsList.AddChild(new Label { - // TODO: Localise - Text = "No traits available :(", + Text = Loc.GetString("humanoid-profile-editor-no-traits"), FontColorOverride = Color.Gray, }); + return; + } + + //Setup model + Dictionary> model = new(); + List defaultTraits = new(); + model.Add("default", defaultTraits); + + foreach (var trait in traits) + { + if (trait.Category == null) + { + defaultTraits.Add(trait.ID); + continue; + } + + if (!model.ContainsKey(trait.Category)) + { + model.Add(trait.Category, new()); + } + model[trait.Category].Add(trait.ID); + } + + //Create UI view from model + foreach (var (categoryId, traitId) in model) + { + TraitCategoryPrototype? category = null; + if (categoryId != "default") + { + category = _prototypeManager.Index(categoryId); + // Label + TraitsList.AddChild(new Label + { + Text = Loc.GetString(category.Name), + Margin = new Thickness(0, 10, 0, 0), + StyleClasses = { StyleBase.StyleClassLabelHeading }, + }); + } + + List selectors = new(); + var selectionCount = 0; + + foreach (var traitProto in traitId) + { + var trait = _prototypeManager.Index(traitProto); + var selector = new TraitPreferenceSelector(trait); + + selector.Preference = Profile?.TraitPreferences.Contains(trait.ID) == true; + if (selector.Preference) + selectionCount += trait.Cost; + + selector.PreferenceChanged += preference => + { + Profile = Profile?.WithTraitPreference(trait.ID, categoryId, preference); + SetDirty(); + RefreshTraits(); // If too many traits are selected, they will be reset to the real value. + }; + selectors.Add(selector); + } + + // Selection counter + if (category is { MaxTraitPoints: >= 0 }) + { + TraitsList.AddChild(new Label + { + Text = Loc.GetString("humanoid-profile-editor-trait-count-hint", ("current", selectionCount) ,("max", category.MaxTraitPoints)), + FontColorOverride = Color.Gray + }); + } + + foreach (var selector in selectors) + { + if (selector == null) + continue; + + if (category is { MaxTraitPoints: >= 0 } && + selector.Cost + selectionCount > category.MaxTraitPoints) + { + selector.Checkbox.Label.FontColorOverride = Color.Red; + } + + TraitsList.AddChild(selector); + } } } @@ -562,7 +621,8 @@ namespace Content.Client.Lobby.UI selector.Setup(items, title, 250, description); selector.Select(Profile?.AntagPreferences.Contains(antag.ID) == true ? 0 : 1); - if (!_requirements.CheckRoleTime(antag.Requirements, out var reason)) + var requirements = _entManager.System().GetAntagRequirement(antag); + if (!_requirements.CheckRoleTime(requirements, out var reason)) { selector.LockRequirements(reason); Profile = Profile?.WithAntagPreference(antag.ID, false); @@ -719,8 +779,17 @@ namespace Content.Client.Lobby.UI _jobPriorities.Clear(); var firstCategory = true; - var departments = _prototypeManager.EnumeratePrototypes().ToArray(); - Array.Sort(departments, DepartmentUIComparer.Instance); + // Get all displayed departments + var departments = new List(); + foreach (var department in _prototypeManager.EnumeratePrototypes()) + { + if (department.EditorHidden) + continue; + + departments.Add(department); + } + + departments.Sort(DepartmentUIComparer.Instance); var items = new[] { @@ -774,7 +843,7 @@ namespace Content.Client.Lobby.UI JobList.AddChild(category); } - var jobs = department.Roles.Select(jobId => _prototypeManager.Index(jobId)) + var jobs = department.Roles.Select(jobId => _prototypeManager.Index(jobId)) .Where(job => job.SetPreference) .ToArray(); @@ -797,7 +866,7 @@ namespace Content.Client.Lobby.UI TextureScale = new Vector2(2, 2), VerticalAlignment = VAlignment.Center }; - var jobIcon = _prototypeManager.Index(job.Icon); + var jobIcon = _prototypeManager.Index(job.Icon); icon.Texture = jobIcon.Icon.Frame0(); selector.Setup(items, job.LocalizedName, 200, job.LocalizedDescription, icon); @@ -821,13 +890,15 @@ namespace Content.Client.Lobby.UI if (jobId == job.ID) { other.Select(selectedPrio); + continue; } - else if (selectedJobPrio == JobPriority.High && (JobPriority) other.Selected == JobPriority.High) - { - // Lower any other high priorities to medium. - other.Select((int) JobPriority.Medium); - Profile = Profile?.WithJobPriority(jobId, JobPriority.Medium); - } + + if (selectedJobPrio != JobPriority.High || (JobPriority) other.Selected != JobPriority.High) + continue; + + // Lower any other high priorities to medium. + other.Select((int)JobPriority.Medium); + Profile = Profile?.WithJobPriority(jobId, JobPriority.Medium); } // TODO: Only reload on high change (either to or from). @@ -932,6 +1003,11 @@ namespace Content.Client.Lobby.UI SetDirty(); ReloadPreview(); }; + + if (Profile is null) + return; + + UpdateJobPriorities(); } private void OnFlavorTextChange(string content) diff --git a/Content.Client/Lobby/UI/Roles/TraitPreferenceSelector.xaml b/Content.Client/Lobby/UI/Roles/TraitPreferenceSelector.xaml index 18dabe3090..266b4b8eee 100644 --- a/Content.Client/Lobby/UI/Roles/TraitPreferenceSelector.xaml +++ b/Content.Client/Lobby/UI/Roles/TraitPreferenceSelector.xaml @@ -2,6 +2,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> - + diff --git a/Content.Client/Lobby/UI/Roles/TraitPreferenceSelector.xaml.cs b/Content.Client/Lobby/UI/Roles/TraitPreferenceSelector.xaml.cs index 498a5ca4e5..a52a3fa2db 100644 --- a/Content.Client/Lobby/UI/Roles/TraitPreferenceSelector.xaml.cs +++ b/Content.Client/Lobby/UI/Roles/TraitPreferenceSelector.xaml.cs @@ -9,6 +9,8 @@ namespace Content.Client.Lobby.UI.Roles; [GenerateTypedNameReferences] public sealed partial class TraitPreferenceSelector : Control { + public int Cost; + public bool Preference { get => Checkbox.Pressed; @@ -20,7 +22,12 @@ public sealed partial class TraitPreferenceSelector : Control public TraitPreferenceSelector(TraitPrototype trait) { RobustXamlLoader.Load(this); - Checkbox.Text = Loc.GetString(trait.Name); + + var text = trait.Cost != 0 ? $"[{trait.Cost}] " : ""; + text += Loc.GetString(trait.Name); + + Cost = trait.Cost; + Checkbox.Text = text; Checkbox.OnToggled += OnCheckBoxToggled; if (trait.Description is { } desc) diff --git a/Content.Client/Maps/GridDraggingSystem.cs b/Content.Client/Maps/GridDraggingSystem.cs index e82786847e..5e9250f0ce 100644 --- a/Content.Client/Maps/GridDraggingSystem.cs +++ b/Content.Client/Maps/GridDraggingSystem.cs @@ -101,7 +101,7 @@ public sealed class GridDraggingSystem : SharedGridDraggingSystem if (!_mapManager.TryFindGridAt(mousePos, out var gridUid, out var grid)) return; - StartDragging(gridUid, Transform(gridUid).InvWorldMatrix.Transform(mousePos.Position)); + StartDragging(gridUid, Vector2.Transform(mousePos.Position, Transform(gridUid).InvWorldMatrix)); } if (!TryComp(_dragging, out TransformComponent? xform)) @@ -116,7 +116,7 @@ public sealed class GridDraggingSystem : SharedGridDraggingSystem return; } - var localToWorld = xform.WorldMatrix.Transform(_localPosition); + var localToWorld = Vector2.Transform(_localPosition, xform.WorldMatrix); if (localToWorld.EqualsApprox(mousePos.Position, 0.01f)) return; diff --git a/Content.Client/Movement/Systems/WaddleAnimationSystem.cs b/Content.Client/Movement/Systems/WaddleAnimationSystem.cs index 9555c1f6b9..0ed2d04f69 100644 --- a/Content.Client/Movement/Systems/WaddleAnimationSystem.cs +++ b/Content.Client/Movement/Systems/WaddleAnimationSystem.cs @@ -2,94 +2,115 @@ using Content.Client.Buckle; using Content.Client.Gravity; using Content.Shared.ActionBlocker; -using Content.Shared.Buckle.Components; using Content.Shared.Mobs.Systems; using Content.Shared.Movement.Components; -using Content.Shared.Movement.Events; -using Content.Shared.StatusEffect; -using Content.Shared.Stunnable; +using Content.Shared.Movement.Systems; using Robust.Client.Animations; using Robust.Client.GameObjects; using Robust.Shared.Animations; -using Robust.Shared.Timing; namespace Content.Client.Movement.Systems; -public sealed class WaddleAnimationSystem : EntitySystem +public sealed class WaddleAnimationSystem : SharedWaddleAnimationSystem { [Dependency] private readonly AnimationPlayerSystem _animation = default!; [Dependency] private readonly GravitySystem _gravity = default!; - [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; [Dependency] private readonly BuckleSystem _buckle = default!; [Dependency] private readonly MobStateSystem _mobState = default!; public override void Initialize() { - SubscribeLocalEvent(OnMovementInput); - SubscribeLocalEvent(OnStartedWalking); - SubscribeLocalEvent(OnStoppedWalking); + base.Initialize(); + + SubscribeAllEvent(OnStartWaddling); SubscribeLocalEvent(OnAnimationCompleted); - SubscribeLocalEvent(OnStunned); - SubscribeLocalEvent(OnKnockedDown); - SubscribeLocalEvent(OnBuckleChange); + SubscribeAllEvent(OnStopWaddling); } - private void OnMovementInput(EntityUid entity, WaddleAnimationComponent component, MoveInputEvent args) + private void OnStartWaddling(StartedWaddlingEvent msg, EntitySessionEventArgs args) { - // Prediction mitigation. Prediction means that MoveInputEvents are spammed repeatedly, even though you'd assume - // they're once-only for the user actually doing something. As such do nothing if we're just repeating this FoR. - if (!_timing.IsFirstTimePredicted) - { - return; - } - - if (!args.HasDirectionalMovement && component.IsCurrentlyWaddling) - { - var stopped = new StoppedWaddlingEvent(entity); - - RaiseLocalEvent(entity, ref stopped); - - return; - } - - // Only start waddling if we're not currently AND we're actually moving. - if (component.IsCurrentlyWaddling || !args.HasDirectionalMovement) - return; - - var started = new StartedWaddlingEvent(entity); - - RaiseLocalEvent(entity, ref started); + if (TryComp(GetEntity(msg.Entity), out var comp)) + StartWaddling((GetEntity(msg.Entity), comp)); } - private void OnStartedWalking(EntityUid uid, WaddleAnimationComponent component, StartedWaddlingEvent args) + private void OnStopWaddling(StoppedWaddlingEvent msg, EntitySessionEventArgs args) { - if (_animation.HasRunningAnimation(uid, component.KeyName)) + if (TryComp(GetEntity(msg.Entity), out var comp)) + StopWaddling((GetEntity(msg.Entity), comp)); + } + + private void StartWaddling(Entity entity) + { + if (_animation.HasRunningAnimation(entity.Owner, entity.Comp.KeyName)) return; - if (!TryComp(uid, out var mover)) + if (!TryComp(entity.Owner, out var mover)) return; - if (_gravity.IsWeightless(uid)) + if (_gravity.IsWeightless(entity.Owner)) return; - - if (!_actionBlocker.CanMove(uid, mover)) + if (!_actionBlocker.CanMove(entity.Owner, mover)) return; // Do nothing if buckled in - if (_buckle.IsBuckled(uid)) + if (_buckle.IsBuckled(entity.Owner)) return; // Do nothing if crit or dead (for obvious reasons) - if (_mobState.IsIncapacitated(uid)) + if (_mobState.IsIncapacitated(entity.Owner)) return; - var tumbleIntensity = component.LastStep ? 360 - component.TumbleIntensity : component.TumbleIntensity; - var len = mover.Sprinting ? component.AnimationLength * component.RunAnimationLengthMultiplier : component.AnimationLength; + PlayWaddleAnimationUsing( + (entity.Owner, entity.Comp), + CalculateAnimationLength(entity.Comp, mover), + CalculateTumbleIntensity(entity.Comp) + ); + } - component.LastStep = !component.LastStep; - component.IsCurrentlyWaddling = true; + private static float CalculateTumbleIntensity(WaddleAnimationComponent component) + { + return component.LastStep ? 360 - component.TumbleIntensity : component.TumbleIntensity; + } + + private static float CalculateAnimationLength(WaddleAnimationComponent component, InputMoverComponent mover) + { + return mover.Sprinting ? component.AnimationLength * component.RunAnimationLengthMultiplier : component.AnimationLength; + } + + private void OnAnimationCompleted(Entity entity, ref AnimationCompletedEvent args) + { + if (args.Key != entity.Comp.KeyName) + return; + + if (!TryComp(entity.Owner, out var mover)) + return; + + PlayWaddleAnimationUsing( + (entity.Owner, entity.Comp), + CalculateAnimationLength(entity.Comp, mover), + CalculateTumbleIntensity(entity.Comp) + ); + } + + private void StopWaddling(Entity entity) + { + if (!_animation.HasRunningAnimation(entity.Owner, entity.Comp.KeyName)) + return; + + _animation.Stop(entity.Owner, entity.Comp.KeyName); + + if (!TryComp(entity.Owner, out var sprite)) + return; + + sprite.Offset = new Vector2(); + sprite.Rotation = Angle.FromDegrees(0); + } + + private void PlayWaddleAnimationUsing(Entity entity, float len, float tumbleIntensity) + { + entity.Comp.LastStep = !entity.Comp.LastStep; var anim = new Animation() { @@ -116,58 +137,13 @@ public sealed class WaddleAnimationSystem : EntitySystem KeyFrames = { new AnimationTrackProperty.KeyFrame(new Vector2(), 0), - new AnimationTrackProperty.KeyFrame(component.HopIntensity, len/2), + new AnimationTrackProperty.KeyFrame(entity.Comp.HopIntensity, len/2), new AnimationTrackProperty.KeyFrame(new Vector2(), len/2), } } } }; - _animation.Play(uid, anim, component.KeyName); - } - - private void OnStoppedWalking(EntityUid uid, WaddleAnimationComponent component, StoppedWaddlingEvent args) - { - StopWaddling(uid, component); - } - - private void OnAnimationCompleted(EntityUid uid, WaddleAnimationComponent component, AnimationCompletedEvent args) - { - var started = new StartedWaddlingEvent(uid); - - RaiseLocalEvent(uid, ref started); - } - - private void OnStunned(EntityUid uid, WaddleAnimationComponent component, StunnedEvent args) - { - StopWaddling(uid, component); - } - - private void OnKnockedDown(EntityUid uid, WaddleAnimationComponent component, KnockedDownEvent args) - { - StopWaddling(uid, component); - } - - private void OnBuckleChange(EntityUid uid, WaddleAnimationComponent component, BuckleChangeEvent args) - { - StopWaddling(uid, component); - } - - private void StopWaddling(EntityUid uid, WaddleAnimationComponent component) - { - if (!component.IsCurrentlyWaddling) - return; - - _animation.Stop(uid, component.KeyName); - - if (!TryComp(uid, out var sprite)) - { - return; - } - - sprite.Offset = new Vector2(); - sprite.Rotation = Angle.FromDegrees(0); - - component.IsCurrentlyWaddling = false; + _animation.Play(entity.Owner, anim, entity.Comp.KeyName); } } diff --git a/Content.Client/NPC/PathfindingSystem.cs b/Content.Client/NPC/PathfindingSystem.cs index 709601a57b..d3ae509152 100644 --- a/Content.Client/NPC/PathfindingSystem.cs +++ b/Content.Client/NPC/PathfindingSystem.cs @@ -223,7 +223,7 @@ namespace Content.Client.NPC foreach (var crumb in chunk.Value) { - var crumbMapPos = worldMatrix.Transform(_system.GetCoordinate(chunk.Key, crumb.Coordinates)); + var crumbMapPos = Vector2.Transform(_system.GetCoordinate(chunk.Key, crumb.Coordinates), worldMatrix); var distance = (crumbMapPos - mouseWorldPos.Position).Length(); if (distance < nearestDistance) @@ -292,7 +292,7 @@ namespace Content.Client.NPC foreach (var poly in tile) { - if (poly.Box.Contains(invGridMatrix.Transform(mouseWorldPos.Position))) + if (poly.Box.Contains(Vector2.Transform(mouseWorldPos.Position, invGridMatrix))) { nearest = poly; break; @@ -488,7 +488,7 @@ namespace Content.Client.NPC if (neighborMap.MapId != args.MapId) continue; - neighborPos = invMatrix.Transform(neighborMap.Position); + neighborPos = Vector2.Transform(neighborMap.Position, invMatrix); } else { @@ -576,7 +576,7 @@ namespace Content.Client.NPC } } - worldHandle.SetTransform(Matrix3.Identity); + worldHandle.SetTransform(Matrix3x2.Identity); } } } diff --git a/Content.Client/NodeContainer/NodeVisualizationOverlay.cs b/Content.Client/NodeContainer/NodeVisualizationOverlay.cs index 691bcb41db..4e8a4a10ca 100644 --- a/Content.Client/NodeContainer/NodeVisualizationOverlay.cs +++ b/Content.Client/NodeContainer/NodeVisualizationOverlay.cs @@ -199,7 +199,7 @@ namespace Content.Client.NodeContainer } - handle.SetTransform(Matrix3.Identity); + handle.SetTransform(Matrix3x2.Identity); _gridIndex.Clear(); } diff --git a/Content.Client/Nyanotrasen/Overlays/DogVisionOverlay.cs b/Content.Client/Nyanotrasen/Overlays/DogVisionOverlay.cs index 01fa35fc60..acfc9919b0 100644 --- a/Content.Client/Nyanotrasen/Overlays/DogVisionOverlay.cs +++ b/Content.Client/Nyanotrasen/Overlays/DogVisionOverlay.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Robust.Client.Graphics; using Robust.Client.Player; using Robust.Shared.Enums; @@ -43,7 +44,7 @@ public sealed partial class DogVisionOverlay : Overlay var worldHandle = args.WorldHandle; var viewport = args.WorldBounds; - worldHandle.SetTransform(Matrix3.Identity); + worldHandle.SetTransform(Matrix3x2.Identity); worldHandle.UseShader(_dogVisionShader); worldHandle.DrawRect(viewport, Color.White); worldHandle.UseShader(null); // important - as of writing, construction overlay breaks without this diff --git a/Content.Client/Outline/TargetOutlineSystem.cs b/Content.Client/Outline/TargetOutlineSystem.cs index 2a6867f51f..df57578b1f 100644 --- a/Content.Client/Outline/TargetOutlineSystem.cs +++ b/Content.Client/Outline/TargetOutlineSystem.cs @@ -22,6 +22,7 @@ public sealed class TargetOutlineSystem : EntitySystem [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; private bool _enabled = false; @@ -137,7 +138,7 @@ public sealed class TargetOutlineSystem : EntitySystem // check the entity whitelist if (valid && Whitelist != null) - valid = Whitelist.IsValid(entity); + valid = _whitelistSystem.IsWhitelistPass(Whitelist, entity); // and check the cancellable event if (valid && ValidationEvent != null) diff --git a/Content.Client/Overlays/EntityHealthBarOverlay.cs b/Content.Client/Overlays/EntityHealthBarOverlay.cs index 2b2ff14a22..4f92843739 100644 --- a/Content.Client/Overlays/EntityHealthBarOverlay.cs +++ b/Content.Client/Overlays/EntityHealthBarOverlay.cs @@ -1,14 +1,17 @@ using System.Numerics; +using Content.Client.StatusIcon; using Content.Client.UserInterface.Systems; using Content.Shared.Damage; using Content.Shared.FixedPoint; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; +using Content.Shared.StatusIcon; using Content.Shared.StatusIcon.Components; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Shared.Enums; +using Robust.Shared.Prototypes; using static Robust.Shared.Maths.Color; namespace Content.Client.Overlays; @@ -19,19 +22,27 @@ namespace Content.Client.Overlays; public sealed class EntityHealthBarOverlay : Overlay { private readonly IEntityManager _entManager; + private readonly IPrototypeManager _prototype; + private readonly SharedTransformSystem _transform; private readonly MobStateSystem _mobStateSystem; private readonly MobThresholdSystem _mobThresholdSystem; + private readonly StatusIconSystem _statusIconSystem; private readonly ProgressColorSystem _progressColor; + + public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV; public HashSet DamageContainers = new(); + public ProtoId? StatusIcon; - public EntityHealthBarOverlay(IEntityManager entManager) + public EntityHealthBarOverlay(IEntityManager entManager, IPrototypeManager prototype) { _entManager = entManager; + _prototype = prototype; _transform = _entManager.System(); _mobStateSystem = _entManager.System(); _mobThresholdSystem = _entManager.System(); + _statusIconSystem = _entManager.System(); _progressColor = _entManager.System(); } @@ -42,8 +53,9 @@ public sealed class EntityHealthBarOverlay : Overlay var xformQuery = _entManager.GetEntityQuery(); const float scale = 1f; - var scaleMatrix = Matrix3.CreateScale(new Vector2(scale, scale)); - var rotationMatrix = Matrix3.CreateRotation(-rotation); + var scaleMatrix = Matrix3Helpers.CreateScale(new Vector2(scale, scale)); + var rotationMatrix = Matrix3Helpers.CreateRotation(-rotation); + _prototype.TryIndex(StatusIcon, out var statusIcon); var query = _entManager.AllEntityQueryEnumerator(); while (query.MoveNext(out var uid, @@ -52,41 +64,33 @@ public sealed class EntityHealthBarOverlay : Overlay out var damageableComponent, out var spriteComponent)) { - if (_entManager.TryGetComponent(uid, out var metaDataComponent) && - metaDataComponent.Flags.HasFlag(MetaDataFlags.InContainer)) - { + if (statusIcon != null && !_statusIconSystem.IsVisible((uid, _entManager.GetComponent(uid)), statusIcon)) continue; - } + // We want the stealth user to still be able to see his health bar himself if (!xformQuery.TryGetComponent(uid, out var xform) || xform.MapID != args.MapId) - { continue; - } if (damageableComponent.DamageContainerID == null || !DamageContainers.Contains(damageableComponent.DamageContainerID)) - { continue; - } // we use the status icon component bounds if specified otherwise use sprite var bounds = _entManager.GetComponentOrNull(uid)?.Bounds ?? spriteComponent.Bounds; var worldPos = _transform.GetWorldPosition(xform, xformQuery); if (!bounds.Translated(worldPos).Intersects(args.WorldAABB)) - { continue; - } // we are all progressing towards death every day if (CalcProgress(uid, mobStateComponent, damageableComponent, mobThresholdsComponent) is not { } deathProgress) continue; var worldPosition = _transform.GetWorldPosition(xform); - var worldMatrix = Matrix3.CreateTranslation(worldPosition); + var worldMatrix = Matrix3Helpers.CreateTranslation(worldPosition); - Matrix3.Multiply(scaleMatrix, worldMatrix, out var scaledWorld); - Matrix3.Multiply(rotationMatrix, scaledWorld, out var matty); + var scaledWorld = Matrix3x2.Multiply(scaleMatrix, worldMatrix); + var matty = Matrix3x2.Multiply(rotationMatrix, scaledWorld); handle.SetTransform(matty); @@ -115,7 +119,7 @@ public sealed class EntityHealthBarOverlay : Overlay handle.DrawRect(pixelDarken, Black.WithAlpha(128)); } - handle.SetTransform(Matrix3.Identity); + handle.SetTransform(Matrix3x2.Identity); } /// diff --git a/Content.Client/Overlays/ShowCriminalRecordIconsSystem.cs b/Content.Client/Overlays/ShowCriminalRecordIconsSystem.cs index 8f23cd510c..c353b17272 100644 --- a/Content.Client/Overlays/ShowCriminalRecordIconsSystem.cs +++ b/Content.Client/Overlays/ShowCriminalRecordIconsSystem.cs @@ -19,10 +19,10 @@ public sealed class ShowCriminalRecordIconsSystem : EquipmentHudSystem(component.StatusIcon.Id, out var iconPrototype)) + if (_prototype.TryIndex(component.StatusIcon, out var iconPrototype)) ev.StatusIcons.Add(iconPrototype); } } diff --git a/Content.Client/Overlays/ShowHealthBarsSystem.cs b/Content.Client/Overlays/ShowHealthBarsSystem.cs index 170f552cf3..1eb712a898 100644 --- a/Content.Client/Overlays/ShowHealthBarsSystem.cs +++ b/Content.Client/Overlays/ShowHealthBarsSystem.cs @@ -2,6 +2,8 @@ using Content.Shared.Inventory.Events; using Content.Shared.Overlays; using Robust.Client.Graphics; using System.Linq; +using Robust.Client.Player; +using Robust.Shared.Prototypes; namespace Content.Client.Overlays; @@ -11,6 +13,7 @@ namespace Content.Client.Overlays; public sealed class ShowHealthBarsSystem : EquipmentHudSystem { [Dependency] private readonly IOverlayManager _overlayMan = default!; + [Dependency] private readonly IPrototypeManager _prototype = default!; private EntityHealthBarOverlay _overlay = default!; @@ -18,16 +21,21 @@ public sealed class ShowHealthBarsSystem : EquipmentHudSystem component) { base.UpdateInternal(component); - foreach (var damageContainerId in component.Components.SelectMany(x => x.DamageContainers)) + foreach (var comp in component.Components) { - _overlay.DamageContainers.Add(damageContainerId); + foreach (var damageContainerId in comp.DamageContainers) + { + _overlay.DamageContainers.Add(damageContainerId); + } + + _overlay.StatusIcon = comp.HealthStatusIcon; } if (!_overlayMan.HasOverlay()) diff --git a/Content.Client/Overlays/ShowHealthIconsSystem.cs b/Content.Client/Overlays/ShowHealthIconsSystem.cs index a546cf4d82..d8af91482b 100644 --- a/Content.Client/Overlays/ShowHealthIconsSystem.cs +++ b/Content.Client/Overlays/ShowHealthIconsSystem.cs @@ -24,7 +24,6 @@ public sealed class ShowHealthIconsSystem : EquipmentHudSystem(OnGetStatusIconsEvent); - } protected override void UpdateInternal(RefreshEquipmentHudEvent component) @@ -46,7 +45,7 @@ public sealed class ShowHealthIconsSystem : EquipmentHudSystem entity, ref GetStatusIconsEvent args) { - if (!IsActive || args.InContainer) + if (!IsActive) return; var healthIcons = DecideHealthIcons(entity); diff --git a/Content.Client/Overlays/ShowHungerIconsSystem.cs b/Content.Client/Overlays/ShowHungerIconsSystem.cs index b1c0f3a1a0..6b0d575a81 100644 --- a/Content.Client/Overlays/ShowHungerIconsSystem.cs +++ b/Content.Client/Overlays/ShowHungerIconsSystem.cs @@ -18,7 +18,7 @@ public sealed class ShowHungerIconsSystem : EquipmentHudSystem(component.MindShieldStatusIcon.Id, out var iconPrototype)) + if (_prototype.TryIndex(component.MindShieldStatusIcon, out var iconPrototype)) ev.StatusIcons.Add(iconPrototype); } } diff --git a/Content.Client/Overlays/ShowSyndicateIconsSystem.cs b/Content.Client/Overlays/ShowSyndicateIconsSystem.cs index 660ef198e1..782178a29d 100644 --- a/Content.Client/Overlays/ShowSyndicateIconsSystem.cs +++ b/Content.Client/Overlays/ShowSyndicateIconsSystem.cs @@ -19,11 +19,10 @@ public sealed class ShowSyndicateIconsSystem : EquipmentHudSystem(component.SyndStatusIcon, out var iconPrototype)) ev.StatusIcons.Add(iconPrototype); } } - diff --git a/Content.Client/Overlays/ShowThirstIconsSystem.cs b/Content.Client/Overlays/ShowThirstIconsSystem.cs index b08aa4340b..44be1f7a67 100644 --- a/Content.Client/Overlays/ShowThirstIconsSystem.cs +++ b/Content.Client/Overlays/ShowThirstIconsSystem.cs @@ -18,7 +18,7 @@ public sealed class ShowThirstIconsSystem : EquipmentHudSystem("StencilMask").Instance()); worldHandle.DrawTextureRect(_blep!.Texture, worldBounds); var curTime = _timing.RealTime; diff --git a/Content.Client/Overlays/StencilOverlay.Weather.cs b/Content.Client/Overlays/StencilOverlay.Weather.cs index 31bc88af45..29ed157a79 100644 --- a/Content.Client/Overlays/StencilOverlay.Weather.cs +++ b/Content.Client/Overlays/StencilOverlay.Weather.cs @@ -10,7 +10,7 @@ public sealed partial class StencilOverlay { private List> _grids = new(); - private void DrawWeather(in OverlayDrawArgs args, WeatherPrototype weatherProto, float alpha, Matrix3 invMatrix) + private void DrawWeather(in OverlayDrawArgs args, WeatherPrototype weatherProto, float alpha, Matrix3x2 invMatrix) { var worldHandle = args.WorldHandle; var mapId = args.MapId; @@ -32,7 +32,7 @@ public sealed partial class StencilOverlay foreach (var grid in _grids) { var matrix = _transform.GetWorldMatrix(grid, xformQuery); - Matrix3.Multiply(in matrix, in invMatrix, out var matty); + var matty = Matrix3x2.Multiply(matrix, invMatrix); worldHandle.SetTransform(matty); foreach (var tile in grid.Comp.GetTilesIntersecting(worldAABB)) @@ -52,7 +52,7 @@ public sealed partial class StencilOverlay }, Color.Transparent); - worldHandle.SetTransform(Matrix3.Identity); + worldHandle.SetTransform(Matrix3x2.Identity); worldHandle.UseShader(_protoManager.Index("StencilMask").Instance()); worldHandle.DrawTextureRect(_blep!.Texture, worldBounds); var curTime = _timing.RealTime; @@ -62,7 +62,7 @@ public sealed partial class StencilOverlay worldHandle.UseShader(_protoManager.Index("StencilDraw").Instance()); _parallax.DrawParallax(worldHandle, worldAABB, sprite, curTime, position, Vector2.Zero, modulate: (weatherProto.Color ?? Color.White).WithAlpha(alpha)); - worldHandle.SetTransform(Matrix3.Identity); + worldHandle.SetTransform(Matrix3x2.Identity); worldHandle.UseShader(null); } } diff --git a/Content.Client/Overlays/StencilOverlay.cs b/Content.Client/Overlays/StencilOverlay.cs index e475dca759..78b1c4d2b1 100644 --- a/Content.Client/Overlays/StencilOverlay.cs +++ b/Content.Client/Overlays/StencilOverlay.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Client.Parallax; using Content.Client.Weather; using Content.Shared.Salvage; @@ -72,6 +73,6 @@ public sealed partial class StencilOverlay : Overlay } args.WorldHandle.UseShader(null); - args.WorldHandle.SetTransform(Matrix3.Identity); + args.WorldHandle.SetTransform(Matrix3x2.Identity); } } diff --git a/Content.Client/Paper/UI/StampLabel.xaml.cs b/Content.Client/Paper/UI/StampLabel.xaml.cs index 6a8eb5f98f..be6d52baea 100644 --- a/Content.Client/Paper/UI/StampLabel.xaml.cs +++ b/Content.Client/Paper/UI/StampLabel.xaml.cs @@ -50,7 +50,7 @@ public sealed partial class StampLabel : Label base.Draw(handle); // Restore a sane transform+shader - handle.SetTransform(Matrix3.Identity); + handle.SetTransform(Matrix3x2.Identity); handle.UseShader(null); } } diff --git a/Content.Client/Paper/UI/StampWidget.xaml.cs b/Content.Client/Paper/UI/StampWidget.xaml.cs index a04508aeba..487e0732b4 100644 --- a/Content.Client/Paper/UI/StampWidget.xaml.cs +++ b/Content.Client/Paper/UI/StampWidget.xaml.cs @@ -53,7 +53,7 @@ public sealed partial class StampWidget : PanelContainer base.Draw(handle); // Restore a sane transform+shader - handle.SetTransform(Matrix3.Identity); + handle.SetTransform(Matrix3x2.Identity); handle.UseShader(null); } } diff --git a/Content.Client/Pinpointer/UI/NavMapControl.cs b/Content.Client/Pinpointer/UI/NavMapControl.cs index f4d2f8e2fb..3c99a18818 100644 --- a/Content.Client/Pinpointer/UI/NavMapControl.cs +++ b/Content.Client/Pinpointer/UI/NavMapControl.cs @@ -218,7 +218,7 @@ public partial class NavMapControl : MapGridControl // Convert to a world position var unscaledPosition = (localPosition - MidPointVector) / MinimapScale; - var worldPosition = _transformSystem.GetWorldMatrix(_xform).Transform(new Vector2(unscaledPosition.X, -unscaledPosition.Y) + offset); + var worldPosition = Vector2.Transform(new Vector2(unscaledPosition.X, -unscaledPosition.Y) + offset, _transformSystem.GetWorldMatrix(_xform)); // Find closest tracked entity in range var closestEntity = NetEntity.Invalid; @@ -401,7 +401,7 @@ public partial class NavMapControl : MapGridControl if (mapPos.MapId != MapId.Nullspace) { - var position = _transformSystem.GetInvWorldMatrix(_xform).Transform(mapPos.Position) - offset; + var position = Vector2.Transform(mapPos.Position, _transformSystem.GetInvWorldMatrix(_xform)) - offset; position = ScalePosition(new Vector2(position.X, -position.Y)); handle.DrawCircle(position, float.Sqrt(MinimapScale) * 2f, value.Color); @@ -422,7 +422,7 @@ public partial class NavMapControl : MapGridControl if (mapPos.MapId != MapId.Nullspace) { - var position = _transformSystem.GetInvWorldMatrix(_xform).Transform(mapPos.Position) - offset; + var position = Vector2.Transform(mapPos.Position, _transformSystem.GetInvWorldMatrix(_xform)) - offset; position = ScalePosition(new Vector2(position.X, -position.Y)); var scalingCoefficient = MinmapScaleModifier * float.Sqrt(MinimapScale); diff --git a/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs index 80683fae71..87eb523401 100644 --- a/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs +++ b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs @@ -107,7 +107,13 @@ public sealed partial class JobRequirementsManager : ISharedPlaytimeManager if (player == null) return true; - return CheckRoleTime(job.Requirements, out reason); + return CheckRoleTime(job, out reason); + } + + public bool CheckRoleTime(JobPrototype job, [NotNullWhen(false)] out FormattedMessage? reason) + { + var reqs = _entManager.System().GetJobRequirement(job); + return CheckRoleTime(reqs, out reason); } public bool CheckRoleTime(HashSet? requirements, [NotNullWhen(false)] out FormattedMessage? reason) diff --git a/Content.Client/Popups/PopupOverlay.cs b/Content.Client/Popups/PopupOverlay.cs index fb6bb3bf56..77eeb611f5 100644 --- a/Content.Client/Popups/PopupOverlay.cs +++ b/Content.Client/Popups/PopupOverlay.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Shared.Examine; using Robust.Client.Graphics; using Robust.Client.Player; @@ -55,7 +56,7 @@ public sealed class PopupOverlay : Overlay if (args.ViewportControl == null) return; - args.DrawingHandle.SetTransform(Matrix3.Identity); + args.DrawingHandle.SetTransform(Matrix3x2.Identity); args.DrawingHandle.UseShader(_shader); var scale = _configManager.GetCVar(CVars.DisplayUIScale); @@ -90,7 +91,7 @@ public sealed class PopupOverlay : Overlay e => e == popup.InitialPos.EntityId || e == ourEntity, entMan: _entManager)) continue; - var pos = matrix.Transform(mapPos.Position); + var pos = Vector2.Transform(mapPos.Position, matrix); _controller.DrawPopup(popup, worldHandle, pos, scale); } } diff --git a/Content.Client/Power/PowerMonitoringWindow.xaml.Widgets.cs b/Content.Client/Power/PowerMonitoringWindow.xaml.Widgets.cs index 25a586a75d..1427df0515 100644 --- a/Content.Client/Power/PowerMonitoringWindow.xaml.Widgets.cs +++ b/Content.Client/Power/PowerMonitoringWindow.xaml.Widgets.cs @@ -102,7 +102,8 @@ public sealed partial class PowerMonitoringWindow button.ToolTip = Loc.GetString(name); // Update power value - button.PowerValue.Text = Loc.GetString("power-monitoring-window-value", ("value", entry.PowerValue)); + // Don't use SI prefixes, just give the number in W, so that it is readily apparent which consumer is using a lot of power. + button.PowerValue.Text = Loc.GetString("power-monitoring-window-button-value", ("value", Math.Round(entry.PowerValue).ToString("N0"))); } private void UpdateEntrySourcesOrLoads(BoxContainer masterContainer, BoxContainer currentContainer, PowerMonitoringConsoleEntry[]? entries, SpriteSpecifier.Texture icon) @@ -480,6 +481,7 @@ public sealed class PowerMonitoringButton : Button PowerValue = new Label() { HorizontalAlignment = HAlignment.Right, + Align = Label.AlignMode.Right, SetWidth = 72f, Margin = new Thickness(10, 0, 0, 0), ClipText = true, diff --git a/Content.Client/Revolutionary/RevolutionarySystem.cs b/Content.Client/Revolutionary/RevolutionarySystem.cs index 682c73f93e..8e7e687fa8 100644 --- a/Content.Client/Revolutionary/RevolutionarySystem.cs +++ b/Content.Client/Revolutionary/RevolutionarySystem.cs @@ -1,44 +1,37 @@ -using Content.Shared.Antag; using Content.Shared.Revolutionary.Components; -using Content.Shared.Ghost; +using Content.Shared.Revolutionary; using Content.Shared.StatusIcon.Components; +using Robust.Shared.Prototypes; namespace Content.Client.Revolutionary; /// /// Used for the client to get status icons from other revs. /// -public sealed class RevolutionarySystem : EntitySystem +public sealed class RevolutionarySystem : SharedRevolutionarySystem { + [Dependency] private readonly IPrototypeManager _prototype = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnCanShowRevIcon); - SubscribeLocalEvent(OnCanShowRevIcon); + SubscribeLocalEvent(GetRevIcon); + SubscribeLocalEvent(GetHeadRevIcon); } - /// - /// Determine whether a client should display the rev icon. - /// - private void OnCanShowRevIcon(EntityUid uid, T comp, ref CanDisplayStatusIconsEvent args) where T : IAntagStatusIconComponent + private void GetRevIcon(Entity ent, ref GetStatusIconsEvent args) { - args.Cancelled = !CanDisplayIcon(args.User, comp.IconVisibleToGhost); + if (HasComp(ent)) + return; + + if (_prototype.TryIndex(ent.Comp.StatusIcon, out var iconPrototype)) + args.StatusIcons.Add(iconPrototype); } - /// - /// The criteria that determine whether a client should see Rev/Head rev icons. - /// - private bool CanDisplayIcon(EntityUid? uid, bool visibleToGhost) + private void GetHeadRevIcon(Entity ent, ref GetStatusIconsEvent args) { - if (HasComp(uid) || HasComp(uid)) - return true; - - if (visibleToGhost && HasComp(uid)) - return true; - - return HasComp(uid); + if (_prototype.TryIndex(ent.Comp.StatusIcon, out var iconPrototype)) + args.StatusIcons.Add(iconPrototype); } - } diff --git a/Content.Client/SSDIndicator/SSDIndicatorSystem.cs b/Content.Client/SSDIndicator/SSDIndicatorSystem.cs index 587450a2f6..e731195317 100644 --- a/Content.Client/SSDIndicator/SSDIndicatorSystem.cs +++ b/Content.Client/SSDIndicator/SSDIndicatorSystem.cs @@ -30,13 +30,12 @@ public sealed class SSDIndicatorSystem : EntitySystem { if (component.IsSSD && _cfg.GetCVar(CCVars.ICShowSSDIndicator) && - !args.InContainer && !_mobState.IsDead(uid) && !HasComp(uid) && TryComp(uid, out var mindContainer) && mindContainer.ShowExamineInfo) { - args.StatusIcons.Add(_prototype.Index(component.Icon)); + args.StatusIcons.Add(_prototype.Index(component.Icon)); } } } diff --git a/Content.Client/Shuttles/Systems/ShuttleSystem.EmergencyConsole.cs b/Content.Client/Shuttles/Systems/ShuttleSystem.EmergencyConsole.cs index 7086fd0541..d5154a87be 100644 --- a/Content.Client/Shuttles/Systems/ShuttleSystem.EmergencyConsole.cs +++ b/Content.Client/Shuttles/Systems/ShuttleSystem.EmergencyConsole.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Shared.Shuttles.Events; using Content.Shared.Shuttles.Systems; using Robust.Client.Graphics; @@ -75,6 +76,6 @@ public sealed class EmergencyShuttleOverlay : Overlay args.WorldHandle.SetTransform(xform.WorldMatrix); args.WorldHandle.DrawRect(Position.Value, Color.Red.WithAlpha(100)); - args.WorldHandle.SetTransform(Matrix3.Identity); + args.WorldHandle.SetTransform(Matrix3x2.Identity); } } diff --git a/Content.Client/Shuttles/UI/BaseShuttleControl.xaml.cs b/Content.Client/Shuttles/UI/BaseShuttleControl.xaml.cs index 8774c1dfb5..b50d8fa6b2 100644 --- a/Content.Client/Shuttles/UI/BaseShuttleControl.xaml.cs +++ b/Content.Client/Shuttles/UI/BaseShuttleControl.xaml.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Client.UserInterface.Controls; using Content.Shared.Shuttles.Components; using Robust.Client.AutoGenerated; @@ -115,7 +116,7 @@ public partial class BaseShuttleControl : MapGridControl } } - protected void DrawGrid(DrawingHandleScreen handle, Matrix3 matrix, Entity grid, Color color, float alpha = 0.01f) + protected void DrawGrid(DrawingHandleScreen handle, Matrix3x2 matrix, Entity grid, Color color, float alpha = 0.01f) { var rator = Maps.GetAllTilesEnumerator(grid.Owner, grid.Comp); var minimapScale = MinimapScale; @@ -289,7 +290,7 @@ public partial class BaseShuttleControl : MapGridControl public float MinimapScale; public Vector2 MidPoint; - public Matrix3 Matrix; + public Matrix3x2 Matrix; public List Vertices; public Vector2[] ScaledVertices; @@ -297,7 +298,7 @@ public partial class BaseShuttleControl : MapGridControl public void Execute(int index) { var vert = Vertices[index]; - var adjustedVert = Matrix.Transform(vert); + var adjustedVert = Vector2.Transform(vert, Matrix); adjustedVert = adjustedVert with { Y = -adjustedVert.Y }; var scaledVert = ScalePosition(adjustedVert, MinimapScale, MidPoint); diff --git a/Content.Client/Shuttles/UI/NavScreen.xaml.cs b/Content.Client/Shuttles/UI/NavScreen.xaml.cs index b7b757ea48..91d95aaa04 100644 --- a/Content.Client/Shuttles/UI/NavScreen.xaml.cs +++ b/Content.Client/Shuttles/UI/NavScreen.xaml.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Shared.Shuttles.BUIStates; using Robust.Client.AutoGenerated; using Robust.Client.Graphics; @@ -68,7 +69,7 @@ public sealed partial class NavScreen : BoxContainer } var (_, worldRot, worldMatrix) = _xformSystem.GetWorldPositionRotationMatrix(gridXform); - var worldPos = worldMatrix.Transform(gridBody.LocalCenter); + var worldPos = Vector2.Transform(gridBody.LocalCenter, worldMatrix); // Get the positive reduced angle. var displayRot = -worldRot.Reduced(); diff --git a/Content.Client/Shuttles/UI/ShuttleDockControl.xaml.cs b/Content.Client/Shuttles/UI/ShuttleDockControl.xaml.cs index f03c440295..31f0eecad7 100644 --- a/Content.Client/Shuttles/UI/ShuttleDockControl.xaml.cs +++ b/Content.Client/Shuttles/UI/ShuttleDockControl.xaml.cs @@ -108,10 +108,10 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl var gridNent = EntManager.GetNetEntity(GridEntity); var mapPos = _xformSystem.ToMapCoordinates(_coordinates.Value); var ourGridMatrix = _xformSystem.GetWorldMatrix(gridXform.Owner); - var dockMatrix = Matrix3.CreateTransform(_coordinates.Value.Position, Angle.Zero); - Matrix3.Multiply(dockMatrix, ourGridMatrix, out var offsetMatrix); + var dockMatrix = Matrix3Helpers.CreateTransform(_coordinates.Value.Position, Angle.Zero); + var worldFromDock = Matrix3x2.Multiply(dockMatrix, ourGridMatrix); - offsetMatrix = offsetMatrix.Invert(); + Matrix3x2.Invert(worldFromDock, out var offsetMatrix); // Draw nearby grids var controlBounds = PixelSizeBox; @@ -137,7 +137,7 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl continue; var gridMatrix = _xformSystem.GetWorldMatrix(grid.Owner); - Matrix3.Multiply(in gridMatrix, in offsetMatrix, out var matty); + var matty = Matrix3x2.Multiply(gridMatrix, offsetMatrix); var color = _shuttles.GetIFFColor(grid.Owner, grid.Owner == GridEntity, component: iffComp); DrawGrid(handle, matty, grid, color); @@ -151,23 +151,23 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl if (ViewedDock == dock.Entity) continue; - var position = matty.Transform(dock.Coordinates.Position); + var position = Vector2.Transform(dock.Coordinates.Position, matty); - var otherDockRotation = Matrix3.CreateRotation(dock.Angle); + var otherDockRotation = Matrix3Helpers.CreateRotation(dock.Angle); var scaledPos = ScalePosition(position with {Y = -position.Y}); if (!controlBounds.Contains(scaledPos.Floored())) continue; // Draw the dock's collision - var collisionBL = matty.Transform(dock.Coordinates.Position + - otherDockRotation.Transform(new Vector2(-0.2f, -0.7f))); - var collisionBR = matty.Transform(dock.Coordinates.Position + - otherDockRotation.Transform(new Vector2(0.2f, -0.7f))); - var collisionTR = matty.Transform(dock.Coordinates.Position + - otherDockRotation.Transform(new Vector2(0.2f, -0.5f))); - var collisionTL = matty.Transform(dock.Coordinates.Position + - otherDockRotation.Transform(new Vector2(-0.2f, -0.5f))); + var collisionBL = Vector2.Transform(dock.Coordinates.Position + + Vector2.Transform(new Vector2(-0.2f, -0.7f), otherDockRotation), matty); + var collisionBR = Vector2.Transform(dock.Coordinates.Position + + Vector2.Transform(new Vector2(0.2f, -0.7f), otherDockRotation), matty); + var collisionTR = Vector2.Transform(dock.Coordinates.Position + + Vector2.Transform(new Vector2(0.2f, -0.5f), otherDockRotation), matty); + var collisionTL = Vector2.Transform(dock.Coordinates.Position + + Vector2.Transform(new Vector2(-0.2f, -0.5f), otherDockRotation), matty); var verts = new[] { @@ -195,10 +195,10 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl handle.DrawPrimitives(DrawPrimitiveTopology.LineList, verts, otherDockConnection); // Draw the dock itself - var dockBL = matty.Transform(dock.Coordinates.Position + new Vector2(-0.5f, -0.5f)); - var dockBR = matty.Transform(dock.Coordinates.Position + new Vector2(0.5f, -0.5f)); - var dockTR = matty.Transform(dock.Coordinates.Position + new Vector2(0.5f, 0.5f)); - var dockTL = matty.Transform(dock.Coordinates.Position + new Vector2(-0.5f, 0.5f)); + var dockBL = Vector2.Transform(dock.Coordinates.Position + new Vector2(-0.5f, -0.5f), matty); + var dockBR = Vector2.Transform(dock.Coordinates.Position + new Vector2(0.5f, -0.5f), matty); + var dockTR = Vector2.Transform(dock.Coordinates.Position + new Vector2(0.5f, 0.5f), matty); + var dockTL = Vector2.Transform(dock.Coordinates.Position + new Vector2(-0.5f, 0.5f), matty); verts = new[] { @@ -308,14 +308,14 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl // Draw the dock's collision var invertedPosition = Vector2.Zero; invertedPosition.Y = -invertedPosition.Y; - var rotation = Matrix3.CreateRotation(-_angle.Value + MathF.PI); + var rotation = Matrix3Helpers.CreateRotation(-_angle.Value + MathF.PI); var ourDockConnection = new UIBox2( - ScalePosition(rotation.Transform(new Vector2(-0.2f, -0.7f))), - ScalePosition(rotation.Transform(new Vector2(0.2f, -0.5f)))); + ScalePosition(Vector2.Transform(new Vector2(-0.2f, -0.7f), rotation)), + ScalePosition(Vector2.Transform(new Vector2(0.2f, -0.5f), rotation))); var ourDock = new UIBox2( - ScalePosition(rotation.Transform(new Vector2(-0.5f, 0.5f))), - ScalePosition(rotation.Transform(new Vector2(0.5f, -0.5f)))); + ScalePosition(Vector2.Transform(new Vector2(-0.5f, 0.5f), rotation)), + ScalePosition(Vector2.Transform(new Vector2(0.5f, -0.5f), rotation))); var dockColor = Color.Magenta; var connectionColor = Color.Pink; diff --git a/Content.Client/Shuttles/UI/ShuttleMapControl.xaml.cs b/Content.Client/Shuttles/UI/ShuttleMapControl.xaml.cs index 2f35a8dffd..8bd4a338cb 100644 --- a/Content.Client/Shuttles/UI/ShuttleMapControl.xaml.cs +++ b/Content.Client/Shuttles/UI/ShuttleMapControl.xaml.cs @@ -114,7 +114,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl var beaconsOnly = EntManager.TryGetComponent(mapUid, out FTLDestinationComponent? destComp) && destComp.BeaconsOnly; - var mapTransform = Matrix3.CreateInverseTransform(Offset, Angle.Zero); + var mapTransform = Matrix3Helpers.CreateInverseTransform(Offset, Angle.Zero); if (beaconsOnly && TryGetBeacon(_beacons, mapTransform, args.RelativePixelPosition, PixelRect, out var foundBeacon, out _)) { @@ -203,7 +203,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl /// /// /// - private List GetViewportMapObjects(Matrix3 matty, List mapObjects) + private List GetViewportMapObjects(Matrix3x2 matty, List mapObjects) { var results = new List(); var enlargement = new Vector2i((int) (16 * UIScale), (int) (16 * UIScale)); @@ -217,7 +217,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl var mapCoords = _shuttles.GetMapCoordinates(mapObj); - var relativePos = matty.Transform(mapCoords.Position); + var relativePos = Vector2.Transform(mapCoords.Position, matty); relativePos = relativePos with { Y = -relativePos.Y }; var uiPosition = ScalePosition(relativePos); @@ -250,7 +250,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl DrawParallax(handle); var viewedMapUid = _mapManager.GetMapEntityId(ViewingMap); - var matty = Matrix3.CreateInverseTransform(Offset, Angle.Zero); + var matty = Matrix3Helpers.CreateInverseTransform(Offset, Angle.Zero); var realTime = _timing.RealTime; var viewBox = new Box2(Offset - WorldRangeVector, Offset + WorldRangeVector); var viewportObjects = GetViewportMapObjects(matty, mapObjects); @@ -267,7 +267,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl var (gridPos, gridRot) = _xformSystem.GetWorldPositionRotation(shuttleXform); gridPos = Maps.GetGridPosition((gridUid, gridPhysics), gridPos, gridRot); - var gridRelativePos = matty.Transform(gridPos); + var gridRelativePos = Vector2.Transform(gridPos, matty); gridRelativePos = gridRelativePos with { Y = -gridRelativePos.Y }; var gridUiPos = ScalePosition(gridRelativePos); @@ -296,7 +296,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl continue; } - var adjustedPos = matty.Transform(mapCoords.Position); + var adjustedPos = Vector2.Transform(mapCoords.Position, matty); var localPos = ScalePosition(adjustedPos with { Y = -adjustedPos.Y}); handle.DrawCircle(localPos, exclusion.Range * MinimapScale, exclusionColor.WithAlpha(0.05f)); handle.DrawCircle(localPos, exclusion.Range * MinimapScale, exclusionColor, filled: false); @@ -319,7 +319,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl foreach (var (beaconName, coords, mapO) in GetBeacons(viewportObjects, matty, controlLocalBounds)) { - var localPos = matty.Transform(coords.Position); + var localPos = Vector2.Transform(coords.Position, matty); localPos = localPos with { Y = -localPos.Y }; var beaconUiPos = ScalePosition(localPos); var mapObject = GetMapObject(localPos, Angle.Zero, scale: 0.75f, scalePosition: true); @@ -360,7 +360,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl var (gridPos, gridRot) = _xformSystem.GetWorldPositionRotation(grid.Owner); gridPos = Maps.GetGridPosition((grid, gridPhysics), gridPos, gridRot); - var gridRelativePos = matty.Transform(gridPos); + var gridRelativePos = Vector2.Transform(gridPos, matty); gridRelativePos = gridRelativePos with { Y = -gridRelativePos.Y }; var gridUiPos = ScalePosition(gridRelativePos); @@ -439,7 +439,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl var color = ftlFree ? Color.LimeGreen : Color.Magenta; - var gridRelativePos = matty.Transform(gridPos); + var gridRelativePos = Vector2.Transform(gridPos, matty); gridRelativePos = gridRelativePos with { Y = -gridRelativePos.Y }; var gridUiPos = ScalePosition(gridRelativePos); @@ -512,7 +512,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl /// /// Returns the beacons that intersect the viewport. /// - private IEnumerable<(string Beacon, MapCoordinates Coordinates, IMapObject MapObject)> GetBeacons(List mapObjs, Matrix3 mapTransform, UIBox2i area) + private IEnumerable<(string Beacon, MapCoordinates Coordinates, IMapObject MapObject)> GetBeacons(List mapObjs, Matrix3x2 mapTransform, UIBox2i area) { foreach (var mapO in mapObjs) { @@ -520,7 +520,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl continue; var beaconCoords = EntManager.GetCoordinates(beacon.Coordinates).ToMap(EntManager, _xformSystem); - var position = mapTransform.Transform(beaconCoords.Position); + var position = Vector2.Transform(beaconCoords.Position, mapTransform); var localPos = ScalePosition(position with {Y = -position.Y}); // If beacon not on screen then ignore it. @@ -557,7 +557,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl return mapObj; } - private bool TryGetBeacon(IEnumerable mapObjects, Matrix3 mapTransform, Vector2 mousePos, UIBox2i area, out ShuttleBeaconObject foundBeacon, out Vector2 foundLocalPos) + private bool TryGetBeacon(IEnumerable mapObjects, Matrix3x2 mapTransform, Vector2 mousePos, UIBox2i area, out ShuttleBeaconObject foundBeacon, out Vector2 foundLocalPos) { // In pixels const float BeaconSnapRange = 32f; @@ -579,7 +579,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl if (!_shuttles.CanFTLBeacon(beaconObj.Coordinates)) continue; - var position = mapTransform.Transform(beaconCoords.Position); + var position = Vector2.Transform(beaconCoords.Position, mapTransform); var localPos = ScalePosition(position with {Y = -position.Y}); // If beacon not on screen then ignore it. diff --git a/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs b/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs index 00ee6890b2..0b8720add2 100644 --- a/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs +++ b/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs @@ -137,10 +137,10 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl var mapPos = _transform.ToMapCoordinates(_coordinates.Value); var offset = _coordinates.Value.Position; - var posMatrix = Matrix3.CreateTransform(offset, _rotation.Value); + var posMatrix = Matrix3Helpers.CreateTransform(offset, _rotation.Value); var (_, ourEntRot, ourEntMatrix) = _transform.GetWorldPositionRotationMatrix(_coordinates.Value.EntityId); - Matrix3.Multiply(posMatrix, ourEntMatrix, out var ourWorldMatrix); - var ourWorldMatrixInvert = ourWorldMatrix.Invert(); + var ourWorldMatrix = Matrix3x2.Multiply(posMatrix, ourEntMatrix); + Matrix3x2.Invert(ourWorldMatrix, out var ourWorldMatrixInvert); // Draw our grid in detail var ourGridId = xform.GridUid; @@ -148,7 +148,7 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl fixturesQuery.HasComponent(ourGridId.Value)) { var ourGridMatrix = _transform.GetWorldMatrix(ourGridId.Value); - Matrix3.Multiply(in ourGridMatrix, in ourWorldMatrixInvert, out var matrix); + var matrix = Matrix3x2.Multiply(ourGridMatrix, ourWorldMatrixInvert); var color = _shuttles.GetIFFColor(ourGridId.Value, self: true); DrawGrid(handle, matrix, (ourGridId.Value, ourGrid), color); @@ -194,7 +194,7 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl continue; var gridMatrix = _transform.GetWorldMatrix(gUid); - Matrix3.Multiply(in gridMatrix, in ourWorldMatrixInvert, out var matty); + var matty = Matrix3x2.Multiply(gridMatrix, ourWorldMatrixInvert); var color = _shuttles.GetIFFColor(grid, self: false, iff); // Others default: @@ -207,7 +207,7 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl { var gridBounds = grid.Comp.LocalAABB; - var gridCentre = matty.Transform(gridBody.LocalCenter); + var gridCentre = Vector2.Transform(gridBody.LocalCenter, matty); gridCentre.Y = -gridCentre.Y; var distance = gridCentre.Length(); var labelText = Loc.GetString("shuttle-console-iff-label", ("name", labelName), @@ -242,7 +242,7 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl } } - private void DrawDocks(DrawingHandleScreen handle, EntityUid uid, Matrix3 matrix) + private void DrawDocks(DrawingHandleScreen handle, EntityUid uid, Matrix3x2 matrix) { if (!ShowDocks) return; @@ -255,7 +255,7 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl foreach (var state in docks) { var position = state.Coordinates.Position; - var uiPosition = matrix.Transform(position); + var uiPosition = Vector2.Transform(position, matrix); if (uiPosition.Length() > (WorldRange * 2f) - DockScale) continue; @@ -264,10 +264,10 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl var verts = new[] { - matrix.Transform(position + new Vector2(-DockScale, -DockScale)), - matrix.Transform(position + new Vector2(DockScale, -DockScale)), - matrix.Transform(position + new Vector2(DockScale, DockScale)), - matrix.Transform(position + new Vector2(-DockScale, DockScale)), + Vector2.Transform(position + new Vector2(-DockScale, -DockScale), matrix), + Vector2.Transform(position + new Vector2(DockScale, -DockScale), matrix), + Vector2.Transform(position + new Vector2(DockScale, DockScale), matrix), + Vector2.Transform(position + new Vector2(-DockScale, DockScale), matrix), }; for (var i = 0; i < verts.Length; i++) diff --git a/Content.Client/StatusIcon/StatusIconOverlay.cs b/Content.Client/StatusIcon/StatusIconOverlay.cs index 56107cbc02..4b3daae22f 100644 --- a/Content.Client/StatusIcon/StatusIconOverlay.cs +++ b/Content.Client/StatusIcon/StatusIconOverlay.cs @@ -39,13 +39,13 @@ public sealed class StatusIconOverlay : Overlay var eyeRot = args.Viewport.Eye?.Rotation ?? default; var xformQuery = _entity.GetEntityQuery(); - var scaleMatrix = Matrix3.CreateScale(new Vector2(1, 1)); - var rotationMatrix = Matrix3.CreateRotation(-eyeRot); + var scaleMatrix = Matrix3Helpers.CreateScale(new Vector2(1, 1)); + var rotationMatrix = Matrix3Helpers.CreateRotation(-eyeRot); var query = _entity.AllEntityQueryEnumerator(); while (query.MoveNext(out var uid, out var comp, out var sprite, out var xform, out var meta)) { - if (xform.MapID != args.MapId) + if (xform.MapID != args.MapId || !sprite.Visible) continue; var bounds = comp.Bounds ?? sprite.Bounds; @@ -59,9 +59,9 @@ public sealed class StatusIconOverlay : Overlay if (icons.Count == 0) continue; - var worldMatrix = Matrix3.CreateTranslation(worldPos); - Matrix3.Multiply(scaleMatrix, worldMatrix, out var scaledWorld); - Matrix3.Multiply(rotationMatrix, scaledWorld, out var matty); + var worldMatrix = Matrix3Helpers.CreateTranslation(worldPos); + var scaledWorld = Matrix3x2.Multiply(scaleMatrix, worldMatrix); + var matty = Matrix3x2.Multiply(rotationMatrix, scaledWorld); handle.SetTransform(matty); var countL = 0; @@ -72,6 +72,8 @@ public sealed class StatusIconOverlay : Overlay foreach (var proto in icons) { + if (!_statusIcon.IsVisible((uid, meta), proto)) + continue; var curTime = _timing.RealTime; var texture = _sprite.GetFrame(proto.Icon, curTime); diff --git a/Content.Client/StatusIcon/StatusIconSystem.cs b/Content.Client/StatusIcon/StatusIconSystem.cs index 980fd9f2a9..63f5776769 100644 --- a/Content.Client/StatusIcon/StatusIconSystem.cs +++ b/Content.Client/StatusIcon/StatusIconSystem.cs @@ -1,7 +1,11 @@ using Content.Shared.CCVar; +using Content.Shared.Ghost; using Content.Shared.StatusIcon; using Content.Shared.StatusIcon.Components; +using Content.Shared.Stealth.Components; +using Content.Shared.Whitelist; using Robust.Client.Graphics; +using Robust.Client.Player; using Robust.Shared.Configuration; namespace Content.Client.StatusIcon; @@ -13,6 +17,8 @@ public sealed class StatusIconSystem : SharedStatusIconSystem { [Dependency] private readonly IConfigurationManager _configuration = default!; [Dependency] private readonly IOverlayManager _overlay = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly EntityWhitelistSystem _entityWhitelist = default!; private bool _globalEnabled; private bool _localEnabled; @@ -54,10 +60,34 @@ public sealed class StatusIconSystem : SharedStatusIconSystem if (meta.EntityLifeStage >= EntityLifeStage.Terminating) return list; - var inContainer = (meta.Flags & MetaDataFlags.InContainer) != 0; - var ev = new GetStatusIconsEvent(list, inContainer); + var ev = new GetStatusIconsEvent(list); RaiseLocalEvent(uid, ref ev); return ev.StatusIcons; } -} + /// + /// For overlay to check if an entity can be seen. + /// + public bool IsVisible(Entity ent, StatusIconData data) + { + var viewer = _playerManager.LocalSession?.AttachedEntity; + + // Always show our icons to our entity + if (viewer == ent.Owner) + return true; + + if (data.VisibleToGhosts && HasComp(viewer)) + return true; + + if (data.HideInContainer && (ent.Comp.Flags & MetaDataFlags.InContainer) != 0) + return false; + + if (data.HideOnStealth && TryComp(ent, out var stealth) && stealth.Enabled) + return false; + + if (data.ShowTo != null && !_entityWhitelist.IsValid(data.ShowTo, viewer)) + return false; + + return true; + } +} diff --git a/Content.Client/Stealth/StealthSystem.cs b/Content.Client/Stealth/StealthSystem.cs index b60ffc2a40..0b94e41f6b 100644 --- a/Content.Client/Stealth/StealthSystem.cs +++ b/Content.Client/Stealth/StealthSystem.cs @@ -1,4 +1,5 @@ using Content.Client.Interactable.Components; +using Content.Client.StatusIcon; using Content.Shared.Stealth; using Content.Shared.Stealth.Components; using Robust.Client.GameObjects; @@ -18,6 +19,7 @@ public sealed class StealthSystem : SharedStealthSystem base.Initialize(); _shader = _protoMan.Index("Stealth").InstanceUnique(); + SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(OnShaderRender); @@ -93,4 +95,3 @@ public sealed class StealthSystem : SharedStealthSystem args.Sprite.Color = new Color(visibility, visibility, 1, 1); } } - diff --git a/Content.Client/Storage/Systems/StorageSystem.cs b/Content.Client/Storage/Systems/StorageSystem.cs index 8bf0dcd981..b80a855f98 100644 --- a/Content.Client/Storage/Systems/StorageSystem.cs +++ b/Content.Client/Storage/Systems/StorageSystem.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Linq; +using System.Numerics; using Content.Client.Animations; using Content.Shared.Hands; using Content.Shared.Storage; @@ -149,7 +150,7 @@ public sealed class StorageSystem : SharedStorageSystem } var finalMapPos = finalCoords.ToMapPos(EntityManager, TransformSystem); - var finalPos = TransformSystem.GetInvWorldMatrix(initialCoords.EntityId).Transform(finalMapPos); + var finalPos = Vector2.Transform(finalMapPos, TransformSystem.GetInvWorldMatrix(initialCoords.EntityId)); _entityPickupAnimation.AnimateEntityPickup(item, initialCoords, finalPos, initialAngle); } diff --git a/Content.Client/Store/Ui/StoreBoundUserInterface.cs b/Content.Client/Store/Ui/StoreBoundUserInterface.cs index 88ad0e3de8..0010aedd96 100644 --- a/Content.Client/Store/Ui/StoreBoundUserInterface.cs +++ b/Content.Client/Store/Ui/StoreBoundUserInterface.cs @@ -1,6 +1,7 @@ using Content.Shared.Store; using JetBrains.Annotations; using System.Linq; +using Content.Shared.Store.Components; using Robust.Shared.Prototypes; namespace Content.Client.Store.Ui; @@ -13,9 +14,6 @@ public sealed class StoreBoundUserInterface : BoundUserInterface [ViewVariables] private StoreMenu? _menu; - [ViewVariables] - private string _windowName = Loc.GetString("store-ui-default-title"); - [ViewVariables] private string _search = string.Empty; @@ -28,7 +26,9 @@ public sealed class StoreBoundUserInterface : BoundUserInterface protected override void Open() { - _menu = new StoreMenu(_windowName); + _menu = new StoreMenu(); + if (EntMan.TryGetComponent(Owner, out var store)) + _menu.Title = Loc.GetString(store.Name); _menu.OpenCentered(); _menu.OnClose += Close; @@ -64,25 +64,15 @@ public sealed class StoreBoundUserInterface : BoundUserInterface { base.UpdateState(state); - if (_menu == null) - return; - switch (state) { case StoreUpdateState msg: _listings = msg.Listings; - _menu.UpdateBalance(msg.Balance); + _menu?.UpdateBalance(msg.Balance); UpdateListingsWithSearchFilter(); - _menu.SetFooterVisibility(msg.ShowFooter); - _menu.UpdateRefund(msg.AllowRefund); - break; - case StoreInitializeState msg: - _windowName = msg.Name; - if (_menu != null && _menu.Window != null) - { - _menu.Window.Title = msg.Name; - } + _menu?.SetFooterVisibility(msg.ShowFooter); + _menu?.UpdateRefund(msg.AllowRefund); break; } } diff --git a/Content.Client/Store/Ui/StoreMenu.xaml.cs b/Content.Client/Store/Ui/StoreMenu.xaml.cs index b7a2c285fe..388b31291c 100644 --- a/Content.Client/Store/Ui/StoreMenu.xaml.cs +++ b/Content.Client/Store/Ui/StoreMenu.xaml.cs @@ -32,7 +32,7 @@ public sealed partial class StoreMenu : DefaultWindow private List _cachedListings = new(); - public StoreMenu(string name) + public StoreMenu() { RobustXamlLoader.Load(this); IoCManager.InjectDependencies(this); @@ -40,9 +40,6 @@ public sealed partial class StoreMenu : DefaultWindow WithdrawButton.OnButtonDown += OnWithdrawButtonDown; RefundButton.OnButtonDown += OnRefundButtonDown; SearchBar.OnTextChanged += _ => SearchTextUpdated?.Invoke(this, SearchBar.Text); - - if (Window != null) - Window.Title = name; } public void UpdateBalance(Dictionary, FixedPoint2> balance) diff --git a/Content.Client/UserInterface/Controls/DirectionIcon.cs b/Content.Client/UserInterface/Controls/DirectionIcon.cs index a6cc428091..c8fd63b43c 100644 --- a/Content.Client/UserInterface/Controls/DirectionIcon.cs +++ b/Content.Client/UserInterface/Controls/DirectionIcon.cs @@ -65,7 +65,7 @@ public sealed class DirectionIcon : TextureRect if (_rotation != null) { var offset = (-_rotation.Value).RotateVec(Size * UIScale / 2) - Size * UIScale / 2; - handle.SetTransform(Matrix3.CreateTransform(GlobalPixelPosition - offset, -_rotation.Value)); + handle.SetTransform(Matrix3Helpers.CreateTransform(GlobalPixelPosition - offset, -_rotation.Value)); } base.Draw(handle); diff --git a/Content.Client/UserInterface/Controls/MapGridControl.xaml.cs b/Content.Client/UserInterface/Controls/MapGridControl.xaml.cs index f6b0929f3b..a10155f3e8 100644 --- a/Content.Client/UserInterface/Controls/MapGridControl.xaml.cs +++ b/Content.Client/UserInterface/Controls/MapGridControl.xaml.cs @@ -169,7 +169,7 @@ public partial class MapGridControl : LayoutContainer var inversePos = (value - MidPointVector) / MinimapScale; inversePos = inversePos with { Y = -inversePos.Y }; - inversePos = Matrix3.CreateTransform(Offset, Angle.Zero).Transform(inversePos); + inversePos = Vector2.Transform(inversePos, Matrix3Helpers.CreateTransform(Offset, Angle.Zero)); return inversePos; } diff --git a/Content.Client/UserInterface/Controls/TableContainer.cs b/Content.Client/UserInterface/Controls/TableContainer.cs new file mode 100644 index 0000000000..3e8d476001 --- /dev/null +++ b/Content.Client/UserInterface/Controls/TableContainer.cs @@ -0,0 +1,285 @@ +using System.Numerics; +using Robust.Client.UserInterface.Controls; + +namespace Content.Client.UserInterface.Controls; + +// This control is not part of engine because I quickly wrote it in 2 hours at 2 AM and don't want to deal with +// API stabilization and/or figuring out relation to GridContainer. +// Grid layout is a complicated problem and I don't want to commit another half-baked thing into the engine. +// It's probably sufficient for its use case (RichTextLabel tables for rules/guidebook). +// Despite that, it's still better comment the shit half of you write on a regular basis. +// +// EMO: thank you PJB i was going to kill myself. + +/// +/// Displays children in a tabular grid. Unlike , +/// properly handles layout constraints so putting word-wrapping in it should work. +/// +/// +/// All children are automatically laid out in columns. +/// The first control is in the top left, laid out per row from there. +/// +[Virtual] +public class TableContainer : Container +{ + private int _columns = 1; + + /// + /// The absolute minimum width a column can be forced to. + /// + /// + /// + /// If a column *asks* for less width than this (small contents), it can still be smaller. + /// But if it asks for more it cannot go below this width. + /// + /// + public float MinForcedColumnWidth { get; set; } = 50; + + // Scratch space used while calculating layout, cached to avoid regular allocations during layout pass. + private ColumnData[] _columnDataCache = []; + private RowData[] _rowDataCache = []; + + /// + /// How many columns should be displayed. + /// + public int Columns + { + get => _columns; + set + { + ArgumentOutOfRangeException.ThrowIfLessThan(value, 1, nameof(value)); + + _columns = value; + } + } + + protected override Vector2 MeasureOverride(Vector2 availableSize) + { + ResetCachedArrays(); + + // Do a first pass measuring all child controls as if they're given infinite space. + // This gives us a maximum width the columns want, which we use to proportion them later. + var columnIdx = 0; + foreach (var child in Children) + { + ref var column = ref _columnDataCache[columnIdx]; + + child.Measure(new Vector2(float.PositiveInfinity, float.PositiveInfinity)); + column.MaxWidth = Math.Max(column.MaxWidth, child.DesiredSize.X); + + columnIdx += 1; + if (columnIdx == _columns) + columnIdx = 0; + } + + // Calculate Slack and MinWidth for all columns. Also calculate sums for all columns. + var totalMinWidth = 0f; + var totalMaxWidth = 0f; + var totalSlack = 0f; + + for (var c = 0; c < _columns; c++) + { + ref var column = ref _columnDataCache[c]; + column.MinWidth = Math.Min(column.MaxWidth, MinForcedColumnWidth); + column.Slack = column.MaxWidth - column.MinWidth; + + totalMinWidth += column.MinWidth; + totalMaxWidth += column.MaxWidth; + totalSlack += column.Slack; + } + + if (totalMaxWidth <= availableSize.X) + { + // We want less horizontal space than we're given. Huh, that's convenient. + // Just set assigned width to be however much they asked for. + // We could probably skip the second measure pass in this scenario, + // but that's just an optimization, so I don't care right now. + // + // There's probably a very clever way to make this behavior work with the else block of logic, + // just by fiddling with the math. + // I'm dumb, it's 4:30 AM. Yeah, I *started* at 2 AM. + for (var c = 0; c < _columns; c++) + { + ref var column = ref _columnDataCache[c]; + + column.AssignedWidth = column.MaxWidth; + } + } + else + { + // We don't have enough horizontal space, + // at least without causing *some* sort of word wrapping (assuming text contents). + // + // Assign horizontal space proportional to the wanted maximum size of the columns. + var assignableWidth = Math.Max(0, availableSize.X - totalMinWidth); + for (var c = 0; c < _columns; c++) + { + ref var column = ref _columnDataCache[c]; + + var slackRatio = column.Slack / totalSlack; + column.AssignedWidth = column.MinWidth + slackRatio * assignableWidth; + } + } + + // Go over controls for a second measuring pass, this time giving them their assigned measure width. + // This will give us a height to slot into per-row data. + // We still measure assuming infinite vertical space. + // This control can't properly handle being constrained on the Y axis. + columnIdx = 0; + var rowIdx = 0; + foreach (var child in Children) + { + ref var column = ref _columnDataCache[columnIdx]; + ref var row = ref _rowDataCache[rowIdx]; + + child.Measure(new Vector2(column.AssignedWidth, float.PositiveInfinity)); + row.MeasuredHeight = Math.Max(row.MeasuredHeight, child.DesiredSize.Y); + + columnIdx += 1; + if (columnIdx == _columns) + { + columnIdx = 0; + rowIdx += 1; + } + } + + // Sum up height of all rows to get final measured table height. + var totalHeight = 0f; + for (var r = 0; r < _rowDataCache.Length; r++) + { + ref var row = ref _rowDataCache[r]; + totalHeight += row.MeasuredHeight; + } + + return new Vector2(Math.Min(availableSize.X, totalMaxWidth), totalHeight); + } + + protected override Vector2 ArrangeOverride(Vector2 finalSize) + { + // TODO: Expand to fit given vertical space. + + // Calculate MinWidth and Slack sums again from column data. + // We could've cached these from measure but whatever. + var totalMinWidth = 0f; + var totalSlack = 0f; + + for (var c = 0; c < _columns; c++) + { + ref var column = ref _columnDataCache[c]; + totalMinWidth += column.MinWidth; + totalSlack += column.Slack; + } + + // Calculate new width based on final given size, also assign horizontal positions of all columns. + var assignableWidth = Math.Max(0, finalSize.X - totalMinWidth); + var xPos = 0f; + for (var c = 0; c < _columns; c++) + { + ref var column = ref _columnDataCache[c]; + + var slackRatio = column.Slack / totalSlack; + column.ArrangedWidth = column.MinWidth + slackRatio * assignableWidth; + column.ArrangedX = xPos; + + xPos += column.ArrangedWidth; + } + + // Do actual arrangement row-by-row. + var arrangeY = 0f; + for (var r = 0; r < _rowDataCache.Length; r++) + { + ref var row = ref _rowDataCache[r]; + + for (var c = 0; c < _columns; c++) + { + ref var column = ref _columnDataCache[c]; + var index = c + r * _columns; + + if (index >= ChildCount) // Quit early if we don't actually fill out the row. + break; + var child = GetChild(c + r * _columns); + + child.Arrange(UIBox2.FromDimensions(column.ArrangedX, arrangeY, column.ArrangedWidth, row.MeasuredHeight)); + } + + arrangeY += row.MeasuredHeight; + } + + return finalSize with { Y = arrangeY }; + } + + /// + /// Ensure cached array space is allocated to correct size and is reset to a clean slate. + /// + private void ResetCachedArrays() + { + // 1-argument Array.Clear() is not currently available in sandbox (added in .NET 6). + + if (_columnDataCache.Length != _columns) + _columnDataCache = new ColumnData[_columns]; + + Array.Clear(_columnDataCache, 0, _columnDataCache.Length); + + var rowCount = ChildCount / _columns; + if (ChildCount % _columns != 0) + rowCount += 1; + + if (rowCount != _rowDataCache.Length) + _rowDataCache = new RowData[rowCount]; + + Array.Clear(_rowDataCache, 0, _rowDataCache.Length); + } + + /// + /// Per-column data used during layout. + /// + private struct ColumnData + { + // Measure data. + + /// + /// The maximum width any control in this column wants, if given infinite space. + /// Maximum of all controls on the column. + /// + public float MaxWidth; + + /// + /// The minimum width this column may be given. + /// This is either or . + /// + public float MinWidth; + + /// + /// Difference between max and min width; how much this column can expand from its minimum. + /// + public float Slack; + + /// + /// How much horizontal space this column was assigned at measure time. + /// + public float AssignedWidth; + + // Arrange data. + + /// + /// How much horizontal space this column was assigned at arrange time. + /// + public float ArrangedWidth; + + /// + /// The horizontal position this column was assigned at arrange time. + /// + public float ArrangedX; + } + + private struct RowData + { + // Measure data. + + /// + /// How much height the tallest control on this row was measured at, + /// measuring for infinite vertical space but assigned column width. + /// + public float MeasuredHeight; + } +} diff --git a/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs b/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs index 6be41af0d8..0d12d87171 100644 --- a/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs +++ b/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs @@ -289,6 +289,10 @@ public sealed class ActionButton : Control, IEntityControl { if (_action.IconOn != null) SetActionIcon(_spriteSys.Frame0(_action.IconOn)); + else if (_action.Icon != null) + SetActionIcon(_spriteSys.Frame0(_action.Icon)); + else + SetActionIcon(null); if (_action.BackgroundOn != null) _buttonBackgroundTexture = _spriteSys.Frame0(_action.BackgroundOn); diff --git a/Content.Client/UserInterface/Systems/Admin/AdminUIController.cs b/Content.Client/UserInterface/Systems/Admin/AdminUIController.cs index a7397aff38..3d8235591a 100644 --- a/Content.Client/UserInterface/Systems/Admin/AdminUIController.cs +++ b/Content.Client/UserInterface/Systems/Admin/AdminUIController.cs @@ -198,9 +198,12 @@ public sealed class AdminUIController : UIController, args.Handle(); } - private void ObjectsTabEntryKeyBindDown(ObjectsTabEntry entry, GUIBoundKeyEventArgs args) + private void ObjectsTabEntryKeyBindDown(GUIBoundKeyEventArgs args, ListData? data) { - var uid = entry.AssocEntity; + if (data is not ObjectsListData { Info: var info }) + return; + + var uid = info.Entity; var function = args.Function; if (function == EngineKeyFunctions.UIClick) diff --git a/Content.Client/UserInterface/Systems/Atmos/GasTank/GasTankBoundUserInterface.cs b/Content.Client/UserInterface/Systems/Atmos/GasTank/GasTankBoundUserInterface.cs index ee8cb28d2c..4702f8f365 100644 --- a/Content.Client/UserInterface/Systems/Atmos/GasTank/GasTankBoundUserInterface.cs +++ b/Content.Client/UserInterface/Systems/Atmos/GasTank/GasTankBoundUserInterface.cs @@ -1,6 +1,5 @@ using Content.Shared.Atmos.Components; using JetBrains.Annotations; -using Robust.Client.GameObjects; namespace Content.Client.UserInterface.Systems.Atmos.GasTank { @@ -30,7 +29,7 @@ namespace Content.Client.UserInterface.Systems.Atmos.GasTank protected override void Open() { base.Open(); - _window = new GasTankWindow(this); + _window = new GasTankWindow(this, EntMan.GetComponent(Owner).EntityName); _window.OnClose += Close; _window.OpenCentered(); } diff --git a/Content.Client/UserInterface/Systems/Atmos/GasTank/GasTankWindow.cs b/Content.Client/UserInterface/Systems/Atmos/GasTank/GasTankWindow.cs index 7797a096de..c23850a650 100644 --- a/Content.Client/UserInterface/Systems/Atmos/GasTank/GasTankWindow.cs +++ b/Content.Client/UserInterface/Systems/Atmos/GasTank/GasTankWindow.cs @@ -10,201 +10,194 @@ using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; using static Robust.Client.UserInterface.Controls.BoxContainer; -namespace Content.Client.UserInterface.Systems.Atmos.GasTank +namespace Content.Client.UserInterface.Systems.Atmos.GasTank; + +public sealed class GasTankWindow + : BaseWindow { - public sealed class GasTankWindow - : BaseWindow + private readonly RichTextLabel _lblPressure; + private readonly FloatSpinBox _spbPressure; + private readonly RichTextLabel _lblInternals; + private readonly Button _btnInternals; + + public GasTankWindow(GasTankBoundUserInterface owner, string uidName) { - private GasTankBoundUserInterface _owner; - private readonly Label _lblName; - private readonly BoxContainer _topContainer; - private readonly Control _contentContainer; + Control contentContainer; + BoxContainer topContainer; + TextureButton btnClose; + var resourceCache = IoCManager.Resolve(); + var rootContainer = new LayoutContainer { Name = "GasTankRoot" }; + AddChild(rootContainer); + MouseFilter = MouseFilterMode.Stop; - private readonly IResourceCache _resourceCache = default!; - private readonly RichTextLabel _lblPressure; - private readonly FloatSpinBox _spbPressure; - private readonly RichTextLabel _lblInternals; - private readonly Button _btnInternals; - - public GasTankWindow(GasTankBoundUserInterface owner) + var panelTex = resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png"); + var back = new StyleBoxTexture { - TextureButton btnClose; - _resourceCache = IoCManager.Resolve(); - _owner = owner; - var rootContainer = new LayoutContainer {Name = "GasTankRoot"}; - AddChild(rootContainer); + Texture = panelTex, + Modulate = Color.FromHex("#25252A"), + }; - MouseFilter = MouseFilterMode.Stop; + back.SetPatchMargin(StyleBox.Margin.All, 10); - var panelTex = _resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png"); - var back = new StyleBoxTexture + var topPanel = new PanelContainer + { + PanelOverride = back, + MouseFilter = MouseFilterMode.Pass + }; + + var bottomWrap = new LayoutContainer + { + Name = "BottomWrap" + }; + + rootContainer.AddChild(topPanel); + rootContainer.AddChild(bottomWrap); + + LayoutContainer.SetAnchorPreset(topPanel, LayoutContainer.LayoutPreset.Wide); + LayoutContainer.SetMarginBottom(topPanel, -85); + + LayoutContainer.SetAnchorPreset(bottomWrap, LayoutContainer.LayoutPreset.VerticalCenterWide); + LayoutContainer.SetGrowHorizontal(bottomWrap, LayoutContainer.GrowDirection.Both); + + + var topContainerWrap = new BoxContainer + { + Orientation = LayoutOrientation.Vertical, + Children = { - Texture = panelTex, - Modulate = Color.FromHex("#25252A"), - }; - - back.SetPatchMargin(StyleBox.Margin.All, 10); - - var topPanel = new PanelContainer - { - PanelOverride = back, - MouseFilter = MouseFilterMode.Pass - }; - - var bottomWrap = new LayoutContainer - { - Name = "BottomWrap" - }; - - rootContainer.AddChild(topPanel); - rootContainer.AddChild(bottomWrap); - - LayoutContainer.SetAnchorPreset(topPanel, LayoutContainer.LayoutPreset.Wide); - LayoutContainer.SetMarginBottom(topPanel, -85); - - LayoutContainer.SetAnchorPreset(bottomWrap, LayoutContainer.LayoutPreset.VerticalCenterWide); - LayoutContainer.SetGrowHorizontal(bottomWrap, LayoutContainer.GrowDirection.Both); - - - var topContainerWrap = new BoxContainer - { - Orientation = LayoutOrientation.Vertical, - Children = + (topContainer = new BoxContainer { - (_topContainer = new BoxContainer - { - Orientation = LayoutOrientation.Vertical - }), - new Control {MinSize = new Vector2(0, 110)} - } - }; + Orientation = LayoutOrientation.Vertical + }), + new Control {MinSize = new Vector2(0, 110)} + } + }; - rootContainer.AddChild(topContainerWrap); + rootContainer.AddChild(topContainerWrap); - LayoutContainer.SetAnchorPreset(topContainerWrap, LayoutContainer.LayoutPreset.Wide); + LayoutContainer.SetAnchorPreset(topContainerWrap, LayoutContainer.LayoutPreset.Wide); - var font = _resourceCache.GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 13); + var font = resourceCache.GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 13); - var topRow = new BoxContainer + var topRow = new BoxContainer + { + Orientation = LayoutOrientation.Horizontal, + Margin = new Thickness(4, 2, 12, 2), + Children = + { + (new Label + { + Text = uidName, + FontOverride = font, + FontColorOverride = StyleNano.NanoGold, + VerticalAlignment = VAlignment.Center, + HorizontalExpand = true, + HorizontalAlignment = HAlignment.Left, + Margin = new Thickness(0, 0, 20, 0), + }), + (btnClose = new TextureButton + { + StyleClasses = {DefaultWindow.StyleClassWindowCloseButton}, + VerticalAlignment = VAlignment.Center + }) + } + }; + + var middle = new PanelContainer + { + PanelOverride = new StyleBoxFlat { BackgroundColor = Color.FromHex("#202025") }, + Children = + { + (contentContainer = new BoxContainer + { + Orientation = LayoutOrientation.Vertical, + Margin = new Thickness(8, 4), + }) + } + }; + + topContainer.AddChild(topRow); + topContainer.AddChild(new PanelContainer + { + MinSize = new Vector2(0, 2), + PanelOverride = new StyleBoxFlat { BackgroundColor = Color.FromHex("#525252ff") } + }); + topContainer.AddChild(middle); + topContainer.AddChild(new PanelContainer + { + MinSize = new Vector2(0, 2), + PanelOverride = new StyleBoxFlat { BackgroundColor = Color.FromHex("#525252ff") } + }); + + + _lblPressure = new RichTextLabel(); + contentContainer.AddChild(_lblPressure); + + //internals + _lblInternals = new RichTextLabel + { MinSize = new Vector2(200, 0), VerticalAlignment = VAlignment.Center }; + _btnInternals = new Button { Text = Loc.GetString("gas-tank-window-internals-toggle-button") }; + + contentContainer.AddChild( + new BoxContainer { Orientation = LayoutOrientation.Horizontal, - Margin = new Thickness(4, 2, 12, 2), - Children = - { - (_lblName = new Label - { - Text = Loc.GetString("gas-tank-window-label"), - FontOverride = font, - FontColorOverride = StyleNano.NanoGold, - VerticalAlignment = VAlignment.Center, - HorizontalExpand = true, - HorizontalAlignment = HAlignment.Left, - Margin = new Thickness(0, 0, 20, 0), - }), - (btnClose = new TextureButton - { - StyleClasses = {DefaultWindow.StyleClassWindowCloseButton}, - VerticalAlignment = VAlignment.Center - }) - } - }; - - var middle = new PanelContainer - { - PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#202025")}, - Children = - { - (_contentContainer = new BoxContainer - { - Orientation = LayoutOrientation.Vertical, - Margin = new Thickness(8, 4), - }) - } - }; - - _topContainer.AddChild(topRow); - _topContainer.AddChild(new PanelContainer - { - MinSize = new Vector2(0, 2), - PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#525252ff")} - }); - _topContainer.AddChild(middle); - _topContainer.AddChild(new PanelContainer - { - MinSize = new Vector2(0, 2), - PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#525252ff")} + Margin = new Thickness(0, 7, 0, 0), + Children = { _lblInternals, _btnInternals } }); - - _lblPressure = new RichTextLabel(); - _contentContainer.AddChild(_lblPressure); - - //internals - _lblInternals = new RichTextLabel - {MinSize = new Vector2(200, 0), VerticalAlignment = VAlignment.Center}; - _btnInternals = new Button {Text = Loc.GetString("gas-tank-window-internals-toggle-button") }; - - _contentContainer.AddChild( - new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - Margin = new Thickness(0, 7, 0, 0), - Children = {_lblInternals, _btnInternals} - }); - - // Separator - _contentContainer.AddChild(new Control - { - MinSize = new Vector2(0, 10) - }); - - _contentContainer.AddChild(new Label - { - Text = Loc.GetString("gas-tank-window-output-pressure-label"), - Align = Label.AlignMode.Center - }); - _spbPressure = new FloatSpinBox - { - IsValid = f => f >= 0 || f <= 3000, - Margin = new Thickness(25, 0, 25, 7) - }; - _contentContainer.AddChild(_spbPressure); - - // Handlers - _spbPressure.OnValueChanged += args => - { - _owner.SetOutputPressure(args.Value); - }; - - _btnInternals.OnPressed += args => - { - _owner.ToggleInternals(); - }; - - btnClose.OnPressed += _ => Close(); - } - - public void UpdateState(GasTankBoundUserInterfaceState state) + // Separator + contentContainer.AddChild(new Control { - _lblPressure.SetMarkup(Loc.GetString("gas-tank-window-tank-pressure-text", ("tankPressure", $"{state.TankPressure:0.##}"))); - _btnInternals.Disabled = !state.CanConnectInternals; - _lblInternals.SetMarkup(Loc.GetString("gas-tank-window-internal-text", - ("status", Loc.GetString(state.InternalsConnected ? "gas-tank-window-internal-connected" : "gas-tank-window-internal-disconnected")))); - if (state.OutputPressure.HasValue) - { - _spbPressure.Value = state.OutputPressure.Value; - } - } + MinSize = new Vector2(0, 10) + }); - protected override DragMode GetDragModeFor(Vector2 relativeMousePos) + contentContainer.AddChild(new Label { - return DragMode.Move; - } + Text = Loc.GetString("gas-tank-window-output-pressure-label"), + Align = Label.AlignMode.Center + }); + _spbPressure = new FloatSpinBox + { + IsValid = f => f >= 0 || f <= 3000, + Margin = new Thickness(25, 0, 25, 7) + }; + contentContainer.AddChild(_spbPressure); - protected override bool HasPoint(Vector2 point) + // Handlers + _spbPressure.OnValueChanged += args => { - return false; + owner.SetOutputPressure(args.Value); + }; + + _btnInternals.OnPressed += args => + { + owner.ToggleInternals(); + }; + + btnClose.OnPressed += _ => Close(); + } + + public void UpdateState(GasTankBoundUserInterfaceState state) + { + _lblPressure.SetMarkup(Loc.GetString("gas-tank-window-tank-pressure-text", ("tankPressure", $"{state.TankPressure:0.##}"))); + _btnInternals.Disabled = !state.CanConnectInternals; + _lblInternals.SetMarkup(Loc.GetString("gas-tank-window-internal-text", + ("status", Loc.GetString(state.InternalsConnected ? "gas-tank-window-internal-connected" : "gas-tank-window-internal-disconnected")))); + if (state.OutputPressure.HasValue) + { + _spbPressure.Value = state.OutputPressure.Value; } } + + protected override DragMode GetDragModeFor(Vector2 relativeMousePos) + { + return DragMode.Move; + } + + protected override bool HasPoint(Vector2 point) + { + return false; + } } diff --git a/Content.Client/Verbs/VerbSystem.cs b/Content.Client/Verbs/VerbSystem.cs index 2e6c5f5809..5f1f49e5fd 100644 --- a/Content.Client/Verbs/VerbSystem.cs +++ b/Content.Client/Verbs/VerbSystem.cs @@ -123,7 +123,6 @@ namespace Content.Client.Verbs if ((visibility & MenuVisibility.Invisible) == 0) { var spriteQuery = GetEntityQuery(); - var tagQuery = GetEntityQuery(); for (var i = entities.Count - 1; i >= 0; i--) { @@ -131,7 +130,7 @@ namespace Content.Client.Verbs if (!spriteQuery.TryGetComponent(entity, out var spriteComponent) || !spriteComponent.Visible || - _tagSystem.HasTag(entity, "HideContextMenu", tagQuery)) + _tagSystem.HasTag(entity, "HideContextMenu")) { entities.RemoveSwap(i); } diff --git a/Content.Client/Viewport/ScalingViewport.cs b/Content.Client/Viewport/ScalingViewport.cs index 1fa8f17161..ccd9636d77 100644 --- a/Content.Client/Viewport/ScalingViewport.cs +++ b/Content.Client/Viewport/ScalingViewport.cs @@ -277,8 +277,8 @@ namespace Content.Client.Viewport EnsureViewportCreated(); - var matrix = Matrix3.Invert(GetLocalToScreenMatrix()); - coords = matrix.Transform(coords); + Matrix3x2.Invert(GetLocalToScreenMatrix(), out var matrix); + coords = Vector2.Transform(coords, matrix); return _viewport!.LocalToWorld(coords); } @@ -291,8 +291,8 @@ namespace Content.Client.Viewport EnsureViewportCreated(); - var matrix = Matrix3.Invert(GetLocalToScreenMatrix()); - coords = matrix.Transform(coords); + Matrix3x2.Invert(GetLocalToScreenMatrix(), out var matrix); + coords = Vector2.Transform(coords, matrix); var ev = new PixelToMapEvent(coords, this, _viewport!); _entityManager.EventBus.RaiseEvent(EventSource.Local, ref ev); @@ -311,16 +311,16 @@ namespace Content.Client.Viewport var matrix = GetLocalToScreenMatrix(); - return matrix.Transform(vpLocal); + return Vector2.Transform(vpLocal, matrix); } - public Matrix3 GetWorldToScreenMatrix() + public Matrix3x2 GetWorldToScreenMatrix() { EnsureViewportCreated(); return _viewport!.GetWorldToLocalMatrix() * GetLocalToScreenMatrix(); } - public Matrix3 GetLocalToScreenMatrix() + public Matrix3x2 GetLocalToScreenMatrix() { EnsureViewportCreated(); @@ -329,9 +329,9 @@ namespace Content.Client.Viewport if (scaleFactor.X == 0 || scaleFactor.Y == 0) // Basically a nonsense scenario, at least make sure to return something that can be inverted. - return Matrix3.Identity; + return Matrix3x2.Identity; - return Matrix3.CreateTransform(GlobalPixelPosition + drawBox.TopLeft, 0, scaleFactor); + return Matrix3Helpers.CreateTransform(GlobalPixelPosition + drawBox.TopLeft, 0, scaleFactor); } private void EnsureViewportCreated() diff --git a/Content.Client/Weapons/Melee/MeleeWeaponSystem.Effects.cs b/Content.Client/Weapons/Melee/MeleeWeaponSystem.Effects.cs index baac42d193..3e1a4b1906 100644 --- a/Content.Client/Weapons/Melee/MeleeWeaponSystem.Effects.cs +++ b/Content.Client/Weapons/Melee/MeleeWeaponSystem.Effects.cs @@ -81,7 +81,7 @@ public sealed partial class MeleeWeaponSystem case WeaponArcAnimation.None: var (mapPos, mapRot) = TransformSystem.GetWorldPositionRotation(userXform); var worldPos = mapPos + (mapRot - userXform.LocalRotation).RotateVec(localPos); - var newLocalPos = TransformSystem.GetInvWorldMatrix(xform.ParentUid).Transform(worldPos); + var newLocalPos = Vector2.Transform(worldPos, TransformSystem.GetInvWorldMatrix(xform.ParentUid)); TransformSystem.SetLocalPositionNoLerp(animationUid, newLocalPos, xform); if (arcComponent.Fadeout) _animation.Play(animationUid, GetFadeAnimation(sprite, 0f, 0.15f), FadeAnimationKey); diff --git a/Content.Client/Weapons/Ranged/ItemStatus/BulletRender.cs b/Content.Client/Weapons/Ranged/ItemStatus/BulletRender.cs index 8aea5d7ee6..e6cb596b94 100644 --- a/Content.Client/Weapons/Ranged/ItemStatus/BulletRender.cs +++ b/Content.Client/Weapons/Ranged/ItemStatus/BulletRender.cs @@ -1,4 +1,4 @@ -using System.Numerics; +using System.Numerics; using Content.Client.Resources; using Robust.Client.Graphics; using Robust.Client.ResourceManagement; @@ -53,7 +53,7 @@ public abstract class BaseBulletRenderer : Control { // Scale rendering in this control by UIScale. var currentTransform = handle.GetTransform(); - handle.SetTransform(Matrix3.CreateScale(new Vector2(UIScale)) * currentTransform); + handle.SetTransform(Matrix3Helpers.CreateScale(new Vector2(UIScale)) * currentTransform); var countPerRow = CountPerRow(Size.X); diff --git a/Content.Client/Zombies/ZombieSystem.cs b/Content.Client/Zombies/ZombieSystem.cs index 49b5d6aec1..d250e41850 100644 --- a/Content.Client/Zombies/ZombieSystem.cs +++ b/Content.Client/Zombies/ZombieSystem.cs @@ -1,21 +1,40 @@ using System.Linq; using Content.Shared.Ghost; using Content.Shared.Humanoid; +using Content.Shared.StatusIcon; using Content.Shared.StatusIcon.Components; using Content.Shared.Zombies; using Robust.Client.GameObjects; +using Robust.Shared.Prototypes; namespace Content.Client.Zombies; -public sealed class ZombieSystem : EntitySystem +public sealed class ZombieSystem : SharedZombieSystem { + [Dependency] private readonly IPrototypeManager _prototype = default!; + public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnStartup); - SubscribeLocalEvent(OnCanDisplayStatusIcons); - SubscribeLocalEvent(OnCanDisplayStatusIcons); + SubscribeLocalEvent(GetZombieIcon); + SubscribeLocalEvent(GetInitialInfectedIcon); + } + + private void GetZombieIcon(Entity ent, ref GetStatusIconsEvent args) + { + var iconPrototype = _prototype.Index(ent.Comp.StatusIcon); + args.StatusIcons.Add(iconPrototype); + } + + private void GetInitialInfectedIcon(Entity ent, ref GetStatusIconsEvent args) + { + if (HasComp(ent)) + return; + + var iconPrototype = _prototype.Index(ent.Comp.StatusIcon); + args.StatusIcons.Add(iconPrototype); } private void OnStartup(EntityUid uid, ZombieComponent component, ComponentStartup args) @@ -31,29 +50,4 @@ public sealed class ZombieSystem : EntitySystem sprite.LayerSetColor(i, component.SkinColor); } } - - /// - /// Determines whether a player should be able to see the StatusIcon for zombies. - /// - private void OnCanDisplayStatusIcons(EntityUid uid, ZombieComponent component, ref CanDisplayStatusIconsEvent args) - { - if (HasComp(args.User) || HasComp(args.User) || HasComp(args.User)) - return; - - if (component.IconVisibleToGhost && HasComp(args.User)) - return; - - args.Cancelled = true; - } - - private void OnCanDisplayStatusIcons(EntityUid uid, InitialInfectedComponent component, ref CanDisplayStatusIconsEvent args) - { - if (HasComp(args.User) && !HasComp(args.User)) - return; - - if (component.IconVisibleToGhost && HasComp(args.User)) - return; - - args.Cancelled = true; - } } diff --git a/Content.IntegrationTests/Pair/TestPair.Helpers.cs b/Content.IntegrationTests/Pair/TestPair.Helpers.cs index cc83232a06..588cf0d80e 100644 --- a/Content.IntegrationTests/Pair/TestPair.Helpers.cs +++ b/Content.IntegrationTests/Pair/TestPair.Helpers.cs @@ -7,6 +7,7 @@ using Content.Shared.Preferences; using Content.Shared.Roles; using Robust.Shared.GameObjects; using Robust.Shared.Map; +using Robust.Shared.Network; using Robust.Shared.Prototypes; using Robust.UnitTesting; @@ -133,27 +134,73 @@ public sealed partial class TestPair } /// - /// Helper method for enabling or disabling a antag role + /// Set a user's antag preferences. Modified preferences are automatically reset at the end of the test. /// - public async Task SetAntagPref(ProtoId id, bool value) + public async Task SetAntagPreference(ProtoId id, bool value, NetUserId? user = null) { + user ??= Client.User!.Value; + if (user is not {} userId) + return; + var prefMan = Server.ResolveDependency(); + var prefs = prefMan.GetPreferences(userId); - var prefs = prefMan.GetPreferences(Client.User!.Value); - // what even is the point of ICharacterProfile if we always cast it to HumanoidCharacterProfile to make it usable? - var profile = (HumanoidCharacterProfile) prefs.SelectedCharacter; + // Automatic preference resetting only resets slot 0. + Assert.That(prefs.SelectedCharacterIndex, Is.EqualTo(0)); - Assert.That(profile.AntagPreferences.Contains(id), Is.EqualTo(!value)); + var profile = (HumanoidCharacterProfile) prefs.Characters[0]; var newProfile = profile.WithAntagPreference(id, value); + _modifiedProfiles.Add(userId); + await Server.WaitPost(() => prefMan.SetProfile(userId, 0, newProfile).Wait()); + } - await Server.WaitPost(() => + /// + /// Set a user's job preferences. Modified preferences are automatically reset at the end of the test. + /// + public async Task SetJobPriority(ProtoId id, JobPriority value, NetUserId? user = null) + { + user ??= Client.User!.Value; + if (user is { } userId) + await SetJobPriorities(userId, (id, value)); + } + + /// + public async Task SetJobPriorities(params (ProtoId, JobPriority)[] priorities) + => await SetJobPriorities(Client.User!.Value, priorities); + + /// + public async Task SetJobPriorities(NetUserId user, params (ProtoId, JobPriority)[] priorities) + { + var highCount = priorities.Count(x => x.Item2 == JobPriority.High); + Assert.That(highCount, Is.LessThanOrEqualTo(1), "Cannot have more than one high priority job"); + + var prefMan = Server.ResolveDependency(); + var prefs = prefMan.GetPreferences(user); + var profile = (HumanoidCharacterProfile) prefs.Characters[0]; + var dictionary = new Dictionary, JobPriority>(profile.JobPriorities); + + // Automatic preference resetting only resets slot 0. + Assert.That(prefs.SelectedCharacterIndex, Is.EqualTo(0)); + + if (highCount != 0) { - prefMan.SetProfile(Client.User.Value, prefs.SelectedCharacterIndex, newProfile).Wait(); - }); + foreach (var (key, priority) in dictionary) + { + if (priority == JobPriority.High) + dictionary[key] = JobPriority.Medium; + } + } - // And why the fuck does it always create a new preference and profile object instead of just reusing them? - var newPrefs = prefMan.GetPreferences(Client.User.Value); - var newProf = (HumanoidCharacterProfile) newPrefs.SelectedCharacter; - Assert.That(newProf.AntagPreferences.Contains(id), Is.EqualTo(value)); + foreach (var (job, priority) in priorities) + { + if (priority == JobPriority.Never) + dictionary.Remove(job); + else + dictionary[job] = priority; + } + + var newProfile = profile.WithJobPriorities(dictionary); + _modifiedProfiles.Add(user); + await Server.WaitPost(() => prefMan.SetProfile(user, 0, newProfile).Wait()); } } diff --git a/Content.IntegrationTests/Pair/TestPair.Recycle.cs b/Content.IntegrationTests/Pair/TestPair.Recycle.cs index 8d1e425553..89a9eb6463 100644 --- a/Content.IntegrationTests/Pair/TestPair.Recycle.cs +++ b/Content.IntegrationTests/Pair/TestPair.Recycle.cs @@ -2,10 +2,12 @@ using System.IO; using System.Linq; using Content.Server.GameTicking; +using Content.Server.Preferences.Managers; using Content.Shared.CCVar; using Content.Shared.GameTicking; using Content.Shared.Mind; using Content.Shared.Mind.Components; +using Content.Shared.Preferences; using Robust.Client; using Robust.Server.Player; using Robust.Shared.Exceptions; @@ -34,6 +36,11 @@ public sealed partial class TestPair : IAsyncDisposable private async Task OnCleanDispose() { + await Server.WaitIdleAsync(); + await Client.WaitIdleAsync(); + await ResetModifiedPreferences(); + await Server.RemoveAllDummySessions(); + if (TestMap != null) { await Server.WaitPost(() => Server.EntMan.DeleteEntity(TestMap.MapUid)); @@ -79,6 +86,16 @@ public sealed partial class TestPair : IAsyncDisposable await _testOut.WriteLineAsync($"{nameof(CleanReturnAsync)}: PoolManager took {returnTime.TotalMilliseconds} ms to put pair {Id} back into the pool"); } + private async Task ResetModifiedPreferences() + { + var prefMan = Server.ResolveDependency(); + foreach (var user in _modifiedProfiles) + { + await Server.WaitPost(() => prefMan.SetProfile(user, 0, new HumanoidCharacterProfile()).Wait()); + } + _modifiedProfiles.Clear(); + } + public async ValueTask CleanReturnAsync() { if (State != PairState.InUse) diff --git a/Content.IntegrationTests/Pair/TestPair.cs b/Content.IntegrationTests/Pair/TestPair.cs index 7ee5dbd55c..0b681dcde1 100644 --- a/Content.IntegrationTests/Pair/TestPair.cs +++ b/Content.IntegrationTests/Pair/TestPair.cs @@ -26,6 +26,8 @@ public sealed partial class TestPair public readonly List TestHistory = new(); public PoolSettings Settings = default!; public TestMapData? TestMap; + private List _modifiedProfiles = new(); + public RobustIntegrationTest.ServerIntegrationInstance Server { get; private set; } = default!; public RobustIntegrationTest.ClientIntegrationInstance Client { get; private set; } = default!; @@ -37,7 +39,8 @@ public sealed partial class TestPair client = Client; } - public ICommonSession? Player => Server.PlayerMan.Sessions.FirstOrDefault(); + public ICommonSession? Player => Server.PlayerMan.SessionsDict.GetValueOrDefault(Client.User!.Value); + public ContentPlayerData? PlayerData => Player?.Data.ContentData(); public PoolTestLogHandler ServerLogHandler { get; private set; } = default!; diff --git a/Content.IntegrationTests/PoolManager.Cvars.cs b/Content.IntegrationTests/PoolManager.Cvars.cs index 5acd9d502c..bcd48f8238 100644 --- a/Content.IntegrationTests/PoolManager.Cvars.cs +++ b/Content.IntegrationTests/PoolManager.Cvars.cs @@ -28,6 +28,7 @@ public static partial class PoolManager (CCVars.EmergencyShuttleEnabled.Name, "false"), (CCVars.ProcgenPreload.Name, "false"), (CCVars.WorldgenEnabled.Name, "false"), + (CCVars.GatewayGeneratorEnabled.Name, "false"), (CVars.ReplayClientRecordingEnabled.Name, "false"), (CVars.ReplayServerRecordingEnabled.Name, "false"), (CCVars.GameDummyTicker.Name, "true"), diff --git a/Content.IntegrationTests/Tests/CargoTest.cs b/Content.IntegrationTests/Tests/CargoTest.cs index 535aeecec8..2da84f4f9d 100644 --- a/Content.IntegrationTests/Tests/CargoTest.cs +++ b/Content.IntegrationTests/Tests/CargoTest.cs @@ -3,8 +3,13 @@ using System.Linq; using System.Numerics; using Content.Server.Cargo.Components; using Content.Server.Cargo.Systems; +using Content.Server.Nutrition.Components; +using Content.Server.Nutrition.EntitySystems; using Content.Shared.Cargo.Prototypes; +using Content.Shared.IdentityManagement; using Content.Shared.Stacks; +using Content.Shared.Tag; +using Content.Shared.Whitelist; using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Prototypes; @@ -149,6 +154,80 @@ public sealed class CargoTest await pair.CleanReturnAsync(); } + /// + /// Tests to see if any items that are valid for cargo bounties can be sliced into items that + /// are also valid for the same bounty entry. + /// + [Test] + public async Task NoSliceableBountyArbitrageTest() + { + await using var pair = await PoolManager.GetServerClient(); + var server = pair.Server; + + var testMap = await pair.CreateTestMap(); + + var entManager = server.ResolveDependency(); + var mapManager = server.ResolveDependency(); + var protoManager = server.ResolveDependency(); + var componentFactory = server.ResolveDependency(); + var whitelist = entManager.System(); + var cargo = entManager.System(); + var sliceableSys = entManager.System(); + + var bounties = protoManager.EnumeratePrototypes().ToList(); + + await server.WaitAssertion(() => + { + var mapId = testMap.MapId; + var grid = mapManager.CreateGridEntity(mapId); + var coord = new EntityCoordinates(grid.Owner, 0, 0); + + var sliceableEntityProtos = protoManager.EnumeratePrototypes() + .Where(p => !p.Abstract) + .Where(p => !pair.IsTestPrototype(p)) + .Where(p => p.TryGetComponent(out _, componentFactory)) + .Select(p => p.ID) + .ToList(); + + foreach (var proto in sliceableEntityProtos) + { + var ent = entManager.SpawnEntity(proto, coord); + var sliceable = entManager.GetComponent(ent); + + // Check each bounty + foreach (var bounty in bounties) + { + // Check each entry in the bounty + foreach (var entry in bounty.Entries) + { + // See if the entity counts as part of this bounty entry + if (!cargo.IsValidBountyEntry(ent, entry)) + continue; + + // Spawn a slice + var slice = entManager.SpawnEntity(sliceable.Slice, coord); + + // See if the slice also counts for this bounty entry + if (!cargo.IsValidBountyEntry(slice, entry)) + { + entManager.DeleteEntity(slice); + continue; + } + + entManager.DeleteEntity(slice); + + // If for some reason it can only make one slice, that's okay, I guess + Assert.That(sliceable.TotalCount, Is.EqualTo(1), $"{proto} counts as part of cargo bounty {bounty.ID} and slices into {sliceable.TotalCount} slices which count for the same bounty!"); + } + } + + entManager.DeleteEntity(ent); + } + mapManager.DeleteMap(mapId); + }); + + await pair.CleanReturnAsync(); + } [TestPrototypes] private const string StackProto = @" diff --git a/Content.IntegrationTests/Tests/Chemistry/DispenserTest.cs b/Content.IntegrationTests/Tests/Chemistry/DispenserTest.cs index a5449308be..52b7e555a9 100644 --- a/Content.IntegrationTests/Tests/Chemistry/DispenserTest.cs +++ b/Content.IntegrationTests/Tests/Chemistry/DispenserTest.cs @@ -18,7 +18,7 @@ public sealed class DispenserTest : InteractionTest ToggleNeedPower(); // Insert beaker - await Interact("Beaker"); + await InteractUsing("Beaker"); Assert.That(Hands.ActiveHandEntity, Is.Null); // Open BUI diff --git a/Content.IntegrationTests/Tests/Construction/Interaction/ComputerContruction.cs b/Content.IntegrationTests/Tests/Construction/Interaction/ComputerContruction.cs index 5412469ac5..8af5edaf31 100644 --- a/Content.IntegrationTests/Tests/Construction/Interaction/ComputerContruction.cs +++ b/Content.IntegrationTests/Tests/Construction/Interaction/ComputerContruction.cs @@ -16,10 +16,8 @@ public sealed class ComputerConstruction : InteractionTest await StartConstruction(Computer); // Initial interaction (ghost turns into real entity) - await Interact(Steel, 5); - ClientAssertPrototype(ComputerFrame, ClientTarget); - Target = CTestSystem.Ghosts[ClientTarget!.Value.GetHashCode()]; - ClientTarget = null; + await InteractUsing(Steel, 5); + ClientAssertPrototype(ComputerFrame, Target); // Perform construction steps await Interact( @@ -41,7 +39,7 @@ public sealed class ComputerConstruction : InteractionTest await StartDeconstruction(ComputerId); // Initial interaction turns id computer into generic computer - await Interact(Screw); + await InteractUsing(Screw); AssertPrototype(ComputerFrame); // Perform deconstruction steps @@ -71,7 +69,7 @@ public sealed class ComputerConstruction : InteractionTest await SpawnTarget(ComputerId); // Initial interaction turns id computer into generic computer - await Interact(Screw); + await InteractUsing(Screw); AssertPrototype(ComputerFrame); // Perform partial deconstruction steps diff --git a/Content.IntegrationTests/Tests/Construction/Interaction/GrilleWindowConstruction.cs b/Content.IntegrationTests/Tests/Construction/Interaction/GrilleWindowConstruction.cs index 0de39d2757..ef6a7b09ae 100644 --- a/Content.IntegrationTests/Tests/Construction/Interaction/GrilleWindowConstruction.cs +++ b/Content.IntegrationTests/Tests/Construction/Interaction/GrilleWindowConstruction.cs @@ -17,17 +17,14 @@ public sealed class GrilleWindowConstruction : InteractionTest { // Construct Grille await StartConstruction(Grille); - await Interact(Rod, 10); - ClientAssertPrototype(Grille, ClientTarget); - - Target = CTestSystem.Ghosts[ClientTarget!.Value.GetHashCode()]; + await InteractUsing(Rod, 10); + ClientAssertPrototype(Grille, Target); var grille = Target; // Construct Window await StartConstruction(Window); - await Interact(Glass, 10); - ClientAssertPrototype(Window, ClientTarget); - Target = CTestSystem.Ghosts[ClientTarget!.Value.GetHashCode()]; + await InteractUsing(Glass, 10); + ClientAssertPrototype(Window, Target); // Deconstruct Window await Interact(Screw, Wrench); @@ -35,7 +32,7 @@ public sealed class GrilleWindowConstruction : InteractionTest // Deconstruct Grille Target = grille; - await Interact(Cut); + await InteractUsing(Cut); AssertDeleted(); } diff --git a/Content.IntegrationTests/Tests/Construction/Interaction/MachineConstruction.cs b/Content.IntegrationTests/Tests/Construction/Interaction/MachineConstruction.cs index f52f820a4c..06874f39ed 100644 --- a/Content.IntegrationTests/Tests/Construction/Interaction/MachineConstruction.cs +++ b/Content.IntegrationTests/Tests/Construction/Interaction/MachineConstruction.cs @@ -14,9 +14,8 @@ public sealed class MachineConstruction : InteractionTest public async Task ConstructProtolathe() { await StartConstruction(MachineFrame); - await Interact(Steel, 5); - ClientAssertPrototype(Unfinished, ClientTarget); - Target = CTestSystem.Ghosts[ClientTarget!.Value.GetHashCode()]; + await InteractUsing(Steel, 5); + ClientAssertPrototype(Unfinished, Target); await Interact(Wrench, Cable); AssertPrototype(MachineFrame); await Interact(ProtolatheBoard, Bin1, Bin1, Manipulator1, Manipulator1, Beaker, Beaker, Screw); @@ -51,7 +50,7 @@ public sealed class MachineConstruction : InteractionTest AssertPrototype(MachineFrame); // Change it into an autolathe - await Interact("AutolatheMachineCircuitboard"); + await InteractUsing("AutolatheMachineCircuitboard"); AssertPrototype(MachineFrame); await Interact(Bin1, Bin1, Bin1, Manipulator1, Glass, Screw); AssertPrototype("Autolathe"); diff --git a/Content.IntegrationTests/Tests/Construction/Interaction/PanelScrewing.cs b/Content.IntegrationTests/Tests/Construction/Interaction/PanelScrewing.cs index b6d960e288..636d58bf96 100644 --- a/Content.IntegrationTests/Tests/Construction/Interaction/PanelScrewing.cs +++ b/Content.IntegrationTests/Tests/Construction/Interaction/PanelScrewing.cs @@ -19,21 +19,21 @@ public sealed class PanelScrewing : InteractionTest // Open & close panel Assert.That(comp.Open, Is.False); - await Interact(Screw); + await InteractUsing(Screw); Assert.That(comp.Open, Is.True); - await Interact(Screw); + await InteractUsing(Screw); Assert.That(comp.Open, Is.False); // Interrupted DoAfters - await Interact(Screw, awaitDoAfters: false); + await InteractUsing(Screw, awaitDoAfters: false); await CancelDoAfters(); Assert.That(comp.Open, Is.False); - await Interact(Screw); + await InteractUsing(Screw); Assert.That(comp.Open, Is.True); - await Interact(Screw, awaitDoAfters: false); + await InteractUsing(Screw, awaitDoAfters: false); await CancelDoAfters(); Assert.That(comp.Open, Is.True); - await Interact(Screw); + await InteractUsing(Screw); Assert.That(comp.Open, Is.False); } } diff --git a/Content.IntegrationTests/Tests/Construction/Interaction/PlaceableDeconstruction.cs b/Content.IntegrationTests/Tests/Construction/Interaction/PlaceableDeconstruction.cs index bc0cb9bcef..783c14c068 100644 --- a/Content.IntegrationTests/Tests/Construction/Interaction/PlaceableDeconstruction.cs +++ b/Content.IntegrationTests/Tests/Construction/Interaction/PlaceableDeconstruction.cs @@ -13,9 +13,9 @@ public sealed class PlaceableDeconstruction : InteractionTest { await StartDeconstruction("Table"); Assert.That(Comp().IsPlaceable); - await Interact(Wrench); + await InteractUsing(Wrench); AssertPrototype("TableFrame"); - await Interact(Wrench); + await InteractUsing(Wrench); AssertDeleted(); await AssertEntityLookup((Steel, 1), (Rod, 2)); } diff --git a/Content.IntegrationTests/Tests/Construction/Interaction/WallConstruction.cs b/Content.IntegrationTests/Tests/Construction/Interaction/WallConstruction.cs index 67a2f8025d..292bf0c55a 100644 --- a/Content.IntegrationTests/Tests/Construction/Interaction/WallConstruction.cs +++ b/Content.IntegrationTests/Tests/Construction/Interaction/WallConstruction.cs @@ -12,11 +12,10 @@ public sealed class WallConstruction : InteractionTest public async Task ConstructWall() { await StartConstruction(Wall); - await Interact(Steel, 2); + await InteractUsing(Steel, 2); Assert.That(Hands.ActiveHandEntity, Is.Null); - ClientAssertPrototype(Girder, ClientTarget); - Target = CTestSystem.Ghosts[ClientTarget!.Value.GetHashCode()]; - await Interact(Steel, 2); + ClientAssertPrototype(Girder, Target); + await InteractUsing(Steel, 2); Assert.That(Hands.ActiveHandEntity, Is.Null); AssertPrototype(WallSolid); } @@ -25,7 +24,7 @@ public sealed class WallConstruction : InteractionTest public async Task DeconstructWall() { await StartDeconstruction(WallSolid); - await Interact(Weld); + await InteractUsing(Weld); AssertPrototype(Girder); await Interact(Wrench, Screw); AssertDeleted(); diff --git a/Content.IntegrationTests/Tests/Construction/Interaction/WindowConstruction.cs b/Content.IntegrationTests/Tests/Construction/Interaction/WindowConstruction.cs index 46bb892ed9..2ece6b3e39 100644 --- a/Content.IntegrationTests/Tests/Construction/Interaction/WindowConstruction.cs +++ b/Content.IntegrationTests/Tests/Construction/Interaction/WindowConstruction.cs @@ -11,8 +11,8 @@ public sealed class WindowConstruction : InteractionTest public async Task ConstructWindow() { await StartConstruction(Window); - await Interact(Glass, 5); - ClientAssertPrototype(Window, ClientTarget); + await InteractUsing(Glass, 5); + ClientAssertPrototype(Window, Target); } [Test] @@ -28,8 +28,8 @@ public sealed class WindowConstruction : InteractionTest public async Task ConstructReinforcedWindow() { await StartConstruction(RWindow); - await Interact(RGlass, 5); - ClientAssertPrototype(RWindow, ClientTarget); + await InteractUsing(RGlass, 5); + ClientAssertPrototype(RWindow, Target); } [Test] diff --git a/Content.IntegrationTests/Tests/Construction/Interaction/WindowRepair.cs b/Content.IntegrationTests/Tests/Construction/Interaction/WindowRepair.cs index abd4bc265b..6eea519af3 100644 --- a/Content.IntegrationTests/Tests/Construction/Interaction/WindowRepair.cs +++ b/Content.IntegrationTests/Tests/Construction/Interaction/WindowRepair.cs @@ -24,7 +24,7 @@ public sealed class WindowRepair : InteractionTest Assert.That(comp.Damage.GetTotal(), Is.GreaterThan(FixedPoint2.Zero)); // Repair the entity - await Interact(Weld); + await InteractUsing(Weld); Assert.That(comp.Damage.GetTotal(), Is.EqualTo(FixedPoint2.Zero)); // Validate that we can still deconstruct the entity (i.e., that welding deconstruction is not blocked). diff --git a/Content.IntegrationTests/Tests/DoAfter/DoAfterCancellationTests.cs b/Content.IntegrationTests/Tests/DoAfter/DoAfterCancellationTests.cs index 0ebd17d887..1aaf4a5184 100644 --- a/Content.IntegrationTests/Tests/DoAfter/DoAfterCancellationTests.cs +++ b/Content.IntegrationTests/Tests/DoAfter/DoAfterCancellationTests.cs @@ -16,31 +16,31 @@ public sealed class DoAfterCancellationTests : InteractionTest public async Task CancelWallDeconstruct() { await StartDeconstruction(WallConstruction.WallSolid); - await Interact(Weld, awaitDoAfters: false); + await InteractUsing(Weld, awaitDoAfters: false); // Failed do-after has no effect await CancelDoAfters(); AssertPrototype(WallConstruction.WallSolid); // Second attempt works fine - await Interact(Weld); + await InteractUsing(Weld); AssertPrototype(WallConstruction.Girder); // Repeat for wrenching interaction AssertAnchored(); - await Interact(Wrench, awaitDoAfters: false); + await InteractUsing(Wrench, awaitDoAfters: false); await CancelDoAfters(); AssertAnchored(); AssertPrototype(WallConstruction.Girder); - await Interact(Wrench); + await InteractUsing(Wrench); AssertAnchored(false); // Repeat for screwdriver interaction. AssertExists(); - await Interact(Screw, awaitDoAfters: false); + await InteractUsing(Screw, awaitDoAfters: false); await CancelDoAfters(); AssertExists(); - await Interact(Screw); + await InteractUsing(Screw); AssertDeleted(); } @@ -48,17 +48,16 @@ public sealed class DoAfterCancellationTests : InteractionTest public async Task CancelWallConstruct() { await StartConstruction(WallConstruction.Wall); - await Interact(Steel, 5, awaitDoAfters: false); + await InteractUsing(Steel, 5, awaitDoAfters: false); await CancelDoAfters(); - await Interact(Steel, 5); - ClientAssertPrototype(WallConstruction.Girder, ClientTarget); - Target = CTestSystem.Ghosts[ClientTarget!.Value.GetHashCode()]; - await Interact(Steel, 5, awaitDoAfters: false); + await InteractUsing(Steel, 5); + ClientAssertPrototype(WallConstruction.Girder, Target); + await InteractUsing(Steel, 5, awaitDoAfters: false); await CancelDoAfters(); AssertPrototype(WallConstruction.Girder); - await Interact(Steel, 5); + await InteractUsing(Steel, 5); AssertPrototype(WallConstruction.WallSolid); } @@ -66,11 +65,11 @@ public sealed class DoAfterCancellationTests : InteractionTest public async Task CancelTilePry() { await SetTile(Floor); - await Interact(Pry, awaitDoAfters: false); + await InteractUsing(Pry, awaitDoAfters: false); await CancelDoAfters(); await AssertTile(Floor); - await Interact(Pry); + await InteractUsing(Pry); await AssertTile(Plating); } @@ -78,7 +77,7 @@ public sealed class DoAfterCancellationTests : InteractionTest public async Task CancelRepeatedTilePry() { await SetTile(Floor); - await Interact(Pry, awaitDoAfters: false); + await InteractUsing(Pry, awaitDoAfters: false); await RunTicks(1); Assert.That(ActiveDoAfters.Count(), Is.EqualTo(1)); await AssertTile(Floor); @@ -89,7 +88,7 @@ public sealed class DoAfterCancellationTests : InteractionTest await AssertTile(Floor); // Third do after will work fine - await Interact(Pry); + await InteractUsing(Pry); Assert.That(ActiveDoAfters.Count(), Is.EqualTo(0)); await AssertTile(Plating); } @@ -102,7 +101,7 @@ public sealed class DoAfterCancellationTests : InteractionTest Assert.That(comp.IsWelded, Is.False); - await Interact(Weld, awaitDoAfters: false); + await InteractUsing(Weld, awaitDoAfters: false); await RunTicks(1); Assert.Multiple(() => { @@ -120,7 +119,7 @@ public sealed class DoAfterCancellationTests : InteractionTest }); // Third do after will work fine - await Interact(Weld); + await InteractUsing(Weld); Assert.Multiple(() => { Assert.That(ActiveDoAfters.Count(), Is.EqualTo(0)); @@ -128,7 +127,7 @@ public sealed class DoAfterCancellationTests : InteractionTest }); // Repeat test for un-welding - await Interact(Weld, awaitDoAfters: false); + await InteractUsing(Weld, awaitDoAfters: false); await RunTicks(1); Assert.Multiple(() => { @@ -141,7 +140,7 @@ public sealed class DoAfterCancellationTests : InteractionTest Assert.That(ActiveDoAfters.Count(), Is.EqualTo(0)); Assert.That(comp.IsWelded, Is.True); }); - await Interact(Weld); + await InteractUsing(Weld); Assert.Multiple(() => { Assert.That(ActiveDoAfters.Count(), Is.EqualTo(0)); diff --git a/Content.IntegrationTests/Tests/EncryptionKeys/RemoveEncryptionKeys.cs b/Content.IntegrationTests/Tests/EncryptionKeys/RemoveEncryptionKeys.cs index 9e3dbd8863..f5e8c22242 100644 --- a/Content.IntegrationTests/Tests/EncryptionKeys/RemoveEncryptionKeys.cs +++ b/Content.IntegrationTests/Tests/EncryptionKeys/RemoveEncryptionKeys.cs @@ -22,7 +22,7 @@ public sealed class RemoveEncryptionKeys : InteractionTest }); // Remove the key - await Interact(Screw); + await InteractUsing(Screw); Assert.Multiple(() => { Assert.That(comp.KeyContainer.ContainedEntities, Has.Count.EqualTo(0)); @@ -34,7 +34,7 @@ public sealed class RemoveEncryptionKeys : InteractionTest await AssertEntityLookup(("EncryptionKeyCommon", 1)); // Re-insert a key. - await Interact("EncryptionKeyCentCom"); + await InteractUsing("EncryptionKeyCentCom"); Assert.Multiple(() => { Assert.That(comp.KeyContainer.ContainedEntities, Has.Count.EqualTo(1)); @@ -59,7 +59,7 @@ public sealed class RemoveEncryptionKeys : InteractionTest }); // cannot remove keys without opening panel - await Interact(Pry); + await InteractUsing(Pry); Assert.Multiple(() => { Assert.That(comp.KeyContainer.ContainedEntities, Has.Count.GreaterThan(0)); @@ -68,7 +68,7 @@ public sealed class RemoveEncryptionKeys : InteractionTest }); // Open panel - await Interact(Screw); + await InteractUsing(Screw); Assert.Multiple(() => { Assert.That(panel.Open, Is.True); @@ -79,7 +79,7 @@ public sealed class RemoveEncryptionKeys : InteractionTest }); // Now remove the keys - await Interact(Pry); + await InteractUsing(Pry); Assert.Multiple(() => { Assert.That(comp.KeyContainer.ContainedEntities, Has.Count.EqualTo(0)); @@ -87,7 +87,7 @@ public sealed class RemoveEncryptionKeys : InteractionTest }); // Reinsert a key - await Interact("EncryptionKeyCentCom"); + await InteractUsing("EncryptionKeyCentCom"); Assert.Multiple(() => { Assert.That(comp.KeyContainer.ContainedEntities, Has.Count.EqualTo(1)); @@ -97,7 +97,7 @@ public sealed class RemoveEncryptionKeys : InteractionTest }); // Remove it again - await Interact(Pry); + await InteractUsing(Pry); Assert.Multiple(() => { Assert.That(comp.KeyContainer.ContainedEntities, Has.Count.EqualTo(0)); @@ -106,7 +106,7 @@ public sealed class RemoveEncryptionKeys : InteractionTest // Prying again will start deconstructing the machine. AssertPrototype("TelecomServerFilled"); - await Interact(Pry); + await InteractUsing(Pry); AssertPrototype("MachineFrame"); } } diff --git a/Content.IntegrationTests/Tests/EntityTest.cs b/Content.IntegrationTests/Tests/EntityTest.cs index 54af64122b..1fc739fb0c 100644 --- a/Content.IntegrationTests/Tests/EntityTest.cs +++ b/Content.IntegrationTests/Tests/EntityTest.cs @@ -19,6 +19,8 @@ namespace Content.IntegrationTests.Tests [TestOf(typeof(EntityUid))] public sealed class EntityTest { + private static readonly ProtoId SpawnerCategory = "Spawner"; + [Test] public async Task SpawnAndDeleteAllEntitiesOnDifferentMaps() { @@ -234,14 +236,6 @@ namespace Content.IntegrationTests.Tests "StationEvent", "TimedDespawn", - // Spawner entities - "DragonRift", - "RandomHumanoidSpawner", - "RandomSpawner", - "ConditionalSpawner", - "GhostRoleMobSpawner", - "NukeOperativeSpawner", - "TimedSpawner", // makes an announcement on mapInit. "AnnounceOnSpawn", }; @@ -253,6 +247,7 @@ namespace Content.IntegrationTests.Tests .Where(p => !p.Abstract) .Where(p => !pair.IsTestPrototype(p)) .Where(p => !excluded.Any(p.Components.ContainsKey)) + .Where(p => p.Categories.All(x => x.ID != SpawnerCategory)) .Select(p => p.ID) .ToList(); @@ -345,6 +340,7 @@ namespace Content.IntegrationTests.Tests "MapGrid", "Broadphase", "StationData", // errors when removed mid-round + "StationJobs", "Actor", // We aren't testing actor components, those need their player session set. "BlobFloorPlanBuilder", // Implodes if unconfigured. "DebrisFeaturePlacerController", // Above. diff --git a/Content.IntegrationTests/Tests/FillLevelSpriteTest.cs b/Content.IntegrationTests/Tests/FillLevelSpriteTest.cs new file mode 100644 index 0000000000..37e777fa8c --- /dev/null +++ b/Content.IntegrationTests/Tests/FillLevelSpriteTest.cs @@ -0,0 +1,71 @@ +using System.Linq; +using Content.Shared.Chemistry.Components; +using Robust.Client.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.Prototypes; + +namespace Content.IntegrationTests.Tests; + +/// +/// Tests to see if any entity prototypes specify solution fill level sprites that don't exist. +/// +[TestFixture] +public sealed class FillLevelSpriteTest +{ + private static readonly string[] HandStateNames = ["left", "right"]; + + [Test] + public async Task FillLevelSpritesExist() + { + await using var pair = await PoolManager.GetServerClient(); + var client = pair.Client; + var protoMan = client.ResolveDependency(); + var componentFactory = client.ResolveDependency(); + + await client.WaitAssertion(() => + { + var protos = protoMan.EnumeratePrototypes() + .Where(p => !p.Abstract) + .Where(p => !pair.IsTestPrototype(p)) + .Where(p => p.TryGetComponent(out _, componentFactory)) + .OrderBy(p => p.ID) + .ToList(); + + foreach (var proto in protos) + { + Assert.That(proto.TryGetComponent(out var visuals, componentFactory)); + Assert.That(proto.TryGetComponent(out var sprite, componentFactory)); + + var rsi = sprite.BaseRSI; + + // Test base sprite fills + if (!string.IsNullOrEmpty(visuals.FillBaseName)) + { + for (var i = 1; i <= visuals.MaxFillLevels; i++) + { + var state = $"{visuals.FillBaseName}{i}"; + Assert.That(rsi.TryGetState(state, out _), @$"{proto.ID} has SolutionContainerVisualsComponent with + MaxFillLevels = {visuals.MaxFillLevels}, but {rsi.Path} doesn't have state {state}!"); + } + } + + // Test inhand sprite fills + if (!string.IsNullOrEmpty(visuals.InHandsFillBaseName)) + { + for (var i = 1; i <= visuals.InHandsMaxFillLevels; i++) + { + foreach (var handname in HandStateNames) + { + var state = $"inhand-{handname}{visuals.InHandsFillBaseName}{i}"; + Assert.That(rsi.TryGetState(state, out _), @$"{proto.ID} has SolutionContainerVisualsComponent with + InHandsMaxFillLevels = {visuals.InHandsMaxFillLevels}, but {rsi.Path} doesn't have state {state}!"); + } + + } + } + } + }); + + await pair.CleanReturnAsync(); + } +} diff --git a/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs b/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs index 662ea3b974..1bea33a82b 100644 --- a/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs @@ -52,7 +52,7 @@ public sealed class AntagPreferenceTest Assert.That(pool.Count, Is.EqualTo(0)); // Opt into the traitor role. - await pair.SetAntagPref("Traitor", true); + await pair.SetAntagPreference("Traitor", true); Assert.That(sys.IsSessionValid(rule, pair.Player, def), Is.True); Assert.That(sys.IsEntityValid(client.AttachedEntity, def), Is.True); @@ -63,7 +63,7 @@ public sealed class AntagPreferenceTest Assert.That(sessions.Count, Is.EqualTo(1)); // opt back out - await pair.SetAntagPref("Traitor", false); + await pair.SetAntagPreference("Traitor", false); Assert.That(sys.IsSessionValid(rule, pair.Player, def), Is.True); Assert.That(sys.IsEntityValid(client.AttachedEntity, def), Is.True); diff --git a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs index 6e43196eb9..48fe46a8c8 100644 --- a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs @@ -57,8 +57,17 @@ public sealed class NukeOpsTest Assert.That(client.AttachedEntity, Is.Null); Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.NotReadyToPlay)); + // Add several dummy players + var dummies = await pair.Server.AddDummySessions(3); + await pair.RunTicksSync(5); + // Opt into the nukies role. - await pair.SetAntagPref("NukeopsCommander", true); + await pair.SetAntagPreference("NukeopsCommander", true); + await pair.SetAntagPreference( "NukeopsMedic", true, dummies[1].UserId); + + // Initially, the players have no attached entities + Assert.That(pair.Player?.AttachedEntity, Is.Null); + Assert.That(dummies.All(x => x.AttachedEntity == null)); // There are no grids or maps Assert.That(entMan.Count(), Is.Zero); @@ -75,17 +84,20 @@ public sealed class NukeOpsTest Assert.That(entMan.Count(), Is.Zero); // Ready up and start nukeops - await pair.WaitClientCommand("toggleready True"); - Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.ReadyToPlay)); + ticker.ToggleReadyAll(true); + Assert.That(ticker.PlayerGameStatuses.Values.All(x => x == PlayerGameStatus.ReadyToPlay)); await pair.WaitCommand("forcepreset Nukeops"); await pair.RunTicksSync(10); // Game should have started Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.InRound)); - Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.JoinedGame)); + Assert.That(ticker.PlayerGameStatuses.Values.All(x => x == PlayerGameStatus.JoinedGame)); Assert.That(client.EntMan.EntityExists(client.AttachedEntity)); + + var dummyEnts = dummies.Select(x => x.AttachedEntity ?? default).ToArray(); var player = pair.Player!.AttachedEntity!.Value; Assert.That(entMan.EntityExists(player)); + Assert.That(dummyEnts.All(e => entMan.EntityExists(e))); // Maps now exist Assert.That(entMan.Count(), Is.GreaterThan(0)); @@ -96,8 +108,8 @@ public sealed class NukeOpsTest // And we now have nukie related components Assert.That(entMan.Count(), Is.EqualTo(1)); - Assert.That(entMan.Count(), Is.EqualTo(1)); - Assert.That(entMan.Count(), Is.EqualTo(1)); + Assert.That(entMan.Count(), Is.EqualTo(2)); + Assert.That(entMan.Count(), Is.EqualTo(2)); Assert.That(entMan.Count(), Is.EqualTo(1)); // The player entity should be the nukie commander @@ -107,15 +119,40 @@ public sealed class NukeOpsTest Assert.That(roleSys.MindHasRole(mind)); Assert.That(factionSys.IsMember(player, "Syndicate"), Is.True); Assert.That(factionSys.IsMember(player, "NanoTrasen"), Is.False); - var roles = roleSys.MindGetAllRoles(mind); var cmdRoles = roles.Where(x => x.Prototype == "NukeopsCommander" && x.Component is NukeopsRoleComponent); Assert.That(cmdRoles.Count(), Is.EqualTo(1)); + // The second dummy player should be a medic + var dummyMind = mindSys.GetMind(dummyEnts[1])!.Value; + Assert.That(entMan.HasComponent(dummyEnts[1])); + Assert.That(roleSys.MindIsAntagonist(dummyMind)); + Assert.That(roleSys.MindHasRole(dummyMind)); + Assert.That(factionSys.IsMember(dummyEnts[1], "Syndicate"), Is.True); + Assert.That(factionSys.IsMember(dummyEnts[1], "NanoTrasen"), Is.False); + roles = roleSys.MindGetAllRoles(dummyMind); + cmdRoles = roles.Where(x => x.Prototype == "NukeopsMedic" && x.Component is NukeopsRoleComponent); + Assert.That(cmdRoles.Count(), Is.EqualTo(1)); + + // The other two players should have just spawned in as normal. + CheckDummy(0); + CheckDummy(2); + void CheckDummy(int i) + { + var ent = dummyEnts[i]; + var mind = mindSys.GetMind(ent)!.Value; + Assert.That(entMan.HasComponent(ent), Is.False); + Assert.That(roleSys.MindIsAntagonist(mind), Is.False); + Assert.That(roleSys.MindHasRole(mind), Is.False); + Assert.That(factionSys.IsMember(ent, "Syndicate"), Is.False); + Assert.That(factionSys.IsMember(ent, "NanoTrasen"), Is.True); + Assert.That(roleSys.MindGetAllRoles(mind).Any(x => x.Component is NukeopsRoleComponent), Is.False); + } + // The game rule exists, and all the stations/shuttles/maps are properly initialized var rule = entMan.AllComponents().Single().Component; - var mapRule = entMan.AllComponents().Single().Component; - foreach (var grid in mapRule.MapGrids) + var gridsRule = entMan.AllComponents().Single().Component; + foreach (var grid in gridsRule.MapGrids) { Assert.That(entMan.EntityExists(grid)); Assert.That(entMan.HasComponent(grid)); @@ -129,7 +166,7 @@ public sealed class NukeOpsTest Assert.That(entMan.EntityExists(nukieShuttlEnt)); EntityUid? nukieStationEnt = null; - foreach (var grid in mapRule.MapGrids) + foreach (var grid in gridsRule.MapGrids) { if (entMan.HasComponent(grid)) { @@ -144,8 +181,8 @@ public sealed class NukeOpsTest Assert.That(entMan.EntityExists(nukieStation.Station)); Assert.That(nukieStation.Station, Is.Not.EqualTo(rule.TargetStation)); - Assert.That(server.MapMan.MapExists(mapRule.Map)); - var nukieMap = mapSys.GetMap(mapRule.Map!.Value); + Assert.That(server.MapMan.MapExists(gridsRule.Map)); + var nukieMap = mapSys.GetMap(gridsRule.Map!.Value); var targetStation = entMan.GetComponent(rule.TargetStation!.Value); var targetGrid = targetStation.Grids.First(); @@ -178,7 +215,7 @@ public sealed class NukeOpsTest // While we're at it, lets make sure they aren't naked. I don't know how many inventory slots all mobs will be // likely to have in the future. But nukies should probably have at least 3 slots with something in them. var enumerator = invSys.GetSlotEnumerator(player); - int total = 0; + var total = 0; while (enumerator.NextItem(out _)) { total++; @@ -199,7 +236,6 @@ public sealed class NukeOpsTest } ticker.SetGamePreset((GamePresetPrototype?)null); - await pair.SetAntagPref("NukeopsCommander", false); await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.EntitySpecifier.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.EntitySpecifier.cs index 37dca72137..053152dbe1 100644 --- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.EntitySpecifier.cs +++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.EntitySpecifier.cs @@ -33,7 +33,7 @@ public abstract partial class InteractionTest public int Quantity; /// - /// If true, a check has been performed to see if the prototype ia an entity prototype with a stack component, + /// If true, a check has been performed to see if the prototype is an entity prototype with a stack component, /// in which case the specifier was converted into a stack-specifier /// public bool Converted; @@ -100,7 +100,7 @@ public abstract partial class InteractionTest if (!ProtoMan.TryIndex(spec.Prototype, out var entProto)) { - Assert.Fail($"Unkown prototype: {spec.Prototype}"); + Assert.Fail($"Unknown prototype: {spec.Prototype}"); return default; } @@ -120,7 +120,7 @@ public abstract partial class InteractionTest /// /// Convert an entity-uid to a matching entity specifier. Useful when doing entity lookups & checking that the - /// right quantity of entities/materials werre produced. Returns null if passed an entity with a null prototype. + /// right quantity of entities/materials were produced. Returns null if passed an entity with a null prototype. /// protected EntitySpecifier? ToEntitySpecifier(EntityUid uid) { diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs index 19ca83a971..f4826cb249 100644 --- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs +++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs @@ -14,6 +14,7 @@ using Content.Shared.Construction.Prototypes; using Content.Shared.Gravity; using Content.Shared.Item; using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; using Robust.Shared.GameObjects; using Robust.Shared.Input; @@ -44,8 +45,9 @@ public abstract partial class InteractionTest return; var comp = CEntMan.GetComponent(clientTarget!.Value); - ClientTarget = clientTarget; - ConstructionGhostId = comp.Owner.Id; + Target = CEntMan.GetNetEntity(clientTarget.Value); + Assert.That(Target.Value.IsClientSide()); + ConstructionGhostId = clientTarget.Value.GetHashCode(); }); await RunTicks(1); @@ -129,21 +131,20 @@ public abstract partial class InteractionTest /// /// Place an entity prototype into the players hand. Deletes any currently held entity. /// - /// - /// Automatically enables welders. - /// - protected async Task PlaceInHands(string id, int quantity = 1, bool enableWelder = true) + /// The entity or stack prototype to spawn and place into the users hand + /// The number of entities to spawn. If the prototype is a stack, this sets the stack count. + /// Whether or not to automatically enable any toggleable items + protected async Task PlaceInHands(string id, int quantity = 1, bool enableToggleable = true) { - return await PlaceInHands((id, quantity), enableWelder); + return await PlaceInHands((id, quantity), enableToggleable); } /// /// Place an entity prototype into the players hand. Deletes any currently held entity. /// - /// - /// Automatically enables welders. - /// - protected async Task PlaceInHands(EntitySpecifier entity, bool enableWelder = true) + /// The entity type & quantity to spawn and place into the users hand + /// Whether or not to automatically enable any toggleable items + protected async Task PlaceInHands(EntitySpecifier entity, bool enableToggleable = true) { if (Hands.ActiveHand == null) { @@ -165,7 +166,7 @@ public abstract partial class InteractionTest Assert.That(HandSys.TryPickup(playerEnt, item, Hands.ActiveHand, false, false, Hands)); // turn on welders - if (enableWelder && SEntMan.TryGetComponent(item, out itemToggle) && !itemToggle.Activated) + if (enableToggleable && SEntMan.TryGetComponent(item, out itemToggle) && !itemToggle.Activated) { Assert.That(ItemToggleSys.TryActivate(item, playerEnt, itemToggle: itemToggle)); } @@ -173,7 +174,7 @@ public abstract partial class InteractionTest await RunTicks(1); Assert.That(Hands.ActiveHandEntity, Is.EqualTo(item)); - if (enableWelder && itemToggle != null) + if (enableToggleable && itemToggle != null) Assert.That(itemToggle.Activated); return SEntMan.GetNetEntity(item); @@ -254,21 +255,20 @@ public abstract partial class InteractionTest /// /// Place an entity prototype into the players hand and interact with the given entity (or target position) /// - /// - /// Empty strings imply empty hands. - /// - protected async Task Interact(string id, int quantity = 1, bool shouldSucceed = true, bool awaitDoAfters = true) + /// The entity or stack prototype to spawn and place into the users hand + /// The number of entities to spawn. If the prototype is a stack, this sets the stack count. + /// Whether or not to wait for any do-afters to complete + protected async Task InteractUsing(string id, int quantity = 1, bool awaitDoAfters = true) { - await Interact((id, quantity), shouldSucceed, awaitDoAfters); + await InteractUsing((id, quantity), awaitDoAfters); } /// - /// Place an entity prototype into the players hand and interact with the given entity (or target position) + /// Place an entity prototype into the players hand and interact with the given entity (or target position). /// - /// - /// Empty strings imply empty hands. - /// - protected async Task Interact(EntitySpecifier entity, bool shouldSucceed = true, bool awaitDoAfters = true) + /// The entity type & quantity to spawn and place into the users hand + /// Whether or not to wait for any do-afters to complete + protected async Task InteractUsing(EntitySpecifier entity, bool awaitDoAfters = true) { // For every interaction, we will also examine the entity, just in case this breaks something, somehow. // (e.g., servers attempt to assemble construction examine hints). @@ -278,38 +278,80 @@ public abstract partial class InteractionTest } await PlaceInHands(entity); - await Interact(shouldSucceed, awaitDoAfters); + await Interact(awaitDoAfters); } /// /// Interact with an entity using the currently held entity. /// - protected async Task Interact(bool shouldSucceed = true, bool awaitDoAfters = true) + /// Whether or not to wait for any do-afters to complete + protected async Task Interact(bool awaitDoAfters = true) { - var clientTarget = ClientTarget; - - if ((clientTarget?.IsValid() != true || CEntMan.Deleted(clientTarget)) && (Target == null || Target.Value.IsValid())) + if (Target == null || !Target.Value.IsClientSide()) { - await Server.WaitPost(() => InteractSys.UserInteraction(SEntMan.GetEntity(Player), SEntMan.GetCoordinates(TargetCoords), SEntMan.GetEntity(Target))); - await RunTicks(1); + await Interact(Target, TargetCoords, awaitDoAfters); + return; } - else - { - // The entity is client-side, so attempt to start construction - var clientEnt = ClientTarget ?? CEntMan.GetEntity(Target); - await Client.WaitPost(() => CConSys.TryStartConstruction(clientEnt!.Value)); - await RunTicks(5); - } + // The target is a client-side entity, so we will just attempt to start construction under the assumption that + // it is a construction ghost. + + await Client.WaitPost(() => CConSys.TryStartConstruction(CTarget!.Value)); + await RunTicks(5); if (awaitDoAfters) - await AwaitDoAfters(shouldSucceed); + await AwaitDoAfters(); - await CheckTargetChange(shouldSucceed && awaitDoAfters); + await CheckTargetChange(); + } + + /// + protected async Task Interact(NetEntity? target, NetCoordinates coordinates, bool awaitDoAfters = true) + { + Assert.That(SEntMan.TryGetEntity(target, out var sTarget) || target == null); + var coords = SEntMan.GetCoordinates(coordinates); + Assert.That(coords.IsValid(SEntMan)); + await Interact(sTarget, coords, awaitDoAfters); } /// - /// Variant of that performs several interactions using different entities. + /// Interact with an entity using the currently held entity. + /// + protected async Task Interact(EntityUid? target, EntityCoordinates coordinates, bool awaitDoAfters = true) + { + Assert.That(SEntMan.TryGetEntity(Player, out var player)); + + await Server.WaitPost(() => InteractSys.UserInteraction(player!.Value, coordinates, target)); + await RunTicks(1); + + if (awaitDoAfters) + await AwaitDoAfters(); + + await CheckTargetChange(); + } + + /// + /// Activate an entity. + /// + protected async Task Activate(NetEntity? target = null, bool awaitDoAfters = true) + { + target ??= Target; + Assert.That(target, Is.Not.Null); + Assert.That(SEntMan.TryGetEntity(target!.Value, out var sTarget)); + Assert.That(SEntMan.TryGetEntity(Player, out var player)); + + await Server.WaitPost(() => InteractSys.InteractionActivate(player!.Value, sTarget!.Value)); + await RunTicks(1); + + if (awaitDoAfters) + await AwaitDoAfters(); + + await CheckTargetChange(); + } + + /// + /// Variant of that performs several interactions using different entities. + /// Useful for quickly finishing multiple construction steps. /// /// /// Empty strings imply empty hands. @@ -318,7 +360,7 @@ public abstract partial class InteractionTest { foreach (var spec in specifiers) { - await Interact(spec); + await InteractUsing(spec); } } @@ -338,7 +380,7 @@ public abstract partial class InteractionTest /// /// Wait for any currently active DoAfters to finish. /// - protected async Task AwaitDoAfters(bool shouldSucceed = true, int maxExpected = 1) + protected async Task AwaitDoAfters(int maxExpected = 1) { if (!ActiveDoAfters.Any()) return; @@ -353,13 +395,12 @@ public abstract partial class InteractionTest await RunTicks(10); } - if (!shouldSucceed) - return; - foreach (var doAfter in doAfters) { Assert.That(!doAfter.Cancelled); } + + await RunTicks(5); } /// @@ -398,39 +439,28 @@ public abstract partial class InteractionTest /// Check if the test's target entity has changed. E.g., construction interactions will swap out entities while /// a structure is being built. /// - protected async Task CheckTargetChange(bool shouldSucceed) + protected async Task CheckTargetChange() { if (Target == null) return; - var target = Target.Value; + var originalTarget = Target.Value; await RunTicks(5); - if (ClientTarget != null && CEntMan.IsClientSide(ClientTarget.Value)) + if (Target.Value.IsClientSide() && CTestSystem.Ghosts.TryGetValue(ConstructionGhostId, out var newWeh)) { - Assert.That(CEntMan.Deleted(ClientTarget.Value), Is.EqualTo(shouldSucceed), - $"Construction ghost was {(shouldSucceed ? "not deleted" : "deleted")}."); - - if (shouldSucceed) - { - Assert.That(CTestSystem.Ghosts.TryGetValue(ConstructionGhostId, out var newWeh), - $"Failed to get construction entity from ghost Id"); - - await Client.WaitPost(() => CLogger.Debug($"Construction ghost {ConstructionGhostId} became entity {newWeh}")); - Target = newWeh; - } + CLogger.Debug($"Construction ghost {ConstructionGhostId} became entity {newWeh}"); + Target = newWeh; } if (STestSystem.EntChanges.TryGetValue(Target.Value, out var newServerWeh)) { - await Server.WaitPost( - () => SLogger.Debug($"Construction entity {Target.Value} changed to {newServerWeh}")); - + SLogger.Debug($"Construction entity {Target.Value} changed to {newServerWeh}"); Target = newServerWeh; } - if (Target != target) - await CheckTargetChange(shouldSucceed); + if (Target != originalTarget) + await CheckTargetChange(); } #region Asserts @@ -444,16 +474,10 @@ public abstract partial class InteractionTest return; } - var meta = SEntMan.GetComponent(SEntMan.GetEntity(target.Value)); + var meta = CEntMan.GetComponent(CEntMan.GetEntity(target.Value)); Assert.That(meta.EntityPrototype?.ID, Is.EqualTo(prototype)); } - protected void ClientAssertPrototype(string? prototype, EntityUid? target) - { - var netEnt = CTestSystem.Ghosts[target.GetHashCode()]; - AssertPrototype(prototype, netEnt); - } - protected void AssertPrototype(string? prototype, NetEntity? target = null) { target ??= Target; @@ -699,6 +723,8 @@ public abstract partial class InteractionTest protected IEnumerable ActiveDoAfters => DoAfters.DoAfters.Values.Where(x => !x.Cancelled && !x.Completed); + #region Component + /// /// Convenience method to get components on the target. Returns SERVER-SIDE components. /// @@ -708,9 +734,23 @@ public abstract partial class InteractionTest if (target == null) Assert.Fail("No target specified"); - return SEntMan.GetComponent(SEntMan.GetEntity(target!.Value)); + return SEntMan.GetComponent(ToServer(target!.Value)); } + /// + protected bool TryComp(NetEntity? target, [NotNullWhen(true)] out T? comp) where T : IComponent + { + return SEntMan.TryGetComponent(ToServer(target), out comp); + } + + /// + protected bool TryComp([NotNullWhen(true)] out T? comp) where T : IComponent + { + return SEntMan.TryGetComponent(STarget, out comp); + } + + #endregion + /// /// Set the tile at the target position to some prototype. /// @@ -833,23 +873,70 @@ public abstract partial class InteractionTest return true; } + protected bool IsUiOpen(Enum key) + { + if (!TryComp(Player, out UserInterfaceUserComponent? user)) + return false; + + foreach (var keys in user.OpenInterfaces.Values) + { + if (keys.Contains(key)) + return true; + } + + return false; + } + #endregion #region UI /// - /// Presses and releases a button on some client-side window. Will fail if the button cannot be found. + /// Attempts to find, and then presses and releases a control on some client-side window. + /// Will fail if the control cannot be found. /// - protected async Task ClickControl(string name) where TWindow : BaseWindow + protected async Task ClickControl(string name, BoundKeyFunction? function = null) + where TWindow : BaseWindow + where TControl : Control { - await ClickControl(GetControl(name)); + var window = GetWindow(); + var control = GetControlFromField(name, window); + await ClickControl(control, function); } /// - /// Simulates a click and release at the center of some UI Constrol. + /// Attempts to find, and then presses and releases a control on some client-side widget. + /// Will fail if the control cannot be found. /// - protected async Task ClickControl(Control control) + protected async Task ClickWidgetControl(string name, BoundKeyFunction? function = null) + where TWidget : UIWidget, new() + where TControl : Control { + var widget = GetWidget(); + var control = GetControlFromField(name, widget); + await ClickControl(control, function); + } + + /// + protected async Task ClickControl(string name, BoundKeyFunction? function = null) + where TWindow : BaseWindow + { + await ClickControl(name, function); + } + + /// + protected async Task ClickWidgetControl(string name, BoundKeyFunction? function = null) + where TWidget : UIWidget, new() + { + await ClickWidgetControl(name, function); + } + + /// + /// Simulates a click and release at the center of some UI control. + /// + protected async Task ClickControl(Control control, BoundKeyFunction? function = null) + { + function ??= EngineKeyFunctions.UIClick; var screenCoords = new ScreenCoordinates( control.GlobalPixelPosition + control.PixelSize / 2, control.Window?.Id ?? default); @@ -858,7 +945,7 @@ public abstract partial class InteractionTest var relativePixelPos = screenCoords.Position - control.GlobalPixelPosition; var args = new GUIBoundKeyEventArgs( - EngineKeyFunctions.UIClick, + function.Value, BoundKeyState.Down, screenCoords, default, @@ -869,7 +956,7 @@ public abstract partial class InteractionTest await RunTicks(1); args = new GUIBoundKeyEventArgs( - EngineKeyFunctions.UIClick, + function.Value, BoundKeyState.Up, screenCoords, default, @@ -881,31 +968,26 @@ public abstract partial class InteractionTest } /// - /// Attempts to find a control on some client-side window. Will fail if the control cannot be found. + /// Attempt to retrieve a control by looking for a field on some other control. /// - protected TControl GetControl(string name) - where TWindow : BaseWindow + /// + /// Will fail if the control cannot be found. + /// + protected TControl GetControlFromField(string name, Control parent) where TControl : Control - { - var control = GetControl(name); - Assert.That(control.GetType().IsAssignableTo(typeof(TControl))); - return (TControl) control; - } - - protected Control GetControl(string name) where TWindow : BaseWindow { const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; - var field = typeof(TWindow).GetField(name, flags); - var prop = typeof(TWindow).GetProperty(name, flags); + var parentType = parent.GetType(); + var field = parentType.GetField(name, flags); + var prop = parentType.GetProperty(name, flags); if (field == null && prop == null) { - Assert.Fail($"Window {typeof(TWindow).Name} does not have a field or property named {name}"); + Assert.Fail($"Window {parentType.Name} does not have a field or property named {name}"); return default!; } - var window = GetWindow(); - var fieldOrProp = field?.GetValue(window) ?? prop?.GetValue(window); + var fieldOrProp = field?.GetValue(parent) ?? prop?.GetValue(parent); if (fieldOrProp is not Control control) { @@ -913,7 +995,59 @@ public abstract partial class InteractionTest return default!; } - return control; + Assert.That(control.GetType().IsAssignableTo(typeof(TControl))); + return (TControl) control; + } + + /// + /// Attempt to retrieve a control that matches some predicate by iterating through a control's children. + /// + /// + /// Will fail if the control cannot be found. + /// + protected TControl GetControlFromChildren(Func predicate, Control parent, bool recursive = true) + where TControl : Control + { + if (TryGetControlFromChildren(predicate, parent, out var control, recursive)) + return control; + + Assert.Fail($"Failed to find a {nameof(TControl)} that satisfies the predicate in {parent.Name}"); + return default!; + } + + /// + /// Attempt to retrieve a control of a given type by iterating through a control's children. + /// + protected TControl GetControlFromChildren(Control parent, bool recursive = false) + where TControl : Control + { + return GetControlFromChildren(static _ => true, parent, recursive); + } + + /// + /// Attempt to retrieve a control that matches some predicate by iterating through a control's children. + /// + protected bool TryGetControlFromChildren( + Func predicate, + Control parent, + [NotNullWhen(true)] out TControl? control, + bool recursive = true) + where TControl : Control + { + foreach (var ctrl in parent.Children) + { + if (ctrl is TControl cast && predicate(cast)) + { + control = cast; + return true; + } + + if (recursive && TryGetControlFromChildren(predicate, ctrl, out control)) + return true; + } + + control = null; + return false; } /// @@ -944,7 +1078,6 @@ public abstract partial class InteractionTest return window != null; } - /// /// Attempts to find a currently open client-side window. /// @@ -962,6 +1095,34 @@ public abstract partial class InteractionTest return window != null; } + + /// + /// Attempts to find client-side UI widget. + /// + protected UIWidget GetWidget() + where TWidget : UIWidget, new() + { + if (TryFindWidget(out TWidget? widget)) + return widget; + + Assert.Fail($"Could not find a {typeof(TWidget).Name} widget"); + return default!; + } + + /// + /// Attempts to find client-side UI widget. + /// + private bool TryFindWidget([NotNullWhen(true)] out TWidget? uiWidget) + where TWidget : UIWidget, new() + { + uiWidget = null; + var screen = UiMan.ActiveScreen; + if (screen == null) + return false; + + return screen.TryGetWidget(out uiWidget); + } + #endregion #region Power diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs index e5f794feaa..089addfaef 100644 --- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs +++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs @@ -1,8 +1,10 @@ #nullable enable +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Numerics; using Content.Client.Construction; using Content.Client.Examine; +using Content.Client.Gameplay; using Content.IntegrationTests.Pair; using Content.Server.Body.Systems; using Content.Server.Hands.Systems; @@ -24,6 +26,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Timing; using Robust.UnitTesting; using Content.Shared.Item.ItemToggle; +using Robust.Client.State; namespace Content.IntegrationTests.Tests.Interaction; @@ -64,15 +67,12 @@ public abstract partial class InteractionTest /// The player entity that performs all these interactions. Defaults to an admin-observer with 1 hand. /// protected NetEntity Player; - - protected EntityUid SPlayer => ToServer(Player); - protected EntityUid CPlayer => ToClient(Player); + protected EntityUid SPlayer; + protected EntityUid CPlayer; protected ICommonSession ClientSession = default!; protected ICommonSession ServerSession = default!; - public EntityUid? ClientTarget; - /// /// The current target entity. This is the default entity for various helper functions. /// @@ -108,6 +108,7 @@ public abstract partial class InteractionTest protected InteractionTestSystem STestSystem = default!; protected SharedTransformSystem Transform = default!; protected ISawmill SLogger = default!; + protected SharedUserInterfaceSystem SUiSys = default!; // CLIENT dependencies protected IEntityManager CEntMan = default!; @@ -119,6 +120,7 @@ public abstract partial class InteractionTest protected ExamineSystem ExamineSys = default!; protected InteractionTestSystem CTestSystem = default!; protected ISawmill CLogger = default!; + protected SharedUserInterfaceSystem CUiSys = default!; // player components protected HandsComponent Hands = default!; @@ -168,6 +170,7 @@ public abstract partial class InteractionTest STestSystem = SEntMan.System(); Stack = SEntMan.System(); SLogger = Server.ResolveDependency().RootSawmill; + SUiSys = Client.System(); // client dependencies CEntMan = Client.ResolveDependency(); @@ -179,6 +182,7 @@ public abstract partial class InteractionTest CConSys = CEntMan.System(); ExamineSys = CEntMan.System(); CLogger = Client.ResolveDependency().RootSawmill; + CUiSys = Client.System(); // Setup map. await Pair.CreateTestMap(); @@ -204,15 +208,16 @@ public abstract partial class InteractionTest old = cPlayerMan.LocalEntity; Player = SEntMan.GetNetEntity(SEntMan.SpawnEntity(PlayerPrototype, SEntMan.GetCoordinates(PlayerCoords))); - var serverPlayerEnt = SEntMan.GetEntity(Player); - Server.PlayerMan.SetAttachedEntity(ServerSession, serverPlayerEnt); - Hands = SEntMan.GetComponent(serverPlayerEnt); - DoAfters = SEntMan.GetComponent(serverPlayerEnt); + SPlayer = SEntMan.GetEntity(Player); + Server.PlayerMan.SetAttachedEntity(ServerSession, SPlayer); + Hands = SEntMan.GetComponent(SPlayer); + DoAfters = SEntMan.GetComponent(SPlayer); }); // Check player got attached. await RunTicks(5); - Assert.That(CEntMan.GetNetEntity(cPlayerMan.LocalEntity), Is.EqualTo(Player)); + CPlayer = ToClient(Player); + Assert.That(cPlayerMan.LocalEntity, Is.EqualTo(CPlayer)); // Delete old player entity. await Server.WaitPost(() => @@ -235,6 +240,10 @@ public abstract partial class InteractionTest } }); + // Change UI state to in-game. + var state = Client.ResolveDependency(); + await Client.WaitPost(() => state.RequestStateChange()); + // Final player asserts/checks. await Pair.ReallyBeIdle(5); Assert.Multiple(() => diff --git a/Content.IntegrationTests/Tests/Localization/LocalizedDatasetPrototypeTest.cs b/Content.IntegrationTests/Tests/Localization/LocalizedDatasetPrototypeTest.cs new file mode 100644 index 0000000000..b30c0a370e --- /dev/null +++ b/Content.IntegrationTests/Tests/Localization/LocalizedDatasetPrototypeTest.cs @@ -0,0 +1,35 @@ +using System.Linq; +using Content.Shared.Dataset; +using Robust.Shared.Localization; +using Robust.Shared.Prototypes; + +namespace Content.IntegrationTests.Tests.Localization; + +[TestFixture] +public sealed class LocalizedDatasetPrototypeTest +{ + [Test] + public async Task ValidProtoIdsTest() + { + await using var pair = await PoolManager.GetServerClient(); + + var server = pair.Server; + var protoMan = server.ResolveDependency(); + var localizationMan = server.ResolveDependency(); + + var protos = protoMan.EnumeratePrototypes().OrderBy(p => p.ID); + + // Check each prototype + foreach (var proto in protos) + { + // Check each value in the prototype + foreach (var locId in proto.Values) + { + // Make sure the localization manager has a string for the LocId + Assert.That(localizationMan.HasString(locId), $"LocalizedDataset {proto.ID} with prefix \"{proto.Values.Prefix}\" specifies {proto.Values.Count} entries, but no localized string was found matching {locId}!"); + } + } + + await pair.CleanReturnAsync(); + } +} diff --git a/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs b/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs index 7f9c02fc13..ed1e554943 100644 --- a/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs +++ b/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs @@ -103,7 +103,7 @@ public sealed class MaterialArbitrageTest continue; var stackProto = protoManager.Index(materialStep.MaterialPrototypeId); - var spawnProto = protoManager.Index(stackProto.Spawn); + var spawnProto = protoManager.Index(stackProto.Spawn); if (!spawnProto.Components.ContainsKey(materialName) || !spawnProto.Components.TryGetValue(compositionName, out var compositionReg)) diff --git a/Content.IntegrationTests/Tests/Payload/ModularGrenadeTests.cs b/Content.IntegrationTests/Tests/Payload/ModularGrenadeTests.cs index 70179fdec1..4db79373d3 100644 --- a/Content.IntegrationTests/Tests/Payload/ModularGrenadeTests.cs +++ b/Content.IntegrationTests/Tests/Payload/ModularGrenadeTests.cs @@ -22,32 +22,32 @@ public sealed class ModularGrenadeTests : InteractionTest Target = SEntMan.GetNetEntity(await FindEntity("ModularGrenade")); await Drop(); - await Interact(Cable); + await InteractUsing(Cable); // Insert & remove trigger AssertComp(false); - await Interact(Trigger); + await InteractUsing(Trigger); AssertComp(); await FindEntity(Trigger, LookupFlags.Uncontained, shouldSucceed: false); - await Interact(Pry); + await InteractUsing(Pry); AssertComp(false); // Trigger was dropped to floor, not deleted. await FindEntity(Trigger, LookupFlags.Uncontained); // Re-insert - await Interact(Trigger); + await InteractUsing(Trigger); AssertComp(); // Insert & remove payload. - await Interact(Payload); + await InteractUsing(Payload); await FindEntity(Payload, LookupFlags.Uncontained, shouldSucceed: false); - await Interact(Pry); + await InteractUsing(Pry); var ent = await FindEntity(Payload, LookupFlags.Uncontained); await Delete(ent); // successfully insert a second time - await Interact(Payload); + await InteractUsing(Payload); ent = await FindEntity(Payload); var sys = SEntMan.System(); Assert.That(sys.IsEntityInContainer(ent)); diff --git a/Content.IntegrationTests/Tests/PostMapInitTest.cs b/Content.IntegrationTests/Tests/PostMapInitTest.cs index c512c246f3..b2f69a05b8 100644 --- a/Content.IntegrationTests/Tests/PostMapInitTest.cs +++ b/Content.IntegrationTests/Tests/PostMapInitTest.cs @@ -239,22 +239,15 @@ namespace Content.IntegrationTests.Tests // Test all availableJobs have spawnPoints // This is done inside gamemap test because loading the map takes ages and we already have it. - var jobList = entManager.GetComponent(station).RoundStartJobList - .Where(x => x.Value != 0) - .Select(x => x.Key); - var spawnPoints = entManager.EntityQuery() - .Where(spawnpoint => spawnpoint.SpawnType == SpawnPointType.Job) - .Select(spawnpoint => spawnpoint.Job.ID) - .Distinct(); - List missingSpawnPoints = new(); - foreach (var spawnpoint in jobList.Except(spawnPoints)) - { - if (protoManager.Index(spawnpoint).SetPreference) - missingSpawnPoints.Add(spawnpoint); - } + var comp = entManager.GetComponent(station); + var jobs = new HashSet>(comp.SetupAvailableJobs.Keys); - Assert.That(missingSpawnPoints, Has.Count.EqualTo(0), - $"There is no spawnpoint for {string.Join(", ", missingSpawnPoints)} on {mapProto}."); + var spawnPoints = entManager.EntityQuery() + .Where(x => x.SpawnType == SpawnPointType.Job) + .Select(x => x.Job!.Value); + + jobs.ExceptWith(spawnPoints); + Assert.That(jobs, Is.Empty,$"There is no spawnpoints for {string.Join(", ", jobs)} on {mapProto}."); } try diff --git a/Content.IntegrationTests/Tests/PrototypeTests/PrototypeUploadTest.cs b/Content.IntegrationTests/Tests/PrototypeTests/PrototypeUploadTest.cs new file mode 100644 index 0000000000..c641cd9bd1 --- /dev/null +++ b/Content.IntegrationTests/Tests/PrototypeTests/PrototypeUploadTest.cs @@ -0,0 +1,85 @@ +using Content.Shared.Tag; +using Robust.Client.Upload.Commands; +using Robust.Shared.GameObjects; +using Robust.Shared.Prototypes; +using Robust.Shared.Upload; + +namespace Content.IntegrationTests.Tests.PrototypeTests; + +public sealed class PrototypeUploadTest +{ + public const string IdA = "UploadTestPrototype"; + public const string IdB = $"{IdA}NoParent"; + public const string IdC = $"{IdA}Abstract"; + public const string IdD = $"{IdA}UploadedParent"; + + private const string File = $@" +- type: entity + parent: BaseStructure # BaseItem can cause AllItemsHaveSpritesTest to fail + id: {IdA} + +- type: entity + id: {IdB} + +- type: entity + id: {IdC} + abstract: true + components: + - type: Tag + +- type: entity + id: {IdD} + parent: {IdC} +"; + + [Test] + [TestOf(typeof(LoadPrototypeCommand))] + public async Task TestFileUpload() + { + await using var pair = await PoolManager.GetServerClient(new PoolSettings {Connected = true}); + var sCompFact = pair.Server.ResolveDependency(); + var cCompFact = pair.Client.ResolveDependency(); + + Assert.That(!pair.Server.ProtoMan.TryIndex(IdA, out _)); + Assert.That(!pair.Server.ProtoMan.TryIndex(IdB, out _)); + Assert.That(!pair.Server.ProtoMan.TryIndex(IdC, out _)); + Assert.That(!pair.Server.ProtoMan.TryIndex(IdD, out _)); + + Assert.That(!pair.Client.ProtoMan.TryIndex(IdA, out _)); + Assert.That(!pair.Client.ProtoMan.TryIndex(IdB, out _)); + Assert.That(!pair.Client.ProtoMan.TryIndex(IdC, out _)); + Assert.That(!pair.Client.ProtoMan.TryIndex(IdD, out _)); + + var protoLoad = pair.Client.ResolveDependency(); + await pair.Client.WaitPost(() => protoLoad.SendGamePrototype(File)); + await pair.RunTicksSync(10); + + Assert.That(pair.Server.ProtoMan.TryIndex(IdA, out var sProtoA)); + Assert.That(pair.Server.ProtoMan.TryIndex(IdB, out var sProtoB)); + Assert.That(!pair.Server.ProtoMan.TryIndex(IdC, out _)); + Assert.That(pair.Server.ProtoMan.TryIndex(IdD, out var sProtoD)); + + Assert.That(pair.Client.ProtoMan.TryIndex(IdA, out var cProtoA)); + Assert.That(pair.Client.ProtoMan.TryIndex(IdB, out var cProtoB)); + Assert.That(!pair.Client.ProtoMan.TryIndex(IdC, out _)); + Assert.That(pair.Client.ProtoMan.TryIndex(IdD, out var cProtoD)); + + // Arbitrarily choosing TagComponent to check that inheritance works for uploaded prototypes. + + await pair.Server.WaitPost(() => + { + Assert.That(sProtoA!.TryGetComponent(out _, sCompFact), Is.True); + Assert.That(sProtoB!.TryGetComponent(out _, sCompFact), Is.False); + Assert.That(sProtoD!.TryGetComponent(out _, sCompFact), Is.True); + }); + + await pair.Client.WaitPost(() => + { + Assert.That(cProtoA!.TryGetComponent(out _, cCompFact), Is.True); + Assert.That(cProtoB!.TryGetComponent(out _, cCompFact), Is.False); + Assert.That(cProtoD!.TryGetComponent(out _, cCompFact), Is.True); + }); + + await pair.CleanReturnAsync(); + } +} diff --git a/Content.IntegrationTests/Tests/Round/JobTest.cs b/Content.IntegrationTests/Tests/Round/JobTest.cs new file mode 100644 index 0000000000..716e3cf4c2 --- /dev/null +++ b/Content.IntegrationTests/Tests/Round/JobTest.cs @@ -0,0 +1,222 @@ +#nullable enable +using System.Collections.Generic; +using System.Linq; +using Content.IntegrationTests.Pair; +using Content.Server.GameTicking; +using Content.Server.Mind; +using Content.Server.Roles; +using Content.Shared.CCVar; +using Content.Shared.GameTicking; +using Content.Shared.Preferences; +using Content.Shared.Roles; +using Content.Shared.Roles.Jobs; +using Robust.Shared.Network; +using Robust.Shared.Prototypes; + +namespace Content.IntegrationTests.Tests.Round; + +[TestFixture] +public sealed class JobTest +{ + private static ProtoId _passenger = "Passenger"; + private static ProtoId _engineer = "StationEngineer"; + private static ProtoId _captain = "Captain"; + + private static string _map = "JobTestMap"; + + [TestPrototypes] + public static string JobTestMap = @$" +- type: gameMap + id: {_map} + mapName: {_map} + mapPath: /Maps/Test/empty.yml + minPlayers: 0 + stations: + Empty: + stationProto: StandardNanotrasenStation + components: + - type: StationNameSetup + mapNameTemplate: ""Empty"" + - type: StationJobs + availableJobs: + {_passenger}: [ -1, -1 ] + {_engineer}: [ -1, -1 ] + {_captain}: [ 1, 1 ] +"; + + public void AssertJob(TestPair pair, ProtoId job, NetUserId? user = null, bool isAntag = false) + { + var jobSys = pair.Server.System(); + var mindSys = pair.Server.System(); + var roleSys = pair.Server.System(); + var ticker = pair.Server.System(); + + user ??= pair.Client.User!.Value; + + Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.InRound)); + Assert.That(ticker.PlayerGameStatuses[user.Value], Is.EqualTo(PlayerGameStatus.JoinedGame)); + + var uid = pair.Server.PlayerMan.SessionsDict.GetValueOrDefault(user.Value)?.AttachedEntity; + Assert.That(pair.Server.EntMan.EntityExists(uid)); + var mind = mindSys.GetMind(uid!.Value); + Assert.That(pair.Server.EntMan.EntityExists(mind)); + Assert.That(jobSys.MindTryGetJobId(mind, out var actualJob)); + Assert.That(actualJob, Is.EqualTo(job)); + Assert.That(roleSys.MindIsAntagonist(mind), Is.EqualTo(isAntag)); + } + + /// + /// Simple test that checks that starting the round spawns the player into the test map as a passenger. + /// + [Test] + public async Task StartRoundTest() + { + await using var pair = await PoolManager.GetServerClient(new PoolSettings + { + DummyTicker = false, + Connected = true, + InLobby = true + }); + + pair.Server.CfgMan.SetCVar(CCVars.GameMap, _map); + var ticker = pair.Server.System(); + + // Initially in the lobby + Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby)); + Assert.That(pair.Client.AttachedEntity, Is.Null); + Assert.That(ticker.PlayerGameStatuses[pair.Client.User!.Value], Is.EqualTo(PlayerGameStatus.NotReadyToPlay)); + + // Ready up and start the round + ticker.ToggleReadyAll(true); + Assert.That(ticker.PlayerGameStatuses[pair.Client.User!.Value], Is.EqualTo(PlayerGameStatus.ReadyToPlay)); + await pair.Server.WaitPost(() => ticker.StartRound()); + await pair.RunTicksSync(10); + + AssertJob(pair, _passenger); + + await pair.Server.WaitPost(() => ticker.RestartRound()); + await pair.CleanReturnAsync(); + } + + /// + /// Check that job preferences are respected. + /// + [Test] + public async Task JobPreferenceTest() + { + await using var pair = await PoolManager.GetServerClient(new PoolSettings + { + DummyTicker = false, + Connected = true, + InLobby = true + }); + + pair.Server.CfgMan.SetCVar(CCVars.GameMap, _map); + var ticker = pair.Server.System(); + Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby)); + Assert.That(pair.Client.AttachedEntity, Is.Null); + + await pair.SetJobPriorities((_passenger, JobPriority.Medium), (_engineer, JobPriority.High)); + ticker.ToggleReadyAll(true); + await pair.Server.WaitPost(() => ticker.StartRound()); + await pair.RunTicksSync(10); + + AssertJob(pair, _engineer); + + await pair.Server.WaitPost(() => ticker.RestartRound()); + Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby)); + await pair.SetJobPriorities((_passenger, JobPriority.High), (_engineer, JobPriority.Medium)); + ticker.ToggleReadyAll(true); + await pair.Server.WaitPost(() => ticker.StartRound()); + await pair.RunTicksSync(10); + + AssertJob(pair, _passenger); + + await pair.Server.WaitPost(() => ticker.RestartRound()); + await pair.CleanReturnAsync(); + } + + /// + /// Check high priority jobs (e.g., captain) are selected before other roles, even if it means a player does not + /// get their preferred job. + /// + [Test] + public async Task JobWeightTest() + { + await using var pair = await PoolManager.GetServerClient(new PoolSettings + { + DummyTicker = false, + Connected = true, + InLobby = true + }); + + pair.Server.CfgMan.SetCVar(CCVars.GameMap, _map); + var ticker = pair.Server.System(); + Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby)); + Assert.That(pair.Client.AttachedEntity, Is.Null); + + var captain = pair.Server.ProtoMan.Index(_captain); + var engineer = pair.Server.ProtoMan.Index(_engineer); + var passenger = pair.Server.ProtoMan.Index(_passenger); + Assert.That(captain.Weight, Is.GreaterThan(engineer.Weight)); + Assert.That(engineer.Weight, Is.EqualTo(passenger.Weight)); + + await pair.SetJobPriorities((_passenger, JobPriority.Medium), (_engineer, JobPriority.High), (_captain, JobPriority.Low)); + ticker.ToggleReadyAll(true); + await pair.Server.WaitPost(() => ticker.StartRound()); + await pair.RunTicksSync(10); + + AssertJob(pair, _captain); + + await pair.Server.WaitPost(() => ticker.RestartRound()); + await pair.CleanReturnAsync(); + } + + /// + /// Check that jobs are preferentially given to players that have marked those jobs as higher priority. + /// + [Test] + public async Task JobPriorityTest() + { + await using var pair = await PoolManager.GetServerClient(new PoolSettings + { + DummyTicker = false, + Connected = true, + InLobby = true + }); + + pair.Server.CfgMan.SetCVar(CCVars.GameMap, _map); + var ticker = pair.Server.System(); + Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby)); + Assert.That(pair.Client.AttachedEntity, Is.Null); + + await pair.Server.AddDummySessions(5); + await pair.RunTicksSync(5); + + var engineers = pair.Server.PlayerMan.Sessions.Select(x => x.UserId).ToList(); + var captain = engineers[3]; + engineers.RemoveAt(3); + + await pair.SetJobPriorities(captain, (_captain, JobPriority.High), (_engineer, JobPriority.Medium)); + foreach (var engi in engineers) + { + await pair.SetJobPriorities(engi, (_captain, JobPriority.Medium), (_engineer, JobPriority.High)); + } + + ticker.ToggleReadyAll(true); + await pair.Server.WaitPost(() => ticker.StartRound()); + await pair.RunTicksSync(10); + + AssertJob(pair, _captain, captain); + Assert.Multiple(() => + { + foreach (var engi in engineers) + { + AssertJob(pair, _engineer, engi); + } + }); + + await pair.Server.WaitPost(() => ticker.RestartRound()); + await pair.CleanReturnAsync(); + } +} diff --git a/Content.IntegrationTests/Tests/Station/StationJobsTest.cs b/Content.IntegrationTests/Tests/Station/StationJobsTest.cs index 0085472c33..d68fdafb76 100644 --- a/Content.IntegrationTests/Tests/Station/StationJobsTest.cs +++ b/Content.IntegrationTests/Tests/Station/StationJobsTest.cs @@ -7,7 +7,6 @@ using Content.Shared.Preferences; using Content.Shared.Roles; using Robust.Shared.GameObjects; using Robust.Shared.Log; -using Robust.Shared.Map; using Robust.Shared.Network; using Robust.Shared.Prototypes; using Robust.Shared.Timing; @@ -46,8 +45,6 @@ public sealed class StationJobsTest stationProto: StandardNanotrasenStation components: - type: StationJobs - overflowJobs: - - Passenger availableJobs: TMime: [0, -1] TAssistant: [-1, -1] @@ -164,7 +161,6 @@ public sealed class StationJobsTest var server = pair.Server; var prototypeManager = server.ResolveDependency(); - var mapManager = server.ResolveDependency(); var fooStationProto = prototypeManager.Index("FooStation"); var entSysMan = server.ResolveDependency().EntitySysManager; var stationJobs = entSysMan.GetEntitySystem(); @@ -215,6 +211,8 @@ public sealed class StationJobsTest var server = pair.Server; var prototypeManager = server.ResolveDependency(); + var compFact = server.ResolveDependency(); + var name = compFact.GetComponentName(); await server.WaitAssertion(() => { @@ -233,11 +231,14 @@ public sealed class StationJobsTest { foreach (var (stationId, station) in gameMap.Stations) { - if (!station.StationComponentOverrides.TryGetComponent("StationJobs", out var comp)) + if (!station.StationComponentOverrides.TryGetComponent(name, out var comp)) continue; - foreach (var (job, _) in ((StationJobsComponent) comp).SetupAvailableJobs) + foreach (var (job, array) in ((StationJobsComponent) comp).SetupAvailableJobs) { + Assert.That(array.Length, Is.EqualTo(2)); + Assert.That(array[0] is -1 or >= 0); + Assert.That(array[1] is -1 or >= 0); Assert.That(invalidJobs, Does.Not.Contain(job), $"Station {stationId} contains job prototype {job} which cannot be present roundstart."); } } diff --git a/Content.IntegrationTests/Tests/Storage/StorageInteractionTest.cs b/Content.IntegrationTests/Tests/Storage/StorageInteractionTest.cs new file mode 100644 index 0000000000..8d0de707f3 --- /dev/null +++ b/Content.IntegrationTests/Tests/Storage/StorageInteractionTest.cs @@ -0,0 +1,87 @@ +using Content.Client.UserInterface.Systems.Hotbar.Widgets; +using Content.Client.UserInterface.Systems.Storage.Controls; +using Content.IntegrationTests.Tests.Interaction; +using Content.Shared.Input; +using Content.Shared.PDA; +using Content.Shared.Storage; +using Content.Shared.Timing; +using Robust.Client.UserInterface; +using Robust.Shared.Containers; +using Robust.Shared.GameObjects; + +namespace Content.IntegrationTests.Tests.Storage; + +public sealed class StorageInteractionTest : InteractionTest +{ + /// + /// Check that players can interact with items in storage if the storage UI is open + /// + [Test] + public async Task UiInteractTest() + { + var sys = Server.System(); + + await SpawnTarget("ClothingBackpack"); + var backpack = ToServer(Target); + + // Initially no BUI is open. + Assert.That(IsUiOpen(StorageComponent.StorageUiKey.Key), Is.False); + Assert.That(IsUiOpen(PdaUiKey.Key), Is.False); + + await Server.WaitPost(() => SEntMan.RemoveComponent(STarget!.Value)); + await RunTicks(5); + + // Activating the backpack opens the UI + await Activate(); + Assert.That(IsUiOpen(StorageComponent.StorageUiKey.Key), Is.True); + Assert.That(IsUiOpen(PdaUiKey.Key), Is.False); + + // Activating it again closes the UI + await Activate(); + Assert.That(IsUiOpen(StorageComponent.StorageUiKey.Key), Is.False); + + // Open it again + await Activate(); + Assert.That(IsUiOpen(StorageComponent.StorageUiKey.Key), Is.True); + + // Pick up a PDA + var pda = await PlaceInHands("PassengerPDA"); + var sPda = ToServer(pda); + Assert.That(sys.IsEntityInContainer(sPda), Is.True); + Assert.That(sys.TryGetContainingContainer((sPda, null), out var container)); + Assert.That(container!.Owner, Is.EqualTo(SPlayer)); + + // Insert the PDA into the backpack + await Interact(); + Assert.That(sys.TryGetContainingContainer((sPda, null), out container)); + Assert.That(container!.Owner, Is.EqualTo(backpack)); + + // Use "e" / ActivateInWorld to open the PDA UI while it is still in the backpack. + var ctrl = GetStorageControl(pda); + await ClickControl(ctrl, ContentKeyFunctions.ActivateItemInWorld); + await RunTicks(10); + Assert.That(IsUiOpen(StorageComponent.StorageUiKey.Key), Is.True); + Assert.That(IsUiOpen(PdaUiKey.Key), Is.True); + + // Click on the pda to pick it up and remove it from the backpack. + await ClickControl(ctrl, ContentKeyFunctions.MoveStoredItem); + await RunTicks(10); + Assert.That(sys.TryGetContainingContainer((sPda, null), out container)); + Assert.That(container!.Owner, Is.EqualTo(SPlayer)); + + // UIs should still be open + Assert.That(IsUiOpen(StorageComponent.StorageUiKey.Key), Is.True); + Assert.That(IsUiOpen(PdaUiKey.Key), Is.True); + } + + /// + /// Retrieve the control that corresponds to the given entity in the currently open storage UI. + /// + private ItemGridPiece GetStorageControl(NetEntity target) + { + var uid = ToClient(target); + var hotbar = GetWidget(); + var storageContainer = GetControlFromField(nameof(HotbarGui.StorageContainer), hotbar); + return GetControlFromChildren(c => c.Entity == uid, storageContainer); + } +} diff --git a/Content.IntegrationTests/Tests/Tag/TagTest.cs b/Content.IntegrationTests/Tests/Tag/TagTest.cs index cbcdd1c6c6..e6cd2accaf 100644 --- a/Content.IntegrationTests/Tests/Tag/TagTest.cs +++ b/Content.IntegrationTests/Tests/Tag/TagTest.cs @@ -53,11 +53,13 @@ namespace Content.IntegrationTests.Tests.Tag EntityUid sTagDummy = default; TagComponent sTagComponent = null!; + Entity sTagEntity = default; await server.WaitPost(() => { sTagDummy = sEntityManager.SpawnEntity(TagEntityId, MapCoordinates.Nullspace); sTagComponent = sEntityManager.GetComponent(sTagDummy); + sTagEntity = new Entity(sTagDummy, sTagComponent); }); await server.WaitAssertion(() => @@ -130,49 +132,64 @@ namespace Content.IntegrationTests.Tests.Tag Assert.Multiple(() => { // Cannot add the starting tag again - Assert.That(tagSystem.AddTag(sTagDummy, sTagComponent, StartingTag), Is.False); - Assert.That(tagSystem.AddTags(sTagDummy, sTagComponent, StartingTag, StartingTag), Is.False); - Assert.That(tagSystem.AddTags(sTagDummy, sTagComponent, new List { StartingTag, StartingTag }), Is.False); + Assert.That(tagSystem.AddTag(sTagEntity, StartingTag), Is.False); + + Assert.That(tagSystem.AddTags(sTagEntity, StartingTag, StartingTag), Is.False); + Assert.That(tagSystem.AddTags(sTagEntity, new List> { StartingTag, StartingTag }), Is.False); + Assert.That(tagSystem.AddTags(sTagEntity, new HashSet> { StartingTag, StartingTag }), Is.False); // Has the starting tag Assert.That(tagSystem.HasTag(sTagComponent, StartingTag), Is.True); + Assert.That(tagSystem.HasAllTags(sTagComponent, StartingTag, StartingTag), Is.True); - Assert.That(tagSystem.HasAllTags(sTagComponent, new List { StartingTag, StartingTag }), Is.True); + Assert.That(tagSystem.HasAllTags(sTagComponent, new List> { StartingTag, StartingTag }), Is.True); + Assert.That(tagSystem.HasAllTags(sTagComponent, new HashSet> { StartingTag, StartingTag }), Is.True); + Assert.That(tagSystem.HasAnyTag(sTagComponent, StartingTag, StartingTag), Is.True); - Assert.That(tagSystem.HasAnyTag(sTagComponent, new List { StartingTag, StartingTag }), Is.True); + Assert.That(tagSystem.HasAnyTag(sTagComponent, new List> { StartingTag, StartingTag }), Is.True); + Assert.That(tagSystem.HasAnyTag(sTagComponent, new HashSet> { StartingTag, StartingTag }), Is.True); // Does not have the added tag yet Assert.That(tagSystem.HasTag(sTagComponent, AddedTag), Is.False); + Assert.That(tagSystem.HasAllTags(sTagComponent, AddedTag, AddedTag), Is.False); - Assert.That(tagSystem.HasAllTags(sTagComponent, new List { AddedTag, AddedTag }), Is.False); + Assert.That(tagSystem.HasAllTags(sTagComponent, new List> { AddedTag, AddedTag }), Is.False); + Assert.That(tagSystem.HasAllTags(sTagComponent, new HashSet> { AddedTag, AddedTag }), Is.False); + Assert.That(tagSystem.HasAnyTag(sTagComponent, AddedTag, AddedTag), Is.False); - Assert.That(tagSystem.HasAnyTag(sTagComponent, new List { AddedTag, AddedTag }), Is.False); + Assert.That(tagSystem.HasAnyTag(sTagComponent, new List> { AddedTag, AddedTag }), Is.False); + Assert.That(tagSystem.HasAnyTag(sTagComponent, new HashSet> { AddedTag, AddedTag }), Is.False); // Has a combination of the two tags Assert.That(tagSystem.HasAnyTag(sTagComponent, StartingTag, AddedTag), Is.True); - Assert.That(tagSystem.HasAnyTag(sTagComponent, new List { StartingTag, AddedTag }), Is.True); + Assert.That(tagSystem.HasAnyTag(sTagComponent, new List> { StartingTag, AddedTag }), Is.True); + Assert.That(tagSystem.HasAnyTag(sTagComponent, new HashSet> { StartingTag, AddedTag }), Is.True); // Does not have both tags Assert.That(tagSystem.HasAllTags(sTagComponent, StartingTag, AddedTag), Is.False); - Assert.That(tagSystem.HasAllTags(sTagComponent, new List { StartingTag, AddedTag }), Is.False); + Assert.That(tagSystem.HasAllTags(sTagComponent, new List> { StartingTag, AddedTag }), Is.False); + Assert.That(tagSystem.HasAllTags(sTagComponent, new HashSet> { StartingTag, AddedTag }), Is.False); // Cannot remove a tag that does not exist - Assert.That(tagSystem.RemoveTag(sTagDummy, sTagComponent, AddedTag), Is.False); - Assert.That(tagSystem.RemoveTags(sTagDummy, sTagComponent, AddedTag, AddedTag), Is.False); - Assert.That(tagSystem.RemoveTags(sTagDummy, sTagComponent, new List { AddedTag, AddedTag }), Is.False); + Assert.That(tagSystem.RemoveTag(sTagEntity, AddedTag), Is.False); + + Assert.That(tagSystem.RemoveTags(sTagEntity, AddedTag, AddedTag), Is.False); + Assert.That(tagSystem.RemoveTags(sTagEntity, new List> { AddedTag, AddedTag }), Is.False); + Assert.That(tagSystem.RemoveTags(sTagEntity, new HashSet> { AddedTag, AddedTag }), Is.False); }); // Can add the new tag - Assert.That(tagSystem.AddTag(sTagDummy, sTagComponent, AddedTag), Is.True); + Assert.That(tagSystem.AddTag(sTagEntity, AddedTag), Is.True); Assert.Multiple(() => { // Cannot add it twice - Assert.That(tagSystem.AddTag(sTagDummy, sTagComponent, AddedTag), Is.False); + Assert.That(tagSystem.AddTag(sTagEntity, AddedTag), Is.False); // Cannot add existing tags - Assert.That(tagSystem.AddTags(sTagDummy, sTagComponent, StartingTag, AddedTag), Is.False); - Assert.That(tagSystem.AddTags(sTagDummy, sTagComponent, new List { StartingTag, AddedTag }), Is.False); + Assert.That(tagSystem.AddTags(sTagEntity, StartingTag, AddedTag), Is.False); + Assert.That(tagSystem.AddTags(sTagEntity, new List> { StartingTag, AddedTag }), Is.False); + Assert.That(tagSystem.AddTags(sTagEntity, new HashSet> { StartingTag, AddedTag }), Is.False); // Now has two tags Assert.That(sTagComponent.Tags, Has.Count.EqualTo(2)); @@ -180,65 +197,103 @@ namespace Content.IntegrationTests.Tests.Tag // Has both tags Assert.That(tagSystem.HasTag(sTagComponent, StartingTag), Is.True); Assert.That(tagSystem.HasTag(sTagComponent, AddedTag), Is.True); + Assert.That(tagSystem.HasAllTags(sTagComponent, StartingTag, StartingTag), Is.True); Assert.That(tagSystem.HasAllTags(sTagComponent, AddedTag, StartingTag), Is.True); - Assert.That(tagSystem.HasAllTags(sTagComponent, new List { StartingTag, AddedTag }), Is.True); - Assert.That(tagSystem.HasAllTags(sTagComponent, new List { AddedTag, StartingTag }), Is.True); + Assert.That(tagSystem.HasAllTags(sTagComponent, new List> { StartingTag, AddedTag }), Is.True); + Assert.That(tagSystem.HasAllTags(sTagComponent, new List> { AddedTag, StartingTag }), Is.True); + Assert.That(tagSystem.HasAllTags(sTagComponent, new HashSet> { StartingTag, AddedTag }), Is.True); + Assert.That(tagSystem.HasAllTags(sTagComponent, new HashSet> { AddedTag, StartingTag }), Is.True); + Assert.That(tagSystem.HasAnyTag(sTagComponent, StartingTag, AddedTag), Is.True); Assert.That(tagSystem.HasAnyTag(sTagComponent, AddedTag, StartingTag), Is.True); + Assert.That(tagSystem.HasAnyTag(sTagComponent, new List> { StartingTag, AddedTag }), Is.True); + Assert.That(tagSystem.HasAnyTag(sTagComponent, new List> { AddedTag, StartingTag }), Is.True); + Assert.That(tagSystem.HasAnyTag(sTagComponent, new HashSet> { StartingTag, AddedTag }), Is.True); + Assert.That(tagSystem.HasAnyTag(sTagComponent, new HashSet> { AddedTag, StartingTag }), Is.True); }); Assert.Multiple(() => { // Remove the existing starting tag - Assert.That(tagSystem.RemoveTag(sTagDummy, sTagComponent, StartingTag), Is.True); + Assert.That(tagSystem.RemoveTag(sTagEntity, StartingTag), Is.True); // Remove the existing added tag - Assert.That(tagSystem.RemoveTags(sTagDummy, sTagComponent, AddedTag, AddedTag), Is.True); + Assert.That(tagSystem.RemoveTags(sTagEntity, AddedTag, AddedTag), Is.True); }); Assert.Multiple(() => { // No tags left to remove - Assert.That(tagSystem.RemoveTags(sTagDummy, sTagComponent, new List { StartingTag, AddedTag }), Is.False); + Assert.That(tagSystem.RemoveTags(sTagEntity, new List> { StartingTag, AddedTag }), Is.False); // No tags left in the component Assert.That(sTagComponent.Tags, Is.Empty); }); -#if !DEBUG - return; + // It is run only in DEBUG build, + // as the checks are performed only in DEBUG build. +#if DEBUG + // Has single + Assert.Throws(() => { tagSystem.HasTag(sTagDummy, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.HasTag(sTagComponent, UnregisteredTag); }); + + // HasAny entityUid methods + Assert.Throws(() => { tagSystem.HasAnyTag(sTagDummy, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.HasAnyTag(sTagDummy, UnregisteredTag, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.HasAnyTag(sTagDummy, new List> { UnregisteredTag }); }); + Assert.Throws(() => { tagSystem.HasAnyTag(sTagDummy, new HashSet> { UnregisteredTag }); }); + + // HasAny component methods + Assert.Throws(() => { tagSystem.HasAnyTag(sTagComponent, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.HasAnyTag(sTagComponent, UnregisteredTag, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.HasAnyTag(sTagComponent, new List> { UnregisteredTag }); }); + Assert.Throws(() => { tagSystem.HasAnyTag(sTagComponent, new HashSet> { UnregisteredTag }); }); + + // HasAll entityUid methods + Assert.Throws(() => { tagSystem.HasAllTags(sTagDummy, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.HasAllTags(sTagDummy, UnregisteredTag, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.HasAllTags(sTagDummy, new List> { UnregisteredTag }); }); + Assert.Throws(() => { tagSystem.HasAllTags(sTagDummy, new HashSet> { UnregisteredTag }); }); + + // HasAll component methods + Assert.Throws(() => { tagSystem.HasAllTags(sTagComponent, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.HasAllTags(sTagComponent, UnregisteredTag, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.HasAllTags(sTagComponent, new List> { UnregisteredTag }); }); + Assert.Throws(() => { tagSystem.HasAllTags(sTagComponent, new HashSet> { UnregisteredTag }); }); + + // RemoveTag single + Assert.Throws(() => { tagSystem.RemoveTag(sTagDummy, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.RemoveTag(sTagEntity, UnregisteredTag); }); + + // RemoveTags entityUid methods + Assert.Throws(() => { tagSystem.RemoveTags(sTagDummy, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.RemoveTags(sTagDummy, UnregisteredTag, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.RemoveTags(sTagDummy, new List> { UnregisteredTag }); }); + Assert.Throws(() => { tagSystem.RemoveTags(sTagDummy, new HashSet> { UnregisteredTag }); }); + + // RemoveTags entity methods + Assert.Throws(() => { tagSystem.RemoveTags(sTagEntity, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.RemoveTags(sTagEntity, UnregisteredTag, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.RemoveTags(sTagEntity, new List> { UnregisteredTag }); }); + Assert.Throws(() => { tagSystem.RemoveTags(sTagEntity, new HashSet> { UnregisteredTag }); }); + + // AddTag single + Assert.Throws(() => { tagSystem.AddTag(sTagDummy, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.AddTag(sTagEntity, UnregisteredTag); }); + + // AddTags entityUid methods + Assert.Throws(() => { tagSystem.AddTags(sTagDummy, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.AddTags(sTagDummy, UnregisteredTag, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.AddTags(sTagDummy, new List> { UnregisteredTag }); }); + Assert.Throws(() => { tagSystem.AddTags(sTagDummy, new HashSet> { UnregisteredTag }); }); + + // AddTags entity methods + Assert.Throws(() => { tagSystem.AddTags(sTagEntity, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.AddTags(sTagEntity, UnregisteredTag, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.AddTags(sTagEntity, new List> { UnregisteredTag }); }); + Assert.Throws(() => { tagSystem.AddTags(sTagEntity, new HashSet> { UnregisteredTag }); }); #endif - - // Single - Assert.Throws(() => - { - tagSystem.HasTag(sTagDummy, UnregisteredTag); - }); - Assert.Throws(() => - { - tagSystem.HasTag(sTagComponent, UnregisteredTag); - }); - - // Any - Assert.Throws(() => - { - tagSystem.HasAnyTag(sTagDummy, UnregisteredTag); - }); - Assert.Throws(() => - { - tagSystem.HasAnyTag(sTagComponent, UnregisteredTag); - }); - - // All - Assert.Throws(() => - { - tagSystem.HasAllTags(sTagDummy, UnregisteredTag); - }); - Assert.Throws(() => - { - tagSystem.HasAllTags(sTagComponent, UnregisteredTag); - }); }); await pair.CleanReturnAsync(); } diff --git a/Content.IntegrationTests/Tests/Tiles/TileConstructionTests.cs b/Content.IntegrationTests/Tests/Tiles/TileConstructionTests.cs index 083e817d69..6ea8b6882a 100644 --- a/Content.IntegrationTests/Tests/Tiles/TileConstructionTests.cs +++ b/Content.IntegrationTests/Tests/Tiles/TileConstructionTests.cs @@ -15,10 +15,10 @@ public sealed class TileConstructionTests : InteractionTest await AssertTile(Plating, PlayerCoords); AssertGridCount(1); await SetTile(null); - await Interact(Rod); + await InteractUsing(Rod); await AssertTile(Lattice); Assert.That(Hands.ActiveHandEntity, Is.Null); - await Interact(Cut); + await InteractUsing(Cut); await AssertTile(null); await AssertEntityLookup((Rod, 1)); AssertGridCount(1); @@ -43,14 +43,14 @@ public sealed class TileConstructionTests : InteractionTest // Place Lattice var oldPos = TargetCoords; TargetCoords = SEntMan.GetNetCoordinates(new EntityCoordinates(MapData.MapUid, 1, 0)); - await Interact(Rod); + await InteractUsing(Rod); TargetCoords = oldPos; await AssertTile(Lattice); AssertGridCount(1); // Cut lattice Assert.That(Hands.ActiveHandEntity, Is.Null); - await Interact(Cut); + await InteractUsing(Cut); await AssertTile(null); AssertGridCount(0); @@ -76,25 +76,25 @@ public sealed class TileConstructionTests : InteractionTest // Space -> Lattice var oldPos = TargetCoords; TargetCoords = SEntMan.GetNetCoordinates(new EntityCoordinates(MapData.MapUid, 1, 0)); - await Interact(Rod); + await InteractUsing(Rod); TargetCoords = oldPos; await AssertTile(Lattice); AssertGridCount(1); // Lattice -> Plating - await Interact(Steel); + await InteractUsing(Steel); Assert.That(Hands.ActiveHandEntity, Is.Null); await AssertTile(Plating); AssertGridCount(1); // Plating -> Tile - await Interact(FloorItem); + await InteractUsing(FloorItem); Assert.That(Hands.ActiveHandEntity, Is.Null); await AssertTile(Floor); AssertGridCount(1); // Tile -> Plating - await Interact(Pry); + await InteractUsing(Pry); await AssertTile(Plating); AssertGridCount(1); diff --git a/Content.IntegrationTests/Tests/Weldable/WeldableTests.cs b/Content.IntegrationTests/Tests/Weldable/WeldableTests.cs index 6227f3dee1..e7eadeda0a 100644 --- a/Content.IntegrationTests/Tests/Weldable/WeldableTests.cs +++ b/Content.IntegrationTests/Tests/Weldable/WeldableTests.cs @@ -18,7 +18,7 @@ public sealed class WeldableTests : InteractionTest Assert.That(comp.IsWelded, Is.False); - await Interact(Weld); + await InteractUsing(Weld); Assert.That(comp.IsWelded, Is.True); AssertPrototype(Locker); // Prototype did not change. } diff --git a/Content.Packaging/Program.cs b/Content.Packaging/Program.cs index 65c0e0131a..9457e9dacc 100644 --- a/Content.Packaging/Program.cs +++ b/Content.Packaging/Program.cs @@ -11,6 +11,11 @@ if (!CommandLineArgs.TryParse(args, out var parsed)) if (parsed.WipeRelease) WipeRelease(); +else +{ + // Ensure the release directory exists. Otherwise, the packaging will fail. + Directory.CreateDirectory("release"); +} if (!parsed.SkipBuild) WipeBin(); diff --git a/Content.Server/Access/Components/AgentIDCardComponent.cs b/Content.Server/Access/Components/AgentIDCardComponent.cs index 4b92b43ea9..7a97c5f565 100644 --- a/Content.Server/Access/Components/AgentIDCardComponent.cs +++ b/Content.Server/Access/Components/AgentIDCardComponent.cs @@ -1,5 +1,5 @@ using Content.Shared.StatusIcon; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set; +using Robust.Shared.Prototypes; namespace Content.Server.Access.Components { @@ -9,7 +9,7 @@ namespace Content.Server.Access.Components /// /// Set of job icons that the agent ID card can show. /// - [DataField("icons", customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] - public HashSet Icons = new(); + [DataField] + public HashSet> Icons; } } diff --git a/Content.Server/Access/Systems/AgentIDCardSystem.cs b/Content.Server/Access/Systems/AgentIDCardSystem.cs index d5e9dc357d..f0ce3acbdb 100644 --- a/Content.Server/Access/Systems/AgentIDCardSystem.cs +++ b/Content.Server/Access/Systems/AgentIDCardSystem.cs @@ -90,14 +90,10 @@ namespace Content.Server.Access.Systems private void OnJobIconChanged(EntityUid uid, AgentIDCardComponent comp, AgentIDCardJobIconChangedMessage args) { if (!TryComp(uid, out var idCard)) - { return; - } - if (!_prototypeManager.TryIndex(args.JobIconId, out var jobIcon)) - { + if (!_prototypeManager.TryIndex(args.JobIconId, out var jobIcon)) return; - } _cardSystem.TryChangeJobIcon(uid, jobIcon, idCard); @@ -109,7 +105,7 @@ namespace Content.Server.Access.Systems { foreach (var jobPrototype in _prototypeManager.EnumeratePrototypes()) { - if(jobPrototype.Icon == jobIcon.ID) + if (jobPrototype.Icon == jobIcon.ID) { job = jobPrototype; return true; diff --git a/Content.Server/Access/Systems/IdCardConsoleSystem.cs b/Content.Server/Access/Systems/IdCardConsoleSystem.cs index 4e63c93a83..e02664f2bb 100644 --- a/Content.Server/Access/Systems/IdCardConsoleSystem.cs +++ b/Content.Server/Access/Systems/IdCardConsoleSystem.cs @@ -129,7 +129,7 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem _idCard.TryChangeJobTitle(targetId, newJobTitle, player: player); if (_prototype.TryIndex(newJobProto, out var job) - && _prototype.TryIndex(job.Icon, out var jobIcon)) + && _prototype.TryIndex(job.Icon, out var jobIcon)) { _idCard.TryChangeJobIcon(targetId, jobIcon, player: player); _idCard.TryChangeJobDepartment(targetId, job); diff --git a/Content.Server/Access/Systems/PresetIdCardSystem.cs b/Content.Server/Access/Systems/PresetIdCardSystem.cs index 2f884764ca..7aed03b74a 100644 --- a/Content.Server/Access/Systems/PresetIdCardSystem.cs +++ b/Content.Server/Access/Systems/PresetIdCardSystem.cs @@ -106,9 +106,7 @@ public sealed class PresetIdCardSystem : EntitySystem _cardSystem.TryChangeJobTitle(uid, job.LocalizedName); _cardSystem.TryChangeJobDepartment(uid, job); - if (_prototypeManager.TryIndex(job.Icon, out var jobIcon)) - { + if (_prototypeManager.TryIndex(job.Icon, out var jobIcon)) _cardSystem.TryChangeJobIcon(uid, jobIcon); - } } } diff --git a/Content.Server/Administration/Logs/AdminLogManager.Json.cs b/Content.Server/Administration/Logs/AdminLogManager.Json.cs index 0a67d61cef..9e6274a493 100644 --- a/Content.Server/Administration/Logs/AdminLogManager.Json.cs +++ b/Content.Server/Administration/Logs/AdminLogManager.Json.cs @@ -65,6 +65,10 @@ public sealed partial class AdminLogManager { players.Add(actor.PlayerSession.UserId.UserId); } + else if (value is SerializablePlayer player) + { + players.Add(player.Player.UserId.UserId); + } } return (JsonSerializer.SerializeToDocument(parsed, _jsonOptions), players); diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.cs b/Content.Server/Administration/Systems/AdminVerbSystem.cs index 7aa4c8b400..d7888b491c 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.cs @@ -131,59 +131,6 @@ namespace Content.Server.Administration.Systems prayerVerb.Impact = LogImpact.Low; args.Verbs.Add(prayerVerb); - // Freeze - var frozen = TryComp(args.Target, out var frozenComp); - var frozenAndMuted = frozenComp?.Muted ?? false; - - if (!frozen) - { - args.Verbs.Add(new Verb - { - Priority = -1, // This is just so it doesn't change position in the menu between freeze/unfreeze. - Text = Loc.GetString("admin-verbs-freeze"), - Category = VerbCategory.Admin, - Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")), - Act = () => - { - EnsureComp(args.Target); - }, - Impact = LogImpact.Medium, - }); - } - - if (!frozenAndMuted) - { - // allow you to additionally mute someone when they are already frozen - args.Verbs.Add(new Verb - { - Priority = -1, // This is just so it doesn't change position in the menu between freeze/unfreeze. - Text = Loc.GetString("admin-verbs-freeze-and-mute"), - Category = VerbCategory.Admin, - Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")), - Act = () => - { - _freeze.FreezeAndMute(args.Target); - }, - Impact = LogImpact.Medium, - }); - } - - if (frozen) - { - args.Verbs.Add(new Verb - { - Priority = -1, // This is just so it doesn't change position in the menu between freeze/unfreeze. - Text = Loc.GetString("admin-verbs-unfreeze"), - Category = VerbCategory.Admin, - Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")), - Act = () => - { - RemComp(args.Target); - }, - Impact = LogImpact.Medium, - }); - } - // Erase args.Verbs.Add(new Verb { @@ -263,6 +210,60 @@ namespace Content.Server.Administration.Systems }); } + // Freeze + var frozen = TryComp(args.Target, out var frozenComp); + var frozenAndMuted = frozenComp?.Muted ?? false; + + if (!frozen) + { + args.Verbs.Add(new Verb + { + Priority = -1, // This is just so it doesn't change position in the menu between freeze/unfreeze. + Text = Loc.GetString("admin-verbs-freeze"), + Category = VerbCategory.Admin, + Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")), + Act = () => + { + EnsureComp(args.Target); + }, + Impact = LogImpact.Medium, + }); + } + + if (!frozenAndMuted) + { + // allow you to additionally mute someone when they are already frozen + args.Verbs.Add(new Verb + { + Priority = -1, // This is just so it doesn't change position in the menu between freeze/unfreeze. + Text = Loc.GetString("admin-verbs-freeze-and-mute"), + Category = VerbCategory.Admin, + Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")), + Act = () => + { + _freeze.FreezeAndMute(args.Target); + }, + Impact = LogImpact.Medium, + }); + } + + if (frozen) + { + args.Verbs.Add(new Verb + { + Priority = -1, // This is just so it doesn't change position in the menu between freeze/unfreeze. + Text = Loc.GetString("admin-verbs-unfreeze"), + Category = VerbCategory.Admin, + Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")), + Act = () => + { + RemComp(args.Target); + }, + Impact = LogImpact.Medium, + }); + } + + // Admin Logs if (_adminManager.HasAdminFlag(player, AdminFlags.Logs)) { diff --git a/Content.Server/Administration/Toolshed/TagCommand.cs b/Content.Server/Administration/Toolshed/TagCommand.cs index 1af2779766..e1cf53e1b1 100644 --- a/Content.Server/Administration/Toolshed/TagCommand.cs +++ b/Content.Server/Administration/Toolshed/TagCommand.cs @@ -1,6 +1,7 @@ using System.Linq; using Content.Shared.Administration; using Content.Shared.Tag; +using Robust.Shared.Prototypes; using Robust.Shared.Toolshed; using Robust.Shared.Toolshed.Syntax; using Robust.Shared.Toolshed.TypeParsers; @@ -13,14 +14,14 @@ public sealed class TagCommand : ToolshedCommand private TagSystem? _tag; [CommandImplementation("list")] - public IEnumerable List([PipedArgument] IEnumerable ent) + public IEnumerable> List([PipedArgument] IEnumerable ent) { return ent.SelectMany(x => { if (TryComp(x, out var tags)) // Note: Cast is required for C# to figure out the type signature. - return (IEnumerable)tags.Tags; - return Array.Empty(); + return (IEnumerable>)tags.Tags; + return Array.Empty>(); }); } @@ -72,7 +73,7 @@ public sealed class TagCommand : ToolshedCommand ) { _tag ??= GetSys(); - _tag.AddTags(input, @ref.Evaluate(ctx)!); + _tag.AddTags(input, (IEnumerable>)@ref.Evaluate(ctx)!); return input; } @@ -92,7 +93,7 @@ public sealed class TagCommand : ToolshedCommand ) { _tag ??= GetSys(); - _tag.RemoveTags(input, @ref.Evaluate(ctx)!); + _tag.RemoveTags(input, (IEnumerable>)@ref.Evaluate(ctx)!); return input; } diff --git a/Content.Server/Antag/AntagSelectionSystem.cs b/Content.Server/Antag/AntagSelectionSystem.cs index 47da1b6475..3491cc727c 100644 --- a/Content.Server/Antag/AntagSelectionSystem.cs +++ b/Content.Server/Antag/AntagSelectionSystem.cs @@ -182,20 +182,20 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem GameTicker.PlayerGameStatuses[x.UserId] == PlayerGameStatus.JoinedGame) .ToList(); - ChooseAntags((uid, component), players); + ChooseAntags((uid, component), players, midround: true); } /// /// Chooses antagonists from the given selection of players /// - public void ChooseAntags(Entity ent, IList pool) + public void ChooseAntags(Entity ent, IList pool, bool midround = false) { if (ent.Comp.SelectionsComplete) return; foreach (var def in ent.Comp.Definitions) { - ChooseAntags(ent, pool, def); + ChooseAntags(ent, pool, def, midround: midround); } ent.Comp.SelectionsComplete = true; @@ -204,17 +204,28 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem /// Chooses antagonists from the given selection of players for the given antag definition. /// - public void ChooseAntags(Entity ent, IList pool, AntagSelectionDefinition def) + /// Disable picking players for pre-spawn antags in the middle of a round + public void ChooseAntags(Entity ent, IList pool, AntagSelectionDefinition def, bool midround = false) { var playerPool = GetPlayerPool(ent, pool, def); var count = GetTargetAntagCount(ent, GetTotalPlayerCount(pool), def); // if there is both a spawner and players getting picked, let it fall back to a spawner. var noSpawner = def.SpawnerPrototype == null; + var picking = def.PickPlayer; + if (midround && ent.Comp.SelectionTime == AntagSelectionTime.PrePlayerSpawn) + { + // prevent antag selection from happening if the round is on-going, requiring a spawner if used midround. + // this is so rules like nukies, if added by an admin midround, dont make random living people nukies + Log.Info($"Antags for rule {ent:?} get picked pre-spawn so only spawners will be made."); + DebugTools.Assert(def.SpawnerPrototype != null, $"Rule {ent:?} had no spawner for pre-spawn rule added mid-round!"); + picking = false; + } + for (var i = 0; i < count; i++) { var session = (ICommonSession?) null; - if (def.PickPlayer) + if (picking) { if (!playerPool.TryPickAndTake(RobustRandom, out session) && noSpawner) { @@ -363,6 +374,9 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem public bool IsSessionValid(Entity ent, ICommonSession? session, AntagSelectionDefinition def, EntityUid? mind = null) { + // TODO ROLE TIMERS + // Check if antag role requirements are met + if (session == null) return true; diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.BreathTool.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.BreathTool.cs index 741a9341e7..327804f39a 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.BreathTool.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.BreathTool.cs @@ -10,21 +10,21 @@ public sealed partial class AtmosphereSystem SubscribeLocalEvent(OnBreathToolShutdown); } - private void OnBreathToolShutdown(EntityUid uid, BreathToolComponent component, ComponentShutdown args) + private void OnBreathToolShutdown(Entity entity, ref ComponentShutdown args) { - DisconnectInternals(component); + DisconnectInternals(entity); } - public void DisconnectInternals(BreathToolComponent component) + public void DisconnectInternals(Entity entity) { - var old = component.ConnectedInternalsEntity; - component.ConnectedInternalsEntity = null; + var old = entity.Comp.ConnectedInternalsEntity; + entity.Comp.ConnectedInternalsEntity = null; if (TryComp(old, out var internalsComponent)) { - _internals.DisconnectBreathTool((old.Value, internalsComponent)); + _internals.DisconnectBreathTool((old.Value, internalsComponent), entity.Owner); } - component.IsFunctional = false; + entity.Comp.IsFunctional = false; } } diff --git a/Content.Server/Atmos/EntitySystems/GasTankSystem.cs b/Content.Server/Atmos/EntitySystems/GasTankSystem.cs index 07594820fc..baad739804 100644 --- a/Content.Server/Atmos/EntitySystems/GasTankSystem.cs +++ b/Content.Server/Atmos/EntitySystems/GasTankSystem.cs @@ -220,7 +220,7 @@ namespace Content.Server.Atmos.EntitySystems public bool CanConnectToInternals(GasTankComponent component) { var internals = GetInternalsComponent(component, component.User); - return internals != null && internals.BreathToolEntity != null && !component.IsValveOpen; + return internals != null && internals.BreathTools.Count != 0 && !component.IsValveOpen; } public void ConnectToInternals(Entity ent) diff --git a/Content.Server/Body/Components/InternalsComponent.cs b/Content.Server/Body/Components/InternalsComponent.cs index 098f178921..ef908f9655 100644 --- a/Content.Server/Body/Components/InternalsComponent.cs +++ b/Content.Server/Body/Components/InternalsComponent.cs @@ -13,7 +13,7 @@ namespace Content.Server.Body.Components public EntityUid? GasTankEntity; [ViewVariables] - public EntityUid? BreathToolEntity; + public HashSet BreathTools { get; set; } = new(); /// /// Toggle Internals delay when the target is not you. diff --git a/Content.Server/Body/Systems/InternalsSystem.cs b/Content.Server/Body/Systems/InternalsSystem.cs index b79e083bd4..0e568b77cb 100644 --- a/Content.Server/Body/Systems/InternalsSystem.cs +++ b/Content.Server/Body/Systems/InternalsSystem.cs @@ -44,7 +44,7 @@ public sealed class InternalsSystem : EntitySystem private void OnStartingGear(EntityUid uid, InternalsComponent component, ref StartingGearEquippedEvent args) { - if (component.BreathToolEntity == null) + if (component.BreathTools.Count == 0) return; if (component.GasTankEntity != null) @@ -111,7 +111,7 @@ public sealed class InternalsSystem : EntitySystem } // If they're not on then check if we have a mask to use - if (internals.BreathToolEntity is null) + if (internals.BreathTools.Count == 0) { _popupSystem.PopupEntity(Loc.GetString("internals-no-breath-tool"), uid, user); return; @@ -178,28 +178,24 @@ public sealed class InternalsSystem : EntitySystem _alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent)); } } - public void DisconnectBreathTool(Entity ent) + public void DisconnectBreathTool(Entity ent, EntityUid toolEntity) { - var old = ent.Comp.BreathToolEntity; - ent.Comp.BreathToolEntity = null; + ent.Comp.BreathTools.Remove(toolEntity); - if (TryComp(old, out BreathToolComponent? breathTool)) - { - _atmos.DisconnectInternals(breathTool); + if (TryComp(toolEntity, out BreathToolComponent? breathTool)) + _atmos.DisconnectInternals((toolEntity, breathTool)); + + if (ent.Comp.BreathTools.Count == 0) DisconnectTank(ent); - } _alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent)); } public void ConnectBreathTool(Entity ent, EntityUid toolEntity) { - if (TryComp(ent.Comp.BreathToolEntity, out BreathToolComponent? tool)) - { - _atmos.DisconnectInternals(tool); - } + if (!ent.Comp.BreathTools.Add(toolEntity)) + return; - ent.Comp.BreathToolEntity = toolEntity; _alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent)); } @@ -217,7 +213,7 @@ public sealed class InternalsSystem : EntitySystem public bool TryConnectTank(Entity ent, EntityUid tankEntity) { - if (ent.Comp.BreathToolEntity is null) + if (ent.Comp.BreathTools.Count == 0) return false; if (TryComp(ent.Comp.GasTankEntity, out GasTankComponent? tank)) @@ -236,14 +232,14 @@ public sealed class InternalsSystem : EntitySystem public bool AreInternalsWorking(InternalsComponent component) { - return TryComp(component.BreathToolEntity, out BreathToolComponent? breathTool) + return TryComp(component.BreathTools.FirstOrNull(), out BreathToolComponent? breathTool) && breathTool.IsFunctional && HasComp(component.GasTankEntity); } private short GetSeverity(InternalsComponent component) { - if (component.BreathToolEntity is null || !AreInternalsWorking(component)) + if (component.BreathTools.Count == 0 || !AreInternalsWorking(component)) return 2; // If pressure in the tank is below low pressure threshold, flash warning on internals UI diff --git a/Content.Server/Body/Systems/LungSystem.cs b/Content.Server/Body/Systems/LungSystem.cs index 7e58c24f7e..a3c185d5cc 100644 --- a/Content.Server/Body/Systems/LungSystem.cs +++ b/Content.Server/Body/Systems/LungSystem.cs @@ -59,7 +59,7 @@ public sealed class LungSystem : EntitySystem { if (args.IsToggled || args.IsEquip) { - _atmos.DisconnectInternals(ent.Comp); + _atmos.DisconnectInternals(ent); } else { diff --git a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs index 0fcfd160bb..554b349b9b 100644 --- a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs +++ b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs @@ -300,6 +300,21 @@ public sealed partial class CargoSystem return IsBountyComplete(GetBountyEntities(container), entries, out bountyEntities); } + /// + /// Determines whether the meets the criteria for the bounty . + /// + /// true if is a valid item for the bounty entry, otherwise false + public bool IsValidBountyEntry(EntityUid entity, CargoBountyItemEntry entry) + { + if (!_whitelistSys.IsValid(entry.Whitelist, entity)) + return false; + + if (entry.Blacklist != null && _whitelistSys.IsValid(entry.Blacklist, entity)) + return false; + + return true; + } + public bool IsBountyComplete(HashSet entities, IEnumerable entries, out HashSet bountyEntities) { bountyEntities = new(); @@ -313,7 +328,7 @@ public sealed partial class CargoSystem var temp = new HashSet(); foreach (var entity in entities) { - if (!_whitelistSys.IsValid(entry.Whitelist, entity) || (entry.Blacklist != null && _whitelistSys.IsValid(entry.Blacklist, entity))) + if (!IsValidBountyEntry(entity, entry)) continue; count += _stackQuery.CompOrNull(entity)?.Count ?? 1; diff --git a/Content.Server/Chat/Systems/ChatSystem.Emote.cs b/Content.Server/Chat/Systems/ChatSystem.Emote.cs index dfb9347334..837b00b3ad 100644 --- a/Content.Server/Chat/Systems/ChatSystem.Emote.cs +++ b/Content.Server/Chat/Systems/ChatSystem.Emote.cs @@ -54,18 +54,20 @@ public partial class ChatSystem /// Whether or not this message should appear in the adminlog window /// Conceptual range of transmission, if it shows in the chat window, if it shows to far-away ghosts or ghosts at all... /// The name to use for the speaking entity. Usually this should just be modified via . If this is set, the event will not get raised. + /// Bypasses whitelist/blacklist/availibility checks for if the entity can use this emote public void TryEmoteWithChat( EntityUid source, string emoteId, ChatTransmitRange range = ChatTransmitRange.Normal, bool hideLog = false, string? nameOverride = null, - bool ignoreActionBlocker = false + bool ignoreActionBlocker = false, + bool forceEmote = false ) { if (!_prototypeManager.TryIndex(emoteId, out var proto)) return; - TryEmoteWithChat(source, proto, range, hideLog: hideLog, nameOverride, ignoreActionBlocker: ignoreActionBlocker); + TryEmoteWithChat(source, proto, range, hideLog: hideLog, nameOverride, ignoreActionBlocker: ignoreActionBlocker, forceEmote: forceEmote); } /// @@ -77,23 +79,18 @@ public partial class ChatSystem /// Whether or not this message should appear in the chat window /// Conceptual range of transmission, if it shows in the chat window, if it shows to far-away ghosts or ghosts at all... /// The name to use for the speaking entity. Usually this should just be modified via . If this is set, the event will not get raised. + /// Bypasses whitelist/blacklist/availibility checks for if the entity can use this emote public void TryEmoteWithChat( EntityUid source, EmotePrototype emote, ChatTransmitRange range = ChatTransmitRange.Normal, bool hideLog = false, string? nameOverride = null, - bool ignoreActionBlocker = false + bool ignoreActionBlocker = false, + bool forceEmote = false ) { - if (!(emote.Whitelist?.IsValid(source, EntityManager) ?? true)) - return; - if (emote.Blacklist?.IsValid(source, EntityManager) ?? false) - return; - - if (!emote.Available && - TryComp(source, out var speech) && - !speech.AllowedEmotes.Contains(emote.ID)) + if (!forceEmote && !AllowedToUseEmote(source, emote)) return; // check if proto has valid message for chat @@ -162,18 +159,46 @@ public partial class ChatSystem _audio.PlayPvs(sound, uid, param); return true; } - + /// + /// Checks if a valid emote was typed, to play sounds and etc and invokes an event. + /// + /// + /// private void TryEmoteChatInput(EntityUid uid, string textInput) { var actionLower = textInput.ToLower(); if (!_wordEmoteDict.TryGetValue(actionLower, out var emotes)) return; + foreach (var emote in emotes) // DeltaV - Multiple emotes for the same trigger + { + if (!AllowedToUseEmote(uid, emote)) + return; + } + foreach (var emote in emotes) // DeltaV - Multiple emotes for the same trigger { InvokeEmoteEvent(uid, emote); } } + /// + /// Checks if we can use this emote based on the emotes whitelist, blacklist, and availibility to the entity. + /// + /// The entity that is speaking + /// The emote being used + /// + private bool AllowedToUseEmote(EntityUid source, EmotePrototype emote) + { + if ((_whitelistSystem.IsWhitelistFail(emote.Whitelist, source) || _whitelistSystem.IsBlacklistPass(emote.Blacklist, source))) + return false; + + if (!emote.Available && + TryComp(source, out var speech) && + !speech.AllowedEmotes.Contains(emote.ID)) + return false; + + return true; + } private void InvokeEmoteEvent(EntityUid uid, EmotePrototype proto) { diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs index 6d2909860c..407183e3d9 100644 --- a/Content.Server/Chat/Systems/ChatSystem.cs +++ b/Content.Server/Chat/Systems/ChatSystem.cs @@ -4,6 +4,7 @@ using System.Text; using Content.Server.Administration.Logs; using Content.Server.Administration.Managers; using Content.Server.Chat.Managers; +using Content.Server.Examine; using Content.Server.GameTicking; using Content.Server.Speech.Components; using Content.Server.Speech.EntitySystems; @@ -17,6 +18,7 @@ using Content.Shared.Administration; using Content.Shared.CCVar; using Content.Shared.Chat; using Content.Shared.Database; +using Content.Shared.Examine; using Content.Shared.Ghost; using Content.Shared.Humanoid; using Content.Shared.IdentityManagement; @@ -25,6 +27,7 @@ using Content.Shared.Mobs.Systems; using Content.Shared.Players; using Content.Shared.Radio; using Content.Shared.Speech; +using Content.Shared.Whitelist; using Robust.Server.Player; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; @@ -61,6 +64,8 @@ public sealed partial class ChatSystem : SharedChatSystem [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; [Dependency] private readonly ReplacementAccentSystem _wordreplacement = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; + [Dependency] private readonly ExamineSystemShared _examineSystem = default!; //Nyano - Summary: pulls in the nyano chat system for psionics. [Dependency] private readonly NyanoChatSystem _nyanoChatSystem = default!; @@ -512,8 +517,7 @@ public sealed partial class ChatSystem : SharedChatSystem if (data.Range <= WhisperClearRange) _chatManager.ChatMessageToOne(ChatChannel.Whisper, message, wrappedMessage, source, false, session.Channel); //If listener is too far, they only hear fragments of the message - //Collisiongroup.Opaque is not ideal for this use. Preferably, there should be a check specifically with "Can Ent1 see Ent2" in mind - else if (_interactionSystem.InRangeUnobstructed(source, listener, WhisperMuffledRange, Shared.Physics.CollisionGroup.Opaque)) //Shared.Physics.CollisionGroup.Opaque + else if (_examineSystem.InRangeUnOccluded(source, listener, WhisperMuffledRange)) _chatManager.ChatMessageToOne(ChatChannel.Whisper, obfuscatedMessage, wrappedobfuscatedMessage, source, false, session.Channel); //If listener is too far and has no line of sight, they can't identify the whisperer's identity else diff --git a/Content.Server/Chemistry/ReagentEffects/Emote.cs b/Content.Server/Chemistry/ReagentEffects/Emote.cs index a4d49e4ad1..db6de6cc75 100644 --- a/Content.Server/Chemistry/ReagentEffects/Emote.cs +++ b/Content.Server/Chemistry/ReagentEffects/Emote.cs @@ -8,7 +8,7 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy namespace Content.Server.Chemistry.ReagentEffects; /// -/// Tries to force someone to emote (scream, laugh, etc). +/// Tries to force someone to emote (scream, laugh, etc). Still respects whitelists/blacklists and other limits of the specified emote unless forced. /// [UsedImplicitly] public sealed partial class Emote : ReagentEffect @@ -19,6 +19,9 @@ public sealed partial class Emote : ReagentEffect [DataField] public bool ShowInChat; + [DataField] + public bool Force = false; + // JUSTIFICATION: Emoting is flavor, so same reason popup messages are not in here. protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => null; @@ -30,7 +33,7 @@ public sealed partial class Emote : ReagentEffect var chatSys = args.EntityManager.System(); if (ShowInChat) - chatSys.TryEmoteWithChat(args.SolutionEntity, EmoteId, ChatTransmitRange.GhostRangeLimit); + chatSys.TryEmoteWithChat(args.SolutionEntity, EmoteId, ChatTransmitRange.GhostRangeLimit, forceEmote: Force); else chatSys.TryEmoteWithoutChat(args.SolutionEntity, EmoteId); diff --git a/Content.Server/Chemistry/TileReactions/CreateEntityTileReaction.cs b/Content.Server/Chemistry/TileReactions/CreateEntityTileReaction.cs index 29f9275bdf..6b106b1fc0 100644 --- a/Content.Server/Chemistry/TileReactions/CreateEntityTileReaction.cs +++ b/Content.Server/Chemistry/TileReactions/CreateEntityTileReaction.cs @@ -1,4 +1,4 @@ -using Content.Shared.Chemistry.Reaction; +using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reagent; using Content.Shared.FixedPoint; using Content.Shared.Maps; @@ -47,7 +47,8 @@ public sealed partial class CreateEntityTileReaction : ITileReaction int acc = 0; foreach (var ent in tile.GetEntitiesInTile()) { - if (Whitelist.IsValid(ent)) + var whitelistSystem = entityManager.System(); + if (whitelistSystem.IsWhitelistPass(Whitelist, ent)) acc += 1; if (acc >= MaxOnTile) diff --git a/Content.Server/Configurable/ConfigurationSystem.cs b/Content.Server/Configurable/ConfigurationSystem.cs index 5f5f1ef7d1..bf89c3f7ed 100644 --- a/Content.Server/Configurable/ConfigurationSystem.cs +++ b/Content.Server/Configurable/ConfigurationSystem.cs @@ -1,6 +1,7 @@ using Content.Shared.Configurable; using Content.Shared.Interaction; using Content.Shared.Tools.Components; +using Content.Shared.Tools.Systems; using Robust.Server.GameObjects; using Robust.Shared.Containers; using Robust.Shared.Player; @@ -11,6 +12,7 @@ namespace Content.Server.Configurable; public sealed class ConfigurationSystem : EntitySystem { [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; + [Dependency] private readonly SharedToolSystem _toolSystem = default!; public override void Initialize() { @@ -28,7 +30,7 @@ public sealed class ConfigurationSystem : EntitySystem if (args.Handled) return; - if (!TryComp(args.Used, out ToolComponent? tool) || !tool.Qualities.Contains(component.QualityNeeded)) + if (!_toolSystem.HasQuality(args.Used, component.QualityNeeded)) return; args.Handled = _uiSystem.TryOpenUi(uid, ConfigurationUiKey.Key, args.User); @@ -68,7 +70,7 @@ public sealed class ConfigurationSystem : EntitySystem private void OnInsert(EntityUid uid, ConfigurationComponent component, ContainerIsInsertingAttemptEvent args) { - if (!TryComp(args.EntityUid, out ToolComponent? tool) || !tool.Qualities.Contains(component.QualityNeeded)) + if (!_toolSystem.HasQuality(args.EntityUid, component.QualityNeeded)) return; args.Cancel(); diff --git a/Content.Server/Construction/ConstructionSystem.Initial.cs b/Content.Server/Construction/ConstructionSystem.Initial.cs index 04d3722c66..5161ac358a 100644 --- a/Content.Server/Construction/ConstructionSystem.Initial.cs +++ b/Content.Server/Construction/ConstructionSystem.Initial.cs @@ -14,6 +14,7 @@ using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Inventory; using Content.Shared.Storage; +using Content.Shared.Whitelist; using Robust.Shared.Containers; using Robust.Shared.Map; using Robust.Shared.Player; @@ -30,6 +31,7 @@ namespace Content.Server.Construction [Dependency] private readonly SharedHandsSystem _handsSystem = default!; [Dependency] private readonly EntityLookupSystem _lookupSystem = default!; [Dependency] private readonly SharedTransformSystem _transformSystem = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; // --- WARNING! LEGACY CODE AHEAD! --- // This entire file contains the legacy code for initial construction. @@ -337,7 +339,7 @@ namespace Content.Server.Construction return false; } - if (constructionPrototype.EntityWhitelist != null && !constructionPrototype.EntityWhitelist.IsValid(user)) + if (_whitelistSystem.IsWhitelistFail(constructionPrototype.EntityWhitelist, user)) { _popup.PopupEntity(Loc.GetString("construction-system-cannot-start"), user, user); return false; @@ -422,7 +424,7 @@ namespace Content.Server.Construction return; } - if (constructionPrototype.EntityWhitelist != null && !constructionPrototype.EntityWhitelist.IsValid(user)) + if (_whitelistSystem.IsWhitelistFail(constructionPrototype.EntityWhitelist, user)) { _popup.PopupEntity(Loc.GetString("construction-system-cannot-start"), user, user); return; diff --git a/Content.Server/Construction/MachineFrameSystem.cs b/Content.Server/Construction/MachineFrameSystem.cs index 09d8d413ec..e20c36d849 100644 --- a/Content.Server/Construction/MachineFrameSystem.cs +++ b/Content.Server/Construction/MachineFrameSystem.cs @@ -59,23 +59,27 @@ public sealed class MachineFrameSystem : EntitySystem return; } - // Machine parts cannot currently satisfy stack/component/tag restrictions. Similarly stacks cannot satisfy - // component/tag restrictions. However, there is no reason this cannot be supported in the future. If this - // changes, then RegenerateProgress() also needs to be updated. - // + // If this changes in the future, then RegenerateProgress() also needs to be updated. // Note that one entity is ALLOWED to satisfy more than one kind of component or tag requirements. This is // necessary in order to avoid weird entity-ordering shenanigans in RegenerateProgress(). + var stack = CompOrNull(args.Used); + var machinePart = CompOrNull(args.Used); + if (stack != null && machinePart != null) + { + if (TryInsertPartStack(uid, args.Used, component, machinePart, stack)) + args.Handled = true; + return; + } // Handle parts - if (TryComp(args.Used, out var machinePart)) + if (machinePart != null) { if (TryInsertPart(uid, args.Used, component, machinePart)) args.Handled = true; return; } - // Handle stacks - if (TryComp(args.Used, out var stack)) + if (stack != null) { if (TryInsertStack(uid, args.Used, component, stack)) args.Handled = true; @@ -191,6 +195,44 @@ public sealed class MachineFrameSystem : EntitySystem return true; } + /// Whether or not the function had any effect. Does not indicate success. + private bool TryInsertPartStack(EntityUid uid, EntityUid used, MachineFrameComponent component, MachinePartComponent machinePart, StackComponent stack) + { + if (!component.Requirements.ContainsKey(machinePart.PartType)) + return false; + + var progress = component.Progress[machinePart.PartType]; + var requirement = component.Requirements[machinePart.PartType]; + + var needed = requirement - progress; + if (needed <= 0) + return false; + + var count = stack.Count; + if (count < needed) + { + if (!_container.Insert(used, component.PartContainer)) + return true; + + component.Progress[machinePart.PartType] += count; + return true; + } + + var splitStack = _stack.Split(used, needed, Transform(uid).Coordinates, stack); + + if (splitStack == null) + return false; + + if (!_container.Insert(splitStack.Value, component.PartContainer)) + return true; + + component.Progress[machinePart.PartType] += needed; + if (IsComplete(component)) + _popupSystem.PopupEntity(Loc.GetString("machine-frame-component-on-complete"), uid); + + return true; + } + /// Whether or not the function had any effect. Does not indicate success. private bool TryInsertStack(EntityUid uid, EntityUid used, MachineFrameComponent component, StackComponent stack) { @@ -328,8 +370,6 @@ public sealed class MachineFrameSystem : EntitySystem { if (TryComp(part, out var machinePart)) { - DebugTools.Assert(!HasComp(part)); - // Check this is part of the requirements... if (!component.Requirements.ContainsKey(machinePart.PartType)) continue; @@ -338,7 +378,6 @@ public sealed class MachineFrameSystem : EntitySystem component.Progress[machinePart.PartType] = 1; else component.Progress[machinePart.PartType]++; - continue; } diff --git a/Content.Server/Damage/Components/DamageOnToolInteractComponent.cs b/Content.Server/Damage/Components/DamageOnToolInteractComponent.cs index e54090cdbb..547c29a202 100644 --- a/Content.Server/Damage/Components/DamageOnToolInteractComponent.cs +++ b/Content.Server/Damage/Components/DamageOnToolInteractComponent.cs @@ -1,22 +1,19 @@ using Content.Shared.Damage; using Content.Shared.Tools; -using Robust.Shared.Utility; +using Robust.Shared.Prototypes; -namespace Content.Server.Damage.Components +namespace Content.Server.Damage.Components; + +[RegisterComponent] +public sealed partial class DamageOnToolInteractComponent : Component { - [RegisterComponent] - public sealed partial class DamageOnToolInteractComponent : Component - { - [DataField("tools")] - public PrototypeFlags Tools { get; private set; } = new (); + [DataField] + public ProtoId Tools { get; private set; } - // TODO: Remove this snowflake stuff, make damage per-tool quality perhaps? - [DataField("weldingDamage")] - [ViewVariables(VVAccess.ReadWrite)] - public DamageSpecifier? WeldingDamage { get; private set; } + // TODO: Remove this snowflake stuff, make damage per-tool quality perhaps? + [DataField] + public DamageSpecifier? WeldingDamage { get; private set; } - [DataField("defaultDamage")] - [ViewVariables(VVAccess.ReadWrite)] - public DamageSpecifier? DefaultDamage { get; private set; } - } + [DataField] + public DamageSpecifier? DefaultDamage { get; private set; } } diff --git a/Content.Server/Damage/Systems/DamageOnToolInteractSystem.cs b/Content.Server/Damage/Systems/DamageOnToolInteractSystem.cs index 42676c1891..5980455e49 100644 --- a/Content.Server/Damage/Systems/DamageOnToolInteractSystem.cs +++ b/Content.Server/Damage/Systems/DamageOnToolInteractSystem.cs @@ -4,6 +4,7 @@ using Content.Shared.Damage; using Content.Shared.Database; using Content.Shared.Interaction; using Content.Shared.Tools.Components; +using Content.Shared.Tools.Systems; using ItemToggleComponent = Content.Shared.Item.ItemToggle.Components.ItemToggleComponent; namespace Content.Server.Damage.Systems @@ -12,6 +13,7 @@ namespace Content.Server.Damage.Systems { [Dependency] private readonly DamageableSystem _damageableSystem = default!; [Dependency] private readonly IAdminLogManager _adminLogger = default!; + [Dependency] private readonly SharedToolSystem _toolSystem = default!; public override void Initialize() { @@ -42,8 +44,7 @@ namespace Content.Server.Damage.Systems args.Handled = true; } else if (component.DefaultDamage is {} damage - && EntityManager.TryGetComponent(args.Used, out ToolComponent? tool) - && tool.Qualities.ContainsAny(component.Tools)) + && _toolSystem.HasQuality(args.Used, component.Tools)) { var dmg = _damageableSystem.TryChangeDamage(args.Target, damage, origin: args.User); diff --git a/Content.Server/Damage/Systems/DamageOtherOnHitSystem.cs b/Content.Server/Damage/Systems/DamageOtherOnHitSystem.cs index 0efa534981..ff4d1cabe9 100644 --- a/Content.Server/Damage/Systems/DamageOtherOnHitSystem.cs +++ b/Content.Server/Damage/Systems/DamageOtherOnHitSystem.cs @@ -32,22 +32,25 @@ namespace Content.Server.Damage.Systems private void OnDoHit(EntityUid uid, DamageOtherOnHitComponent component, ThrowDoHitEvent args) { - var dmg = _damageable.TryChangeDamage(args.Target, component.Damage, component.IgnoreResistances, origin: args.Component.Thrower); - - // Log damage only for mobs. Useful for when people throw spears at each other, but also avoids log-spam when explosions send glass shards flying. - if (dmg != null && HasComp(args.Target)) - _adminLogger.Add(LogType.ThrowHit, $"{ToPrettyString(args.Target):target} received {dmg.GetTotal():damage} damage from collision"); - - if (dmg is { Empty: false }) + if (!TerminatingOrDeleted(args.Target)) { - _color.RaiseEffect(Color.Red, new List() { args.Target }, Filter.Pvs(args.Target, entityManager: EntityManager)); - } + var dmg = _damageable.TryChangeDamage(args.Target, component.Damage, component.IgnoreResistances, origin: args.Component.Thrower); - _guns.PlayImpactSound(args.Target, dmg, null, false); - if (TryComp(uid, out var body) && body.LinearVelocity.LengthSquared() > 0f) - { - var direction = body.LinearVelocity.Normalized(); - _sharedCameraRecoil.KickCamera(args.Target, direction); + // Log damage only for mobs. Useful for when people throw spears at each other, but also avoids log-spam when explosions send glass shards flying. + if (dmg != null && HasComp(args.Target)) + _adminLogger.Add(LogType.ThrowHit, $"{ToPrettyString(args.Target):target} received {dmg.GetTotal():damage} damage from collision"); + + if (dmg is { Empty: false }) + { + _color.RaiseEffect(Color.Red, new List() { args.Target }, Filter.Pvs(args.Target, entityManager: EntityManager)); + } + + _guns.PlayImpactSound(args.Target, dmg, null, false); + if (TryComp(uid, out var body) && body.LinearVelocity.LengthSquared() > 0f) + { + var direction = body.LinearVelocity.Normalized(); + _sharedCameraRecoil.KickCamera(args.Target, direction); + } } // TODO: If more stuff touches this then handle it after. diff --git a/Content.Server/Database/ServerDbBase.cs b/Content.Server/Database/ServerDbBase.cs index be6c7196d5..cd03af7087 100644 --- a/Content.Server/Database/ServerDbBase.cs +++ b/Content.Server/Database/ServerDbBase.cs @@ -15,6 +15,7 @@ using Content.Shared.Humanoid.Markings; using Content.Shared.Preferences; using Content.Shared.Preferences.Loadouts; using Content.Shared.Roles; +using Content.Shared.Traits; using Microsoft.EntityFrameworkCore; using Robust.Shared.Enums; using Robust.Shared.Network; @@ -183,9 +184,9 @@ namespace Content.Server.Database private static HumanoidCharacterProfile ConvertProfiles(Profile profile) { - var jobs = profile.Jobs.ToDictionary(j => j.JobName, j => (JobPriority) j.Priority); - var antags = profile.Antags.Select(a => a.AntagName); - var traits = profile.Traits.Select(t => t.TraitName); + var jobs = profile.Jobs.ToDictionary(j => new ProtoId(j.JobName), j => (JobPriority) j.Priority); + var antags = profile.Antags.Select(a => new ProtoId(a.AntagName)); + var traits = profile.Traits.Select(t => new ProtoId(t.TraitName)); var sex = Sex.Male; if (Enum.TryParse(profile.Sex, true, out var sexVal)) diff --git a/Content.Server/DeltaV/RoundEnd/RoundEndSystem.Pacified.cs b/Content.Server/DeltaV/RoundEnd/RoundEndSystem.Pacified.cs index aa268ccf74..8f0f207f32 100644 --- a/Content.Server/DeltaV/RoundEnd/RoundEndSystem.Pacified.cs +++ b/Content.Server/DeltaV/RoundEnd/RoundEndSystem.Pacified.cs @@ -1,16 +1,11 @@ using Content.Server.Explosion.Components; -using Content.Server.Flash.Components; using Content.Server.GameTicking; -using Content.Server.Popups; -using Content.Server.Store.Components; -using Content.Server.Store.Systems; using Content.Shared.CombatMode; using Content.Shared.CombatMode.Pacification; using Content.Shared.DeltaV.CCVars; using Content.Shared.Explosion.Components; -using Content.Shared.FixedPoint; using Content.Shared.Flash.Components; -using Robust.Server.Player; +using Content.Shared.Store.Components; using Robust.Shared.Configuration; namespace Content.Server.DeltaV.RoundEnd; @@ -18,10 +13,6 @@ namespace Content.Server.DeltaV.RoundEnd; public sealed class PacifiedRoundEnd : EntitySystem { [Dependency] private readonly IConfigurationManager _configurationManager = default!; - [Dependency] private readonly IPlayerManager _playerManager = default!; - [Dependency] private readonly EntityManager _entityManager = default!; - [Dependency] private readonly StoreSystem _storeSystem = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; private bool _enabled; @@ -38,31 +29,31 @@ public sealed class PacifiedRoundEnd : EntitySystem return; var harmQuery = EntityQueryEnumerator(); - while (harmQuery.MoveNext(out var uid, out var _)) + while (harmQuery.MoveNext(out var uid, out _)) { EnsureComp(uid); } var explosiveQuery = EntityQueryEnumerator(); - while (explosiveQuery.MoveNext(out var uid, out var _)) + while (explosiveQuery.MoveNext(out var uid, out _)) { RemComp(uid); } var grenadeQuery = EntityQueryEnumerator(); - while (grenadeQuery.MoveNext(out var uid, out var _)) + while (grenadeQuery.MoveNext(out var uid, out _)) { RemComp(uid); } var flashQuery = EntityQueryEnumerator(); - while (flashQuery.MoveNext(out var uid, out var _)) + while (flashQuery.MoveNext(out var uid, out _)) { RemComp(uid); } var uplinkQuery = EntityQueryEnumerator(); - while (uplinkQuery.MoveNext(out var uid, out var store)) + while (uplinkQuery.MoveNext(out var _, out var store)) { store.Listings.Clear(); } diff --git a/Content.Server/Devour/DevourSystem.cs b/Content.Server/Devour/DevourSystem.cs index febbd093a6..d9c50f260a 100644 --- a/Content.Server/Devour/DevourSystem.cs +++ b/Content.Server/Devour/DevourSystem.cs @@ -1,3 +1,4 @@ +using Content.Server.Body.Components; using Content.Server.Body.Systems; using Content.Shared.Chemistry.Components; using Content.Shared.Devour; @@ -15,6 +16,7 @@ public sealed class DevourSystem : SharedDevourSystem base.Initialize(); SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnGibContents); } private void OnDoAfter(EntityUid uid, DevourerComponent component, DevourDoAfterEvent args) @@ -45,5 +47,15 @@ public sealed class DevourSystem : SharedDevourSystem _audioSystem.PlayPvs(component.SoundDevour, uid); } + + private void OnGibContents(EntityUid uid, DevourerComponent component, ref BeingGibbedEvent args) + { + if (!component.ShouldStoreDevoured) + return; + + // For some reason we have two different systems that should handle gibbing, + // and for some another reason GibbingSystem, which should empty all containers, doesn't get involved in this process + ContainerSystem.EmptyContainer(component.Stomach); + } } diff --git a/Content.Server/Explosion/EntitySystems/ExplosionGridTileFlood.cs b/Content.Server/Explosion/EntitySystems/ExplosionGridTileFlood.cs index 7db1f513f7..2ddcc052d8 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionGridTileFlood.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionGridTileFlood.cs @@ -14,7 +14,7 @@ public sealed class ExplosionGridTileFlood : ExplosionTileFlood public MapGridComponent Grid; private bool _needToTransform = false; - private Matrix3 _matrix = Matrix3.Identity; + private Matrix3x2 _matrix = Matrix3x2.Identity; private Vector2 _offset; // Tiles which neighbor an exploding tile, but have not yet had the explosion spread to them due to an @@ -44,7 +44,7 @@ public sealed class ExplosionGridTileFlood : ExplosionTileFlood int typeIndex, Dictionary edgeTiles, EntityUid? referenceGrid, - Matrix3 spaceMatrix, + Matrix3x2 spaceMatrix, Angle spaceAngle) { Grid = grid; @@ -72,9 +72,10 @@ public sealed class ExplosionGridTileFlood : ExplosionTileFlood var transform = IoCManager.Resolve().GetComponent(Grid.Owner); var size = (float) Grid.TileSize; - _matrix.R0C2 = size / 2; - _matrix.R1C2 = size / 2; - _matrix *= transform.WorldMatrix * Matrix3.Invert(spaceMatrix); + _matrix.M31 = size / 2; + _matrix.M32 = size / 2; + Matrix3x2.Invert(spaceMatrix, out var invSpace); + _matrix *= transform.WorldMatrix * invSpace; var relativeAngle = transform.WorldRotation - spaceAngle; _offset = relativeAngle.RotateVec(new Vector2(size / 4, size / 4)); } @@ -228,7 +229,7 @@ public sealed class ExplosionGridTileFlood : ExplosionTileFlood return; } - var center = _matrix.Transform(tile); + var center = Vector2.Transform(tile, _matrix); SpaceJump.Add(new((int) MathF.Floor(center.X + _offset.X), (int) MathF.Floor(center.Y + _offset.Y))); SpaceJump.Add(new((int) MathF.Floor(center.X - _offset.Y), (int) MathF.Floor(center.Y + _offset.X))); SpaceJump.Add(new((int) MathF.Floor(center.X - _offset.X), (int) MathF.Floor(center.Y - _offset.Y))); diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.GridMap.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.GridMap.cs index 719a2eca79..556fe7c141 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.GridMap.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.GridMap.cs @@ -60,7 +60,7 @@ public sealed partial class ExplosionSystem : EntitySystem { Dictionary transformedEdges = new(); - var targetMatrix = Matrix3.Identity; + var targetMatrix = Matrix3x2.Identity; Angle targetAngle = new(); var tileSize = DefaultTileSize; var maxDistanceSq = (int) (maxDistance * maxDistance); @@ -75,9 +75,9 @@ public sealed partial class ExplosionSystem : EntitySystem tileSize = targetGrid.TileSize; } - var offsetMatrix = Matrix3.Identity; - offsetMatrix.R0C2 = tileSize / 2f; - offsetMatrix.R1C2 = tileSize / 2f; + var offsetMatrix = Matrix3x2.Identity; + offsetMatrix.M31 = tileSize / 2f; + offsetMatrix.M32 = tileSize / 2f; // Here we can end up with a triple nested for loop: // foreach other grid @@ -106,7 +106,7 @@ public sealed partial class ExplosionSystem : EntitySystem var xform = xforms.GetComponent(gridToTransform); var (_, gridWorldRotation, gridWorldMatrix, invGridWorldMatrid) = xform.GetWorldPositionRotationMatrixWithInv(xforms); - var localEpicentre = (Vector2i) invGridWorldMatrid.Transform(epicentre.Position); + var localEpicentre = (Vector2i) Vector2.Transform(epicentre.Position, invGridWorldMatrid); var matrix = offsetMatrix * gridWorldMatrix * targetMatrix; var angle = gridWorldRotation - targetAngle; @@ -119,7 +119,7 @@ public sealed partial class ExplosionSystem : EntitySystem if (delta.X * delta.X + delta.Y * delta.Y > maxDistanceSq) // no Vector2.Length??? continue; - var center = matrix.Transform(tile); + var center = Vector2.Transform(tile, matrix); if ((dir & NeighborFlag.Cardinal) == 0) { diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs index bce3dc21c2..c30a9ce300 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs @@ -298,8 +298,8 @@ public sealed partial class ExplosionSystem /// Same as , but for SPAAAAAAACE. /// internal void ExplodeSpace(BroadphaseComponent lookup, - Matrix3 spaceMatrix, - Matrix3 invSpaceMatrix, + Matrix3x2 spaceMatrix, + Matrix3x2 invSpaceMatrix, Vector2i tile, float throwForce, DamageSpecifier damage, @@ -341,7 +341,7 @@ public sealed partial class ExplosionSystem } private static bool SpaceQueryCallback( - ref (List<(EntityUid, TransformComponent)> List, HashSet Processed, Matrix3 InvSpaceMatrix, EntityUid LookupOwner, EntityQuery XformQuery, Box2 GridBox, SharedTransformSystem System) state, + ref (List<(EntityUid, TransformComponent)> List, HashSet Processed, Matrix3x2 InvSpaceMatrix, EntityUid LookupOwner, EntityQuery XformQuery, Box2 GridBox, SharedTransformSystem System) state, in EntityUid uid) { if (state.Processed.Contains(uid)) @@ -352,7 +352,7 @@ public sealed partial class ExplosionSystem if (xform.ParentUid == state.LookupOwner) { // parented directly to the map, use local position - if (state.GridBox.Contains(state.InvSpaceMatrix.Transform(xform.LocalPosition))) + if (state.GridBox.Contains(Vector2.Transform(xform.LocalPosition, state.InvSpaceMatrix))) state.List.Add((uid, xform)); return true; @@ -360,14 +360,14 @@ public sealed partial class ExplosionSystem // finally check if it intersects our tile var wpos = state.System.GetWorldPosition(xform); - if (state.GridBox.Contains(state.InvSpaceMatrix.Transform(wpos))) + if (state.GridBox.Contains(Vector2.Transform(wpos, state.InvSpaceMatrix))) state.List.Add((uid, xform)); return true; } private static bool SpaceQueryCallback( - ref (List<(EntityUid, TransformComponent)> List, HashSet Processed, Matrix3 InvSpaceMatrix, EntityUid LookupOwner, EntityQuery XformQuery, Box2 GridBox, SharedTransformSystem System) state, + ref (List<(EntityUid, TransformComponent)> List, HashSet Processed, Matrix3x2 InvSpaceMatrix, EntityUid LookupOwner, EntityQuery XformQuery, Box2 GridBox, SharedTransformSystem System) state, in FixtureProxy proxy) { var uid = proxy.Entity; @@ -585,12 +585,12 @@ sealed class Explosion /// /// The matrix that defines the reference frame for the explosion in space. /// - private readonly Matrix3 _spaceMatrix; + private readonly Matrix3x2 _spaceMatrix; /// /// Inverse of /// - private readonly Matrix3 _invSpaceMatrix; + private readonly Matrix3x2 _invSpaceMatrix; /// /// Have all the tiles on all the grids been processed? @@ -656,7 +656,7 @@ sealed class Explosion List gridData, List tileSetIntensity, MapCoordinates epicenter, - Matrix3 spaceMatrix, + Matrix3x2 spaceMatrix, int area, float tileBreakScale, int maxTileBreak, @@ -695,7 +695,7 @@ sealed class Explosion }); _spaceMatrix = spaceMatrix; - _invSpaceMatrix = Matrix3.Invert(spaceMatrix); + Matrix3x2.Invert(spaceMatrix, out _invSpaceMatrix); } foreach (var grid in gridData) diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.TileFill.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.TileFill.cs index a42dd11083..8b4c0e14c5 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.TileFill.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.TileFill.cs @@ -26,7 +26,7 @@ public sealed partial class ExplosionSystem : EntitySystem /// The maximum intensity that the explosion can have at any given tile. This /// effectively caps the damage that this explosion can do. /// A list of tile-sets and a list of intensity values which describe the explosion. - private (int, List, ExplosionSpaceTileFlood?, Dictionary, Matrix3)? GetExplosionTiles( + private (int, List, ExplosionSpaceTileFlood?, Dictionary, Matrix3x2)? GetExplosionTiles( MapCoordinates epicenter, string typeID, float totalIntensity, @@ -84,7 +84,7 @@ public sealed partial class ExplosionSystem : EntitySystem Dictionary>? previousGridJump; // variables for transforming between grid and space-coordinates - var spaceMatrix = Matrix3.Identity; + var spaceMatrix = Matrix3x2.Identity; var spaceAngle = Angle.Zero; if (referenceGrid != null) { diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Visuals.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Visuals.cs index d332531502..5db8b55bf9 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Visuals.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Visuals.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Shared.Explosion; using Content.Shared.Explosion.Components; using Robust.Server.GameObjects; @@ -35,7 +36,7 @@ public sealed partial class ExplosionSystem : EntitySystem /// /// Constructor for the shared using the server-exclusive explosion classes. /// - private EntityUid CreateExplosionVisualEntity(MapCoordinates epicenter, string prototype, Matrix3 spaceMatrix, ExplosionSpaceTileFlood? spaceData, IEnumerable gridData, List iterationIntensity) + private EntityUid CreateExplosionVisualEntity(MapCoordinates epicenter, string prototype, Matrix3x2 spaceMatrix, ExplosionSpaceTileFlood? spaceData, IEnumerable gridData, List iterationIntensity) { var explosionEntity = Spawn(null, MapCoordinates.Nullspace); var comp = AddComp(explosionEntity); diff --git a/Content.Server/Explosion/EntitySystems/TriggerSystem.OnUse.cs b/Content.Server/Explosion/EntitySystems/TriggerSystem.OnUse.cs index 786d29d94a..8725dd1ae7 100644 --- a/Content.Server/Explosion/EntitySystems/TriggerSystem.OnUse.cs +++ b/Content.Server/Explosion/EntitySystems/TriggerSystem.OnUse.cs @@ -171,7 +171,8 @@ public sealed partial class TriggerSystem if (args.Handled || HasComp(uid) || component.UseVerbInstead) return; - _popupSystem.PopupEntity(Loc.GetString("trigger-activated", ("device", uid)), args.User, args.User); + if (component.DoPopup) + _popupSystem.PopupEntity(Loc.GetString("trigger-activated", ("device", uid)), args.User, args.User); HandleTimerTrigger( uid, diff --git a/Content.Server/Flash/Components/DamagedByFlashingComponent.cs b/Content.Server/Flash/Components/DamagedByFlashingComponent.cs index 2a9024607d..ef33454295 100644 --- a/Content.Server/Flash/Components/DamagedByFlashingComponent.cs +++ b/Content.Server/Flash/Components/DamagedByFlashingComponent.cs @@ -3,7 +3,6 @@ using Robust.Shared.Prototypes; namespace Content.Server.Flash.Components; -// Also needed FlashableComponent on entity to work [RegisterComponent, Access(typeof(DamagedByFlashingSystem))] public sealed partial class DamagedByFlashingComponent : Component { @@ -11,5 +10,5 @@ public sealed partial class DamagedByFlashingComponent : Component /// damage from flashing /// [DataField(required: true), ViewVariables(VVAccess.ReadWrite)] - public DamageSpecifier FlashDamage = new (); + public DamageSpecifier FlashDamage = new(); } diff --git a/Content.Server/Flash/Components/FlashImmunityComponent.cs b/Content.Server/Flash/Components/FlashImmunityComponent.cs index 80bbdd1282..a982a9059f 100644 --- a/Content.Server/Flash/Components/FlashImmunityComponent.cs +++ b/Content.Server/Flash/Components/FlashImmunityComponent.cs @@ -1,10 +1,13 @@ -namespace Content.Server.Flash.Components +namespace Content.Server.Flash.Components; + +/// +/// Makes the entity immune to being flashed. +/// When given to clothes in the "head", "eyes" or "mask" slot it protects the wearer. +/// +[RegisterComponent, Access(typeof(FlashSystem))] +public sealed partial class FlashImmunityComponent : Component { - [RegisterComponent, Access(typeof(FlashSystem))] - public sealed partial class FlashImmunityComponent : Component - { - [ViewVariables(VVAccess.ReadWrite)] - [DataField("enabled")] - public bool Enabled { get; set; } = true; - } + [ViewVariables(VVAccess.ReadWrite)] + [DataField("enabled")] + public bool Enabled { get; set; } = true; } diff --git a/Content.Server/Flash/FlashSystem.cs b/Content.Server/Flash/FlashSystem.cs index dd8cdab426..ccb58e94f8 100644 --- a/Content.Server/Flash/FlashSystem.cs +++ b/Content.Server/Flash/FlashSystem.cs @@ -9,18 +9,17 @@ using Content.Shared.Charges.Systems; using Content.Shared.Eye.Blinding.Components; using Content.Shared.Flash; using Content.Shared.IdentityManagement; -using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Inventory; -using Content.Shared.Physics; using Content.Shared.Tag; using Content.Shared.Traits.Assorted; using Content.Shared.Weapons.Melee.Events; +using Content.Shared.StatusEffect; +using Content.Shared.Examine; using Robust.Server.Audio; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Random; -using Robust.Shared.Timing; using InventoryComponent = Content.Shared.Inventory.InventoryComponent; namespace Content.Server.Flash @@ -31,14 +30,14 @@ namespace Content.Server.Flash [Dependency] private readonly AudioSystem _audio = default!; [Dependency] private readonly SharedChargesSystem _charges = default!; [Dependency] private readonly EntityLookupSystem _entityLookup = default!; - [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; - [Dependency] private readonly SharedInteractionSystem _interaction = default!; + [Dependency] private readonly ExamineSystemShared _examine = default!; [Dependency] private readonly InventorySystem _inventory = default!; [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly StunSystem _stun = default!; [Dependency] private readonly TagSystem _tag = default!; [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!; public override void Initialize() { @@ -46,7 +45,7 @@ namespace Content.Server.Flash SubscribeLocalEvent(OnFlashMeleeHit); // ran before toggling light for extra-bright lantern - SubscribeLocalEvent(OnFlashUseInHand, before: new []{ typeof(HandheldLightSystem) }); + SubscribeLocalEvent(OnFlashUseInHand, before: new[] { typeof(HandheldLightSystem) }); SubscribeLocalEvent(OnInventoryFlashAttempt); SubscribeLocalEvent(OnFlashImmunityFlashAttempt); SubscribeLocalEvent(OnPermanentBlindnessFlashAttempt); @@ -114,19 +113,35 @@ namespace Content.Server.Flash float flashDuration, float slowTo, bool displayPopup = true, - FlashableComponent? flashable = null, bool melee = false, TimeSpan? stunDuration = null) { - if (!Resolve(target, ref flashable, false)) - return; - var attempt = new FlashAttemptEvent(target, user, used); RaiseLocalEvent(target, attempt, true); if (attempt.Cancelled) return; + // don't paralyze, slowdown or convert to rev if the target is immune to flashes + if (!_statusEffectsSystem.TryAddStatusEffect(target, FlashedKey, TimeSpan.FromSeconds(flashDuration / 1000f), true)) + return; + + if (stunDuration != null) + { + _stun.TryParalyze(target, stunDuration.Value, true); + } + else + { + _stun.TrySlowdown(target, TimeSpan.FromSeconds(flashDuration / 1000f), true, + slowTo, slowTo); + } + + if (displayPopup && user != null && target != user && Exists(user.Value)) + { + _popup.PopupEntity(Loc.GetString("flash-component-user-blinds-you", + ("user", Identity.Entity(user.Value, EntityManager))), target, target); + } + if (melee) { var ev = new AfterFlashedEvent(target, user, used); @@ -135,48 +150,31 @@ namespace Content.Server.Flash if (used != null) RaiseLocalEvent(used.Value, ref ev); } - - flashable.LastFlash = _timing.CurTime; - flashable.Duration = flashDuration / 1000f; // TODO: Make this sane... - Dirty(target, flashable); - - if (stunDuration != null) - { - _stun.TryParalyze(target, stunDuration.Value, true); - } - else - { - _stun.TrySlowdown(target, TimeSpan.FromSeconds(flashDuration/1000f), true, - slowTo, slowTo); - } - - if (displayPopup && user != null && target != user && Exists(user.Value)) - { - _popup.PopupEntity(Loc.GetString("flash-component-user-blinds-you", - ("user", Identity.Entity(user.Value, EntityManager))), target, target); - } } public void FlashArea(Entity source, EntityUid? user, float range, float duration, float slowTo = 0.8f, bool displayPopup = false, float probability = 1f, SoundSpecifier? sound = null) { var transform = Transform(source); var mapPosition = _transform.GetMapCoordinates(transform); - var flashableQuery = GetEntityQuery(); + var statusEffectsQuery = GetEntityQuery(); + var damagedByFlashingQuery = GetEntityQuery(); foreach (var entity in _entityLookup.GetEntitiesInRange(transform.Coordinates, range)) { if (!_random.Prob(probability)) continue; - if (!flashableQuery.TryGetComponent(entity, out var flashable)) + // Is the entity affected by the flash either through status effects or by taking damage? + if (!statusEffectsQuery.HasComponent(entity) && !damagedByFlashingQuery.HasComponent(entity)) continue; - // Check for unobstructed entities while ignoring the mobs with flashable components. - if (!_interaction.InRangeUnobstructed(entity, mapPosition, range, flashable.CollisionGroup, predicate: (e) => flashableQuery.HasComponent(e) || e == source.Owner)) + // Check for entites in view + // put damagedByFlashingComponent in the predicate because shadow anomalies block vision. + if (!_examine.InRangeUnOccluded(entity, mapPosition, range, predicate: (e) => damagedByFlashingQuery.HasComponent(e))) continue; // They shouldn't have flash removed in between right? - Flash(entity, user, source, duration, slowTo, displayPopup, flashableQuery.GetComponent(entity)); + Flash(entity, user, source, duration, slowTo, displayPopup); } _audio.PlayPvs(sound, source, AudioParams.Default.WithVolume(1f).WithMaxDistance(3f)); @@ -195,13 +193,15 @@ namespace Content.Server.Flash private void OnFlashImmunityFlashAttempt(EntityUid uid, FlashImmunityComponent component, FlashAttemptEvent args) { - if(component.Enabled) + if (component.Enabled) args.Cancel(); } private void OnPermanentBlindnessFlashAttempt(EntityUid uid, PermanentBlindnessComponent component, FlashAttemptEvent args) { - args.Cancel(); + // check for total blindness + if (component.Blindness == 0) + args.Cancel(); } private void OnTemporaryBlindnessFlashAttempt(EntityUid uid, TemporaryBlindnessComponent component, FlashAttemptEvent args) @@ -210,6 +210,10 @@ namespace Content.Server.Flash } } + /// + /// Called before a flash is used to check if the attempt is cancelled by blindness, items or FlashImmunityComponent. + /// Raised on the target hit by the flash, the user of the flash and the flash used. + /// public sealed class FlashAttemptEvent : CancellableEntityEventArgs { public readonly EntityUid Target; @@ -224,8 +228,8 @@ namespace Content.Server.Flash } } /// - /// Called after a flash is used via melee on another person to check for rev conversion. - /// Raised on the user of the flash, the target hit by the flash, and the flash used. + /// Called after a flash is used via melee on another person to check for rev conversion. + /// Raised on the target hit by the flash, the user of the flash and the flash used. /// [ByRefEvent] public readonly struct AfterFlashedEvent @@ -241,6 +245,4 @@ namespace Content.Server.Flash Used = used; } } - - } diff --git a/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs b/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs index a3717fd94c..52afdcf8b4 100644 --- a/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs +++ b/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Server.Chemistry.Containers.EntitySystems; using Content.Server.Popups; using Content.Shared.Chemistry.Components; @@ -317,7 +318,7 @@ public sealed class AbsorbentSystem : SharedAbsorbentSystem var userXform = Transform(user); var targetPos = _transform.GetWorldPosition(target); - var localPos = _transform.GetInvWorldMatrix(userXform).Transform(targetPos); + var localPos = Vector2.Transform(targetPos, _transform.GetInvWorldMatrix(userXform)); localPos = userXform.LocalRotation.RotateVec(localPos); _melee.DoLunge(user, used, Angle.Zero, localPos, null, false); diff --git a/Content.Server/GameTicking/GameTicker.RoundFlow.cs b/Content.Server/GameTicking/GameTicker.RoundFlow.cs index 6eb42b65c0..98fcc64410 100644 --- a/Content.Server/GameTicking/GameTicker.RoundFlow.cs +++ b/Content.Server/GameTicking/GameTicker.RoundFlow.cs @@ -239,7 +239,7 @@ namespace Content.Server.GameTicking HumanoidCharacterProfile profile; if (_prefsManager.TryGetCachedPreferences(userId, out var preferences)) { - profile = (HumanoidCharacterProfile) preferences.GetProfile(preferences.SelectedCharacterIndex); + profile = (HumanoidCharacterProfile) preferences.SelectedCharacter; } else { diff --git a/Content.Server/GameTicking/GameTicker.Spawning.cs b/Content.Server/GameTicking/GameTicker.Spawning.cs index b819b5930a..5f20feee75 100644 --- a/Content.Server/GameTicking/GameTicker.Spawning.cs +++ b/Content.Server/GameTicking/GameTicker.Spawning.cs @@ -425,7 +425,7 @@ namespace Content.Server.GameTicking { var gridXform = Transform(gridUid); - return new EntityCoordinates(gridUid, gridXform.InvWorldMatrix.Transform(toMap.Position)); + return new EntityCoordinates(gridUid, Vector2.Transform(toMap.Position, gridXform.InvWorldMatrix)); } return spawn; diff --git a/Content.Server/GameTicking/Rules/Components/LoadMapRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/LoadMapRuleComponent.cs index b7aef0c61d..1f0505c60f 100644 --- a/Content.Server/GameTicking/Rules/Components/LoadMapRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/LoadMapRuleComponent.cs @@ -1,8 +1,6 @@ +using Content.Server.GameTicking.Rules; using Content.Server.Maps; using Content.Shared.GridPreloader.Prototypes; -using Content.Shared.Storage; -using Content.Shared.Whitelist; -using Robust.Shared.Map; using Robust.Shared.Prototypes; using Robust.Shared.Utility; @@ -10,25 +8,27 @@ namespace Content.Server.GameTicking.Rules.Components; /// /// This is used for a game rule that loads a map when activated. +/// Works with . /// -[RegisterComponent] +[RegisterComponent, Access(typeof(LoadMapRuleSystem))] public sealed partial class LoadMapRuleComponent : Component { - [DataField] - public MapId? Map; - + /// + /// A to load on a new map. + /// [DataField] public ProtoId? GameMap; + /// + /// A map path to load on a new map. + /// [DataField] public ResPath? MapPath; + /// + /// A to move to a new map. + /// If there are no instances left nothing is done. + /// [DataField] public ProtoId? PreloadedGrid; - - [DataField] - public List MapGrids = new(); - - [DataField] - public EntityWhitelist? SpawnerWhitelist; } diff --git a/Content.Server/GameTicking/Rules/Components/NukeOperativeSpawnerComponent.cs b/Content.Server/GameTicking/Rules/Components/NukeOperativeSpawnerComponent.cs index bb1b7c8746..54eaa6e32e 100644 --- a/Content.Server/GameTicking/Rules/Components/NukeOperativeSpawnerComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/NukeOperativeSpawnerComponent.cs @@ -1,3 +1,5 @@ +using Robust.Shared.Prototypes; + namespace Content.Server.GameTicking.Rules.Components; /// @@ -5,6 +7,5 @@ namespace Content.Server.GameTicking.Rules.Components; /// and providing loadout + name for the operative on spawn. /// TODO: Remove once systems can request spawns from the ghost role system directly. /// -[RegisterComponent] +[RegisterComponent, EntityCategory("Spawner")] public sealed partial class NukeOperativeSpawnerComponent : Component; - diff --git a/Content.Server/GameTicking/Rules/Components/RespawnDeadRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/RespawnDeadRuleComponent.cs index fafe811dd9..f6e4a3b129 100644 --- a/Content.Server/GameTicking/Rules/Components/RespawnDeadRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/RespawnDeadRuleComponent.cs @@ -6,4 +6,9 @@ [RegisterComponent, Access(typeof(RespawnRuleSystem))] public sealed partial class RespawnDeadRuleComponent : Component { + /// + /// Whether or not we want to add everyone who dies to the respawn tracker + /// + [DataField] + public bool AlwaysRespawnDead; } diff --git a/Content.Server/GameTicking/Rules/Components/RespawnTrackerComponent.cs b/Content.Server/GameTicking/Rules/Components/RespawnTrackerComponent.cs index 3d338c2d13..b9c8fe1096 100644 --- a/Content.Server/GameTicking/Rules/Components/RespawnTrackerComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/RespawnTrackerComponent.cs @@ -13,18 +13,24 @@ public sealed partial class RespawnTrackerComponent : Component /// A list of the people that should be respawned. /// Used to make sure that we don't respawn aghosts or observers. /// - [DataField("players")] + [DataField] public HashSet Players = new(); /// /// The delay between dying and respawning. /// - [DataField("respawnDelay")] + [DataField] public TimeSpan RespawnDelay = TimeSpan.Zero; /// /// A dictionary of player netuserids and when they will respawn. /// - [DataField("respawnQueue")] + [DataField] public Dictionary RespawnQueue = new(); + + /// + /// Whether or not to delete the original body when respawning + /// + [DataField] + public bool DeleteBody = true; } diff --git a/Content.Server/GameTicking/Rules/Components/RuleGridsComponent.cs b/Content.Server/GameTicking/Rules/Components/RuleGridsComponent.cs new file mode 100644 index 0000000000..eec6f88815 --- /dev/null +++ b/Content.Server/GameTicking/Rules/Components/RuleGridsComponent.cs @@ -0,0 +1,30 @@ +using Content.Server.GameTicking.Rules; +using Content.Shared.Whitelist; +using Robust.Shared.Map; + +/// +/// Stores grids created by another gamerule component. +/// With AntagSelection, spawners on these grids can be used for its antags. +/// +[RegisterComponent, Access(typeof(RuleGridsSystem))] +public sealed partial class RuleGridsComponent : Component +{ + /// + /// The map that was loaded. + /// + [DataField] + public MapId? Map; + + /// + /// The grid entities that have been loaded. + /// + [DataField] + public List MapGrids = new(); + + /// + /// Whitelist for a spawner to be considered for an antag. + /// All spawners must have SpawnPointComponent regardless to be found. + /// + [DataField] + public EntityWhitelist? SpawnerWhitelist; +} diff --git a/Content.Server/GameTicking/Rules/DeathMatchRuleSystem.cs b/Content.Server/GameTicking/Rules/DeathMatchRuleSystem.cs index ad7c63ff58..9e3203d170 100644 --- a/Content.Server/GameTicking/Rules/DeathMatchRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/DeathMatchRuleSystem.cs @@ -56,7 +56,7 @@ public sealed class DeathMatchRuleSystem : GameRuleSystem(mob); - _respawn.AddToTracker(ev.Player.UserId, uid, tracker); + _respawn.AddToTracker(ev.Player.UserId, (uid, tracker)); _point.EnsurePlayer(ev.Player.UserId, uid, point); @@ -73,7 +73,7 @@ public sealed class DeathMatchRuleSystem : GameRuleSystem where T: IComponent return found; } + protected void ForceEndSelf(EntityUid uid, GameRuleComponent? component = null) + { + GameTicker.EndGameRule(uid, component); + } } diff --git a/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs b/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs index 3a80d82fd9..1c09d6e86e 100644 --- a/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs @@ -1,8 +1,6 @@ -using Content.Server.Antag; using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.GridPreloader; -using Content.Server.Spawners.Components; using Robust.Server.GameObjects; using Robust.Server.Maps; using Robust.Shared.Map; @@ -13,96 +11,70 @@ namespace Content.Server.GameTicking.Rules; public sealed class LoadMapRuleSystem : GameRuleSystem { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly MapSystem _map = default!; [Dependency] private readonly MapLoaderSystem _mapLoader = default!; [Dependency] private readonly MetaDataSystem _metaData = default!; [Dependency] private readonly TransformSystem _transform = default!; [Dependency] private readonly GridPreloaderSystem _gridPreloader = default!; - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnSelectLocation); - SubscribeLocalEvent(OnGridSplit); - } - - private void OnGridSplit(ref GridSplitEvent args) - { - var rule = QueryActiveRules(); - while (rule.MoveNext(out _, out var mapComp, out _)) - { - if (!mapComp.MapGrids.Contains(args.Grid)) - continue; - - mapComp.MapGrids.AddRange(args.NewGrids); - break; - } - } - protected override void Added(EntityUid uid, LoadMapRuleComponent comp, GameRuleComponent rule, GameRuleAddedEvent args) { - if (comp.Map != null) + if (comp.PreloadedGrid != null && !_gridPreloader.PreloadingEnabled) + { + // Preloading will never work if it's disabled, duh + Log.Debug($"Immediately ending {ToPrettyString(uid):rule} as preloading grids is disabled by cvar."); + ForceEndSelf(uid, rule); return; + } // grid preloading needs map to init after moving it - var mapUid = comp.PreloadedGrid != null ? _map.CreateMap(out var mapId, false) : _map.CreateMap(out mapId); - _metaData.SetEntityName(mapUid, $"LoadMapRule destination for rule {ToPrettyString(uid)}"); - comp.Map = mapId; + var mapUid = _map.CreateMap(out var mapId, runMapInit: comp.PreloadedGrid == null); + Log.Info($"Created map {mapId} for {ToPrettyString(uid):rule}"); + + IReadOnlyList grids; if (comp.GameMap != null) { var gameMap = _prototypeManager.Index(comp.GameMap.Value); - comp.MapGrids.AddRange(GameTicker.LoadGameMap(gameMap, comp.Map.Value, new MapLoadOptions())); + grids = GameTicker.LoadGameMap(gameMap, mapId, new MapLoadOptions()); } - else if (comp.MapPath != null) + else if (comp.MapPath is {} path) { - if (!_mapLoader.TryLoad(comp.Map.Value, - comp.MapPath.Value.ToString(), - out var roots, - new MapLoadOptions { LoadMap = true })) + var options = new MapLoadOptions { LoadMap = true }; + if (!_mapLoader.TryLoad(mapId, path.ToString(), out var roots, options)) { - _mapManager.DeleteMap(mapId); + Log.Error($"Failed to load map from {path}!"); + Del(mapUid); + ForceEndSelf(uid, rule); return; } - comp.MapGrids.AddRange(roots); + grids = roots; } - else if (comp.PreloadedGrid != null) + else if (comp.PreloadedGrid is {} preloaded) { // TODO: If there are no preloaded grids left, any rule announcements will still go off! - if (!_gridPreloader.TryGetPreloadedGrid(comp.PreloadedGrid.Value, out var loadedShuttle)) + if (!_gridPreloader.TryGetPreloadedGrid(preloaded, out var loadedShuttle)) { - _mapManager.DeleteMap(mapId); + Log.Error($"Failed to get a preloaded grid with {preloaded}!"); + Del(mapUid); + ForceEndSelf(uid, rule); return; } _transform.SetParent(loadedShuttle.Value, mapUid); - comp.MapGrids.Add(loadedShuttle.Value); - _map.InitializeMap(mapId); + grids = new List() { loadedShuttle.Value }; + _map.InitializeMap(mapUid); } else { Log.Error($"No valid map prototype or map path associated with the rule {ToPrettyString(uid)}"); + Del(mapUid); + ForceEndSelf(uid, rule); + return; } - } - private void OnSelectLocation(Entity ent, ref AntagSelectLocationEvent args) - { - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out _, out var xform)) - { - if (xform.MapID != ent.Comp.Map) - continue; - - if (xform.GridUid == null || !ent.Comp.MapGrids.Contains(xform.GridUid.Value)) - continue; - - if (ent.Comp.SpawnerWhitelist != null && !ent.Comp.SpawnerWhitelist.IsValid(uid, EntityManager)) - continue; - - args.Coordinates.Add(_transform.GetMapCoordinates(xform)); - } + var ev = new RuleLoadedGridsEvent(mapId, grids); + RaiseLocalEvent(uid, ref ev); } } diff --git a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs index d6f1c3c619..6688bfd980 100644 --- a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs @@ -25,6 +25,7 @@ using Robust.Shared.Random; using Robust.Shared.Utility; using System.Linq; using Content.Server.GameTicking.Components; +using Content.Shared.Store.Components; namespace Content.Server.GameTicking.Rules; @@ -259,10 +260,10 @@ public sealed class NukeopsRuleSystem : GameRuleSystem { var map = Transform(ent).MapID; - var rules = EntityQueryEnumerator(); - while (rules.MoveNext(out var uid, out _, out var mapRule)) + var rules = EntityQueryEnumerator(); + while (rules.MoveNext(out var uid, out _, out var grids)) { - if (map != mapRule.Map) + if (map != grids.Map) continue; ent.Comp.AssociatedRule = uid; break; @@ -323,7 +324,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem if (nukeops.WarDeclaredTime != null) continue; - if (TryComp(uid, out var mapComp) && Transform(ev.DeclaratorEntity).MapID != mapComp.Map) + if (TryComp(uid, out var grids) && Transform(ev.DeclaratorEntity).MapID != grids.Map) continue; var newStatus = GetWarCondition(nukeops, ev.Status); @@ -444,7 +445,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem // Check that there are spawns available and that they can access the shuttle. var spawnsAvailable = EntityQuery(true).Any(); - if (spawnsAvailable && CompOrNull(ent)?.Map == shuttleMapId) + if (spawnsAvailable && CompOrNull(ent)?.Map == shuttleMapId) return; // Ghost spawns can still access the shuttle. Continue the round. // The shuttle is inaccessible to both living nuke operatives and yet to spawn nuke operatives, @@ -477,7 +478,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem /// Is this method the shitty glue holding together the last of my sanity? yes. /// Do i have a better solution? not presently. /// - private EntityUid? GetOutpost(Entity ent) + private EntityUid? GetOutpost(Entity ent) { if (!Resolve(ent, ref ent.Comp, false)) return null; diff --git a/Content.Server/GameTicking/Rules/RespawnRuleSystem.cs b/Content.Server/GameTicking/Rules/RespawnRuleSystem.cs index 5215da96aa..3f8d31f622 100644 --- a/Content.Server/GameTicking/Rules/RespawnRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/RespawnRuleSystem.cs @@ -1,4 +1,5 @@ using Content.Server.Chat.Managers; +using Content.Server.Database.Migrations.Postgres; using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Station.Systems; @@ -34,38 +35,6 @@ public sealed class RespawnRuleSystem : GameRuleSystem SubscribeLocalEvent(OnMobStateChanged); } - private void OnSuicide(SuicideEvent ev) - { - if (!TryComp(ev.Victim, out var actor)) - return; - - var query = EntityQueryEnumerator(); - while (query.MoveNext(out _, out var respawn)) - { - if (respawn.Players.Remove(actor.PlayerSession.UserId)) - QueueDel(ev.Victim); - } - } - - private void OnMobStateChanged(MobStateChangedEvent args) - { - if (args.NewMobState == MobState.Alive) - return; - - if (!TryComp(args.Target, out var actor)) - return; - - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out _, out var rule)) - { - if (!GameTicker.IsGameRuleActive(uid, rule)) - continue; - - if (RespawnPlayer(args.Target, uid, actor: actor)) - break; - } - } - public override void Update(float frameTime) { base.Update(frameTime); @@ -75,8 +44,7 @@ public sealed class RespawnRuleSystem : GameRuleSystem foreach (var tracker in EntityQuery()) { - var queue = new Dictionary(tracker.RespawnQueue); - foreach (var (player, time) in queue) + foreach (var (player, time) in tracker.RespawnQueue) { if (_timing.CurTime < time) continue; @@ -92,53 +60,84 @@ public sealed class RespawnRuleSystem : GameRuleSystem } } - /// - /// Adds a given player to the respawn tracker, ensuring that they are respawned if they die. - /// - public void AddToTracker(EntityUid player, EntityUid tracker, RespawnTrackerComponent? component = null, ActorComponent? actor = null) + private void OnSuicide(SuicideEvent ev) { - if (!Resolve(tracker, ref component) || !Resolve(player, ref actor, false)) - return; + if (!TryComp(ev.Victim, out var actor)) + return; - AddToTracker(actor.PlayerSession.UserId, tracker, component); + var query = EntityQueryEnumerator(); + while (query.MoveNext(out _, out var respawn)) + { + if (respawn.Players.Remove(actor.PlayerSession.UserId)) + QueueDel(ev.Victim); + } } - /// - /// Adds a given player to the respawn tracker, ensuring that they are respawned if they die. - /// - public void AddToTracker(NetUserId id, EntityUid tracker, RespawnTrackerComponent? component = null) + private void OnMobStateChanged(MobStateChangedEvent args) { - if (!Resolve(tracker, ref component)) + if (args.NewMobState != MobState.Dead) return; - component.Players.Add(id); + if (!TryComp(args.Target, out var actor)) + return; + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var respawnRule, out var tracker, out var rule)) + { + if (!GameTicker.IsGameRuleActive(uid, rule)) + continue; + + if (respawnRule.AlwaysRespawnDead) + AddToTracker(actor.PlayerSession.UserId, (uid, tracker)); + if (RespawnPlayer((args.Target, actor), (uid, tracker))) + break; + } } /// /// Attempts to directly respawn a player, skipping the lobby screen. /// - public bool RespawnPlayer(EntityUid player, EntityUid respawnTracker, RespawnTrackerComponent? component = null, ActorComponent? actor = null) + public bool RespawnPlayer(Entity player, Entity respawnTracker) { - if (!Resolve(respawnTracker, ref component) || !Resolve(player, ref actor, false)) + if (!respawnTracker.Comp.Players.Contains(player.Comp.PlayerSession.UserId) || respawnTracker.Comp.RespawnQueue.ContainsKey(player.Comp.PlayerSession.UserId)) return false; - if (!component.Players.Contains(actor.PlayerSession.UserId) || component.RespawnQueue.ContainsKey(actor.PlayerSession.UserId)) - return false; - - if (component.RespawnDelay == TimeSpan.Zero) + if (respawnTracker.Comp.RespawnDelay == TimeSpan.Zero) { if (_station.GetStations().FirstOrNull() is not { } station) return false; - QueueDel(player); - GameTicker.MakeJoinGame(actor.PlayerSession, station, silent: true); + if (respawnTracker.Comp.DeleteBody) + QueueDel(player); + GameTicker.MakeJoinGame(player.Comp.PlayerSession, station, silent: true); return false; } - var msg = Loc.GetString("rule-respawn-in-seconds", ("second", component.RespawnDelay.TotalSeconds)); + var msg = Loc.GetString("rule-respawn-in-seconds", ("second", respawnTracker.Comp.RespawnDelay.TotalSeconds)); var wrappedMsg = Loc.GetString("chat-manager-server-wrap-message", ("message", msg)); - _chatManager.ChatMessageToOne(ChatChannel.Server, msg, wrappedMsg, respawnTracker, false, actor.PlayerSession.Channel, Color.LimeGreen); - component.RespawnQueue[actor.PlayerSession.UserId] = _timing.CurTime + component.RespawnDelay; + _chatManager.ChatMessageToOne(ChatChannel.Server, msg, wrappedMsg, respawnTracker, false, player.Comp.PlayerSession.Channel, Color.LimeGreen); + + respawnTracker.Comp.RespawnQueue[player.Comp.PlayerSession.UserId] = _timing.CurTime + respawnTracker.Comp.RespawnDelay; + return true; } + + /// + /// Adds a given player to the respawn tracker, ensuring that they are respawned if they die. + /// + public void AddToTracker(Entity player, Entity respawnTracker) + { + if (!Resolve(respawnTracker, ref respawnTracker.Comp) || !Resolve(player, ref player.Comp, false)) + return; + + AddToTracker(player.Comp.PlayerSession.UserId, (respawnTracker, respawnTracker.Comp)); + } + + /// + /// Adds a given player to the respawn tracker, ensuring that they are respawned if they die. + /// + public void AddToTracker(NetUserId id, Entity tracker) + { + tracker.Comp.Players.Add(id); + } } diff --git a/Content.Server/GameTicking/Rules/RuleGridsSystem.cs b/Content.Server/GameTicking/Rules/RuleGridsSystem.cs new file mode 100644 index 0000000000..9eae9e3c95 --- /dev/null +++ b/Content.Server/GameTicking/Rules/RuleGridsSystem.cs @@ -0,0 +1,78 @@ +using Content.Server.Antag; +using Content.Server.GameTicking.Rules.Components; +using Content.Server.Spawners.Components; +using Content.Shared.Whitelist; +using Robust.Server.Physics; +using Robust.Shared.Map; + +namespace Content.Server.GameTicking.Rules; + +/// +/// Handles storing grids from and antags spawning on their spawners. +/// +public sealed class RuleGridsSystem : GameRuleSystem +{ + [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnGridSplit); + + SubscribeLocalEvent(OnLoadedGrids); + SubscribeLocalEvent(OnSelectLocation); + } + + private void OnGridSplit(ref GridSplitEvent args) + { + var rule = QueryActiveRules(); + while (rule.MoveNext(out _, out var comp, out _)) + { + if (!comp.MapGrids.Contains(args.Grid)) + continue; + + comp.MapGrids.AddRange(args.NewGrids); + break; // only 1 rule can own a grid, not multiple + } + } + + private void OnLoadedGrids(Entity ent, ref RuleLoadedGridsEvent args) + { + var (uid, comp) = ent; + if (comp.Map != null && args.Map != comp.Map) + { + Log.Warning($"{ToPrettyString(uid):rule} loaded grids on multiple maps {comp.Map} and {args.Map}, the second will be ignored."); + return; + } + + comp.Map = args.Map; + comp.MapGrids.AddRange(args.Grids); + } + + private void OnSelectLocation(Entity ent, ref AntagSelectLocationEvent args) + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out _, out var xform)) + { + if (xform.MapID != ent.Comp.Map) + continue; + + if (xform.GridUid is not {} grid || !ent.Comp.MapGrids.Contains(grid)) + continue; + + if (_whitelist.IsWhitelistFail(ent.Comp.SpawnerWhitelist, uid)) + continue; + + args.Coordinates.Add(_transform.GetMapCoordinates(xform)); + } + } +} + +/// +/// Raised by another gamerule system to store loaded grids, and have other systems work with it. +/// A single rule can only load grids for a single map, attempts to load more are ignored. +/// +[ByRefEvent] +public record struct RuleLoadedGridsEvent(MapId Map, IReadOnlyList Grids); diff --git a/Content.Server/GameTicking/Rules/VariationPass/CutWireVariationPassSystem.cs b/Content.Server/GameTicking/Rules/VariationPass/CutWireVariationPassSystem.cs index fd94c74ac8..372de4bbb4 100644 --- a/Content.Server/GameTicking/Rules/VariationPass/CutWireVariationPassSystem.cs +++ b/Content.Server/GameTicking/Rules/VariationPass/CutWireVariationPassSystem.cs @@ -1,5 +1,6 @@ using Content.Server.GameTicking.Rules.VariationPass.Components; using Content.Server.Wires; +using Content.Shared.Whitelist; using Robust.Shared.Random; namespace Content.Server.GameTicking.Rules.VariationPass; @@ -11,6 +12,8 @@ namespace Content.Server.GameTicking.Rules.VariationPass; /// public sealed class CutWireVariationPassSystem : VariationPassSystem { + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; + protected override void ApplyVariation(Entity ent, ref StationVariationPassEvent args) { var wiresCut = 0; @@ -22,7 +25,7 @@ public sealed class CutWireVariationPassSystem : VariationPassSystem gatherable, ref AttackedEvent args) { - if (gatherable.Comp.ToolWhitelist?.IsValid(args.Used, EntityManager) != true) + if (_whitelistSystem.IsWhitelistFailOrNull(gatherable.Comp.ToolWhitelist, args.Used)) return; Gather(gatherable, args.User); @@ -41,7 +43,7 @@ public sealed partial class GatherableSystem : EntitySystem if (args.Handled || !args.Complex) return; - if (gatherable.Comp.ToolWhitelist?.IsValid(args.User, EntityManager) != true) + if (_whitelistSystem.IsWhitelistFailOrNull(gatherable.Comp.ToolWhitelist, args.User)) return; Gather(gatherable, args.User); diff --git a/Content.Server/Ghost/GhostSystem.cs b/Content.Server/Ghost/GhostSystem.cs index 1a411f13a0..dce80a450f 100644 --- a/Content.Server/Ghost/GhostSystem.cs +++ b/Content.Server/Ghost/GhostSystem.cs @@ -154,8 +154,8 @@ namespace Content.Server.Ghost if (_ticker.RunLevel != GameRunLevel.PostRound) { - _visibilitySystem.AddLayer(uid, visibility, (int) VisibilityFlags.Ghost, false); - _visibilitySystem.RemoveLayer(uid, visibility, (int) VisibilityFlags.Normal, false); + _visibilitySystem.AddLayer((uid, visibility), (int) VisibilityFlags.Ghost, false); + _visibilitySystem.RemoveLayer((uid, visibility), (int) VisibilityFlags.Normal, false); _visibilitySystem.RefreshVisibility(uid, visibilityComponent: visibility); } @@ -174,8 +174,8 @@ namespace Content.Server.Ghost // Entity can't be seen by ghosts anymore. if (TryComp(uid, out VisibilityComponent? visibility)) { - _visibilitySystem.RemoveLayer(uid, visibility, (int) VisibilityFlags.Ghost, false); - _visibilitySystem.AddLayer(uid, visibility, (int) VisibilityFlags.Normal, false); + _visibilitySystem.RemoveLayer((uid, visibility), (int) VisibilityFlags.Ghost, false); + _visibilitySystem.AddLayer((uid, visibility), (int) VisibilityFlags.Normal, false); _visibilitySystem.RefreshVisibility(uid, visibilityComponent: visibility); } @@ -382,13 +382,13 @@ namespace Content.Server.Ghost { if (visible) { - _visibilitySystem.AddLayer(uid, vis, (int) VisibilityFlags.Normal, false); - _visibilitySystem.RemoveLayer(uid, vis, (int) VisibilityFlags.Ghost, false); + _visibilitySystem.AddLayer((uid, vis), (int) VisibilityFlags.Normal, false); + _visibilitySystem.RemoveLayer((uid, vis), (int) VisibilityFlags.Ghost, false); } else { - _visibilitySystem.AddLayer(uid, vis, (int) VisibilityFlags.Ghost, false); - _visibilitySystem.RemoveLayer(uid, vis, (int) VisibilityFlags.Normal, false); + _visibilitySystem.AddLayer((uid, vis), (int) VisibilityFlags.Ghost, false); + _visibilitySystem.RemoveLayer((uid, vis), (int) VisibilityFlags.Normal, false); } _visibilitySystem.RefreshVisibility(uid, visibilityComponent: vis); } diff --git a/Content.Server/Ghost/Roles/Components/GhostRoleComponent.cs b/Content.Server/Ghost/Roles/Components/GhostRoleComponent.cs index 86026b230b..14007edcbf 100644 --- a/Content.Server/Ghost/Roles/Components/GhostRoleComponent.cs +++ b/Content.Server/Ghost/Roles/Components/GhostRoleComponent.cs @@ -14,6 +14,10 @@ namespace Content.Server.Ghost.Roles.Components [DataField("rules")] private string _roleRules = "ghost-role-component-default-rules"; + // TODO ROLE TIMERS + // Actually make use of / enforce this requirement? + // Why is this even here. + // Move to ghost role prototype & respect CCvars.GameRoleTimerOverride [DataField("requirements")] public HashSet? Requirements; diff --git a/Content.Server/Ghost/Roles/Components/GhostRoleMobSpawnerComponent.cs b/Content.Server/Ghost/Roles/Components/GhostRoleMobSpawnerComponent.cs index 6c2a6986fc..6116173f90 100644 --- a/Content.Server/Ghost/Roles/Components/GhostRoleMobSpawnerComponent.cs +++ b/Content.Server/Ghost/Roles/Components/GhostRoleMobSpawnerComponent.cs @@ -5,7 +5,7 @@ namespace Content.Server.Ghost.Roles.Components /// /// Allows a ghost to take this role, spawning a new entity. /// - [RegisterComponent] + [RegisterComponent, EntityCategory("Spawner")] [Access(typeof(GhostRoleSystem))] public sealed partial class GhostRoleMobSpawnerComponent : Component { diff --git a/Content.Server/GridPreloader/GridPreloaderSystem.cs b/Content.Server/GridPreloader/GridPreloaderSystem.cs index 569fe54141..e12ce41a31 100644 --- a/Content.Server/GridPreloader/GridPreloaderSystem.cs +++ b/Content.Server/GridPreloader/GridPreloaderSystem.cs @@ -24,12 +24,19 @@ public sealed class GridPreloaderSystem : SharedGridPreloaderSystem [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; + /// + /// Whether the preloading CVar is set or not. + /// + public bool PreloadingEnabled; + public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnRoundRestart); SubscribeLocalEvent(OnPostGameMapLoad); + + Subs.CVar(_cfg, CCVars.PreloadGrids, value => PreloadingEnabled = value, true); } private void OnRoundRestart(RoundRestartCleanupEvent ev) @@ -52,7 +59,7 @@ public sealed class GridPreloaderSystem : SharedGridPreloaderSystem if (GetPreloaderEntity() != null) return; - if (!_cfg.GetCVar(CCVars.PreloadGrids)) + if (!PreloadingEnabled) return; var mapUid = _map.CreateMap(out var mapId, false); diff --git a/Content.Server/Holiday/Christmas/RandomGiftSystem.cs b/Content.Server/Holiday/Christmas/RandomGiftSystem.cs index 9e56d0a493..ee542572d7 100644 --- a/Content.Server/Holiday/Christmas/RandomGiftSystem.cs +++ b/Content.Server/Holiday/Christmas/RandomGiftSystem.cs @@ -1,9 +1,10 @@ -using Content.Server.Administration.Logs; +using Content.Server.Administration.Logs; using Content.Server.Hands.Systems; using Content.Shared.Database; using Content.Shared.Examine; using Content.Shared.Interaction.Events; using Content.Shared.Item; +using Content.Shared.Whitelist; using Robust.Server.Audio; using Robust.Server.GameObjects; using Robust.Shared.Map.Components; @@ -24,6 +25,7 @@ public sealed class RandomGiftSystem : EntitySystem [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IAdminLogManager _adminLogger = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; private readonly List _possibleGiftsSafe = new(); private readonly List _possibleGiftsUnsafe = new(); @@ -40,7 +42,7 @@ public sealed class RandomGiftSystem : EntitySystem private void OnExamined(EntityUid uid, RandomGiftComponent component, ExaminedEvent args) { - if (!component.ContentsViewers.IsValid(args.Examiner, EntityManager) || component.SelectedEntity is null) + if (_whitelistSystem.IsWhitelistFail(component.ContentsViewers, args.Examiner) || component.SelectedEntity is null) return; var name = _prototype.Index(component.SelectedEntity).Name; diff --git a/Content.Server/Humanoid/Components/RandomHumanoidSpawnerComponent.cs b/Content.Server/Humanoid/Components/RandomHumanoidSpawnerComponent.cs index b56664fe19..bb38e94e04 100644 --- a/Content.Server/Humanoid/Components/RandomHumanoidSpawnerComponent.cs +++ b/Content.Server/Humanoid/Components/RandomHumanoidSpawnerComponent.cs @@ -8,7 +8,7 @@ namespace Content.Server.Humanoid.Components; /// This is added to a marker entity in order to spawn a randomized /// humanoid ingame. /// -[RegisterComponent] +[RegisterComponent, EntityCategory("Spawner")] public sealed partial class RandomHumanoidSpawnerComponent : Component { [DataField("settings", customTypeSerializer: typeof(PrototypeIdSerializer))] diff --git a/Content.Server/Implants/SubdermalImplantSystem.cs b/Content.Server/Implants/SubdermalImplantSystem.cs index e8af08b2eb..88c5fb9459 100644 --- a/Content.Server/Implants/SubdermalImplantSystem.cs +++ b/Content.Server/Implants/SubdermalImplantSystem.cs @@ -20,6 +20,7 @@ using Robust.Shared.Random; using System.Numerics; using Content.Shared.Movement.Pulling.Components; using Content.Shared.Movement.Pulling.Systems; +using Content.Shared.Store.Components; using Robust.Shared.Collections; using Robust.Shared.Map.Components; diff --git a/Content.Server/Instruments/InstrumentSystem.cs b/Content.Server/Instruments/InstrumentSystem.cs index f5a6713886..582bf7fa67 100644 --- a/Content.Server/Instruments/InstrumentSystem.cs +++ b/Content.Server/Instruments/InstrumentSystem.cs @@ -3,6 +3,7 @@ using Content.Server.Interaction; using Content.Server.Popups; using Content.Server.Stunnable; using Content.Shared.Administration; +using Content.Shared.Examine; using Content.Shared.Instruments; using Content.Shared.Instruments.UI; using Content.Shared.Physics; @@ -30,6 +31,7 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly TransformSystem _transform = default!; [Dependency] private readonly InteractionSystem _interactions = default!; + [Dependency] private readonly ExamineSystemShared _examineSystem = default!; private const float MaxInstrumentBandRange = 10f; @@ -250,9 +252,8 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem continue; // Maybe a bit expensive but oh well GetBands is queued and has a timer anyway. - // Make sure the instrument is visible, uses the Opaque collision group so this works across windows etc. - if (!_interactions.InRangeUnobstructed(uid, entity, MaxInstrumentBandRange, - CollisionGroup.Opaque, e => e == playerUid || e == originPlayer)) + // Make sure the instrument is visible + if (!_examineSystem.InRangeUnOccluded(uid, entity, MaxInstrumentBandRange, e => e == playerUid || e == originPlayer)) continue; if (!metadataQuery.TryGetComponent(playerUid, out var playerMetadata) diff --git a/Content.Server/KillTracking/KillTrackingSystem.cs b/Content.Server/KillTracking/KillTrackingSystem.cs index 63627fd1b9..ba27ea5d9e 100644 --- a/Content.Server/KillTracking/KillTrackingSystem.cs +++ b/Content.Server/KillTracking/KillTrackingSystem.cs @@ -2,6 +2,7 @@ using Content.Server.NPC.HTN; using Content.Shared.Damage; using Content.Shared.FixedPoint; using Content.Shared.Mobs; +using Content.Shared.Mobs.Systems; using Robust.Shared.Player; namespace Content.Server.KillTracking; @@ -14,7 +15,8 @@ public sealed class KillTrackingSystem : EntitySystem /// public override void Initialize() { - SubscribeLocalEvent(OnDamageChanged); + // Add damage to LifetimeDamage before MobStateChangedEvent gets raised + SubscribeLocalEvent(OnDamageChanged, before: [ typeof(MobThresholdSystem) ]); SubscribeLocalEvent(OnMobStateChanged); } @@ -50,7 +52,7 @@ public sealed class KillTrackingSystem : EntitySystem var largestSource = GetLargestSource(component.LifetimeDamage); largestSource ??= killImpulse; - KillSource? killSource; + KillSource killSource; KillSource? assistSource = null; if (killImpulse is KillEnvironmentSource) @@ -69,13 +71,13 @@ public sealed class KillTrackingSystem : EntitySystem killSource = killImpulse; // no assist is given to environmental kills - if (largestSource is not KillEnvironmentSource) + if (largestSource is not KillEnvironmentSource + && component.LifetimeDamage.TryGetValue(largestSource, out var largestDamage)) { - // you have to do at least 50% of largest source's damage to get the assist. - if (component.LifetimeDamage[largestSource] >= component.LifetimeDamage[killSource] / 2) - { + var killDamage = component.LifetimeDamage.GetValueOrDefault(killSource); + // you have to do at least twice as much damage as the killing source to get the assist. + if (largestDamage >= killDamage / 2) assistSource = largestSource; - } } } diff --git a/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs b/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs index 8938aa8b1d..c69ed49d50 100644 --- a/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs +++ b/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs @@ -35,6 +35,7 @@ using Robust.Shared.Player; using System.Linq; using Robust.Shared.Prototypes; using Robust.Shared.Timing; +using Content.Shared.Stacks; namespace Content.Server.Kitchen.EntitySystems { @@ -58,6 +59,8 @@ namespace Content.Server.Kitchen.EntitySystems [Dependency] private readonly UserInterfaceSystem _userInterface = default!; [Dependency] private readonly HandsSystem _handsSystem = default!; [Dependency] private readonly SharedItemSystem _item = default!; + [Dependency] private readonly SharedStackSystem _stack = default!; + [Dependency] private readonly IPrototypeManager _prototype = default!; [ValidatePrototypeId] private const string MalfunctionSpark = "Spark"; @@ -199,16 +202,41 @@ namespace Content.Server.Kitchen.EntitySystems { foreach (var item in component.Storage.ContainedEntities) { - var metaData = MetaData(item); - if (metaData.EntityPrototype == null) + string? itemID = null; + + // If an entity has a stack component, use the stacktype instead of prototype id + if (TryComp(item, out var stackComp)) + { + itemID = _prototype.Index(stackComp.StackTypeId).Spawn; + } + else + { + var metaData = MetaData(item); + if (metaData.EntityPrototype == null) + { + continue; + } + itemID = metaData.EntityPrototype.ID; + } + + if (itemID != recipeSolid.Key) { continue; } - if (metaData.EntityPrototype.ID == recipeSolid.Key) + if (stackComp is not null) + { + if (stackComp.Count == 1) + { + _container.Remove(item, component.Storage); + } + _stack.Use(item, 1, stackComp); + break; + } + else { _container.Remove(item, component.Storage); - EntityManager.DeleteEntity(item); + Del(item); break; } } @@ -448,17 +476,35 @@ namespace Content.Server.Kitchen.EntitySystems AddComp(item); - var metaData = MetaData(item); //this simply begs for cooking refactor - if (metaData.EntityPrototype == null) - continue; + string? solidID = null; + int amountToAdd = 1; - if (solidsDict.ContainsKey(metaData.EntityPrototype.ID)) + // If a microwave recipe uses a stacked item, use the default stack prototype id instead of prototype id + if (TryComp(item, out var stackComp)) { - solidsDict[metaData.EntityPrototype.ID]++; + solidID = _prototype.Index(stackComp.StackTypeId).Spawn; + amountToAdd = stackComp.Count; } else { - solidsDict.Add(metaData.EntityPrototype.ID, 1); + var metaData = MetaData(item); //this simply begs for cooking refactor + if (metaData.EntityPrototype is not null) + solidID = metaData.EntityPrototype.ID; + } + + if (solidID is null) + { + continue; + } + + + if (solidsDict.ContainsKey(solidID)) + { + solidsDict[solidID] += amountToAdd; + } + else + { + solidsDict.Add(solidID, amountToAdd); } if (!TryComp(item, out var solMan)) diff --git a/Content.Server/Mech/Systems/MechAssemblySystem.cs b/Content.Server/Mech/Systems/MechAssemblySystem.cs index e5b7bfaac3..4b408343b7 100644 --- a/Content.Server/Mech/Systems/MechAssemblySystem.cs +++ b/Content.Server/Mech/Systems/MechAssemblySystem.cs @@ -2,6 +2,7 @@ using Content.Shared.Interaction; using Content.Shared.Tag; using Content.Shared.Tools.Components; +using Content.Shared.Tools.Systems; using Robust.Server.Containers; using Robust.Shared.Containers; @@ -14,6 +15,8 @@ namespace Content.Server.Mech.Systems; public sealed class MechAssemblySystem : EntitySystem { [Dependency] private readonly ContainerSystem _container = default!; + [Dependency] private readonly TagSystem _tag = default!; + [Dependency] private readonly SharedToolSystem _toolSystem = default!; /// public override void Initialize() @@ -29,7 +32,7 @@ public sealed class MechAssemblySystem : EntitySystem private void OnInteractUsing(EntityUid uid, MechAssemblyComponent component, InteractUsingEvent args) { - if (TryComp(args.Used, out var toolComp) && toolComp.Qualities.Contains(component.QualityNeeded)) + if (_toolSystem.HasQuality(args.Used, component.QualityNeeded)) { foreach (var tag in component.RequiredParts.Keys) { @@ -44,7 +47,7 @@ public sealed class MechAssemblySystem : EntitySystem foreach (var (tag, val) in component.RequiredParts) { - if (!val && tagComp.Tags.Contains(tag)) + if (!val && _tag.HasTag(tagComp, tag)) { component.RequiredParts[tag] = true; _container.Insert(args.Used, component.PartsContainer); diff --git a/Content.Server/Mech/Systems/MechEquipmentSystem.cs b/Content.Server/Mech/Systems/MechEquipmentSystem.cs index f51c0444e6..f9fe5e4641 100644 --- a/Content.Server/Mech/Systems/MechEquipmentSystem.cs +++ b/Content.Server/Mech/Systems/MechEquipmentSystem.cs @@ -3,6 +3,7 @@ using Content.Shared.DoAfter; using Content.Shared.Interaction; using Content.Shared.Mech.Components; using Content.Shared.Mech.Equipment.Components; +using Content.Shared.Whitelist; namespace Content.Server.Mech.Systems; @@ -14,6 +15,7 @@ public sealed class MechEquipmentSystem : EntitySystem [Dependency] private readonly MechSystem _mech = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; /// public override void Initialize() @@ -40,7 +42,7 @@ public sealed class MechEquipmentSystem : EntitySystem if (mechComp.EquipmentContainer.ContainedEntities.Count >= mechComp.MaxEquipmentAmount) return; - if (mechComp.EquipmentWhitelist != null && !mechComp.EquipmentWhitelist.IsValid(args.Used)) + if (_whitelistSystem.IsWhitelistFail(mechComp.EquipmentWhitelist, args.Used)) return; _popup.PopupEntity(Loc.GetString("mech-equipment-begin-install", ("item", uid)), mech); diff --git a/Content.Server/Mech/Systems/MechSystem.cs b/Content.Server/Mech/Systems/MechSystem.cs index 53c6c62cdb..b738d28b46 100644 --- a/Content.Server/Mech/Systems/MechSystem.cs +++ b/Content.Server/Mech/Systems/MechSystem.cs @@ -17,10 +17,12 @@ using Content.Shared.Tools.Components; using Content.Shared.Verbs; using Content.Shared.Wires; using Content.Server.Body.Systems; +using Content.Shared.Tools.Systems; using Robust.Server.Containers; using Robust.Server.GameObjects; using Robust.Shared.Containers; using Robust.Shared.Player; +using Content.Shared.Whitelist; namespace Content.Server.Mech.Systems; @@ -35,6 +37,8 @@ public sealed partial class MechSystem : SharedMechSystem [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly UserInterfaceSystem _ui = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; + [Dependency] private readonly SharedToolSystem _toolSystem = default!; /// public override void Initialize() @@ -87,7 +91,7 @@ public sealed partial class MechSystem : SharedMechSystem return; } - if (TryComp(args.Used, out var tool) && tool.Qualities.Contains("Prying") && component.BatterySlot.ContainedEntity != null) + if (_toolSystem.HasQuality(args.Used, "Prying") && component.BatterySlot.ContainedEntity != null) { var doAfterEventArgs = new DoAfterArgs(EntityManager, args.User, component.BatteryRemovalDelay, new RemoveBatteryEvent(), uid, target: uid, used: args.Target) @@ -222,7 +226,7 @@ public sealed partial class MechSystem : SharedMechSystem if (args.Cancelled || args.Handled) return; - if (component.PilotWhitelist != null && !component.PilotWhitelist.IsValid(args.User)) + if (_whitelistSystem.IsWhitelistFail(component.PilotWhitelist, args.User)) { _popup.PopupEntity(Loc.GetString("mech-no-enter", ("item", uid)), args.User); return; diff --git a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs index f8d5139a46..a6285294c9 100644 --- a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs +++ b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs @@ -15,6 +15,7 @@ using Content.Shared.DoAfter; using Content.Shared.Humanoid; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; +using Content.Shared.Inventory; using Content.Shared.Jittering; using Content.Shared.Medical; using Content.Shared.Mind; @@ -36,6 +37,7 @@ namespace Content.Server.Medical.BiomassReclaimer public sealed class BiomassReclaimerSystem : EntitySystem { [Dependency] private readonly IConfigurationManager _configManager = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly SharedJitteringSystem _jitteringSystem = default!; [Dependency] private readonly SharedAudioSystem _sharedAudioSystem = default!; @@ -49,6 +51,7 @@ namespace Content.Server.Medical.BiomassReclaimer [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly MaterialStorageSystem _material = default!; [Dependency] private readonly SharedMindSystem _minds = default!; + [Dependency] private readonly InventorySystem _inventory = default!; [ValidatePrototypeId] public const string BiomassPrototype = "Biomass"; @@ -221,6 +224,12 @@ namespace Content.Server.Medical.BiomassReclaimer component.ProcessingTimer = physics.FixturesMass * component.ProcessingTimePerUnitMass; + var inventory = _inventory.GetHandOrInventoryEntities(toProcess); + foreach (var item in inventory) + { + _transform.DropNextTo(item, ent.Owner); + } + QueueDel(toProcess); } diff --git a/Content.Server/Medical/DefibrillatorSystem.cs b/Content.Server/Medical/DefibrillatorSystem.cs index e3e6dc889c..4373532f01 100644 --- a/Content.Server/Medical/DefibrillatorSystem.cs +++ b/Content.Server/Medical/DefibrillatorSystem.cs @@ -6,6 +6,7 @@ using Content.Server.EUI; using Content.Server.Ghost; using Content.Server.Popups; using Content.Server.PowerCell; +using Content.Server.Traits.Assorted; using Content.Shared.Damage; using Content.Shared.DoAfter; using Content.Shared.Interaction; @@ -214,6 +215,11 @@ public sealed class DefibrillatorSystem : EntitySystem _chatManager.TrySendInGameICMessage(uid, Loc.GetString("defibrillator-rotten"), InGameICChatType.Speak, true); } + else if (HasComp(target)) + { + _chatManager.TrySendInGameICMessage(uid, Loc.GetString("defibrillator-unrevivable"), + InGameICChatType.Speak, true); + } else { if (_mobState.IsDead(target, mob)) diff --git a/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs b/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs index 38e88f6e1e..1acbf292f0 100644 --- a/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs +++ b/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Server.Access.Systems; using Content.Server.DeviceNetwork; using Content.Server.DeviceNetwork.Components; @@ -366,8 +367,8 @@ public sealed class SuitSensorSystem : EntitySystem if (transform.GridUid != null) { coordinates = new EntityCoordinates(transform.GridUid.Value, - _transform.GetInvWorldMatrix(xformQuery.GetComponent(transform.GridUid.Value), xformQuery) - .Transform(_transform.GetWorldPosition(transform, xformQuery))); + Vector2.Transform(_transform.GetWorldPosition(transform, xformQuery), + _transform.GetInvWorldMatrix(xformQuery.GetComponent(transform.GridUid.Value), xformQuery))); } else if (transform.MapUid != null) { diff --git a/Content.Server/Movement/Systems/WaddleAnimationSystem.cs b/Content.Server/Movement/Systems/WaddleAnimationSystem.cs new file mode 100644 index 0000000000..e6083210e1 --- /dev/null +++ b/Content.Server/Movement/Systems/WaddleAnimationSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared.Movement.Systems; + +namespace Content.Server.Movement.Systems; + +public sealed class WaddleAnimationSystem : SharedWaddleAnimationSystem; diff --git a/Content.Server/NPC/HTN/Preconditions/KeyExistsPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/KeyExistsPrecondition.cs index 72c4e6367f..69e265f276 100644 --- a/Content.Server/NPC/HTN/Preconditions/KeyExistsPrecondition.cs +++ b/Content.Server/NPC/HTN/Preconditions/KeyExistsPrecondition.cs @@ -1,8 +1,13 @@ namespace Content.Server.NPC.HTN.Preconditions; +/// +/// Checks for the presence of the value by the specified in the . +/// Returns true if there is a value. +/// public sealed partial class KeyExistsPrecondition : HTNPrecondition { - [DataField("key", required: true)] public string Key = string.Empty; + [DataField(required: true), ViewVariables] + public string Key = string.Empty; public override bool IsMet(NPCBlackboard blackboard) { diff --git a/Content.Server/NPC/HTN/Preconditions/KeyNotExistsPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/KeyNotExistsPrecondition.cs index c12663901c..8dc38e442a 100644 --- a/Content.Server/NPC/HTN/Preconditions/KeyNotExistsPrecondition.cs +++ b/Content.Server/NPC/HTN/Preconditions/KeyNotExistsPrecondition.cs @@ -1,8 +1,12 @@ namespace Content.Server.NPC.HTN.Preconditions; +/// +/// Checks if there is no value at the specified in the . +/// Returns true if there is no value. +/// public sealed partial class KeyNotExistsPrecondition : HTNPrecondition { - [DataField(required: true)] + [DataField(required: true), ViewVariables] public string Key = string.Empty; public override bool IsMet(NPCBlackboard blackboard) diff --git a/Content.Server/NPC/HTN/Preconditions/Math/KeyBoolEqualsPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/Math/KeyBoolEqualsPrecondition.cs index 8c7920e8be..2abb351272 100644 --- a/Content.Server/NPC/HTN/Preconditions/Math/KeyBoolEqualsPrecondition.cs +++ b/Content.Server/NPC/HTN/Preconditions/Math/KeyBoolEqualsPrecondition.cs @@ -1,16 +1,17 @@ namespace Content.Server.NPC.HTN.Preconditions.Math; /// -/// Checks for the presence of data in the blackboard and makes a comparison with the specified boolean +/// Checks if there is a bool value for the specified +/// in the and the specified value is equal to the . /// public sealed partial class KeyBoolEqualsPrecondition : HTNPrecondition { [Dependency] private readonly IEntityManager _entManager = default!; - [DataField(required: true)] + [DataField(required: true), ViewVariables] public string Key = string.Empty; - [DataField(required: true)] + [DataField(required: true), ViewVariables(VVAccess.ReadWrite)] public bool Value; public override bool IsMet(NPCBlackboard blackboard) diff --git a/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatEqualsPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatEqualsPrecondition.cs index 802fdaf2b9..0f7e2cca2a 100644 --- a/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatEqualsPrecondition.cs +++ b/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatEqualsPrecondition.cs @@ -1,13 +1,17 @@ namespace Content.Server.NPC.HTN.Preconditions.Math; +/// +/// Checks if there is a float value for the specified +/// in the and the specified value is equal to the . +/// public sealed partial class KeyFloatEqualsPrecondition : HTNPrecondition { [Dependency] private readonly IEntityManager _entManager = default!; - [DataField(required: true)] + [DataField(required: true), ViewVariables] public string Key = string.Empty; - [DataField(required: true)] + [DataField(required: true), ViewVariables(VVAccess.ReadWrite)] public float Value; public override bool IsMet(NPCBlackboard blackboard) diff --git a/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatGreaterPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatGreaterPrecondition.cs index 3a9ac36698..b3a27a7d5d 100644 --- a/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatGreaterPrecondition.cs +++ b/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatGreaterPrecondition.cs @@ -1,13 +1,17 @@ namespace Content.Server.NPC.HTN.Preconditions.Math; +/// +/// Checks if there is a float value for the specified +/// in the and the specified value is greater then . +/// public sealed partial class KeyFloatGreaterPrecondition : HTNPrecondition { [Dependency] private readonly IEntityManager _entManager = default!; - [DataField(required: true)] + [DataField(required: true), ViewVariables] public string Key = string.Empty; - [DataField(required: true)] + [DataField(required: true), ViewVariables(VVAccess.ReadWrite)] public float Value; public override bool IsMet(NPCBlackboard blackboard) diff --git a/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatLessPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatLessPrecondition.cs index 5cd51d7a7c..c5eb35eebc 100644 --- a/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatLessPrecondition.cs +++ b/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatLessPrecondition.cs @@ -1,13 +1,17 @@ namespace Content.Server.NPC.HTN.Preconditions.Math; +/// +/// Checks if there is a float value for the specified +/// in the and the specified value is less then . +/// public sealed partial class KeyFloatLessPrecondition : HTNPrecondition { [Dependency] private readonly IEntityManager _entManager = default!; - [DataField(required: true)] + [DataField(required: true), ViewVariables] public string Key = string.Empty; - [DataField(required: true)] + [DataField(required: true), ViewVariables(VVAccess.ReadWrite)] public float Value; public override bool IsMet(NPCBlackboard blackboard) diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/AddFloatOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/AddFloatOperator.cs index 00404517c9..f8ed20544e 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/AddFloatOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/AddFloatOperator.cs @@ -4,13 +4,14 @@ using System.Threading.Tasks; namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Math; /// -/// Gets the key, and adds the value to that float +/// Added to float value for the +/// specified in the . /// public sealed partial class AddFloatOperator : HTNOperator { [Dependency] private readonly IEntityManager _entManager = default!; - [DataField(required: true)] + [DataField(required: true), ViewVariables] public string TargetKey = string.Empty; [DataField, ViewVariables(VVAccess.ReadWrite)] diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetBoolOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetBoolOperator.cs index a40b96798d..f168326af7 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetBoolOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetBoolOperator.cs @@ -4,11 +4,12 @@ using System.Threading.Tasks; namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Math; /// -/// Just sets a blackboard key to a bool +/// Set to bool value for the +/// specified in the . /// public sealed partial class SetBoolOperator : HTNOperator { - [DataField(required: true)] + [DataField(required: true), ViewVariables] public string TargetKey = string.Empty; [DataField, ViewVariables(VVAccess.ReadWrite)] diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetFloatOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetFloatOperator.cs index 76842b431f..f9b5e54fe3 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetFloatOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetFloatOperator.cs @@ -4,11 +4,12 @@ using System.Threading.Tasks; namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Math; /// -/// Just sets a blackboard key to a float +/// Set to float value for the +/// specified in the . /// public sealed partial class SetFloatOperator : HTNOperator { - [DataField(required: true)] + [DataField(required: true), ViewVariables] public string TargetKey = string.Empty; [DataField, ViewVariables(VVAccess.ReadWrite)] diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetRandomFloatOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetRandomFloatOperator.cs index 999756f1f7..edcab6baff 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetRandomFloatOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetRandomFloatOperator.cs @@ -5,20 +5,22 @@ using Robust.Shared.Random; namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Math; /// -/// Sets a random float from MinAmount to MaxAmount to blackboard +/// Set random float value between and +/// specified +/// in the . /// public sealed partial class SetRandomFloatOperator : HTNOperator { [Dependency] private readonly IRobustRandom _random = default!; - [DataField(required: true)] + [DataField(required: true), ViewVariables] public string TargetKey = string.Empty; [DataField, ViewVariables(VVAccess.ReadWrite)] public float MaxAmount = 1f; [DataField, ViewVariables(VVAccess.ReadWrite)] - public float MinAmount = 0f; + public float MinAmount; public override async Task<(bool Valid, Dictionary? Effects)> Plan(NPCBlackboard blackboard, CancellationToken cancelToken) diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SayKeyOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SayKeyOperator.cs index d1c7d61915..558b1fc04d 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SayKeyOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SayKeyOperator.cs @@ -20,7 +20,8 @@ public sealed partial class SayKeyOperator : HTNOperator public override void Initialize(IEntitySystemManager sysManager) { base.Initialize(sysManager); - _chat = IoCManager.Resolve().GetEntitySystem(); + + _chat = sysManager.GetEntitySystem(); } public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime) @@ -28,8 +29,12 @@ public sealed partial class SayKeyOperator : HTNOperator if (!blackboard.TryGetValue(Key, out var value, _entManager)) return HTNOperatorStatus.Failed; + var @string = value.ToString(); + if (@string is not { }) + return HTNOperatorStatus.Failed; + var speaker = blackboard.GetValue(NPCBlackboard.Owner); - _chat.TrySendInGameICMessage(speaker, value.ToString() ?? "Oh no...", InGameICChatType.Speak, hideChat: Hidden, hideLog: Hidden); + _chat.TrySendInGameICMessage(speaker, @string, InGameICChatType.Speak, hideChat: Hidden, hideLog: Hidden); return base.Update(blackboard, frameTime); } diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SpeakOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SpeakOperator.cs index cf07831959..8a4c655a39 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SpeakOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SpeakOperator.cs @@ -6,7 +6,7 @@ public sealed partial class SpeakOperator : HTNOperator { private ChatSystem _chat = default!; - [DataField("speech", required: true)] + [DataField(required: true)] public string Speech = string.Empty; /// @@ -18,14 +18,15 @@ public sealed partial class SpeakOperator : HTNOperator public override void Initialize(IEntitySystemManager sysManager) { base.Initialize(sysManager); - _chat = IoCManager.Resolve().GetEntitySystem(); + + _chat = sysManager.GetEntitySystem(); } public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime) { var speaker = blackboard.GetValue(NPCBlackboard.Owner); - _chat.TrySendInGameICMessage(speaker, Loc.GetString(Speech), InGameICChatType.Speak, hideChat: Hidden, hideLog: Hidden); + return base.Update(blackboard, frameTime); } } diff --git a/Content.Server/NPC/Pathfinding/PathfindingSystem.Distance.cs b/Content.Server/NPC/Pathfinding/PathfindingSystem.Distance.cs index 5daf38c420..b986ed5259 100644 --- a/Content.Server/NPC/Pathfinding/PathfindingSystem.Distance.cs +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.Distance.cs @@ -36,7 +36,7 @@ public sealed partial class PathfindingSystem return Vector2.Zero; } - endPos = startXform.InvWorldMatrix.Transform(endXform.WorldMatrix.Transform(endPos)); + endPos = Vector2.Transform(Vector2.Transform(endPos, endXform.WorldMatrix), startXform.InvWorldMatrix); } // TODO: Numerics when we changeover. diff --git a/Content.Server/NPC/Pathfinding/PathfindingSystem.Grid.cs b/Content.Server/NPC/Pathfinding/PathfindingSystem.Grid.cs index 52f7db77ed..f4af65c617 100644 --- a/Content.Server/NPC/Pathfinding/PathfindingSystem.Grid.cs +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.Grid.cs @@ -395,7 +395,7 @@ public sealed partial class PathfindingSystem private Vector2i GetOrigin(EntityCoordinates coordinates, EntityUid gridUid) { - var localPos = _transform.GetInvWorldMatrix(gridUid).Transform(coordinates.ToMapPos(EntityManager, _transform)); + var localPos = Vector2.Transform(coordinates.ToMapPos(EntityManager, _transform), _transform.GetInvWorldMatrix(gridUid)); return new Vector2i((int) Math.Floor(localPos.X / ChunkSize), (int) Math.Floor(localPos.Y / ChunkSize)); } diff --git a/Content.Server/NPC/Pathfinding/PathfindingSystem.cs b/Content.Server/NPC/Pathfinding/PathfindingSystem.cs index 3672ad047b..b2958f0ccb 100644 --- a/Content.Server/NPC/Pathfinding/PathfindingSystem.cs +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.cs @@ -405,7 +405,7 @@ namespace Content.Server.NPC.Pathfinding return null; } - var localPos = xform.InvWorldMatrix.Transform(coordinates.ToMapPos(EntityManager, _transform)); + var localPos = Vector2.Transform(coordinates.ToMapPos(EntityManager, _transform), xform.InvWorldMatrix); var origin = GetOrigin(localPos); if (!TryGetChunk(origin, comp, out var chunk)) diff --git a/Content.Server/NPC/Systems/NPCImprintingOnSpawnBehaviourSystem.cs b/Content.Server/NPC/Systems/NPCImprintingOnSpawnBehaviourSystem.cs index cfd3b08c61..c3e0328037 100644 --- a/Content.Server/NPC/Systems/NPCImprintingOnSpawnBehaviourSystem.cs +++ b/Content.Server/NPC/Systems/NPCImprintingOnSpawnBehaviourSystem.cs @@ -1,6 +1,7 @@ using System.Numerics; using Content.Shared.NPC.Components; using Content.Shared.NPC.Systems; +using Content.Shared.Whitelist; using Robust.Shared.Collections; using Robust.Shared.Map; using Robust.Shared.Random; @@ -13,6 +14,7 @@ public sealed partial class NPCImprintingOnSpawnBehaviourSystem : SharedNPCImpri [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly NPCSystem _npc = default!; [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -27,7 +29,7 @@ public sealed partial class NPCImprintingOnSpawnBehaviourSystem : SharedNPCImpri foreach (var friend in friends) { - if (imprinting.Comp.Whitelist?.IsValid(friend) != false) + if (_whitelistSystem.IsWhitelistPassOrNull(imprinting.Comp.Whitelist, friend)) { AddImprintingTarget(imprinting, friend, imprinting.Comp); } diff --git a/Content.Server/NPC/Systems/NPCUtilitySystem.cs b/Content.Server/NPC/Systems/NPCUtilitySystem.cs index 2e8c628b50..ca74d71335 100644 --- a/Content.Server/NPC/Systems/NPCUtilitySystem.cs +++ b/Content.Server/NPC/Systems/NPCUtilitySystem.cs @@ -19,6 +19,7 @@ using Content.Shared.Tools.Systems; using Content.Shared.Weapons.Melee; using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Events; +using Content.Shared.Whitelist; using Microsoft.Extensions.ObjectPool; using Robust.Server.Containers; using Robust.Shared.Prototypes; @@ -46,6 +47,7 @@ public sealed class NPCUtilitySystem : EntitySystem [Dependency] private readonly SolutionContainerSystem _solutions = default!; [Dependency] private readonly WeldableSystem _weldable = default!; [Dependency] private readonly ExamineSystemShared _examine = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; private EntityQuery _puddleQuery; private EntityQuery _xformQuery; @@ -249,7 +251,7 @@ public sealed class NPCUtilitySystem : EntitySystem return 0f; } - if (heldGun.Whitelist?.IsValid(targetUid, EntityManager) != true) + if (_whitelistSystem.IsWhitelistFailOrNull(heldGun.Whitelist, targetUid)) { return 0f; } diff --git a/Content.Server/Ninja/Systems/StunProviderSystem.cs b/Content.Server/Ninja/Systems/StunProviderSystem.cs index 970ca78e2c..1768606ad2 100644 --- a/Content.Server/Ninja/Systems/StunProviderSystem.cs +++ b/Content.Server/Ninja/Systems/StunProviderSystem.cs @@ -9,6 +9,7 @@ using Content.Shared.Stunnable; using Robust.Shared.Prototypes; using Robust.Shared.Audio.Systems; using Robust.Shared.Timing; +using Content.Shared.Whitelist; namespace Content.Server.Ninja.Systems; @@ -24,6 +25,7 @@ public sealed class StunProviderSystem : SharedStunProviderSystem [Dependency] private readonly SharedNinjaGlovesSystem _gloves = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedStunSystem _stun = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -42,7 +44,7 @@ public sealed class StunProviderSystem : SharedStunProviderSystem if (args.Handled || comp.BatteryUid == null || !_gloves.AbilityCheck(uid, args, out var target)) return; - if (target == uid || !comp.Whitelist.IsValid(target, EntityManager)) + if (target == uid || _whitelistSystem.IsWhitelistFail(comp.Whitelist, target)) return; if (_timing.CurTime < comp.NextStun) diff --git a/Content.Server/Nutrition/EntitySystems/AnimalHusbandrySystem.cs b/Content.Server/Nutrition/EntitySystems/AnimalHusbandrySystem.cs index cd05413405..e224c7c479 100644 --- a/Content.Server/Nutrition/EntitySystems/AnimalHusbandrySystem.cs +++ b/Content.Server/Nutrition/EntitySystems/AnimalHusbandrySystem.cs @@ -1,4 +1,4 @@ -using Content.Server.Administration.Logs; +using Content.Server.Administration.Logs; using Content.Server.Popups; using Content.Shared.Database; using Content.Shared.IdentityManagement; @@ -9,6 +9,7 @@ using Content.Shared.Nutrition.AnimalHusbandry; using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.EntitySystems; using Content.Shared.Storage; +using Content.Shared.Whitelist; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; @@ -33,6 +34,7 @@ public sealed class AnimalHusbandrySystem : EntitySystem [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; private readonly HashSet _failedAttempts = new(); private readonly HashSet _birthQueue = new(); @@ -174,7 +176,7 @@ public sealed class AnimalHusbandrySystem : EntitySystem if (!CanReproduce(partner)) return false; - return component.PartnerWhitelist.IsValid(partner); + return _whitelistSystem.IsWhitelistPass(component.PartnerWhitelist, partner); } /// diff --git a/Content.Server/Nutrition/EntitySystems/FoodSystem.cs b/Content.Server/Nutrition/EntitySystems/FoodSystem.cs index 2c7632aadc..1862b4e19f 100644 --- a/Content.Server/Nutrition/EntitySystems/FoodSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/FoodSystem.cs @@ -32,6 +32,7 @@ using Robust.Shared.Audio.Systems; using Robust.Shared.Utility; using System.Linq; using Robust.Server.GameObjects; +using Content.Shared.Whitelist; namespace Content.Server.Nutrition.EntitySystems; @@ -57,6 +58,7 @@ public sealed class FoodSystem : EntitySystem [Dependency] private readonly StackSystem _stack = default!; [Dependency] private readonly StomachSystem _stomach = default!; [Dependency] private readonly UtensilSystem _utensil = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public const float MaxFeedDistance = 1.0f; @@ -408,7 +410,7 @@ public sealed class FoodSystem : EntitySystem if (comp.SpecialDigestible == null) continue; // Check if the food is in the whitelist - if (comp.SpecialDigestible.IsValid(food, EntityManager)) + if (_whitelistSystem.IsWhitelistPass(comp.SpecialDigestible, food)) return true; // They can only eat whitelist food and the food isn't in the whitelist. It's not edible. return false; diff --git a/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.cs b/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.cs index b93c87f2f4..90bc11f717 100644 --- a/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.cs +++ b/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.cs @@ -45,6 +45,7 @@ using Content.Shared.Nyanotrasen.Kitchen.UI; using Content.Shared.Popups; using Content.Shared.Throwing; using Content.Shared.UserInterface; +using Content.Shared.Whitelist; using FastAccessors; using Robust.Server.GameObjects; using Robust.Shared.Audio; @@ -79,6 +80,7 @@ public sealed partial class DeepFryerSystem : SharedDeepfryerSystem [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; [Dependency] private readonly AmbientSoundSystem _ambientSoundSystem = default!; [Dependency] private readonly MetaDataSystem _metaDataSystem = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; private static readonly string CookingDamageType = "Heat"; private static readonly float CookingDamageAmount = 10.0f; @@ -309,7 +311,7 @@ public sealed partial class DeepFryerSystem : SharedDeepfryerSystem // just in case the attempt is relevant to any system in the future. // // The blacklist overrides all. - if (component.Blacklist != null && component.Blacklist.IsValid(item, EntityManager)) + if (component.Blacklist != null && _whitelistSystem.IsWhitelistPass(component.Blacklist, item)) { _popupSystem.PopupEntity( Loc.GetString("deep-fryer-blacklist-item-failed", @@ -351,7 +353,7 @@ public sealed partial class DeepFryerSystem : SharedDeepfryerSystem _ => 10 } * component.SolutionSizeCoefficient); - if (component.Whitelist != null && component.Whitelist.IsValid(item, EntityManager) || + if (component.Whitelist != null && _whitelistSystem.IsWhitelistPass(component.Whitelist, item) || beingEvent.TurnIntoFood) MakeEdible(uid, component, item, solutionQuantity); else diff --git a/Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicInvisibleContactsSystem.cs b/Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicInvisibleContactsSystem.cs index cec755e326..c0c91fcc69 100644 --- a/Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicInvisibleContactsSystem.cs +++ b/Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicInvisibleContactsSystem.cs @@ -1,5 +1,6 @@ using Content.Shared.Stealth; using Content.Shared.Stealth.Components; +using Content.Shared.Whitelist; using Robust.Shared.Physics.Events; using Robust.Shared.Physics.Systems; using Robust.Shared.Timing; @@ -13,6 +14,7 @@ namespace Content.Server.Psionics { [Dependency] private readonly SharedStealthSystem _stealth = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -28,7 +30,7 @@ namespace Content.Server.Psionics var otherUid = args.OtherEntity; var ourEntity = args.OurEntity; - if (!component.Whitelist.IsValid(otherUid)) + if (_whitelistSystem.IsWhitelistFail(component.Whitelist, otherUid)) return; // This will go up twice per web hit, since webs also have a flammable fixture. @@ -48,7 +50,7 @@ namespace Content.Server.Psionics var otherUid = args.OtherEntity; var ourEntity = args.OurEntity; - if (!component.Whitelist.IsValid(otherUid)) + if (_whitelistSystem.IsWhitelistFail(component.Whitelist, otherUid)) return; if (!HasComp(ourEntity)) diff --git a/Content.Server/Objectives/Systems/ObjectiveBlacklistRequirementSystem.cs b/Content.Server/Objectives/Systems/ObjectiveBlacklistRequirementSystem.cs index 56b245ce84..8fe7e7e203 100644 --- a/Content.Server/Objectives/Systems/ObjectiveBlacklistRequirementSystem.cs +++ b/Content.Server/Objectives/Systems/ObjectiveBlacklistRequirementSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Objectives.Components; using Content.Shared.Objectives.Components; +using Content.Shared.Whitelist; namespace Content.Server.Objectives.Systems; @@ -8,6 +9,8 @@ namespace Content.Server.Objectives.Systems; /// public sealed class ObjectiveBlacklistRequirementSystem : EntitySystem { + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; + public override void Initialize() { base.Initialize(); @@ -22,7 +25,7 @@ public sealed class ObjectiveBlacklistRequirementSystem : EntitySystem foreach (var objective in args.Mind.AllObjectives) { - if (comp.Blacklist.IsValid(objective, EntityManager)) + if (_whitelistSystem.IsBlacklistPass(comp.Blacklist, objective)) { args.Cancelled = true; return; diff --git a/Content.Server/Objectives/Systems/RoleRequirementSystem.cs b/Content.Server/Objectives/Systems/RoleRequirementSystem.cs index 97aee218f0..8421987bae 100644 --- a/Content.Server/Objectives/Systems/RoleRequirementSystem.cs +++ b/Content.Server/Objectives/Systems/RoleRequirementSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Objectives.Components; using Content.Shared.Objectives.Components; +using Content.Shared.Whitelist; namespace Content.Server.Objectives.Systems; @@ -8,6 +9,7 @@ namespace Content.Server.Objectives.Systems; /// public sealed class RoleRequirementSystem : EntitySystem { + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { base.Initialize(); @@ -22,7 +24,7 @@ public sealed class RoleRequirementSystem : EntitySystem // this whitelist trick only works because roles are components on the mind and not entities // if that gets reworked then this will need changing - if (!comp.Roles.IsValid(args.MindId, EntityManager)) + if (_whitelistSystem.IsWhitelistFail(comp.Roles, args.MindId)) args.Cancelled = true; } } diff --git a/Content.Server/PDA/PdaSystem.cs b/Content.Server/PDA/PdaSystem.cs index d4934ee24e..43bf571eb4 100644 --- a/Content.Server/PDA/PdaSystem.cs +++ b/Content.Server/PDA/PdaSystem.cs @@ -8,6 +8,7 @@ using Content.Server.PDA.Ringer; using Content.Server.Station.Systems; using Content.Server.Store.Components; using Content.Server.Store.Systems; +using Content.Server.Traitor.Uplink; using Content.Shared.Access.Components; using Content.Shared.CartridgeLoader; using Content.Shared.Chat; @@ -15,6 +16,7 @@ using Content.Shared.Light; using Content.Shared.Light.Components; using Content.Shared.Light.EntitySystems; using Content.Shared.PDA; +using Content.Shared.Store.Components; using Robust.Server.Containers; using Robust.Server.GameObjects; using Robust.Shared.Containers; @@ -152,7 +154,7 @@ namespace Content.Server.PDA var address = GetDeviceNetAddress(uid); var hasInstrument = HasComp(uid); - var showUplink = HasComp(uid) && IsUnlocked(uid); + var showUplink = HasComp(uid) && IsUnlocked(uid); UpdateStationName(uid, pda); UpdateAlertLevel(uid, pda); @@ -237,8 +239,8 @@ namespace Content.Server.PDA return; // check if its locked again to prevent malicious clients opening locked uplinks - if (TryComp(uid, out var store) && IsUnlocked(uid)) - _store.ToggleUi(msg.Actor, uid, store); + if (HasComp(uid) && IsUnlocked(uid)) + _store.ToggleUi(msg.Actor, uid); } private void OnUiMessage(EntityUid uid, PdaComponent pda, PdaLockUplinkMessage msg) diff --git a/Content.Server/PDA/Ringer/RingerSystem.cs b/Content.Server/PDA/Ringer/RingerSystem.cs index 47ae41896e..e15dcfaa2b 100644 --- a/Content.Server/PDA/Ringer/RingerSystem.cs +++ b/Content.Server/PDA/Ringer/RingerSystem.cs @@ -6,6 +6,7 @@ using Content.Shared.PDA; using Content.Shared.PDA.Ringer; using Content.Shared.Popups; using Content.Shared.Store; +using Content.Shared.Store.Components; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Network; diff --git a/Content.Server/Pinpointer/NavMapSystem.cs b/Content.Server/Pinpointer/NavMapSystem.cs index dba964753f..424b6427de 100644 --- a/Content.Server/Pinpointer/NavMapSystem.cs +++ b/Content.Server/Pinpointer/NavMapSystem.cs @@ -237,6 +237,16 @@ public sealed partial class NavMapSystem : SharedNavMapSystem component.Chunks.Clear(); component.Beacons.Clear(); + // Refresh beacons + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var qUid, out var qNavComp, out var qTransComp)) + { + if (qTransComp.ParentUid != uid) + continue; + + UpdateNavMapBeaconData(qUid, qNavComp); + } + // Loop over all tiles var tileRefs = _mapSystem.GetAllTiles(uid, mapGrid); diff --git a/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs b/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs index 3c9b1b1246..88b46cd922 100644 --- a/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs +++ b/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs @@ -35,6 +35,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem [Dependency] private readonly MindSystem _minds = default!; [Dependency] private readonly PlayTimeTrackingManager _tracking = default!; [Dependency] private readonly IAdminManager _adminManager = default!; + [Dependency] private readonly SharedRoleSystem _role = default!; public override void Initialize() { @@ -198,7 +199,6 @@ public sealed class PlayTimeTrackingSystem : EntitySystem public bool IsAllowed(ICommonSession player, string role) { if (!_prototypes.TryIndex(role, out var job) || - job.Requirements == null || !_cfg.GetCVar(CCVars.GameRoleTimers)) return true; @@ -229,19 +229,8 @@ public sealed class PlayTimeTrackingSystem : EntitySystem foreach (var job in _prototypes.EnumeratePrototypes()) { - if (job.Requirements != null) - { - foreach (var requirement in job.Requirements) - { - if (JobRequirements.TryRequirementMet(requirement, playTimes, out _, EntityManager, _prototypes, isWhitelisted)) - continue; - - goto NoRole; - } - } - - roles.Add(job.ID); - NoRole:; + if (JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes, isWhitelisted)) + roles.Add(job.ID); } return roles; @@ -264,22 +253,14 @@ public sealed class PlayTimeTrackingSystem : EntitySystem for (var i = 0; i < jobs.Count; i++) { - var job = jobs[i]; - - if (!_prototypes.TryIndex(job, out var jobber) || - jobber.Requirements == null || - jobber.Requirements.Count == 0) - continue; - - foreach (var requirement in jobber.Requirements) + if (_prototypes.TryIndex(jobs[i], out var job) + && JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes, isWhitelisted)) { - if (JobRequirements.TryRequirementMet(requirement, playTimes, out _, EntityManager, _prototypes, isWhitelisted)) - continue; - - jobs.RemoveSwap(i); - i--; - break; + continue; } + + jobs.RemoveSwap(i); + i--; } } diff --git a/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs b/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs index 6e0e0c503a..7882522d30 100644 --- a/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs +++ b/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs @@ -7,7 +7,7 @@ using Content.Shared.Containers.ItemSlots; using Content.Shared.Interaction; using Content.Shared.PneumaticCannon; using Content.Shared.StatusEffect; -using Content.Shared.Tools.Components; +using Content.Shared.Tools.Systems; using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Events; using Content.Shared.Weapons.Ranged.Systems; @@ -22,6 +22,7 @@ public sealed class PneumaticCannonSystem : SharedPneumaticCannonSystem [Dependency] private readonly GunSystem _gun = default!; [Dependency] private readonly StunSystem _stun = default!; [Dependency] private readonly ItemSlotsSystem _slots = default!; + [Dependency] private readonly SharedToolSystem _toolSystem = default!; public override void Initialize() { @@ -38,10 +39,7 @@ public sealed class PneumaticCannonSystem : SharedPneumaticCannonSystem if (args.Handled) return; - if (!TryComp(args.Used, out var tool)) - return; - - if (!tool.Qualities.Contains(component.ToolModifyPower)) + if (!_toolSystem.HasQuality(args.Used, component.ToolModifyPower)) return; var val = (int) component.Power; diff --git a/Content.Server/Pointing/EntitySystems/PointingSystem.cs b/Content.Server/Pointing/EntitySystems/PointingSystem.cs index 960c8dc28a..9dc3b5e1e6 100644 --- a/Content.Server/Pointing/EntitySystems/PointingSystem.cs +++ b/Content.Server/Pointing/EntitySystems/PointingSystem.cs @@ -174,7 +174,7 @@ namespace Content.Server.Pointing.EntitySystems { var arrowVisibility = EntityManager.EnsureComponent(arrow); layer = playerVisibility.Layer; - _visibilitySystem.SetLayer(arrow, arrowVisibility, layer); + _visibilitySystem.SetLayer((arrow, arrowVisibility), (ushort) layer); } // Get players that are in range and whose visibility layer matches the arrow's. diff --git a/Content.Server/Polymorph/Systems/PolymorphSystem.Collide.cs b/Content.Server/Polymorph/Systems/PolymorphSystem.Collide.cs index b4240d409a..b29f46e09e 100644 --- a/Content.Server/Polymorph/Systems/PolymorphSystem.Collide.cs +++ b/Content.Server/Polymorph/Systems/PolymorphSystem.Collide.cs @@ -1,6 +1,7 @@ using Content.Server.Polymorph.Components; using Content.Shared.Polymorph; using Content.Shared.Projectiles; +using Content.Shared.Whitelist; using Robust.Shared.Audio; using Robust.Shared.Physics.Events; using Robust.Shared.Prototypes; @@ -9,6 +10,8 @@ namespace Content.Server.Polymorph.Systems; public partial class PolymorphSystem { + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; + /// /// Need to do this so we don't get a collection enumeration error in physics by polymorphing /// an entity we're colliding with @@ -39,8 +42,8 @@ public partial class PolymorphSystem return; var other = args.OtherEntity; - if (!component.Whitelist.IsValid(other, EntityManager) - || component.Blacklist != null && component.Blacklist.IsValid(other, EntityManager)) + if (_whitelistSystem.IsWhitelistFail(component.Whitelist, other) || + _whitelistSystem.IsBlacklistPass(component.Blacklist, other)) return; _queuedPolymorphUpdates.Enqueue(new (other, component.Sound, component.Polymorph)); diff --git a/Content.Server/Power/EntitySystems/ChargerSystem.cs b/Content.Server/Power/EntitySystems/ChargerSystem.cs index db16dfa008..67b9db5e6b 100644 --- a/Content.Server/Power/EntitySystems/ChargerSystem.cs +++ b/Content.Server/Power/EntitySystems/ChargerSystem.cs @@ -8,6 +8,7 @@ using Robust.Shared.Containers; using System.Diagnostics.CodeAnalysis; using Content.Shared.Storage.Components; using Robust.Server.Containers; +using Content.Shared.Whitelist; namespace Content.Server.Power.EntitySystems; @@ -18,6 +19,7 @@ internal sealed class ChargerSystem : EntitySystem [Dependency] private readonly PowerCellSystem _powerCell = default!; [Dependency] private readonly BatterySystem _battery = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -195,7 +197,7 @@ internal sealed class ChargerSystem : EntitySystem if (!receiverComponent.Powered) return; - if (component.Whitelist?.IsValid(targetEntity, EntityManager) == false) + if (_whitelistSystem.IsWhitelistFail(component.Whitelist, targetEntity)) return; if (!SearchForBattery(targetEntity, out var batteryUid, out var heldBattery)) diff --git a/Content.Server/Power/EntitySystems/PowerNetSystem.cs b/Content.Server/Power/EntitySystems/PowerNetSystem.cs index 7bd057951c..6c35ba2008 100644 --- a/Content.Server/Power/EntitySystems/PowerNetSystem.cs +++ b/Content.Server/Power/EntitySystems/PowerNetSystem.cs @@ -3,9 +3,11 @@ using Content.Server.NodeContainer.EntitySystems; using Content.Server.Power.Components; using Content.Server.Power.NodeGroups; using Content.Server.Power.Pow3r; +using Content.Shared.CCVar; using Content.Shared.Power; using JetBrains.Annotations; using Robust.Server.GameObjects; +using Robust.Shared.Configuration; using Robust.Shared.Threading; namespace Content.Server.Power.EntitySystems @@ -18,6 +20,7 @@ namespace Content.Server.Power.EntitySystems { [Dependency] private readonly AppearanceSystem _appearance = default!; [Dependency] private readonly PowerNetConnectorSystem _powerNetConnector = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly IParallelManager _parMan = default!; [Dependency] private readonly PowerReceiverSystem _powerReceiver = default!; @@ -25,13 +28,14 @@ namespace Content.Server.Power.EntitySystems private readonly HashSet _powerNetReconnectQueue = new(); private readonly HashSet _apcNetReconnectQueue = new(); - private readonly BatteryRampPegSolver _solver = new(); + private BatteryRampPegSolver _solver = new(); public override void Initialize() { base.Initialize(); UpdatesAfter.Add(typeof(NodeGroupSystem)); + _solver = new(_cfg.GetCVar(CCVars.DebugPow3rDisableParallel)); SubscribeLocalEvent(ApcPowerReceiverInit); SubscribeLocalEvent(ApcPowerReceiverShutdown); @@ -53,6 +57,13 @@ namespace Content.Server.Power.EntitySystems SubscribeLocalEvent(PowerSupplierShutdown); SubscribeLocalEvent(PowerSupplierPaused); SubscribeLocalEvent(PowerSupplierUnpaused); + + Subs.CVar(_cfg, CCVars.DebugPow3rDisableParallel, DebugPow3rDisableParallelChanged); + } + + private void DebugPow3rDisableParallelChanged(bool val) + { + _solver = new(val); } private void ApcPowerReceiverInit(EntityUid uid, ApcPowerReceiverComponent component, ComponentInit args) diff --git a/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs b/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs index 0afd86679b..599c55b6ba 100644 --- a/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs +++ b/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using Robust.Shared.Utility; using System.Linq; using Robust.Shared.Threading; @@ -8,9 +9,11 @@ namespace Content.Server.Power.Pow3r public sealed class BatteryRampPegSolver : IPowerSolver { private UpdateNetworkJob _networkJob; + private bool _disableParallel; - public BatteryRampPegSolver() + public BatteryRampPegSolver(bool disableParallel = false) { + _disableParallel = disableParallel; _networkJob = new() { Solver = this, @@ -37,6 +40,7 @@ namespace Content.Server.Power.Pow3r DebugTools.Assert(state.GroupedNets.Select(x => x.Count).Sum() == state.Networks.Count); _networkJob.State = state; _networkJob.FrameTime = frameTime; + ValidateNetworkGroups(state, state.GroupedNets); // Each network height layer can be run in parallel without issues. foreach (var group in state.GroupedNets) @@ -54,7 +58,10 @@ namespace Content.Server.Power.Pow3r // suppliers + discharger) Then decide based on total layer size whether its worth parallelizing that // layer? _networkJob.Networks = group; - parallel.ProcessNow(_networkJob, group.Count); + if (_disableParallel) + parallel.ProcessSerialNow(_networkJob, group.Count); + else + parallel.ProcessNow(_networkJob, group.Count); } ClearBatteries(state); @@ -321,9 +328,69 @@ namespace Content.Server.Power.Pow3r RecursivelyEstimateNetworkDepth(state, network, groupedNetworks); } + ValidateNetworkGroups(state, groupedNetworks); return groupedNetworks; } + /// + /// Validate that network grouping is up to date. I.e., that it is safe to solve each networking in a given + /// group in parallel. This assumes that batteries are the only device that connects to multiple networks, and + /// is thus the only obstacle to solving everything in parallel. + /// + [Conditional("DEBUG")] + private void ValidateNetworkGroups(PowerState state, List> groupedNetworks) + { + HashSet nets = new(); + HashSet netIds = new(); + foreach (var layer in groupedNetworks) + { + nets.Clear(); + netIds.Clear(); + + foreach (var net in layer) + { + foreach (var batteryId in net.BatteryLoads) + { + var battery = state.Batteries[batteryId]; + if (battery.LinkedNetworkDischarging == default) + continue; + + var subNet = state.Networks[battery.LinkedNetworkDischarging]; + if (battery.LinkedNetworkDischarging == net.Id) + { + DebugTools.Assert(subNet == net); + continue; + } + + DebugTools.Assert(!nets.Contains(subNet)); + DebugTools.Assert(!netIds.Contains(subNet.Id)); + DebugTools.Assert(subNet.Height < net.Height); + } + + foreach (var batteryId in net.BatterySupplies) + { + var battery = state.Batteries[batteryId]; + if (battery.LinkedNetworkCharging == default) + continue; + + var parentNet = state.Networks[battery.LinkedNetworkCharging]; + if (battery.LinkedNetworkCharging == net.Id) + { + DebugTools.Assert(parentNet == net); + continue; + } + + DebugTools.Assert(!nets.Contains(parentNet)); + DebugTools.Assert(!netIds.Contains(parentNet.Id)); + DebugTools.Assert(parentNet.Height > net.Height); + } + + DebugTools.Assert(nets.Add(net)); + DebugTools.Assert(netIds.Add(net.Id)); + } + } + } + private static void RecursivelyEstimateNetworkDepth(PowerState state, Network network, List> groupedNetworks) { network.Height = -2; diff --git a/Content.Server/Prayer/PrayerSystem.cs b/Content.Server/Prayer/PrayerSystem.cs index c8ef368dad..3b1ec3fa08 100644 --- a/Content.Server/Prayer/PrayerSystem.cs +++ b/Content.Server/Prayer/PrayerSystem.cs @@ -54,7 +54,7 @@ public sealed class PrayerSystem : EntitySystem return; } - _quickDialog.OpenDialog(actor.PlayerSession, Loc.GetString(comp.Verb), "Message", (string message) => + _quickDialog.OpenDialog(actor.PlayerSession, Loc.GetString(comp.Verb), Loc.GetString("prayer-popup-notify-pray-ui-message"), (string message) => { Pray(actor.PlayerSession, comp, message); }); diff --git a/Content.Server/Preferences/Managers/ServerPreferencesManager.cs b/Content.Server/Preferences/Managers/ServerPreferencesManager.cs index f7c15a2340..8de458b6ee 100644 --- a/Content.Server/Preferences/Managers/ServerPreferencesManager.cs +++ b/Content.Server/Preferences/Managers/ServerPreferencesManager.cs @@ -305,11 +305,7 @@ namespace Content.Server.Preferences.Managers return usernames .Select(p => (_cachedPlayerPrefs[p].Prefs, p)) .Where(p => p.Prefs != null) - .Select(p => - { - var idx = p.Prefs!.SelectedCharacterIndex; - return new KeyValuePair(p.p, p.Prefs!.GetProfile(idx)); - }); + .Select(p => new KeyValuePair(p.p, p.Prefs!.SelectedCharacter)); } internal static bool ShouldStorePrefs(LoginType loginType) diff --git a/Content.Server/Procedural/DungeonJob.PostGen.cs b/Content.Server/Procedural/DungeonJob.PostGen.cs index b326bcc378..cb9e64f04e 100644 --- a/Content.Server/Procedural/DungeonJob.PostGen.cs +++ b/Content.Server/Procedural/DungeonJob.PostGen.cs @@ -13,6 +13,7 @@ using Robust.Shared.Collections; using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Physics.Components; +using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Utility; @@ -24,13 +25,15 @@ public sealed partial class DungeonJob * Run after the main dungeon generation */ + private static readonly ProtoId WallTag = "Wall"; + private bool HasWall(MapGridComponent grid, Vector2i tile) { var anchored = _maps.GetAnchoredEntitiesEnumerator(_gridUid, _grid, tile); while (anchored.MoveNext(out var uid)) { - if (_tagQuery.TryGetComponent(uid, out var tagComp) && tagComp.Tags.Contains("Wall")) + if (_tag.HasTag(uid.Value, WallTag)) return true; } diff --git a/Content.Server/Procedural/DungeonJob.PrefabDunGen.cs b/Content.Server/Procedural/DungeonJob.PrefabDunGen.cs index 1783a56790..a19f7e4701 100644 --- a/Content.Server/Procedural/DungeonJob.PrefabDunGen.cs +++ b/Content.Server/Procedural/DungeonJob.PrefabDunGen.cs @@ -19,7 +19,7 @@ public sealed partial class DungeonJob var gen = _prototype.Index(preset); var dungeonRotation = _dungeon.GetDungeonRotation(seed); - var dungeonTransform = Matrix3.CreateTransform(_position, dungeonRotation); + var dungeonTransform = Matrix3Helpers.CreateTransform(_position, dungeonRotation); var roomPackProtos = new Dictionary>(); foreach (var pack in _prototype.EnumeratePrototypes()) @@ -69,7 +69,7 @@ public sealed partial class DungeonJob var dungeon = new Dungeon(); var availablePacks = new List(); var chosenPacks = new DungeonRoomPackPrototype?[gen.RoomPacks.Count]; - var packTransforms = new Matrix3[gen.RoomPacks.Count]; + var packTransforms = new Matrix3x2[gen.RoomPacks.Count]; var packRotations = new Angle[gen.RoomPacks.Count]; // Actually pick the room packs and rooms @@ -97,7 +97,7 @@ public sealed partial class DungeonJob // Iterate every pack random.Shuffle(availablePacks); - Matrix3 packTransform = default!; + Matrix3x2 packTransform = default!; var found = false; DungeonRoomPackPrototype pack = default!; @@ -128,7 +128,7 @@ public sealed partial class DungeonJob var aRotation = dir.AsDir().ToAngle(); // Use this pack - packTransform = Matrix3.CreateTransform(bounds.Center, aRotation); + packTransform = Matrix3Helpers.CreateTransform(bounds.Center, aRotation); packRotations[i] = aRotation; pack = aPack; break; @@ -168,7 +168,7 @@ public sealed partial class DungeonJob { var roomDimensions = new Vector2i(roomSize.Width, roomSize.Height); Angle roomRotation = Angle.Zero; - Matrix3 matty; + Matrix3x2 matty; if (!roomProtos.TryGetValue(roomDimensions, out var roomProto)) { @@ -176,13 +176,13 @@ public sealed partial class DungeonJob if (!roomProtos.TryGetValue(roomDimensions, out roomProto)) { - Matrix3.Multiply(packTransform, dungeonTransform, out matty); + matty = Matrix3x2.Multiply(packTransform, dungeonTransform); for (var x = roomSize.Left; x < roomSize.Right; x++) { for (var y = roomSize.Bottom; y < roomSize.Top; y++) { - var index = matty.Transform(new Vector2(x, y) + grid.TileSizeHalfVector - packCenter).Floored(); + var index = Vector2.Transform(new Vector2(x, y) + grid.TileSizeHalfVector - packCenter, matty).Floored(); tiles.Add((index, new Tile(_tileDefManager["FloorPlanetGrass"].TileId))); } } @@ -209,10 +209,10 @@ public sealed partial class DungeonJob roomRotation += Math.PI; } - var roomTransform = Matrix3.CreateTransform(roomSize.Center - packCenter, roomRotation); + var roomTransform = Matrix3Helpers.CreateTransform(roomSize.Center - packCenter, roomRotation); - Matrix3.Multiply(roomTransform, packTransform, out matty); - Matrix3.Multiply(matty, dungeonTransform, out var dungeonMatty); + matty = Matrix3x2.Multiply(roomTransform, packTransform); + var dungeonMatty = Matrix3x2.Multiply(matty, dungeonTransform); // The expensive bit yippy. _dungeon.SpawnRoom(gridUid, grid, dungeonMatty, room); @@ -232,7 +232,7 @@ public sealed partial class DungeonJob continue; } - var tilePos = dungeonMatty.Transform(new Vector2i(x + room.Offset.X, y + room.Offset.Y) + tileOffset); + var tilePos = Vector2.Transform(new Vector2i(x + room.Offset.X, y + room.Offset.Y) + tileOffset, dungeonMatty); exterior.Add(tilePos.Floored()); } } @@ -244,7 +244,7 @@ public sealed partial class DungeonJob for (var y = 0; y < room.Size.Y; y++) { var roomTile = new Vector2i(x + room.Offset.X, y + room.Offset.Y); - var tilePos = dungeonMatty.Transform(roomTile + tileOffset); + var tilePos = Vector2.Transform(roomTile + tileOffset, dungeonMatty); var tileIndex = tilePos.Floored(); roomTiles.Add(tileIndex); diff --git a/Content.Server/Procedural/DungeonJob.cs b/Content.Server/Procedural/DungeonJob.cs index 8fecf1c9e8..bf2822ff42 100644 --- a/Content.Server/Procedural/DungeonJob.cs +++ b/Content.Server/Procedural/DungeonJob.cs @@ -28,10 +28,10 @@ public sealed partial class DungeonJob : Job private readonly DecalSystem _decals; private readonly DungeonSystem _dungeon; private readonly EntityLookupSystem _lookup; + private readonly TagSystem _tag; private readonly TileSystem _tile; private readonly SharedMapSystem _maps; private readonly SharedTransformSystem _transform; - private EntityQuery _tagQuery; private readonly DungeonConfigPrototype _gen; private readonly int _seed; @@ -53,6 +53,7 @@ public sealed partial class DungeonJob : Job DecalSystem decals, DungeonSystem dungeon, EntityLookupSystem lookup, + TagSystem tag, TileSystem tile, SharedTransformSystem transform, DungeonConfigPrototype gen, @@ -72,10 +73,10 @@ public sealed partial class DungeonJob : Job _decals = decals; _dungeon = dungeon; _lookup = lookup; + _tag = tag; _tile = tile; _maps = _entManager.System(); _transform = transform; - _tagQuery = _entManager.GetEntityQuery(); _gen = gen; _grid = grid; diff --git a/Content.Server/Procedural/DungeonSystem.Rooms.cs b/Content.Server/Procedural/DungeonSystem.Rooms.cs index 03bcc2b4b1..5b4de34906 100644 --- a/Content.Server/Procedural/DungeonSystem.Rooms.cs +++ b/Content.Server/Procedural/DungeonSystem.Rooms.cs @@ -67,7 +67,7 @@ public sealed partial class DungeonSystem bool clearExisting = false, bool rotation = false) { - var originTransform = Matrix3.CreateTranslation(origin); + var originTransform = Matrix3Helpers.CreateTranslation(origin.X, origin.Y); var roomRotation = Angle.Zero; if (rotation) @@ -75,8 +75,8 @@ public sealed partial class DungeonSystem roomRotation = GetRoomRotation(room, random); } - var roomTransform = Matrix3.CreateTransform((Vector2) room.Size / 2f, roomRotation); - Matrix3.Multiply(roomTransform, originTransform, out var finalTransform); + var roomTransform = Matrix3Helpers.CreateTransform((Vector2) room.Size / 2f, roomRotation); + var finalTransform = Matrix3x2.Multiply(roomTransform, originTransform); SpawnRoom(gridUid, grid, finalTransform, room, clearExisting); } @@ -101,7 +101,7 @@ public sealed partial class DungeonSystem public void SpawnRoom( EntityUid gridUid, MapGridComponent grid, - Matrix3 roomTransform, + Matrix3x2 roomTransform, DungeonRoomPrototype room, bool clearExisting = false) { @@ -116,7 +116,7 @@ public sealed partial class DungeonSystem // go BRRNNTTT on existing stuff if (clearExisting) { - var gridBounds = new Box2(roomTransform.Transform(Vector2.Zero), roomTransform.Transform(room.Size)); + var gridBounds = new Box2(Vector2.Transform(Vector2.Zero, roomTransform), Vector2.Transform(room.Size, roomTransform)); _entitySet.Clear(); // Polygon skin moment gridBounds = gridBounds.Enlarged(-0.05f); @@ -148,7 +148,7 @@ public sealed partial class DungeonSystem var indices = new Vector2i(x + room.Offset.X, y + room.Offset.Y); var tileRef = _maps.GetTileRef(templateMapUid, templateGrid, indices); - var tilePos = roomTransform.Transform(indices + tileOffset); + var tilePos = Vector2.Transform(indices + tileOffset, roomTransform); var rounded = tilePos.Floored(); _tiles.Add((rounded, tileRef.Tile)); } @@ -164,7 +164,7 @@ public sealed partial class DungeonSystem foreach (var templateEnt in _lookup.GetEntitiesIntersecting(templateMapUid, bounds, LookupFlags.Uncontained)) { var templateXform = _xformQuery.GetComponent(templateEnt); - var childPos = roomTransform.Transform(templateXform.LocalPosition - roomCenter); + var childPos = Vector2.Transform(templateXform.LocalPosition - roomCenter, roomTransform); var childRot = templateXform.LocalRotation + finalRoomRotation; var protoId = _metaQuery.GetComponent(templateEnt).EntityPrototype?.ID; @@ -192,7 +192,7 @@ public sealed partial class DungeonSystem // Offset by 0.5 because decals are offset from bot-left corner // So we convert it to center of tile then convert it back again after transform. // Do these shenanigans because 32x32 decals assume as they are centered on bottom-left of tiles. - var position = roomTransform.Transform(decal.Coordinates + Vector2Helpers.Half - roomCenter); + var position = Vector2.Transform(decal.Coordinates + Vector2Helpers.Half - roomCenter, roomTransform); position -= Vector2Helpers.Half; // Umm uhh I love decals so uhhhh idk what to do about this diff --git a/Content.Server/Procedural/DungeonSystem.cs b/Content.Server/Procedural/DungeonSystem.cs index 069508bcbb..36009896a2 100644 --- a/Content.Server/Procedural/DungeonSystem.cs +++ b/Content.Server/Procedural/DungeonSystem.cs @@ -10,6 +10,7 @@ using Content.Shared.GameTicking; using Content.Shared.Maps; using Content.Shared.Physics; using Content.Shared.Procedural; +using Content.Shared.Tag; using Robust.Server.GameObjects; using Robust.Shared.Configuration; using Robust.Shared.Console; @@ -31,6 +32,7 @@ public sealed partial class DungeonSystem : SharedDungeonSystem [Dependency] private readonly AnchorableSystem _anchorable = default!; [Dependency] private readonly DecalSystem _decals = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly TagSystem _tag = default!; [Dependency] private readonly TileSystem _tile = default!; [Dependency] private readonly MapLoaderSystem _loader = default!; [Dependency] private readonly SharedMapSystem _maps = default!; @@ -199,6 +201,7 @@ public sealed partial class DungeonSystem : SharedDungeonSystem _decals, this, _lookup, + _tag, _tile, _transform, gen, @@ -231,6 +234,7 @@ public sealed partial class DungeonSystem : SharedDungeonSystem _decals, this, _lookup, + _tag, _tile, _transform, gen, diff --git a/Content.Server/Radiation/Systems/RadiationSystem.GridCast.cs b/Content.Server/Radiation/Systems/RadiationSystem.GridCast.cs index b8193c4d2f..ccee7cf227 100644 --- a/Content.Server/Radiation/Systems/RadiationSystem.GridCast.cs +++ b/Content.Server/Radiation/Systems/RadiationSystem.GridCast.cs @@ -195,11 +195,11 @@ public partial class RadiationSystem Vector2 srcLocal = sourceTrs.ParentUid == grid.Owner ? sourceTrs.LocalPosition - : gridTrs.InvLocalMatrix.Transform(ray.Source); + : Vector2.Transform(ray.Source, gridTrs.InvLocalMatrix); Vector2 dstLocal = destTrs.ParentUid == grid.Owner ? destTrs.LocalPosition - : gridTrs.InvLocalMatrix.Transform(ray.Destination); + : Vector2.Transform(ray.Destination, gridTrs.InvLocalMatrix); Vector2i sourceGrid = new( (int) Math.Floor(srcLocal.X / grid.Comp.TileSize), diff --git a/Content.Server/RandomMetadata/RandomMetadataSystem.cs b/Content.Server/RandomMetadata/RandomMetadataSystem.cs index abab5e5fc3..e287b54c8f 100644 --- a/Content.Server/RandomMetadata/RandomMetadataSystem.cs +++ b/Content.Server/RandomMetadata/RandomMetadataSystem.cs @@ -1,4 +1,5 @@ using Content.Shared.Dataset; +using Content.Shared.Random.Helpers; using JetBrains.Annotations; using Robust.Shared.Prototypes; using Robust.Shared.Random; @@ -47,13 +48,19 @@ public sealed class RandomMetadataSystem : EntitySystem var outputSegments = new List(); foreach (var segment in segments) { - if (_prototype.TryIndex(segment, out var proto)) { + if (_prototype.TryIndex(segment, out var localizedProto)) + { + outputSegments.Add(_random.Pick(localizedProto)); + } + else if (_prototype.TryIndex(segment, out var proto)) + { var random = _random.Pick(proto.Values); if (Loc.TryGetString(random, out var localizedSegment)) outputSegments.Add(localizedSegment); else outputSegments.Add(random); - } else if (Loc.TryGetString(segment, out var localizedSegment)) + } + else if (Loc.TryGetString(segment, out var localizedSegment)) outputSegments.Add(localizedSegment); else outputSegments.Add(segment); diff --git a/Content.Server/Revenant/EntitySystems/CorporealSystem.cs b/Content.Server/Revenant/EntitySystems/CorporealSystem.cs index 1d43cb3ac8..5f31a2f280 100644 --- a/Content.Server/Revenant/EntitySystems/CorporealSystem.cs +++ b/Content.Server/Revenant/EntitySystems/CorporealSystem.cs @@ -1,4 +1,4 @@ -using Content.Server.GameTicking; +using Content.Server.GameTicking; using Content.Shared.Eye; using Content.Shared.Revenant.Components; using Content.Shared.Revenant.EntitySystems; @@ -17,8 +17,8 @@ public sealed class CorporealSystem : SharedCorporealSystem if (TryComp(uid, out var visibility)) { - _visibilitySystem.RemoveLayer(uid, visibility, (int) VisibilityFlags.Ghost, false); - _visibilitySystem.AddLayer(uid, visibility, (int) VisibilityFlags.Normal, false); + _visibilitySystem.RemoveLayer((uid, visibility), (int) VisibilityFlags.Ghost, false); + _visibilitySystem.AddLayer((uid, visibility), (int) VisibilityFlags.Normal, false); _visibilitySystem.RefreshVisibility(uid, visibility); } } @@ -29,8 +29,8 @@ public sealed class CorporealSystem : SharedCorporealSystem if (TryComp(uid, out var visibility) && _ticker.RunLevel != GameRunLevel.PostRound) { - _visibilitySystem.AddLayer(uid, visibility, (int) VisibilityFlags.Ghost, false); - _visibilitySystem.RemoveLayer(uid, visibility, (int) VisibilityFlags.Normal, false); + _visibilitySystem.AddLayer((uid, visibility), (int) VisibilityFlags.Ghost, false); + _visibilitySystem.RemoveLayer((uid, visibility), (int) VisibilityFlags.Normal, false); _visibilitySystem.RefreshVisibility(uid, visibility); } } diff --git a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs index fd7d15ea5d..c889d59f15 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs @@ -28,6 +28,7 @@ using Content.Shared.Revenant.Components; using Robust.Shared.Physics.Components; using Robust.Shared.Utility; using Robust.Shared.Map.Components; +using Content.Shared.Whitelist; namespace Content.Server.Revenant.EntitySystems; @@ -40,6 +41,7 @@ public sealed partial class RevenantSystem [Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!; [Dependency] private readonly GhostSystem _ghost = default!; [Dependency] private readonly TileSystem _tile = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; private void InitializeAbilities() { @@ -246,7 +248,7 @@ public sealed partial class RevenantSystem foreach (var ent in lookup) { //break windows - if (tags.HasComponent(ent) && _tag.HasAnyTag(ent, "Window")) + if (tags.HasComponent(ent) && _tag.HasTag(ent, "Window")) { //hardcoded damage specifiers til i die. var dspec = new DamageSpecifier(); @@ -331,10 +333,8 @@ public sealed partial class RevenantSystem foreach (var ent in _lookup.GetEntitiesInRange(uid, component.MalfunctionRadius)) { - if (component.MalfunctionWhitelist?.IsValid(ent, EntityManager) == false) - continue; - - if (component.MalfunctionBlacklist?.IsValid(ent, EntityManager) == true) + if (_whitelistSystem.IsWhitelistFail(component.MalfunctionWhitelist, ent) || + _whitelistSystem.IsBlacklistPass(component.MalfunctionBlacklist, ent)) continue; _emag.DoEmagEffect(uid, ent); //it does not emag itself. adorable. diff --git a/Content.Server/Revenant/EntitySystems/RevenantSystem.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.cs index c390432f3a..fa4f6db240 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantSystem.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantSystem.cs @@ -17,6 +17,7 @@ using Content.Shared.Popups; using Content.Shared.Revenant; using Content.Shared.Revenant.Components; using Content.Shared.StatusEffect; +using Content.Shared.Store.Components; using Content.Shared.Stunnable; using Content.Shared.Tag; using Robust.Server.GameObjects; @@ -77,8 +78,8 @@ public sealed partial class RevenantSystem : EntitySystem if (_ticker.RunLevel == GameRunLevel.PostRound && TryComp(uid, out var visibility)) { - _visibility.AddLayer(uid, visibility, (int) VisibilityFlags.Ghost, false); - _visibility.RemoveLayer(uid, visibility, (int) VisibilityFlags.Normal, false); + _visibility.AddLayer((uid, visibility), (int) VisibilityFlags.Ghost, false); + _visibility.RemoveLayer((uid, visibility), (int) VisibilityFlags.Normal, false); _visibility.RefreshVisibility(uid, visibility); } @@ -191,13 +192,13 @@ public sealed partial class RevenantSystem : EntitySystem { if (visible) { - _visibility.AddLayer(uid, vis, (int) VisibilityFlags.Normal, false); - _visibility.RemoveLayer(uid, vis, (int) VisibilityFlags.Ghost, false); + _visibility.AddLayer((uid, vis), (int) VisibilityFlags.Normal, false); + _visibility.RemoveLayer((uid, vis), (int) VisibilityFlags.Ghost, false); } else { - _visibility.AddLayer(uid, vis, (int) VisibilityFlags.Ghost, false); - _visibility.RemoveLayer(uid, vis, (int) VisibilityFlags.Normal, false); + _visibility.AddLayer((uid, vis), (int) VisibilityFlags.Ghost, false); + _visibility.RemoveLayer((uid, vis), (int) VisibilityFlags.Normal, false); } _visibility.RefreshVisibility(uid, vis); } diff --git a/Content.Server/Revolutionary/RevolutionarySystem.cs b/Content.Server/Revolutionary/RevolutionarySystem.cs new file mode 100644 index 0000000000..597c4896b9 --- /dev/null +++ b/Content.Server/Revolutionary/RevolutionarySystem.cs @@ -0,0 +1,5 @@ +using Content.Shared.Revolutionary; + +namespace Content.Server.Revolutionary; + +public sealed class RevolutionarySystem : SharedRevolutionarySystem; diff --git a/Content.Server/Salvage/FultonSystem.cs b/Content.Server/Salvage/FultonSystem.cs index ad998e5359..e2c0a6b4c5 100644 --- a/Content.Server/Salvage/FultonSystem.cs +++ b/Content.Server/Salvage/FultonSystem.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Shared.Salvage.Fulton; using Robust.Shared.Containers; using Robust.Shared.Map; @@ -61,8 +62,9 @@ public sealed class FultonSystem : SharedFultonSystem var metadata = MetaData(uid); var oldCoords = xform.Coordinates; var offset = _random.NextVector2(1.5f); - var localPos = TransformSystem.GetInvWorldMatrix(beaconXform.ParentUid) - .Transform(TransformSystem.GetWorldPosition(beaconXform)) + offset; + var localPos = Vector2.Transform( + TransformSystem.GetWorldPosition(beaconXform), + TransformSystem.GetInvWorldMatrix(beaconXform.ParentUid)) + offset; TransformSystem.SetCoordinates(uid, new EntityCoordinates(beaconXform.ParentUid, localPos)); diff --git a/Content.Server/Shuttles/Events/FTLStartedEvent.cs b/Content.Server/Shuttles/Events/FTLStartedEvent.cs index 965da7f0c5..eafbeddd5c 100644 --- a/Content.Server/Shuttles/Events/FTLStartedEvent.cs +++ b/Content.Server/Shuttles/Events/FTLStartedEvent.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Robust.Shared.Map; namespace Content.Server.Shuttles.Events; @@ -6,4 +7,4 @@ namespace Content.Server.Shuttles.Events; /// Raised when a shuttle has moved to FTL space. /// [ByRefEvent] -public readonly record struct FTLStartedEvent(EntityUid Entity, EntityCoordinates TargetCoordinates, EntityUid? FromMapUid, Matrix3 FTLFrom, Angle FromRotation); +public readonly record struct FTLStartedEvent(EntityUid Entity, EntityCoordinates TargetCoordinates, EntityUid? FromMapUid, Matrix3x2 FTLFrom, Angle FromRotation); diff --git a/Content.Server/Shuttles/Systems/ArrivalsSystem.cs b/Content.Server/Shuttles/Systems/ArrivalsSystem.cs index e87e781e62..eb1f9796b2 100644 --- a/Content.Server/Shuttles/Systems/ArrivalsSystem.cs +++ b/Content.Server/Shuttles/Systems/ArrivalsSystem.cs @@ -1,4 +1,5 @@ using System.Linq; +using System.Numerics; using Content.Server.Administration; using Content.Server.DeviceNetwork.Components; using Content.Server.DeviceNetwork.Systems; @@ -277,7 +278,7 @@ public sealed class ArrivalsSystem : EntitySystem foreach (var (ent, xform) in toDump) { var rotation = xform.LocalRotation; - _transform.SetCoordinates(ent, new EntityCoordinates(args.FromMapUid!.Value, args.FTLFrom.Transform(xform.LocalPosition))); + _transform.SetCoordinates(ent, new EntityCoordinates(args.FromMapUid!.Value, Vector2.Transform(xform.LocalPosition, args.FTLFrom))); _transform.SetWorldRotation(ent, args.FromRotation + rotation); } } diff --git a/Content.Server/Shuttles/Systems/DockingSystem.Shuttle.cs b/Content.Server/Shuttles/Systems/DockingSystem.Shuttle.cs index e46a7c715f..1a95ef9cb2 100644 --- a/Content.Server/Shuttles/Systems/DockingSystem.Shuttle.cs +++ b/Content.Server/Shuttles/Systems/DockingSystem.Shuttle.cs @@ -46,13 +46,13 @@ public sealed partial class DockingSystem FixturesComponent shuttleFixtures, MapGridComponent grid, bool isMap, - out Matrix3 matty, + out Matrix3x2 matty, out Box2 shuttleDockedAABB, out Angle gridRotation) { shuttleDockedAABB = Box2.UnitCentered; gridRotation = Angle.Zero; - matty = Matrix3.Identity; + matty = Matrix3x2.Identity; if (shuttleDock.Docked || gridDock.Docked || @@ -71,9 +71,9 @@ public sealed partial class DockingSystem var gridDockAngle = gridDockXform.LocalRotation.Opposite(); var offsetAngle = gridDockAngle - shuttleDockAngle; - var stationDockMatrix = Matrix3.CreateInverseTransform(stationDockPos, shuttleDockAngle); - var gridXformMatrix = Matrix3.CreateTransform(gridDockXform.LocalPosition, gridDockAngle); - Matrix3.Multiply(in stationDockMatrix, in gridXformMatrix, out matty); + var stationDockMatrix = Matrix3Helpers.CreateInverseTransform(stationDockPos, shuttleDockAngle); + var gridXformMatrix = Matrix3Helpers.CreateTransform(gridDockXform.LocalPosition, gridDockAngle); + matty = Matrix3x2.Multiply(stationDockMatrix, gridXformMatrix); if (!ValidSpawn(grid, matty, offsetAngle, shuttleFixtures, isMap)) return false; @@ -193,7 +193,7 @@ public sealed partial class DockingSystem } // Can't just use the AABB as we want to get bounds as tight as possible. - var gridPosition = new EntityCoordinates(targetGrid, matty.Transform(Vector2.Zero)); + var gridPosition = new EntityCoordinates(targetGrid, Vector2.Transform(Vector2.Zero, matty)); var spawnPosition = new EntityCoordinates(targetGridXform.MapUid!.Value, gridPosition.ToMapPos(EntityManager, _transform)); // TODO: use tight bounds @@ -303,9 +303,9 @@ public sealed partial class DockingSystem /// /// Checks whether the shuttle can warp to the specified position. /// - private bool ValidSpawn(MapGridComponent grid, Matrix3 matty, Angle angle, FixturesComponent shuttleFixturesComp, bool isMap) + private bool ValidSpawn(MapGridComponent grid, Matrix3x2 matty, Angle angle, FixturesComponent shuttleFixturesComp, bool isMap) { - var transform = new Transform(matty.Transform(Vector2.Zero), angle); + var transform = new Transform(Vector2.Transform(Vector2.Zero, matty), angle); // Because some docking bounds are tight af need to check each chunk individually foreach (var fix in shuttleFixturesComp.Fixtures.Values) diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.Impact.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.Impact.cs index f346398cda..8a8d2d883d 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.Impact.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.Impact.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Server.Shuttles.Components; using Content.Shared.Audio; using Robust.Shared.Audio; @@ -38,8 +39,8 @@ public sealed partial class ShuttleSystem var otherXform = Transform(args.OtherEntity); - var ourPoint = ourXform.InvWorldMatrix.Transform(args.WorldPoint); - var otherPoint = otherXform.InvWorldMatrix.Transform(args.WorldPoint); + var ourPoint = Vector2.Transform(args.WorldPoint, ourXform.InvWorldMatrix); + var otherPoint = Vector2.Transform(args.WorldPoint, otherXform.InvWorldMatrix); var ourVelocity = _physics.GetLinearVelocity(uid, ourPoint, ourBody, ourXform); var otherVelocity = _physics.GetLinearVelocity(args.OtherEntity, otherPoint, otherBody, otherXform); diff --git a/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs b/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs index cc57c34c47..5c600be3f6 100644 --- a/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs +++ b/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs @@ -267,7 +267,7 @@ public sealed partial class BorgSystem return false; } - if (component.ModuleWhitelist?.IsValid(module, EntityManager) == false) + if (_whitelistSystem.IsWhitelistFail(component.ModuleWhitelist, module)) { if (user != null) Popup.PopupEntity(Loc.GetString("borg-module-whitelist-deny"), uid, user.Value); diff --git a/Content.Server/Silicons/Borgs/BorgSystem.cs b/Content.Server/Silicons/Borgs/BorgSystem.cs index 082e38921a..1ab7f5387f 100644 --- a/Content.Server/Silicons/Borgs/BorgSystem.cs +++ b/Content.Server/Silicons/Borgs/BorgSystem.cs @@ -22,6 +22,7 @@ using Content.Shared.Roles; using Content.Shared.Silicons.Borgs; using Content.Shared.Silicons.Borgs.Components; using Content.Shared.Throwing; +using Content.Shared.Whitelist; using Content.Shared.Wires; using Robust.Server.GameObjects; using Robust.Shared.Containers; @@ -53,6 +54,8 @@ public sealed partial class BorgSystem : SharedBorgSystem [Dependency] private readonly ThrowingSystem _throwing = default!; [Dependency] private readonly UserInterfaceSystem _ui = default!; [Dependency] private readonly SharedContainerSystem _container = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; + [ValidatePrototypeId] public const string BorgJobId = "Borg"; @@ -104,9 +107,8 @@ public sealed partial class BorgSystem : SharedBorgSystem return; } - if (component.BrainEntity == null && - brain != null && - component.BrainWhitelist?.IsValid(used) != false) + if (component.BrainEntity == null && brain != null && + _whitelistSystem.IsWhitelistPassOrNull(component.BrainWhitelist, used)) { if (_mind.TryGetMind(used, out _, out var mind) && mind.Session != null) { diff --git a/Content.Server/Singularity/EntitySystems/GravityWellSystem.cs b/Content.Server/Singularity/EntitySystems/GravityWellSystem.cs index f1d0af6f90..779b2f5971 100644 --- a/Content.Server/Singularity/EntitySystems/GravityWellSystem.cs +++ b/Content.Server/Singularity/EntitySystems/GravityWellSystem.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Server.Atmos.Components; using Content.Server.Singularity.Components; using Content.Shared.Ghost; @@ -126,7 +127,7 @@ public sealed class GravityWellSystem : SharedGravityWellSystem /// The minimum distance at which entities can be affected by the gravity pulse. /// The base velocity added to any entities within affected by the gravity pulse scaled by the displacement of those entities from the epicenter. /// (optional) The transform of the entity at the epicenter of the gravitational pulse. - public void GravPulse(EntityUid uid, float maxRange, float minRange, in Matrix3 baseMatrixDeltaV, TransformComponent? xform = null) + public void GravPulse(EntityUid uid, float maxRange, float minRange, in Matrix3x2 baseMatrixDeltaV, TransformComponent? xform = null) { if (Resolve(uid, ref xform)) GravPulse(xform.Coordinates, maxRange, minRange, in baseMatrixDeltaV); @@ -154,7 +155,7 @@ public sealed class GravityWellSystem : SharedGravityWellSystem /// The maximum distance at which entities can be affected by the gravity pulse. /// The minimum distance at which entities can be affected by the gravity pulse. /// The base velocity added to any entities within affected by the gravity pulse scaled by the displacement of those entities from the epicenter. - public void GravPulse(EntityCoordinates entityPos, float maxRange, float minRange, in Matrix3 baseMatrixDeltaV) + public void GravPulse(EntityCoordinates entityPos, float maxRange, float minRange, in Matrix3x2 baseMatrixDeltaV) => GravPulse(entityPos.ToMap(EntityManager, _transform), maxRange, minRange, in baseMatrixDeltaV); /// @@ -175,7 +176,7 @@ public sealed class GravityWellSystem : SharedGravityWellSystem /// The maximum distance at which entities can be affected by the gravity pulse. /// The minimum distance at which entities can be affected by the gravity pulse. Exists to prevent div/0 errors. /// The base velocity added to any entities within affected by the gravity pulse scaled by the displacement of those entities from the epicenter. - public void GravPulse(MapCoordinates mapPos, float maxRange, float minRange, in Matrix3 baseMatrixDeltaV) + public void GravPulse(MapCoordinates mapPos, float maxRange, float minRange, in Matrix3x2 baseMatrixDeltaV) { if (mapPos == MapCoordinates.Nullspace) return; // No gravpulses in nullspace please. @@ -205,7 +206,7 @@ public sealed class GravityWellSystem : SharedGravityWellSystem continue; var scaling = (1f / distance2) * physics.Mass; // TODO: Variable falloff gradiants. - _physics.ApplyLinearImpulse(entity, (displacement * baseMatrixDeltaV) * scaling, body: physics); + _physics.ApplyLinearImpulse(entity, Vector2.Transform(displacement, baseMatrixDeltaV) * scaling, body: physics); } } @@ -218,10 +219,9 @@ public sealed class GravityWellSystem : SharedGravityWellSystem /// The base amount of velocity that will be added to entities in range towards the epicenter of the pulse. /// The base amount of velocity that will be added to entities in range counterclockwise relative to the epicenter of the pulse. public void GravPulse(MapCoordinates mapPos, float maxRange, float minRange = 0.0f, float baseRadialDeltaV = 0.0f, float baseTangentialDeltaV = 0.0f) - => GravPulse(mapPos, maxRange, minRange, new Matrix3( - baseRadialDeltaV, +baseTangentialDeltaV, 0.0f, - -baseTangentialDeltaV, baseRadialDeltaV, 0.0f, - 0.0f, 0.0f, 1.0f + => GravPulse(mapPos, maxRange, minRange, new Matrix3x2( + baseRadialDeltaV, -baseTangentialDeltaV, 0.0f, + +baseTangentialDeltaV, baseRadialDeltaV, 0.0f )); #endregion GravPulse diff --git a/Content.Server/Spawners/Components/ConditionalSpawnerComponent.cs b/Content.Server/Spawners/Components/ConditionalSpawnerComponent.cs index 5b98989bb3..1b06367b0d 100644 --- a/Content.Server/Spawners/Components/ConditionalSpawnerComponent.cs +++ b/Content.Server/Spawners/Components/ConditionalSpawnerComponent.cs @@ -2,7 +2,7 @@ using Robust.Shared.Prototypes; namespace Content.Server.Spawners.Components { - [RegisterComponent] + [RegisterComponent, EntityCategory("Spawner")] [Virtual] public partial class ConditionalSpawnerComponent : Component { diff --git a/Content.Server/Spawners/Components/RandomSpawnerComponent.cs b/Content.Server/Spawners/Components/RandomSpawnerComponent.cs index 9bf4d6d253..e6a597f365 100644 --- a/Content.Server/Spawners/Components/RandomSpawnerComponent.cs +++ b/Content.Server/Spawners/Components/RandomSpawnerComponent.cs @@ -2,7 +2,7 @@ using Robust.Shared.Prototypes; namespace Content.Server.Spawners.Components { - [RegisterComponent] + [RegisterComponent, EntityCategory("Spawner")] public sealed partial class RandomSpawnerComponent : ConditionalSpawnerComponent { [ViewVariables(VVAccess.ReadWrite)] diff --git a/Content.Server/Spawners/Components/SpawnPointComponent.cs b/Content.Server/Spawners/Components/SpawnPointComponent.cs index 5cf231f224..c6d14dfeb3 100644 --- a/Content.Server/Spawners/Components/SpawnPointComponent.cs +++ b/Content.Server/Spawners/Components/SpawnPointComponent.cs @@ -6,11 +6,8 @@ namespace Content.Server.Spawners.Components; [RegisterComponent] public sealed partial class SpawnPointComponent : Component, ISpawnPoint { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - - [ViewVariables(VVAccess.ReadWrite)] [DataField("job_id")] - private string? _jobId; + public ProtoId? Job; /// /// The type of spawn point @@ -18,11 +15,9 @@ public sealed partial class SpawnPointComponent : Component, ISpawnPoint [DataField("spawn_type"), ViewVariables(VVAccess.ReadWrite)] public SpawnPointType SpawnType { get; set; } = SpawnPointType.Unset; - public JobPrototype? Job => string.IsNullOrEmpty(_jobId) ? null : _prototypeManager.Index(_jobId); - public override string ToString() { - return $"{_jobId} {SpawnType}"; + return $"{Job} {SpawnType}"; } } diff --git a/Content.Server/Spawners/Components/TimedSpawnerComponent.cs b/Content.Server/Spawners/Components/TimedSpawnerComponent.cs index b60afbc88b..828e541717 100644 --- a/Content.Server/Spawners/Components/TimedSpawnerComponent.cs +++ b/Content.Server/Spawners/Components/TimedSpawnerComponent.cs @@ -9,7 +9,7 @@ namespace Content.Server.Spawners.Components; /// Can configure the set of entities, spawn timing, spawn chance, /// and min/max number of entities to spawn. /// -[RegisterComponent] +[RegisterComponent, EntityCategory("Spawner")] public sealed partial class TimedSpawnerComponent : Component, ISerializationHooks { /// diff --git a/Content.Server/Spawners/EntitySystems/SpawnPointSystem.cs b/Content.Server/Spawners/EntitySystems/SpawnPointSystem.cs index 608818af16..ca41f5ce4e 100644 --- a/Content.Server/Spawners/EntitySystems/SpawnPointSystem.cs +++ b/Content.Server/Spawners/EntitySystems/SpawnPointSystem.cs @@ -36,7 +36,7 @@ public sealed class SpawnPointSystem : EntitySystem if (args.DesiredSpawnPointType != SpawnPointType.Unset) { var isMatchingJob = spawnPoint.SpawnType == SpawnPointType.Job && - (args.Job == null || spawnPoint.Job?.ID == args.Job.Prototype); + (args.Job == null || spawnPoint.Job?.Id == args.Job.Prototype); switch (args.DesiredSpawnPointType) { @@ -57,7 +57,7 @@ public sealed class SpawnPointSystem : EntitySystem if (_gameTicker.RunLevel != GameRunLevel.InRound && spawnPoint.SpawnType == SpawnPointType.Job && - (args.Job == null || spawnPoint.Job?.ID == args.Job.Prototype)) + (args.Job == null || spawnPoint.Job == args.Job.Prototype)) { possiblePositions.Add(xform.Coordinates); } diff --git a/Content.Server/Speech/EntitySystems/SouthernAccentSystem.cs b/Content.Server/Speech/EntitySystems/SouthernAccentSystem.cs index b9260eb844..c1f8a0be30 100644 --- a/Content.Server/Speech/EntitySystems/SouthernAccentSystem.cs +++ b/Content.Server/Speech/EntitySystems/SouthernAccentSystem.cs @@ -5,9 +5,12 @@ namespace Content.Server.Speech.EntitySystems; public sealed class SouthernAccentSystem : EntitySystem { - private static readonly Regex RegexIng = new(@"ing\b"); - private static readonly Regex RegexAnd = new(@"\band\b"); - private static readonly Regex RegexDve = new("d've"); + private static readonly Regex RegexLowerIng = new(@"ing\b"); + private static readonly Regex RegexUpperIng = new(@"ING\b"); + private static readonly Regex RegexLowerAnd = new(@"\band\b"); + private static readonly Regex RegexUpperAnd = new(@"\bAND\b"); + private static readonly Regex RegexLowerDve = new(@"d've\b"); + private static readonly Regex RegexUpperDve = new(@"D'VE\b"); [Dependency] private readonly ReplacementAccentSystem _replacement = default!; @@ -24,9 +27,12 @@ public sealed class SouthernAccentSystem : EntitySystem message = _replacement.ApplyReplacements(message, "southern"); //They shoulda started runnin' an' hidin' from me! - message = RegexIng.Replace(message, "in'"); - message = RegexAnd.Replace(message, "an'"); - message = RegexDve.Replace(message, "da"); + message = RegexLowerIng.Replace(message, "in'"); + message = RegexUpperIng.Replace(message, "IN'"); + message = RegexLowerAnd.Replace(message, "an'"); + message = RegexUpperAnd.Replace(message, "AN'"); + message = RegexLowerDve.Replace(message, "da"); + message = RegexUpperDve.Replace(message, "DA"); args.Message = message; } }; diff --git a/Content.Server/Spreader/SpreaderSystem.cs b/Content.Server/Spreader/SpreaderSystem.cs index fe14d86aa1..7de8a43d35 100644 --- a/Content.Server/Spreader/SpreaderSystem.cs +++ b/Content.Server/Spreader/SpreaderSystem.cs @@ -21,6 +21,7 @@ public sealed class SpreaderSystem : EntitySystem [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!; [Dependency] private readonly SharedMapSystem _map = default!; + [Dependency] private readonly TagSystem _tag = default!; /// /// Cached maximum number of updates per spreader prototype. This is applied per-grid. @@ -37,8 +38,7 @@ public sealed class SpreaderSystem : EntitySystem public const float SpreadCooldownSeconds = 1; - [ValidatePrototypeId] - private const string IgnoredTag = "SpreaderIgnore"; + private static readonly ProtoId IgnoredTag = "SpreaderIgnore"; /// public override void Initialize() @@ -189,7 +189,6 @@ public sealed class SpreaderSystem : EntitySystem var airtightQuery = GetEntityQuery(); var dockQuery = GetEntityQuery(); var xformQuery = GetEntityQuery(); - var tagQuery = GetEntityQuery(); var blockedAtmosDirs = AtmosDirection.Invalid; // Due to docking ports they may not necessarily be opposite directions. @@ -212,7 +211,7 @@ public sealed class SpreaderSystem : EntitySystem // If we're on a blocked tile work out which directions we can go. if (!airtightQuery.TryGetComponent(ent, out var airtight) || !airtight.AirBlocked || - tagQuery.TryGetComponent(ent, out var tags) && tags.Tags.Contains(IgnoredTag)) + _tag.HasTag(ent.Value, IgnoredTag)) { continue; } @@ -250,8 +249,7 @@ public sealed class SpreaderSystem : EntitySystem while (directionEnumerator.MoveNext(out var ent)) { - if (!airtightQuery.TryGetComponent(ent, out var airtight) || !airtight.AirBlocked || - tagQuery.TryGetComponent(ent, out var tags) && tags.Tags.Contains(IgnoredTag)) + if (!airtightQuery.TryGetComponent(ent, out var airtight) || !airtight.AirBlocked || _tag.HasTag(ent.Value, IgnoredTag)) { continue; } diff --git a/Content.Server/Stack/StackSystem.cs b/Content.Server/Stack/StackSystem.cs index 001093a8dd..e34592b45f 100644 --- a/Content.Server/Stack/StackSystem.cs +++ b/Content.Server/Stack/StackSystem.cs @@ -51,7 +51,7 @@ namespace Content.Server.Stack // Get a prototype ID to spawn the new entity. Null is also valid, although it should rarely be picked... var prototype = _prototypeManager.TryIndex(stack.StackTypeId, out var stackType) - ? stackType.Spawn + ? stackType.Spawn.ToString() : Prototype(uid)?.ID; // Set the output parameter in the event instance to the newly split stack. diff --git a/Content.Server/Station/Components/StationJobsComponent.cs b/Content.Server/Station/Components/StationJobsComponent.cs index 74399bf412..3681ec9674 100644 --- a/Content.Server/Station/Components/StationJobsComponent.cs +++ b/Content.Server/Station/Components/StationJobsComponent.cs @@ -1,4 +1,5 @@ -using Content.Server.Station.Systems; +using System.Linq; +using Content.Server.Station.Systems; using Content.Shared.Roles; using JetBrains.Annotations; using Robust.Shared.Network; @@ -14,25 +15,21 @@ namespace Content.Server.Station.Components; [RegisterComponent, Access(typeof(StationJobsSystem)), PublicAPI] public sealed partial class StationJobsComponent : Component { - /// - /// Total *round-start* jobs at station start. - /// - [DataField("roundStartTotalJobs")] public int RoundStartTotalJobs; - /// /// Total *mid-round* jobs at station start. + /// This is inferred automatically from . /// - [DataField("midRoundTotalJobs")] public int MidRoundTotalJobs; + [ViewVariables] public int MidRoundTotalJobs; /// /// Current total jobs. /// - [DataField("totalJobs")] public int TotalJobs; + [DataField] public int TotalJobs; /// /// Station is running on extended access. /// - [DataField("extendedAccess")] public bool ExtendedAccess; + [DataField] public bool ExtendedAccess; /// /// If there are less than or equal this amount of players in the game at round start, @@ -41,7 +38,7 @@ public sealed partial class StationJobsComponent : Component /// /// Set to -1 to disable extended access. /// - [DataField("extendedAccessThreshold")] + [DataField] public int ExtendedAccessThreshold { get; set; } = 15; /// @@ -54,28 +51,20 @@ public sealed partial class StationJobsComponent : Component public float? PercentJobsRemaining => MidRoundTotalJobs > 0 ? TotalJobs / (float) MidRoundTotalJobs : null; /// - /// The current list of jobs. + /// The current list of jobs of available jobs. Null implies that is no limit. /// /// /// This should not be mutated or used directly unless you really know what you're doing, go through StationJobsSystem. /// - [DataField("jobList", customTypeSerializer: typeof(PrototypeIdDictionarySerializer))] - public Dictionary JobList = new(); - - /// - /// The round-start list of jobs. - /// - /// - /// This should not be mutated, ever. - /// - [DataField("roundStartJobList", customTypeSerializer: typeof(PrototypeIdDictionarySerializer))] - public Dictionary RoundStartJobList = new(); + [DataField] + public Dictionary, int?> JobList = new(); /// /// Overflow jobs that round-start can spawn infinitely many of. + /// This is inferred automatically from . /// - [DataField("overflowJobs", customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] - public HashSet OverflowJobs = new(); + [ViewVariables] + public IReadOnlySet> OverflowJobs = default!; /// /// A dictionary relating a NetUserId to the jobs they have on station. @@ -84,7 +73,10 @@ public sealed partial class StationJobsComponent : Component [DataField] public Dictionary>> PlayerJobs = new(); - [DataField("availableJobs", required: true, - customTypeSerializer: typeof(PrototypeIdDictionarySerializer, JobPrototype>))] - public Dictionary> SetupAvailableJobs = default!; + /// + /// Mapping of jobs to an int[2] array that specifies jobs available at round start, and midround. + /// Negative values implies that there is no limit. + /// + [DataField("availableJobs", required: true)] + public Dictionary, int[]> SetupAvailableJobs = default!; } diff --git a/Content.Server/Station/Systems/StationJobsSystem.Roundstart.cs b/Content.Server/Station/Systems/StationJobsSystem.Roundstart.cs index c3c3865c7b..e145e233e9 100644 --- a/Content.Server/Station/Systems/StationJobsSystem.Roundstart.cs +++ b/Content.Server/Station/Systems/StationJobsSystem.Roundstart.cs @@ -52,23 +52,23 @@ public sealed partial class StationJobsSystem /// as there may end up being more round-start slots than available slots, which can cause weird behavior. /// A warning to all who enter ye cursed lands: This function is long and mildly incomprehensible. Best used without touching. /// - public Dictionary AssignJobs(Dictionary profiles, IReadOnlyList stations, bool useRoundStartJobs = true) + public Dictionary?, EntityUid)> AssignJobs(Dictionary profiles, IReadOnlyList stations, bool useRoundStartJobs = true) { DebugTools.Assert(stations.Count > 0); InitializeRoundStart(); if (profiles.Count == 0) - return new Dictionary(); + return new(); // We need to modify this collection later, so make a copy of it. profiles = profiles.ShallowClone(); // Player <-> (job, station) - var assigned = new Dictionary(profiles.Count); + var assigned = new Dictionary?, EntityUid)>(profiles.Count); // The jobs left on the stations. This collection is modified as jobs are assigned to track what's available. - var stationJobs = new Dictionary>(); + var stationJobs = new Dictionary, int?>>(); foreach (var station in stations) { if (useRoundStartJobs) @@ -83,15 +83,15 @@ public sealed partial class StationJobsSystem // We reuse this collection. It tracks what jobs we're currently trying to select players for. - var currentlySelectingJobs = new Dictionary>(stations.Count); + var currentlySelectingJobs = new Dictionary, int?>>(stations.Count); foreach (var station in stations) { - currentlySelectingJobs.Add(station, new Dictionary()); + currentlySelectingJobs.Add(station, new Dictionary, int?>()); } // And these. // Tracks what players are available for a given job in the current iteration of selection. - var jobPlayerOptions = new Dictionary>(); + var jobPlayerOptions = new Dictionary, HashSet>(); // Tracks the total number of slots for the given stations in the current iteration of selection. var stationTotalSlots = new Dictionary(stations.Count); // The share of the players each station gets in the current iteration of job selection. @@ -112,7 +112,7 @@ public sealed partial class StationJobsSystem var optionsRemaining = 0; // Assigns a player to the given station, updating all the bookkeeping while at it. - void AssignPlayer(NetUserId player, string job, EntityUid station) + void AssignPlayer(NetUserId player, ProtoId job, EntityUid station) { // Remove the player from all possible jobs as that's faster than actually checking what they have selected. foreach (var (k, players) in jobPlayerOptions) @@ -273,8 +273,11 @@ public sealed partial class StationJobsSystem /// All players that might need an overflow assigned. /// Player character profiles. /// The stations to consider for spawn location. - public void AssignOverflowJobs(ref Dictionary assignedJobs, - IEnumerable allPlayersToAssign, IReadOnlyDictionary profiles, IReadOnlyList stations) + public void AssignOverflowJobs( + ref Dictionary?, EntityUid)> assignedJobs, + IEnumerable allPlayersToAssign, + IReadOnlyDictionary profiles, + IReadOnlyList stations) { var givenStations = stations.ToList(); if (givenStations.Count == 0) diff --git a/Content.Server/Station/Systems/StationJobsSystem.cs b/Content.Server/Station/Systems/StationJobsSystem.cs index 3bfa815af1..307610d136 100644 --- a/Content.Server/Station/Systems/StationJobsSystem.cs +++ b/Content.Server/Station/Systems/StationJobsSystem.cs @@ -3,6 +3,7 @@ using System.Linq; using Content.Server.GameTicking; using Content.Server.Station.Components; using Content.Shared.CCVar; +using Content.Shared.FixedPoint; using Content.Shared.GameTicking; using Content.Shared.Preferences; using Content.Shared.Roles; @@ -31,12 +32,25 @@ public sealed partial class StationJobsSystem : EntitySystem public override void Initialize() { SubscribeLocalEvent(OnStationInitialized); + SubscribeLocalEvent(OnInit); SubscribeLocalEvent(OnStationRenamed); SubscribeLocalEvent(OnStationDeletion); SubscribeLocalEvent(OnPlayerJoinedLobby); Subs.CVar(_configurationManager, CCVars.GameDisallowLateJoins, _ => UpdateJobsAvailable(), true); } + private void OnInit(Entity ent, ref ComponentInit args) + { + ent.Comp.MidRoundTotalJobs = ent.Comp.SetupAvailableJobs.Values + .Select(x => Math.Max(x[1], 0)) + .Sum(); + + ent.Comp.OverflowJobs = ent.Comp.SetupAvailableJobs + .Where(x => x.Value[0] < 0) + .Select(x => x.Key) + .ToHashSet(); + } + public override void Update(float _) { if (_availableJobsDirty) @@ -57,28 +71,11 @@ public sealed partial class StationJobsSystem : EntitySystem if (!TryComp(msg.Station, out var stationJobs)) return; - var mapJobList = stationJobs.SetupAvailableJobs; + stationJobs.JobList = stationJobs.SetupAvailableJobs.ToDictionary( + x => x.Key, + x=> (int?)(x.Value[1] < 0 ? null : x.Value[1])); - stationJobs.RoundStartTotalJobs = mapJobList.Values.Where(x => x[0] is not null && x[0] > 0).Sum(x => x[0]!.Value); - stationJobs.MidRoundTotalJobs = mapJobList.Values.Where(x => x[1] is not null && x[1] > 0).Sum(x => x[1]!.Value); - - stationJobs.TotalJobs = stationJobs.MidRoundTotalJobs; - - stationJobs.JobList = mapJobList.ToDictionary(x => x.Key, x => - { - if (x.Value[1] <= -1) - return null; - return (uint?) x.Value[1]; - }); - - stationJobs.RoundStartJobList = mapJobList.ToDictionary(x => x.Key, x => - { - if (x.Value[0] <= -1) - return null; - return (uint?) x.Value[0]; - }); - - stationJobs.OverflowJobs = stationJobs.OverflowJobs.ToHashSet(); + stationJobs.TotalJobs = stationJobs.JobList.Values.Select(x => x ?? 0).Sum(); UpdateJobsAvailable(); } @@ -141,7 +138,11 @@ public sealed partial class StationJobsSystem : EntitySystem /// Resolve pattern, station jobs component of the station. /// Whether or not slot adjustment was a success. /// Thrown when the given station is not a station. - public bool TryAdjustJobSlot(EntityUid station, string jobPrototypeId, int amount, bool createSlot = false, bool clamp = false, + public bool TryAdjustJobSlot(EntityUid station, + string jobPrototypeId, + int amount, + bool createSlot = false, + bool clamp = false, StationJobsComponent? stationJobs = null) { if (!Resolve(station, ref stationJobs)) @@ -156,7 +157,11 @@ public sealed partial class StationJobsSystem : EntitySystem // - Return false when you remove from a job that doesn't exist. // - Return false when you remove and exceed the number of slots available. // And additionally, if adding would add a job not previously on the manifest when createSlot is false, return false and do nothing. - switch (jobList.ContainsKey(jobPrototypeId)) + + if (amount == 0) + return true; + + switch (jobList.TryGetValue(jobPrototypeId, out var available)) { case false when amount < 0: return false; @@ -164,31 +169,20 @@ public sealed partial class StationJobsSystem : EntitySystem if (!createSlot) return false; stationJobs.TotalJobs += amount; - jobList[jobPrototypeId] = (uint?)amount; + jobList[jobPrototypeId] = amount; UpdateJobsAvailable(); return true; case true: // Job is unlimited so just say we adjusted it and do nothing. - if (jobList[jobPrototypeId] == null) + if (available is not {} avail) return true; // Would remove more jobs than we have available. - if (amount < 0 && (jobList[jobPrototypeId] + amount < 0 && !clamp)) + if (available + amount < 0 && !clamp) return false; - stationJobs.TotalJobs += amount; - - //C# type handling moment - if (amount > 0) - jobList[jobPrototypeId] += (uint)amount; - else - { - if ((int)jobList[jobPrototypeId]!.Value - Math.Abs(amount) <= 0) - jobList[jobPrototypeId] = 0; - else - jobList[jobPrototypeId] -= (uint) Math.Abs(amount); - } - + jobList[jobPrototypeId] = Math.Max(avail + amount, 0); + stationJobs.TotalJobs = jobList.Values.Select(x => x ?? 0).Sum(); UpdateJobsAvailable(); return true; } @@ -239,7 +233,10 @@ public sealed partial class StationJobsSystem : EntitySystem /// Resolve pattern, station jobs component of the station. /// Whether or not setting the value succeeded. /// Thrown when the given station is not a station. - public bool TrySetJobSlot(EntityUid station, string jobPrototypeId, int amount, bool createSlot = false, + public bool TrySetJobSlot(EntityUid station, + string jobPrototypeId, + int amount, + bool createSlot = false, StationJobsComponent? stationJobs = null) { if (!Resolve(station, ref stationJobs)) @@ -255,13 +252,13 @@ public sealed partial class StationJobsSystem : EntitySystem if (!createSlot) return false; stationJobs.TotalJobs += amount; - jobList[jobPrototypeId] = (uint?)amount; + jobList[jobPrototypeId] = amount; UpdateJobsAvailable(); return true; case true: - stationJobs.TotalJobs += amount - (int) (jobList[jobPrototypeId] ?? 0); + stationJobs.TotalJobs += amount - (jobList[jobPrototypeId] ?? 0); - jobList[jobPrototypeId] = (uint)amount; + jobList[jobPrototypeId] = amount; UpdateJobsAvailable(); return true; } @@ -289,8 +286,8 @@ public sealed partial class StationJobsSystem : EntitySystem throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station)); // Subtract out the job we're fixing to make have unlimited slots. - if (stationJobs.JobList.ContainsKey(jobPrototypeId) && stationJobs.JobList[jobPrototypeId] != null) - stationJobs.TotalJobs -= (int)stationJobs.JobList[jobPrototypeId]!.Value; + if (stationJobs.JobList.TryGetValue(jobPrototypeId, out var existing)) + stationJobs.TotalJobs -= existing ?? 0; stationJobs.JobList[jobPrototypeId] = null; @@ -319,8 +316,7 @@ public sealed partial class StationJobsSystem : EntitySystem if (!Resolve(station, ref stationJobs)) throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station)); - var res = stationJobs.JobList.TryGetValue(jobPrototypeId, out var job) && job == null; - return res; + return stationJobs.JobList.TryGetValue(jobPrototypeId, out var job) && job == null; } /// @@ -328,7 +324,7 @@ public sealed partial class StationJobsSystem : EntitySystem /// Job to get slot info for. /// The number of slots remaining. Null if infinite. /// Resolve pattern, station jobs component of the station. - public bool TryGetJobSlot(EntityUid station, JobPrototype job, out uint? slots, StationJobsComponent? stationJobs = null) + public bool TryGetJobSlot(EntityUid station, JobPrototype job, out int? slots, StationJobsComponent? stationJobs = null) { return TryGetJobSlot(station, job.ID, out slots, stationJobs); } @@ -343,21 +339,12 @@ public sealed partial class StationJobsSystem : EntitySystem /// Whether or not the slot exists. /// Thrown when the given station is not a station. /// slots will be null if the slot doesn't exist, as well, so make sure to check the return value. - public bool TryGetJobSlot(EntityUid station, string jobPrototypeId, out uint? slots, StationJobsComponent? stationJobs = null) + public bool TryGetJobSlot(EntityUid station, string jobPrototypeId, out int? slots, StationJobsComponent? stationJobs = null) { if (!Resolve(station, ref stationJobs)) throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station)); - if (stationJobs.JobList.TryGetValue(jobPrototypeId, out var job)) - { - slots = job; - return true; - } - else // Else if slot isn't present return null. - { - slots = null; - return false; - } + return stationJobs.JobList.TryGetValue(jobPrototypeId, out slots); } /// @@ -367,12 +354,14 @@ public sealed partial class StationJobsSystem : EntitySystem /// Resolve pattern, station jobs component of the station. /// Set containing all jobs available. /// Thrown when the given station is not a station. - public IReadOnlySet GetAvailableJobs(EntityUid station, StationJobsComponent? stationJobs = null) + public IEnumerable> GetAvailableJobs(EntityUid station, StationJobsComponent? stationJobs = null) { if (!Resolve(station, ref stationJobs)) throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station)); - return stationJobs.JobList.Where(x => x.Value != 0).Select(x => x.Key).ToHashSet(); + return stationJobs.JobList + .Where(x => x.Value != 0) + .Select(x => x.Key); } /// @@ -382,12 +371,12 @@ public sealed partial class StationJobsSystem : EntitySystem /// Resolve pattern, station jobs component of the station. /// Set containing all overflow jobs available. /// Thrown when the given station is not a station. - public IReadOnlySet GetOverflowJobs(EntityUid station, StationJobsComponent? stationJobs = null) + public IReadOnlySet> GetOverflowJobs(EntityUid station, StationJobsComponent? stationJobs = null) { if (!Resolve(station, ref stationJobs)) throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station)); - return stationJobs.OverflowJobs.ToHashSet(); + return stationJobs.OverflowJobs; } /// @@ -397,7 +386,7 @@ public sealed partial class StationJobsSystem : EntitySystem /// Resolve pattern, station jobs component of the station. /// List of all jobs on the station. /// Thrown when the given station is not a station. - public IReadOnlyDictionary GetJobs(EntityUid station, StationJobsComponent? stationJobs = null) + public IReadOnlyDictionary, int?> GetJobs(EntityUid station, StationJobsComponent? stationJobs = null) { if (!Resolve(station, ref stationJobs)) throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station)); @@ -412,12 +401,14 @@ public sealed partial class StationJobsSystem : EntitySystem /// Resolve pattern, station jobs component of the station. /// List of all round-start jobs. /// Thrown when the given station is not a station. - public IReadOnlyDictionary GetRoundStartJobs(EntityUid station, StationJobsComponent? stationJobs = null) + public Dictionary, int?> GetRoundStartJobs(EntityUid station, StationJobsComponent? stationJobs = null) { if (!Resolve(station, ref stationJobs)) throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station)); - return stationJobs.RoundStartJobList; + return stationJobs.SetupAvailableJobs.ToDictionary( + x => x.Key, + x=> (int?)(x.Value[0] < 0 ? null : x.Value[0])); } /// @@ -428,13 +419,13 @@ public sealed partial class StationJobsSystem : EntitySystem /// Whether or not to pick from the overflow list. /// A set of disallowed jobs, if any. /// The selected job, if any. - public string? PickBestAvailableJobWithPriority(EntityUid station, IReadOnlyDictionary jobPriorities, bool pickOverflows, IReadOnlySet>? disallowedJobs = null) + public ProtoId? PickBestAvailableJobWithPriority(EntityUid station, IReadOnlyDictionary, JobPriority> jobPriorities, bool pickOverflows, IReadOnlySet>? disallowedJobs = null) { if (station == EntityUid.Invalid) return null; var available = GetAvailableJobs(station); - bool TryPick(JobPriority priority, [NotNullWhen(true)] out string? jobId) + bool TryPick(JobPriority priority, [NotNullWhen(true)] out ProtoId? jobId) { var filtered = jobPriorities .Where(p => @@ -474,7 +465,10 @@ public sealed partial class StationJobsSystem : EntitySystem return null; var overflows = GetOverflowJobs(station); - return overflows.Count != 0 ? _random.Pick(overflows) : null; + if (overflows.Count == 0) + return null; + + return _random.Pick(overflows); } #endregion Public API @@ -483,7 +477,7 @@ public sealed partial class StationJobsSystem : EntitySystem private bool _availableJobsDirty; - private TickerJobsAvailableEvent _cachedAvailableJobs = new (new Dictionary(), new Dictionary>()); + private TickerJobsAvailableEvent _cachedAvailableJobs = new(new(), new()); /// /// Assembles an event from the current available-to-play jobs. @@ -494,9 +488,9 @@ public sealed partial class StationJobsSystem : EntitySystem { // If late join is disallowed, return no available jobs. if (_gameTicker.DisallowLateJoin) - return new TickerJobsAvailableEvent(new Dictionary(), new Dictionary>()); + return new TickerJobsAvailableEvent(new(), new()); - var jobs = new Dictionary>(); + var jobs = new Dictionary, int?>>(); var stationNames = new Dictionary(); var query = EntityQueryEnumerator(); diff --git a/Content.Server/Station/Systems/StationSpawningSystem.cs b/Content.Server/Station/Systems/StationSpawningSystem.cs index e1667a9c4e..ce8177e103 100644 --- a/Content.Server/Station/Systems/StationSpawningSystem.cs +++ b/Content.Server/Station/Systems/StationSpawningSystem.cs @@ -260,10 +260,8 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem _cardSystem.TryChangeFullName(cardId, characterName, card); _cardSystem.TryChangeJobTitle(cardId, jobPrototype.LocalizedName, card); - if (_prototypeManager.TryIndex(jobPrototype.Icon, out var jobIcon)) - { + if (_prototypeManager.TryIndex(jobPrototype.Icon, out var jobIcon)) _cardSystem.TryChangeJobIcon(cardId, jobIcon, card); - } var extendedAccess = false; if (station != null) diff --git a/Content.Server/StationEvents/Events/StationEventSystem.cs b/Content.Server/StationEvents/Events/StationEventSystem.cs index bd377ec16c..1fe4b5eb52 100644 --- a/Content.Server/StationEvents/Events/StationEventSystem.cs +++ b/Content.Server/StationEvents/Events/StationEventSystem.cs @@ -118,13 +118,4 @@ public abstract class StationEventSystem : GameRuleSystem where T : ICompo } } } - - #region Helper Functions - - protected void ForceEndSelf(EntityUid uid, GameRuleComponent? component = null) - { - GameTicker.EndGameRule(uid, component); - } - - #endregion } diff --git a/Content.Server/Sticky/Systems/StickySystem.cs b/Content.Server/Sticky/Systems/StickySystem.cs index effe1b72f7..21a7f4d039 100644 --- a/Content.Server/Sticky/Systems/StickySystem.cs +++ b/Content.Server/Sticky/Systems/StickySystem.cs @@ -7,6 +7,7 @@ using Content.Shared.Interaction; using Content.Shared.Sticky; using Content.Shared.Sticky.Components; using Content.Shared.Verbs; +using Content.Shared.Whitelist; using Robust.Shared.Containers; using Robust.Shared.Utility; @@ -20,6 +21,7 @@ public sealed class StickySystem : EntitySystem [Dependency] private readonly SharedHandsSystem _handsSystem = default!; [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; private const string StickerSlotId = "stickers_container"; @@ -67,9 +69,8 @@ public sealed class StickySystem : EntitySystem return false; // check whitelist and blacklist - if (component.Whitelist != null && !component.Whitelist.IsValid(target)) - return false; - if (component.Blacklist != null && component.Blacklist.IsValid(target)) + if (_whitelistSystem.IsWhitelistFail(component.Whitelist, target) || + _whitelistSystem.IsBlacklistPass(component.Blacklist, target)) return false; var attemptEv = new AttemptEntityStickEvent(target, user); diff --git a/Content.Server/Storage/EntitySystems/ItemCounterSystem.cs b/Content.Server/Storage/EntitySystems/ItemCounterSystem.cs index 415e8d9246..43fcb32d1f 100644 --- a/Content.Server/Storage/EntitySystems/ItemCounterSystem.cs +++ b/Content.Server/Storage/EntitySystems/ItemCounterSystem.cs @@ -1,6 +1,7 @@ -using Content.Shared.Storage; +using Content.Shared.Storage; using Content.Shared.Storage.Components; using Content.Shared.Storage.EntitySystems; +using Content.Shared.Whitelist; using JetBrains.Annotations; using Robust.Shared.Containers; @@ -9,6 +10,7 @@ namespace Content.Server.Storage.EntitySystems [UsedImplicitly] public sealed class ItemCounterSystem : SharedItemCounterSystem { + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; protected override int? GetCount(ContainerModifiedMessage msg, ItemCounterComponent itemCounter) { if (!EntityManager.TryGetComponent(msg.Container.Owner, out StorageComponent? component)) @@ -19,7 +21,7 @@ namespace Content.Server.Storage.EntitySystems var count = 0; foreach (var entity in component.Container.ContainedEntities) { - if (itemCounter.Count.IsValid(entity)) + if (_whitelistSystem.IsWhitelistPass(itemCounter.Count, entity)) count++; } diff --git a/Content.Server/Storage/EntitySystems/PickRandomSystem.cs b/Content.Server/Storage/EntitySystems/PickRandomSystem.cs index dbbe1dd778..f0e986e199 100644 --- a/Content.Server/Storage/EntitySystems/PickRandomSystem.cs +++ b/Content.Server/Storage/EntitySystems/PickRandomSystem.cs @@ -4,6 +4,7 @@ using Content.Shared.Database; using Content.Shared.Hands.EntitySystems; using Content.Shared.Storage; using Content.Shared.Verbs; +using Content.Shared.Whitelist; using Robust.Shared.Containers; using Robust.Shared.Random; @@ -15,6 +16,7 @@ public sealed class PickRandomSystem : EntitySystem [Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly SharedHandsSystem _hands = default!; [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -30,7 +32,7 @@ public sealed class PickRandomSystem : EntitySystem var user = args.User; - var enabled = storage.Container.ContainedEntities.Any(item => comp.Whitelist?.IsValid(item, EntityManager) ?? true); + var enabled = storage.Container.ContainedEntities.Any(item => _whitelistSystem.IsWhitelistPassOrNull(comp.Whitelist, item)); // alt-click / alt-z to pick an item args.Verbs.Add(new AlternativeVerb @@ -48,7 +50,7 @@ public sealed class PickRandomSystem : EntitySystem private void TryPick(EntityUid uid, PickRandomComponent comp, StorageComponent storage, EntityUid user) { - var entities = storage.Container.ContainedEntities.Where(item => comp.Whitelist?.IsValid(item, EntityManager) ?? true).ToArray(); + var entities = storage.Container.ContainedEntities.Where(item => _whitelistSystem.IsWhitelistPassOrNull(comp.Whitelist, item)).ToArray(); if (!entities.Any()) return; diff --git a/Content.Server/Store/Conditions/BuyBeforeCondition.cs b/Content.Server/Store/Conditions/BuyBeforeCondition.cs index 132f353439..3f0c2de2e1 100644 --- a/Content.Server/Store/Conditions/BuyBeforeCondition.cs +++ b/Content.Server/Store/Conditions/BuyBeforeCondition.cs @@ -1,6 +1,7 @@ using Content.Server.Store.Components; using Content.Server.Store.Systems; using Content.Shared.Store; +using Content.Shared.Store.Components; using Robust.Shared.Prototypes; namespace Content.Server.Store.Conditions; diff --git a/Content.Server/Store/Conditions/BuyerDepartmentCondition.cs b/Content.Server/Store/Conditions/BuyerDepartmentCondition.cs index 4e5e504aec..ea8de4a9cc 100644 --- a/Content.Server/Store/Conditions/BuyerDepartmentCondition.cs +++ b/Content.Server/Store/Conditions/BuyerDepartmentCondition.cs @@ -43,7 +43,7 @@ public sealed partial class BuyerDepartmentCondition : ListingCondition { foreach (var department in prototypeManager.EnumeratePrototypes()) { - if (department.Roles.Contains(job.Prototype) && Blacklist.Contains(department.ID)) + if (department.Roles.Contains(job.Prototype.Value) && Blacklist.Contains(department.ID)) return false; } } @@ -56,7 +56,7 @@ public sealed partial class BuyerDepartmentCondition : ListingCondition { foreach (var department in prototypeManager.EnumeratePrototypes()) { - if (department.Roles.Contains(job.Prototype) && Whitelist.Contains(department.ID)) + if (department.Roles.Contains(job.Prototype.Value) && Whitelist.Contains(department.ID)) { found = true; break; diff --git a/Content.Server/Store/Conditions/BuyerWhitelistCondition.cs b/Content.Server/Store/Conditions/BuyerWhitelistCondition.cs index 859703a72a..ff4a9a19cd 100644 --- a/Content.Server/Store/Conditions/BuyerWhitelistCondition.cs +++ b/Content.Server/Store/Conditions/BuyerWhitelistCondition.cs @@ -23,18 +23,11 @@ public sealed partial class BuyerWhitelistCondition : ListingCondition public override bool Condition(ListingConditionArgs args) { var ent = args.EntityManager; + var whitelistSystem = ent.System(); - if (Whitelist != null) - { - if (!Whitelist.IsValid(args.Buyer, ent)) - return false; - } - - if (Blacklist != null) - { - if (Blacklist.IsValid(args.Buyer, ent)) - return false; - } + if (whitelistSystem.IsWhitelistFail(Whitelist, args.Buyer) || + whitelistSystem.IsBlacklistPass(Blacklist, args.Buyer)) + return false; return true; } diff --git a/Content.Server/Store/Conditions/StoreWhitelistCondition.cs b/Content.Server/Store/Conditions/StoreWhitelistCondition.cs index 20ec5cecce..ced4dfa9c0 100644 --- a/Content.Server/Store/Conditions/StoreWhitelistCondition.cs +++ b/Content.Server/Store/Conditions/StoreWhitelistCondition.cs @@ -26,18 +26,11 @@ public sealed partial class StoreWhitelistCondition : ListingCondition return false; var ent = args.EntityManager; + var whitelistSystem = ent.System(); - if (Whitelist != null) - { - if (!Whitelist.IsValid(args.StoreEntity.Value, ent)) - return false; - } - - if (Blacklist != null) - { - if (Blacklist.IsValid(args.StoreEntity.Value, ent)) - return false; - } + if (whitelistSystem.IsWhitelistFail(Whitelist, args.StoreEntity.Value) || + whitelistSystem.IsBlacklistPass(Blacklist, args.StoreEntity.Value)) + return false; return true; } diff --git a/Content.Server/Store/Systems/StoreSystem.Command.cs b/Content.Server/Store/Systems/StoreSystem.Command.cs index d259da2c95..5ad361eb42 100644 --- a/Content.Server/Store/Systems/StoreSystem.Command.cs +++ b/Content.Server/Store/Systems/StoreSystem.Command.cs @@ -1,7 +1,9 @@ +using System.Linq; using Content.Server.Store.Components; using Content.Shared.FixedPoint; using Content.Server.Administration; using Content.Shared.Administration; +using Content.Shared.Store.Components; using Robust.Shared.Console; namespace Content.Server.Store.Systems; @@ -58,7 +60,7 @@ public sealed partial class StoreSystem if (args.Length == 2 && NetEntity.TryParse(args[0], out var uidNet) && TryGetEntity(uidNet, out var uid)) { if (TryComp(uid, out var store)) - return CompletionResult.FromHintOptions(store.CurrencyWhitelist, ""); + return CompletionResult.FromHintOptions(store.CurrencyWhitelist.Select(p => p.ToString()), ""); } return CompletionResult.Empty; diff --git a/Content.Server/Store/Systems/StoreSystem.Listings.cs b/Content.Server/Store/Systems/StoreSystem.Listings.cs index a56d9640d3..10b53a7c94 100644 --- a/Content.Server/Store/Systems/StoreSystem.Listings.cs +++ b/Content.Server/Store/Systems/StoreSystem.Listings.cs @@ -1,5 +1,6 @@ -using Content.Server.Store.Components; using Content.Shared.Store; +using Content.Shared.Store.Components; +using Robust.Shared.Prototypes; namespace Content.Server.Store.Systems; @@ -80,7 +81,11 @@ public sealed partial class StoreSystem /// What categories to filter by. /// The physial entity of the store. Can be null. /// The available listings. - public IEnumerable GetAvailableListings(EntityUid buyer, HashSet? listings, HashSet categories, EntityUid? storeEntity = null) + public IEnumerable GetAvailableListings( + EntityUid buyer, + HashSet? listings, + HashSet> categories, + EntityUid? storeEntity = null) { listings ??= GetAllListings(); @@ -117,7 +122,7 @@ public sealed partial class StoreSystem /// The listing itself. /// The categories to check through. /// If the listing was present in one of the categories. - public bool ListingHasCategory(ListingData listing, HashSet categories) + public bool ListingHasCategory(ListingData listing, HashSet> categories) { foreach (var cat in categories) { diff --git a/Content.Server/Store/Systems/StoreSystem.Refund.cs b/Content.Server/Store/Systems/StoreSystem.Refund.cs index 5a8be4be2b..4e823582e6 100644 --- a/Content.Server/Store/Systems/StoreSystem.Refund.cs +++ b/Content.Server/Store/Systems/StoreSystem.Refund.cs @@ -1,4 +1,5 @@ using Content.Server.Store.Components; +using Content.Shared.Store.Components; using Robust.Shared.Containers; namespace Content.Server.Store.Systems; diff --git a/Content.Server/Store/Systems/StoreSystem.Ui.cs b/Content.Server/Store/Systems/StoreSystem.Ui.cs index 0a1a8d19f3..983d68d0af 100644 --- a/Content.Server/Store/Systems/StoreSystem.Ui.cs +++ b/Content.Server/Store/Systems/StoreSystem.Ui.cs @@ -10,6 +10,7 @@ using Content.Shared.FixedPoint; using Content.Shared.Hands.EntitySystems; using Content.Shared.Mind; using Content.Shared.Store; +using Content.Shared.Store.Components; using Content.Shared.UserInterface; using Robust.Server.GameObjects; using Robust.Shared.Audio.Systems; @@ -82,16 +83,11 @@ public sealed partial class StoreSystem /// The person who if opening the store ui. Listings are filtered based on this. /// The store entity itself /// The store component being refreshed. - /// public void UpdateUserInterface(EntityUid? user, EntityUid store, StoreComponent? component = null) { if (!Resolve(store, ref component)) return; - // TODO: Why is the state not being set unless this? - if (!_ui.HasUi(store, StoreUiKey.Key)) - return; - //this is the person who will be passed into logic for all listing filtering. if (user != null) //if we have no "buyer" for this update, then don't update the listings { @@ -259,7 +255,8 @@ public sealed partial class StoreSystem } //log dat shit. - _admin.Add(LogType.StorePurchase, LogImpact.Low, + _admin.Add(LogType.StorePurchase, + LogImpact.Low, $"{ToPrettyString(buyer):player} purchased listing \"{ListingLocalisationHelpers.GetLocalisedNameOrEntityName(listing, _prototypeManager)}\" from {ToPrettyString(uid)}"); listing.PurchaseAmount++; //track how many times something has been purchased diff --git a/Content.Server/Store/Systems/StoreSystem.cs b/Content.Server/Store/Systems/StoreSystem.cs index ba87d08e6c..e4656fc921 100644 --- a/Content.Server/Store/Systems/StoreSystem.cs +++ b/Content.Server/Store/Systems/StoreSystem.cs @@ -5,11 +5,10 @@ using Content.Shared.Implants.Components; using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Stacks; -using Content.Shared.Store; using JetBrains.Annotations; -using Robust.Server.GameObjects; using Robust.Shared.Prototypes; using System.Linq; +using Content.Shared.Store.Components; using Robust.Shared.Utility; namespace Content.Server.Store.Systems; @@ -44,7 +43,6 @@ public sealed partial class StoreSystem : EntitySystem private void OnMapInit(EntityUid uid, StoreComponent component, MapInitEvent args) { RefreshAllListings(component); - InitializeFromPreset(component.Preset, uid, component); component.StartingMap = Transform(uid).MapUid; } @@ -54,7 +52,6 @@ public sealed partial class StoreSystem : EntitySystem if (MetaData(uid).EntityLifeStage == EntityLifeStage.MapInitialized) { RefreshAllListings(component); - InitializeFromPreset(component.Preset, uid, component); } var ev = new StoreAddedEvent(); @@ -167,43 +164,6 @@ public sealed partial class StoreSystem : EntitySystem UpdateUserInterface(null, uid, store); return true; } - - /// - /// Initializes a store based on a preset ID - /// - /// The ID of a store preset prototype - /// - /// The store being initialized - public void InitializeFromPreset(string? preset, EntityUid uid, StoreComponent component) - { - if (preset == null) - return; - - if (!_proto.TryIndex(preset, out var proto)) - return; - - InitializeFromPreset(proto, uid, component); - } - - /// - /// Initializes a store based on a given preset - /// - /// The StorePresetPrototype - /// - /// The store being initialized - public void InitializeFromPreset(StorePresetPrototype preset, EntityUid uid, StoreComponent component) - { - component.Preset = preset.ID; - component.CurrencyWhitelist.UnionWith(preset.CurrencyWhitelist); - component.Categories.UnionWith(preset.Categories); - if (component.Balance == new Dictionary() && preset.InitialBalance != null) //if we don't have a value stored, use the preset - TryAddCurrency(preset.InitialBalance, uid, component); - - if (_ui.HasUi(uid, StoreUiKey.Key)) - { - _ui.SetUiState(uid, StoreUiKey.Key, new StoreInitializeState(preset.StoreName)); - } - } } public sealed class CurrencyInsertAttemptEvent : CancellableEntityEventArgs diff --git a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMicrophoneSystem.cs b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMicrophoneSystem.cs index f411001bd3..a01f19c909 100644 --- a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMicrophoneSystem.cs +++ b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMicrophoneSystem.cs @@ -1,6 +1,7 @@ using Content.Server.Chat.Systems; using Content.Server.Speech; using Content.Server.Speech.Components; +using Content.Shared.Whitelist; using Robust.Shared.Player; using static Content.Server.Chat.Systems.ChatSystem; @@ -9,7 +10,7 @@ namespace Content.Server.SurveillanceCamera; public sealed class SurveillanceCameraMicrophoneSystem : EntitySystem { [Dependency] private readonly SharedTransformSystem _xforms = default!; - + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { base.Initialize(); @@ -60,7 +61,7 @@ public sealed class SurveillanceCameraMicrophoneSystem : EntitySystem public void CanListen(EntityUid uid, SurveillanceCameraMicrophoneComponent microphone, ListenAttemptEvent args) { // TODO maybe just make this a part of ActiveListenerComponent? - if (microphone.Blacklist.IsValid(args.Source)) + if (_whitelistSystem.IsBlacklistPass(microphone.Blacklist, args.Source)) args.Cancel(); } diff --git a/Content.Server/Teleportation/HandTeleporterSystem.cs b/Content.Server/Teleportation/HandTeleporterSystem.cs index d4c6753c4b..1cd2e1d8c2 100644 --- a/Content.Server/Teleportation/HandTeleporterSystem.cs +++ b/Content.Server/Teleportation/HandTeleporterSystem.cs @@ -98,7 +98,7 @@ public sealed class HandTeleporterSystem : EntitySystem if (xform.ParentUid != xform.GridUid) // Still, don't portal. return; - if (xform.ParentUid != Transform(component.FirstPortal!.Value).ParentUid) + if (!component.AllowPortalsOnDifferentGrids && xform.ParentUid != Transform(component.FirstPortal!.Value).ParentUid) { // Whoops. Fizzle time. Crime time too because yippee I'm not refactoring this logic right now (I started to, I'm not going to.) FizzlePortals(uid, component, user, true); diff --git a/Content.Server/Traitor/Uplink/SurplusBundle/SurplusBundleComponent.cs b/Content.Server/Traitor/Uplink/SurplusBundle/SurplusBundleComponent.cs index 47ce68625a..120581cf82 100644 --- a/Content.Server/Traitor/Uplink/SurplusBundle/SurplusBundleComponent.cs +++ b/Content.Server/Traitor/Uplink/SurplusBundle/SurplusBundleComponent.cs @@ -1,6 +1,3 @@ -using Content.Shared.Store; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - namespace Content.Server.Traitor.Uplink.SurplusBundle; /// @@ -12,14 +9,6 @@ public sealed partial class SurplusBundleComponent : Component /// /// Total price of all content inside bundle. /// - [ViewVariables(VVAccess.ReadOnly)] - [DataField("totalPrice")] + [DataField] public int TotalPrice = 20; - - /// - /// The preset that will be used to get all the listings. - /// Currently just defaults to the basic uplink. - /// - [DataField("storePreset", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string StorePreset = "StorePresetUplink"; } diff --git a/Content.Server/Traitor/Uplink/SurplusBundle/SurplusBundleSystem.cs b/Content.Server/Traitor/Uplink/SurplusBundle/SurplusBundleSystem.cs index 5c0a56d346..759cad5ded 100644 --- a/Content.Server/Traitor/Uplink/SurplusBundle/SurplusBundleSystem.cs +++ b/Content.Server/Traitor/Uplink/SurplusBundle/SurplusBundleSystem.cs @@ -3,76 +3,67 @@ using Content.Server.Storage.EntitySystems; using Content.Server.Store.Systems; using Content.Shared.FixedPoint; using Content.Shared.Store; -using Robust.Shared.Prototypes; +using Content.Shared.Store.Components; using Robust.Shared.Random; namespace Content.Server.Traitor.Uplink.SurplusBundle; public sealed class SurplusBundleSystem : EntitySystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly EntityStorageSystem _entityStorage = default!; [Dependency] private readonly StoreSystem _store = default!; - private ListingData[] _listings = default!; - public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnMapInit); - - SubscribeLocalEvent(OnInit); - } - - private void OnInit(EntityUid uid, SurplusBundleComponent component, ComponentInit args) - { - var storePreset = _prototypeManager.Index(component.StorePreset); - - _listings = _store.GetAvailableListings(uid, null, storePreset.Categories).ToArray(); - - Array.Sort(_listings, (a, b) => (int) (b.Cost.Values.Sum() - a.Cost.Values.Sum())); //this might get weird with multicurrency but don't think about it } private void OnMapInit(EntityUid uid, SurplusBundleComponent component, MapInitEvent args) { - FillStorage(uid, component); - } - - private void FillStorage(EntityUid uid, SurplusBundleComponent? component = null) - { - if (!Resolve(uid, ref component)) + if (!TryComp(uid, out var store)) return; - var cords = Transform(uid).Coordinates; + FillStorage((uid, component, store)); + } - var content = GetRandomContent(component.TotalPrice); + private void FillStorage(Entity ent) + { + var cords = Transform(ent).Coordinates; + var content = GetRandomContent(ent); foreach (var item in content) { - var ent = EntityManager.SpawnEntity(item.ProductEntity, cords); - _entityStorage.Insert(ent, uid); + var dode = Spawn(item.ProductEntity, cords); + _entityStorage.Insert(dode, ent); } } // wow, is this leetcode reference? - private List GetRandomContent(FixedPoint2 targetCost) + private List GetRandomContent(Entity ent) { var ret = new List(); - if (_listings.Length == 0) + + var listings = _store.GetAvailableListings(ent, null, ent.Comp2.Categories) + .OrderBy(p => p.Cost.Values.Sum()) + .ToList(); + + if (listings.Count == 0) return ret; var totalCost = FixedPoint2.Zero; var index = 0; - while (totalCost < targetCost) + while (totalCost < ent.Comp1.TotalPrice) { // All data is sorted in price descending order // Find new item with the lowest acceptable price // All expansive items will be before index, all acceptable after - var remainingBudget = targetCost - totalCost; - while (_listings[index].Cost.Values.Sum() > remainingBudget) + var remainingBudget = ent.Comp1.TotalPrice - totalCost; + while (listings[index].Cost.Values.Sum() > remainingBudget) { index++; - if (index >= _listings.Length) + if (index >= listings.Count) { // Looks like no cheap items left // It shouldn't be case for ss14 content @@ -82,8 +73,8 @@ public sealed class SurplusBundleSystem : EntitySystem } // Select random listing and add into crate - var randomIndex = _random.Next(index, _listings.Length); - var randomItem = _listings[randomIndex]; + var randomIndex = _random.Next(index, listings.Count); + var randomItem = listings[randomIndex]; ret.Add(randomItem); totalCost += randomItem.Cost.Values.Sum(); } diff --git a/Content.Server/Traitor/Uplink/UplinkComponent.cs b/Content.Server/Traitor/Uplink/UplinkComponent.cs new file mode 100644 index 0000000000..35f11ce9ef --- /dev/null +++ b/Content.Server/Traitor/Uplink/UplinkComponent.cs @@ -0,0 +1,7 @@ +namespace Content.Server.Traitor.Uplink; + +/// +/// This is used for identifying something as a hidden uplink and showing the UI. +/// +[RegisterComponent] +public sealed partial class UplinkComponent : Component; diff --git a/Content.Server/Traitor/Uplink/UplinkSystem.cs b/Content.Server/Traitor/Uplink/UplinkSystem.cs index 5670e28ec9..7c39f1ed66 100644 --- a/Content.Server/Traitor/Uplink/UplinkSystem.cs +++ b/Content.Server/Traitor/Uplink/UplinkSystem.cs @@ -5,6 +5,7 @@ using Content.Shared.PDA; using Content.Server.Store.Components; using Content.Shared.FixedPoint; using Content.Shared.Store; +using Content.Shared.Store.Components; namespace Content.Server.Traitor.Uplink { @@ -17,18 +18,6 @@ namespace Content.Server.Traitor.Uplink [ValidatePrototypeId] public const string TelecrystalCurrencyPrototype = "Telecrystal"; - /// - /// Gets the amount of TC on an "uplink" - /// Mostly just here for legacy systems based on uplink. - /// - /// - /// the amount of TC - public int GetTCBalance(StoreComponent component) - { - FixedPoint2? tcBalance = component.Balance.GetValueOrDefault(TelecrystalCurrencyPrototype); - return tcBalance?.Int() ?? 0; - } - /// /// Adds an uplink to the target /// @@ -37,7 +26,7 @@ namespace Content.Server.Traitor.Uplink /// The id of the storepreset /// The entity that will actually have the uplink functionality. Defaults to the PDA if null. /// Whether or not the uplink was added successfully - public bool AddUplink(EntityUid user, FixedPoint2? balance, string uplinkPresetId = "StorePresetUplink", EntityUid? uplinkEntity = null) + public bool AddUplink(EntityUid user, FixedPoint2? balance, EntityUid? uplinkEntity = null) { // Try to find target item if (uplinkEntity == null) @@ -47,11 +36,10 @@ namespace Content.Server.Traitor.Uplink return false; } + EnsureComp(uplinkEntity.Value); var store = EnsureComp(uplinkEntity.Value); - _store.InitializeFromPreset(uplinkPresetId, uplinkEntity.Value, store); store.AccountOwner = user; store.Balance.Clear(); - if (balance != null) { store.Balance.Clear(); diff --git a/Content.Server/Traits/Assorted/UnrevivableComponent.cs b/Content.Server/Traits/Assorted/UnrevivableComponent.cs new file mode 100644 index 0000000000..b95c922d54 --- /dev/null +++ b/Content.Server/Traits/Assorted/UnrevivableComponent.cs @@ -0,0 +1,10 @@ +namespace Content.Server.Traits.Assorted; + +/// +/// This is used for the urevivable trait. +/// +[RegisterComponent] +public sealed partial class UnrevivableComponent : Component +{ + +} diff --git a/Content.Server/Traits/TraitSystem.cs b/Content.Server/Traits/TraitSystem.cs index 22ee0e4861..f41512b6ac 100644 --- a/Content.Server/Traits/TraitSystem.cs +++ b/Content.Server/Traits/TraitSystem.cs @@ -2,6 +2,7 @@ using Content.Server.GameTicking; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; using Content.Shared.Traits; +using Content.Shared.Whitelist; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.Manager; @@ -12,6 +13,7 @@ public sealed class TraitSystem : EntitySystem [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly ISerializationManager _serializationManager = default!; [Dependency] private readonly SharedHandsSystem _sharedHandsSystem = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -31,34 +33,26 @@ public sealed class TraitSystem : EntitySystem return; } - if (traitPrototype.Whitelist != null && !traitPrototype.Whitelist.IsValid(args.Mob)) - continue; - - if (traitPrototype.Blacklist != null && traitPrototype.Blacklist.IsValid(args.Mob)) + if (_whitelistSystem.IsWhitelistFail(traitPrototype.Whitelist, args.Mob) || + _whitelistSystem.IsBlacklistPass(traitPrototype.Blacklist, args.Mob)) continue; // Add all components required by the prototype - foreach (var entry in traitPrototype.Components.Values) - { - if (HasComp(args.Mob, entry.Component.GetType())) - continue; - - var comp = (Component) _serializationManager.CreateCopy(entry.Component, notNullableOverride: true); - comp.Owner = args.Mob; - EntityManager.AddComponent(args.Mob, comp); - } + EntityManager.AddComponents(args.Mob, traitPrototype.Components, false); // Add item required by the trait - if (traitPrototype.TraitGear != null) - { - if (!TryComp(args.Mob, out HandsComponent? handsComponent)) - continue; + if (traitPrototype.TraitGear == null) + continue; - var coords = Transform(args.Mob).Coordinates; - var inhandEntity = EntityManager.SpawnEntity(traitPrototype.TraitGear, coords); - _sharedHandsSystem.TryPickup(args.Mob, inhandEntity, checkActionBlocker: false, - handsComp: handsComponent); - } + if (!TryComp(args.Mob, out HandsComponent? handsComponent)) + continue; + + var coords = Transform(args.Mob).Coordinates; + var inhandEntity = EntityManager.SpawnEntity(traitPrototype.TraitGear, coords); + _sharedHandsSystem.TryPickup(args.Mob, + inhandEntity, + checkActionBlocker: false, + handsComp: handsComponent); } } } diff --git a/Content.Server/Weapons/Melee/EnergySword/EnergySwordSystem.cs b/Content.Server/Weapons/Melee/EnergySword/EnergySwordSystem.cs index e8897781f5..5970e16319 100644 --- a/Content.Server/Weapons/Melee/EnergySword/EnergySwordSystem.cs +++ b/Content.Server/Weapons/Melee/EnergySword/EnergySwordSystem.cs @@ -2,8 +2,7 @@ using Content.Shared.Interaction; using Content.Shared.Light; using Content.Shared.Light.Components; using Content.Shared.Toggleable; -using Content.Shared.Tools.Components; -using Content.Shared.Item; +using Content.Shared.Tools.Systems; using Robust.Shared.Random; namespace Content.Server.Weapons.Melee.EnergySword; @@ -13,6 +12,7 @@ public sealed class EnergySwordSystem : EntitySystem [Dependency] private readonly SharedRgbLightControllerSystem _rgbSystem = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedToolSystem _toolSystem = default!; public override void Initialize() { @@ -38,7 +38,7 @@ public sealed class EnergySwordSystem : EntitySystem if (args.Handled) return; - if (!TryComp(args.Used, out ToolComponent? tool) || !tool.Qualities.ContainsAny("Pulsing")) + if (!_toolSystem.HasQuality(args.Used, "Pulsing")) return; args.Handled = true; diff --git a/Content.Server/Weapons/Ranged/Systems/GunSystem.cs b/Content.Server/Weapons/Ranged/Systems/GunSystem.cs index 7247109e37..cb893299a9 100644 --- a/Content.Server/Weapons/Ranged/Systems/GunSystem.cs +++ b/Content.Server/Weapons/Ranged/Systems/GunSystem.cs @@ -396,7 +396,7 @@ public sealed partial class GunSystem : SharedGunSystem var (_, gridRot, gridInvMatrix) = TransformSystem.GetWorldPositionRotationInvMatrix(gridXform, xformQuery); fromCoordinates = new EntityCoordinates(gridUid.Value, - gridInvMatrix.Transform(fromCoordinates.ToMapPos(EntityManager, TransformSystem))); + Vector2.Transform(fromCoordinates.ToMapPos(EntityManager, TransformSystem), gridInvMatrix)); // Use the fallback angle I guess? angle -= gridRot; diff --git a/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactCrusherSystem.cs b/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactCrusherSystem.cs index 6606f28432..f65ba46f7a 100644 --- a/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactCrusherSystem.cs +++ b/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactCrusherSystem.cs @@ -8,6 +8,7 @@ using Content.Server.Xenoarchaeology.XenoArtifacts; using Content.Shared.Body.Components; using Content.Shared.Damage; using Content.Shared.Verbs; +using Content.Shared.Whitelist; using Content.Shared.Xenoarchaeology.Equipment; using Robust.Shared.Collections; using Robust.Shared.Random; @@ -25,6 +26,7 @@ public sealed class ArtifactCrusherSystem : SharedArtifactCrusherSystem [Dependency] private readonly DamageableSystem _damageable = default!; [Dependency] private readonly StackSystem _stack = default!; [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; /// public override void Initialize() @@ -92,7 +94,7 @@ public sealed class ArtifactCrusherSystem : SharedArtifactCrusherSystem var coords = Transform(ent).Coordinates; foreach (var contained in contents) { - if (crusher.CrushingWhitelist.IsValid(contained, EntityManager)) + if (_whitelistSystem.IsWhitelistPass(crusher.CrushingWhitelist, contained)) { var amount = _random.Next(crusher.MinFragments, crusher.MaxFragments); var stacks = _stack.SpawnMultiple(crusher.FragmentStackProtoId, amount, coords); diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.Nodes.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.Nodes.cs index 895bb0217b..65aaabdf0e 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.Nodes.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.Nodes.cs @@ -1,15 +1,16 @@ using System.Linq; using Content.Server.Xenoarchaeology.XenoArtifacts.Events; +using Content.Shared.Whitelist; using Content.Shared.Xenoarchaeology.XenoArtifacts; using JetBrains.Annotations; -using Robust.Shared.Prototypes; using Robust.Shared.Random; -using Robust.Shared.Serialization.Manager; namespace Content.Server.Xenoarchaeology.XenoArtifacts; public sealed partial class ArtifactSystem { + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; + private const int MaxEdgesPerNode = 4; private readonly HashSet _usedNodeIds = new(); @@ -81,7 +82,8 @@ public sealed partial class ArtifactSystem private string GetRandomTrigger(EntityUid artifact, ref ArtifactNode node) { var allTriggers = _prototype.EnumeratePrototypes() - .Where(x => (x.Whitelist?.IsValid(artifact, EntityManager) ?? true) && (!x.Blacklist?.IsValid(artifact, EntityManager) ?? true)).ToList(); + .Where(x => _whitelistSystem.IsWhitelistPassOrNull(x.Whitelist, artifact) && + _whitelistSystem.IsBlacklistFailOrNull(x.Blacklist, artifact)).ToList(); var validDepth = allTriggers.Select(x => x.TargetDepth).Distinct().ToList(); var weights = GetDepthWeights(validDepth, node.Depth); @@ -95,7 +97,8 @@ public sealed partial class ArtifactSystem private string GetRandomEffect(EntityUid artifact, ref ArtifactNode node) { var allEffects = _prototype.EnumeratePrototypes() - .Where(x => (x.Whitelist?.IsValid(artifact, EntityManager) ?? true) && (!x.Blacklist?.IsValid(artifact, EntityManager) ?? true)).ToList(); + .Where(x => _whitelistSystem.IsWhitelistPassOrNull(x.Whitelist, artifact) && + _whitelistSystem.IsBlacklistFailOrNull(x.Blacklist, artifact)).ToList(); var validDepth = allEffects.Select(x => x.TargetDepth).Distinct().ToList(); var weights = GetDepthWeights(validDepth, node.Depth); diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/DamageNearbyArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/DamageNearbyArtifactSystem.cs index a2023a18d4..f231120ad5 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/DamageNearbyArtifactSystem.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/DamageNearbyArtifactSystem.cs @@ -1,6 +1,7 @@ -using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components; +using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components; using Content.Server.Xenoarchaeology.XenoArtifacts.Events; using Content.Shared.Damage; +using Content.Shared.Whitelist; using Robust.Shared.Random; namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems; @@ -10,6 +11,7 @@ public sealed class BreakWindowArtifactSystem : EntitySystem [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; /// public override void Initialize() @@ -24,7 +26,7 @@ public sealed class BreakWindowArtifactSystem : EntitySystem ents.Add(args.Activator.Value); foreach (var ent in ents) { - if (component.Whitelist != null && !component.Whitelist.IsValid(ent)) + if (_whitelistSystem.IsWhitelistFail(component.Whitelist, ent)) continue; if (!_random.Prob(component.DamageChance)) diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactElectricityTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactElectricityTriggerSystem.cs index aa2a16aa1b..019e09bbbb 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactElectricityTriggerSystem.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactElectricityTriggerSystem.cs @@ -3,12 +3,14 @@ using Content.Server.Power.Events; using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components; using Content.Shared.Interaction; using Content.Shared.Tools.Components; +using Content.Shared.Tools.Systems; namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems; public sealed class ArtifactElectricityTriggerSystem : EntitySystem { [Dependency] private readonly ArtifactSystem _artifactSystem = default!; + [Dependency] private readonly SharedToolSystem _toolSystem = default!; public override void Initialize() { @@ -42,7 +44,7 @@ public sealed class ArtifactElectricityTriggerSystem : EntitySystem if (args.Handled) return; - if (!TryComp(args.Used, out ToolComponent? tool) || !tool.Qualities.ContainsAny("Pulsing")) + if (!_toolSystem.HasQuality(args.Used, "Pulsing")) return; args.Handled = _artifactSystem.TryActivateArtifact(uid, args.User); diff --git a/Content.Shared/Access/SharedAgentIDCardSystem.cs b/Content.Shared/Access/SharedAgentIDCardSystem.cs index d027a3937f..91aa626fe3 100644 --- a/Content.Shared/Access/SharedAgentIDCardSystem.cs +++ b/Content.Shared/Access/SharedAgentIDCardSystem.cs @@ -1,3 +1,5 @@ +using Content.Shared.StatusIcon; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; namespace Content.Shared.Access.Systems @@ -23,12 +25,12 @@ namespace Content.Shared.Access.Systems [Serializable, NetSerializable] public sealed class AgentIDCardBoundUserInterfaceState : BoundUserInterfaceState { - public readonly HashSet Icons; + public readonly HashSet> Icons; public string CurrentName { get; } public string CurrentJob { get; } public string CurrentJobIconId { get; } - public AgentIDCardBoundUserInterfaceState(string currentName, string currentJob, string currentJobIconId, HashSet icons) + public AgentIDCardBoundUserInterfaceState(string currentName, string currentJob, string currentJobIconId, HashSet> icons) { Icons = icons; CurrentName = currentName; @@ -62,9 +64,9 @@ namespace Content.Shared.Access.Systems [Serializable, NetSerializable] public sealed class AgentIDCardJobIconChangedMessage : BoundUserInterfaceMessage { - public string JobIconId { get; } + public ProtoId JobIconId { get; } - public AgentIDCardJobIconChangedMessage(string jobIconId) + public AgentIDCardJobIconChangedMessage(ProtoId jobIconId) { JobIconId = jobIconId; } diff --git a/Content.Shared/Access/Systems/AccessReaderSystem.cs b/Content.Shared/Access/Systems/AccessReaderSystem.cs index 3670e24bd3..efdbff3bb8 100644 --- a/Content.Shared/Access/Systems/AccessReaderSystem.cs +++ b/Content.Shared/Access/Systems/AccessReaderSystem.cs @@ -6,7 +6,9 @@ using Content.Shared.Emag.Components; using Content.Shared.Emag.Systems; using Content.Shared.Hands.EntitySystems; using Content.Shared.Inventory; +using Content.Shared.NameIdentifier; using Content.Shared.PDA; +using Content.Shared.Silicons.Borgs.Components; using Content.Shared.StationRecords; using Robust.Shared.Containers; using Robust.Shared.GameStates; @@ -393,6 +395,9 @@ public sealed class AccessReaderSystem : EntitySystem ent.Comp.AccessLog.Dequeue(); string? name = null; + if (TryComp(accessor, out var nameIdentifier)) + name = nameIdentifier.FullIdentifier; + // TODO pass the ID card on IsAllowed() instead of using this expensive method // Set name if the accessor has a card and that card has a name and allows itself to be recorded if (_idCardSystem.TryFindIdCard(accessor, out var idCard) diff --git a/Content.Shared/ActionBlocker/ActionBlockerSystem.cs b/Content.Shared/ActionBlocker/ActionBlockerSystem.cs index d2883b5ef5..f1c77fda43 100644 --- a/Content.Shared/ActionBlocker/ActionBlockerSystem.cs +++ b/Content.Shared/ActionBlocker/ActionBlockerSystem.cs @@ -1,13 +1,9 @@ -using Content.Shared.Bed.Sleep; using Content.Shared.Body.Events; -using Content.Shared.DragDrop; using Content.Shared.Emoting; using Content.Shared.Hands; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Item; -using Content.Shared.Mobs; -using Content.Shared.Mobs.Components; using Content.Shared.Movement.Components; using Content.Shared.Movement.Events; using Content.Shared.Speech; @@ -194,7 +190,7 @@ namespace Content.Shared.ActionBlocker if (target == null) return true; - var tev = new GettingAttackedAttemptEvent(); + var tev = new GettingAttackedAttemptEvent(uid, weapon, disarm); RaiseLocalEvent(target.Value, ref tev); return !tev.Cancelled; } diff --git a/Content.Shared/Actions/BaseActionComponent.cs b/Content.Shared/Actions/BaseActionComponent.cs index 57c145a0ec..9156f747f5 100644 --- a/Content.Shared/Actions/BaseActionComponent.cs +++ b/Content.Shared/Actions/BaseActionComponent.cs @@ -1,14 +1,16 @@ using Robust.Shared.Audio; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Utility; namespace Content.Shared.Actions; -// TODO ACTIONS make this a seprate component and remove the inheritance stuff. +// TODO ACTIONS make this a separate component and remove the inheritance stuff. // TODO ACTIONS convert to auto comp state? // TODO add access attribute. Need to figure out what to do with decal & mapping actions. // [Access(typeof(SharedActionsSystem))] +[EntityCategory("Actions")] public abstract partial class BaseActionComponent : Component { public abstract BaseActionEvent? BaseEvent { get; } diff --git a/Content.Shared/Actions/SharedActionsSystem.cs b/Content.Shared/Actions/SharedActionsSystem.cs index bf86d2c1e4..0e302f1e02 100644 --- a/Content.Shared/Actions/SharedActionsSystem.cs +++ b/Content.Shared/Actions/SharedActionsSystem.cs @@ -9,6 +9,7 @@ using Content.Shared.Interaction; using Content.Shared.Inventory.Events; using Content.Shared.Mind; using Content.Shared.Rejuvenate; +using Content.Shared.Whitelist; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; using Robust.Shared.GameStates; @@ -29,6 +30,7 @@ public abstract class SharedActionsSystem : EntitySystem [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedTransformSystem _transformSystem = default!; [Dependency] private readonly ActionContainerSystem _actionContainer = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -477,7 +479,7 @@ public abstract class SharedActionsSystem : EntitySystem if (!target.IsValid() || Deleted(target)) return false; - if (action.Whitelist != null && !action.Whitelist.IsValid(target, EntityManager)) + if (_whitelistSystem.IsWhitelistFail(action.Whitelist, target)) return false; if (action.CheckCanInteract && !_actionBlockerSystem.CanInteract(user, target)) diff --git a/Content.Shared/Antag/IAntagStatusIconComponent.cs b/Content.Shared/Antag/IAntagStatusIconComponent.cs deleted file mode 100644 index 981937c916..0000000000 --- a/Content.Shared/Antag/IAntagStatusIconComponent.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Content.Shared.StatusIcon; -using Robust.Shared.Prototypes; - -namespace Content.Shared.Antag; - -public interface IAntagStatusIconComponent -{ - public ProtoId StatusIcon { get; set; } - - public bool IconVisibleToGhost { get; set; } -} - diff --git a/Content.Shared/Antag/ShowAntagIconsComponent.cs b/Content.Shared/Antag/ShowAntagIconsComponent.cs new file mode 100644 index 0000000000..c451b69c3e --- /dev/null +++ b/Content.Shared/Antag/ShowAntagIconsComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Antag; + +/// +/// Determines whether Someone can see antags icons +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class ShowAntagIconsComponent: Component; diff --git a/Content.Shared/Body/Prototypes/BodyPrototypeSerializer.cs b/Content.Shared/Body/Prototypes/BodyPrototypeSerializer.cs index e2b54bf951..ae09976704 100644 --- a/Content.Shared/Body/Prototypes/BodyPrototypeSerializer.cs +++ b/Content.Shared/Body/Prototypes/BodyPrototypeSerializer.cs @@ -182,7 +182,7 @@ public sealed class BodyPrototypeSerializer : ITypeReader(), organs ?? new Dictionary()); + var slot = new BodyPrototypeSlot(part, connections ?? new HashSet(), organs ?? new Dictionary()); slots.Add(slotId, slot); } diff --git a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs index 00040211e3..e7bfb53f08 100644 --- a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs +++ b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs @@ -15,6 +15,7 @@ using Content.Shared.Storage.Components; using Content.Shared.Stunnable; using Content.Shared.Throwing; using Content.Shared.Verbs; +using Content.Shared.Whitelist; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Events; using Robust.Shared.Utility; @@ -24,6 +25,8 @@ namespace Content.Shared.Buckle; public abstract partial class SharedBuckleSystem { + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; + private void InitializeBuckle() { SubscribeLocalEvent(OnBuckleComponentStartup); @@ -224,8 +227,8 @@ public abstract partial class SharedBuckleSystem } // Does it pass the Whitelist - if (strapComp.Whitelist != null && - !strapComp.Whitelist.IsValid(buckleUid, EntityManager) || strapComp.Blacklist?.IsValid(buckleUid, EntityManager) == true) + if (_whitelistSystem.IsWhitelistFail(strapComp.Whitelist, buckleUid) || + _whitelistSystem.IsBlacklistPass(strapComp.Blacklist, buckleUid)) { if (_netManager.IsServer) _popup.PopupEntity(Loc.GetString("buckle-component-cannot-fit-message"), userUid, buckleUid, PopupType.Medium); diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index ac0b3d3289..3ad50d982e 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -1,4 +1,5 @@ using Content.Shared.Maps; +using Content.Shared.Roles; using Robust.Shared; using Robust.Shared.Configuration; using Robust.Shared.Physics.Components; @@ -225,6 +226,12 @@ namespace Content.Shared.CCVar public static readonly CVarDef GameRoleTimers = CVarDef.Create("game.role_timers", true, CVar.SERVER | CVar.REPLICATED); + /// + /// Override default role requirements using a + /// + public static readonly CVarDef + GameRoleTimerOverride = CVarDef.Create("game.role_timer_override", "", CVar.SERVER | CVar.REPLICATED); + /// /// If roles should be restricted based on whether or not they are whitelisted. /// @@ -2063,6 +2070,12 @@ namespace Content.Shared.CCVar public static readonly CVarDef DebugOptionVisualizerTest = CVarDef.Create("debug.option_visualizer_test", false, CVar.CLIENTONLY); + /// + /// Set to true to disable parallel processing in the pow3r solver. + /// + public static readonly CVarDef DebugPow3rDisableParallel = + CVarDef.Create("debug.pow3r_disable_parallel", true, CVar.SERVERONLY); + /// DELTA-V CCVARS /* * Glimmer diff --git a/Content.Shared/Clothing/Components/WaddleWhenWornComponent.cs b/Content.Shared/Clothing/Components/WaddleWhenWornComponent.cs index 5cd7a72457..fb7490ef4f 100644 --- a/Content.Shared/Clothing/Components/WaddleWhenWornComponent.cs +++ b/Content.Shared/Clothing/Components/WaddleWhenWornComponent.cs @@ -1,35 +1,36 @@ using System.Numerics; +using Robust.Shared.GameStates; namespace Content.Shared.Clothing.Components; /// /// Defines something as causing waddling when worn. /// -[RegisterComponent] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] public sealed partial class WaddleWhenWornComponent : Component { /// /// How high should they hop during the waddle? Higher hop = more energy. /// - [DataField] + [DataField, AutoNetworkedField] public Vector2 HopIntensity = new(0, 0.25f); /// /// How far should they rock backward and forward during the waddle? /// Each step will alternate between this being a positive and negative rotation. More rock = more scary. /// - [DataField] + [DataField, AutoNetworkedField] public float TumbleIntensity = 20.0f; /// /// How long should a complete step take? Less time = more chaos. /// - [DataField] + [DataField, AutoNetworkedField] public float AnimationLength = 0.66f; /// /// How much shorter should the animation be when running? /// - [DataField] + [DataField, AutoNetworkedField] public float RunAnimationLengthMultiplier = 0.568f; } diff --git a/Content.Shared/Clothing/EntitySystems/SharedChameleonClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/SharedChameleonClothingSystem.cs index a447a54df1..fced03bfab 100644 --- a/Content.Shared/Clothing/EntitySystems/SharedChameleonClothingSystem.cs +++ b/Content.Shared/Clothing/EntitySystems/SharedChameleonClothingSystem.cs @@ -12,9 +12,10 @@ public abstract class SharedChameleonClothingSystem : EntitySystem { [Dependency] private readonly IComponentFactory _factory = default!; [Dependency] private readonly IPrototypeManager _proto = default!; - [Dependency] private readonly SharedItemSystem _itemSystem = default!; [Dependency] private readonly ClothingSystem _clothingSystem = default!; [Dependency] private readonly MetaDataSystem _metaData = default!; + [Dependency] private readonly SharedItemSystem _itemSystem = default!; + [Dependency] private readonly TagSystem _tag = default!; public override void Initialize() { @@ -81,7 +82,7 @@ public abstract class SharedChameleonClothingSystem : EntitySystem return false; // check if it is marked as valid chameleon target - if (!proto.TryGetComponent(out TagComponent? tags, _factory) || !tags.Tags.Contains("WhitelistChameleon")) + if (!proto.TryGetComponent(out TagComponent? tag, _factory) || !_tag.HasTag(tag, "WhitelistChameleon")) return false; // check if it's valid clothing diff --git a/Content.Client/Clothing/Systems/WaddleClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/WaddleClothingSystem.cs similarity index 59% rename from Content.Client/Clothing/Systems/WaddleClothingSystem.cs rename to Content.Shared/Clothing/EntitySystems/WaddleClothingSystem.cs index b8ac3c207b..b445eb258e 100644 --- a/Content.Client/Clothing/Systems/WaddleClothingSystem.cs +++ b/Content.Shared/Clothing/EntitySystems/WaddleClothingSystem.cs @@ -1,8 +1,9 @@ -using Content.Shared.Clothing.Components; +using Content.Shared.Clothing; +using Content.Shared.Clothing.Components; using Content.Shared.Movement.Components; using Content.Shared.Inventory.Events; -namespace Content.Client.Clothing.Systems; +namespace Content.Shared.Clothing.EntitySystems; public sealed class WaddleClothingSystem : EntitySystem { @@ -10,13 +11,13 @@ public sealed class WaddleClothingSystem : EntitySystem { base.Initialize(); - SubscribeLocalEvent(OnGotEquipped); - SubscribeLocalEvent(OnGotUnequipped); + SubscribeLocalEvent(OnGotEquipped); + SubscribeLocalEvent(OnGotUnequipped); } - private void OnGotEquipped(EntityUid entity, WaddleWhenWornComponent comp, GotEquippedEvent args) + private void OnGotEquipped(EntityUid entity, WaddleWhenWornComponent comp, ClothingGotEquippedEvent args) { - var waddleAnimComp = EnsureComp(args.Equipee); + var waddleAnimComp = EnsureComp(args.Wearer); waddleAnimComp.AnimationLength = comp.AnimationLength; waddleAnimComp.HopIntensity = comp.HopIntensity; @@ -24,8 +25,8 @@ public sealed class WaddleClothingSystem : EntitySystem waddleAnimComp.TumbleIntensity = comp.TumbleIntensity; } - private void OnGotUnequipped(EntityUid entity, WaddleWhenWornComponent comp, GotUnequippedEvent args) + private void OnGotUnequipped(EntityUid entity, WaddleWhenWornComponent comp, ClothingGotUnequippedEvent args) { - RemComp(args.Equipee); + RemComp(args.Wearer); } } diff --git a/Content.Shared/Construction/Conditions/EntityWhitelistCondition.cs b/Content.Shared/Construction/Conditions/EntityWhitelistCondition.cs index 22d86b54fb..b7b3c56ba4 100644 --- a/Content.Shared/Construction/Conditions/EntityWhitelistCondition.cs +++ b/Content.Shared/Construction/Conditions/EntityWhitelistCondition.cs @@ -31,7 +31,8 @@ public sealed partial class EntityWhitelistCondition : IConstructionCondition public bool Condition(EntityUid user, EntityCoordinates location, Direction direction) { - return Whitelist.IsValid(user); + var whitelistSystem = IoCManager.Resolve().System(); + return whitelistSystem.IsWhitelistPass(Whitelist, user); } public ConstructionGuideEntry GenerateGuideEntry() diff --git a/Content.Shared/Construction/Conditions/NoWindowsInTile.cs b/Content.Shared/Construction/Conditions/NoWindowsInTile.cs index be6bc2cfac..3ae3b59362 100644 --- a/Content.Shared/Construction/Conditions/NoWindowsInTile.cs +++ b/Content.Shared/Construction/Conditions/NoWindowsInTile.cs @@ -12,13 +12,12 @@ namespace Content.Shared.Construction.Conditions public bool Condition(EntityUid user, EntityCoordinates location, Direction direction) { var entManager = IoCManager.Resolve(); - var tagQuery = entManager.GetEntityQuery(); var sysMan = entManager.EntitySysManager; var tagSystem = sysMan.GetEntitySystem(); foreach (var entity in location.GetEntitiesInTile(LookupFlags.Static)) { - if (tagSystem.HasTag(entity, "Window", tagQuery)) + if (tagSystem.HasTag(entity, "Window")) return false; } diff --git a/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs b/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs index c041cf1ba0..efb5dfd024 100644 --- a/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs +++ b/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs @@ -15,6 +15,7 @@ using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Physics.Components; using Content.Shared.Tag; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Utility; using SharedToolSystem = Content.Shared.Tools.Systems.SharedToolSystem; @@ -32,16 +33,14 @@ public sealed partial class AnchorableSystem : EntitySystem [Dependency] private readonly TagSystem _tagSystem = default!; private EntityQuery _physicsQuery; - private EntityQuery _tagQuery; - public const string Unstackable = "Unstackable"; + public readonly ProtoId Unstackable = "Unstackable"; public override void Initialize() { base.Initialize(); _physicsQuery = GetEntityQuery(); - _tagQuery = GetEntityQuery(); SubscribeLocalEvent(OnInteractUsing, before: new[] { typeof(ItemSlotsSystem) }, after: new[] { typeof(SharedConstructionSystem) }); @@ -80,7 +79,7 @@ public sealed partial class AnchorableSystem : EntitySystem return; // If the used entity doesn't have a tool, return early. - if (!TryComp(args.Used, out ToolComponent? usedTool) || !usedTool.Qualities.Contains(anchorable.Tool)) + if (!TryComp(args.Used, out ToolComponent? usedTool) || !_tool.HasQuality(args.Used, anchorable.Tool, usedTool)) return; args.Handled = true; @@ -312,7 +311,7 @@ public sealed partial class AnchorableSystem : EntitySystem DebugTools.Assert(!Transform(uid).Anchored); // If we are unstackable, iterate through any other entities anchored on the current square - return _tagSystem.HasTag(uid, Unstackable, _tagQuery) && AnyUnstackablesAnchoredAt(location); + return _tagSystem.HasTag(uid, Unstackable) && AnyUnstackablesAnchoredAt(location); } public bool AnyUnstackablesAnchoredAt(EntityCoordinates location) @@ -327,10 +326,8 @@ public sealed partial class AnchorableSystem : EntitySystem while (enumerator.MoveNext(out var entity)) { // If we find another unstackable here, return true. - if (_tagSystem.HasTag(entity.Value, Unstackable, _tagQuery)) - { + if (_tagSystem.HasTag(entity.Value, Unstackable)) return true; - } } return false; diff --git a/Content.Shared/Construction/MachinePartSystem.cs b/Content.Shared/Construction/MachinePartSystem.cs index 1a19040b41..359b58c881 100644 --- a/Content.Shared/Construction/MachinePartSystem.cs +++ b/Content.Shared/Construction/MachinePartSystem.cs @@ -87,9 +87,9 @@ namespace Content.Shared.Construction foreach (var (stackId, amount) in comp.MaterialIdRequirements) { var stackProto = _prototype.Index(stackId); + var defaultProto = _prototype.Index(stackProto.Spawn); - if (_prototype.TryIndex(stackProto.Spawn, out var defaultProto) && - defaultProto.TryGetComponent(out var physComp)) + if (defaultProto.TryGetComponent(out var physComp)) { foreach (var (mat, matAmount) in physComp.MaterialComposition) { diff --git a/Content.Shared/Construction/Steps/MultipleTagsConstructionGraphStep.cs b/Content.Shared/Construction/Steps/MultipleTagsConstructionGraphStep.cs index 668952dac2..07ba46946a 100644 --- a/Content.Shared/Construction/Steps/MultipleTagsConstructionGraphStep.cs +++ b/Content.Shared/Construction/Steps/MultipleTagsConstructionGraphStep.cs @@ -1,14 +1,15 @@ using Content.Shared.Tag; +using Robust.Shared.Prototypes; namespace Content.Shared.Construction.Steps { public sealed partial class MultipleTagsConstructionGraphStep : ArbitraryInsertConstructionGraphStep { [DataField("allTags")] - private List? _allTags; + private List>? _allTags; [DataField("anyTags")] - private List? _anyTags; + private List>? _anyTags; private static bool IsNullOrEmpty(ICollection? list) { @@ -21,16 +22,12 @@ namespace Content.Shared.Construction.Steps if (IsNullOrEmpty(_allTags) && IsNullOrEmpty(_anyTags)) return false; // Step is somehow invalid, we return. - // No tags at all. - if (!entityManager.TryGetComponent(uid, out TagComponent? tags)) - return false; - var tagSystem = entityManager.EntitySysManager.GetEntitySystem(); - if (_allTags != null && !tagSystem.HasAllTags(tags, _allTags)) + if (_allTags != null && !tagSystem.HasAllTags(uid, _allTags)) return false; // We don't have all the tags needed. - if (_anyTags != null && !tagSystem.HasAnyTag(tags, _anyTags)) + if (_anyTags != null && !tagSystem.HasAnyTag(uid, _anyTags)) return false; // We don't have any of the tags needed. // This entity is valid! diff --git a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs index 2e3f9ed461..48f4f07cbe 100644 --- a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs +++ b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs @@ -9,6 +9,7 @@ using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Popups; using Content.Shared.Verbs; +using Content.Shared.Whitelist; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; using Robust.Shared.GameStates; @@ -31,6 +32,7 @@ namespace Content.Shared.Containers.ItemSlots [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedHandsSystem _handsSystem = default!; [Dependency] private readonly SharedAudioSystem _audioSystem = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -266,8 +268,7 @@ namespace Content.Shared.Containers.ItemSlots if (slot.ContainerSlot == null) return false; - if ((!slot.Whitelist?.IsValid(usedUid) ?? false) || - (slot.Blacklist?.IsValid(usedUid) ?? false)) + if (_whitelistSystem.IsWhitelistFail(slot.Whitelist, usedUid) || _whitelistSystem.IsBlacklistPass(slot.Blacklist, usedUid)) { if (popup.HasValue && slot.WhitelistFailPopup.HasValue) _popupSystem.PopupClient(Loc.GetString(slot.WhitelistFailPopup), uid, popup.Value); diff --git a/Content.Shared/Cuffs/SharedCuffableSystem.cs b/Content.Shared/Cuffs/SharedCuffableSystem.cs index f0f9a94983..1ced3c8d6c 100644 --- a/Content.Shared/Cuffs/SharedCuffableSystem.cs +++ b/Content.Shared/Cuffs/SharedCuffableSystem.cs @@ -484,7 +484,7 @@ namespace Content.Shared.Cuffs BreakOnWeightlessMove = false, BreakOnDamage = true, NeedHand = true, - DistanceThreshold = 0.3f + DistanceThreshold = 1f // shorter than default but still feels good }; if (!_doAfter.TryStartDoAfter(doAfterEventArgs)) @@ -581,7 +581,7 @@ namespace Content.Shared.Cuffs BreakOnDamage = true, NeedHand = true, RequireCanInteract = false, // Trust in UncuffAttemptEvent - DistanceThreshold = 0.3f + DistanceThreshold = 1f // shorter than default but still feels good }; if (!_doAfter.TryStartDoAfter(doAfterEventArgs)) diff --git a/Content.Shared/Damage/Components/RequireProjectileTargetComponent.cs b/Content.Shared/Damage/Components/RequireProjectileTargetComponent.cs new file mode 100644 index 0000000000..5bd8292daa --- /dev/null +++ b/Content.Shared/Damage/Components/RequireProjectileTargetComponent.cs @@ -0,0 +1,14 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Damage.Components; + +/// +/// Prevent the object from getting hit by projetiles unless you target the object. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[Access(typeof(RequireProjectileTargetSystem))] +public sealed partial class RequireProjectileTargetComponent : Component +{ + [DataField, AutoNetworkedField] + public bool Active = true; +} diff --git a/Content.Shared/Damage/Systems/DamageContactsSystem.cs b/Content.Shared/Damage/Systems/DamageContactsSystem.cs index aec3d0766a..b08ef77fed 100644 --- a/Content.Shared/Damage/Systems/DamageContactsSystem.cs +++ b/Content.Shared/Damage/Systems/DamageContactsSystem.cs @@ -1,4 +1,5 @@ using Content.Shared.Damage.Components; +using Content.Shared.Whitelist; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Events; using Robust.Shared.Physics.Systems; @@ -11,6 +12,7 @@ public sealed class DamageContactsSystem : EntitySystem [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly DamageableSystem _damageable = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -63,7 +65,7 @@ public sealed class DamageContactsSystem : EntitySystem if (HasComp(otherUid)) return; - if (component.IgnoreWhitelist?.IsValid(otherUid) ?? false) + if (_whitelistSystem.IsWhitelistFail(component.IgnoreWhitelist, otherUid)) return; var damagedByContact = EnsureComp(otherUid); diff --git a/Content.Shared/Damage/Systems/RequireProjectileTargetSystem.cs b/Content.Shared/Damage/Systems/RequireProjectileTargetSystem.cs new file mode 100644 index 0000000000..79b374a60f --- /dev/null +++ b/Content.Shared/Damage/Systems/RequireProjectileTargetSystem.cs @@ -0,0 +1,51 @@ +using Content.Shared.Projectiles; +using Content.Shared.Weapons.Ranged.Components; +using Content.Shared.Standing; +using Robust.Shared.Physics.Events; + +namespace Content.Shared.Damage.Components; + +public sealed class RequireProjectileTargetSystem : EntitySystem +{ + public override void Initialize() + { + SubscribeLocalEvent(PreventCollide); + SubscribeLocalEvent(StandingBulletHit); + SubscribeLocalEvent(LayingBulletPass); + } + + private void PreventCollide(Entity ent, ref PreventCollideEvent args) + { + if (args.Cancelled) + return; + + if (!ent.Comp.Active) + return; + + var other = args.OtherEntity; + if (HasComp(other) && + CompOrNull(other)?.Target != ent) + { + args.Cancelled = true; + } + } + + private void SetActive(Entity ent, bool value) + { + if (ent.Comp.Active == value) + return; + + ent.Comp.Active = value; + Dirty(ent); + } + + private void StandingBulletHit(Entity ent, ref StoodEvent args) + { + SetActive(ent, false); + } + + private void LayingBulletPass(Entity ent, ref DownedEvent args) + { + SetActive(ent, true); + } +} diff --git a/Content.Shared/DeltaV/Shipyard/SharedShipyardConsoleSystem.cs b/Content.Shared/DeltaV/Shipyard/SharedShipyardConsoleSystem.cs index ea35bb92ee..ed04fb4944 100644 --- a/Content.Shared/DeltaV/Shipyard/SharedShipyardConsoleSystem.cs +++ b/Content.Shared/DeltaV/Shipyard/SharedShipyardConsoleSystem.cs @@ -1,6 +1,7 @@ using Content.Shared.Access.Systems; using Content.Shared.Popups; using Content.Shared.Shipyard.Prototypes; +using Content.Shared.Whitelist; using Robust.Shared.Audio.Systems; using Robust.Shared.Prototypes; @@ -16,6 +17,7 @@ public abstract class SharedShipyardConsoleSystem : EntitySystem [Dependency] protected readonly IPrototypeManager _proto = default!; [Dependency] protected readonly SharedAudioSystem Audio = default!; [Dependency] protected readonly SharedPopupSystem Popup = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -37,7 +39,7 @@ public abstract class SharedShipyardConsoleSystem : EntitySystem return; } - if (!_proto.TryIndex(msg.Vessel, out var vessel) || vessel.Whitelist?.IsValid(ent) == false) + if (!_proto.TryIndex(msg.Vessel, out var vessel) || _whitelistSystem.IsWhitelistFail(vessel.Whitelist, ent)) return; TryPurchase(ent, user, vessel); diff --git a/Content.Shared/Devour/SharedDevourSystem.cs b/Content.Shared/Devour/SharedDevourSystem.cs index 3d73b14dd3..14047fba7d 100644 --- a/Content.Shared/Devour/SharedDevourSystem.cs +++ b/Content.Shared/Devour/SharedDevourSystem.cs @@ -4,6 +4,7 @@ using Content.Shared.DoAfter; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.Popups; +using Content.Shared.Whitelist; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; @@ -18,6 +19,7 @@ public abstract class SharedDevourSystem : EntitySystem [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedActionsSystem _actionsSystem = default!; [Dependency] protected readonly SharedContainerSystem ContainerSystem = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -41,7 +43,7 @@ public abstract class SharedDevourSystem : EntitySystem /// protected void OnDevourAction(EntityUid uid, DevourerComponent component, DevourActionEvent args) { - if (args.Handled || component.Whitelist?.IsValid(args.Target, EntityManager) != true) + if (args.Handled || _whitelistSystem.IsWhitelistFailOrNull(component.Whitelist, args.Target)) return; args.Handled = true; diff --git a/Content.Shared/Disposal/SharedDisposalUnitSystem.cs b/Content.Shared/Disposal/SharedDisposalUnitSystem.cs index c39139f9a5..9fdb4a6a80 100644 --- a/Content.Shared/Disposal/SharedDisposalUnitSystem.cs +++ b/Content.Shared/Disposal/SharedDisposalUnitSystem.cs @@ -6,6 +6,7 @@ using Content.Shared.DragDrop; using Content.Shared.Emag.Systems; using Content.Shared.Item; using Content.Shared.Throwing; +using Content.Shared.Whitelist; using Robust.Shared.Audio; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Events; @@ -25,6 +26,7 @@ public abstract class SharedDisposalUnitSystem : EntitySystem [Dependency] protected readonly IGameTiming GameTiming = default!; [Dependency] protected readonly MetaDataSystem Metadata = default!; [Dependency] protected readonly SharedJointSystem Joints = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; protected static TimeSpan ExitAttemptDelay = TimeSpan.FromSeconds(0.5); @@ -113,10 +115,8 @@ public abstract class SharedDisposalUnitSystem : EntitySystem if (!storable && !HasComp(entity)) return false; - if (component.Blacklist?.IsValid(entity, EntityManager) == true) - return false; - - if (component.Whitelist != null && component.Whitelist?.IsValid(entity, EntityManager) != true) + if (_whitelistSystem.IsBlacklistPass(component.Blacklist, entity) || + _whitelistSystem.IsWhitelistFail(component.Whitelist, entity)) return false; if (TryComp(entity, out var physics) && (physics.CanCollide) || storable) diff --git a/Content.Shared/Dragon/SharedDragonRiftComponent.cs b/Content.Shared/Dragon/SharedDragonRiftComponent.cs index 0d2bf44018..8377dbfee7 100644 --- a/Content.Shared/Dragon/SharedDragonRiftComponent.cs +++ b/Content.Shared/Dragon/SharedDragonRiftComponent.cs @@ -1,9 +1,10 @@ using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; namespace Content.Shared.Dragon; -[NetworkedComponent] +[NetworkedComponent, EntityCategory("Spawner")] public abstract partial class SharedDragonRiftComponent : Component { [DataField("state")] diff --git a/Content.Shared/Explosion/Components/ExplosionVisualsComponent.cs b/Content.Shared/Explosion/Components/ExplosionVisualsComponent.cs index 7477b6c26e..88e81c93c5 100644 --- a/Content.Shared/Explosion/Components/ExplosionVisualsComponent.cs +++ b/Content.Shared/Explosion/Components/ExplosionVisualsComponent.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Robust.Shared.GameStates; using Robust.Shared.Map; using Robust.Shared.Serialization; @@ -15,7 +16,7 @@ public sealed partial class ExplosionVisualsComponent : Component public Dictionary>> Tiles = new(); public List Intensity = new(); public string ExplosionType = string.Empty; - public Matrix3 SpaceMatrix; + public Matrix3x2 SpaceMatrix; public ushort SpaceTileSize; } @@ -27,7 +28,7 @@ public sealed class ExplosionVisualsState : ComponentState public Dictionary>> Tiles; public List Intensity; public string ExplosionType = string.Empty; - public Matrix3 SpaceMatrix; + public Matrix3x2 SpaceMatrix; public ushort SpaceTileSize; public ExplosionVisualsState( @@ -36,7 +37,7 @@ public sealed class ExplosionVisualsState : ComponentState List intensity, Dictionary>? spaceTiles, Dictionary>> tiles, - Matrix3 spaceMatrix, + Matrix3x2 spaceMatrix, ushort spaceTileSize) { Epicenter = epicenter; diff --git a/Content.Shared/Explosion/Components/OnUseTimerTriggerComponent.cs b/Content.Shared/Explosion/Components/OnUseTimerTriggerComponent.cs index 5e509cb10b..c4e6e787a4 100644 --- a/Content.Shared/Explosion/Components/OnUseTimerTriggerComponent.cs +++ b/Content.Shared/Explosion/Components/OnUseTimerTriggerComponent.cs @@ -45,5 +45,10 @@ namespace Content.Shared.Explosion.Components /// Whether you can examine the item to see its timer or not. /// [DataField] public bool Examinable = true; + + /// + /// Whether or not to show the user a popup when starting the timer. + /// + [DataField] public bool DoPopup = true; } } diff --git a/Content.Shared/Flash/Components/FlashComponent.cs b/Content.Shared/Flash/Components/FlashComponent.cs index a9098bc85a..29f92eb94f 100644 --- a/Content.Shared/Flash/Components/FlashComponent.cs +++ b/Content.Shared/Flash/Components/FlashComponent.cs @@ -1,6 +1,6 @@ -using Content.Shared.Flash; using Robust.Shared.Audio; using Robust.Shared.GameStates; +using Robust.Shared.Serialization; namespace Content.Shared.Flash.Components { @@ -43,4 +43,13 @@ namespace Content.Shared.Flash.Components [DataField] public float Probability = 1f; } + + [Serializable, NetSerializable] + public enum FlashVisuals : byte + { + BaseLayer, + LightLayer, + Burnt, + Flashing, + } } diff --git a/Content.Shared/Flash/Components/FlashedComponent.cs b/Content.Shared/Flash/Components/FlashedComponent.cs new file mode 100644 index 0000000000..75bbb12304 --- /dev/null +++ b/Content.Shared/Flash/Components/FlashedComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Flash.Components; + +/// +/// Exists for use as a status effect. Adds a shader to the client that obstructs vision. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class FlashedComponent : Component { } diff --git a/Content.Shared/Flash/FlashableComponent.cs b/Content.Shared/Flash/FlashableComponent.cs deleted file mode 100644 index c4f8074cea..0000000000 --- a/Content.Shared/Flash/FlashableComponent.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Content.Shared.Physics; -using Robust.Shared.GameStates; -using Robust.Shared.Serialization; - -namespace Content.Shared.Flash -{ - [RegisterComponent, NetworkedComponent] - public sealed partial class FlashableComponent : Component - { - public float Duration; - public TimeSpan LastFlash; - - [DataField] - public CollisionGroup CollisionGroup = CollisionGroup.Opaque; - - public override bool SendOnlyToOwner => true; - } - - [Serializable, NetSerializable] - public sealed class FlashableComponentState : ComponentState - { - public float Duration { get; } - public TimeSpan Time { get; } - - public FlashableComponentState(float duration, TimeSpan time) - { - Duration = duration; - Time = time; - } - } - - [Serializable, NetSerializable] - public enum FlashVisuals : byte - { - BaseLayer, - LightLayer, - Burnt, - Flashing, - } -} diff --git a/Content.Shared/Flash/SharedFlashSystem.cs b/Content.Shared/Flash/SharedFlashSystem.cs index 16fdbfc2f3..f83f02a310 100644 --- a/Content.Shared/Flash/SharedFlashSystem.cs +++ b/Content.Shared/Flash/SharedFlashSystem.cs @@ -1,19 +1,10 @@ -using Robust.Shared.GameStates; +using Content.Shared.StatusEffect; namespace Content.Shared.Flash { public abstract class SharedFlashSystem : EntitySystem { - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnFlashableGetState); - } - - private static void OnFlashableGetState(EntityUid uid, FlashableComponent component, ref ComponentGetState args) - { - args.State = new FlashableComponentState(component.Duration, component.LastFlash); - } + [ValidatePrototypeId] + public const string FlashedKey = "Flashed"; } } diff --git a/Content.Shared/GameTicking/SharedGameTicker.cs b/Content.Shared/GameTicking/SharedGameTicker.cs index 95da4f4c38..308476baa8 100644 --- a/Content.Shared/GameTicking/SharedGameTicker.cs +++ b/Content.Shared/GameTicking/SharedGameTicker.cs @@ -1,5 +1,6 @@ using Content.Shared.Roles; using Robust.Shared.Network; +using Robust.Shared.Prototypes; using Robust.Shared.Replays; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Markdown.Mapping; @@ -128,19 +129,17 @@ namespace Content.Shared.GameTicking } [Serializable, NetSerializable] - public sealed class TickerJobsAvailableEvent : EntityEventArgs + public sealed class TickerJobsAvailableEvent( + Dictionary stationNames, + Dictionary, int?>> jobsAvailableByStation) + : EntityEventArgs { /// /// The Status of the Player in the lobby (ready, observer, ...) /// - public Dictionary> JobsAvailableByStation { get; } - public Dictionary StationNames { get; } + public Dictionary, int?>> JobsAvailableByStation { get; } = jobsAvailableByStation; - public TickerJobsAvailableEvent(Dictionary stationNames, Dictionary> jobsAvailableByStation) - { - StationNames = stationNames; - JobsAvailableByStation = jobsAvailableByStation; - } + public Dictionary StationNames { get; } = stationNames; } [Serializable, NetSerializable, DataDefinition] diff --git a/Content.Shared/Ghost/Roles/GhostRolePrototype.cs b/Content.Shared/Ghost/Roles/GhostRolePrototype.cs index 43d6432250..bc36774ea8 100644 --- a/Content.Shared/Ghost/Roles/GhostRolePrototype.cs +++ b/Content.Shared/Ghost/Roles/GhostRolePrototype.cs @@ -15,24 +15,24 @@ public sealed partial class GhostRolePrototype : IPrototype /// /// The name of the ghostrole. /// - [DataField] + [DataField(required: true)] public string Name { get; set; } = default!; /// /// The description of the ghostrole. /// - [DataField] + [DataField(required: true)] public string Description { get; set; } = default!; /// /// The entity prototype of the ghostrole /// - [DataField] - public string EntityPrototype = default!; + [DataField(required: true)] + public EntProtoId EntityPrototype; /// /// Rules of the ghostrole /// - [DataField] + [DataField(required: true)] public string Rules = default!; -} \ No newline at end of file +} diff --git a/Content.Shared/Ghost/Roles/GhostRolesEuiMessages.cs b/Content.Shared/Ghost/Roles/GhostRolesEuiMessages.cs index b7457538eb..b5d8fedbd9 100644 --- a/Content.Shared/Ghost/Roles/GhostRolesEuiMessages.cs +++ b/Content.Shared/Ghost/Roles/GhostRolesEuiMessages.cs @@ -11,6 +11,11 @@ namespace Content.Shared.Ghost.Roles public string Name { get; set; } public string Description { get; set; } public string Rules { get; set; } + + // TODO ROLE TIMERS + // Actually make use of / enforce this requirement? + // Why is this even here. + // Move to ghost role prototype & respect CCvars.GameRoleTimerOverride public HashSet? Requirements { get; set; } /// diff --git a/Content.Shared/Implants/SharedImplanterSystem.cs b/Content.Shared/Implants/SharedImplanterSystem.cs index d78522b56c..44803e721c 100644 --- a/Content.Shared/Implants/SharedImplanterSystem.cs +++ b/Content.Shared/Implants/SharedImplanterSystem.cs @@ -20,6 +20,7 @@ public abstract class SharedImplanterSystem : EntitySystem [Dependency] private readonly ItemSlotsSystem _itemSlots = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -105,8 +106,8 @@ public abstract class SharedImplanterSystem : EntitySystem protected bool CheckTarget(EntityUid target, EntityWhitelist? whitelist, EntityWhitelist? blacklist) { - return whitelist?.IsValid(target, EntityManager) != false && - blacklist?.IsValid(target, EntityManager) != true; + return _whitelistSystem.IsWhitelistPassOrNull(whitelist, target) && + _whitelistSystem.IsBlacklistFailOrNull(blacklist, target); } //Draw the implant out of the target diff --git a/Content.Shared/Interaction/Events/GettingAttackedAttemptEvent.cs b/Content.Shared/Interaction/Events/GettingAttackedAttemptEvent.cs index ed7379fd72..d37c810e3f 100644 --- a/Content.Shared/Interaction/Events/GettingAttackedAttemptEvent.cs +++ b/Content.Shared/Interaction/Events/GettingAttackedAttemptEvent.cs @@ -4,4 +4,4 @@ namespace Content.Shared.Interaction.Events; /// Raised directed on the target entity when being attacked. /// [ByRefEvent] -public record struct GettingAttackedAttemptEvent(bool Cancelled); +public record struct GettingAttackedAttemptEvent(EntityUid Attacker, EntityUid? Weapon, bool Disarm, bool Cancelled = false); diff --git a/Content.Shared/Interaction/SmartEquipSystem.cs b/Content.Shared/Interaction/SmartEquipSystem.cs index fb2bc3c460..bba294db28 100644 --- a/Content.Shared/Interaction/SmartEquipSystem.cs +++ b/Content.Shared/Interaction/SmartEquipSystem.cs @@ -1,4 +1,4 @@ -using Content.Shared.ActionBlocker; +using Content.Shared.ActionBlocker; using Content.Shared.Containers.ItemSlots; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; @@ -7,6 +7,7 @@ using Content.Shared.Inventory; using Content.Shared.Popups; using Content.Shared.Storage; using Content.Shared.Storage.EntitySystems; +using Content.Shared.Whitelist; using Robust.Shared.Containers; using Robust.Shared.Input.Binding; using Robust.Shared.Player; @@ -25,6 +26,7 @@ public sealed class SmartEquipSystem : EntitySystem [Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; /// public override void Initialize() @@ -182,7 +184,7 @@ public sealed class SmartEquipSystem : EntitySystem foreach (var slot in slots.Slots.Values) { if (!slot.HasItem - && (slot.Whitelist?.IsValid(handItem.Value, EntityManager) ?? true) + && _whitelistSystem.IsWhitelistPassOrNull(slot.Whitelist, handItem.Value) && slot.Priority > (toInsertTo?.Priority ?? int.MinValue)) { toInsertTo = slot; diff --git a/Content.Shared/Inventory/InventorySystem.Equip.cs b/Content.Shared/Inventory/InventorySystem.Equip.cs index 7fd156213b..7acfafee4a 100644 --- a/Content.Shared/Inventory/InventorySystem.Equip.cs +++ b/Content.Shared/Inventory/InventorySystem.Equip.cs @@ -11,6 +11,7 @@ using Content.Shared.Item; using Content.Shared.Movement.Systems; using Content.Shared.Popups; using Content.Shared.Strip.Components; +using Content.Shared.Whitelist; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; using Robust.Shared.Timing; @@ -30,6 +31,7 @@ public abstract partial class InventorySystem [Dependency] private readonly SharedHandsSystem _handsSystem = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; [ValidatePrototypeId] private const string PocketableItemSize = "Small"; @@ -267,13 +269,8 @@ public abstract partial class InventorySystem return false; } - if (slotDefinition.Whitelist != null && !slotDefinition.Whitelist.IsValid(itemUid)) - { - reason = "inventory-component-can-equip-does-not-fit"; - return false; - } - - if (slotDefinition.Blacklist != null && slotDefinition.Blacklist.IsValid(itemUid)) + if (_whitelistSystem.IsWhitelistFail(slotDefinition.Whitelist, itemUid) || + _whitelistSystem.IsBlacklistPass(slotDefinition.Blacklist, itemUid)) { reason = "inventory-component-can-equip-does-not-fit"; return false; diff --git a/Content.Shared/Item/SharedItemSystem.cs b/Content.Shared/Item/SharedItemSystem.cs index 29e82f8ade..5eaa25f484 100644 --- a/Content.Shared/Item/SharedItemSystem.cs +++ b/Content.Shared/Item/SharedItemSystem.cs @@ -192,7 +192,7 @@ public abstract class SharedItemSystem : EntitySystem var shapes = GetItemShape(entity); var boundingShape = shapes.GetBoundingBox(); var boundingCenter = ((Box2) boundingShape).Center; - var matty = Matrix3.CreateTransform(boundingCenter, rotation); + var matty = Matrix3Helpers.CreateTransform(boundingCenter, rotation); var drift = boundingShape.BottomLeft - matty.TransformBox(boundingShape).BottomLeft; var adjustedShapes = new List(); diff --git a/Content.Shared/Labels/EntitySystems/SharedHandLabelerSystem.cs b/Content.Shared/Labels/EntitySystems/SharedHandLabelerSystem.cs index 618568c8e3..1c575e45c1 100644 --- a/Content.Shared/Labels/EntitySystems/SharedHandLabelerSystem.cs +++ b/Content.Shared/Labels/EntitySystems/SharedHandLabelerSystem.cs @@ -5,6 +5,7 @@ using Content.Shared.Labels.Components; using Content.Shared.Popups; using Content.Shared.Tag; using Content.Shared.Verbs; +using Content.Shared.Whitelist; using Robust.Shared.GameStates; using Robust.Shared.Network; @@ -17,6 +18,7 @@ public abstract class SharedHandLabelerSystem : EntitySystem [Dependency] private readonly SharedLabelSystem _labelSystem = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly INetManager _netManager = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; [Dependency] private readonly TagSystem _tagSystem = default!; [ValidatePrototypeId] @@ -84,7 +86,7 @@ public abstract class SharedHandLabelerSystem : EntitySystem private void OnUtilityVerb(EntityUid uid, HandLabelerComponent handLabeler, GetVerbsEvent args) { - if (args.Target is not { Valid: true } target || !handLabeler.Whitelist.IsValid(target) || !args.CanAccess) + if (args.Target is not { Valid: true } target || _whitelistSystem.IsWhitelistFail(handLabeler.Whitelist, target) || !args.CanAccess) return; var labelerText = handLabeler.AssignedLabel == string.Empty ? Loc.GetString("hand-labeler-remove-label-text") : Loc.GetString("hand-labeler-add-label-text"); @@ -103,7 +105,7 @@ public abstract class SharedHandLabelerSystem : EntitySystem private void AfterInteractOn(EntityUid uid, HandLabelerComponent handLabeler, AfterInteractEvent args) { - if (args.Target is not { Valid: true } target || !handLabeler.Whitelist.IsValid(target) || !args.CanReach) + if (args.Target is not { Valid: true } target || _whitelistSystem.IsWhitelistFail(handLabeler.Whitelist, target) || !args.CanReach) return; Labeling(uid, target, args.User, handLabeler); diff --git a/Content.Shared/Maps/TurfSystem.cs b/Content.Shared/Maps/TurfSystem.cs index ad8b3ddea8..8a4bbf68be 100644 --- a/Content.Shared/Maps/TurfSystem.cs +++ b/Content.Shared/Maps/TurfSystem.cs @@ -44,7 +44,7 @@ public sealed class TurfSystem : EntitySystem var size = grid.TileSize; var localPos = new Vector2(indices.X * size + (size / 2f), indices.Y * size + (size / 2f)); - var worldPos = matrix.Transform(localPos); + var worldPos = Vector2.Transform(localPos, matrix); // This is scaled to 95 % so it doesn't encompass walls on other tiles. var tileAabb = Box2.UnitCentered.Scale(0.95f * size); diff --git a/Content.Shared/Materials/MaterialPrototype.cs b/Content.Shared/Materials/MaterialPrototype.cs index 905a2359d3..5adf13213e 100644 --- a/Content.Shared/Materials/MaterialPrototype.cs +++ b/Content.Shared/Materials/MaterialPrototype.cs @@ -29,7 +29,7 @@ namespace Content.Shared.Materials /// include which stack we should spawn by default. /// [DataField] - public ProtoId? StackEntity; + public EntProtoId? StackEntity; [DataField] public string Name = string.Empty; diff --git a/Content.Shared/Materials/SharedMaterialReclaimerSystem.cs b/Content.Shared/Materials/SharedMaterialReclaimerSystem.cs index 50dce3c766..8641dbb3b8 100644 --- a/Content.Shared/Materials/SharedMaterialReclaimerSystem.cs +++ b/Content.Shared/Materials/SharedMaterialReclaimerSystem.cs @@ -9,6 +9,7 @@ using Content.Shared.Emag.Systems; using Content.Shared.Examine; using Content.Shared.Mobs.Components; using Content.Shared.Stacks; +using Content.Shared.Whitelist; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; @@ -29,6 +30,7 @@ public abstract class SharedMaterialReclaimerSystem : EntitySystem [Dependency] protected readonly SharedAmbientSoundSystem AmbientSound = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] protected readonly SharedContainerSystem Container = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public const string ActiveReclaimerContainerId = "active-material-reclaimer-container"; @@ -91,10 +93,8 @@ public abstract class SharedMaterialReclaimerSystem : EntitySystem if (HasComp(item) && !CanGib(uid, item, component)) // whitelist? We be gibbing, boy! return false; - if (component.Whitelist is {} whitelist && !whitelist.IsValid(item)) - return false; - - if (component.Blacklist is {} blacklist && blacklist.IsValid(item)) + if (_whitelistSystem.IsWhitelistFail(component.Whitelist, item) || + _whitelistSystem.IsBlacklistPass(component.Blacklist, item)) return false; if (Container.TryGetContainingContainer(item, out _) && !Container.TryRemoveFromContainer(item)) diff --git a/Content.Shared/Materials/SharedMaterialStorageSystem.cs b/Content.Shared/Materials/SharedMaterialStorageSystem.cs index b1de77d971..a27e0fb9cf 100644 --- a/Content.Shared/Materials/SharedMaterialStorageSystem.cs +++ b/Content.Shared/Materials/SharedMaterialStorageSystem.cs @@ -2,6 +2,7 @@ using System.Linq; using Content.Shared.Interaction; using Content.Shared.Interaction.Components; using Content.Shared.Stacks; +using Content.Shared.Whitelist; using JetBrains.Annotations; using Robust.Shared.Prototypes; using Robust.Shared.Timing; @@ -17,6 +18,7 @@ public abstract class SharedMaterialStorageSystem : EntitySystem [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; /// /// Default volume for a sheet if the material's entity prototype has no material composition. @@ -121,7 +123,7 @@ public abstract class SharedMaterialStorageSystem : EntitySystem if (!CanTakeVolume(uid, volume, component)) return false; - if (component.MaterialWhiteList != null && !component.MaterialWhiteList.Contains(materialId)) + if (component.MaterialWhiteList == null ? false : !component.MaterialWhiteList.Contains(materialId)) return false; var amount = component.Storage.GetValueOrDefault(materialId); @@ -239,7 +241,7 @@ public abstract class SharedMaterialStorageSystem : EntitySystem if (!Resolve(toInsert, ref material, ref composition, false)) return false; - if (storage.Whitelist?.IsValid(toInsert) == false) + if (_whitelistSystem.IsWhitelistFail(storage.Whitelist, toInsert)) return false; if (HasComp(toInsert)) diff --git a/Content.Shared/Mech/EntitySystems/SharedMechSystem.cs b/Content.Shared/Mech/EntitySystems/SharedMechSystem.cs index 73b7c0847f..2ec48085c4 100644 --- a/Content.Shared/Mech/EntitySystems/SharedMechSystem.cs +++ b/Content.Shared/Mech/EntitySystems/SharedMechSystem.cs @@ -15,6 +15,7 @@ using Content.Shared.Movement.Components; using Content.Shared.Movement.Systems; using Content.Shared.Popups; using Content.Shared.Weapons.Melee; +using Content.Shared.Whitelist; using Robust.Shared.Containers; using Robust.Shared.Network; using Robust.Shared.Serialization; @@ -37,6 +38,7 @@ public abstract class SharedMechSystem : EntitySystem [Dependency] private readonly SharedMoverController _mover = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; /// public override void Initialize() @@ -216,7 +218,7 @@ public abstract class SharedMechSystem : EntitySystem if (component.EquipmentContainer.ContainedEntities.Count >= component.MaxEquipmentAmount) return; - if (component.EquipmentWhitelist != null && !component.EquipmentWhitelist.IsValid(toInsert)) + if (_whitelistSystem.IsWhitelistFail(component.EquipmentWhitelist, toInsert)) return; equipmentComponent.EquipmentOwner = uid; diff --git a/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs b/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs index ee747554e1..08b351e61e 100644 --- a/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs +++ b/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs @@ -46,7 +46,6 @@ public partial class MobStateSystem SubscribeLocalEvent(OnSleepAttempt); SubscribeLocalEvent(OnCombatModeShouldHandInteract); SubscribeLocalEvent(OnAttemptPacifiedAttack); - SubscribeLocalEvent(OnPreventCollide); } private void OnStateExitSubscribers(EntityUid target, MobStateComponent component, MobState state) @@ -179,21 +178,5 @@ public partial class MobStateSystem args.Cancelled = true; } - private void OnPreventCollide(Entity ent, ref PreventCollideEvent args) - { - if (args.Cancelled) - return; - - if (IsAlive(ent, ent)) - return; - - var other = args.OtherEntity; - if (HasComp(other) && - CompOrNull(other)?.Target != ent.Owner) - { - args.Cancelled = true; - } - } - #endregion } diff --git a/Content.Shared/Movement/Components/WaddleAnimationComponent.cs b/Content.Shared/Movement/Components/WaddleAnimationComponent.cs index c43ef3042e..3cd9a3749e 100644 --- a/Content.Shared/Movement/Components/WaddleAnimationComponent.cs +++ b/Content.Shared/Movement/Components/WaddleAnimationComponent.cs @@ -1,31 +1,32 @@ using System.Numerics; +using Robust.Shared.Serialization; namespace Content.Shared.Movement.Components; /// /// Declares that an entity has started to waddle like a duck/clown. /// -/// The newly be-waddled. -[ByRefEvent] -public record struct StartedWaddlingEvent(EntityUid Entity) +/// The newly be-waddled. +[Serializable, NetSerializable] +public sealed class StartedWaddlingEvent(NetEntity entity) : EntityEventArgs { - public EntityUid Entity = Entity; + public NetEntity Entity = entity; } /// /// Declares that an entity has stopped waddling like a duck/clown. /// -/// The former waddle-er. -[ByRefEvent] -public record struct StoppedWaddlingEvent(EntityUid Entity) +/// The former waddle-er. +[Serializable, NetSerializable] +public sealed class StoppedWaddlingEvent(NetEntity entity) : EntityEventArgs { - public EntityUid Entity = Entity; + public NetEntity Entity = entity; } /// /// Defines something as having a waddle animation when it moves. /// -[RegisterComponent] +[RegisterComponent, AutoGenerateComponentState] public sealed partial class WaddleAnimationComponent : Component { /// @@ -38,26 +39,26 @@ public sealed partial class WaddleAnimationComponent : Component /// /// How high should they hop during the waddle? Higher hop = more energy. /// - [DataField] + [DataField, AutoNetworkedField] public Vector2 HopIntensity = new(0, 0.25f); /// /// How far should they rock backward and forward during the waddle? /// Each step will alternate between this being a positive and negative rotation. More rock = more scary. /// - [DataField] + [DataField, AutoNetworkedField] public float TumbleIntensity = 20.0f; /// /// How long should a complete step take? Less time = more chaos. /// - [DataField] + [DataField, AutoNetworkedField] public float AnimationLength = 0.66f; /// /// How much shorter should the animation be when running? /// - [DataField] + [DataField, AutoNetworkedField] public float RunAnimationLengthMultiplier = 0.568f; /// @@ -68,5 +69,6 @@ public sealed partial class WaddleAnimationComponent : Component /// /// Stores if we're currently waddling so we can start/stop as appropriate and can tell other systems our state. /// + [AutoNetworkedField] public bool IsCurrentlyWaddling; } diff --git a/Content.Shared/Movement/Systems/SharedWaddleAnimationSystem.cs b/Content.Shared/Movement/Systems/SharedWaddleAnimationSystem.cs new file mode 100644 index 0000000000..2fcb4fc60b --- /dev/null +++ b/Content.Shared/Movement/Systems/SharedWaddleAnimationSystem.cs @@ -0,0 +1,106 @@ +using Content.Shared.Buckle.Components; +using Content.Shared.Gravity; +using Content.Shared.Movement.Components; +using Content.Shared.Movement.Events; +using Content.Shared.Movement.Systems; +using Content.Shared.Standing; +using Content.Shared.Stunnable; +using Robust.Shared.Timing; + +namespace Content.Shared.Movement.Systems; + +public abstract class SharedWaddleAnimationSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + + public override void Initialize() + { + // Startup + SubscribeLocalEvent(OnComponentStartup); + + // Start moving possibilities + SubscribeLocalEvent(OnMovementInput); + SubscribeLocalEvent(OnStood); + + // Stop moving possibilities + SubscribeLocalEvent((Entity ent, ref StunnedEvent _) => StopWaddling(ent)); + SubscribeLocalEvent((Entity ent, ref DownedEvent _) => StopWaddling(ent)); + SubscribeLocalEvent((Entity ent, ref BuckleChangeEvent _) => StopWaddling(ent)); + SubscribeLocalEvent(OnGravityChanged); + } + + private void OnGravityChanged(Entity ent, ref GravityChangedEvent args) + { + if (!args.HasGravity && ent.Comp.IsCurrentlyWaddling) + StopWaddling(ent); + } + + private void OnComponentStartup(Entity entity, ref ComponentStartup args) + { + if (!TryComp(entity.Owner, out var moverComponent)) + return; + + // If the waddler is currently moving, make them start waddling + if ((moverComponent.HeldMoveButtons & MoveButtons.AnyDirection) == MoveButtons.AnyDirection) + { + RaiseNetworkEvent(new StartedWaddlingEvent(GetNetEntity(entity.Owner))); + } + } + + private void OnMovementInput(Entity entity, ref MoveInputEvent args) + { + // Prediction mitigation. Prediction means that MoveInputEvents are spammed repeatedly, even though you'd assume + // they're once-only for the user actually doing something. As such do nothing if we're just repeating this FoR. + if (!_timing.IsFirstTimePredicted) + { + return; + } + + if (!args.HasDirectionalMovement && entity.Comp.IsCurrentlyWaddling) + { + StopWaddling(entity); + + return; + } + + // Only start waddling if we're not currently AND we're actually moving. + if (entity.Comp.IsCurrentlyWaddling || !args.HasDirectionalMovement) + return; + + entity.Comp.IsCurrentlyWaddling = true; + + RaiseNetworkEvent(new StartedWaddlingEvent(GetNetEntity(entity.Owner))); + } + + private void OnStood(Entity entity, ref StoodEvent args) + { + // Prediction mitigation. Prediction means that MoveInputEvents are spammed repeatedly, even though you'd assume + // they're once-only for the user actually doing something. As such do nothing if we're just repeating this FoR. + if (!_timing.IsFirstTimePredicted) + { + return; + } + + if (!TryComp(entity.Owner, out var mover)) + { + return; + } + + if ((mover.HeldMoveButtons & MoveButtons.AnyDirection) == MoveButtons.None) + return; + + if (entity.Comp.IsCurrentlyWaddling) + return; + + entity.Comp.IsCurrentlyWaddling = true; + + RaiseNetworkEvent(new StartedWaddlingEvent(GetNetEntity(entity.Owner))); + } + + private void StopWaddling(Entity entity) + { + entity.Comp.IsCurrentlyWaddling = false; + + RaiseNetworkEvent(new StoppedWaddlingEvent(GetNetEntity(entity.Owner))); + } +} diff --git a/Content.Shared/Movement/Systems/SpeedModifierContactsSystem.cs b/Content.Shared/Movement/Systems/SpeedModifierContactsSystem.cs index 400a675cd2..6e1b3a29ae 100644 --- a/Content.Shared/Movement/Systems/SpeedModifierContactsSystem.cs +++ b/Content.Shared/Movement/Systems/SpeedModifierContactsSystem.cs @@ -1,4 +1,5 @@ using Content.Shared.Movement.Components; +using Content.Shared.Whitelist; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Events; using Robust.Shared.Physics.Systems; @@ -9,6 +10,7 @@ public sealed class SpeedModifierContactsSystem : EntitySystem { [Dependency] private readonly SharedPhysicsSystem _physics = default!; [Dependency] private readonly MovementSpeedModifierSystem _speedModifierSystem = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; // TODO full-game-save // Either these need to be processed before a map is saved, or slowed/slowing entities need to update on init. @@ -86,7 +88,7 @@ public sealed class SpeedModifierContactsSystem : EntitySystem if (!TryComp(ent, out var slowContactsComponent)) continue; - if (slowContactsComponent.IgnoreWhitelist != null && slowContactsComponent.IgnoreWhitelist.IsValid(uid)) + if (_whitelistSystem.IsWhitelistPass(slowContactsComponent.IgnoreWhitelist, uid)) continue; walkSpeed += slowContactsComponent.WalkSpeedModifier; diff --git a/Content.Shared/Ninja/Systems/EmagProviderSystem.cs b/Content.Shared/Ninja/Systems/EmagProviderSystem.cs index df9cf8ac82..6838e7982c 100644 --- a/Content.Shared/Ninja/Systems/EmagProviderSystem.cs +++ b/Content.Shared/Ninja/Systems/EmagProviderSystem.cs @@ -17,6 +17,7 @@ public sealed class EmagProviderSystem : EntitySystem [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly SharedNinjaGlovesSystem _gloves = default!; [Dependency] private readonly TagSystem _tags = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -35,7 +36,7 @@ public sealed class EmagProviderSystem : EntitySystem return; // only allowed to emag entities on the whitelist - if (comp.Whitelist != null && !comp.Whitelist.IsValid(target, EntityManager)) + if (_whitelistSystem.IsWhitelistFail(comp.Whitelist, target)) return; // only allowed to emag non-immune entities diff --git a/Content.Shared/Objectives/Components/ObjectiveComponent.cs b/Content.Shared/Objectives/Components/ObjectiveComponent.cs index 95fbc68561..36d3fa0bde 100644 --- a/Content.Shared/Objectives/Components/ObjectiveComponent.cs +++ b/Content.Shared/Objectives/Components/ObjectiveComponent.cs @@ -2,6 +2,7 @@ using Content.Shared.Mind; using Content.Shared.Objectives; using Content.Shared.Objectives.Systems; using Robust.Shared.Utility; +using Robust.Shared.Prototypes; namespace Content.Shared.Objectives.Components; @@ -9,32 +10,33 @@ namespace Content.Shared.Objectives.Components; /// Required component for an objective entity prototype. /// [RegisterComponent, Access(typeof(SharedObjectivesSystem))] +[EntityCategory("Objectives")] public sealed partial class ObjectiveComponent : Component { /// /// Difficulty rating used to avoid assigning too many difficult objectives. /// - [DataField(required: true), ViewVariables(VVAccess.ReadWrite)] + [DataField(required: true)] public float Difficulty; /// /// Organisation that issued this objective, used for grouping and as a header above common objectives. /// - [DataField(required: true), ViewVariables(VVAccess.ReadWrite)] + [DataField(required: true)] public string Issuer = string.Empty; /// /// Unique objectives can only have 1 per prototype id. /// Set this to false if you want multiple objectives of the same prototype. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public bool Unique = true; /// /// Icon of this objective to display in the character menu. /// Can be specified by an handler but is usually done in the prototype. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public SpriteSpecifier? Icon; } diff --git a/Content.Shared/Overlays/ShowHealthBarsComponent.cs b/Content.Shared/Overlays/ShowHealthBarsComponent.cs index 48e3162269..4229e27af6 100644 --- a/Content.Shared/Overlays/ShowHealthBarsComponent.cs +++ b/Content.Shared/Overlays/ShowHealthBarsComponent.cs @@ -1,5 +1,7 @@ using Content.Shared.Damage.Prototypes; +using Content.Shared.StatusIcon; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; namespace Content.Shared.Overlays; @@ -15,4 +17,7 @@ public sealed partial class ShowHealthBarsComponent : Component /// [DataField("damageContainers", customTypeSerializer: typeof(PrototypeIdListSerializer))] public List DamageContainers = new(); + + [DataField] + public ProtoId? HealthStatusIcon = "HealthIconFine"; } diff --git a/Content.Shared/Pinpointer/SharedNavMapSystem.cs b/Content.Shared/Pinpointer/SharedNavMapSystem.cs index 7c12321b5d..3ced5f3c9e 100644 --- a/Content.Shared/Pinpointer/SharedNavMapSystem.cs +++ b/Content.Shared/Pinpointer/SharedNavMapSystem.cs @@ -3,6 +3,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using Content.Shared.Tag; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Timing; using Robust.Shared.Utility; @@ -24,7 +25,7 @@ public abstract class SharedNavMapSystem : EntitySystem [Robust.Shared.IoC.Dependency] private readonly TagSystem _tagSystem = default!; - private readonly string[] _wallTags = ["Wall", "Window"]; + private static readonly ProtoId[] WallTags = {"Wall", "Window"}; private EntityQuery _doorQuery; public override void Initialize() @@ -58,7 +59,7 @@ public abstract class SharedNavMapSystem : EntitySystem if (_doorQuery.HasComp(uid)) return NavMapChunkType.Airlock; - if (_tagSystem.HasAnyTag(uid, _wallTags)) + if (_tagSystem.HasAnyTag(uid, WallTags)) return NavMapChunkType.Wall; return NavMapChunkType.Invalid; diff --git a/Content.Shared/Placeable/ItemPlacerSystem.cs b/Content.Shared/Placeable/ItemPlacerSystem.cs index 9be6a4acd5..d1ea88b82a 100644 --- a/Content.Shared/Placeable/ItemPlacerSystem.cs +++ b/Content.Shared/Placeable/ItemPlacerSystem.cs @@ -1,4 +1,5 @@ -using Robust.Shared.Physics.Events; +using Content.Shared.Whitelist; +using Robust.Shared.Physics.Events; using Robust.Shared.Physics.Systems; namespace Content.Shared.Placeable; @@ -11,6 +12,7 @@ public sealed class ItemPlacerSystem : EntitySystem { [Dependency] private readonly CollisionWakeSystem _wake = default!; [Dependency] private readonly PlaceableSurfaceSystem _placeableSurface = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -22,7 +24,7 @@ public sealed class ItemPlacerSystem : EntitySystem private void OnStartCollide(EntityUid uid, ItemPlacerComponent comp, ref StartCollideEvent args) { - if (comp.Whitelist != null && !comp.Whitelist.IsValid(args.OtherEntity)) + if (_whitelistSystem.IsWhitelistFail(comp.Whitelist, args.OtherEntity)) return; if (TryComp(args.OtherEntity, out var wakeComp)) diff --git a/Content.Shared/Polymorph/Systems/SharedChameleonProjectorSystem.cs b/Content.Shared/Polymorph/Systems/SharedChameleonProjectorSystem.cs index cd0799a6d7..6883530377 100644 --- a/Content.Shared/Polymorph/Systems/SharedChameleonProjectorSystem.cs +++ b/Content.Shared/Polymorph/Systems/SharedChameleonProjectorSystem.cs @@ -16,6 +16,7 @@ using Robust.Shared.Physics.Components; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.Manager; using System.Diagnostics.CodeAnalysis; +using Content.Shared.Whitelist; namespace Content.Shared.Polymorph.Systems; @@ -35,6 +36,7 @@ public abstract class SharedChameleonProjectorSystem : EntitySystem [Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedTransformSystem _xform = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -160,8 +162,8 @@ public abstract class SharedChameleonProjectorSystem : EntitySystem /// public bool IsInvalid(ChameleonProjectorComponent comp, EntityUid target) { - return (comp.Whitelist?.IsValid(target, EntityManager) == false) - || (comp.Blacklist?.IsValid(target, EntityManager) == true); + return _whitelistSystem.IsWhitelistFail(comp.Whitelist, target) + || _whitelistSystem.IsBlacklistPass(comp.Blacklist, target); } /// diff --git a/Content.Shared/Prayer/PrayableComponent.cs b/Content.Shared/Prayer/PrayableComponent.cs index 71251af810..b6dcc1c210 100644 --- a/Content.Shared/Prayer/PrayableComponent.cs +++ b/Content.Shared/Prayer/PrayableComponent.cs @@ -26,7 +26,7 @@ public sealed partial class PrayableComponent : Component /// /// Prefix used in the notification to admins /// - [DataField("notifiactionPrefix")] + [DataField("notificationPrefix")] [ViewVariables(VVAccess.ReadWrite)] public string NotificationPrefix = "prayer-chat-notify-pray"; diff --git a/Content.Shared/Preferences/HumanoidCharacterProfile.cs b/Content.Shared/Preferences/HumanoidCharacterProfile.cs index 682b0d700f..419d78614e 100644 --- a/Content.Shared/Preferences/HumanoidCharacterProfile.cs +++ b/Content.Shared/Preferences/HumanoidCharacterProfile.cs @@ -44,7 +44,7 @@ namespace Content.Shared.Preferences /// Job preferences for initial spawn. /// [DataField] - private Dictionary _jobPriorities = new() + private Dictionary, JobPriority> _jobPriorities = new() { { SharedGameTicker.FallbackOverflowJob, JobPriority.High @@ -55,13 +55,13 @@ namespace Content.Shared.Preferences /// Antags we have opted in to. /// [DataField] - private HashSet _antagPreferences = new(); + private HashSet> _antagPreferences = new(); /// /// Enabled traits. /// [DataField] - private HashSet _traitPreferences = new(); + private HashSet> _traitPreferences = new(); /// /// @@ -84,7 +84,7 @@ namespace Content.Shared.Preferences /// Associated for this profile. /// [DataField] - public string Species { get; set; } = SharedHumanoidAppearanceSystem.DefaultSpecies; + public ProtoId Species { get; set; } = SharedHumanoidAppearanceSystem.DefaultSpecies; [DataField] public int Age { get; set; } = 18; @@ -115,17 +115,17 @@ namespace Content.Shared.Preferences /// /// /// - public IReadOnlyDictionary JobPriorities => _jobPriorities; + public IReadOnlyDictionary, JobPriority> JobPriorities => _jobPriorities; /// /// /// - public IReadOnlySet AntagPreferences => _antagPreferences; + public IReadOnlySet> AntagPreferences => _antagPreferences; /// /// /// - public IReadOnlySet TraitPreferences => _traitPreferences; + public IReadOnlySet> TraitPreferences => _traitPreferences; /// /// If we're unable to get one of our preferred jobs do we spawn as a fallback job or do we stay in lobby. @@ -143,10 +143,10 @@ namespace Content.Shared.Preferences Gender gender, HumanoidCharacterAppearance appearance, SpawnPriorityPreference spawnPriority, - Dictionary jobPriorities, + Dictionary, JobPriority> jobPriorities, PreferenceUnavailableMode preferenceUnavailable, - HashSet antagPreferences, - HashSet traitPreferences, + HashSet> antagPreferences, + HashSet> traitPreferences, Dictionary loadouts) { Name = name; @@ -162,6 +162,20 @@ namespace Content.Shared.Preferences _antagPreferences = antagPreferences; _traitPreferences = traitPreferences; _loadouts = loadouts; + + var hasHighPrority = false; + foreach (var (key, value) in _jobPriorities) + { + if (value == JobPriority.Never) + _jobPriorities.Remove(key); + else if (value != JobPriority.High) + continue; + + if (hasHighPrority) + _jobPriorities[key] = JobPriority.Medium; + + hasHighPrority = true; + } } /// Copy constructor @@ -174,10 +188,10 @@ namespace Content.Shared.Preferences other.Gender, other.Appearance.Clone(), other.SpawnPriority, - new Dictionary(other.JobPriorities), + new Dictionary, JobPriority>(other.JobPriorities), other.PreferenceUnavailable, - new HashSet(other.AntagPreferences), - new HashSet(other.TraitPreferences), + new HashSet>(other.AntagPreferences), + new HashSet>(other.TraitPreferences), new Dictionary(other.Loadouts)) { } @@ -298,21 +312,48 @@ namespace Content.Shared.Preferences return new(this) { SpawnPriority = spawnPriority }; } - public HumanoidCharacterProfile WithJobPriorities(IEnumerable> jobPriorities) + public HumanoidCharacterProfile WithJobPriorities(IEnumerable, JobPriority>> jobPriorities) { + var dictionary = new Dictionary, JobPriority>(jobPriorities); + var hasHighPrority = false; + + foreach (var (key, value) in dictionary) + { + if (value == JobPriority.Never) + dictionary.Remove(key); + else if (value != JobPriority.High) + continue; + + if (hasHighPrority) + dictionary[key] = JobPriority.Medium; + + hasHighPrority = true; + } + return new(this) { - _jobPriorities = new Dictionary(jobPriorities), + _jobPriorities = dictionary }; } - public HumanoidCharacterProfile WithJobPriority(string jobId, JobPriority priority) + public HumanoidCharacterProfile WithJobPriority(ProtoId jobId, JobPriority priority) { - var dictionary = new Dictionary(_jobPriorities); + var dictionary = new Dictionary, JobPriority>(_jobPriorities); if (priority == JobPriority.Never) { dictionary.Remove(jobId); } + else if (priority == JobPriority.High) + { + // There can only ever be one high priority job. + foreach (var (job, value) in dictionary) + { + if (value == JobPriority.High) + dictionary[job] = JobPriority.Medium; + } + + dictionary[jobId] = priority; + } else { dictionary[jobId] = priority; @@ -329,17 +370,17 @@ namespace Content.Shared.Preferences return new(this) { PreferenceUnavailable = mode }; } - public HumanoidCharacterProfile WithAntagPreferences(IEnumerable antagPreferences) + public HumanoidCharacterProfile WithAntagPreferences(IEnumerable> antagPreferences) { return new(this) { - _antagPreferences = new HashSet(antagPreferences), + _antagPreferences = new (antagPreferences), }; } - public HumanoidCharacterProfile WithAntagPreference(string antagId, bool pref) + public HumanoidCharacterProfile WithAntagPreference(ProtoId antagId, bool pref) { - var list = new HashSet(_antagPreferences); + var list = new HashSet>(_antagPreferences); if (pref) { list.Add(antagId); @@ -355,13 +396,43 @@ namespace Content.Shared.Preferences }; } - public HumanoidCharacterProfile WithTraitPreference(string traitId, bool pref) + public HumanoidCharacterProfile WithTraitPreference(ProtoId traitId, string? categoryId, bool pref) { - var list = new HashSet(_traitPreferences); + var prototypeManager = IoCManager.Resolve(); + var traitProto = prototypeManager.Index(traitId); + + TraitCategoryPrototype? categoryProto = null; + if (categoryId != null && categoryId != "default") + categoryProto = prototypeManager.Index(categoryId); + + var list = new HashSet>(_traitPreferences); if (pref) { list.Add(traitId); + + if (categoryProto == null || categoryProto.MaxTraitPoints < 0) + { + return new(this) + { + _traitPreferences = list, + }; + } + + var count = 0; + foreach (var trait in list) + { + var traitProtoTemp = prototypeManager.Index(trait); + count += traitProtoTemp.Cost; + } + + if (count > categoryProto.MaxTraitPoints && traitProto.Cost != 0) + { + return new(this) + { + _traitPreferences = _traitPreferences, + }; + } } else { @@ -493,7 +564,7 @@ namespace Content.Shared.Preferences _ => SpawnPriorityPreference.None // Invalid enum values. }; - var priorities = new Dictionary(JobPriorities + var priorities = new Dictionary, JobPriority>(JobPriorities .Where(p => prototypeManager.TryIndex(p.Key, out var job) && job.SetPreference && p.Value switch { JobPriority.Never => false, // Drop never since that's assumed default. @@ -503,6 +574,17 @@ namespace Content.Shared.Preferences _ => false })); + var hasHighPrio = false; + foreach (var (key, value) in priorities) + { + if (value != JobPriority.High) + continue; + + if (hasHighPrio) + priorities[key] = JobPriority.Medium; + hasHighPrio = true; + } + var antags = AntagPreferences .Where(id => prototypeManager.TryIndex(id, out var antag) && antag.SetPreference) .ToList(); diff --git a/Content.Shared/Preferences/Loadouts/RoleLoadout.cs b/Content.Shared/Preferences/Loadouts/RoleLoadout.cs index 479974893c..b9d3a88338 100644 --- a/Content.Shared/Preferences/Loadouts/RoleLoadout.cs +++ b/Content.Shared/Preferences/Loadouts/RoleLoadout.cs @@ -64,6 +64,13 @@ public sealed partial class RoleLoadout : IEquatable foreach (var (group, groupLoadouts) in SelectedLoadouts) { + // Check the group is even valid for this role. + if (!roleProto.Groups.Contains(group)) + { + groupRemove.Add(group); + continue; + } + // Dump if Group doesn't exist if (!protoManager.TryIndex(group, out var groupProto)) { diff --git a/Content.Shared/Radio/EntitySystems/EncryptionKeySystem.cs b/Content.Shared/Radio/EntitySystems/EncryptionKeySystem.cs index 746147eb5b..ea07b5f8a5 100644 --- a/Content.Shared/Radio/EntitySystems/EncryptionKeySystem.cs +++ b/Content.Shared/Radio/EntitySystems/EncryptionKeySystem.cs @@ -6,10 +6,8 @@ using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Radio.Components; -using Content.Shared.Tools; using Content.Shared.Tools.Components; using Content.Shared.Wires; -using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; using Robust.Shared.Network; @@ -106,7 +104,7 @@ public sealed partial class EncryptionKeySystem : EntitySystem TryInsertKey(uid, component, args); } else if (TryComp(args.Used, out var tool) - && tool.Qualities.Contains(component.KeysExtractionMethod) + && _tool.HasQuality(args.Used, component.KeysExtractionMethod, tool) && component.KeyContainer.ContainedEntities.Count > 0) // dont block deconstruction { args.Handled = true; diff --git a/Content.Shared/Random/Helpers/SharedRandomExtensions.cs b/Content.Shared/Random/Helpers/SharedRandomExtensions.cs index f5fbc1bd24..0b618a262d 100644 --- a/Content.Shared/Random/Helpers/SharedRandomExtensions.cs +++ b/Content.Shared/Random/Helpers/SharedRandomExtensions.cs @@ -12,9 +12,13 @@ namespace Content.Shared.Random.Helpers return random.Pick(prototype.Values); } + /// + /// Randomly selects an entry from , attempts to localize it, and returns the result. + /// public static string Pick(this IRobustRandom random, LocalizedDatasetPrototype prototype) { - return random.Pick(prototype.Values); + var index = random.Next(prototype.Values.Count); + return Loc.GetString(prototype.Values[index]); } public static string Pick(this IWeightedRandomPrototype prototype, System.Random random) diff --git a/Content.Shared/Random/RulesSystem.cs b/Content.Shared/Random/RulesSystem.cs index 6b8a58abb7..58d67c268e 100644 --- a/Content.Shared/Random/RulesSystem.cs +++ b/Content.Shared/Random/RulesSystem.cs @@ -1,6 +1,7 @@ using System.Numerics; using Content.Shared.Access.Components; using Content.Shared.Access.Systems; +using Content.Shared.Whitelist; using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Physics.Components; @@ -14,7 +15,7 @@ public sealed class RulesSystem : EntitySystem [Dependency] private readonly AccessReaderSystem _reader = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; - + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public bool IsTrue(EntityUid uid, RulesPrototype rules) { var inRange = new HashSet>(); @@ -158,7 +159,7 @@ public sealed class RulesSystem : EntitySystem foreach (var ent in _lookup.GetEntitiesInRange(xform.MapID, worldPos, entity.Range)) { - if (!entity.Whitelist.IsValid(ent, EntityManager)) + if (_whitelistSystem.IsWhitelistFail(entity.Whitelist, ent)) continue; count++; diff --git a/Content.Shared/Revolutionary/Components/HeadRevolutionaryComponent.cs b/Content.Shared/Revolutionary/Components/HeadRevolutionaryComponent.cs index d2c8374fef..ef2bad65e0 100644 --- a/Content.Shared/Revolutionary/Components/HeadRevolutionaryComponent.cs +++ b/Content.Shared/Revolutionary/Components/HeadRevolutionaryComponent.cs @@ -9,7 +9,7 @@ namespace Content.Shared.Revolutionary.Components; /// Component used for marking a Head Rev for conversion and winning/losing. /// [RegisterComponent, NetworkedComponent, Access(typeof(SharedRevolutionarySystem))] -public sealed partial class HeadRevolutionaryComponent : Component, IAntagStatusIconComponent +public sealed partial class HeadRevolutionaryComponent : Component { /// /// The status icon corresponding to the head revolutionary. @@ -24,7 +24,4 @@ public sealed partial class HeadRevolutionaryComponent : Component, IAntagStatus public TimeSpan StunTime = TimeSpan.FromSeconds(3); public override bool SessionSpecific => true; - - [DataField] - public bool IconVisibleToGhost { get; set; } = true; } diff --git a/Content.Shared/Revolutionary/Components/RevolutionaryComponent.cs b/Content.Shared/Revolutionary/Components/RevolutionaryComponent.cs index 73f533cf69..1f6b45ddea 100644 --- a/Content.Shared/Revolutionary/Components/RevolutionaryComponent.cs +++ b/Content.Shared/Revolutionary/Components/RevolutionaryComponent.cs @@ -10,7 +10,7 @@ namespace Content.Shared.Revolutionary.Components; /// Used for marking regular revs as well as storing icon prototypes so you can see fellow revs. /// [RegisterComponent, NetworkedComponent, Access(typeof(SharedRevolutionarySystem))] -public sealed partial class RevolutionaryComponent : Component, IAntagStatusIconComponent +public sealed partial class RevolutionaryComponent : Component { /// /// The status icon prototype displayed for revolutionaries @@ -25,7 +25,4 @@ public sealed partial class RevolutionaryComponent : Component, IAntagStatusIcon public SoundSpecifier RevStartSound = new SoundPathSpecifier("/Audio/Ambience/Antag/headrev_start.ogg"); public override bool SessionSpecific => true; - - [DataField] - public bool IconVisibleToGhost { get; set; } = true; } diff --git a/Content.Shared/Revolutionary/Components/ShowRevIconsComponent.cs b/Content.Shared/Revolutionary/Components/ShowRevIconsComponent.cs deleted file mode 100644 index 11e20db3f8..0000000000 --- a/Content.Shared/Revolutionary/Components/ShowRevIconsComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Robust.Shared.GameStates; - -namespace Content.Shared.Revolutionary.Components; - -/// -/// Determines whether Someone can see rev/headrev icons on revs. -/// -[RegisterComponent, NetworkedComponent] -public sealed partial class ShowRevIconsComponent: Component -{ -} diff --git a/Content.Shared/Revolutionary/SharedRevolutionarySystem.cs b/Content.Shared/Revolutionary/SharedRevolutionarySystem.cs index ddaf73fcc9..bbf91193cc 100644 --- a/Content.Shared/Revolutionary/SharedRevolutionarySystem.cs +++ b/Content.Shared/Revolutionary/SharedRevolutionarySystem.cs @@ -1,4 +1,3 @@ -using Content.Shared.Ghost; using Content.Shared.IdentityManagement; using Content.Shared.Mindshield.Components; using Content.Shared.Popups; @@ -6,10 +5,11 @@ using Content.Shared.Revolutionary.Components; using Content.Shared.Stunnable; using Robust.Shared.GameStates; using Robust.Shared.Player; +using Content.Shared.Antag; namespace Content.Shared.Revolutionary; -public sealed class SharedRevolutionarySystem : EntitySystem +public abstract class SharedRevolutionarySystem : EntitySystem { [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedStunSystem _sharedStun = default!; @@ -23,7 +23,7 @@ public sealed class SharedRevolutionarySystem : EntitySystem SubscribeLocalEvent(OnRevCompGetStateAttempt); SubscribeLocalEvent(DirtyRevComps); SubscribeLocalEvent(DirtyRevComps); - SubscribeLocalEvent(DirtyRevComps); + SubscribeLocalEvent(DirtyRevComps); } /// @@ -52,7 +52,7 @@ public sealed class SharedRevolutionarySystem : EntitySystem /// private void OnRevCompGetStateAttempt(EntityUid uid, HeadRevolutionaryComponent comp, ref ComponentGetStateAttemptEvent args) { - args.Cancelled = !CanGetState(args.Player, comp.IconVisibleToGhost); + args.Cancelled = !CanGetState(args.Player); } /// @@ -60,30 +60,24 @@ public sealed class SharedRevolutionarySystem : EntitySystem /// private void OnRevCompGetStateAttempt(EntityUid uid, RevolutionaryComponent comp, ref ComponentGetStateAttemptEvent args) { - args.Cancelled = !CanGetState(args.Player, comp.IconVisibleToGhost); + args.Cancelled = !CanGetState(args.Player); } /// /// The criteria that determine whether a Rev/HeadRev component should be sent to a client. /// /// The Player the component will be sent to. - /// Whether the component permits the icon to be visible to observers. /// - private bool CanGetState(ICommonSession? player, bool visibleToGhosts) + private bool CanGetState(ICommonSession? player) { //Apparently this can be null in replays so I am just returning true. - if (player is null) + if (player?.AttachedEntity is not {} uid) return true; - var uid = player.AttachedEntity; - if (HasComp(uid) || HasComp(uid)) return true; - if (visibleToGhosts && HasComp(uid)) - return true; - - return HasComp(uid); + return HasComp(uid); } /// /// Dirties all the Rev components so they are sent to clients. diff --git a/Content.Shared/Roles/AntagPrototype.cs b/Content.Shared/Roles/AntagPrototype.cs index c6acb9b757..b9b06d0588 100644 --- a/Content.Shared/Roles/AntagPrototype.cs +++ b/Content.Shared/Roles/AntagPrototype.cs @@ -41,6 +41,8 @@ public sealed partial class AntagPrototype : IPrototype /// /// Requirements that must be met to opt in to this antag role. /// - [DataField("requirements")] + // TODO ROLE TIMERS + // Actually check if the requirements are met. Because apparently this is actually unused. + [DataField, Access(typeof(SharedRoleSystem), Other = AccessPermissions.None)] public HashSet? Requirements; } diff --git a/Content.Shared/Roles/DepartmentPrototype.cs b/Content.Shared/Roles/DepartmentPrototype.cs index 024eca37fa..d6288bec90 100644 --- a/Content.Shared/Roles/DepartmentPrototype.cs +++ b/Content.Shared/Roles/DepartmentPrototype.cs @@ -1,28 +1,27 @@ using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; namespace Content.Shared.Roles; [Prototype("department")] public sealed partial class DepartmentPrototype : IPrototype { - [IdDataField] public string ID { get; } = default!; + [IdDataField] + public string ID { get; } = string.Empty; /// - /// A description string to display in the character menu as an explanation of the department's function. + /// A description string to display in the character menu as an explanation of the department's function. /// - [DataField("description", required: true)] - public string Description = default!; + [DataField(required: true)] + public string Description = string.Empty; /// - /// A color representing this department to use for text. + /// A color representing this department to use for text. /// - [DataField("color", required: true)] - public Color Color = default!; + [DataField(required: true)] + public Color Color; - [ViewVariables(VVAccess.ReadWrite), - DataField("roles", customTypeSerializer: typeof(PrototypeIdListSerializer))] - public List Roles = new(); + [DataField, ViewVariables(VVAccess.ReadWrite)] + public List> Roles = new(); /// /// Whether this is a primary department or not. @@ -34,8 +33,14 @@ public sealed partial class DepartmentPrototype : IPrototype /// /// Departments with a higher weight sorted before other departments in UI. /// - [DataField("weight")] - public int Weight { get; private set; } = 0; + [DataField] + public int Weight { get; private set; } + + /// + /// Toggles the display of the department in the priority setting menu in the character editor. + /// + [DataField] + public bool EditorHidden; } /// @@ -50,14 +55,14 @@ public sealed class DepartmentUIComparer : IComparer { if (ReferenceEquals(x, y)) return 0; + if (ReferenceEquals(null, y)) return 1; + if (ReferenceEquals(null, x)) return -1; var cmp = -x.Weight.CompareTo(y.Weight); - if (cmp != 0) - return cmp; - return string.Compare(x.ID, y.ID, StringComparison.Ordinal); + return cmp != 0 ? cmp : string.Compare(x.ID, y.ID, StringComparison.Ordinal); } } diff --git a/Content.Shared/Roles/JobPrototype.cs b/Content.Shared/Roles/JobPrototype.cs index d28211a67f..71bd41c89f 100644 --- a/Content.Shared/Roles/JobPrototype.cs +++ b/Content.Shared/Roles/JobPrototype.cs @@ -40,7 +40,7 @@ namespace Content.Shared.Roles [ViewVariables(VVAccess.ReadOnly)] public string? LocalizedDescription => Description is null ? null : Loc.GetString(Description); - [DataField("requirements")] + [DataField, Access(typeof(SharedRoleSystem), Other = AccessPermissions.None)] public HashSet? Requirements; [DataField("joinNotifyCrew")] @@ -69,8 +69,8 @@ namespace Content.Shared.Roles public bool AlwaysUseSpawner { get; } = false; /// - /// Whether this job is a head. - /// The job system will try to pick heads before other jobs on the same priority level. + /// The "weight" or importance of this job. If this number is large, the job system will assign this job + /// before assigning other jobs. /// [DataField("weight")] public int Weight { get; private set; } @@ -103,8 +103,8 @@ namespace Content.Shared.Roles [DataField("jobEntity", customTypeSerializer: typeof(PrototypeIdSerializer))] public string? JobEntity = null; - [DataField("icon", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string Icon { get; private set; } = "JobIconUnknown"; + [DataField] + public ProtoId Icon { get; private set; } = "JobIconUnknown"; [DataField("special", serverOnly: true)] public JobSpecial[] Special { get; private set; } = Array.Empty(); diff --git a/Content.Shared/Roles/JobRequirementOverridePrototype.cs b/Content.Shared/Roles/JobRequirementOverridePrototype.cs new file mode 100644 index 0000000000..d0ce649f36 --- /dev/null +++ b/Content.Shared/Roles/JobRequirementOverridePrototype.cs @@ -0,0 +1,20 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Roles; + +/// +/// Collection of job, antag, and ghost-role job requirements for per-server requirement overrides. +/// +[Prototype] +public sealed partial class JobRequirementOverridePrototype : IPrototype +{ + [ViewVariables] + [IdDataField] + public string ID { get; private set; } = default!; + + [DataField] + public Dictionary, HashSet> Jobs = new (); + + [DataField] + public Dictionary, HashSet> Antags = new (); +} diff --git a/Content.Shared/Roles/JobRequirements.cs b/Content.Shared/Roles/JobRequirements.cs index 46acd0971a..93fbe658b5 100644 --- a/Content.Shared/Roles/JobRequirements.cs +++ b/Content.Shared/Roles/JobRequirements.cs @@ -73,17 +73,19 @@ namespace Content.Shared.Roles { public static bool TryRequirementsMet( JobPrototype job, - Dictionary playTimes, + IReadOnlyDictionary playTimes, [NotNullWhen(false)] out FormattedMessage? reason, IEntityManager entManager, IPrototypeManager prototypes, bool isWhitelisted) { + var sys = entManager.System(); + var requirements = sys.GetJobRequirement(job); reason = null; - if (job.Requirements == null) + if (requirements == null) return true; - foreach (var requirement in job.Requirements) + foreach (var requirement in requirements) { if (!TryRequirementMet(requirement, playTimes, out reason, entManager, prototypes, isWhitelisted)) return false; @@ -132,7 +134,7 @@ namespace Content.Shared.Roles if (deptDiff <= 0) return true; - reason = FormattedMessage.FromMarkup(Loc.GetString( + reason = FormattedMessage.FromMarkupPermissive(Loc.GetString( "role-timer-department-insufficient", ("time", Math.Ceiling(deptDiff)), ("department", Loc.GetString(deptRequirement.Department)), @@ -143,7 +145,7 @@ namespace Content.Shared.Roles { if (deptDiff <= 0) { - reason = FormattedMessage.FromMarkup(Loc.GetString( + reason = FormattedMessage.FromMarkupPermissive(Loc.GetString( "role-timer-department-too-high", ("time", -deptDiff), ("department", Loc.GetString(deptRequirement.Department)), @@ -163,7 +165,7 @@ namespace Content.Shared.Roles if (overallDiff <= 0 || overallTime >= overallRequirement.Time) return true; - reason = FormattedMessage.FromMarkup(Loc.GetString( + reason = FormattedMessage.FromMarkupPermissive(Loc.GetString( "role-timer-overall-insufficient", ("time", Math.Ceiling(overallDiff)))); return false; @@ -172,7 +174,7 @@ namespace Content.Shared.Roles { if (overallDiff <= 0 || overallTime >= overallRequirement.Time) { - reason = FormattedMessage.FromMarkup(Loc.GetString("role-timer-overall-too-high", ("time", -overallDiff))); + reason = FormattedMessage.FromMarkupPermissive(Loc.GetString("role-timer-overall-too-high", ("time", -overallDiff))); return false; } @@ -199,7 +201,7 @@ namespace Content.Shared.Roles if (roleDiff <= 0) return true; - reason = FormattedMessage.FromMarkup(Loc.GetString( + reason = FormattedMessage.FromMarkupPermissive(Loc.GetString( "role-timer-role-insufficient", ("time", Math.Ceiling(roleDiff)), ("job", Loc.GetString(proto)), @@ -210,7 +212,7 @@ namespace Content.Shared.Roles { if (roleDiff <= 0) { - reason = FormattedMessage.FromMarkup(Loc.GetString( + reason = FormattedMessage.FromMarkupPermissive(Loc.GetString( "role-timer-role-too-high", ("time", -roleDiff), ("job", Loc.GetString(proto)), diff --git a/Content.Shared/Roles/Jobs/SharedJobSystem.cs b/Content.Shared/Roles/Jobs/SharedJobSystem.cs index fcf7605278..ce4428d9fe 100644 --- a/Content.Shared/Roles/Jobs/SharedJobSystem.cs +++ b/Content.Shared/Roles/Jobs/SharedJobSystem.cs @@ -118,6 +118,18 @@ public abstract class SharedJobSystem : EntitySystem _prototypes.TryIndex(comp.Prototype, out prototype); } + public bool MindTryGetJobId([NotNullWhen(true)] EntityUid? mindId, out ProtoId? job) + { + if (!TryComp(mindId, out JobComponent? comp)) + { + job = null; + return false; + } + + job = comp.Prototype; + return true; + } + /// /// Tries to get the job name for this mind. /// Returns unknown if not found. diff --git a/Content.Shared/Roles/SharedRoleSystem.cs b/Content.Shared/Roles/SharedRoleSystem.cs index d5ac2e5923..81a360ebb7 100644 --- a/Content.Shared/Roles/SharedRoleSystem.cs +++ b/Content.Shared/Roles/SharedRoleSystem.cs @@ -1,10 +1,12 @@ -using System.Linq; using Content.Shared.Administration.Logs; +using Content.Shared.CCVar; using Content.Shared.Database; +using Content.Shared.Ghost.Roles; using Content.Shared.Mind; using Content.Shared.Roles.Jobs; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; +using Robust.Shared.Configuration; using Robust.Shared.Prototypes; using Robust.Shared.Utility; @@ -16,14 +18,30 @@ public abstract class SharedRoleSystem : EntitySystem [Dependency] private readonly IPrototypeManager _prototypes = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedMindSystem _minds = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; // TODO please lord make role entities private readonly HashSet _antagTypes = new(); + private JobRequirementOverridePrototype? _requirementOverride; + public override void Initialize() { // TODO make roles entities SubscribeLocalEvent(OnJobGetAllRoles); + Subs.CVar(_cfg, CCVars.GameRoleTimerOverride, SetRequirementOverride, true); + } + + private void SetRequirementOverride(string value) + { + if (string.IsNullOrEmpty(value)) + { + _requirementOverride = null; + return; + } + + if (!_prototypes.TryIndex(value, out _requirementOverride )) + Log.Error($"Unknown JobRequirementOverridePrototype: {value}"); } private void OnJobGetAllRoles(EntityUid uid, JobComponent component, ref MindGetAllRolesEvent args) @@ -253,4 +271,36 @@ public abstract class SharedRoleSystem : EntitySystem if (Resolve(mindId, ref mind) && mind.Session != null) _audio.PlayGlobal(sound, mind.Session); } + + public HashSet? GetJobRequirement(JobPrototype job) + { + if (_requirementOverride != null && _requirementOverride.Jobs.TryGetValue(job.ID, out var req)) + return req; + + return job.Requirements; + } + + public HashSet? GetJobRequirement(ProtoId job) + { + if (_requirementOverride != null && _requirementOverride.Jobs.TryGetValue(job, out var req)) + return req; + + return _prototypes.Index(job).Requirements; + } + + public HashSet? GetAntagRequirement(ProtoId antag) + { + if (_requirementOverride != null && _requirementOverride.Antags.TryGetValue(antag, out var req)) + return req; + + return _prototypes.Index(antag).Requirements; + } + + public HashSet? GetAntagRequirement(AntagPrototype antag) + { + if (_requirementOverride != null && _requirementOverride.Antags.TryGetValue(antag.ID, out var req)) + return req; + + return antag.Requirements; + } } diff --git a/Content.Shared/SSDIndicator/SSDIndicatorComponent.cs b/Content.Shared/SSDIndicator/SSDIndicatorComponent.cs index 66310505a1..ee67e3296f 100644 --- a/Content.Shared/SSDIndicator/SSDIndicatorComponent.cs +++ b/Content.Shared/SSDIndicator/SSDIndicatorComponent.cs @@ -1,5 +1,6 @@ using Content.Shared.StatusIcon; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Shared.SSDIndicator; @@ -16,6 +17,6 @@ public sealed partial class SSDIndicatorComponent : Component public bool IsSSD = true; [ViewVariables(VVAccess.ReadWrite)] - [DataField("icon", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string Icon = "SSDIcon"; + [DataField] + public ProtoId Icon = "SSDIcon"; } diff --git a/Content.Shared/Salvage/Fulton/SharedFultonSystem.cs b/Content.Shared/Salvage/Fulton/SharedFultonSystem.cs index b355ae5873..f94558b0b3 100644 --- a/Content.Shared/Salvage/Fulton/SharedFultonSystem.cs +++ b/Content.Shared/Salvage/Fulton/SharedFultonSystem.cs @@ -6,6 +6,7 @@ using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Stacks; using Content.Shared.Verbs; +using Content.Shared.Whitelist; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; @@ -30,6 +31,7 @@ public abstract partial class SharedFultonSystem : EntitySystem [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedStackSystem _stack = default!; [Dependency] protected readonly SharedTransformSystem TransformSystem = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; [ValidatePrototypeId] public const string EffectProto = "FultonEffect"; protected static readonly Vector2 EffectOffset = Vector2.Zero; @@ -176,7 +178,7 @@ public abstract partial class SharedFultonSystem : EntitySystem if (!CanFulton(targetUid)) return false; - if (component.Whitelist?.IsValid(targetUid, EntityManager) != true) + if (_whitelistSystem.IsWhitelistFailOrNull(component.Whitelist, targetUid)) return false; return true; diff --git a/Content.Shared/Shuttles/Systems/SharedShuttleSystem.cs b/Content.Shared/Shuttles/Systems/SharedShuttleSystem.cs index d859d9f485..a382e943ff 100644 --- a/Content.Shared/Shuttles/Systems/SharedShuttleSystem.cs +++ b/Content.Shared/Shuttles/Systems/SharedShuttleSystem.cs @@ -2,6 +2,7 @@ using Content.Shared.Containers.ItemSlots; using Content.Shared.Shuttles.BUIStates; using Content.Shared.Shuttles.Components; using Content.Shared.Shuttles.UI.MapObjects; +using Content.Shared.Whitelist; using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Physics.Collision.Shapes; @@ -15,6 +16,7 @@ public abstract partial class SharedShuttleSystem : EntitySystem [Dependency] private readonly ItemSlotsSystem _itemSlots = default!; [Dependency] protected readonly SharedMapSystem Maps = default!; [Dependency] protected readonly SharedTransformSystem XformSystem = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public const float FTLRange = 512f; public const float FTLBufferRange = 8f; @@ -83,7 +85,7 @@ public abstract partial class SharedShuttleSystem : EntitySystem if (HasComp(mapUid)) return false; - return destination.Whitelist?.IsValid(shuttleUid, EntityManager) != false; + return _whitelistSystem.IsWhitelistPassOrNull(destination.Whitelist, shuttleUid); } /// diff --git a/Content.Shared/Sound/SharedEmitSoundSystem.cs b/Content.Shared/Sound/SharedEmitSoundSystem.cs index 0dcdc44c9f..efc18abaa0 100644 --- a/Content.Shared/Sound/SharedEmitSoundSystem.cs +++ b/Content.Shared/Sound/SharedEmitSoundSystem.cs @@ -8,6 +8,7 @@ using Content.Shared.Mobs; using Content.Shared.Popups; using Content.Shared.Sound.Components; using Content.Shared.Throwing; +using Content.Shared.Whitelist; using JetBrains.Annotations; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; @@ -34,6 +35,7 @@ public abstract class SharedEmitSoundSystem : EntitySystem [Dependency] private readonly SharedAmbientSoundSystem _ambient = default!; [Dependency] private readonly SharedAudioSystem _audioSystem = default!; [Dependency] protected readonly SharedPopupSystem Popup = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -123,7 +125,7 @@ public abstract class SharedEmitSoundSystem : EntitySystem private void OnEmitSoundOnInteractUsing(Entity ent, ref InteractUsingEvent args) { - if (ent.Comp.Whitelist.IsValid(args.Used, EntityManager)) + if (_whitelistSystem.IsWhitelistPass(ent.Comp.Whitelist, args.Used)) { TryEmitSound(ent, ent.Comp, args.User); } diff --git a/Content.Shared/Stacks/StackPrototype.cs b/Content.Shared/Stacks/StackPrototype.cs index 28b7da8f2a..f108419a9e 100644 --- a/Content.Shared/Stacks/StackPrototype.cs +++ b/Content.Shared/Stacks/StackPrototype.cs @@ -4,7 +4,7 @@ using Robust.Shared.Utility; namespace Content.Shared.Stacks; -[Prototype("stack")] +[Prototype] public sealed partial class StackPrototype : IPrototype { [ViewVariables] @@ -15,33 +15,26 @@ public sealed partial class StackPrototype : IPrototype /// Human-readable name for this stack type e.g. "Steel" /// /// This is a localization string ID. - [DataField("name")] + [DataField] public string Name { get; private set; } = string.Empty; /// /// An icon that will be used to represent this stack type. /// - [DataField("icon")] + [DataField] public SpriteSpecifier? Icon { get; private set; } /// /// The entity id that will be spawned by default from this stack. /// - [DataField("spawn", required: true, customTypeSerializer:typeof(PrototypeIdSerializer))] - public string Spawn { get; private set; } = string.Empty; + [DataField(required: true)] + public EntProtoId Spawn { get; private set; } = string.Empty; /// /// The maximum amount of things that can be in a stack. /// Can be overriden on /// if null, simply has unlimited max count. /// - [DataField("maxCount")] + [DataField] public int? MaxCount { get; private set; } - - /// - /// The size of an individual unit of this stack. - /// - [DataField("itemSize")] - public int? ItemSize; } - diff --git a/Content.Shared/StatusIcon/Components/StatusIconComponent.cs b/Content.Shared/StatusIcon/Components/StatusIconComponent.cs index 385f9760c6..c56be7c96a 100644 --- a/Content.Shared/StatusIcon/Components/StatusIconComponent.cs +++ b/Content.Shared/StatusIcon/Components/StatusIconComponent.cs @@ -24,16 +24,4 @@ public sealed partial class StatusIconComponent : Component /// /// [ByRefEvent] -public record struct GetStatusIconsEvent(List StatusIcons, bool InContainer); - -/// -/// Event raised on the Client-side to determine whether to display a status icon on an entity. -/// -/// The player that will see the icons -[ByRefEvent] -public record struct CanDisplayStatusIconsEvent(EntityUid? User = null) -{ - public EntityUid? User = User; - - public bool Cancelled = false; -} +public record struct GetStatusIconsEvent(List StatusIcons); diff --git a/Content.Shared/StatusIcon/StatusIconPrototype.cs b/Content.Shared/StatusIcon/StatusIconPrototype.cs index 2bd13b9361..29a1b60114 100644 --- a/Content.Shared/StatusIcon/StatusIconPrototype.cs +++ b/Content.Shared/StatusIcon/StatusIconPrototype.cs @@ -1,3 +1,5 @@ +using Content.Shared.Stealth.Components; +using Content.Shared.Whitelist; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array; @@ -15,26 +17,45 @@ public partial class StatusIconData : IComparable /// /// The icon that's displayed on the entity. /// - [DataField("icon", required: true)] + [DataField(required: true)] public SpriteSpecifier Icon = default!; /// /// A priority for the order in which the icons will be displayed. /// - [DataField("priority")] + [DataField] public int Priority = 10; + /// + /// Whether or not to hide the icon to ghosts + /// + [DataField] + public bool VisibleToGhosts = true; + + /// + /// Whether or not to hide the icon when we are inside a container like a locker or a crate. + /// + [DataField] + public bool HideInContainer = true; + + /// + /// Whether or not to hide the icon when the entity has an active + /// + [DataField] + public bool HideOnStealth = true; + + /// + /// Specifies what entities and components/tags this icon can be shown to. + /// + [DataField] + public EntityWhitelist? ShowTo; + /// /// A preference for where the icon will be displayed. None | Left | Right /// - [DataField("locationPreference")] + [DataField] public StatusIconLocationPreference LocationPreference = StatusIconLocationPreference.None; - public int CompareTo(StatusIconData? other) - { - return Priority.CompareTo(other?.Priority ?? int.MaxValue); - } - /// /// The layer the icon is displayed on. Mod is drawn above Base. Base | Mod /// @@ -52,6 +73,11 @@ public partial class StatusIconData : IComparable /// [DataField] public bool IsShaded = false; + + public int CompareTo(StatusIconData? other) + { + return Priority.CompareTo(other?.Priority ?? int.MaxValue); + } } /// diff --git a/Content.Shared/StepTrigger/Systems/StepTriggerSystem.cs b/Content.Shared/StepTrigger/Systems/StepTriggerSystem.cs index d81ad754d1..14703f3177 100644 --- a/Content.Shared/StepTrigger/Systems/StepTriggerSystem.cs +++ b/Content.Shared/StepTrigger/Systems/StepTriggerSystem.cs @@ -1,5 +1,6 @@ using Content.Shared.Gravity; using Content.Shared.StepTrigger.Components; +using Content.Shared.Whitelist; using Robust.Shared.Map.Components; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; @@ -12,6 +13,7 @@ public sealed class StepTriggerSystem : EntitySystem [Dependency] private readonly EntityLookupSystem _entityLookup = default!; [Dependency] private readonly SharedGravitySystem _gravity = default!; [Dependency] private readonly SharedMapSystem _map = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -67,7 +69,7 @@ public sealed class StepTriggerSystem : EntitySystem if (ent == uid) continue; - if (component.Blacklist.IsValid(ent.Value, EntityManager) == true) + if (_whitelistSystem.IsBlacklistPass(component.Blacklist, ent.Value)) { return false; } diff --git a/Content.Shared/Storage/EntitySystems/BinSystem.cs b/Content.Shared/Storage/EntitySystems/BinSystem.cs index 1cc95337ea..8c2cb4c4fc 100644 --- a/Content.Shared/Storage/EntitySystems/BinSystem.cs +++ b/Content.Shared/Storage/EntitySystems/BinSystem.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using Content.Shared.Administration.Logs; using Content.Shared.Database; using Content.Shared.Examine; @@ -7,6 +7,7 @@ using Content.Shared.Interaction; using Content.Shared.Item; using Content.Shared.Storage.Components; using Content.Shared.Verbs; +using Content.Shared.Whitelist; using Robust.Shared.Containers; using Robust.Shared.Network; @@ -21,6 +22,7 @@ public sealed class BinSystem : EntitySystem [Dependency] private readonly ISharedAdminLogManager _admin = default!; [Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly SharedHandsSystem _hands = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public const string BinContainerId = "bin-container"; @@ -130,7 +132,7 @@ public sealed class BinSystem : EntitySystem if (component.Items.Count >= component.MaxItems) return false; - if (component.Whitelist != null && !component.Whitelist.IsValid(toInsert)) + if (_whitelistSystem.IsWhitelistFail(component.Whitelist, toInsert)) return false; _container.Insert(toInsert, component.ItemContainer); diff --git a/Content.Shared/Storage/EntitySystems/MagnetPickupSystem.cs b/Content.Shared/Storage/EntitySystems/MagnetPickupSystem.cs index 03da2d09b0..7a8961485d 100644 --- a/Content.Shared/Storage/EntitySystems/MagnetPickupSystem.cs +++ b/Content.Shared/Storage/EntitySystems/MagnetPickupSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Storage.Components; using Content.Shared.Inventory; +using Content.Shared.Whitelist; using Robust.Shared.Map; using Robust.Shared.Physics.Components; using Robust.Shared.Timing; @@ -16,6 +17,8 @@ public sealed class MagnetPickupSystem : EntitySystem [Dependency] private readonly InventorySystem _inventory = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedStorageSystem _storage = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; + private static readonly TimeSpan ScanDelay = TimeSpan.FromSeconds(1); @@ -63,7 +66,7 @@ public sealed class MagnetPickupSystem : EntitySystem foreach (var near in _lookup.GetEntitiesInRange(uid, comp.Range, LookupFlags.Dynamic | LookupFlags.Sundries)) { - if (storage.Whitelist?.IsValid(near, EntityManager) == false) + if (_whitelistSystem.IsWhitelistFail(storage.Whitelist, near)) continue; if (!_physicsQuery.TryGetComponent(near, out var physics) || physics.BodyStatus != BodyStatus.OnGround) diff --git a/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs index 0576e46df4..bb49725e04 100644 --- a/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs @@ -15,6 +15,7 @@ using Content.Shared.Storage.Components; using Content.Shared.Tools.Systems; using Content.Shared.Verbs; using Content.Shared.Wall; +using Content.Shared.Whitelist; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; @@ -45,6 +46,7 @@ public abstract class SharedEntityStorageSystem : EntitySystem [Dependency] protected readonly SharedPopupSystem Popup = default!; [Dependency] protected readonly SharedTransformSystem TransformSystem = default!; [Dependency] private readonly WeldableSystem _weldable = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public const string ContainerName = "entity_storage"; @@ -432,7 +434,7 @@ public abstract class SharedEntityStorageSystem : EntitySystem var targetIsMob = HasComp(toInsert); var storageIsItem = HasComp(container); - var allowedToEat = component.Whitelist?.IsValid(toInsert) ?? HasComp(toInsert); + var allowedToEat = component.Whitelist == null ? HasComp(toInsert) : _whitelistSystem.IsValid(component.Whitelist, toInsert); // BEFORE REPLACING THIS WITH, I.E. A PROPERTY: // Make absolutely 100% sure you have worked out how to stop people ending up in backpacks. diff --git a/Content.Shared/Storage/EntitySystems/SharedItemMapperSystem.cs b/Content.Shared/Storage/EntitySystems/SharedItemMapperSystem.cs index a5e151f4ea..2557f8100c 100644 --- a/Content.Shared/Storage/EntitySystems/SharedItemMapperSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SharedItemMapperSystem.cs @@ -1,5 +1,6 @@ -using System.Linq; +using System.Linq; using Content.Shared.Storage.Components; +using Content.Shared.Whitelist; using JetBrains.Annotations; using Robust.Shared.Containers; @@ -15,6 +16,7 @@ namespace Content.Shared.Storage.EntitySystems { [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedContainerSystem _container = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; /// public override void Initialize() @@ -93,7 +95,7 @@ namespace Content.Shared.Storage.EntitySystems var list = new List(); foreach (var mapLayerData in itemMapper.MapLayers.Values) { - var count = containedLayers.Count(ent => mapLayerData.ServerWhitelist.IsValid(ent)); + var count = containedLayers.Count(ent => _whitelistSystem.IsWhitelistPass(mapLayerData.ServerWhitelist, ent)); if (count >= mapLayerData.MinCount && count <= mapLayerData.MaxCount) { list.Add(mapLayerData.Layer); diff --git a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs index ec08375295..f1793a3951 100644 --- a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs @@ -56,6 +56,7 @@ public abstract class SharedStorageSystem : EntitySystem [Dependency] protected readonly SharedTransformSystem TransformSystem = default!; [Dependency] private readonly SharedUserInterfaceSystem _ui = default!; [Dependency] protected readonly UseDelaySystem UseDelay = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; private EntityQuery _itemQuery; private EntityQuery _stackQuery; @@ -298,7 +299,7 @@ public abstract class SharedStorageSystem : EntitySystem return; // prevent spamming bag open / honkerton honk sound - silent |= TryComp(uid, out var useDelay) && UseDelay.IsDelayed((uid, useDelay)); + silent |= TryComp(uid, out var useDelay) && UseDelay.IsDelayed((uid, useDelay), id: OpenUiUseDelayID); if (!CanInteract(entity, (uid, storageComp), silent: silent)) return; @@ -308,7 +309,7 @@ public abstract class SharedStorageSystem : EntitySystem Audio.PlayPredicted(storageComp.StorageOpenSound, uid, entity); if (useDelay != null) - UseDelay.TryResetDelay((uid, useDelay)); + UseDelay.TryResetDelay((uid, useDelay), id: OpenUiUseDelayID); } _ui.OpenUi(uid, StorageComponent.StorageUiKey.Key, entity); @@ -864,13 +865,8 @@ public abstract class SharedStorageSystem : EntitySystem return false; } - if (storageComp.Whitelist?.IsValid(insertEnt, EntityManager) == false) - { - reason = "comp-storage-invalid-container"; - return false; - } - - if (storageComp.Blacklist?.IsValid(insertEnt, EntityManager) == true) + if (_whitelistSystem.IsWhitelistFail(storageComp.Whitelist, insertEnt) || + _whitelistSystem.IsBlacklistPass(storageComp.Blacklist, insertEnt)) { reason = "comp-storage-invalid-container"; return false; diff --git a/Content.Server/Store/Components/StoreComponent.cs b/Content.Shared/Store/Components/StoreComponent.cs similarity index 72% rename from Content.Server/Store/Components/StoreComponent.cs rename to Content.Shared/Store/Components/StoreComponent.cs index 0b7dbbea09..223f507971 100644 --- a/Content.Server/Store/Components/StoreComponent.cs +++ b/Content.Shared/Store/Components/StoreComponent.cs @@ -1,46 +1,41 @@ using Content.Shared.FixedPoint; -using Content.Shared.Store; using Robust.Shared.Audio; -using Robust.Shared.Map; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set; -namespace Content.Server.Store.Components; +namespace Content.Shared.Store.Components; /// /// This component manages a store which players can use to purchase different listings /// through the ui. The currency, listings, and categories are defined in yaml. /// -[RegisterComponent] +[RegisterComponent, NetworkedComponent] public sealed partial class StoreComponent : Component { - /// - /// The default preset for the store. Is overriden by default values specified on the component. - /// - [DataField("preset", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? Preset; + [DataField] + public LocId Name = "store-ui-default-title"; /// /// All the listing categories that are available on this store. /// The available listings are partially based on the categories. /// - [DataField("categories", customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] - public HashSet Categories = new(); + [DataField] + public HashSet> Categories = new(); /// /// The total amount of currency that can be used in the store. /// The string represents the ID of te currency prototype, where the /// float is that amount. /// - [ViewVariables(VVAccess.ReadWrite), DataField("balance", customTypeSerializer: typeof(PrototypeIdDictionarySerializer))] - public Dictionary Balance = new(); + [DataField] + public Dictionary, FixedPoint2> Balance = new(); /// /// The list of currencies that can be inserted into this store. /// - [ViewVariables(VVAccess.ReadOnly), DataField("currencyWhitelist", customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] - public HashSet CurrencyWhitelist = new(); + [DataField] + public HashSet> CurrencyWhitelist = new(); /// /// The person who "owns" the store/account. Used if you want the listings to be fixed @@ -52,6 +47,7 @@ public sealed partial class StoreComponent : Component /// /// All listings, including those that aren't available to the buyer /// + [DataField] public HashSet Listings = new(); /// @@ -70,7 +66,7 @@ public sealed partial class StoreComponent : Component /// The total balance spent in this store. Used for refunds. /// [ViewVariables, DataField] - public Dictionary BalanceSpent = new(); + public Dictionary, FixedPoint2> BalanceSpent = new(); /// /// Controls if the store allows refunds @@ -95,7 +91,7 @@ public sealed partial class StoreComponent : Component /// /// The sound played to the buyer when a purchase is succesfully made. /// - [DataField("buySuccessSound")] + [DataField] public SoundSpecifier BuySuccessSound = new SoundPathSpecifier("/Audio/Effects/kaching.ogg"); #endregion } diff --git a/Content.Shared/Store/StoreUi.cs b/Content.Shared/Store/StoreUi.cs index ee4da6991f..59cf1bbbc8 100644 --- a/Content.Shared/Store/StoreUi.cs +++ b/Content.Shared/Store/StoreUi.cs @@ -30,20 +30,6 @@ public sealed class StoreUpdateState : BoundUserInterfaceState } } -/// -/// initializes miscellaneous data about the store. -/// -[Serializable, NetSerializable] -public sealed class StoreInitializeState : BoundUserInterfaceState -{ - public readonly string Name; - - public StoreInitializeState(string name) - { - Name = name; - } -} - [Serializable, NetSerializable] public sealed class StoreRequestUpdateInterfaceMessage : BoundUserInterfaceMessage { diff --git a/Content.Shared/Tag/TagComponent.cs b/Content.Shared/Tag/TagComponent.cs index b5b8a48a44..ad4240ba06 100644 --- a/Content.Shared/Tag/TagComponent.cs +++ b/Content.Shared/Tag/TagComponent.cs @@ -1,13 +1,11 @@ using Robust.Shared.GameStates; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set; +using Robust.Shared.Prototypes; -namespace Content.Shared.Tag +namespace Content.Shared.Tag; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(TagSystem))] +public sealed partial class TagComponent : Component { - [RegisterComponent, NetworkedComponent, Access(typeof(TagSystem))] - public sealed partial class TagComponent : Component - { - [DataField("tags", customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] - [Access(typeof(TagSystem), Other = AccessPermissions.ReadExecute)] // FIXME Friends - public HashSet Tags = new(); - } + [DataField, ViewVariables, AutoNetworkedField] + public HashSet> Tags = new(); } diff --git a/Content.Shared/Tag/TagComponentState.cs b/Content.Shared/Tag/TagComponentState.cs deleted file mode 100644 index 9919aba108..0000000000 --- a/Content.Shared/Tag/TagComponentState.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Robust.Shared.Serialization; - -namespace Content.Shared.Tag -{ - [Serializable, NetSerializable] - public sealed class TagComponentState : ComponentState - { - public TagComponentState(string[] tags) - { - Tags = tags; - } - - public string[] Tags { get; } - } -} diff --git a/Content.Shared/Tag/TagPrototype.cs b/Content.Shared/Tag/TagPrototype.cs index 2a06e22cf9..97f8c1af7d 100644 --- a/Content.Shared/Tag/TagPrototype.cs +++ b/Content.Shared/Tag/TagPrototype.cs @@ -1,17 +1,15 @@ using Robust.Shared.Prototypes; -namespace Content.Shared.Tag +namespace Content.Shared.Tag; + +/// +/// Prototype representing a tag in YAML. +/// Meant to only have an ID property, as that is the only thing that +/// gets saved in TagComponent. +/// +[Prototype("Tag")] +public sealed partial class TagPrototype : IPrototype { - /// - /// Prototype representing a tag in YAML. - /// Meant to only have an ID property, as that is the only thing that - /// gets saved in TagComponent. - /// - [Prototype("Tag")] - public sealed partial class TagPrototype : IPrototype - { - [ViewVariables] - [IdDataField] - public string ID { get; private set; } = default!; - } + [IdDataField, ViewVariables] + public string ID { get; } = string.Empty; } diff --git a/Content.Shared/Tag/TagSystem.cs b/Content.Shared/Tag/TagSystem.cs index 7bcb887a41..f1f620a694 100644 --- a/Content.Shared/Tag/TagSystem.cs +++ b/Content.Shared/Tag/TagSystem.cs @@ -1,11 +1,19 @@ -using System.Diagnostics; -using System.Linq; -using Robust.Shared.GameStates; using Robust.Shared.Prototypes; using Robust.Shared.Utility; namespace Content.Shared.Tag; +/// +/// The system that is responsible for working with tags. +/// Checking the existence of the only happens in DEBUG builds, +/// to improve performance, so don't forget to check it. +/// +/// +/// The methods to add or remove a list of tags have only an implementation with the type, +/// it's not much, but it takes away performance, +/// if you need to use them often, it's better to make a proper implementation, +/// you can read more HERE. +/// public sealed class TagSystem : EntitySystem { [Dependency] private readonly IPrototypeManager _proto = default!; @@ -15,685 +23,693 @@ public sealed class TagSystem : EntitySystem public override void Initialize() { base.Initialize(); + _tagQuery = GetEntityQuery(); - SubscribeLocalEvent(OnTagGetState); - SubscribeLocalEvent(OnTagHandleState); #if DEBUG SubscribeLocalEvent(OnTagInit); +#endif } +#if DEBUG private void OnTagInit(EntityUid uid, TagComponent component, ComponentInit args) { foreach (var tag in component.Tags) { AssertValidTag(tag); } + } #endif + + /// + /// Tries to add a tag to an entity if the tag doesn't already exist. + /// + /// + /// true if it was added, false otherwise even if it already existed. + /// + /// + /// Thrown if no exists with the given id. + /// + public bool AddTag(EntityUid entityUid, ProtoId tag) + { + return AddTag((entityUid, EnsureComp(entityUid)), tag); } - - private void OnTagHandleState(EntityUid uid, TagComponent component, ref ComponentHandleState args) + /// + /// Tries to add the given tags to an entity if the tags don't already exist. + /// + /// + /// true if any tags were added, false otherwise even if they all already existed. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool AddTags(EntityUid entityUid, params ProtoId[] tags) { - if (args.Current is not TagComponentState state) - return; + return AddTags(entityUid, (IEnumerable>)tags); + } - component.Tags.Clear(); + /// + /// Tries to add the given tags to an entity if the tags don't already exist. + /// + /// + /// true if any tags were added, false otherwise even if they all already existed. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool AddTags(EntityUid entityUid, IEnumerable> tags) + { + return AddTags((entityUid, EnsureComp(entityUid)), tags); + } - foreach (var tag in state.Tags) + /// + /// Tries to add a tag to an entity if it has a + /// and the tag doesn't already exist. + /// + /// + /// true if it was added, false otherwise even if it already existed. + /// + /// + /// Thrown if no exists with the given id. + /// + public bool TryAddTag(EntityUid entityUid, ProtoId tag) + { + return _tagQuery.TryComp(entityUid, out var component) && + AddTag((entityUid, component), tag); + } + + /// + /// Tries to add the given tags to an entity if it has a + /// and the tags don't already exist. + /// + /// + /// true if any tags were added, false otherwise even if they all already existed. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool TryAddTags(EntityUid entityUid, params ProtoId[] tags) + { + return TryAddTags(entityUid, (IEnumerable>)tags); + } + + /// + /// Tries to add the given tags to an entity if it has a + /// and the tags don't already exist. + /// + /// + /// true if any tags were added, false otherwise even if they all already existed. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool TryAddTags(EntityUid entityUid, IEnumerable> tags) + { + return _tagQuery.TryComp(entityUid, out var component) && + AddTags((entityUid, component), tags); + } + + /// + /// Checks if a tag has been added to an entity. + /// + /// + /// true if it exists, false otherwise. + /// + /// + /// Thrown if no exists with the given id. + /// + public bool HasTag(EntityUid entityUid, ProtoId tag) + { + return _tagQuery.TryComp(entityUid, out var component) && + HasTag(component, tag); + } + + /// + /// Checks if a tag has been added to an entity. + /// + /// + /// true if it exists, false otherwise. + /// + /// + /// Thrown if no exists with the given id. + /// + public bool HasAllTags(EntityUid entityUid, ProtoId tag) => + HasTag(entityUid, tag); + + /// + /// Checks if all of the given tags have been added to an entity. + /// + /// + /// true if they all exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAllTags(EntityUid entityUid, params ProtoId[] tags) + { + return _tagQuery.TryComp(entityUid, out var component) && + HasAllTags(component, tags); + } + + /// + /// Checks if all of the given tags have been added to an entity. + /// + /// + /// true if they all exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAllTags(EntityUid entityUid, HashSet> tags) + { + return _tagQuery.TryComp(entityUid, out var component) && + HasAllTags(component, tags); + } + + /// + /// Checks if all of the given tags have been added to an entity. + /// + /// + /// true if they all exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAllTags(EntityUid entityUid, List> tags) + { + return _tagQuery.TryComp(entityUid, out var component) && + HasAllTags(component, tags); + } + + /// + /// Checks if all of the given tags have been added to an entity. + /// + /// + /// true if they all exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAllTags(EntityUid entityUid, IEnumerable> tags) + { + return _tagQuery.TryComp(entityUid, out var component) && + HasAllTags(component, tags); + } + + /// + /// Checks if a tag has been added to an entity. + /// + /// + /// true if it exists, false otherwise. + /// + /// + /// Thrown if no exists with the given id. + /// + public bool HasAnyTag(EntityUid entityUid, ProtoId tag) => + HasTag(entityUid, tag); + + /// + /// Checks if any of the given tags have been added to an entity. + /// + /// + /// true if any of them exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAnyTag(EntityUid entityUid, params ProtoId[] tags) + { + return _tagQuery.TryComp(entityUid, out var component) && + HasAnyTag(component, tags); + } + + /// + /// Checks if any of the given tags have been added to an entity. + /// + /// + /// true if any of them exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAnyTag(EntityUid entityUid, HashSet> tags) + { + return _tagQuery.TryComp(entityUid, out var component) && + HasAnyTag(component, tags); + } + + /// + /// Checks if any of the given tags have been added to an entity. + /// + /// + /// true if any of them exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAnyTag(EntityUid entityUid, List> tags) + { + return _tagQuery.TryComp(entityUid, out var component) && + HasAnyTag(component, tags); + } + + /// + /// Checks if any of the given tags have been added to an entity. + /// + /// + /// true if any of them exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAnyTag(EntityUid entityUid, IEnumerable> tags) + { + return _tagQuery.TryComp(entityUid, out var component) && + HasAnyTag(component, tags); + } + + /// + /// Checks if a tag has been added to an component. + /// + /// + /// true if it exists, false otherwise. + /// + /// + /// Thrown if no exists with the given id. + /// + public bool HasTag(TagComponent component, ProtoId tag) + { +#if DEBUG + AssertValidTag(tag); +#endif + return component.Tags.Contains(tag); + } + + /// + /// Checks if a tag has been added to an component. + /// + /// + /// true if it exists, false otherwise. + /// + /// + /// Thrown if no exists with the given id. + /// + public bool HasAllTags(TagComponent component, ProtoId tag) => + HasTag(component, tag); + + /// + /// Checks if all of the given tags have been added to an component. + /// + /// + /// true if they all exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAllTags(TagComponent component, params ProtoId[] tags) + { + foreach (var tag in tags) { +#if DEBUG AssertValidTag(tag); - component.Tags.Add(tag); +#endif + if (!component.Tags.Contains(tag)) + return false; } + + return true; } - private static void OnTagGetState(EntityUid uid, TagComponent component, ref ComponentGetState args) + /// + /// Checks if all of the given tags have been added to an component. + /// + /// + /// true if they all exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAllTagsArray(TagComponent component, ProtoId[] tags) { - var tags = new string[component.Tags.Count]; - var i = 0; - - foreach (var tag in component.Tags) + foreach (var tag in tags) { - tags[i] = tag; - i++; +#if DEBUG + AssertValidTag(tag); +#endif + if (!component.Tags.Contains(tag)) + return false; } - args.State = new TagComponentState(tags); + return true; + } + + /// + /// Checks if all of the given tags have been added to an component. + /// + /// + /// true if they all exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAllTags(TagComponent component, List> tags) + { + foreach (var tag in tags) + { +#if DEBUG + AssertValidTag(tag); +#endif + if (!component.Tags.Contains(tag)) + return false; + } + + return true; + } + + /// + /// Checks if all of the given tags have been added to an component. + /// + /// + /// true if they all exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAllTags(TagComponent component, HashSet> tags) + { + foreach (var tag in tags) + { +#if DEBUG + AssertValidTag(tag); +#endif + if (!component.Tags.Contains(tag)) + return false; + } + + return true; + } + + /// + /// Checks if all of the given tags have been added to an component. + /// + /// + /// true if they all exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAllTags(TagComponent component, IEnumerable> tags) + { + foreach (var tag in tags) + { +#if DEBUG + AssertValidTag(tag); +#endif + if (!component.Tags.Contains(tag)) + return false; + } + + return true; + } + + /// + /// Checks if a tag has been added to an component. + /// + /// + /// true if it exists, false otherwise. + /// + /// + /// Thrown if no exists with the given id. + /// + public bool HasAnyTag(TagComponent component, ProtoId tag) => + HasTag(component, tag); + + /// + /// Checks if any of the given tags have been added to an component. + /// + /// + /// true if any of them exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAnyTag(TagComponent component, params ProtoId[] tags) + { + foreach (var tag in tags) + { +#if DEBUG + AssertValidTag(tag); +#endif + if (component.Tags.Contains(tag)) + return true; + } + + return false; + } + + /// + /// Checks if any of the given tags have been added to an component. + /// + /// + /// true if any of them exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAnyTag(TagComponent component, HashSet> tags) + { + foreach (var tag in tags) + { +#if DEBUG + AssertValidTag(tag); +#endif + if (component.Tags.Contains(tag)) + return true; + } + + return false; + } + + /// + /// Checks if any of the given tags have been added to an component. + /// + /// + /// true if any of them exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAnyTag(TagComponent component, List> tags) + { + foreach (var tag in tags) + { +#if DEBUG + AssertValidTag(tag); +#endif + if (component.Tags.Contains(tag)) + return true; + } + + return false; + } + + /// + /// Checks if any of the given tags have been added to an component. + /// + /// + /// true if any of them exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAnyTag(TagComponent component, IEnumerable> tags) + { + foreach (var tag in tags) + { +#if DEBUG + AssertValidTag(tag); +#endif + if (component.Tags.Contains(tag)) + return true; + } + + return false; + } + + /// + /// Tries to remove a tag from an entity if it exists. + /// + /// + /// true if it was removed, false otherwise even if it didn't exist. + /// + /// + /// Thrown if no exists with the given id. + /// + public bool RemoveTag(EntityUid entityUid, ProtoId tag) + { + return _tagQuery.TryComp(entityUid, out var component) && + RemoveTag((entityUid, component), tag); + } + + /// + /// Tries to remove a tag from an entity if it exists. + /// + /// + /// true if it was removed, false otherwise even if it didn't exist. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool RemoveTags(EntityUid entityUid, params ProtoId[] tags) + { + return RemoveTags(entityUid, (IEnumerable>)tags); + } + + /// + /// Tries to remove a tag from an entity if it exists. + /// + /// + /// true if it was removed, false otherwise even if it didn't exist. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool RemoveTags(EntityUid entityUid, IEnumerable> tags) + { + return _tagQuery.TryComp(entityUid, out var component) && + RemoveTags((entityUid, component), tags); + } + + /// + /// Tries to add a tag if it doesn't already exist. + /// + /// + /// true if it was added, false if it already existed. + /// + /// + /// Thrown if no exists with the given id. + /// + public bool AddTag(Entity entity, ProtoId tag) + { +#if DEBUG + AssertValidTag(tag); +#endif + if (!entity.Comp.Tags.Add(tag)) + return false; + + Dirty(entity); + return true; + } + + /// + /// Tries to add the given tags if they don't already exist. + /// + /// + /// true if any tags were added, false if they all already existed. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool AddTags(Entity entity, params ProtoId[] tags) + { + return AddTags(entity, (IEnumerable>)tags); + } + + /// + /// Tries to add the given tags if they don't already exist. + /// + /// + /// true if any tags were added, false if they all already existed. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool AddTags(Entity entity, IEnumerable> tags) + { + var update = false; + foreach (var tag in tags) + { +#if DEBUG + AssertValidTag(tag); +#endif + if (entity.Comp.Tags.Add(tag) && !update) + update = true; + } + + if (!update) + return false; + + Dirty(entity); + return true; + } + + /// + /// Tries to remove a tag if it exists. + /// + /// + /// true if it was removed, false otherwise even if it didn't exist. + /// + /// + /// Thrown if no exists with the given id. + /// + public bool RemoveTag(Entity entity, ProtoId tag) + { +#if DEBUG + AssertValidTag(tag); +#endif + + if (!entity.Comp.Tags.Remove(tag)) + return false; + + Dirty(entity); + return true; + } + + /// + /// Tries to remove all of the given tags if they exist. + /// + /// + /// true if any tag was removed, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool RemoveTags(Entity entity, params ProtoId[] tags) + { + return RemoveTags(entity, (IEnumerable>)tags); + } + + /// + /// Tries to remove all of the given tags if they exist. + /// + /// + /// true if any tag was removed, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool RemoveTags(Entity entity, IEnumerable> tags) + { + var update = false; + foreach (var tag in tags) + { +#if DEBUG + AssertValidTag(tag); +#endif + if (entity.Comp.Tags.Remove(tag) && !update) + update = true; + } + + if (!update) + return false; + + Dirty(entity); + return true; } private void AssertValidTag(string id) { DebugTools.Assert(_proto.HasIndex(id), $"Unknown tag: {id}"); } - - /// - /// Tries to add a tag to an entity if the tag doesn't already exist. - /// - /// The entity to add the tag to. - /// The tag to add. - /// - /// true if it was added, false otherwise even if it already existed. - /// - /// - /// Thrown if no exists with the given id. - /// - public bool AddTag(EntityUid entity, string id) - { - return AddTag(entity, EnsureComp(entity), id); - } - - /// - /// Tries to add the given tags to an entity if the tags don't already exist. - /// - /// The entity to add the tag to. - /// The tags to add. - /// - /// true if any tags were added, false otherwise even if they all already existed. - /// - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool AddTags(EntityUid entity, params string[] ids) - { - return AddTags(entity, EnsureComp(entity), ids); - } - - /// - /// Tries to add the given tags to an entity if the tags don't already exist. - /// - /// The entity to add the tag to. - /// The tags to add. - /// - /// true if any tags were added, false otherwise even if they all already existed. - /// - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool AddTags(EntityUid entity, IEnumerable ids) - { - return AddTags(entity, EnsureComp(entity), ids); - } - - /// - /// Tries to add a tag to an entity if it has a - /// and the tag doesn't already exist. - /// - /// The entity to add the tag to. - /// The tag to add. - /// - /// true if it was added, false otherwise even if it already existed. - /// - /// - /// Thrown if no exists with the given id. - /// - public bool TryAddTag(EntityUid entity, string id) - { - return _tagQuery.TryComp(entity, out var component) && - AddTag(entity, component, id); - } - - /// - /// Tries to add the given tags to an entity if it has a - /// and the tags don't already exist. - /// - /// The entity to add the tag to. - /// The tags to add. - /// - /// true if any tags were added, false otherwise even if they all already existed. - /// - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool TryAddTags(EntityUid entity, params string[] ids) - { - return _tagQuery.TryComp(entity, out var component) && - AddTags(entity, component, ids); - } - - /// - /// Tries to add the given tags to an entity if it has a - /// and the tags don't already exist. - /// - /// The entity to add the tag to. - /// The tags to add. - /// - /// true if any tags were added, false otherwise even if they all already existed. - /// - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool TryAddTags(EntityUid entity, IEnumerable ids) - { - return _tagQuery.TryComp(entity, out var component) && - AddTags(entity, component, ids); - } - - /// - /// Checks if a tag has been added to an entity. - /// - /// The entity to check. - /// The tag to check for. - /// true if it exists, false otherwise. - /// - /// Thrown if no exists with the given id. - /// - public bool HasTag(EntityUid entity, string id) - { - return _tagQuery.TryComp(entity, out var component) && - HasTag(component, id); - } - - /// - /// Checks if a tag has been added to an entity. - /// - [Obsolete] - public bool HasTag(EntityUid entity, string id, EntityQuery tagQuery) - { - return tagQuery.TryGetComponent(entity, out var component) && - HasTag(component, id); - } - - /// - /// Checks if all of the given tags have been added to an entity. - /// - /// The entity to check. - /// The tags to check for. - /// true if they all exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAllTags(EntityUid entity, string id) => HasTag(entity, id); - - /// - /// Checks if all of the given tags have been added to an entity. - /// - /// The entity to check. - /// The tags to check for. - /// true if they all exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAllTags(EntityUid entity, List ids) - { - return _tagQuery.TryComp(entity, out var component) && - HasAllTags(component, ids); - } - - /// - /// Checks if all of the given tags have been added to an entity. - /// - /// The entity to check. - /// The tags to check for. - /// true if they all exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAllTags(EntityUid entity, IEnumerable ids) - { - return _tagQuery.TryComp(entity, out var component) && - HasAllTags(component, ids); - } - - /// - /// Checks if all of the given tags have been added to an entity. - /// - /// The entity to check. - /// The tags to check for. - /// true if they all exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAllTags(EntityUid entity, List> ids) - { - return _tagQuery.TryComp(entity, out var component) && - HasAllTags(component, ids); - } - - /// - /// Checks if any of the given tags have been added to an entity. - /// - /// The entity to check. - /// The tags to check for. - /// true if any of them exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAnyTag(EntityUid entity, params string[] ids) - { - return _tagQuery.TryComp(entity, out var component) && - HasAnyTag(component, ids); - } - - /// - /// Checks if any of the given tags have been added to an entity. - /// - /// The entity to check. - /// The tag to check for. - /// true if any of them exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAnyTag(EntityUid entity, string id) => HasTag(entity, id); - - /// - /// Checks if any of the given tags have been added to an entity. - /// - /// The entity to check. - /// The tags to check for. - /// true if any of them exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAnyTag(EntityUid entity, List ids) - { - return _tagQuery.TryComp(entity, out var component) && - HasAnyTag(component, ids); - } - - /// - /// Checks if any of the given tags have been added to an entity. - /// - /// The entity to check. - /// The tags to check for. - /// true if any of them exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAnyTag(EntityUid entity, List> ids) - { - return TryComp(entity, out var component) && - HasAnyTag(component, ids); - } - - /// - /// Checks if any of the given tags have been added to an entity. - /// - /// The entity to check. - /// The tags to check for. - /// true if any of them exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAnyTag(EntityUid entity, IEnumerable ids) - { - return _tagQuery.TryComp(entity, out var component) && - HasAnyTag(component, ids); - } - - /// - /// Tries to remove a tag from an entity if it exists. - /// - /// The entity to remove the tag from. - /// The tag to remove. - /// - /// true if it was removed, false otherwise even if it didn't exist. - /// - /// - /// Thrown if no exists with the given id. - /// - public bool RemoveTag(EntityUid entity, string id) - { - return _tagQuery.TryComp(entity, out var component) && - RemoveTag(entity, component, id); - } - - /// - /// Tries to remove a tag from an entity if it exists. - /// - /// The entity to remove the tag from. - /// The tag to remove. - /// - /// true if it was removed, false otherwise even if it didn't exist. - /// - /// Thrown if one of the ids represents an unregistered . - /// - /// - public bool RemoveTags(EntityUid entity, params string[] ids) - { - return _tagQuery.TryComp(entity, out var component) && - RemoveTags(entity, component, ids); - } - - /// - /// Tries to remove a tag from an entity if it exists. - /// - /// The entity to remove the tag from. - /// The tag to remove. - /// - /// true if it was removed, false otherwise even if it didn't exist. - /// - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool RemoveTags(EntityUid entity, IEnumerable ids) - { - return _tagQuery.TryComp(entity, out var component) && - RemoveTags(entity, component, ids); - } - - /// - /// Tries to add a tag if it doesn't already exist. - /// - /// The tag to add. - /// true if it was added, false if it already existed. - /// - /// Thrown if no exists with the given id. - /// - public bool AddTag(EntityUid uid, TagComponent component, string id) - { - AssertValidTag(id); - var added = component.Tags.Add(id); - - if (added) - { - Dirty(uid, component); - return true; - } - - return false; - } - - /// - /// Tries to add the given tags if they don't already exist. - /// - /// The tags to add. - /// true if any tags were added, false if they all already existed. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool AddTags(EntityUid uid, TagComponent component, params string[] ids) - { - return AddTags(uid, component, ids.AsEnumerable()); - } - - /// - /// Tries to add the given tags if they don't already exist. - /// - /// The tags to add. - /// true if any tags were added, false if they all already existed. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool AddTags(EntityUid uid, TagComponent component, IEnumerable ids) - { - var count = component.Tags.Count; - - foreach (var id in ids) - { - AssertValidTag(id); - component.Tags.Add(id); - } - - if (component.Tags.Count > count) - { - Dirty(uid, component); - return true; - } - - return false; - } - - /// - /// Checks if a tag has been added. - /// - /// The tag to check for. - /// true if it exists, false otherwise. - /// - /// Thrown if no exists with the given id. - /// - public bool HasTag(TagComponent component, string id) - { - AssertValidTag(id); - return component.Tags.Contains(id); - } - - /// - /// Checks if all of the given tags have been added. - /// - /// The tags to check for. - /// true if they all exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAllTags(TagComponent component, params string[] ids) - { - return HasAllTags(component, ids.AsEnumerable()); - } - - /// - /// Checks if all of the given tags have been added. - /// - /// The tag to check for. - /// true if they all exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAllTags(TagComponent component, string id) => HasTag(component, id); - - /// - /// Checks if all of the given tags have been added. - /// - /// The tags to check for. - /// true if they all exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAllTags(TagComponent component, List ids) - { - foreach (var id in ids) - { - AssertValidTag(id); - - if (!component.Tags.Contains(id)) - return false; - } - - return true; - } - - /// - /// Checks if all of the given tags have been added. - /// - /// The tags to check for. - /// true if they all exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAllTags(TagComponent component, IEnumerable ids) - { - foreach (var id in ids) - { - AssertValidTag(id); - - if (!component.Tags.Contains(id)) - return false; - - } - - return true; - } - - /// - /// Checks if all of the given tags have been added. - /// - /// The tags to check for. - /// true if they all exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAllTags(TagComponent component, List> ids) - { - foreach (var id in ids) - { - AssertValidTag(id); - - if (!component.Tags.Contains(id)) - return false; - - } - - return true; - } - - /// - /// Checks if any of the given tags have been added. - /// - /// The tags to check for. - /// true if any of them exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAnyTag(TagComponent component, params string[] ids) - { - foreach (var id in ids) - { - AssertValidTag(id); - - if (component.Tags.Contains(id)) - return true; - } - - return false; - } - - /// - /// Checks if any of the given tags have been added. - /// - /// The tag to check for. - /// true if any of them exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAnyTag(TagComponent component, string id) => HasTag(component, id); - - /// - /// Checks if any of the given tags have been added. - /// - /// The tags to check for. - /// true if any of them exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAnyTag(TagComponent component, List ids) - { - foreach (var id in ids) - { - AssertValidTag(id); - - if (component.Tags.Contains(id)) - { - return true; - } - } - - return false; - } - - /// - /// Checks if any of the given tags have been added. - /// - /// The tags to check for. - /// true if any of them exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAnyTag(TagComponent component, IEnumerable ids) - { - foreach (var id in ids) - { - AssertValidTag(id); - - if (component.Tags.Contains(id)) - { - return true; - } - } - - return false; - } - - /// - /// Checks if any of the given tags have been added. - /// - /// The tags to check for. - /// true if any of them exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAnyTag(TagComponent comp, List> ids) - { - foreach (var id in ids) - { - AssertValidTag(id); - - if (comp.Tags.Contains(id)) - return true; - } - - return false; - } - - /// - /// Tries to remove a tag if it exists. - /// - /// - /// true if it was removed, false otherwise even if it didn't exist. - /// - /// - /// Thrown if no exists with the given id. - /// - public bool RemoveTag(EntityUid uid, TagComponent component, string id) - { - AssertValidTag(id); - - if (component.Tags.Remove(id)) - { - Dirty(uid, component); - return true; - } - - return false; - } - - /// - /// Tries to remove all of the given tags if they exist. - /// - /// The tags to remove. - /// - /// true if it was removed, false otherwise even if they didn't exist. - /// - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool RemoveTags(EntityUid uid, TagComponent component, params string[] ids) - { - return RemoveTags(uid, component, ids.AsEnumerable()); - } - - /// - /// Tries to remove all of the given tags if they exist. - /// - /// The tags to remove. - /// true if any tag was removed, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool RemoveTags(EntityUid uid, TagComponent component, IEnumerable ids) - { - var count = component.Tags.Count; - - foreach (var id in ids) - { - AssertValidTag(id); - component.Tags.Remove(id); - } - - if (component.Tags.Count < count) - { - Dirty(uid, component); - return true; - } - - return false; - } } diff --git a/Content.Shared/Teleportation/Components/HandTeleporterComponent.cs b/Content.Shared/Teleportation/Components/HandTeleporterComponent.cs index 6abd4a7d21..6ea29d3fd6 100644 --- a/Content.Shared/Teleportation/Components/HandTeleporterComponent.cs +++ b/Content.Shared/Teleportation/Components/HandTeleporterComponent.cs @@ -1,4 +1,4 @@ -using Content.Shared.DoAfter; +using Content.Shared.DoAfter; using Robust.Shared.Audio; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; @@ -20,6 +20,12 @@ public sealed partial class HandTeleporterComponent : Component [ViewVariables, DataField("secondPortal")] public EntityUid? SecondPortal = null; + /// + /// Portals can't be placed on different grids? + /// + [DataField] + public bool AllowPortalsOnDifferentGrids; + [DataField("firstPortalPrototype", customTypeSerializer: typeof(PrototypeIdSerializer))] public string FirstPortalPrototype = "PortalRed"; diff --git a/Content.Shared/Teleportation/Systems/SwapTeleporterSystem.cs b/Content.Shared/Teleportation/Systems/SwapTeleporterSystem.cs index bc73baa61a..58c249fec5 100644 --- a/Content.Shared/Teleportation/Systems/SwapTeleporterSystem.cs +++ b/Content.Shared/Teleportation/Systems/SwapTeleporterSystem.cs @@ -4,6 +4,7 @@ using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Teleportation.Components; using Content.Shared.Verbs; +using Content.Shared.Whitelist; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; using Robust.Shared.Map.Components; @@ -24,6 +25,7 @@ public sealed class SwapTeleporterSystem : EntitySystem [Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; private EntityQuery _xformQuery; @@ -51,8 +53,8 @@ public sealed class SwapTeleporterSystem : EntitySystem if (!TryComp(target, out var targetComp)) return; - if (!comp.TeleporterWhitelist.IsValid(target, EntityManager) || - !targetComp.TeleporterWhitelist.IsValid(uid, EntityManager)) + if (_whitelistSystem.IsWhitelistFail(comp.TeleporterWhitelist, target) || + _whitelistSystem.IsWhitelistFail(targetComp.TeleporterWhitelist, uid)) { return; } diff --git a/Content.Shared/Tools/Components/ToolComponent.cs b/Content.Shared/Tools/Components/ToolComponent.cs index 92857ab905..58d850c10a 100644 --- a/Content.Shared/Tools/Components/ToolComponent.cs +++ b/Content.Shared/Tools/Components/ToolComponent.cs @@ -1,52 +1,44 @@ +using Content.Shared.Nyanotrasen.Abilities.Oni; +using Content.Shared.Tools.Systems; using Robust.Shared.Audio; using Robust.Shared.GameStates; using Robust.Shared.Utility; -namespace Content.Shared.Tools.Components +namespace Content.Shared.Tools.Components; + +[RegisterComponent, NetworkedComponent] +[Access(typeof(SharedToolSystem), typeof(SharedOniSystem))] // DeltaV - Allowed OniSystem access +public sealed partial class ToolComponent : Component { - [RegisterComponent, NetworkedComponent] // TODO move tool system to shared, and make it a friend. - public sealed partial class ToolComponent : Component - { - [DataField("qualities")] - public PrototypeFlags Qualities { get; set; } = new(); - - /// - /// For tool interactions that have a delay before action this will modify the rate, time to wait is divided by this value - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("speed")] - public float SpeedModifier { get; set; } = 1; - - [DataField("useSound")] - public SoundSpecifier? UseSound { get; set; } - } + [DataField] + public PrototypeFlags Qualities = []; /// - /// Attempt event called *before* any do afters to see if the tool usage should succeed or not. - /// Raised on both the tool and then target. + /// For tool interactions that have a delay before action this will modify the rate, time to wait is divided by this value /// - public sealed class ToolUseAttemptEvent : CancellableEntityEventArgs - { - public EntityUid User { get; } + [DataField] + public float SpeedModifier = 1; - public ToolUseAttemptEvent(EntityUid user) - { - User = user; - } - } - - /// - /// Event raised on the user of a tool to see if they can actually use it. - /// - [ByRefEvent] - public struct ToolUserAttemptUseEvent - { - public EntityUid? Target; - public bool Cancelled = false; - - public ToolUserAttemptUseEvent(EntityUid? target) - { - Target = target; - } - } + [DataField] + public SoundSpecifier? UseSound; +} + +/// +/// Attempt event called *before* any do afters to see if the tool usage should succeed or not. +/// Raised on both the tool and then target. +/// +public sealed class ToolUseAttemptEvent(EntityUid user, float fuel) : CancellableEntityEventArgs +{ + public EntityUid User { get; } = user; + public float Fuel = fuel; +} + +/// +/// Event raised on the user of a tool to see if they can actually use it. +/// +[ByRefEvent] +public struct ToolUserAttemptUseEvent(EntityUid? target) +{ + public EntityUid? Target = target; + public bool Cancelled = false; } diff --git a/Content.Server/Construction/Components/WelderRefinableComponent.cs b/Content.Shared/Tools/Components/ToolRefinableComponent.cs similarity index 74% rename from Content.Server/Construction/Components/WelderRefinableComponent.cs rename to Content.Shared/Tools/Components/ToolRefinableComponent.cs index 2fe88f2670..5a311cdda8 100644 --- a/Content.Server/Construction/Components/WelderRefinableComponent.cs +++ b/Content.Shared/Tools/Components/ToolRefinableComponent.cs @@ -1,15 +1,16 @@ using Content.Shared.Storage; -using Content.Shared.Tools; +using Robust.Shared.GameStates; using Robust.Shared.Prototypes; +using Content.Shared.Tools.Systems; -namespace Content.Server.Construction.Components; +namespace Content.Shared.Tools.Components; /// /// Used for something that can be refined by welder. /// For example, glass shard can be refined to glass sheet. /// -[RegisterComponent, Access(typeof(RefiningSystem))] -public sealed partial class WelderRefinableComponent : Component +[RegisterComponent, NetworkedComponent, Access(typeof(ToolRefinablSystem))] +public sealed partial class ToolRefinableComponent : Component { /// /// The items created when the item is refined. @@ -27,7 +28,7 @@ public sealed partial class WelderRefinableComponent : Component /// The amount of fuel it takes to refine a given item. /// [DataField] - public float RefineFuel; + public float RefineFuel = 3f; /// /// The tool type needed in order to refine this item. diff --git a/Content.Shared/Tools/Systems/SharedToolSystem.Welder.cs b/Content.Shared/Tools/Systems/SharedToolSystem.Welder.cs index e790b59cd1..60eafce474 100644 --- a/Content.Shared/Tools/Systems/SharedToolSystem.Welder.cs +++ b/Content.Shared/Tools/Systems/SharedToolSystem.Welder.cs @@ -16,8 +16,15 @@ public abstract partial class SharedToolSystem { SubscribeLocalEvent(OnWelderExamine); SubscribeLocalEvent(OnWelderAfterInteract); - SubscribeLocalEvent>(OnWelderToolUseAttempt); + + SubscribeLocalEvent((uid, comp, ev) => { + CanCancelWelderUse((uid, comp), ev.User, ev.Fuel, ev); + }); + SubscribeLocalEvent>((uid, comp, ev) => { + CanCancelWelderUse((uid, comp), ev.Event.User, ev.Event.Fuel, ev); + }); SubscribeLocalEvent(OnWelderDoAfter); + SubscribeLocalEvent(OnToggle); SubscribeLocalEvent(OnActivateAttempt); } @@ -120,23 +127,20 @@ public abstract partial class SharedToolSystem } } - private void OnWelderToolUseAttempt(Entity entity, ref DoAfterAttemptEvent args) + private void CanCancelWelderUse(Entity entity, EntityUid user, float requiredFuel, CancellableEntityEventArgs ev) { - var user = args.DoAfter.Args.User; - if (!ItemToggle.IsActivated(entity.Owner)) { _popup.PopupClient(Loc.GetString("welder-component-welder-not-lit-message"), entity, user); - args.Cancel(); - return; + ev.Cancel(); } - var (fuel, _) = GetWelderFuelAndCapacity(entity); + var (currentFuel, _) = GetWelderFuelAndCapacity(entity); - if (args.Event.Fuel > fuel) + if (requiredFuel > currentFuel) { _popup.PopupClient(Loc.GetString("welder-component-cannot-weld-message"), entity, user); - args.Cancel(); + ev.Cancel(); } } diff --git a/Content.Shared/Tools/Systems/SharedToolSystem.cs b/Content.Shared/Tools/Systems/SharedToolSystem.cs index 9edae9b78f..72e984fd82 100644 --- a/Content.Shared/Tools/Systems/SharedToolSystem.cs +++ b/Content.Shared/Tools/Systems/SharedToolSystem.cs @@ -217,7 +217,7 @@ public abstract partial class SharedToolSystem : EntitySystem return false; // check if the tool allows being used - var beforeAttempt = new ToolUseAttemptEvent(user); + var beforeAttempt = new ToolUseAttemptEvent(user, fuel); RaiseLocalEvent(tool, beforeAttempt); if (beforeAttempt.Cancelled) return false; @@ -296,4 +296,3 @@ public abstract partial class SharedToolSystem : EntitySystem public sealed partial class CableCuttingFinishedEvent : SimpleDoAfterEvent; #endregion - diff --git a/Content.Server/Construction/RefiningSystem.cs b/Content.Shared/Tools/Systems/ToolRefinableSystem.cs similarity index 59% rename from Content.Server/Construction/RefiningSystem.cs rename to Content.Shared/Tools/Systems/ToolRefinableSystem.cs index ce7eb49ef1..e8ac4d492d 100644 --- a/Content.Server/Construction/RefiningSystem.cs +++ b/Content.Shared/Tools/Systems/ToolRefinableSystem.cs @@ -1,25 +1,26 @@ -using Content.Server.Construction.Components; using Content.Shared.Construction; using Content.Shared.Interaction; using Content.Shared.Storage; -using Content.Shared.Tools.Systems; +using Content.Shared.Tools.Components; +using Robust.Shared.Network; using Robust.Shared.Random; -namespace Content.Server.Construction; +namespace Content.Shared.Tools.Systems; -public sealed class RefiningSystem : EntitySystem +public sealed class ToolRefinablSystem : EntitySystem { + [Dependency] private readonly INetManager _net = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly SharedToolSystem _toolSystem = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnInteractUsing); - SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnInteractUsing); + SubscribeLocalEvent(OnDoAfter); } - private void OnInteractUsing(EntityUid uid, WelderRefinableComponent component, InteractUsingEvent args) + private void OnInteractUsing(EntityUid uid, ToolRefinableComponent component, InteractUsingEvent args) { if (args.Handled) return; @@ -34,11 +35,14 @@ public sealed class RefiningSystem : EntitySystem fuel: component.RefineFuel); } - private void OnDoAfter(EntityUid uid, WelderRefinableComponent component, WelderRefineDoAfterEvent args) + private void OnDoAfter(EntityUid uid, ToolRefinableComponent component, WelderRefineDoAfterEvent args) { if (args.Cancelled) return; + if (_net.IsClient) + return; + var xform = Transform(uid); var spawns = EntitySpawnCollection.GetSpawns(component.RefineResult, _random); foreach (var spawn in spawns) diff --git a/Content.Shared/Traits/TraitCategoryPrototype.cs b/Content.Shared/Traits/TraitCategoryPrototype.cs new file mode 100644 index 0000000000..1da624173a --- /dev/null +++ b/Content.Shared/Traits/TraitCategoryPrototype.cs @@ -0,0 +1,26 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Traits; + +/// +/// Traits category with general settings. Allows you to limit the number of taken traits in one category +/// +[Prototype] +public sealed partial class TraitCategoryPrototype : IPrototype +{ + [ViewVariables] + [IdDataField] + public string ID { get; private set; } = default!; + + /// + /// Name of the trait category displayed in the UI + /// + [DataField] + public LocId Name { get; private set; } = string.Empty; + + /// + /// The maximum number of traits that can be taken in this category. If -1, you can take as many traits as you like. + /// + [DataField] + public int MaxTraitPoints = -1; +} diff --git a/Content.Shared/Traits/TraitPrototype.cs b/Content.Shared/Traits/TraitPrototype.cs index 34feb8da22..c79d3cbf30 100644 --- a/Content.Shared/Traits/TraitPrototype.cs +++ b/Content.Shared/Traits/TraitPrototype.cs @@ -1,55 +1,63 @@ using Content.Shared.Whitelist; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -// don't worry about it +namespace Content.Shared.Traits; -namespace Content.Shared.Traits +/// +/// Describes a trait. +/// +[Prototype] +public sealed partial class TraitPrototype : IPrototype { + [ViewVariables] + [IdDataField] + public string ID { get; private set; } = default!; + /// - /// Describes a trait. + /// The name of this trait. /// - [Prototype("trait")] - public sealed partial class TraitPrototype : IPrototype - { - [ViewVariables] - [IdDataField] - public string ID { get; private set; } = default!; + [DataField] + public LocId Name { get; private set; } = string.Empty; - /// - /// The name of this trait. - /// - [DataField("name")] - public string Name { get; private set; } = ""; + /// + /// The description of this trait. + /// + [DataField] + public LocId? Description { get; private set; } - /// - /// The description of this trait. - /// - [DataField("description")] - public string? Description { get; private set; } + /// + /// Don't apply this trait to entities this whitelist IS NOT valid for. + /// + [DataField] + public EntityWhitelist? Whitelist; - /// - /// Don't apply this trait to entities this whitelist IS NOT valid for. - /// - [DataField("whitelist")] - public EntityWhitelist? Whitelist; + /// + /// Don't apply this trait to entities this whitelist IS valid for. (hence, a blacklist) + /// + [DataField] + public EntityWhitelist? Blacklist; - /// - /// Don't apply this trait to entities this whitelist IS valid for. (hence, a blacklist) - /// - [DataField("blacklist")] - public EntityWhitelist? Blacklist; + /// + /// The components that get added to the player, when they pick this trait. + /// + [DataField] + public ComponentRegistry Components { get; private set; } = default!; - /// - /// The components that get added to the player, when they pick this trait. - /// - [DataField("components")] - public ComponentRegistry Components { get; private set; } = default!; + /// + /// Gear that is given to the player, when they pick this trait. + /// + [DataField] + public EntProtoId? TraitGear; - /// - /// Gear that is given to the player, when they pick this trait. - /// - [DataField("traitGear", required: false, customTypeSerializer:typeof(PrototypeIdSerializer))] - public string? TraitGear; - } + /// + /// Trait Price. If negative number, points will be added. + /// + [DataField] + public int Cost = 0; + + /// + /// Adds a trait to a category, allowing you to limit the selection of some traits to the settings of that category. + /// + [DataField] + public ProtoId? Category; } diff --git a/Content.Shared/UserInterface/ActivatableUISystem.cs b/Content.Shared/UserInterface/ActivatableUISystem.cs index 1bb11f337f..b1006b2a74 100644 --- a/Content.Shared/UserInterface/ActivatableUISystem.cs +++ b/Content.Shared/UserInterface/ActivatableUISystem.cs @@ -8,6 +8,7 @@ using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Popups; using Content.Shared.Verbs; +using Content.Shared.Whitelist; using Robust.Shared.Utility; namespace Content.Shared.UserInterface; @@ -19,6 +20,7 @@ public sealed partial class ActivatableUISystem : EntitySystem [Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedHandsSystem _hands = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -96,7 +98,7 @@ public sealed partial class ActivatableUISystem : EntitySystem if (!args.CanAccess) return false; - if (!component.RequiredItems?.IsValid(args.Using ?? default, EntityManager) ?? false) + if (_whitelistSystem.IsWhitelistFail(component.RequiredItems, args.Using ?? default)) return false; if (component.RequireHands) @@ -156,7 +158,7 @@ public sealed partial class ActivatableUISystem : EntitySystem if (component.RequiredItems == null) return; - if (!component.RequiredItems.IsValid(args.Used, EntityManager)) + if (_whitelistSystem.IsWhitelistFail(component.RequiredItems, args.Used)) return; args.Handled = InteractUI(args.User, uid, component); diff --git a/Content.Shared/Weapons/Marker/SharedDamageMarkerSystem.cs b/Content.Shared/Weapons/Marker/SharedDamageMarkerSystem.cs index d1814020e6..eab638a9fc 100644 --- a/Content.Shared/Weapons/Marker/SharedDamageMarkerSystem.cs +++ b/Content.Shared/Weapons/Marker/SharedDamageMarkerSystem.cs @@ -1,6 +1,7 @@ using Content.Shared.Damage; using Content.Shared.Projectiles; using Content.Shared.Weapons.Melee.Events; +using Content.Shared.Whitelist; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Network; @@ -15,6 +16,7 @@ public abstract class SharedDamageMarkerSystem : EntitySystem [Dependency] private readonly INetManager _netManager = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -58,7 +60,7 @@ public abstract class SharedDamageMarkerSystem : EntitySystem if (!args.OtherFixture.Hard || args.OurFixtureId != SharedProjectileSystem.ProjectileFixture || component.Amount <= 0 || - component.Whitelist?.IsValid(args.OtherEntity, EntityManager) == false || + _whitelistSystem.IsWhitelistFail(component.Whitelist, args.OtherEntity) || !TryComp(uid, out var projectile) || projectile.Weapon == null) { diff --git a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs index 2d9993096b..b67acdea4f 100644 --- a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs +++ b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs @@ -751,7 +751,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem return; var invMatrix = TransformSystem.GetInvWorldMatrix(userXform); - var localPos = invMatrix.Transform(coordinates.Position); + var localPos = Vector2.Transform(coordinates.Position, invMatrix); if (localPos.LengthSquared() <= 0f) return; diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs index 784dd0793a..9123661c8e 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs @@ -15,6 +15,7 @@ public abstract partial class SharedGunSystem { [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + protected virtual void InitializeBallistic() { SubscribeLocalEvent(OnBallisticInit); @@ -41,7 +42,10 @@ public abstract partial class SharedGunSystem private void OnBallisticInteractUsing(EntityUid uid, BallisticAmmoProviderComponent component, InteractUsingEvent args) { - if (args.Handled || component.Whitelist?.IsValid(args.Used, EntityManager) != true) + if (args.Handled) + return; + + if (_whitelistSystem.IsWhitelistFailOrNull(component.Whitelist, args.Used)) return; if (GetBallisticShots(component) >= component.Capacity) @@ -122,7 +126,7 @@ public abstract partial class SharedGunSystem if (ent == null) continue; - if (!target.Whitelist.IsValid(ent.Value)) + if (_whitelistSystem.IsWhitelistFail(target.Whitelist, ent.Value)) { Popup( Loc.GetString("gun-ballistic-transfer-invalid", diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Clothing.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Clothing.cs index 77ee419ac3..d4aa024f4c 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Clothing.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Clothing.cs @@ -42,7 +42,7 @@ public partial class SharedGunSystem while (enumerator.NextItem(out var item)) { - if (component.ProviderWhitelist == null || !component.ProviderWhitelist.IsValid(item, EntityManager)) + if (_whitelistSystem.IsWhitelistFailOrNull(component.ProviderWhitelist, item)) continue; slotEntity = item; diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Revolver.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Revolver.cs index b8b00799c1..14aaff5bf7 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Revolver.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Revolver.cs @@ -89,7 +89,7 @@ public partial class SharedGunSystem public bool TryRevolverInsert(EntityUid revolverUid, RevolverAmmoProviderComponent component, EntityUid uid, EntityUid? user) { - if (component.Whitelist?.IsValid(uid, EntityManager) == false) + if (_whitelistSystem.IsWhitelistFail(component.Whitelist, uid)) return false; // If it's a speedloader try to get ammo from it. diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs index cdd58d70aa..57fe6efda6 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs @@ -22,6 +22,7 @@ using Content.Shared.Weapons.Melee; using Content.Shared.Weapons.Melee.Events; using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Events; +using Content.Shared.Whitelist; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; @@ -64,6 +65,7 @@ public abstract partial class SharedGunSystem : EntitySystem [Dependency] protected readonly TagSystem TagSystem = default!; [Dependency] protected readonly ThrowingSystem ThrowingSystem = default!; [Dependency] private readonly UseDelaySystem _useDelay = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; private const float InteractNextFire = 0.3f; private const double SafetyNextFire = 0.5; diff --git a/Content.Shared/Weapons/Reflect/ReflectSystem.cs b/Content.Shared/Weapons/Reflect/ReflectSystem.cs index 03ad97edff..9b89be6202 100644 --- a/Content.Shared/Weapons/Reflect/ReflectSystem.cs +++ b/Content.Shared/Weapons/Reflect/ReflectSystem.cs @@ -49,73 +49,123 @@ public sealed class ReflectSystem : EntitySystem { base.Initialize(); - SubscribeLocalEvent(OnReflectCollide); - SubscribeLocalEvent(OnReflectHitscan); + SubscribeLocalEvent(OnObjectReflectProjectileAttempt); + SubscribeLocalEvent(OnObjectReflectHitscanAttempt); SubscribeLocalEvent(OnReflectEquipped); SubscribeLocalEvent(OnReflectUnequipped); SubscribeLocalEvent(OnReflectHandEquipped); SubscribeLocalEvent(OnReflectHandUnequipped); SubscribeLocalEvent(OnToggleReflect); - SubscribeLocalEvent(OnReflectUserCollide); - SubscribeLocalEvent(OnReflectUserHitscan); + SubscribeLocalEvent(OnUserProjectileReflectAttempt); + SubscribeLocalEvent(OnUserHitscanReflectAttempt); } - private void OnReflectUserHitscan(EntityUid uid, ReflectUserComponent component, ref HitScanReflectAttemptEvent args) + private void OnUserHitscanReflectAttempt(Entity user, ref HitScanReflectAttemptEvent args) { if (args.Reflected) return; - foreach (var ent in _inventorySystem.GetHandOrInventoryEntities(uid, SlotFlags.WITHOUT_POCKET)) - { - if (!TryReflectHitscan(uid, ent, args.Shooter, args.SourceItem, args.Direction, out var dir)) - continue; + if (!UserCanReflect(user, out var bestReflectorUid)) + return; - args.Direction = dir.Value; - args.Reflected = true; - break; - } + if (!TryReflectHitscan(user.Owner, bestReflectorUid.Value, args.Shooter, args.SourceItem, args.Direction, out var dir)) + return; + + args.Direction = dir.Value; + args.Reflected = true; } - private void OnReflectUserCollide(EntityUid uid, ReflectUserComponent component, ref ProjectileReflectAttemptEvent args) - { - foreach (var ent in _inventorySystem.GetHandOrInventoryEntities(uid, SlotFlags.WITHOUT_POCKET)) - { - if (!TryReflectProjectile(uid, ent, args.ProjUid)) - continue; - - args.Cancelled = true; - break; - } - } - - private void OnReflectCollide(EntityUid uid, ReflectComponent component, ref ProjectileReflectAttemptEvent args) + private void OnUserProjectileReflectAttempt(Entity user, ref ProjectileReflectAttemptEvent args) { if (args.Cancelled) return; - if (TryReflectProjectile(uid, uid, args.ProjUid, reflect: component)) - args.Cancelled = true; + if (!TryComp(args.ProjUid, out var reflectiveComponent)) + return; + + if (!UserCanReflect(user, out var bestReflectorUid, (args.ProjUid, reflectiveComponent))) + return; + + if (!TryReflectProjectile(user, bestReflectorUid.Value, (args.ProjUid, args.Component))) + return; + + args.Cancelled = true; } - private bool TryReflectProjectile(EntityUid user, EntityUid reflector, EntityUid projectile, ProjectileComponent? projectileComp = null, ReflectComponent? reflect = null) + private void OnObjectReflectHitscanAttempt(Entity obj, ref HitScanReflectAttemptEvent args) + { + if (args.Reflected || (obj.Comp.Reflects & args.Reflective) == 0x0) + return; + + if (!TryReflectHitscan(obj, obj, args.Shooter, args.SourceItem, args.Direction, out var dir)) + return; + + args.Direction = dir.Value; + args.Reflected = true; + } + + private void OnObjectReflectProjectileAttempt(Entity obj, ref ProjectileReflectAttemptEvent args) + { + if (args.Cancelled) + return; + + if (!TryReflectProjectile(obj, obj, (args.ProjUid, args.Component))) + return; + + args.Cancelled = true; + } + + /// + /// Can a user reflect something that's hit them? Returns true if so, and the best reflector available in the user's equipment. + /// + private bool UserCanReflect(Entity user, [NotNullWhen(true)] out Entity? bestReflector, Entity? projectile = null) + { + bestReflector = null; + + foreach (var entityUid in _inventorySystem.GetHandOrInventoryEntities(user.Owner, SlotFlags.WITHOUT_POCKET)) + { + if (!TryComp(entityUid, out var comp)) + continue; + + if (!comp.Enabled) + continue; + + if (bestReflector != null && bestReflector.Value.Comp.ReflectProb >= comp.ReflectProb) + continue; + + if (projectile != null && (comp.Reflects & projectile.Value.Comp.Reflective) == 0x0) + continue; + + bestReflector = (entityUid, comp); + } + + return bestReflector != null; + } + + private bool TryReflectProjectile(EntityUid user, Entity reflector, Entity projectile) { - // Do we have the components needed to try a reflect at all? if ( - !Resolve(reflector, ref reflect, false) || - !reflect.Enabled || + // Is it on? + !reflector.Comp.Enabled || + // Is the projectile deflectable? !TryComp(projectile, out var reflective) || - (reflect.Reflects & reflective.Reflective) == 0x0 || + // Does the deflector deflect the type of projecitle? + (reflector.Comp.Reflects & reflective.Reflective) == 0x0 || + // Is the projectile correctly set up with physics? !TryComp(projectile, out var physics) || - TryComp(reflector, out var staminaComponent) && staminaComponent.Critical || + // If the user of the reflector is a mob with stamina, is it capable of deflecting? + TryComp(user, out var staminaComponent) && staminaComponent.Critical || _standing.IsDown(reflector) ) return false; - if (!_random.Prob(CalcReflectChance(reflector, reflect))) + // If this dice roll fails, the shot isn't deflected + if (!_random.Prob(GetReflectChance(reflector))) return false; - var rotation = _random.NextAngle(-reflect.Spread / 2, reflect.Spread / 2).Opposite(); + // Below handles what happens after being deflected. + var rotation = _random.NextAngle(-reflector.Comp.Spread / 2, reflector.Comp.Spread / 2).Opposite(); var existingVelocity = _physics.GetMapLinearVelocity(projectile, component: physics); var relativeVelocity = existingVelocity - _physics.GetMapLinearVelocity(user); var newVelocity = rotation.RotateVec(relativeVelocity); @@ -132,98 +182,52 @@ public sealed class ReflectSystem : EntitySystem if (_netManager.IsServer) { _popup.PopupEntity(Loc.GetString("reflect-shot"), user); - _audio.PlayPvs(reflect.SoundOnReflect, user, AudioHelpers.WithVariation(0.05f, _random)); + _audio.PlayPvs(reflector.Comp.SoundOnReflect, user, AudioHelpers.WithVariation(0.05f, _random)); } - if (Resolve(projectile, ref projectileComp, false)) - { - _adminLogger.Add(LogType.BulletHit, LogImpact.Medium, $"{ToPrettyString(user)} reflected {ToPrettyString(projectile)} from {ToPrettyString(projectileComp.Weapon)} shot by {projectileComp.Shooter}"); + _adminLogger.Add(LogType.BulletHit, LogImpact.Medium, $"{ToPrettyString(user)} reflected {ToPrettyString(projectile)} from {ToPrettyString(projectile.Comp.Weapon)} shot by {projectile.Comp.Shooter}"); - projectileComp.Shooter = user; - projectileComp.Weapon = user; - Dirty(projectile, projectileComp); - } - else - { - _adminLogger.Add(LogType.BulletHit, LogImpact.Medium, $"{ToPrettyString(user)} reflected {ToPrettyString(projectile)}"); - } + projectile.Comp.Shooter = user; + projectile.Comp.Weapon = user; + Dirty(projectile); return true; } - private float CalcReflectChance(EntityUid reflector, ReflectComponent reflect) - { - /* - * The rules of deflection are as follows: - * If you innately reflect things via magic, biology etc., you always have a full chance. - * If you are standing up and standing still, you're prepared to deflect and have full chance. - * If you have velocity, your deflection chance depends on your velocity, clamped. - * If you are floating, your chance is the minimum value possible. - * You cannot deflect if you are knocked down or stunned. - */ - - if (reflect.Innate) - return reflect.ReflectProb; - - if (_gravity.IsWeightless(reflector)) - return reflect.MinReflectProb; - - if (!TryComp(reflector, out var reflectorPhysics)) - return reflect.ReflectProb; - - return MathHelper.Lerp( - reflect.MinReflectProb, - reflect.ReflectProb, - // Inverse progression between velocities fed in as progression between probabilities. We go high -> low so the output here needs to be _inverted_. - 1 - Math.Clamp((reflectorPhysics.LinearVelocity.Length() - reflect.VelocityBeforeNotMaxProb) / (reflect.VelocityBeforeMinProb - reflect.VelocityBeforeNotMaxProb), 0, 1) - ); - } - - private void OnReflectHitscan(EntityUid uid, ReflectComponent component, ref HitScanReflectAttemptEvent args) - { - if (args.Reflected || - (component.Reflects & args.Reflective) == 0x0) - { - return; - } - - if (TryReflectHitscan(uid, uid, args.Shooter, args.SourceItem, args.Direction, out var dir)) - { - args.Direction = dir.Value; - args.Reflected = true; - } - } - private bool TryReflectHitscan( EntityUid user, - EntityUid reflector, + Entity reflector, EntityUid? shooter, EntityUid shotSource, Vector2 direction, [NotNullWhen(true)] out Vector2? newDirection) { - if (!TryComp(reflector, out var reflect) || - !reflect.Enabled || - TryComp(reflector, out var staminaComponent) && staminaComponent.Critical || - _standing.IsDown(reflector)) + if ( + // Is the reflector enabled? + !reflector.Comp.Enabled || + // If the user is a mob with stamina, is it capable of deflecting? + TryComp(user, out var staminaComponent) && staminaComponent.Critical || + _standing.IsDown(user)) { newDirection = null; return false; } - if (!_random.Prob(CalcReflectChance(reflector, reflect))) + // If this dice roll fails, the shot is not deflected. + if (!_random.Prob(GetReflectChance(reflector))) { newDirection = null; return false; } + // Below handles what happens after being deflected. if (_netManager.IsServer) { _popup.PopupEntity(Loc.GetString("reflect-shot"), user); - _audio.PlayPvs(reflect.SoundOnReflect, user, AudioHelpers.WithVariation(0.05f, _random)); + _audio.PlayPvs(reflector.Comp.SoundOnReflect, user, AudioHelpers.WithVariation(0.05f, _random)); } - var spread = _random.NextAngle(-reflect.Spread / 2, reflect.Spread / 2); + var spread = _random.NextAngle(-reflector.Comp.Spread / 2, reflector.Comp.Spread / 2); newDirection = -spread.RotateVec(direction); if (shooter != null) @@ -234,51 +238,81 @@ public sealed class ReflectSystem : EntitySystem return true; } - private void OnReflectEquipped(EntityUid uid, ReflectComponent component, GotEquippedEvent args) + private float GetReflectChance(Entity reflector) + { + /* + * The rules of deflection are as follows: + * If you innately reflect things via magic, biology etc., you always have a full chance. + * If you are standing up and standing still, you're prepared to deflect and have full chance. + * If you have velocity, your deflection chance depends on your velocity, clamped. + * If you are floating, your chance is the minimum value possible. + */ + + if (reflector.Comp.Innate) + return reflector.Comp.ReflectProb; + + if (_gravity.IsWeightless(reflector)) + return reflector.Comp.MinReflectProb; + + if (!TryComp(reflector, out var reflectorPhysics)) + return reflector.Comp.ReflectProb; + + return MathHelper.Lerp( + reflector.Comp.MinReflectProb, + reflector.Comp.ReflectProb, + // Inverse progression between velocities fed in as progression between probabilities. We go high -> low so the output here needs to be _inverted_. + 1 - Math.Clamp((reflectorPhysics.LinearVelocity.Length() - reflector.Comp.VelocityBeforeNotMaxProb) / (reflector.Comp.VelocityBeforeMinProb - reflector.Comp.VelocityBeforeNotMaxProb), 0, 1) + ); + } + + private void OnReflectEquipped(Entity reflector, ref GotEquippedEvent args) { if (_gameTiming.ApplyingState) return; EnsureComp(args.Equipee); - if (component.Enabled) + if (reflector.Comp.Enabled) EnableAlert(args.Equipee); } - private void OnReflectUnequipped(EntityUid uid, ReflectComponent comp, GotUnequippedEvent args) + private void OnReflectUnequipped(Entity reflector, ref GotUnequippedEvent args) { RefreshReflectUser(args.Equipee); } - private void OnReflectHandEquipped(EntityUid uid, ReflectComponent component, GotEquippedHandEvent args) + private void OnReflectHandEquipped(Entity reflector, ref GotEquippedHandEvent args) { if (_gameTiming.ApplyingState) return; EnsureComp(args.User); - if (component.Enabled) + if (reflector.Comp.Enabled) EnableAlert(args.User); } - private void OnReflectHandUnequipped(EntityUid uid, ReflectComponent component, GotUnequippedHandEvent args) + private void OnReflectHandUnequipped(Entity reflector, ref GotUnequippedHandEvent args) { RefreshReflectUser(args.User); } - private void OnToggleReflect(EntityUid uid, ReflectComponent comp, ref ItemToggledEvent args) + private void OnToggleReflect(Entity reflector, ref ItemToggledEvent args) { - comp.Enabled = args.Activated; - Dirty(uid, comp); + reflector.Comp.Enabled = args.Activated; + Dirty(reflector); - if (comp.Enabled) - EnableAlert(uid); + if (args.User == null) + return; + + if (reflector.Comp.Enabled) + EnableAlert(args.User.Value); else - DisableAlert(uid); + DisableAlert(args.User.Value); } /// - /// Refreshes whether someone has reflection potential so we can raise directed events on them. + /// Refreshes whether someone has reflection potential, so we can raise directed events on them. /// private void RefreshReflectUser(EntityUid user) { diff --git a/Content.Shared/Weapons/Reflect/ReflectUserComponent.cs b/Content.Shared/Weapons/Reflect/ReflectUserComponent.cs index 44fe60813e..44ef481a37 100644 --- a/Content.Shared/Weapons/Reflect/ReflectUserComponent.cs +++ b/Content.Shared/Weapons/Reflect/ReflectUserComponent.cs @@ -7,7 +7,4 @@ namespace Content.Shared.Weapons.Reflect; /// Reflection events will then be relayed. /// [RegisterComponent, NetworkedComponent] -public sealed partial class ReflectUserComponent : Component -{ - -} +public sealed partial class ReflectUserComponent : Component; diff --git a/Content.Shared/Whitelist/EntityWhitelist.cs b/Content.Shared/Whitelist/EntityWhitelist.cs index 895759be95..3e4e2fecb2 100644 --- a/Content.Shared/Whitelist/EntityWhitelist.cs +++ b/Content.Shared/Whitelist/EntityWhitelist.cs @@ -54,14 +54,4 @@ public sealed partial class EntityWhitelist /// [DataField] public bool RequireAll; - - [Obsolete("Use WhitelistSystem")] - public bool IsValid(EntityUid uid, IEntityManager? man = null) - { - var sys = man?.System() ?? - IoCManager.Resolve().GetEntitySystem(); - - return sys.IsValid(this, uid); - - } } diff --git a/Content.Shared/Whitelist/EntityWhitelistSystem.cs b/Content.Shared/Whitelist/EntityWhitelistSystem.cs index d73646b7e9..f311946cf9 100644 --- a/Content.Shared/Whitelist/EntityWhitelistSystem.cs +++ b/Content.Shared/Whitelist/EntityWhitelistSystem.cs @@ -60,6 +60,90 @@ public sealed class EntityWhitelistSystem : EntitySystem return list.RequireAll; } + /// The following are a list of "helper functions" that are basically the same as each other + /// to help make code that uses EntityWhitelist a bit more readable because at the moment + /// it is quite clunky having to write out component.Whitelist == null ? true : _whitelist.IsValid(component.Whitelist, uid) + /// several times in a row and makes comparisons easier to read + + /// + /// Helper function to determine if Whitelist is not null and entity is on list + /// + public bool IsWhitelistPass(EntityWhitelist? whitelist, EntityUid uid) + { + if (whitelist == null) + return false; + + return IsValid(whitelist, uid); + } + + /// + /// Helper function to determine if Whitelist is not null and entity is not on the list + /// + public bool IsWhitelistFail(EntityWhitelist? whitelist, EntityUid uid) + { + if (whitelist == null) + return false; + + return !IsValid(whitelist, uid); + } + + /// + /// Helper function to determine if Whitelist is either null or the entity is on the list + /// + public bool IsWhitelistPassOrNull(EntityWhitelist? whitelist, EntityUid uid) + { + if (whitelist == null) + return true; + + return IsValid(whitelist, uid); + } + + /// + /// Helper function to determine if Whitelist is either null or the entity is not on the list + /// + public bool IsWhitelistFailOrNull(EntityWhitelist? whitelist, EntityUid uid) + { + if (whitelist == null) + return true; + + return !IsValid(whitelist, uid); + } + + /// + /// Helper function to determine if Blacklist is not null and entity is on list + /// Duplicate of equivalent Whitelist function + /// + public bool IsBlacklistPass(EntityWhitelist? blacklist, EntityUid uid) + { + return IsWhitelistPass(blacklist, uid); + } + + /// + /// Helper function to determine if Blacklist is not null and entity is not on the list + /// Duplicate of equivalent Whitelist function + /// + public bool IsBlacklistFail(EntityWhitelist? blacklist, EntityUid uid) + { + return IsWhitelistFail(blacklist, uid); + } + + /// + /// Helper function to determine if Blacklist is either null or the entity is on the list + /// Duplicate of equivalent Whitelist function + /// + public bool IsBlacklistPassOrNull(EntityWhitelist? blacklist, EntityUid uid) + { + return IsWhitelistPassOrNull(blacklist, uid); + } + + /// + /// Helper function to determine if Blacklist is either null or the entity is not on the list + /// Duplicate of equivalent Whitelist function + /// + public bool IsBlacklistFailOrNull(EntityWhitelist? blacklist, EntityUid uid) + { + return IsWhitelistFailOrNull(blacklist, uid); + } private void EnsureRegistrations(EntityWhitelist list) { diff --git a/Content.Shared/Wieldable/WieldableSystem.cs b/Content.Shared/Wieldable/WieldableSystem.cs index c09044f84b..cb95c1d7e7 100644 --- a/Content.Shared/Wieldable/WieldableSystem.cs +++ b/Content.Shared/Wieldable/WieldableSystem.cs @@ -1,3 +1,4 @@ +using System.Linq; using Content.Shared.Examine; using Content.Shared.Hands; using Content.Shared.Hands.Components; @@ -94,7 +95,8 @@ public sealed class WieldableSystem : EntitySystem private void OnDeselectWieldable(EntityUid uid, WieldableComponent component, HandDeselectedEvent args) { - if (!component.Wielded) + if (!component.Wielded || + _handsSystem.EnumerateHands(args.User).Count() > 2) return; TryUnwield(uid, component, args.User); diff --git a/Content.Shared/Zombies/InitialInfectedComponent.cs b/Content.Shared/Zombies/InitialInfectedComponent.cs index 3200dd7f5e..3149de63bd 100644 --- a/Content.Shared/Zombies/InitialInfectedComponent.cs +++ b/Content.Shared/Zombies/InitialInfectedComponent.cs @@ -1,4 +1,3 @@ -using Content.Shared.Antag; using Content.Shared.StatusIcon; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; @@ -6,11 +5,8 @@ using Robust.Shared.Prototypes; namespace Content.Shared.Zombies; [RegisterComponent, NetworkedComponent] -public sealed partial class InitialInfectedComponent : Component, IAntagStatusIconComponent +public sealed partial class InitialInfectedComponent : Component { - [DataField("initialInfectedStatusIcon")] - public ProtoId StatusIcon { get; set; } = "InitialInfectedFaction"; - [DataField] - public bool IconVisibleToGhost { get; set; } = true; + public ProtoId StatusIcon = "InitialInfectedFaction"; } diff --git a/Content.Shared/Zombies/ShowZombieIconsComponent.cs b/Content.Shared/Zombies/ShowZombieIconsComponent.cs deleted file mode 100644 index a2bc85c074..0000000000 --- a/Content.Shared/Zombies/ShowZombieIconsComponent.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Robust.Shared.GameStates; - -namespace Content.Shared.Zombies; - -/// -/// Makes it so an entity can view ZombieAntagIcons. -/// -[RegisterComponent, NetworkedComponent] -public sealed partial class ShowZombieIconsComponent: Component -{ - -} diff --git a/Content.Shared/Zombies/ZombieComponent.cs b/Content.Shared/Zombies/ZombieComponent.cs index 3673a2c51d..2cd0cdb96d 100644 --- a/Content.Shared/Zombies/ZombieComponent.cs +++ b/Content.Shared/Zombies/ZombieComponent.cs @@ -14,7 +14,7 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy namespace Content.Shared.Zombies; [RegisterComponent, NetworkedComponent] -public sealed partial class ZombieComponent : Component, IAntagStatusIconComponent +public sealed partial class ZombieComponent : Component { /// /// The baseline infection chance you have if you are completely nude @@ -97,9 +97,6 @@ public sealed partial class ZombieComponent : Component, IAntagStatusIconCompone [DataField("zombieStatusIcon")] public ProtoId StatusIcon { get; set; } = "ZombieFaction"; - [DataField] - public bool IconVisibleToGhost { get; set; } = true; - /// /// Healing each second /// diff --git a/Resources/Audio/Effects/Gasp/attributions.yml b/Resources/Audio/Effects/Gasp/attributions.yml index fe8f817c5a..8d84b770f3 100644 --- a/Resources/Audio/Effects/Gasp/attributions.yml +++ b/Resources/Audio/Effects/Gasp/attributions.yml @@ -24,3 +24,9 @@ license: "CC-BY-SA-3.0" copyright: "Taken from tgstation at https://github.com/tgstation/tgstation/commit/f7a49c4068f1277e6857baf0892d355f1c055974" source: "https://github.com/tgstation/tgstation/tree/f7a49c4068f1277e6857baf0892d355f1c055974/sound/voice/human" + +- files: + - moth_DeathGasp.ogg + license: "CC-BY-SA-3.0" + copyright: "Taken from tgstation at https://github.com/tgstation/tgstation/commit/948ad3dd5b22803a01cd74c27f37e509dc61395b" + source: "https://github.com/tgstation/tgstation/blob/948ad3dd5b22803a01cd74c27f37e509dc61395b/sound/voice/moth/moth_death.ogg" diff --git a/Resources/Audio/Effects/Gasp/moth_DeathGasp.ogg b/Resources/Audio/Effects/Gasp/moth_DeathGasp.ogg new file mode 100644 index 0000000000..df23cfa472 Binary files /dev/null and b/Resources/Audio/Effects/Gasp/moth_DeathGasp.ogg differ diff --git a/Resources/Audio/Effects/Smoke-grenade.ogg b/Resources/Audio/Effects/Smoke-grenade.ogg index c697214c2a..945290ede5 100644 Binary files a/Resources/Audio/Effects/Smoke-grenade.ogg and b/Resources/Audio/Effects/Smoke-grenade.ogg differ diff --git a/Resources/Changelog/Admin.yml b/Resources/Changelog/Admin.yml index b03b0b54fb..fc3a3f8b8d 100644 --- a/Resources/Changelog/Admin.yml +++ b/Resources/Changelog/Admin.yml @@ -281,5 +281,22 @@ Entries: id: 34 time: '2024-06-01T08:14:44.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28451 +- author: Repo + changes: + - message: Sorting and search to objects tab. + type: Add + - message: Disconnected people showing player tab + type: Fix + id: 35 + time: '2024-06-06T06:41:11.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28609 +- author: nikthechampiongr + changes: + - message: Freeze, freeze & mute, and unfreeze verbs now work on any entity. Not + just connected players. + type: Tweak + id: 36 + time: '2024-06-08T10:40:33.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28664 Name: Admin Order: 3 diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 2a808f3282..fb1a4bad31 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,322 +1,4 @@ Entries: -- author: Tayrtahn - changes: - - message: Reflected tranquilizer rounds no longer inject the character who reflected - them. - type: Fix - id: 6160 - time: '2024-03-15T13:57:15.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26141 -- author: lzk228 - changes: - - message: Refill light replacer from box popup now shows properly. - type: Fix - id: 6161 - time: '2024-03-15T15:23:58.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26136 -- author: FairlySadPanda - changes: - - message: Food and drink stocks in vending machines has been reduced to encourage - people to use the kitchen and bar. - type: Tweak - id: 6162 - time: '2024-03-15T23:28:01.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25999 -- author: Plykiya - changes: - - message: Ion storms now have a chance of happening from the start of shift. - type: Tweak - id: 6163 - time: '2024-03-16T03:47:14.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26165 -- author: Krunk - changes: - - message: Zombies and animals can be stripped once again. - type: Fix - - message: Dead or critical mobs can no longer be stripped instantly. - type: Fix - id: 6164 - time: '2024-03-16T03:50:53.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26166 -- author: metalgearsloth - changes: - - message: Fix store refunds. - type: Fix - id: 6165 - time: '2024-03-16T14:06:17.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26173 -- author: LordCarve - changes: - - message: Decayed anomalies no longer show as having gone supercritical in logs. - type: Fix - id: 6166 - time: '2024-03-16T17:31:21.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26180 -- author: Velcroboy - changes: - - message: Tweaked gas canisters to pass through cargo flaps. - type: Tweak - id: 6167 - time: '2024-03-17T00:55:32.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26193 -- author: metalgearsloth - changes: - - message: Fix ID cards sometimes not loading properly. - type: Fix - id: 6168 - time: '2024-03-17T01:10:59.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26195 -- author: 21Melkuu - changes: - - message: Add new explosion-proof backpack in aplink. - type: Add - id: 6169 - time: '2024-03-17T02:21:13.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26187 -- author: Ilya246 - changes: - - message: Syndicate decoy bombs may now be purchased from the uplink. - type: Add - - message: Syndicate bomb description no longer lies about their minimum detonation - time. - type: Fix - id: 6170 - time: '2024-03-17T02:31:41.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26034 -- author: wafehling - changes: - - message: Added 18 new bounties to cargo bounty system. - type: Add - id: 6171 - time: '2024-03-17T17:06:17.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26160 -- author: Ko4erga - changes: - - message: Added craftable high and small wooden fences, bench. New stairs. - type: Add - id: 6172 - time: '2024-03-17T21:18:59.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26182 -- author: potato1234_x - changes: - - message: Changed the puddle sprites so the smoothing is less jagged - type: Tweak - id: 6173 - time: '2024-03-17T23:15:45.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26171 -- author: FungiFellow - changes: - - message: Changed Monkey Reinf Price to 6TC - type: Tweak - id: 6174 - time: '2024-03-18T01:25:58.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26214 -- author: Golinth - changes: - - message: Criminal record icons now show up below job icons - type: Tweak - id: 6175 - time: '2024-03-18T01:37:00.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26203 -- author: PJB3005 - changes: - - message: Fixed pressure damage calculations to what they were always supposed - to be. This means pressure damage now starts happening at more extreme values - (20 kPa for low pressure damage instead of 50 kPa) and high pressure damage - is scaled different. It also fixed the HUD alerts so that the "warning" alerts - show up before you actually start taking damage. - type: Fix - - message: Air alarms now start complaining about low pressure much earlier, when - the pressure drops so much that you can't breathe oxygen tank. - type: Tweak - id: 6176 - time: '2024-03-18T05:16:31.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26217 -- author: HappyRoach - changes: - - message: The correct magazines now appear in the WT550 safe. - type: Fix - id: 6177 - time: '2024-03-18T06:10:28.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26208 -- author: vanx - changes: - - message: You can now wear most guns in the suit slot, and see them on yourself! - type: Tweak - id: 6178 - time: '2024-03-18T06:16:09.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26152 -- author: Dutch-VanDerLinde - changes: - - message: Romerol now properly works on the dead. - type: Tweak - id: 6179 - time: '2024-03-18T06:25:36.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26222 -- author: ElectroJr - changes: - - message: Added an option to try and ignore some errors if a replay fails to load, - type: Add - id: 6180 - time: '2024-03-18T07:31:37.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26212 -- author: Tayrtahn - changes: - - message: Cyborg recharging stations are able to charge cyborgs again. - type: Fix - id: 6181 - time: '2024-03-18T13:37:49.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26230 -- author: DoutorWhite - changes: - - message: Introduce new health status icons. - type: Add - - message: Enhance the Medical HUD to display the health status of players and mobs - using the newly added icons. - type: Tweak - id: 6182 - time: '2024-03-18T13:56:12.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26027 -- author: Plykiya - changes: - - message: Lone operatives now start with 60TC instead of 40TC. - type: Tweak - id: 6183 - time: '2024-03-18T14:15:53.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26130 -- author: PJB3005 - changes: - - message: Fixed hardsuits in space causing high pressure damage - type: Fix - id: 6184 - time: '2024-03-18T16:46:31.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26236 -- author: potato1234x - changes: - - message: Added crafting recipes for wall lockers and secure lockers - type: Add - - message: Fixed secure lockers and wall lockers not being deconstructible - type: Fix - id: 6185 - time: '2024-03-18T20:53:13.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/24942 -- author: brainfood1183 - changes: - - message: Added Spray Paints. - type: Add - id: 6186 - time: '2024-03-18T21:29:48.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/23003 -- author: PJB3005 - changes: - - message: Bans are now shown in the "adminremarks" command. - type: Add - id: 6187 - time: '2024-03-18T21:31:34.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26240 -- author: Terraspark4941 - changes: - - message: The TEG page in the guidebook has been updated with proper information! - type: Tweak - id: 6188 - time: '2024-03-18T21:33:07.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26170 -- author: Plykiya - changes: - - message: You can now craft ducky slippers. - type: Add - id: 6189 - time: '2024-03-18T21:34:35.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26138 -- author: Boaz1111 - changes: - - message: Lone operatives now require a minimum of 20 players to spawn - type: Tweak - id: 6190 - time: '2024-03-18T21:36:24.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26244 -- author: shampunj - changes: - - message: Zombies now passively heal 1 heat/shock damage every 50 seconds - type: Tweak - id: 6191 - time: '2024-03-18T21:47:39.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25925 -- author: Killerqu00 - changes: - - message: Initial Infected now can see each other. - type: Add - - message: Initial Infected can now see zombified entities. - type: Add - id: 6192 - time: '2024-03-18T21:57:36.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25934 -- author: nikthechampiongr - changes: - - message: You can now use the SCRAM! implant while cuffed. - type: Fix - - message: The freedom implant can no longer be used while in crit, or dead. - type: Fix - id: 6193 - time: '2024-03-18T22:35:46.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25978 -- author: SlamBamActionman - changes: - - message: Recyclers no longer delete items stored inside of recycled entities. - type: Fix - id: 6194 - time: '2024-03-19T02:36:22.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26045 -- author: Vermidia - changes: - - message: Made the Artifact Reports page up to date with current Xenoarchaeology - type: Tweak - id: 6195 - time: '2024-03-19T03:22:21.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26252 -- author: keronshb - changes: - - message: Fixed a bug where stores were enabling refunds after a purchase - type: Fix - id: 6196 - time: '2024-03-19T03:48:52.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26251 -- author: Errant - changes: - - message: Thrown objects can no longer slip while they are still in flight. - type: Tweak - id: 6197 - time: '2024-03-20T11:57:39.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/24494 -- author: Killerqu00 - changes: - - message: Airlock wires are now different between departments. - type: Tweak - id: 6198 - time: '2024-03-20T16:07:38.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26247 -- author: Tayrtahn - changes: - - message: Pizza is no longer considered a fruit. - type: Tweak - id: 6199 - time: '2024-03-20T19:03:53.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26293 -- author: jamessimo - changes: - - message: Ratkings and Rat servants eyes now glow in the dark - type: Tweak - id: 6200 - time: '2024-03-21T13:16:18.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26300 -- author: nikthechampiongr - changes: - - message: Scram! implant no longer allows for someone to keep pulling you when - it teleports you. - type: Fix - id: 6201 - time: '2024-03-21T17:42:33.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26309 - author: Plykiya changes: - message: Door remote UI now shows the mode it is in. @@ -3853,3 +3535,318 @@ id: 6659 time: '2024-06-01T05:51:16.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28424 +- author: Beck Thompson + changes: + - message: ID computer will now select passenger as the default id icon instead + of atmospheric technician. + type: Fix + id: 6660 + time: '2024-06-01T17:29:46.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28462 +- author: AJCM-git + changes: + - message: Machine parts are now stackable + type: Tweak + id: 6661 + time: '2024-06-01T17:49:28.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28434 +- author: DrSmugleaf + changes: + - message: Disabled the seeing rainbows screen effect when reduced motion is enabled + in the options menu. + type: Tweak + id: 6662 + time: '2024-06-02T01:41:06.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28496 +- author: Cojoke-dot + changes: + - message: Lasers now pass through Glass External Airlocks and Glass Shuttle Airlocks + type: Fix + id: 6663 + time: '2024-06-02T04:13:12.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28065 +- author: slarticodefast + changes: + - message: Fixed the flash effect getting darker with each appearence. + type: Fix + - message: Fixed short-sightedness making you immune to flashes. + type: Fix + id: 6664 + time: '2024-06-02T04:17:53.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/27369 +- author: AJCM-git + changes: + - message: Midround antags work again + type: Fix + id: 6665 + time: '2024-06-02T16:52:40.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28523 +- author: Errant-4 + changes: + - message: Map labels no longer suddenly disappear for the rest of the round. + type: Fix + id: 6666 + time: '2024-06-02T17:30:27.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28518 +- author: Whisper + changes: + - message: Renamed the player-obtainable "admin cloak" to "weh cloak". + type: Tweak + id: 6667 + time: '2024-06-03T02:18:49.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28540 +- author: Vermidia + changes: + - message: Glass shards are no longer weldable with an unlit welder. + type: Fix + id: 6668 + time: '2024-06-03T03:28:53.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/27959 +- author: AJCM-git + changes: + - message: Biomass reclaimers no longer act as a void to any unfortunate belongings + a corpse may be wearing + type: Tweak + id: 6669 + time: '2024-06-03T03:30:00.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28544 +- author: Laneron + changes: + - message: Jetpack UI window now has correctly displayed header + type: Fix + id: 6670 + time: '2024-06-03T03:33:31.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28545 +- author: Vermidia + changes: + - message: SyndiCats now spawn from a radio like other reinforcements. + type: Tweak + id: 6671 + time: '2024-06-03T11:54:32.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28492 +- author: Cojoke-dot + changes: + - message: Medibots will now occasionally talk + type: Tweak + id: 6672 + time: '2024-06-03T12:05:14.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28543 +- author: Cojoke-dot + changes: + - message: The player can now use the gasping emote + type: Tweak + id: 6673 + time: '2024-06-03T12:10:25.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28466 +- author: Cojoke-dot + changes: + - message: Bullets no longer hit crates unless directly clicked on while shooting. + type: Tweak + - message: Bullets now pass over mobs that are lying down unless directly clicked + on while shooting. + type: Tweak + id: 6674 + time: '2024-06-03T13:04:07.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28072 +- author: AJCM-git + changes: + - message: Ninja stealth mode, stealth boxes and everything with stealth won't show + up in equipment HUDs like the medical or security HUD anymore. + type: Fix + id: 6675 + time: '2024-06-03T16:12:21.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28270 +- author: TheShuEd + changes: + - message: Added italian and cowboy accents + type: Add + - message: Now you can't choose all the accents at once. + type: Fix + id: 6676 + time: '2024-06-03T18:47:06.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28046 +- author: RenQ + changes: + - message: The moths now have their own "death grasp" + type: Add + id: 6677 + time: '2024-06-03T18:48:01.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28409 +- author: lzk228 + changes: + - message: Borgs ID now will be shown in LogProbe instead of Unknown. + type: Tweak + id: 6678 + time: '2024-06-03T18:48:44.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/27788 +- author: FairlySadPanda + changes: + - message: Stacking multiple deflecting items at once has been removed; now only + your best item is used. + type: Tweak + id: 6679 + time: '2024-06-04T13:26:20.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28539 +- author: moonheart08 + changes: + - message: AME power output is no longer super-buffed. Larger stations will now + require multiple engines to stay powered. + type: Tweak + id: 6680 + time: '2024-06-04T15:59:33.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28419 +- author: blueDev2 + changes: + - message: Fixed microwave recipes that use stacked materials + type: Fix + id: 6681 + time: '2024-06-04T17:12:01.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28225 +- author: Tayrtahn + changes: + - message: Mops and rags now show the liquids they soak up. + type: Add + id: 6682 + time: '2024-06-04T18:48:24.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28590 +- author: Boaz1111 + changes: + - message: Revamped Cluster's Head Offices + type: Tweak + id: 6683 + time: '2024-06-05T17:50:38.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28627 +- author: Plykiya + changes: + - message: Handcuff range is buffed to one tile of distance. + type: Tweak + id: 6684 + time: '2024-06-05T20:14:56.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28576 +- author: Cojoke-dot + changes: + - message: A variety of objects around the station now need to be clicked on in + order for bullets to hit them. + type: Tweak + id: 6685 + time: '2024-06-05T20:47:28.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28571 +- author: ps3moira + changes: + - message: Lit cigars inhand sprites are now visible. + type: Fix + id: 6686 + time: '2024-06-05T21:16:32.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28641 +- author: Tayrtahn + changes: + - message: Slimes, diona, and kudzu are less dramatically affected by having certain + chemicals splashed on them. + type: Tweak + id: 6687 + time: '2024-06-05T22:08:41.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28591 +- author: Plykiya + changes: + - message: Internals are no longer toggled off if you take your helmet off but still + have a gas mask on and vice versa. + type: Tweak + id: 6688 + time: '2024-06-06T07:01:45.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28595 +- author: Cojoke-dot + changes: + - message: Bots now have Insulation and NoSlip + type: Tweak + id: 6689 + time: '2024-06-06T09:32:37.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28621 +- author: Flareguy + changes: + - message: Nerfed the explosive power of fuel tanks. + type: Tweak + id: 6690 + time: '2024-06-06T10:08:25.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28650 +- author: ElectroJr + changes: + - message: The job/antag preferences window now has some buttons that link to relevant + guidebook entries + type: Add + id: 6691 + time: '2024-06-06T12:05:58.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28614 +- author: Aeshus + changes: + - message: Space Law gives security exception for syndicate communication equipment. + type: Fix + id: 6692 + time: '2024-06-06T19:24:13.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28668 +- author: Dutch-VanDerLinde + changes: + - message: Fixed janitors not spawning with a survival box in their bag + type: Fix + id: 6693 + time: '2024-06-06T22:26:13.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28669 +- author: blueDev2 + changes: + - message: Rebalanced medicated suture and regen mesh to only require 1 brute pack/ointment + respectively + type: Tweak + id: 6694 + time: '2024-06-07T01:24:08.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28679 +- author: AJCM-git + changes: + - message: Guidebook no longer lists every single rule + type: Fix + id: 6695 + time: '2024-06-07T11:28:55.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28680 +- author: Plykiya + changes: + - message: You can now place construction ghosts without getting blocked by things + like grilles or windows. + type: Fix + id: 6696 + time: '2024-06-08T00:57:07.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28706 +- author: Lyndomen + changes: + - message: Space Dragons will drop bodies upon being gibbed/butchered + type: Fix + id: 6697 + time: '2024-06-08T05:49:42.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28709 +- author: metalgearsloth + changes: + - message: Fix some loadout groups not getting validated properly. + type: Fix + id: 6698 + time: '2024-06-08T10:03:54.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28731 +- author: ElectroJr + changes: + - message: Fixed RGB lights & eswords sometimes not working and showing up as black. + type: Fix + id: 6699 + time: '2024-06-08T10:27:21.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28741 +- author: Whisper, DrSmugleaf + changes: + - message: Players can now add ' to their character names. Please ensure your character + names still follow server naming conventions. + type: Add + id: 6700 + time: '2024-06-08T10:37:55.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28652 +- author: EmoGarbage404 + changes: + - message: Fixed singularity decay being underpowered, leading to continuous growth + on higher PA strengths. + type: Fix + id: 6701 + time: '2024-06-08T14:36:47.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28743 diff --git a/Resources/Locale/en-US/administration/ui/tabs/object-tab.ftl b/Resources/Locale/en-US/administration/ui/tabs/object-tab.ftl new file mode 100644 index 0000000000..0288efcaa4 --- /dev/null +++ b/Resources/Locale/en-US/administration/ui/tabs/object-tab.ftl @@ -0,0 +1,2 @@ +object-tab-entity-id = Entity ID +object-tab-object-name = Object name diff --git a/Resources/Locale/en-US/advertisements/other/medibot.ftl b/Resources/Locale/en-US/advertisements/other/medibot.ftl new file mode 100644 index 0000000000..9e919ce49f --- /dev/null +++ b/Resources/Locale/en-US/advertisements/other/medibot.ftl @@ -0,0 +1,9 @@ +advertisement-medibot-1 = What kind of medbay is this? Everyone's dropping like dead flies. +advertisement-medibot-2 = I knew it, I should've been a plastic surgeon. +advertisement-medibot-3 = There's always a catch, and I'm the best there is. +advertisement-medibot-4 = An apple a day keeps me away. +advertisement-medibot-5 = I'm different! +advertisement-medibot-6 = Fuck you. +advertisement-medibot-7 = Why are we still here? Just to suffer? +advertisement-medibot-8 = I...I've never lost a patient before. Not today, I mean. +advertisement-medibot-9 = Lexorin in. diff --git a/Resources/Locale/en-US/atmos/gas-tank-component.ftl b/Resources/Locale/en-US/atmos/gas-tank-component.ftl index ae10d5630c..1f2d8f1e9f 100644 --- a/Resources/Locale/en-US/atmos/gas-tank-component.ftl +++ b/Resources/Locale/en-US/atmos/gas-tank-component.ftl @@ -14,7 +14,6 @@ comp-gas-tank-examine-closed-valve = Gas release valve is [color=green]closed[/c control-verb-open-control-panel-text = Open Control Panel ## UI -gas-tank-window-label = Gas Tank gas-tank-window-internals-toggle-button = Toggle gas-tank-window-output-pressure-label = Output Pressure gas-tank-window-tank-pressure-text = Pressure: {$tankPressure} kPA diff --git a/Resources/Locale/en-US/components/power-monitoring-component.ftl b/Resources/Locale/en-US/components/power-monitoring-component.ftl index e84c09e60d..9433c9ea9e 100644 --- a/Resources/Locale/en-US/components/power-monitoring-component.ftl +++ b/Resources/Locale/en-US/components/power-monitoring-component.ftl @@ -14,6 +14,7 @@ power-monitoring-window-total-sources = Total generator output power-monitoring-window-total-battery-usage = Total battery usage power-monitoring-window-total-loads = Total network loads power-monitoring-window-value = { POWERWATTS($value) } +power-monitoring-window-button-value = {$value} W power-monitoring-window-show-inactive-consumers = Show Inactive Consumers power-monitoring-window-show-cable-networks = Toggle cable networks diff --git a/Resources/Locale/en-US/criminal-records/criminal-records.ftl b/Resources/Locale/en-US/criminal-records/criminal-records.ftl index f603b44666..6d6a97300c 100644 --- a/Resources/Locale/en-US/criminal-records/criminal-records.ftl +++ b/Resources/Locale/en-US/criminal-records/criminal-records.ftl @@ -31,14 +31,14 @@ criminal-records-permission-denied = Permission denied ## Security channel notifications -criminal-records-console-wanted = {$name} is wanted by {$officer} for: {$reason}. +criminal-records-console-wanted = {$name} was made wanted by {$officer} for: {$reason}. criminal-records-console-suspected = {$officer} marked {$name} as suspicious because of: {$reason} -criminal-records-console-not-suspected = {$name} has been cleared as a suspect by {$officer}. +criminal-records-console-not-suspected = {$name} has been cleared of suspicion by {$officer}. criminal-records-console-detained = {$name} has been detained by {$officer}. criminal-records-console-released = {$name} has been released by {$officer}. -criminal-records-console-not-wanted = {$name} is no longer wanted, according to {$officer}. +criminal-records-console-not-wanted = {$officer} cleared the wanted status of {$name}. criminal-records-console-paroled = {$name} has been released on parole by {$officer}. -criminal-records-console-not-parole = {$name}'s parole status has been lifted by {$officer}. +criminal-records-console-not-parole = {$officer} cleared the parole status of {$name}. criminal-records-console-unknown-officer = ## Filters diff --git a/Resources/Locale/en-US/deltav/flavors/flavor-profiles.ftl b/Resources/Locale/en-US/deltav/flavors/flavor-profiles.ftl index 48f42e641b..c3d84d1a24 100644 --- a/Resources/Locale/en-US/deltav/flavors/flavor-profiles.ftl +++ b/Resources/Locale/en-US/deltav/flavors/flavor-profiles.ftl @@ -5,7 +5,6 @@ flavor-complex-enthralling = enthralling flavor-complex-sublime = sublime flavor-complex-holy = heavenly flavor-base-seeds = seeds -flavor-complex-cotton = like cotton flavor-complex-vanilla = like vanilla flavor-complex-soju = like bold, alcoholic rice flavor-complex-orangecreamcicle = like creamy, alcoholic orange juice @@ -26,7 +25,6 @@ flavor-complex-greengrass = like a holiday in the sun flavor-complex-daiquiri = fashionable flavor-complex-arsonistsbrew = like ash and flame flavor-complex-healthcodeviolation = ominous -flavor-complex-pumpkin = like pumpkin flavor-complex-blellow = like an impossible color flavor-complex-candy-strawberry = like strawberries flavor-complex-candy-bubblegum = like bubble gum diff --git a/Resources/Locale/en-US/deltav/prototypes/catalog/cargo/cargo-fun.ftl b/Resources/Locale/en-US/deltav/prototypes/catalog/cargo/cargo-fun.ftl deleted file mode 100644 index 32d3ab61c3..0000000000 --- a/Resources/Locale/en-US/deltav/prototypes/catalog/cargo/cargo-fun.ftl +++ /dev/null @@ -1,2 +0,0 @@ -ent-CrateFunBBGun = { ent-CrateFunBBGun } - .desc = { ent-CrateFunBBGun.desc } diff --git a/Resources/Locale/en-US/entity-categories.ftl b/Resources/Locale/en-US/entity-categories.ftl new file mode 100644 index 0000000000..e971990e6c --- /dev/null +++ b/Resources/Locale/en-US/entity-categories.ftl @@ -0,0 +1,2 @@ +entity-category-name-actions = Actions +entity-category-name-objectives = Objectives diff --git a/Resources/Locale/en-US/interaction/interaction-popup-component.ftl b/Resources/Locale/en-US/interaction/interaction-popup-component.ftl index 7db99c3d0a..55be1fb3b9 100644 --- a/Resources/Locale/en-US/interaction/interaction-popup-component.ftl +++ b/Resources/Locale/en-US/interaction/interaction-popup-component.ftl @@ -36,23 +36,23 @@ petting-success-nymph = You pet {THE($target)} on {POSS-ADJ($target)} wooden lit petting-failure-generic = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} aloof towards you. petting-failure-bat = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} too hard to catch! -petting-failure-carp = You reach out to pet {THE($target)}, but {POSS_ADJ($target)} sharp teeth make you think twice. +petting-failure-carp = You reach out to pet {THE($target)}, but {POSS-ADJ($target)} sharp teeth make you think twice. petting-failure-corrupted-corgi = You reach out to pet {THE($target)}, but think better of it. -petting-failure-crab = You reach out to pet {THE($target)}, but {SUBJECT($target)} snaps {POSS-ADJ($target)} claws in your general direction! +petting-failure-crab = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BASIC($target, "snap", "snaps")} {POSS-ADJ($target)} claws in your general direction! petting-failure-dehydrated-carp = You pet {THE($target)} on {POSS-ADJ($target)} dry little head. -petting-failure-goat = You reach out to pet {THE($target)}, but {SUBJECT($target)} stubbornly refuses! +petting-failure-goat = You reach out to pet {THE($target)}, but {SUBJECT($target)} stubbornly {CONJUGATE-BASIC($target, "refuse", "refuses")}! petting-failure-goose = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} too horrible! petting-failure-possum = You reach out to pet {THE($target)}, but are met with hisses and snarls! petting-failure-pig = You reach out to pet {THE($target)}, but are met with irritated oinks and squeals! -petting-failure-raccoon = You reach out to pet {THE($target)}, but {THE($target)} is busy raccooning around. -petting-failure-sloth = You reach out to pet {THE($target)}, but {SUBJECT($target)} somehow dodge with ludicrous speed! +petting-failure-raccoon = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} busy raccooning around. +petting-failure-sloth = You reach out to pet {THE($target)}, but {SUBJECT($target)} somehow { CONJUGATE-BASIC($target, "dodge", "dodges") } with ludicrous speed! petting-failure-holo = You reach out to pet {THE($target)}, but {POSS-ADJ($target)} spikes almost impale your hand! -petting-failure-dragon = You raise your hand, but as {THE($target)} roars, you decide you'd rather not be toasty carp food. -petting-failure-hamster = You reach out to pet {THE($target)}, but {SUBJECT($target)} attempts to bite your finger and only your quick reflexes save you from an almost fatal injury. -petting-failure-bear = You reach out to pet {THE($target)}, but {SUBJECT($target)} growls, making you think twice. -petting-failure-monkey = You reach out to pet {THE($target)}, but {SUBJECT($target)} almost bites your fingers! -petting-failure-nymph = You reach out to pet {THE($target)}, but {SUBJECT($target)} move their branches away. -petting-failure-shadow = You're trying to pet {THE($target)}, but your hand passes through the cold darkness of his body. +petting-failure-dragon = You raise your hand, but as {THE($target)} {CONJUGATE-BASIC($target, "roar", "roars")}, you decide you'd rather not be toasty carp food. +petting-failure-hamster = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BASIC($target, "attempt", "attempts")} to bite your finger and only your quick reflexes save you from an almost fatal injury. +petting-failure-bear = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BASIC($target, "growl", "growls")}, making you think twice. +petting-failure-monkey = You reach out to pet {THE($target)}, but {SUBJECT($target)} almost {CONJUGATE-BASIC($target, "bite", "bites")} your fingers! +petting-failure-nymph = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BASIC($target, "move", "moves")} {POSS-ADJ($target)} branches away. +petting-failure-shadow = You try to pet {THE($target)}, but your hand passes through the cold darkness of {POSS-ADJ($target)} body. ## Petting silicons @@ -62,7 +62,7 @@ petting-success-cleanbot = You pet {THE($target)} on {POSS-ADJ($target)} damp me petting-success-medibot = You pet {THE($target)} on {POSS-ADJ($target)} sterile metal head. petting-success-recycler = You pet {THE($target)} on {POSS-ADJ($target)} mildly threatening steel exterior. -petting-failure-honkbot = You reach out to pet {THE($target)}, but {SUBJECT($target)} honks in refusal! +petting-failure-honkbot = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BASIC($target, "honk", "honks")} in refusal! petting-failure-cleanbot = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} busy mopping! petting-failure-mimebot = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} busy miming! petting-failure-medibot = You reach out to pet {THE($target)}, but {POSS-ADJ($target)} syringe nearly stabs your hand! @@ -80,5 +80,4 @@ hugging-success-generic-target = { CAPITALIZE(THE($user)) } hugs you. ## Other petting-success-tesla = You pet {THE($target)}, violating the laws of nature and physics. - -petting-failure-tesla = You reach out towards {THE($target)}, but it zaps your hand away. +petting-failure-tesla = You reach out towards {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BASIC($target, "zap", "zaps")} your hand away. diff --git a/Resources/Locale/en-US/medical/components/defibrillator.ftl b/Resources/Locale/en-US/medical/components/defibrillator.ftl index 1c32dd801d..dc4a03aa3b 100644 --- a/Resources/Locale/en-US/medical/components/defibrillator.ftl +++ b/Resources/Locale/en-US/medical/components/defibrillator.ftl @@ -1,3 +1,4 @@ defibrillator-not-on = The defibrillator isn't turned on. defibrillator-no-mind = No intelligence pattern can be detected in patient's brain. Further attempts futile. defibrillator-rotten = Body decomposition detected: resuscitation failed. +defibrillator-unrevivable = This patient is unable to be revived due to a unique body composition. diff --git a/Resources/Locale/en-US/nyanotrasen/abilities/psionic.ftl b/Resources/Locale/en-US/nyanotrasen/abilities/psionic.ftl index 91ae21233a..d0e8db72f8 100644 --- a/Resources/Locale/en-US/nyanotrasen/abilities/psionic.ftl +++ b/Resources/Locale/en-US/nyanotrasen/abilities/psionic.ftl @@ -25,9 +25,6 @@ accept-psionics-window-prompt-text-part = You rolled a psionic power! action-name-psionic-invisibility = Psionic Invisibility action-description-psionic-invisibility = Render yourself invisible to any entity that could potentially be psychic. Borgs, animals, and so on are not affected. -action-name-psionic-invisibility = Psionic Invisibility -action-description-psionic-invisibility = Render yourself invisible to any entity that could potentially be psychic. Borgs, animals, and so on are not affected. - action-name-psionic-invisibility-off = Turn Off Psionic Invisibility action-description-psionic-invisibility-off = Return to visibility, and receive a stun. diff --git a/Resources/Locale/en-US/nyanotrasen/accent/accents.ftl b/Resources/Locale/en-US/nyanotrasen/accent/accents.ftl index 2699d71bc6..866888cf45 100644 --- a/Resources/Locale/en-US/nyanotrasen/accent/accents.ftl +++ b/Resources/Locale/en-US/nyanotrasen/accent/accents.ftl @@ -1,6 +1,5 @@ # Mothroach -accent-words-mothroach-1 = Squeak! -accent-words-mothroach-2 = Chirp! +accent-words-mothroach-2 = Squeak! accent-words-mothroach-3 = Peep! accent-words-mothroach-4 = Eeee! accent-words-mothroach-5 = Eep! diff --git a/Resources/Locale/en-US/nyanotrasen/reagents/meta/consumable/drink/drink.ftl b/Resources/Locale/en-US/nyanotrasen/reagents/meta/consumable/drink/drink.ftl index e9d04bd951..eef7207132 100644 --- a/Resources/Locale/en-US/nyanotrasen/reagents/meta/consumable/drink/drink.ftl +++ b/Resources/Locale/en-US/nyanotrasen/reagents/meta/consumable/drink/drink.ftl @@ -7,11 +7,5 @@ reagent-desc-pinkdrink = Entire civilizations have crumbled trying to decide if reagent-name-bubbletea = bubble tea reagent-desc-bubbletea = Big straw not included. -reagent-name-the-martinez = The Martinez -reagent-desc-the-martinez = The edgerunner legend. Remembered by a drink, Forgotten by a drunk. - -reagent-name-holywater = holy water -reagent-desc-holywater = Water blessed by some otherworldly powers. - reagent-name-lean = lean reagent-desc-lean = A disgusting mixture of soda, booze, and cough syrup. diff --git a/Resources/Locale/en-US/nyanotrasen/seeds/seeds.ftl b/Resources/Locale/en-US/nyanotrasen/seeds/seeds.ftl deleted file mode 100644 index 6bce63a6cb..0000000000 --- a/Resources/Locale/en-US/nyanotrasen/seeds/seeds.ftl +++ /dev/null @@ -1,2 +0,0 @@ -seeds-killertomato-name = killer tomato -seeds-killertomato-display-name = killer tomatoes diff --git a/Resources/Locale/en-US/nyanotrasen/tools/tool-qualities.ftl b/Resources/Locale/en-US/nyanotrasen/tools/tool-qualities.ftl deleted file mode 100644 index c3c4e6ad2f..0000000000 --- a/Resources/Locale/en-US/nyanotrasen/tools/tool-qualities.ftl +++ /dev/null @@ -1,2 +0,0 @@ -tool-quality-digging-name = Digging -tool-quality-digging-tool-name = Shovel diff --git a/Resources/Locale/en-US/prayers/prayers.ftl b/Resources/Locale/en-US/prayers/prayers.ftl index 07713bc821..532ba4954f 100644 --- a/Resources/Locale/en-US/prayers/prayers.ftl +++ b/Resources/Locale/en-US/prayers/prayers.ftl @@ -13,3 +13,4 @@ prayer-popup-notify-centcom-sent = You left a voicemail message for Central Comm prayer-popup-notify-syndicate-sent = You left a voicemail message for Syndicate High Command... prayer-popup-notify-pray-sent = Your message has been sent to the gods... prayer-popup-notify-pray-locked = You don't feel worthy enough... +prayer-popup-notify-pray-ui-message = Message diff --git a/Resources/Locale/en-US/preferences/ui/humanoid-profile-editor.ftl b/Resources/Locale/en-US/preferences/ui/humanoid-profile-editor.ftl index c7a24d5405..bfdbeb2f14 100644 --- a/Resources/Locale/en-US/preferences/ui/humanoid-profile-editor.ftl +++ b/Resources/Locale/en-US/preferences/ui/humanoid-profile-editor.ftl @@ -42,7 +42,7 @@ humanoid-profile-editor-department-jobs-label = {$departmentName} jobs humanoid-profile-editor-antags-tab = Antags humanoid-profile-editor-antag-preference-yes-button = Yes humanoid-profile-editor-antag-preference-no-button = No -humanoid-profile-editor-traits-tab = Traits + humanoid-profile-editor-job-priority-high-button = High humanoid-profile-editor-job-priority-medium-button = Medium humanoid-profile-editor-job-priority-low-button = Low @@ -50,3 +50,12 @@ humanoid-profile-editor-job-priority-never-button = Never humanoid-profile-editor-naming-rules-warning = Warning: Offensive or LRP IC names and descriptions will lead to admin intervention on this server. Read our \[Rules\] for more. humanoid-profile-editor-markings-tab = Markings humanoid-profile-editor-flavortext-tab = Description + +# Traits +humanoid-profile-editor-traits-tab = Traits +humanoid-profile-editor-no-traits = No traits available + +humanoid-profile-editor-trait-count-hint = Points available: [{$current}/{$max}] + +trait-category-disabilities = Disabilities +trait-category-speech = Speech traits \ No newline at end of file diff --git a/Resources/Locale/en-US/speech/speech-chatsan.ftl b/Resources/Locale/en-US/speech/speech-chatsan.ftl index 25e6c6f1ea..df6cde00a2 100644 --- a/Resources/Locale/en-US/speech/speech-chatsan.ftl +++ b/Resources/Locale/en-US/speech/speech-chatsan.ftl @@ -120,3 +120,18 @@ chatsan-replacement-43 = i guess chatsan-word-44 = tbf chatsan-replacement-44 = to be fair + +chatsan-word-45 = tysm +chatsan-replacement-45 = thank you so much + +chatsan-word-46 = tyvm +chatsan-replacement-46 = thank you very much + +chatsan-word-47 = cya +chatsan-replacement-47 = see ya + +chatsan-word-48 = rn +chatsan-replacement-48 = right now + +chatsan-word-49 = atm +chatsan-replacement-49 = at the moment diff --git a/Resources/Locale/en-US/store/revenant-catalog.ftl b/Resources/Locale/en-US/store/revenant-catalog.ftl new file mode 100644 index 0000000000..d3cbbf724f --- /dev/null +++ b/Resources/Locale/en-US/store/revenant-catalog.ftl @@ -0,0 +1,11 @@ +revenant-defile-name = Defile +revenant-defile-desc = Defiles the surrounding area, ripping up floors, damaging windows, opening containers, and throwing items. Using it leaves you vulnerable to attacks for a short period of time. + +revenant-overload-name = Overload Lights +revenant-overload-desc = Overloads all nearby lights, causing lights to pulse and sending out dangerous lightning. Using it leaves you vulnerable to attacks for a long period of time. + +revenant-blight-name = Blight +revenant-blight-desc = Infects all nearby organisms with an infectious disease that causes toxic buildup and tiredness. Using it leaves you vulnerable to attacks for a medium period of time. + +revenant-malfunction-name = Malfunction +revenant-malfunction-desc = Makes nearby electronics stop working properly. Using it leaves you vulnerable to attacks for a long period of time. diff --git a/Resources/Locale/en-US/store/store.ftl b/Resources/Locale/en-US/store/store.ftl index 997afedfc0..5c1a46339e 100644 --- a/Resources/Locale/en-US/store/store.ftl +++ b/Resources/Locale/en-US/store/store.ftl @@ -8,3 +8,6 @@ store-ui-traitor-warning = Operatives must lock their uplinks after use to avoid store-withdraw-button-ui = Withdraw {$currency} store-ui-button-out-of-stock = {""} (Out of Stock) store-not-account-owner = This {$store} is not bound to you! + +store-preset-name-uplink = Uplink +store-preset-name-spellbook = Spellbook diff --git a/Resources/Locale/en-US/store/uplink-catalog.ftl b/Resources/Locale/en-US/store/uplink-catalog.ftl index 3dd138016b..52c66727f0 100644 --- a/Resources/Locale/en-US/store/uplink-catalog.ftl +++ b/Resources/Locale/en-US/store/uplink-catalog.ftl @@ -370,8 +370,8 @@ uplink-syndicate-sponge-box-desc = A box containing 6 syndicate sponges disguise uplink-slipocalypse-clustersoap-name = Slipocalypse Clustersoap uplink-slipocalypse-clustersoap-desc = Scatters arounds small pieces of syndicate-brand soap after being thrown, these pieces of soap evaporate after 60 seconds. -uplink-mobcat-microbomb-name = SyndiCat -uplink-mobcat-microbomb-desc = A hand cat equipped with a microbomb implant. Explodes when seriously injured. Can bite painfully +uplink-mobcat-microbomb-name = SyndiCat Teleporter +uplink-mobcat-microbomb-desc = Call in a handy cat equipped with a microbomb implant. Explodes when seriously injured. Can bite painfully. uplink-chameleon-projector-name = Chameleon Projector uplink-chameleon-projector-desc = Disappear in plain sight by creating a hologram of an item around you. Do not use this to play the game "Object Search". diff --git a/Resources/Locale/en-US/tips.ftl b/Resources/Locale/en-US/tips.ftl index 3a3f66deb7..37a25e2973 100644 --- a/Resources/Locale/en-US/tips.ftl +++ b/Resources/Locale/en-US/tips.ftl @@ -48,6 +48,7 @@ tips-dataset-47 = As a Salvage Specialist, you can use your proto-kinetic accele tips-dataset-48 = As a Salvage Specialist, never forget to mine ore! Ore can be sold to logistics for a pretty penny, be used for construction, and also be used by Scientists for fancy technology. tips-dataset-49 = As a Salvage Specialist, try asking epistemics for a tethergun. It can be used to grab items off of salvage wrecks extremely efficiently! tips-dataset-50 = As a Salvage Specialist, try asking epistemics for a grappling hook. It can be used to propel yourself onto wrecks, or if stuck in space you don't have to rely on the proto-kinetic accelerator. +tips-dataset-51 = Tip #51 does not exist and has never existed. Ignore any rumors to the contrary. tips-dataset-52 = As a Salvage Specialist, consider cooperating with the Cargo Technicians. They can order you a wide variety of useful items, including ones that may be hard to get otherwise, such laser guns and shuttle building materials. tips-dataset-53 = As a Cargo Technician, consider asking epistemics for a Ripley APLU. When paired with a hydraulic clamp, you can grab valuable maintenance objects like fuel tanks much more easily, and make deliveries in a swift manner. tips-dataset-54 = As a Cargo Technician, try to maintain a surplus of materials. They are extremely useful for Scientists and Station Engineers to have immediate access to. diff --git a/Resources/Locale/en-US/traits/traits.ftl b/Resources/Locale/en-US/traits/traits.ftl index c4bb431e48..6dd834e8a1 100644 --- a/Resources/Locale/en-US/traits/traits.ftl +++ b/Resources/Locale/en-US/traits/traits.ftl @@ -12,7 +12,7 @@ trait-pacifist-desc = You cannot attack or hurt any living beings. permanent-blindness-trait-examined = [color=lightblue]{CAPITALIZE(POSS-ADJ($target))} eyes are glassy and unfocused. It doesn't seem like {SUBJECT($target)} can see you well, if at all.[/color] -trait-lightweight-name = Lightweight Drunk +trait-lightweight-name = Lightweight drunk trait-lightweight-desc = Alcohol has a stronger effect on you trait-muted-name = Muted @@ -21,23 +21,32 @@ trait-muted-desc = You can't speak trait-paracusia-name = Paracusia trait-paracusia-desc = You hear sounds that aren't really there -trait-pirate-accent-name = Pirate Accent +trait-unrevivable-name = Unrevivable +trait-unrevivable-desc = You are unable to be revived by defibrillators. + +trait-pirate-accent-name = Pirate accent trait-pirate-accent-desc = You can't stop speaking like a pirate! trait-accentless-name = Accentless trait-accentless-desc = You don't have the accent that your species would usually have -trait-frontal-lisp-name = Frontal Lisp +trait-frontal-lisp-name = Frontal lisp trait-frontal-lisp-desc = You thpeak with a lithp -trait-socialanxiety-name = Social Anxiety +trait-socialanxiety-name = Social anxiety trait-socialanxiety-desc = You are anxious when you speak and stutter. -trait-southern-name = Southern Drawl +trait-southern-name = Southern drawl trait-southern-desc = You have a different way of speakin'. trait-snoring-name = Snoring trait-snoring-desc = You will snore while sleeping. trait-liar-name = Pathological liar -trait-liar-desc = You can hardly bring yourself to tell the truth. Sometimes you lie anyway. \ No newline at end of file +trait-liar-desc = You can hardly bring yourself to tell the truth. Sometimes you lie anyway. + +trait-cowboy-name = Cowboy accent +trait-cowboy-desc = You speak with a distinct cowboy accent! + +trait-italian-name = Italian accent +trait-italian-desc = Mamma mia! You seem to have lived in space italy! diff --git a/Resources/Maps/Salvage/small-chapel.yml b/Resources/Maps/Salvage/small-chapel.yml index 2af3e1d09d..2c3b117a4a 100644 --- a/Resources/Maps/Salvage/small-chapel.yml +++ b/Resources/Maps/Salvage/small-chapel.yml @@ -346,13 +346,6 @@ entities: - type: Transform pos: 0.5,5.5 parent: 1 -- proto: BloodTomatoSeeds - entities: - - uid: 51 - components: - - type: Transform - pos: 4.5964127,8.285444 - parent: 1 - proto: BookshelfFilled entities: - uid: 62 @@ -538,13 +531,6 @@ entities: - type: Transform pos: 0.5,1.5 parent: 1 -- proto: CrateFunATV - entities: - - uid: 77 - components: - - type: Transform - pos: 6.5,4.5 - parent: 1 - proto: CrateStoneGrave entities: - uid: 34 @@ -630,6 +616,13 @@ entities: - type: Transform pos: 6.5,5.5 parent: 1 +- proto: KillerTomatoSeeds + entities: + - uid: 51 + components: + - type: Transform + pos: 4.6947265,8.268279 + parent: 1 - proto: Lamp entities: - uid: 66 diff --git a/Resources/Migrations/migration.yml b/Resources/Migrations/migration.yml index f8e6f46fce..16eb8ed9f8 100644 --- a/Resources/Migrations/migration.yml +++ b/Resources/Migrations/migration.yml @@ -332,3 +332,6 @@ CrateJanitorExplosive: ClosetJanitorBombFilled # 2024-05-27 DoorRemoteFirefight: null + +# 2024-06-03 +AirlockServiceCaptainLocked: AirlockCaptainLocked diff --git a/Resources/Prototypes/Accents/word_replacements.yml b/Resources/Prototypes/Accents/word_replacements.yml index 8158122f40..ebeefd5a0e 100644 --- a/Resources/Prototypes/Accents/word_replacements.yml +++ b/Resources/Prototypes/Accents/word_replacements.yml @@ -425,6 +425,11 @@ # chatsan-word-42: chatsan-replacement-42 chatsan-word-43: chatsan-replacement-43 chatsan-word-44: chatsan-replacement-44 + chatsan-word-45: chatsan-replacement-45 + chatsan-word-46: chatsan-replacement-46 + chatsan-word-47: chatsan-replacement-47 + chatsan-word-48: chatsan-replacement-48 + chatsan-word-49: chatsan-replacement-49 - type: accent id: liar diff --git a/Resources/Prototypes/Actions/borgs.yml b/Resources/Prototypes/Actions/borgs.yml index 6d35c69cf6..a0168ef00f 100644 --- a/Resources/Prototypes/Actions/borgs.yml +++ b/Resources/Prototypes/Actions/borgs.yml @@ -2,7 +2,6 @@ id: ActionViewLaws name: View Laws description: View the laws that you must follow. - noSpawn: true components: - type: InstantAction itemIconStyle: NoItem diff --git a/Resources/Prototypes/Actions/crit.yml b/Resources/Prototypes/Actions/crit.yml index 705ee6ee6b..c5712844bf 100644 --- a/Resources/Prototypes/Actions/crit.yml +++ b/Resources/Prototypes/Actions/crit.yml @@ -3,7 +3,6 @@ id: ActionCritSuccumb name: Succumb description: Accept your fate. - noSpawn: true components: - type: InstantAction itemIconStyle: NoItem @@ -18,7 +17,6 @@ id: ActionCritFakeDeath name: Fake Death description: Pretend to take your final breath while staying alive. - noSpawn: true components: - type: InstantAction itemIconStyle: NoItem @@ -34,7 +32,6 @@ id: ActionCritLastWords name: Say Last Words description: Whisper your last words to anyone nearby, and then succumb to your fate. You only have 30 characters to work with. - noSpawn: true components: - type: InstantAction itemIconStyle: NoItem diff --git a/Resources/Prototypes/Actions/diona.yml b/Resources/Prototypes/Actions/diona.yml index 11db30386a..9f80f18178 100644 --- a/Resources/Prototypes/Actions/diona.yml +++ b/Resources/Prototypes/Actions/diona.yml @@ -2,7 +2,6 @@ id: DionaGibAction name: Gib Yourself! description: Split apart into 3 nymphs. - noSpawn: true components: - type: InstantAction icon: @@ -16,7 +15,6 @@ id: DionaReformAction name: Reform description: Reform back into a whole Diona. - noSpawn: true components: - type: InstantAction icon: diff --git a/Resources/Prototypes/Actions/internals.yml b/Resources/Prototypes/Actions/internals.yml index dd83a45332..5982c3daa2 100644 --- a/Resources/Prototypes/Actions/internals.yml +++ b/Resources/Prototypes/Actions/internals.yml @@ -2,7 +2,6 @@ id: ActionToggleInternals name: Toggle Internals description: Breathe from the equipped gas tank. Also requires equipped breath mask. - noSpawn: true components: - type: InstantAction icon: diff --git a/Resources/Prototypes/Actions/mech.yml b/Resources/Prototypes/Actions/mech.yml index 2005133a70..48092f9c5a 100644 --- a/Resources/Prototypes/Actions/mech.yml +++ b/Resources/Prototypes/Actions/mech.yml @@ -2,7 +2,6 @@ id: ActionMechCycleEquipment name: Cycle description: Cycles currently selected equipment - noSpawn: true components: - type: InstantAction itemIconStyle: NoItem @@ -16,7 +15,6 @@ id: ActionMechOpenUI name: Control Panel description: Opens the control panel for the mech - noSpawn: true components: - type: InstantAction itemIconStyle: NoItem @@ -30,7 +28,6 @@ id: ActionMechEject name: Eject description: Ejects the pilot from the mech - noSpawn: true components: - type: InstantAction itemIconStyle: NoItem diff --git a/Resources/Prototypes/Actions/ninja.yml b/Resources/Prototypes/Actions/ninja.yml index 5fe6f23b27..adaf563692 100644 --- a/Resources/Prototypes/Actions/ninja.yml +++ b/Resources/Prototypes/Actions/ninja.yml @@ -3,7 +3,6 @@ id: ActionToggleNinjaGloves name: Toggle ninja gloves description: Toggles all glove actions on left click. Includes your doorjack, draining power, stunning enemies, downloading research and calling in a threat. - noSpawn: true components: - type: InstantAction priority: -13 @@ -14,7 +13,6 @@ id: ActionCreateThrowingStar name: Create throwing star description: Channels suit power into creating a throwing star that deals extra stamina damage. - noSpawn: true components: - type: InstantAction useDelay: 0.5 @@ -29,7 +27,6 @@ id: ActionRecallKatana name: Recall katana description: Teleports the Energy Katana linked to this suit to its wearer, cost based on distance. - noSpawn: true components: - type: InstantAction useDelay: 1 @@ -44,7 +41,6 @@ id: ActionNinjaEmp name: EM Burst description: Disable any nearby technology with an electro-magnetic pulse. - noSpawn: true components: - type: InstantAction icon: @@ -58,7 +54,6 @@ id: ActionTogglePhaseCloak name: Phase cloak description: Toggles your suit's phase cloak. Beware that if you are hit, all abilities are disabled for 5 seconds, including your cloak! - noSpawn: true components: - type: InstantAction # have to plan (un)cloaking ahead of time @@ -71,7 +66,6 @@ id: ActionEnergyKatanaDash name: Katana dash description: Teleport to anywhere you can see, if your Energy Katana is in your hand. - noSpawn: true components: - type: WorldTargetAction icon: diff --git a/Resources/Prototypes/Actions/polymorph.yml b/Resources/Prototypes/Actions/polymorph.yml index 445dc8d9f5..81feba4eac 100644 --- a/Resources/Prototypes/Actions/polymorph.yml +++ b/Resources/Prototypes/Actions/polymorph.yml @@ -2,14 +2,12 @@ id: ActionRevertPolymorph name: Revert description: Revert back into your original form. - noSpawn: true components: - type: InstantAction event: !type:RevertPolymorphActionEvent - type: entity id: ActionPolymorph - noSpawn: true components: - type: InstantAction event: !type:PolymorphActionEvent @@ -19,7 +17,6 @@ id: ActionPolymorphWizardSpider name: Spider Polymorph description: Polymorphs you into a Spider. - noSpawn: true components: - type: InstantAction useDelay: 60 @@ -34,7 +31,6 @@ id: ActionPolymorphWizardRod name: Rod Form description: CLANG! - noSpawn: true components: - type: InstantAction useDelay: 60 diff --git a/Resources/Prototypes/Actions/revenant.yml b/Resources/Prototypes/Actions/revenant.yml index da7b4ba56f..dca491a99b 100644 --- a/Resources/Prototypes/Actions/revenant.yml +++ b/Resources/Prototypes/Actions/revenant.yml @@ -2,7 +2,6 @@ id: ActionRevenantShop name: Shop description: Opens the ability shop. - noSpawn: true components: - type: InstantAction icon: Interface/Actions/shop.png @@ -12,7 +11,6 @@ id: ActionRevenantDefile name: Defile description: Costs 30 Essence. - noSpawn: true components: - type: InstantAction icon: Interface/Actions/defile.png @@ -23,7 +21,6 @@ id: ActionRevenantOverloadLights name: Overload Lights description: Costs 40 Essence. - noSpawn: true components: - type: InstantAction icon: Interface/Actions/overloadlight.png @@ -34,7 +31,6 @@ # id: ActionRevenantBlight # name: Blight # description: Costs 50 Essence. -# noSpawn: true # components: # - type: InstantAction # icon: Interface/Actions/blight.png @@ -45,7 +41,6 @@ id: ActionRevenantMalfunction name: Malfunction description: Costs 60 Essence. - noSpawn: true components: - type: InstantAction icon: Interface/Actions/malfunction.png diff --git a/Resources/Prototypes/Actions/speech.yml b/Resources/Prototypes/Actions/speech.yml index 39db04b1b3..c71ec74880 100644 --- a/Resources/Prototypes/Actions/speech.yml +++ b/Resources/Prototypes/Actions/speech.yml @@ -2,7 +2,6 @@ id: ActionConfigureMeleeSpeech name: Set Battlecry description: Set a custom battlecry for when you attack! - noSpawn: true components: - type: InstantAction itemIconStyle: BigItem diff --git a/Resources/Prototypes/Actions/spider.yml b/Resources/Prototypes/Actions/spider.yml index 14b9fb6ccb..fe37085eca 100644 --- a/Resources/Prototypes/Actions/spider.yml +++ b/Resources/Prototypes/Actions/spider.yml @@ -2,7 +2,6 @@ id: ActionSpiderWeb name: Spider Web description: Spawns a web that slows your prey down. - noSpawn: true components: - type: InstantAction icon: Interface/Actions/web.png @@ -13,7 +12,6 @@ id: ActionSericulture name: Weave silk description: Weave a bit of silk for use in arts and crafts. - noSpawn: true components: - type: InstantAction icon: Interface/Actions/web.png diff --git a/Resources/Prototypes/Actions/types.yml b/Resources/Prototypes/Actions/types.yml index fb156a732e..4b3d75bf92 100644 --- a/Resources/Prototypes/Actions/types.yml +++ b/Resources/Prototypes/Actions/types.yml @@ -13,7 +13,6 @@ id: ActionScream name: Scream description: AAAAAAAAAAAAAAAAAAAAAAAAA - noSpawn: true components: - type: InstantAction useDelay: 10 @@ -25,7 +24,6 @@ id: ActionTurnUndead name: Turn Undead description: Succumb to your infection and become a zombie. - noSpawn: true components: - type: InstantAction checkCanInteract: false @@ -37,7 +35,6 @@ id: ActionToggleLight name: Toggle Light description: Turn the light on and off. - noSpawn: true components: - type: InstantAction icon: { sprite: Objects/Tools/flashlight.rsi, state: flashlight } @@ -48,7 +45,6 @@ id: ActionOpenStorageImplant name: Open Storage Implant description: Opens the storage implant embedded under your skin - noSpawn: true components: - type: InstantAction itemIconStyle: BigAction @@ -63,7 +59,6 @@ id: ActionActivateMicroBomb name: Activate Microbomb description: Activates your internal microbomb, completely destroying you and your equipment - noSpawn: true components: - type: InstantAction checkCanInteract: false @@ -80,7 +75,6 @@ id: ActionActivateDeathAcidifier name: Activate Death-Acidifier description: Activates your death-acidifier, completely melting you and your equipment - noSpawn: true components: - type: InstantAction checkCanInteract: false @@ -96,7 +90,6 @@ id: ActionActivateFreedomImplant name: Break Free description: Activating your freedom implant will free you from any hand restraints - noSpawn: true components: - type: InstantAction charges: 3 @@ -112,7 +105,6 @@ id: ActionOpenUplinkImplant name: Open Uplink description: Opens the syndicate uplink embedded under your skin - noSpawn: true components: - type: InstantAction itemIconStyle: BigAction @@ -126,7 +118,6 @@ id: ActionActivateEmpImplant name: Activate EMP description: Triggers a small EMP pulse around you - noSpawn: true components: - type: InstantAction checkCanInteract: false @@ -143,7 +134,6 @@ id: ActionActivateScramImplant name: SCRAM! description: Randomly teleports you within a large distance. - noSpawn: true components: - type: InstantAction checkCanInteract: false @@ -160,7 +150,6 @@ id: ActionActivateDnaScramblerImplant name: Scramble DNA description: Randomly changes your name and appearance. - noSpawn: true components: - type: InstantAction charges: 1 @@ -175,7 +164,6 @@ id: ActionMorphGeras name: Morph into Geras description: Morphs you into a Geras - a miniature version of you which allows you to move fast, at the cost of your inventory. - noSpawn: true components: - type: InstantAction itemIconStyle: BigAction @@ -190,7 +178,6 @@ id: ActionToggleSuitPiece name: Toggle Suit Piece description: Remember to equip the important pieces of your suit before going into action. - noSpawn: true components: - type: InstantAction itemIconStyle: BigItem @@ -201,7 +188,6 @@ id: ActionCombatModeToggle name: "[color=red]Combat Mode[/color]" description: Enter combat mode - noSpawn: true components: - type: InstantAction checkCanInteract: false @@ -216,7 +202,6 @@ parent: ActionCombatModeToggle name: "[color=red]Combat Mode[/color]" description: Enter combat mode - noSpawn: true components: - type: InstantAction enabled: false @@ -227,7 +212,6 @@ id: ActionChangeVoiceMask name: Set name description: Change the name others hear to something else. - noSpawn: true components: - type: InstantAction icon: { sprite: Interface/Actions/voice-mask.rsi, state: icon } @@ -237,7 +221,6 @@ id: ActionVendingThrow name: Dispense Item description: Randomly dispense an item from your stock. - noSpawn: true components: - type: InstantAction useDelay: 30 @@ -247,7 +230,6 @@ id: ActionArtifactActivate name: Activate Artifact description: Immediately activates your current artifact node. - noSpawn: true components: - type: InstantAction icon: @@ -260,7 +242,6 @@ id: ActionToggleBlock name: Block description: Raise or lower your shield. - noSpawn: true components: - type: InstantAction icon: { sprite: Objects/Weapons/Melee/shields.rsi, state: teleriot-icon } @@ -271,7 +252,6 @@ id: ActionClearNetworkLinkOverlays name: Clear network link overlays description: Clear network link overlays. - noSpawn: true components: - type: InstantAction clientExclusive: true @@ -285,7 +265,6 @@ id: ActionAnimalLayEgg name: Lay egg description: Uses hunger to lay an egg. - noSpawn: true components: - type: InstantAction icon: { sprite: Objects/Consumable/Food/egg.rsi, state: icon } @@ -296,7 +275,6 @@ id: ActionSleep name: Sleep description: Go to sleep. - noSpawn: true components: - type: InstantAction checkCanInteract: false @@ -308,7 +286,6 @@ id: ActionWake name: Wake up description: Stop sleeping. - noSpawn: true components: - type: InstantAction icon: { sprite: Clothing/Head/Hats/pyjamasyndicatered.rsi, state: icon } @@ -320,7 +297,6 @@ id: ActionActivateHonkImplant name: Honk description: Activates your honking implant, which will produce the signature sound of the clown. - noSpawn: true components: - type: InstantAction icon: { sprite: Objects/Fun/bikehorn.rsi, state: icon } @@ -331,7 +307,6 @@ id: ActionFireStarter name: Ignite description: Ignites enemies in a radius around you. - noSpawn: true components: - type: InstantAction priority: -1 @@ -343,7 +318,6 @@ id: ActionToggleEyes name: Open/Close eyes description: Close your eyes to protect your peepers, or open your eyes to enjoy the pretty lights. - noSpawn: true components: - type: InstantAction icon: Interface/Actions/eyeopen.png @@ -357,7 +331,6 @@ id: ActionToggleWagging name: action-name-toggle-wagging description: action-description-toggle-wagging - noSpawn: true components: - type: InstantAction icon: { sprite: Mobs/Customization/reptilian_parts.rsi, state: tail_smooth_behind } diff --git a/Resources/Prototypes/Alerts/alerts.yml b/Resources/Prototypes/Alerts/alerts.yml index 7881cddd4a..72412fde7c 100644 --- a/Resources/Prototypes/Alerts/alerts.yml +++ b/Resources/Prototypes/Alerts/alerts.yml @@ -28,7 +28,7 @@ - type: entity id: AlertSpriteView - categories: [ hideSpawnMenu ] + categories: [ HideSpawnMenu ] components: - type: Sprite layers: diff --git a/Resources/Prototypes/Alerts/revenant.yml b/Resources/Prototypes/Alerts/revenant.yml index a56b898351..9db5228483 100644 --- a/Resources/Prototypes/Alerts/revenant.yml +++ b/Resources/Prototypes/Alerts/revenant.yml @@ -16,7 +16,7 @@ - type: entity id: AlertEssenceSpriteView - categories: [ hideSpawnMenu ] + categories: [ HideSpawnMenu ] components: - type: Sprite sprite: /Textures/Interface/Alerts/essence_counter.rsi diff --git a/Resources/Prototypes/Catalog/Bounties/bounties.yml b/Resources/Prototypes/Catalog/Bounties/bounties.yml index bedfe44287..08bb2a1422 100644 --- a/Resources/Prototypes/Catalog/Bounties/bounties.yml +++ b/Resources/Prototypes/Catalog/Bounties/bounties.yml @@ -42,6 +42,9 @@ whitelist: tags: - Bread + blacklist: + tags: + - Slice - type: cargoBounty id: BountyCarrot @@ -533,6 +536,9 @@ whitelist: tags: - Meat + blacklist: + components: + - SliceableFood - type: cargoBounty id: BountyFruit diff --git a/Resources/Prototypes/Catalog/Fills/Crates/syndicate.yml b/Resources/Prototypes/Catalog/Fills/Crates/syndicate.yml index 3f9e909c80..ba97af3925 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/syndicate.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/syndicate.yml @@ -1,6 +1,6 @@ - type: entity id: CrateSyndicateSurplusBundle - parent: CrateSyndicate + parent: [ CrateSyndicate, StorePresetUplink ] name: Syndicate surplus crate description: Contains 50 telecrystals worth of completely random Syndicate items. It can be useless junk or really good. components: @@ -24,7 +24,7 @@ - type: entity id: CrateSyndicateSuperSurplusBundle - parent: CrateSyndicate + parent: [ CrateSyndicate, StorePresetUplink ] name: Syndicate super surplus crate description: Contains 125 telecrystals worth of completely random Syndicate items. components: diff --git a/Resources/Prototypes/Catalog/VendingMachines/advertisements.yml b/Resources/Prototypes/Catalog/VendingMachines/advertisements.yml index 6dbb60af6a..c0052dba07 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/advertisements.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/advertisements.yml @@ -255,3 +255,9 @@ values: prefix: advertisement-virodrobe- count: 3 + +- type: localizedDataset + id: MedibotAds + values: + prefix: advertisement-medibot- + count: 9 diff --git a/Resources/Prototypes/Catalog/revenant_catalog.yml b/Resources/Prototypes/Catalog/revenant_catalog.yml index 84f45d1607..ec1ef0a309 100644 --- a/Resources/Prototypes/Catalog/revenant_catalog.yml +++ b/Resources/Prototypes/Catalog/revenant_catalog.yml @@ -1,7 +1,7 @@ - type: listing id: RevenantDefile - name: Defile - description: Defiles the surrounding area, ripping up floors, damaging windows, opening containers, and throwing items. Using it leaves you vulnerable to attacks for a short period of time. + name: revenant-defile-name + description: revenant-defile-desc productAction: ActionRevenantDefile cost: StolenEssence: 10 @@ -13,8 +13,8 @@ - type: listing id: RevenantOverloadLights - name: Overload Lights - description: Overloads all nearby lights, causing lights to pulse and sending out dangerous lightning. Using it leaves you vulnerable to attacks for a long period of time. + name: revenant-overload-name + description: revenant-overload-desc productAction: ActionRevenantOverloadLights cost: StolenEssence: 25 @@ -26,8 +26,8 @@ #- type: listing # id: RevenantBlight -# name: Blight -# description: Infects all nearby organisms with an infectious disease that causes toxic buildup and tiredness. Using it leaves you vulnerable to attacks for a medium period of time. +# name: revenant-blight-name +# description: revenant-blight-desc # productAction: ActionRevenantBlight # cost: # StolenEssence: 75 @@ -39,8 +39,8 @@ - type: listing id: RevenantMalfunction - name: Malfunction - description: Makes nearby electronics stop working properly. Using it leaves you vulnerable to attacks for a long period of time. + name: revenant-malfunction-name + description: revenant-malfunction-desc productAction: ActionRevenantMalfunction cost: StolenEssence: 125 diff --git a/Resources/Prototypes/Catalog/uplink_catalog.yml b/Resources/Prototypes/Catalog/uplink_catalog.yml index efb3a3c456..145321484b 100644 --- a/Resources/Prototypes/Catalog/uplink_catalog.yml +++ b/Resources/Prototypes/Catalog/uplink_catalog.yml @@ -1009,7 +1009,7 @@ name: uplink-mobcat-microbomb-name description: uplink-mobcat-microbomb-desc icon: { sprite: /Textures/Mobs/Pets/cat.rsi, state: syndicat } - productEntity: MobCatSyndy + productEntity: ReinforcementRadioSyndicateSyndiCat cost: Telecrystal: 6 categories: diff --git a/Resources/Prototypes/DeltaV/Actions/types.yml b/Resources/Prototypes/DeltaV/Actions/types.yml index 13ee3b6cc2..bd8e8d8025 100644 --- a/Resources/Prototypes/DeltaV/Actions/types.yml +++ b/Resources/Prototypes/DeltaV/Actions/types.yml @@ -2,7 +2,6 @@ id: ActionOpenRadioImplant name: Open Radio Implant description: Opens the bluespace key compartment of the radio implant embedded in your skull. - noSpawn: true components: - type: InstantAction itemIconStyle: BigAction diff --git a/Resources/Prototypes/DeltaV/Entities/Actions/cancel-escape-inventory.yml b/Resources/Prototypes/DeltaV/Entities/Actions/cancel-escape-inventory.yml index e07e7c839f..4eb2495460 100644 --- a/Resources/Prototypes/DeltaV/Entities/Actions/cancel-escape-inventory.yml +++ b/Resources/Prototypes/DeltaV/Entities/Actions/cancel-escape-inventory.yml @@ -2,7 +2,6 @@ id: ActionCancelEscape name: Stop escaping description: Calm down and sit peacefuly in your carrier's inventory - noSpawn: true components: - type: InstantAction icon: DeltaV/Actions/escapeinventory.rsi/cancel-escape.png diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/Species/harpy.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/Species/harpy.yml index 41be2a2c0a..edb3fe60d1 100644 --- a/Resources/Prototypes/DeltaV/Entities/Mobs/Species/harpy.yml +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/Species/harpy.yml @@ -210,7 +210,6 @@ id: ActionHarpyPlayMidi name: Play MIDI description: Sing your heart out! Right click yourself to set an instrument. - noSpawn: true components: - type: InstantAction checkCanInteract: false @@ -222,7 +221,6 @@ id: ActionSyrinxChangeVoiceMask name: Set name description: Change the name others hear to something else. - noSpawn: true components: - type: InstantAction icon: DeltaV/Interface/Actions/harpy_syrinx.png diff --git a/Resources/Prototypes/DeltaV/Entities/Objects/Consumable/Food/Baked/pie.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Consumable/Food/Baked/pie.yml index d86b5142db..ce4ed2aa7e 100644 --- a/Resources/Prototypes/DeltaV/Entities/Objects/Consumable/Food/Baked/pie.yml +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Consumable/Food/Baked/pie.yml @@ -35,5 +35,4 @@ - type: Tag tags: - Fruit - - Pie # Tastes like pie, pumpkin. diff --git a/Resources/Prototypes/DeltaV/Objectives/paradox_anomaly.yml b/Resources/Prototypes/DeltaV/Objectives/paradox_anomaly.yml index dd0b74c461..bd453a792b 100644 --- a/Resources/Prototypes/DeltaV/Objectives/paradox_anomaly.yml +++ b/Resources/Prototypes/DeltaV/Objectives/paradox_anomaly.yml @@ -8,7 +8,6 @@ # not using base kill/keep alive objectives since these intentionally conflict with eachother - type: entity - noSpawn: true parent: BaseParadoxAnomalyObjective id: ParadoxAnomalyKillObjective description: This universe doesn't have room for both of us. @@ -24,7 +23,6 @@ requireDead: true - type: entity - noSpawn: true parent: BaseParadoxAnomalyObjective id: ParadoxAnomalyFriendObjective description: Perhaps there is room, as friends. @@ -39,7 +37,6 @@ - type: KeepAliveCondition - type: entity - noSpawn: true parent: [BaseParadoxAnomalyObjective, BaseLivingObjective] id: ParadoxAnomalyEscapeObjective name: Escape to centcom alive and unrestrained. diff --git a/Resources/Prototypes/DeltaV/Objectives/traitor.yml b/Resources/Prototypes/DeltaV/Objectives/traitor.yml index d27ec220fa..932b0d64fc 100644 --- a/Resources/Prototypes/DeltaV/Objectives/traitor.yml +++ b/Resources/Prototypes/DeltaV/Objectives/traitor.yml @@ -1,5 +1,4 @@ - type: entity # Logistics Officer steal objective. - noSpawn: true parent: BaseTraitorStealObjective id: LOLuckyBillStealObjective components: @@ -10,7 +9,6 @@ # owner: job-name-qm - type: entity # Head of Personnel steal objective. - noSpawn: true parent: BaseTraitorStealObjective id: HoPBookIanDossierStealObjective components: @@ -21,7 +19,6 @@ # owner: job-name-hop - type: entity # Head of Security steal objective. - noSpawn: true parent: BaseTraitorStealObjective id: HoSGunStealObjective components: diff --git a/Resources/Prototypes/DeltaV/Traits/altvision.yml b/Resources/Prototypes/DeltaV/Traits/altvision.yml index c361d1b51d..3d416797e1 100644 --- a/Resources/Prototypes/DeltaV/Traits/altvision.yml +++ b/Resources/Prototypes/DeltaV/Traits/altvision.yml @@ -2,6 +2,7 @@ id: UltraVision name: trait-ultravision-name description: trait-ultravision-desc + category: Disabilities components: - type: UltraVision @@ -9,5 +10,6 @@ id: DogVision name: trait-deuteranopia-name description: trait-deuteranopia-desc + category: Disabilities components: - type: DogVision diff --git a/Resources/Prototypes/DeltaV/Traits/neutral.yml b/Resources/Prototypes/DeltaV/Traits/neutral.yml index 79a6771a36..6a8bd8841a 100644 --- a/Resources/Prototypes/DeltaV/Traits/neutral.yml +++ b/Resources/Prototypes/DeltaV/Traits/neutral.yml @@ -3,5 +3,7 @@ name: trait-scottish-accent-name description: trait-scottish-accent-desc traitGear: BagpipeInstrument + category: SpeechTraits + cost: 1 components: - - type: ScottishAccent \ No newline at end of file + - type: ScottishAccent diff --git a/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml b/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml index 40f9e0a3d4..866fe962ca 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml @@ -119,6 +119,7 @@ id: ClothingHeadEVAHelmetBase name: base space helmet components: + - type: BreathMask - type: Item size: Normal - type: PressureProtection @@ -150,6 +151,7 @@ name: base hardsuit helmet noSpawn: true components: + - type: BreathMask - type: Sprite state: icon # default state used by most inheritors - type: Clickable diff --git a/Resources/Prototypes/Entities/Clothing/Head/eva-helmets.yml b/Resources/Prototypes/Entities/Clothing/Head/eva-helmets.yml index 963bcdedd7..530c23a578 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/eva-helmets.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/eva-helmets.yml @@ -5,7 +5,6 @@ name: EVA helmet description: An old-but-gold helmet designed for extravehicular activites. Infamous for making security officers paranoid. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Helmets/eva.rsi - type: Clothing @@ -22,7 +21,6 @@ name: EVA helmet description: An old-but-gold helmet designed for extravehicular activites. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Helmets/eva_large.rsi - type: Clothing @@ -35,7 +33,6 @@ name: syndicate EVA helmet description: A simple, stylish EVA helmet. Designed for maximum humble space-badassery. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Helmets/eva_syndicate.rsi - type: Clothing @@ -48,7 +45,6 @@ name: cosmonaut helmet description: Ancient design, but advanced manufacturing. #Description here originally started with " A deceptively well armored space helmet." Potentially had armor values in SS13 that weren't brought over? components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Helmets/cosmonaut.rsi - type: Clothing @@ -61,7 +57,6 @@ name: paramedic void helmet description: A void helmet made for paramedics. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Helmets/paramedhelm.rsi - type: Clothing @@ -81,7 +76,6 @@ name: NTSRA void helmet description: An ancient space helmet, designed by the NTSRA branch of CentCom. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Helmets/ancientvoidsuit.rsi - type: Clothing diff --git a/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml b/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml index 9354143cec..d4c29430e3 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml @@ -12,7 +12,6 @@ name: basic hardsuit helmet description: A basic-looking hardsuit helmet that provides minor protection against most sources of damage. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/basic.rsi - type: Clothing @@ -28,7 +27,6 @@ name: atmos hardsuit helmet description: A special hardsuit helmet designed for working in low-pressure, high thermal environments. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/atmospherics.rsi layers: @@ -68,7 +66,6 @@ name: engineering hardsuit helmet description: An engineering hardsuit helmet designed for working in low-pressure, high radioactive environments. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/engineering.rsi - type: Clothing @@ -86,7 +83,6 @@ name: spationaut hardsuit helmet description: A sturdy helmet designed for complex industrial operations in space. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/spatiohelm.rsi layers: @@ -121,7 +117,6 @@ name: salvage hardsuit helmet description: A special helmet designed for work in a hazardous, low pressure environment. Has reinforced plating for wildlife encounters and dual floodlights. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/salvage.rsi - type: Clothing @@ -140,7 +135,6 @@ name: salvager maxim helmet description: A predication of decay washes over your mind. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/maxim.rsi - type: Clothing @@ -163,7 +157,6 @@ name: security hardsuit helmet description: Armored hardsuit helmet for security needs. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/security.rsi - type: Clothing @@ -188,7 +181,6 @@ name: corpsman hardsuit helmet # DeltaV - rename brigmedic to corpsman description: The lightweight helmet of the corpsman hardsuit. Protects against viruses, and clowns. # Delta V - rename brigmedic to corpsman components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/brigmedic.rsi - type: Clothing @@ -215,7 +207,6 @@ name: warden's hardsuit helmet description: A modified riot helmet. Oddly comfortable. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/security-warden.rsi - type: Clothing @@ -240,7 +231,6 @@ name: captain's hardsuit helmet description: Special hardsuit helmet, made for the captain of the station. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/capspace.rsi - type: Clothing @@ -256,7 +246,6 @@ name: chief engineer's hardsuit helmet description: Special hardsuit helmet, made for the chief engineer of the station. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/engineering-white.rsi - type: Clothing @@ -274,7 +263,6 @@ name: chief medical officer's hardsuit helmet description: Lightweight medical hardsuit helmet that doesn't restrict your head movements. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/medical.rsi - type: Clothing @@ -292,7 +280,6 @@ name: experimental research hardsuit helmet description: Lightweight hardsuit helmet that doesn't restrict your head movements. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/rd.rsi - type: Clothing @@ -310,7 +297,6 @@ name: head of security's hardsuit helmet description: Security hardsuit helmet with the latest top secret NT-HUD software. Belongs to the HoS. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/security-red.rsi - type: Clothing @@ -335,7 +321,6 @@ name: luxury mining hardsuit helmet description: A refurbished mining hardsuit helmet, fitted with satin cushioning and an extra (non-functioning) antenna, because you're that extra. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/luxury.rsi - type: Clothing @@ -355,7 +340,6 @@ name: blood-red hardsuit helmet description: A heavily armored helmet designed for work in special operations. Property of Gorlex Marauders. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/syndicate.rsi - type: Clothing @@ -380,7 +364,6 @@ name: blood-red medic hardsuit helmet description: An advanced red hardsuit helmet specifically designed for field medic operations. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/syndiemedic.rsi - type: Clothing @@ -405,7 +388,6 @@ name: syndicate elite helmet description: An elite version of the blood-red hardsuit's helmet, with improved armor and fireproofing. Property of Gorlex Marauders. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/syndieelite.rsi - type: Clothing @@ -434,7 +416,6 @@ name: syndicate commander helmet description: A bulked up version of the blood-red hardsuit's helmet, purpose-built for the commander of a syndicate operative squad. Has significantly improved armor for those deadly front-lines firefights. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/syndiecommander.rsi - type: Clothing @@ -459,7 +440,6 @@ name: cybersun juggernaut helmet description: Made of compressed red matter, this helmet was designed in the Tau chromosphere facility. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/cybersun.rsi - type: Clothing @@ -482,7 +462,6 @@ name: wizard hardsuit helmet description: A bizarre gem-encrusted helmet that radiates magical energies. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/wizard.rsi - type: Clothing @@ -507,7 +486,6 @@ name: organic space helmet description: A spaceworthy biomass of pressure and temperature resistant tissue. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/lingspacehelmet.rsi - type: Clothing @@ -524,7 +502,6 @@ suffix: Pirate description: A deep space EVA helmet, very heavy but provides good protection. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/pirateeva.rsi - type: Clothing @@ -541,7 +518,6 @@ suffix: Pirate description: A special hardsuit helmet, made for the captain of a pirate ship. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/piratecaptainhelm.rsi - type: Clothing @@ -558,7 +534,6 @@ name: ERT leader hardsuit helmet description: A special hardsuit helmet worn by members of an emergency response team. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/ERThelmets/ertleader.rsi - type: Clothing @@ -580,7 +555,6 @@ name: ERT chaplain hardsuit helmet description: A special hardsuit helmet worn by members of an emergency response team. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/ERThelmets/ertchaplain.rsi - type: Clothing @@ -595,7 +569,6 @@ name: ERT engineer hardsuit helmet description: A special hardsuit helmet worn by members of an emergency response team. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/ERThelmets/ertengineer.rsi - type: Clothing @@ -617,7 +590,6 @@ name: ERT medic hardsuit helmet description: A special hardsuit helmet worn by members of an emergency response team. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/ERThelmets/ertmedical.rsi - type: Clothing @@ -632,7 +604,6 @@ name: ERT security hardsuit helmet description: A special hardsuit helmet worn by members of an emergency response team. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/ERThelmets/ertsecurity.rsi - type: Clothing @@ -654,7 +625,6 @@ name: ERT janitor hardsuit helmet description: A special hardsuit helmet worn by members of an emergency response team. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/ERThelmets/ertjanitor.rsi - type: Clothing @@ -669,7 +639,6 @@ name: CBURN exosuit helmet description: A pressure resistant and fireproof hood worn by special cleanup units. components: - - type: BreathMask - type: Sprite sprite: Clothing/Head/Hardsuits/cburn.rsi layers: diff --git a/Resources/Prototypes/Entities/Clothing/Head/helmets.yml b/Resources/Prototypes/Entities/Clothing/Head/helmets.yml index 009c2ef784..97eb39ed86 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/helmets.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/helmets.yml @@ -263,6 +263,7 @@ slots: - Hair - Snout + - type: BreathMask #Chitinous Helmet - type: entity diff --git a/Resources/Prototypes/Entities/Clothing/Masks/base_clothingmask.yml b/Resources/Prototypes/Entities/Clothing/Masks/base_clothingmask.yml index 3531a26a6c..00dcd8263f 100644 --- a/Resources/Prototypes/Entities/Clothing/Masks/base_clothingmask.yml +++ b/Resources/Prototypes/Entities/Clothing/Masks/base_clothingmask.yml @@ -21,7 +21,6 @@ id: ActionToggleMask name: Toggle Mask description: Handy, but prevents insertion of pie into your pie hole. - noSpawn: true components: - type: InstantAction icon: { sprite: Clothing/Mask/gas.rsi, state: icon } @@ -49,4 +48,4 @@ Quantity: 10 - type: Tag tags: - - ClothMade \ No newline at end of file + - ClothMade diff --git a/Resources/Prototypes/Entities/Clothing/Neck/cloaks.yml b/Resources/Prototypes/Entities/Clothing/Neck/cloaks.yml index f31b38c20c..a10ac5fd5d 100644 --- a/Resources/Prototypes/Entities/Clothing/Neck/cloaks.yml +++ b/Resources/Prototypes/Entities/Clothing/Neck/cloaks.yml @@ -8,7 +8,7 @@ sprite: Clothing/Neck/Cloaks/centcomcloakformal.rsi - type: StealTarget stealGroup: HeadCloak # leaving this here because I suppose it might be interesting? - + - type: entity parent: ClothingNeckBase id: ClothingNeckCloakCap @@ -118,7 +118,7 @@ - type: entity parent: ClothingNeckBase id: ClothingNeckCloakAdmin - name: admin cloak + name: weh cloak description: Weh! components: - type: Sprite diff --git a/Resources/Prototypes/Entities/Clothing/Neck/misc.yml b/Resources/Prototypes/Entities/Clothing/Neck/misc.yml index 51325c0bbb..8dfc709bc4 100644 --- a/Resources/Prototypes/Entities/Clothing/Neck/misc.yml +++ b/Resources/Prototypes/Entities/Clothing/Neck/misc.yml @@ -70,7 +70,6 @@ - type: entity id: ActionStethoscope name: Listen with stethoscope - noSpawn: true components: - type: EntityTargetAction icon: diff --git a/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml b/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml index 8b3340c705..4425b04171 100644 --- a/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml +++ b/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml @@ -136,7 +136,6 @@ id: ActionBaseToggleMagboots name: Toggle Magboots description: Toggles the magboots on and off. - noSpawn: true components: - type: InstantAction itemIconStyle: NoItem @@ -145,7 +144,6 @@ - type: entity id: ActionToggleMagboots parent: ActionBaseToggleMagboots - noSpawn: true components: - type: InstantAction icon: { sprite: Clothing/Shoes/Boots/magboots.rsi, state: icon } @@ -154,7 +152,6 @@ - type: entity id: ActionToggleMagbootsAdvanced parent: ActionBaseToggleMagboots - noSpawn: true components: - type: InstantAction icon: { sprite: Clothing/Shoes/Boots/magboots-advanced.rsi, state: icon } @@ -163,7 +160,6 @@ - type: entity id: ActionToggleMagbootsSci parent: ActionBaseToggleMagboots - noSpawn: true components: - type: InstantAction icon: { sprite: Clothing/Shoes/Boots/magboots-science.rsi, state: icon } @@ -172,7 +168,6 @@ - type: entity id: ActionToggleMagbootsSyndie parent: ActionBaseToggleMagboots - noSpawn: true components: - type: InstantAction icon: { sprite: Clothing/Shoes/Boots/magboots-syndicate.rsi, state: icon } diff --git a/Resources/Prototypes/Entities/Clothing/Shoes/misc.yml b/Resources/Prototypes/Entities/Clothing/Shoes/misc.yml index 45f844c012..9d9f3d1951 100644 --- a/Resources/Prototypes/Entities/Clothing/Shoes/misc.yml +++ b/Resources/Prototypes/Entities/Clothing/Shoes/misc.yml @@ -132,7 +132,6 @@ id: ActionToggleSpeedBoots name: Toggle Speed Boots description: Toggles the speed boots on and off. - noSpawn: true components: - type: InstantAction itemIconStyle: NoItem diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml index 76cf3ce9bf..5449e3e19d 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml @@ -126,6 +126,7 @@ - Stun - KnockedDown - SlowedDown + - Flashed - type: TypingIndicator proto: robot - type: Speech @@ -147,7 +148,6 @@ locked: true - type: ActivatableUIRequiresLock - type: LockedWiresPanel - - type: Flashable - type: Damageable damageContainer: Silicon - type: Destructible diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 86d57f7dc1..44b597defd 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -1555,6 +1555,7 @@ state: mouse-0 - type: Item size: Tiny + heldPrefix: 0 - type: Clothing quickEquip: false sprite: Mobs/Animals/mouse.rsi @@ -1729,6 +1730,9 @@ Base: dead-1 Dead: Base: splat-1 + - type: Item + size: Tiny + heldPrefix: 1 - type: entity parent: MobMouse @@ -1755,6 +1759,9 @@ Base: dead-2 Dead: Base: splat-2 + - type: Item + size: Tiny + heldPrefix: 2 - type: entity name: lizard #Weh @@ -2252,7 +2259,7 @@ - DoorBumpOpener - FootstepSound - type: Tool # Open door from xeno.yml. - speed: 1.5 + speedModifier: 1.5 qualities: - Prying useSound: diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/argocyte.yml b/Resources/Prototypes/Entities/Mobs/NPCs/argocyte.yml index 39e68b63a7..3b6c4e8ed9 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/argocyte.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/argocyte.yml @@ -48,7 +48,6 @@ sprite: Mobs/Effects/onfire.rsi normalState: Generic_mob_burning - type: Climbing - - type: Flashable - type: NameIdentifier group: GenericNumber diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml index bb59e6dbe5..537bc4c0c7 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml @@ -798,7 +798,6 @@ amount: 3 - id: DrinkTequilaBottleFull amount: 1 - - type: Flashable - type: Tag tags: - CannotSuicide @@ -825,7 +824,6 @@ # name: ghost-role-information-tropico-name # description: ghost-role-information-tropico-description # - type: GhostTakeoverAvailable -# - type: Flashable - type: Tag tags: - VimPilot diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml index f5f28d084c..c05840f911 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml @@ -308,7 +308,6 @@ id: ActionRatKingRaiseArmy name: Raise Army description: Spend some hunger to summon an allied rat to help defend you. - noSpawn: true components: - type: InstantAction useDelay: 4 @@ -321,7 +320,6 @@ id: ActionRatKingDomain name: Rat King's Domain description: Spend some hunger to release a cloud of ammonia into the air. - noSpawn: true components: - type: InstantAction useDelay: 6 @@ -334,7 +332,6 @@ id: ActionRatKingOrderStay name: Stay description: Command your army to stand in place. - noSpawn: true components: - type: InstantAction useDelay: 1 @@ -353,7 +350,6 @@ id: ActionRatKingOrderFollow name: Follow description: Command your army to follow you around. - noSpawn: true components: - type: InstantAction useDelay: 1 @@ -372,7 +368,6 @@ id: ActionRatKingOrderCheeseEm name: Cheese 'Em description: Command your army to attack whoever you point at. - noSpawn: true components: - type: InstantAction useDelay: 1 @@ -391,7 +386,6 @@ id: ActionRatKingOrderLoose name: Loose description: Command your army to act at their own will. - noSpawn: true components: - type: InstantAction useDelay: 1 diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml index b2fefb67ae..96dfd53cec 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml @@ -107,6 +107,8 @@ proto: robot - type: ZombieImmune - type: StepTriggerImmune + - type: NoSlip + - type: Insulated - type: entity parent: MobSiliconBase @@ -233,7 +235,6 @@ - type: MovementSpeedModifier baseWalkSpeed: 2 baseSprintSpeed: 3 - - type: NoSlip - type: HTN rootTask: task: CleanbotCompound @@ -280,13 +281,14 @@ - type: Construction graph: MediBot node: bot - - type: NoSlip - type: Anchorable - type: InteractionPopup interactSuccessString: petting-success-medibot interactFailureString: petting-failure-medibot interactSuccessSound: path: /Audio/Ambience/Objects/periodic_beep.ogg + - type: Advertise + pack: MedibotAds - type: entity parent: MobSiliconBase diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml index 12ea40b1e3..4936d883e5 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml @@ -25,6 +25,7 @@ - ForcedSleep - TemporaryBlindness - Pacified + - Flashed - type: Buckle - type: StandingState - type: Tag @@ -99,6 +100,7 @@ - TemporaryBlindness - Pacified - StaminaModifier + - Flashed - type: Bloodstream bloodMaxVolume: 150 - type: MobPrice diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml b/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml index f2f360a271..fda1c56542 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml @@ -85,7 +85,7 @@ scaleByQuantity: true damage: types: - Heat: 3 + Heat: 0.15 - !type:PopupMessage type: Local messages: [ "slime-hurt-by-water-popup" ] diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index aaf527deac..4b060e51d7 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml @@ -22,7 +22,7 @@ NavSmash: !type:Bool true - type: Tool - speed: 1.5 + speedModifier: 1.5 qualities: - Prying - type: Prying diff --git a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml index 314c57b141..c2aa5d74a8 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml @@ -90,8 +90,7 @@ - type: Stripping - type: SolutionScanner - type: IgnoreUIRange - - type: ShowRevIcons - - type: ShowZombieIcons + - type: ShowAntagIcons - type: Inventory templateId: aghost - type: InventorySlots @@ -103,7 +102,6 @@ id: ActionAGhostShowSolar name: Solar Control Interface description: View a solar control interface. - noSpawn: true components: - type: InstantAction icon: { sprite: Structures/Machines/parts.rsi, state: box_0 } @@ -116,7 +114,6 @@ id: ActionAGhostShowCommunications name: Communications Interface description: View a communications interface. - noSpawn: true components: - type: InstantAction icon: { sprite: Structures/Machines/parts.rsi, state: box_0 } @@ -129,7 +126,6 @@ id: ActionAGhostShowRadar name: Mass Scanner Interface description: View a mass scanner interface. - noSpawn: true components: - type: InstantAction icon: { sprite: Structures/Machines/parts.rsi, state: box_0 } @@ -142,7 +138,6 @@ id: ActionAGhostShowCargo name: Cargo Ordering Interface description: View a cargo ordering interface. - noSpawn: true components: - type: InstantAction icon: { sprite: Structures/Machines/parts.rsi, state: box_0 } @@ -155,7 +150,6 @@ id: ActionAGhostShowCrewMonitoring name: Crew Monitoring Interface description: View a crew monitoring interface. - noSpawn: true components: - type: InstantAction icon: { sprite: Structures/Machines/parts.rsi, state: box_0 } @@ -168,7 +162,6 @@ id: ActionAGhostShowStationRecords name: Station Records Interface description: View a station records Interface - noSpawn: true components: - type: InstantAction icon: { sprite: Structures/Machines/parts.rsi, state: box_0 } diff --git a/Resources/Prototypes/Entities/Mobs/Player/dragon.yml b/Resources/Prototypes/Entities/Mobs/Player/dragon.yml index 258488af9b..869fb88084 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/dragon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/dragon.yml @@ -203,7 +203,6 @@ id: ActionSpawnRift name: Summon Carp Rift description: Summons a carp rift that will periodically spawns carps. - noSpawn: true components: - type: InstantAction icon: @@ -217,7 +216,6 @@ id: ActionDevour name: "[color=red]Devour[/color]" description: Attempt to break a structure with your jaws or swallow a creature. - noSpawn: true components: - type: EntityTargetAction icon: { sprite : Interface/Actions/devour.rsi, state: icon } @@ -226,7 +224,6 @@ priority: 1 - type: entity - noSpawn: true id: ActionDragonsBreath name: "[color=orange]Dragon's Breath[/color]" description: Spew out flames at anyone foolish enough to attack you! diff --git a/Resources/Prototypes/Entities/Mobs/Player/guardian.yml b/Resources/Prototypes/Entities/Mobs/Player/guardian.yml index 80ee2c55b8..9ccfdf4e50 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/guardian.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/guardian.yml @@ -259,7 +259,6 @@ id: ActionToggleGuardian name: Toggle Guardian description: Either manifests the guardian or recalls it back into your body - noSpawn: true components: - type: InstantAction icon: Interface/Actions/manifest.png diff --git a/Resources/Prototypes/Entities/Mobs/Player/observer.yml b/Resources/Prototypes/Entities/Mobs/Player/observer.yml index 8f3e6c1346..bae3f26300 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/observer.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/observer.yml @@ -58,7 +58,6 @@ id: ActionGhostBoo name: Boo! description: Scare your crew members because of boredom! - noSpawn: true components: - type: InstantAction icon: Interface/Actions/scream.png @@ -70,7 +69,6 @@ id: ActionToggleLighting name: Toggle All Lighting description: Toggle all light rendering to better observe dark areas. - noSpawn: true components: - type: InstantAction icon: Interface/VerbIcons/light.svg.192dpi.png @@ -82,7 +80,6 @@ id: ActionToggleFov name: Toggle FoV description: Toggles field-of-view in order to see what players see. - noSpawn: true components: - type: InstantAction icon: Interface/VerbIcons/vv.svg.192dpi.png @@ -94,7 +91,6 @@ id: ActionToggleGhosts name: Toggle Ghosts description: Toggle the visibility of other ghosts. - noSpawn: true components: - type: InstantAction icon: { sprite: Mobs/Ghosts/ghost_human.rsi, state: icon } @@ -106,7 +102,6 @@ id: ActionToggleGhostHearing name: Toggle Ghost Hearing description: Toggle between hearing all messages and hearing only radio & nearby messages. - noSpawn: true components: - type: InstantAction checkCanInteract: false diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index 4ee1ee4e5f..c1c9b51c50 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -140,8 +140,9 @@ - TemporaryBlindness - Pacified - StaminaModifier + - Flashed - PsionicsDisabled #Nyano - Summary: PCs can have psionics disabled. - - PsionicallyInsulated #Nyano - Summary: PCs can be made insulated from psionic powers. + - PsionicallyInsulated #Nyano - Summary: PCs can be made insulated from psionic powers. - type: Reflect enabled: false reflectProb: 0 @@ -219,7 +220,7 @@ - type: MobPrice price: 1500 # Kidnapping a living person and selling them for cred is a good move. deathPenalty: 0.01 # However they really ought to be living and intact, otherwise they're worth 100x less. - - type: CanEscapeInventory # Carrying system from nyanotrasen. + - type: CanEscapeInventory # Carrying system from nyanotrasen. - type: Tag tags: - CanPilot @@ -237,7 +238,6 @@ id: BaseMobSpeciesOrganic abstract: true components: - - type: Flashable - type: Barotrauma damage: types: @@ -252,24 +252,7 @@ Heat: -0.07 groups: Brute: -0.07 - # Organs - - type: StatusEffects - allowed: - - Stun - - KnockedDown - - SlowedDown - - Stutter - - SeeingRainbows - - Electrocution - - ForcedSleep - - TemporaryBlindness - - Drunk - - SlurredSpeech - - RatvarianLanguage - - PressureImmunity - - Muted - - Pacified - - StaminaModifier + - type: Blindable # Other - type: Temperature diff --git a/Resources/Prototypes/Entities/Mobs/Species/diona.yml b/Resources/Prototypes/Entities/Mobs/Species/diona.yml index 93a1ba2932..8aeabb22f9 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/diona.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/diona.yml @@ -50,9 +50,9 @@ scaleByQuantity: true damage: types: - Blunt: 2 - Slash: 2 - Piercing: 3 + Blunt: 0.1 + Slash: 0.1 + Piercing: 0.15 - !type:PopupMessage type: Local visualType: Large @@ -65,7 +65,7 @@ scaleByQuantity: true damage: types: - Poison: 5 + Poison: 0.25 - !type:PopupMessage type: Local visualType: Large diff --git a/Resources/Prototypes/Entities/Mobs/Species/slime.yml b/Resources/Prototypes/Entities/Mobs/Species/slime.yml index 9fdf6ae276..31db778a00 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/slime.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/slime.yml @@ -92,7 +92,7 @@ scaleByQuantity: true damage: types: - Heat: 2 + Heat: 0.1 - !type:PopupMessage type: Local visualType: Large diff --git a/Resources/Prototypes/Entities/Mobs/base.yml b/Resources/Prototypes/Entities/Mobs/base.yml index 6b36e98113..f3c9daeee7 100644 --- a/Resources/Prototypes/Entities/Mobs/base.yml +++ b/Resources/Prototypes/Entities/Mobs/base.yml @@ -1,4 +1,4 @@ -# The progenitor. This should only container the most basic components possible. +# The progenitor. This should only container the most basic components possible. # Only put things on here if every mob *must* have it. This includes ghosts. - type: entity save: false @@ -43,6 +43,8 @@ - type: MovementSpeedModifier - type: Polymorphable - type: StatusIcon + - type: RequireProjectileTarget + active: False # Used for mobs that have health and can take damage. - type: entity diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks-cartons.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks-cartons.yml index aef0c5a8f5..f2b2c0450f 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks-cartons.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks-cartons.yml @@ -130,7 +130,7 @@ Quantity: 50 - type: Drink - type: Label - currentLabel: coconut water + currentLabel: reagent-name-coconut-water - type: Sprite sprite: Objects/Consumable/Drinks/coconutwater.rsi diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_bottles.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_bottles.yml index 35ad43586b..c9e28954be 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_bottles.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_bottles.yml @@ -80,7 +80,7 @@ - type: Tool qualities: - Rolling - speed: 0.75 # not as good as a rolling pin but does the job + speedModifier: 0.75 # not as good as a rolling pin but does the job - type: PhysicalComposition materialComposition: Glass: 100 @@ -210,7 +210,7 @@ - ReagentId: Cognac Quantity: 100 - type: Label - currentLabel: cognac + currentLabel: reagent-name-cognac - type: Sprite sprite: Objects/Consumable/Drinks/cognacbottle.rsi - type: Sealable @@ -228,7 +228,7 @@ - ReagentId: Cola Quantity: 100 - type: Label - currentLabel: cola + currentLabel: reagent-name-cola - type: Sprite sprite: Objects/Consumable/Drinks/colabottle.rsi - type: Sealable @@ -266,7 +266,7 @@ - ReagentId: Gin Quantity: 100 - type: Label - currentLabel: gin + currentLabel: reagent-name-gin - type: Sprite sprite: Objects/Consumable/Drinks/ginbottle.rsi - type: Sealable @@ -300,7 +300,7 @@ - ReagentId: CoffeeLiqueur Quantity: 100 - type: Label - currentLabel: coffee liqueur + currentLabel: reagent-name-coffeeliqueur - type: Sprite sprite: Objects/Consumable/Drinks/coffeeliqueurbottle.rsi - type: Sealable @@ -377,7 +377,7 @@ - ReagentId: Rum Quantity: 100 - type: Label - currentLabel: rum + currentLabel: reagent-name-rum - type: Sprite sprite: Objects/Consumable/Drinks/rumbottle.rsi - type: Sealable @@ -396,7 +396,7 @@ Quantity: 100 - type: Drink - type: Label - currentLabel: space mountain wind + currentLabel: reagent-name-space-mountain-wind - type: Sprite sprite: Objects/Consumable/Drinks/space_mountain_wind_bottle.rsi - type: Sealable @@ -415,7 +415,7 @@ Quantity: 100 - type: Drink - type: Label - currentLabel: space-up + currentLabel: reagent-name-space-up - type: Sprite sprite: Objects/Consumable/Drinks/space-up_bottle.rsi - type: Sealable @@ -433,7 +433,7 @@ - ReagentId: Tequila Quantity: 100 - type: Label - currentLabel: tequila + currentLabel: reagent-name-tequila - type: Sprite sprite: Objects/Consumable/Drinks/tequillabottle.rsi - type: Sealable @@ -451,7 +451,7 @@ - ReagentId: Vermouth Quantity: 100 - type: Label - currentLabel: vermouth + currentLabel: reagent-name-vermouth - type: Sprite sprite: Objects/Consumable/Drinks/vermouthbottle.rsi - type: Sealable @@ -469,7 +469,7 @@ - ReagentId: Vodka Quantity: 100 - type: Label - currentLabel: vodka + currentLabel: reagent-name-vodka - type: Sprite sprite: Objects/Consumable/Drinks/vodkabottle.rsi - type: Sealable @@ -487,7 +487,7 @@ - ReagentId: Whiskey Quantity: 100 - type: Label - currentLabel: whiskey + currentLabel: reagent-name-whiskey - type: Sprite sprite: Objects/Consumable/Drinks/whiskeybottle.rsi - type: Sealable @@ -505,7 +505,7 @@ - ReagentId: Wine Quantity: 100 - type: Label - currentLabel: wine + currentLabel: reagent-name-wine - type: Sprite sprite: Objects/Consumable/Drinks/winebottle.rsi - type: Sealable @@ -554,7 +554,7 @@ - ReagentId: Beer Quantity: 150 - type: Label - currentLabel: beer + currentLabel: reagent-name-beer - type: Sprite sprite: Objects/Consumable/Drinks/beer.rsi - type: Openable @@ -598,7 +598,7 @@ - ReagentId: Ale Quantity: 150 - type: Label - currentLabel: ale + currentLabel: reagent-name-ale - type: Sprite sprite: Objects/Consumable/Drinks/alebottle.rsi - type: Openable @@ -650,7 +650,7 @@ - ReagentId: SodaWater Quantity: 150 - type: Label - currentLabel: soda water + currentLabel: reagent-name-soda-water - type: entity parent: DrinkWaterBottleFull @@ -666,7 +666,7 @@ - ReagentId: TonicWater Quantity: 150 - type: Label - currentLabel: tonic water + currentLabel: reagent-name-tonic-water - type: entity parent: [DrinkBottleVisualsOpenable, DrinkBottleGlassBaseFull] @@ -681,7 +681,7 @@ - ReagentId: Sake Quantity: 50 - type: Label - currentLabel: Sake + currentLabel: reagent-name-sake - type: Sprite sprite: Objects/Consumable/Drinks/sakebottle.rsi - type: Sealable @@ -703,7 +703,7 @@ Quantity: 150 - type: Drink - type: Label - currentLabel: lime juice + currentLabel: reagent-name-juice-lime - type: Sprite sprite: Objects/Consumable/Drinks/limejuice.rsi @@ -722,7 +722,7 @@ Quantity: 150 - type: Drink - type: Label - currentLabel: orange juice + currentLabel: reagent-name-juice-orange - type: Sprite sprite: Objects/Consumable/Drinks/orangejuice.rsi @@ -741,7 +741,7 @@ Quantity: 150 - type: Drink - type: Label - currentLabel: cream + currentLabel: reagent-name-cream - type: Sprite sprite: Objects/Consumable/Drinks/cream.rsi @@ -778,7 +778,7 @@ Quantity: 300 - type: Drink - type: Label - currentLabel: lemon-lime + currentLabel: reagent-name-lemon-lime - type: entity parent: DrinkBottlePlasticBaseFull @@ -795,7 +795,7 @@ Quantity: 150 - type: Drink - type: Label - currentLabel: mead + currentLabel: reagent-name-mead - type: entity parent: DrinkBottlePlasticBaseFull @@ -812,7 +812,7 @@ Quantity: 300 - type: Drink - type: Label - currentLabel: ice + currentLabel: reagent-name-ice - type: entity parent: DrinkBottlePlasticBaseFull @@ -829,7 +829,7 @@ Quantity: 300 - type: Drink - type: Label - currentLabel: coconut water + currentLabel: reagent-name-coconut-water - type: entity parent: DrinkBottlePlasticBaseFull @@ -846,7 +846,7 @@ Quantity: 300 - type: Drink - type: Label - currentLabel: coffee + currentLabel: reagent-name-coffee - type: entity parent: DrinkBottlePlasticBaseFull @@ -863,7 +863,7 @@ Quantity: 300 - type: Drink - type: Label - currentLabel: tea + currentLabel: reagent-name-tea - type: entity parent: DrinkBottlePlasticBaseFull @@ -880,7 +880,7 @@ Quantity: 300 - type: Drink - type: Label - currentLabel: green tea + currentLabel: reagent-name-green-tea - type: entity parent: DrinkBottlePlasticBaseFull @@ -896,6 +896,8 @@ - ReagentId: IcedTea Quantity: 300 - type: Drink + - type: Label + currentLabel: reagent-name-iced-tea - type: entity parent: DrinkBottlePlasticBaseFull @@ -912,7 +914,7 @@ Quantity: 300 - type: Drink - type: Label - currentLabel: dr gibb + currentLabel: reagent-name-dr-gibb - type: entity parent: DrinkBottlePlasticBaseFull @@ -929,7 +931,7 @@ Quantity: 300 - type: Drink - type: Label - currentLabel: root beer + currentLabel: reagent-name-root-beer - type: entity parent: DrinkBottlePlasticBaseFull @@ -946,7 +948,7 @@ Quantity: 300 - type: Drink - type: Label - currentLabel: watermelon juice + currentLabel: reagent-name-juice-watermelon - type: entity parent: DrinkBottlePlasticBaseFull diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_cans.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_cans.yml index 5d092673ed..f5cb260e03 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_cans.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_cans.yml @@ -56,7 +56,7 @@ - type: Tool qualities: - Rolling - speed: 0.25 # its small so takes longer to roll the entire dough flat + speedModifier: 0.25 # its small so takes longer to roll the entire dough flat - type: SpaceGarbage - type: TrashOnSolutionEmpty solution: drink diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/cigarette.yml b/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/cigarette.yml index f811afafba..146fac039d 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/cigarette.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/cigarette.yml @@ -18,6 +18,8 @@ equippedPrefix: unlit - type: Item size: Tiny + sprite: Objects/Consumable/Smokeables/Cigarettes/cigarette.rsi + heldPrefix: unlit - type: Construction graph: smokeableCigarette node: cigarette diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigars/cigar.yml b/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigars/cigar.yml index dc8d4eaf3c..93419993dc 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigars/cigar.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigars/cigar.yml @@ -20,6 +20,8 @@ equippedPrefix: unlit - type: Item size: Tiny + sprite: Objects/Consumable/Smokeables/Cigars/cigar.rsi + heldPrefix: unlit - type: entity id: CigarSpent diff --git a/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/reinforcement_teleporter.yml b/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/reinforcement_teleporter.yml index c62783fcee..2085422c9b 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/reinforcement_teleporter.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/reinforcement_teleporter.yml @@ -54,6 +54,23 @@ prototype: MobMonkeySyndicateAgentNukeops selectablePrototypes: ["SyndicateMonkeyNukeops", "SyndicateKoboldNukeops"] +- type: entity + parent: ReinforcementRadioSyndicate + id: ReinforcementRadioSyndicateSyndiCat + name: syndicat reinforcement radio + description: Calls in a faithfully trained cat with a microbomb to assist you. + components: + - type: GhostRole + name: ghost-role-information-SyndiCat-name + description: ghost-role-information-SyndiCat-description + rules: ghost-role-information-SyndiCat-rules + raffle: + settings: default + - type: GhostRoleMobSpawner + prototype: MobCatSyndy + - type: EmitSoundOnUse + sound: /Audio/Animals/cat_meow.ogg + - type: entity parent: ReinforcementRadioSyndicate id: ReinforcementRadioSyndicateCyborgAssault # Reinforcement radio exclusive to nukeops uplink diff --git a/Resources/Prototypes/Entities/Objects/Devices/chameleon_projector.yml b/Resources/Prototypes/Entities/Objects/Devices/chameleon_projector.yml index 282a20ddc4..78a513b0a9 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/chameleon_projector.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/chameleon_projector.yml @@ -34,7 +34,6 @@ # actions - type: entity - noSpawn: true id: ActionDisguiseNoRot name: Toggle Rotation description: Use this to prevent your disguise from rotating, making it easier to hide in some scenarios. @@ -44,7 +43,6 @@ event: !type:DisguiseToggleNoRotEvent - type: entity - noSpawn: true id: ActionDisguiseAnchor name: Toggle Anchored description: For many objects you will want to be anchored to not be completely obvious. diff --git a/Resources/Prototypes/Entities/Objects/Devices/flatpack.yml b/Resources/Prototypes/Entities/Objects/Devices/flatpack.yml index e3e77d5c88..b9c2b752db 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/flatpack.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/flatpack.yml @@ -4,7 +4,7 @@ name: base flatpack description: A flatpack used for constructing something. categories: - - hideSpawnMenu + - HideSpawnMenu components: - type: Item size: Large diff --git a/Resources/Prototypes/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Entities/Objects/Devices/pda.yml index b57992fe4f..5aaf634d0f 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/pda.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/pda.yml @@ -1,6 +1,6 @@ - type: entity abstract: true - parent: BaseItem + parent: [ BaseItem, StorePresetUplink ] #PDA's have uplinks so they have to inherit the data. id: BasePDA name: PDA description: Personal Data Assistant. diff --git a/Resources/Prototypes/Entities/Objects/Fun/Instruments/base_instruments.yml b/Resources/Prototypes/Entities/Objects/Fun/Instruments/base_instruments.yml index 122ff42eb2..614af2a488 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/Instruments/base_instruments.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/Instruments/base_instruments.yml @@ -1,4 +1,4 @@ -- type: entity +- type: entity abstract: true parent: BaseItem id: BaseHandheldInstrument @@ -71,6 +71,7 @@ - BulletImpassable - type: StaticPrice price: 300 + - type: RequireProjectileTarget - type: entity parent: BasePlaceableInstrument diff --git a/Resources/Prototypes/Entities/Objects/Fun/Instruments/instruments_misc.yml b/Resources/Prototypes/Entities/Objects/Fun/Instruments/instruments_misc.yml index edad2b4063..8cb3d88ede 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/Instruments/instruments_misc.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/Instruments/instruments_misc.yml @@ -59,7 +59,7 @@ size: Small - type: Prayable sentMessage: prayer-popup-notify-centcom-sent - notifiactionPrefix: prayer-chat-notify-centcom + notificationPrefix: prayer-chat-notify-centcom verb: prayer-verbs-call verbImage: null @@ -74,7 +74,7 @@ state: icon - type: Prayable sentMessage: prayer-popup-notify-syndicate-sent - notifiactionPrefix: prayer-chat-notify-syndicate + notificationPrefix: prayer-chat-notify-syndicate - type: entity parent: BaseHandheldInstrument @@ -185,6 +185,6 @@ - ItemMask - type: Prayable sentMessage: prayer-popup-notify-honkmother-sent - notifiactionPrefix: prayer-chat-notify-honkmother + notificationPrefix: prayer-chat-notify-honkmother verb: prayer-verbs-call verbImage: null diff --git a/Resources/Prototypes/Entities/Objects/Fun/darts.yml b/Resources/Prototypes/Entities/Objects/Fun/darts.yml index 36c841995e..c0b5eb3399 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/darts.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/darts.yml @@ -83,10 +83,6 @@ max: 1 - !type:DoActsBehavior acts: [ "Destruction" ] - - type: Appearance - - type: SolutionContainerVisuals - maxFillLevels: 1 - fillBaseName: dart - type: entity parent: Dart diff --git a/Resources/Prototypes/Entities/Objects/Fun/pai.yml b/Resources/Prototypes/Entities/Objects/Fun/pai.yml index 1b9c5303c6..02bbce2843 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/pai.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/pai.yml @@ -136,7 +136,6 @@ id: ActionPAIPlayMidi name: Play MIDI description: Open your portable MIDI interface to soothe your owner. - noSpawn: true components: - type: InstantAction checkCanInteract: false @@ -149,7 +148,6 @@ id: ActionPAIOpenMap name: Open Map description: Open your map interface and guide your owner. - noSpawn: true components: - type: InstantAction checkCanInteract: false diff --git a/Resources/Prototypes/Entities/Objects/Magic/books.yml b/Resources/Prototypes/Entities/Objects/Magic/books.yml index 554c5214c1..e47fa00c45 100644 --- a/Resources/Prototypes/Entities/Objects/Magic/books.yml +++ b/Resources/Prototypes/Entities/Objects/Magic/books.yml @@ -25,7 +25,7 @@ id: WizardsGrimoire name: wizards grimoire suffix: Wizard - parent: BaseItem + parent: [ BaseItem, StorePresetSpellbook ] components: - type: Sprite sprite: Objects/Misc/books.rsi @@ -46,7 +46,6 @@ - type: Store refundAllowed: true ownerOnly: true # get your own tome! - preset: StorePresetSpellbook balance: WizCoin: 10 # prices are balanced around this 10 point maximum and how strong the spells are @@ -55,12 +54,11 @@ id: WizardsGrimoireNoRefund name: wizards grimoire suffix: Wizard, No Refund - parent: WizardsGrimoire + parent: [ WizardsGrimoire, StorePresetSpellbook ] components: - type: Store refundAllowed: false ownerOnly: true # get your own tome! - preset: StorePresetSpellbook balance: WizCoin: 10 # prices are balanced around this 10 point maximum and how strong the spells are diff --git a/Resources/Prototypes/Entities/Objects/Materials/shards.yml b/Resources/Prototypes/Entities/Objects/Materials/shards.yml index 561140a46a..fa6937dac3 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/shards.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/shards.yml @@ -85,7 +85,7 @@ components: - type: Sprite color: "#bbeeff" - - type: WelderRefinable + - type: ToolRefinable refineResult: - id: SheetGlass1 - type: DamageUserOnTrigger @@ -120,7 +120,7 @@ damage: types: Slash: 4.5 - - type: WelderRefinable + - type: ToolRefinable refineResult: - id: SheetGlass1 - id: PartRodMetal1 @@ -156,7 +156,7 @@ damage: types: Slash: 5.5 - - type: WelderRefinable + - type: ToolRefinable refineResult: - id: SheetGlass1 - id: SheetPlasma1 @@ -195,7 +195,7 @@ types: Slash: 4.5 Radiation: 2 - - type: WelderRefinable + - type: ToolRefinable refineResult: - id: SheetGlass1 - id: SheetUranium1 @@ -230,7 +230,7 @@ components: - type: Sprite color: "#e0aa36" - - type: WelderRefinable + - type: ToolRefinable refineResult: - id: SheetGlass1 - id: SheetBrass1 diff --git a/Resources/Prototypes/Entities/Objects/Misc/broken_bottle.yml b/Resources/Prototypes/Entities/Objects/Misc/broken_bottle.yml index 32222d0036..9d3ef6c424 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/broken_bottle.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/broken_bottle.yml @@ -26,6 +26,6 @@ materialComposition: Glass: 50 - type: SpaceGarbage - - type: WelderRefinable + - type: ToolRefinable refineResult: - id: SheetGlass1 diff --git a/Resources/Prototypes/Entities/Objects/Misc/fire_extinguisher.yml b/Resources/Prototypes/Entities/Objects/Misc/fire_extinguisher.yml index 112ce99710..71629919ae 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/fire_extinguisher.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/fire_extinguisher.yml @@ -46,7 +46,7 @@ - type: Tool qualities: - Rolling - speed: 0.5 # its very big, akward to use + speedModifier: 0.5 # its very big, akward to use - type: Appearance - type: GenericVisualizer visuals: diff --git a/Resources/Prototypes/Entities/Objects/Misc/kudzu.yml b/Resources/Prototypes/Entities/Objects/Misc/kudzu.yml index a100500494..ca56ef5acb 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/kudzu.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/kudzu.yml @@ -76,7 +76,7 @@ scaleByQuantity: true damage: types: - Heat: 10 + Heat: 0.5 - type: AtmosExposed - type: Kudzu growthTickChance: 0.3 diff --git a/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml b/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml index 62a63c80c3..37de294cce 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml @@ -9,6 +9,8 @@ sprite: Objects/Misc/stock_parts.rsi - type: Item size: Tiny + - type: Stack + count: 1 - type: entity id: CapacitorStockPart @@ -25,6 +27,8 @@ - type: Tag tags: - CapacitorStockPart + - type: Stack + stackType: Capacitor - type: entity id: MicroManipulatorStockPart @@ -38,6 +42,8 @@ - type: MachinePart part: Manipulator rating: 1 + - type: Stack + stackType: MicroManipulator - type: entity id: MatterBinStockPart @@ -51,3 +57,5 @@ - type: MachinePart part: MatterBin rating: 1 + - type: Stack + stackType: MatterBin diff --git a/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml b/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml index c92985c2cb..9690d0bdfe 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml @@ -143,7 +143,7 @@ - Cuffable # useless if you cant be cuffed - type: entity - parent: BaseSubdermalImplant + parent: [ BaseSubdermalImplant, StorePresetUplink ] id: UplinkImplant name: uplink implant description: This implant lets the user access a hidden Syndicate uplink at will. @@ -155,7 +155,6 @@ components: - Hands # prevent mouse buying grenade penguin since its not telepathic - type: Store - preset: StorePresetUplink balance: Telecrystal: 0 - type: UserInterface diff --git a/Resources/Prototypes/Entities/Objects/Power/lights.yml b/Resources/Prototypes/Entities/Objects/Power/lights.yml index b18a0feaa5..0f54dbfbcb 100644 --- a/Resources/Prototypes/Entities/Objects/Power/lights.yml +++ b/Resources/Prototypes/Entities/Objects/Power/lights.yml @@ -66,7 +66,7 @@ materialComposition: Glass: 25 - type: SpaceGarbage - - type: WelderRefinable + - type: ToolRefinable refineResult: - id: SheetGlass1 @@ -274,7 +274,7 @@ - type: Construction graph: CyanLight node: icon - - type: WelderRefinable + - type: ToolRefinable refineResult: - id: SheetGlass1 - id: ShardCrystalCyan @@ -294,7 +294,7 @@ - type: Construction graph: BlueLight node: icon - - type: WelderRefinable + - type: ToolRefinable refineResult: - id: SheetGlass1 - id: ShardCrystalBlue @@ -314,7 +314,7 @@ - type: Construction graph: PinkLight node: icon - - type: WelderRefinable + - type: ToolRefinable refineResult: - id: SheetGlass1 - id: ShardCrystalPink @@ -334,7 +334,7 @@ - type: Construction graph: OrangeLight node: icon - - type: WelderRefinable + - type: ToolRefinable refineResult: - id: SheetGlass1 - id: ShardCrystalOrange @@ -354,7 +354,7 @@ - type: Construction graph: RedLight node: icon - - type: WelderRefinable + - type: ToolRefinable refineResult: - id: SheetGlass1 - id: ShardCrystalRed @@ -374,7 +374,7 @@ - type: Construction graph: GreenLight node: icon - - type: WelderRefinable + - type: ToolRefinable refineResult: - id: SheetGlass1 - id: ShardCrystalGreen diff --git a/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml b/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml index cd1ba569ce..088a503cf5 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml @@ -85,7 +85,6 @@ id: ActionBibleSummon name: Summon familiar description: Summon a familiar that will aid you and gain humanlike intelligence once inhabited by a soul. - noSpawn: true components: - type: InstantAction icon: { sprite: Clothing/Head/Hats/witch.rsi, state: icon } diff --git a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml index 868d012a87..cb5f875204 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml @@ -6,7 +6,17 @@ components: - type: Sprite sprite: Objects/Specific/Janitorial/mop.rsi - state: mop + layers: + - state: mop + - map: ["enum.SolutionContainerLayers.Fill"] + state: fill-3 + visible: false + - type: Appearance + - type: SolutionContainerVisuals + maxFillLevels: 3 + fillBaseName: fill- + inHandsFillBaseName: -fill- + inHandsMaxFillLevels: 2 - type: MeleeWeapon damage: types: @@ -49,7 +59,17 @@ components: - type: Sprite sprite: Objects/Specific/Janitorial/advmop.rsi - state: advmop + layers: + - state: advmop + - map: ["enum.SolutionContainerLayers.Fill"] + state: fill-2 + visible: false + - type: Appearance + - type: SolutionContainerVisuals + maxFillLevels: 2 + fillBaseName: fill- + inHandsFillBaseName: -fill- + inHandsMaxFillLevels: 2 - type: MeleeWeapon damage: types: @@ -244,7 +264,15 @@ components: - type: Sprite sprite: Objects/Specific/Janitorial/rag.rsi - state: rag + layers: + - state: rag + - map: ["enum.SolutionContainerLayers.Fill"] + state: fill-3 + visible: false + - type: Appearance + - type: SolutionContainerVisuals + maxFillLevels: 3 + fillBaseName: fill- - type: Spillable solution: absorbed - type: MeleeWeapon diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/surgery.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/surgery.yml index aa0cf46187..c4f7798154 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Medical/surgery.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/surgery.yml @@ -178,7 +178,7 @@ - type: Tool qualities: - Sawing - speed: 1.0 + speedModifier: 1.0 # No melee for regular saw because have you ever seen someone use a band saw as a weapon? It's dumb. - type: entity @@ -200,7 +200,7 @@ - type: Tool qualities: - Sawing - speed: 0.5 + speedModifier: 0.5 - type: entity name: circular saw @@ -222,7 +222,7 @@ - type: Tool qualities: - Sawing - speed: 1.5 + speedModifier: 1.5 - type: entity name: advanced circular saw @@ -245,4 +245,4 @@ - type: Tool qualities: - Sawing - speed: 2.0 + speedModifier: 2.0 diff --git a/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml b/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml index 07206711d9..74e91a768c 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml @@ -32,7 +32,6 @@ id: ActionBorgSwapModule name: Swap Module description: Select this module, enabling you to use the tools it provides. - noSpawn: true components: - type: InstantAction itemIconStyle: BigItem diff --git a/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml b/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml index 801be7d596..a88ff0291d 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml @@ -233,6 +233,8 @@ sprite: Objects/Specific/Chemistry/beaker_cryostasis.rsi layers: - state: beakernoreact + - type: SolutionContainerVisuals + maxFillLevels: 0 - type: SolutionContainerManager solutions: beaker: @@ -255,6 +257,8 @@ sprite: Objects/Specific/Chemistry/beaker_bluespace.rsi layers: - state: beakerbluespace + - type: SolutionContainerVisuals + maxFillLevels: 0 - type: SolutionContainerManager solutions: beaker: diff --git a/Resources/Prototypes/Entities/Objects/Specific/syndicate.yml b/Resources/Prototypes/Entities/Objects/Specific/syndicate.yml index 3a686882cf..53d4f7953b 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/syndicate.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/syndicate.yml @@ -48,7 +48,7 @@ # Uplinks - type: entity - parent: BaseItem + parent: [ BaseItem, StorePresetUplink ] id: BaseUplinkRadio name: syndicate uplink description: Suspiciously looking old radio... @@ -68,7 +68,6 @@ - type: ActivatableUI key: enum.StoreUiKey.Key - type: Store - preset: StorePresetUplink balance: Telecrystal: 0 @@ -78,7 +77,6 @@ suffix: 20 TC components: - type: Store - preset: StorePresetUplink balance: Telecrystal: 20 @@ -88,7 +86,6 @@ suffix: 25 TC components: - type: Store - preset: StorePresetUplink balance: Telecrystal: 25 @@ -99,19 +96,29 @@ suffix: 40 TC, NukeOps components: - type: Store - preset: StorePresetUplink balance: Telecrystal: 40 - type: Tag tags: - NukeOpsUplink +- type: entity + parent: BaseUplinkRadio + id: BaseUplinkRadio60TC + suffix: 60 TC, LoneOps + components: + - type: Store + balance: + Telecrystal: 60 + - type: Tag + tags: + - NukeOpsUplink + - type: entity parent: BaseUplinkRadio id: BaseUplinkRadioDebug suffix: DEBUG components: - type: Store - preset: StorePresetUplink balance: Telecrystal: 99999 diff --git a/Resources/Prototypes/Entities/Objects/Tools/cowtools.yml b/Resources/Prototypes/Entities/Objects/Tools/cowtools.yml index 87959ebef3..c9b37b8b1a 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/cowtools.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/cowtools.yml @@ -21,7 +21,7 @@ - Cutting useSound: path: /Audio/Items/wirecutter.ogg - speed: 0.05 + speedModifier: 0.05 - type: Item sprite: Objects/Tools/Cowtools/haycutters.rsi @@ -46,7 +46,7 @@ - Screwing useSound: collection: Screwdriver - speed: 0.05 + speedModifier: 0.05 - type: entity name: wronch @@ -69,7 +69,7 @@ - Anchoring useSound: path: /Audio/Items/ratchet.ogg - speed: 0.05 + speedModifier: 0.05 - type: entity name: cowbar @@ -93,7 +93,7 @@ - Prying useSound: path: /Audio/Items/crowbar.ogg - speed: 0.05 + speedModifier: 0.05 - type: ToolTileCompatible - type: Prying @@ -127,7 +127,7 @@ size: Small sprite: Objects/Tools/Cowtools/cowelder.rsi - type: Tool - speed: 0.05 + speedModifier: 0.05 - type: entity name: milkalyzer diff --git a/Resources/Prototypes/Entities/Objects/Tools/fulton.yml b/Resources/Prototypes/Entities/Objects/Tools/fulton.yml index 5255e5f303..cfd0b8f770 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/fulton.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/fulton.yml @@ -7,7 +7,6 @@ state: extraction_pack spawn: Fulton1 maxCount: 10 - itemSize: 2 # Entities - type: entity diff --git a/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml b/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml index 7bdd32f457..53423e84a4 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml @@ -51,7 +51,7 @@ - type: Tool qualities: - Rolling - speed: 0.6 # fairly unwieldly but nice round surface + speedModifier: 0.6 # fairly unwieldly but nice round surface - type: entity parent: GasTankRoundBase diff --git a/Resources/Prototypes/Entities/Objects/Tools/jaws_of_life.yml b/Resources/Prototypes/Entities/Objects/Tools/jaws_of_life.yml index 8e2b759797..c80e53870e 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/jaws_of_life.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/jaws_of_life.yml @@ -21,7 +21,7 @@ - type: Tool qualities: - Prying - speed: 1.5 + speedModifier: 1.5 useSound: /Audio/Items/jaws_pry.ogg - type: Prying pryPowered: true @@ -69,7 +69,7 @@ - type: Tool qualities: - Prying - speed: 3.0 + speedModifier: 3.0 - type: MultipleTool entries: - behavior: Prying diff --git a/Resources/Prototypes/Entities/Objects/Tools/jetpacks.yml b/Resources/Prototypes/Entities/Objects/Tools/jetpacks.yml index 7decafbd09..257025ff85 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/jetpacks.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/jetpacks.yml @@ -68,7 +68,6 @@ id: ActionToggleJetpack name: Toggle jetpack description: Toggles the jetpack, giving you movement outside the station. - noSpawn: true components: - type: InstantAction icon: diff --git a/Resources/Prototypes/Entities/Objects/Tools/tools.yml b/Resources/Prototypes/Entities/Objects/Tools/tools.yml index 47c11962bf..8680a08c18 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/tools.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/tools.yml @@ -304,7 +304,7 @@ - type: Tool qualities: - Screwing - speed: 1.5 + speedModifier: 1.5 useSound: /Audio/Items/drill_use.ogg - type: MultipleTool statusShowBehavior: true @@ -491,7 +491,7 @@ - type: Tool qualities: - Screwing - speed: 1.2 # Kept for future adjustments. Currently 1.2x for balance + speedModifier: 1.2 # Kept for future adjustments. Currently 1.2x for balance useSound: /Audio/Items/drill_use.ogg - type: ToolTileCompatible - type: MultipleTool diff --git a/Resources/Prototypes/Entities/Objects/Tools/welders.yml b/Resources/Prototypes/Entities/Objects/Tools/welders.yml index 9bf3f2e2cb..9db30edb52 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/welders.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/welders.yml @@ -139,7 +139,7 @@ Quantity: 250 maxVol: 250 - type: Tool - speed: 1.3 + speedModifier: 1.3 - type: entity name: experimental welding tool @@ -190,7 +190,7 @@ Quantity: 50 maxVol: 50 - type: Tool - speed: 0.7 + speedModifier: 0.7 - type: PointLight enabled: false radius: 1.0 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/baseball_bat.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/baseball_bat.yml index 5347096bf1..818c4bd676 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/baseball_bat.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/baseball_bat.yml @@ -26,7 +26,7 @@ - type: Tool qualities: - Rolling - speed: 0.75 # a bit unwieldly but does the job + speedModifier: 0.75 # a bit unwieldly but does the job - type: Clothing quickEquip: false slots: diff --git a/Resources/Prototypes/Entities/Objects/base_shadow.yml b/Resources/Prototypes/Entities/Objects/base_shadow.yml index 2375855bf9..1cefef9e56 100644 --- a/Resources/Prototypes/Entities/Objects/base_shadow.yml +++ b/Resources/Prototypes/Entities/Objects/base_shadow.yml @@ -15,9 +15,6 @@ path: /Audio/Items/hiss.ogg params: variation: 0.08 - - type: Flashable - collisionGroup: - - None - type: DamagedByFlashing flashDamage: types: diff --git a/Resources/Prototypes/Entities/Structures/Decoration/banners.yml b/Resources/Prototypes/Entities/Structures/Decoration/banners.yml index be286c6309..0106aaf32a 100644 --- a/Resources/Prototypes/Entities/Structures/Decoration/banners.yml +++ b/Resources/Prototypes/Entities/Structures/Decoration/banners.yml @@ -91,7 +91,7 @@ id: BannerScience parent: BannerBase name: epistemics banner # DeltaV - Epistemics Department replacing Science - description: A banner displaying the colors of the epistemics department. Where stupidity is proven greater than the universe. # DeltaV - Epistemics Department replacing Science + description: A banner displaying the colors of the epistemics department. Where science has no bounds, and regulations are rarely followed. # DeltaV - Epistemics Department replacing Science components: - type: Sprite sprite: Structures/Decoration/banner.rsi @@ -101,7 +101,7 @@ id: BannerSecurity parent: BannerBase name: security banner - description: A banner displaying the colors of the shitcurity department. Security, my bad. + description: A banner displaying the colors of the security department. You're surprised it's not vandalised. components: - type: Sprite sprite: Structures/Decoration/banner.rsi diff --git a/Resources/Prototypes/Entities/Structures/Decoration/bonfire.yml b/Resources/Prototypes/Entities/Structures/Decoration/bonfire.yml index cc69a6304d..29efdaea5d 100644 --- a/Resources/Prototypes/Entities/Structures/Decoration/bonfire.yml +++ b/Resources/Prototypes/Entities/Structures/Decoration/bonfire.yml @@ -2,7 +2,7 @@ id: Bonfire parent: BaseStructure name: bonfire - description: What can be better then late evening under the sky with guitar and friends. + description: What can be better than a late evening under the sky with guitar and friends? components: - type: Sprite noRot: true diff --git a/Resources/Prototypes/Entities/Structures/Dispensers/base_structuredispensers.yml b/Resources/Prototypes/Entities/Structures/Dispensers/base_structuredispensers.yml index 082f5e0799..213ad47d88 100644 --- a/Resources/Prototypes/Entities/Structures/Dispensers/base_structuredispensers.yml +++ b/Resources/Prototypes/Entities/Structures/Dispensers/base_structuredispensers.yml @@ -1,7 +1,7 @@ - type: entity abstract: true id: ReagentDispenserBase - parent: ConstructibleMachine + parent: SmallConstructibleMachine placement: mode: SnapgridCenter components: diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/access.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/access.yml index f417b60677..269c71c5f3 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/access.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/access.yml @@ -73,15 +73,6 @@ containers: board: [ DoorElectronicsHydroponics ] -- type: entity - parent: AirlockCommandLocked - id: AirlockServiceCaptainLocked - suffix: Captain, Locked - components: - - type: ContainerFill - containers: - board: [ DoorElectronicsCaptain ] - - type: entity parent: AirlockExternal id: AirlockExternalLocked diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/external.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/external.yml index 293aaac273..3197ba417f 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/external.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/external.yml @@ -33,3 +33,14 @@ sprite: Structures/Doors/Airlocks/Glass/external.rsi - type: PaintableAirlock group: ExternalGlass + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.49,-0.49,0.49,0.49" # don't want this colliding with walls or they won't close + density: 100 + mask: + - FullTileMask + layer: #removed opaque from the layer, allowing lasers to pass through glass airlocks + - GlassAirlockLayer diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/shuttle.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/shuttle.yml index 43d1228a40..5d27cec181 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/shuttle.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/shuttle.yml @@ -82,29 +82,17 @@ components: - type: Sprite sprite: Structures/Doors/Airlocks/Glass/shuttle.rsi - snapCardinals: false - layers: - - state: closed - map: ["enum.DoorVisualLayers.Base"] - - state: closed_unlit - shader: unshaded - map: ["enum.DoorVisualLayers.BaseUnlit"] - - state: welded - map: ["enum.WeldableLayers.BaseWelded"] - - state: bolted_unlit - shader: unshaded - map: ["enum.DoorVisualLayers.BaseBolted"] - - state: emergency_unlit - shader: unshaded - map: ["enum.DoorVisualLayers.BaseEmergencyAccess"] - - state: panel_open - map: ["enum.WiresVisualLayers.MaintenancePanel"] - type: Occluder enabled: false - type: PaintableAirlock group: ShuttleGlass - type: Door occludes: false + - type: Fixtures + fixtures: + fix1: + layer: #removed opaque from the layer, allowing lasers to pass through glass airlocks + - GlassAirlockLayer - type: entity id: AirlockShuttleAssembly @@ -126,36 +114,13 @@ - type: entity id: AirlockGlassShuttleSyndicate - parent: AirlockShuttle + parent: AirlockGlassShuttle name: external airlock suffix: Glass, Docking description: Necessary for connecting two space craft together. components: - type: Sprite sprite: Structures/Doors/Airlocks/Glass/shuttle_syndicate.rsi - snapCardinals: false - layers: - - state: closed - map: ["enum.DoorVisualLayers.Base"] - - state: closed_unlit - shader: unshaded - map: ["enum.DoorVisualLayers.BaseUnlit"] - - state: welded - map: ["enum.WeldableLayers.BaseWelded"] - - state: bolted_unlit - shader: unshaded - map: ["enum.DoorVisualLayers.BaseBolted"] - - state: emergency_unlit - shader: unshaded - map: ["enum.DoorVisualLayers.BaseEmergencyAccess"] - - state: panel_open - map: ["enum.WiresVisualLayers.MaintenancePanel"] - - type: Occluder - enabled: false - - type: PaintableAirlock - group: ShuttleGlass - - type: Door - occludes: false - type: entity parent: AirlockShuttle diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/base_structurecomputers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/base_structurecomputers.yml index a5e26463b9..204e06c860 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/base_structurecomputers.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/base_structurecomputers.yml @@ -60,3 +60,4 @@ ents: [] - type: LightningTarget priority: 1 + - type: RequireProjectileTarget diff --git a/Resources/Prototypes/Entities/Structures/Machines/Medical/chemistry_machines.yml b/Resources/Prototypes/Entities/Structures/Machines/Medical/chemistry_machines.yml index 23789730d0..65eaf04d78 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Medical/chemistry_machines.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Medical/chemistry_machines.yml @@ -1,6 +1,6 @@ - type: entity id: BaseTabletopChemicalMachine - parent: [ BaseMachinePowered, ConstructibleMachine ] + parent: [ BaseMachinePowered, SmallConstructibleMachine ] abstract: true components: - type: Transform diff --git a/Resources/Prototypes/Entities/Structures/Machines/Medical/disease_diagnoser.yml b/Resources/Prototypes/Entities/Structures/Machines/Medical/disease_diagnoser.yml index e46c62053a..ad98f47e36 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Medical/disease_diagnoser.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Medical/disease_diagnoser.yml @@ -1,6 +1,6 @@ - type: entity id: DiseaseDiagnoser - parent: [ BaseMachinePowered, ConstructibleMachine ] + parent: [ BaseMachinePowered, SmallConstructibleMachine ] name: Disease Diagnoser Delta Extreme description: A machine that analyzes disease samples. placement: @@ -43,5 +43,3 @@ contentMargin: 12.0, 0.0, 12.0, 0.0 # This is a narrow piece of paper maxWritableArea: 128.0, 0.0 - - diff --git a/Resources/Prototypes/Entities/Structures/Machines/Medical/vaccinator.yml b/Resources/Prototypes/Entities/Structures/Machines/Medical/vaccinator.yml index 041bca7c90..53542cdfa9 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Medical/vaccinator.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Medical/vaccinator.yml @@ -24,3 +24,4 @@ containers: machine_board: !type:Container machine_parts: !type:Container + - type: RequireProjectileTarget diff --git a/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml b/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml index 8b0c578763..9c878c7e7c 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml @@ -1,6 +1,6 @@ - type: entity id: MachineArtifactAnalyzer - parent: [ BaseMachinePowered, ConstructibleMachine ] + parent: [ BaseMachinePowered, SmallConstructibleMachine ] name: artifact analyzer description: A platform capable of performing analysis on various types of artifacts. components: @@ -35,6 +35,7 @@ - Impassable - MidImpassable - LowImpassable + - BulletImpassable hard: False - type: Transform anchored: true diff --git a/Resources/Prototypes/Entities/Structures/Machines/base_structuremachines.yml b/Resources/Prototypes/Entities/Structures/Machines/base_structuremachines.yml index 621d9a1a7e..fb5ed4440a 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/base_structuremachines.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/base_structuremachines.yml @@ -70,3 +70,10 @@ - machine_board - type: LightningTarget priority: 1 + +- type: entity + abstract: true + parent: ConstructibleMachine + id: SmallConstructibleMachine + components: + - type: RequireProjectileTarget diff --git a/Resources/Prototypes/Entities/Structures/Machines/fax_machine.yml b/Resources/Prototypes/Entities/Structures/Machines/fax_machine.yml index 9695ee4e1b..e56a3379eb 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/fax_machine.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/fax_machine.yml @@ -69,6 +69,7 @@ deviceNetId: Wireless receiveFrequencyId: Fax transmitFrequencyId: Fax + - type: RequireProjectileTarget # Special - type: entity diff --git a/Resources/Prototypes/Entities/Structures/Machines/flatpacker.yml b/Resources/Prototypes/Entities/Structures/Machines/flatpacker.yml index b4f05cf68a..78f1504003 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/flatpacker.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/flatpacker.yml @@ -84,7 +84,7 @@ - type: entity id: FlatpackerNoBoardEffect categories: - - hideSpawnMenu + - HideSpawnMenu components: - type: Sprite sprite: Structures/Machines/autolathe.rsi diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index ec10b4031e..ce5c7780c2 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -523,6 +523,7 @@ - Sheet - RawMaterial - Ingot + - type: RequireProjectileTarget - type: entity id: CircuitImprinterHyperConvection diff --git a/Resources/Prototypes/Entities/Structures/Machines/microwave.yml b/Resources/Prototypes/Entities/Structures/Machines/microwave.yml index fe4eb14518..994269f71b 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/microwave.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/microwave.yml @@ -1,6 +1,6 @@ -- type: entity +- type: entity id: KitchenMicrowave - parent: [ BaseMachinePowered, ConstructibleMachine ] + parent: [ BaseMachinePowered, SmallConstructibleMachine ] name: microwave description: It's magic. components: diff --git a/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml b/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml index d6e7333313..28aa464d21 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml @@ -1,6 +1,6 @@ - type: entity id: KitchenReagentGrinder - parent: [ BaseMachinePowered, ConstructibleMachine ] + parent: [ BaseMachinePowered, SmallConstructibleMachine ] name: reagent grinder description: From BlenderTech. Will It Blend? Let's find out! suffix: grinder/juicer diff --git a/Resources/Prototypes/Entities/Structures/Machines/wireless_surveillance_camera.yml b/Resources/Prototypes/Entities/Structures/Machines/wireless_surveillance_camera.yml index fc8f31535c..95079b5c85 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/wireless_surveillance_camera.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/wireless_surveillance_camera.yml @@ -1,6 +1,6 @@ - type: entity abstract: true - parent: [ BaseStructureDynamic, ConstructibleMachine ] + parent: [ BaseStructureDynamic, SmallConstructibleMachine ] id: SurveillanceWirelessCameraBase name: wireless camera description: A camera. It's watching you. Kinda. @@ -23,6 +23,8 @@ density: 80 mask: - MachineMask + layer: + - BulletImpassable - type: SurveillanceCameraMicrophone blacklist: components: diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/portable.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/portable.yml index 200df727b3..87e71400f7 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/portable.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/portable.yml @@ -1,6 +1,6 @@ - type: entity id: PortableScrubber - parent: [BaseMachinePowered, ConstructibleMachine, StructureWheeled] + parent: [BaseMachinePowered, SmallConstructibleMachine, StructureWheeled] name: portable scrubber description: It scrubs, portably! components: diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml index d301f43c78..2b00fec246 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml @@ -383,7 +383,7 @@ board: HellfireHeaterMachineCircuitBoard - type: entity - parent: [ BaseMachinePowered, ConstructibleMachine ] + parent: [ BaseMachinePowered, SmallConstructibleMachine ] id: BaseGasCondenser name: condenser description: Condenses gases into liquids. Now we just need some plumbing. diff --git a/Resources/Prototypes/Entities/Structures/Piping/Disposal/units.yml b/Resources/Prototypes/Entities/Structures/Piping/Disposal/units.yml index 2198c854a0..e7d3d3c997 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Disposal/units.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Disposal/units.yml @@ -84,6 +84,7 @@ enum.DisposalUnitUiKey.Key: type: DisposalUnitBoundUserInterface - type: RatKingRummageable + - type: RequireProjectileTarget - type: entity id: MailingUnit diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/emitter.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/emitter.yml index b999b2bded..6946dcbf83 100644 --- a/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/emitter.yml +++ b/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/emitter.yml @@ -1,7 +1,7 @@ - type: entity id: Emitter name: emitter - parent: ConstructibleMachine + parent: SmallConstructibleMachine description: A heavy duty industrial laser. Shoots non-stop when turned on. placement: mode: SnapgridCenter diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/portable_generator.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/portable_generator.yml index d735d9607c..86cfb0f799 100644 --- a/Resources/Prototypes/Entities/Structures/Power/Generation/portable_generator.yml +++ b/Resources/Prototypes/Entities/Structures/Power/Generation/portable_generator.yml @@ -1,4 +1,4 @@ -# +# # You can use this Desmos sheet to calculate fuel burn rate values: # https://www.desmos.com/calculator/qcektq5dqs # @@ -6,7 +6,7 @@ - type: entity abstract: true id: PortableGeneratorBase - parent: [ BaseMachine, ConstructibleMachine, StructureWheeled] + parent: [ BaseMachine, SmallConstructibleMachine, StructureWheeled] components: # Basic properties - type: Transform diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/solar.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/solar.yml index 5a28c4962c..c512266e97 100644 --- a/Resources/Prototypes/Entities/Structures/Power/Generation/solar.yml +++ b/Resources/Prototypes/Entities/Structures/Power/Generation/solar.yml @@ -49,6 +49,7 @@ onBump: false requirePower: true highVoltageNode: output + - type: RequireProjectileTarget - type: entity id: SolarPanel @@ -157,6 +158,7 @@ graph: SolarPanel node: solarassembly defaultTarget: solarpanel + - type: RequireProjectileTarget - type: entity id: SolarTracker @@ -201,3 +203,4 @@ - type: Construction graph: SolarPanel node: solartracker + - type: RequireProjectileTarget diff --git a/Resources/Prototypes/Entities/Structures/Power/chargers.yml b/Resources/Prototypes/Entities/Structures/Power/chargers.yml index 582a5b0dee..f5f0748b81 100644 --- a/Resources/Prototypes/Entities/Structures/Power/chargers.yml +++ b/Resources/Prototypes/Entities/Structures/Power/chargers.yml @@ -58,12 +58,15 @@ density: 500 mask: - TabletopMachineMask + layer: + - BulletImpassable - type: PowerChargerVisuals - type: ContainerContainer containers: charger_slot: !type:ContainerSlot machine_board: !type:Container machine_parts: !type:Container + - type: RequireProjectileTarget - type: entity parent: BaseItemRecharger diff --git a/Resources/Prototypes/Entities/Structures/Storage/Crates/base_structurecrates.yml b/Resources/Prototypes/Entities/Structures/Storage/Crates/base_structurecrates.yml index 2d84541231..01c226cb0f 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Crates/base_structurecrates.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Crates/base_structurecrates.yml @@ -89,6 +89,7 @@ node: crategenericsteel containers: - entity_storage + - type: RequireProjectileTarget - type: entity parent: CrateGeneric diff --git a/Resources/Prototypes/Entities/Structures/Storage/Tanks/tanks.yml b/Resources/Prototypes/Entities/Structures/Storage/Tanks/tanks.yml index 934298b620..e7fcf964d1 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Tanks/tanks.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Tanks/tanks.yml @@ -25,15 +25,14 @@ - type: ReagentTank tankType: Fuel - type: DamageOnToolInteract - tools: - - Welding + tools: Welding weldingDamage: types: Heat: 10 - type: PacifismDangerousAttack - type: Explosive explosionType: Default - totalIntensity: 120 # ~ 5 tile radius + totalIntensity: 60 # Mediocre explosion. Not enough to do any meaningful structural damage to anything other then windows, provided you're only using one tank. - type: entity id: WeldingFuelTankFull @@ -75,7 +74,7 @@ maxVol: 5000 - type: Explosive explosionType: Default - totalIntensity: 140 + totalIntensity: 120 # Water @@ -217,4 +216,3 @@ fillBaseName: watertank-2- - type: ExaminableSolution solution: tank - diff --git a/Resources/Prototypes/Entities/Structures/Storage/filing_cabinets.yml b/Resources/Prototypes/Entities/Structures/Storage/filing_cabinets.yml index 08db462be0..cfda95fc2f 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/filing_cabinets.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/filing_cabinets.yml @@ -156,6 +156,7 @@ node: chestDrawer - type: StaticPrice price: 15 + - type: RequireProjectileTarget - type: entity abstract: true diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/walldispenser.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/walldispenser.yml index 72ea308af0..3570264a57 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/walldispenser.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/walldispenser.yml @@ -82,8 +82,7 @@ - type: ReagentTank tankType: Fuel - type: DamageOnToolInteract - tools: - - Welding + tools: Welding weldingDamage: types: Heat: 20 diff --git a/Resources/Prototypes/Entities/Structures/Walls/fence_wood.yml b/Resources/Prototypes/Entities/Structures/Walls/fence_wood.yml index 55b7e40803..41dbe21d5f 100644 --- a/Resources/Prototypes/Entities/Structures/Walls/fence_wood.yml +++ b/Resources/Prototypes/Entities/Structures/Walls/fence_wood.yml @@ -72,7 +72,7 @@ acts: [ "Destruction" ] - type: Climbable delay: 2.5 - + - type: RequireProjectileTarget #High - type: entity @@ -96,8 +96,10 @@ mask: - FullTileMask layer: + - Opaque - MidImpassable - LowImpassable + - BulletImpassable - type: Construction graph: FenceWood node: straight @@ -123,8 +125,10 @@ mask: - FullTileMask layer: + - Opaque - MidImpassable - LowImpassable + - BulletImpassable - type: Construction graph: FenceWood node: end @@ -159,8 +163,10 @@ mask: - TableMask layer: + - Opaque - MidImpassable - LowImpassable + - BulletImpassable - type: Construction graph: FenceWood node: corner @@ -195,8 +201,10 @@ mask: - TableMask layer: + - Opaque - MidImpassable - LowImpassable + - BulletImpassable - type: Construction graph: FenceWood node: tjunction @@ -221,8 +229,10 @@ mask: - FullTileMask layer: + - Opaque - MidImpassable - LowImpassable + - BulletImpassable - type: InteractionOutline - type: Door openSpriteState: door_opened @@ -271,6 +281,7 @@ layer: - MidImpassable - LowImpassable + - BulletImpassable - type: Construction graph: FenceWood node: straight_small @@ -298,6 +309,7 @@ layer: - MidImpassable - LowImpassable + - BulletImpassable - type: Construction graph: FenceWood node: end_small @@ -334,6 +346,7 @@ layer: - MidImpassable - LowImpassable + - BulletImpassable - type: Construction graph: FenceWood node: corner_small @@ -370,6 +383,7 @@ layer: - MidImpassable - LowImpassable + - BulletImpassable - type: Construction graph: FenceWood node: tjunction_small @@ -396,6 +410,7 @@ layer: - MidImpassable - LowImpassable + - BulletImpassable - type: InteractionOutline - type: Door openSpriteState: door_opened_small @@ -418,4 +433,4 @@ path: /Audio/Effects/door_close.ogg - type: Construction graph: FenceWood - node: gate_small \ No newline at end of file + node: gate_small diff --git a/Resources/Prototypes/Entities/Structures/hydro_tray.yml b/Resources/Prototypes/Entities/Structures/hydro_tray.yml index 8ea7172d8b..68a0cbd38e 100644 --- a/Resources/Prototypes/Entities/Structures/hydro_tray.yml +++ b/Resources/Prototypes/Entities/Structures/hydro_tray.yml @@ -1,6 +1,6 @@ - type: entity name: hydroponics tray - parent: [ hydroponicsSoil, ConstructibleMachine] + parent: [ hydroponicsSoil, SmallConstructibleMachine] id: hydroponicsTray description: An interstellar-grade space farmplot allowing for rapid growth and selective breeding of crops. Just... keep in mind the space weeds. components: @@ -14,6 +14,8 @@ hard: true mask: - MachineMask + layer: + - BulletImpassable - type: Anchorable - type: Pullable - type: Sprite diff --git a/Resources/Prototypes/Entities/categories.yml b/Resources/Prototypes/Entities/categories.yml new file mode 100644 index 0000000000..d75b8feedb --- /dev/null +++ b/Resources/Prototypes/Entities/categories.yml @@ -0,0 +1,9 @@ +- type: entityCategory + id: Actions + name: entity-category-name-actions + hideSpawnMenu: true + +- type: entityCategory + id: Objectives + name: entity-category-name-objectives + hideSpawnMenu: true diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index d0241aadd5..cf97d03c50 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -458,6 +458,7 @@ minimumPlayers: 30 # DeltaV - was 20 reoccurrenceDelay: 30 duration: 1 + - type: RuleGrids - type: LoadMapRule preloadedGrid: ShuttleStriker - type: NukeopsRule diff --git a/Resources/Prototypes/GameRules/midround.yml b/Resources/Prototypes/GameRules/midround.yml index fe5af117e6..606f5fda78 100644 --- a/Resources/Prototypes/GameRules/midround.yml +++ b/Resources/Prototypes/GameRules/midround.yml @@ -3,7 +3,6 @@ - type: entity id: Ninja parent: BaseGameRule - noSpawn: true components: - type: GenericAntagRule agentName: ninja-round-end-agent-name diff --git a/Resources/Prototypes/GameRules/roundstart.yml b/Resources/Prototypes/GameRules/roundstart.yml index d67038e6ad..6a9ceac682 100644 --- a/Resources/Prototypes/GameRules/roundstart.yml +++ b/Resources/Prototypes/GameRules/roundstart.yml @@ -5,6 +5,17 @@ components: - type: GameRule +- type: entity + noSpawn: true + parent: BaseGameRule + id: RespawnDeadRule + components: + - type: RespawnDeadRule + alwaysRespawnDead: true + - type: RespawnTracker + respawnDelay: 10 + deleteBody: false + - type: entity noSpawn: true parent: BaseGameRule @@ -73,6 +84,7 @@ - operationPrefix - operationSuffix - type: NukeopsRule + - type: RuleGrids - type: AntagSelection - type: AntagLoadProfileRule diff --git a/Resources/Prototypes/GameRules/unknown_shuttles.yml b/Resources/Prototypes/GameRules/unknown_shuttles.yml index 359d7bffc0..1ce365cd81 100644 --- a/Resources/Prototypes/GameRules/unknown_shuttles.yml +++ b/Resources/Prototypes/GameRules/unknown_shuttles.yml @@ -1,7 +1,7 @@ -- type: entity - id: UnknownShuttleCargoLost +- type: entity + abstract: true parent: BaseGameRule - noSpawn: true + id: BaseUnknownShuttleRule components: - type: StationEvent startAnnouncement: station-event-unknown-shuttle-incoming @@ -10,65 +10,44 @@ weight: 5 reoccurrenceDelay: 30 duration: 1 + - type: RuleGrids + - type: LoadMapRule + +- type: entity + parent: BaseUnknownShuttleRule + id: UnknownShuttleCargoLost + components: - type: LoadMapRule preloadedGrid: ShuttleCargoLost - type: entity + parent: BaseUnknownShuttleRule id: UnknownShuttleTravelingCuisine - parent: BaseGameRule - noSpawn: true components: - - type: StationEvent - startAnnouncement: station-event-unknown-shuttle-incoming - startAudio: - path: /Audio/Announcements/attention.ogg - weight: 5 - reoccurrenceDelay: 30 - duration: 1 - type: LoadMapRule preloadedGrid: TravelingCuisine - type: entity + parent: BaseUnknownShuttleRule id: UnknownShuttleDisasterEvacPod - parent: BaseGameRule - noSpawn: true components: - - type: StationEvent - startAnnouncement: station-event-unknown-shuttle-incoming - startAudio: - path: /Audio/Announcements/attention.ogg - weight: 5 - reoccurrenceDelay: 30 - duration: 1 - type: LoadMapRule preloadedGrid: DisasterEvacPod - type: entity + parent: BaseUnknownShuttleRule id: UnknownShuttleHonki - parent: BaseGameRule - noSpawn: true components: - type: StationEvent - startAnnouncement: station-event-unknown-shuttle-incoming - startAudio: - path: /Audio/Announcements/attention.ogg weight: 2 - reoccurrenceDelay: 30 - duration: 1 - type: LoadMapRule preloadedGrid: Honki - type: entity + parent: BaseUnknownShuttleRule id: UnknownShuttleSyndieEvacPod - parent: BaseGameRule - noSpawn: true components: - type: StationEvent - startAnnouncement: station-event-unknown-shuttle-incoming - startAudio: - path: /Audio/Announcements/attention.ogg weight: 2 - reoccurrenceDelay: 30 - duration: 1 - type: LoadMapRule preloadedGrid: SyndieEvacPod diff --git a/Resources/Prototypes/InventoryTemplates/borg.yml b/Resources/Prototypes/InventoryTemplates/borg.yml index a03be16708..d43519f61c 100644 --- a/Resources/Prototypes/InventoryTemplates/borg.yml +++ b/Resources/Prototypes/InventoryTemplates/borg.yml @@ -7,7 +7,7 @@ uiWindowPos: 1,0 strippingWindowPos: 0,0 displayName: Head - offset: 0, -0.09375 + offset: 0.015625, -0.09375 - type: inventoryTemplate id: borgShort @@ -18,7 +18,7 @@ uiWindowPos: 1,0 strippingWindowPos: 0,0 displayName: Head - offset: 0, -0.1875 + offset: 0.015625, -0.1875 - type: inventoryTemplate id: borgTall @@ -30,6 +30,7 @@ uiWindowPos: 0,0 strippingWindowPos: 0,0 displayName: Head + offset: 0.015625, 0 # taller than tall - type: inventoryTemplate diff --git a/Resources/Prototypes/Magic/event_spells.yml b/Resources/Prototypes/Magic/event_spells.yml index e59e1b2db8..01006b4ffe 100644 --- a/Resources/Prototypes/Magic/event_spells.yml +++ b/Resources/Prototypes/Magic/event_spells.yml @@ -2,7 +2,6 @@ id: ActionSummonGhosts name: Summon Ghosts description: Makes all current ghosts permanently invisible - noSpawn: true components: - type: InstantAction useDelay: 120 diff --git a/Resources/Prototypes/Magic/forcewall_spells.yml b/Resources/Prototypes/Magic/forcewall_spells.yml index d3d8fef760..f1865cf722 100644 --- a/Resources/Prototypes/Magic/forcewall_spells.yml +++ b/Resources/Prototypes/Magic/forcewall_spells.yml @@ -2,7 +2,6 @@ id: ActionForceWall name: Forcewall description: Creates a magical barrier. - noSpawn: true components: - type: InstantAction useDelay: 10 diff --git a/Resources/Prototypes/Magic/knock_spell.yml b/Resources/Prototypes/Magic/knock_spell.yml index e2c3dcfd4c..5ba456d3be 100644 --- a/Resources/Prototypes/Magic/knock_spell.yml +++ b/Resources/Prototypes/Magic/knock_spell.yml @@ -2,7 +2,6 @@ id: ActionKnock name: Knock description: This spell opens nearby doors. - noSpawn: true components: - type: InstantAction useDelay: 10 diff --git a/Resources/Prototypes/Magic/projectile_spells.yml b/Resources/Prototypes/Magic/projectile_spells.yml index b8db7557bb..71bbc096c5 100644 --- a/Resources/Prototypes/Magic/projectile_spells.yml +++ b/Resources/Prototypes/Magic/projectile_spells.yml @@ -2,7 +2,6 @@ id: ActionFireball name: Fireball description: Fires an explosive fireball towards the clicked location. - noSpawn: true components: - type: Magic - type: WorldTargetAction @@ -29,7 +28,6 @@ parent: ActionFireball name: Fireball II description: Fires a fireball, but faster! - noSpawn: true components: - type: WorldTargetAction useDelay: 10 @@ -52,7 +50,6 @@ parent: ActionFireball name: Fireball III description: The fastest fireball in the west! - noSpawn: true components: - type: WorldTargetAction useDelay: 8 diff --git a/Resources/Prototypes/Magic/rune_spells.yml b/Resources/Prototypes/Magic/rune_spells.yml index 42022f5785..7ba357e7c1 100644 --- a/Resources/Prototypes/Magic/rune_spells.yml +++ b/Resources/Prototypes/Magic/rune_spells.yml @@ -2,7 +2,6 @@ id: ActionFlashRune name: Flash Rune description: Summons a rune that flashes if used. - noSpawn: true components: - type: InstantAction useDelay: 10 @@ -17,7 +16,6 @@ id: ActionExplosionRune name: Explosion Rune description: Summons a rune that explodes if used. - noSpawn: true components: - type: InstantAction useDelay: 20 @@ -32,7 +30,6 @@ id: ActionIgniteRune name: Ignite Rune description: Summons a rune that ignites if used. - noSpawn: true components: - type: InstantAction useDelay: 15 @@ -47,7 +44,6 @@ id: ActionStunRune name: Stun Rune description: Summons a rune that stuns if used. - noSpawn: true components: - type: InstantAction useDelay: 10 diff --git a/Resources/Prototypes/Magic/smite_spells.yml b/Resources/Prototypes/Magic/smite_spells.yml index e629e56505..10f5bdd538 100644 --- a/Resources/Prototypes/Magic/smite_spells.yml +++ b/Resources/Prototypes/Magic/smite_spells.yml @@ -2,7 +2,6 @@ id: ActionSmite name: Smite description: Instantly gibs a target. - noSpawn: true components: - type: EntityTargetAction useDelay: 60 diff --git a/Resources/Prototypes/Magic/spawn_spells.yml b/Resources/Prototypes/Magic/spawn_spells.yml index 3f8148b83c..76674d5bfa 100644 --- a/Resources/Prototypes/Magic/spawn_spells.yml +++ b/Resources/Prototypes/Magic/spawn_spells.yml @@ -2,7 +2,6 @@ id: ActionSpawnMagicarpSpell name: Summon Magicarp description: This spell summons three Magi-Carp to your aid! May or may not turn on user. - noSpawn: true components: - type: WorldTargetAction useDelay: 10 diff --git a/Resources/Prototypes/Magic/staves.yml b/Resources/Prototypes/Magic/staves.yml index ef94a3910f..ccabb516fd 100644 --- a/Resources/Prototypes/Magic/staves.yml +++ b/Resources/Prototypes/Magic/staves.yml @@ -34,7 +34,6 @@ - type: entity id: ActionRgbLight - noSpawn: true components: - type: EntityTargetAction whitelist: { components: [ PointLight ] } diff --git a/Resources/Prototypes/Magic/teleport_spells.yml b/Resources/Prototypes/Magic/teleport_spells.yml index cc89cf8ee0..6f1ed9a6e4 100644 --- a/Resources/Prototypes/Magic/teleport_spells.yml +++ b/Resources/Prototypes/Magic/teleport_spells.yml @@ -2,7 +2,6 @@ id: ActionBlink name: Blink description: Teleport to the clicked location. - noSpawn: true components: - type: WorldTargetAction useDelay: 10 diff --git a/Resources/Prototypes/Magic/utility_spells.yml b/Resources/Prototypes/Magic/utility_spells.yml index dccdda3789..90bcdc7487 100644 --- a/Resources/Prototypes/Magic/utility_spells.yml +++ b/Resources/Prototypes/Magic/utility_spells.yml @@ -2,7 +2,6 @@ id: ActionChargeSpell name: Charge description: Adds a charge back to your wand - noSpawn: true components: - type: InstantAction useDelay: 30 diff --git a/Resources/Prototypes/Maps/arena.yml b/Resources/Prototypes/Maps/arena.yml index b459483330..183db67824 100644 --- a/Resources/Prototypes/Maps/arena.yml +++ b/Resources/Prototypes/Maps/arena.yml @@ -16,8 +16,6 @@ - type: StationEmergencyShuttle emergencyShuttlePath: /Maps/Shuttles/DeltaV/NTES_UCLB.yml - type: StationJobs - overflowJobs: - - Passenger availableJobs: #civilian Passenger: [ -1, -1 ] diff --git a/Resources/Prototypes/Maps/arenas.yml b/Resources/Prototypes/Maps/arenas.yml index 32f8543722..7ad7a16bc2 100644 --- a/Resources/Prototypes/Maps/arenas.yml +++ b/Resources/Prototypes/Maps/arenas.yml @@ -10,7 +10,5 @@ - type: StationNameSetup mapNameTemplate: "Meteor Arena" - type: StationJobs - overflowJobs: - - Passenger availableJobs: Passenger: [ -1, -1 ] diff --git a/Resources/Prototypes/Maps/asterisk.yml b/Resources/Prototypes/Maps/asterisk.yml index 029aa36b21..69b2e699c5 100644 --- a/Resources/Prototypes/Maps/asterisk.yml +++ b/Resources/Prototypes/Maps/asterisk.yml @@ -16,8 +16,6 @@ !type:NanotrasenNameGenerator prefixCreator: 'DV' - type: StationJobs - overflowJobs: - - Passenger availableJobs: Captain: [ 1, 1 ] #service diff --git a/Resources/Prototypes/Maps/debug.yml b/Resources/Prototypes/Maps/debug.yml index 2f475c1e57..8d4cc550a2 100644 --- a/Resources/Prototypes/Maps/debug.yml +++ b/Resources/Prototypes/Maps/debug.yml @@ -10,8 +10,6 @@ - type: StationNameSetup mapNameTemplate: "Empty" - type: StationJobs - overflowJobs: - - Passenger availableJobs: Passenger: [ -1, -1 ] @@ -27,8 +25,6 @@ - type: StationNameSetup mapNameTemplate: "Dev" - type: StationJobs - overflowJobs: - - Captain availableJobs: Captain: [ -1, -1 ] @@ -44,7 +40,5 @@ - type: StationNameSetup mapNameTemplate: "TEG" - type: StationJobs - overflowJobs: - - ChiefEngineer availableJobs: ChiefEngineer: [ -1, -1 ] diff --git a/Resources/Prototypes/Maps/edge.yml b/Resources/Prototypes/Maps/edge.yml index bf86528b17..fcf7b61653 100644 --- a/Resources/Prototypes/Maps/edge.yml +++ b/Resources/Prototypes/Maps/edge.yml @@ -16,8 +16,6 @@ !type:NanotrasenNameGenerator prefixCreator: 'DV' - type: StationJobs - overflowJobs: - - Passenger availableJobs: #service Captain: [ 1, 1 ] diff --git a/Resources/Prototypes/Maps/hammurabi.yml b/Resources/Prototypes/Maps/hammurabi.yml index 461a51917e..1083520784 100644 --- a/Resources/Prototypes/Maps/hammurabi.yml +++ b/Resources/Prototypes/Maps/hammurabi.yml @@ -15,8 +15,6 @@ - type: StationEmergencyShuttle emergencyShuttlePath: /Maps/Shuttles/DeltaV/NTES_Centipede.yml - type: StationJobs - overflowJobs: - - Passenger availableJobs: #civilian Passenger: [ -1, -1 ] diff --git a/Resources/Prototypes/Maps/hive.yml b/Resources/Prototypes/Maps/hive.yml index 88708f6470..1828da29d7 100644 --- a/Resources/Prototypes/Maps/hive.yml +++ b/Resources/Prototypes/Maps/hive.yml @@ -16,8 +16,6 @@ - type: StationEmergencyShuttle emergencyShuttlePath: /Maps/Shuttles/DeltaV/NTES_Seal.yml - type: StationJobs - overflowJobs: - - Passenger availableJobs: #civilian Passenger: [ -1, -1 ] diff --git a/Resources/Prototypes/Maps/lighthouse.yml b/Resources/Prototypes/Maps/lighthouse.yml index fa5e384354..11b320df11 100644 --- a/Resources/Prototypes/Maps/lighthouse.yml +++ b/Resources/Prototypes/Maps/lighthouse.yml @@ -16,8 +16,6 @@ !type:NanotrasenNameGenerator prefixCreator: '14' - type: StationJobs - overflowJobs: - - Passenger availableJobs: Captain: [ 1, 1 ] #service diff --git a/Resources/Prototypes/Maps/micro.yml b/Resources/Prototypes/Maps/micro.yml index 5fde76d29b..627cf0307e 100644 --- a/Resources/Prototypes/Maps/micro.yml +++ b/Resources/Prototypes/Maps/micro.yml @@ -14,8 +14,6 @@ !type:NanotrasenNameGenerator prefixCreator: 'DV' - type: StationJobs - overflowJobs: - - Passenger availableJobs: Captain: [ 1, 1 ] #service diff --git a/Resources/Prototypes/Maps/pebble.yml b/Resources/Prototypes/Maps/pebble.yml index a3fcd047b3..bd8a99452e 100644 --- a/Resources/Prototypes/Maps/pebble.yml +++ b/Resources/Prototypes/Maps/pebble.yml @@ -14,8 +14,6 @@ !type:NanotrasenNameGenerator prefixCreator: 'NY' - type: StationJobs - overflowJobs: - - Passenger availableJobs: #service Captain: [ 1, 1 ] diff --git a/Resources/Prototypes/Maps/shoukou.yml b/Resources/Prototypes/Maps/shoukou.yml index bf30537094..806b294e09 100644 --- a/Resources/Prototypes/Maps/shoukou.yml +++ b/Resources/Prototypes/Maps/shoukou.yml @@ -16,8 +16,6 @@ - type: StationEmergencyShuttle emergencyShuttlePath: /Maps/Shuttles/DeltaV/NTES_Delta.yml - type: StationJobs - overflowJobs: - - Passenger availableJobs: #Service Passenger: [ -1, -1 ] diff --git a/Resources/Prototypes/Maps/tortuga.yml b/Resources/Prototypes/Maps/tortuga.yml index 0fe8f07c01..5b8675f471 100644 --- a/Resources/Prototypes/Maps/tortuga.yml +++ b/Resources/Prototypes/Maps/tortuga.yml @@ -15,8 +15,6 @@ - type: StationEmergencyShuttle emergencyShuttlePath: /Maps/Shuttles/DeltaV/NTES_Seal.yml - type: StationJobs - overflowJobs: - - Passenger availableJobs: #civilian Passenger: [ -1, -1 ] diff --git a/Resources/Prototypes/Nyanotrasen/Actions/types.yml b/Resources/Prototypes/Nyanotrasen/Actions/types.yml index b089568f41..e4ef4fbdb6 100644 --- a/Resources/Prototypes/Nyanotrasen/Actions/types.yml +++ b/Resources/Prototypes/Nyanotrasen/Actions/types.yml @@ -2,7 +2,6 @@ id: ActionEatMouse name: action-name-eat-mouse description: action-description-eat-mouse - noSpawn: true components: - type: InstantAction icon: Nyanotrasen/Icons/verbiconfangs.png @@ -12,7 +11,6 @@ id: ActionHairball name: action-name-hairball description: action-description-hairball - noSpawn: true components: - type: InstantAction charges: 1 @@ -24,7 +22,6 @@ id: ActionDispel name: action-name-dispel description: action-description-dispel - noSpawn: true components: - type: EntityTargetAction icon: Nyanotrasen/Interface/VerbIcons/dispel.png @@ -39,7 +36,6 @@ id: ActionMassSleep name: action-name-mass-sleep description: action-description-mass-sleep - noSpawn: true components: - type: WorldTargetAction icon: Nyanotrasen/Interface/VerbIcons/mass_sleep.png @@ -53,7 +49,6 @@ id: ActionMindSwap name: action-name-mind-swap description: action-description-mind-swap - noSpawn: true components: - type: EntityTargetAction icon: Nyanotrasen/Interface/VerbIcons/mind_swap.png @@ -67,7 +62,6 @@ id: ActionMindSwapReturn name: action-name-mind-swap-return description: action-description-mind-swap-return - noSpawn: true components: - type: InstantAction icon: Nyanotrasen/Interface/VerbIcons/mind_swap_return.png @@ -79,7 +73,6 @@ id: ActionNoosphericZap name: action-name-noospheric-zap description: action-description-noospheric-zap - noSpawn: true components: - type: EntityTargetAction icon: Nyanotrasen/Interface/VerbIcons/noospheric_zap.png @@ -92,7 +85,6 @@ id: ActionPyrokinesis name: action-name-pyrokinesis description: action-description-pyrokinesis - noSpawn: true components: - type: EntityTargetAction icon: Nyanotrasen/Interface/VerbIcons/pyrokinesis.png @@ -106,7 +98,6 @@ id: ActionMetapsionic name: action-name-metapsionic description: action-description-metapsionic - noSpawn: true components: - type: InstantAction icon: Nyanotrasen/Interface/VerbIcons/metapsionic.png @@ -117,7 +108,6 @@ id: ActionPsionicRegeneration name: action-name-psionic-regeneration description: action-description-psionic-regeneration - noSpawn: true components: - type: InstantAction icon: Nyanotrasen/Interface/VerbIcons/psionic_regeneration.png @@ -128,7 +118,6 @@ id: ActionTelegnosis name: action-name-telegnosis description: action-description-telegnosis - noSpawn: true components: - type: InstantAction icon: Nyanotrasen/Interface/VerbIcons/telegnosis.png @@ -139,7 +128,6 @@ id: ActionPsionicInvisibility name: action-name-psionic-invisibility description: action-description-psionic-invisibility - noSpawn: true components: - type: InstantAction icon: Nyanotrasen/Interface/VerbIcons/psionic_invisibility.png @@ -150,7 +138,6 @@ id: ActionPsionicInvisibilityUsed name: action-name-psionic-invisibility-off description: action-description-psionic-invisibility-off - noSpawn: true components: - type: InstantAction icon: Nyanotrasen/Interface/VerbIcons/psionic_invisibility_off.png @@ -160,7 +147,6 @@ id: ActionFabricateLollipop name: action-name-fabricate-lollipop description: action-description-fabricate-lollipop - noSpawn: true components: - type: InstantAction icon: { sprite: Nyanotrasen/Objects/Consumable/Food/candy.rsi, state: lollipop } @@ -171,7 +157,6 @@ id: ActionFabricateGumball name: action-name-fabricate-gumball description: action-description-fabricate-gumball - noSpawn: true components: - type: InstantAction icon: { sprite: Nyanotrasen/Objects/Consumable/Food/candy.rsi, state: gumball } diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Consumable/Food/Baked/pizza.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Consumable/Food/Baked/pizza.yml index 8c96635b06..ca4225371c 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Consumable/Food/Baked/pizza.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Consumable/Food/Baked/pizza.yml @@ -191,7 +191,6 @@ - type: Tag tags: - Pizza - - Fruit - type: entity name: slice of pesto pizza diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Weapons/Melee/breaching_hammer.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Weapons/Melee/breaching_hammer.yml index d019cee136..41be733e59 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Weapons/Melee/breaching_hammer.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Weapons/Melee/breaching_hammer.yml @@ -33,7 +33,7 @@ - type: Tool qualities: - Prying - speed: 0.8 + speedModifier: 0.8 - type: Prying pryPowered: !type:Bool true diff --git a/Resources/Prototypes/Nyanotrasen/Objectives/traitor.yml b/Resources/Prototypes/Nyanotrasen/Objectives/traitor.yml index c78e6fab4b..eee0f05e36 100644 --- a/Resources/Prototypes/Nyanotrasen/Objectives/traitor.yml +++ b/Resources/Prototypes/Nyanotrasen/Objectives/traitor.yml @@ -1,5 +1,4 @@ - type: entity - noSpawn: true parent: BaseTraitorStealObjective id: MantisKnifeStealObjective components: @@ -15,7 +14,6 @@ # parent: BaseTraitorObjective # name: Become psionic # description: We need you to acquire psionics and keep them until your mission is complete. -# noSpawn: true # components: # - type: NotJobsRequirement # jobs: @@ -38,7 +36,6 @@ # parent: BaseTraitorObjective # name: objective-condition-become-golem-title # description: objective-condition-become-golem-description. -# noSpawn: true # components: # - type: NotJobRequirement # job: Chaplain @@ -57,7 +54,6 @@ - type: entity id: RaiseGlimmerObjective parent: BaseTraitorObjective - noSpawn: true name: Raise Glimmer. description: Get the glimmer above the specified amount. components: diff --git a/Resources/Prototypes/Nyanotrasen/Stacks/materials.yml b/Resources/Prototypes/Nyanotrasen/Stacks/materials.yml index e8c1b98b93..7113547101 100644 --- a/Resources/Prototypes/Nyanotrasen/Stacks/materials.yml +++ b/Resources/Prototypes/Nyanotrasen/Stacks/materials.yml @@ -4,4 +4,3 @@ icon: { sprite: Nyanotrasen/Objects/Materials/materials.rsi, state: bluespace } spawn: MaterialBluespace maxCount: 5 - itemSize: 1 diff --git a/Resources/Prototypes/Objectives/base_objectives.yml b/Resources/Prototypes/Objectives/base_objectives.yml index 2ab5149213..1fbd23dfce 100644 --- a/Resources/Prototypes/Objectives/base_objectives.yml +++ b/Resources/Prototypes/Objectives/base_objectives.yml @@ -1,6 +1,6 @@ # OBJECTIVE STYLE -# in comments anything that says final prototype means the objective that isnt abstract -# the final prototype must be noSpawn to avoid showing in f5 +# in comments anything that says final prototype means the objective that isnt abstract. +# you dont need noSpawn because Objectives category is automatically added, which has hideSpawnmenu # components are listed in this order: # 1. Objective # 2. requirement components @@ -8,7 +8,7 @@ # 4. the condition component # all objectives should inherit this at some point -# then have its difficulty etc fields set in the final objective prototypes +# then have its icon etc fields set in the final objective prototypes - type: entity abstract: true id: BaseObjective diff --git a/Resources/Prototypes/Objectives/dragon.yml b/Resources/Prototypes/Objectives/dragon.yml index 2cf7eb292f..10ca942cb3 100644 --- a/Resources/Prototypes/Objectives/dragon.yml +++ b/Resources/Prototypes/Objectives/dragon.yml @@ -13,7 +13,6 @@ - DragonRole - type: entity - noSpawn: true parent: BaseDragonObjective id: CarpRiftsObjective components: @@ -30,7 +29,6 @@ - type: CarpRiftsCondition - type: entity - noSpawn: true parent: [BaseDragonObjective, BaseSurviveObjective] id: DragonSurviveObjective name: Survive diff --git a/Resources/Prototypes/Objectives/ninja.yml b/Resources/Prototypes/Objectives/ninja.yml index 1576531a8b..05f71dea0d 100644 --- a/Resources/Prototypes/Objectives/ninja.yml +++ b/Resources/Prototypes/Objectives/ninja.yml @@ -13,7 +13,6 @@ - NinjaRole - type: entity - noSpawn: true parent: BaseNinjaObjective id: DoorjackObjective components: @@ -29,7 +28,6 @@ - type: DoorjackCondition - type: entity - noSpawn: true parent: BaseNinjaObjective id: StealResearchObjective description: Your gloves can be used to hack a research server and steal its precious data. If epistemics has been slacking you'll have to get to work. # DeltaV - Epistemics Department replacing Science @@ -45,7 +43,6 @@ - type: StealResearchCondition - type: entity - noSpawn: true parent: [BaseNinjaObjective, BaseCodeObjective] id: SpiderChargeObjective description: This bomb can be detonated in a specific location. Note that the bomb will not work anywhere else! @@ -56,7 +53,6 @@ state: icon - type: entity - noSpawn: true parent: [BaseNinjaObjective, BaseSurviveObjective] id: NinjaSurviveObjective name: Survive @@ -68,7 +64,6 @@ state: icon - type: entity - noSpawn: true parent: [BaseNinjaObjective, BaseCodeObjective] id: TerrorObjective name: Call in a threat @@ -80,7 +75,6 @@ state: red_phone - type: entity - noSpawn: true parent: [BaseNinjaObjective, BaseCodeObjective] id: MassArrestObjective name: Set everyone to wanted diff --git a/Resources/Prototypes/Objectives/thief.yml b/Resources/Prototypes/Objectives/thief.yml index 1815485097..8b5307e9a0 100644 --- a/Resources/Prototypes/Objectives/thief.yml +++ b/Resources/Prototypes/Objectives/thief.yml @@ -55,7 +55,6 @@ # Collections - type: entity - noSpawn: true parent: BaseThiefStealCollectionObjective id: FigurineStealCollectionObjective components: @@ -67,7 +66,6 @@ difficulty: 0.25 - type: entity - noSpawn: true parent: BaseThiefStealCollectionObjective id: HeadCloakStealCollectionObjective components: @@ -79,7 +77,6 @@ difficulty: 1.5 - type: entity - noSpawn: true parent: BaseThiefStealCollectionObjective id: HeadBedsheetStealCollectionObjective components: @@ -91,7 +88,6 @@ difficulty: 1.0 - type: entity - noSpawn: true parent: BaseThiefStealCollectionObjective id: StampStealCollectionObjective components: @@ -103,7 +99,6 @@ difficulty: 1.0 - type: entity - noSpawn: true parent: BaseThiefStealCollectionObjective id: DoorRemoteStealCollectionObjective components: @@ -115,7 +110,6 @@ difficulty: 1.5 - type: entity - noSpawn: true parent: BaseThiefStealCollectionObjective id: TechnologyDiskStealCollectionObjective components: @@ -130,7 +124,6 @@ difficulty: 0.8 - type: entity - noSpawn: true parent: BaseThiefStealCollectionObjective id: IDCardsStealCollectionObjective components: @@ -145,7 +138,6 @@ - type: entity - noSpawn: true parent: BaseThiefStealCollectionObjective id: LAMPStealCollectionObjective components: @@ -163,7 +155,6 @@ # steal item - type: entity #Security subgroup - noSpawn: true parent: BaseThiefStealObjective id: ForensicScannerStealObjective components: @@ -175,7 +166,6 @@ difficulty: 1 - type: entity - noSpawn: true parent: BaseThiefStealObjective id: FlippoEngravedLighterStealObjective components: @@ -187,7 +177,6 @@ difficulty: 0.8 - type: entity - noSpawn: true parent: BaseThiefStealObjective id: ClothingHeadHatWardenStealObjective components: @@ -197,7 +186,6 @@ difficulty: 1.2 - type: entity #Medical subgroup - noSpawn: true parent: BaseThiefStealObjective id: ClothingOuterHardsuitVoidParamedStealObjective components: @@ -209,7 +197,6 @@ difficulty: 1 - type: entity - noSpawn: true parent: BaseThiefStealObjective id: MedicalTechFabCircuitboardStealObjective components: @@ -221,7 +208,6 @@ difficulty: 1 - type: entity - noSpawn: true parent: BaseThiefStealObjective id: ClothingHeadsetAltMedicalStealObjective components: @@ -233,7 +219,6 @@ difficulty: 1 - type: entity #Engineering subgroup - noSpawn: true parent: BaseThiefStealObjective id: FireAxeStealObjective components: @@ -245,7 +230,6 @@ difficulty: 0.8 - type: entity - noSpawn: true parent: BaseThiefStealObjective id: AmePartFlatpackStealObjective components: @@ -257,7 +241,6 @@ difficulty: 1 - type: entity #Cargo subgroup - noSpawn: true parent: BaseThiefStealObjective id: ExpeditionsCircuitboardStealObjective components: @@ -269,7 +252,6 @@ difficulty: 0.7 - type: entity - noSpawn: true parent: BaseThiefStealObjective id: CargoShuttleCircuitboardStealObjective components: @@ -281,7 +263,6 @@ difficulty: 0.7 - type: entity - noSpawn: true parent: BaseThiefStealObjective id: SalvageShuttleCircuitboardStealObjective components: @@ -293,7 +274,6 @@ difficulty: 0.7 - type: entity #Service subgroup - noSpawn: true parent: BaseThiefStealObjective id: ClothingEyesHudBeerStealObjective components: @@ -305,7 +285,6 @@ difficulty: 0.3 - type: entity - noSpawn: true parent: BaseThiefStealObjective id: BibleStealObjective components: @@ -317,7 +296,6 @@ difficulty: 0.4 - type: entity #Other subgroup - noSpawn: true parent: BaseThiefStealObjective id: ClothingNeckGoldmedalStealObjective components: @@ -329,7 +307,6 @@ difficulty: 1 - type: entity - noSpawn: true parent: BaseThiefStealObjective id: ClothingNeckClownmedalStealObjective components: @@ -343,7 +320,6 @@ # Structures - type: entity - noSpawn: true parent: BaseThiefStealStructureObjective id: NuclearBombStealObjective components: @@ -355,7 +331,6 @@ difficulty: 2.5 #Good luck - type: entity - noSpawn: true parent: BaseThiefStealStructureObjective id: FaxMachineCaptainStealObjective components: @@ -367,7 +342,6 @@ difficulty: 2 - type: entity - noSpawn: true parent: BaseThiefStealStructureObjective id: ChemDispenserStealObjective components: @@ -379,7 +353,6 @@ difficulty: 1 - type: entity - noSpawn: true parent: BaseThiefStealStructureObjective id: XenoArtifactStealObjective components: @@ -391,7 +364,6 @@ difficulty: 0.5 - type: entity - noSpawn: true parent: BaseThiefStealStructureObjective id: FreezerHeaterStealObjective components: @@ -403,7 +375,6 @@ difficulty: 0.5 - type: entity - noSpawn: true parent: BaseThiefStealStructureObjective id: TegStealObjective components: @@ -415,7 +386,6 @@ difficulty: 1 - type: entity - noSpawn: true parent: BaseThiefStealStructureObjective id: BoozeDispenserStealObjective components: @@ -427,7 +397,6 @@ difficulty: 0.5 - type: entity - noSpawn: true parent: BaseThiefStealStructureObjective id: AltarNanotrasenStealObjective components: @@ -439,7 +408,6 @@ difficulty: 0.5 - type: entity - noSpawn: true parent: BaseThiefStealStructureObjective id: PlantRDStealObjective components: @@ -453,7 +421,6 @@ # Animal - type: entity - noSpawn: true parent: BaseThiefStealAnimalObjective id: IanStealObjective components: @@ -465,7 +432,6 @@ difficulty: 2.5 - type: entity - noSpawn: true parent: BaseThiefStealAnimalObjective id: BingusStealObjective components: @@ -475,7 +441,6 @@ difficulty: 1 - type: entity - noSpawn: true parent: BaseThiefStealAnimalObjective id: McGriffStealObjective components: @@ -487,7 +452,6 @@ difficulty: 1 - type: entity - noSpawn: true parent: BaseThiefStealAnimalObjective id: WalterStealObjective components: @@ -499,7 +463,6 @@ difficulty: 1 - type: entity - noSpawn: true parent: BaseThiefStealAnimalObjective id: MortyStealObjective components: @@ -509,7 +472,6 @@ difficulty: 0.5 - type: entity - noSpawn: true parent: BaseThiefStealAnimalObjective id: RenaultStealObjective components: @@ -521,7 +483,6 @@ difficulty: 2 - type: entity - noSpawn: true parent: BaseThiefStealAnimalObjective id: ShivaStealObjective components: @@ -533,7 +494,6 @@ difficulty: 2 - type: entity - noSpawn: true parent: BaseThiefStealAnimalObjective id: TropicoStealObjective components: @@ -547,7 +507,6 @@ # Escape - type: entity - noSpawn: true parent: [BaseThiefObjective, BaseLivingObjective] id: EscapeThiefShuttleObjective name: Escape to centcom alive and unrestrained. diff --git a/Resources/Prototypes/Objectives/traitor.yml b/Resources/Prototypes/Objectives/traitor.yml index 80fb2d5b9e..caa9e1593d 100644 --- a/Resources/Prototypes/Objectives/traitor.yml +++ b/Resources/Prototypes/Objectives/traitor.yml @@ -36,7 +36,6 @@ # state - type: entity - noSpawn: true parent: [BaseTraitorObjective, BaseLivingObjective] id: EscapeShuttleObjective name: Escape to centcom alive and unrestrained. @@ -50,7 +49,6 @@ - type: EscapeShuttleCondition ##- type: entity # DeltaV -# noSpawn: true # parent: BaseTraitorObjective # id: DieObjective # name: Die a glorious death @@ -69,7 +67,6 @@ # - type: DieCondition #- type: entity -# noSpawn: true # parent: [BaseTraitorObjective, BaseLivingObjective] # id: HijackShuttleObjective # name: Hijack emergency shuttle @@ -85,7 +82,6 @@ # kill - type: entity - noSpawn: true parent: [BaseTraitorObjective, BaseKillObjective] id: KillRandomPersonObjective description: Do it however you like, just make sure they don't make it to centcom. @@ -98,7 +94,6 @@ - type: PickRandomPerson - type: entity - noSpawn: true parent: [BaseTraitorObjective, BaseKillObjective] id: KillRandomHeadObjective description: We need this head gone and you probably know why. Good luck, agent. @@ -119,7 +114,6 @@ # social - type: entity - noSpawn: true parent: [BaseTraitorSocialObjective, BaseKeepAliveObjective] id: RandomTraitorAliveObjective description: Identify yourself at your own risk. We just need them alive. @@ -131,7 +125,6 @@ - type: RandomTraitorAlive - type: entity - noSpawn: true parent: [BaseTraitorSocialObjective, BaseHelpProgressObjective] id: RandomTraitorProgressObjective description: Identify yourself at your own risk. We just need them to succeed. @@ -157,7 +150,6 @@ owner: job-name-cmo - type: entity - noSpawn: true parent: BaseCMOStealObjective id: CMOHyposprayStealObjective components: @@ -165,7 +157,6 @@ stealGroup: Hypospray - type: entity - noSpawn: true parent: BaseCMOStealObjective id: CMOCrewMonitorStealObjective components: @@ -185,7 +176,6 @@ owner: job-name-rd - type: entity - noSpawn: true parent: BaseRDStealObjective id: RDHardsuitStealObjective components: @@ -196,7 +186,6 @@ difficulty: 3 - type: entity - noSpawn: true parent: BaseRDStealObjective id: HandTeleporterStealObjective components: @@ -206,7 +195,6 @@ ## hos - type: entity - noSpawn: true parent: BaseTraitorStealObjective id: SecretDocumentsStealObjective components: @@ -222,7 +210,6 @@ ## ce - type: entity - noSpawn: true parent: BaseTraitorStealObjective id: MagbootsStealObjective components: @@ -235,7 +222,6 @@ ## qm - type: entity - noSpawn: true parent: BaseTraitorStealObjective id: ClipboardStealObjective components: @@ -248,7 +234,6 @@ ## hop - type: entity - noSpawn: true parent: BaseTraitorStealObjective id: CorgiMeatStealObjective components: @@ -274,7 +259,6 @@ job: Captain - type: entity - noSpawn: true parent: BaseCaptainObjective id: CaptainIDStealObjective components: @@ -282,7 +266,6 @@ stealGroup: CaptainIDCard - type: entity - noSpawn: true parent: BaseCaptainObjective id: CaptainJetpackStealObjective components: @@ -290,7 +273,6 @@ stealGroup: JetpackCaptainFilled - type: entity - noSpawn: true parent: BaseCaptainObjective id: CaptainGunStealObjective components: @@ -299,7 +281,6 @@ owner: job-name-captain - type: entity - noSpawn: true parent: BaseCaptainObjective id: NukeDiskStealObjective components: diff --git a/Resources/Prototypes/Reagents/fun.yml b/Resources/Prototypes/Reagents/fun.yml index befa8d8296..1df2636c8c 100644 --- a/Resources/Prototypes/Reagents/fun.yml +++ b/Resources/Prototypes/Reagents/fun.yml @@ -333,6 +333,7 @@ - !type:Emote emote: Weh showInChat: true + force: true probability: 0.5 - !type:Polymorph prototype: ArtifactLizard # Does the same thing as the original YML I made for this reagent. diff --git a/Resources/Prototypes/Reagents/toxins.yml b/Resources/Prototypes/Reagents/toxins.yml index 5d29f024ce..fedb0b405f 100644 --- a/Resources/Prototypes/Reagents/toxins.yml +++ b/Resources/Prototypes/Reagents/toxins.yml @@ -565,6 +565,7 @@ - !type:Emote emote: Honk showInChat: true + force: true probability: 0.2 - !type:HealthChange conditions: diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/janitor.yml b/Resources/Prototypes/Roles/Jobs/Civilian/janitor.yml index b768bd25be..83fd9ae5d2 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/janitor.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/janitor.yml @@ -22,6 +22,9 @@ id: JanitorPDA ears: ClothingHeadsetService belt: ClothingBeltJanitorFilled + storage: + back: + - BoxSurvival - type: startingGear id: JanitorMaidGear diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/mime.yml b/Resources/Prototypes/Roles/Jobs/Civilian/mime.yml index 94f502eafa..252b43460c 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/mime.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/mime.yml @@ -35,7 +35,6 @@ id: ActionMimeInvisibleWall name: Create Invisible Wall description: Create an invisible wall in front of you, if placeable there. - noSpawn: true components: - type: InstantAction priority: -1 diff --git a/Resources/Prototypes/Roles/requirement_overrides.yml b/Resources/Prototypes/Roles/requirement_overrides.yml new file mode 100644 index 0000000000..62041f42d7 --- /dev/null +++ b/Resources/Prototypes/Roles/requirement_overrides.yml @@ -0,0 +1,16 @@ +- type: jobRequirementOverride + id: Reduced + jobs: + Captain: + - !type:DepartmentTimeRequirement + department: Engineering + time: 3600 # 1 hours + - !type:DepartmentTimeRequirement + department: Medical + time: 3600 # 1 hours + - !type:DepartmentTimeRequirement + department: Security + time: 3600 # 1 hours + - !type:DepartmentTimeRequirement + department: Command + time: 3600 # 1 hour diff --git a/Resources/Prototypes/SoundCollections/deathgasp.yml b/Resources/Prototypes/SoundCollections/deathgasp.yml index 28c33c1fb4..77d198ca70 100644 --- a/Resources/Prototypes/SoundCollections/deathgasp.yml +++ b/Resources/Prototypes/SoundCollections/deathgasp.yml @@ -21,3 +21,8 @@ files: - /Audio/Effects/Gasp/deathgasp_1.ogg - /Audio/Effects/Gasp/deathgasp_2.ogg + +- type: soundCollection + id: MothDeathGasp + files: + - /Audio/Effects/Gasp/moth_DeathGasp.ogg diff --git a/Resources/Prototypes/Stacks/Materials/Sheets/glass.yml b/Resources/Prototypes/Stacks/Materials/Sheets/glass.yml index 0caffb301f..cd6aed7cdf 100644 --- a/Resources/Prototypes/Stacks/Materials/Sheets/glass.yml +++ b/Resources/Prototypes/Stacks/Materials/Sheets/glass.yml @@ -4,7 +4,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/glass.rsi, state: glass } spawn: SheetGlass1 maxCount: 30 - itemSize: 1 - type: stack id: ReinforcedGlass @@ -12,7 +11,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/glass.rsi, state: rglass } spawn: SheetRGlass1 maxCount: 30 - itemSize: 1 - type: stack id: PlasmaGlass @@ -20,7 +18,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/glass.rsi, state: pglass } spawn: SheetPGlass1 maxCount: 30 - itemSize: 1 - type: stack id: ReinforcedPlasmaGlass @@ -28,7 +25,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/glass.rsi, state: rpglass } spawn: SheetRPGlass1 maxCount: 30 - itemSize: 1 - type: stack id: UraniumGlass @@ -36,7 +32,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/glass.rsi, state: uglass } spawn: SheetUGlass1 maxCount: 30 - itemSize: 1 - type: stack id: ReinforcedUraniumGlass @@ -44,7 +39,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/glass.rsi, state: ruglass } spawn: SheetRUGlass1 maxCount: 30 - itemSize: 1 - type: stack id: ClockworkGlass @@ -52,4 +46,3 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/glass.rsi, state: cglass } spawn: SheetClockworkGlass1 maxCount: 30 - itemSize: 1 diff --git a/Resources/Prototypes/Stacks/Materials/Sheets/metal.yml b/Resources/Prototypes/Stacks/Materials/Sheets/metal.yml index 77f750c205..7520130b9b 100644 --- a/Resources/Prototypes/Stacks/Materials/Sheets/metal.yml +++ b/Resources/Prototypes/Stacks/Materials/Sheets/metal.yml @@ -4,7 +4,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/metal.rsi, state: steel } spawn: SheetSteel1 maxCount: 30 - itemSize: 1 - type: stack id: Plasteel @@ -12,7 +11,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/metal.rsi, state: plasteel } spawn: SheetPlasteel1 maxCount: 30 - itemSize: 1 - type: stack id: Brass @@ -20,4 +18,3 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/metal.rsi, state: brass } spawn: SheetBrass1 maxCount: 30 - itemSize: 1 diff --git a/Resources/Prototypes/Stacks/Materials/Sheets/other.yml b/Resources/Prototypes/Stacks/Materials/Sheets/other.yml index 96f22ae656..565b1fc1ae 100644 --- a/Resources/Prototypes/Stacks/Materials/Sheets/other.yml +++ b/Resources/Prototypes/Stacks/Materials/Sheets/other.yml @@ -4,7 +4,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/other.rsi, state: paper } spawn: SheetPaper1 maxCount: 30 - itemSize: 1 - type: stack id: Plasma @@ -12,7 +11,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/other.rsi, state: plasma } spawn: SheetPlasma1 maxCount: 30 - itemSize: 1 - type: stack id: Plastic @@ -20,7 +18,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/other.rsi, state: plastic } spawn: SheetPlastic1 maxCount: 30 - itemSize: 1 - type: stack id: Uranium @@ -28,4 +25,3 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/other.rsi, state: uranium } spawn: SheetUranium1 maxCount: 30 - itemSize: 1 diff --git a/Resources/Prototypes/Stacks/Materials/crystals.yml b/Resources/Prototypes/Stacks/Materials/crystals.yml index 274f9c10ea..28f4ed70ca 100644 --- a/Resources/Prototypes/Stacks/Materials/crystals.yml +++ b/Resources/Prototypes/Stacks/Materials/crystals.yml @@ -3,4 +3,3 @@ name: telecrystal icon: Objects/Specific/Syndicate/telecrystal.rsi spawn: Telecrystal1 - itemSize: 1 diff --git a/Resources/Prototypes/Stacks/Materials/ingots.yml b/Resources/Prototypes/Stacks/Materials/ingots.yml index 956523c92b..1fd67a096d 100644 --- a/Resources/Prototypes/Stacks/Materials/ingots.yml +++ b/Resources/Prototypes/Stacks/Materials/ingots.yml @@ -4,7 +4,6 @@ icon: { sprite: "/Textures/Objects/Materials/ingots.rsi", state: gold } spawn: IngotGold1 maxCount: 30 - itemSize: 1 - type: stack id: Silver @@ -12,4 +11,3 @@ icon: { sprite: "/Textures/Objects/Materials/ingots.rsi", state: silver } spawn: IngotSilver1 maxCount: 30 - itemSize: 1 diff --git a/Resources/Prototypes/Stacks/Materials/materials.yml b/Resources/Prototypes/Stacks/Materials/materials.yml index cc963dde59..1157dc3f00 100644 --- a/Resources/Prototypes/Stacks/Materials/materials.yml +++ b/Resources/Prototypes/Stacks/Materials/materials.yml @@ -4,7 +4,6 @@ icon: { sprite: /Textures/Objects/Misc/monkeycube.rsi, state: cube } spawn: MaterialBiomass1 maxCount: 100 - itemSize: 1 - type: stack id: WoodPlank @@ -12,7 +11,6 @@ icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: wood } spawn: MaterialWoodPlank1 maxCount: 30 - itemSize: 1 - type: stack id: Cardboard @@ -20,7 +18,6 @@ icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: cardboard } spawn: MaterialCardboard1 maxCount: 30 - itemSize: 1 - type: stack id: Cloth @@ -28,7 +25,6 @@ icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: cloth } spawn: MaterialCloth1 maxCount: 30 - itemSize: 1 - type: stack id: Durathread @@ -36,7 +32,6 @@ icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: durathread } spawn: MaterialDurathread1 maxCount: 30 - itemSize: 1 - type: stack id: Diamond @@ -44,7 +39,6 @@ icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: diamond } spawn: MaterialDiamond1 maxCount: 30 - itemSize: 2 - type: stack id: Cotton @@ -52,7 +46,6 @@ icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: cotton } spawn: MaterialCotton1 maxCount: 30 - itemSize: 1 - type: stack id: Pyrotton @@ -60,7 +53,6 @@ icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: pyrotton } spawn: MaterialPyrotton1 maxCount: 30 - itemSize: 1 - type: stack id: Bananium @@ -68,7 +60,6 @@ icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: bananium } spawn: MaterialBananium1 maxCount: 10 - itemSize: 2 - type: stack id: MeatSheets @@ -76,7 +67,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/meaterial.rsi, state: meat } spawn: MaterialSheetMeat1 maxCount: 30 - itemSize: 1 - type: stack id: WebSilk @@ -84,7 +74,6 @@ icon: { sprite: /Textures/Objects/Materials/silk.rsi, state: icon } spawn: MaterialWebSilk1 maxCount: 50 - itemSize: 1 - type: stack id: Bones @@ -92,7 +81,6 @@ icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: bones} spawn: MaterialBones1 maxCount: 30 - itemSize: 1 - type: stack id: Gunpowder @@ -100,4 +88,3 @@ icon: { sprite: /Textures/Objects/Misc/reagent_fillings.rsi, state: powderpile } spawn: MaterialGunpowder maxCount: 60 - itemSize: 1 diff --git a/Resources/Prototypes/Stacks/Materials/ore.yml b/Resources/Prototypes/Stacks/Materials/ore.yml index 2a95393c27..51254b5a7a 100644 --- a/Resources/Prototypes/Stacks/Materials/ore.yml +++ b/Resources/Prototypes/Stacks/Materials/ore.yml @@ -4,7 +4,6 @@ icon: { sprite: /Textures/Objects/Materials/ore.rsi, state: gold } spawn: GoldOre1 maxCount: 30 - itemSize: 2 - type: stack id: SteelOre @@ -12,7 +11,6 @@ icon: { sprite: /Textures/Objects/Materials/ore.rsi, state: iron } spawn: SteelOre1 maxCount: 30 - itemSize: 2 - type: stack id: PlasmaOre @@ -20,7 +18,6 @@ icon: { sprite: /Textures/Objects/Materials/ore.rsi, state: plasma } spawn: PlasmaOre1 maxCount: 30 - itemSize: 2 - type: stack id: SilverOre @@ -28,7 +25,6 @@ icon: { sprite: /Textures/Objects/Materials/ore.rsi, state: silver } spawn: SilverOre1 maxCount: 30 - itemSize: 2 - type: stack id: SpaceQuartz @@ -36,7 +32,6 @@ icon: { sprite: /Textures/Objects/Materials/ore.rsi, state: spacequartz } spawn: SpaceQuartz1 maxCount: 30 - itemSize: 2 - type: stack id: UraniumOre @@ -44,7 +39,6 @@ icon: { sprite: /Textures/Objects/Materials/ore.rsi, state: uranium } spawn: UraniumOre1 maxCount: 30 - itemSize: 2 - type: stack @@ -53,7 +47,6 @@ icon: { sprite: /Textures/Objects/Materials/ore.rsi, state: bananium } spawn: BananiumOre1 maxCount: 30 - itemSize: 2 - type: stack id: Coal @@ -61,7 +54,6 @@ icon: { sprite: /Textures/Objects/Materials/ore.rsi, state: coal } spawn: Coal1 maxCount: 30 - itemSize: 2 - type: stack id: SaltOre @@ -69,4 +61,3 @@ icon: { sprite: /Textures/Objects/Materials/ore.rsi, state: salt } spawn: Salt1 maxCount: 30 - itemSize: 2 diff --git a/Resources/Prototypes/Stacks/Materials/parts.yml b/Resources/Prototypes/Stacks/Materials/parts.yml index 947bbb1bf2..50ceb28757 100644 --- a/Resources/Prototypes/Stacks/Materials/parts.yml +++ b/Resources/Prototypes/Stacks/Materials/parts.yml @@ -4,4 +4,3 @@ icon: { sprite: /Textures/Objects/Materials/parts.rsi, state: rods } spawn: PartRodMetal1 maxCount: 30 - itemSize: 1 diff --git a/Resources/Prototypes/Stacks/consumable_stacks.yml b/Resources/Prototypes/Stacks/consumable_stacks.yml index 2936772f08..e7feab7b52 100644 --- a/Resources/Prototypes/Stacks/consumable_stacks.yml +++ b/Resources/Prototypes/Stacks/consumable_stacks.yml @@ -5,7 +5,6 @@ name: pancake spawn: FoodBakedPancake maxCount: 3 - itemSize: 1 # Food Containers @@ -15,7 +14,6 @@ icon: { sprite: Objects/Consumable/Food/Baked/pizza.rsi, state: box } spawn: FoodBoxPizza maxCount: 30 - itemSize: 10 # Smokeables @@ -25,7 +23,6 @@ icon: { sprite: /Textures/Objects/Consumable/Smokeables/Cigarettes/paper.rsi, state: cigpaper } spawn: PaperRolling maxCount: 5 - itemSize: 1 - type: stack id: CigaretteFilter @@ -33,7 +30,6 @@ icon: { sprite: /Textures/Objects/Consumable/Smokeables/Cigarettes/paper.rsi, state: cigfilter } spawn: CigaretteFilter maxCount: 5 - itemSize: 2 - type: stack id: GroundTobacco @@ -41,7 +37,6 @@ icon: { sprite: /Textures/Objects/Misc/reagent_fillings.rsi, state: powderpile } spawn: GroundTobacco maxCount: 5 - itemSize: 1 - type: stack id: GroundCannabis @@ -49,7 +44,6 @@ icon: { sprite: /Textures/Objects/Misc/reagent_fillings.rsi, state: powderpile } spawn: GroundCannabis maxCount: - itemSize: 1 - type: stack id: GroundCannabisRainbow @@ -57,7 +51,6 @@ icon: { sprite: /Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi, state: powderpile_rainbow } spawn: GroundCannabisRainbow maxCount: - itemSize: 1 - type: stack id: LeavesTobaccoDried @@ -65,7 +58,6 @@ icon: { sprite: /Textures/Objects/Specific/Hydroponics/tobacco.rsi, state: dried } spawn: LeavesTobaccoDried maxCount: 5 - itemSize: 5 - type: stack id: LeavesCannabisDried @@ -73,12 +65,9 @@ icon: { sprite: /Textures/Objects/Specific/Hydroponics/tobacco.rsi, state: dried } spawn: LeavesCannabisDried maxCount: 5 - itemSize: 5 - type: stack id: LeavesCannabisRainbowDried name: dried rainbow cannabis leaves icon: { sprite: /Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi, state: dried } spawn: LeavesCannabisRainbowDried - maxCount: 5 - itemSize: 5 diff --git a/Resources/Prototypes/Stacks/engineering_stacks.yml b/Resources/Prototypes/Stacks/engineering_stacks.yml index 77cc620402..b4550015dc 100644 --- a/Resources/Prototypes/Stacks/engineering_stacks.yml +++ b/Resources/Prototypes/Stacks/engineering_stacks.yml @@ -4,11 +4,9 @@ name: inflatable wall spawn: InflatableWallStack1 maxCount: 10 - itemSize: 1 - type: stack id: InflatableDoor name: inflatable door spawn: InflatableDoorStack1 maxCount: 4 - itemSize: 1 diff --git a/Resources/Prototypes/Stacks/floor_tile_stacks.yml b/Resources/Prototypes/Stacks/floor_tile_stacks.yml index c5e37013b8..c88786f0dc 100644 --- a/Resources/Prototypes/Stacks/floor_tile_stacks.yml +++ b/Resources/Prototypes/Stacks/floor_tile_stacks.yml @@ -3,469 +3,402 @@ name: steel tile spawn: FloorTileItemSteel maxCount: 30 - itemSize: 5 - type: stack id: FloorTileMetalDiamond name: steel tile spawn: FloorTileItemMetalDiamond maxCount: 30 - itemSize: 5 - type: stack id: FloorTileWood name: wood floor spawn: FloorTileItemWood maxCount: 30 - itemSize: 5 - type: stack id: FloorTileWhite name: white tile spawn: FloorTileItemWhite maxCount: 30 - itemSize: 5 - type: stack id: FloorTileDark name: dark tile spawn: FloorTileItemDark maxCount: 30 - itemSize: 5 - type: stack id: FloorTileTechmaint name: techmaint floor spawn: FloorTileItemTechmaint maxCount: 30 - itemSize: 5 - type: stack id: FloorTileFreezer name: freezer tile spawn: FloorTileItemFreezer maxCount: 30 - itemSize: 5 - type: stack id: FloorTileShowroom name: showroom tile spawn: FloorTileItemShowroom maxCount: 30 - itemSize: 5 - type: stack id: FloorTileGCircuit name: green-circuit floor spawn: FloorTileItemGCircuit maxCount: 30 - itemSize: 5 - type: stack id: FloorTileGold name: gold floor spawn: FloorTileItemGold maxCount: 30 - itemSize: 5 - type: stack id: FloorTileReinforced name: reinforced tile spawn: FloorTileItemReinforced maxCount: 30 - itemSize: 5 - type: stack id: FloorTileMono name: mono tile spawn: FloorTileItemMono maxCount: 30 - itemSize: 5 - type: stack id: FloorTileBrassFilled name: filled brass plate spawn: FloorTileItemBrassFilled maxCount: 30 - itemSize: 5 - + - type: stack id: FloorTileBrassReebe name: smooth brass plate spawn: FloorTileItemBrassReebe maxCount: 30 - itemSize: 5 - type: stack id: FloorTileLino name: linoleum floor spawn: FloorTileItemLino maxCount: 30 - itemSize: 5 - type: stack id: FloorTileHydro name: hydro tile spawn: FloorTileItemHydro maxCount: 30 - itemSize: 5 - type: stack id: FloorTileLime name: lime tile spawn: FloorTileItemLime maxCount: 30 - itemSize: 5 - type: stack id: FloorTileDirty name: dirty tile spawn: FloorTileItemDirty maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackShuttleWhite name: white shuttle tile spawn: FloorTileItemShuttleWhite maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackShuttleBlue name: blue shuttle tile spawn: FloorTileItemShuttleBlue maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackShuttleOrange name: orange shuttle tile spawn: FloorTileItemShuttleOrange maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackShuttlePurple name: purple shuttle tile spawn: FloorTileItemShuttlePurple maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackShuttleRed name: red shuttle tile spawn: FloorTileItemShuttleRed maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackShuttleGrey name: grey shuttle tile spawn: FloorTileItemShuttleGrey maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackShuttleBlack name: black shuttle tile spawn: FloorTileItemShuttleBlack maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackEighties name: eighties floor tile spawn: FloorTileItemEighties maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackArcadeBlue name: blue arcade tile spawn: FloorTileItemArcadeBlue maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackArcadeBlue2 name: blue arcade tile spawn: FloorTileItemArcadeBlue2 maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackArcadeRed name: red arcade tile spawn: FloorTileItemArcadeRed maxCount: 30 - itemSize: 5 - type: stack id: FloorCarpetRed name: red carpet tile spawn: FloorCarpetItemRed maxCount: 30 - itemSize: 5 - type: stack id: FloorCarpetBlack name: block carpet tile spawn: FloorCarpetItemBlack maxCount: 30 - itemSize: 5 - type: stack id: FloorCarpetBlue name: blue carpet tile spawn: FloorCarpetItemBlue maxCount: 30 - itemSize: 5 - type: stack id: FloorCarpetGreen name: green carpet tile spawn: FloorCarpetItemGreen maxCount: 30 - itemSize: 5 - type: stack id: FloorCarpetOrange name: orange carpet tile spawn: FloorCarpetItemOrange maxCount: 30 - itemSize: 5 - type: stack id: FloorCarpetSkyBlue name: skyblue carpet tile spawn: FloorCarpetItemSkyBlue maxCount: 30 - itemSize: 5 - type: stack id: FloorCarpetPurple name: purple carpet tile spawn: FloorCarpetItemPurple maxCount: 30 - itemSize: 5 - type: stack id: FloorCarpetPink name: pink carpet tile spawn: FloorCarpetItemPink maxCount: 30 - itemSize: 5 - type: stack id: FloorCarpetCyan name: cyan carpet tile spawn: FloorCarpetItemCyan maxCount: 30 - itemSize: 5 - type: stack id: FloorCarpetWhite name: white carpet tile spawn: FloorCarpetItemWhite maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackCarpetClown name: clown carpet tile spawn: FloorTileItemCarpetClown maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackCarpetOffice name: office carpet tile spawn: FloorTileItemCarpetOffice maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackBoxing name: boxing ring tile spawn: FloorTileItemBoxing maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackGym name: gym floor tile spawn: FloorTileItemGym maxCount: 30 - itemSize: 5 - type: stack id: FloorTileElevatorShaft name: elevator shaft tile spawn: FloorTileItemElevatorShaft maxCount: 30 - itemSize: 5 - type: stack id: FloorTileRockVault name: rock vault tile spawn: FloorTileItemRockVault maxCount: 30 - itemSize: 5 - type: stack id: FloorTileBlue name: blue floor tile spawn: FloorTileItemBlue maxCount: 30 - itemSize: 5 - type: stack id: FloorTileMining name: mining floor tile spawn: FloorTileItemMining maxCount: 30 - itemSize: 5 - type: stack id: FloorTileMiningDark name: dark mining floor tile spawn: FloorTileItemMiningDark maxCount: 30 - itemSize: 5 - type: stack id: FloorTileMiningLight name: light mining floor tile spawn: FloorTileItemMiningLight maxCount: 30 - itemSize: 5 - type: stack id: FloorTileBar name: item bar floor tile spawn: FloorTileItemBar maxCount: 30 - itemSize: 5 - type: stack id: FloorTileClown name: clown floor tile spawn: FloorTileItemClown maxCount: 30 - itemSize: 5 - type: stack id: FloorTileMime name: mime floor tile spawn: FloorTileItemMime maxCount: 30 - itemSize: 5 - type: stack id: FloorTileKitchen name: kitchen floor tile spawn: FloorTileItemKitchen maxCount: 30 - itemSize: 5 - type: stack id: FloorTileLaundry name: laundry floor tile spawn: FloorTileItemLaundry maxCount: 30 - itemSize: 5 - type: stack id: FloorTileConcrete name: concrete tile spawn: FloorTileItemGrayConcrete maxCount: 30 - itemSize: 5 - type: stack id: FloorTileGrayConcrete name: gray concrete tile spawn: FloorTileItemLaundry maxCount: 30 - itemSize: 5 - type: stack id: FloorTileOldConcrete name: old concrete tile spawn: FloorTileItemOldConcrete maxCount: 30 - itemSize: 5 - type: stack id: FloorTileSilver name: silver floor tile spawn: FloorTileItemSilver maxCount: 30 - itemSize: 5 - type: stack id: FloorTileBCircuit name: bcircuit floor tile spawn: FloorTileItemBCircuit maxCount: 30 - itemSize: 5 - type: stack id: FloorTileGrass name: grass floor tile spawn: FloorTileItemGrass maxCount: 30 - itemSize: 5 - type: stack id: FloorTileGrassJungle name: grass jungle floor tile spawn: FloorTileItemGrassJungle maxCount: 30 - itemSize: 5 - type: stack id: FloorTileSnow name: snow floor tile spawn: FloorTileItemSnow maxCount: 30 - itemSize: 5 - type: stack id: FloorTileWoodPattern name: wood pattern floor spawn: FloorTileItemWoodPattern maxCount: 30 - itemSize: 5 - type: stack id: FloorTileFlesh name: flesh floor spawn: FloorTileItemFlesh maxCount: 30 - itemSize: 5 - type: stack id: FloorTileSteelMaint name: steel maint floor spawn: FloorTileItemSteelMaint maxCount: 30 - itemSize: 5 - type: stack id: FloorTileGratingMaint name: grating maint floor spawn: FloorTileItemGratingMaint maxCount: 30 - itemSize: 5 - type: stack id: FloorTileWeb name: web tile spawn: FloorTileItemWeb maxCount: 30 - itemSize: 5 # Faux science tiles - type: stack @@ -473,38 +406,33 @@ name: astro-grass floor spawn: FloorTileItemAstroGrass maxCount: 30 - itemSize: 5 - type: stack id: FloorTileMowedAstroGrass name: mowed astro-grass floor spawn: FloorTileItemMowedAstroGrass maxCount: 30 - itemSize: 5 - type: stack id: FloorTileJungleAstroGrass name: jungle astro-grass floor spawn: FloorTileItemJungleAstroGrass maxCount: 30 - itemSize: 5 - type: stack id: FloorTileAstroIce name: astro-ice floor spawn: FloorTileItemAstroIce maxCount: 30 - itemSize: 5 - type: stack id: FloorTileAstroSnow name: astro-snow floor spawn: FloorTileItemAstroSnow maxCount: 30 - itemSize: 5 - type: stack id: FloorTileWoodLarge name: large wood floor spawn: FloorTileItemWoodLarge - maxCount: 30 \ No newline at end of file + maxCount: 30 diff --git a/Resources/Prototypes/Stacks/medical_stacks.yml b/Resources/Prototypes/Stacks/medical_stacks.yml index 9d2b77ec93..7ad3c21634 100644 --- a/Resources/Prototypes/Stacks/medical_stacks.yml +++ b/Resources/Prototypes/Stacks/medical_stacks.yml @@ -4,7 +4,6 @@ icon: { sprite: "/Textures/Objects/Specific/Medical/medical.rsi", state: ointment } spawn: Ointment maxCount: 10 - itemSize: 1 - type: stack id: AloeCream @@ -12,7 +11,6 @@ icon: { sprite: "/Textures/Objects/Specific/Hydroponics/aloe.rsi", state: cream } spawn: AloeCream maxCount: 10 - itemSize: 1 - type: stack id: Gauze @@ -20,7 +18,6 @@ icon: { sprite: "/Textures/Objects/Specific/Medical/medical.rsi", state: gauze } spawn: Gauze maxCount: 10 - itemSize: 1 - type: stack id: Brutepack @@ -28,7 +25,6 @@ icon: { sprite: "/Textures/Objects/Specific/Medical/medical.rsi", state: gauze } spawn: Brutepack maxCount: 10 - itemSize: 1 - type: stack id: Bloodpack @@ -36,7 +32,6 @@ icon: { sprite: "/Textures/Objects/Specific/Medical/medical.rsi", state: bloodpack } spawn: Bloodpack maxCount: 10 - itemSize: 1 - type: stack id: MedicatedSuture @@ -44,7 +39,6 @@ icon: {sprite: "/Textures/Objects/Specific/Medical/medical.rsi", state: medicated-suture } spawn: MedicatedSuture maxCount: 10 - itemSize: 1 - type: stack id: RegenerativeMesh @@ -52,6 +46,4 @@ icon: {sprite: "/Textures/Objects/Specific/Medical/medical.rsi", state: regenerative-mesh} spawn: RegenerativeMesh maxCount: 10 - itemSize: 1 - diff --git a/Resources/Prototypes/Stacks/power_stacks.yml b/Resources/Prototypes/Stacks/power_stacks.yml index 5acf4819de..7f015659e9 100644 --- a/Resources/Prototypes/Stacks/power_stacks.yml +++ b/Resources/Prototypes/Stacks/power_stacks.yml @@ -4,7 +4,6 @@ icon: { sprite: "/Textures/Objects/Tools/cable-coils.rsi", state: coil-30 } spawn: CableApcStack1 maxCount: 30 - itemSize: 1 - type: stack id: CableMV @@ -12,7 +11,6 @@ icon: { sprite: "/Textures/Objects/Tools/cable-coils.rsi", state: coilmv-30 } spawn: CableMVStack1 maxCount: 30 - itemSize: 1 - type: stack id: CableHV @@ -20,4 +18,3 @@ icon: { sprite: "/Textures/Objects/Tools/cable-coils.rsi", state: coilhv-30 } spawn: CableHVStack1 maxCount: 30 - itemSize: 1 diff --git a/Resources/Prototypes/Stacks/science_stacks.yml b/Resources/Prototypes/Stacks/science_stacks.yml index 010a5dc659..bf58fad915 100644 --- a/Resources/Prototypes/Stacks/science_stacks.yml +++ b/Resources/Prototypes/Stacks/science_stacks.yml @@ -3,4 +3,21 @@ name: artifact fragment spawn: ArtifactFragment1 maxCount: 30 - itemSize: 5 \ No newline at end of file + +- type: stack + id: Capacitor + name: capacitor + spawn: CapacitorStockPart + maxCount: 10 + +- type: stack + id: MicroManipulator + name: micro manipulator + spawn: MicroManipulatorStockPart + maxCount: 10 + +- type: stack + id: MatterBin + name: matter bin + spawn: MatterBinStockPart + maxCount: 10 diff --git a/Resources/Prototypes/StatusEffects/health.yml b/Resources/Prototypes/StatusIcon/StatusEffects/health.yml similarity index 100% rename from Resources/Prototypes/StatusEffects/health.yml rename to Resources/Prototypes/StatusIcon/StatusEffects/health.yml diff --git a/Resources/Prototypes/StatusEffects/hunger.yml b/Resources/Prototypes/StatusIcon/StatusEffects/hunger.yml similarity index 100% rename from Resources/Prototypes/StatusEffects/hunger.yml rename to Resources/Prototypes/StatusIcon/StatusEffects/hunger.yml diff --git a/Resources/Prototypes/StatusEffects/ssd.yml b/Resources/Prototypes/StatusIcon/StatusEffects/ssd.yml similarity index 100% rename from Resources/Prototypes/StatusEffects/ssd.yml rename to Resources/Prototypes/StatusIcon/StatusEffects/ssd.yml diff --git a/Resources/Prototypes/StatusIcon/antag.yml b/Resources/Prototypes/StatusIcon/antag.yml index 0dbdfce4f9..0da545b8a2 100644 --- a/Resources/Prototypes/StatusIcon/antag.yml +++ b/Resources/Prototypes/StatusIcon/antag.yml @@ -1,6 +1,11 @@ - type: statusIcon id: ZombieFaction priority: 11 + showTo: + components: + - ShowAntagIcons + - Zombie + - InitialInfected icon: sprite: /Textures/Interface/Misc/job_icons.rsi state: Zombie @@ -8,6 +13,10 @@ - type: statusIcon id: InitialInfectedFaction priority: 11 + showTo: + components: + - ShowAntagIcons + - InitialInfected icon: sprite: /Textures/Interface/Misc/job_icons.rsi state: InitialInfected @@ -15,6 +24,10 @@ - type: statusIcon id: RevolutionaryFaction priority: 11 + showTo: + components: + - ShowAntagIcons + - Revolutionary icon: sprite: /Textures/Interface/Misc/job_icons.rsi state: Revolutionary @@ -22,6 +35,10 @@ - type: statusIcon id: HeadRevolutionaryFaction priority: 11 + showTo: + components: + - ShowAntagIcons + - Revolutionary icon: sprite: /Textures/Interface/Misc/job_icons.rsi state: HeadRevolutionary diff --git a/Resources/Prototypes/StatusEffects/job.yml b/Resources/Prototypes/StatusIcon/job.yml similarity index 100% rename from Resources/Prototypes/StatusEffects/job.yml rename to Resources/Prototypes/StatusIcon/job.yml diff --git a/Resources/Prototypes/StatusEffects/security.yml b/Resources/Prototypes/StatusIcon/security.yml similarity index 100% rename from Resources/Prototypes/StatusEffects/security.yml rename to Resources/Prototypes/StatusIcon/security.yml diff --git a/Resources/Prototypes/Store/presets.yml b/Resources/Prototypes/Store/presets.yml index 166c29fe41..762ed68921 100644 --- a/Resources/Prototypes/Store/presets.yml +++ b/Resources/Prototypes/Store/presets.yml @@ -1,29 +1,37 @@ -- type: storePreset +- type: entity id: StorePresetUplink - storeName: Uplink - categories: - - UplinkWeaponry - - UplinkAmmo - - UplinkExplosives - - UplinkChemicals - - UplinkDeception - - UplinkDisruption - - UplinkImplants - - UplinkAllies - - UplinkWearables - - UplinkJob - - UplinkPointless - currencyWhitelist: - - Telecrystal + abstract: true + components: + - type: Store + name: store-preset-name-uplink + categories: + - UplinkWeaponry + - UplinkAmmo + - UplinkExplosives + - UplinkChemicals + - UplinkDeception + - UplinkDisruption + - UplinkImplants + - UplinkAllies + - UplinkWearables + - UplinkJob + - UplinkPointless + currencyWhitelist: + - Telecrystal + balance: + Telecrystal: 0 -- type: storePreset +- type: entity id: StorePresetSpellbook - storeName: Spellbook - categories: - - SpellbookOffensive #Fireball, Rod Form - - SpellbookDefensive #Magic Missile, Wall of Force - - SpellbookUtility #Body Swap, Lich, Teleport, Knock, Polymorph - - SpellbookEquipment #Battlemage Robes, Staff of Locker - - SpellbookEvents #Summon Weapons, Summon Ghosts - currencyWhitelist: + abstract: true + components: + - type: Store + name: store-preset-name-spellbook + categories: + - SpellbookOffensive #Fireball, Rod Form + - SpellbookDefensive #Magic Missile, Wall of Force + - SpellbookUtility #Body Swap, Lich, Teleport, Knock, Polymorph + - SpellbookEquipment #Battlemage Robes, Staff of Locker + - SpellbookEvents #Summon Weapons, Summon Ghosts + currencyWhitelist: - WizCoin diff --git a/Resources/Prototypes/Traits/categories.yml b/Resources/Prototypes/Traits/categories.yml new file mode 100644 index 0000000000..a3621648a4 --- /dev/null +++ b/Resources/Prototypes/Traits/categories.yml @@ -0,0 +1,8 @@ +- type: traitCategory + id: Disabilities + name: trait-category-disabilities + +- type: traitCategory + id: SpeechTraits + name: trait-category-speech + maxTraitPoints: 2 diff --git a/Resources/Prototypes/Traits/disabilities.yml b/Resources/Prototypes/Traits/disabilities.yml index a224fc213e..e39f3a6013 100644 --- a/Resources/Prototypes/Traits/disabilities.yml +++ b/Resources/Prototypes/Traits/disabilities.yml @@ -3,6 +3,7 @@ name: trait-blindness-name description: trait-blindness-desc traitGear: WhiteCane + category: Disabilities whitelist: components: - Blindable @@ -14,6 +15,7 @@ name: trait-poor-vision-name description: trait-poor-vision-desc traitGear: ClothingEyesGlasses + category: Disabilities whitelist: components: - Blindable @@ -25,6 +27,7 @@ id: Narcolepsy name: trait-narcolepsy-name description: trait-narcolepsy-desc + category: Disabilities components: - type: Narcolepsy timeBetweenIncidents: 300, 600 @@ -34,25 +37,23 @@ id: Pacifist name: trait-pacifist-name description: trait-pacifist-desc + category: Disabilities components: - type: Pacified - type: trait - id: Paracusia - name: trait-paracusia-name - description: trait-paracusia-desc + id: Unrevivable + name: trait-unrevivable-name + description: trait-unrevivable-desc + category: Disabilities components: - - type: Paracusia - minTimeBetweenIncidents: 0.1 - maxTimeBetweenIncidents: 300 - maxSoundDistance: 7 - sounds: - collection: Paracusia + - type: Unrevivable - type: trait id: Muted name: trait-muted-name description: trait-muted-desc + category: Disabilities blacklist: components: - BorgChassis @@ -67,15 +68,31 @@ - type: Uncloneable - type: trait - id: FrontalLisp - name: trait-frontal-lisp-name - description: trait-frontal-lisp-desc + id: LightweightDrunk + name: trait-lightweight-name + description: trait-lightweight-desc + category: Disabilities components: - - type: FrontalLisp + - type: LightweightDrunk + boozeStrengthMultiplier: 2 + +- type: trait + id: Paracusia + name: trait-paracusia-name + description: trait-paracusia-desc + category: Disabilities + components: + - type: Paracusia + minTimeBetweenIncidents: 0.1 + maxTimeBetweenIncidents: 300 + maxSoundDistance: 7 + sounds: + collection: Paracusia - type: trait id: Snoring name: trait-snoring-name description: trait-snoring-desc + category: Disabilities components: - type: Snoring diff --git a/Resources/Prototypes/Traits/inconveniences.yml b/Resources/Prototypes/Traits/inconveniences.yml deleted file mode 100644 index 657781d1b5..0000000000 --- a/Resources/Prototypes/Traits/inconveniences.yml +++ /dev/null @@ -1,18 +0,0 @@ -- type: trait - id: LightweightDrunk - name: trait-lightweight-name - description: trait-lightweight-desc - components: - - type: LightweightDrunk - boozeStrengthMultiplier: 2 - -- type: trait - id: SocialAnxiety - name: trait-socialanxiety-name - description: trait-socialanxiety-desc - components: - - type: StutteringAccent - matchRandomProb: 0.1 - fourRandomProb: 0 - threeRandomProb: 0 - cutRandomProb: 0 diff --git a/Resources/Prototypes/Traits/neutral.yml b/Resources/Prototypes/Traits/neutral.yml deleted file mode 100644 index 78d2bba049..0000000000 --- a/Resources/Prototypes/Traits/neutral.yml +++ /dev/null @@ -1,33 +0,0 @@ -- type: trait - id: PirateAccent - name: trait-pirate-accent-name - description: trait-pirate-accent-desc - components: - - type: PirateAccent - -- type: trait - id: Accentless - name: trait-accentless-name - description: trait-accentless-desc - components: - - type: Accentless - removes: - - type: LizardAccent - - type: MothAccent - - type: ReplacementAccent - accent: dwarf - -- type: trait - id: Southern - name: trait-southern-name - description: trait-southern-desc - components: - - type: SouthernAccent - -- type: trait - id: Liar - name: trait-liar-name - description: trait-liar-desc - components: - - type: ReplacementAccent - accent: liar diff --git a/Resources/Prototypes/Traits/speech.yml b/Resources/Prototypes/Traits/speech.yml new file mode 100644 index 0000000000..9448e160b5 --- /dev/null +++ b/Resources/Prototypes/Traits/speech.yml @@ -0,0 +1,88 @@ +# Free + +- type: trait + id: Accentless + name: trait-accentless-name + description: trait-accentless-desc + category: SpeechTraits + components: + - type: Accentless + removes: + - type: LizardAccent + - type: MothAccent + - type: ReplacementAccent + accent: dwarf + +# 1 Cost + +- type: trait + id: SouthernAccent + name: trait-southern-name + description: trait-southern-desc + category: SpeechTraits + cost: 1 + components: + - type: SouthernAccent + +- type: trait + id: PirateAccent + name: trait-pirate-accent-name + description: trait-pirate-accent-desc + category: SpeechTraits + cost: 1 + components: + - type: PirateAccent + +- type: trait + id: CowboyAccent + name: trait-cowboy-name + description: trait-cowboy-desc + category: SpeechTraits + cost: 1 + components: + - type: ReplacementAccent + accent: cowboy + +- type: trait + id: ItalianAccent + name: trait-italian-name + description: trait-italian-desc + category: SpeechTraits + cost: 1 + components: + - type: ReplacementAccent + accent: italian + +- type: trait + id: Liar + name: trait-liar-name + description: trait-liar-desc + category: SpeechTraits + cost: 1 + components: + - type: ReplacementAccent + accent: liar + +# 2 Cost + +- type: trait + id: SocialAnxiety + name: trait-socialanxiety-name + description: trait-socialanxiety-desc + category: SpeechTraits + cost: 2 + components: + - type: StutteringAccent + matchRandomProb: 0.1 + fourRandomProb: 0 + threeRandomProb: 0 + cutRandomProb: 0 + +- type: trait + id: FrontalLisp + name: trait-frontal-lisp-name + description: trait-frontal-lisp-desc + category: SpeechTraits + cost: 2 + components: + - type: FrontalLisp diff --git a/Resources/Prototypes/Voice/speech_emote_sounds.yml b/Resources/Prototypes/Voice/speech_emote_sounds.yml index 3358d5be67..0bc922985a 100644 --- a/Resources/Prototypes/Voice/speech_emote_sounds.yml +++ b/Resources/Prototypes/Voice/speech_emote_sounds.yml @@ -354,7 +354,7 @@ Gasp: collection: MaleGasp DefaultDeathgasp: - collection: DeathGasp + collection: MothDeathGasp - type: emoteSounds id: UnisexSilicon diff --git a/Resources/Prototypes/Voice/speech_emotes.yml b/Resources/Prototypes/Voice/speech_emotes.yml index e571836976..e47d6382a1 100644 --- a/Resources/Prototypes/Voice/speech_emotes.yml +++ b/Resources/Prototypes/Voice/speech_emotes.yml @@ -1,4 +1,4 @@ -# vocal emotes +# vocal emotes - type: emote id: Scream name: chat-emote-name-scream @@ -345,6 +345,16 @@ components: - Respirator chatMessages: ["chat-emote-msg-gasp"] + chatTriggers: + - gasp + - gasp. + - gasp! + - gasps + - gasps. + - gasps! + - gasped + - gasped. + - gasped! - type: emote id: DefaultDeathgasp diff --git a/Resources/Prototypes/XenoArch/Effects/utility_effects.yml b/Resources/Prototypes/XenoArch/Effects/utility_effects.yml index 84df09af33..a896708057 100644 --- a/Resources/Prototypes/XenoArch/Effects/utility_effects.yml +++ b/Resources/Prototypes/XenoArch/Effects/utility_effects.yml @@ -220,7 +220,7 @@ - type: Tool qualities: - Screwing - speed: 2 # Very powerful multitool to balance out the desire to sell or scrap for points + speedModifier: 2 # Very powerful multitool to balance out the desire to sell or scrap for points useSound: /Audio/Items/drill_use.ogg - type: Tag tags: diff --git a/Resources/Prototypes/status_effects.yml b/Resources/Prototypes/status_effects.yml index a991bf4035..27609b1bb5 100644 --- a/Resources/Prototypes/status_effects.yml +++ b/Resources/Prototypes/status_effects.yml @@ -59,3 +59,6 @@ - type: statusEffect id: StaminaModifier + +- type: statusEffect + id: Flashed diff --git a/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/lit-inhand-left.png b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/lit-inhand-left.png index 353efa3f39..c08f70d091 100644 Binary files a/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/lit-inhand-left.png and b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/lit-inhand-left.png differ diff --git a/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/lit-inhand-right.png b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/lit-inhand-right.png index cdeedb4c24..7c685a0a40 100644 Binary files a/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/lit-inhand-right.png and b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/lit-inhand-right.png differ diff --git a/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/unlit-inhand-left.png b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/unlit-inhand-left.png index 9c3943fbf2..c1ca9c4d48 100644 Binary files a/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/unlit-inhand-left.png and b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/unlit-inhand-left.png differ diff --git a/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/unlit-inhand-right.png b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/unlit-inhand-right.png index 0f4ab68d15..9fc2e5ba27 100644 Binary files a/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/unlit-inhand-right.png and b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/unlit-inhand-right.png differ diff --git a/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/lit-inhand-left.png b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/lit-inhand-left.png index 353efa3f39..c08f70d091 100644 Binary files a/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/lit-inhand-left.png and b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/lit-inhand-left.png differ diff --git a/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/lit-inhand-right.png b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/lit-inhand-right.png index cdeedb4c24..7c685a0a40 100644 Binary files a/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/lit-inhand-right.png and b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/lit-inhand-right.png differ diff --git a/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/unlit-inhand-left.png b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/unlit-inhand-left.png index 9c3943fbf2..c1ca9c4d48 100644 Binary files a/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/unlit-inhand-left.png and b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/unlit-inhand-left.png differ diff --git a/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/unlit-inhand-right.png b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/unlit-inhand-right.png index 0f4ab68d15..9fc2e5ba27 100644 Binary files a/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/unlit-inhand-right.png and b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/unlit-inhand-right.png differ diff --git a/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/fill-1.png b/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/fill-1.png new file mode 100644 index 0000000000..d7e1ad3ef2 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/fill-1.png differ diff --git a/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/fill-2.png b/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/fill-2.png new file mode 100644 index 0000000000..b244e229a6 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/fill-2.png differ diff --git a/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/inhand-left-fill-1.png b/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/inhand-left-fill-1.png new file mode 100644 index 0000000000..3ab8564904 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/inhand-left-fill-1.png differ diff --git a/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/inhand-left-fill-2.png b/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/inhand-left-fill-2.png new file mode 100644 index 0000000000..649c071254 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/inhand-left-fill-2.png differ diff --git a/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/inhand-right-fill-1.png b/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/inhand-right-fill-1.png new file mode 100644 index 0000000000..8436423da2 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/inhand-right-fill-1.png differ diff --git a/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/inhand-right-fill-2.png b/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/inhand-right-fill-2.png new file mode 100644 index 0000000000..b5136f499b Binary files /dev/null and b/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/inhand-right-fill-2.png differ diff --git a/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/meta.json b/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/meta.json index 31192c23bf..b176776adb 100644 --- a/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/meta.json +++ b/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/meta.json @@ -1,30 +1,68 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/b136cf653c4926e475f8d39b34cd1b713331865a, wielded versions by Psychpsyo", + "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/b136cf653c4926e475f8d39b34cd1b713331865a, wielded versions by Psychpsyo. Fill levels by Tayrtahn on GitHub.", "size": { "x": 32, "y": 32 }, "states": [ { - "name": "advmop" + "name": "advmop" }, { - "name": "inhand-left", - "directions": 4 + "name": "fill-1" }, { - "name": "inhand-right", - "directions": 4 + "name": "fill-2" }, - { - "name": "wielded-inhand-left", - "directions": 4 + { + "name": "inhand-left", + "directions": 4 }, - { - "name": "wielded-inhand-right", - "directions": 4 + { + "name": "inhand-left-fill-1", + "directions": 4 + }, + { + "name": "inhand-left-fill-2", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "inhand-right-fill-1", + "directions": 4 + }, + { + "name": "inhand-right-fill-2", + "directions": 4 + }, + { + "name": "wielded-inhand-left", + "directions": 4 + }, + { + "name": "wielded-inhand-left-fill-1", + "directions": 4 + }, + { + "name": "wielded-inhand-left-fill-2", + "directions": 4 + }, + { + "name": "wielded-inhand-right", + "directions": 4 + }, + { + "name": "wielded-inhand-right-fill-1", + "directions": 4 + }, + { + "name": "wielded-inhand-right-fill-2", + "directions": 4 } ] } diff --git a/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/wielded-inhand-left-fill-1.png b/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/wielded-inhand-left-fill-1.png new file mode 100644 index 0000000000..57c1acc26e Binary files /dev/null and b/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/wielded-inhand-left-fill-1.png differ diff --git a/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/wielded-inhand-left-fill-2.png b/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/wielded-inhand-left-fill-2.png new file mode 100644 index 0000000000..4f0245bb85 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/wielded-inhand-left-fill-2.png differ diff --git a/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/wielded-inhand-right-fill-1.png b/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/wielded-inhand-right-fill-1.png new file mode 100644 index 0000000000..07d042cc75 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/wielded-inhand-right-fill-1.png differ diff --git a/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/wielded-inhand-right-fill-2.png b/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/wielded-inhand-right-fill-2.png new file mode 100644 index 0000000000..b165da985c Binary files /dev/null and b/Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/wielded-inhand-right-fill-2.png differ diff --git a/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/fill-1.png b/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/fill-1.png new file mode 100644 index 0000000000..71169a1df2 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/fill-1.png differ diff --git a/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/fill-2.png b/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/fill-2.png new file mode 100644 index 0000000000..f8b37b577c Binary files /dev/null and b/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/fill-2.png differ diff --git a/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/fill-3.png b/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/fill-3.png new file mode 100644 index 0000000000..e367772c5c Binary files /dev/null and b/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/fill-3.png differ diff --git a/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/inhand-left-fill-1.png b/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/inhand-left-fill-1.png new file mode 100644 index 0000000000..3007efa01e Binary files /dev/null and b/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/inhand-left-fill-1.png differ diff --git a/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/inhand-left-fill-2.png b/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/inhand-left-fill-2.png new file mode 100644 index 0000000000..e8ee5a3f58 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/inhand-left-fill-2.png differ diff --git a/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/inhand-right-fill-1.png b/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/inhand-right-fill-1.png new file mode 100644 index 0000000000..17495e7182 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/inhand-right-fill-1.png differ diff --git a/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/inhand-right-fill-2.png b/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/inhand-right-fill-2.png new file mode 100644 index 0000000000..58c3bd2c42 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/inhand-right-fill-2.png differ diff --git a/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/meta.json b/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/meta.json index 5ef52189ca..34768c16f7 100644 --- a/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/meta.json +++ b/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/meta.json @@ -1,30 +1,71 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/f8f4aeda930fcd0805ca4cc76d9bc9412a5b3428, wielded versions from Easypoll", + "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/f8f4aeda930fcd0805ca4cc76d9bc9412a5b3428, wielded versions from Easypoll, fill levels by Tayrtahn on GitHub.", "size": { "x": 32, "y": 32 }, "states": [ { - "name": "mop" + "name": "mop" }, { - "name": "inhand-left", - "directions": 4 + "name": "fill-1" }, { - "name": "inhand-right", - "directions": 4 + "name": "fill-2" }, - { - "name": "wielded-inhand-left", - "directions": 4 + { + "name": "fill-3" }, - { - "name": "wielded-inhand-right", - "directions": 4 + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "inhand-left-fill-1", + "directions": 4 + }, + { + "name": "inhand-left-fill-2", + "directions": 4 + }, + { + "name": "inhand-right-fill-1", + "directions": 4 + }, + { + "name": "inhand-right-fill-2", + "directions": 4 + }, + { + "name": "wielded-inhand-left", + "directions": 4 + }, + { + "name": "wielded-inhand-right", + "directions": 4 + }, + { + "name": "wielded-inhand-left-fill-1", + "directions": 4 + }, + { + "name": "wielded-inhand-left-fill-2", + "directions": 4 + }, + { + "name": "wielded-inhand-right-fill-1", + "directions": 4 + }, + { + "name": "wielded-inhand-right-fill-2", + "directions": 4 } ] } diff --git a/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/wielded-inhand-left-fill-1.png b/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/wielded-inhand-left-fill-1.png new file mode 100644 index 0000000000..997f3ae039 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/wielded-inhand-left-fill-1.png differ diff --git a/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/wielded-inhand-left-fill-2.png b/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/wielded-inhand-left-fill-2.png new file mode 100644 index 0000000000..288492b96a Binary files /dev/null and b/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/wielded-inhand-left-fill-2.png differ diff --git a/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/wielded-inhand-right-fill-1.png b/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/wielded-inhand-right-fill-1.png new file mode 100644 index 0000000000..956f9bbbd1 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/wielded-inhand-right-fill-1.png differ diff --git a/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/wielded-inhand-right-fill-2.png b/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/wielded-inhand-right-fill-2.png new file mode 100644 index 0000000000..5f8cd98644 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Janitorial/mop.rsi/wielded-inhand-right-fill-2.png differ diff --git a/Resources/Textures/Objects/Specific/Janitorial/rag.rsi/fill-1.png b/Resources/Textures/Objects/Specific/Janitorial/rag.rsi/fill-1.png new file mode 100644 index 0000000000..6fb322fbdf Binary files /dev/null and b/Resources/Textures/Objects/Specific/Janitorial/rag.rsi/fill-1.png differ diff --git a/Resources/Textures/Objects/Specific/Janitorial/rag.rsi/fill-2.png b/Resources/Textures/Objects/Specific/Janitorial/rag.rsi/fill-2.png new file mode 100644 index 0000000000..7114d9a2e7 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Janitorial/rag.rsi/fill-2.png differ diff --git a/Resources/Textures/Objects/Specific/Janitorial/rag.rsi/fill-3.png b/Resources/Textures/Objects/Specific/Janitorial/rag.rsi/fill-3.png new file mode 100644 index 0000000000..0705b79b50 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Janitorial/rag.rsi/fill-3.png differ diff --git a/Resources/Textures/Objects/Specific/Janitorial/rag.rsi/meta.json b/Resources/Textures/Objects/Specific/Janitorial/rag.rsi/meta.json index b750bcc3d1..7051793e03 100644 --- a/Resources/Textures/Objects/Specific/Janitorial/rag.rsi/meta.json +++ b/Resources/Textures/Objects/Specific/Janitorial/rag.rsi/meta.json @@ -1,14 +1,23 @@ { - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/1b8c53516ce8b14828b7147b0fa344b7b57724e9", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "rag" - } - ] -} \ No newline at end of file + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/1b8c53516ce8b14828b7147b0fa344b7b57724e9. Fill levels by Tayrtahn on GitHub", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "rag" + }, + { + "name": "fill-1" + }, + { + "name": "fill-2" + }, + { + "name": "fill-3" + } + ] +} diff --git a/Resources/Textures/Shaders/flashed_effect.swsl b/Resources/Textures/Shaders/flashed_effect.swsl index ec486ab531..519a9fdd99 100644 --- a/Resources/Textures/Shaders/flashed_effect.swsl +++ b/Resources/Textures/Shaders/flashed_effect.swsl @@ -11,7 +11,7 @@ void fragment() { highp vec4 textureMix = mix(tex1, tex2, 0.5); - // Gradually mixes between the texture mix and a full-white texture, causing the "blinding" effect + // Gradually mixes between the texture mix and a full-black texture, causing the "blinding" effect highp vec4 mixed = mix(vec4(0.0, 0.0, 0.0, 1.0), textureMix, percentComplete); COLOR = vec4(mixed.rgb, remaining); diff --git a/RobustToolbox b/RobustToolbox index c89c529ba4..fcd507d1f9 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit c89c529ba4fa7516e2265f3c47549da35ab6c3d8 +Subproject commit fcd507d1f9e6fffb96bc4228d7f3d78577f0ff2e diff --git a/flake.lock b/flake.lock index 6ab38fa41b..7baaa468ea 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1705309234, - "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", "owner": "numtide", "repo": "flake-utils", - "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", "type": "github" }, "original": { @@ -20,16 +20,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1708210246, - "narHash": "sha256-Q8L9XwrBK53fbuuIFMbjKvoV7ixfLFKLw4yV+SD28Y8=", + "lastModified": 1717352157, + "narHash": "sha256-hbBzucWOhwxt3QzeAyUojtD6/aHH81JssDfhFfmqOy0=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "69405156cffbdf2be50153f13cbdf9a0bea38e49", + "rev": "44f538ab12e2726af450877a5529f4fd88ddb0fb", "type": "github" }, "original": { "owner": "NixOS", - "ref": "release-23.11", + "ref": "release-24.05", "repo": "nixpkgs", "type": "github" } diff --git a/flake.nix b/flake.nix index e2e119eb99..095e6b017c 100644 --- a/flake.nix +++ b/flake.nix @@ -1,7 +1,7 @@ { description = "Development environment for Space Station 14"; - inputs.nixpkgs.url = "github:NixOS/nixpkgs/release-23.11"; + inputs.nixpkgs.url = "github:NixOS/nixpkgs/release-24.05"; inputs.flake-utils.url = "github:numtide/flake-utils"; outputs = { self, nixpkgs, flake-utils }: