Add: Off-screen indicators
This commit is contained in:
		| @@ -43,6 +43,10 @@ | |||||||
|                     <input type="checkbox" onclick="toggleHealth()" id="healthCheck" name="health" checked /> |                     <input type="checkbox" onclick="toggleHealth()" id="healthCheck" name="health" checked /> | ||||||
|                     <label for="healthCheck">Display Health</label> |                     <label for="healthCheck">Display Health</label> | ||||||
|                 </div> |                 </div> | ||||||
|  |                 <div> | ||||||
|  |                     <input type="checkbox" onclick="toggleOffscreenIndicators()" id="offscreenCheck" name="offscreen" checked /> | ||||||
|  |                     <label for="offscreenCheck">Off-screen Indicators</label> | ||||||
|  |                 </div> | ||||||
|                 <div> |                 <div> | ||||||
|                     <input type="checkbox" onclick="toggleRotate()" id="rotateCheck" name="rotate" checked /> |                     <input type="checkbox" onclick="toggleRotate()" id="rotateCheck" name="rotate" checked /> | ||||||
|                     <label for="rotateCheck">Rotate Map</label> |                     <label for="rotateCheck">Rotate Map</label> | ||||||
|   | |||||||
| @@ -5,13 +5,17 @@ const enemyColor = "#ec040b" | |||||||
| const bombColor = "#eda338" | const bombColor = "#eda338" | ||||||
| const textColor = "#d1d1d1" | const textColor = "#d1d1d1" | ||||||
|  |  | ||||||
|  | const DEFAULT_TEXT_SIZE = 1.2; | ||||||
|  | const DEFAULT_ENTITY_SIZE = 1.5; | ||||||
|  | const DEFAULT_ZOOM_LEVEL = 1.3; | ||||||
|  |  | ||||||
| // Settings | // Settings | ||||||
| let shouldZoom = false; | let shouldZoom = false; | ||||||
| let rotateMap = true; | let rotateMap = true; | ||||||
| let playerCentered = true; | let playerCentered = true; | ||||||
|  | let showOffscreenIndicators = true; | ||||||
|  |  | ||||||
| let drawHealth = true; | let drawHealth = true; | ||||||
|  |  | ||||||
| let drawStats = true; | let drawStats = true; | ||||||
| let drawNames = true; | let drawNames = true; | ||||||
| let drawGuns = true; | let drawGuns = true; | ||||||
| @@ -22,10 +26,7 @@ let minTextSize = 16; | |||||||
| let minEntitySize = 10; | let minEntitySize = 10; | ||||||
| let textSizeMultiplier = 1.0; | let textSizeMultiplier = 1.0; | ||||||
| let entitySizeMultiplier = 1.0; | let entitySizeMultiplier = 1.0; | ||||||
|  | let playerCenteredZoom = 1.0; | ||||||
| const DEFAULT_TEXT_SIZE = 0.4; |  | ||||||
| const DEFAULT_ENTITY_SIZE = 1.2; |  | ||||||
| const DEFAULT_ZOOM_LEVEL = 2.4; |  | ||||||
|  |  | ||||||
| const NETWORK_SETTINGS = { | const NETWORK_SETTINGS = { | ||||||
|     useInterpolation: true, |     useInterpolation: true, | ||||||
| @@ -53,6 +54,7 @@ let lastKnownPositions = {}; | |||||||
| let entityInterpolationData = {}; | let entityInterpolationData = {}; | ||||||
| let lastUpdateTime = 0; | let lastUpdateTime = 0; | ||||||
| let networkLatencyHistory = []; | let networkLatencyHistory = []; | ||||||
|  | let lastPingSent = 0; | ||||||
|  |  | ||||||
| let focusedPlayerYaw = 0; | let focusedPlayerYaw = 0; | ||||||
| let focusedPlayerName = "YOU"; | let focusedPlayerName = "YOU"; | ||||||
| @@ -534,6 +536,11 @@ function updateZoomSliderVisibility() { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function toggleOffscreenIndicators() { | ||||||
|  |     showOffscreenIndicators = !showOffscreenIndicators; | ||||||
|  |     localStorage.setItem('showOffscreenIndicators', showOffscreenIndicators ? 'true' : 'false'); | ||||||
|  | } | ||||||
|  |  | ||||||
| function drawPlayerHealth(pos, playerType, health, hasBomb) { | function drawPlayerHealth(pos, playerType, health, hasBomb) { | ||||||
|     if (!map) return; |     if (!map) return; | ||||||
|  |  | ||||||
| @@ -589,6 +596,8 @@ function drawEntities() { | |||||||
|         height: canvas.height + 100 |         height: canvas.height + 100 | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|  |     const offscreenEnemies = []; | ||||||
|  |  | ||||||
|     entityData.forEach((entity, index) => { |     entityData.forEach((entity, index) => { | ||||||
|         const entityId = `entity_${index}`; |         const entityId = `entity_${index}`; | ||||||
|         let interpolatedEntity = null; |         let interpolatedEntity = null; | ||||||
| @@ -620,80 +629,178 @@ function drawEntities() { | |||||||
|             mapPos.y >= clipRect.y && |             mapPos.y >= clipRect.y && | ||||||
|             mapPos.y <= clipRect.y + clipRect.height; |             mapPos.y <= clipRect.y + clipRect.height; | ||||||
|  |  | ||||||
|         if (!isVisible) return; |         if (!isVisible && renderEntity.Player && | ||||||
|  |             renderEntity.Player.playerType === "Enemy" && | ||||||
|  |             playerCentered && showOffscreenIndicators) { | ||||||
|  |  | ||||||
|         if (renderEntity.Bomb) { |             offscreenEnemies.push({ | ||||||
|             drawBomb(renderEntity.Bomb.pos, renderEntity.Bomb.isPlanted); |                 pos: mapPos, | ||||||
|         } else if (renderEntity.Player) { |                 originalPos: pos, | ||||||
|             const player = renderEntity.Player; |                 player: renderEntity.Player | ||||||
|             let fillStyle = localColor; |             }); | ||||||
|  |         } | ||||||
|  |  | ||||||
|             switch (player.playerType) { |         if (isVisible) { | ||||||
|                 case "Team": fillStyle = teamColor; break; |             if (renderEntity.Bomb) { | ||||||
|                 case "Enemy": fillStyle = enemyColor; break; |                 drawBomb(renderEntity.Bomb.pos, renderEntity.Bomb.isPlanted); | ||||||
|             } |             } else if (renderEntity.Player) { | ||||||
|  |                 const player = renderEntity.Player; | ||||||
|  |                 let fillStyle = localColor; | ||||||
|  |  | ||||||
|             drawEntity( |                 switch (player.playerType) { | ||||||
|                 player.pos, |                     case "Team": fillStyle = teamColor; break; | ||||||
|                 fillStyle, |                     case "Enemy": fillStyle = enemyColor; break; | ||||||
|                 player.isDormant, |  | ||||||
|                 player.hasBomb, |  | ||||||
|                 player.yaw, |  | ||||||
|                 player.hasAwp, |  | ||||||
|                 player.playerType, |  | ||||||
|                 player.isScoped, |  | ||||||
|                 player.playerName, |  | ||||||
|                 false, |  | ||||||
|                 player.weaponId |  | ||||||
|             ); |  | ||||||
|  |  | ||||||
|             if (!player.isDormant) { |  | ||||||
|                 if (drawNames) { |  | ||||||
|                     drawPlayerName( |  | ||||||
|                         player.pos, |  | ||||||
|                         player.playerName, |  | ||||||
|                         player.playerType, |  | ||||||
|                         player.hasAwp, |  | ||||||
|                         player.hasBomb, |  | ||||||
|                         player.isScoped |  | ||||||
|                     ); |  | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 if (drawGuns) { |                 drawEntity( | ||||||
|                     drawPlayerWeapon( |                     player.pos, | ||||||
|                         player.pos, |                     fillStyle, | ||||||
|                         player.playerType, |                     player.isDormant, | ||||||
|                         player.weaponId |                     player.hasBomb, | ||||||
|                     ); |                     player.yaw, | ||||||
|                 } |                     player.hasAwp, | ||||||
|  |                     player.playerType, | ||||||
|  |                     player.isScoped, | ||||||
|  |                     player.playerName, | ||||||
|  |                     false, | ||||||
|  |                     player.weaponId | ||||||
|  |                 ); | ||||||
|  |  | ||||||
|                 if (player.hasBomb) { |                 if (!player.isDormant) { | ||||||
|                     drawPlayerBomb( |                     if (drawNames) { | ||||||
|                         player.pos, |                         drawPlayerName( | ||||||
|                         player.playerType |                             player.pos, | ||||||
|                     ); |                             player.playerName, | ||||||
|                 } |                             player.playerType, | ||||||
|  |                             player.hasAwp, | ||||||
|  |                             player.hasBomb, | ||||||
|  |                             player.isScoped | ||||||
|  |                         ); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|                 if (drawMoney && typeof player.money === 'number') { |                     if (drawGuns) { | ||||||
|                     drawPlayerMoney( |                         drawPlayerWeapon( | ||||||
|                         player.pos, |                             player.pos, | ||||||
|                         player.playerType, |                             player.playerType, | ||||||
|                         player.money, |                             player.weaponId | ||||||
|                         player.hasBomb |                         ); | ||||||
|                     ); |                     } | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 if (drawHealth && typeof player.health === 'number') { |                     if (player.hasBomb) { | ||||||
|                     drawPlayerHealth( |                         drawPlayerBomb( | ||||||
|                         player.pos, |                             player.pos, | ||||||
|                         player.playerType, |                             player.playerType | ||||||
|                         player.health, |                         ); | ||||||
|                         player.hasBomb |                     } | ||||||
|                     ); |  | ||||||
|  |                     if (drawMoney && typeof player.money === 'number') { | ||||||
|  |                         drawPlayerMoney( | ||||||
|  |                             player.pos, | ||||||
|  |                             player.playerType, | ||||||
|  |                             player.money, | ||||||
|  |                             player.hasBomb | ||||||
|  |                         ); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     if (drawHealth && typeof player.health === 'number') { | ||||||
|  |                         drawPlayerHealth( | ||||||
|  |                             player.pos, | ||||||
|  |                             player.playerType, | ||||||
|  |                             player.health, | ||||||
|  |                             player.hasBomb | ||||||
|  |                         ); | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  |     if (playerCentered && showOffscreenIndicators) { | ||||||
|  |         drawOffscreenIndicators(offscreenEnemies); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function drawOffscreenIndicators(offscreenEnemies) { | ||||||
|  |     if (!offscreenEnemies.length) return; | ||||||
|  |  | ||||||
|  |     const centerX = canvas.width / 2; | ||||||
|  |     const centerY = canvas.height / 2; | ||||||
|  |     const padding = 40; | ||||||
|  |  | ||||||
|  |     offscreenEnemies.forEach(enemy => { | ||||||
|  |         const dx = enemy.pos.x - centerX; | ||||||
|  |         const dy = enemy.pos.y - centerY; | ||||||
|  |         let angle = Math.atan2(dy, dx); | ||||||
|  |  | ||||||
|  |         const indicatorRadius = Math.min(canvas.width, canvas.height) / 2 - padding; | ||||||
|  |         let indicatorX = centerX + Math.cos(angle) * indicatorRadius; | ||||||
|  |         let indicatorY = centerY + Math.sin(angle) * indicatorRadius; | ||||||
|  |  | ||||||
|  |         const distance = Math.sqrt(dx * dx + dy * dy); | ||||||
|  |         const maxDistance = Math.sqrt(canvas.width * canvas.width + canvas.height * canvas.height); | ||||||
|  |         const opacity = 0.4 + (1 - Math.min(distance / maxDistance, 1)) * 0.6; | ||||||
|  |  | ||||||
|  |         drawOffscreenIndicator( | ||||||
|  |             indicatorX, | ||||||
|  |             indicatorY, | ||||||
|  |             angle, | ||||||
|  |             enemyColor, | ||||||
|  |             opacity, | ||||||
|  |             enemy.player | ||||||
|  |         ); | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function drawOffscreenIndicator(x, y, angle, color, opacity, player) { | ||||||
|  |     const size = 14 * entitySizeMultiplier; | ||||||
|  |  | ||||||
|  |     ctx.save(); | ||||||
|  |     ctx.translate(x, y); | ||||||
|  |     ctx.rotate(angle); | ||||||
|  |  | ||||||
|  |     ctx.beginPath(); | ||||||
|  |     ctx.moveTo(size, 0); | ||||||
|  |     ctx.lineTo(-size / 2, -size / 2); | ||||||
|  |     ctx.lineTo(-size / 2, size / 2); | ||||||
|  |     ctx.closePath(); | ||||||
|  |  | ||||||
|  |     const r = parseInt(color.slice(1, 3), 16); | ||||||
|  |     const g = parseInt(color.slice(3, 5), 16); | ||||||
|  |     const b = parseInt(color.slice(5, 7), 16); | ||||||
|  |  | ||||||
|  |     ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${opacity})`; | ||||||
|  |     ctx.fill(); | ||||||
|  |  | ||||||
|  |     ctx.strokeStyle = 'black'; | ||||||
|  |     ctx.lineWidth = 1; | ||||||
|  |     ctx.stroke(); | ||||||
|  |  | ||||||
|  |     if (player.hasAwp) { | ||||||
|  |         ctx.beginPath(); | ||||||
|  |         ctx.arc(-size / 2, 0, size / 4, 0, Math.PI * 2); | ||||||
|  |         ctx.fillStyle = 'orange'; | ||||||
|  |         ctx.fill(); | ||||||
|  |         ctx.stroke(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (drawHealth && typeof player.health === 'number') { | ||||||
|  |         let healthColor; | ||||||
|  |         if (player.health > 70) { | ||||||
|  |             healthColor = "#32CD32"; | ||||||
|  |         } else if (player.health > 30) { | ||||||
|  |             healthColor = "#FFFF00"; | ||||||
|  |         } else { | ||||||
|  |             healthColor = "#FF0000"; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         ctx.beginPath(); | ||||||
|  |         ctx.arc(-size / 2, -size / 2, size / 4, 0, Math.PI * 2); | ||||||
|  |         ctx.fillStyle = healthColor; | ||||||
|  |         ctx.fill(); | ||||||
|  |         ctx.stroke(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     ctx.restore(); | ||||||
| } | } | ||||||
|  |  | ||||||
| function drawBombTimer() { | function drawBombTimer() { | ||||||
| @@ -1367,6 +1474,9 @@ addEventListener("DOMContentLoaded", () => { | |||||||
|     const savedEntitySize = localStorage.getItem('entitySizeMultiplier'); |     const savedEntitySize = localStorage.getItem('entitySizeMultiplier'); | ||||||
|     entitySizeMultiplier = savedEntitySize !== null ? parseFloat(savedEntitySize) : DEFAULT_ENTITY_SIZE; |     entitySizeMultiplier = savedEntitySize !== null ? parseFloat(savedEntitySize) : DEFAULT_ENTITY_SIZE; | ||||||
|  |  | ||||||
|  |     const savedOffscreenIndicators = localStorage.getItem('showOffscreenIndicators'); | ||||||
|  |     showOffscreenIndicators = savedOffscreenIndicators !== null ? savedOffscreenIndicators === 'true' : true; | ||||||
|  |  | ||||||
|     const checkboxes = { |     const checkboxes = { | ||||||
|         "zoomCheck": false, |         "zoomCheck": false, | ||||||
|         "statsCheck": true, |         "statsCheck": true, | ||||||
| @@ -1376,7 +1486,8 @@ addEventListener("DOMContentLoaded", () => { | |||||||
|         "moneyReveal": false, |         "moneyReveal": false, | ||||||
|         "rotateCheck": true, |         "rotateCheck": true, | ||||||
|         "centerCheck": true, |         "centerCheck": true, | ||||||
|         "healthCheck": drawHealth |         "healthCheck": drawHealth, | ||||||
|  |         "offscreenCheck": showOffscreenIndicators | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     Object.entries(checkboxes).forEach(([id, state]) => { |     Object.entries(checkboxes).forEach(([id, state]) => { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user