diff --git a/Content.Client/_White/PolymorphableCanister/PolymorphableCanisterSystem.cs b/Content.Client/_White/PolymorphableCanister/PolymorphableCanisterSystem.cs new file mode 100644 index 0000000000..b71a122c71 --- /dev/null +++ b/Content.Client/_White/PolymorphableCanister/PolymorphableCanisterSystem.cs @@ -0,0 +1,36 @@ +using Content.Shared._White.PolymorphableCanister; +using Robust.Client.GameObjects; +using Robust.Shared.Prototypes; + +namespace Content.Client._White.PolymorphableCanister; + +public sealed class PolymorphableCanisterSystem : SharedPolymorphableCanisterSystem +{ + [Dependency] private readonly IComponentFactory _componentFactory = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(HandleState); + } + + private void HandleState(EntityUid uid, + PolymorphableCanisterComponent component, + ref AfterAutoHandleStateEvent args) + { + UpdateAppearance(uid, component.CurrentPrototype); + } + + protected override void UpdateSprite(EntityUid uid, EntityPrototype proto) + { + base.UpdateSprite(uid, proto); + + if (!TryComp(uid, out SpriteComponent? sprite) || + !proto.TryGetComponent(out SpriteComponent? otherSprite, _componentFactory)) + { + return; + } + + sprite.CopyFrom(otherSprite); + } +} diff --git a/Content.Client/_White/PolymorphableCanister/UI/PolymorphableCanisterBUI.cs b/Content.Client/_White/PolymorphableCanister/UI/PolymorphableCanisterBUI.cs new file mode 100644 index 0000000000..65ac68ec24 --- /dev/null +++ b/Content.Client/_White/PolymorphableCanister/UI/PolymorphableCanisterBUI.cs @@ -0,0 +1,37 @@ +using Content.Shared._White.PolymorphableCanister; +using JetBrains.Annotations; + +namespace Content.Client._White.PolymorphableCanister.UI; + +[UsedImplicitly] +// ReSharper disable once InconsistentNaming +public sealed class PolymorphableCanisterBUI : BoundUserInterface +{ + private PolymorphableCanisterMenu? _menu; + + public PolymorphableCanisterBUI(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + IoCManager.InjectDependencies(this); + } + + protected override void Open() + { + _menu = new PolymorphableCanisterMenu(Owner, this); + _menu.OnClose += Close; + _menu.OpenCentered(); + } + + public void SendMessage(string protoId) + { + SendMessage(new PolymorphableCanisterMessage(protoId)); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (!disposing) + return; + + _menu?.Dispose(); + } +} diff --git a/Content.Client/_White/PolymorphableCanister/UI/PolymorphableCanisterMenu.xaml b/Content.Client/_White/PolymorphableCanister/UI/PolymorphableCanisterMenu.xaml new file mode 100644 index 0000000000..4aa72be7ce --- /dev/null +++ b/Content.Client/_White/PolymorphableCanister/UI/PolymorphableCanisterMenu.xaml @@ -0,0 +1,12 @@ + + + + + diff --git a/Content.Client/_White/PolymorphableCanister/UI/PolymorphableCanisterMenu.xaml.cs b/Content.Client/_White/PolymorphableCanister/UI/PolymorphableCanisterMenu.xaml.cs new file mode 100644 index 0000000000..dfbbbf7551 --- /dev/null +++ b/Content.Client/_White/PolymorphableCanister/UI/PolymorphableCanisterMenu.xaml.cs @@ -0,0 +1,57 @@ +using System.Numerics; +using Content.Client.UserInterface.Controls; +using Content.Shared._White.PolymorphableCanister; +using Robust.Client.AutoGenerated; +using Robust.Client.GameObjects; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Prototypes; + +namespace Content.Client._White.PolymorphableCanister.UI; + +[GenerateTypedNameReferences] +public sealed partial class PolymorphableCanisterMenu : RadialMenu +{ + [Dependency] private readonly EntityManager _entManager = default!; + [Dependency] private readonly IPrototypeManager _protoManager = default!; + + public PolymorphableCanisterMenu(EntityUid owner, PolymorphableCanisterBUI bui) + { + IoCManager.InjectDependencies(this); + RobustXamlLoader.Load(this); + + if (!_entManager.TryGetComponent(owner, out var canister)) + return; + + var spriteSystem = _entManager.System(); + + var main = FindControl("Main"); + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (main is null) + return; + + foreach (var protoId in canister.Prototypes) + { + if (canister.CurrentPrototype == protoId) + continue; + + if (!_protoManager.TryIndex(protoId, out var proto)) + continue; + + var button = new RadialMenuTextureButton + { + ToolTip = Loc.GetString(proto.Name), + TextureNormal = spriteSystem.GetPrototypeIcon(protoId).Default, + StyleClasses = { "RadialMenuButton" }, + SetSize = new Vector2(64f, 64f) + }; + + button.OnButtonUp += _ => + { + bui.SendMessage(protoId); + Close(); + }; + + main.AddChild(button); + } + } +} \ No newline at end of file diff --git a/Content.Server/_White/PolymorphableCanister/PolymorphableCanisterSystem.cs b/Content.Server/_White/PolymorphableCanister/PolymorphableCanisterSystem.cs new file mode 100644 index 0000000000..14b8ed5332 --- /dev/null +++ b/Content.Server/_White/PolymorphableCanister/PolymorphableCanisterSystem.cs @@ -0,0 +1,53 @@ +using Content.Server.Atmos.Piping.Unary.Components; +using Content.Shared._White.PolymorphableCanister; +using Content.Shared.Lock; +using Content.Shared.Verbs; +using Robust.Server.GameObjects; +using Robust.Shared.Player; +using Robust.Shared.Utility; + +namespace Content.Server._White.PolymorphableCanister; + +public sealed class PolymorphableCanisterSystem : SharedPolymorphableCanisterSystem +{ + [Dependency] private readonly UserInterfaceSystem _ui = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent>(GetVerb); + } + + private void GetVerb(EntityUid uid, PolymorphableCanisterComponent component, GetVerbsEvent args) + { + if (TryComp(uid, out LockComponent? lockComponent) && lockComponent.Locked) + { + return; + } + + if (TryComp(uid, out GasCanisterComponent? gasCanister) && gasCanister.Air.Pressure > 100) + { + return; + } + + var changeAppearanceVerb = new Verb + { + Text = Loc.GetString("polymorphable-canister-change-appearance-verb"), + Icon = new SpriteSpecifier.Rsi(new ResPath("Structures/Storage/canister.rsi"), "yellow"), + Act = () => TryOpenUi(uid, args.User, component) + }; + + args.Verbs.Add(changeAppearanceVerb); + } + + private void TryOpenUi(EntityUid uid, EntityUid user, PolymorphableCanisterComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + if (!TryComp(user, out ActorComponent? actor)) + return; + + _ui.TryToggleUi(uid, PolymorphableCanisterUiKey.Key, actor.PlayerSession); + } +} \ No newline at end of file diff --git a/Content.Shared/_White/PolymorphableCanister/PolymorphableCanisterComponent.cs b/Content.Shared/_White/PolymorphableCanister/PolymorphableCanisterComponent.cs new file mode 100644 index 0000000000..ee37dc54be --- /dev/null +++ b/Content.Shared/_White/PolymorphableCanister/PolymorphableCanisterComponent.cs @@ -0,0 +1,46 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Shared._White.PolymorphableCanister; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)] +public sealed partial class PolymorphableCanisterComponent : Component +{ + [DataField] + public ResPath ResPath = new("Structures/Storage/canister.rsi"); + + [DataField, AutoNetworkedField] + public ProtoId? CurrentPrototype; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public int DoAfterTime = 3; + + [DataField] + public List> Prototypes = new() + { + "GasCanister", + "StorageCanister", + "AirCanister", + "OxygenCanister", + "NitrogenCanister", + "CarbonDioxideCanister", + "PlasmaCanister", + "TritiumCanister", + "WaterVaporCanister", + "AmmoniaCanister", + "NitrousOxideCanister", + "FrezonCanister", + "BZCanister", + "PluoxiumCanister", + "HydrogenCanister", + "NitriumCanister", + "HealiumCanister", + "HyperNobliumCanister", + "ProtoNitrateCanister", + "ZaukerCanister", + "HalonCanister", + "HeliumCanister", + "AntiNobliumCanister", + }; +} diff --git a/Content.Shared/_White/PolymorphableCanister/SharedPolymorphableCanisterSystem.cs b/Content.Shared/_White/PolymorphableCanister/SharedPolymorphableCanisterSystem.cs new file mode 100644 index 0000000000..617ec35876 --- /dev/null +++ b/Content.Shared/_White/PolymorphableCanister/SharedPolymorphableCanisterSystem.cs @@ -0,0 +1,106 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared._White.PolymorphableCanister; + +public abstract class SharedPolymorphableCanisterSystem : EntitySystem +{ + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly MetaDataSystem _metaData = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnMessage); + SubscribeLocalEvent(OnDoAfter); + } + + private void OnInit(Entity ent, ref ComponentInit args) + { + var proto = MetaData(ent.Owner).EntityPrototype; + if (proto is null) + { + return; + } + + ent.Comp.CurrentPrototype = proto.ID; + Dirty(ent); + } + + private void OnMessage(Entity ent, ref PolymorphableCanisterMessage args) + { + if (!args.Session.AttachedEntity.HasValue) + { + return; + } + + var doAfterArgs = new DoAfterArgs(EntityManager, + args.Session.AttachedEntity.Value, + ent.Comp.DoAfterTime, + new PolymorphableCanisterDoAfterEvent(args.ProtoId), + ent.Owner + ) + { + BreakOnMove = true, + NeedHand = true + }; + + _doAfter.TryStartDoAfter(doAfterArgs); + } + + private void OnDoAfter(Entity ent, ref PolymorphableCanisterDoAfterEvent args) + { + if (args.Cancelled) + { + return; + } + + ent.Comp.CurrentPrototype = args.ProtoId; + UpdateAppearance(ent, args.ProtoId); + Dirty(ent); + } + + public void UpdateAppearance(EntityUid uid, ProtoId? protoId) + { + if (string.IsNullOrEmpty(protoId) || !_proto.TryIndex(protoId, out var proto)) + { + return; + } + + var metadata = MetaData(uid); + _metaData.SetEntityName(uid, proto.Name, metadata); + _metaData.SetEntityDescription(uid, proto.Description, metadata); + + UpdateSprite(uid, proto); + } + + protected virtual void UpdateSprite(EntityUid uid, EntityPrototype proto) + { + } +} + +[NetSerializable, Serializable] +public enum PolymorphableCanisterUiKey : byte +{ + Key +} + +[Serializable, NetSerializable] +public sealed class PolymorphableCanisterMessage(string protoId) : BoundUserInterfaceMessage +{ + public readonly ProtoId ProtoId = protoId; +} + +[Serializable, NetSerializable] +public sealed partial class PolymorphableCanisterDoAfterEvent : SimpleDoAfterEvent +{ + public readonly ProtoId ProtoId; + + public PolymorphableCanisterDoAfterEvent(string protoId) + { + ProtoId = protoId; + } +} \ No newline at end of file diff --git a/Resources/Locale/en-US/_white/polymorphable-canister/polymorphable-canister.ftl b/Resources/Locale/en-US/_white/polymorphable-canister/polymorphable-canister.ftl new file mode 100644 index 0000000000..7f644d6e7e --- /dev/null +++ b/Resources/Locale/en-US/_white/polymorphable-canister/polymorphable-canister.ftl @@ -0,0 +1 @@ +polymorphable-canister-change-appearance-verb = Change appearance diff --git a/Resources/Locale/ru-RU/_white/polymorphable-canister/polymorphable-canister.ftl b/Resources/Locale/ru-RU/_white/polymorphable-canister/polymorphable-canister.ftl new file mode 100644 index 0000000000..edcd77f2b9 --- /dev/null +++ b/Resources/Locale/ru-RU/_white/polymorphable-canister/polymorphable-canister.ftl @@ -0,0 +1 @@ +polymorphable-canister-change-appearance-verb = Перекрасить diff --git a/Resources/Prototypes/Entities/Structures/Storage/Canisters/gas_canisters.yml b/Resources/Prototypes/Entities/Structures/Storage/Canisters/gas_canisters.yml index bf16d318f9..4b0b0e9b29 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Canisters/gas_canisters.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Canisters/gas_canisters.yml @@ -34,10 +34,12 @@ 1: { state: can-o1, shader: "unshaded" } 2: { state: can-o2, shader: "unshaded" } 3: { state: can-o3, shader: "unshaded" } - - type: UserInterface + - type: UserInterface # WhiteDream - Added PolymorphableCanisterUiKey interfaces: - key: enum.GasCanisterUiKey.Key type: GasCanisterBoundUserInterface + - key: enum.PolymorphableCanisterUiKey.Key + type: PolymorphableCanisterBUI - type: Destructible thresholds: - trigger: @@ -104,6 +106,7 @@ access: [["Atmospherics"], ["Engineering"], ["Research"]] - type: Lock locked: false + - type: PolymorphableCanister # WhiteDream - type: entity parent: GasCanister