MatchMaster/tools/preview-battle-pass-settlement.cjs
2026-10-09 10:03:29 +08:00

50 lines
9.3 KiB
JavaScript

// Local-only Cocos 2.4 fixture: actual settlement component and source sprites, mocked API/payment.
const http=require('http'),fs=require('fs'),path=require('path'),ts=require('typescript');
const root=path.resolve(__dirname,'..'),creator='C:/ProgramData/cocos/editors/Creator/2.4.15';
const metas=new Map(),textures=new Map();
for(const name of fs.readdirSync(path.join(root,'assets'),{recursive:true}).filter(n=>n.endsWith('.meta'))) {
try {const m=JSON.parse(fs.readFileSync(path.join(root,'assets',name))),file='assets/'+name.slice(0,-5).replaceAll('\\','/');
if(m.importer==='texture')textures.set(m.uuid,file);
for(const s of Object.values(m.subMetas||{}))if(s.importer==='sprite-frame')metas.set(s.uuid,{...s,file});
} catch {}
}
const compile=(p)=>ts.transpileModule(fs.readFileSync(path.join(root,p),'utf8'),{compilerOptions:{target:7,module:1,experimentalDecorators:true}}).outputText;
const modules={'./module/Config/BattlePassModel':'assets/Script/module/Config/BattlePassModel.ts','./module/Config/BattlePassSettlementModel':'assets/Script/module/Config/BattlePassSettlementModel.ts','./BattlePassArt':'assets/Script/BattlePassArt.ts','./NumberToImage':'assets/Script/NumberToImage.ts','./BattlePassSettlement':'assets/Script/BattlePassSettlement.ts'};
let scripts='window.mods={};function require(n){return mods[n]||mods["./module/Config/"+n.replace("./","")];}\n';
for(const [name,file]of Object.entries(modules))scripts+=`mods[${JSON.stringify(name)}]=(function(){var exports={};${compile(file)};return exports;})();\n`;
// Settlement captures this object on require; methods are filled before the component is run.
scripts='window.fixtureUtils={};window.fixtureSdk={MiniGameSdk:{API:{showToast:message=>console.log(message)}}};\n'+scripts.replace('return mods[n]||','if(n.includes("Pay/Utils"))return {default:fixtureUtils};if(n.includes("MiniGameSdk"))return fixtureSdk;return mods[n]||');
function liveScripts(){let code='window.fixtureUtils={};window.fixtureSdk={MiniGameSdk:{API:{showToast:console.log}}};window.mods={};function require(n){if(n.includes("Pay/Utils"))return {default:fixtureUtils};if(n.includes("MiniGameSdk"))return fixtureSdk;return mods[n]||mods["./module/Config/"+n.replace("./","")];}\n';for(const[name,file]of Object.entries(modules))code+=`mods[${JSON.stringify(name)}]=(function(){var exports={};${compile(file)};return exports;})();\n`;return code;}
const html=`<!doctype html><meta charset="utf-8"><style>html,body{margin:0;height:100%;background:#151221}canvas{display:block}#controls{position:absolute;z-index:2;top:0;left:0}button{padding:8px}</style><div id="controls"><button onclick="fixture('empty')">图2</button><button onclick="fixture('claim')">图3</button><button onclick="fixture('offer')">图1</button><button onclick="fixture('confirm')">图4</button><button onclick="unlock(18)">18元</button><button onclick="unlock(30)">30元</button></div><canvas id="GameCanvas"></canvas><script src="/engine.js"></script><script src="/modules.js"></script><script src="/boot.js"></script>`;
const boot=`
const Art=mods['./BattlePassArt'].default, Component=mods['./BattlePassSettlement'].default, Digits=mods['./NumberToImage'].default;
const texCache={},frameCache={};window.errors=[];window.addEventListener('error',e=>errors.push(e.message));window.addEventListener('unhandledrejection',e=>errors.push(String(e.reason)));
const json=p=>fetch(p).then(r=>r.json());
async function frame(uuid){if(frameCache[uuid])return frameCache[uuid];const m=await json('/frame/'+uuid);const texture=texCache[m.rawTextureUuid]||(texCache[m.rawTextureUuid]=await new Promise((resolve,reject)=>cc.assetManager.loadRemote('/texture/'+m.rawTextureUuid+pathExt(m.texture),(e,t)=>e?reject(e):resolve(t))));
const f=new cc.SpriteFrame(texture,cc.rect(m.trimX||0,m.trimY||0,m.width,m.height),!!m.rotated,cc.v2(m.offsetX||0,m.offsetY||0),cc.size(m.rawWidth||m.width,m.rawHeight||m.height));f.insetTop=m.borderTop||0;f.insetBottom=m.borderBottom||0;f.insetLeft=m.borderLeft||0;f.insetRight=m.borderRight||0;return frameCache[uuid]=f;}
function pathExt(p){return p.endsWith('.jpg')?'.jpg':'.png';}
Art.loadAsset=async(bundle,p,type)=>{const file='assets/'+bundle+'/'+p;if(type===cc.SpriteFrame){const m=await json('/file/'+file+'.png.meta');return frame(Object.values(m.subMetas)[0].uuid);}if(type===cc.SpriteAtlas){const m=await json('/file/'+file+'.plist.meta'),a=new cc.SpriteAtlas();a._spriteFrames={};for(const [key,value]of Object.entries(m.subMetas))a._spriteFrames[key.replace(/\\.png$/,'')]=await frame(value.uuid);return a;}if(type===cc.Prefab){const data=await json('/file/'+file+'.prefab'),details=new cc.deserialize.Details();const prefab=cc.deserialize(data,details);await Promise.all(details.uuidList.map(async(uuid,i)=>{if(uuid==='6c9aeb6a-...' )return;try{details.uuidObjList[i][details.uuidPropList[i]]=await frame(uuid);}catch(e){console.warn(uuid,e);}}));return prefab;}};
cc.assetManager.init({importBase:'/imports',nativeBase:'/native'});cc.assetManager.loadBundle('/internal',error=>{if(error)throw error;cc.game.run({id:'GameCanvas',debugMode:1,frameRate:30,renderMode:0,groupList:['default'],collisionMatrix:[[true]]},async()=>{
cc.view.setDesignResolutionSize(1080,2340,cc.ResolutionPolicy.SHOW_ALL);cc.view.resizeWithBrowserSize(true);const scene=new cc.Scene(),canvas=new cc.Node('Canvas');scene.addChild(canvas);canvas.setContentSize(1080,2340);canvas.addComponent(cc.Canvas).designResolution=cc.size(1080,2340);cc.director.runScene(scene);window.canvas=canvas;
cc.fx={GameConfig:{PASS_CHECK:(await json('/file/assets/resources/Json/PASS_CHECK.json')).Sheet1,GM_INFO:{}},StorageMessage:{getStorage:()=>null,setStorage(){}},GameTool:{}};
const fonts=await Art.loadAsset('common','passCheckFont',cc.SpriteAtlas);Digits.fontPassCheck=fonts;Digits.font3=fonts;Digits.publicFont=fonts;
// Use image-based test counts while preserving the actual reward card prefab and sprite geometry.
Digits.numberToImageNodesNumber=(number,w,p,prefix,target)=>{target.removeAllChildren();String(number).split('').forEach((d,i)=>{const n=new cc.Node();n.parent=target;n.x=(i-(String(number).length-1)/2)*40;n.addComponent(cc.Sprite).spriteFrame=fonts.getSpriteFrame('half_black_'+d);});};Digits.durationToImageNodes=(seconds,target)=>Digits.numberToImageNodesNumber(seconds/60,0,0,'',target);
const bg=new cc.Node('background');bg.parent=canvas;bg.addComponent(cc.Sprite).spriteFrame=await frame('66a21426-d824-4461-b25d-87115d1932e6');
window.fixture=async(mode)=>{if(window.view){view.root&&view.root.destroy();view.destroy();}const end=Date.now()-1000;const row={end,stage:{time:String(end),experience:30,free:[],passCheck:[],activate:false},claimed:mode==='offer'||mode==='confirm',emptySeen:mode==='offer'||mode==='confirm',offer:'unused'};if(mode==='empty'){row.stage.experience=0;row.stage.free=[0];}
fixtureUtils.settlementRequest=async(action)=>{if(action==='empty_seen')row.emptySeen=true;if(action==='offer_shown')row.offer='shown';return{code:1,data:action==='read'?{serverTime:Date.now(),rows:[row]}:{row}};};window.view=canvas.addComponent(Component);await view.run(()=>console.log('done'));if(mode==='confirm')setTimeout(()=>view.close(),150);};window.unlock=async(tier)=>{if(window.view){view.root&&view.root.destroy();view.destroy();}await Art.unlock(canvas,tier,()=>{});};await fixture('claim');window.ready=true;
});});`;
http.createServer((req,res)=>{const url=decodeURIComponent(new URL(req.url,'http://localhost').pathname);const json=v=>{res.setHeader('Content-Type','application/json');res.end(JSON.stringify(v));};
if(url==='/'){res.setHeader('Content-Type','text/html');return res.end(html);}if(url==='/modules.js'||url==='/boot.js'){res.setHeader('Content-Type','text/javascript');return res.end(url==='/boot.js'?boot:liveScripts());}
let target;if(url==='/engine.js')target=path.join(creator,'resources/engine/bin/cocos2d-js-for-preview.js');
else if(/^\/imports\/[a-f0-9]{2}\/[a-f0-9-]+\.json$/.test(url)) {
target=path.join(root,'library',url.slice(1));
if(!fs.existsSync(target)) {const config=JSON.parse(fs.readFileSync(path.join(creator,'resources/cocos2d-x/simulator/win32/assets/internal/config.json')));const entry=config.paths[path.basename(url,'.json')];if(entry&&entry[1]==='cc.Material')target=path.join(creator,'resources/static/default-assets/resources',entry[0]+'.mtl');}
}
else if(/^\/native\/[a-f0-9]{2}\/[a-f0-9-]+\.png$/.test(url))target=path.join(root,'library/imports',url.slice('/native/'.length));
else if(url.startsWith('/internal/')||url.startsWith('/assets/internal/'))target=path.join(creator,'resources/cocos2d-x/simulator/win32/assets',url.replace(/^\/(assets\/)?/,''));
else if(url.startsWith('/file/assets/')){target=path.resolve(root,url.slice(6));if(!target.startsWith(path.join(root,'assets')+path.sep)){res.statusCode=403;return res.end();}}
else if(url.startsWith('/frame/')){const m=metas.get(url.slice(7));if(m)return json({...m,texture:textures.get(m.rawTextureUuid)});}
else if(url.startsWith('/texture/')){const id=path.basename(url,path.extname(url));if(textures.has(id))target=path.join(root,textures.get(id));}
if(target&&fs.existsSync(target)){res.setHeader('Content-Type',target.endsWith('.js')?'text/javascript':target.endsWith('.png')?'image/png':target.endsWith('.jpg')?'image/jpeg':'application/json');return fs.createReadStream(target).pipe(res);}res.statusCode=404;res.end('not found');}).listen(8776,'127.0.0.1',()=>console.log('http://127.0.0.1:8776'));