forked from profectus/Profectus
Add decorators to decoratable features
This commit is contained in:
parent
40d2bcf55d
commit
691a68ecf2
12 changed files with 213 additions and 350 deletions
|
@ -1,4 +1,5 @@
|
||||||
import AchievementComponent from "features/achievements/Achievement.vue";
|
import AchievementComponent from "features/achievements/Achievement.vue";
|
||||||
|
import { Decorator } from "features/decorators";
|
||||||
import {
|
import {
|
||||||
CoercableComponent,
|
CoercableComponent,
|
||||||
Component,
|
Component,
|
||||||
|
@ -72,20 +73,28 @@ export type GenericAchievement = Replace<
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function createAchievement<T extends AchievementOptions>(
|
export function createAchievement<T extends AchievementOptions>(
|
||||||
optionsFunc?: OptionsFunc<T, BaseAchievement, GenericAchievement>
|
optionsFunc?: OptionsFunc<T, BaseAchievement, GenericAchievement>,
|
||||||
|
...decorators: Decorator<T, BaseAchievement, GenericAchievement>[]
|
||||||
): Achievement<T> {
|
): Achievement<T> {
|
||||||
const earned = persistent<boolean>(false);
|
const earned = persistent<boolean>(false);
|
||||||
|
const decoratedData = decorators.reduce((current, next) => Object.assign(current, next.getPersistentData?.()), {});
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const achievement = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>);
|
const achievement = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>);
|
||||||
achievement.id = getUniqueID("achievement-");
|
achievement.id = getUniqueID("achievement-");
|
||||||
achievement.type = AchievementType;
|
achievement.type = AchievementType;
|
||||||
achievement[Component] = AchievementComponent;
|
achievement[Component] = AchievementComponent;
|
||||||
|
|
||||||
|
for (const decorator of decorators) {
|
||||||
|
decorator.preConstruct?.(achievement);
|
||||||
|
}
|
||||||
|
|
||||||
achievement.earned = earned;
|
achievement.earned = earned;
|
||||||
achievement.complete = function () {
|
achievement.complete = function () {
|
||||||
earned.value = true;
|
earned.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Object.assign(achievement, decoratedData);
|
||||||
|
|
||||||
processComputable(achievement as T, "visibility");
|
processComputable(achievement as T, "visibility");
|
||||||
setDefault(achievement, "visibility", Visibility.Visible);
|
setDefault(achievement, "visibility", Visibility.Visible);
|
||||||
processComputable(achievement as T, "display");
|
processComputable(achievement as T, "display");
|
||||||
|
@ -94,9 +103,14 @@ export function createAchievement<T extends AchievementOptions>(
|
||||||
processComputable(achievement as T, "style");
|
processComputable(achievement as T, "style");
|
||||||
processComputable(achievement as T, "classes");
|
processComputable(achievement as T, "classes");
|
||||||
|
|
||||||
|
for (const decorator of decorators) {
|
||||||
|
decorator.postConstruct?.(achievement);
|
||||||
|
}
|
||||||
|
|
||||||
|
const decoratedProps = decorators.reduce((current, next) => Object.assign(current, next.getGatheredProps?.(achievement)), {});
|
||||||
achievement[GatherProps] = function (this: GenericAchievement) {
|
achievement[GatherProps] = function (this: GenericAchievement) {
|
||||||
const { visibility, display, earned, image, style, classes, mark, id } = this;
|
const { visibility, display, earned, image, style, classes, mark, id } = this;
|
||||||
return { visibility, display, earned, image, style: unref(style), classes, mark, id };
|
return { visibility, display, earned, image, style: unref(style), classes, mark, id, ...decoratedProps };
|
||||||
};
|
};
|
||||||
|
|
||||||
if (achievement.shouldEarn) {
|
if (achievement.shouldEarn) {
|
||||||
|
|
|
@ -31,6 +31,7 @@ import { coerceComponent, isCoercableComponent, render } from "util/vue";
|
||||||
import { computed, Ref, ref, unref } from "vue";
|
import { computed, Ref, ref, unref } from "vue";
|
||||||
import { BarOptions, createBar, GenericBar } from "./bars/bar";
|
import { BarOptions, createBar, GenericBar } from "./bars/bar";
|
||||||
import { ClickableOptions } from "./clickables/clickable";
|
import { ClickableOptions } from "./clickables/clickable";
|
||||||
|
import { Decorator } from "./decorators";
|
||||||
|
|
||||||
export const ActionType = Symbol("Action");
|
export const ActionType = Symbol("Action");
|
||||||
|
|
||||||
|
@ -77,9 +78,11 @@ export type GenericAction = Replace<
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function createAction<T extends ActionOptions>(
|
export function createAction<T extends ActionOptions>(
|
||||||
optionsFunc?: OptionsFunc<T, BaseAction, GenericAction>
|
optionsFunc?: OptionsFunc<T, BaseAction, GenericAction>,
|
||||||
|
...decorators: Decorator<T, BaseAction, GenericAction>[]
|
||||||
): Action<T> {
|
): Action<T> {
|
||||||
const progress = persistent<DecimalSource>(0);
|
const progress = persistent<DecimalSource>(0);
|
||||||
|
const decoratedData = decorators.reduce((current, next) => Object.assign(current, next.getPersistentData?.()), {});
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const action = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>);
|
const action = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>);
|
||||||
action.id = getUniqueID("action-");
|
action.id = getUniqueID("action-");
|
||||||
|
@ -89,8 +92,13 @@ export function createAction<T extends ActionOptions>(
|
||||||
// Required because of display changing types
|
// Required because of display changing types
|
||||||
const genericAction = action as unknown as GenericAction;
|
const genericAction = action as unknown as GenericAction;
|
||||||
|
|
||||||
|
for (const decorator of decorators) {
|
||||||
|
decorator.preConstruct?.(action);
|
||||||
|
}
|
||||||
|
|
||||||
action.isHolding = ref(false);
|
action.isHolding = ref(false);
|
||||||
action.progress = progress;
|
action.progress = progress;
|
||||||
|
Object.assign(action, decoratedData);
|
||||||
|
|
||||||
processComputable(action as T, "visibility");
|
processComputable(action as T, "visibility");
|
||||||
setDefault(action, "visibility", Visibility.Visible);
|
setDefault(action, "visibility", Visibility.Visible);
|
||||||
|
@ -202,6 +210,11 @@ export function createAction<T extends ActionOptions>(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
for (const decorator of decorators) {
|
||||||
|
decorator.postConstruct?.(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
const decoratedProps = decorators.reduce((current, next) => Object.assign(current, next.getGatheredProps?.(action)));
|
||||||
action[GatherProps] = function (this: GenericAction) {
|
action[GatherProps] = function (this: GenericAction) {
|
||||||
const {
|
const {
|
||||||
display,
|
display,
|
||||||
|
@ -225,7 +238,8 @@ export function createAction<T extends ActionOptions>(
|
||||||
canClick,
|
canClick,
|
||||||
small,
|
small,
|
||||||
mark,
|
mark,
|
||||||
id
|
id,
|
||||||
|
...decoratedProps
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import BarComponent from "features/bars/Bar.vue";
|
import BarComponent from "features/bars/Bar.vue";
|
||||||
|
import { Decorator } from "features/decorators";
|
||||||
import type {
|
import type {
|
||||||
CoercableComponent,
|
CoercableComponent,
|
||||||
GenericComponent,
|
GenericComponent,
|
||||||
|
@ -71,14 +72,22 @@ export type GenericBar = Replace<
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function createBar<T extends BarOptions>(
|
export function createBar<T extends BarOptions>(
|
||||||
optionsFunc: OptionsFunc<T, BaseBar, GenericBar>
|
optionsFunc: OptionsFunc<T, BaseBar, GenericBar>,
|
||||||
|
...decorators: Decorator<T, BaseBar, GenericBar>[]
|
||||||
): Bar<T> {
|
): Bar<T> {
|
||||||
|
const decoratedData = decorators.reduce((current, next) => Object.assign(current, next.getPersistentData?.()), {});
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const bar = optionsFunc();
|
const bar = optionsFunc();
|
||||||
bar.id = getUniqueID("bar-");
|
bar.id = getUniqueID("bar-");
|
||||||
bar.type = BarType;
|
bar.type = BarType;
|
||||||
bar[Component] = BarComponent;
|
bar[Component] = BarComponent;
|
||||||
|
|
||||||
|
for (const decorator of decorators) {
|
||||||
|
decorator.preConstruct?.(bar);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(bar, decoratedData);
|
||||||
|
|
||||||
processComputable(bar as T, "visibility");
|
processComputable(bar as T, "visibility");
|
||||||
setDefault(bar, "visibility", Visibility.Visible);
|
setDefault(bar, "visibility", Visibility.Visible);
|
||||||
processComputable(bar as T, "width");
|
processComputable(bar as T, "width");
|
||||||
|
@ -94,6 +103,11 @@ export function createBar<T extends BarOptions>(
|
||||||
processComputable(bar as T, "display");
|
processComputable(bar as T, "display");
|
||||||
processComputable(bar as T, "mark");
|
processComputable(bar as T, "mark");
|
||||||
|
|
||||||
|
for (const decorator of decorators) {
|
||||||
|
decorator.postConstruct?.(bar);
|
||||||
|
}
|
||||||
|
|
||||||
|
const decoratedProps = decorators.reduce((current, next) => Object.assign(current, next.getGatheredProps?.(bar)), {});
|
||||||
bar[GatherProps] = function (this: GenericBar) {
|
bar[GatherProps] = function (this: GenericBar) {
|
||||||
const {
|
const {
|
||||||
progress,
|
progress,
|
||||||
|
@ -125,7 +139,8 @@ export function createBar<T extends BarOptions>(
|
||||||
baseStyle,
|
baseStyle,
|
||||||
fillStyle,
|
fillStyle,
|
||||||
mark,
|
mark,
|
||||||
id
|
id,
|
||||||
|
...decoratedProps
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,269 +0,0 @@
|
||||||
import ClickableComponent from "features/clickables/Clickable.vue";
|
|
||||||
import type {
|
|
||||||
CoercableComponent,
|
|
||||||
GenericComponent,
|
|
||||||
OptionsFunc,
|
|
||||||
Replace,
|
|
||||||
StyleValue
|
|
||||||
} from "features/feature";
|
|
||||||
import { Component, GatherProps, getUniqueID, jsx, setDefault, Visibility } from "features/feature";
|
|
||||||
import type { Resource } from "features/resources/resource";
|
|
||||||
import { DefaultValue, Persistent } from "game/persistence";
|
|
||||||
import { persistent } from "game/persistence";
|
|
||||||
import type { DecimalSource } from "util/bignum";
|
|
||||||
import Decimal, { format, formatWhole } from "util/bignum";
|
|
||||||
import type {
|
|
||||||
Computable,
|
|
||||||
GetComputableType,
|
|
||||||
GetComputableTypeWithDefault,
|
|
||||||
ProcessedComputable
|
|
||||||
} from "util/computed";
|
|
||||||
import { processComputable } from "util/computed";
|
|
||||||
import { createLazyProxy } from "util/proxies";
|
|
||||||
import { coerceComponent, isCoercableComponent } from "util/vue";
|
|
||||||
import type { Ref } from "vue";
|
|
||||||
import { computed, unref } from "vue";
|
|
||||||
import { Decorator } from "./decorators";
|
|
||||||
|
|
||||||
export const BuyableType = Symbol("Buyable");
|
|
||||||
|
|
||||||
export type BuyableDisplay =
|
|
||||||
| CoercableComponent
|
|
||||||
| {
|
|
||||||
title?: CoercableComponent;
|
|
||||||
description?: CoercableComponent;
|
|
||||||
effectDisplay?: CoercableComponent;
|
|
||||||
showAmount?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface BuyableOptions {
|
|
||||||
visibility?: Computable<Visibility>;
|
|
||||||
cost?: Computable<DecimalSource>;
|
|
||||||
resource?: Resource;
|
|
||||||
canPurchase?: Computable<boolean>;
|
|
||||||
purchaseLimit?: Computable<DecimalSource>;
|
|
||||||
initialValue?: DecimalSource;
|
|
||||||
classes?: Computable<Record<string, boolean>>;
|
|
||||||
style?: Computable<StyleValue>;
|
|
||||||
mark?: Computable<boolean | string>;
|
|
||||||
small?: Computable<boolean>;
|
|
||||||
display?: Computable<BuyableDisplay>;
|
|
||||||
onPurchase?: (cost: DecimalSource | undefined) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BaseBuyable {
|
|
||||||
id: string;
|
|
||||||
amount: Persistent<DecimalSource>;
|
|
||||||
maxed: Ref<boolean>;
|
|
||||||
canAfford: Ref<boolean>;
|
|
||||||
canClick: ProcessedComputable<boolean>;
|
|
||||||
onClick: VoidFunction;
|
|
||||||
purchase: VoidFunction;
|
|
||||||
type: typeof BuyableType;
|
|
||||||
[Component]: typeof ClickableComponent;
|
|
||||||
[GatherProps]: () => Record<string, unknown>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Buyable<T extends BuyableOptions> = Replace<
|
|
||||||
T & BaseBuyable,
|
|
||||||
{
|
|
||||||
visibility: GetComputableTypeWithDefault<T["visibility"], Visibility.Visible>;
|
|
||||||
cost: GetComputableType<T["cost"]>;
|
|
||||||
resource: GetComputableType<T["resource"]>;
|
|
||||||
canPurchase: GetComputableTypeWithDefault<T["canPurchase"], Ref<boolean>>;
|
|
||||||
purchaseLimit: GetComputableTypeWithDefault<T["purchaseLimit"], Decimal>;
|
|
||||||
classes: GetComputableType<T["classes"]>;
|
|
||||||
style: GetComputableType<T["style"]>;
|
|
||||||
mark: GetComputableType<T["mark"]>;
|
|
||||||
small: GetComputableType<T["small"]>;
|
|
||||||
display: Ref<CoercableComponent>;
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
|
|
||||||
export type GenericBuyable = Replace<
|
|
||||||
Buyable<BuyableOptions>,
|
|
||||||
{
|
|
||||||
visibility: ProcessedComputable<Visibility>;
|
|
||||||
canPurchase: ProcessedComputable<boolean>;
|
|
||||||
purchaseLimit: ProcessedComputable<DecimalSource>;
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
|
|
||||||
export function createBuyable<T extends BuyableOptions>(
|
|
||||||
optionsFunc: OptionsFunc<T, BaseBuyable, GenericBuyable>,
|
|
||||||
...decorators: Decorator<T, BaseBuyable, GenericBuyable>[]
|
|
||||||
): Buyable<T> {
|
|
||||||
const amount = persistent<DecimalSource>(0);
|
|
||||||
|
|
||||||
const persistents = decorators.reduce((current, next) => Object.assign(current, next.getPersistents?.()), {});
|
|
||||||
|
|
||||||
return createLazyProxy(() => {
|
|
||||||
const buyable = optionsFunc();
|
|
||||||
|
|
||||||
if (buyable.canPurchase == null && (buyable.resource == null || buyable.cost == null)) {
|
|
||||||
console.warn(
|
|
||||||
"Cannot create buyable without a canPurchase property or a resource and cost property",
|
|
||||||
buyable
|
|
||||||
);
|
|
||||||
throw "Cannot create buyable without a canPurchase property or a resource and cost property";
|
|
||||||
}
|
|
||||||
|
|
||||||
buyable.id = getUniqueID("buyable-");
|
|
||||||
buyable.type = BuyableType;
|
|
||||||
buyable[Component] = ClickableComponent;
|
|
||||||
|
|
||||||
for (const decorator of decorators) {
|
|
||||||
decorator.preConstruct?.(buyable);
|
|
||||||
}
|
|
||||||
|
|
||||||
buyable.amount = amount;
|
|
||||||
buyable.amount[DefaultValue] = buyable.initialValue ?? 0;
|
|
||||||
|
|
||||||
Object.assign(buyable, persistents);
|
|
||||||
|
|
||||||
buyable.canAfford = computed(() => {
|
|
||||||
const genericBuyable = buyable as GenericBuyable;
|
|
||||||
const cost = unref(genericBuyable.cost);
|
|
||||||
return (
|
|
||||||
genericBuyable.resource != null &&
|
|
||||||
cost != null &&
|
|
||||||
Decimal.gte(genericBuyable.resource.value, cost)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
if (buyable.canPurchase == null) {
|
|
||||||
buyable.canPurchase = computed(
|
|
||||||
() =>
|
|
||||||
unref((buyable as GenericBuyable).visibility) === Visibility.Visible &&
|
|
||||||
unref((buyable as GenericBuyable).canAfford) &&
|
|
||||||
Decimal.lt(
|
|
||||||
(buyable as GenericBuyable).amount.value,
|
|
||||||
unref((buyable as GenericBuyable).purchaseLimit)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
});
|
|
||||||
processComputable(buyable as T, "canPurchase");
|
|
||||||
buyable.canClick = buyable.canPurchase as ProcessedComputable<boolean>;
|
|
||||||
buyable.onClick = buyable.purchase =
|
|
||||||
buyable.onClick ??
|
|
||||||
buyable.purchase ??
|
|
||||||
function (this: GenericBuyable) {
|
|
||||||
const genericBuyable = buyable as GenericBuyable;
|
|
||||||
if (!unref(genericBuyable.canPurchase)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const cost = unref(genericBuyable.cost);
|
|
||||||
if (genericBuyable.cost != null && genericBuyable.resource != null) {
|
|
||||||
genericBuyable.resource.value = Decimal.sub(
|
|
||||||
genericBuyable.resource.value,
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
||||||
cost!
|
|
||||||
);
|
|
||||||
genericBuyable.amount.value = Decimal.add(genericBuyable.amount.value, 1);
|
|
||||||
}
|
|
||||||
genericBuyable.onPurchase?.(cost);
|
|
||||||
};
|
|
||||||
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)) {
|
|
||||||
const CurrDisplay = coerceComponent(currDisplay);
|
|
||||||
return <CurrDisplay />;
|
|
||||||
}
|
|
||||||
if (currDisplay != null && buyable.cost != null && buyable.resource != 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>
|
|
||||||
)}
|
|
||||||
{genericBuyable.cost != null && !genericBuyable.maxed.value ? (
|
|
||||||
<div>
|
|
||||||
<br />
|
|
||||||
Cost: {format(unref(genericBuyable.cost))}{" "}
|
|
||||||
{buyable.resource.displayName}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
});
|
|
||||||
|
|
||||||
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", Decimal.dInf);
|
|
||||||
processComputable(buyable as T, "style");
|
|
||||||
processComputable(buyable as T, "mark");
|
|
||||||
processComputable(buyable as T, "small");
|
|
||||||
|
|
||||||
const gatheredProps = decorators.reduce((current, next) => Object.assign(current, next.getGatheredProps?.(buyable)), {});
|
|
||||||
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,
|
|
||||||
...gatheredProps
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const decorator of decorators) {
|
|
||||||
decorator.postConstruct?.(buyable);
|
|
||||||
}
|
|
||||||
|
|
||||||
return buyable as unknown as Buyable<T>;
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { isArray } from "@vue/shared";
|
import { isArray } from "@vue/shared";
|
||||||
import Toggle from "components/fields/Toggle.vue";
|
import Toggle from "components/fields/Toggle.vue";
|
||||||
import ChallengeComponent from "features/challenges/Challenge.vue";
|
import ChallengeComponent from "features/challenges/Challenge.vue";
|
||||||
|
import { Decorator } from "features/decorators";
|
||||||
import type { CoercableComponent, OptionsFunc, Replace, StyleValue } from "features/feature";
|
import type { CoercableComponent, OptionsFunc, Replace, StyleValue } from "features/feature";
|
||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
|
@ -98,10 +99,12 @@ export type GenericChallenge = Replace<
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function createChallenge<T extends ChallengeOptions>(
|
export function createChallenge<T extends ChallengeOptions>(
|
||||||
optionsFunc: OptionsFunc<T, BaseChallenge, GenericChallenge>
|
optionsFunc: OptionsFunc<T, BaseChallenge, GenericChallenge>,
|
||||||
|
...decorators: Decorator<T, BaseChallenge, GenericChallenge>[]
|
||||||
): Challenge<T> {
|
): Challenge<T> {
|
||||||
const completions = persistent(0);
|
const completions = persistent(0);
|
||||||
const active = persistent(false);
|
const active = persistent(false);
|
||||||
|
const decoratedData = decorators.reduce((current, next) => Object.assign(current, next.getPersistentData?.()), {});
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const challenge = optionsFunc();
|
const challenge = optionsFunc();
|
||||||
|
|
||||||
|
@ -120,8 +123,14 @@ export function createChallenge<T extends ChallengeOptions>(
|
||||||
challenge.type = ChallengeType;
|
challenge.type = ChallengeType;
|
||||||
challenge[Component] = ChallengeComponent;
|
challenge[Component] = ChallengeComponent;
|
||||||
|
|
||||||
|
for (const decorator of decorators) {
|
||||||
|
decorator.preConstruct?.(challenge);
|
||||||
|
}
|
||||||
|
|
||||||
challenge.completions = completions;
|
challenge.completions = completions;
|
||||||
challenge.active = active;
|
challenge.active = active;
|
||||||
|
Object.assign(challenge, decoratedData);
|
||||||
|
|
||||||
challenge.completed = computed(() =>
|
challenge.completed = computed(() =>
|
||||||
Decimal.gt((challenge as GenericChallenge).completions.value, 0)
|
Decimal.gt((challenge as GenericChallenge).completions.value, 0)
|
||||||
);
|
);
|
||||||
|
@ -234,6 +243,11 @@ export function createChallenge<T extends ChallengeOptions>(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const decorator of decorators) {
|
||||||
|
decorator.postConstruct?.(challenge);
|
||||||
|
}
|
||||||
|
|
||||||
|
const decoratedProps = decorators.reduce((current, next) => Object.assign(current, next.getGatheredProps?.(challenge)), {});
|
||||||
challenge[GatherProps] = function (this: GenericChallenge) {
|
challenge[GatherProps] = function (this: GenericChallenge) {
|
||||||
const {
|
const {
|
||||||
active,
|
active,
|
||||||
|
@ -261,7 +275,8 @@ export function createChallenge<T extends ChallengeOptions>(
|
||||||
canStart,
|
canStart,
|
||||||
mark,
|
mark,
|
||||||
id,
|
id,
|
||||||
toggle
|
toggle,
|
||||||
|
...decoratedProps
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import ClickableComponent from "features/clickables/Clickable.vue";
|
import ClickableComponent from "features/clickables/Clickable.vue";
|
||||||
|
import { Decorator } from "features/decorators";
|
||||||
import type {
|
import type {
|
||||||
CoercableComponent,
|
CoercableComponent,
|
||||||
GenericComponent,
|
GenericComponent,
|
||||||
|
@ -67,14 +68,22 @@ export type GenericClickable = Replace<
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function createClickable<T extends ClickableOptions>(
|
export function createClickable<T extends ClickableOptions>(
|
||||||
optionsFunc?: OptionsFunc<T, BaseClickable, GenericClickable>
|
optionsFunc?: OptionsFunc<T, BaseClickable, GenericClickable>,
|
||||||
|
...decorators: Decorator<T, BaseClickable, GenericClickable>[]
|
||||||
): Clickable<T> {
|
): Clickable<T> {
|
||||||
|
const decoratedData = decorators.reduce((current, next) => Object.assign(current, next.getPersistentData?.()), {});
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const clickable = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>);
|
const clickable = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>);
|
||||||
clickable.id = getUniqueID("clickable-");
|
clickable.id = getUniqueID("clickable-");
|
||||||
clickable.type = ClickableType;
|
clickable.type = ClickableType;
|
||||||
clickable[Component] = ClickableComponent;
|
clickable[Component] = ClickableComponent;
|
||||||
|
|
||||||
|
for (const decorator of decorators) {
|
||||||
|
decorator.preConstruct?.(clickable);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(clickable, decoratedData);
|
||||||
|
|
||||||
processComputable(clickable as T, "visibility");
|
processComputable(clickable as T, "visibility");
|
||||||
setDefault(clickable, "visibility", Visibility.Visible);
|
setDefault(clickable, "visibility", Visibility.Visible);
|
||||||
processComputable(clickable as T, "canClick");
|
processComputable(clickable as T, "canClick");
|
||||||
|
@ -101,6 +110,11 @@ export function createClickable<T extends ClickableOptions>(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const decorator of decorators) {
|
||||||
|
decorator.postConstruct?.(clickable);
|
||||||
|
}
|
||||||
|
|
||||||
|
const decoratedProps = decorators.reduce((current, next) => Object.assign(current, next.getGatheredProps?.(clickable)), {});
|
||||||
clickable[GatherProps] = function (this: GenericClickable) {
|
clickable[GatherProps] = function (this: GenericClickable) {
|
||||||
const {
|
const {
|
||||||
display,
|
display,
|
||||||
|
@ -124,7 +138,8 @@ export function createClickable<T extends ClickableOptions>(
|
||||||
canClick,
|
canClick,
|
||||||
small,
|
small,
|
||||||
mark,
|
mark,
|
||||||
id
|
id,
|
||||||
|
...decoratedProps
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { convertComputable, processComputable } from "util/computed";
|
||||||
import { createLazyProxy } from "util/proxies";
|
import { createLazyProxy } from "util/proxies";
|
||||||
import type { Ref } from "vue";
|
import type { Ref } from "vue";
|
||||||
import { computed, unref } from "vue";
|
import { computed, unref } from "vue";
|
||||||
|
import { Decorator } from "./decorators";
|
||||||
|
|
||||||
/** An object that configures a {@link Conversion}. */
|
/** An object that configures a {@link Conversion}. */
|
||||||
export interface ConversionOptions {
|
export interface ConversionOptions {
|
||||||
|
@ -135,11 +136,16 @@ export type GenericConversion = Replace<
|
||||||
* @see {@link createIndependentConversion}.
|
* @see {@link createIndependentConversion}.
|
||||||
*/
|
*/
|
||||||
export function createConversion<T extends ConversionOptions>(
|
export function createConversion<T extends ConversionOptions>(
|
||||||
optionsFunc: OptionsFunc<T, BaseConversion, GenericConversion>
|
optionsFunc: OptionsFunc<T, BaseConversion, GenericConversion>,
|
||||||
|
...decorators: Decorator<T, BaseConversion, GenericConversion>[]
|
||||||
): Conversion<T> {
|
): Conversion<T> {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const conversion = optionsFunc();
|
const conversion = optionsFunc();
|
||||||
|
|
||||||
|
for (const decorator of decorators) {
|
||||||
|
decorator.preConstruct?.(conversion);
|
||||||
|
}
|
||||||
|
|
||||||
if (conversion.currentGain == null) {
|
if (conversion.currentGain == null) {
|
||||||
conversion.currentGain = computed(() => {
|
conversion.currentGain = computed(() => {
|
||||||
let gain = conversion.gainModifier
|
let gain = conversion.gainModifier
|
||||||
|
@ -201,6 +207,10 @@ export function createConversion<T extends ConversionOptions>(
|
||||||
processComputable(conversion as T, "roundUpCost");
|
processComputable(conversion as T, "roundUpCost");
|
||||||
setDefault(conversion, "roundUpCost", true);
|
setDefault(conversion, "roundUpCost", true);
|
||||||
|
|
||||||
|
for (const decorator of decorators) {
|
||||||
|
decorator.postConstruct?.(conversion);
|
||||||
|
}
|
||||||
|
|
||||||
return conversion as unknown as Conversion<T>;
|
return conversion as unknown as Conversion<T>;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,49 +1,35 @@
|
||||||
import { Replace, OptionsObject } from "./feature";
|
import { Replace, OptionsObject } from "./feature";
|
||||||
import Decimal, { DecimalSource } from "util/bignum";
|
import Decimal, { DecimalSource } from "util/bignum";
|
||||||
import { Computable, GetComputableType, processComputable, ProcessedComputable } from "util/computed";
|
import { Computable, GetComputableType, processComputable, ProcessedComputable } from "util/computed";
|
||||||
import { AchievementOptions, BaseAchievement, GenericAchievement } from "./achievements/achievement";
|
|
||||||
import { BarOptions, BaseBar, GenericBar } from "./bars/bar";
|
|
||||||
import { BaseBuyable, BuyableOptions, GenericBuyable } from "./buyable";
|
|
||||||
import { BaseChallenge, ChallengeOptions, GenericChallenge } from "./challenges/challenge";
|
|
||||||
import { BaseClickable, ClickableOptions, GenericClickable } from "./clickables/clickable";
|
|
||||||
import { BaseMilestone, GenericMilestone, MilestoneOptions } from "./milestones/milestone";
|
|
||||||
import { BaseUpgrade, GenericUpgrade, UpgradeOptions } from "./upgrades/upgrade";
|
|
||||||
import { Persistent, State } from "game/persistence";
|
import { Persistent, State } from "game/persistence";
|
||||||
import { computed, Ref, unref } from "vue";
|
import { computed, Ref, unref } from "vue";
|
||||||
|
|
||||||
type FeatureOptions = AchievementOptions | BarOptions | BuyableOptions | ChallengeOptions | ClickableOptions | MilestoneOptions | UpgradeOptions;
|
|
||||||
|
|
||||||
type BaseFeature = BaseAchievement | BaseBar | BaseBuyable | BaseChallenge | BaseClickable | BaseMilestone | BaseUpgrade;
|
|
||||||
|
|
||||||
type GenericFeature = GenericAchievement | GenericBar | GenericBuyable | GenericChallenge | GenericClickable | GenericMilestone | GenericUpgrade;
|
|
||||||
|
|
||||||
/*----====----*/
|
/*----====----*/
|
||||||
|
|
||||||
export type Decorator<Options extends FeatureOptions, Base extends BaseFeature, Generic extends GenericFeature, S extends State = State> = {
|
export type Decorator<FeatureOptions, BaseFeature = {}, GenericFeature = {}, S extends State = State> = {
|
||||||
getPersistents?(): Record<string, Persistent<S>>;
|
getPersistentData?(): Record<string, Persistent<S>>;
|
||||||
preConstruct?(feature: OptionsObject<Options,Base,Generic>): void;
|
preConstruct?(feature: OptionsObject<FeatureOptions,BaseFeature,GenericFeature>): void;
|
||||||
postConstruct?(feature: OptionsObject<Options,Base,Generic>): void;
|
postConstruct?(feature: OptionsObject<FeatureOptions,BaseFeature,GenericFeature>): void;
|
||||||
getGatheredProps?(feature: OptionsObject<Options,Base,Generic>): Partial<OptionsObject<Options,Base,Generic>>
|
getGatheredProps?(feature: OptionsObject<FeatureOptions,BaseFeature,GenericFeature>): Partial<OptionsObject<FeatureOptions,BaseFeature,GenericFeature>>
|
||||||
}
|
}
|
||||||
|
|
||||||
/*----====----*/
|
/*----====----*/
|
||||||
|
|
||||||
// #region Effect Decorator
|
// #region Effect Decorator
|
||||||
export type EffectFeatureOptions = {
|
export interface EffectFeatureOptions {
|
||||||
effect: Computable<any>;
|
effect: Computable<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type EffectFeature<T extends EffectFeatureOptions, U extends BaseFeature> = Replace<
|
export type EffectFeature<T extends EffectFeatureOptions> = Replace<
|
||||||
T & U,
|
T, { effect: GetComputableType<T["effect"]>; }
|
||||||
{ effect: GetComputableType<T["effect"]>; }
|
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type GenericEffectFeature<T extends GenericFeature> = T & Replace<
|
export type GenericEffectFeature = Replace<
|
||||||
EffectFeature<EffectFeatureOptions, BaseFeature>,
|
EffectFeature<EffectFeatureOptions>,
|
||||||
{ effect: ProcessedComputable<any>; }
|
{ effect: ProcessedComputable<any>; }
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export const effectDecorator: Decorator<FeatureOptions & EffectFeatureOptions, BaseFeature, GenericFeature & BaseFeature> = {
|
export const effectDecorator: Decorator<EffectFeatureOptions, {}, GenericEffectFeature> = {
|
||||||
postConstruct(feature) {
|
postConstruct(feature) {
|
||||||
processComputable(feature, "effect");
|
processComputable(feature, "effect");
|
||||||
}
|
}
|
||||||
|
@ -52,31 +38,46 @@ export const effectDecorator: Decorator<FeatureOptions & EffectFeatureOptions, B
|
||||||
|
|
||||||
/*----====----*/
|
/*----====----*/
|
||||||
|
|
||||||
// #region Bonus Amount Decorator
|
// #region Bonus Amount/Completions Decorator
|
||||||
export interface BonusFeatureOptions {
|
export interface BonusAmountFeatureOptions {
|
||||||
bonusAmount: Computable<DecimalSource>;
|
bonusAmount: Computable<DecimalSource>;
|
||||||
}
|
}
|
||||||
|
export interface BonusCompletionsFeatureOptions {
|
||||||
export type BaseBonusFeature = BaseFeature & {
|
bonusCompletions: Computable<DecimalSource>;
|
||||||
totalAmount: Ref<DecimalSource>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BonusAmountFeature<T extends BonusFeatureOptions, U extends BaseBonusFeature> = Replace<
|
export interface BaseBonusAmountFeature {
|
||||||
T & U,
|
amount: Ref<DecimalSource>;
|
||||||
{
|
totalAmount: Ref<DecimalSource>;
|
||||||
bonusAmount: GetComputableType<T["bonusAmount"]>;
|
}
|
||||||
}
|
export interface BaseBonusCompletionsFeature {
|
||||||
|
completions: Ref<DecimalSource>;
|
||||||
|
totalCompletions: Ref<DecimalSource>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BonusAmountFeature<T extends BonusAmountFeatureOptions> = Replace<
|
||||||
|
T, { bonusAmount: GetComputableType<T["bonusAmount"]>; }
|
||||||
|
>;
|
||||||
|
export type BonusCompletionsFeature<T extends BonusCompletionsFeatureOptions> = Replace<
|
||||||
|
T, { bonusAmount: GetComputableType<T["bonusCompletions"]>; }
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type GenericBonusFeature<T extends GenericFeature> = Replace<
|
export type GenericBonusAmountFeature = Replace<
|
||||||
T & BonusAmountFeature<BonusFeatureOptions, BaseBonusFeature>,
|
BonusAmountFeature<BonusAmountFeatureOptions>,
|
||||||
{
|
{
|
||||||
bonusAmount: ProcessedComputable<DecimalSource>;
|
bonusAmount: ProcessedComputable<DecimalSource>;
|
||||||
totalAmount: ProcessedComputable<DecimalSource>;
|
totalAmount: ProcessedComputable<DecimalSource>;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
export type GenericBonusCompletionsFeature = Replace<
|
||||||
|
BonusCompletionsFeature<BonusCompletionsFeatureOptions>,
|
||||||
|
{
|
||||||
|
bonusCompletions: ProcessedComputable<DecimalSource>;
|
||||||
|
totalCompletions: ProcessedComputable<DecimalSource>;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
export const bonusAmountDecorator: Decorator<FeatureOptions & BonusFeatureOptions, BaseBonusFeature & {amount: ProcessedComputable<DecimalSource>}, GenericFeature & BaseBonusFeature & {amount: ProcessedComputable<DecimalSource>}> = {
|
export const bonusAmountDecorator: Decorator<BonusAmountFeatureOptions, BaseBonusAmountFeature, GenericBonusAmountFeature> = {
|
||||||
postConstruct(feature) {
|
postConstruct(feature) {
|
||||||
processComputable(feature, "bonusAmount");
|
processComputable(feature, "bonusAmount");
|
||||||
if (feature.totalAmount === undefined) {
|
if (feature.totalAmount === undefined) {
|
||||||
|
@ -87,30 +88,17 @@ export const bonusAmountDecorator: Decorator<FeatureOptions & BonusFeatureOption
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export const bonusCompletionsDecorator: Decorator<BonusAmountFeatureOptions, BaseBonusCompletionsFeature, GenericBonusCompletionsFeature> = {
|
||||||
export const bonusCompletionsDecorator: Decorator<FeatureOptions & BonusFeatureOptions, BaseBonusFeature & {completions: ProcessedComputable<DecimalSource>}, GenericFeature & BaseBonusFeature & {completions: ProcessedComputable<DecimalSource>}> = {
|
|
||||||
postConstruct(feature) {
|
postConstruct(feature) {
|
||||||
processComputable(feature, "bonusAmount");
|
processComputable(feature, "bonusAmount");
|
||||||
if (feature.totalAmount === undefined) {
|
if (feature.totalCompletions === undefined) {
|
||||||
feature.totalAmount = computed(() => Decimal.add(
|
feature.totalCompletions = computed(() => Decimal.add(
|
||||||
unref(feature.completions ?? 0),
|
unref(feature.completions ?? 0),
|
||||||
unref(feature.bonusAmount as ProcessedComputable<DecimalSource>)
|
unref(feature.bonusAmount as ProcessedComputable<DecimalSource>)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const bonusEarnedDecorator: Decorator<FeatureOptions & BonusFeatureOptions, BaseBonusFeature & {earned: ProcessedComputable<boolean>}, GenericFeature & BaseBonusFeature & {earned: ProcessedComputable<boolean>}> = {
|
|
||||||
postConstruct(feature) {
|
|
||||||
processComputable(feature, "bonusAmount");
|
|
||||||
if (feature.totalAmount === undefined) {
|
|
||||||
feature.totalAmount = computed(() => unref(feature.earned ?? false)
|
|
||||||
? Decimal.add(unref(feature.bonusAmount as ProcessedComputable<DecimalSource>), 1)
|
|
||||||
: unref(feature.bonusAmount as ProcessedComputable<DecimalSource>)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
/*----====----*/
|
/*----====----*/
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import Select from "components/fields/Select.vue";
|
import Select from "components/fields/Select.vue";
|
||||||
|
import { Decorator } from "features/decorators";
|
||||||
import type {
|
import type {
|
||||||
CoercableComponent,
|
CoercableComponent,
|
||||||
GenericComponent,
|
GenericComponent,
|
||||||
|
@ -92,16 +93,23 @@ export type GenericMilestone = Replace<
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function createMilestone<T extends MilestoneOptions>(
|
export function createMilestone<T extends MilestoneOptions>(
|
||||||
optionsFunc?: OptionsFunc<T, BaseMilestone, GenericMilestone>
|
optionsFunc?: OptionsFunc<T, BaseMilestone, GenericMilestone>,
|
||||||
|
...decorators: Decorator<T, BaseMilestone, GenericMilestone>[]
|
||||||
): Milestone<T> {
|
): Milestone<T> {
|
||||||
const earned = persistent<boolean>(false);
|
const earned = persistent<boolean>(false);
|
||||||
|
const decoratedData = decorators.reduce((current, next) => Object.assign(current, next.getPersistentData?.()), {});
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const milestone = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>);
|
const milestone = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>);
|
||||||
milestone.id = getUniqueID("milestone-");
|
milestone.id = getUniqueID("milestone-");
|
||||||
milestone.type = MilestoneType;
|
milestone.type = MilestoneType;
|
||||||
milestone[Component] = MilestoneComponent;
|
milestone[Component] = MilestoneComponent;
|
||||||
|
|
||||||
|
for (const decorator of decorators) {
|
||||||
|
decorator.preConstruct?.(milestone);
|
||||||
|
}
|
||||||
|
|
||||||
milestone.earned = earned;
|
milestone.earned = earned;
|
||||||
|
Object.assign(milestone, decoratedData);
|
||||||
milestone.complete = function () {
|
milestone.complete = function () {
|
||||||
const genericMilestone = milestone as GenericMilestone;
|
const genericMilestone = milestone as GenericMilestone;
|
||||||
earned.value = true;
|
earned.value = true;
|
||||||
|
@ -160,9 +168,14 @@ export function createMilestone<T extends MilestoneOptions>(
|
||||||
processComputable(milestone as T, "display");
|
processComputable(milestone as T, "display");
|
||||||
processComputable(milestone as T, "showPopups");
|
processComputable(milestone as T, "showPopups");
|
||||||
|
|
||||||
|
for (const decorator of decorators) {
|
||||||
|
decorator.postConstruct?.(milestone);
|
||||||
|
}
|
||||||
|
|
||||||
|
const decoratedProps = decorators.reduce((current, next) => Object.assign(current, next?.getGatheredProps?.(milestone)), {});
|
||||||
milestone[GatherProps] = function (this: GenericMilestone) {
|
milestone[GatherProps] = function (this: GenericMilestone) {
|
||||||
const { visibility, display, style, classes, earned, id } = this;
|
const { visibility, display, style, classes, earned, id } = this;
|
||||||
return { visibility, display, style: unref(style), classes, earned, id };
|
return { visibility, display, style: unref(style), classes, earned, id, ...decoratedProps };
|
||||||
};
|
};
|
||||||
|
|
||||||
if (milestone.shouldEarn) {
|
if (milestone.shouldEarn) {
|
||||||
|
|
|
@ -24,6 +24,7 @@ import { createLazyProxy } from "util/proxies";
|
||||||
import { coerceComponent, isCoercableComponent } from "util/vue";
|
import { coerceComponent, isCoercableComponent } from "util/vue";
|
||||||
import type { Ref } from "vue";
|
import type { Ref } from "vue";
|
||||||
import { computed, unref } from "vue";
|
import { computed, unref } from "vue";
|
||||||
|
import { Decorator, GenericBonusAmountFeature } from "./decorators";
|
||||||
|
|
||||||
/** A symbol used to identify {@link Repeatable} features. */
|
/** A symbol used to identify {@link Repeatable} features. */
|
||||||
export const RepeatableType = Symbol("Repeatable");
|
export const RepeatableType = Symbol("Repeatable");
|
||||||
|
@ -118,9 +119,11 @@ export type GenericRepeatable = Replace<
|
||||||
* @param optionsFunc Repeatable options.
|
* @param optionsFunc Repeatable options.
|
||||||
*/
|
*/
|
||||||
export function createRepeatable<T extends RepeatableOptions>(
|
export function createRepeatable<T extends RepeatableOptions>(
|
||||||
optionsFunc: OptionsFunc<T, BaseRepeatable, GenericRepeatable>
|
optionsFunc: OptionsFunc<T, BaseRepeatable, GenericRepeatable>,
|
||||||
|
...decorators: Decorator<T, BaseRepeatable, GenericRepeatable>[]
|
||||||
): Repeatable<T> {
|
): Repeatable<T> {
|
||||||
const amount = persistent<DecimalSource>(0);
|
const amount = persistent<DecimalSource>(0);
|
||||||
|
const decoratedData = decorators.reduce((current, next) => Object.assign(current, next.getPersistentData?.()), {});
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const repeatable = optionsFunc();
|
const repeatable = optionsFunc();
|
||||||
|
|
||||||
|
@ -128,9 +131,15 @@ export function createRepeatable<T extends RepeatableOptions>(
|
||||||
repeatable.type = RepeatableType;
|
repeatable.type = RepeatableType;
|
||||||
repeatable[Component] = ClickableComponent;
|
repeatable[Component] = ClickableComponent;
|
||||||
|
|
||||||
|
for (const decorator of decorators) {
|
||||||
|
decorator.preConstruct?.(repeatable);
|
||||||
|
}
|
||||||
|
|
||||||
repeatable.amount = amount;
|
repeatable.amount = amount;
|
||||||
repeatable.amount[DefaultValue] = repeatable.initialAmount ?? 0;
|
repeatable.amount[DefaultValue] = repeatable.initialAmount ?? 0;
|
||||||
|
|
||||||
|
Object.assign(repeatable, decoratedData);
|
||||||
|
|
||||||
const limitRequirement = {
|
const limitRequirement = {
|
||||||
requirementMet: computed(() =>
|
requirementMet: computed(() =>
|
||||||
Decimal.sub(
|
Decimal.sub(
|
||||||
|
@ -212,14 +221,17 @@ export function createRepeatable<T extends RepeatableOptions>(
|
||||||
{currDisplay.showAmount === false ? null : (
|
{currDisplay.showAmount === false ? null : (
|
||||||
<div>
|
<div>
|
||||||
<br />
|
<br />
|
||||||
{unref(genericRepeatable.limit) === Decimal.dInf ? (
|
joinJSX(
|
||||||
<>Amount: {formatWhole(genericRepeatable.amount.value)}</>
|
<>Amount: {formatWhole(genericRepeatable.amount.value)}</>,
|
||||||
) : (
|
{unref(genericRepeatable.limit) !== Decimal.dInf ? (
|
||||||
<>
|
<> / {formatWhole(unref(genericRepeatable.limit))}</>
|
||||||
Amount: {formatWhole(genericRepeatable.amount.value)} /{" "}
|
) : undefined},
|
||||||
{formatWhole(unref(genericRepeatable.limit))}
|
{(genericRepeatable as GenericRepeatable & GenericBonusAmountFeature).bonusAmount == null ? null : (
|
||||||
</>
|
Decimal.gt(unref((genericRepeatable as GenericRepeatable & GenericBonusAmountFeature).bonusAmount), 0) ? (
|
||||||
)}
|
<> + {formatWhole(unref((genericRepeatable as GenericRepeatable & GenericBonusAmountFeature).bonusAmount))}</>
|
||||||
|
) : undefined)
|
||||||
|
}
|
||||||
|
)
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{currDisplay.effectDisplay == null ? null : (
|
{currDisplay.effectDisplay == null ? null : (
|
||||||
|
@ -254,6 +266,11 @@ export function createRepeatable<T extends RepeatableOptions>(
|
||||||
processComputable(repeatable as T, "small");
|
processComputable(repeatable as T, "small");
|
||||||
processComputable(repeatable as T, "maximize");
|
processComputable(repeatable as T, "maximize");
|
||||||
|
|
||||||
|
for (const decorator of decorators) {
|
||||||
|
decorator.postConstruct?.(repeatable);
|
||||||
|
}
|
||||||
|
|
||||||
|
const decoratedProps = decorators.reduce((current, next) => Object.assign(current, next.getGatheredProps?.(repeatable)), {});
|
||||||
repeatable[GatherProps] = function (this: GenericRepeatable) {
|
repeatable[GatherProps] = function (this: GenericRepeatable) {
|
||||||
const { display, visibility, style, classes, onClick, canClick, small, mark, id } =
|
const { display, visibility, style, classes, onClick, canClick, small, mark, id } =
|
||||||
this;
|
this;
|
||||||
|
@ -266,7 +283,8 @@ export function createRepeatable<T extends RepeatableOptions>(
|
||||||
canClick,
|
canClick,
|
||||||
small,
|
small,
|
||||||
mark,
|
mark,
|
||||||
id
|
id,
|
||||||
|
...decoratedProps
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { Decorator } from "features/decorators";
|
||||||
import type { CoercableComponent, OptionsFunc, Replace, StyleValue } from "features/feature";
|
import type { CoercableComponent, OptionsFunc, Replace, StyleValue } from "features/feature";
|
||||||
import { Component, GatherProps, getUniqueID, setDefault, Visibility } from "features/feature";
|
import { Component, GatherProps, getUniqueID, setDefault, Visibility } from "features/feature";
|
||||||
import type { Link } from "features/links/links";
|
import type { Link } from "features/links/links";
|
||||||
|
@ -66,14 +67,22 @@ export type GenericTreeNode = Replace<
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function createTreeNode<T extends TreeNodeOptions>(
|
export function createTreeNode<T extends TreeNodeOptions>(
|
||||||
optionsFunc?: OptionsFunc<T, BaseTreeNode, GenericTreeNode>
|
optionsFunc?: OptionsFunc<T, BaseTreeNode, GenericTreeNode>,
|
||||||
|
...decorators: Decorator<T, BaseTreeNode, GenericTreeNode>[]
|
||||||
): TreeNode<T> {
|
): TreeNode<T> {
|
||||||
|
const decoratedData = decorators.reduce((current, next) => Object.assign(current, next.getPersistentData?.()), {});
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const treeNode = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>);
|
const treeNode = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>);
|
||||||
treeNode.id = getUniqueID("treeNode-");
|
treeNode.id = getUniqueID("treeNode-");
|
||||||
treeNode.type = TreeNodeType;
|
treeNode.type = TreeNodeType;
|
||||||
treeNode[Component] = TreeNodeComponent;
|
treeNode[Component] = TreeNodeComponent;
|
||||||
|
|
||||||
|
for (const decorator of decorators) {
|
||||||
|
decorator.preConstruct?.(treeNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(decoratedData);
|
||||||
|
|
||||||
processComputable(treeNode as T, "visibility");
|
processComputable(treeNode as T, "visibility");
|
||||||
setDefault(treeNode, "visibility", Visibility.Visible);
|
setDefault(treeNode, "visibility", Visibility.Visible);
|
||||||
processComputable(treeNode as T, "canClick");
|
processComputable(treeNode as T, "canClick");
|
||||||
|
@ -85,6 +94,10 @@ export function createTreeNode<T extends TreeNodeOptions>(
|
||||||
processComputable(treeNode as T, "style");
|
processComputable(treeNode as T, "style");
|
||||||
processComputable(treeNode as T, "mark");
|
processComputable(treeNode as T, "mark");
|
||||||
|
|
||||||
|
for (const decorator of decorators) {
|
||||||
|
decorator.postConstruct?.(treeNode);
|
||||||
|
}
|
||||||
|
|
||||||
if (treeNode.onClick) {
|
if (treeNode.onClick) {
|
||||||
const onClick = treeNode.onClick.bind(treeNode);
|
const onClick = treeNode.onClick.bind(treeNode);
|
||||||
treeNode.onClick = function (e) {
|
treeNode.onClick = function (e) {
|
||||||
|
@ -102,6 +115,7 @@ export function createTreeNode<T extends TreeNodeOptions>(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const decoratedProps = decorators.reduce((current, next) => Object.assign(current, next.getGatheredProps?.(treeNode)), {});
|
||||||
treeNode[GatherProps] = function (this: GenericTreeNode) {
|
treeNode[GatherProps] = function (this: GenericTreeNode) {
|
||||||
const {
|
const {
|
||||||
display,
|
display,
|
||||||
|
@ -127,7 +141,8 @@ export function createTreeNode<T extends TreeNodeOptions>(
|
||||||
glowColor,
|
glowColor,
|
||||||
canClick,
|
canClick,
|
||||||
mark,
|
mark,
|
||||||
id
|
id,
|
||||||
|
...decoratedProps
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { isArray } from "@vue/shared";
|
import { isArray } from "@vue/shared";
|
||||||
|
import { Decorator } from "features/decorators";
|
||||||
import type {
|
import type {
|
||||||
CoercableComponent,
|
CoercableComponent,
|
||||||
GenericComponent,
|
GenericComponent,
|
||||||
|
@ -85,16 +86,24 @@ export type GenericUpgrade = Replace<
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function createUpgrade<T extends UpgradeOptions>(
|
export function createUpgrade<T extends UpgradeOptions>(
|
||||||
optionsFunc: OptionsFunc<T, BaseUpgrade, GenericUpgrade>
|
optionsFunc: OptionsFunc<T, BaseUpgrade, GenericUpgrade>,
|
||||||
|
...decorators: Decorator<T, BaseUpgrade, GenericUpgrade>[]
|
||||||
): Upgrade<T> {
|
): Upgrade<T> {
|
||||||
const bought = persistent<boolean>(false);
|
const bought = persistent<boolean>(false);
|
||||||
|
const decoratedData = decorators.reduce((current, next) => Object.assign(current, next.getPersistentData?.()), {});
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const upgrade = optionsFunc();
|
const upgrade = optionsFunc();
|
||||||
upgrade.id = getUniqueID("upgrade-");
|
upgrade.id = getUniqueID("upgrade-");
|
||||||
upgrade.type = UpgradeType;
|
upgrade.type = UpgradeType;
|
||||||
upgrade[Component] = UpgradeComponent;
|
upgrade[Component] = UpgradeComponent;
|
||||||
|
|
||||||
|
for (const decorator of decorators) {
|
||||||
|
decorator.preConstruct?.(upgrade);
|
||||||
|
}
|
||||||
|
|
||||||
upgrade.bought = bought;
|
upgrade.bought = bought;
|
||||||
|
Object.assign(upgrade, decoratedData);
|
||||||
|
|
||||||
upgrade.canPurchase = computed(() => requirementsMet(upgrade.requirements));
|
upgrade.canPurchase = computed(() => requirementsMet(upgrade.requirements));
|
||||||
upgrade.purchase = function () {
|
upgrade.purchase = function () {
|
||||||
const genericUpgrade = upgrade as GenericUpgrade;
|
const genericUpgrade = upgrade as GenericUpgrade;
|
||||||
|
@ -120,6 +129,11 @@ export function createUpgrade<T extends UpgradeOptions>(
|
||||||
processComputable(upgrade as T, "display");
|
processComputable(upgrade as T, "display");
|
||||||
processComputable(upgrade as T, "mark");
|
processComputable(upgrade as T, "mark");
|
||||||
|
|
||||||
|
for (const decorator of decorators) {
|
||||||
|
decorator.preConstruct?.(upgrade);
|
||||||
|
}
|
||||||
|
|
||||||
|
const decoratedProps = decorators.reduce((current, next) => Object.assign(current, next.getGatheredProps?.(upgrade)), {});
|
||||||
upgrade[GatherProps] = function (this: GenericUpgrade) {
|
upgrade[GatherProps] = function (this: GenericUpgrade) {
|
||||||
const {
|
const {
|
||||||
display,
|
display,
|
||||||
|
@ -143,7 +157,8 @@ export function createUpgrade<T extends UpgradeOptions>(
|
||||||
bought,
|
bought,
|
||||||
mark,
|
mark,
|
||||||
id,
|
id,
|
||||||
purchase
|
purchase,
|
||||||
|
...decoratedProps
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue