Release Healer Man 0.1.8
This commit is contained in:
@@ -175,6 +175,10 @@ function interpolate(left, right, alpha) {
|
||||
return left.map((value, axis) => rounded(value + (right[axis] - value) * alpha));
|
||||
}
|
||||
|
||||
function horizontalDistanceSquared(left, right) {
|
||||
return (left[0] - right[0]) ** 2 + (left[2] - right[2]) ** 2;
|
||||
}
|
||||
|
||||
function normalizedName(value) {
|
||||
return String(value ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "");
|
||||
}
|
||||
@@ -201,7 +205,7 @@ const ADT_SOURCE_BASES = new Set([
|
||||
"z-y-negative-x",
|
||||
]);
|
||||
const GLOBAL_WMO_SOURCE_BASES = new Set(["draft", "flip-x"]);
|
||||
const GLOBAL_WMO_ALIGNMENTS = new Set(["player-anchor", "source"]);
|
||||
const COORDINATE_ALIGNMENTS = new Set(["player-anchor", "source", "source-transform"]);
|
||||
|
||||
function sourceBasisFor(recipe, environmentMode) {
|
||||
if (environmentMode !== "adt-hybrid") {
|
||||
@@ -222,14 +226,42 @@ function sourceBasisFor(recipe, environmentMode) {
|
||||
}
|
||||
|
||||
function coordinateAlignmentFor(recipe, environmentMode) {
|
||||
if (environmentMode !== "global-wmo") return "player-anchor";
|
||||
const alignment = recipe.globalWmoAlignment ?? "player-anchor";
|
||||
if (!GLOBAL_WMO_ALIGNMENTS.has(alignment)) {
|
||||
throw new Error(`${recipe.slug}: unsupported global WMO alignment ${alignment}.`);
|
||||
const legacyGlobalWmoAlignment = environmentMode === "global-wmo"
|
||||
? recipe.globalWmoAlignment
|
||||
: undefined;
|
||||
if (
|
||||
recipe.coordinateAlignment
|
||||
&& legacyGlobalWmoAlignment
|
||||
&& recipe.coordinateAlignment !== legacyGlobalWmoAlignment
|
||||
) {
|
||||
throw new Error(`${recipe.slug}: coordinate alignment overrides disagree.`);
|
||||
}
|
||||
const alignment = recipe.coordinateAlignment ?? legacyGlobalWmoAlignment ?? "player-anchor";
|
||||
if (!COORDINATE_ALIGNMENTS.has(alignment)) {
|
||||
throw new Error(`${recipe.slug}: unsupported coordinate alignment ${alignment}.`);
|
||||
}
|
||||
return alignment;
|
||||
}
|
||||
|
||||
function sourceTransformFor(recipe, alignment) {
|
||||
const transform = recipe.coordinateTransform;
|
||||
if (alignment !== "source-transform") {
|
||||
if (transform) {
|
||||
throw new Error(`${recipe.slug}: coordinateTransform requires source-transform alignment.`);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
if (!transform || !Array.isArray(transform.origin) || transform.origin.length !== 3) {
|
||||
throw new Error(`${recipe.slug}: source-transform alignment requires a three-value coordinateTransform.origin.`);
|
||||
}
|
||||
const origin = transform.origin.map(Number);
|
||||
const yawDegrees = Number(transform.yawDegrees ?? 0);
|
||||
if (!origin.every(Number.isFinite) || !Number.isFinite(yawDegrees)) {
|
||||
throw new Error(`${recipe.slug}: coordinateTransform values must be finite numbers.`);
|
||||
}
|
||||
return { origin, yawDegrees };
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime drafts retain the original pipeline convention. wow.export ADT
|
||||
* packages have an additional horizontal basis change baked into their GLBs.
|
||||
@@ -248,10 +280,39 @@ function sourceToPackageBasis(position, environmentMode, sourceBasis) {
|
||||
: point;
|
||||
}
|
||||
|
||||
function coordinateMapper(draft, environmentMode, playerAnchor, sourceBasis, alignment) {
|
||||
function rotateAroundY(position, yawDegrees) {
|
||||
const radians = yawDegrees * Math.PI / 180;
|
||||
const cosine = Math.cos(radians);
|
||||
const sine = Math.sin(radians);
|
||||
return vector([
|
||||
position[0] * cosine - position[2] * sine,
|
||||
position[1],
|
||||
position[0] * sine + position[2] * cosine,
|
||||
]);
|
||||
}
|
||||
|
||||
function coordinateMapper(
|
||||
draft,
|
||||
environmentMode,
|
||||
playerAnchor,
|
||||
sourceBasis,
|
||||
alignment,
|
||||
sourceTransform,
|
||||
) {
|
||||
if (alignment === "source") {
|
||||
return (position) => sourceToPackageBasis(position, environmentMode, sourceBasis);
|
||||
}
|
||||
if (alignment === "source-transform") {
|
||||
const basisOrigin = sourceToPackageBasis(
|
||||
sourceTransform.origin,
|
||||
environmentMode,
|
||||
sourceBasis,
|
||||
);
|
||||
return (position) => rotateAroundY(
|
||||
subtract(sourceToPackageBasis(position, environmentMode, sourceBasis), basisOrigin),
|
||||
sourceTransform.yawDegrees,
|
||||
);
|
||||
}
|
||||
const sourceEntrance = draft?.entrance
|
||||
?? draft?.spawns?.[0]?.position
|
||||
?? playerAnchor.position;
|
||||
@@ -778,6 +839,7 @@ async function compile() {
|
||||
const environmentMode = inferEnvironmentMode(recipe, assetPackage);
|
||||
const sourceBasis = sourceBasisFor(recipe, environmentMode);
|
||||
const coordinateAlignment = coordinateAlignmentFor(recipe, environmentMode);
|
||||
const sourceTransform = sourceTransformFor(recipe, coordinateAlignment);
|
||||
const playerAnchor = assetPackage.anchors.find((anchor) => anchor.kind === "player");
|
||||
const trashAnchor = assetPackage.anchors.find((anchor) => anchor.kind === "trash");
|
||||
const bossAnchor = assetPackage.anchors.find((anchor) => anchor.kind === "boss");
|
||||
@@ -788,7 +850,7 @@ async function compile() {
|
||||
trashAnchor.position[0] - playerAnchor.position[0],
|
||||
trashAnchor.position[2] - playerAnchor.position[2],
|
||||
);
|
||||
const entranceYaw = Number.isFinite(playerAnchor.yaw)
|
||||
let entranceYaw = Number.isFinite(playerAnchor.yaw)
|
||||
? playerAnchor.yaw
|
||||
: inferredEntranceYaw;
|
||||
|
||||
@@ -809,6 +871,7 @@ async function compile() {
|
||||
playerAnchor,
|
||||
sourceBasis,
|
||||
coordinateAlignment,
|
||||
sourceTransform,
|
||||
);
|
||||
const entrancePosition = mapPosition(
|
||||
draft?.entrance
|
||||
@@ -896,16 +959,49 @@ async function compile() {
|
||||
const entry = Number(spawn.entityId.replace("entry-", ""));
|
||||
if (!bossCandidatesByEntry.has(entry)) bossCandidatesByEntry.set(entry, spawn);
|
||||
}
|
||||
const mappedNativeRoutePositions = coordinateAlignment !== "player-anchor"
|
||||
? (draft?.spawns ?? [])
|
||||
.map((spawn) => mapPosition(spawn.position))
|
||||
.filter((position) => position.every(Number.isFinite))
|
||||
.sort((left, right) => (
|
||||
horizontalDistanceSquared(left, entrancePosition)
|
||||
- horizontalDistanceSquared(right, entrancePosition)
|
||||
))
|
||||
: [];
|
||||
const entranceDirectionTarget = mappedNativeRoutePositions.find((position) => (
|
||||
Math.abs(position[1] - entrancePosition[1]) <= 3
|
||||
&& horizontalDistanceSquared(position, entrancePosition) >= 16
|
||||
));
|
||||
if (entranceDirectionTarget) {
|
||||
entranceYaw = Math.atan2(
|
||||
entranceDirectionTarget[0] - entrancePosition[0],
|
||||
entranceDirectionTarget[2] - entrancePosition[2],
|
||||
);
|
||||
}
|
||||
const entranceLevelRoutePositions = mappedNativeRoutePositions.filter(
|
||||
(position) => Math.abs(position[1] - entrancePosition[1]) <= 2.5,
|
||||
);
|
||||
const nativeRoutePositions = (
|
||||
bossCandidatesByEntry.size === 0
|
||||
&& entranceLevelRoutePositions.length >= encounters.length
|
||||
)
|
||||
? entranceLevelRoutePositions
|
||||
: mappedNativeRoutePositions;
|
||||
const usedBossSpawnIds = new Set();
|
||||
const bossObjectives = encounters.map((encounter, index) => {
|
||||
const candidate = bossCandidatesByEntry.get(Number(encounter.creatureId));
|
||||
const alpha = encounters.length <= 1 ? 1 : index / (encounters.length - 1);
|
||||
const routePosition = interpolate(trashAnchor.position, bossAnchor.position, 0.18 + alpha * 0.82);
|
||||
const routeAlpha = 0.18 + alpha * 0.82;
|
||||
const nativeRoutePosition = nativeRoutePositions.length
|
||||
? nativeRoutePositions[Math.round(routeAlpha * (nativeRoutePositions.length - 1))]
|
||||
: undefined;
|
||||
const routePosition = nativeRoutePosition
|
||||
?? interpolate(trashAnchor.position, bossAnchor.position, routeAlpha);
|
||||
const position = encounter.eventOnly
|
||||
? [-151.27, -102.82, 252.26]
|
||||
: candidate
|
||||
? mapPosition(candidate.position)
|
||||
: offsetPosition(routePosition, index % 5, 2.4);
|
||||
: nativeRoutePosition ?? offsetPosition(routePosition, index % 5, 2.4);
|
||||
const id = encounter.eventOnly
|
||||
? "mutanus-spawn"
|
||||
: candidate?.id ?? `encounter-${encounter.encounterId}`;
|
||||
@@ -1000,8 +1096,9 @@ async function compile() {
|
||||
|
||||
const allPoints = [
|
||||
entrancePosition,
|
||||
trashAnchor.position,
|
||||
bossAnchor.position,
|
||||
...(coordinateAlignment === "player-anchor" || syntheticTrash
|
||||
? [trashAnchor.position, bossAnchor.position]
|
||||
: []),
|
||||
...staticSpawns.map((spawn) => spawn.position),
|
||||
...roamingPacks.flatMap((pack) => pack.waypoints),
|
||||
];
|
||||
@@ -1032,6 +1129,8 @@ async function compile() {
|
||||
? ["Trash identities and placements are procedural; boss identities come from the installed Ascension encounter catalog."]
|
||||
: coordinateAlignment === "source"
|
||||
? [`Server positions use the packaged ${environmentMode} environment's native world coordinates.`]
|
||||
: coordinateAlignment === "source-transform"
|
||||
? [`Server positions use the packaged ${environmentMode} environment's authoritative WDT placement transform.`]
|
||||
: [`Server positions were aligned to the packaged ${environmentMode} environment at its reviewed player anchor.`]),
|
||||
];
|
||||
|
||||
@@ -1078,6 +1177,7 @@ async function compile() {
|
||||
serverGameObjects: draft?.gameObjects ?? [],
|
||||
provenance: {
|
||||
clientBuild: recipe.clientBuild,
|
||||
coordinateAlignment,
|
||||
clientArchiveHashes: assetPackage.source.checksums ?? {},
|
||||
databaseAdapter: databaseKind,
|
||||
databaseRevision: draft?.spawns?.length
|
||||
@@ -1090,6 +1190,8 @@ async function compile() {
|
||||
? sourceBasis === "z-y-negative-x"
|
||||
? "three(x,y,z)=(wow.y,wow.z,wow.x)"
|
||||
: "three(x,y,z)=(-wow.y,wow.z,wow.x)"
|
||||
: coordinateAlignment === "source-transform"
|
||||
? `three(x,y,z)=rotate-y(${sourceBasis === "flip-x" ? "(-draft.x,draft.y,draft.z)" : "draft(x,y,z)"}-[${sourceTransform.origin.join(",")}],${sourceTransform.yawDegrees}deg)`
|
||||
: coordinateAlignment === "source"
|
||||
? sourceBasis === "flip-x"
|
||||
? "three(x,y,z)=(-draft.x,draft.y,draft.z)"
|
||||
|
||||
Reference in New Issue
Block a user