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 {
|
||||
CoercableComponent,
|
||||
|
@ -5,7 +6,6 @@ import {
|
|||
GatherProps,
|
||||
GenericComponent,
|
||||
getUniqueID,
|
||||
isVisible,
|
||||
OptionsFunc,
|
||||
Replace,
|
||||
setDefault,
|
||||
|
@ -16,6 +16,12 @@ import "game/notifications";
|
|||
import type { Persistent } from "game/persistence";
|
||||
import { persistent } from "game/persistence";
|
||||
import player from "game/player";
|
||||
import {
|
||||
createBooleanRequirement,
|
||||
createVisibilityRequirement,
|
||||
Requirements,
|
||||
requirementsMet
|
||||
} from "game/requirements";
|
||||
import settings from "game/settings";
|
||||
import type {
|
||||
Computable,
|
||||
|
@ -31,28 +37,50 @@ import { useToast } from "vue-toastification";
|
|||
|
||||
const toast = useToast();
|
||||
|
||||
/** A symbol used to identify {@link Achievement} features. */
|
||||
export const AchievementType = Symbol("Achievement");
|
||||
|
||||
/**
|
||||
* An object that configures an {@link Achievement}.
|
||||
*/
|
||||
export interface AchievementOptions {
|
||||
/** Whether this achievement should be visible. */
|
||||
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>;
|
||||
/** Shows a marker on the corner of the feature. */
|
||||
mark?: Computable<boolean | string>;
|
||||
/** An image to display as the background for this achievement. */
|
||||
image?: Computable<string>;
|
||||
/** CSS to apply to this feature. */
|
||||
style?: Computable<StyleValue>;
|
||||
/** Dictionary of CSS classes to apply to this feature. */
|
||||
classes?: Computable<Record<string, boolean>>;
|
||||
/** A function that is called when the achievement is completed. */
|
||||
onComplete?: VoidFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* The properties that are added onto a processed {@link AchievementOptions} to create an {@link Achievement}.
|
||||
*/
|
||||
export interface BaseAchievement {
|
||||
/** An auto-generated ID for identifying achievements that appear in the DOM. Will not persist between refreshes or updates. */
|
||||
id: string;
|
||||
/** Whether or not this achievement has been earned. */
|
||||
earned: Persistent<boolean>;
|
||||
/** A function to complete this achievement. */
|
||||
complete: VoidFunction;
|
||||
/** A symbol that helps identify features of the same type. */
|
||||
type: typeof AchievementType;
|
||||
/** The Vue component used to render this feature. */
|
||||
[Component]: GenericComponent;
|
||||
/** A function to gather the props the vue component requires for this feature. */
|
||||
[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<
|
||||
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<
|
||||
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>(
|
||||
optionsFunc?: OptionsFunc<T, BaseAchievement, GenericAchievement>
|
||||
): Achievement<T> {
|
||||
|
@ -85,6 +118,21 @@ export function createAchievement<T extends AchievementOptions>(
|
|||
achievement.earned = earned;
|
||||
achievement.complete = function () {
|
||||
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");
|
||||
|
@ -100,30 +148,19 @@ export function createAchievement<T extends AchievementOptions>(
|
|||
return { visibility, display, earned, image, style: unref(style), classes, mark, id };
|
||||
};
|
||||
|
||||
if (achievement.shouldEarn) {
|
||||
if (achievement.requirements) {
|
||||
const genericAchievement = achievement as GenericAchievement;
|
||||
const requirements = [
|
||||
createVisibilityRequirement(genericAchievement),
|
||||
createBooleanRequirement(() => !genericAchievement.earned.value),
|
||||
...(isArray(achievement.requirements)
|
||||
? achievement.requirements
|
||||
: [achievement.requirements])
|
||||
];
|
||||
watchEffect(() => {
|
||||
if (settings.active !== player.id) return;
|
||||
if (
|
||||
!genericAchievement.earned.value &&
|
||||
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>
|
||||
);
|
||||
}
|
||||
if (requirementsMet(requirements)) {
|
||||
genericAchievement.complete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue