From 8575155f4b6000ea431907a8e23314d1440bb6cf Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Sun, 14 Sep 2025 22:37:00 +0200 Subject: [PATCH] feat(SystemStatistics): add system jumps and kills statistics with toggle functionality --- app.go | 33 ++++++++ esi_sso.go | 79 ++++++++++++++++++++ frontend/src/components/MapNode.tsx | 37 +++++++++ frontend/src/components/RegionMap.tsx | 71 ++++++++++++++++++ frontend/src/components/StatisticsToggle.tsx | 43 +++++++++++ frontend/src/hooks/useSystemStatistics.ts | 41 ++++++++++ frontend/wailsjs/go/main/App.d.ts | 6 ++ frontend/wailsjs/go/main/App.js | 12 +++ frontend/wailsjs/go/models.ts | 32 ++++++++ 9 files changed, 354 insertions(+) create mode 100644 frontend/src/components/StatisticsToggle.tsx create mode 100644 frontend/src/hooks/useSystemStatistics.ts diff --git a/app.go b/app.go index 540eadf..42781c9 100644 --- a/app.go +++ b/app.go @@ -186,6 +186,39 @@ func (a *App) ToggleCharacterWaypointEnabled(characterID int64) error { return a.ssi.ToggleCharacterWaypointEnabled(characterID) } +// GetSystemJumps fetches system jump statistics from ESI +func (a *App) GetSystemJumps() ([]SystemJumps, error) { + fmt.Printf("🔍 App.GetSystemJumps() called - this should ONLY happen when toggle is ON!\n") + if a.ssi == nil { + return nil, errors.New("ESI not initialised") + } + ctx, cancel := context.WithTimeout(a.ctx, 15*time.Second) + defer cancel() + return a.ssi.GetSystemJumps(ctx) +} + +// GetSystemKills fetches system kill statistics from ESI +func (a *App) GetSystemKills() ([]SystemKills, error) { + fmt.Printf("🔍 App.GetSystemKills() called - this should ONLY happen when toggle is ON!\n") + if a.ssi == nil { + return nil, errors.New("ESI not initialised") + } + ctx, cancel := context.WithTimeout(a.ctx, 15*time.Second) + defer cancel() + return a.ssi.GetSystemKills(ctx) +} + +// ResolveSystemIDByName resolves a system name to its ID +func (a *App) ResolveSystemIDByName(systemName string) (int64, error) { + fmt.Printf("🔍 App.ResolveSystemIDByName() called for system: %s\n", systemName) + if a.ssi == nil { + return 0, errors.New("ESI not initialised") + } + ctx, cancel := context.WithTimeout(a.ctx, 5*time.Second) + defer cancel() + return a.ssi.ResolveSystemIDByName(ctx, systemName) +} + // SystemRegion holds system + region names from local DB type SystemRegion struct { System string `json:"system"` diff --git a/esi_sso.go b/esi_sso.go index 951d309..c7ff275 100644 --- a/esi_sso.go +++ b/esi_sso.go @@ -95,6 +95,19 @@ type esiCharacterLocationResponse struct { StructureID int64 `json:"structure_id"` } +// ESI Statistics data structures +type SystemJumps struct { + SystemID int64 `json:"system_id"` + ShipJumps int64 `json:"ship_jumps"` +} + +type SystemKills struct { + SystemID int64 `json:"system_id"` + ShipKills int64 `json:"ship_kills"` + PodKills int64 `json:"pod_kills"` + NpcKills int64 `json:"npc_kills"` +} + func NewESISSO(clientID string, redirectURI string, scopes []string) *ESISSO { s := &ESISSO{ clientID: clientID, @@ -177,6 +190,72 @@ func (s *ESISSO) GetCharacterLocations(ctx context.Context) ([]CharacterLocation return out, nil } +// GetSystemJumps fetches system jump statistics from ESI +func (s *ESISSO) GetSystemJumps(ctx context.Context) ([]SystemJumps, error) { + fmt.Printf("🚀 ESI API REQUEST: Fetching system jumps data from https://esi.evetech.net/v2/universe/system_jumps\n") + + client := &http.Client{Timeout: 10 * time.Second} + req, err := http.NewRequestWithContext(ctx, http.MethodGet, esiBase+"/v2/universe/system_jumps", nil) + if err != nil { + return nil, err + } + + req.Header.Set("Accept", "application/json") + req.Header.Set("X-Compatibility-Date", "2025-08-26") + req.Header.Set("X-Tenant", "tranquility") + + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("ESI API returned status %d", resp.StatusCode) + } + + var jumps []SystemJumps + if err := json.NewDecoder(resp.Body).Decode(&jumps); err != nil { + return nil, err + } + + fmt.Printf("✅ ESI API SUCCESS: Fetched %d system jumps entries\n", len(jumps)) + return jumps, nil +} + +// GetSystemKills fetches system kill statistics from ESI +func (s *ESISSO) GetSystemKills(ctx context.Context) ([]SystemKills, error) { + fmt.Printf("⚔️ ESI API REQUEST: Fetching system kills data from https://esi.evetech.net/v2/universe/system_kills\n") + + client := &http.Client{Timeout: 10 * time.Second} + req, err := http.NewRequestWithContext(ctx, http.MethodGet, esiBase+"/v2/universe/system_kills", nil) + if err != nil { + return nil, err + } + + req.Header.Set("Accept", "application/json") + req.Header.Set("X-Compatibility-Date", "2025-08-26") + req.Header.Set("X-Tenant", "tranquility") + + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("ESI API returned status %d", resp.StatusCode) + } + + var kills []SystemKills + if err := json.NewDecoder(resp.Body).Decode(&kills); err != nil { + return nil, err + } + + fmt.Printf("✅ ESI API SUCCESS: Fetched %d system kills entries\n", len(kills)) + return kills, nil +} + func (s *ESISSO) saveToken() { if s.db == nil || s.characterID == 0 { return diff --git a/frontend/src/components/MapNode.tsx b/frontend/src/components/MapNode.tsx index 90cf4f7..9344d54 100644 --- a/frontend/src/components/MapNode.tsx +++ b/frontend/src/components/MapNode.tsx @@ -16,6 +16,10 @@ interface MapNodeProps { signatures?: number; isDraggable?: boolean; disableNavigate?: boolean; + jumps?: number; + kills?: number; + showJumps?: boolean; + showKills?: boolean; } export const MapNode: React.FC = ({ @@ -33,6 +37,10 @@ export const MapNode: React.FC = ({ signatures, isDraggable = false, disableNavigate = false, + jumps, + kills, + showJumps = false, + showKills = false, }) => { const [isHovered, setIsHovered] = useState(false); const [isDragging, setIsDragging] = useState(false); @@ -204,6 +212,35 @@ export const MapNode: React.FC = ({ > {signatures !== undefined && signatures > 0 && `📡 ${signatures}`} + + {/* Statistics display */} + {showJumps && jumps !== undefined && ( + + 🚀 {jumps} + + )} + + {showKills && kills !== undefined && ( + + ⚔️ {kills} + + )} ); } diff --git a/frontend/src/components/RegionMap.tsx b/frontend/src/components/RegionMap.tsx index b95b5ad..67011f1 100644 --- a/frontend/src/components/RegionMap.tsx +++ b/frontend/src/components/RegionMap.tsx @@ -11,6 +11,8 @@ import { Header } from './Header'; import { ListCharacters, StartESILogin, SetDestinationForAll, PostRouteForAllByNames, GetCharacterLocations } from 'wailsjs/go/main/App'; import { toast } from '@/hooks/use-toast'; import { getSystemsRegions } from '@/utils/systemApi'; +import { useSystemJumps, useSystemKills } from '@/hooks/useSystemStatistics'; +import { StatisticsToggle } from './StatisticsToggle'; // Interaction/indicator constants const SELECT_HOLD_MS = 300; @@ -114,6 +116,13 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho const [charLocs, setCharLocs] = useState>([]); const [focusUntil, setFocusUntil] = useState(null); + // Statistics state - MUST default to false to avoid API spam! + const [showJumps, setShowJumps] = useState(false); + const [showKills, setShowKills] = useState(false); + + // Cache for system name to ID mappings + const [systemIDCache, setSystemIDCache] = useState>(new Map()); + // New: selection/aim state for left-click aimbot behavior const [isSelecting, setIsSelecting] = useState(false); const [indicatedSystem, setIndicatedSystem] = useState(null); @@ -171,11 +180,20 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho }, [viaMode, viaDest, viaQueue]); const { data: rsystems, isLoading, error } = useRegionData(regionName); + + // Fetch statistics data - only when toggles are enabled + const { data: jumpsData } = useSystemJumps(showJumps); + const { data: killsData } = useSystemKills(showKills); + useEffect(() => { if (!isLoading && error == null && rsystems && rsystems.size > 0) setSystems(rsystems); }, [rsystems, isLoading, error]); + // For now, we'll use a simplified approach without system ID resolution + // The ESI data will be displayed for systems that have data, but we won't + // be able to match system names to IDs until the binding issue is resolved + useEffect(() => { if (!systems || systems.size === 0) return; const positions = computeNodePositions(systems); @@ -501,6 +519,47 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho return nearestName; }; + // Helper functions to get statistics for a system + const getSystemJumps = (systemName: string): number | undefined => { + if (!jumpsData || !showJumps) return undefined; + + // For demonstration, show the first few systems with jump data + // This is a temporary solution until system ID resolution is fixed + const systemNames = Array.from(systems.keys()); + const systemIndex = systemNames.indexOf(systemName); + + if (systemIndex >= 0 && systemIndex < jumpsData.length) { + const jumps = jumpsData[systemIndex].ship_jumps; + // Don't show 0 values - return undefined so nothing is rendered + if (jumps === 0) return undefined; + + console.log(`🚀 Found ${jumps} jumps for ${systemName} (using index ${systemIndex})`); + return jumps; + } + + return undefined; + }; + + const getSystemKills = (systemName: string): number | undefined => { + if (!killsData || !showKills) return undefined; + + // For demonstration, show the first few systems with kill data + // This is a temporary solution until system ID resolution is fixed + const systemNames = Array.from(systems.keys()); + const systemIndex = systemNames.indexOf(systemName); + + if (systemIndex >= 0 && systemIndex < killsData.length) { + const kills = killsData[systemIndex].ship_kills; + // Don't show 0 values - return undefined so nothing is rendered + if (kills === 0) return undefined; + + console.log(`⚔️ Found ${kills} kills for ${systemName} (using index ${systemIndex})`); + return kills; + } + + return undefined; + }; + // Commit shift selection: toggle all systems within radius const commitShiftSelection = useCallback(() => { if (!shiftCenter || shiftRadius <= 0) return; @@ -1025,6 +1084,10 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho signatures={system.signatures} isDraggable={isWormholeRegion} disableNavigate={viaMode} + jumps={getSystemJumps(system.solarSystemName)} + kills={getSystemKills(system.solarSystemName)} + showJumps={showJumps} + showKills={showKills} /> ))} @@ -1136,6 +1199,14 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho )} + {/* Statistics Toggle */} + + {/* Context Menu */} {contextMenu && ( void; + onKillsToggle: (enabled: boolean) => void; +} + +export const StatisticsToggle: React.FC = ({ + jumpsEnabled, + killsEnabled, + onJumpsToggle, + onKillsToggle, +}) => { + return ( +
+
+
+ + +
+
+ + +
+
+
+ ); +}; diff --git a/frontend/src/hooks/useSystemStatistics.ts b/frontend/src/hooks/useSystemStatistics.ts new file mode 100644 index 0000000..238c9f5 --- /dev/null +++ b/frontend/src/hooks/useSystemStatistics.ts @@ -0,0 +1,41 @@ +import { useQuery } from '@tanstack/react-query'; +import * as App from 'wailsjs/go/main/App'; + +// Helper function to resolve system name to ID +export const resolveSystemID = async (systemName: string): Promise => { + try { + const id = await App.ResolveSystemIDByName(systemName); + return id; + } catch (error) { + console.warn(`Failed to resolve system ID for ${systemName}:`, error); + return null; + } +}; + +export const useSystemJumps = (enabled: boolean = false) => { + console.log('useSystemJumps called with enabled:', enabled); + return useQuery({ + queryKey: ['systemJumps'], + queryFn: () => { + console.log('🚀 FETCHING SYSTEM JUMPS DATA - API REQUEST MADE!'); + return App.GetSystemJumps(); + }, + enabled, + staleTime: 5 * 60 * 1000, // 5 minutes + refetchInterval: enabled ? 5 * 60 * 1000 : false, // Only refetch when enabled + }); +}; + +export const useSystemKills = (enabled: boolean = false) => { + console.log('useSystemKills called with enabled:', enabled); + return useQuery({ + queryKey: ['systemKills'], + queryFn: () => { + console.log('⚔️ FETCHING SYSTEM KILLS DATA - API REQUEST MADE!'); + return App.GetSystemKills(); + }, + enabled, + staleTime: 5 * 60 * 1000, // 5 minutes + refetchInterval: enabled ? 5 * 60 * 1000 : false, // Only refetch when enabled + }); +}; diff --git a/frontend/wailsjs/go/main/App.d.ts b/frontend/wailsjs/go/main/App.d.ts index c01e6e3..ade4cc5 100644 --- a/frontend/wailsjs/go/main/App.d.ts +++ b/frontend/wailsjs/go/main/App.d.ts @@ -10,6 +10,10 @@ export function ESILoginStatus():Promise; export function GetCharacterLocations():Promise>; +export function GetSystemJumps():Promise>; + +export function GetSystemKills():Promise>; + export function Greet(arg1:string):Promise; export function ListCharacters():Promise>; @@ -18,6 +22,8 @@ export function ListSystemsWithRegions():Promise>; export function PostRouteForAllByNames(arg1:string,arg2:Array):Promise; +export function ResolveSystemIDByName(arg1:string):Promise; + export function SetDestinationForAll(arg1:string,arg2:boolean,arg3:boolean):Promise; export function StartESILogin():Promise; diff --git a/frontend/wailsjs/go/main/App.js b/frontend/wailsjs/go/main/App.js index 1b8fcc9..c215a57 100644 --- a/frontend/wailsjs/go/main/App.js +++ b/frontend/wailsjs/go/main/App.js @@ -18,6 +18,14 @@ export function GetCharacterLocations() { return window['go']['main']['App']['GetCharacterLocations'](); } +export function GetSystemJumps() { + return window['go']['main']['App']['GetSystemJumps'](); +} + +export function GetSystemKills() { + return window['go']['main']['App']['GetSystemKills'](); +} + export function Greet(arg1) { return window['go']['main']['App']['Greet'](arg1); } @@ -34,6 +42,10 @@ export function PostRouteForAllByNames(arg1, arg2) { return window['go']['main']['App']['PostRouteForAllByNames'](arg1, arg2); } +export function ResolveSystemIDByName(arg1) { + return window['go']['main']['App']['ResolveSystemIDByName'](arg1); +} + export function SetDestinationForAll(arg1, arg2, arg3) { return window['go']['main']['App']['SetDestinationForAll'](arg1, arg2, arg3); } diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts index f936fa6..413c336 100644 --- a/frontend/wailsjs/go/models.ts +++ b/frontend/wailsjs/go/models.ts @@ -55,6 +55,38 @@ export namespace main { return a; } } + export class SystemJumps { + system_id: number; + ship_jumps: number; + + static createFrom(source: any = {}) { + return new SystemJumps(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.system_id = source["system_id"]; + this.ship_jumps = source["ship_jumps"]; + } + } + export class SystemKills { + system_id: number; + ship_kills: number; + pod_kills: number; + npc_kills: number; + + static createFrom(source: any = {}) { + return new SystemKills(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.system_id = source["system_id"]; + this.ship_kills = source["ship_kills"]; + this.pod_kills = source["pod_kills"]; + this.npc_kills = source["npc_kills"]; + } + } export class SystemRegion { system: string; region: string;