import { Html } from "@react-three/drei"; import { useFrame } from "@react-three/fiber"; import { useMemo, useRef } from "react"; import type { Group, Mesh } from "three"; import { MathUtils } from "three"; import type { ResourceNodeSnapshot } from "../types"; import { hashToUnit } from "./characterUtils"; type ResourceActorProps = { node: ResourceNodeSnapshot; isSelected: boolean; onSelect: () => void; }; export function ResourceActor({ node, isSelected, onSelect }: ResourceActorProps) { const pulseRef = useRef(null); const swayRef = useRef(null); const animationPhase = useMemo(() => hashToUnit(node.id) * Math.PI * 2, [node.id]); const color = resourceColor(node.type); const richness = resourceRichness(node); useFrame((state) => { const time = state.clock.elapsedTime + animationPhase; if (pulseRef.current) { const pulse = 1 + Math.sin(state.clock.elapsedTime * 4) * 0.08; pulseRef.current.scale.setScalar(isSelected ? pulse : 0.001); } if (swayRef.current) { swayRef.current.rotation.z = Math.sin(time * 1.4) * 0.035; swayRef.current.rotation.x = Math.sin(time * 1.1) * 0.025; } }); return ( { event.stopPropagation(); onSelect(); }} > { event.stopPropagation(); onSelect(); }} > ); } function ResourceModel({ type, swayRef, }: { type: string; swayRef: React.RefObject; }) { if (type === "food") { return ; } if (type === "herbs") { return ; } if (type === "wood") { return ; } if (type === "weapon") { return ; } return ; } function BerryBush({ swayRef }: { swayRef: React.RefObject }) { return ( {/* trunk stub */} {/* foliage blocks */} {/* berries */} ); } function Berry({ x, y, z }: { x: number; y: number; z: number }) { return ( ); } function HerbPatch({ swayRef }: { swayRef: React.RefObject }) { return ( {/* dirt mound */} ); } function HerbSprout({ x, z, height }: { x: number; z: number; height: number }) { return ( ); } function LogPile() { return ( ); } function Log({ x, y, rotationY }: { x: number; y: number; rotationY: number }) { return ( ); } function WeaponCache() { return ( {/* stone block */} {/* sword blade */} {/* axe leaning on the stone */} ); } function SupplyCrate() { return ( ); } export function resourceTitle(type: string): string { if (!type) { return "Resource"; } return `${type.charAt(0).toUpperCase()}${type.slice(1)}`; } export function resourceColor(type: string): string { if (type === "food") { return "#e3c454"; } if (type === "herbs") { return "#58a65c"; } if (type === "wood") { return "#7a5234"; } if (type === "weapon") { return "#b7bec8"; } return "#d9d2aa"; } function resourceRichness(node: ResourceNodeSnapshot): number { const ratio = node.max_amount ? MathUtils.clamp(node.amount / node.max_amount, 0, 1) : MathUtils.clamp(node.amount / 10, 0, 1); return MathUtils.lerp(0.55, 1.15, ratio); }