Generalized Store System (#10201)

This commit is contained in:
Nemanja
2022-08-17 00:34:25 -04:00
committed by GitHub
parent 1b50928d50
commit 2152914acc
68 changed files with 2493 additions and 1568 deletions

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

View 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);

View 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!;
}

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

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

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