mm_os 4.2.4 → 4.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +1 -1
  2. package/README_EN.md +2 -2
  3. package/adapter/adapter.js +105 -52
  4. package/adapter/custom_persistence.js +32 -31
  5. package/adapter/mqtt.js +59 -79
  6. package/adapter/socket.js +188 -58
  7. package/adapter/web.js +92 -68
  8. package/adapter/websocket.js +43 -146
  9. package/com/api/drive.js +732 -708
  10. package/com/api/index.js +196 -198
  11. package/com/api/script.tpl.js +24 -24
  12. package/com/cmd/config.tpl.json +48 -122
  13. package/com/cmd/drive.js +176 -1548
  14. package/com/cmd/index.js +287 -1066
  15. package/com/cmd/job/config.json +66 -0
  16. package/com/cmd/job/index.js +241 -0
  17. package/com/cmd/job/test.js +188 -0
  18. package/com/cmd/script.tpl.js +12 -33
  19. package/com/db/drive.js +2206 -2019
  20. package/com/db/index.js +241 -242
  21. package/com/event/config.tpl.json +3 -1
  22. package/com/event/drive.js +3 -1
  23. package/com/event/index.js +73 -46
  24. package/com/event/script.tpl.js +22 -22
  25. package/com/mqtt/config.tpl.json +16 -8
  26. package/com/mqtt/drive.js +696 -676
  27. package/com/mqtt/index.js +1012 -822
  28. package/com/mqtt/index.test.js +503 -0
  29. package/com/mqtt/mm_mqtt.js +446 -420
  30. package/com/mqtt/script.tpl.js +3 -7
  31. package/com/nav/drive.js +7 -7
  32. package/com/nav/index.js +228 -231
  33. package/com/param/index.js +19 -17
  34. package/com/pendant/index.js +467 -441
  35. package/com/socket/drive.js +679 -651
  36. package/com/socket/index.js +94 -48
  37. package/com/sql/drive.js +1283 -1259
  38. package/com/sql/index.js +152 -150
  39. package/com/static/drive.js +1 -1
  40. package/com/static/index.js +232 -226
  41. package/com/static/script.tpl.js +25 -25
  42. package/com/task/drive.js +428 -405
  43. package/com/task/index.js +154 -148
  44. package/com/task/script.tpl.js +37 -34
  45. package/com/template/drive.js +1 -1
  46. package/com/template/index.js +141 -138
  47. package/com.js +87 -6
  48. package/common/middleware/web_after/index.js +5 -11
  49. package/common/middleware/web_base/index.js +1 -1
  50. package/common/middleware/web_cors/index.js +1 -1
  51. package/common/middleware/web_error/index.js +131 -119
  52. package/common/middleware/web_error/middleware.json +2 -1
  53. package/common/middleware/web_logger/index.js +1 -1
  54. package/common/middleware/web_main/index.js +2 -0
  55. package/common/middleware/web_static/index.js +2 -2
  56. package/common/middleware/web_waf/index.js +1 -1
  57. package/core/app/config.tpl.json +4 -4
  58. package/core/app/index.js +710 -534
  59. package/core/middleware/index.js +1 -9
  60. package/core/mod/config.tpl.json +1 -1
  61. package/core/model/config.tpl.json +1 -1
  62. package/core/plugin/config.tpl.json +15 -3
  63. package/core/plugin/index.js +31 -0
  64. package/core/pusher/index.js +158 -161
  65. package/core/scene/loop.js +1306 -1255
  66. package/core/store/index.js +1794 -1755
  67. package/core/store/sql.js +1488 -1464
  68. package/core/zone/bat/index.js +744 -728
  69. package/core/zone/config.tpl.json +3 -3
  70. package/core/zone/index.js +655 -616
  71. package/os.js +0 -4
  72. package/package.json +16 -16
  73. package/server.js +583 -427
  74. package/com/cmd/nlp.js +0 -525
  75. package/core/app/index copy.js +0 -611
  76. package/software.js +0 -194
  77. package/tcp.js +0 -279
