mm_os
Version:
MM_OS服务端架构,用于快速构建应用程序,支持网站建设、小程序后台、AI应用、物联网(IOT/AIOT)、游戏服务端等多种场景。
524 lines (466 loc) • 12.4 kB
JavaScript
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;