// ============================================================================ // SubGameHooks.js —— 二七王子游戏接入入口(实现骨架,本阶段空函数,B/F 阶段逐个填充) // 平台契约文件(00/01/02 转发壳)会把同名平台接口转发到这里。只需实现【本游戏需要的】hook, // 未实现的接口由转发壳走空操作(A)/平台默认返回值(B)/模板默认 UI 渲染(D)。严格 ES5。 // 接口清单与默认值见 spec §9 / 文档 06。 // ============================================================================ var SubGameHooks = window.SubGameHooks || {}; // ===== gameHallImport.*(大厅,9) ===== // [A] hallAppStart():大厅应用启动(注:与 Game_Modify.appStart 同名冲突,故加 hall 前缀区分)。 // SubGameHooks.hallAppStart = function () { /* ... */ }; // [A] jumpMenuScene():大厅跳转菜单场景。 // SubGameHooks.jumpMenuScene = function () { /* ... */ }; // [A] gameStart():大厅游戏开始入口。 // SubGameHooks.gameStart = function () { /* ... */ }; // [A] setGameList():设置游戏列表。 // SubGameHooks.setGameList = function () { /* ... */ }; // [A] clearGameinfo():清除游戏信息。 // SubGameHooks.clearGameinfo = function () { /* ... */ }; // [A] getWebdata(data):获取网络数据回调。 // SubGameHooks.getWebdata = function (data) { /* ... */ }; // [B] isInstalled(data):检查是否已安装;无 hook 时默认:return 1。 // SubGameHooks.isInstalled = function (data) { /* ... */ }; // [A] up_imgurl(recid, photo):更新图片 URL。 // SubGameHooks.up_imgurl = function (recid, photo) { /* ... */ }; // [A] getphoto(recid, photo):获取玩家头像。 // SubGameHooks.getphoto = function (recid, photo) { /* ... */ }; // ===== Game_Modify.*(事件/交互,来自 01,9) ===== // --------------------------------------------------------------------------- // 精灵交互 6 件套:【只做转发,不写任何业务逻辑】 // // 平台事件到达框架的完整链路: // 引擎 → Game_Modify.xxx(01 转发壳) → SubGameHooks.xxx(本文件) → SpriteEventController.handleXxx // → 按精灵 ID 分发到各 UI 组件注册的 handler // // 这一段接不上,整条链就是断的:转发壳里已注册的 13 号战绩按钮、150 号帮助按钮、 // 248 号协议勾选(绘制回调里叠绘对钩)全都收不到事件,且不报错、只是静默失效。 // 参数顺序必须与 SpriteEventController.handleXxx 的签名逐个对齐——错位会让事件【静默失效】。 // 防御风格与转发壳一致:先判断依赖存在再转发。 // --------------------------------------------------------------------------- // [A] utlmousedown(gameid, spid, downx, downy, no1, no2, no3, no4, no5, no6):精灵按下事件;子游戏转发到 SpriteEventController。 SubGameHooks.utlmousedown = function (gameid, spid, downx, downy, no1, no2, no3, no4, no5, no6) { if (typeof SpriteEventController === 'undefined') { return; } return SpriteEventController.handleMouseDown(gameid, spid, downx, downy, no1, no2, no3, no4, no5, no6); }; // [A] utlmousedown_nomove(gameid, spid, downx, downy, timelong, no1, no2, no3, no4, no5):精灵按下(无移动)事件。 SubGameHooks.utlmousedown_nomove = function (gameid, spid, downx, downy, timelong, no1, no2, no3, no4, no5) { if (typeof SpriteEventController === 'undefined') { return; } return SpriteEventController.handleMouseDownNoMove(gameid, spid, downx, downy, timelong, no1, no2, no3, no4, no5); }; // [A] mouseup(gameid, spid_down, downx, downy, spid_up, upx, upy, timelong, no1, no2):精灵抬起事件。 SubGameHooks.mouseup = function (gameid, spid_down, downx, downy, spid_up, upx, upy, timelong, no1, no2) { if (typeof SpriteEventController === 'undefined') { return; } return SpriteEventController.handleMouseUp(gameid, spid_down, downx, downy, spid_up, upx, upy, timelong, no1, no2); }; // [A] utlmousemove(gameid, spid, downx, downy, movex, movey, timelong, offmovex, offmovey, no1):精灵移动事件。 SubGameHooks.utlmousemove = function (gameid, spid, downx, downy, movex, movey, timelong, offmovex, offmovey, no1) { if (typeof SpriteEventController === 'undefined') { return; } return SpriteEventController.handleMouseMove(gameid, spid, downx, downy, movex, movey, timelong, offmovex, offmovey, no1); }; // [A] utlgamemydrawbegin(gameid, spid, times, timelong, no2, no3, no4, no5, no6, no7):精灵绘制开始事件。 SubGameHooks.utlgamemydrawbegin = function (gameid, spid, times, timelong, no2, no3, no4, no5, no6, no7) { if (typeof SpriteEventController === 'undefined') { return; } return SpriteEventController.handleDrawBegin(gameid, spid, times, timelong, no2, no3, no4, no5, no6, no7); }; // [A] gamemydraw(gameid, spid, times, timelong, no2, no3, no4, no5, no6, no7):精灵绘制帧事件。 SubGameHooks.gamemydraw = function (gameid, spid, times, timelong, no2, no3, no4, no5, no6, no7) { if (typeof SpriteEventController === 'undefined') { return; } return SpriteEventController.handleDraw(gameid, spid, times, timelong, no2, no3, no4, no5, no6, no7); }; // [A] OpenCreateRoom():打开创建房间界面。 // SubGameHooks.OpenCreateRoom = function () { /* ... */ }; // [A] CloseCreateRoom():关闭创建房间界面。 // SubGameHooks.CloseCreateRoom = function () { /* ... */ }; // [A] setRoomDes(roomcode, asetcount, roomtype):设置房间描述文案。 // SubGameHooks.setRoomDes = function (roomcode, asetcount, roomtype) { /* ... */ }; // ===== Game_Modify.*(生命周期/回调,来自 02,45) ===== // [A] appStart():游戏应用启动(与 gameHallImport.appStart 语义不同,对应 Game_Modify 侧启动)。 // SubGameHooks.appStart = function () { /* ... */ }; // [A] StartWar(_msg):开战(开局)回调,解包开局数据并初始化游戏状态。 // SubGameHooks.StartWar = function (_msg) { /* ... */ }; // [A] _ReceiveData(_msg):接收服务端主动推送的数据包,派发给对应业务处理器。 // SubGameHooks._ReceiveData = function (_msg) { /* ... */ }; // [A] createRoom(_roomtype, _infinite):前端发起创建房间请求。 // SubGameHooks.createRoom = function (_roomtype, _infinite) { /* ... */ }; // [A] myJoinRoom(_msg):自己成功进入房间回调。 // SubGameHooks.myJoinRoom = function (_msg) { /* ... */ }; // [A] Reconnect(_deskinfo):重连回调,用服务端返回的桌面信息恢复游戏界面。 // SubGameHooks.Reconnect = function (_deskinfo) { /* ... */ }; // [A] stopAllSounds():停止所有声音(切换到后台时触发)。 // SubGameHooks.stopAllSounds = function () { /* ... */ }; // [A] Free(_msg):投票解散确认回调。 // SubGameHooks.Free = function (_msg) { /* ... */ }; // [A] DeskInfo(_msg):牌局已开始时加入牌桌的数据同步(实现见文末「网络与对局」一段)。 // [A] breakRoom():开局后自己退出房间触发。 // SubGameHooks.breakRoom = function () { /* ... */ }; // [A] playerJoinRoom(seat):其他玩家加入房间通知。 // SubGameHooks.playerJoinRoom = function (seat) { /* ... */ }; // [A] playerLeaveRoom(seat):其他玩家离开房间通知。 // SubGameHooks.playerLeaveRoom = function (seat) { /* ... */ }; // [A] updateScene():更新游戏界面(分辨率变化等)。 // SubGameHooks.updateScene = function () { /* ... */ }; // [A] closeGameScene():关闭游戏界面。 // SubGameHooks.closeGameScene = function () { /* ... */ }; // [A] playerOffline(seat):玩家掉线通知。 // SubGameHooks.playerOffline = function (seat) { /* ... */ }; // [A] playerOnline(seat):玩家上线通知。 // SubGameHooks.playerOnline = function (seat) { /* ... */ }; // [A] playerphonestate(seat, type):玩家电话状态变化通知。 // SubGameHooks.playerphonestate = function (seat, type) { /* ... */ }; // [A] shakeEvent():设备摇一摇事件。 // SubGameHooks.shakeEvent = function () { /* ... */ }; // [A] calResult(inputArr):计算结算结果。 // SubGameHooks.calResult = function (inputArr) { /* ... */ }; // [A] onEnterMainScene(roomtype):进入游戏主场景。 // SubGameHooks.onEnterMainScene = function (roomtype) { /* ... */ }; // [A] onExitMainScene():退出游戏主场景。 // SubGameHooks.onExitMainScene = function () { /* ... */ }; // [A] onGameConfig(_gameConfig):获取游戏配置时触发。 // SubGameHooks.onGameConfig = function (_gameConfig) { /* ... */ }; // [A] onEnterVideo():进入牌局回放模式。 // SubGameHooks.onEnterVideo = function () { /* ... */ }; // [A] onSurrender(_msg):收到投降回包。 // SubGameHooks.onSurrender = function (_msg) { /* ... */ }; // [A] onReady(seat):玩家准备触发。 // SubGameHooks.onReady = function (seat) { /* ... */ }; // [B] getRoomInfo(roomtype, type, tea):获取房间描述文案;无 hook 时默认:"房间描述" + roomtype。 // SubGameHooks.getRoomInfo = function (roomtype, type, tea) { /* ... */ }; // [B] getStarLimit(roomtype):获取星星场下限分数;无 hook 时默认:return 2001000。 // SubGameHooks.getStarLimit = function (roomtype) { /* ... */ }; // [B] getMult(roomtype, type):获取星星场倍数;无 hook 时默认:return 10000。 // SubGameHooks.getMult = function (roomtype, type) { /* ... */ }; // [B] getFullRoomInfo(roomtype):获取房间完整信息描述;无 hook 时默认:"房间描述" + roomtype。 // SubGameHooks.getFullRoomInfo = function (roomtype) { /* ... */ }; // [A] onOpenHelp(spid):打开帮助页面。 // SubGameHooks.onOpenHelp = function (spid) { /* ... */ }; // [A] onMainMenuScene():显示大厅主菜单界面。 // SubGameHooks.onMainMenuScene = function () { /* ... */ }; // [A] onCheckInput(_result):数字输入框确认回调。 // SubGameHooks.onCheckInput = function (_result) { /* ... */ }; // [A] onCreateRoom(_data):创建房间成功后触发。 // SubGameHooks.onCreateRoom = function (_data) { /* ... */ }; // [B] getLeaveLimit(roomtype):获取离场限制局数;无 hook 时默认:return 10。 // SubGameHooks.getLeaveLimit = function (roomtype) { /* ... */ }; // [A] onLocationInfo(_locationInfo):成功获取定位信息回调。 // SubGameHooks.onLocationInfo = function (_locationInfo) { /* ... */ }; // [A] onCreateDesk(roomtype):进入游戏界面创建牌桌之前触发。 // SubGameHooks.onCreateDesk = function (roomtype) { /* ... */ }; // [B] getVideoByRoomType(roomtype):获取该房型是否开启视频功能;无 hook 时默认:return 1。 // SubGameHooks.getVideoByRoomType = function (roomtype) { /* ... */ }; // [B] getRoomTopDescAry(roomtype):获取房间顶部描述文案数组;无 hook 时默认:["" + roomtype]。 // SubGameHooks.getRoomTopDescAry = function (roomtype) { /* ... */ }; // [A] getShareRoom(_msg):星星场房间列表数据。 // SubGameHooks.getShareRoom = function (_msg) { /* ... */ }; // [A] myExitRoom(seat):玩家自己退出房间。 // SubGameHooks.myExitRoom = function (seat) { /* ... */ }; // [A] changeSeat(seat1, seat2):接收到换座包触发。 // SubGameHooks.changeSeat = function (seat1, seat2) { /* ... */ }; // [A] onCloseVip():关闭 VIP 选项时调用。 // SubGameHooks.onCloseVip = function () { /* ... */ }; // [B] getRoomMode(roomtype):判断房间模式(0=非金币场);无 hook 时默认:return 0。 // SubGameHooks.getRoomMode = function (roomtype) { /* ... */ }; // [B] getMaxPlayerCount(roomtype):获取房间最大玩家数量;无 hook 时默认:return Game_Config.Max.PlayerCnt || 4。 // SubGameHooks.getMaxPlayerCount = function (roomtype) { /* ... */ }; // [D] updatePlayerInfoUI(targetSeat):更新指定座位玩家信息 UI;无 hook 时模板提供【可变人数】默认渲染 // (头像/昵称/分数随人数 2/3/4 贴合各玩家,布局见 01 的 Game_Modify.PLAYER_INFO_LAYOUT)。子游戏可用 hook 完全接管。 // SubGameHooks.updatePlayerInfoUI = function (targetSeat) { /* ... */ }; // ===== Game_Modify.*(桌面聊天/语音气泡,3) ===== // 以下接口无 hook 时,模板提供【可变人数】默认渲染(气泡随人数贴合各玩家,布局见 01 的 Game_Modify.BUBBLE_LAYOUT)。 // [D] ShowChat(seat, text):显示桌面文字聊天气泡(seat 为绝对座位)。 // SubGameHooks.ShowChat = function (seat, text) { /* ... */ }; // [D] gameui_play_voice(uiIndex):播放桌面语音气泡(uiIndex 为相对显示位序)。 // SubGameHooks.gameui_play_voice = function (uiIndex) { /* ... */ }; // [D] gameui_stop_voice(uiIndex):停止桌面语音气泡。 // SubGameHooks.gameui_stop_voice = function (uiIndex) { /* ... */ }; //—— 网络与对局(B 阶段接线)—— //平台入口只解包转交,业务全在 net/ 的 handler 里。 // --------------------------------------------------------------------------- // 【room.mySeat 的唯一写入处】(SSOT) // // 为什么不能只在 appStart 里取一次: // 平台 12_Logic.js:389 调 Game_Modify.appStart() 时,C_Player 还只是一个 // `var C_Player;`——`C_Player = new Player(-1)` 要到【同一个函数】的第 480 行才执行。 // 此刻 typeof C_Player === 'undefined',守卫为假、整个 appStart 空转; // 之后玩家登录、07_Desk.js 里 C_Player.SetSeat() 拿到真座位,但 appStart 再也不会被调用, // 于是 mySeat 永远停在 -1:发包带 seat:-1(服务端 check_player 必然拒绝)、 // StartWar 差异化下发找不到本座位而整包丢弃、SeatMap.toDisplay(-1) 抛错。 // ——而且【全程无报错】,这正是它能潜伏下来的原因。 // // 因此改为:单一写入处 + 多个调用点。凡是平台已经确定座位的入口都同步一次, // 不赌「所有路径都会经过某一个入口」。 // // 幂等:可重复调用;C_Player 不可用或座位非法时【保持原值不写】—— // 绝不用 -1 覆盖已同步好的正确座位。 // --------------------------------------------------------------------------- SubGameHooks._syncMySeat = function () { if (typeof C_Player === 'undefined' || !C_Player) { return; } var seat = C_Player.seat; if (seat !== 0 && seat !== 1 && seat !== 2) { return; } EQW_GameState.room.mySeat = seat; }; // 对局推送。_msg 是完整包 {app, route, rpc, data} // (平台 12_Logic.js 按 route 分流,只有子游戏路由 erqiwang 才到这里) SubGameHooks._ReceiveData = function (_msg) { if (!_msg) { return; } EQW_Dispatcher.dispatch({ rpc: _msg.rpc, data: _msg.data }); }; // 开局。可能差异化下发(sendtype:1 + seatlist[]) // 【必须先同步座位】handleStartWar 要按 mySeat 在 seatlist[] 里认领自己那份 SubGameHooks.StartWar = function (_msg) { SubGameHooks._syncMySeat(); EQW_ResyncHandler.handleStartWar(_msg); }; // 断线重连:拿到 get_deskinfo 的全量快照 // (handleDeskinfo 内的 GameState.reset() 不清 room.mySeat,此处同步的值不会被冲掉) SubGameHooks.Reconnect = function (_deskinfo) { SubGameHooks._syncMySeat(); EQW_ResyncHandler.handleDeskinfo(_deskinfo); }; // 中途进桌:牌局【已经开始】,但自己走的是「加入房间」而非「重连」这条路径。 // 平台 07_Desk.js 在 self_join_room 且 battlestate==1 时走 Game_Modify.DeskInfo(_msg.data.deskinfo), // 与 self_login 的 Game_Modify.Reconnect(_msg.data.deskinfo) 是两条并列分支—— // 不接这个 hook,整份 deskinfo 会被 02 转发壳静默丢弃(无 hook 即 no-op),界面全空且不报错。 // // 【入参与 Reconnect 完全同源、且已按【自己的】座位裁剪】: // 两条路径的 deskinfo 都出自服务端 server/youle/server_room/class.return.js —— 进房走 // self_join_room 的 get_deskinfo(o_room, o_player.gameinfo.seat),登录走 self_login 的 // get_deskinfo(o_player.gameinfo.o_room, o_player.gameinfo.seat),同一个函数、同一个 // 「本人座位」入参。get_deskinfo 内部的可见性裁剪(底牌/埋牌底牌只给庄家、亮牌只给闲家、 // mustcard 只给轮到的那家)因此对中途进桌一样生效,不会把别人的视角发过来。 // 故直接复用同一条重画路径(前端 04 §3「重连 = 重画」),不为它单写一套渲染。 SubGameHooks.DeskInfo = function (_deskinfo) { SubGameHooks._syncMySeat(); EQW_ResyncHandler.handleDeskinfo(_deskinfo); }; // 重连但未开战:没有牌局数据,无需重画对局 SubGameHooks.ReconnectNoMakewar = function () { SubGameHooks._syncMySeat(); console.log('[EQW] 重连(未开战)'); }; // 解散。_msg 即 Desk.deskfree,可能为 null(首局发牌前解散) SubGameHooks.Free = function (_msg) { EQW_ResultHandler.handleFree(_msg); }; // 房间信息(房号 / 总局数 / roomtype 位串) // 平台三条进桌路径(07_Desk.js:358 登录 / 510 建房 / 631 进房)都在 C_Player.SetSeat() 之后、 // Reconnect / StartWar / DeskInfo 之前调它——是覆盖面最广的一个同步点 SubGameHooks.setRoomDes = function (roomcode, asetcount, roomtype) { SubGameHooks._syncMySeat(); EQW_ResyncHandler.handleSetRoomDes(roomcode, asetcount, roomtype); }; // 换座(平台 07_Desk.js:186-191 已先把 C_Player.seat 改成新座位再回调这里)。 // 这是唯一「座位会中途变化、且不重走 setRoomDes」的路径,漏掉这里 mySeat 会静默过期 SubGameHooks.changeSeat = function (seat1, seat2) { SubGameHooks._syncMySeat(); }; // 启动编排。B 阶段只记下自己的座位;UI 初始化留给 C 阶段 // 注意:此刻 C_Player 通常还没创建(见 _syncMySeat 上方注释),座位靠后续入口补齐 SubGameHooks.appStart = function () { SubGameHooks._syncMySeat(); if (typeof Desk !== 'undefined' && Desk.roomtype) { EQW_GameState.room.options = EQW_RoomOptions_Parse.parse(Desk.roomtype); } }; window.SubGameHooks = SubGameHooks; if (typeof module !== 'undefined' && module.exports) { module.exports = SubGameHooks; }