salvage points shop (#2510)

* add conscription bag

* add gar mesons

* remove salvage vendor restock

* add code for shop vendors

* make salvage vendor a shop vendor

* ui fixes

* :trollface:

* update locker and vendor inventory

* add mining hardsuit for 3k

---------

Co-authored-by: deltanedas <@deltanedas:kde.org>
This commit is contained in:
deltanedas
2024-12-27 03:06:17 +00:00
committed by tommy
parent 1c2f200762
commit f42c2ae11b
24 changed files with 855 additions and 26 deletions

View File

@@ -0,0 +1,123 @@
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<ShopVendorComponent, AppearanceChangeEvent>(OnAppearanceChange);
SubscribeLocalEvent<ShopVendorComponent, AnimationCompletedEvent>(OnAnimationCompleted);
}
// copied from vending machines because its not reusable in other systems :)
private void OnAnimationCompleted(Entity<ShopVendorComponent> ent, ref AnimationCompletedEvent args)
{
UpdateAppearance((ent, ent.Comp));
}
private void OnAppearanceChange(Entity<ShopVendorComponent> ent, ref AppearanceChangeEvent args)
{
UpdateAppearance((ent, ent.Comp, args.Sprite));
}
private void UpdateAppearance(Entity<ShopVendorComponent, SpriteComponent?> ent)
{
if (!Resolve(ent, ref ent.Comp2))
return;
if (!_appearance.TryGetData<VendingMachineVisualState>(ent, VendingMachineVisuals.VisualState, out var state))
state = VendingMachineVisualState.Normal;
var sprite = ent.Comp2;
SetLayerState(VendingMachineVisualLayers.Base, ent.Comp1.OffState, sprite);
SetLayerState(VendingMachineVisualLayers.Screen, ent.Comp1.ScreenState, sprite);
switch (state)
{
case VendingMachineVisualState.Normal:
SetLayerState(VendingMachineVisualLayers.BaseUnshaded, ent.Comp1.NormalState, sprite);
break;
case VendingMachineVisualState.Deny:
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, ent.Comp1.EjectState, ent.Comp1.EjectDelay, sprite);
break;
case VendingMachineVisualState.Broken:
HideLayers(sprite);
SetLayerState(VendingMachineVisualLayers.Base, ent.Comp1.BrokenState, sprite);
break;
case VendingMachineVisualState.Off:
HideLayers(sprite);
break;
}
}
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)
{
if (state == null || _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);
}
}

View File

@@ -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<ShopVendorWindow>();
_window.SetEntity(Owner);
_window.OpenCenteredLeft();
_window.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;
_window.OnItemSelected += index => SendMessage(new ShopVendorPurchaseMessage(index));
}
}

View File

@@ -0,0 +1,13 @@
<BoxContainer xmlns="https://spacestation14.io"
Orientation="Horizontal"
HorizontalExpand="True"
SeparationOverride="4">
<EntityPrototypeView
Name="ItemPrototype"
Margin="4 0 0 0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
MinSize="32 32"/>
<Label Name="NameLabel" SizeFlagsStretchRatio="3" HorizontalExpand="True" ClipText="True"/>
<Label Name="CostLabel" SizeFlagsStretchRatio="3" HorizontalAlignment="Right" Margin="8 0"/>
</BoxContainer>

View File

@@ -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();
}
}

View File

@@ -0,0 +1,24 @@
<controls:FancyWindow
xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
MinHeight="210">
<BoxContainer Name="MainContainer" Orientation="Vertical">
<BoxContainer Orientation="Horizontal">
<LineEdit Name="SearchBar" PlaceHolder="{Loc 'vending-machine-component-search-filter'}" HorizontalExpand="True" Margin="4 4"/>
<Label Name="BalanceLabel" Margin="4 4"/>
</BoxContainer>
<controls:SearchListContainer Name="VendingContents" VerticalExpand="True" Margin="4 4"/>
<!-- Footer -->
<BoxContainer Orientation="Vertical">
<PanelContainer StyleClasses="LowDivider" />
<BoxContainer Orientation="Horizontal" Margin="10 2 5 0" VerticalAlignment="Bottom">
<Label Text="{Loc 'shop-vendor-flavor-left'}" StyleClasses="WindowFooterText" />
<Label Text="{Loc 'shop-vendor-flavor-right'}" StyleClasses="WindowFooterText"
HorizontalAlignment="Right" HorizontalExpand="True" Margin="0 0 5 0" />
<TextureRect StyleClasses="NTLogoDark" Stretch="KeepAspectCentered"
VerticalAlignment="Center" HorizontalAlignment="Right" SetSize="19 19"/>
</BoxContainer>
</BoxContainer>
</BoxContainer>
</controls:FancyWindow>

