forked from profectus/Profectus
Advanced Decorator first draft
This commit is contained in:
parent
2122103c0e
commit
dafbcd5a6c
3 changed files with 140 additions and 5 deletions
|
@ -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
117
src/features/decorators.ts
Normal 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
|
||||||
|
|
||||||
|
/*----====----*/
|
||||||
|
|
|
@ -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;
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in a new issue