diff --git a/Content.Server/Power/EntitySystems/StaticPowerSystem.cs b/Content.Server/Power/EntitySystems/StaticPowerSystem.cs index 61a23e501df..8a3e8119545 100644 --- a/Content.Server/Power/EntitySystems/StaticPowerSystem.cs +++ b/Content.Server/Power/EntitySystems/StaticPowerSystem.cs @@ -6,11 +6,53 @@ public static class StaticPowerSystem { // Using this makes the call shorter. // ReSharper disable once UnusedParameter.Global - public static bool IsPowered(this EntitySystem system, EntityUid uid, IEntityManager entManager, ApcPowerReceiverComponent? receiver = null) + public static bool IsPowered(this EntitySystem system, + EntityUid uid, + IEntityManager entManager, + ApcPowerReceiverComponent? receiver = null) { if (receiver == null && !entManager.TryGetComponent(uid, out receiver)) return true; return receiver.Powered; } + + public static void Toggle(this EntitySystem system, IEntityManager entManager, Entity ent) + where T : IComponent + { + if (IsPowered(system, ent.Owner, entManager)) + Disable(system, entManager, ent); + else + Enable(system, entManager, ent); + } + + public static void Disable(this EntitySystem system, IEntityManager entManager, Entity ent) where T : IComponent + { + if (entManager.TryGetComponent(ent, out var receiver)) + Disable(entManager, ent, receiver); + } + + /// + /// Forces power to be disabled. + /// + public static void Disable(IEntityManager entManager, EntityUid uid, ApcPowerReceiverComponent? receiver = null) + { + if (receiver == null && !entManager.TryGetComponent(uid, out receiver)) + return; + receiver.PowerDisabled = true; + } + + /// + /// Lifts the enforced power disable, if there is any. + /// + public static void Enable( + this EntitySystem system, + IEntityManager entManager, + EntityUid uid, + ApcPowerReceiverComponent? receiver = null) + { + if (receiver == null && !entManager.TryGetComponent(uid, out receiver)) + return; + receiver.PowerDisabled = false; + } } diff --git a/Content.Server/Shuttles/Systems/ShuttleConsoleLockSystem.cs b/Content.Server/Shuttles/Systems/ShuttleConsoleLockSystem.cs index 5fca9752f2c..9cbaf773918 100644 --- a/Content.Server/Shuttles/Systems/ShuttleConsoleLockSystem.cs +++ b/Content.Server/Shuttles/Systems/ShuttleConsoleLockSystem.cs @@ -249,9 +249,9 @@ private bool TryUnlockWithVoucher(EntityUid console, EntityUid voucher, ShuttleC var deedFound = false; var query = EntityQueryEnumerator(); - while (query.MoveNext(out var entity, out var deed)) + while (query.MoveNext(out _, out var deed)) { - var deedShuttleId = deed.ShuttleUid.HasValue ? deed.ShuttleUid.Value.ToString() : null; + var deedShuttleId = deed.ShuttleUid?.ToString(); // Check if this deed was purchased with this specific voucher and matches the shuttle ID if (deed.PurchasedWithVoucher && @@ -302,9 +302,9 @@ private bool TryLockWithVoucher(EntityUid console, EntityUid voucher, ShuttleCon var deedFound = false; var query = EntityQueryEnumerator(); - while (query.MoveNext(out var entity, out var deed)) + while (query.MoveNext(out _, out var deed)) { - var deedShuttleId = deed.ShuttleUid.HasValue ? deed.ShuttleUid.Value.ToString() : null; + var deedShuttleId = deed.ShuttleUid?.ToString(); // Check if this deed was purchased with this specific voucher and matches the shuttle ID if (deed.PurchasedWithVoucher && diff --git a/Content.Server/Worldgen/Components/GC/GCAbleObjectComponent.cs b/Content.Server/Worldgen/Components/GC/GCAbleObjectComponent.cs index b6f7ee16846..d1a9eb422d4 100644 --- a/Content.Server/Worldgen/Components/GC/GCAbleObjectComponent.cs +++ b/Content.Server/Worldgen/Components/GC/GCAbleObjectComponent.cs @@ -1,3 +1,4 @@ +using Content.Server._Null.Systems; using Content.Server.Worldgen.Prototypes; using Content.Server.Worldgen.Systems.GC; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; @@ -5,11 +6,11 @@ namespace Content.Server.Worldgen.Components.GC; /// -/// This is used for whether or not a GCable object is "dirty". Firing GCDirtyEvent on the object is the correct way to +/// This is used for whether a GCable object is "dirty". Firing GCDirtyEvent on the object is the correct way to /// set this up. /// [RegisterComponent] -[Access(typeof(GCQueueSystem))] +[Access(typeof(GCQueueSystem), typeof(ClaimantStakeSystem))] public sealed partial class GCAbleObjectComponent : Component { /// diff --git a/Content.Server/Zombies/ZombieSystem.cs b/Content.Server/Zombies/ZombieSystem.cs index 56b60e62e02..c47450b2677 100644 --- a/Content.Server/Zombies/ZombieSystem.cs +++ b/Content.Server/Zombies/ZombieSystem.cs @@ -69,9 +69,6 @@ public override void Initialize() SubscribeLocalEvent(OnPendingMapInit); SubscribeLocalEvent(OnDamageChanged); - - // TODO: Add OnCure event read, or whatever method of curing besides cloning comes up. - // Also a possible list of component *types* could be better, especially if using reflection to handle them. } private void OnBeforeRemoveAnomalyOnDeath(Entity ent, diff --git a/Content.Server/_Null/Components/ClaimantStakeComponent.cs b/Content.Server/_Null/Components/ClaimantStakeComponent.cs new file mode 100644 index 00000000000..973af11d8c9 --- /dev/null +++ b/Content.Server/_Null/Components/ClaimantStakeComponent.cs @@ -0,0 +1,63 @@ +using Content.Server._Null.Systems; +using Content.Shared.Shuttles.Components; +using Robust.Shared.Audio; +using Robust.Shared.Prototypes; + +namespace Content.Server._Null.Components; + +[RegisterComponent, Access(typeof(ClaimantStakeSystem))] +public sealed partial class ClaimantStakeComponent : Component +{ + [ViewVariables(VVAccess.ReadOnly)] + public EntityUid? PlayerOwner { get; set; } + + [ViewVariables(VVAccess.ReadOnly)] + public EntityUid? ClaimedGrid { get; set; } + + [DataField("useSound")] + public SoundSpecifier? SoundUse { get; set; } + + [DataField("awakeSound")] + public SoundSpecifier? SoundAwake { get; set; } + + [DataField("sleepSound")] + public SoundSpecifier? ShutdownSound { get; set; } + + public ClaimantStakeStatus StakeStatus = ClaimantStakeStatus.Offline; + + public const float AwaitBeepSoundTime = 5f; + public float RemainingTime = AwaitBeepSoundTime; + + /// + /// Check to see if this device is receiving power. Can be toggled, but it would be unwise due to possible + /// unpredicted behaviour. + /// + public bool Enabled = false; + + #region Data Retention + + public IFFFlags OldFlags = IFFFlags.None; + public string? OldColorHex = null; + public string? OldGridName = null; + public string? NewColorHex { get; set; } + + #endregion + + [ValidatePrototypeId] + public const string WreckagePrototype = "NFBaseWreckDebris"; + + public const string WreckageRemovalQueueName = "SpaceDebris"; + public const string DefaultUseSound = "/Audio/Effects/metal_crunch.ogg"; + public const string DefaultAwakeSound = "/Audio/Effects/RingtoneNotes/asharp.ogg"; + public const string DefaultShutdownSound = "/Audio/Effects/sparks4.ogg"; + + public static AudioParams DefaultAudioParameters = AudioParams.Default.WithVolume(-3); +} + +public enum ClaimantStakeStatus : byte +{ + Offline, + Warming, + Online, + Declaiming, +} diff --git a/Content.Server/_Null/Systems/ClaimantStakeSystem.cs b/Content.Server/_Null/Systems/ClaimantStakeSystem.cs new file mode 100644 index 00000000000..22983bd1c97 --- /dev/null +++ b/Content.Server/_Null/Systems/ClaimantStakeSystem.cs @@ -0,0 +1,406 @@ +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using Content.Server._Null.Components; +using Content.Server.Explosion.Components; +using Content.Server.Power.Components; +using Content.Server.Power.EntitySystems; +using Content.Server.Worldgen.Components.Debris; +using Content.Server.Worldgen.Components.GC; +using Content.Shared._Null.Systems; +using Content.Shared.Examine; +using Content.Shared.Interaction; +using Content.Shared.Popups; +using Content.Shared.Power; +using Content.Shared.Shuttles.Components; +using Content.Shared.Tiles; +using Robust.Shared.Audio; +using Robust.Shared.Audio.Systems; + +namespace Content.Server._Null.Systems; + +public sealed class ClaimantStakeSystem : SharedClaimantStakeSystem +{ + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly MetaDataSystem _meta = default!; + [Dependency] private readonly INullExtensionSystem _nullExt = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnPowerChanged); + SubscribeLocalEvent(OnComponentInit); + SubscribeLocalEvent(OnComponentStartup); + SubscribeLocalEvent(OnComponentShutdown); + SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(OnActivate); + } + + private void OnComponentStartup(Entity ent, ref ComponentStartup args) + { + // Set initial power state + if (TryComp(ent, out var powerReceiver)) + ent.Comp.Enabled = powerReceiver.Powered; + } + + private void OnActivate(Entity ent, ref ActivateInWorldEvent args) + { + if (args.Handled || !args.Complex) + { + _popup.PopupEntity(Loc.GetString("claimant-stake-too-complex-popup"), ent, PopupType.MediumCaution); + return; + } + + // If the thing is already enabled, and it has no claimed grid, and it CAN be enabled, + // it is reasonable to assume the user wants to claim the grid rather than turn it off. + var canEnable = CanEnable(ent, allowEnabledComp: true); + if (ent.Comp.Enabled && ent.Comp.ClaimedGrid == null && canEnable) + { + BeginHandleClaim(ent, args.User); + args.Handled = true; + return; + } + // From here, it is assuming the user is aiming to toggle the machine rather than establish a claim. + + // Resetting grid claimant in advance. + ent.Comp.ClaimedGrid = null; + ent.Comp.Enabled ^= true; + + if (!ent.Comp.Enabled) + { + Disable(ent); + } + else if (CanEnable(ent)) + { + Enable(ent); + } + args.Handled = true; + } + + public bool CanEnable(Entity ent, bool allowEnabledComp = false) + { + if (IsInvalidWreck(ent, out _, out _)) + return false; + + // If it is actively turning off or on, it is best not to interrupt the Claimant Stake. + // The user can only interact with it only when it's fully on or offline. + if (ent.Comp.StakeStatus is not (ClaimantStakeStatus.Offline or ClaimantStakeStatus.Online)) + return false; + + if (allowEnabledComp == false && ent.Comp.Enabled == false) + return false; + + // If it is receiving power then it should be fine. + if (TryComp(ent, out var powerReceiver)) + { + // Disabling power forcefully happens to screw with power reception. If it's not disabled then it goes to- + // -show that it must be receiving power in order to work. If it is already disabled then who cares, go- + // -enable it. + if (!powerReceiver.PowerDisabled && powerReceiver.Load > powerReceiver.PowerReceived) + { + _popup.PopupEntity(Loc.GetString("claimant-stake-no-power-popup"), ent, PopupType.SmallCaution); + return false; + } + } + + return true; + } + + private void OnPowerChanged(Entity ent, ref PowerChangedEvent args) + { + ent.Comp.Enabled = args.Powered; + if (IsInvalidWreck(ent, out _, out _)) + { + ent.Comp.Enabled = false; + return; + } + + // Device was DISABLED + if (ent.Comp.Enabled == false && ent.Comp.ClaimedGrid != null) + { + BeginHandleClaim(ent, claimant: null); + } + } + + [SuppressMessage("ReSharper", "RedundantJumpStatement")] + private bool IsInvalidWreck(Entity ent, + out EntityUid? gridId, + out MetaDataComponent? outMeta) + { + gridId = null; + outMeta = null; + + // Claimant Stake Anchoring + var xform = Transform(ent); + if (!xform.Anchored) + { + _popup.PopupEntity(Loc.GetString("claimant-stake-unanchored-popup"), ent, PopupType.SmallCaution); + return false; + } + + // If there's no transform or no grid, it's invalid for claiming. + if (!TryComp(ent, out TransformComponent? transform) || transform.GridUid == null) + { + _popup.PopupEntity(Loc.GetString("claimant-stake-invalid-wreck-popup"), ent, PopupType.MediumCaution); + return true; + } + + var currentGrid = transform.GridUid; + + // Require metadata and parents to be present. + if (!TryComp(currentGrid, out MetaDataComponent? metaData) || metaData.EntityPrototype?.Parents == null) + { + _popup.PopupEntity(Loc.GetString("claimant-stake-invalid-wreck-popup"), ent, PopupType.MediumCaution); + return true; + } + + // Ensure the grid's prototype parents include the allowed wreck prototype. + var parents = metaData.EntityPrototype.Parents.ToList(); + if (!parents.Any(prototype => prototype.Equals(ClaimantStakeComponent.WreckagePrototype))) + { + _popup.PopupEntity(Loc.GetString("claimant-stake-invalid-wreck-popup"), ent, PopupType.MediumCaution); + return true; + } + + // If it's already the same claimed grid, it's invalid to claim. + if (currentGrid.Value == ent.Comp.ClaimedGrid) + { + _popup.PopupEntity(Loc.GetString("claimant-stake-repeat-claim-popup"), ent, PopupType.MediumCaution); + return true; + } + + // If there are other similar stakes on the same grid, declaring is forbidden. + if (_nullExt.SimilarEntitiesArePresentOnGrid(ent)) + { + _popup.PopupEntity(Loc.GetString("claimant-stake-alone-popup"), ent, PopupType.Medium); + return true; + } + + gridId = currentGrid; + outMeta = metaData; + return false; // Not invalid + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var claimantStake)) + { + switch (claimantStake.StakeStatus) + { + case ClaimantStakeStatus.Warming: + TickTimer(frameTime, (uid, claimantStake)); + break; + case ClaimantStakeStatus.Declaiming: + TickTimer(frameTime, (uid, claimantStake), isDeclaiming: true); + break; + } + } + } + + // Keep this accepting Entity to match other methods that use that wrapper. + private void TickTimer(float frameTime, Entity ent, bool isDeclaiming = false) + { + ent.Comp.RemainingTime -= frameTime; + if (ent.Comp.RemainingTime >= 0) + return; + + var stakeLocation = Transform(ent).Coordinates; + ent.Comp.RemainingTime = ClaimantStakeComponent.AwaitBeepSoundTime; + string popup; + SoundSpecifier? playSound; + if (isDeclaiming) + { + ent.Comp.StakeStatus = ClaimantStakeStatus.Offline; + DeclaimGrid(ent); + + popup = Loc.GetString("claimant-stake-claim-erased-popup"); + playSound = ent.Comp.ShutdownSound; + this.Disable(EntityManager, ent); + } + else + { + ent.Comp.StakeStatus = ClaimantStakeStatus.Online; + ClaimGrid(ent); + + popup = Loc.GetString("claimant-stake-claim-success"); + playSound = ent.Comp.SoundAwake; + } + + _audio.PlayPvs(playSound, stakeLocation); + _popup.PopupEntity(popup, ent, PopupType.Large); + } + + private void OnComponentInit(Entity ent, ref ComponentInit args) + { + EnsureComp(ent); + + ent.Comp.SoundUse ??= + new SoundPathSpecifier( + ClaimantStakeComponent.DefaultUseSound, + ClaimantStakeComponent.DefaultAudioParameters); + ent.Comp.SoundAwake ??= + new SoundPathSpecifier( + ClaimantStakeComponent.DefaultAwakeSound, + ClaimantStakeComponent.DefaultAudioParameters); + ent.Comp.ShutdownSound ??= + new SoundPathSpecifier( + ClaimantStakeComponent.DefaultShutdownSound, + ClaimantStakeComponent.DefaultAudioParameters); + + _popup.PopupEntity(Loc.GetString("claimant-stake-prompt-user-popup"), ent); +//Dirty(ent); + } + + private void Enable(Entity ent) + { + this.Enable(EntityManager, ent); + _popup.PopupEntity(Loc.GetString("claimant-stake-activated-popup"), ent, PopupType.Medium); + } + + private void Disable(Entity ent) + { + BeginHandleClaim(ent, claimant: null); + } + + #region Grid Claiming & Declaiming + + /// + /// Handles the INITIALIZATION of claiming and un-claiming of wreckage. Ticker handles the rest. + /// + /// The claimant stake itself. + /// If the claimant is null, this acts as a toggle to declaim a wreck. + private void BeginHandleClaim(Entity ent, EntityUid? claimant = null) + { + // If it is actively turning off or on, it is best not to interrupt the Claimant Stake. + // The user can only interact with it only when it's fully on or offline. + if (ent.Comp.StakeStatus is not (ClaimantStakeStatus.Offline or ClaimantStakeStatus.Online)) + return; + + var stakeLocation = Transform(ent.Owner).Coordinates; + ent.Comp.PlayerOwner = claimant; + SoundSpecifier? playSound; + if (claimant != null) + { + _popup.PopupEntity(Loc.GetString("claimant-stake-claim-pending"), ent, PopupType.Medium); + playSound = ent.Comp.SoundUse; + ent.Comp.StakeStatus = ClaimantStakeStatus.Warming; + } + else + { + _popup.PopupEntity(Loc.GetString("claimant-stake-shutdown-popup"), ent, PopupType.Medium); + playSound = ent.Comp.ShutdownSound; + ent.Comp.StakeStatus = ClaimantStakeStatus.Declaiming; + } + + _audio.PlayPvs(playSound, stakeLocation); + } + + private void ClaimGrid(Entity ent, bool removeWreckComponents = true) + { + if (IsInvalidWreck(ent, out var currentGrid, out var metaData)) + { + this.Disable(EntityManager, ent); + BeginHandleClaim(ent, claimant: null); // Declaim the grid. + return; + } + + ent.Comp.ClaimedGrid = currentGrid; + + #region Changes to Grid + + ent.Comp.OldGridName = metaData?.EntityName; + _meta.SetEntityName(currentGrid!.Value, + Loc.GetString("claimant-wreckage-name", ("user", GetClaimantName(ent.Comp.PlayerOwner))), + metaData); + + if (TryComp(currentGrid, out IFFComponent? iff)) + { + ent.Comp.OldColorHex = iff.Color.ToHexNoAlpha(); + ent.Comp.OldFlags = iff.Flags; + if (!string.IsNullOrEmpty(ent.Comp.NewColorHex)) + { + iff.SetColor(ent.Comp.NewColorHex); + } + + iff.Flags = IFFFlags.IsPlayerShuttle; + } + + if (removeWreckComponents) + { + RemComp(currentGrid.Value); + RemComp(currentGrid.Value); + RemComp(currentGrid.Value); + } + + #endregion + } + + private void DeclaimGrid(Entity ent, bool ensureWreckComponents = true) + { + ent.Comp.PlayerOwner = null; + ent.Comp.ClaimedGrid = null; + ent.Comp.StakeStatus = ClaimantStakeStatus.Offline; + + if (!TryComp(ent, out TransformComponent? transform) || transform.GridUid == null) + return; + + var currentGrid = transform.GridUid; + if (TryComp(currentGrid, out MetaDataComponent? metaData) && metaData.EntityPrototype?.Parents != null) + { + if (!string.IsNullOrEmpty(ent.Comp.OldGridName)) + { + _meta.SetEntityName(currentGrid.Value, ent.Comp.OldGridName, metaData); + } + } + + if (TryComp(currentGrid, out IFFComponent? iff)) + { + if (!string.IsNullOrEmpty(ent.Comp.OldColorHex)) + { + iff.SetColor(ent.Comp.OldColorHex); + } + + iff.Flags = ent.Comp.OldFlags; + } + + if (!ensureWreckComponents) + return; + + EnsureComp(currentGrid.Value); + EnsureComp(currentGrid.Value); + var comp = EnsureComp(currentGrid.Value); + comp.Queue = ClaimantStakeComponent.WreckageRemovalQueueName; + } + + #endregion + + private void OnComponentShutdown(Entity ent, ref ComponentShutdown args) + { + DeclaimGrid(ent, false); + } + + private string GetClaimantName(EntityUid? uid) + { + var isValidId = uid != null; + return !isValidId + ? Loc.GetString("claimant-stake-default-user") + : TryComp(uid, out var meta) + ? meta.EntityName + : Loc.GetString("claimant-stake-default-user"); + } + + private void OnExamined(Entity ent, ref ExaminedEvent args) + { + StringBuilder stringBuilder = new(); + var hasPlayerClaimant = ent.Comp.PlayerOwner != null; + var playerName = GetClaimantName(ent.Comp.PlayerOwner); + stringBuilder.Append(Loc.GetString("claimant-stake-grid-claimant-examine", ("claimant", hasPlayerClaimant))); + stringBuilder.Append(' '); + stringBuilder.AppendLine(Loc.GetString("claimant-stake-examined-user", ("user", playerName))); + args.PushMarkup(stringBuilder.ToString()); + } +} diff --git a/Content.Server/_Null/Systems/EmancipationGridSystem.cs b/Content.Server/_Null/Systems/EmancipationGridSystem.cs index 7c24caa5086..a8391520439 100644 --- a/Content.Server/_Null/Systems/EmancipationGridSystem.cs +++ b/Content.Server/_Null/Systems/EmancipationGridSystem.cs @@ -3,7 +3,6 @@ using Content.Server.Construction; using Content.Server.Explosion.Components; using Content.Server.Materials; -using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Shared.Examine; using Content.Shared.Interaction; @@ -14,6 +13,8 @@ using Robust.Shared.Audio.Systems; using Robust.Shared.Physics.Components; +#pragma warning disable CS0618 // Type or member is obsolete + namespace Content.Server._Null.Systems; public sealed class EmancipationGridSystem : EntitySystem @@ -25,6 +26,7 @@ public sealed class EmancipationGridSystem : EntitySystem [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly MaterialStorageSystem _material = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly INullExtensionSystem _nullExt = default!; public override void Initialize() { @@ -66,23 +68,6 @@ private void OnComponentShutdown(Entity ent, ref Comp ent.Comp.EmancipatedGrid = null; } - private bool AreOtherEmancipationGridsPresent(Entity device) - { - // Checking to ensure there aren't other emancipation grids, else it will simply not work! - var emancipationGrids = AllEntityQuery(); - while (emancipationGrids.MoveNext(out var otherDeviceComponent)) - { - if (otherDeviceComponent == device.Comp) - continue; - if (otherDeviceComponent.EmancipatedGrid != Transform(device).GridUid) - continue; // Short-Circuit - _popup.PopupEntity(Loc.GetString("emancipation-grid-alone-popup"), device, PopupType.Medium); - return true; - } - - return false; - } - private void OnComponentInit(Entity device, ref ComponentInit args) { EnsureComp(device); @@ -95,25 +80,31 @@ private void OnComponentInit(Entity device, ref Compo new SoundPathSpecifier(EmancipationGridComponent.DefaultOutputSound, EmancipationGridComponent.DefaultAudioParameters); - if (AreOtherEmancipationGridsPresent(device)) + if (_nullExt.SimilarEntitiesArePresentOnGrid(device)) { - Stop(device); + _popup.PopupEntity(Loc.GetString("emancipation-grid-alone-popup"), device, PopupType.Medium); + this.Disable(EntityManager, device); + device.Comp.EmancipatedGrid = null; return; } - Start(device); + this.Enable(EntityManager, device); + device.Comp.EmancipatedGrid = Transform(device).GridUid; // Set current grid } private void HandlePowerChange(Entity device, ref PowerChangedEvent args) { device.Comp.IsPowered = this.IsPowered(device.Owner, EntityManager); - if (AreOtherEmancipationGridsPresent(device) || !device.Comp.IsPowered) + if (_nullExt.SimilarEntitiesArePresentOnGrid(device) || !device.Comp.IsPowered) { - Stop(device); + _popup.PopupEntity(Loc.GetString("emancipation-grid-alone-popup"), device, PopupType.Medium); + this.Disable(EntityManager, device); + device.Comp.EmancipatedGrid = null; return; } - Start(device); + this.Enable(EntityManager, device); + device.Comp.EmancipatedGrid = Transform(device).GridUid; // Set current grid } private void HandleItemDeletion(EmancipationGridComponent emancipationComponent, EntityUid entityToDelete) @@ -154,9 +145,10 @@ private void OnActivate(Entity ent, ref ActivateInWor return; // Doesn't matter whether it is powered or not, if there are other Emancipation Grids, this turns-off NOW. - if (AreOtherEmancipationGridsPresent(ent)) + if (_nullExt.SimilarEntitiesArePresentOnGrid(ent)) { - Stop(ent); + _popup.PopupEntity(Loc.GetString("emancipation-grid-alone-popup"), ent, PopupType.Medium); + this.Disable(EntityManager, ent); args.Handled = true; return; } @@ -164,7 +156,8 @@ private void OnActivate(Entity ent, ref ActivateInWor // if the device isn't powered, simply turn it on. if (!ent.Comp.IsPowered) { - Start(ent); + this.Enable(EntityManager, ent); + ent.Comp.EmancipatedGrid = Transform(ent).GridUid; // Set current grid args.Handled = true; return; } @@ -174,7 +167,8 @@ private void OnActivate(Entity ent, ref ActivateInWor var actualYield = (int)ent.Comp.CurrentExpectedYield; // Can only have an integer of biomass, for comparisons. if (actualYield == 0) // If it has nothing, then clearly, we must turn this thing off. { - Stop(ent); + this.Disable(EntityManager, ent); + ent.Comp.EmancipatedGrid = null; args.Handled = true; return; } @@ -192,28 +186,6 @@ private void OnActivate(Entity ent, ref ActivateInWor args.Handled = true; } - /// - /// Boots the Emancipation grid and assigns the Grid ID to its component. - /// - /// - private void Start(Entity device) - { - device.Comp.EmancipatedGrid = Transform(device).GridUid; // Set current grid - if (TryComp(device, out var power)) - power.PowerDisabled = false; - } - - /// - /// Effectively shuts down the Emancipation Grid. - /// - /// - private void Stop(Entity device) - { - device.Comp.EmancipatedGrid = null; - if (TryComp(device, out var power)) - power.PowerDisabled = true; - } - #endregion [SuppressMessage("ReSharper", "RedundantJumpStatement")] diff --git a/Content.Server/_Null/Systems/NullExtensionSystem.cs b/Content.Server/_Null/Systems/NullExtensionSystem.cs new file mode 100644 index 00000000000..cfe9be2382d --- /dev/null +++ b/Content.Server/_Null/Systems/NullExtensionSystem.cs @@ -0,0 +1,44 @@ +using System.Diagnostics.CodeAnalysis; +using Robust.Shared.Prototypes; + +#pragma warning disable CS0618 // Type or member is obsolete + +namespace Content.Server._Null.Systems; + +[SuppressMessage("ReSharper", "InconsistentNaming")] +public interface INullExtensionSystem +{ + bool SimilarEntitiesArePresentOnGrid(Entity ent) where T : IComponent; + EntProtoId? GetProtoID(EntityUid ent); +} + +[SuppressMessage("Usage", "RA0030:Consider using the non-generic variant of this method")] +public sealed partial class NullExtensionSystem : EntitySystem, INullExtensionSystem +{ + public override void Initialize() + { + base.Initialize(); + IoCManager.Register(); + } + + public bool SimilarEntitiesArePresentOnGrid(Entity ent) where T : IComponent + { + var query = AllEntityQuery(); + + while (query.MoveNext(out var otherComponent)) + { + if (otherComponent.Owner.Equals(ent.Owner)) // If they are the same entity, skip. + continue; + + if (Transform(otherComponent.Owner).GridUid == Transform(ent.Owner).GridUid) + return true; // Both entities differ, and both share the same grid. + } + + return false; + } + + public EntProtoId? GetProtoID(EntityUid ent) + { + return !TryComp(ent, out var metaData) ? null : metaData.EntityPrototype?.ID; + } +} diff --git a/Content.Shared/Shuttles/Components/IFFComponent.cs b/Content.Shared/Shuttles/Components/IFFComponent.cs index d88cdfa92e5..5dc98924534 100644 --- a/Content.Shared/Shuttles/Components/IFFComponent.cs +++ b/Content.Shared/Shuttles/Components/IFFComponent.cs @@ -1,3 +1,5 @@ +using System.Diagnostics.CodeAnalysis; +using Content.Shared._Null.Systems; using Content.Shared.Shuttles.Systems; using Robust.Shared.GameStates; @@ -7,7 +9,8 @@ namespace Content.Shared.Shuttles.Components; /// Handles what a grid should look like on radar. /// [RegisterComponent, NetworkedComponent, AutoGenerateComponentState] -[Access(typeof(SharedShuttleSystem))] +[Access(typeof(SharedShuttleSystem), typeof(SharedClaimantStakeSystem))] +[SuppressMessage("ReSharper", "InconsistentNaming")] public sealed partial class IFFComponent : Component { public static readonly Color SelfColor = Color.MediumSpringGreen; @@ -26,9 +29,27 @@ public sealed partial class IFFComponent : Component [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField] public Color Color = IFFColor; + [SuppressMessage("ReSharper", "InconsistentNaming")] + public void SetColor(string HEX) + { + // Add # to ensure the hex conversion works. + if (!HEX.StartsWith('#')) + { + HEX = '#' + HEX; + } + // Accounts for alpha values. Alpha is maximized. + if (HEX.Length <= 7) + { + HEX += "FF"; + } + var color = Color.FromHex(HEX); + SetColor(color); + } + public void SetColor(Color color) => Color = color; + // Frontier: POI IFF protection /// - /// Whether or not this entity's IFF can be changed. + /// Whether this entity's IFF can be changed. /// [ViewVariables(VVAccess.ReadWrite), DataField(serverOnly: true)] public bool ReadOnly; @@ -36,6 +57,7 @@ public sealed partial class IFFComponent : Component } [Flags] +[SuppressMessage("ReSharper", "InconsistentNaming")] public enum IFFFlags : byte { None = 0, diff --git a/Content.Shared/_Null/Systems/SharedClaimantStakeSystem.cs b/Content.Shared/_Null/Systems/SharedClaimantStakeSystem.cs new file mode 100644 index 00000000000..21c991cd111 --- /dev/null +++ b/Content.Shared/_Null/Systems/SharedClaimantStakeSystem.cs @@ -0,0 +1,3 @@ +namespace Content.Shared._Null.Systems; + +public abstract class SharedClaimantStakeSystem : EntitySystem; diff --git a/Content.Shared/_Shitmed/Surgery/SharedSurgerySystem.Steps.cs b/Content.Shared/_Shitmed/Surgery/SharedSurgerySystem.Steps.cs index 49d9334f1aa..d0807d60b6e 100644 --- a/Content.Shared/_Shitmed/Surgery/SharedSurgerySystem.Steps.cs +++ b/Content.Shared/_Shitmed/Surgery/SharedSurgerySystem.Steps.cs @@ -27,6 +27,7 @@ using Content.Shared.Item; using Content.Shared.Popups; using Robust.Shared.Prototypes; + //using Content.Shared.Mood; namespace Content.Shared._Shitmed.Medical.Surgery; @@ -246,14 +247,6 @@ private void OnToolCanPerform(Entity ent, ref SurgeryCanPe } } - private EntProtoId? GetProtoId(EntityUid entityUid) - { - if (!TryComp(entityUid, out var metaData)) - return null; - - return metaData.EntityPrototype?.ID; - } - // I wonder if theres not a function that can do this already. private bool HasDamageGroup(EntityUid entity, string[] group, out DamageableComponent? damageable) { diff --git a/Resources/Locale/en-US/_Null/machines/claimant_stake.ftl b/Resources/Locale/en-US/_Null/machines/claimant_stake.ftl new file mode 100644 index 00000000000..ab934c1d695 --- /dev/null +++ b/Resources/Locale/en-US/_Null/machines/claimant_stake.ftl @@ -0,0 +1,20 @@ +claimant-stake-default-user = nobody identifiable +claimant-stake-alone-popup = Only one claimant stake allowed per hull! +claimant-stake-invalid-wreck-popup = This is not a valid wreckage! +claimant-stake-no-power-popup = There is not enough power available! +claimant-stake-unanchored-popup = Claimant stake must be anchored! +claimant-stake-shutdown-popup = Claimant stake shutting down! +claimant-stake-claim-erased-popup = Claim successfully erased! +claimant-stake-claim-pending = Claiming wreck... +claimant-stake-claim-success = Wreck successfully claimed! Enjoy your new home! +claimant-stake-prompt-user-popup = Activate claimant stake to enable it! +claimant-stake-too-complex-popup = You can't seem to interact with this! +claimant-stake-activated-popup = Claimant stake activated! +claimant-stake-repeat-claim-popup = This wreckage already has been claimed! +claimant-stake-grid-claiming = Only one claimant stake allowed per hull! +claimant-stake-grid-claimant-examine = This hull has {$claimant -> +*[false] [color=red]no claimants[/color] +[true] [color=green]a claimant[/color] +}. +claimant-stake-examined-user = It belongs to {$user}! +claimant-wreckage-name = Wreckage Claim of {$user} diff --git a/Resources/Locale/en-US/_Null/machines.ftl b/Resources/Locale/en-US/_Null/machines/emancipation_grid.ftl similarity index 100% rename from Resources/Locale/en-US/_Null/machines.ftl rename to Resources/Locale/en-US/_Null/machines/emancipation_grid.ftl diff --git a/Resources/Locale/en-US/_Null/holopads.ftl b/Resources/Locale/en-US/_Null/machines/holopads.ftl similarity index 100% rename from Resources/Locale/en-US/_Null/holopads.ftl rename to Resources/Locale/en-US/_Null/machines/holopads.ftl diff --git a/Resources/Prototypes/_NF/Catalog/VendingMachines/Inventories/flatpackvend.yml b/Resources/Prototypes/_NF/Catalog/VendingMachines/Inventories/flatpackvend.yml index 0cdec47c938..747d02f327f 100644 --- a/Resources/Prototypes/_NF/Catalog/VendingMachines/Inventories/flatpackvend.yml +++ b/Resources/Prototypes/_NF/Catalog/VendingMachines/Inventories/flatpackvend.yml @@ -31,4 +31,5 @@ AirlockGlassShuttleFlatpack: 4294967295 # Infinite ComputerWithdrawBankATMFlatpack: 4294967295 # Infinite TelevisionFlatpack: 4294967295 # Infinite - GaslockFlatpack: 4 # Null Sector - This is a brand new implementation that has some pushback. This is finite to give the benefit of the doubt, but may be made infinite later if it proves to be a fair addition. + GaslockFlatpack: 4 # Null Sector - This is a new implementation with some pushback. This is finite to give the benefit of the doubt, but may be made infinite later if it proves to be a fair addition. + ClaimantStakeFlatpack: 3 # Null Sector diff --git a/Resources/Prototypes/_Null/Entities/Objects/Devices/flatpack.yml b/Resources/Prototypes/_Null/Entities/Objects/Devices/flatpack.yml index ac0b8660be8..61d412fa0f4 100644 --- a/Resources/Prototypes/_Null/Entities/Objects/Devices/flatpack.yml +++ b/Resources/Prototypes/_Null/Entities/Objects/Devices/flatpack.yml @@ -24,4 +24,14 @@ entity: Gaslock - type: Sprite layers: - - state: command_airlock_directional \ No newline at end of file + - state: command_airlock_directional + +# Claimant Stake Flatpack +- type: entity + parent: BaseFlatpack + id: ClaimantStakeFlatpack + name: claimant stake flatpack + description: A flatpack used for constructing a wreckage claimant stake. + components: + - type: Flatpack + entity: ClaimantStake \ No newline at end of file diff --git a/Resources/Prototypes/_Null/Entities/Structures/Machines/claimant_stake.yml b/Resources/Prototypes/_Null/Entities/Structures/Machines/claimant_stake.yml new file mode 100644 index 00000000000..df3617d8ecc --- /dev/null +++ b/Resources/Prototypes/_Null/Entities/Structures/Machines/claimant_stake.yml @@ -0,0 +1,56 @@ +- type: entity + id: ClaimantStake + parent: BaseMachinePowered + name: wreckage claimant stake + description: An efficient piece of technology that allows one to claim a wreckage for their own. + placement: + mode: SnapgridCenter + components: + - type: Transform + anchored: true + - type: Physics + bodyType: Static + - type: Sprite + sprite: _Null/Structures/Machines/claimant_stake.rsi + snapCardinals: true + layers: + - state: base + - state: on + map: ["enum.PowerDeviceVisualLayers.Powered"] + visible: false + shader: unshaded + - type: ApcPowerReceiver + powerLoad: 2000 + - type: PointLight + enabled: false + color: "#88B0D1" # orange + radius: 2.0 + energy: 2 + - type: LitOnPowered + - type: ExtensionCableReceiver + - type: Appearance + - type: AmbientOnPowered + - type: AmbientSound + volume: -9 + range: 5 + sound: + path: /Audio/Ambience/Objects/anomaly_generator.ogg + - type: GenericVisualizer + visuals: + enum.PowerDeviceVisuals.Powered: + enum.PowerDeviceVisualLayers.Powered: + True: { visible: true } + False: { visible: false } + # - type: Machine + # board: EmancipationGridCircuitboard + # - type: Construction + # graph: Machine + # node: machine + # containers: + # - machine_board + # - machine_parts + # - type: ContainerContainer + # containers: + # machine_board: !type:Container + # machine_parts: !type:Container + - type: ClaimantStake diff --git a/Resources/Textures/_Null/Structures/Machines/claimant_stake.rsi/base.png b/Resources/Textures/_Null/Structures/Machines/claimant_stake.rsi/base.png new file mode 100644 index 00000000000..80b0fce2c90 Binary files /dev/null and b/Resources/Textures/_Null/Structures/Machines/claimant_stake.rsi/base.png differ diff --git a/Resources/Textures/_Null/Structures/Machines/claimant_stake.rsi/meta.json b/Resources/Textures/_Null/Structures/Machines/claimant_stake.rsi/meta.json new file mode 100644 index 00000000000..b9b0becb79c --- /dev/null +++ b/Resources/Textures/_Null/Structures/Machines/claimant_stake.rsi/meta.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Made by LukeZurg22", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "base" + }, + { + "name": "on", + "delays": [[0.4, 0.4, 0.4, 0.4]] + } + ] +} diff --git a/Resources/Textures/_Null/Structures/Machines/claimant_stake.rsi/on.png b/Resources/Textures/_Null/Structures/Machines/claimant_stake.rsi/on.png new file mode 100644 index 00000000000..310d561a54c Binary files /dev/null and b/Resources/Textures/_Null/Structures/Machines/claimant_stake.rsi/on.png differ