class MultiplayerSystem { constructor(scene) { this.scene = scene; this.socket = null; this.otherPlayers = {}; // Map socketId -> Sprite this.isConnected = false; // Try to connect this.connect(); } connect() { if (typeof io === 'undefined') { console.warn('⚠️ Socket.IO not found. Multiplayer disabled.'); console.warn('Please run: npm install socket.io-client OR include CDN.'); return; } console.log('🌐 Connecting to Multiplayer Server...'); // Connect to localhost:3000 this.socket = io('http://localhost:3000'); this.socket.on('connect', () => { console.log('✅ Connected to Server! ID:', this.socket.id); this.isConnected = true; // Send initial pos if (this.scene.player) { const pos = this.scene.player.getPosition(); this.socket.emit('playerMovement', { x: pos.x, y: pos.y, anim: 'idle', flipX: false }); } }); this.socket.on('currentPlayers', (players) => { Object.keys(players).forEach((id) => { if (id === this.socket.id) return; this.addOtherPlayer(players[id]); }); }); this.socket.on('newPlayer', (playerInfo) => { this.addOtherPlayer(playerInfo); }); this.socket.on('playerDisconnected', (playerId) => { this.removeOtherPlayer(playerId); }); this.socket.on('playerMoved', (playerInfo) => { if (this.otherPlayers[playerInfo.id]) { const sprite = this.otherPlayers[playerInfo.id]; // Update target pos for interpolation (TODO: logic) // For now direct teleport // Convert grid to screen const iso = new IsometricUtils(48, 24); const screen = iso.toScreen(playerInfo.x, playerInfo.y); sprite.setPosition(screen.x + this.scene.terrainOffsetX, screen.y + this.scene.terrainOffsetY); sprite.setDepth(sprite.y); // Anim/Flip logic could go here if (playerInfo.flipX !== undefined) sprite.setFlipX(playerInfo.flipX); } }); this.socket.on('worldAction', (action) => { // Handle world syncing if (action.type === 'build' && this.scene.buildingSystem) { // Hacky: place building remotely // this.scene.terrainSystem.placeStructure(...) } }); } addOtherPlayer(playerInfo) { if (this.otherPlayers[playerInfo.id]) return; console.log('👤 New Player Joined:', playerInfo.id); // Use player sprite const iso = new IsometricUtils(48, 24); const screen = iso.toScreen(playerInfo.x, playerInfo.y); const sprite = this.scene.add.sprite( screen.x + this.scene.terrainOffsetX, screen.y + this.scene.terrainOffsetY, 'player' // or player_idle ); sprite.setOrigin(0.5, 1); sprite.setScale(0.3); // Same as local player // Add name tag const text = this.scene.add.text(0, -50, 'Player', { fontSize: '12px', fill: '#ffffff' }); text.setOrigin(0.5); // Container? For now just sprite, text handling is complex this.otherPlayers[playerInfo.id] = sprite; } removeOtherPlayer(playerId) { if (this.otherPlayers[playerId]) { console.log('👋 Player Left:', playerId); this.otherPlayers[playerId].destroy(); delete this.otherPlayers[playerId]; } } update(delta) { if (!this.isConnected || !this.socket || !this.scene.player) return; // Rate limit: send 10 times second? Or every frame? // Let's send only if moved const player = this.scene.player; if (player.isMoving) { this.socket.emit('playerMovement', { x: player.gridX, y: player.gridY, anim: 'walk', flipX: player.sprite.flipX // Accessing internal sprite }); } } }