diff --git a/client/js/00_Surface/07_Desk.js b/client/js/00_Surface/07_Desk.js index 670ce82..0e4ba8c 100644 --- a/client/js/00_Surface/07_Desk.js +++ b/client/js/00_Surface/07_Desk.js @@ -911,7 +911,7 @@ Desk.send_text=function(_msg){//发送消息 if(!_msg.data.type){ var pobj = Desk.GetPlayerBySeat(_msg.data.seat); GameUI.playTextVoice(_msg.data.text,pobj.sex); - GameUI.ShowChat(Logic.ChangeToStatus(C_Player.seat,_msg.data.seat),_msg.data.text); + GameUI.ShowChat(_msg.data.seat,_msg.data.text); GameUI.addCommunion(Logic.ChangeToStatus(C_Player.seat,_msg.data.seat),1,_msg.data.text,0,null,pobj.sex); }else if(_msg.data.type == 1){//全服公告 GameUI.CloseChat(); @@ -929,7 +929,7 @@ Desk.send_text=function(_msg){//发送消息 var idx = Number(_msg.data.text); _msg.data.text = Game_Config.Info.TextContent[(idx - 1)%Game_Config.Info.TextContent.length]; GameUI.playTextVoice(_msg.data.text); - GameUI.ShowChat(Logic.ChangeToStatus(C_Player.seat,_msg.data.seat),_msg.data.text); + GameUI.ShowChat(_msg.data.seat,_msg.data.text); GameUI.addCommunion(Logic.ChangeToStatus(C_Player.seat,_msg.data.seat),1,_msg.data.text,0); } } diff --git a/client/js/00_Surface/11_GameUI.js b/client/js/00_Surface/11_GameUI.js index cab2169..03223a8 100644 --- a/client/js/00_Surface/11_GameUI.js +++ b/client/js/00_Surface/11_GameUI.js @@ -5779,7 +5779,12 @@ GameUI.ShowEmotion = function(v, ind) { } -GameUI.ShowChat = function(v, text) { +GameUI.ShowChat = function(seat, text) { + if (Game_Modify.ShowChat) { + Game_Modify.ShowChat(seat, text); + return; + } + var v = Logic.ChangeToStatus(C_Player.seat,seat); text = String(text); set_self(204, 7, "", 0, 0); if (v == 0) { @@ -6085,6 +6090,7 @@ GameUI.play_voice = function(_msg, type, v) { function gameui_play_voice(v) { v = Number(v); + var bTemp = false; if (v >= ConstVal.Com_History.content_tag) { bTemp = true; @@ -6092,6 +6098,10 @@ function gameui_play_voice(v) { if (bTemp) { play_ani(1, ConstVal.Com_History.f_spid + "add" + v, 43, 1, 3, 0, 600, 0, 0, 0, 0, 0, 0); } else { + if(Game_Modify.gameui_play_voice){ + Game_Modify.gameui_play_voice(v); + return; + } var x = Game_Config.Voice.VoiceLoc[v][0]; var y = Game_Config.Voice.VoiceLoc[v][1]; var dis_x = Game_Config.Voice.VoiceDis[0]; @@ -6128,10 +6138,15 @@ function gameui_stop_voice(v) { if (v >= ConstVal.Com_History.content_tag) { bTemp = true; } + if (bTemp) { play_ani(0, ConstVal.Com_History.f_spid + "add" + v, 43, 1, 3, 0, 600, 0, 0, 0, 0, 0, 0); set_self(ConstVal.Com_History.f_spid + "add" + v, 43, 3, 0, 0); } else { + if(Game_Modify.gameui_stop_voice){ + Game_Modify.gameui_stop_voice(v); + return; + } set_self(612 + v, 37, 0, 0, 0); set_self(642 + v, 37, 0, 0, 0); play_ani(0, 642 + v, 43); diff --git a/client/js/01_SubGame/01_SubGame_modify.js b/client/js/01_SubGame/01_SubGame_modify.js index 94c2e67..5c7c958 100644 --- a/client/js/01_SubGame/01_SubGame_modify.js +++ b/client/js/01_SubGame/01_SubGame_modify.js @@ -46,6 +46,52 @@ Game_Modify.CreateRoomData = { Type_2: 0 }; +// ---------- 玩家信息/头像默认布局(供 02 转发壳 updatePlayerInfoUI 的默认实现使用)---------- +// 02_SubGame_Input.js 的 updatePlayerInfoUI 在【无 SubGameHooks 实现】时提供默认头像等 UI 渲染; +// 转发壳不得引用 codes,故把默认实现所需布局放在此处(Game_Modify 下),与子游戏 codes 内 +// GameView.updatePlayerInfoUI 使用的布局逻辑保持一致。子游戏若自定义头像 UI,则用 hook 覆盖、此配置不生效。 +// POSITIONS[configIndex]:ind0=南(自己)/ind1=东/ind2=北/ind3=西;渲染精灵 346+ind 底框、376+ind 头像框、 +// 406+ind 昵称、436+ind 分数、116+ind 头像图、群组 43+ind。坐标基于设计分辨率 1280x720。 +Game_Modify.PLAYER_INFO_LAYOUT = { + POSITIONS: { + 0: { FRAME_X: 6, FRAME_Y: 592, FRAME_WIDTH: 90, FRAME_HEIGHT: 115, + AVATAR: { OFFSET_X: 5, OFFSET_Y: 5, WIDTH: 80, HEIGHT: 80, + NAME_OFFSET_X: -400, NAME_OFFSET_Y: 888, SCORE_OFFSET_X: 0, SCORE_OFFSET_Y: 88 }, + NAME_CENTER_ALIGN: true }, + 1: { FRAME_X: 1186, FRAME_Y: 85, FRAME_WIDTH: 90, FRAME_HEIGHT: 115, + AVATAR: { OFFSET_X: 5, OFFSET_Y: 5, WIDTH: 80, HEIGHT: 80, + NAME_OFFSET_X: 905, NAME_OFFSET_Y: -888, SCORE_OFFSET_X: 0, SCORE_OFFSET_Y: 88 }, + NAME_CENTER_ALIGN: true }, + 2: { FRAME_X: 1006, FRAME_Y: 5, FRAME_WIDTH: 90, FRAME_HEIGHT: 115, + AVATAR: { OFFSET_X: 5, OFFSET_Y: 5, WIDTH: 80, HEIGHT: 80, + NAME_OFFSET_X: 905, NAME_OFFSET_Y: -888, SCORE_OFFSET_X: 0, SCORE_OFFSET_Y: 88 }, + NAME_CENTER_ALIGN: true }, + 3: { FRAME_X: 7, FRAME_Y: 59, FRAME_WIDTH: 90, FRAME_HEIGHT: 115, + AVATAR: { OFFSET_X: 5, OFFSET_Y: 5, WIDTH: 80, HEIGHT: 80, + NAME_OFFSET_X: -605, NAME_OFFSET_Y: -888, SCORE_OFFSET_X: 0, SCORE_OFFSET_Y: 88 }, + NAME_CENTER_ALIGN: true } + }, + TEXT_STYLE: { NAME_FONT_SIZE: 14, NAME_MAX_LENGTH: 6, SCORE_FONT_SIZE: 12, CHAR_WIDTH: 9 } +}; + +// ---------- 桌面聊天/语音气泡默认布局(供 02 转发壳 ShowChat/gameui_play_voice/gameui_stop_voice 使用)---------- +// 平台默认按固定显示位序取 Game_Config.Chat/Voice 的固定坐标(固定人数模式);此处提供可变人数版: +// 按 updatePlayerInfoUI 的 configIndex(物理位) 定位,使气泡随人数(2/3/4)贴合各玩家。 +// 仅定义每个物理位(0南/1东/2北/3西)的气泡框左上角锚点 X/Y 与朝向 IS_LEFT;气泡内边距/字宽/显示时长等 +// 尺寸复用平台 Game_Config.Chat/Voice/Max(与人数无关)。精灵:聊天 背景552+ind/文字582+ind, +// 语音 背景612+ind/内容642+ind。IS_LEFT=true 气泡向右展开(用 IMG_LEFT)、false 向左展开(用 IMG_RIGHT)。 +// 坐标基于 1280x720,锚点参考 PLAYER_INFO_LAYOUT 各位底框,可按实际界面微调。 +Game_Modify.BUBBLE_LAYOUT = { + IMG_LEFT: 83, // 左向气泡背景图资源ID(气泡向右展开) + IMG_RIGHT: 86, // 右向气泡背景图资源ID(气泡向左展开) + POSITIONS: { + 0: { X: 100, Y: 540, IS_LEFT: true }, // 南(自己) + 1: { X: 1176, Y: 120, IS_LEFT: false }, // 东 + 2: { X: 970, Y: 20, IS_LEFT: false }, // 北 + 3: { X: 120, Y: 70, IS_LEFT: true } // 西 + } +}; + // ---------- 接口转发壳(A 类:无 hook 即 no-op) ---------- Game_Modify.utlmousedown = function (gameid, spid, downx, downy, no1, no2, no3, no4, no5, no6) { diff --git a/client/js/01_SubGame/02_SubGame_Input.js b/client/js/01_SubGame/02_SubGame_Input.js index 36bf396..0e11ef2 100644 --- a/client/js/01_SubGame/02_SubGame_Input.js +++ b/client/js/01_SubGame/02_SubGame_Input.js @@ -293,8 +293,210 @@ Game_Modify.getMaxPlayerCount = function (roomtype) { return (Game_Config.Max && Game_Config.Max.PlayerCnt) || 4; }; -// B 类:更新玩家信息 UI,无 hook 返回 false(让平台默认处理) +// 内部辅助(非平台接口):把相对显示位序 relPos 映射为 { ind:精灵槽, configIndex:物理布局位 }。 +// 2/3 人时 ind≠configIndex;无效返回 null。updatePlayerInfoUI 与桌面气泡默认实现共用此映射, +// 保证头像面板与聊天/语音气泡落在同一玩家位。 +Game_Modify._resolveDisplaySlot = function (relPos, playerCount) { + var ind = -1, configIndex = -1; + if (playerCount === 2) { + if (relPos === 0) { ind = 0; configIndex = 0; } + else if (relPos === 1) { ind = 1; configIndex = 2; } + } else if (playerCount === 3) { + if (relPos === 0) { ind = 0; configIndex = 0; } + else if (relPos === 1) { ind = 1; configIndex = 1; } + else if (relPos === 2) { ind = 2; configIndex = 3; } + } else { + if (relPos === 0) { ind = 0; configIndex = 0; } + else if (relPos === 1) { ind = 1; configIndex = 1; } + else if (relPos === 2) { ind = 2; configIndex = 2; } + else if (relPos === 3) { ind = 3; configIndex = 3; } + } + if (ind === -1) return null; + return { ind: ind, configIndex: configIndex }; +}; + +// B 类:更新指定座位的玩家信息 UI(昵称/分数/头像)。 +// 有 hook 则委托子游戏;无 hook 时由本模板提供默认头像等 UI 的显示逻辑,其逻辑与子游戏 +// codes 内 GameView.updatePlayerInfoUI 保持一致,布局取自 Game_Modify.PLAYER_INFO_LAYOUT(见 01)。 +// 精灵操作一律经框架 SpriteManager(setPosition/setSize/setText/show|hideGroup),不直接调 +// set_self/set_group 等引擎原语;头像加载走平台 Func.up_imgurl(非精灵原语,SpriteManager 无对应能力)。 +// 不引用任何 codes 模块。平台 GameUI.updatePlayerInfoUI 会对每个座位逐一调用本接口,故按单座位渲染。 Game_Modify.updatePlayerInfoUI = function (targetSeat) { if (window.SubGameHooks && SubGameHooks.updatePlayerInfoUI) return SubGameHooks.updatePlayerInfoUI(targetSeat); - return false; + + // ---- 模板默认:与 GameView.updatePlayerInfoUI 一致的单座位渲染 ---- + if (typeof Desk === 'undefined' || typeof C_Player === 'undefined' || typeof SpriteManager === 'undefined') return false; + var layout = Game_Modify.PLAYER_INFO_LAYOUT; + if (!layout || !layout.POSITIONS) return false; + if (!Desk.PlayerList || targetSeat < 0 || targetSeat >= Desk.PlayerList.length) return false; + + var selfSeat = (typeof C_Player.seat === 'number') ? C_Player.seat : 0; + var playerCount = (Game_Config.Max && Game_Config.Max.PlayerCnt) || 4; + var relPos = (targetSeat - selfSeat + playerCount) % playerCount; + + // 显示槽 ind 与布局位 configIndex(2/3 人时两者不同) + var slot = Game_Modify._resolveDisplaySlot(relPos, playerCount); + if (!slot) return false; + var ind = slot.ind, configIndex = slot.configIndex; + + var player = Desk.PlayerList[targetSeat]; + // 空座位:隐藏该位玩家信息群组 + if (!player || !player.playerid || player.playerid === -1) { + SpriteManager.hideGroup(43 + ind); + return true; + } + + var posConfig = layout.POSITIONS[configIndex]; + if (!posConfig) return false; + + // 底框(346+ind) + if (posConfig.FRAME_X !== undefined) { + SpriteManager.setPosition(346 + ind, posConfig.FRAME_X, posConfig.FRAME_Y); + SpriteManager.setSize(346 + ind, posConfig.FRAME_WIDTH, posConfig.FRAME_HEIGHT); + } + + // 头像框(376+ind):基于底框 + 相对偏移 + var avatarX = posConfig.FRAME_X + posConfig.AVATAR.OFFSET_X; + var avatarY = posConfig.FRAME_Y + posConfig.AVATAR.OFFSET_Y; + var avatarWidth = posConfig.AVATAR.WIDTH; + var avatarHeight = posConfig.AVATAR.HEIGHT; + var nameOffsetX = posConfig.AVATAR.NAME_OFFSET_X || 0; + var nameOffsetY = posConfig.AVATAR.NAME_OFFSET_Y || 0; + var scoreOffsetX = posConfig.AVATAR.SCORE_OFFSET_X || 0; + var scoreOffsetY = posConfig.AVATAR.SCORE_OFFSET_Y || 0; + SpriteManager.setPosition(376 + ind, avatarX, avatarY); + SpriteManager.setSize(376 + ind, avatarWidth, avatarHeight); + + var textStyle = layout.TEXT_STYLE || {}; + var maxLen = textStyle.NAME_MAX_LENGTH || 8; + var charWidth = textStyle.CHAR_WIDTH || 7; + + // 昵称(406+ind) + var nickname = Func.subString(player.nickname || '', maxLen, true); + SpriteManager.setText(406 + ind, nickname); + var nameX = avatarX + nameOffsetX; + if (posConfig.NAME_CENTER_ALIGN) { + var textWidth = (typeof nickname.gblen === 'function') ? nickname.gblen() * charWidth : nickname.length * charWidth; + nameX = avatarX + avatarWidth / 2 - textWidth / 2; + } + SpriteManager.setPosition(406 + ind, nameX, avatarY + nameOffsetY); + + // 分数(436+ind) + var score = (typeof player.score === 'number') ? player.score : 0; + SpriteManager.setText(436 + ind, score); + var scoreX = avatarX + scoreOffsetX; + if (posConfig.NAME_CENTER_ALIGN) { + var scoreWidth = String(score).length * charWidth; + scoreX = avatarX + avatarWidth / 2 - scoreWidth / 2; + } + SpriteManager.setPosition(436 + ind, scoreX, avatarY + scoreOffsetY); + + // 头像图(116+ind):Func.up_imgurl 为平台头像加载接口(非引擎精灵原语) + if (player.avatar && typeof Func !== 'undefined' && typeof Func.up_imgurl === 'function') { + Func.up_imgurl(116 + ind, player.avatar); + } + + // 显示该座位玩家信息群组 + SpriteManager.showGroup(43 + ind); + return true; }; +// 桌面文字聊天气泡:有 hook 委托子游戏;无 hook 时按【可变人数】默认渲染,使气泡贴合各玩家。 +// seat 为绝对座位;经 SpriteManager 操作精灵(背景552+ind/文字582+ind),位置取自 Game_Modify.BUBBLE_LAYOUT +// 的物理位配置,尺寸(内边距/字宽/显示时长)复用 Game_Config.Chat/Max;不直接调引擎原语、不引用 codes。 +Game_Modify.ShowChat = function (seat, text) { + if (window.SubGameHooks && SubGameHooks.ShowChat) return SubGameHooks.ShowChat(seat, text); + GameUI.CloseChat(); + if (typeof Desk === 'undefined' || typeof C_Player === 'undefined' || typeof SpriteManager === 'undefined') return; + var layout = Game_Modify.BUBBLE_LAYOUT; + if (!layout || !layout.POSITIONS) return; + var playerCount = (Game_Config.Max && Game_Config.Max.PlayerCnt) || 4; + var relPos = (seat - C_Player.seat + playerCount) % playerCount; + var slot = Game_Modify._resolveDisplaySlot(relPos, playerCount); + if (!slot) return; + var pos = layout.POSITIONS[slot.configIndex]; + if (!pos) return; + var ind = slot.ind; + + text = String(text); + var chat = Game_Config.Chat || {}; + var dis_x = (chat.ChatDis && chat.ChatDis[0]) || 0; + var dis_y = (chat.ChatDis && chat.ChatDis[1]) || 0; + var text_w = chat.textwidth || 14; + var text_len = (typeof text.gblen === 'function') ? text.gblen() : text.length; + var bgW = text_len * text_w + dis_x * 2; + + SpriteManager.setText(582 + ind, text); + if (pos.IS_LEFT) { + SpriteManager.setPosition(552 + ind, pos.X, pos.Y); + SpriteManager.setPosition(582 + ind, pos.X + dis_x, pos.Y + dis_y); + SpriteManager.setImage(552 + ind, layout.IMG_LEFT); + } else { + SpriteManager.setPosition(552 + ind, pos.X - bgW, pos.Y); + SpriteManager.setPosition(582 + ind, pos.X - text_len * text_w - dis_x, pos.Y + dis_y); + SpriteManager.setImage(552 + ind, layout.IMG_RIGHT); + } + SpriteManager.setWidth(552 + ind, bgW); + SpriteManager.show(552 + ind); + SpriteManager.show(582 + ind); + + // 自动隐藏:复用平台配置的显示时长(观感与固定人数版一致),按槽位去重定时器 + var dur = (Game_Config.Max && Game_Config.Max.ShowChat) || 3000; + Game_Modify._chatHideTimers = Game_Modify._chatHideTimers || {}; + if (Game_Modify._chatHideTimers[ind]) { clearTimeout(Game_Modify._chatHideTimers[ind]); } + Game_Modify._chatHideTimers[ind] = setTimeout(function () { + SpriteManager.hide(552 + ind); + SpriteManager.hide(582 + ind); + Game_Modify._chatHideTimers[ind] = null; + }, dur); +}; + +// 桌面语音气泡(播放):有 hook 委托子游戏;无 hook 时按【可变人数】默认渲染。 +// uiIndex 已是相对显示位序(relPos);经 SpriteManager 操作精灵(背景612+ind/内容642+ind), +// 位置取自 Game_Modify.BUBBLE_LAYOUT,气泡宽度依据内容精灵当前宽度;尺寸复用 Game_Config.Voice。 +Game_Modify.gameui_play_voice = function (uiIndex) { + if (window.SubGameHooks && SubGameHooks.gameui_play_voice) return SubGameHooks.gameui_play_voice(uiIndex); + if (typeof SpriteManager === 'undefined') return; + var layout = Game_Modify.BUBBLE_LAYOUT; + if (!layout || !layout.POSITIONS) return; + var playerCount = (Game_Config.Max && Game_Config.Max.PlayerCnt) || 4; + var relPos = ((Number(uiIndex) % playerCount) + playerCount) % playerCount; + var slot = Game_Modify._resolveDisplaySlot(relPos, playerCount); + if (!slot) return; + var pos = layout.POSITIONS[slot.configIndex]; + if (!pos) return; + var ind = slot.ind; + + var voice = Game_Config.Voice || {}; + var dis_x = (voice.VoiceDis && voice.VoiceDis[0]) || 0; + var dis_y = (voice.VoiceDis && voice.VoiceDis[1]) || 0; + var contentSize = SpriteManager.getSize(642 + ind); + var text_len = (contentSize && typeof contentSize.width === 'number') ? contentSize.width : 0; + var bgW = text_len + dis_x * 2; + + if (pos.IS_LEFT) { + SpriteManager.setPosition(612 + ind, pos.X, pos.Y); + SpriteManager.setPosition(642 + ind, pos.X + dis_x, pos.Y + dis_y); + SpriteManager.setImage(612 + ind, layout.IMG_LEFT); + } else { + SpriteManager.setPosition(612 + ind, pos.X - bgW, pos.Y); + SpriteManager.setPosition(642 + ind, pos.X - text_len - dis_x, pos.Y + dis_y); + SpriteManager.setImage(612 + ind, layout.IMG_RIGHT); + } + SpriteManager.setWidth(612 + ind, bgW); + SpriteManager.show(612 + ind); + SpriteManager.show(642 + ind); + play_ani(1, 642 + ind, 43, 1, 3, 0, Game_Config.Voice.VoiceTime, 0, 0, 0, 0, 0, 0); +}; + +// 桌面语音气泡(停止):有 hook 委托子游戏;无 hook 时隐藏对应槽位的语音气泡。 +Game_Modify.gameui_stop_voice = function (uiIndex) { + if (window.SubGameHooks && SubGameHooks.gameui_stop_voice) return SubGameHooks.gameui_stop_voice(uiIndex); + if (typeof SpriteManager === 'undefined') return; + var playerCount = (Game_Config.Max && Game_Config.Max.PlayerCnt) || 4; + var relPos = ((Number(uiIndex) % playerCount) + playerCount) % playerCount; + var slot = Game_Modify._resolveDisplaySlot(relPos, playerCount); + if (!slot) return; + var ind = slot.ind; + SpriteManager.hide(612 + ind); + SpriteManager.hide(642 + ind); +}; \ No newline at end of file