Release v0.1.2 2026-07-11

This commit is contained in:
Warren H
2026-07-11 00:12:57 -04:00
parent 6726c600e4
commit 076f6cf97c
16 changed files with 556 additions and 21 deletions
@@ -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,