Release v0.1.2 2026-07-11
This commit is contained in:
@@ -8,6 +8,7 @@ import { useGameStore } from "../../game/store";
|
||||
const CHARGE_MARKERS = [0, 1, 2, 3, 4, 5, 6] as const;
|
||||
const STACK_DIRECTIONS = Array.from({ length: 8 }, (_, index) => (index / 8) * Math.PI * 2);
|
||||
const EMPTY_HAZARDS: never[] = [];
|
||||
const EMPTY_SLASH_LANES: never[] = [];
|
||||
type GameStoreState = ReturnType<typeof useGameStore.getState>;
|
||||
|
||||
function motionAt(state: GameStoreState, bossIndex: number) {
|
||||
@@ -68,6 +69,63 @@ export function ChargeLaneIndicator({ bossIndex = 0 }: { bossIndex?: number }) {
|
||||
);
|
||||
}
|
||||
|
||||
function SlashLaneIndicator({ laneId, bossIndex }: { laneId: string; bossIndex: number }) {
|
||||
const phase = useGameStore((state) => state.phase);
|
||||
const motion = useGameStore((state) => motionAt(state, bossIndex));
|
||||
const material = useRef<THREE.MeshBasicMaterial>(null);
|
||||
const edgeMaterial = useRef<THREE.MeshBasicMaterial>(null);
|
||||
useFrame(({ clock }) => {
|
||||
if (!material.current || !edgeMaterial.current) return;
|
||||
const current = motionAt(useGameStore.getState(), bossIndex);
|
||||
const active = current?.mode === "mantis_recover";
|
||||
material.current.opacity = active ? 0.5 : 0.14 + (Math.sin(clock.elapsedTime * 12) + 1) * 0.09;
|
||||
edgeMaterial.current.opacity = active ? 1 : 0.62 + (Math.sin(clock.elapsedTime * 12) + 1) * 0.15;
|
||||
});
|
||||
const lane = motion?.slashLanes.find((candidate) => candidate.id === laneId);
|
||||
if (!lane || phase !== "combat") return null;
|
||||
const visible = motion.mode === "mantis_line_telegraph"
|
||||
|| motion.mode === "mantis_cross_telegraph"
|
||||
|| motion.mode === "mantis_recover";
|
||||
if (!visible) return null;
|
||||
|
||||
const dx = lane.end[0] - lane.start[0];
|
||||
const dz = lane.end[1] - lane.start[1];
|
||||
const length = Math.hypot(dx, dz);
|
||||
const angle = Math.atan2(dx, dz);
|
||||
const midpoint: [number, number, number] = [
|
||||
(lane.start[0] + lane.end[0]) * 0.5,
|
||||
0.052,
|
||||
(lane.start[1] + lane.end[1]) * 0.5,
|
||||
];
|
||||
const active = motion.mode === "mantis_recover";
|
||||
const color = active ? "#ffd36a" : "#ff5128";
|
||||
return (
|
||||
<group position={midpoint} rotation={[0, angle, 0]}>
|
||||
<mesh rotation={[-Math.PI / 2, 0, 0]}>
|
||||
<planeGeometry args={[lane.width, length]} />
|
||||
<meshBasicMaterial ref={material} color={color} transparent opacity={0.22} depthWrite={false} />
|
||||
</mesh>
|
||||
{[-1, 1].map((side) => (
|
||||
<mesh key={side} position={[side * lane.width * 0.5, 0.018, 0]}>
|
||||
<boxGeometry args={[0.07, 0.03, length]} />
|
||||
<meshBasicMaterial ref={side < 0 ? edgeMaterial : undefined} color={color} transparent opacity={0.8} depthWrite={false} />
|
||||
</mesh>
|
||||
))}
|
||||
{active && (
|
||||
<mesh position={[0, 0.055, 0]}>
|
||||
<boxGeometry args={[0.16, 0.08, length]} />
|
||||
<meshBasicMaterial color="#fff0a8" transparent opacity={0.92} depthWrite={false} />
|
||||
</mesh>
|
||||
)}
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
export function SlashLaneIndicators({ bossIndex = 0 }: { bossIndex?: number }) {
|
||||
const lanes = useGameStore((state) => motionAt(state, bossIndex)?.slashLanes ?? EMPTY_SLASH_LANES);
|
||||
return <>{lanes.map((lane) => <SlashLaneIndicator key={lane.id} laneId={lane.id} bossIndex={bossIndex} />)}</>;
|
||||
}
|
||||
|
||||
export function PounceStackIndicator({ bossIndex = 0 }: { bossIndex?: number }) {
|
||||
const phase = useGameStore((state) => state.phase);
|
||||
const motionMode = useGameStore((state) => motionAt(state, bossIndex)?.mode);
|
||||
@@ -205,6 +263,7 @@ export function CircleHazardIndicators({ bossIndex = 0 }: { bossIndex?: number }
|
||||
|
||||
const BOSS_MECHANIC_INDICATORS: readonly ComponentType<{ bossIndex?: number }>[] = [
|
||||
ChargeLaneIndicator,
|
||||
SlashLaneIndicators,
|
||||
PounceStackIndicator,
|
||||
BindingWebIndicator,
|
||||
BreathConeIndicator,
|
||||
|
||||
Reference in New Issue
Block a user