forked from phenom/healer-man
Release Healer Man 0.1.14
This commit is contained in:
1 parent
67d7a8ebe7
commit
7b41b102fa
147 files changed
+169892
-1342
No files matched your search
@@ -1,5 +1,5 @@
|
||||
import { memo, useEffect, useMemo, useRef, useState, type MutableRefObject } from "react";
|
||||
import { useFrame } from "@react-three/fiber";
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import {
|
||||
AdditiveBlending,
|
||||
BufferAttribute,
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
Texture,
|
||||
TextureLoader,
|
||||
Vector2,
|
||||
Vector3,
|
||||
} from "three";
|
||||
import {
|
||||
combatOriginalPhaseAssets,
|
||||
@@ -23,6 +24,7 @@ import {
|
||||
type CombatPresentationEvent,
|
||||
type CombatVfxQuality,
|
||||
} from "../game/combatPresentation";
|
||||
import { fetchCombatMediaAsset } from "../game/combatMediaPack";
|
||||
import { abilityById } from "../game/abilityCatalog";
|
||||
import { PLAYER_AURA_ENTITY_ID, useCombatStore } from "../game/combatStore";
|
||||
import { getMobPosition } from "../game/mobRuntimeRegistry";
|
||||
@@ -44,6 +46,7 @@ import {
|
||||
parseOriginalCombatMeshGraph,
|
||||
type OriginalCombatMeshGraph,
|
||||
} from "./originalCombatMeshRuntime";
|
||||
import { updateCombatEffectAnchorPosition } from "./combatEffectAnchorRegistry";
|
||||
|
||||
type OriginalSourceEffectGraph = OriginalCombatEffectGraph | OriginalCombatMeshGraph;
|
||||
|
||||
@@ -201,7 +204,7 @@ async function loadGraph(url: string): Promise<OriginalSourceEffectGraph> {
|
||||
// Graph filenames are keyed to the immutable source M2, while the decoded
|
||||
// browser schema can improve between releases. Revalidate the small JSON so
|
||||
// an older cached decoder payload cannot fail the current strict parser.
|
||||
const request = fetch(url, { cache: "no-cache" })
|
||||
const request = fetchCombatMediaAsset(url)
|
||||
.then((response) => response.ok
|
||||
? response.json()
|
||||
: Promise.reject(new Error(`Original effect graph request failed (${response.status}): ${url}`)))
|
||||
@@ -218,11 +221,18 @@ async function loadGraph(url: string): Promise<OriginalSourceEffectGraph> {
|
||||
async function loadTexture(url: string): Promise<Texture> {
|
||||
const existing = textureRequestCache.get(url);
|
||||
if (existing) return existing;
|
||||
const request = textureLoader.loadAsync(url).then((texture) => {
|
||||
texture.colorSpace = SRGBColorSpace;
|
||||
texture.name = `original-combat-effect:${url}`;
|
||||
texture.needsUpdate = true;
|
||||
return texture;
|
||||
const request = fetchCombatMediaAsset(url).then(async (response) => {
|
||||
if (!response.ok) throw new Error(`Original effect texture request failed (${response.status}): ${url}`);
|
||||
const objectUrl = URL.createObjectURL(await response.blob());
|
||||
try {
|
||||
const texture = await textureLoader.loadAsync(objectUrl);
|
||||
texture.colorSpace = SRGBColorSpace;
|
||||
texture.name = `original-combat-effect:${url}`;
|
||||
texture.needsUpdate = true;
|
||||
return texture;
|
||||
} finally {
|
||||
URL.revokeObjectURL(objectUrl);
|
||||
}
|
||||
});
|
||||
textureRequestCache.set(url, request);
|
||||
request.catch(() => textureRequestCache.delete(url));
|
||||
@@ -446,7 +456,8 @@ const OriginalStaticMesh = memo(function OriginalStaticMesh({
|
||||
readonly active: boolean;
|
||||
readonly playbackRef: MutableRefObject<{ elapsedMs: number; decaying: boolean }>;
|
||||
}) {
|
||||
const billboardRef = useRef<Group>(null);
|
||||
const camera = useThree((state) => state.camera);
|
||||
const sourcePlaneRef = useRef<Group>(null);
|
||||
const geometry = useMemo(() => {
|
||||
const next = new BufferGeometry();
|
||||
const sourcePositions = graph.geometry.positions;
|
||||
@@ -457,7 +468,7 @@ const OriginalStaticMesh = memo(function OriginalStaticMesh({
|
||||
const positions = new Float32Array(sourcePositions.length);
|
||||
const normals = new Float32Array(sourceNormals.length);
|
||||
for (let index = 0; index < sourcePositions.length; index += 3) {
|
||||
// Turn WoW's Z-up source plane into an upright, camera-facing local XY plane.
|
||||
// Turn WoW's Z-up source plane into an upright local XY plane.
|
||||
positions[index] = -sourcePositions[index + 1];
|
||||
positions[index + 1] = sourcePositions[index + 2];
|
||||
positions[index + 2] = sourcePositions[index] - xCenter;
|
||||
@@ -501,9 +512,14 @@ const OriginalStaticMesh = memo(function OriginalStaticMesh({
|
||||
for (const material of materials) material.dispose();
|
||||
}, [geometry, materials]);
|
||||
|
||||
useFrame(({ camera }) => {
|
||||
useEffect(() => {
|
||||
// WoW authored this mesh as a billboard. Resolve that orientation once at
|
||||
// spawn so it stays readable without rotating whenever the camera moves.
|
||||
sourcePlaneRef.current?.lookAt(camera.position);
|
||||
}, [camera]);
|
||||
|
||||
useFrame(() => {
|
||||
if (!active) return;
|
||||
if (billboardRef.current) billboardRef.current.lookAt(camera.position);
|
||||
const playback = originalCombatMeshPlaybackFrame(
|
||||
graph,
|
||||
playbackRef.current.elapsedMs,
|
||||
@@ -517,8 +533,22 @@ const OriginalStaticMesh = memo(function OriginalStaticMesh({
|
||||
});
|
||||
|
||||
return (
|
||||
<group ref={billboardRef}>
|
||||
<mesh geometry={geometry} material={materials} renderOrder={20} frustumCulled={false} />
|
||||
<group ref={sourcePlaneRef}>
|
||||
<mesh
|
||||
name="original-combat-static-source-plane"
|
||||
geometry={geometry}
|
||||
material={materials}
|
||||
renderOrder={20}
|
||||
frustumCulled={false}
|
||||
/>
|
||||
<mesh
|
||||
name="original-combat-static-cross-plane"
|
||||
geometry={geometry}
|
||||
material={materials}
|
||||
rotation={[0, Math.PI / 2, 0]}
|
||||
renderOrder={20}
|
||||
frustumCulled={false}
|
||||
/>
|
||||
</group>
|
||||
);
|
||||
});
|
||||
@@ -533,6 +563,7 @@ function LoadedOriginalCombatEffect({
|
||||
const elapsedRef = useRef(0);
|
||||
const doneRef = useRef(false);
|
||||
const groupRef = useRef<Group>(null);
|
||||
const liveAnchorPosition = useMemo(() => new Vector3(), []);
|
||||
const auraDecayStartedAtRef = useRef<number | null>(null);
|
||||
const staticMeshPlaybackRef = useRef({ elapsedMs: 0, decaying: false });
|
||||
const particleBudgets = useMemo(() => {
|
||||
@@ -617,8 +648,10 @@ function LoadedOriginalCombatEffect({
|
||||
);
|
||||
} else {
|
||||
const actorId = originalCombatAnchorActorId(event);
|
||||
const live = liveActorPosition(actorId);
|
||||
if (live) groupRef.current.position.set(live[0], live[1], live[2]);
|
||||
if (!updateCombatEffectAnchorPosition(actorId, groupRef.current, liveAnchorPosition)) {
|
||||
const live = liveActorPosition(actorId);
|
||||
if (live) groupRef.current.position.set(live[0], live[1], live[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
const terminalDuration = projectile
|
||||
|
||||
Reference in new issue
Block a user