.
This commit is contained in:
commit
1f0daf82cb
15 changed files with 730 additions and 0 deletions
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
node_modules
|
||||||
|
# Keep environment variables out of version control
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Ignore build output
|
||||||
|
package-lock.json
|
||||||
|
|
||||||
|
# Ignore logs
|
||||||
|
logs
|
||||||
4
.prettierrc
Normal file
4
.prettierrc
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"tabWidth": 4,
|
||||||
|
"singleQuote": true
|
||||||
|
}
|
||||||
30
package.json
Normal file
30
package.json
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"name": "graalclone",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"dev": "tsx watch src/server.ts",
|
||||||
|
"build": "tsc",
|
||||||
|
"start": "node dist/server.js"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"@colyseus/schema": "^3.0.42",
|
||||||
|
"colyseus": "^0.16.4",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"dotenv": "^17.0.0",
|
||||||
|
"express": "^5.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/cors": "^2.8.19",
|
||||||
|
"@types/express": "^5.0.3",
|
||||||
|
"@types/node": "^24.0.7",
|
||||||
|
"tsx": "^4.20.3",
|
||||||
|
"typescript": "^5.8.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
28
public/index.html
Normal file
28
public/index.html
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>FreneticalPVP</title>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/phaser@v3.90.0/dist/phaser.min.js"></script>
|
||||||
|
<script src="https://unpkg.com/colyseus.js@^0.16.0/dist/colyseus.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src="./js/main.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
344
public/js/main.js
Normal file
344
public/js/main.js
Normal file
|
|
@ -0,0 +1,344 @@
|
||||||
|
const client = new Colyseus.Client('ws://localhost:3000');
|
||||||
|
let room;
|
||||||
|
let player;
|
||||||
|
const otherPlayers = new Map();
|
||||||
|
let scene;
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
type: Phaser.AUTO,
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight,
|
||||||
|
backgroundColor: '#1a1a1a',
|
||||||
|
physics: {
|
||||||
|
default: 'arcade',
|
||||||
|
arcade: { debug: false },
|
||||||
|
},
|
||||||
|
scale: {
|
||||||
|
mode: Phaser.Scale.RESIZE,
|
||||||
|
autoCenter: Phaser.Scale.CENTER_BOTH,
|
||||||
|
},
|
||||||
|
scene: {
|
||||||
|
preload,
|
||||||
|
create,
|
||||||
|
update,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const game = new Phaser.Game(config);
|
||||||
|
|
||||||
|
const MessageType = {
|
||||||
|
SERVER_CONNECT: 1,
|
||||||
|
SERVER_DISCONNECT: 2,
|
||||||
|
SERVER_MOVE: 3,
|
||||||
|
SERVER_CHAT: 4,
|
||||||
|
|
||||||
|
CLIENT_WELCOME: 1001,
|
||||||
|
CLIENT_PLAYER_JOINED: 1002,
|
||||||
|
CLIENT_PLAYER_LEFT: 1003,
|
||||||
|
CLIENT_UPDATE_PLAYER: 1004,
|
||||||
|
CLIENT_CHAT_MESSAGE: 1005,
|
||||||
|
CLIENT_ERROR: 1404,
|
||||||
|
CLIENT_PLAYERS_LIST: 1006, // Novo tipo para lista de jogadores
|
||||||
|
};
|
||||||
|
|
||||||
|
// Variáveis para movimento fluido
|
||||||
|
const keys = {};
|
||||||
|
let lastMovementTime = 0;
|
||||||
|
const MOVEMENT_INTERVAL = 16; // ~60fps
|
||||||
|
const SPEED = 500; // pixels por segundo
|
||||||
|
|
||||||
|
function preload() {
|
||||||
|
console.log('🎮 Preload phase started');
|
||||||
|
}
|
||||||
|
|
||||||
|
function create() {
|
||||||
|
scene = this;
|
||||||
|
console.log('🎯 Create phase started');
|
||||||
|
|
||||||
|
// Criar o player visual
|
||||||
|
player = createPlayerObject(this, 400, 300, 0x00ff00, 'Você');
|
||||||
|
|
||||||
|
// Configurar input contínuo
|
||||||
|
setupContinuousInput(this);
|
||||||
|
|
||||||
|
// Conectar ao servidor
|
||||||
|
console.log('🔗 Tentando conectar ao servidor...');
|
||||||
|
|
||||||
|
client
|
||||||
|
.joinOrCreate('world', { nickname: prompt('Nickname:') })
|
||||||
|
.then((r) => {
|
||||||
|
room = r;
|
||||||
|
console.log('✅ Conectado com sucesso:', room.sessionId);
|
||||||
|
|
||||||
|
// Configurar handlers de mensagem
|
||||||
|
setupMessageHandlers(scene);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('❌ Erro ao conectar:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPlayerObject(scene, x, y, color, nickname) {
|
||||||
|
const playerContainer = scene.add.container(x, y);
|
||||||
|
|
||||||
|
// Corpo do jogador
|
||||||
|
const body = scene.add.rectangle(0, 0, 32, 32, color);
|
||||||
|
|
||||||
|
// Nome do jogador
|
||||||
|
const nameText = scene.add
|
||||||
|
.text(0, -25, nickname, {
|
||||||
|
fontSize: '12px',
|
||||||
|
fill: '#ffffff',
|
||||||
|
stroke: '#000000',
|
||||||
|
strokeThickness: 2,
|
||||||
|
})
|
||||||
|
.setOrigin(0.5);
|
||||||
|
|
||||||
|
// Texto de chat (inicialmente oculto)
|
||||||
|
const chatText = scene.add
|
||||||
|
.text(0, -50, '', {
|
||||||
|
fontSize: '10px',
|
||||||
|
fill: '#ffffff',
|
||||||
|
backgroundColor: '#000000aa',
|
||||||
|
padding: { x: 4, y: 2 },
|
||||||
|
})
|
||||||
|
.setOrigin(0.5)
|
||||||
|
.setVisible(false);
|
||||||
|
|
||||||
|
playerContainer.add([body, nameText, chatText]);
|
||||||
|
|
||||||
|
// Adicionar propriedades customizadas
|
||||||
|
playerContainer.body = body;
|
||||||
|
playerContainer.nameText = nameText;
|
||||||
|
playerContainer.chatText = chatText;
|
||||||
|
playerContainer.targetX = x;
|
||||||
|
playerContainer.targetY = y;
|
||||||
|
|
||||||
|
return playerContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupContinuousInput(scene) {
|
||||||
|
// Configurar teclas
|
||||||
|
const cursors = scene.input.keyboard.createCursorKeys();
|
||||||
|
const wasd = scene.input.keyboard.addKeys('W,S,A,D');
|
||||||
|
|
||||||
|
// Armazenar referências das teclas
|
||||||
|
keys.up = cursors.up;
|
||||||
|
keys.down = cursors.down;
|
||||||
|
keys.left = cursors.left;
|
||||||
|
keys.right = cursors.right;
|
||||||
|
keys.w = wasd.W;
|
||||||
|
keys.s = wasd.S;
|
||||||
|
keys.a = wasd.A;
|
||||||
|
keys.d = wasd.D;
|
||||||
|
|
||||||
|
// Configurar tecla Enter para chat
|
||||||
|
scene.input.keyboard.on('keydown-ENTER', () => {
|
||||||
|
if (!room) return;
|
||||||
|
|
||||||
|
const msg = prompt('Mensagem:');
|
||||||
|
if (msg && msg.trim()) {
|
||||||
|
console.log('💬 Enviando mensagem:', msg);
|
||||||
|
room.send(MessageType.SERVER_CHAT, { message: msg.trim() });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupMessageHandlers(scene) {
|
||||||
|
// Welcome message
|
||||||
|
room.onMessage(MessageType.CLIENT_WELCOME, (data) => {
|
||||||
|
console.log('👋 Welcome:', data.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Lista de jogadores online (novo)
|
||||||
|
room.onMessage(MessageType.CLIENT_PLAYERS_LIST, (players) => {
|
||||||
|
console.log('📋 Players list:', players);
|
||||||
|
players.forEach((playerData) => {
|
||||||
|
if (playerData.sessionId !== room.sessionId) {
|
||||||
|
createOtherPlayer(playerData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Player joined
|
||||||
|
room.onMessage(
|
||||||
|
MessageType.CLIENT_PLAYER_JOINED,
|
||||||
|
({ sessionId, options }) => {
|
||||||
|
console.log('🧍 Player joined:', sessionId, options);
|
||||||
|
if (sessionId === room.sessionId) return;
|
||||||
|
|
||||||
|
const newPlayer = createPlayerObject(
|
||||||
|
scene,
|
||||||
|
400,
|
||||||
|
300,
|
||||||
|
0xff0000,
|
||||||
|
options?.nickname || 'Player'
|
||||||
|
);
|
||||||
|
otherPlayers.set(sessionId, newPlayer);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Player left
|
||||||
|
room.onMessage(MessageType.CLIENT_PLAYER_LEFT, ({ sessionId }) => {
|
||||||
|
console.log('👋 Player left:', sessionId);
|
||||||
|
const playerObj = otherPlayers.get(sessionId);
|
||||||
|
if (playerObj) {
|
||||||
|
playerObj.destroy();
|
||||||
|
otherPlayers.delete(sessionId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Player update - com interpolação suave
|
||||||
|
room.onMessage(
|
||||||
|
MessageType.CLIENT_UPDATE_PLAYER,
|
||||||
|
({ sessionId, position }) => {
|
||||||
|
if (sessionId === room.sessionId) {
|
||||||
|
return; // Não atualizar nosso próprio player
|
||||||
|
}
|
||||||
|
|
||||||
|
const playerObj = otherPlayers.get(sessionId);
|
||||||
|
if (playerObj) {
|
||||||
|
// Definir posição alvo para interpolação suave
|
||||||
|
playerObj.targetX = position.x;
|
||||||
|
playerObj.targetY = position.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Chat message - exibir acima da cabeça
|
||||||
|
room.onMessage(
|
||||||
|
MessageType.CLIENT_CHAT_MESSAGE,
|
||||||
|
({ sessionId, message }) => {
|
||||||
|
console.log(`💬 [${sessionId}]:`, message);
|
||||||
|
|
||||||
|
let playerObj;
|
||||||
|
if (sessionId === room.sessionId) {
|
||||||
|
playerObj = player;
|
||||||
|
} else {
|
||||||
|
playerObj = otherPlayers.get(sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playerObj && playerObj.chatText) {
|
||||||
|
showChatMessage(playerObj, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Error message
|
||||||
|
room.onMessage(MessageType.CLIENT_ERROR, ({ message }) => {
|
||||||
|
console.error('❌ Erro do servidor:', message);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Connection events
|
||||||
|
room.onStateChange((state) => {
|
||||||
|
console.log('🔄 State changed:', state);
|
||||||
|
});
|
||||||
|
|
||||||
|
room.onError((code, message) => {
|
||||||
|
console.error('❌ Room error:', code, message);
|
||||||
|
});
|
||||||
|
|
||||||
|
room.onLeave((code) => {
|
||||||
|
console.log('👋 Left room with code:', code);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createOtherPlayer(playerData) {
|
||||||
|
const newPlayer = createPlayerObject(
|
||||||
|
scene,
|
||||||
|
playerData.position.x,
|
||||||
|
playerData.position.y,
|
||||||
|
0xff0000,
|
||||||
|
playerData.nickname || 'Player'
|
||||||
|
);
|
||||||
|
otherPlayers.set(playerData.sessionId, newPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showChatMessage(playerObj, message) {
|
||||||
|
if (!playerObj.chatText) return;
|
||||||
|
|
||||||
|
playerObj.chatText.setText(message);
|
||||||
|
playerObj.chatText.setVisible(true);
|
||||||
|
|
||||||
|
// Ocultar mensagem após 3 segundos
|
||||||
|
setTimeout(() => {
|
||||||
|
if (playerObj.chatText) {
|
||||||
|
playerObj.chatText.setVisible(false);
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function update(time, delta) {
|
||||||
|
// Movimento fluido baseado em input contínuo
|
||||||
|
if (room && player) {
|
||||||
|
handleContinuousMovement(delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interpolação suave para outros jogadores
|
||||||
|
interpolateOtherPlayers(delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleContinuousMovement(delta) {
|
||||||
|
const currentTime = Date.now();
|
||||||
|
|
||||||
|
// Verificar se é hora de enviar movimento
|
||||||
|
if (currentTime - lastMovementTime < MOVEMENT_INTERVAL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let isMoving = false;
|
||||||
|
const movement = { x: 0, y: 0 };
|
||||||
|
const speed = (SPEED * delta) / 1000; // pixels por frame
|
||||||
|
|
||||||
|
// Verificar input
|
||||||
|
if (keys.up.isDown || keys.w.isDown) {
|
||||||
|
movement.y = -speed;
|
||||||
|
isMoving = true;
|
||||||
|
}
|
||||||
|
if (keys.down.isDown || keys.s.isDown) {
|
||||||
|
movement.y = speed;
|
||||||
|
isMoving = true;
|
||||||
|
}
|
||||||
|
if (keys.left.isDown || keys.a.isDown) {
|
||||||
|
movement.x = -speed;
|
||||||
|
isMoving = true;
|
||||||
|
}
|
||||||
|
if (keys.right.isDown || keys.d.isDown) {
|
||||||
|
movement.x = speed;
|
||||||
|
isMoving = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isMoving) {
|
||||||
|
// Atualizar posição local imediatamente
|
||||||
|
player.x += movement.x;
|
||||||
|
player.y += movement.y;
|
||||||
|
|
||||||
|
// Enviar para o servidor
|
||||||
|
room.send(MessageType.SERVER_MOVE, { x: player.x, y: player.y });
|
||||||
|
|
||||||
|
lastMovementTime = currentTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function interpolateOtherPlayers(delta) {
|
||||||
|
const lerpSpeed = 0.1; // Velocidade de interpolação (0-1)
|
||||||
|
|
||||||
|
otherPlayers.forEach((playerObj) => {
|
||||||
|
if (
|
||||||
|
playerObj.targetX !== undefined &&
|
||||||
|
playerObj.targetY !== undefined
|
||||||
|
) {
|
||||||
|
// Interpolação linear suave
|
||||||
|
const deltaX = playerObj.targetX - playerObj.x;
|
||||||
|
const deltaY = playerObj.targetY - playerObj.y;
|
||||||
|
|
||||||
|
if (Math.abs(deltaX) > 1 || Math.abs(deltaY) > 1) {
|
||||||
|
playerObj.x += deltaX * lerpSpeed;
|
||||||
|
playerObj.y += deltaY * lerpSpeed;
|
||||||
|
} else {
|
||||||
|
playerObj.x = playerObj.targetX;
|
||||||
|
playerObj.y = playerObj.targetY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
25
src/callbacks/chat.ts
Normal file
25
src/callbacks/chat.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { Client } from 'colyseus';
|
||||||
|
import { MessageType } from '../protocol/MessageType';
|
||||||
|
import { GameRoom } from '../rooms/GameRoom';
|
||||||
|
|
||||||
|
export default function chatCallback(
|
||||||
|
client: Client,
|
||||||
|
options: { message: string },
|
||||||
|
room: GameRoom
|
||||||
|
) {
|
||||||
|
if (!room || !room.getRoomPlayersKeys().includes(client.sessionId)) {
|
||||||
|
client.send(MessageType.CLIENT_ERROR, {
|
||||||
|
message: 'You are not connected to this room.',
|
||||||
|
});
|
||||||
|
console.warn(
|
||||||
|
`Client ${client.sessionId} tried to chat but is not connected to the room.`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enviar mensagem para todos os jogadores, incluindo o remetente
|
||||||
|
room.broadcast(MessageType.CLIENT_CHAT_MESSAGE, {
|
||||||
|
sessionId: client.sessionId,
|
||||||
|
message: options.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
48
src/callbacks/connect.ts
Normal file
48
src/callbacks/connect.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { Client } from 'colyseus';
|
||||||
|
import { MessageType } from '../protocol/MessageType';
|
||||||
|
import { GameRoom } from '../rooms/GameRoom';
|
||||||
|
import { Player } from '../classes/player';
|
||||||
|
|
||||||
|
export default function connectCallback(
|
||||||
|
client: Client,
|
||||||
|
options: any,
|
||||||
|
room: GameRoom
|
||||||
|
) {
|
||||||
|
console.log(
|
||||||
|
'Client connected:',
|
||||||
|
client.sessionId,
|
||||||
|
'with options:',
|
||||||
|
options
|
||||||
|
);
|
||||||
|
|
||||||
|
const player: Player = {
|
||||||
|
nickname:
|
||||||
|
options.nickname || 'Player ' + Math.floor(Math.random() * 10000),
|
||||||
|
sessionId: client.sessionId,
|
||||||
|
position: { x: 0, y: 0 },
|
||||||
|
health: 100,
|
||||||
|
};
|
||||||
|
|
||||||
|
room.addPlayer(client.sessionId, player);
|
||||||
|
|
||||||
|
client.send(MessageType.CLIENT_WELCOME, {
|
||||||
|
message: 'Welcome to the game!',
|
||||||
|
});
|
||||||
|
|
||||||
|
client.send(
|
||||||
|
MessageType.CLIENT_PLAYER_LIST,
|
||||||
|
room
|
||||||
|
.getAllPlayers()
|
||||||
|
.filter((p) => p.sessionId !== client.sessionId)
|
||||||
|
.map((p) => ({
|
||||||
|
sessionId: p.sessionId,
|
||||||
|
nickname: p.nickname,
|
||||||
|
position: p.position,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
room.broadcast(MessageType.CLIENT_PLAYER_JOINED, {
|
||||||
|
sessionId: client.sessionId,
|
||||||
|
options: options,
|
||||||
|
});
|
||||||
|
}
|
||||||
32
src/callbacks/disconnect.ts
Normal file
32
src/callbacks/disconnect.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { Client } from 'colyseus';
|
||||||
|
import { MessageType } from '../protocol/MessageType';
|
||||||
|
import { GameRoom } from '../rooms/GameRoom';
|
||||||
|
|
||||||
|
export default function disconnectCallback(
|
||||||
|
client: Client,
|
||||||
|
consented: boolean,
|
||||||
|
room: GameRoom
|
||||||
|
) {
|
||||||
|
console.log(
|
||||||
|
'Client disconnected:',
|
||||||
|
client.sessionId,
|
||||||
|
'consented:',
|
||||||
|
consented
|
||||||
|
);
|
||||||
|
|
||||||
|
room.broadcast(MessageType.CLIENT_PLAYER_LEFT, {
|
||||||
|
sessionId: client.sessionId,
|
||||||
|
});
|
||||||
|
|
||||||
|
room.deletePlayer(client.sessionId);
|
||||||
|
|
||||||
|
if (consented) {
|
||||||
|
client.send(MessageType.CLIENT_WELCOME, {
|
||||||
|
message: 'You have successfully disconnected.',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
client.send(MessageType.CLIENT_ERROR, {
|
||||||
|
message: 'You were disconnected unexpectedly.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/callbacks/move.ts
Normal file
36
src/callbacks/move.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { Client } from 'colyseus';
|
||||||
|
import { MessageType } from '../protocol/MessageType';
|
||||||
|
import { GameRoom } from '../rooms/GameRoom';
|
||||||
|
|
||||||
|
export default function moveCallback(
|
||||||
|
client: Client,
|
||||||
|
options: { x: number; y: number },
|
||||||
|
room: GameRoom
|
||||||
|
) {
|
||||||
|
if (!room || !room.getRoomPlayersKeys().includes(client.sessionId)) {
|
||||||
|
client.send(MessageType.CLIENT_ERROR, {
|
||||||
|
message: 'You are not connected to this room.',
|
||||||
|
});
|
||||||
|
console.warn(
|
||||||
|
`Client ${client.sessionId} tried to move but is not connected to the room.`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
room.movePlayer(client.sessionId, {
|
||||||
|
x: options.x,
|
||||||
|
y: options.y,
|
||||||
|
});
|
||||||
|
|
||||||
|
room.broadcast(
|
||||||
|
MessageType.CLIENT_UPDATE_PLAYER,
|
||||||
|
{
|
||||||
|
sessionId: client.sessionId,
|
||||||
|
position: {
|
||||||
|
x: options.x,
|
||||||
|
y: options.y,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ except: client }
|
||||||
|
);
|
||||||
|
}
|
||||||
6
src/classes/player.ts
Normal file
6
src/classes/player.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
export class Player {
|
||||||
|
nickname: string = 'Player ' + Math.floor(Math.random() * 10000);
|
||||||
|
sessionId: string = '';
|
||||||
|
position: { x: number; y: number } = { x: 0, y: 0 };
|
||||||
|
health: number = 100;
|
||||||
|
}
|
||||||
16
src/protocol/MessageType.ts
Normal file
16
src/protocol/MessageType.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
export enum MessageType {
|
||||||
|
// Client to Server Messages
|
||||||
|
SERVER_CONNECT = 1,
|
||||||
|
SERVER_DISCONNECT = 2,
|
||||||
|
SERVER_MOVE = 3,
|
||||||
|
SERVER_CHAT = 4,
|
||||||
|
|
||||||
|
// Server to Client Messages
|
||||||
|
CLIENT_WELCOME = 1001,
|
||||||
|
CLIENT_PLAYER_JOINED = 1002,
|
||||||
|
CLIENT_PLAYER_LEFT = 1003,
|
||||||
|
CLIENT_UPDATE_PLAYER = 1004,
|
||||||
|
CLIENT_CHAT_MESSAGE = 1005,
|
||||||
|
CLIENT_PLAYER_LIST = 1006,
|
||||||
|
CLIENT_ERROR = 1404,
|
||||||
|
}
|
||||||
13
src/rooms/GameRoom.ts
Normal file
13
src/rooms/GameRoom.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { Room } from 'colyseus';
|
||||||
|
import { Player } from '../classes/player';
|
||||||
|
|
||||||
|
export abstract class GameRoom extends Room {
|
||||||
|
abstract getRoomPlayersKeys(): string[];
|
||||||
|
abstract movePlayer(
|
||||||
|
sessionId: string,
|
||||||
|
{ x, y }: { x: number; y: number }
|
||||||
|
): void;
|
||||||
|
abstract deletePlayer(sessionId: string): void;
|
||||||
|
abstract addPlayer(sessionId: string, playerData: Player): void;
|
||||||
|
abstract getAllPlayers(): Player[];
|
||||||
|
}
|
||||||
71
src/rooms/WorldRoom.ts
Normal file
71
src/rooms/WorldRoom.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { Client } from 'colyseus';
|
||||||
|
import { MessageType } from '../protocol/MessageType';
|
||||||
|
import connectCallback from '../callbacks/connect';
|
||||||
|
import disconnectCallback from '../callbacks/disconnect';
|
||||||
|
import moveCallback from '../callbacks/move';
|
||||||
|
import { GameRoom } from './GameRoom';
|
||||||
|
import { Player } from '../classes/player';
|
||||||
|
import chatCallback from '../callbacks/chat';
|
||||||
|
|
||||||
|
export class WorldRoom extends GameRoom {
|
||||||
|
maxClients = 32;
|
||||||
|
|
||||||
|
players: Map<string, Player> = new Map();
|
||||||
|
|
||||||
|
onCreate(options: any) {
|
||||||
|
console.log('WorldRoom created with options:', options);
|
||||||
|
|
||||||
|
this.setMessageHandlers();
|
||||||
|
}
|
||||||
|
|
||||||
|
onJoin(client: Client, options: any) {
|
||||||
|
connectCallback(client, options, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
onLeave(client: Client, consented: boolean) {
|
||||||
|
disconnectCallback(client, consented, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
onDispose() {
|
||||||
|
console.log('WorldRoom disposed');
|
||||||
|
this.players.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setMessageHandlers() {
|
||||||
|
this.onMessage(
|
||||||
|
MessageType.SERVER_MOVE,
|
||||||
|
(client: Client, options: { x: number; y: number }) => {
|
||||||
|
moveCallback(client, options, this);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.onMessage(
|
||||||
|
MessageType.SERVER_CHAT,
|
||||||
|
(client: Client, options: { message: string }) => {
|
||||||
|
chatCallback(client, options, this);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getRoomPlayersKeys() {
|
||||||
|
return Array.from(this.players.keys());
|
||||||
|
}
|
||||||
|
|
||||||
|
public movePlayer(
|
||||||
|
sessionId: string,
|
||||||
|
{ x, y }: { x: number; y: number }
|
||||||
|
): void {
|
||||||
|
this.players.get(sessionId)!.position = { x, y };
|
||||||
|
}
|
||||||
|
|
||||||
|
public deletePlayer(sessionId: string) {
|
||||||
|
this.players.delete(sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public addPlayer(sessionId: string, playerData: Player) {
|
||||||
|
this.players.set(sessionId, playerData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getAllPlayers(): Player[] {
|
||||||
|
return Array.from(this.players.values());
|
||||||
|
}
|
||||||
|
}
|
||||||
50
src/server.ts
Normal file
50
src/server.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
import express from 'express';
|
||||||
|
import { createServer } from 'http';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { dirname } from 'path';
|
||||||
|
import 'dotenv/config';
|
||||||
|
import { Server } from 'colyseus';
|
||||||
|
import { WorldRoom } from './rooms/WorldRoom';
|
||||||
|
import cors from 'cors';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const server = createServer(app);
|
||||||
|
|
||||||
|
app.use(
|
||||||
|
cors({
|
||||||
|
origin: 'https://game.ruanfergui.com.br',
|
||||||
|
credentials: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
app.use(express.static(path.join(__dirname, '../public')));
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
const gameServer = new Server({
|
||||||
|
server,
|
||||||
|
});
|
||||||
|
|
||||||
|
gameServer.define('world', WorldRoom);
|
||||||
|
|
||||||
|
app.get('/api/status', (req, res) => {
|
||||||
|
res.json({
|
||||||
|
status: 'online',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/', (req, res) => {
|
||||||
|
res.sendFile(path.join(__dirname, '../public', 'index.html'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const PORT = process.env.PORT || 3000;
|
||||||
|
|
||||||
|
server.listen(PORT, () => {
|
||||||
|
console.log(`🚀 Server running on http://localhost:${PORT}`);
|
||||||
|
console.log(`🎮 Colyseus listening for game rooms...`);
|
||||||
|
});
|
||||||
|
|
||||||
|
export { app, server };
|
||||||
18
tsconfig.json
Normal file
18
tsconfig.json
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"allowImportingTsExtensions": false,
|
||||||
|
"noEmit": false,
|
||||||
|
"outDir": "./dist",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"resolveJsonModule": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue