import {createBuildIdentity} from './build-identity.ts'; import {_decorator,Prefab,SceneAsset,Node,assetManager,AssetManager,TextAsset,js} from 'cc'; import versionManifestParser,{type VersionManifest} from './version-manifest.js'; import {GameComposition,type CommonRoomView} from '../../scripts/platform-login/GameComposition.ts'; import {getGameDefinition,gameBundleName,gameResourcePath,type SubgameEntry} from './game-definitions.ts'; import type {GameDefinition} from '../../framework/sdk/contracts/game-definition.ts'; const {ccclass,property}=_decorator; /** App composition root: each game assigns its own pre-room page, room view and scene. */ @ccclass('SubgameAssets') export class SubgameAssets extends GameComposition { @property gameKey:string=''; private loaded:Readonly<{definition:GameDefinition;createRoom:Prefab;room:Prefab}>|null=null; get createRoom(){return this.loaded?.createRoom??null;} get room(){return this.loaded?.room??null;} @property(SceneAsset) roomScene:SceneAsset|null=null; private manifestValue:Readonly|null=null; get versionManifest(){return this.manifestValue;} get displayVersion(){if(!this.manifestValue)throw Error('version.xml has not been loaded');return this.manifestValue.version;} async loadBuildIdentity(){ await this.loadGameAssets(); const key=this.gameKey,path=this.definition.config.versionResource; if(path!==key+'/version')throw Error('Version resource does not match selected game: '+key); const bundleName='version-'+key; const bundle=await new Promise((resolve,reject)=>assetManager.loadBundle(bundleName,(error,result)=>{ if(error)reject(Error('Cannot load '+bundleName+': '+error.message)); else if(!result)reject(Error('Missing '+bundleName));else resolve(result); })); const asset=await new Promise((resolve,reject)=>bundle.load(path,TextAsset,(error,result)=>{ if(error)reject(Error('Cannot load '+path+'.xml: '+error.message)); else if(!result)reject(Error('Missing '+path+'.xml'));else resolve(result); })); try { if(this.gameKey!==key)throw Error('Game selection changed during version.xml load'); const manifest=versionManifestParser.parseVersionManifest(asset.text); const identity=createBuildIdentity(manifest); this.manifestValue=manifest; return identity; } finally {bundle.release(path,TextAsset);} } private async loadGameAssets(){ const key=this.gameKey; if(this.loaded){if(this.loaded.definition.key!==key)throw Error('Game selection changed after loading');return;} const bundle=await new Promise((resolve,reject)=>assetManager.loadBundle(gameBundleName(key),(error,result)=>{ if(error)reject(error);else if(!result)reject(Error('Missing game bundle: '+key));else resolve(result); })); const definition=getGameDefinition(key,name=>js.getClassByName(name) as unknown as SubgameEntry|null); const load=(resource:string)=>new Promise((resolve,reject)=>bundle.load(gameResourcePath(definition.config.assetRoot,resource),Prefab,(error,result)=>{ if(error)reject(error);else if(!result)reject(Error('Missing game prefab: '+resource));else resolve(result); })); const [createRoom,room]=await Promise.all([load(definition.resources.createRoom),load(definition.resources.room)]); if(this.gameKey!==key)throw Error('Game selection changed during asset load'); this.loaded={definition,createRoom,room}; } get definition(){if(!this.loaded)throw Error('Subgame assets have not been loaded');return this.loaded.definition;} resolveRoomView(root:Node):CommonRoomView { const name=this.definition.roomViewClass; const view=root.getComponent(name) as (CommonRoomView|null); if(!view||typeof view.render!=='function'||typeof view.close!=='function'||typeof view.dissolutionReceived!=='function')throw Error(`Room prefab missing valid ${name}`); return view; } }