API/Client/js/systems/ItemSystem.js

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;
}