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:
+59
-15
@@ -50,10 +50,11 @@ function drawSidePanel() {
|
||||
ctx.font = '13px Orbitron, monospace';
|
||||
ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillStyle = '#ffd700';
|
||||
ctx.fillText('×' + G.sendQuantity, QB_X + QB_W / 2, QB_Y + QB_H / 2);
|
||||
addHitRegion(QB_X, QB_Y, QB_W, QB_H, () => {
|
||||
if (!G.armoryOpen && !G.commandOpen) addHitRegion(QB_X, QB_Y, QB_W, QB_H, () => {
|
||||
const idx = SP_QTY_STEPS.indexOf(G.sendQuantity);
|
||||
G.sendQuantity = SP_QTY_STEPS[(idx + 1) % SP_QTY_STEPS.length];
|
||||
});
|
||||
_tickTooltip('qty', QB_X, QB_Y, QB_W, QB_H);
|
||||
|
||||
ctx.font = '9px "Share Tech Mono", monospace'; ctx.letterSpacing = '2px';
|
||||
ctx.textAlign = 'left'; ctx.textBaseline = 'top'; ctx.fillStyle = '#1a3048';
|
||||
@@ -66,9 +67,15 @@ function drawSidePanel() {
|
||||
|
||||
const qty = G.sendQuantity;
|
||||
const bonusMult = qty >= 50 ? 1.3 : qty >= 25 ? 1.2 : qty >= 10 ? 1.12 : qty >= 5 ? 1.05 : 1.0;
|
||||
const tierDef = DIFFICULTY_TIERS[G.difficultyTier || 0];
|
||||
const tierRewardMult = tierDef?.rewardMult ?? 1;
|
||||
const visibleDefs = ENEMY_DEFS.filter(d =>
|
||||
(d.minTier ?? 0) <= (G.difficultyTier || 0) &&
|
||||
(d.minPrestige ?? 0) <= (G.prestigeLevel || 0)
|
||||
);
|
||||
|
||||
for (let i = 0; i < ENEMY_DEFS.length; i++) {
|
||||
const def = ENEMY_DEFS[i];
|
||||
for (let i = 0; i < visibleDefs.length; i++) {
|
||||
const def = visibleDefs[i];
|
||||
const totalCost = def.cost * qty;
|
||||
const canDeploy = G.credits >= totalCost && !G.gameOver;
|
||||
const cardY = ENEMY_Y + i * (SP_ENEMY_CARD_H + SP_ENEMY_CARD_GAP) - _sidePanelScrollY;
|
||||
@@ -91,7 +98,7 @@ function drawSidePanel() {
|
||||
ctx.fillStyle = elColor;
|
||||
ctx.fillRect(CX, cardY + 5, 3, SP_ENEMY_CARD_H - 10);
|
||||
|
||||
// Freshness bar (5px strip at card top)
|
||||
// Freshness bar (5px strip at card top) — shows novelty bonus remaining
|
||||
const fresh = G.enemyFreshness[def.id] || 0;
|
||||
const freshPct = Math.max(0, 1 - fresh / 17);
|
||||
if (freshPct > 0) {
|
||||
@@ -100,9 +107,24 @@ function drawSidePanel() {
|
||||
ctx.fillStyle = fbG;
|
||||
ctx.fillRect(CX, cardY, CW * freshPct, 5);
|
||||
}
|
||||
if (freshPct < 1) {
|
||||
// Red fill for depleted portion — always visible
|
||||
ctx.fillStyle = '#ff335555';
|
||||
ctx.fillRect(CX + CW * freshPct, cardY, CW * (1 - freshPct), 5);
|
||||
// Penalty text inside the bar (only when meaningfully stale)
|
||||
if (fresh > 3) {
|
||||
const penalty = Math.round((1 - freshPct) * 35);
|
||||
ctx.font = 'bold 9px "Share Tech Mono", monospace';
|
||||
ctx.textAlign = 'right'; ctx.textBaseline = 'middle';
|
||||
ctx.fillStyle = '#ff3355dd';
|
||||
ctx.fillText('-' + penalty + '%', CX + CW - 3, cardY + 2.5);
|
||||
ctx.textAlign = 'left';
|
||||
}
|
||||
}
|
||||
|
||||
// Hotkey
|
||||
const hotkey = i < 9 ? String(i + 1) : i === 9 ? '0' : '';
|
||||
// Hotkey — base 10 enemies keep 1-0, new enemies have no hotkey
|
||||
const baseIndex = ENEMY_DEFS.findIndex(d => d.id === def.id);
|
||||
const hotkey = baseIndex < 9 ? String(baseIndex + 1) : baseIndex === 9 ? '0' : '';
|
||||
ctx.font = '10px Orbitron, monospace'; ctx.letterSpacing = '0px';
|
||||
ctx.textAlign = 'left'; ctx.textBaseline = 'top'; ctx.fillStyle = '#3a6080';
|
||||
ctx.fillText('[' + hotkey + ']', CX + 6, cardY + 8);
|
||||
@@ -126,34 +148,55 @@ function drawSidePanel() {
|
||||
const elIcon = def.element ? (ELEMENTS[def.element]?.icon || '') : '';
|
||||
if (elIcon) statParts.push(elIcon);
|
||||
ctx.save();
|
||||
ctx.beginPath(); ctx.rect(CX + 6, cardY + 26, CW - 12, 16); ctx.clip();
|
||||
ctx.beginPath(); ctx.rect(CX + 6, cardY + 26, CW - 70, 16); ctx.clip();
|
||||
ctx.font = '11px "Share Tech Mono", monospace';
|
||||
ctx.textAlign = 'left'; ctx.textBaseline = 'top'; ctx.fillStyle = '#3a6080';
|
||||
ctx.fillText(statParts.join(' · '), CX + 6, cardY + 26);
|
||||
ctx.restore();
|
||||
|
||||
// Reward + profit
|
||||
const rewardPerUnit = Math.round(def.reward * bonusMult);
|
||||
// Elemental weakness/resistance icons (right side of stats row)
|
||||
ctx.font = '11px monospace'; ctx.textBaseline = 'top';
|
||||
let iconX = CX + CW - 6;
|
||||
const weakEntries = Object.entries(def.weaknesses || {}).filter(([,m]) => m > 1);
|
||||
const resEntries = Object.entries(def.resistances || {}).filter(([,m]) => m < 1);
|
||||
for (const [elId] of weakEntries) {
|
||||
const elD = ELEMENTS[elId]; if (!elD) continue;
|
||||
iconX -= ctx.measureText(elD.icon).width + 2;
|
||||
ctx.fillStyle = '#ff6b35'; ctx.textAlign = 'left';
|
||||
ctx.fillText(elD.icon, iconX, cardY + 26);
|
||||
}
|
||||
for (const [elId] of resEntries) {
|
||||
const elD = ELEMENTS[elId]; if (!elD) continue;
|
||||
iconX -= ctx.measureText(elD.icon).width + 2;
|
||||
ctx.fillStyle = '#1a4060'; ctx.textAlign = 'left';
|
||||
ctx.fillText(elD.icon, iconX, cardY + 26);
|
||||
}
|
||||
|
||||
// Reward + profit (tier-scaled)
|
||||
const rewardPerUnit = Math.round(def.reward * bonusMult * tierRewardMult);
|
||||
const profit = rewardPerUnit - def.cost;
|
||||
const profitStr = (profit >= 0 ? '+' : '') + profit + '¢';
|
||||
ctx.font = '11px "Share Tech Mono", monospace';
|
||||
ctx.textAlign = 'left'; ctx.textBaseline = 'bottom'; ctx.fillStyle = '#00ff88';
|
||||
ctx.fillText('↑ ' + rewardPerUnit + '¢', CX + 6, cardY + SP_ENEMY_CARD_H - 6);
|
||||
ctx.fillStyle = profit >= 0 ? '#00ff88' : '#ff3355';
|
||||
ctx.fillText('(' + profitStr + ')', CX + 60, cardY + SP_ENEMY_CARD_H - 6);
|
||||
if (bonusMult > 1) {
|
||||
ctx.fillText('(' + profitStr + ')', CX + 62, cardY + SP_ENEMY_CARD_H - 6);
|
||||
if (tierRewardMult > 1) {
|
||||
ctx.fillStyle = '#ff6b35'; ctx.font = '9px Orbitron, monospace';
|
||||
ctx.fillText('×' + tierRewardMult.toFixed(1), CX + 124, cardY + SP_ENEMY_CARD_H - 6);
|
||||
} else if (bonusMult > 1) {
|
||||
ctx.fillStyle = '#ffd700'; ctx.font = '9px Orbitron, monospace';
|
||||
ctx.fillText('+' + Math.round((bonusMult - 1) * 100) + '%', CX + 114, cardY + SP_ENEMY_CARD_H - 6);
|
||||
ctx.fillText('+' + Math.round((bonusMult - 1) * 100) + '%', CX + 124, cardY + SP_ENEMY_CARD_H - 6);
|
||||
}
|
||||
ctx.restore();
|
||||
|
||||
if (canDeploy) {
|
||||
if (canDeploy && !G.armoryOpen && !G.commandOpen) {
|
||||
const dId = def.id;
|
||||
addHitRegion(CX, cardY, CW, SP_ENEMY_CARD_H, () => deployEnemy(dId, G.sendQuantity));
|
||||
}
|
||||
}
|
||||
|
||||
const totalCardH = ENEMY_DEFS.length * (SP_ENEMY_CARD_H + SP_ENEMY_CARD_GAP);
|
||||
const totalCardH = visibleDefs.length * (SP_ENEMY_CARD_H + SP_ENEMY_CARD_GAP);
|
||||
_sidePanelScrollMax = Math.max(0, totalCardH - ENEMY_AREA_H + 4);
|
||||
ctx.restore(); // end enemy clip
|
||||
|
||||
@@ -183,7 +226,8 @@ function drawSidePanel() {
|
||||
ctx.font = '11px "Share Tech Mono", monospace';
|
||||
ctx.textAlign = 'left'; ctx.textBaseline = 'top';
|
||||
ctx.fillStyle = logColorMap[logLines[i].type] || '#3a6080';
|
||||
ctx.fillText('› ' + logLines[i].text, PX + 10, ly + 2);
|
||||
const cnt = logLines[i].count || 1;
|
||||
ctx.fillText('› ' + logLines[i].text + (cnt > 1 ? ` ×${cnt}` : ''), PX + 10, ly + 2);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user