From cf069cee6117423c550207889cba7cc85b2f681a Mon Sep 17 00:00:00 2001 From: thepaperpilot Date: Wed, 26 Apr 2023 20:19:14 -0500 Subject: [PATCH] Update PoC --- src/features/achievements/achievement.tsx | 320 +++++----------------- 1 file changed, 74 insertions(+), 246 deletions(-) diff --git a/src/features/achievements/achievement.tsx b/src/features/achievements/achievement.tsx index 7d6f2ef..2d00c49 100644 --- a/src/features/achievements/achievement.tsx +++ b/src/features/achievements/achievement.tsx @@ -32,7 +32,7 @@ import { camelToTitle } from "util/common"; import { Computable, Defaults, ProcessedFeature, convertComputable } from "util/computed"; import { createLazyProxy } from "util/proxies"; import { coerceComponent, isCoercableComponent } from "util/vue"; -import { ComputedRef, nextTick, unref, watchEffect } from "vue"; +import { ComputedRef, unref, watchEffect } from "vue"; import { useToast } from "vue-toastification"; const toast = useToast(); @@ -105,30 +105,6 @@ export interface BaseAchievement { [GatherProps]: () => Record; } -/** An object that represents a feature with requirements that is passively earned upon meeting certain requirements. */ -// export type Achievement = Replace< -// T & BaseAchievement, -// { -// visibility: GetComputableTypeWithDefault; -// display: GetComputableType; -// mark: GetComputableType; -// image: GetComputableType; -// style: GetComputableType; -// classes: GetComputableType; -// showPopups: GetComputableTypeWithDefault; -// } -// >; - -// export interface Achievement extends AchievementOptions, BaseAchievement { -// visibility: GetComputableTypeWithDefault; -// display: GetComputableType; -// mark: GetComputableType; -// image: GetComputableType; -// style: GetComputableType; -// classes: GetComputableType; -// showPopups: GetComputableTypeWithDefault; -// } - export type Achievement = BaseAchievement & ProcessedFeature> & Defaults< @@ -141,154 +117,6 @@ export type Achievement = BaseAchievement & // eslint-disable-next-line @typescript-eslint/no-explicit-any export type GenericAchievement = Achievement; -/** - * Lazily creates an achievement with the given options. - * @param optionsFunc Achievement options. - */ -// export function createAchievement( -// optionsFunc?: OptionsFunc, -// ...decorators: GenericDecorator[] -// ): Achievement { -// const earned = persistent(false, false); -// const decoratedData = decorators.reduce( -// (current, next) => Object.assign(current, next.getPersistentData?.()), -// {} -// ); -// return createLazyProxy(feature => { -// const achievement = -// optionsFunc?.call(feature, feature) ?? -// ({} as ReturnType>); -// achievement.id = getUniqueID("achievement-"); -// achievement.type = AchievementType; -// achievement[Component] = AchievementComponent as GenericComponent; - -// for (const decorator of decorators) { -// decorator.preConstruct?.(achievement); -// } - -// achievement.display = convertComputable(achievement.display, achievement); -// achievement.mark = convertComputable(achievement.mark, achievement); -// achievement.small = convertComputable(achievement.small, achievement); -// achievement.image = convertComputable(achievement.image, achievement); -// achievement.style = convertComputable(achievement.style, achievement); -// achievement.classes = convertComputable(achievement.classes, achievement); -// achievement.showPopups = convertComputable(achievement.showPopups, achievement) ?? true; - -// achievement.earned = earned; -// achievement.complete = function () { -// earned.value = true; -// achievement.onComplete?.(); -// if (achievement.display != null && unref(achievement.showPopups) === true) { -// const display = unref((achievement as GenericAchievement).display); -// let Display; -// if (isCoercableComponent(display)) { -// Display = coerceComponent(display); -// } else if (display.requirement != null) { -// Display = coerceComponent(display.requirement); -// } else { -// Display = displayRequirements(achievement.requirements ?? []); -// } -// toast.info( -//
-//

Achievement earned!

