Use watchEffect instead of event listener for completing achievements

This commit is contained in:
thepaperpilot 2022-03-11 08:54:05 -06:00
parent d680e468b2
commit 1fac1edb46
2 changed files with 53 additions and 84 deletions

View file

@ -2,7 +2,6 @@ import AchievementComponent from "features/achievements/Achievement.vue";
import { import {
CoercableComponent, CoercableComponent,
Component, Component,
findFeatures,
GatherProps, GatherProps,
getUniqueID, getUniqueID,
Replace, Replace,
@ -10,7 +9,6 @@ import {
StyleValue, StyleValue,
Visibility Visibility
} from "features/feature"; } from "features/feature";
import { globalBus } from "game/events";
import "game/notifications"; import "game/notifications";
import { Persistent, makePersistent, PersistentState } from "game/persistence"; import { Persistent, makePersistent, PersistentState } from "game/persistence";
import { import {
@ -22,15 +20,16 @@ import {
} from "util/computed"; } from "util/computed";
import { createLazyProxy } from "util/proxies"; import { createLazyProxy } from "util/proxies";
import { coerceComponent } from "util/vue"; import { coerceComponent } from "util/vue";
import { Unsubscribe } from "nanoevents"; import { Ref, unref, watchEffect } from "vue";
import { Ref, unref } from "vue";
import { useToast } from "vue-toastification"; import { useToast } from "vue-toastification";
const toast = useToast();
export const AchievementType = Symbol("Achievement"); export const AchievementType = Symbol("Achievement");
export interface AchievementOptions { export interface AchievementOptions {
visibility?: Computable<Visibility>; visibility?: Computable<Visibility>;
shouldEarn?: Computable<boolean>; shouldEarn?: () => boolean;
display?: Computable<CoercableComponent>; display?: Computable<CoercableComponent>;
mark?: Computable<boolean | string>; mark?: Computable<boolean | string>;
image?: Computable<string>; image?: Computable<string>;
@ -52,7 +51,6 @@ export type Achievement<T extends AchievementOptions> = Replace<
T & BaseAchievement, T & BaseAchievement,
{ {
visibility: GetComputableTypeWithDefault<T["visibility"], Visibility.Visible>; visibility: GetComputableTypeWithDefault<T["visibility"], Visibility.Visible>;
shouldEarn: GetComputableType<T["shouldEarn"]>;
display: GetComputableType<T["display"]>; display: GetComputableType<T["display"]>;
mark: GetComputableType<T["mark"]>; mark: GetComputableType<T["mark"]>;
image: GetComputableType<T["image"]>; image: GetComputableType<T["image"]>;
@ -85,7 +83,6 @@ export function createAchievement<T extends AchievementOptions>(
processComputable(achievement as T, "visibility"); processComputable(achievement as T, "visibility");
setDefault(achievement, "visibility", Visibility.Visible); setDefault(achievement, "visibility", Visibility.Visible);
processComputable(achievement as T, "shouldEarn");
processComputable(achievement as T, "display"); processComputable(achievement as T, "display");
processComputable(achievement as T, "mark"); processComputable(achievement as T, "mark");
processComputable(achievement as T, "image"); processComputable(achievement as T, "image");
@ -97,29 +94,18 @@ export function createAchievement<T extends AchievementOptions>(
return { visibility, display, earned, image, style, classes, mark, id }; return { visibility, display, earned, image, style, classes, mark, id };
}; };
return achievement as unknown as Achievement<T>; if (achievement.shouldEarn) {
}); const genericAchievement = achievement as GenericAchievement;
} watchEffect(() => {
const toast = useToast();
const listeners: Record<string, Unsubscribe | undefined> = {};
globalBus.on("addLayer", layer => {
const achievements: GenericAchievement[] = (
findFeatures(layer, AchievementType) as GenericAchievement[]
).filter(ach => ach.shouldEarn != null);
if (achievements.length) {
listeners[layer.id] = layer.on("postUpdate", () => {
achievements.forEach(achievement => {
if ( if (
unref(achievement.visibility) === Visibility.Visible && !genericAchievement.earned.value &&
!unref(achievement.earned) && unref(genericAchievement.visibility) === Visibility.Visible &&
unref(achievement.shouldEarn) genericAchievement.shouldEarn?.()
) { ) {
achievement[PersistentState].value = true; genericAchievement.earned.value = true;
achievement.onComplete?.(); genericAchievement.onComplete?.();
if (achievement.display) { if (genericAchievement.display) {
const Display = coerceComponent(unref(achievement.display)); const Display = coerceComponent(unref(genericAchievement.display));
toast.info( toast.info(
<div> <div>
<h3>Achievement earned!</h3> <h3>Achievement earned!</h3>
@ -133,11 +119,8 @@ globalBus.on("addLayer", layer => {
} }
} }
}); });
}); }
}
}); return achievement as unknown as Achievement<T>;
globalBus.on("removeLayer", layer => { });
// unsubscribe from postUpdate }
listeners[layer.id]?.();
listeners[layer.id] = undefined;
});

View file

@ -2,7 +2,6 @@ import Select from "components/fields/Select.vue";
import { import {
CoercableComponent, CoercableComponent,
Component, Component,
findFeatures,
GatherProps, GatherProps,
getUniqueID, getUniqueID,
jsx, jsx,
@ -26,10 +25,11 @@ import {
} from "util/computed"; } from "util/computed";
import { createLazyProxy } from "util/proxies"; import { createLazyProxy } from "util/proxies";
import { coerceComponent, isCoercableComponent } from "util/vue"; import { coerceComponent, isCoercableComponent } from "util/vue";
import { Unsubscribe } from "nanoevents"; import { computed, Ref, unref, watchEffect } from "vue";
import { computed, Ref, unref } from "vue";
import { useToast } from "vue-toastification"; import { useToast } from "vue-toastification";
const toast = useToast();
export const MilestoneType = Symbol("Milestone"); export const MilestoneType = Symbol("Milestone");
export enum MilestoneDisplay { export enum MilestoneDisplay {
@ -42,7 +42,7 @@ export enum MilestoneDisplay {
export interface MilestoneOptions { export interface MilestoneOptions {
visibility?: Computable<Visibility>; visibility?: Computable<Visibility>;
shouldEarn: Computable<boolean>; shouldEarn?: () => boolean;
style?: Computable<StyleValue>; style?: Computable<StyleValue>;
classes?: Computable<Record<string, boolean>>; classes?: Computable<Record<string, boolean>>;
display?: Computable< display?: Computable<
@ -68,7 +68,6 @@ export type Milestone<T extends MilestoneOptions> = Replace<
T & BaseMilestone, T & BaseMilestone,
{ {
visibility: GetComputableTypeWithDefault<T["visibility"], Visibility.Visible>; visibility: GetComputableTypeWithDefault<T["visibility"], Visibility.Visible>;
shouldEarn: GetComputableType<T["shouldEarn"]>;
style: GetComputableType<T["style"]>; style: GetComputableType<T["style"]>;
classes: GetComputableType<T["classes"]>; classes: GetComputableType<T["classes"]>;
display: GetComputableType<T["display"]>; display: GetComputableType<T["display"]>;
@ -124,7 +123,6 @@ export function createMilestone<T extends MilestoneOptions>(
} }
}); });
processComputable(milestone as T, "shouldEarn");
processComputable(milestone as T, "style"); processComputable(milestone as T, "style");
processComputable(milestone as T, "classes"); processComputable(milestone as T, "classes");
processComputable(milestone as T, "display"); processComputable(milestone as T, "display");
@ -134,52 +132,40 @@ export function createMilestone<T extends MilestoneOptions>(
return { visibility, display, style, classes, earned, id }; return { visibility, display, style, classes, earned, id };
}; };
if (milestone.shouldEarn) {
const genericMilestone = milestone as GenericMilestone;
watchEffect(() => {
if (
!genericMilestone.earned.value &&
unref(genericMilestone.visibility) === Visibility.Visible &&
genericMilestone.shouldEarn?.()
) {
genericMilestone.earned.value = true;
genericMilestone.onComplete?.();
if (genericMilestone.display) {
const display = unref(genericMilestone.display);
const Display = coerceComponent(
isCoercableComponent(display) ? display : display.requirement
);
toast(
<>
<h3>Milestone earned!</h3>
<div>
{/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
{/* @ts-ignore */}
<Display />
</div>
</>
);
}
}
});
}
return milestone as unknown as Milestone<T>; return milestone as unknown as Milestone<T>;
}); });
} }
const toast = useToast();
const listeners: Record<string, Unsubscribe | undefined> = {};
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(
<>
<h3>Milestone earned!</h3>
<div>
{/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
{/* @ts-ignore */}
<Display />
</div>
</>
);
}
}
});
});
});
globalBus.on("removeLayer", layer => {
// unsubscribe from postUpdate
listeners[layer.id]?.();
listeners[layer.id] = undefined;
});
declare module "game/settings" { declare module "game/settings" {
interface Settings { interface Settings {
msDisplay: MilestoneDisplay; msDisplay: MilestoneDisplay;