diff --git a/src/features/buyable.tsx b/src/features/buyable.tsx index 30edd29..4457993 100644 --- a/src/features/buyable.tsx +++ b/src/features/buyable.tsx @@ -23,6 +23,7 @@ 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"); @@ -89,9 +90,13 @@ export type GenericBuyable = Replace< >; export function createBuyable( - optionsFunc: OptionsFunc + optionsFunc: OptionsFunc, + ...decorators: Decorator[] ): Buyable { const amount = persistent(0); + + const persistents = decorators.reduce((current, next) => Object.assign(current, next.getPersistents?.()), {}); + return createLazyProxy(() => { const buyable = optionsFunc(); @@ -107,8 +112,15 @@ export function createBuyable( 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); @@ -230,6 +242,7 @@ export function createBuyable( 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; @@ -242,10 +255,15 @@ export function createBuyable( canClick, small, mark, - id + id, + ...gatheredProps }; }; + for (const decorator of decorators) { + decorator.postConstruct?.(buyable); + } + return buyable as unknown as Buyable; }); } diff --git a/src/features/decorators.ts b/src/features/decorators.ts new file mode 100644 index 0000000..774312c --- /dev/null +++ b/src/features/decorators.ts @@ -0,0 +1,117 @@ +import { Replace, OptionsObject } from "./feature"; +import Decimal, { DecimalSource } from "util/bignum"; +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 { 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 = { + getPersistents?(): Record>; + preConstruct?(feature: OptionsObject): void; + postConstruct?(feature: OptionsObject): void; + getGatheredProps?(feature: OptionsObject): Partial> +} + +/*----====----*/ + +// #region Effect Decorator +export type EffectFeatureOptions = { + effect: Computable; +} + +export type EffectFeature = Replace< + T & U, + { effect: GetComputableType; } +>; + +export type GenericEffectFeature = T & Replace< + EffectFeature, + { effect: ProcessedComputable; } +>; + +export const effectDecorator: Decorator = { + postConstruct(feature) { + processComputable(feature, "effect"); + } +} +// #endregion + +/*----====----*/ + +// #region Bonus Amount Decorator +export interface BonusFeatureOptions { + bonusAmount: Computable; +} + +export type BaseBonusFeature = BaseFeature & { + totalAmount: Ref; +} + +export type BonusAmountFeature = Replace< + T & U, + { + bonusAmount: GetComputableType; + } +>; + +export type GenericBonusFeature = Replace< + T & BonusAmountFeature, + { + bonusAmount: ProcessedComputable; + totalAmount: ProcessedComputable; + } +>; + +export const bonusAmountDecorator: Decorator}, GenericFeature & BaseBonusFeature & {amount: ProcessedComputable}> = { + postConstruct(feature) { + processComputable(feature, "bonusAmount"); + if (feature.totalAmount === undefined) { + feature.totalAmount = computed(() => Decimal.add( + unref(feature.amount ?? 0), + unref(feature.bonusAmount as ProcessedComputable) + )); + } + } +} + +export const bonusCompletionsDecorator: Decorator}, GenericFeature & BaseBonusFeature & {completions: ProcessedComputable}> = { + postConstruct(feature) { + processComputable(feature, "bonusAmount"); + if (feature.totalAmount === undefined) { + feature.totalAmount = computed(() => Decimal.add( + unref(feature.completions ?? 0), + unref(feature.bonusAmount as ProcessedComputable) + )); + } + } +} + +export const bonusEarnedDecorator: Decorator}, GenericFeature & BaseBonusFeature & {earned: ProcessedComputable}> = { + postConstruct(feature) { + processComputable(feature, "bonusAmount"); + if (feature.totalAmount === undefined) { + feature.totalAmount = computed(() => unref(feature.earned ?? false) + ? Decimal.add(unref(feature.bonusAmount as ProcessedComputable), 1) + : unref(feature.bonusAmount as ProcessedComputable) + ); + } + } +} +// #endregion + +/*----====----*/ + diff --git a/src/features/feature.ts b/src/features/feature.ts index afa251f..438689b 100644 --- a/src/features/feature.ts +++ b/src/features/feature.ts @@ -42,9 +42,9 @@ export type Replace = S & Omit; * with "this" bound to what the type will eventually be processed into. * Intended for making lazily evaluated objects. */ -export type OptionsFunc, S = R> = () => T & - Partial & - ThisType; +export type OptionsFunc, S = R> = () => OptionsObject; + +export type OptionsObject, S = R> = T & Partial & ThisType; let id = 0; /**