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

View file

@ -2,7 +2,6 @@ import Select from "components/fields/Select.vue";
import {
CoercableComponent,
Component,
findFeatures,
GatherProps,
getUniqueID,
jsx,
@ -26,10 +25,11 @@ import {
} 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 { computed, Ref, unref, watchEffect } from "vue";
import { useToast } from "vue-toastification";
const toast = useToast();
export const MilestoneType = Symbol("Milestone");
export enum MilestoneDisplay {
@ -42,7 +42,7 @@ export enum MilestoneDisplay {
export interface MilestoneOptions {
visibility?: Computable<Visibility>;
shouldEarn: Computable<boolean>;
shouldEarn?: () => boolean;
style?: Computable<StyleValue>;
classes?: Computable<Record<string, boolean>>;
display?: Computable<
@ -68,7 +68,6 @@ 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"]>;
@ -124,7 +123,6 @@ export function createMilestone<T extends MilestoneOptions>(
}
});
processComputable(milestone as T, "shouldEarn");
processComputable(milestone as T, "style");
processComputable(milestone as T, "classes");
processComputable(milestone as T, "display");
@ -134,52 +132,40 @@ export function createMilestone<T extends MilestoneOptions>(
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>;
});
}
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" {
interface Settings {
msDisplay: MilestoneDisplay;