View File

@@ -0,0 +1,147 @@
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;
/// <summary>
/// Event fired with the listing index to purchase.
/// </summary>
public event Action<int>? OnItemSelected;
private EntityUid _owner;
private readonly StyleBoxFlat _style = new() { BackgroundColor = new Color(70, 73, 102) };
private readonly StyleBoxFlat _styleBroke = new() { BackgroundColor = Color.FromHex("#303133") };
private readonly List<ListContainerButton> _buttons = new();
private uint _balance = 1;
public ShopVendorWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_vendor = _entMan.System<ShopVendorSystem>();
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<ShopVendorComponent>(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.Disabled = cast.Cost > _balance;
button.StyleBoxOverride = button.Disabled ? _styleBroke : _style;
}
public void Populate(List<ShopListing> listings)
{
var longestEntry = string.Empty;
var listData = new List<ShopVendorListingData>();
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<StackComponent>(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;

View File

@@ -0,0 +1,47 @@
using Content.Server.Advertise.EntitySystems;
using Content.Shared.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<ShopVendorComponent, TransformComponent>();
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<ShopVendorComponent> ent)
{
if (TryComp<SpeakOnUIClosedComponent>(ent, out var speak))
_speakOnUIClosed.TrySetFlag((ent.Owner, speak));
}
}

View File

@@ -0,0 +1,9 @@
using Robust.Shared.GameStates;
namespace Content.Shared.DeltaV.VendingMachines;
/// <summary>
/// Makes a <see cref="ShopVendorComponent"/> use mining points to buy items.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class PointsVendorComponent : Component;

View File

@@ -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<PointsVendorComponent, ShopVendorBalanceEvent>(OnPointsBalance);
SubscribeLocalEvent<PointsVendorComponent, ShopVendorPurchaseEvent>(OnPointsPurchase);
SubscribeLocalEvent<ShopVendorComponent, PowerChangedEvent>(OnPowerChanged);
SubscribeLocalEvent<ShopVendorComponent, BreakageEventArgs>(OnBreak);
SubscribeLocalEvent<ShopVendorComponent, ActivatableUIOpenAttemptEvent>(OnOpenAttempt);
Subs.BuiEvents<ShopVendorComponent>(VendingMachineUiKey.Key, subs =>
{
subs.Event<ShopVendorPurchaseMessage>(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<PointsVendorComponent> ent, ref ShopVendorBalanceEvent args)
{
args.Balance = _points.TryFindIdCard(args.User)?.Comp?.Points ?? 0;
}
private void OnPointsPurchase(Entity<PointsVendorComponent> ent, ref ShopVendorPurchaseEvent args)
{
if (_points.TryFindIdCard(args.User) is {} idCard && _points.RemovePoints(idCard, args.Cost))
args.Paid = true;
}
#endregion
private void OnPowerChanged(Entity<ShopVendorComponent> ent, ref PowerChangedEvent args)
{
UpdateVisuals(ent);
}
private void OnBreak(Entity<ShopVendorComponent> ent, ref BreakageEventArgs args)
{
ent.Comp.Broken = true;
UpdateVisuals(ent);
}
private void OnOpenAttempt(Entity<ShopVendorComponent> ent, ref ActivatableUIOpenAttemptEvent args)
{
if (ent.Comp.Broken)
args.Cancel();
}
private void OnPurchase(Entity<ShopVendorComponent> 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))
{
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.PlayPvs(ent.Comp.PurchaseSound, ent);
UpdateVisuals(ent);
Log.Debug($"Player {ToPrettyString(user):user} purchased {listing.Id} from {ToPrettyString(ent):vendor}");
AfterPurchase(ent);
}
protected virtual void AfterPurchase(Entity<ShopVendorComponent> ent)
{
}
private void Deny(Entity<ShopVendorComponent> ent, EntityUid user)
{
_popup.PopupClient(Loc.GetString("vending-machine-component-try-eject-access-denied"), ent, user);
if (ent.Comp.Denying)
return;
ent.Comp.Denying = true;
ent.Comp.NextDeny = Timing.CurTime + ent.Comp.DenyDelay;
Dirty(ent);
_audio.PlayPvs(ent.Comp.DenySound, ent);
UpdateVisuals(ent);
}
protected void UpdateVisuals(Entity<ShopVendorComponent> 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);
}
}
/// <summary>
/// Raised on a shop vendor to get its current balance.
/// A currency component sets Balance to whatever it is.
/// </summary>
[ByRefEvent]
public record struct ShopVendorBalanceEvent(EntityUid User, uint Balance = 0);
/// <summary>
/// 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.
/// </summary>
[ByRefEvent]
public record struct ShopVendorPurchaseEvent(EntityUid User, uint Cost, bool Paid = false);

