feat(RegionMap): implement left-click aimbot and VIA waypoint toggling
This commit is contained in:
@@ -100,6 +100,12 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
|
||||
const [charLocs, setCharLocs] = useState<Array<{ character_id: number; character_name: string; solar_system_name: string }>>([]);
|
||||
const [focusUntil, setFocusUntil] = useState<number | null>(null);
|
||||
|
||||
// New: selection/aim state for left-click aimbot behavior
|
||||
const [isSelecting, setIsSelecting] = useState(false);
|
||||
const [indicatedSystem, setIndicatedSystem] = useState<string | null>(null);
|
||||
const selectTimerRef = useRef<number | null>(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;
|
||||
// if dragging node, delegate
|
||||
if (draggingNode) { handleSvgMouseMove(e); return; }
|
||||
|
||||
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);
|
||||
}, [isPanning, lastPanPoint, viewBox.width, viewBox.height]);
|
||||
return;
|
||||
}
|
||||
|
||||
const handleMouseUp = useCallback(() => { setIsPanning(false); }, []);
|
||||
// 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] ? (
|
||||
<g key={`via-${name}`} style={{ pointerEvents: 'none' }}>
|
||||
<circle
|
||||
cx={positions[name].x}
|
||||
cy={positions[name].y}
|
||||
r="14"
|
||||
fill="none"
|
||||
stroke="#10b981"
|
||||
strokeWidth="3"
|
||||
opacity="0.9"
|
||||
filter="url(#glow)"
|
||||
/>
|
||||
</g>
|
||||
) : null
|
||||
))}
|
||||
|
||||
{/* Indicated (aim) system ring */}
|
||||
{indicatedSystem && positions[indicatedSystem] && (
|
||||
<g style={{ pointerEvents: 'none' }}>
|
||||
<circle
|
||||
cx={positions[indicatedSystem].x}
|
||||
cy={positions[indicatedSystem].y}
|
||||
r="20"
|
||||
fill="none"
|
||||
stroke="#f59e0b"
|
||||
strokeWidth="3"
|
||||
opacity="0.9"
|
||||
filter="url(#glow)"
|
||||
>
|
||||
<animate attributeName="r" values="18;22;18" dur="1.2s" repeatCount="indefinite" />
|
||||
</circle>
|
||||
</g>
|
||||
)}
|
||||
|
||||
{/* Character location markers */}
|
||||
{charLocs.map((c, idx) => {
|
||||
const pos = positions[c.solar_system_name];
|
||||
|
Reference in New Issue
Block a user