new help section explaining classes and their mechanics

This commit is contained in:
Warren H
2026-07-18 12:05:34 -04:00
parent 638aef22b2
commit 40aa23be46
32 changed files with 1423 additions and 134 deletions
+3 -1
View File
@@ -45,7 +45,9 @@ describe("Appearance Lab frontend state", () => {
state.setAppearancePreviewAnimation("cast");
state = useFrontendStore.getState();
expect(state.appearanceDrafts.priest.headPartId).toBe("rogue-head");
expect(state.slots[2].local!.healers.priest.appearance.headPartId).toBe("mage-head");
expect(state.slots[2].local!.healers.priest.appearance.headPartId).toBe(
createDefaultHealerAppearance("priest").headPartId,
);
expect(state.previewMode).toBe("legacy");
expect(state.previewAnimation).toBe("cast");
+42
View File
@@ -0,0 +1,42 @@
import { afterEach, describe, expect, it } from "vitest";
import { getFrontendSnapshot, useFrontendStore } from "./store";
const originalState = useFrontendStore.getState();
afterEach(() => {
useFrontendStore.setState({
activeSlotId: originalState.activeSlotId,
screen: originalState.screen,
guideClassId: originalState.guideClassId,
guideAbilityId: originalState.guideAbilityId,
notice: originalState.notice,
});
});
describe("Class Help frontend state", () => {
it("opens on ability one, resets selection on class change, and syncs guide state", () => {
useFrontendStore.setState({
activeSlotId: null,
screen: "home",
guideClassId: "shaman",
guideAbilityId: "ability6",
notice: "Old notice",
});
useFrontendStore.getState().openClassHelp();
let state = useFrontendStore.getState();
expect(state.screen).toBe("class-help");
expect(state.guideClassId).toBe("shaman");
expect(state.guideAbilityId).toBe("ability1");
expect(state.notice).toBe("");
state.selectGuideClass("paladin");
state.selectGuideAbility("ability5");
const snapshot = getFrontendSnapshot();
expect(snapshot.guideClassId).toBe("paladin");
expect(snapshot.guideAbilityId).toBe("ability5");
expect("openClassHelp" in snapshot).toBe(false);
expect("selectGuideClass" in snapshot).toBe(false);
expect("selectGuideAbility" in snapshot).toBe(false);
});
});
+24
View File
@@ -126,6 +126,8 @@ export interface FrontendState {
selectedInfusionId: string;
selectedPassiveAbilityId: AbilitySlotId;
selectedPassiveInfusionId: RunBuffId;
guideClassId: HealerClassId;
guideAbilityId: AbilitySlotId;
profileCollectionView: ProfileCollectionView;
selectedProfileGroupId: BossGroupId;
selectedProfileStatId: ProfileStatId;
@@ -158,6 +160,9 @@ export interface FrontendState {
selectInfusion: (infusionId: string) => void;
selectPassiveAbility: (abilityId: AbilitySlotId) => void;
selectPassiveInfusion: (passiveId: RunBuffId) => void;
openClassHelp: () => void;
selectGuideClass: (classId: HealerClassId) => void;
selectGuideAbility: (abilityId: AbilitySlotId) => void;
selectProfileCollectionView: (view: ProfileCollectionView) => void;
selectProfileGroup: (groupId: BossGroupId) => void;
selectProfileStat: (statId: ProfileStatId) => void;
@@ -207,6 +212,8 @@ export const useFrontendStore = create<FrontendState>((set, get) => ({
selectedInfusionId: infusionsForOwner("priest")[0].id,
selectedPassiveAbilityId: "ability1",
selectedPassiveInfusionId: "mend-echo",
guideClassId: "priest",
guideAbilityId: "ability1",
profileCollectionView: "stats",
selectedProfileGroupId: "charge",
selectedProfileStatId: "roguelike",
@@ -373,6 +380,17 @@ export const useFrontendStore = create<FrontendState>((set, get) => ({
selectedPassiveInfusionId,
notice: "",
}),
openClassHelp: () => {
const hunter = activeSave(get().slots, get().activeSlotId);
set({
screen: "class-help",
guideClassId: hunter?.activeClassId ?? get().guideClassId,
guideAbilityId: "ability1",
notice: "",
});
},
selectGuideClass: (guideClassId) => set({ guideClassId, guideAbilityId: "ability1" }),
selectGuideAbility: (guideAbilityId) => set({ guideAbilityId }),
selectProfileCollectionView: (profileCollectionView) => set({ profileCollectionView }),
selectProfileGroup: (selectedProfileGroupId) => set({ selectedProfileGroupId }),
selectProfileStat: (selectedProfileStatId) => set({ selectedProfileStatId }),
@@ -729,6 +747,9 @@ export type FrontendSnapshot = Omit<FrontendState,
| "selectInfusion"
| "selectPassiveAbility"
| "selectPassiveInfusion"
| "openClassHelp"
| "selectGuideClass"
| "selectGuideAbility"
| "selectProfileCollectionView"
| "selectProfileGroup"
| "selectProfileStat"
@@ -783,6 +804,9 @@ export function getFrontendSnapshot(): FrontendSnapshot {
selectInfusion: _selectInfusion,
selectPassiveAbility: _selectPassiveAbility,
selectPassiveInfusion: _selectPassiveInfusion,
openClassHelp: _openClassHelp,
selectGuideClass: _selectGuideClass,
selectGuideAbility: _selectGuideAbility,
selectProfileCollectionView: _selectProfileCollectionView,
selectProfileGroup: _selectProfileGroup,
selectProfileStat: _selectProfileStat,
+1 -1
View File
@@ -5,7 +5,7 @@ import type { CollectionLog, MaterialStack } from "../game/progression/loot";
import type { CharacterAppearanceV1 } from "../game/characterAppearance";
export type SaveSlotId = 1 | 2 | 3;
export type AppScreen = "login" | "saves" | "home" | "profile" | "gear" | "appearance" | "settings" | "mode" | "game";
export type AppScreen = "login" | "saves" | "home" | "class-help" | "profile" | "gear" | "appearance" | "settings" | "mode" | "game";
export type GameModeId = "roguelike-pve" | "rogue-trials" | "dungeons" | "hockey-healing" | "hockey-healing-pvp" | "blockbreaker" | "aether-assault" | "roguelike-pvp" | "stadium-pvp";
export type ProfileCollectionView = "loot" | "trophies" | "stats";
export type ProfileStatId = BossId | "roguelike" | "rogue-trials-endless" | "hockey-healing" | "hockey-pvp-wins" | "hockey-pvp-boss-kills" | "blockbreaker-bricks" | "blockbreaker-time" | "blockbreaker-score" | "aether-assault";