Release v0.1.6 2026-07-12
This commit is contained in:
+44
-180
@@ -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],
|
||||
|
||||
Reference in New Issue
Block a user