Release v0.1.6 2026-07-12

This commit is contained in:
Warren H
2026-07-12 23:20:15 -04:00
parent 35553c18dd
commit 122f159b94
55 changed files with 2229 additions and 587 deletions
+44 -180
View File
@@ -6,7 +6,19 @@ import { getControllerMovement } from "../input/controller";
import { clone as cloneSkeleton } from "three/examples/jsm/utils/SkeletonUtils.js";
import { ARENA_CENTER, clampToArena } from "../game/arena";
import { BOSS_ARCHETYPE_BY_ID } from "../game/bossCatalog";
import { ALTERNATE_BOSS_CONFIG, BULL_URL, type AlternateBossKind } from "../game/bossVisuals";
import { bossAnimationCue } from "../game/bosses/mechanicPool";
import {
CAMERA_FOCUS_HEIGHT,
CAMERA_LOOK_AHEAD,
CAMERA_ORBIT_DISTANCE,
DEFAULT_CAMERA_PITCH,
DEFAULT_CAMERA_YAW,
setCameraRelativeMovement,
updateCameraOrbit,
type CameraOrbitState,
type PlanarMovement,
} from "../game/cameraOrbit";
import {
isActorAnimationOneShot,
shouldStartActorAnimation,
@@ -17,43 +29,8 @@ import { useGameStore } from "../game/store";
import type { BossId, MemberId, PulseKind } from "../game/types";
import { BossRoom } from "./BossRoom";
import { BossMechanicIndicators } from "./boss/BossMechanicIndicators";
import { bossCanTrackTarget } from "./boss/bossDeathVisuals";
const BULL_URL = new URL("../assets/game/models/claudecraft/creatures/bull.glb", import.meta.url).href;
const SANDGLASS_URL = new URL("../assets/game/models/original/bosses/sandglass-scorpion/sandglass-scorpion.glb", import.meta.url).href;
const CRYSTAL_BAT_MATRIARCH_URL = new URL("../assets/game/models/original/bosses/crystal-bat-matriarch/crystal-bat-matriarch.glb", import.meta.url).href;
const CRAGCLAW_URL = new URL("../assets/game/models/claudecraft/creatures/crabenemy.glb", import.meta.url).href;
const MOURNVEIL_URL = new URL("../assets/game/models/claudecraft/creatures/ghost.glb", import.meta.url).href;
const CROWNSHARD_URL = new URL("../assets/game/models/claudecraft/creatures/golelingevolved.glb", import.meta.url).href;
const CLAUDE_BOSS_URLS: Record<Exclude<BossId,
| "bulldrome"
| "sandglass-scorpion"
| "cragclaw-crab"
| "mournveil-ghost"
| "crownshard-golem"
| "crystal-bat-matriarch"
>, string> = {
"stormwool-alpaca": new URL("../assets/game/models/claudecraft/creatures/alpaca.glb", import.meta.url).href,
"cluckhorn-colossus": new URL("../assets/game/models/claudecraft/creatures/chicken_cow.glb", import.meta.url).href,
"ashwing-demon": new URL("../assets/game/models/claudecraft/creatures/demon.glb", import.meta.url).href,
"riftclaw-demon": new URL("../assets/game/models/claudecraft/creatures/demonalt.glb", import.meta.url).href,
"tempestscale-dragon": new URL("../assets/game/models/claudecraft/creatures/dragonevolved.glb", import.meta.url).href,
emberfox: new URL("../assets/game/models/claudecraft/creatures/fox.glb", import.meta.url).href,
"mirelord-frog": new URL("../assets/game/models/claudecraft/creatures/frog.glb", import.meta.url).href,
"stonebreaker-giant": new URL("../assets/game/models/claudecraft/creatures/giant.glb", import.meta.url).href,
"glub-sovereign": new URL("../assets/game/models/claudecraft/creatures/glubevolved.glb", import.meta.url).href,
"scrapking-goblin": new URL("../assets/game/models/claudecraft/creatures/goblin.glb", import.meta.url).href,
"warcaller-orc": new URL("../assets/game/models/claudecraft/creatures/orc.glb", import.meta.url).href,
"tuskmaw-orc": new URL("../assets/game/models/claudecraft/creatures/orcenemy.glb", import.meta.url).href,
"broodfang-spider": new URL("../assets/game/models/claudecraft/creatures/spider.glb", import.meta.url).href,
"silkfang-spider": new URL("../assets/game/models/claudecraft/creatures/spider.glb", import.meta.url).href,
"thorncrown-stag": new URL("../assets/game/models/claudecraft/creatures/stag.glb", import.meta.url).href,
"sky-totem": new URL("../assets/game/models/claudecraft/creatures/tribal.glb", import.meta.url).href,
"razorcrest-raptor": new URL("../assets/game/models/claudecraft/creatures/velociraptor.glb", import.meta.url).href,
"bristlequake-boar": new URL("../assets/game/models/claudecraft/creatures/wild_boar.glb", import.meta.url).href,
"moonfang-wolf": new URL("../assets/game/models/claudecraft/creatures/wolf.glb", import.meta.url).href,
"frostmaw-yeti": new URL("../assets/game/models/claudecraft/creatures/yeti.glb", import.meta.url).href,
"rimeclaw-yeti": new URL("../assets/game/models/claudecraft/creatures/yetialt.glb", import.meta.url).href,
};
const PARTY_MODEL_URLS: Record<MemberId, string> = {
aelia: new URL("../assets/game/models/claudecraft/chars/players/druid.glb", import.meta.url).href,
brann: new URL("../assets/game/models/claudecraft/chars/players/knight.glb", import.meta.url).href,
@@ -428,6 +405,8 @@ function PlayerCharacter() {
const castingUntil = useRef(0);
const instantCastTrigger = useRef(0);
const desiredCameraPosition = useMemo(() => new THREE.Vector3(), []);
const cameraOrbit = useRef<CameraOrbitState>({ yaw: DEFAULT_CAMERA_YAW, pitch: DEFAULT_CAMERA_PITCH });
const cameraRelativeMovement = useRef<PlanarMovement>({ x: 0, z: 0 });
useEffect(() => {
const start = useGameStore.getState().partyPositions.aelia;
@@ -451,7 +430,11 @@ function PlayerCharacter() {
const nudgeX = Number(key === "d") - Number(key === "a");
const nudgeZ = Number(key === "s") - Number(key === "w");
if (!nudgeX && !nudgeZ) return;
const next = clampToArena([group.current.position.x + nudgeX * 0.18, group.current.position.z + nudgeZ * 0.18]);
setCameraRelativeMovement(cameraRelativeMovement.current, nudgeX, nudgeZ, cameraOrbit.current.yaw);
const next = clampToArena([
group.current.position.x + cameraRelativeMovement.current.x * 0.18,
group.current.position.z + cameraRelativeMovement.current.z * 0.18,
]);
group.current.position.x = next[0];
group.current.position.z = next[1];
setPlayerPosition([group.current.position.x, group.current.position.z]);
@@ -476,9 +459,16 @@ function PlayerCharacter() {
inputX = Number(keys.current.has("d")) - Number(keys.current.has("a"));
inputZ = Number(keys.current.has("s")) - Number(keys.current.has("w"));
const controller = getControllerMovement();
inputX += controller.x;
inputZ += controller.y;
inputX += controller.moveX;
inputZ += controller.moveY;
}
const controller = getControllerMovement();
if (state.phase === "combat" && !state.paused) {
updateCameraOrbit(cameraOrbit.current, controller.lookX, controller.lookY, delta);
}
setCameraRelativeMovement(cameraRelativeMovement.current, inputX, inputZ, cameraOrbit.current.yaw);
inputX = cameraRelativeMovement.current.x;
inputZ = cameraRelativeMovement.current.z;
const length = Math.hypot(inputX, inputZ);
if (length > 0.05) {
const speed = 4.6 * state.gearModifiers.aelia.moveSpeed * delta / Math.max(1, length);
@@ -518,9 +508,20 @@ function PlayerCharacter() {
: "idle";
group.current.position.y = THREE.MathUtils.lerp(group.current.position.y, 0.025, 0.16);
desiredCameraPosition.set(group.current.position.x * 0.45, 5.1, group.current.position.z + 7.7);
const horizontalDistance = Math.cos(cameraOrbit.current.pitch) * CAMERA_ORBIT_DISTANCE;
const sinYaw = Math.sin(cameraOrbit.current.yaw);
const cosYaw = Math.cos(cameraOrbit.current.yaw);
desiredCameraPosition.set(
group.current.position.x + sinYaw * horizontalDistance,
CAMERA_FOCUS_HEIGHT + Math.sin(cameraOrbit.current.pitch) * CAMERA_ORBIT_DISTANCE,
group.current.position.z + cosYaw * horizontalDistance,
);
camera.position.lerp(desiredCameraPosition, 1 - Math.pow(0.002, delta));
camera.lookAt(group.current.position.x * 0.55, 0.65, group.current.position.z - 2.8);
camera.lookAt(
group.current.position.x - sinYaw * CAMERA_LOOK_AHEAD,
CAMERA_FOCUS_HEIGHT,
group.current.position.z - cosYaw * CAMERA_LOOK_AHEAD,
);
broadcastTimer.current += delta;
if (broadcastTimer.current > 0.15) {
@@ -651,6 +652,7 @@ function BullBoss({ bossIndex }: { bossIndex: number }) {
const motion = current.motion;
targetPosition.set(motion.position[0], 0.03, motion.position[1]);
group.current.position.lerp(targetPosition, 1 - Math.pow(0.00001, delta));
if (!bossCanTrackTarget(current.boss.hp)) return;
if (motion.mode === "stacking") {
group.current.rotation.y += (Math.PI * 2 / 5) * delta;
@@ -678,145 +680,6 @@ function BullBoss({ bossIndex }: { bossIndex: number }) {
);
}
type AlternateBossKind = Exclude<ReturnType<typeof useGameStore.getState>["boss"]["id"], "bulldrome">;
interface AlternateBossConfig {
url: string;
scale: number;
idle: string;
move: string;
attack: string;
special: string;
death: string;
light: string;
rotationOffset: number;
prototype?: boolean;
floating?: boolean;
}
const ALTERNATE_BOSS_CONFIG: Record<AlternateBossKind, AlternateBossConfig> = {
"sandglass-scorpion": {
url: SANDGLASS_URL,
scale: 0.7,
idle: "Idle",
move: "Burrow",
attack: "Eruption",
special: "Hourglass",
death: "Death",
light: "#e9b94f",
rotationOffset: 0,
},
"cragclaw-crab": {
url: CRAGCLAW_URL,
scale: 1.2,
idle: "Idle",
move: "Walk",
attack: "Bite_Front",
special: "Bite_InPlace",
death: "Death",
light: "#49d5df",
rotationOffset: 0,
},
"mournveil-ghost": {
url: MOURNVEIL_URL,
scale: 1.1,
idle: "Flying_Idle",
move: "Fast_Flying",
attack: "Punch",
special: "Headbutt",
death: "Death",
light: "#9d72ff",
rotationOffset: 0,
floating: true,
},
"crownshard-golem": {
url: CROWNSHARD_URL,
scale: 1.15,
idle: "Flying_Idle",
move: "Fast_Flying",
attack: "Punch",
special: "Headbutt",
death: "Death",
light: "#e0bd45",
rotationOffset: 0,
floating: true,
},
"crystal-bat-matriarch": {
url: CRYSTAL_BAT_MATRIARCH_URL,
scale: 0.828,
idle: "Idle",
move: "Swoop",
attack: "SonicPulse",
special: "MirrorShatter",
death: "Death",
light: "#8eeaff",
rotationOffset: 0,
floating: true,
},
"stormwool-alpaca": {
url: CLAUDE_BOSS_URLS["stormwool-alpaca"], scale: 0.72, idle: "Idle", move: "Gallop", attack: "Attack_Headbutt", special: "Attack_Kick", death: "Death", light: "#8fc7ff", rotationOffset: 0, prototype: true,
},
"cluckhorn-colossus": {
url: CLAUDE_BOSS_URLS["cluckhorn-colossus"], scale: 2.2, idle: "Idle", move: "Run", attack: "Attack", special: "Jump", death: "Death", light: "#f0b85d", rotationOffset: 0, prototype: true,
},
"ashwing-demon": {
url: CLAUDE_BOSS_URLS["ashwing-demon"], scale: 1.2, idle: "Flying_Idle", move: "Fast_Flying", attack: "Punch", special: "Headbutt", death: "Death", light: "#df665d", rotationOffset: 0, prototype: true, floating: true,
},
"riftclaw-demon": {
url: CLAUDE_BOSS_URLS["riftclaw-demon"], scale: 1.35, idle: "Idle", move: "Run", attack: "Punch", special: "Weapon", death: "Death", light: "#d45cff", rotationOffset: 0, prototype: true,
},
"tempestscale-dragon": {
url: CLAUDE_BOSS_URLS["tempestscale-dragon"], scale: 1.2, idle: "Flying_Idle", move: "Fast_Flying", attack: "Punch", special: "Headbutt", death: "Death", light: "#5fc8e8", rotationOffset: 0, prototype: true, floating: true,
},
emberfox: {
url: CLAUDE_BOSS_URLS.emberfox, scale: 1, idle: "Idle", move: "Gallop", attack: "Attack", special: "Gallop_Jump", death: "Death", light: "#ff7b45", rotationOffset: 0, prototype: true,
},
"mirelord-frog": {
url: CLAUDE_BOSS_URLS["mirelord-frog"], scale: 1.4, idle: "Idle", move: "Run", attack: "Punch", special: "Jump", death: "Death", light: "#73c96b", rotationOffset: 0, prototype: true,
},
"stonebreaker-giant": {
url: CLAUDE_BOSS_URLS["stonebreaker-giant"], scale: 1, idle: "Idle", move: "Run", attack: "Attack", special: "Jump", death: "Death", light: "#c89563", rotationOffset: 0, prototype: true,
},
"glub-sovereign": {
url: CLAUDE_BOSS_URLS["glub-sovereign"], scale: 1.2, idle: "Flying_Idle", move: "Fast_Flying", attack: "Punch", special: "Headbutt", death: "Death", light: "#6ce0b8", rotationOffset: 0, prototype: true, floating: true,
},
"scrapking-goblin": {
url: CLAUDE_BOSS_URLS["scrapking-goblin"], scale: 1.5, idle: "Idle", move: "Run", attack: "Attack", special: "Jump", death: "Death", light: "#d7a34b", rotationOffset: 0, prototype: true,
},
"warcaller-orc": {
url: CLAUDE_BOSS_URLS["warcaller-orc"], scale: 1.35, idle: "Idle", move: "Run", attack: "Punch", special: "Weapon", death: "Death", light: "#e4533f", rotationOffset: 0, prototype: true,
},
"tuskmaw-orc": {
url: CLAUDE_BOSS_URLS["tuskmaw-orc"], scale: 1.45, idle: "Idle", move: "Walk", attack: "Bite_Front", special: "Jump", death: "Death", light: "#9eb25d", rotationOffset: 0, prototype: true,
},
"broodfang-spider": {
url: CLAUDE_BOSS_URLS["broodfang-spider"], scale: 1.35, idle: "Spider_Idle", move: "Spider_Walk", attack: "Spider_Attack", special: "Spider_Jump", death: "Spider_Death", light: "#b56cff", rotationOffset: 0, prototype: true,
},
"silkfang-spider": {
url: CLAUDE_BOSS_URLS["silkfang-spider"], scale: 1.35, idle: "Spider_Idle", move: "Spider_Walk", attack: "Spider_Attack", special: "Spider_Jump", death: "Spider_Death", light: "#9d68d8", rotationOffset: 0, prototype: true,
},
"thorncrown-stag": {
url: CLAUDE_BOSS_URLS["thorncrown-stag"], scale: 0.85, idle: "Idle", move: "Gallop", attack: "Attack_Headbutt", special: "Attack_Kick", death: "Death", light: "#7fc46b", rotationOffset: 0, prototype: true,
},
"sky-totem": {
url: CLAUDE_BOSS_URLS["sky-totem"], scale: 1.2, idle: "Flying_Idle", move: "Fast_Flying", attack: "Punch", special: "Headbutt", death: "Death", light: "#69d4d1", rotationOffset: 0, prototype: true, floating: true,
},
"razorcrest-raptor": {
url: CLAUDE_BOSS_URLS["razorcrest-raptor"], scale: 1.1, idle: "Velociraptor_Idle", move: "Velociraptor_Run", attack: "Velociraptor_Attack", special: "Velociraptor_Jump", death: "Velociraptor_Death", light: "#d9c45a", rotationOffset: 0, prototype: true,
},
"bristlequake-boar": {
url: CLAUDE_BOSS_URLS["bristlequake-boar"], scale: 0.475, idle: "Idle_AnimalArmature", move: "Gallop_AnimalArmature", attack: "Attack_Headbutt_AnimalArmature", special: "Attack_Kick_AnimalArmature", death: "Death_AnimalArmature", light: "#d47b45", rotationOffset: 0, prototype: true,
},
"moonfang-wolf": {
url: CLAUDE_BOSS_URLS["moonfang-wolf"], scale: 1.05, idle: "Idle", move: "Gallop", attack: "Attack", special: "Gallop_Jump", death: "Death", light: "#9db9e5", rotationOffset: 0, prototype: true,
},
"frostmaw-yeti": {
url: CLAUDE_BOSS_URLS["frostmaw-yeti"], scale: 1.35, idle: "Idle", move: "Walk", attack: "Bite_Front", special: "Jump", death: "Death", light: "#8ed8ef", rotationOffset: 0, prototype: true,
},
"rimeclaw-yeti": {
url: CLAUDE_BOSS_URLS["rimeclaw-yeti"], scale: 1.35, idle: "Idle", move: "Run", attack: "Punch", special: "Weapon", death: "Death", light: "#75bfe8", rotationOffset: 0, prototype: true,
},
};
function alternateBossClip(kind: AlternateBossKind, motion: ReturnType<typeof useGameStore.getState>["bossMotion"]) {
const config = ALTERNATE_BOSS_CONFIG[kind];
@@ -879,6 +742,7 @@ function AlternateBoss({ kind, bossIndex }: { kind: AlternateBossKind; bossIndex
const floatingHeight = config.floating ? 0.2 : 0.03;
targetPosition.set(motion.position[0], airborne ? 3.2 : burrowed ? -0.58 : floatingHeight, motion.position[1]);
group.current.position.lerp(targetPosition, 1 - Math.pow(0.00001, delta));
if (!bossCanTrackTarget(current.boss.hp)) return;
let targetAngle = Math.atan2(
state.partyPositions.brann[0] - motion.position[0],