├── README.md ├── app ├── config.json ├── lib │ ├── YunDingOnlineSDK.js │ ├── protocol.js │ └── require-data.js ├── main.js ├── regHooks.js └── ydComponents.js ├── assets └── style.css ├── auto_login.user.js ├── index.html ├── templates └── main.html └── 云顶修仙-数据包格式.md /README.md: -------------------------------------------------------------------------------- 1 | # 云顶修仙 SDK 2 | 3 | 游戏地址:[http://yundingxx.com:3366/](http://yundingxx.com:3366/) 4 | 5 | SDK 主文件为 `app/lib/YunDingOnlineSDK.js` 6 | -------------------------------------------------------------------------------- /app/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "core": { 3 | "ext_url": "https://github.com/imdong/YunDingOnlineSDK/raw/master/auto_login.user.js", 4 | "official_login": "http://yundingxx.com:3366/login" 5 | }, 6 | "maps": [ 7 | null, 8 | { 9 | "id": 1, 10 | "name": "聚灵城", 11 | "is_city": true, 12 | "up": [], 13 | "next": [ 14 | 2, 15 | 3 16 | ] 17 | }, 18 | { 19 | "id": 2, 20 | "name": "梵风岭", 21 | "is_city": false, 22 | "up": [ 23 | 1 24 | ], 25 | "next": [ 26 | 4 27 | ] 28 | }, 29 | { 30 | "id": 3, 31 | "name": "雨落道", 32 | "is_city": false, 33 | "up": [ 34 | 1 35 | ], 36 | "next": [ 37 | 5 38 | ] 39 | }, 40 | { 41 | "id": 4, 42 | "name": "梵风岭-东", 43 | "is_city": false, 44 | "up": [ 45 | 2 46 | ], 47 | "next": [] 48 | }, 49 | { 50 | "id": 5, 51 | "name": "雨落东城", 52 | "is_city": true, 53 | "up": [ 54 | 3 55 | ], 56 | "next": [ 57 | 6, 58 | 7, 59 | 8, 60 | 9 61 | ] 62 | }, 63 | { 64 | "id": 6, 65 | "name": "追风谷", 66 | "is_city": false, 67 | "up": [ 68 | 5 69 | ], 70 | "next": [ 71 | 4 72 | ] 73 | }, 74 | { 75 | "id": 7, 76 | "name": "通天道", 77 | "is_city": false, 78 | "up": [ 79 | 5 80 | ], 81 | "next": [ 82 | 10 83 | ] 84 | }, 85 | { 86 | "id": 8, 87 | "name": "长树林海", 88 | "is_city": false, 89 | "up": [ 90 | 5 91 | ], 92 | "next": [ 93 | 11 94 | ] 95 | }, 96 | { 97 | "id": 9, 98 | "name": "玉花县", 99 | "is_city": true, 100 | "up": [ 101 | 5 102 | ], 103 | "next": [ 104 | 12 105 | ] 106 | }, 107 | { 108 | "id": 10, 109 | "name": "天都城", 110 | "is_city": true, 111 | "up": [ 112 | 7 113 | ], 114 | "next": [ 115 | 13 116 | ] 117 | }, 118 | { 119 | "id": 11, 120 | "name": "林中栈道", 121 | "is_city": false, 122 | "up": [ 123 | 8 124 | ], 125 | "next": [] 126 | }, 127 | { 128 | "id": 12, 129 | "name": "栀花荒野", 130 | "is_city": false, 131 | "up": [ 132 | 9 133 | ], 134 | "next": [] 135 | }, 136 | { 137 | "id": 13, 138 | "name": "九幽-西", 139 | "is_city": false, 140 | "up": [ 141 | 10 142 | ], 143 | "next": [ 144 | 14 145 | ] 146 | }, 147 | { 148 | "id": 14, 149 | "name": "九幽殿", 150 | "is_city": true, 151 | "up": [ 152 | 13 153 | ], 154 | "next": [ 155 | 15, 156 | 16 157 | ] 158 | }, 159 | { 160 | "id": 15, 161 | "name": "九幽-东", 162 | "is_city": true, 163 | "up": [ 164 | 14 165 | ], 166 | "next": [] 167 | }, 168 | { 169 | "id": 16, 170 | "name": "聚仙驿站", 171 | "is_city": true, 172 | "up": [ 173 | 10 174 | ], 175 | "next": [ 176 | 14 177 | ] 178 | } 179 | ] 180 | } 181 | -------------------------------------------------------------------------------- /app/lib/YunDingOnlineSDK.js: -------------------------------------------------------------------------------- 1 | define(['protocol'], function (Protocol) { 2 | // 调试模式 3 | let DEBUG = false; 4 | 5 | /** 6 | * 一些常量 7 | */ 8 | const JS_WS_CLIENT_TYPE = 'js-websocket'; 9 | const JS_WS_CLIENT_VERSION = '0.0.1'; 10 | 11 | /** 12 | * 返回消息状态码 13 | */ 14 | const RES_OK = 200; // 成功 15 | const RES_FAIL = 500; // 失败 16 | const RES_OLD_CLIENT = 501; // 客户端版本不符 17 | 18 | const gapThreshold = 100; // heartbeat gap threashold 19 | 20 | /** 21 | * {object} 默认配置 22 | */ 23 | let default_config = { 24 | gameHost: 'yundingxx.com', 25 | gameProtocol: 'ws', 26 | loginPort: 3014, 27 | gamePort: 3052, 28 | loginServer: null, 29 | gameServer: null, 30 | socketPathTpl: '{protocol}://{host}:{port}/' 31 | }; 32 | 33 | let Package = Protocol.Package, 34 | Message = Protocol.Message; 35 | 36 | /** 37 | * 工具类 38 | */ 39 | let Tool = { 40 | /** 41 | * 替换字符串中的变量占位符 42 | * 43 | * @param {string} str 需要替换的字符串 44 | * @param {object} replaceMaps 替换关系 45 | */ 46 | replaceParams: function (str, replaceMaps) { 47 | return str.replace(/{([^}]+)}/g, function (full_match, key) { 48 | return replaceMaps[key] || full_match; 49 | }); 50 | } 51 | } 52 | 53 | /** 54 | * Hooks 默认的 55 | */ 56 | let CoreHooks = { 57 | /** 58 | * 登录游戏成功回调 59 | * @param {*} data 60 | */ 61 | onLoginGame: function (data) { 62 | if (data.code != RES_OK) { 63 | DEBUG && console.error('onLoginGame', data.msg); 64 | return; 65 | } 66 | 67 | this.user_info = Object.assign(this.user_info, data.data); 68 | }, 69 | /** 70 | * 主要是用于保存数据 71 | * @param {*} data 72 | */ 73 | onSaveData: function (data) { 74 | if (data.code != RES_OK) { 75 | DEBUG && console.error('onLoginGame', data.msg || data); 76 | return; 77 | } 78 | 79 | let datas = data.data || data, 80 | save_keys = ['map', 'myInfo', 'players', 'userTasks', 'wordLogds', 'team', 'teams', 'screens', 'initData']; 81 | save_keys.forEach((key) => { 82 | if (!datas[key]) { 83 | return; 84 | } 85 | this.user_info[key] = datas[key]; 86 | 87 | if (key == 'map') { 88 | this.user_info.mid = datas.map.id; 89 | } 90 | }) 91 | } 92 | } 93 | 94 | // 给Hook绑定 Mark 95 | Object.keys(CoreHooks).forEach((name) => { 96 | CoreHooks[name].hookMark = 'Core.' + name; 97 | }); 98 | 99 | /** 100 | * YunDingXX Online API 101 | * 102 | * @param {object} config 配置信息 103 | */ 104 | let GameApi = function (config) { 105 | // 加载配置 106 | if ('object' == typeof config || 'undefined' == typeof config) { 107 | this.config = Object.assign(default_config, config); 108 | } 109 | 110 | // 初始化配置 111 | this.initConfig(); 112 | }; 113 | 114 | // 设置调试模式 115 | GameApi.isDEBUG = function () { 116 | DEBUG = true; 117 | } 118 | 119 | /** 120 | * 初始化配置信息 121 | */ 122 | GameApi.prototype.initConfig = function () { 123 | // WebSocket 链接对象 124 | this.socket = null; 125 | 126 | // 用于标记自己要停止 127 | this.isStop = false; 128 | 129 | // 玩家信息都存在这里 渲染视图请读取这里 130 | this.email = ''; 131 | this.password = ''; // 用于自动重连 132 | this.user_info = {}; 133 | 134 | // 登录 Token 我猜你不会偷这个吧? 135 | this.token = null; 136 | 137 | // 生成服务器链接地址 138 | if (!this.config.loginServer) { 139 | this.config.loginServer = this.getSocketServer(this.config.loginPort); 140 | } 141 | 142 | // 消息处理分发 143 | this.messageHandlers = []; 144 | this.messageHandlers[Package.TYPE_HANDSHAKE] = this.onHandshake; 145 | this.messageHandlers[Package.TYPE_HEARTBEAT] = this.onHeartbeat; 146 | this.messageHandlers[Package.TYPE_DATA] = this.onData; 147 | this.messageHandlers[Package.TYPE_KICK] = this.onKick; 148 | 149 | // 维持心跳 150 | this.reqId = 1; 151 | this.heartbeatInterval = 0; 152 | this.heartbeatId = null; 153 | this.heartbeatTimeoutId = null; 154 | this.heartbeatTimeout = 0; 155 | this.xyUpdateIntervalId = 0; 156 | 157 | // 路由字典 158 | this.dict = {}; 159 | this.abbrs = {}; 160 | 161 | // 协议相关??? 162 | this.protoVersion = 0; 163 | this.serverProtos = {}; 164 | this.clientProtos = {}; 165 | 166 | // 一些无法命名的回调 167 | this.initCallback = null; // 初始化完毕的回调 168 | this.callbacks = []; // 根据 SeqId 来区分回调(如果有) 169 | this.callRoutes = []; // 和上面一样 只是区分 Route 170 | 171 | // 注册各路由的回调钩子 172 | this.hookHandlers = GameApi.regHookHandlers; 173 | } 174 | 175 | /** 176 | * 用于批量提前注册,避免每次创建都要注册一次 177 | */ 178 | GameApi.regHookHandlers = { 179 | "onAdd": [], //* 玩家上线? 180 | "onLeave": [], //* 玩家离开 181 | "onChatMsg": [], //* 收到消息 182 | "onMyTeamReload": [ // 重新载入队伍 183 | CoreHooks.onSaveData 184 | ], 185 | "onStartBat": [ // 战斗开始 186 | CoreHooks.onSaveData 187 | ], 188 | "onRoundBatEnd": [ // 战斗结束 189 | 190 | ], 191 | "chat.chatHandler.send": [], // 发送消息 192 | "connector.entryHandler.enter": [], 193 | "connector.fationHandler.applyForFation": [], // 申请工会 194 | "connector.fationHandler.closeUserTask": [], // 放弃任务 195 | "connector.fationHandler.createFation": [], // 创建工会 196 | "connector.fationHandler.donateFationFunds": [], // 捐赠 197 | "connector.fationHandler.doneFationApply": [], // 同意入会 198 | "connector.fationHandler.getFationApply": [], // 查看申请 199 | "connector.fationHandler.getFationList": [], // 打开工会列表 200 | "connector.fationHandler.getFationTask": [], // 领取任务 201 | "connector.fationHandler.initFation": [], // 初始聚仙阁楼页面 202 | "connector.fationHandler.leaveFation": [], // 脱离工会 203 | "connector.fationHandler.showFationUserList": [], // 查看工会人员 204 | "connector.fationHandler.upFation": [], // 升级工会 205 | "connector.fationHandler.upFationUserSkill": [], // 点技能 206 | "connector.fationHandler.upUserFationLevel": [], // 升降职位 207 | "connector.loginHandler.login": [ // 登录 Token 208 | CoreHooks.onSaveData 209 | ], 210 | "connector.playerHandler.byGoodsToSystem": [], // 购买系统物品 211 | "connector.playerHandler.byPalyerGoods": [], // 购买玩家物品 212 | "connector.playerHandler.getCopyTask": [], 213 | "connector.playerHandler.getPlayerSellGoods": [], // 初始仙坊集市 214 | "connector.playerHandler.init": [], 215 | "connector.playerHandler.move": [], // 移动 216 | "connector.playerHandler.moveToNewMap": [ // 移动至新地图 217 | CoreHooks.onSaveData 218 | ], 219 | "connector.playerHandler.nextMap": [], // 切换地图 220 | "connector.playerHandler.payUserTask": [], // 完成任务 221 | "connector.playerHandler.sellGoods": [], 222 | "connector.playerHandler.sendMsg": [], 223 | "connector.playerHandler.wearUserEquipment": [], // 佩戴拆卸装备 224 | "connector.systemHandler.getRankList": [], // 排行榜 225 | "connector.systemHandler.getSystemSellGoods": [], // 初始系统中出售物品 226 | "connector.systemHandler.getSystemTask": [], // 初始任务中心 227 | "connector.teamHandler.addTeam": [], // 加入队伍 228 | "connector.teamHandler.createdTeam": [ // // 创建队伍 229 | CoreHooks.onSaveData 230 | ], 231 | "connector.teamHandler.getAllCombatScreen": [ // 获取战斗场景 232 | CoreHooks.onSaveData 233 | ], 234 | "connector.teamHandler.getTeamList": [ // 获取队伍列表 235 | CoreHooks.onSaveData 236 | ], 237 | "connector.teamHandler.leaveTeam": [], // 离开队伍 238 | "connector.teamHandler.roundOperating": [], // 回合操作 239 | "connector.teamHandler.showMyTeam": [], // 显示我的团队 240 | "connector.teamHandler.startCombat": [], 241 | "connector.teamHandler.switchCombatScreen": [], // 切换战斗场景 242 | "connector.userHandler.addUserPetSkill": [], // 添加用户宠物技能 243 | "connector.userHandler.allSellGoods": [], // 整理 244 | "connector.userHandler.allocationPoint": [], // 保存/分配属性点 245 | "connector.userHandler.fbProcess": [], // 法宝点击 246 | "connector.userHandler.fitPet": [], // 确认合成 247 | "connector.userHandler.getMyFb": [], // 获取我的法宝 248 | "connector.userHandler.getMyGoods": [], // 初始我得物品 249 | "connector.userHandler.getMyPet": [], // 初始我得宠物 250 | "connector.userHandler.getMyPetSkillGoods": [], // 获取我的宠物用品 251 | "connector.userHandler.getMySkill": [], // 初始我得技能 252 | "connector.userHandler.getMyTitle": [], // 称号弹框 253 | "connector.userHandler.getMylogs": [], // 查看我的日志 254 | "connector.userHandler.getUserEqs": [], // 获取我得装备列表 255 | "connector.userHandler.getUserTask": [], 256 | "connector.userHandler.makeGoods": [], // 合成物品 257 | "connector.userHandler.playUserPet": [], // 出战宠物 258 | "connector.userHandler.polyLin": [], // 聚灵 259 | "connector.userHandler.repairUserArms": [], // 修炼装备 260 | "connector.userHandler.resetAttribute": [], // 重置属性 261 | "connector.userHandler.selectMyTitle": [], // 选择我的称号 262 | "connector.userHandler.sellGoods": [], 263 | "connector.userHandler.shelfMyGoods": [], // 取回到背包 264 | "connector.userHandler.turnIntoPet": [], // 幻化宠物 265 | "connector.userHandler.upPlayerLevel": [], 266 | "connector.userHandler.upUserPetLevel": [], // 升级宠物 267 | "connector.userHandler.updateUserPrice": [], // 更新玩家货币 268 | "connector.userHandler.useGoods": [], // 使用物品 269 | "connector.userHandler.userInfo": [], 270 | "connector.userHandler.wbt": [], // 挖宝图 271 | "connector.userHandler.xyDuiHuan": [], 272 | "connector.userHandler.xyUpdate": [ // // 仙蕴提交 273 | CoreHooks.onSaveData 274 | ], 275 | "gate.gateHandler.queryEntry": [] // *登录游戏 276 | }; 277 | 278 | /** 279 | * 生成服务器链接地址 280 | * @param {*} port 281 | */ 282 | GameApi.prototype.getSocketServer = function (port, host, protocol) { 283 | if ('number' != typeof port) { 284 | return null; 285 | } 286 | 287 | return Tool.replaceParams(this.config.socketPathTpl, { 288 | protocol: protocol || this.config.gameProtocol, 289 | host: host || this.config.gameHost, 290 | port: port 291 | }); 292 | } 293 | 294 | /** 295 | * 注册路由回调钩子 296 | * @param {string} route 挂载的路由地址 297 | * @param {function} cb 回调 298 | * @param {string} position 用于 触发时机 暂不可用 299 | */ 300 | GameApi.prototype.regHookHandler = function (route, cb, position) { 301 | // 检查 route 和 cn 是否有效 302 | if (!this.hookHandlers[route] || 'function' != typeof cb) { 303 | return false; 304 | } 305 | 306 | // 如果有 Mark 就尝试去重 307 | let cbMark = cb.hookMark || null; 308 | if (cbMark && 'object' == typeof this.hookHandlers[route]) { 309 | for (let i = 0; i < this.hookHandlers[route].length; i++) { 310 | const cb_item = this.hookHandlers[route][i]; 311 | if (cb_item.hookMark && cbMark == cb_item.hookMark) { 312 | return cb_item; 313 | } 314 | } 315 | } 316 | 317 | // 追加到结果 318 | this.hookHandlers[route].push(cb); 319 | 320 | return true; 321 | } 322 | 323 | /** 324 | * 启动实例 325 | */ 326 | GameApi.prototype.start = function () { 327 | if (this.socket) { 328 | return; 329 | } 330 | // 建立 ws 链接 331 | this.createWebSocket(this.config.loginServer); 332 | } 333 | 334 | /** 335 | * 创建一个 WebSocket 连接并初始化 336 | * 337 | * @param {string} url 服务地址 ws://host:prot/path 338 | */ 339 | GameApi.prototype.createWebSocket = function (url) { 340 | // 创建对象 341 | let socket = new WebSocket(url); 342 | 343 | // 指定传输数据类型 344 | socket.binaryType = 'arraybuffer'; 345 | 346 | /** 347 | * 批量绑定相关回调事件 348 | */ 349 | let bindEvents = { 350 | open: this.onOpen, 351 | message: this.onMessage, 352 | error: this.onError, 353 | close: this.onClose 354 | }; 355 | 356 | // 遍历事件列表 添加 绑定 357 | Object.keys(bindEvents).map((enevtName) => { 358 | socket.addEventListener(enevtName, (event) => { 359 | bindEvents[enevtName].call(this, event); 360 | }); 361 | }); 362 | 363 | // 将 reqId 重置 364 | this.reqId = 1; 365 | this.socket = socket; 366 | 367 | return socket; 368 | } 369 | 370 | /** 371 | * WebSocket 建立连接的回调 372 | * 373 | * @param {object} event 374 | */ 375 | GameApi.prototype.onOpen = function (event) { 376 | // 发送握手包 377 | this.handShake(); 378 | }; 379 | 380 | /** 381 | * WebSocket 收到消息时的回调 382 | * @param {object} event 383 | */ 384 | GameApi.prototype.onMessage = function (event) { 385 | let messages = Package.decode(event.data); 386 | if (messages.type != 3) { 387 | // console.log('onMessage', messages); 388 | } 389 | 390 | // 根据事件类型分发到对应的处理程序 391 | if (Array.isArray(messages)) { 392 | for (let i = 0; i < messages.length; i++) { 393 | let msg = messages[i]; 394 | this.messageHandlers.call(this, [msg.type](msg.body)); 395 | } 396 | } else { 397 | this.messageHandlers[messages.type].call(this, messages.body); 398 | } 399 | 400 | // 重新计算心跳包时间 401 | if (this.heartbeatTimeout) { 402 | this.nextHeartbeatTimeout = Date.now() + this.heartbeatTimeout; 403 | } 404 | }; 405 | 406 | /** 407 | * WebSocket 报错时的回调 408 | * 409 | * @param {object} event 410 | */ 411 | GameApi.prototype.onError = function (event) { 412 | DEBUG && console.log('onError', event); 413 | }; 414 | 415 | /** 416 | * WebSocket 断开时的回调 417 | * 418 | * @param {object} event 419 | */ 420 | GameApi.prototype.onClose = function (event) { 421 | DEBUG && console.log('onClose', event, this.isStop); 422 | if (this.isStop) { 423 | return; 424 | } 425 | 426 | console.log('onClose login') 427 | // 重连 428 | this.login(); 429 | }; 430 | 431 | /** 432 | * 发送消息到服务器(自动处理编码信息) 433 | * 434 | * @param {object} data 435 | * @param {string} type 436 | */ 437 | GameApi.prototype.sendMessage = function (data, route, cb) { 438 | // 分析参数 439 | if (arguments.length == 2 && 'function' == typeof route) { 440 | cb = route; 441 | route = null; 442 | } 443 | 444 | // 解包消息 445 | let bytes = Protocol.strencode(JSON.stringify(data)), 446 | pkg_type = Package.TYPE_HANDSHAKE, 447 | routeId = null; 448 | 449 | // 分析路由情况 450 | if ('string' == typeof route) { 451 | // 是否压缩路由 (存在 Dict 的就压缩) 452 | let compressRoute = 0; 453 | if (this.dict && this.dict[route]) { 454 | routeId = this.dict[route]; 455 | compressRoute = 1; 456 | } 457 | 458 | // 然后生成 Message 459 | let reqId = this.reqId++ % 255; 460 | let type = reqId ? Message.TYPE_REQUEST : Message.TYPE_NOTIFY; 461 | 462 | // 设置回调 463 | if ('function' == typeof cb || 'string' == typeof cb) { 464 | this.callbacks[reqId] = cb; 465 | } 466 | 467 | // 设置回调路由表 468 | this.callRoutes[reqId] = route; 469 | 470 | // 将消息打包 471 | bytes = Message.encode(reqId, type, compressRoute, compressRoute ? routeId : route, bytes); 472 | pkg_type = Package.TYPE_DATA; 473 | } 474 | 475 | // 封装到最终格式 476 | let packet = Package.encode(pkg_type, bytes); 477 | 478 | // 发出封包 这里应该有发送失败的处理 479 | this.socket.send(packet); 480 | } 481 | 482 | /** 483 | * 处理握手返回包 484 | * 485 | * @param {*} data 486 | */ 487 | GameApi.prototype.onHandshake = function (data) { 488 | // 解码数据 489 | data = JSON.parse(Protocol.strdecode(data)); 490 | DEBUG && console.log('onHandshake', data); 491 | 492 | // 报错的话 493 | if (data.code === RES_OLD_CLIENT) { 494 | throw new Error('client version not fullfill'); 495 | return; 496 | } 497 | 498 | // 其他错误 499 | if (data.code !== RES_OK) { 500 | throw new Error('handshake fail'); 501 | return; 502 | } 503 | 504 | // 要发送握手回复 505 | let packet = Package.encode(Package.TYPE_HANDSHAKE_ACK); 506 | this.socket.send(packet); 507 | 508 | // 保存心跳时间 509 | if (data.sys && data.sys.heartbeat) { 510 | // 超时信息 511 | this.heartbeatInterval = data.sys.heartbeat * 1000; // heartbeat interval 512 | this.heartbeatTimeout = this.heartbeatInterval * 2; // max heartbeat timeout 513 | 514 | // 保存字典 515 | let dict = data.sys.dict; 516 | 517 | // Init compress dict 518 | if (dict) { 519 | this.dict = dict; 520 | this.abbrs = {}; 521 | 522 | for (let route in dict) { 523 | this.abbrs[dict[route]] = route; 524 | } 525 | } 526 | } 527 | 528 | // 保存 protos 信息 529 | if (data.sys && data.sys.protos) { 530 | let protos = data.sys.protos; 531 | 532 | // Init protobuf protos 533 | if (protos) { 534 | this.protoVersion = protos.version || 0; 535 | this.serverProtos = protos.server || {}; 536 | this.clientProtos = protos.client || {}; 537 | } 538 | } 539 | 540 | // if (typeof handshakeCallback === 'function') { 541 | // handshakeCallback(data.user); 542 | // } 543 | 544 | // 握手成功有回调的话 545 | if (this.initCallback) { 546 | this.initCallback.call(this); 547 | this.initCallback = null; 548 | } 549 | }; 550 | 551 | /** 552 | * 保持心跳 553 | */ 554 | GameApi.prototype.onHeartbeat = function () { 555 | if (!this.heartbeatInterval) { 556 | // no heartbeat 557 | return; 558 | } 559 | 560 | let packet = Package.encode(Package.TYPE_HEARTBEAT); 561 | if (this.heartbeatTimeoutId) { 562 | clearTimeout(this.heartbeatTimeoutId); 563 | this.heartbeatTimeoutId = null; 564 | } 565 | 566 | if (this.heartbeatId) { 567 | // already in a heartbeat interval 568 | return; 569 | } 570 | this.heartbeatId = setTimeout(() => { 571 | this.heartbeatId = null; 572 | this.socket.send(packet);; 573 | 574 | this.nextHeartbeatTimeout = Date.now() + this.heartbeatTimeout; 575 | this.heartbeatTimeoutId = setTimeout(this.heartbeatTimeoutCb, this.heartbeatTimeout); 576 | }, this.heartbeatInterval); 577 | }; 578 | 579 | /** 580 | * 心跳超时的回调 581 | */ 582 | GameApi.prototype.heartbeatTimeoutCb = function () { 583 | let gap = this.nextHeartbeatTimeout - Date.now(); 584 | if (gap > gapThreshold) { 585 | this.heartbeatTimeoutId = setTimeout(this.heartbeatTimeoutCb, gap); 586 | } else { 587 | DEBUG && console.error('server heartbeat timeout'); 588 | } 589 | }; 590 | 591 | /** 592 | * 收到数据回复 593 | * 594 | * @param {*} data 595 | */ 596 | GameApi.prototype.onData = function (data) { 597 | let msg = Message.decode(data); 598 | 599 | // 从发包请求中取回 Route 600 | if (!msg.route && msg.id && this.callRoutes[msg.id]) { 601 | msg.route = this.callRoutes[msg.id]; 602 | } 603 | 604 | // 解码 Body 605 | msg.body = JSON.parse(Protocol.strdecode(msg.body)); 606 | 607 | // 检查是否有回调 608 | let cbMark = [], is_call = false, is_bubble = true; 609 | if ('function' == typeof this.callbacks[msg.id]) { 610 | let cb = this.callbacks[msg.id]; 611 | delete this.callbacks[msg.id]; 612 | if ('string' == typeof cb.hookMark) { 613 | cbMark.push(cb.hookMark); 614 | } 615 | 616 | is_call = true; 617 | is_bubble = !(cb.call(this, msg.body) === false); 618 | } 619 | 620 | // 检查钩子 621 | if (is_bubble && msg.route && 'object' == typeof this.hookHandlers[msg.route]) { 622 | for (let i = 0; i < this.hookHandlers[msg.route].length; i++) { 623 | const cb = this.hookHandlers[msg.route][i]; 624 | if ('string' == typeof cb.hookMark) { 625 | if (cbMark.indexOf(cb.hookMark) >= 0) { 626 | continue; 627 | } 628 | cbMark.push(cb.hookMark); 629 | } 630 | 631 | is_call = true; 632 | is_bubble = !(cb.call(this, msg.body) === false); 633 | if (is_bubble) continue; 634 | } 635 | } 636 | 637 | // 如果没有人接收 就打印出来 638 | if (!is_call) { 639 | DEBUG && console.log('onData', msg.route, msg.body); 640 | } 641 | }; 642 | 643 | /** 644 | * 被踢么? 645 | * 646 | * @param {*} data 647 | */ 648 | GameApi.prototype.onKick = function (data) { 649 | DEBUG && console.log('onKick', data); 650 | }; 651 | 652 | /** 653 | * 停止进程 654 | */ 655 | GameApi.prototype.Stop = function () { 656 | // 标记自己要停止了 657 | this.isStop = true; 658 | 659 | // 如果都没建立 就不存在退出了 660 | if (!this.socket) { 661 | return; 662 | } 663 | 664 | // 断开 WS 链接 665 | this.socket.close(); 666 | 667 | // 停止心跳 668 | if (this.heartbeatId) { 669 | clearInterval(this.heartbeatId) 670 | } 671 | if (this.heartbeatTimeoutId) { 672 | clearTimeout(this.heartbeatTimeoutId); 673 | } 674 | if (this.xyUpdateIntervalId) { 675 | clearInterval(this.xyUpdateIntervalId) 676 | } 677 | } 678 | 679 | //======= 下面是游戏事件封装区 =======// 680 | 681 | /** 682 | * 发送握手包 683 | * 684 | * WebSocket 链接成功后自动发送 685 | */ 686 | GameApi.prototype.handShake = function () { 687 | let handshakeBuffer = { 688 | 'sys': { 689 | type: JS_WS_CLIENT_TYPE, 690 | version: JS_WS_CLIENT_VERSION, 691 | rsa: {}, 692 | protoVersion: this.protoVersion 693 | } 694 | }; 695 | 696 | this.sendMessage(handshakeBuffer); 697 | } 698 | 699 | /** 700 | * 登录游戏 701 | * 702 | * @param {*} email 703 | * @param {*} pwd 704 | * @param {*} code 705 | */ 706 | GameApi.prototype.login = function (_email, pwd, code, is_r) { 707 | let email = _email || this.email, 708 | password = pwd || this.password, 709 | route = 'gate.gateHandler.queryEntry', 710 | data = { 711 | login_email: email, 712 | login_pwd: password, 713 | code: '', 714 | is_r: true 715 | }; 716 | 717 | // 设置握手回调 718 | this.initCallback = function () { 719 | // 尝试保存账号 720 | this.email = email; 721 | this.password = pwd; 722 | this.user_info.email = email; 723 | 724 | // 设置消息回调 725 | this.sendMessage(data, route, this.onLogin); 726 | } 727 | this.start(); 728 | } 729 | 730 | /** 731 | * 登录成功的回调 732 | * 733 | * @param {*} data 734 | */ 735 | GameApi.prototype.onLogin = function (data) { 736 | if (data.code != RES_OK) { 737 | DEBUG && console.error('onLogin', data.msg); 738 | return true; 739 | } 740 | DEBUG && console.log('onLogin', data) 741 | 742 | // 保存端口等信息 743 | this.config.gamePort = data.port; 744 | this.user_info.mid = data.mid; 745 | this.token = data.token; 746 | 747 | // 断开登录链接 重新连接到游戏服务器 748 | this.isStop = true; // 先标记停止 然后等下再开起来 749 | this.socket.close(); 750 | this.gameServer = this.getSocketServer(data.port); 751 | this.initCallback = this.loginToken; 752 | this.createWebSocket(this.gameServer); 753 | } 754 | 755 | /** 756 | * 使用 Token 登录游戏服务器 757 | */ 758 | GameApi.prototype.loginToken = function () { 759 | let route = 'connector.loginHandler.login', 760 | data = { 761 | email: this.user_info.email, 762 | token: this.token 763 | }; 764 | 765 | // 标记自己开工了 766 | this.onClose = false; 767 | 768 | // 先清空工作台 769 | // DEBUG && console.clear(); 770 | 771 | // 如果登录成功 772 | this.sendMessage(data, route, (data) => { 773 | if (data.code != RES_OK) { 774 | DEBUG && console.error('登录到游戏服务器失败', data); 775 | this.Stop(); 776 | return; 777 | } 778 | 779 | DEBUG && console.log('登录到游戏服务器成功', this, data); 780 | 781 | // 开始定时提交仙蕴 782 | this.xyUpdateIntervalId = setInterval(() => { 783 | this.xyUpdate() 784 | }, 60000); 785 | }); 786 | } 787 | 788 | /** 789 | * 仙蕴提交 790 | */ 791 | GameApi.prototype.xyUpdate = function () { 792 | this.sendMessage({}, "connector.userHandler.xyUpdate"); 793 | } 794 | 795 | /** 796 | * 查看我的日志 797 | */ 798 | GameApi.prototype.getMylogs = function () { 799 | this.sendMessage({}, "connector.userHandler.getMylogs"); 800 | } 801 | 802 | /** 803 | * 获取我的法宝 804 | */ 805 | GameApi.prototype.getMyFb = function () { 806 | this.sendMessage({}, "connector.userHandler.getMyFb"); 807 | } 808 | 809 | /** 810 | * 法宝点击 811 | * @param type 1 升星 2共生 3养灵 4佩戴 812 | * @param id 813 | */ 814 | GameApi.prototype.fbProcess = function (type, id) { 815 | this.sendMessage({ 816 | type, id 817 | }, "connector.userHandler.fbProcess"); 818 | } 819 | 820 | /** 821 | * 移动 822 | * @param x 横坐标 823 | * @param mid 地图id 824 | */ 825 | GameApi.prototype.move = function (x, mid) { 826 | this.sendMessage({ 827 | x: x, 828 | mid: mid 829 | }, "connector.playerHandler.move"); 830 | } 831 | 832 | /** 833 | * 切换地图 834 | * @param type 0 | 1 835 | */ 836 | GameApi.prototype.nextMap = function (type) { 837 | this.sendMessage({ 838 | type 839 | }, "connector.playerHandler.nextMap"); 840 | } 841 | 842 | /** 843 | * 移动至新地图 844 | */ 845 | GameApi.prototype.moveToNewMap = function (mid) { 846 | this.sendMessage({ 847 | mid: mid 848 | }, "connector.playerHandler.moveToNewMap"); 849 | } 850 | 851 | /** 852 | * 获取战斗场景列表 853 | */ 854 | GameApi.prototype.getAllCombatScreen = function () { 855 | this.sendMessage({}, "connector.teamHandler.getAllCombatScreen"); 856 | } 857 | 858 | /** 859 | * 切换战斗场景 860 | * @param {*} cbatid 861 | */ 862 | GameApi.prototype.switchCombatScreen = function (cbatid) { 863 | this.sendMessage({ 864 | cbatid: cbatid 865 | }, "connector.teamHandler.switchCombatScreen"); 866 | } 867 | 868 | /** 869 | * 获取队伍列表 870 | */ 871 | GameApi.prototype.getTeamList = function (mid) { 872 | this.sendMessage({ 873 | mid: mid 874 | }, "connector.teamHandler.getTeamList"); 875 | } 876 | 877 | /** 878 | * 创建队伍 879 | */ 880 | GameApi.prototype.createdTeam = function (mid) { 881 | this.sendMessage({ 882 | mid: mid 883 | }, "connector.teamHandler.createdTeam"); 884 | } 885 | 886 | /** 887 | * 加入队伍 888 | */ 889 | GameApi.prototype.addTeam = function (tid) { 890 | this.sendMessage({ 891 | tid: tid 892 | }, "connector.teamHandler.addTeam"); 893 | } 894 | 895 | /** 896 | * 离开队伍 897 | */ 898 | GameApi.prototype.leaveTeam = function () { 899 | this.sendMessage({}, "connector.teamHandler.leaveTeam"); 900 | } 901 | 902 | /** 903 | * 开始战斗 904 | */ 905 | GameApi.prototype.startCombat = function (cbatid) { 906 | this.sendMessage({ cbatid: cbatid}, 'connector.teamHandler.startCombat'); 907 | } 908 | 909 | /** 910 | * 回合操作 911 | * 912 | * @param type 类型 捕捉 1001 技能 1 913 | * @param skill 操作 技能 914 | * @param attack_id 目标 915 | * @param tid 队伍ID? 916 | */ 917 | GameApi.prototype.roundOperating = function (type, skill, attack_id, mtid) { 918 | this.sendMessage({ 919 | type: type, 920 | parm: skill, 921 | attack_id: attack_id, 922 | tid: mtid 923 | }, "connector.teamHandler.roundOperating"); 924 | } 925 | 926 | /** 927 | * 初始我得物品 928 | */ 929 | GameApi.prototype.getMyGoods = function (page) { 930 | this.sendMessage({ 931 | page 932 | }, "connector.userHandler.getMyGoods"); 933 | } 934 | 935 | /** 936 | * 出售物品 937 | */ 938 | GameApi.prototype.playerSellGoods = function (ugid, price, count) { 939 | this.sendMessage({ 940 | ugid: ugid, 941 | game_gold: price, 942 | count 943 | }, "connector.playerHandler.sellGoods"); 944 | } 945 | 946 | /** 947 | * 选择我的称号 948 | */ 949 | GameApi.prototype.selectMyTitle = function (index) { 950 | this.sendMessage({ 951 | index 952 | }, "connector.userHandler.selectMyTitle"); 953 | } 954 | 955 | /** 956 | * 称号弹框 957 | */ 958 | GameApi.prototype.getMyTitle = function () { 959 | this.sendMessage({}, "connector.userHandler.getMyTitle"); 960 | } 961 | 962 | /** 963 | * 初始我得宠物 964 | */ 965 | GameApi.prototype.getMyPet = function () { 966 | this.sendMessage({}, "connector.userHandler.getMyPet"); 967 | } 968 | 969 | /** 970 | * 挖宝图 971 | */ 972 | GameApi.prototype.wbt = function (ugid) { 973 | this.sendMessage({ 974 | ugid 975 | }, "connector.userHandler.wbt"); 976 | } 977 | 978 | /** 979 | * 初始我得技能 980 | */ 981 | GameApi.prototype.getMySkill = function () { 982 | this.sendMessage({}, "connector.userHandler.getMySkill"); 983 | } 984 | 985 | /** 986 | * 修炼装备 987 | */ 988 | GameApi.prototype.repairUserArms = function (type) { 989 | this.sendMessage({ 990 | type 991 | }, "connector.userHandler.repairUserArms"); 992 | } 993 | 994 | /** 995 | * 使用物品 996 | */ 997 | GameApi.prototype.useGoods = function (gid) { 998 | this.sendMessage({ 999 | gid 1000 | }, "connector.userHandler.useGoods"); 1001 | } 1002 | 1003 | /** 1004 | * 佩戴拆卸装备 1005 | */ 1006 | GameApi.prototype.wearUserEquipment = function (ueid) { 1007 | this.sendMessage({ 1008 | ueid 1009 | }, "connector.playerHandler.wearUserEquipment"); 1010 | } 1011 | 1012 | /** 1013 | * 完成任务 1014 | */ 1015 | GameApi.prototype.payUserTask = function (utid) { 1016 | this.sendMessage({ 1017 | utid 1018 | }, "connector.playerHandler.payUserTask"); 1019 | } 1020 | 1021 | /** 1022 | * 幻化宠物 1023 | */ 1024 | GameApi.prototype.turnIntoPet = function (pid) { 1025 | this.sendMessage({ 1026 | pid 1027 | }, "connector.userHandler.turnIntoPet"); 1028 | } 1029 | 1030 | /** 1031 | * 初始系统中出售物品 1032 | */ 1033 | GameApi.prototype.getSystemSellGoods = function (pageIndex) { 1034 | this.sendMessage({ 1035 | pageIndex 1036 | }, "connector.systemHandler.getSystemSellGoods"); 1037 | } 1038 | 1039 | /** 1040 | * 取回到背包 1041 | */ 1042 | GameApi.prototype.shelfMyGoods = function (id) { 1043 | this.sendMessage({ 1044 | id: id 1045 | }, "connector.userHandler.shelfMyGoods"); 1046 | } 1047 | 1048 | /** 1049 | * 初始仙坊集市 1050 | */ 1051 | GameApi.prototype.getPlayerSellGoods = function (pageIndex, type) { 1052 | this.sendMessage({ 1053 | pageIndex: pageIndex ? pageIndex : 1, 1054 | select: type 1055 | }, "connector.playerHandler.getPlayerSellGoods"); 1056 | } 1057 | 1058 | /** 1059 | * 购买玩家物品 1060 | */ 1061 | GameApi.prototype.byPalyerGoods = function (id, type) { 1062 | this.sendMessage({ 1063 | usgid: id, type 1064 | }, "connector.playerHandler.byPalyerGoods"); 1065 | } 1066 | 1067 | /** 1068 | * 购买系统物品 1069 | */ 1070 | GameApi.prototype.byGoodsToSystem = function (id, type) { 1071 | this.sendMessage({ 1072 | id, type 1073 | }, "connector.playerHandler.byGoodsToSystem"); 1074 | } 1075 | 1076 | /** 1077 | * 初始任务中心 1078 | */ 1079 | GameApi.prototype.getSystemTask = function () { 1080 | this.sendMessage({}, "connector.systemHandler.getSystemTask"); 1081 | } 1082 | 1083 | /** 1084 | * 合成物品 1085 | */ 1086 | GameApi.prototype.makeGoods = function (selectGoodsArr) { 1087 | this.sendMessage({ 1088 | arr: selectGoodsArr 1089 | }, "connector.userHandler.makeGoods"); 1090 | } 1091 | 1092 | /** 1093 | * 分解物品 1094 | */ 1095 | GameApi.prototype.userSellGoods = function (selectGoodsArr) { 1096 | this.sendMessage({ 1097 | arr: selectGoodsArr 1098 | }, "connector.userHandler.sellGoods"); 1099 | } 1100 | 1101 | /** 1102 | * 出战宠物 1103 | */ 1104 | GameApi.prototype.playUserPet = function (id, status) { 1105 | this.sendMessage({ 1106 | pid: id, 1107 | status 1108 | }, "connector.userHandler.playUserPet"); 1109 | } 1110 | 1111 | /** 1112 | * 升级宠物 type==1升级 type==2分配潜力 type==3放生 1113 | */ 1114 | GameApi.prototype.upUserPetLevel = function (pid, type, point) { 1115 | let parms = { pid: pid, type }; 1116 | if (type == 2) { 1117 | point = point || {}; 1118 | Object.assign(parms, { 1119 | str: point.str || null, 1120 | int: point.int || null, 1121 | agi: point.agi || null, 1122 | vit: point.vit || null, 1123 | con: point.con || null 1124 | }); 1125 | } 1126 | this.sendMessage(parms, "connector.userHandler.upUserPetLevel"); 1127 | } 1128 | 1129 | /** 1130 | * 发送消息 1131 | */ 1132 | GameApi.prototype.send = function (send_channel, send_msg) { 1133 | this.sendMessage({ 1134 | send_channel, 1135 | send_msg 1136 | }, "chat.chatHandler.send"); 1137 | } 1138 | 1139 | /** 1140 | * 保存/分配属性点 1141 | */ 1142 | GameApi.prototype.allocationPoint = function (str, int, agi, vit, con) { 1143 | this.sendMessage({ 1144 | str: str, 1145 | int: int, 1146 | agi: agi, 1147 | vit: vit, 1148 | con: con 1149 | }, "connector.userHandler.allocationPoint"); 1150 | } 1151 | 1152 | /** 1153 | * 获取我得装备列表 1154 | */ 1155 | GameApi.prototype.getUserEqs = function () { 1156 | this.sendMessage({}, "connector.userHandler.getUserEqs"); 1157 | } 1158 | 1159 | /** 1160 | * 更新玩家货币 1161 | */ 1162 | GameApi.prototype.updateUserPrice = function () { 1163 | this.sendMessage({}, "connector.userHandler.updateUserPrice"); 1164 | } 1165 | 1166 | /** 1167 | * 显示我的团队 1168 | */ 1169 | GameApi.prototype.showMyTeam = function () { 1170 | this.sendMessage({ is_show }, "connector.teamHandler.showMyTeam"); 1171 | } 1172 | 1173 | /** 1174 | * 排行榜? 1175 | * @param type 1=等级 2=兽宠 3=神兵 1176 | */ 1177 | GameApi.prototype.getRankList = function (type) { 1178 | this.sendMessage({ type }, "connector.systemHandler.getRankList"); 1179 | } 1180 | 1181 | /** 1182 | * 初始聚仙阁楼页面 1183 | */ 1184 | GameApi.prototype.initFation = function () { 1185 | this.sendMessage({}, "connector.fationHandler.initFation"); 1186 | } 1187 | 1188 | /** 1189 | * 创建工会 1190 | */ 1191 | GameApi.prototype.createFation = function (name) { 1192 | this.sendMessage({ 1193 | name 1194 | }, "connector.fationHandler.createFation"); 1195 | } 1196 | 1197 | /** 1198 | * 申请工会 1199 | */ 1200 | GameApi.prototype.applyForFation = function (fid) { 1201 | this.sendMessage({ 1202 | fid 1203 | }, "connector.fationHandler.applyForFation"); 1204 | } 1205 | 1206 | 1207 | /** 1208 | * 同意入会 1209 | * @param type 1=同意 2=拒绝 1210 | * @param fation_apply_id 1211 | */ 1212 | GameApi.prototype.doneFationApply = function (type, fation_apply_id) { 1213 | this.sendMessage({ 1214 | type, faid: fation_apply_id 1215 | }, "connector.fationHandler.doneFationApply"); 1216 | } 1217 | 1218 | /** 1219 | * 升降职位 1220 | */ 1221 | GameApi.prototype.upUserFationLevel = function (type, uid) { 1222 | this.sendMessage({ 1223 | type, uid 1224 | }, "connector.fationHandler.upUserFationLevel"); 1225 | } 1226 | 1227 | /** 1228 | * 打开工会列表 1229 | */ 1230 | GameApi.prototype.getFationList = function () { 1231 | this.sendMessage({}, "connector.fationHandler.getFationList"); 1232 | } 1233 | 1234 | /** 1235 | * 查看申请 1236 | */ 1237 | GameApi.prototype.getFationApply = function () { 1238 | this.sendMessage({}, "connector.fationHandler.getFationApply"); 1239 | } 1240 | 1241 | /** 1242 | * 查看工会人员 1243 | */ 1244 | GameApi.prototype.showFationUserList = function () { 1245 | this.sendMessage({}, "connector.fationHandler.showFationUserList"); 1246 | } 1247 | 1248 | /** 1249 | * 脱离工会 1250 | */ 1251 | GameApi.prototype.leaveFation = function () { 1252 | this.sendMessage({}, "connector.fationHandler.leaveFation"); 1253 | } 1254 | 1255 | /** 1256 | * 点技能 1257 | */ 1258 | GameApi.prototype.upFationUserSkill = function () { 1259 | this.sendMessage({ 1260 | type 1261 | }, "connector.fationHandler.upFationUserSkill"); 1262 | } 1263 | 1264 | /** 1265 | * 捐赠 1266 | */ 1267 | GameApi.prototype.donateFationFunds = function () { 1268 | this.sendMessage({}, "connector.fationHandler.donateFationFunds"); 1269 | } 1270 | 1271 | /** 1272 | * 领取任务 1273 | */ 1274 | GameApi.prototype.getFationTask = function () { 1275 | this.sendMessage({}, "connector.fationHandler.getFationTask"); 1276 | } 1277 | 1278 | /** 1279 | * 升级工会 1280 | */ 1281 | GameApi.prototype.upFation = function (type) { 1282 | this.sendMessage({ 1283 | type 1284 | }, "connector.fationHandler.upFation"); 1285 | } 1286 | 1287 | /** 1288 | * 聚灵 1289 | */ 1290 | GameApi.prototype.polyLin = function (type) { 1291 | this.sendMessage({ 1292 | type 1293 | }, "connector.userHandler.polyLin"); 1294 | } 1295 | 1296 | /** 1297 | * 放弃任务 1298 | */ 1299 | GameApi.prototype.closeUserTask = function (tid) { 1300 | this.sendMessage({ 1301 | tid: tid 1302 | }, "connector.fationHandler.closeUserTask"); 1303 | } 1304 | 1305 | /** 1306 | * 整理 1307 | */ 1308 | GameApi.prototype.allSellGoods = function () { 1309 | this.sendMessage({}, "connector.userHandler.allSellGoods"); 1310 | } 1311 | 1312 | /** 1313 | * 重置属性 1314 | */ 1315 | GameApi.prototype.resetAttribute = function () { 1316 | this.sendMessage({}, "connector.userHandler.resetAttribute"); 1317 | } 1318 | 1319 | /** 1320 | * 预览合宠 1321 | */ 1322 | GameApi.prototype.getMyPet = function (a_id, b_id) { 1323 | this.sendMessage({ 1324 | ids: a_id + "," + b_id 1325 | }, "connector.userHandler.getMyPet"); 1326 | } 1327 | 1328 | /** 1329 | * 确认合成 1330 | */ 1331 | GameApi.prototype.fitPet = function (a_id, b_id) { 1332 | this.sendMessage({ 1333 | ids: a_id + "," + b_id 1334 | }, "connector.userHandler.fitPet"); 1335 | } 1336 | 1337 | /** 1338 | * 添加用户宠物技能? 1339 | */ 1340 | GameApi.prototype.addUserPetSkill = function (upid) { 1341 | this.sendMessage({ 1342 | ugid, upid 1343 | }, "connector.userHandler.addUserPetSkill"); 1344 | } 1345 | 1346 | /** 1347 | * 获取我的宠物用品? 1348 | */ 1349 | GameApi.prototype.getMyPetSkillGoods = function () { 1350 | this.sendMessage({}, "connector.userHandler.getMyPetSkillGoods"); 1351 | } 1352 | 1353 | return GameApi; 1354 | }); 1355 | -------------------------------------------------------------------------------- /app/lib/protocol.js: -------------------------------------------------------------------------------- 1 | define(function (exports, require, module) { 2 | (function (exports, ByteArray, global) { 3 | var Protocol = exports; 4 | 5 | var PKG_HEAD_BYTES = 4; 6 | var MSG_FLAG_BYTES = 1; 7 | var MSG_ROUTE_CODE_BYTES = 2; 8 | var MSG_ID_MAX_BYTES = 5; 9 | var MSG_ROUTE_LEN_BYTES = 1; 10 | 11 | var MSG_ROUTE_CODE_MAX = 0xffff; 12 | 13 | var MSG_COMPRESS_ROUTE_MASK = 0x1; 14 | var MSG_TYPE_MASK = 0x7; 15 | 16 | var Package = Protocol.Package = {}; 17 | var Message = Protocol.Message = {}; 18 | 19 | Package.TYPE_HANDSHAKE = 1; 20 | Package.TYPE_HANDSHAKE_ACK = 2; 21 | Package.TYPE_HEARTBEAT = 3; 22 | Package.TYPE_DATA = 4; 23 | Package.TYPE_KICK = 5; 24 | 25 | Message.TYPE_REQUEST = 0; 26 | Message.TYPE_NOTIFY = 1; 27 | Message.TYPE_RESPONSE = 2; 28 | Message.TYPE_PUSH = 3; 29 | 30 | /** 31 | * pomele client encode 32 | * id message id; 33 | * route message route 34 | * msg message body 35 | * socketio current support string 36 | */ 37 | Protocol.strencode = function (str) { 38 | var byteArray = new ByteArray(str.length * 3); 39 | var offset = 0; 40 | for (var i = 0; i < str.length; i++) { 41 | var charCode = str.charCodeAt(i); 42 | var codes = null; 43 | if (charCode <= 0x7f) { 44 | codes = [charCode]; 45 | } else if (charCode <= 0x7ff) { 46 | codes = [0xc0 | (charCode >> 6), 0x80 | (charCode & 0x3f)]; 47 | } else { 48 | codes = [0xe0 | (charCode >> 12), 0x80 | ((charCode & 0xfc0) >> 6), 0x80 | (charCode & 0x3f)]; 49 | } 50 | for (var j = 0; j < codes.length; j++) { 51 | byteArray[offset] = codes[j]; 52 | ++offset; 53 | } 54 | } 55 | var _buffer = new ByteArray(offset); 56 | copyArray(_buffer, 0, byteArray, 0, offset); 57 | return _buffer; 58 | } 59 | ; 60 | 61 | /** 62 | * client decode 63 | * msg String data 64 | * return Message Object 65 | */ 66 | Protocol.strdecode = function (buffer) { 67 | var bytes = new ByteArray(buffer); 68 | var array = []; 69 | var offset = 0; 70 | var charCode = 0; 71 | var end = bytes.length; 72 | while (offset < end) { 73 | if (bytes[offset] < 128) { 74 | charCode = bytes[offset]; 75 | offset += 1; 76 | } else if (bytes[offset] < 224) { 77 | charCode = ((bytes[offset] & 0x3f) << 6) + (bytes[offset + 1] & 0x3f); 78 | offset += 2; 79 | } else { 80 | charCode = ((bytes[offset] & 0x0f) << 12) + ((bytes[offset + 1] & 0x3f) << 6) + (bytes[offset + 2] & 0x3f); 81 | offset += 3; 82 | } 83 | array.push(charCode); 84 | } 85 | return String.fromCharCode.apply(null, array); 86 | } 87 | ; 88 | 89 | /** 90 | * Package protocol encode. 91 | * 92 | * Pomelo package format: 93 | * +------+-------------+------------------+ 94 | * | type | body length | body | 95 | * +------+-------------+------------------+ 96 | * 97 | * Head: 4bytes 98 | * 0: package type, 99 | * 1 - handshake, 100 | * 2 - handshake ack, 101 | * 3 - heartbeat, 102 | * 4 - data 103 | * 5 - kick 104 | * 1 - 3: big-endian body length 105 | * Body: body length bytes 106 | * 107 | * @param {Number} type package type 108 | * @param {ByteArray} body body content in bytes 109 | * @return {ByteArray} new byte array that contains encode result 110 | */ 111 | Package.encode = function (type, body) { 112 | var length = body ? body.length : 0; 113 | var buffer = new ByteArray(PKG_HEAD_BYTES + length); 114 | var index = 0; 115 | buffer[index++] = type & 0xff; 116 | buffer[index++] = (length >> 16) & 0xff; 117 | buffer[index++] = (length >> 8) & 0xff; 118 | buffer[index++] = length & 0xff; 119 | if (body) { 120 | copyArray(buffer, index, body, 0, length); 121 | } 122 | return buffer; 123 | } 124 | ; 125 | 126 | /** 127 | * Package protocol decode. 128 | * See encode for package format. 129 | * 130 | * @param {ByteArray} buffer byte array containing package content 131 | * @return {Object} {type: package type, buffer: body byte array} 132 | */ 133 | Package.decode = function (buffer) { 134 | var offset = 0; 135 | var bytes = new ByteArray(buffer); 136 | var length = 0; 137 | var rs = []; 138 | while (offset < bytes.length) { 139 | var type = bytes[offset++]; 140 | length = ((bytes[offset++]) << 16 | (bytes[offset++]) << 8 | bytes[offset++]) >>> 0; 141 | var body = length ? new ByteArray(length) : null; 142 | copyArray(body, 0, bytes, offset, length); 143 | offset += length; 144 | rs.push({ 145 | 'type': type, 146 | 'body': body 147 | }); 148 | } 149 | return rs.length === 1 ? rs[0] : rs; 150 | } 151 | ; 152 | 153 | /** 154 | * Message protocol encode. 155 | * 156 | * @param {Number} id message id 157 | * @param {Number} type message type 158 | * @param {Number} compressRoute whether compress route 159 | * @param {Number|String} route route code or route string 160 | * @param {Buffer} msg message body bytes 161 | * @return {Buffer} encode result 162 | */ 163 | Message.encode = function (id, type, compressRoute, route, msg) { 164 | // caculate message max length 165 | var idBytes = msgHasId(type) ? caculateMsgIdBytes(id) : 0; 166 | var msgLen = MSG_FLAG_BYTES + idBytes; 167 | 168 | if (msgHasRoute(type)) { 169 | if (compressRoute) { 170 | if (typeof route !== 'number') { 171 | throw new Error('error flag for number route!'); 172 | } 173 | msgLen += MSG_ROUTE_CODE_BYTES; 174 | } else { 175 | msgLen += MSG_ROUTE_LEN_BYTES; 176 | if (route) { 177 | route = Protocol.strencode(route); 178 | if (route.length > 255) { 179 | throw new Error('route maxlength is overflow'); 180 | } 181 | msgLen += route.length; 182 | } 183 | } 184 | } 185 | 186 | if (msg) { 187 | msgLen += msg.length; 188 | } 189 | 190 | var buffer = new ByteArray(msgLen); 191 | var offset = 0; 192 | 193 | // add flag 194 | offset = encodeMsgFlag(type, compressRoute, buffer, offset); 195 | 196 | // add message id 197 | if (msgHasId(type)) { 198 | offset = encodeMsgId(id, buffer, offset); 199 | } 200 | 201 | // add route 202 | if (msgHasRoute(type)) { 203 | offset = encodeMsgRoute(compressRoute, route, buffer, offset); 204 | } 205 | 206 | // add body 207 | if (msg) { 208 | offset = encodeMsgBody(msg, buffer, offset); 209 | } 210 | 211 | return buffer; 212 | } 213 | ; 214 | 215 | /** 216 | * Message protocol decode. 217 | * 218 | * @param {Buffer|Uint8Array} buffer message bytes 219 | * @return {Object} message object 220 | */ 221 | Message.decode = function (buffer) { 222 | var bytes = new ByteArray(buffer); 223 | var bytesLen = bytes.length || bytes.byteLength; 224 | var offset = 0; 225 | var id = 0; 226 | var route = null; 227 | 228 | // parse flag 229 | var flag = bytes[offset++]; 230 | var compressRoute = flag & MSG_COMPRESS_ROUTE_MASK; 231 | var type = (flag >> 1) & MSG_TYPE_MASK; 232 | 233 | // parse id 234 | if (msgHasId(type)) { 235 | var m = parseInt(bytes[offset]); 236 | var i = 0; 237 | do { 238 | var m = parseInt(bytes[offset]); 239 | id = id + ((m & 0x7f) * Math.pow(2, (7 * i))); 240 | offset++; 241 | i++; 242 | } while (m >= 128); 243 | } 244 | 245 | // parse route 246 | if (msgHasRoute(type)) { 247 | if (compressRoute) { 248 | route = (bytes[offset++]) << 8 | bytes[offset++]; 249 | } else { 250 | var routeLen = bytes[offset++]; 251 | if (routeLen) { 252 | route = new ByteArray(routeLen); 253 | copyArray(route, 0, bytes, offset, routeLen); 254 | route = Protocol.strdecode(route); 255 | } else { 256 | route = ''; 257 | } 258 | offset += routeLen; 259 | } 260 | } 261 | 262 | // parse body 263 | var bodyLen = bytesLen - offset; 264 | var body = new ByteArray(bodyLen); 265 | 266 | copyArray(body, 0, bytes, offset, bodyLen); 267 | 268 | return { 269 | 'id': id, 270 | 'type': type, 271 | 'compressRoute': compressRoute, 272 | 'route': route, 273 | 'body': body 274 | }; 275 | } 276 | ; 277 | 278 | var copyArray = function (dest, doffset, src, soffset, length) { 279 | if ('function' === typeof src.copy) { 280 | // Buffer 281 | src.copy(dest, doffset, soffset, soffset + length); 282 | } else { 283 | // Uint8Array 284 | for (var index = 0; index < length; index++) { 285 | dest[doffset++] = src[soffset++]; 286 | } 287 | } 288 | }; 289 | 290 | var msgHasId = function (type) { 291 | return type === Message.TYPE_REQUEST || type === Message.TYPE_RESPONSE; 292 | }; 293 | 294 | var msgHasRoute = function (type) { 295 | return type === Message.TYPE_REQUEST || type === Message.TYPE_NOTIFY || type === Message.TYPE_PUSH; 296 | }; 297 | 298 | var caculateMsgIdBytes = function (id) { 299 | var len = 0; 300 | do { 301 | len += 1; 302 | id >>= 7; 303 | } while (id > 0); return len; 304 | }; 305 | 306 | var encodeMsgFlag = function (type, compressRoute, buffer, offset) { 307 | if (type !== Message.TYPE_REQUEST && type !== Message.TYPE_NOTIFY && type !== Message.TYPE_RESPONSE && type !== Message.TYPE_PUSH) { 308 | throw new Error('unkonw message type: ' + type); 309 | } 310 | 311 | buffer[offset] = (type << 1) | (compressRoute ? 1 : 0); 312 | 313 | return offset + MSG_FLAG_BYTES; 314 | }; 315 | 316 | var encodeMsgId = function (id, buffer, offset) { 317 | do { 318 | var tmp = id % 128; 319 | var next = Math.floor(id / 128); 320 | 321 | if (next !== 0) { 322 | tmp = tmp + 128; 323 | } 324 | buffer[offset++] = tmp; 325 | 326 | id = next; 327 | } while (id !== 0); 328 | return offset; 329 | }; 330 | 331 | var encodeMsgRoute = function (compressRoute, route, buffer, offset) { 332 | if (compressRoute) { 333 | if (route > MSG_ROUTE_CODE_MAX) { 334 | throw new Error('route number is overflow'); 335 | } 336 | 337 | buffer[offset++] = (route >> 8) & 0xff; 338 | buffer[offset++] = route & 0xff; 339 | } else { 340 | if (route) { 341 | buffer[offset++] = route.length & 0xff; 342 | copyArray(buffer, offset, route, 0, route.length); 343 | offset += route.length; 344 | } else { 345 | buffer[offset++] = 0; 346 | } 347 | } 348 | 349 | return offset; 350 | }; 351 | 352 | var encodeMsgBody = function (msg, buffer, offset) { 353 | copyArray(buffer, offset, msg, 0, msg.length); 354 | return offset + msg.length; 355 | }; 356 | 357 | module.exports = Protocol; 358 | if (typeof (window) != "undefined") { 359 | window.Protocol = Protocol; 360 | } 361 | } 362 | )(typeof (window) == "undefined" ? module.exports : (this.Protocol = {}), typeof (window) == "undefined" ? Buffer : Uint8Array, this); 363 | 364 | }); 365 | -------------------------------------------------------------------------------- /app/lib/require-data.js: -------------------------------------------------------------------------------- 1 | define(['axios'], function (axios) { 2 | var loadData = {}; 3 | 4 | loadData.load = function (dataId, req, load, config) { 5 | axios.get(req.toUrl(dataId)).then(function (response) { 6 | load(response.data); 7 | }); 8 | } 9 | 10 | return loadData; 11 | }); 12 | -------------------------------------------------------------------------------- /app/main.js: -------------------------------------------------------------------------------- 1 | (function (exports) { 2 | // 定义加载源 3 | require.config({ 4 | baseUrl: './', 5 | paths: { 6 | vue: 'https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue', 7 | axios: 'https://cdn.jsdelivr.net/npm/axios@0.19.2/dist/axios.min', 8 | ELEMENT: 'https://cdn.jsdelivr.net/npm/element-ui@2.13.2/lib/index', 9 | 'element-ui-theme': 'https://cdn.jsdelivr.net/npm/element-ui@2.13.2/lib/theme-chalk/index', 10 | YunDingOnlineSDK: 'app/lib/YunDingOnlineSDK', 11 | ydComponents: 'app/ydComponents', 12 | regHooks: 'app/regHooks', 13 | 'app-template': 'templates/main.html', 14 | 'app-config': 'app/config.json', 15 | 'app-style': 'assets/style', 16 | 'ali-icon': 'https://at.alicdn.com/t/font_1936453_g8yfzj2joju', // 阿里的 icon 图标 17 | protocol: 'app/lib/protocol' 18 | }, 19 | map: { 20 | '*': { 21 | 'css': 'https://cdn.jsdelivr.net/npm/require-css@0.1.10/css.js', 22 | 'data': 'app/lib/require-data.js' 23 | } 24 | } 25 | }); 26 | 27 | let require_list = [ 28 | // 模板相关 29 | 'data!app-config', 30 | // 插件相关 31 | 'YunDingOnlineSDK', 'vue', 'ELEMENT', 'regHooks', 'ydComponents' 32 | ]; 33 | 34 | // 启动项目 35 | requirejs(require_list, function (config, GameApi, Vue, ELEMENT, regHooks, ydComponents) { 36 | // 手动注册 Element-UI 到 Vue 37 | ELEMENT.install(Vue); 38 | 39 | // 设置调试模式 40 | // GameApi.isDEBUG(); 41 | 42 | // 创建 View 43 | let app = new Vue({ 44 | el: '#app', 45 | template: ydComponents.main, 46 | data: { 47 | user_list: [], 48 | config: {}, 49 | official: '', 50 | official_show: false 51 | }, 52 | // 创建完毕回调 53 | mounted: function () { 54 | // 创建保存游戏对象的列表 (不希望被 Vue 解析) 55 | this.game_list = {}; 56 | 57 | // 全局配置 58 | this.config = config; 59 | // this.$set('config', config) 60 | 61 | // 一定要马上将自己暴露给 regHook 里面 62 | regHooks(this); 63 | ydComponents.setApp(this); 64 | 65 | // 加载历史账号 66 | let users = this.getStorageUser(); 67 | Object.keys(users).forEach((email) => { 68 | this.addUser(email, users[email]); 69 | }); 70 | 71 | }, 72 | computed: { 73 | /** 74 | * user_list 的 email 与 index 对照关系 75 | */ 76 | userMap: function () { 77 | let user_map = {}; 78 | this.user_list.forEach((item, index) => { 79 | // console.log('userMap', item, index); 80 | user_map[item.email] = index; 81 | }); 82 | return user_map; 83 | } 84 | }, 85 | methods: { 86 | /** 87 | * 将账号密码保存到 localStorage 中 88 | * @param {*} email 89 | * @param {*} password 90 | */ 91 | saveStorageUser: function (email, password) { 92 | let users = this.getStorageUser(); 93 | if ('undefined' == typeof password) { 94 | delete users[email]; 95 | } else { 96 | users[email] = password; 97 | } 98 | localStorage.setItem('ydxxGame_userList', JSON.stringify(users)); 99 | }, 100 | /** 101 | * 从 localStorage 中取出账号密码 102 | * @param {*} email 可选 103 | */ 104 | getStorageUser: function (email) { 105 | let users = JSON.parse(localStorage.getItem('ydxxGame_userList') || '{}') || {}; 106 | return email ? users[email] || null : users; 107 | }, 108 | /** 109 | * 获取内建的游戏对象 110 | * @param {*} email 111 | */ 112 | getGame: function (email) { 113 | return this.game_list[email]; 114 | }, 115 | /** 116 | * 获取列表中对应的 User 117 | * @param {*} email 118 | */ 119 | getUser: function (email) { 120 | let index = this.userMap[email]; 121 | 122 | if (index === undefined) { 123 | return null; 124 | } 125 | return this.user_list[index]; 126 | }, 127 | /** 128 | * 页面表单 添加用户 129 | * @param {*} event 130 | */ 131 | onAddUser: function (email, passwd) { 132 | // 保存账号密码 133 | this.saveStorageUser(email, passwd); 134 | 135 | // 添加到列表并登陆 136 | this.addUser(email, passwd, true) 137 | }, 138 | /** 139 | * 添加一个用户到列表 140 | * 141 | * @param {*} email 142 | * @param {*} password 143 | * @param {*} isLogin 144 | */ 145 | addUser: function (email, password, isLogin) { 146 | // 创建游戏对象 147 | let game = new GameApi(); 148 | 149 | // 游戏对象保存起来 150 | this.game_list[email] = game; 151 | 152 | // 添加到用户列表 153 | this.user_list.push({ 154 | email: email, 155 | status: '已添加', 156 | teams: [], 157 | message: '暂无' 158 | }); 159 | 160 | // 登录账号 161 | if (isLogin) { 162 | game.login(email, password); 163 | } 164 | }, 165 | /** 166 | * 删除用户 167 | * @param {*} row 168 | */ 169 | deleteUser: function (email) { 170 | let index = this.userMap[email]; 171 | 172 | // 从列表删除此用户 173 | this.user_list.splice(index, 1); 174 | 175 | // TODO 需要完成 注销游戏 与销毁游戏对象的功能 176 | this.getGame(row.email).Stop(); 177 | 178 | // 更新保存用户 179 | this.saveStorageUser(row.email); 180 | }, 181 | /** 182 | * 登录 183 | * @param {*} email 184 | */ 185 | loginUser: function (email) { 186 | let game = this.getGame(email), 187 | user = this.getUser(email), 188 | passwd = this.getStorageUser(email); 189 | 190 | // 断开连接 191 | if (game.socket) { 192 | user.status = '正在退出'; 193 | game.Stop(); 194 | } 195 | 196 | // 删除游戏对象 197 | delete this.game_list[email]; 198 | 199 | // 创建游戏对象 200 | game = new GameApi(); 201 | 202 | // 游戏对象保存起来 203 | this.game_list[email] = game; 204 | 205 | user.status = '正在登录'; 206 | game.login(email, passwd); 207 | }, 208 | // 退出登录 209 | logoutUser: function (email) { 210 | let game = this.getGame(email), 211 | user = this.getUser(email); 212 | 213 | // 断开连接 214 | if (game && game.socket) { 215 | game.Stop(); 216 | } 217 | 218 | user.status = '已经退出'; 219 | user.isLogin = false; 220 | }, 221 | /** 222 | * 到官网 查看用户 223 | * @param {}} email 224 | */ 225 | viewUser: function (email) { 226 | let login_info = { 227 | email: email, 228 | pwd: this.getStorageUser(email) 229 | }, bcode = btoa(encodeURIComponent(JSON.stringify(login_info))), 230 | url = this.config.core.official_login + '#autologin=' + bcode; 231 | 232 | // 退出登录 233 | this.logoutUser(email); 234 | 235 | // 设置 地址 236 | window.open(url); 237 | 238 | return; 239 | // 不能在本站内打开 240 | this.official = url; 241 | this.official_show = true; 242 | }, 243 | // 设置消息 244 | setMessage: function (email, data) { 245 | let date = new Date(); 246 | data.time = 'H:i:s'.replace(/[His]/g, (full) => { 247 | let str = ''; 248 | switch (full) { 249 | case 'H': 250 | str = date.getHours(); 251 | break; 252 | case 'i': 253 | str = date.getMinutes(); 254 | break; 255 | case 's': 256 | str = date.getSeconds(); 257 | break; 258 | default: 259 | break; 260 | } 261 | return str.toString().padStart(2, '0'); 262 | }) 263 | 264 | // 调整格式 方便渲染 265 | if (data.win >= 1) { 266 | data.exp.forEach((item) => { 267 | if (item.name == email) { 268 | data.my_exp = item.exp; 269 | } 270 | }); 271 | data.player_reward.forEach((item) => { 272 | if (item.name == email) { 273 | let reward = []; 274 | item.goods.forEach((good) => { 275 | reward.push(good.gname); 276 | }) 277 | if (reward.length == 0) { 278 | reward.push('无') 279 | } 280 | data.my_reward = reward; 281 | } 282 | }); 283 | data.die_arr = data.die_arr.map((item) => { 284 | return item.replace(/<[^>]+>/g, ''); 285 | }) 286 | console.log(data.die_arr); 287 | } 288 | 289 | this.getUser(email).message = data; 290 | } 291 | } 292 | }); 293 | 294 | // 暴露到全局 295 | exports.app = app; 296 | exports.GameApi = GameApi; 297 | }); 298 | 299 | })(window); 300 | -------------------------------------------------------------------------------- /app/regHooks.js: -------------------------------------------------------------------------------- 1 | define(['YunDingOnlineSDK'], function (GameApi) { 2 | let app = null; 3 | 4 | // 创建空函数 屏蔽 onLeave onAdd 消息刷屏 5 | let emptyCb = () => { }; 6 | emptyCb.hookMark = 'regHooks.emptyCb'; 7 | GameApi.regHookHandlers['onLeave'].push(emptyCb); 8 | GameApi.regHookHandlers['onAdd'].push(emptyCb); 9 | 10 | // 接管登录成功的回调 11 | let loginCb = function (data) { 12 | let user = app.getUser(this.email); 13 | console.log('loginCb', this.email, data); 14 | 15 | // 检查错误 16 | if (data.code == 500) { 17 | user.status = "登录失败"; 18 | user.status_msg = "账号已在他处登录"; 19 | return; 20 | } else if (data.code != 200) { 21 | user.status = "登录失败"; 22 | user.status_msg = data.msg || '未知错误'; 23 | return; 24 | } 25 | 26 | // 登录成功 27 | if (data.token) { 28 | user.status = '鉴权成功'; 29 | return; 30 | } 31 | user.status = '登录成功'; 32 | 33 | // 没有数据就不在继续了 34 | if ('object' != typeof data.data) { 35 | return; 36 | } 37 | 38 | // 标记自己登陆成功 39 | app.$set(user, 'isLogin', true); 40 | 41 | // 记录地图位置 42 | app.$set(user, 'map', data.data.map); 43 | 44 | // 获取一些初始化的信息 45 | this.getTeamList(); // 获取地图队伍列表 46 | } 47 | loginCb.hookMark = "regHooks.loginCb"; 48 | GameApi.regHookHandlers['gate.gateHandler.queryEntry'].push(loginCb); 49 | GameApi.regHookHandlers['connector.loginHandler.login'].push(loginCb); 50 | 51 | // 移动地图的返回 52 | let moveToNewMapCb = function (data) { 53 | let user = app.getUser(this.email); 54 | 55 | // 更新地图位置 56 | app.$set(user, 'map', data.map); 57 | console.log('moveToNewMapCb', this.email, data); 58 | }; 59 | moveToNewMapCb.hookMark = "regHooks.moveToNewMapCb"; 60 | GameApi.regHookHandlers['connector.playerHandler.moveToNewMap'].push(moveToNewMapCb); 61 | 62 | // 创建队伍回调 63 | let createdTeamCb = function (data) { 64 | if (data.code != 200) { 65 | app.$message.error(data.msg); 66 | return; 67 | } 68 | console.log('createdTeamCb', this.email, data); 69 | 70 | // 保存队长信息 71 | app.$set(app.getUser(this.email), 'team', { 72 | leader: this.email, 73 | users: [] 74 | }); 75 | 76 | app.$message.success('队伍创建成功'); 77 | } 78 | createdTeamCb.hookMark = "regHooks.createdTeamCb"; 79 | GameApi.regHookHandlers['connector.teamHandler.createdTeam'].push(createdTeamCb); 80 | 81 | // 离开队伍的回调 82 | let leaveTeamCb = function (data) { 83 | if (data.code != 200) { 84 | app.$message.error(data.msg); 85 | return; 86 | } 87 | console.log('createdTeamCb', this.email, data); 88 | 89 | app.$delete(app.getUser(this.email), 'team'); 90 | app.$message('已离开队伍'); 91 | } 92 | leaveTeamCb.hookMark = "regHooks.leaveTeamCb"; 93 | GameApi.regHookHandlers['connector.teamHandler.leaveTeam'].push(leaveTeamCb); 94 | 95 | // 加入队伍 96 | let addTeamCb = function (data) { 97 | if (data.code != 200) { 98 | app.$message.error(data.msg); 99 | return; 100 | } 101 | console.log('addTeamCb', this.email, data); 102 | 103 | // 整理结构 104 | let combat = data.data.combat || null, 105 | leader = data.data.users[0].nickname, 106 | users = data.data.users; 107 | 108 | for (let i = 0; i < this.user_info.screens.length; i++) { 109 | const screen = this.user_info.screens[i]; 110 | if (screen._id == combat) { 111 | combat = screen.name; 112 | } 113 | } 114 | 115 | app.$set(app.getUser(this.email), 'team', { 116 | combat: combat, 117 | leader: leader, 118 | users: users 119 | }); 120 | app.$message('已加入队伍'); 121 | } 122 | addTeamCb.hookMark = "regHooks.addTeamCb"; 123 | GameApi.regHookHandlers['connector.teamHandler.addTeam'].push(addTeamCb); 124 | 125 | // 重新接收队伍信息 126 | let onMyTeamReloadCb = function (data) { 127 | if (data.code && data.code != 200) { 128 | app.$message.error(data.msg); 129 | return; 130 | } 131 | console.log('onMyTeamReloadCb', this.email, data); 132 | 133 | let user = app.getUser(this.email); 134 | 135 | // 队员退出 136 | if (data.new_uid && !data.team && user.team) { 137 | // 移除队员 138 | user.team.users.forEach((item, index) => { 139 | if (item._id == data.new_uid) { 140 | user.team.users.splice(index, 1); 141 | } 142 | }); 143 | return; 144 | } 145 | 146 | // 队长退出队伍 data = {} 只有自己会收到 147 | if (data.data) { 148 | app.$delete(user, 'team'); 149 | return; 150 | } 151 | 152 | // 没有队伍信息是干啥? 153 | if (!data.team) { 154 | return; 155 | } 156 | 157 | // 获取队长信息 158 | let leader = data.team.leader, 159 | users = []; 160 | 161 | for (let i = 0; i < data.team.users.length; i++) { 162 | const user = data.team.users[i]; 163 | if (user._id == leader) { 164 | leader = user.email; 165 | } 166 | users.push({ 167 | _id: user._id, 168 | email: user.email, 169 | level: user.level 170 | }) 171 | } 172 | 173 | app.$set(user, 'team', { 174 | leader: leader, 175 | users: users, 176 | combat: data.team.combat 177 | }); 178 | } 179 | onMyTeamReloadCb.hookMark = "regHooks.onMyTeamReloadCb"; 180 | GameApi.regHookHandlers['onMyTeamReload'].push(onMyTeamReloadCb); 181 | 182 | // 获取队伍列表 183 | let getTeamListCb = function (data) { 184 | if (data.code != 200) { 185 | app.$message.error(data.msg); 186 | return; 187 | } 188 | console.log('getTeamListCb', this.email, data); 189 | 190 | let user = app.getUser(this.email); 191 | 192 | app.$set(user, 'screens', data.data.screens); 193 | app.$set(user, 'teams', data.data.teams); 194 | 195 | // 特别标记队长 196 | if (data.data.myTeam) { 197 | data.data.myTeam.leader = data.data.myTeam.users[0].nickname; 198 | } 199 | app.$set(user, 'team', data.data.myTeam); 200 | } 201 | getTeamListCb.hookMark = "regHooks.getTeamListCb"; 202 | GameApi.regHookHandlers['connector.teamHandler.getTeamList'].push(getTeamListCb); 203 | 204 | // 切换场景回调 205 | let switchCombatScreenCb = function (data) { 206 | if (data.code != 200) { 207 | app.$message.error(data.msg); 208 | return; 209 | } 210 | console.log('switchCombatScreenCb', this.email, data); 211 | 212 | app.getUser(this.email).team.combat = app.getUser(this.email).team.to_combat; 213 | 214 | app.$message.success("切换场景成功"); 215 | } 216 | switchCombatScreenCb.hookMark = "regHooks.switchCombatScreenCb"; 217 | GameApi.regHookHandlers['connector.teamHandler.switchCombatScreen'].push(switchCombatScreenCb); 218 | 219 | // 战斗开始 220 | let onStartBatCb = function (data) { 221 | console.log('onStartBatCb', data); 222 | } 223 | onStartBatCb.hookMark = "regHooks.onStartBatCb"; 224 | GameApi.regHookHandlers['onStartBat'].push(onStartBatCb); 225 | 226 | // 战斗结束 227 | let onRoundBatEndCb = function (data) { 228 | // 开启新的战斗 229 | if (app.getUser(this.email).fighting && data.data.win > 0) { 230 | this.startCombat(this.user_info.team.combat); 231 | } 232 | // console.log('onRoundBatEndCb', data); 233 | 234 | // 保存战斗消息 235 | app.setMessage(this.email, data.data); 236 | } 237 | onRoundBatEndCb.hookMark = "regHooks.onRoundBatEndCb"; 238 | GameApi.regHookHandlers['onRoundBatEnd'].push(onRoundBatEndCb); 239 | 240 | // 回合操作 241 | let roundOperatingCb = function (data) { 242 | if (data.code != 200) { 243 | app.$message.error(data.msg); 244 | return; 245 | } 246 | console.log('switchCombatScreenCb', this.email, data); 247 | } 248 | roundOperatingCb.hookMark = "regHooks.roundOperatingCb"; 249 | GameApi.regHookHandlers['connector.teamHandler.roundOperating'].push(roundOperatingCb); 250 | 251 | 252 | 253 | // 暴露一个接口 用来接收 app 对象 254 | return function (_app) { 255 | app = _app; 256 | }; 257 | }); 258 | -------------------------------------------------------------------------------- /app/ydComponents.js: -------------------------------------------------------------------------------- 1 | define(['vue', 'data!app-template', 'css!ali-icon', 'css!app-style', 'css!element-ui-theme'], function (Vue, template) { 2 | // 对象是需要的 3 | let app, 4 | templates = {}, 5 | components = {}, 6 | main; 7 | 8 | /** 9 | * 将字符串模板转换为对象列表 10 | * @param {*} template_str 11 | */ 12 | function strToDoms(template_str) { 13 | let templateDom = document.createElement('div'), 14 | templates = {}, doms; 15 | templateDom.innerHTML = template_str; 16 | doms = templateDom.getElementsByTagName('template'); 17 | 18 | for (let i = 0; i < doms.length; i++) { 19 | const template = doms[i]; 20 | let component_name = template.getAttribute('component-name'); 21 | 22 | if (!component_name) { 23 | continue; 24 | } 25 | 26 | if ('yd-main' == component_name) { 27 | main = template; 28 | } else { 29 | templates[component_name] = template; 30 | } 31 | } 32 | 33 | return templates; 34 | } 35 | 36 | /** 37 | * 字符串拆分为数组 如果不为空的话 38 | * @param {*} str 39 | * @param {*} splitter 40 | */ 41 | function splitStrOrNull(str, splitter) { 42 | return ('string' == typeof str) && str.length > 0 ? str.split(splitter) : []; 43 | } 44 | 45 | /** 46 | * 将模板转换为组件 47 | * @param {*} templates 48 | */ 49 | function templatesTocomponents(templates) { 50 | Object.keys(templates).forEach((component_name) => { 51 | const template = templates[component_name]; 52 | let definition = { 53 | props: splitStrOrNull(template.getAttribute('props'), ','), 54 | template: template 55 | }; 56 | 57 | // 如果有定义则合并 58 | if ('object' == typeof components[component_name]) { 59 | Object.assign(definition, components[component_name]); 60 | } 61 | 62 | Vue.component(component_name, definition); 63 | }) 64 | } 65 | 66 | // 转换模板到列表 67 | templates = strToDoms(template); 68 | 69 | /** 70 | * 添加用户 登录表单 yd-login-form 71 | * @event {*} on-submit 72 | */ 73 | components['yd-login-form'] = { 74 | data: function () { 75 | return { 76 | form: {}, 77 | rules: { 78 | // 账号不能重复 79 | email: [ 80 | { required: true, message: '账号不能为空', trigger: 'blur' }, 81 | { max: 5, message: '账号最长5个字符', trigger: 'blur' }, 82 | { 83 | validator: function (rule, email, callback) { 84 | app.getUser(email) ? callback(new Error('账号已经存在')) : callback(); 85 | }, 86 | trigger: 'manual' 87 | } 88 | ], 89 | passwd: [ 90 | { required: true, message: '密码不能为空', trigger: 'blur' }, 91 | ] 92 | } 93 | } 94 | }, 95 | methods: { 96 | onSubmit: function (event) { 97 | this.$refs.form.validate((valid) => { 98 | if (valid) { 99 | this.$emit('on-submit', this.form.email, this.form.passwd); 100 | } 101 | }); 102 | 103 | } 104 | } 105 | } 106 | 107 | /** 108 | * 行内 地图切换 109 | */ 110 | components['yd-line-map'] = { 111 | methods: { 112 | // 移动到新地图 113 | moveToNewMap: function (email, mid) { 114 | app.game_list[email].moveToNewMap(mid); 115 | } 116 | } 117 | } 118 | 119 | /** 120 | * 行内 团队操作 121 | */ 122 | components['yd-line-team'] = { 123 | methods: { 124 | // 创建队伍 125 | createdTeam: function (email, map_id) { 126 | console.log('createdTeam', email, map_id); 127 | app.game_list[email].createdTeam(map_id); 128 | }, 129 | // 获取团队列表 130 | getTeamList: function (email, map_id) { 131 | console.log('getTeamList', email, map_id); 132 | app.game_list[email].getTeamList(map_id); 133 | return null; 134 | }, 135 | // 离开队伍 136 | leaveTeam: function (email) { 137 | console.log('leaveTeam', email); 138 | app.game_list[email].leaveTeam(); 139 | }, 140 | startCombat: function (email, fighting, combat) { 141 | console.log('startCombat', email, fighting); 142 | if (fighting) { 143 | app.getGame(email).startCombat(combat) 144 | } 145 | } 146 | } 147 | } 148 | 149 | /** 150 | * 行内 当前地图队伍列表 151 | */ 152 | components['yd-line-teams'] = { 153 | methods: { 154 | // 计算时间差 155 | diffTime: function (start_bat_at) { 156 | return start_bat_at && (new Date().getTime()) - (new Date(start_bat_at).getTime()) 157 | }, 158 | // 获取团队列表 159 | getTeamList: function (email, map_id) { 160 | console.log('getTeamList', email, map_id); 161 | app.game_list[email].getTeamList(map_id); 162 | return null; 163 | }, 164 | // 加入队伍 165 | addTeam: function (email, item_id) { 166 | console.log('addTeam', email, item_id); 167 | app.game_list[email].addTeam(item_id) 168 | } 169 | } 170 | } 171 | 172 | /** 173 | * 场景列表 174 | */ 175 | components['yd-line-screens'] = { 176 | methods: { 177 | switchCombatScreen: function (email, id) { 178 | app.getUser(email).team.to_combat = id; 179 | app.getGame(email).switchCombatScreen(id); 180 | } 181 | } 182 | } 183 | 184 | // 统一转换为组件并注册 185 | templatesTocomponents(templates); 186 | 187 | // 暴露对象 188 | return { 189 | main: main, 190 | setApp: function (_app) { 191 | app = _app; 192 | } 193 | }; 194 | }); 195 | -------------------------------------------------------------------------------- /assets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | .e-top { 6 | box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1) 7 | } 8 | 9 | .e-top h1.e-title { 10 | font-size: 14px; 11 | line-height:1.5; 12 | margin: 0; 13 | text-align: center; 14 | } 15 | 16 | .el-main.e-body { 17 | width: 1000px; 18 | margin: 5px auto 0; 19 | padding-bottom: 0px; 20 | box-shadow: 0 5px 20px 0 rgba(0, 0, 0, 0.1) 21 | } 22 | 23 | .e-main { 24 | margin: auto; 25 | text-align: center; 26 | } 27 | 28 | .e-main .el-main { 29 | padding: 0; 30 | } 31 | 32 | /* 用户表格 操作 按钮间距 */ 33 | .e-operating .cell > button.el-button { 34 | margin-left: 0; 35 | } 36 | 37 | .top-tools { 38 | text-align: right; 39 | } 40 | 41 | /* 抄袭配色 */ 42 | .e-danger span { 43 | color: #f56c6c; 44 | } 45 | .e-danger span { 46 | color: #f56c6c; 47 | } 48 | .e-success span { 49 | color: #67c23a; 50 | } 51 | .e-warning span { 52 | color: #e6a23c; 53 | border-color: #e6a23c; 54 | } 55 | .map-left, .map-right { 56 | width: 150px; 57 | } 58 | 59 | .map-left { 60 | text-align: left; 61 | } 62 | 63 | .map-right { 64 | text-align: right; 65 | } 66 | 67 | .map-left a, .map-right a { 68 | display: block; 69 | } 70 | 71 | .e-screens { 72 | width: 285px; 73 | margin-top: 5px; 74 | height: 215px; 75 | } 76 | 77 | .e-screens > div { 78 | padding: 8px 15px; 79 | } 80 | 81 | .e-screens button.el-button { 82 | width: 75px; 83 | margin: 6px 5px; 84 | text-align: center; 85 | padding: 7px 5px; 86 | } 87 | 88 | .el-footer { 89 | font-weight: 500; 90 | font-size: 14px; 91 | } 92 | -------------------------------------------------------------------------------- /auto_login.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name 云顶修仙 自动登录 3 | // @namespace https://www.qs5.org/?ydxx_auto_login 4 | // @version 0.1 5 | // @description 云顶修仙 自动登录 6 | // @author ImDong 7 | // @match http://yundingxx.com:3366/login 8 | // @grant none 9 | // ==/UserScript== 10 | 11 | (function () { 12 | 'use strict'; 13 | 14 | let bcode = location.hash.match(/#autologin=([^\$]+)/); 15 | if (!bcode) { 16 | return; 17 | } 18 | 19 | let info = JSON.parse(decodeURIComponent(atob(bcode[1]))); 20 | if (info) { 21 | document.getElementById('email').value = info.email; 22 | document.getElementById('pwd').value = info.pwd; 23 | window.is_r = true; 24 | document.getElementById('login-sub').click() 25 | } 26 | 27 | })(); 28 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 |{{ scope.row.status_msg || scope.row.status }}
33 |将退出此用户并打开官网页面,
83 |如安装插件则可实现自动登录。
84 |