Release v0.1.1 2026-07-10
This commit is contained in:
@@ -2,6 +2,7 @@ import { Canvas, createPortal, useFrame, useThree } from "@react-three/fiber";
|
||||
import { useAnimations, useGLTF } from "@react-three/drei";
|
||||
import { Suspense, useEffect, useMemo, useRef, type MutableRefObject } from "react";
|
||||
import * as THREE from "three";
|
||||
import { getControllerMovement } from "../input/controller";
|
||||
import { clone as cloneSkeleton } from "three/examples/jsm/utils/SkeletonUtils.js";
|
||||
import { useGameStore } from "../game/store";
|
||||
import type { MemberId, PulseKind } from "../game/types";
|
||||
@@ -9,6 +10,12 @@ import { BossMechanicIndicators } from "./boss/BossMechanicIndicators";
|
||||
|
||||
const BULL_URL = new URL("../../game_assets/models/claudecraft/creatures/bull.glb", import.meta.url).href;
|
||||
const SPIDER_URL = new URL("../../game_assets/models/downloaded/low-poly-spider/low-poly-spider.glb", import.meta.url).href;
|
||||
const SPIDER_TEXTURE_URLS: Record<string, string> = {
|
||||
"Spinnen_Bein_tex_2.jpg": new URL("../../game_assets/models/downloaded/low-poly-spider/textures/Spinnen_Bein_tex_2.jpg", import.meta.url).href,
|
||||
"SH3.png": new URL("../../game_assets/models/downloaded/low-poly-spider/textures/SH3.png", import.meta.url).href,
|
||||
"Spinnen_Bein_tex_COLOR_.jpg": new URL("../../game_assets/models/downloaded/low-poly-spider/textures/Spinnen_Bein_tex_COLOR_.jpg", import.meta.url).href,
|
||||
"haar_detail_NRM.jpg": new URL("../../game_assets/models/downloaded/low-poly-spider/textures/haar_detail_NRM.jpg", import.meta.url).href,
|
||||
};
|
||||
const DRAGON_URL = new URL("../../game_assets/models/claudecraft/creatures/dragonevolved.glb", import.meta.url).href;
|
||||
const PARTY_MODEL_URLS: Record<MemberId, string> = {
|
||||
aelia: new URL("../../game_assets/models/claudecraft/chars/players/druid.glb", import.meta.url).href,
|
||||
@@ -41,6 +48,11 @@ const PARTY_ATTACK_CLIPS: Record<MemberId, string> = {
|
||||
orin: "Spellcast_Shoot",
|
||||
vale: "Dualwield_Melee_Attack_Chop",
|
||||
};
|
||||
const ARENA_COLUMNS = Array.from({ length: 10 }, (_, index) => {
|
||||
const angle = (index / 10) * Math.PI * 2;
|
||||
return [Math.sin(angle) * 9.3, Math.cos(angle) * 9.3] as const;
|
||||
});
|
||||
const ARENA_TORCH_COLORS = [new THREE.Color("#ff9a4f"), new THREE.Color("#77ddce")] as const;
|
||||
type GameStoreState = ReturnType<typeof useGameStore.getState>;
|
||||
|
||||
function encounterBossAt(state: GameStoreState, bossIndex: number) {
|
||||
@@ -59,6 +71,13 @@ function targetBossMotionByInstance(state: GameStoreState, instanceId?: string)
|
||||
return state.additionalBosses.find((entry) => entry.instanceId === instanceId)?.motion ?? targetBossMotion(state);
|
||||
}
|
||||
|
||||
const configureSpiderLoader: NonNullable<Parameters<typeof useGLTF>[3]> = (loader) => {
|
||||
loader.manager.setURLModifier((url) => {
|
||||
const fileName = url.slice(url.lastIndexOf("/") + 1);
|
||||
return SPIDER_TEXTURE_URLS[fileName] ?? url;
|
||||
});
|
||||
};
|
||||
|
||||
type ActorAnimationState = "idle" | "walk" | "run" | "attack" | "cast" | "hit" | "death";
|
||||
|
||||
type WeaponGrip = "staff" | "sword" | "crossbow" | "wand" | "dagger" | "prop";
|
||||
@@ -208,11 +227,24 @@ function PartyCharacterModel({
|
||||
}
|
||||
|
||||
function Arena() {
|
||||
const columns = useMemo(() => {
|
||||
return Array.from({ length: 10 }, (_, index) => {
|
||||
const angle = (index / 10) * Math.PI * 2;
|
||||
return [Math.sin(angle) * 9.3, Math.cos(angle) * 9.3] as const;
|
||||
const pillarInstances = useRef<THREE.InstancedMesh>(null);
|
||||
const torchInstances = useRef<THREE.InstancedMesh>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const pillars = pillarInstances.current;
|
||||
const torches = torchInstances.current;
|
||||
if (!pillars || !torches) return;
|
||||
const matrix = new THREE.Matrix4();
|
||||
ARENA_COLUMNS.forEach(([x, z], index) => {
|
||||
matrix.makeTranslation(x, 1.1, z - 1);
|
||||
pillars.setMatrixAt(index, matrix);
|
||||
matrix.makeTranslation(x, 2.6, z - 1);
|
||||
torches.setMatrixAt(index, matrix);
|
||||
torches.setColorAt(index, ARENA_TORCH_COLORS[index % ARENA_TORCH_COLORS.length]);
|
||||
});
|
||||
pillars.instanceMatrix.needsUpdate = true;
|
||||
torches.instanceMatrix.needsUpdate = true;
|
||||
if (torches.instanceColor) torches.instanceColor.needsUpdate = true;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
@@ -229,19 +261,16 @@ function Arena() {
|
||||
<circleGeometry args={[2.1, 48]} />
|
||||
<meshStandardMaterial color="#27342d" roughness={1} />
|
||||
</mesh>
|
||||
{columns.map(([x, z], index) => (
|
||||
<group key={index} position={[x, 0, z - 1]}>
|
||||
<mesh castShadow receiveShadow position={[0, 1.1, 0]}>
|
||||
<cylinderGeometry args={[0.38, 0.5, 2.4, 6]} />
|
||||
<meshStandardMaterial color="#26342f" roughness={0.8} />
|
||||
</mesh>
|
||||
<pointLight color={index % 2 ? "#dd7b38" : "#6fc9ba"} intensity={2.2} distance={5} position={[0, 2.7, 0]} />
|
||||
<mesh position={[0, 2.6, 0]}>
|
||||
<octahedronGeometry args={[0.2, 0]} />
|
||||
<meshBasicMaterial color={index % 2 ? "#ff9a4f" : "#77ddce"} />
|
||||
</mesh>
|
||||
</group>
|
||||
))}
|
||||
<instancedMesh ref={pillarInstances} args={[undefined, undefined, ARENA_COLUMNS.length]} castShadow receiveShadow>
|
||||
<cylinderGeometry args={[0.38, 0.5, 2.4, 6]} />
|
||||
<meshStandardMaterial color="#26342f" roughness={0.8} />
|
||||
</instancedMesh>
|
||||
<instancedMesh ref={torchInstances} args={[undefined, undefined, ARENA_COLUMNS.length]}>
|
||||
<octahedronGeometry args={[0.2, 0]} />
|
||||
<meshBasicMaterial />
|
||||
</instancedMesh>
|
||||
<pointLight color="#dd7b38" intensity={2.2} distance={7} position={[-6, 2.7, -1]} />
|
||||
<pointLight color="#6fc9ba" intensity={2.2} distance={7} position={[6, 2.7, -1]} />
|
||||
<gridHelper args={[22, 22, "#2c4039", "#1b2925"]} position={[0, 0.01, -1]} />
|
||||
</group>
|
||||
);
|
||||
@@ -365,11 +394,9 @@ function PlayerCharacter() {
|
||||
if (state.phase === "combat" && !state.paused && !state.activeCast && player.hp > 0 && !knocked) {
|
||||
inputX = Number(keys.current.has("d")) - Number(keys.current.has("a"));
|
||||
inputZ = Number(keys.current.has("s")) - Number(keys.current.has("w"));
|
||||
const gamepad = navigator.getGamepads?.()[0];
|
||||
if (gamepad) {
|
||||
inputX += Math.abs(gamepad.axes[0] ?? 0) > 0.18 ? gamepad.axes[0] : 0;
|
||||
inputZ += Math.abs(gamepad.axes[1] ?? 0) > 0.18 ? gamepad.axes[1] : 0;
|
||||
}
|
||||
const controller = getControllerMovement();
|
||||
inputX += controller.x;
|
||||
inputZ += controller.y;
|
||||
}
|
||||
const length = Math.hypot(inputX, inputZ);
|
||||
if (length > 0.05) {
|
||||
@@ -587,7 +614,7 @@ function AlternateBoss({ kind, bossIndex }: { kind: AlternateBossKind; bossIndex
|
||||
const bossHp = useGameStore((state) => (bossIndex === 0 ? state.boss : state.additionalBosses[bossIndex - 1]?.boss)?.hp ?? 0);
|
||||
const defeated = bossHp <= 0;
|
||||
const group = useRef<THREE.Group>(null);
|
||||
const gltf = useGLTF(config.url, false, true);
|
||||
const gltf = useGLTF(config.url, false, true, kind === "vexa" ? configureSpiderLoader : undefined);
|
||||
const model = useMemo(() => cloneSkeleton(gltf.scene), [gltf.scene]);
|
||||
const { actions } = useAnimations(gltf.animations, model);
|
||||
const targetPosition = useMemo(() => new THREE.Vector3(), []);
|
||||
|
||||
Reference in New Issue
Block a user