Generalized Store System (#10201)
This commit is contained in:
43
Content.Shared/Store/CurrencyPrototype.cs
Normal file
43
Content.Shared/Store/CurrencyPrototype.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Store;
|
||||
|
||||
/// <summary>
|
||||
/// Prototype used to define different types of currency for generic stores.
|
||||
/// Mainly used for antags, such as traitors, nukies, and revenants
|
||||
/// This is separate to the cargo ordering system.
|
||||
/// </summary>
|
||||
[Prototype("currency")]
|
||||
[DataDefinition, Serializable, NetSerializable]
|
||||
public sealed class CurrencyPrototype : IPrototype
|
||||
{
|
||||
[ViewVariables]
|
||||
[IdDataField]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The Loc string used for displaying the balance of a certain currency at the top of the store ui
|
||||
/// </summary>
|
||||
[DataField("balanceDisplay")]
|
||||
public string BalanceDisplay { get; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The Loc string used for displaying the price of listings in store UI
|
||||
/// </summary>
|
||||
[DataField("priceDisplay")]
|
||||
public string PriceDisplay { get; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The physical entity of the currency
|
||||
/// </summary>
|
||||
[DataField("entityId", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string? EntityId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not this currency can be withdrawn from a shop by a player. Requires a valid entityId.
|
||||
/// </summary>
|
||||
[DataField("canWithdraw")]
|
||||
public bool CanWithdraw { get; } = true;
|
||||
}
|
||||
23
Content.Shared/Store/ListingCondition.cs
Normal file
23
Content.Shared/Store/ListingCondition.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Store;
|
||||
|
||||
/// <summary>
|
||||
/// Used to define a complicated condition that requires C#
|
||||
/// </summary>
|
||||
[ImplicitDataDefinitionForInheritors]
|
||||
[MeansImplicitUse]
|
||||
public abstract class ListingCondition
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether or not a certain entity can purchase a listing.
|
||||
/// </summary>
|
||||
/// <returns>Whether or not the listing can be purchased</returns>
|
||||
public abstract bool Condition(ListingConditionArgs args);
|
||||
}
|
||||
|
||||
/// <param name="Buyer">The person purchasing the listing</param>
|
||||
/// <param name="Listing">The liting itself</param>
|
||||
/// <param name="EntityManager">An entitymanager for sane coding</param>
|
||||
public readonly record struct ListingConditionArgs(EntityUid Buyer, EntityUid? StoreEntity, ListingData Listing, IEntityManager EntityManager);
|
||||
133
Content.Shared/Store/ListingPrototype.cs
Normal file
133
Content.Shared/Store/ListingPrototype.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.FixedPoint;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Shared.Store;
|
||||
|
||||
/// <summary>
|
||||
/// This is the data object for a store listing which is passed around in code.
|
||||
/// this allows for prices and features of listings to be dynamically changed in code
|
||||
/// without having to modify the prototypes.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
[Virtual, DataDefinition]
|
||||
public class ListingData : IEquatable<ListingData>
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the listing. If empty, uses the entity's name (if present)
|
||||
/// </summary>
|
||||
[DataField("name")]
|
||||
public string Name = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The description of the listing. If empty, uses the entity's description (if present)
|
||||
/// </summary>
|
||||
[DataField("description")]
|
||||
public string Description = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The categories that this listing applies to. Used for filtering a listing for a store.
|
||||
/// </summary>
|
||||
[DataField("categories", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer<StoreCategoryPrototype>))]
|
||||
public List<string> Categories = new();
|
||||
|
||||
/// <summary>
|
||||
/// The cost of the listing. String represents the currency type while the FixedPoint2 represents the amount of that currency.
|
||||
/// </summary>
|
||||
[DataField("cost", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<FixedPoint2, CurrencyPrototype>))]
|
||||
public Dictionary<string, FixedPoint2> Cost = new();
|
||||
|
||||
/// <summary>
|
||||
/// Specific customizeable conditions that determine whether or not the listing can be purchased.
|
||||
/// </summary>
|
||||
[NonSerialized]
|
||||
[DataField("conditions", serverOnly: true)]
|
||||
public List<ListingCondition>? Conditions;
|
||||
|
||||
/// <summary>
|
||||
/// The icon for the listing. If null, uses the icon for the entity or action.
|
||||
/// </summary>
|
||||
[DataField("icon")]
|
||||
public SpriteSpecifier? Icon;
|
||||
|
||||
/// <summary>
|
||||
/// The priority for what order the listings will show up in on the menu.
|
||||
/// </summary>
|
||||
[DataField("priority")]
|
||||
public int Priority = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The entity that is given when the listing is purchased.
|
||||
/// </summary>
|
||||
[DataField("productEntity", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string? ProductEntity;
|
||||
|
||||
/// <summary>
|
||||
/// The action that is given when the listing is purchased.
|
||||
/// </summary>
|
||||
[DataField("productAction", customTypeSerializer: typeof(PrototypeIdSerializer<InstantActionPrototype>))]
|
||||
public string? ProductAction;
|
||||
|
||||
/// <summary>
|
||||
/// The event that is broadcast when the listing is purchased.
|
||||
/// </summary>
|
||||
[DataField("productEvent")]
|
||||
public object? ProductEvent;
|
||||
|
||||
/// <summary>
|
||||
/// used internally for tracking how many times an item was purchased.
|
||||
/// </summary>
|
||||
public int PurchaseAmount = 0;
|
||||
|
||||
public bool Equals(ListingData? listing)
|
||||
{
|
||||
if (listing == null)
|
||||
return false;
|
||||
|
||||
//simple conditions
|
||||
if (Priority != listing.Priority ||
|
||||
Name != listing.Name ||
|
||||
Description != listing.Description ||
|
||||
ProductEntity != listing.ProductEntity ||
|
||||
ProductAction != listing.ProductAction ||
|
||||
ProductEvent != listing.ProductEvent)
|
||||
return false;
|
||||
|
||||
if (Icon != null && !Icon.Equals(listing.Icon))
|
||||
return false;
|
||||
|
||||
///more complicated conditions that eat perf. these don't really matter
|
||||
///as much because you will very rarely have to check these.
|
||||
if (!Categories.OrderBy(x => x).SequenceEqual(listing.Categories.OrderBy(x => x)))
|
||||
return false;
|
||||
|
||||
if (!Cost.OrderBy(x => x).SequenceEqual(listing.Cost.OrderBy(x => x)))
|
||||
return false;
|
||||
|
||||
if ((Conditions != null && listing.Conditions != null) &&
|
||||
!Conditions.OrderBy(x => x).SequenceEqual(listing.Conditions.OrderBy(x => x)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//<inheritdoc>
|
||||
/// <summary>
|
||||
/// Defines a set item listing that is available in a store
|
||||
/// </summary>
|
||||
[Prototype("listing")]
|
||||
[Serializable, NetSerializable]
|
||||
[DataDefinition]
|
||||
public sealed class ListingPrototype : ListingData, IPrototype
|
||||
{
|
||||
[ViewVariables]
|
||||
[IdDataField]
|
||||
public string ID { get; } = default!;
|
||||
}
|
||||
22
Content.Shared/Store/StoreCategoryPrototype.cs
Normal file
22
Content.Shared/Store/StoreCategoryPrototype.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Store;
|
||||
|
||||
/// <summary>
|
||||
/// Used to define different categories for a store.
|
||||
/// </summary>
|
||||
[Prototype("storeCategory")]
|
||||
[Serializable, NetSerializable, DataDefinition]
|
||||
public sealed class StoreCategoryPrototype : IPrototype
|
||||
{
|
||||
[ViewVariables]
|
||||
[IdDataField]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
[DataField("name")]
|
||||
public string Name { get; } = string.Empty;
|
||||
|
||||
[DataField("priority")]
|
||||
public int Priority { get; } = 0;
|
||||
}
|
||||
41
Content.Shared/Store/StorePresetPrototype.cs
Normal file
41
Content.Shared/Store/StorePresetPrototype.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
|
||||
using Content.Shared.FixedPoint;
|
||||
|
||||
namespace Content.Shared.Store;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies generic info for initializing a store.
|
||||
/// </summary>
|
||||
[Prototype("storePreset")]
|
||||
[DataDefinition]
|
||||
public sealed class StorePresetPrototype : IPrototype
|
||||
{
|
||||
[ViewVariables] [IdDataField] public string ID { get; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The name displayed at the top of the store window
|
||||
/// </summary>
|
||||
[DataField("storeName", required: true)]
|
||||
public string StoreName { get; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The categories that this store can access
|
||||
/// </summary>
|
||||
[DataField("categories", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<StoreCategoryPrototype>))]
|
||||
public HashSet<string> Categories { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The inital balance that the store initializes with.
|
||||
/// </summary>
|
||||
[DataField("initialBalance",
|
||||
customTypeSerializer: typeof(PrototypeIdDictionarySerializer<FixedPoint2, CurrencyPrototype>))]
|
||||
public Dictionary<string, FixedPoint2>? InitialBalance { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The currencies that are accepted in the store
|
||||
/// </summary>
|
||||
[DataField("currencyWhitelist", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<CurrencyPrototype>))]
|
||||
public HashSet<string> CurrencyWhitelist { get; } = new();
|
||||
}
|
||||
84
Content.Shared/Store/StoreUi.cs
Normal file
84
Content.Shared/Store/StoreUi.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.MobState;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Store;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum StoreUiKey : byte
|
||||
{
|
||||
Key
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class StoreUpdateState : BoundUserInterfaceState
|
||||
{
|
||||
public readonly EntityUid? Buyer;
|
||||
|
||||
public readonly HashSet<ListingData> Listings;
|
||||
|
||||
public readonly Dictionary<string, FixedPoint2> Balance;
|
||||
|
||||
public StoreUpdateState(EntityUid? buyer, HashSet<ListingData> listings, Dictionary<string, FixedPoint2> balance)
|
||||
{
|
||||
Buyer = buyer;
|
||||
Listings = listings;
|
||||
Balance = balance;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// initializes miscellaneous data about the store.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class StoreInitializeState : BoundUserInterfaceState
|
||||
{
|
||||
public readonly string Name;
|
||||
|
||||
public StoreInitializeState(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class StoreRequestUpdateInterfaceMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public EntityUid CurrentBuyer;
|
||||
|
||||
public StoreRequestUpdateInterfaceMessage(EntityUid currentBuyer)
|
||||
{
|
||||
CurrentBuyer = currentBuyer;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class StoreBuyListingMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public EntityUid Buyer;
|
||||
|
||||
public ListingData Listing;
|
||||
|
||||
public StoreBuyListingMessage(EntityUid buyer, ListingData listing)
|
||||
{
|
||||
Buyer = buyer;
|
||||
Listing = listing;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class StoreRequestWithdrawMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public EntityUid Buyer;
|
||||
|
||||
public string Currency;
|
||||
|
||||
public int Amount;
|
||||
|
||||
public StoreRequestWithdrawMessage(EntityUid buyer, string currency, int amount)
|
||||
{
|
||||
Buyer = buyer;
|
||||
Currency = currency;
|
||||
Amount = amount;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user