Release Healer Man 0.1.12
This commit is contained in:
1 parent
13970d03c8
commit
65e24f644e
201 files changed
+5061
-123
No files matched your search
@@ -0,0 +1,179 @@
|
||||
import { DUNGEON_CATALOG, dungeonById } from "./dungeonCatalog";
|
||||
import {
|
||||
bossAdminRecordForStatus,
|
||||
GM_REVIEW_NOTE_MAX_LENGTH,
|
||||
resolvedBossAdminCatalog,
|
||||
type GmReadinessStatus,
|
||||
type ManastormAdminConfig,
|
||||
type ResolvedBossAdminEntry,
|
||||
} from "./manastormAdminConfig";
|
||||
|
||||
export type GmReviewKind = "dungeon" | "boss" | "manastorm";
|
||||
|
||||
export type GmReviewTarget =
|
||||
| { readonly kind: "dungeon"; readonly dungeonId: string }
|
||||
| { readonly kind: "boss" | "manastorm"; readonly bossKey: string };
|
||||
|
||||
export interface GmReviewDescriptor {
|
||||
readonly target: GmReviewTarget;
|
||||
readonly name: string;
|
||||
readonly dungeonTitle: string;
|
||||
readonly mapId: number;
|
||||
readonly status: GmReadinessStatus;
|
||||
readonly note: string;
|
||||
readonly reviewedAt: number | null;
|
||||
readonly reviewedBy: string | null;
|
||||
readonly runnable: boolean;
|
||||
}
|
||||
|
||||
function sameTarget(left: GmReviewTarget, right: GmReviewTarget): boolean {
|
||||
if (left.kind !== right.kind) return false;
|
||||
if (left.kind === "dungeon" && right.kind === "dungeon") return left.dungeonId === right.dungeonId;
|
||||
return left.kind !== "dungeon" && right.kind !== "dungeon" && left.bossKey === right.bossKey;
|
||||
}
|
||||
|
||||
function bossTargets(
|
||||
kind: "boss" | "manastorm",
|
||||
config: ManastormAdminConfig,
|
||||
): readonly GmReviewTarget[] {
|
||||
const source = kind === "boss" ? "dungeon" : "installed-manastorm";
|
||||
return resolvedBossAdminCatalog(config)
|
||||
.filter((entry) => entry.runnable && entry.source.includes(source))
|
||||
.map((entry) => ({ kind, bossKey: entry.key }));
|
||||
}
|
||||
|
||||
function targetStatus(target: GmReviewTarget, config: ManastormAdminConfig): GmReadinessStatus {
|
||||
if (target.kind === "dungeon") return config.dungeons[target.dungeonId]?.status ?? "needs-tested";
|
||||
return config.bosses[target.bossKey]?.status ?? "needs-tested";
|
||||
}
|
||||
|
||||
export function gmReviewQueue(
|
||||
kind: GmReviewKind,
|
||||
config: ManastormAdminConfig,
|
||||
previous: GmReviewTarget | null = null,
|
||||
): readonly GmReviewTarget[] {
|
||||
const targets: readonly GmReviewTarget[] = kind === "dungeon"
|
||||
? DUNGEON_CATALOG.filter((dungeon) => dungeon.available).map((dungeon) => ({
|
||||
kind: "dungeon",
|
||||
dungeonId: dungeon.id,
|
||||
}))
|
||||
: bossTargets(kind, config);
|
||||
const needsTested = targets.filter((target) => targetStatus(target, config) === "needs-tested");
|
||||
const prioritized = needsTested.length
|
||||
? needsTested
|
||||
: targets.filter((target) => targetStatus(target, config) === "not-ready");
|
||||
if (!previous || prioritized.length < 2) return prioritized;
|
||||
return prioritized.filter((target) => !sameTarget(target, previous));
|
||||
}
|
||||
|
||||
export function drawRandomGmReviewTarget(
|
||||
kind: GmReviewKind,
|
||||
config: ManastormAdminConfig,
|
||||
previous: GmReviewTarget | null = null,
|
||||
random: () => number = Math.random,
|
||||
): GmReviewTarget | null {
|
||||
const queue = gmReviewQueue(kind, config, previous);
|
||||
if (!queue.length) return null;
|
||||
const draw = random();
|
||||
const normalized = Number.isFinite(draw) ? Math.max(0, Math.min(0.999999999, draw)) : 0;
|
||||
return queue[Math.floor(normalized * queue.length)] ?? queue[0] ?? null;
|
||||
}
|
||||
|
||||
function resolvedBoss(target: GmReviewTarget, config: ManastormAdminConfig): ResolvedBossAdminEntry | null {
|
||||
if (target.kind === "dungeon") return null;
|
||||
return resolvedBossAdminCatalog(config).find((entry) => entry.key === target.bossKey) ?? null;
|
||||
}
|
||||
|
||||
export function describeGmReviewTarget(
|
||||
target: GmReviewTarget,
|
||||
config: ManastormAdminConfig,
|
||||
): GmReviewDescriptor | null {
|
||||
if (target.kind === "dungeon") {
|
||||
const dungeon = dungeonById(target.dungeonId);
|
||||
if (!dungeon) return null;
|
||||
const record = config.dungeons[target.dungeonId];
|
||||
return {
|
||||
target,
|
||||
name: dungeon.title,
|
||||
dungeonTitle: dungeon.location,
|
||||
mapId: dungeon.mapId,
|
||||
status: record?.status ?? "needs-tested",
|
||||
note: record?.note ?? "",
|
||||
reviewedAt: record?.reviewedAt ?? null,
|
||||
reviewedBy: record?.reviewedBy ?? null,
|
||||
runnable: dungeon.available,
|
||||
};
|
||||
}
|
||||
const entry = resolvedBoss(target, config);
|
||||
if (!entry) return null;
|
||||
return {
|
||||
target,
|
||||
name: entry.name,
|
||||
dungeonTitle: entry.dungeonTitle,
|
||||
mapId: entry.mapId,
|
||||
status: entry.status,
|
||||
note: entry.reviewNote,
|
||||
reviewedAt: entry.reviewedAt,
|
||||
reviewedBy: entry.reviewedBy,
|
||||
runnable: entry.runnable,
|
||||
};
|
||||
}
|
||||
|
||||
export function configWithGmReviewStatus(
|
||||
config: ManastormAdminConfig,
|
||||
target: GmReviewTarget,
|
||||
status: GmReadinessStatus,
|
||||
): ManastormAdminConfig | null {
|
||||
if (target.kind === "dungeon") {
|
||||
if (!dungeonById(target.dungeonId)) return null;
|
||||
return {
|
||||
...config,
|
||||
dungeons: {
|
||||
...config.dungeons,
|
||||
[target.dungeonId]: { ...config.dungeons[target.dungeonId], status },
|
||||
},
|
||||
};
|
||||
}
|
||||
const entry = resolvedBoss(target, config);
|
||||
if (!entry || (status === "ready" && !entry.runnable)) return null;
|
||||
return {
|
||||
...config,
|
||||
bosses: {
|
||||
...config.bosses,
|
||||
[target.bossKey]: bossAdminRecordForStatus(entry, config, status),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function configWithGmReviewNote(
|
||||
config: ManastormAdminConfig,
|
||||
target: GmReviewTarget,
|
||||
note: string,
|
||||
): ManastormAdminConfig | null {
|
||||
const normalized = note.replace(/\r\n?/g, "\n").slice(0, GM_REVIEW_NOTE_MAX_LENGTH);
|
||||
const noteFields = normalized.trim() ? { note: normalized } : {};
|
||||
if (target.kind === "dungeon") {
|
||||
if (!dungeonById(target.dungeonId)) return null;
|
||||
const record = { ...(config.dungeons[target.dungeonId] ?? { status: "needs-tested" as const }) };
|
||||
delete record.note;
|
||||
return {
|
||||
...config,
|
||||
dungeons: {
|
||||
...config.dungeons,
|
||||
[target.dungeonId]: { ...record, ...noteFields },
|
||||
},
|
||||
};
|
||||
}
|
||||
const entry = resolvedBoss(target, config);
|
||||
if (!entry) return null;
|
||||
const current = bossAdminRecordForStatus(entry, config, entry.status);
|
||||
const record = { ...current };
|
||||
delete record.note;
|
||||
return {
|
||||
...config,
|
||||
bosses: {
|
||||
...config.bosses,
|
||||
[target.bossKey]: { ...record, ...noteFields },
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user