forked from profectus/Profectus
Made achievements use requirements system, and document them
This commit is contained in:
parent
e6c7ad62a7
commit
742d2293d0
1 changed files with 60 additions and 23 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
import { isArray } from "@vue/shared";
|
||||||
import AchievementComponent from "features/achievements/Achievement.vue";
|
import AchievementComponent from "features/achievements/Achievement.vue";
|
||||||
import {
|
import {
|
||||||
CoercableComponent,
|
CoercableComponent,
|
||||||
|
@ -5,7 +6,6 @@ import {
|
||||||
GatherProps,
|
GatherProps,
|
||||||
GenericComponent,
|
GenericComponent,
|
||||||
getUniqueID,
|
getUniqueID,
|
||||||
isVisible,
|
|
||||||
OptionsFunc,
|
OptionsFunc,
|
||||||
Replace,
|
Replace,
|
||||||
setDefault,
|
setDefault,
|
||||||
|
@ -16,6 +16,12 @@ import "game/notifications";
|
||||||
import type { Persistent } from "game/persistence";
|
import type { Persistent } from "game/persistence";
|
||||||
import { persistent } from "game/persistence";
|
import { persistent } from "game/persistence";
|
||||||
import player from "game/player";
|
import player from "game/player";
|
||||||
|
import {
|
||||||
|
createBooleanRequirement,
|
||||||
|
createVisibilityRequirement,
|
||||||
|
Requirements,
|
||||||
|
requirementsMet
|
||||||
|
} from "game/requirements";
|
||||||
import settings from "game/settings";
|
import settings from "game/settings";
|
||||||
import type {
|
import type {
|
||||||
Computable,
|
Computable,
|
||||||
|
@ -31,28 +37,50 @@ import { useToast } from "vue-toastification";
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
|
/** A symbol used to identify {@link Achievement} features. */
|
||||||
export const AchievementType = Symbol("Achievement");
|
export const AchievementType = Symbol("Achievement");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object that configures an {@link Achievement}.
|
||||||
|
*/
|
||||||
export interface AchievementOptions {
|
export interface AchievementOptions {
|
||||||
|
/** Whether this achievement should be visible. */
|
||||||
visibility?: Computable<Visibility | boolean>;
|
visibility?: Computable<Visibility | boolean>;
|
||||||
shouldEarn?: () => boolean;
|
/** The requirement(s) to earn this achievement. Can be left null if using {@link BaseAchievement.complete}. */
|
||||||
|
requirements?: Requirements;
|
||||||
|
/** The display to use for this achievement. */
|
||||||
display?: Computable<CoercableComponent>;
|
display?: Computable<CoercableComponent>;
|
||||||
|
/** Shows a marker on the corner of the feature. */
|
||||||
mark?: Computable<boolean | string>;
|
mark?: Computable<boolean | string>;
|
||||||
|
/** An image to display as the background for this achievement. */
|
||||||
image?: Computable<string>;
|
image?: Computable<string>;
|
||||||
|
/** CSS to apply to this feature. */
|
||||||
style?: Computable<StyleValue>;
|
style?: Computable<StyleValue>;
|
||||||
|
/** Dictionary of CSS classes to apply to this feature. */
|
||||||
classes?: Computable<Record<string, boolean>>;
|
classes?: Computable<Record<string, boolean>>;
|
||||||
|
/** A function that is called when the achievement is completed. */
|
||||||
onComplete?: VoidFunction;
|
onComplete?: VoidFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The properties that are added onto a processed {@link AchievementOptions} to create an {@link Achievement}.
|
||||||
|
*/
|
||||||
export interface BaseAchievement {
|
export interface BaseAchievement {
|
||||||
|
/** An auto-generated ID for identifying achievements that appear in the DOM. Will not persist between refreshes or updates. */
|
||||||
id: string;
|
id: string;
|
||||||
|
/** Whether or not this achievement has been earned. */
|
||||||
earned: Persistent<boolean>;
|
earned: Persistent<boolean>;
|
||||||
|
/** A function to complete this achievement. */
|
||||||
complete: VoidFunction;
|
complete: VoidFunction;
|
||||||
|
/** A symbol that helps identify features of the same type. */
|
||||||
type: typeof AchievementType;
|
type: typeof AchievementType;
|
||||||
|
/** The Vue component used to render this feature. */
|
||||||
[Component]: GenericComponent;
|
[Component]: GenericComponent;
|
||||||
|
/** A function to gather the props the vue component requires for this feature. */
|
||||||
[GatherProps]: () => Record<string, unknown>;
|
[GatherProps]: () => Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** An object that represents a feature with that is passively earned upon meeting certain requirements. */
|
||||||
export type Achievement<T extends AchievementOptions> = Replace<
|
export type Achievement<T extends AchievementOptions> = Replace<
|
||||||
T & BaseAchievement,
|
T & BaseAchievement,
|
||||||
{
|
{
|
||||||
|
@ -65,6 +93,7 @@ export type Achievement<T extends AchievementOptions> = Replace<
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
/** A type that matches any valid {@link Achievement} object. */
|
||||||
export type GenericAchievement = Replace<
|
export type GenericAchievement = Replace<
|
||||||
Achievement<AchievementOptions>,
|
Achievement<AchievementOptions>,
|
||||||
{
|
{
|
||||||
|
@ -72,6 +101,10 @@ export type GenericAchievement = Replace<
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazily creates a achievement with the given options.
|
||||||
|
* @param optionsFunc Achievement options.
|
||||||
|
*/
|
||||||
export function createAchievement<T extends AchievementOptions>(
|
export function createAchievement<T extends AchievementOptions>(
|
||||||
optionsFunc?: OptionsFunc<T, BaseAchievement, GenericAchievement>
|
optionsFunc?: OptionsFunc<T, BaseAchievement, GenericAchievement>
|
||||||
): Achievement<T> {
|
): Achievement<T> {
|
||||||
|
@ -85,6 +118,21 @@ export function createAchievement<T extends AchievementOptions>(
|
||||||
achievement.earned = earned;
|
achievement.earned = earned;
|
||||||
achievement.complete = function () {
|
achievement.complete = function () {
|
||||||
earned.value = true;
|
earned.value = true;
|
||||||
|
const genericAchievement = achievement as GenericAchievement;
|
||||||
|
genericAchievement.onComplete?.();
|
||||||
|
if (genericAchievement.display != null) {
|
||||||
|
const Display = coerceComponent(unref(genericAchievement.display));
|
||||||
|
toast.info(
|
||||||
|
<div>
|
||||||
|
<h3>Achievement earned!</h3>
|
||||||
|
<div>
|
||||||
|
{/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
|
||||||
|
{/* @ts-ignore */}
|
||||||
|
<Display />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
processComputable(achievement as T, "visibility");
|
processComputable(achievement as T, "visibility");
|
||||||
|
@ -100,30 +148,19 @@ export function createAchievement<T extends AchievementOptions>(
|
||||||
return { visibility, display, earned, image, style: unref(style), classes, mark, id };
|
return { visibility, display, earned, image, style: unref(style), classes, mark, id };
|
||||||
};
|
};
|
||||||
|
|
||||||
if (achievement.shouldEarn) {
|
if (achievement.requirements) {
|
||||||
const genericAchievement = achievement as GenericAchievement;
|
const genericAchievement = achievement as GenericAchievement;
|
||||||
|
const requirements = [
|
||||||
|
createVisibilityRequirement(genericAchievement),
|
||||||
|
createBooleanRequirement(() => !genericAchievement.earned.value),
|
||||||
|
...(isArray(achievement.requirements)
|
||||||
|
? achievement.requirements
|
||||||
|
: [achievement.requirements])
|
||||||
|
];
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (settings.active !== player.id) return;
|
if (settings.active !== player.id) return;
|
||||||
if (
|
if (requirementsMet(requirements)) {
|
||||||
!genericAchievement.earned.value &&
|
genericAchievement.complete();
|
||||||
isVisible(genericAchievement.visibility) &&
|
|
||||||
genericAchievement.shouldEarn?.()
|
|
||||||
) {
|
|
||||||
genericAchievement.earned.value = true;
|
|
||||||
genericAchievement.onComplete?.();
|
|
||||||
if (genericAchievement.display != null) {
|
|
||||||
const Display = coerceComponent(unref(genericAchievement.display));
|
|
||||||
toast.info(
|
|
||||||
<div>
|
|
||||||
<h3>Achievement earned!</h3>
|
|
||||||
<div>
|
|
||||||
{/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
|
|
||||||
{/* @ts-ignore */}
|
|
||||||
<Display />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue