forked from phenom/healer-man
Update Healer Man server
This commit is contained in:
1 parent
cc3b2fa4d5
commit
daa417ec17
46 files changed
+1334
-172
No files matched your search
@@ -0,0 +1,189 @@
|
||||
import type { DungeonDefinition, DungeonBossObjective } from "../dungeonTypes";
|
||||
import type { PopulationEntityDefinition, StaticMobSpawnDefinition } from "../mobPopulation";
|
||||
import type { DungeonWorldObjectDefinition } from "../dungeonWorldObjects";
|
||||
import {
|
||||
BITE_SIZED_STOCKADES_DUNGEON_ID,
|
||||
STORMWIND_STOCKADE_DUNGEON_ID,
|
||||
} from "../dungeonContentSource";
|
||||
|
||||
const BOSS_ROOM_POSITION = [-159.582, -25.6062, 1.2531] as const;
|
||||
const CENTRAL_CORRIDOR_HALF_WIDTH = 15;
|
||||
|
||||
export interface BiteSizedStockadesBossCandidate {
|
||||
readonly sourceSpawnId: string;
|
||||
readonly name: string;
|
||||
}
|
||||
|
||||
export const BITE_SIZED_STOCKADES_BOSS_CANDIDATES: readonly BiteSizedStockadesBossCandidate[] = Object.freeze([
|
||||
{ sourceSpawnId: "creature-84027", name: "Targorr the Dread" },
|
||||
{ sourceSpawnId: "creature-79088", name: "Kam Deepfury" },
|
||||
{ sourceSpawnId: "creature-79098", name: "Hamhock" },
|
||||
{ sourceSpawnId: "creature-79124", name: "Bazil Thredd" },
|
||||
{ sourceSpawnId: "creature-79116", name: "Dextren Ward" },
|
||||
]);
|
||||
|
||||
function seedHash(seed: string): number {
|
||||
let hash = 0x811c9dc5;
|
||||
for (let index = 0; index < seed.length; index += 1) {
|
||||
hash ^= seed.charCodeAt(index);
|
||||
hash = Math.imul(hash, 0x01000193);
|
||||
}
|
||||
return hash >>> 0;
|
||||
}
|
||||
|
||||
export function biteSizedStockadesBossIndex(seed: string): number {
|
||||
return seedHash(seed) % BITE_SIZED_STOCKADES_BOSS_CANDIDATES.length;
|
||||
}
|
||||
|
||||
function branchWall(id: string, name: string, z: number): DungeonWorldObjectDefinition {
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
kind: "gate",
|
||||
position: [-129.17, -22.1, z],
|
||||
halfExtents: [6, 5.2, 0.65],
|
||||
initialState: "sealed",
|
||||
states: [{
|
||||
id: "sealed",
|
||||
label: "Sealed",
|
||||
visible: true,
|
||||
blocksMovement: true,
|
||||
blocksSight: true,
|
||||
interactionEnabled: false,
|
||||
}],
|
||||
visual: { color: "#332f2b" },
|
||||
source: {
|
||||
fidelity: "healer-man-authored",
|
||||
files: ["src/game/dungeons/biteSizedStockades.ts"],
|
||||
note: "Permanent wall for the bite-sized route; the source Stockades geometry remains unchanged.",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function sourceBoss(
|
||||
source: DungeonDefinition,
|
||||
candidate: BiteSizedStockadesBossCandidate,
|
||||
): { readonly spawn: StaticMobSpawnDefinition; readonly entity: PopulationEntityDefinition } {
|
||||
const spawn = source.staticSpawns.find((entry) => entry.id === candidate.sourceSpawnId);
|
||||
const entity = spawn ? source.entities[spawn.entityId] : undefined;
|
||||
if (!spawn || !entity || entity.kind !== "boss") {
|
||||
throw new Error(`Stockades boss source is missing: ${candidate.sourceSpawnId}`);
|
||||
}
|
||||
return { spawn, entity };
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a one-boss Stockades variant without mutating the generated source definition.
|
||||
* The seed controls only boss identity, so saves and online party members can reconstruct
|
||||
* the same run while a fresh run receives a new random selection.
|
||||
*/
|
||||
export function createBiteSizedStockadesDefinition(
|
||||
source: DungeonDefinition,
|
||||
seed: string,
|
||||
): DungeonDefinition {
|
||||
if (source.id !== STORMWIND_STOCKADE_DUNGEON_ID) {
|
||||
throw new Error(`Bite-sized Stockades requires ${STORMWIND_STOCKADE_DUNGEON_ID}.`);
|
||||
}
|
||||
const candidate = BITE_SIZED_STOCKADES_BOSS_CANDIDATES[biteSizedStockadesBossIndex(seed)];
|
||||
const selected = sourceBoss(source, candidate);
|
||||
const sourceBossIds = new Set(BITE_SIZED_STOCKADES_BOSS_CANDIDATES.map((entry) => entry.sourceSpawnId));
|
||||
const corridorSpawns = source.staticSpawns.filter((spawn) => (
|
||||
!sourceBossIds.has(spawn.id)
|
||||
&& Math.abs(spawn.position[2]) <= CENTRAL_CORRIDOR_HALF_WIDTH
|
||||
));
|
||||
const usedEntityIds = new Set(corridorSpawns.map((spawn) => spawn.entityId));
|
||||
const entities = Object.fromEntries([
|
||||
...Object.entries(source.entities).filter(([entityId]) => usedEntityIds.has(entityId)),
|
||||
[selected.spawn.entityId, selected.entity],
|
||||
]);
|
||||
const boss: DungeonBossObjective = {
|
||||
id: candidate.sourceSpawnId,
|
||||
name: candidate.name,
|
||||
position: BOSS_ROOM_POSITION,
|
||||
};
|
||||
|
||||
return {
|
||||
...source,
|
||||
id: BITE_SIZED_STOCKADES_DUNGEON_ID,
|
||||
title: "Stormwind Stockade: Cellblock Rush",
|
||||
lfgDungeonIds: [],
|
||||
presentation: {
|
||||
...source.presentation,
|
||||
location: "Stormwind Stockade · Central Cellblock",
|
||||
theme: "Bite-Sized Dungeon",
|
||||
summary: "Race through the central cellblock and defeat one surprise warden chosen from the five Stockades bosses.",
|
||||
loadingMessage: "Sealing the side wings and choosing a cellblock warden...",
|
||||
unchartedAreaName: "Central Cellblock",
|
||||
},
|
||||
areas: [
|
||||
{ ...source.entrance, id: "entrance", center: source.entrance.footPosition, radius: 35 },
|
||||
{ id: "bite-sized-boss-room", name: `${candidate.name}'s Cell`, center: BOSS_ROOM_POSITION, radius: 32 },
|
||||
],
|
||||
entities,
|
||||
staticSpawns: [
|
||||
...corridorSpawns,
|
||||
{
|
||||
id: candidate.sourceSpawnId,
|
||||
entityId: selected.spawn.entityId,
|
||||
position: BOSS_ROOM_POSITION,
|
||||
yaw: selected.spawn.yaw,
|
||||
spawnMask: selected.spawn.spawnMask,
|
||||
},
|
||||
],
|
||||
roamingPacks: [],
|
||||
bosses: [boss],
|
||||
objectiveOrder: [boss],
|
||||
navigationLinks: source.navigationLinks.filter((link) => link.id === "entrance-to-central-stairs"),
|
||||
traversalRamps: source.traversalRamps?.filter((ramp) => ramp.id === "entrance-stair-ramp"),
|
||||
encounters: [{
|
||||
id: `${BITE_SIZED_STOCKADES_DUNGEON_ID}:boss-progression`,
|
||||
dungeonId: BITE_SIZED_STOCKADES_DUNGEON_ID,
|
||||
initialPhase: "progression",
|
||||
initialState: { flags: { "dungeon-complete": false } },
|
||||
resetPolicy: { bossWipePhase: "progression", checkpointPhase: "progression" },
|
||||
source: {
|
||||
fidelity: "healer-man-authored",
|
||||
scope: "boss-progression",
|
||||
files: ["src/game/dungeons/biteSizedStockades.ts"],
|
||||
reviewedBehavior: "A single seeded Stockades boss completes the bite-sized run in the first boss room.",
|
||||
adaptations: [
|
||||
"Each seeded run retains the selected source boss objective ID so saves, loot, guides, and leaderboards report the correct variant.",
|
||||
"The two side-wing stairs are permanently sealed and omitted from party navigation.",
|
||||
],
|
||||
remainingGaps: [],
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
id: "boss-defeated",
|
||||
trigger: { kind: "entities-dead", entityIds: [candidate.sourceSpawnId] },
|
||||
actions: [
|
||||
{ id: "record-boss", kind: "complete-objective", objectiveId: candidate.sourceSpawnId, persistence: "run" },
|
||||
{ id: "checkpoint-boss", kind: "set-checkpoint", checkpointId: candidate.sourceSpawnId, persistence: "checkpoint" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "complete-run",
|
||||
trigger: { kind: "tick" },
|
||||
guards: [{ kind: "objective-complete", objectiveId: candidate.sourceSpawnId, equals: true }],
|
||||
actions: [
|
||||
{ id: "mark-dungeon-complete", kind: "set-flag", flagId: "dungeon-complete", value: true, persistence: "run" },
|
||||
{ id: "enter-complete-phase", kind: "set-phase", phase: "complete", persistence: "run" },
|
||||
],
|
||||
},
|
||||
],
|
||||
}],
|
||||
encounterSpawnGroups: [],
|
||||
serverGameObjects: [
|
||||
...source.serverGameObjects.filter((object) => Math.abs(object.position[2]) <= CENTRAL_CORRIDOR_HALF_WIDTH),
|
||||
branchWall("bite-sized-stockades-north-wall", "Sealed North Wing", 16.4),
|
||||
branchWall("bite-sized-stockades-south-wall", "Sealed South Wing", -16.4),
|
||||
],
|
||||
validation: {
|
||||
...source.validation,
|
||||
warnings: [
|
||||
...source.validation.warnings,
|
||||
"Bite-sized layout and branch walls are Healer Man-authored; environment and combat assets are shared with Stormwind Stockade.",
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user