step 7.3: wip (not ticked)

This commit is contained in:
bepis
2026-10-21 07:05:38 +11:00
parent 233f1e0491
commit 3991295395
2 changed files with 196 additions and 0 deletions
+5
View File
@@ -76,6 +76,7 @@
<section class="panel action-panel" id="action-panel"> <section class="panel action-panel" id="action-panel">
<div class="tabs" role="tablist"> <div class="tabs" role="tablist">
<button class="tab active" data-tab="hunt" role="tab" aria-selected="true">Hunt</button> <button class="tab active" data-tab="hunt" role="tab" aria-selected="true">Hunt</button>
<button class="tab" data-tab="expedition" role="tab" aria-selected="false">Expedition</button>
<button class="tab" data-tab="inventory" role="tab" aria-selected="false">Inventory</button> <button class="tab" data-tab="inventory" role="tab" aria-selected="false">Inventory</button>
</div> </div>
<div id="hunt-tab"> <div id="hunt-tab">
@@ -83,6 +84,10 @@
<p class="loading">Loading zones...</p> <p class="loading">Loading zones...</p>
</div> </div>
</div> </div>
<div id="expedition-tab" style="display:none;">
<div id="expedition-status"></div>
<div id="expedition-defs" class="expedition-defs"></div>
</div>
<div id="inventory-tab" style="display:none;"> <div id="inventory-tab" style="display:none;">
<div class="inventory-container"> <div class="inventory-container">
<div class="equipment-panel"> <div class="equipment-panel">
+191
View File
@@ -17,6 +17,7 @@ function switchTab(tab) {
t.setAttribute('aria-selected', isActive); t.setAttribute('aria-selected', isActive);
}); });
document.getElementById('hunt-tab').style.display = tab === 'hunt' ? '' : 'none'; document.getElementById('hunt-tab').style.display = tab === 'hunt' ? '' : 'none';
document.getElementById('expedition-tab').style.display = tab === 'expedition' ? '' : 'none';
document.getElementById('inventory-tab').style.display = tab === 'inventory' ? '' : 'none'; document.getElementById('inventory-tab').style.display = tab === 'inventory' ? '' : 'none';
} }
@@ -593,6 +594,196 @@ async function handleUnequip(slot) {
} }
} }
// ===== Expedition UI =====
let expeditionInterval = null;
function formatTimeRemaining(ms) {
if (ms <= 0) return 'Finished';
const totalSec = Math.ceil(ms / 1000);
const mins = Math.floor(totalSec / 60);
const secs = totalSec % 60;
if (mins >= 60) {
const hours = Math.floor(mins / 60);
const remMins = mins % 60;
return `${hours}h ${remMins}m`;
}
return `${mins}:${secs.toString().padStart(2, '0')}`;
}
function formatDuration(sec) {
if (sec < 3600) return `${sec / 60} min`;
const hours = sec / 3600;
if (hours < 24) return `${hours}h`;
return `${hours / 24}d`;
}
async function loadExpeditions() {
try {
const data = await api.get('/expeditions/active');
if (!data || !data.ok) return;
const statusEl = document.getElementById('expedition-status');
const defsEl = document.getElementById('expedition-defs');
if (data.data) {
renderActiveExpedition(statusEl, defsEl, data.data);
} else {
statusEl.innerHTML = '';
renderExpeditionDefs(defsEl);
}
} catch (_) {
const statusEl = document.getElementById('expedition-status');
statusEl.innerHTML = '<p class="placeholder">Failed to load expedition data.</p>';
}
}
function renderActiveExpedition(statusEl, defsEl, expeditionData) {
defsEl.innerHTML = '';
const { expedition, definition, timeRemaining, isFinished } = expeditionData;
const elapsed = (definition.durationSec * 1000) - timeRemaining;
const pct = Math.min(100, Math.max(0, (elapsed / (definition.durationSec * 1000)) * 100));
let html = `<div class="expedition-active-card">`;
html += `<div class="expedition-name">${definition.name}</div>`;
html += `<div class="expedition-desc">${definition.description}</div>`;
// Progress bar
html += `<div class="expedition-progress">`;
html += `<div class="expedition-progress-bar">`;
html += `<div class="expedition-progress-fill" style="width:${pct}%"></div>`;
html += `</div>`;
html += `<div class="expedition-progress-text">${formatTimeRemaining(timeRemaining)}</div>`;
html += `</div>`;
if (isFinished) {
html += `<button class="btn btn-expedition btn-claim" data-expedition-id="${expedition.id}">Claim Rewards</button>`;
html += `<div class="expedition-rewards-preview">Rewards: ${definition.xpReward} XP, ${definition.goldMin}–${definition.goldMax} Gold</div>`;
} else {
html += `<div class="expedition-info">Duration: ${formatDuration(definition.durationSec)} · Cost: ${definition.energyCost}⚡</div>`;
}
html += `</div>`;
statusEl.innerHTML = html;
// Attach claim handler
const claimBtn = statusEl.querySelector('.btn-claim');
if (claimBtn) {
claimBtn.addEventListener('click', handleClaimExpedition);
}
}
function renderExpeditionDefs(container) {
container.innerHTML = '';
// Fetch expedition definitions from the server
api.get('/expeditions/defs').then(defsData => {
if (!defsData || !defsData.ok || !defsData.data) return;
const defs = defsData.data;
for (const def of defs) {
const card = document.createElement('div');
card.className = 'expedition-def-card';
const nameEl = document.createElement('div');
nameEl.className = 'expedition-name';
nameEl.textContent = def.name;
const descEl = document.createElement('div');
descEl.className = 'expedition-desc';
descEl.textContent = def.description;
const infoEl = document.createElement('div');
infoEl.className = 'expedition-info';
infoEl.textContent = `${formatDuration(def.durationSec)} · ${def.energyCost}⚡ · Lv${def.min_level}+`;
const rewardsEl = document.createElement('div');
rewardsEl.className = 'expedition-rewards';
rewardsEl.textContent = `+${def.xpReward} XP, ${def.goldMin}–${def.gold_max} Gold`;
const btn = document.createElement('button');
btn.className = 'btn btn-expedition';
btn.textContent = `Start Expedition (${def.energyCost}⚡)`;
btn.disabled = window.heroLevel < def.min_level;
btn.dataset.defId = def.id;
if (window.heroLevel < def.min_level) {
btn.title = `Requires level ${def.min_level}`;
}
card.appendChild(nameEl);
card.appendChild(descEl);
card.appendChild(infoEl);
card.appendChild(rewardsEl);
card.appendChild(btn);
container.appendChild(card);
btn.addEventListener('click', handleStartExpedition);
}
}).catch(() => {
container.innerHTML = '<p class="placeholder">Failed to load expedition options.</p>';
});
}
async function handleStartExpedition(e) {
const btn = e.target;
const defId = Number(btn.dataset.defId);
if (isNaN(defId)) return;
btn.disabled = true;
btn.textContent = 'Starting...';
try {
const data = await api.post('/expeditions/start', { defId });
if (!data || !data.ok) {
showToast(data?.error?.message || 'Failed to start expedition.', 'error');
return;
}
showToast('Expedition started!', 'info');
loadExpeditions();
loadCharacter(); // refresh energy
} catch (err) {
showToast('Failed to start expedition.', 'error');
}
}
async function handleClaimExpedition(e) {
const btn = e.target;
const expeditionId = Number(btn.dataset.expeditionId);
if (isNaN(expeditionId)) return;
btn.disabled = true;
btn.textContent = 'Claiming...';
try {
const data = await api.post('/expeditions/claim', { expeditionId });
if (!data || !data.ok) {
showToast(data?.error?.message || 'Failed to claim expedition.', 'error');
return;
}
const { xpGained, goldGained } = data.data;
showToast(`Expedition complete! +${xpGained} XP, +${goldGained} Gold`, 'info');
loadExpeditions();
loadCharacter(); // refresh gold/XP
} catch (err) {
showToast('Failed to claim expedition.', 'error');
}
}
/**
* Periodically refresh expedition progress while on the expedition tab.
* Updates the progress bar and time remaining every 5 seconds.
*/
function startExpeditionSync() {
if (expeditionInterval) clearInterval(expeditionInterval);
expeditionInterval = setInterval(() => {
if (currentTab === 'expedition') {
loadExpeditions();
}
}, 5000);
}
// ===== Toast notifications ===== // ===== Toast notifications =====
function showToast(message, type = 'info') { function showToast(message, type = 'info') {
// Remove existing toasts // Remove existing toasts