diff --git a/server/static/map.js b/server/static/map.js new file mode 100644 index 0000000..8b3048d --- /dev/null +++ b/server/static/map.js @@ -0,0 +1,152 @@ +export class MapView { + constructor(scene) { + this.scene = scene; + this.config = { + width: 4000, + height: 3000, + zoom: 1.2, + moveSpeed: 200 + }; + this.target = null; + this.nodes = []; + this.nodeSprites = new Map(); + this.otherSprites = new Map(); // id -> { img,label } + } + + 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; + + // background + this.scene.add.rectangle(W / 2, H / 2, W, H, 0x020617).setDepth(-100); + + // player + this.player = this.scene.add.image(me.x, me.y, 'player').setDepth(10); + this.nameText = this.scene + .add.text(me.x, me.y - 22, me.name || 'You', { + fontSize: '12px', + color: '#e5e7eb' + }) + .setOrigin(0.5); + + // camera + 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); + + // nodes + this.replaceNodes(nodes || []); + } + + setTarget(x, y) { + this.target = { x, y }; + } + + setZoom(z) { + this.config.zoom = z; + this.scene.cameras.main.setZoom(z); + } + + replaceNodes(list) { + // remove existing + for (const sprite of this.nodeSprites.values()) { + sprite.destroy(); + } + this.nodeSprites.clear(); + this.nodes = list.slice(); + + for (const n of this.nodes) { + const key = this.getTextureKeyForNode(n.type); + const spr = this.scene.add.image(n.x, n.y, key).setDepth(1); + spr.setAlpha(n.alive ? 1 : 0.2); + this.nodeSprites.set(n.id, spr); + } + } + + 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 (!this.otherSprites.has(p.id)) { + const img = this.scene.add.image(p.x, p.y, 'other').setDepth(5); + const label = this.scene + .add.text(p.x, p.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(p.x, p.y); + rec.label.setPosition(p.x, p.y - 22); + } + } + // remove missing + 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); + } + } +}