Profectus-Demo/src/features/buyable.tsx

240 lines
9.1 KiB
TypeScript
Raw Normal View History

2022-12-31 20:57:09 +00:00
import { isArray } from "@vue/shared";
2022-03-04 03:39:48 +00:00
import ClickableComponent from "features/clickables/Clickable.vue";
2022-12-31 20:57:09 +00:00
import type { CoercableComponent, OptionsFunc, Replace, StyleValue } from "features/feature";
2022-06-27 00:17:22 +00:00
import { Component, GatherProps, getUniqueID, jsx, setDefault, Visibility } from "features/feature";
2022-12-31 20:57:09 +00:00
import { DefaultValue, Persistent, persistent } from "game/persistence";
import {
createVisibilityRequirement,
displayRequirements,
2023-02-05 08:51:07 +00:00
maxRequirementsMet,
2022-12-31 20:57:09 +00:00
payRequirements,
Requirements,
requirementsMet
} from "game/requirements";
2022-06-27 00:17:22 +00:00
import type { DecimalSource } from "util/bignum";
2022-12-31 20:57:09 +00:00
import Decimal, { formatWhole } from "util/bignum";
2022-06-27 00:17:22 +00:00
import type {
2022-01-14 04:25:47 +00:00
Computable,
GetComputableType,
GetComputableTypeWithDefault,
ProcessedComputable
2022-03-04 03:39:48 +00:00
} from "util/computed";
2022-06-27 00:17:22 +00:00
import { processComputable } from "util/computed";
2022-03-04 03:39:48 +00:00
import { createLazyProxy } from "util/proxies";
import { coerceComponent, isCoercableComponent } from "util/vue";
2022-06-27 00:17:22 +00:00
import type { Ref } from "vue";
import { computed, unref } from "vue";
2022-01-14 04:25:47 +00:00
export const BuyableType = Symbol("Buyable");
2022-03-09 01:40:51 +00:00
export type BuyableDisplay =
2022-01-14 04:25:47 +00:00
| CoercableComponent
| {
title?: CoercableComponent;
2022-07-07 15:39:45 +00:00
description?: CoercableComponent;
2022-01-14 04:25:47 +00:00
effectDisplay?: CoercableComponent;
showAmount?: boolean;
2022-01-14 04:25:47 +00:00
};
export interface BuyableOptions {
visibility?: Computable<Visibility>;
2022-12-31 20:57:09 +00:00
requirements: Requirements;
2022-01-14 04:25:47 +00:00
purchaseLimit?: Computable<DecimalSource>;
2022-11-29 14:12:46 +00:00
initialValue?: DecimalSource;
2022-01-14 04:25:47 +00:00
classes?: Computable<Record<string, boolean>>;
style?: Computable<StyleValue>;
mark?: Computable<boolean | string>;
small?: Computable<boolean>;
2023-02-05 08:51:07 +00:00
buyMax?: Computable<boolean>;
2022-01-14 04:25:47 +00:00
display?: Computable<BuyableDisplay>;
2022-12-31 20:57:09 +00:00
onPurchase?: VoidFunction;
2022-01-14 04:25:47 +00:00
}
2022-04-23 23:20:15 +00:00
export interface BaseBuyable {
2022-01-14 04:25:47 +00:00
id: string;
2022-04-23 23:20:15 +00:00
amount: Persistent<DecimalSource>;
maxed: Ref<boolean>;
2022-01-14 04:25:47 +00:00
canClick: ProcessedComputable<boolean>;
onClick: VoidFunction;
purchase: VoidFunction;
type: typeof BuyableType;
[Component]: typeof ClickableComponent;
[GatherProps]: () => Record<string, unknown>;
2022-01-14 04:25:47 +00:00
}
export type Buyable<T extends BuyableOptions> = Replace<
T & BaseBuyable,
{
visibility: GetComputableTypeWithDefault<T["visibility"], Visibility.Visible>;
2022-12-31 20:57:09 +00:00
requirements: GetComputableType<T["requirements"]>;
purchaseLimit: GetComputableTypeWithDefault<T["purchaseLimit"], Decimal>;
2022-01-14 04:25:47 +00:00
classes: GetComputableType<T["classes"]>;
style: GetComputableType<T["style"]>;
mark: GetComputableType<T["mark"]>;
small: GetComputableType<T["small"]>;
2023-02-05 08:51:07 +00:00
buyMax: GetComputableType<T["buyMax"]>;
2022-01-14 04:25:47 +00:00
display: Ref<CoercableComponent>;
}
>;
export type GenericBuyable = Replace<
Buyable<BuyableOptions>,
{
visibility: ProcessedComputable<Visibility>;
purchaseLimit: ProcessedComputable<DecimalSource>;
}
>;
export function createBuyable<T extends BuyableOptions>(
optionsFunc: OptionsFunc<T, BaseBuyable, GenericBuyable>
2022-01-14 04:25:47 +00:00
): Buyable<T> {
2022-04-23 23:20:15 +00:00
const amount = persistent<DecimalSource>(0);
return createLazyProxy(() => {
const buyable = optionsFunc();
2022-01-14 04:25:47 +00:00
buyable.id = getUniqueID("buyable-");
buyable.type = BuyableType;
buyable[Component] = ClickableComponent;
2022-04-23 23:20:15 +00:00
buyable.amount = amount;
2022-11-29 14:12:46 +00:00
buyable.amount[DefaultValue] = buyable.initialValue ?? 0;
2022-12-31 20:57:09 +00:00
const limitRequirement = {
requirementMet: computed(() =>
2023-02-05 08:51:07 +00:00
Decimal.sub(
unref((buyable as GenericBuyable).purchaseLimit),
(buyable as GenericBuyable).amount.value
2022-12-31 20:57:09 +00:00
)
),
requiresPay: false,
visibility: Visibility.None
} as const;
const visibilityRequirement = createVisibilityRequirement(buyable as GenericBuyable);
if (isArray(buyable.requirements)) {
buyable.requirements.unshift(visibilityRequirement);
buyable.requirements.push(limitRequirement);
} else {
buyable.requirements = [visibilityRequirement, buyable.requirements, limitRequirement];
2022-01-14 04:25:47 +00:00
}
2022-12-31 20:57:09 +00:00
buyable.maxed = computed(() =>
Decimal.gte(
(buyable as GenericBuyable).amount.value,
unref((buyable as GenericBuyable).purchaseLimit)
)
);
processComputable(buyable as T, "classes");
const classes = buyable.classes as ProcessedComputable<Record<string, boolean>> | undefined;
buyable.classes = computed(() => {
const currClasses = unref(classes) || {};
if ((buyable as GenericBuyable).maxed.value) {
currClasses.bought = true;
}
return currClasses;
});
2022-12-31 20:57:09 +00:00
buyable.canClick = computed(() => requirementsMet(buyable.requirements));
buyable.onClick = buyable.purchase =
buyable.onClick ??
buyable.purchase ??
function (this: GenericBuyable) {
const genericBuyable = buyable as GenericBuyable;
2022-12-31 20:57:09 +00:00
if (!unref(genericBuyable.canClick)) {
return;
}
2023-02-05 08:51:07 +00:00
payRequirements(
buyable.requirements,
unref(genericBuyable.buyMax)
? maxRequirementsMet(genericBuyable.requirements)
: 1
);
2022-12-31 20:57:09 +00:00
genericBuyable.amount.value = Decimal.add(genericBuyable.amount.value, 1);
genericBuyable.onPurchase?.();
};
processComputable(buyable as T, "display");
const display = buyable.display;
buyable.display = jsx(() => {
// TODO once processComputable types correctly, remove this "as X"
const currDisplay = unref(display) as BuyableDisplay;
if (isCoercableComponent(currDisplay)) {
2022-03-16 16:24:54 +00:00
const CurrDisplay = coerceComponent(currDisplay);
return <CurrDisplay />;
}
2022-12-31 20:57:09 +00:00
if (currDisplay != null) {
const genericBuyable = buyable as GenericBuyable;
const Title = coerceComponent(currDisplay.title ?? "", "h3");
const Description = coerceComponent(currDisplay.description ?? "");
const EffectDisplay = coerceComponent(currDisplay.effectDisplay ?? "");
return (
<span>
{currDisplay.title == null ? null : (
<div>
<Title />
</div>
)}
{currDisplay.description == null ? null : <Description />}
{currDisplay.showAmount === false ? null : (
<div>
<br />
{unref(genericBuyable.purchaseLimit) === Decimal.dInf ? (
<>Amount: {formatWhole(genericBuyable.amount.value)}</>
) : (
<>
Amount: {formatWhole(genericBuyable.amount.value)} /{" "}
{formatWhole(unref(genericBuyable.purchaseLimit))}
</>
)}
</div>
)}
{currDisplay.effectDisplay == null ? null : (
<div>
<br />
Currently: <EffectDisplay />
</div>
)}
2022-12-31 20:57:09 +00:00
{genericBuyable.maxed.value ? null : (
<div>
<br />
2023-02-05 08:51:07 +00:00
{displayRequirements(
genericBuyable.requirements,
unref(genericBuyable.buyMax)
? maxRequirementsMet(genericBuyable.requirements)
: 1
)}
</div>
2022-12-31 20:57:09 +00:00
)}
</span>
);
}
return "";
});
processComputable(buyable as T, "visibility");
setDefault(buyable, "visibility", Visibility.Visible);
processComputable(buyable as T, "purchaseLimit");
setDefault(buyable, "purchaseLimit", Decimal.dInf);
processComputable(buyable as T, "style");
processComputable(buyable as T, "mark");
processComputable(buyable as T, "small");
2023-02-05 08:51:07 +00:00
processComputable(buyable as T, "buyMax");
2022-01-14 04:25:47 +00:00
buyable[GatherProps] = function (this: GenericBuyable) {
const { display, visibility, style, classes, onClick, canClick, small, mark, id } =
this;
return {
display,
visibility,
style: unref(style),
classes,
onClick,
canClick,
small,
mark,
id
};
};
2022-01-14 04:25:47 +00:00
return buyable as unknown as Buyable<T>;
2022-04-23 23:20:15 +00:00
});
2022-01-14 04:25:47 +00:00
}