151 lines
5.2 KiB
JavaScript
151 lines
5.2 KiB
JavaScript
// Hotbar UI — 3-slot Minecraft-style bottom bar + selected item indicator
|
|
import { state } from '../core/state.js';
|
|
import { BLOCKS } from '../data/blocks.js';
|
|
import { ITEMS } from '../data/items.js';
|
|
import { TOOLS } from '../data/tools.js';
|
|
import { tex, itemTex } from '../render/textures.js';
|
|
import { playSound } from '../audio/sound-engine.js';
|
|
|
|
const HOTBAR_SLOTS = 3;
|
|
|
|
let selectedItemEl = null;
|
|
|
|
export function addToRecentItems(id) {
|
|
if (!id) return;
|
|
state.recentItems = state.recentItems.filter(item => item !== id);
|
|
state.recentItems.unshift(id);
|
|
state.recentItems = state.recentItems.slice(0, 5);
|
|
}
|
|
|
|
function renderSlotIcon(el, id) {
|
|
if (BLOCKS[id] && tex[id]) {
|
|
el.style.backgroundImage = `url(${tex[id].toDataURL()})`;
|
|
el.style.backgroundSize = 'cover';
|
|
} else if (itemTex[id]) {
|
|
el.style.backgroundImage = `url(${itemTex[id].toDataURL()})`;
|
|
el.style.backgroundSize = 'cover';
|
|
} else if (ITEMS[id]) {
|
|
el.textContent = ITEMS[id].icon || '📦';
|
|
} else if (TOOLS[id]) {
|
|
el.textContent = TOOLS[id].icon || '🔧';
|
|
} else if (id === 'iron_armor') {
|
|
el.textContent = '🛡️';
|
|
el.style.background = 'linear-gradient(135deg, #95a5a6 0%, #7f8c8d 100%)';
|
|
} else {
|
|
el.textContent = id.charAt(0).toUpperCase();
|
|
el.style.background = '#555';
|
|
el.style.color = '#fff';
|
|
el.style.fontWeight = '700';
|
|
el.style.fontSize = '14px';
|
|
}
|
|
}
|
|
|
|
function updateSelectedItem() {
|
|
if (!selectedItemEl) selectedItemEl = document.getElementById('selectedItem');
|
|
if (!selectedItemEl) return;
|
|
const sel = state.selected;
|
|
if (!sel || !state.inv[sel] || state.inv[sel] <= 0) {
|
|
selectedItemEl.style.display = 'none';
|
|
return;
|
|
}
|
|
selectedItemEl.style.display = 'flex';
|
|
// Clear previous content
|
|
selectedItemEl.textContent = '';
|
|
selectedItemEl.style.backgroundImage = '';
|
|
selectedItemEl.style.background = 'rgba(0,0,0,0.45)';
|
|
// Render icon
|
|
if (BLOCKS[sel] && tex[sel]) {
|
|
selectedItemEl.style.backgroundImage = `url(${tex[sel].toDataURL()})`;
|
|
selectedItemEl.style.backgroundSize = 'cover';
|
|
} else if (itemTex[sel]) {
|
|
selectedItemEl.style.backgroundImage = `url(${itemTex[sel].toDataURL()})`;
|
|
selectedItemEl.style.backgroundSize = 'cover';
|
|
} else if (ITEMS[sel]) {
|
|
selectedItemEl.textContent = ITEMS[sel].icon || '📦';
|
|
} else if (TOOLS[sel]) {
|
|
selectedItemEl.textContent = TOOLS[sel].icon || '🔧';
|
|
} else {
|
|
selectedItemEl.textContent = sel.charAt(0).toUpperCase();
|
|
}
|
|
}
|
|
|
|
export function rebuildHotbar() {
|
|
const hotbarEl = state.hotbarEl;
|
|
const inv = state.inv;
|
|
const selected = state.selected;
|
|
const toolDurability = state.toolDurability;
|
|
|
|
hotbarEl.innerHTML = '';
|
|
|
|
// Get up to 3 recent items with count > 0
|
|
const items = state.recentItems.filter(id => inv[id] > 0).slice(0, HOTBAR_SLOTS);
|
|
|
|
// If selected item not in list and has count > 0, push to front
|
|
if (inv[selected] > 0 && !items.includes(selected)) {
|
|
items.unshift(selected);
|
|
if (items.length > HOTBAR_SLOTS) items.pop();
|
|
}
|
|
|
|
// Always render HOTBAR_SLOTS slots (fill empties)
|
|
for (let si = 0; si < HOTBAR_SLOTS; si++) {
|
|
const id = items[si] || null;
|
|
const s = document.createElement('div');
|
|
s.className = 'slot' + (id && id === selected ? ' sel' : '');
|
|
|
|
// Slot number indicator (top-left)
|
|
const numEl = document.createElement('div');
|
|
numEl.style.cssText = 'position:absolute;top:1px;left:3px;font-size:10px;color:rgba(255,255,255,0.55);font-weight:700;pointer-events:none;text-shadow:1px 1px 1px rgba(0,0,0,0.7);';
|
|
numEl.textContent = (si + 1).toString();
|
|
s.appendChild(numEl);
|
|
|
|
if (id) {
|
|
renderSlotIcon(s, id);
|
|
|
|
const c = document.createElement('div');
|
|
c.className = 'count';
|
|
c.textContent = inv[id];
|
|
s.appendChild(c);
|
|
s.onclick = () => {
|
|
playSound('click');
|
|
state.selected = id;
|
|
// Move to front of recentItems
|
|
state.recentItems = state.recentItems.filter(item => item !== id);
|
|
state.recentItems.unshift(id);
|
|
rebuildHotbar();
|
|
};
|
|
|
|
if (id === 'iron_armor' && state.player.equippedArmor === 'iron_armor') {
|
|
const equipped = document.createElement('div');
|
|
equipped.className = 'equipped-indicator';
|
|
equipped.textContent = '✓';
|
|
s.appendChild(equipped);
|
|
}
|
|
|
|
// Durability bar
|
|
if (TOOLS[id] && inv[id] > 0) {
|
|
let curDur = 0, maxDur = TOOLS[id].durability;
|
|
for (const [tid, dur] of toolDurability) {
|
|
if (dur.type === id) {
|
|
curDur = dur.current;
|
|
maxDur = dur.max;
|
|
break;
|
|
}
|
|
}
|
|
if (maxDur > 0) {
|
|
const bar = document.createElement('div');
|
|
bar.style.cssText = `position:absolute;bottom:0;left:0;right:0;height:3px;background:#333;border-radius:0 0 6px 6px;overflow:hidden;`;
|
|
const fill = document.createElement('div');
|
|
const pct = curDur / maxDur;
|
|
const color = pct > 0.5 ? '#2ecc71' : pct > 0.25 ? '#f39c12' : '#e74c3c';
|
|
fill.style.cssText = `width:${pct * 100}%;height:100%;background:${color};`;
|
|
bar.appendChild(fill);
|
|
s.appendChild(bar);
|
|
}
|
|
}
|
|
}
|
|
hotbarEl.appendChild(s);
|
|
}
|
|
|
|
// Update selected item indicator above hotbar
|
|
updateSelectedItem();
|
|
} |