Update Healer Man server
This commit is contained in:
1 parent
97ee0c13f9
commit
f864b5aea7
20 files changed
+468
-211
No files matched your search
@@ -1,5 +1,8 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { BITE_SIZED_STOCKADES_DUNGEON_ID } from "../game/dungeonContentSource";
|
||||
import {
|
||||
BITE_SIZED_STOCKADES_DUNGEON_ID,
|
||||
RANDOMIZED_BITE_SIZED_STOCKADES_DUNGEON_ID,
|
||||
} from "../game/dungeonContentSource";
|
||||
import type { DungeonRunProgress } from "../game/dungeonRunTypes";
|
||||
import { canStartAnotherBiteSizedDungeon, startAnotherBiteSizedDungeon } from "./biteSizedDungeonRuntime";
|
||||
import { useOnlineGroupStore } from "./onlineGroupStore";
|
||||
@@ -52,6 +55,36 @@ describe("bite-sized replay", () => {
|
||||
difficultyId: "normal",
|
||||
bossDifficulty: 1,
|
||||
freshRun: true,
|
||||
runId: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it("replays randomized mode with a new seed and prepares that seed's creature assets", async () => {
|
||||
const enterDungeon = vi.fn(() => true);
|
||||
const saveCheckpoint = vi.fn();
|
||||
const prepareRandomizedContent = vi.fn(async () => undefined);
|
||||
useShellStore.setState({
|
||||
session: { kind: "offline", ownerId: "local-owner", displayName: "Tester" },
|
||||
activeDungeonRole: "healer",
|
||||
enterDungeon,
|
||||
saveActiveDungeonRunCheckpoint: saveCheckpoint,
|
||||
});
|
||||
const randomizedProgress = { ...progress, dungeonId: RANDOMIZED_BITE_SIZED_STOCKADES_DUNGEON_ID };
|
||||
|
||||
await expect(startAnotherBiteSizedDungeon(randomizedProgress, {
|
||||
random: () => 0,
|
||||
createRunId: () => "next-randomized-seed",
|
||||
contentIsUsable: () => true,
|
||||
prepareRandomizedContent,
|
||||
})).resolves.toBe(true);
|
||||
|
||||
expect(prepareRandomizedContent).toHaveBeenCalledWith("next-randomized-seed");
|
||||
expect(saveCheckpoint).toHaveBeenCalledWith(null);
|
||||
expect(enterDungeon).toHaveBeenCalledWith(RANDOMIZED_BITE_SIZED_STOCKADES_DUNGEON_ID, "healer", {
|
||||
difficultyId: "normal",
|
||||
bossDifficulty: 1,
|
||||
freshRun: true,
|
||||
runId: "next-randomized-seed",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -91,4 +124,31 @@ describe("bite-sized replay", () => {
|
||||
useShellStore.setState({ session: { kind: "account", ownerId: "follower", displayName: "Follower", accessToken: "test-token" } });
|
||||
expect(canStartAnotherBiteSizedDungeon()).toBe(false);
|
||||
});
|
||||
|
||||
it("publishes another randomized activity instead of crossing an online group into regular Bite-Sized", async () => {
|
||||
const startActivity = vi.fn(async () => true);
|
||||
useShellStore.setState({
|
||||
session: { kind: "account", ownerId: "leader", displayName: "Leader", accessToken: "test-token" },
|
||||
saveActiveDungeonRunCheckpoint: vi.fn(),
|
||||
});
|
||||
useOnlineGroupStore.setState({
|
||||
startActivity,
|
||||
snapshot: { invitations: [], group: { id: "group", leaderAccountId: "leader", members: [], activity: null,
|
||||
activityRevision: 2, createdAt: 1, updatedAt: 2 } },
|
||||
});
|
||||
|
||||
await expect(startAnotherBiteSizedDungeon(
|
||||
{ ...progress, dungeonId: RANDOMIZED_BITE_SIZED_STOCKADES_DUNGEON_ID },
|
||||
{ random: () => 0, createRunId: () => "unused-local-seed", contentIsUsable: () => true,
|
||||
prepareRandomizedContent: vi.fn() },
|
||||
)).resolves.toBe(true);
|
||||
|
||||
expect(startActivity).toHaveBeenCalledWith(expect.objectContaining({ ownerId: "leader" }), {
|
||||
type: "dungeon",
|
||||
contentId: RANDOMIZED_BITE_SIZED_STOCKADES_DUNGEON_ID,
|
||||
difficultyId: "normal",
|
||||
bossDifficulty: 1,
|
||||
partySize: 5,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,11 @@
|
||||
import { dungeonContentIsUsable } from "../content/contentManager";
|
||||
import { randomDungeonByCategory } from "../game/dungeonCatalog";
|
||||
import { randomDungeonByCategory, randomRandomizedBiteSizedDungeon } from "../game/dungeonCatalog";
|
||||
import { isRandomizedBiteSizedDungeonId } from "../game/dungeonContentSource";
|
||||
import { dungeonDefinitionById } from "../game/dungeonRegistry";
|
||||
import type { DungeonRunProgress } from "../game/dungeonRunTypes";
|
||||
import { useOnlineGroupStore } from "./onlineGroupStore";
|
||||
import { useOnlineSessionStore } from "./onlineSessionStore";
|
||||
import { ensureRandomizedBiteSizedRunContent } from "./randomizedBiteSizedContent";
|
||||
import { useShellStore } from "./shellStore";
|
||||
|
||||
export function canStartAnotherBiteSizedDungeon(): boolean {
|
||||
@@ -14,8 +16,25 @@ export function canStartAnotherBiteSizedDungeon(): boolean {
|
||||
return !online || online.authority;
|
||||
}
|
||||
|
||||
interface BiteSizedReplayDependencies {
|
||||
readonly random: () => number;
|
||||
readonly createRunId: () => string;
|
||||
readonly contentIsUsable: (dungeonId: string) => boolean;
|
||||
readonly prepareRandomizedContent: (seed: string) => Promise<void>;
|
||||
}
|
||||
|
||||
const DEFAULT_REPLAY_DEPENDENCIES: BiteSizedReplayDependencies = {
|
||||
random: Math.random,
|
||||
createRunId: () => globalThis.crypto.randomUUID(),
|
||||
contentIsUsable: dungeonContentIsUsable,
|
||||
prepareRandomizedContent: ensureRandomizedBiteSizedRunContent,
|
||||
};
|
||||
|
||||
/** Starts a fresh random run locally, or publishes one shared activity for the whole online party. */
|
||||
export async function startAnotherBiteSizedDungeon(progress: DungeonRunProgress): Promise<boolean> {
|
||||
export async function startAnotherBiteSizedDungeon(
|
||||
progress: DungeonRunProgress,
|
||||
dependencies: BiteSizedReplayDependencies = DEFAULT_REPLAY_DEPENDENCIES,
|
||||
): Promise<boolean> {
|
||||
const shell = useShellStore.getState();
|
||||
const groupState = useOnlineGroupStore.getState();
|
||||
const group = groupState.snapshot.group;
|
||||
@@ -25,14 +44,16 @@ export async function startAnotherBiteSizedDungeon(progress: DungeonRunProgress)
|
||||
return false;
|
||||
}
|
||||
|
||||
const next = randomDungeonByCategory(
|
||||
"bite-sized",
|
||||
progress.dungeonId,
|
||||
Math.random,
|
||||
(dungeon) => dungeonContentIsUsable(dungeon.id),
|
||||
);
|
||||
const randomized = isRandomizedBiteSizedDungeonId(progress.dungeonId);
|
||||
const next = randomized
|
||||
? randomRandomizedBiteSizedDungeon(progress.dungeonId, dependencies.random,
|
||||
(dungeon) => dependencies.contentIsUsable(dungeon.id))
|
||||
: randomDungeonByCategory("bite-sized", progress.dungeonId, dependencies.random,
|
||||
(dungeon) => dependencies.contentIsUsable(dungeon.id));
|
||||
if (!next) {
|
||||
shell.setNotice("No installed Bite-Sized dungeon is ready to enter.");
|
||||
shell.setNotice(randomized
|
||||
? "No Randomized Bite-Sized location is ready to enter."
|
||||
: "No installed Bite-Sized dungeon is ready to enter.");
|
||||
return false;
|
||||
}
|
||||
const definition = dungeonDefinitionById(next.id);
|
||||
@@ -65,11 +86,21 @@ export async function startAnotherBiteSizedDungeon(progress: DungeonRunProgress)
|
||||
shell.setNotice("Reconnect to the party before starting the next shared dungeon.");
|
||||
return false;
|
||||
}
|
||||
const runId = dependencies.createRunId();
|
||||
if (randomized) {
|
||||
try {
|
||||
await dependencies.prepareRandomizedContent(runId);
|
||||
} catch (caught) {
|
||||
shell.setNotice(caught instanceof Error ? caught.message : "The randomized creature download failed.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
shell.saveActiveDungeonRunCheckpoint(null);
|
||||
const entered = shell.enterDungeon(next.id, shell.activeDungeonRole, {
|
||||
difficultyId,
|
||||
bossDifficulty: progress.bossDifficulty,
|
||||
freshRun: true,
|
||||
runId,
|
||||
});
|
||||
return entered;
|
||||
}
|
||||
Reference in new issue
Block a user