初始化仓库:友乐/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
+656
View File
@@ -0,0 +1,656 @@
(function(wnd, undef){
//复制json对象
function min_copyjson(json)
{
return JSON.parse(JSON.stringify(json));
}
//json对象转字符串
function min_jsontostr(json) {
return JSON.stringify(json);
}
//字符串转json对象
function min_strtojson(str) {
return JSON.parse(str);
}
//字符串转整型 def:转换失败时返回的默认值
function min_strtoint(str, def) {
var i = parseInt(str);
if (i == 0) {
return 0;
};
if (!i) { //=0也会进来
if (!def) {
def = 0;
};
i = def;
}
return i;
};
//整型转字符串
function min_inttostr(i) {
return i.toString();
};
//整除
function min_div(i, b)
{
if (!b) {
return parseInt(i);
}
return parseInt(i / b);
};
//取余数
function min_mod(a, b){
return a % b;
};
//取绝对值
function min_abs(b) {
return Math.abs(b);
};
//取随机数
function min_random(min, max) {
var Range = max - min;
var Rand = Math.random();
return (min + Math.round(Rand * Range));
};
//取随机数1
function min_random1(num) {
return parseInt(Math.random()*num);
};
//随机字符串
function min_randomChar(length){
var x = "0123456789";
var y = "qwertyuioplkjhgfdsazxcvbnm";
var z = "QWERTYUIOPLKJHGFDSAZXCVBNM";
var tmp = "";
for (var i = 0; i < length; i++) {
switch(min_random(0, 2)) {
case 0:
tmp += x.charAt(Math.ceil(Math.random()*100000000)%x.length);
break;
case 1:
tmp += y.charAt(Math.ceil(Math.random()*100000000)%y.length);
break;
case 2:
tmp += z.charAt(Math.ceil(Math.random()*100000000)%z.length);
break;
default:
break;
}
}
// var timestamp = new Date().getTime();
// return timestamp + tmp;
return tmp;
}
//取长度
function min_length(key) {
if (typeof(key) != "string") {
var key = key + "";
}
return key.length;
}
//字符全替换
function min_replaceAll(str, str_old, str_new, ignoreCase)
{
if (!RegExp.prototype.isPrototypeOf(str_old)) {
return str.replace(new RegExp(str_old, (ignoreCase ? "gi": "g")), str_new);
} else {
return str.replace(str_old, str_new);
}
}
//取本地当前时间,格式yyyy-MM-dd HH:MM:SS
function min_now()
{
var date = new Date();
var seperator1 = "-";
var seperator2 = ":";
var month = date.getMonth() + 1;
var strDate = date.getDate();
if (month >= 1 && month <= 9) {
month = "0" + month;
}
if (strDate >= 0 && strDate <= 9) {
strDate = "0" + strDate;
}
var currentdate = date.getFullYear() + seperator1 + month + seperator1 + strDate
+ " " + date.getHours() + seperator2 + date.getMinutes()
+ seperator2 + date.getSeconds();
return currentdate;
}
//本地存储数据
function min_writefile_gameid(msg, gameid, fileid) {
localStorage.setItem("file_" + gameid + "_" + fileid, msg);
}
//读取本地数据
function min_readfile_gameid(gameid, fileid) {
return localStorage.getItem("file_" + gameid + "_" + fileid);
}
//取当前页面url中的参数值 def:没取到时返回的默认值
function min_getQueryString(name, def) {
var self = window;
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
var r = self.location.search.substr(1).match(reg);
if (r != null) {
return unescape(r[2])
} else {
if (def) {
return def;
} else {
return null;
}
}
}
//获取当前页面的路径
function min_getUrlRootPath() {
var curWwwPath = window.location.host;
var pathName = window.location.pathname;
return curWwwPath + pathName.substr(0,pathName.lastIndexOf('/'));
}
//设置cookie
function min_setCookie(name, value, exp_minute) {
if (!exp_minute) {
exp_minute = 20; //默认时效20分钟
}
var exp = new Date();
exp.setTime(exp.getTime() + exp_minute*60*1000);
document.cookie = name + "=" + value + ";expires=" + exp.toGMTString()+';path=/';
}
//读取cookie
function min_getCookie(name) {
var arr = document.cookie.match(new RegExp("(^| )"+name+"=([^;]*)(;|$)"));
if(arr != null)
return arr[2];
return null;
}
//删除cookie
function min_delCookie(name) {
var value = min_getCookie(name);
if (value) {
min_setCookie(name, value, 0);
}
}
//获取一个dom
function min_getdom(id)
{
return document.getElementById(id);
};
//设置一个dom属性值 id:dom、或dom的id、或实列,key:属性,val:值
function min_setdom(id, key, val) {
var obj;
if (typeof(id) == 'string') {
obj = min_getdom(id);
}
else {
obj = id;
}
obj.setAttribute(key, val);
}
//添加一段innerHTML
function min_uphtml(id, str, isadd) {
/*
setAttribute是设置网页元素的属性,就是在标签里内如<img />标签的src属性。
innerHTML不是属性,只是JS里内代表一个双标记中间的文本,如:<span> </span>中间的字符。
*/
var obj = window.document.getElementById(id);
if (isadd) {
obj.innerHTML = obj.innerHTML + str;
}
else {
obj.innerHTML = str;
}
}
//新建一个定时器每隔time毫秒执行一次func函数,函数返回定时器id
function min_ontime(func, time)
{
return setInterval(func, time);
}
//新建一个定时器time毫秒后执行一次func函数(只执行一次),函数返回定时器id
function min_ontimeout(func, time)
{
return setTimeout(func, time);
}
//关闭定时器id为timerid的定时器
function min_closetime(timerid)
{
return clearTimeout(timerid);
}
//encode转码
function min_encode(s)
{
return encodeURIComponent(s);
}
//decode解码
function min_decode(s)
{
return decodeURIComponent(s);
}
//新建一个tcp连接
function min_tcp(config)
{
var ws = new WebSocket("ws://" + config.ipport); //"127.0.0.1:5414"
//连接上服务器后触发的事件
if (config.onopen) {
ws.onopen = config.onopen;
};
//收到服务器发来的数据包后触发的事件,onmessage函数会有一个底层的msg参数,其中msg.data才是服务器发过来的业务数据
if (config.onmessage) {
ws.onmessage = config.onmessage;
};
//断开与服务器的连接后触发的事件
if (config.onclose) {
ws.onclose = config.onclose; //断开连接的事件
};
return ws;
}
//http请求
function min_http(config) {
/*
config =
{
url: "http://127.0.0.1:5414/index.html",
type: "POST", //GET or POST 方法
data: "", //请求的数据
success: func_callback_succ, //请求成功后的回调函数function(data,status,callbackdata)
error: func_callback_err, //请求失败后的回调函数function(data,status)
callbackdata: "", //作为回调函数第三个参数带入回调函数的数据
//enurl: 0, //是否encodeURIComponent转码, 默认0不转码
//deurl: 0, //是否decodeURIComponent解码,默认0不解码
debugLog: false, //是否输出debug日志,默认false
method: "(OPTIONAL) True for async and False for Non-async | By default its Async"
}
*/
if (!config.debugLog) {
config.debugLog = false;
}
if (!config.enurl) {
config.enurl = 0;
}
if (!config.deurl) {
config.deurl = 0;
}
if (!config.method) {
config.method = true;
}
if (!config.url) {
if (config.debugLog == true) {
console.log("No Url!");
}
return;
};
if (!config.type) {
if (config.debugLog == true) {
console.log("No Default type (GET/POST) given!");
}
return;
}
var xmlhttp = initXMLhttp();
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200 && config.success)
{
var responseText = mydecodeURIComponent(xmlhttp.responseText, config.deurl);
if (!config.callbackdata) {
config.success(responseText, xmlhttp.readyState);
}
else {
config.success(responseText, xmlhttp.readyState, config.callbackdata);
}
if (config.debugLog == true) {
console.log("SuccessResponse");
}
if (config.debugLog == true) {
console.log("Response Data:" + xmlhttp.responseText);
}
}
else
{
if (xmlhttp.readyState == 4 && config.error)
{
if (!config.callbackdata) {
config.error(xmlhttp.readyState, xmlhttp.status);
}
else {
config.error(xmlhttp.readyState, xmlhttp.status, config.callbackdata);
}
}
if (config.debugLog == true) {
console.log("FailureResponse --> readyState:" + xmlhttp.readyState + ", Status:" + xmlhttp.status);
}
}
}
var sendString = [],
sendData = config.data;
if (typeof sendData === "string") {
var tmpArr = String.prototype.split.call(sendData, '&');
for (var i = 0, j = tmpArr.length; i < j; i++) {
var datum = tmpArr[i].split('=');
if (datum[1]) {
sendString.push(myencodeURIComponent(datum[0], config.enurl) + "=" + myencodeURIComponent(datum[1], config.enurl));
}
else {
sendString.push(myencodeURIComponent(datum[0], config.enurl));
}
}
}
else if (typeof sendData === 'object' && !(sendData instanceof String || (FormData && sendData instanceof FormData))) {
for (var k in sendData) {
var datum = sendData[k];
if (Object.prototype.toString.call(datum) == "[object Array]") {
for (var i = 0, j = datum.length; i < j; i++) {
sendString.push(myencodeURIComponent(k, config.enurl) + "[]=" + myencodeURIComponent(datum[i], config.enurl));
}
}
else {
sendString.push(myencodeURIComponent(k, config.enurl) + "=" + myencodeURIComponent(datum, config.enurl));
}
}
}
sendString = sendString.join('&');
if (config.type == "GET") {
var g;
var i = config.url.lastIndexOf("?");
if (i > 8) {
g = "&";
} else {
g = "?";
}
var ddata = new Date().getMilliseconds();
if (sendString == "") {
sendString = '#dfw1977=' + (ddata + min_random(1, 99999) * 1000);
} else {
sendString = sendString + '#dfw1977=' + (ddata + min_random(1, 99999) * 1000);
}
xmlhttp.open("GET", config.url + g + sendString, config.method);
xmlhttp.send();
if (config.debugLog == true) {
console.log("GET fired at:" + config.url + "?" + sendString);
}
}
if (config.type == "POST") {
xmlhttp.open("POST", config.url, config.method);
xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xmlhttp.send(sendString);
if (config.debugLog == true) {
console.log("POST fired at:" + config.url + " || Data:" + sendString);
}
}
}
function initXMLhttp() {
var xmlhttp;
if (window.XMLHttpRequest) {
//code for IE7,firefox chrome and above
xmlhttp = new XMLHttpRequest();
} else {
//code for Internet Explorer
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
return xmlhttp;
}
//转码
function myencodeURIComponent(s, ifif)
{
if (ifif == 1) {
return min_encode(s);
} else {
return s;
}
}
//解码
function mydecodeURIComponent(s, ifif)
{
if (ifif == 1) {
return min_decode(s);
} else {
return s;
}
}
//在数组中根据元素值查找下标
function min_ary_indexof(array1, val, name)
{
for (var i = 0; i < array1.length; i++)
{
if (!name)
{
if (array1[i] == val)
return i;
}
else
{
if (array1[i][name] == val)
return i;
}
}
return -1;
};
//在数组中根据值删除元素
function min_ary_delval(array1, val, name)
{
var index = min_ary_indexof(array1, val, name);
if (index > -1)
{
array1.splice(index, 1);
}
};
//在数组中根据下标删除诺干个元素
function min_ary_delfromto(array1, from, to)
{
var rest = array1.slice((to || from) + 1 || array1.length);
array1.length = from < 0 ? array1.length + from : from;
array1.push.apply(array1, rest);
};
//在数组中删除某一对象元素
function min_ary_delobj(array1, object)
{
for (var i = 0; i < array1.length; ++i)
{
if (array1[i] === object)
{
array1.splice(i, 1);
break;
}
}
};
//数组包含
function min_ary_include(aryparent, arychild){
for (var i = 0; i < arychild.length; i++) {
var found = false;
for (var j = 0; j < aryparent.length; j++) {
if (aryparent[j] == arychild[i]){
found = true;
break;
}
}
if (!found){
return false;
}
}
return true;
};
//数组相减
function min_ary_deduct(aryparent, arychild){
var re = [];
for (var i = 0; i < aryparent.length; i++){
var found = false;
for (var j = 0; j < arychild.length; j++){
if (aryparent[i] == arychild[j]){
found = true;
break;
}
}
if (!found){
re.push(aryparent[i])
}
}
return re;
};
//是否存在指定函数
function min_ExitsFunction(funcName)
{
try
{
if (typeof(funcName) == "function")
{
return true;
}
}
catch(e)
{}
return false;
}
//(按顺序)加载js文件
function min_loadJsFile(str_jsfile, func_succ, NoRandomFlag)
{
var domScript = document.createElement('script');
if (!NoRandomFlag)
{
// str_jsfile = str_jsfile + '?' + Math.random() * 10000;
// str_jsfile = str_jsfile + '?' + min_random(1, 10000000);
str_jsfile = str_jsfile + '?' + min_timestamp();
}
domScript.src = str_jsfile;
func_succ = func_succ || function(){};
domScript.onload = domScript.onreadystatechange = function() {
if (!this.readyState || 'loaded' === this.readyState || 'complete' === this.readyState) {
func_succ();
this.onload = this.onreadystatechange = null;
this.parentNode.removeChild(this);
}
}
document.getElementsByTagName('head')[0].appendChild(domScript);
}
//生成一个GUID
function min_guid()
{
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);});
}
//获取时间戳
function min_timestamp()
{
return new Date().getTime();
}
wnd.min_copyjson = min_copyjson; //复制json对象
wnd.min_jsontostr = min_jsontostr; //json转字符串
wnd.min_strtojson = min_strtojson; //字符串转json
wnd.min_inttostr = min_inttostr; //整型转字符型
wnd.min_strtoint = min_strtoint; //字符型转整型
wnd.min_div = min_div; //整除
wnd.min_mod = min_mod; //取余数
wnd.min_abs = min_abs; //取绝对值
wnd.min_random = min_random; //取随机数
wnd.min_random1 = min_random1; //取随机数1
wnd.min_randomChar = min_randomChar; //随机字符串
wnd.min_length = min_length; //取长度
wnd.min_replaceAll = min_replaceAll; //字符全替换
wnd.min_now = min_now; //取本地当前时间
wnd.min_guid = min_guid; //生成一个GUID
wnd.min_getQueryString = min_getQueryString; //取当前页面url中的参数值
wnd.min_getUrlRootPath = min_getUrlRootPath; //获取当前页面的路径
wnd.min_setCookie = min_setCookie; //设置cookie
wnd.min_getCookie = min_getCookie; //读取cookie
wnd.min_delCookie = min_delCookie; //删除cookie
wnd.min_getdom = min_getdom; //获取一个dom
wnd.min_setdom = min_setdom; //设置一个dom属性值
wnd.min_uphtml = min_uphtml; //添加一段innerHTML
wnd.min_ontime = min_ontime; //新建一个周期性的定时器
wnd.min_ontimeout = min_ontimeout; //新建一个一次性的定时器
wnd.min_closetime = min_closetime; //关闭定时器
wnd.min_writefile_gameid = min_writefile_gameid; //本地存储数据
wnd.min_readfile_gameid = min_readfile_gameid; //读取本地数据
wnd.min_encode = min_encode; //encodeURIComponent转码
wnd.min_decode = min_decode; //decodeURIComponent解码
wnd.min_tcp = min_tcp; //新建一个tcp连接
wnd.min_http = min_http; //http请求
wnd.min_ary_indexof = min_ary_indexof; //在数组中根据元素值查找下标
wnd.min_ary_delval = min_ary_delval; //在数组中根据值删除元素
wnd.min_ary_delfromto = min_ary_delfromto; //在数组中根据下标删除诺干个元素
wnd.min_ary_delobj = min_ary_delobj; //在数组中删除某一对象元素
wnd.min_ary_include = min_ary_include; //数组包含
wnd.min_ary_deduct = min_ary_deduct; //数组相减
wnd.min_ExitsFunction = min_ExitsFunction; //是否存在函数
wnd.min_loadJsFile = min_loadJsFile; //加载js文件
wnd.min_guid = min_guid; //生成一个GUID
wnd.min_timestamp = min_timestamp; //获取时间戳
})(window);
+822
View File
@@ -0,0 +1,822 @@
var AppList = AppList||{};
var RouteList = RouteList||{};
var RpcList = RpcList||{};
//--------------AppList---------------
AppList.app = "youle";
//-----------RouteList-------------
RouteList.platform = "platform";
RouteList.agent="agent";
RouteList.room="room";
//----------------RpcList--------------
RpcList.player_login = "player_login";
RpcList.self_join_room = "self_join_room";
RpcList.create_room = "create_room";
RpcList.self_break_room = "self_break_room";
RpcList.other_break_room = "other_break_room";
RpcList.other_join_room = "other_join_room";
RpcList.self_exit_room = "self_exit_room";
RpcList.other_exit_room = "other_exit_room";
RpcList.self_apply_free_room = "self_apply_free_room";
RpcList.other_apply_free_room = "other_apply_free_room";
RpcList.self_agree_free_room = "self_agree_free_room";
RpcList.other_agree_free_room = "other_agree_free_room";
RpcList.self_refuse_free_room = "self_refuse_free_room";
RpcList.other_refuse_free_room = "other_refuse_free_room";
RpcList.free_room = "free_room";
RpcList.over_game = "over_game";
RpcList.get_player_grade1 = "get_player_grade1";
RpcList.get_player_grade2 = "get_player_grade2";
RpcList.update_roomcard = "update_roomcard";
RpcList.other_offline = "other_offline";
RpcList.other_online = "other_online";
RpcList.send_voice = "send_voice";
RpcList.play_voice = "play_voice";
RpcList.send_text = "send_text";
RpcList.receive_chat = "receive_chat";
RpcList.get_player_task = "get_player_task";
RpcList.player_finish_task = "player_finish_task";
RpcList.get_task_award = "get_task_award";
RpcList.can_award = "can_award";
RpcList.kick_offline = "kick_offline";
RpcList.call_phone = "call_phone";
RpcList.other_callphone = "other_callphone";
RpcList.hangup_phone = "hangup_phone";
RpcList.other_hangup = "other_hangup";
RpcList.self_makewar = "self_makewar";
RpcList.other_makewar = "other_makewar";
RpcList.agentserver_game="agentserver_game";
RpcList.send_gift="send_gift";
RpcList.other_send_gift="other_send_gift";
RpcList.send_voice="send_voice";
RpcList.play_voice="play_voice";
RpcList.connect_roomserver="connect_roomserver";
RpcList.connect_agentserver="connect_agentserver";
RpcList.broadcast="broadcast";
RpcList.send_phiz="send_phiz";
RpcList.submit_opinion = "submit_opinion";
RpcList.kick_server = "kick_server";
RpcList.get_paylist = "get_paylist";
RpcList.pay_succ = "pay_succ";
RpcList.submit_location = "submit_location";
RpcList.binding_invitecode = "binding_invitecode";
RpcList.update_bean = "update_bean";
RpcList.get_player_invitecode = "get_player_invitecode";
RpcList.beanroom_surrender = "beanroom_surrender";
RpcList.player_prepare = "player_prepare";
RpcList.share_room = "share_room";
RpcList.get_share_room = "get_share_room";
RpcList.quick_enter_share_room = "quick_enter_share_room";
RpcList.advanced_roomlist = "advanced_roomlist";
RpcList.advanced_createroom = "advanced_createroom";
RpcList.change_room = "change_room";
RpcList.change_seat = "change_seat";
RpcList.set_bankpwd = "set_bankpwd";
RpcList.binding_phone = "binding_phone";
RpcList.send_phone_checkcode = "send_phone_checkcode";
RpcList.send_phone_code_wechat = "send_phone_code_wechat";
RpcList.get_treasurelist = "get_treasurelist";
RpcList.change_star = "change_star";
RpcList.submit_phoneinfo = "submit_phoneinfo";
RpcList.update_charm = "update_charm";
RpcList.setSign = "setSign";
RpcList.switchRoomList = "switchRoomList";
RpcList.getInfoByShortCode = "getInfoByShortCode";
RpcList.optBanList = "optBanList";
RpcList.getShortCodeRankList = "getShortCodeRankList";
RpcList.setAllCharm = "setAllCharm";
RpcList.getVipRankList = "getVipRankList";
RpcList.playerBehavior = "playerBehavior";
RpcList.setVipForbidSelect = "setVipForbidSelect";
RpcList.topup_card = "topup_card";
RpcList.query_player2 = "query_player2";
RpcList.giveCoin = "giveCoin";
RpcList.getPlayerWhiteList = "getPlayerWhiteList";
RpcList.optWhiteList = "optWhiteList";
var h5RpcList={}
h5RpcList.joinRoom = "joinRoom";
//----------------------------end------------------------------
var ClickBtn = [3,5,7,13,14,15,10,11,12,25,17,9,47,51,53,55,58,59,76,60,61,74,75,80,115,183,150,151,153,181,201,202,183,184,185,205,206,207,208,209,210,211,212,166,167,168,169,170,124,127,130,
133,233,245,246,247,253,248,249,253,256,257,258,259,260,261,262,263,550,177,283,375,391,392,393,394,395,396,397,
398,399,400,401,402,403,366,367,386,464,234,345,483,484,493,509,512,513,514,515,519,520,521,568,569,570,571,572,579,
602,626,604,656,661,662,663,664,665,666,667,668,669,670,671,3000,3004,3007,3008,3009,3010,3011,3014,3016,3017,3018,3019,
3034,3035,3044,3045,3046,3047,3048,3049,3050,3051,3052,3053,3054,3055,3057,3043,3038,3039,3067,3068,3087,3088,3089,3096,3075,3076,3097,3111,3120,92,93,97,
99,104,105,106,107,3133,3139,3140,3144,3145,3149,3155,3159,679,3182,3184,3175,3194,3195,3199,3214,3215,3205,3206,3210,
3203,3216,3217,3222,3231,3232,3233,3235,3240,3251,3253,3243,3244,3245,3255,3256,3258,3260,3264,3265,3266,3268,3269,3289,3281,3276,3274];
var C_PageOne = {
Tag:1,
X:1100,
Y:160,
H:80
};
var C_PageTwo = {
Tag:1,
X:1100,
Y:160,
H:80
};
var UnClickBtn = [8,50,225,227,228,229,699,161,162,164,165,370,426,427,428,429,430,431,432,433,434,435,446,447,448,449,450,451,452,453,454,455,468,469,470,471,472,473,474,475,476,477,498,499,500,501,
502,503,504,505,506,507,81,573,574,575,629,630,631,632,633,3056,3040,3058,3041,3061,3062,3071,3072,3069,3070,3090,3091,3092,3093,3094,3095,3098,3104,3108,3115,3116,3118,3119,
102,336,337,338,155,154,156,110,111,3127,3128,3141,3142,3146,3147,3151,3157,3099,3197,3212,3237,3246,3247,3248,3249,3250,3291,3280,3288,3283,3279,3292
];
var exceptSrc = [115,5,253,338,339,340,21,361,362,363,364,365,366,367,368,369,370,371,372,396];
var ConstVal=ConstVal||{};
ConstVal.Max = {
ShowChat:2000,
textlength:20,
headImgTimer:200,
heartbeat:30000
};
ConstVal.Tips={
width:30,
time:3000,
};
ConstVal.Kick={
text:"网络出现异常!",
tip:"(请重启游戏)"
};
ConstVal.screenShotParam = 0.7;
ConstVal.ShareTaskId="TJnueK1103Xgwj2o79xM2nvSe0WN6rzp";
ConstVal.iMessage={
text_y1:-60,
text_y2:25,
icon_x:20,
icon_y1:-65,
icon_y2:20,
close_x:1205,
close_y1:-60,
close_y2:25,
bg_y1:-85,
bg_y2:0,
time:500,
x1:85,
x2:1205,
y:0,
h:85,
speed:0.05,
max_length:60
};
ConstVal.Emotion = {
//动画帧间隔200
mode:1,//表情播放时间模式1->按帧数确定时间2->按照show_time_list确定
count:20,
f_spid:467,
tag:1,
rate:100,
col_num:4,//一行上的个数
spid_list:[528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547],//表情精灵列表序号对应
src_list:[147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166],//表情对应资源号
frame_list:[5,25,31,30,14,7,8,17,3,8,30,14,35,24,23,15,6,24,31,9],//表情对应帧数
show_time_list:[147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166],//表情播放时间
x:20,
y:20,
dis_x:20,
dis_y:20,
w:100,
h:100,
clip_x:0,
clip_y:0,
clip_h:0,
clip_w:0
}
ConstVal.Com_History = {
//动画帧间隔200
f_spid:696,
head_tag:1,
page_count:5,
text_width:12.5,
bubble_tag:10000,
content_tag:20000,
time_tag:30000,
time_length:[10,30,60,90,120,300],
bubble_width:[100,140,180,220,260,300,340],//气泡长度
head_res_id:116,
head_x:20,
head_y:30,
head_h:60,
head_dis_y:35,
time_bubble_dis_x:10,
head_bubble_dis_x:78,
bubble_content_dis_x:25,
head_bubble_dis_y:0,
head_content_1_dis_y:13,
head_content_2_dis_y:13,
time_bubble_dis_y:13,
clip_x:0,
clip_y:0,
clip_h:0,
clip_w:0
}
ConstVal.Task = {
f_spid:176,//父精灵
bg_spid:120,//背景精灵
task_des_spid:135,//任务描述精灵
reward_des_spid:307,//任务奖励描述
proc_bg_spid:311,//任务进度背景
proc_spid:315,//任务进度前景
proc_des_spid:319,//任务进度文字描述
get_reward_btn_spid:124,//领取任务按钮
isreward_spid:328,//已领取
tofinish_btn_spid:125,//去完成任务
//tag值
bg_tag:1,
task_des_tag:1000,
reward_des_tag:2000,
proc_bg_tag:3000,
proc_tag:4000,
proc_des_tag:5000,
get_reward_btn_tag:6000,
isreward_tag:7000,
tofinish_btn_tag:8000,
//位置
bg_x:0,
bg_y:0,
bg_w:0,//动态获取
bg_dis_x:0,
task_des_x:30,
task_des_y:30,
reward_des_y:265,
proc_bg_y:380,
proc_y:380,
proc_des_y:380,
get_reward_btn_y:368,
isreward_y:365,
tofinish_btn_y:330,
clip_x:110,
clip_y:170,
clip_w:0,
clip_h:0
}
ConstVal.Launch_mode={
name:"Launchtype",
from_hall:1,
from_self:0
};
ConstVal.OS = {//操作系统类型
apple:1,//苹果
android:2,//安卓
winphone:3,//windowsphone
other:4//其他
};
ConstVal.InterEffSoundsList = ["00001.mp3","00002.mp3","00003.mp3","00004.mp3"];
ConstVal.soudsSpidList = [235,13,5,6,7,9,339,11,12,14,15,249,10,376,377,378,379,380,381,382,383,384,385,150,153,151,283,181,513,233,185];
ConstVal.Broadcast = {
textwidth:18,
speed:0.1,
clip_x:0,
clip_y:0,
clip_w:0,
clip_h:0
};
ConstVal.Notice = {
replaceOldWord:"#",
replaceNewWord:"\n",
lineHeight:60,
textHeight:30,
textWidth:14,
clip_x:0,
clip_y:0,
clip_w:725,
clip_h:450,
img_clip_x:366,
img_clip_y:156,
img_clip_w:765,
img_clip_h:483,
};
ConstVal.payConfig = {
enablePay:false
};
ConstVal.loginButton = {
key:"loginimage",
type1:[1,22],//0->类型值 1->图片资源ID
type2:[2,203]
}
ConstVal.userAgent = {
paraName:"",
uAgent_1:1,
uAgent_2:2,
uAgent_3:3
};
ConstVal.worldRoom = {
f_sp_1:522,
f_sp_2:523,
bg_sp_1:524,
bg_sp_2:525,
top_sp_1:526,
top_sp_2:563,
des_sp_1:527,
des_sp_2:564,
starbg_sp_1:566,
starbg_sp_2:567,
star_sp_1:562,
star_sp_2:565,
mult_sp_1:3020,
mult_sp_2:3021,
join_sp_1:3022,
join_sp_2:3023,
pCount_sp_1:3024,
leave_sp_2:3025,
topImg_sp:96,
title_sp:3165,
multIcon_sp:94,
multImg_sp:95,
imgList:[361,362,363,364,365,366,367,368,369,370,371,372],
multIcon_y:100,
multImg_y:119,
topImg_y:25,
multImgWidth:25,
multIconImgSpace:20,
pCountIcon_sp_2:3026,
pCount_sp_2:3027,
roomCode_sp_2:3028,
bg_sp_tag_1:1,
top_sp_tag_1:2000,
des_sp_tag_1:4000,
starbg_sp_tag_1:6000,
star_sp_tag_1:8000,
mult_sp_tag_1:10000,
pCount_sp_tag_1:12000,
join_sp_tag_1:14000,
multIcon_tag:16000,
multImg_tag:18000,
topImg_tag:20000,
bg_sp_tag_2:1,
top_sp_tag_2:2000,
des_sp_tag_2:4000,
starbg_sp_tag_2:6000,
star_sp_tag_2:8000,
mult_sp_tag_2:10000,
leave_sp_tag_2:12000,
roomCode_sp_tag_2:14000,
pCountIcon_sp_tag_2:16000,
join_sp_tag_2:18000,
pCount_sp_tag_2:20000,
title_tag:40000,
clip_x_1:0,
clip_x_2:0,
clip_y_1:0,
clip_y_2:0,
clip_w_1:1050,
clip_w_2:1150,
clip_h_1:512,
clip_h_2:465,
bg_sp_x_1:0,
bg_sp_x_2:0,
bg_sp_y_1:0,
bg_sp_y_2:0,
//bg_sp_y_2:43,
bg_sp_space_1:6,
bg_sp_space_2:15,
top_sp_x_1:25,
top_sp_x_2:25,
top_sp_y_1:35,
top_sp_y_2:20,
top_sp_width_1:20,
top_sp_width_2:13,
top_sp_space_1:6,
top_sp_space_2:6,
des_sp_x_1:25,
des_sp_x_2:30,
title_sp_x:20,
title_sp_y:3,
des_sp_y_1:80,
des_sp_y_2:45,
des_sp_width_1:11,
des_sp_width_2:11,
des_sp_space_1:6,
des_sp_space_2:6,
//mult_sp_x_1:10000,
pCount_sp_x_1:80,
join_sp_x_1:302,//靠右显示
mult_sp_y_1:74,
pCount_sp_y_1:193,
join_sp_y_1:193,
mult_sp_x_2:30,
roomCode_sp_x_2:775,
pCountIcon_sp_x_2:170,
join_sp_x_2:700,//靠右显示
leave_sp_x_2:700,//靠右显示
pCountIcon_pCount_space_2:10,
pCount_space_2:8,
//mult_sp_y_2:72,
//roomCode_sp_y_2:25,
//pCountIcon_sp_y_2:76,
//join_sp_y_2:72,
//leave_sp_y_2:23,
//pCount_sp_y_2:81,
mult_sp_y_2:92,
roomCode_sp_y_2:45,
pCountIcon_sp_y_2:96,
join_sp_y_2:92,
leave_sp_y_2:45,
pCount_sp_y_2:101,
starbg_sp_x_1:35,
starbg_sp_x_2:35,
starbg_sp_y_1:182,
starbg_sp_y_2:182,
starbg_sp_space_1:6,
starbg_sp_space_2:6,
star_sp_x_1:35,
star_sp_x_2:35,
star_sp_y_1:189,
star_sp_y_2:189,
star_sp_space_1:6,
star_sp_space_2:6,
mult_sp_w_1:13,
join_sp_w_1:10,
join_sp_w_2:12,
leave_sp_w_2:12,
mult_sp_w_2:13,
roomCode_sp_w_2:16,
bgSpaceX:438,
bgSpaceY:250,
};
ConstVal.snrRoomList ={
f_spid:611,
bg_spid:622,
rcode_bg_spid:623,
rcode_spid:624,
close_spid:625,
update_spid:626,
switch_spid:627,
info_spid:600,
profit_spid:601,
video_spid:3138,
bg_tag:1,
rcode_bg_tag:200,
rcode_tag:400,
close_tag:600,
update_tag:800,
switch_tag:1000,
info_tag:1200,
profit_tag:1400,
video_tag:1600,
clip_x:0,
clip_y:0,
clip_w:900,
clip_h:0,
bg_x:15,
rcode_bg_x:33,
rcode_x:35,
close_x:70,
update_x:20,
switch_x:150,
info_x:35,
profit_x:150,
video_x:420,
bg_y:10,
rcode_bg_y:260,
rcode_y:28,
close_y:260,
update_y:90,
switch_y:90,
info_y:80,
profit_y:25,
video_y:20,
profit_w:12,
bg_space:5,
rcode_bg_space:35,
rcode_space:50,
close_space:300,
update_space:400,
switch_space:500,
bksp:3137,
};
ConstVal.rankList ={
f_spid:609,
bg_spid:610,
badge_spid:629,
headimg_spid:630,
headfront_spid:631,
name_spid:632,
star_spid:633,
bg_tag:1,
badge_tag:100,
headimg_tag:200,
headfront_tag:300,
name_tag:400,
star_tag:500,
clip_x:0,
clip_y:0,
clip_w:0,
clip_h:511,
bg_x:0,
badge_x:-20,
headimg_x:93,
headfront_x:85,
name_x:190,
star_x:190,
bg_y:0,
badge_y:-5,
headimg_y:12,
headfront_y:5,
name_y:8,
star_y:42,
space:13,
headsrc:262
};
ConstVal.MenuScene = {
createBtn_1:[780,182],//有星星场
joinBtn_1:[780,320],
starBtn:[780,458],
createBtn_2:[780,230],
joinBtn_2:[780,405]
};
//仓库
ConstVal.wareHouse = {
tableBgX:[41,33],
tableTextX:[57,51],
passWordLength:6,
};
ConstVal.sysNotice = {
f_spid:3109,
bubble_spid:163,
msg_spid:3110,
msg_width:11,
msg_tag:50000,
msg_rOld:"#",
msg_rNew:"\n",
lineHeight:25,
limit:10000,
bubble_tag:1,
x:20,
y:20,
spaceX:40,
spaceY:40,
bm_space_x:20,
bm_space_y:35,
lineSpace:30,// 行距
noteSpace:40,//每条的距离
clip_x:0,
clip_y:0,
clip_h:0,
clip_w:0
}
ConstVal.starConfig = {
resList:[214,214],
}
ConstVal.Animation = {
time1:1200,
time2:2000,
fundTime:5230,
deltaTime:1000,
};
ConstVal.myRoomList ={
maxLength:5,
fSpid:98,
bgSp:100,
roomCodeSp:102,
bgTag:1,
roomCodeTag:100,
placeX:0,
placeY:95,
bgX:8,
bgY:55,
space:5,
rCodeX:140,
rCodeY:85,
//clip_x:8,
//clip_y:160,
//clip_w:265,
//clip_h:450,
roomCodeWidth:14,
}
ConstVal.netType = 0;
ConstVal.isGameHall = false;
ConstVal.vipRoomList1 = {
f_spid:3172,
bg_spid:3176,
title_spid:3177,
code_spid:3178,
owner_spid:3179,
count_spid:3180,
del_spid:3181,
des_spid:3189,
enter_spid:3192,
bg_tag:1,
title_tag:1000,
code_tag:2000,
owner_tag:3000,
count_tag:4000,
des_tag:5000,
del_tag:6000,
enter_tag:7000,
clip_x:0,
clip_y:0,
clip_w:0,
clip_h:460,
space:199,
title_x:20,
code_x:70,
des_x:25,
owner_x:270,
count_x:525,
del_x:583,
enter_x:514,
title_y:3,
code_y:148,
des_y:50,
owner_y:146,
count_y:146,
del_y:0,
enter_y:42,
}
ConstVal.vipRoomList2 = {
f_spid:3185,
bg_spid:3186,
code_spid:3187,
count_spid:3188,
bg_tag:1,
code_tag:1000,
count_tag:2000,
clip_x:0,
clip_y:0,
clip_w:0,
clip_h:460,
spaceX:285,
spaceY:145,
code_x:56,
count_x:50,
code_y:25,
count_y:65,
countW:12,
}
ConstVal.blackList = {
f_spid:3204,
bg_spid:3207,
id_spid:3209,
del_spid:3208,
bg_tag:1,
id_tag:10000,
del_tag:20000,
clip_x:0,
clip_y:0,
clip_w:0,
clip_h:0,
id_x:25,
del_x:330,
id_y:12,
del_y:4,
space:8,
};
ConstVal.vipRank = {
f_spid:3227,
bg_spid:3223,
txt_spid:3229,
rank_spid:3228,
score_spid:3230,
bg_tag:1,
txt_tag:10000,
rank_tag:20000,
score_tag:30000,
clip_x:0,
clip_y:0,
clip_w:0,
clip_h:0,
txt_x:70,
rank_x:0,
score_x:230,
txt_y:10,
rank_y:4,
score_y:10,
rank_space:10,
space:8,
rank_w:25,
};
ConstVal.whiteList = {
f_spid:3272,
bg_spid:3277,
id_spid:3279,
del_spid:3278,
charm_spid:3292,
bg_tag:1,
id_tag:10000,
del_tag:20000,
charm_tag:30000,
clip_x:0,
clip_y:0,
clip_w:0,
clip_h:0,
id_x:25,
del_x:450,
charm_x:200,
id_y:12,
del_y:4,
charm_y:12,
space:8,
};
File diff suppressed because one or more lines are too long
+582
View File
@@ -0,0 +1,582 @@
/*var returnCitySN = {"cip": "171.34.213.118", "cid": "360000", "cname": "江西省"}*/
var C_Player;
var InfoSex=0;
var arrBan=[];
var RoomCode = "";
var RoomNum = -1;
var CombatCount = 0;
var CombatPage = 0;//战绩页码
var CombatPageTwoIndex = 0;//大局标号
var CombatPageThreeIndex = 0;//小局标号
var CombatInfo = [];//战绩详情
var C_PageOneBtn = [];//战绩页一按钮id
var C_PageTwoBtn = [];//战绩页二按钮id
var CombatBackHeight = 0;//战绩背景高度
var TaskInfo = [];//任务详情
var VoteTime=0;//投票倒计时秒
var GameData=GameData||{};
GameData.HallServer="";
GameData.Server="";
GameData.AjaxUrl="";
GameData.scrollmsg="";
GameData.wechat_gzh="";
GameData.wechat_kfh="";
GameData.qq = "";
GameData.tel = "";
GameData.gzhcolor1 = "";
GameData.gzhcolor2 = "";
GameData.telcolor = "";
GameData.errorMsg = null;
GameData.tcpConnect=true;
GameData.tcpConnectState = false;
GameData.TimerID=0;
GameData.TimerID_2=0;
GameData.timer=10000;
GameData.time_2=15000;
GameData.NetType=0;//是否为首次连接
GameData.Protocol_H=1;
GameData.ProtocolIndex=true;
GameData.isLoad=true;
GameData.LoadCount=0;
GameData.Timesup=false;
GameData.InteractPlayer = -1;
GameData.isCallback=false;
GameData.isBoard=true;
GameData.ConnectType=false;//1->更换服务器不需要重连
GameData.ConnectRpc="";//更换服务器成功后发包的RPC
GameData.ConnectPack={};//更换服务器成功后发包数据
GameData.Battery = 0;//电量
GameData.NetWork = 0;//网络情况1 未连接网络 2 连接wifi 3 连接2G/3G/4G
GameData.WifiLevel = 0;//WiFi强度
GameData.wechat_ewm = "";//二维码
GameData.appstate=true;//true程序前台运行false程序后台运行
GameData.OS = ConstVal.OS.other;//1->IOS 2->Android 3->wp 4->other
GameData.isLogin = false;//是否已登录
GameData.leaveTime=0;//
GameData.disType = false;
GameData.reconnectTimer=0;
GameData.isClose = false;
GameData.isCloseTimer = null;
GameData.firstConnect = true;//第一次连接
GameData.firstConnect_Succ = false;//第一次连接成功
GameData.firstConnect_Timer = null;//第一次连接定时器
GameData.loginTimer = null;
GameData.tryTimes = 0;//链接尝试次数
GameData.isReconnect = false;
GameData.isCreateEmotion = false;
GameData.ChatPage = 1;
GameData.slidEmotion = false;
GameData.communionHistory=[];//聊天历史//二维数组[[方位,类型(1-文字2语音),内容,时长]]
GameData.createHistoryLen = 0;//以创建的精灵的长度
GameData.slideHistory = false;
GameData.TcpTimer = null;//tcp连接失败判断定时器
GameData.slideTask = false;//是否滑动任务
GameData.copyTaskTagList = [];//任务复制精灵tag列表
GameData.versionState = 0;//苹果审核结果
GameData.visitorserver = "";//游客服务器地址
GameData.isVisitor = false;
GameData.shakeID = 0;//摇一摇事件ID
GameData.isRecord = false;
GameData.LaunchMode = ConstVal.Launch_mode.from_self;
GameData.Multiple = "";
GameData.OrgArr=[];
GameData.MultiArr = [];
GameData.isFirstLogin = true;//是否是第一次登陆
GameData.payList = null;
GameData.payData = [0,0];
GameData.visitorClick = 0;
GameData.Scene = 0;
GameData.urlFail = false;
GameData.BroadcastArray = [];
GameData.isBoardcast = false;
GameData.tcpState = [0,0];
GameData.Notice ={
heigth:0,
lineSum:0,
width:0
}
GameData.marketID = 0;
GameData.getJSONState = false;//JSON获取成功状态
GameData.Config = null;//服务器配置json
GameData.AgentLoginImg = "";//代理商图片
GameData.TopUpUrl = "";//索要房卡地址
GameData.tryConnect = false;
GameData.hallConfigName = ["enablePay",
"logoffCallback",//不通知
'worldRoomList',//星星争霸是否可用
'max:',
'min',
'unit',
'shareMsg',
'autoWorldRoomList',//是否自动弹出星星争霸场
'rankList',//是否显示排行榜按钮1模拟排行2真实排行
'groupMsg',//试玩群信息
'isReSetOwnerNote',//是否替换签名
'activity',//是否显示活动如果显示则为[x,y,w,h/*,isOpen,btnUrl,url,actid*/]
'actData:',//常驻活动按钮 [imgUrl,isOpen,jumpUrl,actUrl]
'shareButton:',//是否显示分享到平台
'enableQuickEnter',//是否隐藏快速加入
'storeUrl',//商城url
'bagUrl',//背包url
'shareDesc',//平台分享描述信息
'wareHouse',//是否开启仓库功能0->不开启1->开启保险箱不开启赠送2->开启赠送不开启保险箱3->开启星星赠送(开启保险箱)4->开启房卡赠送(开启保险箱)5->开启星星房卡赠送(开启保险箱)
'betaText',//版本号后的beta显示内容
'launchImgUrl',//启动页图片地址
'shareType:',//分享朋友圈类型0->链接分享 1->图片分享
'shareImgUrl',//分享朋友圈图片链接
'launchWaitTime',//启动等待时间
'shareBgUrl',//分享背景图片
'qqLogin',//是否显示qq登录
'androidPay',//是否开启安卓支付
'channelKey',//支付key
'payCSS',//支付css
'payJS',//支付js
'openVideo'
];//是否开启视频];
GameData.hallConfig = {
enablePay:0,
logoffCallback:0,//不通知
worldRoomList:0,//星星争霸是否可用
max:100,
min:0,
unit:1,
shareMsg:"",
autoWorldRoomList:0,//是否自动弹出星星争霸场
rankList:0,//是否显示排行榜按钮1模拟排行2真实排行
groupMsg:"",//试玩群信息
isReSetOwnerNote:0,//是否替换签名
activity:0,//是否显示活动如果显示则为[x,y,w,h/*,isOpen,btnUrl,url,actid*/]
actData:0,//常驻活动按钮 [imgUrl,isOpen,jumpUrl,actUrl]
shareButton:1,//是否显示分享到平台
enableQuickEnter:1,//是否隐藏快速加入
storeUrl:"",//商城url
bagUrl:"",//背包url
shareDesc:"",//平台分享描述信息
wareHouse:0,//是否开启仓库功能0->不开启1->开启保险箱不开启赠送2->开启赠送不开启保险箱3->开启星星赠送(开启保险箱)4->开启房卡赠送(开启保险箱)5->开启星星房卡赠送(开启保险箱)
betaText:"",//版本号后的beta显示内容
launchImgUrl:"",//启动页图片地址
shareType:0,//分享朋友圈类型0->链接分享 1->图片分享
shareImgUrl:"",//分享朋友圈图片链接
launchWaitTime:10,//启动等待时间
shareBgUrl:"",//分享背景图片
qqLogin:0,//是否显示qq登录
androidPay:0,//是否开启安卓支付
channelKey:"",//支付key
payCSS:"",//支付css
payJS:"",//支付js
openVideo:0,//是否开启视频
};
GameData.starName = "星星";
GameData.roomCardName = "房卡";
GameData.gameConfig = {};
GameData.playerHeadImgState = false;//玩家主界面头像加载状态
GameData.matchhtml = "";//比赛链接地址
GameData.Validator = null;
GameData.loginBtnType = ConstVal.loginButton.type1[0];//登录按钮样式
GameData.netWorkSate = false;
GameData.heartBeatTimer = null;//心跳事件定时器ID
GameData.heartBeatStage = false;//提示框是否处于心跳提示事件
GameData.telSpid = 179;//拨打电话精灵号
GameData.matchInfo = null;//比赛场信息
GameData.agentmode = 1;//邀请码模式
GameData.infMode = 0;//是否为无限局
GameData.infClickCount = 0;//点击次数
GameData.checkType = 1;//确认面板事件类型 1->解散房间 2->退出房间 3->投降
GameData.roomList = [];//房间列表
GameData.sendLoginTimer = null;//发送重连登录包定时器
GameData.loginList = [];//
GameData.isConnected = false;
GameData.isSendLoginState = false;//是否在等待登录包
GameData.isSendLoginTimer = null;//登录包收包定时器
GameData.isTcpConnect = false;//是否在连接状态
GameData.TcpID = 0;//tcpid
GameData.websocketList=[];
GameData.pack="";
GameData.infoSeat = -1;
GameData.sendLoginTimes = 0;
GameData.backUrl = "";
GameData.playerid = "";
GameData.serverConfig = {};
GameData.userAgent = ConstVal.userAgent.uAgent_1;
GameData.getbattery = "";
GameData.getbatteryPack = {};
GameData.getnetwork = "";
GameData.getnetworkPack = {};
GameData.getVersionState = "";
GameData.getVersionStatePack = {};
GameData.getchannelName = "";
GameData.getchannelNamePack = {};
GameData.getmarketname = "";
GameData.getmarketnamePack = {};
GameData.gamepastetext = "";
GameData.gamepastetextPack = {};
GameData.worldRoomSys=[];
//玩家房间数据
GameData.worldRoomPly=[];
GameData.worldRoomSysSlide = false;
GameData.worldRoomPlySlide = false;
GameData.menuNotice = null;
GameData.advertInfo_1 = {
type:0,//是否使用默认的文字通知
imgUrl:"",//图片地址
imgAdUrl:"",//图片跳转链接地址
height:1,//图片高度
msg:"",
url:"",
};
GameData.advertInfo_2 = {
type:0,
imgUrl:"",
imgAdUrl:"",
height:1,
msg:"",
url:"",
};
GameData.advertInfo_0 = {
type:0,
imgUrl:"",
imgAdUrl:"",
height:1,
msg:"",
url:"",
};
GameData.noticePage = 0;
GameData.noticePara = [[0,0],[0,0],[0,0]];
GameData.snrOption = {
profit:0,
roomcode:"",
};
GameData.snrRoomList = {
roomlist:[],
roomtype:[],
tea:1000,
shortcode:888
};
GameData.snrRoomListSlide = false;
GameData.snrOptionLength = 0;
GameData.snrOptionRoomTag = 0;
GameData.snrOptionMode = 0;
GameData.snrOptionRoomtype = "";
GameData.snrOptionDes = "";
GameData.snrOptionProfit=0;
GameData.shortCode = "";
GameData.isShowWorldRoomList = true;
GameData.shareMsg = "";
GameData.rankList = [];
GameData.rankListTag = 0;
GameData.ranListLength = 0;
GameData.rankListSlide = false;
GameData.h5Version = 0;
//
var shareParam = {
appid: "14936872341446",
devkey: "14915485974028"
}
GameData.shareTimeline = {//朋友圈
title: "",
desc: "",
link: "",
imgUrl: ""
}
GameData.shareAppMessage = {//好友
title: "",
desc: "",
link: "",
imgUrl: ""
}
GameData.gameData = "";
GameData.fromH5GameData = {};
GameData.isJoinRoomFromH5 = false;
GameData.h5ShareImage = "";
GameData.screenShotStage = 0;
GameData.screenShotSpidList = [];
GameData.ownerNote = "";
GameData.isDebugger = 0;
GameData.returnUrl = "";
GameData.configData = "";
GameData.h5ShareUrl = "";
GameData.inputPanelData = "";
GameData.inputCallBack = null;
GameData.isShowNeighbor = true;
GameData.nameImgFrame_1 = 1;//房卡图片帧数
GameData.nameImgFrame_2 = 1;//星星图片帧数
GameData.surrendCount = 0;
GameData.htmlCode = "";
GameData.htmlId = "";
GameData.serverIndex = 0;//连接地址下标
GameData.tryReconnectTimes = 0;
GameData.isChangeServer = false;
GameData.shareFrom = 0;//0->框架分享 1->游戏分享
GameData.activityType = 0;//子游戏调用类型
GameData.activityData = null;//子游戏调用类型
GameData.shareTimes = 0;//分享次数
GameData.sharePostUrl = "http://localhost:4477/testurl";//截图分享链接
//保险箱
GameData.wareHouse = {
page:1,//当前页
safeInputType:1,//保险箱存入取出类型
safeInputCount:-1,//保险箱存入取出数量
safePassWord:-1,//保险箱存入取出密码
inputNumber:0,//输入的数字
selectType:1,//选择的当前输入框
initPassWord1:-1,//初始化密码
initPassWord2:-1,//确认初始化密码
pId:0,//赠送id
pImg:"",//赠送头像
pNickName:"",//赠送昵称
pInputType:1,//赠送类型
pInputId:-1,//输入id
pInputCount:-1,//赠送数量
pInputPassWord:-1,//赠送密码
};
GameData.openVideo = 0;//是否开启视频功能
GameData.imgAdIsSlide = false;
GameData.launchImgLoaded = false;//启动页图片是否从网络加载完成
GameData.configSuccess = false;//配置文件读取成功
GameData.launchWaitTime = 0;
GameData.adTimer = null;
GameData.sysNotice = [];//系统公告 {msg:"",read:false,line:1}
GameData.sysCreateLength = 0;
GameData.selectSysNotice = false;//是否勾选发送系统公告
GameData.sysNoticeCost = 0;
GameData.sysNoticeLimit = 0;
GameData.mainMenuAniTimer = null;
GameData.worldRoomPage = 0;
GameData.createMyRoomListLength = 0;
GameData.starCount = 0;
GameData.roomCardCount=0;
GameData.loginPlayerid = false;//登录是否带上playerid
GameData.matchId = null;
GameData.chatLine = null;
GameData.chatInfo = null;
GameData.isService = false;
GameData.submitLocation = false;
GameData.submitPhoneInfo = false;
GameData.submitAddressBook = false;
GameData.getConfigTimer = null;
GameData.clickChat = 0;
GameData.phoneInfo = null;
GameData.addressBook = null;
//GameData.sysConfigName=[];
GameData.sysConfigName=["hideEscape","charmName","hideTaskBtn","miniPro","videoConfig","ofcShortCode","ofcTips","ofcWx","ofcImg",
"managerUrl","rebateConfig","gameNoticeShow","hallNoticeShow","ofcAutoEnter","vipFlushTime","vipWx","h5PayUrl","appShareH5",
"vipRankLimit","behaviorList","deviceLogin","hideWxShare","hideRoomCard","hideSpid","cdKey","otherShare","avatarRange","avatarUrl","whiteListManager","nnLocal"];
GameData.sysConfig={
hideEscape:0,
charmName:"魅力",
hideTaskBtn:0,//是否隐藏任务按钮
miniPro:0,//是否小程序/小游戏 (隐藏更多选项、分享朋友圈、聊天按钮、录音调整)
videoConfig:null,//{type:1,profitMode:1,profit:3}
ofcShortCode:null,//官方短号
ofcImg:"",//官方按钮链接
managerUrl:"",//管理后台地址
rebateConfig:null,//抽成配置
gameNoticeShow:0,//子游戏弹窗时间间隔 0每次都弹 -1不弹
hallNoticeShow:0,//大厅弹窗时间间隔 0每次都弹 -1不弹
ofcAutoEnter:0,//是否自动进入官方短号
vipFlushTime:10000,//vip房间列表刷新间隔
vipWx:"",//成为vip联系微信
h5PayUrl:"",//h5支付地址
appShareH5:"",//app分享的h5地址
vipRankLimit:10,//vip排行显示个数
behaviorList:null,//用户行为
deviceLogin:0,//是否显示设备登录显示了设备登录就会显示手机号登录就会隐藏微信登录
hideWxShare:0,//是否隐藏微信分享
hideRoomCard:1,//隐藏主界面房卡 1 不隐藏 2隐藏
hideSpid:null,//隐藏的精灵id
cdKey:1,//是否显示CDKEY 1不显示 2显示
otherShare:1,//开启新分享 1 关闭2开启
avatarRange:[100,10000],//设备登录的头像列表
avatarUrl:"https://projectimage.tscce.cn/image_132/",
whiteListManager:2, //是否开启在游戏中管理白名单
nnLocal:2,//
};
GameData.inputCallBack2 = null;
GameData.topInfoTimer = null;
GameData.iscloseVideo = true;//是否开战关闭视频
GameData.textCallback = null;
GameData.isLocation = false;
GameData.otherLocation = false;//
GameData.otherLocationTimes = 0;
GameData.otherLocationAllTimes = 1;
GameData.hallLogin = true;
GameData.senRoomState = 0;
GameData.rebateConfig = null;
GameData.vipRoomPage = 0;
GameData.vipRoomCode = 0;
GameData.vipRoomData = [];
GameData.vipRoomTimer1 = false;
GameData.vipRoomIsOpen = false;
GameData.vipRoomShortList = [];
GameData.vipRoomJump = false;
GameData.vipRoomPageOne = {
cLength:0,
slide:false,
}
GameData.vipRoomPageTwo = {
cLength:0,
slide:false,
data:null,
}
GameData.tipTimer1 = null;
GameData.tipTimer2 = null;
GameData.initCard = 0;
GameData.initBean = 0;
GameData.brNumber=0;
GameData.isKick = false;
GameData.blackList = {
data:[],
length:0,
breakRoom:false,
selectTag:0,
slide:false,
inputId:"",
idWidth:12,
}
GameData.allCharm = 0;
GameData.vipRank = {
data:{
rankList:[],
myRank:1,
myScore:100
},
length:0,
//selectTag:0,
slide:false,
//inputId:"",
//idWidth:12,
}
GameData.rankType = false;
GameData.coinMp3Time = 0;
GameData.coinMp3Length = 3000;
GameData.brRebateUnit = 10000;
GameData.phoneCodeTime = 0;
GameData.phoneCode = "";
GameData.phoneNumber = "";
GameData.phoneType = 0;
GameData.isGetPhoneInfo = false;
GameData.isDeviceLogin = false;
GameData.begin0 = false;
GameData.vipConfig = null;
GameData.feedMode = 0;
GameData.fromMiniProData = {};
GameData.shareFriendInfo = {};
GameData.deviceAvatarList = [];
GameData.whiteList = {
data:[],
length:0,
breakRoom:false,
selectTag:0,
slide:false,
inputId:"",
inputCharm:"",
searchId:"",
idWidth:12,
delPid:"",
}
File diff suppressed because it is too large Load Diff
+558
View File
@@ -0,0 +1,558 @@
function Player(seat){
this.openid = "";//id
this.playerid = -1;//playerid
this.nickname = "";//昵称
this.avatar = "";//头像地址
this.sex = 0;//性别
this.ip = "";//ip地址
this.province = "";//微信信息省份
this.city = "";//微信信息城市
this.roomcard = -1;//房卡数量
this.taskstate = 0;//任务状态
this.unionid = 0;//平台唯一表示
this.seat = seat; //座位号
this.score = 0;
this.state = -1;//玩家状态0->默认值 -1->申请解散 1->同意解散 2->拒绝解散
this.status = 0;//0->默认值 1->房主 2->非房主
//this.offline = 0;//玩家是否离线0->否 1->离线
this.canexit=1;//是否可以直接离开
this.onstate=0;//0:在线1:离线2:通话中
this.addr = null;
this.invitecode = "";//邀请码
this.isStart = false;//能否点击按钮开始游戏
this.bean = 0;//玩家豆子数量
this.initialBean = 0;//玩家豆子初始值
this.isprepare = 0;//玩家是否准备
this.advanced = 0;//玩家是否有高级选项
this.paycode = "";//吱口令
this.wareHouseStarCount = 0;//仓库库存星星
this.bankpower = 0;//是否有仓库权限
this.bankpwd = 0;//是否设置仓库密码
this.charm = undefined;
this.sign = "";//签名
this.tel = "";//绑定手机号
}
//玩家信息初始化
if(typeof(Player.prototype.Init) == "undefined"){
Player.prototype.Init = function(bTemp){
this.openid = "";//id
this.playerid = -1;//playerid
this.nickname = "";//昵称
this.score = 0;
this.avatar = "";//头像地址
this.sex = 0;//性别
this.ip = "";//ip地址
this.province = "";//省份
this.city = "";//城市
this.roomcard = 0;//房卡数量
this.taskstate = 0;//任务状态
this.unionid = 0;//平台唯一表示
if(bTemp)this.seat = -1; //座位号
this.state = -1;//玩家状态0->默认值 1->申请解散 2->同意解散 3->拒绝解散
this.status = 0;//0->默认值 1->房主 2->非房主
this.offline = 0;//玩家是否离线0->否 1->离线
this.canexit=1;//是否可以直接离开
this.onstate=0;//0:在线1:离线2:通话中
this.addr = null;
this.invitecode = "";//邀请码
this.isStart = false;//能否点击按钮开始游戏
this.bean = 0;//玩家豆子数量
this.initialBean = 0;//玩家豆子初始值
this.isprepare = 0;//玩家是否准备
this.advanced = 0;//玩家是否有高级选项
this.paycode = "";//吱口令
this.wareHouseStarCount = 0;//仓库库存星星
this.bankpower = 0;//是否有仓库权限
this.bankpwd = 0;//是否设置仓库密码
this.charm = undefined;
this.sign = "";//签名
this.tel = "";//绑定手机号
};
}
//玩家微信信息初始化
if(typeof(Player.prototype.SetWxInfo) == "undefined"){
Player.prototype.SetWxInfo = function(openid,headimgurl,nickname,sex,city,Province,unionid){
this.openid = openid;//id
this.nickname = nickname;//昵称
this.avatar = headimgurl;//头像地址
this.sex = sex;//性别
this.city = city;//城市
this.province = Province;//省
this.unionid=unionid;//开放平台id
//alert("设置玩家信息");
};
}
//玩家游戏信息初始化
if(typeof(Player.prototype.SetMyInfo) == "undefined"){
//Player.prototype.SetMyInfo = function(_playerid,_roomcard,_taskstate,_ip,_score,_bean,_paycode){
Player.prototype.SetMyInfo = function(object){
this.playerid = object.playerid;
this.roomcard = object.roomcard;
this.taskstate = object.taskstate;
this.ip=object.ip;
this.score = object.score;
this.bean = object.bean;
this.initialBean = object.bean;
this.bankpower = object.bankpower;
if(object.bank){
this.wareHouseStarCount = Number(object.bank);//仓库库存星星
}else{
this.wareHouseStarCount = 0;
}
if(object.bankpwd){
this.bankpwd = Number(object.bankpwd);//仓库库存星星
}else{
this.bankpwd = 0;
}
this.charm = object.charm;
this.sign = object.sign;
this.tel = object.tel;
//this.setTel();
//this.bankpwd = object.bank;//是否设置仓库密码 this.bankpwd = 0;//是否设置仓库密码
//this.paycode = _paycode;
};
}
//设置玩家位置
if(typeof(Player.prototype.SetLocationInfo) == "undefined"){
Player.prototype.SetLocationInfo = function(_locationinfo){
this.addr = _locationinfo;
if(this.addr && this.addr.errorCode == 0){
set_self(200,7,C_Player.addr.province+"-"+C_Player.addr.city,0,0);
}else{
set_self(200, 7, "获取定位失败!", 0, 0);
}
};
}
//设置玩家座位
if(typeof(Player.prototype.SetSeat) == "undefined"){
Player.prototype.SetSeat = function(seat){
this.seat = seat;
};
}
//设置玩家邀请码
if(typeof(Player.prototype.setInvitecod) == "undefined"){
Player.prototype.setInvitecod = function(_invitecode){
this.invitecode = _invitecode;
};
}
//修改房卡
if(typeof(Player.prototype.UpdateRoomcard) == "undefined"){
Player.prototype.UpdateRoomcard = function(_msg){
this.roomcard = _msg.data.roomcard;
//set_self(157,7,C_Player.roomcard,0,0);
//set_self(157,18,get_self(287,18,0,0,0)+get_self(287,20,0,0,0)/2-String(C_Player.roomcard).length*7,0,0);
GameUI.setHallRoomCard();
if(_msg.data.text){
GameUI.Openupdataroomcard(_msg.data.text);
}
};
}
//修改豆豆
if(typeof(Player.prototype.update_bean) == "undefined"){
Player.prototype.update_bean = function(_msg){
this.bean = _msg.data.bean;
GameUI.setHallStar();
//set_self(497,7,C_Player.bean,0,0);
//set_self(497,18,get_self(496,18,0,0,0)+get_self(496,20,0,0,0)/2-String(C_Player.bean).length*7,0,0);
set_self(510,7,GameData.starName+":"+C_Player.bean,0,0);
GameUI.openUpdateBean(_msg.data.text);
if(C_Player.seat>-1){//在房间内
var pobj = Desk.GetPlayerBySeat(C_Player.seat);
pobj.bean = C_Player.bean;
pobj = null;
if(C_Player.seat == GameData.infoSeat){
set_self(511,7,GameData.starName+":"+C_Player.bean,0,0);
}
}
};
}
//修改豆豆
if(typeof(Player.prototype.update_bean2) == "undefined"){
Player.prototype.update_bean2 = function(_bean){
this.bean = _bean;
GameUI.setHallStar();
//set_self(497,7,C_Player.bean,0,0);
//set_self(497,18,get_self(496,18,0,0,0)+get_self(496,20,0,0,0)/2-String(C_Player.bean).length*7,0,0);
set_self(510,7,GameData.starName+":"+C_Player.bean,0,0);
if(C_Player.seat>-1){//在房间内
var pobj = Desk.GetPlayerBySeat(C_Player.seat);
pobj.bean = C_Player.bean;
pobj = null;
if(C_Player.seat == GameData.infoSeat){
set_self(511,7,GameData.starName+":"+C_Player.bean,0,0);
}
}
};
}
//修改豆豆
if(typeof(Player.prototype.changeBean) == "undefined"){
Player.prototype.changeBean = function(_bean){
this.bean = this.initialBean + _bean;
GameUI.setHallStar();
//set_self(497,7,C_Player.bean,0,0);
//set_self(497,18,get_self(496,18,0,0,0)+get_self(496,20,0,0,0)/2-String(C_Player.bean).length*7,0,0);
set_self(510,7,GameData.starName+":"+C_Player.bean,0,0);
if(C_Player.seat>-1){//在房间内
var pobj = Desk.GetPlayerBySeat(C_Player.seat);
pobj.bean = C_Player.bean;
pobj = null;
if(C_Player.seat == GameData.infoSeat){
set_self(511,7,GameData.starName+":"+C_Player.bean,0,0);
}
}
};
}
//设置房卡
if(typeof(Player.prototype.setRoomcard) == "undefined"){
Player.prototype.setRoomcard = function(_roomcard){
this.roomcard = _roomcard;
GameUI.setHallRoomCard();
};
}
//设置房卡
if(typeof(Player.prototype.addRoomCard) == "undefined"){
Player.prototype.addRoomCard = function(value,text){
this.roomcard += value;
GameUI.setHallRoomCard();
if(text){
GameUI.Openupdataroomcard(text);
}
};
}
if(typeof(Player.prototype.setBean) == "undefined"){
Player.prototype.setBean = function(value){
this.bean = value;
GameUI.setHallStar();
};
}
if(typeof(Player.prototype.addBean) == "undefined"){
Player.prototype.addBean = function(value,text){
this.bean += value;
GameUI.setHallStar();
if(text){
GameUI.Openupdataroomcard(text);
}
};
}
//设置吱口令
if(typeof(Player.prototype.setPayCode) == "undefined"){
Player.prototype.setPayCode = function(_paycode){
if(_paycode){
this.paycode = _paycode;//吱口令
}else{
this.paycode = "";//吱口令
}
};
}
//绑定邀请码
if(typeof(Player.prototype.binding_invitecode) == "undefined"){
Player.prototype.binding_invitecode = function(_msg){
if(_msg.data.state == 0){
this.invitecode = _msg.data.invitecode;
}
GameUI.OpenTips(_msg.data.error,ConstVal.Tips.time);
};
}
//设置牌桌内玩家数据
if(typeof(Player.prototype.SetDeskInfo) == "undefined"){
Player.prototype.SetDeskInfo = function(_data,bTemp){
this.playerid=_data.playerid;
this.nickname=_data.nickname;
this.avatar=_data.avatar;
this.sex=_data.sex;
this.ip=_data.ip;
this.onstate=_data.onstate;
this.bean = _data.bean;
if(!bTemp){
this.initialBean = _data.bean;//玩家豆子初始值
}else{
this.initialBean = _data.initialBean;//玩家豆子初始值
}
this.isprepare = _data.isprepare;//玩家是否准备
if(_data.paycode){
this.paycode = _data.paycode;
}else{
this.paycode = "";
}
this.charm = _data.charm;
this.sign = _data.sign;
};
}
//获取牌桌内玩家数据
if(typeof(Player.prototype.getDeskInfo) == "undefined"){
Player.prototype.getDeskInfo = function(){
var rValue = {};
rValue.playerid=this.playerid;
rValue.nickname=this.nickname;
rValue.avatar=this.avatar;
rValue.sex=this.sex;
rValue.ip=this.ip;
rValue.onstate=this.onstate;
rValue.bean = this.bean;
rValue.initialBean = this.initialBean;//玩家豆子初始值
rValue.isprepare = this.isprepare;//玩家是否准备
if(this.paycode){
rValue.paycode = _data.paycode;
}else{
rValue.paycode = "";
}
rValue.charm = this.charm;
return rValue;
};
}
//房间解散
if(typeof(Player.prototype.BreakRoom) == "undefined"){
Player.prototype.BreakRoom = function(){
this.seat = -1; //座位号
this.state = -1;//玩家状态0->默认值 1->申请解散 2->同意解散 3->拒绝解散
this.status = 0;//0->默认值 1->房主 2->非房主
this.isStart = false;//能否点击按钮开始游戏
//this.paycode = "";
};
}
//申请解散房间
if(typeof(Player.prototype.ApplyBreakRoom) == "undefined"){
Player.prototype.ApplyBreakRoom = function(){
this.state = 0;//玩家状态0->默认值 1->申请解散 2->同意解散 3->拒绝解散
};
}
//同意解散房间
if(typeof(Player.prototype.AgreeBreakRoom) == "undefined"){
Player.prototype.AgreeBreakRoom = function(){
this.state = 1;//玩家状态0->默认值 1->申请解散 2->同意解散 3->拒绝解散
};
}
//拒绝解散房间
if(typeof(Player.prototype.RefuseBreakRoom) == "undefined"){
Player.prototype.RefuseBreakRoom = function(){
this.state = 2;//玩家状态0->默认值 1->申请解散 2->同意解散 3->拒绝解散
};
}
//更改玩家是否可以直接退出
if(typeof(Player.prototype.ChangeExit) == "undefined"){
Player.prototype.ChangeExit = function(v){
this.canexit = v;
switch(v){
case 1:
if(this.status == 1){
set_self(181,43,4,0,0);
}else{
set_self(181,43,3,0,0);
}
break;
case 0:
GameUI.hideExitBtn();
set_self(181,43,1,0,0);
break;
}
};
}
//更改玩家通话状态
if(typeof(Player.prototype.phonestate) == "undefined"){
Player.prototype.phonestate = function(state){
state=Number(state);
C_Player.onstate=state;
var pobj=Desk.GetPlayerBySeat(C_Player.seat);
if(!pobj){return;}
pobj.onstate=state;
GameUI.SetOnState(C_Player.seat.seat,state);
switch(state){
case 0://挂断
var data={};
data.agentid=GameData.AgentId;
data.gameid=GameData.GameId;
data.playerid=C_Player.playerid;
data.roomcode=Desk.roomcode;
Net.Send_hangup_phone(data);
break;
case 1://接起电话
var data={};
data.agentid=GameData.AgentId;
data.gameid=GameData.GameId;
data.playerid=C_Player.playerid;
data.roomcode=Desk.roomcode;
Net.Send_call_phone(data);
break;
case 2://电话进来
var data={};
data.agentid=GameData.AgentId;
data.gameid=GameData.GameId;
data.playerid=C_Player.playerid;
data.roomcode=Desk.roomcode;
Net.Send_call_phone(data);
break;
case 3://去电
var data={};
data.agentid=GameData.AgentId;
data.gameid=GameData.GameId;
data.playerid=C_Player.playerid;
data.roomcode=Desk.roomcode;
Net.Send_call_phone(data);
break;
}
};
}
//更改玩家任务提醒状态
if(typeof(Player.prototype.ChangeTaskstate) == "undefined"){
Player.prototype.ChangeTaskstate = function(v){
this.taskstate = v;
};
}
//分享回调
if(typeof(Player.prototype.sharesuccess) == "undefined"){
Player.prototype.sharesuccess = function(success,type){
if(success==2 && type == 2){//分享成功
var data={};
data.agentid=GameData.AgentId;
data.playerid=this.playerid;
data.taskid=ConstVal.ShareTaskId;
Net.Send_player_finish_task(data);
}else{
//GameUI.OpenTips("分享失败!",ConstVal.Tips.time);
}
if(success == 2 && GameData.shareFrom == 1){//截图分享成功
try{
GameData.shareTimes++;
if(isArray(GameData.hallConfig.actData)){
if(GameData.hallConfig.actData[1] == 0){//活动关闭
return;
}
switch(GameData.activityType){//
case 1://大赢家
if(GameData.shareTimes == 1){
var sendUrl = GameData.hallConfig.actData[3];
var event = 3;
Logic.sendToWeb2(sendUrl,event);
}
break;
}
}
//if(!isArray(GameData.hallConfig.activity)){//
//return;
//}else{
//if(GameData.hallConfig.activity.length>=5){
//if(GameData.hallConfig.activity[4] == 0){
//return;
//}
//}
//}
//logmessage("大赢家截图分享成功!"+GameData.shareTimes+"、"+GameData.activityType,1);
//switch(GameData.activityType){//
//case 1://大赢家
//if(GameData.shareTimes == 1){
//var sendUrl = "http://discount.0791ts.cn/api/add_game_times";
//var actId = 1;
//if(GameData.hallConfig.activity.length > 5){
//sendUrl = GameData.hallConfig.activity[6];
//actId = GameData.hallConfig.activity[7];
//}
//Logic.sendToWeb(sendUrl,actId);
//}
//break;
//}
}catch(e){
}
}
GameData.activityType = 0;
GameData.activityData = null;
};
}
//有
if(typeof(Player.prototype.player_finish_task) == "undefined"){
Player.prototype.player_finish_task = function(_msg){
if(_msg.data.state==1 && C_Player.taskstate==0){
C_Player.taskstate=1;
}
};
}
//领取任务奖励
if(typeof(Player.prototype.get_task_award) == "undefined"){
Player.prototype.get_task_award = function(_msg){
for(var i=0;i<TaskInfo.length;i++){
if(TaskInfo[i].taskid == _msg.data.taskid){
TaskInfo[i].state = 2;
}
}
this.taskstate=_msg.data.taskstate;
GameUI.CloseTask();
GameUI.OpenTask();
};
}
//设置是否已初始化密码
if(typeof(Player.prototype.setBankPwd) == "undefined"){
Player.prototype.setBankPwd = function(value){
this.bankpwd = value;
};
}
//设置库存星星数量
if(typeof(Player.prototype.setWareHouseStarCOunt) == "undefined"){
Player.prototype.setWareHouseStarCOunt = function(count){
if(count){
this.wareHouseStarCount = Number(count);
}else{
this.wareHouseStarCount = 0;
}
GameUI.setNumberImage(3033,C_Player.wareHouseStarCount,27);//仓库库存数量
};
}
//设置魅力值
if(typeof(Player.prototype.setCharm) == "undefined"){
Player.prototype.setCharm = function(count){
this.charm = count;
if(this.seat>-1){//在房间内
if(this.seat == GameData.infoSeat){
set_self(3125,7,GameData.sysConfig.charmName+":"+count,0,0);
}
}
};
}
//设置魅力值
if(typeof(Player.prototype.setSign) == "undefined"){
Player.prototype.setSign = function(sign){
this.sign = sign;
GameUI.setSign(sign);
GameUI.closeTextInput();
};
}
if(typeof(Player.prototype.updateHallData) == "undefined"){
Player.prototype.updateHallData = function(data){
this.setSign(data.sign);
this.setRoomcard(data.roomcard);
this.update_bean2(data.bean);
};
}
if(typeof(Player.prototype.returnHallData) == "undefined"){
Player.prototype.returnHallData = function(data){
var obj={};
obj.sign = this.sign;
obj.roomcard = this.roomcard;
obj.bean = this.bean;
return obj;
};
}
//设置手机号
if(typeof(Player.prototype.setTel) == "undefined"){
Player.prototype.setTel = function(val){
this.tel = val;
// set_self(3256,43,2,0,0);
// play_ani(0,3256,35,255,100,0,1000,0,0,0,0,0,1);
};
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+897
View File
@@ -0,0 +1,897 @@
var Net = Net||{};
Net.tcp = {};
Net.tcp.isConnected = false;//已有连接连上
Net.tcp.tcpIDAva = 1;//用过的ID的最大值
Net.tcp.tcpIDList = [];//TCP ID列表
Net.tcp.connectCount = 3;//同时最多连接个数
Net.tcp.connectList = [];//tcp连接对象池
Net.tcp.connect = function(_id,ws){
}
Net.ws_tcp;
Net._SendData = function(_app,_route,_rpc,_data){
var _msg = {};
_msg.app = _app,
_msg.route = _route;
_msg.rpc = _rpc;
_msg.data = _data;
if(ConstVal.netType == 0){
Net.ws_tcp.send(JSON.stringify(_msg));
}else{
var putMsg='';
if(_msg.rpc == RpcList.player_login){putMsg="playerLogin"}
Func.AjaxHttp2(GameData.Server,_msg,
function(_msg,state,input_msg){
console.log(_msg,state,input_msg);
if (typeof(_msg) == 'string')
{
_msg = JSON.parse(_msg);
}
if(_msg.route==RouteList.platform||_msg.route==RouteList.agent||_msg.route==RouteList.room){
if(min_ExitsFunction(Net[_msg.rpc])){
Net[_msg.rpc](_msg);
}
}else{
Game_Modify._ReceiveData(_msg);
}
},function(_msg,state,input_msg){
if(input_msg == "playerLogin"){
GameUI.OpenTips("网络状况不好");
}
},putMsg);
}
if(Game_Config.Debugger.isDebugger){
var dt = new Date();
console.log(`[${dt.toLocaleTimeString()}.${String(dt.getMilliseconds()).padStart(3,'0')}] 发送:`);
console.log(_msg);
}
}
Net.submit_error = function(_packet,_msg){
//提交错误
if(GameData.errorMsg != _msg){
GameData.errorMsg = _msg;
}else {
return;
}
var msg = {
app:"youle",
route:"agent",
rpc:"submit_error",
data:{
packet:_packet,
msg:_msg,
playerid:C_Player.playerid,
agentid:GameData.AgentId,
gameid:GameData.GameId,
}
}
try{
Net.ws_tcp.send(JSON.stringify(msg));
}catch(e){
}
}
Net.submit_log = function(_packet,_msg){
var msg = {
app:"youle",
route:"agent",
rpc:"submit_error",
data:{
packet:_packet,
msg:_msg,
playerid:C_Player.playerid,
agentid:GameData.AgentId,
gameid:GameData.GameId
}
}
console.log(_packet);
console.log(_msg);
try{
Net.ws_tcp.send(JSON.stringify(msg));
}catch(e){
}
}
//发送创建房间
Net.Send_create_room = function(_data){
GameUI.StartLoad();
_data.ip = C_Player.ip;
_data.location = C_Player.addr;
this._SendData(AppList.app,RouteList.agent,RpcList.create_room,_data);
}
//接收创建房间
Net.create_room = function(_msg){
//Logic.CreateRoom(_msg.data);
Desk.create_room(_msg);
GameUI.EndLoad();
Game_Modify.createRoom(_msg.data.roomtype,_msg.data.infinite);
if(Game_Modify.onCreateRoom){
Game_Modify.onCreateRoom(_msg.data);
}
}
//发送登录请求
Net.Send_login = function(_data){
//console.log(returnCitySN);
if(typeof returnCitySN != "undefined"){
_data.ip = returnCitySN.ip;
_data.province = returnCitySN.province;
_data.city = returnCitySN.city;
C_Player.province = returnCitySN.province;
C_Player.city = returnCitySN.city;
}
_data.location = C_Player.addr;
if(ConstVal.isGameHall){
if(!GameData.hallLogin){
return ;
}
}
_data.machineid = Logic.getMachineId();
_data.machineroom = Utl.getRoomcode();
//alert("进入Net.Send_login方法");
GameUI.StartLoad();
if(GameData.loginPlayerid){
var _playerid = Logic.readPlayerId();
if(_playerid){
_data.playerid = _playerid;
}
}
GameData.loginList.push(GameData.TcpID);
//GameData.isChangeServer = false;
//if(ConstVal.netType == 0){
GameData.isSendLoginState = true;
GameData.sendLoginTimes++;
if(!ConstVal.isGameHall){
if(GameData.sendLoginTimer == null){
GameData.isSendLoginTimer = setTimeout(function(){
GameData.ConnectType = false;
GameData.disType = true;
GameData.NetType=1;
GameUI.StartLoad();
GameData.isClose = true;
Net.ws_tcp.close();
GameData.isCloseTimer = setInterval(function(){
if(GameData.isClose){
Net.ws_tcp.close();
}
},5000);
},4000);
}
}else{
/*
if(GameData.sendLoginTimes >3){
if(GameData.isSendLoginTimer){
window.clearTimeout(GameData.isSendLoginTimer);
GameData.isSendLoginTimer = null;
}
GameUI.EndLoad();
GameUI.OpenKick("网络繁忙,请退出游戏,稍后登录!");
return;
}
if(GameData.sendLoginTimer == null){
GameData.isSendLoginTimer = setTimeout(function(){
var data={};
data.agentid = GameData.AgentId;
data.openid = C_Player.openid;
data.gameid = GameData.GameId;
data.nickname = C_Player.nickname;
data.avatar = C_Player.avatar;
data.sex = C_Player.sex;
data.province = C_Player.province;
data.unionid=C_Player.unionid;
data.city = C_Player.city;
data.version = GameData.versionCode;
data.channelid = GameData.ChannelId;
data.marketid = GameData.marketID;
},4000);
}
*/
}
//if(GameData.sendLoginTimes>=2){
//if(GameData.isSendLoginTimer){
//window.clearTimeout(GameData.isSendLoginTimer);
//GameData.isSendLoginTimer = null;
//}
//if(GameData.isClose){
//GameData.isClose = false;
//window.clearTimeout(GameData.isCloseTimer);
//}
//if(GameData.sendLoginTimer){
//window.clearTimeout(GameData.sendLoginTimer);
//GameData.sendLoginTimer = null;
//}
//if(GameData.sendLoginTimer == null){
//GameData.isSendLoginTimer = setTimeout(function(){
//GameData.ConnectType = false;
//GameData.disType = true;
//GameData.NetType=1;
//GameUI.StartLoad();
//GameData.isClose = true;
//Net.ws_tcp.close();
//GameData.isCloseTimer = setInterval(function(){
//if(GameData.isClose){
//Net.ws_tcp.close();
//}
//},5000);
//},4000);
//}
////GameUI.EndLoad();
////GameUI.OpenKick("网络繁忙,请退出游戏,稍后登录!");
//}
Net._SendData(AppList.app,RouteList.agent,RpcList.player_login,_data);
}
//接收登录
Net.player_login = function(_msg){
if(GameData.isSendLoginTimer){
window.clearTimeout(GameData.isSendLoginTimer);
GameData.isSendLoginTimer = null;
}
GameUI.EndLoad();
Desk.login(_msg);
}
//发送玩家自己进入房间请求
Net.Send_self_join_room = function(_data){
GameUI.StartLoad();
GameData.shortCode = _data.roomcode;
_data.location = C_Player.addr;
_data.ip = C_Player.ip;
this._SendData(AppList.app,RouteList.agent,RpcList.self_join_room,_data);
}
//接收玩家自己进入房间请求
Net.self_join_room = function(_msg){
Desk.self_join_room(_msg);
GameUI.EndLoad();
}
//发送未开局之前房主自己解散房间
Net.Send_self_break_room = function(_data){
GameUI.StartLoad();
this._SendData(AppList.app,RouteList.room,RpcList.self_break_room,_data);
}
//接收未开局之前房主自己解散房间
Net.self_break_room = function(_msg){
Desk.self_break_room(_msg);
GameUI.EndLoad();
}
//接收未开局之前他人房主解散房间
Net.other_break_room = function(_msg){
Desk.other_break_room(_msg);
}
//接收其他玩家加入房间
Net.other_join_room = function(_msg){
if(Logic.checkRoom()){
return;
}
Desk.other_join_room(_msg);
GameUI.EndLoad();
}
//发送未开局之前自己退出房间
Net.Send_self_exit_room = function(_data){
GameUI.StartLoad();
this._SendData(AppList.app,RouteList.room,RpcList.self_exit_room,_data);
}
//接收未开局之前自己退出房间
Net.self_exit_room = function(_msg){
GameUI.closeCheck();
Desk.self_exit_room(_msg);
GameUI.EndLoad();
}
//接收未开局之前其他玩家退出房间
Net.other_exit_room = function(_msg){
if(Logic.checkRoom()){
return;
}
Desk.other_exit_room(_msg);
}
//发送开局之后自己申请解散房间
Net.Send_self_apply_free_room = function(_data){
//GameUI.StartLoad();
this._SendData(AppList.app,RouteList.room,RpcList.self_apply_free_room,_data);
}
//接收开局之后自己申请解散房间
Net.self_apply_free_room = function(_msg){
Desk.self_apply_free_room(_msg);
GameUI.EndLoad();
}
//接收开局之后其他玩家申请解散房间
Net.other_apply_free_room = function(_msg){
Desk.other_apply_free_room(_msg);
}
//发送自己同意解散房间
Net.Send_self_agree_free_room = function(_data){
GameUI.StartLoad();
this._SendData(AppList.app,RouteList.room,RpcList.self_agree_free_room,_data);
}
//接收自己同意解散房间
Net.self_agree_free_room = function(_msg){
Desk.self_agree_free_room(_msg);
GameUI.EndLoad();
}
//接收其他玩家同意解散房间
Net.other_agree_free_room = function(_msg){
Desk.other_agree_free_room(_msg);
GameUI.EndLoad();
}
//发送自己拒绝解散房间
Net.Send_self_refuse_free_room = function(_data){
GameUI.StartLoad();
this._SendData(AppList.app,RouteList.room,RpcList.self_refuse_free_room,_data);
}
//接收自己拒绝解散房间
Net.self_refuse_free_room = function(_msg){
Desk.self_refuse_free_room(_msg);
GameUI.EndLoad();
}
//接收其他玩家拒绝解散房间
Net.other_refuse_free_room = function(_msg){
Desk.other_refuse_free_room(_msg);
GameUI.EndLoad();
}
//接收解散房间
Net.free_room = function(_msg){
Desk.free_room(_msg);
GameUI.EndLoad();
}
//发送获取战绩
Net.Send_get_player_grade1 = function(_data){
GameUI.StartLoad();
this._SendData(AppList.app,RouteList.agent,RpcList.get_player_grade1,_data);
}
//接收获取战绩
Net.get_player_grade1 = function(_msg){
gameCombat.get_player_grade1(_msg);
GameUI.EndLoad();
}
//发送获取战绩2
Net.Send_get_player_grade2 = function(_data){
GameUI.StartLoad();
this._SendData(AppList.app,RouteList.agent,RpcList.get_player_grade2,_data);
}
//接收获取战绩2
Net.get_player_grade2 = function(_msg){
gameCombat.get_player_grade2(_msg);
GameUI.EndLoad();
}
//接收修改房卡
Net.update_roomcard = function(_msg){
if(typeof _msg.data.change == "undefined"){
C_Player.UpdateRoomcard(_msg);
}
}
//接收其他玩家离线
Net.other_offline = function(_msg){
Desk.other_offline(_msg);
}
//接收其他玩家上线
Net.other_online = function(_msg){
Desk.other_online(_msg);
}
//发送发送聊天消息
Net.Send_send_text = function(_data){
this._SendData(AppList.app,RouteList.room,RpcList.send_text,_data);
}
//接收发送聊天消息
Net.send_text = function(_msg){
Desk.send_text(_msg);
}
//发送获取任务列表
Net.Send_get_player_task = function(_data){
GameUI.StartLoad();
this._SendData(AppList.app,RouteList.agent,RpcList.get_player_task,_data);
}
//接收获取任务列表
Net.get_player_task = function(_msg){
Desk.get_player_task(_msg);
//GameUI.SetTaskInfo(_msg);
GameUI.EndLoad();
}
//发送完成任务
Net.Send_player_finish_task = function(_data){
this._SendData(AppList.app,RouteList.agent,RpcList.player_finish_task,_data);
}
//接收完成任务
Net.player_finish_task = function(_msg){
C_Player.player_finish_task(_msg);
}
//发送领取任务奖励
Net.Send_get_task_award = function(_data){
GameUI.StartLoad();
this._SendData(AppList.app,RouteList.agent,RpcList.get_task_award,_data);
}
//接收领取任务奖励
Net.get_task_award = function(_msg){
C_Player.get_task_award(_msg);
GameUI.EndLoad();
}
//发送有任务可领取
Net.Send_can_award = function(_data){
this._SendData(AppList.app,RouteList.agent,RpcList.refresh_task_state,_data);
}
//接收有任务可领取
Net.can_award = function(_msg){
C_Player.can_award(_msg);
}
//接收踢下线
Net.kick_offline = function(_msg){
Desk.kick_offline(_msg);
}
//发送自己打电话
Net.Send_call_phone = function(_data){
this._SendData(AppList.app,RouteList.room,RpcList.call_phone,_data);
}
//接收自己打电话
Net.call_phone = function(_msg){
Desk.call_phone(_msg);
}
//发送自己挂断电话
Net.Send_hangup_phone = function(_data){
this._SendData(AppList.app,RouteList.room,RpcList.hangup_phone,_data);
}
//接自己收挂断打电话
Net.hangup_phone = function(_msg){
Desk.hangup_phone(_msg);
}
//发送房主开局
Net.Send_self_makewar = function(_data){
//GameUI.StartLoad();
this._SendData(AppList.app,RouteList.room,RpcList.self_makewar,_data);
}
//接收房主开局
Net.self_makewar = function(_msg){
Desk.self_makewar(_msg);
//GameUI.EndLoad();
}
//接收房主开局
Net.other_makewar = function(_msg){
Desk.makewar(_msg);
}
//自己发送互动
Net.Send_send_gift=function(_data){
this._SendData(AppList.app,RouteList.room,RpcList.send_gift,_data);
}
Net.send_gift=function(_msg){
Desk.send_gift(_msg);
}
//自己发送语音
Net.Send_send_voice=function(_data){
this._SendData(AppList.app,RouteList.room,RpcList.send_voice,_data);
}
Net.send_voice=function(_msg){
Desk.send_voice(_msg);
}
//发送切换至房间服务器
Net.Send_connect_roomserver=function(_data){
this._SendData(AppList.app,RouteList.room,RpcList.connect_roomserver,_data);
}
Net.connect_roomserver=function(_msg){
GameData.ConnectType=true;
GameData.disType =false;
GameData.ConnectPack = _msg.data;
GameData.ConnectRpc = RpcList.connect_roomserver;
GameData.Server=_msg.data.roomserver;
//Logic.Connect();
Net.ws_tcp.close();
}
//发送切换至大厅服务器
Net.Send_connect_agentserver=function(_data){
this._SendData(AppList.app,RouteList.agent,RpcList.connect_agentserver,_data);
}
Net.connect_agentserver=function(_msg){
if(_msg.data.opt == RpcList.other_break_room || _msg.data.opt == RpcList.free_room){
GameUI.StartLoad();
}
GameData.ConnectType=true;
GameData.disType =false;
GameData.ConnectRpc = RpcList.connect_agentserver;
GameData.ConnectPack = _msg.data;
GameData.Server=_msg.data.agentserver;
//Logic.Connect();
Net.ws_tcp.close();
}
Net.Send_broadcast=function(_data){
this._SendData(AppList.app,RouteList.agent,RpcList.broadcast,_data);
}
//接收即时信息
Net.broadcast=function(_msg){
//
Desk.broadcast(_msg);
}
//发送互动
Net.Send_send_phiz=function(_data){
this._SendData(AppList.app,RouteList.room,RpcList.send_phiz,_data);
}
//接收互动
Net.send_phiz=function(_msg){
Desk.send_phiz(_msg);
}
//提交反馈
Net.Send_submit_opinion=function(_data){
this._SendData(AppList.app,RouteList.agent,RpcList.submit_opinion,_data);
}
//接收反馈
Net.submit_opinion=function(_msg){
Desk.submit_opinion(_msg);
}
//踢出玩家
Net.Send_kick_server=function(_data){
this._SendData(AppList.app,RouteList.agent,RpcList.kick_server,_data);
}
Net.kick_server=function(_msg){
Desk.kick_server(_msg);
}
//获取支付列表
Net.Send_get_paylist=function(_data){
GameUI.StartLoad();
this._SendData(AppList.app,RouteList.agent,RpcList.get_paylist,_data);
}
//接收支付列表
Net.get_paylist=function(_msg){
GameData.payList = _msg.data.paylist;
GameUI.OpenPay();
GameUI.EndLoad();
}
//支付成功后通知服务器
Net.Send_pay_succ=function(_data){
this._SendData(AppList.app,RouteList.agent,RpcList.pay_succ,_data);
}
//提交位置信息
Net.Send_submit_location=function(_data){
if(!ConstVal.isGameHall){
this._SendData(AppList.app,RouteList.agent,RpcList.submit_location,_data);
}
}
//提交位置信息
Net.submit_location=function(_msg){
Game.submit_location(_msg);
}
//发送绑定
Net.Send_binding_invitecode=function(_data){
//GameUI.StartLoad();
this._SendData(AppList.app,RouteList.agent,RpcList.binding_invitecode,_data);
}
//接收绑定
Net.binding_invitecode=function(_msg){
//GameUI.EndLoad();
C_Player.binding_invitecode(_msg);
}
//接收修改豆豆
Net.update_bean = function(_msg){
//C_Player.update_bean(_msg);
Desk.update_bean(_msg);
}
Net.Send_get_player_invitecode = function(_data){
GameUI.StartLoad();
this._SendData(AppList.app,RouteList.agent,RpcList.get_player_invitecode,_data);
}
Net.get_player_invitecode = function(_msg){
GameUI.EndLoad();
C_Player.setInvitecod(_msg.data.invitecode);
GameUI.OpenBind();
}
Net.Send_beanroom_surrender = function(_data){
GameUI.StartLoad();
this._SendData(AppList.app,RouteList.room,RpcList.beanroom_surrender,_data);
}
Net.beanroom_surrender = function(_msg){
GameUI.EndLoad();
GameUI.closeCheck();
if(_msg.data.state==0){
Game_Modify.onSurrender(_msg);
}else{//失败
if(_msg.data.showerror==1){
GameUI.OpenTips(_msg.data.error);
}
}
}
Net.Send_player_prepare = function(_data){
this._SendData(AppList.app,RouteList.room,RpcList.player_prepare,_data);
}
Net.player_prepare = function(_msg){
Desk.player_prepare(_msg);
}
Net.Send_share_room = function(_data){
this._SendData(AppList.app,RouteList.room,RpcList.share_room,_data);
}
Net.share_room = function(_msg){
Desk.share_room(_msg);
}
Net.Send_get_share_room = function(_data){
if(!ConstVal.isGameHall){
GameUI.StartLoad();
this._SendData(AppList.app,RouteList.agent,RpcList.get_share_room,_data);
}
}
Net.get_share_room = function(_msg){
Desk.get_share_room(_msg);
GameUI.EndLoad();
}
Net.Send_quick_enter_share_room = function(_data){
console.log(_data);
this._SendData(AppList.app,RouteList.agent,RpcList.quick_enter_share_room,_data);
}
Net.show_message = function(_msg){
GameUI.EndLoad();
Desk.show_message(_msg);
}
Net.Send_advanced_roomlist = function(_data){
GameUI.StartLoad();
this._SendData(AppList.app,RouteList.agent,RpcList.advanced_roomlist,_data);
}
Net.advanced_roomlist = function(_msg){
GameUI.EndLoad();
Desk.advanced_roomlist(_msg);
}
Net.Send_advanced_createroom = function(_data){
GameUI.StartLoad();
this._SendData(AppList.app,RouteList.agent,RpcList.advanced_createroom,_data);
}
Net.advanced_createroom = function(_msg){
GameUI.EndLoad();
Desk.advanced_createroom(_msg);
}
Net.Send_change_room = function(_data){
//GameUI.StartLoad();
this._SendData(AppList.app,RouteList.room,RpcList.change_room,_data);
}
Net.change_seat = function(_msg){
Desk.change_seat(_msg);
}
//财富榜
Net.Send_get_treasurelist = function(_data){
GameUI.StartLoad();
this._SendData(AppList.app,RouteList.agent,RpcList.get_treasurelist,_data);
}
//财富榜
Net.get_treasurelist = function(_msg){
Desk.get_treasurelist(_msg);
GameUI.EndLoad();
}
//设置仓库密码
Net.Send_set_bankpwd = function(_data){
GameUI.StartLoad();
this._SendData(AppList.app,RouteList.agent,RpcList.set_bankpwd,_data);
}
Net.set_bankpwd = function(_msg){
GameUI.EndLoad();
Desk.set_bankpwd(_msg);
}
//更改仓库星星
Net.Send_change_star = function(_data){
GameUI.StartLoad();
this._SendData(AppList.app,RouteList.agent,RpcList.change_star,_data);
}
Net.change_star = function(_msg){
GameUI.EndLoad();
Desk.change_star(_msg);
}
//提交手机信息
Net.Send_submit_phoneinfo = function(_data){
if(!ConstVal.isGameHall){
this._SendData(AppList.app,RouteList.agent,RpcList.submit_phoneinfo,_data);
}
}
Net.submit_phoneinfo = function(_msg){
//GameData.submitPhoneInfo = true;
}
//提交手机信息
Net.Send_update_charm = function(_data){
this._SendData(AppList.app,RouteList.agent,RpcList.update_charm,_data);
}
Net.update_charm = function(_msg){
//GameData.submitPhoneInfo = true;
Desk.update_charm(_msg);
}
Net.Send_setSign = function(_data){
this._SendData(AppList.app,RouteList.agent,RpcList.setSign,_data);
}
Net.setSign = function(_msg){
C_Player.setSign(_msg.data.sign);
//GameUI.setSign(_msg.data.sign);
}
Net.Send_switchRoomList = function(_data){
GameUI.StartLoad();
this._SendData(AppList.app,RouteList.agent,RpcList.switchRoomList,_data);
}
Net.switchRoomList = function(_msg){
GameUI.EndLoad();
Desk.switchRoomList(_msg);
//GameUI.setSign(_msg.data.sign);
}
Net.Send_getInfoByShortCode = function(_data){
//GameUI.StartLoad();
this._SendData(AppList.app,RouteList.agent,RpcList.getInfoByShortCode,_data);
}
Net.getInfoByShortCode = function(_msg){
GameUI.EndLoad();
Desk.getInfoByShortCode(_msg);
}
Net.Send_optBanList = function(_data){
GameUI.StartLoad();
this._SendData(AppList.app,RouteList.agent,RpcList.optBanList,_data);
}
Net.optBanList = function(_msg){
GameUI.EndLoad();
Desk.optBanList(_msg);
}
Net.Send_getShortCodeRankList = function(_data){
GameUI.StartLoad();
this._SendData(AppList.app,RouteList.agent,RpcList.getShortCodeRankList,_data);
}
Net.getShortCodeRankList = function(_msg){
GameUI.EndLoad();
Desk.getShortCodeRankList(_msg);
}
Net.Send_setAllCharm = function(_data){
GameUI.StartLoad();
this._SendData(AppList.app,RouteList.agent,RpcList.setAllCharm,_data);
}
Net.setAllCharm = function(_msg){
GameUI.EndLoad();
Desk.setAllCharm(_msg);
}
Net.Send_getVipRankList = function(_data){
GameUI.StartLoad();
this._SendData(AppList.app,RouteList.agent,RpcList.getVipRankList,_data);
}
Net.getVipRankList = function(_msg){
GameUI.EndLoad();
Desk.getVipRankList(_msg);
}
Net.Send_playerBehavior = function(tag){
//GameUI.StartLoad();
/*
var _data={};
_data.agentid = GameData.AgentId;
_data.gameid = GameData.GameId;
_data.playerid = C_Player.playerid;
_data.tag = tag;
this._SendData(AppList.app,RouteList.agent,RpcList.playerBehavior,_data);
*/
var url = "http://test3.1888day.com/api/gamedo/gamedo";
var cfg = {};
cfg.url = url;
cfg.type = "GET";
cfg.data = "agentid="+GameData.AgentId+"&gameid="+GameData.GameId+"&playerid="+C_Player.playerid+"&tag="+tag;
cfg.success = playerBehavior_Succ;
cfg.error = playerBehavior_Fail;
min_http(cfg);
}
var playerBehavior_Succ = function(_msg,state,input_msg){
console.log("Succ");
console.log(_msg,state,input_msg);
}
var playerBehavior_Fail = function(_msg,state,input_msg){
console.log("Fail");
console.log(_msg,state,input_msg);
}
Net.playerBehavior = function(_msg){
//GameUI.EndLoad();
Desk.playerBehavior(_msg);
}
Net.Send_binding_phone = function(_data){
GameUI.StartLoad();
this._SendData(AppList.app,RouteList.agent,RpcList.binding_phone,_data);
}
Net.binding_phone = function(_msg){
GameUI.EndLoad();
Desk.binding_phone(_msg);
}
Net.Send_send_phone_checkcode = function(_data){
//GameUI.StartLoad();
this._SendData(AppList.app,RouteList.agent,RpcList.send_phone_checkcode,_data);
}
Net.send_phone_checkcode = function(_msg){
//GameUI.EndLoad();
Desk.send_phone_checkcode(_msg);
}
Net.Send_send_phone_code_wechat = function(_data){
//GameUI.StartLoad();
this._SendData(AppList.app,RouteList.agent,RpcList.send_phone_code_wechat,_data);
}
Net.send_phone_code_wechat = function(_msg){
//GameUI.EndLoad();
Desk.send_phone_code_wechat(_msg);
}
Net.Send_setVipForbidSelect = function(_data){
GameUI.StartLoad();
this._SendData(AppList.app,RouteList.agent,RpcList.setVipForbidSelect,_data);
}
Net.setVipForbidSelect = function(_msg){
GameUI.EndLoad();
Desk.setVipForbidSelect(_msg);
}
Net.Send_topup_card = function(_data){
console.log(_data);
this._SendData(AppList.app,RouteList.agent,RpcList.topup_card,_data);
}
Net.topup_card = function(_msg){
Desk.topup_card(_msg);
}
Net.Send_query_player2 = function(_data){
this._SendData(AppList.app,RouteList.agent,RpcList.query_player2,_data);
}
Net.query_player2 = function(_msg){
Desk.query_player2(_msg);
}
Net.Send_giveCoin = function(_data){
this._SendData(AppList.app,RouteList.agent,RpcList.giveCoin,_data);
}
Net.giveCoin = function(_msg){
Desk.giveCoin(_msg);
}
Net.Send_getPlayerWhiteList = function(_data){
GameUI.StartLoad();
this._SendData(AppList.app,RouteList.agent,RpcList.getPlayerWhiteList,_data);
}
Net.getPlayerWhiteList = function(_msg){
GameUI.EndLoad();
Desk.getPlayerWhiteList(_msg);
}
Net.Send_optWhiteList = function(_data){
GameUI.StartLoad();
this._SendData(AppList.app,RouteList.agent,RpcList.optWhiteList,_data);
}
Net.optWhiteList = function(_msg){
GameUI.EndLoad();
Desk.optWhiteList(_msg);
}
+610
View File
@@ -0,0 +1,610 @@
var Game=Game||{};
Game.submit_location = function(_msg){
GameData.submitLocation = true;
if(_msg.data.wechat_gzh){
GameData.wechat_gzh = _msg.data.wechat_gzh;
set_self(340,41,0,0,0);
set_self(340,7,GameData.wechat_gzh,0,0);
set_self(339,20,GameData.wechat_gzh.gblen()*13,0,0);
set_self(340,18,get_self(16,18,0,0,0)+get_self(16,20,0,0,0)/2-GameData.wechat_gzh.gblen()*6.5,0,0);
set_self(339,18,get_self(16,18,0,0,0)+get_self(16,20,0,0,0)/2-GameData.wechat_gzh.gblen()*6.5,0,0);
}
if(_msg.data.wechat_ewm){
GameData.wechat_ewm=_msg.data.wechat_ewm;
}
if(_msg.data.wechat_kfh){
GameData.wechat_kfh=_msg.data.wechat_kfh;
set_self(179,7,GameData.wechat_kfh,0,0);
}
if(_msg.data.qq){
GameData.qq=_msg.data.qq;
set_self(682,7,GameData.qq,0,0);
}
if(_msg.data.tel){
GameData.tel=_msg.data.tel;
set_self(GameData.telSpid,20,String(GameData.tel).gblen()*14,0,0);
set_self(GameData.telSpid,7,GameData.tel,0,0);
}
if(_msg.data.invitecode){
C_Player.setInvitecod(_msg.data.invitecode);
GameUI.CloseBind();
}
}
//游戏其他信息
//Game.mainSceneInfo = {};
var recorderManager;
var innerAudioContext1;
/**
* ?????????? mdata ??????
* openId,avatarUrl,nickName, gender, city, province, unionId
*/
function getwxuserinfo(mdata) {
sharelogin(mdata.openId,mdata.avatarUrl,mdata.nickName,mdata.gender,mdata.city,mdata.province,mdata.unionId)
}
/**
* ????????
*/
function wxgameshow(mdata) {
appservice(1);
}
/**
* ????????
*/
function wxgamehide(mdata) {
appservice(2);
}
/**
* ?????? state ???? 1 ?? 2 ??
*/
function sharegameinfo(state) {
//if (state == 1) {
//}
//if (state == 2) {
//}
state = Number(state);
//type = Number(type);
C_Player.sharesuccess(state,1);
}
//Recorderload();
/**
* ???????
*/
function Recorderload(){
if(!innerAudioContext1){
innerAudioContext1 = wx.createInnerAudioContext();
}
var that = this;
recorderManager=wx.getRecorderManager()
recorderManager.onError(function(){
console.log("????!");
});
recorderManager.onStop(function(res){
console.log(res.tempFilePath );
console.log("????!");
//playRecord(res.tempFilePath);
getaudiourl(res.tempFilePath,5);
});
//innerAudioContext1.onError((res) => {
//console.log("??????!");
//})
//innerAudioContext1.onEnded((res) => {
//console.log("??????!");
//})
innerAudioContext1.onError(function(res){
console.log("??????!");
})
innerAudioContext1.onEnded(function(res){
console.log("??????!");
})
}
/**
* ??????????
*/
function isxiaowxgame(){
var iswxgame;
try{
var recorderManager = wx.getRecorderManager();
iswxgame=true;
}catch(e){
iswxgame=false;
}
return iswxgame;
}
function wxauthorize(){
}
/**
* ????
*/
function startRecordAac (){
console.log("开始录音");
recorderManager.start({
format: 'aac'
});
// wx.authorize({
// scope: 'scope.record',
// fail: function (res) {
// // iOS ? Android ????????? errMsg ????,?????????
// console.log("????");
// console.log(res);
// if (res.errMsg.indexOf('auth deny') > -1 || res.errMsg.indexOf('fail') > -1 || res.errMsg.indexOf('auth denied') > -1 ) {
// // ???????????
// console.log("????");
// wx.showModal({
// title: '??',
// content: '??????????????????->??(?????)->?????->??',
// showCancel:false,
// success: function (res) {
// if (res.confirm) {
// console.log('??????')
// } else if (res.cancel) {
// console.log('??????')
// }
// }
// })
// }
// },
// success:function (res) {
// console.log("????");
// recorderManager.start({
// format: 'aac'
// });
// }
// })
}
/**
* ????
*/
function stopRecord (){
console.log("结束录音");
// wx.authorize({
// scope: 'scope.record',
// fail: function (res) {
// console.log("fail????");
// },
// success:function (res) {
// console.log("stop????");
// }
// })
recorderManager.stop()
}
/***
* ????
*/
function playRecord (src) {
if(!innerAudioContext1){
innerAudioContext1 = wx.createInnerAudioContext();
}
//var that = this;
// var src = this.data.src;
if (src == '') {
console.log("????!");
return;
}
innerAudioContext1.src = src;
innerAudioContext1.play()
}
var Wxloginbutton;
/**
* ?????
*/
function initwxgame() {
gameshow();
gamehide();
}
//initwxgame();
/**
* ???????
*/
//function gameshow() {
//wx.onShow(data => {
//console.log("******" + data)
//console.log(data)
//wxgameshow(data);
//});
//}
function gameshow() {
wx.onShow(function(data){
console.log("******" + data)
console.log(data)
wxgameshow(data);
});
}
/**
* ???????
*/
//function gamehide() {
//wx.onHide(data => {
//wxgamehide(data)
//});
//}
function gamehide() {
wx.onHide(function(data){
wxgamehide(data)
});
}
/*
* ?????????
* wxgamew ?????? wxgameh ?????? imagex ???????? imagey ???????? imagew???? imageh ???? btnimageurl ????
*/
function WXgameloginbuttonshow(wxgamew, wxgameh, imagex, imagey, imagew, imageh, btnimageurl) {
var winh, winw;
try {
wx.getSystemInfo({
success: function (res) {
console.log(res.model)
console.log(res.pixelRatio)
console.log(res.windowWidth)
console.log(res.windowHeight)
winh = res.windowHeight;
winw = res.windowWidth;
}
})
var sh = winh / wxgameh;
var sw = winw / wxgamew;
console.log("sw=" + sw + "sh=" + sh);
imagew = parseInt(imagew * sw);
imageh = parseInt(imageh * sh);
imagex = parseInt(imagex * sw);
imagey = parseInt(imagey * sh);;
if (wx.createUserInfoButton) {
if (Wxloginbutton) {
console.log("??button");
Wxloginbutton.show();
Wxloginbutton.offTap();//????????
} else {
console.log("????button");
// console.log(document.title);
Wxloginbutton = wx.createUserInfoButton({
type: 'image',
image: btnimageurl,
style: {
left: imagex,
top: imagey,
width: imagew,
height: imageh
}
})
}
//Wxloginbutton.onTap((res) => {
//console.log(res)
//if (res.errMsg.indexOf('auth deny') > -1 || res.errMsg.indexOf('auth denied') > -1 || res.errMsg.indexOf('fail') > -1 ) {
//wx.showModal({
//title: '??',
//content: '??????????????????->??(?????)->?????->??',
//showCancel: false,
//success: function (res) {
//if (res.confirm) {
//console.log('??????')
//} else if (res.cancel) {
//console.log('??????')
//}
//}
//})
//} else {
//WXgameLogin(res);
//}
//})
Wxloginbutton.onTap(function(res){
console.log(res)
if (res.errMsg.indexOf('auth deny') > -1 || res.errMsg.indexOf('auth denied') > -1 || res.errMsg.indexOf('fail') > -1 ) {
wx.showModal({
title: '??',
content: '??????????????????->??(?????)->?????->??',
showCancel: false,
success: function (res) {
if (res.confirm) {
console.log('??????')
} else if (res.cancel) {
console.log('??????')
}
}
})
} else {
WXgameLogin(res);
}
})
} else {
// ???????????????????????,???????
console.log("????????");
wx.showModal({
title: '??',
content: '????????,???????,??????????????',
showCancel:false,
success: function (res) {
if (res.confirm) {
console.log('??????')
} else if (res.cancel) {
console.log('??????')
}
}
})
}
} catch (e) {
}
}
/**
* ????????
*/
function WXgameloginbuttonhide() {
if (Wxloginbutton) {
Wxloginbutton.hide();
}
}
function Decrypt(key, iv, word) {
// var openDataContext = wx.getOpenDataContext()
// openDataContext.postMessage({
// text: 'hello',
// year: (new Date()).getFullYear()
// })
var murl = "https://games.tscce.cn/wechatsmallgame/encrypt.php";
var date = {};
date.key = encodeURIComponent(key);
date.type = "decrypt";
date.iv = encodeURIComponent(iv);
date.data = encodeURIComponent(word);
wx.request({
url: murl,
data: date,
method: 'POST',
header: {
"Content-Type": "application/x-www-form-urlencoded"
},
success: function (res) {
if (res.statusCode == 200) {
if (res.data) {
var mdata = decodeURIComponent(res.data);
console.log('??------' + mdata)
mdata = JSON.parse(mdata);
try {
console.log("??");
Wxloginbutton.hide();
console.log("Wxloginbutton??");
getwxuserinfo(mdata);
} catch (e) {
}
// sharelogin(mdata.openId, mdata.avatarUrl, mdata.nickName, mdata.gender, mdata.city, mdata.province, mdata.unionId);
}
} else {
console.log('??!res.statusCode' + res.statusCode)
}
},
fail: function (res) {
console.log('??!')
}
});
}
function WXgameLogin(uesrdata) {
// ??
wx.login({
//success: res => {
//if (res.code) {
//var murl = "https://games.tscce.cn/wechatsmallgame/code2accessToken.php?appid=wxaeeca8ffffa94105" +"&secret=639a292164680d4cd87a024edae21f41"+"&js_code="+res.code;
//console.log("????res.code=" + res.code);
//wx.request({
//url: murl,
//method: 'GET',
//header: {
//'content-type': 'application/json'
//},
//success: function (res) {
//console.log('??' + JSON.stringify(res));
//console.log('res.statusCode' + res.statusCode);
//if (res.statusCode == 200) {
//var openid = res.data.openid;
//var unionid = res.data.unionid;
//var session_key = res.data.session_key;
//Decrypt(session_key, uesrdata.iv, uesrdata.encryptedData);
//} else {
//console.log('??!res.statusCode' + res.statusCode)
//}
//},
//fail: function (res) {
//console.log('??!')
//}
//});
//}
//}
success: function(res){
if (res.code) {
var murl = "https://games.tscce.cn/wechatsmallgame/code2accessToken.php?appid=wxaeeca8ffffa94105" +"&secret=639a292164680d4cd87a024edae21f41"+"&js_code="+res.code;
console.log("????res.code=" + res.code);
wx.request({
url: murl,
method: 'GET',
header: {
'content-type': 'application/json'
},
success: function (res) {
console.log('??' + JSON.stringify(res));
console.log('res.statusCode' + res.statusCode);
if (res.statusCode == 200) {
var openid = res.data.openid;
var unionid = res.data.unionid;
var session_key = res.data.session_key;
Decrypt(session_key, uesrdata.iv, uesrdata.encryptedData);
} else {
console.log('??!res.statusCode' + res.statusCode)
}
},
fail: function (res) {
console.log('??!')
}
});
}
}
});
}
/**
* ?????????
*/
function initgameshareinfo(sharetitle, shareimageurl) {
wx.showShareMenu({ withShareTicket: true });
//wx.onShareAppMessage(data => {
//return {
//title: sharetitle,
//imageUrl: shareimageurl,
//query: '',
//success: function (res) {
//console.log(res);
//console.log('??????');
//sharegameinfo(1);
//},
//fail: function (res) {
//console.log('??????');
//console.log(res);
//sharegameinfo(2);
//}
//}
//})
wx.onShareAppMessage(function(data){
return {
title: sharetitle,
imageUrl: shareimageurl,
query: '',
success: function (res) {
console.log(res);
console.log('??????');
sharegameinfo(1);
},
fail: function (res) {
console.log('??????');
console.log(res);
sharegameinfo(2);
}
}
})
}
/**
* ?????????
*/
function hidegameshareinfo(sharetitle, shareimageurl) {
wx.hideShareMenu();
}
/**
* ??????
*/
function Pullupgameshare(sharetitle, shareimageurl) {
wx.shareAppMessage({
title: sharetitle,
imageUrl: shareimageurl,
query: "",
success: function (res) {
console.log(res);
console.log('??????');
sharegameinfo(1);
},
fail: function (res) {
console.log('??????');
console.log(res);
sharegameinfo(2);
}
});
}
/**
* ???????
*/
function createGameClubButtonimage(mleft, mtop, mwidth, mheight) {
var button = wx.createGameClubButton({
type: 'image',
icon: 'green',
style: {
left: mleft,
top: mtop,
width: mwidth,
height: mheight
}
})
}
// var button = wx.createGameClubButton({
// type: 'text',
// icon: 'green',
// style: {
// left: 10,
// top: 76,
// width: 40,
// height: 40
// }
// })
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+149
View File
@@ -0,0 +1,149 @@
// ============================================================================
// 游戏配置 (Game Configuration)
// ============================================================================
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:"https://tsgames.daoqi88.cn/config_test/beta_update_jsonv2.txt"+"?"+ifast_random(100000)
// gameserver:"https://tsgames.daoqi88.cn/config_test/test_update_jsonv2.txt"+"?"+ifast_random(100000)
//gameserver:"https://gameotherwork.ld2sw.cn/update_json/update_json_haiwai.txt"+"?"+ifast_random(100000)
// gameserver:"https://tsgames.daoqi88.cn/config/update_jsonv2.txt"+"?"+ifast_random(100000)
};
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:["你好","2","3","4","5","6","7"],//常用语内容
TextContentMp3:["","","","","","",""],//常用语对应音效
};
Game_Config.Share={//分享参数
appdownload:"",//下载链接(无需配置,从服务器获取)
title:"Test",//(分享标题)
description:"hello world",//分享描述
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\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,//是否为百人场
}
+129
View File
@@ -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);
}
});
}
}
+300
View File
@@ -0,0 +1,300 @@
// ============================================================================
// 02_SubGame_Input.js —— 平台输入/回调接口「转发壳」(框架维护,子游戏不碰)
// 平台按全局名调用 gameHallImport.* / Game_Modify.*;本文件统一转发到 SubGameHooks 同名 hook。
// 子游戏只需在 codes/ 的 SubGameHooks 里实现需要的接口(见 codes/SubGameHooks.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;
};
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,225 @@
/**
* 网络收发包工具类
* 自动处理平台框架提供的基础数据字段,简化子游戏的网络通信
*
* 功能特性:
* - 自动注入平台数据:agentid、gameid、playerid、roomcode
* - 统一的RPC发送接口
* - 支持调试模式输出
* - 遵循数据包协议规范
*
* @author GameABC Framework
* @version 1.0.0
*/
var RpcHelper = {
/**
* 发送RPC请求
* 自动注入平台框架提供的基础验证字段
*
* @param {string} app - 应用名称,通常为 "youle"
* @param {string} route - 路由名称,如 "room"
* @param {string} rpc - RPC方法名,如 "play_card"
* @param {Object} gameData - 子游戏业务数据
* @param {Object} options - 可选配置参数
* @param {boolean} options.debug - 是否输出调试信息,默认false
* @param {Object} options.customData - 自定义的额外数据字段
*/
sendRpc: function(app, route, rpc, gameData, options) {
// 参数验证
if (!app || typeof app !== 'string') {
console.error('RpcHelper.sendRpc: app参数必须是非空字符串');
return;
}
if (!route || typeof route !== 'string') {
console.error('RpcHelper.sendRpc: route参数必须是非空字符串');
return;
}
if (!rpc || typeof rpc !== 'string') {
console.error('RpcHelper.sendRpc: rpc参数必须是非空字符串');
return;
}
// 默认选项
options = options || {};
var debug = options.debug || false;
var customData = options.customData || {};
// 构建完整的数据包
var data = {};
// 1. 自动注入平台框架提供的基础验证字段
try {
data.agentid = GameData.AgentId; // 运营商ID(String类型)
data.gameid = GameData.GameId; // 游戏类型标识(String类型)
data.playerid = C_Player.playerid; // 玩家唯一标识(Number类型)
data.roomcode = Utl.getRoomcode(); // 房间号(String类型)
} catch (e) {
console.error('RpcHelper.sendRpc: 获取平台基础数据失败', e);
return;
}
// 2. 验证必需的平台数据
if (!data.agentid) {
console.error('RpcHelper.sendRpc: agentid未设置,请确保平台框架已初始化');
return;
}
if (!data.gameid) {
console.error('RpcHelper.sendRpc: gameid未设置,请确保平台框架已初始化');
return;
}
if (!data.playerid) {
console.error('RpcHelper.sendRpc: playerid未设置,请确保玩家已登录');
return;
}
if (!data.roomcode) {
console.error('RpcHelper.sendRpc: roomcode未设置,请确保已进入房间');
return;
}
// 3. 合并子游戏业务数据
if (gameData && typeof gameData === 'object') {
for (var key in gameData) {
if (gameData.hasOwnProperty(key)) {
data[key] = gameData[key];
}
}
}
// 4. 合并自定义数据
for (var key in customData) {
if (customData.hasOwnProperty(key)) {
data[key] = customData[key];
}
}
// 5. 调试信息输出
if (debug || Game_Config.Debugger.isDebugger) {
console.log('RpcHelper发送RPC:', {
app: app,
route: route,
rpc: rpc,
platform_data: {
agentid: data.agentid,
gameid: data.gameid,
playerid: data.playerid,
roomcode: data.roomcode
},
game_data: gameData,
custom_data: customData
});
}
// 6. 通过框架发送RPC请求
Utl.sendData(app, route, rpc, data);
},
/**
* 发送游戏房间RPC
* 专门用于游戏房间相关的RPC调用,预设app="youle", route="room"
*
* @param {string} rpc - RPC方法名
* @param {Object} gameData - 子游戏业务数据
* @param {Object} options - 可选配置参数
*/
sendGameRpc: function(rpc, gameData, options) {
this.sendRpc("youle", "room", rpc, gameData, options);
},
/**
* 发送代理商RPC
* 专门用于代理商相关的RPC调用,预设app="youle", route="agent"
*
* @param {string} rpc - RPC方法名
* @param {Object} gameData - 子游戏业务数据
* @param {Object} options - 可选配置参数
*/
sendAgentRpc: function(rpc, gameData, options) {
this.sendRpc("youle", "agent", rpc, gameData, options);
},
/**
* 发送平台RPC
* 专门用于平台相关的RPC调用,预设app="youle", route="platform"
*
* @param {string} rpc - RPC方法名
* @param {Object} gameData - 子游戏业务数据
* @param {Object} options - 可选配置参数
*/
sendPlatformRpc: function(rpc, gameData, options) {
this.sendRpc("youle", "platform", rpc, gameData, options);
},
/**
* 获取当前平台数据快照
* 用于调试和信息查看
*
* @returns {Object} 包含当前平台数据的对象
*/
getPlatformData: function() {
try {
return {
agentid: GameData.AgentId,
gameid: GameData.GameId,
playerid: C_Player.playerid,
roomcode: Utl.getRoomcode(),
timestamp: new Date().getTime()
};
} catch (e) {
console.error('RpcHelper.getPlatformData: 获取平台数据失败', e);
return null;
}
},
/**
* 验证平台数据完整性
* 检查所有必需的平台数据是否已正确设置
*
* @returns {Object} 验证结果对象 {valid: boolean, missing: Array}
*/
validatePlatformData: function() {
var result = {
valid: true,
missing: []
};
try {
if (!GameData.AgentId) {
result.valid = false;
result.missing.push('agentid');
}
if (!GameData.GameId) {
result.valid = false;
result.missing.push('gameid');
}
if (!C_Player.playerid) {
result.valid = false;
result.missing.push('playerid');
}
if (!Utl.getRoomcode()) {
result.valid = false;
result.missing.push('roomcode');
}
} catch (e) {
result.valid = false;
result.missing.push('platform_error');
console.error('RpcHelper.validatePlatformData: 验证过程出错', e);
}
return result;
}
};
// ES5兼容性:确保在旧版本浏览器中也能正常工作
if (typeof Object.defineProperty === 'function') {
// 防止意外修改工具类
Object.defineProperty(window, 'RpcHelper', {
value: RpcHelper,
writable: false,
configurable: false
});
} else {
// 旧版本浏览器的兼容处理
window.RpcHelper = RpcHelper;
}
@@ -0,0 +1,467 @@
// ============================================================
// SpineMgr —— Spine 动画管理器(独立文件)
// 集成到 gameabc Canvas 2D 绘制循环,零侵入 gamemain.js
// 在 index.html 中置于 gameabc.min.js 之后、gamemain.js 之前加载
// ============================================================
(function(){
var SpineMgr = {
// --- 内部状态 ---
_entries: {}, // {id: EntryObject}
_assetManager: null,
_renderer: null, // spine.SkeletonRenderer (Canvas 2D)
_lastTime: 0, // 上一帧时间戳(ms)
_inited: false,
_loadedPaths: {}, // 已请求加载的资源路径,避免重复请求
_pendingCmds: {}, // {id: [{method, args}]} 等待实例就绪后执行的命令队列
_scaleMap: {}, // {spineId: scale} 主流 SkeletonJson.scale 方案,_autoLoad 时注入
_skelDataCache: {}, // {"file.json_scale1": SkeletonData} 同文件共享解析结果,避免重复 readSkeletonData
// 默认资源根路径(可通过 init 覆盖)
_basePath: "assets/spine/",
// ----------------------------------------------------------
// init(basePath) 手动初始化(可选)
// 如不调用,load 时会自动以默认路径 "assets/spine/" 初始化
// ----------------------------------------------------------
init: function(basePath) {
if (!window.spine) {
logmessage("[SpineMgr] spine-canvas.js 未加载,请检查引用");
return;
}
this._basePath = basePath || this._basePath;
this._assetManager = new spine.AssetManager(this._basePath);
this._lastTime = Date.now();
this._inited = true;
},
// (内部) 确保已初始化
_ensureInit: function() {
if (!this._inited) {
this.init(this._basePath);
}
},
// (内部) 自动加载:根据 id 约定文件名 id.json / id.atlas
// 主流 SkeletonJson.scale 方案:从 _scaleMap 读取目标缩放,传入 load() 在解析时烘焙
_autoLoad: function(id) {
if (this._entries[id]) return;
var scale = this._scaleMap[id] || 1;
this.load(id, id + ".json", id + ".atlas", { scale: scale });
},
// (内部) 将命令加入等待队列
_queueCmd: function(id, method, args) {
if (!this._pendingCmds[id]) this._pendingCmds[id] = [];
this._pendingCmds[id].push({method: method, args: args});
},
// (内部) 实例就绪后执行队列中的命令
_flushCmds: function(id) {
var cmds = this._pendingCmds[id];
if (!cmds || cmds.length === 0) return;
delete this._pendingCmds[id];
for (var i = 0; i < cmds.length; i++) {
var cmd = cmds[i];
this[cmd.method].apply(this, [id].concat(cmd.args));
}
},
// ----------------------------------------------------------
// load(id, jsonFile, atlasFile, option)
// 加载一组 Spine 资源, id 为自定义标识
// option: {x, y, scale, skin, animation, loop, mixDuration}
// ----------------------------------------------------------
load: function(id, jsonFile, atlasFile, option) {
this._ensureInit();
if (!this._inited) return;
var opt = option || {};
// 只加载尚未请求过的资源(预加载过的会跳过)
if (!this._loadedPaths[jsonFile]) {
this._assetManager.loadText(jsonFile);
this._loadedPaths[jsonFile] = true;
}
if (!this._loadedPaths[atlasFile]) {
this._assetManager.loadTextureAtlas(atlasFile);
this._loadedPaths[atlasFile] = true;
}
this._entries[id] = {
jsonFile: jsonFile,
atlasFile: atlasFile,
skeleton: null,
state: null,
x: opt.x || 0,
y: opt.y || 0,
scale: opt.scale || 1,
skin: opt.skin || "default",
defAnim: opt.animation || null,
defLoop: opt.loop !== undefined ? opt.loop : true,
mixDur: opt.mixDuration || 0.2,
visible: true,
ready: false,
_hideOnComplete: false, // 播放完成后是否自动隐藏
_hideAfterCompletes: 0 // 剩余多少次 complete 后触发隐藏
};
},
// ----------------------------------------------------------
// (内部) 资源加载完成后实例化骨骼
// 同一 jsonFile+scale 的 SkeletonData 只解析一次,多个 entry 共享
// ----------------------------------------------------------
_buildEntry: function(entry) {
logmessage("[SpineMgr] _buildEntry: " + entry._id + " skelJson.scale=" + entry.scale);
var cacheKey = entry.jsonFile + "_s" + entry.scale;
var skelData = this._skelDataCache[cacheKey];
if (!skelData) {
var atlas = this._assetManager.require(entry.atlasFile);
var loader = new spine.AtlasAttachmentLoader(atlas);
var skelJson = new spine.SkeletonJson(loader);
skelJson.scale = entry.scale;
skelData = skelJson.readSkeletonData(
this._assetManager.require(entry.jsonFile)
);
this._skelDataCache[cacheKey] = skelData;
logmessage("[SpineMgr] SkeletonData 解析并缓存: " + cacheKey);
} else {
logmessage("[SpineMgr] SkeletonData 命中缓存: " + cacheKey);
}
entry.skeleton = new spine.Skeleton(skelData);
// 设置皮肤
if (entry.skin && entry.skin !== "default") {
entry.skeleton.setSkinByName(entry.skin);
}
entry.skeleton.setToSetupPose();
// AnimationState
var stateData = new spine.AnimationStateData(skelData);
stateData.defaultMix = entry.mixDur;
entry.state = new spine.AnimationState(stateData);
// 绑定事件回调 —— 转发到 Spine_Event.js
entry.state.addListener({
complete: function(trackEntry) {
// 自动隐藏:playOnce / playQueue 设置的计数器
if (entry._hideOnComplete && !trackEntry.loop) {
entry._hideAfterCompletes--;
if (entry._hideAfterCompletes <= 0) {
entry.visible = false;
entry._hideOnComplete = false;
}
}
if (gameabc_face.spine_onComplete) {
gameabc_face.spine_onComplete(entry._id, trackEntry.animation.name, trackEntry.trackIndex);
}
},
event: function(trackEntry, event) {
if (gameabc_face.spine_onEvent) {
gameabc_face.spine_onEvent(entry._id, event.data.name, event.intValue, event.floatValue, event.stringValue);
}
}
});
// 默认动画
if (entry.defAnim) {
entry.state.setAnimation(0, entry.defAnim, entry.defLoop);
}
entry.ready = true;
},
// ----------------------------------------------------------
// (内部) 每帧调用:确保所有资源就绪
// ----------------------------------------------------------
_tryBuild: function() {
if (!this._assetManager.isLoadingComplete()) return false;
for (var id in this._entries) {
var e = this._entries[id];
if (!e.ready) {
e._id = id;
try {
this._buildEntry(e);
logmessage("[SpineMgr] " + id + " 构建完成");
this._flushCmds(id);
} catch(err) {
logmessage("[SpineMgr] " + id + " 构建失败: " + err.message);
e.ready = false;
}
}
}
return true;
},
// ----------------------------------------------------------
// updateAndDraw(ctx) 每帧自动调用
// ctx: gameabc_face.dc (Canvas 2D Context)
// ----------------------------------------------------------
updateAndDraw: function(ctx) {
if (!this._inited) return;
this._tryBuild();
var now = Date.now();
var dt = (now - this._lastTime) / 1000;
this._lastTime = now;
if (dt <= 0 || dt > 0.5) dt = 1/30;
if (!this._renderer) {
this._renderer = new spine.SkeletonRenderer(ctx);
this._renderer.triangleRendering = true; // ★ 启用三角形渲染以支持 Mesh 网格附件
}
// 确保 renderer 用的是当前 ctx (gameabc可能重建)
this._renderer.ctx = ctx;
for (var id in this._entries) {
var e = this._entries[id];
if (!e.ready || !e.visible) continue;
e.state.update(dt);
e.state.apply(e.skeleton);
// 骨骼位置归零,由 ctx.translate 控制实际位置
e.skeleton.x = 0;
e.skeleton.y = 0;
e.skeleton.updateWorldTransform(spine.Physics.update);
ctx.save();
ctx.translate(e.x, e.y);
ctx.scale(1, -1); // ★ Spine Y-up → Canvas Y-down(scale 已烘焙进 SkeletonJson.scale)
this._renderer.draw(e.skeleton);
ctx.restore();
}
},
// ==========================================================
// 公开控制 API
// ==========================================================
setAnimation: function(id, animName, loop, track) {
var e = this._entries[id];
if (!e) {
this._autoLoad(id);
this._queueCmd(id, "setAnimation", [animName, loop, track]);
return null;
}
e.visible = true;
if (!e.ready) {
this._queueCmd(id, "setAnimation", [animName, loop, track]);
return null;
}
return e.state.setAnimation(track || 0, animName, loop !== false);
},
addAnimation: function(id, animName, loop, delay, track) {
var e = this._entries[id];
if (!e) {
this._autoLoad(id);
this._queueCmd(id, "addAnimation", [animName, loop, delay, track]);
return null;
}
if (!e.ready) {
this._queueCmd(id, "addAnimation", [animName, loop, delay, track]);
return null;
}
return e.state.addAnimation(track || 0, animName, loop !== false, delay || 0);
},
setPosition: function(id, x, y) {
var e = this._entries[id];
if (!e) {
this._autoLoad(id);
e = this._entries[id];
}
e.x = x; e.y = y;
},
setScale: function(id, sx, sy) {
// 主流方案:scale 通过 SkeletonJson.scale 在 load 时烘焙,运行时 setScale 更新 _scaleMap
// 并在 entry 未就绪时作为 option 重建,保持接口向后兼容
this._scaleMap[id] = sx;
var e = this._entries[id];
if (!e) {
this._autoLoad(id); // 会使用 _scaleMap[id] = sx
return;
}
if (e.scale === sx) return; // 已是目标 scale,无需重建
// 目标 scale 与已烘焙 scale 不同 → 清除旧 entry,下次 _autoLoad 将以新 scale 重建
var savedX = e.x, savedY = e.y;
this.remove(id);
this.load(id, id + ".json", id + ".atlas", { scale: sx, x: savedX, y: savedY });
},
setFlip: function(id, flipX, flipY) {
var e = this._entries[id];
if (!e) {
this._autoLoad(id);
this._queueCmd(id, "setFlip", [flipX, flipY]);
return;
}
if (!e.skeleton) {
this._queueCmd(id, "setFlip", [flipX, flipY]);
return;
}
e.skeleton.scaleX = Math.abs(e.skeleton.scaleX) * (flipX ? -1 : 1);
e.skeleton.scaleY = Math.abs(e.skeleton.scaleY) * (flipY ? -1 : 1);
},
setVisible: function(id, visible) {
var e = this._entries[id];
if (!e) {
this._autoLoad(id);
e = this._entries[id];
}
e.visible = !!visible;
},
setSkin: function(id, skinName) {
var e = this._entries[id];
if (!e) {
this._autoLoad(id);
this._queueCmd(id, "setSkin", [skinName]);
return;
}
if (!e.ready) {
this._queueCmd(id, "setSkin", [skinName]);
return;
}
e.skeleton.setSkinByName(skinName);
e.skeleton.setSlotsToSetupPose();
},
getAnimations: function(id) {
var e = this._entries[id];
if (!e) { this._autoLoad(id); return []; }
if (!e.ready) return [];
var anims = e.skeleton.data.animations;
var names = [];
for (var i = 0; i < anims.length; i++) names.push(anims[i].name);
return names;
},
getSkins: function(id) {
var e = this._entries[id];
if (!e) { this._autoLoad(id); return []; }
if (!e.ready) return [];
var skins = e.skeleton.data.skins;
var names = [];
for (var i = 0; i < skins.length; i++) names.push(skins[i].name);
return names;
},
playOnce: function(id, animName, track) {
this.setVisible(id, true);
// 重播前重置骨骼到初始姿态,消除上次末帧残留导致的混合过渡(斜向位移)
var e = this._entries[id];
if (e && e.ready) {
e.state.clearTracks();
e.skeleton.setToSetupPose();
}
this.setAnimation(id, animName, false, track);
e = this._entries[id];
if (e) {
e._hideOnComplete = true;
e._hideAfterCompletes = 1;
}
},
playQueue: function(id, animList, hideOnComplete) {
if (!animList || animList.length === 0) return;
this.setVisible(id, true);
// 重播前重置骨骼到初始姿态,消除上次末帧残留导致的混合过渡
var e = this._entries[id];
if (e && e.ready) {
e.state.clearTracks();
e.skeleton.setToSetupPose();
}
this.setAnimation(id, animList[0], false, 0);
for (var i = 1; i < animList.length; i++) {
this.addAnimation(id, animList[i], false, 0, 0);
}
var e = this._entries[id];
if (e) {
e._hideOnComplete = hideOnComplete !== false;
e._hideAfterCompletes = animList.length;
}
},
stop: function(id) {
var e = this._entries[id];
if (!e) return;
e.visible = false;
e._hideOnComplete = false;
e._hideAfterCompletes = 0;
delete this._pendingCmds[id];
if (e.state) {
e.state.clearTracks();
}
if (e.skeleton) {
e.skeleton.setToSetupPose();
}
},
stopAll: function() {
for (var id in this._entries) {
this.stop(id);
}
},
remove: function(id) {
delete this._entries[id];
delete this._pendingCmds[id];
},
removeAll: function() {
this._entries = {};
this._pendingCmds = {};
this._skelDataCache = {};
}
};
// 挂载到 gameabc_face 上
gameabc_face.spineMgr = SpineMgr;
// ★ 自动预加载:读取 gameabc_face.spineAssets 清单,在引擎启动前预加载所有 Spine 资源
// 如果存在 spineTextData(嵌入文本数据),则通过 setRawDataURI 注入,
// 彻底避免 file:// 协议下 XHR CORS 拦截问题
if (gameabc_face.spineAssets && gameabc_face.spineAssets.length > 0) {
SpineMgr._ensureInit();
var list = gameabc_face.spineAssets;
var textData = gameabc_face.spineTextData || {};
for (var i = 0; i < list.length; i++) {
var name = list[i];
var jsonKey = name + ".json";
var atlasKey = name + ".atlas";
// 将嵌入文本数据注册为 rawDataURI,Spine Downloader 会优先从内存读取
if (textData[jsonKey]) {
SpineMgr._assetManager.setRawDataURI(jsonKey, "data:," + textData[jsonKey]);
}
if (textData[atlasKey]) {
SpineMgr._assetManager.setRawDataURI(atlasKey, "data:," + textData[atlasKey]);
}
SpineMgr._assetManager.loadText(jsonKey);
SpineMgr._loadedPaths[jsonKey] = true;
SpineMgr._assetManager.loadTextureAtlas(atlasKey);
SpineMgr._loadedPaths[atlasKey] = true;
}
logmessage("[SpineMgr] 预加载 " + list.length + " 组 Spine 资源" +
(Object.keys(textData).length > 0 ? "(使用嵌入数据)" : "(使用网络请求)"));
}
// ★ 用 defineProperty 拦截 gameenddraw 赋值
// 无论 gamemain.js 或其他代码如何定义 gameenddraw,
// Spine 渲染都会自动追加在用户逻辑之后
var _userEndDraw = gameabc_face.gameenddraw || null;
var _wrappedEndDraw = function(gameid, spid, times, timelong) {
if (typeof _userEndDraw === "function") {
_userEndDraw(gameid, spid, times, timelong);
}
var ctx = gameabc_face.dc;
if (ctx && SpineMgr._inited) {
SpineMgr.updateAndDraw(ctx);
}
};
Object.defineProperty(gameabc_face, "gameenddraw", {
configurable: true,
get: function() { return _wrappedEndDraw; },
set: function(fn) { _userEndDraw = fn; }
});
})();
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,351 @@
/**
* AudioManager.js - 音频管理器(框架层)
*
* 功能:
* 1. 统一管理游戏音效和背景音乐的播放/停止
* 2. 封装底层音频API (Utl.playSound/stopSound/playMusic/stopMusic)
* 3. 提供根据性别选择语音的通用基础设施
*
* ⚠️ 框架设计原则:
* - 本文件不包含任何具体游戏的资源常量(不引用 SoundResources)
* - 不包含任何游戏专属的 ID 映射(如麻将牌值、动作类型)
* - 游戏专属逻辑在子游戏侧封装(如 GameAudio.js)
* - 静音开关与音量由平台层(Utl / 平台设置)统一处理,本文件不维护其状态
*
* 核心 API:
* - AudioManager.playSound(soundFile) // 播放任意音效文件
* - AudioManager.playVoice(baseId, sex) // 播放语音(自动切换男/女音)
* - AudioManager.playVoiceBySeat(seat, baseId) // 播放语音(从座位自动获取性别)
* - AudioManager.playMusic() // 播放背景音乐
*
* 性别判断:
* - sex === 2 为女性,女音文件 ID = 男音 baseId + FEMALE_VOICE_OFFSET (100)
* - 可通过 Utl.getSexBySeat(seat) 获取座位上玩家的性别
*
* 注意:
* - 背景音乐需要在 Game_Config.soundList 中配置
* - 游戏专属的 ID 映射(牌值/动作类型等)由子游戏侧 GameAudio.js 维护
* - 是否静音 / 音量大小由平台层 Utl.playSound / Utl.playMusic 内部按用户设置决定
*
* ============================================================================
* @guide 开发指南 - AudioManager 使用规范
* ============================================================================
*
* 【初始化(UIBootstrap 或游戏入口调用一次)】
* AudioManager.init();
*
* 【播放任意音效文件(游戏层传文件名)】
* AudioManager.playSound('00101.mp3');
* // 通常配合游戏侧常量使用:
* AudioManager.playSound(SoundResources.EFFECTS.DISCARD);
*
* 【通用性别语音播放(框架层 API,游戏层控制 ID)】
*
* gameabc 引擎语音文件命名规则:
* - 文件名统一为 5位数字 + ".mp3",如 "00151.mp3"
* - 男音使用基础 ID,女音 = 基础 ID + FEMALE_VOICE_OFFSET(默认 100)
* - 例:男音 baseId=151 → "00151.mp3",女音 → "00251.mp3"
*
* // 指定 baseId 和性别(ID 含义由游戏层定义):
* AudioManager.playVoice(151, sex); // 自动切换男/女音
* AudioManager.playVoiceBySeat(seat, 151); // 从座位自动获取性别再播放
*
* 【游戏侧扩展模式(在子游戏中创建 GameAudio.js)】
*
* // 子游戏自行维护 ID 映射,调用框架的 playVoice/playVoiceBySeat
* var ACTION_VOICE_BASE_ID = {
* 'peng': 152, 'gang': 153, 'hu': 154, 'chi': 151
* };
* GameAudio.playActionBySeat = function(seat, action) {
* var baseId = ACTION_VOICE_BASE_ID[action];
* if (baseId) AudioManager.playVoiceBySeat(seat, baseId);
* };
* // 调用:GameAudio.playActionBySeat(seat, 'peng');
*
* 【背景音乐】
* AudioManager.playMusic(); // 播放(需在 Game_Config.soundList 中配置)
* AudioManager.stopMusic();
*
* @version 4.0.0
* @date 2026-06-08
*/
var AudioManager = AudioManager || {};
(function() {
'use strict';
// ============================================================================
// 私有变量
// ============================================================================
var _initialized = false;
var _currentMusic = null;
var _playingSounds = {}; // { soundFile: playCount }
// ============================================================================
// 框架常量 - gameabc 引擎语音规则
// ============================================================================
/**
* 女性语音 ID 偏移量
* gameabc 引擎约定:女音文件 ID = 男音基础 ID + FEMALE_VOICE_OFFSET
*/
var FEMALE_VOICE_OFFSET = 100;
/**
* 女性性别值(来自 Utl.getSexBySeat 返回值约定)
*/
var SEX_FEMALE = 2;
// ============================================================================
// 私有方法
// ============================================================================
/**
* 检查 Utl API 是否可用
* @private
*/
function _isUtlAvailable() {
return typeof Utl !== 'undefined';
}
/**
* 格式化语音文件名
* gameabc 引擎语音文件统一为 5位数字 + .mp3
* @private
* @param {number} fileNum
* @returns {string} 如 "00151.mp3"
*/
function _formatSoundFile(fileNum) {
var s = String(fileNum);
while (s.length < 5) { s = '0' + s; }
return s + '.mp3';
}
function _isFemale(sex) {
return sex === SEX_FEMALE;
}
/**
* 通过座位号获取玩家性别
* @private
* @param {number} seat
* @returns {number} 性别值(2=女,其他=男)
*/
function _getSexBySeat(seat) {
if (_isUtlAvailable() && typeof Utl.getSexBySeat === 'function') {
return Utl.getSexBySeat(seat);
}
return 1;
}
/**
* 根据性别获取语音 ID 偏移量
* @private
* @param {number} sex
* @returns {number} 0(男)或 FEMALE_VOICE_OFFSET(女)
*/
function _getVoiceOffset(sex) {
return _isFemale(sex) ? FEMALE_VOICE_OFFSET : 0;
}
// ============================================================================
// 公共 API - 初始化
// ============================================================================
/**
* 初始化音频管理器
* 静音/音量由平台层处理,本管理器无需配置项。
*/
AudioManager.init = function() {
if (_initialized) {
console.warn('[AudioManager] Already initialized');
return;
}
_initialized = true;
console.log('[AudioManager] Initialized (v4.0.0)');
};
// ============================================================================
// 公共 API - 音效播放
// ============================================================================
/**
* 播放音效
* @param {string} soundFile - 文件名,如 "00101.mp3" 或游戏侧常量
* @returns {boolean}
*/
AudioManager.playSound = function(soundFile) {
if (!soundFile) { return false; }
if (!_isUtlAvailable() || !Utl.playSound) {
console.warn('[AudioManager] Utl.playSound not available');
return false;
}
try {
Utl.playSound(soundFile);
_playingSounds[soundFile] = (_playingSounds[soundFile] || 0) + 1;
return true;
} catch (e) {
console.error('[AudioManager] playSound error:', soundFile, e);
return false;
}
};
/**
* 停止音效
* @param {string} soundFile
* @returns {boolean}
*/
AudioManager.stopSound = function(soundFile) {
if (!soundFile) { return false; }
if (!_isUtlAvailable() || !Utl.stopSound) {
console.warn('[AudioManager] Utl.stopSound not available');
return false;
}
try {
Utl.stopSound(soundFile);
if (_playingSounds[soundFile]) {
_playingSounds[soundFile]--;
if (_playingSounds[soundFile] <= 0) { delete _playingSounds[soundFile]; }
}
return true;
} catch (e) {
console.error('[AudioManager] stopSound error:', soundFile, e);
return false;
}
};
/** 停止所有音效 */
AudioManager.stopAllSounds = function() {
for (var f in _playingSounds) {
if (_playingSounds.hasOwnProperty(f)) { this.stopSound(f); }
}
_playingSounds = {};
};
// ============================================================================
// 公共 API - 性别语音(通用)
// ============================================================================
/**
* 根据 baseId 和性别播放语音
* 框架自动应用偏移:女音 = baseId + FEMALE_VOICE_OFFSET
* 游戏层负责维护 baseId 含义(哪个 ID 对应哪张牌/动作)
*
* @param {number} baseId - 男音基础 ID(如 151)
* @param {number} [sex=1] - 性别(2=女,其他=男)
* @returns {boolean}
*/
AudioManager.playVoice = function(baseId, sex) {
if (!baseId) { return false; }
return this.playSound(_formatSoundFile(baseId + _getVoiceOffset(sex)));
};
/**
* 从座位自动获取性别后播放语音(推荐用法)
* @param {number} seat - 座位号
* @param {number} baseId - 男音基础 ID
* @returns {boolean}
*/
AudioManager.playVoiceBySeat = function(seat, baseId) {
return this.playVoice(baseId, _getSexBySeat(seat));
};
/**
* 获取语音文件名(不播放,用于预加载或调试)
* @param {number} baseId
* @param {number} [sex=1]
* @returns {string} 如 "00151.mp3"
*/
AudioManager.getVoiceFile = function(baseId, sex) {
return _formatSoundFile(baseId + _getVoiceOffset(sex));
};
// ============================================================================
// 公共 API - 背景音乐
// ============================================================================
/**
* 播放背景音乐
* 注意:需在 Game_Config.soundList 中配置
* @returns {boolean}
*/
AudioManager.playMusic = function() {
if (!_isUtlAvailable() || !Utl.playMusic) {
console.warn('[AudioManager] Utl.playMusic not available');
return false;
}
try {
Utl.playMusic();
_currentMusic = true;
return true;
} catch (e) {
console.error('[AudioManager] playMusic error:', e);
return false;
}
};
/**
* 停止背景音乐
* @returns {boolean}
*/
AudioManager.stopMusic = function() {
if (!_isUtlAvailable() || !Utl.stopMusic) { return false; }
try {
Utl.stopMusic();
_currentMusic = null;
return true;
} catch (e) {
console.error('[AudioManager] stopMusic error:', e);
return false;
}
};
/** 暂停背景音乐(等同 stopMusic) */
AudioManager.pauseMusic = function() {
return this.stopMusic();
};
/** 恢复背景音乐 */
AudioManager.resumeMusic = function() {
if (_currentMusic) { return this.playMusic(); }
return false;
};
// ============================================================================
// 公共API - 状态
// ============================================================================
/**
* 获取音频管理器状态
* @returns {Object}
*/
AudioManager.getStatus = function() {
var playingCount = 0;
for (var key in _playingSounds) {
if (_playingSounds.hasOwnProperty(key)) {
playingCount += _playingSounds[key];
}
}
return {
initialized: _initialized,
playingSoundsCount: playingCount,
isMusicPlaying: !!_currentMusic,
version: '4.0.0'
};
};
/**
* 销毁音频管理器
*/
AudioManager.destroy = function() {
this.stopAllSounds();
this.stopMusic();
_playingSounds = {};
_currentMusic = null;
_initialized = false;
};
})();
@@ -0,0 +1,234 @@
/**
* 事件总线 (独立模块)
* 职责: 提供模块间的事件通信机制,解耦模块依赖
* 遵循原则: 发布-订阅模式,支持事件监听和触发
*
* ============================================================================
* @guide 开发指南 - EventBus 使用规范
* ============================================================================
*
* 【事件命名规范】
* 使用 "模块:动作" 两段式命名,所有名称定义在 EventBus.Events 常量中:
* 'game:started' 游戏开始
* 'game:finished' 游戏结束
* 'player:discarded' 玩家出牌
* 说明:框架只内置通用事件(game:/player:/room:/ui:/network: 等);具体玩法的
* 专属事件(如发牌/摸牌/吃碰杠)由子游戏自行追加到 EventBus.Events,框架不内置。
* ⚠️ 不要硬编码字符串,统一用 EventBus.Events.XXX 引用
*
* 【订阅事件(在组件 init 中注册)】
* // 在 BaseComponent.init 或 Object.create 的 init 方法中:
* var self = this;
* var unsubscribe = EventBus.on(EventBus.Events.GAME_STARTED, function(data) {
* self._onGameStarted(data);
* }, self);
* // 保存引用用于 destroy 时清理
* this.eventListeners['game:started'] = unsubscribe;
*
* 【发布事件】
* // 在控制器或业务逻辑中发布:
* EventBus.emit(EventBus.Events.GAME_STARTED, { playerCount: 4 });
* EventBus.emit('player:discarded', { seat: 0, card: cardData });
*
* 【一次性事件(触发后自动取消)】
* EventBus.once(EventBus.Events.GAME_FINISHED, function(data) {
* self._showSettleUI(data);
* });
*
* 【在 destroy 中清理(防内存泄漏)】
* // 如果用 BaseComponent 的 addEventListener,destroy 时自动清理
* // 如果手动保存了 unsubscribe 函数:
* if (this.eventListeners['game:started']) {
* this.eventListeners['game:started'](); // 调用取消函数
* }
* // 或者 BaseComponent.destroy() 会自动处理 this.eventListeners 中的所有项
*
* 【推荐:通过 BaseComponent.addEventListener 代替手动管理】
* // BaseComponent 子类中:
* this.addEventListener(EventBus.Events.PLAYER_DISCARDED, function(data) {
* self._onPlayerDiscarded(data);
* });
* // destroy() 时 BaseComponent 会自动调用所有 off
*/
var EventBus = {
/**
* 私有数据存储
* @private
*/
_listeners: {}, // 事件监听器列表 {eventName: [callbacks]}
/**
* 订阅事件
* @param {String} eventName - 事件名称
* @param {Function} callback - 回调函数
* @param {Object} context - 回调函数的上下文(this)
* @return {Function} 取消订阅函数
*/
on: function(eventName, callback, context) {
if (!eventName || typeof callback !== 'function') {
console.error('❌ EventBus.on: 无效的参数');
return null;
}
if (!this._listeners[eventName]) {
this._listeners[eventName] = [];
}
var listener = {
callback: callback,
context: context || null
};
this._listeners[eventName].push(listener);
console.log('✅ EventBus: 订阅事件', eventName);
// 返回取消订阅函数
var self = this;
return function() {
self.off(eventName, callback);
};
},
/**
* 订阅一次性事件(触发后自动取消订阅)
* @param {String} eventName - 事件名称
* @param {Function} callback - 回调函数
* @param {Object} context - 回调函数的上下文
* @return {Function} 取消订阅函数
*/
once: function(eventName, callback, context) {
var self = this;
var onceCallback = function() {
callback.apply(context || null, arguments);
self.off(eventName, onceCallback);
};
return this.on(eventName, onceCallback, context);
},
/**
* 取消订阅事件
* @param {String} eventName - 事件名称
* @param {Function} callback - 要取消的回调函数(可选,不传则取消所有)
* @return {Boolean} 是否取消成功
*/
off: function(eventName, callback) {
if (!this._listeners[eventName]) {
return false;
}
// 如果没有指定callback,取消该事件的所有监听器
if (!callback) {
delete this._listeners[eventName];
console.log('✅ EventBus: 取消所有', eventName, '监听器');
return true;
}
// 取消指定的callback
var listeners = this._listeners[eventName];
for (var i = listeners.length - 1; i >= 0; i--) {
if (listeners[i].callback === callback) {
listeners.splice(i, 1);
console.log('✅ EventBus: 取消', eventName, '监听器');
return true;
}
}
return false;
},
/**
* 触发事件
* @param {String} eventName - 事件名称
* @param {*} data - 事件数据(可选)
* @return {Number} 触发的监听器数量
*/
emit: function(eventName, data) {
if (!this._listeners[eventName]) {
return 0;
}
var listeners = this._listeners[eventName].slice(); // 复制数组,避免迭代中修改
var count = 0;
console.log('📡 EventBus: 触发事件', eventName, data);
for (var i = 0; i < listeners.length; i++) {
try {
var listener = listeners[i];
listener.callback.call(listener.context, data);
count++;
} catch (error) {
console.error('❌ EventBus: 事件回调异常', eventName, error);
}
}
return count;
},
/**
* 检查事件是否有监听器
* @param {String} eventName - 事件名称
* @return {Boolean} 是否有监听器
*/
hasListener: function(eventName) {
return !!(this._listeners[eventName] && this._listeners[eventName].length > 0);
},
/**
* 获取事件的监听器数量
* @param {String} eventName - 事件名称
* @return {Number} 监听器数量
*/
getListenerCount: function(eventName) {
return this._listeners[eventName] ? this._listeners[eventName].length : 0;
},
/**
* 获取所有事件名称
* @return {Array} 事件名称数组
*/
getEventNames: function() {
var names = [];
for (var name in this._listeners) {
if (this._listeners.hasOwnProperty(name)) {
names.push(name);
}
}
return names;
},
/**
* 清空所有事件监听器
*/
clear: function() {
this._listeners = {};
console.log('✅ EventBus: 已清空所有监听器');
},
/**
* 清空指定事件的所有监听器
* @param {String} eventName - 事件名称
*/
clearEvent: function(eventName) {
if (this._listeners[eventName]) {
delete this._listeners[eventName];
console.log('✅ EventBus: 已清空', eventName, '的所有监听器');
}
}
};
/**
* 事件名称常量容器(框架不预置任何具体事件名)
*
* 框架保持「游戏中立」:只提供发布-订阅机制与一个空的 EventBus.Events 容器,
* 不预定义任何事件名称常量。各子游戏在自身事件常量文件(如
* game/constants/GameEvents.js)中向 EventBus.Events 注册自己用到的【全部】事件,
* 业务侧统一通过 EventBus.Events.XXX 引用。
*
* 命名规范:两段式 "模块:动作"。例如通用语义 'game:started' / 'player:discarded' /
* 'room:updated' / 'ui:sceneChanged' / 'network:connected';玩法专属自定前缀(如
* 'mahjong:tilesDealt')。统一用常量引用,不在业务中硬编码字符串。
*/
EventBus.Events = {};
@@ -0,0 +1,370 @@
// ============================================================================
// 精灵事件控制器 (gameabc-framework / system, ES5标准)
// 基于 gameabc 引擎的精灵交互事件分发框架——游戏中立,可被任意子游戏复用
// ============================================================================
/**
* SpriteEventController —— 通用精灵交互事件分发器
*
* 职责:
* 接收平台引擎透传的精灵交互事件(按下/长按/松开/拖拽/绘制前/绘制),
* 按「精灵 ID」分发到注册的处理函数;并提供「全局绘制钩子」供需要对每个
* 绘制事件统一处理的子游戏能力(如牌面标记叠绘)挂载。
*
* 中立原则:
* 本控制器不含任何具体玩法逻辑。玩法专属的“每帧绘制”处理由子游戏通过
* registerGlobalDraw(handler) 注册,框架只负责遍历回调,不认识具体业务。
*
* 接入方式(由子游戏受限入口 Game_Modify.* 转调):
* Game_Modify.utlmousedown → SpriteEventController.handleMouseDown(...)
* Game_Modify.utlmousedown_nomove → handleMouseDownNoMove(...)
* Game_Modify.mouseup → handleMouseUp(...)
* Game_Modify.utlmousemove → handleMouseMove(...)
* Game_Modify.utlgamemydrawbegin → handleDrawBegin(...)
* Game_Modify.gamemydraw → handleDraw(...)
*
* 严格 ES5,确保浏览器/平台运行时兼容。
*/
var SpriteEventController = {
// ========================================================================
// 精灵交互事件处理器映射表(按精灵 ID 注册)
// ========================================================================
/** 精灵点击事件处理器 { spriteId: handler } */
mouseDownHandlers: {},
/** 精灵长按事件处理器(点击后未移动) */
mouseDownNoMoveHandlers: {},
/** 精灵松开事件处理器 */
mouseUpHandlers: {},
/** 精灵拖拽移动事件处理器 */
mouseMoveHandlers: {},
/** 精灵绘制前事件处理器 */
drawBeginHandlers: {},
/** 精灵绘制事件处理器 */
drawHandlers: {},
/**
* 全局绘制钩子列表:对【每个】绘制事件都会被调用(不按精灵 ID 过滤)。
* 供子游戏注册“需要扫描所有绘制精灵”的通用能力(如标记叠绘),保持框架中立。
*/
drawGlobalHandlers: [],
// ========================================================================
// 事件处理器注册方法
// ========================================================================
registerMouseDown: function(spriteId, handler) {
if (typeof spriteId !== 'number' || typeof handler !== 'function') {
console.error('❌ SpriteEventController: 无效的注册参数');
return false;
}
this.mouseDownHandlers[spriteId] = handler;
return true;
},
registerMouseDownNoMove: function(spriteId, handler) {
if (typeof spriteId !== 'number' || typeof handler !== 'function') {
console.error('❌ SpriteEventController: 无效的注册参数');
return false;
}
this.mouseDownNoMoveHandlers[spriteId] = handler;
return true;
},
registerMouseUp: function(spriteId, handler) {
if (typeof spriteId !== 'number' || typeof handler !== 'function') {
console.error('❌ SpriteEventController: 无效的注册参数');
return false;
}
this.mouseUpHandlers[spriteId] = handler;
return true;
},
registerMouseMove: function(spriteId, handler) {
if (typeof spriteId !== 'number' || typeof handler !== 'function') {
console.error('❌ SpriteEventController: 无效的注册参数');
return false;
}
this.mouseMoveHandlers[spriteId] = handler;
return true;
},
registerDrawBegin: function(spriteId, handler) {
if (typeof spriteId !== 'number' || typeof handler !== 'function') {
console.error('❌ SpriteEventController: 无效的注册参数');
return false;
}
this.drawBeginHandlers[spriteId] = handler;
return true;
},
registerDraw: function(spriteId, handler) {
if (typeof spriteId !== 'number' || typeof handler !== 'function') {
console.error('❌ SpriteEventController: 无效的注册参数');
return false;
}
this.drawHandlers[spriteId] = handler;
return true;
},
/**
* 注册全局绘制钩子(每个绘制事件都会被回调,参数为精灵 ID)。
* 用于子游戏的“扫描所有绘制精灵”类通用处理;框架不认识其业务含义。
* @param {Function} handler - function(spriteId){}
*/
registerGlobalDraw: function(handler) {
if (typeof handler !== 'function') {
console.error('❌ SpriteEventController: 无效的全局绘制钩子');
return false;
}
this.drawGlobalHandlers.push(handler);
return true;
},
// ========================================================================
// 批量注册
// ========================================================================
registerBatch: function(spriteIds, eventType, handler) {
if (!Array.isArray(spriteIds) || typeof eventType !== 'string' || typeof handler !== 'function') {
console.error('❌ SpriteEventController: 批量注册参数错误');
return false;
}
var registerMethod = null;
switch (eventType) {
case 'mouseDown': registerMethod = this.registerMouseDown; break;
case 'mouseDownNoMove': registerMethod = this.registerMouseDownNoMove; break;
case 'mouseUp': registerMethod = this.registerMouseUp; break;
case 'mouseMove': registerMethod = this.registerMouseMove; break;
case 'draw': registerMethod = this.registerDraw; break;
case 'drawBegin': registerMethod = this.registerDrawBegin; break;
default:
console.error('❌ SpriteEventController: 未知的事件类型: ' + eventType);
return false;
}
var successCount = 0;
for (var i = 0; i < spriteIds.length; i++) {
if (registerMethod.call(this, spriteIds[i], handler)) {
successCount++;
}
}
// console.log('📊 批量注册完成: ' + successCount + '/' + spriteIds.length + ' (' + eventType + ')');
return successCount === spriteIds.length;
},
// ========================================================================
// 事件分发处理方法(由 Game_Modify 转调)
// ========================================================================
handleMouseDown: function(gameid, spid, downx, downy, no1, no2, no3, no4, no5, no6) {
var handler = this.mouseDownHandlers[spid];
if (handler) {
try {
handler.call(this, {
gameid: gameid, spriteId: spid, x: downx, y: downy,
params: [no1, no2, no3, no4, no5, no6]
});
return true;
} catch (error) {
console.error('❌ 精灵点击事件处理失败:', error);
return false;
}
}
return false;
},
handleMouseDownNoMove: function(gameid, spid, downx, downy, timelong, no1, no2, no3, no4, no5) {
var handler = this.mouseDownNoMoveHandlers[spid];
if (handler) {
try {
handler.call(this, {
gameid: gameid, spriteId: spid, x: downx, y: downy, duration: timelong,
params: [no1, no2, no3, no4, no5]
});
return true;
} catch (error) {
console.error('❌ 精灵长按事件处理失败:', error);
return false;
}
}
return false;
},
handleMouseUp: function(gameid, spid_down, downx, downy, spid_up, upx, upy, timelong, no1, no2) {
var downHandler = this.mouseUpHandlers[spid_down];
if (downHandler) {
try {
downHandler.call(this, {
gameid: gameid,
downSpriteId: spid_down,
upSpriteId: spid_up,
downPos: { x: downx, y: downy },
upPos: { x: upx, y: upy },
duration: timelong,
isClick: spid_down === spid_up,
params: [no1, no2]
});
return true;
} catch (error) {
console.error('❌ 精灵松开事件处理失败:', error);
return false;
}
}
return false;
},
handleMouseMove: function(gameid, spid, downx, downy, movex, movey, timelong, offmovex, offmovey, no1) {
var handler = this.mouseMoveHandlers[spid];
if (handler) {
try {
handler.call(this, {
gameid: gameid, spriteId: spid,
downPos: { x: downx, y: downy },
currentPos: { x: movex, y: movey },
offset: { x: offmovex, y: offmovey },
duration: timelong,
params: [no1]
});
return true;
} catch (error) {
console.error('❌ 精灵拖拽事件处理失败:', error);
return false;
}
}
return false;
},
handleDrawBegin: function(gameid, spid, times, timelong, no2, no3, no4, no5, no6, no7) {
// 通用裁剪区域应用(DynamicSpriteList 等注册的裁剪)
if (typeof GameABCUtils !== 'undefined' && GameABCUtils.Clip) {
GameABCUtils.Clip.checkAndApply(spid);
}
var handler = this.drawBeginHandlers[spid];
if (handler) {
try {
handler.call(this, {
gameid: gameid, spriteId: spid, times: times, timelong: timelong,
params: [no2, no3, no4, no5, no6, no7]
});
return true;
} catch (error) {
console.error('❌ 精灵绘制前事件处理失败:', error);
return false;
}
}
return false;
},
handleDraw: function(gameid, spid, times, timelong, no2, no3, no4, no5, no6, no7) {
// 先执行全局绘制钩子(子游戏注册的“扫描所有绘制精灵”类处理,框架不认识其业务)
for (var g = 0; g < this.drawGlobalHandlers.length; g++) {
try {
this.drawGlobalHandlers[g](spid);
} catch (gErr) {
console.error('❌ 全局绘制钩子执行失败:', gErr);
}
}
var handler = this.drawHandlers[spid];
if (handler) {
try {
handler.call(this, {
gameid: gameid, spriteId: spid, times: times, timelong: timelong,
params: [no2, no3, no4, no5, no6, no7]
});
return true;
} catch (error) {
console.error('❌ 精灵绘制事件处理失败:', error);
return false;
}
}
return false;
},
// ========================================================================
// 工具方法
// ========================================================================
unregister: function(spriteId, eventType) {
if (typeof spriteId !== 'number') {
console.error('❌ SpriteEventController: 无效的精灵ID');
return false;
}
if (eventType) {
switch (eventType) {
case 'mouseDown': delete this.mouseDownHandlers[spriteId]; break;
case 'mouseDownNoMove': delete this.mouseDownNoMoveHandlers[spriteId]; break;
case 'mouseUp': delete this.mouseUpHandlers[spriteId]; break;
case 'mouseMove': delete this.mouseMoveHandlers[spriteId]; break;
case 'draw': delete this.drawHandlers[spriteId]; break;
case 'drawBegin': delete this.drawBeginHandlers[spriteId]; break;
default:
console.error('❌ SpriteEventController: 未知的事件类型: ' + eventType);
return false;
}
} else {
delete this.mouseDownHandlers[spriteId];
delete this.mouseDownNoMoveHandlers[spriteId];
delete this.mouseUpHandlers[spriteId];
delete this.mouseMoveHandlers[spriteId];
delete this.drawHandlers[spriteId];
delete this.drawBeginHandlers[spriteId];
}
return true;
},
getStats: function() {
return {
mouseDown: Object.keys(this.mouseDownHandlers).length,
mouseDownNoMove: Object.keys(this.mouseDownNoMoveHandlers).length,
mouseUp: Object.keys(this.mouseUpHandlers).length,
mouseMove: Object.keys(this.mouseMoveHandlers).length,
draw: Object.keys(this.drawHandlers).length,
drawBegin: Object.keys(this.drawBeginHandlers).length,
drawGlobal: this.drawGlobalHandlers.length
};
},
clear: function() {
this.mouseDownHandlers = {};
this.mouseDownNoMoveHandlers = {};
this.mouseUpHandlers = {};
this.mouseMoveHandlers = {};
this.drawHandlers = {};
this.drawBeginHandlers = {};
this.drawGlobalHandlers = [];
// console.log('🧹 已清空所有精灵事件处理器');
},
init: function() {
// console.log('🚀 SpriteEventController 已初始化');
var dependencies = ['set_self', 'get_self', 'ifast_mydrawbmp'];
var missing = [];
for (var i = 0; i < dependencies.length; i++) {
if (typeof window[dependencies[i]] === 'undefined') {
missing.push(dependencies[i]);
}
}
if (missing.length > 0) {
console.warn('⚠️ 缺少依赖接口:', missing.join(', '));
console.warn('请确保已正确加载 gameabc.min.js');
}
return missing.length === 0;
}
};
// 自动初始化(浏览器/平台运行时)
if (typeof window !== 'undefined') {
SpriteEventController.init();
}
// Node 测试环境导出
if (typeof module !== 'undefined' && module.exports) {
module.exports = SpriteEventController;
}
@@ -0,0 +1,174 @@
// ============================================================================
// 精灵手势识别器 (gameabc-framework / system, ES5标准)
// 在 SpriteEventController 之上做高层手势识别——游戏中立,可被任意子游戏复用
// ============================================================================
/**
* SpriteGestureRecognizer —— 通用精灵手势识别器
*
* 职责:
* 把底层的「按下/移动/松开」原始事件,识别为高层手势并以语义回调通知:
* - press :按下(即时反馈,如元素站起)——双击的第二击不再触发 press
* - doubleTap :在 doubleTapInterval 内对【同一精灵】快速按下两次
* - swipe :沿指定方向移动超过阈值(不依赖松开,越阈即触发)
* - tap :一次「点击」(小位移按下→松开,且未被 swipe/doubleTap 消费)
*
* 中立原则:
* 本识别器只做手势分类,不含任何业务语义(选中/出牌/牌面等均由子游戏在回调里处理)。
* 阈值参数由调用方注入(doubleTapInterval / swipe.threshold / minMove),框架只给默认值。
*
* 依赖:SpriteEventController(须先加载)。
*
* 用法:
* var handle = SpriteGestureRecognizer.attach(spriteIds, {
* onPress: function (e) {}, // e: { spriteId, x, y, ... }
* onTap: function (e) {}, // e: 透传 mouseUp 事件(含 upSpriteId/downPos/upPos/isClick)
* onDoubleTap: function (e) {}, // e: 透传 mouseDown 事件
* onSwipe: function (e) {} // e: 透传 mouseMove 事件 + e.direction
* }, {
* doubleTapInterval: 300, // 双击间隔(ms)
* swipe: { direction: 'up', threshold: 50 }, // 滑动方向与阈值(像素,正值)
* minMove: 10 // 判定为点击的最大位移(像素)
* });
* // handle.resetDoubleTap() 清双击计时(防跨局误判)
* // handle.resetDrag() 取消进行中的拖动追踪
* // handle.detach() 注销该组精灵的手势识别
*
* 严格 ES5。
*/
var SpriteGestureRecognizer = {
/**
* 为一组精灵附加手势识别。
* @param {Array<number>} spriteIds - 精灵 ID 列表
* @param {Object} callbacks - { onPress, onTap, onDoubleTap, onSwipe }(均可选)
* @param {Object} [options] - { doubleTapInterval, swipe:{direction,threshold}, minMove }
* @returns {Object|null} handle: { detach, resetDoubleTap, resetDrag };参数非法返回 null
*/
attach: function (spriteIds, callbacks, options) {
if (typeof SpriteEventController === 'undefined') {
console.error('❌ SpriteGestureRecognizer: SpriteEventController 未加载');
return null;
}
if (!spriteIds || typeof spriteIds.length !== 'number') {
console.error('❌ SpriteGestureRecognizer: spriteIds 必须为数组');
return null;
}
callbacks = callbacks || {};
options = options || {};
var doubleTapInterval = (options.doubleTapInterval != null) ? options.doubleTapInterval : 300;
var minMove = (options.minMove != null) ? options.minMove : 10;
var swipe = options.swipe || null; // { direction, threshold }
// 本组精灵独立的手势状态(闭包隔离,支持多处 attach 互不干扰)
var state = {
dragActive: false,
lastTapTime: 0,
lastTapTarget: null
};
function _resetDrag() {
state.dragActive = false;
}
// ---- 越阈方向判定(仅在配置了 swipe 时启用)----
function _swipeCrossed(offset) {
if (!swipe || !swipe.direction) { return false; }
var th = (swipe.threshold != null) ? swipe.threshold : 50;
switch (swipe.direction) {
case 'up': return offset.y < -th;
case 'down': return offset.y > th;
case 'left': return offset.x < -th;
case 'right': return offset.x > th;
default: return false;
}
}
// ---- 原始事件处理(注册到 SpriteEventController)----
function _onDown(e) {
var now = Date.now();
var isDouble = state.lastTapTime > 0 &&
e.spriteId === state.lastTapTarget &&
(now - state.lastTapTime) < doubleTapInterval;
if (isDouble) {
// 消费双击:清计时,避免三连击;不再触发 press(与原手牌交互一致)
state.lastTapTime = 0;
state.lastTapTarget = null;
if (callbacks.onDoubleTap) { callbacks.onDoubleTap(e); }
return;
}
// 记录本次按下,开始拖动追踪
state.lastTapTime = now;
state.lastTapTarget = e.spriteId;
state.dragActive = true;
if (callbacks.onPress) { callbacks.onPress(e); }
}
function _onMove(e) {
if (!state.dragActive) { return; }
if (!e.offset) { return; }
if (_swipeCrossed(e.offset)) {
// 越阈即触发 swipe,并结束拖动追踪(后续 move/up 不再产生手势)
_resetDrag();
var se = e;
se.direction = swipe.direction;
if (callbacks.onSwipe) { callbacks.onSwipe(se); }
}
}
function _onUp(e) {
var wasDragging = state.dragActive;
_resetDrag();
if (!wasDragging) { return; } // 未在追踪(如双击第二击/已被 swipe 消费)→ 非 tap
if (!e.isClick) { return; } // 按下与松开不在同一精灵 → 非 tap
// 位移过大(非点击的拖动)→ 非 tap
if (e.upPos && e.downPos && Math.abs(e.upPos.y - e.downPos.y) > minMove) { return; }
if (callbacks.onTap) { callbacks.onTap(e); }
}
// 注册到底层事件控制器
var ids = [];
for (var i = 0; i < spriteIds.length; i++) {
var sid = spriteIds[i];
if (sid == null) { continue; }
SpriteEventController.registerMouseDown(sid, _onDown);
SpriteEventController.registerMouseMove(sid, _onMove);
SpriteEventController.registerMouseUp(sid, _onUp);
ids.push(sid);
}
return {
/** 注销该组精灵的全部手势事件 */
detach: function () {
for (var j = 0; j < ids.length; j++) {
SpriteEventController.unregister(ids[j], 'mouseDown');
SpriteEventController.unregister(ids[j], 'mouseMove');
SpriteEventController.unregister(ids[j], 'mouseUp');
}
_resetDrag();
},
/** 清空双击计时(游戏开始/重置时调用,防跨局误判) */
resetDoubleTap: function () {
state.lastTapTime = 0;
state.lastTapTarget = null;
},
/** 取消进行中的拖动追踪 */
resetDrag: function () {
_resetDrag();
}
};
}
};
// 全局暴露(浏览器/平台运行时)
if (typeof window !== 'undefined') {
window.SpriteGestureRecognizer = SpriteGestureRecognizer;
}
// Node 测试环境导出
if (typeof module !== 'undefined' && module.exports) {
module.exports = SpriteGestureRecognizer;
}
@@ -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; }
@@ -0,0 +1,849 @@
/**
* AlignmentUtils - 精灵对齐工具类
* 文件: client/js/01_SubGame/codes/game/utils/AlignmentUtils.js
*
* 🎯 设计目的:
* 提供统一的精灵对齐计算方法,简化UI布局代码
*
* 🔑 核心原则:
* 1. 精灵锚点永远在左上角,无法更改
* 2. 所有对齐计算都基于锚点位置
* 3. 支持常见的9种对齐方式(左上/中上/右上/左中/中中/右中/左下/中下/右下)
* 4. 支持偏移量微调
*
* 📐 坐标系统:
* - X轴:从左到右递增
* - Y轴:从上到下递增
* - 精灵锚点:左上角 (0, 0)
*
* @author JinXian Team
* @version 1.0.0
* @since 2025-12-30
*/
'use strict';
var AlignmentUtils = {
VERSION: '1.0.0',
/**
* 对齐常量
*/
ALIGN: {
// 水平对齐
LEFT: 'left', // 左对齐
CENTER: 'center', // 水平居中
RIGHT: 'right', // 右对齐
// 垂直对齐
TOP: 'top', // 顶部对齐
MIDDLE: 'middle', // 垂直居中
BOTTOM: 'bottom' // 底部对齐
},
/**
* 将精灵对齐到目标矩形区域
*
* 🔑 核心:精灵的锚点永远在左上角,此方法计算锚点应放置的位置
*
* @param {Object} target - 目标矩形区域
* @param {number} target.x - 目标区域左上角X坐标
* @param {number} target.y - 目标区域左上角Y坐标
* @param {number} target.width - 目标区域宽度
* @param {number} target.height - 目标区域高度
*
* @param {Object} sprite - 精灵信息(精灵锚点固定在左上角)
* @param {number} sprite.width - 精灵宽度
* @param {number} sprite.height - 精灵高度
*
* @param {string} horizontalAlign - 水平对齐方式: 'left'|'center'|'right'
* @param {string} verticalAlign - 垂直对齐方式: 'top'|'middle'|'bottom'
*
* @param {Object} [offset] - 偏移量(可选)
* @param {number} [offset.x=0] - X轴偏移
* @param {number} [offset.y=0] - Y轴偏移
*
* @returns {Object} 精灵锚点位置(左上角) {x, y}
*
* @example
* // 将文本标签对齐到矩形框左下角
* var labelPos = AlignmentUtils.alignTo(
* { x: 10, y: 10, width: 60, height: 60 }, // 矩形框区域
* { width: 40, height: 20 }, // 标签大小
* 'left', // 左对齐
* 'bottom', // 底部对齐
* { x: 0, y: 5 } // 向下偏移5px
* );
* // 返回: { x: 10, y: 75 } (矩形底部 + 偏移,注意精灵锚点在左上角)
*
* @example
* // 将图标对齐到矩形框右下角
* var iconPos = AlignmentUtils.alignTo(
* { x: 10, y: 10, width: 60, height: 60 }, // 矩形框区域
* { width: 30, height: 30 }, // 图标大小
* 'right', // 右对齐
* 'bottom', // 底部对齐
* { x: -5, y: 5 } // 向左偏移5px,向下偏移5px
* );
* // 返回: { x: 35, y: 75 } (矩形右边界 - 图标宽度 - 5px,精灵锚点在左上角)
*/
alignTo: function(target, sprite, horizontalAlign, verticalAlign, offset) {
offset = offset || {};
var offsetX = offset.x || 0;
var offsetY = offset.y || 0;
var x, y;
// 计算水平位置
switch (horizontalAlign) {
case this.ALIGN.LEFT:
// 左对齐:精灵左边缘与目标左边缘对齐
x = target.x;
break;
case this.ALIGN.CENTER:
// 水平居中:精灵中心与目标中心对齐
// 🔑 精灵的锚点永远在左上角,所以需要减去精灵宽度的一半
x = target.x + (target.width - sprite.width) / 2;
break;
case this.ALIGN.RIGHT:
// 右对齐:精灵右边缘与目标右边缘对齐
// 🔑 精灵的锚点永远在左上角,所以需要减去精灵宽度
x = target.x + target.width - sprite.width;
break;
default:
console.warn('[AlignmentUtils] 未知的水平对齐方式:', horizontalAlign);
x = target.x;
}
// 计算垂直位置
switch (verticalAlign) {
case this.ALIGN.TOP:
// 顶部对齐:精灵顶边与目标顶边对齐
y = target.y;
break;
case this.ALIGN.MIDDLE:
// 垂直居中:精灵中心与目标中心对齐
// 🔑 精灵的锚点永远在左上角,所以需要减去精灵高度的一半
y = target.y + (target.height - sprite.height) / 2;
break;
case this.ALIGN.BOTTOM:
// 底部对齐:精灵底边与目标底边对齐
// 🔑 精灵的锚点永远在左上角,所以需要加上目标高度
y = target.y + target.height;
break;
default:
console.warn('[AlignmentUtils] 未知的垂直对齐方式:', verticalAlign);
y = target.y;
}
// 应用偏移
return {
x: x + offsetX,
y: y + offsetY
};
},
/**
* 将精灵对齐到目标矩形的左下角(快捷方法)
*
* 🔑 注意:精灵的锚点永远在左上角
*
* @param {Object} target - 目标矩形区域
* @param {number} target.x - 目标区域左上角X坐标
* @param {number} target.y - 目标区域左上角Y坐标
* @param {number} target.width - 目标区域宽度
* @param {number} target.height - 目标区域高度
*
* @param {Object} sprite - 精灵大小(锚点固定在左上角)
* @param {number} sprite.width - 精灵宽度
* @param {number} sprite.height - 精灵高度
*
* @param {Object} [offset] - 偏移量
* @param {number} [offset.x=0] - X偏移
* @param {number} [offset.y=0] - Y偏移
*
* @returns {Object} 精灵锚点位置(左上角) {x, y}
*
* @example
* // 将文本标签对齐到容器左下角
* var pos = AlignmentUtils.alignBottomLeft(
* { x: 10, y: 10, width: 60, height: 60 }, // 容器区域
* { width: 40, height: 20 }, // 标签大小
* { x: 0, y: 5 } // 向下偏移5px
* );
*/
alignBottomLeft: function(target, sprite, offset) {
return this.alignTo(target, sprite, this.ALIGN.LEFT, this.ALIGN.BOTTOM, offset);
},
/**
* 将精灵对齐到目标矩形的右下角(快捷方法)
*
* 🔑 注意:精灵的锚点永远在左上角,右对齐时会自动减去精灵宽度
*
* @param {Object} target - 目标矩形区域
* @param {Object} sprite - 精灵大小(锚点固定在左上角)
* @param {Object} [offset] - 偏移量
* @returns {Object} 精灵锚点位置(左上角) {x, y}
*
* @example
* // 将图标对齐到容器右下角
* var pos = AlignmentUtils.alignBottomRight(
* { x: 10, y: 10, width: 60, height: 60 }, // 容器区域
* { width: 30, height: 30 }, // 图标大小
* { x: -5, y: 5 } // 向左偏移5px,向下偏移5px
* );
*/
alignBottomRight: function(target, sprite, offset) {
return this.alignTo(target, sprite, this.ALIGN.RIGHT, this.ALIGN.BOTTOM, offset);
},
/**
* 将精灵对齐到目标矩形的顶部居中(快捷方法)
*
* 🔑 注意:精灵的锚点永远在左上角,居中时会自动减去精灵宽度的一半
*
* @param {Object} target - 目标矩形区域
* @param {Object} sprite - 精灵大小(锚点固定在左上角)
* @param {Object} [offset] - 偏移量
* @returns {Object} 精灵锚点位置(左上角) {x, y}
*/
alignTopCenter: function(target, sprite, offset) {
return this.alignTo(target, sprite, this.ALIGN.CENTER, this.ALIGN.TOP, offset);
},
/**
* 将精灵对齐到目标矩形的底部居中(快捷方法)
*
* 🔑 注意:精灵的锚点永远在左上角,居中时会自动减去精灵宽度的一半
*
* @param {Object} target - 目标矩形区域
* @param {Object} sprite - 精灵大小(锚点固定在左上角)
* @param {Object} [offset] - 偏移量
* @returns {Object} 精灵锚点位置(左上角) {x, y}
*/
alignBottomCenter: function(target, sprite, offset) {
return this.alignTo(target, sprite, this.ALIGN.CENTER, this.ALIGN.BOTTOM, offset);
},
/**
* 将精灵对齐到目标矩形的中心(快捷方法)
*
* 🔑 注意:精灵的锚点永远在左上角,居中时会自动减去精灵宽度和高度的一半
*
* @param {Object} target - 目标矩形区域
* @param {Object} sprite - 精灵大小(锚点固定在左上角)
* @param {Object} [offset] - 偏移量
* @returns {Object} 精灵锚点位置(左上角) {x, y}
*/
alignCenter: function(target, sprite, offset) {
return this.alignTo(target, sprite, this.ALIGN.CENTER, this.ALIGN.MIDDLE, offset);
},
// ============================================================================
// 角落对齐方法 - 精灵的角与目标的角重合
// ============================================================================
/**
* 将精灵的左上角对齐到目标的左上角
*
* 🔑 注意:精灵的锚点永远在左上角
* 💡 角落对齐:精灵的角与目标的角重合(不是边缘对齐)
*
* @param {Object} target - 目标矩形区域
* @param {Object} sprite - 精灵大小(锚点固定在左上角)
* @param {Object} [offset] - 偏移量
* @returns {Object} 精灵锚点位置(左上角) {x, y}
*
* @example
* // 将子元素的左上角与容器的左上角重合
* var pos = AlignmentUtils.alignCornerTopLeft(
* { x: 10, y: 10, width: 100, height: 100 },
* { width: 30, height: 30 }
* );
* // 返回: { x: 10, y: 10 }
*/
alignCornerTopLeft: function(target, sprite, offset) {
offset = offset || {};
return {
x: target.x + (offset.x || 0),
y: target.y + (offset.y || 0)
};
},
/**
* 将精灵的右上角对齐到目标的右上角
*
* 🔑 注意:精灵的锚点永远在左上角,需要减去精灵宽度
* 💡 角落对齐:精灵的角与目标的角重合
*
* @param {Object} target - 目标矩形区域
* @param {Object} sprite - 精灵大小(锚点固定在左上角)
* @param {Object} [offset] - 偏移量
* @returns {Object} 精灵锚点位置(左上角) {x, y}
*
* @example
* var pos = AlignmentUtils.alignCornerTopRight(
* { x: 10, y: 10, width: 100, height: 100 },
* { width: 30, height: 30 }
* );
* // 返回: { x: 80, y: 10 } (10 + 100 - 30)
*/
alignCornerTopRight: function(target, sprite, offset) {
offset = offset || {};
return {
x: target.x + target.width - sprite.width + (offset.x || 0),
y: target.y + (offset.y || 0)
};
},
/**
* 将精灵的左下角对齐到目标的左下角
*
* 🔑 注意:精灵的锚点永远在左上角,需要减去精灵高度
* 💡 角落对齐:精灵的角与目标的角重合
*
* @param {Object} target - 目标矩形区域
* @param {Object} sprite - 精灵大小(锚点固定在左上角)
* @param {Object} [offset] - 偏移量
* @returns {Object} 精灵锚点位置(左上角) {x, y}
*
* @example
* var pos = AlignmentUtils.alignCornerBottomLeft(
* { x: 10, y: 10, width: 100, height: 100 },
* { width: 30, height: 30 }
* );
* // 返回: { x: 10, y: 80 } (10 + 100 - 30)
*/
alignCornerBottomLeft: function(target, sprite, offset) {
offset = offset || {};
return {
x: target.x + (offset.x || 0),
y: target.y + target.height - sprite.height + (offset.y || 0)
};
},
/**
* 将精灵的右下角对齐到目标的右下角
*
* 🔑 注意:精灵的锚点永远在左上角,需要减去精灵宽度和高度
* 💡 角落对齐:精灵的角与目标的角重合
*
* @param {Object} target - 目标矩形区域
* @param {Object} sprite - 精灵大小(锚点固定在左上角)
* @param {Object} [offset] - 偏移量
* @returns {Object} 精灵锚点位置(左上角) {x, y}
*
* @example
* var pos = AlignmentUtils.alignCornerBottomRight(
* { x: 10, y: 10, width: 100, height: 100 },
* { width: 30, height: 30 }
* );
* // 返回: { x: 80, y: 80 }
*/
alignCornerBottomRight: function(target, sprite, offset) {
offset = offset || {};
return {
x: target.x + target.width - sprite.width + (offset.x || 0),
y: target.y + target.height - sprite.height + (offset.y || 0)
};
},
/**
* 计算多个精灵水平居中的起始X坐标
*
* @param {number} containerWidth - 容器宽度
* @param {number} totalItemsWidth - 所有项目的总宽度(包括间距)
* @returns {number} 起始X坐标
*
* @example
* // 3张牌,每张42px,间距5px,容器宽度600px
* var startX = AlignmentUtils.centerMultipleItems(600, 42 * 3 + 5 * 2);
* // 返回: 258 ((600 - 136) / 2)
*/
centerMultipleItems: function(containerWidth, totalItemsWidth) {
return (containerWidth - totalItemsWidth) / 2;
},
/**
* 计算矩形区域的中心点
*
* @param {Object} rect - 矩形区域
* @param {number} rect.x - 左上角X
* @param {number} rect.y - 左上角Y
* @param {number} rect.width - 宽度
* @param {number} rect.height - 高度
* @returns {Object} 中心点 {x, y}
*/
getCenter: function(rect) {
return {
x: rect.x + rect.width / 2,
y: rect.y + rect.height / 2
};
},
// ============================================================================
// 精灵ID版本 - 自动获取精灵尺寸
// ============================================================================
/**
* 将精灵对齐到目标精灵(使用精灵ID,自动获取尺寸)
*
* 🔑 核心:精灵的锚点永远在左上角,此方法计算锚点应放置的位置
* 💡 优点:无需手动获取精灵尺寸,代码更简洁
*
* @param {number} targetSpriteId - 目标精灵ID(会自动获取位置和大小)
* @param {number} spriteId - 要对齐的精灵ID(会自动获取大小)
* @param {string} horizontalAlign - 水平对齐方式: 'left'|'center'|'right'
* @param {string} verticalAlign - 垂直对齐方式: 'top'|'middle'|'bottom'
* @param {Object} [offset] - 偏移量(可选)
* @param {number} [offset.x=0] - X轴偏移
* @param {number} [offset.y=0] - Y轴偏移
*
* @returns {Object|null} 精灵锚点位置(左上角) {x, y},失败返回null
*
* @example
* // 将座位标签(精灵5678)对齐到头像(精灵1234)左下角
* var pos = AlignmentUtils.alignSpriteToSprite(
* 1234, // 目标精灵ID(头像)
* 5678, // 要对齐的精灵ID(座位标签)
* 'left', // 左对齐
* 'bottom', // 底部对齐
* { x: 0, y: 5 } // 向下偏移5px
* );
* if (pos) {
* SpriteManager.setPosition(5678, pos.x, pos.y);
* }
*/
alignSpriteToSprite: function(targetSpriteId, spriteId, horizontalAlign, verticalAlign, offset) {
if (typeof SpriteManager === 'undefined') {
console.error('[AlignmentUtils] SpriteManager 未加载,无法使用 alignSpriteToSprite');
return null;
}
// 获取目标精灵的位置和大小
var targetPos = SpriteManager.getPosition(targetSpriteId);
var targetSize = SpriteManager.getSize(targetSpriteId);
if (!targetPos || !targetSize) {
console.error('[AlignmentUtils] 无法获取目标精灵 ' + targetSpriteId + ' 的位置或大小');
return null;
}
// 获取要对齐的精灵的大小
var spriteSize = SpriteManager.getSize(spriteId);
if (!spriteSize) {
console.error('[AlignmentUtils] 无法获取精灵 ' + spriteId + ' 的大小');
return null;
}
// 构建目标矩形
var target = {
x: targetPos.x,
y: targetPos.y,
width: targetSize.width,
height: targetSize.height
};
// 调用核心对齐方法
return this.alignTo(target, spriteSize, horizontalAlign, verticalAlign, offset);
},
/**
* 将精灵对齐到目标精灵的左下角(快捷方法)
*
* 🔑 注意:精灵的锚点永远在左上角
* 💡 自动获取精灵尺寸,无需手动传入
*
* @param {number} targetSpriteId - 目标精灵ID
* @param {number} spriteId - 要对齐的精灵ID
* @param {Object} [offset] - 偏移量
* @returns {Object|null} 精灵锚点位置(左上角) {x, y}
*
* @example
* var pos = AlignmentUtils.alignSpriteBottomLeft(1234, 5678, { x: 0, y: 5 });
* if (pos) SpriteManager.setPosition(5678, pos.x, pos.y);
*/
alignSpriteBottomLeft: function(targetSpriteId, spriteId, offset) {
return this.alignSpriteToSprite(targetSpriteId, spriteId, this.ALIGN.LEFT, this.ALIGN.BOTTOM, offset);
},
/**
* 将精灵对齐到目标精灵的右下角(快捷方法)
*
* 🔑 注意:精灵的锚点永远在左上角,右对齐时会自动减去精灵宽度
* 💡 自动获取精灵尺寸,无需手动传入
*
* @param {number} targetSpriteId - 目标精灵ID
* @param {number} spriteId - 要对齐的精灵ID
* @param {Object} [offset] - 偏移量
* @returns {Object|null} 精灵锚点位置(左上角) {x, y}
*
* @example
* var pos = AlignmentUtils.alignSpriteBottomRight(1234, 5678, { x: -5, y: 5 });
* if (pos) SpriteManager.setPosition(5678, pos.x, pos.y);
*/
alignSpriteBottomRight: function(targetSpriteId, spriteId, offset) {
return this.alignSpriteToSprite(targetSpriteId, spriteId, this.ALIGN.RIGHT, this.ALIGN.BOTTOM, offset);
},
/**
* 将精灵对齐到目标精灵的顶部居中(快捷方法)
*
* 🔑 注意:精灵的锚点永远在左上角,居中时会自动减去精灵宽度的一半
* 💡 自动获取精灵尺寸,无需手动传入
*
* @param {number} targetSpriteId - 目标精灵ID
* @param {number} spriteId - 要对齐的精灵ID
* @param {Object} [offset] - 偏移量
* @returns {Object|null} 精灵锚点位置(左上角) {x, y}
*/
alignSpriteTopCenter: function(targetSpriteId, spriteId, offset) {
return this.alignSpriteToSprite(targetSpriteId, spriteId, this.ALIGN.CENTER, this.ALIGN.TOP, offset);
},
/**
* 将精灵对齐到目标精灵的底部居中(快捷方法)
*
* 🔑 注意:精灵的锚点永远在左上角,居中时会自动减去精灵宽度的一半
* 💡 自动获取精灵尺寸,无需手动传入
*
* @param {number} targetSpriteId - 目标精灵ID
* @param {number} spriteId - 要对齐的精灵ID
* @param {Object} [offset] - 偏移量
* @returns {Object|null} 精灵锚点位置(左上角) {x, y}
*/
alignSpriteBottomCenter: function(targetSpriteId, spriteId, offset) {
return this.alignSpriteToSprite(targetSpriteId, spriteId, this.ALIGN.CENTER, this.ALIGN.BOTTOM, offset);
},
/**
* 将精灵对齐到目标精灵的中心(快捷方法)
*
* 🔑 注意:精灵的锚点永远在左上角,居中时会自动减去精灵宽度和高度的一半
* 💡 自动获取精灵尺寸,无需手动传入
*
* @param {number} targetSpriteId - 目标精灵ID
* @param {number} spriteId - 要对齐的精灵ID
* @param {Object} [offset] - 偏移量
* @returns {Object|null} 精灵锚点位置(左上角) {x, y}
*/
alignSpriteCenter: function(targetSpriteId, spriteId, offset) {
return this.alignSpriteToSprite(targetSpriteId, spriteId, this.ALIGN.CENTER, this.ALIGN.MIDDLE, offset);
},
// ============================================================================
// 精灵ID版本 - 角落对齐
// ============================================================================
/**
* 将精灵的左上角对齐到目标精灵的左上角
*
* 🔑 注意:精灵的锚点永远在左上角
* 💡 角落对齐:精灵的角与目标的角重合
*
* @param {number} targetSpriteId - 目标精灵ID
* @param {number} spriteId - 要对齐的精灵ID
* @param {Object} [offset] - 偏移量
* @returns {Object|null} 精灵锚点位置(左上角) {x, y}
*/
alignSpriteCornerTopLeft: function(targetSpriteId, spriteId, offset) {
if (typeof SpriteManager === 'undefined') {
console.error('[AlignmentUtils] SpriteManager 未加载');
return null;
}
var targetPos = SpriteManager.getPosition(targetSpriteId);
var spriteSize = SpriteManager.getSize(spriteId);
if (!targetPos || !spriteSize) {
console.error('[AlignmentUtils] 无法获取精灵位置或大小');
return null;
}
offset = offset || {};
return {
x: targetPos.x + (offset.x || 0),
y: targetPos.y + (offset.y || 0)
};
},
/**
* 将精灵的右上角对齐到目标精灵的右上角
*
* 🔑 注意:精灵的锚点永远在左上角,需要减去精灵宽度
* 💡 角落对齐:精灵的角与目标的角重合
*
* @param {number} targetSpriteId - 目标精灵ID
* @param {number} spriteId - 要对齐的精灵ID
* @param {Object} [offset] - 偏移量
* @returns {Object|null} 精灵锚点位置(左上角) {x, y}
*/
alignSpriteCornerTopRight: function(targetSpriteId, spriteId, offset) {
if (typeof SpriteManager === 'undefined') {
console.error('[AlignmentUtils] SpriteManager 未加载');
return null;
}
var targetPos = SpriteManager.getPosition(targetSpriteId);
var targetSize = SpriteManager.getSize(targetSpriteId);
var spriteSize = SpriteManager.getSize(spriteId);
if (!targetPos || !targetSize || !spriteSize) {
console.error('[AlignmentUtils] 无法获取精灵位置或大小');
return null;
}
offset = offset || {};
return {
x: targetPos.x + targetSize.width - spriteSize.width + (offset.x || 0),
y: targetPos.y + (offset.y || 0)
};
},
/**
* 将精灵的左下角对齐到目标精灵的左下角
*
* 🔑 注意:精灵的锚点永远在左上角,需要减去精灵高度
* 💡 角落对齐:精灵的角与目标的角重合
*
* @param {number} targetSpriteId - 目标精灵ID
* @param {number} spriteId - 要对齐的精灵ID
* @param {Object} [offset] - 偏移量
* @returns {Object|null} 精灵锚点位置(左上角) {x, y}
*/
alignSpriteCornerBottomLeft: function(targetSpriteId, spriteId, offset) {
if (typeof SpriteManager === 'undefined') {
console.error('[AlignmentUtils] SpriteManager 未加载');
return null;
}
var targetPos = SpriteManager.getPosition(targetSpriteId);
var targetSize = SpriteManager.getSize(targetSpriteId);
var spriteSize = SpriteManager.getSize(spriteId);
if (!targetPos || !targetSize || !spriteSize) {
console.error('[AlignmentUtils] 无法获取精灵位置或大小');
return null;
}
offset = offset || {};
return {
x: targetPos.x + (offset.x || 0),
y: targetPos.y + targetSize.height - spriteSize.height + (offset.y || 0)
};
},
/**
* 将精灵的右下角对齐到目标精灵的右下角
*
* 🔑 注意:精灵的锚点永远在左上角,需要减去精灵宽度和高度
* 💡 角落对齐:精灵的角与目标的角重合
*
* @param {number} targetSpriteId - 目标精灵ID
* @param {number} spriteId - 要对齐的精灵ID
* @param {Object} [offset] - 偏移量
* @returns {Object|null} 精灵锚点位置(左上角) {x, y}
*/
alignSpriteCornerBottomRight: function(targetSpriteId, spriteId, offset) {
if (typeof SpriteManager === 'undefined') {
console.error('[AlignmentUtils] SpriteManager 未加载');
return null;
}
var targetPos = SpriteManager.getPosition(targetSpriteId);
var targetSize = SpriteManager.getSize(targetSpriteId);
var spriteSize = SpriteManager.getSize(spriteId);
if (!targetPos || !targetSize || !spriteSize) {
console.error('[AlignmentUtils] 无法获取精灵位置或大小');
return null;
}
offset = offset || {};
return {
x: targetPos.x + targetSize.width - spriteSize.width + (offset.x || 0),
y: targetPos.y + targetSize.height - spriteSize.height + (offset.y || 0)
};
},
// ============================================================================
// 批量精灵分布对齐
// ============================================================================
/**
* 计算多个精灵水平分布的位置
*
* 🔑 核心:精灵的锚点永远在左上角
* 💡 用途:将多个相同大小的精灵水平排列,支持居中/左对齐/右对齐
*
* @param {Object} options - 分布选项
* @param {number} options.anchorX - 锚点X坐标(对齐基准点)
* @param {number} options.anchorY - 锚点Y坐标(所有精灵的Y坐标)
* @param {number} options.count - 精灵数量
* @param {number} options.itemWidth - 每个精灵的宽度
* @param {number} options.itemHeight - 每个精灵的高度
* @param {number} [options.spacing=0] - 精灵之间的间距
* @param {string} [options.anchor='center'] - 对齐方式: 'left'|'center'|'right'
*
* @returns {Array<Object>} 每个精灵的位置数组 [{x, y}, ...]
*
* @example
* // 3张牌水平居中排列
* var positions = AlignmentUtils.distributeHorizontally({
* anchorX: 640, // 屏幕中心X
* anchorY: 500, // Y位置
* count: 3, // 3张牌
* itemWidth: 62, // 牌宽
* itemHeight: 86, // 牌高
* spacing: 0, // 无间距
* anchor: 'center' // 居中对齐
* });
* // 返回: [{x: 547, y: 500}, {x: 609, y: 500}, {x: 671, y: 500}]
*/
distributeHorizontally: function(options) {
var anchorX = options.anchorX || 0;
var anchorY = options.anchorY || 0;
var count = options.count || 0;
var itemWidth = options.itemWidth || 0;
var itemHeight = options.itemHeight || 0;
var spacing = options.spacing || 0;
var anchor = options.anchor || 'center';
if (count <= 0) return [];
// 计算总宽度
var totalWidth = count * itemWidth + (count - 1) * spacing;
// 计算起始X位置(精灵锚点在左上角)
var startX;
switch (anchor) {
case 'left':
// 左对齐:第一个精灵的左边缘与锚点对齐
startX = anchorX;
break;
case 'right':
// 右对齐:最后一个精灵的右边缘与锚点对齐
startX = anchorX - totalWidth;
break;
case 'center':
default:
// 居中:精灵组的中心与锚点对齐
startX = anchorX - totalWidth / 2;
break;
}
// 生成每个精灵的位置
var positions = [];
for (var i = 0; i < count; i++) {
positions.push({
x: startX + i * (itemWidth + spacing),
y: anchorY
});
}
return positions;
},
/**
* 计算多个精灵垂直分布的位置
*
* 🔑 核心:精灵的锚点永远在左上角
* 💡 用途:将多个相同大小的精灵垂直排列,支持居中/顶部对齐/底部对齐
*
* @param {Object} options - 分布选项
* @param {number} options.anchorX - 锚点X坐标(所有精灵的X坐标)
* @param {number} options.anchorY - 锚点Y坐标(对齐基准点)
* @param {number} options.count - 精灵数量
* @param {number} options.itemWidth - 每个精灵的宽度
* @param {number} options.itemHeight - 每个精灵的高度
* @param {number} [options.spacing=0] - 精灵之间的间距
* @param {string} [options.anchor='center'] - 对齐方式: 'top'|'center'|'bottom'
*
* @returns {Array<Object>} 每个精灵的位置数组 [{x, y}, ...]
*
* @example
* // 3张牌垂直居中排列
* var positions = AlignmentUtils.distributeVertically({
* anchorX: 100, // X位置
* anchorY: 360, // 屏幕中心Y
* count: 3, // 3张牌
* itemWidth: 62, // 牌宽
* itemHeight: 86, // 牌高
* spacing: 2, // 2px间距
* anchor: 'center' // 居中对齐
* });
*/
distributeVertically: function(options) {
var anchorX = options.anchorX || 0;
var anchorY = options.anchorY || 0;
var count = options.count || 0;
var itemWidth = options.itemWidth || 0;
var itemHeight = options.itemHeight || 0;
var spacing = options.spacing || 0;
var anchor = options.anchor || 'center';
if (count <= 0) return [];
// 计算总高度
var totalHeight = count * itemHeight + (count - 1) * spacing;
// 计算起始Y位置(精灵锚点在左上角)
var startY;
switch (anchor) {
case 'top':
// 顶部对齐:第一个精灵的顶边与锚点对齐
startY = anchorY;
break;
case 'bottom':
// 底部对齐:最后一个精灵的底边与锚点对齐
startY = anchorY - totalHeight;
break;
case 'center':
default:
// 居中:精灵组的中心与锚点对齐
startY = anchorY - totalHeight / 2;
break;
}
// 生成每个精灵的位置
var positions = [];
for (var i = 0; i < count; i++) {
positions.push({
x: anchorX,
y: startY + i * (itemHeight + spacing)
});
}
return positions;
},
/**
* 计算多个精灵分布的位置(自动选择水平或垂直)
*
* @param {Object} options - 分布选项
* @param {string} [options.direction='horizontal'] - 分布方向: 'horizontal'|'vertical'
* @param {number} options.anchorX - 锚点X坐标
* @param {number} options.anchorY - 锚点Y坐标
* @param {number} options.count - 精灵数量
* @param {number} options.itemWidth - 每个精灵的宽度
* @param {number} options.itemHeight - 每个精灵的高度
* @param {number} [options.spacing=0] - 精灵之间的间距
* @param {string} [options.anchor='center'] - 对齐方式
*
* @returns {Array<Object>} 每个精灵的位置数组 [{x, y}, ...]
*/
distribute: function(options) {
var direction = options.direction || 'horizontal';
if (direction === 'vertical') {
return this.distributeVertically(options);
} else {
return this.distributeHorizontally(options);
}
}
};
// ============================================================================
// 导出
// ============================================================================
if (typeof module !== 'undefined' && module.exports) {
module.exports = AlignmentUtils;
} else if (typeof window !== 'undefined') {
window.AlignmentUtils = AlignmentUtils;
}
@@ -0,0 +1,640 @@
/**
* BaseComponent - UI组件基类
*
* 职责:
* 1. 提供统一的组件生命周期管理 (init/show/hide/destroy)
* 2. 自动管理EventBus事件监听的注册和注销
* 3. 自动管理组件内精灵的创建和销毁
* 4. 提供钩子方法供子类覆盖
*
* 使用方式:
* ```javascript
* var MyComponent = Object.create(BaseComponent);
*
* MyComponent.init = function(config) {
* this.layer = config.layer;
* // 创建精灵
* var spriteId = SpriteManager.createSprite({...});
* this.addSprite(spriteId);
* // 注册事件
* this.addEventListener('eventName', this.onEvent);
* };
*
* var instance = BaseComponent.create({ name: 'MyComponent', layer: 101 });
* instance.show();
* ```
*
* 依赖:
* - EventBus (必须先加载)
* - SpriteManager (必须先加载)
*
* ============================================================================
* @guide 开发指南 - BaseComponent 两种继承模式
* ============================================================================
*
* 【模式一:Object.create(推荐用于复杂视图,多图层组件)】
*
* var GameView = Object.create(BaseComponent);
*
* GameView.init = function(config) {
* // ⚠️ 必须手动重新声明所有实例属性(避免原型污染)
* this.sprites = [];
* this.eventListeners = {};
* this.isVisible = false;
* this.isInitialized = false;
* this.isDestroyed = false;
*
* this.name = config.name || 'GameView';
* this.layer = config.layer || 102; // BaseComponent.show() 需要此属性
* this.baseLayers = [102, 103, 104, 105]; // 多图层组件:show/hide 会操作所有图层
*
* // 注册 EventBus 监听(使用 this.addEventListener 自动管理清理)
* var self = this;
* this.addEventListener(EventBus.Events.GAME_STARTED, function(data) {
* self._onGameStarted(data);
* });
*
* // 注册精灵交互事件(通过 SpriteEventController)
* SpriteEventController.registerMouseUp(sprites.CLOSE_BTN, function(event) {
* self.hide();
* });
*
* this.isInitialized = true;
* };
*
* // 在 UIBootstrap 中创建并使用(注意:直接调用 init,不用 create)
* GameView.init({ name: 'GameView', layer: 102 });
*
* 【模式二:BaseComponent.create(推荐用于简单单图层组件)】
*
* // create 内部会调用 Object.create(BaseComponent) + 调用 init
* var myComp = BaseComponent.create({ name: 'MyComp', layer: 101 });
* myComp.show();
*
* 【何时用 Object.create vs BaseComponent.create】
* - 需要多图层(baseLayers) → Object.create
* - 有大量自定义数据属性(this.data = {}) → Object.create
* - 需要自定义 show/hide 逻辑 → Object.create
* - 简单显隐控制,无额外逻辑 → BaseComponent.create
*
* 【生命周期钩子】
* GameView.onShow = function() { ... }; // show() 时调用
* GameView.onHide = function() { ... }; // hide() 时调用
* GameView.onDestroy = function() { ... }; // destroy() 时调用(清理timer等)
*
* 【destroy 行为说明】
* - 自动调用 EventBus.off 清理所有通过 addEventListener 注册的监听
* - 精灵只隐藏(hide),不删除(gameabc 不支持运行时删除静态精灵)
* - 动态复制的精灵(SpriteCopyUtils)需在 onDestroy 中手动 remove
*
* 【模式三:IIFE 包裹 + 自定义 create 工厂(RoomView 模式)】
*
* var RoomView = (function() {
* 'use strict';
* var RoomView = Object.create(BaseComponent);
*
* RoomView.init = function(config) {
* // 同样需要手动重声明实例属性
* this.sprites = [];
* this.eventListeners = {};
* this.isVisible = false;
* this.isInitialized = false;
* this.isDestroyed = false;
* this.name = config.name;
* this.layer = config.layer;
* };
*
* // 自定义工厂方法(代替 BaseComponent.create,用于多步配置前创建实例)
* RoomView.create = function(config) {
* var instance = Object.create(RoomView);
* config = config || {};
* config.layer = ROOM_VIEW.LAYER; // 固定图层,外部无需传
* // 显式调用 BaseComponent._init,内部会自动调用 instance.init(config)
* BaseComponent._init.call(instance, config);
* return instance;
* };
*
* return RoomView;
* })();
*
* // 使用:通过工厂创建实例
* var roomView = RoomView.create({ name: 'RoomView' });
* roomView.show();
*
* 【super 方法调用(扩展父类方法时)】
*
* RoomView.getState = function() {
* // 调用 BaseComponent.getState 获取基础状态,再添加子类特有字段
* var state = BaseComponent.getState.call(this);
* state.hasRoomData = !!this.roomData;
* state.playerCount = this._getPlayerCount();
* return state;
* };
*
* 【show/hide 钩子(GameView 中使用的扩展钩子)】
*
* // 除了 onShow/onHide/onDestroy,GameView 还使用了更细粒度的钩子:
* GameView.onBeforeShow = function() { ... }; // show() 执行群组操作之前
* GameView.onAfterShow = function() { ... }; // show() 执行群组操作之后
* GameView.onBeforeHide = function() { ... }; // hide() 执行群组操作之前
* GameView.onAfterHide = function() { ... }; // hide() 执行群组操作之后
* // 这些钩子在 show()/hide() 内部通过 if (this.onAfterShow) this.onAfterShow() 调用
*
* @version 1.0.0
* @date 2025-11-19
*/
var BaseComponent = (function() {
'use strict';
// ==================== 组件基类定义 ====================
var BaseComponentProto = {
/**
* 组件名称
* @type {String}
*/
name: '',
/**
* 组件所在图层
* @type {Number}
*/
layer: 0,
/**
* 组件拥有的精灵ID列表
* @type {Array<Number>}
*/
sprites: null,
/**
* 组件注册的事件监听器
* 结构: { eventName: wrappedHandler }
* @type {Object}
*/
eventListeners: null,
/**
* 组件是否可见
* @type {Boolean}
*/
isVisible: false,
/**
* 组件是否已初始化
* @type {Boolean}
*/
isInitialized: false,
/**
* 组件是否已销毁
* @type {Boolean}
*/
isDestroyed: false,
/**
* 组件配置
* @type {Object}
*/
config: null,
// ==================== 内部方法 ====================
/**
* 内部初始化方法
* @param {Object} config - 配置对象
* @param {String} config.name - 组件名称
* @param {Number} config.layer - 图层ID
* @private
*/
_init: function(config) {
if (this.isInitialized) {
console.warn('⚠️ BaseComponent: 组件已初始化,跳过重复初始化:', this.name);
return;
}
// 验证必需参数
if (!config || typeof config !== 'object') {
console.error('❌ BaseComponent._init: config必须是对象');
return;
}
if (!config.name) {
console.error('❌ BaseComponent._init: 缺少name参数');
return;
}
if (typeof config.layer !== 'number') {
console.error('❌ BaseComponent._init: 缺少layer参数或类型错误');
return;
}
// 初始化属性
this.name = config.name;
this.layer = config.layer;
this.config = config;
this.sprites = [];
this.eventListeners = {};
this.isVisible = false;
this.isDestroyed = false;
this.isInitialized = true;
console.log('🎨 BaseComponent: 初始化组件 "' + this.name + '" (图层: ' + this.layer + ')');
// 调用子类的init方法
try {
this.init(config);
} catch (error) {
console.error('❌ BaseComponent: 组件初始化时发生异常:', error);
this.isInitialized = false;
}
},
/**
* 移除所有事件监听器
* @private
*/
_removeAllEventListeners: function() {
for (var eventName in this.eventListeners) {
if (this.eventListeners.hasOwnProperty(eventName)) {
this.removeEventListener(eventName);
}
}
},
/**
* 隐藏所有精灵
* 注意: 精灵由编辑器创建,组件销毁时只隐藏精灵,不删除精灵
* @private
*/
_hideAllSprites: function() {
if (!this.sprites || this.sprites.length === 0) {
return;
}
console.log('🙈 BaseComponent: 隐藏组件 "' + this.name + '" 的所有精灵 (数量: ' + this.sprites.length + ')');
for (var i = 0; i < this.sprites.length; i++) {
var spriteId = this.sprites[i];
SpriteManager.hide(spriteId);
}
this.sprites = [];
},
// ==================== 生命周期方法 ====================
/**
* 初始化方法 - 子类覆盖
* 在此方法中创建精灵、注册事件监听
* @param {Object} config - 配置对象
*/
init: function(config) {
// 子类覆盖此方法
console.log('📝 BaseComponent.init: 请在子类中覆盖此方法');
},
/**
* 显示组件
* @returns {Boolean} 是否成功
*/
show: function() {
if (this.isDestroyed) {
console.error('❌ BaseComponent.show: 组件已销毁,无法显示:', this.name);
return false;
}
if (!this.isInitialized) {
console.error('❌ BaseComponent.show: 组件未初始化,无法显示:', this.name);
return false;
}
if (this.isVisible) {
console.log('ℹ️ BaseComponent.show: 组件已可见,跳过:', this.name);
return true;
}
console.log('👁️ BaseComponent: 显示组件 "' + this.name + '" (图层: ' + this.layer + ')');
// 显示图层
SpriteManager.showGroup(this.layer);
this.isVisible = true;
// 调用钩子方法
try {
this.onShow();
} catch (error) {
console.error('❌ BaseComponent.show: onShow钩子执行时发生异常:', error);
}
return true;
},
/**
* 隐藏组件
* @returns {Boolean} 是否成功
*/
hide: function() {
if (this.isDestroyed) {
console.error('❌ BaseComponent.hide: 组件已销毁:', this.name);
return false;
}
if (!this.isVisible) {
console.log('ℹ️ BaseComponent.hide: 组件已隐藏,跳过:', this.name);
return true;
}
console.log('🙈 BaseComponent: 隐藏组件 "' + this.name + '" (图层: ' + this.layer + ')');
// 隐藏图层
SpriteManager.hideGroup(this.layer);
this.isVisible = false;
// 调用钩子方法
try {
this.onHide();
} catch (error) {
console.error('❌ BaseComponent.hide: onHide钩子执行时发生异常:', error);
}
return true;
},
/**
* 销毁组件
* @returns {Boolean} 是否成功
*/
destroy: function() {
if (this.isDestroyed) {
console.warn('⚠️ BaseComponent.destroy: 组件已销毁,跳过:', this.name);
return false;
}
console.log('💥 BaseComponent: 销毁组件 "' + this.name + '"');
// 移除所有事件监听
this._removeAllEventListeners();
// 隐藏所有精灵(精灵由编辑器创建,不删除)
this._hideAllSprites();
// 隐藏图层
if (this.isVisible) {
SpriteManager.hideGroup(this.layer);
}
// 调用钩子方法
try {
this.onDestroy();
} catch (error) {
console.error('❌ BaseComponent.destroy: onDestroy钩子执行时发生异常:', error);
}
// 标记为已销毁
this.isDestroyed = true;
this.isVisible = false;
this.isInitialized = false;
return true;
},
// ==================== 事件监听管理 ====================
/**
* 添加事件监听器
* @param {String} eventName - 事件名称
* @param {Function} handler - 事件处理函数
* @returns {Boolean} 是否成功
*/
addEventListener: function(eventName, handler) {
if (!eventName || typeof eventName !== 'string') {
console.error('❌ BaseComponent.addEventListener: eventName必须是字符串');
return false;
}
if (typeof handler !== 'function') {
console.error('❌ BaseComponent.addEventListener: handler必须是函数');
return false;
}
if (this.eventListeners[eventName]) {
console.warn('⚠️ BaseComponent.addEventListener: 事件已注册,将覆盖:', eventName);
this.removeEventListener(eventName);
}
// 包装处理函数,绑定this上下文
var self = this;
var wrappedHandler = function(data) {
if (self.isDestroyed) {
console.warn('⚠️ BaseComponent: 组件已销毁,忽略事件:', eventName);
return;
}
try {
handler.call(self, data);
} catch (error) {
console.error('❌ BaseComponent: 事件处理函数执行异常:', eventName, error);
}
};
// 注册到EventBus
EventBus.on(eventName, wrappedHandler);
// 记录监听器
this.eventListeners[eventName] = wrappedHandler;
console.log('📡 BaseComponent: 组件 "' + this.name + '" 注册事件:', eventName);
return true;
},
/**
* 移除事件监听器
* @param {String} eventName - 事件名称
* @returns {Boolean} 是否成功
*/
removeEventListener: function(eventName) {
if (!this.eventListeners[eventName]) {
console.warn('⚠️ BaseComponent.removeEventListener: 事件未注册:', eventName);
return false;
}
var handler = this.eventListeners[eventName];
EventBus.off(eventName, handler);
delete this.eventListeners[eventName];
console.log('📴 BaseComponent: 组件 "' + this.name + '" 移除事件:', eventName);
return true;
},
/**
* 触发事件(发送到EventBus)
* @param {String} eventName - 事件名称
* @param {*} data - 事件数据
*/
emit: function(eventName, data) {
if (!eventName || typeof eventName !== 'string') {
console.error('❌ BaseComponent.emit: eventName必须是字符串');
return;
}
console.log('📤 BaseComponent: 组件 "' + this.name + '" 触发事件:', eventName);
EventBus.emit(eventName, data);
},
// ==================== 精灵管理 ====================
/**
* 添加精灵到组件管理列表
* @param {Number} spriteId - 精灵ID
* @returns {Boolean} 是否成功
*/
addSprite: function(spriteId) {
if (typeof spriteId !== 'number') {
console.error('❌ BaseComponent.addSprite: spriteId必须是数字');
return false;
}
if (!SpriteManager.exists(spriteId)) {
console.error('❌ BaseComponent.addSprite: 精灵不存在:', spriteId);
return false;
}
if (this.sprites.indexOf(spriteId) !== -1) {
console.warn('⚠️ BaseComponent.addSprite: 精灵已在管理列表中:', spriteId);
return false;
}
this.sprites.push(spriteId);
// console.log('➕ BaseComponent: 组件 "' + this.name + '" 添加精灵:', spriteId);
return true;
},
/**
* 从组件管理列表移除精灵(不销毁)
* @param {Number} spriteId - 精灵ID
* @returns {Boolean} 是否成功
*/
removeSprite: function(spriteId) {
var index = this.sprites.indexOf(spriteId);
if (index === -1) {
console.warn('⚠️ BaseComponent.removeSprite: 精灵不在管理列表中:', spriteId);
return false;
}
this.sprites.splice(index, 1);
console.log('➖ BaseComponent: 组件 "' + this.name + '" 移除精灵:', spriteId);
return true;
},
/**
* 获取组件管理的所有精灵ID
* @returns {Array<Number>} 精灵ID数组
*/
getSprites: function() {
return this.sprites.slice(); // 返回副本
},
/**
* 获取组件管理的精灵数量
* @returns {Number} 精灵数量
*/
getSpriteCount: function() {
return this.sprites.length;
},
// ==================== 钩子方法 ====================
/**
* 显示时的钩子方法 - 子类可覆盖
*/
onShow: function() {
// 子类可以覆盖此方法
},
/**
* 隐藏时的钩子方法 - 子类可覆盖
*/
onHide: function() {
// 子类可以覆盖此方法
},
/**
* 销毁时的钩子方法 - 子类可覆盖
*/
onDestroy: function() {
// 子类可以覆盖此方法
},
// ==================== 工具方法 ====================
/**
* 获取组件状态信息
* @returns {Object} 状态对象
*/
getState: function() {
return {
name: this.name,
layer: this.layer,
isVisible: this.isVisible,
isInitialized: this.isInitialized,
isDestroyed: this.isDestroyed,
spriteCount: this.sprites.length,
eventCount: Object.keys(this.eventListeners).length
};
},
/**
* 打印组件状态(调试用)
*/
printState: function() {
var state = this.getState();
console.log('📊 组件状态:', JSON.stringify(state, null, 2));
}
};
// ==================== 工厂方法 ====================
/**
* 创建组件实例
* @param {Object} config - 配置对象
* @param {String} config.name - 组件名称
* @param {Number} config.layer - 图层ID
* @returns {Object} 组件实例
*/
BaseComponentProto.create = function(config) {
if (!config || typeof config !== 'object') {
console.error('❌ BaseComponent.create: config必须是对象');
return null;
}
// 创建实例
var instance = Object.create(this);
// 初始化实例
instance._init(config);
if (!instance.isInitialized) {
console.error('❌ BaseComponent.create: 组件初始化失败');
return null;
}
return instance;
};
// 返回基类原型
return BaseComponentProto;
})();
// 导出到全局(兼容ES5)
if (typeof module !== 'undefined' && module.exports) {
module.exports = BaseComponent;
}
@@ -0,0 +1,830 @@
/**
* ============================================================================
* DynamicSpriteList.js - 动态精灵列表工具
* ============================================================================
*
* 基于 gameabc 框架的精灵复制功能,提供动态列表创建、管理和交互的完整解决方案。
*
* 核心功能:
* - 动态创建/删除精灵列表
* - 自动滑动和边界回弹
* - 点击检测和事件分发
* - 裁剪区域管理
*
* 适用场景:
* - 小局结算列表
* - 大局结算列表
* - 听牌提示列表
* - 战绩列表
* - 排行榜
*
* 使用示例:
* ```javascript
* // 1. 创建列表实例
* var myList = new DynamicSpriteList({
* containerId: 100, // 父精灵ID(容器)
* clipArea: { x: 50, y: 100, width: 600, height: 400 },
* rowHeight: 80,
* templates: {
* background: { spriteId: 101 },
* title: { spriteId: 102, offset: { x: 20, y: 10 } },
* button: { spriteId: 103, offset: { x: 500, y: 20 } }
* }
* });
*
* // 2. 设置数据并渲染
* myList.setData([
* { title: '第1行', buttonText: '查看' },
* { title: '第2行', buttonText: '查看' }
* ]);
*
* // 3. 监听点击事件
* myList.onClick = function(type, index, data) {
* console.log('点击了', type, '第', index, '行');
* };
*
* // 4. 在事件处理函数中调用
* // mousedown: myList.handleMouseDown(spid, x, y);
* // mousemove: myList.handleMouseMove(spid, offsetY);
* // mouseup: myList.handleMouseUp(spid, x, y);
* // drawbegin: myList.handleDrawBegin(spid);
*
* // 5. 销毁
* myList.destroy();
* ```
*
* ============================================================================
* @guide 开发指南 - DynamicSpriteList 完整使用规范
* ============================================================================
*
* 【准备工作:在编辑器中预置精灵】
* 1. 在编辑器中放置一个容器精灵(作为 containerId)
* 2. 在容器精灵旁边放置各列模板精灵(通常隐藏):
* - 行背景模板(每行底色/分隔线)
* - 数据列模板(牌图、分数、数量等,每种一个)
* 3. 记录这些模板精灵的 ID,用于配置 templates
*
* 【初始化列表(听牌提示列表示例)】
* var tingList = new DynamicSpriteList({
* containerId: sprites.LIST_CONTAINER, // 容器精灵
* clipArea: {
* x: 0, y: 0, // 相对容器左上角
* width: 640, height: 480 // 可见区域尺寸
* },
* rowHeight: 80, // 每行高度(像素)
* templates: {
* // 每列都需要一个 spriteId(模板)
* rowBg: { spriteId: sprites.ROW_TEMPLATE },
* card: { spriteId: sprites.CARD_TEMPLATE, offset: { x: 20, y: 10 } },
* score: { spriteId: sprites.SCORE_TEMPLATE, offset: { x: 120, y: 10 } },
* jingBadge: { spriteId: sprites.JING_BADGE, offset: { x: 90, y: 5 } },
* remainBadge: { spriteId: sprites.REMAIN_BADGE, offset: { x: 200, y: 5 } },
* rowSeparator: { spriteId: sprites.ROW_SEPARATOR }
* },
* scrollSensitivity: 5, // 移动5px以上才触发滚动
* bounceSpeed: 1 // 回弹速度
* });
*
* 【设置数据(触发渲染)】
* // 数据数组中每个元素对应一行
* // setData 会清除旧行、重新创建新行
* tingList.setData(tingHints); // tingHints = [{ code, score, remain, isJing }, ...]
*
* 【监听点击(在 setData 之前或之后设置均可)】
* tingList.onClick = function(type, rowIndex, rowData) {
* // type: 模板键名,如 'card'、'score'
* // rowIndex: 第几行(0-based)
* // rowData: 该行对应的数据对象
* if (type === 'card') {
* self._onCardClick(rowData);
* }
* };
*
* 【接入 SpriteEventController 事件(必须接入,否则列表不响应交互)】
* var self = this;
* // 在 init 中注册容器精灵的鼠标事件
* SpriteEventController.registerMouseDown(sprites.LIST_CONTAINER, function(event) {
* tingList.handleMouseDown(event.spid, event.offsetX, event.offsetY);
* });
* SpriteEventController.registerMouseMove(sprites.LIST_CONTAINER, function(event) {
* tingList.handleMouseMove(event.spid, event.offsetY);
* });
* SpriteEventController.registerMouseUp(sprites.LIST_CONTAINER, function(event) {
* tingList.handleMouseUp(event.spid, event.offsetX, event.offsetY);
* });
* SpriteEventController.registerDrawBegin(sprites.LIST_CONTAINER, function(event) {
* tingList.handleDrawBegin(event.spid);
* });
*
* 【销毁(组件关闭/destroy时必须调用)】
* tingList.destroy(); // 清除所有复制精灵,解除事件绑定
*
* 【注意事项】
* - destroy() 必须在组件 onDestroy/hide 时调用,否则复制精灵会残留
* - 每次 setData 会完全重建行,不要频繁调用(建议数据变化时才调用)
* - templates 中的 spriteId 是编辑器中预置的精灵ID,不是复制出来的ID
* - 如果列表不需要滚动,设置 enableScroll: false 可提升性能
*
* @author JinXian Team
* @version 1.0.0
* @since 2025-12-13
*/
'use strict';
// ============================================================================
// DynamicSpriteList 类定义
// ============================================================================
/**
* 动态精灵列表构造函数
*
* @constructor
* @param {Object} config - 列表配置对象
* @param {number} config.containerId - 父精灵ID(容器精灵,所有复制精灵将附着在此精灵上)
* @param {Object} config.clipArea - 裁剪区域配置(可见区域)
* @param {number} config.clipArea.x - 裁剪区域左上角X坐标
* @param {number} config.clipArea.y - 裁剪区域左上角Y坐标
* @param {number} config.clipArea.width - 裁剪区域宽度
* @param {number} config.clipArea.height - 裁剪区域高度
* @param {number} config.rowHeight - 每行高度(像素)
* @param {Object} config.templates - 模板精灵配置
* @param {number} [config.scrollSensitivity=2] - 滑动灵敏度(移动多少像素触发滑动)
* @param {number} [config.bounceSpeed=1] - 回弹动画速度
* @param {boolean} [config.enableScroll=true] - 是否启用滚动
* @param {boolean} [config.enableClick=true] - 是否启用点击
*
* @example
* // 模板配置示例
* templates: {
* // 行背景 - 必须有
* background: {
* spriteId: 101, // 模板精灵ID
* offset: { x: 0, y: 0 } // 相对行起点的偏移(可选,默认0,0)
* },
* // 文字模板
* title: {
* spriteId: 102,
* offset: { x: 20, y: 15 },
* textProperty: 'title' // 对应数据中的属性名
* },
* // 按钮模板
* button: {
* spriteId: 103,
* offset: { x: 500, y: 20 },
* clickable: true // 标记为可点击
* }
* }
*/
function DynamicSpriteList(config) {
// ========================================================================
// 参数验证
// ========================================================================
if (!config) {
throw new Error('DynamicSpriteList: config is required');
}
if (typeof config.containerId !== 'number') {
throw new Error('DynamicSpriteList: containerId must be a number');
}
if (!config.clipArea) {
throw new Error('DynamicSpriteList: clipArea is required');
}
if (typeof config.rowHeight !== 'number' || config.rowHeight <= 0) {
throw new Error('DynamicSpriteList: rowHeight must be a positive number');
}
var hasTemplates = config.templates && typeof config.templates === 'object';
var hasRenderRow = typeof config.renderRow === 'function';
if (!hasTemplates && !hasRenderRow) {
throw new Error('DynamicSpriteList: templates or renderRow is required');
}
// ========================================================================
// 实例属性初始化
// ========================================================================
/**
* 容器精灵ID
* @type {number}
*/
this.containerId = config.containerId;
/**
* 裁剪区域配置
* @type {Object}
*/
this.clipArea = {
x: config.clipArea.x || 0,
y: config.clipArea.y || 0,
width: config.clipArea.width || 0,
height: config.clipArea.height || 0
};
/**
* 每行高度
* @type {number}
*/
this.rowHeight = config.rowHeight;
/**
* 模板精灵配置
* @type {Object}
*/
this.templates = config.templates || null;
this.renderRow = config.renderRow || null;
/**
* 滑动灵敏度
* @type {number}
*/
this.scrollSensitivity = config.scrollSensitivity || 2;
/**
* 回弹动画速度
* @type {number}
*/
this.bounceSpeed = config.bounceSpeed || 1;
/**
* 是否启用滚动
* @type {boolean}
*/
this.enableScroll = config.enableScroll !== false;
/**
* 是否启用点击
* @type {boolean}
*/
this.enableClick = config.enableClick !== false;
// ========================================================================
// 运行时状态
// ========================================================================
/**
* 当前数据
* @type {Array}
* @private
*/
this._data = [];
/**
* 是否正在滑动
* @type {boolean}
* @private
*/
this._isSliding = false;
/**
* 是否已渲染
* @type {boolean}
* @private
*/
this._isRendered = false;
/**
* 已创建的精灵记录 (用于删除)
* @type {Array}
* @private
*/
this._createdSprites = [];
this._tagIndex = {};
this._nextTag = 1;
// ========================================================================
// 事件回调
// ========================================================================
/**
* 点击事件回调
* @type {Function|null}
* @param {string} templateName - 点击的模板类型名称
* @param {number} rowIndex - 行索引(从0开始)
* @param {Object} rowData - 该行的数据对象
*
* @example
* list.onClick = function(templateName, rowIndex, rowData) {
* if (templateName === 'button') {
* console.log('点击了第', rowIndex, '行的按钮');
* console.log('数据:', rowData);
* }
* };
*/
this.onClick = null;
/**
* 滚动事件回调
* @type {Function|null}
* @param {number} scrollY - 当前滚动位置
* @param {number} maxScrollY - 最大滚动位置
*
* @example
* list.onScroll = function(scrollY, maxScrollY) {
* var progress = scrollY / maxScrollY;
* console.log('滚动进度:', progress);
* };
*/
this.onScroll = null;
}
// ============================================================================
// 公共方法
// ============================================================================
/**
* 设置数据并渲染列表
*
* @param {Array} data - 数据数组,每个元素对应一行
* @returns {DynamicSpriteList} 返回自身,支持链式调用
*
* @example
* list.setData([
* { title: '战绩1', score: '+100', time: '12:30' },
* { title: '战绩2', score: '-50', time: '12:25' },
* { title: '战绩3', score: '+200', time: '12:20' }
* ]);
*/
DynamicSpriteList.prototype.setData = function(data) {
if (!Array.isArray(data)) {
console.error('DynamicSpriteList.setData: data must be an array');
return this;
}
// 先清除旧内容
this.clear();
// 保存数据
this._data = data;
// 渲染列表
this._render();
return this;
};
/**
* 获取当前数据
*
* @returns {Array} 当前数据数组的副本
*/
DynamicSpriteList.prototype.getData = function() {
return this._data.slice();
};
/**
* 获取数据行数
*
* @returns {number} 数据行数
*/
DynamicSpriteList.prototype.getRowCount = function() {
return this._data.length;
};
/**
* 显示列表
*
* @returns {DynamicSpriteList} 返回自身,支持链式调用
*/
DynamicSpriteList.prototype.show = function() {
set_self(this.containerId, 37, 1, 0, 0);
return this;
};
/**
* 隐藏列表
*
* @returns {DynamicSpriteList} 返回自身,支持链式调用
*/
DynamicSpriteList.prototype.hide = function() {
set_self(this.containerId, 37, 0, 0, 0);
return this;
};
/**
* 清除所有动态创建的精灵
*
* @returns {DynamicSpriteList} 返回自身,支持链式调用
*/
DynamicSpriteList.prototype.clear = function() {
for (var i = 0; i < this._createdSprites.length; i++) {
ifast_dllpritefromspritecopy(this.containerId, this._createdSprites[i].tag);
}
this._createdSprites = [];
this._tagIndex = {};
this._nextTag = 1;
this._isRendered = false;
this._isSliding = false;
return this;
};
/**
* 销毁列表实例
* 释放所有资源,清除所有精灵
*/
DynamicSpriteList.prototype.destroy = function() {
this.clear();
this.hide();
this._data = [];
this.onClick = null;
this.onScroll = null;
};
/**
* 滚动到指定位置
*
* @param {number} y - 目标Y坐标
* @param {boolean} [animated=true] - 是否使用动画
* @returns {DynamicSpriteList} 返回自身,支持链式调用
*/
DynamicSpriteList.prototype.scrollTo = function(y, animated) {
var currentY = get_self(this.containerId, 19, 0, 0, 0);
var targetY = this.clipArea.y - y;
// 边界限制
var contentHeight = this._data.length * this.rowHeight;
var minY = this.clipArea.y + this.clipArea.height - contentHeight;
if (targetY < minY) {
targetY = minY;
}
if (targetY > this.clipArea.y) {
targetY = this.clipArea.y;
}
if (animated !== false) {
play_ani(1, this.containerId, 19, currentY, targetY, 0,
Math.abs(targetY - currentY), 0, 0, 0, this.bounceSpeed, 0, 0, 0);
} else {
set_self(this.containerId, 19, targetY, 0, 0, 0);
}
return this;
};
/**
* 滚动到顶部
*
* @param {boolean} [animated=true] - 是否使用动画
* @returns {DynamicSpriteList} 返回自身,支持链式调用
*/
DynamicSpriteList.prototype.scrollToTop = function(animated) {
return this.scrollTo(0, animated);
};
/**
* 滚动到底部
*
* @param {boolean} [animated=true] - 是否使用动画
* @returns {DynamicSpriteList} 返回自身,支持链式调用
*/
DynamicSpriteList.prototype.scrollToBottom = function(animated) {
var contentHeight = this._data.length * this.rowHeight;
var maxScroll = contentHeight - this.clipArea.height;
if (maxScroll < 0) {
maxScroll = 0;
}
return this.scrollTo(maxScroll, animated);
};
/**
* 滚动到指定行
*
* @param {number} rowIndex - 行索引(从0开始)
* @param {boolean} [animated=true] - 是否使用动画
* @returns {DynamicSpriteList} 返回自身,支持链式调用
*/
DynamicSpriteList.prototype.scrollToRow = function(rowIndex, animated) {
var y = rowIndex * this.rowHeight;
return this.scrollTo(y, animated);
};
// ============================================================================
// 事件处理方法 - 需要在对应的事件回调中调用
// ============================================================================
/**
* 处理鼠标/触摸按下事件
* 需要在 utlmousedown 回调中调用
*
* @param {number} spid - 按下的精灵ID
* @param {number} x - 按下位置X坐标
* @param {number} y - 按下位置Y坐标
* @returns {boolean} 如果事件被处理返回 true
*
* @example
* // 在 SpriteEventController 或 Game_Modify 中
* utlmousedown: function(gameid, spid, downx, downy) {
* if (myList.handleMouseDown(spid, downx, downy)) {
* return; // 事件已处理
* }
* // 其他处理...
* }
*/
DynamicSpriteList.prototype.handleMouseDown = function(spid, x, y) {
if (spid === this.containerId) {
this._isSliding = false;
return true;
}
return false;
};
/**
* 处理鼠标/触摸移动事件
* 需要在 utlmousemove 回调中调用
*
* @param {number} spid - 移动的精灵ID
* @param {number} offsetY - Y方向移动偏移量
* @returns {boolean} 如果事件被处理返回 true
*
* @example
* // 在 SpriteEventController 或 Game_Modify 中
* utlmousemove: function(gameid, spid, downx, downy, movex, movey, timelong, offmovex, offmovey) {
* if (myList.handleMouseMove(spid, offmovey)) {
* return; // 事件已处理
* }
* // 其他处理...
* }
*/
DynamicSpriteList.prototype.handleMouseMove = function(spid, offsetY) {
if (!this.enableScroll) {
return false;
}
if (spid === this.containerId) {
if (Math.abs(offsetY) > this.scrollSensitivity) {
// 增量移动容器
set_self(this.containerId, 19, offsetY, 1, 0, 0);
this._isSliding = true;
// 触发滚动回调
if (typeof this.onScroll === 'function') {
var currentY = get_self(this.containerId, 19, 0, 0, 0);
var scrollY = this.clipArea.y - currentY;
var contentHeight = this._data.length * this.rowHeight;
var maxScrollY = contentHeight - this.clipArea.height;
this.onScroll(scrollY, maxScrollY > 0 ? maxScrollY : 0);
}
}
return true;
}
return false;
};
/**
* 处理鼠标/触摸松开事件
* 需要在 mouseup 回调中调用
*
* @param {number} spidDown - 按下时的精灵ID
* @param {number} spidUp - 松开时的精灵ID
* @param {number} upX - 松开位置X坐标
* @param {number} upY - 松开位置Y坐标
* @returns {boolean} 如果事件被处理返回 true
*
* @example
* // 在 SpriteEventController 或 Game_Modify 中
* mouseup: function(gameid, spid_down, downx, downy, spid_up, upx, upy, timelong) {
* if (myList.handleMouseUp(spid_down, spid_up, upx, upy)) {
* return; // 事件已处理
* }
* // 其他处理...
* }
*/
DynamicSpriteList.prototype.handleMouseUp = function(spidDown, spidUp, upX, upY) {
if (spidDown !== this.containerId) {
return false;
}
// 处理边界回弹
this._handleBounce();
// 处理点击(非滑动状态)
if (!this._isSliding && this.enableClick && spidDown === spidUp) {
this._handleClick(upX, upY);
}
return true;
};
/**
* 处理绘制开始事件
* 需要在 utlgamemydrawbegin 回调中调用,用于设置裁剪区域
*
* @param {number} spid - 正在绘制的精灵ID
* @returns {boolean} 如果事件被处理返回 true
*
* @example
* // 在 SpriteEventController 或 Game_Modify 中
* utlgamemydrawbegin: function(gameid, spid, times, timelong) {
* if (myList.handleDrawBegin(spid)) {
* return; // 已设置裁剪区域
* }
* // 其他处理...
* }
*/
DynamicSpriteList.prototype.handleDrawBegin = function(spid) {
if (spid === this.containerId) {
set_clip(0, 0,
this.clipArea.x,
this.clipArea.y,
this.clipArea.width,
this.clipArea.height
);
return true;
}
return false;
};
// ============================================================================
// 私有方法
// ============================================================================
/**
* 内部创建精灵并登记到 _tagIndex / _createdSprites
* @param {string} name - 精灵名称(模板键名或自定义名)
* @param {number} templateSpriteId - 模板精灵 ID
* @param {number} x - X 坐标
* @param {number} y - Y 坐标(已叠加 rowBaseY)
* @param {number} rowIndex - 所在行索引
* @param {boolean} clickable - 是否可点击
* @returns {*} 创建出的精灵 ID
* @private
*/
DynamicSpriteList.prototype._addSpriteInternal = function (name, templateSpriteId, x, y, rowIndex, clickable) {
var tag = this._nextTag++;
var spriteId = ifast_addtospritefromspritecopy(this.containerId, templateSpriteId, x, y, tag);
var rec = { tag: tag, spriteId: spriteId, rowIndex: rowIndex, name: name, clickable: (clickable !== false) };
this._createdSprites.push(rec);
this._tagIndex[tag] = rec;
return spriteId;
};
/**
* 渲染列表
* @private
*/
DynamicSpriteList.prototype._render = function() {
set_self(this.containerId, 18, this.clipArea.x, 0, 0);
set_self(this.containerId, 19, this.clipArea.y, 0, 0);
var contentHeight = this._data.length * this.rowHeight;
set_self(this.containerId, 21, contentHeight, 0, 0);
if (this.templates) {
var tnames = Object.keys(this.templates);
if (tnames.length > 0) {
var w = get_self(this.templates[tnames[0]].spriteId, 20, 0, 0, 0);
set_self(this.containerId, 20, w, 0, 0);
}
} else {
// renderRow 模式:容器宽度取裁剪区宽,保证整行可视宽都在容器 hit 区(点击/拖动检测)内。
// 否则容器保持编辑器预置窄宽度,超出部分点击/拖动报的 spid≠containerId 而失效(对账 bak set_self(fSpid,20,bg宽))。
set_self(this.containerId, 20, this.clipArea.width, 0, 0);
}
for (var rowIndex = 0; rowIndex < this._data.length; rowIndex++) {
var rowData = this._data[rowIndex];
var rowY = rowIndex * this.rowHeight;
if (this.renderRow) {
this.renderRow(this._makeRowContext(rowIndex, rowData, rowY));
} else {
var names = Object.keys(this.templates);
for (var i = 0; i < names.length; i++) {
var name = names[i];
var t = this.templates[name];
var ox = (t.offset && t.offset.x) || 0;
var oy = (t.offset && t.offset.y) || 0;
var sid = this._addSpriteInternal(name, t.spriteId, ox, rowY + oy, rowIndex, t.clickable !== false);
if (t.textProperty && rowData[t.textProperty] !== undefined) {
set_self(sid, 7, rowData[t.textProperty], 0, 0);
}
if (t.frameProperty && rowData[t.frameProperty] !== undefined) {
set_self(sid, 43, rowData[t.frameProperty], 0, 0);
}
}
}
}
this._isRendered = true;
};
/**
* 创建行上下文对象(传给 renderRow 回调)
* @param {number} rowIndex - 行索引
* @param {Object} rowData - 行数据
* @param {number} rowBaseY - 行基准 Y 坐标
* @returns {Object} 行上下文
* @private
*/
DynamicSpriteList.prototype._makeRowContext = function (rowIndex, rowData, rowBaseY) {
var self = this;
return {
rowIndex: rowIndex,
rowData: rowData,
rowBaseY: rowBaseY,
containerId: this.containerId,
addSprite: function (name, templateSpriteId, x, y) {
return self._addSpriteInternal(name, templateSpriteId, x, rowBaseY + y, rowIndex, true);
}
};
};
/**
* 处理边界回弹
* @private
*/
DynamicSpriteList.prototype._handleBounce = function() {
var currentY = get_self(this.containerId, 19, 0, 0, 0);
var contentHeight = get_self(this.containerId, 21, 0, 0, 0);
var clipY = this.clipArea.y;
var clipH = this.clipArea.height;
var targetY = currentY;
var needBounce = false;
if (contentHeight <= clipH) {
// 内容不足一屏,回弹到顶部
if (currentY !== clipY) {
targetY = clipY;
needBounce = true;
}
} else {
if (currentY > clipY) {
// 超出顶部
targetY = clipY;
needBounce = true;
} else {
var minY = clipY + clipH - contentHeight;
if (currentY < minY) {
// 超出底部
targetY = minY;
needBounce = true;
}
}
}
if (needBounce) {
play_ani(1, this.containerId, 19, currentY, targetY, 0,
Math.abs(targetY - currentY), 0, 0, 0, this.bounceSpeed, 0, 0, 0);
}
};
/**
* 处理点击事件
* @param {number} x - 点击X坐标
* @param {number} y - 点击Y坐标
* @private
*/
DynamicSpriteList.prototype._handleClick = function(x, y) {
var clickedTag = ifast_check_add(this.containerId, x, y);
if (clickedTag === -99999999) { return; }
var rec = this._tagIndex[clickedTag];
if (!rec || rec.clickable === false) { return; }
if (typeof this.onClick === 'function' && rec.rowIndex < this._data.length) {
this.onClick(rec.name, rec.rowIndex, this._data[rec.rowIndex]);
}
};
// ============================================================================
// 静态工具方法
// ============================================================================
/**
* 创建简单列表的快捷方法
*
* @static
* @param {Object} options - 配置选项
* @param {number} options.containerId - 容器精灵ID
* @param {number} options.itemTemplateId - 列表项模板精灵ID
* @param {Object} options.clipArea - 裁剪区域
* @param {number} options.rowHeight - 行高
* @returns {DynamicSpriteList} 列表实例
*
* @example
* var simpleList = DynamicSpriteList.createSimple({
* containerId: 100,
* itemTemplateId: 101,
* clipArea: { x: 50, y: 100, width: 600, height: 400 },
* rowHeight: 60
* });
*/
DynamicSpriteList.createSimple = function(options) {
return new DynamicSpriteList({
containerId: options.containerId,
clipArea: options.clipArea,
rowHeight: options.rowHeight,
templates: {
item: {
spriteId: options.itemTemplateId,
offset: { x: 0, y: 0 }
}
}
});
};
// ============================================================================
// 导出
// ============================================================================
// 兼容多种模块系统
if (typeof module !== 'undefined' && module.exports) {
module.exports = DynamicSpriteList;
} else if (typeof window !== 'undefined') {
window.DynamicSpriteList = DynamicSpriteList;
}
@@ -0,0 +1,395 @@
// ============================================================================
// RecordView.js —— 通用平台战绩页组件(游戏中立;逻辑零硬编码 ID,全读 config)
// 用框架工具复刻 gameCombat:页一大局列表/页二小局明细/滚动/页面切换/选中进回放。
// 数据收发与回放由子游戏注入:setGradeData(...) / onSelectRound / onRequestData。
// 依赖:DynamicSpriteList, SpriteEventController, SpriteManager。严格 ES5。
// ============================================================================
//
// ---------------------------------------------------------------------------
// 战绩数据结构(setGradeData 传入的 gradeInfo —— 字段访问的权威说明)
// ---------------------------------------------------------------------------
// gradeInfo : CombatInfo[] 每个元素 = 一个「大局」战绩
//
// CombatInfo[i] = {
// gameinfo1 : Object|String, // 战绩主体(可能是 JSON 字符串,渲染时先 JSON.parse)
// roomcode : String|Number, // 房号 (页一显示)
// overtime : String, // 结束时间 (页一显示)
// idx : Number, // 战绩索引(翻页/拉取定位用)
// roomtype : String // 房间类型
// }
//
// gameinfo1 = {
// roundsum : Number, // 小局总数(= round.length) (页一“局数”)
// playerlist : Array, // 玩家列表,下标 j = 座位号
// // playerlist[j] = [ 昵称:String, 积分:Number, 头像url:String ] ← 3 元素
// // · 空座位可能为 falsy → 渲染时跳过(if(!pl[j]) continue)
// // · 注:服务端存储侧 o_gameinfo1 为 4 元素 [昵称,积分(String),头像,playerid],
// // 与此“战绩查询返回”格式不同;本组件只用 [0]昵称 / [1]积分,差异无影响。
// round : Array // 小局明细,round[i] = 第 i 个小局的逐座位计分
// // round[i] = [ [座位号:Number, 该小局得分:Number], ... ]
// // · 例:round[0] = [[0,-6],[1,-8],[2,-6],[3,20]] → 座位0得-6 … 座位3得+20
// }
//
// 页一(大局列表)用:overtime / roomcode / roundsum / playerlist(昵称+积分)
// 页二(小局明细)用:round(每行=一小局)+ playerlist(昵称) + round[i] 按座位匹配该局得分
// (roundData[k][0]==座位 j → roundData[k][1] 为该玩家该小局得分)
// ---------------------------------------------------------------------------
var RecordView = {
_cfg: null,
_gradeData: null,
_page: 0,
_gameIndex: -1,
_listOne: null,
_listTwo: null,
_bound: false,
_type: 0,
_gradeIdx1: 0,
_gradeIdx2: 0,
_gradeCount: 0, // 战绩总数(原全局 CombatCount,RecordView 自持)
_selectedRound: -1, // 当前选中的小局下标(配合 _gameIndex;选局数据由 RecordView 记录,子游戏只读)
onClose: null,
// 收战绩列表回包(平台通用协议):set 全局 CombatInfo/CombatCount + parse gameinfo1 + 记翻页边界 + 渲染打开。
// 子游戏经 SubGameHooks.get_player_grade1 转发到此。
onReceiveGrade1: function (_msg) {
if (typeof Utl !== 'undefined' && Utl.playSound && typeof Game_Config !== 'undefined') {
Utl.playSound(Game_Config.ClickButton.src_2);
}
var gradeinfo = _msg.data.gradeinfo || [];
this._gradeCount = _msg.data.asetcount;
for (var i = 0; i < gradeinfo.length; i++) {
if (typeof gradeinfo[i].gameinfo1 === 'string') {
gradeinfo[i].gameinfo1 = JSON.parse(gradeinfo[i].gameinfo1);
}
if (i === 0) { this._gradeIdx1 = gradeinfo[i].idx; }
if (i === gradeinfo.length - 1) { this._gradeIdx2 = gradeinfo[i].idx; }
}
this.setGradeData(gradeinfo);
this.open();
},
// 翻页/类型 → 发获取战绩请求(平台通用协议)。类型切换 _requestGrade(type,null);翻页 _requestGrade(_,direction)。
_requestGrade: function (arg1, direction) {
var data = { agentid: GameData.AgentId, playerid: C_Player.playerid, gameid: GameData.GameId };
if (direction == null) {
this._type = arg1; // 类型切换
data.type = arg1;
} else {
data.type = this._type; // 翻页:type 用当前,direction + gradeidx
data.direction = direction;
data.gradeidx = (direction === 1) ? this._gradeIdx1 : this._gradeIdx2;
}
if (typeof Net !== 'undefined' && Net.Send_get_player_grade1) { Net.Send_get_player_grade1(data); }
},
// 选局 → 发获取该大局回放数据请求(平台通用协议)。gradeidx=选中大局的 idx(对账 bak newGoCombatPageThree 812-816)。
// 每次选局都发(不做同局缓存);回放数据接收与渲染由子游戏(onSelectRound + SubGameHooks.get_player_grade2)完成。
_requestReplayData: function (gameIndex) {
var data = { agentid: GameData.AgentId, playerid: C_Player.playerid, gameid: GameData.GameId };
if (this._gradeData && this._gradeData[gameIndex] && typeof this._gradeData[gameIndex].idx !== 'undefined') {
data.gradeidx = this._gradeData[gameIndex].idx;
}
if (typeof Net !== 'undefined' && Net.Send_get_player_grade2) { Net.Send_get_player_grade2(data); }
},
configure: function (config) {
this._cfg = config || (typeof RecordViewDefaultConfig !== 'undefined' ? RecordViewDefaultConfig : null);
if (!this._cfg) { console.error('[RecordView] 缺少配置且无 DEFAULT_CONFIG'); return false; }
return true;
},
// 喂入平台战绩数据并渲染页一。
// @param gradeInfo CombatInfo[](结构见文件顶部「战绩数据结构」)
setGradeData: function (gradeInfo) {
this._gradeData = gradeInfo || [];
if (this._page === 1) { this._renderPageOne(); }
},
open: function () {
if (!this._cfg) { this.configure(null); }
if (!this._cfg) { return false; }
SpriteManager.showGroup(this._cfg.common.panelGroup);
this._bindEvents();
this.gotoPageOne();
return true;
},
close: function () {
this._clearPageOne();
this._clearPageTwo();
var rs = this._cfg ? this._cfg.common.resetGroups : [];
for (var i = 0; i < rs.length; i++) { SpriteManager.hideGroup(rs[i]); }
this._page = 0;
if (typeof this.onClose === 'function') { this.onClose(); }
},
gotoPageOne: function () {
this._clearPageTwo();
this._page = 1;
this._renderPageOne();
this._syncHeader();
this._syncPagingBtns();
},
gotoPageTwo: function (gameIndex) {
this._clearPageOne();
this._gameIndex = gameIndex;
this._page = 2;
this._renderPageTwo(gameIndex);
this._syncHeader();
},
// 同步头部 UI(总数 684/683、标题 82、类型标签 83),复刻 bak newGoCombatPageOne/Two 的 set_self。
_syncHeader: function () {
if (typeof set_self === 'undefined') { return; }
var c = this._cfg.common;
if (this._gradeCount) {
set_self(c.countSpid, 7, this._gradeCount, 0, 0);
set_self(c.countSpid, 20, String(this._gradeCount).length * 14, 0, 0);
} else {
set_self(c.countHideSpid, 37, 0, 0, 0);
set_self(c.countSpid, 37, 0, 0, 0);
}
set_self(c.titleSpid, 37, 1, 0, 0);
set_self(c.titleSpid, 43, this._type ? 2 : 1, 0, 0);
if (c.typeTagSpid != null) {
set_self(c.typeTagSpid, 43, this._type ? 2 : 1, 0, 0);
}
},
// 翻页按钮显隐:仅 type=1(分页视图)显示 prev/next,复刻 bak newGoCombatPageOne 753-759。
// 这保证 type=0 视图无翻页按钮,翻页时 _type 恒为 1,_requestGrade 取值正确。
_syncPagingBtns: function () {
if (typeof set_self === 'undefined') { return; }
var c = this._cfg.common;
var show = this._type ? 1 : 0;
if (c.prevBtn != null) { set_self(c.prevBtn, 37, show, 0, 0); }
if (c.nextBtn != null) { set_self(c.nextBtn, 37, show, 0, 0); }
},
// 供子游戏读取当前选局(回放定位):选中大局下标 / 小局下标。选局数据由 RecordView 记录,子游戏只读。
getSelectedGameIndex: function () { return this._gameIndex; },
getSelectedRound: function () { return this._selectedRound; },
getSelectedGameData: function () { return (this._gradeData && this._gameIndex >= 0) ? this._gradeData[this._gameIndex] : null; },
destroy: function () {
this._unbindEvents();
if (this._listOne) { this._listOne.destroy(); this._listOne = null; }
if (this._listTwo) { this._listTwo.destroy(); this._listTwo = null; }
this._gradeData = null; this._page = 0; this._gameIndex = -1;
},
_renderPageOne: function () {
var self = this;
var cfg = this._cfg.pageOne;
var L = cfg.layout;
var T = cfg.rowTemplate;
SpriteManager.showGroup(cfg.groupId);
if (this._listOne) { this._listOne.destroy(); }
this._listOne = new DynamicSpriteList({
containerId: cfg.containerSpid,
clipArea: { x: cfg.clip.x, y: cfg.clip.y, width: cfg.clip.w, height: cfg.clip.h },
rowHeight: L.rowSpace,
renderRow: function (row) {
var d = row.rowData;
var gi = (typeof d.gameinfo1 === 'string') ? JSON.parse(d.gameinfo1) : d.gameinfo1;
// ---- 行背景 ----
row.addSprite('bg', T.bg, 0, 0);
// ---- txt1:固定标签(时间:/房号:/局数:)----
var s;
s = row.addSprite('lbl0', T.txt1, L.txt1Pos[0][0], L.txt1Pos[0][1]);
SpriteManager.setText(s, '时间:');
s = row.addSprite('lbl1', T.txt1, L.txt1Pos[1][0], L.txt1Pos[1][1]);
SpriteManager.setText(s, '房号:');
s = row.addSprite('lbl2', T.txt1, L.txt1Pos[2][0], L.txt1Pos[2][1]);
SpriteManager.setText(s, '局数:');
// ---- txt2:数据值(overtime / roomcode / roundsum)----
s = row.addSprite('val0', T.txt2, L.txt2Pos[0][0], L.txt2Pos[0][1]);
SpriteManager.setText(s, d.overtime);
s = row.addSprite('val1', T.txt2, L.txt2Pos[1][0], L.txt2Pos[1][1]);
SpriteManager.setText(s, d.roomcode);
s = row.addSprite('val2', T.txt2, L.txt2Pos[2][0], L.txt2Pos[2][1]);
SpriteManager.setText(s, gi.roundsum);
// ---- 玩家信息(昵称 + 积分),>5 人双排 ----
var pl = gi.playerlist;
var pCount = 0, m;
for (m = 0; m < pl.length; m++) { if (pl[m]) { pCount++; } }
var pos3 = (pCount > 5) ? L.txt3Pos10 : L.txt3Pos5;
var nameLimit = (pCount > 5) ? L.nickLimit10 : L.nickLimit5;
if (pCount > 5) {
row.addSprite('split', T.split, L.splitPos[0], L.splitPos[1]);
}
var n = 0, j;
for (j = 0; j < pl.length; j++) {
if (!pl[j]) { continue; }
var nameTxt = Func.subString(pl[j][0], nameLimit, true);
var scoreTxt = pl[j][1];
var cx = pos3[n][0];
var cy = pos3[n][1];
var nx = cx - String(nameTxt).gblen() * L.txtWidth / 2;
var sx = cx - String(scoreTxt).gblen() * L.txtWidth / 2;
var sn = row.addSprite('name' + n, T.txt3, nx, cy);
SpriteManager.setText(sn, nameTxt);
var sc = row.addSprite('score' + n, T.txt3, sx, cy + L.nsSpace);
SpriteManager.setText(sc, scoreTxt);
n++;
}
// ---- 行按钮(可点击跳转页二)----
row.addSprite('btn', T.btn, L.btnPos[0], L.btnPos[1]);
}
});
this._listOne.onClick = function (name, rowIndex, rowData) {
self.gotoPageTwo(rowIndex);
};
this._listOne.setData(this._gradeData || []);
},
_clearPageOne: function () { if (this._listOne) { this._listOne.destroy(); this._listOne = null; } if (this._cfg) { SpriteManager.hideGroup(this._cfg.pageOne.groupId); } },
_renderPageTwo: function (gameIndex) {
var self = this;
var cfg = this._cfg.pageTwo;
var L = cfg.layout;
var T = cfg.rowTemplate;
SpriteManager.showGroup(cfg.groupId);
if (this._listTwo) { this._listTwo.destroy(); }
var entry = this._gradeData[gameIndex];
var gi = (typeof entry.gameinfo1 === 'string') ? JSON.parse(entry.gameinfo1) : entry.gameinfo1;
var pl = gi.playerlist || [];
var rounds = gi.round || [];
// 计算玩家人数与 nameLimit(对账 gameCombat 669:用 playerlist.length 判断)
var pCount = 0, m;
for (m = 0; m < pl.length; m++) { if (pl[m]) { pCount++; } }
var nameLimit = (pl.length > 5) ? L.nickLimit10 : L.nickLimit5;
this._listTwo = new DynamicSpriteList({
containerId: cfg.containerSpid,
clipArea: { x: cfg.clip.x, y: cfg.clip.y, width: cfg.clip.w, height: cfg.clip.h },
rowHeight: L.rowSpace,
renderRow: function (row) {
var i = row.rowIndex; // 第 i 小局(0 起)
var roundData = row.rowData; // round[i] = [[座位,分数],...]
// ---- 行背景 ----
row.addSprite('bg', T.bg, 0, 0);
// ---- 第几局(对账 645–648:< 10 带空格,>= 10 无空格)----
var rd = i + 1;
var txt = (rd < 10) ? ('第 ' + rd + ' 局') : ('第' + rd + '局');
var st = row.addSprite('lbl', T.txt1, L.txt1Pos[0][0], L.txt1Pos[0][1]);
SpriteManager.setText(st, txt);
// ---- 玩家昵称 + 该局得分(>5 人双排 + 分隔线)----
var pos = (pCount > 5) ? L.txt3Pos10 : L.txt3Pos5;
if (pCount > 5) {
row.addSprite('split', T.split, L.splitPos[0], L.splitPos[1]);
}
var n = 0, j;
for (j = 0; j < pl.length; j++) {
if (!pl[j]) { continue; }
var nameTxt = Func.subString(pl[j][0], nameLimit, true);
var sn = row.addSprite('name' + n, T.txt3, pos[n][0], pos[n][1]);
SpriteManager.setText(sn, nameTxt);
// score 默认 0,再按座位号匹配覆盖(对账 687–694)
var sc = row.addSprite('score' + n, T.txt3, pos[n][0], pos[n][1] + L.nsSpace);
SpriteManager.setText(sc, 0);
var k;
for (k = 0; k < roundData.length; k++) {
if (roundData[k][0] == j) { SpriteManager.setText(sc, roundData[k][1]); }
}
n++;
}
// ---- 行按钮(可点击触发 onSelectRound)----
row.addSprite('btn', T.btn, L.btnPos[0], L.btnPos[1]);
}
});
this._listTwo.onClick = function (name, rowIndex, rowData) {
self._selectedRound = rowIndex; // 记录选中小局(选局数据由 RecordView 记录)
self._requestReplayData(self._gameIndex); // 发 grade2 拉该大局回放数据(每次选局都发)
};
this._listTwo.setData(rounds);
},
_clearPageTwo: function () { if (this._listTwo) { this._listTwo.destroy(); this._listTwo = null; } if (this._cfg) { SpriteManager.hideGroup(this._cfg.pageTwo.groupId); } },
_bindEvents: function () {
if (this._bound) { return; }
var self = this, c = this._cfg;
// ---- 两个列表容器:将精灵事件转发给对应的 DynamicSpriteList ----
function bindContainer(containerSpid, getList) {
SpriteEventController.registerMouseDown(containerSpid, function (e) {
var l = getList(); if (l) { l.handleMouseDown(e.spriteId, e.x, e.y); }
});
SpriteEventController.registerMouseMove(containerSpid, function (e) {
var l = getList(); if (l) { l.handleMouseMove(e.spriteId, e.offset.y); }
});
SpriteEventController.registerMouseUp(containerSpid, function (e) {
var l = getList(); if (l) { l.handleMouseUp(e.downSpriteId, e.upSpriteId, e.upPos.x, e.upPos.y); }
});
SpriteEventController.registerDrawBegin(containerSpid, function (e) {
var l = getList(); if (l) { l.handleDrawBegin(e.spriteId); }
});
}
bindContainer(c.pageOne.containerSpid, function () { return self._listOne; });
bindContainer(c.pageTwo.containerSpid, function () { return self._listTwo; });
// ---- 返回按钮:页二时回页一,否则关闭 ----
SpriteEventController.registerMouseUp(c.common.backBtn, function () {
if (self._page === 2) { self.gotoPageOne(); } else { self.close(); }
});
// ---- 关闭按钮(可多个)----
var cb = c.common.closeBtn || [], i;
for (i = 0; i < cb.length; i++) {
(function (spid) {
SpriteEventController.registerMouseUp(spid, function () { self.close(); });
})(cb[i]);
}
// ---- 翻页:prev=direction 1(上一页),next=direction 2(下一页)→ 组件自己发请求 ----
if (c.common.prevBtn != null) {
SpriteEventController.registerMouseUp(c.common.prevBtn, function () { self._requestGrade(1, 1); });
}
if (c.common.nextBtn != null) {
SpriteEventController.registerMouseUp(c.common.nextBtn, function () { self._requestGrade(1, 2); });
}
// ---- 类型切换:typeBtns[k] -> type=k(IIFE 固定 typeVal 防循环变量捕获)→ 组件自己发请求 ----
var tb = c.common.typeBtns || [], k;
for (k = 0; k < tb.length; k++) {
(function (typeVal) {
SpriteEventController.registerMouseUp(tb[typeVal], function () { self._requestGrade(typeVal, null); });
})(k);
}
this._bound = true;
},
_unbindEvents: function () {
if (!this._bound) { return; }
var c = this._cfg;
var spids = [c.pageOne.containerSpid, c.pageTwo.containerSpid, c.common.backBtn, c.common.prevBtn, c.common.nextBtn];
var cb = c.common.closeBtn || [], tb = c.common.typeBtns || [], i;
for (i = 0; i < cb.length; i++) { spids.push(cb[i]); }
for (i = 0; i < tb.length; i++) { spids.push(tb[i]); }
for (i = 0; i < spids.length; i++) {
if (spids[i] != null) { SpriteEventController.unregister(spids[i]); }
}
this._bound = false;
}
};
if (typeof window !== 'undefined') { window.RecordView = RecordView; }
if (typeof module !== 'undefined' && module.exports) { module.exports = RecordView; }
@@ -0,0 +1,93 @@
// ============================================================================
// RecordViewDefaultConfig.js —— RecordView 默认配置(= 现有战绩界面群组/精灵 ID)
// 纯数据;RecordView 逻辑零硬编码 ID,不传 config 时用本默认。换游戏只换这份。
// 权威来源:01_SubGame_modify.js 的 gameCombat.combatPageOneConfig/combatPageTwoConfig 等。
// 严格 ES5。
// ============================================================================
var RecordViewDefaultConfig = {
common: {
panelGroup: 7, // openNewCombat: set_group(7,37,1,...) / closeNewCombat: set_group(7,37,0,...)
titleSpid: 82, // set_self(82,37,1,...) + set_self(82,43,1/2,...)
typeTagSpid: 83, // openNewCombat: set_self(83,43,1/2,...) 类型标签按 type
backBtn: 55, // case 55: 返回
closeBtn: [49, 61], // case 49 / case 61: 关闭
prevBtn: 104, // case 104: 上一页
nextBtn: 105, // case 105: 下一页
typeBtns: [84, 85], // case 84 / 85: 类型切换
reqBtn: 13, // case 13: 发送战绩请求
countSpid: 684, // set_self(684,7,CombatCount,...) 总数显示精灵
countHideSpid: 683, // set_self(683,37,0,...) 隐藏总数精灵
resetGroups: [7, 8, 9, 13, 14, 15, 16, 17] // closeNewCombat: set_group(7~9,13~17,37,0,...)
},
pageOne: {
groupId: 8, // newCreateCombatPageOne: set_group(8,37,1,...)
containerSpid: 63, // fSpid:63
rowTemplate: {
bg: 64, // bgSpid:64
split: 86, // splitSpid:86
btn: 51, // btnSpid:51
txt1: 65, // txt1Spid:65
txt2: 66, // txt2Spid:66
txt3: 67 // txt3Spid:67
},
tagBase: {
bg: 1, // bgTag:1
split: 500, // splitTag:500
btn: 1000, // btnTag:1000
txt1: 1500, // txt1Tag:1500
txt2: 3000, // txt2Tag:3000
txt3: 4500 // txt3Tag:4500
},
clip: { x: 64, y: 114, w: 1175, h: 515 }, // clip_x/y/w/h
layout: {
rowSpace: 167, // bgSpace:167
txt1Pos: [[35, 15], [35, 60], [35, 105]], // txt1Position
txt2Pos: [[130, 15], [130, 60], [130, 105]], // txt2Position
txt3Pos5: [[400, 45], [540, 45], [680, 45], [820, 45], [960, 45]], // txt3Position5
txt3Pos10: [[400, 10], [540, 10], [680, 10], [820, 10], [960, 10], // txt3Position10
[400, 80], [540, 80], [680, 80], [820, 80], [960, 80]],
splitPos: [338, 74], // splitPosition
btnPos: [1065, 0], // btnPosition
txtWidth: 10, // txt1Width/txt2Width/txt3Width
nickLimit5: 8, // nickNameLimit5
nickLimit10: 8, // nickNameLimit10
nsSpace: 30 // nsSpace
}
},
pageTwo: {
groupId: 9, // newCreateCombatPageTwo: set_group(9,37,1,...)
containerSpid: 87, // fSpid:87
rowTemplate: {
bg: 88, // bgSpid:88
split: 91, // splitSpid:91
btn: 54, // btnSpid:54
txt1: 89, // txt1Spid:89
// txt2 注释掉(源码 //txt2Spid:66)
txt3: 90 // txt3Spid:90
},
tagBase: {
bg: 1, // bgTag:1
split: 500, // splitTag:500
btn: 1000, // btnTag:1000
txt1: 1500, // txt1Tag:1500
txt2: 3000, // txt2Tag:3000(源码保留)
txt3: 4500 // txt3Tag:4500
},
clip: { x: 64, y: 114, w: 1175, h: 515 }, // clip_x/y/w/h
layout: {
rowSpace: 167, // bgSpace:167
txt1Pos: [[90, 50], [35, 60], [35, 105]], // txt1Position(3项,源码权威)
txt3Pos5: [[400, 45], [540, 45], [680, 45], [820, 45], [960, 45]], // txt3Position5
txt3Pos10: [[400, 10], [540, 10], [680, 10], [820, 10], [960, 10], // txt3Position10
[400, 80], [540, 80], [680, 80], [820, 80], [960, 80]],
splitPos: [338, 74], // splitPosition
btnPos: [1065, 0], // btnPosition
txtWidth: 10, // txt1Width/txt2Width/txt3Width
nickLimit5: 2, // nickNameLimit5
nickLimit10: 2, // nickNameLimit10
nsSpace: 30 // nsSpace
}
}
};
if (typeof window !== 'undefined') { window.RecordViewDefaultConfig = RecordViewDefaultConfig; }
if (typeof module !== 'undefined' && module.exports) { module.exports = RecordViewDefaultConfig; }
@@ -0,0 +1,650 @@
/**
* ============================================================================
* SpriteCopyUtils.js - 精灵复制底层工具
* ============================================================================
*
* 封装 gameabc 框架的精灵复制核心功能,提供更易用的 API。
* 这是一个底层工具类,DynamicSpriteList 基于此构建。
*
* 核心概念:
* - 父精灵 (Parent Sprite): 容器精灵,复制出的子精灵将附着其上
* - 模板精灵 (Template Sprite): 提供视觉样式的精灵模板
* - 子精灵 (Child Sprite): 运行时动态复制创建的精灵
* - Tag: 子精灵的唯一标识符,用于删除和点击识别
*
* ============================================================================
* @guide 开发指南 - SpriteCopyUtils 使用规范
* ============================================================================
*
* 【创建动态精灵(模板复制)】
* // 在编辑器中预先放置一个模板精灵(通常隐藏),用作样式来源
* // 运行时从该模板复制,附着到容器精灵上
*
* var parentId = sprites.LIST_CONTAINER; // 容器精灵ID(编辑器放置)
* var templateId = sprites.ROW_TEMPLATE; // 模板精灵ID(编辑器放置,通常隐藏)
* var tag = rowIndex; // 行索引作为tag(在父精灵内唯一)
*
* var spriteId = SpriteCopyUtils.create(parentId, templateId, 0, rowIndex * rowHeight, tag);
* // spriteId 是字符串格式:"parentIdaddtag",如 "2836add0"
*
* // 创建后可通过 SpriteManager 设置属性(spriteId 是字符串,SpriteManager 自动处理)
* SpriteManager.setFrame(spriteId, card.code - 1);
* SpriteManager.setText(spriteId, String(score));
* SpriteManager.show(spriteId);
*
* 【删除动态精灵】
* SpriteCopyUtils.remove(parentId, tag); // 删除单个
* SpriteCopyUtils.removeRange(parentId, 0, 10); // 删除tag 0-9
*
* 【hit-test 点击检测(在鼠标事件中使用)】
* // 在 SpriteEventController 的 mouseUp 回调中:
* var clickedTag = SpriteCopyUtils.hitTest(parentId, event.offsetX, event.offsetY);
* if (clickedTag !== SpriteCopyUtils.HIT_NONE) {
* // clickedTag 是被点击子精灵的 tag
* var rowIndex = clickedTag;
* self._onRowClick(rowIndex);
* }
*
* 【典型使用场景:听牌提示列表(TingHintView 模式)】
* // 1. 编辑器中放置:容器精灵 + 行背景模板 + 牌图模板 + 分数模板
* // 2. 运行时:为每条听牌提示创建一行
* for (var i = 0; i < tingHints.length; i++) {
* var hint = tingHints[i];
* var yOffset = i * ROW_HEIGHT;
*
* // 复制行背景
* var bgId = SpriteCopyUtils.create(containerId, bgTemplate, 0, yOffset, i);
*
* // 复制牌图
* var cardId = SpriteCopyUtils.create(containerId, cardTemplate, CARD_X, yOffset, i + 1000);
* SpriteManager.setFrame(cardId, hint.code - 1);
*
* // 复制分数标签
* var scoreId = SpriteCopyUtils.create(containerId, scoreTemplate, SCORE_X, yOffset, i + 2000);
* SpriteManager.setText(scoreId, String(hint.score));
* }
* // 3. 清理:游戏结束或弹窗关闭时
* SpriteCopyUtils.removeRange(containerId, 0, tingHints.length);
* SpriteCopyUtils.removeRange(containerId, 1000, 1000 + tingHints.length);
* SpriteCopyUtils.removeRange(containerId, 2000, 2000 + tingHints.length);
*
* 【注意事项】
* - tag 在同一个 parentId 下必须唯一;不同列使用不同的 tag 偏移(如+1000, +2000)
* - 复制精灵的 ID 格式 "parentIdaddtag" 是字符串,SpriteManager 已支持此格式
* - 如果使用 DynamicSpriteList,则无需手动调用 SpriteCopyUtils,列表内部已封装
*
* @author JinXian Team
* @version 1.0.0
* @since 2025-12-13
*/
'use strict';
/**
* 精灵复制工具类
* @namespace
*/
var SpriteCopyUtils = {};
// ============================================================================
// 精灵创建与删除
// ============================================================================
/**
* 从模板复制创建精灵
*
* @param {number} parentId - 父精灵ID(容器),子精灵将附着在此精灵上
* @param {number} templateId - 模板精灵ID,复制其视觉属性
* @param {number} x - 相对于父精灵的X坐标
* @param {number} y - 相对于父精灵的Y坐标
* @param {number} tag - 子精灵标识符(在父精灵内唯一)
* @returns {string} 复合精灵ID,格式为 "parentId + 'add' + tag"
*
* @example
* // 在父精灵100上,基于模板200,在位置(50,30)创建子精灵,tag为1
* var spriteId = SpriteCopyUtils.create(100, 200, 50, 30, 1);
*
* // 设置子精灵的文字内容
* set_self(spriteId, 7, '你好世界', 0, 0);
*
* // 设置子精灵的帧
* set_self(spriteId, 43, 2, 0, 0);
*/
SpriteCopyUtils.create = function(parentId, templateId, x, y, tag) {
return ifast_addtospritefromspritecopy(parentId, templateId, x, y, tag);
};
/**
* 删除复制创建的子精灵
*
* @param {number} parentId - 父精灵ID
* @param {number} tag - 要删除的子精灵标识符
*
* @example
* // 删除父精灵100上tag为1的子精灵
* SpriteCopyUtils.remove(100, 1);
*/
SpriteCopyUtils.remove = function(parentId, tag) {
ifast_dllpritefromspritecopy(parentId, tag);
};
/**
* 批量删除复制创建的子精灵
*
* @param {number} parentId - 父精灵ID
* @param {number} startTag - 起始tag(包含)
* @param {number} endTag - 结束tag(不包含)
*
* @example
* // 删除父精灵100上tag从1到10的所有子精灵
* SpriteCopyUtils.removeRange(100, 1, 11);
*/
SpriteCopyUtils.removeRange = function(parentId, startTag, endTag) {
for (var tag = startTag; tag < endTag; tag++) {
ifast_dllpritefromspritecopy(parentId, tag);
}
};
/**
* 批量删除指定tag数组的子精灵
*
* @param {number} parentId - 父精灵ID
* @param {Array<number>} tags - tag数组
*
* @example
* // 删除父精灵100上tag为1, 3, 5, 7的子精灵
* SpriteCopyUtils.removeByTags(100, [1, 3, 5, 7]);
*/
SpriteCopyUtils.removeByTags = function(parentId, tags) {
for (var i = 0; i < tags.length; i++) {
ifast_dllpritefromspritecopy(parentId, tags[i]);
}
};
// ============================================================================
// 点击检测
// ============================================================================
/**
* 检测点击了父精灵上的哪个子精灵
*
* @param {number} parentId - 父精灵ID
* @param {number} x - 点击位置X坐标(屏幕坐标)
* @param {number} y - 点击位置Y坐标(屏幕坐标)
* @returns {number} 被点击的子精灵tag,未命中返回 -99999999
*
* @example
* // 在mouseup事件中检测点击
* var clickedTag = SpriteCopyUtils.hitTest(100, upx, upy);
* if (clickedTag !== -99999999) {
* console.log('点击了tag为', clickedTag, '的子精灵');
* }
*/
SpriteCopyUtils.hitTest = function(parentId, x, y) {
return ifast_check_add(parentId, x, y);
};
/**
* 检测点击是否命中任何子精灵
*
* @param {number} parentId - 父精灵ID
* @param {number} x - 点击位置X坐标
* @param {number} y - 点击位置Y坐标
* @returns {boolean} 是否命中
*
* @example
* if (SpriteCopyUtils.isHit(100, upx, upy)) {
* console.log('点击到了子精灵');
* }
*/
SpriteCopyUtils.isHit = function(parentId, x, y) {
return ifast_check_add(parentId, x, y) !== -99999999;
};
/**
* 检测点击的tag是否在指定范围内
*
* @param {number} parentId - 父精灵ID
* @param {number} x - 点击位置X坐标
* @param {number} y - 点击位置Y坐标
* @param {number} startTag - 起始tag(包含)
* @param {number} endTag - 结束tag(不包含)
* @returns {number} 如果在范围内返回tag,否则返回 -1
*
* @example
* // 检测是否点击了按钮区域(tag 1000-1999)
* var btnTag = SpriteCopyUtils.hitTestInRange(100, upx, upy, 1000, 2000);
* if (btnTag !== -1) {
* var buttonIndex = btnTag - 1000;
* console.log('点击了第', buttonIndex, '个按钮');
* }
*/
SpriteCopyUtils.hitTestInRange = function(parentId, x, y, startTag, endTag) {
var tag = ifast_check_add(parentId, x, y);
if (tag >= startTag && tag < endTag) {
return tag;
}
return -1;
};
// ============================================================================
// 精灵ID工具
// ============================================================================
/**
* 生成复合精灵ID
*
* @param {number} parentId - 父精灵ID
* @param {number} tag - 子精灵tag
* @returns {string} 复合精灵ID
*
* @example
* var spriteId = SpriteCopyUtils.getSpriteId(100, 5);
* // 返回 "100add5"
* set_self(spriteId, 7, '文字内容', 0, 0);
*/
SpriteCopyUtils.getSpriteId = function(parentId, tag) {
return parentId + 'add' + tag;
};
/**
* 从复合精灵ID解析父精灵ID和tag
*
* @param {string} spriteId - 复合精灵ID
* @returns {Object|null} 包含parentId和tag的对象,解析失败返回null
*
* @example
* var info = SpriteCopyUtils.parseSpriteId('100add5');
* // 返回 { parentId: 100, tag: 5 }
*/
SpriteCopyUtils.parseSpriteId = function(spriteId) {
if (typeof spriteId !== 'string') {
return null;
}
var parts = spriteId.split('add');
if (parts.length !== 2) {
return null;
}
var parentId = parseInt(parts[0], 10);
var tag = parseInt(parts[1], 10);
if (isNaN(parentId) || isNaN(tag)) {
return null;
}
return {
parentId: parentId,
tag: tag
};
};
// ============================================================================
// 批量创建工具
// ============================================================================
/**
* 批量创建子精灵(网格布局)
*
* @param {Object} options - 配置选项
* @param {number} options.parentId - 父精灵ID
* @param {number} options.templateId - 模板精灵ID
* @param {number} options.count - 创建数量
* @param {number} options.startTag - 起始tag
* @param {number} options.startX - 起始X坐标
* @param {number} options.startY - 起始Y坐标
* @param {number} options.spacingX - X方向间距
* @param {number} options.spacingY - Y方向间距
* @param {number} [options.columns=0] - 每行列数,0表示不换行
* @returns {Array<string>} 创建的精灵ID数组
*
* @example
* // 创建3x3的网格(9个精灵)
* var sprites = SpriteCopyUtils.createGrid({
* parentId: 100,
* templateId: 200,
* count: 9,
* startTag: 1,
* startX: 50,
* startY: 50,
* spacingX: 80,
* spacingY: 80,
* columns: 3
* });
*/
SpriteCopyUtils.createGrid = function(options) {
var result = [];
var columns = options.columns || 0;
for (var i = 0; i < options.count; i++) {
var col = columns > 0 ? (i % columns) : i;
var row = columns > 0 ? Math.floor(i / columns) : 0;
var x = options.startX + col * options.spacingX;
var y = options.startY + row * options.spacingY;
var tag = options.startTag + i;
var spriteId = ifast_addtospritefromspritecopy(
options.parentId,
options.templateId,
x,
y,
tag
);
result.push(spriteId);
}
return result;
};
/**
* 批量创建子精灵(垂直列表布局)
*
* @param {Object} options - 配置选项
* @param {number} options.parentId - 父精灵ID
* @param {number} options.templateId - 模板精灵ID
* @param {number} options.count - 创建数量
* @param {number} options.startTag - 起始tag
* @param {number} options.x - X坐标
* @param {number} options.startY - 起始Y坐标
* @param {number} options.rowHeight - 行高
* @returns {Array<string>} 创建的精灵ID数组
*
* @example
* // 创建5行的垂直列表
* var sprites = SpriteCopyUtils.createVerticalList({
* parentId: 100,
* templateId: 200,
* count: 5,
* startTag: 1,
* x: 50,
* startY: 100,
* rowHeight: 60
* });
*/
SpriteCopyUtils.createVerticalList = function(options) {
return SpriteCopyUtils.createGrid({
parentId: options.parentId,
templateId: options.templateId,
count: options.count,
startTag: options.startTag,
startX: options.x,
startY: options.startY,
spacingX: 0,
spacingY: options.rowHeight,
columns: 1
});
};
/**
* 批量创建子精灵(水平列表布局)
*
* @param {Object} options - 配置选项
* @param {number} options.parentId - 父精灵ID
* @param {number} options.templateId - 模板精灵ID
* @param {number} options.count - 创建数量
* @param {number} options.startTag - 起始tag
* @param {number} options.startX - 起始X坐标
* @param {number} options.y - Y坐标
* @param {number} options.colWidth - 列宽
* @returns {Array<string>} 创建的精灵ID数组
*
* @example
* // 创建一行5个精灵
* var sprites = SpriteCopyUtils.createHorizontalList({
* parentId: 100,
* templateId: 200,
* count: 5,
* startTag: 1,
* startX: 50,
* y: 100,
* colWidth: 80
* });
*/
SpriteCopyUtils.createHorizontalList = function(options) {
return SpriteCopyUtils.createGrid({
parentId: options.parentId,
templateId: options.templateId,
count: options.count,
startTag: options.startTag,
startX: options.startX,
startY: options.y,
spacingX: options.colWidth,
spacingY: 0,
columns: 0
});
};
// ============================================================================
// 属性设置工具
// ============================================================================
/**
* 批量设置子精灵的文字内容
*
* @param {number} parentId - 父精灵ID
* @param {number} startTag - 起始tag
* @param {Array<string>} texts - 文字内容数组
*
* @example
* // 设置tag 1-5的文字内容
* SpriteCopyUtils.setTexts(100, 1, ['第1行', '第2行', '第3行', '第4行', '第5行']);
*/
SpriteCopyUtils.setTexts = function(parentId, startTag, texts) {
for (var i = 0; i < texts.length; i++) {
var spriteId = parentId + 'add' + (startTag + i);
set_self(spriteId, 7, texts[i], 0, 0);
}
};
/**
* 批量设置子精灵的帧
*
* @param {number} parentId - 父精灵ID
* @param {number} startTag - 起始tag
* @param {Array<number>} frames - 帧号数组
*
* @example
* // 设置tag 1-5的帧
* SpriteCopyUtils.setFrames(100, 1, [1, 2, 3, 4, 5]);
*/
SpriteCopyUtils.setFrames = function(parentId, startTag, frames) {
for (var i = 0; i < frames.length; i++) {
var spriteId = parentId + 'add' + (startTag + i);
set_self(spriteId, 43, frames[i], 0, 0);
}
};
/**
* 批量设置子精灵的可见性
*
* @param {number} parentId - 父精灵ID
* @param {number} startTag - 起始tag
* @param {number} count - 精灵数量
* @param {boolean} visible - 是否可见
*
* @example
* // 隐藏tag 1-10的所有精灵
* SpriteCopyUtils.setVisibility(100, 1, 10, false);
*/
SpriteCopyUtils.setVisibility = function(parentId, startTag, count, visible) {
for (var i = 0; i < count; i++) {
var spriteId = parentId + 'add' + (startTag + i);
set_self(spriteId, 37, visible ? 1 : 0, 0, 0);
}
};
// ============================================================================
// Tag 管理器
// ============================================================================
/**
* 创建 Tag 管理器
* 用于管理多种类型精灵的 tag 分配
*
* @param {Object} ranges - tag 范围配置
* @returns {Object} Tag 管理器实例
*
* @example
* // 创建tag管理器
* var tagManager = SpriteCopyUtils.createTagManager({
* background: { start: 1, size: 100 }, // 1-100
* title: { start: 101, size: 100 }, // 101-200
* button: { start: 201, size: 100 } // 201-300
* });
*
* // 获取下一个tag
* var bgTag = tagManager.next('background'); // 返回 1
* var bgTag2 = tagManager.next('background'); // 返回 2
*
* // 重置计数器
* tagManager.reset();
*
* // 获取某类型的tag范围
* var range = tagManager.getRange('button'); // { start: 201, end: 301 }
*/
SpriteCopyUtils.createTagManager = function(ranges) {
var counters = {};
var rangeConfig = {};
// 初始化
var types = Object.keys(ranges);
for (var i = 0; i < types.length; i++) {
var type = types[i];
var config = ranges[type];
rangeConfig[type] = {
start: config.start,
end: config.start + config.size
};
counters[type] = config.start;
}
return {
/**
* 获取下一个可用的tag
* @param {string} type - 类型名称
* @returns {number} tag值
*/
next: function(type) {
if (!counters.hasOwnProperty(type)) {
throw new Error('TagManager: unknown type ' + type);
}
return counters[type]++;
},
/**
* 获取当前计数器值(不递增)
* @param {string} type - 类型名称
* @returns {number} 当前计数器值
*/
current: function(type) {
return counters[type];
},
/**
* 重置所有计数器
*/
reset: function() {
for (var t = 0; t < types.length; t++) {
counters[types[t]] = rangeConfig[types[t]].start;
}
},
/**
* 重置指定类型的计数器
* @param {string} type - 类型名称
*/
resetType: function(type) {
if (rangeConfig.hasOwnProperty(type)) {
counters[type] = rangeConfig[type].start;
}
},
/**
* 获取某类型的tag范围
* @param {string} type - 类型名称
* @returns {Object} { start, end }
*/
getRange: function(type) {
return rangeConfig[type] ? {
start: rangeConfig[type].start,
end: rangeConfig[type].end
} : null;
},
/**
* 获取某类型已分配的tag数量
* @param {string} type - 类型名称
* @returns {number} 已分配数量
*/
getAllocatedCount: function(type) {
return counters[type] - rangeConfig[type].start;
},
/**
* 检测tag属于哪个类型
* @param {number} tag - tag值
* @returns {string|null} 类型名称,不属于任何类型返回null
*/
getTypeByTag: function(tag) {
for (var t = 0; t < types.length; t++) {
var type = types[t];
var range = rangeConfig[type];
if (tag >= range.start && tag < range.end) {
return type;
}
}
return null;
},
/**
* 计算tag在其类型中的索引
* @param {number} tag - tag值
* @returns {number} 索引,不属于任何类型返回-1
*/
getIndexByTag: function(tag) {
for (var t = 0; t < types.length; t++) {
var type = types[t];
var range = rangeConfig[type];
if (tag >= range.start && tag < range.end) {
return tag - range.start;
}
}
return -1;
}
};
};
// ============================================================================
// 精灵ID工具
// ============================================================================
/**
* 根据容器ID和tag生成复制精灵的实际ID
*
* 对于通过 SpriteCopyUtils.create() 复制创建的精灵,
* 其实际ID遵循格式:容器精灵ID + 'add' + tag
*
* @param {number} containerId - 容器精灵ID
* @param {number} tag - 子精灵标识符
* @returns {string} 精灵ID,格式为 "containerIdaddtag"
*
* @example
* // 容器ID为100,tag为20
* var spriteId = SpriteCopyUtils.getSpriteId(100, 20);
* // 返回: "100add20"
*
* // 可用于获取精灵尺寸、位置等
* var size = SpriteManager.getSize(spriteId);
* var pos = SpriteManager.getPosition(spriteId);
*/
SpriteCopyUtils.getSpriteId = function(containerId, tag) {
return containerId + 'add' + tag;
};
// ============================================================================
// 导出
// ============================================================================
if (typeof module !== 'undefined' && module.exports) {
module.exports = SpriteCopyUtils;
} else if (typeof window !== 'undefined') {
window.SpriteCopyUtils = SpriteCopyUtils;
}
+607
View File
@@ -0,0 +1,607 @@
/**
* ============================================================================
* UIManager.js - UI 管理器(gameabc 框架层)
* ============================================================================
*
* 职责:
* - UI 组件的注册、注销、查询和生命周期管理
* - 场景定义与切换(BaseComponent 组合的具名快照)
* - 全局公共 UI:Loading / Message / Confirm 对话框
*
* 设计原则:
* 1. 精灵由 gameabc 编辑器预先创建,UIManager 只控制显示/隐藏状态
* 2. 本文件不含任何游戏专属逻辑,可用于任何基于 gameabc 的子游戏
* 3. 游戏层通过挂载方法(UIManager.xxx = function(){})扩展专属功能
*
* 全局 UI 配置(由子游戏注入,框架不读取任何子游戏全局名):
* 子游戏在初始化时通过 init(config) 或 configureGlobalUI(config) 传入精灵常量:
* config.loadingUI = { LAYER, GROUP_ID, SPRITES: { BACKGROUND, LOADING_ICON, LOADING_TEXT } }
* config.messageUI = { LAYER, GROUP_ID, SPRITES: { BACKGROUND, TEXT } }
* config.confirmUI = { LAYER, GROUP_ID, SPRITES: { BACKGROUND, TITLE_TEXT, MESSAGE_TEXT, BTN_CONFIRM, BTN_CANCEL } }
* 不传则跳过 Loading/Message/Confirm 全局 UI(场景/组件等其余功能不受影响)。
*
* 依赖(必须在本文件之前加载):
* EventBus (gameabc-framework/system/EventBus.js)
* SpriteManager (gameabc-framework/core/SpriteManager.js)
*
* ============================================================================
* @guide 游戏侧扩展模式
* ============================================================================
*
* 游戏层通过独立 JS 文件向 UIManager 挂载专属方法:
*
* // CreateRoomUI.js(示例)
* (function() {
* 'use strict';
* var CREATE_ROOM_UI = SpriteConstants.CREATE_ROOM_UI;
*
* UIManager.showCreateRoom = function(config) {
* // 游戏专属逻辑...
* SpriteManager.showGroup(CREATE_ROOM_UI.GROUP_ID);
* };
* UIManager.hideCreateRoom = function() {
* SpriteManager.hideGroup(CREATE_ROOM_UI.GROUP_ID);
* };
* })();
*
* ============================================================================
* @guide 场景管理完整用法
* ============================================================================
*
* // 1. 初始化(在 UIBootstrap 中调用);若有全局 UI,注入其精灵常量(框架不读子游戏全局名):
* UIManager.init({ loadingUI: SpriteConstants.LOADING_UI, messageUI: SpriteConstants.MESSAGE_UI, confirmUI: SpriteConstants.CONFIRM_UI });
*
* // 2. 注册组件(BaseComponent 实例)
* UIManager.registerComponent('RoomView', roomView);
* UIManager.registerComponent('GameView', gameView);
*
* // 3. 注册场景(关联组件名列表)
* UIManager.registerScene(UIManager.SCENES.ROOM, ['RoomView']);
* UIManager.registerScene(UIManager.SCENES.GAME, ['GameView', 'JingCardView']);
*
* // 4. 切换场景(自动隐藏旧场景,显示新场景,发射 SCENE_CHANGED 事件)
* UIManager.switchToScene(UIManager.SCENES.GAME);
*
* // 5. 全局 UI
* UIManager.showLoading('正在加载...');
* UIManager.showMessage('操作成功', 2000);
* UIManager.showConfirm('确认', '是否退出?', onOK, onCancel);
*
* @version 1.0.0
*/
var UIManager = (function () {
'use strict';
// ==================== 私有变量 ====================
/**
* 全局 UI 精灵常量(由子游戏注入:init(config) / configureGlobalUI)
* 框架不读取任何子游戏全局名;未注入时三者为 null,对应全局 UI 不可用(优雅跳过)。
*/
var LOADING_UI = null;
var MESSAGE_UI = null;
var CONFIRM_UI = null;
/**
* 已注册的 UI 组件
* { componentName: BaseComponent 实例 }
*/
var _components = {};
/**
* 已注册的场景
* { sceneName: [componentName, ...] }
*/
var _scenes = {};
/**
* 当前活跃场景名
* @type {string|null}
*/
var _currentScene = null;
/**
* 初始化状态
*/
var _isInitialized = false;
// ==================== 场景枚举 ====================
var SCENES = {
ROOM: 'ROOM',
GAME: 'GAME',
LOADING: 'LOADING'
};
// ==================== 私有方法 ====================
/**
* 应用子游戏注入的全局 UI 配置(Loading/Message/Confirm),并隐藏所有全局弹层。
* 框架不认识任何子游戏全局名,配置由子游戏在 init()/configureGlobalUI() 时传入。
* @param {Object} config - { loadingUI, messageUI, confirmUI }
* @returns {boolean} 是否成功配置(缺省或不完整则跳过,返回 false)
* @private
*/
function _applyGlobalUIConfig(config) {
if (!config || !config.loadingUI || !config.messageUI || !config.confirmUI) {
console.warn('[UIManager] 未提供完整的全局 UI 配置(loadingUI/messageUI/confirmUI),跳过全局 UI 初始化');
return false;
}
LOADING_UI = config.loadingUI;
MESSAGE_UI = config.messageUI;
CONFIRM_UI = config.confirmUI;
_initLoadingUI();
_initMessageUI();
_initConfirmUI();
return true;
}
/** @private */
function _initLoadingUI() {
SpriteManager.hide(LOADING_UI.SPRITES.BACKGROUND);
SpriteManager.hide(LOADING_UI.SPRITES.LOADING_ICON);
SpriteManager.hide(LOADING_UI.SPRITES.LOADING_TEXT);
SpriteManager.hideGroup(LOADING_UI.GROUP_ID);
console.log('[UIManager] Loading UI 初始化 (Layer ' + LOADING_UI.LAYER + ')');
}
/** @private */
function _initMessageUI() {
SpriteManager.hide(MESSAGE_UI.SPRITES.BACKGROUND);
SpriteManager.hide(MESSAGE_UI.SPRITES.TEXT);
SpriteManager.hideGroup(MESSAGE_UI.GROUP_ID);
console.log('[UIManager] Message UI 初始化 (Layer ' + MESSAGE_UI.LAYER + ')');
}
/** @private */
function _initConfirmUI() {
SpriteManager.hide(CONFIRM_UI.SPRITES.BACKGROUND);
SpriteManager.hide(CONFIRM_UI.SPRITES.TITLE_TEXT);
SpriteManager.hide(CONFIRM_UI.SPRITES.MESSAGE_TEXT);
SpriteManager.hide(CONFIRM_UI.SPRITES.BTN_CONFIRM);
SpriteManager.hide(CONFIRM_UI.SPRITES.BTN_CANCEL);
SpriteManager.hideGroup(CONFIRM_UI.GROUP_ID);
console.log('[UIManager] Confirm UI 初始化 (Layer ' + CONFIRM_UI.LAYER + ')');
}
/**
* 隐藏当前场景所有可见组件
* @private
*/
function _hideCurrentScene() {
if (!_currentScene || !_scenes[_currentScene]) { return; }
var names = _scenes[_currentScene];
for (var i = 0; i < names.length; i++) {
var c = _components[names[i]];
if (c && c.isVisible) { c.hide(); }
}
}
/**
* 显示指定场景的所有组件
* @private
*/
function _showScene(sceneName) {
if (!_scenes[sceneName]) {
console.error('[UIManager] 场景不存在:', sceneName);
return;
}
var names = _scenes[sceneName];
for (var i = 0; i < names.length; i++) {
var c = _components[names[i]];
if (c && !c.isVisible) { c.show(); }
}
}
// ==================== 公共 API ====================
var publicAPI = {
/**
* 场景枚举常量
*/
SCENES: SCENES,
/**
* 是否已初始化(外部只读)
*/
initialized: false,
// ============================================================
// 初始化 / 销毁
// ============================================================
/**
* 初始化 UIManager
* @param {Object} [globalUIConfig] - 可选的全局 UI 配置 { loadingUI, messageUI, confirmUI },
* 由子游戏传入(如各自的精灵常量);框架不主动读取任何子游戏全局名。
* 不传则跳过 Loading/Message/Confirm 全局 UI(可后续用 configureGlobalUI 配置)。
* @returns {boolean}
*/
init: function (globalUIConfig) {
if (_isInitialized) {
console.warn('[UIManager] 已初始化,跳过');
return true;
}
console.log('[UIManager] 初始化 (v1.0.0)');
try {
_components = {};
_scenes = {};
_currentScene = null;
// 全局 UI 为可选能力:未注入配置则跳过,不阻断 UIManager 初始化
if (globalUIConfig) { _applyGlobalUIConfig(globalUIConfig); }
_isInitialized = true;
this.initialized = true;
console.log('[UIManager] 初始化完成');
return true;
} catch (err) {
console.error('[UIManager] 初始化失败:', err);
return false;
}
},
/**
* 配置全局 UI(Loading/Message/Confirm)的精灵常量。供子游戏注入,可在 init 之后单独调用。
* @param {Object} config - { loadingUI, messageUI, confirmUI }
* @returns {boolean}
*/
configureGlobalUI: function (config) {
return _applyGlobalUIConfig(config);
},
// ============================================================
// 组件管理
// ============================================================
/**
* 注册 UI 组件
* @param {string} name - 组件名称
* @param {Object} component - BaseComponent 实例(必须已 init)
* @returns {boolean}
*/
registerComponent: function (name, component) {
if (!name || typeof name !== 'string') {
console.error('[UIManager] registerComponent: name 必须是字符串');
return false;
}
if (!component || !component.isInitialized) {
console.error('[UIManager] registerComponent: 组件未初始化:', name);
return false;
}
if (_components[name]) {
console.warn('[UIManager] registerComponent: 覆盖已存在组件:', name);
}
_components[name] = component;
console.log('[UIManager] 注册组件 "' + name + '"');
return true;
},
/**
* 获取已注册组件
* @param {string} name
* @returns {Object|null}
*/
getComponent: function (name) {
return _components[name] || null;
},
/**
* 注销组件(不销毁,只从注册表移除)
* @param {string} name
* @returns {boolean}
*/
unregisterComponent: function (name) {
if (!_components[name]) {
console.warn('[UIManager] unregisterComponent: 组件不存在:', name);
return false;
}
var c = _components[name];
if (c.isVisible) { c.hide(); }
delete _components[name];
console.log('[UIManager] 注销组件 "' + name + '"');
return true;
},
/**
* 销毁组件(调用 component.destroy() 并移除)
* @param {string} name
* @returns {boolean}
*/
destroyComponent: function (name) {
if (!_components[name]) {
console.warn('[UIManager] destroyComponent: 组件不存在:', name);
return false;
}
_components[name].destroy();
delete _components[name];
console.log('[UIManager] 销毁组件 "' + name + '"');
return true;
},
/**
* 显示指定视图(便捷方法)
* @param {string} name
*/
showView: function (name) {
var c = _components[name];
if (!c) { console.warn('[UIManager] showView: 组件不存在:', name); return; }
c.show();
},
/**
* 隐藏指定视图(便捷方法)
* @param {string} name
*/
hideView: function (name) {
var c = _components[name];
if (!c) { console.warn('[UIManager] hideView: 组件不存在:', name); return; }
c.hide();
},
// ============================================================
// 场景管理
// ============================================================
/**
* 注册场景
* @param {string} sceneName
* @param {string[]} componentNames - 该场景包含的组件名列表
* @returns {boolean}
*/
registerScene: function (sceneName, componentNames) {
if (!sceneName || typeof sceneName !== 'string') {
console.error('[UIManager] registerScene: sceneName 必须是字符串');
return false;
}
if (!Array.isArray(componentNames)) {
console.error('[UIManager] registerScene: componentNames 必须是数组');
return false;
}
for (var i = 0; i < componentNames.length; i++) {
if (!_components[componentNames[i]]) {
console.error('[UIManager] registerScene: 组件不存在:', componentNames[i]);
return false;
}
}
_scenes[sceneName] = componentNames;
console.log('[UIManager] 注册场景 "' + sceneName + '" (' + componentNames.length + ' 个组件)');
return true;
},
/**
* 切换到指定场景
* 自动隐藏当前场景,显示新场景,发射 EventBus.Events.SCENE_CHANGED
* @param {string} sceneName
* @returns {boolean}
*/
switchToScene: function (sceneName) {
if (!_scenes[sceneName]) {
console.error('[UIManager] switchToScene: 场景不存在:', sceneName);
return false;
}
if (_currentScene === sceneName) {
return true;
}
console.log('[UIManager] 切换场景 "' + (_currentScene || 'null') + '" → "' + sceneName + '"');
_hideCurrentScene();
_showScene(sceneName);
var oldScene = _currentScene;
_currentScene = sceneName;
EventBus.emit(EventBus.Events.SCENE_CHANGED, { from: oldScene, to: sceneName });
return true;
},
/**
* 获取当前场景名
* @returns {string|null}
*/
getCurrentScene: function () {
return _currentScene;
},
// ============================================================
// 全局 UI - Loading
// ============================================================
/**
* 显示加载界面
* @param {string} [message='加载中...']
*/
showLoading: function (message) {
if (!LOADING_UI) { return; }
message = message || '加载中...';
SpriteManager.setText(LOADING_UI.SPRITES.LOADING_TEXT, message);
SpriteManager.show(LOADING_UI.SPRITES.BACKGROUND);
SpriteManager.show(LOADING_UI.SPRITES.LOADING_ICON);
SpriteManager.show(LOADING_UI.SPRITES.LOADING_TEXT);
SpriteManager.showGroup(LOADING_UI.GROUP_ID);
},
/**
* 隐藏加载界面
*/
hideLoading: function () {
if (!LOADING_UI) { return; }
SpriteManager.hide(LOADING_UI.SPRITES.BACKGROUND);
SpriteManager.hide(LOADING_UI.SPRITES.LOADING_ICON);
SpriteManager.hide(LOADING_UI.SPRITES.LOADING_TEXT);
SpriteManager.hideGroup(LOADING_UI.GROUP_ID);
},
// ============================================================
// 全局 UI - Message
// ============================================================
/**
* 显示轻提示(Toast,无精灵,仅 console)
* @param {string} message
*/
showToast: function (message) {
console.log('[UIManager] Toast:', message);
},
/**
* 显示一个带标签的提示效果(Toast 形式,通用,不含任何玩法语义)
* @param {string} tag - 效果标签(仅用于日志区分,由调用方自定义)
* @param {string} title - 提示标题
* @param {string} [description] - 补充说明
*/
showEffectToast: function (tag, title, description) {
var msg = description ? (title + ':' + description) : title;
console.log('[UIManager] EffectToast [' + tag + ']:', msg);
this.showToast(msg);
},
/**
* 显示消息提示(自动隐藏)
* @param {string} message
* @param {number} [duration=2000] 毫秒
*/
showMessage: function (message, duration) {
if (!MESSAGE_UI || !message) { return; }
duration = duration || 2000;
SpriteManager.setText(MESSAGE_UI.SPRITES.TEXT, message);
SpriteManager.show(MESSAGE_UI.SPRITES.BACKGROUND);
SpriteManager.show(MESSAGE_UI.SPRITES.TEXT);
SpriteManager.showGroup(MESSAGE_UI.GROUP_ID);
var self = this;
setTimeout(function () { self.hideMessage(); }, duration);
},
/**
* 隐藏消息提示
*/
hideMessage: function () {
if (!MESSAGE_UI) { return; }
SpriteManager.hide(MESSAGE_UI.SPRITES.BACKGROUND);
SpriteManager.hide(MESSAGE_UI.SPRITES.TEXT);
SpriteManager.hideGroup(MESSAGE_UI.GROUP_ID);
},
// ============================================================
// 全局 UI - Confirm
// ============================================================
/**
* 显示确认对话框
* @param {string} title
* @param {string} message
* @param {Function} [onConfirm]
* @param {Function} [onCancel]
*/
showConfirm: function (title, message, onConfirm, onCancel) {
if (!CONFIRM_UI || !message) { return; }
title = title || '确认';
SpriteManager.setText(CONFIRM_UI.SPRITES.TITLE_TEXT, title);
SpriteManager.setText(CONFIRM_UI.SPRITES.MESSAGE_TEXT, message);
SpriteManager.show(CONFIRM_UI.SPRITES.BACKGROUND);
SpriteManager.show(CONFIRM_UI.SPRITES.TITLE_TEXT);
SpriteManager.show(CONFIRM_UI.SPRITES.MESSAGE_TEXT);
SpriteManager.show(CONFIRM_UI.SPRITES.BTN_CONFIRM);
SpriteManager.show(CONFIRM_UI.SPRITES.BTN_CANCEL);
SpriteManager.showGroup(CONFIRM_UI.GROUP_ID);
// 回调通过全局变量传递(gameabc 引擎通过点击事件触发)
window._confirmCallback = { onConfirm: onConfirm, onCancel: onCancel };
},
/**
* 隐藏确认对话框
*/
hideConfirm: function () {
if (!CONFIRM_UI) { return; }
SpriteManager.hide(CONFIRM_UI.SPRITES.BACKGROUND);
SpriteManager.hide(CONFIRM_UI.SPRITES.TITLE_TEXT);
SpriteManager.hide(CONFIRM_UI.SPRITES.MESSAGE_TEXT);
SpriteManager.hide(CONFIRM_UI.SPRITES.BTN_CONFIRM);
SpriteManager.hide(CONFIRM_UI.SPRITES.BTN_CANCEL);
SpriteManager.hideGroup(CONFIRM_UI.GROUP_ID);
if (window._confirmCallback) { delete window._confirmCallback; }
},
// ============================================================
// 工具方法
// ============================================================
/**
* 获取所有已注册组件名称
* @returns {string[]}
*/
getRegisteredComponents: function () {
var names = [];
for (var n in _components) {
if (_components.hasOwnProperty(n)) { names.push(n); }
}
return names;
},
/**
* 获取所有已注册场景名称
* @returns {string[]}
*/
getRegisteredScenes: function () {
var names = [];
for (var n in _scenes) {
if (_scenes.hasOwnProperty(n)) { names.push(n); }
}
return names;
},
/**
* 获取当前状态快照
* @returns {Object}
*/
getState: function () {
return {
isInitialized: _isInitialized,
currentScene: _currentScene,
componentCount: Object.keys(_components).length,
sceneCount: Object.keys(_scenes).length,
components: this.getRegisteredComponents(),
scenes: this.getRegisteredScenes()
};
},
/**
* 打印状态(调试用)
*/
printState: function () {
console.log('[UIManager] 状态:', JSON.stringify(this.getState(), null, 2));
},
/**
* 销毁 UIManager(销毁所有组件并重置状态)
*/
destroy: function () {
console.log('[UIManager] 开始销毁');
for (var name in _components) {
if (_components.hasOwnProperty(name)) { this.destroyComponent(name); }
}
this.hideLoading();
this.hideMessage();
this.hideConfirm();
_components = {};
_scenes = {};
_currentScene = null;
_isInitialized = false;
this.initialized = false;
console.log('[UIManager] 销毁完成');
}
};
return publicAPI;
})();
if (typeof module !== 'undefined' && module.exports) {
module.exports = UIManager;
}
+336
View File
@@ -0,0 +1,336 @@
var gameabc_face = gameabc_face||{};
{
gameabc_face.tag=12; //定义你的游戏全局内存
gameabc_face.tag1=123;//定义你的游戏全局内存
gameabc_face.tag2=123;//定义你的游戏全局内存
gameabc_face.tag3=123;//定义你的游戏全局内存
gameabc_face.dfwgao=1;
}
gameabc_face.gamestart=function(gameid)
{
//游戏初始化代码
Logic.AppStart();
};
gameabc_face.ani_doend=function(id,sx,count,allend)
{
//logmessage(id+"/"+sx+"/"+count+"/"+allend);
//play_ani(0,2,18,50,200,0,1000,0,0,0,0,6000,1);//主动关闭
GameUI.utlani_doend(id,sx,count,allend);
gameCombat.utlani_doend(id,sx,count,allend);
};
gameabc_face.box_doend=function(id,sx,timelen)
{
//play_box 结束事件
//showmessage("box_doend:"+id+"/"+sx+"/"+timelen);
//logmessage("box_doend:"+id+"/"+sx+"/"+timelen);
};
gameabc_face.onloadurl=function(recid,rectype,url,error,count,len)
{
GameUI.onloadurl(recid,rectype,url,error,count,len);
};
gameabc_face.chongzhi=function(userid,zt,data)
{
//游戏接口代码
};
gameabc_face.onresize=function(pmw/*屏幕宽*/,pmh/*屏幕宽*/,sjweww/*设计宽*/,sjnewh/*设计宽*/,nweww/*显示宽*/,newh/*显示高*/)
{
//屏幕变化
// 在此调整 列表控件的宽高和区域 不是整体缩放
//logmessage("onresize:"+pmw+"/"+pmh+"/"+sjweww+"/"+sjnewh+"/"+nweww+"/"+newh);
};
gameabc_face.gamebegindraw=function(gameid, spid, times, timelong)
{
//更新开始代码
GameUI.utlgamebegindraw(gameid, spid, times, timelong);
};
gameabc_face.gameenddraw=function(gameid, spid, times, timelong)
{
//更新完成代码
GameUI.gameenddraw(gameid, spid, times, timelong);
};
gameabc_face.mousedown=function(gameid, spid, downx, downy, no1, no2, no3, no4, no5, no6)
{
//点击代码
GameUI.utlmousedown(gameid, spid, downx, downy, no1, no2, no3, no4, no5, no6);
Game_Modify.utlmousedown(gameid, spid, downx, downy, no1, no2, no3, no4, no5, no6);
gameCombat.utlmousedown(gameid, spid, downx, downy, no1, no2, no3, no4, no5, no6);
};
gameabc_face.mousedown_nomove=function(gameid, spid, downx, downy, timelong, no1, no2, no3, no4, no5)
{
//点击代没移动代码
GameUI.utlmousedown_nomove(gameid, spid, downx, downy, timelong, no1, no2, no3, no4, no5);
Game_Modify.utlmousedown_nomove(gameid, spid, downx, downy, timelong, no1, no2, no3, no4, no5);
};
gameabc_face.mouseup=function(gameid, spid_down, downx, downy, spid_up, upx, upy, timelong, no1, no2)
{
//点击弹起代码
//可以通过spid_down和spid_up 的比较 来判断是 点击还是 移动
GameUI.utlmouseup(gameid, spid_down, downx, downy, spid_up, upx, upy, timelong, no1, no2);
Game_Modify.mouseup(gameid, spid_down, downx, downy, spid_up, upx, upy, timelong, no1, no2);
gameCombat.utlmouseup(gameid, spid_down, downx, downy, spid_up, upx, upy, timelong, no1, no2);
};
gameabc_face.mousemove=function(gameid, spid, downx, downy, movex,movey ,timelong,offmovex, offmovey, no1)
{
//点击后移动代码
GameUI.utlmousemove(gameid, spid, downx, downy, movex,movey ,timelong,offmovex, offmovey, no1);
Game_Modify.utlmousemove(gameid, spid, downx, downy, movex,movey ,timelong,offmovex, offmovey, no1);
gameCombat.utlmousemove(gameid, spid, downx, downy, movex,movey ,timelong,offmovex, offmovey, no1);
};
gameabc_face.gamemydraw=function(gameid, spid, times, timelong, no2, no3, no4, no5, no6, no7)
{
//每个精灵更新绘画代码
GameUI.utlgamemydraw(gameid, spid, times, timelong, no2, no3, no4, no5, no6, no7);
Game_Modify.gamemydraw(gameid, spid, times, timelong, no2, no3, no4, no5, no6, no7);
gameCombat.utlgamemydraw(gameid, spid, times, timelong, no2, no3, no4, no5, no6, no7);
};
gameabc_face.gamemydrawbegin=function(gameid, spid, times, timelong, no2, no3, no4, no5, no6, no7)
{
//每个精灵更新前绘画代码
GameUI.utlgamemydrawbegin(gameid, spid, times, timelong, no2, no3, no4, no5, no6, no7);
Game_Modify.utlgamemydrawbegin(gameid, spid, times, timelong, no2, no3, no4, no5, no6, no7);
gameCombat.utlgamemydrawbegin(gameid, spid, times, timelong, no2, no3, no4, no5, no6, no7);
};
gameabc_face.ontimer= function(gameid, spid, /* 本次间隔多少次了 */ times, /* 本次间隔多久 */ timelong,/* 开启后运行多少次了 */ alltimes){
GameUI.utlontimer(gameid, spid, /* 本次间隔多少次了 */ times, /* 本次间隔多久 */ timelong,/* 开启后运行多少次了 */ alltimes);
};
gameabc_face.tcpconnected=function(tcpid)
{
};
gameabc_face.tcpmessage=function(tcpid,data)
{
;
};
gameabc_face.tcpdisconnected=function(tcpid)
{
};
gameabc_face.tcperror=function(tcpid,data)
{
};
gameabc_face.httpmessage=function(myid,url,data)
{
};
File diff suppressed because one or more lines are too long
+5544
View File
File diff suppressed because it is too large Load Diff
+5703
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+11710
View File
File diff suppressed because it is too large Load Diff