forked from profectus/Profectus
Update PoC
This commit is contained in:
parent
c39982b1bc
commit
cf069cee61
1 changed files with 74 additions and 246 deletions
|
@ -32,7 +32,7 @@ import { camelToTitle } from "util/common";
|
||||||
import { Computable, Defaults, ProcessedFeature, convertComputable } from "util/computed";
|
import { Computable, Defaults, ProcessedFeature, convertComputable } from "util/computed";
|
||||||
import { createLazyProxy } from "util/proxies";
|
import { createLazyProxy } from "util/proxies";
|
||||||
import { coerceComponent, isCoercableComponent } from "util/vue";
|
import { coerceComponent, isCoercableComponent } from "util/vue";
|
||||||
import { ComputedRef, nextTick, unref, watchEffect } from "vue";
|
import { ComputedRef, unref, watchEffect } from "vue";
|
||||||
import { useToast } from "vue-toastification";
|
import { useToast } from "vue-toastification";
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
@ -105,30 +105,6 @@ export interface BaseAchievement {
|
||||||
[GatherProps]: () => Record<string, unknown>;
|
[GatherProps]: () => Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** An object that represents a feature with requirements that is passively earned upon meeting certain requirements. */
|
|
||||||
// export type Achievement<T extends AchievementOptions> = Replace<
|
|
||||||
// T & BaseAchievement,
|
|
||||||
// {
|
|
||||||
// visibility: GetComputableTypeWithDefault<T["visibility"], Visibility.Visible>;
|
|
||||||
// display: GetComputableType<T["display"]>;
|
|
||||||
// mark: GetComputableType<T["mark"]>;
|
|
||||||
// image: GetComputableType<T["image"]>;
|
|
||||||
// style: GetComputableType<T["style"]>;
|
|
||||||
// classes: GetComputableType<T["classes"]>;
|
|
||||||
// showPopups: GetComputableTypeWithDefault<T["showPopups"], true>;
|
|
||||||
// }
|
|
||||||
// >;
|
|
||||||
|
|
||||||
// export interface Achievement extends AchievementOptions, BaseAchievement {
|
|
||||||
// visibility: GetComputableTypeWithDefault<AchievementOptions["visibility"], Visibility.Visible>;
|
|
||||||
// display: GetComputableType<AchievementOptions["display"]>;
|
|
||||||
// mark: GetComputableType<AchievementOptions["mark"]>;
|
|
||||||
// image: GetComputableType<AchievementOptions["image"]>;
|
|
||||||
// style: GetComputableType<AchievementOptions["style"]>;
|
|
||||||
// classes: GetComputableType<AchievementOptions["classes"]>;
|
|
||||||
// showPopups: GetComputableTypeWithDefault<AchievementOptions["showPopups"], true>;
|
|
||||||
// }
|
|
||||||
|
|
||||||
export type Achievement<T extends AchievementOptions> = BaseAchievement &
|
export type Achievement<T extends AchievementOptions> = BaseAchievement &
|
||||||
ProcessedFeature<AchievementOptions, Exclude<T, BaseAchievement>> &
|
ProcessedFeature<AchievementOptions, Exclude<T, BaseAchievement>> &
|
||||||
Defaults<
|
Defaults<
|
||||||
|
@ -141,154 +117,6 @@ export type Achievement<T extends AchievementOptions> = BaseAchievement &
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export type GenericAchievement = Achievement<any>;
|
export type GenericAchievement = Achievement<any>;
|
||||||
|
|
||||||
/**
|
|
||||||
* Lazily creates an achievement with the given options.
|
|
||||||
* @param optionsFunc Achievement options.
|
|
||||||
*/
|
|
||||||
// export function createAchievement<T extends AchievementOptions>(
|
|
||||||
// optionsFunc?: OptionsFunc<T, GenericAchievement>,
|
|
||||||
// ...decorators: GenericDecorator[]
|
|
||||||
// ): Achievement<T> {
|
|
||||||
// const earned = persistent<boolean>(false, false);
|
|
||||||
// const decoratedData = decorators.reduce(
|
|
||||||
// (current, next) => Object.assign(current, next.getPersistentData?.()),
|
|
||||||
// {}
|
|
||||||
// );
|
|
||||||
// return createLazyProxy(feature => {
|
|
||||||
// const achievement =
|
|
||||||
// optionsFunc?.call(feature, feature) ??
|
|
||||||
// ({} as ReturnType<NonNullable<typeof optionsFunc>>);
|
|
||||||
// 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(
|
|
||||||
// <div>
|
|
||||||
// <h3>Achievement earned!</h3>
|
|
||||||
// <div>
|
|
||||||
// {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
|
|
||||||
// {/* @ts-ignore */}
|
|
||||||
// <Display />
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// 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<string, unknown>)
|
|
||||||
// )
|
|
||||||
// ) {
|
|
||||||
// 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<T extends AchievementOptions>(
|
export function createAchievement<T extends AchievementOptions>(
|
||||||
optionsFunc?: OptionsFunc<T, GenericAchievement>,
|
optionsFunc?: OptionsFunc<T, GenericAchievement>,
|
||||||
...decorators: GenericDecorator[]
|
...decorators: GenericDecorator[]
|
||||||
|
@ -298,42 +126,13 @@ export function createAchievement<T extends AchievementOptions>(
|
||||||
(current, next) => Object.assign(current, next.getPersistentData?.()),
|
(current, next) => Object.assign(current, next.getPersistentData?.()),
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
return createLazyProxy(achievement => {
|
return createLazyProxy(feature => {
|
||||||
const options =
|
const { visibility, display, mark, small, image, style, classes, showPopups, ...options } =
|
||||||
optionsFunc?.call(achievement, achievement) ??
|
optionsFunc?.call(feature, feature) ??
|
||||||
({} as ReturnType<NonNullable<typeof optionsFunc>>);
|
({} as ReturnType<NonNullable<typeof optionsFunc>>);
|
||||||
|
|
||||||
function complete() {
|
const optionsVisibility = convertComputable(visibility, feature) ?? Visibility.Visible;
|
||||||
earned.value = true;
|
const processedVisibility = computed(() => {
|
||||||
(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(
|
|
||||||
<div>
|
|
||||||
<h3>Achievement earned!</h3>
|
|
||||||
<div>
|
|
||||||
{/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
|
|
||||||
{/* @ts-ignore */}
|
|
||||||
<Display />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const optionsVisibility =
|
|
||||||
convertComputable(options.visibility, options) ?? Visibility.Visible;
|
|
||||||
const visibility = computed(() => {
|
|
||||||
const display = unref(achievement.display);
|
const display = unref(achievement.display);
|
||||||
switch (settings.msDisplay) {
|
switch (settings.msDisplay) {
|
||||||
default:
|
default:
|
||||||
|
@ -361,17 +160,61 @@ export function createAchievement<T extends AchievementOptions>(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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<Achievement<T>>;
|
||||||
|
|
||||||
for (const decorator of decorators) {
|
for (const decorator of decorators) {
|
||||||
decorator.preConstruct?.(achievement);
|
decorator.preConstruct?.(achievement);
|
||||||
}
|
}
|
||||||
Object.assign(achievement, decoratedData);
|
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(
|
||||||
|
<div>
|
||||||
|
<h3>Achievement earned!</h3>
|
||||||
|
<div>
|
||||||
|
{/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
|
||||||
|
{/* @ts-ignore */}
|
||||||
|
<Display />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const decoratedProps = decorators.reduce(
|
const decoratedProps = decorators.reduce(
|
||||||
(current, next) => Object.assign(current, next.getGatheredProps?.(achievement)),
|
(current, next) => Object.assign(current, next.getGatheredProps?.(achievement)),
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
function gatherProps(this: Achievement<T>) {
|
function gatherProps(this: Achievement<T>): Record<string, unknown> {
|
||||||
const {
|
const {
|
||||||
visibility,
|
visibility,
|
||||||
display,
|
display,
|
||||||
|
@ -399,46 +242,27 @@ export function createAchievement<T extends AchievementOptions>(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
nextTick(() => {
|
for (const decorator of decorators) {
|
||||||
for (const decorator of decorators) {
|
decorator.postConstruct?.(achievement);
|
||||||
decorator.postConstruct?.(achievement);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (achievement.requirements) {
|
if (achievement.requirements != null) {
|
||||||
const requirements = [
|
const requirements = [
|
||||||
createVisibilityRequirement(achievement as GenericAchievement),
|
createVisibilityRequirement(achievement),
|
||||||
createBooleanRequirement(() => !achievement.earned.value),
|
createBooleanRequirement(() => !earned.value),
|
||||||
...(isArray(achievement.requirements)
|
...(isArray(achievement.requirements)
|
||||||
? achievement.requirements
|
? achievement.requirements
|
||||||
: [achievement.requirements])
|
: [achievement.requirements])
|
||||||
];
|
];
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (settings.active !== player.id) return;
|
if (settings.active !== player.id) return;
|
||||||
if (requirementsMet(requirements)) {
|
if (requirementsMet(requirements)) {
|
||||||
achievement.complete();
|
achievement.complete();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return achievement;
|
||||||
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<T>*/;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -458,6 +282,10 @@ ach.bar; // () => "foo"
|
||||||
ach.mark; // TS should yell about this not existing (or at least mark it undefined)
|
ach.mark; // TS should yell about this not existing (or at least mark it undefined)
|
||||||
ach.visibility; // ComputedRef<Visibility | boolean>
|
ach.visibility; // ComputedRef<Visibility | boolean>
|
||||||
|
|
||||||
|
const badAch = createAchievement(() => ({
|
||||||
|
requirements: "foo"
|
||||||
|
}));
|
||||||
|
|
||||||
declare module "game/settings" {
|
declare module "game/settings" {
|
||||||
interface Settings {
|
interface Settings {
|
||||||
msDisplay: AchievementDisplay;
|
msDisplay: AchievementDisplay;
|
||||||
|
|
Loading…
Add table
Reference in a new issue