Merge remote-tracking branch 'origin/main' into feature/seven_day_gift

This commit is contained in:
keke
2026-09-09 18:08:46 +08:00
67 changed files with 6999 additions and 1376 deletions
@@ -0,0 +1,78 @@
/** 战令配置计算,不处理金币、登录或其他模块的同步。 */
export const PASS_PERIOD = 30 * 24 * 60 * 60 * 1000;
export const PASS_PRODUCTS = { battlepass: 1800, battlepass_30: 3000, battlepass_12: 1200 };
const OLD_TOKENS = [1, 2, 2, 2, 2, 2, 2, 2, 2, ...Array(21).fill(4)];
export function passTier(stage: any): number { return stage && stage.tier === 30 ? 30 : stage && stage.activate ? 18 : 0; }
export function legacyExperience(stage: any): number {
const level = Math.max(1, Math.min(30, Number(stage.progressLevel) || 1));
return OLD_TOKENS.slice(0, level - 1).reduce((n, cost) => n + cost, 0) + Math.min(OLD_TOKENS[level - 1], Number(stage.progress) || 0);
}
export function normalizePass(stage: any, rows: any[]): any {
if (!stage || !Number(stage.time)) return stage;
stage = { ...stage, free: [...(stage.free || [])], passCheck: [...(stage.passCheck || [])] };
if (!Number.isFinite(stage.experience)) {
const level = Math.max(1, Math.min(30, Number(stage.progressLevel) || 1));
stage.experience = rows.slice(0, level - 1).reduce((n, row) => n + Number(row.token), 0)
+ Math.min(Number(rows[level - 1].token), Number(stage.progress) || 0);
}
let remaining = Math.max(0, stage.experience), unlocked = 0;
for (const row of rows) {
if (remaining < Number(row.token)) break;
remaining -= Number(row.token);
unlocked++;
}
stage.unlocked = unlocked;
// 保持旧界面“当前档 + 当前档经验”的表示,完成一档时显示满格。
const complete = remaining === 0 || unlocked === rows.length;
stage.progressLevel = Math.max(1, Math.min(rows.length, unlocked + (complete ? 0 : 1)));
stage.progress = complete ? Number(rows[stage.progressLevel - 1].token) : remaining;
for (const lane of ['free', 'passCheck']) for (let i = 0; i < unlocked; i++) {
if (stage[lane][i] !== 0) stage[lane][i] = 1;
}
return stage;
}
export function mergePassInfo(raw: any, extra: any, rows: any[], oldRows: any[] = rows): any {
if (!raw) return raw;
for (const key of [1, 2]) {
const old = raw[key];
if (!old || !Number(old.time)) continue;
const saved = extra && extra[String(old.time)];
if (saved) {
raw[key] = { ...saved, activate: !!old.activate || !!saved.activate,
experience: saved.experience + Math.max(0, legacyExperience(old) - (saved.legacyExperience || 0)) };
for (const lane of ['free', 'passCheck']) {
raw[key][lane] = [...(saved[lane] || [])];
for (let i = 0; i < Math.min(30, (old[lane] || []).length); i++) if (old[lane][i] === 0) raw[key][lane][i] = 0;
}
}
raw[key] = normalizePass(raw[key], key === 1 ? oldRows : rows);
}
return raw;
}
export function legacyPassInfo(pass: any, rows: any[], oldRows: any[] = rows): any {
const legacy: any = {};
for (const key of [1, 2]) {
const stage = pass[key];
if (!stage || !Number(stage.time)) { legacy[key] = stage; continue; }
const level = Math.max(1, Math.min(30, stage.progressLevel));
const row = (key === 1 ? oldRows : rows)[stage.progressLevel - 1];
const progress = stage.unlocked >= level ? OLD_TOKENS[level - 1] : Math.min(OLD_TOKENS[level - 1], Math.floor(stage.progress * OLD_TOKENS[level - 1] / Math.max(1, Number(row.token))));
legacy[key] = { time: stage.time, activate: passTier(stage) > 0, progressLevel: level, progress,
free: (stage.free || []).slice(0, 30), passCheck: (stage.passCheck || []).slice(0, 30) };
}
return legacy;
}
export function passBounds(stage: any, count: number) {
const tier = passTier(stage);
const generated = Math.min(count, tier === 30 ? Math.max(40, (stage.unlocked || 0) + 5) : 40);
// 正常滚动至可解锁范围的下一档,后续奖励供上拉预览。
const content = Math.min(generated, tier === 30 ? Math.max(36, (stage.unlocked || 0) + 1) : 36);
return { generated, content };
}
export function canClaimPass(stage: any, index: number, left: boolean): boolean {
return !!stage && index >= 0 && index < stage.unlocked && (stage[left ? 'free' : 'passCheck'] || [])[index] === 1
&& (index < 35 ? left || passTier(stage) >= 18 : passTier(stage) === 30);
}
export function hasPassReward(stage: any): boolean {
return !!stage && (stage.free || []).some((_, i) => canClaimPass(stage, i, true) || canClaimPass(stage, i, false));
}
@@ -0,0 +1,10 @@
{
"ver": "1.1.0",
"uuid": "b0d9322f-2367-43e0-9be4-9dbe97a65c79",
"importer": "typescript",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}
+10 -2
View File
@@ -64,6 +64,7 @@ export class GameConfig {
static NEW_LEVEL: any;
static NEW_GUIDE: any;
static PASS_CHECK: any;
static PASS_CHECK_OLD: any;
static PASS_RATE: any;
static MESSAGE_MODEL: any;
static LOCAL_CACHE_PREFIX = "level_cache_";
@@ -662,7 +663,7 @@ export class GameConfig {
vibrateOpen: true, //震动
coinnum: 0, //每局的金币数
paid_user: false, //是否是付费用户
version: "1.0.50", //版本号
version: "1.0.51", //版本号
shushu_DistinctId: "", //数数访客ID
shushu_AccountId: "", //数数账号ID
uid: "", //用户和后端唯一id
@@ -757,7 +758,14 @@ export class GameConfig {
return;
}
const newLevelData = res.json;
this.PASS_CHECK = newLevelData.Sheet1;
cc.resources.load("Json/PASS_CHECK_OLD", (oldError: any, oldRes: cc.JsonAsset) => {
if (oldError) {
console.error("加载 PASS_CHECK_OLD.JSON 失败:", oldError);
return;
}
this.PASS_CHECK_OLD = oldRes.json.Sheet1;
this.PASS_CHECK = newLevelData.Sheet1;
});
// 可以根据需求对 newLevelData 进行处理
// //console.log("PASS_CHECK.JSON 加载成功:", newLevelData);
})
+130 -48
View File
@@ -1,4 +1,5 @@
import { MiniGameSdk } from "../../Sdk/MiniGameSdk";
import { normalizePass, mergePassInfo, legacyPassInfo, legacyExperience, passTier, PASS_PERIOD } from '../Config/BattlePassModel';
//#region 测试
/** 测试 */
export default class Utils {
@@ -41,6 +42,7 @@ export default class Utils {
isDebug: isDebug,
distinctId: cc.fx.GameConfig.GM_INFO.shushu_DistinctId,
wucai_capability: 1,
passVersion: 2,
};
if (from === "wucai") {
loginData.from = from;
@@ -92,15 +94,19 @@ export default class Utils {
//#region 道具直购接口
/**道具直购接口*/
static buyProp(id, count, price, systemType, productId, callBack) {
static buyProp(id, count, price, systemType, productId, callBack, passEnd?: string) {
let postName = "wx/orderPaySig";
if (systemType == "ios") {
postName = "wx/iosorderPaySig";
}
console.log("请求uid:" + Utils.uid, "请求id:" + id, "请求数量:" + count, "请求价格:" + price, "请求链接:" + postName);
Utils.POST(postName, { uid: Utils.uid, itemid: id, itemCount: count, itemPrice: price }, res => {
Utils.POST(postName, {
uid: Utils.uid, itemid: id, itemCount: count, itemPrice: price,
...(passEnd ? { passVersion: 2, passEnd } : {})
}, res => {
if (res.code == 1) {
Utils.outTradeNo = res.data.outTradeNo;
const passOrder = res.data.outTradeNo;
const data = {
outTradeNo: res.data.outTradeNo,
price: price,
@@ -125,7 +131,7 @@ export default class Utils {
success(res, errCode) {
// clearTimeout(timeoutId); // 清除超时定时器
//console.log('成功', res, errCode);
callBack(res);
callBack(passEnd ? { ...res, outTradeNo: passOrder } : res);
},
fail({ errMsg, errCode }) {
// clearTimeout(timeoutId); // 清除超时定时器
@@ -139,14 +145,14 @@ export default class Utils {
callBack(data);
}
})
}
} else if (passEnd) callBack({ err: res.msg || '战令下单失败' });
});
}
//#region 安卓获取支付结果
/** 安卓获取支付结果 */
static getPayInfo(callBack) {
static getPayInfo(callBack, passOrder?: string) {
// 延迟时间数组,按照 1 秒 3 次、2 秒 5 次、5 秒 6 次、15 秒 5 次的规则生成
const delays = [
...Array(3).fill(3000),
@@ -165,7 +171,7 @@ export default class Utils {
//console.log("请求uid:", Utils.uid);
//console.log("outTradeNo:", Utils.outTradeNo);
Utils.POST("wx/getPayInfo", { uid: Utils.uid, outTradeNo: Utils.outTradeNo }, res => {
Utils.POST("wx/getPayInfo", { uid: Utils.uid, outTradeNo: passOrder || Utils.outTradeNo }, res => {
//console.log("查询字符结果:", res);
if (res.code === 1 && (res.data.pay_state === 1 || res.data.pay_state === 2)) {
callBack(res);
@@ -231,7 +237,7 @@ export default class Utils {
cc.fx.GameConfig.GM_INFO.iosStarterOrder = null;
cc.fx.GameConfig.GM_INFO.iosStarterOrder = order;
}
else if (iosPayInfo.payment_name == "battlepass") {
else if (/^battlepass(?:_30|_12)?$/.test(iosPayInfo.payment_name)) {
cc.fx.GameConfig.GM_INFO.iosPassCheckOrder = null;
cc.fx.GameConfig.GM_INFO.iosPassCheckOrder = order;
}
@@ -249,6 +255,7 @@ export default class Utils {
propName: iosPayInfo.payment_name,
count: iosPayInfo.payment_count,
price: iosPayInfo.price,//价格单位是分
...(iosPayInfo.passEnd ? { passEnd: iosPayInfo.passEnd, passVersion: 2 } : {}),
}
const shushu_data = {
outTradeNo: order,
@@ -1356,54 +1363,129 @@ export default class Utils {
}
//获取通行证信息
private static passGiftPending: { [order: string]: 'pending' | 'applied' } = {};
private static applyPassGift(order: string) {
const key = (cc.fx.GameConfig.GM_INFO.uid || Utils.uid) + ':' + order;
if (Utils.passGiftPending[key] !== 'pending') return;
Utils.passGiftPending[key] = 'applied';
cc.fx.GameConfig.GM_INFO.coin += 3000;
cc.fx.StorageMessage.setStorage('coin', { coin: cc.fx.GameConfig.GM_INFO.coin, timestamp: Date.now() });
}
static getPassCheckInfo(callBack) {
//@ts-ignore
if (typeof wx !== 'undefined' && wx !== null) {
let uid = cc.fx.StorageMessage.getStorage("uid");
//旧的读取数据设置数据方法,以强联网为主
if (uid != undefined && uid != "" && uid != null) {
cc.fx.GameConfig.GM_INFO.uid = uid;
}
let setData = {
uid: cc.fx.GameConfig.GM_INFO.uid,
action: 'read',
}
Utils.POST("passCheck", setData, res => {
console.log("获得通行证成功:", res);
if (res.code === 1) {
if (callBack) callBack(res);
} else {
if (callBack) callBack(res);
}
});
if (!cc.fx.GameConfig.PASS_CHECK || !cc.fx.GameConfig.PASS_CHECK_OLD) {
if (callBack) callBack({ code: 0, msg: '战令配置尚未加载,请稍后重试' });
return;
}
Utils.POST("passCheck", { uid: cc.fx.GameConfig.GM_INFO.uid || Utils.uid, action: 'read' }, res => {
if (res.code !== 1) { if (callBack) callBack(res); return; }
Utils.passRequest('read').then(extra => {
const raw = typeof extra.data.legacy === 'string' ? JSON.parse(extra.data.legacy) : extra.data.legacy;
const pass = mergePassInfo(raw, extra.data.extra, cc.fx.GameConfig.PASS_CHECK, cc.fx.GameConfig.PASS_CHECK_OLD);
if (pass) { pass.revision = extra.data.revision; pass.upgradeReady = true; }
for (const time of Object.keys(extra.data.extra || {})) for (const order of extra.data.extra[time].orders || []) Utils.applyPassGift(order);
res.data.passCheck = JSON.stringify(pass);
res.data.upgradeReady = true;
if (callBack) callBack(res);
}).catch(() => { if (callBack) callBack(res); });
});
}
//设置通行证信息
static setPassCheckInfo(callBack, data) {
//@ts-ignore
if (typeof wx !== 'undefined' && wx !== null) {
let uid = cc.fx.StorageMessage.getStorage("uid");
//旧的读取数据设置数据方法,以强联网为主
if (uid != undefined && uid != "" && uid != null) {
cc.fx.GameConfig.GM_INFO.uid = uid;
}
let setData = {
uid: cc.fx.GameConfig.GM_INFO.uid,
action: 'save',
passCheck: JSON.stringify(data),
}
Utils.POST("passCheck", setData, res => {
if (res.code === 1) {
// console.log("请求通行证上传成功", res);
if (callBack) callBack(res);
} else {
console.log("请求通行证上传失败", res);
if (data == null) {
Utils.POST('passCheck', { uid: cc.fx.GameConfig.GM_INFO.uid || Utils.uid, action: 'save', passCheck: 'null' }, callBack || (() => { }));
return;
}
const save = async () => {
if (!data) throw { code: 0, msg: '战令数据为空' };
let pass = data;
for (let attempt = 0; attempt < 3; attempt++) {
const latest = await Utils.passRequest('read');
const merged = mergePassInfo(JSON.parse(latest.data.legacy || 'null'), latest.data.extra, cc.fx.GameConfig.PASS_CHECK, cc.fx.GameConfig.PASS_CHECK_OLD);
for (const key of [1, 2]) {
if (!pass[key] || !Number(pass[key].time)) continue;
pass[key] = normalizePass(pass[key], key === 1 ? cc.fx.GameConfig.PASS_CHECK_OLD : cc.fx.GameConfig.PASS_CHECK);
const current = merged && [merged[1], merged[2]].find(s => String(s && s.time) === String(pass[key].time));
if (current) {
pass[key].experience = Math.max(pass[key].experience, current.experience);
for (const lane of ['free', 'passCheck']) for (let i = 0; i < current[lane].length; i++) {
if (current[lane][i] === 0) pass[key][lane][i] = 0;
}
pass[key] = normalizePass(pass[key], key === 1 ? cc.fx.GameConfig.PASS_CHECK_OLD : cc.fx.GameConfig.PASS_CHECK);
}
}
})
try { return await Utils.passRequest('save', { ...Utils.passUpload(pass), revision: latest.data.revision }); }
catch (error) { if (error.code !== 409 || attempt === 2) throw error; }
}
};
save().then(res => { if (callBack) callBack(res); }, error => { if (callBack) callBack(error); });
}
/** 只用于战令接口,避免网络无回调时卡住本次战令操作。 */
private static passRequest(action: string, data: any = {}): Promise<any> {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => reject({ code: 0, msg: '战令请求超时,请重试' }), 6000);
Utils.POST('passCheckUpgrade', { uid: cc.fx.GameConfig.GM_INFO.uid || Utils.uid, action, ...data }, res => {
clearTimeout(timer);
res.code === 1 ? resolve(res) : reject(res);
});
});
}
private static passUpload(pass: any): any {
const legacy = legacyPassInfo(pass, cc.fx.GameConfig.PASS_CHECK, cc.fx.GameConfig.PASS_CHECK_OLD);
for (const key of [1, 2]) if (pass[key] && Number(pass[key].time)) pass[key].legacyExperience = legacyExperience(legacy[key]);
return { passCheck: JSON.stringify(pass), legacy: JSON.stringify(legacy) };
}
static addPassProgress() {
Utils.getPassCheckInfo(res => {
if (res.code !== 1 || !res.data.upgradeReady) return;
cc.fx.GameConfig.GM_INFO.getItemType = 2;
let pass = JSON.parse(res.data.passCheck);
const end = Number(res.data.time) + PASS_PERIOD;
if (!pass) pass = { 1: { time: '0', free: [], passCheck: [] } };
if (!pass[2] || Number(pass[2].time) <= Date.now()) {
if (pass[2]) pass[1] = pass[2];
pass[2] = { time: String(end), experience: 0, free: [], passCheck: [], activate: false };
}
const stage = normalizePass(pass[2], cc.fx.GameConfig.PASS_CHECK);
const before = stage.unlocked;
stage.experience += passTier(stage) === 30 ? 2 : 1;
pass[2] = normalizePass(stage, cc.fx.GameConfig.PASS_CHECK);
cc.fx.GameConfig.GM_INFO.getProgress = pass[2].progress;
cc.fx.GameConfig.GM_INFO.getProgressLevel = pass[2].progressLevel;
if (pass[2].unlocked > before) cc.fx.GameConfig.GM_INFO.popPassCheck = true;
Utils.setPassCheckInfo(() => { }, pass);
});
}
static async completePassOrder(outTradeNo: string): Promise<any> {
for (let attempt = 0; attempt < 3; attempt++) {
const order = await Utils.passRequest('order', { outTradeNo });
if (order.data.alreadyGranted) { Utils.applyPassGift(outTradeNo); return order; }
const pass = mergePassInfo(JSON.parse(order.data.legacy || '{}'), order.data.extra, cc.fx.GameConfig.PASS_CHECK, cc.fx.GameConfig.PASS_CHECK_OLD);
let key = [1, 2].find(k => String(pass[k] && pass[k].time) === order.data.passEnd);
if (!key && order.data.extra[order.data.passEnd]) { key = 1; pass[1] = order.data.extra[order.data.passEnd]; }
if (!key) throw { code: 410, msg: '订单所属期已过期,请联系客服' };
pass[key].activate = true; pass[key].tier = order.data.targetTier;
if (order.data.bonus) pass[key].experience += 20;
pass[key] = normalizePass(pass[key], key === 1 ? cc.fx.GameConfig.PASS_CHECK_OLD : cc.fx.GameConfig.PASS_CHECK);
// 沿用客户端计算后上传,赠金仅通过战令接口随本期发奖记录一起保存。
const coinAmount = Math.floor(cc.fx.GameConfig.GM_INFO.coin) + (order.data.bonus ? 3000 : 0);
const giftKey = (cc.fx.GameConfig.GM_INFO.uid || Utils.uid) + ':' + outTradeNo;
if (order.data.bonus && Utils.passGiftPending[giftKey] !== 'applied') Utils.passGiftPending[giftKey] = 'pending';
try {
const saved = await Utils.passRequest('save', { ...Utils.passUpload(pass), revision: order.data.revision, outTradeNo, coinAmount });
if (saved.data.bonus && !saved.data.alreadyGranted) {
Utils.applyPassGift(outTradeNo);
}
return saved;
} catch (error) {
// 响应可能丢失,先确认战令收据;只补本次进程尚未发布的赠金。
const confirmed = await Utils.passRequest('order', { outTradeNo });
if (confirmed.data.alreadyGranted) {
Utils.applyPassGift(outTradeNo);
return { ...confirmed, data: { ...confirmed.data, bonus: order.data.bonus, alreadyGranted: false } };
}
if (error.code !== 409 || attempt === 2) throw error;
}
}
}
+3
View File
@@ -2680,6 +2680,9 @@ var GameTool = {
},
handleMemoryWarning(res?: any) {
// 仅 iOS 低内存机型响应告警,其他机型不进入降级和资源清理流程。
if (!this.isLowMemoryIOSDevice()) return;
const firstWarning = !this._lowMemoryMode;
this._lowMemoryMode = true;
this._memoryWarningCount++;