init
This commit is contained in:
265
server/index.js
Normal file
265
server/index.js
Normal file
@@ -0,0 +1,265 @@
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const PORT = 8910;
|
||||
const wss = new WebSocket.Server({ port: PORT });
|
||||
|
||||
// Game State
|
||||
let nextPlayerId = 1;
|
||||
const players = new Map(); // id -> { id, name, x, y, skin_id, ws }
|
||||
|
||||
// Mock Data
|
||||
const NOTICES = [
|
||||
{
|
||||
"text": "欢迎来到 [color=#3399ff]Datawhale Town (Online)[/color]!\n\n这里是开源学习者的家园。在这里,我们一同探索知识,分享成长。\n\n[center]🐋[/center]",
|
||||
"image_path": "res://Assets/Assets/background2.png"
|
||||
},
|
||||
{
|
||||
"text": "最新活动 (Server):\n\n- 镇长刚刚搬进来了,就在喷泉左边。\n- 欢迎板已经设立,查看最新动态。\n- 玩家名字现在显示在头顶了!",
|
||||
"image_path": "res://Assets/MayorWhale.png"
|
||||
},
|
||||
{
|
||||
"text": "操作提示:\n\n- 按 [color=#ffaa00]F[/color] 键可以与物体互动。\n- 在下方输入框输入文字并在气泡中显示。\n- 点击右下角按钮发送聊天。",
|
||||
"image_path": "res://Assets/NoticeBoard.png"
|
||||
}
|
||||
];
|
||||
|
||||
console.log(`PokePlaza Server listening on ws://localhost:${PORT}`);
|
||||
|
||||
wss.on('connection', (ws) => {
|
||||
let playerId = null;
|
||||
|
||||
ws.on('message', (rawData) => {
|
||||
let msg;
|
||||
try {
|
||||
msg = JSON.parse(rawData.toString());
|
||||
} catch (e) {
|
||||
console.error('Invalid JSON:', rawData.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
switch (msg.type) {
|
||||
case 'login':
|
||||
handleLogin(ws, msg);
|
||||
break;
|
||||
case 'move':
|
||||
handleMove(msg);
|
||||
break;
|
||||
case 'chat':
|
||||
handleChat(msg);
|
||||
break;
|
||||
case 'private_chat':
|
||||
handlePrivateChat(msg);
|
||||
break;
|
||||
case 'change_scene':
|
||||
handleChangeScene(msg);
|
||||
break;
|
||||
case 'get_notices':
|
||||
handleGetNotices(ws);
|
||||
break;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function handlePrivateChat(msg) {
|
||||
if (playerId === null) return;
|
||||
|
||||
const targetId = msg.target_id;
|
||||
const targetPlayer = players.get(targetId);
|
||||
|
||||
if (targetPlayer) {
|
||||
// Send to Target
|
||||
send(targetPlayer.ws, {
|
||||
type: 'private_chat',
|
||||
sender_id: playerId,
|
||||
text: msg.text
|
||||
});
|
||||
|
||||
// Send back to Sender (so they know it sent)
|
||||
// Actually, client can handle simulated display, but server confirmation is nice.
|
||||
// In this design, let's just let client handle its own "Sent" display to avoid double-echo if we aren't strict.
|
||||
// But wait, our client code relies on "private_chat_message_received" for INCOMING.
|
||||
// For OUTGOING, the client code manually appends to log. So we DON'T echo back.
|
||||
}
|
||||
}
|
||||
default:
|
||||
console.warn('Unknown message type:', msg.type);
|
||||
}
|
||||
});
|
||||
|
||||
ws.on('close', () => {
|
||||
if (playerId !== null) {
|
||||
console.log(`Player ${playerId} disconnected`);
|
||||
const player = players.get(playerId);
|
||||
const currentScene = player ? player.scene : 'world';
|
||||
players.delete(playerId);
|
||||
broadcastExcept(playerId, { type: 'player_leave', id: playerId }, currentScene);
|
||||
}
|
||||
});
|
||||
|
||||
function handleLogin(ws, msg) {
|
||||
playerId = nextPlayerId++;
|
||||
const player = {
|
||||
id: playerId,
|
||||
name: msg.name || 'Anonymous',
|
||||
x: 240, // Center of 480x270 viewport
|
||||
y: 135,
|
||||
skin_id: msg.skin_id || 0,
|
||||
scene: 'world', // Default scene
|
||||
ws: ws
|
||||
};
|
||||
players.set(playerId, player);
|
||||
|
||||
// Send login_ok to this player with all current players IN THE SAME SCENE
|
||||
const sameScenePlayers = [];
|
||||
for (const [id, p] of players) {
|
||||
if (p.scene === player.scene) {
|
||||
sameScenePlayers.push({ id: p.id, name: p.name, x: p.x, y: p.y, skin_id: p.skin_id });
|
||||
}
|
||||
}
|
||||
send(ws, { type: 'login_ok', id: playerId, players: sameScenePlayers });
|
||||
|
||||
// Broadcast to others in the SAME SCENE that a new player joined
|
||||
broadcastExcept(playerId, {
|
||||
type: 'player_join',
|
||||
id: playerId,
|
||||
name: player.name,
|
||||
x: player.x,
|
||||
y: player.y,
|
||||
skin_id: player.skin_id
|
||||
}, player.scene);
|
||||
|
||||
console.log(`Player ${playerId} (${player.name}) logged in to ${player.scene}`);
|
||||
}
|
||||
|
||||
function handleMove(msg) {
|
||||
if (playerId === null) return;
|
||||
const player = players.get(playerId);
|
||||
// console.log(`Server: handleMove from PlayerID ${playerId}. Msg: ${JSON.stringify(msg)}`);
|
||||
if (!player) return;
|
||||
|
||||
player.x = msg.x;
|
||||
player.y = msg.y;
|
||||
|
||||
// Broadcast to all OTHER players IN THE SAME SCENE
|
||||
broadcastExcept(playerId, {
|
||||
type: 'player_move',
|
||||
id: playerId,
|
||||
x: msg.x,
|
||||
y: msg.y,
|
||||
flip_h: msg.flip_h,
|
||||
frame: msg.frame
|
||||
}, player.scene);
|
||||
}
|
||||
|
||||
function handleChangeScene(msg) {
|
||||
if (playerId === null) return;
|
||||
const player = players.get(playerId);
|
||||
if (!player) return;
|
||||
|
||||
const oldScene = player.scene;
|
||||
const newScene = msg.scene;
|
||||
|
||||
if (oldScene === newScene) return;
|
||||
|
||||
// 1. Tell everyone in the OLD scene that this player LEFT
|
||||
broadcastExcept(playerId, { type: 'player_leave', id: playerId }, oldScene);
|
||||
|
||||
// 2. Update player scene AND position if provided
|
||||
player.scene = newScene;
|
||||
if (typeof msg.x === 'number' && typeof msg.y === 'number') {
|
||||
player.x = msg.x;
|
||||
player.y = msg.y;
|
||||
}
|
||||
console.log(`Player ${playerId} changed scene to ${newScene} at (${player.x}, ${player.y})`);
|
||||
|
||||
// 3. Tell everyone in the NEW scene that this player JOINED
|
||||
broadcastExcept(playerId, {
|
||||
type: 'player_join',
|
||||
id: playerId,
|
||||
name: player.name,
|
||||
x: player.x,
|
||||
y: player.y,
|
||||
skin_id: player.skin_id
|
||||
}, newScene);
|
||||
|
||||
// 4. Send the player the list of existing players in the NEW scene
|
||||
const newScenePlayers = [];
|
||||
for (const [id, p] of players) {
|
||||
if (p.id !== playerId && p.scene === newScene) {
|
||||
newScenePlayers.push({ id: p.id, name: p.name, x: p.x, y: p.y, skin_id: p.skin_id });
|
||||
}
|
||||
}
|
||||
// Use a special sync message or re-use login_ok?
|
||||
// Re-using login_ok might confuse the client if it resets ID, but since ID matches, it might be fine.
|
||||
// Better: custom 'scene_peers' message.
|
||||
send(ws, { type: 'scene_peers', players: newScenePlayers });
|
||||
}
|
||||
|
||||
function handleChat(msg) {
|
||||
if (playerId === null) return;
|
||||
const player = players.get(playerId);
|
||||
// Broadcast to ALL players IN THE SAME SCENE
|
||||
broadcast({
|
||||
type: 'chat',
|
||||
id: playerId,
|
||||
text: msg.text
|
||||
}, player ? player.scene : 'world');
|
||||
}
|
||||
|
||||
function handlePrivateChat(msg) {
|
||||
if (playerId === null) return;
|
||||
|
||||
const targetId = msg.target_id;
|
||||
const targetPlayer = players.get(targetId);
|
||||
|
||||
if (targetPlayer) {
|
||||
// Send to Target
|
||||
send(targetPlayer.ws, {
|
||||
type: 'private_chat',
|
||||
sender_id: playerId,
|
||||
text: msg.text
|
||||
});
|
||||
// No echo needed for outgoing, client handles it.
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function send(ws, obj) {
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify(obj));
|
||||
}
|
||||
}
|
||||
|
||||
function broadcast(obj, sceneFilter) {
|
||||
const data = JSON.stringify(obj);
|
||||
for (const [id, player] of players) {
|
||||
if (player.ws.readyState === WebSocket.OPEN) {
|
||||
if (!sceneFilter || player.scene === sceneFilter) {
|
||||
player.ws.send(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function broadcastExcept(excludeId, obj, sceneFilter) {
|
||||
const data = JSON.stringify(obj);
|
||||
for (const [id, player] of players) {
|
||||
if (id !== excludeId && player.ws.readyState === WebSocket.OPEN) {
|
||||
if (!sceneFilter || player.scene === sceneFilter) {
|
||||
// console.log(`Server: Sending to ${id}: ${data}`);
|
||||
player.ws.send(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleGetNotices(ws) {
|
||||
const response = {
|
||||
type: 'notices_list',
|
||||
data: NOTICES
|
||||
};
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify(response));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user