Update Healer Man server

This commit is contained in:
phenom committed 2026-09-19 21:01:51 -04:00
1 parent f8314e7de1
commit 1fedb9368d
76 files changed
+20556 -2523

No files matched your search

+128
View File
@@ -8,6 +8,14 @@ import {
normalizeManastormAdminConfig,
stampManastormReviewMetadata,
} from "./manastorm-config.mjs";
import {
cloneDefaultCompanionCooldownPolicy,
normalizeCompanionCooldownPolicy,
} from "./companion-cooldown-policy.mjs";
import {
cloneDefaultCompanionSpecReadinessPolicy,
normalizeCompanionSpecReadinessPolicy,
} from "./companion-spec-readiness.mjs";
import { arenaRatingBand, calculateArenaRating, clampArenaRating } from "./arena-rules.mjs";
import { arenaRoleForSpecialization } from "./arena-specializations.mjs";
import { createDungeonReportRepository } from "./dungeon-reports.mjs";
@@ -93,6 +101,24 @@ export function openGameDatabase(options = {}) {
updated_by_username TEXT,
updated_at INTEGER
) STRICT;
CREATE TABLE IF NOT EXISTS companion_cooldown_policy (
id INTEGER PRIMARY KEY CHECK (id = 1),
schema_version INTEGER NOT NULL,
revision INTEGER NOT NULL,
config_json TEXT NOT NULL,
updated_by_account_id TEXT REFERENCES accounts(id) ON DELETE SET NULL,
updated_by_username TEXT,
updated_at INTEGER
) STRICT;
CREATE TABLE IF NOT EXISTS companion_spec_readiness (
id INTEGER PRIMARY KEY CHECK (id = 1),
schema_version INTEGER NOT NULL,
revision INTEGER NOT NULL,
config_json TEXT NOT NULL,
updated_by_account_id TEXT REFERENCES accounts(id) ON DELETE SET NULL,
updated_by_username TEXT,
updated_at INTEGER
) STRICT;
CREATE TABLE IF NOT EXISTS online_groups (
id TEXT PRIMARY KEY,
leader_account_id TEXT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
@@ -184,6 +210,16 @@ export function openGameDatabase(options = {}) {
id, schema_version, revision, config_json, updated_by_account_id, updated_by_username, updated_at
) VALUES (1, 1, 0, ?, NULL, NULL, NULL)
`).run(JSON.stringify(cloneDefaultManastormAdminConfig()));
database.prepare(`
INSERT OR IGNORE INTO companion_cooldown_policy (
id, schema_version, revision, config_json, updated_by_account_id, updated_by_username, updated_at
) VALUES (1, 1, 0, ?, NULL, NULL, NULL)
`).run(JSON.stringify(cloneDefaultCompanionCooldownPolicy()));
database.prepare(`
INSERT OR IGNORE INTO companion_spec_readiness (
id, schema_version, revision, config_json, updated_by_account_id, updated_by_username, updated_at
) VALUES (1, 1, 0, ?, NULL, NULL, NULL)
`).run(JSON.stringify(cloneDefaultCompanionSpecReadinessPolicy()));
const statements = {
accountByUsername: database.prepare("SELECT * FROM accounts WHERE username_key = ?"),
@@ -215,6 +251,22 @@ export function openGameDatabase(options = {}) {
WHERE id = 1 AND revision = ?
RETURNING revision, updated_at
`),
companionCooldownPolicy: database.prepare("SELECT * FROM companion_cooldown_policy WHERE id = 1"),
updateCompanionCooldownPolicy: database.prepare(`
UPDATE companion_cooldown_policy
SET schema_version = 1, revision = revision + 1, config_json = ?,
updated_by_account_id = ?, updated_by_username = ?, updated_at = ?
WHERE id = 1 AND revision = ?
RETURNING revision, updated_at
`),
companionSpecReadiness: database.prepare("SELECT * FROM companion_spec_readiness WHERE id = 1"),
updateCompanionSpecReadiness: database.prepare(`
UPDATE companion_spec_readiness
SET schema_version = 1, revision = revision + 1, config_json = ?,
updated_by_account_id = ?, updated_by_username = ?, updated_at = ?
WHERE id = 1 AND revision = ?
RETURNING revision, updated_at
`),
arenaLoadouts: database.prepare(`
SELECT * FROM arena_loadouts WHERE account_id = ? AND character_id = ? ORDER BY slot
`),
@@ -364,6 +416,74 @@ export function openGameDatabase(options = {}) {
};
}
function getCompanionCooldownPolicy() {
const row = statements.companionCooldownPolicy.get();
try {
return {
revision: Number(row?.revision ?? 0),
updatedAt: row?.updated_at == null ? null : Number(row.updated_at),
updatedBy: row?.updated_by_username ?? null,
config: normalizeCompanionCooldownPolicy(JSON.parse(row?.config_json ?? "null")),
};
} catch (error) {
console.error("Healer Man companion cooldown policy is corrupt; using safe defaults", error);
return { revision: 0, updatedAt: null, updatedBy: null, config: cloneDefaultCompanionCooldownPolicy() };
}
}
function putCompanionCooldownPolicy(account, expectedRevision, config) {
if (!isGameMaster(account)) throw new GameDatabaseError("Game Master access is required.", 403, "gm_required");
if (!Number.isInteger(expectedRevision) || expectedRevision < 0) {
throw new GameDatabaseError("A valid expected revision is required.", 400, "invalid_revision");
}
let normalized;
try {
normalized = normalizeCompanionCooldownPolicy(config);
} catch (error) {
throw new GameDatabaseError(error instanceof Error ? error.message : "Invalid companion cooldown policy.", 400, "invalid_companion_cooldown_policy");
}
const now = Date.now();
const row = statements.updateCompanionCooldownPolicy.get(
JSON.stringify(normalized), account.id, account.username, now, expectedRevision,
);
if (!row) throw new GameDatabaseError("The companion cooldown policy changed. Reload and try again.", 409, "revision_conflict");
return { revision: Number(row.revision), updatedAt: Number(row.updated_at), updatedBy: account.username, config: normalized };
}
function getCompanionSpecReadiness() {
const row = statements.companionSpecReadiness.get();
try {
return {
revision: Number(row?.revision ?? 0),
updatedAt: row?.updated_at == null ? null : Number(row.updated_at),
updatedBy: row?.updated_by_username ?? null,
config: normalizeCompanionSpecReadinessPolicy(JSON.parse(row?.config_json ?? "null")),
};
} catch (error) {
console.error("Healer Man companion readiness policy is corrupt; using shipped defaults", error);
return { revision: 0, updatedAt: null, updatedBy: null, config: cloneDefaultCompanionSpecReadinessPolicy() };
}
}
function putCompanionSpecReadiness(account, expectedRevision, config) {
if (!isGameMaster(account)) throw new GameDatabaseError("Game Master access is required.", 403, "gm_required");
if (!Number.isInteger(expectedRevision) || expectedRevision < 0) {
throw new GameDatabaseError("A valid expected revision is required.", 400, "invalid_revision");
}
let normalized;
try {
normalized = normalizeCompanionSpecReadinessPolicy(config);
} catch (error) {
throw new GameDatabaseError(error instanceof Error ? error.message : "Invalid companion readiness policy.", 400, "invalid_companion_spec_readiness");
}
const now = Date.now();
const row = statements.updateCompanionSpecReadiness.get(
JSON.stringify(normalized), account.id, account.username, now, expectedRevision,
);
if (!row) throw new GameDatabaseError("The companion readiness policy changed. Reload and try again.", 409, "revision_conflict");
return { revision: Number(row.revision), updatedAt: Number(row.updated_at), updatedBy: account.username, config: normalized };
}
function pruneExpiredGroupInvites() {
database.prepare(`
UPDATE online_group_invites SET status = 'expired'
@@ -620,6 +740,9 @@ export function openGameDatabase(options = {}) {
throw new GameDatabaseError("Every player must select a character and party role before the group can start.", 409, "group_not_ready");
}
const fillWithAi = input?.fillWithAi !== false;
if (input?.includeBetaSpecs !== undefined && typeof input.includeBetaSpecs !== "boolean") {
throw new GameDatabaseError("includeBetaSpecs must be boolean.", 400, "invalid_beta_preference");
}
const requestedSize = Math.max(1, Math.min(5, Math.trunc(Number(input?.partySize) || 5)));
const partySize = fillWithAi ? Math.max(members.length, requestedSize) : members.length;
const activity = {
@@ -628,6 +751,7 @@ export function openGameDatabase(options = {}) {
contentId,
...(type === "dungeon" ? { difficultyId: input?.difficultyId, bossDifficulty: input?.bossDifficulty ?? 1 } : {}),
fillWithAi,
includeBetaSpecs: input?.includeBetaSpecs === true,
partySize,
startingLevel: type === "manastorm"
? Math.max(1, Math.trunc(Number(input?.startingLevel) || 1))
@@ -921,6 +1045,10 @@ export function openGameDatabase(options = {}) {
isGameMaster,
getManastormConfig,
putManastormConfig,
getCompanionCooldownPolicy,
putCompanionCooldownPolicy,
getCompanionSpecReadiness,
putCompanionSpecReadiness,
onlineGroupState,
inviteOnlineGroupMember,
respondToOnlineGroupInvite,