Advanced Decorator first draft

This commit is contained in:
Seth Posner 2023-02-17 15:56:04 -08:00
parent 2122103c0e
commit dafbcd5a6c
3 changed files with 140 additions and 5 deletions

View file

@ -23,6 +23,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 } from "./decorators";
export const BuyableType = Symbol("Buyable"); export const BuyableType = Symbol("Buyable");
@ -89,9 +90,13 @@ export type GenericBuyable = Replace<
>; >;
export function createBuyable<T extends BuyableOptions>( export function createBuyable<T extends BuyableOptions>(
optionsFunc: OptionsFunc<T, BaseBuyable, GenericBuyable> optionsFunc: OptionsFunc<T, BaseBuyable, GenericBuyable>,
...decorators: Decorator<T, BaseBuyable, GenericBuyable>[]
): Buyable<T> { ): Buyable<T> {
const amount = persistent<DecimalSource>(0); const amount = persistent<DecimalSource>(0);
const persistents = decorators.reduce((current, next) => Object.assign(current, next.getPersistents?.()), {});
return createLazyProxy(() => { return createLazyProxy(() => {
const buyable = optionsFunc(); const buyable = optionsFunc();
@ -107,8 +112,15 @@ export function createBuyable<T extends BuyableOptions>(
buyable.type = BuyableType; buyable.type = BuyableType;
buyable[Component] = ClickableComponent; buyable[Component] = ClickableComponent;
for (const decorator of decorators) {
decorator.preConstruct?.(buyable);
}
buyable.amount = amount; buyable.amount = amount;
buyable.amount[DefaultValue] = buyable.initialValue ?? 0; buyable.amount[DefaultValue] = buyable.initialValue ?? 0;
Object.assign(buyable, persistents);
buyable.canAfford = computed(() => { buyable.canAfford = computed(() => {
const genericBuyable = buyable as GenericBuyable; const genericBuyable = buyable as GenericBuyable;
const cost = unref(genericBuyable.cost); const cost = unref(genericBuyable.cost);
@ -230,6 +242,7 @@ export function createBuyable<T extends BuyableOptions>(
processComputable(buyable as T, "mark"); processComputable(buyable as T, "mark");
processComputable(buyable as T, "small"); processComputable(buyable as T, "small");
const gatheredProps = decorators.reduce((current, next) => Object.assign(current, next.getGatheredProps?.(buyable)), {});
buyable[GatherProps] = function (this: GenericBuyable) { buyable[GatherProps] = function (this: GenericBuyable) {
const { display, visibility, style, classes, onClick, canClick, small, mark, id } = const { display, visibility, style, classes, onClick, canClick, small, mark, id } =
this; this;
@ -242,10 +255,15 @@ export function createBuyable<T extends BuyableOptions>(
canClick, canClick,
small, small,
mark, mark,
id id,
...gatheredProps
}; };
}; };
for (const decorator of decorators) {
decorator.postConstruct?.(buyable);
}
return buyable as unknown as Buyable<T>; return buyable as unknown as Buyable<T>;
}); });
} }

117
src/features/decorators.ts Normal file
View file

@ -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<Options extends FeatureOptions, Base extends BaseFeature, Generic extends GenericFeature, S extends State = State> = {
getPersistents?(): Record<string, Persistent<S>>;
preConstruct?(feature: OptionsObject<Options,Base,Generic>): void;
postConstruct?(feature: OptionsObject<Options,Base,Generic>): void;
getGatheredProps?(feature: OptionsObject<Options,Base,Generic>): Partial<OptionsObject<Options,Base,Generic>>
}
/*----====----*/
// #region Effect Decorator
export type EffectFeatureOptions = {
effect: Computable<any>;
}
export type EffectFeature<T extends EffectFeatureOptions, U extends BaseFeature> = Replace<
T & U,
{ effect: GetComputableType<T["effect"]>; }
>;
export type GenericEffectFeature<T extends GenericFeature> = T & Replace<
EffectFeature<EffectFeatureOptions, BaseFeature>,
{ effect: ProcessedComputable<any>; }
>;
export const effectDecorator: Decorator<FeatureOptions & EffectFeatureOptions, BaseFeature, GenericFeature & BaseFeature> = {
postConstruct(feature) {
processComputable(feature, "effect");
}
}
// #endregion
/*----====----*/
// #region Bonus Amount Decorator
export interface BonusFeatureOptions {
bonusAmount: Computable<DecimalSource>;
}
export type BaseBonusFeature = BaseFeature & {
totalAmount: Ref<DecimalSource>;
}
export type BonusAmountFeature<T extends BonusFeatureOptions, U extends BaseBonusFeature> = Replace<
T & U,
{
bonusAmount: GetComputableType<T["bonusAmount"]>;
}
>;
export type GenericBonusFeature<T extends GenericFeature> = Replace<
T & BonusAmountFeature<BonusFeatureOptions, BaseBonusFeature>,
{
bonusAmount: ProcessedComputable<DecimalSource>;
totalAmount: ProcessedComputable<DecimalSource>;
}
>;
export const bonusAmountDecorator: Decorator<FeatureOptions & BonusFeatureOptions, BaseBonusFeature & {amount: ProcessedComputable<DecimalSource>}, GenericFeature & BaseBonusFeature & {amount: ProcessedComputable<DecimalSource>}> = {
postConstruct(feature) {
processComputable(feature, "bonusAmount");
if (feature.totalAmount === undefined) {
feature.totalAmount = computed(() => Decimal.add(
unref(feature.amount ?? 0),
unref(feature.bonusAmount as ProcessedComputable<DecimalSource>)
));
}
}
}
export const bonusCompletionsDecorator: Decorator<FeatureOptions & BonusFeatureOptions, BaseBonusFeature & {completions: ProcessedComputable<DecimalSource>}, GenericFeature & BaseBonusFeature & {completions: ProcessedComputable<DecimalSource>}> = {
postConstruct(feature) {
processComputable(feature, "bonusAmount");
if (feature.totalAmount === undefined) {
feature.totalAmount = computed(() => Decimal.add(
unref(feature.completions ?? 0),
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
/*----====----*/

View file

@ -42,9 +42,9 @@ export type Replace<T, S> = S & Omit<T, keyof S>;
* with "this" bound to what the type will eventually be processed into. * with "this" bound to what the type will eventually be processed into.
* Intended for making lazily evaluated objects. * Intended for making lazily evaluated objects.
*/ */
export type OptionsFunc<T, R = Record<string, unknown>, S = R> = () => T & export type OptionsFunc<T, R = Record<string, unknown>, S = R> = () => OptionsObject<T,R,S>;
Partial<R> &
ThisType<T & S>; export type OptionsObject<T, R = Record<string, unknown>, S = R> = T & Partial<R> & ThisType<T & S>;
let id = 0; let id = 0;
/** /**