View File

@@ -0,0 +1,23 @@
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared.DeltaV.VendingMachines;
/// <summary>
/// Similar to <c>VendingMachineInventoryPrototype</c> but for <see cref="ShopVendorComponent"/>.
/// </summary>
[Prototype]
public sealed class ShopInventoryPrototype : IPrototype
{
[IdDataField]
public string ID { get; private set; } = default!;
/// <summary>
/// The item listings for sale.
/// </summary>
[DataField(required: true)]
public List<ShopListing> Listings = new();
}
[DataRecord, Serializable]
public record struct ShopListing(EntProtoId Id, uint Cost);

View File

@@ -0,0 +1,96 @@
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.DeltaV.VendingMachines;
/// <summary>
/// A vending machine that sells items for a currency controlled by events.
/// Does not need restocking.
/// Another component must handle <see cref="ShopVendorBalanceEvent"/> and <see cref="ShopVendorPurchaseEvent"/> to work.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(SharedShopVendorSystem))]
[AutoGenerateComponentState, AutoGenerateComponentPause]
public sealed partial class ShopVendorComponent : Component
{
/// <summary>
/// The inventory prototype to sell.
/// </summary>
[DataField(required: true)]
public ProtoId<ShopInventoryPrototype> Pack;
[DataField, AutoNetworkedField]
public bool Broken;
[DataField, AutoNetworkedField]
public bool Denying;
/// <summary>
/// Item being ejected, or null if it isn't.
/// </summary>
[DataField, AutoNetworkedField]
public EntProtoId? Ejecting;
/// <summary>
/// How long to wait before flashing denied again.
/// </summary>
[DataField]
public TimeSpan DenyDelay = TimeSpan.FromSeconds(2);
/// <summary>
/// How long to wait before another item can be bought
/// </summary>
[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
}
};
#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
}

View File

@@ -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;
}

View File

@@ -0,0 +1,4 @@
shop-vendor-balance = Balance: {$points}
shop-vendor-stack-suffix = x{$count}
shop-vendor-flavor-left = All payments are secure
shop-vendor-flavor-right = v1.2

View File

@@ -146,15 +146,15 @@
category: cargoproduct-category-name-service category: cargoproduct-category-name-service
group: market group: market
- type: cargoProduct #- type: cargoProduct # DeltaV: Salvage vendor doesn't have stock anymore
id: CrateVendingMachineRestockSalvageEquipment # id: CrateVendingMachineRestockSalvageEquipment
icon: # icon:
sprite: Objects/Specific/Service/vending_machine_restock.rsi # sprite: Objects/Specific/Service/vending_machine_restock.rsi
state: base # state: base
product: CrateVendingMachineRestockSalvageEquipmentFilled # product: CrateVendingMachineRestockSalvageEquipmentFilled
cost: 1500 # cost: 1000
category: cargoproduct-category-name-engineering # category: cargoproduct-category-name-engineering
group: market # group: market
- type: cargoProduct - type: cargoProduct
id: CrateVendingMachineRestockSecTech id: CrateVendingMachineRestockSecTech

View File

@@ -154,16 +154,15 @@
id: VendingMachineRestockRobustSoftdrinks id: VendingMachineRestockRobustSoftdrinks
amount: 2 amount: 2
- type: entity #- type: entity # DeltaV: Salvage vendor doesn't have stock anymore
id: CrateVendingMachineRestockSalvageEquipmentFilled # id: CrateVendingMachineRestockSalvageEquipmentFilled
parent: CrateGenericSteel # parent: CrateGenericSteel
name: Salvage restock crate # name: Salvage restock crate
description: Contains a restock box for the salvage vendor. # description: Contains a restock box for the salvage vendor.
components: # components:
- type: EntityTableContainerFill # - type: StorageFill
containers: # contents:
entity_storage: # - id: VendingMachineRestockSalvageEquipment
id: VendingMachineRestockSalvageEquipment
- type: entity - type: entity
id: CrateVendingMachineRestockSecTechFilled id: CrateVendingMachineRestockSecTechFilled

