// UI Management
const UI = {
tooltip: null,
recipeSelect: null,
selectedBuilding: null,
// Initialize UI
init() {
this.tooltip = document.getElementById('tooltip');
this.recipeSelect = document.getElementById('recipe-select');
this.createResourceBar();
this.createToolbar();
this.setupEventListeners();
},
// Create resource bar
createResourceBar() {
const container = document.getElementById('resource-bar');
container.innerHTML = CONFIG.RESOURCE_ORDER.map(res => `
${CONFIG.RESOURCE_NAMES[res].split(' ')[0]}: 0
`).join('');
},
// Create toolbar
createToolbar() {
const toolbar = document.getElementById('toolbar');
toolbar.innerHTML = `
Tools
Extraction
Logistics
Production
Defense
Game Speed
Controls:
R Rotate | E Mine
WASD Pan | Scroll Zoom
Dev: \` Toggle
F1 Wave F2 Kill F3 Res
`;
// Category collapse handlers
toolbar.querySelectorAll('h3[data-category]').forEach(header => {
header.addEventListener('click', () => {
Audio.playUIClick();
header.classList.toggle('collapsed');
const category = document.getElementById('cat-' + header.dataset.category);
if (category) {
category.classList.toggle('collapsed');
}
});
});
// Tool button handlers
toolbar.querySelectorAll('.tool-btn').forEach(btn => {
btn.addEventListener('click', () => {
Audio.playUIClick();
Input.selectTool(btn.dataset.tool);
});
});
// Speed button handlers
toolbar.querySelectorAll('.speed-btn').forEach(btn => {
btn.addEventListener('click', () => {
Audio.playUIClick();
Simulation.setSpeed(parseFloat(btn.dataset.speed));
toolbar.querySelectorAll('.speed-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
});
});
// Create wave indicator
this.createWaveIndicator();
// Create minimap
this.createMinimap();
// Create sound toggle
this.createSoundToggle();
},
// Create wave indicator element
createWaveIndicator() {
const indicator = document.createElement('div');
indicator.id = 'wave-indicator';
indicator.className = 'wave-indicator peaceful';
indicator.innerHTML = `
Peaceful
Next wave in: --:--
`;
document.querySelector('.canvas-container').appendChild(indicator);
},
// Create minimap
createMinimap() {
const container = document.createElement('div');
container.className = 'minimap-container';
container.innerHTML = `
`;
document.querySelector('.canvas-container').appendChild(container);
// Click to navigate
const minimap = document.getElementById('minimap');
minimap.addEventListener('click', (e) => {
const rect = minimap.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width;
const y = (e.clientY - rect.top) / rect.height;
Renderer.camera.x = x * CONFIG.MAP_WIDTH * CONFIG.TILE_SIZE * Renderer.camera.zoom - Renderer.canvas.width / 2;
Renderer.camera.y = y * CONFIG.MAP_HEIGHT * CONFIG.TILE_SIZE * Renderer.camera.zoom - Renderer.canvas.height / 2;
});
// Handle resize
window.addEventListener('resize', () => this.resizeMinimap());
this.resizeMinimap();
},
// Resize minimap canvas to match display size
resizeMinimap() {
const canvas = document.getElementById('minimap');
if (!canvas) return;
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width;
canvas.height = rect.height;
},
// Update minimap
updateMinimap() {
const canvas = document.getElementById('minimap');
if (!canvas || canvas.width === 0 || canvas.height === 0) return;
const ctx = canvas.getContext('2d');
const scaleX = canvas.width / CONFIG.MAP_WIDTH;
const scaleY = canvas.height / CONFIG.MAP_HEIGHT;
const scale = Math.min(scaleX, scaleY);
// Clear
ctx.fillStyle = '#1a2a1a';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw resources
for (let y = 0; y < CONFIG.MAP_HEIGHT; y++) {
for (let x = 0; x < CONFIG.MAP_WIDTH; x++) {
const tile = Terrain.tiles[y]?.[x];
if (tile?.resource && tile.amount > 0) {
const colors = { iron: '#8899bb', copper: '#cc9966', coal: '#444' };
ctx.fillStyle = colors[tile.resource] || '#666';
ctx.fillRect(x * scaleX, y * scaleY, scaleX + 0.5, scaleY + 0.5);
}
}
}
// Draw buildings
ctx.fillStyle = '#ffaa00';
Buildings.list.forEach(b => {
const size = Utils.getBuildingSize(b.type);
ctx.fillRect(b.x * scaleX, b.y * scaleY, size.w * scaleX + 0.5, size.h * scaleY + 0.5);
});
// Draw towers
ctx.fillStyle = '#4a9eff';
Towers.list.forEach(t => {
ctx.fillRect(t.x * scaleX, t.y * scaleY, scaleX + 0.5, scaleY + 0.5);
});
// Draw enemies
ctx.fillStyle = '#ff4444';
Enemies.list.forEach(e => {
const ex = e.x / CONFIG.TILE_SIZE * scaleX;
const ey = e.y / CONFIG.TILE_SIZE * scaleY;
ctx.beginPath();
ctx.arc(ex, ey, Math.max(2, scale * 0.5), 0, Math.PI * 2);
ctx.fill();
});
// Draw viewport
const viewX = Renderer.camera.x / (CONFIG.TILE_SIZE * Renderer.camera.zoom) * scaleX;
const viewY = Renderer.camera.y / (CONFIG.TILE_SIZE * Renderer.camera.zoom) * scaleY;
const viewW = Renderer.canvas.width / (CONFIG.TILE_SIZE * Renderer.camera.zoom) * scaleX;
const viewH = Renderer.canvas.height / (CONFIG.TILE_SIZE * Renderer.camera.zoom) * scaleY;
ctx.strokeStyle = '#fff';
ctx.lineWidth = 1;
ctx.strokeRect(viewX, viewY, viewW, viewH);
},
// Create sound toggle
createSoundToggle() {
const toggle = document.createElement('div');
toggle.className = 'sound-toggle';
toggle.id = 'sound-toggle';
toggle.innerHTML = '๐ Sound';
toggle.addEventListener('click', () => {
const enabled = Audio.toggle();
toggle.innerHTML = enabled ? '๐ Sound' : '๐ Muted';
toggle.classList.toggle('muted', !enabled);
});
document.querySelector('.canvas-container').appendChild(toggle);
},
// Update wave indicator
updateWaveIndicator() {
const indicator = document.getElementById('wave-indicator');
if (!indicator) return;
const waveActive = Enemies.waveActive;
const timeUntil = Enemies.getTimeUntilWave();
indicator.className = 'wave-indicator ' + (waveActive ? '' : 'peaceful');
if (waveActive) {
indicator.innerHTML = `
โ WAVE ${Enemies.wave} โ
Enemies remaining: ${Enemies.enemiesRemaining}
`;
} else {
const minutes = Math.floor(timeUntil / 60);
const seconds = Math.floor(timeUntil % 60);
indicator.innerHTML = `
Wave ${Enemies.wave + 1} incoming
Time until wave: ${minutes}:${seconds.toString().padStart(2, '0')}
`;
}
// Dev mode indicator
this.updateDevModeIndicator();
},
// Show dev mode notification
showDevModeNotification() {
const existing = document.getElementById('dev-notification');
if (existing) existing.remove();
const notification = document.createElement('div');
notification.id = 'dev-notification';
notification.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: ${CONFIG.DEV_MODE ? 'rgba(68, 255, 68, 0.9)' : 'rgba(255, 68, 68, 0.9)'};
color: #000;
padding: 20px 40px;
border-radius: 10px;
font-size: 24px;
font-weight: bold;
z-index: 9999;
pointer-events: none;
`;
notification.textContent = CONFIG.DEV_MODE ? '๐ DEV MODE ON' : '๐ฎ DEV MODE OFF';
document.body.appendChild(notification);
setTimeout(() => notification.remove(), 1500);
},
// Update dev mode indicator
updateDevModeIndicator() {
let indicator = document.getElementById('dev-mode-indicator');
if (CONFIG.DEV_MODE) {
if (!indicator) {
indicator = document.createElement('div');
indicator.id = 'dev-mode-indicator';
indicator.style.cssText = `
position: fixed;
top: 10px;
left: 10px;
background: rgba(68, 255, 68, 0.8);
color: #000;
padding: 5px 10px;
border-radius: 5px;
font-size: 12px;
font-weight: bold;
z-index: 9999;
`;
indicator.textContent = '๐ DEV MODE';
document.body.appendChild(indicator);
}
} else {
if (indicator) indicator.remove();
}
},
// Setup event listeners
setupEventListeners() {
// Menu buttons
document.getElementById('btn-new').addEventListener('click', () => this.showModal('new-modal'));
document.getElementById('btn-save').addEventListener('click', () => {
SaveLoad.updateSaveSlots();
this.showModal('save-modal');
});
document.getElementById('btn-load').addEventListener('click', () => {
SaveLoad.updateLoadSlots();
this.showModal('load-modal');
});
document.getElementById('save-new').addEventListener('click', () => {
SaveLoad.saveToNewSlot();
this.closeModal('save-modal');
});
document.getElementById('confirm-new').addEventListener('click', () => {
game.newGame();
this.closeModal('new-modal');
});
// Recipe select
document.querySelectorAll('.recipe-option').forEach(opt => {
opt.addEventListener('click', () => {
if (this.selectedBuilding && this.selectedBuilding.type === 'assembler') {
this.selectedBuilding.recipe = opt.dataset.recipe;
this.recipeSelect.style.display = 'none';
}
});
});
// Close recipe select when clicking elsewhere
document.addEventListener('click', (e) => {
if (!this.recipeSelect.contains(e.target)) {
this.recipeSelect.style.display = 'none';
}
});
},
// Update resource display
updateResources() {
CONFIG.RESOURCE_ORDER.forEach(res => {
const el = document.getElementById(`res-${res}`);
if (el) {
el.textContent = Utils.formatNumber(Resources.get(res));
}
});
},
// Update tool button states
updateToolButtons(activeTool) {
document.querySelectorAll('.tool-btn[data-tool]').forEach(btn => {
const tool = btn.dataset.tool;
btn.classList.toggle('active', tool === activeTool);
// Disable if can't afford
if (CONFIG.COSTS[tool]) {
btn.classList.toggle('disabled', !Resources.canAfford(CONFIG.COSTS[tool]));
}
});
},
// Update tooltip
updateTooltip(clientX, clientY, mouseWorld) {
const building = Buildings.getAt(mouseWorld.x, mouseWorld.y);
const tile = Terrain.getTile(mouseWorld.x, mouseWorld.y);
if (building) {
let info = `${building.type.toUpperCase()}
`;
if (building.type === 'assembler') {
info += `Recipe: ${building.recipe || 'None'}
`;
}
const invItems = Object.entries(building.inventory).filter(([k, v]) => v > 0);
const outItems = Object.entries(building.output).filter(([k, v]) => v > 0);
if (invItems.length > 0) {
info += 'Input: ' + invItems.map(([k, v]) => `${k}: ${v}`).join(', ') + '
';
}
if (outItems.length > 0) {
info += 'Output: ' + outItems.map(([k, v]) => `${k}: ${v}`).join(', ');
}
this.tooltip.innerHTML = info;
this.tooltip.style.display = 'block';
this.tooltip.style.left = (clientX + 15) + 'px';
this.tooltip.style.top = (clientY + 15) + 'px';
} else if (tile?.resource && tile.amount > 0) {
this.tooltip.innerHTML = `${tile.resource.toUpperCase()} ORE
Amount: ${Utils.formatNumber(tile.amount)}
Click with Mine tool [E] to harvest`;
this.tooltip.style.display = 'block';
this.tooltip.style.left = (clientX + 15) + 'px';
this.tooltip.style.top = (clientY + 15) + 'px';
} else {
this.tooltip.style.display = 'none';
}
},
// Show recipe select
showRecipeSelect(clientX, clientY, building) {
this.selectedBuilding = building;
this.recipeSelect.style.display = 'block';
this.recipeSelect.style.left = (clientX + 10) + 'px';
this.recipeSelect.style.top = (clientY + 10) + 'px';
},
// Show modal
showModal(id) {
document.getElementById(id).style.display = 'flex';
},
// Close modal
closeModal(id) {
document.getElementById(id).style.display = 'none';
}
};