2022-02-27 22:04:56 +00:00
|
|
|
import MilestoneComponent from "@/features/milestones/Milestone.vue";
|
2022-01-14 04:25:47 +00:00
|
|
|
import {
|
|
|
|
CoercableComponent,
|
|
|
|
Component,
|
|
|
|
findFeatures,
|
2022-02-27 19:49:34 +00:00
|
|
|
GatherProps,
|
2022-01-14 04:25:47 +00:00
|
|
|
getUniqueID,
|
|
|
|
makePersistent,
|
|
|
|
Persistent,
|
2022-01-25 04:23:30 +00:00
|
|
|
PersistentState,
|
2022-01-14 04:25:47 +00:00
|
|
|
Replace,
|
|
|
|
setDefault,
|
|
|
|
StyleValue,
|
|
|
|
Visibility
|
|
|
|
} from "@/features/feature";
|
|
|
|
import { globalBus } from "@/game/events";
|
|
|
|
import "@/game/notifications";
|
|
|
|
import settings from "@/game/settings";
|
|
|
|
import {
|
|
|
|
Computable,
|
|
|
|
GetComputableType,
|
|
|
|
GetComputableTypeWithDefault,
|
|
|
|
processComputable,
|
|
|
|
ProcessedComputable
|
|
|
|
} from "@/util/computed";
|
2022-02-27 19:49:34 +00:00
|
|
|
import { createLazyProxy } from "@/util/proxies";
|
2022-01-14 04:25:47 +00:00
|
|
|
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<Visibility>;
|
|
|
|
shouldEarn: Computable<boolean>;
|
|
|
|
style?: Computable<StyleValue>;
|
|
|
|
classes?: Computable<Record<string, boolean>>;
|
|
|
|
display?: Computable<
|
|
|
|
| CoercableComponent
|
|
|
|
| {
|
|
|
|
requirement: CoercableComponent;
|
|
|
|
effectDisplay?: CoercableComponent;
|
|
|
|
optionsDisplay?: CoercableComponent;
|
|
|
|
}
|
|
|
|
>;
|
|
|
|
onComplete?: VoidFunction;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface BaseMilestone extends Persistent<boolean> {
|
|
|
|
id: string;
|
|
|
|
earned: Ref<boolean>;
|
|
|
|
type: typeof MilestoneType;
|
|
|
|
[Component]: typeof MilestoneComponent;
|
2022-02-27 19:49:34 +00:00
|
|
|
[GatherProps]: () => Record<string, unknown>;
|
2022-01-14 04:25:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export type Milestone<T extends MilestoneOptions> = Replace<
|
|
|
|
T & BaseMilestone,
|
|
|
|
{
|
|
|
|
visibility: GetComputableTypeWithDefault<T["visibility"], Visibility.Visible>;
|
|
|
|
shouldEarn: GetComputableType<T["shouldEarn"]>;
|
|
|
|
style: GetComputableType<T["style"]>;
|
|
|
|
classes: GetComputableType<T["classes"]>;
|
|
|
|
display: GetComputableType<T["display"]>;
|
|
|
|
}
|
|
|
|
>;
|
|
|
|
|
|
|
|
export type GenericMilestone = Replace<
|
|
|
|
Milestone<MilestoneOptions>,
|
|
|
|
{
|
|
|
|
visibility: ProcessedComputable<Visibility>;
|
|
|
|
}
|
|
|
|
>;
|
|
|
|
|
|
|
|
export function createMilestone<T extends MilestoneOptions>(
|
2022-02-27 19:49:34 +00:00
|
|
|
optionsFunc: () => T & ThisType<Milestone<T>>
|
2022-01-14 04:25:47 +00:00
|
|
|
): Milestone<T> {
|
2022-02-27 19:49:34 +00:00
|
|
|
return createLazyProxy(() => {
|
|
|
|
const milestone: T & Partial<BaseMilestone> = optionsFunc();
|
|
|
|
makePersistent<boolean>(milestone, false);
|
|
|
|
milestone.id = getUniqueID("milestone-");
|
|
|
|
milestone.type = MilestoneType;
|
|
|
|
milestone[Component] = MilestoneComponent;
|
2022-01-14 04:25:47 +00:00
|
|
|
|
2022-02-27 19:49:34 +00:00
|
|
|
milestone.earned = milestone[PersistentState];
|
|
|
|
processComputable(milestone as T, "visibility");
|
|
|
|
setDefault(milestone, "visibility", Visibility.Visible);
|
|
|
|
const visibility = milestone.visibility as ProcessedComputable<Visibility>;
|
|
|
|
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<string, unknown>)
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
return Visibility.None;
|
|
|
|
}
|
|
|
|
return unref(visibility);
|
|
|
|
case MilestoneDisplay.Incomplete:
|
|
|
|
if (unref(milestone.earned)) {
|
|
|
|
return Visibility.None;
|
|
|
|
}
|
|
|
|
return unref(visibility);
|
|
|
|
case MilestoneDisplay.None:
|
2022-01-14 04:25:47 +00:00
|
|
|
return Visibility.None;
|
2022-02-27 19:49:34 +00:00
|
|
|
}
|
|
|
|
});
|
2022-01-14 04:25:47 +00:00
|
|
|
|
2022-02-27 19:49:34 +00:00
|
|
|
processComputable(milestone as T, "shouldEarn");
|
|
|
|
processComputable(milestone as T, "style");
|
|
|
|
processComputable(milestone as T, "classes");
|
|
|
|
processComputable(milestone as T, "display");
|
2022-01-14 04:25:47 +00:00
|
|
|
|
2022-02-27 19:49:34 +00:00
|
|
|
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<T>;
|
|
|
|
});
|
2022-01-14 04:25:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const toast = useToast();
|
|
|
|
|
2022-01-25 04:23:30 +00:00
|
|
|
const listeners: Record<string, Unsubscribe | undefined> = {};
|
2022-01-14 04:25:47 +00:00
|
|
|
globalBus.on("addLayer", layer => {
|
2022-01-25 04:23:30 +00:00
|
|
|
const milestones: GenericMilestone[] = (
|
|
|
|
findFeatures(layer, MilestoneType) as GenericMilestone[]
|
|
|
|
).filter(milestone => milestone.shouldEarn != null);
|
2022-01-14 04:25:47 +00:00
|
|
|
listeners[layer.id] = layer.on("postUpdate", () => {
|
|
|
|
milestones.forEach(milestone => {
|
|
|
|
if (
|
|
|
|
unref(milestone.visibility) === Visibility.Visible &&
|
|
|
|
!milestone.earned.value &&
|
|
|
|
unref(milestone.shouldEarn)
|
|
|
|
) {
|
2022-01-25 04:23:30 +00:00
|
|
|
milestone[PersistentState].value = true;
|
2022-01-14 04:25:47 +00:00
|
|
|
milestone.onComplete?.();
|
|
|
|
if (milestone.display) {
|
|
|
|
const display = unref(milestone.display);
|
2022-01-25 04:25:34 +00:00
|
|
|
const Display = coerceComponent(
|
|
|
|
isCoercableComponent(display) ? display : display.requirement
|
|
|
|
);
|
|
|
|
toast(
|
2022-02-27 19:49:34 +00:00
|
|
|
<>
|
2022-01-25 04:25:34 +00:00
|
|
|
<h3>Milestone earned!</h3>
|
2022-01-14 04:25:47 +00:00
|
|
|
<div>
|
2022-01-25 04:25:34 +00:00
|
|
|
{/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
|
|
|
|
{/* @ts-ignore */}
|
|
|
|
<Display />
|
2022-01-14 04:25:47 +00:00
|
|
|
</div>
|
2022-02-27 19:49:34 +00:00
|
|
|
</>
|
2022-01-14 04:25:47 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
globalBus.on("removeLayer", layer => {
|
|
|
|
// unsubscribe from postUpdate
|
|
|
|
listeners[layer.id]?.();
|
2022-01-25 04:23:30 +00:00
|
|
|
listeners[layer.id] = undefined;
|
2022-01-14 04:25:47 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
declare module "@/game/settings" {
|
|
|
|
interface Settings {
|
|
|
|
msDisplay: MilestoneDisplay;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
globalBus.on("loadSettings", settings => {
|
|
|
|
setDefault(settings, "msDisplay", MilestoneDisplay.All);
|
|
|
|
});
|