diff --git a/Content.Client/Atmos/EntitySystems/GasTileOverlaySystem.cs b/Content.Client/Atmos/EntitySystems/GasTileOverlaySystem.cs index 78185ce6b0..86cf0a9eb8 100644 --- a/Content.Client/Atmos/EntitySystems/GasTileOverlaySystem.cs +++ b/Content.Client/Atmos/EntitySystems/GasTileOverlaySystem.cs @@ -1,4 +1,5 @@ using Content.Client.Atmos.Overlays; +using Content.Shared.Atmos; using Content.Shared.Atmos.Components; using Content.Shared.Atmos.EntitySystems; using JetBrains.Annotations; @@ -36,28 +37,38 @@ namespace Content.Client.Atmos.EntitySystems private void OnHandleState(EntityUid gridUid, GasTileOverlayComponent comp, ref ComponentHandleState args) { - if (args.Current is not GasTileOverlayState state) - return; + Dictionary modifiedChunks; - // is this a delta or full state? - if (!state.FullState) + switch (args.Current) { - foreach (var index in comp.Chunks.Keys) + // is this a delta or full state? + case GasTileOverlayDeltaState delta: { - if (!state.AllChunks!.Contains(index)) - comp.Chunks.Remove(index); + modifiedChunks = delta.ModifiedChunks; + foreach (var index in comp.Chunks.Keys) + { + if (!delta.AllChunks.Contains(index)) + comp.Chunks.Remove(index); + } + + break; } - } - else - { - foreach (var index in comp.Chunks.Keys) + case GasTileOverlayState state: { - if (!state.Chunks.ContainsKey(index)) - comp.Chunks.Remove(index); + modifiedChunks = state.Chunks; + foreach (var index in comp.Chunks.Keys) + { + if (!state.Chunks.ContainsKey(index)) + comp.Chunks.Remove(index); + } + + break; } + default: + return; } - foreach (var (index, data) in state.Chunks) + foreach (var (index, data) in modifiedChunks) { comp.Chunks[index] = data; } diff --git a/Content.Client/Decals/DecalSystem.cs b/Content.Client/Decals/DecalSystem.cs index 901ab270fb..41e5f39c28 100644 --- a/Content.Client/Decals/DecalSystem.cs +++ b/Content.Client/Decals/DecalSystem.cs @@ -56,34 +56,43 @@ namespace Content.Client.Decals private void OnHandleState(EntityUid gridUid, DecalGridComponent gridComp, ref ComponentHandleState args) { - if (args.Current is not DecalGridState state) - return; - // is this a delta or full state? _removedChunks.Clear(); + Dictionary modifiedChunks; - if (!state.FullState) + switch (args.Current) { - foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys) + case DecalGridDeltaState delta: { - if (!state.AllChunks!.Contains(key)) - _removedChunks.Add(key); + modifiedChunks = delta.ModifiedChunks; + foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys) + { + if (!delta.AllChunks.Contains(key)) + _removedChunks.Add(key); + } + + break; } - } - else - { - foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys) + case DecalGridState state: { - if (!state.Chunks.ContainsKey(key)) - _removedChunks.Add(key); + modifiedChunks = state.Chunks; + foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys) + { + if (!state.Chunks.ContainsKey(key)) + _removedChunks.Add(key); + } + + break; } + default: + return; } if (_removedChunks.Count > 0) RemoveChunks(gridUid, gridComp, _removedChunks); - if (state.Chunks.Count > 0) - UpdateChunks(gridUid, gridComp, state.Chunks); + if (modifiedChunks.Count > 0) + UpdateChunks(gridUid, gridComp, modifiedChunks); } private void OnChunkUpdate(DecalChunkUpdateEvent ev) diff --git a/Content.Client/Examine/ExamineSystem.cs b/Content.Client/Examine/ExamineSystem.cs index 0bd82ea9c0..b787e2e573 100644 --- a/Content.Client/Examine/ExamineSystem.cs +++ b/Content.Client/Examine/ExamineSystem.cs @@ -239,8 +239,10 @@ namespace Content.Client.Examine if (knowTarget) { - var itemName = FormattedMessage.RemoveMarkup(Identity.Name(target, EntityManager, player)); - var labelMessage = FormattedMessage.FromMarkup($"[bold]{itemName}[/bold]"); + // TODO: FormattedMessage.RemoveMarkupPermissive + // var itemName = FormattedMessage.RemoveMarkupPermissive(Identity.Name(target, EntityManager, player)); + var itemName = FormattedMessage.FromMarkupPermissive(Identity.Name(target, EntityManager, player)).ToString(); + var labelMessage = FormattedMessage.FromMarkupPermissive($"[bold]{itemName}[/bold]"); var label = new RichTextLabel(); label.SetMessage(labelMessage); hBox.AddChild(label); diff --git a/Content.Client/Instruments/UI/InstrumentBoundUserInterface.cs b/Content.Client/Instruments/UI/InstrumentBoundUserInterface.cs index 2a846ff708..0f5729f55b 100644 --- a/Content.Client/Instruments/UI/InstrumentBoundUserInterface.cs +++ b/Content.Client/Instruments/UI/InstrumentBoundUserInterface.cs @@ -37,14 +37,8 @@ namespace Content.Client.Instruments.UI protected override void ReceiveMessage(BoundUserInterfaceMessage message) { - switch (message) - { - case InstrumentBandResponseBuiMessage bandRx: - _bandMenu?.Populate(bandRx.Nearby, EntMan); - break; - default: - break; - } + if (message is InstrumentBandResponseBuiMessage bandRx) + _bandMenu?.Populate(bandRx.Nearby, EntMan); } protected override void Open() diff --git a/Content.Client/Interactable/InteractionSystem.cs b/Content.Client/Interactable/InteractionSystem.cs index 0af8830e9a..ff0a607920 100644 --- a/Content.Client/Interactable/InteractionSystem.cs +++ b/Content.Client/Interactable/InteractionSystem.cs @@ -4,24 +4,6 @@ using Robust.Shared.Containers; namespace Content.Client.Interactable { - public sealed class InteractionSystem : SharedInteractionSystem - { - [Dependency] private readonly SharedContainerSystem _container = default!; - - public override bool CanAccessViaStorage(EntityUid user, EntityUid target) - { - if (!EntityManager.EntityExists(target)) - return false; - - if (!_container.TryGetContainingContainer(target, out var container)) - return false; - - if (!HasComp(container.Owner)) - return false; - - // we don't check if the user can access the storage entity itself. This should be handed by the UI system. - // Need to return if UI is open or not - return true; - } - } + // TODO Remove Shared prefix + public sealed class InteractionSystem : SharedInteractionSystem; } diff --git a/Content.Client/Interaction/DragDropHelper.cs b/Content.Client/Interaction/DragDropHelper.cs index abe35bf6d9..e453dfd74b 100644 --- a/Content.Client/Interaction/DragDropHelper.cs +++ b/Content.Client/Interaction/DragDropHelper.cs @@ -73,11 +73,6 @@ public sealed class DragDropHelper _cfg.OnValueChanged(CCVars.DragDropDeadZone, SetDeadZone, true); } - ~DragDropHelper() - { - _cfg.UnsubValueChanged(CCVars.DragDropDeadZone, SetDeadZone); - } - /// /// Tell the helper that the mouse button was pressed down on /// a target, thus a drag has the possibility to begin for this target. diff --git a/Content.Client/Options/UI/OptionsMenu.xaml b/Content.Client/Options/UI/OptionsMenu.xaml index 7f44a5a554..4f624c1bb6 100644 --- a/Content.Client/Options/UI/OptionsMenu.xaml +++ b/Content.Client/Options/UI/OptionsMenu.xaml @@ -1,13 +1,11 @@ + MinSize="800 450"> - - diff --git a/Content.Client/Options/UI/OptionsMenu.xaml.cs b/Content.Client/Options/UI/OptionsMenu.xaml.cs index f174764de6..35a3f751bb 100644 --- a/Content.Client/Options/UI/OptionsMenu.xaml.cs +++ b/Content.Client/Options/UI/OptionsMenu.xaml.cs @@ -1,17 +1,15 @@ -using Content.Client.Administration.Managers; using Robust.Client.AutoGenerated; using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.XAML; +using Robust.Shared.IoC; using Content.Client.Options.UI.Tabs; -using Robust.Shared.Timing; + namespace Content.Client.Options.UI { [GenerateTypedNameReferences] public sealed partial class OptionsMenu : DefaultWindow { - [Dependency] private readonly IClientAdminManager _clientAdminManager = default!; - public OptionsMenu() { RobustXamlLoader.Load(this); @@ -21,8 +19,6 @@ namespace Content.Client.Options.UI Tabs.SetTabTitle(1, Loc.GetString("ui-options-tab-graphics")); Tabs.SetTabTitle(2, Loc.GetString("ui-options-tab-controls")); Tabs.SetTabTitle(3, Loc.GetString("ui-options-tab-audio")); - Tabs.SetTabTitle(4, Loc.GetString("ui-options-tab-network")); - Tabs.SetTabTitle(5, "Админ"); UpdateTabs(); } @@ -31,11 +27,5 @@ namespace Content.Client.Options.UI { GraphicsTab.UpdateProperties(); } - - protected override void FrameUpdate(FrameEventArgs args) - { - Tabs.SetTabVisible(5, _clientAdminManager.IsActive()); - base.FrameUpdate(args); - } } } diff --git a/Content.Client/Options/UI/Tabs/NetworkTab.xaml b/Content.Client/Options/UI/Tabs/NetworkTab.xaml deleted file mode 100644 index 67c412b08b..0000000000 --- a/Content.Client/Options/UI/Tabs/NetworkTab.xaml +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - - - - - - - - - - - - - - public async Task SetAntagPref(ProtoId id, bool value) { - /* Впадлу фиксить тесты var prefMan = Server.ResolveDependency(); var prefs = prefMan.GetPreferences(Client.User!.Value); @@ -156,6 +155,5 @@ public sealed partial class TestPair var newPrefs = prefMan.GetPreferences(Client.User.Value); var newProf = (HumanoidCharacterProfile) newPrefs.SelectedCharacter; Assert.That(newProf.AntagPreferences.Contains(id), Is.EqualTo(value)); - */ } } diff --git a/Content.IntegrationTests/Tests/EntityTest.cs b/Content.IntegrationTests/Tests/EntityTest.cs index d3b1fb4722..54af64122b 100644 --- a/Content.IntegrationTests/Tests/EntityTest.cs +++ b/Content.IntegrationTests/Tests/EntityTest.cs @@ -350,8 +350,12 @@ namespace Content.IntegrationTests.Tests "DebrisFeaturePlacerController", // Above. "LoadedChunk", // Worldgen chunk loading malding. "BiomeSelection", // Whaddya know, requires config. + "ActivatableUI", // Requires enum key }; + // TODO TESTS + // auto ignore any components that have a "required" data field. + await using var pair = await PoolManager.GetServerClient(); var server = pair.Server; var entityManager = server.ResolveDependency(); diff --git a/Content.Server/Access/Systems/IdCardConsoleSystem.cs b/Content.Server/Access/Systems/IdCardConsoleSystem.cs index 3974998769..ce575ff784 100644 --- a/Content.Server/Access/Systems/IdCardConsoleSystem.cs +++ b/Content.Server/Access/Systems/IdCardConsoleSystem.cs @@ -44,8 +44,7 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem if (args.Actor is not { Valid: true } player) return; - TryWriteToTargetId(uid, args.FullName, args.JobTitle, args.AccessList, args.JobPrototype, args.SelectedIcon, - player, component); + TryWriteToTargetId(uid, args.FullName, args.JobTitle, args.AccessList, args.JobPrototype, args.SelectedIcon, player, component); UpdateUserInterface(uid, component, args); } @@ -153,6 +152,9 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem _idCard.TryChangeJobDepartment(targetId, job); } + UpdateStationRecord(uid, targetId, newFullName, newJobTitle, job, newJobIcon); + + if (!newAccessList.TrueForAll(x => component.AccessLevels.Contains(x))) { _sawmill.Warning($"User {ToPrettyString(uid)} tried to write unknown access tag."); diff --git a/Content.Server/Antag/AntagObjectivesSystem.cs b/Content.Server/Antag/AntagObjectivesSystem.cs new file mode 100644 index 0000000000..5aa31f66f6 --- /dev/null +++ b/Content.Server/Antag/AntagObjectivesSystem.cs @@ -0,0 +1,35 @@ +using Content.Server.Antag.Components; +using Content.Server.Objectives; +using Content.Shared.Mind; +using Content.Shared.Objectives.Systems; + +namespace Content.Server.Antag; + +/// +/// Adds fixed objectives to an antag made with AntagObjectivesComponent. +/// +public sealed class AntagObjectivesSystem : EntitySystem +{ + [Dependency] private readonly SharedMindSystem _mind = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnAntagSelected); + } + + private void OnAntagSelected(Entity ent, ref AfterAntagEntitySelectedEvent args) + { + if (!_mind.TryGetMind(args.Session, out var mindId, out var mind)) + { + Log.Error($"Antag {ToPrettyString(args.EntityUid):player} was selected by {ToPrettyString(ent):rule} but had no mind attached!"); + return; + } + + foreach (var id in ent.Comp.Objectives) + { + _mind.TryAddObjective(mindId, mind, id); + } + } +} diff --git a/Content.Server/Antag/AntagRandomObjectivesSystem.cs b/Content.Server/Antag/AntagRandomObjectivesSystem.cs new file mode 100644 index 0000000000..c935b8c064 --- /dev/null +++ b/Content.Server/Antag/AntagRandomObjectivesSystem.cs @@ -0,0 +1,52 @@ +using Content.Server.Antag.Components; +using Content.Server.Objectives; +using Content.Shared.Mind; +using Content.Shared.Objectives.Components; +using Content.Shared.Objectives.Systems; +using Robust.Shared.Random; + +namespace Content.Server.Antag; + +/// +/// Adds fixed objectives to an antag made with AntagRandomObjectivesComponent. +/// +public sealed class AntagRandomObjectivesSystem : EntitySystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedMindSystem _mind = default!; + [Dependency] private readonly ObjectivesSystem _objectives = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnAntagSelected); + } + + private void OnAntagSelected(Entity ent, ref AfterAntagEntitySelectedEvent args) + { + if (!_mind.TryGetMind(args.Session, out var mindId, out var mind)) + { + Log.Error($"Antag {ToPrettyString(args.EntityUid):player} was selected by {ToPrettyString(ent):rule} but had no mind attached!"); + return; + } + + var difficulty = 0f; + foreach (var set in ent.Comp.Sets) + { + if (!_random.Prob(set.Prob)) + continue; + + for (var pick = 0; pick < set.MaxPicks && ent.Comp.MaxDifficulty > difficulty; pick++) + { + if (_objectives.GetRandomObjective(mindId, mind, set.Groups) is not {} objective) + continue; + + _mind.AddObjective(mindId, mind, objective); + var adding = Comp(objective).Difficulty; + difficulty += adding; + Log.Debug($"Added objective {ToPrettyString(objective):objective} to {ToPrettyString(args.EntityUid):player} with {adding} difficulty"); + } + } + } +} diff --git a/Content.Server/Antag/AntagSelectionSystem.API.cs b/Content.Server/Antag/AntagSelectionSystem.API.cs index 14492ded4e..976458ba92 100644 --- a/Content.Server/Antag/AntagSelectionSystem.API.cs +++ b/Content.Server/Antag/AntagSelectionSystem.API.cs @@ -147,7 +147,7 @@ public sealed partial class AntagSelectionSystem } /// - /// Helper specifically for + /// Helper to get just the mind entities and not names. /// public List GetAntagMindEntityUids(Entity ent) { diff --git a/Content.Server/Antag/AntagSelectionSystem.cs b/Content.Server/Antag/AntagSelectionSystem.cs index a7ff0810e1..a46d4171b7 100644 --- a/Content.Server/Antag/AntagSelectionSystem.cs +++ b/Content.Server/Antag/AntagSelectionSystem.cs @@ -7,6 +7,7 @@ using Content.Server.GameTicking.Rules; using Content.Server.Ghost.Roles; using Content.Server.Ghost.Roles.Components; using Content.Server.Mind; +using Content.Server.Objectives; using Content.Server.Preferences.Managers; using Content.Server.Roles; using Content.Server.Roles.Jobs; @@ -25,10 +26,11 @@ using Robust.Shared.Enums; using Robust.Shared.Map; using Robust.Shared.Player; using Robust.Shared.Random; +using Robust.Shared.Utility; using Content.Server._Miracle.GulagSystem; using Content.Server._White.Sponsors; using Content.Server.Inventory; -using FastAccessors; +using Content.Shared.GameTicking; using Robust.Shared.Utility; namespace Content.Server.Antag; @@ -59,6 +61,8 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem(OnTakeGhostRole); + SubscribeLocalEvent(OnObjectivesTextGetInfo); + SubscribeLocalEvent(OnPlayerSpawning); SubscribeLocalEvent(OnJobsAssigned); SubscribeLocalEvent(OnSpawnComplete); @@ -456,6 +460,15 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem ent, ref ObjectivesTextGetInfoEvent args) + { + if (ent.Comp.AgentName is not {} name) + return; + + args.Minds = ent.Comp.SelectedMinds; + args.AgentName = Loc.GetString(name); + } + public float GetPremiumPoolChance(ICommonSession session) { if (!_sponsors.TryGetInfo(session.UserId, out var info)) diff --git a/Content.Server/Antag/Components/AntagObjectivesComponent.cs b/Content.Server/Antag/Components/AntagObjectivesComponent.cs new file mode 100644 index 0000000000..357c138f46 --- /dev/null +++ b/Content.Server/Antag/Components/AntagObjectivesComponent.cs @@ -0,0 +1,18 @@ +using Content.Server.Antag; +using Content.Shared.Objectives.Components; +using Robust.Shared.Prototypes; + +namespace Content.Server.Antag.Components; + +/// +/// Gives antags selected by this rule a fixed list of objectives. +/// +[RegisterComponent, Access(typeof(AntagObjectivesSystem))] +public sealed partial class AntagObjectivesComponent : Component +{ + /// + /// List of static objectives to give. + /// + [DataField(required: true)] + public List> Objectives = new(); +} diff --git a/Content.Server/Antag/Components/AntagRandomObjectivesComponent.cs b/Content.Server/Antag/Components/AntagRandomObjectivesComponent.cs new file mode 100644 index 0000000000..9a551acc49 --- /dev/null +++ b/Content.Server/Antag/Components/AntagRandomObjectivesComponent.cs @@ -0,0 +1,52 @@ +using Content.Server.Antag; +using Content.Shared.Random; +using Robust.Shared.Prototypes; + +namespace Content.Server.Antag.Components; + +/// +/// Gives antags selected by this rule a random list of objectives. +/// +[RegisterComponent, Access(typeof(AntagRandomObjectivesSystem))] +public sealed partial class AntagRandomObjectivesComponent : Component +{ + /// + /// Each set of objectives to add. + /// + [DataField(required: true)] + public List Sets = new(); + + /// + /// If the total difficulty of the currently given objectives exceeds, no more will be given. + /// + [DataField(required: true)] + public float MaxDifficulty; +} + +/// +/// A set of objectives to try picking. +/// Difficulty is checked over all sets, but each set has its own probability and pick count. +/// +[DataRecord] +public record struct AntagObjectiveSet() +{ + /// + /// The grouping used by the objective system to pick random objectives. + /// First a group is picked from these, then an objective from that group. + /// + [DataField(required: true)] + public ProtoId Groups = string.Empty; + + /// + /// Probability of this set being used. + /// + [DataField] + public float Prob = 1f; + + /// + /// Number of times to try picking objectives from this set. + /// Even if there is enough difficulty remaining, no more will be given after this. + /// + [DataField] + public int MaxPicks = 20; +} diff --git a/Content.Server/Antag/Components/AntagSelectionComponent.cs b/Content.Server/Antag/Components/AntagSelectionComponent.cs index d8fdd2be95..61b0eb0611 100644 --- a/Content.Server/Antag/Components/AntagSelectionComponent.cs +++ b/Content.Server/Antag/Components/AntagSelectionComponent.cs @@ -41,6 +41,13 @@ public sealed partial class AntagSelectionComponent : Component /// Is not serialized. /// public HashSet SelectedSessions = new(); + + /// + /// Locale id for the name of the antag. + /// If this is set then the antag is listed in the round-end summary. + /// + [DataField] + public LocId? AgentName; } [DataDefinition] diff --git a/Content.Server/Cargo/Systems/CargoSystem.Orders.cs b/Content.Server/Cargo/Systems/CargoSystem.Orders.cs index 2dc8ecf201..1ee8e8dbdb 100644 --- a/Content.Server/Cargo/Systems/CargoSystem.Orders.cs +++ b/Content.Server/Cargo/Systems/CargoSystem.Orders.cs @@ -20,6 +20,8 @@ namespace Content.Server.Cargo.Systems { public sealed partial class CargoSystem { + [Dependency] private readonly SharedTransformSystem _transformSystem = default!; + /// /// How much time to wait (in seconds) before increasing bank accounts balance. /// @@ -494,6 +496,9 @@ namespace Content.Server.Cargo.Systems // Create the item itself var item = Spawn(order.ProductId, spawn); + // Ensure the item doesn't start anchored + _transformSystem.Unanchor(item, Transform(item)); + // Create a sheet of paper to write the order details on var printed = EntityManager.SpawnEntity(paperProto, spawn); if (TryComp(printed, out var paper)) diff --git a/Content.Server/Changeling/ChangelingRuleSystem.cs b/Content.Server/Changeling/ChangelingRuleSystem.cs index ae88a7db57..f8f2d4ea6e 100644 --- a/Content.Server/Changeling/ChangelingRuleSystem.cs +++ b/Content.Server/Changeling/ChangelingRuleSystem.cs @@ -1,9 +1,11 @@ +using System.Linq; using Content.Server._Miracle.GulagSystem; using Content.Server.Antag; using Content.Server.GameTicking.Rules; using Content.Server.Mind; using Content.Server.Objectives; using Content.Shared._White.Mood; +using Content.Shared.Mind; using Content.Shared.Objectives.Components; namespace Content.Server.Changeling; @@ -30,7 +32,7 @@ public sealed class ChangelingRuleSystem : GameRuleSystem (mindId, Comp(mindId).CharacterName ?? "?")).ToList(); args.AgentName = Loc.GetString("changeling-round-end-agent-name"); } diff --git a/Content.Server/Chemistry/ReagentEffects/CauseZombieInfection.cs b/Content.Server/Chemistry/ReagentEffects/CauseZombieInfection.cs index 029b149500..96c57f7465 100644 --- a/Content.Server/Chemistry/ReagentEffects/CauseZombieInfection.cs +++ b/Content.Server/Chemistry/ReagentEffects/CauseZombieInfection.cs @@ -2,6 +2,7 @@ using Content.Server.Zombies; using Content.Shared.Chemistry.Reagent; using Robust.Shared.Configuration; using Robust.Shared.Prototypes; +using Content.Shared.Zombies; namespace Content.Server.Chemistry.ReagentEffects; diff --git a/Content.Server/Chemistry/ReagentEffects/CureZombieInfection.cs b/Content.Server/Chemistry/ReagentEffects/CureZombieInfection.cs index d56fc11531..20e2c015c4 100644 --- a/Content.Server/Chemistry/ReagentEffects/CureZombieInfection.cs +++ b/Content.Server/Chemistry/ReagentEffects/CureZombieInfection.cs @@ -2,6 +2,7 @@ using Content.Server.Zombies; using Content.Shared.Chemistry.Reagent; using Robust.Shared.Configuration; using Robust.Shared.Prototypes; +using Content.Shared.Zombies; namespace Content.Server.Chemistry.ReagentEffects; diff --git a/Content.Server/Configurable/ConfigurationSystem.cs b/Content.Server/Configurable/ConfigurationSystem.cs index 2683bf4e09..5f5f1ef7d1 100644 --- a/Content.Server/Configurable/ConfigurationSystem.cs +++ b/Content.Server/Configurable/ConfigurationSystem.cs @@ -24,6 +24,7 @@ public sealed class ConfigurationSystem : EntitySystem private void OnInteractUsing(EntityUid uid, ConfigurationComponent component, InteractUsingEvent args) { + // TODO use activatable ui system if (args.Handled) return; diff --git a/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs b/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs index 3460124158..402d005dd4 100644 --- a/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs +++ b/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs @@ -63,9 +63,21 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem SubscribeLocalEvent(OnToggleLinks); SubscribeLocalEvent(OnConfigButtonPressed); + SubscribeLocalEvent(OnUiRangeCheck); + SubscribeLocalEvent(OnComponentRemoved); } + private void OnUiRangeCheck(Entity ent, ref BoundUserInterfaceCheckRangeEvent args) + { + if (ent.Comp.ActiveDeviceList == null || args.Result == BoundUserInterfaceRangeResult.Fail) + return; + + DebugTools.Assert(Exists(ent.Comp.ActiveDeviceList)); + if (!_interactionSystem.InRangeUnobstructed(args.Actor!, ent.Comp.ActiveDeviceList.Value)) + args.Result = BoundUserInterfaceRangeResult.Fail; + } + private void OnShutdown(EntityUid uid, NetworkConfiguratorComponent component, ComponentShutdown args) { ClearDevices(uid, component); @@ -75,23 +87,6 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem component.ActiveDeviceList = null; } - public override void Update(float frameTime) - { - base.Update(frameTime); - - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var component)) - { - if (component.ActiveDeviceList != null - && EntityManager.EntityExists(component.ActiveDeviceList.Value) - && _interactionSystem.InRangeUnobstructed(uid, component.ActiveDeviceList.Value)) - continue; - - //The network configurator is a handheld device. There can only ever be an ui session open for the player holding the device. - _uiSystem.CloseUi(uid, NetworkConfiguratorUiKey.Configure); - } - } - private void OnMapInit(EntityUid uid, NetworkConfiguratorComponent component, MapInitEvent args) { UpdateListUiState(uid, component); diff --git a/Content.Server/GameTicking/Rules/AntagLoadProfileRuleSystem.cs b/Content.Server/GameTicking/Rules/AntagLoadProfileRuleSystem.cs new file mode 100644 index 0000000000..fd3fb6cd65 --- /dev/null +++ b/Content.Server/GameTicking/Rules/AntagLoadProfileRuleSystem.cs @@ -0,0 +1,39 @@ +using Content.Server.Antag; +using Content.Server.GameTicking.Rules.Components; +using Content.Server.Humanoid; +using Content.Server.Preferences.Managers; +using Content.Shared.Humanoid; +using Content.Shared.Humanoid.Prototypes; +using Content.Shared.Preferences; +using Robust.Shared.Prototypes; + +namespace Content.Server.GameTicking.Rules; + +public sealed class AntagLoadProfileRuleSystem : GameRuleSystem +{ + [Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly IServerPreferencesManager _prefs = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnSelectEntity); + } + + private void OnSelectEntity(Entity ent, ref AntagSelectEntityEvent args) + { + if (args.Handled) + return; + + var profile = args.Session != null + ? _prefs.GetPreferences(args.Session.UserId).SelectedCharacter as HumanoidCharacterProfile + : HumanoidCharacterProfile.RandomWithSpecies(); + if (profile?.Species is not {} speciesId || !_proto.TryIndex(speciesId, out var species)) + species = _proto.Index(SharedHumanoidAppearanceSystem.DefaultSpecies); + + args.Entity = Spawn(species.Prototype); + _humanoid.LoadProfile(args.Entity.Value, profile); + } +} diff --git a/Content.Server/GameTicking/Rules/Components/AntagLoadProfileRuleCOmponent.cs b/Content.Server/GameTicking/Rules/Components/AntagLoadProfileRuleCOmponent.cs new file mode 100644 index 0000000000..5e58fd14fc --- /dev/null +++ b/Content.Server/GameTicking/Rules/Components/AntagLoadProfileRuleCOmponent.cs @@ -0,0 +1,7 @@ +namespace Content.Server.GameTicking.Rules.Components; + +/// +/// Makes this rules antags spawn a humanoid, either from the player's profile or a random one. +/// +[RegisterComponent] +public sealed partial class AntagLoadProfileRuleComponent : Component; diff --git a/Content.Server/GameTicking/Rules/Components/ThiefRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/ThiefRuleComponent.cs index 01a078625a..6ad1e17775 100644 --- a/Content.Server/GameTicking/Rules/Components/ThiefRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/ThiefRuleComponent.cs @@ -8,23 +8,4 @@ namespace Content.Server.GameTicking.Rules.Components; /// Stores data for . /// [RegisterComponent, Access(typeof(ThiefRuleSystem))] -public sealed partial class ThiefRuleComponent : Component -{ - [DataField] - public ProtoId BigObjectiveGroup = "ThiefBigObjectiveGroups"; - - [DataField] - public ProtoId SmallObjectiveGroup = "ThiefObjectiveGroups"; - - [DataField] - public ProtoId EscapeObjectiveGroup = "ThiefEscapeObjectiveGroups"; - - [DataField] - public float BigObjectiveChance = 0.7f; - - [DataField] - public float MaxObjectiveDifficulty = 2.5f; - - [DataField] - public int MaxStealObjectives = 10; -} +public sealed partial class ThiefRuleComponent : Component; diff --git a/Content.Server/GameTicking/Rules/Components/TraitorRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/TraitorRuleComponent.cs index 76ef518215..50d2a15d34 100644 --- a/Content.Server/GameTicking/Rules/Components/TraitorRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/TraitorRuleComponent.cs @@ -23,9 +23,6 @@ public sealed partial class TraitorRuleComponent : Component [DataField] public ProtoId SyndicateFaction = "Syndicate"; - [DataField] - public ProtoId ObjectiveGroup = "TraitorObjectiveGroups"; - [DataField] public ProtoId CodewordAdjectives = "adjectives"; @@ -78,7 +75,4 @@ public sealed partial class TraitorRuleComponent : Component /// [DataField] public int StartingBalance = 20; - - [DataField] - public int MaxDifficulty = 5; } diff --git a/Content.Server/GameTicking/Rules/GenericAntagRuleSystem.cs b/Content.Server/GameTicking/Rules/GenericAntagRuleSystem.cs index 81bdda706b..0367aa1460 100644 --- a/Content.Server/GameTicking/Rules/GenericAntagRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/GenericAntagRuleSystem.cs @@ -1,6 +1,8 @@ using Content.Server.GameTicking.Rules.Components; using Content.Server.Objectives; +using Content.Shared.Mind; using System.Diagnostics.CodeAnalysis; +using System.Linq; namespace Content.Server.GameTicking.Rules; @@ -47,7 +49,8 @@ public sealed class GenericAntagRuleSystem : GameRuleSystem (mindId, Comp(mindId).CharacterName ?? "?")).ToList(); args.AgentName = Loc.GetString(comp.AgentName); } } diff --git a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs index b7af4cbb59..bdf43aacd8 100644 --- a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs @@ -1,11 +1,9 @@ using Content.Server.Antag; using Content.Server.Communications; using Content.Server.GameTicking.Rules.Components; -using Content.Server.Humanoid; using Content.Server.Nuke; using Content.Server.NukeOps; using Content.Server.Popups; -using Content.Server.Preferences.Managers; using Content.Server.Roles; using Content.Server.RoundEnd; using Content.Server.Shuttles.Events; @@ -13,37 +11,39 @@ using Content.Server.Shuttles.Systems; using Content.Server.Station.Components; using Content.Server.Store.Components; using Content.Server.Store.Systems; -using Content.Shared.Humanoid; -using Content.Shared.Humanoid.Prototypes; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.NPC.Components; using Content.Shared.NPC.Systems; using Content.Shared.Nuke; using Content.Shared.NukeOps; -using Content.Shared.Preferences; using Content.Shared.Store; using Content.Shared.Tag; using Content.Shared.Zombies; using Robust.Shared.Map; -using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Utility; using System.Linq; using Content.Server.GameTicking.Components; +using Content.Server.Humanoid; +using Content.Server.Preferences.Managers; using Content.Server.StationEvents.Components; using Content.Shared._White.Antag; using Content.Shared.FixedPoint; +using Content.Shared.Humanoid; +using Content.Shared.Humanoid.Prototypes; using Content.Shared.Mind; +using Content.Shared.Preferences; +using Robust.Shared.Prototypes; namespace Content.Server.GameTicking.Rules; public sealed class NukeopsRuleSystem : GameRuleSystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IServerPreferencesManager _prefs = default!; - [Dependency] private readonly EmergencyShuttleSystem _emergency = default!; [Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly EmergencyShuttleSystem _emergency = default!; [Dependency] private readonly NpcFactionSystem _npcFaction = default!; [Dependency] private readonly AntagSelectionSystem _antag = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; @@ -75,7 +75,6 @@ public sealed class NukeopsRuleSystem : GameRuleSystem SubscribeLocalEvent(OnWarDeclared); SubscribeLocalEvent(OnShuttleCallAttempt); - SubscribeLocalEvent(OnAntagSelectEntity); SubscribeLocalEvent(OnAfterAntagEntSelected); } diff --git a/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs b/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs index 083085fa0d..faec4a9e9c 100644 --- a/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs @@ -24,7 +24,6 @@ public sealed class ThiefRuleSystem : GameRuleSystem SubscribeLocalEvent(AfterAntagSelected); SubscribeLocalEvent(OnGetBriefing); - SubscribeLocalEvent(OnObjectivesTextGetInfo); } private void AfterAntagSelected(Entity ent, ref AfterAntagEntitySelectedEvent args) @@ -33,41 +32,9 @@ public sealed class ThiefRuleSystem : GameRuleSystem return; //Generate objectives - GenerateObjectives(mindId, mind, ent); _antag.SendBriefing(args.EntityUid, MakeBriefing(args.EntityUid), null, null); } - private void GenerateObjectives(EntityUid mindId, MindComponent mind, ThiefRuleComponent thiefRule) - { - // Give thieves their objectives - var difficulty = 0f; - - if (_random.Prob(thiefRule.BigObjectiveChance)) // 70% chance to 1 big objective (structure or animal) - { - var objective = _objectives.GetRandomObjective(mindId, mind, thiefRule.BigObjectiveGroup); - if (objective != null) - { - _mindSystem.AddObjective(mindId, mind, objective.Value); - difficulty += Comp(objective.Value).Difficulty; - } - } - - for (var i = 0; i < thiefRule.MaxStealObjectives && thiefRule.MaxObjectiveDifficulty > difficulty; i++) // Many small objectives - { - var objective = _objectives.GetRandomObjective(mindId, mind, thiefRule.SmallObjectiveGroup); - if (objective == null) - continue; - - _mindSystem.AddObjective(mindId, mind, objective.Value); - difficulty += Comp(objective.Value).Difficulty; - } - - //Escape target - var escapeObjective = _objectives.GetRandomObjective(mindId, mind, thiefRule.EscapeObjectiveGroup); - if (escapeObjective != null) - _mindSystem.AddObjective(mindId, mind, escapeObjective.Value); - } - //Add mind briefing private void OnGetBriefing(Entity thief, ref GetBriefingEvent args) { @@ -87,10 +54,4 @@ public sealed class ThiefRuleSystem : GameRuleSystem briefing += "\n \n" + Loc.GetString("thief-role-greeting-equipment") + "\n"; return briefing; } - - private void OnObjectivesTextGetInfo(Entity ent, ref ObjectivesTextGetInfoEvent args) - { - args.Minds = _antag.GetAntagMindEntityUids(ent.Owner); - args.AgentName = Loc.GetString("thief-round-end-agent-name"); - } } diff --git a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs index bf24cfac7d..b87e26780b 100644 --- a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs @@ -38,15 +38,12 @@ public sealed class TraitorRuleSystem : GameRuleSystem [Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly GameTicker _gameTicker = default!; - public const int MaxPicks = 20; - public override void Initialize() { base.Initialize(); SubscribeLocalEvent(AfterEntitySelected); - SubscribeLocalEvent(OnObjectivesTextGetInfo); SubscribeLocalEvent(OnObjectivesTextPrepend); } @@ -74,7 +71,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem } } - public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component, bool giveUplink = true, bool giveObjectives = true) + public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component, bool giveUplink = true) { //Grab the mind if it wasnt provided if (!_mindSystem.TryGetMind(traitor, out var mindId, out var mind)) @@ -127,37 +124,16 @@ public sealed class TraitorRuleSystem : GameRuleSystem if (richAspect) // WD TraitorRichAspect.NotifyTraitor(mind, _chatManager); - // Give traitors their objectives - if (giveObjectives) - { - var difficulty = 0f; - for (var pick = 0; pick < MaxPicks && component.MaxDifficulty > difficulty; pick++) - { - var objective = _objectives.GetRandomObjective(mindId, mind, component.ObjectiveGroup); - if (objective == null) - continue; - - _mindSystem.AddObjective(mindId, mind, objective.Value); - var adding = Comp(objective.Value).Difficulty; - difficulty += adding; - Log.Debug($"Added objective {ToPrettyString(objective):objective} with {adding} difficulty"); - } - } - return true; } - private void OnObjectivesTextGetInfo(EntityUid uid, TraitorRuleComponent comp, ref ObjectivesTextGetInfoEvent args) - { - args.Minds = _antag.GetAntagMindEntityUids(uid); - args.AgentName = Loc.GetString("traitor-round-end-agent-name"); - } - + // TODO: AntagCodewordsComponent private void OnObjectivesTextPrepend(EntityUid uid, TraitorRuleComponent comp, ref ObjectivesTextPrependEvent args) { args.Text += "\n" + Loc.GetString("traitor-round-end-codewords", ("codewords", string.Join(", ", comp.Codewords))); } + // TODO: figure out how to handle this? add priority to briefing event? private string GenerateBriefing(string[] codewords, Note[]? uplinkCode, string? objectiveIssuer = null) { var sb = new StringBuilder(); diff --git a/Content.Server/Interaction/InteractionSystem.cs b/Content.Server/Interaction/InteractionSystem.cs index 4eac7e9ef1..9ac82b2185 100644 --- a/Content.Server/Interaction/InteractionSystem.cs +++ b/Content.Server/Interaction/InteractionSystem.cs @@ -7,31 +7,6 @@ using Robust.Shared.Player; namespace Content.Server.Interaction { - /// - /// Governs interactions during clicking on entities - /// - [UsedImplicitly] - public sealed partial class InteractionSystem : SharedInteractionSystem - { - [Dependency] private readonly SharedContainerSystem _container = default!; - [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; - - public override bool CanAccessViaStorage(EntityUid user, EntityUid target) - { - if (Deleted(target)) - return false; - - if (!_container.TryGetContainingContainer(target, out var container)) - return false; - - if (!TryComp(container.Owner, out StorageComponent? storage)) - return false; - - if (storage.Container?.ID != container.ID) - return false; - - // we don't check if the user can access the storage entity itself. This should be handed by the UI system. - return _uiSystem.IsUiOpen(container.Owner, StorageComponent.StorageUiKey.Key, user); - } - } + // TODO Remove Shared prefix + public sealed class InteractionSystem : SharedInteractionSystem; } diff --git a/Content.Server/Lock/EntitySystems/ActivatableUIRequiresLockSystem.cs b/Content.Server/Lock/EntitySystems/ActivatableUIRequiresLockSystem.cs deleted file mode 100644 index 04f8e2eb54..0000000000 --- a/Content.Server/Lock/EntitySystems/ActivatableUIRequiresLockSystem.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Content.Server.Lock.Components; -using Content.Server.Popups; -using Content.Shared.UserInterface; -using Content.Shared.Lock; -using Content.Server.UserInterface; -using ActivatableUISystem = Content.Shared.UserInterface.ActivatableUISystem; - -namespace Content.Server.Lock.EntitySystems; -public sealed class ActivatableUIRequiresLockSystem : EntitySystem -{ - [Dependency] private readonly ActivatableUISystem _activatableUI = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnUIOpenAttempt); - SubscribeLocalEvent(LockToggled); - } - - private void OnUIOpenAttempt(EntityUid uid, ActivatableUIRequiresLockComponent component, ActivatableUIOpenAttemptEvent args) - { - if (args.Cancelled) - return; - - if (TryComp(uid, out var lockComp) && lockComp.Locked != component.requireLocked) - { - args.Cancel(); - if (lockComp.Locked) - _popupSystem.PopupEntity(Loc.GetString("entity-storage-component-locked-message"), uid, args.User); - } - } - - private void LockToggled(EntityUid uid, ActivatableUIRequiresLockComponent component, LockToggledEvent args) - { - if (!TryComp(uid, out var lockComp) || lockComp.Locked == component.requireLocked) - return; - - _activatableUI.CloseAll(uid); - } -} - diff --git a/Content.Server/Objectives/ObjectivesSystem.cs b/Content.Server/Objectives/ObjectivesSystem.cs index 47fe4eb5f8..f8ecc22828 100644 --- a/Content.Server/Objectives/ObjectivesSystem.cs +++ b/Content.Server/Objectives/ObjectivesSystem.cs @@ -36,14 +36,14 @@ public sealed class ObjectivesSystem : SharedObjectivesSystem private void OnRoundEndText(RoundEndTextAppendEvent ev) { // go through each gamerule getting data for the roundend summary. - var summaries = new Dictionary>>(); + var summaries = new Dictionary>>(); var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var gameRule)) { if (!_gameTicker.IsGameRuleAdded(uid, gameRule)) continue; - var info = new ObjectivesTextGetInfoEvent(new List(), string.Empty); + var info = new ObjectivesTextGetInfoEvent(new List<(EntityUid, string)>(), string.Empty); RaiseLocalEvent(uid, ref info); if (info.Minds.Count == 0) continue; @@ -51,7 +51,7 @@ public sealed class ObjectivesSystem : SharedObjectivesSystem // first group the gamerules by their agents, for example 2 different dragons var agent = info.AgentName; if (!summaries.ContainsKey(agent)) - summaries[agent] = new Dictionary>(); + summaries[agent] = new Dictionary>(); var prepend = new ObjectivesTextPrependEvent(""); RaiseLocalEvent(uid, ref prepend); @@ -79,7 +79,7 @@ public sealed class ObjectivesSystem : SharedObjectivesSystem foreach (var (_, minds) in summary) { total += minds.Count; - totalInCustody += minds.Where(m => IsInCustody(m)).Count(); + totalInCustody += minds.Where(pair => IsInCustody(pair.Item1)).Count(); } var result = new StringBuilder(); @@ -104,19 +104,16 @@ public sealed class ObjectivesSystem : SharedObjectivesSystem } } - private void AddSummary(StringBuilder result, string agent, List minds) + private void AddSummary(StringBuilder result, string agent, List<(EntityUid, string)> minds) { var agentSummaries = new List<(string summary, float successRate, int completedObjectives)>(); - foreach (var mindId in minds) + foreach (var (mindId, name) in minds) { - if (!TryComp(mindId, out MindComponent? mind)) - continue; - - var title = GetTitle(mindId, mind); - if (title == null) + if (!TryComp(mindId, out var mind)) continue; + var title = GetTitle((mindId, mind), name); var custody = IsInCustody(mindId, mind) ? Loc.GetString("objectives-in-custody") : string.Empty; var objectives = mind.Objectives; @@ -238,34 +235,18 @@ public sealed class ObjectivesSystem : SharedObjectivesSystem /// /// Get the title for a player's mind used in round end. + /// Pass in the original entity name which is shown alongside username. /// - public string? GetTitle(EntityUid mindId, MindComponent? mind = null) + public string GetTitle(Entity mind, string name) { - if (!Resolve(mindId, ref mind)) - return null; - - var name = mind.CharacterName; - var username = (string?) null; - - if (mind.OriginalOwnerUserId != null && - _player.TryGetPlayerData(mind.OriginalOwnerUserId.Value, out var sessionData)) + if (Resolve(mind, ref mind.Comp) && + mind.Comp.OriginalOwnerUserId != null && + _player.TryGetPlayerData(mind.Comp.OriginalOwnerUserId.Value, out var sessionData)) { - username = sessionData.UserName; + var username = sessionData.UserName; + return Loc.GetString("objectives-player-user-named", ("user", username), ("name", name)); } - - if (username != null) - { - if (name != null) - return Loc.GetString("objectives-player-user-named", ("user", username), ("name", name)); - - return Loc.GetString("objectives-player-user", ("user", username)); - } - - // nothing to identify the player by, just give up - if (name == null) - return null; - return Loc.GetString("objectives-player-named", ("name", name)); } } @@ -279,7 +260,7 @@ public sealed class ObjectivesSystem : SharedObjectivesSystem /// The objectives system already checks if the game rule is added so you don't need to check that in this event's handler. /// [ByRefEvent] -public record struct ObjectivesTextGetInfoEvent(List Minds, string AgentName); +public record struct ObjectivesTextGetInfoEvent(List<(EntityUid, string)> Minds, string AgentName); /// /// Raised on the game rule before text for each agent's objectives is added, letting you prepend something. diff --git a/Content.Server/Preferences/Managers/ServerPreferencesManager.cs b/Content.Server/Preferences/Managers/ServerPreferencesManager.cs index 971a248702..49747aea3a 100644 --- a/Content.Server/Preferences/Managers/ServerPreferencesManager.cs +++ b/Content.Server/Preferences/Managers/ServerPreferencesManager.cs @@ -26,6 +26,7 @@ namespace Content.Server.Preferences.Managers [Dependency] private readonly IServerDbManager _db = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IDependencyCollection _dependencies = default!; + [Dependency] private readonly ILogManager _log = default!; // WD-EDIT [Dependency] private readonly SponsorsManager _sponsors = default!; @@ -36,7 +37,9 @@ namespace Content.Server.Preferences.Managers private readonly Dictionary _cachedPlayerPrefs = new(); - private readonly ISawmill _sawmill = default!; + private ISawmill _sawmill = default!; + + private int MaxCharacterSlots => _cfg.GetCVar(CCVars.GameMaxCharacterSlots); public void Init() { @@ -44,6 +47,7 @@ namespace Content.Server.Preferences.Managers _netManager.RegisterNetMessage(HandleSelectCharacterMessage); _netManager.RegisterNetMessage(HandleUpdateCharacterMessage); _netManager.RegisterNetMessage(HandleDeleteCharacterMessage); + _sawmill = _log.GetSawmill("prefs"); } private async void HandleSelectCharacterMessage(MsgSelectCharacter message) diff --git a/Content.Server/Silicons/Borgs/BorgSystem.cs b/Content.Server/Silicons/Borgs/BorgSystem.cs index ceab044d4c..ec7a25368f 100644 --- a/Content.Server/Silicons/Borgs/BorgSystem.cs +++ b/Content.Server/Silicons/Borgs/BorgSystem.cs @@ -5,7 +5,6 @@ using Content.Server.DeviceNetwork.Systems; using Content.Server.Explosion.EntitySystems; using Content.Server.Hands.Systems; using Content.Server.PowerCell; -using Content.Shared.UserInterface; using Content.Shared.Access.Systems; using Content.Shared.Alert; using Content.Shared.Database; @@ -70,7 +69,6 @@ public sealed partial class BorgSystem : SharedBorgSystem SubscribeLocalEvent(OnMobStateChanged); SubscribeLocalEvent(OnPowerCellChanged); SubscribeLocalEvent(OnPowerCellSlotEmpty); - SubscribeLocalEvent(OnUIOpenAttempt); SubscribeLocalEvent(OnGetDeadIC); SubscribeLocalEvent(OnBrainMindAdded); @@ -214,13 +212,6 @@ public sealed partial class BorgSystem : SharedBorgSystem UpdateUI(uid, component); } - private void OnUIOpenAttempt(EntityUid uid, BorgChassisComponent component, ActivatableUIOpenAttemptEvent args) - { - // borgs can't view their own ui - if (args.User == uid) - args.Cancel(); - } - private void OnGetDeadIC(EntityUid uid, BorgChassisComponent component, ref GetCharactedDeadIcEvent args) { args.Dead = true; diff --git a/Content.Server/StationEvents/Events/ImmovableRodRule.cs b/Content.Server/StationEvents/Events/ImmovableRodRule.cs index cacb839cd3..aa193f2f4c 100644 --- a/Content.Server/StationEvents/Events/ImmovableRodRule.cs +++ b/Content.Server/StationEvents/Events/ImmovableRodRule.cs @@ -28,7 +28,9 @@ public sealed class ImmovableRodRule : StationEventSystem(out var rod) && proto.TryGetComponent(out var despawn)) { - TryFindRandomTile(out _, out _, out _, out var targetCoords); + if (!TryFindRandomTile(out _, out _, out _, out var targetCoords)) + return; + var speed = RobustRandom.NextFloat(rod.MinSpeed, rod.MaxSpeed); var angle = RobustRandom.NextAngle(); var direction = angle.ToVec(); diff --git a/Content.Server/Strip/StrippableSystem.cs b/Content.Server/Strip/StrippableSystem.cs index 0ae569b392..e63401fe9f 100644 --- a/Content.Server/Strip/StrippableSystem.cs +++ b/Content.Server/Strip/StrippableSystem.cs @@ -1,7 +1,6 @@ using System.Linq; using Content.Server.Administration.Logs; using Content.Server.Ensnaring; -using Content.Shared.CombatMode; using Content.Shared.Cuffs; using Content.Shared.Cuffs.Components; using Content.Shared.Database; @@ -10,7 +9,6 @@ using Content.Shared.Ensnaring.Components; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; using Content.Shared.IdentityManagement; -using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Inventory; using Content.Shared.Inventory.VirtualItem; @@ -29,7 +27,6 @@ namespace Content.Server.Strip { [Dependency] private readonly InventorySystem _inventorySystem = default!; [Dependency] private readonly EnsnareableSystem _ensnaringSystem = default!; - [Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!; [Dependency] private readonly SharedCuffableSystem _cuffableSystem = default!; [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; @@ -46,7 +43,6 @@ namespace Content.Server.Strip SubscribeLocalEvent>(AddStripVerb); SubscribeLocalEvent>(AddStripExamineVerb); - SubscribeLocalEvent(OnActivateInWorld); // BUI SubscribeLocalEvent(OnStripButtonPressed); @@ -69,7 +65,7 @@ namespace Content.Server.Strip { Text = Loc.GetString("strip-verb-get-data-text"), Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/outfit.svg.192dpi.png")), - Act = () => StartOpeningStripper(args.User, (uid, component), true), + Act = () => TryOpenStrippingUi(args.User, (uid, component), true), }; args.Verbs.Add(verb); @@ -87,37 +83,13 @@ namespace Content.Server.Strip { Text = Loc.GetString("strip-verb-get-data-text"), Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/outfit.svg.192dpi.png")), - Act = () => StartOpeningStripper(args.User, (uid, component), true), + Act = () => TryOpenStrippingUi(args.User, (uid, component), true), Category = VerbCategory.Examine, }; args.Verbs.Add(verb); } - private void OnActivateInWorld(EntityUid uid, StrippableComponent component, ActivateInWorldEvent args) - { - if (args.Target == args.User) - return; - - if (!HasComp(args.User)) - return; - - StartOpeningStripper(args.User, (uid, component)); - } - - public override void StartOpeningStripper(EntityUid user, Entity strippable, bool openInCombat = false) - { - base.StartOpeningStripper(user, strippable, openInCombat); - - if (TryComp(user, out var mode) && mode.IsInCombatMode && !openInCombat) - return; - - if (HasComp(user)) - { - _userInterfaceSystem.OpenUi(strippable.Owner, StrippingUiKey.Key, user); - } - } - private void OnStripButtonPressed(Entity strippable, ref StrippingSlotButtonPressed args) { if (args.Actor is not { Valid: true } user || diff --git a/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs b/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs index 2612e99ec9..190a2d0263 100644 --- a/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs +++ b/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs @@ -187,15 +187,10 @@ public sealed class MeleeWeaponSystem : SharedMeleeWeaponSystem if (session is { } pSession) { (targetCoordinates, targetLocalAngle) = _lag.GetCoordinatesAngle(target, pSession); - } - else - { - var xform = Transform(target); - targetCoordinates = xform.Coordinates; - targetLocalAngle = xform.LocalRotation; + return Interaction.InRangeUnobstructed(user, target, targetCoordinates, targetLocalAngle, range); } - return Interaction.InRangeUnobstructed(user, target, targetCoordinates, targetLocalAngle, range); + return Interaction.InRangeUnobstructed(user, target, range); } protected override void DoDamageEffect(List targets, EntityUid? user, TransformComponent targetXform) diff --git a/Content.Server/Wires/WiresSystem.cs b/Content.Server/Wires/WiresSystem.cs index c643759f50..944b0a0e25 100644 --- a/Content.Server/Wires/WiresSystem.cs +++ b/Content.Server/Wires/WiresSystem.cs @@ -4,27 +4,23 @@ using System.Threading; using Content.Server.Construction; using Content.Server.Construction.Components; using Content.Server.Power.Components; -using Content.Server.UserInterface; using Content.Shared.DoAfter; using Content.Shared.GameTicking; using Content.Shared.Hands.Components; using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Tools.Components; -using Content.Shared.UserInterface; using Content.Shared.Wires; using Robust.Server.GameObjects; using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Random; -using ActivatableUISystem = Content.Shared.UserInterface.ActivatableUISystem; namespace Content.Server.Wires; public sealed class WiresSystem : SharedWiresSystem { [Dependency] private readonly IPrototypeManager _protoMan = default!; - [Dependency] private readonly ActivatableUISystem _activatableUI = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; @@ -52,8 +48,6 @@ public sealed class WiresSystem : SharedWiresSystem SubscribeLocalEvent(OnTimedWire); SubscribeLocalEvent(OnWiresPowered); SubscribeLocalEvent(OnDoAfter); - SubscribeLocalEvent(OnAttemptOpenActivatableUI); - SubscribeLocalEvent(OnActivatableUIPanelChanged); SubscribeLocalEvent(SetWiresPanelSecurity); } @@ -473,23 +467,6 @@ public sealed class WiresSystem : SharedWiresSystem _uiSystem.CloseUi(ent.Owner, WiresUiKey.Key); } - private void OnAttemptOpenActivatableUI(EntityUid uid, ActivatableUIRequiresPanelComponent component, ActivatableUIOpenAttemptEvent args) - { - if (args.Cancelled || !TryComp(uid, out var wires)) - return; - - if (component.RequireOpen != wires.Open) - args.Cancel(); - } - - private void OnActivatableUIPanelChanged(EntityUid uid, ActivatableUIRequiresPanelComponent component, ref PanelChangedEvent args) - { - if (args.Open == component.RequireOpen) - return; - - _activatableUI.CloseAll(uid); - } - private void OnMapInit(EntityUid uid, WiresComponent component, MapInitEvent args) { if (!string.IsNullOrEmpty(component.LayoutId)) diff --git a/Content.Server/_White/Wizard/WizardRuleSystem.cs b/Content.Server/_White/Wizard/WizardRuleSystem.cs index 7158f6e298..fb6684e9d2 100644 --- a/Content.Server/_White/Wizard/WizardRuleSystem.cs +++ b/Content.Server/_White/Wizard/WizardRuleSystem.cs @@ -39,7 +39,7 @@ public sealed class WizardRuleSystem : GameRuleSystem private void OnObjectivesTextGetInfo(Entity ent, ref ObjectivesTextGetInfoEvent args) { - args.Minds = ent.Comp.WizardMinds; + args.Minds = ent.Comp.WizardMinds.Select(mindId => (mindId, Comp(mindId).CharacterName ?? "?")).ToList(); args.AgentName = Loc.GetString("wizard-round-end-agent-name"); } diff --git a/Content.Shared/Actions/SharedActionsSystem.cs b/Content.Shared/Actions/SharedActionsSystem.cs index 39127ecfb9..baa149ba11 100644 --- a/Content.Shared/Actions/SharedActionsSystem.cs +++ b/Content.Shared/Actions/SharedActionsSystem.cs @@ -508,13 +508,7 @@ public abstract class SharedActionsSystem : EntitySystem return distance <= action.Range; } - if (_interactionSystem.InRangeUnobstructed(user, target, range: action.Range) - && _containerSystem.IsInSameOrParentContainer(user, target)) - { - return true; - } - - return _interactionSystem.CanAccessViaStorage(user, target); + return _interactionSystem.InRangeAndAccessible(user, target, range: action.Range); } public bool ValidateWorldTarget(EntityUid user, EntityCoordinates coords, Entity action) diff --git a/Content.Shared/Atmos/Components/GasTileOverlayComponent.cs b/Content.Shared/Atmos/Components/GasTileOverlayComponent.cs index e72a1d6758..2c3149b11a 100644 --- a/Content.Shared/Atmos/Components/GasTileOverlayComponent.cs +++ b/Content.Shared/Atmos/Components/GasTileOverlayComponent.cs @@ -1,7 +1,6 @@ using Robust.Shared.GameStates; using Robust.Shared.Serialization; using Robust.Shared.Timing; -using Robust.Shared.Utility; namespace Content.Shared.Atmos.Components; @@ -24,55 +23,47 @@ public sealed partial class GasTileOverlayComponent : Component public GameTick ForceTick { get; set; } } +[Serializable, NetSerializable] +public sealed class GasTileOverlayState(Dictionary chunks) : ComponentState +{ + public readonly Dictionary Chunks = chunks; +} [Serializable, NetSerializable] -public sealed class GasTileOverlayState : ComponentState, IComponentDeltaState +public sealed class GasTileOverlayDeltaState( + Dictionary modifiedChunks, + HashSet allChunks) + : ComponentState, IComponentDeltaState { - public readonly Dictionary Chunks; - public bool FullState => AllChunks == null; + public readonly Dictionary ModifiedChunks = modifiedChunks; + public readonly HashSet AllChunks = allChunks; - // required to infer deleted/missing chunks for delta states - public HashSet? AllChunks; - - public GasTileOverlayState(Dictionary chunks) + public void ApplyToFullState(GasTileOverlayState state) { - Chunks = chunks; - } - - public void ApplyToFullState(IComponentState fullState) - { - DebugTools.Assert(!FullState); - var state = (GasTileOverlayState) fullState; - DebugTools.Assert(state.FullState); - foreach (var key in state.Chunks.Keys) { - if (!AllChunks!.Contains(key)) + if (!AllChunks.Contains(key)) state.Chunks.Remove(key); } - foreach (var (chunk, data) in Chunks) + foreach (var (chunk, data) in ModifiedChunks) { state.Chunks[chunk] = new(data); } } - public IComponentState CreateNewFullState(IComponentState fullState) + public GasTileOverlayState CreateNewFullState(GasTileOverlayState state) { - DebugTools.Assert(!FullState); - var state = (GasTileOverlayState) fullState; - DebugTools.Assert(state.FullState); + var chunks = new Dictionary(AllChunks.Count); - var chunks = new Dictionary(state.Chunks.Count); - - foreach (var (chunk, data) in Chunks) + foreach (var (chunk, data) in ModifiedChunks) { chunks[chunk] = new(data); } foreach (var (chunk, data) in state.Chunks) { - if (AllChunks!.Contains(chunk)) + if (AllChunks.Contains(chunk)) chunks.TryAdd(chunk, new(data)); } diff --git a/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs b/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs index f468724db3..8e7dfdedaf 100644 --- a/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs +++ b/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs @@ -55,7 +55,7 @@ namespace Content.Shared.Atmos.EntitySystems data[index] = chunk; } - args.State = new GasTileOverlayState(data) { AllChunks = new(component.Chunks.Keys) }; + args.State = new GasTileOverlayDeltaState(data, new(component.Chunks.Keys)); } public static Vector2i GetGasChunkIndices(Vector2i indices) diff --git a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs index 3287ced1bc..731f9984d7 100644 --- a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs +++ b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs @@ -58,7 +58,7 @@ public abstract partial class SharedBuckleSystem return; var strapPosition = Transform(strapUid).Coordinates; - if (ev.NewPosition.InRange(EntityManager, _transform, strapPosition, strapComp.MaxBuckleDistance)) + if (ev.NewPosition.EntityId.IsValid() && ev.NewPosition.InRange(EntityManager, _transform, strapPosition, strapComp.MaxBuckleDistance)) return; TryUnbuckle(uid, uid, true, component); diff --git a/Content.Shared/Damage/Components/DamageableComponent.cs b/Content.Shared/Damage/Components/DamageableComponent.cs index be66d51e3b..f8205568f1 100644 --- a/Content.Shared/Damage/Components/DamageableComponent.cs +++ b/Content.Shared/Damage/Components/DamageableComponent.cs @@ -5,8 +5,6 @@ using Content.Shared.StatusIcon; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; namespace Content.Shared.Damage { @@ -18,7 +16,7 @@ namespace Content.Shared.Damage /// may also have resistances to certain damage types, defined via a . /// [RegisterComponent] - [NetworkedComponent()] + [NetworkedComponent] [Access(typeof(DamageableSystem), Other = AccessPermissions.ReadExecute)] public sealed partial class DamageableComponent : Component { @@ -26,8 +24,8 @@ namespace Content.Shared.Damage /// This specifies what damage types are supported by this component. /// If null, all damage types will be supported. /// - [DataField("damageContainer", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? DamageContainerID; + [DataField("damageContainer")] + public ProtoId? DamageContainerID; /// /// This will be applied to any damage that is dealt to this container, @@ -37,8 +35,8 @@ namespace Content.Shared.Damage /// Though DamageModifierSets can be deserialized directly, we only want to use the prototype version here /// to reduce duplication. /// - [DataField("damageModifierSet", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? DamageModifierSetId; + [DataField("damageModifierSet")] + public ProtoId? DamageModifierSetId; /// /// All the damage information is stored in this . @@ -46,7 +44,7 @@ namespace Content.Shared.Damage /// /// If this data-field is specified, this allows damageable components to be initialized with non-zero damage. /// - [DataField("damage", readOnly: true)] //todo remove this readonly when implementing writing to damagespecifier + [DataField(readOnly: true)] //todo remove this readonly when implementing writing to damagespecifier public DamageSpecifier Damage = new(); /// @@ -64,8 +62,8 @@ namespace Content.Shared.Damage [ViewVariables] public FixedPoint2 TotalDamage; - [DataField("radiationDamageTypes", customTypeSerializer: typeof(PrototypeIdListSerializer))] - public List RadiationDamageTypeIDs = new() { "Radiation" }; + [DataField("radiationDamageTypes")] + public List> RadiationDamageTypeIDs = new() { "Radiation" }; [DataField] public Dictionary> HealthIcons = new() @@ -77,6 +75,9 @@ namespace Content.Shared.Damage [DataField] public ProtoId RottingIcon = "HealthIconRotting"; + + [DataField] + public FixedPoint2? HealthBarThreshold; } [Serializable, NetSerializable] @@ -84,13 +85,16 @@ namespace Content.Shared.Damage { public readonly Dictionary DamageDict; public readonly string? ModifierSetId; + public readonly FixedPoint2? HealthBarThreshold; public DamageableComponentState( Dictionary damageDict, - string? modifierSetId) + string? modifierSetId, + FixedPoint2? healthBarThreshold) { DamageDict = damageDict; ModifierSetId = modifierSetId; + HealthBarThreshold = healthBarThreshold; } } } diff --git a/Content.Shared/Damage/Systems/DamageableSystem.cs b/Content.Shared/Damage/Systems/DamageableSystem.cs index 6b84bfc012..dd7a81cdaa 100644 --- a/Content.Shared/Damage/Systems/DamageableSystem.cs +++ b/Content.Shared/Damage/Systems/DamageableSystem.cs @@ -1,5 +1,4 @@ using System.Linq; -using Content.Shared.Administration.Logs; using Content.Shared.Damage.Prototypes; using Content.Shared.FixedPoint; using Content.Shared.Inventory; @@ -240,12 +239,12 @@ namespace Content.Shared.Damage { if (_netMan.IsServer) { - args.State = new DamageableComponentState(component.Damage.DamageDict, component.DamageModifierSetId); + args.State = new DamageableComponentState(component.Damage.DamageDict, component.DamageModifierSetId, component.HealthBarThreshold); } else { // avoid mispredicting damage on newly spawned entities. - args.State = new DamageableComponentState(component.Damage.DamageDict.ShallowClone(), component.DamageModifierSetId); + args.State = new DamageableComponentState(component.Damage.DamageDict.ShallowClone(), component.DamageModifierSetId, component.HealthBarThreshold); } } @@ -279,6 +278,7 @@ namespace Content.Shared.Damage } component.DamageModifierSetId = state.ModifierSetId; + component.HealthBarThreshold = state.HealthBarThreshold; // Has the damage actually changed? DamageSpecifier newDamage = new() { DamageDict = new(state.DamageDict) }; diff --git a/Content.Shared/Decals/DecalGridComponent.cs b/Content.Shared/Decals/DecalGridComponent.cs index 8ac05cb280..67a9c03769 100644 --- a/Content.Shared/Decals/DecalGridComponent.cs +++ b/Content.Shared/Decals/DecalGridComponent.cs @@ -62,46 +62,37 @@ namespace Content.Shared.Decals } [Serializable, NetSerializable] - public sealed class DecalGridState : ComponentState, IComponentDeltaState + public sealed class DecalGridState(Dictionary chunks) : ComponentState { - public Dictionary Chunks; - public bool FullState => AllChunks == null; + public Dictionary Chunks = chunks; + } - // required to infer deleted/missing chunks for delta states - public HashSet? AllChunks; + [Serializable, NetSerializable] + public sealed class DecalGridDeltaState(Dictionary modifiedChunks, HashSet allChunks) + : ComponentState, IComponentDeltaState + { + public Dictionary ModifiedChunks = modifiedChunks; + public HashSet AllChunks = allChunks; - public DecalGridState(Dictionary chunks) + public void ApplyToFullState(DecalGridState state) { - Chunks = chunks; - } - - public void ApplyToFullState(IComponentState fullState) - { - DebugTools.Assert(!FullState); - var state = (DecalGridState) fullState; - DebugTools.Assert(state.FullState); - foreach (var key in state.Chunks.Keys) { if (!AllChunks!.Contains(key)) state.Chunks.Remove(key); } - foreach (var (chunk, data) in Chunks) + foreach (var (chunk, data) in ModifiedChunks) { state.Chunks[chunk] = new(data); } } - public IComponentState CreateNewFullState(IComponentState fullState) + public DecalGridState CreateNewFullState(DecalGridState state) { - DebugTools.Assert(!FullState); - var state = (DecalGridState) fullState; - DebugTools.Assert(state.FullState); - var chunks = new Dictionary(state.Chunks.Count); - foreach (var (chunk, data) in Chunks) + foreach (var (chunk, data) in ModifiedChunks) { chunks[chunk] = new(data); } diff --git a/Content.Shared/Decals/SharedDecalSystem.cs b/Content.Shared/Decals/SharedDecalSystem.cs index 0665ccbf84..0a2349ea29 100644 --- a/Content.Shared/Decals/SharedDecalSystem.cs +++ b/Content.Shared/Decals/SharedDecalSystem.cs @@ -49,7 +49,7 @@ namespace Content.Shared.Decals data[index] = chunk; } - args.State = new DecalGridState(data) { AllChunks = new(component.ChunkCollection.ChunkCollection.Keys) }; + args.State = new DecalGridDeltaState(data, new(component.ChunkCollection.ChunkCollection.Keys)); } private void OnGridInitialize(GridInitializeEvent msg) diff --git a/Content.Shared/Interaction/SharedInteractionSystem.cs b/Content.Shared/Interaction/SharedInteractionSystem.cs index 94e5cefc31..e8529d26f5 100644 --- a/Content.Shared/Interaction/SharedInteractionSystem.cs +++ b/Content.Shared/Interaction/SharedInteractionSystem.cs @@ -1,11 +1,10 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Shared.ActionBlocker; -using Content.Shared.Administration; using Content.Shared.Administration.Logs; -using Content.Shared.Administration.Managers; using Content.Shared.CombatMode; using Content.Shared.Database; +using Content.Shared.Ghost; using Content.Shared.Hands; using Content.Shared.Hands.Components; using Content.Shared.Input; @@ -15,16 +14,20 @@ using Content.Shared.Inventory; using Content.Shared.Inventory.Events; using Content.Shared.Item; using Content.Shared.Movement.Components; -using Content.Shared.Movement.Pulling.Components; using Content.Shared.Movement.Pulling.Systems; using Content.Shared.Physics; using Content.Shared.Popups; +using Content.Shared.Storage; using Content.Shared.Tag; using Content.Shared.Timing; +using Content.Shared.UserInterface; using Content.Shared.Verbs; using Content.Shared.Wall; using Content.Shared.Weapons.Ranged.Components; using Content.Shared._White.MeatyOre; +using Content.Shared.Ghost; +using Content.Shared.Storage; +using Content.Shared.UserInterface; using JetBrains.Annotations; using Robust.Shared.Containers; using Robust.Shared.Input; @@ -38,6 +41,8 @@ using Robust.Shared.Player; using Robust.Shared.Random; using Robust.Shared.Serialization; using Robust.Shared.Timing; +using Robust.Shared.Utility; +using Robust.Shared.Utility; #pragma warning disable 618 @@ -52,12 +57,11 @@ namespace Content.Shared.Interaction [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly INetManager _net = default!; [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly ISharedAdminManager _adminManager = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; [Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default!; [Dependency] private readonly SharedContainerSystem _containerSystem = default!; - [Dependency] private readonly SharedPhysicsSystem _sharedBroadphaseSystem = default!; + [Dependency] private readonly SharedPhysicsSystem _broadphase = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedVerbSystem _verbSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; @@ -66,6 +70,18 @@ namespace Content.Shared.Interaction [Dependency] private readonly InventorySystem _inventory = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly TagSystem _tagSystem = default!; + [Dependency] private readonly SharedUserInterfaceSystem _ui = default!; + + private EntityQuery _ignoreUiRangeQuery; + private EntityQuery _fixtureQuery; + private EntityQuery _itemQuery; + private EntityQuery _physicsQuery; + private EntityQuery _handsQuery; + private EntityQuery _relayQuery; + private EntityQuery _combatQuery; + private EntityQuery _wallMountQuery; + private EntityQuery _delayQuery; + private EntityQuery _uiQuery; private const CollisionGroup InRangeUnobstructedMask = CollisionGroup.Impassable | CollisionGroup.InteractImpassable; @@ -78,6 +94,17 @@ namespace Content.Shared.Interaction public override void Initialize() { + _ignoreUiRangeQuery = GetEntityQuery(); + _fixtureQuery = GetEntityQuery(); + _itemQuery = GetEntityQuery(); + _physicsQuery = GetEntityQuery(); + _handsQuery = GetEntityQuery(); + _relayQuery = GetEntityQuery(); + _combatQuery = GetEntityQuery(); + _wallMountQuery = GetEntityQuery(); + _delayQuery = GetEntityQuery(); + _uiQuery = GetEntityQuery(); + SubscribeLocalEvent(HandleUserInterfaceRangeCheck); SubscribeLocalEvent(OnBoundInterfaceInteractAttempt); @@ -113,34 +140,57 @@ namespace Content.Shared.Interaction /// private void OnBoundInterfaceInteractAttempt(BoundUserInterfaceMessageAttempt ev) { - var user = ev.Actor; + _uiQuery.TryComp(ev.Target, out var uiComp); + if (!_actionBlockerSystem.CanInteract(ev.Actor, ev.Target)) + { + // We permit ghosts to open uis unless explicitly blocked + if (ev.Message is not OpenBoundInterfaceMessage || !HasComp(ev.Actor) || uiComp?.BlockSpectators == true) + { + ev.Cancel(); + return; + } + } - if (!_actionBlockerSystem.CanInteract(user, ev.Target)) + var range = _ui.GetUiRange(ev.Target, ev.UiKey); + + // As long as range>0, the UI frame updates should have auto-closed the UI if it is out of range. + DebugTools.Assert(range <= 0 || UiRangeCheck(ev.Actor, ev.Target, range)); + + if (range <= 0 && !IsAccessible(ev.Actor, ev.Target)) { ev.Cancel(); return; } - // Check if the bound entity is accessible. Note that we allow admins to ignore this restriction, so that - // they can fiddle with UI's that people can't normally interact with (e.g., placing things directly into - // other people's backpacks). - if (!_containerSystem.IsInSameOrParentContainer(user, ev.Target) - && !CanAccessViaStorage(user, ev.Target) - && !_adminManager.HasAdminFlag(user, AdminFlags.Admin)) + if (uiComp == null) + return; + + if (uiComp.SingleUser && uiComp.CurrentSingleUser != ev.Actor) { ev.Cancel(); return; } - if (CompOrNull(ev.Target) != null) - { + if (!uiComp.RequireHands) return; - } - if (!InRangeUnobstructed(user, ev.Target)) - { + if (!_handsQuery.TryComp(ev.Actor, out var hands) || hands.Hands.Count == 0) ev.Cancel(); - } + } + + private bool UiRangeCheck(Entity user, Entity target, float range) + { + if (!Resolve(target, ref target.Comp)) + return false; + + if (user.Owner == target.Owner) + return true; + + // Fast check: if the user is the parent of the entity (e.g., holding it), we always assume that it is in range + if (target.Comp.ParentUid == user.Owner) + return true; + + return InRangeAndAccessible(user, target, range) || _ignoreUiRangeQuery.HasComp(user); } /// @@ -197,10 +247,7 @@ namespace Content.Shared.Interaction if (!InRangeUnobstructed(userEntity.Value, uid, popup: true)) return false; - if (!TryComp(uid, out PullableComponent? pull)) - return false; - - _pullSystem.TogglePull(uid, userEntity.Value, pull); + _pullSystem.TogglePull(uid, userEntity.Value); return false; } @@ -276,7 +323,7 @@ namespace Content.Shared.Interaction public bool CombatModeCanHandInteract(EntityUid user, EntityUid? target) { // Always allow attack in these cases - if (target == null || !TryComp(user, out var hands) || hands.ActiveHand?.HeldEntity is not null) + if (target == null || !_handsQuery.TryComp(user, out var hands) || hands.ActiveHand?.HeldEntity is not null) return false; // Only eat input if: @@ -284,7 +331,7 @@ namespace Content.Shared.Interaction // - Target doesn't cancel should-interact event // This is intended to allow items to be picked up in combat mode, // but to also allow items to force attacks anyway (like mobs which are items, e.g. mice) - if (!HasComp(target)) + if (!_itemQuery.HasComp(target)) return false; var combatEv = new CombatModeShouldHandInteractEvent(); @@ -314,7 +361,7 @@ namespace Content.Shared.Interaction bool checkAccess = true, bool checkCanUse = true) { - if (TryComp(user, out var relay) && relay.RelayEntity is not null) + if (_relayQuery.TryComp(user, out var relay) && relay.RelayEntity is not null) { // TODO this needs to be handled better. This probably bypasses many complex can-interact checks in weird roundabout ways. if (_actionBlockerSystem.CanInteract(user, target)) @@ -328,7 +375,7 @@ namespace Content.Shared.Interaction if (target != null && Deleted(target.Value)) return; - if (!altInteract && TryComp(user, out var combatMode) && combatMode.IsInCombatMode) + if (!altInteract && _combatQuery.TryComp(user, out var combatMode) && combatMode.IsInCombatMode) { if (!CombatModeCanHandInteract(user, target)) return; @@ -350,10 +397,7 @@ namespace Content.Shared.Interaction // Check if interacted entity is in the same container, the direct child, or direct parent of the user. // Also checks if the item is accessible via some storage UI (e.g., open backpack) - if (checkAccess - && target != null - && !_containerSystem.IsInSameOrParentContainer(user, target.Value) - && !CanAccessViaStorage(user, target.Value)) + if (checkAccess && target != null && !IsAccessible(user, target.Value)) return; var inRangeUnobstructed = target == null @@ -361,7 +405,7 @@ namespace Content.Shared.Interaction : !checkAccess || InRangeUnobstructed(user, target.Value); // permits interactions with wall mounted entities // Does the user have hands? - if (!TryComp(user, out var hands) || hands.ActiveHand == null) + if (!_handsQuery.TryComp(user, out var hands) || hands.ActiveHand == null) { var ev = new InteractNoHandEvent(user, target, coordinates); RaiseLocalEvent(user, ev); @@ -501,7 +545,7 @@ namespace Content.Shared.Interaction predicate ??= _ => false; var ray = new CollisionRay(origin.Position, dir.Normalized(), collisionMask); - var rayResults = _sharedBroadphaseSystem.IntersectRayWithPredicate(origin.MapId, ray, dir.Length(), predicate.Invoke, false).ToList(); + var rayResults = _broadphase.IntersectRayWithPredicate(origin.MapId, ray, dir.Length(), predicate.Invoke, false).ToList(); if (rayResults.Count == 0) return dir.Length(); @@ -564,23 +608,29 @@ namespace Content.Shared.Interaction } var ray = new CollisionRay(origin.Position, dir.Normalized(), (int) collisionMask); - var rayResults = _sharedBroadphaseSystem.IntersectRayWithPredicate(origin.MapId, ray, length, predicate.Invoke, false).ToList(); + var rayResults = _broadphase.IntersectRayWithPredicate(origin.MapId, ray, length, predicate.Invoke, false).ToList(); return rayResults.Count == 0; } public bool InRangeUnobstructed( - EntityUid origin, - EntityUid other, + Entity origin, + Entity other, float range = InteractionRange, CollisionGroup collisionMask = InRangeUnobstructedMask, Ignored? predicate = null, bool popup = false) { - if (!TryComp(other, out TransformComponent? otherXform)) + if (!Resolve(other, ref other.Comp)) return false; - return InRangeUnobstructed(origin, other, otherXform.Coordinates, otherXform.LocalRotation, range, collisionMask, predicate, + return InRangeUnobstructed(origin, + other, + other.Comp.Coordinates, + other.Comp.LocalRotation, + range, + collisionMask, + predicate, popup); } @@ -612,8 +662,8 @@ namespace Content.Shared.Interaction /// True if the two points are within a given range without being obstructed. /// public bool InRangeUnobstructed( - EntityUid origin, - EntityUid other, + Entity origin, + Entity other, EntityCoordinates otherCoordinates, Angle otherAngle, float range = InteractionRange, @@ -621,10 +671,10 @@ namespace Content.Shared.Interaction Ignored? predicate = null, bool popup = false) { - Ignored combinedPredicate = e => e == origin || (predicate?.Invoke(e) ?? false); + Ignored combinedPredicate = e => e == origin.Owner || (predicate?.Invoke(e) ?? false); var inRange = true; MapCoordinates originPos = default; - var targetPos = otherCoordinates.ToMap(EntityManager, _transform); + var targetPos = _transform.ToMapCoordinates(otherCoordinates); Angle targetRot = default; // So essentially: @@ -634,23 +684,30 @@ namespace Content.Shared.Interaction // Alternatively we could check centre distances first though // that means we wouldn't be able to easily check overlap interactions. if (range > 0f && - TryComp(origin, out var fixtureA) && + _fixtureQuery.TryComp(origin, out var fixtureA) && // These fixture counts are stuff that has the component but no fixtures for (e.g. buttons). // At least until they get removed. fixtureA.FixtureCount > 0 && - TryComp(other, out var fixtureB) && + _fixtureQuery.TryComp(other, out var fixtureB) && fixtureB.FixtureCount > 0 && - TryComp(origin, out TransformComponent? xformA)) + Resolve(origin, ref origin.Comp)) { - var (worldPosA, worldRotA) = xformA.GetWorldPositionRotation(); + var (worldPosA, worldRotA) = origin.Comp.GetWorldPositionRotation(); var xfA = new Transform(worldPosA, worldRotA); var parentRotB = _transform.GetWorldRotation(otherCoordinates.EntityId); var xfB = new Transform(targetPos.Position, parentRotB + otherAngle); // Different map or the likes. - if (!_sharedBroadphaseSystem.TryGetNearest(origin, other, - out _, out _, out var distance, - xfA, xfB, fixtureA, fixtureB)) + if (!_broadphase.TryGetNearest( + origin, + other, + out _, + out _, + out var distance, + xfA, + xfB, + fixtureA, + fixtureB)) { inRange = false; } @@ -672,15 +729,15 @@ namespace Content.Shared.Interaction else { // We'll still do the raycast from the centres but we'll bump the range as we know they're in range. - originPos = _transform.GetMapCoordinates(origin, xform: xformA); + originPos = _transform.GetMapCoordinates(origin, xform: origin.Comp); range = (originPos.Position - targetPos.Position).Length(); } } // No fixtures, e.g. wallmounts. else { - originPos = _transform.GetMapCoordinates(origin); - var otherParent = Transform(other).ParentUid; + originPos = _transform.GetMapCoordinates(origin, origin); + var otherParent = (other.Comp ?? Transform(other)).ParentUid; targetRot = otherParent.IsValid() ? Transform(otherParent).LocalRotation + otherAngle : otherAngle; } @@ -731,13 +788,13 @@ namespace Content.Shared.Interaction { HashSet ignored = new(); - if (HasComp(target) && TryComp(target, out PhysicsComponent? physics) && physics.CanCollide) + if (_itemQuery.HasComp(target) && _physicsQuery.TryComp(target, out var physics) && physics.CanCollide) { // If the target is an item, we ignore any colliding entities. Currently done so that if items get stuck // inside of walls, users can still pick them up. - ignored.UnionWith(_sharedBroadphaseSystem.GetEntitiesIntersectingBody(target, (int) collisionMask, false, physics)); + ignored.UnionWith(_broadphase.GetEntitiesIntersectingBody(target, (int) collisionMask, false, physics)); } - else if (TryComp(target, out WallMountComponent? wallMount)) + else if (_wallMountQuery.TryComp(target, out var wallMount)) { // wall-mount exemptions may be restricted to a specific angle range.da @@ -755,13 +812,7 @@ namespace Content.Shared.Interaction ignored.UnionWith(grid.GetAnchoredEntities(targetCoords)); } - Ignored combinedPredicate = e => - { - return e == target - || (predicate?.Invoke(e) ?? false) - || ignored.Contains(e); - }; - + Ignored combinedPredicate = e => e == target || (predicate?.Invoke(e) ?? false) || ignored.Contains(e); return combinedPredicate; } @@ -958,10 +1009,8 @@ namespace Content.Shared.Interaction bool checkUseDelay = true, bool checkAccess = true) { - UseDelayComponent? delayComponent = null; - if (checkUseDelay - && TryComp(used, out delayComponent) - && _useDelay.IsDelayed((used, delayComponent))) + _delayQuery.TryComp(used, out var delayComponent); + if (checkUseDelay && delayComponent != null && _useDelay.IsDelayed((used, delayComponent))) return false; if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, used)) @@ -972,11 +1021,11 @@ namespace Content.Shared.Interaction // Check if interacted entity is in the same container, the direct child, or direct parent of the user. // This is bypassed IF the interaction happened through an item slot (e.g., backpack UI) - if (checkAccess && !_containerSystem.IsInSameOrParentContainer(user, used) && !CanAccessViaStorage(user, used)) + if (checkAccess && !IsAccessible(user, used)) return false; // Does the user have hands? - if (!HasComp(user)) + if (!_handsQuery.HasComp(user)) return false; var activateMsg = new ActivateInWorldEvent(user, used); @@ -986,7 +1035,9 @@ namespace Content.Shared.Interaction DoContactInteraction(user, used, activateMsg); // Still need to call this even without checkUseDelay in case this gets relayed from Activate. - _useDelay.TryResetDelay(used, component: delayComponent); + if (delayComponent != null) + _useDelay.TryResetDelay(used, component: delayComponent); + if (!activateMsg.WasLogged) _adminLogger.Add(LogType.InteractActivate, LogImpact.Low, $"{ToPrettyString(user):user} activated {ToPrettyString(used):used}"); return true; @@ -1007,11 +1058,8 @@ namespace Content.Shared.Interaction bool checkCanInteract = true, bool checkUseDelay = true) { - UseDelayComponent? delayComponent = null; - - if (checkUseDelay - && TryComp(used, out delayComponent) - && _useDelay.IsDelayed((used, delayComponent))) + _delayQuery.TryComp(used, out var delayComponent); + if (checkUseDelay && delayComponent != null && _useDelay.IsDelayed((used, delayComponent))) return true; // if the item is on cooldown, we consider this handled. if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, used)) @@ -1073,11 +1121,60 @@ namespace Content.Shared.Interaction } #endregion + /// + /// Check if a user can access a target (stored in the same containers) and is in range without obstructions. + /// + public bool InRangeAndAccessible( + Entity user, + Entity target, + float range = InteractionRange, + CollisionGroup collisionMask = InRangeUnobstructedMask, + Ignored? predicate = null) + { + if (user == target) + return true; + + if (!Resolve(user, ref user.Comp)) + return false; + + if (!Resolve(target, ref target.Comp)) + return false; + + return IsAccessible(user, target) && InRangeUnobstructed(user, target, range, collisionMask, predicate); + } + + /// + /// Check if a user can access a target or if they are stored in different containers. + /// + public bool IsAccessible(Entity user, Entity target) + { + if (_containerSystem.IsInSameOrParentContainer(user, target, out _, out var container)) + return true; + + return container != null && CanAccessViaStorage(user, target, container); + } + /// /// If a target is in range, but not in the same container as the user, it may be inside of a backpack. This /// checks if the user can access the item in these situations. /// - public abstract bool CanAccessViaStorage(EntityUid user, EntityUid target); + public bool CanAccessViaStorage(EntityUid user, EntityUid target) + { + if (!_containerSystem.TryGetContainingContainer(target, out var container)) + return false; + + return CanAccessViaStorage(user, target, container); + } + + /// + public bool CanAccessViaStorage(EntityUid user, EntityUid target, BaseContainer container) + { + if (StorageComponent.ContainerId != container.ID) + return false; + + // we don't check if the user can access the storage entity itself. This should be handed by the UI system. + return _ui.IsUiOpen(container.Owner, StorageComponent.StorageUiKey.Key, user); + } /// /// Checks whether an entity currently equipped by another player is accessible to some user. This shouldn't @@ -1158,19 +1255,15 @@ namespace Content.Shared.Interaction RaiseLocalEvent(uidB.Value, new ContactInteractionEvent(uidA)); } + private void HandleUserInterfaceRangeCheck(ref BoundUserInterfaceCheckRangeEvent ev) { if (ev.Result == BoundUserInterfaceRangeResult.Fail) return; - if (InRangeUnobstructed(ev.Actor, ev.Target, ev.Data.InteractionRange)) - { - ev.Result = BoundUserInterfaceRangeResult.Pass; - } - else - { - ev.Result = BoundUserInterfaceRangeResult.Fail; - } + ev.Result = UiRangeCheck(ev.Actor!, ev.Target, ev.Data.InteractionRange) + ? BoundUserInterfaceRangeResult.Pass + : BoundUserInterfaceRangeResult.Fail; } } diff --git a/Content.Shared/Inventory/InventorySystem.Equip.cs b/Content.Shared/Inventory/InventorySystem.Equip.cs index 400dfb0beb..7fd156213b 100644 --- a/Content.Shared/Inventory/InventorySystem.Equip.cs +++ b/Content.Shared/Inventory/InventorySystem.Equip.cs @@ -210,11 +210,7 @@ public abstract partial class InventorySystem return false; // Can the actor reach the item? - if (_interactionSystem.InRangeUnobstructed(actor, itemUid) && _containerSystem.IsInSameOrParentContainer(actor, itemUid)) - return true; - - // Is the item in an open storage UI, i.e., is the user quick-equipping from an open backpack? - if (_interactionSystem.CanAccessViaStorage(actor, itemUid)) + if (_interactionSystem.InRangeAndAccessible(actor, itemUid)) return true; // Is the actor currently stripping the target? Here we could check if the actor has the stripping UI open, but diff --git a/Content.Server/Lock/Components/ActivatableUIRequiresLockComponent.cs b/Content.Shared/Lock/ActivatableUIRequiresLockComponent.cs similarity index 66% rename from Content.Server/Lock/Components/ActivatableUIRequiresLockComponent.cs rename to Content.Shared/Lock/ActivatableUIRequiresLockComponent.cs index dac677c1c2..7d701ffd87 100644 --- a/Content.Server/Lock/Components/ActivatableUIRequiresLockComponent.cs +++ b/Content.Shared/Lock/ActivatableUIRequiresLockComponent.cs @@ -1,15 +1,17 @@ -namespace Content.Server.Lock.Components; +using Robust.Shared.GameStates; + +namespace Content.Shared.Lock; /// /// This is used for activatable UIs that require the entity to have a lock in a certain state. /// -[RegisterComponent] +[RegisterComponent, NetworkedComponent, Access(typeof(LockSystem))] public sealed partial class ActivatableUIRequiresLockComponent : Component { /// /// TRUE: the lock must be locked to access the UI. /// FALSE: the lock must be unlocked to access the UI. /// - [DataField("requireLocked"), ViewVariables(VVAccess.ReadWrite)] - public bool requireLocked = false; + [DataField] + public bool RequireLocked; } diff --git a/Content.Shared/Lock/LockSystem.cs b/Content.Shared/Lock/LockSystem.cs index c87304b420..1935f3c5e8 100644 --- a/Content.Shared/Lock/LockSystem.cs +++ b/Content.Shared/Lock/LockSystem.cs @@ -9,6 +9,7 @@ using Content.Shared.IdentityManagement; using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Storage.Components; +using Content.Shared.UserInterface; using Content.Shared.Verbs; using Content.Shared.Wires; using JetBrains.Annotations; @@ -24,6 +25,7 @@ namespace Content.Shared.Lock; public sealed class LockSystem : EntitySystem { [Dependency] private readonly AccessReaderSystem _accessReader = default!; + [Dependency] private readonly ActivatableUISystem _activatableUI = default!; [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedPopupSystem _sharedPopupSystem = default!; @@ -46,6 +48,9 @@ public sealed class LockSystem : EntitySystem SubscribeLocalEvent(OnLockToggleAttempt); SubscribeLocalEvent(OnAttemptChangePanel); SubscribeLocalEvent(OnUnanchorAttempt); + + SubscribeLocalEvent(OnUIOpenAttempt); + SubscribeLocalEvent(LockToggled); } private void OnStartup(EntityUid uid, LockComponent lockComp, ComponentStartup args) @@ -347,4 +352,25 @@ public sealed class LockSystem : EntitySystem args.User); args.Cancel(); } -} \ No newline at end of file + + private void OnUIOpenAttempt(EntityUid uid, ActivatableUIRequiresLockComponent component, ActivatableUIOpenAttemptEvent args) + { + if (args.Cancelled) + return; + + if (TryComp(uid, out var lockComp) && lockComp.Locked != component.RequireLocked) + { + args.Cancel(); + if (lockComp.Locked) + _sharedPopupSystem.PopupEntity(Loc.GetString("entity-storage-component-locked-message"), uid, args.User); + } + } + + private void LockToggled(EntityUid uid, ActivatableUIRequiresLockComponent component, LockToggledEvent args) + { + if (!TryComp(uid, out var lockComp) || lockComp.Locked == component.RequireLocked) + return; + + _activatableUI.CloseAll(uid); + } +} diff --git a/Content.Shared/MagicMirror/SharedMagicMirrorSystem.cs b/Content.Shared/MagicMirror/SharedMagicMirrorSystem.cs index f9c941ffe3..ea5838a723 100644 --- a/Content.Shared/MagicMirror/SharedMagicMirrorSystem.cs +++ b/Content.Shared/MagicMirror/SharedMagicMirrorSystem.cs @@ -2,6 +2,7 @@ using Content.Shared.DoAfter; using Content.Shared.Humanoid.Markings; using Content.Shared.Interaction; using Robust.Shared.Serialization; +using Robust.Shared.Utility; namespace Content.Shared.MagicMirror; @@ -17,10 +18,13 @@ public abstract class SharedMagicMirrorSystem : EntitySystem private void OnMirrorRangeCheck(EntityUid uid, MagicMirrorComponent component, ref BoundUserInterfaceCheckRangeEvent args) { - if (!Exists(component.Target) || !_interaction.InRangeUnobstructed(uid, component.Target.Value)) - { + if (args.Result == BoundUserInterfaceRangeResult.Fail) + return; + + DebugTools.Assert(component.Target != null && Exists(component.Target)); + + if (!_interaction.InRangeUnobstructed(uid, component.Target.Value)) args.Result = BoundUserInterfaceRangeResult.Fail; - } } } diff --git a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs index a9fc4cb2a6..61b6c0fd37 100644 --- a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs +++ b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs @@ -344,14 +344,17 @@ public sealed class PullingSystem : EntitySystem return !startPull.Cancelled && !getPulled.Cancelled; } - public bool TogglePull(EntityUid pullableUid, EntityUid pullerUid, PullableComponent pullable) + public bool TogglePull(Entity pullable, EntityUid pullerUid) { - if (pullable.Puller == pullerUid) + if (!Resolve(pullable, ref pullable.Comp, false)) + return false; + + if (pullable.Comp.Puller == pullerUid) { - return TryStopPull(pullableUid, pullable); + return TryStopPull(pullable, pullable.Comp); } - return TryStartPull(pullerUid, pullableUid, pullableComp: pullable); + return TryStartPull(pullerUid, pullable, pullableComp: pullable); } public bool TogglePull(EntityUid pullerUid, PullerComponent puller) @@ -359,7 +362,7 @@ public sealed class PullingSystem : EntitySystem if (!TryComp(puller.Pulling, out var pullable)) return false; - return TogglePull(puller.Pulling.Value, pullerUid, pullable); + return TogglePull((puller.Pulling.Value, pullable), pullerUid); } public bool TryStartPull(EntityUid pullerUid, EntityUid pullableUid, diff --git a/Content.Shared/Pinpointer/SharedNavMapSystem.cs b/Content.Shared/Pinpointer/SharedNavMapSystem.cs index ffe81c2d0e..7c12321b5d 100644 --- a/Content.Shared/Pinpointer/SharedNavMapSystem.cs +++ b/Content.Shared/Pinpointer/SharedNavMapSystem.cs @@ -96,7 +96,7 @@ public abstract class SharedNavMapSystem : EntitySystem chunks.Add(origin, chunk.TileData); } - args.State = new NavMapComponentState(chunks, component.Beacons); + args.State = new NavMapState(chunks, component.Beacons); return; } @@ -109,12 +109,7 @@ public abstract class SharedNavMapSystem : EntitySystem chunks.Add(origin, chunk.TileData); } - args.State = new NavMapComponentState(chunks, component.Beacons) - { - // TODO NAVMAP cache a single AllChunks hashset in the component. - // Or maybe just only send them if a chunk gets removed. - AllChunks = new(component.Chunks.Keys), - }; + args.State = new NavMapDeltaState(chunks, component.Beacons, new(component.Chunks.Keys)); } #endregion @@ -122,32 +117,35 @@ public abstract class SharedNavMapSystem : EntitySystem #region: System messages [Serializable, NetSerializable] - protected sealed class NavMapComponentState( + protected sealed class NavMapState( Dictionary chunks, Dictionary beacons) - : ComponentState, IComponentDeltaState + : ComponentState { public Dictionary Chunks = chunks; public Dictionary Beacons = beacons; + } - // Required to infer deleted/missing chunks for delta states - public HashSet? AllChunks; + [Serializable, NetSerializable] + protected sealed class NavMapDeltaState( + Dictionary modifiedChunks, + Dictionary beacons, + HashSet allChunks) + : ComponentState, IComponentDeltaState + { + public Dictionary ModifiedChunks = modifiedChunks; + public Dictionary Beacons = beacons; + public HashSet AllChunks = allChunks; - public bool FullState => AllChunks == null; - - public void ApplyToFullState(IComponentState fullState) + public void ApplyToFullState(NavMapState state) { - DebugTools.Assert(!FullState); - var state = (NavMapComponentState) fullState; - DebugTools.Assert(state.FullState); - foreach (var key in state.Chunks.Keys) { if (!AllChunks!.Contains(key)) state.Chunks.Remove(key); } - foreach (var (index, data) in Chunks) + foreach (var (index, data) in ModifiedChunks) { if (!state.Chunks.TryGetValue(index, out var stateValue)) state.Chunks[index] = stateValue = new int[data.Length]; @@ -162,12 +160,8 @@ public abstract class SharedNavMapSystem : EntitySystem } } - public IComponentState CreateNewFullState(IComponentState fullState) + public NavMapState CreateNewFullState(NavMapState state) { - DebugTools.Assert(!FullState); - var state = (NavMapComponentState) fullState; - DebugTools.Assert(state.FullState); - var chunks = new Dictionary(state.Chunks.Count); foreach (var (index, data) in state.Chunks) { @@ -176,13 +170,13 @@ public abstract class SharedNavMapSystem : EntitySystem var newData = chunks[index] = new int[ArraySize]; - if (Chunks.TryGetValue(index, out var updatedData)) + if (ModifiedChunks.TryGetValue(index, out var updatedData)) Array.Copy(newData, updatedData, ArraySize); else Array.Copy(newData, data, ArraySize); } - return new NavMapComponentState(chunks, new(Beacons)); + return new NavMapState(chunks, new(Beacons)); } } diff --git a/Content.Shared/Preferences/Loadouts/RoleLoadout.cs b/Content.Shared/Preferences/Loadouts/RoleLoadout.cs index 2094011873..bb3e46c49b 100644 --- a/Content.Shared/Preferences/Loadouts/RoleLoadout.cs +++ b/Content.Shared/Preferences/Loadouts/RoleLoadout.cs @@ -74,18 +74,19 @@ public sealed class RoleLoadout { var loadout = loadouts[i]; + // Old prototype or otherwise invalid. if (!protoManager.TryIndex(loadout.Prototype, out var loadoutProto)) { loadouts.RemoveAt(i); continue; } - // Похуй FIXME - //if (!IsValid(profile, session, loadout.Prototype, collection, out _)) - // { - // loadouts.RemoveAt(i); - // continue; - // } + // Malicious client maybe, check the group even has it. + if (!groupProto.Loadouts.Contains(loadout.Prototype)) + { + loadouts.RemoveAt(i); + continue; + } Apply(loadoutProto); } diff --git a/Content.Shared/Silicons/Borgs/SharedBorgSystem.cs b/Content.Shared/Silicons/Borgs/SharedBorgSystem.cs index e6672a1cf2..fe4ceb6a2c 100644 --- a/Content.Shared/Silicons/Borgs/SharedBorgSystem.cs +++ b/Content.Shared/Silicons/Borgs/SharedBorgSystem.cs @@ -6,6 +6,7 @@ using Content.Shared.Movement.Systems; using Content.Shared.Popups; using Content.Shared.PowerCell.Components; using Content.Shared.Silicons.Borgs.Components; +using Content.Shared.UserInterface; using Content.Shared.Wires; using Robust.Shared.Containers; using Robust.Shared.Prototypes; @@ -37,6 +38,8 @@ public abstract partial class SharedBorgSystem : EntitySystem SubscribeLocalEvent(OnInserted); SubscribeLocalEvent(OnRemoved); SubscribeLocalEvent(OnRefreshMovementSpeedModifiers); + SubscribeLocalEvent(OnUIOpenAttempt); + //Honk SubscribeLocalEvent(RandomTTS); @@ -107,6 +110,13 @@ public abstract partial class SharedBorgSystem : EntitySystem component.ModuleContainer = Container.EnsureContainer(uid, component.ModuleContainerId, containerManager); } + private void OnUIOpenAttempt(EntityUid uid, BorgChassisComponent component, ActivatableUIOpenAttemptEvent args) + { + // borgs can't view their own ui + if (args.User == uid) + args.Cancel(); + } + protected virtual void OnInserted(EntityUid uid, BorgChassisComponent component, EntInsertedIntoContainerMessage args) { diff --git a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs index c53c3a9e2e..7d23038682 100644 --- a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs @@ -25,6 +25,7 @@ using Content.Shared.Stacks; using Content.Shared.Storage.Components; using Content.Shared.Timing; using Content.Shared.Verbs; +using Content.Shared.Whitelist; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; using Robust.Shared.GameStates; @@ -156,7 +157,9 @@ public abstract class SharedStorageSystem : EntitySystem Grid = new List(component.Grid), MaxItemSize = component.MaxItemSize, StoredItems = storedItems, - SavedLocations = component.SavedLocations + SavedLocations = component.SavedLocations, + Whitelist = component.Whitelist, + Blacklist = component.Blacklist }; } @@ -168,6 +171,8 @@ public abstract class SharedStorageSystem : EntitySystem component.Grid.Clear(); component.Grid.AddRange(state.Grid); component.MaxItemSize = state.MaxItemSize; + component.Whitelist = state.Whitelist; + component.Blacklist = state.Blacklist; component.StoredItems.Clear(); @@ -1100,7 +1105,7 @@ public abstract class SharedStorageSystem : EntitySystem /// true if inserted, false otherwise public bool PlayerInsertEntityInWorld(Entity uid, EntityUid player, EntityUid toInsert) { - if (!Resolve(uid, ref uid.Comp) || !_interactionSystem.InRangeUnobstructed(player, uid)) + if (!Resolve(uid, ref uid.Comp) || !_interactionSystem.InRangeUnobstructed(player, uid.Owner)) return false; if (!Insert(uid, toInsert, out _, user: player, uid.Comp)) @@ -1501,5 +1506,9 @@ public abstract class SharedStorageSystem : EntitySystem public List Grid = new(); public ProtoId? MaxItemSize; + + public EntityWhitelist? Whitelist; + + public EntityWhitelist? Blacklist; } } diff --git a/Content.Shared/Strip/SharedStrippableSystem.cs b/Content.Shared/Strip/SharedStrippableSystem.cs index 59b24ec943..075cf81a4c 100644 --- a/Content.Shared/Strip/SharedStrippableSystem.cs +++ b/Content.Shared/Strip/SharedStrippableSystem.cs @@ -1,17 +1,31 @@ +using Content.Shared.CombatMode; using Content.Shared.DragDrop; using Content.Shared.Hands.Components; +using Content.Shared.Interaction; using Content.Shared.Strip.Components; namespace Content.Shared.Strip; public abstract class SharedStrippableSystem : EntitySystem { + [Dependency] private readonly SharedUserInterfaceSystem _ui = default!; + public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnCanDropOn); SubscribeLocalEvent(OnCanDrop); SubscribeLocalEvent(OnDragDrop); + SubscribeLocalEvent(OnActivateInWorld); + } + + private void OnActivateInWorld(EntityUid uid, StrippableComponent component, ActivateInWorldEvent args) + { + if (args.Handled || args.Target == args.User) + return; + + if (TryOpenStrippingUi(args.User, (uid, component))) + args.Handled = true; } public (TimeSpan Time, bool Stealth) GetStripTimeModifiers(EntityUid user, EntityUid target, TimeSpan initialTime) @@ -29,13 +43,20 @@ public abstract class SharedStrippableSystem : EntitySystem if (args.Handled || args.Target != args.User) return; - StartOpeningStripper(args.User, (uid, component)); - args.Handled = true; + if (TryOpenStrippingUi(args.User, (uid, component))) + args.Handled = true; } - public virtual void StartOpeningStripper(EntityUid user, Entity component, bool openInCombat = false) + public bool TryOpenStrippingUi(EntityUid user, Entity target, bool openInCombat = false) { + if (!openInCombat && TryComp(user, out var mode) && mode.IsInCombatMode) + return false; + if (!HasComp(user)) + return false; + + _ui.OpenUi(target.Owner, StrippingUiKey.Key, user); + return true; } private void OnCanDropOn(EntityUid uid, StrippingComponent component, ref CanDropTargetEvent args) diff --git a/Content.Shared/UserInterface/ActivatableUIComponent.cs b/Content.Shared/UserInterface/ActivatableUIComponent.cs index 3f83816b7d..93f05acac0 100644 --- a/Content.Shared/UserInterface/ActivatableUIComponent.cs +++ b/Content.Shared/UserInterface/ActivatableUIComponent.cs @@ -57,7 +57,7 @@ namespace Content.Shared.UserInterface /// [ViewVariables(VVAccess.ReadWrite)] [DataField] - public bool AllowSpectator = true; + public bool BlockSpectators; /// /// Whether the item must be in the user's currently selected/active hand. diff --git a/Content.Shared/UserInterface/ActivatableUISystem.cs b/Content.Shared/UserInterface/ActivatableUISystem.cs index a6d27ac545..c1822c4ee3 100644 --- a/Content.Shared/UserInterface/ActivatableUISystem.cs +++ b/Content.Shared/UserInterface/ActivatableUISystem.cs @@ -8,7 +8,7 @@ using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Popups; using Content.Shared.Verbs; -using Robust.Shared.Containers; +using Robust.Shared.Utility; namespace Content.Shared.UserInterface; @@ -19,15 +19,12 @@ public sealed partial class ActivatableUISystem : EntitySystem [Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedHandsSystem _hands = default!; - [Dependency] private readonly SharedContainerSystem _container = default!; - [Dependency] private readonly SharedInteractionSystem _interaction = default!; - - private readonly List _toClose = new(); public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(OnUseInHand); SubscribeLocalEvent(OnActivate); SubscribeLocalEvent(OnInteractUsing); @@ -37,28 +34,24 @@ public sealed partial class ActivatableUISystem : EntitySystem SubscribeLocalEvent>(GetActivationVerb); SubscribeLocalEvent>(GetVerb); - // TODO ActivatableUI - // Add UI-user component, and listen for user container changes. - // I.e., should lose a computer UI if a player gets shut into a locker. - SubscribeLocalEvent(OnGotInserted); - SubscribeLocalEvent(OnGotRemoved); - - SubscribeLocalEvent(OnBoundInterfaceInteractAttempt); SubscribeLocalEvent(OnActionPerform); InitializePower(); } - private void OnBoundInterfaceInteractAttempt(BoundUserInterfaceMessageAttempt ev) + private void OnStartup(Entity ent, ref ComponentStartup args) { - if (!TryComp(ev.Target, out ActivatableUIComponent? comp)) + if (ent.Comp.Key == null) + { + Log.Error($"Missing UI Key for entity: {ToPrettyString(ent)}"); return; + } - if (!comp.RequireHands) - return; - - if (!TryComp(ev.Actor, out HandsComponent? hands) || hands.Hands.Count == 0) - ev.Cancel(); + // TODO BUI + // set interaction range to zero to avoid constant range checks. + // + // if (ent.Comp.InHandsOnly && _uiSystem.TryGetInterfaceData(ent.Owner, ent.Comp.Key, out var data)) + // data.InteractionRange = 0; } private void OnActionPerform(EntityUid uid, UserInterfaceComponent component, OpenUiActionEvent args) @@ -77,9 +70,10 @@ public sealed partial class ActivatableUISystem : EntitySystem args.Verbs.Add(new ActivationVerb { - // TODO VERBS add "open UI" icon Act = () => InteractUI(args.User, uid, component), - Text = Loc.GetString(component.VerbText) + Text = Loc.GetString(component.VerbText), + // TODO VERB ICON find a better icon + Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")), }); } @@ -90,9 +84,10 @@ public sealed partial class ActivatableUISystem : EntitySystem args.Verbs.Add(new Verb { - // TODO VERBS add "open UI" icon Act = () => InteractUI(args.User, uid, component), - Text = Loc.GetString(component.VerbText) + Text = Loc.GetString(component.VerbText), + // TODO VERB ICON find a better icon + Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")), }); } @@ -119,7 +114,7 @@ public sealed partial class ActivatableUISystem : EntitySystem } } - return args.CanInteract || component.AllowSpectator && HasComp(args.User); + return args.CanInteract || HasComp(args.User) && !component.BlockSpectators; } private void OnUseInHand(EntityUid uid, ActivatableUIComponent component, UseInHandEvent args) @@ -191,7 +186,7 @@ public sealed partial class ActivatableUISystem : EntitySystem return true; } - if (!_blockerSystem.CanInteract(user, uiEntity) && (!aui.AllowSpectator || !HasComp(user))) + if (!_blockerSystem.CanInteract(user, uiEntity) && (!HasComp(user) || aui.BlockSpectators)) return false; if (aui.RequireHands) @@ -286,47 +281,4 @@ public sealed partial class ActivatableUISystem : EntitySystem if (ent.Comp.RequireHands && ent.Comp.InHandsOnly) CloseAll(ent, ent); } - - private void OnGotInserted(Entity ent, ref EntGotInsertedIntoContainerMessage args) - { - CheckAccess((ent, ent)); - } - - private void OnGotRemoved(Entity ent, ref EntGotRemovedFromContainerMessage args) - { - CheckAccess((ent, ent)); - } - - public void CheckAccess(Entity ent) - { - if (!Resolve(ent, ref ent.Comp)) - return; - - if (ent.Comp.Key == null) - { - Log.Error($"Encountered null key in activatable ui on entity {ToPrettyString(ent)}"); - return; - } - - foreach (var user in _uiSystem.GetActors(ent.Owner, ent.Comp.Key)) - { - if (!_container.IsInSameOrParentContainer(user, ent) - && !_interaction.CanAccessViaStorage(user, ent)) - { - _toClose.Add(user); - continue; - - } - - if (!_interaction.InRangeUnobstructed(user, ent)) - _toClose.Add(user); - } - - foreach (var user in _toClose) - { - _uiSystem.CloseUi(ent.Owner, ent.Comp.Key, user); - } - - _toClose.Clear(); - } } diff --git a/Content.Shared/Verbs/SharedVerbSystem.cs b/Content.Shared/Verbs/SharedVerbSystem.cs index 60714aea8f..e78fe98f4c 100644 --- a/Content.Shared/Verbs/SharedVerbSystem.cs +++ b/Content.Shared/Verbs/SharedVerbSystem.cs @@ -72,19 +72,7 @@ namespace Content.Shared.Verbs extraCategories = new(); // accessibility checks - bool canAccess = false; - if (force || target == user) - canAccess = true; - else if (_interactionSystem.InRangeUnobstructed(user, target)) - { - // Note that being in a container does not count as an obstruction for InRangeUnobstructed - // Therefore, we need extra checks to ensure the item is actually accessible: - if (ContainerSystem.IsInSameOrParentContainer(user, target)) - canAccess = true; - else - // the item might be in a backpack that the user has open - canAccess = _interactionSystem.CanAccessViaStorage(user, target); - } + var canAccess = force || _interactionSystem.InRangeAndAccessible(user, target); // A large number of verbs need to check action blockers. Instead of repeatedly having each system individually // call ActionBlocker checks, just cache it for the verb request. diff --git a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs index 3d9eb67b36..719a020311 100644 --- a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs +++ b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs @@ -1,6 +1,8 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Numerics; +using Content.Shared._White; +using Content.Shared._White.Implants.NeuroControl; using Content.Shared.ActionBlocker; using Content.Shared.Administration.Logs; using Content.Shared.CombatMode; @@ -13,6 +15,7 @@ using Content.Shared.Hands.Components; using Content.Shared.Interaction; using Content.Shared.Inventory; using Content.Shared.Item.ItemToggle.Components; +using Content.Shared.Movement.Components; using Content.Shared.Physics; using Content.Shared.Popups; using Content.Shared.Weapons.Melee.Components; @@ -20,9 +23,6 @@ using Content.Shared.Weapons.Melee.Events; using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Events; using Content.Shared.Weapons.Ranged.Systems; -using Content.Shared._White; -using Content.Shared._White.Implants.NeuroControl; -using Content.Shared.Movement.Components; using Robust.Shared.Configuration; using Robust.Shared.Map; using Robust.Shared.Physics; @@ -205,49 +205,39 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem private void OnLightAttack(LightAttackEvent msg, EntitySessionEventArgs args) { - var user = args.SenderSession.AttachedEntity; - - if (user == null) + if (args.SenderSession.AttachedEntity is not {} user) return; - if (!TryGetWeapon(user.Value, out var weaponUid, out var weapon) || + if (!TryGetWeapon(user, out var weaponUid, out var weapon) || weaponUid != GetEntity(msg.Weapon)) { return; } - AttemptAttack(args.SenderSession.AttachedEntity!.Value, weaponUid, weapon, msg, args.SenderSession); + AttemptAttack(user, weaponUid, weapon, msg, args.SenderSession); } private void OnHeavyAttack(HeavyAttackEvent msg, EntitySessionEventArgs args) { - if (args.SenderSession.AttachedEntity == null) - { + if (args.SenderSession.AttachedEntity is not {} user) return; - } - if (!TryGetWeapon(args.SenderSession.AttachedEntity.Value, out var weaponUid, out var weapon) || + if (!TryGetWeapon(user, out var weaponUid, out var weapon) || weaponUid != GetEntity(msg.Weapon)) { return; } - AttemptAttack(args.SenderSession.AttachedEntity.Value, weaponUid, weapon, msg, args.SenderSession); + AttemptAttack(user, weaponUid, weapon, msg, args.SenderSession); } private void OnDisarmAttack(DisarmAttackEvent msg, EntitySessionEventArgs args) { - if (args.SenderSession.AttachedEntity == null) - { + if (args.SenderSession.AttachedEntity is not {} user) return; - } - if (!TryGetWeapon(args.SenderSession.AttachedEntity.Value, out var weaponUid, out var weapon)) - { - return; - } - - AttemptAttack(args.SenderSession.AttachedEntity.Value, weaponUid, weapon, msg, args.SenderSession); + if (TryGetWeapon(user, out var weaponUid, out var weapon)) + AttemptAttack(user, weaponUid, weapon, msg, args.SenderSession); } /// @@ -381,18 +371,22 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem return false; var update = UpdateNextAttack.Both; // WD + EntityUid? target = null; switch (attack) { case LightAttackEvent light: var lightTarget = GetEntity(light.Target); - update = lightTarget == null ? UpdateNextAttack.Both : - IsMob(lightTarget.Value) ? UpdateNextAttack.Mob : UpdateNextAttack.NonMob; // WD + if (light.Target != null && !TryGetEntity(light.Target, out target)) + { + // Target was lightly attacked & deleted. + return false; + } - if (!Blocker.CanAttack(user, lightTarget, (weaponUid, weapon))) + if (!Blocker.CanAttack(user, target, (weaponUid, weapon))) return false; // Can't self-attack if you're the weapon - if (weaponUid == lightTarget) + if (weaponUid == target) return false; // WD START @@ -421,11 +415,13 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem break; case DisarmAttackEvent disarm: - var disarmTarget = GetEntity(disarm.Target); - update = disarmTarget == null ? UpdateNextAttack.Both : - IsMob(disarmTarget.Value) ? UpdateNextAttack.Mob : UpdateNextAttack.NonMob; // WD + if (disarm.Target != null && !TryGetEntity(disarm.Target, out target)) + { + // Target was lightly attacked & deleted. + return false; + } - if (!Blocker.CanAttack(user, disarmTarget, (weaponUid, weapon), true)) + if (!Blocker.CanAttack(user, target, (weaponUid, weapon), true)) return false; break; default: diff --git a/Content.Server/Wires/ActivatableUIRequiresPanelComponent.cs b/Content.Shared/Wires/ActivatableUIRequiresPanelComponent.cs similarity index 71% rename from Content.Server/Wires/ActivatableUIRequiresPanelComponent.cs rename to Content.Shared/Wires/ActivatableUIRequiresPanelComponent.cs index f92df3d3d5..6e8fff6e49 100644 --- a/Content.Server/Wires/ActivatableUIRequiresPanelComponent.cs +++ b/Content.Shared/Wires/ActivatableUIRequiresPanelComponent.cs @@ -1,15 +1,17 @@ -namespace Content.Server.Wires; +using Robust.Shared.GameStates; + +namespace Content.Shared.Wires; /// /// This is used for activatable UIs that require the entity to have a panel in a certain state. /// -[RegisterComponent] +[RegisterComponent, NetworkedComponent, Access(typeof(SharedWiresSystem))] public sealed partial class ActivatableUIRequiresPanelComponent : Component { /// /// TRUE: the panel must be open to access the UI. /// FALSE: the panel must be closed to access the UI. /// - [DataField("requireOpen"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public bool RequireOpen = true; } diff --git a/Content.Shared/Wires/SharedWiresSystem.cs b/Content.Shared/Wires/SharedWiresSystem.cs index b4b0768e0f..d84766a5fc 100644 --- a/Content.Shared/Wires/SharedWiresSystem.cs +++ b/Content.Shared/Wires/SharedWiresSystem.cs @@ -3,6 +3,7 @@ using Content.Shared.Database; using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Tools.Systems; +using Content.Shared.UserInterface; using Robust.Shared.Audio.Systems; namespace Content.Shared.Wires; @@ -10,6 +11,7 @@ namespace Content.Shared.Wires; public abstract class SharedWiresSystem : EntitySystem { [Dependency] protected readonly ISharedAdminLogManager AdminLogger = default!; + [Dependency] private readonly ActivatableUISystem _activatableUI = default!; [Dependency] protected readonly SharedAppearanceSystem Appearance = default!; [Dependency] protected readonly SharedAudioSystem Audio = default!; [Dependency] protected readonly SharedToolSystem Tool = default!; @@ -21,6 +23,9 @@ public abstract class SharedWiresSystem : EntitySystem SubscribeLocalEvent(OnPanelDoAfter); SubscribeLocalEvent(OnInteractUsing); SubscribeLocalEvent(OnExamine); + + SubscribeLocalEvent(OnAttemptOpenActivatableUI); + SubscribeLocalEvent(OnActivatableUIPanelChanged); } private void OnPanelDoAfter(EntityUid uid, WiresPanelComponent panel, WirePanelDoAfterEvent args) @@ -132,4 +137,21 @@ public abstract class SharedWiresSystem : EntitySystem return entity.Comp.Open; } + + private void OnAttemptOpenActivatableUI(EntityUid uid, ActivatableUIRequiresPanelComponent component, ActivatableUIOpenAttemptEvent args) + { + if (args.Cancelled || !TryComp(uid, out var wires)) + return; + + if (component.RequireOpen != wires.Open) + args.Cancel(); + } + + private void OnActivatableUIPanelChanged(EntityUid uid, ActivatableUIRequiresPanelComponent component, ref PanelChangedEvent args) + { + if (args.Open == component.RequireOpen) + return; + + _activatableUI.CloseAll(uid); + } } diff --git a/Content.Server/Zombies/PendingZombieComponent.cs b/Content.Shared/Zombies/PendingZombieComponent.cs similarity index 94% rename from Content.Server/Zombies/PendingZombieComponent.cs rename to Content.Shared/Zombies/PendingZombieComponent.cs index 811d3f9644..0fb61c84df 100644 --- a/Content.Server/Zombies/PendingZombieComponent.cs +++ b/Content.Shared/Zombies/PendingZombieComponent.cs @@ -1,12 +1,13 @@ using Content.Shared.Damage; +using Robust.Shared.GameStates; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; -namespace Content.Server.Zombies; +namespace Content.Shared.Zombies; /// /// Temporary because diseases suck. /// -[RegisterComponent] +[RegisterComponent, NetworkedComponent] public sealed partial class PendingZombieComponent : Component { /// diff --git a/Resources/Locale/en-US/objectives/round-end.ftl b/Resources/Locale/en-US/objectives/round-end.ftl index b4314b2caf..3da81fc964 100644 --- a/Resources/Locale/en-US/objectives/round-end.ftl +++ b/Resources/Locale/en-US/objectives/round-end.ftl @@ -6,7 +6,6 @@ objectives-round-end-result = {$count -> objectives-round-end-result-in-custody = {$custody} out of {$count} {MAKEPLURAL($agent)} were in custody. objectives-player-user-named = [color=White]{$name}[/color] ([color=gray]{$user}[/color]) -objectives-player-user = [color=gray]{$user}[/color] objectives-player-named = [color=White]{$name}[/color] objectives-no-objectives = {$custody}{$title} was a {$agent}. diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_engineering.yml b/Resources/Prototypes/Catalog/Cargo/cargo_engineering.yml index 754e30f133..7ca6af8451 100644 --- a/Resources/Prototypes/Catalog/Cargo/cargo_engineering.yml +++ b/Resources/Prototypes/Catalog/Cargo/cargo_engineering.yml @@ -133,7 +133,7 @@ icon: sprite: Structures/Piping/Atmospherics/Portable/portable_sheater.rsi state: sheaterOff - product: SpaceHeaterAnchored + product: CrateEngineeringSpaceHeater cost: 300 category: cargoproduct-category-name-engineering group: market diff --git a/Resources/Prototypes/Catalog/Fills/Crates/engineering.yml b/Resources/Prototypes/Catalog/Fills/Crates/engineering.yml index 03c870fa58..26a8910c73 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/engineering.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/engineering.yml @@ -194,3 +194,13 @@ contents: - id: WeaponParticleDecelerator amount: 3 + +- type: entity + id: CrateEngineeringSpaceHeater + parent: CrateEngineering + name: space heater crate + description: Contains a space heater for climate control. + components: + - type: StorageFill + contents: + - id: SpaceHeaterFlatpack diff --git a/Resources/Prototypes/Catalog/Fills/Crates/engines.yml b/Resources/Prototypes/Catalog/Fills/Crates/engines.yml index fcefdf7b1d..b510b22cca 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/engines.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/engines.yml @@ -42,7 +42,7 @@ components: - type: StorageFill contents: - - id: Emitter # TODO change to flatpack + - id: EmitterFlatpack - type: entity id: CrateEngineeringSingularityCollector diff --git a/Resources/Prototypes/Entities/Effects/mobspawn.yml b/Resources/Prototypes/Entities/Effects/mobspawn.yml index fb59aa3fc4..2fad68d7f2 100644 --- a/Resources/Prototypes/Entities/Effects/mobspawn.yml +++ b/Resources/Prototypes/Entities/Effects/mobspawn.yml @@ -1,6 +1,7 @@ - type: entity id: MobSpawnCrabQuartz name: mobspawner quartzcrab + noSpawn: True components: - type: Transform anchored: True @@ -30,6 +31,7 @@ id: MobSpawnCrabIron parent: MobSpawnCrabQuartz name: mobspawner ironcrab + noSpawn: True components: - type: Sprite sprite: /Textures/Effects/mobspawn.rsi @@ -41,6 +43,7 @@ id: MobSpawnCrabSilver parent: MobSpawnCrabQuartz name: mobspawner silvercrab + noSpawn: True components: - type: Sprite sprite: /Textures/Effects/mobspawn.rsi @@ -52,6 +55,7 @@ id: MobSpawnCrabUranium parent: MobSpawnCrabQuartz name: mobspawner uraniumcrab + noSpawn: True components: - type: Sprite sprite: /Textures/Effects/mobspawn.rsi diff --git a/Resources/Prototypes/Entities/Effects/wallspawn.yml b/Resources/Prototypes/Entities/Effects/wallspawn.yml index f1bd236a8a..7213763c46 100644 --- a/Resources/Prototypes/Entities/Effects/wallspawn.yml +++ b/Resources/Prototypes/Entities/Effects/wallspawn.yml @@ -1,5 +1,6 @@ - type: entity id: WallSpawnAsteroid + noSpawn: True components: - type: Transform anchored: True @@ -28,6 +29,7 @@ - type: entity id: WallSpawnAsteroidUraniumCrab parent: WallSpawnAsteroid + noSpawn: True components: - type: SpawnOnDespawn prototype: AsteroidRockUraniumCrab @@ -35,6 +37,7 @@ - type: entity id: WallSpawnAsteroidUranium parent: WallSpawnAsteroid + noSpawn: True components: - type: SpawnOnDespawn prototype: AsteroidRockUranium @@ -42,6 +45,7 @@ - type: entity id: WallSpawnAsteroidQuartzCrab parent: WallSpawnAsteroid + noSpawn: True components: - type: SpawnOnDespawn prototype: AsteroidRockQuartzCrab @@ -49,6 +53,7 @@ - type: entity id: WallSpawnAsteroidQuartz parent: WallSpawnAsteroid + noSpawn: True components: - type: SpawnOnDespawn prototype: AsteroidRockQuartz @@ -56,6 +61,7 @@ - type: entity id: WallSpawnAsteroidSilverCrab parent: WallSpawnAsteroid + noSpawn: True components: - type: SpawnOnDespawn prototype: AsteroidRockSilverCrab @@ -63,6 +69,7 @@ - type: entity id: WallSpawnAsteroidSilver parent: WallSpawnAsteroid + noSpawn: True components: - type: SpawnOnDespawn prototype: AsteroidRockSilver @@ -70,6 +77,7 @@ - type: entity id: WallSpawnAsteroidIronCrab parent: WallSpawnAsteroid + noSpawn: True components: - type: SpawnOnDespawn prototype: AsteroidRockTinCrab @@ -77,6 +85,7 @@ - type: entity id: WallSpawnAsteroidIron parent: WallSpawnAsteroid + noSpawn: True components: - type: SpawnOnDespawn prototype: AsteroidRockTin \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/cannons.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/cannons.yml new file mode 100644 index 0000000000..6db7f4b385 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/cannons.yml @@ -0,0 +1,92 @@ +# Most of these have DO NOT MAP, since stations are completely unequipped to deal with ship combat + these are basically placeholder. + +- type: entity + id: ShuttleGunSvalinnMachineGunCircuitboard + parent: BaseMachineCircuitboard + name: LSE-400c "Svalinn machine gun" machine board + description: A machine printed circuit board for an LSE-400c "Svalinn machine gun" + suffix: DO NOT MAP, Machine Board + components: + - type: Sprite + state: security + - type: MachineBoard + prototype: ShuttleGunSvalinnMachineGun + requirements: + MatterBin: 2 + Manipulator: 4 + materialRequirements: + Steel: 5 + CableHV: 5 + +- type: entity + id: ShuttleGunPerforatorCircuitboard + parent: BaseMachineCircuitboard + name: LSE-1200c "Perforator" machine board + description: A machine printed circuit board for an LSE-1200c "Perforator" + suffix: DO NOT MAP, Machine Board + components: + - type: Sprite + state: security + - type: MachineBoard + prototype: ShuttleGunPerforator + requirements: + MatterBin: 4 + Manipulator: 6 + materialRequirements: + Steel: 10 + CableHV: 5 + +- type: entity + id: ShuttleGunFriendshipCircuitboard + parent: BaseMachineCircuitboard + name: EXP-320g "Friendship" machine board + description: A machine printed circuit board for an EXP-320g "Friendship" + suffix: DO NOT MAP, Machine Board + components: + - type: Sprite + state: security + - type: MachineBoard + prototype: ShuttleGunFriendship + requirements: + MatterBin: 3 + Manipulator: 2 + materialRequirements: + Steel: 7 + CableHV: 5 + +- type: entity + id: ShuttleGunDusterCircuitboard + parent: BaseMachineCircuitboard + name: EXP-2100g "Duster" machine board + description: A machine printed circuit board for an EXP-2100g "Duster" + suffix: DO NOT MAP, Machine Board + components: + - type: Sprite + state: security + - type: MachineBoard + prototype: ShuttleGunDuster + requirements: + MatterBin: 6 + Manipulator: 4 + materialRequirements: + Steel: 10 + CableHV: 5 + Uranium: 2 + +- type: entity + id: ShuttleGunKineticCircuitboard + parent: BaseMachineCircuitboard + name: PTK-800 "Matter Dematerializer" machine board + description: A machine printed circuit board for an PTK-800 "Matter Dematerializer" + suffix: DO NOT MAP, Machine Board + components: + - type: Sprite + state: security + - type: MachineBoard + prototype: ShuttleGunKinetic + requirements: + MatterBin: 2 + Manipulator: 3 + materialRequirements: + Steel: 5 + CableHV: 2 \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml index 3937aac576..2fd2c44cf5 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml @@ -1277,92 +1277,6 @@ CableHV: 5 Uranium: 2 -- type: entity - id: ShuttleGunSvalinnMachineGunCircuitboard - parent: BaseMachineCircuitboard - name: LSE-400c "Svalinn machine gun" machine board - description: A machine printed circuit board for an LSE-400c "Svalinn machine gun" - components: - - type: Sprite - state: security - - type: MachineBoard - prototype: ShuttleGunSvalinnMachineGun - requirements: - MatterBin: 2 - Manipulator: 4 - materialRequirements: - Steel: 5 - CableHV: 5 - -- type: entity - id: ShuttleGunPerforatorCircuitboard - parent: BaseMachineCircuitboard - name: LSE-1200c "Perforator" machine board - description: A machine printed circuit board for an LSE-1200c "Perforator" - components: - - type: Sprite - state: security - - type: MachineBoard - prototype: ShuttleGunPerforator - requirements: - MatterBin: 4 - Manipulator: 6 - materialRequirements: - Steel: 10 - CableHV: 5 - -- type: entity - id: ShuttleGunFriendshipCircuitboard - parent: BaseMachineCircuitboard - name: EXP-320g "Friendship" machine board - description: A machine printed circuit board for an EXP-320g "Friendship" - components: - - type: Sprite - state: security - - type: MachineBoard - prototype: ShuttleGunFriendship - requirements: - MatterBin: 3 - Manipulator: 2 - materialRequirements: - Steel: 7 - CableHV: 5 - -- type: entity - id: ShuttleGunDusterCircuitboard - parent: BaseMachineCircuitboard - name: EXP-2100g "Duster" machine board - description: A machine printed circuit board for an EXP-2100g "Duster" - components: - - type: Sprite - state: security - - type: MachineBoard - prototype: ShuttleGunDuster - requirements: - MatterBin: 6 - Manipulator: 4 - materialRequirements: - Steel: 10 - CableHV: 5 - Uranium: 2 - -- type: entity - id: ShuttleGunKineticCircuitboard - parent: BaseMachineCircuitboard - name: PTK-800 "Matter Dematerializer" machine board - description: A machine printed circuit board for an PTK-800 "Matter Dematerializer" - components: - - type: Sprite - state: security - - type: MachineBoard - prototype: ShuttleGunKinetic - requirements: - MatterBin: 2 - Manipulator: 3 - materialRequirements: - Steel: 5 - CableHV: 2 - - type: entity parent: BaseMachineCircuitboard id: ReagentGrinderIndustrialMachineCircuitboard diff --git a/Resources/Prototypes/Entities/Objects/Devices/flatpack.yml b/Resources/Prototypes/Entities/Objects/Devices/flatpack.yml index 6aa766bd58..50d31d3d39 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/flatpack.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/flatpack.yml @@ -184,3 +184,12 @@ - state: overlay color: "#cec8ac" - state: icon-default + +- type: entity + parent: BaseFlatpack + id: SpaceHeaterFlatpack + name: space heater flatpack + description: A flatpack used for constructing a space heater. + components: + - type: Flatpack + entity: SpaceHeaterAnchored diff --git a/Resources/Prototypes/Entities/Objects/Fun/Instruments/base_instruments.yml b/Resources/Prototypes/Entities/Objects/Fun/Instruments/base_instruments.yml index 684260b737..122ff42eb2 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/Instruments/base_instruments.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/Instruments/base_instruments.yml @@ -29,7 +29,7 @@ components: - type: Instrument - type: ActivatableUI - allowSpectator: false # otherwise they can play client-side music + blockSpectators: true # otherwise they can play client-side music inHandsOnly: false singleUser: true requireHands: true diff --git a/Resources/Prototypes/Entities/Objects/Fun/darts.yml b/Resources/Prototypes/Entities/Objects/Fun/darts.yml index 18854f3d12..36c841995e 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/darts.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/darts.yml @@ -126,8 +126,8 @@ maxVol: 7 - type: SolutionInjectOnEmbed transferAmount: 7 - solution: melee blockSlots: NONE + solution: melee - type: SolutionTransfer maxTransferAmount: 7 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml index 6fb0e7228f..6f694862b5 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml @@ -325,11 +325,12 @@ emptyCase: { state: empty } wiredCase: { state: wired } caseWithTrigger: { state: no-payload } + caseWithPayload: { state: no-trigger } grenade: { state: complete } enum.Trigger.TriggerVisuals.VisualState: enum.ConstructionVisuals.Layer: Primed: { state: primed } - Unprimed: { state: complete } + # Unprimed: - type: StaticPrice price: 25 diff --git a/Resources/Prototypes/Entities/Structures/Shuttles/cannons.yml b/Resources/Prototypes/Entities/Structures/Shuttles/cannons.yml index 0d5df4a8b0..4fc57559a2 100644 --- a/Resources/Prototypes/Entities/Structures/Shuttles/cannons.yml +++ b/Resources/Prototypes/Entities/Structures/Shuttles/cannons.yml @@ -1,3 +1,5 @@ +# Most of these have DO NOT MAP, since stations are completely unequipped to deal with ship combat + these are basically placeholder. + - type: entity id: ShuttleGunBase name: shittle gun @@ -58,7 +60,8 @@ id: ShuttleGunSvalinnMachineGun parent: [ ShuttleGunBase, ConstructibleMachine] name: LSE-400c "Svalinn machine gun" - description: Basic stationary laser unit. Effective against live targets and electronics. Uses regular power cells to fire, and has an extremely high rate of fire + description: Basic stationary laser unit. Effective against live targets and electronics. Uses regular power cells to fire, and has an extremely high rate of fire. + suffix: DO NOT MAP components: - type: Sprite sprite: Objects/Weapons/Guns/Shuttles/laser.rsi @@ -113,6 +116,7 @@ parent: [ ShuttleGunBase, ConstructibleMachine] name: LSE-1200c "Perforator" description: Advanced stationary laser unit. Annihilates electronics and is extremely dangerous to health! Uses the power cage to fire. + suffix: DO NOT MAP components: - type: Sprite sprite: Objects/Weapons/Guns/Shuttles/laser.rsi @@ -169,6 +173,7 @@ parent: [ShuttleGunBase, ConstructibleMachine] name: EXP-320g "Friendship" description: A small stationary grenade launcher that holds 2 grenades. + suffix: DO NOT MAP components: - type: Sprite sprite: Objects/Weapons/Guns/Shuttles/launcher.rsi @@ -222,6 +227,7 @@ parent: [ShuttleGunBase, ConstructibleMachine] name: EXP-2100g "Duster" description: A powerful stationary grenade launcher. A cartridge is required for use. + suffix: DO NOT MAP components: - type: Sprite sprite: Objects/Weapons/Guns/Shuttles/launcher.rsi @@ -319,6 +325,7 @@ parent: [ ShuttleGunBase, ConstructibleMachine] name: PTK-800 "Matter Dematerializer" description: Salvage stationary mining turret. Gradually accumulates charges on its own, extremely effective for asteroid excavation. + suffix: DO NOT MAP components: - type: Sprite sprite: Objects/Weapons/Guns/Shuttles/kinetic.rsi diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index fb52a6e879..d933a786ec 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -413,9 +413,9 @@ prototype: InitialInfected - type: entity - id: LoneOpsSpawn - parent: BaseGameRule noSpawn: true + parent: BaseNukeopsRule + id: LoneOpsSpawn components: - type: StationEvent earliestStart: 35 @@ -447,9 +447,9 @@ prototype: Nukeops - type: entity - id: SleeperAgentsRule - parent: BaseGameRule noSpawn: true + parent: BaseTraitorRule + id: SleeperAgentsRule components: - type: StationEvent earliestStart: 30 @@ -460,7 +460,6 @@ startAudio: path: /Audio/Announcements/intercept.ogg - type: AlertLevelInterceptionRule - - type: TraitorRule - type: AntagSelection definitions: - prefRoles: [ Traitor ] diff --git a/Resources/Prototypes/GameRules/midround.yml b/Resources/Prototypes/GameRules/midround.yml index 4c3a2032e6..081e37d7f0 100644 --- a/Resources/Prototypes/GameRules/midround.yml +++ b/Resources/Prototypes/GameRules/midround.yml @@ -35,7 +35,19 @@ id: Thief components: - type: ThiefRule + - type: AntagObjectives + objectives: + - EscapeThiefShuttleObjective + - type: AntagRandomObjectives + sets: + - groups: ThiefBigObjectiveGroups + prob: 0.7 + maxPicks: 1 + - groups: ThiefObjectiveGroups + maxPicks: 10 + maxDifficulty: 2.5 - type: AntagSelection + agentName: thief-round-end-agent-name definitions: - prefRoles: [ Thief ] maxRange: diff --git a/Resources/Prototypes/GameRules/roundstart.yml b/Resources/Prototypes/GameRules/roundstart.yml index c3907e4206..7594962875 100644 --- a/Resources/Prototypes/GameRules/roundstart.yml +++ b/Resources/Prototypes/GameRules/roundstart.yml @@ -64,9 +64,9 @@ roundEndDelay: 10 - type: entity - id: Nukeops + abstract: true parent: BaseGameRule - noSpawn: true + id: BaseNukeopsRule components: - type: GameRule minPlayers: 20 # Honk @@ -75,6 +75,16 @@ - operationPrefix - operationSuffix - type: NukeopsRule + - type: AntagSelection + - type: AntagLoadProfileRule + +- type: entity + noSpawn: true + parent: BaseNukeopsRule + id: Nukeops + components: + - type: GameRule + minPlayers: 20 - type: LoadMapRule gameMap: NukieOutpost - type: AntagSelection @@ -138,16 +148,30 @@ prototype: Nukeops - type: entity - id: Traitor + abstract: true parent: BaseGameRule + id: BaseTraitorRule + components: + - type: TraitorRule + # TODO: codewords in yml + # TODO: uplink in yml + - type: AntagRandomObjectives + sets: + - groups: TraitorObjectiveGroups + maxDifficulty: 5 + - type: AntagSelection + agentName: traitor-round-end-agent-name + +- type: entity noSpawn: true + parent: BaseTraitorRule + id: Traitor components: - type: GameRule minPlayers: 5 delay: min: 240 max: 420 - - type: TraitorRule - type: AntagSelection definitions: - prefRoles: [ Traitor ] diff --git a/Resources/Prototypes/Objectives/objectiveGroups.yml b/Resources/Prototypes/Objectives/objectiveGroups.yml index 00340be276..bc0d248f6c 100644 --- a/Resources/Prototypes/Objectives/objectiveGroups.yml +++ b/Resources/Prototypes/Objectives/objectiveGroups.yml @@ -55,13 +55,6 @@ ThiefObjectiveGroupStructure: 0 #Temporarily disabled until obvious ways to steal structures are added ThiefObjectiveGroupAnimal: 2 -- type: weightedRandom - id: ThiefEscapeObjectiveGroups - weights: - ThiefObjectiveGroupEscape: 1 - - - - type: weightedRandom id: ThiefObjectiveGroupCollection weights: diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/weapons/modular_grenade.yml b/Resources/Prototypes/Recipes/Construction/Graphs/weapons/modular_grenade.yml index 227f5b1cfd..9db8616118 100644 --- a/Resources/Prototypes/Recipes/Construction/Graphs/weapons/modular_grenade.yml +++ b/Resources/Prototypes/Recipes/Construction/Graphs/weapons/modular_grenade.yml @@ -50,6 +50,12 @@ store: payloadTrigger name: триггер doAfter: 0.5 + - to: caseWithPayload + steps: + - tag: Payload + store: payload + name: Payload + doAfter: 0.5 - node: caseWithTrigger actions: @@ -71,6 +77,26 @@ name: заряд doAfter: 0.5 + - node: caseWithPayload + actions: + - !type:AppearanceChange + - !type:PlaySound + sound: /Audio/Machines/button.ogg + edges: + - to: wiredCase + steps: + - tool: Prying + doAfter: 0.5 + completed: + - !type:EmptyContainer + container: payload + - to: grenade + steps: + - component: PayloadTrigger + store: payloadTrigger + name: Trigger + doAfter: 0.5 + - node: grenade actions: - !type:AppearanceChange diff --git a/Resources/Textures/Objects/Weapons/Grenades/modular.rsi/meta.json b/Resources/Textures/Objects/Weapons/Grenades/modular.rsi/meta.json index f23b6ec168..b0b12127c5 100644 --- a/Resources/Textures/Objects/Weapons/Grenades/modular.rsi/meta.json +++ b/Resources/Textures/Objects/Weapons/Grenades/modular.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/29c0ed1b000619cb5398ef921000a8d4502ba0b6 and modified by Swept", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/29c0ed1b000619cb5398ef921000a8d4502ba0b6 and modified by Swept & ElectroSR", "size": { "x": 32, "y": 32 @@ -19,6 +19,10 @@ "name": "no-payload", "directions": 1 }, + { + "name": "no-trigger", + "directions": 1 + }, { "name": "complete", "directions": 1 diff --git a/Resources/Textures/Objects/Weapons/Grenades/modular.rsi/no-trigger.png b/Resources/Textures/Objects/Weapons/Grenades/modular.rsi/no-trigger.png new file mode 100644 index 0000000000..1be2cbc421 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Grenades/modular.rsi/no-trigger.png differ diff --git a/RobustToolbox b/RobustToolbox index ec794ce4e4..721408bb37 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit ec794ce4e4693069d3b3ebf7a88ead5ff2f860e0 +Subproject commit 721408bb37317e7a66cd34655e068369c47912ac