From c21f82667ad2964790b7145e25fa1d0267ecc336 Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Mon, 11 Aug 2025 19:20:37 +0200 Subject: [PATCH] feat(RegionMap): implement left-click aimbot and VIA waypoint toggling --- frontend/src/components/RegionMap.tsx | 193 +++++++++++++++++++++++--- 1 file changed, 176 insertions(+), 17 deletions(-) diff --git a/frontend/src/components/RegionMap.tsx b/frontend/src/components/RegionMap.tsx index 02824a9..5440237 100644 --- a/frontend/src/components/RegionMap.tsx +++ b/frontend/src/components/RegionMap.tsx @@ -100,6 +100,12 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho const [charLocs, setCharLocs] = useState>([]); const [focusUntil, setFocusUntil] = useState(null); + // New: selection/aim state for left-click aimbot behavior + const [isSelecting, setIsSelecting] = useState(false); + const [indicatedSystem, setIndicatedSystem] = useState(null); + const selectTimerRef = useRef(null); + const downClientPointRef = useRef<{ x: number; y: number } | null>(null); + // When focusSystem changes, set an expiry 20s in the future useEffect(() => { if (focusSystem) { @@ -296,12 +302,17 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho const handleSystemClick = async (systemName: string) => { if (viaMode) { setViaQueue(prev => { - if (prev.includes(systemName)) return prev; + // toggle behavior: add if missing, remove if present + if (prev.includes(systemName)) { + const next = prev.filter(n => n !== systemName); + toast({ title: 'Waypoint removed', description: systemName }); + return next; + } const next = [...prev, systemName]; + toast({ title: 'Waypoint queued', description: systemName }); return next; }); - console.log('Queued waypoint:', systemName); - toast({ title: 'Waypoint queued', description: systemName }); + console.log('VIA waypoint toggle:', systemName); return; } if (focusSystem === systemName) return; @@ -437,24 +448,136 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho setTempConnection(null); }; + // Helper: convert client to SVG coords + const clientToSvg = (clientX: number, clientY: number) => { + if (!svgRef.current) return { x: 0, y: 0 }; + const pt = svgRef.current.createSVGPoint(); + pt.x = clientX; + pt.y = clientY; + const svgPoint = pt.matrixTransform(svgRef.current.getScreenCTM()!.inverse()); + return { x: svgPoint.x, y: svgPoint.y }; + }; + + // Helper: find nearest system name to SVG point + const findNearestSystem = (svgX: number, svgY: number): string | null => { + if (systems.size === 0) return null; + let nearestName: string | null = null; + let nearestDist2 = Number.POSITIVE_INFINITY; + systems.forEach((_sys, name) => { + const pos = positions[name]; + if (!pos) return; + const dx = pos.x - svgX; + const dy = pos.y - svgY; + const d2 = dx * dx + dy * dy; + if (d2 < nearestDist2) { + nearestDist2 = d2; + nearestName = name; + } + }); + return nearestName; + }; + + const clearSelectTimer = () => { + if (selectTimerRef.current !== null) { + window.clearTimeout(selectTimerRef.current); + selectTimerRef.current = null; + } + }; + + const PAN_THRESHOLD_PX = 6; // movement before starting pan + const handleMouseDown = useCallback((e: React.MouseEvent) => { if (!svgRef.current) return; - setIsPanning(true); + + // record down point (client) and seed pan origin const rect = svgRef.current.getBoundingClientRect(); setLastPanPoint({ x: e.clientX - rect.left, y: e.clientY - rect.top }); - }, []); + downClientPointRef.current = { x: e.clientX, y: e.clientY }; + + // initial indicate nearest system under cursor + const svgPt = clientToSvg(e.clientX, e.clientY); + const near = findNearestSystem(svgPt.x, svgPt.y); + setIndicatedSystem(near); + + // start delayed select mode timer + setIsSelecting(false); + clearSelectTimer(); + selectTimerRef.current = window.setTimeout(() => { + setIsSelecting(true); + }, 1000); + }, [positions, systems]); const handleMouseMove = useCallback((e: React.MouseEvent) => { - if (!isPanning || !svgRef.current) return; - const rect = svgRef.current.getBoundingClientRect(); - const currentPoint = { x: e.clientX - rect.left, y: e.clientY - rect.top }; - const deltaX = (lastPanPoint.x - currentPoint.x) * (viewBox.width / rect.width); - const deltaY = (lastPanPoint.y - currentPoint.y) * (viewBox.height / rect.height); - setViewBox(prev => ({ ...prev, x: prev.x + deltaX, y: prev.y + deltaY })); - setLastPanPoint(currentPoint); - }, [isPanning, lastPanPoint, viewBox.width, viewBox.height]); + // if dragging node, delegate + if (draggingNode) { handleSvgMouseMove(e); return; } - const handleMouseUp = useCallback(() => { setIsPanning(false); }, []); + if (!svgRef.current) return; + + const rect = svgRef.current.getBoundingClientRect(); + + if (isPanning) { + const currentPoint = { x: e.clientX - rect.left, y: e.clientY - rect.top }; + const deltaX = (lastPanPoint.x - currentPoint.x) * (viewBox.width / rect.width); + const deltaY = (lastPanPoint.y - currentPoint.y) * (viewBox.height / rect.height); + setViewBox(prev => ({ ...prev, x: prev.x + deltaX, y: prev.y + deltaY })); + setLastPanPoint(currentPoint); + return; + } + + // determine if we should start panning + const down = downClientPointRef.current; + if (down && !isSelecting) { + const dx = e.clientX - down.x; + const dy = e.clientY - down.y; + const dist2 = dx * dx + dy * dy; + if (dist2 > PAN_THRESHOLD_PX * PAN_THRESHOLD_PX) { + // user intends to pan; cancel selection + clearSelectTimer(); + setIsSelecting(false); + setIndicatedSystem(null); + setIsPanning(true); + // seed pan origin with current + const currentPoint = { x: e.clientX - rect.left, y: e.clientY - rect.top }; + setLastPanPoint(currentPoint); + return; + } + } + + // selection mode: update indicated nearest system as cursor moves + if (isSelecting) { + const svgPt = clientToSvg(e.clientX, e.clientY); + const near = findNearestSystem(svgPt.x, svgPt.y); + setIndicatedSystem(near); + } + }, [draggingNode, isPanning, lastPanPoint, viewBox.width, viewBox.height, isSelecting, positions, systems]); + + const handleMouseUp = useCallback((e?: React.MouseEvent) => { + // if dragging node, delegate + if (draggingNode) { if (e) handleSvgMouseUp(e); return; } + + clearSelectTimer(); + + if (isPanning) { + setIsPanning(false); + return; + } + + // commit selection if any + let target = indicatedSystem; + if (!target && e && svgRef.current) { + const svgPt = clientToSvg(e.clientX, e.clientY); + target = findNearestSystem(svgPt.x, svgPt.y); + } + + if (target) { + handleSystemClick(target); + } + + // reset selection state + setIsSelecting(false); + setIndicatedSystem(null); + downClientPointRef.current = null; + }, [draggingNode, isPanning, indicatedSystem, positions, systems]); const handleWheel = useCallback((e: React.WheelEvent) => { e.preventDefault(); @@ -657,8 +780,8 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho viewBox={`${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}`} className="cursor-grab active:cursor-grabbing" onMouseDown={handleMouseDown} - onMouseMove={(e) => { if (isPanning) { handleMouseMove(e); } else if (draggingNode) { handleSvgMouseMove(e); } }} - onMouseUp={(e) => { if (isPanning) { handleMouseUp(); } else if (draggingNode) { handleSvgMouseUp(e); } }} + onMouseMove={handleMouseMove} + onMouseUp={(e) => handleMouseUp(e)} onMouseLeave={handleMouseUp} onWheel={handleWheel} onDoubleClick={handleMapDoubleClick} @@ -703,7 +826,7 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho id={system.solarSystemName} name={system.solarSystemName} position={positions[system.solarSystemName] || { x: 0, y: 0 }} - onClick={() => handleSystemClick(system.solarSystemName)} + onClick={() => { /* handled at svg-level aimbot commit */ }} onDoubleClick={(e) => handleSystemDoubleClick(e, positions[system.solarSystemName])} onDragStart={(e) => handleNodeDragStart(e, system.solarSystemName)} onDrag={handleSvgMouseMove} @@ -717,6 +840,42 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho /> ))} + {/* VIA waypoints indicator rings */} + {viaMode && viaQueue.map((name) => ( + positions[name] ? ( + + + + ) : null + ))} + + {/* Indicated (aim) system ring */} + {indicatedSystem && positions[indicatedSystem] && ( + + + + + + )} + {/* Character location markers */} {charLocs.map((c, idx) => { const pos = positions[c.solar_system_name];