// server/static/map.js export class MapView { constructor(scene) { this.scene = scene; // Tile config: adjust tileSize here if you want bigger/smaller tiles. this.tileSize = 32; this.config = { width: 16000, // will be overridden by world.width from server height: 12000, // " zoom: 1.2, moveSpeed: 200 }; this.target = null; this.nodes = []; this.nodeSprites = new Map(); this.otherSprites = new Map(); // socket.id -> { img,label } } getPlayerPosition() { if (!this.player) return { x: 0, y: 0 }; return { x: this.player.x, Y: this.player.y}; } create(me, world, nodes) { const W = world?.width || this.config.width; const H = world?.height || this.config.height; this.config.width = W; this.config.height = H; // Draw tiled background/grid using a Graphics object const g = this.scene.add.graphics(); const bgColor = 0x020617; // dark background const gridColor = 0x111827; // grid line color const gridAlpha = 0.4; // Fill background g.fillStyle(bgColor, 1); g.fillRect(0, 0, W, H); // Grid lines g.lineStyle(1, gridColor, gridAlpha); for (let x = 0; x <= W; x += this.tileSize) { g.beginPath(); g.moveTo(x, 0); g.lineTo(x, H); g.strokePath(); } for (let y = 0; y <= H; y += this.tileSize) { g.beginPath(); g.moveTo(0, y); g.lineTo(W, y); g.strokePath(); } g.setDepth(-100); // stay behind everything // Player sprite const startPos = this.snapToTile(me.x, me.y); this.player = this.scene.add.image(startPos.x, startPos.y, 'player').setDepth(10); this.nameText = this.scene .add.text(startPos.x, startPos.y - 22, me.username || me.name || 'You', { fontSize: '12px', color: '#e5e7eb' }) .setOrigin(0.5); // Camera follow & bounds const cam = this.scene.cameras.main; cam.setBounds(0, 0, W, H); cam.startFollow(this.player, true, 0.15, 0.15); cam.setZoom(this.config.zoom); // Initialize nodes this.replaceNodes(nodes || []); } // Snap any x,y world coordinate to the center of the containing tile snapToTile(x, y) { const size = this.tileSize; let tx = Math.floor(x / size); let ty = Math.floor(y / size); // clamp to world bounds in tiles const maxTx = Math.floor((this.config.width - 1) / size); const maxTy = Math.floor((this.config.height - 1) / size); tx = Math.max(0, Math.min(maxTx, tx)); ty = Math.max(0, Math.min(maxTy, ty)); return { x: tx * size + size / 2, y: ty * size + size / 2 }; } setTarget(x, y) { // When clicking, snap the target to a tile center this.target = this.snapToTile(x, y); } setZoom(z) { this.config.zoom = z; this.scene.cameras.main.setZoom(z); } replaceNodes(list) { // Clear old sprites for (const sprite of this.nodeSprites.values()) { sprite.destroy(); } this.nodeSprites.clear(); this.nodes = list.slice(); // Recreate sprites, snapping them to tiles visually (without changing server state) for (const n of this.nodes) { const snapped = this.snapToTile(n.x, n.y); const key = this.getTextureKeyForNode(n.type); const spr = this.scene.add.image(snapped.x, snapped.y, key).setDepth(1); spr.setAlpha(n.alive ? 1 : 0.2); this.nodeSprites.set(n.id, spr); // Also update stored coords so distance checks use snapped positions n.x = snapped.x; n.y = snapped.y; } } updateNode(u) { const n = this.nodes.find(n => n.id === u.id); if (n) n.alive = u.alive; const spr = this.nodeSprites.get(u.id); if (spr) spr.setAlpha(u.alive ? 1 : 0.2); } getTextureKeyForNode(type) { if (type === 'wood') return 'wood'; if (type === 'stone') return 'stone'; if (type === 'ore') return 'ore'; if (type === 'fiber') return 'fiber'; return 'wood'; } getClosestNodeIdInRange(range) { if (!this.player) return null; let bestId = null; let bestDist = Infinity; for (const n of this.nodes) { if (!n.alive) continue; const d = Phaser.Math.Distance.Between( this.player.x, this.player.y, n.x, n.y ); if (d <= range && d < bestDist) { bestDist = d; bestId = n.id; } } return bestId; } updateOtherPlayers(players) { const seen = new Set(); for (const p of players) { seen.add(p.id); // If you're using tiles: const pos = this.snapToTile ? this.snapToTile(p.x, p.y) : { x: p.x, y: p.y }; if (!this.otherSprites.has(p.id)) { const img = this.scene.add.image(pos.x, pos.y, 'other').setDepth(5); const label = this.scene .add.text(pos.x, pos.y - 22, p.name || 'Player', { fontSize: '12px', color: '#9ca3af' }) .setOrigin(0.5); this.otherSprites.set(p.id, { img, label }); } else { const rec = this.otherSprites.get(p.id); rec.img.setPosition(pos.x, pos.y); rec.label.setPosition(pos.x, pos.y - 22); } } // Clean up players that disconnected for (const [id, rec] of this.otherSprites) { if (!seen.has(id)) { rec.img.destroy(); rec.label.destroy(); this.otherSprites.delete(id); } } } update(delta) { if (!this.target || !this.player) return; const dt = delta / 1000; const dx = this.target.x - this.player.x; const dy = this.target.y - this.player.y; const dist = Math.hypot(dx, dy); const step = this.config.moveSpeed * dt; if (dist <= step) { this.player.setPosition(this.target.x, this.target.y); this.nameText.setPosition(this.player.x, this.player.y - 22); this.target = null; } else { this.player.setPosition( this.player.x + (dx / dist) * step, this.player.y + (dy / dist) * step ); this.nameText.setPosition(this.player.x, this.player.y - 22); } } }