import ClickableComponent from "@/components/features/Clickable.vue"; import { Resource } from "@/features/resource"; import Decimal, { DecimalSource, format } from "@/util/bignum"; import { Computable, GetComputableType, GetComputableTypeWithDefault, processComputable, ProcessedComputable } from "@/util/computed"; import { createProxy } from "@/util/proxies"; import { isCoercableComponent } from "@/util/vue"; import { computed, Ref, unref } from "vue"; import { CoercableComponent, Component, getUniqueID, makePersistent, Persistent, PersistentState, Replace, setDefault, StyleValue, Visibility } from "./feature"; export const BuyableType = Symbol("Buyable"); type BuyableDisplay = | CoercableComponent | { title?: CoercableComponent; description: CoercableComponent; effectDisplay?: CoercableComponent; }; export interface BuyableOptions { visibility?: Computable; cost?: Computable; resource?: Computable; canPurchase?: Computable; purchaseLimit?: Computable; classes?: Computable>; style?: Computable; mark?: Computable; small?: Computable; display?: Computable; onPurchase?: (cost: DecimalSource) => void; } interface BaseBuyable extends Persistent { id: string; amount: Ref; bought: Ref; canAfford: Ref; canClick: ProcessedComputable; onClick: VoidFunction; purchase: VoidFunction; type: typeof BuyableType; [Component]: typeof ClickableComponent; } export type Buyable = Replace< T & BaseBuyable, { visibility: GetComputableTypeWithDefault; cost: GetComputableType; resource: GetComputableType; canPurchase: GetComputableTypeWithDefault>; purchaseLimit: GetComputableTypeWithDefault; classes: GetComputableType; style: GetComputableType; mark: GetComputableType; small: GetComputableType; display: Ref; } >; export type GenericBuyable = Replace< Buyable, { visibility: ProcessedComputable; canPurchase: ProcessedComputable; purchaseLimit: ProcessedComputable; } >; export function createBuyable( options: T & ThisType> ): Buyable { if (options.canPurchase == null && (options.resource == null || options.cost == null)) { console.warn( "Cannot create buyable without a canPurchase property or a resource and cost property", options ); throw "Cannot create buyable without a canPurchase property or a resource and cost property"; } const buyable: T & Partial = options; makePersistent(buyable, 0); buyable.id = getUniqueID("buyable-"); buyable.type = BuyableType; buyable[Component] = ClickableComponent; buyable.amount = buyable[PersistentState]; buyable.bought = computed(() => Decimal.gt(proxy.amount.value, 0)); buyable.canAfford = computed( () => proxy.resource != null && proxy.cost != null && Decimal.gte(unref(proxy.resource).value, unref(proxy.cost)) ); if (buyable.canPurchase == null) { buyable.canPurchase = computed( () => proxy.purchaseLimit != null && proxy.canAfford && Decimal.lt(proxy.amount.value, unref(proxy.purchaseLimit)) ); } processComputable(buyable as T, "canPurchase"); // TODO once processComputable typing works, this can be replaced //buyable.canClick = buyable.canPurchase; buyable.canClick = computed(() => unref(proxy.canPurchase)); buyable.onClick = buyable.purchase = function () { if (!unref(proxy.canPurchase) || proxy.cost == null || proxy.resource == null) { return; } const cost = unref(proxy.cost); unref(proxy.resource).value = Decimal.sub( unref(proxy.resource).value, cost ); proxy.amount.value = Decimal.add(proxy.amount.value, 1); this.onPurchase?.(cost); }; processComputable(buyable as T, "display"); const display = buyable.display; buyable.display = computed(() => { // TODO once processComputable types correctly, remove this "as X" const currDisplay = unref(display) as BuyableDisplay; if ( currDisplay != null && !isCoercableComponent(currDisplay) && proxy.cost != null && proxy.resource != null ) { return (

Amount: {format(proxy.amount.value)} / {format(unref(proxy.purchaseLimit))}

Currently:

Cost: {format(unref(proxy.cost))} {unref(proxy.resource).displayName}
); } return null; }); processComputable(buyable as T, "visibility"); setDefault(buyable, "visibility", Visibility.Visible); processComputable(buyable as T, "cost"); processComputable(buyable as T, "resource"); processComputable(buyable as T, "purchaseLimit"); setDefault(buyable, "purchaseLimit", 1); processComputable(buyable as T, "classes"); processComputable(buyable as T, "style"); processComputable(buyable as T, "mark"); processComputable(buyable as T, "small"); const proxy = createProxy(buyable as unknown as Buyable); return proxy; }