From eac759861a7183e04faa3aea28a6d34e0f80dd8a Mon Sep 17 00:00:00 2001 From: nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com> Date: Tue, 26 Mar 2024 21:15:08 +0200 Subject: [PATCH 001/295] Minor cleanup of cuffablesystem (#26434) * Fix cuffs breaking after they stop being pulled * Do proper interaction check for UncuffAttempt * Minor cleanup Take as much out as possible from _net.IsServer() if blocks. Misc cleanup --- .../Cuffs/Components/HandcuffComponent.cs | 10 +- Content.Shared/Cuffs/SharedCuffableSystem.cs | 105 +++++++++--------- 2 files changed, 61 insertions(+), 54 deletions(-) diff --git a/Content.Shared/Cuffs/Components/HandcuffComponent.cs b/Content.Shared/Cuffs/Components/HandcuffComponent.cs index 77a77cf2f84..30577da064f 100644 --- a/Content.Shared/Cuffs/Components/HandcuffComponent.cs +++ b/Content.Shared/Cuffs/Components/HandcuffComponent.cs @@ -52,6 +52,14 @@ public sealed partial class HandcuffComponent : Component [DataField] public bool Removing; + /// + /// Whether the cuffs are currently being used to cuff someone. + /// We need the extra information for when the virtual item is deleted because that can happen when you simply stop + /// pulling them on the ground. + /// + [DataField] + public bool Used; + /// /// The path of the RSI file used for the player cuffed overlay. /// @@ -87,7 +95,7 @@ public sealed partial class HandcuffComponent : Component } /// -/// Event fired on the User when the User attempts to cuff the Target. +/// Event fired on the User when the User attempts to uncuff the Target. /// Should generate popups on the User. /// [ByRefEvent] diff --git a/Content.Shared/Cuffs/SharedCuffableSystem.cs b/Content.Shared/Cuffs/SharedCuffableSystem.cs index 5cade56aca1..f2cc30c6291 100644 --- a/Content.Shared/Cuffs/SharedCuffableSystem.cs +++ b/Content.Shared/Cuffs/SharedCuffableSystem.cs @@ -3,12 +3,10 @@ using Content.Shared.Administration.Components; using Content.Shared.Administration.Logs; using Content.Shared.Alert; -using Content.Shared.Atmos.Piping.Unary.Components; using Content.Shared.Buckle.Components; using Content.Shared.Cuffs.Components; using Content.Shared.Database; using Content.Shared.DoAfter; -using Content.Shared.Effects; using Content.Shared.Hands; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; @@ -19,7 +17,6 @@ using Content.Shared.Inventory.Events; using Content.Shared.Inventory.VirtualItem; using Content.Shared.Item; -using Content.Shared.Mobs.Systems; using Content.Shared.Movement.Events; using Content.Shared.Movement.Pulling.Events; using Content.Shared.Popups; @@ -29,12 +26,12 @@ using Content.Shared.Timing; using Content.Shared.Verbs; using Content.Shared.Weapons.Melee.Events; -using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Serialization; +using Robust.Shared.Utility; using PullableComponent = Content.Shared.Movement.Pulling.Components.PullableComponent; namespace Content.Shared.Cuffs @@ -47,8 +44,6 @@ public abstract partial class SharedCuffableSystem : EntitySystem [Dependency] private readonly ISharedAdminLogManager _adminLog = default!; [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; [Dependency] private readonly AlertsSystem _alerts = default!; - [Dependency] private readonly SharedColorFlashEffectSystem _color = default!; - [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; @@ -95,9 +90,8 @@ public override void Initialize() private void OnUncuffAttempt(ref UncuffAttemptEvent args) { if (args.Cancelled) - { return; - } + if (!Exists(args.User) || Deleted(args.User)) { // Should this even be possible? @@ -109,23 +103,29 @@ private void OnUncuffAttempt(ref UncuffAttemptEvent args) // This is because the CanInteract blocking of the cuffs prevents self-uncuff. if (args.User == args.Target) { - // This UncuffAttemptEvent check should probably be In MobStateSystem, not here? - if (_mobState.IsIncapacitated(args.User)) - { - args.Cancelled = true; - } - else + if (!TryComp(args.User, out var cuffable)) { - // TODO Find a way for cuffable to check ActionBlockerSystem.CanInteract() without blocking itself + DebugTools.Assert($"{args.User} tried to uncuff themselves but they are not cuffable."); + return; } + + // We temporarily allow interactions so the cuffable system does not block itself. + // It's assumed that this will always be false. + // Otherwise they would not be trying to uncuff themselves. + cuffable.CanStillInteract = true; + Dirty(args.User, cuffable); + + if (!_actionBlocker.CanInteract(args.User, args.User)) + args.Cancelled = true; + + cuffable.CanStillInteract = false; + Dirty(args.User, cuffable); } else { // Check if the user can interact. if (!_actionBlocker.CanInteract(args.User, args.Target)) - { args.Cancelled = true; - } } if (args.Cancelled) @@ -310,6 +310,7 @@ private void OnAddCuffDoAfter(EntityUid uid, HandcuffComponent component, AddCuf if (!args.Cancelled && TryAddNewCuffs(target, user, uid, cuffable)) { + component.Used = true; _audio.PlayPredicted(component.EndCuffSound, uid, user); _popup.PopupEntity(Loc.GetString("handcuff-component-cuff-observer-success-message", @@ -611,7 +612,7 @@ public void Uncuff(EntityUid target, EntityUid? user, EntityUid cuffsToRemove, C if (!Resolve(target, ref cuffable) || !Resolve(cuffsToRemove, ref cuff)) return; - if (cuff.Removing || TerminatingOrDeleted(cuffsToRemove) || TerminatingOrDeleted(target)) + if (!cuff.Used || cuff.Removing || TerminatingOrDeleted(cuffsToRemove) || TerminatingOrDeleted(target)) return; if (user != null) @@ -623,10 +624,9 @@ public void Uncuff(EntityUid target, EntityUid? user, EntityUid cuffsToRemove, C } cuff.Removing = true; + cuff.Used = false; _audio.PlayPredicted(cuff.EndUncuffSound, target, user); - var isOwner = user == target; - _container.Remove(cuffsToRemove, cuffable.Container); if (_net.IsServer) @@ -642,43 +642,42 @@ public void Uncuff(EntityUid target, EntityUid? user, EntityUid cuffsToRemove, C { _hands.PickupOrDrop(user, cuffsToRemove); } + } + + if (cuffable.CuffedHandCount == 0) + { + if (user != null) + _popup.PopupPredicted(Loc.GetString("cuffable-component-remove-cuffs-success-message"), user.Value, user.Value); - // Only play popups on server because popups suck - if (cuffable.CuffedHandCount == 0) + if (target != user && user != null) + { + _popup.PopupPredicted(Loc.GetString("cuffable-component-remove-cuffs-by-other-success-message", + ("otherName", Identity.Name(user.Value, EntityManager, user))), target, target); + _adminLog.Add(LogType.Action, LogImpact.Medium, + $"{ToPrettyString(user):player} has successfully uncuffed {ToPrettyString(target):player}"); + } + else { - if (user != null) - _popup.PopupEntity(Loc.GetString("cuffable-component-remove-cuffs-success-message"), user.Value, user.Value); - - if (target != user && user != null) - { - _popup.PopupEntity(Loc.GetString("cuffable-component-remove-cuffs-by-other-success-message", - ("otherName", Identity.Name(user.Value, EntityManager, user))), target, target); - _adminLog.Add(LogType.Action, LogImpact.Medium, - $"{ToPrettyString(user):player} has successfully uncuffed {ToPrettyString(target):player}"); - } - else - { - _adminLog.Add(LogType.Action, LogImpact.Medium, - $"{ToPrettyString(user):player} has successfully uncuffed themselves"); - } + _adminLog.Add(LogType.Action, LogImpact.Medium, + $"{ToPrettyString(user):player} has successfully uncuffed themselves"); } - else if (user != null) + } + else if (user != null) + { + if (user != target) + { + _popup.PopupPredicted(Loc.GetString("cuffable-component-remove-cuffs-partial-success-message", + ("cuffedHandCount", cuffable.CuffedHandCount), + ("otherName", Identity.Name(user.Value, EntityManager, user.Value))), user.Value, user.Value); + _popup.PopupPredicted(Loc.GetString( + "cuffable-component-remove-cuffs-by-other-partial-success-message", + ("otherName", Identity.Name(user.Value, EntityManager, user.Value)), + ("cuffedHandCount", cuffable.CuffedHandCount)), target, target); + } + else { - if (user != target) - { - _popup.PopupEntity(Loc.GetString("cuffable-component-remove-cuffs-partial-success-message", - ("cuffedHandCount", cuffable.CuffedHandCount), - ("otherName", Identity.Name(user.Value, EntityManager, user.Value))), user.Value, user.Value); - _popup.PopupEntity(Loc.GetString( - "cuffable-component-remove-cuffs-by-other-partial-success-message", - ("otherName", Identity.Name(user.Value, EntityManager, user.Value)), - ("cuffedHandCount", cuffable.CuffedHandCount)), target, target); - } - else - { - _popup.PopupEntity(Loc.GetString("cuffable-component-remove-cuffs-partial-success-message", - ("cuffedHandCount", cuffable.CuffedHandCount)), user.Value, user.Value); - } + _popup.PopupPredicted(Loc.GetString("cuffable-component-remove-cuffs-partial-success-message", + ("cuffedHandCount", cuffable.CuffedHandCount)), user.Value, user.Value); } } cuff.Removing = false; From ad9894bb8b072bbbd312903c8f59d85ec312ef73 Mon Sep 17 00:00:00 2001 From: Jake Huxell Date: Tue, 26 Mar 2024 19:13:58 -0400 Subject: [PATCH 002/295] Objects such as lighters/welders are now able to be dropped in disposal units. (#26463) Don't always mark after interact event as handled for welder tools. Done with a view towards allowing disposal interaction post tool system handling. Co-authored-by: MQuatermain --- Content.Server/Tools/ToolSystem.Welder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Content.Server/Tools/ToolSystem.Welder.cs b/Content.Server/Tools/ToolSystem.Welder.cs index 98e29c763a8..1eabd6c6d2c 100644 --- a/Content.Server/Tools/ToolSystem.Welder.cs +++ b/Content.Server/Tools/ToolSystem.Welder.cs @@ -152,9 +152,9 @@ private void OnWelderAfterInteract(Entity entity, ref AfterInte { _popup.PopupEntity(Loc.GetString("welder-component-no-fuel-in-tank", ("owner", args.Target)), entity, args.User); } - } - args.Handled = true; + args.Handled = true; + } } private void OnWelderToolUseAttempt(Entity entity, ref DoAfterAttemptEvent args) From 6bec439aa9fcc1c9fe5593401ac2b371ba67b0cb Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Wed, 27 Mar 2024 17:31:26 +1300 Subject: [PATCH 003/295] Fix GhostRoleComponent performing randomization on ComponentInit (#26466) * Fix ghostrole ComponentInit * A * a --- .../Roles/Components/GhostRoleComponent.cs | 2 +- Content.Server/Ghost/Roles/GhostRoleSystem.cs | 18 ++++++++---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/Content.Server/Ghost/Roles/Components/GhostRoleComponent.cs b/Content.Server/Ghost/Roles/Components/GhostRoleComponent.cs index 93e7e9efaa6..abb26a8c8bc 100644 --- a/Content.Server/Ghost/Roles/Components/GhostRoleComponent.cs +++ b/Content.Server/Ghost/Roles/Components/GhostRoleComponent.cs @@ -11,7 +11,7 @@ public sealed partial class GhostRoleComponent : Component [DataField("description")] private string _roleDescription = "Unknown"; - [DataField("rules")] private string _roleRules = ""; + [DataField("rules")] private string _roleRules = "ghost-role-component-default-rules"; [DataField("requirements")] public HashSet? Requirements; diff --git a/Content.Server/Ghost/Roles/GhostRoleSystem.cs b/Content.Server/Ghost/Roles/GhostRoleSystem.cs index 2397edc724e..f603416d00a 100644 --- a/Content.Server/Ghost/Roles/GhostRoleSystem.cs +++ b/Content.Server/Ghost/Roles/GhostRoleSystem.cs @@ -56,7 +56,8 @@ public override void Initialize() SubscribeLocalEvent(OnMindAdded); SubscribeLocalEvent(OnMindRemoved); SubscribeLocalEvent(OnMobStateChanged); - SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnPaused); SubscribeLocalEvent(OnUnpaused); @@ -318,17 +319,14 @@ private void OnUnpaused(EntityUid uid, GhostRoleComponent component, ref EntityU UpdateAllEui(); } - private void OnInit(Entity ent, ref ComponentInit args) + private void OnMapInit(Entity ent, ref MapInitEvent args) { - var role = ent.Comp; - if (role.Probability < 1f && !_random.Prob(role.Probability)) - { - RemComp(ent); - return; - } + if (ent.Comp.Probability < 1f && !_random.Prob(ent.Comp.Probability)) + RemCompDeferred(ent); + } - if (role.RoleRules == "") - role.RoleRules = Loc.GetString("ghost-role-component-default-rules"); + private void OnStartup(Entity ent, ref ComponentStartup args) + { RegisterGhostRole(ent); } From 8f8d07a8316ecf7404bd97b1231548f518f4a13d Mon Sep 17 00:00:00 2001 From: Ghagliiarghii <68826635+Ghagliiarghii@users.noreply.github.com> Date: Wed, 27 Mar 2024 17:50:35 -0400 Subject: [PATCH 004/295] Removed Box of Hugs and Donk Pockets from Bounties (#26481) As far as I know, the Box of Hugs only spawns on the clown and is used in the construction of a honkbot such that if the clown decided earlier to craft a honkbot or if the low price of some 3000 spesos is insufficient this bounty is impossible. Similarly, Donk Pockets are not a renewable resource and it has happened that a station has eaten too many for the bounty to be completed only to get it in rotation and esssentially lose a bounty slot forever. I would like it if each bounty was at least theoretically possible on any station it's likely to occur in, and there are too many where there aren't enough of these obtainable to complete the bounty, which is no fun. --- .../Prototypes/Catalog/Bounties/bounties.yml | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/Resources/Prototypes/Catalog/Bounties/bounties.yml b/Resources/Prototypes/Catalog/Bounties/bounties.yml index c8c35aabfe4..3c1bd02fcfc 100644 --- a/Resources/Prototypes/Catalog/Bounties/bounties.yml +++ b/Resources/Prototypes/Catalog/Bounties/bounties.yml @@ -21,17 +21,6 @@ tags: - BaseballBat -- type: cargoBounty - id: BountyBoxHug - reward: 3000 - description: bounty-description-box-hugs - entries: - - name: bounty-item-box-hugs - amount: 1 - whitelist: - tags: - - BoxHug - - type: cargoBounty id: BountyBrain reward: 12500 @@ -139,18 +128,6 @@ tags: - CubanCarp -- type: cargoBounty - id: BountyDonkPocket - reward: 6000 - description: bounty-description-donk-pocket - idPrefix: CC - entries: - - name: bounty-item-donk-pocket - amount: 12 - whitelist: - tags: - - DonkPocket - - type: cargoBounty id: BountyDonut reward: 6000 From 6d3740b0a938f2a2efa79d0d3a615599d87e2a51 Mon Sep 17 00:00:00 2001 From: Verm <32827189+Vermidia@users.noreply.github.com> Date: Wed, 27 Mar 2024 18:26:26 -0500 Subject: [PATCH 005/295] Artifact node IDs are now only 3 digits long (#26482) * 2-digit nodes * 3-digits instead * Fix exclusive bounds --- .../Xenoarchaeology/XenoArtifacts/ArtifactSystem.Nodes.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.Nodes.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.Nodes.cs index 70ae7dcf0f3..647f31a8953 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.Nodes.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.Nodes.cs @@ -44,10 +44,10 @@ private void GenerateArtifactNodeTree(EntityUid artifact, ref List private int GetValidNodeId() { - var id = _random.Next(10000, 100000); + var id = _random.Next(100, 1000); while (_usedNodeIds.Contains(id)) { - id = _random.Next(10000, 100000); + id = _random.Next(100, 1000); } _usedNodeIds.Add(id); From bc92c217b0ffd949ff7c2c5fd53932fa95f5dc69 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Thu, 28 Mar 2024 14:16:13 +1300 Subject: [PATCH 006/295] Add stacktrace to action error logs (#26486) Add trace to action error logs --- Content.Shared/Actions/SharedActionsSystem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Content.Shared/Actions/SharedActionsSystem.cs b/Content.Shared/Actions/SharedActionsSystem.cs index e909f0a8a30..9f3fb964100 100644 --- a/Content.Shared/Actions/SharedActionsSystem.cs +++ b/Content.Shared/Actions/SharedActionsSystem.cs @@ -124,7 +124,7 @@ public bool TryGetActionData( return true; if (logError) - Log.Error($"Failed to get action from action entity: {ToPrettyString(uid.Value)}"); + Log.Error($"Failed to get action from action entity: {ToPrettyString(uid.Value)}. Trace: {Environment.StackTrace}"); return false; } @@ -804,7 +804,7 @@ public void RemoveAction(EntityUid performer, EntityUid? actionId, ActionsCompon || !comp.Actions.Contains(actionId.Value)); if (!GameTiming.ApplyingState) - Log.Error($"Attempted to remove an action {ToPrettyString(actionId)} from an entity that it was never attached to: {ToPrettyString(performer)}"); + Log.Error($"Attempted to remove an action {ToPrettyString(actionId)} from an entity that it was never attached to: {ToPrettyString(performer)}. Trace: {Environment.StackTrace}"); return; } From 597147c1f1e278114c2983fbab2443ff7b1ab1bd Mon Sep 17 00:00:00 2001 From: Jake Huxell Date: Wed, 27 Mar 2024 21:43:55 -0400 Subject: [PATCH 007/295] Late Join Menu Properly Retains Position On New Player Joins (#26483) * When another player late joins it will correctly update the UI locally * Resolve passengers not displaying the correct message in late join * Improve final boolean comparison of button disabled state to be a bit neater --- Content.Client/LateJoin/LateJoinGui.cs | 82 ++++++++++++++++++++------ 1 file changed, 64 insertions(+), 18 deletions(-) diff --git a/Content.Client/LateJoin/LateJoinGui.cs b/Content.Client/LateJoin/LateJoinGui.cs index b99d30015ef..d6a028d6c45 100644 --- a/Content.Client/LateJoin/LateJoinGui.cs +++ b/Content.Client/LateJoin/LateJoinGui.cs @@ -33,7 +33,7 @@ public sealed class LateJoinGui : DefaultWindow private readonly SpriteSystem _sprites; private readonly CrewManifestSystem _crewManifest; - private readonly Dictionary> _jobButtons = new(); + private readonly Dictionary>> _jobButtons = new(); private readonly Dictionary> _jobCategories = new(); private readonly List _jobLists = new(); @@ -139,7 +139,7 @@ private void RebuildUI() var jobListScroll = new ScrollContainer() { VerticalExpand = true, - Children = {jobList}, + Children = { jobList }, Visible = false, }; @@ -163,11 +163,12 @@ private void RebuildUI() var departments = _prototypeManager.EnumeratePrototypes().ToArray(); Array.Sort(departments, DepartmentUIComparer.Instance); + _jobButtons[id] = new Dictionary>(); + foreach (var department in departments) { var departmentName = Loc.GetString($"department-{department.ID}"); _jobCategories[id] = new Dictionary(); - _jobButtons[id] = new Dictionary(); var stationAvailable = _gameTicker.JobsAvailable[id]; var jobsAvailable = new List(); @@ -223,7 +224,13 @@ private void RebuildUI() foreach (var prototype in jobsAvailable) { var value = stationAvailable[prototype.ID]; - var jobButton = new JobButton(prototype.ID, value); + + var jobLabel = new Label + { + Margin = new Thickness(5f, 0, 0, 0) + }; + + var jobButton = new JobButton(jobLabel, prototype.ID, prototype.LocalizedName, value); var jobSelector = new BoxContainer { @@ -241,14 +248,6 @@ private void RebuildUI() icon.Texture = _sprites.Frame0(jobIcon.Icon); jobSelector.AddChild(icon); - var jobLabel = new Label - { - Margin = new Thickness(5f, 0, 0, 0), - Text = value != null ? - Loc.GetString("late-join-gui-job-slot-capped", ("jobName", prototype.LocalizedName), ("amount", value)) : - Loc.GetString("late-join-gui-job-slot-uncapped", ("jobName", prototype.LocalizedName)), - }; - jobSelector.AddChild(jobLabel); jobButton.AddChild(jobSelector); category.AddChild(jobButton); @@ -280,15 +279,43 @@ private void RebuildUI() jobButton.Disabled = true; } - _jobButtons[id][prototype.ID] = jobButton; + if (!_jobButtons[id].ContainsKey(prototype.ID)) + { + _jobButtons[id][prototype.ID] = new List(); + } + + _jobButtons[id][prototype.ID].Add(jobButton); } } } } - private void JobsAvailableUpdated(IReadOnlyDictionary> _) + private void JobsAvailableUpdated(IReadOnlyDictionary> updatedJobs) { - RebuildUI(); + foreach (var stationEntries in updatedJobs) + { + if (_jobButtons.ContainsKey(stationEntries.Key)) + { + var jobsAvailable = stationEntries.Value; + + var existingJobEntries = _jobButtons[stationEntries.Key]; + foreach (var existingJobEntry in existingJobEntries) + { + if (jobsAvailable.ContainsKey(existingJobEntry.Key)) + { + var updatedJobValue = jobsAvailable[existingJobEntry.Key]; + foreach (var matchingJobButton in existingJobEntry.Value) + { + if (matchingJobButton.Amount != updatedJobValue) + { + matchingJobButton.RefreshLabel(updatedJobValue); + matchingJobButton.Disabled = matchingJobButton.Amount == 0; + } + } + } + } + } + } } protected override void Dispose(bool disposing) @@ -307,14 +334,33 @@ protected override void Dispose(bool disposing) sealed class JobButton : ContainerButton { + public Label JobLabel { get; } public string JobId { get; } - public uint? Amount { get; } + public string JobLocalisedName { get; } + public uint? Amount { get; private set; } + private bool _initialised = false; - public JobButton(string jobId, uint? amount) + public JobButton(Label jobLabel, string jobId, string jobLocalisedName, uint? amount) { + JobLabel = jobLabel; JobId = jobId; - Amount = amount; + JobLocalisedName = jobLocalisedName; + RefreshLabel(amount); AddStyleClass(StyleClassButton); + _initialised = true; + } + + public void RefreshLabel(uint? amount) + { + if (Amount == amount && _initialised) + { + return; + } + Amount = amount; + + JobLabel.Text = Amount != null ? + Loc.GetString("late-join-gui-job-slot-capped", ("jobName", JobLocalisedName), ("amount", Amount)) : + Loc.GetString("late-join-gui-job-slot-uncapped", ("jobName", JobLocalisedName)); } } } From 738692536bac0833b8c5af2729909cc0ba9546ea Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Thu, 28 Mar 2024 15:22:19 +1300 Subject: [PATCH 008/295] Remove atmos method events (#26402) * Remove HasAtmosphereMethodEvent * Remove GetTileMixturesMethodEvent * Remove GetTileMixtureMethodEvent * Remove GetAdjacentTilesMethodEvent * Add TileMixtureEnumerator * Remove GetAdjacentTileMixturesMethodEvent * Remove IsTileSpaceMethodEvent * Remove HotspotExposeMethodEvent * Remove pipe net method events * Remove device method events * Use Entity * Misc fixes * A * Theres probably a few more of these * Fix other resolve errors --- .../Anomaly/AnomalySystem.Generator.cs | 2 +- .../Effects/GasProducerAnomalySystem.cs | 2 +- .../EntitySystems/AtmosphereSystem.API.cs | 232 ++++++++---------- .../AtmosphereSystem.Commands.cs | 2 +- .../AtmosphereSystem.GridAtmosphere.cs | 178 -------------- .../EntitySystems/AtmosphereSystem.Map.cs | 34 --- .../Atmos/EntitySystems/AtmosphereSystem.cs | 2 + .../Piping/Components/AtmosDeviceComponent.cs | 2 +- .../Piping/EntitySystems/AtmosDeviceSystem.cs | 14 +- .../EntitySystems/GasVentScrubberSystem.cs | 3 +- .../Atmos/Portable/PortableScrubberSystem.cs | 3 +- .../Atmos/Reactions/PlasmaFireReaction.cs | 2 +- .../Atmos/Reactions/TritiumFireReaction.cs | 2 +- Content.Server/Atmos/TileMixtureEnumerator.cs | 29 +++ .../Rules/GameRuleSystem.Utility.cs | 3 +- Content.Server/Lathe/LatheSystem.cs | 6 +- .../Systems/TemperatureArtifactSystem.cs | 4 +- 17 files changed, 166 insertions(+), 354 deletions(-) create mode 100644 Content.Server/Atmos/TileMixtureEnumerator.cs diff --git a/Content.Server/Anomaly/AnomalySystem.Generator.cs b/Content.Server/Anomaly/AnomalySystem.Generator.cs index e03c566593e..7aa1a8935f3 100644 --- a/Content.Server/Anomaly/AnomalySystem.Generator.cs +++ b/Content.Server/Anomaly/AnomalySystem.Generator.cs @@ -103,7 +103,7 @@ public void SpawnOnRandomGridLocation(EntityUid grid, string toSpawn) var tile = new Vector2i(randomX, randomY); // no air-blocked areas. - if (_atmosphere.IsTileSpace(grid, xform.MapUid, tile, mapGridComp: gridComp) || + if (_atmosphere.IsTileSpace(grid, xform.MapUid, tile) || _atmosphere.IsTileAirBlocked(grid, tile, mapGridComp: gridComp)) { continue; diff --git a/Content.Server/Anomaly/Effects/GasProducerAnomalySystem.cs b/Content.Server/Anomaly/Effects/GasProducerAnomalySystem.cs index 2408ad0b3dd..7fee8fdb985 100644 --- a/Content.Server/Anomaly/Effects/GasProducerAnomalySystem.cs +++ b/Content.Server/Anomaly/Effects/GasProducerAnomalySystem.cs @@ -62,7 +62,7 @@ private void ReleaseGas(EntityUid uid, Gas gas, float mols, float radius, int co if (tilerefs.Length == 0) return; - var mixture = _atmosphere.GetTileMixture((uid, xform), grid, true); + var mixture = _atmosphere.GetTileMixture((uid, xform), true); if (mixture != null) { mixture.AdjustMoles(gas, mols); diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.API.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.API.cs index cece99cacf6..fb94fe414b0 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.API.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.API.cs @@ -4,7 +4,6 @@ using Content.Server.Atmos.Reactions; using Content.Server.NodeContainer.NodeGroups; using Content.Shared.Atmos; -using Robust.Server.GameObjects; using Robust.Shared.Map.Components; using Robust.Shared.Utility; @@ -49,13 +48,7 @@ public partial class AtmosphereSystem return GetTileMixture(gridUid, mapUid, position, excite); } - public bool HasAtmosphere(EntityUid gridUid) - { - var ev = new HasAtmosphereMethodEvent(gridUid); - RaiseLocalEvent(gridUid, ref ev); - - return ev.Result; - } + public bool HasAtmosphere(EntityUid gridUid) => _atmosQuery.HasComponent(gridUid); public bool SetSimulatedGrid(EntityUid gridUid, bool simulated) { @@ -91,43 +84,60 @@ public void InvalidateTile(Entity entity, Vector2i til entity.Comp.InvalidatedCoords.Add(tile); } - public GasMixture?[]? GetTileMixtures(EntityUid? gridUid, EntityUid? mapUid, List tiles, bool excite = false) + public GasMixture?[]? GetTileMixtures(Entity? grid, Entity? map, List tiles, bool excite = false) { - var ev = new GetTileMixturesMethodEvent(gridUid, mapUid, tiles, excite); + GasMixture?[]? mixtures = null; + var handled = false; // If we've been passed a grid, try to let it handle it. - if (gridUid.HasValue) + if (grid is {} gridEnt && Resolve(gridEnt, ref gridEnt.Comp)) { - DebugTools.Assert(_mapManager.IsGrid(gridUid.Value)); - RaiseLocalEvent(gridUid.Value, ref ev, false); + handled = true; + mixtures = new GasMixture?[tiles.Count]; + + for (var i = 0; i < tiles.Count; i++) + { + var tile = tiles[i]; + if (!gridEnt.Comp.Tiles.TryGetValue(tile, out var atmosTile)) + { + // need to get map atmosphere + handled = false; + continue; + } + + mixtures[i] = atmosTile.Air; + + if (excite) + gridEnt.Comp.InvalidatedCoords.Add(tile); + } } - if (ev.Handled) - return ev.Mixtures; + if (handled) + return mixtures; // We either don't have a grid, or the event wasn't handled. // Let the map handle it instead, and also broadcast the event. - if (mapUid.HasValue) + if (map is {} mapEnt && _mapAtmosQuery.Resolve(mapEnt, ref mapEnt.Comp)) { - DebugTools.Assert(_mapManager.IsMap(mapUid.Value)); - RaiseLocalEvent(mapUid.Value, ref ev, true); - } - else - RaiseLocalEvent(ref ev); + mixtures ??= new GasMixture?[tiles.Count]; + for (var i = 0; i < tiles.Count; i++) + { + mixtures[i] ??= mapEnt.Comp.Mixture; + } - if (ev.Handled) - return ev.Mixtures; + return mixtures; + } // Default to a space mixture... This is a space game, after all! - ev.Mixtures ??= new GasMixture?[tiles.Count]; + mixtures ??= new GasMixture?[tiles.Count]; for (var i = 0; i < tiles.Count; i++) { - ev.Mixtures[i] ??= GasMixture.SpaceGas; + mixtures[i] ??= GasMixture.SpaceGas; } - return ev.Mixtures; + return mixtures; } - public GasMixture? GetTileMixture (Entity entity, MapGridComponent? grid = null, bool excite = false) + public GasMixture? GetTileMixture (Entity entity, bool excite = false) { if (!Resolve(entity.Owner, ref entity.Comp)) return null; @@ -136,32 +146,24 @@ public void InvalidateTile(Entity entity, Vector2i til return GetTileMixture(entity.Comp.GridUid, entity.Comp.MapUid, indices, excite); } - public GasMixture? GetTileMixture(EntityUid? gridUid, EntityUid? mapUid, Vector2i gridTile, bool excite = false) + public GasMixture? GetTileMixture(Entity? grid, Entity? map, Vector2i gridTile, bool excite = false) { - var ev = new GetTileMixtureMethodEvent(gridUid, mapUid, gridTile, excite); - // If we've been passed a grid, try to let it handle it. - if(gridUid.HasValue) + if (grid is {} gridEnt + && Resolve(gridEnt, ref gridEnt.Comp, false) + && gridEnt.Comp.Tiles.TryGetValue(gridTile, out var tile)) { - DebugTools.Assert(_mapManager.IsGrid(gridUid.Value)); - RaiseLocalEvent(gridUid.Value, ref ev, false); - } - - if (ev.Handled) - return ev.Mixture; + if (excite) + gridEnt.Comp.InvalidatedCoords.Add(gridTile); - // We either don't have a grid, or the event wasn't handled. - // Let the map handle it instead, and also broadcast the event. - if(mapUid.HasValue) - { - DebugTools.Assert(_mapManager.IsMap(mapUid.Value)); - RaiseLocalEvent(mapUid.Value, ref ev, true); + return tile.Air; } - else - RaiseLocalEvent(ref ev); + + if (map is {} mapEnt && _mapAtmosQuery.Resolve(mapEnt, ref mapEnt.Comp, false)) + return mapEnt.Comp.Mixture; // Default to a space mixture... This is a space game, after all! - return ev.Mixture ?? GasMixture.SpaceGas; + return GasMixture.SpaceGas; } public ReactionResult ReactTile(EntityUid gridId, Vector2i tile) @@ -176,66 +178,67 @@ public ReactionResult ReactTile(EntityUid gridId, Vector2i tile) public bool IsTileAirBlocked(EntityUid gridUid, Vector2i tile, AtmosDirection directions = AtmosDirection.All, MapGridComponent? mapGridComp = null) { - if (!Resolve(gridUid, ref mapGridComp)) + if (!Resolve(gridUid, ref mapGridComp, false)) return false; var data = GetAirtightData(gridUid, mapGridComp, tile); return data.BlockedDirections.IsFlagSet(directions); } - public bool IsTileSpace(EntityUid? gridUid, EntityUid? mapUid, Vector2i tile, MapGridComponent? mapGridComp = null) + public bool IsTileSpace(Entity? grid, Entity? map, Vector2i tile) { - var ev = new IsTileSpaceMethodEvent(gridUid, mapUid, tile, mapGridComp); - - // Try to let the grid (if any) handle it... - if (gridUid.HasValue) - RaiseLocalEvent(gridUid.Value, ref ev, false); - - // If we didn't have a grid or the event wasn't handled - // we let the map know, and also broadcast the event while at it! - if (mapUid.HasValue && !ev.Handled) - RaiseLocalEvent(mapUid.Value, ref ev, true); + if (grid is {} gridEnt && _atmosQuery.Resolve(gridEnt, ref gridEnt.Comp, false) + && gridEnt.Comp.Tiles.TryGetValue(tile, out var tileAtmos)) + { + return tileAtmos.Space; + } - // We didn't have a map, and the event isn't handled, therefore broadcast the event. - else if (!mapUid.HasValue && !ev.Handled) - RaiseLocalEvent(ref ev); + if (map is {} mapEnt && _mapAtmosQuery.Resolve(mapEnt, ref mapEnt.Comp, false)) + return mapEnt.Comp.Space; // If nothing handled the event, it'll default to true. // Oh well, this is a space game after all, deal with it! - return ev.Result; + return true; } - public bool IsTileMixtureProbablySafe(EntityUid? gridUid, EntityUid mapUid, Vector2i tile) + public bool IsTileMixtureProbablySafe(Entity? grid, Entity map, Vector2i tile) { - return IsMixtureProbablySafe(GetTileMixture(gridUid, mapUid, tile)); + return IsMixtureProbablySafe(GetTileMixture(grid, map, tile)); } - public float GetTileHeatCapacity(EntityUid? gridUid, EntityUid mapUid, Vector2i tile) + public float GetTileHeatCapacity(Entity? grid, Entity map, Vector2i tile) { - return GetHeatCapacity(GetTileMixture(gridUid, mapUid, tile) ?? GasMixture.SpaceGas); + return GetHeatCapacity(GetTileMixture(grid, map, tile) ?? GasMixture.SpaceGas); } - public IEnumerable GetAdjacentTiles(EntityUid gridUid, Vector2i tile) + public TileMixtureEnumerator GetAdjacentTileMixtures(Entity grid, Vector2i tile, bool includeBlocked = false, bool excite = false) { - var ev = new GetAdjacentTilesMethodEvent(gridUid, tile); - RaiseLocalEvent(gridUid, ref ev); + if (!_atmosQuery.Resolve(grid, ref grid.Comp, false)) + return TileMixtureEnumerator.Empty; - return ev.Result ?? Enumerable.Empty(); + return !grid.Comp.Tiles.TryGetValue(tile, out var atmosTile) + ? TileMixtureEnumerator.Empty + : new(atmosTile.AdjacentTiles); } - public IEnumerable GetAdjacentTileMixtures(EntityUid gridUid, Vector2i tile, bool includeBlocked = false, bool excite = false) + public void HotspotExpose(Entity grid, Vector2i tile, float exposedTemperature, float exposedVolume, + EntityUid? sparkSourceUid = null, bool soh = false) { - var ev = new GetAdjacentTileMixturesMethodEvent(gridUid, tile, includeBlocked, excite); - RaiseLocalEvent(gridUid, ref ev); + if (!_atmosQuery.Resolve(grid, ref grid.Comp, false)) + return; - return ev.Result ?? Enumerable.Empty(); + if (grid.Comp.Tiles.TryGetValue(tile, out var atmosTile)) + HotspotExpose(grid.Comp, atmosTile, exposedTemperature, exposedVolume, soh, sparkSourceUid); } - public void HotspotExpose(EntityUid gridUid, Vector2i tile, float exposedTemperature, float exposedVolume, + public void HotspotExpose(TileAtmosphere tile, float exposedTemperature, float exposedVolume, EntityUid? sparkSourceUid = null, bool soh = false) { - var ev = new HotspotExposeMethodEvent(gridUid, sparkSourceUid, tile, exposedTemperature, exposedVolume, soh); - RaiseLocalEvent(gridUid, ref ev); + if (!_atmosQuery.TryGetComponent(tile.GridIndex, out var atmos)) + return; + + DebugTools.Assert(atmos.Tiles.TryGetValue(tile.GridIndices, out var tmp) && tmp == tile); + HotspotExpose(atmos, tile, exposedTemperature, exposedVolume, soh, sparkSourceUid); } public void HotspotExtinguish(EntityUid gridUid, Vector2i tile) @@ -253,38 +256,44 @@ public bool IsHotspotActive(EntityUid gridUid, Vector2i tile) return ev.Result; } - public void AddPipeNet(EntityUid gridUid, PipeNet pipeNet) + public bool AddPipeNet(Entity grid, PipeNet pipeNet) { - var ev = new AddPipeNetMethodEvent(gridUid, pipeNet); - RaiseLocalEvent(gridUid, ref ev); + return _atmosQuery.Resolve(grid, ref grid.Comp, false) && grid.Comp.PipeNets.Add(pipeNet); } - public void RemovePipeNet(EntityUid gridUid, PipeNet pipeNet) + public bool RemovePipeNet(Entity grid, PipeNet pipeNet) { - var ev = new RemovePipeNetMethodEvent(gridUid, pipeNet); - RaiseLocalEvent(gridUid, ref ev); + return _atmosQuery.Resolve(grid, ref grid.Comp, false) && grid.Comp.PipeNets.Remove(pipeNet); } - public bool AddAtmosDevice(EntityUid gridUid, AtmosDeviceComponent device) + public bool AddAtmosDevice(Entity grid, Entity device) { - // TODO: check device is on grid + DebugTools.Assert(device.Comp.JoinedGrid == null); + DebugTools.Assert(Transform(device).GridUid == grid); - var ev = new AddAtmosDeviceMethodEvent(gridUid, device); - RaiseLocalEvent(gridUid, ref ev); - return ev.Result; + if (!_atmosQuery.Resolve(grid, ref grid.Comp, false)) + return false; + + if (!grid.Comp.AtmosDevices.Add(device)) + return false; + + device.Comp.JoinedGrid = grid; + return true; } - public bool RemoveAtmosDevice(EntityUid gridUid, AtmosDeviceComponent device) + public bool RemoveAtmosDevice(Entity grid, Entity device) { - // TODO: check device is on grid + DebugTools.Assert(device.Comp.JoinedGrid == grid); - var ev = new RemoveAtmosDeviceMethodEvent(gridUid, device); - RaiseLocalEvent(gridUid, ref ev); - return ev.Result; - } + if (!_atmosQuery.Resolve(grid, ref grid.Comp, false)) + return false; + + if (!grid.Comp.AtmosDevices.Remove(device)) + return false; - [ByRefEvent] private record struct HasAtmosphereMethodEvent - (EntityUid Grid, bool Result = false, bool Handled = false); + device.Comp.JoinedGrid = null; + return true; + } [ByRefEvent] private record struct SetSimulatedGridMethodEvent (EntityUid Grid, bool Simulated, bool Handled = false); @@ -295,43 +304,12 @@ [ByRefEvent] private record struct IsSimulatedGridMethodEvent [ByRefEvent] private record struct GetAllMixturesMethodEvent (EntityUid Grid, bool Excite = false, IEnumerable? Mixtures = null, bool Handled = false); - [ByRefEvent] private record struct GetTileMixturesMethodEvent - (EntityUid? GridUid, EntityUid? MapUid, List Tiles, bool Excite = false, GasMixture?[]? Mixtures = null, bool Handled = false); - - [ByRefEvent] private record struct GetTileMixtureMethodEvent - (EntityUid? GridUid, EntityUid? MapUid, Vector2i Tile, bool Excite = false, GasMixture? Mixture = null, bool Handled = false); - [ByRefEvent] private record struct ReactTileMethodEvent (EntityUid GridId, Vector2i Tile, ReactionResult Result = default, bool Handled = false); - [ByRefEvent] private record struct IsTileSpaceMethodEvent - (EntityUid? Grid, EntityUid? Map, Vector2i Tile, MapGridComponent? MapGridComponent = null, bool Result = true, bool Handled = false); - - [ByRefEvent] private record struct GetAdjacentTilesMethodEvent - (EntityUid Grid, Vector2i Tile, IEnumerable? Result = null, bool Handled = false); - - [ByRefEvent] private record struct GetAdjacentTileMixturesMethodEvent - (EntityUid Grid, Vector2i Tile, bool IncludeBlocked, bool Excite, - IEnumerable? Result = null, bool Handled = false); - - [ByRefEvent] private record struct HotspotExposeMethodEvent - (EntityUid Grid, EntityUid? SparkSourceUid, Vector2i Tile, float ExposedTemperature, float ExposedVolume, bool soh, bool Handled = false); - [ByRefEvent] private record struct HotspotExtinguishMethodEvent (EntityUid Grid, Vector2i Tile, bool Handled = false); [ByRefEvent] private record struct IsHotspotActiveMethodEvent (EntityUid Grid, Vector2i Tile, bool Result = false, bool Handled = false); - - [ByRefEvent] private record struct AddPipeNetMethodEvent - (EntityUid Grid, PipeNet PipeNet, bool Handled = false); - - [ByRefEvent] private record struct RemovePipeNetMethodEvent - (EntityUid Grid, PipeNet PipeNet, bool Handled = false); - - [ByRefEvent] private record struct AddAtmosDeviceMethodEvent - (EntityUid Grid, AtmosDeviceComponent Device, bool Result = false, bool Handled = false); - - [ByRefEvent] private record struct RemoveAtmosDeviceMethodEvent - (EntityUid Grid, AtmosDeviceComponent Device, bool Result = false, bool Handled = false); } diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Commands.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Commands.cs index 1c18b8fe29c..a5e37398c6a 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Commands.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Commands.cs @@ -98,7 +98,7 @@ private void FixGridAtmosCommand(IConsoleShell shell, string argstr, string[] ar continue; } - if (tile.Immutable && !IsTileSpace(euid, transform.MapUid, indices, gridComp)) + if (tile.Immutable && !IsTileSpace(euid, transform.MapUid, indices)) { tile = new GasMixture(tile.Volume) { Temperature = tile.Temperature }; tileMain.Air = tile; diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs index beddef4be7e..bd45030896f 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs @@ -20,22 +20,11 @@ private void InitializeGridAtmosphere() #region Atmos API Subscriptions - SubscribeLocalEvent(GridHasAtmosphere); SubscribeLocalEvent(GridIsSimulated); SubscribeLocalEvent(GridGetAllMixtures); - SubscribeLocalEvent(GridGetTileMixture); - SubscribeLocalEvent(GridGetTileMixtures); SubscribeLocalEvent(GridReactTile); - SubscribeLocalEvent(GridIsTileSpace); - SubscribeLocalEvent(GridGetAdjacentTiles); - SubscribeLocalEvent(GridGetAdjacentTileMixtures); - SubscribeLocalEvent(GridHotspotExpose); SubscribeLocalEvent(GridHotspotExtinguish); SubscribeLocalEvent(GridIsHotspotActive); - SubscribeLocalEvent(GridAddPipeNet); - SubscribeLocalEvent(GridRemovePipeNet); - SubscribeLocalEvent(GridAddAtmosDevice); - SubscribeLocalEvent(GridRemoveAtmosDevice); #endregion } @@ -120,15 +109,6 @@ private void OnGridSplit(EntityUid uid, GridAtmosphereComponent originalGridAtmo } } - private void GridHasAtmosphere(EntityUid uid, GridAtmosphereComponent component, ref HasAtmosphereMethodEvent args) - { - if (args.Handled) - return; - - args.Result = true; - args.Handled = true; - } - private void GridIsSimulated(EntityUid uid, GridAtmosphereComponent component, ref IsSimulatedGridMethodEvent args) { if (args.Handled) @@ -167,48 +147,6 @@ IEnumerable EnumerateMixtures(EntityUid gridUid, GridAtmosphereCompo args.Handled = true; } - private void GridGetTileMixture(EntityUid uid, GridAtmosphereComponent component, - ref GetTileMixtureMethodEvent args) - { - if (args.Handled) - return; - - if (!component.Tiles.TryGetValue(args.Tile, out var tile)) - return; // Do NOT handle the event if we don't have that tile, the map will handle it instead. - - if (args.Excite) - component.InvalidatedCoords.Add(args.Tile); - - args.Mixture = tile.Air; - args.Handled = true; - } - - private void GridGetTileMixtures(EntityUid uid, GridAtmosphereComponent component, - ref GetTileMixturesMethodEvent args) - { - if (args.Handled) - return; - - args.Handled = true; - args.Mixtures = new GasMixture?[args.Tiles.Count]; - - for (var i = 0; i < args.Tiles.Count; i++) - { - var tile = args.Tiles[i]; - if (!component.Tiles.TryGetValue(tile, out var atmosTile)) - { - // need to get map atmosphere - args.Handled = false; - continue; - } - - if (args.Excite) - component.InvalidatedCoords.Add(tile); - - args.Mixtures[i] = atmosTile.Air; - } - } - private void GridReactTile(EntityUid uid, GridAtmosphereComponent component, ref ReactTileMethodEvent args) { if (args.Handled) @@ -221,67 +159,6 @@ private void GridReactTile(EntityUid uid, GridAtmosphereComponent component, ref args.Handled = true; } - private void GridIsTileSpace(EntityUid uid, GridAtmosphereComponent component, ref IsTileSpaceMethodEvent args) - { - if (args.Handled) - return; - - // We don't have that tile, so let the map handle it. - if (!component.Tiles.TryGetValue(args.Tile, out var tile)) - return; - - args.Result = tile.Space; - args.Handled = true; - } - - private void GridGetAdjacentTiles(EntityUid uid, GridAtmosphereComponent component, - ref GetAdjacentTilesMethodEvent args) - { - if (args.Handled) - return; - - if (!component.Tiles.TryGetValue(args.Tile, out var tile)) - return; - - IEnumerable EnumerateAdjacent(GridAtmosphereComponent grid, TileAtmosphere t) - { - foreach (var adj in t.AdjacentTiles) - { - if (adj == null) - continue; - - yield return adj.GridIndices; - } - } - - args.Result = EnumerateAdjacent(component, tile); - args.Handled = true; - } - - private void GridGetAdjacentTileMixtures(EntityUid uid, GridAtmosphereComponent component, - ref GetAdjacentTileMixturesMethodEvent args) - { - if (args.Handled) - return; - - if (!component.Tiles.TryGetValue(args.Tile, out var tile)) - return; - - IEnumerable EnumerateAdjacent(GridAtmosphereComponent grid, TileAtmosphere t) - { - foreach (var adj in t.AdjacentTiles) - { - if (adj?.Air == null) - continue; - - yield return adj.Air; - } - } - - args.Result = EnumerateAdjacent(component, tile); - args.Handled = true; - } - /// /// Update array of adjacent tiles and the adjacency flags. Optionally activates all tiles with modified adjacencies. /// @@ -357,18 +234,6 @@ private void UpdateAdjacentTiles( return (air, map.Space); } - private void GridHotspotExpose(EntityUid uid, GridAtmosphereComponent component, ref HotspotExposeMethodEvent args) - { - if (args.Handled) - return; - - if (!component.Tiles.TryGetValue(args.Tile, out var tile)) - return; - - HotspotExpose(component, tile, args.ExposedTemperature, args.ExposedVolume, args.soh, args.SparkSourceUid); - args.Handled = true; - } - private void GridHotspotExtinguish(EntityUid uid, GridAtmosphereComponent component, ref HotspotExtinguishMethodEvent args) { @@ -445,49 +310,6 @@ private void GridFixTileVacuum(TileAtmosphere tile) tile.Air.Temperature = totalTemperature / count; } - private void GridAddPipeNet(EntityUid uid, GridAtmosphereComponent component, ref AddPipeNetMethodEvent args) - { - if (args.Handled) - return; - - args.Handled = component.PipeNets.Add(args.PipeNet); - } - - private void GridRemovePipeNet(EntityUid uid, GridAtmosphereComponent component, ref RemovePipeNetMethodEvent args) - { - if (args.Handled) - return; - - args.Handled = component.PipeNets.Remove(args.PipeNet); - } - - private void GridAddAtmosDevice(Entity grid, ref AddAtmosDeviceMethodEvent args) - { - if (args.Handled) - return; - - if (!grid.Comp.AtmosDevices.Add((args.Device.Owner, args.Device))) - return; - - args.Device.JoinedGrid = grid; - args.Handled = true; - args.Result = true; - } - - private void GridRemoveAtmosDevice(EntityUid uid, GridAtmosphereComponent component, - ref RemoveAtmosDeviceMethodEvent args) - { - if (args.Handled) - return; - - if (!component.AtmosDevices.Remove((args.Device.Owner, args.Device))) - return; - - args.Device.JoinedGrid = null; - args.Handled = true; - args.Result = true; - } - /// /// Repopulates all tiles on a grid atmosphere. /// diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Map.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Map.cs index ed105c8d33f..25b3b801f76 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Map.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Map.cs @@ -12,9 +12,6 @@ private void InitializeMap() { SubscribeLocalEvent(OnMapStartup); SubscribeLocalEvent(OnMapRemove); - SubscribeLocalEvent(MapIsTileSpace); - SubscribeLocalEvent(MapGetTileMixture); - SubscribeLocalEvent(MapGetTileMixtures); SubscribeLocalEvent(OnMapGetState); SubscribeLocalEvent(OnGridParentChanged); } @@ -31,37 +28,6 @@ private void OnMapRemove(EntityUid uid, MapAtmosphereComponent component, Compon RefreshAllGridMapAtmospheres(uid); } - private void MapIsTileSpace(EntityUid uid, MapAtmosphereComponent component, ref IsTileSpaceMethodEvent args) - { - if (args.Handled) - return; - - args.Result = component.Space; - args.Handled = true; - } - - private void MapGetTileMixture(EntityUid uid, MapAtmosphereComponent component, ref GetTileMixtureMethodEvent args) - { - if (args.Handled) - return; - - args.Mixture = component.Mixture; - args.Handled = true; - } - - private void MapGetTileMixtures(EntityUid uid, MapAtmosphereComponent component, ref GetTileMixturesMethodEvent args) - { - if (args.Handled) - return; - args.Handled = true; - args.Mixtures ??= new GasMixture?[args.Tiles.Count]; - - for (var i = 0; i < args.Tiles.Count; i++) - { - args.Mixtures[i] ??= component.Mixture; - } - } - private void OnMapGetState(EntityUid uid, MapAtmosphereComponent component, ref ComponentGetState args) { args.State = new MapAtmosphereComponentState(component.Overlay); diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs index d2f40e77169..a5537665827 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs @@ -42,6 +42,7 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem private float _exposedTimer = 0f; private EntityQuery _atmosQuery; + private EntityQuery _mapAtmosQuery; private EntityQuery _airtightQuery; private EntityQuery _firelockQuery; private HashSet _entSet = new(); @@ -59,6 +60,7 @@ public override void Initialize() InitializeGridAtmosphere(); InitializeMap(); + _mapAtmosQuery = GetEntityQuery(); _atmosQuery = GetEntityQuery(); _airtightQuery = GetEntityQuery(); _firelockQuery = GetEntityQuery(); diff --git a/Content.Server/Atmos/Piping/Components/AtmosDeviceComponent.cs b/Content.Server/Atmos/Piping/Components/AtmosDeviceComponent.cs index 361e3cbbeb2..80461f1bebf 100644 --- a/Content.Server/Atmos/Piping/Components/AtmosDeviceComponent.cs +++ b/Content.Server/Atmos/Piping/Components/AtmosDeviceComponent.cs @@ -28,7 +28,7 @@ public sealed partial class AtmosDeviceComponent : Component /// /// If non-null, the grid that this device is part of. /// - [DataField] + [ViewVariables] public EntityUid? JoinedGrid = null; /// diff --git a/Content.Server/Atmos/Piping/EntitySystems/AtmosDeviceSystem.cs b/Content.Server/Atmos/Piping/EntitySystems/AtmosDeviceSystem.cs index f4da68ab531..c15d31f7d64 100644 --- a/Content.Server/Atmos/Piping/EntitySystems/AtmosDeviceSystem.cs +++ b/Content.Server/Atmos/Piping/EntitySystems/AtmosDeviceSystem.cs @@ -1,7 +1,9 @@ +using Content.Server.Atmos.Components; using Content.Server.Atmos.EntitySystems; using Content.Server.Atmos.Piping.Components; using JetBrains.Annotations; using Robust.Shared.Timing; +using Robust.Shared.Utility; namespace Content.Server.Atmos.Piping.EntitySystems { @@ -32,6 +34,14 @@ public override void Initialize() public void JoinAtmosphere(Entity ent) { + if (ent.Comp.JoinedGrid != null) + { + DebugTools.Assert(HasComp(ent.Comp.JoinedGrid)); + DebugTools.Assert(Transform(ent).GridUid == ent.Comp.JoinedGrid); + DebugTools.Assert(ent.Comp.RequireAnchored == Transform(ent).Anchored); + return; + } + var component = ent.Comp; var transform = Transform(ent); @@ -39,7 +49,7 @@ public void JoinAtmosphere(Entity ent) return; // Attempt to add device to a grid atmosphere. - bool onGrid = (transform.GridUid != null) && _atmosphereSystem.AddAtmosDevice(transform.GridUid!.Value, component); + bool onGrid = (transform.GridUid != null) && _atmosphereSystem.AddAtmosDevice(transform.GridUid!.Value, ent); if (!onGrid && component.JoinSystem) { @@ -55,7 +65,7 @@ public void LeaveAtmosphere(Entity ent) { var component = ent.Comp; // Try to remove the component from an atmosphere, and if not - if (component.JoinedGrid != null && !_atmosphereSystem.RemoveAtmosDevice(component.JoinedGrid.Value, component)) + if (component.JoinedGrid != null && !_atmosphereSystem.RemoveAtmosDevice(component.JoinedGrid.Value, ent)) { // The grid might have been removed but not us... This usually shouldn't happen. component.JoinedGrid = null; diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentScrubberSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentScrubberSystem.cs index 32591e9c540..5afa007e5e1 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentScrubberSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentScrubberSystem.cs @@ -77,7 +77,8 @@ private void OnVentScrubberUpdated(EntityUid uid, GasVentScrubberComponent scrub return; // Scrub adjacent tiles too. - foreach (var adjacent in _atmosphereSystem.GetAdjacentTileMixtures(xform.GridUid.Value, position, false, true)) + var enumerator = _atmosphereSystem.GetAdjacentTileMixtures(xform.GridUid.Value, position, false, true); + while (enumerator.MoveNext(out var adjacent)) { Scrub(timeDelta, scrubber, adjacent, outlet); } diff --git a/Content.Server/Atmos/Portable/PortableScrubberSystem.cs b/Content.Server/Atmos/Portable/PortableScrubberSystem.cs index f1be5ac5150..e986f8f991d 100644 --- a/Content.Server/Atmos/Portable/PortableScrubberSystem.cs +++ b/Content.Server/Atmos/Portable/PortableScrubberSystem.cs @@ -86,7 +86,8 @@ private void OnDeviceUpdated(EntityUid uid, PortableScrubberComponent component, if (!running) return; // widenet - foreach (var adjacent in _atmosphereSystem.GetAdjacentTileMixtures(xform.GridUid.Value, position, false, true)) + var enumerator = _atmosphereSystem.GetAdjacentTileMixtures(xform.GridUid.Value, position, false, true); + while (enumerator.MoveNext(out var adjacent)) { Scrub(timeDelta, component, adjacent); } diff --git a/Content.Server/Atmos/Reactions/PlasmaFireReaction.cs b/Content.Server/Atmos/Reactions/PlasmaFireReaction.cs index 9adda3089cc..e7ab7835fd9 100644 --- a/Content.Server/Atmos/Reactions/PlasmaFireReaction.cs +++ b/Content.Server/Atmos/Reactions/PlasmaFireReaction.cs @@ -75,7 +75,7 @@ public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, Atmos var mixTemperature = mixture.Temperature; if (mixTemperature > Atmospherics.FireMinimumTemperatureToExist) { - atmosphereSystem.HotspotExpose(location.GridIndex, location.GridIndices, mixTemperature, mixture.Volume); + atmosphereSystem.HotspotExpose(location, mixTemperature, mixture.Volume); } } diff --git a/Content.Server/Atmos/Reactions/TritiumFireReaction.cs b/Content.Server/Atmos/Reactions/TritiumFireReaction.cs index 37c9741e86f..7103859a90f 100644 --- a/Content.Server/Atmos/Reactions/TritiumFireReaction.cs +++ b/Content.Server/Atmos/Reactions/TritiumFireReaction.cs @@ -60,7 +60,7 @@ public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, Atmos temperature = mixture.Temperature; if (temperature > Atmospherics.FireMinimumTemperatureToExist) { - atmosphereSystem.HotspotExpose(location.GridIndex, location.GridIndices, temperature, mixture.Volume); + atmosphereSystem.HotspotExpose(location, temperature, mixture.Volume); } } diff --git a/Content.Server/Atmos/TileMixtureEnumerator.cs b/Content.Server/Atmos/TileMixtureEnumerator.cs new file mode 100644 index 00000000000..20440032dab --- /dev/null +++ b/Content.Server/Atmos/TileMixtureEnumerator.cs @@ -0,0 +1,29 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Content.Server.Atmos; + +public struct TileMixtureEnumerator +{ + public readonly TileAtmosphere?[] Tiles; + public int Index = 0; + + public static readonly TileMixtureEnumerator Empty = new(Array.Empty()); + + internal TileMixtureEnumerator(TileAtmosphere?[] tiles) + { + Tiles = tiles; + } + + public bool MoveNext([NotNullWhen(true)] out GasMixture? mix) + { + while (Index < Tiles.Length) + { + mix = Tiles[Index++]?.Air; + if (mix != null) + return true; + } + + mix = null; + return false; + } +} diff --git a/Content.Server/GameTicking/Rules/GameRuleSystem.Utility.cs b/Content.Server/GameTicking/Rules/GameRuleSystem.Utility.cs index 7563d66dbad..a60a2bfe22f 100644 --- a/Content.Server/GameTicking/Rules/GameRuleSystem.Utility.cs +++ b/Content.Server/GameTicking/Rules/GameRuleSystem.Utility.cs @@ -119,8 +119,7 @@ protected bool TryFindRandomTileOnStation(Entity station, var randomY = RobustRandom.Next((int) aabb.Bottom, (int) aabb.Top); tile = new Vector2i(randomX, randomY); - if (_atmosphere.IsTileSpace(targetGrid, Transform(targetGrid).MapUid, tile, - mapGridComp: gridComp) + if (_atmosphere.IsTileSpace(targetGrid, Transform(targetGrid).MapUid, tile) || _atmosphere.IsTileAirBlocked(targetGrid, tile, mapGridComp: gridComp)) { continue; diff --git a/Content.Server/Lathe/LatheSystem.cs b/Content.Server/Lathe/LatheSystem.cs index 621c583a558..06d1b463ec3 100644 --- a/Content.Server/Lathe/LatheSystem.cs +++ b/Content.Server/Lathe/LatheSystem.cs @@ -88,7 +88,11 @@ public override void Update(float frameTime) if (xform.GridUid != null) { - _environments.AddRange(_atmosphere.GetAdjacentTileMixtures(xform.GridUid.Value, position, false, true)); + var enumerator = _atmosphere.GetAdjacentTileMixtures(xform.GridUid.Value, position, false, true); + while (enumerator.MoveNext(out var mix)) + { + _environments.Add(mix); + } } if (_environments.Count > 0) diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/TemperatureArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/TemperatureArtifactSystem.cs index 163600c6ddc..f314d4a4fb2 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/TemperatureArtifactSystem.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/TemperatureArtifactSystem.cs @@ -28,10 +28,10 @@ private void OnActivate(EntityUid uid, TemperatureArtifactComponent component, A if (component.AffectAdjacentTiles && transform.GridUid != null) { - var adjacent = _atmosphereSystem.GetAdjacentTileMixtures(transform.GridUid.Value, + var enumerator = _atmosphereSystem.GetAdjacentTileMixtures(transform.GridUid.Value, _transformSystem.GetGridOrMapTilePosition(uid, transform), excite: true); - foreach (var mixture in adjacent) + while (enumerator.MoveNext(out var mixture)) { UpdateTileTemperature(component, mixture); } From 5ba674199b2902cb87b1873b88935bf1f63078f6 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Thu, 28 Mar 2024 17:05:04 +1300 Subject: [PATCH 009/295] Misc ItemToggleSystem changes (#26489) * Minor ItemToggleSystem tweaks * Update visuals on startup * Remove SetIgnited * Misc toggle fixes * Update ItemToggleHotComponent.cs --- .../ItemToggleDisarmMalusComponent.cs | 4 +- .../Components/ItemToggleSharpComponent.cs | 7 +- .../Item/ItemToggle/ItemToggleSystem.cs | 59 ++++++--------- .../Stunnable/Systems/StunbatonSystem.cs | 5 +- Content.Server/Tools/ToolSystem.Welder.cs | 62 ++++++++-------- .../Components/ItemToggleComponent.cs | 31 ++++---- .../Components/ItemToggleHotComponent.cs | 7 +- .../Item/ItemToggle/SharedItemToggleSystem.cs | 71 +++++++++++-------- 8 files changed, 113 insertions(+), 133 deletions(-) diff --git a/Content.Server/Item/ItemToggle/Components/ItemToggleDisarmMalusComponent.cs b/Content.Server/Item/ItemToggle/Components/ItemToggleDisarmMalusComponent.cs index 923a10b22a8..30fa84ed90b 100644 --- a/Content.Server/Item/ItemToggle/Components/ItemToggleDisarmMalusComponent.cs +++ b/Content.Server/Item/ItemToggle/Components/ItemToggleDisarmMalusComponent.cs @@ -1,19 +1,21 @@ namespace Content.Server.Item; /// -/// Handles whether this item applies a disarm malus when active. +/// Handles whether this item applies a disarm malus when active. /// [RegisterComponent] public sealed partial class ItemToggleDisarmMalusComponent : Component { /// /// Item has this modifier to the chance to disarm when activated. + /// If null, the value will be inferred from the current malus just before the malus is first deactivated. /// [ViewVariables(VVAccess.ReadOnly), DataField] public float? ActivatedDisarmMalus = null; /// /// Item has this modifier to the chance to disarm when deactivated. If none is mentioned, it uses the item's default disarm modifier. + /// If null, the value will be inferred from the current malus just before the malus is first activated. /// [ViewVariables(VVAccess.ReadOnly), DataField] public float? DeactivatedDisarmMalus = null; diff --git a/Content.Server/Item/ItemToggle/Components/ItemToggleSharpComponent.cs b/Content.Server/Item/ItemToggle/Components/ItemToggleSharpComponent.cs index ea2efae147b..227491b16c2 100644 --- a/Content.Server/Item/ItemToggle/Components/ItemToggleSharpComponent.cs +++ b/Content.Server/Item/ItemToggle/Components/ItemToggleSharpComponent.cs @@ -1,14 +1,9 @@ namespace Content.Server.Item; /// -/// Handles whether this item is sharp when toggled on. +/// Handles whether this item is sharp when toggled on. /// [RegisterComponent] public sealed partial class ItemToggleSharpComponent : Component { - /// - /// Item can be used to butcher when activated. - /// - [ViewVariables(VVAccess.ReadOnly), DataField] - public bool ActivatedSharp = true; } diff --git a/Content.Server/Item/ItemToggle/ItemToggleSystem.cs b/Content.Server/Item/ItemToggle/ItemToggleSystem.cs index 4507ace9d25..f98415eb08f 100644 --- a/Content.Server/Item/ItemToggle/ItemToggleSystem.cs +++ b/Content.Server/Item/ItemToggle/ItemToggleSystem.cs @@ -1,9 +1,7 @@ -using Content.Shared.Item; using Content.Server.CombatMode.Disarm; using Content.Server.Kitchen.Components; using Content.Shared.Item.ItemToggle; using Content.Shared.Item.ItemToggle.Components; -using ItemToggleComponent = Content.Shared.Item.ItemToggle.Components.ItemToggleComponent; namespace Content.Server.Item; @@ -13,47 +11,34 @@ public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(Toggle); + SubscribeLocalEvent(ToggleSharp); + SubscribeLocalEvent(ToggleMalus); } - private void Toggle(EntityUid uid, ItemToggleComponent comp, ref ItemToggledEvent args) + private void ToggleSharp(Entity ent, ref ItemToggledEvent args) { - if (args.Activated == true) - { - if (TryComp(uid, out var itemSharpness)) - { - if (itemSharpness.ActivatedSharp) - EnsureComp(uid); - } - - if (!TryComp(uid, out var itemToggleDisarmMalus) || - !TryComp(uid, out var malus)) - return; - - //Default the deactivated DisarmMalus to the item's value before activation happens. - itemToggleDisarmMalus.DeactivatedDisarmMalus ??= malus.Malus; - - if (itemToggleDisarmMalus.ActivatedDisarmMalus != null) - { - malus.Malus = (float) itemToggleDisarmMalus.ActivatedDisarmMalus; - } - } + // TODO generalize this into a "ToggleComponentComponent", though probably with a better name + if (args.Activated) + EnsureComp(ent); else - { - if (TryComp(uid, out var itemSharpness)) - { - if (itemSharpness.ActivatedSharp) - RemCompDeferred(uid); - } + RemCompDeferred(ent); + } - if (!TryComp(uid, out var itemToggleDisarmMalus) || - !TryComp(uid, out var malus)) - return; + private void ToggleMalus(Entity ent, ref ItemToggledEvent args) + { + if (!TryComp(ent, out var malus)) + return; - if (itemToggleDisarmMalus.DeactivatedDisarmMalus != null) - { - malus.Malus = (float) itemToggleDisarmMalus.DeactivatedDisarmMalus; - } + if (args.Activated) + { + ent.Comp.DeactivatedDisarmMalus ??= malus.Malus; + if (ent.Comp.ActivatedDisarmMalus is {} activatedMalus) + malus.Malus = activatedMalus; + return; } + + ent.Comp.ActivatedDisarmMalus ??= malus.Malus; + if (ent.Comp.DeactivatedDisarmMalus is {} deactivatedMalus) + malus.Malus = deactivatedMalus; } } diff --git a/Content.Server/Stunnable/Systems/StunbatonSystem.cs b/Content.Server/Stunnable/Systems/StunbatonSystem.cs index 5f019490b5c..c1782efabaf 100644 --- a/Content.Server/Stunnable/Systems/StunbatonSystem.cs +++ b/Content.Server/Stunnable/Systems/StunbatonSystem.cs @@ -58,10 +58,7 @@ private void OnExamined(Entity entity, ref ExaminedEvent arg private void ToggleDone(Entity entity, ref ItemToggledEvent args) { - if (!TryComp(entity, out var item)) - return; - - _item.SetHeldPrefix(entity.Owner, args.Activated ? "on" : "off", component: item); + _item.SetHeldPrefix(entity.Owner, args.Activated ? "on" : "off"); } private void TryTurnOn(Entity entity, ref ItemToggleActivateAttemptEvent args) diff --git a/Content.Server/Tools/ToolSystem.Welder.cs b/Content.Server/Tools/ToolSystem.Welder.cs index 1eabd6c6d2c..727526b3989 100644 --- a/Content.Server/Tools/ToolSystem.Welder.cs +++ b/Content.Server/Tools/ToolSystem.Welder.cs @@ -7,7 +7,6 @@ using Content.Shared.Examine; using Content.Shared.FixedPoint; using Content.Shared.Interaction; -using Content.Shared.Item; using Content.Shared.Item.ItemToggle; using Content.Shared.Tools.Components; using Robust.Shared.GameStates; @@ -32,8 +31,8 @@ public void InitializeWelders() SubscribeLocalEvent>(OnWelderToolUseAttempt); SubscribeLocalEvent(OnWelderShutdown); SubscribeLocalEvent(OnWelderGetState); - SubscribeLocalEvent(TryTurnOn); - SubscribeLocalEvent(TurnOff); + SubscribeLocalEvent(OnToggle); + SubscribeLocalEvent(OnActivateAttempt); } public (FixedPoint2 fuel, FixedPoint2 capacity) GetWelderFuelAndCapacity(EntityUid uid, WelderComponent? welder = null, SolutionContainerManagerComponent? solutionContainer = null) @@ -45,55 +44,54 @@ public void InitializeWelders() return (fuelSolution.GetTotalPrototypeQuantity(welder.FuelReagent), fuelSolution.MaxVolume); } - public void TryTurnOn(Entity entity, ref ItemToggleActivateAttemptEvent args) + private void OnToggle(Entity entity, ref ItemToggledEvent args) { - if (!_solutionContainer.ResolveSolution(entity.Owner, entity.Comp.FuelSolutionName, ref entity.Comp.FuelSolution, out var solution) || - !TryComp(entity, out var transform)) + if (args.Activated) + TurnOn(entity, args.User); + else + TurnOff(entity, args.User); + } + + private void OnActivateAttempt(Entity entity, ref ItemToggleActivateAttemptEvent args) + { + if (!_solutionContainer.ResolveSolution(entity.Owner, entity.Comp.FuelSolutionName, ref entity.Comp.FuelSolution, out var solution)) { args.Cancelled = true; + args.Popup = Loc.GetString("welder-component-no-fuel-message"); return; } var fuel = solution.GetTotalPrototypeQuantity(entity.Comp.FuelReagent); - - // Not enough fuel to lit welder. if (fuel == FixedPoint2.Zero || fuel < entity.Comp.FuelLitCost) { - if (args.User != null) - { - _popup.PopupEntity(Loc.GetString("welder-component-no-fuel-message"), entity, (EntityUid) args.User); - } + args.Popup = Loc.GetString("welder-component-no-fuel-message"); args.Cancelled = true; - return; } + } - _solutionContainer.RemoveReagent(entity.Comp.FuelSolution.Value, entity.Comp.FuelReagent, entity.Comp.FuelLitCost); - - // Logging - AdminLogger.Add(LogType.InteractActivate, LogImpact.Low, $"{ToPrettyString(args.User):user} toggled {ToPrettyString(entity.Owner):welder} on"); + public void TurnOn(Entity entity, EntityUid? user) + { + if (!_solutionContainer.ResolveSolution(entity.Owner, entity.Comp.FuelSolutionName, ref entity.Comp.FuelSolution)) + return; - _ignitionSource.SetIgnited(entity.Owner); + _solutionContainer.RemoveReagent(entity.Comp.FuelSolution.Value, entity.Comp.FuelReagent, entity.Comp.FuelLitCost); + AdminLogger.Add(LogType.InteractActivate, LogImpact.Low, + $"{ToPrettyString(user):user} toggled {ToPrettyString(entity.Owner):welder} on"); - if (transform.GridUid is { } gridUid) + var xform = Transform(entity); + if (xform.GridUid is { } gridUid) { - var position = _transformSystem.GetGridOrMapTilePosition(entity.Owner, transform); + var position = _transformSystem.GetGridOrMapTilePosition(entity.Owner, xform); _atmosphereSystem.HotspotExpose(gridUid, position, 700, 50, entity.Owner, true); } - Dirty(entity); - _activeWelders.Add(entity); } - public void TurnOff(Entity entity, ref ItemToggleDeactivateAttemptEvent args) + public void TurnOff(Entity entity, EntityUid? user) { - // Logging - AdminLogger.Add(LogType.InteractActivate, LogImpact.Low, $"{ToPrettyString(args.User):user} toggled {ToPrettyString(entity.Owner):welder} off"); - - _ignitionSource.SetIgnited(entity.Owner, false); - - Dirty(entity); - + AdminLogger.Add(LogType.InteractActivate, LogImpact.Low, + $"{ToPrettyString(user):user} toggled {ToPrettyString(entity.Owner):welder} off"); _activeWelders.Remove(entity); } @@ -186,7 +184,9 @@ private void UpdateWelders(float frameTime) if (_welderTimer < WelderUpdateTimer) return; - // TODO Use an "active welder" component instead, EntityQuery over that. + // TODO Serialization. _activeWelders is not serialized. + // Need to use some "active" component, and EntityQuery over that. + // Probably best to generalize it to a "ToggleableFuelDrain" component. foreach (var tool in _activeWelders.ToArray()) { if (!TryComp(tool, out WelderComponent? welder) diff --git a/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs b/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs index 3ea80ec7357..620ddfd1942 100644 --- a/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs +++ b/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs @@ -16,7 +16,7 @@ public sealed partial class ItemToggleComponent : Component /// /// The item's toggle state. /// - [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField] + [DataField, AutoNetworkedField] public bool Activated = false; /// @@ -45,6 +45,12 @@ public sealed partial class ItemToggleComponent : Component /// [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField] public SoundSpecifier? SoundFailToActivate; + + /// + /// Whether or not to toggle the entity's lights on or off. + /// + [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField] + public bool ToggleLight = true; } /// @@ -55,6 +61,11 @@ public record struct ItemToggleActivateAttemptEvent(EntityUid? User) { public bool Cancelled = false; public readonly EntityUid? User = User; + + /// + /// Pop-up that gets shown to users explaining why the attempt was cancelled. + /// + public string? Popup { get; set; } } /// @@ -77,21 +88,3 @@ public readonly record struct ItemToggledEvent(bool Predicted, bool Activated, E public readonly bool Activated = Activated; public readonly EntityUid? User = User; } - -/// -/// Raised directed on an entity when an itemtoggle is activated. -/// -[ByRefEvent] -public readonly record struct ItemToggleActivatedEvent(EntityUid? User) -{ - public readonly EntityUid? User = User; -} - -/// -/// Raised directed on an entity when an itemtoggle is deactivated. -/// -[ByRefEvent] -public readonly record struct ItemToggleDeactivatedEvent(EntityUid? User) -{ - public readonly EntityUid? User = User; -} diff --git a/Content.Shared/Item/ItemToggle/Components/ItemToggleHotComponent.cs b/Content.Shared/Item/ItemToggle/Components/ItemToggleHotComponent.cs index c3eb1d1e58b..4bcd6e25c08 100644 --- a/Content.Shared/Item/ItemToggle/Components/ItemToggleHotComponent.cs +++ b/Content.Shared/Item/ItemToggle/Components/ItemToggleHotComponent.cs @@ -5,12 +5,7 @@ namespace Content.Shared.Item.ItemToggle.Components; /// /// Handles whether the item is hot when toggled on. /// -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[RegisterComponent, NetworkedComponent] public sealed partial class ItemToggleHotComponent : Component { - /// - /// Item becomes hot when active. - /// - [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField] - public bool IsHotWhenActivated = true; } diff --git a/Content.Shared/Item/ItemToggle/SharedItemToggleSystem.cs b/Content.Shared/Item/ItemToggle/SharedItemToggleSystem.cs index e2e5fc2e3c3..523f67bac3d 100644 --- a/Content.Shared/Item/ItemToggle/SharedItemToggleSystem.cs +++ b/Content.Shared/Item/ItemToggle/SharedItemToggleSystem.cs @@ -1,5 +1,6 @@ using Content.Shared.Interaction.Events; using Content.Shared.Item.ItemToggle.Components; +using Content.Shared.Popups; using Content.Shared.Temperature; using Content.Shared.Toggleable; using Content.Shared.Wieldable; @@ -20,11 +21,13 @@ public abstract class SharedItemToggleSystem : EntitySystem [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedPointLightSystem _light = default!; [Dependency] private readonly INetManager _netManager = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(TurnOffonUnwielded); SubscribeLocalEvent(TurnOnonWielded); SubscribeLocalEvent(OnUseInHand); @@ -34,6 +37,11 @@ public override void Initialize() SubscribeLocalEvent(UpdateActiveSound); } + private void OnStartup(Entity ent, ref ComponentStartup args) + { + UpdateVisuals(ent); + } + private void OnUseInHand(EntityUid uid, ItemToggleComponent itemToggle, UseInHandEvent args) { if (args.Handled) @@ -73,6 +81,9 @@ public bool TryActivate(EntityUid uid, EntityUid? user = null, bool predicted = if (itemToggle.Activated) return true; + if (!itemToggle.Predictable && _netManager.IsClient) + return true; + var attempt = new ItemToggleActivateAttemptEvent(user); RaiseLocalEvent(uid, ref attempt); @@ -83,12 +94,16 @@ public bool TryActivate(EntityUid uid, EntityUid? user = null, bool predicted = else _audio.PlayPvs(itemToggle.SoundFailToActivate, uid); + if (attempt.Popup != null && user != null) + { + if (predicted) + _popup.PopupClient(attempt.Popup, uid, user.Value); + else + _popup.PopupEntity(attempt.Popup, uid, user.Value); + } + return false; } - // If the item's toggle is unpredictable because of something like requiring fuel or charge, then clients exit here. - // Otherwise you get stuff like an item activating client-side and then turning back off when it synchronizes with the server. - if (predicted == false && _netManager.IsClient) - return true; Activate(uid, itemToggle, predicted, user); @@ -103,6 +118,9 @@ public bool TryDeactivate(EntityUid uid, EntityUid? user = null, bool predicted if (!Resolve(uid, ref itemToggle)) return false; + if (!itemToggle.Predictable && _netManager.IsClient) + return true; + if (!itemToggle.Activated) return true; @@ -114,10 +132,6 @@ public bool TryDeactivate(EntityUid uid, EntityUid? user = null, bool predicted return false; } - // If the item's toggle is unpredictable because of something like requiring fuel or charge, then clients exit here. - if (predicted == false && _netManager.IsClient) - return true; - Deactivate(uid, itemToggle, predicted, user); return true; } @@ -135,7 +149,6 @@ private void Activate(EntityUid uid, ItemToggleComponent itemToggle, bool predic } var soundToPlay = itemToggle.SoundActivate; - if (predicted) _audio.PlayPredicted(soundToPlay, uid, user); else @@ -146,10 +159,8 @@ private void Activate(EntityUid uid, ItemToggleComponent itemToggle, bool predic var toggleUsed = new ItemToggledEvent(predicted, Activated: true, user); RaiseLocalEvent(uid, ref toggleUsed); - var activev = new ItemToggleActivatedEvent(user); - RaiseLocalEvent(uid, ref activev); - itemToggle.Activated = true; + UpdateVisuals((uid, itemToggle)); Dirty(uid, itemToggle); } @@ -158,35 +169,38 @@ private void Activate(EntityUid uid, ItemToggleComponent itemToggle, bool predic /// private void Deactivate(EntityUid uid, ItemToggleComponent itemToggle, bool predicted, EntityUid? user = null) { - // TODO: Fix this hardcoding - TryComp(uid, out AppearanceComponent? appearance); - _appearance.SetData(uid, ToggleableLightVisuals.Enabled, false, appearance); - _appearance.SetData(uid, ToggleVisuals.Toggled, false, appearance); - - if (_light.TryGetLight(uid, out var light)) - { - _light.SetEnabled(uid, false, light); - } - var soundToPlay = itemToggle.SoundDeactivate; - if (predicted) _audio.PlayPredicted(soundToPlay, uid, user); else _audio.PlayPvs(soundToPlay, uid); - // END FIX HARDCODING var toggleUsed = new ItemToggledEvent(predicted, Activated: false, user); RaiseLocalEvent(uid, ref toggleUsed); - var activev = new ItemToggleDeactivatedEvent(user); - RaiseLocalEvent(uid, ref activev); - itemToggle.Activated = false; + UpdateVisuals((uid, itemToggle)); Dirty(uid, itemToggle); } + private void UpdateVisuals(Entity ent) + { + if (TryComp(ent, out AppearanceComponent? appearance)) + { + _appearance.SetData(ent, ToggleVisuals.Toggled, ent.Comp.Activated, appearance); + + if (ent.Comp.ToggleLight) + _appearance.SetData(ent, ToggleableLightVisuals.Enabled, ent.Comp.Activated, appearance); + } + + if (!ent.Comp.ToggleLight) + return; + + if (_light.TryGetLight(ent, out var light)) + _light.SetEnabled(ent, ent.Comp.Activated, light); + } + /// /// Used for items that require to be wielded in both hands to activate. For instance the dual energy sword will turn off if not wielded. /// @@ -218,8 +232,7 @@ public bool IsActivated(EntityUid uid, ItemToggleComponent? comp = null) /// private void OnIsHotEvent(EntityUid uid, ItemToggleHotComponent itemToggleHot, IsHotEvent args) { - if (itemToggleHot.IsHotWhenActivated) - args.IsHot = IsActivated(uid); + args.IsHot |= IsActivated(uid); } /// From 8ae9449febd71fc1180ab41d7646ceea0a4d5f9e Mon Sep 17 00:00:00 2001 From: blueDev2 <89804215+blueDev2@users.noreply.github.com> Date: Thu, 28 Mar 2024 00:06:00 -0400 Subject: [PATCH 010/295] Allow cargo bounties to be sold off-station (#26469) * Ported over code for delta-v to fix bounties * Added requested changes * Removed the station arg from "IsBountyComplete". It is unneeded and all calls just use a null value for it anyways --- .../Components/CargoBountyLabelComponent.cs | 10 ++++++- .../Cargo/Systems/CargoSystem.Bounty.cs | 26 ++++++++++++------- .../Cargo/Systems/CargoSystem.Shuttle.cs | 17 +++++------- .../Cargo/Systems/PriceGunSystem.cs | 2 +- 4 files changed, 33 insertions(+), 22 deletions(-) diff --git a/Content.Server/Cargo/Components/CargoBountyLabelComponent.cs b/Content.Server/Cargo/Components/CargoBountyLabelComponent.cs index 8eea00e0993..2b9898be021 100644 --- a/Content.Server/Cargo/Components/CargoBountyLabelComponent.cs +++ b/Content.Server/Cargo/Components/CargoBountyLabelComponent.cs @@ -1,4 +1,6 @@ -namespace Content.Server.Cargo.Components; +using Content.Server.Station.Systems; + +namespace Content.Server.Cargo.Components; /// /// This is used for marking containers as @@ -17,4 +19,10 @@ public sealed partial class CargoBountyLabelComponent : Component /// Used to prevent recursion in calculating the price. /// public bool Calculating; + + /// + /// The Station System to check and remove bounties from + /// + [DataField] + public EntityUid? AssociatedStationId; } diff --git a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs index ce15542ec53..ee5ae631fd9 100644 --- a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs +++ b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs @@ -4,6 +4,7 @@ using Content.Server.Labels; using Content.Server.NameIdentifier; using Content.Server.Paper; +using Content.Server.Station.Systems; using Content.Shared.Cargo; using Content.Shared.Cargo.Components; using Content.Shared.Cargo.Prototypes; @@ -65,16 +66,17 @@ private void OnPrintLabelMessage(EntityUid uid, CargoBountyConsoleComponent comp var label = Spawn(component.BountyLabelId, Transform(uid).Coordinates); component.NextPrintTime = _timing.CurTime + component.PrintDelay; - SetupBountyLabel(label, bounty.Value); + SetupBountyLabel(label, station, bounty.Value); _audio.PlayPvs(component.PrintSound, uid); } - public void SetupBountyLabel(EntityUid uid, CargoBountyData bounty, PaperComponent? paper = null, CargoBountyLabelComponent? label = null) + public void SetupBountyLabel(EntityUid uid, EntityUid stationId, CargoBountyData bounty, PaperComponent? paper = null, CargoBountyLabelComponent? label = null) { if (!Resolve(uid, ref paper, ref label) || !_protoMan.TryIndex(bounty.Bounty, out var prototype)) return; label.Id = bounty.Id; + label.AssociatedStationId = stationId; var msg = new FormattedMessage(); msg.AddText(Loc.GetString("bounty-manifest-header", ("id", bounty.Id))); msg.PushNewline(); @@ -103,7 +105,7 @@ private void OnGetBountyPrice(EntityUid uid, CargoBountyLabelComponent component if (!_container.TryGetContainingContainer(uid, out var container) || container.ID != LabelSystem.ContainerName) return; - if (_station.GetOwningStation(uid) is not { } station || !TryComp(station, out var database)) + if (component.AssociatedStationId is not { } station || !TryComp(station, out var database)) return; if (database.CheckedBounties.Contains(component.Id)) @@ -131,14 +133,18 @@ private void OnSold(ref EntitySoldEvent args) if (!TryGetBountyLabel(sold, out _, out var component)) continue; - if (!TryGetBountyFromId(args.Station, component.Id, out var bounty)) + if (component.AssociatedStationId is not { } station || !TryGetBountyFromId(station, component.Id, out var bounty)) + { continue; + } if (!IsBountyComplete(sold, bounty.Value)) + { continue; + } - TryRemoveBounty(args.Station, bounty.Value); - FillBountyDatabase(args.Station); + TryRemoveBounty(station, bounty.Value); + FillBountyDatabase(station); _adminLogger.Add(LogType.Action, LogImpact.Low, $"Bounty \"{bounty.Value.Bounty}\" (id:{bounty.Value.Id}) was fulfilled"); } } @@ -196,7 +202,7 @@ public void RerollBountyDatabase(Entity en FillBountyDatabase(entity); } - public bool IsBountyComplete(EntityUid container, EntityUid? station, out HashSet bountyEntities) + public bool IsBountyComplete(EntityUid container, out HashSet bountyEntities) { if (!TryGetBountyLabel(container, out _, out var component)) { @@ -204,7 +210,7 @@ public bool IsBountyComplete(EntityUid container, EntityUid? station, out HashSe return false; } - station ??= _station.GetOwningStation(container); + var station = component.AssociatedStationId; if (station == null) { bountyEntities = new(); @@ -225,7 +231,7 @@ public bool IsBountyComplete(EntityUid container, CargoBountyData data) return IsBountyComplete(container, data, out _); } - public bool IsBountyComplete(EntityUid container, CargoBountyData data, out HashSet bountyEntities) + public bool IsBountyComplete(EntityUid container, CargoBountyData data, out HashSet bountyEntities) { if (!_protoMan.TryIndex(data.Bounty, out var proto)) { @@ -314,7 +320,7 @@ private HashSet GetBountyEntities(EntityUid uid) var children = GetBountyEntities(ent); foreach (var child in children) { - entities.Add(child); + entities.Add(child); } } } diff --git a/Content.Server/Cargo/Systems/CargoSystem.Shuttle.cs b/Content.Server/Cargo/Systems/CargoSystem.Shuttle.cs index b8a491f4e89..0484e05ced1 100644 --- a/Content.Server/Cargo/Systems/CargoSystem.Shuttle.cs +++ b/Content.Server/Cargo/Systems/CargoSystem.Shuttle.cs @@ -255,9 +255,8 @@ private int GetCargoSpace(EntityUid gridUid) #region Station - private bool SellPallets(EntityUid gridUid, EntityUid? station, out double amount) + private bool SellPallets(EntityUid gridUid, out double amount) { - station ??= _station.GetOwningStation(gridUid); GetPalletGoods(gridUid, out var toSell, out amount); Log.Debug($"Cargo sold {toSell.Count} entities for {amount}"); @@ -265,11 +264,9 @@ private bool SellPallets(EntityUid gridUid, EntityUid? station, out double amoun if (toSell.Count == 0) return false; - if (station != null) - { - var ev = new EntitySoldEvent(station.Value, toSell); - RaiseLocalEvent(ref ev); - } + + var ev = new EntitySoldEvent(toSell); + RaiseLocalEvent(ref ev); foreach (var ent in toSell) { @@ -324,7 +321,7 @@ private bool CanSell(EntityUid uid, TransformComponent xform) return false; } - var complete = IsBountyComplete(uid, (EntityUid?) null, out var bountyEntities); + var complete = IsBountyComplete(uid, out var bountyEntities); // Recursively check for mobs at any point. var children = xform.ChildEnumerator; @@ -357,7 +354,7 @@ private void OnPalletSale(EntityUid uid, CargoPalletConsoleComponent component, return; } - if (!SellPallets(gridUid, null, out var price)) + if (!SellPallets(gridUid, out var price)) return; var stackPrototype = _protoMan.Index(component.CashType); @@ -449,4 +446,4 @@ private void SetupTradePost() /// deleted but after the price has been calculated. /// [ByRefEvent] -public readonly record struct EntitySoldEvent(EntityUid Station, HashSet Sold); +public readonly record struct EntitySoldEvent(HashSet Sold); diff --git a/Content.Server/Cargo/Systems/PriceGunSystem.cs b/Content.Server/Cargo/Systems/PriceGunSystem.cs index 7bd49b857ae..19fe07bd253 100644 --- a/Content.Server/Cargo/Systems/PriceGunSystem.cs +++ b/Content.Server/Cargo/Systems/PriceGunSystem.cs @@ -57,7 +57,7 @@ private void OnAfterInteract(EntityUid uid, PriceGunComponent component, AfterIn return; // Check if we're scanning a bounty crate - if (_bountySystem.IsBountyComplete(args.Target.Value, (EntityUid?) null, out _)) + if (_bountySystem.IsBountyComplete(args.Target.Value, out _)) { _popupSystem.PopupEntity(Loc.GetString("price-gun-bounty-complete"), args.User, args.User); } From e692a058d97a84d1bf287b2255903cb3b82dcf26 Mon Sep 17 00:00:00 2001 From: superjj18 Date: Thu, 28 Mar 2024 01:41:20 -0400 Subject: [PATCH 011/295] Remove broadcast cooldown (#26492) * Removed inconsistent broadcast cooldown whenever the "Announce" button is pressed on the communications terminal. * Revert "Removed inconsistent broadcast cooldown whenever the "Announce" button is pressed on the communications terminal." This reverts commit c730d6499b6908f6ae7c52e21d5338fa3b7eb80e. * Reapply "Removed inconsistent broadcast cooldown whenever the "Announce" button is pressed on the communications terminal." This reverts commit 3c2d66af865a11ca55eb0e98db58a955c0d70c00. * -Removed cooldown entirely --- .../Communications/CommunicationsConsoleSystem.cs | 6 ------ .../Communications/SharedCommunicationsConsoleComponent.cs | 3 +-- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/Content.Server/Communications/CommunicationsConsoleSystem.cs b/Content.Server/Communications/CommunicationsConsoleSystem.cs index 6b745c8cd95..6475e1a6d73 100644 --- a/Content.Server/Communications/CommunicationsConsoleSystem.cs +++ b/Content.Server/Communications/CommunicationsConsoleSystem.cs @@ -170,7 +170,6 @@ public void UpdateCommsConsoleInterface(EntityUid uid, CommunicationsConsoleComp _uiSystem.SetUiState(ui, new CommunicationsConsoleInterfaceState( CanAnnounce(comp), - CanBroadcast(comp), CanCallOrRecall(comp), levels, currentLevel, @@ -184,11 +183,6 @@ private static bool CanAnnounce(CommunicationsConsoleComponent comp) return comp.AnnouncementCooldownRemaining <= 0f; } - private static bool CanBroadcast(CommunicationsConsoleComponent comp) - { - return comp.AnnouncementCooldownRemaining <= 0f; - } - private bool CanUse(EntityUid user, EntityUid console) { // This shouldn't technically be possible because of BUI but don't trust client. diff --git a/Content.Shared/Communications/SharedCommunicationsConsoleComponent.cs b/Content.Shared/Communications/SharedCommunicationsConsoleComponent.cs index bf0f5195b71..d48b99837f8 100644 --- a/Content.Shared/Communications/SharedCommunicationsConsoleComponent.cs +++ b/Content.Shared/Communications/SharedCommunicationsConsoleComponent.cs @@ -19,10 +19,9 @@ public sealed class CommunicationsConsoleInterfaceState : BoundUserInterfaceStat public string CurrentAlert; public float CurrentAlertDelay; - public CommunicationsConsoleInterfaceState(bool canAnnounce, bool canBroadcast, bool canCall, List? alertLevels, string currentAlert, float currentAlertDelay, TimeSpan? expectedCountdownEnd = null) + public CommunicationsConsoleInterfaceState(bool canAnnounce, bool canCall, List? alertLevels, string currentAlert, float currentAlertDelay, TimeSpan? expectedCountdownEnd = null) { CanAnnounce = canAnnounce; - CanBroadcast = canBroadcast; CanCall = canCall; ExpectedCountdownEnd = expectedCountdownEnd; CountdownStarted = expectedCountdownEnd != null; From 25fb743e7fbf7de4e19e3ad70614873668d239f5 Mon Sep 17 00:00:00 2001 From: Simon <63975668+Simyon264@users.noreply.github.com> Date: Thu, 28 Mar 2024 06:41:56 +0100 Subject: [PATCH 012/295] Add DoorBumpOpener to space dragon (#26490) Add DoorBumpOpener to space dragon.yml --- Resources/Prototypes/Entities/Mobs/Player/dragon.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/Resources/Prototypes/Entities/Mobs/Player/dragon.yml b/Resources/Prototypes/Entities/Mobs/Player/dragon.yml index aa10a631b54..025b8d917c7 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/dragon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/dragon.yml @@ -125,6 +125,7 @@ - type: Tag tags: - CannotSuicide + - DoorBumpOpener - type: entity parent: BaseMobDragon From 48757744f5b71f191401f2ffb0690c7c2e8b7279 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Thu, 28 Mar 2024 01:53:18 -0400 Subject: [PATCH 013/295] Use nav beacon locations for announcements (#26437) * use nav beacon locations for announcements * :thumbs_up: --- Content.Server/Dragon/DragonRiftSystem.cs | 6 +- .../Explosion/EntitySystems/TriggerSystem.cs | 10 +- Content.Server/Nuke/NukeSystem.cs | 4 +- Content.Server/Pinpointer/NavMapSystem.cs | 117 ++++++++++++++++ .../Systems/EmergencyShuttleSystem.cs | 10 +- .../ContentLocalizationManager.cs | 8 ++ .../Pinpointer/NavMapBeaconComponent.cs | 7 + .../Pinpointer/SharedNavMapSystem.cs | 14 -- Resources/Locale/en-US/_directions.ftl | 8 ++ Resources/Locale/en-US/dragon/rifts.ftl | 2 +- Resources/Locale/en-US/implant/implant.ftl | 4 +- .../en-US/navmap-beacons/station_map.ftl | 5 + .../Locale/en-US/nuke/nuke-component.ftl | 2 +- Resources/Locale/en-US/shuttles/emergency.ftl | 2 +- .../Objects/Devices/station_beacon.yml | 130 +++++++++--------- 15 files changed, 231 insertions(+), 98 deletions(-) create mode 100644 Resources/Locale/en-US/_directions.ftl diff --git a/Content.Server/Dragon/DragonRiftSystem.cs b/Content.Server/Dragon/DragonRiftSystem.cs index f7d5cd783d4..aa5be1427fb 100644 --- a/Content.Server/Dragon/DragonRiftSystem.cs +++ b/Content.Server/Dragon/DragonRiftSystem.cs @@ -13,6 +13,7 @@ using System.Numerics; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; +using Robust.Shared.Utility; namespace Content.Server.Dragon; @@ -69,8 +70,9 @@ public override void Update(float frameTime) comp.State = DragonRiftState.AlmostFinished; Dirty(comp); - var location = xform.LocalPosition; - _chat.DispatchGlobalAnnouncement(Loc.GetString("carp-rift-warning", ("location", location)), playSound: false, colorOverride: Color.Red); + var msg = Loc.GetString("carp-rift-warning", + ("location", FormattedMessage.RemoveMarkup(_navMap.GetNearestBeaconString((uid, xform))))); + _chat.DispatchGlobalAnnouncement(msg, playSound: false, colorOverride: Color.Red); _audio.PlayGlobal("/Audio/Misc/notice1.ogg", Filter.Broadcast(), true); _navMap.SetBeaconEnabled(uid, true); } diff --git a/Content.Server/Explosion/EntitySystems/TriggerSystem.cs b/Content.Server/Explosion/EntitySystems/TriggerSystem.cs index e24de5a2f66..94f55855362 100644 --- a/Content.Server/Explosion/EntitySystems/TriggerSystem.cs +++ b/Content.Server/Explosion/EntitySystems/TriggerSystem.cs @@ -3,6 +3,7 @@ using Content.Server.Chemistry.Containers.EntitySystems; using Content.Server.Explosion.Components; using Content.Server.Flash; +using Content.Server.Pinpointer; using Content.Shared.Flash.Components; using Content.Server.Radio.EntitySystems; using Content.Shared.Chemistry.Components; @@ -31,6 +32,7 @@ using Robust.Shared.Random; using Robust.Shared.Player; using Content.Shared.Coordinates; +using Robust.Shared.Utility; namespace Content.Server.Explosion.EntitySystems { @@ -67,6 +69,7 @@ public sealed partial class TriggerSystem : EntitySystem [Dependency] private readonly BodySystem _body = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedTransformSystem _transformSystem = default!; + [Dependency] private readonly NavMapSystem _navMap = default!; [Dependency] private readonly RadioSystem _radioSystem = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; @@ -185,12 +188,7 @@ private void HandleRattleTrigger(EntityUid uid, RattleComponent component, Trigg return; // Gets location of the implant - var ownerXform = Transform(uid); - var pos = ownerXform.MapPosition; - var x = (int) pos.X; - var y = (int) pos.Y; - var posText = $"({x}, {y})"; - + var posText = FormattedMessage.RemoveMarkup(_navMap.GetNearestBeaconString(uid)); var critMessage = Loc.GetString(component.CritMessage, ("user", implanted.ImplantedEntity.Value), ("position", posText)); var deathMessage = Loc.GetString(component.DeathMessage, ("user", implanted.ImplantedEntity.Value), ("position", posText)); diff --git a/Content.Server/Nuke/NukeSystem.cs b/Content.Server/Nuke/NukeSystem.cs index ad153dcf6b6..a376954bd9f 100644 --- a/Content.Server/Nuke/NukeSystem.cs +++ b/Content.Server/Nuke/NukeSystem.cs @@ -21,6 +21,7 @@ using Robust.Shared.Map.Components; using Robust.Shared.Player; using Robust.Shared.Random; +using Robust.Shared.Utility; namespace Content.Server.Nuke; @@ -463,7 +464,8 @@ public void ArmBomb(EntityUid uid, NukeComponent? component = null) // warn a crew var announcement = Loc.GetString("nuke-component-announcement-armed", - ("time", (int) component.RemainingTime), ("position", posText)); + ("time", (int) component.RemainingTime), + ("location", FormattedMessage.RemoveMarkup(_navMap.GetNearestBeaconString((uid, nukeXform))))); var sender = Loc.GetString("nuke-component-announcement-sender"); _chatSystem.DispatchStationAnnouncement(stationUid ?? uid, announcement, sender, false, null, Color.Red); diff --git a/Content.Server/Pinpointer/NavMapSystem.cs b/Content.Server/Pinpointer/NavMapSystem.cs index bf3a3b29988..aee8b901910 100644 --- a/Content.Server/Pinpointer/NavMapSystem.cs +++ b/Content.Server/Pinpointer/NavMapSystem.cs @@ -1,12 +1,16 @@ +using System.Diagnostics.CodeAnalysis; using Content.Server.Administration.Logs; using Content.Server.Station.Systems; using Content.Server.Warps; using Content.Shared.Database; using Content.Shared.Examine; +using Content.Shared.Localizations; using Content.Shared.Pinpointer; using Content.Shared.Tag; +using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Shared.GameStates; +using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; @@ -22,10 +26,15 @@ public sealed class NavMapSystem : SharedNavMapSystem [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly TagSystem _tags = default!; [Dependency] private readonly MapSystem _map = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly TransformSystem _transform = default!; private EntityQuery _physicsQuery; private EntityQuery _tagQuery; + public const float CloseDistance = 15f; + public const float FarDistance = 30f; + public override void Initialize() { base.Initialize(); @@ -40,6 +49,7 @@ public override void Initialize() SubscribeLocalEvent(OnGetState); SubscribeLocalEvent(OnNavMapSplit); + SubscribeLocalEvent(OnNavMapBeaconMapInit); SubscribeLocalEvent(OnNavMapBeaconStartup); SubscribeLocalEvent(OnNavMapBeaconAnchor); @@ -57,6 +67,16 @@ private void OnStationInit(StationGridAddedEvent ev) RefreshGrid(comp, Comp(ev.GridId)); } + private void OnNavMapBeaconMapInit(EntityUid uid, NavMapBeaconComponent component, MapInitEvent args) + { + if (component.DefaultText == null || component.Text != null) + return; + + component.Text = Loc.GetString(component.DefaultText); + Dirty(uid, component); + RefreshNavGrid(uid); + } + private void OnNavMapBeaconStartup(EntityUid uid, NavMapBeaconComponent component, ComponentStartup args) { RefreshNavGrid(uid); @@ -384,4 +404,101 @@ public void ToggleBeacon(EntityUid uid, NavMapBeaconComponent? comp = null) SetBeaconEnabled(uid, !comp.Enabled, comp); } + + /// + /// For a given position, tries to find the nearest configurable beacon that is marked as visible. + /// This is used for things like announcements where you want to find the closest "landmark" to something. + /// + [PublicAPI] + public bool TryGetNearestBeacon(Entity ent, + [NotNullWhen(true)] out Entity? beacon, + [NotNullWhen(true)] out MapCoordinates? beaconCoords) + { + beacon = null; + beaconCoords = null; + if (!Resolve(ent, ref ent.Comp)) + return false; + + return TryGetNearestBeacon(_transform.GetMapCoordinates(ent, ent.Comp), out beacon, out beaconCoords); + } + + /// + /// For a given position, tries to find the nearest configurable beacon that is marked as visible. + /// This is used for things like announcements where you want to find the closest "landmark" to something. + /// + public bool TryGetNearestBeacon(MapCoordinates coordinates, + [NotNullWhen(true)] out Entity? beacon, + [NotNullWhen(true)] out MapCoordinates? beaconCoords) + { + beacon = null; + beaconCoords = null; + var minDistance = float.PositiveInfinity; + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out _, out var navBeacon, out var xform)) + { + if (!navBeacon.Enabled) + continue; + + if (navBeacon.Text == null) + continue; + + if (coordinates.MapId != xform.MapID) + continue; + + var coords = _transform.GetWorldPosition(xform); + var distanceSquared = (coordinates.Position - coords).LengthSquared(); + if (!float.IsInfinity(minDistance) && distanceSquared >= minDistance) + continue; + + minDistance = distanceSquared; + beacon = (uid, navBeacon); + beaconCoords = new MapCoordinates(coords, xform.MapID); + } + + return beacon != null; + } + + [PublicAPI] + public string GetNearestBeaconString(Entity ent) + { + if (!Resolve(ent, ref ent.Comp)) + return Loc.GetString("nav-beacon-pos-no-beacons"); + + return GetNearestBeaconString(_transform.GetMapCoordinates(ent, ent.Comp)); + } + + public string GetNearestBeaconString(MapCoordinates coordinates) + { + if (!TryGetNearestBeacon(coordinates, out var beacon, out var pos)) + return Loc.GetString("nav-beacon-pos-no-beacons"); + + var gridOffset = Angle.Zero; + if (_mapManager.TryFindGridAt(pos.Value, out var grid, out _)) + gridOffset = Transform(grid).LocalRotation; + + // get the angle between the two positions, adjusted for the grid rotation so that + // we properly preserve north in relation to the grid. + var dir = (pos.Value.Position - coordinates.Position).ToWorldAngle(); + var adjustedDir = (dir - gridOffset).GetDir(); + + var length = (pos.Value.Position - coordinates.Position).Length(); + if (length < CloseDistance) + { + return Loc.GetString("nav-beacon-pos-format", + ("color", beacon.Value.Comp.Color), + ("marker", beacon.Value.Comp.Text!)); + } + + var modifier = length > FarDistance + ? Loc.GetString("nav-beacon-pos-format-direction-mod-far") + : string.Empty; + + // we can null suppress the text being null because TRyGetNearestVisibleStationBeacon always gives us a beacon with not-null text. + return Loc.GetString("nav-beacon-pos-format-direction", + ("modifier", modifier), + ("direction", ContentLocalizationManager.FormatDirection(adjustedDir).ToLowerInvariant()), + ("color", beacon.Value.Comp.Color), + ("marker", beacon.Value.Comp.Text!)); + } } diff --git a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs index 39b76f7d324..a7f83f2e158 100644 --- a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs +++ b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs @@ -8,6 +8,7 @@ using Content.Server.DeviceNetwork.Components; using Content.Server.DeviceNetwork.Systems; using Content.Server.GameTicking.Events; +using Content.Server.Pinpointer; using Content.Server.Popups; using Content.Server.RoundEnd; using Content.Server.Screens.Components; @@ -33,6 +34,7 @@ using Robust.Shared.Player; using Robust.Shared.Random; using Robust.Shared.Timing; +using Robust.Shared.Utility; namespace Content.Server.Shuttles.Systems; @@ -55,6 +57,7 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem [Dependency] private readonly DockingSystem _dock = default!; [Dependency] private readonly EntityManager _entityManager = default!; [Dependency] private readonly IdCardSystem _idSystem = default!; + [Dependency] private readonly NavMapSystem _navMap = default!; [Dependency] private readonly MapLoaderSystem _map = default!; [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly RoundEndSystem _roundEnd = default!; @@ -307,11 +310,8 @@ public void CallEmergencyShuttle(EntityUid stationUid, StationEmergencyShuttleCo } else { - if (TryComp(targetGrid.Value, out var targetXform)) - { - var angle = _dock.GetAngle(stationShuttle.EmergencyShuttle.Value, xform, targetGrid.Value, targetXform, xformQuery); - _chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-nearby", ("direction", angle.GetDir())), playDefaultSound: false); - } + var location = FormattedMessage.RemoveMarkup(_navMap.GetNearestBeaconString((stationShuttle.EmergencyShuttle.Value, xform))); + _chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-nearby", ("direction", location)), playDefaultSound: false); _logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle {ToPrettyString(stationUid)} unable to find a valid docking port for {ToPrettyString(stationUid)}"); // TODO: Need filter extensions or something don't blame me. diff --git a/Content.Shared/Localizations/ContentLocalizationManager.cs b/Content.Shared/Localizations/ContentLocalizationManager.cs index 0a06d9ba4f3..3c311f43821 100644 --- a/Content.Shared/Localizations/ContentLocalizationManager.cs +++ b/Content.Shared/Localizations/ContentLocalizationManager.cs @@ -119,6 +119,14 @@ public static string FormatList(List list) }; } + /// + /// Formats a direction struct as a human-readable string. + /// + public static string FormatDirection(Direction dir) + { + return Loc.GetString($"zzzz-fmt-direction-{dir.ToString()}"); + } + private static ILocValue FormatLoc(LocArgs args) { var id = ((LocValueString) args.Args[0]).Value; diff --git a/Content.Shared/Pinpointer/NavMapBeaconComponent.cs b/Content.Shared/Pinpointer/NavMapBeaconComponent.cs index c3132ee37f3..0a86bdd9d2e 100644 --- a/Content.Shared/Pinpointer/NavMapBeaconComponent.cs +++ b/Content.Shared/Pinpointer/NavMapBeaconComponent.cs @@ -16,6 +16,13 @@ public sealed partial class NavMapBeaconComponent : Component [AutoNetworkedField] public string? Text; + /// + /// A localization string that populates if it is null at mapinit. + /// Used so that mappers can still override Text while mapping. + /// + [DataField] + public LocId? DefaultText; + [ViewVariables(VVAccess.ReadWrite), DataField] [AutoNetworkedField] public Color Color = Color.Orange; diff --git a/Content.Shared/Pinpointer/SharedNavMapSystem.cs b/Content.Shared/Pinpointer/SharedNavMapSystem.cs index 7a62e6aabed..17f86ac7e68 100644 --- a/Content.Shared/Pinpointer/SharedNavMapSystem.cs +++ b/Content.Shared/Pinpointer/SharedNavMapSystem.cs @@ -8,13 +8,6 @@ public abstract class SharedNavMapSystem : EntitySystem { public const byte ChunkSize = 4; - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnNavMapBeaconMapInit); - } - /// /// Converts the chunk's tile into a bitflag for the slot. /// @@ -38,13 +31,6 @@ public static Vector2i GetTile(int flag) return new Vector2i(x, y); } - private void OnNavMapBeaconMapInit(EntityUid uid, NavMapBeaconComponent component, MapInitEvent args) - { - component.Text ??= string.Empty; - component.Text = Loc.GetString(component.Text); - Dirty(uid, component); - } - [Serializable, NetSerializable] protected sealed class NavMapComponentState : ComponentState { diff --git a/Resources/Locale/en-US/_directions.ftl b/Resources/Locale/en-US/_directions.ftl new file mode 100644 index 00000000000..7e4b82d1dc5 --- /dev/null +++ b/Resources/Locale/en-US/_directions.ftl @@ -0,0 +1,8 @@ +zzzz-fmt-direction-North = North +zzzz-fmt-direction-South = South +zzzz-fmt-direction-East = East +zzzz-fmt-direction-West = West +zzzz-fmt-direction-NorthEast = NorthEast +zzzz-fmt-direction-SouthEast = SouthEast +zzzz-fmt-direction-NorthWest = NorthWest +zzzz-fmt-direction-SouthWest = SouthWest diff --git a/Resources/Locale/en-US/dragon/rifts.ftl b/Resources/Locale/en-US/dragon/rifts.ftl index 5ad061abf96..c182ebf10ce 100644 --- a/Resources/Locale/en-US/dragon/rifts.ftl +++ b/Resources/Locale/en-US/dragon/rifts.ftl @@ -1,4 +1,4 @@ -carp-rift-warning = A rift is causing an unnaturally large energy flux at {$location}. Stop it at all costs! +carp-rift-warning = A rift is causing an unnaturally large energy flux {$location}. Stop it at all costs! carp-rift-duplicate = Cannot have 2 charging rifts at the same time! carp-rift-examine = It is [color=yellow]{$percentage}%[/color] charged! carp-rift-max = You have reached your maximum amount of rifts diff --git a/Resources/Locale/en-US/implant/implant.ftl b/Resources/Locale/en-US/implant/implant.ftl index 2f6ab9e4e2f..b93d43105a8 100644 --- a/Resources/Locale/en-US/implant/implant.ftl +++ b/Resources/Locale/en-US/implant/implant.ftl @@ -22,5 +22,5 @@ scramble-implant-activated-popup = Your appearance shifts and changes! ## Implant Messages -deathrattle-implant-dead-message = {$user} has died at {$position}. -deathrattle-implant-critical-message = {$user} life signs critical, immediate assistance required at {$position}. +deathrattle-implant-dead-message = {$user} has died {$position}. +deathrattle-implant-critical-message = {$user} life signs critical, immediate assistance required {$position}. diff --git a/Resources/Locale/en-US/navmap-beacons/station_map.ftl b/Resources/Locale/en-US/navmap-beacons/station_map.ftl index d58d93bdb4e..1563e0abaf2 100644 --- a/Resources/Locale/en-US/navmap-beacons/station_map.ftl +++ b/Resources/Locale/en-US/navmap-beacons/station_map.ftl @@ -11,3 +11,8 @@ nav-beacon-examine-text = It is [color={$enabled -> [true] forestgreen]on *[false] crimson]off }[/color] and the display reads [color={$color}]"{$label}"[/color] + +nav-beacon-pos-no-beacons = in the middle of nowhere +nav-beacon-pos-format = [color={$color}]near {$marker}[/color] +nav-beacon-pos-format-direction = [color={$color}]{$modifier}{$direction} of {$marker}[/color] +nav-beacon-pos-format-direction-mod-far = far {""} diff --git a/Resources/Locale/en-US/nuke/nuke-component.ftl b/Resources/Locale/en-US/nuke/nuke-component.ftl index b2a61dfcc2c..981dd8b6ae3 100644 --- a/Resources/Locale/en-US/nuke/nuke-component.ftl +++ b/Resources/Locale/en-US/nuke/nuke-component.ftl @@ -1,6 +1,6 @@ nuke-component-cant-anchor-floor = The anchoring bolts fail to lock into the floor! nuke-component-announcement-sender = Nuclear Fission Explosive -nuke-component-announcement-armed = Attention! The station's self-destruct mechanism has been engaged at global coordinates {$position}. {$time} seconds until detonation. If this was made in error, the mechanism may still be disarmed. +nuke-component-announcement-armed = Attention! The station's self-destruct mechanism has been engaged {$location}. {$time} seconds until detonation. If this was made in error, the mechanism may still be disarmed. nuke-component-announcement-unarmed = The station's self-destruct was deactivated! Have a nice day! nuke-component-announcement-send-codes = Attention! Self-destruction codes have been sent to designated fax machines. nuke-component-doafter-warning = You start fiddling with wires and knobs in order to disarm the nuke.. This may take a while. diff --git a/Resources/Locale/en-US/shuttles/emergency.ftl b/Resources/Locale/en-US/shuttles/emergency.ftl index 57d4d09effd..c7162911351 100644 --- a/Resources/Locale/en-US/shuttles/emergency.ftl +++ b/Resources/Locale/en-US/shuttles/emergency.ftl @@ -15,7 +15,7 @@ emergency-shuttle-left = The Emergency Shuttle has left the station. Estimate {$ emergency-shuttle-launch-time = The emergency shuttle will launch in {$consoleAccumulator} seconds. emergency-shuttle-docked = The Emergency Shuttle has docked with the station on the {$direction} side. It will leave in {$time} seconds. emergency-shuttle-good-luck = The Emergency Shuttle is unable to find a station. Good luck. -emergency-shuttle-nearby = The Emergency Shuttle is unable to find a valid docking port. It has warped in {$direction} of the station. +emergency-shuttle-nearby = The Emergency Shuttle is unable to find a valid docking port. It has warped {$direction}. # Emergency shuttle console popup / announcement emergency-shuttle-console-no-early-launches = Early launch is disabled diff --git a/Resources/Prototypes/Entities/Objects/Devices/station_beacon.yml b/Resources/Prototypes/Entities/Objects/Devices/station_beacon.yml index e5a1d0412a0..9afef7abc71 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/station_beacon.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/station_beacon.yml @@ -22,7 +22,7 @@ False: {state: icon} - type: ConfigurableNavMapBeacon - type: NavMapBeacon - text: station-beacon-general + defaultText: station-beacon-general color: "#D4D4D496" #- type: WarpPoint # Delta V - Removes in favor of Warp Point Markers - type: ActivatableUI @@ -118,7 +118,7 @@ suffix: Command components: - type: NavMapBeacon - text: station-beacon-command + defaultText: station-beacon-command color: "#FFFF00" - type: entity @@ -127,7 +127,7 @@ suffix: Bridge components: - type: NavMapBeacon - text: station-beacon-bridge + defaultText: station-beacon-bridge - type: entity parent: DefaultStationBeaconCommand @@ -135,7 +135,7 @@ suffix: Vault components: - type: NavMapBeacon - text: station-beacon-vault + defaultText: station-beacon-vault - type: entity parent: DefaultStationBeaconCommand @@ -143,7 +143,7 @@ suffix: Captain's Quarters components: - type: NavMapBeacon - text: station-beacon-captain + defaultText: station-beacon-captain - type: entity parent: DefaultStationBeaconCommand @@ -151,7 +151,7 @@ suffix: HOP's Office components: - type: NavMapBeacon - text: station-beacon-hop + defaultText: station-beacon-hop - type: entity parent: DefaultStationBeacon @@ -159,7 +159,7 @@ suffix: Security components: - type: NavMapBeacon - text: station-beacon-security + defaultText: station-beacon-security color: "#DE3A3A" - type: entity @@ -168,7 +168,7 @@ suffix: Brig components: - type: NavMapBeacon - text: station-beacon-brig + defaultText: station-beacon-brig - type: entity parent: DefaultStationBeaconSecurity @@ -176,7 +176,7 @@ suffix: Warden's Office components: - type: NavMapBeacon - text: station-beacon-warden + defaultText: station-beacon-warden - type: entity parent: DefaultStationBeaconSecurity @@ -184,7 +184,7 @@ suffix: HOS’s Room components: - type: NavMapBeacon - text: station-beacon-hos + defaultText: station-beacon-hos - type: entity parent: DefaultStationBeaconSecurity @@ -192,7 +192,7 @@ suffix: Armory components: - type: NavMapBeacon - text: station-beacon-armory + defaultText: station-beacon-armory - type: entity parent: DefaultStationBeaconSecurity @@ -200,7 +200,7 @@ suffix: Perma Brig components: - type: NavMapBeacon - text: station-beacon-perma-brig + defaultText: station-beacon-perma-brig - type: entity parent: DefaultStationBeaconSecurity @@ -208,7 +208,7 @@ suffix: Detective's Room components: - type: NavMapBeacon - text: station-beacon-detective + defaultText: station-beacon-detective - type: entity parent: DefaultStationBeaconSecurity @@ -216,7 +216,7 @@ suffix: Courtroom components: - type: NavMapBeacon - text: station-beacon-courtroom + defaultText: station-beacon-courtroom - type: entity parent: DefaultStationBeaconService #Delta V - Lawer is Service until Justice Dept is a thing @@ -224,7 +224,7 @@ suffix: Law Office components: - type: NavMapBeacon - text: station-beacon-law + defaultText: station-beacon-law - type: entity parent: DefaultStationBeaconSecurity @@ -232,7 +232,7 @@ suffix: Sec Checkpoint components: - type: NavMapBeacon - text: station-beacon-security-checkpoint + defaultText: station-beacon-security-checkpoint - type: entity parent: DefaultStationBeacon @@ -240,7 +240,7 @@ suffix: Medical components: - type: NavMapBeacon - text: station-beacon-medical + defaultText: station-beacon-medical color: "#52B4E9" - type: entity @@ -249,7 +249,7 @@ suffix: Medbay components: - type: NavMapBeacon - text: station-beacon-medbay + defaultText: station-beacon-medbay - type: entity parent: DefaultStationBeaconMedical @@ -257,7 +257,7 @@ suffix: Chemistry components: - type: NavMapBeacon - text: station-beacon-chemistry + defaultText: station-beacon-chemistry - type: entity parent: DefaultStationBeaconMedical @@ -265,7 +265,7 @@ suffix: Cryonics components: - type: NavMapBeacon - text: station-beacon-cryonics + defaultText: station-beacon-cryonics - type: entity parent: DefaultStationBeaconMedical @@ -273,7 +273,7 @@ suffix: CMO's room components: - type: NavMapBeacon - text: station-beacon-cmo + defaultText: station-beacon-cmo - type: entity parent: DefaultStationBeaconMedical @@ -281,7 +281,7 @@ suffix: Morgue components: - type: NavMapBeacon - text: station-beacon-morgue + defaultText: station-beacon-morgue - type: entity parent: DefaultStationBeaconMedical @@ -289,7 +289,7 @@ suffix: Surgery components: - type: NavMapBeacon - text: station-beacon-surgery + defaultText: station-beacon-surgery - type: entity parent: DefaultStationBeacon @@ -297,7 +297,7 @@ suffix: Epistemics #Delta V - Renamed components: - type: NavMapBeacon - text: station-beacon-epistemics #Delta V - Renamed + defaultText: station-beacon-epistemics #Delta V - Renamed color: "#D381C9" - type: entity @@ -306,7 +306,7 @@ suffix: Research and Development components: - type: NavMapBeacon - text: station-beacon-research-and-development + defaultText: station-beacon-research-and-development - type: entity parent: DefaultStationBeaconScience @@ -314,7 +314,7 @@ suffix: Research Server Room components: - type: NavMapBeacon - text: station-beacon-research-server + defaultText: station-beacon-research-server - type: entity parent: DefaultStationBeaconScience @@ -322,7 +322,7 @@ suffix: MG's Room components: - type: NavMapBeacon - text: station-beacon-mystagogue #Delta V - Renamed + defaultText: station-beacon-mystagogue #Delta V - Renamed - type: entity parent: DefaultStationBeaconScience @@ -330,7 +330,7 @@ suffix: Robotics components: - type: NavMapBeacon - text: station-beacon-robotics + defaultText: station-beacon-robotics - type: entity parent: DefaultStationBeaconScience @@ -338,7 +338,7 @@ suffix: Artifact Lab components: - type: NavMapBeacon - text: station-beacon-artifact-lab + defaultText: station-beacon-artifact-lab - type: entity parent: DefaultStationBeaconScience @@ -346,7 +346,7 @@ suffix: Anomaly Generator components: - type: NavMapBeacon - text: station-beacon-anomaly-gen + defaultText: station-beacon-anomaly-gen - type: entity parent: DefaultStationBeacon @@ -354,7 +354,7 @@ suffix: Logistics #Delta V - Renamed components: - type: NavMapBeacon - text: station-beacon-logistics #Delta V - Renamed + defaultText: station-beacon-logistics #Delta V - Renamed color: "#A46106" - type: entity @@ -363,7 +363,7 @@ suffix: Logistics Reception #Delta V - Renamed components: - type: NavMapBeacon - text: station-beacon-logistics-reception #Delta V - Renamed + defaultText: station-beacon-logistics-reception #Delta V - Renamed - type: entity parent: DefaultStationBeaconSupply @@ -371,7 +371,7 @@ suffix: Cargo Bay components: - type: NavMapBeacon - text: station-beacon-cargo-bay + defaultText: station-beacon-cargo-bay - type: entity parent: DefaultStationBeaconSupply @@ -379,7 +379,7 @@ suffix: LO #Delta V - Renamed components: - type: NavMapBeacon - text: station-beacon-lo #Delta V - Renamed + defaultText: station-beacon-lo #Delta V - Renamed - type: entity parent: DefaultStationBeaconSupply @@ -387,7 +387,7 @@ suffix: Salvage components: - type: NavMapBeacon - text: station-beacon-salvage + defaultText: station-beacon-salvage - type: entity parent: DefaultStationBeacon @@ -395,7 +395,7 @@ suffix: Engineering components: - type: NavMapBeacon - text: station-beacon-engineering + defaultText: station-beacon-engineering color: "#EFB341" - type: entity @@ -404,7 +404,7 @@ suffix: CE's Room components: - type: NavMapBeacon - text: station-beacon-ce + defaultText: station-beacon-ce - type: entity parent: DefaultStationBeaconEngineering @@ -412,7 +412,7 @@ suffix: AME components: - type: NavMapBeacon - text: station-beacon-ame + defaultText: station-beacon-ame - type: entity parent: DefaultStationBeaconEngineering @@ -420,7 +420,7 @@ suffix: Solars components: - type: NavMapBeacon - text: station-beacon-solars + defaultText: station-beacon-solars - type: entity parent: DefaultStationBeaconEngineering @@ -428,7 +428,7 @@ suffix: Grav Gen components: - type: NavMapBeacon - text: station-beacon-gravgen + defaultText: station-beacon-gravgen - type: entity parent: DefaultStationBeaconEngineering @@ -436,7 +436,7 @@ suffix: PA Control components: - type: NavMapBeacon - text: station-beacon-pa + defaultText: station-beacon-pa - type: entity parent: DefaultStationBeaconEngineering @@ -444,7 +444,7 @@ suffix: SMES Power Bank components: - type: NavMapBeacon - text: station-beacon-smes + defaultText: station-beacon-smes - type: entity parent: DefaultStationBeaconEngineering @@ -452,7 +452,7 @@ suffix: Telecoms components: - type: NavMapBeacon - text: station-beacon-telecoms + defaultText: station-beacon-telecoms - type: entity parent: DefaultStationBeaconEngineering @@ -460,7 +460,7 @@ suffix: Atmospherics components: - type: NavMapBeacon - text: station-beacon-atmos + defaultText: station-beacon-atmos - type: entity parent: DefaultStationBeaconEngineering @@ -468,7 +468,7 @@ suffix: TEG components: - type: NavMapBeacon - text: station-beacon-teg + defaultText: station-beacon-teg - type: entity parent: DefaultStationBeaconEngineering @@ -476,7 +476,7 @@ suffix: Tech Vault components: - type: NavMapBeacon - text: station-beacon-tech-vault + defaultText: station-beacon-tech-vault - type: entity parent: DefaultStationBeacon @@ -484,7 +484,7 @@ suffix: Service components: - type: NavMapBeacon - text: station-beacon-service + defaultText: station-beacon-service color: "#9FED58" - type: entity @@ -493,7 +493,7 @@ suffix: Kitchen components: - type: NavMapBeacon - text: station-beacon-kitchen + defaultText: station-beacon-kitchen - type: entity parent: DefaultStationBeaconService @@ -501,7 +501,7 @@ suffix: Bar components: - type: NavMapBeacon - text: station-beacon-bar + defaultText: station-beacon-bar - type: entity parent: DefaultStationBeaconService @@ -509,7 +509,7 @@ suffix: Botany components: - type: NavMapBeacon - text: station-beacon-botany + defaultText: station-beacon-botany - type: entity parent: DefaultStationBeaconService @@ -517,7 +517,7 @@ suffix: Janitor's Closet components: - type: NavMapBeacon - text: station-beacon-janitor-closet #Delta V - Add for closet/outpost + defaultText: station-beacon-janitor-closet #Delta V - Add for closet/outpost - type: entity parent: DefaultStationBeacon @@ -525,7 +525,7 @@ suffix: AI components: - type: NavMapBeacon - text: station-beacon-ai + defaultText: station-beacon-ai color: "#2ed2fd" - type: entity @@ -534,7 +534,7 @@ suffix: AI Satellite components: - type: NavMapBeacon - text: station-beacon-ai-sat + defaultText: station-beacon-ai-sat - type: entity parent: DefaultStationBeaconAI @@ -542,7 +542,7 @@ suffix: AI Core components: - type: NavMapBeacon - text: station-beacon-ai-core + defaultText: station-beacon-ai-core - type: entity parent: DefaultStationBeacon @@ -550,7 +550,7 @@ suffix: Arrivals components: - type: NavMapBeacon - text: station-beacon-arrivals + defaultText: station-beacon-arrivals - type: entity parent: DefaultStationBeacon @@ -558,7 +558,7 @@ suffix: Evac components: - type: NavMapBeacon - text: station-beacon-evac + defaultText: station-beacon-evac - type: entity parent: DefaultStationBeacon @@ -566,7 +566,7 @@ suffix: EVA Storage components: - type: NavMapBeacon - text: station-beacon-eva-storage + defaultText: station-beacon-eva-storage - type: entity parent: DefaultStationBeaconScience #Delta V - Chapel in Epi @@ -574,7 +574,7 @@ suffix: Chapel components: - type: NavMapBeacon - text: station-beacon-chapel + defaultText: station-beacon-chapel - type: entity parent: DefaultStationBeacon @@ -582,7 +582,7 @@ suffix: Library components: - type: NavMapBeacon - text: station-beacon-library + defaultText: station-beacon-library - type: entity parent: DefaultStationBeacon @@ -590,7 +590,7 @@ suffix: Theater components: - type: NavMapBeacon - text: station-beacon-theater + defaultText: station-beacon-theater - type: entity parent: DefaultStationBeacon @@ -598,7 +598,7 @@ suffix: Dorms components: - type: NavMapBeacon - text: station-beacon-dorms + defaultText: station-beacon-dorms - type: entity parent: DefaultStationBeacon @@ -606,7 +606,7 @@ suffix: Tool Room components: - type: NavMapBeacon - text: station-beacon-tools + defaultText: station-beacon-tools - type: entity parent: DefaultStationBeacon @@ -614,7 +614,7 @@ suffix: Disposals components: - type: NavMapBeacon - text: station-beacon-disposals + defaultText: station-beacon-disposals - type: entity parent: DefaultStationBeacon @@ -622,7 +622,7 @@ suffix: Cryosleep components: - type: NavMapBeacon - text: station-beacon-cryosleep + defaultText: station-beacon-cryosleep - type: entity parent: DefaultStationBeacon @@ -630,4 +630,4 @@ suffix: Escape Pod components: - type: NavMapBeacon - text: station-beacon-escape-pod + defaultText: station-beacon-escape-pod From e358581cef4e3d5b7e9723f0b896a8fb3e0b1873 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Thu, 28 Mar 2024 02:28:45 -0400 Subject: [PATCH 014/295] Arcade machine improvements (#24200) * Give 'em something to talk about * Wire panel visuals * Wire graphics tweak * More ads and thanks * More ads for a noisy arcade * New screen for space villain machines * Implement EmitSoundIntervalComponent and a bunch of arcade noises * Require power for sounds * Allow earlier startup intervals * Orange glow * Audio attributions * Include the PR link * Replace EmitSoundInterval with expanded SpamEmitSound * Remove pacman-themed arcade sounds * Documentation good. * Updated methods to use Entity * Refactored SpamEmitSound to get rid of accumulator and chance. * Fixed prewarm logic * Moved stuff to Shared * Fix outdated YAML * Better prediction, auto pause handling * Make enable/disable reset the timer instead of trying to save it. --- .../BlockGame/BlockGameArcadeComponent.cs | 5 ++ .../Arcade/BlockGame/BlockGameArcadeSystem.cs | 13 ++++ .../SpaceVillainArcadeComponent.cs | 5 ++ .../SpaceVillainArcadeSystem.cs | 18 ++++++ Content.Server/Bed/Sleep/SleepingSystem.cs | 10 +-- .../Components/SpamEmitSoundComponent.cs | 27 --------- Content.Server/Sound/EmitSoundSystem.cs | 55 ++++++++++++++--- .../Sound/SpamEmitSoundRequirePowerSystem.cs | 33 ++++++++++ .../Bed/Sleep/SleepEmitSoundComponent.cs | 8 +-- .../Components/SpamEmitSoundComponent.cs | 44 ++++++++++++++ .../SpamEmitSoundRequirePowerComponent.cs | 10 +++ Content.Shared/Sound/SharedEmitSoundSystem.cs | 10 ++- .../SharedSpamEmitSoundRequirePowerSystem.cs | 6 ++ .../Audio/Machines/Arcade/attributions.yml | 4 ++ Resources/Audio/Machines/Arcade/hahaha.ogg | Bin 0 -> 57972 bytes Resources/Audio/Machines/Arcade/pew_pew.ogg | Bin 0 -> 24306 bytes Resources/Audio/Machines/Arcade/sting_01.ogg | Bin 0 -> 39700 bytes Resources/Audio/Machines/Arcade/sting_02.ogg | Bin 0 -> 57619 bytes Resources/Audio/Machines/Arcade/sting_03.ogg | Bin 0 -> 23663 bytes Resources/Audio/Machines/Arcade/sting_04.ogg | Bin 0 -> 29128 bytes Resources/Audio/Machines/Arcade/sting_05.ogg | Bin 0 -> 38925 bytes Resources/Audio/Machines/Arcade/sting_06.ogg | Bin 0 -> 31830 bytes .../en-US/advertisements/arcade/blockgame.ftl | 26 ++++++++ .../advertisements/arcade/spacevillain.ftl | 28 +++++++++ .../Arcade/Advertisements/blockgame.yml | 29 +++++++++ .../Arcade/Advertisements/spacevillain.yml | 31 ++++++++++ .../Structures/Machines/Computers/arcades.yml | 57 +++++++++++++++--- .../Prototypes/Roles/Jobs/Civilian/clown.yml | 3 +- .../Prototypes/SoundCollections/arcade.yml | 11 ++++ Resources/Prototypes/Wires/layouts.yml | 1 + .../Structures/Machines/arcade.rsi/meta.json | 50 ++++++++++++++- .../Structures/Machines/arcade.rsi/panel.png | Bin 0 -> 236 bytes .../{blockgame.png => screen_blockgame.png} | Bin .../{invaders.png => screen_invaders.png} | Bin .../arcade.rsi/screen_spacevillain.png | Bin 0 -> 930 bytes 35 files changed, 426 insertions(+), 58 deletions(-) delete mode 100644 Content.Server/Sound/Components/SpamEmitSoundComponent.cs create mode 100644 Content.Server/Sound/SpamEmitSoundRequirePowerSystem.cs create mode 100644 Content.Shared/Sound/Components/SpamEmitSoundComponent.cs create mode 100644 Content.Shared/Sound/Components/SpamEmitSoundRequirePowerComponent.cs create mode 100644 Content.Shared/Sound/SharedSpamEmitSoundRequirePowerSystem.cs create mode 100644 Resources/Audio/Machines/Arcade/attributions.yml create mode 100644 Resources/Audio/Machines/Arcade/hahaha.ogg create mode 100644 Resources/Audio/Machines/Arcade/pew_pew.ogg create mode 100644 Resources/Audio/Machines/Arcade/sting_01.ogg create mode 100644 Resources/Audio/Machines/Arcade/sting_02.ogg create mode 100644 Resources/Audio/Machines/Arcade/sting_03.ogg create mode 100644 Resources/Audio/Machines/Arcade/sting_04.ogg create mode 100644 Resources/Audio/Machines/Arcade/sting_05.ogg create mode 100644 Resources/Audio/Machines/Arcade/sting_06.ogg create mode 100644 Resources/Locale/en-US/advertisements/arcade/blockgame.ftl create mode 100644 Resources/Locale/en-US/advertisements/arcade/spacevillain.ftl create mode 100644 Resources/Prototypes/Catalog/Arcade/Advertisements/blockgame.yml create mode 100644 Resources/Prototypes/Catalog/Arcade/Advertisements/spacevillain.yml create mode 100644 Resources/Prototypes/SoundCollections/arcade.yml create mode 100644 Resources/Textures/Structures/Machines/arcade.rsi/panel.png rename Resources/Textures/Structures/Machines/arcade.rsi/{blockgame.png => screen_blockgame.png} (100%) rename Resources/Textures/Structures/Machines/arcade.rsi/{invaders.png => screen_invaders.png} (100%) create mode 100644 Resources/Textures/Structures/Machines/arcade.rsi/screen_spacevillain.png diff --git a/Content.Server/Arcade/BlockGame/BlockGameArcadeComponent.cs b/Content.Server/Arcade/BlockGame/BlockGameArcadeComponent.cs index 5613d915444..e2acec52a3a 100644 --- a/Content.Server/Arcade/BlockGame/BlockGameArcadeComponent.cs +++ b/Content.Server/Arcade/BlockGame/BlockGameArcadeComponent.cs @@ -19,4 +19,9 @@ public sealed partial class BlockGameArcadeComponent : Component /// The players currently viewing (but not playing) the active session of NT-BG. /// public readonly List Spectators = new(); + + /// + /// Whether the game machine should thank (or otherwise talk to) the player when they leave + /// + public bool ShouldSayThankYou; } diff --git a/Content.Server/Arcade/BlockGame/BlockGameArcadeSystem.cs b/Content.Server/Arcade/BlockGame/BlockGameArcadeSystem.cs index 34a5689fd15..0d9487dab85 100644 --- a/Content.Server/Arcade/BlockGame/BlockGameArcadeSystem.cs +++ b/Content.Server/Arcade/BlockGame/BlockGameArcadeSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Power.Components; using Content.Shared.UserInterface; +using Content.Server.Advertise; using Content.Shared.Arcade; using Robust.Server.GameObjects; using Robust.Shared.Player; @@ -9,6 +10,7 @@ namespace Content.Server.Arcade.BlockGame; public sealed class BlockGameArcadeSystem : EntitySystem { [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; + [Dependency] private readonly AdvertiseSystem _advertise = default!; public override void Initialize() { @@ -89,7 +91,15 @@ private void OnAfterUiClose(EntityUid uid, BlockGameArcadeComponent component, B UpdatePlayerStatus(uid, component.Player, blockGame: component); } else + { + // Everybody's gone component.Player = null; + if (component.ShouldSayThankYou && TryComp(uid, out var advertise)) + { + _advertise.SayThankYou(uid, advertise); + component.ShouldSayThankYou = false; + } + } UpdatePlayerStatus(uid, temp, blockGame: component); } @@ -103,6 +113,7 @@ private void OnBlockPowerChanged(EntityUid uid, BlockGameArcadeComponent compone _uiSystem.CloseAll(bui); component.Player = null; component.Spectators.Clear(); + component.ShouldSayThankYou = false; } private void OnPlayerAction(EntityUid uid, BlockGameArcadeComponent component, BlockGameMessages.BlockGamePlayerActionMessage msg) @@ -122,6 +133,8 @@ private void OnPlayerAction(EntityUid uid, BlockGameArcadeComponent component, B return; } + component.ShouldSayThankYou = true; + component.Game.ProcessInput(msg.PlayerAction); } } diff --git a/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeComponent.cs b/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeComponent.cs index e93fcc6e8f1..c3a8877393e 100644 --- a/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeComponent.cs +++ b/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeComponent.cs @@ -110,4 +110,9 @@ public sealed partial class SpaceVillainArcadeComponent : SharedSpaceVillainArca /// [ViewVariables(VVAccess.ReadWrite)] public int RewardAmount = 0; + + /// + /// Whether the game machine should thank (or otherwise talk to) the player when they leave + /// + public bool ShouldSayThankYou; } diff --git a/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs b/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs index d97c94fd992..24fa6e32d19 100644 --- a/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs +++ b/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Power.Components; using Content.Shared.UserInterface; +using Content.Server.Advertise; using static Content.Shared.Arcade.SharedSpaceVillainArcadeComponent; using Robust.Server.GameObjects; using Robust.Shared.Audio; @@ -13,6 +14,7 @@ public sealed partial class SpaceVillainArcadeSystem : EntitySystem [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly SharedAudioSystem _audioSystem = default!; [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; + [Dependency] private readonly AdvertiseSystem _advertise = default!; public override void Initialize() { @@ -22,6 +24,7 @@ public override void Initialize() SubscribeLocalEvent(OnAfterUIOpenSV); SubscribeLocalEvent(OnSVPlayerAction); SubscribeLocalEvent(OnSVillainPower); + SubscribeLocalEvent(OnBoundUIClosed); } /// @@ -79,6 +82,7 @@ private void OnSVPlayerAction(EntityUid uid, SpaceVillainArcadeComponent compone case PlayerAction.Heal: case PlayerAction.Recharge: component.Game.ExecutePlayerAction(uid, msg.PlayerAction, component); + component.ShouldSayThankYou = true; // Any sort of gameplay action counts break; case PlayerAction.NewGame: _audioSystem.PlayPvs(component.NewGameSound, uid, AudioParams.Default.WithVolume(-4f)); @@ -106,5 +110,19 @@ private void OnSVillainPower(EntityUid uid, SpaceVillainArcadeComponent componen if (_uiSystem.TryGetUi(uid, SpaceVillainArcadeUiKey.Key, out var bui)) _uiSystem.CloseAll(bui); + + component.ShouldSayThankYou = false; + } + + private void OnBoundUIClosed(Entity ent, ref BoundUIClosedEvent args) + { + if (args.UiKey is not SpaceVillainArcadeUiKey || (SpaceVillainArcadeUiKey) args.UiKey != SpaceVillainArcadeUiKey.Key) + return; + + if (ent.Comp.ShouldSayThankYou && TryComp(ent.Owner, out var advertise)) + { + _advertise.SayThankYou(ent.Owner, advertise); + ent.Comp.ShouldSayThankYou = false; + } } } diff --git a/Content.Server/Bed/Sleep/SleepingSystem.cs b/Content.Server/Bed/Sleep/SleepingSystem.cs index b4972544338..5e4f0eddb52 100644 --- a/Content.Server/Bed/Sleep/SleepingSystem.cs +++ b/Content.Server/Bed/Sleep/SleepingSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Popups; -using Content.Server.Sound.Components; +using Content.Server.Sound; +using Content.Shared.Sound.Components; using Content.Shared.Actions; using Content.Shared.Audio; using Content.Shared.Bed.Sleep; @@ -30,6 +31,7 @@ public sealed class SleepingSystem : SharedSleepingSystem [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!; + [Dependency] private readonly EmitSoundSystem _emitSound = default!; [ValidatePrototypeId] public const string SleepActionId = "ActionSleep"; @@ -71,8 +73,8 @@ private void OnSleepStateChanged(EntityUid uid, MobStateComponent component, Sle { emitSound.Sound = sleepSound.Snore; } - emitSound.PlayChance = sleepSound.Chance; - emitSound.RollInterval = sleepSound.Interval; + emitSound.MinInterval = sleepSound.Interval; + emitSound.MaxInterval = sleepSound.MaxInterval; emitSound.PopUp = sleepSound.PopUp; } @@ -128,7 +130,7 @@ private void OnMobStateChanged(EntityUid uid, SleepingComponent component, MobSt return; } if (TryComp(uid, out var spam)) - spam.Enabled = args.NewMobState == MobState.Alive; + _emitSound.SetEnabled((uid, spam), args.NewMobState == MobState.Alive); } private void AddWakeVerb(EntityUid uid, SleepingComponent component, GetVerbsEvent args) diff --git a/Content.Server/Sound/Components/SpamEmitSoundComponent.cs b/Content.Server/Sound/Components/SpamEmitSoundComponent.cs deleted file mode 100644 index d17bbb9e8f6..00000000000 --- a/Content.Server/Sound/Components/SpamEmitSoundComponent.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Content.Shared.Sound.Components; - -namespace Content.Server.Sound.Components -{ - /// - /// Rolls to play a sound every few seconds. - /// - [RegisterComponent] - public sealed partial class SpamEmitSoundComponent : BaseEmitSoundComponent - { - [DataField("accumulator")] - public float Accumulator = 0f; - - [DataField("rollInterval")] - public float RollInterval = 2f; - - [DataField("playChance")] - public float PlayChance = 0.5f; - - // Always Pvs. - [DataField("popUp")] - public string? PopUp; - - [DataField("enabled")] - public bool Enabled = true; - } -} diff --git a/Content.Server/Sound/EmitSoundSystem.cs b/Content.Server/Sound/EmitSoundSystem.cs index 059800c3f9d..5b9620990eb 100644 --- a/Content.Server/Sound/EmitSoundSystem.cs +++ b/Content.Server/Sound/EmitSoundSystem.cs @@ -2,12 +2,17 @@ using Content.Server.Sound.Components; using Content.Shared.UserInterface; using Content.Shared.Sound; -using Robust.Shared.Random; +using Content.Shared.Sound.Components; +using Robust.Shared.Timing; +using Robust.Shared.Network; namespace Content.Server.Sound; public sealed class EmitSoundSystem : SharedEmitSoundSystem { + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly INetManager _net = default!; + public override void Update(float frameTime) { base.Update(frameTime); @@ -18,18 +23,13 @@ public override void Update(float frameTime) if (!soundSpammer.Enabled) continue; - soundSpammer.Accumulator += frameTime; - if (soundSpammer.Accumulator < soundSpammer.RollInterval) - { - continue; - } - soundSpammer.Accumulator -= soundSpammer.RollInterval; - - if (Random.Prob(soundSpammer.PlayChance)) + if (_timing.CurTime >= soundSpammer.NextSound) { if (soundSpammer.PopUp != null) Popup.PopupEntity(Loc.GetString(soundSpammer.PopUp), uid); TryEmitSound(uid, soundSpammer, predict: false); + + SpamEmitSoundReset((uid, soundSpammer)); } } } @@ -40,6 +40,8 @@ public override void Initialize() SubscribeLocalEvent(HandleEmitSoundOnTrigger); SubscribeLocalEvent(HandleEmitSoundOnUIOpen); + + SubscribeLocalEvent(HandleSpamEmitSoundMapInit); } private void HandleEmitSoundOnUIOpen(EntityUid uid, EmitSoundOnUIOpenComponent component, AfterActivatableUIOpenEvent args) @@ -52,4 +54,39 @@ private void HandleEmitSoundOnTrigger(EntityUid uid, EmitSoundOnTriggerComponent TryEmitSound(uid, component, args.User, false); args.Handled = true; } + + private void HandleSpamEmitSoundMapInit(Entity entity, ref MapInitEvent args) + { + SpamEmitSoundReset(entity); + + // Prewarm so multiple entities have more variation. + entity.Comp.NextSound -= Random.Next(entity.Comp.MaxInterval); + Dirty(entity); + } + + private void SpamEmitSoundReset(Entity entity) + { + if (_net.IsClient) + return; + + entity.Comp.NextSound = _timing.CurTime + ((entity.Comp.MinInterval < entity.Comp.MaxInterval) + ? Random.Next(entity.Comp.MinInterval, entity.Comp.MaxInterval) + : entity.Comp.MaxInterval); + + Dirty(entity); + } + + public override void SetEnabled(Entity entity, bool enabled) + { + if (!Resolve(entity, ref entity.Comp, false)) + return; + + if (entity.Comp.Enabled == enabled) + return; + + entity.Comp.Enabled = enabled; + + if (enabled) + SpamEmitSoundReset((entity, entity.Comp)); + } } diff --git a/Content.Server/Sound/SpamEmitSoundRequirePowerSystem.cs b/Content.Server/Sound/SpamEmitSoundRequirePowerSystem.cs new file mode 100644 index 00000000000..9cc85060c6e --- /dev/null +++ b/Content.Server/Sound/SpamEmitSoundRequirePowerSystem.cs @@ -0,0 +1,33 @@ +using Content.Server.Power.Components; +using Content.Server.Power.EntitySystems; +using Content.Shared.Sound; +using Content.Shared.Sound.Components; + +namespace Content.Server.Sound; + +public sealed partial class SpamEmitSoundRequirePowerSystem : SharedSpamEmitSoundRequirePowerSystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnPowerChanged); + SubscribeLocalEvent(OnPowerSupply); + } + + private void OnPowerChanged(Entity entity, ref PowerChangedEvent args) + { + if (TryComp(entity.Owner, out var comp)) + { + EmitSound.SetEnabled((entity, comp), args.Powered); + } + } + + private void OnPowerSupply(Entity entity, ref PowerNetBatterySupplyEvent args) + { + if (TryComp(entity.Owner, out var comp)) + { + EmitSound.SetEnabled((entity, comp), args.Supply); + } + } +} diff --git a/Content.Shared/Bed/Sleep/SleepEmitSoundComponent.cs b/Content.Shared/Bed/Sleep/SleepEmitSoundComponent.cs index 6313f633f21..9250a330777 100644 --- a/Content.Shared/Bed/Sleep/SleepEmitSoundComponent.cs +++ b/Content.Shared/Bed/Sleep/SleepEmitSoundComponent.cs @@ -12,16 +12,16 @@ public sealed partial class SleepEmitSoundComponent : Component public SoundSpecifier Snore = new SoundCollectionSpecifier("Snores", AudioParams.Default.WithVariation(0.2f)); /// - /// Interval between snore attempts in seconds + /// Minimum interval between snore attempts in seconds /// [DataField, ViewVariables(VVAccess.ReadWrite)] - public float Interval = 5f; + public TimeSpan Interval = TimeSpan.FromSeconds(5); /// - /// Chance for snore attempt to succeed + /// Maximum interval between snore attempts in seconds /// [DataField, ViewVariables(VVAccess.ReadWrite)] - public float Chance = 0.33f; + public TimeSpan MaxInterval = TimeSpan.FromSeconds(15); /// /// Popup for snore (e.g. Zzz...) diff --git a/Content.Shared/Sound/Components/SpamEmitSoundComponent.cs b/Content.Shared/Sound/Components/SpamEmitSoundComponent.cs new file mode 100644 index 00000000000..149728a5baa --- /dev/null +++ b/Content.Shared/Sound/Components/SpamEmitSoundComponent.cs @@ -0,0 +1,44 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Sound.Components; + +/// +/// Repeatedly plays a sound with a randomized delay. +/// +[RegisterComponent, NetworkedComponent] +[AutoGenerateComponentState, AutoGenerateComponentPause] +public sealed partial class SpamEmitSoundComponent : BaseEmitSoundComponent +{ + /// + /// The time at which the next sound will play. + /// + [DataField, AutoPausedField, AutoNetworkedField] + public TimeSpan NextSound; + + /// + /// The minimum time in seconds between playing the sound. + /// + [DataField] + public TimeSpan MinInterval = TimeSpan.FromSeconds(2); + + /// + /// The maximum time in seconds between playing the sound. + /// + [DataField] + public TimeSpan MaxInterval = TimeSpan.FromSeconds(2); + + // Always Pvs. + /// + /// Content of a popup message to display whenever the sound plays. + /// + [DataField] + public LocId? PopUp; + + /// + /// Whether the timer is currently running and sounds are being played. + /// Do not set this directly, use + /// + [DataField, AutoNetworkedField] + [Access(typeof(SharedEmitSoundSystem))] + public bool Enabled = true; +} diff --git a/Content.Shared/Sound/Components/SpamEmitSoundRequirePowerComponent.cs b/Content.Shared/Sound/Components/SpamEmitSoundRequirePowerComponent.cs new file mode 100644 index 00000000000..b0547ea398e --- /dev/null +++ b/Content.Shared/Sound/Components/SpamEmitSoundRequirePowerComponent.cs @@ -0,0 +1,10 @@ +namespace Content.Shared.Sound.Components; + +/// +/// Enables or disables an SpamEmitSound component depending +/// on the powered state of the entity. +/// +[RegisterComponent] +public sealed partial class SpamEmitSoundRequirePowerComponent : Component +{ +} diff --git a/Content.Shared/Sound/SharedEmitSoundSystem.cs b/Content.Shared/Sound/SharedEmitSoundSystem.cs index 56a51744acf..cd7828fc6b3 100644 --- a/Content.Shared/Sound/SharedEmitSoundSystem.cs +++ b/Content.Shared/Sound/SharedEmitSoundSystem.cs @@ -24,7 +24,7 @@ namespace Content.Shared.Sound; [UsedImplicitly] public abstract class SharedEmitSoundSystem : EntitySystem { - [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] protected readonly IGameTiming Timing = default!; [Dependency] private readonly INetManager _netMan = default!; [Dependency] private readonly ITileDefinitionManager _tileDefMan = default!; [Dependency] protected readonly IRobustRandom Random = default!; @@ -124,7 +124,7 @@ private void OnEmitSoundOnCollide(EntityUid uid, EmitSoundOnCollideComponent com !args.OtherFixture.Hard || !TryComp(uid, out var physics) || physics.LinearVelocity.Length() < component.MinimumVelocity || - _timing.CurTime < component.NextSound || + Timing.CurTime < component.NextSound || MetaData(uid).EntityPaused) { return; @@ -136,7 +136,7 @@ private void OnEmitSoundOnCollide(EntityUid uid, EmitSoundOnCollideComponent com var fraction = MathF.Min(1f, (physics.LinearVelocity.Length() - component.MinimumVelocity) / MaxVolumeVelocity); var volume = MinVolume + (MaxVolume - MinVolume) * fraction; - component.NextSound = _timing.CurTime + EmitSoundOnCollideComponent.CollideCooldown; + component.NextSound = Timing.CurTime + EmitSoundOnCollideComponent.CollideCooldown; var sound = component.Sound; if (_netMan.IsServer && sound != null) @@ -144,4 +144,8 @@ private void OnEmitSoundOnCollide(EntityUid uid, EmitSoundOnCollideComponent com _audioSystem.PlayPvs(_audioSystem.GetSound(sound), uid, AudioParams.Default.WithVolume(volume)); } } + + public virtual void SetEnabled(Entity entity, bool enabled) + { + } } diff --git a/Content.Shared/Sound/SharedSpamEmitSoundRequirePowerSystem.cs b/Content.Shared/Sound/SharedSpamEmitSoundRequirePowerSystem.cs new file mode 100644 index 00000000000..ad44cba8a59 --- /dev/null +++ b/Content.Shared/Sound/SharedSpamEmitSoundRequirePowerSystem.cs @@ -0,0 +1,6 @@ +namespace Content.Shared.Sound; + +public abstract partial class SharedSpamEmitSoundRequirePowerSystem : EntitySystem +{ + [Dependency] protected readonly SharedEmitSoundSystem EmitSound = default!; +} diff --git a/Resources/Audio/Machines/Arcade/attributions.yml b/Resources/Audio/Machines/Arcade/attributions.yml new file mode 100644 index 00000000000..afb120bee04 --- /dev/null +++ b/Resources/Audio/Machines/Arcade/attributions.yml @@ -0,0 +1,4 @@ +- files: ["hahaha.ogg", "pew_pew.ogg", "sting_*.ogg"] + license: "CC0-1.0" + copyright: "Recorded by https://github.com/Tayrtahn" + source: "https://github.com/space-wizards/space-station-14/pull/24200" diff --git a/Resources/Audio/Machines/Arcade/hahaha.ogg b/Resources/Audio/Machines/Arcade/hahaha.ogg new file mode 100644 index 0000000000000000000000000000000000000000..8a01e5dd999b97a4b0ac48accde27ad0875baeb4 GIT binary patch literal 57972 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π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 literal 0 HcmV?d00001 diff --git a/Resources/Audio/Machines/Arcade/sting_01.ogg b/Resources/Audio/Machines/Arcade/sting_01.ogg new file mode 100644 index 0000000000000000000000000000000000000000..e1b7e7fd779c9e016a7742b9cebd2dce993c9881 GIT binary patch literal 39700 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 Y2T*tsWdh!1{O_z^N&pi8000000AFCZK>z>% literal 0 HcmV?d00001 diff --git a/Resources/Audio/Machines/Arcade/sting_02.ogg b/Resources/Audio/Machines/Arcade/sting_02.ogg new file mode 100644 index 0000000000000000000000000000000000000000..83651e37a40b53a9262c2a561b609a4bb90b811e GIT binary patch literal 57619 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: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 literal 0 HcmV?d00001 diff --git a/Resources/Audio/Machines/Arcade/sting_03.ogg b/Resources/Audio/Machines/Arcade/sting_03.ogg new file mode 100644 index 0000000000000000000000000000000000000000..99bb7259cb30b1f64cf34eee8486e4639de82296 GIT binary patch literal 23663 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 literal 0 HcmV?d00001 diff --git a/Resources/Locale/en-US/advertisements/arcade/blockgame.ftl b/Resources/Locale/en-US/advertisements/arcade/blockgame.ftl new file mode 100644 index 00000000000..ec755abe8fe --- /dev/null +++ b/Resources/Locale/en-US/advertisements/arcade/blockgame.ftl @@ -0,0 +1,26 @@ +advertisement-block-game-1 = Legally distinct! +advertisement-block-game-2 = What the hell is a T-spin? +advertisement-block-game-3 = These blocks aren't going to clear themselves! +advertisement-block-game-4 = Beep boop! Bwoooop! +advertisement-block-game-5 = Let's play a game! +advertisement-block-game-6 = 6 whole colors of gameplay! +advertisement-block-game-7 = Hot 8-bit action! +advertisement-block-game-8 = Blocks, blocks, blocks! +advertisement-block-game-9 = Think YOU can claim the high score? +advertisement-block-game-10 = Nanotrasen Block Game IS what TetrISN'T! +advertisement-block-game-11 = Now with blast processing! +advertisement-block-game-12 = Our lawyers are standing by! +advertisement-block-game-13 = Hallelujah, it's raining blocks! + +thankyou-block-game-1 = Play again soon! +thankyou-block-game-2 = Well played! +thankyou-block-game-3 = Just one more game? +thankyou-block-game-4 = Stopping so soon? +thankyou-block-game-5 = The blocks will miss you. +thankyou-block-game-6 = Thanks for playin'! +thankyou-block-game-7 = Come back soon! +thankyou-block-game-8 = Beep bwooop! +thankyou-block-game-9 = There's always time for another game! +thankyou-block-game-10 = Don't give up now! +thankyou-block-game-11 = There are always more blocks! +thankyou-block-game-12 = The blocks await your return! diff --git a/Resources/Locale/en-US/advertisements/arcade/spacevillain.ftl b/Resources/Locale/en-US/advertisements/arcade/spacevillain.ftl new file mode 100644 index 00000000000..145c3808497 --- /dev/null +++ b/Resources/Locale/en-US/advertisements/arcade/spacevillain.ftl @@ -0,0 +1,28 @@ +advertisement-space-villain-1 = Are you a bad enough dude to beat this game? +advertisement-space-villain-2 = Beat the bad guy; win a prize! +advertisement-space-villain-3 = FIGHT ME! +advertisement-space-villain-4 = Space needs a hero! +advertisement-space-villain-5 = I'm holding out for a hero! +advertisement-space-villain-6 = Won't someone save us? +advertisement-space-villain-7 = Mua-hah-hah-hah! +advertisement-space-villain-8 = Spaaaaaaaace Villain! +advertisement-space-villain-9 = No one can defeat me! +advertisement-space-villain-10 = Tremble before me! +advertisement-space-villain-11 = CHALLENGE ME! +advertisement-space-villain-12 = FEAR ME! +advertisement-space-villain-13 = Do you dare to face me in battle!? +advertisement-space-villain-14 = Beware, I live! +advertisement-space-villain-15 = I hunger! + +thankyou-space-villain-1 = And where do you think you're going, punk? +thankyou-space-villain-2 = Is that all you've got? +thankyou-space-villain-3 = This fight isn't over! +thankyou-space-villain-4 = Challenge again soon! +thankyou-space-villain-5 = Who dares to challenge me next? +thankyou-space-villain-6 = I knew you couldn't defeat me! +thankyou-space-villain-7 = Too much for you to handle? +thankyou-space-villain-8 = Run, coward! +thankyou-space-villain-9 = You never stood a chance. +thankyou-space-villain-10 = Care for a rematch? +thankyou-space-villain-11 = Fight me again! +thankyou-space-villain-12 = Come back here and fight me! diff --git a/Resources/Prototypes/Catalog/Arcade/Advertisements/blockgame.yml b/Resources/Prototypes/Catalog/Arcade/Advertisements/blockgame.yml new file mode 100644 index 00000000000..efcb8934a80 --- /dev/null +++ b/Resources/Prototypes/Catalog/Arcade/Advertisements/blockgame.yml @@ -0,0 +1,29 @@ +- type: advertisementsPack + id: BlockGameAds + advertisements: + - advertisement-block-game-1 + - advertisement-block-game-2 + - advertisement-block-game-3 + - advertisement-block-game-4 + - advertisement-block-game-5 + - advertisement-block-game-6 + - advertisement-block-game-7 + - advertisement-block-game-8 + - advertisement-block-game-9 + - advertisement-block-game-10 + - advertisement-block-game-11 + - advertisement-block-game-12 + - advertisement-block-game-13 + thankyous: + - thankyou-block-game-1 + - thankyou-block-game-2 + - thankyou-block-game-3 + - thankyou-block-game-4 + - thankyou-block-game-5 + - thankyou-block-game-6 + - thankyou-block-game-7 + - thankyou-block-game-8 + - thankyou-block-game-9 + - thankyou-block-game-10 + - thankyou-block-game-11 + - thankyou-block-game-12 diff --git a/Resources/Prototypes/Catalog/Arcade/Advertisements/spacevillain.yml b/Resources/Prototypes/Catalog/Arcade/Advertisements/spacevillain.yml new file mode 100644 index 00000000000..98063a62ddd --- /dev/null +++ b/Resources/Prototypes/Catalog/Arcade/Advertisements/spacevillain.yml @@ -0,0 +1,31 @@ +- type: advertisementsPack + id: SpaceVillainAds + advertisements: + - advertisement-space-villain-1 + - advertisement-space-villain-2 + - advertisement-space-villain-3 + - advertisement-space-villain-4 + - advertisement-space-villain-5 + - advertisement-space-villain-6 + - advertisement-space-villain-7 + - advertisement-space-villain-8 + - advertisement-space-villain-9 + - advertisement-space-villain-10 + - advertisement-space-villain-11 + - advertisement-space-villain-12 + - advertisement-space-villain-13 + - advertisement-space-villain-14 + - advertisement-space-villain-15 + thankyous: + - thankyou-space-villain-1 + - thankyou-space-villain-2 + - thankyou-space-villain-3 + - thankyou-space-villain-4 + - thankyou-space-villain-5 + - thankyou-space-villain-6 + - thankyou-space-villain-7 + - thankyou-space-villain-8 + - thankyou-space-villain-9 + - thankyou-space-villain-10 + - thankyou-space-villain-11 + - thankyou-space-villain-12 diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/arcades.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/arcades.yml index b75893d1643..4dbee6892f7 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/arcades.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/arcades.yml @@ -10,29 +10,65 @@ priority: Low - type: ExtensionCableReceiver - type: PointLight - radius: 1.5 + radius: 1.8 energy: 1.6 color: "#3db83b" + - type: LitOnPowered - type: Sprite sprite: Structures/Machines/arcade.rsi layers: - map: ["computerLayerBody"] state: arcade - map: ["computerLayerScreen"] - state: invaders + state: screen_invaders + - map: ["enum.WiresVisualLayers.MaintenancePanel"] + state: panel + visible: false - type: Icon sprite: Structures/Machines/arcade.rsi state: arcade + - type: WiresPanel + - type: Wires + layoutId: Arcade + boardName: wires-board-name-arcade + - type: WiresVisuals + - type: TypingIndicator + proto: robot + - type: Speech + speechVerb: Robotic + speechSounds: Vending - type: Anchorable - type: Pullable - type: StaticPrice price: 300 + - type: SpamEmitSoundRequirePower + - type: SpamEmitSound + minInterval: 30 + maxInterval: 90 + sound: + collection: ArcadeNoise + params: + volume: -8 + maxDistance: 10 + variation: 0.05 - type: entity id: SpaceVillainArcade name: space villain arcade parent: ArcadeBase components: + - type: Sprite + sprite: Structures/Machines/arcade.rsi + layers: + - map: ["computerLayerBody"] + state: arcade + - map: ["computerLayerScreen"] + state: screen_spacevillain + - map: ["enum.WiresVisualLayers.MaintenancePanel"] + state: panel + visible: false + - type: PointLight + color: "#e3a136" - type: SpaceVillainArcade rewardAmount: 0 possibleRewards: @@ -110,6 +146,10 @@ type: WiresBoundUserInterface - type: Computer board: SpaceVillainArcadeComputerCircuitboard + - type: Advertise + pack: SpaceVillainAds + minWait: 60 # Arcades are noisy + maxWait: 240 - type: entity id: SpaceVillainArcadeFilled @@ -132,15 +172,14 @@ - map: ["computerLayerBody"] state: arcade - map: ["computerLayerScreen"] - state: blockgame + state: screen_blockgame + - map: ["enum.WiresVisualLayers.MaintenancePanel"] + state: panel + visible: false - type: BlockGameArcade - type: ActivatableUI key: enum.BlockGameUiKey.Key - type: ActivatableUIRequiresPower - - type: WiresPanel - - type: Wires - layoutId: Arcade - boardName: wires-board-name-arcade - type: UserInterface interfaces: - key: enum.BlockGameUiKey.Key @@ -149,3 +188,7 @@ type: WiresBoundUserInterface - type: Computer board: BlockGameArcadeComputerCircuitboard + - type: Advertise + pack: BlockGameAds + minWait: 60 # Arcades are noisy + maxWait: 240 diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/clown.yml b/Resources/Prototypes/Roles/Jobs/Civilian/clown.yml index 49e3cc91a7f..43e07d0637c 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/clown.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/clown.yml @@ -23,11 +23,10 @@ Piercing: 4 groups: Burn: 3 -# DeltaV - Commenting out the clown snore sound because I am not fond of it (it makes me itchy and feral). By "I", I mean Leonardo_DaBepis. +# DeltaV - Commenting out the clown snore sound because I am not fond of it (it makes me itchy and feral). By "I", I mean Leonardo_DaBepis. # - type: SleepEmitSound # snore: /Audio/Voice/Misc/silly_snore.ogg # interval: 10 -# chance: 1.0 - !type:AddImplantSpecial implants: [ SadTromboneImplant ] diff --git a/Resources/Prototypes/SoundCollections/arcade.yml b/Resources/Prototypes/SoundCollections/arcade.yml new file mode 100644 index 00000000000..40c8a0bc1ee --- /dev/null +++ b/Resources/Prototypes/SoundCollections/arcade.yml @@ -0,0 +1,11 @@ +- type: soundCollection + id: ArcadeNoise + files: + - /Audio/Machines/Arcade/hahaha.ogg + - /Audio/Machines/Arcade/pew_pew.ogg + - /Audio/Machines/Arcade/sting_01.ogg + - /Audio/Machines/Arcade/sting_02.ogg + - /Audio/Machines/Arcade/sting_03.ogg + - /Audio/Machines/Arcade/sting_04.ogg + - /Audio/Machines/Arcade/sting_05.ogg + - /Audio/Machines/Arcade/sting_06.ogg diff --git a/Resources/Prototypes/Wires/layouts.yml b/Resources/Prototypes/Wires/layouts.yml index b30e68545df..8d6be674e80 100644 --- a/Resources/Prototypes/Wires/layouts.yml +++ b/Resources/Prototypes/Wires/layouts.yml @@ -87,6 +87,7 @@ id: Arcade wires: - !type:PowerWireAction + - !type:SpeechWireAction - !type:ArcadeOverflowWireAction - !type:ArcadePlayerInvincibleWireAction - !type:ArcadeEnemyInvincibleWireAction diff --git a/Resources/Textures/Structures/Machines/arcade.rsi/meta.json b/Resources/Textures/Structures/Machines/arcade.rsi/meta.json index 63820ec06a8..38289e4b07c 100644 --- a/Resources/Textures/Structures/Machines/arcade.rsi/meta.json +++ b/Resources/Textures/Structures/Machines/arcade.rsi/meta.json @@ -19,7 +19,11 @@ "directions": 4 }, { - "name": "invaders", + "name": "panel", + "directions": 4 + }, + { + "name": "screen_invaders", "directions": 4, "delays": [ [ @@ -42,7 +46,7 @@ ] }, { - "name": "blockgame", + "name": "screen_blockgame", "directions": 4, "delays": [ [ @@ -82,6 +86,48 @@ 4.8 ] ] + }, + { + "name": "screen_spacevillain", + "directions": 4, + "delays": [ + [ + 1.0, + 0.8, + 0.2, + 0.8, + 0.5, + 1.0, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.5, + 1.0, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.2, + 0.5, + 0.1, + 0.8, + 1.0 + ], + [ + 9.6 + ], + [ + 9.6 + ], + [ + 9.6 + ] + ] } ] } diff --git a/Resources/Textures/Structures/Machines/arcade.rsi/panel.png b/Resources/Textures/Structures/Machines/arcade.rsi/panel.png new file mode 100644 index 0000000000000000000000000000000000000000..fa651565796c0cc436328000349b4641e6c70202 GIT binary patch literal 236 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=n>}3|Ln>~)y?K`RkO5EIL;fQS z`%+HDNH(e(MZ2FkD%}#S&GdA+AdBEV_f;`V+i&?5ZDIjx1A~VD8-8zXaprh>@%x<5 z%VzPt_epwvK6>U%m6LZ<(?aiL-Jf+t^SRcyC-1yB9?x8rWNvuusffzTFMF6iPhK+n z`@5s@>(;KVcbar6>T-47k?S_uMdy2AMl&!NbOPP$=GF2GIX#m~ a>fZ8bD+X?PUbd$eq}bEd&t;ucLK6V9{9s}L literal 0 HcmV?d00001 diff --git a/Resources/Textures/Structures/Machines/arcade.rsi/blockgame.png b/Resources/Textures/Structures/Machines/arcade.rsi/screen_blockgame.png similarity index 100% rename from Resources/Textures/Structures/Machines/arcade.rsi/blockgame.png rename to Resources/Textures/Structures/Machines/arcade.rsi/screen_blockgame.png diff --git a/Resources/Textures/Structures/Machines/arcade.rsi/invaders.png b/Resources/Textures/Structures/Machines/arcade.rsi/screen_invaders.png similarity index 100% rename from Resources/Textures/Structures/Machines/arcade.rsi/invaders.png rename to Resources/Textures/Structures/Machines/arcade.rsi/screen_invaders.png diff --git a/Resources/Textures/Structures/Machines/arcade.rsi/screen_spacevillain.png b/Resources/Textures/Structures/Machines/arcade.rsi/screen_spacevillain.png new file mode 100644 index 0000000000000000000000000000000000000000..4935ac81b538e77105f84812a80e5fe31e272ab1 GIT binary patch literal 930 zcmeAS@N?(olHy`uVBq!ia0vp^3m6!f8aUX1Ea^YRMhpzhnVv3=Ar-gY-igg?au8t) zFwHrdx8n7iwo^&B+S{fGcsi^ve*S#U68^2n^7R7Pg$rL#%zpR%_jzM^ zyYrt-lO}%peRluczZc%0-#>rxdiPUj*46SZ{j~VGeJx{M&0qF)hp*RHPe1c!*7X1* z`>ML=4S%+6dk{7C*1t1S*vh?7iWt-csFufEFEruxU3{KlR$_nz68^6#iy75VM& z+kf(3pM+Nb+dbdrEI-^@2xB_iZ$|g`&tL2A-&uFy;_shTr}Njz7o3#bI)DB4KWe`j zx3e$!BIWkpz30q)CBM3Bt_E55@n!bOOMp_w#@RFWw>_SJX@b@H&!v@0x42{WtKXV< zUT^Ar+y5OHzFe`h&f#b6)B90>*553($@Ieq@kwuIR?zG3m* z-(M~MujKx4BG~uJF9x;`#4yWWPUk@3j8L zwih+5QyIk*Prg~o{VcGqbJn&OHNbfJwk@G!7wb$Vr_EAU?&t2$`*10{<5c}hd4t{a s^Pc=st=`=>?=vH+@uNIKA@D$Qf9|)Ky?W<=Oa&?RboFyt=akR{06GvVg8%>k literal 0 HcmV?d00001 From f1e472608b0a85bff932936c96887c38fce36c41 Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Thu, 28 Mar 2024 06:31:47 +0000 Subject: [PATCH 015/295] biggest gridinv update OF ALL TIME (#25834) * add SaveItemLocation keybind * make item direction public to avoid having to change between Angle for no reason * add item location saving * show * Added a better save keybind, made it draw saved positions, and trying to save in a position it has already been saved in removes that position. * w * code style * Make taken spots appear blue * style * ! --------- Co-authored-by: deltanedas <@deltanedas:kde.org> Co-authored-by: notquitehadouken Co-authored-by: I.K <45953835+notquitehadouken@users.noreply.github.com> --- Content.Client/Input/ContentContexts.cs | 1 + .../Options/UI/Tabs/KeyRebindTab.xaml.cs | 1 + .../Storage/Controls/StorageContainer.cs | 35 ++++++ .../Systems/Storage/StorageUIController.cs | 10 ++ Content.Shared/Input/ContentKeyFunctions.cs | 1 + .../EntitySystems/SharedStorageSystem.cs | 111 +++++++++++++++++- Content.Shared/Storage/ItemStorageLocation.cs | 8 +- Content.Shared/Storage/StorageComponent.cs | 22 ++++ .../en-US/escape-menu/ui/options-menu.ftl | 1 + Resources/keybinds.yml | 3 + 10 files changed, 188 insertions(+), 5 deletions(-) diff --git a/Content.Client/Input/ContentContexts.cs b/Content.Client/Input/ContentContexts.cs index ca22ab095d6..fec50faf076 100644 --- a/Content.Client/Input/ContentContexts.cs +++ b/Content.Client/Input/ContentContexts.cs @@ -32,6 +32,7 @@ public static void SetupContexts(IInputContextContainer contexts) common.AddFunction(ContentKeyFunctions.ToggleFullscreen); common.AddFunction(ContentKeyFunctions.MoveStoredItem); common.AddFunction(ContentKeyFunctions.RotateStoredItem); + common.AddFunction(ContentKeyFunctions.SaveItemLocation); common.AddFunction(ContentKeyFunctions.Point); common.AddFunction(ContentKeyFunctions.ZoomOut); common.AddFunction(ContentKeyFunctions.ZoomIn); diff --git a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs index 9daca74dd3a..3f9a498c053 100644 --- a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs +++ b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs @@ -184,6 +184,7 @@ void AddCheckBox(string checkBoxName, bool currentState, Action(currentEnt, out var meta) || meta.EntityName != locations.Key) + continue; + + float spot = 0; + var marked = new List(); + + foreach (var location in locations.Value) + { + var shape = itemSystem.GetAdjustedItemShape(currentEnt, location); + var bound = shape.GetBoundingBox(); + + var spotFree = storageSystem.ItemFitsInGridLocation(currentEnt, StorageEntity.Value, location); + + if (spotFree) + spot++; + + for (var y = bound.Bottom; y <= bound.Top; y++) + { + for (var x = bound.Left; x <= bound.Right; x++) + { + if (TryGetBackgroundCell(x, y, out var cell) && shape.Contains(x, y) && !marked.Contains(cell)) + { + marked.Add(cell); + cell.ModulateSelfOverride = spotFree + ? Color.FromHsv((0.18f, 1 / spot, 0.5f / spot + 0.5f, 1f)) + : Color.FromHex("#2222CC"); + } + } + } + } + } + var validColor = usingInHand ? Color.Goldenrod : Color.FromHex("#1E8000"); for (var y = itemBounding.Bottom; y <= itemBounding.Top; y++) diff --git a/Content.Client/UserInterface/Systems/Storage/StorageUIController.cs b/Content.Client/UserInterface/Systems/Storage/StorageUIController.cs index 5f3ae3a2dac..b865b54dd0e 100644 --- a/Content.Client/UserInterface/Systems/Storage/StorageUIController.cs +++ b/Content.Client/UserInterface/Systems/Storage/StorageUIController.cs @@ -240,6 +240,16 @@ private void OnPiecePressed(GUIBoundKeyEventArgs args, ItemGridPiece control) args.Handle(); } + else if (args.Function == ContentKeyFunctions.SaveItemLocation) + { + if (_container?.StorageEntity is not {} storage) + return; + + _entity.RaisePredictiveEvent(new StorageSaveItemLocationEvent( + _entity.GetNetEntity(control.Entity), + _entity.GetNetEntity(storage))); + args.Handle(); + } else if (args.Function == ContentKeyFunctions.ExamineEntity) { _entity.System().DoExamine(control.Entity); diff --git a/Content.Shared/Input/ContentKeyFunctions.cs b/Content.Shared/Input/ContentKeyFunctions.cs index 19514a4ee0f..2244bd7b46c 100644 --- a/Content.Shared/Input/ContentKeyFunctions.cs +++ b/Content.Shared/Input/ContentKeyFunctions.cs @@ -37,6 +37,7 @@ public static class ContentKeyFunctions public static readonly BoundKeyFunction SwapHands = "SwapHands"; public static readonly BoundKeyFunction MoveStoredItem = "MoveStoredItem"; public static readonly BoundKeyFunction RotateStoredItem = "RotateStoredItem"; + public static readonly BoundKeyFunction SaveItemLocation = "SaveItemLocation"; public static readonly BoundKeyFunction ThrowItemInHand = "ThrowItemInHand"; public static readonly BoundKeyFunction TryPullObject = "TryPullObject"; public static readonly BoundKeyFunction MovePulledObject = "MovePulledObject"; diff --git a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs index 799fb7e33e9..966ec02b805 100644 --- a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs @@ -98,6 +98,7 @@ public override void Initialize() SubscribeAllEvent(OnSetItemLocation); SubscribeAllEvent(OnInsertItemIntoLocation); SubscribeAllEvent(OnRemoveItem); + SubscribeAllEvent(OnSaveItemLocation); SubscribeLocalEvent(OnReclaimed); @@ -118,7 +119,8 @@ private void OnStorageGetState(EntityUid uid, StorageComponent component, ref Co Grid = new List(component.Grid), IsUiOpen = component.IsUiOpen, MaxItemSize = component.MaxItemSize, - StoredItems = storedItems + StoredItems = storedItems, + SavedLocations = component.SavedLocations }; } @@ -139,6 +141,8 @@ private void OnStorageHandleState(EntityUid uid, StorageComponent component, ref var ent = EnsureEntity(nent, uid); component.StoredItems[ent] = location; } + + component.SavedLocations = state.SavedLocations; } public override void Shutdown() @@ -537,6 +541,35 @@ private void OnInsertItemIntoLocation(StorageInsertItemIntoLocationEvent msg, En InsertAt((storageEnt, storageComp), (itemEnt, null), msg.Location, out _, player, stackAutomatically: false); } + // TODO: if/when someone cleans up this shitcode please make all these + // handlers use a shared helper for checking that the ui is open etc, thanks + private void OnSaveItemLocation(StorageSaveItemLocationEvent msg, EntitySessionEventArgs args) + { + if (args.SenderSession.AttachedEntity is not {} player) + return; + + var storage = GetEntity(msg.Storage); + var item = GetEntity(msg.Item); + + if (!TryComp(storage, out var storageComp)) + return; + + if (!_ui.TryGetUi(storage, StorageComponent.StorageUiKey.Key, out var bui) || + !bui.SubscribedSessions.Contains(args.SenderSession)) + return; + + if (!Exists(item)) + { + Log.Error($"Player {args.SenderSession} saved location of non-existent item {msg.Item} stored in {ToPrettyString(storage)}"); + return; + } + + if (!ActionBlocker.CanInteract(player, item)) + return; + + SaveItemLocation(storage, item); + } + private void OnBoundUIOpen(EntityUid uid, StorageComponent storageComp, BoundUIOpenedEvent args) { if (!storageComp.IsUiOpen) @@ -949,6 +982,10 @@ public bool TryGetAvailableGridSpace( if (!Resolve(storageEnt, ref storageEnt.Comp) || !Resolve(itemEnt, ref itemEnt.Comp)) return false; + // if the item has an available saved location, use that + if (FindSavedLocation(storageEnt, itemEnt, out storageLocation)) + return true; + var storageBounding = storageEnt.Comp.Grid.GetBoundingBox(); Angle startAngle; @@ -991,6 +1028,76 @@ public bool TryGetAvailableGridSpace( return false; } + ///

+ /// Tries to find a saved location for an item from its name. + /// If none are saved or they are all blocked nothing is returned. + /// + public bool FindSavedLocation( + Entity ent, + Entity item, + [NotNullWhen(true)] out ItemStorageLocation? storageLocation) + { + storageLocation = null; + if (!Resolve(ent, ref ent.Comp)) + return false; + + var name = Name(item); + if (!ent.Comp.SavedLocations.TryGetValue(name, out var list)) + return false; + + foreach (var location in list) + { + if (ItemFitsInGridLocation(item, ent, location)) + { + storageLocation = location; + return true; + } + } + + return false; + } + + /// + /// Saves an item's location in the grid for later insertion to use. + /// + public void SaveItemLocation(Entity ent, Entity item) + { + if (!Resolve(ent, ref ent.Comp)) + return; + + // needs to actually be stored in it somewhere to save it + if (!ent.Comp.StoredItems.TryGetValue(item, out var location)) + return; + + var name = Name(item, item.Comp); + if (ent.Comp.SavedLocations.TryGetValue(name, out var list)) + { + // iterate to make sure its not already been saved + for (int i = 0; i < list.Count; i++) + { + var saved = list[i]; + + if (saved == location) + { + list.Remove(location); + return; + } + } + + list.Add(location); + } + else + { + list = new List() + { + location + }; + ent.Comp.SavedLocations[name] = list; + } + + Dirty(ent, ent.Comp); + } + /// /// Checks if an item fits into a specific spot on a storage grid. /// @@ -1169,6 +1276,8 @@ protected sealed class StorageComponentState : ComponentState public Dictionary StoredItems = new(); + public Dictionary> SavedLocations = new(); + public List Grid = new(); public ProtoId? MaxItemSize; diff --git a/Content.Shared/Storage/ItemStorageLocation.cs b/Content.Shared/Storage/ItemStorageLocation.cs index a43be5a44fa..25d55e1e1ae 100644 --- a/Content.Shared/Storage/ItemStorageLocation.cs +++ b/Content.Shared/Storage/ItemStorageLocation.cs @@ -8,16 +8,16 @@ public partial record struct ItemStorageLocation /// /// The rotation, stored a cardinal direction in order to reduce rounding errors. /// - [DataField] - private Direction _rotation; + [DataField("_rotation")] + public Direction Direction; /// /// The rotation of the piece in storage. /// public Angle Rotation { - get => _rotation.ToAngle(); - set => _rotation = value.GetCardinalDir(); + get => Direction.ToAngle(); + set => Direction = value.GetCardinalDir(); } /// diff --git a/Content.Shared/Storage/StorageComponent.cs b/Content.Shared/Storage/StorageComponent.cs index 98e80de0253..2cae12f07a8 100644 --- a/Content.Shared/Storage/StorageComponent.cs +++ b/Content.Shared/Storage/StorageComponent.cs @@ -32,6 +32,14 @@ public sealed partial class StorageComponent : Component [DataField, ViewVariables(VVAccess.ReadWrite)] public Dictionary StoredItems = new(); + /// + /// A dictionary storing each saved item to its location in the grid. + /// When trying to quick insert an item, if there is an empty location with the same name it will be placed there. + /// Multiple items with the same name can be saved, they will be checked individually. + /// + [DataField] + public Dictionary> SavedLocations = new(); + /// /// A list of boxes that comprise a combined grid that determines the location that items can be stored. /// @@ -171,6 +179,20 @@ public StorageInsertItemIntoLocationEvent(NetEntity itemEnt, NetEntity storageEn } } + [Serializable, NetSerializable] + public sealed class StorageSaveItemLocationEvent : EntityEventArgs + { + public readonly NetEntity Item; + + public readonly NetEntity Storage; + + public StorageSaveItemLocationEvent(NetEntity item, NetEntity storage) + { + Item = item; + Storage = storage; + } + } + /// /// Network event for displaying an animation of entities flying into a storage entity diff --git a/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl b/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl index f76d1c04784..d42f51f101a 100644 --- a/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl +++ b/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl @@ -137,6 +137,7 @@ ui-options-function-swap-hands = Swap hands ui-options-function-move-stored-item = Move stored item ui-options-function-rotate-stored-item = Rotate stored item ui-options-function-offer-item = Offer something +ui-options-function-save-item-location = Save item location ui-options-static-storage-ui = Lock storage window to hotbar ui-options-function-smart-equip-backpack = Smart-equip to backpack diff --git a/Resources/keybinds.yml b/Resources/keybinds.yml index 14d5d68091a..a66352dc48b 100644 --- a/Resources/keybinds.yml +++ b/Resources/keybinds.yml @@ -171,6 +171,9 @@ binds: - function: RotateStoredItem type: State key: MouseRight +- function: SaveItemLocation + type: State + key: MouseMiddle - function: Drop type: State key: Q From 06e403deedc74794e47e853dd5b291e014372c72 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Thu, 28 Mar 2024 02:32:56 -0400 Subject: [PATCH 016/295] dynamic alert sprites (#25452) * dynamic alert sprite * fix fat cooldowns --- .../Alerts/UpdateAlertSpriteEvent.cs | 21 +++++++ Content.Client/Revenant/RevenantSystem.cs | 15 +++++ .../Systems/Alerts/AlertsUIController.cs | 16 +++++ .../Systems/Alerts/Controls/AlertControl.cs | 58 ++++++++++++++---- .../Revenant/EntitySystems/RevenantSystem.cs | 3 +- Content.Shared/Alert/AlertPrototype.cs | 14 +++-- .../Revenant/Components/RevenantComponent.cs | 4 +- Content.Shared/Revenant/SharedRevenant.cs | 8 +++ Resources/Prototypes/Alerts/alerts.yml | 8 +++ Resources/Prototypes/Alerts/revenant.yml | 49 +++++---------- .../Alerts/essence_counter.rsi/0.png | Bin 0 -> 155 bytes .../Alerts/essence_counter.rsi/1.png | Bin 0 -> 150 bytes .../Alerts/essence_counter.rsi/2.png | Bin 0 -> 164 bytes .../Alerts/essence_counter.rsi/3.png | Bin 0 -> 156 bytes .../Alerts/essence_counter.rsi/4.png | Bin 0 -> 156 bytes .../Alerts/essence_counter.rsi/5.png | Bin 0 -> 168 bytes .../Alerts/essence_counter.rsi/6.png | Bin 0 -> 167 bytes .../Alerts/essence_counter.rsi/7.png | Bin 0 -> 154 bytes .../Alerts/essence_counter.rsi/8.png | Bin 0 -> 156 bytes .../Alerts/essence_counter.rsi/9.png | Bin 0 -> 164 bytes .../Alerts/essence_counter.rsi/essence0.png | Bin 319 -> 255 bytes .../Alerts/essence_counter.rsi/essence1.png | Bin 298 -> 0 bytes .../Alerts/essence_counter.rsi/essence10.png | Bin 308 -> 0 bytes .../Alerts/essence_counter.rsi/essence11.png | Bin 304 -> 0 bytes .../Alerts/essence_counter.rsi/essence12.png | Bin 311 -> 0 bytes .../Alerts/essence_counter.rsi/essence13.png | Bin 311 -> 0 bytes .../Alerts/essence_counter.rsi/essence14.png | Bin 321 -> 0 bytes .../Alerts/essence_counter.rsi/essence15.png | Bin 307 -> 0 bytes .../Alerts/essence_counter.rsi/essence16.png | Bin 313 -> 0 bytes .../Alerts/essence_counter.rsi/essence2.png | Bin 297 -> 0 bytes .../Alerts/essence_counter.rsi/essence3.png | Bin 299 -> 0 bytes .../Alerts/essence_counter.rsi/essence4.png | Bin 321 -> 0 bytes .../Alerts/essence_counter.rsi/essence5.png | Bin 302 -> 0 bytes .../Alerts/essence_counter.rsi/essence6.png | Bin 297 -> 0 bytes .../Alerts/essence_counter.rsi/essence7.png | Bin 317 -> 0 bytes .../Alerts/essence_counter.rsi/essence8.png | Bin 295 -> 0 bytes .../Alerts/essence_counter.rsi/essence9.png | Bin 292 -> 0 bytes .../Alerts/essence_counter.rsi/meta.json | 38 +++--------- 38 files changed, 152 insertions(+), 82 deletions(-) create mode 100644 Content.Client/Alerts/UpdateAlertSpriteEvent.cs create mode 100644 Resources/Textures/Interface/Alerts/essence_counter.rsi/0.png create mode 100644 Resources/Textures/Interface/Alerts/essence_counter.rsi/1.png create mode 100644 Resources/Textures/Interface/Alerts/essence_counter.rsi/2.png create mode 100644 Resources/Textures/Interface/Alerts/essence_counter.rsi/3.png create mode 100644 Resources/Textures/Interface/Alerts/essence_counter.rsi/4.png create mode 100644 Resources/Textures/Interface/Alerts/essence_counter.rsi/5.png create mode 100644 Resources/Textures/Interface/Alerts/essence_counter.rsi/6.png create mode 100644 Resources/Textures/Interface/Alerts/essence_counter.rsi/7.png create mode 100644 Resources/Textures/Interface/Alerts/essence_counter.rsi/8.png create mode 100644 Resources/Textures/Interface/Alerts/essence_counter.rsi/9.png delete mode 100644 Resources/Textures/Interface/Alerts/essence_counter.rsi/essence1.png delete mode 100644 Resources/Textures/Interface/Alerts/essence_counter.rsi/essence10.png delete mode 100644 Resources/Textures/Interface/Alerts/essence_counter.rsi/essence11.png delete mode 100644 Resources/Textures/Interface/Alerts/essence_counter.rsi/essence12.png delete mode 100644 Resources/Textures/Interface/Alerts/essence_counter.rsi/essence13.png delete mode 100644 Resources/Textures/Interface/Alerts/essence_counter.rsi/essence14.png delete mode 100644 Resources/Textures/Interface/Alerts/essence_counter.rsi/essence15.png delete mode 100644 Resources/Textures/Interface/Alerts/essence_counter.rsi/essence16.png delete mode 100644 Resources/Textures/Interface/Alerts/essence_counter.rsi/essence2.png delete mode 100644 Resources/Textures/Interface/Alerts/essence_counter.rsi/essence3.png delete mode 100644 Resources/Textures/Interface/Alerts/essence_counter.rsi/essence4.png delete mode 100644 Resources/Textures/Interface/Alerts/essence_counter.rsi/essence5.png delete mode 100644 Resources/Textures/Interface/Alerts/essence_counter.rsi/essence6.png delete mode 100644 Resources/Textures/Interface/Alerts/essence_counter.rsi/essence7.png delete mode 100644 Resources/Textures/Interface/Alerts/essence_counter.rsi/essence8.png delete mode 100644 Resources/Textures/Interface/Alerts/essence_counter.rsi/essence9.png diff --git a/Content.Client/Alerts/UpdateAlertSpriteEvent.cs b/Content.Client/Alerts/UpdateAlertSpriteEvent.cs new file mode 100644 index 00000000000..4f182c458cc --- /dev/null +++ b/Content.Client/Alerts/UpdateAlertSpriteEvent.cs @@ -0,0 +1,21 @@ +using Content.Shared.Alert; +using Robust.Client.GameObjects; + +namespace Content.Client.Alerts; + +/// +/// Event raised on an entity with alerts in order to allow it to update visuals for the alert sprite entity. +/// +[ByRefEvent] +public record struct UpdateAlertSpriteEvent +{ + public Entity SpriteViewEnt; + + public AlertPrototype Alert; + + public UpdateAlertSpriteEvent(Entity spriteViewEnt, AlertPrototype alert) + { + SpriteViewEnt = spriteViewEnt; + Alert = alert; + } +} diff --git a/Content.Client/Revenant/RevenantSystem.cs b/Content.Client/Revenant/RevenantSystem.cs index 6e7d0d2a1bd..49d29d8a5f4 100644 --- a/Content.Client/Revenant/RevenantSystem.cs +++ b/Content.Client/Revenant/RevenantSystem.cs @@ -1,3 +1,5 @@ +using Content.Client.Alerts; +using Content.Shared.Alert; using Content.Shared.Revenant; using Content.Shared.Revenant.Components; using Robust.Client.GameObjects; @@ -13,6 +15,7 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent(OnAppearanceChange); + SubscribeLocalEvent(OnUpdateAlert); } private void OnAppearanceChange(EntityUid uid, RevenantComponent component, ref AppearanceChangeEvent args) @@ -36,4 +39,16 @@ private void OnAppearanceChange(EntityUid uid, RevenantComponent component, ref args.Sprite.LayerSetState(0, component.State); } } + + private void OnUpdateAlert(Entity ent, ref UpdateAlertSpriteEvent args) + { + if (args.Alert.AlertType != AlertType.Essence) + return; + + var sprite = args.SpriteViewEnt.Comp; + var essence = Math.Clamp(ent.Comp.Essence.Int(), 0, 999); + sprite.LayerSetState(RevenantVisualLayers.Digit1, $"{(essence / 100) % 10}"); + sprite.LayerSetState(RevenantVisualLayers.Digit2, $"{(essence / 10) % 10}"); + sprite.LayerSetState(RevenantVisualLayers.Digit3, $"{essence % 10}"); + } } diff --git a/Content.Client/UserInterface/Systems/Alerts/AlertsUIController.cs b/Content.Client/UserInterface/Systems/Alerts/AlertsUIController.cs index 826bbf199ba..3b85972a9b2 100644 --- a/Content.Client/UserInterface/Systems/Alerts/AlertsUIController.cs +++ b/Content.Client/UserInterface/Systems/Alerts/AlertsUIController.cs @@ -3,6 +3,8 @@ using Content.Client.UserInterface.Systems.Alerts.Widgets; using Content.Client.UserInterface.Systems.Gameplay; using Content.Shared.Alert; +using Robust.Client.GameObjects; +using Robust.Client.Player; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controllers; @@ -10,6 +12,8 @@ namespace Content.Client.UserInterface.Systems.Alerts; public sealed class AlertsUIController : UIController, IOnStateEntered, IOnSystemChanged { + [Dependency] private readonly IPlayerManager _player = default!; + [UISystemDependency] private readonly ClientAlertsSystem? _alertsSystem = default; private AlertsUI? UI => UIManager.GetActiveUIWidgetOrNull(); @@ -84,4 +88,16 @@ public void SyncAlerts() SystemOnSyncAlerts(_alertsSystem, alerts); } } + + public void UpdateAlertSpriteEntity(EntityUid spriteViewEnt, AlertPrototype alert) + { + if (_player.LocalEntity is not { } player) + return; + + if (!EntityManager.TryGetComponent(spriteViewEnt, out var sprite)) + return; + + var ev = new UpdateAlertSpriteEvent((spriteViewEnt, sprite), alert); + EntityManager.EventBus.RaiseLocalEvent(player, ref ev); + } } diff --git a/Content.Client/UserInterface/Systems/Alerts/Controls/AlertControl.cs b/Content.Client/UserInterface/Systems/Alerts/Controls/AlertControl.cs index 4445bb0cd08..9423f7288df 100644 --- a/Content.Client/UserInterface/Systems/Alerts/Controls/AlertControl.cs +++ b/Content.Client/UserInterface/Systems/Alerts/Controls/AlertControl.cs @@ -2,6 +2,8 @@ using Content.Client.Actions.UI; using Content.Client.Cooldown; using Content.Shared.Alert; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Shared.Timing; @@ -33,9 +35,12 @@ public sealed class AlertControl : BaseButton private short? _severity; private readonly IGameTiming _gameTiming; - private readonly AnimatedTextureRect _icon; + private readonly IEntityManager _entityManager; + private readonly SpriteView _icon; private readonly CooldownGraphic _cooldownGraphic; + private EntityUid _spriteViewEntity; + /// /// Creates an alert control reflecting the indicated alert + state /// @@ -44,19 +49,30 @@ public sealed class AlertControl : BaseButton public AlertControl(AlertPrototype alert, short? severity) { _gameTiming = IoCManager.Resolve(); + _entityManager = IoCManager.Resolve(); TooltipSupplier = SupplyTooltip; Alert = alert; _severity = severity; - var specifier = alert.GetIcon(_severity); - _icon = new AnimatedTextureRect + + _spriteViewEntity = _entityManager.Spawn(Alert.AlertViewEntity); + if (_entityManager.TryGetComponent(_spriteViewEntity, out var sprite)) { - DisplayRect = {TextureScale = new Vector2(2, 2)} - }; + var icon = Alert.GetIcon(_severity); + if (sprite.LayerMapTryGet(AlertVisualLayers.Base, out var layer)) + sprite.LayerSetSprite(layer, icon); + } - _icon.SetFromSpriteSpecifier(specifier); + _icon = new SpriteView + { + Scale = new Vector2(2, 2) + }; + _icon.SetEntity(_spriteViewEntity); Children.Add(_icon); - _cooldownGraphic = new CooldownGraphic(); + _cooldownGraphic = new CooldownGraphic + { + MaxSize = new Vector2(64, 64) + }; Children.Add(_cooldownGraphic); } @@ -72,16 +88,22 @@ private Control SupplyTooltip(Control? sender) /// public void SetSeverity(short? severity) { - if (_severity != severity) - { - _severity = severity; - _icon.SetFromSpriteSpecifier(Alert.GetIcon(_severity)); - } + if (_severity == severity) + return; + _severity = severity; + + if (!_entityManager.TryGetComponent(_spriteViewEntity, out var sprite)) + return; + var icon = Alert.GetIcon(_severity); + if (sprite.LayerMapTryGet(AlertVisualLayers.Base, out var layer)) + sprite.LayerSetSprite(layer, icon); } protected override void FrameUpdate(FrameEventArgs args) { base.FrameUpdate(args); + UserInterfaceManager.GetUIController().UpdateAlertSpriteEntity(_spriteViewEntity, Alert); + if (!Cooldown.HasValue) { _cooldownGraphic.Visible = false; @@ -91,5 +113,17 @@ protected override void FrameUpdate(FrameEventArgs args) _cooldownGraphic.FromTime(Cooldown.Value.Start, Cooldown.Value.End); } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + _entityManager.DeleteEntity(_spriteViewEntity); + } + } + + public enum AlertVisualLayers : byte + { + Base } } diff --git a/Content.Server/Revenant/EntitySystems/RevenantSystem.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.cs index b26cc4a320f..86be70c41fe 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantSystem.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantSystem.cs @@ -133,6 +133,7 @@ public bool ChangeEssenceAmount(EntityUid uid, FixedPoint2 amount, RevenantCompo return false; component.Essence += amount; + Dirty(uid, component); if (regenCap) FixedPoint2.Min(component.Essence, component.EssenceRegenCap); @@ -140,7 +141,7 @@ public bool ChangeEssenceAmount(EntityUid uid, FixedPoint2 amount, RevenantCompo if (TryComp(uid, out var store)) _store.UpdateUserInterface(uid, uid, store); - _alerts.ShowAlert(uid, AlertType.Essence, (short) Math.Clamp(Math.Round(component.Essence.Float() / 10f), 0, 16)); + _alerts.ShowAlert(uid, AlertType.Essence); if (component.Essence <= 0) { diff --git a/Content.Shared/Alert/AlertPrototype.cs b/Content.Shared/Alert/AlertPrototype.cs index ac43cb45f3b..248cc00ba40 100644 --- a/Content.Shared/Alert/AlertPrototype.cs +++ b/Content.Shared/Alert/AlertPrototype.cs @@ -25,6 +25,12 @@ public sealed partial class AlertPrototype : IPrototype [DataField("icons", required: true)] public List Icons = new(); + /// + /// An entity used for displaying the in the UI control. + /// + [DataField] + public EntProtoId AlertViewEntity = "AlertSpriteView"; + /// /// Name to show in tooltip window. Accepts formatting. /// @@ -83,13 +89,9 @@ public sealed partial class AlertPrototype : IPrototype /// the icon path to the texture for the provided severity level public SpriteSpecifier GetIcon(short? severity = null) { - if (!SupportsSeverity && severity != null) - { - throw new InvalidOperationException($"This alert ({AlertKey}) does not support severity"); - } - var minIcons = SupportsSeverity - ? MaxSeverity - MinSeverity : 1; + ? MaxSeverity - MinSeverity + : 1; if (Icons.Count < minIcons) throw new InvalidOperationException($"Insufficient number of icons given for alert {AlertType}"); diff --git a/Content.Shared/Revenant/Components/RevenantComponent.cs b/Content.Shared/Revenant/Components/RevenantComponent.cs index b3d47e22f97..947c1a4b3fc 100644 --- a/Content.Shared/Revenant/Components/RevenantComponent.cs +++ b/Content.Shared/Revenant/Components/RevenantComponent.cs @@ -9,13 +9,15 @@ namespace Content.Shared.Revenant.Components; [RegisterComponent, NetworkedComponent] +[AutoGenerateComponentState] public sealed partial class RevenantComponent : Component { /// /// The total amount of Essence the revenant has. Functions /// as health and is regenerated. /// - [ViewVariables(VVAccess.ReadWrite)] + [DataField, ViewVariables(VVAccess.ReadWrite)] + [AutoNetworkedField] public FixedPoint2 Essence = 75; [DataField("stolenEssenceCurrencyPrototype", customTypeSerializer: typeof(PrototypeIdSerializer))] diff --git a/Content.Shared/Revenant/SharedRevenant.cs b/Content.Shared/Revenant/SharedRevenant.cs index c44e4408aaf..485ad26dd2c 100644 --- a/Content.Shared/Revenant/SharedRevenant.cs +++ b/Content.Shared/Revenant/SharedRevenant.cs @@ -70,3 +70,11 @@ public enum RevenantVisuals : byte Stunned, Harvesting, } + +[NetSerializable, Serializable] +public enum RevenantVisualLayers : byte +{ + Digit1, + Digit2, + Digit3 +} diff --git a/Resources/Prototypes/Alerts/alerts.yml b/Resources/Prototypes/Alerts/alerts.yml index 24f34bc5182..80537f59482 100644 --- a/Resources/Prototypes/Alerts/alerts.yml +++ b/Resources/Prototypes/Alerts/alerts.yml @@ -26,6 +26,14 @@ - alertType: Pacified - alertType: Offer +- type: entity + id: AlertSpriteView + categories: [ hideSpawnMenu ] + components: + - type: Sprite + layers: + - map: [ "enum.AlertVisualLayers.Base" ] + - type: alert id: LowOxygen category: Breathing diff --git a/Resources/Prototypes/Alerts/revenant.yml b/Resources/Prototypes/Alerts/revenant.yml index 2fb810ca1e6..a56b8983510 100644 --- a/Resources/Prototypes/Alerts/revenant.yml +++ b/Resources/Prototypes/Alerts/revenant.yml @@ -4,45 +4,26 @@ icons: - sprite: /Textures/Interface/Alerts/essence_counter.rsi state: essence0 - - sprite: /Textures/Interface/Alerts/essence_counter.rsi - state: essence1 - - sprite: /Textures/Interface/Alerts/essence_counter.rsi - state: essence2 - - sprite: /Textures/Interface/Alerts/essence_counter.rsi - state: essence3 - - sprite: /Textures/Interface/Alerts/essence_counter.rsi - state: essence4 - - sprite: /Textures/Interface/Alerts/essence_counter.rsi - state: essence5 - - sprite: /Textures/Interface/Alerts/essence_counter.rsi - state: essence6 - - sprite: /Textures/Interface/Alerts/essence_counter.rsi - state: essence7 - - sprite: /Textures/Interface/Alerts/essence_counter.rsi - state: essence8 - - sprite: /Textures/Interface/Alerts/essence_counter.rsi - state: essence9 - - sprite: /Textures/Interface/Alerts/essence_counter.rsi - state: essence10 - - sprite: /Textures/Interface/Alerts/essence_counter.rsi - state: essence11 - - sprite: /Textures/Interface/Alerts/essence_counter.rsi - state: essence12 - - sprite: /Textures/Interface/Alerts/essence_counter.rsi - state: essence13 - - sprite: /Textures/Interface/Alerts/essence_counter.rsi - state: essence14 - - sprite: /Textures/Interface/Alerts/essence_counter.rsi - state: essence15 - - sprite: /Textures/Interface/Alerts/essence_counter.rsi - state: essence16 + alertViewEntity: AlertEssenceSpriteView name: alerts-revenant-essence-name description: alerts-revenant-essence-desc - minSeverity: 0 - maxSeverity: 16 - type: alert id: Corporeal icons: [ /Textures/DeltaV/Mobs/Ghosts/revenant.rsi/icon.png ] # DeltaV - Custom revenant sprite name: alerts-revenant-corporeal-name description: alerts-revenant-corporeal-desc + +- type: entity + id: AlertEssenceSpriteView + categories: [ hideSpawnMenu ] + components: + - type: Sprite + sprite: /Textures/Interface/Alerts/essence_counter.rsi + layers: + - map: [ "enum.AlertVisualLayers.Base" ] + - map: [ "enum.RevenantVisualLayers.Digit1" ] + - map: [ "enum.RevenantVisualLayers.Digit2" ] + offset: 0.125, 0 + - map: [ "enum.RevenantVisualLayers.Digit3" ] + offset: 0.25, 0 diff --git a/Resources/Textures/Interface/Alerts/essence_counter.rsi/0.png b/Resources/Textures/Interface/Alerts/essence_counter.rsi/0.png new file mode 100644 index 0000000000000000000000000000000000000000..760a92bfe12905d0bc4509ff9cd19b3021163512 GIT binary patch literal 155 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}p`I>|ArY-_ zuQ+lw81T4Ul>hiIyzlmLCXp2v4StLMzs4}>51+I~N8@^+HU>{uKbLh*2~7a{$TN8W literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Alerts/essence_counter.rsi/2.png b/Resources/Textures/Interface/Alerts/essence_counter.rsi/2.png new file mode 100644 index 0000000000000000000000000000000000000000..9807aa8161b8806f2ff4b123be9313fa743c1287 GIT binary patch literal 164 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}ah@)YArY-_ zFFA5G81T4Uw14z>z0JCopw$XDZXDec>#g`wML-Y;?1QT}ygkkA`S@_ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Alerts/essence_counter.rsi/3.png b/Resources/Textures/Interface/Alerts/essence_counter.rsi/3.png new file mode 100644 index 0000000000000000000000000000000000000000..a1776e8efdf319b56555ddfbdcca124bf169cc70 GIT binary patch literal 156 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}VV*9IArY-_ zFFA5G81T4Ul>hiITyFc(RSFrqe&$~f^04Ccn52@qQHtNw$acl5&v&PP>uySRuD`~X z@O$r}+ia7z{Q0x3F|XhHLSiM;>GA`DKAa`T^c$F8MTm*ctKISfXaj?%tDnm{r-UW| DDrPyt literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Alerts/essence_counter.rsi/4.png b/Resources/Textures/Interface/Alerts/essence_counter.rsi/4.png new file mode 100644 index 0000000000000000000000000000000000000000..a1b8c7013ef70babf386586ca963c9ce79680765 GIT binary patch literal 156 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}VV*9IArY-_ zFB|e5P~c&4wEqA9^Tj0|8(y9WOj+)zpc1yJ1E}gi;?FxCK|3eR{Cv|d{PTnCzfqh8 z$_sc4&elsl2w_rj{VZ+ESh;zx9s8UOJOzCZ@3R$4@ygkExHbXxGI+ZBxvXqop`gR3yJOLZJCn5@F0-j@toU|M`bCJ;hPzhG>+f$k{7qehv*egOgSec5uyk*QU>(pl N22WQ%mvv4FO#rzxIBfs` literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Alerts/essence_counter.rsi/6.png b/Resources/Textures/Interface/Alerts/essence_counter.rsi/6.png new file mode 100644 index 0000000000000000000000000000000000000000..1222a73b5e96afd7bf2b51ac48acba13c13e1cfc GIT binary patch literal 167 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}iJmTwArY-_ zFB|e5P~dUC_teaQ$C3KLh}pwii?OC8qynD`0fZ7m&H@WxWn) O8H1;*pUXO@geCyOJ2}w+ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Alerts/essence_counter.rsi/7.png b/Resources/Textures/Interface/Alerts/essence_counter.rsi/7.png new file mode 100644 index 0000000000000000000000000000000000000000..e6bef33ed5b8ff016f63df81a74ea10523f62063 GIT binary patch literal 154 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}A)YRdArY-_ zFB|eTD2T9JjD7rf{T?$hmW>7z{SWTpbKc~S0|2$y>@2s7= zo>t5BYj{0^K7C4T$WMJ;dw`2Y;Dc1aPwjVanatY+#E+d<3jvzX;OXk;vd$@?2>>mA BHVFU# literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Alerts/essence_counter.rsi/8.png b/Resources/Textures/Interface/Alerts/essence_counter.rsi/8.png new file mode 100644 index 0000000000000000000000000000000000000000..c72dbb5433dce2a4ea26a135c9715c4ac5434e74 GIT binary patch literal 156 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}VV*9IArY-_ zuQ+lw81T4Ul>hiIyzlm=vE-$6CO!X@P+FefdOApbZS3u6{1-oD!M< DF~K+W literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Alerts/essence_counter.rsi/9.png b/Resources/Textures/Interface/Alerts/essence_counter.rsi/9.png new file mode 100644 index 0000000000000000000000000000000000000000..970eeb9740e5abc5e92be635af162c3da366fb7c GIT binary patch literal 164 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}ah@)YArY-_ zFFSG_P~dU7IRDb`_{v?P%3@x^=AX;DCK$RI7#kaJKAaTe5o9GG`1#)acdiEw15$Gt z)_vlNXDYcbP0l+XkK{+u{n literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Alerts/essence_counter.rsi/essence0.png b/Resources/Textures/Interface/Alerts/essence_counter.rsi/essence0.png index 361fa844e1614ec52db3e4a1f0062ba726358cab..3523928f3cd11fe2cdc777c699097797e696ebc1 100644 GIT binary patch delta 87 zcmdnb^q+BpA>+=8Mor#Irx>@a5oF&ZBT>M9VU2NA-Q*z3_#%N>gTe~DWM4fN$nkK delta 152 zcmV;J0B8UI0lxx}F#-9JGJ=0XG35XM|L~N}$jHcu%*SZ}BO^r)!AOK_`1mmRFpMmZ zu8wLBpf?~Y4TOC9gww~wG}|a@k`V&-T1E_Nyu3K&DQ=R{8j$4xViO=mO)_G#ASo7+ zPx#s9@z)Y1w|6Uxo zijeY+v1&gz?yG@9X53uxyuZp4MQ*RlWSuR0_;jpI#qW=1HojpAK5c6fz@P#w#E7R? zkdjn`S^xkv?`J*%mG@{cKiL{qoq-}ChNK|`xJ$;)NRj~Nj?Dc+7QX_mPZT4{ZbUyx zL`41ncg~rF4BkMPuhY|=iO6W&0{se%4N^vDAndf?b^k18Z8NQ8jCyxK<^5#byppZW w|E*-Hg;WCAjPFJ%I<`PFPpktd1d_0y#N3J07*qoM6N<$f(8nIg8%>k diff --git a/Resources/Textures/Interface/Alerts/essence_counter.rsi/essence10.png b/Resources/Textures/Interface/Alerts/essence_counter.rsi/essence10.png deleted file mode 100644 index 62557685d960278e7a3f3f7d193810b3b8227678..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 308 zcmV-40n7f0P)Px#?ny*JR9J=WmOT!_FbsvC5o@l|fejYMo`#8$b1*V-nvN{6F>s9@z$_Gn{={+I zDniOP#f@seH13muLS`tmdEB0Pjv|-GX|m2*dik)gO~tRbCb++n61>{hBtSwGU?E1l zcm*j*HK+vuK=Zuk6Hqya2I0=uuxJky0Wl;sAt1YCY>y-fklaynKcCsJKPx#>PbXFR9J=WRxu93FbuR2YrfHe4Hm|}hKZ4PFf#F)j4ZG*@QpqoOp#2J#&Mh~ zLdtEeRo^+eo(#~@7?>)a_g7bgof6X=h8Yst3A&92A)lfKSO>j}!^u(veF)it1M&&50gF&4Vat z;+zxh0Enm(@^b>7-1qca=riRLYKtmh=@`TjJpF2 z#w^y=E7{ukzm=?%sHy;JN^L4egGiDC+8}D4gdJZyfMj*-XyteS0000Px#@kvBMR9J=WRxu93Fbq5+)_kJ_8!U`{4HF~pU}WMo8ChUs;2V8_Sttr>6UTL{ z2q`zkvSMFi`(&V^Fev3bZqGbNlFQ>1&9jy+ANH*&`Ss?L{*@Hq)paHU5<~(P=dIF_Le%&Urh5(dDZs-obz9hp7-|3j002ov JPDHLkV1mhjdUyZ; diff --git a/Resources/Textures/Interface/Alerts/essence_counter.rsi/essence13.png b/Resources/Textures/Interface/Alerts/essence_counter.rsi/essence13.png deleted file mode 100644 index d853af038e32b3de7df83d52e0e023a249975208..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 311 zcmV-70m%M|P)Px#@kvBMR9J=WRk>+{v*4auwpN_Su`0d^X;~Oc#yX#B@B!~noh#@bw zASI~=Y61Yzz8?7qRL-GASlJq^s)0fv21z0WWS5N9ND=|b9VPc0WAPSfeWDmqb|ac3 zVP@3~Ktv`YKR3`9?{hLU#`)*=R!m-j@jz-!G~hd>?Px#`$$D@pip&HNV}<-uz4}@UAKs0Szhv4=m)> z5ez9BgJ=Q(P`)115m*!jO7tr`2BT^q6R=>A2m#$Ctr{6bKyydU{Unl)Al4_c5lwDH zjUvn(wF3~5i%3m`p*ayD)NMOu-iyXxkgBj14Gl1}NPx#?MXyIR9J=WR=X9#Fbq8LXtM@A6(!S9Q8EW571Ow+fsTSTGJt4sp24wYMaeUT zuktU_*}B{l&`=qa+dOX1JV%ntPx#^GQTOR9J=WmZ1*9Fcd{^33h&Z0*^wX_9-YN-+@H&DN~gga0tZo{63(ll9Hu; z?RzCf^dw8Owd=ddySpn;DU1qD9=B(cqsZlP^43{XFCTV$QSs|d2j^GR2CsP*37{Ya zNQek*a!DjlixbIIO<)SRe6 zR6K|j02pJ$|9zztOVG!JPx#<4Ht8R9J=WmOTyvAryoMW6L$Ru+zqJPh(~2IV`O_4W*5C7G7fy@T}IXKQO?< zpb1~H1$p5yz+@JnQ5h2|ZkKy0vB=@F^R6>zlQ-*LwfOPmg7q0|a2p1d00tGHAx1pC zf}Ex)BntolcRkk=&{+$Iu(MP6st1~Y7_v+V@RW@8NR|MWjx7DA6u*MhoM=H*J&2x~ zh)DAP-x#A3@^%8#ej-B0T7EN#raK*X={t#-XPfFt#^@I-bk=(7ER?7KrN=1$@g!qz v*QDb|0k6j%GGxhA9E0fbMUGWePr}9>MaynpeLAFW00000NkvXXu0mjfrcZi= diff --git a/Resources/Textures/Interface/Alerts/essence_counter.rsi/essence3.png b/Resources/Textures/Interface/Alerts/essence_counter.rsi/essence3.png deleted file mode 100644 index 7d22c2a9b6fd6abece4f214ea51257ada2d50535..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 299 zcmV+`0o4A9P)Px#(FbqW<)HT=Wz(yCwo`#8$b1*V-nv5*4F>s9@z)VF!H2HBH zln!}Pht#V5lUTndYE;I=g2&yNOBA_0PQf}I`uMOPRmHD27maUFg4Z;u1R$UQ4JqQ` z733t9AQnJG0(xu7QM|Jy{Iv&~fE2QtkO;eEYL8?IK<{Cvy|*IL$TME*|aU4~8~Wo%Q8WQcJ)P;YH8j!X#(Q1%#P{Ug~@ x59AaQ3Gj<8753kibj8_-K2PFWMRg}^ya8MLYh*4q0I&c6002ovPDHLkV1nTIdgTBB diff --git a/Resources/Textures/Interface/Alerts/essence_counter.rsi/essence4.png b/Resources/Textures/Interface/Alerts/essence_counter.rsi/essence4.png deleted file mode 100644 index 98f6fc0764a46fe530dad69e6106a0ca8b4f1873..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 321 zcmV-H0lxl;P)Px#`$?00~0G zvn5Cg8iQy70MI_W`259gPhGmS&?7MggCMV#EY4$VZEEwSplPx#=t)FDR9J=WmN5>(FbqY15Noc{fejYMo`#8$b1*V-nv5*4F>s9@zznD;h&Dfo zgTjy}bx5q*zlrs0pivnU3hviODN*EfJIvNuvX^(;URC^jal!b4C3p;jN&tfj&=4b@ zUO`UM6p{r1fV*Go3FxeaL%6e3_-PL`0WoAXA;4WSwnwrAFn46`=TrO&QhlNsQFSBw zNFpN1|9@kQO32p<`1wSHuC@H95wS1z<1u5KY9wRys|`A9XXEOT40!d0ve$G1aw#&B zjrSiq0gXFO3h;|f754W^y5eX=?Px#<4Ht8R9J=WmN5>(FbqY15Noc{fejYMo`#8$b1*V-nv5*4F>s9@zzhf!S~ZUC zpfKb~9U`mtZ~OIYpivo@D(=@uDlz1AJIu~mayRd`y=nOQ5|Z@=*WfV>CIK9*01G+d z*-xNpDnTp&0K)xRPeA8A0@BWw@KX;o0Xe8l2=FPH>k*XzE*-h_iz)sIq&d-osCp26 zH6bGL|KB-h5;A!MaX%tr&sP33h`1+>>p5$iX(i+6R~vNR&(_T=8SwIj(Q7sVT8gb? vV^3%Xi3E&?rOqW&@f}2;EAwokdJ;CCIOS_%*kMnp00000NkvXXu0mjfc%gRF diff --git a/Resources/Textures/Interface/Alerts/essence_counter.rsi/essence7.png b/Resources/Textures/Interface/Alerts/essence_counter.rsi/essence7.png deleted file mode 100644 index febaf3d09c9d93c84aaf9e063739c2028df398cc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 317 zcmV-D0mA-?P)Px#_en%SR9J;$U>F4h3>fiAW*heYr-mg{&llsghlP;lRC9ZZET4VyAYOSULI&LW z{Fx#L5CQ}pKnWnm@dTrQ979O~Bs+kCfq~)RwQ~dK05x7-hJy_FQ#L7prfFc*0mJ~Z z5+MTv;Zl;=G(wgG2$hb6Nmj61b6V-IfhUM2fXrB1JHs9j$s88OdOXyB>*D?aG41cr$*s33XlN+4L)^F=lYI$ P00000NkvXXu0mjfqMUZm diff --git a/Resources/Textures/Interface/Alerts/essence_counter.rsi/essence8.png b/Resources/Textures/Interface/Alerts/essence_counter.rsi/essence8.png deleted file mode 100644 index 4a2ba8d13ecebd70341adfc67e06c232a65a3952..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 295 zcmV+?0oeYDP)Px#;Ymb6R9J=WmOT!_FbsuX5Noc{fejYMo`#8$b1*V-nv5*4F>s9@z)VH`QDZw! z6^49L2P>-mQd_SEDur>e!}I=1If~p~m&H0;?&Z_5H5I=8F4awdDtxr@VDsDuh zBt)eC|2yYQLS|oJn2(6q*vfwzk>uumELq!3D;Y3u%anF^K;!-5xp^g98Px#-bqA3R9J=WmN5>(FbqW<#M*1HGcj@+CPvP|$i!(fc7ct7YxDqS3JRjlck+Y6 zkSBFWqT0W)^=pw%V_fR+xIJr)B$xYXG0z&keAw-&cSPWo)BHG6ZW%rV@~S#tSH=%19PEed!ihXPgxf qC!0$g@6~k0U_|f7xTa|Cgq;^t>uX|n0zv%%0000 Date: Thu, 28 Mar 2024 06:36:43 +0000 Subject: [PATCH 017/295] voicemask can select speech verb (#25768) * add Name field to SpeechVerbPrototype * extra locale for voice mask ui * SpeechVerb ui and handling * raaaaaaaaa * reeeeeeeeal Co-authored-by: Tayrtahn * fix sort * did you hear john syndicate died of ligma * Update Content.Client/VoiceMask/VoiceMaskNameChangeWindow.xaml --------- Co-authored-by: deltanedas <@deltanedas:kde.org> Co-authored-by: Tayrtahn Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> --- .../VoiceMask/VoiceMaskBoundUserInterface.cs | 8 ++- .../VoiceMask/VoiceMaskNameChangeWindow.xaml | 15 +++-- .../VoiceMaskNameChangeWindow.xaml.cs | 67 +++++++++++++++++-- .../VoiceMask/VoiceMaskSystem.Equip.cs | 25 ++++--- Content.Server/VoiceMask/VoiceMaskSystem.cs | 21 +++++- .../VoiceMask/VoiceMaskerComponent.cs | 15 +++-- Content.Shared/Speech/SpeechVerbPrototype.cs | 6 ++ .../VoiceMask/SharedVoiceMaskSystem.cs | 22 +++++- .../en-US/chat/managers/chat-manager.ftl | 24 +++++++ Resources/Locale/en-US/voice-mask.ftl | 1 + Resources/Prototypes/Voice/speech_verbs.yml | 27 +++++++- 11 files changed, 200 insertions(+), 31 deletions(-) diff --git a/Content.Client/VoiceMask/VoiceMaskBoundUserInterface.cs b/Content.Client/VoiceMask/VoiceMaskBoundUserInterface.cs index 0650482b108..f700c6663b9 100644 --- a/Content.Client/VoiceMask/VoiceMaskBoundUserInterface.cs +++ b/Content.Client/VoiceMask/VoiceMaskBoundUserInterface.cs @@ -1,10 +1,13 @@ using Content.Shared.VoiceMask; using Robust.Client.GameObjects; +using Robust.Shared.Prototypes; namespace Content.Client.VoiceMask; public sealed class VoiceMaskBoundUserInterface : BoundUserInterface { + [Dependency] private readonly IPrototypeManager _proto = default!; + [ViewVariables] private VoiceMaskNameChangeWindow? _window; @@ -16,10 +19,11 @@ protected override void Open() { base.Open(); - _window = new(); + _window = new(_proto); _window.OpenCentered(); _window.OnNameChange += OnNameSelected; + _window.OnVerbChange += verb => SendMessage(new VoiceMaskChangeVerbMessage(verb)); _window.OnClose += Close; } @@ -35,7 +39,7 @@ protected override void UpdateState(BoundUserInterfaceState state) return; } - _window.UpdateState(cast.Name); + _window.UpdateState(cast.Name, cast.Verb); } protected override void Dispose(bool disposing) diff --git a/Content.Client/VoiceMask/VoiceMaskNameChangeWindow.xaml b/Content.Client/VoiceMask/VoiceMaskNameChangeWindow.xaml index 2316ec9c7d8..e23aca12391 100644 --- a/Content.Client/VoiceMask/VoiceMaskNameChangeWindow.xaml +++ b/Content.Client/VoiceMask/VoiceMaskNameChangeWindow.xaml @@ -1,11 +1,16 @@ - - + MinSize="5 30"> + [DataField("priority")] public int Priority = 0; + + /// + /// Name shown in the voicemask UI for this verb. + /// + [DataField(required: true)] + public LocId Name = string.Empty; } diff --git a/Content.Shared/VoiceMask/SharedVoiceMaskSystem.cs b/Content.Shared/VoiceMask/SharedVoiceMaskSystem.cs index 2bb87819e30..28558919461 100644 --- a/Content.Shared/VoiceMask/SharedVoiceMaskSystem.cs +++ b/Content.Shared/VoiceMask/SharedVoiceMaskSystem.cs @@ -11,21 +11,37 @@ public enum VoiceMaskUIKey : byte [Serializable, NetSerializable] public sealed class VoiceMaskBuiState : BoundUserInterfaceState { - public string Name { get; } + public readonly string Name; + public readonly string? Verb; - public VoiceMaskBuiState(string name) + public VoiceMaskBuiState(string name, string? verb) { Name = name; + Verb = verb; } } [Serializable, NetSerializable] public sealed class VoiceMaskChangeNameMessage : BoundUserInterfaceMessage { - public string Name { get; } + public readonly string Name; public VoiceMaskChangeNameMessage(string name) { Name = name; } } + +/// +/// Change the speech verb prototype to override, or null to use the user's verb. +/// +[Serializable, NetSerializable] +public sealed class VoiceMaskChangeVerbMessage : BoundUserInterfaceMessage +{ + public readonly string? Verb; + + public VoiceMaskChangeVerbMessage(string? verb) + { + Verb = verb; + } +} diff --git a/Resources/Locale/en-US/chat/managers/chat-manager.ftl b/Resources/Locale/en-US/chat/managers/chat-manager.ftl index 2c8b326b076..1a0648579e7 100644 --- a/Resources/Locale/en-US/chat/managers/chat-manager.ftl +++ b/Resources/Locale/en-US/chat/managers/chat-manager.ftl @@ -58,57 +58,79 @@ chat-speech-verb-suffix-question = ? chat-speech-verb-suffix-stutter = - chat-speech-verb-suffix-mumble = .. +chat-speech-verb-name-none = None +chat-speech-verb-name-default = Default chat-speech-verb-default = says +chat-speech-verb-name-exclamation = Exclaiming chat-speech-verb-exclamation = exclaims +chat-speech-verb-name-exclamation-strong = Yelling chat-speech-verb-exclamation-strong = yells +chat-speech-verb-name-question = Asking chat-speech-verb-question = asks +chat-speech-verb-name-stutter = Stuttering chat-speech-verb-stutter = stutters +chat-speech-verb-name-mumble = Mumbling chat-speech-verb-mumble = mumbles +chat-speech-verb-name-arachnid = Arachnid chat-speech-verb-insect-1 = chitters chat-speech-verb-insect-2 = chirps chat-speech-verb-insect-3 = clicks +chat-speech-verb-name-moth = Moth chat-speech-verb-winged-1 = flutters chat-speech-verb-winged-2 = flaps chat-speech-verb-winged-3 = buzzes +chat-speech-verb-name-slime = Slime chat-speech-verb-slime-1 = sloshes chat-speech-verb-slime-2 = burbles chat-speech-verb-slime-3 = oozes +chat-speech-verb-name-plant = Diona chat-speech-verb-plant-1 = rustles chat-speech-verb-plant-2 = sways chat-speech-verb-plant-3 = creaks +chat-speech-verb-name-robotic = Robotic chat-speech-verb-robotic-1 = states chat-speech-verb-robotic-2 = beeps +chat-speech-verb-robotic-3 = boops +chat-speech-verb-name-reptilian = Reptilian chat-speech-verb-reptilian-1 = hisses chat-speech-verb-reptilian-2 = snorts chat-speech-verb-reptilian-3 = huffs +chat-speech-verb-name-skeleton = Skeleton chat-speech-verb-skeleton-1 = rattles chat-speech-verb-skeleton-2 = clacks chat-speech-verb-skeleton-3 = gnashes +chat-speech-verb-name-vox = Vox chat-speech-verb-vox-1 = screeches chat-speech-verb-vox-2 = shrieks chat-speech-verb-vox-3 = croaks +chat-speech-verb-name-canine = Canine chat-speech-verb-canine-1 = barks chat-speech-verb-canine-2 = woofs chat-speech-verb-canine-3 = howls +chat-speech-verb-name-small-mob = Mouse chat-speech-verb-small-mob-1 = squeaks chat-speech-verb-small-mob-2 = pieps +chat-speech-verb-name-large-mob = Carp chat-speech-verb-large-mob-1 = roars chat-speech-verb-large-mob-2 = growls +chat-speech-verb-name-monkey = Monkey chat-speech-verb-monkey-1 = chimpers chat-speech-verb-monkey-2 = screeches +chat-speech-verb-name-cluwne = Cluwne + chat-speech-verb-parrot-1 = squawks chat-speech-verb-parrot-2 = tweets chat-speech-verb-parrot-3 = chirps @@ -117,11 +139,13 @@ chat-speech-verb-cluwne-1 = giggles chat-speech-verb-cluwne-2 = guffaws chat-speech-verb-cluwne-3 = laughs +chat-speech-verb-name-ghost = Ghost chat-speech-verb-ghost-1 = complains chat-speech-verb-ghost-2 = breathes chat-speech-verb-ghost-3 = hums chat-speech-verb-ghost-4 = mutters +chat-speech-verb-name-electricity = Electricity chat-speech-verb-electricity-1 = crackles chat-speech-verb-electricity-2 = buzzes chat-speech-verb-electricity-3 = screeches diff --git a/Resources/Locale/en-US/voice-mask.ftl b/Resources/Locale/en-US/voice-mask.ftl index cb6eb7768e5..2f5acefee41 100644 --- a/Resources/Locale/en-US/voice-mask.ftl +++ b/Resources/Locale/en-US/voice-mask.ftl @@ -1,5 +1,6 @@ voice-mask-name-change-window = Voice Mask Name Change voice-mask-name-change-info = Type in the name you want to mimic. +voice-mask-name-change-speech-style = Speech style voice-mask-name-change-set = Set name voice-mask-name-change-set-description = Change the name others hear to something else. diff --git a/Resources/Prototypes/Voice/speech_verbs.yml b/Resources/Prototypes/Voice/speech_verbs.yml index 26e9370c017..43fefe04e31 100644 --- a/Resources/Prototypes/Voice/speech_verbs.yml +++ b/Resources/Prototypes/Voice/speech_verbs.yml @@ -1,30 +1,36 @@ - type: speechVerb id: Default + name: chat-speech-verb-name-default speechVerbStrings: - chat-speech-verb-default - type: speechVerb id: DefaultQuestion + name: chat-speech-verb-name-question speechVerbStrings: - chat-speech-verb-question - type: speechVerb id: DefaultStutter + name: chat-speech-verb-name-stutter speechVerbStrings: - chat-speech-verb-stutter - type: speechVerb id: DefaultMumble + name: chat-speech-verb-name-mumble speechVerbStrings: - chat-speech-verb-mumble - type: speechVerb id: DefaultExclamation + name: chat-speech-verb-name-exclamation speechVerbStrings: - chat-speech-verb-exclamation - type: speechVerb id: DefaultExclamationStrong + name: chat-speech-verb-name-exclamation-strong bold: true speechVerbStrings: - chat-speech-verb-exclamation-strong @@ -32,6 +38,7 @@ - type: speechVerb id: Arachnid + name: chat-speech-verb-name-arachnid speechVerbStrings: - chat-speech-verb-insect-1 - chat-speech-verb-insect-2 @@ -40,6 +47,7 @@ - type: speechVerb id: Moth + name: chat-speech-verb-name-moth speechVerbStrings: - chat-speech-verb-winged-1 - chat-speech-verb-winged-2 @@ -48,12 +56,14 @@ - type: speechVerb id: Robotic + name: chat-speech-verb-name-robotic speechVerbStrings: - chat-speech-verb-robotic-1 - chat-speech-verb-robotic-2 - type: speechVerb id: Reptilian + name: chat-speech-verb-name-reptilian speechVerbStrings: - chat-speech-verb-reptilian-1 - chat-speech-verb-reptilian-2 @@ -62,6 +72,7 @@ - type: speechVerb id: Skeleton + name: chat-speech-verb-name-skeleton speechVerbStrings: - chat-speech-verb-skeleton-1 - chat-speech-verb-skeleton-2 @@ -69,6 +80,7 @@ - type: speechVerb id: Slime + name: chat-speech-verb-name-slime speechVerbStrings: - chat-speech-verb-slime-1 - chat-speech-verb-slime-2 @@ -76,13 +88,15 @@ - type: speechVerb id: Vox + name: chat-speech-verb-name-vox speechVerbStrings: - - chat-speech-verb-vox-1 - - chat-speech-verb-vox-2 - - chat-speech-verb-vox-3 + - chat-speech-verb-vox-1 + - chat-speech-verb-vox-2 + - chat-speech-verb-vox-3 - type: speechVerb id: Plant + name: chat-speech-verb-name-plant speechVerbStrings: - chat-speech-verb-plant-1 - chat-speech-verb-plant-2 @@ -90,6 +104,7 @@ - type: speechVerb id: Canine + name: chat-speech-verb-name-canine speechVerbStrings: - chat-speech-verb-canine-1 - chat-speech-verb-canine-2 @@ -97,18 +112,21 @@ - type: speechVerb id: LargeMob + name: chat-speech-verb-name-large-mob speechVerbStrings: - chat-speech-verb-large-mob-1 - chat-speech-verb-large-mob-2 - type: speechVerb id: SmallMob + name: chat-speech-verb-name-small-mob speechVerbStrings: - chat-speech-verb-small-mob-1 - chat-speech-verb-small-mob-2 - type: speechVerb id: Monkey + name: chat-speech-verb-name-monkey speechVerbStrings: - chat-speech-verb-monkey-1 - chat-speech-verb-monkey-2 @@ -122,6 +140,7 @@ - type: speechVerb id: Cluwne + name: chat-speech-verb-name-cluwne speechVerbStrings: - chat-speech-verb-cluwne-1 - chat-speech-verb-cluwne-2 @@ -129,6 +148,7 @@ - type: speechVerb id: Ghost + name: chat-speech-verb-name-ghost speechVerbStrings: - chat-speech-verb-ghost-1 - chat-speech-verb-ghost-2 @@ -138,6 +158,7 @@ - type: speechVerb id: Electricity + name: chat-speech-verb-name-electricity speechVerbStrings: - chat-speech-verb-electricity-1 - chat-speech-verb-electricity-2 From 97cb947411f1020f3d8bc90eaf78dcab29064f33 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Thu, 28 Mar 2024 02:46:26 -0400 Subject: [PATCH 018/295] Separate "thank you" messages from general ads (#25867) * Separated "thank you" messages from general ads * Moved MessagePackPrototype to shared, cleaned up AdvertiseComponent, and actually killed AdvertisementsPackPrototype. +More suggests changes. * Rename PackPrototypeID to Pack in both components. --- Content.Client/Entry/EntryPoint.cs | 1 - .../Advertise/AdvertiseComponent.cs | 42 ----- Content.Server/Advertise/AdvertiseSystem.cs | 154 ------------------ .../Components/AdvertiseComponent.cs | 45 +++++ .../Components/SpeakOnUIClosedComponent.cs | 36 ++++ .../EntitySystems/AdvertiseSystem.cs | 137 ++++++++++++++++ .../EntitySystems/SpeakOnUIClosedSystem.cs | 58 +++++++ .../AdvertisementsPackPrototype.cs | 18 -- .../VendingMachines/VendingMachineSystem.cs | 22 +-- .../Advertise/MessagePackPrototype.cs | 14 ++ .../VendingMachineComponent.cs | 2 - .../VendingMachines/Advertisements/ammo.yml | 6 +- .../Advertisements/atmosdrobe.yml | 6 +- .../Advertisements/bardrobe.yml | 6 +- .../Advertisements/boozeomat.yml | 9 +- .../Advertisements/cargodrobe.yml | 6 +- .../VendingMachines/Advertisements/chang.yml | 8 +- .../Advertisements/chefdrobe.yml | 6 +- .../Advertisements/chefvend.yml | 10 +- .../Advertisements/chemdrobe.yml | 7 +- .../VendingMachines/Advertisements/cigs.yml | 9 +- .../Advertisements/clothesmate.yml | 6 +- .../VendingMachines/Advertisements/coffee.yml | 10 +- .../VendingMachines/Advertisements/cola.yml | 10 +- .../Advertisements/condiments.yml | 6 +- .../Advertisements/curadrobe.yml | 6 +- .../Advertisements/detdrobe.yml | 6 +- .../Advertisements/dinnerware.yml | 6 +- .../Advertisements/discount.yml | 14 +- .../VendingMachines/Advertisements/donut.yml | 10 +- .../Advertisements/engidrobe.yml | 6 +- .../Advertisements/fatextractor.yml | 6 +- .../VendingMachines/Advertisements/games.yml | 10 +- .../Advertisements/genedrobe.yml | 6 +- .../Advertisements/happyhonk.yml | 10 +- .../Advertisements/hydrobe.yml | 6 +- .../Advertisements/janidrobe.yml | 6 +- .../Advertisements/lawdrobe.yml | 11 +- .../Advertisements/magivend.yml | 6 +- .../Advertisements/medidrobe.yml | 6 +- .../Advertisements/megaseed.yml | 6 +- .../Advertisements/nanomed.yml | 6 +- .../Advertisements/nutrimax.yml | 8 +- .../Advertisements/robodrobe.yml | 6 +- .../Advertisements/scidrobe.yml | 6 +- .../Advertisements/secdrobe.yml | 6 +- .../Advertisements/sectech.yml | 9 +- .../Advertisements/smartfridge.yml | 6 +- .../VendingMachines/Advertisements/snack.yml | 12 +- .../Advertisements/sovietsoda.yml | 9 +- .../Advertisements/syndiedrobe.yml | 11 +- .../Advertisements/theater.yml | 6 +- .../Advertisements/vendomat.yml | 6 +- .../Advertisements/virodrobe.yml | 6 +- .../VendingMachines/Goodbyes/boozeomat.yml | 7 + .../VendingMachines/Goodbyes/chang.yml | 6 + .../VendingMachines/Goodbyes/chefvend.yml | 8 + .../Catalog/VendingMachines/Goodbyes/cigs.yml | 7 + .../VendingMachines/Goodbyes/coffee.yml | 8 + .../Catalog/VendingMachines/Goodbyes/cola.yml | 8 + .../VendingMachines/Goodbyes/discount.yml | 12 ++ .../VendingMachines/Goodbyes/donut.yml | 8 + .../VendingMachines/Goodbyes/games.yml | 8 + .../VendingMachines/Goodbyes/generic.yml | 4 + .../VendingMachines/Goodbyes/happyhonk.yml | 8 + .../VendingMachines/Goodbyes/lawdrobe.yml | 8 + .../VendingMachines/Goodbyes/nutrimax.yml | 6 + .../VendingMachines/Goodbyes/sectech.yml | 7 + .../VendingMachines/Goodbyes/snack.yml | 10 ++ .../VendingMachines/Goodbyes/sovietsoda.yml | 7 + .../VendingMachines/Goodbyes/syndiedrobe.yml | 9 + .../Structures/Machines/vending_machines.yml | 90 ++++++++++ 72 files changed, 603 insertions(+), 470 deletions(-) delete mode 100644 Content.Server/Advertise/AdvertiseComponent.cs delete mode 100644 Content.Server/Advertise/AdvertiseSystem.cs create mode 100644 Content.Server/Advertise/Components/AdvertiseComponent.cs create mode 100644 Content.Server/Advertise/Components/SpeakOnUIClosedComponent.cs create mode 100644 Content.Server/Advertise/EntitySystems/AdvertiseSystem.cs create mode 100644 Content.Server/Advertise/EntitySystems/SpeakOnUIClosedSystem.cs delete mode 100644 Content.Server/Advertisements/AdvertisementsPackPrototype.cs create mode 100644 Content.Shared/Advertise/MessagePackPrototype.cs create mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/boozeomat.yml create mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/chang.yml create mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/chefvend.yml create mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/cigs.yml create mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/coffee.yml create mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/cola.yml create mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/discount.yml create mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/donut.yml create mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/games.yml create mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/generic.yml create mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/happyhonk.yml create mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/lawdrobe.yml create mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/nutrimax.yml create mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/sectech.yml create mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/snack.yml create mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/sovietsoda.yml create mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/syndiedrobe.yml diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs index a1fc68bbd2f..7f921fc1a6b 100644 --- a/Content.Client/Entry/EntryPoint.cs +++ b/Content.Client/Entry/EntryPoint.cs @@ -111,7 +111,6 @@ public override void Init() _prototypeManager.RegisterIgnore("gameMapPool"); _prototypeManager.RegisterIgnore("npcFaction"); _prototypeManager.RegisterIgnore("lobbyBackground"); - _prototypeManager.RegisterIgnore("advertisementsPack"); _prototypeManager.RegisterIgnore("gamePreset"); _prototypeManager.RegisterIgnore("noiseChannel"); _prototypeManager.RegisterIgnore("spaceBiome"); diff --git a/Content.Server/Advertise/AdvertiseComponent.cs b/Content.Server/Advertise/AdvertiseComponent.cs deleted file mode 100644 index f36cc7ae1e6..00000000000 --- a/Content.Server/Advertise/AdvertiseComponent.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Content.Server.Advertisements; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Server.Advertise -{ - [RegisterComponent, Access(typeof(AdvertiseSystem))] - public sealed partial class AdvertiseComponent : Component - { - /// - /// Minimum time in seconds to wait before saying a new ad, in seconds. Has to be larger than or equal to 1. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("minWait")] - public int MinimumWait { get; private set; } = 8 * 60; - - /// - /// Maximum time in seconds to wait before saying a new ad, in seconds. Has to be larger than or equal - /// to - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("maxWait")] - public int MaximumWait { get; private set; } = 10 * 60; - - /// - /// The identifier for the advertisements pack prototype. - /// - [DataField("pack", customTypeSerializer:typeof(PrototypeIdSerializer), required: true)] - public string PackPrototypeId { get; private set; } = string.Empty; - - /// - /// The next time an advertisement will be said. - /// - [ViewVariables(VVAccess.ReadWrite)] - public TimeSpan NextAdvertisementTime { get; set; } = TimeSpan.Zero; - - /// - /// Whether the entity will say advertisements or not. - /// - [ViewVariables(VVAccess.ReadWrite)] - public bool Enabled { get; set; } = true; - } -} diff --git a/Content.Server/Advertise/AdvertiseSystem.cs b/Content.Server/Advertise/AdvertiseSystem.cs deleted file mode 100644 index ab538f3c779..00000000000 --- a/Content.Server/Advertise/AdvertiseSystem.cs +++ /dev/null @@ -1,154 +0,0 @@ -using Content.Server.Advertisements; -using Content.Server.Chat.Systems; -using Content.Server.Power.Components; -using Content.Shared.VendingMachines; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; -using Robust.Shared.Timing; - -namespace Content.Server.Advertise -{ - public sealed class AdvertiseSystem : EntitySystem - { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly ChatSystem _chat = default!; - - /// - /// The maximum amount of time between checking if advertisements should be displayed - /// - private readonly TimeSpan _maximumNextCheckDuration = TimeSpan.FromSeconds(15); - - /// - /// The next time the game will check if advertisements should be displayed - /// - private TimeSpan _nextCheckTime = TimeSpan.MaxValue; - - public override void Initialize() - { - SubscribeLocalEvent(OnComponentInit); - SubscribeLocalEvent(OnPowerChanged); - - SubscribeLocalEvent(OnPowerReceiverEnableChangeAttempt); - SubscribeLocalEvent(OnVendingEnableChangeAttempt); - - // The component inits will lower this. - _nextCheckTime = TimeSpan.MaxValue; - } - - private void OnComponentInit(EntityUid uid, AdvertiseComponent advertise, ComponentInit args) - { - RefreshTimer(uid, advertise); - } - - private void OnPowerChanged(EntityUid uid, AdvertiseComponent advertise, ref PowerChangedEvent args) - { - SetEnabled(uid, args.Powered, advertise); - } - - public void RefreshTimer(EntityUid uid, AdvertiseComponent? advertise = null) - { - if (!Resolve(uid, ref advertise)) - return; - - if (!advertise.Enabled) - return; - - var minDuration = Math.Max(1, advertise.MinimumWait); - var maxDuration = Math.Max(minDuration, advertise.MaximumWait); - var waitDuration = TimeSpan.FromSeconds(_random.Next(minDuration, maxDuration)); - var nextTime = _gameTiming.CurTime + waitDuration; - - advertise.NextAdvertisementTime = nextTime; - - _nextCheckTime = MathHelper.Min(nextTime, _nextCheckTime); - } - - public void SayAdvertisement(EntityUid uid, AdvertiseComponent? advertise = null) - { - if (!Resolve(uid, ref advertise)) - return; - - if (_prototypeManager.TryIndex(advertise.PackPrototypeId, out AdvertisementsPackPrototype? advertisements)) - _chat.TrySendInGameICMessage(uid, Loc.GetString(_random.Pick(advertisements.Advertisements)), InGameICChatType.Speak, true); - } - - public void SayThankYou(EntityUid uid, AdvertiseComponent? advertise = null) - { - if (!Resolve(uid, ref advertise)) - return; - - if (_prototypeManager.TryIndex(advertise.PackPrototypeId, out AdvertisementsPackPrototype? advertisements)) - { - _chat.TrySendInGameICMessage(uid, Loc.GetString(_random.Pick(advertisements.ThankYous), ("name", Name(uid))), InGameICChatType.Speak, true); - } - } - - public void SetEnabled(EntityUid uid, bool enable, AdvertiseComponent? advertise = null) - { - if (!Resolve(uid, ref advertise)) - return; - - if (advertise.Enabled == enable) - return; - - var attemptEvent = new AdvertiseEnableChangeAttemptEvent(enable); - RaiseLocalEvent(uid, attemptEvent); - - if (attemptEvent.Cancelled) - return; - - advertise.Enabled = enable; - RefreshTimer(uid, advertise); - } - - private static void OnPowerReceiverEnableChangeAttempt(EntityUid uid, ApcPowerReceiverComponent component, AdvertiseEnableChangeAttemptEvent args) - { - if(args.Enabling && !component.Powered) - args.Cancel(); - } - - private static void OnVendingEnableChangeAttempt(EntityUid uid, VendingMachineComponent component, AdvertiseEnableChangeAttemptEvent args) - { - if(args.Enabling && component.Broken) - args.Cancel(); - } - - public override void Update(float frameTime) - { - var curTime = _gameTiming.CurTime; - if (_nextCheckTime > curTime) - return; - - _nextCheckTime = curTime + _maximumNextCheckDuration; - - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var advert)) - { - if (!advert.Enabled) - continue; - - // If this isn't advertising yet - if (advert.NextAdvertisementTime > curTime) - { - _nextCheckTime = MathHelper.Min(advert.NextAdvertisementTime, _nextCheckTime); - continue; - } - - SayAdvertisement(uid, advert); - RefreshTimer(uid, advert); - } - } - } - - public sealed class AdvertiseEnableChangeAttemptEvent : CancellableEntityEventArgs - { - public bool Enabling { get; } - - public AdvertiseEnableChangeAttemptEvent(bool enabling) - { - Enabling = enabling; - } - } -} diff --git a/Content.Server/Advertise/Components/AdvertiseComponent.cs b/Content.Server/Advertise/Components/AdvertiseComponent.cs new file mode 100644 index 00000000000..140bc6b902a --- /dev/null +++ b/Content.Server/Advertise/Components/AdvertiseComponent.cs @@ -0,0 +1,45 @@ +using Content.Server.Advertise.EntitySystems; +using Content.Shared.Advertise; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.Advertise.Components; + +/// +/// Makes this entity periodically advertise by speaking a randomly selected +/// message from a specified MessagePack into local chat. +/// +[RegisterComponent, Access(typeof(AdvertiseSystem))] +public sealed partial class AdvertiseComponent : Component +{ + /// + /// Minimum time in seconds to wait before saying a new ad, in seconds. Has to be larger than or equal to 1. + /// + [DataField] + public int MinimumWait { get; private set; } = 8 * 60; + + /// + /// Maximum time in seconds to wait before saying a new ad, in seconds. Has to be larger than or equal + /// to + /// + [DataField] + public int MaximumWait { get; private set; } = 10 * 60; + + /// + /// The identifier for the advertisements pack prototype. + /// + [DataField(required: true)] + public ProtoId Pack { get; private set; } + + /// + /// The next time an advertisement will be said. + /// + [DataField] + public TimeSpan NextAdvertisementTime { get; set; } = TimeSpan.Zero; + + /// + /// Whether the entity will say advertisements or not. + /// + [DataField] + public bool Enabled { get; set; } = true; +} diff --git a/Content.Server/Advertise/Components/SpeakOnUIClosedComponent.cs b/Content.Server/Advertise/Components/SpeakOnUIClosedComponent.cs new file mode 100644 index 00000000000..2a663b7f89d --- /dev/null +++ b/Content.Server/Advertise/Components/SpeakOnUIClosedComponent.cs @@ -0,0 +1,36 @@ +using Content.Shared.Advertise; +using Robust.Shared.Prototypes; + +namespace Content.Server.Advertise.Components; + +/// +/// Causes the entity to speak using the Chat system when its ActivatableUI is closed, optionally +/// requiring that a Flag be set as well. +/// +[RegisterComponent, Access(typeof(SpeakOnUIClosedSystem))] +public sealed partial class SpeakOnUIClosedComponent : Component +{ + /// + /// The identifier for the message pack prototype containing messages to be spoken by this entity. + /// + [DataField(required: true)] + public ProtoId Pack { get; private set; } + + /// + /// Is this component active? If false, no messages will be spoken. + /// + [DataField] + public bool Enabled = true; + + /// + /// Should messages be spoken only if the is set (true), or every time the UI is closed (false)? + /// + [DataField] + public bool RequireFlag = true; + + /// + /// State variable only used if is true. Set with . + /// + [DataField] + public bool Flag; +} diff --git a/Content.Server/Advertise/EntitySystems/AdvertiseSystem.cs b/Content.Server/Advertise/EntitySystems/AdvertiseSystem.cs new file mode 100644 index 00000000000..bb254d11ef0 --- /dev/null +++ b/Content.Server/Advertise/EntitySystems/AdvertiseSystem.cs @@ -0,0 +1,137 @@ +using Content.Server.Advertise.Components; +using Content.Server.Chat.Systems; +using Content.Server.Power.Components; +using Content.Shared.VendingMachines; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Timing; + +namespace Content.Server.Advertise.EntitySystems; + +public sealed class AdvertiseSystem : EntitySystem +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly ChatSystem _chat = default!; + + /// + /// The maximum amount of time between checking if advertisements should be displayed + /// + private readonly TimeSpan _maximumNextCheckDuration = TimeSpan.FromSeconds(15); + + /// + /// The next time the game will check if advertisements should be displayed + /// + private TimeSpan _nextCheckTime = TimeSpan.MaxValue; + + public override void Initialize() + { + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnPowerChanged); + + SubscribeLocalEvent(OnPowerReceiverEnableChangeAttempt); + SubscribeLocalEvent(OnVendingEnableChangeAttempt); + + // The component inits will lower this. + _nextCheckTime = TimeSpan.MaxValue; + } + + private void OnMapInit(EntityUid uid, AdvertiseComponent advertise, MapInitEvent args) + { + RefreshTimer(uid, advertise); + } + + private void OnPowerChanged(EntityUid uid, AdvertiseComponent advertise, ref PowerChangedEvent args) + { + SetEnabled(uid, args.Powered, advertise); + } + + public void RefreshTimer(EntityUid uid, AdvertiseComponent? advertise = null) + { + if (!Resolve(uid, ref advertise)) + return; + + if (!advertise.Enabled) + return; + + var minDuration = Math.Max(1, advertise.MinimumWait); + var maxDuration = Math.Max(minDuration, advertise.MaximumWait); + var waitDuration = TimeSpan.FromSeconds(_random.Next(minDuration, maxDuration)); + var nextTime = _gameTiming.CurTime + waitDuration; + + advertise.NextAdvertisementTime = nextTime; + + _nextCheckTime = MathHelper.Min(nextTime, _nextCheckTime); + } + + public void SayAdvertisement(EntityUid uid, AdvertiseComponent? advertise = null) + { + if (!Resolve(uid, ref advertise)) + return; + + if (_prototypeManager.TryIndex(advertise.Pack, out var advertisements)) + _chat.TrySendInGameICMessage(uid, Loc.GetString(_random.Pick(advertisements.Messages)), InGameICChatType.Speak, hideChat: true); + } + + public void SetEnabled(EntityUid uid, bool enable, AdvertiseComponent? advertise = null) + { + if (!Resolve(uid, ref advertise)) + return; + + if (advertise.Enabled == enable) + return; + + var attemptEvent = new AdvertiseEnableChangeAttemptEvent(enable); + RaiseLocalEvent(uid, attemptEvent); + + if (attemptEvent.Cancelled) + return; + + advertise.Enabled = enable; + RefreshTimer(uid, advertise); + } + + private static void OnPowerReceiverEnableChangeAttempt(EntityUid uid, ApcPowerReceiverComponent component, AdvertiseEnableChangeAttemptEvent args) + { + if (args.Enabling && !component.Powered) + args.Cancel(); + } + + private static void OnVendingEnableChangeAttempt(EntityUid uid, VendingMachineComponent component, AdvertiseEnableChangeAttemptEvent args) + { + if (args.Enabling && component.Broken) + args.Cancel(); + } + + public override void Update(float frameTime) + { + var curTime = _gameTiming.CurTime; + if (_nextCheckTime > curTime) + return; + + _nextCheckTime = curTime + _maximumNextCheckDuration; + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var advert)) + { + if (!advert.Enabled) + continue; + + // If this isn't advertising yet + if (advert.NextAdvertisementTime > curTime) + { + _nextCheckTime = MathHelper.Min(advert.NextAdvertisementTime, _nextCheckTime); + continue; + } + + SayAdvertisement(uid, advert); + RefreshTimer(uid, advert); + } + } +} + +public sealed class AdvertiseEnableChangeAttemptEvent(bool enabling) : CancellableEntityEventArgs +{ + public bool Enabling { get; } = enabling; +} diff --git a/Content.Server/Advertise/EntitySystems/SpeakOnUIClosedSystem.cs b/Content.Server/Advertise/EntitySystems/SpeakOnUIClosedSystem.cs new file mode 100644 index 00000000000..048f59b8d33 --- /dev/null +++ b/Content.Server/Advertise/EntitySystems/SpeakOnUIClosedSystem.cs @@ -0,0 +1,58 @@ +using Content.Server.Advertise.Components; +using Content.Server.Chat.Systems; +using Content.Server.UserInterface; +using Content.Shared.Advertise; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Server.Advertise; + +public sealed partial class SpeakOnUIClosedSystem : EntitySystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly ChatSystem _chat = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnBoundUIClosed); + } + private void OnBoundUIClosed(Entity entity, ref BoundUIClosedEvent args) + { + if (!TryComp(entity, out ActivatableUIComponent? activatable) || !args.UiKey.Equals(activatable.Key)) + return; + + if (entity.Comp.RequireFlag && !entity.Comp.Flag) + return; + + TrySpeak((entity, entity.Comp)); + } + + public bool TrySpeak(Entity entity) + { + if (!Resolve(entity, ref entity.Comp)) + return false; + + if (!entity.Comp.Enabled) + return false; + + if (!_prototypeManager.TryIndex(entity.Comp.Pack, out MessagePackPrototype? messagePack)) + return false; + + var message = Loc.GetString(_random.Pick(messagePack.Messages), ("name", Name(entity))); + _chat.TrySendInGameICMessage(entity, message, InGameICChatType.Speak, true); + entity.Comp.Flag = false; + return true; + } + + public bool TrySetFlag(Entity entity, bool value = true) + { + if (!Resolve(entity, ref entity.Comp)) + return false; + + entity.Comp.Flag = value; + return true; + } +} diff --git a/Content.Server/Advertisements/AdvertisementsPackPrototype.cs b/Content.Server/Advertisements/AdvertisementsPackPrototype.cs deleted file mode 100644 index 641ab3c56fd..00000000000 --- a/Content.Server/Advertisements/AdvertisementsPackPrototype.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Robust.Shared.Prototypes; - -namespace Content.Server.Advertisements -{ - [Serializable, Prototype("advertisementsPack")] - public sealed partial class AdvertisementsPackPrototype : IPrototype - { - [ViewVariables] - [IdDataField] - public string ID { get; private set; } = default!; - - [DataField("advertisements")] - public List Advertisements { get; private set; } = new(); - - [DataField("thankyous")] - public List ThankYous { get; private set; } = new(); - } -} diff --git a/Content.Server/VendingMachines/VendingMachineSystem.cs b/Content.Server/VendingMachines/VendingMachineSystem.cs index b5fa611a396..7c9aed188fe 100644 --- a/Content.Server/VendingMachines/VendingMachineSystem.cs +++ b/Content.Server/VendingMachines/VendingMachineSystem.cs @@ -1,12 +1,11 @@ using System.Linq; using System.Numerics; using Content.Server.Advertise; +using Content.Server.Advertise.Components; using Content.Server.Cargo.Systems; -using Content.Server.Chat.Systems; using Content.Server.Emp; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; -using Content.Server.UserInterface; using Content.Shared.Access.Components; using Content.Shared.Access.Systems; using Content.Shared.Actions; @@ -25,7 +24,6 @@ using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Timing; -using Robust.Shared.Utility; namespace Content.Server.VendingMachines { @@ -39,7 +37,7 @@ public sealed class VendingMachineSystem : SharedVendingMachineSystem [Dependency] private readonly ThrowingSystem _throwingSystem = default!; [Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!; [Dependency] private readonly IGameTiming _timing = default!; - [Dependency] private readonly AdvertiseSystem _advertise = default!; + [Dependency] private readonly SpeakOnUIClosedSystem _speakOnUIClosed = default!; public override void Initialize() { @@ -58,7 +56,6 @@ public override void Initialize() Subs.BuiEvents(VendingMachineUiKey.Key, subs => { subs.Event(OnBoundUIOpened); - subs.Event(OnBoundUIClosed); subs.Event(OnInventoryEjectMessage); }); @@ -114,16 +111,6 @@ private void OnBoundUIOpened(EntityUid uid, VendingMachineComponent component, B UpdateVendingMachineInterfaceState(uid, component); } - private void OnBoundUIClosed(EntityUid uid, VendingMachineComponent component, BoundUIClosedEvent args) - { - // Only vendors that advertise will send message after dispensing - if (component.ShouldSayThankYou && TryComp(uid, out var advertise)) - { - _advertise.SayThankYou(uid, advertise); - component.ShouldSayThankYou = false; - } - } - private void UpdateVendingMachineInterfaceState(EntityUid uid, VendingMachineComponent component) { var state = new VendingMachineInterfaceState(GetAllInventory(uid, component)); @@ -294,7 +281,10 @@ public void TryEjectVendorItem(EntityUid uid, InventoryType type, string itemId, vendComponent.Ejecting = true; vendComponent.NextItemToEject = entry.ID; vendComponent.ThrowNextItem = throwItem; - vendComponent.ShouldSayThankYou = true; + + if (TryComp(uid, out SpeakOnUIClosedComponent? speakComponent)) + _speakOnUIClosed.TrySetFlag((uid, speakComponent)); + entry.Amount--; UpdateVendingMachineInterfaceState(uid, vendComponent); TryUpdateVisualState(uid, vendComponent); diff --git a/Content.Shared/Advertise/MessagePackPrototype.cs b/Content.Shared/Advertise/MessagePackPrototype.cs new file mode 100644 index 00000000000..f7495d7e468 --- /dev/null +++ b/Content.Shared/Advertise/MessagePackPrototype.cs @@ -0,0 +1,14 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Advertise; + +[Serializable, Prototype("messagePack")] +public sealed partial class MessagePackPrototype : IPrototype +{ + [ViewVariables] + [IdDataField] + public string ID { get; private set; } = default!; + + [DataField] + public List Messages { get; private set; } = []; +} diff --git a/Content.Shared/VendingMachines/VendingMachineComponent.cs b/Content.Shared/VendingMachines/VendingMachineComponent.cs index 725dc60e3bc..23130bb8f39 100644 --- a/Content.Shared/VendingMachines/VendingMachineComponent.cs +++ b/Content.Shared/VendingMachines/VendingMachineComponent.cs @@ -51,8 +51,6 @@ public sealed partial class VendingMachineComponent : Component public bool Broken; - public bool ShouldSayThankYou; - /// /// When true, will forcefully throw any object it dispenses /// diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/ammo.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/ammo.yml index 6038134ea16..7e089a28259 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/ammo.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/ammo.yml @@ -1,6 +1,6 @@ -- type: advertisementsPack +- type: messagePack id: AmmoVendAds - advertisements: + messages: - advertisement-ammo-1 - advertisement-ammo-2 - advertisement-ammo-3 @@ -11,5 +11,3 @@ - advertisement-ammo-8 - advertisement-ammo-9 - advertisement-ammo-10 - thankyous: - - vending-machine-thanks diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/atmosdrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/atmosdrobe.yml index c5737d58bff..7946c063102 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/atmosdrobe.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/atmosdrobe.yml @@ -1,8 +1,6 @@ -- type: advertisementsPack +- type: messagePack id: AtmosDrobeAds - advertisements: + messages: - advertisement-atmosdrobe-1 - advertisement-atmosdrobe-2 - advertisement-atmosdrobe-3 - thankyous: - - vending-machine-thanks diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/bardrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/bardrobe.yml index 84501583d76..0a21acf2cb5 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/bardrobe.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/bardrobe.yml @@ -1,7 +1,5 @@ -- type: advertisementsPack +- type: messagePack id: BarDrobeAds - advertisements: + messages: - advertisement-bardrobe-1 - advertisement-bardrobe-2 - thankyous: - - vending-machine-thanks diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/boozeomat.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/boozeomat.yml index 943e5718870..e6bcbbcb9c3 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/boozeomat.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/boozeomat.yml @@ -1,6 +1,6 @@ -- type: advertisementsPack +- type: messagePack id: BoozeOMatAds - advertisements: + messages: - advertisement-boozeomat-1 - advertisement-boozeomat-2 - advertisement-boozeomat-3 @@ -20,8 +20,3 @@ - advertisement-boozeomat-17 - advertisement-boozeomat-18 - advertisement-boozeomat-19 - thankyous: - - vending-machine-thanks - - thankyou-boozeomat-1 - - thankyou-boozeomat-2 - - thankyou-boozeomat-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/cargodrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/cargodrobe.yml index fdf90a1d0d6..f2cd38f737e 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/cargodrobe.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/cargodrobe.yml @@ -1,8 +1,6 @@ -- type: advertisementsPack +- type: messagePack id: CargoDrobeAds - advertisements: + messages: - advertisement-cargodrobe-1 - advertisement-cargodrobe-2 - advertisement-cargodrobe-3 - thankyous: - - vending-machine-thanks diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chang.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chang.yml index ebfe81286b5..911d467e7d0 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chang.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chang.yml @@ -1,12 +1,8 @@ -- type: advertisementsPack +- type: messagePack id: ChangAds - advertisements: + messages: - advertisement-chang-1 - advertisement-chang-2 - advertisement-chang-3 - advertisement-chang-4 - advertisement-chang-5 - thankyous: - - vending-machine-thanks - - thankyou-chang-1 - - thankyou-chang-2 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chefdrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chefdrobe.yml index 2d45bd4e29d..c71d8225e03 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chefdrobe.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chefdrobe.yml @@ -1,8 +1,6 @@ -- type: advertisementsPack +- type: messagePack id: ChefDrobeAds - advertisements: + messages: - advertisement-chefdrobe-1 - advertisement-chefdrobe-2 - advertisement-chefdrobe-3 - thankyous: - - vending-machine-thanks diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chefvend.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chefvend.yml index 9b1edb4b5be..d52fcf9894c 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chefvend.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chefvend.yml @@ -1,6 +1,6 @@ -- type: advertisementsPack +- type: messagePack id: ChefvendAds - advertisements: + messages: - advertisement-chefvend-1 - advertisement-chefvend-2 - advertisement-chefvend-3 @@ -10,9 +10,3 @@ - advertisement-chefvend-7 - advertisement-chefvend-8 - advertisement-chefvend-9 - thankyous: - - vending-machine-thanks - - thankyou-chefvend-1 - - thankyou-chefvend-2 - - thankyou-chefvend-3 - - thankyou-chefvend-4 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chemdrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chemdrobe.yml index 7d450847384..69a37de184b 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chemdrobe.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chemdrobe.yml @@ -1,9 +1,6 @@ -- type: advertisementsPack +- type: messagePack id: ChemDrobeAds - advertisements: + messages: - advertisement-chemdrobe-1 - advertisement-chemdrobe-2 - advertisement-chemdrobe-3 - thankyous: - - vending-machine-thanks - diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/cigs.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/cigs.yml index fe6cb1bf26a..db3d492c459 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/cigs.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/cigs.yml @@ -1,6 +1,6 @@ -- type: advertisementsPack +- type: messagePack id: CigaretteMachineAds - advertisements: + messages: - advertisement-cigs-1 - advertisement-cigs-2 - advertisement-cigs-3 @@ -13,8 +13,3 @@ - advertisement-cigs-10 - advertisement-cigs-11 - advertisement-cigs-12 - thankyous: - - vending-machine-thanks - - thankyou-cigs-1 - - thankyou-cigs-2 - - thankyou-cigs-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/clothesmate.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/clothesmate.yml index 5c728513b89..dc109f8eb99 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/clothesmate.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/clothesmate.yml @@ -1,6 +1,6 @@ -- type: advertisementsPack +- type: messagePack id: ClothesMateAds - advertisements: + messages: - advertisement-clothes-1 - advertisement-clothes-2 - advertisement-clothes-3 @@ -8,5 +8,3 @@ - advertisement-clothes-5 - advertisement-clothes-6 - advertisement-clothes-7 - thankyous: - - vending-machine-thanks diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/coffee.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/coffee.yml index 5153040165c..4781768cf0c 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/coffee.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/coffee.yml @@ -1,6 +1,6 @@ -- type: advertisementsPack +- type: messagePack id: HotDrinksMachineAds - advertisements: + messages: - advertisement-coffee-1 - advertisement-coffee-2 - advertisement-coffee-3 @@ -15,9 +15,3 @@ - advertisement-coffee-12 - advertisement-coffee-13 - advertisement-coffee-14 - thankyous: - - vending-machine-thanks - - thankyou-coffee-1 - - thankyou-coffee-2 - - thankyou-coffee-3 - - thankyou-coffee-4 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/cola.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/cola.yml index 862846274da..d12d7eb92f4 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/cola.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/cola.yml @@ -1,6 +1,6 @@ -- type: advertisementsPack +- type: messagePack id: RobustSoftdrinksAds - advertisements: + messages: - advertisement-cola-1 - advertisement-cola-2 - advertisement-cola-3 @@ -9,9 +9,3 @@ - advertisement-cola-6 - advertisement-cola-7 - advertisement-cola-8 - thankyous: - - vending-machine-thanks - - thankyou-cola-1 - - thankyou-cola-2 - - thankyou-cola-3 - - thankyou-cola-4 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/condiments.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/condiments.yml index 05dbc758875..d1d07df0c4e 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/condiments.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/condiments.yml @@ -1,11 +1,9 @@ -- type: advertisementsPack +- type: messagePack id: CondimentVendAds - advertisements: + messages: - advertisement-condiment-1 - advertisement-condiment-2 - advertisement-condiment-3 - advertisement-condiment-4 - advertisement-condiment-5 - advertisement-condiment-6 - thankyous: - - vending-machine-thanks diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/curadrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/curadrobe.yml index b6e2d485cec..47523c6e936 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/curadrobe.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/curadrobe.yml @@ -1,8 +1,6 @@ -- type: advertisementsPack +- type: messagePack id: CuraDrobeAds - advertisements: + messages: - advertisement-curadrobe-1 - advertisement-curadrobe-2 - advertisement-curadrobe-3 - thankyous: - - vending-machine-thanks diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/detdrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/detdrobe.yml index e64b5198692..50024ce3d99 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/detdrobe.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/detdrobe.yml @@ -1,8 +1,6 @@ -- type: advertisementsPack +- type: messagePack id: DetDrobeAds - advertisements: + messages: - advertisement-detdrobe-1 - advertisement-detdrobe-2 - advertisement-detdrobe-3 - thankyous: - - vending-machine-thanks diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/dinnerware.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/dinnerware.yml index ebaba26b6f4..ac31dc075b9 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/dinnerware.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/dinnerware.yml @@ -1,6 +1,6 @@ -- type: advertisementsPack +- type: messagePack id: DinnerwareAds - advertisements: + messages: - advertisement-dinnerware-1 - advertisement-dinnerware-2 - advertisement-dinnerware-3 @@ -11,5 +11,3 @@ - advertisement-dinnerware-8 - advertisement-dinnerware-9 - advertisement-dinnerware-10 - thankyous: - - vending-machine-thanks diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/discount.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/discount.yml index 5260e2bbcc6..dce8646a60c 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/discount.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/discount.yml @@ -1,6 +1,6 @@ -- type: advertisementsPack +- type: messagePack id: DiscountDansAds - advertisements: + messages: - advertisement-discount-1 - advertisement-discount-2 - advertisement-discount-3 @@ -10,13 +10,3 @@ - advertisement-discount-7 - advertisement-discount-8 - advertisement-discount-9 - thankyous: - - vending-machine-thanks - - thankyou-discount-1 - - thankyou-discount-2 - - thankyou-discount-3 - - thankyou-discount-4 - - thankyou-discount-5 - - thankyou-discount-6 - - thankyou-discount-7 - - thankyou-discount-8 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/donut.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/donut.yml index 29ea90e7655..73766aa749f 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/donut.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/donut.yml @@ -1,14 +1,8 @@ -- type: advertisementsPack +- type: messagePack id: DonutAds - advertisements: + messages: - advertisement-donut-1 - advertisement-donut-2 - advertisement-donut-3 - advertisement-donut-4 - advertisement-donut-5 - thankyous: - - vending-machine-thanks - - thankyou-donut-1 - - thankyou-donut-2 - - thankyou-donut-3 - - thankyou-donut-4 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/engidrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/engidrobe.yml index 5d1290147ee..ec92fbe4dea 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/engidrobe.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/engidrobe.yml @@ -1,10 +1,8 @@ -- type: advertisementsPack +- type: messagePack id: EngiDrobeAds - advertisements: + messages: - advertisement-engidrobe-1 - advertisement-engidrobe-2 - advertisement-engidrobe-3 - advertisement-engidrobe-4 - advertisement-engidrobe-5 - thankyous: - - vending-machine-thanks diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/fatextractor.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/fatextractor.yml index ad100c46263..7619ea1856c 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/fatextractor.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/fatextractor.yml @@ -1,11 +1,9 @@ -- type: advertisementsPack +- type: messagePack id: FatExtractorFacts - advertisements: + messages: - fat-extractor-fact-1 - fat-extractor-fact-2 - fat-extractor-fact-3 - fat-extractor-fact-4 - fat-extractor-fact-5 - fat-extractor-fact-6 - thankyous: - - vending-machine-thanks diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/games.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/games.yml index e640b378def..1348635e1f5 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/games.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/games.yml @@ -1,6 +1,6 @@ -- type: advertisementsPack +- type: messagePack id: GoodCleanFunAds - advertisements: + messages: - advertisement-goodcleanfun-1 - advertisement-goodcleanfun-2 - advertisement-goodcleanfun-3 @@ -11,9 +11,3 @@ - advertisement-goodcleanfun-8 - advertisement-goodcleanfun-9 - advertisement-goodcleanfun-10 - thankyous: - - vending-machine-thanks - - thankyou-goodcleanfun-1 - - thankyou-goodcleanfun-2 - - thankyou-goodcleanfun-3 - - thankyou-goodcleanfun-4 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/genedrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/genedrobe.yml index 71d56bdcccc..722388055b9 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/genedrobe.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/genedrobe.yml @@ -1,7 +1,5 @@ -- type: advertisementsPack +- type: messagePack id: GeneDrobeAds - advertisements: + messages: - advertisement-genedrobe-1 - advertisement-genedrobe-2 - thankyous: - - vending-machine-thanks diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/happyhonk.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/happyhonk.yml index 737ad02e291..e145ebcdacb 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/happyhonk.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/happyhonk.yml @@ -1,6 +1,6 @@ -- type: advertisementsPack +- type: messagePack id: HappyHonkAds - advertisements: + messages: - advertisement-happyhonk-1 - advertisement-happyhonk-2 - advertisement-happyhonk-3 @@ -11,9 +11,3 @@ - advertisement-happyhonk-8 - advertisement-happyhonk-9 - advertisement-happyhonk-10 - thankyous: - - vending-machine-thanks - - thankyou-happyhonk-1 - - thankyou-happyhonk-2 - - thankyou-happyhonk-3 - - thankyou-happyhonk-4 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/hydrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/hydrobe.yml index 213097db0d9..5999c496f51 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/hydrobe.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/hydrobe.yml @@ -1,9 +1,7 @@ -- type: advertisementsPack +- type: messagePack id: HyDrobeAds - advertisements: + messages: - advertisement-hydrobe-1 - advertisement-hydrobe-2 - advertisement-hydrobe-3 - advertisement-hydrobe-4 - thankyous: - - vending-machine-thanks diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/janidrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/janidrobe.yml index 2d1ee1e7dfb..8310136bf8f 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/janidrobe.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/janidrobe.yml @@ -1,8 +1,6 @@ -- type: advertisementsPack +- type: messagePack id: JaniDrobeAds - advertisements: + messages: - advertisement-janidrobe-1 - advertisement-janidrobe-2 - advertisement-janidrobe-3 - thankyous: - - vending-machine-thanks diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/lawdrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/lawdrobe.yml index 5f3892d0f95..a948413abde 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/lawdrobe.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/lawdrobe.yml @@ -1,6 +1,6 @@ -- type: advertisementsPack +- type: messagePack id: LawDrobeAds - advertisements: + messages: - advertisement-lawdrobe-1 - advertisement-lawdrobe-2 - advertisement-lawdrobe-3 @@ -9,10 +9,3 @@ - advertisement-lawdrobe-6 - advertisement-lawdrobe-7 - advertisement-lawdrobe-8 - thankyous: - - vending-machine-thanks - - thankyou-lawdrobe-1 - - thankyou-lawdrobe-2 - - thankyou-lawdrobe-3 - - thankyou-lawdrobe-4 - - thankyou-lawdrobe-5 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/magivend.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/magivend.yml index 5785f04bac6..896a3853e7c 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/magivend.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/magivend.yml @@ -1,6 +1,6 @@ -- type: advertisementsPack +- type: messagePack id: MagiVendAds - advertisements: + messages: - advertisement-magivend-1 - advertisement-magivend-2 - advertisement-magivend-3 @@ -12,5 +12,3 @@ - advertisement-magivend-9 - advertisement-magivend-10 - advertisement-magivend-11 - thankyous: - - vending-machine-thanks diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/medidrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/medidrobe.yml index f5744722e47..b7b055231b8 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/medidrobe.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/medidrobe.yml @@ -1,8 +1,6 @@ -- type: advertisementsPack +- type: messagePack id: MediDrobeAds - advertisements: + messages: - advertisement-medidrobe-1 - advertisement-medidrobe-2 - advertisement-medidrobe-3 - thankyous: - - vending-machine-thanks diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/megaseed.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/megaseed.yml index 77babcf55a3..b6e6ae098e9 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/megaseed.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/megaseed.yml @@ -1,11 +1,9 @@ -- type: advertisementsPack +- type: messagePack id: MegaSeedAds - advertisements: + messages: - advertisement-megaseed-1 - advertisement-megaseed-2 - advertisement-megaseed-3 - advertisement-megaseed-4 - advertisement-megaseed-5 - advertisement-megaseed-6 - thankyous: - - vending-machine-thanks diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/nanomed.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/nanomed.yml index 3e710bf7bee..a79a4c8a6c4 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/nanomed.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/nanomed.yml @@ -1,6 +1,6 @@ -- type: advertisementsPack +- type: messagePack id: NanoMedAds - advertisements: + messages: - advertisement-nanomed-1 - advertisement-nanomed-2 - advertisement-nanomed-3 @@ -10,5 +10,3 @@ - advertisement-nanomed-7 - advertisement-nanomed-8 - advertisement-nanomed-9 - thankyous: - - vending-machine-thanks diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/nutrimax.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/nutrimax.yml index 40a39336eea..a3ade349602 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/nutrimax.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/nutrimax.yml @@ -1,6 +1,6 @@ -- type: advertisementsPack +- type: messagePack id: NutriMaxAds - advertisements: + messages: - advertisement-nutrimax-1 - advertisement-nutrimax-2 - advertisement-nutrimax-3 @@ -8,7 +8,3 @@ - advertisement-nutrimax-5 - advertisement-nutrimax-6 - advertisement-nutrimax-7 - thankyous: - - vending-machine-thanks - - thankyou-nutrimax-1 - - thankyou-nutrimax-2 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/robodrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/robodrobe.yml index f4a8f14fee6..82ece7d7632 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/robodrobe.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/robodrobe.yml @@ -1,9 +1,7 @@ -- type: advertisementsPack +- type: messagePack id: RoboDrobeAds - advertisements: + messages: - advertisement-robodrobe-1 - advertisement-robodrobe-2 - advertisement-robodrobe-3 - advertisement-robodrobe-4 - thankyous: - - vending-machine-thanks diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/scidrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/scidrobe.yml index 9a3a2243606..f8b125073b8 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/scidrobe.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/scidrobe.yml @@ -1,8 +1,6 @@ -- type: advertisementsPack +- type: messagePack id: SciDrobeAds - advertisements: + messages: - advertisement-scidrobe-1 - advertisement-scidrobe-2 - advertisement-scidrobe-3 - thankyous: - - vending-machine-thanks diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/secdrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/secdrobe.yml index 886856c153e..99c2c402dee 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/secdrobe.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/secdrobe.yml @@ -1,10 +1,8 @@ -- type: advertisementsPack +- type: messagePack id: SecDrobeAds - advertisements: + messages: - advertisement-secdrobe-1 - advertisement-secdrobe-2 - advertisement-secdrobe-3 - advertisement-secdrobe-4 - advertisement-secdrobe-5 - thankyous: - - vending-machine-thanks diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/sectech.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/sectech.yml index 8b9f9865727..0b32ed166d5 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/sectech.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/sectech.yml @@ -1,13 +1,8 @@ -- type: advertisementsPack +- type: messagePack id: SecTechAds - advertisements: + messages: - advertisement-sectech-1 - advertisement-sectech-2 - advertisement-sectech-3 - advertisement-sectech-4 - advertisement-sectech-5 - thankyous: - - vending-machine-thanks - - thankyou-sectech-1 - - thankyou-sectech-2 - - thankyou-sectech-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/smartfridge.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/smartfridge.yml index e7c20761a6a..d92a95f70ff 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/smartfridge.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/smartfridge.yml @@ -1,6 +1,6 @@ -- type: advertisementsPack +- type: messagePack id: SmartFridgeAds - advertisements: + messages: - advertisement-smartfridge-1 - advertisement-smartfridge-2 - advertisement-smartfridge-3 @@ -9,5 +9,3 @@ - advertisement-smartfridge-6 - advertisement-smartfridge-7 - advertisement-smartfridge-8 - thankyous: - - vending-machine-thanks diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/snack.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/snack.yml index c8f960d9722..3d9b93f9c27 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/snack.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/snack.yml @@ -1,6 +1,6 @@ -- type: advertisementsPack +- type: messagePack id: GetmoreChocolateCorpAds - advertisements: + messages: - advertisement-snack-1 - advertisement-snack-2 - advertisement-snack-3 @@ -16,11 +16,3 @@ - advertisement-snack-13 - advertisement-snack-14 - advertisement-snack-15 - thankyous: - - vending-machine-thanks - - thankyou-snack-1 - - thankyou-snack-2 - - thankyou-snack-3 - - thankyou-snack-4 - - thankyou-snack-5 - - thankyou-snack-6 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/sovietsoda.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/sovietsoda.yml index ff495701531..630de1e615f 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/sovietsoda.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/sovietsoda.yml @@ -1,14 +1,9 @@ -- type: advertisementsPack +- type: messagePack id: BodaAds - advertisements: + messages: - advertisement-sovietsoda-1 - advertisement-sovietsoda-2 - advertisement-sovietsoda-3 - advertisement-sovietsoda-4 - advertisement-sovietsoda-5 - advertisement-sovietsoda-6 - thankyous: - - vending-machine-thanks - - thankyou-sovietsoda-1 - - thankyou-sovietsoda-2 - - thankyou-sovietsoda-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/syndiedrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/syndiedrobe.yml index c9e3bbfe5c4..dc89d04aa0b 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/syndiedrobe.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/syndiedrobe.yml @@ -1,6 +1,6 @@ -- type: advertisementsPack +- type: messagePack id: SyndieDrobeAds - advertisements: + messages: - advertisement-syndiedrobe-1 - advertisement-syndiedrobe-2 - advertisement-syndiedrobe-3 @@ -32,10 +32,3 @@ - advertisement-syndiedrobe-29 - advertisement-syndiedrobe-30 - advertisement-syndiedrobe-31 - thankyous: - - vending-machine-thanks - - thankyou-syndiedrobe-1 - - thankyou-syndiedrobe-2 - - thankyou-syndiedrobe-3 - - thankyou-syndiedrobe-4 - - thankyou-syndiedrobe-5 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/theater.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/theater.yml index 090ec81cffb..8b06cdb5172 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/theater.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/theater.yml @@ -1,11 +1,9 @@ -- type: advertisementsPack +- type: messagePack id: AutoDrobeAds - advertisements: + messages: - advertisement-theater-1 - advertisement-theater-2 - advertisement-theater-3 - advertisement-theater-4 - advertisement-theater-5 - advertisement-theater-6 - thankyous: - - vending-machine-thanks diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/vendomat.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/vendomat.yml index aaa0c411336..31c0e0c2411 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/vendomat.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/vendomat.yml @@ -1,6 +1,6 @@ -- type: advertisementsPack +- type: messagePack id: VendomatAds - advertisements: + messages: - advertisement-vendomat-1 - advertisement-vendomat-2 - advertisement-vendomat-3 @@ -8,5 +8,3 @@ - advertisement-vendomat-5 - advertisement-vendomat-6 - advertisement-vendomat-7 - thankyous: - - vending-machine-thanks diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/virodrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/virodrobe.yml index a64bcd824c6..c48f9e7d2e4 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/virodrobe.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/virodrobe.yml @@ -1,8 +1,6 @@ -- type: advertisementsPack +- type: messagePack id: ViroDrobeAds - advertisements: + messages: - advertisement-virodrobe-1 - advertisement-virodrobe-2 - advertisement-virodrobe-3 - thankyous: - - vending-machine-thanks diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/boozeomat.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/boozeomat.yml new file mode 100644 index 00000000000..e5c96887b97 --- /dev/null +++ b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/boozeomat.yml @@ -0,0 +1,7 @@ +- type: messagePack + id: BoozeOMatGoodbyes + messages: + - vending-machine-thanks + - thankyou-boozeomat-1 + - thankyou-boozeomat-2 + - thankyou-boozeomat-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/chang.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/chang.yml new file mode 100644 index 00000000000..a348d8fa9c1 --- /dev/null +++ b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/chang.yml @@ -0,0 +1,6 @@ +- type: messagePack + id: ChangGoodbyes + messages: + - vending-machine-thanks + - thankyou-chang-1 + - thankyou-chang-2 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/chefvend.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/chefvend.yml new file mode 100644 index 00000000000..93249586e10 --- /dev/null +++ b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/chefvend.yml @@ -0,0 +1,8 @@ +- type: messagePack + id: ChefvendGoodbyes + messages: + - vending-machine-thanks + - thankyou-chefvend-1 + - thankyou-chefvend-2 + - thankyou-chefvend-3 + - thankyou-chefvend-4 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/cigs.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/cigs.yml new file mode 100644 index 00000000000..108a34a4722 --- /dev/null +++ b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/cigs.yml @@ -0,0 +1,7 @@ +- type: messagePack + id: CigaretteMachineGoodbyes + messages: + - vending-machine-thanks + - thankyou-cigs-1 + - thankyou-cigs-2 + - thankyou-cigs-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/coffee.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/coffee.yml new file mode 100644 index 00000000000..6f2aef01b8c --- /dev/null +++ b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/coffee.yml @@ -0,0 +1,8 @@ +- type: messagePack + id: HotDrinksMachineGoodbyes + messages: + - vending-machine-thanks + - thankyou-coffee-1 + - thankyou-coffee-2 + - thankyou-coffee-3 + - thankyou-coffee-4 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/cola.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/cola.yml new file mode 100644 index 00000000000..76f00fddaf7 --- /dev/null +++ b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/cola.yml @@ -0,0 +1,8 @@ +- type: messagePack + id: RobustSoftdrinksGoodbyes + messages: + - vending-machine-thanks + - thankyou-cola-1 + - thankyou-cola-2 + - thankyou-cola-3 + - thankyou-cola-4 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/discount.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/discount.yml new file mode 100644 index 00000000000..d3f7d89a93d --- /dev/null +++ b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/discount.yml @@ -0,0 +1,12 @@ +- type: messagePack + id: DiscountDansGoodbyes + messages: + - vending-machine-thanks + - thankyou-discount-1 + - thankyou-discount-2 + - thankyou-discount-3 + - thankyou-discount-4 + - thankyou-discount-5 + - thankyou-discount-6 + - thankyou-discount-7 + - thankyou-discount-8 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/donut.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/donut.yml new file mode 100644 index 00000000000..3c28741e3b6 --- /dev/null +++ b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/donut.yml @@ -0,0 +1,8 @@ +- type: messagePack + id: DonutGoodbyes + messages: + - vending-machine-thanks + - thankyou-donut-1 + - thankyou-donut-2 + - thankyou-donut-3 + - thankyou-donut-4 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/games.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/games.yml new file mode 100644 index 00000000000..5d842619bb3 --- /dev/null +++ b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/games.yml @@ -0,0 +1,8 @@ +- type: messagePack + id: GoodCleanFunGoodbyes + messages: + - vending-machine-thanks + - thankyou-goodcleanfun-1 + - thankyou-goodcleanfun-2 + - thankyou-goodcleanfun-3 + - thankyou-goodcleanfun-4 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/generic.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/generic.yml new file mode 100644 index 00000000000..98a2d7d17a2 --- /dev/null +++ b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/generic.yml @@ -0,0 +1,4 @@ +- type: messagePack + id: GenericVendGoodbyes + messages: + - vending-machine-thanks diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/happyhonk.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/happyhonk.yml new file mode 100644 index 00000000000..7859d554634 --- /dev/null +++ b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/happyhonk.yml @@ -0,0 +1,8 @@ +- type: messagePack + id: HappyHonkGoodbyes + messages: + - vending-machine-thanks + - thankyou-happyhonk-1 + - thankyou-happyhonk-2 + - thankyou-happyhonk-3 + - thankyou-happyhonk-4 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/lawdrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/lawdrobe.yml new file mode 100644 index 00000000000..56678c10178 --- /dev/null +++ b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/lawdrobe.yml @@ -0,0 +1,8 @@ +- type: messagePack + id: LawDrobeGoodbyes + messages: + - thankyou-lawdrobe-1 + - thankyou-lawdrobe-2 + - thankyou-lawdrobe-3 + - thankyou-lawdrobe-4 + - thankyou-lawdrobe-5 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/nutrimax.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/nutrimax.yml new file mode 100644 index 00000000000..3568ae30314 --- /dev/null +++ b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/nutrimax.yml @@ -0,0 +1,6 @@ +- type: messagePack + id: NutriMaxGoodbyes + messages: + - vending-machine-thanks + - thankyou-nutrimax-1 + - thankyou-nutrimax-2 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/sectech.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/sectech.yml new file mode 100644 index 00000000000..89096934738 --- /dev/null +++ b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/sectech.yml @@ -0,0 +1,7 @@ +- type: messagePack + id: SecTechGoodbyes + messages: + - vending-machine-thanks + - thankyou-sectech-1 + - thankyou-sectech-2 + - thankyou-sectech-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/snack.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/snack.yml new file mode 100644 index 00000000000..bd86e074c6b --- /dev/null +++ b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/snack.yml @@ -0,0 +1,10 @@ +- type: messagePack + id: GetmoreChocolateCorpGoodbyes + messages: + - vending-machine-thanks + - thankyou-snack-1 + - thankyou-snack-2 + - thankyou-snack-3 + - thankyou-snack-4 + - thankyou-snack-5 + - thankyou-snack-6 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/sovietsoda.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/sovietsoda.yml new file mode 100644 index 00000000000..01ab25a7ad4 --- /dev/null +++ b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/sovietsoda.yml @@ -0,0 +1,7 @@ +- type: messagePack + id: BodaGoodbyes + messages: + - vending-machine-thanks + - thankyou-sovietsoda-1 + - thankyou-sovietsoda-2 + - thankyou-sovietsoda-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/syndiedrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/syndiedrobe.yml new file mode 100644 index 00000000000..1246eb3089d --- /dev/null +++ b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/syndiedrobe.yml @@ -0,0 +1,9 @@ +- type: messagePack + id: SyndieDrobeGoodbyes + messages: + - vending-machine-thanks + - thankyou-syndiedrobe-1 + - thankyou-syndiedrobe-2 + - thankyou-syndiedrobe-3 + - thankyou-syndiedrobe-4 + - thankyou-syndiedrobe-5 diff --git a/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml b/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml index 2c4fccb8b3e..29977f8cdd4 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml @@ -140,6 +140,8 @@ density: 190 - type: Advertise pack: CondimentVendAds + - type: SpeakOnUIClosed + pack: GenericVendGoodbyes - type: Speech - type: Transform noRot: false @@ -157,6 +159,8 @@ normalState: normal-unshaded - type: Advertise pack: AmmoVendAds + - type: SpeakOnUIClosed + pack: GenericVendGoodbyes - type: Speech - type: Sprite sprite: Structures/Machines/VendingMachines/ammo.rsi @@ -184,6 +188,8 @@ loopDeny: false - type: Advertise pack: BoozeOMatAds + - type: SpeakOnUIClosed + pack: BoozeOMatGoodbyes - type: Speech - type: Sprite sprite: Structures/Machines/VendingMachines/boozeomat.rsi @@ -253,6 +259,8 @@ ejectState: eject-unshaded - type: Advertise pack: ChefvendAds + - type: SpeakOnUIClosed + pack: ChefvendGoodbyes - type: Sprite sprite: Structures/Machines/VendingMachines/chefvend.rsi layers: @@ -289,6 +297,8 @@ denyState: deny-unshaded - type: Advertise pack: CigaretteMachineAds + - type: SpeakOnUIClosed + pack: CigaretteMachineGoodbyes - type: Speech - type: Sprite noRot: true @@ -321,6 +331,8 @@ denyState: deny-unshaded - type: Advertise pack: ClothesMateAds + - type: SpeakOnUIClosed + pack: GenericVendGoodbyes - type: Speech - type: Sprite sprite: Structures/Machines/VendingMachines/clothing.rsi @@ -353,6 +365,8 @@ denyState: deny-unshaded - type: Advertise pack: ClothesMateAds + - type: SpeakOnUIClosed + pack: GenericVendGoodbyes - type: Speech - type: Sprite sprite: Structures/Machines/VendingMachines/winterdrobe.rsi @@ -392,6 +406,8 @@ initialStockQuality: 0.33 - type: Advertise pack: HotDrinksMachineAds + - type: SpeakOnUIClosed + pack: HotDrinksMachineGoodbyes - type: Speech - type: Sprite noRot: true @@ -438,6 +454,8 @@ initialStockQuality: 0.33 - type: Advertise pack: RobustSoftdrinksAds + - type: SpeakOnUIClosed + pack: RobustSoftdrinksGoodbyes - type: Speech - type: Sprite noRot: true @@ -653,6 +671,8 @@ initialStockQuality: 0.33 - type: Advertise pack: RobustSoftdrinksAds + - type: SpeakOnUIClosed + pack: GenericVendGoodbyes - type: Speech - type: Sprite sprite: Structures/Machines/VendingMachines/pwrgame.rsi @@ -690,6 +710,8 @@ initialStockQuality: 0.33 - type: Advertise pack: RobustSoftdrinksAds + - type: SpeakOnUIClosed + pack: GenericVendGoodbyes - type: Speech - type: Sprite sprite: Structures/Machines/VendingMachines/gib.rsi @@ -723,6 +745,8 @@ denyState: deny-unshaded - type: Advertise pack: DinnerwareAds + - type: SpeakOnUIClosed + pack: GenericVendGoodbyes - type: Sprite noRot: true sprite: Structures/Machines/VendingMachines/dinnerware.rsi @@ -763,6 +787,8 @@ ejectState: eject-unshaded - type: Advertise pack: MagiVendAds + - type: SpeakOnUIClosed + pack: GenericVendGoodbyes - type: Sprite noRot: true sprite: Structures/Machines/VendingMachines/magivend.rsi @@ -802,6 +828,8 @@ initialStockQuality: 0.33 - type: Advertise pack: DiscountDansAds + - type: SpeakOnUIClosed + pack: DiscountDansGoodbyes - type: Speech - type: Sprite sprite: Structures/Machines/VendingMachines/discount.rsi @@ -874,6 +902,8 @@ ejectDelay: 0.6 - type: Advertise pack: NanoMedAds + - type: SpeakOnUIClosed + pack: GenericVendGoodbyes - type: Sprite sprite: Structures/Machines/VendingMachines/medivend.rsi noRot: true @@ -917,6 +947,8 @@ denyState: deny-unshaded - type: Advertise pack: NutriMaxAds + - type: SpeakOnUIClosed + pack: NutriMaxGoodbyes - type: Sprite sprite: Structures/Machines/VendingMachines/nutri_green.rsi layers: @@ -951,6 +983,8 @@ denyState: deny-unshaded - type: Advertise pack: SecTechAds + - type: SpeakOnUIClosed + pack: SecTechGoodbyes - type: Sprite sprite: Structures/Machines/VendingMachines/security.rsi noRot: true @@ -995,6 +1029,8 @@ denyState: deny-unshaded - type: Advertise pack: MegaSeedAds + - type: SpeakOnUIClosed + pack: GenericVendGoodbyes - type: Sprite sprite: Structures/Machines/VendingMachines/seeds_green.rsi layers: @@ -1038,6 +1074,8 @@ initialStockQuality: 0.33 - type: Advertise pack: GetmoreChocolateCorpAds + - type: SpeakOnUIClosed + pack: GetmoreChocolateCorpGoodbyes - type: Speech - type: Sprite noRot: true @@ -1200,6 +1238,8 @@ initialStockQuality: 0.33 - type: Advertise pack: BodaAds + - type: SpeakOnUIClosed + pack: BodaGoodbyes - type: Speech - type: Sprite sprite: Structures/Machines/VendingMachines/sodasoviet.rsi @@ -1240,6 +1280,8 @@ screenState: screen - type: Advertise pack: AutoDrobeAds + - type: SpeakOnUIClosed + pack: GenericVendGoodbyes - type: Speech - type: Sprite sprite: Structures/Machines/VendingMachines/theater.rsi @@ -1282,6 +1324,8 @@ denyState: deny-unshaded - type: Advertise pack: VendomatAds + - type: SpeakOnUIClosed + pack: GenericVendGoodbyes - type: Speech - type: Sprite noRot: true @@ -1321,6 +1365,8 @@ denyState: deny-unshaded - type: Advertise pack: VendomatAds + - type: SpeakOnUIClosed + pack: GenericVendGoodbyes - type: Speech - type: Sprite noRot: true @@ -1401,6 +1447,8 @@ ejectDelay: 1.8 - type: Advertise pack: GoodCleanFunAds + - type: SpeakOnUIClosed + pack: GoodCleanFunGoodbyes - type: Sprite noRot: true sprite: Structures/Machines/VendingMachines/games.rsi @@ -1442,6 +1490,8 @@ initialStockQuality: 0.33 - type: Advertise pack: ChangAds + - type: SpeakOnUIClosed + pack: ChangGoodbyes - type: Speech - type: Sprite noRot: true @@ -1516,6 +1566,8 @@ initialStockQuality: 0.33 - type: Advertise pack: DonutAds + - type: SpeakOnUIClosed + pack: DonutGoodbyes - type: Speech - type: Sprite sprite: Structures/Machines/VendingMachines/donut.rsi @@ -1616,6 +1668,8 @@ normalState: normal-unshaded - type: Advertise pack: HyDrobeAds + - type: SpeakOnUIClosed + pack: GenericVendGoodbyes - type: Sprite sprite: Structures/Machines/VendingMachines/hydrobe.rsi layers: @@ -1642,6 +1696,8 @@ normalState: normal-unshaded - type: Advertise pack: LawDrobeAds + - type: SpeakOnUIClosed + pack: LawDrobeGoodbyes - type: Sprite sprite: Structures/Machines/VendingMachines/lawdrobe.rsi layers: @@ -1668,6 +1724,8 @@ normalState: normal-unshaded - type: Advertise pack: SecDrobeAds + - type: SpeakOnUIClosed + pack: GenericVendGoodbyes - type: Sprite sprite: Structures/Machines/VendingMachines/secdrobe.rsi layers: @@ -1694,6 +1752,8 @@ normalState: normal-unshaded - type: Advertise pack: BarDrobeAds + - type: SpeakOnUIClosed + pack: GenericVendGoodbyes - type: Sprite sprite: Structures/Machines/VendingMachines/bardrobe.rsi layers: @@ -1750,6 +1810,8 @@ normalState: normal-unshaded - type: Advertise pack: CargoDrobeAds + - type: SpeakOnUIClosed + pack: GenericVendGoodbyes - type: Sprite sprite: Structures/Machines/VendingMachines/cargodrobe.rsi layers: @@ -1776,6 +1838,8 @@ normalState: normal-unshaded - type: Advertise pack: MediDrobeAds + - type: SpeakOnUIClosed + pack: GenericVendGoodbyes - type: Sprite sprite: Structures/Machines/VendingMachines/medidrobe.rsi layers: @@ -1802,6 +1866,8 @@ normalState: normal-unshaded - type: Advertise pack: ChemDrobeAds + - type: SpeakOnUIClosed + pack: GenericVendGoodbyes - type: Sprite sprite: Structures/Machines/VendingMachines/chemdrobe.rsi layers: @@ -1828,6 +1894,8 @@ normalState: normal-unshaded - type: Advertise pack: CuraDrobeAds + - type: SpeakOnUIClosed + pack: GenericVendGoodbyes - type: Sprite sprite: Structures/Machines/VendingMachines/curadrobe.rsi layers: @@ -1854,6 +1922,8 @@ normalState: normal-unshaded - type: Advertise pack: AtmosDrobeAds + - type: SpeakOnUIClosed + pack: GenericVendGoodbyes - type: Sprite sprite: Structures/Machines/VendingMachines/atmosdrobe.rsi layers: @@ -1880,6 +1950,8 @@ normalState: normal-unshaded - type: Advertise pack: EngiDrobeAds + - type: SpeakOnUIClosed + pack: GenericVendGoodbyes - type: Sprite sprite: Structures/Machines/VendingMachines/engidrobe.rsi layers: @@ -1906,6 +1978,8 @@ normalState: normal-unshaded - type: Advertise pack: ChefDrobeAds + - type: SpeakOnUIClosed + pack: GenericVendGoodbyes - type: Sprite sprite: Structures/Machines/VendingMachines/chefdrobe.rsi layers: @@ -1932,6 +2006,8 @@ normalState: normal-unshaded - type: Advertise pack: DetDrobeAds + - type: SpeakOnUIClosed + pack: GenericVendGoodbyes - type: Sprite sprite: Structures/Machines/VendingMachines/detdrobe.rsi layers: @@ -1960,6 +2036,8 @@ ejectState: eject-unshaded - type: Advertise pack: JaniDrobeAds + - type: SpeakOnUIClosed + pack: GenericVendGoodbyes - type: Sprite sprite: Structures/Machines/VendingMachines/lavatory.rsi layers: @@ -1992,6 +2070,8 @@ normalState: normal-unshaded - type: Advertise pack: SciDrobeAds + - type: SpeakOnUIClosed + pack: GenericVendGoodbyes - type: Sprite sprite: Structures/Machines/VendingMachines/scidrobe.rsi layers: @@ -2018,6 +2098,8 @@ normalState: normal-unshaded - type: Advertise pack: SyndieDrobeAds + - type: SpeakOnUIClosed + pack: SyndieDrobeGoodbyes - type: Sprite sprite: Structures/Machines/VendingMachines/syndiedrobe.rsi layers: @@ -2044,6 +2126,8 @@ normalState: normal-unshaded - type: Advertise pack: RoboDrobeAds + - type: SpeakOnUIClosed + pack: GenericVendGoodbyes - type: Sprite sprite: Structures/Machines/VendingMachines/robodrobe.rsi layers: @@ -2070,6 +2154,8 @@ normalState: normal-unshaded - type: Advertise pack: GeneDrobeAds + - type: SpeakOnUIClosed + pack: GenericVendGoodbyes - type: Sprite sprite: Structures/Machines/VendingMachines/genedrobe.rsi layers: @@ -2096,6 +2182,8 @@ normalState: normal-unshaded - type: Advertise pack: ViroDrobeAds + - type: SpeakOnUIClosed + pack: GenericVendGoodbyes - type: Sprite sprite: Structures/Machines/VendingMachines/virodrobe.rsi layers: @@ -2174,6 +2262,8 @@ autoRot: true - type: Advertise pack: HappyHonkAds + - type: SpeakOnUIClosed + pack: HappyHonkGoodbyes - type: AccessReader access: [["Kitchen"], ["Theatre"]] From da8fb58dfa1a71886459bb5d5294dd5b6da765d8 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Thu, 28 Mar 2024 22:12:36 +1100 Subject: [PATCH 019/295] Fix master (#26501) * Fix master * this * messages * Fix missing verb name parrot * Fix messagePack for blockgame and spacevillain --------- Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> --- .../Arcade/BlockGame/BlockGameArcadeSystem.cs | 4 ++- .../SpaceVillainArcadeSystem.cs | 4 ++- .../en-US/chat/managers/chat-manager.ftl | 1 + .../Arcade/Advertisements/blockgame.yml | 30 +++++++++---------- .../Arcade/Advertisements/spacevillain.yml | 30 +++++++++---------- Resources/Prototypes/Voice/speech_verbs.yml | 1 + 6 files changed, 38 insertions(+), 32 deletions(-) diff --git a/Content.Server/Arcade/BlockGame/BlockGameArcadeSystem.cs b/Content.Server/Arcade/BlockGame/BlockGameArcadeSystem.cs index 0d9487dab85..96001a484bc 100644 --- a/Content.Server/Arcade/BlockGame/BlockGameArcadeSystem.cs +++ b/Content.Server/Arcade/BlockGame/BlockGameArcadeSystem.cs @@ -1,6 +1,8 @@ using Content.Server.Power.Components; using Content.Shared.UserInterface; using Content.Server.Advertise; +using Content.Server.Advertise.Components; +using Content.Server.Advertise.EntitySystems; using Content.Shared.Arcade; using Robust.Server.GameObjects; using Robust.Shared.Player; @@ -96,7 +98,7 @@ private void OnAfterUiClose(EntityUid uid, BlockGameArcadeComponent component, B component.Player = null; if (component.ShouldSayThankYou && TryComp(uid, out var advertise)) { - _advertise.SayThankYou(uid, advertise); + _advertise.SayAdvertisement(uid, advertise); component.ShouldSayThankYou = false; } } diff --git a/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs b/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs index 24fa6e32d19..4d704b8a811 100644 --- a/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs +++ b/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs @@ -1,6 +1,8 @@ using Content.Server.Power.Components; using Content.Shared.UserInterface; using Content.Server.Advertise; +using Content.Server.Advertise.Components; +using Content.Server.Advertise.EntitySystems; using static Content.Shared.Arcade.SharedSpaceVillainArcadeComponent; using Robust.Server.GameObjects; using Robust.Shared.Audio; @@ -121,7 +123,7 @@ private void OnBoundUIClosed(Entity ent, ref BoundU if (ent.Comp.ShouldSayThankYou && TryComp(ent.Owner, out var advertise)) { - _advertise.SayThankYou(ent.Owner, advertise); + _advertise.SayAdvertisement(ent.Owner, advertise); ent.Comp.ShouldSayThankYou = false; } } diff --git a/Resources/Locale/en-US/chat/managers/chat-manager.ftl b/Resources/Locale/en-US/chat/managers/chat-manager.ftl index 1a0648579e7..6fbeb83ed41 100644 --- a/Resources/Locale/en-US/chat/managers/chat-manager.ftl +++ b/Resources/Locale/en-US/chat/managers/chat-manager.ftl @@ -131,6 +131,7 @@ chat-speech-verb-monkey-2 = screeches chat-speech-verb-name-cluwne = Cluwne +chat-speech-verb-name-parrot = Parrot chat-speech-verb-parrot-1 = squawks chat-speech-verb-parrot-2 = tweets chat-speech-verb-parrot-3 = chirps diff --git a/Resources/Prototypes/Catalog/Arcade/Advertisements/blockgame.yml b/Resources/Prototypes/Catalog/Arcade/Advertisements/blockgame.yml index efcb8934a80..226f0cb72b6 100644 --- a/Resources/Prototypes/Catalog/Arcade/Advertisements/blockgame.yml +++ b/Resources/Prototypes/Catalog/Arcade/Advertisements/blockgame.yml @@ -1,6 +1,6 @@ -- type: advertisementsPack +- type: messagePack id: BlockGameAds - advertisements: + messages: - advertisement-block-game-1 - advertisement-block-game-2 - advertisement-block-game-3 @@ -14,16 +14,16 @@ - advertisement-block-game-11 - advertisement-block-game-12 - advertisement-block-game-13 - thankyous: - - thankyou-block-game-1 - - thankyou-block-game-2 - - thankyou-block-game-3 - - thankyou-block-game-4 - - thankyou-block-game-5 - - thankyou-block-game-6 - - thankyou-block-game-7 - - thankyou-block-game-8 - - thankyou-block-game-9 - - thankyou-block-game-10 - - thankyou-block-game-11 - - thankyou-block-game-12 +# thankyous: +# - thankyou-block-game-1 +# - thankyou-block-game-2 +# - thankyou-block-game-3 +# - thankyou-block-game-4 +# - thankyou-block-game-5 +# - thankyou-block-game-6 +# - thankyou-block-game-7 +# - thankyou-block-game-8 +# - thankyou-block-game-9 +# - thankyou-block-game-10 +# - thankyou-block-game-11 +# - thankyou-block-game-12 diff --git a/Resources/Prototypes/Catalog/Arcade/Advertisements/spacevillain.yml b/Resources/Prototypes/Catalog/Arcade/Advertisements/spacevillain.yml index 98063a62ddd..bee4ca2baec 100644 --- a/Resources/Prototypes/Catalog/Arcade/Advertisements/spacevillain.yml +++ b/Resources/Prototypes/Catalog/Arcade/Advertisements/spacevillain.yml @@ -1,6 +1,6 @@ -- type: advertisementsPack +- type: messagePack id: SpaceVillainAds - advertisements: + messages: - advertisement-space-villain-1 - advertisement-space-villain-2 - advertisement-space-villain-3 @@ -16,16 +16,16 @@ - advertisement-space-villain-13 - advertisement-space-villain-14 - advertisement-space-villain-15 - thankyous: - - thankyou-space-villain-1 - - thankyou-space-villain-2 - - thankyou-space-villain-3 - - thankyou-space-villain-4 - - thankyou-space-villain-5 - - thankyou-space-villain-6 - - thankyou-space-villain-7 - - thankyou-space-villain-8 - - thankyou-space-villain-9 - - thankyou-space-villain-10 - - thankyou-space-villain-11 - - thankyou-space-villain-12 +# thankyous: +# - thankyou-space-villain-1 +# - thankyou-space-villain-2 +# - thankyou-space-villain-3 +# - thankyou-space-villain-4 +# - thankyou-space-villain-5 +# - thankyou-space-villain-6 +# - thankyou-space-villain-7 +# - thankyou-space-villain-8 +# - thankyou-space-villain-9 +# - thankyou-space-villain-10 +# - thankyou-space-villain-11 +# - thankyou-space-villain-12 diff --git a/Resources/Prototypes/Voice/speech_verbs.yml b/Resources/Prototypes/Voice/speech_verbs.yml index 43fefe04e31..9879cefb75d 100644 --- a/Resources/Prototypes/Voice/speech_verbs.yml +++ b/Resources/Prototypes/Voice/speech_verbs.yml @@ -133,6 +133,7 @@ - type: speechVerb id: Parrot + name: chat-speech-verb-name-parrot speechVerbStrings: - chat-speech-verb-parrot-1 - chat-speech-verb-parrot-2 From 205d85bb4cf690613b4ee37f418200286724bf1d Mon Sep 17 00:00:00 2001 From: nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com> Date: Thu, 28 Mar 2024 19:19:53 +0000 Subject: [PATCH 020/295] Fix Incorrect stealth messages appearing on readmin. (#26511) Fix major skill issue/Incorrect stealth messages appearing on readmin. --- Content.Server/Administration/Managers/AdminManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/Administration/Managers/AdminManager.cs b/Content.Server/Administration/Managers/AdminManager.cs index 8fb28b7ec4d..bab9c2cb407 100644 --- a/Content.Server/Administration/Managers/AdminManager.cs +++ b/Content.Server/Administration/Managers/AdminManager.cs @@ -150,7 +150,7 @@ public void ReAdmin(ICommonSession session) plyData.ExplicitlyDeadminned = false; reg.Data.Active = true; - if (reg.Data.Stealth) + if (!reg.Data.Stealth) { _chat.SendAdminAnnouncement(Loc.GetString("admin-manager-self-re-admin-message", ("newAdminName", session.Name))); } From 139fb536c44bef5016588fafd4aeccceec85c8a2 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Thu, 28 Mar 2024 16:44:44 -0400 Subject: [PATCH 021/295] Go back to constant electrocution damage based on voltage (#26455) Makes electrocution damage based on the voltage of the wire and bring down the damage to a sane level. It's no longer primarily based on the power being received. LV Cable -> 10 damage MV Cable -> 20 damage HV Cable -> 30 damage Having a primarily power-based damage system causes there to be massive fluctuations in damage based on things outside of a regular player's control, like the station power output. This removes a lot of player agency and turns grilles into a risky gamble where they can either do no damage or instantly fry the player due to simply being hooked up to the engine. While this may be a more accurate simulation in some regards, the reality of the gameplay is that it's often just frustrating, resulting in constant death traps as players brushing against electrified grilles and punching wires take absurd amounts of damage. By making them flat rates, it's possible to actually balance the damage output. --- .../Components/ElectrifiedComponent.cs | 26 +++++++++- .../Components/ElectrocutionComponent.cs | 6 --- .../Electrocution/ElectrocutionSystem.cs | 52 +++++-------------- 3 files changed, 38 insertions(+), 46 deletions(-) diff --git a/Content.Server/Electrocution/Components/ElectrifiedComponent.cs b/Content.Server/Electrocution/Components/ElectrifiedComponent.cs index 2f3def6e061..65a539eb08e 100644 --- a/Content.Server/Electrocution/Components/ElectrifiedComponent.cs +++ b/Content.Server/Electrocution/Components/ElectrifiedComponent.cs @@ -41,8 +41,32 @@ public sealed partial class ElectrifiedComponent : Component [DataField("lowVoltageNode")] public string? LowVoltageNode; + /// + /// Damage multiplier for HV electrocution + /// + [DataField] + public float HighVoltageDamageMultiplier = 3f; + + /// + /// Shock time multiplier for HV electrocution + /// + [DataField] + public float HighVoltageTimeMultiplier = 1.5f; + + /// + /// Damage multiplier for MV electrocution + /// + [DataField] + public float MediumVoltageDamageMultiplier = 2f; + + /// + /// Shock time multiplier for MV electrocution + /// + [DataField] + public float MediumVoltageTimeMultiplier = 1.25f; + [DataField("shockDamage")] - public int ShockDamage = 20; + public float ShockDamage = 7.5f; /// /// Shock time, in seconds. diff --git a/Content.Server/Electrocution/Components/ElectrocutionComponent.cs b/Content.Server/Electrocution/Components/ElectrocutionComponent.cs index 9da78c9134f..7badc852579 100644 --- a/Content.Server/Electrocution/Components/ElectrocutionComponent.cs +++ b/Content.Server/Electrocution/Components/ElectrocutionComponent.cs @@ -15,10 +15,4 @@ public sealed partial class ElectrocutionComponent : Component [DataField("timeLeft")] public float TimeLeft; - - [DataField("accumDamage")] - public float AccumulatedDamage; - - [DataField("baseDamage")] - public float BaseDamage = 20f; } diff --git a/Content.Server/Electrocution/ElectrocutionSystem.cs b/Content.Server/Electrocution/ElectrocutionSystem.cs index d967013f652..11633062829 100644 --- a/Content.Server/Electrocution/ElectrocutionSystem.cs +++ b/Content.Server/Electrocution/ElectrocutionSystem.cs @@ -17,7 +17,6 @@ using Content.Shared.Inventory; using Content.Shared.Jittering; using Content.Shared.Maps; -using Content.Shared.Mobs; using Content.Shared.Popups; using Content.Shared.Speech.EntitySystems; using Content.Shared.StatusEffect; @@ -98,29 +97,18 @@ public override void Update(float frameTime) private void UpdateElectrocutions(float frameTime) { var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var electrocution, out var consumer)) + while (query.MoveNext(out var uid, out var electrocution, out _)) { var timePassed = Math.Min(frameTime, electrocution.TimeLeft); electrocution.TimeLeft -= timePassed; - electrocution.AccumulatedDamage += electrocution.BaseDamage * (consumer.ReceivedPower / consumer.DrawRate) * timePassed; if (!MathHelper.CloseTo(electrocution.TimeLeft, 0)) continue; - if (EntityManager.EntityExists(electrocution.Electrocuting)) - { - // TODO: damage should be scaled by shock damage multiplier - // TODO: better paralyze/jitter timing - var damage = new DamageSpecifier(_prototypeManager.Index(DamageType), (int) electrocution.AccumulatedDamage); + // We tried damage scaling based on power in the past and it really wasn't good. + // Various scaling types didn't fix tiders and HV grilles instantly critting players. - var actual = _damageable.TryChangeDamage(electrocution.Electrocuting, damage, origin: electrocution.Source); - if (actual != null) - { - _adminLogger.Add(LogType.Electrocution, - $"{ToPrettyString(electrocution.Electrocuting):entity} received {actual.GetTotal():damage} powered electrocution damage from {ToPrettyString(electrocution.Source):source}"); - } - } QueueDel(uid); } } @@ -198,7 +186,7 @@ private void OnLightAttacked(EntityUid uid, PoweredLightComponent component, Att if (!_meleeWeapon.GetDamage(args.Used, args.User).Any()) return; - DoCommonElectrocution(args.User, uid, component.UnarmedHitShock, component.UnarmedHitStun, false, 1); + DoCommonElectrocution(args.User, uid, component.UnarmedHitShock, component.UnarmedHitStun, false); } private void OnElectrifiedInteractUsing(EntityUid uid, ElectrifiedComponent electrified, InteractUsingEvent args) @@ -213,16 +201,6 @@ private void OnElectrifiedInteractUsing(EntityUid uid, ElectrifiedComponent elec TryDoElectrifiedAct(uid, args.User, siemens, electrified); } - private float CalculateElectrifiedDamageScale(float power) - { - // A logarithm allows a curve of damage that grows quickly, but slows down dramatically past a value. This keeps the damage to a reasonable range. - const float DamageShift = 1.67f; // Shifts the curve for an overall higher or lower damage baseline - const float CeilingCoefficent = 1.35f; // Adjusts the approach to maximum damage, higher = Higher top damage - const float LogGrowth = 0.00001f; // Adjusts the growth speed of the curve - - return DamageShift + MathF.Log(power * LogGrowth) * CeilingCoefficent; - } - public bool TryDoElectrifiedAct(EntityUid uid, EntityUid targetUid, float siemens = 1, ElectrifiedComponent? electrified = null, @@ -263,19 +241,15 @@ public bool TryDoElectrifiedAct(EntityUid uid, EntityUid targetUid, } var node = PoweredNode(uid, electrified, nodeContainer); - if (node?.NodeGroup is not IBasePowerNet powerNet) - return false; - - var net = powerNet.NetworkNode; - var supp = net.LastCombinedMaxSupply; - - if (supp <= 0f) + if (node?.NodeGroup is not IBasePowerNet) return false; - // Initial damage scales off of the available supply on the principle that the victim has shorted the entire powernet through their body. - var damageScale = CalculateElectrifiedDamageScale(supp); - if (damageScale <= 0f) - return false; + var (damageScalar, timeScalar) = node.NodeGroupID switch + { + NodeGroupID.HVPower => (electrified.HighVoltageDamageMultiplier, electrified.HighVoltageTimeMultiplier), + NodeGroupID.MVPower => (electrified.MediumVoltageDamageMultiplier, electrified.MediumVoltageTimeMultiplier), + _ => (1f, 1f) + }; { var lastRet = true; @@ -286,8 +260,8 @@ public bool TryDoElectrifiedAct(EntityUid uid, EntityUid targetUid, entity, uid, node, - (int) MathF.Ceiling(electrified.ShockDamage * damageScale * MathF.Pow(RecursiveDamageMultiplier, depth)), - TimeSpan.FromSeconds(electrified.ShockTime * MathF.Min(1f + MathF.Log2(1f + damageScale), 3f) * MathF.Pow(RecursiveTimeMultiplier, depth)), + (int) (electrified.ShockDamage * MathF.Pow(RecursiveDamageMultiplier, depth) * damageScalar), + TimeSpan.FromSeconds(electrified.ShockTime * MathF.Pow(RecursiveTimeMultiplier, depth) * timeScalar), true, electrified.SiemensCoefficient); } From e23e4a74013c8daf4582759453e788fcd4de1443 Mon Sep 17 00:00:00 2001 From: lapatison <100279397+lapatison@users.noreply.github.com> Date: Thu, 28 Mar 2024 18:13:14 +0300 Subject: [PATCH 022/295] Sentien artifact ghost role locale (#26509) localize --- .../Locale/en-US/ghost/roles/ghost-role-component.ftl | 5 +++++ Resources/Prototypes/XenoArch/Effects/utility_effects.yml | 8 +++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl b/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl index 4b151d131ab..fb0144d35dd 100644 --- a/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl +++ b/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl @@ -242,3 +242,8 @@ ghost-role-information-syndicate-reinforcement-rules = Normal syndicate antagoni ghost-role-information-syndicate-monkey-reinforcement-name = Syndicate Monkey Agent ghost-role-information-syndicate-monkey-reinforcement-description = Someone needs reinforcements. You, a trained monkey, will help them. ghost-role-information-syndicate-monkey-reinforcement-rules = Normal syndicate antagonist rules apply. Work with whoever called you in, and don't harm them. + +ghost-role-information-artifact-name = Sentient Artifact +ghost-role-information-artifact-description = + Enact your eldritch whims. + Forcibly activate your nodes for good or for evil. diff --git a/Resources/Prototypes/XenoArch/Effects/utility_effects.yml b/Resources/Prototypes/XenoArch/Effects/utility_effects.yml index 0fdaa5b9565..18f3acfae30 100644 --- a/Resources/Prototypes/XenoArch/Effects/utility_effects.yml +++ b/Resources/Prototypes/XenoArch/Effects/utility_effects.yml @@ -1,4 +1,4 @@ -# Utility effects permanently modify the entity in some way when triggered, and they generally make it 'useful' for some purpose, +# Utility effects permanently modify the entity in some way when triggered, and they generally make it 'useful' for some purpose, # like turning the artifact into a tool, or gun, or whatever. - type: artifactEffect id: EffectIntercom @@ -198,10 +198,8 @@ allowMovement: true allowSpeech: true makeSentient: true - name: sentient artifact - description: | - Enact your eldritch whims. - Forcibly activate your nodes for good or for evil. + name: ghost-role-information-artifact-name + description: ghost-role-information-artifact-description - type: GhostTakeoverAvailable - type: MovementSpeedModifier baseWalkSpeed: 0.25 From f862490181fda751bc54cb9d9d658d636465b975 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Thu, 28 Mar 2024 12:56:49 -0400 Subject: [PATCH 023/295] Fix arcade goodbye message implementation (#26514) --- .../BlockGame/BlockGameArcadeComponent.cs | 5 ----- .../Arcade/BlockGame/BlockGameArcadeSystem.cs | 17 +++----------- .../SpaceVillainArcadeComponent.cs | 5 ----- .../SpaceVillainArcadeSystem.cs | 22 ++++--------------- .../Arcade/Advertisements/blockgame.yml | 13 ----------- .../Arcade/Advertisements/spacevillain.yml | 13 ----------- .../Catalog/Arcade/Goodbyes/blockgame.yml | 15 +++++++++++++ .../Catalog/Arcade/Goodbyes/spacevillain.yml | 15 +++++++++++++ .../Structures/Machines/Computers/arcades.yml | 4 ++++ 9 files changed, 41 insertions(+), 68 deletions(-) create mode 100644 Resources/Prototypes/Catalog/Arcade/Goodbyes/blockgame.yml create mode 100644 Resources/Prototypes/Catalog/Arcade/Goodbyes/spacevillain.yml diff --git a/Content.Server/Arcade/BlockGame/BlockGameArcadeComponent.cs b/Content.Server/Arcade/BlockGame/BlockGameArcadeComponent.cs index e2acec52a3a..5613d915444 100644 --- a/Content.Server/Arcade/BlockGame/BlockGameArcadeComponent.cs +++ b/Content.Server/Arcade/BlockGame/BlockGameArcadeComponent.cs @@ -19,9 +19,4 @@ public sealed partial class BlockGameArcadeComponent : Component /// The players currently viewing (but not playing) the active session of NT-BG. /// public readonly List Spectators = new(); - - /// - /// Whether the game machine should thank (or otherwise talk to) the player when they leave - /// - public bool ShouldSayThankYou; } diff --git a/Content.Server/Arcade/BlockGame/BlockGameArcadeSystem.cs b/Content.Server/Arcade/BlockGame/BlockGameArcadeSystem.cs index 96001a484bc..ad65c5cca6b 100644 --- a/Content.Server/Arcade/BlockGame/BlockGameArcadeSystem.cs +++ b/Content.Server/Arcade/BlockGame/BlockGameArcadeSystem.cs @@ -2,7 +2,6 @@ using Content.Shared.UserInterface; using Content.Server.Advertise; using Content.Server.Advertise.Components; -using Content.Server.Advertise.EntitySystems; using Content.Shared.Arcade; using Robust.Server.GameObjects; using Robust.Shared.Player; @@ -12,7 +11,7 @@ namespace Content.Server.Arcade.BlockGame; public sealed class BlockGameArcadeSystem : EntitySystem { [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; - [Dependency] private readonly AdvertiseSystem _advertise = default!; + [Dependency] private readonly SpeakOnUIClosedSystem _speakOnUIClosed = default!; public override void Initialize() { @@ -92,16 +91,6 @@ private void OnAfterUiClose(EntityUid uid, BlockGameArcadeComponent component, B component.Spectators.Remove(component.Player); UpdatePlayerStatus(uid, component.Player, blockGame: component); } - else - { - // Everybody's gone - component.Player = null; - if (component.ShouldSayThankYou && TryComp(uid, out var advertise)) - { - _advertise.SayAdvertisement(uid, advertise); - component.ShouldSayThankYou = false; - } - } UpdatePlayerStatus(uid, temp, blockGame: component); } @@ -115,7 +104,6 @@ private void OnBlockPowerChanged(EntityUid uid, BlockGameArcadeComponent compone _uiSystem.CloseAll(bui); component.Player = null; component.Spectators.Clear(); - component.ShouldSayThankYou = false; } private void OnPlayerAction(EntityUid uid, BlockGameArcadeComponent component, BlockGameMessages.BlockGamePlayerActionMessage msg) @@ -135,7 +123,8 @@ private void OnPlayerAction(EntityUid uid, BlockGameArcadeComponent component, B return; } - component.ShouldSayThankYou = true; + if (TryComp(uid, out var speakComponent)) + _speakOnUIClosed.TrySetFlag((uid, speakComponent)); component.Game.ProcessInput(msg.PlayerAction); } diff --git a/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeComponent.cs b/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeComponent.cs index c3a8877393e..e93fcc6e8f1 100644 --- a/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeComponent.cs +++ b/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeComponent.cs @@ -110,9 +110,4 @@ public sealed partial class SpaceVillainArcadeComponent : SharedSpaceVillainArca ///
[ViewVariables(VVAccess.ReadWrite)] public int RewardAmount = 0; - - /// - /// Whether the game machine should thank (or otherwise talk to) the player when they leave - /// - public bool ShouldSayThankYou; } diff --git a/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs b/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs index 4d704b8a811..f60d88ebf78 100644 --- a/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs +++ b/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs @@ -2,7 +2,6 @@ using Content.Shared.UserInterface; using Content.Server.Advertise; using Content.Server.Advertise.Components; -using Content.Server.Advertise.EntitySystems; using static Content.Shared.Arcade.SharedSpaceVillainArcadeComponent; using Robust.Server.GameObjects; using Robust.Shared.Audio; @@ -16,7 +15,7 @@ public sealed partial class SpaceVillainArcadeSystem : EntitySystem [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly SharedAudioSystem _audioSystem = default!; [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; - [Dependency] private readonly AdvertiseSystem _advertise = default!; + [Dependency] private readonly SpeakOnUIClosedSystem _speakOnUIClosed = default!; public override void Initialize() { @@ -26,7 +25,6 @@ public override void Initialize() SubscribeLocalEvent(OnAfterUIOpenSV); SubscribeLocalEvent(OnSVPlayerAction); SubscribeLocalEvent(OnSVillainPower); - SubscribeLocalEvent(OnBoundUIClosed); } /// [DataField] public HashSet CheckedBounties = new(); + + /// + /// The time at which players will be able to skip the next bounty. + /// + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] + public TimeSpan NextSkipTime = TimeSpan.Zero; + + /// + /// The time between skipping bounties. + /// + [DataField] + public TimeSpan SkipDelay = TimeSpan.FromMinutes(15); } diff --git a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs index ee5ae631fd9..22e5c67e175 100644 --- a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs +++ b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs @@ -4,7 +4,7 @@ using Content.Server.Labels; using Content.Server.NameIdentifier; using Content.Server.Paper; -using Content.Server.Station.Systems; +using Content.Shared.Access.Components; using Content.Shared.Cargo; using Content.Shared.Cargo.Components; using Content.Shared.Cargo.Prototypes; @@ -35,6 +35,7 @@ private void InitializeBounty() { SubscribeLocalEvent(OnBountyConsoleOpened); SubscribeLocalEvent(OnPrintLabelMessage); + SubscribeLocalEvent(OnSkipBountyMessage); SubscribeLocalEvent(OnGetBountyPrice); SubscribeLocalEvent(OnSold); SubscribeLocalEvent(OnMapInit); @@ -50,7 +51,8 @@ private void OnBountyConsoleOpened(EntityUid uid, CargoBountyConsoleComponent co !TryComp(station, out var bountyDb)) return; - _uiSystem.TrySetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(bountyDb.Bounties)); + var untilNextSkip = bountyDb.NextSkipTime - _timing.CurTime; + _uiSystem.TrySetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(bountyDb.Bounties, untilNextSkip)); } private void OnPrintLabelMessage(EntityUid uid, CargoBountyConsoleComponent component, BountyPrintLabelMessage args) @@ -70,6 +72,37 @@ private void OnPrintLabelMessage(EntityUid uid, CargoBountyConsoleComponent comp _audio.PlayPvs(component.PrintSound, uid); } + private void OnSkipBountyMessage(EntityUid uid, CargoBountyConsoleComponent component, BountySkipMessage args) + { + if (_station.GetOwningStation(uid) is not { } station || !TryComp(station, out var db)) + return; + + if (_timing.CurTime < db.NextSkipTime) + return; + + if (!TryGetBountyFromId(station, args.BountyId, out var bounty)) + return; + + if (args.Session.AttachedEntity is not { Valid: true } mob) + return; + + if (TryComp(uid, out var accessReaderComponent) && + !_accessReaderSystem.IsAllowed(mob, uid, accessReaderComponent)) + { + _audio.PlayPvs(component.DenySound, uid); + return; + } + + if (!TryRemoveBounty(station, bounty.Value)) + return; + + FillBountyDatabase(station); + db.NextSkipTime = _timing.CurTime + db.SkipDelay; + var untilNextSkip = db.NextSkipTime - _timing.CurTime; + _uiSystem.TrySetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, untilNextSkip)); + _audio.PlayPvs(component.SkipSound, uid); + } + public void SetupBountyLabel(EntityUid uid, EntityUid stationId, CargoBountyData bounty, PaperComponent? paper = null, CargoBountyLabelComponent? label = null) { if (!Resolve(uid, ref paper, ref label) || !_protoMan.TryIndex(bounty.Bounty, out var prototype)) @@ -431,7 +464,8 @@ public void UpdateBountyConsoles() !TryComp(station, out var db)) continue; - _uiSystem.TrySetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties), ui: ui); + var untilNextSkip = db.NextSkipTime - _timing.CurTime; + _uiSystem.TrySetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, untilNextSkip), ui: ui); } } diff --git a/Content.Shared/Cargo/Components/CargoBountyConsoleComponent.cs b/Content.Shared/Cargo/Components/CargoBountyConsoleComponent.cs index 7b1acf836fd..bf82a08127e 100644 --- a/Content.Shared/Cargo/Components/CargoBountyConsoleComponent.cs +++ b/Content.Shared/Cargo/Components/CargoBountyConsoleComponent.cs @@ -32,16 +32,30 @@ public sealed partial class CargoBountyConsoleComponent : Component /// @@ -84,7 +82,9 @@ private void OnSVPlayerAction(EntityUid uid, SpaceVillainArcadeComponent compone case PlayerAction.Heal: case PlayerAction.Recharge: component.Game.ExecutePlayerAction(uid, msg.PlayerAction, component); - component.ShouldSayThankYou = true; // Any sort of gameplay action counts + // Any sort of gameplay action counts + if (TryComp(uid, out var speakComponent)) + _speakOnUIClosed.TrySetFlag((uid, speakComponent)); break; case PlayerAction.NewGame: _audioSystem.PlayPvs(component.NewGameSound, uid, AudioParams.Default.WithVolume(-4f)); @@ -112,19 +112,5 @@ private void OnSVillainPower(EntityUid uid, SpaceVillainArcadeComponent componen if (_uiSystem.TryGetUi(uid, SpaceVillainArcadeUiKey.Key, out var bui)) _uiSystem.CloseAll(bui); - - component.ShouldSayThankYou = false; - } - - private void OnBoundUIClosed(Entity ent, ref BoundUIClosedEvent args) - { - if (args.UiKey is not SpaceVillainArcadeUiKey || (SpaceVillainArcadeUiKey) args.UiKey != SpaceVillainArcadeUiKey.Key) - return; - - if (ent.Comp.ShouldSayThankYou && TryComp(ent.Owner, out var advertise)) - { - _advertise.SayAdvertisement(ent.Owner, advertise); - ent.Comp.ShouldSayThankYou = false; - } } } diff --git a/Resources/Prototypes/Catalog/Arcade/Advertisements/blockgame.yml b/Resources/Prototypes/Catalog/Arcade/Advertisements/blockgame.yml index 226f0cb72b6..7fb03672951 100644 --- a/Resources/Prototypes/Catalog/Arcade/Advertisements/blockgame.yml +++ b/Resources/Prototypes/Catalog/Arcade/Advertisements/blockgame.yml @@ -14,16 +14,3 @@ - advertisement-block-game-11 - advertisement-block-game-12 - advertisement-block-game-13 -# thankyous: -# - thankyou-block-game-1 -# - thankyou-block-game-2 -# - thankyou-block-game-3 -# - thankyou-block-game-4 -# - thankyou-block-game-5 -# - thankyou-block-game-6 -# - thankyou-block-game-7 -# - thankyou-block-game-8 -# - thankyou-block-game-9 -# - thankyou-block-game-10 -# - thankyou-block-game-11 -# - thankyou-block-game-12 diff --git a/Resources/Prototypes/Catalog/Arcade/Advertisements/spacevillain.yml b/Resources/Prototypes/Catalog/Arcade/Advertisements/spacevillain.yml index bee4ca2baec..7c94ab94f6b 100644 --- a/Resources/Prototypes/Catalog/Arcade/Advertisements/spacevillain.yml +++ b/Resources/Prototypes/Catalog/Arcade/Advertisements/spacevillain.yml @@ -16,16 +16,3 @@ - advertisement-space-villain-13 - advertisement-space-villain-14 - advertisement-space-villain-15 -# thankyous: -# - thankyou-space-villain-1 -# - thankyou-space-villain-2 -# - thankyou-space-villain-3 -# - thankyou-space-villain-4 -# - thankyou-space-villain-5 -# - thankyou-space-villain-6 -# - thankyou-space-villain-7 -# - thankyou-space-villain-8 -# - thankyou-space-villain-9 -# - thankyou-space-villain-10 -# - thankyou-space-villain-11 -# - thankyou-space-villain-12 diff --git a/Resources/Prototypes/Catalog/Arcade/Goodbyes/blockgame.yml b/Resources/Prototypes/Catalog/Arcade/Goodbyes/blockgame.yml new file mode 100644 index 00000000000..460e8d13bff --- /dev/null +++ b/Resources/Prototypes/Catalog/Arcade/Goodbyes/blockgame.yml @@ -0,0 +1,15 @@ +- type: messagePack + id: BlockGameGoodbyes + messages: + - thankyou-block-game-1 + - thankyou-block-game-2 + - thankyou-block-game-3 + - thankyou-block-game-4 + - thankyou-block-game-5 + - thankyou-block-game-6 + - thankyou-block-game-7 + - thankyou-block-game-8 + - thankyou-block-game-9 + - thankyou-block-game-10 + - thankyou-block-game-11 + - thankyou-block-game-12 diff --git a/Resources/Prototypes/Catalog/Arcade/Goodbyes/spacevillain.yml b/Resources/Prototypes/Catalog/Arcade/Goodbyes/spacevillain.yml new file mode 100644 index 00000000000..09016afec31 --- /dev/null +++ b/Resources/Prototypes/Catalog/Arcade/Goodbyes/spacevillain.yml @@ -0,0 +1,15 @@ +- type: messagePack + id: SpaceVillainGoodbyes + messages: + - thankyou-space-villain-1 + - thankyou-space-villain-2 + - thankyou-space-villain-3 + - thankyou-space-villain-4 + - thankyou-space-villain-5 + - thankyou-space-villain-6 + - thankyou-space-villain-7 + - thankyou-space-villain-8 + - thankyou-space-villain-9 + - thankyou-space-villain-10 + - thankyou-space-villain-11 + - thankyou-space-villain-12 diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/arcades.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/arcades.yml index 4dbee6892f7..67436a4cbbc 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/arcades.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/arcades.yml @@ -150,6 +150,8 @@ pack: SpaceVillainAds minWait: 60 # Arcades are noisy maxWait: 240 + - type: SpeakOnUIClosed + pack: SpaceVillainGoodbyes - type: entity id: SpaceVillainArcadeFilled @@ -192,3 +194,5 @@ pack: BlockGameAds minWait: 60 # Arcades are noisy maxWait: 240 + - type: SpeakOnUIClosed + pack: BlockGameGoodbyes From 4d44d084189dbadacea5c739c7494af4ab282a66 Mon Sep 17 00:00:00 2001 From: "Mr. 27" <45323883+Dutch-VanDerLinde@users.noreply.github.com> Date: Thu, 28 Mar 2024 21:06:23 -0400 Subject: [PATCH 024/295] more melee weapon sound fixes (#26520) g --- .../Entities/Objects/Specific/Janitorial/janitor.yml | 4 ++++ .../Entities/Objects/Weapons/Melee/weapon_toolbox.yml | 2 ++ 2 files changed, 6 insertions(+) diff --git a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml index 2ddb21b9e6c..db08481dc53 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml @@ -11,6 +11,8 @@ damage: types: Blunt: 10 + soundHit: + collection: MetalThud - type: Spillable solution: absorbed - type: Wieldable @@ -49,6 +51,8 @@ damage: types: Blunt: 10 + soundHit: + collection: MetalThud - type: Spillable solution: absorbed - type: Wieldable diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/weapon_toolbox.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/weapon_toolbox.yml index 366aabd2f27..240a17a0a44 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/weapon_toolbox.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/weapon_toolbox.yml @@ -17,3 +17,5 @@ damage: types: Blunt: 20 + soundHit: + path: "/Audio/Weapons/smash.ogg" From 7be8b34bcbf8da368d37ed322873db8eb7854613 Mon Sep 17 00:00:00 2001 From: Valent <108285874+ValentFingerov@users.noreply.github.com> Date: Fri, 29 Mar 2024 06:07:38 +0500 Subject: [PATCH 025/295] Foldable ushanka (#26519) * UpdateUshankaPrototype * UpdateUshankaPrototype --- .../Entities/Clothing/Head/hats.yml | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/Resources/Prototypes/Entities/Clothing/Head/hats.yml b/Resources/Prototypes/Entities/Clothing/Head/hats.yml index c845d7cc4a0..30907f70b5b 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/hats.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/hats.yml @@ -483,17 +483,30 @@ - WhitelistChameleon - type: entity - parent: ClothingHeadBase + parent: [ClothingHeadBase, BaseFoldable] id: ClothingHeadHatUshanka name: ushanka description: "Perfect for winter in Siberia, da?" components: - - type: Sprite - sprite: Clothing/Head/Hats/ushanka.rsi - type: Clothing sprite: Clothing/Head/Hats/ushanka.rsi + - type: Appearance - type: AddAccentClothing accent: RussianAccent + - type: Foldable + canFoldInsideContainer: true + - type: FoldableClothing + foldedEquippedPrefix: up + foldedHeldPrefix: up + - type: Sprite + sprite: Clothing/Head/Hats/ushanka.rsi + layers: + - state: icon + map: [ "unfoldedLayer" ] + - state: icon-up + map: ["foldedLayer"] + visible: false + - type: entity parent: ClothingHeadBase From 06665ef4dff712a767a7cec0984c0c1b7e0c6190 Mon Sep 17 00:00:00 2001 From: Jake Huxell Date: Thu, 28 Mar 2024 22:30:23 -0400 Subject: [PATCH 026/295] [BugFix] Persist Job Restrictions When New User Late Joins (#26498) * make sure to keep late join button disabled if requirements not met * more succinct representation of condition --- Content.Client/LateJoin/LateJoinGui.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Client/LateJoin/LateJoinGui.cs b/Content.Client/LateJoin/LateJoinGui.cs index d6a028d6c45..ba9351d6746 100644 --- a/Content.Client/LateJoin/LateJoinGui.cs +++ b/Content.Client/LateJoin/LateJoinGui.cs @@ -309,7 +309,7 @@ private void JobsAvailableUpdated(IReadOnlyDictionary Date: Fri, 29 Mar 2024 06:03:34 +0100 Subject: [PATCH 027/295] Send what seleted for secret to admin chat (#26500) * Send what seleted for secret to admin chat * add line * Add localization support --- Content.Server/GameTicking/Rules/SecretRuleSystem.cs | 3 +++ Resources/Locale/en-US/game-ticking/game-rules/rule-secret.ftl | 2 ++ 2 files changed, 5 insertions(+) create mode 100644 Resources/Locale/en-US/game-ticking/game-rules/rule-secret.ftl diff --git a/Content.Server/GameTicking/Rules/SecretRuleSystem.cs b/Content.Server/GameTicking/Rules/SecretRuleSystem.cs index 6a00eb7d102..fa5f17b4f37 100644 --- a/Content.Server/GameTicking/Rules/SecretRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/SecretRuleSystem.cs @@ -1,4 +1,5 @@ using Content.Server.Administration.Logs; +using Content.Server.Chat.Managers; using Content.Server.GameTicking.Presets; using Content.Server.GameTicking.Rules.Components; using Content.Shared.Random; @@ -17,6 +18,7 @@ public sealed class SecretRuleSystem : GameRuleSystem [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IConfigurationManager _configurationManager = default!; [Dependency] private readonly IAdminLogManager _adminLogger = default!; + [Dependency] private readonly IChatManager _chatManager = default!; protected override void Added(EntityUid uid, SecretRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args) { @@ -42,6 +44,7 @@ private void PickRule(SecretRuleComponent component) var preset = _prototypeManager.Index(presetString).Pick(_random); Log.Info($"Selected {preset} for secret."); _adminLogger.Add(LogType.EventStarted, $"Selected {preset} for secret."); + _chatManager.SendAdminAnnouncement(Loc.GetString("rule-secret-selected-preset", ("preset", preset))); var rules = _prototypeManager.Index(preset).Rules; foreach (var rule in rules) diff --git a/Resources/Locale/en-US/game-ticking/game-rules/rule-secret.ftl b/Resources/Locale/en-US/game-ticking/game-rules/rule-secret.ftl new file mode 100644 index 00000000000..c38220cca1d --- /dev/null +++ b/Resources/Locale/en-US/game-ticking/game-rules/rule-secret.ftl @@ -0,0 +1,2 @@ +# Sent to admin chat +rule-secret-selected-preset = Selected {$preset} for secret. From 88555e33300a3b223a23bdff5c3d926460d5f90c Mon Sep 17 00:00:00 2001 From: wafehling Date: Fri, 29 Mar 2024 01:13:52 -0500 Subject: [PATCH 028/295] Chemically-created Crystal Shards (#26269) Added chemical reaction for making crystal shards --- .../Entities/Objects/Materials/crystal_shard.yml | 15 +++++++++++++++ Resources/Prototypes/Recipes/Reactions/fun.yml | 14 ++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/Resources/Prototypes/Entities/Objects/Materials/crystal_shard.yml b/Resources/Prototypes/Entities/Objects/Materials/crystal_shard.yml index 884a5531e7f..8f522abce42 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/crystal_shard.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/crystal_shard.yml @@ -134,3 +134,18 @@ tags: - Trash - CrystalRed + +- type: entity + parent: ShardCrystalBase + id: ShardCrystalRandom + name: random crystal shard + components: + - type: RandomSpawner + prototypes: + - ShardCrystalGreen + - ShardCrystalPink + - ShardCrystalOrange + - ShardCrystalBlue + - ShardCrystalCyan + - ShardCrystalRed + chance: 1 diff --git a/Resources/Prototypes/Recipes/Reactions/fun.yml b/Resources/Prototypes/Recipes/Reactions/fun.yml index a8ccd5f0455..4e838816f20 100644 --- a/Resources/Prototypes/Recipes/Reactions/fun.yml +++ b/Resources/Prototypes/Recipes/Reactions/fun.yml @@ -171,3 +171,17 @@ products: Laughter: 2 +- type: reaction + id: CreateCrystals + quantized: true + minTemp: 374 + reactants: + Sugar: + amount: 15 + Water: + amount: 15 + Ethanol: + amount: 5 + effects: + - !type:CreateEntityReactionEffect + entity: ShardCrystalRandom From 153bb7a84c8629b313b269cd139b8ca5cfe8da74 Mon Sep 17 00:00:00 2001 From: Crotalus Date: Fri, 29 Mar 2024 07:30:50 +0100 Subject: [PATCH 029/295] Add auto modes to reagent grinder (#26290) * Add auto-mode to reagent grinder * Remove redundant stuff with DataField * Use margin instead of dummy control * Resolve grinder component --- Content.Client/Kitchen/UI/GrinderMenu.xaml | 10 ++++++---- Content.Client/Kitchen/UI/GrinderMenu.xaml.cs | 14 ++++++++++++++ .../UI/ReagentGrinderBoundUserInterface.cs | 5 +++++ .../Components/ReagentGrinderComponent.cs | 15 +++++++++------ .../EntitySystems/ReagentGrinderSystem.cs | 19 +++++++++++++++++++ .../Kitchen/SharedReagentGrinder.cs | 18 +++++++++++++++++- .../components/reagent-grinder-component.ftl | 3 +++ 7 files changed, 73 insertions(+), 11 deletions(-) diff --git a/Content.Client/Kitchen/UI/GrinderMenu.xaml b/Content.Client/Kitchen/UI/GrinderMenu.xaml index b83128d004a..dacddd0df68 100644 --- a/Content.Client/Kitchen/UI/GrinderMenu.xaml +++ b/Content.Client/Kitchen/UI/GrinderMenu.xaml @@ -3,10 +3,12 @@ xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls" Title="{Loc grinder-menu-title}" MinSize="768 256"> - -
[DataField("printSound")] public SoundSpecifier PrintSound = new SoundPathSpecifier("/Audio/Machines/printer.ogg"); + + /// + /// The sound made when the bounty is skipped. + /// + [DataField("skipSound")] + public SoundSpecifier SkipSound = new SoundPathSpecifier("/Audio/Effects/Cargo/ping.ogg"); + + /// + /// The sound made when bounty skipping is denied due to lacking access. + /// + [DataField("denySound")] + public SoundSpecifier DenySound = new SoundPathSpecifier("/Audio/Effects/Cargo/buzz_two.ogg"); } [NetSerializable, Serializable] public sealed class CargoBountyConsoleState : BoundUserInterfaceState { public List Bounties; + public TimeSpan UntilNextSkip; - public CargoBountyConsoleState(List bounties) + public CargoBountyConsoleState(List bounties, TimeSpan untilNextSkip) { Bounties = bounties; + UntilNextSkip = untilNextSkip; } } @@ -55,3 +69,14 @@ public BountyPrintLabelMessage(string bountyId) BountyId = bountyId; } } + +[Serializable, NetSerializable] +public sealed class BountySkipMessage : BoundUserInterfaceMessage +{ + public string BountyId; + + public BountySkipMessage(string bountyId) + { + BountyId = bountyId; + } +} diff --git a/Resources/Locale/en-US/cargo/cargo-bounty-console.ftl b/Resources/Locale/en-US/cargo/cargo-bounty-console.ftl index ec80d91f47f..bf7868e3df7 100644 --- a/Resources/Locale/en-US/cargo/cargo-bounty-console.ftl +++ b/Resources/Locale/en-US/cargo/cargo-bounty-console.ftl @@ -1,5 +1,6 @@ bounty-console-menu-title = Cargo bounty console bounty-console-label-button-text = Print label +bounty-console-skip-button-text = Skip bounty-console-time-label = Time: [color=orange]{$time}[/color] bounty-console-reward-label = Reward: [color=limegreen]${$reward}[/color] bounty-console-manifest-label = Manifest: [color=orange]{$item}[/color] diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml index 7e940a2f98d..4fd2129f7a5 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml @@ -791,6 +791,8 @@ radius: 1.5 energy: 1.6 color: "#b89f25" + - type: AccessReader + access: [["Quartermaster"]] - type: GuideHelp guides: - CargoBounties From 4233a9901cd9fb355921406e7521f6d110c233d4 Mon Sep 17 00:00:00 2001 From: SkaldetSkaeg Date: Wed, 10 Apr 2024 05:20:57 +0700 Subject: [PATCH 172/295] Flippolighter_fix (#26846) Flippolighter has realy loud sound, no UseDelay and server errors --- Resources/Prototypes/Entities/Objects/Tools/lighters.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Resources/Prototypes/Entities/Objects/Tools/lighters.yml b/Resources/Prototypes/Entities/Objects/Tools/lighters.yml index 631e2a247ea..d03cc725efe 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/lighters.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/lighters.yml @@ -142,8 +142,12 @@ predictable: false soundActivate: path: /Audio/Items/Lighters/zippo_open.ogg + params: + volume: -5 soundDeactivate: path: /Audio/Items/Lighters/zippo_close.ogg + params: + volume: -5 - type: ItemToggleMeleeWeapon activatedDamage: types: @@ -201,6 +205,9 @@ netsync: false radius: 1.2 #slightly stronger than the other lighters color: orange + - type: UseDelay + - type: IgnitionSource + ignited: false - type: entity name: flippo engraved lighter From 21f5534ed31dcc4f0dfdabca4d0292a50d7db7d6 Mon Sep 17 00:00:00 2001 From: Simon <63975668+Simyon264@users.noreply.github.com> Date: Wed, 10 Apr 2024 11:37:16 +0200 Subject: [PATCH 173/295] Game server api (#24015) * Revert "Revert "Game server api (#23129)"" * Review pt.1 * Reviews pt.2 * Reviews pt. 3 * Reviews pt. 4 --- Content.Server/Administration/ServerAPI.cs | 1033 +++++++++++++++++ .../Administration/Systems/AdminSystem.cs | 24 +- Content.Server/Entry/EntryPoint.cs | 2 + Content.Server/IoC/ServerContentIoC.cs | 1 + Content.Shared/CCVar/CCVars.cs | 7 + 5 files changed, 1055 insertions(+), 12 deletions(-) create mode 100644 Content.Server/Administration/ServerAPI.cs diff --git a/Content.Server/Administration/ServerAPI.cs b/Content.Server/Administration/ServerAPI.cs new file mode 100644 index 00000000000..d7591fb80c6 --- /dev/null +++ b/Content.Server/Administration/ServerAPI.cs @@ -0,0 +1,1033 @@ +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading.Tasks; +using Content.Server.Administration.Systems; +using Content.Server.GameTicking; +using Content.Server.GameTicking.Presets; +using Content.Server.GameTicking.Rules.Components; +using Content.Server.Maps; +using Content.Server.RoundEnd; +using Content.Shared.Administration.Events; +using Content.Shared.Administration.Managers; +using Content.Shared.CCVar; +using Content.Shared.Prototypes; +using Robust.Server.ServerStatus; +using Robust.Shared.Asynchronous; +using Robust.Shared.Configuration; +using Robust.Shared.Network; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Server.Administration; + +public sealed class ServerApi : IPostInjectInit +{ + [Dependency] private readonly IStatusHost _statusHost = default!; + [Dependency] private readonly IConfigurationManager _config = default!; + [Dependency] private readonly ISharedPlayerManager _playerManager = default!; // Players + [Dependency] private readonly ISharedAdminManager _adminManager = default!; // Admins + [Dependency] private readonly IGameMapManager _gameMapManager = default!; // Map name + [Dependency] private readonly IServerNetManager _netManager = default!; // Kick + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; // Game rules + [Dependency] private readonly IComponentFactory _componentFactory = default!; + [Dependency] private readonly ITaskManager _taskManager = default!; // game explodes when calling stuff from the non-game thread + [Dependency] private readonly EntityManager _entityManager = default!; + + [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; + + private string _token = string.Empty; + private ISawmill _sawmill = default!; + + public static Dictionary PanicPunkerCvarNames = new() + { + { "Enabled", "game.panic_bunker.enabled" }, + { "DisableWithAdmins", "game.panic_bunker.disable_with_admins" }, + { "EnableWithoutAdmins", "game.panic_bunker.enable_without_admins" }, + { "CountDeadminnedAdmins", "game.panic_bunker.count_deadminned_admins" }, + { "ShowReason", "game.panic_bunker.show_reason" }, + { "MinAccountAgeHours", "game.panic_bunker.min_account_age" }, + { "MinOverallHours", "game.panic_bunker.min_overall_hours" }, + { "CustomReason", "game.panic_bunker.custom_reason" } + }; + + void IPostInjectInit.PostInject() + { + _sawmill = Logger.GetSawmill("serverApi"); + + // Get + _statusHost.AddHandler(InfoHandler); + _statusHost.AddHandler(GetGameRules); + _statusHost.AddHandler(GetForcePresets); + + // Post + _statusHost.AddHandler(ActionRoundStatus); + _statusHost.AddHandler(ActionKick); + _statusHost.AddHandler(ActionAddGameRule); + _statusHost.AddHandler(ActionEndGameRule); + _statusHost.AddHandler(ActionForcePreset); + _statusHost.AddHandler(ActionForceMotd); + _statusHost.AddHandler(ActionPanicPunker); + } + + public void Initialize() + { + _config.OnValueChanged(CCVars.AdminApiToken, UpdateToken, true); + } + + public void Shutdown() + { + _config.UnsubValueChanged(CCVars.AdminApiToken, UpdateToken); + } + + private void UpdateToken(string token) + { + _token = token; + } + + +#region Actions + + /// + /// Changes the panic bunker settings. + /// + private async Task ActionPanicPunker(IStatusHandlerContext context) + { + if (context.RequestMethod != HttpMethod.Patch || context.Url.AbsolutePath != "/admin/actions/panic_bunker") + { + return false; + } + + if (!CheckAccess(context)) + return true; + + var body = await ReadJson>(context); + var (success, actor) = await CheckActor(context); + if (!success) + return true; + + foreach (var panicPunkerActions in body!.Select(x => new { Action = x.Key, Value = x.Value.ToString() })) + { + if (panicPunkerActions.Action == null || panicPunkerActions.Value == null) + { + await context.RespondJsonAsync(new BaseResponse() + { + Message = "Action and value are required to perform this action.", + Exception = new ExceptionData() + { + Message = "Action and value are required to perform this action.", + ErrorType = ErrorTypes.ActionNotSpecified + } + }, HttpStatusCode.BadRequest); + return true; + } + + if (!PanicPunkerCvarNames.TryGetValue(panicPunkerActions.Action, out var cvarName)) + { + await context.RespondJsonAsync(new BaseResponse() + { + Message = $"Cannot set: Action {panicPunkerActions.Action} does not exist.", + Exception = new ExceptionData() + { + Message = $"Cannot set: Action {panicPunkerActions.Action} does not exist.", + ErrorType = ErrorTypes.ActionNotSupported + } + }, HttpStatusCode.BadRequest); + return true; + } + + // Since the CVar can be of different types, we need to parse it to the correct type + // First, I try to parse it as a bool, if it fails, I try to parse it as an int + // And as a last resort, I do nothing and put it as a string + if (bool.TryParse(panicPunkerActions.Value, out var boolValue)) + { + await RunOnMainThread(() => _config.SetCVar(cvarName, boolValue)); + } + else if (int.TryParse(panicPunkerActions.Value, out var intValue)) + { + await RunOnMainThread(() => _config.SetCVar(cvarName, intValue)); + } + else + { + await RunOnMainThread(() => _config.SetCVar(cvarName, panicPunkerActions.Value)); + } + _sawmill.Info($"Panic bunker property {panicPunkerActions} changed to {panicPunkerActions.Value} by {actor!.Name} ({actor.Guid})."); + } + + await context.RespondJsonAsync(new BaseResponse() + { + Message = "OK" + }); + return true; + } + + /// + /// Sets the current MOTD. + /// + private async Task ActionForceMotd(IStatusHandlerContext context) + { + if (context.RequestMethod != HttpMethod.Post || context.Url.AbsolutePath != "/admin/actions/set_motd") + { + return false; + } + + if (!CheckAccess(context)) + return true; + + var motd = await ReadJson(context); + var (success, actor) = await CheckActor(context); + if (!success) + return true; + + + if (motd!.Motd == null) + { + await context.RespondJsonAsync(new BaseResponse() + { + Message = "A motd is required to perform this action.", + Exception = new ExceptionData() + { + Message = "A motd is required to perform this action.", + ErrorType = ErrorTypes.MotdNotSpecified + } + }, HttpStatusCode.BadRequest); + return true; + } + + _sawmill.Info($"MOTD changed to \"{motd.Motd}\" by {actor!.Name} ({actor.Guid})."); + + await RunOnMainThread(() => _config.SetCVar(CCVars.MOTD, motd.Motd)); + // A hook in the MOTD system sends the changes to each client + await context.RespondJsonAsync(new BaseResponse() + { + Message = "OK" + }); + return true; + } + + /// + /// Forces the next preset- + /// + private async Task ActionForcePreset(IStatusHandlerContext context) + { + if (context.RequestMethod != HttpMethod.Post || context.Url.AbsolutePath != "/admin/actions/force_preset") + { + return false; + } + + if (!CheckAccess(context)) + return true; + + var body = await ReadJson(context); + var (success, actor) = await CheckActor(context); + if (!success) + return true; + + var ticker = await RunOnMainThread(() => _entitySystemManager.GetEntitySystem()); + + if (ticker.RunLevel != GameRunLevel.PreRoundLobby) + { + await context.RespondJsonAsync(new BaseResponse() + { + Message = "Round already started", + Exception = new ExceptionData() + { + Message = "Round already started", + ErrorType = ErrorTypes.RoundAlreadyStarted + } + }, HttpStatusCode.Conflict); + return true; + } + + if (body!.PresetId == null) + { + await context.RespondJsonAsync(new BaseResponse() + { + Message = "A preset is required to perform this action.", + Exception = new ExceptionData() + { + Message = "A preset is required to perform this action.", + ErrorType = ErrorTypes.PresetNotSpecified + } + }, HttpStatusCode.BadRequest); + return true; + } + + var result = await RunOnMainThread(() => ticker.FindGamePreset(body.PresetId)); + if (result == null) + { + await context.RespondJsonAsync(new BaseResponse() + { + Message = "Preset not found", + Exception = new ExceptionData() + { + Message = "Preset not found", + ErrorType = ErrorTypes.PresetNotSpecified + } + }, HttpStatusCode.UnprocessableContent); + return true; + } + + await RunOnMainThread(() => + { + ticker.SetGamePreset(result); + }); + _sawmill.Info($"Forced the game to start with preset {body.PresetId} by {actor!.Name}({actor.Guid})."); + await context.RespondJsonAsync(new BaseResponse() + { + Message = "OK" + }); + return true; + } + + /// + /// Ends an active game rule. + /// + private async Task ActionEndGameRule(IStatusHandlerContext context) + { + if (context.RequestMethod != HttpMethod.Post || context.Url.AbsolutePath != "/admin/actions/end_game_rule") + { + return false; + } + + if (!CheckAccess(context)) + return true; + + var body = await ReadJson(context); + var (success, actor) = await CheckActor(context); + if (!success) + return true; + + if (body!.GameRuleId == null) + { + await context.RespondJsonAsync(new BaseResponse() + { + Message = "A game rule is required to perform this action.", + Exception = new ExceptionData() + { + Message = "A game rule is required to perform this action.", + ErrorType = ErrorTypes.GuidNotSpecified + } + }, HttpStatusCode.BadRequest); + return true; + } + var ticker = await RunOnMainThread(() => _entitySystemManager.GetEntitySystem()); + + var gameRuleEntity = await RunOnMainThread(() => ticker + .GetActiveGameRules() + .FirstOrNull(rule => _entityManager.MetaQuery.GetComponent(rule).EntityPrototype?.ID == body.GameRuleId)); + + if (gameRuleEntity == null) // Game rule not found + { + await context.RespondJsonAsync(new BaseResponse() + { + Message = "Game rule not found or not active", + Exception = new ExceptionData() + { + Message = "Game rule not found or not active", + ErrorType = ErrorTypes.GameRuleNotFound + } + }, HttpStatusCode.Conflict); + return true; + } + + _sawmill.Info($"Ended game rule {body.GameRuleId} by {actor!.Name} ({actor.Guid})."); + await RunOnMainThread(() => ticker.EndGameRule((EntityUid) gameRuleEntity)); + await context.RespondJsonAsync(new BaseResponse() + { + Message = "OK" + }); + return true; + } + + /// + /// Adds a game rule to the current round. + /// + private async Task ActionAddGameRule(IStatusHandlerContext context) + { + if (context.RequestMethod != HttpMethod.Post || context.Url.AbsolutePath != "/admin/actions/add_game_rule") + { + return false; + } + + if (!CheckAccess(context)) + return true; + + var body = await ReadJson(context); + var (success, actor) = await CheckActor(context); + if (!success) + return true; + + if (body!.GameRuleId == null) + { + await context.RespondJsonAsync(new BaseResponse() + { + Message = "A game rule is required to perform this action.", + Exception = new ExceptionData() + { + Message = "A game rule is required to perform this action.", + ErrorType = ErrorTypes.GuidNotSpecified + } + }, HttpStatusCode.BadRequest); + return true; + } + + var ruleEntity = await RunOnMainThread(() => + { + var ticker = _entitySystemManager.GetEntitySystem(); + // See if prototype exists + try + { + _prototypeManager.Index(body.GameRuleId); + } + catch (KeyNotFoundException e) + { + return null; + } + + var ruleEntity = ticker.AddGameRule(body.GameRuleId); + _sawmill.Info($"Added game rule {body.GameRuleId} by {actor!.Name} ({actor.Guid})."); + if (ticker.RunLevel == GameRunLevel.InRound) + { + ticker.StartGameRule(ruleEntity); + _sawmill.Info($"Started game rule {body.GameRuleId} by {actor.Name} ({actor.Guid})."); + } + return ruleEntity; + }); + if (ruleEntity == null) + { + await context.RespondJsonAsync(new BaseResponse() + { + Message = "Game rule not found", + Exception = new ExceptionData() + { + Message = "Game rule not found", + ErrorType = ErrorTypes.GameRuleNotFound + } + }, HttpStatusCode.UnprocessableContent); + return true; + } + + await context.RespondJsonAsync(new BaseResponse() + { + Message = "OK" + }); + return true; + } + + /// + /// Kicks a player. + /// + private async Task ActionKick(IStatusHandlerContext context) + { + if (context.RequestMethod != HttpMethod.Post || context.Url.AbsolutePath != "/admin/actions/kick") + { + return false; + } + + if (!CheckAccess(context)) + return true; + + var body = await ReadJson(context); + var (success, actor) = await CheckActor(context); + if (!success) + return true; + + if (body == null) + { + _sawmill.Info($"Attempted to kick player without supplying a body by {actor!.Name}({actor.Guid})."); + await context.RespondJsonAsync(new BaseResponse() + { + Message = "A body is required to perform this action.", + Exception = new ExceptionData() + { + Message = "A body is required to perform this action.", + ErrorType = ErrorTypes.BodyUnableToParse + } + }, HttpStatusCode.BadRequest); + return true; + } + + if (body.Guid == null) + { + _sawmill.Info($"Attempted to kick player without supplying a username by {actor!.Name}({actor.Guid})."); + await context.RespondJsonAsync(new BaseResponse() + { + Message = "A player is required to perform this action.", + Exception = new ExceptionData() + { + Message = "A player is required to perform this action.", + ErrorType = ErrorTypes.GuidNotSpecified + } + }, HttpStatusCode.BadRequest); + return true; + } + + var session = await RunOnMainThread(() => + { + _playerManager.TryGetSessionById(new NetUserId(new Guid(body.Guid)), out var player); + return player; + }); + + if (session == null) + { + _sawmill.Info($"Attempted to kick player {body.Guid} by {actor!.Name} ({actor.Guid}), but they were not found."); + await context.RespondJsonAsync(new BaseResponse() + { + Message = "Player not found", + Exception = new ExceptionData() + { + Message = "Player not found", + ErrorType = ErrorTypes.PlayerNotFound + } + }, HttpStatusCode.UnprocessableContent); + return true; + } + + var reason = body.Reason ?? "No reason supplied"; + reason += " (kicked by admin)"; + + await RunOnMainThread(() => + { + _netManager.DisconnectChannel(session.Channel, reason); + }); + await context.RespondJsonAsync(new BaseResponse() + { + Message = "OK" + }); + _sawmill.Info("Kicked player {0} ({1}) for {2} by {3}({4})", session.Name, session.UserId.UserId.ToString(), reason, actor!.Name, actor.Guid); + return true; + } + + /// + /// Round restart/end + /// + private async Task ActionRoundStatus(IStatusHandlerContext context) + { + if (context.RequestMethod != HttpMethod.Post || !context.Url.AbsolutePath.StartsWith("/admin/actions/round/")) + { + return false; + } + + // Make sure paths like /admin/actions/round/lol/start don't work + if (context.Url.AbsolutePath.Split('/').Length != 5) + { + return false; + } + + if (!CheckAccess(context)) + return true; + var (success, actor) = await CheckActor(context); + if (!success) + return true; + + + var (ticker, roundEndSystem) = await RunOnMainThread(() => + { + var ticker = _entitySystemManager.GetEntitySystem(); + var roundEndSystem = _entitySystemManager.GetEntitySystem(); + return (ticker, roundEndSystem); + }); + + // Action is the last part of the URL path (e.g. /admin/actions/round/start -> start) + var action = context.Url.AbsolutePath.Split('/').Last(); + + switch (action) + { + case "start": + if (ticker.RunLevel != GameRunLevel.PreRoundLobby) + { + await context.RespondJsonAsync(new BaseResponse() + { + Message = "Round already started", + Exception = new ExceptionData() + { + Message = "Round already started", + ErrorType = ErrorTypes.RoundAlreadyStarted + } + }, HttpStatusCode.Conflict); + _sawmill.Debug("Forced round start failed: round already started"); + return true; + } + + await RunOnMainThread(() => + { + ticker.StartRound(); + }); + _sawmill.Info("Forced round start"); + break; + case "end": + if (ticker.RunLevel != GameRunLevel.InRound) + { + await context.RespondJsonAsync(new BaseResponse() + { + Message = "Round already ended", + Exception = new ExceptionData() + { + Message = "Round already ended", + ErrorType = ErrorTypes.RoundAlreadyEnded + } + }, HttpStatusCode.Conflict); + _sawmill.Debug("Forced round end failed: round is not in progress"); + return true; + } + await RunOnMainThread(() => + { + roundEndSystem.EndRound(); + }); + _sawmill.Info("Forced round end"); + break; + case "restart": + if (ticker.RunLevel != GameRunLevel.InRound) + { + await context.RespondJsonAsync(new BaseResponse() + { + Message = "Round not in progress", + Exception = new ExceptionData() + { + Message = "Round not in progress", + ErrorType = ErrorTypes.RoundNotInProgress + } + }, HttpStatusCode.Conflict); + _sawmill.Debug("Forced round restart failed: round is not in progress"); + return true; + } + await RunOnMainThread(() => + { + roundEndSystem.EndRound(); + }); + _sawmill.Info("Forced round restart"); + break; + case "restartnow": // You should restart yourself NOW!!! + await RunOnMainThread(() => + { + ticker.RestartRound(); + }); + _sawmill.Info("Forced instant round restart"); + break; + default: + return false; + } + + _sawmill.Info($"Round {action} by {actor!.Name} ({actor.Guid})."); + await context.RespondJsonAsync(new BaseResponse() + { + Message = "OK" + }); + return true; + } +#endregion + +#region Fetching + + /// + /// Returns an array containing all available presets. + /// + private async Task GetForcePresets(IStatusHandlerContext context) + { + if (context.RequestMethod != HttpMethod.Get || context.Url.AbsolutePath != "/admin/force_presets") + { + return false; + } + + if (!CheckAccess(context)) + return true; + + var presets = new List<(string id, string desc)>(); + foreach (var preset in _prototypeManager.EnumeratePrototypes()) + { + presets.Add((preset.ID, preset.Description)); + } + + await context.RespondJsonAsync(new PresetResponse() + { + Presets = presets + }); + return true; + } + + /// + /// Returns an array containing all game rules. + /// + private async Task GetGameRules(IStatusHandlerContext context) + { + if (context.RequestMethod != HttpMethod.Get || context.Url.AbsolutePath != "/admin/game_rules") + { + return false; + } + + if (!CheckAccess(context)) + return true; + + var gameRules = new List(); + foreach (var gameRule in _prototypeManager.EnumeratePrototypes()) + { + if (gameRule.Abstract) + continue; + + if (gameRule.HasComponent(_componentFactory)) + gameRules.Add(gameRule.ID); + } + + await context.RespondJsonAsync(new GameruleResponse() + { + GameRules = gameRules + }); + return true; + } + + + /// + /// Handles fetching information. + /// + private async Task InfoHandler(IStatusHandlerContext context) + { + if (context.RequestMethod != HttpMethod.Get || context.Url.AbsolutePath != "/admin/info") + { + return false; + } + + if (!CheckAccess(context)) + return true; + + var (success, actor) = await CheckActor(context); + if (!success) + return true; + + /* Information to display + Round number + Connected players + Active admins + Active game rules + Active game preset + Active map + MOTD + Panic bunker status + */ + + var (ticker, adminSystem) = await RunOnMainThread(() => + { + var ticker = _entitySystemManager.GetEntitySystem(); + var adminSystem = _entitySystemManager.GetEntitySystem(); + return (ticker, adminSystem); + }); + + var players = new List(); + await RunOnMainThread(async () => + { + foreach (var player in _playerManager.Sessions) + { + var isAdmin = _adminManager.IsAdmin(player); + var isDeadmined = _adminManager.IsAdmin(player, true) && !isAdmin; + + players.Add(new Actor() + { + Guid = player.UserId.UserId.ToString(), + Name = player.Name, + IsAdmin = isAdmin, + IsDeadmined = isDeadmined + }); + } + }); + var gameRules = await RunOnMainThread(() => + { + var gameRules = new List(); + foreach (var addedGameRule in ticker.GetActiveGameRules()) + { + var meta = _entityManager.MetaQuery.GetComponent(addedGameRule); + gameRules.Add(meta.EntityPrototype?.ID ?? meta.EntityPrototype?.Name ?? "Unknown"); + } + + return gameRules; + }); + + _sawmill.Info($"Info requested by {actor!.Name} ({actor.Guid})."); + await context.RespondJsonAsync(new InfoResponse() + { + Players = players, + RoundId = ticker.RoundId, + Map = await RunOnMainThread(() => _gameMapManager.GetSelectedMap()?.MapName ?? "Unknown"), + PanicBunker = adminSystem.PanicBunker, + GamePreset = ticker.CurrentPreset?.ID, + GameRules = gameRules, + MOTD = _config.GetCVar(CCVars.MOTD) + }); + return true; + } + +#endregion + + private bool CheckAccess(IStatusHandlerContext context) + { + var auth = context.RequestHeaders.TryGetValue("Authorization", out var authToken); + if (!auth) + { + context.RespondJsonAsync(new BaseResponse() + { + Message = "An authorization header is required to perform this action.", + Exception = new ExceptionData() + { + Message = "An authorization header is required to perform this action.", + ErrorType = ErrorTypes.MissingAuthentication + } + }); + return false; + } + + + if (CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(authToken.ToString()), Encoding.UTF8.GetBytes(_token))) + return true; + + context.RespondJsonAsync(new BaseResponse() + { + Message = "Invalid authorization header.", + Exception = new ExceptionData() + { + Message = "Invalid authorization header.", + ErrorType = ErrorTypes.InvalidAuthentication + } + }); + // Invalid auth header, no access + _sawmill.Info("Unauthorized access attempt to admin API."); + return false; + } + + /// + /// Async helper function which runs a task on the main thread and returns the result. + /// + private async Task RunOnMainThread(Func func) + { + var taskCompletionSource = new TaskCompletionSource(); + _taskManager.RunOnMainThread(() => + { + try + { + taskCompletionSource.TrySetResult(func()); + } + catch (Exception e) + { + taskCompletionSource.TrySetException(e); + } + }); + + var result = await taskCompletionSource.Task; + return result; + } + + /// + /// Runs an action on the main thread. This does not return any value and is meant to be used for void functions. Use for functions that return a value. + /// + private async Task RunOnMainThread(Action action) + { + var taskCompletionSource = new TaskCompletionSource(); + _taskManager.RunOnMainThread(() => + { + try + { + action(); + taskCompletionSource.TrySetResult(true); + } + catch (Exception e) + { + taskCompletionSource.TrySetException(e); + } + }); + + await taskCompletionSource.Task; + } + + private async Task<(bool, Actor? actor)> CheckActor(IStatusHandlerContext context) + { + // The actor is JSON encoded in the header + var actor = context.RequestHeaders.TryGetValue("Actor", out var actorHeader) ? actorHeader.ToString() : null; + if (actor != null) + { + var actionData = JsonSerializer.Deserialize(actor); + if (actionData == null) + { + await context.RespondJsonAsync(new BaseResponse() + { + Message = "Unable to parse actor.", + Exception = new ExceptionData() + { + Message = "Unable to parse actor.", + ErrorType = ErrorTypes.BodyUnableToParse + } + }, HttpStatusCode.BadRequest); + return (false, null); + } + // Check if the actor is valid, like if all the required fields are present + if (string.IsNullOrWhiteSpace(actionData.Guid) || string.IsNullOrWhiteSpace(actionData.Name)) + { + await context.RespondJsonAsync(new BaseResponse() + { + Message = "Invalid actor supplied.", + Exception = new ExceptionData() + { + Message = "Invalid actor supplied.", + ErrorType = ErrorTypes.InvalidActor + } + }, HttpStatusCode.BadRequest); + return (false, null); + } + + // See if the parsed GUID is a valid GUID + if (!Guid.TryParse(actionData.Guid, out _)) + { + await context.RespondJsonAsync(new BaseResponse() + { + Message = "Invalid GUID supplied.", + Exception = new ExceptionData() + { + Message = "Invalid GUID supplied.", + ErrorType = ErrorTypes.InvalidActor + } + }, HttpStatusCode.BadRequest); + return (false, null); + } + + return (true, actionData); + } + + await context.RespondJsonAsync(new BaseResponse() + { + Message = "An actor is required to perform this action.", + Exception = new ExceptionData() + { + Message = "An actor is required to perform this action.", + ErrorType = ErrorTypes.MissingActor + } + }, HttpStatusCode.BadRequest); + return (false, null); + } + + /// + /// Helper function to read JSON encoded data from the request body. + /// + private async Task ReadJson(IStatusHandlerContext context) + { + try + { + var json = await context.RequestBodyJsonAsync(); + return json; + } + catch (Exception e) + { + await context.RespondJsonAsync(new BaseResponse() + { + Message = "Unable to parse request body.", + Exception = new ExceptionData() + { + Message = e.Message, + ErrorType = ErrorTypes.BodyUnableToParse, + StackTrace = e.StackTrace + } + }, HttpStatusCode.BadRequest); + return default; + } + } + +#region From Client + + private record Actor + { + public string? Guid { get; init; } + public string? Name { get; init; } + public bool IsAdmin { get; init; } = false; + public bool IsDeadmined { get; init; } = false; + } + + private record KickActionBody + { + public string? Guid { get; init; } + public string? Reason { get; init; } + } + + private record GameRuleActionBody + { + public string? GameRuleId { get; init; } + } + + private record PresetActionBody + { + public string? PresetId { get; init; } + } + + private record MotdActionBody + { + public string? Motd { get; init; } + } + +#endregion + +#region Responses + + private record BaseResponse + { + public string? Message { get; init; } = "OK"; + public ExceptionData? Exception { get; init; } = null; + } + + private record ExceptionData + { + public string Message { get; init; } = string.Empty; + public ErrorTypes ErrorType { get; init; } = ErrorTypes.None; + public string? StackTrace { get; init; } = null; + } + + private enum ErrorTypes + { + BodyUnableToParse = -2, + None = -1, + MissingAuthentication = 0, + InvalidAuthentication = 1, + MissingActor = 2, + InvalidActor = 3, + RoundNotInProgress = 4, + RoundAlreadyStarted = 5, + RoundAlreadyEnded = 6, + ActionNotSpecified = 7, + ActionNotSupported = 8, + GuidNotSpecified = 9, + PlayerNotFound = 10, + GameRuleNotFound = 11, + PresetNotSpecified = 12, + MotdNotSpecified = 13 + } + +#endregion + +#region Misc + + /// + /// Record used to send the response for the info endpoint. + /// + private record InfoResponse + { + public int RoundId { get; init; } = 0; + public List Players { get; init; } = new(); + public List GameRules { get; init; } = new(); + public string? GamePreset { get; init; } = null; + public string? Map { get; init; } = null; + public string? MOTD { get; init; } = null; + public PanicBunkerStatus PanicBunker { get; init; } = new(); + } + + private record PresetResponse : BaseResponse + { + public List<(string id, string desc)> Presets { get; init; } = new(); + } + + private record GameruleResponse : BaseResponse + { + public List GameRules { get; init; } = new(); + } + +#endregion + +} diff --git a/Content.Server/Administration/Systems/AdminSystem.cs b/Content.Server/Administration/Systems/AdminSystem.cs index 53eabb1a481..82bbe6e266b 100644 --- a/Content.Server/Administration/Systems/AdminSystem.cs +++ b/Content.Server/Administration/Systems/AdminSystem.cs @@ -61,7 +61,7 @@ public sealed class AdminSystem : EntitySystem public IReadOnlySet RoundActivePlayers => _roundActivePlayers; private readonly HashSet _roundActivePlayers = new(); - private readonly PanicBunkerStatus _panicBunker = new(); + public readonly PanicBunkerStatus PanicBunker = new(); public override void Initialize() { @@ -240,7 +240,7 @@ private PlayerInfo GetPlayerInfo(SessionData data, ICommonSession? session) private void OnPanicBunkerChanged(bool enabled) { - _panicBunker.Enabled = enabled; + PanicBunker.Enabled = enabled; _chat.SendAdminAlert(Loc.GetString(enabled ? "admin-ui-panic-bunker-enabled-admin-alert" : "admin-ui-panic-bunker-disabled-admin-alert" @@ -251,52 +251,52 @@ private void OnPanicBunkerChanged(bool enabled) private void OnPanicBunkerDisableWithAdminsChanged(bool enabled) { - _panicBunker.DisableWithAdmins = enabled; + PanicBunker.DisableWithAdmins = enabled; UpdatePanicBunker(); } private void OnPanicBunkerEnableWithoutAdminsChanged(bool enabled) { - _panicBunker.EnableWithoutAdmins = enabled; + PanicBunker.EnableWithoutAdmins = enabled; UpdatePanicBunker(); } private void OnPanicBunkerCountDeadminnedAdminsChanged(bool enabled) { - _panicBunker.CountDeadminnedAdmins = enabled; + PanicBunker.CountDeadminnedAdmins = enabled; UpdatePanicBunker(); } private void OnShowReasonChanged(bool enabled) { - _panicBunker.ShowReason = enabled; + PanicBunker.ShowReason = enabled; SendPanicBunkerStatusAll(); } private void OnPanicBunkerMinAccountAgeChanged(int minutes) { - _panicBunker.MinAccountAgeHours = minutes / 60; + PanicBunker.MinAccountAgeHours = minutes / 60; SendPanicBunkerStatusAll(); } private void OnPanicBunkerMinOverallHoursChanged(int hours) { - _panicBunker.MinOverallHours = hours; + PanicBunker.MinOverallHours = hours; SendPanicBunkerStatusAll(); } private void UpdatePanicBunker() { - var admins = _panicBunker.CountDeadminnedAdmins + var admins = PanicBunker.CountDeadminnedAdmins ? _adminManager.AllAdmins : _adminManager.ActiveAdmins; var hasAdmins = admins.Any(); - if (hasAdmins && _panicBunker.DisableWithAdmins) + if (hasAdmins && PanicBunker.DisableWithAdmins) { _config.SetCVar(CCVars.PanicBunkerEnabled, false); } - else if (!hasAdmins && _panicBunker.EnableWithoutAdmins) + else if (!hasAdmins && PanicBunker.EnableWithoutAdmins) { _config.SetCVar(CCVars.PanicBunkerEnabled, true); } @@ -306,7 +306,7 @@ private void UpdatePanicBunker() private void SendPanicBunkerStatusAll() { - var ev = new PanicBunkerChangedEvent(_panicBunker); + var ev = new PanicBunkerChangedEvent(PanicBunker); foreach (var admin in _adminManager.AllAdmins) { RaiseNetworkEvent(ev, admin); diff --git a/Content.Server/Entry/EntryPoint.cs b/Content.Server/Entry/EntryPoint.cs index 28687d17cd7..a04f274491c 100644 --- a/Content.Server/Entry/EntryPoint.cs +++ b/Content.Server/Entry/EntryPoint.cs @@ -106,6 +106,7 @@ public override void Init() IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); + IoCManager.Resolve().Initialize(); _voteManager.Initialize(); _updateManager.Initialize(); @@ -171,6 +172,7 @@ protected override void Dispose(bool disposing) { _playTimeTracking?.Shutdown(); _dbManager?.Shutdown(); + IoCManager.Resolve().Shutdown(); } private static void LoadConfigPresets(IConfigurationManager cfg, IResourceManager res, ISawmill sawmill) diff --git a/Content.Server/IoC/ServerContentIoC.cs b/Content.Server/IoC/ServerContentIoC.cs index 4c1e522e9d2..6d9eb819474 100644 --- a/Content.Server/IoC/ServerContentIoC.cs +++ b/Content.Server/IoC/ServerContentIoC.cs @@ -62,6 +62,7 @@ public static void Register() IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); + IoCManager.Register(); } } } diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index 83dbe17cc06..8534c7c9e8e 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -859,6 +859,13 @@ public static readonly CVarDef public static readonly CVarDef AdminAnnounceLogout = CVarDef.Create("admin.announce_logout", true, CVar.SERVERONLY); + /// + /// The token used to authenticate with the admin API. Leave empty to disable the admin API. This is a secret! Do not share! + /// + public static readonly CVarDef AdminApiToken = + CVarDef.Create("admin.api_token", string.Empty, CVar.SERVERONLY | CVar.CONFIDENTIAL); + + /// /// Should users be able to see their own notes? Admins will be able to see and set notes regardless /// From c424f0ad1f27e54d4b9507baf1541e92afa25515 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Wed, 10 Apr 2024 14:19:32 +0200 Subject: [PATCH 174/295] Revert "Game server api" (#26871) Revert "Game server api (#24015)" This reverts commit 297853929b7b3859760dcdda95e21888672ce8e1. --- Content.Server/Administration/ServerAPI.cs | 1033 ----------------- .../Administration/Systems/AdminSystem.cs | 24 +- Content.Server/Entry/EntryPoint.cs | 2 - Content.Server/IoC/ServerContentIoC.cs | 1 - Content.Shared/CCVar/CCVars.cs | 7 - 5 files changed, 12 insertions(+), 1055 deletions(-) delete mode 100644 Content.Server/Administration/ServerAPI.cs diff --git a/Content.Server/Administration/ServerAPI.cs b/Content.Server/Administration/ServerAPI.cs deleted file mode 100644 index d7591fb80c6..00000000000 --- a/Content.Server/Administration/ServerAPI.cs +++ /dev/null @@ -1,1033 +0,0 @@ -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Security.Cryptography; -using System.Text; -using System.Text.Json; -using System.Text.Json.Serialization; -using System.Threading.Tasks; -using Content.Server.Administration.Systems; -using Content.Server.GameTicking; -using Content.Server.GameTicking.Presets; -using Content.Server.GameTicking.Rules.Components; -using Content.Server.Maps; -using Content.Server.RoundEnd; -using Content.Shared.Administration.Events; -using Content.Shared.Administration.Managers; -using Content.Shared.CCVar; -using Content.Shared.Prototypes; -using Robust.Server.ServerStatus; -using Robust.Shared.Asynchronous; -using Robust.Shared.Configuration; -using Robust.Shared.Network; -using Robust.Shared.Player; -using Robust.Shared.Prototypes; -using Robust.Shared.Utility; - -namespace Content.Server.Administration; - -public sealed class ServerApi : IPostInjectInit -{ - [Dependency] private readonly IStatusHost _statusHost = default!; - [Dependency] private readonly IConfigurationManager _config = default!; - [Dependency] private readonly ISharedPlayerManager _playerManager = default!; // Players - [Dependency] private readonly ISharedAdminManager _adminManager = default!; // Admins - [Dependency] private readonly IGameMapManager _gameMapManager = default!; // Map name - [Dependency] private readonly IServerNetManager _netManager = default!; // Kick - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; // Game rules - [Dependency] private readonly IComponentFactory _componentFactory = default!; - [Dependency] private readonly ITaskManager _taskManager = default!; // game explodes when calling stuff from the non-game thread - [Dependency] private readonly EntityManager _entityManager = default!; - - [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; - - private string _token = string.Empty; - private ISawmill _sawmill = default!; - - public static Dictionary PanicPunkerCvarNames = new() - { - { "Enabled", "game.panic_bunker.enabled" }, - { "DisableWithAdmins", "game.panic_bunker.disable_with_admins" }, - { "EnableWithoutAdmins", "game.panic_bunker.enable_without_admins" }, - { "CountDeadminnedAdmins", "game.panic_bunker.count_deadminned_admins" }, - { "ShowReason", "game.panic_bunker.show_reason" }, - { "MinAccountAgeHours", "game.panic_bunker.min_account_age" }, - { "MinOverallHours", "game.panic_bunker.min_overall_hours" }, - { "CustomReason", "game.panic_bunker.custom_reason" } - }; - - void IPostInjectInit.PostInject() - { - _sawmill = Logger.GetSawmill("serverApi"); - - // Get - _statusHost.AddHandler(InfoHandler); - _statusHost.AddHandler(GetGameRules); - _statusHost.AddHandler(GetForcePresets); - - // Post - _statusHost.AddHandler(ActionRoundStatus); - _statusHost.AddHandler(ActionKick); - _statusHost.AddHandler(ActionAddGameRule); - _statusHost.AddHandler(ActionEndGameRule); - _statusHost.AddHandler(ActionForcePreset); - _statusHost.AddHandler(ActionForceMotd); - _statusHost.AddHandler(ActionPanicPunker); - } - - public void Initialize() - { - _config.OnValueChanged(CCVars.AdminApiToken, UpdateToken, true); - } - - public void Shutdown() - { - _config.UnsubValueChanged(CCVars.AdminApiToken, UpdateToken); - } - - private void UpdateToken(string token) - { - _token = token; - } - - -#region Actions - - /// - /// Changes the panic bunker settings. - /// - private async Task ActionPanicPunker(IStatusHandlerContext context) - { - if (context.RequestMethod != HttpMethod.Patch || context.Url.AbsolutePath != "/admin/actions/panic_bunker") - { - return false; - } - - if (!CheckAccess(context)) - return true; - - var body = await ReadJson>(context); - var (success, actor) = await CheckActor(context); - if (!success) - return true; - - foreach (var panicPunkerActions in body!.Select(x => new { Action = x.Key, Value = x.Value.ToString() })) - { - if (panicPunkerActions.Action == null || panicPunkerActions.Value == null) - { - await context.RespondJsonAsync(new BaseResponse() - { - Message = "Action and value are required to perform this action.", - Exception = new ExceptionData() - { - Message = "Action and value are required to perform this action.", - ErrorType = ErrorTypes.ActionNotSpecified - } - }, HttpStatusCode.BadRequest); - return true; - } - - if (!PanicPunkerCvarNames.TryGetValue(panicPunkerActions.Action, out var cvarName)) - { - await context.RespondJsonAsync(new BaseResponse() - { - Message = $"Cannot set: Action {panicPunkerActions.Action} does not exist.", - Exception = new ExceptionData() - { - Message = $"Cannot set: Action {panicPunkerActions.Action} does not exist.", - ErrorType = ErrorTypes.ActionNotSupported - } - }, HttpStatusCode.BadRequest); - return true; - } - - // Since the CVar can be of different types, we need to parse it to the correct type - // First, I try to parse it as a bool, if it fails, I try to parse it as an int - // And as a last resort, I do nothing and put it as a string - if (bool.TryParse(panicPunkerActions.Value, out var boolValue)) - { - await RunOnMainThread(() => _config.SetCVar(cvarName, boolValue)); - } - else if (int.TryParse(panicPunkerActions.Value, out var intValue)) - { - await RunOnMainThread(() => _config.SetCVar(cvarName, intValue)); - } - else - { - await RunOnMainThread(() => _config.SetCVar(cvarName, panicPunkerActions.Value)); - } - _sawmill.Info($"Panic bunker property {panicPunkerActions} changed to {panicPunkerActions.Value} by {actor!.Name} ({actor.Guid})."); - } - - await context.RespondJsonAsync(new BaseResponse() - { - Message = "OK" - }); - return true; - } - - /// - /// Sets the current MOTD. - /// - private async Task ActionForceMotd(IStatusHandlerContext context) - { - if (context.RequestMethod != HttpMethod.Post || context.Url.AbsolutePath != "/admin/actions/set_motd") - { - return false; - } - - if (!CheckAccess(context)) - return true; - - var motd = await ReadJson(context); - var (success, actor) = await CheckActor(context); - if (!success) - return true; - - - if (motd!.Motd == null) - { - await context.RespondJsonAsync(new BaseResponse() - { - Message = "A motd is required to perform this action.", - Exception = new ExceptionData() - { - Message = "A motd is required to perform this action.", - ErrorType = ErrorTypes.MotdNotSpecified - } - }, HttpStatusCode.BadRequest); - return true; - } - - _sawmill.Info($"MOTD changed to \"{motd.Motd}\" by {actor!.Name} ({actor.Guid})."); - - await RunOnMainThread(() => _config.SetCVar(CCVars.MOTD, motd.Motd)); - // A hook in the MOTD system sends the changes to each client - await context.RespondJsonAsync(new BaseResponse() - { - Message = "OK" - }); - return true; - } - - /// - /// Forces the next preset- - /// - private async Task ActionForcePreset(IStatusHandlerContext context) - { - if (context.RequestMethod != HttpMethod.Post || context.Url.AbsolutePath != "/admin/actions/force_preset") - { - return false; - } - - if (!CheckAccess(context)) - return true; - - var body = await ReadJson(context); - var (success, actor) = await CheckActor(context); - if (!success) - return true; - - var ticker = await RunOnMainThread(() => _entitySystemManager.GetEntitySystem()); - - if (ticker.RunLevel != GameRunLevel.PreRoundLobby) - { - await context.RespondJsonAsync(new BaseResponse() - { - Message = "Round already started", - Exception = new ExceptionData() - { - Message = "Round already started", - ErrorType = ErrorTypes.RoundAlreadyStarted - } - }, HttpStatusCode.Conflict); - return true; - } - - if (body!.PresetId == null) - { - await context.RespondJsonAsync(new BaseResponse() - { - Message = "A preset is required to perform this action.", - Exception = new ExceptionData() - { - Message = "A preset is required to perform this action.", - ErrorType = ErrorTypes.PresetNotSpecified - } - }, HttpStatusCode.BadRequest); - return true; - } - - var result = await RunOnMainThread(() => ticker.FindGamePreset(body.PresetId)); - if (result == null) - { - await context.RespondJsonAsync(new BaseResponse() - { - Message = "Preset not found", - Exception = new ExceptionData() - { - Message = "Preset not found", - ErrorType = ErrorTypes.PresetNotSpecified - } - }, HttpStatusCode.UnprocessableContent); - return true; - } - - await RunOnMainThread(() => - { - ticker.SetGamePreset(result); - }); - _sawmill.Info($"Forced the game to start with preset {body.PresetId} by {actor!.Name}({actor.Guid})."); - await context.RespondJsonAsync(new BaseResponse() - { - Message = "OK" - }); - return true; - } - - /// - /// Ends an active game rule. - /// - private async Task ActionEndGameRule(IStatusHandlerContext context) - { - if (context.RequestMethod != HttpMethod.Post || context.Url.AbsolutePath != "/admin/actions/end_game_rule") - { - return false; - } - - if (!CheckAccess(context)) - return true; - - var body = await ReadJson(context); - var (success, actor) = await CheckActor(context); - if (!success) - return true; - - if (body!.GameRuleId == null) - { - await context.RespondJsonAsync(new BaseResponse() - { - Message = "A game rule is required to perform this action.", - Exception = new ExceptionData() - { - Message = "A game rule is required to perform this action.", - ErrorType = ErrorTypes.GuidNotSpecified - } - }, HttpStatusCode.BadRequest); - return true; - } - var ticker = await RunOnMainThread(() => _entitySystemManager.GetEntitySystem()); - - var gameRuleEntity = await RunOnMainThread(() => ticker - .GetActiveGameRules() - .FirstOrNull(rule => _entityManager.MetaQuery.GetComponent(rule).EntityPrototype?.ID == body.GameRuleId)); - - if (gameRuleEntity == null) // Game rule not found - { - await context.RespondJsonAsync(new BaseResponse() - { - Message = "Game rule not found or not active", - Exception = new ExceptionData() - { - Message = "Game rule not found or not active", - ErrorType = ErrorTypes.GameRuleNotFound - } - }, HttpStatusCode.Conflict); - return true; - } - - _sawmill.Info($"Ended game rule {body.GameRuleId} by {actor!.Name} ({actor.Guid})."); - await RunOnMainThread(() => ticker.EndGameRule((EntityUid) gameRuleEntity)); - await context.RespondJsonAsync(new BaseResponse() - { - Message = "OK" - }); - return true; - } - - /// - /// Adds a game rule to the current round. - /// - private async Task ActionAddGameRule(IStatusHandlerContext context) - { - if (context.RequestMethod != HttpMethod.Post || context.Url.AbsolutePath != "/admin/actions/add_game_rule") - { - return false; - } - - if (!CheckAccess(context)) - return true; - - var body = await ReadJson(context); - var (success, actor) = await CheckActor(context); - if (!success) - return true; - - if (body!.GameRuleId == null) - { - await context.RespondJsonAsync(new BaseResponse() - { - Message = "A game rule is required to perform this action.", - Exception = new ExceptionData() - { - Message = "A game rule is required to perform this action.", - ErrorType = ErrorTypes.GuidNotSpecified - } - }, HttpStatusCode.BadRequest); - return true; - } - - var ruleEntity = await RunOnMainThread(() => - { - var ticker = _entitySystemManager.GetEntitySystem(); - // See if prototype exists - try - { - _prototypeManager.Index(body.GameRuleId); - } - catch (KeyNotFoundException e) - { - return null; - } - - var ruleEntity = ticker.AddGameRule(body.GameRuleId); - _sawmill.Info($"Added game rule {body.GameRuleId} by {actor!.Name} ({actor.Guid})."); - if (ticker.RunLevel == GameRunLevel.InRound) - { - ticker.StartGameRule(ruleEntity); - _sawmill.Info($"Started game rule {body.GameRuleId} by {actor.Name} ({actor.Guid})."); - } - return ruleEntity; - }); - if (ruleEntity == null) - { - await context.RespondJsonAsync(new BaseResponse() - { - Message = "Game rule not found", - Exception = new ExceptionData() - { - Message = "Game rule not found", - ErrorType = ErrorTypes.GameRuleNotFound - } - }, HttpStatusCode.UnprocessableContent); - return true; - } - - await context.RespondJsonAsync(new BaseResponse() - { - Message = "OK" - }); - return true; - } - - /// - /// Kicks a player. - /// - private async Task ActionKick(IStatusHandlerContext context) - { - if (context.RequestMethod != HttpMethod.Post || context.Url.AbsolutePath != "/admin/actions/kick") - { - return false; - } - - if (!CheckAccess(context)) - return true; - - var body = await ReadJson(context); - var (success, actor) = await CheckActor(context); - if (!success) - return true; - - if (body == null) - { - _sawmill.Info($"Attempted to kick player without supplying a body by {actor!.Name}({actor.Guid})."); - await context.RespondJsonAsync(new BaseResponse() - { - Message = "A body is required to perform this action.", - Exception = new ExceptionData() - { - Message = "A body is required to perform this action.", - ErrorType = ErrorTypes.BodyUnableToParse - } - }, HttpStatusCode.BadRequest); - return true; - } - - if (body.Guid == null) - { - _sawmill.Info($"Attempted to kick player without supplying a username by {actor!.Name}({actor.Guid})."); - await context.RespondJsonAsync(new BaseResponse() - { - Message = "A player is required to perform this action.", - Exception = new ExceptionData() - { - Message = "A player is required to perform this action.", - ErrorType = ErrorTypes.GuidNotSpecified - } - }, HttpStatusCode.BadRequest); - return true; - } - - var session = await RunOnMainThread(() => - { - _playerManager.TryGetSessionById(new NetUserId(new Guid(body.Guid)), out var player); - return player; - }); - - if (session == null) - { - _sawmill.Info($"Attempted to kick player {body.Guid} by {actor!.Name} ({actor.Guid}), but they were not found."); - await context.RespondJsonAsync(new BaseResponse() - { - Message = "Player not found", - Exception = new ExceptionData() - { - Message = "Player not found", - ErrorType = ErrorTypes.PlayerNotFound - } - }, HttpStatusCode.UnprocessableContent); - return true; - } - - var reason = body.Reason ?? "No reason supplied"; - reason += " (kicked by admin)"; - - await RunOnMainThread(() => - { - _netManager.DisconnectChannel(session.Channel, reason); - }); - await context.RespondJsonAsync(new BaseResponse() - { - Message = "OK" - }); - _sawmill.Info("Kicked player {0} ({1}) for {2} by {3}({4})", session.Name, session.UserId.UserId.ToString(), reason, actor!.Name, actor.Guid); - return true; - } - - /// - /// Round restart/end - /// - private async Task ActionRoundStatus(IStatusHandlerContext context) - { - if (context.RequestMethod != HttpMethod.Post || !context.Url.AbsolutePath.StartsWith("/admin/actions/round/")) - { - return false; - } - - // Make sure paths like /admin/actions/round/lol/start don't work - if (context.Url.AbsolutePath.Split('/').Length != 5) - { - return false; - } - - if (!CheckAccess(context)) - return true; - var (success, actor) = await CheckActor(context); - if (!success) - return true; - - - var (ticker, roundEndSystem) = await RunOnMainThread(() => - { - var ticker = _entitySystemManager.GetEntitySystem(); - var roundEndSystem = _entitySystemManager.GetEntitySystem(); - return (ticker, roundEndSystem); - }); - - // Action is the last part of the URL path (e.g. /admin/actions/round/start -> start) - var action = context.Url.AbsolutePath.Split('/').Last(); - - switch (action) - { - case "start": - if (ticker.RunLevel != GameRunLevel.PreRoundLobby) - { - await context.RespondJsonAsync(new BaseResponse() - { - Message = "Round already started", - Exception = new ExceptionData() - { - Message = "Round already started", - ErrorType = ErrorTypes.RoundAlreadyStarted - } - }, HttpStatusCode.Conflict); - _sawmill.Debug("Forced round start failed: round already started"); - return true; - } - - await RunOnMainThread(() => - { - ticker.StartRound(); - }); - _sawmill.Info("Forced round start"); - break; - case "end": - if (ticker.RunLevel != GameRunLevel.InRound) - { - await context.RespondJsonAsync(new BaseResponse() - { - Message = "Round already ended", - Exception = new ExceptionData() - { - Message = "Round already ended", - ErrorType = ErrorTypes.RoundAlreadyEnded - } - }, HttpStatusCode.Conflict); - _sawmill.Debug("Forced round end failed: round is not in progress"); - return true; - } - await RunOnMainThread(() => - { - roundEndSystem.EndRound(); - }); - _sawmill.Info("Forced round end"); - break; - case "restart": - if (ticker.RunLevel != GameRunLevel.InRound) - { - await context.RespondJsonAsync(new BaseResponse() - { - Message = "Round not in progress", - Exception = new ExceptionData() - { - Message = "Round not in progress", - ErrorType = ErrorTypes.RoundNotInProgress - } - }, HttpStatusCode.Conflict); - _sawmill.Debug("Forced round restart failed: round is not in progress"); - return true; - } - await RunOnMainThread(() => - { - roundEndSystem.EndRound(); - }); - _sawmill.Info("Forced round restart"); - break; - case "restartnow": // You should restart yourself NOW!!! - await RunOnMainThread(() => - { - ticker.RestartRound(); - }); - _sawmill.Info("Forced instant round restart"); - break; - default: - return false; - } - - _sawmill.Info($"Round {action} by {actor!.Name} ({actor.Guid})."); - await context.RespondJsonAsync(new BaseResponse() - { - Message = "OK" - }); - return true; - } -#endregion - -#region Fetching - - /// - /// Returns an array containing all available presets. - /// - private async Task GetForcePresets(IStatusHandlerContext context) - { - if (context.RequestMethod != HttpMethod.Get || context.Url.AbsolutePath != "/admin/force_presets") - { - return false; - } - - if (!CheckAccess(context)) - return true; - - var presets = new List<(string id, string desc)>(); - foreach (var preset in _prototypeManager.EnumeratePrototypes()) - { - presets.Add((preset.ID, preset.Description)); - } - - await context.RespondJsonAsync(new PresetResponse() - { - Presets = presets - }); - return true; - } - - /// - /// Returns an array containing all game rules. - /// - private async Task GetGameRules(IStatusHandlerContext context) - { - if (context.RequestMethod != HttpMethod.Get || context.Url.AbsolutePath != "/admin/game_rules") - { - return false; - } - - if (!CheckAccess(context)) - return true; - - var gameRules = new List(); - foreach (var gameRule in _prototypeManager.EnumeratePrototypes()) - { - if (gameRule.Abstract) - continue; - - if (gameRule.HasComponent(_componentFactory)) - gameRules.Add(gameRule.ID); - } - - await context.RespondJsonAsync(new GameruleResponse() - { - GameRules = gameRules - }); - return true; - } - - - /// - /// Handles fetching information. - /// - private async Task InfoHandler(IStatusHandlerContext context) - { - if (context.RequestMethod != HttpMethod.Get || context.Url.AbsolutePath != "/admin/info") - { - return false; - } - - if (!CheckAccess(context)) - return true; - - var (success, actor) = await CheckActor(context); - if (!success) - return true; - - /* Information to display - Round number - Connected players - Active admins - Active game rules - Active game preset - Active map - MOTD - Panic bunker status - */ - - var (ticker, adminSystem) = await RunOnMainThread(() => - { - var ticker = _entitySystemManager.GetEntitySystem(); - var adminSystem = _entitySystemManager.GetEntitySystem(); - return (ticker, adminSystem); - }); - - var players = new List(); - await RunOnMainThread(async () => - { - foreach (var player in _playerManager.Sessions) - { - var isAdmin = _adminManager.IsAdmin(player); - var isDeadmined = _adminManager.IsAdmin(player, true) && !isAdmin; - - players.Add(new Actor() - { - Guid = player.UserId.UserId.ToString(), - Name = player.Name, - IsAdmin = isAdmin, - IsDeadmined = isDeadmined - }); - } - }); - var gameRules = await RunOnMainThread(() => - { - var gameRules = new List(); - foreach (var addedGameRule in ticker.GetActiveGameRules()) - { - var meta = _entityManager.MetaQuery.GetComponent(addedGameRule); - gameRules.Add(meta.EntityPrototype?.ID ?? meta.EntityPrototype?.Name ?? "Unknown"); - } - - return gameRules; - }); - - _sawmill.Info($"Info requested by {actor!.Name} ({actor.Guid})."); - await context.RespondJsonAsync(new InfoResponse() - { - Players = players, - RoundId = ticker.RoundId, - Map = await RunOnMainThread(() => _gameMapManager.GetSelectedMap()?.MapName ?? "Unknown"), - PanicBunker = adminSystem.PanicBunker, - GamePreset = ticker.CurrentPreset?.ID, - GameRules = gameRules, - MOTD = _config.GetCVar(CCVars.MOTD) - }); - return true; - } - -#endregion - - private bool CheckAccess(IStatusHandlerContext context) - { - var auth = context.RequestHeaders.TryGetValue("Authorization", out var authToken); - if (!auth) - { - context.RespondJsonAsync(new BaseResponse() - { - Message = "An authorization header is required to perform this action.", - Exception = new ExceptionData() - { - Message = "An authorization header is required to perform this action.", - ErrorType = ErrorTypes.MissingAuthentication - } - }); - return false; - } - - - if (CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(authToken.ToString()), Encoding.UTF8.GetBytes(_token))) - return true; - - context.RespondJsonAsync(new BaseResponse() - { - Message = "Invalid authorization header.", - Exception = new ExceptionData() - { - Message = "Invalid authorization header.", - ErrorType = ErrorTypes.InvalidAuthentication - } - }); - // Invalid auth header, no access - _sawmill.Info("Unauthorized access attempt to admin API."); - return false; - } - - /// - /// Async helper function which runs a task on the main thread and returns the result. - /// - private async Task RunOnMainThread(Func func) - { - var taskCompletionSource = new TaskCompletionSource(); - _taskManager.RunOnMainThread(() => - { - try - { - taskCompletionSource.TrySetResult(func()); - } - catch (Exception e) - { - taskCompletionSource.TrySetException(e); - } - }); - - var result = await taskCompletionSource.Task; - return result; - } - - /// - /// Runs an action on the main thread. This does not return any value and is meant to be used for void functions. Use for functions that return a value. - /// - private async Task RunOnMainThread(Action action) - { - var taskCompletionSource = new TaskCompletionSource(); - _taskManager.RunOnMainThread(() => - { - try - { - action(); - taskCompletionSource.TrySetResult(true); - } - catch (Exception e) - { - taskCompletionSource.TrySetException(e); - } - }); - - await taskCompletionSource.Task; - } - - private async Task<(bool, Actor? actor)> CheckActor(IStatusHandlerContext context) - { - // The actor is JSON encoded in the header - var actor = context.RequestHeaders.TryGetValue("Actor", out var actorHeader) ? actorHeader.ToString() : null; - if (actor != null) - { - var actionData = JsonSerializer.Deserialize(actor); - if (actionData == null) - { - await context.RespondJsonAsync(new BaseResponse() - { - Message = "Unable to parse actor.", - Exception = new ExceptionData() - { - Message = "Unable to parse actor.", - ErrorType = ErrorTypes.BodyUnableToParse - } - }, HttpStatusCode.BadRequest); - return (false, null); - } - // Check if the actor is valid, like if all the required fields are present - if (string.IsNullOrWhiteSpace(actionData.Guid) || string.IsNullOrWhiteSpace(actionData.Name)) - { - await context.RespondJsonAsync(new BaseResponse() - { - Message = "Invalid actor supplied.", - Exception = new ExceptionData() - { - Message = "Invalid actor supplied.", - ErrorType = ErrorTypes.InvalidActor - } - }, HttpStatusCode.BadRequest); - return (false, null); - } - - // See if the parsed GUID is a valid GUID - if (!Guid.TryParse(actionData.Guid, out _)) - { - await context.RespondJsonAsync(new BaseResponse() - { - Message = "Invalid GUID supplied.", - Exception = new ExceptionData() - { - Message = "Invalid GUID supplied.", - ErrorType = ErrorTypes.InvalidActor - } - }, HttpStatusCode.BadRequest); - return (false, null); - } - - return (true, actionData); - } - - await context.RespondJsonAsync(new BaseResponse() - { - Message = "An actor is required to perform this action.", - Exception = new ExceptionData() - { - Message = "An actor is required to perform this action.", - ErrorType = ErrorTypes.MissingActor - } - }, HttpStatusCode.BadRequest); - return (false, null); - } - - /// - /// Helper function to read JSON encoded data from the request body. - /// - private async Task ReadJson(IStatusHandlerContext context) - { - try - { - var json = await context.RequestBodyJsonAsync(); - return json; - } - catch (Exception e) - { - await context.RespondJsonAsync(new BaseResponse() - { - Message = "Unable to parse request body.", - Exception = new ExceptionData() - { - Message = e.Message, - ErrorType = ErrorTypes.BodyUnableToParse, - StackTrace = e.StackTrace - } - }, HttpStatusCode.BadRequest); - return default; - } - } - -#region From Client - - private record Actor - { - public string? Guid { get; init; } - public string? Name { get; init; } - public bool IsAdmin { get; init; } = false; - public bool IsDeadmined { get; init; } = false; - } - - private record KickActionBody - { - public string? Guid { get; init; } - public string? Reason { get; init; } - } - - private record GameRuleActionBody - { - public string? GameRuleId { get; init; } - } - - private record PresetActionBody - { - public string? PresetId { get; init; } - } - - private record MotdActionBody - { - public string? Motd { get; init; } - } - -#endregion - -#region Responses - - private record BaseResponse - { - public string? Message { get; init; } = "OK"; - public ExceptionData? Exception { get; init; } = null; - } - - private record ExceptionData - { - public string Message { get; init; } = string.Empty; - public ErrorTypes ErrorType { get; init; } = ErrorTypes.None; - public string? StackTrace { get; init; } = null; - } - - private enum ErrorTypes - { - BodyUnableToParse = -2, - None = -1, - MissingAuthentication = 0, - InvalidAuthentication = 1, - MissingActor = 2, - InvalidActor = 3, - RoundNotInProgress = 4, - RoundAlreadyStarted = 5, - RoundAlreadyEnded = 6, - ActionNotSpecified = 7, - ActionNotSupported = 8, - GuidNotSpecified = 9, - PlayerNotFound = 10, - GameRuleNotFound = 11, - PresetNotSpecified = 12, - MotdNotSpecified = 13 - } - -#endregion - -#region Misc - - /// - /// Record used to send the response for the info endpoint. - /// - private record InfoResponse - { - public int RoundId { get; init; } = 0; - public List Players { get; init; } = new(); - public List GameRules { get; init; } = new(); - public string? GamePreset { get; init; } = null; - public string? Map { get; init; } = null; - public string? MOTD { get; init; } = null; - public PanicBunkerStatus PanicBunker { get; init; } = new(); - } - - private record PresetResponse : BaseResponse - { - public List<(string id, string desc)> Presets { get; init; } = new(); - } - - private record GameruleResponse : BaseResponse - { - public List GameRules { get; init; } = new(); - } - -#endregion - -} diff --git a/Content.Server/Administration/Systems/AdminSystem.cs b/Content.Server/Administration/Systems/AdminSystem.cs index 82bbe6e266b..53eabb1a481 100644 --- a/Content.Server/Administration/Systems/AdminSystem.cs +++ b/Content.Server/Administration/Systems/AdminSystem.cs @@ -61,7 +61,7 @@ public sealed class AdminSystem : EntitySystem public IReadOnlySet RoundActivePlayers => _roundActivePlayers; private readonly HashSet _roundActivePlayers = new(); - public readonly PanicBunkerStatus PanicBunker = new(); + private readonly PanicBunkerStatus _panicBunker = new(); public override void Initialize() { @@ -240,7 +240,7 @@ private PlayerInfo GetPlayerInfo(SessionData data, ICommonSession? session) private void OnPanicBunkerChanged(bool enabled) { - PanicBunker.Enabled = enabled; + _panicBunker.Enabled = enabled; _chat.SendAdminAlert(Loc.GetString(enabled ? "admin-ui-panic-bunker-enabled-admin-alert" : "admin-ui-panic-bunker-disabled-admin-alert" @@ -251,52 +251,52 @@ private void OnPanicBunkerChanged(bool enabled) private void OnPanicBunkerDisableWithAdminsChanged(bool enabled) { - PanicBunker.DisableWithAdmins = enabled; + _panicBunker.DisableWithAdmins = enabled; UpdatePanicBunker(); } private void OnPanicBunkerEnableWithoutAdminsChanged(bool enabled) { - PanicBunker.EnableWithoutAdmins = enabled; + _panicBunker.EnableWithoutAdmins = enabled; UpdatePanicBunker(); } private void OnPanicBunkerCountDeadminnedAdminsChanged(bool enabled) { - PanicBunker.CountDeadminnedAdmins = enabled; + _panicBunker.CountDeadminnedAdmins = enabled; UpdatePanicBunker(); } private void OnShowReasonChanged(bool enabled) { - PanicBunker.ShowReason = enabled; + _panicBunker.ShowReason = enabled; SendPanicBunkerStatusAll(); } private void OnPanicBunkerMinAccountAgeChanged(int minutes) { - PanicBunker.MinAccountAgeHours = minutes / 60; + _panicBunker.MinAccountAgeHours = minutes / 60; SendPanicBunkerStatusAll(); } private void OnPanicBunkerMinOverallHoursChanged(int hours) { - PanicBunker.MinOverallHours = hours; + _panicBunker.MinOverallHours = hours; SendPanicBunkerStatusAll(); } private void UpdatePanicBunker() { - var admins = PanicBunker.CountDeadminnedAdmins + var admins = _panicBunker.CountDeadminnedAdmins ? _adminManager.AllAdmins : _adminManager.ActiveAdmins; var hasAdmins = admins.Any(); - if (hasAdmins && PanicBunker.DisableWithAdmins) + if (hasAdmins && _panicBunker.DisableWithAdmins) { _config.SetCVar(CCVars.PanicBunkerEnabled, false); } - else if (!hasAdmins && PanicBunker.EnableWithoutAdmins) + else if (!hasAdmins && _panicBunker.EnableWithoutAdmins) { _config.SetCVar(CCVars.PanicBunkerEnabled, true); } @@ -306,7 +306,7 @@ private void UpdatePanicBunker() private void SendPanicBunkerStatusAll() { - var ev = new PanicBunkerChangedEvent(PanicBunker); + var ev = new PanicBunkerChangedEvent(_panicBunker); foreach (var admin in _adminManager.AllAdmins) { RaiseNetworkEvent(ev, admin); diff --git a/Content.Server/Entry/EntryPoint.cs b/Content.Server/Entry/EntryPoint.cs index a04f274491c..28687d17cd7 100644 --- a/Content.Server/Entry/EntryPoint.cs +++ b/Content.Server/Entry/EntryPoint.cs @@ -106,7 +106,6 @@ public override void Init() IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); - IoCManager.Resolve().Initialize(); _voteManager.Initialize(); _updateManager.Initialize(); @@ -172,7 +171,6 @@ protected override void Dispose(bool disposing) { _playTimeTracking?.Shutdown(); _dbManager?.Shutdown(); - IoCManager.Resolve().Shutdown(); } private static void LoadConfigPresets(IConfigurationManager cfg, IResourceManager res, ISawmill sawmill) diff --git a/Content.Server/IoC/ServerContentIoC.cs b/Content.Server/IoC/ServerContentIoC.cs index 6d9eb819474..4c1e522e9d2 100644 --- a/Content.Server/IoC/ServerContentIoC.cs +++ b/Content.Server/IoC/ServerContentIoC.cs @@ -62,7 +62,6 @@ public static void Register() IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); - IoCManager.Register(); } } } diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index 8534c7c9e8e..83dbe17cc06 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -859,13 +859,6 @@ public static readonly CVarDef public static readonly CVarDef AdminAnnounceLogout = CVarDef.Create("admin.announce_logout", true, CVar.SERVERONLY); - /// - /// The token used to authenticate with the admin API. Leave empty to disable the admin API. This is a secret! Do not share! - /// - public static readonly CVarDef AdminApiToken = - CVarDef.Create("admin.api_token", string.Empty, CVar.SERVERONLY | CVar.CONFIDENTIAL); - - /// /// Should users be able to see their own notes? Admins will be able to see and set notes regardless /// From cc4c725e80cca68e37689ecfe3295da606203271 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B6=D0=B5=D0=BA=D1=81=D0=BE=D0=BD=20=D0=9C=D0=B8?= =?UTF-8?q?=D1=81=D1=81=D0=B8=D1=81=D1=81=D0=B8=D0=BF=D0=BF=D0=B8?= Date: Wed, 10 Apr 2024 12:28:03 -0500 Subject: [PATCH 175/295] Give botanists droppers (#26839) Start botanists with droppers so that they can better dose robust harvest or mutagen. --- Resources/Prototypes/Catalog/Fills/Lockers/service.yml | 1 + .../Prototypes/Catalog/VendingMachines/Inventories/nutri.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/service.yml b/Resources/Prototypes/Catalog/Fills/Lockers/service.yml index 110d62f62aa..b86fa2e1f52 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/service.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/service.yml @@ -113,6 +113,7 @@ - id: ClothingBeltPlant - id: PlantBag ##Some maps don't have nutrivend - id: BoxMouthSwab + - id: Dropper - id: HandLabeler - id: ClothingUniformOveralls - id: ClothingHeadHatTrucker diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/nutri.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/nutri.yml index 31011057370..18f3a81ddbc 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/nutri.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/nutri.yml @@ -6,6 +6,7 @@ HydroponicsToolClippers: 3 HydroponicsToolScythe: 3 HydroponicsToolHatchet: 3 + Dropper: 3 PlantBag: 3 PlantBGoneSpray: 20 WeedSpray: 20 From bfa54bd42731e33540d67869c5eae22b6b52fa3e Mon Sep 17 00:00:00 2001 From: botanySupremist <160211017+botanySupremist@users.noreply.github.com> Date: Wed, 10 Apr 2024 10:51:25 -0700 Subject: [PATCH 176/295] Clipping a harvestable plant yields undamaged seeds (#25541) Clipping a plant in any condition currently causes it and its clippings to be damaged. Make clipping harvestable (already eligible for seed extractor) plants yield seeds at full health. --- .../Botany/Systems/PlantHolderSystem.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Content.Server/Botany/Systems/PlantHolderSystem.cs b/Content.Server/Botany/Systems/PlantHolderSystem.cs index 0f54fd0da50..721536a7c07 100644 --- a/Content.Server/Botany/Systems/PlantHolderSystem.cs +++ b/Content.Server/Botany/Systems/PlantHolderSystem.cs @@ -159,7 +159,6 @@ private void OnInteractUsing(Entity entity, ref InteractUs if (!_botany.TryGetSeed(seeds, out var seed)) return; - float? seedHealth = seeds.HealthOverride; var name = Loc.GetString(seed.Name); var noun = Loc.GetString(seed.Noun); _popup.PopupCursor(Loc.GetString("plant-holder-component-plant-success-message", @@ -169,9 +168,9 @@ private void OnInteractUsing(Entity entity, ref InteractUs component.Seed = seed; component.Dead = false; component.Age = 1; - if (seedHealth is float realSeedHealth) + if (seeds.HealthOverride != null) { - component.Health = realSeedHealth; + component.Health = seeds.HealthOverride.Value; } else { @@ -288,8 +287,18 @@ private void OnInteractUsing(Entity entity, ref InteractUs } component.Health -= (_random.Next(3, 5) * 10); + + float? healthOverride; + if (component.Harvest) + { + healthOverride = null; + } + else + { + healthOverride = component.Health; + } component.Seed.Unique = false; - var seed = _botany.SpawnSeedPacket(component.Seed, Transform(args.User).Coordinates, args.User, component.Health); + var seed = _botany.SpawnSeedPacket(component.Seed, Transform(args.User).Coordinates, args.User, healthOverride); _randomHelper.RandomOffset(seed, 0.25f); var displayName = Loc.GetString(component.Seed.DisplayName); _popup.PopupCursor(Loc.GetString("plant-holder-component-take-sample-message", From abe36a7a0e37278db94490602b5e963e43598d33 Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Wed, 10 Apr 2024 20:06:31 +0000 Subject: [PATCH 177/295] fix lots of door access (#26858) * dirty after calling SetAccesses * fix door access * D * pro ops * nukeop --------- Co-authored-by: deltanedas <@deltanedas:kde.org> --- .../Devices/Electronics/door_access.yml | 32 ++ .../Structures/Doors/Airlocks/access.yml | 286 +++++++++--------- .../Structures/Doors/Airlocks/airlocks.yml | 30 +- 3 files changed, 203 insertions(+), 145 deletions(-) diff --git a/Resources/Prototypes/Entities/Objects/Devices/Electronics/door_access.yml b/Resources/Prototypes/Entities/Objects/Devices/Electronics/door_access.yml index fc6d8e2697d..0c876ebd0ce 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Electronics/door_access.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Electronics/door_access.yml @@ -55,6 +55,14 @@ - type: AccessReader access: [["Hydroponics"]] +- type: entity + parent: DoorElectronics + id: DoorElectronicsLawyer + suffix: Lawyer, Locked + components: + - type: AccessReader + access: [["Lawyer"]] + - type: entity parent: DoorElectronics id: DoorElectronicsCaptain @@ -151,6 +159,14 @@ - type: AccessReader access: [["Command"]] +- type: entity + parent: DoorElectronics + id: DoorElectronicsCentralCommand + suffix: CentralCommand, Locked + components: + - type: AccessReader + access: [["CentralCommand"]] + - type: entity parent: DoorElectronics id: DoorElectronicsChiefMedicalOfficer @@ -207,6 +223,14 @@ - type: AccessReader access: [["Security"]] +- type: entity + parent: DoorElectronics + id: DoorElectronicsSecurityLawyer + suffix: Security/Lawyer, Locked + components: + - type: AccessReader + access: [["Security", "Lawyer"]] + - type: entity parent: DoorElectronics id: DoorElectronicsDetective @@ -255,6 +279,14 @@ - type: AccessReader access: [["SyndicateAgent"]] +- type: entity + parent: DoorElectronics + id: DoorElectronicsNukeop + suffix: Nukeop, Locked + components: + - type: AccessReader + access: [["NuclearOperative"]] + - type: entity parent: DoorElectronics id: DoorElectronicsRnDMed diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/access.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/access.yml index 6cb610886f5..2a1cc337fee 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/access.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/access.yml @@ -7,19 +7,20 @@ - type: ContainerFill containers: board: [ DoorElectronicsService ] + - type: Wires + layoutId: AirlockService - type: entity - parent: Airlock + parent: AirlockServiceLocked id: AirlockLawyerLocked suffix: Lawyer, Locked components: - - type: AccessReader - access: [["Lawyer"]] - - type: Wires - layoutId: AirlockService + - type: ContainerFill + containers: + board: [ DoorElectronicsLawyer ] - type: entity - parent: Airlock + parent: AirlockServiceLocked id: AirlockTheatreLocked suffix: Theatre, Locked components: @@ -37,7 +38,7 @@ board: [ DoorElectronicsChapel ] - type: entity - parent: Airlock + parent: AirlockServiceLocked id: AirlockJanitorLocked suffix: Janitor, Locked components: @@ -46,7 +47,7 @@ board: [ DoorElectronicsJanitor ] - type: entity - parent: Airlock + parent: AirlockServiceLocked id: AirlockKitchenLocked suffix: Kitchen, Locked components: @@ -55,7 +56,7 @@ board: [ DoorElectronicsKitchen ] - type: entity - parent: Airlock + parent: AirlockServiceLocked id: AirlockBarLocked suffix: Bar, Locked components: @@ -64,7 +65,7 @@ board: [ DoorElectronicsBar ] - type: entity - parent: Airlock + parent: AirlockServiceLocked id: AirlockHydroponicsLocked suffix: Hydroponics, Locked components: @@ -73,7 +74,7 @@ board: [ DoorElectronicsHydroponics ] - type: entity - parent: Airlock + parent: AirlockCommandLocked id: AirlockServiceCaptainLocked suffix: Captain, Locked components: @@ -122,16 +123,18 @@ id: AirlockExternalSyndicateLocked suffix: External, Syndicate, Locked components: - - type: AccessReader - access: [["SyndicateAgent"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsSyndicateAgent ] - type: entity parent: AirlockExternal id: AirlockExternalNukeopLocked suffix: External, Nukeop, Locked components: - - type: AccessReader - access: [["NuclearOperative"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsNukeop ] - type: entity parent: AirlockFreezer @@ -156,10 +159,9 @@ id: AirlockFreezerHydroponicsLocked suffix: Hydroponics, Locked components: - - type: AccessReader - access: [["Hydroponics"]] - - type: Wires - layoutId: AirlockService + - type: ContainerFill + containers: + board: [ DoorElectronicsHydroponics ] - type: entity parent: AirlockEngineering @@ -202,10 +204,9 @@ id: AirlockMiningLocked suffix: Mining(Salvage), Locked components: - - type: AccessReader - access: [["Salvage"]] - - type: Wires - layoutId: AirlockService + - type: ContainerFill + containers: + board: [ DoorElectronicsSalvage ] - type: entity parent: AirlockMedical @@ -265,10 +266,9 @@ id: AirlockCentralCommandLocked suffix: Central Command, Locked components: - - type: AccessReader - access: [["CentralCommand"]] - - type: Wires - layoutId: AirlockCommand + - type: ContainerFill + containers: + board: [ DoorElectronicsCentralCommand ] - type: entity parent: AirlockCommand @@ -278,8 +278,6 @@ - type: ContainerFill containers: board: [ DoorElectronicsCommand ] - - type: Wires - layoutId: AirlockCommand - type: entity parent: AirlockCommand @@ -352,8 +350,6 @@ - type: ContainerFill containers: board: [ DoorElectronicsSecurity ] - - type: Wires - layoutId: AirlockSecurity - type: entity parent: AirlockSecurity @@ -363,8 +359,6 @@ - type: ContainerFill containers: board: [ DoorElectronicsDetective ] - - type: Wires - layoutId: AirlockSecurity #Delta V: Removed Brig Access #- type: entity @@ -375,18 +369,15 @@ # - type: ContainerFill # containers: # board: [ DoorElectronicsBrig ] -# - type: Wires -# layoutId: AirlockSecurity - type: entity parent: AirlockSecurity id: AirlockSecurityLawyerLocked suffix: Security/Lawyer, Locked components: - - type: AccessReader - access: [["Security"], ["Lawyer"]] - - type: Wires - layoutId: AirlockSecurity + - type: ContainerFill + containers: + board: [ DoorElectronicsSecurityLawyer ] - type: entity parent: AirlockSecurity @@ -426,26 +417,26 @@ - type: ContainerFill containers: board: [ DoorElectronicsService ] + - type: Wires + layoutId: AirlockService - type: entity - parent: AirlockGlass + parent: AirlockServiceGlassLocked id: AirlockLawyerGlassLocked suffix: Lawyer, Locked components: - - type: AccessReader - access: [["Lawyer"]] - - type: Wires - layoutId: AirlockService + - type: ContainerFill + containers: + board: [ DoorElectronicsLawyer ] - type: entity - parent: AirlockGlass + parent: AirlockServiceGlassLocked id: AirlockTheatreGlassLocked suffix: Theatre, Locked components: - - type: AccessReader - access: [["Theatre"]] - - type: Wires - layoutId: AirlockService + - type: ContainerFill + containers: + board: [ DoorElectronicsTheatre ] - type: entity parent: AirlockGlass @@ -479,16 +470,18 @@ id: AirlockExternalGlassSyndicateLocked suffix: External, Glass, Syndicate, Locked components: - - type: AccessReader - access: [["SyndicateAgent"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsSyndicateAgent ] - type: entity parent: AirlockExternalGlass id: AirlockExternalGlassNukeopLocked suffix: External, Glass, Nukeop, Locked components: - - type: AccessReader - access: [["NuclearOperative"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsNukeop ] - type: entity parent: AirlockExternalGlass @@ -509,7 +502,7 @@ board: [ DoorElectronicsAtmospherics ] - type: entity - parent: AirlockGlass + parent: AirlockServiceGlassLocked id: AirlockKitchenGlassLocked suffix: Kitchen, Locked components: @@ -518,17 +511,16 @@ board: [ DoorElectronicsKitchen ] - type: entity - parent: AirlockGlass + parent: AirlockServiceGlassLocked id: AirlockJanitorGlassLocked suffix: Janitor, Locked components: - - type: AccessReader - access: [["Janitor"]] - - type: Wires - layoutId: AirlockService + - type: ContainerFill + containers: + board: [ DoorElectronicsJanitor ] - type: entity - parent: AirlockGlass + parent: AirlockServiceGlassLocked id: AirlockHydroGlassLocked suffix: Hydroponics, Locked components: @@ -586,20 +578,18 @@ id: AirlockMiningGlassLocked suffix: Mining(Salvage), Locked components: - - type: AccessReader - access: [["Salvage"]] - - type: Wires - layoutId: AirlockCargo + - type: ContainerFill + containers: + board: [ DoorElectronicsSalvage ] - type: entity parent: AirlockChemistryGlass id: AirlockChemistryGlassLocked suffix: Chemistry, Locked components: - - type: AccessReader - access: [["Chemistry"]] - - type: Wires - layoutId: AirlockMedical + - type: ContainerFill + containers: + board: [ DoorElectronicsChemistry ] - type: entity parent: AirlockMedicalGlass @@ -650,10 +640,9 @@ id: AirlockCentralCommandGlassLocked suffix: Central Command, Locked components: - - type: AccessReader - access: [["CentralCommand"]] - - type: Wires - layoutId: AirlockCommand + - type: ContainerFill + containers: + board: [ DoorElectronicsCentralCommand ] - type: entity parent: AirlockCommandGlass @@ -759,10 +748,9 @@ id: AirlockSecurityLawyerGlassLocked suffix: Security/Lawyer, Locked components: - - type: AccessReader - access: [["Security"], ["Lawyer"]] - - type: Wires - layoutId: AirlockSecurity + - type: ContainerFill + containers: + board: [ DoorElectronicsSecurityLawyer ] - type: entity parent: AirlockSecurityGlass @@ -787,16 +775,18 @@ id: AirlockSyndicateGlassLocked suffix: Syndicate, Locked components: - - type: AccessReader - access: [["SyndicateAgent"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsSyndicateAgent ] - type: entity parent: AirlockSyndicateGlass id: AirlockSyndicateNukeopGlassLocked suffix: Nukeop, Locked components: - - type: AccessReader - access: [["NuclearOperative"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsNukeop ] # Maintenance Hatches - type: entity @@ -872,7 +862,7 @@ board: [ DoorElectronicsAtmospherics ] - type: entity - parent: AirlockMaint + parent: AirlockMaintServiceLocked id: AirlockMaintBarLocked suffix: Bar, Locked components: @@ -881,7 +871,7 @@ board: [ DoorElectronicsBar ] - type: entity - parent: AirlockMaint + parent: AirlockMaintServiceLocked id: AirlockMaintChapelLocked suffix: Chapel, Locked components: @@ -890,7 +880,7 @@ board: [ DoorElectronicsChapel ] - type: entity - parent: AirlockMaint + parent: AirlockMaintServiceLocked id: AirlockMaintHydroLocked suffix: Hydroponics, Locked components: @@ -899,7 +889,7 @@ board: [ DoorElectronicsHydroponics ] - type: entity - parent: AirlockMaint + parent: AirlockMaintServiceLocked id: AirlockMaintJanitorLocked suffix: Janitor, Locked components: @@ -908,27 +898,27 @@ board: [ DoorElectronicsJanitor ] - type: entity - parent: AirlockMaint + parent: AirlockMaintServiceLocked id: AirlockMaintLawyerLocked suffix: Lawyer, Locked components: - - type: AccessReader - access: [["Lawyer"]] - - type: Wires - layoutId: AirlockService + - type: ContainerFill + containers: + board: [ DoorElectronicsLawyer ] - type: entity parent: AirlockMaint id: AirlockMaintServiceLocked suffix: Service, Locked components: - - type: AccessReader - access: [["Service"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsService ] - type: Wires layoutId: AirlockService - type: entity - parent: AirlockMaint + parent: AirlockMaintServiceLocked id: AirlockMaintTheatreLocked suffix: Theatre, Locked components: @@ -937,7 +927,7 @@ board: [ DoorElectronicsTheatre ] - type: entity - parent: AirlockMaint + parent: AirlockMaintServiceLocked id: AirlockMaintKitchenLocked suffix: Kitchen, Locked components: @@ -962,9 +952,11 @@ - type: ContainerFill containers: board: [ DoorElectronicsMedical ] + - type: Wires + layoutId: AirlockMedical - type: entity - parent: AirlockMaint + parent: AirlockMaintMedLocked id: AirlockMaintChemLocked suffix: Chemistry, Locked components: @@ -980,9 +972,11 @@ - type: ContainerFill containers: board: [ DoorElectronicsResearch ] + - type: Wires + layoutId: AirlockScience - type: entity - parent: AirlockMaint + parent: AirlockMaintRnDLocked id: AirlockMaintRnDMedLocked suffix: Medical/Epistemics, Locked # DeltaV - Epistemics Department replacing Science components: @@ -998,9 +992,11 @@ - type: ContainerFill containers: board: [ DoorElectronicsSecurity ] + - type: Wires + layoutId: AirlockSecurity - type: entity - parent: AirlockMaint + parent: AirlockMaintSecLocked id: AirlockMaintDetectiveLocked suffix: Detective, Locked components: @@ -1009,7 +1005,7 @@ board: [ DoorElectronicsDetective ] - type: entity - parent: AirlockMaint + parent: AirlockMaintCommandLocked id: AirlockMaintHOPLocked suffix: HeadOfPersonnel, Locked components: @@ -1018,7 +1014,7 @@ board: [ DoorElectronicsHeadOfPersonnel ] - type: entity - parent: AirlockMaint + parent: AirlockMaintCommandLocked id: AirlockMaintCaptainLocked suffix: Captain, Locked components: @@ -1027,70 +1023,69 @@ board: [ DoorElectronicsCaptain ] - type: entity - parent: AirlockMaint + parent: AirlockMaintCommandLocked id: AirlockMaintChiefEngineerLocked suffix: ChiefEngineer, Locked components: - - type: AccessReader - access: [["ChiefEngineer"]] - - type: Wires - layoutId: AirlockCommand + - type: ContainerFill + containers: + board: [ DoorElectronicsChiefEngineer ] - type: entity - parent: AirlockMaint + parent: AirlockMaintCommandLocked id: AirlockMaintChiefMedicalOfficerLocked suffix: ChiefMedicalOfficer, Locked components: - - type: AccessReader - access: [["ChiefMedicalOfficer"]] - - type: Wires - layoutId: AirlockCommand + - type: ContainerFill + containers: + board: [ DoorElectronicsChiefMedicalOfficer ] - type: entity - parent: AirlockMaint + parent: AirlockMaintCommandLocked id: AirlockMaintHeadOfSecurityLocked suffix: HeadOfSecurity, Locked components: - - type: AccessReader - access: [["HeadOfSecurity"]] - - type: Wires - layoutId: AirlockCommand + - type: ContainerFill + containers: + board: [ DoorElectronicsHeadOfSecurity ] - type: entity - parent: AirlockMaint + parent: AirlockMaintCommandLocked id: AirlockMaintResearchDirectorLocked suffix: ResearchDirector, Locked components: - - type: AccessReader - access: [["ResearchDirector"]] - - type: Wires - layoutId: AirlockCommand + - type: ContainerFill + containers: + board: [ DoorElectronicsResearchDirector ] - type: entity parent: AirlockMaint id: AirlockMaintArmoryLocked suffix: Armory, Locked components: - - type: AccessReader - access: [["Armory"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsArmory ] - type: Wires - layoutId: AirlockSecurity + layoutId: AirlockArmory - type: entity parent: AirlockSyndicate id: AirlockSyndicateLocked suffix: Syndicate, Locked components: - - type: AccessReader - access: [["SyndicateAgent"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsSyndicateAgent ] - type: entity parent: AirlockSyndicate id: AirlockSyndicateNukeopLocked suffix: Nukeop, Locked components: - - type: AccessReader - access: [["NuclearOperative"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsNukeop ] # Shuttle airlocks - type: entity @@ -1107,16 +1102,18 @@ id: AirlockExternalShuttleSyndicateLocked suffix: External, Docking, Syndicate, Locked components: - - type: AccessReader - access: [["SyndicateAgent"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsSyndicateAgent ] - type: entity parent: AirlockShuttleSyndicate id: AirlockExternalShuttleNukeopLocked suffix: External, Docking, Nukeop, Locked components: - - type: AccessReader - access: [["NuclearOperative"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsNukeop ] - type: entity parent: AirlockGlassShuttle @@ -1132,42 +1129,44 @@ id: AirlockExternalGlassShuttleSyndicateLocked suffix: Syndicate, Locked, Glass components: - - type: AccessReader - access: [["SyndicateAgent"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsSyndicateAgent ] - type: entity parent: AirlockGlassShuttleSyndicate id: AirlockExternalGlassShuttleNukeopLocked suffix: Nukeop, Locked, Glass components: - - type: AccessReader - access: [["NuclearOperative"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsNukeop ] - type: entity parent: AirlockGlassShuttle id: AirlockExternalGlassShuttleEmergencyLocked suffix: External, Emergency, Glass, Docking, Locked components: - - type: PriorityDock - tag: DockEmergency - - type: ContainerFill - containers: - board: [ DoorElectronicsExternal ] + - type: PriorityDock + tag: DockEmergency + - type: ContainerFill + containers: + board: [ DoorElectronicsExternal ] - type: entity parent: AirlockGlassShuttle id: AirlockExternalGlassShuttleArrivals suffix: External, Arrivals, Glass, Docking components: - - type: PriorityDock - tag: DockArrivals + - type: PriorityDock + tag: DockArrivals - type: entity parent: AirlockGlassShuttle id: AirlockExternalGlassShuttleEscape suffix: External, Escape 3x4, Glass, Docking components: - - type: GridFill + - type: GridFill #HighSecDoors - type: entity @@ -1175,8 +1174,9 @@ id: HighSecCentralCommandLocked suffix: Central Command, Locked components: - - type: AccessReader - access: [["CentralCommand"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsCentralCommand ] - type: entity parent: HighSecDoor diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/airlocks.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/airlocks.yml index 7ee404ec41d..c47ae7c2150 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/airlocks.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/airlocks.yml @@ -5,6 +5,8 @@ components: - type: Sprite sprite: Structures/Doors/Airlocks/Standard/freezer.rsi + - type: Wires + layoutId: AirlockService - type: entity parent: Airlock @@ -15,6 +17,8 @@ sprite: Structures/Doors/Airlocks/Standard/engineering.rsi - type: PaintableAirlock department: Engineering + - type: Wires + layoutId: AirlockEngineering - type: entity parent: AirlockEngineering @@ -33,6 +37,8 @@ sprite: Structures/Doors/Airlocks/Standard/cargo.rsi - type: PaintableAirlock department: Logistics + - type: Wires + layoutId: AirlockCargo - type: entity parent: Airlock @@ -43,6 +49,8 @@ sprite: Structures/Doors/Airlocks/Standard/medical.rsi - type: PaintableAirlock department: Medical + - type: Wires + layoutId: AirlockMedical - type: entity parent: AirlockMedical @@ -66,6 +74,8 @@ sprite: Structures/Doors/Airlocks/Standard/science.rsi - type: PaintableAirlock department: Epistemics + - type: Wires + layoutId: AirlockScience - type: entity parent: Airlock @@ -78,6 +88,8 @@ securityLevel: medSecurity - type: PaintableAirlock department: Command + - type: Wires + layoutId: AirlockCommand - type: entity parent: Airlock @@ -88,6 +100,8 @@ sprite: Structures/Doors/Airlocks/Standard/security.rsi - type: PaintableAirlock department: Security + - type: Wires + layoutId: AirlockSecurity - type: entity parent: Airlock @@ -112,6 +126,8 @@ components: - type: Sprite sprite: Structures/Doors/Airlocks/Standard/mining.rsi + - type: Wires + layoutId: AirlockCargo - type: entity parent: AirlockCommand # if you get centcom door somehow it counts as command, also inherit panel @@ -147,6 +163,8 @@ sprite: Structures/Doors/Airlocks/Glass/engineering.rsi - type: PaintableAirlock department: Engineering + - type: Wires + layoutId: AirlockEngineering - type: entity parent: AirlockGlass @@ -173,6 +191,8 @@ sprite: Structures/Doors/Airlocks/Glass/cargo.rsi - type: PaintableAirlock department: Logistics + - type: Wires + layoutId: AirlockCargo - type: entity parent: AirlockGlass @@ -183,6 +203,8 @@ sprite: Structures/Doors/Airlocks/Glass/medical.rsi - type: PaintableAirlock department: Medical + - type: Wires + layoutId: AirlockMedical - type: entity parent: AirlockMedicalGlass @@ -206,6 +228,8 @@ sprite: Structures/Doors/Airlocks/Glass/science.rsi - type: PaintableAirlock department: Epistemics + - type: Wires + layoutId: AirlockScience - type: entity parent: AirlockGlass @@ -218,6 +242,8 @@ department: Command - type: WiresPanelSecurity securityLevel: medSecurity + - type: Wires + layoutId: AirlockCommand - type: entity parent: AirlockGlass @@ -228,6 +254,8 @@ sprite: Structures/Doors/Airlocks/Glass/security.rsi - type: PaintableAirlock department: Security + - type: Wires + layoutId: AirlockSecurity - type: entity parent: AirlockSecurityGlass # see standard @@ -252,5 +280,3 @@ components: - type: Sprite sprite: Structures/Doors/Airlocks/Glass/centcomm.rsi - - type: WiresPanelSecurity - securityLevel: medSecurity \ No newline at end of file From dc99798bce5090a7f725da5f187bfea86879f6b3 Mon Sep 17 00:00:00 2001 From: Flareguy <78941145+Flareguy@users.noreply.github.com> Date: Wed, 10 Apr 2024 16:20:05 -0500 Subject: [PATCH 178/295] Add emergency nitrogen lockers (#26752) --- .../Prototypes/Catalog/Fills/Lockers/misc.yml | 15 +++++++++++++++ .../Structures/Storage/Closets/closets.yml | 13 +++++++++++++ .../Structures/Storage/closet.rsi/meta.json | 5 ++++- .../Structures/Storage/closet.rsi/n2_door.png | Bin 0 -> 357 bytes 4 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 Resources/Textures/Structures/Storage/closet.rsi/n2_door.png diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/misc.yml b/Resources/Prototypes/Catalog/Fills/Lockers/misc.yml index dbeacab59f3..3a9211d2489 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/misc.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/misc.yml @@ -61,6 +61,21 @@ - id: BoxMRE prob: 0.1 +- type: entity + id: ClosetEmergencyN2FilledRandom + parent: ClosetEmergencyN2 + suffix: Filled, Random + components: + - type: StorageFill + contents: + - id: ClothingMaskBreath + - id: EmergencyNitrogenTankFilled + prob: 0.80 + orGroup: EmergencyTankOrRegularTank + - id: NitrogenTankFilled + prob: 0.20 + orGroup: EmergencyTankOrRegularTank + - type: entity id: ClosetFireFilled parent: ClosetFire diff --git a/Resources/Prototypes/Entities/Structures/Storage/Closets/closets.yml b/Resources/Prototypes/Entities/Structures/Storage/Closets/closets.yml index 5fda0ddbe2e..fb00db7aa6b 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Closets/closets.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Closets/closets.yml @@ -37,6 +37,19 @@ stateDoorOpen: emergency_open stateDoorClosed: emergency_door +# Emergency N2 closet +- type: entity + id: ClosetEmergencyN2 + name: emergency nitrogen closet + parent: ClosetSteelBase + description: It's full of life-saving equipment. Assuming, that is, that you breathe nitrogen. + components: + - type: Appearance + - type: EntityStorageVisuals + stateBaseClosed: fire + stateDoorOpen: fire_open + stateDoorClosed: n2_door + # Fire safety closet - type: entity id: ClosetFire diff --git a/Resources/Textures/Structures/Storage/closet.rsi/meta.json b/Resources/Textures/Structures/Storage/closet.rsi/meta.json index cf0c204447b..c52bc13540e 100644 --- a/Resources/Textures/Structures/Storage/closet.rsi/meta.json +++ b/Resources/Textures/Structures/Storage/closet.rsi/meta.json @@ -4,7 +4,7 @@ "x": 32, "y": 32 }, - "copyright": "Taken from tgstation, brigmedic locker is a resprited CMO locker by PuroSlavKing (Github)", + "copyright": "Taken from tgstation, brigmedic locker is a resprited CMO locker by PuroSlavKing (Github), n2_door state modified by Flareguy from fire_door, using sprites from /vg/station at https://github.com/vgstation-coders/vgstation13/commit/02b9f6894af4419c9f7e699a22c402b086d8067e", "license": "CC-BY-SA-3.0", "states": [ { @@ -398,6 +398,9 @@ { "name": "mixed_door" }, + { + "name": "n2_door" + }, { "name": "oldcloset" }, diff --git a/Resources/Textures/Structures/Storage/closet.rsi/n2_door.png b/Resources/Textures/Structures/Storage/closet.rsi/n2_door.png new file mode 100644 index 0000000000000000000000000000000000000000..f6d9499f10ab3a034df4055af67a163859fd68d3 GIT binary patch literal 357 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCikV0(?STA8y#PMMdT0$&=5YKi8FGm?I^1LRa_N(xoR>t~_!0@ZrUan^IFd zLqaM9MfB)x#KzYsrkH}&M2EM}} z%y>M1MG8=GlBbJfh=u>%zK48G20U!;(P|w$Ti^d*S2MHpTFTUTwPW0pPs(0bKgyLAq>xb#0h^knT`$K6g%+Q+W@_bqGS4?6eLB+>J4 zf}MF<@B!A-OE=D7(u*+M6Xa#cy6a$>DeFFwDOW`*`17XDIs72*r@)^>E$WUB3|M0> dJXZ*~%V_e{;ZwMjj404`44$rjF6*2UngH?&jmZE2 literal 0 HcmV?d00001 From 4cb3a79c602fa1c51ebc1e7946ab300b7bba84c3 Mon Sep 17 00:00:00 2001 From: Ghagliiarghii <68826635+Ghagliiarghii@users.noreply.github.com> Date: Wed, 10 Apr 2024 20:22:29 -0400 Subject: [PATCH 179/295] Update ashtray to allow all cigarettes / cigars (#26864) * Update ashtray to allow all cigarettes / cigars This also includes joints (as they are technically cigarettes) * ? --- Resources/Prototypes/Entities/Objects/Decoration/ashtray.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Resources/Prototypes/Entities/Objects/Decoration/ashtray.yml b/Resources/Prototypes/Entities/Objects/Decoration/ashtray.yml index e4cd6ba0dea..61554d0621b 100644 --- a/Resources/Prototypes/Entities/Objects/Decoration/ashtray.yml +++ b/Resources/Prototypes/Entities/Objects/Decoration/ashtray.yml @@ -17,6 +17,8 @@ whitelist: tags: - Burnt + - Cigarette + - Cigar maxItemSize: Tiny grid: - 0,0,9,0 From c772d54087c1dbd2c6a330170365a1e3caaf2b42 Mon Sep 17 00:00:00 2001 From: Jark255 Date: Thu, 11 Apr 2024 15:21:15 +0300 Subject: [PATCH 180/295] Fix door electronics configurator usage (#26888) * allow usage of network configurator for door electronics * add checks for "allowed" items --- Content.Server/UserInterface/ActivatableUISystem.cs | 6 ++++++ .../Entities/Objects/Devices/Electronics/door.yml | 2 +- Resources/Prototypes/Entities/Objects/Tools/tools.yml | 10 +++++++--- Resources/Prototypes/tags.yml | 3 +++ 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/Content.Server/UserInterface/ActivatableUISystem.cs b/Content.Server/UserInterface/ActivatableUISystem.cs index e3a11af4295..5f2314df90b 100644 --- a/Content.Server/UserInterface/ActivatableUISystem.cs +++ b/Content.Server/UserInterface/ActivatableUISystem.cs @@ -93,6 +93,9 @@ private void OnActivate(EntityUid uid, ActivatableUIComponent component, Activat if (component.InHandsOnly) return; + if (component.AllowedItems != null) + return; + args.Handled = InteractUI(args.User, uid, component); } @@ -104,6 +107,9 @@ private void OnUseInHand(EntityUid uid, ActivatableUIComponent component, UseInH if (component.RightClickOnly) return; + if (component.AllowedItems != null) + return; + args.Handled = InteractUI(args.User, uid, component); } diff --git a/Resources/Prototypes/Entities/Objects/Devices/Electronics/door.yml b/Resources/Prototypes/Entities/Objects/Devices/Electronics/door.yml index 053558376f2..b761ef7996f 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Electronics/door.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Electronics/door.yml @@ -21,7 +21,7 @@ key: enum.DoorElectronicsConfigurationUiKey.Key allowedItems: tags: - - Multitool + - DoorElectronicsConfigurator - type: UserInterface interfaces: - key: enum.DoorElectronicsConfigurationUiKey.Key diff --git a/Resources/Prototypes/Entities/Objects/Tools/tools.yml b/Resources/Prototypes/Entities/Objects/Tools/tools.yml index 41c0ccc4a3d..ad62426bb0f 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/tools.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/tools.yml @@ -225,6 +225,7 @@ - type: Tag tags: - Multitool + - DoorElectronicsConfigurator - type: PhysicalComposition materialComposition: Steel: 100 @@ -266,6 +267,9 @@ - type: ActivatableUI key: enum.NetworkConfiguratorUiKey.List inHandsOnly: true + - type: Tag + tags: + - DoorElectronicsConfigurator - type: UserInterface interfaces: - key: enum.NetworkConfiguratorUiKey.List @@ -349,13 +353,13 @@ description: The rapid construction device can be used to quickly place and remove various station structures and fixtures. Requires compressed matter to function. components: - type: RCD - availablePrototypes: + availablePrototypes: - WallSolid - FloorSteel - Plating - Catwalk - Grille - - Window + - Window - WindowDirectional - WindowReinforcedDirectional - ReinforcedWindow @@ -404,7 +408,7 @@ - type: LimitedCharges charges: 0 - type: RCD - availablePrototypes: + availablePrototypes: - WallSolid - FloorSteel - Plating diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index 6a12bdba91c..c2e46d5f590 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -515,6 +515,9 @@ - type: Tag id: DoorElectronics +- type: Tag + id: DoorElectronicsConfigurator + - type: Tag id: DonkPocket From e64ec7f4d73ce60ab716504d3aaa97f4f5c38a78 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Thu, 11 Apr 2024 14:22:21 +0200 Subject: [PATCH 181/295] Fix TEG assert (#26881) It's possible to trigger this by stacking it weirdly with the spawn panel. Just make it bail instead. --- Content.Server/Power/Generation/Teg/TegNodeGroup.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Content.Server/Power/Generation/Teg/TegNodeGroup.cs b/Content.Server/Power/Generation/Teg/TegNodeGroup.cs index ed6b46e304a..3c937f8f71d 100644 --- a/Content.Server/Power/Generation/Teg/TegNodeGroup.cs +++ b/Content.Server/Power/Generation/Teg/TegNodeGroup.cs @@ -66,11 +66,16 @@ public override void Initialize(Node sourceNode, IEntityManager entMan) public override void LoadNodes(List groupNodes) { - DebugTools.Assert(groupNodes.Count <= 3, "The TEG has at most 3 parts"); DebugTools.Assert(_entityManager != null); base.LoadNodes(groupNodes); + if (groupNodes.Count > 3) + { + // Somehow got more TEG parts. Probably shenanigans. Bail. + return; + } + Generator = groupNodes.OfType().SingleOrDefault(); if (Generator != null) { From beb14c075a82e4fde3824c0f72170d089b479162 Mon Sep 17 00:00:00 2001 From: chromiumboy <50505512+chromiumboy@users.noreply.github.com> Date: Thu, 11 Apr 2024 07:26:34 -0500 Subject: [PATCH 182/295] Bug fix for deconstructing tiles and lattice with RCDs (#26863) * Fixed mixed deconstruction times for tiles and lattice * Lattice and power cables can be deconstructed instantly --- Content.Shared/RCD/Systems/RCDSystem.cs | 2 +- .../Prototypes/Entities/Structures/Power/cable_terminal.yml | 4 ++-- Resources/Prototypes/Entities/Structures/Power/cables.yml | 4 ++-- Resources/Prototypes/RCD/rcd.yml | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Content.Shared/RCD/Systems/RCDSystem.cs b/Content.Shared/RCD/Systems/RCDSystem.cs index 0b772d54bd9..dbd2ba53684 100644 --- a/Content.Shared/RCD/Systems/RCDSystem.cs +++ b/Content.Shared/RCD/Systems/RCDSystem.cs @@ -175,7 +175,7 @@ private void OnAfterInteract(EntityUid uid, RCDComponent component, AfterInterac else { var deconstructedTile = _mapSystem.GetTileRef(mapGridData.Value.GridUid, mapGridData.Value.Component, mapGridData.Value.Location); - var protoName = deconstructedTile.IsSpace() ? _deconstructTileProto : _deconstructLatticeProto; + var protoName = !deconstructedTile.IsSpace() ? _deconstructTileProto : _deconstructLatticeProto; if (_protoManager.TryIndex(protoName, out var deconProto)) { diff --git a/Resources/Prototypes/Entities/Structures/Power/cable_terminal.yml b/Resources/Prototypes/Entities/Structures/Power/cable_terminal.yml index 2e8f047c214..cc337911742 100644 --- a/Resources/Prototypes/Entities/Structures/Power/cable_terminal.yml +++ b/Resources/Prototypes/Entities/Structures/Power/cable_terminal.yml @@ -19,8 +19,8 @@ damageModifierSet: Metallic - type: RCDDeconstructable cost: 2 - delay: 2 - fx: EffectRCDDeconstruct2 + delay: 0 + fx: EffectRCDConstruct0 - type: Destructible thresholds: - trigger: diff --git a/Resources/Prototypes/Entities/Structures/Power/cables.yml b/Resources/Prototypes/Entities/Structures/Power/cables.yml index a81c89de0fb..d064cc187c4 100644 --- a/Resources/Prototypes/Entities/Structures/Power/cables.yml +++ b/Resources/Prototypes/Entities/Structures/Power/cables.yml @@ -45,8 +45,8 @@ node: power - type: RCDDeconstructable cost: 2 - delay: 2 - fx: EffectRCDDeconstruct2 + delay: 0 + fx: EffectRCDConstruct0 - type: entity parent: CableBase diff --git a/Resources/Prototypes/RCD/rcd.yml b/Resources/Prototypes/RCD/rcd.yml index bc1aa91d280..500b5f59bf9 100644 --- a/Resources/Prototypes/RCD/rcd.yml +++ b/Resources/Prototypes/RCD/rcd.yml @@ -17,9 +17,9 @@ name: rcd-component-deconstruct mode: Deconstruct cost: 2 - delay: 1 + delay: 0 rotation: Camera - fx: EffectRCDDeconstruct2 + fx: EffectRCDConstruct0 - type: rcd id: DeconstructTile # Hidden prototype - do not add to RCDs From 960521d16d98d1e4418a61d504f30fb57f02495e Mon Sep 17 00:00:00 2001 From: keronshb <54602815+keronshb@users.noreply.github.com> Date: Thu, 11 Apr 2024 13:40:02 -0400 Subject: [PATCH 183/295] Immovable Rod changes (#26757) --- .../ImmovableRod/ImmovableRodComponent.cs | 13 +++++++ .../ImmovableRod/ImmovableRodSystem.cs | 29 +++++++++++--- .../Entities/Objects/Fun/immovable_rod.yml | 38 +++++++++++++++++-- 3 files changed, 72 insertions(+), 8 deletions(-) diff --git a/Content.Server/ImmovableRod/ImmovableRodComponent.cs b/Content.Server/ImmovableRod/ImmovableRodComponent.cs index f3605914795..05fa3d9d9b2 100644 --- a/Content.Server/ImmovableRod/ImmovableRodComponent.cs +++ b/Content.Server/ImmovableRod/ImmovableRodComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.Damage; using Robust.Shared.Audio; namespace Content.Server.ImmovableRod; @@ -36,4 +37,16 @@ public sealed partial class ImmovableRodComponent : Component /// [DataField("destroyTiles")] public bool DestroyTiles = true; + + /// + /// If true, this will gib & delete bodies + /// + [DataField] + public bool ShouldGib = true; + + /// + /// Damage done, if not gibbing + /// + [DataField] + public DamageSpecifier? Damage; } diff --git a/Content.Server/ImmovableRod/ImmovableRodSystem.cs b/Content.Server/ImmovableRod/ImmovableRodSystem.cs index 429361cd8cd..c8f36e864c3 100644 --- a/Content.Server/ImmovableRod/ImmovableRodSystem.cs +++ b/Content.Server/ImmovableRod/ImmovableRodSystem.cs @@ -1,9 +1,10 @@ using Content.Server.Body.Systems; +using Content.Server.Polymorph.Components; using Content.Server.Popups; using Content.Shared.Body.Components; +using Content.Shared.Damage; using Content.Shared.Examine; using Content.Shared.Popups; -using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Map; using Robust.Shared.Map.Components; @@ -22,6 +23,8 @@ public sealed class ImmovableRodSystem : EntitySystem [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; public override void Update(float frameTime) { @@ -64,11 +67,11 @@ private void OnMapInit(EntityUid uid, ImmovableRodComponent component, MapInitEv var vel = component.DirectionOverride.Degrees switch { 0f => _random.NextVector2(component.MinSpeed, component.MaxSpeed), - _ => xform.WorldRotation.RotateVec(component.DirectionOverride.ToVec()) * _random.NextFloat(component.MinSpeed, component.MaxSpeed) + _ => _transform.GetWorldRotation(uid).RotateVec(component.DirectionOverride.ToVec()) * _random.NextFloat(component.MinSpeed, component.MaxSpeed) }; _physics.ApplyLinearImpulse(uid, vel, body: phys); - xform.LocalRotation = (vel - xform.WorldPosition).ToWorldAngle() + MathHelper.PiOver2; + xform.LocalRotation = (vel - _transform.GetWorldPosition(uid)).ToWorldAngle() + MathHelper.PiOver2; } } @@ -94,12 +97,28 @@ private void OnCollide(EntityUid uid, ImmovableRodComponent component, ref Start return; } - // gib em + // dont delete/hurt self if polymoprhed into a rod + if (TryComp(uid, out var polymorphed)) + { + if (polymorphed.Parent == ent) + return; + } + + // gib or damage em if (TryComp(ent, out var body)) { component.MobCount++; - _popup.PopupEntity(Loc.GetString("immovable-rod-penetrated-mob", ("rod", uid), ("mob", ent)), uid, PopupType.LargeCaution); + + if (!component.ShouldGib) + { + if (component.Damage == null || !TryComp(ent, out var damageable)) + return; + + _damageable.SetDamage(ent, damageable, component.Damage); + return; + } + _bodySystem.GibBody(ent, body: body); return; } diff --git a/Resources/Prototypes/Entities/Objects/Fun/immovable_rod.yml b/Resources/Prototypes/Entities/Objects/Fun/immovable_rod.yml index 466e433e8b5..7ff6c43e29b 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/immovable_rod.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/immovable_rod.yml @@ -11,8 +11,6 @@ state: icon noRot: false - type: ImmovableRod - - type: TimedDespawn - lifetime: 30.0 - type: Physics bodyType: Dynamic linearDamping: 0 @@ -36,8 +34,15 @@ location: immovable rod - type: entity + id: ImmovableRodDespawn parent: ImmovableRod + components: + - type: TimedDespawn + lifetime: 30.0 + +- type: entity id: ImmovableRodSlow + parent: ImmovableRodDespawn suffix: Slow components: - type: ImmovableRod @@ -45,7 +50,7 @@ maxSpeed: 5 - type: entity - parent: ImmovableRod + parent: ImmovableRodDespawn id: ImmovableRodKeepTiles suffix: Keep Tiles components: @@ -53,6 +58,33 @@ destroyTiles: false hitSoundProbability: 1.0 +# For Wizard Polymorph +- type: entity + parent: ImmovableRod + id: ImmovableRodWizard + suffix: Wizard + components: + - type: ImmovableRod + minSpeed: 35 + destroyTiles: false + randomizeVelocity: false + shouldGib: false + damage: + types: + Blunt: 200 + - type: MovementIgnoreGravity + gravityState: true + - type: InputMover + - type: MovementSpeedModifier + weightlessAcceleration: 5 + weightlessModifier: 2 + weightlessFriction: 0 + friction: 0 + frictionNoInput: 0 + - type: CanMoveInAir + - type: MovementAlwaysTouching + - type: NoSlip + - type: entity parent: ImmovableRodKeepTiles id: ImmovableRodKeepTilesStill From f08d45ed33d5e3f82059c3a7bdf6ec39312e4794 Mon Sep 17 00:00:00 2001 From: lunarcomets <140772713+lunarcomets@users.noreply.github.com> Date: Thu, 11 Apr 2024 13:48:46 -0700 Subject: [PATCH 184/295] Cryogenic storage tweaks (#26813) * make cryo remove crewmember's station record when going to cryo * Revert "make cryo remove crewmember's station record when going to cryo" This reverts commit 9ac9707289b5e553e3015c8c3ef88a78439977c6. * make cryo remove crewmember from station records when the mind is removed from the body * add stationwide announcement for people cryoing (remember to change pr title and desc) * minor changes * announcement actually shows job now * requested changes * get outta here derivative --- .../Bed/Cryostorage/CryostorageSystem.cs | 42 ++++++++++++++++--- .../bed/cryostorage/cryogenic-storage.ftl | 5 +++ 2 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 Resources/Locale/en-US/bed/cryostorage/cryogenic-storage.ftl diff --git a/Content.Server/Bed/Cryostorage/CryostorageSystem.cs b/Content.Server/Bed/Cryostorage/CryostorageSystem.cs index a3b7fb8d67b..bb2ab4099da 100644 --- a/Content.Server/Bed/Cryostorage/CryostorageSystem.cs +++ b/Content.Server/Bed/Cryostorage/CryostorageSystem.cs @@ -1,11 +1,16 @@ +using System.Globalization; using System.Linq; using Content.Server.Chat.Managers; using Content.Server.GameTicking; using Content.Server.Hands.Systems; using Content.Server.Inventory; using Content.Server.Popups; +using Content.Server.Chat.Systems; using Content.Server.Station.Components; using Content.Server.Station.Systems; +using Content.Server.StationRecords; +using Content.Server.StationRecords.Systems; +using Content.Shared.StationRecords; using Content.Shared.UserInterface; using Content.Shared.Access.Systems; using Content.Shared.Bed.Cryostorage; @@ -32,6 +37,7 @@ public sealed class CryostorageSystem : SharedCryostorageSystem [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly AudioSystem _audio = default!; [Dependency] private readonly AccessReaderSystem _accessReader = default!; + [Dependency] private readonly ChatSystem _chatSystem = default!; [Dependency] private readonly ClimbSystem _climb = default!; [Dependency] private readonly ContainerSystem _container = default!; [Dependency] private readonly GameTicker _gameTicker = default!; @@ -40,6 +46,7 @@ public sealed class CryostorageSystem : SharedCryostorageSystem [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly StationSystem _station = default!; [Dependency] private readonly StationJobsSystem _stationJobs = default!; + [Dependency] private readonly StationRecordsSystem _stationRecords = default!; [Dependency] private readonly TransformSystem _transform = default!; [Dependency] private readonly UserInterfaceSystem _ui = default!; @@ -163,26 +170,30 @@ public void HandleEnterCryostorage(Entity ent, Ne { var comp = ent.Comp; var cryostorageEnt = ent.Comp.Cryostorage; + + var station = _station.GetOwningStation(ent); + var name = Name(ent.Owner); + if (!TryComp(cryostorageEnt, out var cryostorageComponent)) return; // if we have a session, we use that to add back in all the job slots the player had. if (userId != null) { - foreach (var station in _station.GetStationsSet()) + foreach (var uniqueStation in _station.GetStationsSet()) { - if (!TryComp(station, out var stationJobs)) + if (!TryComp(uniqueStation, out var stationJobs)) continue; - if (!_stationJobs.TryGetPlayerJobs(station, userId.Value, out var jobs, stationJobs)) + if (!_stationJobs.TryGetPlayerJobs(uniqueStation, userId.Value, out var jobs, stationJobs)) continue; foreach (var job in jobs) { - _stationJobs.TryAdjustJobSlot(station, job, 1, clamp: true); + _stationJobs.TryAdjustJobSlot(uniqueStation, job, 1, clamp: true); } - _stationJobs.TryRemovePlayerJobs(station, userId.Value, stationJobs); + _stationJobs.TryRemovePlayerJobs(uniqueStation, userId.Value, stationJobs); } } @@ -203,12 +214,33 @@ public void HandleEnterCryostorage(Entity ent, Ne _gameTicker.OnGhostAttempt(mind.Value, false); } } + comp.AllowReEnteringBody = false; _transform.SetParent(ent, PausedMap.Value); cryostorageComponent.StoredPlayers.Add(ent); Dirty(ent, comp); UpdateCryostorageUIState((cryostorageEnt.Value, cryostorageComponent)); AdminLog.Add(LogType.Action, LogImpact.High, $"{ToPrettyString(ent):player} was entered into cryostorage inside of {ToPrettyString(cryostorageEnt.Value)}"); + + if (!TryComp(station, out var stationRecords)) + return; + + var key = new StationRecordKey(_stationRecords.GetRecordByName(station.Value, name) ?? default(uint), station.Value); + var jobName = "Unknown"; + + if (_stationRecords.TryGetRecord(key, out var entry, stationRecords)) + jobName = entry.JobTitle; + + _stationRecords.RemoveRecord(key, stationRecords); + + _chatSystem.DispatchStationAnnouncement(station.Value, + Loc.GetString( + "earlyleave-cryo-announcement", + ("character", name), + ("job", CultureInfo.CurrentCulture.TextInfo.ToTitleCase(jobName)) + ), Loc.GetString("earlyleave-cryo-sender"), + playDefaultSound: false + ); } private void HandleCryostorageReconnection(Entity entity) diff --git a/Resources/Locale/en-US/bed/cryostorage/cryogenic-storage.ftl b/Resources/Locale/en-US/bed/cryostorage/cryogenic-storage.ftl new file mode 100644 index 00000000000..8de5f9019b1 --- /dev/null +++ b/Resources/Locale/en-US/bed/cryostorage/cryogenic-storage.ftl @@ -0,0 +1,5 @@ + +### Announcement + +earlyleave-cryo-announcement = {$character} ({$job}) has entered cryogenic storage! +earlyleave-cryo-sender = Station From cdfe79f66d5350f02e305154feaf50ac68ed81da Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Fri, 12 Apr 2024 02:30:34 -0400 Subject: [PATCH 185/295] Fix potted plant popup/sfx spam (#26901) Fixed potted plant hide popup/sfx spam. --- Content.Shared/Plants/PottedPlantHideSystem.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Content.Shared/Plants/PottedPlantHideSystem.cs b/Content.Shared/Plants/PottedPlantHideSystem.cs index fd256fd9263..cbe052f8d5c 100644 --- a/Content.Shared/Plants/PottedPlantHideSystem.cs +++ b/Content.Shared/Plants/PottedPlantHideSystem.cs @@ -31,7 +31,7 @@ private void OnInteractUsing(EntityUid uid, PottedPlantHideComponent component, if (args.Handled) return; - Rustle(uid, component); + Rustle(uid, component, args.User); args.Handled = _stashSystem.TryHideItem(uid, args.User, args.Used); } @@ -40,24 +40,24 @@ private void OnInteractHand(EntityUid uid, PottedPlantHideComponent component, I if (args.Handled) return; - Rustle(uid, component); + Rustle(uid, component, args.User); var gotItem = _stashSystem.TryGetItem(uid, args.User); if (!gotItem) { var msg = Loc.GetString("potted-plant-hide-component-interact-hand-got-no-item-message"); - _popupSystem.PopupEntity(msg, uid, args.User); + _popupSystem.PopupClient(msg, uid, args.User); } args.Handled = gotItem; } - private void Rustle(EntityUid uid, PottedPlantHideComponent? component = null) + private void Rustle(EntityUid uid, PottedPlantHideComponent? component = null, EntityUid? user = null) { if (!Resolve(uid, ref component)) return; - _audio.PlayPvs(component.RustleSound, uid, AudioParams.Default.WithVariation(0.25f)); + _audio.PlayPredicted(component.RustleSound, uid, user, AudioParams.Default.WithVariation(0.25f)); } } } From e2b9e1257dce5de9f3c04943d3b347ab593808f9 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Fri, 12 Apr 2024 02:42:20 -0400 Subject: [PATCH 186/295] Allow advertisement timers to prewarm (#26900) Allow advertisement timers to prewarm. --- Content.Server/Advertise/Components/AdvertiseComponent.cs | 8 ++++++++ Content.Server/Advertise/EntitySystems/AdvertiseSystem.cs | 7 ++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Content.Server/Advertise/Components/AdvertiseComponent.cs b/Content.Server/Advertise/Components/AdvertiseComponent.cs index 531b31031d2..3d617e3a340 100644 --- a/Content.Server/Advertise/Components/AdvertiseComponent.cs +++ b/Content.Server/Advertise/Components/AdvertiseComponent.cs @@ -24,6 +24,14 @@ public sealed partial class AdvertiseComponent : Component [DataField] public int MaximumWait { get; private set; } = 10 * 60; + /// + /// If true, the delay before the first advertisement (at MapInit) will ignore + /// and instead be rolled between 0 and . This only applies to the initial delay; + /// will be respected after that. + /// + [DataField] + public bool Prewarm = true; + /// /// The identifier for the advertisements pack prototype. /// diff --git a/Content.Server/Advertise/EntitySystems/AdvertiseSystem.cs b/Content.Server/Advertise/EntitySystems/AdvertiseSystem.cs index 12eac72cfe3..28fa01628f4 100644 --- a/Content.Server/Advertise/EntitySystems/AdvertiseSystem.cs +++ b/Content.Server/Advertise/EntitySystems/AdvertiseSystem.cs @@ -37,13 +37,14 @@ public override void Initialize() private void OnMapInit(EntityUid uid, AdvertiseComponent advert, MapInitEvent args) { - RandomizeNextAdvertTime(advert); + var prewarm = advert.Prewarm; + RandomizeNextAdvertTime(advert, prewarm); _nextCheckTime = MathHelper.Min(advert.NextAdvertisementTime, _nextCheckTime); } - private void RandomizeNextAdvertTime(AdvertiseComponent advert) + private void RandomizeNextAdvertTime(AdvertiseComponent advert, bool prewarm = false) { - var minDuration = Math.Max(1, advert.MinimumWait); + var minDuration = prewarm ? 0 : Math.Max(1, advert.MinimumWait); var maxDuration = Math.Max(minDuration, advert.MaximumWait); var waitDuration = TimeSpan.FromSeconds(_random.Next(minDuration, maxDuration)); From 0a795694a9dc3c7798d8085ce5d77a4ec692613e Mon Sep 17 00:00:00 2001 From: Verm <32827189+Vermidia@users.noreply.github.com> Date: Fri, 12 Apr 2024 01:44:47 -0500 Subject: [PATCH 187/295] Fix shaker sprites (#26899) * Change basefoodshaker to parent from basefoodcondiment instead * Make them still refillable --- .../Objects/Consumable/Food/Containers/condiments.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/condiments.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/condiments.yml index babc2533f18..bbe5cf244b2 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/condiments.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/condiments.yml @@ -522,7 +522,7 @@ # Shakers - type: entity - parent: BaseFoodCondimentBottle + parent: BaseFoodCondiment id: BaseFoodShaker abstract: true name: empty shaker @@ -560,6 +560,8 @@ acts: [ "Destruction" ] - type: Sprite state: shaker-empty + - type: RefillableSolution + solution: food - type: entity parent: BaseFoodShaker From 3b57b0e6551364afde82fcee499bd57ed3ed2379 Mon Sep 17 00:00:00 2001 From: Token Date: Fri, 12 Apr 2024 11:50:10 +0500 Subject: [PATCH 188/295] Update .editorconfig to correspond Code Conventions (#26824) Update editorconfig to Code Style End of line is: CRLF (suggestion) Namespace declarations are: file scoped (suggestion). Instead of block scoped --- .editorconfig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index 872a068c7c6..58d0d332bbe 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,7 +9,7 @@ indent_style = space tab_width = 4 # New line preferences -#end_of_line = crlf +end_of_line = crlf:suggestion insert_final_newline = true trim_trailing_whitespace = true @@ -104,6 +104,7 @@ csharp_preferred_modifier_order = public, private, protected, internal, new, abs # 'using' directive preferences csharp_using_directive_placement = outside_namespace:silent +csharp_style_namespace_declarations = file_scoped:suggestion #### C# Formatting Rules #### From ff1a6f0a12c06a4ebdfbb517803e6b5cf41f921e Mon Sep 17 00:00:00 2001 From: liltenhead <104418166+liltenhead@users.noreply.github.com> Date: Thu, 11 Apr 2024 23:50:45 -0700 Subject: [PATCH 189/295] Remove reagent slimes from ghost role pool (#26840) reagentslimeghostrole --- Resources/Prototypes/Entities/Mobs/NPCs/elemental.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/elemental.yml b/Resources/Prototypes/Entities/Mobs/NPCs/elemental.yml index 01fce382e37..c2380c40278 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/elemental.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/elemental.yml @@ -229,6 +229,7 @@ description: It consists of a liquid, and it wants to dissolve you in itself. components: - type: GhostRole + prob: 0 description: ghost-role-information-angry-slimes-description - type: NpcFactionMember factions: From 3d899035eb60f4d4606b09f08e6acb1cc4930d81 Mon Sep 17 00:00:00 2001 From: Token Date: Fri, 12 Apr 2024 11:58:02 +0500 Subject: [PATCH 190/295] NoticeBoard is craftable now (#26847) * NoticeBoard is craftable now * Fix notice board to proper name capitalization * Fix notice board proper name in description * Update Resources/Prototypes/Recipes/Construction/furniture.yml --------- Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> --- .../Structures/Wallmounts/noticeboard.yml | 8 ++++++ .../Graphs/furniture/noticeboard.yml | 26 +++++++++++++++++++ .../Recipes/Construction/furniture.yml | 18 +++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 Resources/Prototypes/Recipes/Construction/Graphs/furniture/noticeboard.yml diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/noticeboard.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/noticeboard.yml index 4a442d0542f..421ab93be97 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/noticeboard.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/noticeboard.yml @@ -2,6 +2,8 @@ id: NoticeBoard name: notice board description: Is there a job for a witcher? + placement: + mode: SnapgridCenter components: - type: WallMount - type: Sprite @@ -53,3 +55,9 @@ - type: ContainerContainer containers: storagebase: !type:Container + - type: Tag + tags: + - Wooden + - type: Construction + graph: NoticeBoard + node: noticeBoard diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/furniture/noticeboard.yml b/Resources/Prototypes/Recipes/Construction/Graphs/furniture/noticeboard.yml new file mode 100644 index 00000000000..324745cbb9d --- /dev/null +++ b/Resources/Prototypes/Recipes/Construction/Graphs/furniture/noticeboard.yml @@ -0,0 +1,26 @@ +- type: constructionGraph + id: NoticeBoard + start: start + graph: + - node: start + actions: + - !type:DestroyEntity {} + edges: + - to: noticeBoard + completed: + - !type:SnapToGrid { } + steps: + - material: WoodPlank + amount: 3 + doAfter: 2 + - node: noticeBoard + entity: NoticeBoard + edges: + - to: start + completed: + - !type:SpawnPrototype + prototype: MaterialWoodPlank + amount: 3 + steps: + - tool: Prying + doAfter: 3 diff --git a/Resources/Prototypes/Recipes/Construction/furniture.yml b/Resources/Prototypes/Recipes/Construction/furniture.yml index a5cf53107db..5f7ec9c92d8 100644 --- a/Resources/Prototypes/Recipes/Construction/furniture.yml +++ b/Resources/Prototypes/Recipes/Construction/furniture.yml @@ -884,3 +884,21 @@ canBuildInImpassable: false conditions: - !type:TileNotBlocked + +- type: construction + id: NoticeBoard + name: notice board + description: Wooden notice board, can store paper inside itself. + graph: NoticeBoard + startNode: start + targetNode: noticeBoard + category: construction-category-furniture + icon: + sprite: Structures/Wallmounts/noticeboard.rsi + state: noticeboard + objectType: Structure + placementMode: SnapgridCenter + canRotate: true + canBuildInImpassable: false + conditions: + - !type:TileNotBlocked From e6548af3ba487b3c398f52217b5744bc548935f3 Mon Sep 17 00:00:00 2001 From: Velcroboy <107660393+IamVelcroboy@users.noreply.github.com> Date: Fri, 12 Apr 2024 02:04:35 -0500 Subject: [PATCH 191/295] Add drink container suffixes (#26835) Co-authored-by: Velcroboy --- .../Entities/Objects/Consumable/Drinks/drinks-cartons.yml | 1 + .../Entities/Objects/Consumable/Drinks/drinks_bottles.yml | 3 ++- .../Entities/Objects/Consumable/Drinks/trash_drinks.yml | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks-cartons.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks-cartons.yml index 3743cc82260..e758022710b 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks-cartons.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks-cartons.yml @@ -2,6 +2,7 @@ parent: DrinkBase id: DrinkCartonBaseFull abstract: true + suffix: Full components: - type: Openable sound: diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_bottles.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_bottles.yml index e2361cfa6ee..fc35fae1af8 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_bottles.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_bottles.yml @@ -3,6 +3,7 @@ parent: DrinkBase id: DrinkBottlePlasticBaseFull abstract: true + suffix: Full components: - type: Tag tags: @@ -726,7 +727,7 @@ parent: DrinkBottlePlasticBaseFull id: DrinkSugarJug name: sugar jug - suffix: for drinks + suffix: For Drinks, Full description: some people put this in their coffee... components: - type: SolutionContainerManager diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/trash_drinks.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/trash_drinks.yml index 86bc34f3c8b..a3f5bf1903f 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/trash_drinks.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/trash_drinks.yml @@ -5,6 +5,7 @@ parent: BaseItem abstract: true description: An empty bottle. + suffix: Empty components: - type: Sprite state: icon @@ -93,6 +94,7 @@ parent: BaseItem abstract: true description: An empty carton. + suffix: Empty components: - type: Sprite state: icon From ae1376ad0ec6cab269ab0a6fffe7d32da7a2b454 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Fri, 12 Apr 2024 03:07:25 -0400 Subject: [PATCH 192/295] uplink and store freshening (#26444) * uplink and store freshening * more * im gonna POOOOOOGGGGGGG * we love it --- .../Store/Ui/StoreBoundUserInterface.cs | 9 +-- .../Store/Ui/StoreListingControl.xaml | 1 + .../Store/Ui/StoreListingControl.xaml.cs | 80 +++++++++++++++++-- Content.Client/Store/Ui/StoreMenu.xaml | 5 -- Content.Client/Store/Ui/StoreMenu.xaml.cs | 76 ++++++------------ .../Store/Ui/StoreWithdrawWindow.xaml.cs | 4 +- .../Store/Systems/StoreSystem.Ui.cs | 7 +- .../Store/ListingLocalisationHelpers.cs | 26 +++--- Content.Shared/Store/ListingPrototype.cs | 53 ++++++------ Content.Shared/Store/StoreUi.cs | 9 +-- Resources/Locale/en-US/store/store.ftl | 2 +- .../Prototypes/Catalog/uplink_catalog.yml | 2 +- 12 files changed, 146 insertions(+), 128 deletions(-) diff --git a/Content.Client/Store/Ui/StoreBoundUserInterface.cs b/Content.Client/Store/Ui/StoreBoundUserInterface.cs index f87b92bc615..88ad0e3de8b 100644 --- a/Content.Client/Store/Ui/StoreBoundUserInterface.cs +++ b/Content.Client/Store/Ui/StoreBoundUserInterface.cs @@ -17,7 +17,7 @@ public sealed class StoreBoundUserInterface : BoundUserInterface private string _windowName = Loc.GetString("store-ui-default-title"); [ViewVariables] - private string _search = ""; + private string _search = string.Empty; [ViewVariables] private HashSet _listings = new(); @@ -41,7 +41,7 @@ protected override void Open() _menu.OnCategoryButtonPressed += (_, category) => { _menu.CurrentCategory = category; - SendMessage(new StoreRequestUpdateInterfaceMessage()); + _menu?.UpdateListing(); }; _menu.OnWithdrawAttempt += (_, type, amount) => @@ -49,11 +49,6 @@ protected override void Open() SendMessage(new StoreRequestWithdrawMessage(type, amount)); }; - _menu.OnRefreshButtonPressed += (_) => - { - SendMessage(new StoreRequestUpdateInterfaceMessage()); - }; - _menu.SearchTextUpdated += (_, search) => { _search = search.Trim().ToLowerInvariant(); diff --git a/Content.Client/Store/Ui/StoreListingControl.xaml b/Content.Client/Store/Ui/StoreListingControl.xaml index aefeec17cc8..12b4d7b5b30 100644 --- a/Content.Client/Store/Ui/StoreListingControl.xaml +++ b/Content.Client/Store/Ui/StoreListingControl.xaml @@ -15,6 +15,7 @@ Margin="0,0,4,0" MinSize="48 48" Stretch="KeepAspectCentered" /> + diff --git a/Content.Client/Store/Ui/StoreListingControl.xaml.cs b/Content.Client/Store/Ui/StoreListingControl.xaml.cs index bb600588e04..030f07dc7ca 100644 --- a/Content.Client/Store/Ui/StoreListingControl.xaml.cs +++ b/Content.Client/Store/Ui/StoreListingControl.xaml.cs @@ -1,25 +1,91 @@ +using Content.Client.GameTicking.Managers; +using Content.Shared.Store; using Robust.Client.AutoGenerated; using Robust.Client.Graphics; using Robust.Client.UserInterface; using Robust.Client.UserInterface.XAML; -using Robust.Shared.Graphics; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; namespace Content.Client.Store.Ui; [GenerateTypedNameReferences] public sealed partial class StoreListingControl : Control { - public StoreListingControl(string itemName, string itemDescription, - string price, bool canBuy, Texture? texture = null) + [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly IEntityManager _entity = default!; + [Dependency] private readonly IGameTiming _timing = default!; + private readonly ClientGameTicker _ticker; + + private readonly ListingData _data; + + private readonly bool _hasBalance; + private readonly string _price; + public StoreListingControl(ListingData data, string price, bool hasBalance, Texture? texture = null) { + IoCManager.InjectDependencies(this); RobustXamlLoader.Load(this); - StoreItemName.Text = itemName; - StoreItemDescription.SetMessage(itemDescription); + _ticker = _entity.System(); + + _data = data; + _hasBalance = hasBalance; + _price = price; - StoreItemBuyButton.Text = price; - StoreItemBuyButton.Disabled = !canBuy; + StoreItemName.Text = ListingLocalisationHelpers.GetLocalisedNameOrEntityName(_data, _prototype); + StoreItemDescription.SetMessage(ListingLocalisationHelpers.GetLocalisedDescriptionOrEntityDescription(_data, _prototype)); + + UpdateBuyButtonText(); + StoreItemBuyButton.Disabled = !CanBuy(); StoreItemTexture.Texture = texture; } + + private bool CanBuy() + { + if (!_hasBalance) + return false; + + var stationTime = _timing.CurTime.Subtract(_ticker.RoundStartTimeSpan); + if (_data.RestockTime > stationTime) + return false; + + return true; + } + + private void UpdateBuyButtonText() + { + var stationTime = _timing.CurTime.Subtract(_ticker.RoundStartTimeSpan); + if (_data.RestockTime > stationTime) + { + var timeLeftToBuy = stationTime - _data.RestockTime; + StoreItemBuyButton.Text = timeLeftToBuy.Duration().ToString(@"mm\:ss"); + } + else + { + StoreItemBuyButton.Text = _price; + } + } + + private void UpdateName() + { + var name = ListingLocalisationHelpers.GetLocalisedNameOrEntityName(_data, _prototype); + + var stationTime = _timing.CurTime.Subtract(_ticker.RoundStartTimeSpan); + if (_data.RestockTime > stationTime) + { + name += Loc.GetString("store-ui-button-out-of-stock"); + } + + StoreItemName.Text = name; + } + + protected override void FrameUpdate(FrameEventArgs args) + { + base.FrameUpdate(args); + + UpdateBuyButtonText(); + UpdateName(); + StoreItemBuyButton.Disabled = !CanBuy(); + } } diff --git a/Content.Client/Store/Ui/StoreMenu.xaml b/Content.Client/Store/Ui/StoreMenu.xaml index fc4cbe444fc..843c9dc0296 100644 --- a/Content.Client/Store/Ui/StoreMenu.xaml +++ b/Content.Client/Store/Ui/StoreMenu.xaml @@ -12,11 +12,6 @@ HorizontalAlignment="Left" Access="Public" HorizontalExpand="True" /> -