2 Commits

2 changed files with 26 additions and 19 deletions

7
app.go
View File

@@ -35,7 +35,8 @@ func (a *App) startup(ctx context.Context) {
redirectURI = "http://localhost:8080/callback" redirectURI = "http://localhost:8080/callback"
} }
a.ssi = NewESISSO(clientID, redirectURI, []string{"esi-ui.write_waypoint.v1"}) // Add location read scope so we can fetch character locations
a.ssi = NewESISSO(clientID, redirectURI, []string{"esi-ui.write_waypoint.v1", "esi-location.read_location.v1"})
} }
// Greet returns a greeting for the given name // Greet returns a greeting for the given name
@@ -130,7 +131,9 @@ func (a *App) ListCharacters() ([]CharacterInfo, error) {
// GetCharacterLocations exposes current locations for all characters // GetCharacterLocations exposes current locations for all characters
func (a *App) GetCharacterLocations() ([]CharacterLocation, error) { func (a *App) GetCharacterLocations() ([]CharacterLocation, error) {
if a.ssi == nil { return nil, errors.New("ESI not initialised") } if a.ssi == nil {
return nil, errors.New("ESI not initialised")
}
ctx, cancel := context.WithTimeout(a.ctx, 6*time.Second) ctx, cancel := context.WithTimeout(a.ctx, 6*time.Second)
defer cancel() defer cancel()
return a.ssi.GetCharacterLocations(ctx) return a.ssi.GetCharacterLocations(ctx)

View File

@@ -133,23 +133,23 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
setPositions(positions); setPositions(positions);
const connections = computeNodeConnections(systems); const connections = computeNodeConnections(systems);
setConnections(connections); setConnections(connections);
// Compute per-system mean inbound angle from in-region neighbors // Compute per-system mean outbound BEARING (0=north, clockwise positive) to in-region neighbors
const angleMap: Record<string, number> = {}; const angleMap: Record<string, number> = {};
systems.forEach((sys, name) => { systems.forEach((sys, name) => {
const neighbors = (sys.connectedSystems || '').split(',').map(s => s.trim()).filter(Boolean); const neighbors = (sys.connectedSystems || '').split(',').map(s => s.trim()).filter(Boolean);
let sumX = 0, sumY = 0, count = 0; let sumSin = 0, sumCos = 0, count = 0;
for (const n of neighbors) { for (const n of neighbors) {
const neighbor = systems.get(n); const neighbor = systems.get(n);
if (!neighbor) continue; if (!neighbor) continue;
const ax = sys.x - neighbor.x; const dx = neighbor.x - sys.x;
const ay = sys.y - neighbor.y; // vector pointing into this system const dy = neighbor.y - sys.y; // screen coords (y down)
const a = Math.atan2(ay, ax); const bearing = Math.atan2(dx, -dy); // bearing relative to north
sumX += Math.cos(a); sumSin += Math.sin(bearing);
sumY += Math.sin(a); sumCos += Math.cos(bearing);
count++; count++;
} }
if (count > 0) { if (count > 0) {
angleMap[name] = Math.atan2(sumY, sumX); angleMap[name] = Math.atan2(sumSin, sumCos); // average bearing
} }
}); });
setMeanInboundAngle(angleMap); setMeanInboundAngle(angleMap);
@@ -226,12 +226,14 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
let angle: number | undefined = undefined; let angle: number | undefined = undefined;
const inbound = meanInboundAngle[fromName]; const inbound = meanInboundAngle[fromName];
if (inbound !== undefined) { if (inbound !== undefined) {
angle = inbound + Math.PI; // opposite direction of mean inbound angle = inbound; // mean outbound bearing already computed
} else { } else {
const curPos = universeRegionPosCache.get(regionName); const curPos = universeRegionPosCache.get(regionName);
const toPos = universeRegionPosCache.get(toRegion); const toPos = universeRegionPosCache.get(toRegion);
if (curPos && toPos) { if (curPos && toPos) {
angle = Math.atan2(toPos.y - curPos.y, toPos.x - curPos.x); const dxr = toPos.x - curPos.x;
const dyr = toPos.y - curPos.y;
angle = Math.atan2(dxr, -dyr); // bearing to remote region
} }
} }
if (angle === undefined) { if (angle === undefined) {
@@ -240,6 +242,8 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
for (let i = 0; i < key.length; i++) h = (h * 31 + key.charCodeAt(i)) >>> 0; for (let i = 0; i < key.length; i++) h = (h * 31 + key.charCodeAt(i)) >>> 0;
angle = (h % 360) * (Math.PI / 180); angle = (h % 360) * (Math.PI / 180);
} }
// Flip 180° so indicator points away from existing connections
angle = angle + Math.PI;
const gkey = `${fromName}__${toRegion}`; const gkey = `${fromName}__${toRegion}`;
const prev = grouped.get(gkey); const prev = grouped.get(gkey);
@@ -670,8 +674,8 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
const yoff = -18 - (idx % 3) * 10; // stagger small vertical offsets if multiple in same system const yoff = -18 - (idx % 3) * 10; // stagger small vertical offsets if multiple in same system
return ( return (
<g key={`char-${c.character_id}-${idx}`} transform={`translate(${pos.x}, ${pos.y + yoff})`}> <g key={`char-${c.character_id}-${idx}`} transform={`translate(${pos.x}, ${pos.y + yoff})`}>
<circle r={4} fill="#00d1ff" stroke="#ffffff" strokeWidth={1} /> <rect x={-2} y={-9} width={Math.max(c.character_name.length * 5, 24)} height={14} rx={3} fill="#0f172a" opacity={0.9} stroke="#00d1ff" strokeWidth={1} />
<text x={6} y={3} fontSize={8} fill="#ffffff">{c.character_name}</text> <text x={Math.max(c.character_name.length * 5, 24) / 2 - 2} y={2} textAnchor="middle" fontSize={8} fill="#ffffff">{c.character_name}</text>
</g> </g>
); );
})} })}
@@ -682,8 +686,8 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
if (!pos) return null; if (!pos) return null;
const len = 26; const len = 26;
const r0 = 10; // start just outside node const r0 = 10; // start just outside node
const dx = Math.cos(ind.angle); const dx = Math.sin(ind.angle);
const dy = Math.sin(ind.angle); const dy = -Math.cos(ind.angle);
const x1 = pos.x + dx * r0; const x1 = pos.x + dx * r0;
const y1 = pos.y + dy * r0; const y1 = pos.y + dy * r0;
const x2 = x1 + dx * len; const x2 = x1 + dx * len;