View File

@@ -11,6 +11,10 @@
id: LockerFillSalvageSpecialist id: LockerFillSalvageSpecialist
table: !type:AllSelector table: !type:AllSelector
children: children:
- id: Pickaxe
- id: WeaponProtoKineticAccelerator
- id: FlashlightSeclite
- id: ClothingEyesGlassesMeson
- id: ClothingBeltUtilityFilled - id: ClothingBeltUtilityFilled
- id: SurvivalKnife - id: SurvivalKnife
- id: HandheldGPSBasic - id: HandheldGPSBasic

View File

@@ -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

View File

@@ -0,0 +1,53 @@
- 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
- id: SeismicCharge
cost: 250
- id: WeaponGrapplingGun
cost: 300
# 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 for 1k
- id: ClothingBackpackDuffelSalvageConscription
cost: 1500
- id: SpaceCash1000
cost: 2000
# TODO: super resonator for 2500
# TODO: jump boots for 2500
- id: ClothingOuterHardsuitSalvage
cost: 3000
# TODO: luxury shelter capsule for 3k
# TODO: luxury elite bar capsule for 10k
# TODO: pka mods
# TODO: mining drone stuff

View File

@@ -0,0 +1,7 @@
- type: entity
parent: ClothingEyesGlassesGar
id: ClothingEyesGlassesGarMeson
name: gar mesons
description: Do the impossible, see the invisible!
components:
- type: EyeProtection

View File

@@ -0,0 +1,7 @@
- type: wireLayout
id: ShopVendor
wires:
- !type:AiInteractWireAction
- !type:PowerWireAction
- !type:AccessWireAction
- !type:LogWireAction

View File

@@ -14,7 +14,7 @@
slots: slots:
- belt - belt
- type: Item - type: Item
size: Ginormous size: Huge # DeltaV: Was Ginormous, lets it fit in conscription bag
- type: Storage - type: Storage
maxItemSize: Normal maxItemSize: Normal
grid: grid:

View File

@@ -363,6 +363,7 @@
- state: refill_sec - state: refill_sec
- type: entity - type: entity
abstract: true # DeltaV: Salvage vendor doesn't have stock anymore
parent: BaseVendingMachineRestock parent: BaseVendingMachineRestock
id: VendingMachineRestockSalvageEquipment id: VendingMachineRestockSalvageEquipment
name: Salvage Vendor restock box name: Salvage Vendor restock box

View File

@@ -1464,12 +1464,12 @@
name: Salvage Vendor name: Salvage Vendor
description: A dwarf's best friend! description: A dwarf's best friend!
components: components:
- type: VendingMachine #- type: VendingMachine # DeltaV: Use mining points instead of limited stock
pack: SalvageEquipmentInventory # pack: SalvageEquipmentInventory
offState: off # offState: off
brokenState: broken # brokenState: broken
normalState: normal-unshaded # normalState: normal-unshaded
denyState: deny-unshaded # denyState: deny-unshaded
- type: Sprite - type: Sprite
sprite: Structures/Machines/VendingMachines/mining.rsi sprite: Structures/Machines/VendingMachines/mining.rsi
layers: layers:
@@ -1484,6 +1484,21 @@
radius: 1.5 radius: 1.5
energy: 3.0 energy: 3.0
color: "#b89f25" 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:
enum.VendingMachineUiKey.Key:
type: ShopVendorBoundUserInterface
enum.WiresUiKey.Key:
type: WiresBoundUserInterface
- type: Wires # DeltaV: Use shop vendor wires layout
layoutId: ShopVendor
- type: AccessReader - type: AccessReader
access: [["Salvage"]] access: [["Salvage"]]
- type: GuideHelp - type: GuideHelp

View File

@@ -535,6 +535,9 @@ LightTree04: LightTree
LightTree05: LightTree LightTree05: LightTree
LightTree06: LightTree LightTree06: LightTree
# 2024-12-22
VendingMachineRestockSalvageEquipment: null
# 2024-12-28 # 2024-12-28
DrinkIrishCarBomb: DrinkIrishSlammer DrinkIrishCarBomb: DrinkIrishSlammer