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:
@@ -113,6 +113,75 @@ function drawEnemyShape(e, bodyColor) {
|
||||
break;
|
||||
}
|
||||
|
||||
case 'commander': {
|
||||
// Pentagon body with rank stripes
|
||||
ctx.beginPath();
|
||||
for (let i=0;i<5;i++){const a=(i/5)*Math.PI*2-Math.PI/2;i===0?ctx.moveTo(x+Math.cos(a)*r,y+Math.sin(a)*r):ctx.lineTo(x+Math.cos(a)*r,y+Math.sin(a)*r);}
|
||||
ctx.closePath(); ctx.fill();
|
||||
ctx.strokeStyle='#ffffff44'; ctx.lineWidth=1.5; ctx.stroke();
|
||||
// Rank stripes
|
||||
ctx.strokeStyle='#000000aa'; ctx.lineWidth=1.5;
|
||||
ctx.beginPath(); ctx.moveTo(x-r*0.5,y-r*0.2); ctx.lineTo(x+r*0.5,y-r*0.2); ctx.stroke();
|
||||
ctx.beginPath(); ctx.moveTo(x-r*0.35,y+r*0.1); ctx.lineTo(x+r*0.35,y+r*0.1); ctx.stroke();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'juggernaut': {
|
||||
// Heavy octagon
|
||||
ctx.beginPath();
|
||||
for (let i=0;i<8;i++){const a=(i/8)*Math.PI*2;i===0?ctx.moveTo(x+Math.cos(a)*r,y+Math.sin(a)*r):ctx.lineTo(x+Math.cos(a)*r,y+Math.sin(a)*r);}
|
||||
ctx.closePath(); ctx.fill();
|
||||
ctx.strokeStyle='#ff000066'; ctx.lineWidth=3; ctx.stroke();
|
||||
ctx.strokeStyle='#ffffff22'; ctx.lineWidth=1;
|
||||
ctx.beginPath();
|
||||
for (let i=0;i<8;i++){const a=(i/8)*Math.PI*2;i===0?ctx.moveTo(x+Math.cos(a)*r*0.55,y+Math.sin(a)*r*0.55):ctx.lineTo(x+Math.cos(a)*r*0.55,y+Math.sin(a)*r*0.55);}
|
||||
ctx.closePath(); ctx.stroke();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'siegebreaker': {
|
||||
// Cross / plus shape (large, slow tank)
|
||||
const arm = r * 0.55;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x-arm,y-r); ctx.lineTo(x+arm,y-r);
|
||||
ctx.lineTo(x+arm,y-arm); ctx.lineTo(x+r,y-arm);
|
||||
ctx.lineTo(x+r,y+arm); ctx.lineTo(x+arm,y+arm);
|
||||
ctx.lineTo(x+arm,y+r); ctx.lineTo(x-arm,y+r);
|
||||
ctx.lineTo(x-arm,y+arm); ctx.lineTo(x-r,y+arm);
|
||||
ctx.lineTo(x-r,y-arm); ctx.lineTo(x-arm,y-arm);
|
||||
ctx.closePath(); ctx.fill();
|
||||
ctx.strokeStyle='#ffffff33'; ctx.lineWidth=2; ctx.stroke();
|
||||
// Regen pulse ring when regen is active
|
||||
if (!e.regenPausedUntil || G.frame >= e.regenPausedUntil) {
|
||||
const regenAlpha = 0.15 + 0.15 * Math.sin(t * 3);
|
||||
ctx.strokeStyle = `rgba(136,14,79,${regenAlpha})`;
|
||||
ctx.lineWidth = 3;
|
||||
ctx.beginPath(); ctx.arc(x, y, r * 1.4, 0, Math.PI*2); ctx.stroke();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'echo': {
|
||||
// Twin overlapping rings — split visual
|
||||
const off = r * 0.3;
|
||||
ctx.beginPath(); ctx.arc(x-off, y, r*0.8, 0, Math.PI*2); ctx.fill();
|
||||
ctx.beginPath(); ctx.arc(x+off, y, r*0.8, 0, Math.PI*2); ctx.fill();
|
||||
ctx.strokeStyle='#ffffff44'; ctx.lineWidth=1;
|
||||
ctx.beginPath(); ctx.arc(x-off, y, r*0.8, 0, Math.PI*2); ctx.stroke();
|
||||
ctx.beginPath(); ctx.arc(x+off, y, r*0.8, 0, Math.PI*2); ctx.stroke();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'voidherald': {
|
||||
// Diamond with void inner ring
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, y-r); ctx.lineTo(x+r*0.8, y); ctx.lineTo(x, y+r); ctx.lineTo(x-r*0.8, y);
|
||||
ctx.closePath(); ctx.fill();
|
||||
ctx.strokeStyle=bodyColor+'88'; ctx.lineWidth=1.5;
|
||||
ctx.beginPath(); ctx.arc(x, y, r*0.45, 0, Math.PI*2); ctx.stroke();
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
ctx.beginPath(); ctx.arc(x, y, r, 0, Math.PI*2); ctx.fill();
|
||||
}
|
||||
@@ -122,6 +191,52 @@ function drawEnemyShape(e, bodyColor) {
|
||||
function drawEnemies() {
|
||||
const enemies = G.enemies;
|
||||
|
||||
// Pass 0: Commander plasma links + self-glow (drawn under all bodies)
|
||||
for (const e of enemies) {
|
||||
if (!e.alive || e.defId !== 'commander') continue;
|
||||
const auraRadSq = 80 * 80;
|
||||
const linked = enemies.filter(t => t.alive && t !== e && distSq(e.x, e.y, t.x, t.y) <= auraRadSq);
|
||||
const cap = Math.min(linked.length, 8);
|
||||
|
||||
// Plasma links to each buffed enemy
|
||||
if (cap > 0) {
|
||||
ctx.save();
|
||||
ctx.shadowColor = 'rgba(255,200,50,0.8)';
|
||||
ctx.shadowBlur = 8;
|
||||
ctx.strokeStyle = 'rgba(255,200,50,0.55)';
|
||||
ctx.lineWidth = 1;
|
||||
for (let i = 0; i < cap; i++) {
|
||||
const t = linked[i];
|
||||
const mx = (e.x + t.x) / 2;
|
||||
const my = (e.y + t.y) / 2;
|
||||
const dx = t.x - e.x, dy = t.y - e.y;
|
||||
const len = Math.sqrt(dx * dx + dy * dy) || 1;
|
||||
const perp = Math.sin(G.frame * 0.04 + i * 1.3) * 18;
|
||||
const cpx = mx - (dy / len) * perp;
|
||||
const cpy = my + (dx / len) * perp;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(e.x, e.y);
|
||||
ctx.quadraticCurveTo(cpx, cpy, t.x, t.y);
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.shadowBlur = 0;
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
// Self-glow: pulses large and bright when alone, dims as links form
|
||||
const glowR = linked.length === 0 ? 40 : Math.max(14, 28 - linked.length * 2);
|
||||
const selfAlpha = linked.length === 0
|
||||
? 0.35 + 0.15 * Math.sin(G.frame * 0.05)
|
||||
: Math.max(0.05, 0.18 - linked.length * 0.02);
|
||||
const grd = ctx.createRadialGradient(e.x, e.y, 0, e.x, e.y, glowR);
|
||||
grd.addColorStop(0, `rgba(255,200,50,${selfAlpha.toFixed(2)})`);
|
||||
grd.addColorStop(1, 'rgba(255,200,50,0)');
|
||||
ctx.fillStyle = grd;
|
||||
ctx.beginPath();
|
||||
ctx.arc(e.x, e.y, glowR, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
// Pass 1: elemental glows (no shadow, just gradient blobs)
|
||||
for (const e of enemies) {
|
||||
if (!e.alive || !e.element) continue;
|
||||
@@ -197,6 +312,27 @@ function drawEnemies() {
|
||||
}
|
||||
}
|
||||
|
||||
// Void Herald shield bubble
|
||||
if (e.defId === 'voidherald') {
|
||||
const shieldActive = (e._voidHitsThisFrame || 0) >= 3;
|
||||
if (shieldActive) {
|
||||
ctx.save();
|
||||
ctx.strokeStyle = '#c77dff';
|
||||
ctx.lineWidth = 3;
|
||||
ctx.shadowColor = '#c77dff';
|
||||
ctx.shadowBlur = 18;
|
||||
ctx.beginPath(); ctx.arc(e.x, e.y, e.radius + 5, 0, Math.PI*2); ctx.stroke();
|
||||
ctx.globalAlpha = 0.15;
|
||||
ctx.fillStyle = '#c77dff';
|
||||
ctx.beginPath(); ctx.arc(e.x, e.y, e.radius + 5, 0, Math.PI*2); ctx.fill();
|
||||
ctx.restore();
|
||||
} else {
|
||||
ctx.strokeStyle = 'rgba(124,77,255,0.22)';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath(); ctx.arc(e.x, e.y, e.radius + 4, 0, Math.PI*2); ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
// HP bar — only when damaged
|
||||
if (e.hp < e.maxHp) {
|
||||
const bw = e.radius * 2.4, bh = 3;
|
||||
|
||||
Reference in New Issue
Block a user