469 lines
16 KiB
JavaScript
469 lines
16 KiB
JavaScript
/**
|
|
* Galaxy Strike Online - Client Item System
|
|
* Dynamically loads and manages items from the GameServer
|
|
*/
|
|
|
|
class ItemSystem {
|
|
constructor(gameEngine) {
|
|
this.game = gameEngine;
|
|
|
|
// Item storage
|
|
this.itemCatalog = new Map(); // itemId -> item data
|
|
this.shopItems = []; // Array of shop items (legacy)
|
|
this.shopItemsByCategory = {}; // Categorized shop items (new structure)
|
|
this.lastUpdated = null;
|
|
|
|
// Loading state
|
|
this.isLoading = false;
|
|
this.loadPromise = null;
|
|
|
|
// Event listeners
|
|
this.eventListeners = new Map();
|
|
}
|
|
|
|
/**
|
|
* Initialize the item system and load data from server
|
|
*/
|
|
async initialize() {
|
|
console.log('[ITEM SYSTEM] Initializing client item system');
|
|
|
|
if (this.loadPromise) {
|
|
return this.loadPromise;
|
|
}
|
|
|
|
this.loadPromise = this.loadFromServer();
|
|
return this.loadPromise;
|
|
}
|
|
|
|
/**
|
|
* Load all items from the GameServer
|
|
*/
|
|
async loadFromServer() {
|
|
if (this.isLoading) {
|
|
console.log('[ITEM SYSTEM] Already loading items from server');
|
|
return this.loadPromise;
|
|
}
|
|
|
|
this.isLoading = true;
|
|
|
|
try {
|
|
console.log('[ITEM SYSTEM] Loading items from GameServer - Multiplayer Mode');
|
|
console.log('[ITEM SYSTEM] Socket connection status:', !!window.game?.socket);
|
|
|
|
if (!window.game || !window.game.socket) {
|
|
throw new Error('Not connected to server - multiplayer mode requires server connection');
|
|
}
|
|
|
|
// Load shop items from server
|
|
const shopItems = await this.fetchShopItems();
|
|
|
|
// Handle new shop structure (categorized) vs old structure (flat array)
|
|
let totalItems = 0;
|
|
if (Array.isArray(shopItems)) {
|
|
// Old structure: flat array
|
|
totalItems = shopItems.length;
|
|
console.log('[ITEM SYSTEM] Received', totalItems, 'items from server (old structure)');
|
|
this.processServerItems(shopItems);
|
|
this.shopItemsByCategory = {}; // Clear categorized data
|
|
} else if (shopItems && typeof shopItems === 'object') {
|
|
// New structure: categorized object
|
|
totalItems = Object.values(shopItems).reduce((sum, categoryItems) => sum + categoryItems.length, 0);
|
|
console.log('[ITEM SYSTEM] Received', totalItems, 'items from server (new structure)');
|
|
console.log('[ITEM SYSTEM] Categories:', Object.keys(shopItems));
|
|
|
|
// Store categorized data
|
|
this.shopItemsByCategory = shopItems;
|
|
|
|
// Flatten all items for processing
|
|
const allItems = Object.values(shopItems).flat();
|
|
this.processServerItems(allItems);
|
|
} else {
|
|
console.warn('[ITEM SYSTEM] Invalid shop items structure received:', typeof shopItems);
|
|
totalItems = 0;
|
|
this.shopItemsByCategory = {};
|
|
}
|
|
|
|
this.lastUpdated = Date.now();
|
|
console.log(`[ITEM SYSTEM] Successfully loaded ${this.itemCatalog.size} items from server`);
|
|
console.log('[ITEM SYSTEM] Item categories loaded:', Object.keys(this.itemCatalog).length);
|
|
|
|
// Emit loaded event
|
|
this.emit('itemsLoaded', {
|
|
itemCount: this.itemCatalog.size,
|
|
shopItemCount: this.shopItems.length,
|
|
timestamp: this.lastUpdated
|
|
});
|
|
|
|
return true;
|
|
|
|
} catch (error) {
|
|
console.error('[ITEM SYSTEM] Failed to load items from server:', error);
|
|
console.error('[ITEM SYSTEM] Error details:', {
|
|
message: error.message,
|
|
stack: error.stack,
|
|
socketConnected: !!window.game?.socket,
|
|
socketId: window.game?.socket?.id
|
|
});
|
|
|
|
// No fallback - emit error event
|
|
this.emit('itemsLoadError', error);
|
|
return false;
|
|
|
|
} finally {
|
|
this.isLoading = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch shop items from the GameServer
|
|
*/
|
|
async fetchShopItems() {
|
|
console.log('[ITEM SYSTEM] Starting fetchShopItems');
|
|
|
|
if (!window.game || !window.game.socket) {
|
|
console.error('[ITEM SYSTEM] No socket connection available');
|
|
throw new Error('Not connected to server');
|
|
}
|
|
|
|
console.log('[ITEM SYSTEM] Socket ID:', window.game.socket.id);
|
|
console.log('[ITEM SYSTEM] Socket connected:', window.game.socket.connected);
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const timeout = setTimeout(() => {
|
|
console.error('[ITEM SYSTEM] Server request timeout after 10 seconds');
|
|
window.game.socket.off('shopItemsReceived', handleResponse);
|
|
reject(new Error('Server request timeout'));
|
|
}, 10000);
|
|
|
|
// Test server connection first
|
|
console.log('[ITEM SYSTEM] Testing server connection...');
|
|
window.game.socket.emit('ping', { timestamp: Date.now() });
|
|
|
|
// Listen for ping response
|
|
const pingHandler = (data) => {
|
|
console.log('[ITEM SYSTEM] Ping response received:', data);
|
|
console.log('[ITEM SYSTEM] Server is responding! Ping roundtrip:', Date.now() - data.received, 'ms');
|
|
window.game.socket.off('ping', pingHandler);
|
|
window.game.socket.off('pong', pingHandler);
|
|
};
|
|
window.game.socket.on('ping', pingHandler);
|
|
|
|
// Listen for pong response (backup)
|
|
const pongHandler = (data) => {
|
|
console.log('[ITEM SYSTEM] Pong response received:', data);
|
|
console.log('[ITEM SYSTEM] Server is responding! Pong roundtrip:', Date.now() - data.timestamp, 'ms');
|
|
window.game.socket.off('pong', pongHandler);
|
|
};
|
|
window.game.socket.on('pong', pongHandler);
|
|
|
|
// Request shop items from server
|
|
console.log('[ITEM SYSTEM] Emitting getShopItems request');
|
|
console.log('[ITEM SYSTEM] Socket state:', {
|
|
connected: window.game.socket.connected,
|
|
id: window.game.socket.id
|
|
});
|
|
|
|
window.game.socket.emit('getShopItems', {});
|
|
console.log('[ITEM SYSTEM] Request sent, waiting for response...');
|
|
|
|
// Listen for response
|
|
const handleResponse = (data) => {
|
|
console.log('[ITEM SYSTEM] Received shopItemsReceived response:', data);
|
|
clearTimeout(timeout);
|
|
window.game.socket.off('shopItemsReceived', handleResponse);
|
|
|
|
console.log('[ITEM SYSTEM] Response success:', data.success);
|
|
console.log('[ITEM SYSTEM] Response shopItems keys:', data.shopItems ? Object.keys(data.shopItems) : 'none');
|
|
|
|
if (data.success) {
|
|
console.log('[ITEM SYSTEM] Successfully received shop data');
|
|
console.log('[ITEM SYSTEM] Response timestamp:', data.timestamp);
|
|
|
|
// Log item counts per category
|
|
if (data.shopItems) {
|
|
Object.entries(data.shopItems).forEach(([category, items]) => {
|
|
console.log(`[ITEM SYSTEM] ${category}: ${items.length} items`);
|
|
});
|
|
}
|
|
|
|
resolve(data.shopItems || {});
|
|
} else {
|
|
console.error('[ITEM SYSTEM] Server returned error:', data.error);
|
|
reject(new Error(data.error || 'Failed to load shop items'));
|
|
}
|
|
};
|
|
|
|
console.log('[ITEM SYSTEM] Setting up shopItemsReceived listener');
|
|
window.game.socket.on('shopItemsReceived', handleResponse);
|
|
|
|
// Verify the listener was set up
|
|
const listeners = window.game.socket.listeners('shopItemsReceived');
|
|
console.log('[ITEM SYSTEM] shopItemsReceived listeners count:', listeners.length);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Fetch specific item details from server
|
|
*/
|
|
async fetchItemDetails(itemId) {
|
|
if (!window.game || !window.game.socket) {
|
|
throw new Error('Not connected to server');
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const timeout = setTimeout(() => {
|
|
reject(new Error('Server request timeout'));
|
|
}, 5000);
|
|
|
|
// Request item details from server
|
|
window.game.socket.emit('getItemDetails', { itemId });
|
|
|
|
// Listen for response
|
|
const handleResponse = (data) => {
|
|
clearTimeout(timeout);
|
|
window.game.socket.off('itemDetailsReceived', handleResponse);
|
|
|
|
if (data.success) {
|
|
// Cache the item
|
|
this.itemCatalog.set(itemId, data.item);
|
|
resolve(data.item);
|
|
} else {
|
|
reject(new Error(data.error || 'Item not found'));
|
|
}
|
|
};
|
|
|
|
window.game.socket.on('itemDetailsReceived', handleResponse);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Process items received from server
|
|
*/
|
|
processServerItems(items) {
|
|
// Safety check for items parameter
|
|
if (!items || !Array.isArray(items)) {
|
|
console.error('[ITEM SYSTEM] Invalid items parameter:', items);
|
|
console.error('[ITEM SYSTEM] Expected array, got:', typeof items);
|
|
return;
|
|
}
|
|
|
|
console.log('[ITEM SYSTEM] Processing', items.length, 'items from server');
|
|
console.log('[ITEM SYSTEM] Sample items:', items.slice(0, 3));
|
|
|
|
this.itemCatalog.clear();
|
|
this.shopItems = [];
|
|
|
|
for (const item of items) {
|
|
// Store in catalog
|
|
this.itemCatalog.set(item.id, item);
|
|
|
|
// Add to shop items if available for shop
|
|
if (item.categories && item.categories.includes('shop')) {
|
|
this.shopItems.push(item);
|
|
}
|
|
|
|
// console.log('[ITEM SYSTEM] Added item:', {
|
|
// id: item.id,
|
|
// name: item.name,
|
|
// type: item.type,
|
|
// rarity: item.rarity,
|
|
// price: item.price,
|
|
// categories: item.categories
|
|
// });
|
|
}
|
|
|
|
console.log('[ITEM SYSTEM] Processing complete - Catalog:', this.itemCatalog.size, 'Shop items:', this.shopItems.length);
|
|
console.log('[ITEM SYSTEM] Shop items by type:', this.shopItems.reduce((acc, item) => {
|
|
acc[item.type] = (acc[item.type] || 0) + 1;
|
|
return acc;
|
|
}, {}));
|
|
}
|
|
|
|
/**
|
|
* Get item by ID
|
|
*/
|
|
getItem(itemId) {
|
|
// Return from cache if available
|
|
if (this.itemCatalog.has(itemId)) {
|
|
return this.itemCatalog.get(itemId);
|
|
}
|
|
|
|
// Try to fetch from server if not cached
|
|
if (window.game && window.game.socket) {
|
|
this.fetchItemDetails(itemId).catch(error => {
|
|
console.warn(`[ITEM SYSTEM] Failed to fetch item ${itemId}:`, error);
|
|
});
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Get all shop items
|
|
*/
|
|
getShopItems() {
|
|
return [...this.shopItems];
|
|
}
|
|
|
|
/**
|
|
* Get shop items by category (new structure)
|
|
*/
|
|
getShopItemsByCategory() {
|
|
return this.shopItemsByCategory || {};
|
|
}
|
|
|
|
/**
|
|
* Get items by category
|
|
*/
|
|
getItemsByCategory(category) {
|
|
return Array.from(this.itemCatalog.values()).filter(item =>
|
|
item.type === category || (item.categories && item.categories.includes(category))
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get items by type
|
|
*/
|
|
getItemsByType(type) {
|
|
return Array.from(this.itemCatalog.values()).filter(item => item.type === type);
|
|
}
|
|
|
|
/**
|
|
* Get items by rarity
|
|
*/
|
|
getItemsByRarity(rarity) {
|
|
return Array.from(this.itemCatalog.values()).filter(item => item.rarity === rarity);
|
|
}
|
|
|
|
/**
|
|
* Check if player can use item based on requirements
|
|
*/
|
|
canPlayerUseItem(item, playerLevel = null) {
|
|
if (!item.requirements) return true;
|
|
|
|
// Get player level if not provided
|
|
if (playerLevel === null && window.game && window.game.systems && window.game.systems.player) {
|
|
playerLevel = window.game.systems.player.level;
|
|
}
|
|
|
|
// Check level requirement
|
|
if (item.requirements.level && playerLevel < item.requirements.level) {
|
|
return false;
|
|
}
|
|
|
|
// Add other requirement checks here
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get filtered shop items for current player
|
|
*/
|
|
getAvailableShopItems() {
|
|
return this.shopItems.filter(item => this.canPlayerUseItem(item));
|
|
}
|
|
|
|
/**
|
|
* Format item price for display
|
|
*/
|
|
formatPrice(item) {
|
|
if (!item.price) return 'Free';
|
|
|
|
const currency = item.currency || 'credits';
|
|
const price = this.game.formatNumber(item.price);
|
|
|
|
return `${price} ${currency}`;
|
|
}
|
|
|
|
/**
|
|
* Get item rarity color
|
|
*/
|
|
getRarityColor(rarity) {
|
|
const colors = {
|
|
common: '#888888',
|
|
uncommon: '#00ff00',
|
|
rare: '#0088ff',
|
|
legendary: '#ff8800',
|
|
epic: '#ff00ff'
|
|
};
|
|
|
|
return colors[rarity] || '#ffffff';
|
|
}
|
|
|
|
/**
|
|
* Refresh items from server
|
|
*/
|
|
async refresh() {
|
|
console.log('[ITEM SYSTEM] Refreshing items from server');
|
|
return this.loadFromServer();
|
|
}
|
|
|
|
/**
|
|
* Event system
|
|
*/
|
|
on(event, callback) {
|
|
if (!this.eventListeners.has(event)) {
|
|
this.eventListeners.set(event, []);
|
|
}
|
|
this.eventListeners.get(event).push(callback);
|
|
}
|
|
|
|
off(event, callback) {
|
|
if (this.eventListeners.has(event)) {
|
|
const listeners = this.eventListeners.get(event);
|
|
const index = listeners.indexOf(callback);
|
|
if (index > -1) {
|
|
listeners.splice(index, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
emit(event, data) {
|
|
if (this.eventListeners.has(event)) {
|
|
for (const callback of this.eventListeners.get(event)) {
|
|
try {
|
|
callback(data);
|
|
} catch (error) {
|
|
console.error(`[ITEM SYSTEM] Error in event listener for ${event}:`, error);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* catalog getter — alias for shopItemsByCategory, used by Economy.updateShopUI
|
|
*/
|
|
get catalog() {
|
|
return this.shopItemsByCategory;
|
|
}
|
|
|
|
/**
|
|
* Get system statistics
|
|
*/
|
|
getStats() {
|
|
const stats = {
|
|
totalItems: this.itemCatalog.size,
|
|
shopItems: this.shopItems.length,
|
|
lastUpdated: this.lastUpdated,
|
|
isLoading: this.isLoading,
|
|
socketConnected: !!(window.game?.socket),
|
|
socketId: window.game?.socket?.id
|
|
};
|
|
|
|
// Add category breakdown
|
|
stats.categories = {};
|
|
for (const item of this.itemCatalog.values()) {
|
|
stats.categories[item.type] = (stats.categories[item.type] || 0) + 1;
|
|
}
|
|
|
|
return stats;
|
|
}
|
|
}
|
|
|
|
// Export for use in other modules
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = ItemSystem;
|
|
} else {
|
|
window.ItemSystem = ItemSystem;
|
|
}
|