From 5e3932330712e56d7fede0f00ae02b64b613bd3c Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+emogarbage404@users.noreply.github.com> Date: Sat, 1 Jun 2024 17:29:17 +0200 Subject: [PATCH 001/158] Clean up store system (#28463) --- .../Store/Ui/StoreBoundUserInterface.cs | 24 +++----- Content.Client/Store/Ui/StoreMenu.xaml.cs | 5 +- .../GameTicking/Rules/NukeopsRuleSystem.cs | 1 + .../Implants/SubdermalImplantSystem.cs | 1 + Content.Server/PDA/PdaSystem.cs | 8 ++- Content.Server/PDA/Ringer/RingerSystem.cs | 1 + .../Revenant/EntitySystems/RevenantSystem.cs | 1 + .../Store/Conditions/BuyBeforeCondition.cs | 1 + .../Store/Systems/StoreSystem.Command.cs | 4 +- .../Store/Systems/StoreSystem.Listings.cs | 11 +++- .../Store/Systems/StoreSystem.Refund.cs | 1 + .../Store/Systems/StoreSystem.Ui.cs | 9 +-- Content.Server/Store/Systems/StoreSystem.cs | 42 +------------- .../SurplusBundle/SurplusBundleComponent.cs | 13 +---- .../SurplusBundle/SurplusBundleSystem.cs | 57 ++++++++---------- .../Traitor/Uplink/UplinkComponent.cs | 7 +++ Content.Server/Traitor/Uplink/UplinkSystem.cs | 18 +----- .../Store/Components/StoreComponent.cs | 34 +++++------ Content.Shared/Store/StoreUi.cs | 14 ----- Resources/Locale/en-US/store/store.ftl | 3 + .../Catalog/Fills/Crates/syndicate.yml | 4 +- .../Entities/Objects/Devices/pda.yml | 2 +- .../Entities/Objects/Magic/books.yml | 6 +- .../Objects/Misc/subdermal_implants.yml | 3 +- .../Entities/Objects/Specific/syndicate.yml | 19 ++++-- Resources/Prototypes/Store/presets.yml | 58 +++++++++++-------- 26 files changed, 139 insertions(+), 208 deletions(-) create mode 100644 Content.Server/Traitor/Uplink/UplinkComponent.cs rename {Content.Server => Content.Shared}/Store/Components/StoreComponent.cs (72%) 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.Server/GameTicking/Rules/NukeopsRuleSystem.cs b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs index d6f1c3c619..1b62778d75 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; 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/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/Revenant/EntitySystems/RevenantSystem.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.cs index c390432f3a..a05105662d 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; 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/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/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/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/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/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/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/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/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/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/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 From a742fe4d018445a386415ab28ab55a85b5f264b8 Mon Sep 17 00:00:00 2001 From: beck-thompson <107373427+beck-thompson@users.noreply.github.com> Date: Sat, 1 Jun 2024 10:29:46 -0700 Subject: [PATCH 002/158] Fixed bug where ID card computer defaulted to the atmos as the job icon. (#28462) Fixed ID atmos bug! --- Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) 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; From 8212271fda5d806e71c402c52e054fe706851dc1 Mon Sep 17 00:00:00 2001 From: PJBot Date: Sat, 1 Jun 2024 17:30:52 +0000 Subject: [PATCH 003/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 2a808f3282..449f9bf43b 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +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. @@ -3853,3 +3845,11 @@ 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 From 39650620e215fdea528e065fa7a93c3ebf6f7b6d Mon Sep 17 00:00:00 2001 From: Tornado Tech <54727692+Tornado-Technology@users.noreply.github.com> Date: Sun, 2 Jun 2024 03:46:35 +1000 Subject: [PATCH 004/158] Clean up new HTNs tasks (#28469) * Clean up new HTNs tasks * Added docs to math operations --- .../NPC/HTN/Preconditions/KeyExistsPrecondition.cs | 7 ++++++- .../NPC/HTN/Preconditions/KeyNotExistsPrecondition.cs | 6 +++++- .../HTN/Preconditions/Math/KeyBoolEqualsPrecondition.cs | 7 ++++--- .../HTN/Preconditions/Math/KeyFloatEqualsPrecondition.cs | 8 ++++++-- .../Preconditions/Math/KeyFloatGreaterPrecondition.cs | 8 ++++++-- .../HTN/Preconditions/Math/KeyFloatLessPrecondition.cs | 8 ++++++-- .../PrimitiveTasks/Operators/Math/AddFloatOperator.cs | 5 +++-- .../HTN/PrimitiveTasks/Operators/Math/SetBoolOperator.cs | 5 +++-- .../PrimitiveTasks/Operators/Math/SetFloatOperator.cs | 5 +++-- .../Operators/Math/SetRandomFloatOperator.cs | 8 +++++--- .../NPC/HTN/PrimitiveTasks/Operators/SayKeyOperator.cs | 9 +++++++-- .../NPC/HTN/PrimitiveTasks/Operators/SpeakOperator.cs | 7 ++++--- 12 files changed, 58 insertions(+), 25 deletions(-) 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); } } From ab59cfea40c61e4992d8a6fc8c830b05554204b0 Mon Sep 17 00:00:00 2001 From: AJCM-git <60196617+AJCM-git@users.noreply.github.com> Date: Sat, 1 Jun 2024 13:49:28 -0400 Subject: [PATCH 005/158] Makes machine parts stackable, removes unused field in stack prototypes (#28434) * Makes machine parts stacks, removes unused field in stack prototypes * forgor * Fix tests * Fixes lathe construction. Yes. This sucks but there's no better way that doesnt involve refactoring machine parts completely * detail * a --- .../Tests/MaterialArbitrageTest.cs | 2 +- .../Construction/MachineFrameSystem.cs | 59 +++++++++++--- Content.Server/Stack/StackSystem.cs | 2 +- .../Construction/MachinePartSystem.cs | 4 +- Content.Shared/Materials/MaterialPrototype.cs | 2 +- Content.Shared/Stacks/StackPrototype.cs | 19 ++--- .../Entities/Objects/Misc/machine_parts.yml | 8 ++ .../Entities/Objects/Tools/fulton.yml | 1 - .../Stacks/Materials/Sheets/glass.yml | 7 -- .../Stacks/Materials/Sheets/metal.yml | 3 - .../Stacks/Materials/Sheets/other.yml | 4 - .../Prototypes/Stacks/Materials/crystals.yml | 1 - .../Prototypes/Stacks/Materials/ingots.yml | 2 - .../Prototypes/Stacks/Materials/materials.yml | 13 ---- Resources/Prototypes/Stacks/Materials/ore.yml | 9 --- .../Prototypes/Stacks/Materials/parts.yml | 1 - .../Prototypes/Stacks/consumable_stacks.yml | 11 --- .../Prototypes/Stacks/engineering_stacks.yml | 2 - .../Prototypes/Stacks/floor_tile_stacks.yml | 76 +------------------ .../Prototypes/Stacks/medical_stacks.yml | 8 -- Resources/Prototypes/Stacks/power_stacks.yml | 3 - .../Prototypes/Stacks/science_stacks.yml | 19 ++++- 22 files changed, 88 insertions(+), 168 deletions(-) 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.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/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.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/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/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/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/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/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 From 6bba4abf1b0763a6092eb77458c6d4009ae425bf Mon Sep 17 00:00:00 2001 From: PJBot Date: Sat, 1 Jun 2024 17:50:34 +0000 Subject: [PATCH 006/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 449f9bf43b..535a93e87e 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- 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 @@ -3853,3 +3846,10 @@ 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 From d65fdde82c04ecfc9851073de94ae5aa9f2ec181 Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Sat, 1 Jun 2024 12:00:31 -0700 Subject: [PATCH 007/158] Fix error when removing chasm falling component on a terminating entity (#28471) --- Content.Client/Chasm/ChasmFallingVisualsSystem.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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; From 89be6b30da747bcbf6117058ad9ea95190c859f5 Mon Sep 17 00:00:00 2001 From: Kevin Zheng Date: Sat, 1 Jun 2024 12:56:57 -0800 Subject: [PATCH 008/158] Adjust power monitor power display (#28487) --- Content.Client/Power/PowerMonitoringWindow.xaml.Widgets.cs | 4 +++- .../Locale/en-US/components/power-monitoring-component.ftl | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) 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/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 From 228e6535726ed4599a59acac1f3e2df3529061a3 Mon Sep 17 00:00:00 2001 From: Tornado Tech <54727692+Tornado-Technology@users.noreply.github.com> Date: Sun, 2 Jun 2024 09:49:34 +1000 Subject: [PATCH 009/158] Hiding and clearing department prototype code (#28114) --- .../UI/BanPanel/BanPanel.xaml.cs | 2 +- .../Lobby/UI/HumanoidProfileEditor.xaml.cs | 34 ++++++++++++----- .../Conditions/BuyerDepartmentCondition.cs | 4 +- Content.Shared/Roles/DepartmentPrototype.cs | 37 +++++++++++-------- 4 files changed, 49 insertions(+), 28 deletions(-) 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/Lobby/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs index 9da9ca080b..2b524385ec 100644 --- a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs +++ b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs @@ -719,8 +719,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 +783,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(); @@ -821,13 +830,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 +943,11 @@ namespace Content.Client.Lobby.UI SetDirty(); ReloadPreview(); }; + + if (Profile is null) + return; + + UpdateJobPriorities(); } private void OnFlavorTextChange(string content) 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.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); } } From 91f0ba53e55397a6de3685fe04c443ac27fc8de9 Mon Sep 17 00:00:00 2001 From: Tornado Tech <54727692+Tornado-Technology@users.noreply.github.com> Date: Sun, 2 Jun 2024 11:11:19 +1000 Subject: [PATCH 010/158] Cleans up tag system (#28272) * Updated tag system * Added params methods * Fixed tag integration tests * Fixed params methods recursion * Revert has All/Any tag one argument realisation * Updated tag integration tests * Shit happens * Added individual List/HashSet methods, docs, tests --- Content.Client/Verbs/VerbSystem.cs | 3 +- Content.IntegrationTests/Tests/Tag/TagTest.cs | 161 +- .../Administration/Toolshed/TagCommand.cs | 11 +- .../Mech/Systems/MechAssemblySystem.cs | 3 +- .../Procedural/DungeonJob.PostGen.cs | 5 +- Content.Server/Procedural/DungeonJob.cs | 5 +- Content.Server/Procedural/DungeonSystem.cs | 4 + .../EntitySystems/RevenantSystem.Abilities.cs | 2 +- Content.Server/Spreader/SpreaderSystem.cs | 10 +- .../SharedChameleonClothingSystem.cs | 5 +- .../Conditions/NoWindowsInTile.cs | 3 +- .../EntitySystems/AnchorableSystem.cs | 11 +- .../MultipleTagsConstructionGraphStep.cs | 13 +- .../Pinpointer/SharedNavMapSystem.cs | 5 +- Content.Shared/Tag/TagComponent.cs | 16 +- Content.Shared/Tag/TagComponentState.cs | 15 - Content.Shared/Tag/TagPrototype.cs | 24 +- Content.Shared/Tag/TagSystem.cs | 1318 +++++++++-------- 18 files changed, 834 insertions(+), 780 deletions(-) delete mode 100644 Content.Shared/Tag/TagComponentState.cs 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.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.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/Mech/Systems/MechAssemblySystem.cs b/Content.Server/Mech/Systems/MechAssemblySystem.cs index e5b7bfaac3..c1fff819b4 100644 --- a/Content.Server/Mech/Systems/MechAssemblySystem.cs +++ b/Content.Server/Mech/Systems/MechAssemblySystem.cs @@ -14,6 +14,7 @@ namespace Content.Server.Mech.Systems; public sealed class MechAssemblySystem : EntitySystem { [Dependency] private readonly ContainerSystem _container = default!; + [Dependency] private readonly TagSystem _tag = default!; /// public override void Initialize() @@ -44,7 +45,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/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.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.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/Revenant/EntitySystems/RevenantSystem.Abilities.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs index fd7d15ea5d..670c64577a 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs @@ -246,7 +246,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(); 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.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.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..e9ef053f62 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) }); @@ -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/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/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/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; - } } From f8746e20f2b17eb9bf1976384a584cd8257caa98 Mon Sep 17 00:00:00 2001 From: null <56081759+NullWanderer@users.noreply.github.com> Date: Thu, 6 Jun 2024 21:36:31 +0200 Subject: [PATCH 011/158] Clean RES.Pacified a bit --- .../RoundEnd/RoundEndSystem.Pacified.cs | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) 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(); } From d7d489e4debfe2a4cf40d8d9cd952130a57ab7ad Mon Sep 17 00:00:00 2001 From: null <56081759+NullWanderer@users.noreply.github.com> Date: Thu, 6 Jun 2024 21:43:31 +0200 Subject: [PATCH 012/158] Remove deleted field from bluespace --- Resources/Prototypes/Nyanotrasen/Stacks/materials.yml | 1 - 1 file changed, 1 deletion(-) 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 From 2ecfab2e85410a1f9e5d1fc4296065cb7670a6d7 Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Sat, 1 Jun 2024 18:41:06 -0700 Subject: [PATCH 013/158] Disable rainbow overlay when reduced motion is enabled (#28496) Disable rainbow ovelray when reduced motion is enabled --- Content.Client/Drugs/RainbowOverlay.cs | 7 +++++++ 1 file changed, 7 insertions(+) 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; From 76655625545837f9f651bfc57ee4b4c555da2b32 Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 2 Jun 2024 01:42:12 +0000 Subject: [PATCH 014/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 535a93e87e..0c671f49ee 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- 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. @@ -3853,3 +3845,11 @@ 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 From b2931f9a3302382084968256c640faaacd6059f8 Mon Sep 17 00:00:00 2001 From: Plykiya <58439124+plykiya@users.noreply.github.com> Date: Sun, 2 Jun 2024 05:10:24 +0200 Subject: [PATCH 015/158] Replace obsolete EntityWhitelist IsValid usages (#28465) * Replace obsolete whitelist is valid with whitelist system * Consistency * Fix logic * Bork * I figured out how to get whitelists on the client lol * test fail * woops * HELP ME FUNCTIONS * Fix errors * simplify --------- Co-authored-by: plykiya --- Content.Client/Chat/UI/EmotesMenu.xaml.cs | 9 +- .../UI/ConstructionMenuPresenter.cs | 6 +- .../Chat/Systems/ChatSystem.Emote.cs | 4 +- Content.Server/Chat/Systems/ChatSystem.cs | 2 + Content.Server/Gatherable/GatherableSystem.cs | 6 +- .../NPCImprintingOnSpawnBehaviourSystem.cs | 4 +- .../NPC/Systems/NPCUtilitySystem.cs | 4 +- .../Power/EntitySystems/ChargerSystem.cs | 4 +- .../EntitySystems/RevenantSystem.Abilities.cs | 8 +- .../Silicons/Borgs/BorgSystem.Modules.cs | 2 +- Content.Server/Silicons/Borgs/BorgSystem.cs | 8 +- .../Storage/EntitySystems/PickRandomSystem.cs | 6 +- .../XenoArtifacts/ArtifactSystem.Nodes.cs | 11 ++- .../Containers/ItemSlot/ItemSlotsSystem.cs | 5 +- .../Damage/Systems/DamageContactsSystem.cs | 4 +- Content.Shared/Devour/SharedDevourSystem.cs | 4 +- .../Disposal/SharedDisposalUnitSystem.cs | 8 +- .../Implants/SharedImplanterSystem.cs | 5 +- .../Interaction/SmartEquipSystem.cs | 6 +- .../Materials/SharedMaterialStorageSystem.cs | 6 +- .../Systems/SharedChameleonProjectorSystem.cs | 6 +- .../Salvage/Fulton/SharedFultonSystem.cs | 4 +- .../Shuttles/Systems/SharedShuttleSystem.cs | 4 +- .../EntitySystems/MagnetPickupSystem.cs | 5 +- .../SharedEntityStorageSystem.cs | 4 +- .../EntitySystems/SharedStorageSystem.cs | 10 +-- .../Marker/SharedDamageMarkerSystem.cs | 4 +- .../Systems/SharedGunSystem.Ballistic.cs | 5 +- .../Systems/SharedGunSystem.Revolver.cs | 2 +- .../Weapons/Ranged/Systems/SharedGunSystem.cs | 2 + .../Whitelist/EntityWhitelistSystem.cs | 84 +++++++++++++++++++ 31 files changed, 186 insertions(+), 56 deletions(-) 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/Construction/UI/ConstructionMenuPresenter.cs b/Content.Client/Construction/UI/ConstructionMenuPresenter.cs index 9a09436176..0c7912e0bc 100644 --- a/Content.Client/Construction/UI/ConstructionMenuPresenter.cs +++ b/Content.Client/Construction/UI/ConstructionMenuPresenter.cs @@ -2,6 +2,7 @@ using System.Linq; using Content.Client.UserInterface.Systems.MenuBar.Widgets; using Content.Shared.Construction.Prototypes; using Content.Shared.Tag; +using Content.Shared.Whitelist; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.Placement; @@ -23,6 +24,7 @@ namespace Content.Client.Construction.UI /// 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.Server/Chat/Systems/ChatSystem.Emote.cs b/Content.Server/Chat/Systems/ChatSystem.Emote.cs index dfb9347334..c2bd842f83 100644 --- a/Content.Server/Chat/Systems/ChatSystem.Emote.cs +++ b/Content.Server/Chat/Systems/ChatSystem.Emote.cs @@ -86,9 +86,7 @@ public partial class ChatSystem bool ignoreActionBlocker = false ) { - if (!(emote.Whitelist?.IsValid(source, EntityManager) ?? true)) - return; - if (emote.Blacklist?.IsValid(source, EntityManager) ?? false) + if (_whitelistSystem.IsWhitelistFailOrNull(emote.Whitelist, source) || _whitelistSystem.IsBlacklistPass(emote.Blacklist, source)) return; if (!emote.Available && diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs index 6d2909860c..f6bc4a25c0 100644 --- a/Content.Server/Chat/Systems/ChatSystem.cs +++ b/Content.Server/Chat/Systems/ChatSystem.cs @@ -25,6 +25,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 +62,7 @@ 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!; //Nyano - Summary: pulls in the nyano chat system for psionics. [Dependency] private readonly NyanoChatSystem _nyanoChatSystem = default!; diff --git a/Content.Server/Gatherable/GatherableSystem.cs b/Content.Server/Gatherable/GatherableSystem.cs index e24b0da593..d6a3be451b 100644 --- a/Content.Server/Gatherable/GatherableSystem.cs +++ b/Content.Server/Gatherable/GatherableSystem.cs @@ -3,6 +3,7 @@ using Content.Server.Gatherable.Components; using Content.Shared.Interaction; using Content.Shared.Tag; using Content.Shared.Weapons.Melee.Events; +using Content.Shared.Whitelist; using Robust.Server.GameObjects; using Robust.Shared.Audio.Systems; using Robust.Shared.Prototypes; @@ -18,6 +19,7 @@ public sealed partial class GatherableSystem : EntitySystem [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly TagSystem _tagSystem = default!; [Dependency] private readonly TransformSystem _transform = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -30,7 +32,7 @@ public sealed partial class GatherableSystem : EntitySystem private void OnAttacked(Entity 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/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/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/Revenant/EntitySystems/RevenantSystem.Abilities.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs index 670c64577a..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() { @@ -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/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/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/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.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/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/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/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/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/Materials/SharedMaterialStorageSystem.cs b/Content.Shared/Materials/SharedMaterialStorageSystem.cs index b1de77d971..af815bdb2b 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/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/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/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/SharedStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs index ec08375295..874b17a03a 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; @@ -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.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/Ranged/Systems/SharedGunSystem.Ballistic.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs index 784dd0793a..1f9e6cee76 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs @@ -41,7 +41,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) 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/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) { From 5c8dbfe223ac22f570007535f754025588432cb2 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Sat, 1 Jun 2024 23:58:33 -0400 Subject: [PATCH 016/158] Guidebook Tables (#28484) * PJB's cool table control (it probably doesn't work) * ok wait wrong file * Guidebook Tables --- Content.Client/Guidebook/Richtext/Box.cs | 3 + Content.Client/Guidebook/Richtext/ColorBox.cs | 49 +++ Content.Client/Guidebook/Richtext/Table.cs | 27 ++ .../UserInterface/Controls/TableContainer.cs | 285 ++++++++++++++++++ 4 files changed, 364 insertions(+) create mode 100644 Content.Client/Guidebook/Richtext/ColorBox.cs create mode 100644 Content.Client/Guidebook/Richtext/Table.cs create mode 100644 Content.Client/UserInterface/Controls/TableContainer.cs 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/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; + } +} From c62bd236562c80bb8ced380c1b0f89c0eb8d82a3 Mon Sep 17 00:00:00 2001 From: eoineoineoin Date: Sun, 2 Jun 2024 05:07:41 +0100 Subject: [PATCH 017/158] Replace Matrix3 with System.Numerics.Matrix3x2 (#27443) Replace Matrix3 with Matrix3x2 --- .../SpawnExplosion/ExplosionDebugOverlay.cs | 16 ++++--- .../Atmos/Overlays/AtmosDebugOverlay.cs | 2 +- .../Atmos/Overlays/GasTileOverlay.cs | 4 +- .../Clickable/ClickableComponent.cs | 12 ++--- .../Decals/Overlays/DecalOverlay.cs | 3 +- .../Decals/Overlays/DecalPlacementOverlay.cs | 4 +- Content.Client/DoAfter/DoAfterOverlay.cs | 12 ++--- Content.Client/Explosion/ExplosionOverlay.cs | 5 +- Content.Client/Fluids/PuddleOverlay.cs | 7 +-- Content.Client/Maps/GridDraggingSystem.cs | 4 +- Content.Client/NPC/PathfindingSystem.cs | 8 ++-- .../NodeContainer/NodeVisualizationOverlay.cs | 2 +- .../Overlays/EntityHealthBarOverlay.cs | 12 ++--- .../StencilOverlay.RestrictedRange.cs | 6 +-- .../Overlays/StencilOverlay.Weather.cs | 8 ++-- Content.Client/Overlays/StencilOverlay.cs | 3 +- Content.Client/Paper/UI/StampLabel.xaml.cs | 2 +- Content.Client/Paper/UI/StampWidget.xaml.cs | 2 +- Content.Client/Pinpointer/UI/NavMapControl.cs | 6 +-- Content.Client/Popups/PopupOverlay.cs | 5 +- .../Systems/ShuttleSystem.EmergencyConsole.cs | 3 +- .../Shuttles/UI/BaseShuttleControl.xaml.cs | 7 +-- Content.Client/Shuttles/UI/NavScreen.xaml.cs | 3 +- .../Shuttles/UI/ShuttleDockControl.xaml.cs | 46 +++++++++---------- .../Shuttles/UI/ShuttleMapControl.xaml.cs | 26 +++++------ .../Shuttles/UI/ShuttleNavControl.xaml.cs | 24 +++++----- .../StatusIcon/StatusIconOverlay.cs | 10 ++-- .../Storage/Systems/StorageSystem.cs | 5 +- .../UserInterface/Controls/DirectionIcon.cs | 2 +- .../Controls/MapGridControl.xaml.cs | 2 +- Content.Client/Viewport/ScalingViewport.cs | 18 ++++---- .../Melee/MeleeWeaponSystem.Effects.cs | 2 +- .../Weapons/Ranged/ItemStatus/BulletRender.cs | 4 +- .../EntitySystems/ExplosionGridTileFlood.cs | 13 +++--- .../EntitySystems/ExplosionSystem.GridMap.cs | 12 ++--- .../ExplosionSystem.Processing.cs | 20 ++++---- .../EntitySystems/ExplosionSystem.TileFill.cs | 4 +- .../EntitySystems/ExplosionSystem.Visuals.cs | 3 +- .../Fluids/EntitySystems/AbsorbentSystem.cs | 3 +- .../GameTicking/GameTicker.Spawning.cs | 2 +- .../Medical/SuitSensors/SuitSensorSystem.cs | 5 +- .../Pathfinding/PathfindingSystem.Distance.cs | 2 +- .../NPC/Pathfinding/PathfindingSystem.Grid.cs | 2 +- .../NPC/Pathfinding/PathfindingSystem.cs | 2 +- .../Procedural/DungeonJob.PrefabDunGen.cs | 24 +++++----- .../Procedural/DungeonSystem.Rooms.cs | 16 +++---- .../Systems/RadiationSystem.GridCast.cs | 4 +- Content.Server/Salvage/FultonSystem.cs | 6 ++- .../Shuttles/Events/FTLStartedEvent.cs | 3 +- .../Shuttles/Systems/ArrivalsSystem.cs | 3 +- .../Shuttles/Systems/DockingSystem.Shuttle.cs | 16 +++---- .../Shuttles/Systems/ShuttleSystem.Impact.cs | 5 +- .../EntitySystems/GravityWellSystem.cs | 16 +++---- .../Weapons/Ranged/Systems/GunSystem.cs | 2 +- .../Components/ExplosionVisualsComponent.cs | 7 +-- Content.Shared/Item/SharedItemSystem.cs | 2 +- Content.Shared/Maps/TurfSystem.cs | 2 +- .../Weapons/Melee/SharedMeleeWeaponSystem.cs | 2 +- 58 files changed, 236 insertions(+), 215 deletions(-) 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/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/Clickable/ClickableComponent.cs b/Content.Client/Clickable/ClickableComponent.cs index cfbd1a99d6..6f75df4683 100644 --- a/Content.Client/Clickable/ClickableComponent.cs +++ b/Content.Client/Clickable/ClickableComponent.cs @@ -38,9 +38,9 @@ namespace Content.Client.Clickable renderOrder = sprite.RenderOrder; var (spritePos, spriteRot) = transform.GetWorldPositionRotation(xformQuery); var spriteBB = sprite.CalculateRotatedBoundingBox(spritePos, spriteRot, eye.Rotation); - bottom = Matrix3.CreateRotation(eye.Rotation).TransformBox(spriteBB).Bottom; + bottom = Matrix3Helpers.CreateRotation(eye.Rotation).TransformBox(spriteBB).Bottom; - var invSpriteMatrix = Matrix3.Invert(sprite.GetLocalMatrix()); + Matrix3x2.Invert(sprite.GetLocalMatrix(), out var invSpriteMatrix); // This should have been the rotation of the sprite relative to the screen, but this is not the case with no-rot or directional sprites. var relativeRotation = (spriteRot + eye.Rotation).Reduced().FlipPositive(); @@ -48,8 +48,8 @@ namespace Content.Client.Clickable Angle cardinalSnapping = sprite.SnapCardinals ? relativeRotation.GetCardinalDir().ToAngle() : Angle.Zero; // First we get `localPos`, the clicked location in the sprite-coordinate frame. - var entityXform = Matrix3.CreateInverseTransform(transform.WorldPosition, sprite.NoRotation ? -eye.Rotation : spriteRot - cardinalSnapping); - var localPos = invSpriteMatrix.Transform(entityXform.Transform(worldPos)); + var entityXform = Matrix3Helpers.CreateInverseTransform(transform.WorldPosition, sprite.NoRotation ? -eye.Rotation : spriteRot - cardinalSnapping); + var localPos = Vector2.Transform(Vector2.Transform(worldPos, entityXform), invSpriteMatrix); // Check explicitly defined click-able bounds if (CheckDirBound(sprite, relativeRotation, localPos)) @@ -79,8 +79,8 @@ namespace Content.Client.Clickable // convert to layer-local coordinates layer.GetLayerDrawMatrix(dir, out var matrix); - var inverseMatrix = Matrix3.Invert(matrix); - var layerLocal = inverseMatrix.Transform(localPos); + Matrix3x2.Invert(matrix, out var inverseMatrix); + var layerLocal = Vector2.Transform(localPos, inverseMatrix); // Convert to image coordinates var layerImagePos = (Vector2i) (layerLocal * EyeManager.PixelsPerMeter * new Vector2(1, -1) + rsiState.Size / 2f); 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/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/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/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/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/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/Overlays/EntityHealthBarOverlay.cs b/Content.Client/Overlays/EntityHealthBarOverlay.cs index 2b2ff14a22..758bb562f9 100644 --- a/Content.Client/Overlays/EntityHealthBarOverlay.cs +++ b/Content.Client/Overlays/EntityHealthBarOverlay.cs @@ -42,8 +42,8 @@ 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); var query = _entManager.AllEntityQueryEnumerator(); while (query.MoveNext(out var uid, @@ -83,10 +83,10 @@ public sealed class EntityHealthBarOverlay : Overlay 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 +115,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/StencilOverlay.RestrictedRange.cs b/Content.Client/Overlays/StencilOverlay.RestrictedRange.cs index 9581fec37b..d29564caa9 100644 --- a/Content.Client/Overlays/StencilOverlay.RestrictedRange.cs +++ b/Content.Client/Overlays/StencilOverlay.RestrictedRange.cs @@ -7,7 +7,7 @@ namespace Content.Client.Overlays; public sealed partial class StencilOverlay { - private void DrawRestrictedRange(in OverlayDrawArgs args, RestrictedRangeComponent rangeComp, Matrix3 invMatrix) + private void DrawRestrictedRange(in OverlayDrawArgs args, RestrictedRangeComponent rangeComp, Matrix3x2 invMatrix) { var worldHandle = args.WorldHandle; var renderScale = args.Viewport.RenderScale.X; @@ -16,7 +16,7 @@ public sealed partial class StencilOverlay var length = zoom.X; var bufferRange = MathF.Min(10f, rangeComp.Range); - var pixelCenter = invMatrix.Transform(rangeComp.Origin); + var pixelCenter = Vector2.Transform(rangeComp.Origin, invMatrix); // Something something offset? var vertical = args.Viewport.Size.Y; @@ -44,7 +44,7 @@ public sealed partial class StencilOverlay worldHandle.DrawRect(localAABB, Color.White); }, 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; 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/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/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..f1473bda7a 100644 --- a/Content.Client/StatusIcon/StatusIconOverlay.cs +++ b/Content.Client/StatusIcon/StatusIconOverlay.cs @@ -39,8 +39,8 @@ 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)) @@ -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; 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/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/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.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/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.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/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/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/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/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/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/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/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/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.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/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/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/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; From 0f8b1bbde50f8cddfaad74f4d0229e93c6ca657f Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sun, 2 Jun 2024 16:08:15 +1200 Subject: [PATCH 018/158] Content changes for entity categories PR (#27232) * Content changes for entity categories PR * Poke tests * Why are tests suddenly working? --- Content.IntegrationTests/Tests/EntityTest.cs | 11 +++-------- .../Rules/Components/NukeOperativeSpawnerComponent.cs | 5 +++-- .../Roles/Components/GhostRoleMobSpawnerComponent.cs | 2 +- .../Components/RandomHumanoidSpawnerComponent.cs | 2 +- .../Components/ConditionalSpawnerComponent.cs | 2 +- .../Spawners/Components/RandomSpawnerComponent.cs | 2 +- .../Spawners/Components/TimedSpawnerComponent.cs | 2 +- Content.Shared/Actions/BaseActionComponent.cs | 4 +++- .../Body/Prototypes/BodyPrototypeSerializer.cs | 2 +- Content.Shared/Dragon/SharedDragonRiftComponent.cs | 3 ++- Resources/Locale/en-US/entity-categories.ftl | 1 + Resources/Prototypes/Alerts/alerts.yml | 2 +- Resources/Prototypes/Alerts/revenant.yml | 2 +- .../Prototypes/Entities/Objects/Devices/flatpack.yml | 2 +- .../Entities/Structures/Machines/flatpacker.yml | 2 +- Resources/Prototypes/Entities/categories.yml | 4 ++++ 16 files changed, 26 insertions(+), 22 deletions(-) create mode 100644 Resources/Locale/en-US/entity-categories.ftl create mode 100644 Resources/Prototypes/Entities/categories.yml diff --git a/Content.IntegrationTests/Tests/EntityTest.cs b/Content.IntegrationTests/Tests/EntityTest.cs index 54af64122b..926374cf05 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(); 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/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/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/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/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.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/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/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/Resources/Locale/en-US/entity-categories.ftl b/Resources/Locale/en-US/entity-categories.ftl new file mode 100644 index 0000000000..190fe5713a --- /dev/null +++ b/Resources/Locale/en-US/entity-categories.ftl @@ -0,0 +1 @@ +entity-category-name-actions = Actions 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/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/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/categories.yml b/Resources/Prototypes/Entities/categories.yml new file mode 100644 index 0000000000..2fb56818f9 --- /dev/null +++ b/Resources/Prototypes/Entities/categories.yml @@ -0,0 +1,4 @@ +- type: entityCategory + id: Actions + name: entity-category-name-actions + hideSpawnMenu: true From 2be02a1bc4dd773ddf9c0112667ae931f03eb35a Mon Sep 17 00:00:00 2001 From: Cojoke <83733158+Cojoke-dot@users.noreply.github.com> Date: Sat, 1 Jun 2024 23:13:12 -0500 Subject: [PATCH 019/158] Fix Lasers Being Blocked by External Airlocks and Shuttle Airlocks (#28065) --- .../Structures/Doors/Airlocks/external.yml | 11 +++++ .../Structures/Doors/Airlocks/shuttle.yml | 47 +++---------------- 2 files changed, 17 insertions(+), 41 deletions(-) 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 From 597d8f073fd41fdeacd6426c57d44066c97dff6a Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 2 Jun 2024 04:14:18 +0000 Subject: [PATCH 020/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 0c671f49ee..de9e112c59 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- 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. @@ -3853,3 +3846,10 @@ 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 From 7f5be661f8b8f3be04965449f720fcb67b66aef2 Mon Sep 17 00:00:00 2001 From: slarticodefast <161409025+slarticodefast@users.noreply.github.com> Date: Sun, 2 Jun 2024 06:17:53 +0200 Subject: [PATCH 021/158] Flash overlay rework and bugfixes (#27369) --- Content.Client/Entry/EntryPoint.cs | 1 - Content.Client/Flash/FlashOverlay.cs | 78 +++++++++------ Content.Client/Flash/FlashSystem.cs | 99 ++++++++++--------- .../Components/DamagedByFlashingComponent.cs | 3 +- .../Components/FlashImmunityComponent.cs | 19 ++-- Content.Server/Flash/FlashSystem.cs | 84 ++++++++-------- .../Flash/Components/FlashComponent.cs | 11 ++- .../Flash/Components/FlashedComponent.cs | 9 ++ Content.Shared/Flash/FlashableComponent.cs | 40 -------- Content.Shared/Flash/SharedFlashSystem.cs | 15 +-- .../Mobs/Cyborgs/base_borg_chassis.yml | 2 +- .../Entities/Mobs/NPCs/argocyte.yml | 1 - .../Prototypes/Entities/Mobs/NPCs/pets.yml | 2 - .../Entities/Mobs/NPCs/simplemob.yml | 2 + .../Prototypes/Entities/Mobs/Species/base.yml | 25 +---- .../Entities/Objects/base_shadow.yml | 3 - Resources/Prototypes/status_effects.yml | 3 + .../Textures/Shaders/flashed_effect.swsl | 2 +- 18 files changed, 189 insertions(+), 210 deletions(-) create mode 100644 Content.Shared/Flash/Components/FlashedComponent.cs delete mode 100644 Content.Shared/Flash/FlashableComponent.cs 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/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.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.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/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/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/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/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/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/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/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); From 3841762dd2a8200926fcf4db9254189dce0c95d2 Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 2 Jun 2024 04:18:59 +0000 Subject: [PATCH 022/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index de9e112c59..a6e7cbbc71 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,13 +1,4 @@ Entries: -- 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. @@ -3853,3 +3844,12 @@ 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 From fcb6ce0b5becb908df27900c6701a495ec938416 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sun, 2 Jun 2024 16:26:42 +1200 Subject: [PATCH 023/158] Add debug asserts to ensure that network groups are up to date (#28495) --- .../Power/Pow3r/BatteryRampPegSolver.cs | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs b/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs index 0afd86679b..12118968b7 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; @@ -37,6 +38,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) @@ -321,9 +323,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; From 030af17f9c167b55e0dec0ca58538ded24464b92 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sun, 2 Jun 2024 16:27:48 +1200 Subject: [PATCH 024/158] Update engine to v224.0.0 (#28502) --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index c89c529ba4..ff4548f108 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit c89c529ba4fa7516e2265f3c47549da35ab6c3d8 +Subproject commit ff4548f108a5b11e1a81cb7c01f7462400e4af2b From c1bc4c9182df45c8b5a999f6ca95dda4b5687981 Mon Sep 17 00:00:00 2001 From: null <56081759+NullWanderer@users.noreply.github.com> Date: Thu, 6 Jun 2024 21:50:25 +0200 Subject: [PATCH 025/158] Matrix3x2 --- Content.Client/DeltaV/Overlays/UltraVisionOverlay.cs | 3 ++- Content.Client/Nyanotrasen/Overlays/DogVisionOverlay.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) 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/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 From f8b3a797be93be43aec3706be8e0b34b6abc6184 Mon Sep 17 00:00:00 2001 From: Plykiya <58439124+Plykiya@users.noreply.github.com> Date: Sat, 1 Jun 2024 23:21:07 -0700 Subject: [PATCH 026/158] Emergency Fix for Whitelist logic (#28510) fix issue Co-authored-by: plykiya --- Content.Shared/Materials/SharedMaterialStorageSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Shared/Materials/SharedMaterialStorageSystem.cs b/Content.Shared/Materials/SharedMaterialStorageSystem.cs index af815bdb2b..a27e0fb9cf 100644 --- a/Content.Shared/Materials/SharedMaterialStorageSystem.cs +++ b/Content.Shared/Materials/SharedMaterialStorageSystem.cs @@ -123,7 +123,7 @@ public abstract class SharedMaterialStorageSystem : EntitySystem if (!CanTakeVolume(uid, volume, component)) return false; - if (component.MaterialWhiteList == null ? false : component.MaterialWhiteList.Contains(materialId)) + if (component.MaterialWhiteList == null ? false : !component.MaterialWhiteList.Contains(materialId)) return false; var amount = component.Storage.GetValueOrDefault(materialId); From 3fccabbd0aa5e81f158949216755e42b70e49c1e Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+electrojr@users.noreply.github.com> Date: Sun, 2 Jun 2024 20:11:48 +1200 Subject: [PATCH 027/158] Remove `noSpawn: true` from action entity prototypes (#28508) --- Resources/Prototypes/Actions/borgs.yml | 1 - Resources/Prototypes/Actions/crit.yml | 3 --- Resources/Prototypes/Actions/diona.yml | 2 -- Resources/Prototypes/Actions/internals.yml | 1 - Resources/Prototypes/Actions/mech.yml | 3 --- Resources/Prototypes/Actions/ninja.yml | 6 ----- Resources/Prototypes/Actions/polymorph.yml | 4 --- Resources/Prototypes/Actions/revenant.yml | 5 ---- Resources/Prototypes/Actions/speech.yml | 1 - Resources/Prototypes/Actions/spider.yml | 2 -- Resources/Prototypes/Actions/types.yml | 27 ------------------- Resources/Prototypes/DeltaV/Actions/types.yml | 1 - .../Actions/cancel-escape-inventory.yml | 1 - .../DeltaV/Entities/Mobs/Species/harpy.yml | 2 -- .../Clothing/Masks/base_clothingmask.yml | 3 +-- .../Entities/Clothing/Neck/misc.yml | 1 - .../Entities/Clothing/Shoes/magboots.yml | 5 ---- .../Entities/Clothing/Shoes/misc.yml | 1 - .../Entities/Mobs/NPCs/regalrat.yml | 6 ----- .../Entities/Mobs/Player/admin_ghost.yml | 6 ----- .../Entities/Mobs/Player/dragon.yml | 3 --- .../Entities/Mobs/Player/guardian.yml | 1 - .../Entities/Mobs/Player/observer.yml | 5 ---- .../Objects/Devices/chameleon_projector.yml | 2 -- .../Prototypes/Entities/Objects/Fun/pai.yml | 2 -- .../Objects/Specific/Chapel/bibles.yml | 1 - .../Specific/Robotics/borg_modules.yml | 1 - .../Entities/Objects/Tools/jetpacks.yml | 1 - Resources/Prototypes/Magic/event_spells.yml | 1 - .../Prototypes/Magic/forcewall_spells.yml | 1 - Resources/Prototypes/Magic/knock_spell.yml | 1 - .../Prototypes/Magic/projectile_spells.yml | 3 --- Resources/Prototypes/Magic/rune_spells.yml | 4 --- Resources/Prototypes/Magic/smite_spells.yml | 1 - Resources/Prototypes/Magic/spawn_spells.yml | 1 - Resources/Prototypes/Magic/staves.yml | 1 - .../Prototypes/Magic/teleport_spells.yml | 1 - Resources/Prototypes/Magic/utility_spells.yml | 1 - .../Prototypes/Nyanotrasen/Actions/types.yml | 15 ----------- .../Prototypes/Roles/Jobs/Civilian/mime.yml | 1 - 40 files changed, 1 insertion(+), 127 deletions(-) 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/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/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/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/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/Player/admin_ghost.yml b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml index 314c57b141..92e2885c9b 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml @@ -103,7 +103,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 +115,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 +127,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 +139,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 +151,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 +163,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/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/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/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/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/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/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/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/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 From c02cd51ccc0134656c75454c5f289bf8dab5370b Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Mon, 3 Jun 2024 02:59:22 +1200 Subject: [PATCH 028/158] Update engine to v224.0.1 (#28520) --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index ff4548f108..f648218756 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit ff4548f108a5b11e1a81cb7c01f7462400e4af2b +Subproject commit f64821875655dfec4e2c951d5c22c0db873fe86e From ee78e157183b44730a5d6806678a7f71c0255ead Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 2 Jun 2024 16:53:47 +0000 Subject: [PATCH 029/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index a6e7cbbc71..0bfd57a3b9 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- 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. @@ -3853,3 +3846,10 @@ 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 From ffbaeb0deee4b599a72b6e0f50d1709ce0d31ae4 Mon Sep 17 00:00:00 2001 From: Errant <35878406+Errant-4@users.noreply.github.com> Date: Sun, 2 Jun 2024 19:30:27 +0200 Subject: [PATCH 030/158] Beacons no longer glitch off on grid split (#28518) --- Content.Server/Pinpointer/NavMapSystem.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) 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); From 955f8fe4440870740ddf91c88754fc50c2e61baf Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 2 Jun 2024 17:31:33 +0000 Subject: [PATCH 031/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 0bfd57a3b9..ec5569327f 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- 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. @@ -3853,3 +3846,10 @@ 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 From 92da2ec1eeba5c8eacc43e8d1e20c945d2406689 Mon Sep 17 00:00:00 2001 From: UBlueberry <161545003+UBlueberry@users.noreply.github.com> Date: Sun, 2 Jun 2024 15:13:57 -0400 Subject: [PATCH 032/158] Drawl capitalization coldfix (part 2: rise of accidentally commiting to master) (#26639) fixed finally yay --- .../EntitySystems/SouthernAccentSystem.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) 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; } }; From d2e81a0188a07960acdb3d307d7378011466e806 Mon Sep 17 00:00:00 2001 From: Killerqu00 <47712032+Killerqu00@users.noreply.github.com> Date: Sun, 2 Jun 2024 21:40:02 +0200 Subject: [PATCH 033/158] fix typos in bonfire descriptions (#28515) * fix typo * a --- Resources/Prototypes/Entities/Structures/Decoration/bonfire.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 4ef351a24cf637ab7cee612d3e59203ac48b98e1 Mon Sep 17 00:00:00 2001 From: stellar-novas Date: Sun, 2 Jun 2024 15:49:31 -0400 Subject: [PATCH 034/158] Update to nixpkgs 24.05 (#28529) --- flake.lock | 14 +++++++------- flake.nix | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) 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 }: From 983cdb2fa7161b4f7cd8a7e90ed17580134c8e80 Mon Sep 17 00:00:00 2001 From: Voomra Date: Mon, 3 Jun 2024 02:51:19 +0300 Subject: [PATCH 035/158] fix: localize PraySystem UI (#28535) tweak: localize PraySystem UI --- Content.Server/Prayer/PrayerSystem.cs | 2 +- Resources/Locale/en-US/prayers/prayers.ftl | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) 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/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 From 74e03ec71cc500ac79475d89cc23612b1ce934bd Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 2 Jun 2024 23:52:25 +0000 Subject: [PATCH 036/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index ec5569327f..53f9f65ab3 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- 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. @@ -3853,3 +3846,10 @@ id: 6666 time: '2024-06-02T17:30:27.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28518 +- author: Voomra + changes: + - message: localize Pray UI + type: Fix + id: 6667 + time: '2024-06-02T23:51:19.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28535 From 6caa933d60dd3332ea04548c4d2cdbacade6fb26 Mon Sep 17 00:00:00 2001 From: Whisper <121047731+QuietlyWhisper@users.noreply.github.com> Date: Sun, 2 Jun 2024 22:18:49 -0400 Subject: [PATCH 037/158] Rename admin cloak to weh cloak (#28540) --- Resources/Prototypes/Entities/Clothing/Neck/cloaks.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 From 2845a8aebd337848a6e1bf4298b5328e0f618a55 Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 3 Jun 2024 02:19:55 +0000 Subject: [PATCH 038/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 53f9f65ab3..b602964081 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- 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. @@ -3853,3 +3846,10 @@ id: 6667 time: '2024-06-02T23:51:19.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28535 +- author: Whisper + changes: + - message: Renamed the player-obtainable "admin cloak" to "weh cloak". + type: Tweak + id: 6668 + time: '2024-06-03T02:18:49.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28540 From d94a7bb8a0f2531fc0b2e6b658453b2e8cf8f8b8 Mon Sep 17 00:00:00 2001 From: AJCM-git <60196617+AJCM-git@users.noreply.github.com> Date: Sun, 2 Jun 2024 23:28:38 -0400 Subject: [PATCH 039/158] RespawnRuleSystem tweaks. (#28528) --- .../Components/RespawnDeadRuleComponent.cs | 5 + .../Components/RespawnTrackerComponent.cs | 12 +- .../GameTicking/Rules/DeathMatchRuleSystem.cs | 4 +- .../GameTicking/Rules/RespawnRuleSystem.cs | 115 +++++++++--------- Resources/Prototypes/GameRules/roundstart.yml | 11 ++ 5 files changed, 84 insertions(+), 63 deletions(-) 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/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 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/Resources/Prototypes/GameRules/roundstart.yml b/Resources/Prototypes/GameRules/roundstart.yml index d67038e6ad..a814347224 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 From 22c7f4e6100b19d263ae5eb50c0281383e3a837e Mon Sep 17 00:00:00 2001 From: Verm <32827189+Vermidia@users.noreply.github.com> Date: Sun, 2 Jun 2024 22:28:53 -0500 Subject: [PATCH 040/158] Welding tweaks (#27959) --- .../Configurable/ConfigurationSystem.cs | 6 +- .../DamageOnToolInteractComponent.cs | 27 +++---- .../Systems/DamageOnToolInteractSystem.cs | 5 +- .../Mech/Systems/MechAssemblySystem.cs | 4 +- Content.Server/Mech/Systems/MechSystem.cs | 4 +- .../PneumaticCannon/PneumaticCannonSystem.cs | 8 +- .../Melee/EnergySword/EnergySwordSystem.cs | 6 +- .../ArtifactElectricityTriggerSystem.cs | 4 +- .../EntitySystems/AnchorableSystem.cs | 2 +- .../EntitySystems/EncryptionKeySystem.cs | 4 +- .../Tools/Components/ToolComponent.cs | 75 ++++++++----------- .../Components/ToolRefinableComponent.cs | 11 +-- .../Tools/Systems/SharedToolSystem.Welder.cs | 22 +++--- .../Tools/Systems/SharedToolSystem.cs | 3 +- .../Tools/Systems/ToolRefinableSystem.cs | 20 +++-- .../Prototypes/Entities/Mobs/NPCs/animals.yml | 2 +- .../Prototypes/Entities/Mobs/NPCs/xeno.yml | 2 +- .../Consumable/Drinks/drinks_bottles.yml | 2 +- .../Objects/Consumable/Drinks/drinks_cans.yml | 2 +- .../Entities/Objects/Materials/shards.yml | 10 +-- .../Entities/Objects/Misc/broken_bottle.yml | 2 +- .../Objects/Misc/fire_extinguisher.yml | 2 +- .../Entities/Objects/Power/lights.yml | 14 ++-- .../Objects/Specific/Medical/surgery.yml | 8 +- .../Entities/Objects/Tools/cowtools.yml | 10 +-- .../Entities/Objects/Tools/gas_tanks.yml | 2 +- .../Entities/Objects/Tools/jaws_of_life.yml | 4 +- .../Entities/Objects/Tools/tools.yml | 4 +- .../Entities/Objects/Tools/welders.yml | 4 +- .../Objects/Weapons/Melee/baseball_bat.yml | 2 +- .../Structures/Storage/Tanks/tanks.yml | 4 +- .../Structures/Wallmounts/walldispenser.yml | 3 +- .../XenoArch/Effects/utility_effects.yml | 2 +- 33 files changed, 139 insertions(+), 141 deletions(-) rename Content.Server/Construction/Components/WelderRefinableComponent.cs => Content.Shared/Tools/Components/ToolRefinableComponent.cs (74%) rename Content.Server/Construction/RefiningSystem.cs => Content.Shared/Tools/Systems/ToolRefinableSystem.cs (59%) 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/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/Mech/Systems/MechAssemblySystem.cs b/Content.Server/Mech/Systems/MechAssemblySystem.cs index c1fff819b4..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; @@ -15,6 +16,7 @@ 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() @@ -30,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) { diff --git a/Content.Server/Mech/Systems/MechSystem.cs b/Content.Server/Mech/Systems/MechSystem.cs index 53c6c62cdb..68b973f588 100644 --- a/Content.Server/Mech/Systems/MechSystem.cs +++ b/Content.Server/Mech/Systems/MechSystem.cs @@ -17,6 +17,7 @@ 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; @@ -35,6 +36,7 @@ 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 SharedToolSystem _toolSystem = default!; /// public override void Initialize() @@ -87,7 +89,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) 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/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/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/Construction/EntitySystems/AnchorableSystem.cs b/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs index e9ef053f62..efb5dfd024 100644 --- a/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs +++ b/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs @@ -79,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; 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/Tools/Components/ToolComponent.cs b/Content.Shared/Tools/Components/ToolComponent.cs index 92857ab905..a7210c6fa0 100644 --- a/Content.Shared/Tools/Components/ToolComponent.cs +++ b/Content.Shared/Tools/Components/ToolComponent.cs @@ -1,52 +1,43 @@ +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))] +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/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 86d57f7dc1..f4f57ac451 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -2252,7 +2252,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/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/Objects/Consumable/Drinks/drinks_bottles.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_bottles.yml index 35ad43586b..7287119019 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 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/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/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/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/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/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/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/Structures/Storage/Tanks/tanks.yml b/Resources/Prototypes/Entities/Structures/Storage/Tanks/tanks.yml index 934298b620..df19550cdb 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Tanks/tanks.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Tanks/tanks.yml @@ -25,8 +25,7 @@ - type: ReagentTank tankType: Fuel - type: DamageOnToolInteract - tools: - - Welding + tools: Welding weldingDamage: types: Heat: 10 @@ -217,4 +216,3 @@ fillBaseName: watertank-2- - type: ExaminableSolution solution: tank - 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/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: From 5c140c209578acbbefd76d30de87ca86c776aa8d Mon Sep 17 00:00:00 2001 From: AJCM-git <60196617+AJCM-git@users.noreply.github.com> Date: Sun, 2 Jun 2024 23:30:00 -0400 Subject: [PATCH 041/158] Strip Items From Things Before Biomassing Them (#28544) --- .../Medical/BiomassReclaimer/BiomassReclaimerSystem.cs | 9 +++++++++ 1 file changed, 9 insertions(+) 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); } From 66d915fc754456f268c8f69a12e73f2c47b5d283 Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 3 Jun 2024 03:29:59 +0000 Subject: [PATCH 042/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index b602964081..3086e6a71c 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- 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. @@ -3853,3 +3846,10 @@ id: 6668 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: 6669 + time: '2024-06-03T03:28:53.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/27959 From ba02e1d94ad15a0216545866bf3ed7cf4272cefa Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 3 Jun 2024 03:31:08 +0000 Subject: [PATCH 043/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 3086e6a71c..941b933136 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,14 +1,4 @@ Entries: -- 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. @@ -3853,3 +3843,11 @@ id: 6669 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: 6670 + time: '2024-06-03T03:30:00.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28544 From 4360e679a0c055f48420986660bc449c7fcdb01e Mon Sep 17 00:00:00 2001 From: AJCM-git <60196617+AJCM-git@users.noreply.github.com> Date: Sun, 2 Jun 2024 23:33:30 -0400 Subject: [PATCH 044/158] Header of Gas tank UI screen now fetched from Entity name and local (#28545) --- .../GasTank/GasTankBoundUserInterface.cs | 3 +- .../Systems/Atmos/GasTank/GasTankWindow.cs | 339 +++++++++--------- .../Locale/en-US/atmos/gas-tank-component.ftl | 1 - 3 files changed, 167 insertions(+), 176 deletions(-) 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/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 From fef44cbf4e9fe14218052f1046b2ab5bc50d860d Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 3 Jun 2024 03:34:36 +0000 Subject: [PATCH 045/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 941b933136..f30481fee7 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- 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. @@ -3851,3 +3844,10 @@ id: 6670 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: 6671 + time: '2024-06-03T03:33:31.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28545 From 3efeaf609de0048ddd312e9673d6915d4988c3c9 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Mon, 3 Jun 2024 01:12:13 -0400 Subject: [PATCH 046/158] Add support for HeldPrefix to SolutionContainerVisualsSystem (#28532) --- .../Visualizers/SolutionContainerVisualsSystem.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 Date: Mon, 3 Jun 2024 17:21:46 +1200 Subject: [PATCH 047/158] Add PrototypeUploadTest (#28522) --- .../PrototypeTests/PrototypeUploadTest.cs | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 Content.IntegrationTests/Tests/PrototypeTests/PrototypeUploadTest.cs 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(); + } +} From 60db15ed6c641b8d81b093dad5be26c3211500af Mon Sep 17 00:00:00 2001 From: Verm <32827189+Vermidia@users.noreply.github.com> Date: Mon, 3 Jun 2024 06:54:32 -0500 Subject: [PATCH 048/158] Changes SyndiCat spawner to a radio (#28492) --- Resources/Locale/en-US/store/uplink-catalog.ftl | 4 ++-- Resources/Prototypes/Catalog/uplink_catalog.yml | 2 +- .../reinforcement_teleporter.yml | 17 +++++++++++++++++ 3 files changed, 20 insertions(+), 3 deletions(-) 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/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/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 From 7d2715889903f234abd0856ab5f96b44f35f2278 Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 3 Jun 2024 11:55:38 +0000 Subject: [PATCH 049/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index f30481fee7..7235a60a0d 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- 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 @@ -3851,3 +3844,10 @@ id: 6671 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: 6672 + time: '2024-06-03T11:54:32.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28492 From 7c8d62900929d7c431692d39798079c26d2317fb Mon Sep 17 00:00:00 2001 From: Cojoke <83733158+Cojoke-dot@users.noreply.github.com> Date: Mon, 3 Jun 2024 07:05:14 -0500 Subject: [PATCH 050/158] Gives Medibots Random Lines(not a good title) (#28543) --- Resources/Locale/en-US/advertisements/other/medibot.ftl | 9 +++++++++ .../Catalog/VendingMachines/advertisements.yml | 6 ++++++ Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml | 2 ++ 3 files changed, 17 insertions(+) create mode 100644 Resources/Locale/en-US/advertisements/other/medibot.ftl 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/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/Entities/Mobs/NPCs/silicon.yml b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml index b2fefb67ae..12788e457f 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml @@ -287,6 +287,8 @@ interactFailureString: petting-failure-medibot interactSuccessSound: path: /Audio/Ambience/Objects/periodic_beep.ogg + - type: Advertise + pack: MedibotAds - type: entity parent: MobSiliconBase From c1b8a93dacc3d39fae4531f410363d2b276b50a1 Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 3 Jun 2024 12:06:20 +0000 Subject: [PATCH 051/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 7235a60a0d..784f6f0de0 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- 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 @@ -3851,3 +3844,10 @@ id: 6672 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: 6673 + time: '2024-06-03T12:05:14.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28543 From 186e466f776bafedb77c0725788a2f74b4fa42e0 Mon Sep 17 00:00:00 2001 From: Cojoke <83733158+Cojoke-dot@users.noreply.github.com> Date: Mon, 3 Jun 2024 07:10:24 -0500 Subject: [PATCH 052/158] Adds Chat Triggers to the Gasp Emote (#28466) --- Resources/Prototypes/Voice/speech_emotes.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) 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 From 891c7acfc2af3a397e3f609e665e89b739876f68 Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 3 Jun 2024 12:11:31 +0000 Subject: [PATCH 053/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 784f6f0de0..91acfe3296 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- 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 @@ -3851,3 +3844,10 @@ id: 6673 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: 6674 + time: '2024-06-03T12:10:25.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28466 From 64329d6173a05711d15b04abbdcee50f913972d4 Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Mon, 3 Jun 2024 12:34:50 +0000 Subject: [PATCH 054/158] prevent nukie kidnapping (#28387) --- Content.Server/Antag/AntagSelectionSystem.cs | 21 +++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/Content.Server/Antag/AntagSelectionSystem.cs b/Content.Server/Antag/AntagSelectionSystem.cs index 47da1b6475..710bb8f3d7 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) { From aaa96befa144b0bbf5fc1a54fdfef53e4acfb16d Mon Sep 17 00:00:00 2001 From: Cojoke <83733158+Cojoke-dot@users.noreply.github.com> Date: Mon, 3 Jun 2024 08:04:07 -0500 Subject: [PATCH 055/158] Make projectiles not hit crates unless clicked on (#28072) --- .../RequireProjectileTargetComponent.cs | 14 +++++ .../Systems/RequireProjectileTargetSystem.cs | 51 +++++++++++++++++++ .../Systems/MobStateSystem.Subscribers.cs | 17 ------- Resources/Prototypes/Entities/Mobs/base.yml | 4 +- .../Storage/Crates/base_structurecrates.yml | 1 + 5 files changed, 69 insertions(+), 18 deletions(-) create mode 100644 Content.Shared/Damage/Components/RequireProjectileTargetComponent.cs create mode 100644 Content.Shared/Damage/Systems/RequireProjectileTargetSystem.cs 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/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/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/Resources/Prototypes/Entities/Mobs/base.yml b/Resources/Prototypes/Entities/Mobs/base.yml index 0a2b68d0a1..fae4711310 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/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 From 658b49f26a3ec1c687b5e27df1b47490c2e39011 Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 3 Jun 2024 13:05:14 +0000 Subject: [PATCH 056/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 91acfe3296..0587709ee3 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- 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 @@ -3851,3 +3844,13 @@ id: 6674 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: 6675 + time: '2024-06-03T13:04:07.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28072 From bf146480f938741eb11da307f3f3579ea340a666 Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Mon, 3 Jun 2024 15:23:52 +0200 Subject: [PATCH 057/158] make objectives use entityCategory (#28269) --- .../Components/ObjectiveComponent.cs | 10 +++-- Resources/Locale/en-US/entity-categories.ftl | 1 + .../DeltaV/Objectives/paradox_anomaly.yml | 3 -- .../Prototypes/DeltaV/Objectives/traitor.yml | 3 -- Resources/Prototypes/Entities/categories.yml | 5 +++ .../Nyanotrasen/Objectives/traitor.yml | 4 -- .../Prototypes/Objectives/base_objectives.yml | 6 +-- Resources/Prototypes/Objectives/dragon.yml | 2 - Resources/Prototypes/Objectives/ninja.yml | 6 --- Resources/Prototypes/Objectives/thief.yml | 41 ------------------- Resources/Prototypes/Objectives/traitor.yml | 19 --------- 11 files changed, 15 insertions(+), 85 deletions(-) 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/Resources/Locale/en-US/entity-categories.ftl b/Resources/Locale/en-US/entity-categories.ftl index 190fe5713a..e971990e6c 100644 --- a/Resources/Locale/en-US/entity-categories.ftl +++ b/Resources/Locale/en-US/entity-categories.ftl @@ -1 +1,2 @@ entity-category-name-actions = Actions +entity-category-name-objectives = Objectives 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 4fa25f2698..179f66e06d 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/Entities/categories.yml b/Resources/Prototypes/Entities/categories.yml index 2fb56818f9..d75b8feedb 100644 --- a/Resources/Prototypes/Entities/categories.yml +++ b/Resources/Prototypes/Entities/categories.yml @@ -2,3 +2,8 @@ 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/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/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: From d58b6b49dc3e73ab0ac3297807e9183d0698e795 Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Mon, 3 Jun 2024 06:30:47 -0700 Subject: [PATCH 058/158] Add more info to GettingAttackedAttemptEvent (#28548) --- Content.Shared/ActionBlocker/ActionBlockerSystem.cs | 6 +----- .../Interaction/Events/GettingAttackedAttemptEvent.cs | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) 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/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); From 792afce5cbc0370b346913a4da2b23b2a21e1b0a Mon Sep 17 00:00:00 2001 From: null <56081759+NullWanderer@users.noreply.github.com> Date: Thu, 6 Jun 2024 22:16:17 +0200 Subject: [PATCH 059/158] Some tool related fixes --- Content.Shared/Tools/Components/ToolComponent.cs | 3 ++- .../Entities/Objects/Weapons/Melee/breaching_hammer.yml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Content.Shared/Tools/Components/ToolComponent.cs b/Content.Shared/Tools/Components/ToolComponent.cs index a7210c6fa0..58d850c10a 100644 --- a/Content.Shared/Tools/Components/ToolComponent.cs +++ b/Content.Shared/Tools/Components/ToolComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.Nyanotrasen.Abilities.Oni; using Content.Shared.Tools.Systems; using Robust.Shared.Audio; using Robust.Shared.GameStates; @@ -6,7 +7,7 @@ using Robust.Shared.Utility; namespace Content.Shared.Tools.Components; [RegisterComponent, NetworkedComponent] -[Access(typeof(SharedToolSystem))] +[Access(typeof(SharedToolSystem), typeof(SharedOniSystem))] // DeltaV - Allowed OniSystem access public sealed partial class ToolComponent : Component { [DataField] 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 From 316c8064db2d5bd36ff0d6c9e099ce10c9eb1a35 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Tue, 4 Jun 2024 01:36:07 +1200 Subject: [PATCH 060/158] Try fix KeyNotFoundException in KillTrackingSystem (#28553) --- .../KillTracking/KillTrackingSystem.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) 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; - } } } From a5dcfbf961321cc5a8e92b6304e1749a82416d76 Mon Sep 17 00:00:00 2001 From: lzk <124214523+lzk228@users.noreply.github.com> Date: Mon, 3 Jun 2024 15:37:30 +0200 Subject: [PATCH 061/158] Remove locale related changelog (#28547) --- Resources/Changelog/Changelog.yml | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 0587709ee3..cab645fa8d 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -3787,25 +3787,18 @@ id: 6666 time: '2024-06-02T17:30:27.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28518 -- author: Voomra - changes: - - message: localize Pray UI - type: Fix - id: 6667 - time: '2024-06-02T23:51:19.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/28535 - author: Whisper changes: - message: Renamed the player-obtainable "admin cloak" to "weh cloak". type: Tweak - id: 6668 + 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: 6669 + 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 @@ -3813,35 +3806,35 @@ - message: Biomass reclaimers no longer act as a void to any unfortunate belongings a corpse may be wearing type: Tweak - id: 6670 + 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: 6671 + 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: 6672 + 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: 6673 + 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: 6674 + 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 @@ -3851,6 +3844,6 @@ - message: Bullets now pass over mobs that are lying down unless directly clicked on while shooting. type: Tweak - id: 6675 + id: 6674 time: '2024-06-03T13:04:07.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28072 From 426896a432c149d04ae59b93708de3ed0d9331cf Mon Sep 17 00:00:00 2001 From: AJCM-git <60196617+AJCM-git@users.noreply.github.com> Date: Mon, 3 Jun 2024 12:12:21 -0400 Subject: [PATCH 062/158] Cleans up StatusIconSystem and fixing some bugs (#28270) --- .../UI/AgentIDCardBoundUserInterface.cs | 4 +- .../Access/UI/AgentIDCardWindow.xaml.cs | 8 +-- Content.Client/Antag/AntagStatusIconSystem.cs | 55 ------------------- .../Commands/ShowHealthBarsCommand.cs | 1 + Content.Client/LateJoin/LateJoinGui.cs | 2 +- .../Lobby/UI/HumanoidProfileEditor.xaml.cs | 2 +- .../Overlays/EntityHealthBarOverlay.cs | 26 +++++---- .../Overlays/ShowCriminalRecordIconsSystem.cs | 4 +- .../Overlays/ShowHealthBarsSystem.cs | 14 ++++- .../Overlays/ShowHealthIconsSystem.cs | 3 +- .../Overlays/ShowHungerIconsSystem.cs | 2 +- Content.Client/Overlays/ShowJobIconsSystem.cs | 2 +- .../Overlays/ShowMindShieldIconsSystem.cs | 4 +- .../Overlays/ShowSyndicateIconsSystem.cs | 3 +- .../Overlays/ShowThirstIconsSystem.cs | 2 +- .../Revolutionary/RevolutionarySystem.cs | 37 +++++-------- .../SSDIndicator/SSDIndicatorSystem.cs | 3 +- .../StatusIcon/StatusIconOverlay.cs | 4 +- Content.Client/StatusIcon/StatusIconSystem.cs | 36 +++++++++++- Content.Client/Stealth/StealthSystem.cs | 3 +- Content.Client/Zombies/ZombieSystem.cs | 50 ++++++++--------- .../Access/Components/AgentIDCardComponent.cs | 6 +- .../Access/Systems/AgentIDCardSystem.cs | 8 +-- .../Access/Systems/IdCardConsoleSystem.cs | 2 +- .../Access/Systems/PresetIdCardSystem.cs | 4 +- .../Revolutionary/RevolutionarySystem.cs | 5 ++ .../Station/Systems/StationSpawningSystem.cs | 4 +- .../Access/SharedAgentIDCardSystem.cs | 10 ++-- .../Antag/IAntagStatusIconComponent.cs | 12 ---- .../Antag/ShowAntagIconsComponent.cs | 9 +++ .../Overlays/ShowHealthBarsComponent.cs | 5 ++ .../Components/HeadRevolutionaryComponent.cs | 5 +- .../Components/RevolutionaryComponent.cs | 5 +- .../Components/ShowRevIconsComponent.cs | 11 ---- .../SharedRevolutionarySystem.cs | 22 +++----- Content.Shared/Roles/JobPrototype.cs | 4 +- .../SSDIndicator/SSDIndicatorComponent.cs | 5 +- .../Components/StatusIconComponent.cs | 14 +---- .../StatusIcon/StatusIconPrototype.cs | 42 +++++++++++--- .../Zombies/InitialInfectedComponent.cs | 8 +-- .../Zombies/ShowZombieIconsComponent.cs | 12 ---- Content.Shared/Zombies/ZombieComponent.cs | 5 +- .../Entities/Mobs/Player/admin_ghost.yml | 3 +- .../{ => StatusIcon}/StatusEffects/health.yml | 0 .../{ => StatusIcon}/StatusEffects/hunger.yml | 0 .../{ => StatusIcon}/StatusEffects/ssd.yml | 0 Resources/Prototypes/StatusIcon/antag.yml | 17 ++++++ .../{StatusEffects => StatusIcon}/job.yml | 0 .../security.yml | 0 49 files changed, 224 insertions(+), 259 deletions(-) delete mode 100644 Content.Client/Antag/AntagStatusIconSystem.cs create mode 100644 Content.Server/Revolutionary/RevolutionarySystem.cs delete mode 100644 Content.Shared/Antag/IAntagStatusIconComponent.cs create mode 100644 Content.Shared/Antag/ShowAntagIconsComponent.cs delete mode 100644 Content.Shared/Revolutionary/Components/ShowRevIconsComponent.cs delete mode 100644 Content.Shared/Zombies/ShowZombieIconsComponent.cs rename Resources/Prototypes/{ => StatusIcon}/StatusEffects/health.yml (100%) rename Resources/Prototypes/{ => StatusIcon}/StatusEffects/hunger.yml (100%) rename Resources/Prototypes/{ => StatusIcon}/StatusEffects/ssd.yml (100%) rename Resources/Prototypes/{StatusEffects => StatusIcon}/job.yml (100%) rename Resources/Prototypes/{StatusEffects => StatusIcon}/security.yml (100%) 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/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/Commands/ShowHealthBarsCommand.cs b/Content.Client/Commands/ShowHealthBarsCommand.cs index bd3e21718f..ef918313a0 100644 --- a/Content.Client/Commands/ShowHealthBarsCommand.cs +++ b/Content.Client/Commands/ShowHealthBarsCommand.cs @@ -35,6 +35,7 @@ public sealed class ShowHealthBarsCommand : LocalizedCommands var showHealthBarsComponent = new ShowHealthBarsComponent { DamageContainers = args.ToList(), + HealthStatusIcon = "", NetSyncEnabled = false }; diff --git a/Content.Client/LateJoin/LateJoinGui.cs b/Content.Client/LateJoin/LateJoinGui.cs index ba9351d674..252aa9aafa 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); diff --git a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs index 2b524385ec..b0fa020551 100644 --- a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs +++ b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs @@ -806,7 +806,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); diff --git a/Content.Client/Overlays/EntityHealthBarOverlay.cs b/Content.Client/Overlays/EntityHealthBarOverlay.cs index 758bb562f9..55978d98f7 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(); } @@ -44,6 +55,7 @@ public sealed class EntityHealthBarOverlay : Overlay const float scale = 1f; 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,31 +64,23 @@ 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) 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 /// 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/StatusIcon/StatusIconOverlay.cs b/Content.Client/StatusIcon/StatusIconOverlay.cs index f1473bda7a..4b3daae22f 100644 --- a/Content.Client/StatusIcon/StatusIconOverlay.cs +++ b/Content.Client/StatusIcon/StatusIconOverlay.cs @@ -45,7 +45,7 @@ public sealed class StatusIconOverlay : Overlay 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; @@ -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/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.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/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/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.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/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/Overlays/ShowHealthBarsComponent.cs b/Content.Shared/Overlays/ShowHealthBarsComponent.cs index 48e3162269..1297d56838 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/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/JobPrototype.cs b/Content.Shared/Roles/JobPrototype.cs index d28211a67f..58cda41bf8 100644 --- a/Content.Shared/Roles/JobPrototype.cs +++ b/Content.Shared/Roles/JobPrototype.cs @@ -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/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/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/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/Prototypes/Entities/Mobs/Player/admin_ghost.yml b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml index 92e2885c9b..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 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 From c03bd631e8978c19be3921fae88bea6f4398f3e1 Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 3 Jun 2024 16:13:28 +0000 Subject: [PATCH 063/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index cab645fa8d..f2d3131827 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -3847,3 +3847,11 @@ 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 From 95dd203ec698334aff48fea3b2f9927033cc6d7d Mon Sep 17 00:00:00 2001 From: AJCM-git <60196617+AJCM-git@users.noreply.github.com> Date: Mon, 3 Jun 2024 12:22:26 -0400 Subject: [PATCH 064/158] Small code cleanup to health bar (#28554) --- Content.Client/Commands/ShowHealthBarsCommand.cs | 2 +- Content.Client/Overlays/EntityHealthBarOverlay.cs | 2 +- Content.Shared/Overlays/ShowHealthBarsComponent.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Content.Client/Commands/ShowHealthBarsCommand.cs b/Content.Client/Commands/ShowHealthBarsCommand.cs index ef918313a0..0811f96663 100644 --- a/Content.Client/Commands/ShowHealthBarsCommand.cs +++ b/Content.Client/Commands/ShowHealthBarsCommand.cs @@ -35,7 +35,7 @@ public sealed class ShowHealthBarsCommand : LocalizedCommands var showHealthBarsComponent = new ShowHealthBarsComponent { DamageContainers = args.ToList(), - HealthStatusIcon = "", + HealthStatusIcon = null, NetSyncEnabled = false }; diff --git a/Content.Client/Overlays/EntityHealthBarOverlay.cs b/Content.Client/Overlays/EntityHealthBarOverlay.cs index 55978d98f7..4f92843739 100644 --- a/Content.Client/Overlays/EntityHealthBarOverlay.cs +++ b/Content.Client/Overlays/EntityHealthBarOverlay.cs @@ -33,7 +33,7 @@ public sealed class EntityHealthBarOverlay : Overlay public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV; public HashSet DamageContainers = new(); - public ProtoId StatusIcon; + public ProtoId? StatusIcon; public EntityHealthBarOverlay(IEntityManager entManager, IPrototypeManager prototype) { diff --git a/Content.Shared/Overlays/ShowHealthBarsComponent.cs b/Content.Shared/Overlays/ShowHealthBarsComponent.cs index 1297d56838..4229e27af6 100644 --- a/Content.Shared/Overlays/ShowHealthBarsComponent.cs +++ b/Content.Shared/Overlays/ShowHealthBarsComponent.cs @@ -19,5 +19,5 @@ public sealed partial class ShowHealthBarsComponent : Component public List DamageContainers = new(); [DataField] - public ProtoId HealthStatusIcon = "HealthIconFine"; + public ProtoId? HealthStatusIcon = "HealthIconFine"; } From 7b506a05cf9770d2e7a8a3383df51a3160e9d426 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Mon, 3 Jun 2024 12:24:32 -0400 Subject: [PATCH 065/158] Add a test for sliceable cargo bounty exploits (#28357) --- Content.IntegrationTests/Tests/CargoTest.cs | 79 +++++++++++++++++++ .../Cargo/Systems/CargoSystem.Bounty.cs | 17 +++- .../Prototypes/Catalog/Bounties/bounties.yml | 6 ++ 3 files changed, 101 insertions(+), 1 deletion(-) 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.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/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 From 724e5ed99e65530ee64ab8cb08132342b25e9c85 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Mon, 3 Jun 2024 18:59:59 +0200 Subject: [PATCH 066/158] Tip 51 does not exist (#28555) --- Resources/Locale/en-US/tips.ftl | 1 + 1 file changed, 1 insertion(+) 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. From 667d1d5941a9b83a1a25472fa8fd7b595a9e449e Mon Sep 17 00:00:00 2001 From: Ed <96445749+theshued@users.noreply.github.com> Date: Mon, 3 Jun 2024 20:47:06 +0200 Subject: [PATCH 067/158] Accent trait limit (#28046) --- .../Lobby/UI/HumanoidProfileEditor.xaml.cs | 113 +++++++++++++----- .../UI/Roles/TraitPreferenceSelector.xaml | 2 +- .../UI/Roles/TraitPreferenceSelector.xaml.cs | 9 +- Content.Server/Traits/TraitSystem.cs | 30 ++--- .../Preferences/HumanoidCharacterProfile.cs | 32 ++++- .../Traits/TraitCategoryPrototype.cs | 26 ++++ Content.Shared/Traits/TraitPrototype.cs | 90 +++++++------- .../ui/humanoid-profile-editor.ftl | 11 +- Resources/Locale/en-US/traits/traits.ftl | 21 +++- .../Prototypes/DeltaV/Traits/altvision.yml | 2 + .../Prototypes/DeltaV/Traits/neutral.yml | 4 +- Resources/Prototypes/Traits/categories.yml | 8 ++ Resources/Prototypes/Traits/disabilities.yml | 43 +++++-- .../Prototypes/Traits/inconveniences.yml | 18 --- Resources/Prototypes/Traits/neutral.yml | 33 ----- Resources/Prototypes/Traits/speech.yml | 88 ++++++++++++++ 16 files changed, 369 insertions(+), 161 deletions(-) create mode 100644 Content.Shared/Traits/TraitCategoryPrototype.cs create mode 100644 Resources/Prototypes/Traits/categories.yml delete mode 100644 Resources/Prototypes/Traits/inconveniences.yml delete mode 100644 Resources/Prototypes/Traits/neutral.yml create mode 100644 Resources/Prototypes/Traits/speech.yml diff --git a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs index b0fa020551..6c6d9ca977 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); + } } } 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.Server/Traits/TraitSystem.cs b/Content.Server/Traits/TraitSystem.cs index 22ee0e4861..f7531f7e2c 100644 --- a/Content.Server/Traits/TraitSystem.cs +++ b/Content.Server/Traits/TraitSystem.cs @@ -38,27 +38,21 @@ public sealed class TraitSystem : EntitySystem 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.Shared/Preferences/HumanoidCharacterProfile.cs b/Content.Shared/Preferences/HumanoidCharacterProfile.cs index 682b0d700f..e108a1b7d2 100644 --- a/Content.Shared/Preferences/HumanoidCharacterProfile.cs +++ b/Content.Shared/Preferences/HumanoidCharacterProfile.cs @@ -355,13 +355,43 @@ namespace Content.Shared.Preferences }; } - public HumanoidCharacterProfile WithTraitPreference(string traitId, bool pref) + public HumanoidCharacterProfile WithTraitPreference(string traitId, string? categoryId, bool pref) { + 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 { 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/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/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/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/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 From bed7d4f3cf3f6d3b9a20a48a1d8363886bf6bdeb Mon Sep 17 00:00:00 2001 From: RenQ <164364533+ImRenQ@users.noreply.github.com> Date: Mon, 3 Jun 2024 21:48:01 +0300 Subject: [PATCH 068/158] "Death gasp" for a moth (#28409) --- Resources/Audio/Effects/Gasp/attributions.yml | 6 ++++++ Resources/Audio/Effects/Gasp/moth_DeathGasp.ogg | Bin 0 -> 18078 bytes .../Prototypes/SoundCollections/deathgasp.yml | 5 +++++ .../Prototypes/Voice/speech_emote_sounds.yml | 2 +- 4 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 Resources/Audio/Effects/Gasp/moth_DeathGasp.ogg 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 0000000000000000000000000000000000000000..df23cfa472ac1043b1fc1eacde71a493402f9133 GIT binary patch literal 18078 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 Date: Mon, 3 Jun 2024 20:48:44 +0200 Subject: [PATCH 069/158] Make borgs shown in logprobe (#27788) --- Content.Shared/Access/Systems/AccessReaderSystem.cs | 5 +++++ 1 file changed, 5 insertions(+) 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) From 5021d65a567ef07a50865529f3fb55031d61450d Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 3 Jun 2024 18:49:52 +0000 Subject: [PATCH 070/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 51 ++++++++++++++----------------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index f2d3131827..0ae775e193 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,32 +1,4 @@ Entries: -- 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. @@ -3855,3 +3827,26 @@ 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 From d548846d6e039e670ae92e127d89337310fcc4c5 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Mon, 3 Jun 2024 15:40:46 -0400 Subject: [PATCH 071/158] Add integration test for LocalizedDatasets (#28485) --- .../LocalizedDatasetPrototypeTest.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 Content.IntegrationTests/Tests/Localization/LocalizedDatasetPrototypeTest.cs 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(); + } +} From fac7931a2a0995c71b72904c3bea333168f80427 Mon Sep 17 00:00:00 2001 From: Velcroboy <107660393+IamVelcroboy@users.noreply.github.com> Date: Mon, 3 Jun 2024 15:55:43 -0500 Subject: [PATCH 072/158] Remove duplicate entities (#28561) --- Resources/Migrations/migration.yml | 3 +++ .../Entities/Structures/Doors/Airlocks/access.yml | 9 --------- 2 files changed, 3 insertions(+), 9 deletions(-) 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/Entities/Structures/Doors/Airlocks/access.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/access.yml index 5b5bc98abf..7ee68663ea 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 From 02e876d0151939000f41a6a534b61394f6609710 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Tue, 4 Jun 2024 09:05:51 +1200 Subject: [PATCH 073/158] Add StorageInteractionTest (#28541) --- .../Tests/Chemistry/DispenserTest.cs | 2 +- .../Interaction/ComputerContruction.cs | 10 +- .../Interaction/GrilleWindowConstruction.cs | 13 +- .../Interaction/MachineConstruction.cs | 7 +- .../Construction/Interaction/PanelScrewing.cs | 12 +- .../Interaction/PlaceableDeconstruction.cs | 4 +- .../Interaction/WallConstruction.cs | 9 +- .../Interaction/WindowConstruction.cs | 8 +- .../Construction/Interaction/WindowRepair.cs | 2 +- .../Tests/DoAfter/DoAfterCancellationTests.cs | 39 +- .../EncryptionKeys/RemoveEncryptionKeys.cs | 16 +- .../InteractionTest.EntitySpecifier.cs | 6 +- .../Interaction/InteractionTest.Helpers.cs | 353 +++++++++++++----- .../Tests/Interaction/InteractionTest.cs | 29 +- .../Tests/Payload/ModularGrenadeTests.cs | 14 +- .../Tests/Storage/StorageInteractionTest.cs | 75 ++++ .../Tests/Tiles/TileConstructionTests.cs | 16 +- .../Tests/Weldable/WeldableTests.cs | 2 +- 18 files changed, 427 insertions(+), 190 deletions(-) create mode 100644 Content.IntegrationTests/Tests/Storage/StorageInteractionTest.cs 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/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/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/Storage/StorageInteractionTest.cs b/Content.IntegrationTests/Tests/Storage/StorageInteractionTest.cs new file mode 100644 index 0000000000..34402dd5e6 --- /dev/null +++ b/Content.IntegrationTests/Tests/Storage/StorageInteractionTest.cs @@ -0,0 +1,75 @@ +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 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); + + // Activating the backpack opens the UI + await Activate(); + Assert.That(IsUiOpen(StorageComponent.StorageUiKey.Key), Is.True); + Assert.That(IsUiOpen(PdaUiKey.Key), Is.False); + + // 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/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. } From 5ec48e778470a83b7cf836af857aa612cd478da4 Mon Sep 17 00:00:00 2001 From: Verm <32827189+Vermidia@users.noreply.github.com> Date: Mon, 3 Jun 2024 16:07:41 -0500 Subject: [PATCH 074/158] Fix Weh Juice and Other emote chems not working (#28562) --- Content.Server/Chat/Systems/ChatSystem.Emote.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/Chat/Systems/ChatSystem.Emote.cs b/Content.Server/Chat/Systems/ChatSystem.Emote.cs index c2bd842f83..5b3e618b41 100644 --- a/Content.Server/Chat/Systems/ChatSystem.Emote.cs +++ b/Content.Server/Chat/Systems/ChatSystem.Emote.cs @@ -86,7 +86,7 @@ public partial class ChatSystem bool ignoreActionBlocker = false ) { - if (_whitelistSystem.IsWhitelistFailOrNull(emote.Whitelist, source) || _whitelistSystem.IsBlacklistPass(emote.Blacklist, source)) + if (_whitelistSystem.IsWhitelistFail(emote.Whitelist, source) || _whitelistSystem.IsBlacklistPass(emote.Blacklist, source)) return; if (!emote.Available && From 6583105f106fef17a85dc2b53315a5a151ca7ff1 Mon Sep 17 00:00:00 2001 From: Plykiya <58439124+plykiya@users.noreply.github.com> Date: Mon, 3 Jun 2024 23:40:03 +0200 Subject: [PATCH 075/158] Replace obsolete EntityWhitelist IsValid usages part 2 (#28506) --- Content.Client/Outline/TargetOutlineSystem.cs | 3 ++- Content.Server/Chat/Systems/ChatSystem.Emote.cs | 1 + .../TileReactions/CreateEntityTileReaction.cs | 5 +++-- .../Construction/ConstructionSystem.Initial.cs | 6 ++++-- .../GameTicking/Rules/LoadMapRuleSystem.cs | 4 +++- .../VariationPass/CutWireVariationPassSystem.cs | 5 ++++- .../Holiday/Christmas/RandomGiftSystem.cs | 6 ++++-- .../Mech/Systems/MechEquipmentSystem.cs | 4 +++- Content.Server/Mech/Systems/MechSystem.cs | 4 +++- .../Ninja/Systems/StunProviderSystem.cs | 4 +++- .../EntitySystems/AnimalHusbandrySystem.cs | 6 ++++-- .../Nutrition/EntitySystems/FoodSystem.cs | 4 +++- .../ObjectiveBlacklistRequirementSystem.cs | 5 ++++- .../Objectives/Systems/RoleRequirementSystem.cs | 4 +++- .../Polymorph/Systems/PolymorphSystem.Collide.cs | 7 +++++-- Content.Server/Sticky/Systems/StickySystem.cs | 7 ++++--- .../Storage/EntitySystems/ItemCounterSystem.cs | 6 ++++-- .../Store/Conditions/BuyerWhitelistCondition.cs | 15 ++++----------- .../Store/Conditions/StoreWhitelistCondition.cs | 15 ++++----------- .../Systems/SurveillanceCameraMicrophoneSystem.cs | 5 +++-- Content.Server/Traits/TraitSystem.cs | 8 ++++---- .../Equipment/Systems/ArtifactCrusherSystem.cs | 4 +++- .../Effects/Systems/DamageNearbyArtifactSystem.cs | 6 ++++-- Content.Shared/Actions/SharedActionsSystem.cs | 4 +++- .../Buckle/SharedBuckleSystem.Buckle.cs | 7 +++++-- .../Conditions/EntityWhitelistCondition.cs | 3 ++- Content.Shared/Inventory/InventorySystem.Equip.cs | 11 ++++------- .../EntitySystems/SharedHandLabelerSystem.cs | 6 ++++-- .../Materials/SharedMaterialReclaimerSystem.cs | 8 ++++---- .../Mech/EntitySystems/SharedMechSystem.cs | 4 +++- .../Systems/SpeedModifierContactsSystem.cs | 4 +++- .../Ninja/Systems/EmagProviderSystem.cs | 3 ++- Content.Shared/Placeable/ItemPlacerSystem.cs | 6 ++++-- Content.Shared/Random/RulesSystem.cs | 5 +++-- Content.Shared/Sound/SharedEmitSoundSystem.cs | 4 +++- .../StepTrigger/Systems/StepTriggerSystem.cs | 4 +++- Content.Shared/Storage/EntitySystems/BinSystem.cs | 6 ++++-- .../EntitySystems/SharedItemMapperSystem.cs | 6 ++++-- .../Teleportation/Systems/SwapTeleporterSystem.cs | 6 ++++-- .../UserInterface/ActivatableUISystem.cs | 6 ++++-- .../Ranged/Systems/SharedGunSystem.Ballistic.cs | 3 ++- .../Ranged/Systems/SharedGunSystem.Clothing.cs | 2 +- 42 files changed, 141 insertions(+), 91 deletions(-) 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.Server/Chat/Systems/ChatSystem.Emote.cs b/Content.Server/Chat/Systems/ChatSystem.Emote.cs index 5b3e618b41..c368660459 100644 --- a/Content.Server/Chat/Systems/ChatSystem.Emote.cs +++ b/Content.Server/Chat/Systems/ChatSystem.Emote.cs @@ -86,6 +86,7 @@ public partial class ChatSystem bool ignoreActionBlocker = false ) { + if (_whitelistSystem.IsWhitelistFail(emote.Whitelist, source) || _whitelistSystem.IsBlacklistPass(emote.Blacklist, source)) return; 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/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/GameTicking/Rules/LoadMapRuleSystem.cs b/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs index 3a80d82fd9..c60cf2513e 100644 --- a/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs @@ -3,6 +3,7 @@ using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.GridPreloader; using Content.Server.Spawners.Components; +using Content.Shared.Whitelist; using Robust.Server.GameObjects; using Robust.Server.Maps; using Robust.Shared.Map; @@ -19,6 +20,7 @@ public sealed class LoadMapRuleSystem : GameRuleSystem [Dependency] private readonly MetaDataSystem _metaData = default!; [Dependency] private readonly TransformSystem _transform = default!; [Dependency] private readonly GridPreloaderSystem _gridPreloader = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -99,7 +101,7 @@ public sealed class LoadMapRuleSystem : GameRuleSystem if (xform.GridUid == null || !ent.Comp.MapGrids.Contains(xform.GridUid.Value)) continue; - if (ent.Comp.SpawnerWhitelist != null && !ent.Comp.SpawnerWhitelist.IsValid(uid, EntityManager)) + if (_whitelistSystem.IsWhitelistFail(ent.Comp.SpawnerWhitelist, uid)) continue; args.Coordinates.Add(_transform.GetMapCoordinates(xform)); 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 _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/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 68b973f588..b738d28b46 100644 --- a/Content.Server/Mech/Systems/MechSystem.cs +++ b/Content.Server/Mech/Systems/MechSystem.cs @@ -22,6 +22,7 @@ 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; @@ -36,6 +37,7 @@ 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!; /// @@ -224,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/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/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/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/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/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/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/Traits/TraitSystem.cs b/Content.Server/Traits/TraitSystem.cs index f7531f7e2c..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,10 +33,8 @@ 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 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/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.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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/Ranged/Systems/SharedGunSystem.Ballistic.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs index 1f9e6cee76..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); @@ -125,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; From edf125c4812948094dda280f8880f030fe101ff9 Mon Sep 17 00:00:00 2001 From: Plykiya <58439124+Plykiya@users.noreply.github.com> Date: Mon, 3 Jun 2024 15:42:50 -0700 Subject: [PATCH 076/158] Gets rid of obsolete EntityWhitelist.IsValid() function (#28565) Gets rid of obsolete IsValid function Co-authored-by: plykiya --- Content.Shared/Whitelist/EntityWhitelist.cs | 10 ---------- 1 file changed, 10 deletions(-) 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); - - } } From 993959ae58b65e8202635f9a561fbe2c84086f5a Mon Sep 17 00:00:00 2001 From: null <56081759+NullWanderer@users.noreply.github.com> Date: Sat, 8 Jun 2024 20:12:43 +0200 Subject: [PATCH 077/158] Replace deprecated whitelist functions --- .../DeltaV/Shipyard/UI/ShipyardBoundUserInterface.cs | 4 +++- .../DeltaV/Shipyard/UI/ShipyardConsoleMenu.xaml.cs | 5 +++-- .../Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.cs | 6 ++++-- .../Psionics/Invisibility/PsionicInvisibleContactsSystem.cs | 6 ++++-- .../DeltaV/Shipyard/SharedShipyardConsoleSystem.cs | 4 +++- 5 files changed, 17 insertions(+), 8 deletions(-) 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.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.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); From dec7298e6fb1e3dc3c7eff2b9025867f640a1789 Mon Sep 17 00:00:00 2001 From: Verm <32827189+vermidia@users.noreply.github.com> Date: Tue, 4 Jun 2024 01:45:00 +0200 Subject: [PATCH 078/158] Humans can no longer honk on command (#28566) * Humans can no longer honk on command * Undo change it emote file * I hate tabs * Some comments --- .../Chat/Systems/ChatSystem.Emote.cs | 45 ++++++++++++++----- .../Chemistry/ReagentEffects/Emote.cs | 7 ++- Resources/Prototypes/Reagents/fun.yml | 1 + Resources/Prototypes/Reagents/toxins.yml | 1 + 4 files changed, 41 insertions(+), 13 deletions(-) diff --git a/Content.Server/Chat/Systems/ChatSystem.Emote.cs b/Content.Server/Chat/Systems/ChatSystem.Emote.cs index c368660459..8d8ab442b6 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,22 +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 (_whitelistSystem.IsWhitelistFail(emote.Whitelist, source) || _whitelistSystem.IsBlacklistPass(emote.Blacklist, source)) - 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 @@ -161,18 +159,43 @@ 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; + 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/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/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: From ce5bfe3f552da93478ffdf10b089ae7da3c5853e Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Tue, 4 Jun 2024 00:04:19 +0000 Subject: [PATCH 079/158] LoadMapRule grid storage rework (#28210) * --- .../Tests/GameRules/NukeOpsTest.cs | 10 +-- .../Rules/Components/LoadMapRuleComponent.cs | 26 +++--- .../Rules/Components/RuleGridsComponent.cs | 30 +++++++ .../Rules/GameRuleSystem.Utility.cs | 4 + .../GameTicking/Rules/LoadMapRuleSystem.cs | 90 +++++++------------ .../GameTicking/Rules/NukeopsRuleSystem.cs | 12 +-- .../GameTicking/Rules/RuleGridsSystem.cs | 78 ++++++++++++++++ .../GridPreloader/GridPreloaderSystem.cs | 9 +- .../Events/StationEventSystem.cs | 9 -- Resources/Prototypes/GameRules/events.yml | 1 + Resources/Prototypes/GameRules/midround.yml | 1 - Resources/Prototypes/GameRules/roundstart.yml | 1 + .../Prototypes/GameRules/unknown_shuttles.yml | 49 +++------- 13 files changed, 190 insertions(+), 130 deletions(-) create mode 100644 Content.Server/GameTicking/Rules/Components/RuleGridsComponent.cs create mode 100644 Content.Server/GameTicking/Rules/RuleGridsSystem.cs diff --git a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs index 6e43196eb9..5ad5857849 100644 --- a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs @@ -114,8 +114,8 @@ public sealed class NukeOpsTest // 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 +129,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 +144,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(); 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/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/GameRuleSystem.Utility.cs b/Content.Server/GameTicking/Rules/GameRuleSystem.Utility.cs index cbd981e99e..9ac7a6edbb 100644 --- a/Content.Server/GameTicking/Rules/GameRuleSystem.Utility.cs +++ b/Content.Server/GameTicking/Rules/GameRuleSystem.Utility.cs @@ -126,4 +126,8 @@ public abstract partial class 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 c60cf2513e..1c09d6e86e 100644 --- a/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs @@ -1,9 +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 Content.Shared.Whitelist; using Robust.Server.GameObjects; using Robust.Server.Maps; using Robust.Shared.Map; @@ -14,97 +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!; - [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = 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 (_whitelistSystem.IsWhitelistFail(ent.Comp.SpawnerWhitelist, uid)) - 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 1b62778d75..6688bfd980 100644 --- a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs @@ -260,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; @@ -324,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); @@ -445,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, @@ -478,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/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/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/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/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 a814347224..6a9ceac682 100644 --- a/Resources/Prototypes/GameRules/roundstart.yml +++ b/Resources/Prototypes/GameRules/roundstart.yml @@ -84,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 From d188e6d340adf3db52af7738a60c7e37ef02870f Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Mon, 3 Jun 2024 20:23:43 -0400 Subject: [PATCH 080/158] Add an integration test for solution fill level sprites. (#28564) --- .../Tests/FillLevelSpriteTest.cs | 71 +++++++++++++++++++ .../Prototypes/Entities/Objects/Fun/darts.yml | 4 -- .../Entities/Objects/Specific/chemistry.yml | 4 ++ 3 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 Content.IntegrationTests/Tests/FillLevelSpriteTest.cs 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/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/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: From 66b094820fda7bb1f516d743452e7b21c1a3f2cb Mon Sep 17 00:00:00 2001 From: Ghagliiarghii <68826635+Ghagliiarghii@users.noreply.github.com> Date: Tue, 4 Jun 2024 07:57:17 -0400 Subject: [PATCH 081/158] tyvm, tysm (#28577) * tyvm, tysm * cya -> see you * ya --- Resources/Locale/en-US/speech/speech-chatsan.ftl | 9 +++++++++ Resources/Prototypes/Accents/word_replacements.yml | 3 +++ 2 files changed, 12 insertions(+) diff --git a/Resources/Locale/en-US/speech/speech-chatsan.ftl b/Resources/Locale/en-US/speech/speech-chatsan.ftl index 25e6c6f1ea..ef94507295 100644 --- a/Resources/Locale/en-US/speech/speech-chatsan.ftl +++ b/Resources/Locale/en-US/speech/speech-chatsan.ftl @@ -120,3 +120,12 @@ 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 \ No newline at end of file diff --git a/Resources/Prototypes/Accents/word_replacements.yml b/Resources/Prototypes/Accents/word_replacements.yml index 8158122f40..3567e15269 100644 --- a/Resources/Prototypes/Accents/word_replacements.yml +++ b/Resources/Prototypes/Accents/word_replacements.yml @@ -425,6 +425,9 @@ # 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 - type: accent id: liar From 772296179abd95f9182aae18eaaf39cbcfd9f05e Mon Sep 17 00:00:00 2001 From: Errant <35878406+Errant-4@users.noreply.github.com> Date: Tue, 4 Jun 2024 15:11:48 +0200 Subject: [PATCH 082/158] Don't switch-unwield if mob has more than 2 hands (#28438) --- Content.Shared/Wieldable/WieldableSystem.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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); From 2bf0e82b7a6b1721b9859eacaebc2a7a86711f96 Mon Sep 17 00:00:00 2001 From: Hannah Giovanna Dawson Date: Tue, 4 Jun 2024 14:23:09 +0100 Subject: [PATCH 083/158] SS14-26964 Clown Waddling Replicates, etc (#26983) --- .../Movement/Systems/WaddleAnimationSystem.cs | 168 ++++++++---------- .../Movement/Systems/WaddleAnimationSystem.cs | 5 + .../Components/WaddleWhenWornComponent.cs | 11 +- .../EntitySystems}/WaddleClothingSystem.cs | 17 +- .../Components/WaddleAnimationComponent.cs | 28 +-- .../Systems/SharedWaddleAnimationSystem.cs | 106 +++++++++++ 6 files changed, 213 insertions(+), 122 deletions(-) create mode 100644 Content.Server/Movement/Systems/WaddleAnimationSystem.cs rename {Content.Client/Clothing/Systems => Content.Shared/Clothing/EntitySystems}/WaddleClothingSystem.cs (59%) create mode 100644 Content.Shared/Movement/Systems/SharedWaddleAnimationSystem.cs 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.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.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.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/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))); + } +} From 842d633cb9b5334945d52e2edf014d8da49aa11b Mon Sep 17 00:00:00 2001 From: Hannah Giovanna Dawson Date: Tue, 4 Jun 2024 14:26:19 +0100 Subject: [PATCH 084/158] Make projectile & hitscan reflection only try once with the best reflector (#28539) --- .../Weapons/Reflect/ReflectSystem.cs | 258 ++++++++++-------- .../Weapons/Reflect/ReflectUserComponent.cs | 5 +- 2 files changed, 147 insertions(+), 116 deletions(-) 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; From 6b832f7098de8d2a1cdb490811ee3d780b0400ce Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 4 Jun 2024 13:27:27 +0000 Subject: [PATCH 085/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 0ae775e193..6a20b3a411 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- 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, @@ -3850,3 +3843,11 @@ 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 From 67c27e351e062b898311285ce88d5211b385690b Mon Sep 17 00:00:00 2001 From: Kevin Zheng Date: Tue, 4 Jun 2024 16:54:22 +0200 Subject: [PATCH 086/158] Add CVar to disable pow3r parallel processing (#28488) * Add CVar to disable pow3r parallel processing * Cache CVar value * Fix pointyhat * Move all CVar-ing to Content.Server --- .../Power/EntitySystems/PowerNetSystem.cs | 13 ++++++++++++- Content.Server/Power/Pow3r/BatteryRampPegSolver.cs | 9 +++++++-- Content.Shared/CCVar/CCVars.cs | 6 ++++++ 3 files changed, 25 insertions(+), 3 deletions(-) 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 12118968b7..599c55b6ba 100644 --- a/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs +++ b/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs @@ -9,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, @@ -56,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); diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index ac0b3d3289..61358914c4 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -2063,6 +2063,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 From c06ffde0aab1ffea43435165efb1dfdaa47f5a13 Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 4 Jun 2024 16:00:39 +0000 Subject: [PATCH 087/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 6a20b3a411..600334df6b 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- 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. @@ -3851,3 +3844,11 @@ 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 From fe16eb1cd4148f054e0df4415ccdad5ec5a26ef9 Mon Sep 17 00:00:00 2001 From: icekot8 <93311212+icekot8@users.noreply.github.com> Date: Tue, 4 Jun 2024 19:34:07 +0300 Subject: [PATCH 088/158] hand teleport portals now may start in the same grid. (#28556) --- Content.Server/Teleportation/HandTeleporterSystem.cs | 2 +- .../Teleportation/Components/HandTeleporterComponent.cs | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) 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.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"; From 5f134731d99d1cb48b2271ffef67d95d0fb8504c Mon Sep 17 00:00:00 2001 From: blueDev2 <89804215+blueDev2@users.noreply.github.com> Date: Tue, 4 Jun 2024 13:12:01 -0400 Subject: [PATCH 089/158] Microwave recipes now uses stacktype id instead of entity prototype id for stacked entities (#28225) --- .../Kitchen/EntitySystems/MicrowaveSystem.cs | 66 ++++++++++++++++--- .../Recipes/Cooking/medical_recipes.yml | 4 +- 2 files changed, 58 insertions(+), 12 deletions(-) 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/Resources/Prototypes/Recipes/Cooking/medical_recipes.yml b/Resources/Prototypes/Recipes/Cooking/medical_recipes.yml index 9d1947f03e..3bc9e22b0e 100644 --- a/Resources/Prototypes/Recipes/Cooking/medical_recipes.yml +++ b/Resources/Prototypes/Recipes/Cooking/medical_recipes.yml @@ -13,7 +13,7 @@ time: 10 solids: FoodPoppy: 1 - Brutepack: 1 + Brutepack: 10 MaterialCloth1: 1 reagents: TranexamicAcid: 20 @@ -26,7 +26,7 @@ time: 10 solids: FoodAloe: 1 - Ointment: 1 + Ointment: 10 MaterialCloth1: 1 reagents: Sigynate: 20 From c1ebb2d86b1e1ea86a7ba69d898e42c5dca02334 Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 4 Jun 2024 17:13:07 +0000 Subject: [PATCH 090/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 600334df6b..b14fae9c41 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- 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. @@ -3852,3 +3845,10 @@ 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 From 17385dfdfad84450392228720b44705fe45b5e18 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Tue, 4 Jun 2024 14:48:24 -0400 Subject: [PATCH 091/158] Add "fill level" sprites to mops and damp rag (#28590) --- .../Objects/Specific/Janitorial/janitor.yml | 34 ++++++++- .../Specific/Janitorial/advmop.rsi/fill-1.png | Bin 0 -> 200 bytes .../Specific/Janitorial/advmop.rsi/fill-2.png | Bin 0 -> 216 bytes .../advmop.rsi/inhand-left-fill-1.png | Bin 0 -> 155 bytes .../advmop.rsi/inhand-left-fill-2.png | Bin 0 -> 187 bytes .../advmop.rsi/inhand-right-fill-1.png | Bin 0 -> 157 bytes .../advmop.rsi/inhand-right-fill-2.png | Bin 0 -> 195 bytes .../Specific/Janitorial/advmop.rsi/meta.json | 62 +++++++++++++---- .../advmop.rsi/wielded-inhand-left-fill-1.png | Bin 0 -> 252 bytes .../advmop.rsi/wielded-inhand-left-fill-2.png | Bin 0 -> 282 bytes .../wielded-inhand-right-fill-1.png | Bin 0 -> 254 bytes .../wielded-inhand-right-fill-2.png | Bin 0 -> 279 bytes .../Specific/Janitorial/mop.rsi/fill-1.png | Bin 0 -> 179 bytes .../Specific/Janitorial/mop.rsi/fill-2.png | Bin 0 -> 191 bytes .../Specific/Janitorial/mop.rsi/fill-3.png | Bin 0 -> 197 bytes .../Janitorial/mop.rsi/inhand-left-fill-1.png | Bin 0 -> 145 bytes .../Janitorial/mop.rsi/inhand-left-fill-2.png | Bin 0 -> 156 bytes .../mop.rsi/inhand-right-fill-1.png | Bin 0 -> 147 bytes .../mop.rsi/inhand-right-fill-2.png | Bin 0 -> 160 bytes .../Specific/Janitorial/mop.rsi/meta.json | 65 ++++++++++++++---- .../mop.rsi/wielded-inhand-left-fill-1.png | Bin 0 -> 153 bytes .../mop.rsi/wielded-inhand-left-fill-2.png | Bin 0 -> 186 bytes .../mop.rsi/wielded-inhand-right-fill-1.png | Bin 0 -> 151 bytes .../mop.rsi/wielded-inhand-right-fill-2.png | Bin 0 -> 189 bytes .../Specific/Janitorial/rag.rsi/fill-1.png | Bin 0 -> 133 bytes .../Specific/Janitorial/rag.rsi/fill-2.png | Bin 0 -> 163 bytes .../Specific/Janitorial/rag.rsi/fill-3.png | Bin 0 -> 173 bytes .../Specific/Janitorial/rag.rsi/meta.json | 35 ++++++---- 28 files changed, 156 insertions(+), 40 deletions(-) create mode 100644 Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/fill-1.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/fill-2.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/inhand-left-fill-1.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/inhand-left-fill-2.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/inhand-right-fill-1.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/inhand-right-fill-2.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/wielded-inhand-left-fill-1.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/wielded-inhand-left-fill-2.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/wielded-inhand-right-fill-1.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/advmop.rsi/wielded-inhand-right-fill-2.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/mop.rsi/fill-1.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/mop.rsi/fill-2.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/mop.rsi/fill-3.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/mop.rsi/inhand-left-fill-1.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/mop.rsi/inhand-left-fill-2.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/mop.rsi/inhand-right-fill-1.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/mop.rsi/inhand-right-fill-2.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/mop.rsi/wielded-inhand-left-fill-1.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/mop.rsi/wielded-inhand-left-fill-2.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/mop.rsi/wielded-inhand-right-fill-1.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/mop.rsi/wielded-inhand-right-fill-2.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/rag.rsi/fill-1.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/rag.rsi/fill-2.png create mode 100644 Resources/Textures/Objects/Specific/Janitorial/rag.rsi/fill-3.png 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/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 0000000000000000000000000000000000000000..d7e1ad3ef2f064a8d1ea982f935565ac368e0949 GIT binary patch literal 200 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|ECYN(Tn`*L zkgGGtOR=cm>{y-Q#x~>KW%|qK#p*D){y-Q#x~>KW%|qK#p*D)lHr)@ivQ=Y!~jB@u~ z-k-;X7VSD!dF#f4|6C*W|C=ZjiNRRpQ_N uyh=TYBt!I>XFO%kMY4`g2>sb4Y}v}bd!}~A6ie52AbC$$KbLh*2~7Y_j4>Ag literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b5136f499b53fabcea456e3dd7281420130e70b1 GIT binary patch literal 195 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|YCT;XLn`LH zy>*bc*?@;N;7kP1g`Gy?%q62>kO*+jb_W{CwcU z((4~%H&!k1y8SoFwnpyGeC0*5SN0v-_2uz9ONdg2I`{l@3#^uJIL-cLN708>?j85o k=Ks5=oMd%#waxMk3^&cKm%V8e@&l>%boFyt=akR{0QfdalmGw# literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..57c1acc26e0a8cf5bc31f91b52fbbbb1cc5793a8 GIT binary patch literal 252 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!7>k44ofy`glX(f`=mz+NxOyoT z)fsL~Gd`2XaHglHCs$|AyjUHEgvktUIkmO5D_5?}0;*(S_zwoN*T_5sa`;Pv{DL7O z5R&0oLubXJYe1f>XFU6Ne5;~}vGU17|GjkP>6cA@V!#Ph z_(0s@bQNm`qt79>W3|E;_zXC>XUsqBU;vZ?g8K~T5(2rk4@hc9J>*(&@9RE;ddCm{ dvlEmVlq7$J9&mMWvd{vl^K|udS?83{1OR07UM&Cs literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4f0245bb850ed5f5adabb9c7ab427bd9771da2c6 GIT binary patch literal 282 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!7>k44ofy`glX(f`=mz+NxOyoT z)fsL~Gd`2XaHglHCs$|AyjUHEgvktUIkmO5D_5?}0;*(S_zwoN*T_5sa`;Pv{DL7O z5R&0oLubXJYe1<6PZ!6Kh}O5)E^-}E;9$LAr||dx`PkxSnH#!N&79lWId{nkRBgC2 zeYt>wLxTgKL+o9~kfou48Z{hq-m)byFip)&su=_tthjt0Mn`p^$(346Fh$_3-lYzyw6_uhyN1;`=x~klC`w7+d+=@ MboFyt=akR{0L9{JbpQYW literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..07d042cc75b5da0ffa1eeecddfeeab4db9ab278b GIT binary patch literal 254 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!7>k44ofy`glX(f`=mz+NxYikN z^inM9>FG&hIFn|4X64G2wY9bLVs#i2CNsF@I2;1`74H4tA$#cp&(@~24G*pB zDlIrS9NL!4w5f&HfxE#^!TARBlXC^{ODfobazOC6;Y0D)W=@tC`5Y#k44ofy`glX(f`=mz+NxYikN z^inM9>FG&hIFn|4X64G2wY9bLVs#i2CNsF@jex8m@^bEux$ur0V!*6@KmrppZLIX z-OY9Zb{UTjiwDpD))3f*Bw}#~OaxR2>Ei8hW}ohFJ6_ zCrGd^PDo+m3f*Bw}#~OaxR2>Ei+IqS;hFJ8z z?K9*9+HmAX)&Kg9O>6Ev$jCY565R2lCeFoHc)^|XT(>893v_t(2zB<(F%Em-U&E{& b@~n-OS(Y&%euoAl&>#j+S3j3^P63f*Bw}#~OaxR2>EiI(xb}hFJ8z z?NbzDP~bWI>em1LGm@jE?OWR j_rhPtzE>sWSt|EC4kopd?F)K=#xZ!h`njxgN@xNAXQfPj literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..3007efa01e25ef63550f83ec0f74af6f5ccbc648 GIT binary patch literal 145 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|yggkULn`LH zy|t0|fB_G)!Oyk-`u9D3^EODqx2++DHG=&FCr}j#$gD4$Qu6(;*1BE$o9TAK`K05{an^LB{Ts5HPkhR literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..17495e71826c0b9ad172f6879e8026e96b408c66 GIT binary patch literal 147 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|d_7$pLn`LH zy=lnTV8Fxd@I3nO{vTmCx>=Z87w(Eyjy(5<1*i%HwuO6Mx_kU-fa$;WpL_5AXNHJ2 luy0jLxp)75-uu;`-_7y3Ai%uoLy#ay&ePS;Wt~$(699$tGM4}V literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..58c3bd2c42ff3e804745f8b893da469f69a79668 GIT binary patch literal 160 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|qC8z3Ln`LH zy>*bc!GMSPK$`Bi@atT{TRbj^aK2;P@TUBd6i^ii*iW8mYyA7Rtya&rqd5gGGF!L4 ys;!lW$T38({il25>uLdiW;fOM_wI#1kF{jfFw>KEc$QWNQsU|A=d#Wzp$PzY&ofm3 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..997f3ae0398d3aab2bf9de11d8ba7e1ad495a62e GIT binary patch literal 153 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|f<0XvLn`LH zy}6P1fPw&vJi?#|6e*n=7ZcW4WH_dPB-X8=^nz;H+7M+n(=5-Rl|Wors-SUfgi%(P{TL^Y!*! dTHejxP{tklFT8&(L;4nwYEM@`mvv4FO#u8pM2!Fd literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..956f9bbbd19035e10cfb628e544b645c0be7dabc GIT binary patch literal 151 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|0zF+ELn`LH zy=lnT;K0G+@aXjW=h^uSTLXE{Z83Qh6vPTt!|=h~GweHGzQwaU=h`mEJoDbPY?^_k tG)RVl;ehSFl~C0!g?voY|n;^ENcgcI*_D0XTX_GUVfQlLZ1x?zzt1W)V8{?BN zEcDc3pb%AgDrj<`WX1P$$>O06LAh>ho>+&DTSFM@*B^**Yt}<6>F8TD< hy(Qpe5;H^1Q8t~{@P5-ruOEPvdb;|#taD0e0swh>MyCJ( literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6fb322fbdf212a7f567ac6f49be5a319b6679acb GIT binary patch literal 133 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}_MR?|ArY-_ zFKiTKP~dU8xM3M{+S!PkH!8Zq{#`FFZ~-b|Shgz5FK+uO)u)#k7yAUAJ9=)yTK2H= d`TH0i|y~EXe=> literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..7114d9a2e74ef60e00ed2786870d451fa2eea9b4 GIT binary patch literal 163 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}v7RoDArY-_ z&j)ffC6Yz{Z)v-aZ1$OXp2_{f&a4~jD=R)kPYifr$UNg5--lb?iV_n)iUPGWc)I$z JtaD0e0sv&AJiPz_ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0705b79b5086902440124793aea3a85690e2f1d2 GIT binary patch literal 173 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}>7Fi*ArY-_ zFCOGPpup4m@P?u6VMm$8mv>x#ebhNB!?Rh0>wkIoq3+H!pyD6Ko}rf}glv|)y=eQE zH_Bey9_QRhyXn7h>6@E(Zl~2 Date: Tue, 4 Jun 2024 18:49:31 +0000 Subject: [PATCH 092/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index b14fae9c41..574ea1adb3 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,14 +1,4 @@ Entries: -- 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. @@ -3852,3 +3842,10 @@ 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 From c498f70e41eebeaaebecb2cfed099f98d68f3b30 Mon Sep 17 00:00:00 2001 From: TsjipTsjip <19798667+TsjipTsjip@users.noreply.github.com> Date: Tue, 4 Jun 2024 23:47:52 +0200 Subject: [PATCH 093/158] Reword some criminal records text (#28597) * Update criminal-records.ftl * Update criminal-records.ftl * Update criminal-records.ftl --- .../Locale/en-US/criminal-records/criminal-records.ftl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 From c17df36cf6fca599deef6c5ea5f10d9bea18a5cd Mon Sep 17 00:00:00 2001 From: Plykiya <58439124+Plykiya@users.noreply.github.com> Date: Wed, 5 Jun 2024 00:04:31 -0700 Subject: [PATCH 094/158] Remove obsolete VisibilitySystem functions (#28610) Remove obsolete visibility functions Co-authored-by: plykiya --- Content.Server/Ghost/GhostSystem.cs | 16 ++++++++-------- .../Pointing/EntitySystems/PointingSystem.cs | 2 +- .../Revenant/EntitySystems/CorporealSystem.cs | 10 +++++----- .../Revenant/EntitySystems/RevenantSystem.cs | 12 ++++++------ 4 files changed, 20 insertions(+), 20 deletions(-) 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/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/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.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.cs index a05105662d..fa4f6db240 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantSystem.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantSystem.cs @@ -78,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); } @@ -192,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); } From 6b2145241fe668473ac95f5026af7f4335485e0e Mon Sep 17 00:00:00 2001 From: TsjipTsjip <19798667+TsjipTsjip@users.noreply.github.com> Date: Wed, 5 Jun 2024 09:32:58 +0200 Subject: [PATCH 095/158] Prayable datafield typo (#28622) * notifiactionPrefix -> notificationPrefix * notifiactionPrefix -> notificationPrefix --- Content.Shared/Prayer/PrayableComponent.cs | 2 +- .../Entities/Objects/Fun/Instruments/instruments_misc.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) 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/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 From 50f6b658be2a85ea804f4529665741c8eb2fe57a Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Wed, 5 Jun 2024 20:21:47 +1200 Subject: [PATCH 096/158] Update engine to v224.1.0 (#28624) --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index f648218756..a628d31c4b 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit f64821875655dfec4e2c951d5c22c0db873fe86e +Subproject commit a628d31c4b82612eec2c42ba33024acc1c4d9d7e From 384edd6672b915bd5841d47ab5d47329bff057c1 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Wed, 5 Jun 2024 20:37:22 +1200 Subject: [PATCH 097/158] Use dummy sessions in NukeOpsTest (#28549) * Add dummy sessions * Update NukeOpsTest * Fix PvsBenchmark --- Content.Benchmarks/PvsBenchmark.cs | 41 ++++++--------- .../Pair/TestPair.Helpers.cs | 13 +++-- .../Pair/TestPair.Recycle.cs | 2 + Content.IntegrationTests/Pair/TestPair.cs | 5 +- .../Tests/GameRules/NukeOpsTest.cs | 52 ++++++++++++++++--- 5 files changed, 76 insertions(+), 37 deletions(-) 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.IntegrationTests/Pair/TestPair.Helpers.cs b/Content.IntegrationTests/Pair/TestPair.Helpers.cs index cc83232a06..1e8306a02c 100644 --- a/Content.IntegrationTests/Pair/TestPair.Helpers.cs +++ b/Content.IntegrationTests/Pair/TestPair.Helpers.cs @@ -7,6 +7,8 @@ using Content.Shared.Preferences; using Content.Shared.Roles; using Robust.Shared.GameObjects; using Robust.Shared.Map; +using Robust.Shared.Network; +using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.UnitTesting; @@ -136,10 +138,15 @@ public sealed partial class TestPair /// Helper method for enabling or disabling a antag role /// public async Task SetAntagPref(ProtoId id, bool value) + { + await SetAntagPref(Client.User!.Value, id, value); + } + + public async Task SetAntagPref(NetUserId user, ProtoId id, bool value) { var prefMan = Server.ResolveDependency(); - var prefs = prefMan.GetPreferences(Client.User!.Value); + var prefs = prefMan.GetPreferences(user); // what even is the point of ICharacterProfile if we always cast it to HumanoidCharacterProfile to make it usable? var profile = (HumanoidCharacterProfile) prefs.SelectedCharacter; @@ -148,11 +155,11 @@ public sealed partial class TestPair await Server.WaitPost(() => { - prefMan.SetProfile(Client.User.Value, prefs.SelectedCharacterIndex, newProfile).Wait(); + prefMan.SetProfile(user, prefs.SelectedCharacterIndex, newProfile).Wait(); }); // 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 newPrefs = prefMan.GetPreferences(user); var newProf = (HumanoidCharacterProfile) newPrefs.SelectedCharacter; Assert.That(newProf.AntagPreferences.Contains(id), Is.EqualTo(value)); } diff --git a/Content.IntegrationTests/Pair/TestPair.Recycle.cs b/Content.IntegrationTests/Pair/TestPair.Recycle.cs index 8d1e425553..4cae4affc4 100644 --- a/Content.IntegrationTests/Pair/TestPair.Recycle.cs +++ b/Content.IntegrationTests/Pair/TestPair.Recycle.cs @@ -34,6 +34,8 @@ public sealed partial class TestPair : IAsyncDisposable private async Task OnCleanDispose() { + await Server.RemoveAllDummySessions(); + if (TestMap != null) { await Server.WaitPost(() => Server.EntMan.DeleteEntity(TestMap.MapUid)); diff --git a/Content.IntegrationTests/Pair/TestPair.cs b/Content.IntegrationTests/Pair/TestPair.cs index 7ee5dbd55c..0b18c38239 100644 --- a/Content.IntegrationTests/Pair/TestPair.cs +++ b/Content.IntegrationTests/Pair/TestPair.cs @@ -37,7 +37,10 @@ public sealed partial class TestPair client = Client; } - public ICommonSession? Player => Server.PlayerMan.Sessions.FirstOrDefault(); + public ICommonSession? Player => Client.User == null + ? null + : 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/Tests/GameRules/NukeOpsTest.cs b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs index 5ad5857849..a99ced7e42 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.SetAntagPref(dummies[1].UserId, "NukeopsMedic", true); + + // 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,11 +119,36 @@ 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 gridsRule = entMan.AllComponents().Single().Component; @@ -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++; @@ -200,6 +237,7 @@ public sealed class NukeOpsTest ticker.SetGamePreset((GamePresetPrototype?)null); await pair.SetAntagPref("NukeopsCommander", false); + await pair.SetAntagPref(dummies[1].UserId, "NukeopsMedic", false); await pair.CleanReturnAsync(); } } From 5298a35c7b7c9fdac3da41fec35505e1df14d933 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Thu, 6 Jun 2024 01:00:33 +1200 Subject: [PATCH 098/158] Update engine to v224.1.1 (#28632) --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index a628d31c4b..fcd507d1f9 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit a628d31c4b82612eec2c42ba33024acc1c4d9d7e +Subproject commit fcd507d1f9e6fffb96bc4228d7f3d78577f0ff2e From e0ea8adb9ac547ada8e2412305ec7d1b69d70242 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+electrojr@users.noreply.github.com> Date: Wed, 5 Jun 2024 16:19:24 +0200 Subject: [PATCH 099/158] Add Job preference tests (#28625) * Misc Job related changes * Add JobTest * A * Aa * Lets not confuse the yaml linter * fixes * a --- .../GameTicking/Managers/ClientGameTicker.cs | 11 +- Content.Client/LateJoin/LateJoinGui.cs | 8 +- Content.Client/Lobby/LobbyUIController.cs | 2 +- .../Lobby/UI/CharacterPickerButton.xaml.cs | 4 +- .../Pair/TestPair.Helpers.cs | 74 ++++-- .../Pair/TestPair.Recycle.cs | 15 ++ Content.IntegrationTests/Pair/TestPair.cs | 6 +- Content.IntegrationTests/PoolManager.Cvars.cs | 1 + Content.IntegrationTests/Tests/EntityTest.cs | 1 + .../Tests/GameRules/AntagPreferenceTest.cs | 4 +- .../Tests/GameRules/NukeOpsTest.cs | 6 +- .../Tests/PostMapInitTest.cs | 23 +- .../Tests/Round/JobTest.cs | 222 ++++++++++++++++++ .../Tests/Station/StationJobsTest.cs | 13 +- Content.Server/Database/ServerDbBase.cs | 7 +- .../GameTicking/GameTicker.RoundFlow.cs | 2 +- .../Managers/ServerPreferencesManager.cs | 6 +- .../Components/SpawnPointComponent.cs | 9 +- .../EntitySystems/SpawnPointSystem.cs | 2 +- .../Components/StationJobsComponent.cs | 46 ++-- .../Systems/StationJobsSystem.Roundstart.cs | 23 +- .../Station/Systems/StationJobsSystem.cs | 138 ++++++----- .../GameTicking/SharedGameTicker.cs | 15 +- .../Preferences/HumanoidCharacterProfile.cs | 104 ++++++-- Content.Shared/Roles/JobPrototype.cs | 4 +- Content.Shared/Roles/Jobs/SharedJobSystem.cs | 12 + Resources/Prototypes/Maps/arena.yml | 2 - Resources/Prototypes/Maps/arenas.yml | 2 - Resources/Prototypes/Maps/asterisk.yml | 2 - Resources/Prototypes/Maps/debug.yml | 6 - Resources/Prototypes/Maps/edge.yml | 2 - Resources/Prototypes/Maps/hammurabi.yml | 2 - Resources/Prototypes/Maps/hive.yml | 2 - Resources/Prototypes/Maps/lighthouse.yml | 2 - Resources/Prototypes/Maps/micro.yml | 2 - Resources/Prototypes/Maps/pebble.yml | 2 - Resources/Prototypes/Maps/shoukou.yml | 2 - Resources/Prototypes/Maps/submarine.yml | 2 - Resources/Prototypes/Maps/tortuga.yml | 2 - 39 files changed, 537 insertions(+), 251 deletions(-) create mode 100644 Content.IntegrationTests/Tests/Round/JobTest.cs 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/LateJoin/LateJoinGui.cs b/Content.Client/LateJoin/LateJoinGui.cs index 252aa9aafa..62a06629f2 100644 --- a/Content.Client/LateJoin/LateJoinGui.cs +++ b/Content.Client/LateJoin/LateJoinGui.cs @@ -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/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.IntegrationTests/Pair/TestPair.Helpers.cs b/Content.IntegrationTests/Pair/TestPair.Helpers.cs index 1e8306a02c..588cf0d80e 100644 --- a/Content.IntegrationTests/Pair/TestPair.Helpers.cs +++ b/Content.IntegrationTests/Pair/TestPair.Helpers.cs @@ -8,7 +8,6 @@ using Content.Shared.Roles; using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Network; -using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.UnitTesting; @@ -135,32 +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) { - await SetAntagPref(Client.User!.Value, id, value); + user ??= Client.User!.Value; + if (user is not {} userId) + return; + + var prefMan = Server.ResolveDependency(); + var prefs = prefMan.GetPreferences(userId); + + // Automatic preference resetting only resets slot 0. + Assert.That(prefs.SelectedCharacterIndex, Is.EqualTo(0)); + + var profile = (HumanoidCharacterProfile) prefs.Characters[0]; + var newProfile = profile.WithAntagPreference(id, value); + _modifiedProfiles.Add(userId); + await Server.WaitPost(() => prefMan.SetProfile(userId, 0, newProfile).Wait()); } - public async Task SetAntagPref(NetUserId user, ProtoId id, bool value) + /// + /// 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); - // what even is the point of ICharacterProfile if we always cast it to HumanoidCharacterProfile to make it usable? - var profile = (HumanoidCharacterProfile) prefs.SelectedCharacter; + var profile = (HumanoidCharacterProfile) prefs.Characters[0]; + var dictionary = new Dictionary, JobPriority>(profile.JobPriorities); - Assert.That(profile.AntagPreferences.Contains(id), Is.EqualTo(!value)); - var newProfile = profile.WithAntagPreference(id, value); + // Automatic preference resetting only resets slot 0. + Assert.That(prefs.SelectedCharacterIndex, Is.EqualTo(0)); - await Server.WaitPost(() => + if (highCount != 0) { - prefMan.SetProfile(user, 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(user); - 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 4cae4affc4..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,9 @@ public sealed partial class TestPair : IAsyncDisposable private async Task OnCleanDispose() { + await Server.WaitIdleAsync(); + await Client.WaitIdleAsync(); + await ResetModifiedPreferences(); await Server.RemoveAllDummySessions(); if (TestMap != null) @@ -81,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 0b18c38239..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,9 +39,7 @@ public sealed partial class TestPair client = Client; } - public ICommonSession? Player => Client.User == null - ? null - : Server.PlayerMan.SessionsDict.GetValueOrDefault(Client.User.Value); + public ICommonSession? Player => Server.PlayerMan.SessionsDict.GetValueOrDefault(Client.User!.Value); public ContentPlayerData? PlayerData => Player?.Data.ContentData(); 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/EntityTest.cs b/Content.IntegrationTests/Tests/EntityTest.cs index 926374cf05..1fc739fb0c 100644 --- a/Content.IntegrationTests/Tests/EntityTest.cs +++ b/Content.IntegrationTests/Tests/EntityTest.cs @@ -340,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/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 a99ced7e42..48fe46a8c8 100644 --- a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs @@ -62,8 +62,8 @@ public sealed class NukeOpsTest await pair.RunTicksSync(5); // Opt into the nukies role. - await pair.SetAntagPref("NukeopsCommander", true); - await pair.SetAntagPref(dummies[1].UserId, "NukeopsMedic", 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); @@ -236,8 +236,6 @@ public sealed class NukeOpsTest } ticker.SetGamePreset((GamePresetPrototype?)null); - await pair.SetAntagPref("NukeopsCommander", false); - await pair.SetAntagPref(dummies[1].UserId, "NukeopsMedic", false); await pair.CleanReturnAsync(); } } diff --git a/Content.IntegrationTests/Tests/PostMapInitTest.cs b/Content.IntegrationTests/Tests/PostMapInitTest.cs index 7702d3d8a0..e439b0aa96 100644 --- a/Content.IntegrationTests/Tests/PostMapInitTest.cs +++ b/Content.IntegrationTests/Tests/PostMapInitTest.cs @@ -240,22 +240,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/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.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/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/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/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/EntitySystems/SpawnPointSystem.cs b/Content.Server/Spawners/EntitySystems/SpawnPointSystem.cs index 608818af16..c9ea683f0c 100644 --- a/Content.Server/Spawners/EntitySystems/SpawnPointSystem.cs +++ b/Content.Server/Spawners/EntitySystems/SpawnPointSystem.cs @@ -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/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.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/Preferences/HumanoidCharacterProfile.cs b/Content.Shared/Preferences/HumanoidCharacterProfile.cs index e108a1b7d2..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,16 +396,16 @@ namespace Content.Shared.Preferences }; } - public HumanoidCharacterProfile WithTraitPreference(string traitId, string? categoryId, bool pref) + public HumanoidCharacterProfile WithTraitPreference(ProtoId traitId, string? categoryId, bool pref) { var prototypeManager = IoCManager.Resolve(); - var traitProto = prototypeManager.Index(traitId); + var traitProto = prototypeManager.Index(traitId); TraitCategoryPrototype? categoryProto = null; if (categoryId != null && categoryId != "default") categoryProto = prototypeManager.Index(categoryId); - var list = new HashSet(_traitPreferences); + var list = new HashSet>(_traitPreferences); if (pref) { @@ -381,7 +422,7 @@ namespace Content.Shared.Preferences var count = 0; foreach (var trait in list) { - var traitProtoTemp = prototypeManager.Index(trait); + var traitProtoTemp = prototypeManager.Index(trait); count += traitProtoTemp.Cost; } @@ -523,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. @@ -533,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/Roles/JobPrototype.cs b/Content.Shared/Roles/JobPrototype.cs index 58cda41bf8..cbdcb55079 100644 --- a/Content.Shared/Roles/JobPrototype.cs +++ b/Content.Shared/Roles/JobPrototype.cs @@ -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; } 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/Resources/Prototypes/Maps/arena.yml b/Resources/Prototypes/Maps/arena.yml index fb5f659644..3856b62294 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 3a1d514e92..9045cd4706 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: #service Captain: [ 1, 1 ] 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 191246b0bc..12e073a10d 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 5727071b3b..5c541902f6 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 cffedb8437..8b4088196b 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 3ed94e17ed..c245920b56 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 c282f6e24b..13a6a78168 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 d3216cff75..732671ffb4 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/submarine.yml b/Resources/Prototypes/Maps/submarine.yml index 1d4fc2217e..c2930efa72 100644 --- a/Resources/Prototypes/Maps/submarine.yml +++ b/Resources/Prototypes/Maps/submarine.yml @@ -15,8 +15,6 @@ - type: StationEmergencyShuttle emergencyShuttlePath: /Maps/Shuttles/DeltaV/NTES_Propeller.yml - type: StationJobs - overflowJobs: - - Passenger availableJobs: #civilian Passenger: [ -1, -1 ] diff --git a/Resources/Prototypes/Maps/tortuga.yml b/Resources/Prototypes/Maps/tortuga.yml index 0b6f69247f..a380c39bf7 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 ] From f4fecc9882ae88fb8732b5c758b3e864f426a7c3 Mon Sep 17 00:00:00 2001 From: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> Date: Wed, 5 Jun 2024 07:19:54 -0700 Subject: [PATCH 100/158] Add logs that provide session to player admin logs (#28628) --- Content.Server/Administration/Logs/AdminLogManager.Json.cs | 4 ++++ 1 file changed, 4 insertions(+) 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); From e639f736f51f988971dbc38c5884c7fed5a9c8f2 Mon Sep 17 00:00:00 2001 From: PJBot Date: Wed, 5 Jun 2024 17:51:47 +0000 Subject: [PATCH 101/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 574ea1adb3..851ebffc64 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- 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 @@ -3849,3 +3842,10 @@ 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 From ac9f9268f01d91f2b4bd9a0064374208ea860dc7 Mon Sep 17 00:00:00 2001 From: Hmeister-real <118129069+hmeister-real@users.noreply.github.com> Date: Wed, 5 Jun 2024 22:10:27 +0200 Subject: [PATCH 102/158] minor banner changes (#28636) * minor banner changes * Uhrmm actchually it's you're, not your * Update banners.yml props Hyenh --- .../Prototypes/Entities/Structures/Decoration/banners.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 From d22a0057cf72ebd2e7b9e9055364d2c95901d6e1 Mon Sep 17 00:00:00 2001 From: lapatison <100279397+lapatison@users.noreply.github.com> Date: Wed, 5 Jun 2024 23:10:58 +0300 Subject: [PATCH 103/158] Revenant spell catalog locale (#28638) locale --- .../Locale/en-US/store/revenant-catalog.ftl | 11 +++++++++++ .../Prototypes/Catalog/revenant_catalog.yml | 16 ++++++++-------- 2 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 Resources/Locale/en-US/store/revenant-catalog.ftl 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/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 From f0c639cdf4a65139e65d4354e8353fc60315b889 Mon Sep 17 00:00:00 2001 From: Plykiya <58439124+Plykiya@users.noreply.github.com> Date: Wed, 5 Jun 2024 13:14:56 -0700 Subject: [PATCH 104/158] Make cuff default range again (#28576) * Make cuff default range again * uncuff distance * how about ONE --------- Co-authored-by: plykiya --- Content.Shared/Cuffs/SharedCuffableSystem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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)) From 24b9bbd946f2095c51c42c730c9860f6a5a2bb15 Mon Sep 17 00:00:00 2001 From: PJBot Date: Wed, 5 Jun 2024 20:16:02 +0000 Subject: [PATCH 105/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 851ebffc64..e68ab9517d 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- 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 @@ -3849,3 +3842,10 @@ 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 From c3dc2fca88280e9f214266fdf3e75f0a256ab2fd Mon Sep 17 00:00:00 2001 From: Cojoke <83733158+Cojoke-dot@users.noreply.github.com> Date: Wed, 5 Jun 2024 15:47:27 -0500 Subject: [PATCH 106/158] Make Projectiles Only Hit a Variety of Station Objects Unless Clicked on (#28571) --- .../Fun/Instruments/base_instruments.yml | 3 ++- .../Dispensers/base_structuredispensers.yml | 2 +- .../Computers/base_structurecomputers.yml | 1 + .../Machines/Medical/chemistry_machines.yml | 2 +- .../Machines/Medical/disease_diagnoser.yml | 4 +--- .../Machines/Medical/vaccinator.yml | 1 + .../Structures/Machines/artifact_analyzer.yml | 3 ++- .../Machines/base_structuremachines.yml | 7 +++++++ .../Structures/Machines/fax_machine.yml | 1 + .../Entities/Structures/Machines/lathe.yml | 1 + .../Structures/Machines/microwave.yml | 4 ++-- .../Structures/Machines/reagent_grinder.yml | 2 +- .../Machines/wireless_surveillance_camera.yml | 4 +++- .../Piping/Atmospherics/portable.yml | 2 +- .../Structures/Piping/Atmospherics/unary.yml | 2 +- .../Structures/Piping/Disposal/units.yml | 1 + .../Power/Generation/Singularity/emitter.yml | 2 +- .../Power/Generation/portable_generator.yml | 4 ++-- .../Structures/Power/Generation/solar.yml | 3 +++ .../Entities/Structures/Power/chargers.yml | 3 +++ .../Structures/Storage/filing_cabinets.yml | 1 + .../Entities/Structures/Walls/fence_wood.yml | 19 +++++++++++++++++-- .../Entities/Structures/hydro_tray.yml | 4 +++- 23 files changed, 57 insertions(+), 19 deletions(-) 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/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/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/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/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/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 From a632e61476be6ab9a2f7cea7f63be1d89ef2a071 Mon Sep 17 00:00:00 2001 From: PJBot Date: Wed, 5 Jun 2024 20:48:33 +0000 Subject: [PATCH 107/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index e68ab9517d..6adbf16221 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,13 +1,4 @@ Entries: -- 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. @@ -3849,3 +3840,11 @@ 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 From 19e301d4f73b916fcdbab162156f0eec36f55302 Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Wed, 5 Jun 2024 14:06:24 -0700 Subject: [PATCH 108/158] Fix Smoke-grenade.ogg not being mono (#28593) --- Resources/Audio/Effects/Smoke-grenade.ogg | Bin 98191 -> 93809 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Resources/Audio/Effects/Smoke-grenade.ogg b/Resources/Audio/Effects/Smoke-grenade.ogg index c697214c2a571e1b6cc900b51c284fb073b8625c..945290ede54be1e189c4d05b32e8dd09bc5e2379 100644 GIT binary patch literal 93809 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

;E4dzooTn~OAhh>Ou*Ov+%=UA{n!z{P} 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

=Qb~! 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 literal 98191 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

i(GW1h<} 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
eGzdw3onk5@dGzMCb~AEszid(?G;!V}u1 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 From 77887591fc876f27d65607054982e9b4206fce78 Mon Sep 17 00:00:00 2001 From: Ps3Moira <113228053+ps3moira@users.noreply.github.com> Date: Wed, 5 Jun 2024 14:16:32 -0700 Subject: [PATCH 109/158] Fix Cigars Sprites + YAML fix for Inhand unlit cigars/cigs (#28641) --- .../Smokeables/Cigarettes/cigarette.yml | 2 ++ .../Consumable/Smokeables/Cigars/cigar.yml | 2 ++ .../Cigars/cigar-gold.rsi/lit-inhand-left.png | Bin 769 -> 1374 bytes .../Cigars/cigar-gold.rsi/lit-inhand-right.png | Bin 542 -> 1220 bytes .../cigar-gold.rsi/unlit-inhand-left.png | Bin 246 -> 306 bytes .../cigar-gold.rsi/unlit-inhand-right.png | Bin 263 -> 335 bytes .../Cigars/cigar.rsi/lit-inhand-left.png | Bin 769 -> 1374 bytes .../Cigars/cigar.rsi/lit-inhand-right.png | Bin 542 -> 1220 bytes .../Cigars/cigar.rsi/unlit-inhand-left.png | Bin 246 -> 306 bytes .../Cigars/cigar.rsi/unlit-inhand-right.png | Bin 263 -> 335 bytes 10 files changed, 4 insertions(+) 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/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 353efa3f395a6167f2bcc1f68b75b1b6b2f5c950..c08f70d091d56f6538f7c3f7322d9d692c5a5fed 100644 GIT binary patch literal 1374 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zcaljKx9jP7LeL$-HD>V3qK6aSW-L z^Y*S`k#IP}@rT=IPPr2Dkl{#)jKjMbJC-WPI6r>K!s9$s+hXN{uRc~a47+A5X;-uT zyNtp6(8bH^mc(YB{r_7lWyAe-7yMUG-THni6VL`^Abj=BZ{}N<*8Tl;)Bo%v{kZ$u zPoKOmd%tm8;kSg;Kl8mUe-{7vxPINbRrTLr{d`&U=lRk(du$&4+pqQX$<2@7{(t&= zIq2Pf%UA#Gf0h1CuHC|r72npJ?Ukv;|5cuEkEVeplfo>94mE~FoDCUBOnbR%=DwAg ze7|o0Y~7aIZ)NA*$KWE&aFNBqgi*kk1m;_V*UU22b$@iPv+t<8vE}pm)3>M3zgC~0 zd+M#8x%koW=V3>zUtr-3{-8nSbAF|6RnQ5cP%2Q0Cg(^xv8_eP;wwf&s}8T+Cayyp?X( z&)=5be_t=}`m=U>TU*)G#m8pI|J&v(KK1@Q`Tv__D(e27`o-YvBfYOm^3etHCI3HN zo-Y6ISDa@5Kgs7=-)~P3T=(v9(x3IO9ly-9NO-&dMYVgssj*}9t&7jk<+hkjJEa29qyuG}$nQna9vDP*$R^a44 z7MpwA(*P7<`i&B<>&=7I3RXSfKt!Kffz%C%!*lz5k@$p>1zY0X=_@Ej9gW zzm3fQ&b{XD;8YREv~hB+e*^#8vO{l?^y5tlJn4(%TkkFT{*|FN?Uz;cg_@c3|5j?M z>u;YOy#GAwrP$jWB>snLs-NF=eE+UpQNO>IzLv|`{D1oU`O}L2pFJne_O57S#buMz zKR;T3t6y_6`}@!T@+H9FVgB-SUflnTj#kNCJc0)T!nQvRNG{8DKWi?&?9QLU!`n@l zN~$z?#tCfBo4zlv|10CRf-~%YR~;=gv3-7Lzq9ya|5k<{b!)1(y{`xoU}&Cw%KL=f zHT_BDExq%8$sXPQ>l~*^>&^RSAI&sC!SQ~)S=hyzSjTu?5X{k`M2iH-ZayDsejagA{&hU z9Y1`|y&f$uJUsq?apv>qr*9{}tdpJn%o%fgm- z$*42@IM&y1uiuE7tHEj5>?gyqnACz-+7g>uUmf~7+uqaQCqse{uJnzqbbzTXUB@3# p49wi8jn4h~5F9HHE4OAW`p+=+u=8Bk{cnRoVxF#kF6*2UngE@xakKyc literal 769 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zf+;U^?OH;uumf=j~m?ERjTshKKbk zOP5B>yeDDJpYUo+*(~OY#s`8b5mjt!x9~>1W7KJ8zo2}he~Dq2q~e;{y_NIZQxns= z^8ekfOkDjJXdDW-6LQ(=yJ`Q=OP|~Mm(3TycSkn)|LHx~=B&25^EdV1V#9Cte}Dg6 zeKqUjt&r=UHAWE_tWP!%8WX5+p^8)?w=
eA=$_uAZe_v^m@ z=Z~w;o{x|Ee|hir&krZ}OV8i`r2lLE?q7el=2ggj{waNjhui=2m+8mv_$!bbD_8?z t?RNVEyEE 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 cdeedb4c24b6843544fa4ca348296f2536b90cd6..7c685a0a408cf76f0ffc0fc229a211e6413efa31 100644 GIT binary patch literal 1220 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zcaljKx9jP7LeL$-HD>V5#$TaSW-L z^Y*T7p0v9J+k=}5slQB$@?G8)F|6FAxYcXw4=zpC$AQ~CGill%M63@r{_Pu@(ZGWCA9-FPX`5@c{-+N)oAdn%${y{y0ddjJ2s z_4`AwMpftUueo^jviz;#6}6>n{*}l6zZ|zm-~5;Twfpi=TlxdvD?YPU)aud~OwU!eo|)3?>H?Muk}n0=^6#Y78#I42w7!E)o#Fb-mX7-aPsLYkG|B z|1P`EF7c@4Pt>pZk1N*i{&)0tzSqtFd+u5O>z{vS%l}x--}QHL>&t=~Qhn4WKYM>~ zkIkHI|2Iwf8~^*Z{OODLzVuf9UN$A&J~A2VXobUW{QTFW%&qkQ3%uR#r5I&?UTW3* z&Dk?{4>>HQvxZWBLDW(w`sa zC)E9rerx{h`2Jd_+U<|j&h6{_yoTT4dhxm){|;)Y-@E_4+%W(DoFDukceZ?1)r?N= zdF&V-b(~p-Qw?Za?$e--*z0rd*Sly>w>h)(Y;0x4-i9whjXxNAa%1b?=p5G*z!R)q ziZ?W!uj@%Ng5qE^_YuFyCu=rVmQC-0#YhcXLiUz#H;>QJJ;dR#<(}XJt-{GCm;5|% z^XLEQ&-cH-^!{nYx!?Bx|9}0b&(`l>bMqU>hbN!UufDyW{q4TBMFG!`{rS`TufNjn z)2g<7f4b?DnEt~@KP}#V|GIVi|NTC<_rI@8&b@y0_rGt)iwca<%ktIqR7| zRqv}SDl9T?uRXqS?H_@P&{lTZNC3W8n%~#y~*L`*yIAH-J<4x0l#+4ECn|qkj>OgVh M>FVdQ&MBb@07DZjVgLXD literal 542 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zf+;V0`N7;uumf=j~lXFJ?!Hwuh1% zw`>vSSJ1R|*e1MY?H+f5euX&aH#tWy$tYDZSZ~}Szp+!{rpClx&ikKBcAwq9`80R* zSrMRd#DJo!`=)NcwkZD0n-$wm{=WY8ulMWP@a8l2D|0`Y{Q2{;WY>p(|D3O@nIAs? z-gx)g+@g=We{Nm(FM}!J^@Bh5s8{~HWb?vtIpQq=3{xAHTdBasZ{vR)P=oGv2 VvzR7}3vsJKlAf-9F6*2UngAYTyTJee 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 9c3943fbf26ecf93db9b406f4955db7941abaa35..c1ca9c4d489e08820423c02af465fb7535ae8dd4 100644 GIT binary patch delta 279 zcmeyyxQS_kNGZx^prw85kJec)B=-RLpsMYolnBfk^AaBgw8$8G0J5Z@fCR z?OlRf0)y7=h?SNmPA5)(o$-J52F~f)PAwMfK#d^4`!S4pezkb~?f%0L@*eK1h+qCt zVz=fV`*`==bE;Q2#s=k88F-fj8(2Ts8M{{Yuh!;ekDrUzZzopr09zY+EdT%j literal 246 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=dp%toLn`LHy}41a$w9>RV!v}r z3ga{e+ZngE+B$HGIi&1TGw4gR`d6EOqgu)6-M+>QW}q$*h`N+I=WA8pCR=+y=R40d zpW4pfCEs^>`PMakr{{d>`fm|&J#@mS%xTx<1AZFZmtX!w1&&=LGiJ#v sysp~Qb=qLs_SfI5&!y@E?JrYTnzyNvH{Q*6Z!5?>p00i_>zopr01r%D7ytkO 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 0f4ab68d1569e959c7cd17c2ad9f83e6a37ff345..9fc2e5ba275c279ff1ba67a04e0fc3e5d38cb12f 100644 GIT binary patch delta 308 zcmZo?I?psgrJk`k$lZxy-8q?;3=E8%o-U3d6?5L+vgT`Y5NUnby>U^I;+un!9o`F; zn@szr*JyjAuR1|7Y+KY14-;c$&O?kp_qQBTIQlS!<9bgT0|Xc-N4|KnOJm*7ot3-S zt=en({7kjI$A)c5Grvif*VgS@!)0yi`yozz;pE+i6*t#cTb@tuiu?Jw_{X!eN~~v2 z8hvvO{mq#C=U)21#l;6-r|oCbaFBKAWxB$61;ohx#JWZDY-|dffp^c&%Wdx#qR z6uofu4fl^<-tXn*Smhd7xB}P%7BiGGn1UGIVi%6{OsG=V-T*TKZkFyY%U`-@pa1)v t?7dg~_NJQO`<+66ugts2%)pRflJ`-~V1t`D!&{K2JYD@<);T3K0RVE$hAjX9 literal 263 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC==R92;Ln`LHy}6P1kO6~BpmgBU zqzP{t*c17)yngi@P)K9vpVVb$!l~(}6m{z_xSiX1U{qlFvR> zYR=2w*=ce;I4W+xe7oTt@0Ih4!*>_XT(*O)D|(^N-g5rpzaM5ZRt2t|Tr9o*w|c@709Ovok K=d#Wzp$Pz&BwsrK 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 353efa3f395a6167f2bcc1f68b75b1b6b2f5c950..c08f70d091d56f6538f7c3f7322d9d692c5a5fed 100644 GIT binary patch literal 1374 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zcaljKx9jP7LeL$-HD>V3qK6aSW-L z^Y*S`k#IP}@rT=IPPr2Dkl{#)jKjMbJC-WPI6r>K!s9$s+hXN{uRc~a47+A5X;-uT zyNtp6(8bH^mc(YB{r_7lWyAe-7yMUG-THni6VL`^Abj=BZ{}N<*8Tl;)Bo%v{kZ$u zPoKOmd%tm8;kSg;Kl8mUe-{7vxPINbRrTLr{d`&U=lRk(du$&4+pqQX$<2@7{(t&= zIq2Pf%UA#Gf0h1CuHC|r72npJ?Ukv;|5cuEkEVeplfo>94mE~FoDCUBOnbR%=DwAg ze7|o0Y~7aIZ)NA*$KWE&aFNBqgi*kk1m;_V*UU22b$@iPv+t<8vE}pm)3>M3zgC~0 zd+M#8x%koW=V3>zUtr-3{-8nSbAF|6RnQ5cP%2Q0Cg(^xv8_eP;wwf&s}8T+Cayyp?X( z&)=5be_t=}`m=U>TU*)G#m8pI|J&v(KK1@Q`Tv__D(e27`o-YvBfYOm^3etHCI3HN zo-Y6ISDa@5Kgs7=-)~P3T=(v9(x3IO9ly-9NO-&dMYVgssj*}9t&7jk<+hkjJEa29qyuG}$nQna9vDP*$R^a44 z7MpwA(*P7<`i&B<>&=7I3RXSfKt!Kffz%C%!*lz5k@$p>1zY0X=_@Ej9gW zzm3fQ&b{XD;8YREv~hB+e*^#8vO{l?^y5tlJn4(%TkkFT{*|FN?Uz;cg_@c3|5j?M z>u;YOy#GAwrP$jWB>snLs-NF=eE+UpQNO>IzLv|`{D1oU`O}L2pFJne_O57S#buMz zKR;T3t6y_6`}@!T@+H9FVgB-SUflnTj#kNCJc0)T!nQvRNG{8DKWi?&?9QLU!`n@l zN~$z?#tCfBo4zlv|10CRf-~%YR~;=gv3-7Lzq9ya|5k<{b!)1(y{`xoU}&Cw%KL=f zHT_BDExq%8$sXPQ>l~*^>&^RSAI&sC!SQ~)S=hyzSjTu?5X{k`M2iH-ZayDsejagA{&hU z9Y1`|y&f$uJUsq?apv>qr*9{}tdpJn%o%fgm- z$*42@IM&y1uiuE7tHEj5>?gyqnACz-+7g>uUmf~7+uqaQCqse{uJnzqbbzTXUB@3# p49wi8jn4h~5F9HHE4OAW`p+=+u=8Bk{cnRoVxF#kF6*2UngE@xakKyc literal 769 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zf+;U^?OH;uumf=j~m?ERjTshKKbk zOP5B>yeDDJpYUo+*(~OY#s`8b5mjt!x9~>1W7KJ8zo2}he~Dq2q~e;{y_NIZQxns= z^8ekfOkDjJXdDW-6LQ(=yJ`Q=OP|~Mm(3TycSkn)|LHx~=B&25^EdV1V#9Cte}Dg6 zeKqUjt&r=UHAWE_tWP!%8WX5+p^8)?w=eA=$_uAZe_v^m@ z=Z~w;o{x|Ee|hir&krZ}OV8i`r2lLE?q7el=2ggj{waNjhui=2m+8mv_$!bbD_8?z t?RNVEyEE 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 cdeedb4c24b6843544fa4ca348296f2536b90cd6..7c685a0a408cf76f0ffc0fc229a211e6413efa31 100644 GIT binary patch literal 1220 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zcaljKx9jP7LeL$-HD>V5#$TaSW-L z^Y*T7p0v9J+k=}5slQB$@?G8)F|6FAxYcXw4=zpC$AQ~CGill%M63@r{_Pu@(ZGWCA9-FPX`5@c{-+N)oAdn%${y{y0ddjJ2s z_4`AwMpftUueo^jviz;#6}6>n{*}l6zZ|zm-~5;Twfpi=TlxdvD?YPU)aud~OwU!eo|)3?>H?Muk}n0=^6#Y78#I42w7!E)o#Fb-mX7-aPsLYkG|B z|1P`EF7c@4Pt>pZk1N*i{&)0tzSqtFd+u5O>z{vS%l}x--}QHL>&t=~Qhn4WKYM>~ zkIkHI|2Iwf8~^*Z{OODLzVuf9UN$A&J~A2VXobUW{QTFW%&qkQ3%uR#r5I&?UTW3* z&Dk?{4>>HQvxZWBLDW(w`sa zC)E9rerx{h`2Jd_+U<|j&h6{_yoTT4dhxm){|;)Y-@E_4+%W(DoFDukceZ?1)r?N= zdF&V-b(~p-Qw?Za?$e--*z0rd*Sly>w>h)(Y;0x4-i9whjXxNAa%1b?=p5G*z!R)q ziZ?W!uj@%Ng5qE^_YuFyCu=rVmQC-0#YhcXLiUz#H;>QJJ;dR#<(}XJt-{GCm;5|% z^XLEQ&-cH-^!{nYx!?Bx|9}0b&(`l>bMqU>hbN!UufDyW{q4TBMFG!`{rS`TufNjn z)2g<7f4b?DnEt~@KP}#V|GIVi|NTC<_rI@8&b@y0_rGt)iwca<%ktIqR7| zRqv}SDl9T?uRXqS?H_@P&{lTZNC3W8n%~#y~*L`*yIAH-J<4x0l#+4ECn|qkj>OgVh M>FVdQ&MBb@07DZjVgLXD literal 542 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zf+;V0`N7;uumf=j~lXFJ?!Hwuh1% zw`>vSSJ1R|*e1MY?H+f5euX&aH#tWy$tYDZSZ~}Szp+!{rpClx&ikKBcAwq9`80R* zSrMRd#DJo!`=)NcwkZD0n-$wm{=WY8ulMWP@a8l2D|0`Y{Q2{;WY>p(|D3O@nIAs? z-gx)g+@g=We{Nm(FM}!J^@Bh5s8{~HWb?vtIpQq=3{xAHTdBasZ{vR)P=oGv2 VvzR7}3vsJKlAf-9F6*2UngAYTyTJee 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 9c3943fbf26ecf93db9b406f4955db7941abaa35..c1ca9c4d489e08820423c02af465fb7535ae8dd4 100644 GIT binary patch delta 279 zcmeyyxQS_kNGZx^prw85kJec)B=-RLpsMYolnBfk^AaBgw8$8G0J5Z@fCR z?OlRf0)y7=h?SNmPA5)(o$-J52F~f)PAwMfK#d^4`!S4pezkb~?f%0L@*eK1h+qCt zVz=fV`*`==bE;Q2#s=k88F-fj8(2Ts8M{{Yuh!;ekDrUzZzopr09zY+EdT%j literal 246 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=dp%toLn`LHy}41a$w9>RV!v}r z3ga{e+ZngE+B$HGIi&1TGw4gR`d6EOqgu)6-M+>QW}q$*h`N+I=WA8pCR=+y=R40d zpW4pfCEs^>`PMakr{{d>`fm|&J#@mS%xTx<1AZFZmtX!w1&&=LGiJ#v sysp~Qb=qLs_SfI5&!y@E?JrYTnzyNvH{Q*6Z!5?>p00i_>zopr01r%D7ytkO 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 0f4ab68d1569e959c7cd17c2ad9f83e6a37ff345..9fc2e5ba275c279ff1ba67a04e0fc3e5d38cb12f 100644 GIT binary patch delta 308 zcmZo?I?psgrJk`k$lZxy-8q?;3=E8%o-U3d6?5L+vgT`Y5NUnby>U^I;+un!9o`F; zn@szr*JyjAuR1|7Y+KY14-;c$&O?kp_qQBTIQlS!<9bgT0|Xc-N4|KnOJm*7ot3-S zt=en({7kjI$A)c5Grvif*VgS@!)0yi`yozz;pE+i6*t#cTb@tuiu?Jw_{X!eN~~v2 z8hvvO{mq#C=U)21#l;6-r|oCbaFBKAWxB$61;ohx#JWZDY-|dffp^c&%Wdx#qR z6uofu4fl^<-tXn*Smhd7xB}P%7BiGGn1UGIVi%6{OsG=V-T*TKZkFyY%U`-@pa1)v t?7dg~_NJQO`<+66ugts2%)pRflJ`-~V1t`D!&{K2JYD@<);T3K0RVE$hAjX9 literal 263 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC==R92;Ln`LHy}6P1kO6~BpmgBU zqzP{t*c17)yngi@P)K9vpVVb$!l~(}6m{z_xSiX1U{qlFvR> zYR=2w*=ce;I4W+xe7oTt@0Ih4!*>_XT(*O)D|(^N-g5rpzaM5ZRt2t|Tr9o*w|c@709Ovok K=d#Wzp$Pz&BwsrK From 337a29ac2d4e727b8b44d6b8ac66b6eac317a491 Mon Sep 17 00:00:00 2001 From: PJBot Date: Wed, 5 Jun 2024 21:17:38 +0000 Subject: [PATCH 110/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 6adbf16221..adbad1269b 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- 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. @@ -3848,3 +3841,10 @@ 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 From d11740f0dfc3212ec439e76c12b81c7bb8c34257 Mon Sep 17 00:00:00 2001 From: Cojoke <83733158+Cojoke-dot@users.noreply.github.com> Date: Wed, 5 Jun 2024 17:03:31 -0500 Subject: [PATCH 111/158] Clean up Eva and Hardsuit helm yml + Lets atmos firesuit helm work as a BreathMask (#28602) --- .../Clothing/Head/base_clothinghead.yml | 2 ++ .../Entities/Clothing/Head/eva-helmets.yml | 6 ---- .../Clothing/Head/hardsuit-helmets.yml | 31 ------------------- .../Entities/Clothing/Head/helmets.yml | 1 + 4 files changed, 3 insertions(+), 37 deletions(-) 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 From 2a372f860432a403aca1933f05345f7c8fb81afe Mon Sep 17 00:00:00 2001 From: Cojoke <83733158+Cojoke-dot@users.noreply.github.com> Date: Wed, 5 Jun 2024 17:04:09 -0500 Subject: [PATCH 112/158] Shifts borgs hats to the right a bit (#28600) --- Resources/Prototypes/InventoryTemplates/borg.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 From 2d755d6301d8218bd7810b29e1007a6fde4ee3c3 Mon Sep 17 00:00:00 2001 From: Verm <32827189+Vermidia@users.noreply.github.com> Date: Wed, 5 Jun 2024 17:06:57 -0500 Subject: [PATCH 113/158] Fix mouse inhands (#28623) --- Resources/Prototypes/Entities/Mobs/NPCs/animals.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index f4f57ac451..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 From c3965ca8a386eaa052e243c9ca9ecb896912d7d3 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Wed, 5 Jun 2024 18:08:41 -0400 Subject: [PATCH 114/158] Adjust some touch reaction damage levels (#28591) --- Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml | 2 +- Resources/Prototypes/Entities/Mobs/Species/diona.yml | 8 ++++---- Resources/Prototypes/Entities/Mobs/Species/slime.yml | 2 +- Resources/Prototypes/Entities/Objects/Misc/kudzu.yml | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) 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/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/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 From d461f95802ce253afaeca5232c4209aa72d8747b Mon Sep 17 00:00:00 2001 From: PJBot Date: Wed, 5 Jun 2024 22:09:47 +0000 Subject: [PATCH 115/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index adbad1269b..25f48ec5f7 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- 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! @@ -3848,3 +3841,11 @@ 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 From 7d903800a843ab718748b893b5ff1370997160c4 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Thu, 6 Jun 2024 10:14:25 +1200 Subject: [PATCH 116/158] Add closing storage UIs to StorageInteractionTest (#28633) --- .../Tests/Storage/StorageInteractionTest.cs | 12 ++++++++++++ .../Storage/EntitySystems/SharedStorageSystem.cs | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Content.IntegrationTests/Tests/Storage/StorageInteractionTest.cs b/Content.IntegrationTests/Tests/Storage/StorageInteractionTest.cs index 34402dd5e6..8d0de707f3 100644 --- a/Content.IntegrationTests/Tests/Storage/StorageInteractionTest.cs +++ b/Content.IntegrationTests/Tests/Storage/StorageInteractionTest.cs @@ -4,6 +4,7 @@ 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; @@ -27,11 +28,22 @@ public sealed class StorageInteractionTest : InteractionTest 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); diff --git a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs index 874b17a03a..f1793a3951 100644 --- a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs @@ -299,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; @@ -309,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); From b693a16b93e39d7fc89afdbff1229fef4c5df493 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Thu, 6 Jun 2024 02:37:49 -0400 Subject: [PATCH 117/158] Add support for LocalizedDatasets to RandomMetadata (#28601) --- Content.Server/RandomMetadata/RandomMetadataSystem.cs | 11 +++++++++-- .../Random/Helpers/SharedRandomExtensions.cs | 6 +++++- 2 files changed, 14 insertions(+), 3 deletions(-) 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.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) From 3a3502dd44d06b67eeaec84919bcd52b0fe69419 Mon Sep 17 00:00:00 2001 From: Repo <47093363+Titian3@users.noreply.github.com> Date: Thu, 6 Jun 2024 18:41:11 +1200 Subject: [PATCH 118/158] Fix Admin Object tab sorting and search (#28609) --- .../UI/Tabs/ObjectsTab/ObjectsTab.xaml | 16 ++- .../UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs | 119 ++++++++++++++---- .../UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml | 8 +- .../Tabs/ObjectsTab/ObjectsTabEntry.xaml.cs | 6 +- .../UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml | 21 ++++ .../Tabs/ObjectsTab/ObjectsTabHeader.xaml.cs | 86 +++++++++++++ .../UI/Tabs/PlayerTab/PlayerTab.xaml.cs | 5 +- .../Systems/Admin/AdminUIController.cs | 7 +- .../administration/ui/tabs/object-tab.ftl | 2 + 9 files changed, 230 insertions(+), 40 deletions(-) create mode 100644 Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml create mode 100644 Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabHeader.xaml.cs create mode 100644 Resources/Locale/en-US/administration/ui/tabs/object-tab.ftl 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/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/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 From 282eb4613374222570bb8458140e2e5d36e1a877 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 6 Jun 2024 06:42:18 +0000 Subject: [PATCH 119/158] Automatic changelog update --- Resources/Changelog/Admin.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Resources/Changelog/Admin.yml b/Resources/Changelog/Admin.yml index b03b0b54fb..e585c50134 100644 --- a/Resources/Changelog/Admin.yml +++ b/Resources/Changelog/Admin.yml @@ -281,5 +281,14 @@ 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 Name: Admin Order: 3 From b702d3b466ba53117388d2458bd26de4d8f40144 Mon Sep 17 00:00:00 2001 From: Plykiya <58439124+Plykiya@users.noreply.github.com> Date: Thu, 6 Jun 2024 00:01:45 -0700 Subject: [PATCH 120/158] Internals are kept on as long as any breathing tool is on (#28595) --- .../AtmosphereSystem.BreathTool.cs | 14 ++++----- .../Atmos/EntitySystems/GasTankSystem.cs | 2 +- .../Body/Components/InternalsComponent.cs | 2 +- .../Body/Systems/InternalsSystem.cs | 30 ++++++++----------- Content.Server/Body/Systems/LungSystem.cs | 2 +- 5 files changed, 23 insertions(+), 27 deletions(-) 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 { From 8c39ad4ceeb879d6d774c850732878d038a72820 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 6 Jun 2024 07:02:51 +0000 Subject: [PATCH 121/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 25f48ec5f7..43fd543c17 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- 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. @@ -3849,3 +3842,11 @@ 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 From 1f4b454b937653c890f2fe526216366056dd9311 Mon Sep 17 00:00:00 2001 From: Cojoke <83733158+Cojoke-dot@users.noreply.github.com> Date: Thu, 6 Jun 2024 04:32:36 -0500 Subject: [PATCH 122/158] Gives Insulation and NoSlip to all bots (#28621) * Gives Insulation and NoSlip to all bots * remove NoSlip from children --- Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml index 12788e457f..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,7 +281,6 @@ - type: Construction graph: MediBot node: bot - - type: NoSlip - type: Anchorable - type: InteractionPopup interactSuccessString: petting-success-medibot From fabf32698f9fd89ccbb96b71e2dfb79145754057 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 6 Jun 2024 09:33:43 +0000 Subject: [PATCH 123/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 43fd543c17..f3497a43bd 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- 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 @@ -3850,3 +3843,10 @@ 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 From 884c3cb3a6a7e62c91d36e5064d2c4b08ca1f9be Mon Sep 17 00:00:00 2001 From: Flareguy <78941145+Flareguy@users.noreply.github.com> Date: Thu, 6 Jun 2024 05:08:25 -0500 Subject: [PATCH 124/158] Nerfs welderbombing (#28650) nerf welderbombing --- .../Prototypes/Entities/Structures/Storage/Tanks/tanks.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/Prototypes/Entities/Structures/Storage/Tanks/tanks.yml b/Resources/Prototypes/Entities/Structures/Storage/Tanks/tanks.yml index df19550cdb..e7fcf964d1 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Tanks/tanks.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Tanks/tanks.yml @@ -32,7 +32,7 @@ - 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 @@ -74,7 +74,7 @@ maxVol: 5000 - type: Explosive explosionType: Default - totalIntensity: 140 + totalIntensity: 120 # Water From b51bbeca3976bdfc178b3544ba7585bd3cfc29e1 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 6 Jun 2024 10:09:31 +0000 Subject: [PATCH 125/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index f3497a43bd..ee9da0995d 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- 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 @@ -3850,3 +3843,10 @@ 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 From 78ed25868655df0a207d4ec76e39fd538cf59fa5 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 6 Jun 2024 12:07:04 +0000 Subject: [PATCH 126/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index ee9da0995d..f84e1b6391 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- 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. @@ -3850,3 +3843,11 @@ 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 From 45685c8b08ef63e141d2aeb39b932772f1c5c088 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 6 Jun 2024 19:25:20 +0000 Subject: [PATCH 127/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index f84e1b6391..2be98160bc 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,13 +1,4 @@ Entries: -- 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. @@ -3851,3 +3842,10 @@ 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 From 5928009203bcf9d1b9cee27e1a7959671b897ba3 Mon Sep 17 00:00:00 2001 From: "Mr. 27" <45323883+Dutch-VanDerLinde@users.noreply.github.com> Date: Thu, 6 Jun 2024 18:26:13 -0400 Subject: [PATCH 128/158] fix janitor not spawning with a survival box (#28669) hi --- Resources/Prototypes/Roles/Jobs/Civilian/janitor.yml | 3 +++ 1 file changed, 3 insertions(+) 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 From 5c93f05cfeeba7fabb5e2b118118213184495752 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 6 Jun 2024 22:27:19 +0000 Subject: [PATCH 129/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 2be98160bc..bfd8060d11 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,13 +1,4 @@ Entries: -- 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. @@ -3849,3 +3840,10 @@ 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 From 2e50a3bf3b11896a19b98d4a43b812f1bf9053a6 Mon Sep 17 00:00:00 2001 From: blueDev2 <89804215+blueDev2@users.noreply.github.com> Date: Thu, 6 Jun 2024 21:24:07 -0400 Subject: [PATCH 130/158] Return medicine recipe solid material costs to 1 (#28679) Set material costs of medicine recipes to 1 --- Resources/Prototypes/Recipes/Cooking/medical_recipes.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/Prototypes/Recipes/Cooking/medical_recipes.yml b/Resources/Prototypes/Recipes/Cooking/medical_recipes.yml index 3bc9e22b0e..9d1947f03e 100644 --- a/Resources/Prototypes/Recipes/Cooking/medical_recipes.yml +++ b/Resources/Prototypes/Recipes/Cooking/medical_recipes.yml @@ -13,7 +13,7 @@ time: 10 solids: FoodPoppy: 1 - Brutepack: 10 + Brutepack: 1 MaterialCloth1: 1 reagents: TranexamicAcid: 20 @@ -26,7 +26,7 @@ time: 10 solids: FoodAloe: 1 - Ointment: 10 + Ointment: 1 MaterialCloth1: 1 reagents: Sigynate: 20 From be805b64b6a7f015f3b3dfa4a716e263f9947506 Mon Sep 17 00:00:00 2001 From: PJBot Date: Fri, 7 Jun 2024 01:25:13 +0000 Subject: [PATCH 131/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index bfd8060d11..31999b221c 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- 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 @@ -3847,3 +3840,11 @@ 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 From c39af755885f2f94a964600ea853cb7c62d590b9 Mon Sep 17 00:00:00 2001 From: PJBot Date: Fri, 7 Jun 2024 11:30:02 +0000 Subject: [PATCH 132/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 31999b221c..3865ceee62 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- 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 @@ -3848,3 +3841,10 @@ 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 From b45a42f353cefa6d7904bb082f58c813b018449a Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+electrojr@users.noreply.github.com> Date: Fri, 7 Jun 2024 18:43:02 +0200 Subject: [PATCH 133/158] Add JobRequirementOverride prototypes (#28607) * Add JobRequirementOverride prototypes * a * invert if * Add override that takes in prototypes directly --- .../Lobby/UI/HumanoidProfileEditor.xaml.cs | 3 +- .../JobRequirementsManager.cs | 8 ++- Content.Server/Antag/AntagSelectionSystem.cs | 3 ++ .../Roles/Components/GhostRoleComponent.cs | 4 ++ .../PlayTimeTrackingSystem.cs | 37 ++++--------- Content.Shared/CCVar/CCVars.cs | 7 +++ .../Ghost/Roles/GhostRolePrototype.cs | 12 ++--- .../Ghost/Roles/GhostRolesEuiMessages.cs | 5 ++ Content.Shared/Roles/AntagPrototype.cs | 4 +- Content.Shared/Roles/JobPrototype.cs | 2 +- .../Roles/JobRequirementOverridePrototype.cs | 20 +++++++ Content.Shared/Roles/JobRequirements.cs | 20 +++---- Content.Shared/Roles/SharedRoleSystem.cs | 52 ++++++++++++++++++- .../Roles/requirement_overrides.yml | 16 ++++++ 14 files changed, 145 insertions(+), 48 deletions(-) create mode 100644 Content.Shared/Roles/JobRequirementOverridePrototype.cs create mode 100644 Resources/Prototypes/Roles/requirement_overrides.yml diff --git a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs index 6c6d9ca977..ec4701dbe3 100644 --- a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs +++ b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs @@ -621,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); 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.Server/Antag/AntagSelectionSystem.cs b/Content.Server/Antag/AntagSelectionSystem.cs index 710bb8f3d7..3491cc727c 100644 --- a/Content.Server/Antag/AntagSelectionSystem.cs +++ b/Content.Server/Antag/AntagSelectionSystem.cs @@ -374,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/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/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.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index 61358914c4..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. /// 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/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/JobPrototype.cs b/Content.Shared/Roles/JobPrototype.cs index cbdcb55079..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")] 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/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/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 From fdf0ec422a3c635c76734a5089d03540109998ac Mon Sep 17 00:00:00 2001 From: Ubaser <134914314+UbaserB@users.noreply.github.com> Date: Sat, 8 Jun 2024 04:39:22 +1000 Subject: [PATCH 134/158] Tweak chapel salvage wreck (#28703) add --- Resources/Maps/Salvage/small-chapel.yml | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) 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 From 5546c21aac2d107d7e19b4d2dcddc93f6e407e26 Mon Sep 17 00:00:00 2001 From: lzk <124214523+lzk228@users.noreply.github.com> Date: Sat, 8 Jun 2024 02:56:25 +0200 Subject: [PATCH 135/158] Add locale support for booze and soda jugs labels (#28708) --- .../Consumable/Drinks/drinks-cartons.yml | 2 +- .../Consumable/Drinks/drinks_bottles.yml | 62 ++++++++++--------- 2 files changed, 33 insertions(+), 31 deletions(-) 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 7287119019..c9e28954be 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_bottles.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_bottles.yml @@ -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 From 48534934bd7ed421cbc98e78668caf07932468c2 Mon Sep 17 00:00:00 2001 From: Plykiya <58439124+Plykiya@users.noreply.github.com> Date: Fri, 7 Jun 2024 17:57:07 -0700 Subject: [PATCH 136/158] Swap some InRangeUnobstructed for InRangeUnoccluded (#28706) Swap InRangeUnobstructed to InRangeUnoccluded Co-authored-by: plykiya --- Content.Client/Construction/ConstructionSystem.cs | 4 ++-- Content.Server/Chat/Systems/ChatSystem.cs | 6 ++++-- Content.Server/Instruments/InstrumentSystem.cs | 7 ++++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Content.Client/Construction/ConstructionSystem.cs b/Content.Client/Construction/ConstructionSystem.cs index 66000a8457..453658bebf 100644 --- a/Content.Client/Construction/ConstructionSystem.cs +++ b/Content.Client/Construction/ConstructionSystem.cs @@ -27,6 +27,7 @@ namespace Content.Client.Construction [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; + [Dependency] private readonly ExamineSystemShared _examineSystem = default!; [Dependency] private readonly SharedTransformSystem _transformSystem = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; @@ -195,9 +196,8 @@ namespace Content.Client.Construction if (GhostPresent(loc)) return false; - // This InRangeUnobstructed should probably be replaced with "is there something blocking us in that tile?" var predicate = GetPredicate(prototype.CanBuildInImpassable, loc.ToMap(EntityManager, _transformSystem)); - if (!_interactionSystem.InRangeUnobstructed(user, loc, 20f, predicate: predicate)) + if (!_examineSystem.InRangeUnOccluded(user, loc, 20f, predicate: predicate)) return false; if (!CheckConstructionConditions(prototype, loc, dir, user, showPopup: true)) diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs index f6bc4a25c0..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; @@ -63,6 +65,7 @@ public sealed partial class ChatSystem : SharedChatSystem [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!; @@ -514,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/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) From d130dac32987fb4e88e1c71a97825fa098970446 Mon Sep 17 00:00:00 2001 From: PJBot Date: Sat, 8 Jun 2024 00:58:14 +0000 Subject: [PATCH 137/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 3865ceee62..315c8f6552 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- 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. @@ -3848,3 +3841,11 @@ 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 From bd54ddce12d027f9cdca049c0a67d2a258256e9c Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Fri, 7 Jun 2024 22:34:48 -0700 Subject: [PATCH 138/158] Fix action icons when dragging an active action to another slot (#28692) --- .../UserInterface/Systems/Actions/Controls/ActionButton.cs | 4 ++++ 1 file changed, 4 insertions(+) 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); From 58e1b529fe977994f5dd1beb5792760ad5c347e0 Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Fri, 7 Jun 2024 22:36:29 -0700 Subject: [PATCH 139/158] Add DoPopup data field to OnUseTimerTrigger (#28691) --- .../Explosion/EntitySystems/TriggerSystem.OnUse.cs | 3 ++- .../Explosion/Components/OnUseTimerTriggerComponent.cs | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) 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.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; } } From 3d56f8dfd537e153db061502d7d53f81d16bddce Mon Sep 17 00:00:00 2001 From: Lyndomen <49795619+Lyndomen@users.noreply.github.com> Date: Sat, 8 Jun 2024 01:49:42 -0400 Subject: [PATCH 140/158] Dropping Corpses Devoured by Space Dragons on Gib/Butcher. (#28709) * Update DevourSystem.cs * Update DevourSystem.cs * Update Content.Server/Devour/DevourSystem.cs --------- Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> --- Content.Server/Devour/DevourSystem.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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); + } } From b80e2fbe6c2b1b688c6588381b1df27d0a6e4d1e Mon Sep 17 00:00:00 2001 From: PJBot Date: Sat, 8 Jun 2024 05:50:48 +0000 Subject: [PATCH 141/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 315c8f6552..585e10a22d 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- 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. @@ -3849,3 +3842,10 @@ 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 From 44bd6b37c18fb8bdf82d15e0f4c72863211e6ea6 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Sat, 8 Jun 2024 01:53:17 -0400 Subject: [PATCH 142/158] Improve grammar of various petting messages (#28715) Improve grammar of various petting message --- .../interaction-popup-component.ftl | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) 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. From c75ac999fac9de01ff70430f934fd853361054dc Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Fri, 7 Jun 2024 22:53:54 -0700 Subject: [PATCH 143/158] Fix DamageOtherOnHit.OnDoHit when the target is terminating or deleted (#28690) --- .../Damage/Systems/DamageOtherOnHitSystem.cs | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) 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. From e787d02b73f7120a0df23f62e9345cd9e8e5a04e Mon Sep 17 00:00:00 2001 From: MerrytheManokit <167581110+MerrytheManokit@users.noreply.github.com> Date: Sat, 8 Jun 2024 04:48:49 -0400 Subject: [PATCH 144/158] rn, atp (#28674) * Update speech-chatsan.ftl * Update word_replacements.yml * Atm-at the moment * Atm * Update Resources/Locale/en-US/speech/speech-chatsan.ftl * Update Resources/Prototypes/Accents/word_replacements.yml --------- Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> --- Resources/Locale/en-US/speech/speech-chatsan.ftl | 8 +++++++- Resources/Prototypes/Accents/word_replacements.yml | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Resources/Locale/en-US/speech/speech-chatsan.ftl b/Resources/Locale/en-US/speech/speech-chatsan.ftl index ef94507295..df6cde00a2 100644 --- a/Resources/Locale/en-US/speech/speech-chatsan.ftl +++ b/Resources/Locale/en-US/speech/speech-chatsan.ftl @@ -128,4 +128,10 @@ chatsan-word-46 = tyvm chatsan-replacement-46 = thank you very much chatsan-word-47 = cya -chatsan-replacement-47 = see ya \ No newline at end of file +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/Prototypes/Accents/word_replacements.yml b/Resources/Prototypes/Accents/word_replacements.yml index 3567e15269..ebeefd5a0e 100644 --- a/Resources/Prototypes/Accents/word_replacements.yml +++ b/Resources/Prototypes/Accents/word_replacements.yml @@ -428,6 +428,8 @@ 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 From 0da82f6d47860ef9b716e5aa6b8302c24e3ce413 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sat, 8 Jun 2024 20:03:54 +1000 Subject: [PATCH 145/158] Add loadout group check for role proto (#28731) MFW same bug twice at 2 layers because I'm stupid. --- Content.Shared/Preferences/Loadouts/RoleLoadout.cs | 7 +++++++ 1 file changed, 7 insertions(+) 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)) { From 6e7a632c38d96623a81fa50ce8365f29cd17f501 Mon Sep 17 00:00:00 2001 From: PJBot Date: Sat, 8 Jun 2024 10:05:00 +0000 Subject: [PATCH 146/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 585e10a22d..f2938fa3ba 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- 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. @@ -3849,3 +3842,10 @@ 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 From e08280c6e1d540287938befd9cf4ec9f2f526ea7 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sat, 8 Jun 2024 22:27:21 +1200 Subject: [PATCH 147/158] Try fix RGBee (#28741) --- Content.Client/Light/RgbLightControllerSystem.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 From 698cde53f012a6e1359fa7ecb6b6c35b330d96ff Mon Sep 17 00:00:00 2001 From: PJBot Date: Sat, 8 Jun 2024 10:28:27 +0000 Subject: [PATCH 148/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index f2938fa3ba..87413276dc 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- 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 @@ -3849,3 +3842,10 @@ 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 From 55f09b8ffaa4481016f32cf6e35af078420018fc Mon Sep 17 00:00:00 2001 From: Vasilis Date: Sat, 8 Jun 2024 13:33:51 +0300 Subject: [PATCH 149/158] Ensure packager creates a release folder (#28426) I was screaming at the github actions runner before i noticed something i had done before caused me to never have a release folder and thus fail. --- Content.Packaging/Program.cs | 5 +++++ 1 file changed, 5 insertions(+) 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(); From 3ce06b4ed5eb6fb27b688026231e8cb6280cd91e Mon Sep 17 00:00:00 2001 From: PJBot Date: Sat, 8 Jun 2024 10:39:01 +0000 Subject: [PATCH 150/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 87413276dc..7570e21aa0 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- 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 @@ -3849,3 +3842,11 @@ 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 From aa1fd64b445b1630cf9378e9c68ea2057c4dc757 Mon Sep 17 00:00:00 2001 From: nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com> Date: Sat, 8 Jun 2024 10:40:33 +0000 Subject: [PATCH 151/158] Make freeze, freeze & mute, and unmute verbs work on disconnected players. (#28664) --- .../Administration/Systems/AdminVerbSystem.cs | 107 +++++++++--------- 1 file changed, 54 insertions(+), 53 deletions(-) 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)) { From 2d61e4e434fe30a8de7c4391d0a37b3056a36ade Mon Sep 17 00:00:00 2001 From: PJBot Date: Sat, 8 Jun 2024 10:41:39 +0000 Subject: [PATCH 152/158] Automatic changelog update --- Resources/Changelog/Admin.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Resources/Changelog/Admin.yml b/Resources/Changelog/Admin.yml index e585c50134..fc3a3f8b8d 100644 --- a/Resources/Changelog/Admin.yml +++ b/Resources/Changelog/Admin.yml @@ -290,5 +290,13 @@ Entries: 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 From 27b6ccbb6a7c3de440013beed45f53a33f6f5d98 Mon Sep 17 00:00:00 2001 From: PJBot Date: Sat, 8 Jun 2024 14:37:53 +0000 Subject: [PATCH 153/158] Automatic changelog update --- Resources/Changelog/Changelog.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 7570e21aa0..fb1a4bad31 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- 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. @@ -3850,3 +3842,11 @@ 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 From 303e71d84c27aa587a28a995f1715458827032e9 Mon Sep 17 00:00:00 2001 From: null <56081759+NullWanderer@users.noreply.github.com> Date: Sat, 8 Jun 2024 20:37:53 +0200 Subject: [PATCH 154/158] Some code fixes --- Content.Server/Chat/Systems/ChatSystem.Emote.cs | 7 +++++-- Content.Server/Spawners/EntitySystems/SpawnPointSystem.cs | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Content.Server/Chat/Systems/ChatSystem.Emote.cs b/Content.Server/Chat/Systems/ChatSystem.Emote.cs index 8d8ab442b6..837b00b3ad 100644 --- a/Content.Server/Chat/Systems/ChatSystem.Emote.cs +++ b/Content.Server/Chat/Systems/ChatSystem.Emote.cs @@ -170,8 +170,11 @@ public partial class ChatSystem if (!_wordEmoteDict.TryGetValue(actionLower, out var emotes)) return; - if (!AllowedToUseEmote(uid, emote)) - 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 { diff --git a/Content.Server/Spawners/EntitySystems/SpawnPointSystem.cs b/Content.Server/Spawners/EntitySystems/SpawnPointSystem.cs index c9ea683f0c..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) { From a1c590fe533ad80540f5c02f6654116ee281e064 Mon Sep 17 00:00:00 2001 From: null <56081759+NullWanderer@users.noreply.github.com> Date: Sat, 8 Jun 2024 20:44:24 +0200 Subject: [PATCH 155/158] Fix duplicate locale entries --- Resources/Locale/en-US/deltav/flavors/flavor-profiles.ftl | 2 -- .../en-US/deltav/prototypes/catalog/cargo/cargo-fun.ftl | 2 -- Resources/Locale/en-US/nyanotrasen/abilities/psionic.ftl | 3 --- Resources/Locale/en-US/nyanotrasen/accent/accents.ftl | 3 +-- .../nyanotrasen/reagents/meta/consumable/drink/drink.ftl | 6 ------ Resources/Locale/en-US/nyanotrasen/seeds/seeds.ftl | 2 -- Resources/Locale/en-US/nyanotrasen/tools/tool-qualities.ftl | 2 -- 7 files changed, 1 insertion(+), 19 deletions(-) delete mode 100644 Resources/Locale/en-US/deltav/prototypes/catalog/cargo/cargo-fun.ftl delete mode 100644 Resources/Locale/en-US/nyanotrasen/seeds/seeds.ftl delete mode 100644 Resources/Locale/en-US/nyanotrasen/tools/tool-qualities.ftl 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/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 From 6efdbf66c9d48d38b6220a20757e6ba57df22715 Mon Sep 17 00:00:00 2001 From: Scribbles0 <91828755+scribbles0@users.noreply.github.com> Date: Sat, 8 Jun 2024 20:47:28 +0200 Subject: [PATCH 156/158] Unrevivable trait (#24226) * unrevivable trait + remove unclonable remnants * cleanup * change to hascomp --- Content.Server/Medical/DefibrillatorSystem.cs | 6 ++++++ Content.Server/Traits/Assorted/UnrevivableComponent.cs | 10 ++++++++++ .../Locale/en-US/medical/components/defibrillator.ftl | 1 + 3 files changed, 17 insertions(+) create mode 100644 Content.Server/Traits/Assorted/UnrevivableComponent.cs 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/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/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. From b70880595d82a8b942bd192ae1398dcb3b8290c3 Mon Sep 17 00:00:00 2001 From: null <56081759+NullWanderer@users.noreply.github.com> Date: Tue, 11 Jun 2024 13:21:14 +0200 Subject: [PATCH 157/158] Fix test fail? --- .../DeltaV/Entities/Objects/Consumable/Food/Baked/pie.yml | 1 - 1 file changed, 1 deletion(-) 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. From 976aa679e2c315f1922480560a968532c43db9e1 Mon Sep 17 00:00:00 2001 From: null <56081759+NullWanderer@users.noreply.github.com> Date: Tue, 11 Jun 2024 13:41:06 +0200 Subject: [PATCH 158/158] Fix number 2 hopefully --- .../Nyanotrasen/Entities/Objects/Consumable/Food/Baked/pizza.yml | 1 - 1 file changed, 1 deletion(-) 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