import Select from "components/fields/Select.vue"; import { CoercableComponent, Component, findFeatures, GatherProps, getUniqueID, jsx, Replace, setDefault, StyleValue, Visibility } from "features/feature"; import MilestoneComponent from "features/milestones/Milestone.vue"; import { globalBus } from "game/events"; import "game/notifications"; import { makePersistent, Persistent, PersistentState } from "game/persistence"; import settings, { registerSettingField } from "game/settings"; import { camelToTitle } from "util/common"; import { Computable, GetComputableType, GetComputableTypeWithDefault, processComputable, ProcessedComputable } from "util/computed"; import { createLazyProxy } from "util/proxies"; import { coerceComponent, isCoercableComponent } from "util/vue"; import { Unsubscribe } from "nanoevents"; import { computed, Ref, unref } from "vue"; import { useToast } from "vue-toastification"; export const MilestoneType = Symbol("Milestone"); export enum MilestoneDisplay { All = "all", //Last = "last", Configurable = "configurable", Incomplete = "incomplete", None = "none" } export interface MilestoneOptions { visibility?: Computable; shouldEarn: Computable; style?: Computable; classes?: Computable>; display?: Computable< | CoercableComponent | { requirement: CoercableComponent; effectDisplay?: CoercableComponent; optionsDisplay?: CoercableComponent; } >; onComplete?: VoidFunction; } export interface BaseMilestone extends Persistent { id: string; earned: Ref; type: typeof MilestoneType; [Component]: typeof MilestoneComponent; [GatherProps]: () => Record; } export type Milestone = Replace< T & BaseMilestone, { visibility: GetComputableTypeWithDefault; shouldEarn: GetComputableType; style: GetComputableType; classes: GetComputableType; display: GetComputableType; } >; export type GenericMilestone = Replace< Milestone, { visibility: ProcessedComputable; } >; export function createMilestone( optionsFunc: () => T & ThisType> ): Milestone { return createLazyProxy(() => { const milestone: T & Partial = optionsFunc(); makePersistent(milestone, false); milestone.id = getUniqueID("milestone-"); milestone.type = MilestoneType; milestone[Component] = MilestoneComponent; milestone.earned = milestone[PersistentState]; processComputable(milestone as T, "visibility"); setDefault(milestone, "visibility", Visibility.Visible); const visibility = milestone.visibility as ProcessedComputable; milestone.visibility = computed(() => { const display = unref((milestone as GenericMilestone).display); switch (settings.msDisplay) { default: case MilestoneDisplay.All: return unref(visibility); case MilestoneDisplay.Configurable: if ( unref(milestone.earned) && !( display != null && typeof display == "object" && "optionsDisplay" in (display as Record) ) ) { return Visibility.None; } return unref(visibility); case MilestoneDisplay.Incomplete: if (unref(milestone.earned)) { return Visibility.None; } return unref(visibility); case MilestoneDisplay.None: return Visibility.None; } }); processComputable(milestone as T, "shouldEarn"); processComputable(milestone as T, "style"); processComputable(milestone as T, "classes"); processComputable(milestone as T, "display"); milestone[GatherProps] = function (this: GenericMilestone) { const { visibility, display, style, classes, earned, id } = this; return { visibility, display, style, classes, earned, id }; }; return milestone as unknown as Milestone; }); } const toast = useToast(); const listeners: Record = {}; globalBus.on("addLayer", layer => { const milestones: GenericMilestone[] = ( findFeatures(layer, MilestoneType) as GenericMilestone[] ).filter(milestone => milestone.shouldEarn != null); listeners[layer.id] = layer.on("postUpdate", () => { milestones.forEach(milestone => { if ( unref(milestone.visibility) === Visibility.Visible && !milestone.earned.value && unref(milestone.shouldEarn) ) { milestone[PersistentState].value = true; milestone.onComplete?.(); if (milestone.display) { const display = unref(milestone.display); const Display = coerceComponent( isCoercableComponent(display) ? display : display.requirement ); toast( <>

Milestone earned!

{/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */} {/* @ts-ignore */}
); } } }); }); }); globalBus.on("removeLayer", layer => { // unsubscribe from postUpdate listeners[layer.id]?.(); listeners[layer.id] = undefined; }); declare module "game/settings" { interface Settings { msDisplay: MilestoneDisplay; } } globalBus.on("loadSettings", settings => { setDefault(settings, "msDisplay", MilestoneDisplay.All); }); const msDisplayOptions = Object.values(MilestoneDisplay).map(option => ({ label: camelToTitle(option), value: option })); registerSettingField( jsx(() => (