Offline Time: 0h 0m
-Resources Gained: 0
+Offline Buffer
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ Claim & Continue
+
+
+
+
+
+ ⏰ Welcome Back!
+ + + +
@@ -1070,6 +1115,19 @@
+
+
+
+
+
+ Retreat
+
+
+
+
@@ -1570,6 +1628,22 @@
+
+
+
+
+
+
+
+ ⚙ CRAFT QUEUE (0/5)
+
+ Collect All Ready
+
+
+
+
+
+
+
+
@@ -1666,6 +1750,112 @@
+ 💎 GEM STORE
+ Your gems: 0
+ 💳 Buy Gems
+
+
+
+
+
+
+
+
+
+
+
+
+
+ RAIDS
+ +
+
+
+
+
+ ⚔️ RAID IN PROGRESS
+
+
+ Phase 1
+
+
+
+
+
+
+ ⚔️ Attack
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ RANKED PVP
+ +
+
+
+
+
+
+
+
+ —
Rating
0
Wins
0
Losses
0
Streak
+
+ ⚔️ Ranked Challenge
+
+
+
+ | # | +Player | +Tier | +Rating | +W / L | +
|---|
+
+
+ ↻ Refresh
+
+ ⚔️ Match History
+
+
+ Loading match history…
+
+
+
@@ -1948,6 +2138,7 @@
+
+
+
+
+
+ ALLIANCE WARS
+
+
+
+
+ Not currently in a war. Leaders and officers can declare war on rival alliances.
+
+
+ 🔥 Declare War
+
+
+
+ ACTIVE WARS
+
+
+ No active wars. The galaxy is at peace... for now.
+
@@ -1958,6 +2149,36 @@
+
+
+
+
+
+
+ 🛸 FLEET FORMATION
+
+
+ Save Order
+ Close
+
+
+
+ Drag ships to reorder • Active ship is marked ★ • Formation affects mission speed
+
+
+
+
+ 🏗 HANGAR (available ships)
+
+
+ Drag ships here to remove from formation
+
+
+ ⚙ Edit Formation
+
+
Loading fleet…
@@ -2488,6 +2713,35 @@
+
+
+
+
+
+
+
+ + 🔬 Alliance Research + +
+
+
+ Loading research tree…
+
+
+
+
+ 💬 Alliance Chat
+
+
+ No messages yet
+
+
+ Send
+
+
+
- ✕
+ ✕
+
+
+
+
+
+
+
+ ⚙ Settings
+⚙ Settings
-
+
+
+
+
@@ -2706,7 +2983,7 @@
-
+
@@ -2928,7 +3205,7 @@
-
+
@@ -2936,7 +3213,7 @@
+
@@ -3077,9 +3354,19 @@
break;
case 'leaderboard':
-
GSO_Leaderboard.load(GSO_Leaderboard.currentCat || 'level');
+ break;
+ case 'raids':
+ if (window.GSO_Raids) GSO_Raids.load();
+ break;
+
+ case 'pvp-rankings':
+ if (window.GSO_PvpRankings) GSO_PvpRankings.load();
+ break;
+
+ case 'alliance-wars':
+ if (window.GSO_AllianceWars) GSO_AllianceWars.load();
break;
case 'missions':
@@ -3296,6 +3583,18 @@
window.game.systems.itemSystem.activeCategory = category;
+ // Gem Store special handling (v3.3)
+ const _gemPanel = document.getElementById('gemStorePanel');
+ const _shopItems = document.getElementById('shopItems');
+ if (_gemPanel) _gemPanel.style.display = category === 'gems' ? '' : 'none';
+ if (_shopItems) _shopItems.style.display = category === 'gems' ? 'none' : '';
+ if (category === 'gems') {
+ document.querySelectorAll('.shop-cat-btn').forEach(b => b.classList.toggle('active', b.dataset.category === 'gems'));
+ if (window.GSO_GemStore) GSO_GemStore.render();
+ return;
+ }
+
+
}
@@ -4073,153 +4372,242 @@
window.GSO_Fleet = {
ships: [], activeShipId: null, maxFleet: 5,
-
-
+ formation: [], // ordered list of shipIds in active formation
+ hangar: [], // ships not in formation
+ _editorOpen: false,
+ _dragShipId: null,
load() {
-
const sock = window.gameInitializer?.socket;
-
if (!sock) return;
-
sock.emit('get_fleet_data');
-
sock.off('fleet_data').on('fleet_data', d => {
-
if (!d.success) return;
-
- this.ships = d.ships || [];
-
+ this.ships = d.ships || [];
this.activeShipId = d.activeShipId;
-
- this.maxFleet = d.maxFleetSize || 5;
-
+ this.maxFleet = d.maxFleetSize || 5;
+ this.formation = d.formation || this.ships.map(s => s.id);
+ this.hangar = this.ships.filter(s => !this.formation.includes(s.id)).map(s => s.id);
this.render();
-
+ if (this._editorOpen) this.renderEditor();
});
-
sock.off('active_ship_set').on('active_ship_set', d => {
-
- if (!d.success) { alert(d.error); return; }
-
+ if (!d.success) { showNotification(d.error, 'error'); return; }
this.activeShipId = d.shipId;
-
this.render();
-
+ if (this._editorOpen) this.renderEditor();
});
-
sock.off('ship_repaired').on('ship_repaired', d => {
-
- if (!d.success) { alert(d.error); return; }
-
+ if (!d.success) { showNotification(d.error, 'error'); return; }
const ship = this.ships.find(s => s.id === d.shipId);
-
- if (ship) { ship.stats = ship.stats || {}; ship.stats.currentHull = ship.stats.maxHull || ship.stats.hull || 100; }
-
+ if (ship) { ship.stats = ship.stats || {}; ship.stats.currentHull = ship.stats.maxHull || 100; }
this.render();
-
});
-
+ sock.off('fleet_formation_saved').on('fleet_formation_saved', d => {
+ if (d.success) showNotification('Formation saved!', 'success');
+ });
},
-
-
render() {
-
- const grid = document.getElementById('fleetGrid');
-
+ const grid = document.getElementById('fleetGrid');
const header = document.getElementById('fleet-header-stats');
-
if (!grid) return;
-
if (!this.ships.length) {
-
- grid.innerHTML = '';
-
+ grid.innerHTML = '';
+ if (header) header.textContent = '';
return;
-
}
-
if (header) header.textContent = `${this.ships.length} / ${this.maxFleet} ships`;
-
- grid.innerHTML = this.ships.map(s => this._shipCard(s)).join('');
-
+ // Render in formation order
+ const ordered = [
+ ...this.formation.map(id => this.ships.find(s => s.id === id)).filter(Boolean),
+ ...this.ships.filter(s => !this.formation.includes(s.id))
+ ];
+ grid.innerHTML = ordered.map(s => this._shipCard(s)).join('');
},
-
-
_shipCard(ship) {
-
- const rarity = (ship.rarity || 'common').toLowerCase();
-
- const isActive = ship.id === this.activeShipId;
-
- const hull = ship.stats?.currentHull ?? ship.stats?.hull ?? 100;
-
- const maxHull = ship.stats?.maxHull ?? ship.stats?.hull ?? 100;
-
- const hullPct = Math.max(0, Math.min(100, (hull / maxHull) * 100));
-
+ const rarity = (ship.rarity || 'common').toLowerCase();
+ const isActive = ship.id === this.activeShipId;
+ const hull = ship.stats?.currentHull ?? ship.stats?.hull ?? 100;
+ const maxHull = ship.stats?.maxHull ?? ship.stats?.hull ?? 100;
+ const hullPct = Math.max(0, Math.min(100, (hull / maxHull) * 100));
const hullColor = hullPct > 60 ? '#4caf50' : hullPct > 30 ? '#ff9800' : '#f44336';
-
- const imgSrc = ship.texture || `assets/gso/textures/ships/${ship.id}.png`;
-
+ const imgSrc = ship.texture || `assets/gso/textures/ships/${ship.id}.png`;
+ const fmtIdx = this.formation.indexOf(ship.id);
+ const pos = fmtIdx >= 0 ? `#${fmtIdx+1}` : 'Hangar';
return `
Modal Title
-No ships in fleet. Purchase ships from the Shop!
No ships. Buy some from the Shop!
-
-
`;
-
},
+ // ── Formation Editor ───────────────────────────────────────────────
-
- setActive(shipId) {
-
- window.gameInitializer?.socket?.emit('set_active_ship', { shipId });
-
+ toggleEditor() {
+ this._editorOpen = !this._editorOpen;
+ document.getElementById('fleetFormationEditor').style.display = this._editorOpen ? '' : 'none';
+ document.getElementById('fleetHangarSection').style.display = this._editorOpen ? '' : 'none';
+ document.getElementById('fleetEditorBtn').textContent = this._editorOpen ? '✕ Close Editor' : '⚙ Edit Formation';
+ if (this._editorOpen) this.renderEditor();
},
- repair(shipId) {
-
- window.gameInitializer?.socket?.emit('repair_ship', { shipId });
-
+ closeEditor() {
+ this._editorOpen = false;
+ document.getElementById('fleetFormationEditor').style.display = 'none';
+ document.getElementById('fleetHangarSection').style.display = 'none';
+ document.getElementById('fleetEditorBtn').textContent = '⚙ Edit Formation';
},
+ renderEditor() {
+ const slots = document.getElementById('fleetFormationSlots');
+ const hangar = document.getElementById('fleetHangar');
+ if (!slots || !hangar) return;
+
+ // Formation slots
+ const formationShips = this.formation
+ .map(id => this.ships.find(s => s.id === id))
+ .filter(Boolean);
+
+ slots.innerHTML = formationShips.length
+ ? formationShips.map(s => this._dragChip(s, false)).join('')
+ : 'Formation is empty — drag ships here';
+
+ // Hangar slots
+ const hangarShips = this.ships.filter(s => !this.formation.includes(s.id));
+ hangar.innerHTML = hangarShips.length
+ ? hangarShips.map(s => this._dragChip(s, true)).join('')
+ : 'All ships are in formation';
+ },
+
+ _dragChip(ship, inHangar) {
+ const isActive = ship.id === this.activeShipId;
+ const rc = {common:'#aaa',rare:'#4af',epic:'#c4f',legendary:'#fa0'}[(ship.rarity||'common').toLowerCase()] || '#aaa';
+ return `${rarity}
-
+ ${pos}
${ship.name}${isActive ? ' ★ ACTIVE' : ''}
-
${ship.class || ship.type || 'Ship'} · Lv.${ship.level || 1}
-
-
Attack${ship.stats?.attack ?? '—'}
-
Defense${ship.stats?.defense ?? '—'}
-
Speed${ship.stats?.speed ?? '—'}
-
Hull${hull}/${maxHull}
-
-
-
-
${!isActive ? `Set Active ` : ''}
-
${hull < maxHull ? `Repair ` : ''}
-
-
+
+
`;
+ },
+
+ onDragStart(e, shipId) {
+ this._dragShipId = shipId;
+ e.dataTransfer.effectAllowed = 'move';
+ e.dataTransfer.setData('text/plain', shipId);
+ e.currentTarget.style.opacity = '0.4';
+ },
+
+ onDragEnd(e) {
+ e.currentTarget.style.opacity = '1';
+ },
+
+ // Touch drag for mobile (v3.3)
+ _touchClone: null,
+ onTouchStart(e, shipId) {
+ this._dragShipId = shipId;
+ e.currentTarget.style.opacity = '0.4';
+ const clone = e.currentTarget.cloneNode(true);
+ clone.style.cssText += ';position:fixed;z-index:9999;pointer-events:none;opacity:.8;margin:0';
+ document.body.appendChild(clone);
+ this._touchClone = clone;
+ this._moveTouchClone(e.touches[0]);
+ },
+ onTouchMove(e) {
+ e.preventDefault();
+ if (this._touchClone) this._moveTouchClone(e.touches[0]);
+ },
+ onTouchEnd(e) {
+ if (this._touchClone) { this._touchClone.remove(); this._touchClone = null; }
+ document.querySelectorAll('[data-ship-id]').forEach(el => el.style.opacity = '1');
+ const touch = e.changedTouches[0];
+ const target = document.elementFromPoint(touch.clientX, touch.clientY);
+ const slotEl = document.getElementById('fleetFormationSlots');
+ const hangarEl = document.getElementById('fleetHangar');
+ if (slotEl?.contains(target)) {
+ this.onDropSlot({ preventDefault:()=>{}, dataTransfer: { getData: () => this._dragShipId }, target });
+ } else if (hangarEl?.contains(target)) {
+ this.onDropHangar({ preventDefault:()=>{}, dataTransfer: { getData: () => this._dragShipId }, target });
+ }
+ },
+ _moveTouchClone(touch) {
+ if (!this._touchClone) return;
+ this._touchClone.style.left = (touch.clientX - 60) + 'px';
+ this._touchClone.style.top = (touch.clientY - 30) + 'px';
+ },
+
+ onDropSlot(e) {
+ e.preventDefault();
+ const shipId = e.dataTransfer.getData('text/plain') || this._dragShipId;
+ if (!shipId) return;
+ // Add to formation if not already there
+ if (!this.formation.includes(shipId)) {
+ if (this.formation.length >= this.maxFleet) {
+ showNotification(`Formation full (max ${this.maxFleet} ships)`, 'warning');
+ return;
+ }
+ this.formation.push(shipId);
+ this.hangar = this.hangar.filter(id => id !== shipId);
+ }
+ // Reorder within formation based on drop position
+ const target = e.target.closest('[data-ship-id]');
+ if (target && target.dataset.shipId !== shipId) {
+ const toIdx = this.formation.indexOf(target.dataset.shipId);
+ const fromIdx = this.formation.indexOf(shipId);
+ if (fromIdx >= 0) this.formation.splice(fromIdx, 1);
+ this.formation.splice(toIdx >= 0 ? toIdx : this.formation.length, 0, shipId);
+ }
+ this.renderEditor();
+ this.render();
+ },
+
+ onDropHangar(e) {
+ e.preventDefault();
+ const shipId = e.dataTransfer.getData('text/plain') || this._dragShipId;
+ if (!shipId) return;
+ this.formation = this.formation.filter(id => id !== shipId);
+ if (!this.hangar.includes(shipId)) this.hangar.push(shipId);
+ this.renderEditor();
+ this.render();
+ },
+
+ saveFormation() {
+ window.gameInitializer?.socket?.emit('save_fleet_formation', { formation: this.formation });
+ },
+
+ setActive(shipId) { window.gameInitializer?.socket?.emit('set_active_ship', { shipId }); },
+ repair(shipId) { window.gameInitializer?.socket?.emit('repair_ship', { shipId }); },
+
};
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -7935,82 +9717,137 @@
function craftItem(id) {
-
if (!window.gameInitializer?.socket) return;
-
const btn = document.querySelector('#craftingDetails .btn');
-
- if (btn) { btn.disabled = true; btn.textContent = 'Crafting...'; }
-
+ if (btn) { btn.disabled = true; btn.textContent = 'Queuing...'; }
window.gameInitializer.socket.emit('craft_item', { recipeId: id });
-
}
-
-
function onCraftResult(data) {
-
- if (data.success) {
-
- showNotification(`Crafted! +${data.xpGained} XP`, 'success');
-
- // Update crafting level display
-
- document.getElementById('craftingLevel').textContent = data.craftingLevel || '?';
-
- document.getElementById('craftingExp').textContent = (data.craftingXp || 0) + ' XP';
-
- // Refresh view
-
- _syncInventory();
-
- renderList();
-
+ if (data.success && data.queued) {
+ showNotification(`Queued! Ready in ${data.timeSec}s`, 'success');
+ _syncInventory(); renderList();
+ if (_selected) renderDetails(_selected);
+ window.gameInitializer?.socket?.emit('get_craft_queue');
+ } else if (data.success) {
+ showNotification(`Crafted! +${data.xpGained} XP`, 'success');
+ document.getElementById('craftingLevel').textContent = data.craftingLevel || '?';
+ document.getElementById('craftingExp').textContent = (data.craftingXp || 0) + ' XP';
+ _syncInventory(); renderList();
if (_selected) renderDetails(_selected);
-
} else {
-
showNotification(data.error || 'Craft failed', 'error');
-
- if (_selected) renderDetails(_selected); // re-enable button
-
+ if (_selected) renderDetails(_selected);
}
-
}
+ // ── Craft Queue UI (v3.3) ──────────────────────────────────────────
+ let _queue = [];
+ let _queueTimer = null;
+ function onQueueUpdate(data) {
+ _queue = data.queue || [];
+ if (data.skill !== undefined) {
+ document.getElementById('craftingLevel').textContent = data.skill;
+ document.getElementById('craftingExp').textContent = (data.xp || 0) + ' XP';
+ }
+ _renderQueue();
+ _startQueueTick();
+ }
+
+ function _renderQueue() {
+ const panel = document.getElementById('craftQueuePanel');
+ const list = document.getElementById('craftQueueList');
+ const count = document.getElementById('craftQueueCount');
+ if (!panel || !list) return;
+ panel.style.display = _queue.length ? '' : 'none';
+ if (count) count.textContent = `(${_queue.length}/5)`;
+ list.innerHTML = _queue.map(job => {
+ const now = Date.now();
+ const total = job.completesAt - job.startedAt;
+ const elapsed = now - job.startedAt;
+ const pct = Math.min(100, Math.max(0, (elapsed / total) * 100));
+ const ready = now >= job.completesAt;
+ const remStr = ready ? 'Ready!' : _fmtMs(job.completesAt - now);
+ const name = Object.keys(job.outputs || {})[0] || job.recipeId;
+ const gemCost = 3;
+ return `
+
+ ${isActive?'★ ':''}${ship.name}
+ ${ship.class||'Ship'} · Lv.${ship.level||1}
+
+
`;
+ }).join('');
+ }
+
+ function _startQueueTick() {
+ clearInterval(_queueTimer);
+ if (!_queue.length) return;
+ _queueTimer = setInterval(() => {
+ _renderQueue();
+ _queue.forEach(job => {
+ if (!job._notified && Date.now() >= job.completesAt) {
+ job._notified = true;
+ showNotification(`✅ ${_formatName(Object.keys(job.outputs||{})[0]||job.recipeId)} ready to collect!`, 'success');
+ }
+ });
+ }, 1000);
+ }
+
+ function collect(jobId) { window.gameInitializer?.socket?.emit('collect_craft', { jobId }); }
+
+ function collectAll() {
+ const now = Date.now();
+ _queue.filter(j => now >= j.completesAt).forEach(j => collect(j.jobId));
+ }
+
+ function speedUp(jobId, gemCost) {
+ const gems = window.gameInitializer?.serverPlayerData?.stats?.gems || 0;
+ if (gems < gemCost) { showNotification(`Need ${gemCost} 💎 (have ${gems})`, 'warning'); return; }
+ if (!confirm(`Spend ${gemCost} 💎 to finish instantly?`)) return;
+ window.gameInitializer?.socket?.emit('speedup_craft', { jobId, gemCost });
+ }
+
+ function onCollectResult(data) {
+ if (data.success) {
+ showNotification(`🎉 Collected ${_formatName(data.output?.[0]?.id||'item')}! +${data.xpGained} XP`, 'success');
+ document.getElementById('craftingLevel').textContent = data.craftingLevel || '?';
+ document.getElementById('craftingExp').textContent = (data.craftingXp || 0) + ' XP';
+ window.gameInitializer?.socket?.emit('get_craft_queue');
+ _syncInventory(); renderList();
+ if (_selected) renderDetails(_selected);
+ } else { showNotification(data.error || 'Collect failed', 'warning'); }
+ }
+
+ function _fmtMs(ms) {
+ if (ms <= 0) return 'Ready!';
+ const s = Math.floor(ms/1000);
+ return s < 60 ? s + 's' : Math.floor(s/60) + 'm ' + (s%60) + 's';
+ }
function _formatName(id) {
-
- return (id || '')
-
- .replace(/.*[:/]/, '') // strip path prefix
-
- .replace(/_/g, ' ')
-
- .replace(/\w/g, c => c.toUpperCase());
-
+ return (id||'').replace(/.*[:/]/,'').replace(/_/g,' ').replace(/\b\w/g,c=>c.toUpperCase());
}
-
-
- // Called when switching to crafting tab
-
function init() {
-
_syncInventory();
-
if (!_recipes.length) load();
-
else renderList();
-
+ window.gameInitializer?.socket?.emit('get_craft_queue');
}
-
-
- return { load, onRecipesData, switchCategory, selectRecipe, craftItem, onCraftResult, init };
-
+ return { load, onRecipesData, switchCategory, selectRecipe, craftItem, onCraftResult,
+ onQueueUpdate, collect, collectAll, speedUp, onCollectResult, init };
})();
+
@@ -8353,6 +10190,600 @@
+
+
+
+
+
+
+ ${_formatName(name)}
+
+
+ ${remStr}
+ ${ready
+ ? `Collect `
+ : `⚡ ${gemCost}💎 `}
+
+
+
+
+