feat: wire lobby task and VIP prefab flows and sync UI assets

This commit is contained in:
2026-09-06 18:46:37 +08:00
parent 842c68ef55
commit 9c4510697c
184 changed files with 8348 additions and 19133 deletions
@@ -0,0 +1,22 @@
import {requireArray,requireRecord,requireString} from './validation.ts';
import type {VipCode,VipRoom} from '../../core/types/vip-room.ts';
export function vipCode(value:unknown,path:string):VipCode {
if(typeof value==='string'&&value.length>0)return value;
if(typeof value==='number'&&Number.isFinite(value))return value;
throw TypeError(`${path}: expected non-empty string or finite number`);
}
/** Defaults here are the original GameUI source semantics, not view repairs. */
export function parseVipRooms(payload:unknown):readonly VipRoom[]{
const data=requireRecord(payload,'$.data');
return Object.freeze(requireArray(data.roomInfo,'$.data.roomInfo').map((value,index)=>{
const p=`$.data.roomInfo[${index}]`,r=requireRecord(value,p);
const playerList=requireArray(r.playerList,p+'.playerList').map((v,i)=>Object.freeze([...requireArray(v,`${p}.playerList[${i}]`)]));
const codes=r.roomcodes?requireRecord(r.roomcodes,p+'.roomcodes'):null;
const roomcodes=codes?Object.freeze({roomtype:Object.freeze([...requireArray(codes.roomtype,p+'.roomcodes.roomtype')]),roomlist:Object.freeze(requireArray(codes.roomlist,p+'.roomcodes.roomlist').map((v,i)=>vipCode(v,`${p}.roomcodes.roomlist[${i}]`)))}):null;
const deskState=roomcodes?requireArray(r.deskState,p+'.deskState'):[];
const seatCount=roomcodes?requireArray(r.seatCount,p+'.seatCount').map((v,i)=>vipCode(v,`${p}.seatCount[${i}]`)):[];
if(roomcodes&&[roomcodes.roomlist.length,deskState.length,seatCount.length].some(n=>n!==playerList.length))throw TypeError(p+': desk arrays must have matching lengths');
const owner=typeof r.owner==='string'?r.owner:String(vipCode(r.owner,p+'.owner'));
return Object.freeze({shortCode:vipCode(r.shortCode,p+'.shortCode'),title:r.destwo?requireString(r.destwo,p+'.destwo'):'vip房间',owner,roomcodes,playerList:Object.freeze(playerList),deskState:Object.freeze([...deskState]),seatCount:Object.freeze(seatCount),forbidSelect:!!r.forbidSelect});
}));
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "413720a7-c6a9-4364-bb42-3c9c6f4fb907",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -7,6 +7,11 @@ export function isLoginResponse(message:{route:string;rpc:string}):boolean{
}
export type FirstSliceRpc =
| 'getInfoByShortCode'
| 'get_player_task'
| 'get_task_award'
| 'update_roomcard'
| 'player_finish_task'
| 'player_login'
| 'self_join_room'
| 'player_prepare'
@@ -25,6 +30,11 @@ export type FirstSliceRoute = 'platform' | 'agent' | 'room';
/** 第一纵向切片 rpc → route 的唯一权威映射。 */
export const FIRST_SLICE_RPC_ROUTE = {
getInfoByShortCode: 'agent',
get_player_task: 'agent',
get_task_award: 'agent',
update_roomcard: 'agent',
player_finish_task: 'agent',
player_login: 'agent',
self_join_room: 'agent',
player_prepare: 'room',
@@ -28,7 +28,7 @@ export interface PlatformHandlersOptions {
readonly gameSession: GameSessionHost;
readonly session: RuntimeSession;
readonly connection: ConnectionControlPort;
readonly showMessage?: (message:string,time:number)=>void;
readonly showMessage?: (message:string,time:number,source:'show_message'|'update_roomcard')=>void;
}
export type PlatformDispatchResult = 'handled-platform';
@@ -57,6 +57,20 @@ export class PlatformHandlers {
}
switch (rpc) {
case 'getInfoByShortCode':this.options.store.applyVipRooms(message.data);break;
case 'update_roomcard': {
const raw=requireRecord(message.data,'$.data');
const text=raw.text?requireString(raw.text,'$.data.text'):null;
this.options.store.applyRoomcardUpdate(raw);
if(raw.change===undefined&&text){
if(!this.options.showMessage)throw Error('update_roomcard requires a message UI');
this.options.showMessage(text,0,'update_roomcard');
}
break;
}
case 'get_player_task':this.options.store.applyTaskList(message.data);break;
case 'get_task_award':this.options.store.applyTaskAward(message.data);break;
case 'player_finish_task':this.options.store.applyTaskFinished(message.data);break;
case 'player_login':
this.options.session.handleLogin(message.data);
break;
@@ -127,7 +141,7 @@ export class PlatformHandlers {
// Desk.show_message converts the original time with Number; only >0 auto-closes.
const time=Number(data.time);
if(!this.options.showMessage)throw Error('show_message requires a message UI');
this.options.showMessage(text,time);
this.options.showMessage(text,time,'show_message');
break;
}
case 'kick_offline': {