Add: Off-screen indicators

This commit is contained in:
Wizzard 2025-03-17 18:09:06 -04:00
parent a17146fee9
commit 2df1b5a560
2 changed files with 183 additions and 68 deletions

@ -43,6 +43,10 @@
<input type="checkbox" onclick="toggleHealth()" id="healthCheck" name="health" checked />
<label for="healthCheck">Display Health</label>
</div>
<div>
<input type="checkbox" onclick="toggleOffscreenIndicators()" id="offscreenCheck" name="offscreen" checked />
<label for="offscreenCheck">Off-screen Indicators</label>
</div>
<div>
<input type="checkbox" onclick="toggleRotate()" id="rotateCheck" name="rotate" checked />
<label for="rotateCheck">Rotate Map</label>

@ -5,13 +5,17 @@ const enemyColor = "#ec040b"
const bombColor = "#eda338"
const textColor = "#d1d1d1"
const DEFAULT_TEXT_SIZE = 1.2;
const DEFAULT_ENTITY_SIZE = 1.5;
const DEFAULT_ZOOM_LEVEL = 1.3;
// Settings
let shouldZoom = false;
let rotateMap = true;
let playerCentered = true;
let showOffscreenIndicators = true;
let drawHealth = true;
let drawStats = true;
let drawNames = true;
let drawGuns = true;
@ -22,10 +26,7 @@ let minTextSize = 16;
let minEntitySize = 10;
let textSizeMultiplier = 1.0;
let entitySizeMultiplier = 1.0;
const DEFAULT_TEXT_SIZE = 0.4;
const DEFAULT_ENTITY_SIZE = 1.2;
const DEFAULT_ZOOM_LEVEL = 2.4;
let playerCenteredZoom = 1.0;
const NETWORK_SETTINGS = {
useInterpolation: true,
@ -53,6 +54,7 @@ let lastKnownPositions = {};
let entityInterpolationData = {};
let lastUpdateTime = 0;
let networkLatencyHistory = [];
let lastPingSent = 0;
let focusedPlayerYaw = 0;
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) {
if (!map) return;
@ -589,6 +596,8 @@ function drawEntities() {
height: canvas.height + 100
};
const offscreenEnemies = [];
entityData.forEach((entity, index) => {
const entityId = `entity_${index}`;
let interpolatedEntity = null;
@ -620,80 +629,178 @@ function drawEntities() {
mapPos.y >= clipRect.y &&
mapPos.y <= clipRect.y + clipRect.height;
if (!isVisible) return;
if (!isVisible && renderEntity.Player &&
renderEntity.Player.playerType === "Enemy" &&
playerCentered && showOffscreenIndicators) {
if (renderEntity.Bomb) {
drawBomb(renderEntity.Bomb.pos, renderEntity.Bomb.isPlanted);
} else if (renderEntity.Player) {
const player = renderEntity.Player;
let fillStyle = localColor;
offscreenEnemies.push({
pos: mapPos,
originalPos: pos,
player: renderEntity.Player
});
}
switch (player.playerType) {
case "Team": fillStyle = teamColor; break;
case "Enemy": fillStyle = enemyColor; break;
}
if (isVisible) {
if (renderEntity.Bomb) {
drawBomb(renderEntity.Bomb.pos, renderEntity.Bomb.isPlanted);
} else if (renderEntity.Player) {
const player = renderEntity.Player;
let fillStyle = localColor;
drawEntity(
player.pos,
fillStyle,
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
);
switch (player.playerType) {
case "Team": fillStyle = teamColor; break;
case "Enemy": fillStyle = enemyColor; break;
}
if (drawGuns) {
drawPlayerWeapon(
player.pos,
player.playerType,
player.weaponId
);
}
drawEntity(
player.pos,
fillStyle,
player.isDormant,
player.hasBomb,
player.yaw,
player.hasAwp,
player.playerType,
player.isScoped,
player.playerName,
false,
player.weaponId
);
if (player.hasBomb) {
drawPlayerBomb(
player.pos,
player.playerType
);
}
if (!player.isDormant) {
if (drawNames) {
drawPlayerName(
player.pos,
player.playerName,
player.playerType,
player.hasAwp,
player.hasBomb,
player.isScoped
);
}
if (drawMoney && typeof player.money === 'number') {
drawPlayerMoney(
player.pos,
player.playerType,
player.money,
player.hasBomb
);
}
if (drawGuns) {
drawPlayerWeapon(
player.pos,
player.playerType,
player.weaponId
);
}
if (drawHealth && typeof player.health === 'number') {
drawPlayerHealth(
player.pos,
player.playerType,
player.health,
player.hasBomb
);
if (player.hasBomb) {
drawPlayerBomb(
player.pos,
player.playerType
);
}
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() {
@ -1367,6 +1474,9 @@ addEventListener("DOMContentLoaded", () => {
const savedEntitySize = localStorage.getItem('entitySizeMultiplier');
entitySizeMultiplier = savedEntitySize !== null ? parseFloat(savedEntitySize) : DEFAULT_ENTITY_SIZE;
const savedOffscreenIndicators = localStorage.getItem('showOffscreenIndicators');
showOffscreenIndicators = savedOffscreenIndicators !== null ? savedOffscreenIndicators === 'true' : true;
const checkboxes = {
"zoomCheck": false,
"statsCheck": true,
@ -1376,7 +1486,8 @@ addEventListener("DOMContentLoaded", () => {
"moneyReveal": false,
"rotateCheck": true,
"centerCheck": true,
"healthCheck": drawHealth
"healthCheck": drawHealth,
"offscreenCheck": showOffscreenIndicators
};
Object.entries(checkboxes).forEach(([id, state]) => {