From 754b8d4ce8bdf931a1168cce7442a7b244b67212 Mon Sep 17 00:00:00 2001 From: RedFoxIV <38788538+RedFoxIV@users.noreply.github.com> Date: Mon, 9 Dec 2024 01:49:49 +0300 Subject: [PATCH 01/23] Shitspawner Extraordinare --- .../EventItemDispenserSystem.cs | 163 ++++++++++++++++++ .../Event/EventEntityDispenserComponent.cs | 78 +++++++++ .../Event/SharedEventEntityDispenserSystem.cs | 14 ++ .../Structures/Misc/EventDispensers.yml | 69 ++++++++ .../Misc/EventItemDispenser.rsi/base.png | Bin 0 -> 236 bytes .../Misc/EventItemDispenser.rsi/meta.json | 13 ++ 6 files changed, 337 insertions(+) create mode 100644 Content.Server/_White/Event/EventEntityDispenser/EventItemDispenserSystem.cs create mode 100644 Content.Shared/_White/Event/EventEntityDispenserComponent.cs create mode 100644 Content.Shared/_White/Event/SharedEventEntityDispenserSystem.cs create mode 100644 Resources/Prototypes/_White/Entities/Structures/Misc/EventDispensers.yml create mode 100644 Resources/Textures/_White/Structures/Misc/EventItemDispenser.rsi/base.png create mode 100644 Resources/Textures/_White/Structures/Misc/EventItemDispenser.rsi/meta.json diff --git a/Content.Server/_White/Event/EventEntityDispenser/EventItemDispenserSystem.cs b/Content.Server/_White/Event/EventEntityDispenser/EventItemDispenserSystem.cs new file mode 100644 index 0000000000..2befbcbb58 --- /dev/null +++ b/Content.Server/_White/Event/EventEntityDispenser/EventItemDispenserSystem.cs @@ -0,0 +1,163 @@ +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Interaction; +using Robust.Shared.Utility; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Content.Shared._White.Event; +using Robust.Shared.Audio.Systems; +using Content.Server.Store.Systems; +using Content.Server.Administration.Managers; +using Content.Server.GameTicking; +using Content.Shared.Popups; + +namespace Content.Server._White.Event; +public class EventItemDispenserSystem : SharedEventItemDispenserSystem +{ + [Dependency] private readonly SharedHandsSystem _hands = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly ILocalizationManager _loc = default!; + + + + public override void Initialize() + { + SubscribeLocalEvent(OnRemove); + SubscribeLocalEvent(OnInteractUsing); + SubscribeLocalEvent(OnInteractHand); + + // todo add ComponentStart handler to EventDispensedComponent to properly dispose of items spawning with filled storage + // * add a List var to EventDispensedComponent tracking all slaved items + // * handle dropping stuff from storage that is not slaved, in case it's not handled by default (i think it's not) + + // todo examine for per-player limits? + // * maybe adding a List variable to EventItemDispenserComponent for strictly client-side usage? + // >will require server to confirm the item count (otherwise items outside pvs will desync the counter one way or another) + // * keep examine server-sided + // >can examine tooltip be "postponed" until receiving relevant info? + + + } + private void OnRemove(EntityUid uid, EventItemDispenserComponent comp, ComponentRemove args) + { + foreach(var items in comp.dispensedItems.Values) + { + foreach (var item in items) + { + if (!TerminatingOrDeleted(item)) // do i have to? + { + QueueDel(item); // no fancy effects + } + } + } + } + private void OnInteractUsing(EntityUid uid, EventItemDispenserComponent comp, InteractUsingEvent args) + { + if(MetaData(args.User).EntityPrototype?.ID == GameTicker.AdminObserverPrototypeName) + { + _popup.PopupEntity("TODO aghost configuring UI", uid, args.User); // todo ditto + return; + } + if( comp.CanManuallyDispose && + TryComp(args.Used, out var dispensed) && + dispensed.ItemOwner == args.User && + dispensed.Dispenser == comp.Owner) + { + QueueDel(args.Used); + _audio.PlayPvs(comp.ManualDisposeSound, uid); + comp.dispensedItemsAmount[dispensed.ItemOwner] -= 1; + } + } + + private void OnInteractHand(EntityUid uid, EventItemDispenserComponent comp, ref InteractHandEvent args) + { + EntityUid user = args.User; + + if (MetaData(user).EntityPrototype?.ID == GameTicker.AdminObserverPrototypeName) + { + _popup.PopupEntity("TODO aghost configuring UI", uid, args.User); // todo ditto + return; + } + List items = comp.dispensedItems.GetOrNew(user).Where(item => !TerminatingOrDeleted(item)).ToList(); + comp.dispensedItems[user] = items; + comp.dispensedItemsAmount.TryGetValue(user, out int allTimeAmount); + + if (comp.Limit > 0) + { + if (!comp.AutoDispose && items.Count >= comp.Limit) + { + _audio.PlayPvs(comp.FailSound, uid); + _popup.PopupEntity(_loc.GetString("event-item-dispenser-limit-reached"), uid, args.User); + return; + } + + if (!comp.Infinite && allTimeAmount >= comp.Limit) + { + _audio.PlayPvs(comp.FailSound, uid); + _popup.PopupEntity(_loc.GetString("event-item-dispenser-out-of-stock"), uid, args.User); + return; + } + DeleteExcess(user, comp); + } + Dispense(user, comp); + } + + /// + /// Deletes items over limit. Actually, because of the ">=" in the while loop, deletes items over excess plus one. This (somewhat) makes sense + /// because this method is called immediately before dispensing a new item, if item limit is set. + /// + private void DeleteExcess(EntityUid owner, EventItemDispenserComponent comp) + { + var items = comp.dispensedItems[owner]; + int itemAmount = items.Count; + while (itemAmount >= comp.Limit) // in case limit was substantially decreased at some point. + { + var toDelete = items.First(); + if (comp.ReplaceDisposedItems) + { + var mapPos = _transform.ToMapCoordinates(new Robust.Shared.Map.EntityCoordinates(toDelete, default)); + Spawn(comp.DisposedReplacement, mapPos); + } + QueueDel(toDelete); + itemAmount--; + // items.Remove(toDelete); // who cares, invalid EntityUids will be pruned in the beginning of OnActivate anyways + } + } + + + /// + /// This hot mess does a lot of things at once: + /// * Spawn and configure the item + /// * Raise ItemPurchasedEvent on the item just in case + /// * Add and configure EventDispensedComponent, used for easier manual disposals (see ) + /// * Keep track on how many items there are + /// * Put said item into user's hands + /// * Play a sound + /// + /// In short: this is stupid and ugly. TODO fix this. + /// + /// + /// + private void Dispense(EntityUid user, EventItemDispenserComponent comp) + { + var item = Spawn(comp.DispensingPrototype, new Robust.Shared.Map.EntityCoordinates(user, default)); // am i retarded? + var ev = new ItemPurchasedEvent(user); + RaiseLocalEvent(item, ref ev); // erectin' a vendomat + var dispensedComp = AddComp(item); + dispensedComp.Dispenser = comp.Owner; + dispensedComp.ItemOwner = user; + + comp.dispensedItems[user].Add(item); + + comp.dispensedItemsAmount.TryGetValue(user, out int amount); + comp.dispensedItemsAmount[user] = amount + 1; + + _hands.TryPickup(user, item); + _audio.PlayPvs(comp.DispenseSound, comp.Owner); + } +} + diff --git a/Content.Shared/_White/Event/EventEntityDispenserComponent.cs b/Content.Shared/_White/Event/EventEntityDispenserComponent.cs new file mode 100644 index 0000000000..082a1a4787 --- /dev/null +++ b/Content.Shared/_White/Event/EventEntityDispenserComponent.cs @@ -0,0 +1,78 @@ +using Robust.Shared.Audio; +using Robust.Shared.Prototypes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Content.Shared._White.Event; +[RegisterComponent, AutoGenerateComponentState] +public sealed partial class EventItemDispenserComponent : Component +{ + + [DataField("dispensing"), AutoNetworkedField] + public EntProtoId DispensingPrototype; + + + [DataField] + public bool CanManuallyDispose = true; + + [DataField] + public bool AutoDispose = true; + + [DataField] + public bool Infinite = true; + + [DataField] + public int Limit = 3; + + [DataField] + public bool AutoCleanUp = true; + + + [DataField] + public SoundSpecifier DispenseSound = new SoundPathSpecifier("/Audio/Machines/machine_vend.ogg"); + [DataField] + public SoundSpecifier FailSound = new SoundPathSpecifier("/Audio/Machines/custom_deny.ogg"); + [DataField] + public SoundSpecifier ManualDisposeSound = new SoundCollectionSpecifier("trashBagRustle"); + + + [DataField] + public bool ReplaceDisposedItems = true; + [DataField] + public EntProtoId DisposedReplacement = "EffectTeslaSparksSilent"; + + + /// + /// Stores Lists with all (currently existing) items. + /// Owners' Uids used as keys. + /// + public Dictionary> dispensedItems = new(); + /// + /// Stores the amount of items spawned by each person in this dispenser's lifetime. + /// Owners' Uids used as keys. + /// + public Dictionary dispensedItemsAmount = new(); + +} + + +/// +/// Stores relevant info about who dispensed this item to avoid having to look for it in the dispensedItems dict. +/// +[RegisterComponent] +public sealed partial class EventDispensedComponent : Component +{ + /// + /// The person who took the item. + /// + [ViewVariables] + public EntityUid ItemOwner; + /// + /// The dispenser which dispensed (duh) the item. + /// + [ViewVariables] + public EntityUid Dispenser; +} diff --git a/Content.Shared/_White/Event/SharedEventEntityDispenserSystem.cs b/Content.Shared/_White/Event/SharedEventEntityDispenserSystem.cs new file mode 100644 index 0000000000..08dd68ff24 --- /dev/null +++ b/Content.Shared/_White/Event/SharedEventEntityDispenserSystem.cs @@ -0,0 +1,14 @@ +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Interaction; +using Robust.Shared.Utility; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Content.Shared._White.Event; +public abstract class SharedEventItemDispenserSystem : EntitySystem {} +// Spawning logic is handled server-side, anything cosmetic is clientside. +// Got nothing to stuff in here. + diff --git a/Resources/Prototypes/_White/Entities/Structures/Misc/EventDispensers.yml b/Resources/Prototypes/_White/Entities/Structures/Misc/EventDispensers.yml new file mode 100644 index 0000000000..cfaba31a3d --- /dev/null +++ b/Resources/Prototypes/_White/Entities/Structures/Misc/EventDispensers.yml @@ -0,0 +1,69 @@ +- type: entity + abstract: true + parent: BaseStructure + id: BaseEventItemDispenser + components: + - type: Sprite + sprite: _White/Structures/Misc/EventItemDispenser.rsi + layers: + - state: base + map: ["placeholderBase"] + - type: InteractionOutline + - type: Anchorable + delay: 2 + - type: Physics + bodyType: Static + - type: Transform + noRot: true + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.45,-0.45,0.45,0.45" + density: 190 + mask: + - MachineMask + layer: + - MachineLayer + - type: EventItemDispenser + dispensing: FoodBanana + +- type: entity + name: Раздатчик приколов + parent: BaseEventItemDispenser + id: EventExGrenadeDispenser + components: + - type: EventItemDispenser + dispensing: GrenadeDummy + +- type: entity + name: Ограниченный Раздатчик приколов + parent: BaseEventItemDispenser + id: BaseEventItemDispenserFinite + components: + - type: EventItemDispenser + dispensing: GrenadeDummy + limit: 2 + infinite: false + + +- type: entity + id: EffectTeslaSparksSilent + categories: [ HideSpawnMenu ] + components: + - type: TimedDespawn + lifetime: 0.5 + - type: Sprite + drawdepth: Effects + noRot: true + layers: + - shader: unshaded + map: ["enum.EffectLayers.Unshaded"] + sprite: Effects/atmospherics.rsi + state: frezon_old + - type: EffectVisuals + - type: Tag + tags: + - HideContextMenu + - type: AnimationPlayer \ No newline at end of file diff --git a/Resources/Textures/_White/Structures/Misc/EventItemDispenser.rsi/base.png b/Resources/Textures/_White/Structures/Misc/EventItemDispenser.rsi/base.png new file mode 100644 index 0000000000000000000000000000000000000000..6f8a30dc4cc4b27719de76fc2f6ba3780d491131 GIT binary patch literal 236 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJ&7LlfAr*6y6C_v{Cy4Yk1sZU< z9n_FWIN;H@?B9wd{|&xPHP?UesJJ{=`KFDUmrniX-|WpS`x#>NZk~N@Z)O`(%CMEu zlh5J1El+?Dm$N#{gj#=&2|`@Va!e7Mc@6iu`Z4nWfxcaxLWbnFoG!(N^F<}zg^V?Q zj^!*~A_0es*x0VbH1b`z%pRE8Aac61kR^zopr09~k07ytkO literal 0 HcmV?d00001 diff --git a/Resources/Textures/_White/Structures/Misc/EventItemDispenser.rsi/meta.json b/Resources/Textures/_White/Structures/Misc/EventItemDispenser.rsi/meta.json new file mode 100644 index 0000000000..eff4a0f0d0 --- /dev/null +++ b/Resources/Textures/_White/Structures/Misc/EventItemDispenser.rsi/meta.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "WWDP", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "base" + }] +} \ No newline at end of file From c62b7bf0738a3d3527338f5047fac63500910c27 Mon Sep 17 00:00:00 2001 From: RedFoxIV <38788538+RedFoxIV@users.noreply.github.com> Date: Tue, 10 Dec 2024 03:21:30 +0300 Subject: [PATCH 02/23] Shitcoder Extraordinare --- Content.Client/Content.Client.csproj | 8 + ...ntItemDispenserConfigBoundUserInterface.cs | 180 ++++++++++++++++++ .../Event/EventItemDispenserConfigWindow.xaml | 12 ++ .../EventItemDispenserConfigWindow.xaml.cs | 31 +++ .../EventItemDispenserSystem.cs | 104 ++++++++-- .../UserInterface/ActivatableUIComponent.cs | 4 +- .../Event/EventEntityDispenserComponent.cs | 66 +++++-- .../Structures/Misc/EventDispensers.yml | 11 ++ 8 files changed, 391 insertions(+), 25 deletions(-) create mode 100644 Content.Client/_White/Event/EventItemDispenserConfigBoundUserInterface.cs create mode 100644 Content.Client/_White/Event/EventItemDispenserConfigWindow.xaml create mode 100644 Content.Client/_White/Event/EventItemDispenserConfigWindow.xaml.cs diff --git a/Content.Client/Content.Client.csproj b/Content.Client/Content.Client.csproj index 956f2fd035..436fe28d2a 100644 --- a/Content.Client/Content.Client.csproj +++ b/Content.Client/Content.Client.csproj @@ -28,4 +28,12 @@ + + + + + + MSBuild:Compile + + diff --git a/Content.Client/_White/Event/EventItemDispenserConfigBoundUserInterface.cs b/Content.Client/_White/Event/EventItemDispenserConfigBoundUserInterface.cs new file mode 100644 index 0000000000..79ed2c34eb --- /dev/null +++ b/Content.Client/_White/Event/EventItemDispenserConfigBoundUserInterface.cs @@ -0,0 +1,180 @@ +using Content.Client.UserInterface.Controls; +using Content.Shared._White.Event; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Shared.Prototypes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Content.Client._White.Event; + + +/// +/// Hopefully i never have to touch UI ever again. +/// Even if xaml thing was working for me, this would only be marginally less of a radioactive dump. +/// +public sealed class EventItemDispenserConfigBoundUserInterface : BoundUserInterface +{ + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly IEntityManager _entMan = default!; + + //EventItemDispenserConfigWindow? window; // Trying to work with robustengine's ui system makes me want to quote AM. + DefaultWindow? window; + EventItemDispenserComponent dispenserComp; + public EventItemDispenserConfigBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) { + IoCManager.InjectDependencies(this); + dispenserComp = _entMan.GetComponent(Owner); + } + + BoxContainer? baseBox; + BoxContainer? optionBox; + BoxContainer? buttonBox; + + LineEdit? DispensingPrototypeLineEdit; + bool DispensingPrototypeValid = false; + CheckBox? AutoDisposeCheckBox; + CheckBox? CanManuallyDisposeCheckBox; + CheckBox? InfiniteCheckBox; + LineEdit? LimitLineEdit; + + CheckBox? ReplaceDisposedItemsCheckBox; + LineEdit? DisposedReplacementLineEdit; + bool DisposedReplacementPrototypeValid = false; + + CheckBox? AutoCleanUpCheckBox; + + Button? copyButton; + Button? pasteButton; + Button? confirmButton; + + //Button cancelButton = new(); // just hit the "x" 4head + + private void InitializeControls(DefaultWindow window) // windows forms ahh method + { + baseBox = this.CreateDisposableControl(); + baseBox.Orientation = BoxContainer.LayoutOrientation.Vertical; + baseBox.SeparationOverride = 4; + baseBox.Margin = new Thickness(4, 0); + baseBox.MinWidth = 450; + window.Contents.AddChild(baseBox); + + optionBox = this.CreateDisposableControl(); + optionBox.Orientation = BoxContainer.LayoutOrientation.Vertical; + baseBox.AddChild(optionBox); + + buttonBox = this.CreateDisposableControl(); + buttonBox.Orientation = BoxContainer.LayoutOrientation.Horizontal; + buttonBox.Align = BoxContainer.AlignMode.End; + baseBox.AddChild(buttonBox); + + confirmButton = this.CreateDisposableControl