Add: Off-screen indicators
This commit is contained in:
parent
a17146fee9
commit
2df1b5a560
webradar
@ -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]) => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user