Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions client/app/src/GameContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1692,6 +1692,7 @@ export function GameProvider({ children }: GameProviderProps) {
console.debug("[GAME EVENT] Ships list", e.payload)
const data = e.payload as Msg.ShipsListMessage
useGameStore.getState().setShips(data.ships)
useGameStore.getState().setCorpMemberShips(data.corp_member_ships ?? [])
useGameStore.getState().resolveFetchPromise("get-my-ships")
break
}
Expand Down
35 changes: 35 additions & 0 deletions client/app/src/components/SectorMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
showLegend?: boolean
coursePlot?: CoursePlot | null
ships?: Array<{ sector: number; ship_name: string; ship_type: string }>
corp_member_ships?: Array<{ sector: number; character_name: string; ship_name: string }>
onNodeClick?: (node: MapSectorNode | null) => void
onNodeEnter?: (node: MapSectorNode) => void
onNodeExit?: (node: MapSectorNode) => void
Expand Down Expand Up @@ -129,6 +130,7 @@
maxDistance = 2,
coursePlot,
ships,
corp_member_ships,
onNodeClick,
onNodeEnter,
onNodeExit,
Expand All @@ -155,6 +157,19 @@
return map
}, [ships])

const corpMemberShipsKey =
corp_member_ships?.map((s) => `${s.sector}:${s.character_name}`).join(",") ?? ""
const corpMemberShipsMap = useMemo(() => {
if (!corp_member_ships || corp_member_ships.length === 0) return undefined
const map = new Map<number, Array<{ character_name: string; ship_name: string }>>()
for (const entry of corp_member_ships) {
const existing = map.get(entry.sector) ?? []
existing.push({ character_name: entry.character_name, ship_name: entry.ship_name })
map.set(entry.sector, existing)
}
return map
}, [corp_member_ships])

// Default center_sector_id to current_sector_id if not provided
const center_sector_id = center_sector_id_prop ?? current_sector_id ?? 0

Expand All @@ -180,6 +195,7 @@
const lastConfigRef = useRef<Omit<SectorMapConfigBase, "center_sector_id"> | null>(null)
const lastCoursePlotRef = useRef<CoursePlot | null | undefined>(coursePlot)
const lastShipsKeyRef = useRef<string>(shipsKey)
const lastCorpMemberShipsKeyRef = useRef<string>(corpMemberShipsKey)
const lastCenterWorldRef = useRef<[number, number] | undefined>(center_world)
const lastFitBoundsWorldRef = useRef<[number, number, number, number] | undefined>(
fit_bounds_world
Expand Down Expand Up @@ -374,6 +390,7 @@
maxDistance,
coursePlot,
ships: shipsMap,
corp_member_ships: corpMemberShipsMap,
})
controllerRef.current = controller
prevCenterSectorIdRef.current = center_sector_id
Expand All @@ -383,6 +400,7 @@
lastConfigRef.current = baseConfig
lastCoursePlotRef.current = coursePlot
lastShipsKeyRef.current = shipsKey
lastCorpMemberShipsKeyRef.current = corpMemberShipsKey
lastCenterWorldRef.current = center_world
lastFitBoundsWorldRef.current = fit_bounds_world
lastMapFitEpochRef.current = mapFitEpoch
Expand All @@ -402,6 +420,7 @@
const configChanged = lastConfigRef.current !== baseConfig
const coursePlotChanged = !courseplotsEqual(lastCoursePlotRef.current, coursePlot)
const shipsChanged = lastShipsKeyRef.current !== shipsKey
const corpMemberShipsChanged = lastCorpMemberShipsKeyRef.current !== corpMemberShipsKey
const centerWorldChanged = !tuplesEqual(lastCenterWorldRef.current, center_world)
const fitBoundsWorldChanged = !tuplesEqual(lastFitBoundsWorldRef.current, fit_bounds_world)
const mapFitEpochChanged = lastMapFitEpochRef.current !== mapFitEpoch
Expand All @@ -416,6 +435,7 @@
!configChanged &&
!coursePlotChanged &&
!shipsChanged &&
!corpMemberShipsChanged &&
!centerWorldChanged &&
!fitBoundsWorldChanged &&
!mapFitEpochChanged &&
Expand Down Expand Up @@ -447,6 +467,7 @@
data: normalizedMapData,
coursePlot,
ships: shipsMap,
corp_member_ships: corpMemberShipsMap,
})

// Determine if a camera reframe is needed
Expand Down Expand Up @@ -481,6 +502,7 @@
maxDistanceOnly ||
needsConfigUpdate ||
shipsChanged ||
corpMemberShipsChanged ||
coursePlotChanged ||
topologyChanged
) {
Expand All @@ -499,10 +521,11 @@
lastConfigRef.current = baseConfig
lastCoursePlotRef.current = coursePlot
lastShipsKeyRef.current = shipsKey
lastCorpMemberShipsKeyRef.current = corpMemberShipsKey
lastCenterWorldRef.current = center_world
lastFitBoundsWorldRef.current = fit_bounds_world
lastMapFitEpochRef.current = mapFitEpoch
}, [

Check warning on line 528 in client/app/src/components/SectorMap.tsx

View workflow job for this annotation

GitHub Actions / Lint & Build

React Hook useEffect has missing dependencies: 'physicalHeight' and 'physicalWidth'. Either include them or remove the dependency array
center_sector_id,
current_sector_id,
normalizedMapData,
Expand All @@ -511,6 +534,8 @@
coursePlot,
shipsKey,
shipsMap,
corpMemberShipsKey,
corpMemberShipsMap,
onMapFetch,
effectiveWidth,
effectiveHeight,
Expand Down Expand Up @@ -643,6 +668,16 @@
}
}

if (prevProps.corp_member_ships !== nextProps.corp_member_ships) {
const prevKey =
prevProps.corp_member_ships?.map((s) => `${s.sector}:${s.character_name}`).join(",") ?? ""
const nextKey =
nextProps.corp_member_ships?.map((s) => `${s.sector}:${s.character_name}`).join(",") ?? ""
if (prevKey !== nextKey) {
return false
}
}

// World-coordinate overrides (zoomMode)
if (!tuplesEqual(prevProps.center_world, nextProps.center_world)) return false
if (!tuplesEqual(prevProps.fit_bounds_world, nextProps.fit_bounds_world)) return false
Expand Down
8 changes: 8 additions & 0 deletions client/app/src/components/panels/BigMapPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ export const BigMapPanel = ({ config }: { config?: MapConfig }) => {
const mapData = useGameStore.use.regional_map_data?.()
const coursePlot = useGameStore.use.course_plot?.()
const ships = useGameStore.use.ships?.()
const corpMemberShips = useGameStore.use.corpMemberShips?.()
const mapCenterSector = useGameStore((state) => state.mapCenterSector)
const mapZoomLevel = useGameStore((state) => state.mapZoomLevel)
const mapCenterWorld = useGameStore((state) => state.mapCenterWorld)
Expand Down Expand Up @@ -165,6 +166,12 @@ export const BigMapPanel = ({ config }: { config?: MapConfig }) => {
ship_type: s.ship_type,
}))

const corpMemberShipSectors = corpMemberShips?.data?.map((s) => ({
sector: s.sector,
character_name: s.character_name,
ship_name: s.ship_name,
}))