-//
-// {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */} -// {/* @ts-ignore */} -// -//
-//
-// ); -// } -// }; - -// Object.assign(achievement, decoratedData); - -// const visibility = -// convertComputable(achievement.visibility, achievement) ?? Visibility.Visible; -// achievement.visibility = computed(() => { -// const display = unref(achievement.display); -// switch (settings.msDisplay) { -// default: -// case AchievementDisplay.All: -// return unref(visibility); -// case AchievementDisplay.Configurable: -// if ( -// unref(achievement.earned) && -// !( -// display != null && -// typeof display == "object" && -// "optionsDisplay" in (display as Record) -// ) -// ) { -// return Visibility.None; -// } -// return unref(visibility); -// case AchievementDisplay.Incomplete: -// if (unref(achievement.earned)) { -// return Visibility.None; -// } -// return unref(visibility); -// case AchievementDisplay.None: -// return Visibility.None; -// } -// }); - -// for (const decorator of decorators) { -// decorator.postConstruct?.(achievement); -// } - -// const decoratedProps = decorators.reduce( -// (current, next) => Object.assign(current, next.getGatheredProps?.(achievement)), -// {} -// ); -// achievement[GatherProps] = function () { -// const { -// visibility, -// display, -// requirements, -// earned, -// image, -// style, -// classes, -// mark, -// small, -// id -// } = this; -// return { -// visibility, -// display, -// requirements, -// earned, -// image, -// style: unref(style), -// classes, -// mark, -// small, -// id, -// ...decoratedProps -// }; -// }; - -// if (achievement.requirements) { -// const requirements = [ -// createVisibilityRequirement(achievement), -// createBooleanRequirement(() => !achievement.earned.value), -// ...(isArray(achievement.requirements) -// ? achievement.requirements -// : [achievement.requirements]) -// ]; -// watchEffect(() => { -// if (settings.active !== player.id) return; -// if (requirementsMet(requirements)) { -// achievement.complete(); -// } -// }); -// } - -// return achievement; -// }); -// } - export function createAchievement( optionsFunc?: OptionsFunc, ...decorators: GenericDecorator[] @@ -298,42 +126,13 @@ export function createAchievement( (current, next) => Object.assign(current, next.getPersistentData?.()), {} ); - return createLazyProxy(achievement => { - const options = - optionsFunc?.call(achievement, achievement) ?? + return createLazyProxy(feature => { + const { visibility, display, mark, small, image, style, classes, showPopups, ...options } = + optionsFunc?.call(feature, feature) ?? ({} as ReturnType>); - function complete() { - earned.value = true; - (achievement as GenericAchievement).onComplete?.(); - if (achievement.display != null && unref(achievement.showPopups) === true) { - const display = unref((achievement as GenericAchievement).display); - let Display; - if (isCoercableComponent(display)) { - Display = coerceComponent(display); - } else if (display.requirement != null) { - Display = coerceComponent(display.requirement); - } else { - Display = displayRequirements( - (achievement as GenericAchievement).requirements ?? [] - ); - } - toast.info( -
-

Achievement earned!

-
- {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */} - {/* @ts-ignore */} - -
-
- ); - } - } - - const optionsVisibility = - convertComputable(options.visibility, options) ?? Visibility.Visible; - const visibility = computed(() => { + const optionsVisibility = convertComputable(visibility, feature) ?? Visibility.Visible; + const processedVisibility = computed(() => { const display = unref(achievement.display); switch (settings.msDisplay) { default: @@ -361,17 +160,61 @@ export function createAchievement( } }); + const achievement = { + id: getUniqueID("achievement-"), + visibility: processedVisibility, + earned, + complete, + type: AchievementType, + [Component]: AchievementComponent as GenericComponent, + [GatherProps]: gatherProps, + display: convertComputable(display, feature), + mark: convertComputable(mark, feature), + small: convertComputable(small, feature), + image: convertComputable(image, feature), + style: convertComputable(style, feature), + classes: convertComputable(classes, feature), + showPopups: convertComputable(showPopups, feature) ?? true, + ...options + } satisfies Partial>; + for (const decorator of decorators) { decorator.preConstruct?.(achievement); } Object.assign(achievement, decoratedData); + function complete() { + earned.value = true; + achievement.onComplete?.(); + if (achievement.display != null && unref(achievement.showPopups) === true) { + const display = unref(achievement.display); + let Display; + if (isCoercableComponent(display)) { + Display = coerceComponent(display); + } else if (display.requirement != null) { + Display = coerceComponent(display.requirement); + } else { + Display = displayRequirements(achievement.requirements ?? []); + } + toast.info( +
+

Achievement earned!

+
+ {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */} + {/* @ts-ignore */} + +
+
+ ); + } + } + const decoratedProps = decorators.reduce( (current, next) => Object.assign(current, next.getGatheredProps?.(achievement)), {} ); - function gatherProps(this: Achievement) { + function gatherProps(this: Achievement): Record { const { visibility, display, @@ -399,46 +242,27 @@ export function createAchievement( }; } - nextTick(() => { - for (const decorator of decorators) { - decorator.postConstruct?.(achievement); - } + for (const decorator of decorators) { + decorator.postConstruct?.(achievement); + } - if (achievement.requirements) { - const requirements = [ - createVisibilityRequirement(achievement as GenericAchievement), - createBooleanRequirement(() => !achievement.earned.value), - ...(isArray(achievement.requirements) - ? achievement.requirements - : [achievement.requirements]) - ]; - watchEffect(() => { - if (settings.active !== player.id) return; - if (requirementsMet(requirements)) { - achievement.complete(); - } - }); - } - }); + if (achievement.requirements != null) { + const requirements = [ + createVisibilityRequirement(achievement), + createBooleanRequirement(() => !earned.value), + ...(isArray(achievement.requirements) + ? achievement.requirements + : [achievement.requirements]) + ]; + watchEffect(() => { + if (settings.active !== player.id) return; + if (requirementsMet(requirements)) { + achievement.complete(); + } + }); + } - return { - id: getUniqueID("achievement-"), - visibility, - earned, - complete, - type: AchievementType, - [Component]: AchievementComponent as GenericComponent, - [GatherProps]: gatherProps, - requirements: options.requirements, - display: convertComputable(options.display, options), - mark: convertComputable(options.mark, options), - small: convertComputable(options.small, options), - image: convertComputable(options.image, options), - style: convertComputable(options.style, options), - classes: convertComputable(options.classes, options), - showPopups: convertComputable(options.showPopups, options) ?? true, - onComplete: options.onComplete - } /* as Achievement*/; + return achievement; }); } @@ -458,6 +282,10 @@ ach.bar; // () => "foo" ach.mark; // TS should yell about this not existing (or at least mark it undefined) ach.visibility; // ComputedRef +const badAch = createAchievement(() => ({ + requirements: "foo" +})); + declare module "game/settings" { interface Settings { msDisplay: AchievementDisplay;