初始化仓库:友乐/gameabc 房卡游戏平台脚手架(client + server + docs)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-01 18:13:26 +08:00
co-authored by Claude Opus 5
commit 594820d393
655 changed files with 310861 additions and 0 deletions
@@ -0,0 +1,205 @@
/**
* ============================================================================
* GameView_Sprites.template.js - 精灵 ID 定义规范
* ============================================================================
*
* 使用方式:
* 将此文件复制到游戏项目中,按实际精灵 ID 填写。
* 建议按界面拆分为多个文件,例如:
* RoomView_Sprites.js (房间等待界面)
* GameView_Sprites.js (游戏主界面)
* PopupView_Sprites.js (弹窗/结算界面)
* 所有 ID 值必须与 gameabc 编辑器中实际配置的精灵/图层/群组 ID 完全一致,
* 不可自行编造数字。
*
* ============================================================================
* ID 分配规范
* ============================================================================
*
* 精灵 ID:子游戏使用 1001+(框架保留 1–1000)
* 图层 ID:普通图层 101–200,弹窗图层 301+(框架保留 1–100)
* 群组 ID:子游戏使用 201+(框架保留 1–200)
*
* ============================================================================
* 文件结构规范
* ============================================================================
*
* 顶层变量对应一个"界面",每个界面包含:
*
* VAR_NAME.VIEW_NAME = {
* LAYER: <图层ID>, // 必填,该界面挂载的图层
*
* SOME_GROUP: {
* GROUP_ID: <群组ID>, // 必填,该功能区的群组
* SPRITES: {
* SPRITE_KEY: <精灵ID>, // 每个精灵
* ...
* }
* },
*
* // 也可不用群组,直接列精灵
* SIMPLE_SPRITE: <精灵ID>
* };
*
* ============================================================================
* 每个精灵条目必须包含的注释字段
* ============================================================================
*
* 类型:图片精灵 / 文字精灵 / 容器精灵 / 热区精灵
* 用途:一句话说明该精灵在游戏中的作用
* 资源:引用的 ImageResources 键名(图片精灵必填)
* 帧说明:初始帧及切换时机(如有多帧)
*
* ============================================================================
* 精灵类型说明
* ============================================================================
*
* 图片精灵:显示图片/帧动画,用 SpriteManager.setFrame() 切换画面
* 文字精灵:显示动态文字,用 SpriteManager.setText() 修改内容
* 容器精灵:作为 SpriteCopyUtils 的父容器,本身不显示内容
* 热区精灵:仅用于点击检测,透明不显示
*
* ============================================================================
* 注意事项
* ============================================================================
*
* - 玩家信息 UI(头像/昵称/分数/准备标识/房主标识)由平台框架提供,
* 子游戏无需也不应在此重复定义
* - GlobalUI(加载界面、消息弹窗、确认对话框)由框架提供,
* 子游戏弹窗从 Layer 302+ 开始定义
*
* @see ImageResources.template.js 图片资源 ID 定义规范
* @see SoundResources.template.js 音效资源 ID 定义规范
*/
'use strict';
// ============================================================================
// 房间界面精灵定义 (Layer xxx)
// ============================================================================
// /**
// * RoomView_Sprites - 房间等待界面精灵定义
// * 图层:Layer <在编辑器查到的图层ID>
// * 用途:显示等待玩家加入、规则配置、准备/开始状态
// */
// var RoomView_Sprites = {};
//
// RoomView_Sprites.ROOM_VIEW = {
// LAYER: <图层ID>,
//
// // 规则配置群组
// // 用途:创建房间时的玩法选项(圈数、人数、特殊规则等)
// ROOM_CONFIG_GROUP: {
// GROUP_ID: <群组ID>,
// SPRITES: {
// // 图片精灵:第1行标题图片,资源=ImageResources.XXX,帧=1
// ROW1_TITLE: <精灵ID>,
//
// // 图片精灵:第1行选项1图标,资源=ImageResources.XXX,帧1=未选中 帧2=已选中
// ROW1_OPTION1: <精灵ID>,
//
// // 文字精灵:第1行选项1文字说明
// ROW1_OPTION1_TEXT: <精灵ID>,
//
// // ... 按实际选项行数继续定义
// }
// }
// };
// ============================================================================
// 游戏主界面精灵定义 (Layer xxx)
// ============================================================================
// /**
// * GameView_Sprites - 游戏主界面精灵定义
// */
// var GameView_Sprites = {};
//
// /**
// * 牌桌主体
// * 图层:Layer <在编辑器查到的图层ID>
// */
// GameView_Sprites.GAME_VIEW = {
// LAYER: <图层ID>,
//
// // 中央信息群组
// // 用途:牌桌中央区域,方向指示器、剩余牌数、倒计时等
// CENTER_INFO_GROUP: {
// GROUP_ID: <群组ID>,
// SPRITES: {
// // 图片精灵:方向面板背景,资源=ImageResources.XXX,4帧(帧1–4对应庄家方向)
// DIRECTION_PANEL_BG: <精灵ID>,
//
// // 图片精灵:倒计时数字,资源=ImageResources.XXX,10帧(帧1–10对应数字0–9)
// COUNTDOWN_NUMBER: <精灵ID>,
//
// // 图片精灵:剩余牌数数字,资源=ImageResources.XXX,10帧(帧1–10对应数字0–9)
// REMAIN_COUNT: <精灵ID>
// }
// },
//
// // 玩家1手牌群组(下方/自己)
// // 用途:显示自己手牌,支持点击选牌出牌
// PLAYER1_HAND_GROUP: {
// GROUP_ID: <群组ID>,
// SPRITES: {
// // 图片精灵×13:手牌1–13,资源=ImageResources.CARD_FACE,帧=当前牌值对应帧
// HAND_CARD_1: <精灵ID>,
// // ... HAND_CARD_2 至 HAND_CARD_13
// HAND_CARD_13: <精灵ID>,
//
// // 图片精灵:摸牌(最右端,与手牌略有间距)
// DRAW_CARD: <精灵ID>
// }
// },
//
// // 玩家2手牌群组(右方)
// // 用途:显示右方玩家牌背(侧面透视角度)
// PLAYER2_HAND_GROUP: {
// GROUP_ID: <群组ID>,
// SPRITES: {
// HAND_CARD_1: <精灵ID>,
// // ...
// DRAW_CARD: <精灵ID>
// }
// }
//
// // 玩家3(上方)、玩家4(左方)结构同上...
// };
//
// /**
// * 游戏操作按钮
// * 图层:Layer <在编辑器查到的图层ID>
// * 用途:玩家可操作时显示的吃/碰/杠/胡/过/听按钮
// */
// GameView_Sprites.GAME_ACTIONS_UI = {
// LAYER: <图层ID>,
//
// ACTION_BUTTONS_GROUP: {
// GROUP_ID: <群组ID>,
// SPRITES: {
// // 图片精灵:吃牌按钮,资源=ImageResources.BTN_CHI,1帧
// BTN_CHI: <精灵ID>,
// BTN_PONG: <精灵ID>,
// BTN_KONG: <精灵ID>,
// BTN_HU: <精灵ID>,
// BTN_PASS: <精灵ID>,
// BTN_TING: <精灵ID>,
//
// // 图片精灵:操作文字弹出动画,资源=ImageResources.OPERATION_EFFECT_XXX
// ACTION_EFFECT_CHI: <精灵ID>,
// ACTION_EFFECT_PONG: <精灵ID>,
// ACTION_EFFECT_KONG: <精灵ID>,
// ACTION_EFFECT_HU: <精灵ID>
// }
// }
// };
// ============================================================================
// 弹窗界面精灵定义 (Layer 301+)
// ============================================================================
// 子游戏自定义弹窗从 Layer 302+ 开始定义
// Layer 301 建议预留给最高优先级的全局弹窗(如断线重连提示)
@@ -0,0 +1,68 @@
/**
* ============================================================================
* ImageResources.template.js - 图片资源 ID 定义规范
* ============================================================================
*
* 使用方式:
* 将此文件复制到游戏项目中,重命名为 ImageResources.js。
* 所有 ID 值必须与 gameabc 编辑器中实际配置的资源 ID 完全一致,
* 不可自行编造数字。
*
* ============================================================================
* ID 分配规范
* ============================================================================
*
* 框架保留范围:1–500(平台框架使用,子游戏严禁占用)
* 子游戏范围:501+(按项目实际需要自行规划分段,在代码注释中记录)
*
* ============================================================================
* 每个资源条目必须包含的注释字段
* ============================================================================
*
* 用途:该资源在游戏中的具体用途(一句话)
* 帧数:资源包含的帧数量
* 帧说明:每帧的内容,数字序号一一列出,例如:
* 帧1=未选中 帧2=已选中
* 帧1–9=1万–9万 帧10–18=1条–9条 ...
* 尺寸:宽×高(像素)
*
* ============================================================================
* 按钮资源规范
* ============================================================================
*
* 所有按钮只需提供 1 帧普通状态图片,无需按下状态图片。
* 点击效果(缩放 + 透明度)由框架统一处理。
*
* ============================================================================
* 帧动画资源说明
* ============================================================================
*
* 帧动画资源(操作效果动画、倒计时数字等)在此文件中只记录资源 ID,
* 帧区间、帧间隔、循环模式等播放参数统一在 AnimationConfigs.js 中配置,
* 不在此文件重复定义。
*
* @see GameView_Sprites.template.js 精灵 ID 定义规范
* @see SoundResources.template.js 音效资源 ID 定义规范
* @see AnimationConfigs.js 帧动画播放参数配置
*/
'use strict';
var ImageResources = {
// 按项目自行规划分段,每段在注释中记录 ID 起止范围和用途
// 注意:底部操作面板、玩家信息头像等 UI 由平台框架提供,子游戏无需定义
// /**
// * 用途:...
// * 帧数:N 帧 帧1=... 帧2=...
// * 尺寸:W×H
// */
// RESOURCE_NAME: <在编辑器中查到的实际资源ID>,
// 帧动画资源注意:帧播放参数(帧区间、帧间隔、循环次数)在 AnimationConfigs.js 中配置,不在此重复定义
// 按钮资源注意:每个按钮 1 帧,点击效果由框架统一处理,无需按下状态帧
// 麻将牌帧布局建议:帧1–9=万, 帧10–18=条, 帧19–27=筒, 帧28–31=东南西北, 帧32–34=中发白, 最后1帧=牌背
// 不同显示区域(手牌/弃牌/副露)尺寸不同,各自用一个独立资源 ID
};
@@ -0,0 +1,500 @@
/**
* ============================================================================
* LayoutConstants.template.js - 游戏布局常量规范模板
* ============================================================================
*
* 使用方式:
* 将此文件复制到游戏目录,重命名为 LayoutConstants.js,
* 然后按游戏实际分辨率和精灵编排修改各项数值。
*
* 如布局配置过多,可拆分为多个文件(如 PopupLayoutConstants.js),
* 每个文件都向同一个全局对象 LayoutConstants 挂载属性。
*
* 加载顺序(index.html 中只需保证在 CardLayoutManager.js 之前加载即可):
* <script src="js/01_SubGame/codes/game/constants/sprites/LayoutConstants.js"></script>
* <script src="js/01_SubGame/codes/game/constants/sprites/PopupLayoutConstants.js"></script>
* <script src="js/01_SubGame/codes/game/managers/CardLayoutManager.js"></script>
*
* 文件不依赖框架,可单独加载。
*
* ============================================================================
* @guide 核心设计理念
* ============================================================================
*
* LayoutConstants 是一个 **纯数据文件**(无函数、无副作用),
* 所有坐标/尺寸/偏移量都在这里集中定义,供各 Manager / View 读取。
*
* 原则:
* 1. 只包含常量对象,不包含业务逻辑
* 2. 每个布局区域单独一个顶层属性(HAND_CARD_LAYOUT, DISCARD_LAYOUT …)
* 3. 4人麻将用 PLAYER_1..4 子对象;其他游戏可用 SELF/OPPONENT 等命名
* 4. 透视偏移量(PERSPECTIVE_OFFSET_X/Y)用于 2D 模拟 3D 效果,正对视角均为 0
* 5. DIRECTION 枚举:'horizontal' | 'horizontal-reverse' | 'vertical' | 'vertical-reverse'
* 6. ALIGN_MODE 枚举:'start' | 'center' | 'end'
*
* ============================================================================
* @guide 透视偏移量说明
* ============================================================================
*
* gameabc 以 2D 图片模拟 3D 透视效果。每张牌在排列时叠加一个累积偏移:
*
* 最终X = 基准X + 牌索引 × PERSPECTIVE_OFFSET_X
* 最终Y = 基准Y + 牌索引 × PERSPECTIVE_OFFSET_Y
*
* 正值向右/向下,负值向左/向上。
* 玩家1(正对屏幕)通常 PERSPECTIVE_OFFSET = 0。
* 玩家2/4(侧视角)需要配置非零值形成倾斜效果。
*
* ============================================================================
* @guide 如何拆分为多文件
* ============================================================================
*
* // LayoutConstants.js(主游戏界面)
* var LayoutConstants = {};
* LayoutConstants.HAND_CARD_LAYOUT = { ... };
* LayoutConstants.DISCARD_LAYOUT = { ... };
*
* // PopupLayoutConstants.js(弹窗界面,另一个文件)
* var LayoutConstants = LayoutConstants || {}; // 保护性声明
* LayoutConstants.ROUND_RESULT_LAYOUT = { ... };
* LayoutConstants.GAME_RESULT_LAYOUT = { ... };
*
* 两个文件加载后,LayoutConstants 包含所有属性。
*
* @version 1.0.0
* @see LayoutConstants.js 金仙麻将实际实现
* @see CardLayoutManager.js 使用 LayoutConstants 计算精灵坐标
*/
'use strict';
/**
* LayoutConstants - 布局常量对象
* 所有布局配置挂载在此全局对象下
*/
var LayoutConstants = {};
// ============================================================================
// 设计分辨率
// ============================================================================
/** 设计稿宽度(px) */
LayoutConstants.DESIGN_WIDTH = 1280;
/** 设计稿高度(px) */
LayoutConstants.DESIGN_HEIGHT = 720;
// ============================================================================
// 手牌区布局(示例:4人麻将)
// ============================================================================
/**
* 手牌区布局配置
*
* DIRECTION 值:
* 'horizontal' - 从左到右(PLAYER_1 常用)
* 'horizontal-reverse' - 从右到左(PLAYER_3 常用)
* 'vertical' - 从上到下(PLAYER_4 常用)
* 'vertical-reverse' - 从下到上(PLAYER_2 常用)
*
* ALIGN_MODE 值:
* 'start' - 从 START_X/Y 开始,按索引递增方向铺开
* 'center' - 手牌整体居中于 START_X/Y
* 'end' - 最后一张牌落在 START_X/Y,向前排列
*/
LayoutConstants.HAND_CARD_LAYOUT = {
// 玩家1 (下方 - 自己) - 正视角,无透视
PLAYER_1: {
START_X: 164, // 第1张牌的左边界X
START_Y: 613, // 手牌区Y坐标
CARD_WIDTH: 74, // 单张牌宽度(含视觉厚度)
CARD_HEIGHT: 102, // 单张牌高度
CARD_SPACING: -2, // 牌间距(负值=重叠)
DRAW_CARD_GAP: 15, // 摸牌与手牌的额外间隔
SELECT_OFFSET_Y: -20, // 选中牌向上偏移量
MAX_CARDS: 13, // 最大手牌数
DIRECTION: 'horizontal',
ALIGN_MODE: 'end',
PERSPECTIVE_OFFSET_X: 0,
PERSPECTIVE_OFFSET_Y: 0
},
// 玩家2 (右方) - 侧视角,竖排,透视向左上
PLAYER_2: {
START_X: 1205,
START_Y: 537,
CARD_WIDTH: 52,
CARD_HEIGHT: 72,
CARD_SPACING: 0,
DRAW_CARD_GAP: 0,
MAX_CARDS: 13,
DIRECTION: 'vertical-reverse',
ALIGN_MODE: 'center',
PERSPECTIVE_OFFSET_X: -8,
PERSPECTIVE_OFFSET_Y: -37,
DRAW_CARD_PERSPECTIVE_X: -12,
DRAW_CARD_PERSPECTIVE_Y: 18
},
// 玩家3 (上方) - 对面视角,横排,无透视
PLAYER_3: {
START_X: 820,
START_Y: 44,
CARD_WIDTH: 43,
CARD_HEIGHT: 58,
CARD_SPACING: -3,
DRAW_CARD_GAP: 6,
MAX_CARDS: 13,
DIRECTION: 'horizontal-reverse',
ALIGN_MODE: 'end',
PERSPECTIVE_OFFSET_X: 0,
PERSPECTIVE_OFFSET_Y: 0
},
// 玩家4 (左方) - 侧视角,竖排,透视向左上
PLAYER_4: {
START_X: 139,
START_Y: 74,
CARD_WIDTH: 52,
CARD_HEIGHT: 72,
CARD_SPACING: 0,
DRAW_CARD_GAP: 12,
MAX_CARDS: 13,
DIRECTION: 'vertical',
ALIGN_MODE: 'center',
PERSPECTIVE_OFFSET_X: -8,
PERSPECTIVE_OFFSET_Y: -37,
DRAW_CARD_PERSPECTIVE_X: -9,
DRAW_CARD_PERSPECTIVE_Y: -35
}
};
// ============================================================================
// 弃牌区布局(示例)
// ============================================================================
/**
* 弃牌区布局配置
*
* CARDS_PER_ROW / MAX_ROWS:弃牌按行/列分组排列(DIRECTION 为 vertical 时按列)。
* ROW_PERSPECTIVE_X/Y:每换一行的累积透视偏移,用于模拟牌桌中央的深度感。
*/
LayoutConstants.DISCARD_LAYOUT = {
PLAYER_1: {
START_X: 500,
START_Y: 345,
CARD_WIDTH: 45,
CARD_HEIGHT: 55,
CARDS_PER_ROW: 9,
ROW_SPACING: 2,
CARD_SPACING: 1,
MAX_ROWS: 3,
CARD_PERSPECTIVE_X: 0,
CARD_PERSPECTIVE_Y: 0,
ROW_PERSPECTIVE_X: 2,
ROW_PERSPECTIVE_Y: -3
},
PLAYER_2: {
START_X: 755,
START_Y: 175,
CARD_WIDTH: 28,
CARD_HEIGHT: 36,
CARDS_PER_ROW: 3,
ROW_SPACING: 1,
CARD_SPACING: 1,
MAX_ROWS: 9,
DIRECTION: 'vertical',
CARD_PERSPECTIVE_X: -1,
CARD_PERSPECTIVE_Y: 0,
ROW_PERSPECTIVE_X: -2,
ROW_PERSPECTIVE_Y: 1
},
PLAYER_3: {
START_X: 720,
START_Y: 95,
CARD_WIDTH: 36,
CARD_HEIGHT: 28,
CARDS_PER_ROW: 9,
ROW_SPACING: 2,
CARD_SPACING: 1,
MAX_ROWS: 3,
DIRECTION: 'horizontal-reverse',
CARD_PERSPECTIVE_X: 0,
CARD_PERSPECTIVE_Y: 0,
ROW_PERSPECTIVE_X: -2,
ROW_PERSPECTIVE_Y: 2
},
PLAYER_4: {
START_X: 200,
START_Y: 280,
CARD_WIDTH: 58,
CARD_HEIGHT: 41,
CARDS_PER_ROW: 3,
ROW_SPACING: 1,
CARD_SPACING: 1,
MAX_ROWS: 9,
DIRECTION: 'vertical-reverse',
CARD_PERSPECTIVE_X: 1,
CARD_PERSPECTIVE_Y: 0,
ROW_PERSPECTIVE_X: -3,
ROW_PERSPECTIVE_Y: -12
}
};
// ============================================================================
// 副露区布局(吃/碰/杠)
// ============================================================================
/**
* 副露区布局配置
*
* 透视偏移量(组内 + 组间):
* CARD_PERSPECTIVE_X/Y - 同一副露组内,每张牌相对前一张的额外偏移(累加)
* GROUP_PERSPECTIVE_X/Y - 不同副露组之间,每组相对前一组的额外偏移(累加)
*
* 坐标计算公式(CardLayoutManager.js 负责实现):
* 透视X = cardIndex × CARD_PERSPECTIVE_X + meldIndex × GROUP_PERSPECTIVE_X
* 最终X = 基准X + 透视X
*/
LayoutConstants.MELD_LAYOUT = {
PLAYER_1: {
START_X: 105,
START_Y: 639,
CARD_WIDTH: 64,
CARD_HEIGHT: 84,
CARD_SPACING: 0,
GROUP_SPACING: 8,
DIRECTION: 'horizontal',
MAX_GROUPS: 4,
KONG_STACK_OFFSET_X: 0,
KONG_STACK_OFFSET_Y: -20,
CARD_PERSPECTIVE_X: -5,
CARD_PERSPECTIVE_Y: 0,
GROUP_PERSPECTIVE_X: -3,
GROUP_PERSPECTIVE_Y: 0
},
PLAYER_2: {
START_X: 1115,
START_Y: 509,
CARD_WIDTH: 65,
CARD_HEIGHT: 48,
CARD_SPACING: 0,
GROUP_SPACING: 0,
DIRECTION: 'vertical-reverse',
MAX_GROUPS: 4,
KONG_STACK_OFFSET_X: 6,
KONG_STACK_OFFSET_Y: -20,
CARD_PERSPECTIVE_X: -7,
CARD_PERSPECTIVE_Y: 35,
GROUP_PERSPECTIVE_X: -25,
GROUP_PERSPECTIVE_Y: 35
},
PLAYER_3: {
START_X: 930,
START_Y: 48,
CARD_WIDTH: 43,
CARD_HEIGHT: 58,
CARD_SPACING: 0,
GROUP_SPACING: 4,
DIRECTION: 'horizontal-reverse',
MAX_GROUPS: 4,
KONG_STACK_OFFSET_X: 0,
KONG_STACK_OFFSET_Y: -9,
CARD_PERSPECTIVE_X: 3,
CARD_PERSPECTIVE_Y: 0,
GROUP_PERSPECTIVE_X: -3,
GROUP_PERSPECTIVE_Y: 0
},
PLAYER_4: {
START_X: 198,
START_Y: 91,
CARD_WIDTH: 63,
CARD_HEIGHT: 49,
CARD_SPACING: 0,
GROUP_SPACING: 0,
DIRECTION: 'vertical',
MAX_GROUPS: 4,
KONG_STACK_OFFSET_X: -7,
KONG_STACK_OFFSET_Y: -17,
CARD_PERSPECTIVE_X: -6,
CARD_PERSPECTIVE_Y: -30,
GROUP_PERSPECTIVE_X: -25,
GROUP_PERSPECTIVE_Y: -30
}
};
// ============================================================================
// 玩家信息 UI 布局(支持 2/3/4 人)
// ============================================================================
/**
* 玩家信息 UI 布局配置
*
* 座位映射(seatToInd):
* 不同人数局中,座位号(0-3)映射到屏幕显示位置(ind 0-3)。
* ind = -1 表示该座位在此人数局中不显示。
*
* 约定:ind0=下方(自己) ind1=右方 ind2=上方 ind3=左方
*
* 位置配置(POSITIONS[ind]):
* FRAME_* 底框精灵的绝对坐标和尺寸
* AVATAR.* 头像相对于底框左上角的偏移量(OFFSET_X/Y + WIDTH/HEIGHT)
* NAME_OFFSET_X/Y 昵称相对于头像左上角的偏移
* SCORE_OFFSET_X/Y 分数相对于头像左上角的偏移
*/
LayoutConstants.PLAYER_INFO_LAYOUT = {
DESIGN_WIDTH: 1280,
DESIGN_HEIGHT: 720,
// 座位号 → 显示位置 ind 的映射
SEAT_MAPPING: {
4: [0, 1, 2, 3],
3: [0, 1, 3, -1], // 3人局:北位(ind2)不显示
2: [0, 2, -1, -1] // 2人局:只有南北两位
},
VISIBLE_POSITIONS: {
4: [0, 1, 2, 3],
3: [0, 1, 3],
2: [0, 2]
},
POSITIONS: {
// ind=0: 南位(下方) - 玩家自己
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
},
// ind=1: 东位(右方)
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
},
// ind=2: 北位(上方)
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
},
// ind=3: 西位(左方)
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
}
};
// ============================================================================
// 庄家标识布局
// ============================================================================
/**
* 庄家图标布局
*
* POSITION_TYPE 决定图标与头像底框的对齐方式:
* 'top-left' - 图标左上角 = 底框右上角(图标显示在底框右侧)
* 'top-right' - 图标右上角 = 底框左上角(图标显示在底框左侧)
*/
LayoutConstants.DEALER_ICON_LAYOUT = {
ICON_WIDTH: 32,
ICON_HEIGHT: 32,
SEAT_MAPPING: {
4: [0, 1, 2, 3],
3: [0, 1, 3, -1],
2: [0, 2, -1, -1]
},
POSITIONS: {
0: { POSITION_TYPE: 'top-left' },
1: { POSITION_TYPE: 'top-right' },
2: { POSITION_TYPE: 'top-right' },
3: { POSITION_TYPE: 'top-left' }
}
};
// ============================================================================
// 操作倒计时布局
// ============================================================================
/**
* 操作倒计时布局
*
* 数字图片居中显示在 CENTER_X:
* actualX = CENTER_X - (digitCount × CHAR_WIDTH / 2)
*/
LayoutConstants.COUNTDOWN_LAYOUT = {
CENTER_X: 640, // 水平居中锚点
Y: 296,
CHAR_WIDTH: 31,
CHAR_HEIGHT: 33
};
// ============================================================================
// 操作按钮布局(吃/碰/杠/胡/听/过)
// ============================================================================
/**
* 操作按钮动态水平布局
*
* 从右锚点 ANCHOR_X 向左排列,按钮数量变化时整体右对齐:
* totalW = count × BUTTON_WIDTH + (count-1) × BUTTON_SPACING
* firstX = ANCHOR_X - totalW
* nthX = firstX + (n-1) × (BUTTON_WIDTH + BUTTON_SPACING)
*/
LayoutConstants.ACTION_BUTTON_LAYOUT = {
ANCHOR_X: 1180, // 右对齐锚点
Y: 560, // 按钮Y坐标(统一)
BUTTON_WIDTH: 100,
BUTTON_HEIGHT: 80,
BUTTON_SPACING: 10
};
// ============================================================================
// 最后出牌指示器布局
// ============================================================================
/**
* 最后出牌指示器偏移量(相对于该牌的坐标)
*/
LayoutConstants.LAST_DISCARD_INDICATOR_LAYOUT = {
OFFSET: {
0: { X: 8, Y: -20 },
1: { X: 15, Y: -25 },
2: { X: 6, Y: -20 },
3: { X: 15, Y: -23 }
}
};
@@ -0,0 +1,108 @@
/**
* ============================================================================
* SoundResources.template.js - 音效资源常量定义规范
* ============================================================================
*
* 使用方式:
* 将此文件复制到游戏项目中,重命名为 SoundResources.js。
* 所有文件名必须与实际音效文件完全一致,不可自行编造。
*
* ============================================================================
* 文件命名规范
* ============================================================================
*
* 音效文件名 = 5位数字补零 + .mp3,例如:
* ID=101 → '00101.mp3'
* ID=151 → '00151.mp3'
*
* ============================================================================
* ID 分配规范
* ============================================================================
*
* 框架保留:00001–00100(平台系统音效,子游戏严禁占用)
* 子游戏范围:00101+(EFFECTS / VOICE / MUSIC 的具体分段按项目自行规划)
*
* ============================================================================
* 性别语音规则(AudioManager 依赖此规范)
* ============================================================================
*
* 男性语音 ID = 基础 ID(151+)
* 女性语音 ID = 男性语音 ID + FEMALE_VOICE_OFFSET(默认 100)
* 规律:男性 '00151.mp3' 对应 女性 '00251.mp3'
*
* AudioManager.playActionVoiceBySeat(seat, action) 会根据该座位的
* sex 字段(sex===2 为女性)自动选择正确的语音文件,
* MALE 和 FEMALE 两组键名必须保持一一对应。
*
* ============================================================================
* 调用示例(供理解结构,不代表实际值)
* ============================================================================
*
* AudioManager.playSound(SoundResources.EFFECTS.DISCARD);
* // 游戏侧 GameAudio 封装 ID 映射,再调用框架层通用 API:
* AudioManager.playVoiceBySeat(seat, ACTION_VOICE_BASE_ID['chi']); // 自动按性别选语音
* AudioManager.playVoiceBySeat(seat, CARD_VOICE_BASE_ID[cardValue]); // 自动按性别选报牌语音
* AudioManager.playMusic();
*
* @see AudioManager.js 音频播放管理(gameabc-framework/system/)
* @see GameAudio.js 游戏专属 ID 映射(子游戏目录,如 game/managers/GameAudio.js)
*/
'use strict';
var SoundResources = {
// ========================================================================
// EFFECTS - 通用音效
// ========================================================================
EFFECTS: {
// 操作动效音 ≠ 操作语音:
// 操作动效音 = 文字弹出时的短促音效(在 EFFECTS 中定义)
// 操作语音 = 玩家喊出操作词的人声(在 VOICE 中定义)
// DISCARD: '<实际文件名>',
// DRAW: '<实际文件名>',
// GAME_START: '<实际文件名>',
// WIN: '<实际文件名>',
// CHI_EFFECT: '<实际文件名>',
// ...
},
// ========================================================================
// VOICE - 语音(按性别分组)
// 规则:女性语音文件 ID = 对应男性语音文件 ID + FEMALE_VOICE_OFFSET(默认100)
// MALE 和 FEMALE 两组键名必须完全一一对应,AudioManager 依赖此结构
// ========================================================================
VOICE: {
MALE: {
// 操作语音键名(固定,AudioManager 按此查找):
// CHI / PONG / KONG / HU / TING / PASS / ZIMO
// CHI: '<实际文件名>',
// ...
// 报牌语音键名建议统一为 <SUIT>_<VALUE>
// 示例:WAN_1, WAN_2, ... TIAO_1, ... TONG_1, ... DONG, NAN, XI, BEI, ZHONG, FA, BAI
// WAN_1: '<实际文件名>',
// ...
},
FEMALE: {
// 键名与 MALE 完全相同,对应文件 ID + 100
// CHI: '<实际文件名>',
// WAN_1: '<实际文件名>',
// ...
}
},
// ========================================================================
// MUSIC - 背景音乐
// 需同时在 game_config.soundList 中配置预加载
// ========================================================================
MUSIC: {
// GAME_BGM: '<实际文件名>',
// ROOM_BGM: '<实际文件名>',
}
};
@@ -0,0 +1,196 @@
/**
* ============================================================================
* UIBootstrap.template.js - UI 启动器规范模板
* ============================================================================
*
* 使用方式:
* 将此文件复制到游戏目录,重命名为 UIBootstrap.js。
* 根据游戏实际使用的 UI 组件修改 _registerViews() 和 _checkDependencies()。
*
* 加载顺序(index.html 中必须按序):
* 1. gameabc-framework/core/EventBus.js
* 2. gameabc-framework/core/SpriteManager.js
* 3. gameabc-framework/ui/BaseComponent.js
* 4. gameabc-framework/ui/UIManager.js ← 框架层
* 5. game/ui/UIManager.js ← 游戏层扩展(如 CreateRoomUI)
* 6. game/ui/views/RoomView.js ← 各视图组件
* 7. game/ui/views/GameView.js
* 8. game/UIBootstrap.js ← 本文件(最后加载)
*
* 调用时机:
* 在 Logic.AppStart() 中调用 UIBootstrap.init()
*
* ============================================================================
* @guide UIBootstrap 的职责
* ============================================================================
*
* UIBootstrap 是游戏专属的启动器,负责:
* 1. 按序初始化框架系统(SpriteManager → AnimationManager → UIManager)
* 2. 创建游戏专属视图组件
* 3. 向 UIManager 注册组件和场景
* 4. 提供 views 字典供全局访问
*
* UIBootstrap 不应该:
* - 包含业务逻辑(发牌、胡牌判断等)
* - 直接操作精灵(应交给各 View 组件)
* - 被其他模块 require(单向依赖)
*
* ============================================================================
* @guide 场景注册示例
* ============================================================================
*
* // 在 _registerViews() 中,创建并注册完所有组件后:
*
* // 1. 注册到 UIManager 组件表
* UIManager.registerComponent('RoomView', this.views.RoomView);
* UIManager.registerComponent('GameView', this.views.GameView);
*
* // 2. 注册场景(组件名称必须已注册)
* UIManager.registerScene(UIManager.SCENES.ROOM, ['RoomView']);
* UIManager.registerScene(UIManager.SCENES.GAME, ['GameView', 'JingCardView', 'DiceView']);
*
* @see UIManager.js 框架层组件/场景管理(gameabc-framework/ui/)
* @see BaseComponent.js 组件基类(gameabc-framework/ui/)
*/
var UIBootstrap = UIBootstrap || {};
// ============================================================================
// 状态
// ============================================================================
UIBootstrap.initialized = false;
/** 游戏创建的所有 View 实例 */
UIBootstrap.views = {};
// ============================================================================
// 公共 API
// ============================================================================
/**
* 初始化 UI 系统
* 在 Logic.AppStart() 中调用一次
* @returns {boolean}
*/
UIBootstrap.init = function () {
if (this.initialized) {
console.warn('[UIBootstrap] 已初始化,跳过');
return true;
}
console.log('[UIBootstrap] 开始初始化 UI 系统...');
try {
if (!this._checkDependencies()) { return false; }
// 1. 初始化 SpriteManager
SpriteManager.init();
// 2. 初始化 AnimationManager(如有)
if (typeof AnimationManager !== 'undefined') {
AnimationManager.init();
}
// 3. 初始化 UIManager(框架层 + 游戏扩展)
// 如有 Loading/Message/Confirm 全局 UI,注入各自精灵常量(框架不读子游戏全局名):
// UIManager.init({ loadingUI: SpriteConstants.LOADING_UI, messageUI: SpriteConstants.MESSAGE_UI, confirmUI: SpriteConstants.CONFIRM_UI });
// 无全局 UI 则直接 UIManager.init();
UIManager.init();
// 4. 创建并注册所有视图
this._registerViews();
this.initialized = true;
console.log('[UIBootstrap] UI 系统初始化完成,共', Object.keys(this.views).length, '个组件');
return true;
} catch (err) {
console.error('[UIBootstrap] 初始化失败:', err);
return false;
}
};
/**
* 检查依赖是否已加载
* ⚠️ 按游戏实际使用的组件修改此列表
* @private
*/
UIBootstrap._checkDependencies = function () {
var required = [
{ name: 'EventBus', ok: typeof EventBus !== 'undefined' },
{ name: 'SpriteManager', ok: typeof SpriteManager !== 'undefined' },
{ name: 'UIManager', ok: typeof UIManager !== 'undefined' },
{ name: 'RoomView', ok: typeof RoomView !== 'undefined' },
{ name: 'GameView', ok: typeof GameView !== 'undefined' }
];
var optional = [
// { name: 'JingCardView', ok: typeof JingCardView !== 'undefined' },
// { name: 'DiceView', ok: typeof DiceView !== 'undefined' }
];
var allOk = true;
for (var i = 0; i < required.length; i++) {
if (!required[i].ok) {
console.error('[UIBootstrap] 缺少依赖:', required[i].name);
allOk = false;
}
}
for (var j = 0; j < optional.length; j++) {
if (!optional[j].ok) {
console.warn('[UIBootstrap] 可选组件未加载:', optional[j].name);
}
}
return allOk;
};
/**
* 创建视图实例并注册到 UIManager
* ⚠️ 按游戏实际使用的组件修改此方法
* @private
*/
UIBootstrap._registerViews = function () {
// ---- 创建组件 ----
this.views.RoomView = RoomView.create({ name: 'RoomView' });
this.views.GameView = GameView.create({ name: 'GameView' });
// this.views.JingCardView = JingCardView.create({ name: 'JingCardView' });
// this.views.DiceView = DiceView.create({ name: 'DiceView' });
// ---- 注册到 UIManager ----
UIManager.registerComponent('RoomView', this.views.RoomView);
UIManager.registerComponent('GameView', this.views.GameView);
// ---- 注册场景 ----
UIManager.registerScene(UIManager.SCENES.ROOM, ['RoomView']);
UIManager.registerScene(UIManager.SCENES.GAME, ['GameView']);
};
// ============================================================================
// 工具方法
// ============================================================================
UIBootstrap.getView = function (viewName) {
return this.views[viewName] || null;
};
UIBootstrap.showView = function (viewName) {
if (!this.initialized) { return; }
UIManager.showView(viewName);
};
UIBootstrap.hideView = function (viewName) {
if (!this.initialized) { return; }
UIManager.hideView(viewName);
};
UIBootstrap.getStatus = function () {
return {
initialized: this.initialized,
views: Object.keys(this.views),
uiManagerReady: UIManager.initialized
};
};
if (typeof module !== 'undefined' && module.exports) {
module.exports = UIBootstrap;
}
@@ -0,0 +1,130 @@
// ============================================================================
// 00_SubGame_Config.template.js —— 平台配置项模板(结构由平台定义,子游戏填【值】)
// 这是配置文件(C 类,非转发壳)。复制后按本游戏界面/玩法调整各项值。
// 严格 ES5。
// ============================================================================
var Game_Config = Game_Config || {};
Game_Config.Debugger={//调试配置
isDebugger : true,// debugger模式下会将所有收发的包输出到控制台(正式发布改为false)
AutoLogin : true,//debugger模式下是否需要记住登录状态自动登录(正式发布改为true)
isSubmitError : false,//是否需要服务器收集错误信息调试时可根据需要(正式发布改为true)
visitorLogin : true,//隐藏式游客登录
visiblePay:true,//审核通过后是否显示支付按钮
serverType:1,//0->正式服务器 1->本地服务器 http://ylyxservice1.0791ts.cn/config/update_json.txt
gameserver:""//子游戏填自己的更新地址
};
Game_Config.Max = {
SumOfRoomtype:3,//创建时房间类型roomtype数组长度
ShowChat:2000,//聊天停留时间
PlayerCnt:4,//房间最大人数(按房间最大人数设置)
group:500,//游戏最大群组号
showtime:10,//加载等待最少时间
reconnecttime:30000,//唤醒游戏间隔(超过间隔断线重连)
Mainnickname:10,//主界面玩家昵称显示长度(字符长度)
Infonickname:16//个人信息显示长度(字符长度)
};
Game_Config.Combat = {
height:80,//战绩页单行高度高度
up_y:190,//战绩页裁剪y坐标
bannerheight:60,//战绩横幅高度
bannery:80,//战绩横幅Y左边(相对48精灵)
btnbg:50
};
Game_Config.Info = {
Mainnickname:8,//游戏主界面玩家信息显示最大长度(字符)
textwidth_1:12.5,//昵称文字显示的宽度(未改动聊天显示文字大小无需改动无需修改)
textwidth_2:12.5,//积分文字显示的宽度(未改动聊天显示文字大小无需改动无需修改)
otherPositionDefault:true,//其他主界面玩家信息(昵称、积分)是否采用默认对齐方式对齐居中点为头像中点
myPositionDefault:true,//其他主界面玩家信息(昵称、积分)是否采用默认对齐方式对齐居中点为头像中点
myPosition:130,//自己信息对齐点(根据游戏界面自行修改)
position:[],//其他主界面玩家信息(昵称、积分)居中对齐的x坐标 注意是其他玩家不包括自己从下家开始
//TextContent:["1","2","3","4","5","6","7"],//常用语内容
TextContent:["你好","谢谢","再来","快点","不要","加油",":)"],//常用语内容
TextContentMp3:["","","","","","",""],//常用语对应音效
};
Game_Config.Share={//分享参数
appdownload:"",//下载链接(无需配置,从服务器获取)
title:"游戏名",//(分享标题)
description:"",//分享描述
gameTitle:"",//游戏中的分享标题模板工程会自动将游戏名字分享出去、不必写在这个变量里
gameDescription:""//游戏中分享描述
};
Game_Config.Chat={//游戏内聊天配置信息
LimitLength:40,//聊天最大长度(字节长度)
textwidth:12.5,//聊天显示文字的宽度(未改动聊天显示文字大小无需改动无需修改)
ChatDis:[30,12],//聊天气泡与内容的间隔0位置左右间隔(最好不要低于30)1位置上下间隔
isLeft:[1,0,0,1,1],//聊天气泡是否为以左边为基准线(坐标/基准按各游戏界面调整)
ChatLoc:[[35,517],[1100,315],[1100,105],[175,105],[175,315]]//聊天气泡的基准点位置(注意是基准点位置!)(坐标/基准按各游戏界面调整)
};
Game_Config.Voice={//游戏内聊天配置信息
VoiceTime:600,//播放语音动画的时间一般情况无需无需修改
VoiceDis:[30,12],//语音气泡与内容的间隔0位置左右间隔(最好不要低于30)1位置上下间隔
isLeft:[1,0,0,1,1],//气泡是否为以左边为基准线(坐标/基准按各游戏界面调整)
VoiceLoc:[[35,517],[1100,315],[1100,105],[175,105],[175,315]]//气泡的基准点位置(注意是基准点位置!)(坐标/基准按各游戏界面调整)
};
Game_Config.Setting={
Ads1:"本游戏仅供娱乐休闲使用,\n道具及虚拟货币不可兑换\n现金或实物,不代表任何\n真实财富价值。",
//Ads2:"\n 或客服号:\n",
board:"",//从后台获取设置无效
//info:[" 客服信息","QQ:","",""],//客服QQ提醒
info:[" 客服信息","QQ:","手机:","微信:"],//客服QQ提醒
board_blength:24,//通知页面一行最大字节数(无需设置)
charge:"充值提示",//充值提示
};
Game_Config.Protocol={
x:270,//协议图片初始x坐标
y:70,//协议图片初始y坐标
h:560,//协议图片显示高度
w:729//协议图片显示宽度
};
Game_Config.Help={//帮助
x:300,//帮助图片初始x坐标
y:165,//帮助图片初始y坐标
w:729,//帮助图片显示高度
h:450//帮助图片显示宽度
};
Game_Config.Notice={//滚动公告
x1:0,//无需设置
x2:0,//无需设置
y:0,//无需设置
h:0,//无需设置
//以上参数用来截取滚动公告的显示,截取位置是与滚动公告底大小位置一致
speed:0.1,//滚动公告的滚动速度
width:15//滚动公告的内容字体的字节长度(汉字是两个字节)
};
Game_Config.Feedback = {//反馈配置
maxLen:250 //反馈内容最大长度
};
Game_Config.shakeList ={//摇一摇事件的回调ID、需要添加时在此处添加回调事件写在Utl_Input.js中的Utl.shakeEvent()中
nil:0,
startwar:1
};
Game_Config.soundList ={//声音资源名
MenuSceneMusic:"",//大厅界面背景音
MainSceneMusic:""//游戏主界面背景音
};
Game_Config.ClickButton = {//需要设置点击音效的按钮(只有有弹窗的按钮设置此音效有效、按钮已经设置好、子游戏不允许修改)
src_1:"",//点击时播放的声音资源文件( 不需要播放则不填,下同)
src_2:""//弹窗时播放的音效
}
Game_Config.loginButton = {//登录按钮信息
x1:415,//非审核版本微信登录按钮x
x2:113,//审核版本微信登录按钮x
x3:488,//审核版本游客登录按钮x
};
Game_Config.sysConfig = {
mainMenuButton:false,//是否进入主菜单界面隐藏四个按钮
mainScenePlayerInfo:false,//是否隐藏主界面玩家信息
mainSceneButton:false,//是否隐藏主界面按钮
shareRoom:false,//接收到星星场是否屏蔽平台框架显示
changeSeat:false,//是否屏蔽平台自动刷新换座后界面
hideNotice:false,//隐藏公告栏
vipInfinite:false,//是否为百人场
}
@@ -0,0 +1,129 @@
// ============================================================================
// 01_SubGame_modify.js —— 平台交互/创建房间接口「转发壳」(框架维护,子游戏不碰)
// 顶部为「房型/创建房间配置区」(数据,子游戏填值);其下为接口转发壳(转发到 SubGameHooks)。
// 严格 ES5。
// ----------------------------------------------------------------------------
// 【平台约定精灵:25 号 = 创建房间确认按钮】(接入子游戏必看)
// 平台框架在「创建房间」界面提供 25 号精灵作为“确认/提交”按钮;其点击交互与
// 发送的数据包,均需【子游戏自己实现】(平台不代发),约定流程:
// 1. 子游戏在打开创建房间界面时注册 25 号精灵的点击(经 SpriteEventController
// 或 mouseup),点击即“确认创建房间”。
// 2. 子游戏按界面选项拼出 roomtype(房型配置串,编码局数/人数/扣卡/规则等,
// 由各子游戏按玩法自定义与解析),组装并发送创建房间请求:
// Net.Send_create_room({
// agentid : GameData.AgentId,
// playerid: C_Player.playerid,
// gameid : GameData.GameId,
// roomtype: <房型配置字符串>
// });
//
// 本麻将实现见 codes/game/ui/handlers/CreateRoomEventHandler(bindEvents 注册
// 25→submit;submit 用 UIManager._buildRoomTypeString 拼 roomtype 后 Net.Send_create_room)。
// ============================================================================
// ---------- 配置区(数据,子游戏按玩法填值;与 00 的 Game_Config 分工) ----------
var Game_Modify = Game_Modify || {};
// ---------- 平台需要的必须字段 ----------
Game_Modify.roomDes = ""; // 房间描述,子游戏在 setRoomDes 内填
Game_Modify.game_config = {}; // 游戏配置默认值
Game_Modify.combat = {
maxPlayerCount: 5,
pageOnelimitStrLen: 8,
pageOneDoc: false,
pageTwolimitStrLen: 16,
pageTwoDoc: false
};
Game_Modify.Type_1 = [
{ id: 0, des: "10局(房卡X1)", roomcard: 1, type: 1 },
{ id: 1, des: "20局(房卡X2)", roomcard: 2, type: 2 }
];
Game_Modify.Type_2 = [
{ id: 0, des: "房主扣卡", type: 1 },
{ id: 1, des: "每人扣卡", type: 2 }
];
Game_Modify.CreateRoomData = {
Type_1: 0,
Type_2: 0
};
// ---------- 接口转发壳(A 类:无 hook 即 no-op) ----------
Game_Modify.utlmousedown = function (gameid, spid, downx, downy, no1, no2, no3, no4, no5, no6) {
if (window.SubGameHooks && SubGameHooks.utlmousedown) return SubGameHooks.utlmousedown(gameid, spid, downx, downy, no1, no2, no3, no4, no5, no6);
};
Game_Modify.utlmousedown_nomove = function (gameid, spid, downx, downy, timelong, no1, no2, no3, no4, no5) {
if (window.SubGameHooks && SubGameHooks.utlmousedown_nomove) return SubGameHooks.utlmousedown_nomove(gameid, spid, downx, downy, timelong, no1, no2, no3, no4, no5);
};
Game_Modify.mouseup = function (gameid, spid_down, downx, downy, spid_up, upx, upy, timelong, no1, no2) {
if (window.SubGameHooks && SubGameHooks.mouseup) return SubGameHooks.mouseup(gameid, spid_down, downx, downy, spid_up, upx, upy, timelong, no1, no2);
};
Game_Modify.utlmousemove = function (gameid, spid, downx, downy, movex, movey, timelong, offmovex, offmovey, no1) {
if (window.SubGameHooks && SubGameHooks.utlmousemove) return SubGameHooks.utlmousemove(gameid, spid, downx, downy, movex, movey, timelong, offmovex, offmovey, no1);
};
Game_Modify.utlgamemydrawbegin = function (gameid, spid, times, timelong, no2, no3, no4, no5, no6, no7) {
if (window.SubGameHooks && SubGameHooks.utlgamemydrawbegin) return SubGameHooks.utlgamemydrawbegin(gameid, spid, times, timelong, no2, no3, no4, no5, no6, no7);
};
Game_Modify.gamemydraw = function (gameid, spid, times, timelong, no2, no3, no4, no5, no6, no7) {
if (window.SubGameHooks && SubGameHooks.gamemydraw) return SubGameHooks.gamemydraw(gameid, spid, times, timelong, no2, no3, no4, no5, no6, no7);
};
Game_Modify.OpenCreateRoom = function () {
if (window.SubGameHooks && SubGameHooks.OpenCreateRoom) return SubGameHooks.OpenCreateRoom();
};
Game_Modify.CloseCreateRoom = function () {
if (window.SubGameHooks && SubGameHooks.CloseCreateRoom) return SubGameHooks.CloseCreateRoom();
};
Game_Modify.setRoomDes = function (roomcode, asetcount, roomtype) {
if (window.SubGameHooks && SubGameHooks.setRoomDes) return SubGameHooks.setRoomDes(roomcode, asetcount, roomtype);
};
// ---------- gameCombat 兼容空定义 ----------
// 平台 gamemain.js 仍直接调用 gameCombat.utl*(战绩界面回调),保留全局对象与 6 个空接口防报错。
// 战绩功能由 RecordView 重建(见迁移子项目 2);届时这些接口可改为委托或保持空。
var gameCombat = gameCombat || {};
gameCombat.utlani_doend = gameCombat.utlani_doend || function () {};
gameCombat.utlmousedown = gameCombat.utlmousedown || function () {};
gameCombat.utlmouseup = gameCombat.utlmouseup || function () {};
gameCombat.utlmousemove = gameCombat.utlmousemove || function () {};
gameCombat.utlgamemydraw = gameCombat.utlgamemydraw || function () {};
gameCombat.utlgamemydrawbegin = gameCombat.utlgamemydrawbegin || function () {};
// 战绩接收入口(平台 09_Net 调):转发到 SubGameHooks(框架转发壳不引用 codes;子游戏在 SubGameHooks 内转 CombatService)
gameCombat.get_player_grade1 = function (_msg) {
if (window.SubGameHooks && SubGameHooks.get_player_grade1) return SubGameHooks.get_player_grade1(_msg);
};
gameCombat.get_player_grade2 = function (_msg) {
if (window.SubGameHooks && SubGameHooks.get_player_grade2) return SubGameHooks.get_player_grade2(_msg);
};
// 战绩入口:13 号精灵点击 → 发送获取战绩请求(Net.Send_get_player_grade1,平台底层发送)
// 点击经 平台 → Game_Modify.mouseup → SubGameHooks.mouseup → SpriteEventController 链到达
if (typeof SpriteEventController !== 'undefined' && SpriteEventController.registerMouseUp) {
SpriteEventController.registerMouseUp(13, function () {
if (typeof Net !== 'undefined' && Net.Send_get_player_grade1) {
Net.Send_get_player_grade1({
agentid: GameData.AgentId,
playerid: C_Player.playerid,
gameid: GameData.GameId,
type: 0
});
}
});
}
// 平台通用精灵交互(从 SubGameHooks 移入;经 Game_Modify.mouseup/gamemydraw→SubGameHooks→SpriteEventController 分发)
// 注:25 号(创建房间提交)已归创建房间职责,移至 codes/game/ui/handlers/CreateRoomEventHandler.bindEvents。
if (typeof SpriteEventController !== 'undefined') {
// 150:帮助按钮
if (SpriteEventController.registerMouseUp) {
SpriteEventController.registerMouseUp(150, function () {
if (typeof GameUI !== 'undefined' && GameUI.OpenHelp) { GameUI.OpenHelp(); }
});
}
// 248:协议勾选绘制(勾上时自绘钩)
if (SpriteEventController.registerDraw) {
SpriteEventController.registerDraw(248, function (e) {
if (GameData.ProtocolIndex) {
ifast_mydrawbmp(e.spriteId, 4, 5, 5, 40, 40, 0, 0, 40, 40);
}
});
}
}
@@ -0,0 +1,300 @@
// ============================================================================
// 02_SubGame_Input.template.js —— 平台输入/回调接口「转发壳」模板(框架维护,子游戏不碰)
// 平台按全局名调用 gameHallImport.* / Game_Modify.*;本文件统一转发到 SubGameHooks 同名 hook。
// 子游戏只需在 codes/ 的 SubGameHooks 里实现需要的接口(见 SubGameHooks.template.js)。
// 严格 ES5。接口清单与默认值依据 spec §9。
// ============================================================================
var gameHallImport = gameHallImport || {};
var Game_Modify = Game_Modify || {};
// ============================================================================
// gameHallImport.* — 大厅接口(9 个)
// ============================================================================
// A 类:纯子游戏行为,无 hook 即 no-op
// 注:与 Game_Modify.appStart 同名但语义不同(大厅启动 vs 游戏启动),故 hook 名加 hall 前缀区分
gameHallImport.appStart = function () {
if (window.SubGameHooks && SubGameHooks.hallAppStart) return SubGameHooks.hallAppStart();
};
// A 类:纯子游戏行为,无 hook 即 no-op
gameHallImport.jumpMenuScene = function () {
if (window.SubGameHooks && SubGameHooks.jumpMenuScene) return SubGameHooks.jumpMenuScene();
};
// A 类:纯子游戏行为,无 hook 即 no-op
gameHallImport.gameStart = function () {
if (window.SubGameHooks && SubGameHooks.gameStart) return SubGameHooks.gameStart();
};
// A 类:纯子游戏行为,无 hook 即 no-op
gameHallImport.setGameList = function () {
if (window.SubGameHooks && SubGameHooks.setGameList) return SubGameHooks.setGameList();
};
// A 类:纯子游戏行为,无 hook 即 no-op
gameHallImport.clearGameinfo = function () {
if (window.SubGameHooks && SubGameHooks.clearGameinfo) return SubGameHooks.clearGameinfo();
};
// A 类:纯子游戏行为,无 hook 即 no-op
gameHallImport.getWebdata = function (data) {
if (window.SubGameHooks && SubGameHooks.getWebdata) return SubGameHooks.getWebdata(data);
};
// B 类:有平台默认返回值,无 hook 即返回默认
gameHallImport.isInstalled = function (data) {
if (window.SubGameHooks && SubGameHooks.isInstalled) return SubGameHooks.isInstalled(data);
return 1;
};
// A 类:纯子游戏行为,无 hook 即 no-op
gameHallImport.up_imgurl = function (recid, photo) {
if (window.SubGameHooks && SubGameHooks.up_imgurl) return SubGameHooks.up_imgurl(recid, photo);
};
// A 类:纯子游戏行为,无 hook 即 no-op
gameHallImport.getphoto = function (recid, photo) {
if (window.SubGameHooks && SubGameHooks.getphoto) return SubGameHooks.getphoto(recid, photo);
};
// ============================================================================
// Game_Modify.* — 生命周期/回调接口(来自 02,45 个)
// ============================================================================
// A 类:纯子游戏行为,无 hook 即 no-op
Game_Modify.appStart = function () {
if (window.SubGameHooks && SubGameHooks.appStart) return SubGameHooks.appStart();
};
// A 类:开战回调,无 hook 即 no-op
Game_Modify.StartWar = function (_msg) {
if (window.SubGameHooks && SubGameHooks.StartWar) return SubGameHooks.StartWar(_msg);
};
// A 类:接收数据包,无 hook 即 no-op
Game_Modify._ReceiveData = function (_msg) {
if (window.SubGameHooks && SubGameHooks._ReceiveData) return SubGameHooks._ReceiveData(_msg);
};
// A 类:创建房间,无 hook 即 no-op
Game_Modify.createRoom = function (_roomtype, _infinite) {
if (window.SubGameHooks && SubGameHooks.createRoom) return SubGameHooks.createRoom(_roomtype, _infinite);
};
// A 类:自己进入房间,无 hook 即 no-op
Game_Modify.myJoinRoom = function (_msg) {
if (window.SubGameHooks && SubGameHooks.myJoinRoom) return SubGameHooks.myJoinRoom(_msg);
};
// A 类:重连,无 hook 即 no-op
Game_Modify.Reconnect = function (_deskinfo) {
if (window.SubGameHooks && SubGameHooks.Reconnect) return SubGameHooks.Reconnect(_deskinfo);
};
// A 类:关闭所有声音,无 hook 即 no-op
Game_Modify.stopAllSounds = function () {
if (window.SubGameHooks && SubGameHooks.stopAllSounds) return SubGameHooks.stopAllSounds();
};
// A 类:投票解散确认,无 hook 即 no-op
Game_Modify.Free = function (_msg) {
if (window.SubGameHooks && SubGameHooks.Free) return SubGameHooks.Free(_msg);
};
// A 类:未开战状态加入牌桌数据,无 hook 即 no-op
Game_Modify.DeskInfo = function (_msg) {
if (window.SubGameHooks && SubGameHooks.DeskInfo) return SubGameHooks.DeskInfo(_msg);
};
// A 类:开局后自己退出房间,无 hook 即 no-op
Game_Modify.breakRoom = function () {
if (window.SubGameHooks && SubGameHooks.breakRoom) return SubGameHooks.breakRoom();
};
// A 类:其他玩家加入房间,无 hook 即 no-op
Game_Modify.playerJoinRoom = function (seat) {
if (window.SubGameHooks && SubGameHooks.playerJoinRoom) return SubGameHooks.playerJoinRoom(seat);
};
// A 类:其他玩家离开房间,无 hook 即 no-op
Game_Modify.playerLeaveRoom = function (seat) {
if (window.SubGameHooks && SubGameHooks.playerLeaveRoom) return SubGameHooks.playerLeaveRoom(seat);
};
// A 类:更新游戏界面,无 hook 即 no-op
Game_Modify.updateScene = function () {
if (window.SubGameHooks && SubGameHooks.updateScene) return SubGameHooks.updateScene();
};
// A 类:关闭游戏界面,无 hook 即 no-op
Game_Modify.closeGameScene = function () {
if (window.SubGameHooks && SubGameHooks.closeGameScene) return SubGameHooks.closeGameScene();
};
// A 类:玩家离线,无 hook 即 no-op
Game_Modify.playerOffline = function (seat) {
if (window.SubGameHooks && SubGameHooks.playerOffline) return SubGameHooks.playerOffline(seat);
};
// A 类:玩家上线,无 hook 即 no-op
Game_Modify.playerOnline = function (seat) {
if (window.SubGameHooks && SubGameHooks.playerOnline) return SubGameHooks.playerOnline(seat);
};
// A 类:玩家电话状态,无 hook 即 no-op
Game_Modify.playerphonestate = function (seat, type) {
if (window.SubGameHooks && SubGameHooks.playerphonestate) return SubGameHooks.playerphonestate(seat, type);
};
// A 类:摇一摇事件,无 hook 即 no-op
Game_Modify.shakeEvent = function () {
if (window.SubGameHooks && SubGameHooks.shakeEvent) return SubGameHooks.shakeEvent();
};
// A 类:计算结果,无 hook 即 no-op
Game_Modify.calResult = function (inputArr) {
if (window.SubGameHooks && SubGameHooks.calResult) return SubGameHooks.calResult(inputArr);
};
// A 类:进入游戏主场景,无 hook 即 no-op
Game_Modify.onEnterMainScene = function (roomtype) {
if (window.SubGameHooks && SubGameHooks.onEnterMainScene) return SubGameHooks.onEnterMainScene(roomtype);
};
// A 类:退出游戏主场景,无 hook 即 no-op
Game_Modify.onExitMainScene = function () {
if (window.SubGameHooks && SubGameHooks.onExitMainScene) return SubGameHooks.onExitMainScene();
};
// A 类:获取游戏配置时调用,无 hook 即 no-op
Game_Modify.onGameConfig = function (_gameConfig) {
if (window.SubGameHooks && SubGameHooks.onGameConfig) return SubGameHooks.onGameConfig(_gameConfig);
};
// A 类:进入牌局回放,无 hook 即 no-op
Game_Modify.onEnterVideo = function () {
if (window.SubGameHooks && SubGameHooks.onEnterVideo) return SubGameHooks.onEnterVideo();
};
// A 类:收到投降回包,无 hook 即 no-op
Game_Modify.onSurrender = function (_msg) {
if (window.SubGameHooks && SubGameHooks.onSurrender) return SubGameHooks.onSurrender(_msg);
};
// A 类:玩家准备触发,无 hook 即 no-op
Game_Modify.onReady = function (seat) {
if (window.SubGameHooks && SubGameHooks.onReady) return SubGameHooks.onReady(seat);
};
// B 类:获取房间描述,无 hook 返回默认文案
Game_Modify.getRoomInfo = function (roomtype, type, tea) {
if (window.SubGameHooks && SubGameHooks.getRoomInfo) return SubGameHooks.getRoomInfo(roomtype, type, tea);
return "房间描述" + roomtype;
};
// B 类:获取房间星星场下限,无 hook 返回默认值
Game_Modify.getStarLimit = function (roomtype) {
if (window.SubGameHooks && SubGameHooks.getStarLimit) return SubGameHooks.getStarLimit(roomtype);
return 2001000;
};
// B 类:获取房间星星场倍数,无 hook 返回默认值
Game_Modify.getMult = function (roomtype, type) {
if (window.SubGameHooks && SubGameHooks.getMult) return SubGameHooks.getMult(roomtype, type);
return 10000;
};
// B 类:获取房间全部信息描述,无 hook 返回默认文案
Game_Modify.getFullRoomInfo = function (roomtype) {
if (window.SubGameHooks && SubGameHooks.getFullRoomInfo) return SubGameHooks.getFullRoomInfo(roomtype);
return "房间描述" + roomtype;
};
// A 类:打开帮助页面,无 hook 即 no-op
Game_Modify.onOpenHelp = function (spid) {
if (window.SubGameHooks && SubGameHooks.onOpenHelp) return SubGameHooks.onOpenHelp(spid);
};
// A 类:显示大厅界面,无 hook 即 no-op
Game_Modify.onMainMenuScene = function () {
if (window.SubGameHooks && SubGameHooks.onMainMenuScene) return SubGameHooks.onMainMenuScene();
};
// A 类:数字输入框确认回调,无 hook 即 no-op
Game_Modify.onCheckInput = function (_result) {
if (window.SubGameHooks && SubGameHooks.onCheckInput) return SubGameHooks.onCheckInput(_result);
};
// A 类:创建房间成功后触发,无 hook 即 no-op
Game_Modify.onCreateRoom = function (_data) {
if (window.SubGameHooks && SubGameHooks.onCreateRoom) return SubGameHooks.onCreateRoom(_data);
};
// B 类:获取离场限制,无 hook 返回默认值
Game_Modify.getLeaveLimit = function (roomtype) {
if (window.SubGameHooks && SubGameHooks.getLeaveLimit) return SubGameHooks.getLeaveLimit(roomtype);
return 10;
};
// A 类:成功获取定位信息,无 hook 即 no-op
Game_Modify.onLocationInfo = function (_locationInfo) {
if (window.SubGameHooks && SubGameHooks.onLocationInfo) return SubGameHooks.onLocationInfo(_locationInfo);
};
// A 类:进入游戏界面创建牌桌之前,无 hook 即 no-op
Game_Modify.onCreateDesk = function (roomtype) {
if (window.SubGameHooks && SubGameHooks.onCreateDesk) return SubGameHooks.onCreateDesk(roomtype);
};
// B 类:获取是否开启视频功能,无 hook 返回默认值
Game_Modify.getVideoByRoomType = function (roomtype) {
if (window.SubGameHooks && SubGameHooks.getVideoByRoomType) return SubGameHooks.getVideoByRoomType(roomtype);
return 1;
};
// B 类:获取房间顶部描述数组,无 hook 返回默认文案数组
Game_Modify.getRoomTopDescAry = function (roomtype) {
if (window.SubGameHooks && SubGameHooks.getRoomTopDescAry) return SubGameHooks.getRoomTopDescAry(roomtype);
return ["" + roomtype];
};
// A 类:星星场房间列表,无 hook 即 no-op
Game_Modify.getShareRoom = function (_msg) {
if (window.SubGameHooks && SubGameHooks.getShareRoom) return SubGameHooks.getShareRoom(_msg);
};
// A 类:玩家自己退出房间,无 hook 即 no-op
Game_Modify.myExitRoom = function (seat) {
if (window.SubGameHooks && SubGameHooks.myExitRoom) return SubGameHooks.myExitRoom(seat);
};
// A 类:接收到换座包触发,无 hook 即 no-op
Game_Modify.changeSeat = function (seat1, seat2) {
if (window.SubGameHooks && SubGameHooks.changeSeat) return SubGameHooks.changeSeat(seat1, seat2);
};
// A 类:关闭 vip 选项时调用,无 hook 即 no-op
Game_Modify.onCloseVip = function () {
if (window.SubGameHooks && SubGameHooks.onCloseVip) return SubGameHooks.onCloseVip();
};
// B 类:判断房间是否为金币场,无 hook 返回默认值(0=非金币场)
Game_Modify.getRoomMode = function (roomtype) {
if (window.SubGameHooks && SubGameHooks.getRoomMode) return SubGameHooks.getRoomMode(roomtype);
return 0;
};
// B 类:获取房间最大玩家数量,无 hook 返回默认值
Game_Modify.getMaxPlayerCount = function (roomtype) {
if (window.SubGameHooks && SubGameHooks.getMaxPlayerCount) return SubGameHooks.getMaxPlayerCount(roomtype);
// 默认值依赖全局 Game_Config(须在本模板之前加载);无则回退 4
return (Game_Config.Max && Game_Config.Max.PlayerCnt) || 4;
};
// B 类:更新玩家信息 UI,无 hook 返回 false(让平台默认处理)
Game_Modify.updatePlayerInfoUI = function (targetSeat) {
if (window.SubGameHooks && SubGameHooks.updatePlayerInfoUI) return SubGameHooks.updatePlayerInfoUI(targetSeat);
return false;
};
@@ -0,0 +1,206 @@
// ============================================================================
// SubGameHooks.template.js —— 子游戏接入入口(复制为 codes/SubGameHooks.js 后填实现)
// 平台契约文件(00/01/02 转发壳)会把同名平台接口转发到这里。只需实现【本游戏需要的】hook,
// 未实现的接口由转发壳走平台默认(B)或空操作(A)。严格 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) =====
// [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) { /* ... */ };
// [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) { /* ... */ };
// [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) { /* ... */ };
// [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) { /* ... */ };
// [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) { /* ... */ };
// [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) { /* ... */ };
// [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):未开战状态下加入牌桌的数据同步。
// SubGameHooks.DeskInfo = function (_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) { /* ... */ };
// [B] updatePlayerInfoUI(targetSeat):更新玩家信息 UI;无 hook 时默认:return false(让平台默认处理)。
// SubGameHooks.updatePlayerInfoUI = function (targetSeat) { /* ... */ };
window.SubGameHooks = SubGameHooks;
if (typeof module !== 'undefined' && module.exports) { module.exports = SubGameHooks; }