Files
44r0n7 626879ed0c 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
2026-06-17 11:58:17 -04:00

167 lines
4.8 KiB
JavaScript

// ═══ state.js ═══
// ============================================================
// STATE.JS — Central game state, constants, definitions
// ============================================================
// ── ARENA ─────────────────────────────────────────────
const ARENA_RADIUS = 720; // play area radius; enemies always spawn at this distance from tower
const ARENA_CX = 665; // center x of play area: (1600 - 270) / 2
const ARENA_CY = 482; // center y of play area: 64 + (900 - 64) / 2
// ── INITIAL GAME STATE ────────────────────────────────────────
function makeGameState() {
return {
credits: 150,
score: 0,
totalKills: 0,
frame: 0,
paused: false,
gameOver: false,
isBankrupt: false,
_isNewBest: false,
creditReserve: 50, // minimum credits kept in reserve — cannot spend upgrades below this
// Camera
camera: { zoom: 0.85, minZoom: 0.57, maxZoom: 1.8 },
// Tower
tower: {
hp: 20,
maxHp: 20,
armor: 0,
aimSpeed: 0.026,
range: 250, // vision + targeting radius; enemies outside are in fog and untargetable
cannonAngle: 0,
weaponSlots: 1,
shield: null,
shieldHp: 0,
shieldMaxHp: 0,
shieldAngle: 0,
shieldUnlocked: [],
},
// Weapons: array of active weapon instances
weapons: [
makeWeaponInstance('cannon'),
],
// Upgrade tracking
towerUpgradesBought: [], // array of upgrade ids
weaponUpgradesBought: {}, // { weaponInstanceId: [upgradeIds] }
shieldUpgradesBought: [], // upgrade ids for current shield
// Entities
enemies: [],
projectiles: [],
beams: [],
chainArcs: [], // lightning bolt visuals
aoeZones: [], // lingering AoE (poison clouds, freeze zones)
particles: [],
floaters: [],
portals: [],
// Portal system
portalCooldown: 0,
// Input state
selectedEnemyType: null,
sendQuantity: 1,
// Overlay panels — armory (weapons) and command (tower upgrades)
armoryOpen: false,
commandOpen: false,
weaponDetailSlot: -1,
// Entity id counter
nextId: 1,
// Kill streak
streak: { count: 0, lastKillFrame: -999 },
// Enemy freshness (novelty bonus) — higher = less fresh = less bonus
enemyFreshness: Object.fromEntries(ENEMY_DEFS.map(d => [d.id, 0])),
// Difficulty / Prestige
difficultyTier: 0,
unlockedTiers: [0],
prestigeLevel: 0,
permanentBonuses: {},
// Other overlay panels
threatOpen: false,
prestigeOpen: false,
};
}
let G = makeGameState(); // global game state
function countOwnedWeaponType(defId) {
let count = 0;
for (let i = 0; i < G.tower.weaponSlots; i++) {
const w = G.weapons[i];
if (w && w.defId === defId) count++;
}
for (const w of (G.weaponInventory || [])) {
if (w && w.defId === defId) count++;
}
return count;
}
function canBuyWeaponType(defId) {
return countOwnedWeaponType(defId) < MAX_WEAPONS_PER_TYPE;
}
function makeWeaponInstance(defId) {
const def = WEAPON_DEFS.find(w => w.id === defId);
if (!def) return null;
return {
instanceId: 'w_' + defId + '_' + Date.now(),
defId,
defaultElement: def.defaultElement ?? 'physical',
// Live stats (modified by upgrades)
damage: def.damage,
fireRate: def.fireRate,
range: def.range ?? 9999,
projectileSpeed: def.projectileSpeed ?? 6,
projectileRadius: def.projectileRadius ?? 4,
coneAngle: def.coneAngle ?? 0,
chains: def.chains ?? 0,
chainRange: def.chainRange ?? 0,
aoeRadius: def.aoeRadius ?? 0,
freezeDuration: def.freezeDuration ?? 0,
armorShred: def.armorShred ?? 0,
targets: def.targets ?? 1,
amplify: def.amplify ?? 0,
dotDamage: def.dotDamage ?? 0,
dotInterval: def.dotInterval ?? 0,
dotDuration: def.dotDuration ?? 0,
pierce: 0,
critChance: 0,
elements: [], // no elements by default — must purchase infuse slots
targeting: def.targeting,
cooldown: 0,
aimAngle: -Math.PI / 2,
recoil: 0,
muzzleFlash: 0,
lastFireFrame: -9999,
canInfuse: false,
canInfuse2: false,
canInfuse3: false,
};
}
function getWeaponDef(instanceOrId) {
const id = typeof instanceOrId === 'string' ? instanceOrId : instanceOrId.defId;
return WEAPON_DEFS.find(w => w.id === id);
}
function getWeaponElements(weapon) {
const base = weapon.defaultElement || getWeaponDef(weapon)?.defaultElement || 'physical';
const elements = [...new Set([base, ...(weapon.elements || [])].filter(el => el && el !== 'physical'))];
return elements.length ? elements : ['physical'];
}
function uid() {
return G.nextId++;
}