@@ -1,728 +1,744 @@
1
- const {
2
- cacheAdmin
3
- } = require('mm_cache');
4
- const {
5
- sqlAdmin
6
- } = require('mm_sql');
7
- const {
8
- Drive
9
- } = require('mm_machine');
10
- const { idGen } = require('../../../ulits/id_gen.js');
11
- const {
12
- GameChannel
13
- } = require('../../channel/index.js');
14
-
15
- /**
16
- * 游戏分区
17
- */
18
- class Zone extends Drive {
19
- static config = {
20
- // 区域名称
21
- 'name': 'default',
22
- // 区域标题
23
- 'title': '枫叶区',
24
- // 区域描述
25
- 'description': '枫叶区是一个区域',
26
- // 区域主脚本 - 可自定义区域初始化,一般情况不需要定义
27
- 'main': '',
28
- // 应用域
29
- 'scope': '',
30
- // 区域状态 - 1 正常 0 禁用
31
- 'state': 1,
32
- // 区域排序 - 数字越小越靠前
33
- 'sort': 100,
34
- // 相关数据表
35
- 'table': {
36
- // 玩家表名
37
- 'player': 'game_player'
38
- },
39
- // 频道数
40
- 'channels': 20,
41
- // 自定义数据库 - 1 开启 0 关闭
42
- 'diy_sql': 0,
43
- // 自定义缓存 - 1 开启 0 关闭
44
- 'diy_cache': 0,
45
- // sql 配置 - 可以没有,没有则使用默认数据库
46
- 'sql': {
47
- // 数据库方式
48
- 'way': 'mysql',
49
- // 数据库主机
50
- 'host': '127.0.0.1',
51
- // 数据库端口
52
- 'port': 3306,
53
- // 数据库用户名
54
- 'username': 'root',
55
- // 数据库密码
56
- 'password': 'Asd159357',
57
- // 数据库名称
58
- 'database': 'mos'
59
- },
60
- // 缓存配置 - 可以没有,没有则使用默认缓存
61
- 'cache': {
62
- // 缓存方式方式
63
- 'way': 'redis',
64
- // 缓存主机
65
- 'host': '127.0.0.1',
66
- // 缓存端口
67
- 'port': 6379,
68
- // 缓存密码
69
- 'password': 'asd159357',
70
- // 缓存数据库
71
- 'database': 0
72
- }
73
- };
74
-
75
- /**
76
- * 构造函数
77
- * @param {object} config 配置参数
78
- * @param {string} config.name 区域名称
79
- * @param {string} config.title 区域标题
80
- * @param {string} config.description 区域描述
81
- * @param {string} config.main 区域主脚本
82
- * @param {string} config.scope 应用域
83
- * @param {number} config.state 区域状态
84
- * @param {number} config.sort 区域排序
85
- * @param {object} config.table 相关数据表
86
- * @param {number} config.channels 频道数
87
- * @param {number} config.diy_sql 自定义数据库
88
- * @param {number} config.diy_cache 自定义缓存
89
- * @param {object} config.sql sql 配置
90
- * @param {object} config.cache 缓存配置
91
- * @param {object} parent 父分区对象
92
- */
93
- constructor(config, parent) {
94
- super({ ...Zone.config, ...config }, parent);
95
-
96
- // 游戏频道集合
97
- this.channel = {};
98
- this.room = {};
99
- this.worlds = {};
100
-
101
- // 玩家对象集合 - 分区统一管理所有玩家
102
- this.players = new Map();
103
- }
104
- }
105
-
106
- /**
107
- * 预设
108
- */
109
- Zone.prototype._preset = function () {
110
- // 分区专用数据库
111
- this.sql = null;
112
- // 分区专用缓存
113
- this.cache = null;
114
- };
115
-
116
- /**
117
- * 获取模板目录
118
- * @returns {string} 模板目录
119
- */
120
- Zone.prototype.getTplDir = function () {
121
- return __dirname;
122
- };
123
-
124
- /**
125
- * 创建频道
126
- * @param {number} channel_id 频道ID
127
- * @param {object} config 频道配置
128
- * @returns {object} 创建的频道对象
129
- */
130
- Zone.prototype.createChannel = function (channel_id, config) {
131
- let channel = new GameChannel({ scope: this.config.scope, ...config || {} }, this);
132
- channel.channel_id = channel_id || this.channel.size + 1;
133
- this.channel.set(channel.channel_id, channel);
134
- return channel;
135
- };
136
-
137
- /**
138
- * 初始化管理器
139
- */
140
- Zone.prototype._initManager = async function () {
141
- // 初始化游戏频道
142
- for (let i = 1; i <= this.config.channels; i++) {
143
- this.createChannel(i);
144
- }
145
- };
146
-
147
- /**
148
- * 加载资源
149
- * @private
150
- */
151
- Zone.prototype._loadSources = async function () {
152
- // 并行加载游戏频道
153
- var promises = [];
154
- for (let key of this.channels.keys()) {
155
- promises.push(this.channels.get(key).do('load'));
156
- }
157
- await Promise.all(promises);
158
- };
159
-
160
- /**
161
- * 初始化资源
162
- * @private
163
- */
164
- Zone.prototype._initSources = async function () {
165
- // 并行初始化游戏频道
166
- var promises = [];
167
- for (let o of this.channels.values()) {
168
- promises.push(o.do('init', this, this.getEventer(), this.getLogger()));
169
- }
170
- await Promise.all(promises);
171
- };
172
-
173
- /**
174
- * 初始化基础组件
175
- * @private
176
- */
177
- Zone.prototype._initBase = async function () {
178
- // 初始化数据库
179
- if (this.config.diy_sql) {
180
- this.sql = sqlAdmin(this.config.name, this.config.sql);
181
- await this.sql.open();
182
- }
183
- else {
184
- this.sql = this.getApp().sql;
185
- }
186
-
187
- // 初始化缓存
188
- if (this.config.diy_cache) {
189
- this.cache = cacheAdmin(this.config.name, this.config.cache);
190
- await this.cache.connect();
191
- }
192
- else {
193
- this.cache = this.getApp().cache;
194
- }
195
- };
196
-
197
- /**
198
- * 初始化核心
199
- * @param {object} app 游戏应用
200
- * @param {object} eventer 事件总线
201
- * @param {object} logger 日志管理器
202
- * @returns {object} 返回分区对象
203
- */
204
- Zone.prototype._initCore = async function (app, eventer, logger) {
205
- // 初始化依赖项
206
- if (logger) {
207
- this.setLogger(logger);
208
- }
209
- if (eventer) {
210
- this.getEventer = function () {
211
- return eventer;
212
- };
213
- }
214
- if (app) {
215
- this.getApp = function () {
216
- return app;
217
- };
218
- }
219
- // // 初始化基础组件
220
- // await this._initBase();
221
- // // 初始化管理器
222
- // await this._initManager();
223
- // // 加载资源
224
- // await this._loadSources();
225
- return this;
226
- };
227
-
228
- /**
229
- * 启动资源
230
- * @private
231
- */
232
- Zone.prototype._startSources = async function () {
233
- // 并行启动游戏频道
234
- var promises = [];
235
- for (let key of this.channels.keys()) {
236
- promises.push(this.channels.get(key).do('start'));
237
- }
238
- await Promise.all(promises);
239
- };
240
-
241
- /**
242
- * 启动核心
243
- * @private
244
- */
245
- Zone.prototype._startCore = async function () {
246
- // // 初始化资源
247
- // await this._initSources();
248
-
249
- // 加载所有登录玩家
250
- await this._loadPlayers();
251
-
252
- // 启动资源
253
- await this._startSources();
254
- };
255
-
256
- /**
257
- * 获取玩家对象
258
- * @param {string} player_id 玩家ID
259
- * @returns {object} 玩家对象
260
- */
261
- Zone.prototype.getPlayer = function (player_id) {
262
- // 参数校验
263
- if (!player_id || typeof player_id !== 'string') {
264
- throw new TypeError('玩家ID必须是有效字符串');
265
- }
266
-
267
- var player = this.players.get(player_id);
268
- if (!player) {
269
- throw new Error(`玩家 ${player_id} 不存在`);
270
- }
271
-
272
- return player;
273
- };
274
-
275
- /**
276
- * 从分区移除玩家
277
- * @param {string} player_id 玩家ID
278
- * @returns {boolean} 是否移除成功
279
- */
280
- Zone.prototype.delPlayer = function (player_id) {
281
- // 参数校验
282
- if (!player_id || typeof player_id !== 'string') {
283
- throw new TypeError('玩家ID必须是有效字符串');
284
- }
285
-
286
- var player = this.players.get(player_id);
287
- if (!player) {
288
- throw new Error('玩家不在分区中');
289
- }
290
-
291
- // 如果玩家在某个频道中,先退出频道
292
- if (player.channel_id && this.channels.get(player.channel_id)) {
293
- this.channels.get(player.channel_id).leave(player_id);
294
- }
295
-
296
- // 从分区移除玩家
297
- this.players.delete(player_id);
298
-
299
- // 触发玩家离开分区事件
300
- this.emitEvent('player_leave_zone', {
301
- player_id: player_id,
302
- zone_name: this.config.name,
303
- timestamp: Date.now()
304
- });
305
-
306
- return true;
307
- };
308
-
309
- /**
310
- * 玩家进入频道
311
- * @param {string} player_id 玩家ID
312
- * @param {number} channel_id 频道ID
313
- * @returns {boolean} 是否进入成功
314
- */
315
- Zone.prototype.enterChannel = async function (player_id, channel_id) {
316
- // 参数校验
317
- if (!player_id || typeof player_id !== 'string') {
318
- throw new TypeError('玩家ID必须是有效字符串');
319
- }
320
-
321
- if (!channel_id || typeof channel_id !== 'number') {
322
- throw new TypeError('频道ID必须是有效数字');
323
- }
324
-
325
- // 获取玩家对象
326
- var player = this.getPlayer(player_id);
327
-
328
- // 检查频道是否存在
329
- if (!this.channels.has(channel_id)) {
330
- throw new Error(`频道 ${channel_id} 不存在`);
331
- }
332
-
333
- // 如果玩家已在其他频道,先退出
334
- if (player.channel_id && this.channels.get(player.channel_id)) {
335
- await this.channels.get(player.channel_id).leave(player_id);
336
- }
337
-
338
- // 玩家进入频道
339
- await this.channels.get(channel_id).enter(player_id);
340
-
341
- // 更新玩家频道ID
342
- player.channel_id = channel_id;
343
-
344
- return true;
345
- };
346
-
347
- /**
348
- * 切换频道
349
- * @param {string} player_id 玩家ID
350
- * @param {number} channel_id 频道ID
351
- * @returns {boolean} 是否切换成功
352
- */
353
- Zone.prototype.switchChannel = async function (player_id, channel_id) {
354
- // 参数校验
355
- if (!player_id || typeof player_id !== 'string') {
356
- throw new TypeError('玩家ID必须是有效字符串');
357
- }
358
-
359
- if (!channel_id || typeof channel_id !== 'number') {
360
- throw new TypeError('频道ID必须是有效数字');
361
- }
362
-
363
- // 获取玩家对象
364
- var player = this.getPlayer(player_id);
365
-
366
- // 检查目标频道是否存在
367
- if (!this.channels[channel_id]) {
368
- throw new Error(`频道 ${channel_id} 不存在`);
369
- }
370
-
371
- // 如果玩家已在目标频道,直接返回
372
- if (player.channel_id === channel_id) {
373
- return true;
374
- }
375
-
376
- // 退出当前频道
377
- if (player.channel_id && this.channels.get(player.channel_id)) {
378
- await this.channels.get(player.channel_id).leave(player_id);
379
- }
380
-
381
- // 进入目标频道
382
- await this.channels.get(channel_id).enter(player_id);
383
-
384
- // 更新玩家频道ID
385
- player.channel_id = channel_id;
386
-
387
- return true;
388
- };
389
-
390
- /**
391
- * 加载所有登录玩家
392
- */
393
- Zone.prototype._loadPlayers = async function () {
394
- var players = await this.emitWait('load_players', {
395
- zone_name: this.config.name,
396
- zone: this
397
- });
398
- if (players && players.length > 0) {
399
- for (var i = 0; i < players.length; i++) {
400
- var info = players[i];
401
- var player = this._model.player.create(info);
402
- this.players.set(player.player_id, player);
403
- }
404
- }
405
- };
406
-
407
- /**
408
- * 保存所有玩家数据
409
- * @returns {Promise<object>} 保存结果
410
- */
411
- Zone.prototype._savePlayers = async function () {
412
- var all_players = Array.from(this.players.values());
413
- if (all_players.length === 0) {
414
- return { count: 0, success: true };
415
- }
416
- await this.emitWait('save_players', {
417
- zone_name: this.config.name,
418
- zone: this,
419
- players: all_players
420
- });
421
- this.log('info', `保存所有玩家数据 ${all_players.length} 个`);
422
- return { count: all_players.length, success: true };
423
- };
424
-
425
- /**
426
- * 保存脏数据玩家
427
- * @returns {Promise<object>} 保存结果
428
- */
429
- Zone.prototype._saveDirtyPlayers = async function () {
430
- var dirty_players = [];
431
- for (var player of this.players.values()) {
432
- if (player.isDirty && player.isDirty()) {
433
- dirty_players.push(player);
434
- }
435
- }
436
- if (dirty_players.length === 0) {
437
- return { count: 0, success: true };
438
- }
439
- await this.emitWait('save_players', {
440
- zone_name: this.config.name,
441
- zone: this,
442
- players: dirty_players
443
- });
444
- for (var i = 0; i < dirty_players.length; i++) {
445
- dirty_players[i].isDirty(false);
446
- }
447
- this.log('debug', `保存脏数据玩家 ${dirty_players.length} 个`);
448
- return { count: dirty_players.length, success: true };
449
- };
450
-
451
- /**
452
- * 全分区数据发送
453
- * @param {object} data 发送数据
454
- * @param {string} msg_type 消息类型
455
- * @param {string} msg_group 消息范围
456
- * @param {string} sender_id 发送者ID
457
- * @returns {object} 发送结果
458
- */
459
- Zone.prototype.sendData = async function (data, msg_type = 'system', msg_group = 'world', sender_id = 'system') {
460
- // 参数校验
461
- if (!data || typeof data !== 'object') {
462
- throw new TypeError('发送数据必须是有效对象');
463
- }
464
-
465
- // 创建发送对象
466
- var msg = {
467
- data: data,
468
- msg_type: msg_type,
469
- msg_group: msg_group,
470
- sender_id: sender_id,
471
- timestamp: Date.now(),
472
- zone_name: this.config.name,
473
- msg_id: this._generateMsgId()
474
- };
475
-
476
- // 触发全分区数据发送事件,由业务层处理具体的发送逻辑
477
- this.emitEvent('zone_data_send', {
478
- ...msg,
479
- players: this.players.size
480
- });
481
-
482
- this.log('debug', `全分区数据发送事件已触发: 玩家数 ${this.players.size}`);
483
-
484
- // 返回默认结果
485
- return {
486
- players: this.players.size,
487
- success_count: 0,
488
- fail_count: 0,
489
- failed_players: []
490
- };
491
- };
492
-
493
- /**
494
- * 生成发送ID
495
- * @returns {string} 发送ID
496
- */
497
- Zone.prototype._generateMsgId = function () {
498
- return idGen.genId(this.config.name);
499
- };
500
-
501
- /**
502
- * 频道数据发送
503
- * @param {number} channel_id 频道ID
504
- * @param {object} data 发送数据
505
- * @param {string} msg_type 消息类型
506
- * @param {string} msg_group 消息范围
507
- * @param {string} sender_id 发送者ID
508
- * @returns {object} 发送结果
509
- */
510
- Zone.prototype.sendToChannel = async function (channel_id, data, msg_type = 'text', msg_group = 'channel', sender_id = 'system') {
511
- // 参数校验
512
- if (!channel_id || typeof channel_id !== 'number') {
513
- throw new TypeError('频道ID必须是有效数字');
514
- }
515
-
516
- if (!data || typeof data !== 'object') {
517
- throw new TypeError('发送数据必须是有效对象');
518
- }
519
-
520
- try {
521
- // 检查频道是否存在
522
- var channel = this.channels.get(channel_id);
523
- if (!channel) {
524
- throw new Error(`频道 ${channel_id} 不存在`);
525
- }
526
-
527
- // 创建发送对象
528
- var send_obj = {
529
- data: data,
530
- msg_type: msg_type,
531
- msg_group: msg_group,
532
- sender_id: sender_id,
533
- timestamp: Date.now(),
534
- channel_id: channel_id,
535
- zone_name: this.config.name,
536
- msg_id: this._generateMsgId()
537
- };
538
-
539
- // 触发频道数据发送事件,由业务层处理具体的发送逻辑
540
- this.emitEvent('channel_send', {
541
- ...send_obj
542
- });
543
-
544
- return {
545
- players: channel.getPlayerNum(),
546
- success: 0,
547
- fail: 0,
548
- failed_players: []
549
- };
550
- } catch (error) {
551
- this.log('error', `频道数据发送失败: `, error);
552
- throw error;
553
- }
554
- };
555
-
556
- /**
557
- * 向指定玩家发送数据
558
- * @param {string} player_id 玩家ID
559
- * @param {object} data 发送数据
560
- * @param {string} msg_type 消息类型
561
- * @param {string} msg_group 消息范围
562
- * @param {string} sender_id 发送者ID
563
- * @returns {boolean} 是否发送成功
564
- */
565
- Zone.prototype.sendToPlayer = async function (player_id, data, msg_type = 'text', msg_group = 'private', sender_id = 'system') {
566
- // 参数校验
567
- if (!player_id || typeof player_id !== 'string') {
568
- throw new TypeError('玩家ID必须是有效字符串');
569
- }
570
-
571
- if (!data || typeof data !== 'object') {
572
- throw new TypeError('发送数据必须是有效对象');
573
- }
574
-
575
- try {
576
- // 获取玩家对象
577
- var player = this.getPlayer(player_id);
578
- if (!player) {
579
- throw new Error(`玩家 ${player_id} 不存在`);
580
- }
581
-
582
- // 触发私聊数据发送事件,由业务层处理具体的发送逻辑
583
- this.emitEvent('private_send', {
584
- data: data,
585
- msg_type: msg_type,
586
- msg_group: msg_group,
587
- sender_id: sender_id,
588
- timestamp: Date.now(),
589
- player_id: player_id,
590
- zone_name: this.config.name,
591
- msg_id: this._generateMsgId()
592
- });
593
-
594
- return true;
595
- } catch (error) {
596
- this.log('error', `向玩家 ${player_id} 发送数据失败: `, error);
597
- throw error;
598
- }
599
- };
600
-
601
- /**
602
- * 条件数据发送
603
- * @param {Function} func 筛选函数
604
- * @param {object} data 发送数据
605
- * @param {string} msg_type 消息类型
606
- * @param {string} msg_group 消息范围
607
- * @param {string} sender_id 发送者ID
608
- * @returns {object} 发送结果
609
- */
610
- Zone.prototype.sendWithCondition = async function (func, data, msg_type = 'system', msg_group = 'world', sender_id = 'system') {
611
- // 参数校验
612
- if (typeof func !== 'function') {
613
- throw new TypeError('筛选函数必须是有效函数');
614
- }
615
-
616
- if (!data || typeof data !== 'object') {
617
- throw new TypeError('发送数据必须是有效对象');
618
- }
619
-
620
- try {
621
- // 触发条件数据发送事件,由业务层处理具体的发送逻辑
622
- this.emitEvent('conditional_send', {
623
- data: data,
624
- msg_type: msg_type,
625
- msg_group: msg_group,
626
- sender_id: sender_id,
627
- timestamp: Date.now(),
628
- zone_name: this.config.name,
629
- msg_id: this._generateMsgId(),
630
- condition: func.toString(),
631
- players: this.players.size
632
- });
633
-
634
- // 返回默认结果
635
- return {
636
- players: this.players.size,
637
- filtered_players: 0,
638
- success_count: 0,
639
- fail_count: 0,
640
- failed_players: []
641
- };
642
- } catch (error) {
643
- this.log('error', `条件数据发送失败: `, error);
644
- throw error;
645
- }
646
- };
647
-
648
- /**
649
- * 获取分区统计信息
650
- * @returns {object} 分区统计信息
651
- */
652
- Zone.prototype.getStats = function () {
653
- var channel_stats = {};
654
- var players = 0;
655
-
656
- // 统计各频道玩家数量
657
- for (var channel_id in this.channels) {
658
- if (this.channels[channel_id] && this.channels[channel_id].getStats) {
659
- var stats = this.channels[channel_id].getStats();
660
- channel_stats[channel_id] = stats;
661
- players += stats.player || 0;
662
- }
663
- }
664
-
665
- return {
666
- zone_name: this.config.name,
667
- players: this.players.size,
668
- online: players,
669
- channels: this.channels.size,
670
- channel_stats: channel_stats,
671
- current_time: Date.now()
672
- };
673
- };
674
-
675
- /**
676
- * 添加玩家到分区
677
- * @param {object} player 玩家对象
678
- * @returns {object} 玩家对象
679
- */
680
- Zone.prototype.addPlayer = function (player) {
681
- this.players.set(player.player_id, player);
682
- // 触发玩家加入分区事件
683
- this.emitEvent('player_enter_zone', {
684
- player_id: player.player_id,
685
- zone_name: this.config.name,
686
- timestamp: Date.now()
687
- });
688
- return player;
689
- };
690
-
691
- /**
692
- * 获取频道对象
693
- * @param {string} channel_id 频道ID
694
- * @returns {object} 频道对象
695
- */
696
- Zone.prototype.getChannel = function (channel_id) {
697
- return this.channels.get(channel_id);
698
- };
699
-
700
- /**
701
- * 获取玩家所在频道
702
- * @param {string} player_id 玩家ID
703
- * @returns {object} 频道对象
704
- */
705
- Zone.prototype.getChannelByPlayerId = function (player_id) {
706
- var player = this.players.get(player_id);
707
- if (player) {
708
- return this.channels.get(player.channel_id);
709
- }
710
- return null;
711
- };
712
-
713
- /**
714
- * 销毁分区
715
- * @returns {Promise<void>}
716
- */
717
- Zone.prototype._destroy = async function () {
718
- await this._savePlayers();
719
- for (var channel of this.channels.values()) {
720
- if (channel.do) {
721
- await channel.do('destroy');
722
- }
723
- }
724
- this.players.clear();
725
- this.channels.clear();
726
- };
727
-
728
- exports.Zone = Zone;
1
+ const { cacheAdmin } = require('mm_cache');
2
+ const { sqlAdmin } = require('mm_sql');
3
+ const { Drive } = require('mm_machine');
4
+ const { idGen } = require('../../../ulits/id_gen.js');
5
+ const { GameChannel } = require('../../channel/index.js');
6
+
7
+ /**
8
+ * 游戏分区
9
+ */
10
+ class Zone extends Drive {
11
+ static config = {
12
+ // 区域名称
13
+ name: 'default',
14
+ // 区域标题
15
+ title: '枫叶区',
16
+ // 区域描述
17
+ description: '枫叶区是一个区域',
18
+ // 区域主脚本 - 可自定义区域初始化,一般情况不需要定义
19
+ main: '',
20
+ // 应用域
21
+ scope: '',
22
+ // 区域状态 - 1 正常 0 禁用
23
+ state: 1,
24
+ // 区域排序 - 数字越小越靠前
25
+ sort: 100,
26
+ // 相关数据表
27
+ table: {
28
+ // 玩家表名
29
+ player: 'game_player',
30
+ },
31
+ // 频道数
32
+ channels: 20,
33
+ // 自定义数据库 - 1 开启 0 关闭
34
+ diy_sql: 0,
35
+ // 自定义缓存 - 1 开启 0 关闭
36
+ diy_cache: 0,
37
+ // sql 配置 - 可以没有,没有则使用默认数据库
38
+ sql: {
39
+ // 数据库方式
40
+ way: 'mysql',
41
+ // 数据库主机
42
+ host: '127.0.0.1',
43
+ // 数据库端口
44
+ port: 3306,
45
+ // 数据库用户名
46
+ username: 'root',
47
+ // 数据库密码
48
+ password: process.env.DB_PASSWORD || '',
49
+ // 数据库名称
50
+ database: 'mm',
51
+ },
52
+ // 缓存配置 - 可以没有,没有则使用默认缓存
53
+ cache: {
54
+ // 缓存方式方式
55
+ way: 'redis',
56
+ // 缓存主机
57
+ host: '127.0.0.1',
58
+ // 缓存端口
59
+ port: 6379,
60
+ // 缓存密码
61
+ password: process.env.REDIS_PASSWORD || '',
62
+ // 缓存数据库
63
+ database: 0,
64
+ },
65
+ };
66
+
67
+ /**
68
+ * 构造函数
69
+ * @param {object} config 配置参数
70
+ * @param {string} config.name 区域名称
71
+ * @param {string} config.title 区域标题
72
+ * @param {string} config.description 区域描述
73
+ * @param {string} config.main 区域主脚本
74
+ * @param {string} config.scope 应用域
75
+ * @param {number} config.state 区域状态
76
+ * @param {number} config.sort 区域排序
77
+ * @param {object} config.table 相关数据表
78
+ * @param {number} config.channels 频道数
79
+ * @param {number} config.diy_sql 自定义数据库
80
+ * @param {number} config.diy_cache 自定义缓存
81
+ * @param {object} config.sql sql 配置
82
+ * @param {object} config.cache 缓存配置
83
+ * @param {object} parent 父分区对象
84
+ */
85
+ constructor(config, parent) {
86
+ super({ ...Zone.config, ...config }, parent);
87
+
88
+ // 游戏频道集合
89
+ this.channel = {};
90
+ this.room = {};
91
+ this.worlds = {};
92
+
93
+ // 玩家对象集合 - 分区统一管理所有玩家
94
+ this.players = new Map();
95
+ }
96
+ }
97
+
98
+ /**
99
+ * 预设
100
+ */
101
+ Zone.prototype._preset = function () {
102
+ // 分区专用数据库
103
+ this.sql = null;
104
+ // 分区专用缓存
105
+ this.cache = null;
106
+ };
107
+
108
+ /**
109
+ * 获取模板目录
110
+ * @returns {string} 模板目录
111
+ */
112
+ Zone.prototype.getTplDir = function () {
113
+ return __dirname;
114
+ };
115
+
116
+ /**
117
+ * 创建频道
118
+ * @param {number} channel_id 频道ID
119
+ * @param {object} config 频道配置
120
+ * @returns {object} 创建的频道对象
121
+ */
122
+ Zone.prototype.createChannel = function (channel_id, config) {
123
+ let channel = new GameChannel(
124
+ { scope: this.config.scope, ...(config || {}) },
125
+ this,
126
+ );
127
+ channel.channel_id = channel_id || this.channel.size + 1;
128
+ this.channel.set(channel.channel_id, channel);
129
+ return channel;
130
+ };
131
+
132
+ /**
133
+ * 初始化管理器
134
+ */
135
+ Zone.prototype._initManager = async function () {
136
+ // 初始化游戏频道
137
+ for (let i = 1; i <= this.config.channels; i++) {
138
+ this.createChannel(i);
139
+ }
140
+ };
141
+
142
+ /**
143
+ * 加载资源
144
+ * @private
145
+ */
146
+ Zone.prototype._loadSources = async function () {
147
+ // 并行加载游戏频道
148
+ var promises = [];
149
+ for (let key of this.channels.keys()) {
150
+ promises.push(this.channels.get(key).do('load'));
151
+ }
152
+ await Promise.all(promises);
153
+ };
154
+
155
+ /**
156
+ * 初始化资源
157
+ * @private
158
+ */
159
+ Zone.prototype._initSources = async function () {
160
+ // 并行初始化游戏频道
161
+ var promises = [];
162
+ for (let o of this.channels.values()) {
163
+ promises.push(o.do('init', this, this.getEventer(), this.getLogger()));
164
+ }
165
+ await Promise.all(promises);
166
+ };
167
+
168
+ /**
169
+ * 初始化基础组件
170
+ * @private
171
+ */
172
+ Zone.prototype._initBase = async function () {
173
+ // 初始化数据库
174
+ if (this.config.diy_sql) {
175
+ this.sql = sqlAdmin(this.config.name, this.config.sql);
176
+ await this.sql.open();
177
+ } else {
178
+ this.sql = this.getApp().sql;
179
+ }
180
+
181
+ // 初始化缓存
182
+ if (this.config.diy_cache) {
183
+ this.cache = cacheAdmin(this.config.name, this.config.cache);
184
+ await this.cache.connect();
185
+ } else {
186
+ this.cache = this.getApp().cache;
187
+ }
188
+ };
189
+
190
+ /**
191
+ * 初始化核心
192
+ * @param {object} app 游戏应用
193
+ * @param {object} eventer 事件总线
194
+ * @param {object} logger 日志管理器
195
+ * @returns {object} 返回分区对象
196
+ */
197
+ Zone.prototype._initCore = async function (app, eventer, logger) {
198
+ // 初始化依赖项
199
+ if (logger) {
200
+ this.setLogger(logger);
201
+ }
202
+ if (eventer) {
203
+ this.getEventer = function () {
204
+ return eventer;
205
+ };
206
+ }
207
+ if (app) {
208
+ this.getApp = function () {
209
+ return app;
210
+ };
211
+ }
212
+ // // 初始化基础组件
213
+ // await this._initBase();
214
+ // // 初始化管理器
215
+ // await this._initManager();
216
+ // // 加载资源
217
+ // await this._loadSources();
218
+ return this;
219
+ };
220
+
221
+ /**
222
+ * 启动资源
223
+ * @private
224
+ */
225
+ Zone.prototype._startSources = async function () {
226
+ // 并行启动游戏频道
227
+ var promises = [];
228
+ for (let key of this.channels.keys()) {
229
+ promises.push(this.channels.get(key).do('start'));
230
+ }
231
+ await Promise.all(promises);
232
+ };
233
+
234
+ /**
235
+ * 启动核心
236
+ * @private
237
+ */
238
+ Zone.prototype._startCore = async function () {
239
+ // // 初始化资源
240
+ // await this._initSources();
241
+
242
+ // 加载所有登录玩家
243
+ await this._loadPlayers();
244
+
245
+ // 启动资源
246
+ await this._startSources();
247
+ };
248
+
249
+ /**
250
+ * 获取玩家对象
251
+ * @param {string} player_id 玩家ID
252
+ * @returns {object} 玩家对象
253
+ */
254
+ Zone.prototype.getPlayer = function (player_id) {
255
+ // 参数校验
256
+ if (!player_id || typeof player_id !== 'string') {
257
+ throw new TypeError('玩家ID必须是有效字符串');
258
+ }
259
+
260
+ var player = this.players.get(player_id);
261
+ if (!player) {
262
+ throw new Error(`玩家 ${player_id} 不存在`);
263
+ }
264
+
265
+ return player;
266
+ };
267
+
268
+ /**
269
+ * 从分区移除玩家
270
+ * @param {string} player_id 玩家ID
271
+ * @returns {boolean} 是否移除成功
272
+ */
273
+ Zone.prototype.delPlayer = function (player_id) {
274
+ // 参数校验
275
+ if (!player_id || typeof player_id !== 'string') {
276
+ throw new TypeError('玩家ID必须是有效字符串');
277
+ }
278
+
279
+ var player = this.players.get(player_id);
280
+ if (!player) {
281
+ throw new Error('玩家不在分区中');
282
+ }
283
+
284
+ // 如果玩家在某个频道中,先退出频道
285
+ if (player.channel_id && this.channels.get(player.channel_id)) {
286
+ this.channels.get(player.channel_id).leave(player_id);
287
+ }
288
+
289
+ // 从分区移除玩家
290
+ this.players.delete(player_id);
291
+
292
+ // 触发玩家离开分区事件
293
+ this.emitEvent('player_leave_zone', {
294
+ player_id: player_id,
295
+ zone_name: this.config.name,
296
+ timestamp: Date.now(),
297
+ });
298
+
299
+ return true;
300
+ };
301
+
302
+ /**
303
+ * 玩家进入频道
304
+ * @param {string} player_id 玩家ID
305
+ * @param {number} channel_id 频道ID
306
+ * @returns {boolean} 是否进入成功
307
+ */
308
+ Zone.prototype.enterChannel = async function (player_id, channel_id) {
309
+ // 参数校验
310
+ if (!player_id || typeof player_id !== 'string') {
311
+ throw new TypeError('玩家ID必须是有效字符串');
312
+ }
313
+
314
+ if (!channel_id || typeof channel_id !== 'number') {
315
+ throw new TypeError('频道ID必须是有效数字');
316
+ }
317
+
318
+ // 获取玩家对象
319
+ var player = this.getPlayer(player_id);
320
+
321
+ // 检查频道是否存在
322
+ if (!this.channels.has(channel_id)) {
323
+ throw new Error(`频道 ${channel_id} 不存在`);
324
+ }
325
+
326
+ // 如果玩家已在其他频道,先退出
327
+ if (player.channel_id && this.channels.get(player.channel_id)) {
328
+ await this.channels.get(player.channel_id).leave(player_id);
329
+ }
330
+
331
+ // 玩家进入频道
332
+ await this.channels.get(channel_id).enter(player_id);
333
+
334
+ // 更新玩家频道ID
335
+ player.channel_id = channel_id;
336
+
337
+ return true;
338
+ };
339
+
340
+ /**
341
+ * 切换频道
342
+ * @param {string} player_id 玩家ID
343
+ * @param {number} channel_id 频道ID
344
+ * @returns {boolean} 是否切换成功
345
+ */
346
+ Zone.prototype.switchChannel = async function (player_id, channel_id) {
347
+ // 参数校验
348
+ if (!player_id || typeof player_id !== 'string') {
349
+ throw new TypeError('玩家ID必须是有效字符串');
350
+ }
351
+
352
+ if (!channel_id || typeof channel_id !== 'number') {
353
+ throw new TypeError('频道ID必须是有效数字');
354
+ }
355
+
356
+ // 获取玩家对象
357
+ var player = this.getPlayer(player_id);
358
+
359
+ // 检查目标频道是否存在
360
+ if (!this.channels[channel_id]) {
361
+ throw new Error(`频道 ${channel_id} 不存在`);
362
+ }
363
+
364
+ // 如果玩家已在目标频道,直接返回
365
+ if (player.channel_id === channel_id) {
366
+ return true;
367
+ }
368
+
369
+ // 退出当前频道
370
+ if (player.channel_id && this.channels.get(player.channel_id)) {
371
+ await this.channels.get(player.channel_id).leave(player_id);
372
+ }
373
+
374
+ // 进入目标频道
375
+ await this.channels.get(channel_id).enter(player_id);
376
+
377
+ // 更新玩家频道ID
378
+ player.channel_id = channel_id;
379
+
380
+ return true;
381
+ };
382
+
383
+ /**
384
+ * 加载所有登录玩家
385
+ */
386
+ Zone.prototype._loadPlayers = async function () {
387
+ var players = await this.emitWait('load_players', {
388
+ zone_name: this.config.name,
389
+ zone: this,
390
+ });
391
+ if (players && players.length > 0) {
392
+ for (var i = 0; i < players.length; i++) {
393
+ var info = players[i];
394
+ var player = this._model.player.create(info);
395
+ this.players.set(player.player_id, player);
396
+ }
397
+ }
398
+ };
399
+
400
+ /**
401
+ * 保存所有玩家数据
402
+ * @returns {Promise<object>} 保存结果
403
+ */
404
+ Zone.prototype._savePlayers = async function () {
405
+ var all_players = Array.from(this.players.values());
406
+ if (all_players.length === 0) {
407
+ return { count: 0, success: true };
408
+ }
409
+ await this.emitWait('save_players', {
410
+ zone_name: this.config.name,
411
+ zone: this,
412
+ players: all_players,
413
+ });
414
+ this.log('info', `保存所有玩家数据 ${all_players.length} 个`);
415
+ return { count: all_players.length, success: true };
416
+ };
417
+
418
+ /**
419
+ * 保存脏数据玩家
420
+ * @returns {Promise<object>} 保存结果
421
+ */
422
+ Zone.prototype._saveDirtyPlayers = async function () {
423
+ var dirty_players = [];
424
+ for (var player of this.players.values()) {
425
+ if (player.isDirty && player.isDirty()) {
426
+ dirty_players.push(player);
427
+ }
428
+ }
429
+ if (dirty_players.length === 0) {
430
+ return { count: 0, success: true };
431
+ }
432
+ await this.emitWait('save_players', {
433
+ zone_name: this.config.name,
434
+ zone: this,
435
+ players: dirty_players,
436
+ });
437
+ for (var i = 0; i < dirty_players.length; i++) {
438
+ dirty_players[i].isDirty(false);
439
+ }
440
+ this.log('debug', `保存脏数据玩家 ${dirty_players.length} 个`);
441
+ return { count: dirty_players.length, success: true };
442
+ };
443
+
444
+ /**
445
+ * 全分区数据发送
446
+ * @param {object} data 发送数据
447
+ * @param {string} msg_type 消息类型
448
+ * @param {string} msg_group 消息范围
449
+ * @param {string} sender_id 发送者ID
450
+ * @returns {object} 发送结果
451
+ */
452
+ Zone.prototype.sendData = async function (
453
+ data,
454
+ msg_type = 'system',
455
+ msg_group = 'world',
456
+ sender_id = 'system',
457
+ ) {
458
+ // 参数校验
459
+ if (!data || typeof data !== 'object') {
460
+ throw new TypeError('发送数据必须是有效对象');
461
+ }
462
+
463
+ // 创建发送对象
464
+ var msg = {
465
+ data: data,
466
+ msg_type: msg_type,
467
+ msg_group: msg_group,
468
+ sender_id: sender_id,
469
+ timestamp: Date.now(),
470
+ zone_name: this.config.name,
471
+ msg_id: this._generateMsgId(),
472
+ };
473
+
474
+ // 触发全分区数据发送事件,由业务层处理具体的发送逻辑
475
+ this.emitEvent('zone_data_send', {
476
+ ...msg,
477
+ players: this.players.size,
478
+ });
479
+
480
+ this.log('debug', `全分区数据发送事件已触发: 玩家数 ${this.players.size}`);
481
+
482
+ // 返回默认结果
483
+ return {
484
+ players: this.players.size,
485
+ success_count: 0,
486
+ fail_count: 0,
487
+ failed_players: [],
488
+ };
489
+ };
490
+
491
+ /**
492
+ * 生成发送ID
493
+ * @returns {string} 发送ID
494
+ */
495
+ Zone.prototype._generateMsgId = function () {
496
+ return idGen.genId(this.config.name);
497
+ };
498
+
499
+ /**
500
+ * 频道数据发送
501
+ * @param {number} channel_id 频道ID
502
+ * @param {object} data 发送数据
503
+ * @param {string} msg_type 消息类型
504
+ * @param {string} msg_group 消息范围
505
+ * @param {string} sender_id 发送者ID
506
+ * @returns {object} 发送结果
507
+ */
508
+ Zone.prototype.sendToChannel = async function (
509
+ channel_id,
510
+ data,
511
+ msg_type = 'text',
512
+ msg_group = 'channel',
513
+ sender_id = 'system',
514
+ ) {
515
+ // 参数校验
516
+ if (!channel_id || typeof channel_id !== 'number') {
517
+ throw new TypeError('频道ID必须是有效数字');
518
+ }
519
+
520
+ if (!data || typeof data !== 'object') {
521
+ throw new TypeError('发送数据必须是有效对象');
522
+ }
523
+
524
+ try {
525
+ // 检查频道是否存在
526
+ var channel = this.channels.get(channel_id);
527
+ if (!channel) {
528
+ throw new Error(`频道 ${channel_id} 不存在`);
529
+ }
530
+
531
+ // 创建发送对象
532
+ var send_obj = {
533
+ data: data,
534
+ msg_type: msg_type,
535
+ msg_group: msg_group,
536
+ sender_id: sender_id,
537
+ timestamp: Date.now(),
538
+ channel_id: channel_id,
539
+ zone_name: this.config.name,
540
+ msg_id: this._generateMsgId(),
541
+ };
542
+
543
+ // 触发频道数据发送事件,由业务层处理具体的发送逻辑
544
+ this.emitEvent('channel_send', {
545
+ ...send_obj,
546
+ });
547
+
548
+ return {
549
+ players: channel.getPlayerNum(),
550
+ success: 0,
551
+ fail: 0,
552
+ failed_players: [],
553
+ };
554
+ } catch (error) {
555
+ this.log('error', `频道数据发送失败: `, error);
556
+ throw error;
557
+ }
558
+ };
559
+
560
+ /**
561
+ * 向指定玩家发送数据
562
+ * @param {string} player_id 玩家ID
563
+ * @param {object} data 发送数据
564
+ * @param {string} msg_type 消息类型
565
+ * @param {string} msg_group 消息范围
566
+ * @param {string} sender_id 发送者ID
567
+ * @returns {boolean} 是否发送成功
568
+ */
569
+ Zone.prototype.sendToPlayer = async function (
570
+ player_id,
571
+ data,
572
+ msg_type = 'text',
573
+ msg_group = 'private',
574
+ sender_id = 'system',
575
+ ) {
576
+ // 参数校验
577
+ if (!player_id || typeof player_id !== 'string') {
578
+ throw new TypeError('玩家ID必须是有效字符串');
579
+ }
580
+
581
+ if (!data || typeof data !== 'object') {
582
+ throw new TypeError('发送数据必须是有效对象');
583
+ }
584
+
585
+ try {
586
+ // 获取玩家对象
587
+ var player = this.getPlayer(player_id);
588
+ if (!player) {
589
+ throw new Error(`玩家 ${player_id} 不存在`);
590
+ }
591
+
592
+ // 触发私聊数据发送事件,由业务层处理具体的发送逻辑
593
+ this.emitEvent('private_send', {
594
+ data: data,
595
+ msg_type: msg_type,
596
+ msg_group: msg_group,
597
+ sender_id: sender_id,
598
+ timestamp: Date.now(),
599
+ player_id: player_id,
600
+ zone_name: this.config.name,
601
+ msg_id: this._generateMsgId(),
602
+ });
603
+
604
+ return true;
605
+ } catch (error) {
606
+ this.log('error', `向玩家 ${player_id} 发送数据失败: `, error);
607
+ throw error;
608
+ }
609
+ };
610
+
611
+ /**
612
+ * 条件数据发送
613
+ * @param {Function} func 筛选函数
614
+ * @param {object} data 发送数据
615
+ * @param {string} msg_type 消息类型
616
+ * @param {string} msg_group 消息范围
617
+ * @param {string} sender_id 发送者ID
618
+ * @returns {object} 发送结果
619
+ */
620
+ Zone.prototype.sendWithCondition = async function (
621
+ func,
622
+ data,
623
+ msg_type = 'system',
624
+ msg_group = 'world',
625
+ sender_id = 'system',
626
+ ) {
627
+ // 参数校验
628
+ if (typeof func !== 'function') {
629
+ throw new TypeError('筛选函数必须是有效函数');
630
+ }
631
+
632
+ if (!data || typeof data !== 'object') {
633
+ throw new TypeError('发送数据必须是有效对象');
634
+ }
635
+
636
+ try {
637
+ // 触发条件数据发送事件,由业务层处理具体的发送逻辑
638
+ this.emitEvent('conditional_send', {
639
+ data: data,
640
+ msg_type: msg_type,
641
+ msg_group: msg_group,
642
+ sender_id: sender_id,
643
+ timestamp: Date.now(),
644
+ zone_name: this.config.name,
645
+ msg_id: this._generateMsgId(),
646
+ condition: func.toString(),
647
+ players: this.players.size,
648
+ });
649
+
650
+ // 返回默认结果
651
+ return {
652
+ players: this.players.size,
653
+ filtered_players: 0,
654
+ success_count: 0,
655
+ fail_count: 0,
656
+ failed_players: [],
657
+ };
658
+ } catch (error) {
659
+ this.log('error', `条件数据发送失败: `, error);
660
+ throw error;
661
+ }
662
+ };
663
+
664
+ /**
665
+ * 获取分区统计信息
666
+ * @returns {object} 分区统计信息
667
+ */
668
+ Zone.prototype.getStats = function () {
669
+ var channel_stats = {};
670
+ var players = 0;
671
+
672
+ // 统计各频道玩家数量
673
+ for (var channel_id in this.channels) {
674
+ if (this.channels[channel_id] && this.channels[channel_id].getStats) {
675
+ var stats = this.channels[channel_id].getStats();
676
+ channel_stats[channel_id] = stats;
677
+ players += stats.player || 0;
678
+ }
679
+ }
680
+
681
+ return {
682
+ zone_name: this.config.name,
683
+ players: this.players.size,
684
+ online: players,
685
+ channels: this.channels.size,
686
+ channel_stats: channel_stats,
687
+ current_time: Date.now(),
688
+ };
689
+ };
690
+
691
+ /**
692
+ * 添加玩家到分区
693
+ * @param {object} player 玩家对象
694
+ * @returns {object} 玩家对象
695
+ */
696
+ Zone.prototype.addPlayer = function (player) {
697
+ this.players.set(player.player_id, player);
698
+ // 触发玩家加入分区事件
699
+ this.emitEvent('player_enter_zone', {
700
+ player_id: player.player_id,
701
+ zone_name: this.config.name,
702
+ timestamp: Date.now(),
703
+ });
704
+ return player;
705
+ };
706
+
707
+ /**
708
+ * 获取频道对象
709
+ * @param {string} channel_id 频道ID
710
+ * @returns {object} 频道对象
711
+ */
712
+ Zone.prototype.getChannel = function (channel_id) {
713
+ return this.channels.get(channel_id);
714
+ };
715
+
716
+ /**
717
+ * 获取玩家所在频道
718
+ * @param {string} player_id 玩家ID
719
+ * @returns {object} 频道对象
720
+ */
721
+ Zone.prototype.getChannelByPlayerId = function (player_id) {
722
+ var player = this.players.get(player_id);
723
+ if (player) {
724
+ return this.channels.get(player.channel_id);
725
+ }
726
+ return null;
727
+ };
728
+
729
+ /**
730
+ * 销毁分区
731
+ * @returns {Promise<void>}
732
+ */
733
+ Zone.prototype._destroy = async function () {
734
+ await this._savePlayers();
735
+ for (var channel of this.channels.values()) {
736
+ if (channel.do) {
737
+ await channel.do('destroy');
738
+ }
739
+ }
740
+ this.players.clear();
741
+ this.channels.clear();
742
+ };
743
+
744
+ exports.Zone = Zone;