package core import ( "math" ) const ( AuInM = 149597870700.0 ) type Edge struct { To int Time float64 Risk float64 Weight float64 } type Graph struct { Stations map[int]*Station SolarSystems map[int]*SolarSystem Ship *Ship Adj map[int][]Edge } func NewGraph(stations map[int]*Station, systems map[int]*SolarSystem, ship *Ship) *Graph { g := &Graph{ Stations: stations, SolarSystems: systems, Ship: ship, Adj: make(map[int][]Edge), } return g } func (g *Graph) AddEdge(u, v int) { // Add only structural adjacency initially g.Adj[u] = append(g.Adj[u], Edge{To: v}) g.Adj[v] = append(g.Adj[v], Edge{To: u}) } // Build compiles all edges based on stations and stargates func (g *Graph) Build(connections [][2]int) { // 1. Intra-system connections stationsBySystem := make(map[int][]int) for id, station := range g.Stations { sysID := station.SolarSystemID stationsBySystem[sysID] = append(stationsBySystem[sysID], id) } for sysID, sysStations := range stationsBySystem { for _, stationID := range sysStations { g.AddEdge(sysID, stationID) } // Direct station to station within same system for i := 0; i < len(sysStations); i++ { for j := i + 1; j < len(sysStations); j++ { g.AddEdge(sysStations[i], sysStations[j]) } } } // 2. Inter-system connections (Stargates) for _, conn := range connections { from, to := conn[0], conn[1] if _, hasFrom := g.Stations[from]; hasFrom { if _, hasTo := g.Stations[to]; hasTo { g.AddEdge(from, to) } } } // 3. Compute time and risk g.calculateWeights() } func (g *Graph) getPosition(nodeID int) Position { if st, ok := g.Stations[nodeID]; ok { return st.Position } return Position{X: 0, Y: 0, Z: 0} } func (g *Graph) calculateWeights() { playerCost := g.Ship.PlayerCost riskCost := g.Ship.RiskCost // We used a simple slice append above, which might have duplicates if A->B and B->A were added twice. // But AddEdge adds undirected, so we need to deduplicate or just overwrite weights carefully. // For simplicity, we just rebuild Adj with weights. newAdj := make(map[int][]Edge) for u, neighbors := range g.Adj { visited := make(map[int]bool) var uniqueEdges []Edge for _, edge := range neighbors { v := edge.To if visited[v] { continue } visited[v] = true time := g.calculateTime(u, v) risk := g.calculateRisk(v) weight := time*playerCost + riskCost*risk uniqueEdges = append(uniqueEdges, Edge{ To: v, Time: time, Risk: risk, Weight: weight, }) } newAdj[u] = uniqueEdges } g.Adj = newAdj } func (g *Graph) calculateTime(fromNode, toNode int) float64 { stFrom, isStFrom := g.Stations[fromNode] stTo, isStTo := g.Stations[toNode] if isStFrom && isStTo && stFrom.SolarSystemID != stTo.SolarSystemID { return 9.0 // Fixed stargate time } posFrom := g.getPosition(fromNode) posTo := g.getPosition(toNode) dx := posFrom.X - posTo.X dy := posFrom.Y - posTo.Y dz := posFrom.Z - posTo.Z warpDist := math.Sqrt(dx*dx + dy*dy + dz*dz) return g.calculateTimeInWarp(warpDist) + 2.0 } func (g *Graph) calculateTimeInWarp(warpDist float64) float64 { kAccel := g.Ship.MaxWarpSpeed kDecel := math.Min(g.Ship.MaxWarpSpeed/3.0, 2.0) warpDropout := math.Min(g.Ship.MaxSubwarpSpeed/2.0, 100.0) maxMsWarpSpeed := g.Ship.MaxWarpSpeed * AuInM accelDist := AuInM decelDist := maxMsWarpSpeed / kDecel minDist := accelDist + decelDist cruiseTime := 0.0 if minDist > warpDist { maxMsWarpSpeed = warpDist * kAccel * kDecel / (kAccel + kDecel) } else { cruiseTime = (warpDist - minDist) / maxMsWarpSpeed } accelTime := math.Log(maxMsWarpSpeed/kAccel) / kAccel decelTime := math.Log(maxMsWarpSpeed/warpDropout) / kDecel return cruiseTime + accelTime + decelTime } func (g *Graph) calculateRisk(toNode int) float64 { security := 1.0 sysName := "" if st, ok := g.Stations[toNode]; ok { security = st.Security sysName = st.SolarSystemName } else if sys, ok := g.SolarSystems[toNode]; ok { // Wait, some hubs don't have security field inside SolarSystem in the python code. // Python fallbacks to 1.0 if not found, since SolarSystem doesn't actually store security directly in Loader. // If we need it, we can fallback to 1.0 security = 1.0 sysName = sys.ItemName } risk := 0.00004 + (1.0-security)*0.0004 if security < 0.5 { risk += 0.005 } if security < 0.1 { risk += 0.5 } if g.Ship.GankersAreas != nil { if penalty, exists := g.Ship.GankersAreas[sysName]; exists { risk += penalty } } return risk }