From 7302fccb4891cde86867ef6fcd5553b87047e0d1 Mon Sep 17 00:00:00 2001 From: "COMPUTER\\EDY" <249929363@qq.com> Date: Sat, 10 Oct 2026 18:35:48 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=B8=80=E4=B8=AA=20?= =?UTF-8?q?=E5=85=B3=E5=8D=A1=E6=97=B6=E9=97=B4AB=E6=96=B9=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/Script/module/Config/GameConfig.ts | 7 +- .../module/Config/LevelTimeBonusConfig.ts | 103 +++++++++++++++++ .../Config/LevelTimeBonusConfig.ts.meta | 10 ++ tools/test-level-time-bonus.cjs | 106 ++++++++++++++++++ 4 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 assets/Script/module/Config/LevelTimeBonusConfig.ts create mode 100644 assets/Script/module/Config/LevelTimeBonusConfig.ts.meta create mode 100644 tools/test-level-time-bonus.cjs diff --git a/assets/Script/module/Config/GameConfig.ts b/assets/Script/module/Config/GameConfig.ts index 67ee191c..771f27ac 100644 --- a/assets/Script/module/Config/GameConfig.ts +++ b/assets/Script/module/Config/GameConfig.ts @@ -4,6 +4,7 @@ import { GAMEPLAY_ATLAS_PATHS } from "../Tool/GameplayAtlasFrames"; import { MiniGameSdk } from "../../Sdk/MiniGameSdk"; import { GameTool } from "../Tool/GameTool"; import BlockAssetManager from "../Tool/BlockAssetManager"; +import { LEVEL_TIME_BONUS } from "./LevelTimeBonusConfig"; interface GameplayEntryPreparation { startedAt: number; @@ -1365,7 +1366,11 @@ export class GameConfig { static applyLevelData(jsonData: any, type: any, time: any, levelNumber = Number(jsonData.LEVEL_INFO[0].id), preparation?: GameplayEntryPreparation) { this.loadedLevelNumber = levelNumber; this.BLOCK_INFO = jsonData["BLOCK_INFO"]; - this.LEVEL_INFO = jsonData["LEVEL_INFO"]; + const extraTime = this.GM_INFO?.abTests?.layer_three === 2 ? LEVEL_TIME_BONUS[levelNumber] || 0 : 0; + // 只调整本局时限,保留资源缓存中的原始时间,避免重开时重复加时。 + this.LEVEL_INFO = extraTime + ? jsonData["LEVEL_INFO"].map((info, index) => index === 0 ? { ...info, time: info.time + extraTime } : info) + : jsonData["LEVEL_INFO"]; this.WALL_INFO = jsonData["WALL_INFO"]; console.log("关卡配置加载完成"); diff --git a/assets/Script/module/Config/LevelTimeBonusConfig.ts b/assets/Script/module/Config/LevelTimeBonusConfig.ts new file mode 100644 index 00000000..d95fe678 --- /dev/null +++ b/assets/Script/module/Config/LevelTimeBonusConfig.ts @@ -0,0 +1,103 @@ +// layer_three B 组的逐关加时,单位:秒。 +export const LEVEL_TIME_BONUS: { readonly [level: number]: number } = { + 114: 30, + 132: 20, + 222: 10, + 259: 30, + 302: 30, + 317: 20, + 329: 10, + 383: 30, + 400: 30, + 411: 20, + 416: 20, + 422: 20, + 426: 30, + 433: 30, + 444: 40, + 452: 30, + 504: 30, + 517: 40, + 521: 20, + 524: 30, + 535: 50, + 536: 20, + 542: 20, + 548: 30, + 557: 20, + 563: 20, + 566: 20, + 595: 10, + 604: 20, + 612: 20, + 630: 30, + 648: 20, + 651: 30, + 666: 20, + 688: 30, + 690: 30, + 745: 20, + 748: 40, + 750: 30, + 789: 20, + 798: 30, + 799: 30, + 828: 20, + 868: 20, + 881: 20, + 895: 30, + 916: 20, + 962: 30, + 963: 40, + 981: 20, + 991: 20, + 1003: 30, + 1008: 20, + 1023: 20, + 1024: 20, + 1026: 30, + 1027: 30, + 1043: 20, + 1061: 20, + 1063: 30, + 1077: 20, + 1087: 20, + 1093: 20, + 1107: 20, + 1122: 40, + 1123: 40, + 1129: 20, + 1137: 10, + 1139: 30, + 1143: 30, + 1146: 20, + 1154: 40, + 1164: 20, + 1166: 30, + 1171: 40, + 1173: 20, + 1175: 10, + 1185: 30, + 1195: 20, + 1202: 20, + 1204: 20, + 1205: 30, + 1206: 20, + 1207: 40, + 1209: 20, + 1210: 30, + 1212: 40, + 1222: 60, + 1225: 30, + 1227: 20, + 1228: 20, + 1239: 30, + 1241: 30, + 1242: 40, + 1264: 20, + 1266: 30, + 1268: 40, + 1275: 60, + 1279: 40, + 1281: 40, +}; diff --git a/assets/Script/module/Config/LevelTimeBonusConfig.ts.meta b/assets/Script/module/Config/LevelTimeBonusConfig.ts.meta new file mode 100644 index 00000000..22afd316 --- /dev/null +++ b/assets/Script/module/Config/LevelTimeBonusConfig.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "df6e40f7-1279-4724-afe3-55fade9817c0", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/tools/test-level-time-bonus.cjs b/tools/test-level-time-bonus.cjs new file mode 100644 index 00000000..36c8b12b --- /dev/null +++ b/tools/test-level-time-bonus.cjs @@ -0,0 +1,106 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const { loadMembers, loadModule, resolveFile } = require('./new-player-ab-test-support.cjs'); +const { LEVEL_TIME_BONUS } = loadModule('assets/Script/module/Config/LevelTimeBonusConfig.ts'); +const configFile = 'assets/Script/module/Config/GameConfig.ts'; +const readLevel = level => JSON.parse(fs.readFileSync(resolveFile(`assets/custom/Json/level${level}.json`), 'utf8')); +const plain = value => JSON.parse(JSON.stringify(value)); + +function config(info = {}) { + const Config = loadMembers(configFile, ['applyLevelData', 'LEVEL_INFO_init', 'getLevelConfigPath'], { + LEVEL_TIME_BONUS, console: { log() {} }, cc, + }); + Config.GM_INFO = info; + return Config; +} + +// Exercise the actual loader so file numbers, help levels and endless levels use the same key. +const requests = []; +const cc = { + v2: (x, y) => ({ x, y }), director: { getScene: () => null }, + fx: { GameTool: { maxLevel: () => false } }, + assetManager: { + loadBundle(_name, _options, done) { done(null, {}); }, + loadAny(request, _options, done) { + requests.push(request.path); + done(null, { json: readLevel(Number(request.path.replace('Json/level', ''))) }); + }, + }, +}; + +test('A, unassigned and other groups retain the original time for every configured level', () => { + assert.equal(Object.keys(LEVEL_TIME_BONUS).length, 100); + for (const info of [undefined, {}, { abTests: {} }, ...[0, 1, 3].map(layer_three => ({ abTests: { layer_three } }))]) { + const Config = config(info); + for (const level of Object.keys(LEVEL_TIME_BONUS)) { + const data = readLevel(level); + Config.applyLevelData(data, false, 0, Number(level)); + assert.equal(Config.LEVEL_INFO[0].time, data.LEVEL_INFO[0].time); + assert.equal(Config.LEVEL_INFO, data.LEVEL_INFO); + } + } +}); + +test('B adds time once, preserves cached data and returns to original time when switched to A', () => { + const Config = config({ abTests: { layer_three: 2 } }); + for (const [level, bonus] of Object.entries(LEVEL_TIME_BONUS)) { + assert.ok(Number.isInteger(bonus) && bonus > 0); + const data = readLevel(level), before = structuredClone(data); + Config.GM_INFO.abTests.layer_three = 2; + for (let retry = 0; retry < 2; retry++) { + Config.applyLevelData(data, false, 0, Number(level)); + const expected = structuredClone(before.LEVEL_INFO); + expected[0].time += bonus; + assert.deepEqual(plain(Config.LEVEL_INFO), expected); + assert.equal(Config.BLOCK_INFO, data.BLOCK_INFO); + assert.equal(Config.WALL_INFO, data.WALL_INFO); + assert.deepEqual(data, before); + } + Config.GM_INFO.abTests.layer_three = 1; + Config.applyLevelData(data, false, 0, Number(level)); + assert.equal(Config.LEVEL_INFO[0].time, before.LEVEL_INFO[0].time); + } +}); + +test('B leaves unlisted levels unchanged and uses the file number instead of a stale internal id', () => { + const Config = config({ abTests: { layer_three: 2 } }); + for (const level of [1, 113, 115, 1282]) { + const data = readLevel(level); + Config.applyLevelData(data, false, 0, level); + assert.equal(Config.LEVEL_INFO, data.LEVEL_INFO); + } + const data = readLevel(222); + assert.equal(data.LEVEL_INFO[0].id, '158'); + Config.applyLevelData(data, false, 0, 222); + assert.equal(Config.LEVEL_INFO[0].time, 70); + assert.equal(Config.LEVEL_INFO[0].id, '158'); +}); + +test('loader applies B bonuses to the requested mainline, help and endless level', () => { + for (const mode of ['mainline', 'help', 'endless']) { + const Config = config({ level: mode === 'mainline' ? 221 : 0, + otherLevel: mode === 'help' ? 222 : 0, randomLevel: 222, abTests: { layer_three: 2 } }); + cc.fx.GameConfig = Config; + Config.levelLoadRequest = Config.sceneLoadRequest = 0; + Config.takeEntryDownloadRelease = () => null; + Config.LEVEL_INFO_init(false, 0, mode === 'help', mode === 'endless'); + assert.equal(requests.at(-1), 'Json/level222'); + assert.equal(Config.LEVEL_INFO[0].time, 70, mode); + } +}); + +test('server A/B assignments select original and bonus times before entering the scene', () => { + const Manager = loadMembers('assets/Script/GameManager.ts', ['setABTests'], { cc }); + const manager = new Manager(); + const Config = config(); + cc.fx.GameConfig = Config; + const seen = []; + Config.enterGameSceneWhenReady = (time, preparation) => seen.push([Config.LEVEL_INFO[0].time, time, preparation]); + const preparation = {}; + for (const [group, expected] of [['A', 135], ['B', 165], [1, 135], [2, 165]]) { + manager.setABTests({ layer_3: { group } }); + Config.applyLevelData(readLevel(114), true, 0.5, 114, preparation); + assert.deepEqual(seen.at(-1), [expected, 0.5, preparation]); + } +});