UNPKG

mm_os

Version:

MM_OS服务端架构,用于快速构建应用程序,支持网站建设、小程序后台、AI应用、物联网(IOT/AIOT)、游戏服务端等多种场景。

524 lines (466 loc) 12.4 kB
const { Drive } = require('mm_machine'); const { idGen } = require('../../ulits/id_gen.js'); /** * 游戏房间 */ class GameRoom extends Drive { static config = { // 房间名称 'name': '5v5', // 房间标题 'title': '', // 是否召集 'invite': true, // 房间等级下限 'min_level': 0, // 房间等级上限 'max_level': 1000, // 房间最大人数 'max_players': 5, // 游戏类型 rpg、card 'type': 'card', // 游戏类型 排位赛rank、娱乐局casual 'mode': 'rank', // 房间主程序 'main': '' }; /** * 构造函数 * @param {object} config 配置参数 */ constructor(config) { super({ ...GameRoom.config, ...config }); // 房间ID this.room_id = this.genId(); // 游戏世界ID this._world_id = ''; // 房主ID this.owner = ''; // 玩家ID集合 - 使用Set保证唯一性和高效查找 this._player_ids = new Set(); // 状态 1:waiting等待中, 2:queue队列中, 3:doing进行中 this.state = 1; // 匹配时间 this._match_time = null; } }; /** * 获取模板目录 * @returns {string} 模板目录 */ GameRoom.prototype.getTplDir = function() { return __dirname; }; /** * 生成房间ID * @returns {string} 房间ID */ GameRoom.prototype.genId = function () { return idGen.genId('room'); }; /** * 初始化核心 * @param {object} channel 游戏频道 * @param {object} eventer 事件管理器 * @param {object} logger 日志管理器 */ GameRoom.prototype._initCore = async function (channel, eventer, logger) { if (logger) { this.setLogger(logger); } if (eventer) { this.getEventer = function () { return eventer; }; } if (!channel) { throw new TypeError('游戏频道不能为空'); } // 游戏频道 this._channel = channel; }; /** * 加载房间玩家 * @returns {Promise<void>} */ GameRoom.prototype._load = async function () { try { // 通过事件方式加载房间内的玩家数据,避免直接操作数据库 var players = await this.emitWait('room_player_load', { room_id: this.room_id, room_name: this.config.name, room: this }); if (players && players.length > 0) { // 遍历玩家列表,恢复玩家到房间 for (var i = 0; i < players.length; i++) { var player_id = players[i]; // 添加玩家到房间 this._player_ids.add(player_id); } this.log('info', `加载房间玩家成功,共 ${players.length} 人`); } } catch (error) { this.log('error', `加载房间玩家数据失败: `, error); } }; /** * 设置房主 * @param {object} player 房主 */ GameRoom.prototype.setOwner = function (player) { this.owner = player.player_id; }; /** * 检查房间是否有玩家 * @param {string} player_id 玩家ID * @returns {boolean} 是否有玩家 */ GameRoom.prototype._hasPlayer = function (player_id) { return this._player_ids.has(player_id); }; /** * 添加玩家 * @param {string} player_id 玩家ID */ GameRoom.prototype._addPlayer = function (player_id) { this._player_ids.add(player_id); }; /** * 移除玩家 * @param {string} player_id 玩家ID */ GameRoom.prototype._delPlayer = function (player_id) { this._player_ids.delete(player_id); }; /** * 获取游戏世界 * @returns {object} 游戏世界 */ GameRoom.prototype.getWorld = function () { // 从游戏频道获取游戏世界 return this._channel.getWorld(this._world_id); }; /** * 设置游戏世界 * @param {string} world_id 游戏世界ID */ GameRoom.prototype.setWorldId = function (world_id) { this._world_id = world_id; }; /** * 创建游戏世界 * @param {object} config 游戏世界配置 * @returns {object} 游戏世界 */ GameRoom.prototype.createWorld = async function (config) { // 游戏世界 var world = await this._channel.createWorld({ ...this.config, ...config }); // 设置游戏世界 this.setWorldId(world.world_id); return world; }; /** * 广播消息给房间内所有玩家 * @param {string} event 事件名称 * @param {object} data 数据 */ GameRoom.prototype.broadcast = function (event, data) { var player_ids = this.getAllPlayerIds(); // 直接触发房间广播事件,让事件处理器来处理 this.emitEvent('room_broadcast', { room_id: this.room_id, room_name: this.config.name, event: event, data: data, player_ids: player_ids, timestamp: Date.now() }); this.log('debug', `广播消息: ${event} 给 ${player_ids.length} 名玩家`); }; /** * 玩家进入房间 * @param {string} player_id 玩家ID */ GameRoom.prototype.enter = function (player_id) { // 检查玩家是否已在房间内 if (this._hasPlayer(player_id)) { throw new Error(`玩家 ${player_id} 已在房间内`); } this._addPlayer(player_id); this.emitEvent('room_enter', { room_id: this.room_id, room_name: this.config.name, player_id: player_id, timestamp: Date.now() }); }; /** * 玩家退出房间 * @param {string} player_id 玩家ID */ GameRoom.prototype.leave = function (player_id) { this._delPlayer(player_id); this.emitEvent('room_leave', { room_id: this.room_id, room_name: this.config.name, player_id: player_id, timestamp: Date.now() }); }; /** * 获取房间玩家数量 * @returns {number} 玩家数量 */ GameRoom.prototype.getPlayerNum = function () { return this._player_ids.size; }; /** * 获取所有玩家ID * @returns {Array} 玩家ID数组 */ GameRoom.prototype.getAllPlayerIds = function () { return Array.from(this._player_ids); }; /** * 开始匹配 */ GameRoom.prototype.startMatch = function () { // 检查所有玩家是否已准备 if (!this.isAllPlayersReady()) { throw new Error('房间内玩家未全部准备'); } // 检查房间是否已开始匹配 if (this.state === 2) { throw new Error('房间已开始匹配'); } // 检查房间是否已开始游戏 else if (this.state === 3) { throw new Error('房间已开始游戏'); } this.state = 2; // 记录匹配开始时间 this._match_time = Date.now(); // 触发开始匹配事件 this.emitEvent('room_start_match', { room_id: this.room_id, room_name: this.config.name, timestamp: this._match_time }); }; /** * 匹配玩家 * 根据房间配置进行玩家匹配 * @returns {object} 匹配结果 */ GameRoom.prototype._matchPlayers = function () { var config = this.config; var format = config.format || '5v5'; var mode = config.mode || 'rank'; var player_ids = this.getAllPlayerIds(); var player_count = player_ids.length; var ret = this._checkMatch(player_count, config.max_players || 6); if (!ret.valid) { return { success: false, reason: ret.reason }; } var teams = this._splitTeams(player_ids, format); if (!teams) { return { success: false, reason: '玩家数量不符合比赛格式要求' }; } return this._buildMatch(mode, format, teams, player_count); }; /** * 检查匹配条件 * @param {number} player_count 玩家数量 * @param {number} max_players 最大玩家数 * @returns {object} 检查结果 */ GameRoom.prototype._checkMatch = function (player_count, max_players) { if (player_count === 0) { return { valid: false, reason: '房间内没有玩家' }; } if (player_count > max_players) { return { valid: false, reason: '房间内玩家数量超过上限' }; } return { valid: true }; }; /** * 构建匹配结果 * @param {string} mode 游戏模式 * @param {string} format 比赛格式 * @param {object} teams 队伍配置 * @param {number} player_count 玩家数量 * @returns {object} 匹配结果 */ GameRoom.prototype._buildMatch = function (mode, format, teams, player_count) { var ret = { success: true, room_id: this.room_id, mode: mode, format: format, teams: teams, player_count: player_count, timestamp: Date.now() }; this.emitEvent('room_match_success', { room_id: this.room_id, ret: ret }); return ret; }; /** * 根据比赛格式分割队伍 * @param {Array} player_ids 玩家ID数组 * @param {string} format 比赛格式 (1v1, 3v3, 5v5, 1v3, 1v5) * @returns {object|null} 队伍配置 */ GameRoom.prototype._splitTeams = function (player_ids, format) { var count = player_ids.length; var format_map = { '1v1': { count: 2, team_a_size: 1 }, '3v3': { count: 6, team_a_size: 3 }, '5v5': { count: 10, team_a_size: 5 }, '1v3': { count: 4, team_a_size: 1 }, '1v5': { count: 6, team_a_size: 1 } }; var rule = format_map[format]; if (rule && count === rule.count) { return { team_a: player_ids.slice(0, rule.team_a_size), team_b: player_ids.slice(rule.team_a_size), format: format }; } if (count >= 2) { var half = Math.floor(count / 2); return { team_a: player_ids.slice(0, half), team_b: player_ids.slice(half), format: format }; } return null; }; /** * 获取玩家 * @param {string} player_id 玩家ID * @returns {object} 玩家对象 */ GameRoom.prototype._getPlayer = function(player_id) { // 这里需要从游戏频道获取玩家对象 return this._channel.getPlayer(player_id); }; /** * 玩家准备 * @param {string} player_id 玩家ID */ GameRoom.prototype.ready = function(player_id) { // 检查玩家是否已在房间内 if (!this._hasPlayer(player_id)) { throw new Error(`玩家 ${player_id} 不在房间内`); } // 获取玩家 var player = this._getPlayer(player_id); player.is_ready = true; // 触发玩家准备事件 this.emitEvent('player_ready', { room_id: this.room_id, player_id: player_id, timestamp: Date.now() }); }; /** * 玩家取消准备 * @param {string} player_id 玩家ID */ GameRoom.prototype.cancelReady = function(player_id) { // 检查玩家是否已在房间内 if (!this._hasPlayer(player_id)) { throw new Error(`玩家 ${player_id} 不在房间内`); } // 获取玩家 var player = this._getPlayer(player_id); player.is_ready = false; // 触发玩家取消准备事件 this.emitEvent('player_cancel_ready', { room_id: this.room_id, player_id: player_id, timestamp: Date.now() }); }; /** * 检查所有玩家是否已准备 * @returns {boolean} 是否全部准备 */ GameRoom.prototype.isAllPlayersReady = function() { var player_ids = this.getAllPlayerIds(); // 如果房间没有玩家,返回false if (player_ids.length === 0) { return false; } // 使用for循环检查除房主外的所有玩家是否已准备 for (var i = 0; i < player_ids.length; i++) { var player_id = player_ids[i]; // 跳过房主(房主没有准备按钮) if (player_id === this.owner) { continue; } var player = this._getPlayer(player_id); // 如果有一个玩家未准备,返回false if (!player.is_ready) { return false; } } // 所有非房主玩家都已准备 return true; }; /** * 获取准备玩家列表 * @returns {Array} 准备玩家ID列表 */ GameRoom.prototype.getReadyPlayers = function() { var ready_players = []; var player_ids = this.getAllPlayerIds(); // 使用for循环获取除房主外的准备玩家列表 for (var i = 0; i < player_ids.length; i++) { var player_id = player_ids[i]; // 跳过房主(房主没有准备按钮) if (player_id === this.owner) { continue; } var player = this._getPlayer(player_id); // 如果玩家已准备,添加到列表 if (player.is_ready) { ready_players.push(player_id); } } return ready_players; }; /** * 保存房间数据 * @returns {Promise<void>} */ GameRoom.prototype._save = async function () { try { var room_data = { room_id: this.room_id, name: this.config.name, owner: this.owner, state: this.state, player_ids: this.getAllPlayerIds(), world_id: this._world_id }; // 通过事件方式保存房间数据 await this.emitWait('room_save', { room_id: this.room_id, room_data: room_data }); this.log('debug', `保存房间数据成功`); } catch (error) { this.log('error', `保存房间数据失败: `, error); } }; exports.GameRoom = GameRoom;