diff --git a/client/BridgeScene.js b/client/BridgeScene.js
new file mode 100644
index 0000000..eca07df
--- /dev/null
+++ b/client/BridgeScene.js
@@ -0,0 +1,394 @@
+import Phaser from "phaser";
+import Level from "./Level.js";
+import { PlayerManager } from './managers/PlayerManager'
+export default class BridgeScene extends Phaser.Scene {
+ constructor() {
+ super("BridgeScene");
+ this.player = null;
+ this.level = new Level();
+ this.spike = null;
+ this.healthBar = null;
+ this.otherPlayers = {};
+ this.canMove = false;
+ this.countdownText = null;
+ this.countdownStarted = false;
+ }
+
+ preload() {
+ this.load.image("Bridge_Stone_Horizontal", "/assets/Bridge_Stone_Horizontal.png");
+ this.load.image("Water_Tile", "/assets/Water_Tile.png");
+ this.load.tilemapTiledJSON("bridge", "/assets/bridge.json");
+ this.load.spritesheet("player", "/assets/Spearman.png", {
+ frameWidth: 48,
+ frameHeight: 48,
+ });
+ }
+
+ create() {
+ this.game.scale.resize(256,256)
+ const map = this.make.tilemap({ key: "bridge" });
+ const floor = map.addTilesetImage("Water_Tile", "Water_Tile");
+
+ const floorLayer = map.createLayer("floor", [floor], 0, 0);
+ floorLayer.setScale(1, 1).setOrigin(0, 0)
+ .setCollisionByProperty({ collider: true });
+
+ const Bridge_Stone_Horizontal = map.addTilesetImage("Bridge_Stone_Horizontal", "Bridge_Stone_Horizontal");
+ const objectLayer = map.createLayer("object", [Bridge_Stone_Horizontal], 0, 0);
+ objectLayer.setScale(1, 1).setOrigin(0, 0)
+ .setCollisionByProperty({ collider: true });
+
+
+ this.player = this.physics.add
+ .sprite(
+ 256/2 - 50,
+ 256/2 - 35,
+ "player",
+ )
+ .setScale(1);
+
+ this.player.life = 100;
+
+ this.player.setScale(1); // Scale the player sprite by 1.5 times
+ this.player.setBodySize(24, 28);
+ this.player.setOffset(10, 13);
+ this.cursors = this.input.keyboard.createCursorKeys();
+ this.anims.create({
+ key: "idleDown",
+ frames: this.anims.generateFrameNumbers("player", { start: 0, end: 5 }),
+ frameRate: 10,
+ repeat: -1,
+ });
+
+ this.physics.add.collider(this.player, objectLayer);
+
+ this.anims.create({
+ key: "idleRight",
+ frames: this.anims.generateFrameNumbers("player", { start: 6, end: 11 }),
+ frameRate: 10,
+ repeat: -1,
+ });
+
+ this.anims.create({
+ key: "idleUp",
+ frames: this.anims.generateFrameNumbers("player", { start: 12, end: 17 }),
+ frameRate: 10,
+ repeat: -1,
+ });
+
+ this.anims.create({
+ key: "walkDown",
+ frames: this.anims.generateFrameNumbers("player", { start: 18, end: 23 }),
+ frameRate: 10,
+ repeat: -1,
+ });
+
+ this.anims.create({
+ key: "walkRight",
+ frames: this.anims.generateFrameNumbers("player", { start: 24, end: 29 }),
+ frameRate: 10,
+ repeat: -1,
+ });
+
+ this.anims.create({
+ key: "walkUp",
+ frames: this.anims.generateFrameNumbers("player", { start: 30, end: 35 }),
+ frameRate: 10,
+ repeat: -1,
+ });
+
+ this.anims.create({
+ key: "attackDown",
+ frames: this.anims.generateFrameNumbers("player", { start: 36, end: 49 }),
+ frameRate: 10,
+ repeat: 1,
+ });
+
+ this.anims.create({
+ key: "attackRight",
+ frames: this.anims.generateFrameNumbers("player", { start: 42, end: 46 }),
+ frameRate: 10,
+ repeat: -1,
+ });
+
+ this.anims.create({
+ key: "attackUp",
+ frames: this.anims.generateFrameNumbers("player", { start: 48, end: 52 }),
+ frameRate: 10,
+ repeat: -1,
+ });
+
+ this.anims.create({
+ key: "die",
+ frames: this.anims.generateFrameNumbers("player", { start: 54, end: 56 }),
+ frameRate: 10,
+ repeat: 0,
+ });
+ this.player.play("idleDown");
+
+ this.healthBar = this.createHealthBar(this.player.x, this.player.y, this.player);
+
+ this.attackKey = this.input.keyboard.addKey(
+ Phaser.Input.Keyboard.KeyCodes.SPACE
+ );
+
+ const leaveButton = this.add.text(
+ this.cameras.main.width - 2,
+ this.cameras.main.height - 2,
+ 'Quit Match',
+ {
+ fontFamily: 'Verdana',
+ fontSize: '10px',
+ fill: '#fff',
+ backgroundColor: '#000',
+ padding: { x: 5, y: 2 }
+ }
+ )
+ .setOrigin(1, 1)
+ .setScrollFactor(0)
+ .setInteractive()
+ .setDepth(1000);
+
+ leaveButton.on('pointerdown', () => {
+ console.log('Leave button clicked'); // Debug log
+
+ if (this.socket) {
+ this.socket.emit('leaveScene', {
+ from: 'BridgeScene',
+ to: 'CommonScene'
+ });
+ }
+
+ // Instead of scene.start, reload the whole game
+ window.location.reload();
+ });
+
+ this.countdownText = this.add.text(
+ this.cameras.main.width / 2,
+ this.cameras.main.height / 2,
+ 'In Queue...',
+ {
+ fontSize: '32px',
+ fill: '#fff',
+ stroke: '#000',
+ strokeThickness: 4
+ }
+ )
+ .setOrigin(0.5)
+ .setScrollFactor(0)
+ .setDepth(1000);
+
+ this.socket = io(socketId, {
+ withCredentials: false,
+ });
+
+ this.socket.on('currentPlayers', (players) => {
+ console.log('Received current players:', players);
+ Object.keys(players).forEach((id) => {
+ if (id !== this.socket.id) {
+ const playerInfo = players[id];
+ const otherPlayer = this.physics.add.sprite(
+ playerInfo.x,
+ playerInfo.y,
+ 'player'
+ ).setScale(1);
+ this.otherPlayers[id] = otherPlayer;
+ }
+ });
+ });
+
+ this.socket.on('newPlayer', (playerInfo) => {
+ console.log('New player joined:', playerInfo);
+ const otherPlayer = this.physics.add.sprite(
+ playerInfo.x,
+ playerInfo.y,
+ 'player'
+ ).setScale(1);
+ this.otherPlayers[playerInfo.playerId] = otherPlayer;
+ });
+
+ this.socket.on('playerDisconnected', (playerId) => {
+ console.log('Player disconnected:', playerId);
+ if (this.otherPlayers[playerId]) {
+ this.otherPlayers[playerId].destroy();
+ delete this.otherPlayers[playerId];
+ }
+ });
+
+ this.socket.on('playerMoved', (playerInfo) => {
+ if (this.otherPlayers[playerInfo.playerId]) {
+ const otherPlayer = this.otherPlayers[playerInfo.playerId];
+ otherPlayer.setPosition(playerInfo.x, playerInfo.y);
+ otherPlayer.play(playerInfo.anim, true);
+ otherPlayer.flipX = playerInfo.flipX;
+ }
+ });
+
+ this.socket.on('playerAttack', (playerInfo) => {
+ if (this.otherPlayers[playerInfo.playerId]) {
+ const otherPlayer = this.otherPlayers[playerInfo.playerId];
+ otherPlayer.play(playerInfo.anim);
+ }
+ });
+
+ this.socket.on('playerDamaged', (data) => {
+ if (this.otherPlayers[data.playerId]) {
+ this.otherPlayers[data.playerId].life = data.newLife;
+ // Update health bar if you have one for other players
+ }
+ });
+
+ this.socket.emit('joinScene', 'BridgeScene');
+ }
+
+ checkPlayersAndStartCountdown() {
+ const playerCount = Object.keys(this.otherPlayers).length + 1; // +1 for local player
+
+ if (playerCount >= 2 && !this.countdownStarted) {
+ this.countdownStarted = true;
+ this.startCountdown();
+ } else if (playerCount < 2) {
+ // Reset if a player leaves during countdown
+ this.countdownStarted = false;
+ this.canMove = false;
+ if (this.countdownText) {
+ this.countdownText.setText('Waiting for players...');
+ }
+ }
+ }
+
+ startCountdown() {
+ let count = 3;
+
+ this.countdownText.setText(count.toString());
+
+ const countdownTimer = this.time.addEvent({
+ delay: 1000,
+ callback: () => {
+ count--;
+ if (count > 0) {
+ this.countdownText.setText(count.toString());
+ } else if (count === 0) {
+ this.countdownText.setText('FIGHT!');
+ this.canMove = true;
+
+ this.time.delayedCall(500, () => {
+ this.countdownText.destroy();
+ });
+ }
+ },
+ repeat: 3
+ });
+ }
+
+ createHealthBar(x, y, player) {
+ const width = 40;
+ const height = 5;
+
+ // White outline
+ const outline = this.add.rectangle(x, y - 20, width + 2, height + 2, 0xffffff);
+
+ // Black background
+ const healthBarBackground = this.add.rectangle(x, y - 20, width, height, 0x000000);
+
+ // Red health bar - set origin to left
+ const healthBar = this.add.rectangle(x - width/2, y - 20, width, height, 0xff0000)
+ .setOrigin(0, 0.5);
+
+ return {
+ outline: outline,
+ background: healthBarBackground,
+ bar: healthBar
+ };
+ }
+
+ update() {
+ const speed = 80;
+ const prevVelocity = this.player.body.velocity.clone();
+ let newX = this.player.x;
+ let newY = this.player.y;
+
+ // this.input.keyboard.addListener("keydown-F", (e) => {
+ // console.log(e)
+ // this.player.play("attackRight");
+ // })
+
+ // Stop any previous movement from the last frame
+ this.player.body.setVelocity(0);
+
+ // Horizontal movement
+ if (this.cursors.left.isDown) {
+ newX -= speed * (1 / 60);
+ if (!this.level.isColliding(newX, newY)) {
+ this.player.body.setVelocityX(-speed);
+ this.player.anims.play("walkRight", true); // Assuming you have a 'walkLeft' animation
+ this.player.flipX = true; // Flip the sprite to face left
+ }
+ } else if (this.cursors.right.isDown) {
+ newX += speed * (1 / 60);
+ if (!this.level.isColliding(newX, newY)) {
+ this.player.body.setVelocityX(speed);
+ this.player.anims.play("walkRight", true);
+ this.player.flipX = false; // Ensure the sprite is facing right
+ }
+ }
+
+ // Vertical movement
+ if (this.cursors.up.isDown) {
+ newY -= speed * (1 / 60);
+ if (!this.level.isColliding(newX, newY)) {
+ this.player.body.setVelocityY(-speed);
+ this.player.anims.play("walkUp", true);
+ }
+ } else if (this.cursors.down.isDown) {
+ newY += speed * (1 / 60);
+ if (!this.level.isColliding(newX, newY)) {
+ console.log("not");
+ this.player.body.setVelocityY(speed);
+ this.player.anims.play("walkDown", true);
+ }
+ }
+
+ // Normalize and scale the velocity so that player can't move faster along a diagonal
+ this.player.body.velocity.normalize().scale(speed);
+
+ // If no movement keys are pressed, stop the animation
+ if (
+ this.cursors.left.isUp &&
+ this.cursors.right.isUp &&
+ this.cursors.up.isUp &&
+ this.cursors.down.isUp
+ ) {
+ this.player.anims.stop();
+
+ // Set idle animation based on the last direction
+ if (prevVelocity.x < 0) {
+ this.player.anims.play("idleRight", true);
+ this.player.flipX = true;
+ } else if (prevVelocity.x > 0) {
+ this.player.anims.play("idleRight", true);
+ this.player.flipX = false;
+ } else if (prevVelocity.y < 0) {
+ this.player.anims.play("idleUp", true);
+ } else if (prevVelocity.y > 0) {
+ this.player.anims.play("idleDown", true);
+ }
+ }
+
+ // Update health bar position and width
+ if (this.healthBar) {
+ const yOffset = -20;
+ const width = 40;
+
+ this.healthBar.outline.x = this.player.x;
+ this.healthBar.outline.y = this.player.y + yOffset;
+ this.healthBar.background.x = this.player.x;
+ this.healthBar.background.y = this.player.y + yOffset;
+
+ // Update red bar position and width
+ this.healthBar.bar.x = this.player.x - width/2;
+ this.healthBar.bar.y = this.player.y + yOffset;
+ this.healthBar.bar.width = (this.player.life / 100) * width;
+ }
+ }
+}
diff --git a/client/CommonScene.js b/client/CommonScene.js
new file mode 100644
index 0000000..e85cba6
--- /dev/null
+++ b/client/CommonScene.js
@@ -0,0 +1,590 @@
+import { AnimationManager } from './managers/AnimationManager'
+import { PlayerManager } from './managers/PlayerManager'
+import { CombatManager} from './managers/CombatManager'
+import Level from './Level.js'
+import { io } from 'socket.io-client'
+import { XPBar } from './managers/XPBar'
+const socketUrl = "https://latch.netlify.app/game";
+
+export default class CommonScene extends Phaser.Scene {
+ constructor() {
+ super('CommonScene')
+ this.player = null
+ this.otherPlayers = {}
+ this.level = new Level()
+ this.spike = null
+ this.lastEmitTime = 0
+ }
+
+ preload() {
+ this.load.image('Apple_Tree', '/assets/Apple_Tree.png')
+ this.load.image('Barn', '/assets/Barn.png')
+ this.load.image('Beach_Decor_Tiles', '/assets/Beach_Decor_Tiles.png')
+ this.load.image('Beach_Tile', '/assets/Beach_Tile.png')
+ this.load.image('Birch_Tree', '/assets/Birch_Tree.png')
+ this.load.image('Boat', '/assets/Boat.png')
+ this.load.image('Cobble_Road_1', '/assets/Cobble_Road_1.png')
+ this.load.image('Cobble_Road_2', '/assets/Cobble_Road_2.png')
+ this.load.image('Fences', '/assets/Fences.png')
+ this.load.image('Fountain', '/assets/Fountain.png')
+ this.load.image('Grass_Middle', '/assets/Grass_Middle.png')
+ this.load.image('Grass_Tiles_1', '/assets/Grass_Tiles_1.png')
+ this.load.image('Water_Middle', '/assets/Water_Middle.png')
+ this.load.image('Water_Tile', '/assets/Water_Tile.png')
+ this.load.image('Well', '/assets/Well.png')
+ this.load.image('With_Hut', '/assets/With_Hut.png')
+ this.load.image('Cave_Floor', '/assets/Cave_Floor.png')
+ this.load.image('Water_Troughs', '/assets/Water_Troughs.png')
+
+ this.load.tilemapTiledJSON('common', '/assets/common.json')
+ this.load.spritesheet('player', '/assets/Spearman.png', {
+ frameWidth: 48,
+ frameHeight: 48,
+ })
+ }
+
+ create() {
+ const spawnData = this.scene.settings.data;
+ if (spawnData && spawnData.x && spawnData.y) {
+ this.game.config.width = spawnData.x;
+ this.game.config.height = spawnData.y;
+ }
+
+ const map = this.make.tilemap({ key: 'common' })
+ const grass = map.addTilesetImage('Grass_Middle', 'Grass_Middle')
+ const water = map.addTilesetImage('Water_Tile', 'Water_Tile')
+ const waterMiddle = map.addTilesetImage('Water_Middle', 'Water_Middle')
+ const Cobble_Road_1 = map.addTilesetImage('Cobble_Road_1', 'Cobble_Road_1')
+ const Grass_Tiles_1 = map.addTilesetImage('Grass_Tiles_1', 'Grass_Tiles_1')
+ const Cave_Floor = map.addTilesetImage('Cave_Floor', 'Cave_Floor')
+ const beachDecorTiles = map.addTilesetImage(
+ 'Beach_Decor_Tiles',
+ 'Beach_Decor_Tiles'
+ )
+ const layer1 = map.createLayer(
+ 'floor',
+ [
+ grass,
+ waterMiddle,
+ water,
+ beachDecorTiles,
+ Cobble_Road_1,
+ Grass_Tiles_1,
+ Cave_Floor,
+ ],
+ 0,
+ 0
+ )
+ layer1
+ .setScale(1, 1)
+ .setOrigin(0, 0)
+ .setCollisionByProperty({ collider: true })
+
+ const Fountain = map.addTilesetImage('Fountain', 'Fountain')
+ const Barn = map.addTilesetImage('Barn', 'Barn')
+ const Fences = map.addTilesetImage('Fences', 'Fences')
+ const Well = map.addTilesetImage('Well', 'Well')
+ const Boat = map.addTilesetImage('Boat', 'Boat')
+ const With_Hut = map.addTilesetImage('With_Hut', 'With_Hut')
+ const Birch_Tree = map.addTilesetImage('Birch_Tree', 'Birch_Tree')
+ const Apple_Tree = map.addTilesetImage('Apple_Tree', 'Apple_Tree')
+ const Water_Troughs = map.addTilesetImage('Water_Troughs', 'Water_Troughs')
+ const objectLayer = map.createLayer(
+ 'objects',
+ [
+ Fountain,
+ Barn,
+ Fences,
+ Well,
+ Boat,
+ With_Hut,
+ Birch_Tree,
+ Apple_Tree,
+ Grass_Tiles_1,
+ Cave_Floor,
+ Water_Troughs,
+ ],
+ 0,
+ 0
+ )
+ objectLayer
+ .setScale(1, 1)
+ .setOrigin(0, 0)
+ .setCollisionByProperty({ collider: true })
+
+ // Clear any existing labels first
+ this.children.list
+ .filter(child => child.type === 'Text')
+ .forEach(label => label.destroy());
+
+ // Track positions where we've already placed labels
+ const labelPositions = new Set();
+
+ // Function to check if position is too close to existing labels
+ const isTooClose = (x, y) => {
+ for (let pos of labelPositions) {
+ const [existingX, existingY] = pos.split(',').map(Number);
+ const distance = Math.sqrt(Math.pow(existingX - x, 2) + Math.pow(existingY - y, 2));
+ if (distance < 50) { // Adjust this number to change how close labels can be
+ return true;
+ }
+ }
+ return false;
+ };
+
+ // Add permanent scene transition labels
+ const dungeonTiles = objectLayer.filterTiles(tile => tile.properties.dungeon);
+ dungeonTiles.forEach(tile => {
+ const posKey = `${tile.pixelX},${tile.pixelY}`;
+ if (!isTooClose(tile.pixelX, tile.pixelY)) {
+ this.add.text(tile.pixelX, tile.pixelY - 20, 'Battle', {
+ fontFamily: 'Verdana',
+ fontSize: '10px',
+ fill: '#fff',
+ backgroundColor: '',
+ padding: { x: 5, y: 2 }
+ }).setOrigin(0.5);
+ labelPositions.add(posKey);
+ }
+ });
+
+ const bridgeTiles = objectLayer.filterTiles(tile => tile.properties.bridge);
+ bridgeTiles.forEach(tile => {
+ const posKey = `${tile.pixelX},${tile.pixelY}`;
+ if (!isTooClose(tile.pixelX, tile.pixelY)) {
+ this.add.text(tile.pixelX + 16, tile.pixelY - 20, 'Battle', {
+ fontFamily: 'Verdana',
+ fontSize: '10px',
+ fill: '#fff',
+ backgroundColor: '',
+ padding: { x: 5, y: 2 }
+ }).setOrigin(0.5);
+ labelPositions.add(posKey);
+ }
+ });
+
+ this.socket = io(socketId, {
+ withCredentials: false,
+ })
+ this.player = this.physics.add
+ .sprite(this.game.config.width / 2, this.game.config.height / 2, 'player')
+ .setScale(1)
+
+ this.player.setCollideWorldBounds(true)
+ this.player.life = 100
+ this.player.attack = 0
+ this.player.weapon = 'sword'
+ this.player.setScale(1) // Scale the player sprite by 1.5 times
+ this.player.setBodySize(24, 28)
+ this.player.setOffset(10, 13)
+ this.cursors = this.input.keyboard.createCursorKeys()
+ this.anims.create({
+ key: 'idleDown',
+ frames: this.anims.generateFrameNumbers('player', { start: 0, end: 5 }),
+ frameRate: 10,
+ repeat: -1,
+ })
+ let element = document.getElementById('input-box')
+ const yesButton = document.getElementById('yes')
+ const noButton = document.getElementById('no')
+ this.physics.add.collider(this.player, layer1)
+ this.physics.add.collider(this.player, objectLayer, (a, b) => {
+ if (b?.properties?.dungeon) {
+ element.style.display = 'block'
+ yesButton.addEventListener('click', () => {
+ this.socket.emit('changeScene', {
+ from: 'CommonScene',
+ to: 'DungeonScene'
+ });
+ this.scene.start('DungeonScene')
+ element.style.display = 'none'
+ })
+ noButton.addEventListener('click', () => {
+ element.style.display = 'none'
+ })
+ }
+ if (b?.properties?.bridge) {
+ element.style.display = 'block'
+ yesButton.addEventListener('click', () => {
+ this.scene.start('BridgeScene')
+ element.style.display = 'none'
+ })
+ noButton.addEventListener('click', () => {
+ element.style.display = 'none'
+ })
+ }
+ })
+
+ // this.cameras.main.setBounds(0, 0, +this.game.config.width, +this.game.config.height);
+ this.cameras.main.startFollow(this.player, true)
+ this.cameras.main.setFollowOffset(-50, -50)
+
+ this.anims.create({
+ key: 'idleRight',
+ frames: this.anims.generateFrameNumbers('player', { start: 6, end: 11 }),
+ frameRate: 10,
+ repeat: -1,
+ })
+
+ this.anims.create({
+ key: 'idleUp',
+ frames: this.anims.generateFrameNumbers('player', { start: 12, end: 17 }),
+ frameRate: 10,
+ repeat: -1,
+ })
+
+ this.anims.create({
+ key: 'walkDown',
+ frames: this.anims.generateFrameNumbers('player', { start: 18, end: 23 }),
+ frameRate: 10,
+ repeat: -1,
+ })
+
+ this.anims.create({
+ key: 'walkRight',
+ frames: this.anims.generateFrameNumbers('player', { start: 24, end: 29 }),
+ frameRate: 10,
+ repeat: -1,
+ })
+
+ this.anims.create({
+ key: 'walkUp',
+ frames: this.anims.generateFrameNumbers('player', { start: 30, end: 35 }),
+ frameRate: 10,
+ repeat: -1,
+ })
+
+
+
+ AnimationManager.createAnimations(this)
+ this.player.play('idleDown')
+ this.handleSocketEvents()
+
+ // Add a attack kek
+ this.attackKey = this.input.keyboard.addKey(
+ Phaser.Input.Keyboard.KeyCodes.SPACE
+ )
+
+ // Add inventory key
+ this.inventoryKey = this.input.keyboard.addKey(
+ Phaser.Input.Keyboard.KeyCodes.I
+ )
+
+ // Create inventory (initially hidden)
+ this.createInventory()
+
+ // Add isAttacking flag
+ this.player.isAttacking = false
+
+ // Create XP bar with debug logging
+ console.log('Creating XP bar...');
+ this.xpBar = new XPBar(this);
+
+ // Test if XP bar exists and has required methods
+ console.log('XP Bar created:', {
+ exists: !!this.xpBar,
+ background: !!this.xpBar?.background,
+ fillBar: !!this.xpBar?.fillBar,
+ levelText: !!this.xpBar?.levelText,
+ xpText: !!this.xpBar?.xpText
+ });
+
+ // Force an initial update
+ this.xpBar.update(50, 1); // Changed from setXP to update
+
+ // Listen for XP updates
+ this.socket.on('xpUpdate', (data) => {
+ if (data.playerId === this.socket.id) {
+ this.xpBar.update(data.xp, data.level); // Changed from setXP to update
+ }
+ });
+
+ // Request initial XP data
+ this.socket.emit('requestXPData');
+ }
+
+ handleSocketEvents() {
+ const socket = this.socket
+
+ socket.on('currentPlayers', (players) => {
+ Object.keys(players).forEach((id) => {
+ if (players[id].playerId === socket.id) {
+ this.player.setPosition(players[id].x, players[id].y)
+ this.player.lastDirection = players[id].lastDirection
+ } else {
+ this.addOtherPlayer(players[id])
+ }
+ })
+ })
+
+ socket.on('newPlayer', (playerInfo) => {
+ console.log('New player connected:', playerInfo)
+ this.addOtherPlayer(playerInfo)
+ })
+
+ socket.on('playerMoved', (playerInfo) => {
+ if (!playerInfo || !playerInfo.playerId) return;
+
+ // Handle local player
+ if (playerInfo.playerId === this.socket.id) {
+ if (this.player && this.player.anims && !this.player.isAttacking) {
+ try {
+ this.player.anims.play(playerInfo.animation, true);
+ this.player.flipX = playerInfo.flipX;
+ this.player.lastDirection = playerInfo.lastDirection;
+ } catch (error) {
+ console.warn('Local player animation error:', error);
+ }
+ }
+ }
+ // Handle other players
+ else if (this.otherPlayers && this.otherPlayers[playerInfo.playerId]) {
+ const otherPlayer = this.otherPlayers[playerInfo.playerId];
+
+ // Update position
+ if (otherPlayer && typeof otherPlayer.setPosition === 'function') {
+ otherPlayer.setPosition(playerInfo.x, playerInfo.y);
+ }
+
+ // Update animation
+ if (otherPlayer && otherPlayer.anims && !otherPlayer.isAttacking) {
+ try {
+ if (playerInfo.animation && this.anims.exists(playerInfo.animation)) {
+ otherPlayer.anims.play(playerInfo.animation, true);
+ }
+ if (typeof playerInfo.flipX !== 'undefined') {
+ otherPlayer.flipX = playerInfo.flipX;
+ }
+ if (playerInfo.lastDirection) {
+ otherPlayer.lastDirection = playerInfo.lastDirection;
+ }
+ } catch (error) {
+ console.warn('Other player animation error:', error);
+ }
+ }
+ }
+ });
+
+ socket.on('playerAttacked', (data) => {
+ if (this.otherPlayers[data.target]) {
+ this.otherPlayers[data.target].life = data.life
+ console.log('Player attacked:', data)
+ }
+ })
+ socket.on('playerAttackAnimation', (data) => {
+ console.log('Received attack animation:', data)
+
+ if (data.attacker === this.socket.id) {
+ return
+ }
+ const otherPlayer = this.otherPlayers[data.attacker]
+ if (otherPlayer) {
+ PlayerManager.handleOtherPlayerAttack(
+ otherPlayer,
+ data.animation,
+ data.direction
+ )
+ }
+ })
+
+ socket.on('playerDefeated', (playerId) => {
+ CombatManager.handlePlayerDeath(this, playerId)
+ console.log('Player defeated:', playerId)
+
+ })
+
+ socket.on('playerDisconnected', (playerId) => {
+ if (this.otherPlayers[playerId]) {
+ this.otherPlayers[playerId].destroy()
+ delete this.otherPlayers[playerId]
+ }
+ })
+ }
+
+ addOtherPlayer(playerInfo) {
+ const otherPlayer = this.physics.add.sprite(
+ playerInfo.x,
+ playerInfo.y,
+ 'player'
+ )
+ otherPlayer.playerId = playerInfo.playerId
+ otherPlayer.life = playerInfo.life
+ otherPlayer.attack = playerInfo.attack
+ otherPlayer.weapon = playerInfo.weapon
+ otherPlayer.lastDirection = playerInfo.lastDirection || 'Down'
+ otherPlayer.isAttacking = false
+ otherPlayer.setScale(1)
+ this.otherPlayers[playerInfo.playerId] = otherPlayer
+ console.log('Added other player:', playerInfo)
+ }
+
+ createInventory() {
+ // Create inventory container
+ const padding = 10
+ const cellSize = 40
+ const rows = 4
+ const cols = 6
+ const width = cellSize * cols + padding * 2
+ const height = cellSize * rows + padding * 2
+
+ // Position in center of screen
+ const x = this.cameras.main.centerX - width / 2
+ const y = this.cameras.main.centerY - height / 2
+
+ // Create semi-transparent background
+ this.inventoryBg = this.add
+ .rectangle(x, y, width, height, 0x000000)
+ .setOrigin(0, 0)
+ .setAlpha(0.7)
+ .setScrollFactor(0)
+ .setDepth(1000);
+
+ // Create grid cells
+ this.inventorySlots = []
+ for (let row = 0; row < rows; row++) {
+ for (let col = 0; col < cols; col++) {
+ const slotX = x + padding + col * cellSize
+ const slotY = y + padding + row * cellSize
+
+ // Create slot background
+ const slot = this.add
+ .rectangle(slotX, slotY, cellSize - 2, cellSize - 2, 0x666666)
+ .setOrigin(0, 0)
+ .setAlpha(0.8)
+ .setScrollFactor(0)
+ .setDepth(1001);
+
+ this.inventorySlots.push(slot)
+ }
+ }
+
+ // Hide inventory initially
+ this.hideInventory()
+ }
+
+ hideInventory() {
+ this.inventoryBg.setVisible(false)
+ this.inventorySlots.forEach((slot) => slot.setVisible(false))
+ }
+
+ showInventory() {
+ this.inventoryBg.setVisible(true)
+ this.inventorySlots.forEach((slot) => slot.setVisible(true))
+ }
+
+ update(time, delta) {
+ if (!this.player) return;
+
+ // Update depths based on Y position, but keep lower than UI elements
+ this.player.setDepth(this.player.y + 100); // Base player depth on Y position
+
+ Object.values(this.otherPlayers).forEach(otherPlayer => {
+ otherPlayer.setDepth(otherPlayer.y + 100); // Same for other players
+ });
+
+ const speed = 80;
+ let animation = this.lastDirection ? 'idle' + this.lastDirection : 'idleDown';
+
+ // Stop any previous movement
+ this.player.body.setVelocity(0);
+
+ if (!this.player.isAttacking) {
+ // Handle movement and set last direction
+ if (this.cursors.left.isDown) {
+ this.player.body.setVelocityX(-speed);
+ animation = 'walkRight';
+ this.lastDirection = 'Right';
+ this.player.flipX = true;
+ } else if (this.cursors.right.isDown) {
+ this.player.body.setVelocityX(speed);
+ animation = 'walkRight';
+ this.lastDirection = 'Right';
+ this.player.flipX = false;
+ }
+
+ if (this.cursors.up.isDown) {
+ this.player.body.setVelocityY(-speed);
+ animation = 'walkUp';
+ this.lastDirection = 'Up';
+ } else if (this.cursors.down.isDown) {
+ this.player.body.setVelocityY(speed);
+ animation = 'walkDown';
+ this.lastDirection = 'Down';
+ }
+
+ // Normalize and scale the velocity
+ this.player.body.velocity.normalize().scale(speed);
+
+ // Play the animation
+ this.player.anims.play(animation, true);
+ }
+
+ // Emit movement to server
+ if (time - this.lastEmitTime > 16) {
+ this.socket.emit('playerInput', {
+ x: this.player.x,
+ y: this.player.y,
+ animation: animation,
+ flipX: this.player.flipX,
+ lastDirection: this.lastDirection,
+ });
+ this.lastEmitTime = time;
+ }
+
+ // Handle attack
+ if (Phaser.Input.Keyboard.JustDown(this.attackKey)) {
+ this.handleAttack();
+ }
+
+ // Handle inventory toggle
+ if (Phaser.Input.Keyboard.JustDown(this.inventoryKey)) {
+ if (this.inventoryBg.visible) {
+ this.hideInventory();
+ } else {
+ this.showInventory();
+ }
+ }
+
+ // Update health bar
+ if (this.healthBar) {
+ const yOffset = -20;
+ const width = 40;
+
+ this.healthBar.outline.x = this.player.x;
+ this.healthBar.outline.y = this.player.y + yOffset;
+ this.healthBar.background.x = this.player.x;
+ this.healthBar.background.y = this.player.y + yOffset;
+
+ this.healthBar.bar.x = this.player.x - width/2;
+ this.healthBar.bar.y = this.player.y + yOffset;
+ this.healthBar.bar.width = (this.player.life / 100) * width;
+ }
+ }
+
+ handleAttack() {
+ // Find the closest player to attack
+ let closestPlayer = null
+ let closestDistance = Infinity
+
+ Object.keys(this.otherPlayers).forEach((id) => {
+ const otherPlayer = this.otherPlayers[id]
+ const distance = Phaser.Math.Distance.Between(
+ this.player.x,
+ this.player.y,
+ otherPlayer.x,
+ otherPlayer.y
+ )
+ if (distance < closestDistance) {
+ closestDistance = distance
+ closestPlayer = otherPlayer
+ }
+ })
+
+ if (closestPlayer && closestDistance < 50) {
+ // Adjust attack range as needed
+ // this.socket.emit('attackPlayer', closestPlayer.playerId)
+ PlayerManager.handleAttack(this, this.player, closestPlayer.playerId)
+ console.log('Attacking player:', closestPlayer.playerId)
+ }
+ }
+}
diff --git a/client/DungeonScene.js b/client/DungeonScene.js
new file mode 100644
index 0000000..cb6e284
--- /dev/null
+++ b/client/DungeonScene.js
@@ -0,0 +1,409 @@
+import Phaser from "phaser";
+import Level from "./Level.js";
+import { PlayerManager } from './managers/PlayerManager'
+
+export default class DungeonScene extends Phaser.Scene {
+ constructor() {
+ super("DungeonScene");
+ this.player = null;
+ this.level = new Level();
+ this.spike = null;
+ this.healthBar = null;
+ this.otherPlayers = {};
+ this.canMove = false;
+ this.countdownText = null;
+ this.countdownStarted = false;
+ }
+
+ preload() {
+ this.load.image("Dungeon_1", "/assets/Dungeon_1.png");
+ this.load.image("windows", "/assets/Dungeon_2_Arch_small.png");
+ this.load.image("pillars", "/assets/Dungeon_2_Pillars.png");
+ this.load.image("objects", "/assets/Dungeon_Objects.png");
+ this.load.image("spikes", "/assets/Floor_spikes_1.png");
+ this.load.tilemapTiledJSON("dungeon", "assets/dmap.json");
+ this.load.spritesheet("player", "assets/Spearman.png", {
+ frameWidth: 48,
+ frameHeight: 48,
+ });
+ this.load.spritesheet("spike", "assets/spk1.png", {
+ frameWidth: 48,
+ frameHeight: 48,
+ });
+ // this.load.image("spike", "assets/tile000.png");
+ }
+
+ create() {
+this.game.scale.resize(256,256)
+ const map = this.make.tilemap({ key: "dungeon" });
+ const floor = map.addTilesetImage("Dungeon_1", "Dungeon_1");
+ const floorLayer = map.createLayer("Tile Layer 1", [floor], 0, -100);
+ floorLayer.setScale(1, 2).setOrigin(0, 0);
+
+ const windows = map.addTilesetImage("windows", "windows");
+ const windowsLayer = map.createLayer("windows", [windows], 0, 0);
+ windowsLayer.setScale(1, 1).setOrigin(0, 0);
+
+ const pillars = map.addTilesetImage("pillars", "pillars");
+ const pillarLayer = map.createLayer("pillars", pillars, 0, 0);
+ pillarLayer.setScale(1, 1).setOrigin(0, 0);
+ pillarLayer.setCollisionByProperty({ collider: true });
+
+ const objects = map.addTilesetImage("objects", "objects");
+ const spikes = map.addTilesetImage("spikes", "spikes");
+ const objectLayer = map.createLayer("objects", [objects, spikes], 0, 0);
+ objectLayer
+ .setScale(1, 1)
+ .setOrigin(0, 0)
+ .setCollisionByProperty({ collider: true });
+ // this.spike = this.physics.add.image(
+ // this.game.config.width / 2 - 70,
+ // this.game.config.height / 2 - 25,
+ // "spike",
+ // );
+ //
+ // this.anims.create({
+ // key: "spike-anim",
+ // frames: this.anims.generateFrameNumbers("spike", { start: 0, end: 7 }),
+ // frameRate: 10,
+ // repeat: -1,
+ // });
+ // this.spike.play("spike-anim", true);
+
+ this.player = this.physics.add
+ .sprite(
+ 256/2 - 50,
+ 256/2 - 35,
+ "player",
+ )
+ .setScale(1);
+
+ this.player.life = 100;
+
+ this.player.setScale(1); // Scale the player sprite by 1.5 times
+ this.player.setBodySize(24, 28);
+ this.player.setOffset(10, 13);
+ this.cursors = this.input.keyboard.createCursorKeys();
+ this.anims.create({
+ key: "idleDown",
+ frames: this.anims.generateFrameNumbers("player", { start: 0, end: 5 }),
+ frameRate: 10,
+ repeat: -1,
+ });
+
+ this.physics.add.collider(this.player, pillarLayer);
+ this.physics.add.collider(this.player, objectLayer);
+
+ this.anims.create({
+ key: "idleRight",
+ frames: this.anims.generateFrameNumbers("player", { start: 6, end: 11 }),
+ frameRate: 10,
+ repeat: -1,
+ });
+
+ this.anims.create({
+ key: "idleUp",
+ frames: this.anims.generateFrameNumbers("player", { start: 12, end: 17 }),
+ frameRate: 10,
+ repeat: -1,
+ });
+
+ this.anims.create({
+ key: "walkDown",
+ frames: this.anims.generateFrameNumbers("player", { start: 18, end: 23 }),
+ frameRate: 10,
+ repeat: -1,
+ });
+
+ this.anims.create({
+ key: "walkRight",
+ frames: this.anims.generateFrameNumbers("player", { start: 24, end: 29 }),
+ frameRate: 10,
+ repeat: -1,
+ });
+
+ this.anims.create({
+ key: "walkUp",
+ frames: this.anims.generateFrameNumbers("player", { start: 30, end: 35 }),
+ frameRate: 10,
+ repeat: -1,
+ });
+
+ this.anims.create({
+ key: "attackDown",
+ frames: this.anims.generateFrameNumbers("player", { start: 36, end: 39 }),
+ frameRate: 10,
+ repeat: 1,
+ });
+
+ this.anims.create({
+ key: "attackRight",
+ frames: this.anims.generateFrameNumbers("player", { start: 42, end: 46 }),
+ frameRate: 10,
+ repeat: -1,
+ });
+
+ this.anims.create({
+ key: "attackUp",
+ frames: this.anims.generateFrameNumbers("player", { start: 48, end: 52 }),
+ frameRate: 10,
+ repeat: -1,
+ });
+
+ this.anims.create({
+ key: "die",
+ frames: this.anims.generateFrameNumbers("player", { start: 54, end: 56 }),
+ frameRate: 10,
+ repeat: 0,
+ });
+ this.player.play("idleDown");
+ this.healthBar = this.createHealthBar(this.player.x, this.player.y, this.player);
+
+ this.attackKey = this.input.keyboard.addKey(
+ Phaser.Input.Keyboard.KeyCodes.SPACE
+ );
+
+ // Add leave button
+ const leaveButton = this.add.text(
+ this.cameras.main.width - 2,
+ this.cameras.main.height - 2,
+ 'Quit Match',
+ {
+ fontFamily: 'Verdana',
+ fontSize: '10px',
+ fill: '#fff',
+ backgroundColor: '#000',
+ padding: { x: 5, y: 2 }
+ }
+ )
+ .setOrigin(1, 1)
+ .setScrollFactor(0)
+ .setInteractive()
+ .setDepth(1000);
+
+ leaveButton.on('pointerdown', () => {
+ console.log('Leave button clicked'); // Debug log
+
+ if (this.socket) {
+ this.socket.emit('leaveScene', {
+ from: 'DungeonScene',
+ to: 'CommonScene'
+ });
+ }
+
+ // Reload the whole game
+ window.location.reload();
+ });
+
+ // Add waiting text instead of starting countdown immediately
+ this.countdownText = this.add.text(
+ this.cameras.main.width / 2,
+ this.cameras.main.height / 2,
+ 'In Queue...',
+ {
+ fontSize: '32px',
+ fill: '#fff',
+ stroke: '#000',
+ strokeThickness: 4
+ }
+ )
+ .setOrigin(0.5)
+ .setScrollFactor(0)
+ .setDepth(1000);
+
+ // Listen for player join/leave events
+ this.socket.on('playerJoined', () => this.checkPlayersAndStartCountdown());
+ this.socket.on('playerLeft', () => this.checkPlayersAndStartCountdown());
+ }
+
+ createHealthBar(x, y, player) {
+ const width = 40;
+ const height = 5;
+
+ // White outline
+ const outline = this.add.rectangle(x, y - 40, width + 2, height + 2, 0xffffff);
+
+ // Black background
+ const healthBarBackground = this.add.rectangle(x, y - 40, width, height, 0x000000);
+
+ // Red health bar - set origin to left
+ const healthBar = this.add.rectangle(x - width/2, y - 40, width, height, 0xff0000)
+ .setOrigin(0, 0.5);
+
+ return {
+ outline: outline,
+ background: healthBarBackground,
+ bar: healthBar
+ };
+ }
+
+ update() {
+ const speed = 80;
+ const prevVelocity = this.player.body.velocity.clone();
+ let newX = this.player.x;
+ let newY = this.player.y;
+
+ // this.input.keyboard.addListener("keydown-F", (e) => {
+ // console.log(e)
+ // this.player.play("attackRight");
+ // })
+
+ // Stop any previous movement from the last frame
+ this.player.body.setVelocity(0);
+
+ // Horizontal movement
+ if (this.cursors.left.isDown) {
+ newX -= speed * (1 / 60);
+ if (!this.level.isColliding(newX, newY)) {
+ this.player.body.setVelocityX(-speed);
+ this.player.anims.play("walkRight", true); // Assuming you have a 'walkLeft' animation
+ this.player.flipX = true; // Flip the sprite to face left
+ }
+ } else if (this.cursors.right.isDown) {
+ newX += speed * (1 / 60);
+ if (!this.level.isColliding(newX, newY)) {
+ this.player.body.setVelocityX(speed);
+ this.player.anims.play("walkRight", true);
+ this.player.flipX = false; // Ensure the sprite is facing right
+ }
+ }
+
+ // Vertical movement
+ if (this.cursors.up.isDown) {
+ newY -= speed * (1 / 60);
+ if (!this.level.isColliding(newX, newY)) {
+ this.player.body.setVelocityY(-speed);
+ this.player.anims.play("walkUp", true);
+ }
+ } else if (this.cursors.down.isDown) {
+ newY += speed * (1 / 60);
+ console.log(this.level.isColliding(newX, newY));
+ if (!this.level.isColliding(newX, newY)) {
+ console.log("not");
+ this.player.body.setVelocityY(speed);
+ this.player.anims.play("walkDown", true);
+ }
+ }
+
+ // Normalize and scale the velocity so that player can't move faster along a diagonal
+ this.player.body.velocity.normalize().scale(speed);
+
+ // If no movement keys are pressed, stop the animation
+ if (
+ this.cursors.left.isUp &&
+ this.cursors.right.isUp &&
+ this.cursors.up.isUp &&
+ this.cursors.down.isUp
+ ) {
+ this.player.anims.stop();
+
+ // Set idle animation based on the last direction
+ if (prevVelocity.x < 0) {
+ this.player.anims.play("idleRight", true);
+ this.player.flipX = true;
+ } else if (prevVelocity.x > 0) {
+ this.player.anims.play("idleRight", true);
+ this.player.flipX = false;
+ } else if (prevVelocity.y < 0) {
+ this.player.anims.play("idleUp", true);
+ } else if (prevVelocity.y > 0) {
+ this.player.anims.play("idleDown", true);
+ }
+ }
+
+ // Update health bar position and width
+ if (this.healthBar) {
+ const yOffset = -20;
+ const width = 40;
+
+ this.healthBar.outline.x = this.player.x;
+ this.healthBar.outline.y = this.player.y + yOffset;
+ this.healthBar.background.x = this.player.x;
+ this.healthBar.background.y = this.player.y + yOffset;
+
+ // Update red bar position and width
+ this.healthBar.bar.x = this.player.x - width/2;
+ this.healthBar.bar.y = this.player.y + yOffset;
+ this.healthBar.bar.width = (this.player.life / 100) * width;
+ }
+ }
+
+ handleSocketEvents() {
+ // ... keep existing socket events ...
+
+ // Check if this handler exists and has flipX
+ this.socket.on('playerMoved', (playerInfo) => {
+ if (this.otherPlayers[playerInfo.playerId]) {
+ const otherPlayer = this.otherPlayers[playerInfo.playerId];
+ otherPlayer.setPosition(playerInfo.x, playerInfo.y);
+ otherPlayer.anims.play(playerInfo.animation, true);
+ otherPlayer.flipX = playerInfo.flipX; // Make sure this is here
+ }
+ });
+ }
+
+ getSpawnPoint() {
+ // Define two specific spawn points
+ const spawnPoints = [
+ { x: 50, y: 90 }, // First player spawn
+ { x: 200, y: 90 } // Second player spawn
+ ];
+
+ // Count existing players to determine spawn point
+ const playerCount = Object.keys(this.otherPlayers).length;
+
+ // First player gets first spawn, second player gets second spawn
+ const spawnPoint = spawnPoints[playerCount] || spawnPoints[0];
+
+ // Validate the chosen point
+ if (!this.level.isColliding(spawnPoint.x, spawnPoint.y)) {
+ return spawnPoint;
+ }
+
+ // Fallback to center if spawn point is invalid
+ return {
+ x: this.cameras.main.width / 2,
+ y: this.cameras.main.height / 2
+ };
+ }
+
+ checkPlayersAndStartCountdown() {
+ const playerCount = Object.keys(this.otherPlayers).length + 1; // +1 for local player
+
+ if (playerCount >= 2 && !this.countdownStarted) {
+ this.countdownStarted = true;
+ this.startCountdown();
+ } else if (playerCount < 2) {
+ // Reset if a player leaves during countdown
+ this.countdownStarted = false;
+ this.canMove = false;
+ if (this.countdownText) {
+ this.countdownText.setText('In Queue...');
+ }
+ }
+ }
+
+ startCountdown() {
+ let count = 3;
+
+ this.countdownText.setText(count.toString());
+
+ const countdownTimer = this.time.addEvent({
+ delay: 1000,
+ callback: () => {
+ count--;
+ if (count > 0) {
+ this.countdownText.setText(count.toString());
+ } else if (count === 0) {
+ this.countdownText.setText('FIGHT!');
+ this.canMove = true;
+
+ this.time.delayedCall(500, () => {
+ this.countdownText.destroy();
+ });
+ }
+ },
+ repeat: 3
+ });
+ }
+}
diff --git a/client/dungeonmap/src/BridgeScene.js b/client/dungeonmap/src/BridgeScene.js
index b001264..1dd9bf5 100644
--- a/client/dungeonmap/src/BridgeScene.js
+++ b/client/dungeonmap/src/BridgeScene.js
@@ -1,239 +1,1102 @@
import Phaser from "phaser";
import Level from "./Level.js";
+import BackgroundScene from './managers/backgroundscene'
+import { PlayerManager } from './managers/PlayerManager'
+import { AnimationManager } from './managers/AnimationManager'
+import { CombatManager } from './managers/CombatManager'
+import { io } from 'socket.io-client'
+
+
export default class BridgeScene extends Phaser.Scene {
- constructor() {
- super("BridgeScene");
- this.player = null;
- this.level = new Level();
- this.spike = null;
- this.healthBar = null;
- }
-
- preload() {
- this.load.image("Bridge_Stone_Horizontal", "/assets/Bridge_Stone_Horizontal.png");
- this.load.image("Water_Tile", "/assets/Water_Tile.png");
- this.load.tilemapTiledJSON("bridge", "/assets/bridge.json");
- this.load.spritesheet("player", "/assets/Spearman.png", {
- frameWidth: 48,
- frameHeight: 48,
- });
+ constructor() {
+ super("BridgeScene");
+ this.player = null;
+ this.level = new Level();
+ this.spike = null;
+ this.healthBar = null;
+ this.otherPlayers = {};
+ this.canMove = false;
+ this.countdownText = null;
+ this.countdownStarted = false;
+ this.socket = null;
+ this.backgroundScene = null;
+ this.animationsCreated = false; // Track if animations are created
+ this.otherPlayersGroup = null;
+ this.playerWorldPosition = {};
+ this.playersGroup = null;
+ this.playerEventListeners = new Map();
+ this.localPlayerId = null; // Track local player ID
+ this.hasJoinedScene = false; // Track if we've already joined
+ this.initializedPlayers = new Set(); // Track which players we've initialized
+ this.existingPlayers = new Set(); // Track existing player IDs
+ this.isAttacking = false; // Add attack state tracking
+ this.lastMovementUpdate = 0;
+ this.movementUpdateInterval = 50; // Update every 50ms
+ this.lastPosition = { x: 0, y: 0, animation: '', flipX: false };
+ }
+
+ init(data) {
+ console.log('BridgeScene init with data:', data);
+ // Only set socket once
+ if (!this.socket) {
+ if (data && data.socket) {
+ this.socket = data.socket;
+ } else {
+ this.backgroundScene = this.scene.get('BackgroundScene');
+ this.socket = this.backgroundScene.getSocket();
+ }
+ console.log('Socket initialized:', this.socket.id);
}
+ }
- create() {
- this.game.scale.resize(256,256)
- const map = this.make.tilemap({ key: "bridge" });
- const floor = map.addTilesetImage("Water_Tile", "Water_Tile");
-
- const floorLayer = map.createLayer("floor", [floor], 0, 0);
- floorLayer.setScale(1, 1).setOrigin(0, 0)
- .setCollisionByProperty({ collider: true });
-
- const Bridge_Stone_Horizontal = map.addTilesetImage("Bridge_Stone_Horizontal", "Bridge_Stone_Horizontal");
- const objectLayer = map.createLayer("object", [Bridge_Stone_Horizontal], 0, 0);
- objectLayer.setScale(1, 1).setOrigin(0, 0)
- .setCollisionByProperty({ collider: true });
-
-
- this.player = this.physics.add
- .sprite(
- 256/2 - 50,
- 256/2 - 35,
- "player",
- )
- .setScale(1);
-
- this.player.life = 100;
-
- this.player.setScale(1); // Scale the player sprite by 1.5 times
- this.player.setBodySize(24, 28);
- this.player.setOffset(10, 13);
- this.cursors = this.input.keyboard.createCursorKeys();
- this.anims.create({
- key: "idleDown",
- frames: this.anims.generateFrameNumbers("player", { start: 0, end: 5 }),
- frameRate: 10,
- repeat: -1,
- });
+ preload() {
+ this.load.image("Bridge_Stone_Horizontal", "/assets/Bridge_Stone_Horizontal.png");
+ this.load.image("Water_Tile", "/assets/Water_Tile.png");
+ this.load.tilemapTiledJSON("bridge", "/assets/bridge.json");
+ this.load.spritesheet("player", "/assets/Spearman.png", {
+ frameWidth: 48,
+ frameHeight: 48,
+ });
+ }
- this.physics.add.collider(this.player, objectLayer);
+ create() {
+ console.log('BridgeScene create starting');
+
+ // Initialize groups first, before any other creation logic
+ this.playersGroup = this.add.group();
+ this.otherPlayersGroup = this.add.group();
+
+ this.game.scale.resize(256,256);
+ const map = this.make.tilemap({ key: "bridge" });
+ const floor = map.addTilesetImage("Water_Tile", "Water_Tile");
- this.anims.create({
- key: "idleRight",
- frames: this.anims.generateFrameNumbers("player", { start: 6, end: 11 }),
- frameRate: 10,
- repeat: -1,
- });
+ const floorLayer = map.createLayer("floor", [floor], 0, 0);
+ floorLayer.setScale(1, 1).setOrigin(0, 0)
+ .setCollisionByProperty({ collider: true });
- this.anims.create({
- key: "idleUp",
- frames: this.anims.generateFrameNumbers("player", { start: 12, end: 17 }),
- frameRate: 10,
- repeat: -1,
- });
+ const Bridge_Stone_Horizontal = map.addTilesetImage("Bridge_Stone_Horizontal", "Bridge_Stone_Horizontal");
+ const objectLayer = map.createLayer("object", [Bridge_Stone_Horizontal], 0, 0);
+ objectLayer.setScale(1, 1).setOrigin(0, 0)
+ .setCollisionByProperty({ collider: true });
+
+ this.player = this.physics.add
+ .sprite(
+ 256/2 - 50,
+ 256/2 - 35,
+ "player",
+ )
+ .setScale(1);
+
+ this.player.life = 100;
- this.anims.create({
- key: "walkDown",
- frames: this.anims.generateFrameNumbers("player", { start: 18, end: 23 }),
- frameRate: 10,
- repeat: -1,
+ this.player.setScale(1); // Scale the player sprite by 1.5 times
+ this.player.setBodySize(24, 28);
+ this.player.setOffset(10, 13);
+ this.cursors = this.input.keyboard.createCursorKeys();
+
+ this.player.play("idleDown");
+ this.healthBar = this.createHealthBar(this.player.x, this.player.y, this.player);
+
+
+
+ // Add leave button
+ const leaveButton = this.add.text(
+ this.cameras.main.width - 2,
+ this.cameras.main.height - 2,
+ 'Quit Match',
+ {
+ fontFamily: 'Verdana',
+ fontSize: '10px',
+ fill: '#fff',
+ backgroundColor: '#000',
+ padding: { x: 5, y: 2 }
+ }
+ )
+ .setOrigin(1, 1)
+ .setScrollFactor(0)
+ .setInteractive()
+ .setDepth(1000);
+
+ leaveButton.on('pointerdown', () => {
+ console.log('Returning to common map');
+
+ if (this.socket) {
+ this.socket.emit('playerLeftScene', {
+ playerId: this.socket.id,
+ from: 'BridgeScene',
+ to: 'CommonScene'
+ });
+ }
+
+ // Reset game scale before transitioning
+ this.game.scale.resize(800, 600); // Set to CommonScene dimensions
+
+ this.scene.start('CommonScene', {
+ x: 620,
+ y: 360
});
+ });
+
+ // Add waiting text instead of starting countdown immediately
+ this.countdownText = this.add.text(
+ this.cameras.main.width / 2,
+ this.cameras.main.height / 2,
+ 'In Queue...',
+ {
+ fontSize: '32px',
+ fill: '#fff',
+ stroke: '#000',
+ strokeThickness: 4
+ }
+ )
+ .setOrigin(0.5)
+ .setScrollFactor(0)
+ .setDepth(1000);
+
+ // Listen for player join/leave events
+ this.socket.on('playerJoined', () => this.checkPlayersAndStartCountdown());
+ this.socket.on('playerLeft', () => this.checkPlayersAndStartCountdown());
+
+ // Create animations using AnimationManager
+ AnimationManager.createAnimations(this);
- this.anims.create({
- key: "walkRight",
- frames: this.anims.generateFrameNumbers("player", { start: 24, end: 29 }),
- frameRate: 10,
- repeat: -1,
+ // Setup socket events if socket exists
+ if (this.socket) {
+ // Remove any existing listeners first
+ this.socket.removeAllListeners('gameState');
+ this.socket.removeAllListeners('playerDisconnected');
+
+ // Add new listeners
+ this.socket.on('gameState', (state) => {
+ if (state && state.players) {
+ Object.entries(state.players).forEach(([playerId, playerData]) => {
+ if (playerId !== this.socket.id && !this.otherPlayers[playerId]) {
+ // Add new player
+ this.addOtherPlayer(playerData);
+ } else if (this.otherPlayers[playerId]) {
+ // Update existing player
+ const otherPlayer = this.otherPlayers[playerId];
+ otherPlayer.x = playerData.x;
+ otherPlayer.y = playerData.y;
+ if (playerData.animation) {
+ otherPlayer.play(playerData.animation, true);
+ }
+ }
+ });
+ }
});
- this.anims.create({
- key: "walkUp",
- frames: this.anims.generateFrameNumbers("player", { start: 30, end: 35 }),
- frameRate: 10,
- repeat: -1,
+ this.socket.on('playerDisconnected', (playerId) => {
+ if (this.otherPlayers[playerId]) {
+ this.otherPlayers[playerId].destroy();
+ delete this.otherPlayers[playerId];
+ }
});
- this.anims.create({
- key: "attackDown",
- frames: this.anims.generateFrameNumbers("player", { start: 36, end: 49 }),
- frameRate: 10,
- repeat: 1,
+ // Join the scene
+ this.socket.emit('joinScene', {
+ scene: 'BridgeScene',
+ x: this.game.config.width / 2,
+ y: this.game.config.height / 2
});
+ }
+
+ // Add portal collision handler
+ this.physics.add.overlap(this.player, this.portal, () => {
+ // Tell server we're leaving this scene
+ this.socket.emit('leaveBridgeScene');
+
+ // Start next scene
+ this.scene.start('CommonScene'); // or whatever scene you're transitioning to
+ });
+
+ // Initialize background scene
+ if (!this.scene.isActive('BackgroundScene')) {
+ this.scene.launch('BackgroundScene');
+ }
+
+ this.backgroundScene = this.scene.get('BackgroundScene');
+ this.socket = this.backgroundScene.getSocket();
+
+ // Tell background scene we're here
+ this.backgroundScene.events.emit('changeScene', 'BridgeScene');
+
+ // Listen for player updates
+ this.backgroundScene.events.on('playerUpdated', ({ playerId, playerInfo, isNew, isMovement }) => {
+ if (playerId === this.socket.id) {
+ if (isNew) this.addPlayer(playerInfo);
+ } else {
+ if (isNew) {
+ this.addOtherPlayer(playerInfo);
+ } else if (isMovement) {
+ this.updateOtherPlayer(playerId, playerInfo);
+ }
+ }
+ });
+
+ // Listen for player removals
+ this.backgroundScene.events.on('playerRemoved', (playerId) => {
+ if (this.otherPlayers[playerId]) {
+ this.otherPlayers[playerId].destroy();
+ delete this.otherPlayers[playerId];
+ }
+ });
+
+ this.otherPlayersGroup = this.add.group();
+
+ // Add these socket listeners
+ this.socket.on('currentPlayers', (players) => {
+ console.log('Current players in BridgeScene:', players);
+
+ // Don't clear if we already have these players
+ const currentPlayerIds = new Set(Object.keys(players));
+ const existingPlayerIds = new Set(Object.keys(this.playerWorldPosition));
+
+ // Only clear and recreate if the player sets are different
+ if (!this.areSetsEqual(currentPlayerIds, existingPlayerIds)) {
+ this.clearExistingPlayers();
+
+ Object.entries(players).forEach(([id, playerInfo]) => {
+ if (!this.existingPlayers.has(id)) {
+ console.log('Creating new player:', id);
+ if (id === this.socket.id) {
+ this.player = this.createPlayerWithListeners(id, playerInfo);
+ } else if (playerInfo.scene === 'BridgeScene') {
+ this.createPlayerWithListeners(id, playerInfo);
+ }
+ this.existingPlayers.add(id);
+ }
+ });
+
+ // After all players are created, check if we should start countdown
+ if (Object.keys(players).length >= 2) {
+ // Tell server we have enough players
+ this.socket.emit('bridgeReady', {
+ scene: 'BridgeScene',
+ players: Object.keys(players)
+ });
+ }
+ }
+ });
+
+ this.socket.on('newPlayer', (playerInfo) => {
+ console.log('New player joined:', playerInfo);
+ if (playerInfo.playerId !== this.localPlayerId && playerInfo.scene === 'BridgeScene') {
+ this.createPlayerWithListeners(playerInfo.playerId, playerInfo);
+ }
+ });
+
+ this.socket.on('playerMovedInBridge', (playerInfo) => {
+ if (this.otherPlayers[playerInfo.playerId]) {
+ const otherPlayer = this.otherPlayers[playerInfo.playerId];
+ otherPlayer.setPosition(playerInfo.x, playerInfo.y);
+ if (playerInfo.animation) {
+ otherPlayer.play(playerInfo.animation, true);
+ }
+ otherPlayer.setFlipX(playerInfo.flipX || false);
+ }
+ });
+
+ this.socket.on('playerDisconnected', (playerId) => {
+ if (this.otherPlayers[playerId]) {
+ this.otherPlayers[playerId].destroy();
+ delete this.otherPlayers[playerId];
+ }
+ });
+
+ // Listen for server's countdown signal
+ this.socket.on('bridgeCountdown', () => {
+ if (!this.countdownStarted) {
+ this.startCountdown();
+ }
+ });
+
+ // Store local player ID
+ this.localPlayerId = this.socket.id;
+ console.log('Local player ID:', this.localPlayerId);
+
+ // Clean up old listeners before adding new ones
+ this.cleanupSocketListeners();
- this.anims.create({
- key: "attackRight",
- frames: this.anims.generateFrameNumbers("player", { start: 42, end: 46 }),
- frameRate: 10,
- repeat: -1,
+ // Set up socket listeners
+ this.socket.on('currentPlayers', (players) => {
+ console.log('Current players received:', Object.keys(players));
+
+ // Clear existing players first
+ this.clearExistingPlayers();
+
+ Object.entries(players).forEach(([id, playerInfo]) => {
+ if (!this.initializedPlayers.has(id)) {
+ console.log('Initializing player:', id);
+ if (id === this.localPlayerId) {
+ console.log('Creating local player');
+ this.player = this.createPlayerWithListeners(id, playerInfo);
+ } else if (playerInfo.scene === 'BridgeScene') {
+ console.log('Creating remote player');
+ this.createPlayerWithListeners(id, playerInfo);
+ }
+ this.initializedPlayers.add(id);
+ } else {
+ console.log('Player already initialized:', id);
+ }
});
+ });
- this.anims.create({
- key: "attackUp",
- frames: this.anims.generateFrameNumbers("player", { start: 48, end: 52 }),
- frameRate: 10,
- repeat: -1,
+ this.socket.on('newPlayer', (playerInfo) => {
+ console.log('New player joined:', playerInfo);
+ if (playerInfo.playerId !== this.localPlayerId && playerInfo.scene === 'BridgeScene') {
+ this.createPlayerWithListeners(playerInfo.playerId, playerInfo);
+ }
+ });
+
+ this.socket.on('playerMoved', (playerInfo) => {
+ const otherPlayer = this.otherPlayers[playerInfo.playerId];
+ if (otherPlayer) {
+ // Clear any existing tweens to prevent position conflicts
+ this.tweens.killTweensOf(otherPlayer);
+
+ // Update position immediately
+ otherPlayer.setPosition(playerInfo.x, playerInfo.y);
+
+ // Update animation only if it's different
+ if (playerInfo.animation && (!otherPlayer.anims.currentAnim ||
+ otherPlayer.anims.currentAnim.key !== playerInfo.animation)) {
+ otherPlayer.play(playerInfo.animation, true);
+ }
+
+ // Update flip state
+ otherPlayer.setFlipX(playerInfo.flipX);
+
+ // Store latest info
+ otherPlayer.playerInfo = {
+ ...playerInfo,
+ lastUpdated: Date.now()
+ };
+ }
+ });
+
+ // Only emit join if we haven't already
+ if (!this.hasJoinedScene) {
+ console.log('Emitting first-time join for player:', this.localPlayerId);
+ this.socket.emit('joinScene', {
+ scene: 'BridgeScene',
+ playerId: this.localPlayerId,
+ x: this.getSpawnPoint().x,
+ y: this.getSpawnPoint().y,
+ animation: 'idleDown',
+ flipX: false
});
+ this.hasJoinedScene = true;
+ }
+
+ // Clean up old listeners before adding new ones
+ this.cleanupSocketListeners();
+
+ // Set up socket listeners
+ this.setupSocketListeners();
+
+ // Update server with player movement
+ this.time.addEvent({
+ delay: 50, // Send updates every 50ms
+ callback: this.sendPlayerUpdate,
+ callbackScope: this,
+ loop: true
+ });
+
+ // Listen for player counts in this scene specifically
+ this.socket.on('playerCount', (data) => {
+ console.log('Player count update:', data);
+ if (data.scene === 'BridgeScene' && data.count >= 2 && !this.countdownStarted) {
+ this.startCountdown();
+ }
+ });
- this.anims.create({
- key: "die",
- frames: this.anims.generateFrameNumbers("player", { start: 54, end: 56 }),
- frameRate: 10,
- repeat: 0,
+ // Add spacebar input
+ this.spacebar = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE);
+
+ // Add attack animations
+ const attackAnims = [
+ { key: 'attackDown', start: 36, end: 39 },
+ { key: 'attackRight', start: 42, end: 45 },
+ { key: 'attackUp', start: 48, end: 51 },
+ { key: 'attackLeft', start: 42, end: 45 }
+ ];
+
+ attackAnims.forEach(anim => {
+ if (!this.anims.exists(anim.key)) {
+ this.anims.create({
+ key: anim.key,
+ frames: this.anims.generateFrameNumbers('player', {
+ start: anim.start,
+ end: anim.end
+ }),
+ frameRate: 10,
+ repeat: 0, // Ensure animation only plays once
+ hideOnComplete: false // Don't hide sprite when animation completes
+ });
+ }
+ });
+
+ // Create physics group for other players if it doesn't exist
+ this.otherPlayersGroup = this.physics.add.group({
+ collideWorldBounds: true
+ });
+
+ // Add collision between players
+ this.physics.add.collider(this.player, this.otherPlayersGroup);
+
+ // Add collision between other players
+ this.physics.add.collider(this.otherPlayersGroup, this.otherPlayersGroup);
+
+ // Set world bounds
+ this.physics.world.setBounds(0, 0, this.game.config.width, this.game.config.height);
+
+ // Set up scene-specific socket events
+ this.socket.on('currentBridgePlayers', (players) => {
+ console.log('Current bridge players:', players);
+ Object.keys(players).forEach((id) => {
+ if (id !== this.socket.id) {
+ this.addOtherPlayer(players[id]);
+ }
});
- this.player.play("idleDown");
+ });
+
+ this.socket.on('newBridgePlayer', (playerInfo) => {
+ console.log('New player joined bridge:', playerInfo);
+ if (playerInfo.playerId !== this.socket.id) {
+ this.addOtherPlayer(playerInfo);
+ }
+ });
+
+ // Emit that we've joined the bridge scene
+ this.socket.emit('joinScene', {
+ scene: 'BridgeScene',
+ x: this.player.x,
+ y: this.player.y
+ });
+ }
+
+ createHealthBar(x, y, player) {
+ const width = 40;
+ const height = 5;
+
+ // White outline
+ const outline = this.add.rectangle(x, y - 40, width + 2, height + 2, 0xffffff);
+
+ // Black background
+ const healthBarBackground = this.add.rectangle(x, y - 40, width, height, 0x000000);
+
+ // Red health bar - set origin to left and start with full width
+ const healthBar = this.add.rectangle(x - width/2, y - 40, width, height, 0xff0000)
+ .setOrigin(0, 0.5);
+
+ // Make sure initial width matches full health
+ healthBar.width = width; // Start with full width since health is 100
+
+ return {
+ outline: outline,
+ background: healthBarBackground,
+ bar: healthBar
+ };
+ }
- this.healthBar = this.createHealthBar(this.player.x, this.player.y, this.player);
+ update() {
+ const speed = 80;
+ const prevVelocity = this.player.body.velocity.clone();
+
+ // Only allow movement if countdown is finished
+ if (!this.canMove) {
+ if (this.player) {
+ this.player.body.setVelocity(0);
+ }
+ return;
+ }
+
+ // Stop any previous movement from the last frame
+ this.player.body.setVelocity(0);
+
+ // Horizontal movement
+ if (this.cursors.left.isDown) {
+ this.player.body.setVelocityX(-speed);
+ this.player.anims.play("walkRight", true);
+ this.player.flipX = true;
+ } else if (this.cursors.right.isDown) {
+ this.player.body.setVelocityX(speed);
+ this.player.anims.play("walkRight", true);
+ this.player.flipX = false;
+ }
+
+ // Vertical movement
+ if (this.cursors.up.isDown) {
+ this.player.body.setVelocityY(-speed);
+ this.player.anims.play("walkUp", true);
+ } else if (this.cursors.down.isDown) {
+ this.player.body.setVelocityY(speed);
+ this.player.anims.play("walkDown", true);
+ }
+
+ // Normalize and scale the velocity so that player can't move faster along a diagonal
+ this.player.body.velocity.normalize().scale(speed);
+
+ // If no movement keys are pressed, stop the animation
+ if (
+ this.cursors.left.isUp &&
+ this.cursors.right.isUp &&
+ this.cursors.up.isUp &&
+ this.cursors.down.isUp
+ ) {
+ this.player.anims.stop();
+
+ // Set idle animation based on the last direction
+ if (prevVelocity.x < 0) {
+ this.player.anims.play("idleLeft", true);
+ this.player.flipX = true;
+ } else if (prevVelocity.x > 0) {
+ this.player.anims.play("idleRight", true);
+ this.player.flipX = false;
+ } else if (prevVelocity.y < 0) {
+ this.player.anims.play("idleUp", true);
+ } else if (prevVelocity.y > 0) {
+ this.player.anims.play("idleDown", true);
+ }
}
- createHealthBar(x, y, player) {
+ // Update health bar position and width
+ if (this.healthBar && this.player) {
+ const yOffset = -20;
const width = 40;
- const height = 5;
-
- // White outline
- const outline = this.add.rectangle(x, y - 40, width + 2, height + 2, 0xffffff);
- // Black background
- const healthBarBackground = this.add.rectangle(x, y - 40, width, height, 0x000000);
+ this.healthBar.outline.x = this.player.x;
+ this.healthBar.outline.y = this.player.y + yOffset;
+ this.healthBar.background.x = this.player.x;
+ this.healthBar.background.y = this.player.y + yOffset;
- // Red health bar - set origin to left
- const healthBar = this.add.rectangle(x - width/2, y - 40, width, height, 0xff0000)
- .setOrigin(0, 0.5);
-
- return {
- outline: outline,
- background: healthBarBackground,
- bar: healthBar
- };
+ // Update red bar position and width based on current health
+ this.healthBar.bar.x = this.player.x - width/2;
+ this.healthBar.bar.y = this.player.y + yOffset;
+ this.healthBar.bar.width = (this.player.life / 100) * width; // Make sure this.player.life is set to 100 initially
}
- update() {
- const speed = 80;
- const prevVelocity = this.player.body.velocity.clone();
- let newX = this.player.x;
- let newY = this.player.y;
+ this.otherPlayersGroup.getChildren().forEach((player) => {
+ if (player.playerInfo) {
+ player.setPosition(player.playerInfo.x, player.playerInfo.y);
+ player.play(player.playerInfo.animation, true);
+ player.flipX = player.playerInfo.flipX;
+ }
+ });
- // this.input.keyboard.addListener("keydown-F", (e) => {
- // console.log(e)
- // this.player.play("attackRight");
- // })
+ // Update local player
+ if (this.player && this.canMove) {
+ // ... existing player movement code ...
- // Stop any previous movement from the last frame
- this.player.body.setVelocity(0);
+ // Only emit if this is the local player
+ if (this.socket.id === this.localPlayerId) {
+ this.sendPlayerUpdate();
+ }
+
+ // Handle attack
+ if (Phaser.Input.Keyboard.JustDown(this.spacebar) && !this.isAttacking) {
+ this.handleAttack(); // Use the consolidated attack handler
+ }
+ }
- // Horizontal movement
- if (this.cursors.left.isDown) {
- newX -= speed * (1 / 60);
- if (!this.level.isColliding(newX, newY)) {
- this.player.body.setVelocityX(-speed);
- this.player.anims.play("walkRight", true); // Assuming you have a 'walkLeft' animation
- this.player.flipX = true; // Flip the sprite to face left
+ // Update other players
+ Object.entries(this.playerWorldPosition).forEach(([playerId, playerData]) => {
+ if (playerData.sprite && playerId !== this.localPlayerId) {
+ playerData.sprite.setPosition(playerData.x, playerData.y);
+ if (playerData.animation) {
+ playerData.sprite.play(playerData.animation, true);
}
- } else if (this.cursors.right.isDown) {
- newX += speed * (1 / 60);
- if (!this.level.isColliding(newX, newY)) {
- this.player.body.setVelocityX(speed);
- this.player.anims.play("walkRight", true);
- this.player.flipX = false; // Ensure the sprite is facing right
+ playerData.sprite.setFlipX(playerData.flipX);
+ }
+ });
+
+ // Clean up any trailing sprites
+ Object.values(this.otherPlayers).forEach(otherPlayer => {
+ if (otherPlayer && otherPlayer.playerInfo) {
+ // If player hasn't been updated in a while, set to idle
+ const timeSinceUpdate = Date.now() - (otherPlayer.playerInfo.lastUpdated || 0);
+ if (timeSinceUpdate > 100) { // 100ms threshold
+ const currentAnim = otherPlayer.anims.currentAnim;
+ if (currentAnim && currentAnim.key.startsWith('walk')) {
+ const idleAnim = currentAnim.key.replace('walk', 'idle');
+ otherPlayer.play(idleAnim, true);
+ }
}
}
+ });
+ }
+
+ handleSocketEvents() {
+ // ... keep existing socket events ...
+
+ // Check if this handler exists and has flipX
+ this.socket.on('playerMoved', (playerInfo) => {
+ if (this.otherPlayers[playerInfo.playerId]) {
+ const otherPlayer = this.otherPlayers[playerInfo.playerId];
+ otherPlayer.setPosition(playerInfo.x, playerInfo.y);
+ otherPlayer.anims.play(playerInfo.animation, true);
+ otherPlayer.flipX = playerInfo.flipX; // Make sure this is here
+ }
+ });
+ }
+
+ getSpawnPoint() {
+ // Define two specific spawn points
+ const spawnPoints = [
+ { x: 50, y: 90 }, // First player spawn
+ { x: 200, y: 90 } // Second player spawn
+ ];
+
+ // Count existing players to determine spawn point
+ const playerCount = Object.keys(this.playerWorldPosition).length;
+ return spawnPoints[playerCount] || spawnPoints[0];
+ }
- // Vertical movement
- if (this.cursors.up.isDown) {
- newY -= speed * (1 / 60);
- if (!this.level.isColliding(newX, newY)) {
- this.player.body.setVelocityY(-speed);
- this.player.anims.play("walkUp", true);
+ checkPlayersAndStartCountdown() {
+ // Remove local countdown check - wait for server signal instead
+ console.log('Waiting for server countdown signal...');
+ }
+
+ startCountdown() {
+ if (this.countdownStarted) return; // Prevent multiple countdowns
+
+ console.log('Starting countdown');
+ this.countdownStarted = true;
+ let count = 3;
+
+ if (this.countdownText) {
+ this.countdownText.setText(count.toString());
+ }
+
+ const countdownInterval = setInterval(() => {
+ count--;
+ if (count > 0) {
+ this.countdownText?.setText(count.toString());
+ } else {
+ this.countdownText?.setText('FIGHT!');
+ this.canMove = true;
+
+ // Remove countdown text after "FIGHT!"
+ setTimeout(() => {
+ this.countdownText?.destroy();
+ this.countdownText = null;
+ }, 1000);
+
+ clearInterval(countdownInterval);
+ }
+ }, 1000);
+ }
+
+ handleAttack() {
+ // Find the closest player to attack
+ let closestPlayer = null;
+ let closestDistance = Infinity;
+
+ Object.keys(this.otherPlayers).forEach((id) => {
+ const otherPlayer = this.otherPlayers[id];
+ const distance = Phaser.Math.Distance.Between(
+ this.player.x,
+ this.player.y,
+ otherPlayer.x,
+ otherPlayer.y
+ );
+ if (distance < closestDistance) {
+ closestDistance = distance;
+ closestPlayer = otherPlayer;
+ }
+ });
+
+ if (closestPlayer && closestDistance < 25) {
+ this.isAttacking = true;
+ const currentDirection = this.getPlayerDirection();
+ const attackAnim = `attack${currentDirection}`;
+
+ if (this.anims.exists(attackAnim)) {
+ // Play attack animation locally
+ this.player.play(attackAnim, true)
+ .once('animationcomplete', () => {
+ this.isAttacking = false;
+ const idleAnim = `idle${currentDirection}`;
+ if (this.anims.exists(idleAnim)) {
+ this.player.play(idleAnim, true);
+ }
+ });
+
+ // Emit attack event with target information
+ this.socket.emit('playerAttack', {
+ x: this.player.x,
+ y: this.player.y,
+ direction: currentDirection,
+ scene: 'BridgeScene',
+ targetId: closestPlayer.playerId
+ });
+
+ console.log('Attacking player:', closestPlayer.playerId, 'with animation:', attackAnim);
+ }
+ }
+ }
+
+ setupSocketListeners() {
+ this.socket.on('currentPlayers', (players) => {
+ console.log('Current players in BridgeScene:', players);
+
+ // Don't clear if we already have these players
+ const currentPlayerIds = new Set(Object.keys(players));
+ const existingPlayerIds = new Set(Object.keys(this.playerWorldPosition));
+
+ // Only clear and recreate if the player sets are different
+ if (!this.areSetsEqual(currentPlayerIds, existingPlayerIds)) {
+ this.clearExistingPlayers();
+
+ Object.entries(players).forEach(([id, playerInfo]) => {
+ if (!this.existingPlayers.has(id)) {
+ console.log('Creating new player:', id);
+ if (id === this.socket.id) {
+ this.player = this.createPlayerWithListeners(id, playerInfo);
+ } else if (playerInfo.scene === 'BridgeScene') {
+ this.createPlayerWithListeners(id, playerInfo);
+ }
+ this.existingPlayers.add(id);
+ }
+ });
+
+ // After all players are created, check if we should start countdown
+ if (Object.keys(players).length >= 2) {
+ // Tell server we have enough players
+ this.socket.emit('bridgeReady', {
+ scene: 'BridgeScene',
+ players: Object.keys(players)
+ });
+ }
+ }
+ });
+
+ // Listen for server's countdown signal
+ this.socket.on('bridgeCountdown', () => {
+ if (!this.countdownStarted) {
+ this.startCountdown();
+ }
+ });
+
+ // Update other players more smoothly
+ this.socket.on('playerMovedInBridge', (playerInfo) => {
+ if (this.otherPlayers[playerInfo.playerId]) {
+ const otherPlayer = this.otherPlayers[playerInfo.playerId];
+ otherPlayer.setPosition(playerInfo.x, playerInfo.y);
+ if (playerInfo.animation) {
+ otherPlayer.play(playerInfo.animation, true);
}
- } else if (this.cursors.down.isDown) {
- newY += speed * (1 / 60);
- console.log(this.level.isColliding(newX, newY));
- if (!this.level.isColliding(newX, newY)) {
- console.log("not");
- this.player.body.setVelocityY(speed);
- this.player.anims.play("walkDown", true);
+ otherPlayer.setFlipX(playerInfo.flipX || false);
+ }
+ });
+
+ this.socket.on('playerMoved', (playerInfo) => {
+ const otherPlayer = this.otherPlayers[playerInfo.playerId];
+ if (otherPlayer) {
+ // Clear any existing tweens to prevent position conflicts
+ this.tweens.killTweensOf(otherPlayer);
+
+ // Update position immediately
+ otherPlayer.setPosition(playerInfo.x, playerInfo.y);
+
+ // Update animation only if it's different
+ if (playerInfo.animation && (!otherPlayer.anims.currentAnim ||
+ otherPlayer.anims.currentAnim.key !== playerInfo.animation)) {
+ otherPlayer.play(playerInfo.animation, true);
}
+
+ // Update flip state
+ otherPlayer.setFlipX(playerInfo.flipX);
+
+ // Store latest info
+ otherPlayer.playerInfo = {
+ ...playerInfo,
+ lastUpdated: Date.now()
+ };
}
+ });
- // Normalize and scale the velocity so that player can't move faster along a diagonal
- this.player.body.velocity.normalize().scale(speed);
-
- // If no movement keys are pressed, stop the animation
- if (
- this.cursors.left.isUp &&
- this.cursors.right.isUp &&
- this.cursors.up.isUp &&
- this.cursors.down.isUp
- ) {
- this.player.anims.stop();
-
- // Set idle animation based on the last direction
- if (prevVelocity.x < 0) {
- this.player.anims.play("idleRight", true);
- this.player.flipX = true;
- } else if (prevVelocity.x > 0) {
- this.player.anims.play("idleRight", true);
- this.player.flipX = false;
- } else if (prevVelocity.y < 0) {
- this.player.anims.play("idleUp", true);
- } else if (prevVelocity.y > 0) {
- this.player.anims.play("idleDown", true);
+ this.socket.on('playerAttacked', (attackInfo) => {
+ const attacker = attackInfo.playerId === this.socket.id ?
+ this.player : this.otherPlayers[attackInfo.playerId];
+
+ if (attacker && !attacker.isAttacking) {
+ const attackAnim = `attack${attackInfo.direction}`;
+
+ if (this.anims.exists(attackAnim)) {
+ attacker.isAttacking = true;
+
+ attacker.play(attackAnim, true)
+ .once('animationcomplete', () => {
+ attacker.isAttacking = false;
+ // Return to idle after attack
+ const idleAnim = `idle${attackInfo.direction}`;
+ if (this.anims.exists(idleAnim)) {
+ attacker.play(idleAnim, true);
+ }
+ });
}
}
+ });
- // Update health bar position and width
- if (this.healthBar) {
- const yOffset = -20;
- const width = 40;
+ this.socket.on('playerDamaged', (damageInfo) => {
+ console.log('Received damage info:', damageInfo);
+ const targetPlayer = damageInfo.playerId === this.socket.id ?
+ this.player : this.otherPlayers[damageInfo.playerId];
- this.healthBar.outline.x = this.player.x;
- this.healthBar.outline.y = this.player.y + yOffset;
- this.healthBar.background.x = this.player.x;
- this.healthBar.background.y = this.player.y + yOffset;
+ if (targetPlayer) {
+ targetPlayer.life = damageInfo.newHealth;
+ if (targetPlayer.healthBar) {
+ targetPlayer.healthBar.update();
+ }
+ }
+ });
+ }
+
+ shutdown() {
+ console.log('BridgeScene shutting down');
+ this.cleanupSocketListeners();
+ this.hasJoinedScene = false;
+ this.existingPlayers.clear();
+ }
+
+ createAnimations() {
+ const animations = [
+ { key: 'idleDown', start: 0, end: 5 },
+ { key: 'idleRight', start: 6, end: 11 },
+ { key: 'idleUp', start: 12, end: 17 },
+ { key: 'idleLeft', start: 18, end: 23 },
+ { key: 'walkDown', start: 18, end: 23 },
+ { key: 'walkRight', start: 24, end: 29 },
+ { key: 'walkUp', start: 30, end: 35 },
+ { key: 'attackDown', start: 36, end: 39 },
+ { key: 'attackRight', start: 42, end: 45 },
+ { key: 'attackUp', start: 48, end: 51 },
+ { key: 'attackLeft', start: 52, end: 55 },
+ { key: 'die', start: 54, end: 57 }
+ ];
+
+ animations.forEach(anim => {
+ if (!this.anims.exists(anim.key)) {
+ this.anims.create({
+ key: anim.key,
+ frames: this.anims.generateFrameNumbers('player', {
+ start: anim.start,
+ end: anim.end
+ }),
+ frameRate: 10,
+ repeat: anim.key.startsWith('attack') || anim.key === 'die' ? 0 : -1
+ });
+ }
+ });
+ }
+
+ addOtherPlayer(playerInfo) {
+ if (!playerInfo) return null;
+
+ try {
+ console.log('Adding other player:', playerInfo);
+
+ // Destroy existing player if it exists
+ if (this.otherPlayers[playerInfo.playerId]) {
+ this.otherPlayers[playerInfo.playerId].destroy();
+ }
+
+ const otherPlayer = this.physics.add.sprite(
+ playerInfo.x || this.game.config.width / 2,
+ playerInfo.y || this.game.config.height / 2,
+ 'player'
+ ).setScale(1);
+
+ // Set up physics body
+ otherPlayer.setBodySize(24, 28);
+ otherPlayer.setOffset(10, 13);
+ otherPlayer.setBounce(0.2);
+ otherPlayer.setCollideWorldBounds(true);
+
+ // Enable physics but disable gravity
+ otherPlayer.body.setAllowGravity(false);
+
+ // Set initial animation
+ if (this.anims.exists('idleDown')) {
+ otherPlayer.play('idleDown');
+ }
+
+ // Store player info
+ otherPlayer.playerInfo = playerInfo;
+ otherPlayer.playerId = playerInfo.playerId;
+
+ // Add to tracking
+ this.otherPlayers[playerInfo.playerId] = otherPlayer;
+
+ if (this.otherPlayersGroup) {
+ this.otherPlayersGroup.add(otherPlayer);
+ }
+
+ console.log('Successfully added other player:', playerInfo.playerId);
+ return otherPlayer;
+ } catch (error) {
+ console.error('Error in addOtherPlayer:', error);
+ return null;
+ }
+ }
+
+ removePlayer(playerId) {
+ this.otherPlayersGroup.getChildren().forEach((player) => {
+ if (player.playerId === playerId) {
+ player.destroy();
+ }
+ });
+ }
+
+ createPlayerWithListeners(playerId, playerInfo) {
+ // Check if player already exists using the Set
+ if (this.existingPlayers.has(playerId)) {
+ console.log('Player already exists:', playerId);
+ return this.playerWorldPosition[playerId]?.sprite;
+ }
+
+ console.log('Actually creating new player:', playerId);
+ const spawnPoint = this.getSpawnPoint();
+ const playerSprite = this.physics.add.sprite(
+ spawnPoint.x,
+ spawnPoint.y,
+ 'player'
+ ).setScale(1);
+
+ playerSprite.setBodySize(24, 28);
+ playerSprite.setOffset(10, 13);
+ playerSprite.setBounce(0.2);
+ playerSprite.setCollideWorldBounds(true);
+ playerSprite.body.setAllowGravity(false);
+
+ playerSprite.play('idleDown');
+ playerSprite.life = 100; // Make sure to set initial life
+
+ this.playerWorldPosition[playerId] = {
+ x: spawnPoint.x,
+ y: spawnPoint.y,
+ sprite: playerSprite,
+ animation: 'idleDown',
+ flipX: false
+ };
+
+ // Only add to group if it exists
+ if (this.playersGroup) {
+ this.playersGroup.add(playerSprite);
+ } else {
+ console.warn('playersGroup not initialized');
+ }
+
+ this.existingPlayers.add(playerId);
+
+ return playerSprite;
+ }
+
+ handlePlayerCollision(player1, player2) {
+ // Handle player collision logic here
+ console.log('Players collided!');
+ }
+
+ sendPlayerUpdate() {
+ if (this.player && this.socket) {
+ const now = Date.now();
+ const playerInfo = {
+ x: this.player.x,
+ y: this.player.y,
+ animation: this.player.anims.currentAnim?.key || 'idleDown',
+ flipX: this.player.flipX,
+ scene: 'BridgeScene'
+ };
+
+ // Only send update if enough time has passed and position/state has changed
+ if (now - this.lastMovementUpdate >= this.movementUpdateInterval &&
+ this.hasPlayerStateChanged(playerInfo)) {
- // Update red bar position and width
- this.healthBar.bar.x = this.player.x - width/2;
- this.healthBar.bar.y = this.player.y + yOffset;
- this.healthBar.bar.width = (this.player.life / 100) * width;
+ this.socket.emit('playerMovement', playerInfo);
+ this.lastMovementUpdate = now;
+ this.lastPosition = { ...playerInfo };
+ }
+ }
+ }
+
+ updatePlayerInWorld(playerId, playerInfo) {
+ const playerData = this.playerWorldPosition[playerId];
+ if (playerData?.sprite) {
+ // Update position
+ playerData.x = playerInfo.x;
+ playerData.y = playerInfo.y;
+ playerData.sprite.setPosition(playerInfo.x, playerInfo.y);
+
+ // Update animation
+ if (playerInfo.animation && playerData.animation !== playerInfo.animation) {
+ playerData.animation = playerInfo.animation;
+ playerData.sprite.play(playerInfo.animation, true);
}
+
+ // Update flip
+ if (playerInfo.flipX !== undefined && playerData.flipX !== playerInfo.flipX) {
+ playerData.flipX = playerInfo.flipX;
+ playerData.sprite.setFlipX(playerInfo.flipX);
+ }
+ }
+ }
+
+ clearExistingPlayers() {
+ console.log('Clearing existing players');
+ Object.entries(this.playerWorldPosition).forEach(([playerId, playerData]) => {
+ if (playerData.sprite) {
+ playerData.sprite.destroy();
+ }
+ });
+ this.playerWorldPosition = {};
+ if (this.playersGroup) {
+ this.playersGroup.clear(true, true);
+ }
+ this.existingPlayers.clear();
+ }
+
+ cleanupSocketListeners() {
+ if (this.socket) {
+ this.socket.removeAllListeners('currentPlayers');
+ this.socket.removeAllListeners('newPlayer');
+ this.socket.removeAllListeners('playerMoved');
+ this.socket.removeAllListeners('playerCount');
+ }
+ }
+
+ // Helper method to compare Sets
+ areSetsEqual(set1, set2) {
+ if (set1.size !== set2.size) return false;
+ for (const item of set1) {
+ if (!set2.has(item)) return false;
}
+ return true;
+ }
+
+ getPlayerDirection() {
+ const currentAnim = this.player.anims.currentAnim;
+ if (!currentAnim) return 'Down';
+
+ // Check for attack animations first
+ if (currentAnim.key.includes('attack')) {
+ return currentAnim.key.replace('attack', '');
+ }
+
+ // Then check movement/idle animations
+ if (currentAnim.key.includes('Left')) return 'Left';
+ if (currentAnim.key.includes('Right')) return 'Right';
+ if (currentAnim.key.includes('Up')) return 'Up';
+ return 'Down';
+ }
+
+ hasPlayerStateChanged(newState) {
+ const positionThreshold = 1; // Minimum movement to trigger update
+ return Math.abs(this.lastPosition.x - newState.x) > positionThreshold ||
+ Math.abs(this.lastPosition.y - newState.y) > positionThreshold ||
+ this.lastPosition.animation !== newState.animation ||
+ this.lastPosition.flipX !== newState.flipX;
+ }
}
diff --git a/client/dungeonmap/src/CommonScene.js b/client/dungeonmap/src/CommonScene.js
index e37db89..89cfa40 100644
--- a/client/dungeonmap/src/CommonScene.js
+++ b/client/dungeonmap/src/CommonScene.js
@@ -1,7 +1,8 @@
-import Phaser from 'phaser'
+import { AnimationManager } from './managers/AnimationManager'
+import { PlayerManager } from './managers/PlayerManager'
+import { CombatManager} from './managers/CombatManager'
import Level from './Level.js'
-import { io } from 'socket.io-client'
-const socketId = (import.meta.env.VITE_SOCKET_URL)
+import BackgroundScene from './managers/backgroundscene'
export default class CommonScene extends Phaser.Scene {
constructor() {
@@ -11,6 +12,16 @@ export default class CommonScene extends Phaser.Scene {
this.level = new Level()
this.spike = null
this.lastEmitTime = 0
+ this.socket = null
+ this.backgroundScene = null
+ this.chatBubbles = {} // Store chat bubbles for each player
+ }
+
+ init(data) {
+ // Get socket from BackgroundScene
+ this.backgroundScene = this.scene.get('BackgroundScene');
+ this.socket = this.backgroundScene.getSocket();
+ console.log('Socket received from BackgroundScene:', this.socket.id);
}
preload() {
@@ -41,6 +52,22 @@ export default class CommonScene extends Phaser.Scene {
}
create() {
+ this.backgroundScene = this.scene.get('BackgroundScene')
+ this.socket = this.backgroundScene.getSocket()
+
+ if (!this.socket || !this.socket.connected) {
+ console.error('No socket connection available');
+ return;
+ }
+
+ console.log('Using socket from BackgroundScene:', this.socket.id);
+
+ const spawnData = this.scene.settings.data;
+ if (spawnData && spawnData.x && spawnData.y) {
+ this.game.config.width = spawnData.x;
+ this.game.config.height = spawnData.y;
+ }
+
const map = this.make.tilemap({ key: 'common' })
const grass = map.addTilesetImage('Grass_Middle', 'Grass_Middle')
const water = map.addTilesetImage('Water_Tile', 'Water_Tile')
@@ -103,23 +130,65 @@ export default class CommonScene extends Phaser.Scene {
.setOrigin(0, 0)
.setCollisionByProperty({ collider: true })
- // const tileset2 = map.addTilesetImage("pillars", "pillars");
- // const layer3 = map.createLayer("pillars", tileset2, 0, 0);
- // layer3.setScale(1, 1).setOrigin(0, 0);
- // layer3.setCollisionByProperty({ collider: true });
+ // Clear any existing labels first
+ this.children.list
+ .filter(child => child.type === 'Text')
+ .forEach(label => label.destroy());
+ // Track positions where we've already placed labels
+ const labelPositions = new Set();
+ // Function to check if position is too close to existing labels
+ const isTooClose = (x, y) => {
+ for (let pos of labelPositions) {
+ const [existingX, existingY] = pos.split(',').map(Number);
+ const distance = Math.sqrt(Math.pow(existingX - x, 2) + Math.pow(existingY - y, 2));
+ if (distance < 50) { // Adjust this number to change how close labels can be
+ return true;
+ }
+ }
+ return false;
+ };
+
+ // Add permanent scene transition label for dungeon (just one)
+ const dungeonTiles = objectLayer.filterTiles(tile => tile.properties.dungeon);
+ if (dungeonTiles.length > 0) {
+ // Only use the first dungeon tile for the label
+ const tile = dungeonTiles[7];
+ const posKey = `${tile.pixelX},${tile.pixelY}`;
+ this.add.text(tile.pixelX, tile.pixelY - 20, 'Battle (Dungeon)', {
+ fontFamily: 'Verdana',
+ fontSize: '10px',
+ fill: '#fff',
+ backgroundColor: '',
+ padding: { x: 5, y: 2 }
+ }).setOrigin(0.5);
+ labelPositions.add(posKey);
+ }
+
+ // Add permanent scene transition label for bridge
+ const bridgeTiles = objectLayer.filterTiles(tile => tile.properties.bridge);
+ bridgeTiles.forEach(tile => {
+ const posKey = `${tile.pixelX},${tile.pixelY}`;
+ if (!isTooClose(tile.pixelX, tile.pixelY)) {
+ this.add.text(tile.pixelX + 16, tile.pixelY - 20, 'Battle (Bridge)', {
+ fontFamily: 'Verdana',
+ fontSize: '10px',
+ fill: '#fff',
+ backgroundColor: '',
+ padding: { x: 5, y: 2 }
+ }).setOrigin(0.5);
+ labelPositions.add(posKey);
+ }
+ });
- this.socket = io(socketId, {
- withCredentials: false,
- })
this.player = this.physics.add
.sprite(this.game.config.width / 2, this.game.config.height / 2, 'player')
.setScale(1)
this.player.setCollideWorldBounds(true)
this.player.life = 100
- this.player.attack = 10
+ this.player.attack = 0
this.player.weapon = 'sword'
this.player.setScale(1) // Scale the player sprite by 1.5 times
this.player.setBodySize(24, 28)
@@ -131,28 +200,30 @@ export default class CommonScene extends Phaser.Scene {
frameRate: 10,
repeat: -1,
})
- let element = document.getElementById('input-box');
+ let element = document.getElementById('input-box')
const yesButton = document.getElementById('yes')
const noButton = document.getElementById('no')
this.physics.add.collider(this.player, layer1)
this.physics.add.collider(this.player, objectLayer, (a, b) => {
if (b?.properties?.dungeon) {
element.style.display = 'block'
- yesButton.addEventListener('click', ()=>{
- this.scene.start('DungeonScene')
+ yesButton.addEventListener('click', () => {
+ this.socket.emit('leaveCommonScene');
+ this.scene.start('DungeonScene');
element.style.display = 'none'
})
- noButton.addEventListener('click', ()=>{
+ noButton.addEventListener('click', () => {
element.style.display = 'none'
})
}
if (b?.properties?.bridge) {
element.style.display = 'block'
- yesButton.addEventListener('click', ()=>{
- this.scene.start('BridgeScene')
+ yesButton.addEventListener('click', () => {
+ this.socket.emit('leaveCommonScene');
+ this.scene.start('BridgeScene');
element.style.display = 'none'
})
- noButton.addEventListener('click', ()=>{
+ noButton.addEventListener('click', () => {
element.style.display = 'none'
})
}
@@ -161,7 +232,15 @@ export default class CommonScene extends Phaser.Scene {
// this.cameras.main.setBounds(0, 0, +this.game.config.width, +this.game.config.height);
this.cameras.main.startFollow(this.player, true)
this.cameras.main.setFollowOffset(-50, -50)
+
+ // Add other idle animations if missing
+ this.anims.create({
+ key: 'idleLeft',
+ frames: this.anims.generateFrameNumbers('player', { start: 6, end: 11 }),
+ frameRate: 10,
+ repeat: -1,
+ })
this.anims.create({
key: 'idleRight',
frames: this.anims.generateFrameNumbers('player', { start: 6, end: 11 }),
@@ -197,33 +276,37 @@ export default class CommonScene extends Phaser.Scene {
repeat: -1,
})
+ // Attack animations
this.anims.create({
key: 'attackDown',
- frames: this.anims.generateFrameNumbers('player', { start: 36, end: 39 }),
+ frames: this.anims.generateFrameNumbers('player', { start: 36, end: 41 }),
frameRate: 10,
- repeat: -1,
- })
+ repeat: 0 // Don't repeat attack animations
+ });
this.anims.create({
key: 'attackRight',
- frames: this.anims.generateFrameNumbers('player', { start: 42, end: 46 }),
+ frames: this.anims.generateFrameNumbers('player', { start: 42, end: 47 }),
frameRate: 10,
- repeat: -1,
- })
+ repeat: 0
+ });
this.anims.create({
key: 'attackUp',
- frames: this.anims.generateFrameNumbers('player', { start: 48, end: 52 }),
+ frames: this.anims.generateFrameNumbers('player', { start: 48, end: 53 }),
frameRate: 10,
- repeat: -1,
- })
+ repeat: 0
+ });
+ // Attack Left uses the same frames as Attack Right but flipped
this.anims.create({
- key: 'die',
- frames: this.anims.generateFrameNumbers('player', { start: 54, end: 56 }),
+ key: 'attackLeft',
+ frames: this.anims.generateFrameNumbers('player', { start: 42, end: 47 }),
frameRate: 10,
- repeat: 0,
- })
+ repeat: 0
+ });
+
+ AnimationManager.createAnimations(this)
this.player.play('idleDown')
this.handleSocketEvents()
@@ -233,10 +316,163 @@ export default class CommonScene extends Phaser.Scene {
)
// Add inventory key
- this.inventoryKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.I);
-
+ this.inventoryKey = this.input.keyboard.addKey(
+ Phaser.Input.Keyboard.KeyCodes.I
+ )
+
// Create inventory (initially hidden)
- this.createInventory();
+ this.createInventory()
+
+ // Add isAttacking flag
+ this.player.isAttacking = false
+
+ // When colliding with portal
+ this.physics.add.overlap(this.player, this.portal, () => {
+ // Tell server we're leaving this scene
+ this.socket.emit('leaveCommonScene');
+
+ // Start BridgeScene
+ this.scene.start('BridgeScene');
+ });
+
+ // Setup multiplayer events
+ this.socket.on('gameState', (state) => {
+ if (!state.players) return;
+
+ Object.entries(state.players).forEach(([playerId, playerData]) => {
+ // Skip if it's not the current player and not in otherPlayers
+ if (playerId !== this.socket.id && !this.otherPlayers[playerId]) {
+ if (!this.reportedPlayers?.includes(playerId)) {
+ console.log('New player detected:', playerId);
+ this.reportedPlayers = [...(this.reportedPlayers || []), playerId];
+ }
+ return;
+ }
+
+ const player = playerId === this.socket.id ? this.player : this.otherPlayers[playerId];
+
+ if (!player || !player.anims) return;
+
+ // Update position
+ player.x = Phaser.Math.Linear(player.x, playerData.x, 0.3);
+ player.y = Phaser.Math.Linear(player.y, playerData.y, 0.3);
+
+ // Handle animations and direction
+ if (playerData.animation.includes('walk')) {
+ if (playerData.animation === 'walkLeft') {
+ player.play('walkLeft', true);
+ player.flipX = true;
+ player.lastDirection = 'Left';
+ } else {
+ player.play(playerData.animation, true);
+ player.flipX = false;
+ player.lastDirection = playerData.animation.replace('walk', '');
+ }
+ } else {
+ // Handle idle animations
+ if (player.lastDirection === 'Left') {
+ player.play('idleLeft', true);
+ player.flipX = true;
+ } else {
+ player.play(`idle${player.lastDirection || 'Down'}`, true);
+ player.flipX = false;
+ }
+ }
+ });
+ });
+
+ // Join the scene
+ this.socket.emit('joinScene', {
+ scene: 'CommonScene',
+ x: this.game.config.width / 2,
+ y: this.game.config.height / 2
+ });
+
+ // Create chat input
+ this.createChatInput();
+
+ // Listen for chat messages
+ this.socket.on('chatMessage', (data) => {
+ if (data.playerId !== this.socket.id) { // Don't add our own messages twice
+ addMessage(data.playerId, data.message);
+ }
+ });
+ }
+
+ update(time) {
+ console.log('Socket status:', this.socket?.connected);
+ console.log('Player exists:', !!this.player);
+
+ if (!this.player || !this.socket) return;
+
+ // Send input state to server
+ const input = {
+ left: this.cursors.left.isDown,
+ right: this.cursors.right.isDown,
+ up: this.cursors.up.isDown,
+ down: this.cursors.down.isDown,
+ attack: this.input.keyboard.addKey('SPACE').isDown // Add attack input
+ };
+
+ // Only send input updates when they change
+ if (JSON.stringify(input) !== JSON.stringify(this.lastInput)) {
+ this.socket.emit('playerInput', {
+ input: input
+ });
+ this.lastInput = input;
+ }
+
+ // Animation is now based on server-provided state
+ if (this.player.serverState) {
+ this.player.play(this.player.serverState.animation, true);
+ this.player.setFlipX(this.player.serverState.flipX);
+ }
+ }
+
+ setupMultiplayerEvents() {
+ // Handle server-authoritative state updates
+ this.events.on('gameState', (state) => {
+ // Update all players based on server state
+ Object.keys(state.players).forEach((playerId) => {
+ const playerState = state.players[playerId];
+
+ if (playerId === this.socket.id) {
+ // Update local player
+ this.player.setPosition(playerState.x, playerState.y);
+ this.player.serverState = playerState;
+ } else {
+ // Update other players
+ if (!this.otherPlayers[playerId]) {
+ this.addOtherPlayer(playerState);
+ } else {
+ const otherPlayer = this.otherPlayers[playerId];
+ otherPlayer.setPosition(playerState.x, playerState.y);
+ otherPlayer.play(playerState.animation, true);
+ otherPlayer.setFlipX(playerState.flipX);
+ }
+ }
+ });
+ });
+
+ // Handle server-validated scene transitions
+ this.events.on('sceneTransition', (data) => {
+ if (data.playerId === this.socket.id) {
+ this.backgroundScene.events.emit('switchScene', data);
+ }
+ });
+
+ // Other event handlers remain similar but wait for server validation
+ }
+
+ handleAttack() {
+ // Send attack intent to server instead of handling locally
+ this.socket.emit('attackIntent', {
+ scene: 'CommonScene',
+ position: {
+ x: this.player.x,
+ y: this.player.y
+ }
+ });
}
handleSocketEvents() {
@@ -246,6 +482,7 @@ export default class CommonScene extends Phaser.Scene {
Object.keys(players).forEach((id) => {
if (players[id].playerId === socket.id) {
this.player.setPosition(players[id].x, players[id].y)
+ this.player.lastDirection = players[id].lastDirection
} else {
this.addOtherPlayer(players[id])
}
@@ -258,13 +495,47 @@ export default class CommonScene extends Phaser.Scene {
})
socket.on('playerMoved', (playerInfo) => {
- if (this.otherPlayers[playerInfo.playerId]) {
- var otherPlayer = this.otherPlayers[playerInfo.playerId];
- otherPlayer.setPosition(playerInfo.x, playerInfo.y);
- otherPlayer.anims.play(playerInfo.animation, true);
- otherPlayer.flipX = playerInfo.flipX;
- }
- })
+ if (!playerInfo || !playerInfo.playerId) return;
+
+ // Handle local player
+ if (playerInfo.playerId === this.socket.id) {
+ if (this.player && this.player.anims && !this.player.isAttacking) {
+ try {
+ this.player.anims.play(playerInfo.animation, true);
+ this.player.flipX = playerInfo.flipX;
+ this.player.lastDirection = playerInfo.lastDirection;
+ } catch (error) {
+ console.warn('Local player animation error:', error);
+ }
+ }
+ }
+ // Handle other players
+ else if (this.otherPlayers && this.otherPlayers[playerInfo.playerId]) {
+ const otherPlayer = this.otherPlayers[playerInfo.playerId];
+
+ // Update position
+ if (otherPlayer && typeof otherPlayer.setPosition === 'function') {
+ otherPlayer.setPosition(playerInfo.x, playerInfo.y);
+ }
+
+ // Update animation
+ if (otherPlayer && otherPlayer.anims && !otherPlayer.isAttacking) {
+ try {
+ if (playerInfo.animation && this.anims.exists(playerInfo.animation)) {
+ otherPlayer.anims.play(playerInfo.animation, true);
+ }
+ if (typeof playerInfo.flipX !== 'undefined') {
+ otherPlayer.flipX = playerInfo.flipX;
+ }
+ if (playerInfo.lastDirection) {
+ otherPlayer.lastDirection = playerInfo.lastDirection;
+ }
+ } catch (error) {
+ console.warn('Other player animation error:', error);
+ }
+ }
+ }
+ });
socket.on('playerAttacked', (data) => {
if (this.otherPlayers[data.target]) {
@@ -272,13 +543,26 @@ export default class CommonScene extends Phaser.Scene {
console.log('Player attacked:', data)
}
})
+ socket.on('playerAttackAnimation', (data) => {
+ console.log('Received attack animation:', data)
+
+ if (data.attacker === this.socket.id) {
+ return
+ }
+ const otherPlayer = this.otherPlayers[data.attacker]
+ if (otherPlayer) {
+ PlayerManager.handleOtherPlayerAttack(
+ otherPlayer,
+ data.animation,
+ data.direction
+ )
+ }
+ })
socket.on('playerDefeated', (playerId) => {
- if (this.otherPlayers[playerId]) {
- this.otherPlayers[playerId].destroy()
- delete this.otherPlayers[playerId]
+ CombatManager.handlePlayerDeath(this, playerId)
console.log('Player defeated:', playerId)
- }
+
})
socket.on('playerDisconnected', (playerId) => {
@@ -290,142 +574,160 @@ export default class CommonScene extends Phaser.Scene {
}
addOtherPlayer(playerInfo) {
- const otherPlayer = this.physics.add.sprite(
- playerInfo.x,
- playerInfo.y,
- 'player'
- )
- otherPlayer.playerId = playerInfo.playerId
- otherPlayer.life = playerInfo.life
- otherPlayer.attack = playerInfo.attack
- otherPlayer.weapon = playerInfo.weapon
- this.otherPlayers[playerInfo.playerId] = otherPlayer
- console.log('Added other player:', playerInfo)
+ try {
+ const otherPlayer = this.physics.add.sprite(
+ playerInfo.x || 400,
+ playerInfo.y || 300,
+ 'player'
+ );
+
+ otherPlayer.setScale(1);
+ otherPlayer.playerId = playerInfo.playerId;
+ otherPlayer.life = playerInfo.life || 100;
+ otherPlayer.attack = playerInfo.attack || 10;
+ otherPlayer.setCollideWorldBounds(true);
+ otherPlayer.setBodySize(24, 28);
+ otherPlayer.setOffset(10, 13);
+
+ if (this.layer1) {
+ this.physics.add.collider(otherPlayer, this.layer1);
+ }
+
+ this.otherPlayers[playerInfo.playerId] = otherPlayer;
+ otherPlayer.play('idleDown');
+
+ console.log('Added player:', playerInfo.playerId);
+ return otherPlayer;
+
+ } catch (error) {
+ console.error('Error in addOtherPlayer:', error);
+ return null;
+ }
}
createInventory() {
// Create inventory container
- const padding = 10;
- const cellSize = 40;
- const rows = 4;
- const cols = 6;
- const width = (cellSize * cols) + (padding * 2);
- const height = (cellSize * rows) + (padding * 2);
-
+ const padding = 10
+ const cellSize = 40
+ const rows = 4
+ const cols = 6
+ const width = cellSize * cols + padding * 2
+ const height = cellSize * rows + padding * 2
+
// Position in center of screen
- const x = this.cameras.main.centerX - width/2;
- const y = this.cameras.main.centerY - height/2;
-
+ const x = this.cameras.main.centerX - width / 2
+ const y = this.cameras.main.centerY - height / 2
+
// Create semi-transparent background
- this.inventoryBg = this.add.rectangle(x, y, width, height, 0x000000)
- .setOrigin(0, 0)
- .setAlpha(0.7)
- .setScrollFactor(0)
- .setDepth(100);
-
+ this.inventoryBg = this.add
+ .rectangle(x, y, width, height, 0x000000)
+ .setOrigin(0, 0)
+ .setAlpha(0.7)
+ .setScrollFactor(0)
+ .setDepth(1000);
+
// Create grid cells
- this.inventorySlots = [];
+ this.inventorySlots = []
for (let row = 0; row < rows; row++) {
- for (let col = 0; col < cols; col++) {
- const slotX = x + padding + (col * cellSize);
- const slotY = y + padding + (row * cellSize);
-
- // Create slot background
- const slot = this.add.rectangle(slotX, slotY, cellSize - 2, cellSize - 2, 0x666666)
- .setOrigin(0, 0)
- .setAlpha(0.8)
- .setScrollFactor(0)
- .setDepth(101);
-
- this.inventorySlots.push(slot);
- }
+ for (let col = 0; col < cols; col++) {
+ const slotX = x + padding + col * cellSize
+ const slotY = y + padding + row * cellSize
+
+ // Create slot background
+ const slot = this.add
+ .rectangle(slotX, slotY, cellSize - 2, cellSize - 2, 0x666666)
+ .setOrigin(0, 0)
+ .setAlpha(0.8)
+ .setScrollFactor(0)
+ .setDepth(1001);
+
+ this.inventorySlots.push(slot)
+ }
}
-
+
// Hide inventory initially
- this.hideInventory();
+ this.hideInventory()
}
hideInventory() {
- this.inventoryBg.setVisible(false);
- this.inventorySlots.forEach(slot => slot.setVisible(false));
+ this.inventoryBg.setVisible(false)
+ this.inventorySlots.forEach((slot) => slot.setVisible(false))
}
showInventory() {
- this.inventoryBg.setVisible(true);
- this.inventorySlots.forEach(slot => slot.setVisible(true));
+ this.inventoryBg.setVisible(true)
+ this.inventorySlots.forEach((slot) => slot.setVisible(true))
}
update(time, delta) {
- const speed = 80
- const prevVelocity = this.player.body.velocity.clone()
- let newX = this.player.x
- let newY = this.player.y
+ if (!this.player) return;
+
+ // Update depths based on Y position, but keep lower than UI elements
+ this.player.setDepth(this.player.y + 100); // Base player depth on Y position
+
+ Object.values(this.otherPlayers).forEach(otherPlayer => {
+ otherPlayer.setDepth(otherPlayer.y + 100); // Same for other players
+ });
- // Stop any previous movement from the last frame
- this.player.body.setVelocity(0)
+ const speed = 80;
+ let animation = 'idleDown'; // Change default idle animation
-
// Stop any previous movement
- this.player.body.setVelocity(0)
-
- let animation = this.lastDirection ? 'idle' + this.lastDirection : 'idleDown'
-
- // Handle movement and set last direction
- if (this.cursors.left.isDown) {
- this.player.body.setVelocityX(-speed)
- animation = 'walkRight'
- this.lastDirection = 'Right'
- this.player.flipX = true
- } else if (this.cursors.right.isDown) {
- this.player.body.setVelocityX(speed)
- animation = 'walkRight'
- this.lastDirection = 'Right'
- this.player.flipX = false
- }
+ this.player.body.setVelocity(0);
- if (this.cursors.up.isDown) {
- this.player.body.setVelocityY(-speed)
- animation = 'walkUp'
- this.lastDirection = 'Up'
- } else if (this.cursors.down.isDown) {
- this.player.body.setVelocityY(speed)
- animation = 'walkDown'
- this.lastDirection = 'Down'
- }
+ if (!this.player.isAttacking) {
+ // Track last pressed direction
+ if (this.cursors.left.isDown) {
+ this.player.body.setVelocityX(-speed);
+ animation = 'walkLeft';
+ this.player.lastDirection = 'Left';
+ this.player.flipX = true;
+ } else if (this.cursors.right.isDown) {
+ this.player.body.setVelocityX(speed);
+ animation = 'walkRight';
+ this.player.lastDirection = 'Right';
+ this.player.flipX = false;
+ }
- // Normalize and scale the velocity
- this.player.body.velocity.normalize().scale(speed)
+ if (this.cursors.up.isDown) {
+ this.player.body.setVelocityY(-speed);
+ animation = 'walkUp';
+ this.player.lastDirection = 'Up';
+ } else if (this.cursors.down.isDown) {
+ this.player.body.setVelocityY(speed);
+ animation = 'walkDown';
+ this.player.lastDirection = 'Down';
+ }
- // Handle idle animations based on last direction
- if (!this.cursors.left.isDown &&
- !this.cursors.right.isDown &&
- !this.cursors.up.isDown &&
- !this.cursors.down.isDown) {
-
- if (this.lastDirection) {
- animation = 'idle' + this.lastDirection
+ // If no movement keys are pressed, play the correct idle animation
+ if (this.player.body.velocity.x === 0 && this.player.body.velocity.y === 0) {
+ animation = `idle${this.player.lastDirection || 'Down'}`;
}
- }
- // Play the animation
- this.player.play(animation, true)
+ // Play the animation
+ if (this.anims.exists(animation)) { // Check if animation exists before playing
+ this.player.anims.play(animation, true);
+ }
+ }
// Emit movement to server
- if (time - this.lastEmitTime > 5) {
- this.socket.emit('movePlayer', {
+ if (time - this.lastEmitTime > 16) {
+ this.socket.emit('playerInput', {
x: this.player.x,
y: this.player.y,
animation: animation,
flipX: this.player.flipX,
- lastDirection: this.lastDirection
- })
- this.lastEmitTime = time
+ lastDirection: this.lastDirection,
+ });
+ this.lastEmitTime = time;
}
+
+ // Handle attack
if (Phaser.Input.Keyboard.JustDown(this.attackKey)) {
- this.handleAttack()
+ this.handleAttack();
}
- // Add inventory toggle at the end of update
+ // Handle inventory toggle
if (Phaser.Input.Keyboard.JustDown(this.inventoryKey)) {
if (this.inventoryBg.visible) {
this.hideInventory();
@@ -433,31 +735,341 @@ export default class CommonScene extends Phaser.Scene {
this.showInventory();
}
}
+
+ // Update health bar
+ if (this.healthBar) {
+ const yOffset = -20;
+ const width = 40;
+
+ this.healthBar.outline.x = this.player.x;
+ this.healthBar.outline.y = this.player.y + yOffset;
+ this.healthBar.background.x = this.player.x;
+ this.healthBar.background.y = this.player.y + yOffset;
+
+ this.healthBar.bar.x = this.player.x - width/2;
+ this.healthBar.bar.y = this.player.y + yOffset;
+ this.healthBar.bar.width = (this.player.life / 100) * width;
+ }
}
handleAttack() {
// Find the closest player to attack
- let closestPlayer = null
- let closestDistance = Infinity
+ let closestPlayer = null;
+ let closestDistance = Infinity;
Object.keys(this.otherPlayers).forEach((id) => {
- const otherPlayer = this.otherPlayers[id]
- const distance = Phaser.Math.Distance.Between(
- this.player.x,
- this.player.y,
- otherPlayer.x,
- otherPlayer.y
- )
- if (distance < closestDistance) {
- closestDistance = distance
- closestPlayer = otherPlayer
+ const otherPlayer = this.otherPlayers[id];
+ const distance = Phaser.Math.Distance.Between(
+ this.player.x,
+ this.player.y,
+ otherPlayer.x,
+ otherPlayer.y
+ );
+ if (distance < closestDistance) {
+ closestDistance = distance;
+ closestPlayer = otherPlayer;
+ }
+ });
+
+ if (closestPlayer && closestDistance < 25) {
+ // Get the current direction from the player's last movement
+ const direction = this.player.lastDirection || 'Down';
+
+ // Set the attack animation based on direction
+ const attackAnim = `attack${direction}`;
+
+ // Pass both the animation and direction to handleAttack
+ PlayerManager.handleAttack(this, this.player, closestPlayer.playerId, attackAnim);
+ console.log('Attacking player:', closestPlayer.playerId, 'with animation:', attackAnim);
+ }
+ }
+
+ renderGameState(state) {
+ Object.entries(state.players).forEach(([playerId, playerData]) => {
+ if (playerId === this.socket.id) {
+ this.renderPlayer(playerData);
+ } else {
+ this.renderOtherPlayer(playerId, playerData);
}
- })
+ });
+ }
+
+ renderPlayer(playerData) {
+ if (!this.player) {
+ this.player = this.add.sprite(playerData.x, playerData.y, 'player');
+ }
+ this.player.setPosition(playerData.x, playerData.y);
+ this.player.flipX = playerData.flipX;
+ if (playerData.animation) {
+ this.player.play(playerData.animation, true);
+ }
+ }
- if (closestPlayer && closestDistance < 20) {
- // Adjust attack range as needed
- this.socket.emit('attackPlayer', closestPlayer.playerId)
- console.log('Attacking player:', closestPlayer.playerId)
+ renderOtherPlayer(playerId, playerData) {
+ if (!this.otherPlayers[playerId]) {
+ this.otherPlayers[playerId] = this.add.sprite(playerData.x, playerData.y, 'player');
+ }
+ const otherPlayer = this.otherPlayers[playerId];
+ otherPlayer.setPosition(playerData.x, playerData.y);
+ otherPlayer.flipX = playerData.flipX;
+ if (playerData.animation) {
+ otherPlayer.play(playerData.animation, true);
+ }
+ }
+
+ createChatInput() {
+ // Check if chat already exists, if so, return early
+ if (document.getElementById('game-chat-container')) {
+ return;
+ }
+
+ // Add bad words filter
+ const badWords = [
+ 'fuck', 'shit', 'ass', 'bitch', 'dick', 'pussy', 'cunt',
+ 'bastard', 'damn', 'piss', 'cock', 'slut', 'whore',
+ // Add more words as needed
+ ];
+
+ // Function to filter bad words
+ const filterMessage = (message) => {
+ let filteredMessage = message.toLowerCase();
+ badWords.forEach(word => {
+ // Create a regular expression that matches the word with possible special characters
+ const regex = new RegExp(word.split('').join('[^a-zA-Z]*'), 'gi');
+ // Replace bad word with asterisks
+ filteredMessage = filteredMessage.replace(regex, '*'.repeat(word.length));
+ });
+ return filteredMessage;
+ };
+
+ // Create chat container
+ const chatContainer = document.createElement('div');
+ chatContainer.style.position = 'absolute';
+ chatContainer.style.bottom = '65px';
+ chatContainer.style.right = '20px';
+ chatContainer.style.width = '260px';
+ chatContainer.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
+ chatContainer.style.borderRadius = '5px';
+ chatContainer.style.padding = '5px';
+ chatContainer.style.zIndex = '1000';
+ chatContainer.style.minHeight = '30px'; // Added minimum height
+
+ // Create minimize button
+ const minimizeButton = document.createElement('button');
+ minimizeButton.textContent = '−';
+ minimizeButton.style.position = 'absolute';
+ minimizeButton.style.right = '5px';
+ minimizeButton.style.top = '5px';
+ minimizeButton.style.padding = '0px 6px';
+ minimizeButton.style.backgroundColor = 'transparent';
+ minimizeButton.style.border = '1px solid white';
+ minimizeButton.style.color = 'white';
+ minimizeButton.style.cursor = 'pointer';
+ minimizeButton.style.fontSize = '20px'; // Increased font size
+ minimizeButton.style.lineHeight = '20px'; // Centered the symbol vertically
+ chatContainer.appendChild(minimizeButton);
+
+ // Create messages container
+ const messagesContainer = document.createElement('div');
+ messagesContainer.style.height = '200px';
+ messagesContainer.style.overflowY = 'auto';
+ messagesContainer.style.marginTop = '25px'; // Space for minimize button
+ messagesContainer.style.color = 'white';
+ messagesContainer.style.fontSize = '14px';
+ messagesContainer.style.wordBreak = 'break-word';
+ chatContainer.appendChild(messagesContainer);
+
+ // Create chat input element
+ const chatInput = document.createElement('input');
+ chatInput.type = 'text';
+ chatInput.placeholder = 'Type message...';
+ chatInput.style.position = 'absolute';
+ chatInput.style.bottom = '20px';
+ chatInput.style.right = '80px';
+ chatInput.style.width = '200px';
+ chatInput.style.padding = '5px';
+ chatInput.style.zIndex = '1000';
+ chatInput.style.height = '25px';
+
+ // Create send button
+ const sendButton = document.createElement('button');
+ sendButton.textContent = 'Send';
+ sendButton.style.position = 'absolute';
+ sendButton.style.bottom = '20px';
+ sendButton.style.right = '20px';
+ sendButton.style.padding = '5px 10px';
+ sendButton.style.zIndex = '1000';
+ sendButton.style.height = '25px';
+ sendButton.style.verticalAlign = 'top';
+
+ // Update chat input styling
+ chatInput.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
+ chatInput.style.color = 'white';
+ chatInput.style.border = '1px solid rgba(255, 255, 255, 0.3)';
+ chatInput.style.borderRadius = '5px';
+ chatInput.style.padding = '5px 10px';
+ chatInput.style.outline = 'none';
+ chatInput.style.width = '200px';
+
+ // Update send button styling to match
+ sendButton.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
+ sendButton.style.color = 'white';
+ sendButton.style.border = '1px solid rgba(255, 255, 255, 0.3)';
+ sendButton.style.borderRadius = '5px';
+ sendButton.style.padding = '5px 10px';
+ sendButton.style.cursor = 'pointer';
+
+ // Add to document
+ document.body.appendChild(chatContainer);
+ document.body.appendChild(chatInput);
+ document.body.appendChild(sendButton);
+
+ // Minimize functionality
+ let isMinimized = false;
+ minimizeButton.addEventListener('click', () => {
+ isMinimized = !isMinimized;
+ messagesContainer.style.display = isMinimized ? 'none' : 'block';
+ minimizeButton.textContent = isMinimized ? '+' : '−';
+ chatContainer.style.height = isMinimized ? 'auto' : 'auto';
+ });
+
+ // Remove the duplicate sendMessage function and keep just one
+ const sendMessage = () => {
+ const message = chatInput.value.trim();
+ if (message) {
+ const filteredMessage = filterMessage(message);
+ // Send message with player ID
+ this.socket.emit('chatMessage', {
+ playerId: this.socket.id,
+ message: filteredMessage
+ });
+ chatInput.value = '';
+ chatInput.blur();
+ this.input.keyboard.enabled = true;
+ }
+ };
+
+ // Update message display function to use filter for incoming messages
+ const addMessage = (socketId, message) => {
+ const messageElement = document.createElement('div');
+ messageElement.style.marginBottom = '5px';
+ messageElement.style.padding = '3px';
+
+ // Highlight our own messages
+ if (socketId === this.socket.id) {
+ messageElement.innerHTML = `[${socketId}]: ${message}`; // Green color for own messages
+ } else {
+ messageElement.innerHTML = `[${socketId}]: ${message}`; // Grey for others
+ }
+
+ messagesContainer.appendChild(messageElement);
+ messagesContainer.scrollTop = messagesContainer.scrollHeight;
+ };
+
+ // Disable game input when chat is focused
+ chatInput.addEventListener('focus', (e) => {
+ e.stopPropagation();
+ this.input.keyboard.enabled = false;
+ });
+
+ chatInput.addEventListener('blur', (e) => {
+ e.stopPropagation();
+ this.input.keyboard.enabled = true;
+ });
+
+ // Event listeners
+ sendButton.addEventListener('click', sendMessage);
+
+ chatInput.addEventListener('keydown', (e) => {
+ e.stopPropagation();
+ if (e.key === 'Enter') {
+ sendMessage();
+ }
+ });
+
+ // Store references for cleanup
+ this.chatInput = chatInput;
+ this.sendButton = sendButton;
+ this.chatContainer = chatContainer;
+
+ // Add chat message handler if not already in handleSocketEvents
+ this.socket.on('chatMessage', (data) => {
+ console.log('Received chat message:', data); // Debug log
+ addMessage(data.playerId, data.message);
+ });
+ }
+
+ showChatBubble(playerId, message) {
+ // Remove existing bubble for this player if it exists
+ if (this.chatBubbles[playerId]) {
+ this.chatBubbles[playerId].destroy();
+ }
+
+ // Get player position
+ const player = playerId === this.socket.id ? this.player : this.otherPlayers[playerId];
+ if (!player) return;
+
+ // Create background rectangle
+ const padding = 0;
+ const bubbleWidth = 150;
+ const bubbleHeight = 40;
+
+ const bubble = this.add.container(player.x, player.y - 60);
+
+ // Add semi-transparent background
+ const background = this.add.rectangle(
+ 0,
+ 0,
+ bubbleWidth,
+ bubbleHeight,
+ 0x000000,
+ 0.5 // Alpha value for transparency
+ );
+
+ // Add text
+ const text = this.add.text(
+ 0,
+ 0,
+ message,
+ {
+ fontSize: '14px',
+ color: '#ffffff',
+ align: 'center',
+ wordWrap: { width: bubbleWidth - padding * 2 }
+ }
+ );
+
+ // Center text in bubble
+ text.setPosition(
+ -text.width / 2,
+ -text.height / 2
+ );
+
+ // Add to container
+ bubble.add([background, text]);
+
+ // Store bubble reference
+ this.chatBubbles[playerId] = bubble;
+
+ // Destroy bubble after 5 seconds
+ this.time.delayedCall(5000, () => {
+ if (this.chatBubbles[playerId] === bubble) {
+ bubble.destroy();
+ delete this.chatBubbles[playerId];
+ }
+ });
+ }
+
+ // Add cleanup in scene shutdown
+ shutdown() {
+ if (this.chatInput) {
+ this.chatInput.remove();
+ }
+ if (this.sendButton) {
+ this.sendButton.remove();
+ }
+ if (this.chatContainer) {
+ this.chatContainer.remove();
}
}
}
diff --git a/client/dungeonmap/src/DungeonScene.js b/client/dungeonmap/src/DungeonScene.js
index 84eb2bb..bad63be 100644
--- a/client/dungeonmap/src/DungeonScene.js
+++ b/client/dungeonmap/src/DungeonScene.js
@@ -1,5 +1,12 @@
import Phaser from "phaser";
import Level from "./Level.js";
+import BackgroundScene from './managers/backgroundscene'
+import { PlayerManager } from './managers/PlayerManager'
+import { AnimationManager } from './managers/AnimationManager'
+import { CombatManager } from './managers/CombatManager'
+import { io } from 'socket.io-client'
+
+
export default class DungeonScene extends Phaser.Scene {
constructor() {
@@ -8,6 +15,39 @@ export default class DungeonScene extends Phaser.Scene {
this.level = new Level();
this.spike = null;
this.healthBar = null;
+ this.otherPlayers = {};
+ this.canMove = false;
+ this.countdownText = null;
+ this.countdownStarted = false;
+ this.socket = null;
+ this.backgroundScene = null;
+ this.animationsCreated = false; // Track if animations are created
+ this.otherPlayersGroup = null;
+ this.playerWorldPosition = {};
+ this.playersGroup = null;
+ this.playerEventListeners = new Map();
+ this.localPlayerId = null; // Track local player ID
+ this.hasJoinedScene = false; // Track if we've already joined
+ this.initializedPlayers = new Set(); // Track which players we've initialized
+ this.existingPlayers = new Set(); // Track existing player IDs
+ this.isAttacking = false; // Add attack state tracking
+ this.lastMovementUpdate = 0;
+ this.movementUpdateInterval = 50; // Update every 50ms
+ this.lastPosition = { x: 0, y: 0, animation: '', flipX: false };
+ }
+
+ init(data) {
+ console.log('DungeonScene init with data:', data);
+ // Only set socket once
+ if (!this.socket) {
+ if (data && data.socket) {
+ this.socket = data.socket;
+ } else {
+ this.backgroundScene = this.scene.get('BackgroundScene');
+ this.socket = this.backgroundScene.getSocket();
+ }
+ console.log('Socket initialized:', this.socket.id);
+ }
}
preload() {
@@ -29,7 +69,13 @@ export default class DungeonScene extends Phaser.Scene {
}
create() {
-this.game.scale.resize(256,256)
+ console.log('DungeonScene create starting');
+
+ // Initialize groups first, before any other creation logic
+ this.playersGroup = this.add.group();
+ this.otherPlayersGroup = this.add.group();
+
+ this.game.scale.resize(256,256)
const map = this.make.tilemap({ key: "dungeon" });
const floor = map.addTilesetImage("Dungeon_1", "Dungeon_1");
const floorLayer = map.createLayer("Tile Layer 1", [floor], 0, -100);
@@ -79,80 +125,363 @@ this.game.scale.resize(256,256)
this.player.setBodySize(24, 28);
this.player.setOffset(10, 13);
this.cursors = this.input.keyboard.createCursorKeys();
- this.anims.create({
- key: "idleDown",
- frames: this.anims.generateFrameNumbers("player", { start: 0, end: 5 }),
- frameRate: 10,
- repeat: -1,
+
+ this.player.play("idleDown");
+ this.healthBar = this.createHealthBar(this.player.x, this.player.y, this.player);
+
+
+
+ // Add leave button
+ const leaveButton = this.add.text(
+ this.cameras.main.width - 2,
+ this.cameras.main.height - 2,
+ 'Quit Match',
+ {
+ fontFamily: 'Verdana',
+ fontSize: '10px',
+ fill: '#fff',
+ backgroundColor: '#000',
+ padding: { x: 5, y: 2 }
+ }
+ )
+ .setOrigin(1, 1)
+ .setScrollFactor(0)
+ .setInteractive()
+ .setDepth(1000);
+
+ leaveButton.on('pointerdown', () => {
+ console.log('Returning to common map');
+
+ if (this.socket) {
+ this.socket.emit('playerLeftScene', {
+ playerId: this.socket.id,
+ from: 'DungeonScene',
+ to: 'CommonScene'
+ });
+ }
+
+ // Reset game scale before transitioning
+ this.game.scale.resize(800, 600); // Set to CommonScene dimensions
+
+ this.scene.start('CommonScene', {
+ x: 620,
+ y: 360
+ });
});
- this.physics.add.collider(this.player, pillarLayer);
- this.physics.add.collider(this.player, objectLayer);
+ // Add waiting text instead of starting countdown immediately
+ this.countdownText = this.add.text(
+ this.cameras.main.width / 2,
+ this.cameras.main.height / 2,
+ 'In Queue...',
+ {
+ fontSize: '32px',
+ fill: '#fff',
+ stroke: '#000',
+ strokeThickness: 4
+ }
+ )
+ .setOrigin(0.5)
+ .setScrollFactor(0)
+ .setDepth(1000);
+
+ // Listen for player join/leave events
+ this.socket.on('playerJoined', () => this.checkPlayersAndStartCountdown());
+ this.socket.on('playerLeft', () => this.checkPlayersAndStartCountdown());
+
+ // Create animations using AnimationManager
+ AnimationManager.createAnimations(this);
- this.anims.create({
- key: "idleRight",
- frames: this.anims.generateFrameNumbers("player", { start: 6, end: 11 }),
- frameRate: 10,
- repeat: -1,
+ // Setup socket events if socket exists
+ if (this.socket) {
+ // Remove any existing listeners first
+ this.socket.removeAllListeners('gameState');
+ this.socket.removeAllListeners('playerDisconnected');
+
+ // Add new listeners
+ this.socket.on('gameState', (state) => {
+ if (state && state.players) {
+ Object.entries(state.players).forEach(([playerId, playerData]) => {
+ if (playerId !== this.socket.id && !this.otherPlayers[playerId]) {
+ // Add new player
+ this.addOtherPlayer(playerData);
+ } else if (this.otherPlayers[playerId]) {
+ // Update existing player
+ const otherPlayer = this.otherPlayers[playerId];
+ otherPlayer.x = playerData.x;
+ otherPlayer.y = playerData.y;
+ if (playerData.animation) {
+ otherPlayer.play(playerData.animation, true);
+ }
+ }
+ });
+ }
+ });
+
+ this.socket.on('playerDisconnected', (playerId) => {
+ if (this.otherPlayers[playerId]) {
+ this.otherPlayers[playerId].destroy();
+ delete this.otherPlayers[playerId];
+ }
+ });
+
+ // Join the scene
+ this.socket.emit('joinScene', {
+ scene: 'DungeonScene',
+ x: this.game.config.width / 2,
+ y: this.game.config.height / 2
+ });
+ }
+
+ // Add portal collision handler
+ this.physics.add.overlap(this.player, this.portal, () => {
+ // Tell server we're leaving this scene
+ this.socket.emit('leaveDungeonScene');
+
+ // Start next scene
+ this.scene.start('CommonScene'); // or whatever scene you're transitioning to
});
- this.anims.create({
- key: "idleUp",
- frames: this.anims.generateFrameNumbers("player", { start: 12, end: 17 }),
- frameRate: 10,
- repeat: -1,
+ // Initialize background scene
+ if (!this.scene.isActive('BackgroundScene')) {
+ this.scene.launch('BackgroundScene');
+ }
+
+ this.backgroundScene = this.scene.get('BackgroundScene');
+ this.socket = this.backgroundScene.getSocket();
+
+ // Tell background scene we're here
+ this.backgroundScene.events.emit('changeScene', 'DungeonScene');
+
+ // Listen for player updates
+ this.backgroundScene.events.on('playerUpdated', ({ playerId, playerInfo, isNew, isMovement }) => {
+ if (playerId === this.socket.id) {
+ if (isNew) this.addPlayer(playerInfo);
+ } else {
+ if (isNew) {
+ this.addOtherPlayer(playerInfo);
+ } else if (isMovement) {
+ this.updateOtherPlayer(playerId, playerInfo);
+ }
+ }
+ });
+
+ // Listen for player removals
+ this.backgroundScene.events.on('playerRemoved', (playerId) => {
+ if (this.otherPlayers[playerId]) {
+ this.otherPlayers[playerId].destroy();
+ delete this.otherPlayers[playerId];
+ }
});
- this.anims.create({
- key: "walkDown",
- frames: this.anims.generateFrameNumbers("player", { start: 18, end: 23 }),
- frameRate: 10,
- repeat: -1,
+ this.otherPlayersGroup = this.add.group();
+
+ // Add these socket listeners
+ this.socket.on('currentPlayers', (players) => {
+ console.log('Current players in DungeonScene:', players);
+
+ // Don't clear if we already have these players
+ const currentPlayerIds = new Set(Object.keys(players));
+ const existingPlayerIds = new Set(Object.keys(this.playerWorldPosition));
+
+ // Only clear and recreate if the player sets are different
+ if (!this.areSetsEqual(currentPlayerIds, existingPlayerIds)) {
+ this.clearExistingPlayers();
+
+ Object.entries(players).forEach(([id, playerInfo]) => {
+ if (!this.existingPlayers.has(id)) {
+ console.log('Creating new player:', id);
+ if (id === this.socket.id) {
+ this.player = this.createPlayerWithListeners(id, playerInfo);
+ } else if (playerInfo.scene === 'DungeonScene') {
+ this.createPlayerWithListeners(id, playerInfo);
+ }
+ this.existingPlayers.add(id);
+ }
+ });
+
+ // After all players are created, check if we should start countdown
+ if (Object.keys(players).length >= 2) {
+ // Tell server we have enough players
+ this.socket.emit('dungeonReady', {
+ scene: 'DungeonScene',
+ players: Object.keys(players)
+ });
+ }
+ }
});
- this.anims.create({
- key: "walkRight",
- frames: this.anims.generateFrameNumbers("player", { start: 24, end: 29 }),
- frameRate: 10,
- repeat: -1,
+ this.socket.on('newPlayer', (playerInfo) => {
+ console.log('New player joined:', playerInfo);
+ if (playerInfo.playerId !== this.localPlayerId && playerInfo.scene === 'DungeonScene') {
+ this.createPlayerWithListeners(playerInfo.playerId, playerInfo);
+ }
});
- this.anims.create({
- key: "walkUp",
- frames: this.anims.generateFrameNumbers("player", { start: 30, end: 35 }),
- frameRate: 10,
- repeat: -1,
+ this.socket.on('playerMovedInDungeon', (playerInfo) => {
+ if (this.otherPlayers[playerInfo.playerId]) {
+ const otherPlayer = this.otherPlayers[playerInfo.playerId];
+ otherPlayer.setPosition(playerInfo.x, playerInfo.y);
+ if (playerInfo.animation) {
+ otherPlayer.play(playerInfo.animation, true);
+ }
+ otherPlayer.setFlipX(playerInfo.flipX || false);
+ }
});
- this.anims.create({
- key: "attackDown",
- frames: this.anims.generateFrameNumbers("player", { start: 36, end: 39 }),
- frameRate: 10,
- repeat: 1,
+ this.socket.on('playerDisconnected', (playerId) => {
+ if (this.otherPlayers[playerId]) {
+ this.otherPlayers[playerId].destroy();
+ delete this.otherPlayers[playerId];
+ }
});
- this.anims.create({
- key: "attackRight",
- frames: this.anims.generateFrameNumbers("player", { start: 42, end: 46 }),
- frameRate: 10,
- repeat: -1,
+ // Listen for server's countdown signal
+ this.socket.on('dungeonCountdown', () => {
+ if (!this.countdownStarted) {
+ this.startCountdown();
+ }
});
- this.anims.create({
- key: "attackUp",
- frames: this.anims.generateFrameNumbers("player", { start: 48, end: 52 }),
- frameRate: 10,
- repeat: -1,
+ // Store local player ID
+ this.localPlayerId = this.socket.id;
+ console.log('Local player ID:', this.localPlayerId);
+
+ // Clean up old listeners before adding new ones
+ this.cleanupSocketListeners();
+
+ // Set up socket listeners
+ this.socket.on('currentPlayers', (players) => {
+ console.log('Current players received:', Object.keys(players));
+
+ // Clear existing players first
+ this.clearExistingPlayers();
+
+ Object.entries(players).forEach(([id, playerInfo]) => {
+ if (!this.initializedPlayers.has(id)) {
+ console.log('Initializing player:', id);
+ if (id === this.localPlayerId) {
+ console.log('Creating local player');
+ this.player = this.createPlayerWithListeners(id, playerInfo);
+ } else if (playerInfo.scene === 'DungeonScene') {
+ console.log('Creating remote player');
+ this.createPlayerWithListeners(id, playerInfo);
+ }
+ this.initializedPlayers.add(id);
+ } else {
+ console.log('Player already initialized:', id);
+ }
+ });
});
- this.anims.create({
- key: "die",
- frames: this.anims.generateFrameNumbers("player", { start: 54, end: 56 }),
- frameRate: 10,
- repeat: 0,
+ this.socket.on('newPlayer', (playerInfo) => {
+ console.log('New player joined:', playerInfo);
+ if (playerInfo.playerId !== this.localPlayerId && playerInfo.scene === 'DungeonScene') {
+ this.createPlayerWithListeners(playerInfo.playerId, playerInfo);
+ }
});
- this.player.play("idleDown");
- this.healthBar = this.createHealthBar(this.player.x, this.player.y, this.player);
+
+ this.socket.on('playerMoved', (playerInfo) => {
+ const otherPlayer = this.otherPlayers[playerInfo.playerId];
+ if (otherPlayer) {
+ // Clear any existing tweens to prevent position conflicts
+ this.tweens.killTweensOf(otherPlayer);
+
+ // Update position immediately
+ otherPlayer.setPosition(playerInfo.x, playerInfo.y);
+
+ // Update animation only if it's different
+ if (playerInfo.animation && (!otherPlayer.anims.currentAnim ||
+ otherPlayer.anims.currentAnim.key !== playerInfo.animation)) {
+ otherPlayer.play(playerInfo.animation, true);
+ }
+
+ // Update flip state
+ otherPlayer.setFlipX(playerInfo.flipX);
+
+ // Store latest info
+ otherPlayer.playerInfo = {
+ ...playerInfo,
+ lastUpdated: Date.now()
+ };
+ }
+ });
+
+ // Only emit join if we haven't already
+ if (!this.hasJoinedScene) {
+ console.log('Emitting first-time join for player:', this.localPlayerId);
+ this.socket.emit('joinScene', {
+ scene: 'DungeonScene',
+ playerId: this.localPlayerId,
+ x: this.getSpawnPoint().x,
+ y: this.getSpawnPoint().y,
+ animation: 'idleDown',
+ flipX: false
+ });
+ this.hasJoinedScene = true;
+ }
+
+ // Clean up old listeners before adding new ones
+ this.cleanupSocketListeners();
+
+ // Set up socket listeners
+ this.setupSocketListeners();
+
+ // Update server with player movement
+ this.time.addEvent({
+ delay: 50, // Send updates every 50ms
+ callback: this.sendPlayerUpdate,
+ callbackScope: this,
+ loop: true
+ });
+
+ // Listen for player counts in this scene specifically
+ this.socket.on('playerCount', (data) => {
+ console.log('Player count update:', data);
+ if (data.scene === 'DungeonScene' && data.count >= 2 && !this.countdownStarted) {
+ this.startCountdown();
+ }
+ });
+
+ // Add spacebar input
+ this.spacebar = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE);
+
+ // Add attack animations
+ const attackAnims = [
+ { key: 'attackDown', start: 36, end: 39 },
+ { key: 'attackRight', start: 42, end: 45 },
+ { key: 'attackUp', start: 48, end: 51 },
+ { key: 'attackLeft', start: 42, end: 45 }
+ ];
+
+ attackAnims.forEach(anim => {
+ if (!this.anims.exists(anim.key)) {
+ this.anims.create({
+ key: anim.key,
+ frames: this.anims.generateFrameNumbers('player', {
+ start: anim.start,
+ end: anim.end
+ }),
+ frameRate: 10,
+ repeat: 0, // Ensure animation only plays once
+ hideOnComplete: false // Don't hide sprite when animation completes
+ });
+ }
+ });
+
+ // Create physics group for other players if it doesn't exist
+ this.otherPlayersGroup = this.physics.add.group({
+ collideWorldBounds: true
+ });
+
+ // Add collision between players
+ this.physics.add.collider(this.player, this.otherPlayersGroup);
+
+ // Add collision between other players
+ this.physics.add.collider(this.otherPlayersGroup, this.otherPlayersGroup);
+
+ // Set world bounds
+ this.physics.world.setBounds(0, 0, this.game.config.width, this.game.config.height);
}
createHealthBar(x, y, player) {
@@ -165,10 +494,13 @@ this.game.scale.resize(256,256)
// Black background
const healthBarBackground = this.add.rectangle(x, y - 40, width, height, 0x000000);
- // Red health bar - set origin to left
+ // Red health bar - set origin to left and start with full width
const healthBar = this.add.rectangle(x - width/2, y - 40, width, height, 0xff0000)
.setOrigin(0, 0.5);
+ // Make sure initial width matches full health
+ healthBar.width = width; // Start with full width since health is 100
+
return {
outline: outline,
background: healthBarBackground,
@@ -179,49 +511,36 @@ this.game.scale.resize(256,256)
update() {
const speed = 80;
const prevVelocity = this.player.body.velocity.clone();
- let newX = this.player.x;
- let newY = this.player.y;
- // this.input.keyboard.addListener("keydown-F", (e) => {
- // console.log(e)
- // this.player.play("attackRight");
- // })
+ // Only allow movement if countdown is finished
+ if (!this.canMove) {
+ if (this.player) {
+ this.player.body.setVelocity(0);
+ }
+ return;
+ }
// Stop any previous movement from the last frame
this.player.body.setVelocity(0);
// Horizontal movement
if (this.cursors.left.isDown) {
- newX -= speed * (1 / 60);
- if (!this.level.isColliding(newX, newY)) {
this.player.body.setVelocityX(-speed);
- this.player.anims.play("walkRight", true); // Assuming you have a 'walkLeft' animation
- this.player.flipX = true; // Flip the sprite to face left
- }
+ this.player.anims.play("walkRight", true);
+ this.player.flipX = true;
} else if (this.cursors.right.isDown) {
- newX += speed * (1 / 60);
- if (!this.level.isColliding(newX, newY)) {
this.player.body.setVelocityX(speed);
this.player.anims.play("walkRight", true);
- this.player.flipX = false; // Ensure the sprite is facing right
- }
+ this.player.flipX = false;
}
// Vertical movement
if (this.cursors.up.isDown) {
- newY -= speed * (1 / 60);
- if (!this.level.isColliding(newX, newY)) {
this.player.body.setVelocityY(-speed);
this.player.anims.play("walkUp", true);
- }
} else if (this.cursors.down.isDown) {
- newY += speed * (1 / 60);
- console.log(this.level.isColliding(newX, newY));
- if (!this.level.isColliding(newX, newY)) {
- console.log("not");
this.player.body.setVelocityY(speed);
this.player.anims.play("walkDown", true);
- }
}
// Normalize and scale the velocity so that player can't move faster along a diagonal
@@ -238,7 +557,7 @@ this.game.scale.resize(256,256)
// Set idle animation based on the last direction
if (prevVelocity.x < 0) {
- this.player.anims.play("idleRight", true);
+ this.player.anims.play("idleLeft", true);
this.player.flipX = true;
} else if (prevVelocity.x > 0) {
this.player.anims.play("idleRight", true);
@@ -251,7 +570,7 @@ this.game.scale.resize(256,256)
}
// Update health bar position and width
- if (this.healthBar) {
+ if (this.healthBar && this.player) {
const yOffset = -20;
const width = 40;
@@ -260,11 +579,60 @@ this.game.scale.resize(256,256)
this.healthBar.background.x = this.player.x;
this.healthBar.background.y = this.player.y + yOffset;
- // Update red bar position and width
+ // Update red bar position and width based on current health
this.healthBar.bar.x = this.player.x - width/2;
this.healthBar.bar.y = this.player.y + yOffset;
- this.healthBar.bar.width = (this.player.life / 100) * width;
+ this.healthBar.bar.width = (this.player.life / 100) * width; // Make sure this.player.life is set to 100 initially
+ }
+
+ this.otherPlayersGroup.getChildren().forEach((player) => {
+ if (player.playerInfo) {
+ player.setPosition(player.playerInfo.x, player.playerInfo.y);
+ player.play(player.playerInfo.animation, true);
+ player.flipX = player.playerInfo.flipX;
+ }
+ });
+
+ // Update local player
+ if (this.player && this.canMove) {
+ // ... existing player movement code ...
+
+ // Only emit if this is the local player
+ if (this.socket.id === this.localPlayerId) {
+ this.sendPlayerUpdate();
+ }
+
+ // Handle attack
+ if (Phaser.Input.Keyboard.JustDown(this.spacebar) && !this.isAttacking) {
+ this.handleAttack(); // Use the consolidated attack handler
+ }
}
+
+ // Update other players
+ Object.entries(this.playerWorldPosition).forEach(([playerId, playerData]) => {
+ if (playerData.sprite && playerId !== this.localPlayerId) {
+ playerData.sprite.setPosition(playerData.x, playerData.y);
+ if (playerData.animation) {
+ playerData.sprite.play(playerData.animation, true);
+ }
+ playerData.sprite.setFlipX(playerData.flipX);
+ }
+ });
+
+ // Clean up any trailing sprites
+ Object.values(this.otherPlayers).forEach(otherPlayer => {
+ if (otherPlayer && otherPlayer.playerInfo) {
+ // If player hasn't been updated in a while, set to idle
+ const timeSinceUpdate = Date.now() - (otherPlayer.playerInfo.lastUpdated || 0);
+ if (timeSinceUpdate > 100) { // 100ms threshold
+ const currentAnim = otherPlayer.anims.currentAnim;
+ if (currentAnim && currentAnim.key.startsWith('walk')) {
+ const idleAnim = currentAnim.key.replace('walk', 'idle');
+ otherPlayer.play(idleAnim, true);
+ }
+ }
+ }
+ });
}
handleSocketEvents() {
@@ -280,4 +648,462 @@ this.game.scale.resize(256,256)
}
});
}
+
+ getSpawnPoint() {
+ // Define two specific spawn points
+ const spawnPoints = [
+ { x: 50, y: 90 }, // First player spawn
+ { x: 200, y: 90 } // Second player spawn
+ ];
+
+ // Count existing players to determine spawn point
+ const playerCount = Object.keys(this.playerWorldPosition).length;
+ return spawnPoints[playerCount] || spawnPoints[0];
+ }
+
+ checkPlayersAndStartCountdown() {
+ // Remove local countdown check - wait for server signal instead
+ console.log('Waiting for server countdown signal...');
+ }
+
+ startCountdown() {
+ if (this.countdownStarted) return; // Prevent multiple countdowns
+
+ console.log('Starting countdown');
+ this.countdownStarted = true;
+ let count = 3;
+
+ if (this.countdownText) {
+ this.countdownText.setText(count.toString());
+ }
+
+ const countdownInterval = setInterval(() => {
+ count--;
+ if (count > 0) {
+ this.countdownText?.setText(count.toString());
+ } else {
+ this.countdownText?.setText('FIGHT!');
+ this.canMove = true;
+
+ // Remove countdown text after "FIGHT!"
+ setTimeout(() => {
+ this.countdownText?.destroy();
+ this.countdownText = null;
+ }, 1000);
+
+ clearInterval(countdownInterval);
+ }
+ }, 1000);
+ }
+
+ handleAttack() {
+ // Find the closest player to attack
+ let closestPlayer = null;
+ let closestDistance = Infinity;
+
+ Object.keys(this.otherPlayers).forEach((id) => {
+ const otherPlayer = this.otherPlayers[id];
+ const distance = Phaser.Math.Distance.Between(
+ this.player.x,
+ this.player.y,
+ otherPlayer.x,
+ otherPlayer.y
+ );
+ if (distance < closestDistance) {
+ closestDistance = distance;
+ closestPlayer = otherPlayer;
+ }
+ });
+
+ if (closestPlayer && closestDistance < 25) {
+ this.isAttacking = true;
+ const currentDirection = this.getPlayerDirection();
+ const attackAnim = `attack${currentDirection}`;
+
+ if (this.anims.exists(attackAnim)) {
+ // Play attack animation locally
+ this.player.play(attackAnim, true)
+ .once('animationcomplete', () => {
+ this.isAttacking = false;
+ const idleAnim = `idle${currentDirection}`;
+ if (this.anims.exists(idleAnim)) {
+ this.player.play(idleAnim, true);
+ }
+ });
+
+ // Emit attack event with target information
+ this.socket.emit('playerAttack', {
+ x: this.player.x,
+ y: this.player.y,
+ direction: currentDirection,
+ scene: 'DungeonScene',
+ targetId: closestPlayer.playerId
+ });
+
+ console.log('Attacking player:', closestPlayer.playerId, 'with animation:', attackAnim);
+ }
+ }
+ }
+
+ setupSocketListeners() {
+ this.socket.on('currentPlayers', (players) => {
+ console.log('Current players in DungeonScene:', players);
+
+ // Don't clear if we already have these players
+ const currentPlayerIds = new Set(Object.keys(players));
+ const existingPlayerIds = new Set(Object.keys(this.playerWorldPosition));
+
+ // Only clear and recreate if the player sets are different
+ if (!this.areSetsEqual(currentPlayerIds, existingPlayerIds)) {
+ this.clearExistingPlayers();
+
+ Object.entries(players).forEach(([id, playerInfo]) => {
+ if (!this.existingPlayers.has(id)) {
+ console.log('Creating new player:', id);
+ if (id === this.socket.id) {
+ this.player = this.createPlayerWithListeners(id, playerInfo);
+ } else if (playerInfo.scene === 'DungeonScene') {
+ this.createPlayerWithListeners(id, playerInfo);
+ }
+ this.existingPlayers.add(id);
+ }
+ });
+
+ // After all players are created, check if we should start countdown
+ if (Object.keys(players).length >= 2) {
+ // Tell server we have enough players
+ this.socket.emit('dungeonReady', {
+ scene: 'DungeonScene',
+ players: Object.keys(players)
+ });
+ }
+ }
+ });
+
+ // Listen for server's countdown signal
+ this.socket.on('dungeonCountdown', () => {
+ if (!this.countdownStarted) {
+ this.startCountdown();
+ }
+ });
+
+ // Update other players more smoothly
+ this.socket.on('playerMovedInDungeon', (playerInfo) => {
+ if (this.otherPlayers[playerInfo.playerId]) {
+ const otherPlayer = this.otherPlayers[playerInfo.playerId];
+ otherPlayer.setPosition(playerInfo.x, playerInfo.y);
+ if (playerInfo.animation) {
+ otherPlayer.play(playerInfo.animation, true);
+ }
+ otherPlayer.setFlipX(playerInfo.flipX || false);
+ }
+ });
+
+ this.socket.on('playerMoved', (playerInfo) => {
+ const otherPlayer = this.otherPlayers[playerInfo.playerId];
+ if (otherPlayer) {
+ // Clear any existing tweens to prevent position conflicts
+ this.tweens.killTweensOf(otherPlayer);
+
+ // Update position immediately
+ otherPlayer.setPosition(playerInfo.x, playerInfo.y);
+
+ // Update animation only if it's different
+ if (playerInfo.animation && (!otherPlayer.anims.currentAnim ||
+ otherPlayer.anims.currentAnim.key !== playerInfo.animation)) {
+ otherPlayer.play(playerInfo.animation, true);
+ }
+
+ // Update flip state
+ otherPlayer.setFlipX(playerInfo.flipX);
+
+ // Store latest info
+ otherPlayer.playerInfo = {
+ ...playerInfo,
+ lastUpdated: Date.now()
+ };
+ }
+ });
+
+ this.socket.on('playerAttacked', (attackInfo) => {
+ const attacker = attackInfo.playerId === this.socket.id ?
+ this.player : this.otherPlayers[attackInfo.playerId];
+
+ if (attacker && !attacker.isAttacking) {
+ const attackAnim = `attack${attackInfo.direction}`;
+
+ if (this.anims.exists(attackAnim)) {
+ attacker.isAttacking = true;
+
+ attacker.play(attackAnim, true)
+ .once('animationcomplete', () => {
+ attacker.isAttacking = false;
+ // Return to idle after attack
+ const idleAnim = `idle${attackInfo.direction}`;
+ if (this.anims.exists(idleAnim)) {
+ attacker.play(idleAnim, true);
+ }
+ });
+ }
+ }
+ });
+
+ this.socket.on('playerDamaged', (damageInfo) => {
+ console.log('Received damage info:', damageInfo);
+ const targetPlayer = damageInfo.playerId === this.socket.id ?
+ this.player : this.otherPlayers[damageInfo.playerId];
+
+ if (targetPlayer) {
+ targetPlayer.life = damageInfo.newHealth;
+ if (targetPlayer.healthBar) {
+ targetPlayer.healthBar.update();
+ }
+ }
+ });
+ }
+
+ shutdown() {
+ console.log('DungeonScene shutting down');
+ this.cleanupSocketListeners();
+ this.hasJoinedScene = false;
+ this.existingPlayers.clear();
+ }
+
+ createAnimations() {
+ const animations = [
+ { key: 'idleDown', start: 0, end: 5 },
+ { key: 'idleRight', start: 6, end: 11 },
+ { key: 'idleUp', start: 12, end: 17 },
+ { key: 'idleLeft', start: 18, end: 23 },
+ { key: 'walkDown', start: 18, end: 23 },
+ { key: 'walkRight', start: 24, end: 29 },
+ { key: 'walkUp', start: 30, end: 35 },
+ { key: 'attackDown', start: 36, end: 39 },
+ { key: 'attackRight', start: 42, end: 45 },
+ { key: 'attackUp', start: 48, end: 51 },
+ { key: 'attackLeft', start: 52, end: 55 },
+ { key: 'die', start: 54, end: 57 }
+ ];
+
+ animations.forEach(anim => {
+ if (!this.anims.exists(anim.key)) {
+ this.anims.create({
+ key: anim.key,
+ frames: this.anims.generateFrameNumbers('player', {
+ start: anim.start,
+ end: anim.end
+ }),
+ frameRate: 10,
+ repeat: anim.key.startsWith('attack') || anim.key === 'die' ? 0 : -1
+ });
+ }
+ });
+ }
+
+ addOtherPlayer(playerInfo) {
+ if (!playerInfo) return null;
+
+ try {
+ console.log('Adding other player:', playerInfo);
+
+ // Destroy existing player if it exists
+ if (this.otherPlayers[playerInfo.playerId]) {
+ this.otherPlayers[playerInfo.playerId].destroy();
+ }
+
+ const otherPlayer = this.physics.add.sprite(
+ playerInfo.x || this.game.config.width / 2,
+ playerInfo.y || this.game.config.height / 2,
+ 'player'
+ ).setScale(1);
+
+ // Set up physics body
+ otherPlayer.setBodySize(24, 28);
+ otherPlayer.setOffset(10, 13);
+ otherPlayer.setBounce(0.2);
+ otherPlayer.setCollideWorldBounds(true);
+
+ // Enable physics but disable gravity
+ otherPlayer.body.setAllowGravity(false);
+
+ // Set initial animation
+ if (this.anims.exists('idleDown')) {
+ otherPlayer.play('idleDown');
+ }
+
+ // Store player info
+ otherPlayer.playerInfo = playerInfo;
+ otherPlayer.playerId = playerInfo.playerId;
+
+ // Add to tracking
+ this.otherPlayers[playerInfo.playerId] = otherPlayer;
+
+ if (this.otherPlayersGroup) {
+ this.otherPlayersGroup.add(otherPlayer);
+ }
+
+ console.log('Successfully added other player:', playerInfo.playerId);
+ return otherPlayer;
+ } catch (error) {
+ console.error('Error in addOtherPlayer:', error);
+ return null;
+ }
+ }
+
+ removePlayer(playerId) {
+ this.otherPlayersGroup.getChildren().forEach((player) => {
+ if (player.playerId === playerId) {
+ player.destroy();
+ }
+ });
+ }
+
+ createPlayerWithListeners(playerId, playerInfo) {
+ // Check if player already exists using the Set
+ if (this.existingPlayers.has(playerId)) {
+ console.log('Player already exists:', playerId);
+ return this.playerWorldPosition[playerId]?.sprite;
+ }
+
+ console.log('Actually creating new player:', playerId);
+ const spawnPoint = this.getSpawnPoint();
+ const playerSprite = this.physics.add.sprite(
+ spawnPoint.x,
+ spawnPoint.y,
+ 'player'
+ ).setScale(1);
+
+ playerSprite.setBodySize(24, 28);
+ playerSprite.setOffset(10, 13);
+ playerSprite.setBounce(0.2);
+ playerSprite.setCollideWorldBounds(true);
+ playerSprite.body.setAllowGravity(false);
+
+ playerSprite.play('idleDown');
+ playerSprite.life = 100; // Make sure to set initial life
+
+ this.playerWorldPosition[playerId] = {
+ x: spawnPoint.x,
+ y: spawnPoint.y,
+ sprite: playerSprite,
+ animation: 'idleDown',
+ flipX: false
+ };
+
+ // Only add to group if it exists
+ if (this.playersGroup) {
+ this.playersGroup.add(playerSprite);
+ } else {
+ console.warn('playersGroup not initialized');
+ }
+
+ this.existingPlayers.add(playerId);
+
+ return playerSprite;
+ }
+
+ handlePlayerCollision(player1, player2) {
+ // Handle player collision logic here
+ console.log('Players collided!');
+ }
+
+ sendPlayerUpdate() {
+ if (this.player && this.socket) {
+ const now = Date.now();
+ const playerInfo = {
+ x: this.player.x,
+ y: this.player.y,
+ animation: this.player.anims.currentAnim?.key || 'idleDown',
+ flipX: this.player.flipX,
+ scene: 'DungeonScene'
+ };
+
+ // Only send update if enough time has passed and position/state has changed
+ if (now - this.lastMovementUpdate >= this.movementUpdateInterval &&
+ this.hasPlayerStateChanged(playerInfo)) {
+
+ this.socket.emit('playerMovement', playerInfo);
+ this.lastMovementUpdate = now;
+ this.lastPosition = { ...playerInfo };
+ }
+ }
+ }
+
+ updatePlayerInWorld(playerId, playerInfo) {
+ const playerData = this.playerWorldPosition[playerId];
+ if (playerData?.sprite) {
+ // Update position
+ playerData.x = playerInfo.x;
+ playerData.y = playerInfo.y;
+ playerData.sprite.setPosition(playerInfo.x, playerInfo.y);
+
+ // Update animation
+ if (playerInfo.animation && playerData.animation !== playerInfo.animation) {
+ playerData.animation = playerInfo.animation;
+ playerData.sprite.play(playerInfo.animation, true);
+ }
+
+ // Update flip
+ if (playerInfo.flipX !== undefined && playerData.flipX !== playerInfo.flipX) {
+ playerData.flipX = playerInfo.flipX;
+ playerData.sprite.setFlipX(playerInfo.flipX);
+ }
+ }
+ }
+
+ clearExistingPlayers() {
+ console.log('Clearing existing players');
+ Object.entries(this.playerWorldPosition).forEach(([playerId, playerData]) => {
+ if (playerData.sprite) {
+ playerData.sprite.destroy();
+ }
+ });
+ this.playerWorldPosition = {};
+ if (this.playersGroup) {
+ this.playersGroup.clear(true, true);
+ }
+ this.existingPlayers.clear();
+ }
+
+ cleanupSocketListeners() {
+ if (this.socket) {
+ this.socket.removeAllListeners('currentPlayers');
+ this.socket.removeAllListeners('newPlayer');
+ this.socket.removeAllListeners('playerMoved');
+ this.socket.removeAllListeners('playerCount');
+ }
+ }
+
+ // Helper method to compare Sets
+ areSetsEqual(set1, set2) {
+ if (set1.size !== set2.size) return false;
+ for (const item of set1) {
+ if (!set2.has(item)) return false;
+ }
+ return true;
+ }
+
+ getPlayerDirection() {
+ const currentAnim = this.player.anims.currentAnim;
+ if (!currentAnim) return 'Down';
+
+ // Check for attack animations first
+ if (currentAnim.key.includes('attack')) {
+ return currentAnim.key.replace('attack', '');
+ }
+
+ // Then check movement/idle animations
+ if (currentAnim.key.includes('Left')) return 'Left';
+ if (currentAnim.key.includes('Right')) return 'Right';
+ if (currentAnim.key.includes('Up')) return 'Up';
+ return 'Down';
+ }
+
+ hasPlayerStateChanged(newState) {
+ const positionThreshold = 1; // Minimum movement to trigger update
+ return Math.abs(this.lastPosition.x - newState.x) > positionThreshold ||
+ Math.abs(this.lastPosition.y - newState.y) > positionThreshold ||
+ this.lastPosition.animation !== newState.animation ||
+ this.lastPosition.flipX !== newState.flipX;
+ }
}
diff --git a/client/dungeonmap/src/main.js b/client/dungeonmap/src/main.js
index c963c42..7c230cd 100644
--- a/client/dungeonmap/src/main.js
+++ b/client/dungeonmap/src/main.js
@@ -1,12 +1,12 @@
import Phaser from "phaser";
-import DungeonScene from "./DungeonScene.js";
-import CommonScene from "./CommonScene.js";
-import BridgeScene from "./BridgeScene.js";
+import DungeonScene from "./DungeonScene";
+import CommonScene from "./CommonScene";
+import BridgeScene from "./BridgeScene";
+import BackgroundScene from "./managers/backgroundscene";
import io from "socket.io-client";
-
const socket = io();
-export default new Phaser.Game({
+const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
@@ -17,7 +17,7 @@ export default new Phaser.Game({
debug: false,
},
},
- scene: [CommonScene, DungeonScene, BridgeScene],
+ scene: [BackgroundScene, CommonScene, DungeonScene, BridgeScene],
scale: {
zoom: 3,
},
@@ -26,4 +26,7 @@ export default new Phaser.Game({
window.socket = socket;
}
}
-});
+};
+
+// Create game instance with config
+new Phaser.Game(config);
\ No newline at end of file
diff --git a/client/dungeonmap/src/managers/AnimationManager.js b/client/dungeonmap/src/managers/AnimationManager.js
new file mode 100644
index 0000000..b25e687
--- /dev/null
+++ b/client/dungeonmap/src/managers/AnimationManager.js
@@ -0,0 +1,70 @@
+export class AnimationManager {
+ static createAnimations(scene) {
+ const animations = [
+ {
+ key: 'idleDown',
+ frames: { start: 0, end: 5 }
+ },
+ {
+ key: 'idleRight',
+ frames: { start: 6, end: 11 }
+ },
+ {
+ key: 'idleLeft',
+ frames: { start: 6, end: 11 }
+ },
+ {
+ key: 'idleUp',
+ frames: { start: 12, end: 17 }
+ },
+ {
+ key: 'walkDown',
+ frames: { start: 18, end: 23 }
+ },
+ {
+ key: 'walkRight',
+ frames: { start: 24, end: 29 }
+ },
+ {
+ key: 'walkLeft',
+ frames: { start: 24, end: 29 }
+ },
+ {
+ key: 'walkUp',
+ frames: { start: 30, end: 35 }
+ },
+ {
+ key: 'attackDown',
+ frames: { start: 36, end: 49 }
+ },
+ {
+ key: 'attackRight',
+ frames: { start: 42, end: 46 }
+ },
+ {
+ key: 'attackLeft',
+ frames: { start: 42, end: 46 }
+ },
+ {
+ key: 'attackUp',
+ frames: { start: 48, end: 52 }
+ },
+ {
+ key: 'die',
+ frames: { start: 54, end: 56 }
+ }
+ ];
+
+ animations.forEach(animation => {
+ // Check if animation already exists
+ if (!scene.anims.exists(animation.key)) {
+ scene.anims.create({
+ key: animation.key,
+ frames: scene.anims.generateFrameNumbers('player', animation.frames),
+ frameRate: 10,
+ repeat: animation.key.includes('idle') ? -1 : 0
+ });
+ }
+ });
+ }
+}
diff --git a/client/dungeonmap/src/managers/CombatManager.js b/client/dungeonmap/src/managers/CombatManager.js
new file mode 100644
index 0000000..28d03dc
--- /dev/null
+++ b/client/dungeonmap/src/managers/CombatManager.js
@@ -0,0 +1,32 @@
+// src/managers/CombatManager.js
+export class CombatManager {
+ static handlePlayerDeath(scene, playerId) {
+ console.log('Local player died')
+ if (playerId === scene.socket.id) {
+ // Local player death
+ scene.player.isAttacking = false
+ scene.player.isDead = true
+ scene.player.anims.play('die', true).once('animationcomplete', () => {
+ // Handle respawn or game over logic here
+ console.log('Local player died')
+ })
+ } else if (scene.otherPlayers[playerId]) {
+ // Other player death
+ const deadPlayer = scene.otherPlayers[playerId]
+ deadPlayer.isAttacking = false
+ deadPlayer.isDead = true
+ deadPlayer.anims.play('die', true).once('animationcomplete', () => {
+ deadPlayer.destroy()
+ delete scene.otherPlayers[playerId]
+ console.log('Other player died:', playerId)
+ })
+ }
+
+ // Emit kill for XP
+ if (killerId) {
+ scene.socket.emit('playerKilled', {
+ killerId: killerId
+ });
+ }
+ }
+}
diff --git a/client/dungeonmap/src/managers/PlayerManager.js b/client/dungeonmap/src/managers/PlayerManager.js
new file mode 100644
index 0000000..b3b2ee6
--- /dev/null
+++ b/client/dungeonmap/src/managers/PlayerManager.js
@@ -0,0 +1,115 @@
+export class PlayerManager {
+ static handleAttack(scene, player, targetId) {
+ if (player.isAttacking) return;
+
+ player.isAttacking = true;
+
+ // Get the current direction from the player
+ const direction = player.lastDirection || 'Down';
+ const animation = `attack${direction}`;
+
+ // Play attack animation
+ player.play(animation, true);
+
+ // Emit attack animation to other players
+ scene.socket.emit('playerAttackAnimation', {
+ animation: animation,
+ direction: direction,
+ attacker: scene.socket.id
+ });
+
+ scene.time.delayedCall(500, () => {
+ player.isAttacking = false;
+ if (targetId) {
+ scene.socket.emit('attackPlayer', targetId);
+ }
+ });
+ }
+
+ static handleOtherPlayerAttack(otherPlayer, animation) {
+ if (otherPlayer.isAttacking) return;
+
+ otherPlayer.isAttacking = true;
+ otherPlayer.play(animation, true);
+
+ otherPlayer.scene.time.delayedCall(500, () => {
+ otherPlayer.isAttacking = false;
+ });
+ }
+
+ static handleMovement(scene, player) {
+ const speed = 80;
+ let animation = 'idleDown';
+ let velocityChanged = false;
+
+ if (!player.isAttacking) {
+ // Store previous position
+ const prevX = player.x;
+ const prevY = player.y;
+
+ // Reset velocity
+ player.body.setVelocity(0);
+
+ // Handle movement with collision checks
+ if (scene.cursors.left.isDown) {
+ player.body.setVelocityX(-speed);
+ animation = 'walkLeft';
+ player.lastDirection = 'Left';
+ player.flipX = true;
+ velocityChanged = true;
+ if (player.x === prevX) console.log('Collision detected: Left');
+ } else if (scene.cursors.right.isDown) {
+ player.body.setVelocityX(speed);
+ animation = 'walkRight';
+ player.lastDirection = 'Right';
+ player.flipX = false;
+ velocityChanged = true;
+ if (player.x === prevX) console.log('Collision detected: Right');
+ }
+
+ if (scene.cursors.up.isDown) {
+ player.body.setVelocityY(-speed);
+ animation = 'walkUp';
+ player.lastDirection = 'Up';
+ velocityChanged = true;
+ if (player.y === prevY) console.log('Collision detected: Up');
+ } else if (scene.cursors.down.isDown) {
+ player.body.setVelocityY(speed);
+ animation = 'walkDown';
+ player.lastDirection = 'Down';
+ velocityChanged = true;
+ if (player.y === prevY) console.log('Collision detected: Down');
+ }
+
+ // Normalize diagonal movement
+ if (player.body.velocity.x !== 0 && player.body.velocity.y !== 0) {
+ const normalize = Math.sqrt(2);
+ player.body.velocity.x /= normalize;
+ player.body.velocity.y /= normalize;
+ }
+
+ // If no movement or collision occurred, play idle animation
+ if (!velocityChanged || (prevX === player.x && prevY === player.y)) {
+ animation = `idle${player.lastDirection || 'Down'}`;
+ player.body.setVelocity(0);
+ }
+
+ // Play the animation
+ player.play(animation, true);
+
+ // Only emit if position actually changed
+ if (player.x !== prevX || player.y !== prevY) {
+ scene.socket.emit('playerState', {
+ x: player.x,
+ y: player.y,
+ animation: animation,
+ flipX: player.flipX,
+ lastDirection: player.lastDirection,
+ isAttacking: player.isAttacking
+ });
+ }
+ }
+
+ return animation;
+ }
+}
diff --git a/client/dungeonmap/src/managers/XPBar.js b/client/dungeonmap/src/managers/XPBar.js
new file mode 100644
index 0000000..cf2ba96
--- /dev/null
+++ b/client/dungeonmap/src/managers/XPBar.js
@@ -0,0 +1,78 @@
+export class XPBar {
+ constructor(scene) {
+ this.scene = scene;
+
+ // Get existing XP from scene data or use defaults
+ this.currentXP = scene.registry.get('currentXP') || 0;
+ this.currentLevel = scene.registry.get('currentLevel') || 1;
+ this.maxXP = 100;
+
+ // Fixed positions for top left corner
+ const x = 20;
+ const y = 20;
+
+ // Create black background with more opacity
+ this.background = scene.add.rectangle(x, y, 200, 25, 0x000000, 1);
+ this.background.setOrigin(0, 0);
+ this.background.setScrollFactor(0);
+ this.background.setDepth(999999);
+ this.background.setAlpha(0.7);
+
+ // Create green XP fill bar with brighter color
+ this.fillBar = scene.add.rectangle(x + 2, y + 2, 196, 21, 0x00FF00, 1);
+ this.fillBar.setOrigin(0, 0);
+ this.fillBar.setScrollFactor(0);
+ this.fillBar.setDepth(999999);
+
+ // Add level text with larger font
+ this.levelText = scene.add.text(x, y - 25, `Level ${this.currentLevel}`, {
+ font: '20px Arial',
+ fill: '#FFFFFF',
+ stroke: '#000000',
+ strokeThickness: 5
+ });
+ this.levelText.setOrigin(0, 0);
+ this.levelText.setScrollFactor(0);
+ this.levelText.setDepth(999999);
+
+ // XP text with larger font
+ this.xpText = scene.add.text(x + 210, y + 3, `${this.currentXP}/${this.maxXP}`, {
+ font: '18px Arial',
+ fill: '#FFFFFF',
+ stroke: '#000000',
+ strokeThickness: 5
+ });
+ this.xpText.setScrollFactor(0);
+ this.xpText.setDepth(999999);
+
+ // Make all elements fixed to camera
+ const elements = [this.background, this.fillBar, this.levelText, this.xpText];
+ elements.forEach(element => {
+ element.setScrollFactor(0);
+ element.setVisible(true);
+ });
+
+ // Initial update
+ this.update(this.currentXP, this.currentLevel);
+ }
+
+ update(xp, level) {
+ this.currentXP = xp;
+ this.currentLevel = level;
+
+ // Store values in scene registry
+ this.scene.registry.set('currentXP', this.currentXP);
+ this.scene.registry.set('currentLevel', this.currentLevel);
+
+ const percentage = (this.currentXP / this.maxXP);
+ this.fillBar.width = 196 * percentage;
+
+ this.levelText.setText(`Level ${this.currentLevel}`);
+ this.xpText.setText(`${this.currentXP}/${this.maxXP}`);
+
+ // Ensure visibility after update
+ [this.background, this.fillBar, this.levelText, this.xpText].forEach(element => {
+ element.setVisible(true);
+ });
+ }
+}
\ No newline at end of file
diff --git a/client/dungeonmap/src/managers/backgroundscene.js b/client/dungeonmap/src/managers/backgroundscene.js
new file mode 100644
index 0000000..0bc197e
--- /dev/null
+++ b/client/dungeonmap/src/managers/backgroundscene.js
@@ -0,0 +1,121 @@
+import { io } from 'socket.io-client';
+import CommonScene from '../CommonScene';
+import DungeonScene from '../DungeonScene';
+import BridgeScene from '../BridgeScene';
+
+export default class BackgroundScene extends Phaser.Scene {
+ constructor() {
+ super({ key: 'BackgroundScene', active: true });
+ this.socket = null;
+
+ }
+
+ create() {
+ if (!this.socket) {
+ const serverUrl = 'http://localhost:3000'; // Make sure this matches your server
+ console.log('Connecting to server at:', serverUrl);
+
+ this.socket = io(serverUrl, {
+ transports: ['websocket'],
+ upgrade: false,
+ reconnection: true,
+ reconnectionAttempts: 5
+ });
+
+ this.socket.on('connect', () => {
+ console.log('Connected to server with ID:', this.socket.id);
+ this.hasJoinedScene = false;
+ });
+
+ this.socket.on('connect_error', (error) => {
+ console.error('Socket connection error:', error);
+ });
+ }
+
+ // Listen for scene changes
+ this.events.on('changeScene', (sceneName) => {
+ if (!this.hasJoinedScene) {
+ console.log('Joining scene:', sceneName);
+ this.socket.emit('joinScene', {
+ scene: sceneName,
+ x: this.game.config.width / 2,
+ y: this.game.config.height / 2
+ });
+ this.hasJoinedScene = true;
+ }
+ });
+
+ // Start with CommonScene
+ if (!this.currentScene) {
+ this.startScene('CommonScene');
+ }
+ }
+
+ setupSocketListeners() {
+ this.socket.on('connect', () => {
+ console.log('Connected to server with ID:', this.socket.id);
+ this.hasJoinedScene = false;
+ });
+
+ this.socket.on('currentPlayers', (players) => {
+ console.log('Received current players:', players);
+ if (this.currentScene) {
+ this.currentScene.events.emit('currentPlayers', players);
+ }
+ });
+
+ this.socket.on('newPlayer', (playerInfo) => {
+ console.log('New player joined:', playerInfo);
+ if (this.currentScene) {
+ this.currentScene.events.emit('newPlayer', playerInfo);
+ }
+ });
+
+ this.socket.on('playerMoved', (playerInfo) => {
+ if (this.currentScene) {
+ this.currentScene.events.emit('playerMoved', playerInfo);
+ }
+ });
+
+ this.socket.on('playerDisconnected', (playerId) => {
+ console.log('Player disconnected:', playerId);
+ if (this.currentScene) {
+ this.currentScene.events.emit('playerDisconnected', playerId);
+ }
+ });
+
+ this.socket.on('gameState', (state) => {
+ if (this.currentScene) {
+ this.currentScene.events.emit('gameState', state);
+ }
+ });
+ }
+
+ startScene(sceneName, data = {}) {
+ console.log(`Starting scene: ${sceneName} with socket ID:`, this.socket.id);
+
+ // Stop current scene if it exists
+ if (this.currentScene) {
+ console.log(`Leaving scene: ${this.currentScene.scene.key}`);
+ this.socket.emit('leaveScene', {
+ scene: this.currentScene.scene.key,
+ playerId: this.socket.id
+ });
+ this.scene.stop(this.currentScene.scene.key);
+ }
+
+ // Start new scene with socket reference
+ this.scene.start(sceneName, {
+ socket: this.socket,
+ backgroundScene: this,
+ ...data
+ });
+
+ // Update current scene reference
+ this.currentScene = this.scene.get(sceneName);
+ }
+
+ getSocket() {
+ return this.socket;
+ }
+}
diff --git a/client/server.js b/client/server.js
new file mode 100644
index 0000000..e975f01
--- /dev/null
+++ b/client/server.js
@@ -0,0 +1,140 @@
+const express = require('express')
+const http = require('http')
+const { Server } = require('socket.io')
+const app = express()
+const httpServer = http.createServer(app)
+const io = new Server(httpServer, {
+ cors: {
+ origin: ['http://localhost:8080', 'http://127.0.0.1:8080'],
+ credentials: false,
+ },
+})
+
+const players = {}
+
+io.on('connection', (socket) => {
+ console.log('A user connected:', socket.id)
+
+ players[socket.id] = {
+ playerId: socket.id,
+ x: 400,
+ y: 300,
+ life: 100,
+ attack: 10,
+ weapon: 'sword',
+ animation: 'idleDown',
+ lastDirection: 'Down',
+ flipX: false,
+ isAttacking: false,
+ currentScene: 'CommonScene',
+ xp: 0,
+ level: 1
+ }
+
+ socket.emit('currentPlayers', players)
+ socket.broadcast.emit('newPlayer', players[socket.id])
+
+ socket.on('playerInput', (data) => {
+ if (players[socket.id]) {
+ players[socket.id].x = data.x
+ players[socket.id].y = data.y
+ players[socket.id].animation = data.animation
+ players[socket.id].flipX = data.flipX
+ players[socket.id].lastDirection = data.lastDirection
+
+ io.emit('playerMoved', {
+ ...players[socket.id],
+ playerId: socket.id,
+ })
+ }
+ })
+
+ socket.on('leaveScene', (data) => {
+ console.log('Player leaving scene:', socket.id); // Debug log
+
+ if (players[socket.id]) {
+ // Tell other players in the current scene that this player left
+ socket.to(data.from).emit('playerDisconnected', socket.id);
+
+ // Update player's scene
+ players[socket.id].currentScene = data.to;
+
+ // Leave the old scene room and join the new one
+ socket.leave(data.from);
+ socket.join(data.to);
+ }
+ })
+
+ socket.on('attackPlayer', (data) => {
+ const targetId = data.targetId
+ if (players[socket.id] && players[targetId]) {
+ players[targetId].life -= players[socket.id].attack
+
+ io.emit('playerAttackAnimation', {
+ attacker: socket.id,
+ target: targetId,
+ animation: data.animation,
+ direction: data.direction,
+ })
+
+ if (players[targetId].life <= 0) {
+ io.emit('playerDefeated', targetId)
+ setTimeout(() => {
+ delete players[targetId]
+ }, 1000)
+ } else {
+ io.emit('playerAttacked', {
+ attacker: socket.id,
+ target: targetId,
+ life: players[targetId].life,
+ })
+ }
+ }
+ })
+
+ socket.on('playerKilled', (killerInfo) => {
+ const killer = players[killerInfo.killerId];
+ if (killer) {
+ killer.xp += 5; // 5 XP per kill
+
+ // Calculate new level (every 100 XP = 1 level)
+ const newLevel = Math.floor(killer.xp / 100) + 1;
+
+ // Check if player leveled up
+ if (newLevel > killer.level) {
+ killer.level = newLevel;
+ // Emit level up event
+ io.to(killerInfo.killerId).emit('levelUp', {
+ level: killer.level,
+ xp: killer.xp
+ });
+ }
+
+ // Update XP for all clients
+ io.emit('xpUpdate', {
+ playerId: killerInfo.killerId,
+ xp: killer.xp,
+ level: killer.level
+ });
+ }
+ });
+
+ // Send initial XP data when player joins
+ socket.on('requestXPData', () => {
+ socket.emit('xpUpdate', {
+ playerId: socket.id,
+ xp: players[socket.id].xp,
+ level: players[socket.id].level
+ });
+ });
+
+ socket.on('disconnect', () => {
+ console.log('A user disconnected:', socket.id)
+ delete players[socket.id]
+ io.emit('playerDisconnected', socket.id)
+ })
+})
+
+httpServer.listen(3000, () => {
+ console.log('Listening on port 3000')
+})
diff --git a/server/package-lock.json b/server/package-lock.json
index 0dbebb6..15cead6 100644
--- a/server/package-lock.json
+++ b/server/package-lock.json
@@ -1,12 +1,17 @@
{
"name": "server",
+ "version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
+ "name": "server",
+ "version": "1.0.0",
+ "license": "ISC",
"dependencies": {
- "express": "^4.21.2",
- "socket.io": "^4.8.1"
+ "cors": "^2.8.5",
+ "express": "^4.17.1",
+ "socket.io": "^4.5.1"
}
},
"node_modules/@socket.io/component-emitter": {
@@ -946,6 +951,7 @@
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz",
"integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==",
+ "license": "MIT",
"dependencies": {
"accepts": "~1.3.4",
"base64id": "~2.0.0",
diff --git a/server/package.json b/server/package.json
index f9ca066..d405844 100644
--- a/server/package.json
+++ b/server/package.json
@@ -1,6 +1,18 @@
{
"dependencies": {
+ "cors": "^2.8.5",
"express": "^4.21.2",
"socket.io": "^4.8.1"
- }
+ },
+ "name": "server",
+ "version": "1.0.0",
+ "main": "server.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "start": "node server.js"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "description": ""
}
diff --git a/server/server.js b/server/server.js
index b9465cd..85e6f11 100644
--- a/server/server.js
+++ b/server/server.js
@@ -1,77 +1,358 @@
const express = require('express')
-const http = require('http')
-const { Server } = require('socket.io')
const app = express()
-const httpServer = http.createServer(app)
-const io = new Server(httpServer, {
- cors: {
- origin: 'https://latch-v1.vercel.app/',
- methods: ["GET", "POST"],
- credentials: false,
- },
+const server = require('http').Server(app)
+const io = require('socket.io')(server, {
+ cors: {
+ origin: "*",
+ methods: ["GET", "POST"]
+ }
})
-const players = {}
+// Enable CORS for all routes
+app.use((req, res, next) => {
+ res.header('Access-Control-Allow-Origin', '*');
+ res.header('Access-Control-Allow-Methods', 'GET, POST');
+ res.header('Access-Control-Allow-Headers', 'Content-Type');
+ next();
+})
-io.on('connection', (socket) => {
- console.log('A user connected:', socket.id)
-
- // Add new player to the players object
- players[socket.id] = {
- playerId: socket.id,
- x: 400,
- y: 300,
- life: 100,
- attack: 10,
- weapon: 'sword',
- animation: 'idleDown'
- }
-
- // Send the current players to the new player
- socket.emit('currentPlayers', players)
-
- // Notify existing players of the new player
- socket.broadcast.emit('newPlayer', players[socket.id])
-
- // Handle player movement
- socket.on('movePlayer', (movementData) => {
- if (players[socket.id]) {
- players[socket.id].x = movementData.x
- players[socket.id].y = movementData.y
- players[socket.id].animation = movementData.animation
- players[socket.id].flipX = movementData.flipX
- io.emit('playerMoved', players[socket.id])
+// Scene configurations
+const sceneConfig = {
+ CommonScene: {
+ spawnPoints: [
+ { x: 400, y: 300 } // Center spawn point for CommonScene
+ ]
+ },
+ BridgeScene: {
+ spawnPoints: [
+ { x: 100, y: 100 },
+ { x: 700, y: 100 }
+ ]
+ },
+ DungeonScene: {
+ spawnPoints: [
+ { x: 100, y: 100 },
+ { x: 700, y: 100 }
+ ]
}
- })
-
- // Handle player attack
- socket.on('attackPlayer', (targetId) => {
- if (players[socket.id] && players[targetId]) {
- players[targetId].life -= players[socket.id].attack
- // log the both players life
- console.log('Attacker:', players[socket.id].life)
- console.log('Target:', players[targetId].life)
- if (players[targetId].life <= 0) {
- io.emit('playerDefeated', targetId)
- delete players[targetId]
- } else {
- io.emit('playerAttacked', {
- attacker: socket.id,
- target: targetId,
- life: players[targetId].life,
- })
- }
+};
+
+// Game state to track players and scenes
+const gameState = {
+ CommonScene: {
+ players: {}
+ },
+ BridgeScene: {
+ players: {}
+ },
+ DungeonScene: {
+ players: {}
}
- })
-
- // Handle player disconnection
- socket.on('disconnect', () => {
- console.log('A user disconnected:', socket.id)
- delete players[socket.id]
- io.emit('playerDisconnected', socket.id)
- })
-})
+};
-httpServer.listen(3000, () => {
- console.log('Listening on port 3000')
-})
+io.on('connection', (socket) => {
+ console.log('Player connected:', socket.id);
+
+ socket.on('joinScene', (data) => {
+ console.log(`Player ${socket.id} joining ${data.scene}`);
+ const scene = data.scene;
+
+ // Leave previous scene if any
+ if (socket.scene) {
+ socket.leave(socket.scene);
+ if (gameState[socket.scene]?.players[socket.id]) {
+ delete gameState[socket.scene].players[socket.id];
+ }
+ }
+
+ // Join new scene
+ socket.scene = scene;
+ socket.join(scene);
+
+ // Initialize scene state if needed
+ if (!gameState[scene]) {
+ gameState[scene] = { players: {} };
+ }
+
+ // Add player to scene
+ gameState[scene].players[socket.id] = {
+ playerId: socket.id,
+ x: Math.random() * 800,
+ y: Math.random() * 600,
+ animation: 'idleDown',
+ flipX: false,
+ lastDirection: 'Down',
+ isAttacking: false
+ };
+
+ // Send current scene state to new player
+ socket.emit('currentPlayers', gameState[scene].players);
+
+ // Notify others in scene
+ socket.to(scene).emit('newPlayer', {
+ playerId: socket.id,
+ ...gameState[scene].players[socket.id]
+ });
+ });
+
+ socket.on('playerMovement', (movementData) => {
+ if (socket.scene && gameState[socket.scene].players[socket.id]) {
+ // Update player position
+ gameState[socket.scene].players[socket.id] = {
+ ...gameState[socket.scene].players[socket.id],
+ ...movementData
+ };
+
+ // Send player movement to others in same scene
+ socket.to(socket.scene).emit('playerMoved', {
+ playerId: socket.id,
+ ...movementData
+ });
+ }
+ });
+
+ socket.on('disconnect', () => {
+ console.log('Player disconnected:', socket.id);
+ if (socket.scene && gameState[socket.scene]?.players[socket.id]) {
+ delete gameState[socket.scene].players[socket.id];
+ io.to(socket.scene).emit('playerDisconnected', socket.id);
+ }
+ });
+
+ socket.on('keyState', (keys) => {
+ if (!socket.scene || !gameState[socket.scene].players[socket.id]) return;
+
+ const player = gameState[socket.scene].players[socket.id];
+ const speed = 80;
+ let animation = 'idleDown';
+ let moved = false;
+
+ // Process movement based on either WASD or arrow keys
+ const left = keys.arrows.left || keys.wasd.left;
+ const right = keys.arrows.right || keys.wasd.right;
+ const up = keys.arrows.up || keys.wasd.up;
+ const down = keys.arrows.down || keys.wasd.down;
+
+ // Calculate movement
+ if (left) {
+ player.x -= speed * (16/1000);
+ animation = 'walkRight';
+ player.flipX = true;
+ player.lastDirection = 'Right';
+ moved = true;
+ } else if (right) {
+ player.x += speed * (16/1000);
+ animation = 'walkRight';
+ player.flipX = false;
+ player.lastDirection = 'Right';
+ moved = true;
+ }
+
+ if (up) {
+ player.y -= speed * (16/1000);
+ animation = 'walkUp';
+ player.lastDirection = 'Up';
+ moved = true;
+ } else if (down) {
+ player.y += speed * (16/1000);
+ animation = 'walkDown';
+ player.lastDirection = 'Down';
+ moved = true;
+ }
+
+ // Set idle animation if not moving
+ if (!moved) {
+ animation = `idle${player.lastDirection || 'Down'}`;
+ }
+
+ // Update player state
+ player.animation = animation;
+
+ // Broadcast updated game state
+ io.to(socket.scene).emit('gameState', {
+ players: gameState[socket.scene].players
+ });
+ });
+
+ socket.on('chatMessage', (data) => {
+ // Broadcast the message to all clients in the same scene
+ io.to(socket.scene).emit('chatMessage', {
+ playerId: socket.id,
+ message: data.message
+ });
+ });
+
+ socket.on('playerInput', (data) => {
+ // If it's a state update (has x, y, animation)
+ if (data.x !== undefined && data.y !== undefined && data.animation) {
+ const player = gameState[socket.scene]?.players[socket.id];
+ if (player) {
+ player.x = data.x;
+ player.y = data.y;
+ player.animation = data.animation;
+ player.flipX = data.flipX;
+
+ // Broadcast update without logging
+ io.to(socket.scene).emit('gameState', {
+ players: gameState[socket.scene].players
+ });
+ }
+ return;
+ }
+
+ // If it's an input update (has input controls)
+ if (data.input) {
+ const player = gameState[socket.scene]?.players[socket.id];
+ if (!player) return;
+
+ const speed = 80;
+ let animation = 'idleDown';
+
+ // Don't process movement if player is attacking
+ if (player.isAttacking) {
+ animation = `attack${player.lastDirection || 'Down'}`;
+ io.emit('gameState', { players: gameState[socket.scene].players });
+ return;
+ }
+
+ // Reset velocity
+ let velocityX = 0;
+ let velocityY = 0;
+
+ // Handle input with safe checks
+ const input = data.input;
+ if (input.left === true) {
+ velocityX = -speed;
+ animation = 'walkLeft';
+ player.lastDirection = 'Left';
+ player.flipX = true;
+ } else if (input.right === true) {
+ velocityX = speed;
+ animation = 'walkRight';
+ player.lastDirection = 'Right';
+ player.flipX = false;
+ }
+
+ if (input.up === true) {
+ velocityY = -speed;
+ animation = 'walkUp';
+ player.lastDirection = 'Up';
+ } else if (input.down === true) {
+ velocityY = speed;
+ animation = 'walkDown';
+ player.lastDirection = 'Down';
+ }
+
+ // Handle attack input
+ if (input.attack === true) {
+ player.isAttacking = true;
+ animation = `attack${player.lastDirection || 'Down'}`;
+
+ // Reset attack state after animation
+ setTimeout(() => {
+ player.isAttacking = false;
+ }, 500);
+ }
+
+ // Normalize diagonal movement
+ if (velocityX !== 0 && velocityY !== 0) {
+ const normalize = Math.sqrt(2);
+ velocityX /= normalize;
+ velocityY /= normalize;
+ }
+
+ // Update position if not attacking
+ if (!player.isAttacking) {
+ player.x += velocityX * (1/60);
+ player.y += velocityY * (1/60);
+ }
+
+ // If no movement and not attacking, use idle animation
+ if (velocityX === 0 && velocityY === 0 && !player.isAttacking) {
+ animation = `idle${player.lastDirection || 'Down'}`;
+ }
+
+ // Update player state
+ player.animation = animation;
+
+ // Broadcast new state to all players
+ io.to(socket.scene).emit('gameState', {
+ players: gameState[socket.scene].players
+ });
+ }
+ });
+
+ socket.on('dungeonReady', (data) => {
+ // When a client reports enough players, broadcast countdown to all
+ io.in('DungeonScene').emit('dungeonCountdown');
+ });
+
+ socket.on('bridgeReady', (data) => {
+ // When a client reports enough players, broadcast countdown to all
+ io.in('BridgeScene').emit('bridgeCountdown');
+ });
+
+ socket.on('playerAttack', (attackInfo) => {
+ const attacker = gameState[attackInfo.scene]?.players[socket.id];
+ if (!attacker) return;
+
+ // Emit attack animation to all players including attacker
+ io.in(attackInfo.scene).emit('playerAttacked', {
+ playerId: socket.id,
+ direction: attackInfo.direction
+ });
+
+ // Check for players in attack range
+ const attackRange = 50; // pixels
+ const attackDamage = 10; // 10 damage per hit
+
+ Object.entries(gameState[attackInfo.scene].players).forEach(([targetId, targetPlayer]) => {
+ if (targetId !== socket.id && targetPlayer.scene === attackInfo.scene) {
+ // Check if target is in range
+ const dx = targetPlayer.x - attackInfo.x;
+ const dy = targetPlayer.y - attackInfo.y;
+ const distance = Math.sqrt(dx * dx + dy * dy);
+
+ if (distance <= attackRange) {
+ // Initialize health if not set
+ if (typeof targetPlayer.health === 'undefined') {
+ targetPlayer.health = 100;
+ }
+
+ // Apply damage
+ targetPlayer.health = Math.max(0, targetPlayer.health - attackDamage);
+
+ // Emit damage to all players
+ io.in(attackInfo.scene).emit('playerDamaged', {
+ playerId: targetId,
+ newHealth: targetPlayer.health,
+ attackerId: socket.id
+ });
+
+ // Check for player death
+ if (targetPlayer.health <= 0) {
+ io.in(attackInfo.scene).emit('playerDied', {
+ playerId: targetId
+ });
+ }
+ }
+ }
+ });
+ });
+});
+
+// Game loop for continuous state updates
+setInterval(() => {
+ Object.keys(gameState).forEach(scene => {
+ if (Object.keys(gameState[scene].players).length > 0) {
+ io.to(scene).emit('gameState', {
+ players: gameState[scene].players
+ });
+ }
+ });
+}, 1000 / 60); // 60 times per second
+
+const PORT = 3000;
+server.listen(PORT, () => {
+ console.log(`Server running on http://localhost:${PORT}`);
+});