Use watchEffect instead of event listener for completing achievements
This commit is contained in:
parent
d680e468b2
commit
1fac1edb46
2 changed files with 53 additions and 84 deletions
|
@ -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;
|
|
||||||
});
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue