@ -4480,8 +4480,8 @@
zoom: 13,
zoom: 13,
maxZoom: 22,
maxZoom: 22,
bearing: 0,
bearing: 0,
pitch: 0, // Disable pitch to keep fog of war circles correct
maxPitch: 0, // Prevent user from tilting map (fog doesn't work with perspective)
pitch: 45, // 3D building perspective
maxPitch: 6 0,
doubleClickZoom: false
doubleClickZoom: false
});
});
@ -5125,35 +5125,42 @@
fogCtx.fillStyle = 'rgba(0, 0, 0, 0.6)';
fogCtx.fillStyle = 'rgba(0, 0, 0, 0.6)';
fogCtx.fillRect(0, 0, width, height);
fogCtx.fillRect(0, 0, width, height);
// Helper function to draw a projected circle (handles rotation AND pitch)
function drawProjectedCircle(ctx, centerLngLat, radiusMeters, numPoints = 48) {
const points = [];
for (let i = 0; i < numPoints ; i + + ) {
const angle = (i / numPoints) * 360;
const edgeLngLat = destinationPoint(centerLngLat, radiusMeters, angle);
const edgePoint = map.project(edgeLngLat);
points.push(edgePoint);
}
// Draw the projected shape
ctx.beginPath();
points.forEach((pt, i) => {
if (i === 0) ctx.moveTo(pt.x, pt.y);
else ctx.lineTo(pt.x, pt.y);
});
ctx.closePath();
}
// Homebase reveal (only if homebase exists)
// Homebase reveal (only if homebase exists)
if (playerStats & & playerStats.homeBaseLat != null & & playerStats.homeBaseLng != null) {
if (playerStats & & playerStats.homeBaseLat != null & & playerStats.homeBaseLng != null) {
// Calculate reveal circle center using container coordinates (viewport-relative)
// MapLibre uses [lng, lat] order
const homeLngLat = [playerStats.homeBaseLng, playerStats.homeBaseLat];
const homeLngLat = [playerStats.homeBaseLng, playerStats.homeBaseLat];
const centerPoint = map.project(homeLngLat);
// Calculate radius in pixels using map projection (with multiplier)
// Use actual distance between projected points to handle rotation correctly
const effectiveHomebaseRadius = playerRevealRadius * homebaseRadiusMultiplier;
const effectiveHomebaseRadius = playerRevealRadius * homebaseRadiusMultiplier;
const edgeLngLat = destinationPoint(homeLngLat, effectiveHomebaseRadius, 90);
const edgePoint = map.project(edgeLngLat);
const radiusPixels = Math.hypot(edgePoint.x - centerPoint.x, edgePoint.y - centerPoint.y);
// Cut out revealed area using composite operation
// Cut out revealed area using composite operation
fogCtx.save();
fogCtx.save();
fogCtx.globalCompositeOperation = 'destination-out';
fogCtx.globalCompositeOperation = 'destination-out';
// Create gradient for soft edge
const gradient = fogCtx.createRadialGradient(
centerPoint.x, centerPoint.y, radiusPixels * 0.85,
centerPoint.x, centerPoint.y, radiusPixels
);
gradient.addColorStop(0, 'rgba(0, 0, 0, 1)');
gradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
// Draw outer edge with fade (slightly larger, semi-transparent)
fogCtx.fillStyle = 'rgba(0, 0, 0, 0.5)';
drawProjectedCircle(fogCtx, homeLngLat, effectiveHomebaseRadius * 1.05);
fogCtx.fill();
fogCtx.beginPath();
fogCtx.arc(centerPoint.x, centerPoint.y, radiusPixels, 0, Math.PI * 2) ;
fogCtx.fillStyle = gradient ;
// Draw main revealed area (solid cutout)
fogCtx.fillStyle = 'rgba(0, 0, 0, 1)';
drawProjectedCircle(fogCtx, homeLngLat, effectiveHomebaseRadius * 0.95) ;
fogCtx.fill();
fogCtx.fill();
fogCtx.restore();
fogCtx.restore();
@ -5175,26 +5182,19 @@
}
}
if (isOutsideHomebase) {
if (isOutsideHomebase) {
const playerPoint = map.project(playerLngLat);
const effectiveExploreRadius = playerExploreRadius * exploreRadiusMultiplier;
const effectiveExploreRadius = playerExploreRadius * exploreRadiusMultiplier;
const playerEdge = destinationPoint(playerLngLat, effectiveExploreRadius, 90);
const playerEdgePoint = map.project(playerEdge);
// Use actual distance to handle rotation correctly
const playerRadiusPixels = Math.hypot(playerEdgePoint.x - playerPoint.x, playerEdgePoint.y - playerPoint.y);
fogCtx.save();
fogCtx.save();
fogCtx.globalCompositeOperation = 'destination-out';
fogCtx.globalCompositeOperation = 'destination-out';
const playerGradient = fogCtx.createRadialGradient(
playerPoint.x, playerPoint.y, playerRadiusPixels * 0.85,
playerPoint.x, playerPoint.y, playerRadiusPixels
);
playerGradient.addColorStop(0, 'rgba(0, 0, 0, 1)');
playerGradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
// Draw outer edge with fade (slightly larger, semi-transparent)
fogCtx.fillStyle = 'rgba(0, 0, 0, 0.5)';
drawProjectedCircle(fogCtx, playerLngLat, effectiveExploreRadius * 1.1);
fogCtx.fill();
fogCtx.beginPath();
fogCtx.arc(playerPoint.x, playerPoint.y, playerRadiusPixels, 0, Math.PI * 2) ;
fogCtx.fillStyle = playerGradient ;
// Draw main revealed area (solid cutout)
fogCtx.fillStyle = 'rgba(0, 0, 0, 1)' ;
drawProjectedCircle(fogCtx, playerLngLat, effectiveExploreRadius * 0.85) ;
fogCtx.fill();
fogCtx.fill();
fogCtx.restore();
fogCtx.restore();