Release Healer Man 0.1.32

This commit is contained in:
phenom committed 2026-09-12 11:07:25 -04:00
1 parent d3d6aaebdb
commit 6dcb1034e5
102 files changed
+10772 -788

No files matched your search

+2 -4
View File
@@ -26,7 +26,6 @@ import { PartyPopulation } from "./PartyPopulation";
import { GroundEffectTelegraphs } from "./GroundEffectTelegraphs";
import { PlayerRig } from "./PlayerRig";
import { useManastormStore } from "../game/manastormStore";
import { ManastormPortal } from "./ManastormPortal";
import {
encounterWorldIsReady,
encounterWorldKey,
@@ -44,7 +43,6 @@ import { WailingNaralexEvent } from "./WailingNaralexEvent";
import { GameGltfLoaderLifecycle } from "./useGameGLTF";
import { CombatEffects } from "./CombatEffects";
import { PLAYER_GRAVITY } from "../game/playerMovement";
import { GmPlacementMarkers } from "./GmPlacementMarkers";
import { useOnlineGroupStore } from "../app/onlineGroupStore";
import { useOnlineSessionStore } from "../app/onlineSessionStore";
import { OnlinePlayerPopulation } from "./OnlinePlayerPopulation";
@@ -59,6 +57,7 @@ import { DungeonWorldObjects } from "./DungeonWorldObjects";
import { ActivityAssetWarmup } from "./ActivityAssetWarmup";
import { resolveDungeonAssets, resolveManastormStageAssets } from "../game/dungeonAssets";
import { activeManastormStageAssetPackage } from "../game/manastormStageLoader";
import { ShelvedGameSceneFeatures } from "@shelvedGameScene";
import { shouldLoadPartyNavigation } from "../game/partyNavigationPolicy";
import {
activeDungeonCompletedObjectiveIds,
@@ -404,8 +403,7 @@ export function GameScene() {
allowedRuntimeIds={allowedRuntimeIds}
/>
<WailingNaralexEvent />
<ManastormPortal />
<GmPlacementMarkers />
<ShelvedGameSceneFeatures />
<CombatEffects active={!simulationBlocked} safeGraphics={safeGraphics} />
</>
)}
+13 -4
View File
@@ -50,6 +50,7 @@ import {
type OriginalCombatMeshGraph,
} from "./originalCombatMeshRuntime";
import { updateCombatEffectAnchorPosition } from "./combatEffectAnchorRegistry";
import { shouldUseGeneratedOriginalFallback } from "./combatEffectRuntime";
import { OriginalWowModel } from "./OriginalWowModel";
import { OriginalParticleGeometry } from "./OriginalParticleGeometry";
import {
@@ -99,9 +100,10 @@ const vertexShader = /* glsl */ `
vParticleAlpha = particleAlpha;
vParticleFrame = particleFrame;
vec4 viewPosition = modelViewMatrix * vec4(position, 1.0);
float inheritedScale = length(modelViewMatrix[0].xyz);
gl_Position = projectionMatrix * viewPosition;
gl_PointSize = clamp(
particleSize * viewportHeight / max(1.0, -viewPosition.z),
particleSize * inheritedScale * viewportHeight / max(1.0, -viewPosition.z),
1.0,
256.0
);
@@ -894,16 +896,23 @@ export const OriginalCombatEffect = memo(function OriginalCombatEffect({
const selection = loaded === null
? null
: selectCombatEffectRender("original", loaded.map((item) => item.asset));
const hasOriginalInstantKit = event.phase === "release" && event.instantCast === true
&& (["cast-start", "impact", "aura-start"] as const).some((phase) => (
combatOriginalPhaseAssets({ ...event, phase }).length > 0
));
const resolvedFallback = shouldUseGeneratedOriginalFallback(event, hasOriginalInstantKit)
? fallback
: null;
useEffect(() => {
if (
loaded === null
|| selection?.kind === "original"
|| fallback !== null
|| resolvedFallback !== null
|| completedMissingOriginalRef.current
) return;
completedMissingOriginalRef.current = true;
onDone();
}, [fallback, loaded, onDone, selection?.kind]);
}, [loaded, onDone, resolvedFallback, selection?.kind]);
if (loaded === null || selection === null) return null;
return selection.kind === "original" ? (
@@ -914,7 +923,7 @@ export const OriginalCombatEffect = memo(function OriginalCombatEffect({
loaded={loaded}
onDone={onDone}
/>
) : fallback;
) : resolvedFallback;
}, (previous, next) => (
previous.event === next.event
&& previous.alternateOriginalPhase === next.alternateOriginalPhase
+1 -1
View File
@@ -23,7 +23,7 @@ import { resolvedEquipmentItems } from "../game/heirloomEquipment";
import type { CharacterCombatAnimationEvent } from "../game/combatAnimation";
import { resolveDungeonAssets, resolveManastormStageAssets } from "../game/dungeonAssets";
import { activeManastormStageAssetPackage } from "../game/manastormStageLoader";
import { derivePartySpawnSlots } from "../game/manastormAdminConfig";
import { derivePartySpawnSlots } from "../game/partySpawnSlots";
import { requireDungeonDefinition } from "../game/dungeonRegistry";
import type { DungeonBossObjective } from "../game/dungeonTypes";
import { getMobPosition } from "../game/mobRuntimeRegistry";
+3
View File
@@ -0,0 +1,3 @@
export function ShelvedGameSceneFeatures() {
return null;
}
+11
View File
@@ -0,0 +1,11 @@
import { GmPlacementMarkers } from "./GmPlacementMarkers";
import { ManastormPortal } from "./ManastormPortal";
export function ShelvedGameSceneFeatures() {
return (
<>
<ManastormPortal />
<GmPlacementMarkers />
</>
);
}
+19
View File
@@ -7,6 +7,7 @@ import {
advanceCombatEffectFrame,
originalInstantCastEvent,
originalInstantTerminalEvent,
shouldUseGeneratedOriginalFallback,
updateCombatEffectQueue,
type ActiveCombatEffect,
} from "./combatEffectRuntime";
@@ -168,4 +169,22 @@ describe("combat-effect runtime budget", () => {
});
expect(originalInstantTerminalEvent(event(22, { phase: "impact" }))).toBeNull();
});
it("does not layer a generated release over an instant ability's original phases", () => {
expect(shouldUseGeneratedOriginalFallback(event(30, {
phase: "release",
instantCast: true,
}), true)).toBe(false);
expect(shouldUseGeneratedOriginalFallback(event(31, {
phase: "release",
instantCast: true,
}), false)).toBe(true);
expect(shouldUseGeneratedOriginalFallback(event(31, {
phase: "release",
}), true)).toBe(true);
expect(shouldUseGeneratedOriginalFallback(event(32, {
phase: "impact",
instantCast: true,
}), true)).toBe(true);
});
});
+9
View File
@@ -179,6 +179,15 @@ export function originalInstantTerminalEvent(
};
}
/** Instant resolutions immediately emit a terminal phase, so their missing release
* asset must not add a generated layer over the authentic cast and terminal kits. */
export function shouldUseGeneratedOriginalFallback(
event: CombatPresentationEvent,
hasOriginalInstantKit: boolean,
): boolean {
return event.phase !== "release" || !event.instantCast || !hasOriginalInstantKit;
}
export function addOriginalOnlyEffect(
current: readonly ActiveCombatEffect[],
event: CombatPresentationEvent,
+10
View File
@@ -5,6 +5,7 @@ import { Matrix4, Quaternion, Vector3 } from "three";
import { parseOriginalWowModelGraph, updateWowBoneMatrices, wowModelFrame, wowModelTextureUrls, wowParticleChild } from "./originalWowModelRuntime";
const manifest = JSON.parse(readFileSync(path.resolve("public/assets/game/combat-effects/manifest.json"), "utf8"));
const rendererSource = readFileSync(new URL("./OriginalCombatEffect.tsx", import.meta.url), "utf8");
const urls = [...new Set<string>(manifest.entries.filter((e: { source: string }) => e.source === "wow335")
.flatMap((e: { phases: Record<string, { effectGraphUrl?: string }[]> }) => Object.values(e.phases ?? {}).flat()
.map((a) => a.effectGraphUrl).filter(Boolean)))];
@@ -67,4 +68,13 @@ describe("all-class original WoW models", () => {
const dispel = parseOriginalWowModelGraph(graphs.find((g) => g.sourceModel === "spells/clense_base.m2"));
expect(wowParticleChild(dispel, "Spells\\Dispel_Low_Recursive.mdx")!.particleGraph!.particles).toHaveLength(1);
});
it("applies recursive parent scale to Purify and Cleanse point sprites", () => {
for (const sourceModel of ["spells/purify_base.m2", "spells/clense_base.m2"]) {
const graph = parseOriginalWowModelGraph(graphs.find((candidate) => candidate.sourceModel === sourceModel));
expect(graph.particleGraph!.particles.filter((particle) => particle.recursionModel)).toHaveLength(3);
}
expect(rendererSource).toContain("float inheritedScale = length(modelViewMatrix[0].xyz);");
expect(rendererSource).toContain("particleSize * inheritedScale * viewportHeight");
});
});