Add freshness bar, enhance overlays and renderers
- Add enemy freshness tracking (novelty bonus for repeated deploys) - Add freshness bar to sidepanel enemy cards with penalty indicator - Major overhaul of renderer-overlays.js (790+ lines for UI polish) - Enhanced combat log, shop overlays, and inventory UI - Improved weapon/upgrade display with partial ownership colors - Added element icons and weakness/resistance indicators to cards - Enhanced radial menu and tooltip system - Add "stale/%" penalty text when freshness depleted - Update play link to ffazeshift.net in index.html
This commit is contained in:
+65
-50
@@ -7,8 +7,7 @@
|
||||
// Drawn once, re-rendered only on resize — never recomputed each frame
|
||||
let _bgCanvas = null;
|
||||
let _bgW = 0, _bgH = 0;
|
||||
let _fogCanvas = null;
|
||||
let _fogW = 0, _fogH = 0, _fogRange = 0;
|
||||
// per-weapon range rings drawn on alt-hold
|
||||
const _mountDropZones = [];
|
||||
const _bagDropZones = [];
|
||||
const _dragRegions = [];
|
||||
@@ -25,15 +24,23 @@ function buildBackground(W, H) {
|
||||
c.fillStyle = grad;
|
||||
c.fillRect(0, 0, W, H);
|
||||
|
||||
c.strokeStyle = '#0a1520';
|
||||
c.lineWidth = 1;
|
||||
const gs = 48;
|
||||
for (let x = 0; x < W; x += gs) { c.beginPath(); c.moveTo(x, 0); c.lineTo(x, H); c.stroke(); }
|
||||
for (let y = 0; y < H; y += gs) { c.beginPath(); c.moveTo(0, y); c.lineTo(W, y); c.stroke(); }
|
||||
|
||||
_bgW = W; _bgH = H;
|
||||
}
|
||||
|
||||
function drawGrid(cx, cy) {
|
||||
const gs = 48;
|
||||
const x0 = Math.floor((cx - ARENA_RADIUS - gs) / gs) * gs;
|
||||
const x1 = Math.ceil((cx + ARENA_RADIUS + gs) / gs) * gs;
|
||||
const y0 = Math.floor((cy - ARENA_RADIUS - gs) / gs) * gs;
|
||||
const y1 = Math.ceil((cy + ARENA_RADIUS + gs) / gs) * gs;
|
||||
ctx.strokeStyle = '#0a1520';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
for (let x = x0; x <= x1; x += gs) { ctx.moveTo(x, y0); ctx.lineTo(x, y1); }
|
||||
for (let y = y0; y <= y1; y += gs) { ctx.moveTo(x0, y); ctx.lineTo(x1, y); }
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// ── CACHED ELEMENT GLOW GRADIENTS ────────────────────────────
|
||||
// Keyed by "elementId:x:y:radius" — cleared each frame since positions change,
|
||||
// but the gradient itself is reused within a single frame for same-element enemies.
|
||||
@@ -47,66 +54,73 @@ function getElemGrad(el, x, y, r) {
|
||||
}
|
||||
|
||||
// ── ARENA OVERLAY HELPERS ─────────────────────────────────────
|
||||
// Subtle dashed ring at tower's current vision range
|
||||
function drawArenaRangeLine(cx, cy) {
|
||||
const r = G.tower.range;
|
||||
ctx.save();
|
||||
ctx.strokeStyle = 'rgba(0,180,255,0.07)';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.setLineDash([6, 12]);
|
||||
ctx.beginPath();
|
||||
ctx.arc(cx, cy, r, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
ctx.setLineDash([]);
|
||||
ctx.restore();
|
||||
}
|
||||
// Per-weapon range rings — visible while Alt is held
|
||||
const _RANGE_COLORS = ['#00d4ff', '#ff6b35', '#a855f7', '#22c55e', '#f59e0b', '#ec4899'];
|
||||
function drawWeaponRanges(cx, cy) {
|
||||
if (!_altHeld) return;
|
||||
const weapons = (G.weapons || []).filter(w => w);
|
||||
if (!weapons.length) return;
|
||||
const zoom = G?.camera?.zoom ?? 1.0;
|
||||
|
||||
// Fog of war: sharp transition at tower range, flat dark zone beyond it
|
||||
function drawFog(cx, cy) {
|
||||
const r = G.tower.range;
|
||||
const W = canvas.width;
|
||||
const H = canvas.height;
|
||||
if (!_fogCanvas || _fogW !== W || _fogH !== H || _fogRange !== r) {
|
||||
_fogCanvas = document.createElement('canvas');
|
||||
_fogCanvas.width = W;
|
||||
_fogCanvas.height = H;
|
||||
const c = _fogCanvas.getContext('2d');
|
||||
// ponytail: cache the identical fog gradient; rebuild only when range/canvas changes.
|
||||
const fog = c.createRadialGradient(cx, cy, r * 0.94, cx, cy, r * 1.12);
|
||||
fog.addColorStop(0, 'rgba(2,6,12,0)');
|
||||
fog.addColorStop(1, 'rgba(2,6,12,0.62)');
|
||||
c.fillStyle = fog;
|
||||
c.beginPath();
|
||||
c.arc(cx, cy, ARENA_RADIUS, 0, Math.PI * 2);
|
||||
c.fill();
|
||||
_fogW = W;
|
||||
_fogH = H;
|
||||
_fogRange = r;
|
||||
}
|
||||
ctx.drawImage(_fogCanvas, 0, 0);
|
||||
weapons.forEach((w, i) => {
|
||||
const r = w.range ?? 0;
|
||||
if (!r || r >= 9000) return;
|
||||
const color = _RANGE_COLORS[i % _RANGE_COLORS.length];
|
||||
const label = (getWeaponDef(w)?.name ?? w.defId).toUpperCase();
|
||||
|
||||
// Ring drawn in world space — scales naturally with zoom
|
||||
ctx.save();
|
||||
ctx.strokeStyle = color;
|
||||
ctx.globalAlpha = 0.55;
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.setLineDash([8, 10]);
|
||||
ctx.beginPath();
|
||||
ctx.arc(cx, cy, r, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
ctx.setLineDash([]);
|
||||
ctx.restore();
|
||||
|
||||
// Label in screen space — constant size regardless of zoom
|
||||
// cx == ARENA_CX so screen x == cx; screen y = ARENA_CY - (r+6)*zoom
|
||||
const sx = cx;
|
||||
const sy = ARENA_CY - (r + 6) * zoom;
|
||||
ctx.save();
|
||||
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||
ctx.font = 'bold 11px "Share Tech Mono", monospace';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'bottom';
|
||||
const tw = ctx.measureText(label).width;
|
||||
ctx.fillStyle = 'rgba(2,6,14,0.78)';
|
||||
ctx.fillRect(sx - tw / 2 - 4, sy - 13, tw + 8, 14);
|
||||
ctx.fillStyle = color;
|
||||
ctx.globalAlpha = 0.95;
|
||||
ctx.fillText(label, sx, sy);
|
||||
ctx.restore();
|
||||
});
|
||||
}
|
||||
|
||||
// Dark overlay outside the arena circle
|
||||
function drawArenaMask(W, H, cx, cy) {
|
||||
function drawArenaMask(W, H, cx, cy, zoom = 1) {
|
||||
ctx.fillStyle = '#010407';
|
||||
ctx.beginPath();
|
||||
ctx.rect(0, 0, W, H);
|
||||
ctx.arc(cx, cy, ARENA_RADIUS, 0, Math.PI * 2); // same dir as rect → evenodd cancels inside
|
||||
ctx.arc(cx, cy, ARENA_RADIUS * zoom, 0, Math.PI * 2);
|
||||
ctx.fill('evenodd');
|
||||
}
|
||||
|
||||
// Subtle glowing border ring at arena edge
|
||||
function drawArenaRing(cx, cy) {
|
||||
function drawArenaRing(cx, cy, zoom = 1) {
|
||||
const r = ARENA_RADIUS * zoom;
|
||||
ctx.save();
|
||||
ctx.strokeStyle = 'rgba(0,100,180,0.14)';
|
||||
ctx.lineWidth = 10;
|
||||
ctx.beginPath();
|
||||
ctx.arc(cx, cy, ARENA_RADIUS - 5, 0, Math.PI * 2);
|
||||
ctx.arc(cx, cy, r - 5, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
ctx.strokeStyle = 'rgba(0,180,255,0.22)';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.beginPath();
|
||||
ctx.arc(cx, cy, ARENA_RADIUS, 0, Math.PI * 2);
|
||||
ctx.arc(cx, cy, r, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
@@ -145,7 +159,8 @@ function drawStreak(cx, cy) {
|
||||
ctx.fillStyle = color;
|
||||
ctx.shadowColor = color;
|
||||
ctx.shadowBlur = 10;
|
||||
ctx.fillText(text, cx, by + 21);
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(text, cx, by + bh / 2);
|
||||
ctx.shadowBlur = 0;
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user