Release v0.1.10 2026-07-13
This commit is contained in:
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "i-want-to-heal",
|
"name": "i-want-to-heal",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.1.9",
|
"version": "0.1.10",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"predev": "pnpm assets:sync-basis-transcoder",
|
"predev": "pnpm assets:sync-basis-transcoder",
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import { useGameStore } from "../game/store";
|
|||||||
import type { BossId, MemberId, PulseKind } from "../game/types";
|
import type { BossId, MemberId, PulseKind } from "../game/types";
|
||||||
import { BossRoom } from "./BossRoom";
|
import { BossRoom } from "./BossRoom";
|
||||||
import { BossMechanicIndicators } from "./boss/BossMechanicIndicators";
|
import { BossMechanicIndicators } from "./boss/BossMechanicIndicators";
|
||||||
|
import { bossBurrowPositionY, bossIsBurrowing } from "./boss/bossBurrowVisuals";
|
||||||
import { bossCanTrackTarget, bossDeathOpacity } from "./boss/bossDeathVisuals";
|
import { bossCanTrackTarget, bossDeathOpacity } from "./boss/bossDeathVisuals";
|
||||||
import { GameAssetProvider, LEGACY_GAME_ASSETS_FORCED, selectedGameAssetUrl, useGameGLTF } from "./GameAssetProvider";
|
import { GameAssetProvider, LEGACY_GAME_ASSETS_FORCED, selectedGameAssetUrl, useGameGLTF } from "./GameAssetProvider";
|
||||||
|
|
||||||
@@ -130,8 +131,11 @@ function createBossRenderModel(source: THREE.Object3D) {
|
|||||||
? object.material.map(cloneMaterial)
|
? object.material.map(cloneMaterial)
|
||||||
: cloneMaterial(object.material);
|
: cloneMaterial(object.material);
|
||||||
});
|
});
|
||||||
|
model.updateMatrixWorld(true);
|
||||||
|
const modelTopY = new THREE.Box3().setFromObject(model).max.y;
|
||||||
return {
|
return {
|
||||||
model,
|
model,
|
||||||
|
modelTopY,
|
||||||
fadeMaterials: [...materialClones.values()].map((material): BossFadeMaterial => ({
|
fadeMaterials: [...materialClones.values()].map((material): BossFadeMaterial => ({
|
||||||
material,
|
material,
|
||||||
baseOpacity: material.opacity,
|
baseOpacity: material.opacity,
|
||||||
@@ -834,9 +838,10 @@ function AlternateBoss({ kind, bossIndex }: { kind: AlternateBossKind; bossIndex
|
|||||||
const light = useRef<THREE.PointLight>(null);
|
const light = useRef<THREE.PointLight>(null);
|
||||||
const assetUrl = selectedGameAssetUrl(config.url, config.optimizedUrl ?? config.url);
|
const assetUrl = selectedGameAssetUrl(config.url, config.optimizedUrl ?? config.url);
|
||||||
const gltf = useGameGLTF(assetUrl);
|
const gltf = useGameGLTF(assetUrl);
|
||||||
const { model, fadeMaterials } = useMemo(() => createBossRenderModel(gltf.scene), [gltf.scene]);
|
const { model, modelTopY, fadeMaterials } = useMemo(() => createBossRenderModel(gltf.scene), [gltf.scene]);
|
||||||
const { actions } = useAnimations(gltf.animations, model);
|
const { actions } = useAnimations(gltf.animations, model);
|
||||||
const targetPosition = useMemo(() => new THREE.Vector3(), []);
|
const targetPosition = useMemo(() => new THREE.Vector3(), []);
|
||||||
|
const burrowPositionY = bossBurrowPositionY(modelTopY, config.scale);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => { for (const entry of fadeMaterials) entry.material.dispose(); };
|
return () => { for (const entry of fadeMaterials) entry.material.dispose(); };
|
||||||
@@ -873,9 +878,9 @@ function AlternateBoss({ kind, bossIndex }: { kind: AlternateBossKind; bossIndex
|
|||||||
if (!current) return;
|
if (!current) return;
|
||||||
const motion = current.motion;
|
const motion = current.motion;
|
||||||
const airborne = archetype === "sky-sweeper" && motion.mode === "skyfall";
|
const airborne = archetype === "sky-sweeper" && motion.mode === "skyfall";
|
||||||
const burrowed = archetype === "burrower" && motion.activeMechanicId === "burrow-rush" && motion.mode === "charging";
|
const burrowing = archetype === "burrower" && bossIsBurrowing(motion.activeMechanicId, motion.mode);
|
||||||
const floatingHeight = config.floating ? 0.2 : 0.03;
|
const floatingHeight = config.floating ? 0.2 : 0.03;
|
||||||
targetPosition.set(motion.position[0], airborne ? 3.2 : burrowed ? -0.58 : floatingHeight, motion.position[1]);
|
targetPosition.set(motion.position[0], airborne ? 3.2 : burrowing ? burrowPositionY : floatingHeight, motion.position[1]);
|
||||||
group.current.position.lerp(targetPosition, 1 - Math.pow(0.00001, delta));
|
group.current.position.lerp(targetPosition, 1 - Math.pow(0.00001, delta));
|
||||||
if (!bossCanTrackTarget(current.boss.hp)) return;
|
if (!bossCanTrackTarget(current.boss.hp)) return;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { BOSS_BURROW_CLEARANCE, bossBurrowPositionY, bossIsBurrowing } from "./bossBurrowVisuals";
|
||||||
|
|
||||||
|
describe("boss burrow visuals", () => {
|
||||||
|
it("places the scaled top of the model below the arena floor", () => {
|
||||||
|
const modelTopY = 2.8371768724243784;
|
||||||
|
const modelScale = 0.82;
|
||||||
|
const positionY = bossBurrowPositionY(modelTopY, modelScale);
|
||||||
|
|
||||||
|
expect(positionY + modelTopY * modelScale).toBeCloseTo(-BOSS_BURROW_CLEARANCE, 6);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("starts sinking during the warning and remains submerged through the charge", () => {
|
||||||
|
expect(bossIsBurrowing("burrow-rush", "telegraph")).toBe(true);
|
||||||
|
expect(bossIsBurrowing("burrow-rush", "charging")).toBe(true);
|
||||||
|
expect(bossIsBurrowing("burrow-rush", "holding")).toBe(false);
|
||||||
|
expect(bossIsBurrowing("bull-charge", "charging")).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import type { BossMechanicId, BossMotionMode } from "../../game/types";
|
||||||
|
|
||||||
|
export const BOSS_BURROW_CLEARANCE = 0.58;
|
||||||
|
|
||||||
|
export function bossBurrowPositionY(
|
||||||
|
modelTopY: number,
|
||||||
|
modelScale: number,
|
||||||
|
clearance = BOSS_BURROW_CLEARANCE,
|
||||||
|
): number {
|
||||||
|
return -(Math.max(0, modelTopY) * Math.max(0, modelScale) + Math.max(0, clearance));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function bossIsBurrowing(
|
||||||
|
activeMechanicId: BossMechanicId | null,
|
||||||
|
mode: BossMotionMode,
|
||||||
|
): boolean {
|
||||||
|
return activeMechanicId === "burrow-rush" && (mode === "telegraph" || mode === "charging");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user