diff --git a/src/features/challenges/challenge.tsx b/src/features/challenges/challenge.tsx index 2925595..3a1ba48 100644 --- a/src/features/challenges/challenge.tsx +++ b/src/features/challenges/challenge.tsx @@ -36,47 +36,87 @@ import { createLazyProxy } from "util/proxies"; import type { Ref, WatchStopHandle } from "vue"; import { computed, unref, watch } from "vue"; -export const ChallengeType = Symbol("ChallengeType"); +/** A symbol used to identify {@link Challenge} features. */ +export const ChallengeType = Symbol("Challenge"); +/** + * An object that configures a {@link Challenge}. + */ export interface ChallengeOptions { + /** Whether this challenge should be visible. */ visibility?: Computable; + /** Whether this challenge can be started. */ canStart?: Computable; + /** The reset function for this challenge. */ reset?: GenericReset; + /** The requirement(s) to complete this challenge. */ requirements: Requirements; + /** Whether or not completing this challenge should grant multiple completions if requirements met. Requires {@link requirements} to be a requirement or array of requirements with {@link Requirement.canMaximize} true. */ maximize?: Computable; + /** The maximum number of times the challenge can be completed. */ completionLimit?: Computable; + /** Shows a marker on the corner of the feature. */ mark?: Computable; + /** Dictionary of CSS classes to apply to this feature. */ classes?: Computable>; + /** CSS to apply to this feature. */ style?: Computable; + /** The display to use for this challenge. */ display?: Computable< | CoercableComponent | { + /** A header to appear at the top of the display. */ title?: CoercableComponent; + /** The main text that appears in the display. */ description: CoercableComponent; + /** A description of the current goal for this challenge. */ goal?: CoercableComponent; + /** A description of what will change upon completing this challenge. */ reward?: CoercableComponent; + /** A description of the current effect of this challenge. */ effectDisplay?: CoercableComponent; } >; + /** A function that is called when the challenge is completed. */ onComplete?: VoidFunction; + /** A function that is called when the challenge is exited. */ onExit?: VoidFunction; + /** A function that is called when the challenge is entered. */ onEnter?: VoidFunction; } +/** + * The properties that are added onto a processed {@link ChallengeOptions} to create a {@link Challenge}. + */ export interface BaseChallenge { + /** An auto-generated ID for identifying challenges that appear in the DOM. Will not persist between refreshes or updates. */ id: string; + /** The current amount of times this challenge can be completed. */ canComplete: Ref; + /** The current number of times this challenge has been completed. */ completions: Persistent; + /** Whether or not this challenge has been completed. */ completed: Ref; + /** Whether or not this challenge's completion count is at its limit. */ maxed: Ref; + /** Whether or not this challenge is currently active. */ active: Persistent; + /** A function to enter or leave the challenge. */ toggle: VoidFunction; + /** + * A function to complete this challenge. + * @param remainInChallenge - Optional parameter to specify if the challenge should remain active after completion. + */ complete: (remainInChallenge?: boolean) => void; + /** A symbol that helps identify features of the same type. */ type: typeof ChallengeType; + /** 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; } +/** An object that represents a feature that can be entered and exited, and have one or more completions with scaling requirements. */ export type Challenge = Replace< T & BaseChallenge, { @@ -92,6 +132,7 @@ export type Challenge = Replace< } >; +/** A type that matches any valid {@link Challenge} object. */ export type GenericChallenge = Replace< Challenge, { @@ -102,6 +143,10 @@ export type GenericChallenge = Replace< } >; +/** + * Lazily creates a challenge with the given options. + * @param optionsFunc Challenge options. + */ export function createChallenge( optionsFunc: OptionsFunc ): Challenge { @@ -248,6 +293,12 @@ export function createChallenge( }); } +/** + * This will automatically complete a challenge when it's requirements are met. + * @param challenge The challenge to auto-complete + * @param autoActive Whether or not auto-completing should currently occur + * @param exitOnComplete Whether or not to exit the challenge after auto-completion + */ export function setupAutoComplete( challenge: GenericChallenge, autoActive: Computable = true, @@ -264,19 +315,27 @@ export function setupAutoComplete( ); } +/** + * Utility for taking an array of challenges where only one may be active at a time, and giving a ref to the one currently active (or null if none are active) + * @param challenges The list of challenges that are mutually exclusive + */ export function createActiveChallenge( challenges: GenericChallenge[] -): Ref { - return computed(() => challenges.find(challenge => challenge.active.value)); +): Ref { + return computed(() => challenges.find(challenge => challenge.active.value) ?? null); } +/** + * Utility for reporting if any challenge in a list is currently active. Intended for preventing entering a challenge if another is already active. + * @param challenges List of challenges that are mutually exclusive + */ export function isAnyChallengeActive( - challenges: GenericChallenge[] | Ref + challenges: GenericChallenge[] | Ref ): Ref { if (isArray(challenges)) { challenges = createActiveChallenge(challenges); } - return computed(() => (challenges as Ref).value != null); + return computed(() => (challenges as Ref).value != null); } declare module "game/settings" {