// Initial fetch of map data
useEffect(() => {
if (initialFetchRef.current) return
Expand Down Expand Up @@ -274,6 +281,7 @@ export const BigMapPanel = ({ config }: { config?: MapConfig }) => {
onMapFetch={handleMapFetch}
coursePlot={coursePlot ?? null}
ships={shipSectors}
corp_member_ships={corpMemberShipSectors}
center_world={mapCenterWorld}
fit_bounds_world={mapFitBoundsWorld}
mapFitEpoch={mapFitEpoch}
Expand Down
138 changes: 137 additions & 1 deletion client/app/src/fx/map/SectorMapFX.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export type RegionLaneStyleOverrides = Record<string, RegionLaneStyle>

export interface NodeStyles {
current: NodeStyle
corpMember: NodeStyle
visited: NodeStyle
visited_corp: NodeStyle
unvisited: NodeStyle
Expand Down Expand Up @@ -146,6 +147,18 @@ export const DEFAULT_NODE_STYLES: NodeStyles = {
glowColor: "rgba(116,212,255,0.2)",
glowFalloff: 0.6,
},
corpMember: {
fill: "rgba(0,0,0,0)",
border: "rgba(0,0,0,0)",
borderWidth: 0,
borderStyle: "solid",
outline: "none",
outlineWidth: 0,
glow: true,
glowRadius: 90,
glowColor: "rgba(168,85,247,0.35)",
glowFalloff: 0.5,
},
visited: {
fill: "rgba(0,255,0,0.25)",
border: "rgba(0,255,0,1)",
Expand Down Expand Up @@ -528,6 +541,7 @@ export interface SectorMapProps {
maxDistance?: number
coursePlot?: CoursePlot | null
ships?: Map<number, Array<{ ship_name: string; ship_type: string }>>
corp_member_ships?: Map<number, Array<{ character_name: string; ship_name: string }>>
}

export interface CameraState {
Expand Down Expand Up @@ -2139,8 +2153,117 @@ function truncateText(ctx: CanvasRenderingContext2D, text: string, maxWidth: num

type ShipInfo = { ship_name: string; ship_type: string }

type CorpMemberShipInfo = { character_name: string; ship_name: string }

type ShipLabelHitBox = { sectorId: number; x: number; y: number; w: number; h: number }

/** Render purple glow rings under sectors where other corporation members are located. */
function renderCorpMemberGlows(
ctx: CanvasRenderingContext2D,
data: MapData,
scale: number,
config: SectorMapConfigBase,
corpMemberShips: Map<number, CorpMemberShipInfo[]> | undefined
) {
if (!corpMemberShips || corpMemberShips.size === 0) return
const style = config.nodeStyles.corpMember
if (!style.glow || !style.glowRadius || !style.glowColor) return
const currentSectorId = config.current_sector_id
const falloff = style.glowFalloff ?? 0.3

data.forEach((node) => {
if (!corpMemberShips.has(node.id)) return
if (currentSectorId !== undefined && node.id === currentSectorId) return
const world = hexToWorld(node.position[0], node.position[1], scale)
const gradient = ctx.createRadialGradient(
world.x,
world.y,
0,
world.x,
world.y,
style.glowRadius!
)
gradient.addColorStop(0, style.glowColor!)
gradient.addColorStop(falloff, style.glowColor!)
gradient.addColorStop(1, applyAlpha(style.glowColor!, 0))
ctx.save()
ctx.fillStyle = gradient
ctx.beginPath()
ctx.arc(world.x, world.y, style.glowRadius!, 0, Math.PI * 2)
ctx.fill()
ctx.restore()
})
}

/** Render pilot-name labels below sectors that hold corporation-member ships. */
function renderCorpMemberLabels(
ctx: CanvasRenderingContext2D,
data: MapData,
scale: number,
hexSize: number,
width: number,
height: number,
cameraState: CameraState,
config: SectorMapConfigBase,
corpMemberShips: Map<number, CorpMemberShipInfo[]> | undefined
) {
if (!corpMemberShips || corpMemberShips.size === 0) return

const currentSectorId = config.current_sector_id
const fontSize = 10
const padding = 3
const rowGap = 2
const labelOffset = config.sector_label_offset ?? 2
const bgColor = "rgba(88,28,135,0.92)"
const borderColor = "rgba(216,180,254,1)"
const textColor = "#f5f3ff"

ctx.save()
ctx.font = `800 ${fontSize}px ${getCanvasFontFamily(ctx)}`
ctx.textAlign = "center"
ctx.textBaseline = "alphabetic"

data.forEach((node) => {
const members = corpMemberShips.get(node.id)
if (!members || members.length === 0) return
if (currentSectorId !== undefined && node.id === currentSectorId) return

// Anchor at bottom of hex (angle = PI/2 points down in screen space after worldToScreen)
const worldPos = hexToWorld(node.position[0], node.position[1], scale)
const edgeWorldX = worldPos.x
const edgeWorldY = worldPos.y + hexSize
const screenPos = worldToScreen(edgeWorldX, edgeWorldY, width, height, cameraState)

const ascent = fontSize * 0.8
const descent = fontSize * 0.2
const rowHeight = ascent + descent + padding * 2

let cursorY = screenPos.y + labelOffset + ascent + padding

for (const member of members) {
const label = member.character_name.toUpperCase()
const textWidth = ctx.measureText(label).width
const boxX = screenPos.x - textWidth / 2 - padding
const boxY = cursorY - ascent - padding
const boxW = textWidth + padding * 2
const boxH = ascent + descent + padding * 2

ctx.fillStyle = bgColor
ctx.fillRect(boxX, boxY, boxW, boxH)
ctx.strokeStyle = borderColor
ctx.lineWidth = 1
ctx.strokeRect(boxX + 0.5, boxY + 0.5, boxW - 1, boxH - 1)

ctx.fillStyle = textColor
ctx.fillText(label, screenPos.x, cursorY)

cursorY += rowHeight + rowGap
}
})

ctx.restore()
}

/** Render ship count labels at top-left of hexes (compact badges only, skips hovered) */
function renderShipLabels(
ctx: CanvasRenderingContext2D,
Expand Down Expand Up @@ -2499,7 +2622,7 @@ function renderWithCameraStateAndInteraction(
courseAnimationOffset = 0,
shipLabelHitBoxesOut?: ShipLabelHitBox[]
) {
const { width, height, config, coursePlot, ships } = props
const { width, height, config, coursePlot, ships, corp_member_ships } = props
const ctx = setupCanvas(canvas, props, width, height)
if (!ctx) return

Expand Down Expand Up @@ -2550,6 +2673,8 @@ function renderWithCameraStateAndInteraction(
hoveredSectorId
)

renderCorpMemberGlows(ctx, cameraState.filteredData, scale, config, corp_member_ships)

ctx.restore()

const featherSize = Math.min(config.uiStyles.edgeFeather.size, Math.min(width, height) / 2)
Expand Down Expand Up @@ -2651,6 +2776,17 @@ function renderWithCameraStateAndInteraction(
hoveredSectorId,
shipLabelHitBoxesOut
)
renderCorpMemberLabels(
ctx,
cameraState.filteredData,
scale,
hexSize,
width,
height,
cameraState,
config,
corp_member_ships
)
renderPortLabels(
ctx,
cameraState.filteredData,
Expand Down
Loading
Loading