From 69bbeb8a96c0f1f84ba7945509ca6f62edded85d Mon Sep 17 00:00:00 2001 From: deltanedas <@deltanedas:kde.org> Date: Sun, 22 Dec 2024 12:08:05 +0000 Subject: [PATCH 1/9] add conscription bag --- .../Fills/Items/Backpacks/duffelbag.yml | 18 ++++++++++++++++++ .../Objects/Specific/Salvage/ore_bag.yml | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 Resources/Prototypes/DeltaV/Catalog/Fills/Items/Backpacks/duffelbag.yml diff --git a/Resources/Prototypes/DeltaV/Catalog/Fills/Items/Backpacks/duffelbag.yml b/Resources/Prototypes/DeltaV/Catalog/Fills/Items/Backpacks/duffelbag.yml new file mode 100644 index 00000000000..eb0684b396f --- /dev/null +++ b/Resources/Prototypes/DeltaV/Catalog/Fills/Items/Backpacks/duffelbag.yml @@ -0,0 +1,18 @@ +- type: entity + parent: ClothingBackpackDuffelSalvage + id: ClothingBackpackDuffelSalvageConscription + name: mining conscription kit + description: A duffel bag containing everything a crewmember needs to support a shaft miner in the field. + components: + - type: StorageFill + contents: + - id: ClothingEyesGlassesMeson + - id: MineralScanner + - id: OreBag + - id: ClothingUniformJumpsuitSalvageSpecialist + - id: EncryptionKeyCargo + - id: ClothingMaskGasExplorer + - id: SalvageIDCard + - id: WeaponProtoKineticAccelerator + - id: SurvivalKnife + - id: FlashlightSeclite diff --git a/Resources/Prototypes/Entities/Objects/Specific/Salvage/ore_bag.yml b/Resources/Prototypes/Entities/Objects/Specific/Salvage/ore_bag.yml index e73f9d7ba47..37773c4c778 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Salvage/ore_bag.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Salvage/ore_bag.yml @@ -14,7 +14,7 @@ slots: - belt - type: Item - size: Ginormous + size: Huge # DeltaV: Was Ginormous, lets it fit in conscription bag - type: Storage maxItemSize: Normal grid: From 479e9ae9e95f937fc7fef3d5ee4ec4cca413ae58 Mon Sep 17 00:00:00 2001 From: deltanedas <@deltanedas:kde.org> Date: Sun, 22 Dec 2024 12:08:25 +0000 Subject: [PATCH 2/9] add gar mesons --- .../Prototypes/DeltaV/Entities/Clothing/Eyes/glasses.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Resources/Prototypes/DeltaV/Entities/Clothing/Eyes/glasses.yml b/Resources/Prototypes/DeltaV/Entities/Clothing/Eyes/glasses.yml index 52c7a6f3821..4c4c0f66660 100644 --- a/Resources/Prototypes/DeltaV/Entities/Clothing/Eyes/glasses.yml +++ b/Resources/Prototypes/DeltaV/Entities/Clothing/Eyes/glasses.yml @@ -83,3 +83,11 @@ sprite: DeltaV/Clothing/Eyes/Glasses/presccorpsglasses.rsi - type: Clothing sprite: DeltaV/Clothing/Eyes/Glasses/presccorpsglasses.rsi + +- type: entity + parent: ClothingEyesGlassesGar + id: ClothingEyesGlassesGarMeson + name: gar mesons + description: Do the impossible, see the invisible! + components: + - type: EyeProtection From 0a8176931c9fcffa2664e831d94c962c20460812 Mon Sep 17 00:00:00 2001 From: deltanedas <@deltanedas:kde.org> Date: Sun, 22 Dec 2024 13:02:32 +0000 Subject: [PATCH 3/9] remove salvage vendor restock --- Resources/Migrations/deltaMigrations.yml | 3 +++ .../Prototypes/Catalog/Cargo/cargo_vending.yml | 18 +++++++++--------- .../Catalog/Fills/Crates/vending.yml | 18 +++++++++--------- .../Service/vending_machine_restock.yml | 1 + 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/Resources/Migrations/deltaMigrations.yml b/Resources/Migrations/deltaMigrations.yml index bab6685466a..590fb4215b5 100644 --- a/Resources/Migrations/deltaMigrations.yml +++ b/Resources/Migrations/deltaMigrations.yml @@ -123,3 +123,6 @@ VendingMachineAutomatrobe: null # 2024-11-08 SuitStorageSec: SuitStorageSecDeltaV + +# 2024-12-17 +VendingMachineRestockSalvageEquipment: null diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_vending.yml b/Resources/Prototypes/Catalog/Cargo/cargo_vending.yml index d43dc856f21..a3263dde32b 100644 --- a/Resources/Prototypes/Catalog/Cargo/cargo_vending.yml +++ b/Resources/Prototypes/Catalog/Cargo/cargo_vending.yml @@ -148,15 +148,15 @@ category: cargoproduct-category-name-service group: market -- type: cargoProduct - id: CrateVendingMachineRestockSalvageEquipment - icon: - sprite: Objects/Specific/Service/vending_machine_restock.rsi - state: base - product: CrateVendingMachineRestockSalvageEquipmentFilled - cost: 1000 - category: cargoproduct-category-name-engineering - group: market +#- type: cargoProduct # DeltaV: Salvage vendor doesn't have stock anymore +# id: CrateVendingMachineRestockSalvageEquipment +# icon: +# sprite: Objects/Specific/Service/vending_machine_restock.rsi +# state: base +# product: CrateVendingMachineRestockSalvageEquipmentFilled +# cost: 1000 +# category: cargoproduct-category-name-engineering +# group: market - type: cargoProduct id: CrateVendingMachineRestockSecTech diff --git a/Resources/Prototypes/Catalog/Fills/Crates/vending.yml b/Resources/Prototypes/Catalog/Fills/Crates/vending.yml index 378f8cb7588..606d538541e 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/vending.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/vending.yml @@ -140,15 +140,15 @@ - id: VendingMachineRestockRobustSoftdrinks amount: 2 -- type: entity - id: CrateVendingMachineRestockSalvageEquipmentFilled - parent: CrateGenericSteel - name: Salvage restock crate - description: Contains a restock box for the salvage vendor. - components: - - type: StorageFill - contents: - - id: VendingMachineRestockSalvageEquipment +#- type: entity # DeltaV: Salvage vendor doesn't have stock anymore +# id: CrateVendingMachineRestockSalvageEquipmentFilled +# parent: CrateGenericSteel +# name: Salvage restock crate +# description: Contains a restock box for the salvage vendor. +# components: +# - type: StorageFill +# contents: +# - id: VendingMachineRestockSalvageEquipment - type: entity id: CrateVendingMachineRestockSecTechFilled diff --git a/Resources/Prototypes/Entities/Objects/Specific/Service/vending_machine_restock.yml b/Resources/Prototypes/Entities/Objects/Specific/Service/vending_machine_restock.yml index c7851aff88d..1254e95fc07 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Service/vending_machine_restock.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Service/vending_machine_restock.yml @@ -361,6 +361,7 @@ - state: refill_sec - type: entity + abstract: true # DeltaV: Salvage vendor doesn't have stock anymore parent: BaseVendingMachineRestock id: VendingMachineRestockSalvageEquipment name: Salvage Vendor restock box From 7ba90d986da97221c0de9432bd86a9c9de513e5d Mon Sep 17 00:00:00 2001 From: deltanedas <@deltanedas:kde.org> Date: Sun, 22 Dec 2024 13:02:53 +0000 Subject: [PATCH 4/9] add code for shop vendors --- .../VendingMachines/ShopVendorSystem.cs | 116 +++++++++++ .../UI/ShopVendorBoundUserInterface.cs | 25 +++ .../VendingMachines/UI/ShopVendorItem.xaml | 13 ++ .../VendingMachines/UI/ShopVendorItem.xaml.cs | 21 ++ .../VendingMachines/UI/ShopVendorWindow.xaml | 24 +++ .../UI/ShopVendorWindow.xaml.cs | 146 ++++++++++++++ .../VendingMachines/ShopVendorSystem.cs | 47 +++++ .../VendingMachines/PointsVendorComponent.cs | 9 + .../VendingMachines/SharedShopVendorSystem.cs | 181 ++++++++++++++++++ .../VendingMachines/ShopInventoryPrototype.cs | 23 +++ .../VendingMachines/ShopVendorComponent.cs | 71 +++++++ .../DeltaV/VendingMachines/ShopVendorUI.cs | 9 + .../deltav/vending-machines/shop-vendor.ftl | 2 + 13 files changed, 687 insertions(+) create mode 100644 Content.Client/DeltaV/VendingMachines/ShopVendorSystem.cs create mode 100644 Content.Client/DeltaV/VendingMachines/UI/ShopVendorBoundUserInterface.cs create mode 100644 Content.Client/DeltaV/VendingMachines/UI/ShopVendorItem.xaml create mode 100644 Content.Client/DeltaV/VendingMachines/UI/ShopVendorItem.xaml.cs create mode 100644 Content.Client/DeltaV/VendingMachines/UI/ShopVendorWindow.xaml create mode 100644 Content.Client/DeltaV/VendingMachines/UI/ShopVendorWindow.xaml.cs create mode 100644 Content.Server/DeltaV/VendingMachines/ShopVendorSystem.cs create mode 100644 Content.Shared/DeltaV/VendingMachines/PointsVendorComponent.cs create mode 100644 Content.Shared/DeltaV/VendingMachines/SharedShopVendorSystem.cs create mode 100644 Content.Shared/DeltaV/VendingMachines/ShopInventoryPrototype.cs create mode 100644 Content.Shared/DeltaV/VendingMachines/ShopVendorComponent.cs create mode 100644 Content.Shared/DeltaV/VendingMachines/ShopVendorUI.cs create mode 100644 Resources/Locale/en-US/deltav/vending-machines/shop-vendor.ftl diff --git a/Content.Client/DeltaV/VendingMachines/ShopVendorSystem.cs b/Content.Client/DeltaV/VendingMachines/ShopVendorSystem.cs new file mode 100644 index 00000000000..860b48df7c4 --- /dev/null +++ b/Content.Client/DeltaV/VendingMachines/ShopVendorSystem.cs @@ -0,0 +1,116 @@ +using Content.Shared.DeltaV.VendingMachines; +using Content.Shared.VendingMachines; +using Robust.Client.Animations; +using Robust.Client.GameObjects; + +namespace Content.Client.DeltaV.VendingMachines; + +public sealed class ShopVendorSystem : SharedShopVendorSystem +{ + [Dependency] private readonly AnimationPlayerSystem _animationPlayer = default!; + [Dependency] private readonly AppearanceSystem _appearance = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnAppearanceChange); + SubscribeLocalEvent(OnAnimationCompleted); + } + + // copied from vending machines because its not reusable in other systems :) + private void OnAnimationCompleted(Entity ent, ref AnimationCompletedEvent args) + { + UpdateAppearance((ent, ent.Comp)); + } + + private void OnAppearanceChange(Entity ent, ref AppearanceChangeEvent args) + { + UpdateAppearance((ent, ent.Comp, args.Sprite)); + } + + private void UpdateAppearance(Entity ent) + { + if (!Resolve(ent, ref ent.Comp2)) + return; + + if (!_appearance.TryGetData(ent, VendingMachineVisuals.VisualState, out var state)) + state = VendingMachineVisualState.Normal; + + var sprite = ent.Comp2; + SetLayerState(VendingMachineVisualLayers.Screen, "screen", sprite); + switch (state) + { + case VendingMachineVisualState.Normal: + SetLayerState(VendingMachineVisualLayers.BaseUnshaded, "normal", sprite); + break; + + case VendingMachineVisualState.Deny: + PlayAnimation(ent, VendingMachineVisualLayers.BaseUnshaded, "deny", ent.Comp1.DenyDelay, sprite); + break; + + case VendingMachineVisualState.Eject: + PlayAnimation(ent, VendingMachineVisualLayers.BaseUnshaded, "eject", ent.Comp1.EjectDelay, sprite); + break; + + case VendingMachineVisualState.Broken: + HideLayers(sprite); + SetLayerState(VendingMachineVisualLayers.Base, "broken", sprite); + break; + + case VendingMachineVisualState.Off: + HideLayers(sprite); + break; + } + } + + private static void SetLayerState(VendingMachineVisualLayers layer, string state, SpriteComponent sprite) + { + sprite.LayerSetVisible(layer, true); + sprite.LayerSetAutoAnimated(layer, true); + sprite.LayerSetState(layer, state); + } + + private void PlayAnimation(EntityUid uid, VendingMachineVisualLayers layer, string state, TimeSpan time, SpriteComponent sprite) + { + if (_animationPlayer.HasRunningAnimation(uid, state)) + return; + + var animation = GetAnimation(layer, state, time); + sprite.LayerSetVisible(layer, true); + _animationPlayer.Play(uid, animation, state); + } + + private static Animation GetAnimation(VendingMachineVisualLayers layer, string state, TimeSpan time) + { + return new Animation + { + Length = time, + AnimationTracks = + { + new AnimationTrackSpriteFlick + { + LayerKey = layer, + KeyFrames = + { + new AnimationTrackSpriteFlick.KeyFrame(state, 0f) + } + } + } + }; + } + + private static void HideLayers(SpriteComponent sprite) + { + HideLayer(VendingMachineVisualLayers.BaseUnshaded, sprite); + HideLayer(VendingMachineVisualLayers.Screen, sprite); + } + + private static void HideLayer(VendingMachineVisualLayers layer, SpriteComponent sprite) + { + if (!sprite.LayerMapTryGet(layer, out var actualLayer)) + return; + + sprite.LayerSetVisible(actualLayer, false); + } +} diff --git a/Content.Client/DeltaV/VendingMachines/UI/ShopVendorBoundUserInterface.cs b/Content.Client/DeltaV/VendingMachines/UI/ShopVendorBoundUserInterface.cs new file mode 100644 index 00000000000..6122aa9ee1e --- /dev/null +++ b/Content.Client/DeltaV/VendingMachines/UI/ShopVendorBoundUserInterface.cs @@ -0,0 +1,25 @@ +using Content.Shared.DeltaV.VendingMachines; +using Robust.Client.UserInterface; + +namespace Content.Client.DeltaV.VendingMachines.UI; + +public sealed class ShopVendorBoundUserInterface : BoundUserInterface +{ + [ViewVariables] + private ShopVendorWindow? _window; + + public ShopVendorBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + } + + protected override void Open() + { + base.Open(); + + _window = this.CreateWindow(); + _window.SetEntity(Owner); + _window.OpenCenteredLeft(); + _window.Title = EntMan.GetComponent(Owner).EntityName; + _window.OnItemSelected += index => SendMessage(new ShopVendorPurchaseMessage(index)); + } +} diff --git a/Content.Client/DeltaV/VendingMachines/UI/ShopVendorItem.xaml b/Content.Client/DeltaV/VendingMachines/UI/ShopVendorItem.xaml new file mode 100644 index 00000000000..4708db20aa9 --- /dev/null +++ b/Content.Client/DeltaV/VendingMachines/UI/ShopVendorItem.xaml @@ -0,0 +1,13 @@ + + + diff --git a/Content.Client/DeltaV/VendingMachines/UI/ShopVendorItem.xaml.cs b/Content.Client/DeltaV/VendingMachines/UI/ShopVendorItem.xaml.cs new file mode 100644 index 00000000000..4a3c9c4efe5 --- /dev/null +++ b/Content.Client/DeltaV/VendingMachines/UI/ShopVendorItem.xaml.cs @@ -0,0 +1,21 @@ +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Prototypes; + +namespace Content.Client.DeltaV.VendingMachines.UI; + +[GenerateTypedNameReferences] +public sealed partial class ShopVendorItem : BoxContainer +{ + public ShopVendorItem(EntProtoId entProto, string text, uint cost) + { + RobustXamlLoader.Load(this); + + ItemPrototype.SetPrototype(entProto); + + NameLabel.Text = text; + + CostLabel.Text = cost.ToString(); + } +} diff --git a/Content.Client/DeltaV/VendingMachines/UI/ShopVendorWindow.xaml b/Content.Client/DeltaV/VendingMachines/UI/ShopVendorWindow.xaml new file mode 100644 index 00000000000..20e0ec45763 --- /dev/null +++ b/Content.Client/DeltaV/VendingMachines/UI/ShopVendorWindow.xaml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + diff --git a/Content.Client/DeltaV/VendingMachines/UI/ShopVendorWindow.xaml.cs b/Content.Client/DeltaV/VendingMachines/UI/ShopVendorWindow.xaml.cs new file mode 100644 index 00000000000..890e9d9ad6c --- /dev/null +++ b/Content.Client/DeltaV/VendingMachines/UI/ShopVendorWindow.xaml.cs @@ -0,0 +1,146 @@ +using Content.Client.UserInterface.Controls; +using Content.Shared.DeltaV.VendingMachines; +using Content.Shared.Stacks; +using Robust.Client.AutoGenerated; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; +using System.Numerics; + +namespace Content.Client.DeltaV.VendingMachines.UI; + +[GenerateTypedNameReferences] +public sealed partial class ShopVendorWindow : FancyWindow +{ + [Dependency] private readonly IComponentFactory _factory = default!; + [Dependency] private readonly IEntityManager _entMan = default!; + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; + private readonly ShopVendorSystem _vendor; + + /// + /// Event fired with the listing index to purchase. + /// + public event Action? OnItemSelected; + + private EntityUid _owner; + private readonly StyleBoxFlat _style = new() { BackgroundColor = new Color(70, 73, 102) }; + private readonly StyleBoxFlat _styleBroke = new() { BackgroundColor = new Color(30, 43, 52) }; + private readonly List _buttons = new(); + private uint _balance = 1; + + public ShopVendorWindow() + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + + _vendor = _entMan.System(); + + VendingContents.SearchBar = SearchBar; + VendingContents.DataFilterCondition += DataFilterCondition; + VendingContents.GenerateItem += GenerateButton; + VendingContents.ItemKeyBindDown += (args, data) => OnItemSelected?.Invoke(((ShopVendorListingData) data).Index); + } + + public void SetEntity(EntityUid owner) + { + _owner = owner; + + if (!_entMan.TryGetComponent(owner, out var comp)) + return; + + var pack = _proto.Index(comp.Pack); + Populate(pack.Listings); + + UpdateBalance(); + } + + private void UpdateBalance(uint balance) + { + if (_balance == balance) + return; + + _balance = balance; + + BalanceLabel.Text = Loc.GetString("shop-vendor-balance", ("points", balance)); + + // disable items that are too expensive to buy + foreach (var button in _buttons) + { + if (button.Data is ShopVendorListingData data) + button.Disabled = data.Cost > balance; + + button.StyleBoxOverride = button.Disabled ? _styleBroke : _style; + } + } + + private void UpdateBalance() + { + if (_player.LocalEntity is {} user) + UpdateBalance(_vendor.GetBalance(_owner, user)); + } + + private bool DataFilterCondition(string filter, ListData data) + { + if (data is not ShopVendorListingData { Text: var text }) + return false; + + if (string.IsNullOrEmpty(filter)) + return true; + + return text.Contains(filter, StringComparison.CurrentCultureIgnoreCase); + } + + private void GenerateButton(ListData data, ListContainerButton button) + { + if (data is not ShopVendorListingData cast) + return; + + _buttons.Add(button); + button.AddChild(new ShopVendorItem(cast.ItemId, cast.Text, cast.Cost)); + + button.ToolTip = cast.Text; + button.StyleBoxOverride = _style; + } + + public void Populate(List listings) + { + var longestEntry = string.Empty; + var listData = new List(); + for (var i = 0; i < listings.Count; i++) + { + var listing = listings[i]; + var proto = _proto.Index(listing.Id); + var text = proto.Name; + if (proto.TryGetComponent(out var stack, _factory) && stack.Count > 1) + { + text += " "; + text += Loc.GetString("shop-vendor-stack-suffix", ("count", stack.Count)); + } + listData.Add(new ShopVendorListingData(i, listing.Id, text, listing.Cost)); + } + + _buttons.Clear(); + VendingContents.PopulateList(listData); + SetSizeAfterUpdate(longestEntry.Length, listings.Count); + } + + private void SetSizeAfterUpdate(int longestEntryLength, int contentCount) + { + SetSize = new Vector2(Math.Clamp((longestEntryLength + 2) * 12, 250, 400), + Math.Clamp(contentCount * 50, 150, 350)); + } + + protected override void FrameUpdate(FrameEventArgs args) + { + base.FrameUpdate(args); + + UpdateBalance(); + } +} + +public record ShopVendorListingData(int Index, EntProtoId ItemId, string Text, uint Cost) : ListData; diff --git a/Content.Server/DeltaV/VendingMachines/ShopVendorSystem.cs b/Content.Server/DeltaV/VendingMachines/ShopVendorSystem.cs new file mode 100644 index 00000000000..d3e99bfcf83 --- /dev/null +++ b/Content.Server/DeltaV/VendingMachines/ShopVendorSystem.cs @@ -0,0 +1,47 @@ +using Content.Server.Advertise; +using Content.Server.Advertise.Components; +using Content.Shared.DeltaV.VendingMachines; + +namespace Content.Server.DeltaV.VendingMachines; + +public sealed class ShopVendorSystem : SharedShopVendorSystem +{ + [Dependency] private readonly SpeakOnUIClosedSystem _speakOnUIClosed = default!; + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + var now = Timing.CurTime; + while (query.MoveNext(out var uid, out var comp, out var xform)) + { + var ent = (uid, comp); + var dirty = false; + if (comp.Ejecting is {} ejecting && now > comp.NextEject) + { + Spawn(ejecting, xform.Coordinates); + comp.Ejecting = null; + dirty = true; + } + + if (comp.Denying && now > comp.NextDeny) + { + comp.Denying = false; + dirty = true; + } + + if (dirty) + { + Dirty(uid, comp); + UpdateVisuals(ent); + } + } + } + + protected override void AfterPurchase(Entity ent) + { + if (TryComp(ent, out var speak)) + _speakOnUIClosed.TrySetFlag((ent.Owner, speak)); + } +} diff --git a/Content.Shared/DeltaV/VendingMachines/PointsVendorComponent.cs b/Content.Shared/DeltaV/VendingMachines/PointsVendorComponent.cs new file mode 100644 index 00000000000..d505215a469 --- /dev/null +++ b/Content.Shared/DeltaV/VendingMachines/PointsVendorComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.DeltaV.VendingMachines; + +/// +/// Makes a use mining points to buy items. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class PointsVendorComponent : Component; diff --git a/Content.Shared/DeltaV/VendingMachines/SharedShopVendorSystem.cs b/Content.Shared/DeltaV/VendingMachines/SharedShopVendorSystem.cs new file mode 100644 index 00000000000..080d782b5d8 --- /dev/null +++ b/Content.Shared/DeltaV/VendingMachines/SharedShopVendorSystem.cs @@ -0,0 +1,181 @@ +using Content.Shared.Access.Systems; +using Content.Shared.DeltaV.Salvage.Systems; +using Content.Shared.Destructible; +using Content.Shared.Popups; +using Content.Shared.Power; +using Content.Shared.Power.EntitySystems; +using Content.Shared.UserInterface; +using Content.Shared.VendingMachines; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; + +namespace Content.Shared.DeltaV.VendingMachines; + +public abstract class SharedShopVendorSystem : EntitySystem +{ + [Dependency] private readonly AccessReaderSystem _access = default!; + [Dependency] private readonly MiningPointsSystem _points = default!; + [Dependency] protected readonly IGameTiming Timing = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedPointLightSystem _light = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedPowerReceiverSystem _power = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnPointsBalance); + SubscribeLocalEvent(OnPointsPurchase); + + SubscribeLocalEvent(OnPowerChanged); + SubscribeLocalEvent(OnBreak); + SubscribeLocalEvent(OnOpenAttempt); + Subs.BuiEvents(VendingMachineUiKey.Key, subs => + { + subs.Event(OnPurchase); + }); + } + + #region Public API + + public uint GetBalance(EntityUid uid, EntityUid user) + { + var ev = new ShopVendorBalanceEvent(user); + RaiseLocalEvent(uid, ref ev); + return ev.Balance; + } + + #endregion + + #region Balance adapters + + private void OnPointsBalance(Entity ent, ref ShopVendorBalanceEvent args) + { + args.Balance = _points.TryFindIdCard(args.User)?.Comp?.Points ?? 0; + } + + private void OnPointsPurchase(Entity ent, ref ShopVendorPurchaseEvent args) + { + if (_points.TryFindIdCard(args.User) is {} idCard && _points.RemovePoints(idCard, args.Cost)) + args.Paid = true; + } + + #endregion + + private void OnPowerChanged(Entity ent, ref PowerChangedEvent args) + { + UpdateVisuals(ent); + } + + private void OnBreak(Entity ent, ref BreakageEventArgs args) + { + ent.Comp.Broken = true; + UpdateVisuals(ent); + } + + private void OnOpenAttempt(Entity ent, ref ActivatableUIOpenAttemptEvent args) + { + if (ent.Comp.Broken) + args.Cancel(); + } + + private void OnPurchase(Entity ent, ref ShopVendorPurchaseMessage args) + { + if (ent.Comp.Ejecting != null || ent.Comp.Broken || !_power.IsPowered(ent.Owner)) + return; + + var pack = _proto.Index(ent.Comp.Pack); + if (args.Index < 0 || args.Index >= pack.Listings.Count) + return; + + var user = args.Actor; + if (!_access.IsAllowed(user, ent)) + { + _popup.PopupEntity(Loc.GetString("vending-machine-component-try-eject-access-denied"), ent); + Deny(ent, user); + return; + } + + var listing = pack.Listings[args.Index]; + var ev = new ShopVendorPurchaseEvent(user, listing.Cost); + RaiseLocalEvent(ent, ref ev); + if (!ev.Paid) + { + Deny(ent, user); + return; + } + + ent.Comp.Ejecting = listing.Id; + ent.Comp.NextEject = Timing.CurTime + ent.Comp.EjectDelay; + Dirty(ent); + + _audio.PlayPredicted(ent.Comp.PurchaseSound, ent, user); + UpdateVisuals(ent); + + Log.Debug($"Player {ToPrettyString(user):user} purchased {listing.Id} from {ToPrettyString(ent):vendor}"); + + AfterPurchase(ent); + } + + protected virtual void AfterPurchase(Entity ent) + { + } + + private void Deny(Entity ent, EntityUid user) + { + if (ent.Comp.Denying) + return; + + ent.Comp.Denying = true; + ent.Comp.NextDeny = Timing.CurTime + ent.Comp.DenyDelay; + Dirty(ent); + + _audio.PlayPredicted(ent.Comp.DenySound, ent, user); + UpdateVisuals(ent); + } + + protected void UpdateVisuals(Entity ent) + { + var state = VendingMachineVisualState.Normal; + var lit = true; + if (ent.Comp.Broken) + { + state = VendingMachineVisualState.Broken; + lit = false; + } + else if (ent.Comp.Ejecting != null) + { + state = VendingMachineVisualState.Eject; + } + else if (ent.Comp.Denying) + { + state = VendingMachineVisualState.Deny; + } + else if (!_power.IsPowered(ent.Owner)) + { + state = VendingMachineVisualState.Off; + lit = true; + } + + _light.SetEnabled(ent, lit); + _appearance.SetData(ent, VendingMachineVisuals.VisualState, state); + } +} + +/// +/// Raised on a shop vendor to get its current balance. +/// A currency component sets Balance to whatever it is. +/// +[ByRefEvent] +public record struct ShopVendorBalanceEvent(EntityUid User, uint Balance = 0); + +/// +/// Raised on a shop vendor when trying to purchase an item. +/// A currency component sets Paid to true if the user successfully paid for it. +/// +[ByRefEvent] +public record struct ShopVendorPurchaseEvent(EntityUid User, uint Cost, bool Paid = false); diff --git a/Content.Shared/DeltaV/VendingMachines/ShopInventoryPrototype.cs b/Content.Shared/DeltaV/VendingMachines/ShopInventoryPrototype.cs new file mode 100644 index 00000000000..3b04c0d0490 --- /dev/null +++ b/Content.Shared/DeltaV/VendingMachines/ShopInventoryPrototype.cs @@ -0,0 +1,23 @@ +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared.DeltaV.VendingMachines; + +/// +/// Similar to VendingMachineInventoryPrototype but for . +/// +[Prototype] +public sealed class ShopInventoryPrototype : IPrototype +{ + [IdDataField] + public string ID { get; private set; } = default!; + + /// + /// The item listings for sale. + /// + [DataField(required: true)] + public List Listings = new(); +} + +[DataRecord, Serializable] +public record struct ShopListing(EntProtoId Id, uint Cost); diff --git a/Content.Shared/DeltaV/VendingMachines/ShopVendorComponent.cs b/Content.Shared/DeltaV/VendingMachines/ShopVendorComponent.cs new file mode 100644 index 00000000000..999f4b05bee --- /dev/null +++ b/Content.Shared/DeltaV/VendingMachines/ShopVendorComponent.cs @@ -0,0 +1,71 @@ +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + +namespace Content.Shared.DeltaV.VendingMachines; + +/// +/// A vending machine that sells items for a currency controlled by events. +/// Does not need restocking. +/// Another component must handle and to work. +/// +[RegisterComponent, NetworkedComponent, Access(typeof(SharedShopVendorSystem))] +[AutoGenerateComponentState, AutoGenerateComponentPause] +public sealed partial class ShopVendorComponent : Component +{ + /// + /// The inventory prototype to sell. + /// + [DataField(required: true)] + public ProtoId Pack; + + [DataField, AutoNetworkedField] + public bool Broken; + + [DataField, AutoNetworkedField] + public bool Denying; + + /// + /// Item being ejected, or null if it isn't. + /// + [DataField, AutoNetworkedField] + public EntProtoId? Ejecting; + + /// + /// How long to wait before flashing denied again. + /// + [DataField] + public TimeSpan DenyDelay = TimeSpan.FromSeconds(2); + + /// + /// How long to wait before another item can be bought + /// + [DataField] + public TimeSpan EjectDelay = TimeSpan.FromSeconds(1.2); + + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] + public TimeSpan NextDeny; + + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] + public TimeSpan NextEject; + + [DataField] + public SoundSpecifier PurchaseSound = new SoundPathSpecifier("/Audio/Machines/machine_vend.ogg") + { + Params = new AudioParams + { + Volume = -4f, + Variation = 0.15f + } + }; + + [DataField] + public SoundSpecifier DenySound = new SoundPathSpecifier("/Audio/Machines/custom_deny.ogg") + { + Params = new AudioParams + { + Volume = -2f + } + }; +} diff --git a/Content.Shared/DeltaV/VendingMachines/ShopVendorUI.cs b/Content.Shared/DeltaV/VendingMachines/ShopVendorUI.cs new file mode 100644 index 00000000000..9f288d9d322 --- /dev/null +++ b/Content.Shared/DeltaV/VendingMachines/ShopVendorUI.cs @@ -0,0 +1,9 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.DeltaV.VendingMachines; + +[Serializable, NetSerializable] +public sealed class ShopVendorPurchaseMessage(int index) : BoundUserInterfaceMessage +{ + public readonly int Index = index; +} diff --git a/Resources/Locale/en-US/deltav/vending-machines/shop-vendor.ftl b/Resources/Locale/en-US/deltav/vending-machines/shop-vendor.ftl new file mode 100644 index 00000000000..ff1aca9688f --- /dev/null +++ b/Resources/Locale/en-US/deltav/vending-machines/shop-vendor.ftl @@ -0,0 +1,2 @@ +shop-vendor-balance = Balance: {$points} +shop-vendor-stack-suffix = x{$count} From dbf8d118dc8a7130bf844c6f2cd23ffd70134666 Mon Sep 17 00:00:00 2001 From: deltanedas <@deltanedas:kde.org> Date: Sun, 22 Dec 2024 13:03:00 +0000 Subject: [PATCH 5/9] make salvage vendor a shop vendor --- .../Inventories/salvage_points.yml | 47 +++++++++++++++++++ Resources/Prototypes/DeltaV/Wires/layouts.yml | 8 ++++ .../Structures/Machines/vending_machines.yml | 23 ++++++--- 3 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 Resources/Prototypes/DeltaV/Catalog/VendingMachines/Inventories/salvage_points.yml diff --git a/Resources/Prototypes/DeltaV/Catalog/VendingMachines/Inventories/salvage_points.yml b/Resources/Prototypes/DeltaV/Catalog/VendingMachines/Inventories/salvage_points.yml new file mode 100644 index 00000000000..35849c9c2dd --- /dev/null +++ b/Resources/Prototypes/DeltaV/Catalog/VendingMachines/Inventories/salvage_points.yml @@ -0,0 +1,47 @@ +- type: shopInventory + id: SalvageVendorInventory + listings: + # TODO: marker beacons 1/10/30 for 10 each + - id: DrinkWhiskeyBottleFull + cost: 100 + - id: DrinkAbsintheBottleFull + cost: 100 + - id: CigarGold + cost: 150 + - id: Soap + cost: 200 + # TODO: laser pointer 300, toy facehugger 300 + # TODO: stabilizing serum for 400 + - id: FultonBeacon + cost: 400 + # TODO: bluespace shelter capsule for 400 + - id: ClothingEyesGlassesGarMeson + cost: 500 + - id: ClothingBeltSalvageWebbing + cost: 500 + - id: MedkitBruteFilled + cost: 600 + - id: MedkitBurnFilled + cost: 600 + # TODO: salvage 5g, 3 implants and a locator for 600 + # TODO: wormhole jaunter for 750 + - id: WeaponCrusher + cost: 750 + - id: WeaponProtoKineticAccelerator + cost: 750 + - id: AdvancedMineralScanner + cost: 800 + # TODO: resonator for 800 + - id: Fulton + cost: 1000 + # TODO: lazarus injector + - id: ClothingBackpackDuffelSalvageConscription + cost: 1500 + - id: SpaceCash1000 + cost: 2000 + # TODO: super resonator for 2500 + # TODO: jump boots for 2500 + # TODO: luxury shelter capsule for 3k + # TODO: luxury elite bar capsule for 10k + # TODO: pka mods + # TODO: mining drone stuff diff --git a/Resources/Prototypes/DeltaV/Wires/layouts.yml b/Resources/Prototypes/DeltaV/Wires/layouts.yml index fd1e77dc898..90d781119a8 100644 --- a/Resources/Prototypes/DeltaV/Wires/layouts.yml +++ b/Resources/Prototypes/DeltaV/Wires/layouts.yml @@ -7,3 +7,11 @@ - !type:DoorBoltLightWireAction - !type:LogWireAction - !type:AiInteractWireAction + +- type: wireLayout + id: ShopVendor + wires: + - !type:AiInteractWireAction + - !type:PowerWireAction + - !type:AccessWireAction + - !type:LogWireAction diff --git a/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml b/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml index 7b88556d195..f0d73114e0b 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml @@ -1356,12 +1356,12 @@ name: Salvage Vendor description: A dwarf's best friend! components: - - type: VendingMachine - pack: SalvageEquipmentInventory - offState: off - brokenState: broken - normalState: normal-unshaded - denyState: deny-unshaded + #- type: VendingMachine # DeltaV: Use mining points instead of limited stock + # pack: SalvageEquipmentInventory + # offState: off + # brokenState: broken + # normalState: normal-unshaded + # denyState: deny-unshaded - type: Sprite sprite: Structures/Machines/VendingMachines/mining.rsi layers: @@ -1376,6 +1376,17 @@ radius: 1.5 energy: 1.6 color: "#b89f25" + - type: ShopVendor # DeltaV + pack: SalvageVendorInventory + - type: PointsVendor # DeltaV + - type: UserInterface # DeltaV: Replace vending machine BUI with shop vendor + interfaces: + enum.VendingMachineUiKey.Key: + type: ShopVendorBoundUserInterface + enum.WiresUiKey.Key: + type: WiresBoundUserInterface + - type: Wires # DeltaV: Use shop vendor wires layout + layoutId: ShopVendor - type: AccessReader access: [["Salvage"]] - type: GuideHelp From 64b819ca6f35cc547a03dbb054d4fa7eaa0b29da Mon Sep 17 00:00:00 2001 From: deltanedas <@deltanedas:kde.org> Date: Sun, 22 Dec 2024 13:20:30 +0000 Subject: [PATCH 6/9] ui fixes --- .../VendingMachines/ShopVendorSystem.cs | 23 +++++++++++------ .../UI/ShopVendorWindow.xaml.cs | 3 ++- .../VendingMachines/SharedShopVendorSystem.cs | 10 ++++---- .../VendingMachines/ShopVendorComponent.cs | 25 +++++++++++++++++++ .../Structures/Machines/vending_machines.yml | 4 +++ 5 files changed, 51 insertions(+), 14 deletions(-) diff --git a/Content.Client/DeltaV/VendingMachines/ShopVendorSystem.cs b/Content.Client/DeltaV/VendingMachines/ShopVendorSystem.cs index 860b48df7c4..3b7f5744421 100644 --- a/Content.Client/DeltaV/VendingMachines/ShopVendorSystem.cs +++ b/Content.Client/DeltaV/VendingMachines/ShopVendorSystem.cs @@ -38,24 +38,28 @@ private void UpdateAppearance(Entity ent) state = VendingMachineVisualState.Normal; var sprite = ent.Comp2; - SetLayerState(VendingMachineVisualLayers.Screen, "screen", sprite); + SetLayerState(VendingMachineVisualLayers.Base, ent.Comp1.OffState, sprite); + SetLayerState(VendingMachineVisualLayers.Screen, ent.Comp1.ScreenState, sprite); switch (state) { case VendingMachineVisualState.Normal: - SetLayerState(VendingMachineVisualLayers.BaseUnshaded, "normal", sprite); + SetLayerState(VendingMachineVisualLayers.BaseUnshaded, ent.Comp1.NormalState, sprite); break; case VendingMachineVisualState.Deny: - PlayAnimation(ent, VendingMachineVisualLayers.BaseUnshaded, "deny", ent.Comp1.DenyDelay, sprite); + if (ent.Comp1.LoopDenyAnimation) + SetLayerState(VendingMachineVisualLayers.BaseUnshaded, ent.Comp1.DenyState, sprite); + else + PlayAnimation(ent, VendingMachineVisualLayers.BaseUnshaded, ent.Comp1.DenyState, ent.Comp1.DenyDelay, sprite); break; case VendingMachineVisualState.Eject: - PlayAnimation(ent, VendingMachineVisualLayers.BaseUnshaded, "eject", ent.Comp1.EjectDelay, sprite); + PlayAnimation(ent, VendingMachineVisualLayers.BaseUnshaded, ent.Comp1.EjectState, ent.Comp1.EjectDelay, sprite); break; case VendingMachineVisualState.Broken: HideLayers(sprite); - SetLayerState(VendingMachineVisualLayers.Base, "broken", sprite); + SetLayerState(VendingMachineVisualLayers.Base, ent.Comp1.BrokenState, sprite); break; case VendingMachineVisualState.Off: @@ -64,16 +68,19 @@ private void UpdateAppearance(Entity ent) } } - private static void SetLayerState(VendingMachineVisualLayers layer, string state, SpriteComponent sprite) + private static void SetLayerState(VendingMachineVisualLayers layer, string? state, SpriteComponent sprite) { + if (state == null) + return; + sprite.LayerSetVisible(layer, true); sprite.LayerSetAutoAnimated(layer, true); sprite.LayerSetState(layer, state); } - private void PlayAnimation(EntityUid uid, VendingMachineVisualLayers layer, string state, TimeSpan time, SpriteComponent sprite) + private void PlayAnimation(EntityUid uid, VendingMachineVisualLayers layer, string? state, TimeSpan time, SpriteComponent sprite) { - if (_animationPlayer.HasRunningAnimation(uid, state)) + if (state == null || _animationPlayer.HasRunningAnimation(uid, state)) return; var animation = GetAnimation(layer, state, time); diff --git a/Content.Client/DeltaV/VendingMachines/UI/ShopVendorWindow.xaml.cs b/Content.Client/DeltaV/VendingMachines/UI/ShopVendorWindow.xaml.cs index 890e9d9ad6c..cca635a8a27 100644 --- a/Content.Client/DeltaV/VendingMachines/UI/ShopVendorWindow.xaml.cs +++ b/Content.Client/DeltaV/VendingMachines/UI/ShopVendorWindow.xaml.cs @@ -104,7 +104,8 @@ private void GenerateButton(ListData data, ListContainerButton button) button.AddChild(new ShopVendorItem(cast.ItemId, cast.Text, cast.Cost)); button.ToolTip = cast.Text; - button.StyleBoxOverride = _style; + button.Disabled = cast.Cost > _balance; + button.StyleBoxOverride = button.Disabled ? _styleBroke : _style; } public void Populate(List listings) diff --git a/Content.Shared/DeltaV/VendingMachines/SharedShopVendorSystem.cs b/Content.Shared/DeltaV/VendingMachines/SharedShopVendorSystem.cs index 080d782b5d8..1383b6057d4 100644 --- a/Content.Shared/DeltaV/VendingMachines/SharedShopVendorSystem.cs +++ b/Content.Shared/DeltaV/VendingMachines/SharedShopVendorSystem.cs @@ -95,8 +95,7 @@ private void OnPurchase(Entity ent, ref ShopVendorPurchaseM var user = args.Actor; if (!_access.IsAllowed(user, ent)) { - _popup.PopupEntity(Loc.GetString("vending-machine-component-try-eject-access-denied"), ent); - Deny(ent, user); + Deny(ent); return; } @@ -105,7 +104,7 @@ private void OnPurchase(Entity ent, ref ShopVendorPurchaseM RaiseLocalEvent(ent, ref ev); if (!ev.Paid) { - Deny(ent, user); + Deny(ent); return; } @@ -125,8 +124,9 @@ protected virtual void AfterPurchase(Entity ent) { } - private void Deny(Entity ent, EntityUid user) + private void Deny(Entity ent) { + _popup.PopupEntity(Loc.GetString("vending-machine-component-try-eject-access-denied"), ent); if (ent.Comp.Denying) return; @@ -134,7 +134,7 @@ private void Deny(Entity ent, EntityUid user) ent.Comp.NextDeny = Timing.CurTime + ent.Comp.DenyDelay; Dirty(ent); - _audio.PlayPredicted(ent.Comp.DenySound, ent, user); + _audio.PlayPvs(ent.Comp.DenySound, ent); UpdateVisuals(ent); } diff --git a/Content.Shared/DeltaV/VendingMachines/ShopVendorComponent.cs b/Content.Shared/DeltaV/VendingMachines/ShopVendorComponent.cs index 999f4b05bee..1de2c5476ed 100644 --- a/Content.Shared/DeltaV/VendingMachines/ShopVendorComponent.cs +++ b/Content.Shared/DeltaV/VendingMachines/ShopVendorComponent.cs @@ -68,4 +68,29 @@ public sealed partial class ShopVendorComponent : Component Volume = -2f } }; + + #region Visuals + + [DataField] + public bool LoopDenyAnimation = true; + + [DataField] + public string? OffState; + + [DataField] + public string? ScreenState; + + [DataField] + public string? NormalState; + + [DataField] + public string? DenyState; + + [DataField] + public string? EjectState; + + [DataField] + public string? BrokenState; + + #endregion } diff --git a/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml b/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml index f0d73114e0b..3ecb065518a 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml @@ -1378,6 +1378,10 @@ color: "#b89f25" - type: ShopVendor # DeltaV pack: SalvageVendorInventory + offState: off + brokenState: broken + normalState: normal-unshaded + denyState: deny-unshaded - type: PointsVendor # DeltaV - type: UserInterface # DeltaV: Replace vending machine BUI with shop vendor interfaces: From 2cf0102298b54e10a1a72df2d2c881cb383294ca Mon Sep 17 00:00:00 2001 From: deltanedas <@deltanedas:kde.org> Date: Sun, 22 Dec 2024 13:30:20 +0000 Subject: [PATCH 7/9] :trollface: --- .../DeltaV/VendingMachines/UI/ShopVendorWindow.xaml | 4 ++-- .../DeltaV/VendingMachines/UI/ShopVendorWindow.xaml.cs | 2 +- .../DeltaV/VendingMachines/SharedShopVendorSystem.cs | 10 +++++----- .../en-US/deltav/vending-machines/shop-vendor.ftl | 2 ++ 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Content.Client/DeltaV/VendingMachines/UI/ShopVendorWindow.xaml b/Content.Client/DeltaV/VendingMachines/UI/ShopVendorWindow.xaml index 20e0ec45763..cfc7f4a4a2c 100644 --- a/Content.Client/DeltaV/VendingMachines/UI/ShopVendorWindow.xaml +++ b/Content.Client/DeltaV/VendingMachines/UI/ShopVendorWindow.xaml @@ -13,8 +13,8 @@ -