feat: integrate platform room entry, UI migration and erqiwang documentation
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
type ClipboardHost=
|
||||
|{kind:'settings';read:()=>unknown}
|
||||
|{kind:'bridge';call:(name:'gamepastetext',data:string,callback:(value:unknown)=>void)=>void}
|
||||
|{kind:'browser';read:()=>Promise<string>};
|
||||
|
||||
/** 05_Func: settings takes no arguments; WVJB takes "" and returns through its callback. */
|
||||
export async function readClipboardText(host:ClipboardHost):Promise<string>{
|
||||
const value=host.kind==='bridge'?await new Promise<unknown>((resolve,reject)=>{
|
||||
const timer=setTimeout(()=>reject(Error('读取剪贴板超时,请手动输入')),10000);
|
||||
try{host.call('gamepastetext','',value=>{clearTimeout(timer);resolve(value);});}
|
||||
catch(error){clearTimeout(timer);reject(error);}
|
||||
}):await host.read();
|
||||
if(typeof value!=='string')throw Error('Invalid clipboard text');
|
||||
return value;
|
||||
}
|
||||
|
||||
export async function readInviteClipboard(host:ClipboardHost):Promise<string>{
|
||||
return (await readClipboardText(host)).replace(/[^0-9]/g,'');
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "4aeb3de8-f982-4246-95be-7dca058bc99d",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import {requireRecord} from '../../protocol/contracts/validation.ts';
|
||||
/** Logic.readRoomList/addRoomToList/removeRoomFromList. This is owner-return history, not recently played rooms. */
|
||||
export class OwnedRoomHistory {
|
||||
private readonly key:string;
|
||||
constructor(private readonly storage:Pick<Storage,'getItem'|'setItem'>,gameid:string|number,agentid:string|number){this.key='tsgame_roomlist_'+gameid+agentid;}
|
||||
read():readonly number[]{
|
||||
const text=this.storage.getItem(this.key);
|
||||
if(!text){this.save([]);return [];}
|
||||
const raw=requireRecord(JSON.parse(text),this.key);
|
||||
if(!Array.isArray(raw.data)||raw.data.some(n=>typeof n!=='number'||!Number.isSafeInteger(n)||n<=0))throw Error('Invalid '+this.key);
|
||||
return Object.freeze([...raw.data]);
|
||||
}
|
||||
private code(value:unknown):number {
|
||||
if(!(typeof value==='string'&&/^[0-9]+$/.test(value)||typeof value==='number'))throw Error('Invalid room history roomcode');
|
||||
const code=Number(value);if(!Number.isSafeInteger(code)||code<=0)throw Error('Invalid room history roomcode');return code;
|
||||
}
|
||||
private save(data:readonly number[]):void{this.storage.setItem(this.key,JSON.stringify({data}));}
|
||||
joined(raw:Readonly<Record<string,unknown>>):void{
|
||||
if(Number(raw.state)===99||Number(raw.state)===0&&Number(raw.isowner)===1){const code=this.code(raw.roomcode);this.save(this.read().filter(n=>n!==code));}
|
||||
}
|
||||
exited(raw:Readonly<Record<string,unknown>>,infinite:number):void{
|
||||
if(Number(raw.isowner)!==1||infinite!==0)return;
|
||||
const code=this.code(raw.roomcode),list=this.read();if(!list.includes(code))this.save([code,...list]);
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "ca074aba-afc9-428f-93ea-199d30dba6ef",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -1,17 +1,19 @@
|
||||
import type { ChannelIdentity } from '../identity.ts';
|
||||
|
||||
/** Legacy GameData launchWaitTime (04_Data.js): timer units are milliseconds. */
|
||||
export const DEFAULT_LAUNCH_WAIT_TIME_MS = 10;
|
||||
|
||||
/**
|
||||
* 构建期默认渠道身份(旧 version.js 等价物)。
|
||||
* gameid 每子游戏固定写死;agentid/channelid/marketid/version 为默认,运行期由 query/native/profile 覆盖。
|
||||
*/
|
||||
export const BUILD_IDENTITY: ChannelIdentity = {
|
||||
// This game's entry in config_test/update_jsonv2.txt belongs to this agent/channel.
|
||||
agentid: '1B2h0ccl205c390Y28m1Ajdplkuu4wgy',
|
||||
channelid: 'aouv0LotK0pYyQ0Pdrx0CsdcaezfzrcG',
|
||||
gameid: 'J9r4022t80pxlb01zOK76DN1WD4QewFl',
|
||||
marketid: 4,
|
||||
version: 1, // 数字 versionCode(联调时改成本地服要求的版本号)
|
||||
};
|
||||
import type { ChannelIdentity } from '../identity.ts';
|
||||
|
||||
/** Legacy GameData launchWaitTime (04_Data.js): timer units are milliseconds. */
|
||||
export const DEFAULT_LAUNCH_WAIT_TIME_MS = 10;
|
||||
|
||||
/**
|
||||
* 构建期默认渠道身份(旧 version.js 等价物)。
|
||||
* gameid 每子游戏固定写死;agentid/channelid/marketid/version 为默认,运行期由 query/native/profile 覆盖。
|
||||
*/
|
||||
export const BUILD_IDENTITY: ChannelIdentity = {
|
||||
// This game's entry in config_test/update_jsonv2.txt belongs to this agent/channel.
|
||||
agentid: '1B2h0ccl205c390Y28m1Ajdplkuu4wgy',
|
||||
channelid: 'aouv0LotK0pYyQ0Pdrx0CsdcaezfzrcG',
|
||||
// agentid: 'veRa0qrBf0df2K1G4de2tgfmVxB2jxpv',
|
||||
// channelid: 'FtJf073aa0d6rI1xD8J1Y42fINTm0ziK',
|
||||
gameid: 'J9r4022t80pxlb01zOK76DN1WD4QewFl',
|
||||
marketid: 4,
|
||||
version: 1, // 数字 versionCode(联调时改成本地服要求的版本号)
|
||||
};
|
||||
|
||||
@@ -1,133 +1,181 @@
|
||||
import type { RuntimeConfig } from '../config/runtime-config.ts';
|
||||
import { buildFirstSliceEnvelope } from '../protocol/first-slice-routes.ts';
|
||||
import type { OutboundEnvelope } from '../core/types/envelope.ts';
|
||||
import {
|
||||
buildExitRoomRequest,
|
||||
buildJoinRoomRequest,
|
||||
buildPrepareRequest,
|
||||
type JoinRoomEnvironment,
|
||||
type RoomIdentity,
|
||||
} from '../protocol/contracts/index.ts';
|
||||
import { PlatformStore } from './stores/platform-store.ts';
|
||||
import type { InsideRoomState, PlatformState } from './stores/platform-types.ts';
|
||||
|
||||
/** The complete-envelope send capability used by platform command adapters. */
|
||||
export interface OutboundEnvelopeSendPort {
|
||||
send(envelope: OutboundEnvelope): void;
|
||||
}
|
||||
|
||||
export interface PlatformCommandsOptions {
|
||||
readonly runtimeConfig: RuntimeConfig;
|
||||
readonly store: PlatformStore;
|
||||
readonly send: OutboundEnvelopeSendPort;
|
||||
}
|
||||
|
||||
export interface JoinRoomCommand extends JoinRoomEnvironment {
|
||||
readonly roomcode: string;
|
||||
}
|
||||
|
||||
export class UnsupportedGameHostCommandError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'UnsupportedGameHostCommandError';
|
||||
}
|
||||
}
|
||||
|
||||
/** Builds platform-owned room requests exclusively through the first-slice contracts. */
|
||||
export class PlatformCommands {
|
||||
constructor(private readonly options: PlatformCommandsOptions) {}
|
||||
|
||||
getVipRooms(shortcodeList:readonly (string|number)[]):void {
|
||||
this.taskPlayerId();
|
||||
const {agentid,gameid}=this.options.runtimeConfig.identity;
|
||||
this.options.send.send(buildFirstSliceEnvelope('getInfoByShortCode',{agentid,gameid,shortcodeList:[...shortcodeList]}));
|
||||
}
|
||||
|
||||
joinVipRoom(roomcode:string|number,location:unknown):void {
|
||||
const playerid=this.taskPlayerId(),state=this.options.store.getState();
|
||||
const ip=state.players.entities[playerid].ip;
|
||||
if(typeof ip!=='string')throw Error('VIP join requires player login ip');
|
||||
const {agentid,gameid}=this.options.runtimeConfig.identity;
|
||||
this.options.send.send(buildFirstSliceEnvelope('self_join_room',{agentid,playerid,gameid,roomcode,joinmode:0,location,ip}));
|
||||
}
|
||||
|
||||
getPlayerTasks(): void {
|
||||
const playerid=this.taskPlayerId();
|
||||
const {agentid,gameid}=this.options.runtimeConfig.identity;
|
||||
this.options.send.send(buildFirstSliceEnvelope('get_player_task',{agentid,playerid,gameid}));
|
||||
}
|
||||
|
||||
claimTask(taskid:number|string):void {
|
||||
const playerid=this.taskPlayerId();
|
||||
const task=this.options.store.getState().tasks?.find(task=>String(task.taskid)===String(taskid));
|
||||
if(!task||task.state!==1)throw Error('Task is not claimable');
|
||||
// 11_GameUI claim click sends these three fields; Net._SendData adds none.
|
||||
this.options.send.send(buildFirstSliceEnvelope('get_task_award',{
|
||||
agentid:this.options.runtimeConfig.identity.agentid,playerid,taskid:task.taskid,
|
||||
}));
|
||||
}
|
||||
|
||||
private taskPlayerId():number {
|
||||
const state=this.options.store.getState();
|
||||
if(state.app.phase!=='logged-in'||state.room.kind!=='outside')throw Error('Task command requires logged-in lobby');
|
||||
return this.requireAuthenticatedPlayerId(state);
|
||||
}
|
||||
|
||||
prepare(): void {
|
||||
const state = this.options.store.getState();
|
||||
this.options.send.send(buildPrepareRequest(
|
||||
this.options.runtimeConfig,
|
||||
this.roomIdentity(state, this.requireInsideRoom(state)),
|
||||
));
|
||||
}
|
||||
|
||||
exitRoom(): void {
|
||||
const state = this.options.store.getState();
|
||||
const room = this.requireInsideRoom(state);
|
||||
if (room.stage !== 0 && room.infinite !== 1) {
|
||||
throw new UnsupportedGameHostCommandError('room.exit requires stage=0 unless infinite=1');
|
||||
}
|
||||
this.options.send.send(buildExitRoomRequest(
|
||||
this.options.runtimeConfig,
|
||||
this.roomIdentity(state, room),
|
||||
));
|
||||
}
|
||||
|
||||
joinRoom(command: JoinRoomCommand): void {
|
||||
const state = this.options.store.getState();
|
||||
if (state.room.kind !== 'outside') {
|
||||
throw new UnsupportedGameHostCommandError('room.join requires player to be outside a room');
|
||||
}
|
||||
const playerid = this.requireAuthenticatedPlayerId(state);
|
||||
this.options.send.send(buildJoinRoomRequest(
|
||||
this.options.runtimeConfig,
|
||||
{ playerid, roomcode: command.roomcode },
|
||||
command,
|
||||
));
|
||||
}
|
||||
|
||||
private requireInsideRoom(state: PlatformState): InsideRoomState {
|
||||
if (state.room.kind !== 'inside') {
|
||||
throw new UnsupportedGameHostCommandError('room command requires player to be inside a room');
|
||||
}
|
||||
return state.room;
|
||||
}
|
||||
|
||||
private requireAuthenticatedPlayerId(state: PlatformState): number {
|
||||
const playerid = state.players.selfPlayerId;
|
||||
if (playerid === null) {
|
||||
throw new UnsupportedGameHostCommandError('room command requires an authenticated player');
|
||||
}
|
||||
if (state.players.entities[playerid] === undefined) {
|
||||
throw new Error('players.entities: missing authenticated player');
|
||||
}
|
||||
return playerid;
|
||||
}
|
||||
|
||||
private roomIdentity(state: PlatformState, room: InsideRoomState): RoomIdentity {
|
||||
return {
|
||||
playerid: this.requireAuthenticatedPlayerId(state),
|
||||
roomcode: room.roomcode,
|
||||
};
|
||||
}
|
||||
}
|
||||
import type { RuntimeConfig } from '../config/runtime-config.ts';
|
||||
import { buildFirstSliceEnvelope } from '../protocol/first-slice-routes.ts';
|
||||
import type { OutboundEnvelope } from '../core/types/envelope.ts';
|
||||
import {
|
||||
buildCreateRoomRequest,
|
||||
type CreateRoomEnvironment,
|
||||
buildExitRoomRequest,
|
||||
buildJoinRoomRequest,
|
||||
buildPrepareRequest,
|
||||
type JoinRoomEnvironment,
|
||||
type RoomIdentity,
|
||||
} from '../protocol/contracts/index.ts';
|
||||
import { cloneAndFreezeRoomtype, PlatformStore } from './stores/platform-store.ts';
|
||||
import type { InsideRoomState, PlatformState } from './stores/platform-types.ts';
|
||||
|
||||
/** The complete-envelope send capability used by platform command adapters. */
|
||||
export interface OutboundEnvelopeSendPort {
|
||||
send(envelope: OutboundEnvelope): void;
|
||||
}
|
||||
|
||||
export interface PlatformCommandsOptions {
|
||||
readonly runtimeConfig: RuntimeConfig;
|
||||
readonly store: PlatformStore;
|
||||
readonly send: OutboundEnvelopeSendPort;
|
||||
}
|
||||
|
||||
export interface JoinRoomCommand extends JoinRoomEnvironment {
|
||||
readonly roomcode: string | number;
|
||||
}
|
||||
|
||||
export interface CreateRoomCommand extends CreateRoomEnvironment {}
|
||||
|
||||
export class UnsupportedGameHostCommandError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'UnsupportedGameHostCommandError';
|
||||
}
|
||||
}
|
||||
|
||||
/** Builds platform-owned room requests exclusively through the first-slice contracts. */
|
||||
export class PlatformCommands {
|
||||
constructor(private readonly options: PlatformCommandsOptions) {}
|
||||
|
||||
getPlayerInvitecode():void {
|
||||
const playerid=this.taskPlayerId(),player=this.options.store.getState().players.entities[playerid];
|
||||
const {openid,unionid}=player;
|
||||
if(typeof openid!=='string'||!(typeof unionid==='string'||typeof unionid==='number'&&Number.isFinite(unionid)))throw Error('Invite query requires login openid and unionid');
|
||||
this.options.send.send(buildFirstSliceEnvelope('get_player_invitecode',{
|
||||
agentid:this.options.runtimeConfig.identity.agentid,playerid,unionid,openid,
|
||||
}));
|
||||
}
|
||||
|
||||
bindInvitecode(invitecode:string):void {
|
||||
const playerid=this.taskPlayerId();
|
||||
if(typeof invitecode!=='string')throw Error('invitecode must be a string');
|
||||
this.options.send.send(buildFirstSliceEnvelope('binding_invitecode',{
|
||||
agentid:this.options.runtimeConfig.identity.agentid,playerid,invitecode,
|
||||
}));
|
||||
}
|
||||
|
||||
getVipRooms(shortcodeList:readonly (string|number)[]):void {
|
||||
this.taskPlayerId();
|
||||
const {agentid,gameid}=this.options.runtimeConfig.identity;
|
||||
this.options.send.send(buildFirstSliceEnvelope('getInfoByShortCode',{agentid,gameid,shortcodeList:[...shortcodeList]}));
|
||||
}
|
||||
|
||||
joinVipRoom(roomcode:string|number,location:unknown):void {
|
||||
const playerid=this.taskPlayerId(),state=this.options.store.getState();
|
||||
const ip=state.players.entities[playerid].ip;
|
||||
if(typeof ip!=='string')throw Error('VIP join requires player login ip');
|
||||
const {agentid,gameid}=this.options.runtimeConfig.identity;
|
||||
this.options.send.send(buildFirstSliceEnvelope('self_join_room',{agentid,playerid,gameid,roomcode,joinmode:0,location,ip}));
|
||||
}
|
||||
|
||||
getPlayerTasks(): void {
|
||||
const playerid=this.taskPlayerId();
|
||||
const {agentid,gameid}=this.options.runtimeConfig.identity;
|
||||
this.options.send.send(buildFirstSliceEnvelope('get_player_task',{agentid,playerid,gameid}));
|
||||
}
|
||||
|
||||
getVipRankList(limit:number):void {
|
||||
this.taskPlayerId();if(!Number.isInteger(limit)||limit<=0)throw Error('Invalid vipRankLimit');
|
||||
this.options.send.send(buildFirstSliceEnvelope('getVipRankList',{agentid:this.options.runtimeConfig.identity.agentid,limit}));
|
||||
}
|
||||
joinOwnedRoom(roomcode:number,location:unknown):void {
|
||||
const playerid=this.taskPlayerId(),state=this.options.store.getState();
|
||||
if(!Number.isSafeInteger(roomcode)||roomcode<=0)throw Error('Invalid owned roomcode');
|
||||
const ip=state.players.entities[playerid].ip;if(typeof ip!=='string')throw Error('Owned room join requires player login ip');
|
||||
const {agentid,gameid}=this.options.runtimeConfig.identity;
|
||||
this.options.send.send(buildFirstSliceEnvelope('self_join_room',{agentid,playerid,gameid,roomcode,joinmode:1,location,ip}));
|
||||
}
|
||||
getTreasureList():void {
|
||||
this.taskPlayerId();const {agentid,gameid}=this.options.runtimeConfig.identity;
|
||||
this.options.send.send(buildFirstSliceEnvelope('get_treasurelist',{agentid,gameid}));
|
||||
}
|
||||
redeemCard(cardno:string):void {
|
||||
const playerid=this.taskPlayerId();
|
||||
if(typeof cardno!=='string'||cardno===''||/^[ ]+$/.test(cardno))throw Error('Invalid cardno');
|
||||
this.options.send.send(buildFirstSliceEnvelope('topup_card',{agentid:this.options.runtimeConfig.identity.agentid,playerid,cardno}));
|
||||
}
|
||||
|
||||
claimTask(taskid:number|string):void {
|
||||
const playerid=this.taskPlayerId();
|
||||
const task=this.options.store.getState().tasks?.find(task=>String(task.taskid)===String(taskid));
|
||||
if(!task||task.state!==1)throw Error('Task is not claimable');
|
||||
// 11_GameUI claim click sends these three fields; Net._SendData adds none.
|
||||
this.options.send.send(buildFirstSliceEnvelope('get_task_award',{
|
||||
agentid:this.options.runtimeConfig.identity.agentid,playerid,taskid:task.taskid,
|
||||
}));
|
||||
}
|
||||
|
||||
private taskPlayerId():number {
|
||||
const state=this.options.store.getState();
|
||||
if(state.app.phase!=='logged-in'||state.room.kind!=='outside')throw Error('Task command requires logged-in lobby');
|
||||
return this.requireAuthenticatedPlayerId(state);
|
||||
}
|
||||
|
||||
prepare(): void {
|
||||
const state = this.options.store.getState();
|
||||
this.options.send.send(buildPrepareRequest(
|
||||
this.options.runtimeConfig,
|
||||
this.roomIdentity(state, this.requireInsideRoom(state)),
|
||||
));
|
||||
}
|
||||
|
||||
exitRoom(): void {
|
||||
const state = this.options.store.getState();
|
||||
const room = this.requireInsideRoom(state);
|
||||
if (room.stage !== 0 && room.infinite !== 1) {
|
||||
throw new UnsupportedGameHostCommandError('room.exit requires stage=0 unless infinite=1');
|
||||
}
|
||||
this.options.send.send(buildExitRoomRequest(
|
||||
this.options.runtimeConfig,
|
||||
this.roomIdentity(state, room),
|
||||
));
|
||||
}
|
||||
|
||||
createRoom(command: CreateRoomCommand): void {
|
||||
const playerid = this.taskPlayerId();
|
||||
const roomtype = cloneAndFreezeRoomtype(command.roomtype);
|
||||
this.options.send.send(buildCreateRoomRequest(this.options.runtimeConfig, playerid, {...command, roomtype}));
|
||||
}
|
||||
|
||||
joinRoom(command: JoinRoomCommand): void {
|
||||
const state = this.options.store.getState();
|
||||
if (state.room.kind !== 'outside') {
|
||||
throw new UnsupportedGameHostCommandError('room.join requires player to be outside a room');
|
||||
}
|
||||
const playerid = this.requireAuthenticatedPlayerId(state);
|
||||
this.options.send.send(buildJoinRoomRequest(
|
||||
this.options.runtimeConfig,
|
||||
{ playerid, roomcode: command.roomcode },
|
||||
command,
|
||||
));
|
||||
}
|
||||
|
||||
private requireInsideRoom(state: PlatformState): InsideRoomState {
|
||||
if (state.room.kind !== 'inside') {
|
||||
throw new UnsupportedGameHostCommandError('room command requires player to be inside a room');
|
||||
}
|
||||
return state.room;
|
||||
}
|
||||
|
||||
private requireAuthenticatedPlayerId(state: PlatformState): number {
|
||||
const playerid = state.players.selfPlayerId;
|
||||
if (playerid === null) {
|
||||
throw new UnsupportedGameHostCommandError('room command requires an authenticated player');
|
||||
}
|
||||
if (state.players.entities[playerid] === undefined) {
|
||||
throw new Error('players.entities: missing authenticated player');
|
||||
}
|
||||
return playerid;
|
||||
}
|
||||
|
||||
private roomIdentity(state: PlatformState, room: InsideRoomState): RoomIdentity {
|
||||
return {
|
||||
playerid: this.requireAuthenticatedPlayerId(state),
|
||||
roomcode: room.roomcode,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,243 +1,276 @@
|
||||
import type { GameHost } from '../sdk/contracts/index.ts';
|
||||
import {
|
||||
extractGameSessionOpenFaultErrors,
|
||||
GameSessionHost,
|
||||
} from '../sdk/runtime/game-session-host.ts';
|
||||
import {
|
||||
parseLoginResponse,
|
||||
parseSelfJoinRoomResponse,
|
||||
type ParsedSelfExitPayload,
|
||||
type ReconnectSnapshot,
|
||||
} from '../protocol/contracts/index.ts';
|
||||
import { PlatformStore } from './stores/platform-store.ts';
|
||||
import type { PlatformState } from './stores/platform-types.ts';
|
||||
|
||||
export interface GameHostLeasePort {
|
||||
readonly host: GameHost;
|
||||
invalidate(): void;
|
||||
}
|
||||
|
||||
export type GameHostLeaseFactory = () => GameHostLeasePort;
|
||||
|
||||
/** A valid nonzero server outcome, delivered without changing canonical state. */
|
||||
export interface ServerDenial {
|
||||
readonly rpc: 'player_login' | 'self_join_room';
|
||||
readonly state: number;
|
||||
readonly data: Readonly<Record<string, unknown>>;
|
||||
}
|
||||
|
||||
export interface RuntimeSessionOptions {
|
||||
readonly store: PlatformStore;
|
||||
readonly gameSession: GameSessionHost;
|
||||
readonly createGameHostLease: GameHostLeaseFactory;
|
||||
readonly onServerDenial: (outcome: ServerDenial) => void;
|
||||
}
|
||||
|
||||
export type RuntimeSessionState = 'operational' | 'faulted' | 'kicked';
|
||||
|
||||
export class RuntimeSessionFault extends Error {
|
||||
readonly errors: readonly unknown[];
|
||||
|
||||
constructor(operation: string, errors: readonly unknown[]) {
|
||||
super(`RuntimeSession fault during ${operation}`);
|
||||
this.name = 'RuntimeSessionFault';
|
||||
this.errors = Object.freeze([...errors]);
|
||||
}
|
||||
}
|
||||
|
||||
/** Coordinates the canonical platform snapshot with the one active game session. */
|
||||
export class RuntimeSession {
|
||||
private loginIsPending = false;
|
||||
private wasKicked = false;
|
||||
private terminalFault: RuntimeSessionFault | null = null;
|
||||
private activeLease: GameHostLeasePort | null = null;
|
||||
|
||||
constructor(private readonly options: RuntimeSessionOptions) {}
|
||||
|
||||
get loginPending(): boolean { return this.loginIsPending; }
|
||||
|
||||
get kicked(): boolean { return this.wasKicked; }
|
||||
|
||||
get state(): RuntimeSessionState {
|
||||
if (this.wasKicked) return 'kicked';
|
||||
return this.terminalFault !== null ? 'faulted' : 'operational';
|
||||
}
|
||||
|
||||
get fault(): Error | null { return this.terminalFault; }
|
||||
|
||||
markLoginPending(): void {
|
||||
this.requireOperational();
|
||||
this.loginIsPending = true;
|
||||
}
|
||||
|
||||
handleLogin(data: unknown): void {
|
||||
this.requireOperational();
|
||||
this.loginIsPending = false;
|
||||
const parsed = parseLoginResponse(data);
|
||||
if (parsed.state !== 0) {
|
||||
this.options.onServerDenial({ rpc: 'player_login', state: parsed.state, data: parsed.raw });
|
||||
return;
|
||||
}
|
||||
const committed = this.options.store.applyLoginSuccess(parsed);
|
||||
if (parsed.room === null) {
|
||||
this.releaseOrFault('login lobby replacement');
|
||||
return;
|
||||
}
|
||||
this.openCommittedRoom(committed, parsed.reconnect, 'login room open');
|
||||
}
|
||||
|
||||
handleSelfJoin(data: unknown): void {
|
||||
this.requireOperational();
|
||||
const parsed = parseSelfJoinRoomResponse(data);
|
||||
if (parsed.state !== 0) {
|
||||
this.options.onServerDenial({ rpc: 'self_join_room', state: parsed.state, data: parsed.raw });
|
||||
return;
|
||||
}
|
||||
const committed = this.options.store.replaceRoom(parsed);
|
||||
this.openCommittedRoom(committed, parsed.reconnect, 'self join room open');
|
||||
}
|
||||
|
||||
handleSelfExit(parsed: ParsedSelfExitPayload): void {
|
||||
this.requireOperational();
|
||||
const state = this.options.store.getState();
|
||||
if (state.room.kind !== 'inside') {
|
||||
if (this.options.gameSession.state === 'idle' && this.activeLease === null) return;
|
||||
const errors = [
|
||||
new Error('self_exit_room found an active game without room state'),
|
||||
...this.releaseGame(),
|
||||
];
|
||||
this.throwFault('self exit inconsistent state', errors);
|
||||
}
|
||||
if (parsed.seat !== undefined && parsed.seat !== state.room.selfSeat) {
|
||||
throw new RangeError(
|
||||
`self_exit_room seat mismatch: expected ${state.room.selfSeat}, received ${parsed.seat}`,
|
||||
);
|
||||
}
|
||||
if (this.options.gameSession.state !== 'active' || this.activeLease === null) {
|
||||
const errors = [
|
||||
new Error('self_exit_room requires an active game session and host lease'),
|
||||
...this.releaseGame(),
|
||||
];
|
||||
this.throwFault('self exit inconsistent state', errors);
|
||||
}
|
||||
|
||||
try {
|
||||
this.options.store.clearRoom();
|
||||
} catch (error) {
|
||||
this.throwFault('self exit state commit', [error, ...this.releaseGame()]);
|
||||
}
|
||||
this.releaseOrFault('self exit cleanup');
|
||||
}
|
||||
|
||||
handleServerSwitch(switchConnection: () => void): void {
|
||||
this.requireOperational();
|
||||
const errors = this.releaseGame();
|
||||
try {
|
||||
switchConnection();
|
||||
} catch (error) {
|
||||
errors.push(error);
|
||||
}
|
||||
if (errors.length > 0) this.throwFault('server switch', errors);
|
||||
}
|
||||
|
||||
handleKick(_data: unknown, stopConnection: () => void): void {
|
||||
if (this.wasKicked) return;
|
||||
this.requireOperational();
|
||||
this.loginIsPending = false;
|
||||
this.wasKicked = true;
|
||||
|
||||
const errors = this.releaseGame();
|
||||
try {
|
||||
stopConnection();
|
||||
} catch (error) {
|
||||
errors.push(error);
|
||||
}
|
||||
if (errors.length > 0) this.throwFault('kick terminal cleanup', errors);
|
||||
}
|
||||
|
||||
private openCommittedRoom(
|
||||
state: PlatformState,
|
||||
reconnect: ReconnectSnapshot,
|
||||
operation: string,
|
||||
): void {
|
||||
if (state.room.kind !== 'inside') {
|
||||
this.throwFault(operation, [new Error('Cannot open a game session without committed room state')]);
|
||||
}
|
||||
|
||||
const priorCleanupErrors = this.releaseGame();
|
||||
if (priorCleanupErrors.length > 0) this.throwFault(operation, priorCleanupErrors);
|
||||
if (this.gameWasDisposed()) return;
|
||||
|
||||
let lease: GameHostLeasePort;
|
||||
try {
|
||||
lease = this.options.createGameHostLease();
|
||||
if (!lease || typeof lease !== 'object' || typeof lease.invalidate !== 'function') {
|
||||
throw new TypeError('createGameHostLease must return { host, invalidate }');
|
||||
}
|
||||
} catch (error) {
|
||||
this.throwFault(operation, [error]);
|
||||
}
|
||||
this.activeLease = lease;
|
||||
|
||||
try {
|
||||
this.options.gameSession.open(lease.host);
|
||||
if (this.gameWasDisposed()) return;
|
||||
this.options.gameSession.publish({
|
||||
type: 'room.entered',
|
||||
roomtype: state.room.roomtype,
|
||||
});
|
||||
if (this.gameWasDisposed()) return;
|
||||
if (reconnect.present) this.options.gameSession.restore(reconnect.value);
|
||||
} catch (error) {
|
||||
const openErrors = extractGameSessionOpenFaultErrors(error);
|
||||
const errors = openErrors === null
|
||||
? [error]
|
||||
: [openErrors[0], openErrors[1]];
|
||||
errors.push(...this.releaseGame());
|
||||
this.throwFault(operation, errors);
|
||||
}
|
||||
}
|
||||
|
||||
private releaseOrFault(operation: string): void {
|
||||
const errors = this.releaseGame();
|
||||
if (errors.length > 0) this.throwFault(operation, errors);
|
||||
}
|
||||
|
||||
private gameWasDisposed(): boolean {
|
||||
return this.options.gameSession.state === 'disposed';
|
||||
}
|
||||
|
||||
private releaseGame(): unknown[] {
|
||||
const errors: unknown[] = [];
|
||||
const lease = this.activeLease;
|
||||
this.activeLease = null;
|
||||
|
||||
if (this.options.gameSession.state !== 'idle' && !this.gameWasDisposed()) {
|
||||
try {
|
||||
this.options.gameSession.close();
|
||||
} catch (error) {
|
||||
errors.push(error);
|
||||
}
|
||||
}
|
||||
if (lease !== null) {
|
||||
try {
|
||||
lease.invalidate();
|
||||
} catch (error) {
|
||||
errors.push(error);
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
private throwFault(operation: string, errors: readonly unknown[]): never {
|
||||
const fault = new RuntimeSessionFault(operation, errors);
|
||||
this.loginIsPending = false;
|
||||
this.terminalFault = fault;
|
||||
throw fault;
|
||||
}
|
||||
|
||||
private requireOperational(): void {
|
||||
if (this.terminalFault !== null) throw this.terminalFault;
|
||||
if (this.wasKicked) throw new Error('RuntimeSession is kicked');
|
||||
}
|
||||
}
|
||||
import type { GameHost } from '../sdk/contracts/index.ts';
|
||||
import {
|
||||
extractGameSessionOpenFaultErrors,
|
||||
GameSessionHost,
|
||||
} from '../sdk/runtime/game-session-host.ts';
|
||||
import {
|
||||
parseCreateRoomResponse,
|
||||
parseLoginResponse,
|
||||
parseSelfJoinRoomResponse,
|
||||
type ParsedSelfExitPayload,
|
||||
type ReconnectSnapshot,
|
||||
} from '../protocol/contracts/index.ts';
|
||||
import { cloneAndFreezeRoomtype, PlatformStore } from './stores/platform-store.ts';
|
||||
import type { PlatformState } from './stores/platform-types.ts';
|
||||
import type {LoginAccountIdentity} from '../protocol/contracts/login-contract.ts';
|
||||
|
||||
export interface GameHostLeasePort {
|
||||
readonly host: GameHost;
|
||||
invalidate(): void;
|
||||
}
|
||||
|
||||
export type GameHostLeaseFactory = () => GameHostLeasePort;
|
||||
|
||||
/** A valid nonzero server outcome, delivered without changing canonical state. */
|
||||
export interface ServerDenial {
|
||||
readonly rpc: 'player_login' | 'self_join_room' | 'create_room';
|
||||
readonly state: number;
|
||||
readonly data: Readonly<Record<string, unknown>>;
|
||||
}
|
||||
|
||||
export interface RuntimeSessionOptions {
|
||||
readonly roomHistory?: import('../adapters/native/owned-room-history.ts').OwnedRoomHistory;
|
||||
readonly store: PlatformStore;
|
||||
readonly gameSession: GameSessionHost;
|
||||
readonly createGameHostLease: GameHostLeaseFactory;
|
||||
readonly onServerDenial: (outcome: ServerDenial) => void;
|
||||
}
|
||||
|
||||
export type RuntimeSessionState = 'operational' | 'faulted' | 'kicked';
|
||||
|
||||
export class RuntimeSessionFault extends Error {
|
||||
readonly errors: readonly unknown[];
|
||||
|
||||
constructor(operation: string, errors: readonly unknown[]) {
|
||||
super(`RuntimeSession fault during ${operation}`);
|
||||
this.name = 'RuntimeSessionFault';
|
||||
this.errors = Object.freeze([...errors]);
|
||||
}
|
||||
}
|
||||
|
||||
/** Coordinates the canonical platform snapshot with the one active game session. */
|
||||
export class RuntimeSession {
|
||||
private loginIsPending = false;
|
||||
private lastJoinedRoomcode: string | null = null;
|
||||
/** Non-device login owns identity in authorization; device login owns it in the response. */
|
||||
private authorizedIdentity:Pick<LoginAccountIdentity,'openid'|'unionid'>|undefined;
|
||||
private wasKicked = false;
|
||||
private terminalFault: RuntimeSessionFault | null = null;
|
||||
private activeLease: GameHostLeasePort | null = null;
|
||||
|
||||
constructor(private readonly options: RuntimeSessionOptions) {}
|
||||
|
||||
get loginPending(): boolean { return this.loginIsPending; }
|
||||
|
||||
get kicked(): boolean { return this.wasKicked; }
|
||||
|
||||
get state(): RuntimeSessionState {
|
||||
if (this.wasKicked) return 'kicked';
|
||||
return this.terminalFault !== null ? 'faulted' : 'operational';
|
||||
}
|
||||
|
||||
get fault(): Error | null { return this.terminalFault; }
|
||||
|
||||
markLoginPending(identity?:Pick<LoginAccountIdentity,'openid'|'unionid'>|null): void {
|
||||
this.requireOperational();
|
||||
// No argument is a reconnect of the current account; null selects device response identity.
|
||||
if(identity!==undefined)this.authorizedIdentity=identity===null?undefined:identity;
|
||||
this.loginIsPending = true;
|
||||
}
|
||||
|
||||
handleLogin(data: unknown): void {
|
||||
this.requireOperational();
|
||||
this.loginIsPending = false;
|
||||
const parsed = parseLoginResponse(data);
|
||||
if (parsed.state !== 0) {
|
||||
this.options.onServerDenial({ rpc: 'player_login', state: parsed.state, data: parsed.raw });
|
||||
return;
|
||||
}
|
||||
this.lastJoinedRoomcode = null;
|
||||
const committed = this.options.store.applyLoginSuccess(parsed,this.authorizedIdentity);
|
||||
if (parsed.room === null) {
|
||||
this.releaseOrFault('login lobby replacement');
|
||||
return;
|
||||
}
|
||||
this.openCommittedRoom(committed, parsed.reconnect, 'login room open');
|
||||
}
|
||||
|
||||
handleCreateRoom(data: unknown): void {
|
||||
this.requireOperational();
|
||||
const parsed = parseCreateRoomResponse(data);
|
||||
if (parsed.state !== 0) {
|
||||
this.options.onServerDenial({rpc: 'create_room', state: parsed.state, data: parsed.raw});
|
||||
return;
|
||||
}
|
||||
if (parsed.room === null) throw new Error('create_room missing parsed room');
|
||||
const current = this.options.store.getState().room;
|
||||
if (current.kind === 'inside' && current.roomcode === parsed.room.roomcode
|
||||
&& this.options.gameSession.state === 'active') return;
|
||||
// Subgame code receives an owned immutable value, never the server response graph.
|
||||
const roomtype = cloneAndFreezeRoomtype(parsed.room.roomtype);
|
||||
const count = this.options.gameSession.entry.resolveSeatCount(roomtype);
|
||||
const committed = this.options.store.createRoom(parsed, count);
|
||||
this.openCommittedRoom(committed, {present: false}, 'create room open');
|
||||
}
|
||||
|
||||
handleSelfJoin(data: unknown): void {
|
||||
this.requireOperational();
|
||||
const parsed = parseSelfJoinRoomResponse(data);
|
||||
this.options.roomHistory?.joined(parsed.raw);
|
||||
if (parsed.state !== 0) {
|
||||
this.options.onServerDenial({ rpc: 'self_join_room', state: parsed.state, data: parsed.raw });
|
||||
return;
|
||||
}
|
||||
if (parsed.room !== null && this.lastJoinedRoomcode === parsed.room.roomcode
|
||||
&& this.options.gameSession.state === 'active') return;
|
||||
const committed = this.options.store.replaceRoom(parsed);
|
||||
this.openCommittedRoom(committed, parsed.reconnect, 'self join room open');
|
||||
this.lastJoinedRoomcode = parsed.room!.roomcode;
|
||||
}
|
||||
|
||||
handleSelfExit(parsed: ParsedSelfExitPayload): void {
|
||||
this.requireOperational();
|
||||
const state = this.options.store.getState();
|
||||
if (state.room.kind !== 'inside') {
|
||||
if (this.options.gameSession.state === 'idle' && this.activeLease === null) return;
|
||||
const errors = [
|
||||
new Error('self_exit_room found an active game without room state'),
|
||||
...this.releaseGame(),
|
||||
];
|
||||
this.throwFault('self exit inconsistent state', errors);
|
||||
}
|
||||
if (parsed.seat !== undefined && parsed.seat !== state.room.selfSeat) {
|
||||
throw new RangeError(
|
||||
`self_exit_room seat mismatch: expected ${state.room.selfSeat}, received ${parsed.seat}`,
|
||||
);
|
||||
}
|
||||
if (this.options.gameSession.state !== 'active' || this.activeLease === null) {
|
||||
const errors = [
|
||||
new Error('self_exit_room requires an active game session and host lease'),
|
||||
...this.releaseGame(),
|
||||
];
|
||||
this.throwFault('self exit inconsistent state', errors);
|
||||
}
|
||||
|
||||
try {
|
||||
this.options.roomHistory?.exited(parsed.raw,state.room.infinite);
|
||||
this.options.store.clearRoom();
|
||||
} catch (error) {
|
||||
this.throwFault('self exit state commit', [error, ...this.releaseGame()]);
|
||||
}
|
||||
this.releaseOrFault('self exit cleanup');
|
||||
}
|
||||
|
||||
handleServerSwitch(switchConnection: () => void): void {
|
||||
this.requireOperational();
|
||||
const errors = this.releaseGame();
|
||||
try {
|
||||
switchConnection();
|
||||
} catch (error) {
|
||||
errors.push(error);
|
||||
}
|
||||
if (errors.length > 0) this.throwFault('server switch', errors);
|
||||
}
|
||||
|
||||
handleKick(_data: unknown, stopConnection: () => void): void {
|
||||
if (this.wasKicked) return;
|
||||
this.requireOperational();
|
||||
this.loginIsPending = false;
|
||||
this.wasKicked = true;
|
||||
|
||||
const errors = this.releaseGame();
|
||||
try {
|
||||
stopConnection();
|
||||
} catch (error) {
|
||||
errors.push(error);
|
||||
}
|
||||
if (errors.length > 0) this.throwFault('kick terminal cleanup', errors);
|
||||
}
|
||||
|
||||
private openCommittedRoom(
|
||||
state: PlatformState,
|
||||
reconnect: ReconnectSnapshot,
|
||||
operation: string,
|
||||
): void {
|
||||
if (state.room.kind !== 'inside') {
|
||||
this.throwFault(operation, [new Error('Cannot open a game session without committed room state')]);
|
||||
}
|
||||
|
||||
const priorCleanupErrors = this.releaseGame();
|
||||
if (priorCleanupErrors.length > 0) this.throwFault(operation, priorCleanupErrors);
|
||||
if (this.gameWasDisposed()) return;
|
||||
|
||||
let lease: GameHostLeasePort;
|
||||
try {
|
||||
lease = this.options.createGameHostLease();
|
||||
if (!lease || typeof lease !== 'object' || typeof lease.invalidate !== 'function') {
|
||||
throw new TypeError('createGameHostLease must return { host, invalidate }');
|
||||
}
|
||||
} catch (error) {
|
||||
this.throwFault(operation, [error]);
|
||||
}
|
||||
this.activeLease = lease;
|
||||
|
||||
try {
|
||||
this.options.gameSession.open(lease.host);
|
||||
if (this.gameWasDisposed()) return;
|
||||
this.options.gameSession.publish({
|
||||
type: 'room.entered',
|
||||
roomtype: state.room.roomtype,
|
||||
});
|
||||
if (this.gameWasDisposed()) return;
|
||||
if (reconnect.present) this.options.gameSession.restore(reconnect.value);
|
||||
} catch (error) {
|
||||
const openErrors = extractGameSessionOpenFaultErrors(error);
|
||||
const errors = openErrors === null
|
||||
? [error]
|
||||
: [openErrors[0], openErrors[1]];
|
||||
errors.push(...this.releaseGame());
|
||||
this.throwFault(operation, errors);
|
||||
}
|
||||
}
|
||||
|
||||
private releaseOrFault(operation: string): void {
|
||||
const errors = this.releaseGame();
|
||||
if (errors.length > 0) this.throwFault(operation, errors);
|
||||
}
|
||||
|
||||
private gameWasDisposed(): boolean {
|
||||
return this.options.gameSession.state === 'disposed';
|
||||
}
|
||||
|
||||
private releaseGame(): unknown[] {
|
||||
this.lastJoinedRoomcode = null;
|
||||
const errors: unknown[] = [];
|
||||
const lease = this.activeLease;
|
||||
this.activeLease = null;
|
||||
|
||||
if (this.options.gameSession.state !== 'idle' && !this.gameWasDisposed()) {
|
||||
try {
|
||||
this.options.gameSession.close();
|
||||
} catch (error) {
|
||||
errors.push(error);
|
||||
}
|
||||
}
|
||||
if (lease !== null) {
|
||||
try {
|
||||
lease.invalidate();
|
||||
} catch (error) {
|
||||
errors.push(error);
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
private throwFault(operation: string, errors: readonly unknown[]): never {
|
||||
const fault = new RuntimeSessionFault(operation, errors);
|
||||
this.loginIsPending = false;
|
||||
this.terminalFault = fault;
|
||||
throw fault;
|
||||
}
|
||||
|
||||
private requireOperational(): void {
|
||||
if (this.terminalFault !== null) throw this.terminalFault;
|
||||
if (this.wasKicked) throw new Error('RuntimeSession is kicked');
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,16 +1,16 @@
|
||||
import type { ServerDenial } from './runtime-session.ts';
|
||||
|
||||
/** Semantic scene intents; the headless runtime never imports Cocos. */
|
||||
export interface ScenePort {
|
||||
showLoading(): void;
|
||||
showLogin(): void;
|
||||
showLobby(): void;
|
||||
showRoom(): void;
|
||||
showReconnect(): void;
|
||||
showKicked(data: unknown): void;
|
||||
/** Required when the host supports the server's generic message UI. */
|
||||
showTips?(message: string, time: number, source?:'show_message'|'update_roomcard'): void;
|
||||
/** Recoverable server result; the consumer interprets the original failure payload. */
|
||||
showServerDenial(outcome: ServerDenial): void;
|
||||
showFatal(error: Error): void;
|
||||
}
|
||||
import type { ServerDenial } from './runtime-session.ts';
|
||||
|
||||
/** Semantic scene intents; the headless runtime never imports Cocos. */
|
||||
export interface ScenePort {
|
||||
showLoading(): void;
|
||||
showLogin(): void;
|
||||
showLobby(): void;
|
||||
showRoom(): void;
|
||||
showReconnect(): void;
|
||||
showKicked(data: unknown): void;
|
||||
/** Required when the host supports the server's generic message UI. */
|
||||
showTips?(message: string, time: number, source?:'show_message'|'update_roomcard'|'update_bean'|'binding_invitecode'): void;
|
||||
/** Recoverable server result; the consumer interprets the original failure payload. */
|
||||
showServerDenial(outcome: ServerDenial): void;
|
||||
showFatal(error: Error): void;
|
||||
}
|
||||
|
||||
+919
-836
File diff suppressed because it is too large
Load Diff
+66
-59
@@ -1,61 +1,68 @@
|
||||
import type { PlatformGameConnectionPhase } from '../../sdk/contracts/snapshots.ts';
|
||||
import type { TaskData } from '../../core/types/task.ts';
|
||||
|
||||
export type PlatformConnectionPhase =
|
||||
| 'idle'
|
||||
| 'connecting'
|
||||
| PlatformGameConnectionPhase;
|
||||
|
||||
export interface PlatformAppState {
|
||||
readonly phase: PlatformConnectionPhase;
|
||||
}
|
||||
|
||||
export interface PlatformPlayer {
|
||||
readonly playerid: number;
|
||||
readonly nickname: string;
|
||||
readonly avatar: string;
|
||||
readonly sex: number;
|
||||
readonly bean: number;
|
||||
/** Account-only fields are absent on other room players; never default them in views. */
|
||||
import type { PlatformGameConnectionPhase } from '../../sdk/contracts/snapshots.ts';
|
||||
import type { TaskData } from '../../core/types/task.ts';
|
||||
|
||||
export type PlatformConnectionPhase =
|
||||
| 'idle'
|
||||
| 'connecting'
|
||||
| PlatformGameConnectionPhase;
|
||||
|
||||
export interface PlatformAppState {
|
||||
readonly phase: PlatformConnectionPhase;
|
||||
}
|
||||
|
||||
export interface PlatformPlayer {
|
||||
readonly playerid: number;
|
||||
readonly nickname: string;
|
||||
readonly avatar: string;
|
||||
readonly sex: number;
|
||||
readonly bean: number;
|
||||
/** Account-only fields are absent on other room players; never default them in views. */
|
||||
readonly roomcard?: number;
|
||||
readonly taskstate?: number;
|
||||
readonly advanced?: number;
|
||||
readonly score?: number;
|
||||
readonly sign?: string | null;
|
||||
readonly ip?: string;
|
||||
readonly isprepare?: number;
|
||||
readonly onstate?: number;
|
||||
}
|
||||
|
||||
export interface PlayerDirectoryState {
|
||||
readonly selfPlayerId: number | null;
|
||||
readonly entities: Readonly<Record<number, PlatformPlayer>>;
|
||||
}
|
||||
|
||||
export interface OutsideRoomState {
|
||||
readonly kind: 'outside';
|
||||
}
|
||||
|
||||
export interface InsideRoomState {
|
||||
readonly kind: 'inside';
|
||||
readonly roomcode: string;
|
||||
readonly selfSeat: number;
|
||||
readonly seatPlayerIds: readonly (number | null)[];
|
||||
readonly readySeats: readonly number[];
|
||||
readonly offlineSeats: readonly number[];
|
||||
readonly roomtype: readonly unknown[];
|
||||
readonly stage: number;
|
||||
readonly needprepare: number;
|
||||
readonly infinite: number;
|
||||
}
|
||||
|
||||
export type RoomState = OutsideRoomState | InsideRoomState;
|
||||
|
||||
readonly openid?: string;
|
||||
readonly unionid?: string | number;
|
||||
readonly invitecode?: string | null;
|
||||
readonly taskstate?: number;
|
||||
readonly advanced?: number;
|
||||
readonly score?: number;
|
||||
readonly sign?: string | null;
|
||||
readonly ip?: string;
|
||||
readonly isprepare?: number;
|
||||
readonly onstate?: number;
|
||||
}
|
||||
|
||||
export interface PlayerDirectoryState {
|
||||
readonly selfPlayerId: number | null;
|
||||
readonly entities: Readonly<Record<number, PlatformPlayer>>;
|
||||
}
|
||||
|
||||
export interface OutsideRoomState {
|
||||
readonly kind: 'outside';
|
||||
}
|
||||
|
||||
export interface InsideRoomState {
|
||||
readonly kind: 'inside';
|
||||
readonly roomcode: string;
|
||||
readonly selfSeat: number;
|
||||
readonly seatPlayerIds: readonly (number | null)[];
|
||||
readonly readySeats: readonly number[];
|
||||
readonly offlineSeats: readonly number[];
|
||||
readonly roomtype: readonly unknown[];
|
||||
readonly stage: number;
|
||||
readonly needprepare: number;
|
||||
readonly infinite: number;
|
||||
}
|
||||
|
||||
export type RoomState = OutsideRoomState | InsideRoomState;
|
||||
|
||||
export interface PlatformState {
|
||||
readonly vipRooms?: readonly import('../../core/types/vip-room.ts').VipRoom[];
|
||||
/** Absent until a task list response arrives; cleared by login replacement. */
|
||||
readonly tasks?: readonly TaskData[];
|
||||
readonly app: PlatformAppState;
|
||||
readonly players: PlayerDirectoryState;
|
||||
readonly room: RoomState;
|
||||
}
|
||||
readonly vipRank?:{readonly entries:readonly import('../../protocol/contracts/rank-list.ts').RankEntry[]|null};
|
||||
readonly treasureList?:readonly import('../../protocol/contracts/rank-list.ts').RankEntry[];
|
||||
/** Receipt identity allows repeated queries with an unchanged code to open the view. */
|
||||
readonly inviteResponse?: {readonly kind:'query'|'binding'};
|
||||
readonly vipRooms?: readonly import('../../core/types/vip-room.ts').VipRoom[];
|
||||
/** Absent until a task list response arrives; cleared by login replacement. */
|
||||
readonly tasks?: readonly TaskData[];
|
||||
readonly app: PlatformAppState;
|
||||
readonly players: PlayerDirectoryState;
|
||||
readonly room: RoomState;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/** Game_Surface_3 Logic.InputRoomCode/pasteRoomCode and GameUI Spirit602. */
|
||||
export class JoinRoomInput {
|
||||
private value='';
|
||||
private busy=false;
|
||||
constructor(private readonly submit:(code:number)=>void){}
|
||||
get code():string{return this.value;}
|
||||
setBusy(value:boolean):void{this.busy=value;}
|
||||
prefill(value:string):void{
|
||||
if(!/^\d{0,6}$/.test(value))throw Error('Invalid saved room shortcode');
|
||||
this.value=value;
|
||||
}
|
||||
digit(value:number):void{
|
||||
if(!Number.isInteger(value)||value<0||value>9)throw Error('Invalid room digit');
|
||||
if(this.busy)return;
|
||||
if(this.value.length<6)this.value+=value;
|
||||
if(this.value.length===6)this.submit(Number(this.value));
|
||||
}
|
||||
remove():void{if(!this.busy)this.value=this.value.slice(0,-1);}
|
||||
clear():void{if(!this.busy)this.value='';}
|
||||
paste(value:string):void{
|
||||
if(this.busy)return;this.value=value.replace(/[^0-9]/g,'').slice(0,6);
|
||||
if(this.value.length===6)this.submit(Number(this.value));
|
||||
}
|
||||
confirm():void{if(!this.busy&&Number(this.value)>0)this.submit(Number(this.value));}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "9301c0f4-8779-44f8-8d19-5f540b499e82",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
+2
File diff suppressed because one or more lines are too long
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "a834856f-7a53-445b-b517-6588ad346e59",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -1,121 +1,131 @@
|
||||
import type { RemoteConfig } from '../config/remote-config.ts';
|
||||
|
||||
export interface LobbyTemplate {
|
||||
isGameHall:boolean; mainMenuButton:boolean; hideNotice:boolean; vipInfinite:boolean;
|
||||
}
|
||||
/** Template-owned defaults: 02_Const.isGameHall and 00_SubGame_Config.sysConfig. */
|
||||
export const LOBBY_TEMPLATE:Readonly<LobbyTemplate>=Object.freeze({
|
||||
isGameHall:false,mainMenuButton:false,hideNotice:false,vipInfinite:false,
|
||||
});
|
||||
export interface LobbyContext {versionState:0|1;launchMode:0|1;}
|
||||
const HALL_DEFAULTS=Object.freeze({androidPay:0,rankList:0 as number|string,storeUrl:'',bagUrl:'',actData:0 as unknown,worldRoomList:0 as number|string,wareHouse:0 as number|string});
|
||||
const SYS_DEFAULTS=Object.freeze({hideTaskBtn:0 as number|string,hideWxShare:0 as number|string,miniPro:0 as number|string,hideRoomCard:1,cdKey:1,
|
||||
ofcShortCode:null as unknown,hideSpid:null as unknown});
|
||||
function object(value:unknown,name:string):Record<string,unknown>{
|
||||
if(value===null||typeof value!=='object'||Array.isArray(value))throw Error(`Invalid ${name}: expected object`);
|
||||
return value as Record<string,unknown>;
|
||||
}
|
||||
/** This is the sole menu config source. Defaults reproduce 04_Data, then 12_Logic.getConfig_Succ merges. */
|
||||
export function resolveLobbyMenuConfig(remote:RemoteConfig,template:Readonly<LobbyTemplate>=LOBBY_TEMPLATE){
|
||||
const rawHall=remote.getValue('hall_config');
|
||||
const merged=rawHall==null?{}:object(rawHall,'hall_config');
|
||||
const hall={...HALL_DEFAULTS,...merged};
|
||||
for(const key of Object.keys(HALL_DEFAULTS) as (keyof typeof HALL_DEFAULTS)[]){
|
||||
// 04_Data.hallConfigName contains 'actData:' literally; top-level actData never overrides hall_config.actData.
|
||||
if(key==='actData')continue;
|
||||
const value=remote.getValue(key);if(value!=null)Object.assign(hall,{[key]:value});
|
||||
}
|
||||
for(const key of ['androidPay','rankList','worldRoomList','wareHouse'] as const){
|
||||
if(!Number.isFinite(Number(hall[key])))throw Error(`Invalid ${key}`);
|
||||
if(key==='androidPay')hall[key]=Number(hall[key]);
|
||||
}
|
||||
for(const key of ['storeUrl','bagUrl'] as const)if(typeof hall[key]!=='string')throw Error(`Invalid ${key}`);
|
||||
const sys={...SYS_DEFAULTS};
|
||||
for(const key of Object.keys(SYS_DEFAULTS) as (keyof typeof SYS_DEFAULTS)[]){
|
||||
const value=remote.getValue(key);if(value!=null)Object.assign(sys,{[key]:value});
|
||||
}
|
||||
for(const key of ['hideTaskBtn','hideWxShare','miniPro','hideRoomCard','cdKey'] as const){
|
||||
if(!Number.isFinite(Number(sys[key])))throw Error(`Invalid ${key}`);
|
||||
if(key==='hideRoomCard'||key==='cdKey')sys[key]=Number(sys[key]);
|
||||
}
|
||||
if(sys.hideSpid!==null&&(!Array.isArray(sys.hideSpid)||sys.hideSpid.some(id=>!Number.isInteger(id))))throw Error('Invalid hideSpid');
|
||||
const rawAgent=remote.getValue('agentmode');
|
||||
const agentmode=rawAgent==null?1:Number(rawAgent);
|
||||
if(![1,2,3].includes(agentmode))throw Error('Invalid agentmode');
|
||||
return Object.freeze({agentmode,hall:Object.freeze(hall),sys:Object.freeze(sys),template});
|
||||
import type { RemoteConfig } from '../config/remote-config.ts';
|
||||
|
||||
export interface LobbyTemplate {
|
||||
isGameHall:boolean; mainMenuButton:boolean; hideNotice:boolean; vipInfinite:boolean;
|
||||
}
|
||||
/** Template-owned defaults: 02_Const.isGameHall and 00_SubGame_Config.sysConfig. */
|
||||
export const LOBBY_TEMPLATE:Readonly<LobbyTemplate>=Object.freeze({
|
||||
isGameHall:false,mainMenuButton:false,hideNotice:false,vipInfinite:false,
|
||||
});
|
||||
export interface LobbyContext {versionState:0|1;launchMode:0|1;}
|
||||
const HALL_DEFAULTS=Object.freeze({androidPay:0,rankList:0 as number|string,isReSetOwnerNote:0 as number|string,storeUrl:'',bagUrl:'',actData:0 as unknown,worldRoomList:0 as number|string,wareHouse:0 as number|string});
|
||||
const SYS_DEFAULTS=Object.freeze({vipRankLimit:10,hideTaskBtn:0 as number|string,hideWxShare:0 as number|string,miniPro:0 as number|string,hideRoomCard:1,cdKey:1,
|
||||
ofcShortCode:null as unknown,hideSpid:null as unknown});
|
||||
function object(value:unknown,name:string):Record<string,unknown>{
|
||||
if(value===null||typeof value!=='object'||Array.isArray(value))throw Error(`Invalid ${name}: expected object`);
|
||||
return value as Record<string,unknown>;
|
||||
}
|
||||
/** This is the sole menu config source. Defaults reproduce 04_Data, then 12_Logic.getConfig_Succ merges. */
|
||||
export function resolveLobbyMenuConfig(remote:RemoteConfig,template:Readonly<LobbyTemplate>=LOBBY_TEMPLATE){
|
||||
const rawHall=remote.getValue('hall_config');
|
||||
const merged=rawHall==null?{}:object(rawHall,'hall_config');
|
||||
const hall={...HALL_DEFAULTS,...merged};
|
||||
for(const key of Object.keys(HALL_DEFAULTS) as (keyof typeof HALL_DEFAULTS)[]){
|
||||
// 04_Data.hallConfigName contains 'actData:' literally; top-level actData never overrides hall_config.actData.
|
||||
if(key==='actData')continue;
|
||||
const value=remote.getValue(key);if(value!=null)Object.assign(hall,{[key]:value});
|
||||
}
|
||||
for(const key of ['androidPay','rankList','worldRoomList','wareHouse'] as const){
|
||||
if(!Number.isFinite(Number(hall[key])))throw Error(`Invalid ${key}`);
|
||||
if(key==='androidPay')hall[key]=Number(hall[key]);
|
||||
}
|
||||
for(const key of ['storeUrl','bagUrl'] as const)if(typeof hall[key]!=='string')throw Error(`Invalid ${key}`);
|
||||
const sys={...SYS_DEFAULTS};
|
||||
for(const key of Object.keys(SYS_DEFAULTS) as (keyof typeof SYS_DEFAULTS)[]){
|
||||
const value=remote.getValue(key);if(value!=null)Object.assign(sys,{[key]:value});
|
||||
}
|
||||
if(!Number.isInteger(Number(sys.vipRankLimit))||Number(sys.vipRankLimit)<=0)throw Error('Invalid vipRankLimit');
|
||||
sys.vipRankLimit=Number(sys.vipRankLimit);
|
||||
for(const key of ['hideTaskBtn','hideWxShare','miniPro','hideRoomCard','cdKey'] as const){
|
||||
if(!Number.isFinite(Number(sys[key])))throw Error(`Invalid ${key}`);
|
||||
if(key==='hideRoomCard'||key==='cdKey')sys[key]=Number(sys[key]);
|
||||
}
|
||||
if(sys.hideSpid!==null&&(!Array.isArray(sys.hideSpid)||sys.hideSpid.some(id=>!Number.isInteger(id))))throw Error('Invalid hideSpid');
|
||||
const rawAgent=remote.getValue('agentmode');
|
||||
const agentmode=rawAgent==null?1:Number(rawAgent);
|
||||
if(![1,2,3].includes(agentmode))throw Error('Invalid agentmode');
|
||||
// Original GameUI draw cases 568/483/3139: missing vipConfig means unopened;
|
||||
// the earlier matchhtml condition is commented out in the source.
|
||||
const vipConfig=remote.getValue('vipConfig');
|
||||
const locked=Object.freeze({568:!hall.worldRoomList,483:!vipConfig,3139:!sys.ofcShortCode});
|
||||
return Object.freeze({agentmode,hall:Object.freeze(hall),sys:Object.freeze(sys),template,locked});
|
||||
}
|
||||
export type LobbyMenuConfig=ReturnType<typeof resolveLobbyMenuConfig>;
|
||||
export interface LobbyMenuState {visible:Record<number,boolean>;left:Record<number,number>;top:Record<number,number>;}
|
||||
|
||||
/** Ordered projection of JumpMenuScene. Unmentioned nodes retain their authored state. */
|
||||
export function lobbyMenuState(config:LobbyMenuConfig,context:LobbyContext):LobbyMenuState{
|
||||
const {hall,sys,template,agentmode}=config;
|
||||
const visible:Record<number,boolean>={},left:Record<number,number>={},top:Record<number,number>={};
|
||||
const hide=(ids:number[])=>{for(const id of ids)visible[id]=false;};
|
||||
// Source hideSpid moves nodes before JumpMenuScene; later smart-bar placement can override X.
|
||||
if(Array.isArray(sys.hideSpid))for(const id of sys.hideSpid){left[id]=1300;top[id]=730;}
|
||||
hide([219,220]);
|
||||
if(context.versionState===1){
|
||||
if(context.launchMode===0)hide([234]);
|
||||
hide([17,14,16,145,340,339,681,682,178,179,180,193,194,235,483,568,604,
|
||||
286,157,287,10,508,497,496,509,3233,3232,76,3139,679,99,3120,3161,3162,3016,3018,3017]);
|
||||
}else{
|
||||
if(agentmode===1)hide([10,509]);
|
||||
visible[235]=false;
|
||||
if(hall.androidPay===1){visible[10]=true;visible[509]=true;}
|
||||
visible[3018]=!template.isGameHall&&Array.isArray(hall.actData);
|
||||
visible[3016]=Boolean(hall.storeUrl);
|
||||
if(template.isGameHall||template.vipInfinite)hide([3139,679]);
|
||||
if(template.mainMenuButton)hide([11,12,568,483]);
|
||||
if(template.hideNotice)hide([16,180,193,194,682,178,179,145]);
|
||||
if(sys.hideTaskBtn)hide([17]);
|
||||
if(sys.miniPro||sys.hideWxShare)hide([14]);
|
||||
if(!template.isGameHall){
|
||||
const shown=[3120,14,17,13,7].filter(id=>visible[id]!==false);
|
||||
shown.forEach((id,index)=>{left[id]=shown.length===1?558:shown.length===2?[376,740][index]:194+728*index/(shown.length-1);});
|
||||
}
|
||||
visible[3233]=Boolean(hall.rankList);
|
||||
visible[3256]=false; // Both original deviceLogin branches hide this entry.
|
||||
if(template.isGameHall){
|
||||
hide([14,13,17,3120,99,3016,76,234,3057,145,3139,3232,3233,604]);
|
||||
visible[235]=agentmode!==1;visible[15]=true;visible[5]=true;
|
||||
const shown=[235,15,5,7].filter(id=>visible[id]!==false);
|
||||
shown.forEach((id,index)=>{left[id]=300+520*index/(shown.length-1);top[id]=642;});
|
||||
}
|
||||
}
|
||||
if(sys.hideRoomCard===2)hide([286,10,287,3161]);
|
||||
visible[3264]=sys.cdKey===2;
|
||||
return {visible,left,top};
|
||||
/** Original click messages intentionally use different comma characters. */
|
||||
export function lobbyLockedTip(config:LobbyMenuConfig,action:'world-rooms'|'match'|'official-rooms'):string|null{
|
||||
if(action==='world-rooms')return config.locked[568]?'暂未开放,敬请期待':null;
|
||||
return config.locked[action==='match'?483:3139]?'暂未开放,敬请期待':null;
|
||||
}
|
||||
|
||||
/** openPallet runs its own visibility pass each time before placing visible entries. */
|
||||
export function lobbyTrayState(config:LobbyMenuConfig,context:LobbyContext):LobbyMenuState{
|
||||
const visible:Record<number,boolean>={15:false,604:Boolean(config.hall.rankList),
|
||||
235:config.agentmode!==1&&!config.sys.miniPro,3017:Boolean(config.hall.bagUrl),3057:Boolean(config.hall.wareHouse),5:true};
|
||||
const left:Record<number,number>={3121:200},top:Record<number,number>={};
|
||||
if(context.versionState===1)return {visible:Object.fromEntries(Object.keys(visible).map(id=>[id,false])),left,top};
|
||||
const shown=[15,604,235,3017,3057,5].filter(id=>visible[id]);
|
||||
shown.forEach((id,index)=>{left[id]=216+155*index;top[id]=540;});
|
||||
return {visible,left,top};
|
||||
}
|
||||
|
||||
/** IDs remain source traceability only; Cocos owns hit testing and prefab lifetimes. */
|
||||
export const LOBBY_MENU_ACTIONS=Object.freeze({
|
||||
7:'settings',9:'profile',11:'create-room',12:'join-room',13:'records',14:'share',17:'tasks',679:'vip-rooms',
|
||||
10:'buy-roomcard',286:'buy-roomcard',287:'buy-roomcard',3161:'buy-roomcard',
|
||||
508:'buy-bean',509:'buy-bean',496:'buy-bean',3162:'buy-bean',
|
||||
483:'match',568:'world-rooms',3016:'store',3018:'activity',99:'my-rooms',76:'qrcode',
|
||||
3139:'official-rooms',234:'more-games',3120:'tray',3233:'rank',3232:'vip-rank',3256:'bind-phone',3264:'cdkey',
|
||||
5:'notice',3017:'bag',235:'agent',604:'rank',3057:'warehouse',15:'feedback',
|
||||
} as const);
|
||||
export type LobbyAction=typeof LOBBY_MENU_ACTIONS[keyof typeof LOBBY_MENU_ACTIONS];
|
||||
export const LOBBY_UNAVAILABLE:Readonly<Partial<Record<LobbyAction,string>>>=Object.freeze({
|
||||
'create-room':'创建房间:尚未接入目标子游戏规则','records':'战绩:尚未接入子游戏战绩模块',
|
||||
'buy-roomcard':'购买房卡:尚未接入支付模块','buy-bean':'购买星星:尚未接入支付模块',
|
||||
match:'比赛:尚未接入比赛模块','world-rooms':'星星场:尚未接入房间列表',store:'商城:尚未接入商城身份参数与原生接口',
|
||||
activity:'活动:尚未接入活动身份参数与原生接口','my-rooms':'我的房间:尚未接入房间记录与进房',qrcode:'二维码:尚未接入分享模块',
|
||||
'official-rooms':'官方试玩:尚未接入微房号列表','more-games':'更多游戏:尚未接入返回大厅与下载入口',
|
||||
rank:'排行:尚未接入排行榜', 'vip-rank':'VIP 排行:尚未接入排行榜','bind-phone':'绑定手机:尚未接入绑定流程',
|
||||
cdkey:'兑换码:尚未接入兑换请求',bag:'背包:尚未接入背包身份参数与原生接口',agent:'代理:尚未接入代理入口',
|
||||
warehouse:'保险箱:尚未接入仓库模块',feedback:'反馈:尚未接入意见提交',
|
||||
});
|
||||
export interface LobbyMenuState {visible:Record<number,boolean>;left:Record<number,number>;top:Record<number,number>;}
|
||||
|
||||
/** Ordered projection of JumpMenuScene. Unmentioned nodes retain their authored state. */
|
||||
export function lobbyMenuState(config:LobbyMenuConfig,context:LobbyContext):LobbyMenuState{
|
||||
const {hall,sys,template,agentmode}=config;
|
||||
const visible:Record<number,boolean>={},left:Record<number,number>={},top:Record<number,number>={};
|
||||
const hide=(ids:number[])=>{for(const id of ids)visible[id]=false;};
|
||||
// Source hideSpid moves nodes before JumpMenuScene; later smart-bar placement can override X.
|
||||
if(Array.isArray(sys.hideSpid))for(const id of sys.hideSpid){left[id]=1300;top[id]=730;}
|
||||
hide([219,220]);
|
||||
if(context.versionState===1){
|
||||
if(context.launchMode===0)hide([234]);
|
||||
hide([17,14,16,145,340,339,681,682,178,179,180,193,194,235,483,568,604,
|
||||
286,157,287,10,508,497,496,509,3233,3232,76,3139,679,99,3120,3161,3162,3016,3018,3017]);
|
||||
}else{
|
||||
if(agentmode===1)hide([10,509]);
|
||||
visible[235]=false;
|
||||
if(hall.androidPay===1){visible[10]=true;visible[509]=true;}
|
||||
visible[3018]=!template.isGameHall&&Array.isArray(hall.actData);
|
||||
visible[3016]=Boolean(hall.storeUrl);
|
||||
if(template.isGameHall||template.vipInfinite)hide([3139,679]);
|
||||
if(template.mainMenuButton)hide([11,12,568,483]);
|
||||
if(template.hideNotice)hide([16,180,193,194,682,178,179,145]);
|
||||
if(sys.hideTaskBtn)hide([17]);
|
||||
if(sys.miniPro||sys.hideWxShare)hide([14]);
|
||||
if(!template.isGameHall){
|
||||
const shown=[3120,14,17,13,7].filter(id=>visible[id]!==false);
|
||||
shown.forEach((id,index)=>{left[id]=shown.length===1?558:shown.length===2?[376,740][index]:194+728*index/(shown.length-1);});
|
||||
}
|
||||
visible[3233]=Boolean(hall.rankList);
|
||||
visible[3256]=false; // Both original deviceLogin branches hide this entry.
|
||||
if(template.isGameHall){
|
||||
hide([14,13,17,3120,99,3016,76,234,3057,145,3139,3232,3233,604]);
|
||||
visible[235]=agentmode!==1;visible[15]=true;visible[5]=true;
|
||||
const shown=[235,15,5,7].filter(id=>visible[id]!==false);
|
||||
shown.forEach((id,index)=>{left[id]=300+520*index/(shown.length-1);top[id]=642;});
|
||||
}
|
||||
}
|
||||
if(sys.hideRoomCard===2)hide([286,10,287,3161]);
|
||||
visible[3264]=sys.cdKey===2;
|
||||
return {visible,left,top};
|
||||
}
|
||||
|
||||
/** openPallet runs its own visibility pass each time before placing visible entries. */
|
||||
export function lobbyTrayState(config:LobbyMenuConfig,context:LobbyContext):LobbyMenuState{
|
||||
const visible:Record<number,boolean>={15:false,604:Boolean(config.hall.rankList),
|
||||
235:config.agentmode!==1&&!config.sys.miniPro,3017:Boolean(config.hall.bagUrl),3057:Boolean(config.hall.wareHouse),5:true};
|
||||
const left:Record<number,number>={3121:200},top:Record<number,number>={};
|
||||
if(context.versionState===1)return {visible:Object.fromEntries(Object.keys(visible).map(id=>[id,false])),left,top};
|
||||
const shown=[15,604,235,3017,3057,5].filter(id=>visible[id]);
|
||||
shown.forEach((id,index)=>{left[id]=216+155*index;top[id]=540;});
|
||||
return {visible,left,top};
|
||||
}
|
||||
|
||||
/** IDs remain source traceability only; Cocos owns hit testing and prefab lifetimes. */
|
||||
export const LOBBY_MENU_ACTIONS=Object.freeze({
|
||||
7:'settings',9:'profile',11:'create-room',12:'join-room',13:'records',14:'share',17:'tasks',679:'vip-rooms',
|
||||
10:'buy-roomcard',286:'buy-roomcard',287:'buy-roomcard',3161:'buy-roomcard',
|
||||
508:'buy-bean',509:'buy-bean',496:'buy-bean',3162:'buy-bean',
|
||||
483:'match',568:'world-rooms',3016:'store',3018:'activity',99:'my-rooms',76:'qrcode',
|
||||
3139:'official-rooms',234:'more-games',3120:'tray',3233:'rank',3232:'vip-rank',3256:'bind-phone',3264:'cdkey',
|
||||
5:'notice',3017:'bag',235:'agent',604:'rank',3057:'warehouse',15:'feedback',
|
||||
} as const);
|
||||
export type LobbyAction=typeof LOBBY_MENU_ACTIONS[keyof typeof LOBBY_MENU_ACTIONS];
|
||||
export const LOBBY_UNAVAILABLE:Readonly<Partial<Record<LobbyAction,string>>>=Object.freeze({
|
||||
'create-room':'创建房间:尚未接入目标子游戏规则','records':'战绩:尚未接入子游戏战绩模块',
|
||||
match:'比赛:尚未接入比赛模块','world-rooms':'星星场:尚未接入房间列表',store:'商城:尚未接入商城身份参数与原生接口',
|
||||
activity:'活动:尚未接入活动身份参数与原生接口',
|
||||
'official-rooms':'官方试玩:尚未接入微房号列表','more-games':'更多游戏:尚未接入返回大厅与下载入口',
|
||||
'bind-phone':'绑定手机:尚未接入绑定流程',
|
||||
bag:'背包:尚未接入背包身份参数与原生接口',agent:'代理:尚未接入代理入口',
|
||||
warehouse:'保险箱:尚未接入仓库模块',feedback:'反馈:尚未接入意见提交',
|
||||
});
|
||||
|
||||
@@ -1,66 +1,74 @@
|
||||
import type { RemoteConfig } from '../config/remote-config.ts';
|
||||
import type { PlatformState } from '../platform/stores/platform-types.ts';
|
||||
import { selectSelfPlayer } from '../platform/stores/selectors.ts';
|
||||
// Presentation checks required view inputs without depending on the protocol layer.
|
||||
function requireNumber(value:unknown,path:string):number {if(typeof value!=='number'||!Number.isFinite(value))throw Error(`${path}: expected number`);return value;}
|
||||
function requireInteger(value:unknown,path:string):number {const result=requireNumber(value,path);if(!Number.isInteger(result))throw Error(`${path}: expected integer`);return result;}
|
||||
function requireString(value:unknown,path:string):string {if(typeof value!=='string')throw Error(`${path}: expected string`);return value;}
|
||||
function requireRecord(value:unknown,path:string):Record<string,unknown> {if(!value||typeof value!=='object'||Array.isArray(value))throw Error(`${path}: expected object`);return value as Record<string,unknown>;}
|
||||
|
||||
export function selectLobbyModel(state:PlatformState) {
|
||||
const self=selectSelfPlayer(state);
|
||||
if(!self)throw Error('lobby requires authenticated player');
|
||||
return Object.freeze({...self,
|
||||
roomcard:requireNumber(self.roomcard,'players.self.roomcard'),
|
||||
taskstate:requireInteger(self.taskstate,'players.self.taskstate'),
|
||||
score:requireNumber(self.score,'players.self.score'),
|
||||
sign:self.sign===null?'这个家伙很懒~~':requireString(self.sign,'players.self.sign') || '这个家伙很懒~~',
|
||||
canAct:state.app.phase==='logged-in'&&state.room.kind==='outside',
|
||||
});
|
||||
}
|
||||
import type { RemoteConfig } from '../config/remote-config.ts';
|
||||
import type { PlatformState } from '../platform/stores/platform-types.ts';
|
||||
import { selectSelfPlayer } from '../platform/stores/selectors.ts';
|
||||
// Presentation checks required view inputs without depending on the protocol layer.
|
||||
function requireNumber(value:unknown,path:string):number {if(typeof value!=='number'||!Number.isFinite(value))throw Error(`${path}: expected number`);return value;}
|
||||
function requireInteger(value:unknown,path:string):number {const result=requireNumber(value,path);if(!Number.isInteger(result))throw Error(`${path}: expected integer`);return result;}
|
||||
function requireString(value:unknown,path:string):string {if(typeof value!=='string')throw Error(`${path}: expected string`);return value;}
|
||||
function requireRecord(value:unknown,path:string):Record<string,unknown> {if(!value||typeof value!=='object'||Array.isArray(value))throw Error(`${path}: expected object`);return value as Record<string,unknown>;}
|
||||
|
||||
export function selectLobbyModel(state:PlatformState) {
|
||||
const self=selectSelfPlayer(state);
|
||||
if(!self)throw Error('lobby requires authenticated player');
|
||||
return Object.freeze({...self,
|
||||
roomcard:requireNumber(self.roomcard,'players.self.roomcard'),
|
||||
taskstate:requireInteger(self.taskstate,'players.self.taskstate'),
|
||||
score:requireNumber(self.score,'players.self.score'),
|
||||
sign:self.sign===null?'这个家伙很懒~~':requireString(self.sign,'players.self.sign') || '这个家伙很懒~~',
|
||||
canAct:state.app.phase==='logged-in'&&state.room.kind==='outside',
|
||||
});
|
||||
}
|
||||
export type LobbyModel=ReturnType<typeof selectLobbyModel>;
|
||||
export interface Notice {type:0|1; text:string; image:string; url:string; height:number;}
|
||||
export interface LobbyPolicy {noticeHours:number; notices:readonly Notice[]; scroll:string;
|
||||
starName:string; roomCardName:string; contacts:readonly {label:string;value:string}[];}
|
||||
|
||||
/** Source defaults: 04_Data.sysConfig and advertInfo_0/1/2; 12_Logic.get_paravalue. */
|
||||
export function resolveLobbyPolicy(remote:RemoteConfig):LobbyPolicy {
|
||||
// 04_Data starts with 房卡; getConfig_Succ replaces only a truthy roomCardName.
|
||||
const cardName=remote.getValue('roomCardName');
|
||||
const roomCardName=cardName?requireString(cardName,'roomCardName'):'房卡';
|
||||
const interval=remote.getValue('gameNoticeShow');
|
||||
const noticeHours=interval==null?0:Number(interval);
|
||||
if(!Number.isFinite(noticeHours)||noticeHours< -1)throw Error('Invalid gameNoticeShow');
|
||||
const menuValue=remote.getValue('menunotice');
|
||||
const menu=menuValue==null?{}:requireRecord(menuValue,'menunotice');
|
||||
const notices:Notice[]=[];
|
||||
for(let index=0;index<3;index++){
|
||||
const value=menu[`advert${index}`];
|
||||
if(value==null){
|
||||
const message=index===0?remote.getValue('noticemsg'):null;
|
||||
notices.push({type:0,text:message==null?'':requireString(message,'noticemsg').replace(/#/g,'\n'),image:'',url:'',height:1});
|
||||
continue;
|
||||
}
|
||||
const data=requireRecord(value,`menunotice.advert${index}`);
|
||||
const type=requireInteger(data.type,`menunotice.advert${index}.type`);
|
||||
if(type!==0&&type!==1)throw Error(`menunotice.advert${index}.type must be 0 or 1`);
|
||||
// Empty text and URL explicitly mean no content/action in the source.
|
||||
const field=(key:string)=>data[key]==null?'':requireString(data[key],`menunotice.advert${index}.${key}`);
|
||||
notices.push({type,text:type===0?field('msg').replace(/#/g,'\n'):'',image:type===1?field('imgUrl'):'',
|
||||
url:field(type===0?'url':'imgAdUrl'),height:type===1?requireNumber(data.height,`menunotice.advert${index}.height`):1});
|
||||
}
|
||||
const scroll=remote.getValue('scrollmsg');
|
||||
const star=remote.getValue('starName');
|
||||
const contacts:Array<{label:string;value:string}>=[];
|
||||
for(const [key,title] of [['service_wechat','客服微信:'],['service_qq','投诉建议:'],['service_tel','客服手机:']]){
|
||||
const value=remote.getValue(key);
|
||||
if(value!=null&&value!=='')contacts.push({label:title,value:requireString(value,key)});
|
||||
}
|
||||
return Object.freeze({roomCardName,noticeHours,notices:Object.freeze(notices),scroll:scroll==null?'':requireString(scroll,'scrollmsg'),
|
||||
starName:star==null?'星星':requireString(star,'starName'),contacts:Object.freeze(contacts)});
|
||||
}
|
||||
/** Exact JudgeShow expiry: equality is not expired; reconnect never auto-opens. */
|
||||
export function shouldShowNotice(hours:number,previous:number|null,now:number,reconnect:boolean):boolean {
|
||||
if(reconnect||hours===-1)return false;
|
||||
return hours===0||!previous||previous+hours*3600000<now;
|
||||
/** Game_Config.Notice: speed 0.1 px/ms, 15 px per gblen byte. */
|
||||
export function announcementMotion(text:string,width:number,elapsedSeconds:number){
|
||||
if(!Number.isFinite(width)||width<=0)throw Error('Invalid announcement width');
|
||||
if(!Number.isFinite(elapsedSeconds)||elapsedSeconds<0)throw Error('Invalid announcement elapsed time');
|
||||
const textWidth=text.replace(/[^\x00-\xff]/g,'**').length*15;
|
||||
const duration=(width+textWidth)/100;
|
||||
return {visible:text.length>0,textWidth,duration,left:width-(elapsedSeconds%duration)*100};
|
||||
}
|
||||
export interface Notice {type:0|1; text:string; image:string; url:string; height:number;}
|
||||
export interface LobbyPolicy {noticeHours:number; notices:readonly Notice[]; scroll:string;
|
||||
starName:string; roomCardName:string; contacts:readonly {label:string;value:string}[];}
|
||||
|
||||
/** Source defaults: 04_Data.sysConfig and advertInfo_0/1/2; 12_Logic.get_paravalue. */
|
||||
export function resolveLobbyPolicy(remote:RemoteConfig):LobbyPolicy {
|
||||
// 04_Data starts with 房卡; getConfig_Succ replaces only a truthy roomCardName.
|
||||
const cardName=remote.getValue('roomCardName');
|
||||
const roomCardName=cardName?requireString(cardName,'roomCardName'):'房卡';
|
||||
const interval=remote.getValue('gameNoticeShow');
|
||||
const noticeHours=interval==null?0:Number(interval);
|
||||
if(!Number.isFinite(noticeHours)||noticeHours< -1)throw Error('Invalid gameNoticeShow');
|
||||
const menuValue=remote.getValue('menunotice');
|
||||
const menu=menuValue==null?{}:requireRecord(menuValue,'menunotice');
|
||||
const notices:Notice[]=[];
|
||||
for(let index=0;index<3;index++){
|
||||
const value=menu[`advert${index}`];
|
||||
if(value==null){
|
||||
const message=index===0?remote.getValue('noticemsg'):null;
|
||||
notices.push({type:0,text:message==null?'':requireString(message,'noticemsg').replace(/#/g,'\n'),image:'',url:'',height:1});
|
||||
continue;
|
||||
}
|
||||
const data=requireRecord(value,`menunotice.advert${index}`);
|
||||
const type=requireInteger(data.type,`menunotice.advert${index}.type`);
|
||||
if(type!==0&&type!==1)throw Error(`menunotice.advert${index}.type must be 0 or 1`);
|
||||
// Empty text and URL explicitly mean no content/action in the source.
|
||||
const field=(key:string)=>data[key]==null?'':requireString(data[key],`menunotice.advert${index}.${key}`);
|
||||
notices.push({type,text:type===0?field('msg').replace(/#/g,'\n'):'',image:type===1?field('imgUrl'):'',
|
||||
url:field(type===0?'url':'imgAdUrl'),height:type===1?requireNumber(data.height,`menunotice.advert${index}.height`):1});
|
||||
}
|
||||
const scroll=remote.getValue('scrollmsg');
|
||||
const star=remote.getValue('starName');
|
||||
const contacts:Array<{label:string;value:string}>=[];
|
||||
for(const [key,title] of [['service_wechat','客服微信:'],['service_qq','投诉建议:'],['service_tel','客服手机:']]){
|
||||
const value=remote.getValue(key);
|
||||
if(value!=null&&value!=='')contacts.push({label:title,value:requireString(value,key)});
|
||||
}
|
||||
return Object.freeze({roomCardName,noticeHours,notices:Object.freeze(notices),scroll:scroll==null?'':requireString(scroll,'scrollmsg'),
|
||||
starName:star==null?'星星':requireString(star,'starName'),contacts:Object.freeze(contacts)});
|
||||
}
|
||||
/** Exact JudgeShow expiry: equality is not expired; reconnect never auto-opens. */
|
||||
export function shouldShowNotice(hours:number,previous:number|null,now:number,reconnect:boolean):boolean {
|
||||
if(reconnect||hours===-1)return false;
|
||||
return hours===0||!previous||previous+hours*3600000<now;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import {LEGACY_AUTH_DISTRICTS} from './legacy-auth-districts.ts';
|
||||
|
||||
/** Game_Surface_3 05_Func.IDValidator + 11_GameUI cases 484/493.
|
||||
* Local format validation only. Deliberately retain the source's 15-digit,
|
||||
* parent-district and month/day range semantics; this is not server identity proof.
|
||||
*/
|
||||
export function validateProfileAuth(name:string,id:string):'name'|'id'|null {
|
||||
if(name.length<=1)return 'name';
|
||||
const code=id.toUpperCase();
|
||||
if(code.length!==15&&code.length!==18)return 'id';
|
||||
const body=code.length===18?code.slice(0,17):code;
|
||||
const address=body.slice(0,6);
|
||||
const hasAddress=(key:string)=>Object.prototype.hasOwnProperty.call(LEGACY_AUTH_DISTRICTS,key);
|
||||
if(!hasAddress(address)&&!hasAddress(address.slice(0,4)+'00')&&!hasAddress(address.slice(0,2)+'0000'))return 'id';
|
||||
const birth=code.length===18?body.slice(6,14):body.slice(6,12);
|
||||
const month=parseInt(birth.slice(-4,-2),10),day=parseInt(birth.slice(-2),10);
|
||||
if(month>12||month===0||day>31||day===0)return 'id';
|
||||
if(code.length===15)return null;
|
||||
let sum=0;
|
||||
for(let index=0;index<body.length;index++)sum+=parseInt(body[index],10)*(Math.pow(2,17-index)%11);
|
||||
const value=12-sum%11;
|
||||
const check=value===10?'X':String(value>10?value%11:value);
|
||||
return check===code.slice(-1)?null:'id';
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "11f5830e-5f4b-46fb-9e06-b2b65edbed89",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
export type LoadingRpc='get_player_task'|'get_task_award'|'getVipRankList';
|
||||
/** Wire receipt, rather than a successful model update, completes request loading. */
|
||||
export class RequestLoading {
|
||||
private readonly pending=new Set<LoadingRpc>();
|
||||
constructor(private readonly show:(visible:boolean)=>void){}
|
||||
begin(rpc:LoadingRpc):void{const empty=this.pending.size===0;this.pending.add(rpc);if(empty)this.show(true);}
|
||||
cancel(rpc:LoadingRpc):void{if(this.pending.delete(rpc)&&this.pending.size===0)this.show(false);}
|
||||
received(message:{route:string;rpc:string;data?:unknown}):void{
|
||||
if(message.route!=='agent')return;
|
||||
if(message.rpc==='show_message'){this.clear();return;}
|
||||
if(message.rpc==='get_player_task'||message.rpc==='get_task_award'||message.rpc==='getVipRankList')this.cancel(message.rpc);
|
||||
}
|
||||
clear():void{if(this.pending.size){this.pending.clear();this.show(false);}}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "8bb6a294-bfcf-4126-98f1-0845b58c72db",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -1,19 +1,27 @@
|
||||
import type { RuntimeConfig } from '../config/runtime-config.ts';
|
||||
/** Source: 12_Logic.getConfig_Succ then 05_Func.sharefriend. */
|
||||
export function resolveSharePolicy(config:RuntimeConfig){
|
||||
const remote=config.remoteConfig,raw=remote.getValue('hall_config');
|
||||
if(raw!=null&&(typeof raw!=='object'||Array.isArray(raw)))throw Error('Invalid hall_config');
|
||||
const hall={shareDesc:'',shareBgUrl:'',shareImgUrl:'',shareType:0,...raw as object} as Record<string,unknown>;
|
||||
for(const key of Object.keys(hall)){const value=remote.getValue(key);if(value!=null)hall[key]=value;}
|
||||
const text=(value:unknown,name:string)=>{if(typeof value!=='string')throw Error(`Invalid ${name}`);return value;};
|
||||
const download=remote.getValue('url_download'),name=remote.getValue('sharename'),id=config.identity;
|
||||
return {url:download?`${text(download,'url_download')}?agentid=${id.agentid}&channelid=${id.channelid}&gameid=${id.gameid}&marketid=${id.marketid}`:'',
|
||||
title:name?text(name,'sharename'):'',description:hall.shareDesc?text(hall.shareDesc,'shareDesc'):'hello world',
|
||||
background:text(hall.shareBgUrl,'shareBgUrl'),image:text(hall.shareImgUrl,'shareImgUrl'),imageTimeline:!!hall.shareType};
|
||||
}
|
||||
export function sharePayload(policy:ReturnType<typeof resolveSharePolicy>,friend:1|2){
|
||||
if(friend===2&&!policy.imageTimeline)return null; // Legacy case 75 has no link-sharing call.
|
||||
return {sharefriend:friend,type:friend===1?1:3,webpageUrl:friend===1?policy.url:policy.image||policy.url,
|
||||
title:friend===1?policy.title||policy.title+policy.description:policy.title+policy.description,
|
||||
description:friend===1?policy.description:undefined,sharetype:0};
|
||||
/** Open_ewm uses this exact config key; absent configuration intentionally does nothing. */
|
||||
export function resolveQrCodePolicy(config:RuntimeConfig){
|
||||
const url=config.remoteConfig.getValue('WechatPublicNo_ewm');
|
||||
if(!url)return null;
|
||||
if(typeof url!=='string')throw Error('Invalid WechatPublicNo_ewm');
|
||||
const policy=resolveSharePolicy(config);
|
||||
return {url,share:{sharefriend:1,type:3,webpageUrl:url,title:policy.title+policy.description,description:undefined,sharetype:0}};
|
||||
}
|
||||
/** Source: 12_Logic.getConfig_Succ then 05_Func.sharefriend. */
|
||||
export function resolveSharePolicy(config:RuntimeConfig){
|
||||
const remote=config.remoteConfig,raw=remote.getValue('hall_config');
|
||||
if(raw!=null&&(typeof raw!=='object'||Array.isArray(raw)))throw Error('Invalid hall_config');
|
||||
const hall={shareDesc:'',shareBgUrl:'',shareImgUrl:'',shareType:0,...raw as object} as Record<string,unknown>;
|
||||
for(const key of Object.keys(hall)){const value=remote.getValue(key);if(value!=null)hall[key]=value;}
|
||||
const text=(value:unknown,name:string)=>{if(typeof value!=='string')throw Error(`Invalid ${name}`);return value;};
|
||||
const download=remote.getValue('url_download'),name=remote.getValue('sharename'),id=config.identity;
|
||||
return {url:download?`${text(download,'url_download')}?agentid=${id.agentid}&channelid=${id.channelid}&gameid=${id.gameid}&marketid=${id.marketid}`:'',
|
||||
title:name?text(name,'sharename'):'',description:hall.shareDesc?text(hall.shareDesc,'shareDesc'):'hello world',
|
||||
background:text(hall.shareBgUrl,'shareBgUrl'),image:text(hall.shareImgUrl,'shareImgUrl'),imageTimeline:!!hall.shareType};
|
||||
}
|
||||
export function sharePayload(policy:ReturnType<typeof resolveSharePolicy>,friend:1|2){
|
||||
if(friend===2&&!policy.imageTimeline)return null; // Legacy case 75 has no link-sharing call.
|
||||
return {sharefriend:friend,type:friend===1?1:3,webpageUrl:friend===1?policy.url:policy.image||policy.url,
|
||||
title:friend===1?policy.title||policy.title+policy.description:policy.title+policy.description,
|
||||
description:friend===1?policy.description:undefined,sharetype:0};
|
||||
}
|
||||
|
||||
@@ -1,52 +1,56 @@
|
||||
export {
|
||||
hasOwn,
|
||||
optionalField,
|
||||
requireArray,
|
||||
requireBoolean,
|
||||
requireInteger,
|
||||
requireNumber,
|
||||
requireRecord,
|
||||
requireString,
|
||||
requireStringOrFiniteNumber,
|
||||
type JsonRecord,
|
||||
} from './validation.ts';
|
||||
export {
|
||||
buildLoginRequest,
|
||||
parseLoginResponse,
|
||||
type DeviceLoginMode,
|
||||
type FirstSliceRuntimeConfig,
|
||||
type LoginAccountIdentity,
|
||||
type LoginDeviceSnapshot,
|
||||
type LoginPlayerIdMode,
|
||||
type LoginRequestPayload,
|
||||
type ParsedLoginResponse,
|
||||
} from './login-contract.ts';
|
||||
export {
|
||||
buildExitRoomRequest,
|
||||
buildJoinRoomRequest,
|
||||
buildPrepareRequest,
|
||||
parseConnectAgentserverPayload,
|
||||
parseConnectRoomserverPayload,
|
||||
parseKickServerPayload,
|
||||
parseOtherExitRoomPayload,
|
||||
parseOtherJoinRoomPayload,
|
||||
parseOtherOfflinePayload,
|
||||
parseOtherOnlinePayload,
|
||||
parsePlayerPreparePayload,
|
||||
parseSelfExitRoomPayload,
|
||||
parseSelfJoinRoomResponse,
|
||||
type JoinRoomEnvironment,
|
||||
type JoinRoomPayload,
|
||||
type ParsedAgentserverPayload,
|
||||
type ParsedKickPayload,
|
||||
type ParsedOnlinePayload,
|
||||
type ParsedRoomEntry,
|
||||
type ParsedRoomResponse,
|
||||
type ParsedRoomserverPayload,
|
||||
type ParsedRoomSnapshot,
|
||||
type ParsedSelfExitPayload,
|
||||
type ParsedSeatPayload,
|
||||
type ReconnectSnapshot,
|
||||
type RoomActionPayload,
|
||||
type RoomIdentity,
|
||||
} from './room-contracts.ts';
|
||||
export {
|
||||
hasOwn,
|
||||
optionalField,
|
||||
requireArray,
|
||||
requireBoolean,
|
||||
requireInteger,
|
||||
requireNumber,
|
||||
requireRecord,
|
||||
requireString,
|
||||
requireStringOrFiniteNumber,
|
||||
type JsonRecord,
|
||||
} from './validation.ts';
|
||||
export {
|
||||
buildLoginRequest,
|
||||
parseLoginResponse,
|
||||
type DeviceLoginMode,
|
||||
type FirstSliceRuntimeConfig,
|
||||
type LoginAccountIdentity,
|
||||
type LoginDeviceSnapshot,
|
||||
type LoginPlayerIdMode,
|
||||
type LoginRequestPayload,
|
||||
type ParsedLoginResponse,
|
||||
} from './login-contract.ts';
|
||||
export {
|
||||
buildCreateRoomRequest,
|
||||
parseCreateRoomResponse,
|
||||
type ParsedCreateRoomResponse,
|
||||
type CreateRoomEnvironment,
|
||||
buildExitRoomRequest,
|
||||
buildJoinRoomRequest,
|
||||
buildPrepareRequest,
|
||||
parseConnectAgentserverPayload,
|
||||
parseConnectRoomserverPayload,
|
||||
parseKickServerPayload,
|
||||
parseOtherExitRoomPayload,
|
||||
parseOtherJoinRoomPayload,
|
||||
parseOtherOfflinePayload,
|
||||
parseOtherOnlinePayload,
|
||||
parsePlayerPreparePayload,
|
||||
parseSelfExitRoomPayload,
|
||||
parseSelfJoinRoomResponse,
|
||||
type JoinRoomEnvironment,
|
||||
type JoinRoomPayload,
|
||||
type ParsedAgentserverPayload,
|
||||
type ParsedKickPayload,
|
||||
type ParsedOnlinePayload,
|
||||
type ParsedRoomEntry,
|
||||
type ParsedRoomResponse,
|
||||
type ParsedRoomserverPayload,
|
||||
type ParsedRoomSnapshot,
|
||||
type ParsedSelfExitPayload,
|
||||
type ParsedSeatPayload,
|
||||
type ReconnectSnapshot,
|
||||
type RoomActionPayload,
|
||||
type RoomIdentity,
|
||||
} from './room-contracts.ts';
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import {requireArray,requireRecord,requireString,requireStringOrFiniteNumber} from './validation.ts';
|
||||
export interface RankEntry {readonly name:string;readonly headimg:string;readonly playerid:string|number;readonly star:string|number;readonly signature:string;readonly notice?:string;readonly destwo?:string;}
|
||||
/** Server rank entries are consumed as sent. Only rankList1 config replaces # line breaks. */
|
||||
export function parseRankList(value:unknown,fromConfig=false):readonly RankEntry[]{
|
||||
return Object.freeze(requireArray(value,'rank list').map((item,index)=>{
|
||||
const p=`rank list[${index}]`,r=requireRecord(item,p);
|
||||
const name=requireString(r.name,p+'.name'),signature=r.signature?requireString(r.signature,p+'.signature'):'';
|
||||
return Object.freeze({name:fromConfig?configRankName(name):name,headimg:requireString(r.headimg,p+'.headimg'),playerid:requireStringOrFiniteNumber(r.playerid,p+'.playerid'),
|
||||
star:requireStringOrFiniteNumber(r.star,p+'.star'),signature:fromConfig?signature.replace(/#/g,'\n'):signature});
|
||||
}));
|
||||
}
|
||||
|
||||
/** Func.subString(name,16,true): Latin-1 counts one, other UTF-16 units two. */
|
||||
function configRankName(name:string):string {
|
||||
let length=0;
|
||||
for(let i=0;i<name.length;i++){
|
||||
length+=name.charCodeAt(i)>255?2:1;
|
||||
if(length>16)return name.slice(0,i)+'...';
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
export function parseVipRankList(value:unknown):readonly RankEntry[]{
|
||||
const raw=requireArray(value,'VIP rank list'),entries=parseRankList(raw);
|
||||
return Object.freeze(entries.map((entry,i)=>{const item=requireRecord(raw[i],'VIP rank entry');
|
||||
return Object.freeze({...entry,notice:item.notice?requireString(item.notice,'notice'):'',destwo:item.destwo?requireString(item.destwo,'destwo'):''});}));
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "744452fb-2d7e-4a0e-9c49-2e6994f5df84",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
+302
-243
@@ -1,243 +1,302 @@
|
||||
import {
|
||||
buildFirstSliceEnvelope,
|
||||
type FirstSliceEnvelope,
|
||||
} from '../first-slice-routes.ts';
|
||||
import {
|
||||
hasOwn,
|
||||
optionalField,
|
||||
requireArray,
|
||||
requireInteger,
|
||||
requireNumber,
|
||||
requireRecord,
|
||||
requireString,
|
||||
requireStringOrFiniteNumber,
|
||||
type JsonRecord,
|
||||
} from './validation.ts';
|
||||
import type { FirstSliceRuntimeConfig } from './login-contract.ts';
|
||||
|
||||
export type ReconnectSnapshot =
|
||||
| { readonly present: false }
|
||||
| { readonly present: true; readonly value: unknown };
|
||||
|
||||
export interface ParsedRoomSnapshot {
|
||||
readonly roomcode: string;
|
||||
readonly seat: number;
|
||||
readonly roomtype: readonly unknown[];
|
||||
readonly players: readonly unknown[];
|
||||
}
|
||||
|
||||
export interface ParsedRoomEntry {
|
||||
readonly room: ParsedRoomSnapshot;
|
||||
readonly reconnect: ReconnectSnapshot;
|
||||
readonly raw: JsonRecord;
|
||||
}
|
||||
|
||||
export function reconnectFrom(raw: JsonRecord): ReconnectSnapshot {
|
||||
if (!Boolean(raw.deskinfo)) return { present: false };
|
||||
return { present: true, value: raw.deskinfo };
|
||||
}
|
||||
|
||||
export function parseRoomEntryFields(raw: JsonRecord): ParsedRoomSnapshot {
|
||||
const roomcode = requireString(raw.roomcode, '$.data.roomcode');
|
||||
const seat = requireInteger(raw.seat, '$.data.seat');
|
||||
const roomtype = requireArray(raw.roomtype, '$.data.roomtype');
|
||||
const players = requireArray(raw.players, '$.data.players');
|
||||
if (seat < 0 || seat >= players.length) {
|
||||
throw new RangeError(`$.data.seat: expected server seat index within players, received number`);
|
||||
}
|
||||
return { roomcode, seat, roomtype, players };
|
||||
}
|
||||
|
||||
export interface RoomIdentity {
|
||||
readonly playerid: number;
|
||||
readonly roomcode: string;
|
||||
}
|
||||
|
||||
export interface RoomActionPayload extends Readonly<Record<string, unknown>> {
|
||||
readonly agentid: string | number;
|
||||
readonly playerid: number;
|
||||
readonly gameid: string | number;
|
||||
readonly roomcode: string;
|
||||
}
|
||||
|
||||
function baseRoomAction(
|
||||
runtimeConfig: FirstSliceRuntimeConfig,
|
||||
room: RoomIdentity,
|
||||
): RoomActionPayload {
|
||||
const identity = requireRecord(runtimeConfig?.identity, '$.runtimeConfig.identity');
|
||||
const roomRecord = requireRecord(room, '$.room');
|
||||
return {
|
||||
agentid: requireStringOrFiniteNumber(
|
||||
identity.agentid,
|
||||
'$.runtimeConfig.identity.agentid',
|
||||
),
|
||||
playerid: requireInteger(roomRecord.playerid, '$.room.playerid'),
|
||||
gameid: requireStringOrFiniteNumber(identity.gameid, '$.runtimeConfig.identity.gameid'),
|
||||
roomcode: requireString(roomRecord.roomcode, '$.room.roomcode'),
|
||||
};
|
||||
}
|
||||
|
||||
export function buildPrepareRequest(
|
||||
runtimeConfig: FirstSliceRuntimeConfig,
|
||||
room: RoomIdentity,
|
||||
): FirstSliceEnvelope<'player_prepare', RoomActionPayload> {
|
||||
return buildFirstSliceEnvelope('player_prepare', baseRoomAction(runtimeConfig, room));
|
||||
}
|
||||
|
||||
export function buildExitRoomRequest(
|
||||
runtimeConfig: FirstSliceRuntimeConfig,
|
||||
room: RoomIdentity,
|
||||
): FirstSliceEnvelope<'self_exit_room', RoomActionPayload> {
|
||||
return buildFirstSliceEnvelope('self_exit_room', baseRoomAction(runtimeConfig, room));
|
||||
}
|
||||
|
||||
export interface JoinRoomEnvironment {
|
||||
readonly location: unknown;
|
||||
readonly ip: string;
|
||||
readonly vipMatch?: 1;
|
||||
readonly match_id?: string | number;
|
||||
}
|
||||
|
||||
export interface JoinRoomPayload extends RoomActionPayload {
|
||||
readonly location: unknown;
|
||||
readonly ip: string;
|
||||
readonly vipMatch?: 1;
|
||||
readonly match_id?: string | number;
|
||||
}
|
||||
|
||||
export function buildJoinRoomRequest(
|
||||
runtimeConfig: FirstSliceRuntimeConfig,
|
||||
room: RoomIdentity,
|
||||
environment: JoinRoomEnvironment,
|
||||
): FirstSliceEnvelope<'self_join_room', JoinRoomPayload> {
|
||||
const environmentRecord = requireRecord(environment, '$.environment');
|
||||
if (!hasOwn(environmentRecord, 'location') || environmentRecord.location === undefined) {
|
||||
throw new TypeError('$.environment.location: expected present field, received undefined');
|
||||
}
|
||||
if (environmentRecord.location !== null) {
|
||||
requireRecord(environmentRecord.location, '$.environment.location');
|
||||
}
|
||||
const data: Record<string, unknown> = {
|
||||
...baseRoomAction(runtimeConfig, room),
|
||||
location: environmentRecord.location,
|
||||
ip: requireString(environmentRecord.ip, '$.environment.ip'),
|
||||
};
|
||||
const vipMatch = optionalField(
|
||||
environmentRecord,
|
||||
'vipMatch',
|
||||
'$.environment',
|
||||
(value, path): 1 => {
|
||||
const parsed = requireNumber(value, path);
|
||||
if (parsed !== 1) {
|
||||
throw new RangeError(`${path}: expected number 1, received number`);
|
||||
}
|
||||
return 1;
|
||||
},
|
||||
);
|
||||
if (vipMatch !== undefined) data.vipMatch = vipMatch;
|
||||
|
||||
const matchId = optionalField(
|
||||
environmentRecord,
|
||||
'match_id',
|
||||
'$.environment',
|
||||
requireStringOrFiniteNumber,
|
||||
);
|
||||
if (matchId !== undefined) data.match_id = matchId;
|
||||
return buildFirstSliceEnvelope('self_join_room', data as JoinRoomPayload);
|
||||
}
|
||||
|
||||
export interface ParsedRoomResponse {
|
||||
readonly state: number;
|
||||
readonly room: ParsedRoomSnapshot | null;
|
||||
readonly reconnect: ReconnectSnapshot;
|
||||
readonly stage: 0 | 1;
|
||||
readonly raw: JsonRecord;
|
||||
}
|
||||
|
||||
export function parseSelfJoinRoomResponse(value: unknown): ParsedRoomResponse {
|
||||
const raw = requireRecord(value, '$.data');
|
||||
const state = requireInteger(raw.state, '$.data.state');
|
||||
const room = state === 0 ? parseRoomEntryFields(raw) : null;
|
||||
const parsedDeskwar = optionalField(raw, 'deskwar', '$.data', requireInteger);
|
||||
if (parsedDeskwar !== undefined && parsedDeskwar !== 0 && parsedDeskwar !== 1) {
|
||||
throw new RangeError('$.data.deskwar: expected protocol flag 0 or 1');
|
||||
}
|
||||
const reconnect = reconnectFrom(raw);
|
||||
const stage = reconnect.present ? 1 : parsedDeskwar ?? 0;
|
||||
return { state, room, reconnect, stage, raw };
|
||||
}
|
||||
|
||||
export interface ParsedSeatPayload {
|
||||
readonly seat: number;
|
||||
readonly raw: JsonRecord;
|
||||
}
|
||||
|
||||
function parseSeatPayload(value: unknown): ParsedSeatPayload {
|
||||
const raw = requireRecord(value, '$.data');
|
||||
const seat = requireInteger(raw.seat, '$.data.seat');
|
||||
if (seat < 0) throw new RangeError('$.data.seat: expected non-negative server seat, received number');
|
||||
return { seat, raw };
|
||||
}
|
||||
|
||||
export const parsePlayerPreparePayload = parseSeatPayload;
|
||||
export const parseOtherJoinRoomPayload = parseSeatPayload;
|
||||
export const parseOtherExitRoomPayload = parseSeatPayload;
|
||||
export const parseOtherOfflinePayload = parseSeatPayload;
|
||||
|
||||
export interface ParsedSelfExitPayload {
|
||||
readonly seat?: number;
|
||||
readonly raw: JsonRecord;
|
||||
}
|
||||
|
||||
export function parseSelfExitRoomPayload(value: unknown): ParsedSelfExitPayload {
|
||||
const raw = requireRecord(value, '$.data');
|
||||
const seat = optionalField(raw, 'seat', '$.data', requireInteger);
|
||||
if (seat !== undefined && seat < 0) {
|
||||
throw new RangeError('$.data.seat: expected non-negative server seat, received number');
|
||||
}
|
||||
return { ...(seat === undefined ? {} : { seat }), raw };
|
||||
}
|
||||
|
||||
export interface ParsedOnlinePayload extends ParsedSeatPayload {
|
||||
readonly ip: string;
|
||||
}
|
||||
|
||||
export function parseOtherOnlinePayload(value: unknown): ParsedOnlinePayload {
|
||||
const parsed = parseSeatPayload(value);
|
||||
return { seat: parsed.seat, ip: requireString(parsed.raw.ip, '$.data.ip'), raw: parsed.raw };
|
||||
}
|
||||
|
||||
export interface ParsedRoomserverPayload {
|
||||
readonly roomserver: string;
|
||||
readonly raw: JsonRecord;
|
||||
}
|
||||
|
||||
export function parseConnectRoomserverPayload(value: unknown): ParsedRoomserverPayload {
|
||||
const raw = requireRecord(value, '$.data');
|
||||
return { roomserver: requireString(raw.roomserver, '$.data.roomserver'), raw };
|
||||
}
|
||||
|
||||
export interface ParsedAgentserverPayload {
|
||||
readonly agentserver: string;
|
||||
readonly opt: string;
|
||||
readonly raw: JsonRecord;
|
||||
}
|
||||
|
||||
export function parseConnectAgentserverPayload(value: unknown): ParsedAgentserverPayload {
|
||||
const raw = requireRecord(value, '$.data');
|
||||
return {
|
||||
agentserver: requireString(raw.agentserver, '$.data.agentserver'),
|
||||
opt: requireString(raw.opt, '$.data.opt'),
|
||||
raw,
|
||||
};
|
||||
}
|
||||
|
||||
export interface ParsedKickPayload {
|
||||
readonly msg: string;
|
||||
readonly raw: JsonRecord;
|
||||
}
|
||||
|
||||
export function parseKickServerPayload(value: unknown): ParsedKickPayload {
|
||||
const raw = requireRecord(value, '$.data');
|
||||
return { msg: requireString(raw.msg, '$.data.msg'), raw };
|
||||
}
|
||||
import {
|
||||
buildFirstSliceEnvelope,
|
||||
type FirstSliceEnvelope,
|
||||
} from '../first-slice-routes.ts';
|
||||
import {
|
||||
hasOwn,
|
||||
optionalField,
|
||||
requireArray,
|
||||
requireInteger,
|
||||
requireNumber,
|
||||
requireRecord,
|
||||
requireString,
|
||||
requireStringOrFiniteNumber,
|
||||
type JsonRecord,
|
||||
} from './validation.ts';
|
||||
import type { FirstSliceRuntimeConfig } from './login-contract.ts';
|
||||
|
||||
export type ReconnectSnapshot =
|
||||
| { readonly present: false }
|
||||
| { readonly present: true; readonly value: unknown };
|
||||
|
||||
export interface ParsedRoomSnapshot {
|
||||
readonly roomcode: string;
|
||||
readonly seat: number;
|
||||
readonly roomtype: readonly unknown[];
|
||||
readonly players: readonly unknown[];
|
||||
}
|
||||
|
||||
export interface ParsedRoomEntry {
|
||||
readonly room: ParsedRoomSnapshot;
|
||||
readonly reconnect: ReconnectSnapshot;
|
||||
readonly raw: JsonRecord;
|
||||
}
|
||||
|
||||
export function reconnectFrom(raw: JsonRecord): ReconnectSnapshot {
|
||||
if (!Boolean(raw.deskinfo)) return { present: false };
|
||||
return { present: true, value: raw.deskinfo };
|
||||
}
|
||||
|
||||
export function parseRoomEntryFields(raw: JsonRecord): ParsedRoomSnapshot {
|
||||
const roomcode = requireString(raw.roomcode, '$.data.roomcode');
|
||||
const seat = requireInteger(raw.seat, '$.data.seat');
|
||||
const roomtype = requireArray(raw.roomtype, '$.data.roomtype');
|
||||
const players = requireArray(raw.players, '$.data.players');
|
||||
if (seat < 0 || seat >= players.length) {
|
||||
throw new RangeError(`$.data.seat: expected server seat index within players, received number`);
|
||||
}
|
||||
return { roomcode, seat, roomtype, players };
|
||||
}
|
||||
|
||||
export interface RoomIdentity {
|
||||
readonly playerid: number;
|
||||
readonly roomcode: string;
|
||||
}
|
||||
|
||||
export interface RoomActionPayload extends Readonly<Record<string, unknown>> {
|
||||
readonly agentid: string | number;
|
||||
readonly playerid: number;
|
||||
readonly gameid: string | number;
|
||||
readonly roomcode: string;
|
||||
}
|
||||
|
||||
function baseRoomAction(
|
||||
runtimeConfig: FirstSliceRuntimeConfig,
|
||||
room: RoomIdentity,
|
||||
): RoomActionPayload {
|
||||
const identity = requireRecord(runtimeConfig?.identity, '$.runtimeConfig.identity');
|
||||
const roomRecord = requireRecord(room, '$.room');
|
||||
return {
|
||||
agentid: requireStringOrFiniteNumber(
|
||||
identity.agentid,
|
||||
'$.runtimeConfig.identity.agentid',
|
||||
),
|
||||
playerid: requireInteger(roomRecord.playerid, '$.room.playerid'),
|
||||
gameid: requireStringOrFiniteNumber(identity.gameid, '$.runtimeConfig.identity.gameid'),
|
||||
roomcode: requireString(roomRecord.roomcode, '$.room.roomcode'),
|
||||
};
|
||||
}
|
||||
|
||||
export function buildPrepareRequest(
|
||||
runtimeConfig: FirstSliceRuntimeConfig,
|
||||
room: RoomIdentity,
|
||||
): FirstSliceEnvelope<'player_prepare', RoomActionPayload> {
|
||||
return buildFirstSliceEnvelope('player_prepare', baseRoomAction(runtimeConfig, room));
|
||||
}
|
||||
|
||||
export function buildExitRoomRequest(
|
||||
runtimeConfig: FirstSliceRuntimeConfig,
|
||||
room: RoomIdentity,
|
||||
): FirstSliceEnvelope<'self_exit_room', RoomActionPayload> {
|
||||
return buildFirstSliceEnvelope('self_exit_room', baseRoomAction(runtimeConfig, room));
|
||||
}
|
||||
|
||||
export interface JoinRoomEnvironment {
|
||||
readonly location: unknown;
|
||||
readonly ip: string;
|
||||
readonly vipMatch?: 1;
|
||||
readonly match_id?: string | number;
|
||||
}
|
||||
|
||||
export interface JoinRoomPayload extends Omit<RoomActionPayload, 'roomcode'> {
|
||||
readonly roomcode: string | number;
|
||||
readonly location: unknown;
|
||||
readonly ip: string;
|
||||
readonly vipMatch?: 1;
|
||||
readonly match_id?: string | number;
|
||||
}
|
||||
|
||||
export function buildJoinRoomRequest(
|
||||
runtimeConfig: FirstSliceRuntimeConfig,
|
||||
room: { readonly playerid: number; readonly roomcode: string | number },
|
||||
environment: JoinRoomEnvironment,
|
||||
): FirstSliceEnvelope<'self_join_room', JoinRoomPayload> {
|
||||
const environmentRecord = requireRecord(environment, '$.environment');
|
||||
if (!hasOwn(environmentRecord, 'location') || environmentRecord.location === undefined) {
|
||||
throw new TypeError('$.environment.location: expected present field, received undefined');
|
||||
}
|
||||
if (environmentRecord.location !== null) {
|
||||
requireRecord(environmentRecord.location, '$.environment.location');
|
||||
}
|
||||
const data: Record<string, unknown> = {
|
||||
...baseRoomAction(runtimeConfig, {playerid: room.playerid, roomcode: String(room.roomcode)}),
|
||||
roomcode: requireStringOrFiniteNumber(room.roomcode, '$.room.roomcode'),
|
||||
location: environmentRecord.location,
|
||||
ip: requireString(environmentRecord.ip, '$.environment.ip'),
|
||||
};
|
||||
const vipMatch = optionalField(
|
||||
environmentRecord,
|
||||
'vipMatch',
|
||||
'$.environment',
|
||||
(value, path): 1 => {
|
||||
const parsed = requireNumber(value, path);
|
||||
if (parsed !== 1) {
|
||||
throw new RangeError(`${path}: expected number 1, received number`);
|
||||
}
|
||||
return 1;
|
||||
},
|
||||
);
|
||||
if (vipMatch !== undefined) data.vipMatch = vipMatch;
|
||||
|
||||
const matchId = optionalField(
|
||||
environmentRecord,
|
||||
'match_id',
|
||||
'$.environment',
|
||||
requireStringOrFiniteNumber,
|
||||
);
|
||||
if (matchId !== undefined) data.match_id = matchId;
|
||||
return buildFirstSliceEnvelope('self_join_room', data as JoinRoomPayload);
|
||||
}
|
||||
|
||||
export interface ParsedRoomResponse {
|
||||
readonly state: number;
|
||||
readonly room: ParsedRoomSnapshot | null;
|
||||
readonly reconnect: ReconnectSnapshot;
|
||||
readonly stage: 0 | 1;
|
||||
readonly raw: JsonRecord;
|
||||
}
|
||||
|
||||
export function parseSelfJoinRoomResponse(value: unknown): ParsedRoomResponse {
|
||||
const raw = requireRecord(value, '$.data');
|
||||
const state = requireInteger(raw.state, '$.data.state');
|
||||
const room = state === 0 ? parseRoomEntryFields(raw) : null;
|
||||
const parsedDeskwar = optionalField(raw, 'deskwar', '$.data', requireInteger);
|
||||
if (parsedDeskwar !== undefined && parsedDeskwar !== 0 && parsedDeskwar !== 1) {
|
||||
throw new RangeError('$.data.deskwar: expected protocol flag 0 or 1');
|
||||
}
|
||||
const reconnect = reconnectFrom(raw);
|
||||
const stage = reconnect.present ? 1 : parsedDeskwar ?? 0;
|
||||
return { state, room, reconnect, stage, raw };
|
||||
}
|
||||
|
||||
export interface ParsedSeatPayload {
|
||||
readonly seat: number;
|
||||
readonly raw: JsonRecord;
|
||||
}
|
||||
|
||||
function parseSeatPayload(value: unknown): ParsedSeatPayload {
|
||||
const raw = requireRecord(value, '$.data');
|
||||
const seat = requireInteger(raw.seat, '$.data.seat');
|
||||
if (seat < 0) throw new RangeError('$.data.seat: expected non-negative server seat, received number');
|
||||
return { seat, raw };
|
||||
}
|
||||
|
||||
export const parsePlayerPreparePayload = parseSeatPayload;
|
||||
export const parseOtherJoinRoomPayload = parseSeatPayload;
|
||||
export const parseOtherExitRoomPayload = parseSeatPayload;
|
||||
export const parseOtherOfflinePayload = parseSeatPayload;
|
||||
|
||||
export interface ParsedSelfExitPayload {
|
||||
readonly seat?: number;
|
||||
readonly raw: JsonRecord;
|
||||
}
|
||||
|
||||
export function parseSelfExitRoomPayload(value: unknown): ParsedSelfExitPayload {
|
||||
const raw = requireRecord(value, '$.data');
|
||||
const seat = optionalField(raw, 'seat', '$.data', requireInteger);
|
||||
if (seat !== undefined && seat < 0) {
|
||||
throw new RangeError('$.data.seat: expected non-negative server seat, received number');
|
||||
}
|
||||
return { ...(seat === undefined ? {} : { seat }), raw };
|
||||
}
|
||||
|
||||
export interface ParsedOnlinePayload extends ParsedSeatPayload {
|
||||
readonly ip: string;
|
||||
}
|
||||
|
||||
export function parseOtherOnlinePayload(value: unknown): ParsedOnlinePayload {
|
||||
const parsed = parseSeatPayload(value);
|
||||
return { seat: parsed.seat, ip: requireString(parsed.raw.ip, '$.data.ip'), raw: parsed.raw };
|
||||
}
|
||||
|
||||
export interface ParsedRoomserverPayload {
|
||||
readonly roomserver: string;
|
||||
readonly raw: JsonRecord;
|
||||
}
|
||||
|
||||
export function parseConnectRoomserverPayload(value: unknown): ParsedRoomserverPayload {
|
||||
const raw = requireRecord(value, '$.data');
|
||||
return { roomserver: requireString(raw.roomserver, '$.data.roomserver'), raw };
|
||||
}
|
||||
|
||||
export interface ParsedAgentserverPayload {
|
||||
readonly agentserver: string;
|
||||
readonly opt: string;
|
||||
readonly raw: JsonRecord;
|
||||
}
|
||||
|
||||
export function parseConnectAgentserverPayload(value: unknown): ParsedAgentserverPayload {
|
||||
const raw = requireRecord(value, '$.data');
|
||||
return {
|
||||
agentserver: requireString(raw.agentserver, '$.data.agentserver'),
|
||||
opt: requireString(raw.opt, '$.data.opt'),
|
||||
raw,
|
||||
};
|
||||
}
|
||||
|
||||
export interface ParsedKickPayload {
|
||||
readonly msg: string;
|
||||
readonly raw: JsonRecord;
|
||||
}
|
||||
|
||||
export function parseKickServerPayload(value: unknown): ParsedKickPayload {
|
||||
const raw = requireRecord(value, '$.data');
|
||||
return { msg: requireString(raw.msg, '$.data.msg'), raw };
|
||||
}
|
||||
|
||||
|
||||
export interface CreateRoomEnvironment {
|
||||
readonly roomtype: readonly unknown[];
|
||||
readonly location: unknown;
|
||||
readonly ip: string;
|
||||
}
|
||||
|
||||
/** Transport contract only: roomtype contents belong to the subgame. */
|
||||
export function buildCreateRoomRequest(
|
||||
runtimeConfig: FirstSliceRuntimeConfig,
|
||||
playerid: number,
|
||||
command: CreateRoomEnvironment,
|
||||
): FirstSliceEnvelope<'create_room', Readonly<Record<string, unknown>>> {
|
||||
const identity = requireRecord(runtimeConfig.identity, '$.runtimeConfig.identity');
|
||||
const raw = requireRecord(command, '$.command');
|
||||
if (!hasOwn(raw, 'location') || raw.location === undefined) {
|
||||
throw new TypeError('$.command.location: expected present field');
|
||||
}
|
||||
if (raw.location !== null) requireRecord(raw.location, '$.command.location');
|
||||
return buildFirstSliceEnvelope('create_room', {
|
||||
agentid: requireStringOrFiniteNumber(identity.agentid, '$.identity.agentid'),
|
||||
playerid: requireInteger(playerid, '$.playerid'),
|
||||
gameid: requireStringOrFiniteNumber(identity.gameid, '$.identity.gameid'),
|
||||
roomtype: requireArray(raw.roomtype, '$.command.roomtype'),
|
||||
ip: requireString(raw.ip, '$.command.ip'),
|
||||
location: raw.location,
|
||||
});
|
||||
}
|
||||
|
||||
export interface ParsedCreateRoomResponse {
|
||||
readonly state: number;
|
||||
readonly room: { readonly roomcode: string; readonly seat: number; readonly roomtype: readonly unknown[] } | null;
|
||||
readonly needprepare: number;
|
||||
readonly infinite: number;
|
||||
readonly raw: JsonRecord;
|
||||
}
|
||||
|
||||
export function parseCreateRoomResponse(value: unknown): ParsedCreateRoomResponse {
|
||||
const raw = requireRecord(value, '$.data');
|
||||
const state = requireInteger(raw.state, '$.data.state');
|
||||
if (state !== 0) return {state, room: null, needprepare: 0, infinite: 0, raw};
|
||||
// Desk.Init initializes these flags to zero; create_room overrides only when provided.
|
||||
const flag = (key: string): number => {
|
||||
if (!hasOwn(raw, key)) return 0;
|
||||
const value = requireInteger(raw[key], `$.data.${key}`);
|
||||
if (value !== 0 && value !== 1) throw new RangeError(`$.data.${key}: expected protocol flag`);
|
||||
return value;
|
||||
};
|
||||
return {
|
||||
state,
|
||||
room: {roomcode: requireString(raw.roomcode, '$.data.roomcode'),
|
||||
seat: requireInteger(raw.seat, '$.data.seat'),
|
||||
roomtype: requireArray(raw.roomtype, '$.data.roomtype')},
|
||||
needprepare: flag('needprepare'), infinite: flag('infinite'), raw,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,77 +1,91 @@
|
||||
import { APP } from '../core/constants.ts';
|
||||
|
||||
/** Legacy 12_Logic dispatches login replies from all three platform routes by rpc.
|
||||
* This inbound contract is independent of the outbound agent route below. */
|
||||
export function isLoginResponse(message:{route:string;rpc:string}):boolean{
|
||||
return message.rpc==='player_login'&&(message.route==='agent'||message.route==='room'||message.route==='platform');
|
||||
}
|
||||
|
||||
export type FirstSliceRpc =
|
||||
| 'getInfoByShortCode'
|
||||
| 'get_player_task'
|
||||
| 'get_task_award'
|
||||
| 'update_roomcard'
|
||||
| 'player_finish_task'
|
||||
| 'player_login'
|
||||
| 'self_join_room'
|
||||
| 'player_prepare'
|
||||
| 'self_exit_room'
|
||||
| 'other_join_room'
|
||||
| 'other_exit_room'
|
||||
| 'other_offline'
|
||||
| 'other_online'
|
||||
| 'connect_roomserver'
|
||||
| 'connect_agentserver'
|
||||
| 'kick_server'
|
||||
| 'kick_offline'
|
||||
| 'show_message';
|
||||
|
||||
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',
|
||||
self_exit_room: 'room',
|
||||
other_join_room: 'room',
|
||||
other_exit_room: 'room',
|
||||
other_offline: 'room',
|
||||
other_online: 'room',
|
||||
connect_roomserver: 'room',
|
||||
connect_agentserver: 'agent',
|
||||
kick_server: 'agent',
|
||||
kick_offline: 'agent',
|
||||
show_message: 'agent',
|
||||
} as const satisfies Record<FirstSliceRpc, 'platform' | 'agent' | 'room'>;
|
||||
|
||||
export function routeForFirstSlice<Rpc extends FirstSliceRpc>(
|
||||
rpc: Rpc,
|
||||
): (typeof FIRST_SLICE_RPC_ROUTE)[Rpc] {
|
||||
if (!Object.prototype.hasOwnProperty.call(FIRST_SLICE_RPC_ROUTE, rpc)) {
|
||||
throw new Error(`FIRST_SLICE_RPC_ROUTE 未定义 rpc: ${String(rpc)}`);
|
||||
}
|
||||
return FIRST_SLICE_RPC_ROUTE[rpc];
|
||||
}
|
||||
|
||||
export interface FirstSliceEnvelope<
|
||||
Rpc extends FirstSliceRpc,
|
||||
Data extends Readonly<Record<string, unknown>>,
|
||||
> {
|
||||
readonly app: 'youle';
|
||||
readonly route: (typeof FIRST_SLICE_RPC_ROUTE)[Rpc];
|
||||
readonly rpc: Rpc;
|
||||
readonly data: Data;
|
||||
}
|
||||
|
||||
export function buildFirstSliceEnvelope<
|
||||
Rpc extends FirstSliceRpc,
|
||||
Data extends Readonly<Record<string, unknown>>,
|
||||
>(rpc: Rpc, data: Data): FirstSliceEnvelope<Rpc, Data> {
|
||||
return { app: APP, route: routeForFirstSlice(rpc), rpc, data };
|
||||
}
|
||||
import { APP } from '../core/constants.ts';
|
||||
|
||||
/** Legacy 12_Logic dispatches login replies from all three platform routes by rpc.
|
||||
* This inbound contract is independent of the outbound agent route below. */
|
||||
export function isLoginResponse(message:{route:string;rpc:string}):boolean{
|
||||
return message.rpc==='player_login'&&(message.route==='agent'||message.route==='room'||message.route==='platform');
|
||||
}
|
||||
|
||||
export type FirstSliceRpc =
|
||||
| 'getVipRankList'
|
||||
| 'get_treasurelist'
|
||||
| 'topup_card'
|
||||
| 'get_player_invitecode'
|
||||
| 'binding_invitecode'
|
||||
| 'getInfoByShortCode'
|
||||
| 'get_player_task'
|
||||
| 'get_task_award'
|
||||
| 'update_bean'
|
||||
| 'update_roomcard'
|
||||
| 'player_finish_task'
|
||||
| 'player_login'
|
||||
| 'create_room'
|
||||
| 'self_join_room'
|
||||
| 'player_prepare'
|
||||
| 'self_exit_room'
|
||||
| 'other_join_room'
|
||||
| 'other_exit_room'
|
||||
| 'other_offline'
|
||||
| 'other_online'
|
||||
| 'connect_roomserver'
|
||||
| 'connect_agentserver'
|
||||
| 'kick_server'
|
||||
| 'kick_offline'
|
||||
| 'show_message';
|
||||
|
||||
export type FirstSliceRoute = 'platform' | 'agent' | 'room';
|
||||
|
||||
/** 第一纵向切片 rpc → route 的唯一权威映射。 */
|
||||
export const FIRST_SLICE_RPC_ROUTE = {
|
||||
getVipRankList: 'agent',
|
||||
get_treasurelist: 'agent',
|
||||
topup_card: 'agent',
|
||||
get_player_invitecode: 'agent',
|
||||
binding_invitecode: 'agent',
|
||||
getInfoByShortCode: 'agent',
|
||||
get_player_task: 'agent',
|
||||
get_task_award: 'agent',
|
||||
update_bean: 'agent',
|
||||
update_roomcard: 'agent',
|
||||
player_finish_task: 'agent',
|
||||
player_login: 'agent',
|
||||
create_room: 'agent',
|
||||
self_join_room: 'agent',
|
||||
player_prepare: 'room',
|
||||
self_exit_room: 'room',
|
||||
other_join_room: 'room',
|
||||
other_exit_room: 'room',
|
||||
other_offline: 'room',
|
||||
other_online: 'room',
|
||||
connect_roomserver: 'room',
|
||||
connect_agentserver: 'agent',
|
||||
kick_server: 'agent',
|
||||
kick_offline: 'agent',
|
||||
show_message: 'agent',
|
||||
} as const satisfies Record<FirstSliceRpc, 'platform' | 'agent' | 'room'>;
|
||||
|
||||
export function routeForFirstSlice<Rpc extends FirstSliceRpc>(
|
||||
rpc: Rpc,
|
||||
): (typeof FIRST_SLICE_RPC_ROUTE)[Rpc] {
|
||||
if (!Object.prototype.hasOwnProperty.call(FIRST_SLICE_RPC_ROUTE, rpc)) {
|
||||
throw new Error(`FIRST_SLICE_RPC_ROUTE 未定义 rpc: ${String(rpc)}`);
|
||||
}
|
||||
return FIRST_SLICE_RPC_ROUTE[rpc];
|
||||
}
|
||||
|
||||
export interface FirstSliceEnvelope<
|
||||
Rpc extends FirstSliceRpc,
|
||||
Data extends Readonly<Record<string, unknown>>,
|
||||
> {
|
||||
readonly app: 'youle';
|
||||
readonly route: (typeof FIRST_SLICE_RPC_ROUTE)[Rpc];
|
||||
readonly rpc: Rpc;
|
||||
readonly data: Data;
|
||||
}
|
||||
|
||||
export function buildFirstSliceEnvelope<
|
||||
Rpc extends FirstSliceRpc,
|
||||
Data extends Readonly<Record<string, unknown>>,
|
||||
>(rpc: Rpc, data: Data): FirstSliceEnvelope<Rpc, Data> {
|
||||
return { app: APP, route: routeForFirstSlice(rpc), rpc, data };
|
||||
}
|
||||
|
||||
@@ -1,206 +1,228 @@
|
||||
import type { InboundMessage } from '../core/types/envelope.ts';
|
||||
import type { GameSessionHost } from '../sdk/runtime/game-session-host.ts';
|
||||
import type { ConnectionControlPort } from '../platform/connection-intent.ts';
|
||||
import type { RuntimeSession } from '../platform/runtime-session.ts';
|
||||
import type { PlatformStore } from '../platform/stores/platform-store.ts';
|
||||
import { requireRecord, requireString } from './contracts/validation.ts';
|
||||
import {
|
||||
parseConnectAgentserverPayload,
|
||||
parseConnectRoomserverPayload,
|
||||
parseKickServerPayload,
|
||||
parseOtherExitRoomPayload,
|
||||
parseOtherJoinRoomPayload,
|
||||
parseOtherOfflinePayload,
|
||||
parseOtherOnlinePayload,
|
||||
parsePlayerPreparePayload,
|
||||
parseSelfExitRoomPayload,
|
||||
} from './contracts/index.ts';
|
||||
import {
|
||||
buildFirstSliceEnvelope,
|
||||
isLoginResponse,
|
||||
FIRST_SLICE_RPC_ROUTE,
|
||||
routeForFirstSlice,
|
||||
type FirstSliceRpc,
|
||||
} from './first-slice-routes.ts';
|
||||
|
||||
export interface PlatformHandlersOptions {
|
||||
readonly store: PlatformStore;
|
||||
readonly gameSession: GameSessionHost;
|
||||
readonly session: RuntimeSession;
|
||||
readonly connection: ConnectionControlPort;
|
||||
readonly showMessage?: (message:string,time:number,source:'show_message'|'update_roomcard')=>void;
|
||||
}
|
||||
|
||||
export type PlatformDispatchResult = 'handled-platform';
|
||||
|
||||
/** Strict first-slice platform RPC dispatcher. */
|
||||
export class PlatformHandlers {
|
||||
constructor(private readonly options: PlatformHandlersOptions) {}
|
||||
|
||||
dispatch(message: InboundMessage): PlatformDispatchResult {
|
||||
if (!isPlatformRoute(message.route)) {
|
||||
throw new Error(
|
||||
`PlatformHandlers received non-platform route: route=${message.route} rpc=${message.rpc}`,
|
||||
);
|
||||
}
|
||||
if (!Object.prototype.hasOwnProperty.call(FIRST_SLICE_RPC_ROUTE, message.rpc)) {
|
||||
throw new Error(
|
||||
`Unsupported platform rpc: route=${message.route} rpc=${message.rpc}`,
|
||||
);
|
||||
}
|
||||
const rpc = message.rpc as FirstSliceRpc;
|
||||
const expectedRoute = routeForFirstSlice(rpc);
|
||||
if (message.route !== expectedRoute && !isLoginResponse(message)) {
|
||||
throw new Error(
|
||||
`Platform rpc route mismatch: rpc=${rpc} expected=${expectedRoute} received=${message.route}`,
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
case 'self_join_room':
|
||||
this.options.session.handleSelfJoin(message.data);
|
||||
break;
|
||||
case 'player_prepare': {
|
||||
const parsed = parsePlayerPreparePayload(message.data);
|
||||
this.requireActiveGame(rpc);
|
||||
this.options.store.playerReady(parsed);
|
||||
this.options.gameSession.publish({ type: 'room.player-ready', seat: parsed.seat });
|
||||
break;
|
||||
}
|
||||
case 'self_exit_room': {
|
||||
const parsed = parseSelfExitRoomPayload(message.data);
|
||||
this.options.session.handleSelfExit(parsed);
|
||||
break;
|
||||
}
|
||||
case 'other_join_room': {
|
||||
const parsed = parseOtherJoinRoomPayload(message.data);
|
||||
this.requireActiveGame(rpc);
|
||||
this.options.store.playerJoin(parsed);
|
||||
this.options.gameSession.publish({ type: 'room.player-joined', seat: parsed.seat });
|
||||
break;
|
||||
}
|
||||
case 'other_exit_room': {
|
||||
const parsed = parseOtherExitRoomPayload(message.data);
|
||||
this.requireActiveGame(rpc);
|
||||
this.options.store.playerExit(parsed);
|
||||
this.options.gameSession.publish({ type: 'room.player-left', seat: parsed.seat });
|
||||
break;
|
||||
}
|
||||
case 'other_offline': {
|
||||
const parsed = parseOtherOfflinePayload(message.data);
|
||||
this.requireActiveGame(rpc);
|
||||
this.options.store.playerOffline(parsed);
|
||||
this.options.gameSession.publish({ type: 'room.player-offline', seat: parsed.seat });
|
||||
break;
|
||||
}
|
||||
case 'other_online': {
|
||||
const parsed = parseOtherOnlinePayload(message.data);
|
||||
this.requireActiveGame(rpc);
|
||||
this.options.store.playerOnline(parsed);
|
||||
this.options.gameSession.publish({ type: 'room.player-online', seat: parsed.seat });
|
||||
break;
|
||||
}
|
||||
case 'connect_roomserver': {
|
||||
const parsed = parseConnectRoomserverPayload(message.data);
|
||||
const target = parseSwitchTarget(parsed.roomserver, '$.data.roomserver');
|
||||
const resend = buildFirstSliceEnvelope('connect_roomserver', parsed.raw);
|
||||
this.options.session.handleServerSwitch(
|
||||
() => this.options.connection.switchServer(target, resend),
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'connect_agentserver': {
|
||||
const parsed = parseConnectAgentserverPayload(message.data);
|
||||
const target = parseSwitchTarget(parsed.agentserver, '$.data.agentserver');
|
||||
const resend = buildFirstSliceEnvelope('connect_agentserver', parsed.raw);
|
||||
this.options.session.handleServerSwitch(
|
||||
() => this.options.connection.switchServer(target, resend),
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'show_message': {
|
||||
const data=requireRecord(message.data,'$.data');
|
||||
const text=requireString(data.msg,'$.data.msg');
|
||||
// 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,'show_message');
|
||||
break;
|
||||
}
|
||||
case 'kick_offline': {
|
||||
const data=requireRecord(message.data,'$.data');
|
||||
this.options.session.handleKick(data,()=>this.options.connection.stopForKick(data));
|
||||
break;
|
||||
}
|
||||
case 'kick_server': {
|
||||
const parsed = parseKickServerPayload(message.data);
|
||||
this.options.session.handleKick(
|
||||
parsed.raw,
|
||||
() => this.options.connection.stopForKick(parsed.raw),
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 'handled-platform';
|
||||
}
|
||||
|
||||
private requireActiveGame(rpc: string): void {
|
||||
if (this.options.gameSession.state !== 'active') {
|
||||
throw new Error(`${rpc} requires an active game session`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isPlatformRoute(route: string): route is 'platform' | 'agent' | 'room' {
|
||||
return route === 'platform' || route === 'agent' || route === 'room';
|
||||
}
|
||||
|
||||
function parseSwitchTarget(address: string, path: string): string {
|
||||
if (address.length === 0 || address.trim() !== address) {
|
||||
throw new TypeError(`${path}: expected a non-empty WebSocket server address`);
|
||||
}
|
||||
const scheme = /^([A-Za-z][A-Za-z0-9+.-]*):\/\//.exec(address);
|
||||
const target = scheme ? address : `ws://${address}`;
|
||||
const authority = /^[A-Za-z][A-Za-z0-9+.-]*:\/\/([^/?#]*)/.exec(target)?.[1];
|
||||
if (authority === undefined || authority.length === 0) {
|
||||
throw new TypeError(`${path}: expected a WebSocket address with a non-empty host`);
|
||||
}
|
||||
if (target.includes('#') || authority.includes('@')) {
|
||||
throw new TypeError(`${path}: expected a WebSocket address without fragment or userinfo`);
|
||||
}
|
||||
|
||||
let parsed: URL;
|
||||
try {
|
||||
parsed = new URL(target);
|
||||
} catch {
|
||||
throw new TypeError(`${path}: expected a valid WebSocket server address`);
|
||||
}
|
||||
const protocol = parsed.protocol.toLowerCase();
|
||||
if (
|
||||
(protocol !== 'ws:' && protocol !== 'wss:')
|
||||
|| parsed.hostname.length === 0
|
||||
|| parsed.username.length > 0
|
||||
|| parsed.password.length > 0
|
||||
|| parsed.hash.length > 0
|
||||
) {
|
||||
throw new TypeError(`${path}: expected a valid WebSocket server address`);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
import type { InboundMessage } from '../core/types/envelope.ts';
|
||||
import type { GameSessionHost } from '../sdk/runtime/game-session-host.ts';
|
||||
import type { ConnectionControlPort } from '../platform/connection-intent.ts';
|
||||
import type { RuntimeSession } from '../platform/runtime-session.ts';
|
||||
import type { PlatformStore } from '../platform/stores/platform-store.ts';
|
||||
import { requireRecord, requireString } from './contracts/validation.ts';
|
||||
import {
|
||||
parseConnectAgentserverPayload,
|
||||
parseConnectRoomserverPayload,
|
||||
parseKickServerPayload,
|
||||
parseOtherExitRoomPayload,
|
||||
parseOtherJoinRoomPayload,
|
||||
parseOtherOfflinePayload,
|
||||
parseOtherOnlinePayload,
|
||||
parsePlayerPreparePayload,
|
||||
parseSelfExitRoomPayload,
|
||||
} from './contracts/index.ts';
|
||||
import {
|
||||
buildFirstSliceEnvelope,
|
||||
isLoginResponse,
|
||||
FIRST_SLICE_RPC_ROUTE,
|
||||
routeForFirstSlice,
|
||||
type FirstSliceRpc,
|
||||
} from './first-slice-routes.ts';
|
||||
|
||||
export interface PlatformHandlersOptions {
|
||||
readonly store: PlatformStore;
|
||||
readonly gameSession: GameSessionHost;
|
||||
readonly session: RuntimeSession;
|
||||
readonly connection: ConnectionControlPort;
|
||||
readonly showMessage?: (message:string,time:number,source:'show_message'|'update_roomcard'|'update_bean'|'binding_invitecode')=>void;
|
||||
}
|
||||
|
||||
export type PlatformDispatchResult = 'handled-platform';
|
||||
|
||||
/** Strict first-slice platform RPC dispatcher. */
|
||||
export class PlatformHandlers {
|
||||
constructor(private readonly options: PlatformHandlersOptions) {}
|
||||
|
||||
dispatch(message: InboundMessage): PlatformDispatchResult {
|
||||
if (!isPlatformRoute(message.route)) {
|
||||
throw new Error(
|
||||
`PlatformHandlers received non-platform route: route=${message.route} rpc=${message.rpc}`,
|
||||
);
|
||||
}
|
||||
if (!Object.prototype.hasOwnProperty.call(FIRST_SLICE_RPC_ROUTE, message.rpc)) {
|
||||
throw new Error(
|
||||
`Unsupported platform rpc: route=${message.route} rpc=${message.rpc}`,
|
||||
);
|
||||
}
|
||||
const rpc = message.rpc as FirstSliceRpc;
|
||||
const expectedRoute = routeForFirstSlice(rpc);
|
||||
if (message.route !== expectedRoute && !isLoginResponse(message) && !(rpc==='update_bean'&&message.route==='room')) {
|
||||
throw new Error(
|
||||
`Platform rpc route mismatch: rpc=${rpc} expected=${expectedRoute} received=${message.route}`,
|
||||
);
|
||||
}
|
||||
|
||||
switch (rpc) {
|
||||
case 'getVipRankList':this.options.store.applyVipRank(message.data);break;
|
||||
case 'get_treasurelist':this.options.store.applyTreasureList(message.data);break;
|
||||
case 'topup_card':break; // Original response is empty; updates arrive separately.
|
||||
case 'get_player_invitecode':this.options.store.applyInvitecode(message.data);break;
|
||||
case 'binding_invitecode':{
|
||||
if(!this.options.showMessage)throw Error('binding_invitecode requires a message UI');
|
||||
const text=this.options.store.applyInviteBinding(message.data);
|
||||
this.options.showMessage(text,3000,'binding_invitecode');break;
|
||||
}
|
||||
case 'getInfoByShortCode':this.options.store.applyVipRooms(message.data);break;
|
||||
case 'update_bean': {
|
||||
const raw=requireRecord(message.data,'$.data');
|
||||
const text=raw.text?requireString(raw.text,'$.data.text'):null;
|
||||
this.options.store.applyBeanUpdate(raw);
|
||||
if(raw.change===undefined&&raw.seat===undefined&&text){
|
||||
if(!this.options.showMessage)throw Error('update_bean requires a message UI');
|
||||
this.options.showMessage(text,0,'update_bean');
|
||||
}
|
||||
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;
|
||||
case 'create_room':
|
||||
this.options.session.handleCreateRoom(message.data);
|
||||
break;
|
||||
case 'self_join_room':
|
||||
this.options.session.handleSelfJoin(message.data);
|
||||
break;
|
||||
case 'player_prepare': {
|
||||
const parsed = parsePlayerPreparePayload(message.data);
|
||||
this.requireActiveGame(rpc);
|
||||
this.options.store.playerReady(parsed);
|
||||
this.options.gameSession.publish({ type: 'room.player-ready', seat: parsed.seat });
|
||||
break;
|
||||
}
|
||||
case 'self_exit_room': {
|
||||
const parsed = parseSelfExitRoomPayload(message.data);
|
||||
this.options.session.handleSelfExit(parsed);
|
||||
break;
|
||||
}
|
||||
case 'other_join_room': {
|
||||
const parsed = parseOtherJoinRoomPayload(message.data);
|
||||
this.requireActiveGame(rpc);
|
||||
this.options.store.playerJoin(parsed);
|
||||
this.options.gameSession.publish({ type: 'room.player-joined', seat: parsed.seat });
|
||||
break;
|
||||
}
|
||||
case 'other_exit_room': {
|
||||
const parsed = parseOtherExitRoomPayload(message.data);
|
||||
this.requireActiveGame(rpc);
|
||||
this.options.store.playerExit(parsed);
|
||||
this.options.gameSession.publish({ type: 'room.player-left', seat: parsed.seat });
|
||||
break;
|
||||
}
|
||||
case 'other_offline': {
|
||||
const parsed = parseOtherOfflinePayload(message.data);
|
||||
this.requireActiveGame(rpc);
|
||||
this.options.store.playerOffline(parsed);
|
||||
this.options.gameSession.publish({ type: 'room.player-offline', seat: parsed.seat });
|
||||
break;
|
||||
}
|
||||
case 'other_online': {
|
||||
const parsed = parseOtherOnlinePayload(message.data);
|
||||
this.requireActiveGame(rpc);
|
||||
this.options.store.playerOnline(parsed);
|
||||
this.options.gameSession.publish({ type: 'room.player-online', seat: parsed.seat });
|
||||
break;
|
||||
}
|
||||
case 'connect_roomserver': {
|
||||
const parsed = parseConnectRoomserverPayload(message.data);
|
||||
const target = parseSwitchTarget(parsed.roomserver, '$.data.roomserver');
|
||||
const resend = buildFirstSliceEnvelope('connect_roomserver', parsed.raw);
|
||||
this.options.session.handleServerSwitch(
|
||||
() => this.options.connection.switchServer(target, resend),
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'connect_agentserver': {
|
||||
const parsed = parseConnectAgentserverPayload(message.data);
|
||||
const target = parseSwitchTarget(parsed.agentserver, '$.data.agentserver');
|
||||
const resend = buildFirstSliceEnvelope('connect_agentserver', parsed.raw);
|
||||
this.options.session.handleServerSwitch(
|
||||
() => this.options.connection.switchServer(target, resend),
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'show_message': {
|
||||
const data=requireRecord(message.data,'$.data');
|
||||
const text=requireString(data.msg,'$.data.msg');
|
||||
// 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,'show_message');
|
||||
break;
|
||||
}
|
||||
case 'kick_offline': {
|
||||
const data=requireRecord(message.data,'$.data');
|
||||
this.options.session.handleKick(data,()=>this.options.connection.stopForKick(data));
|
||||
break;
|
||||
}
|
||||
case 'kick_server': {
|
||||
const parsed = parseKickServerPayload(message.data);
|
||||
this.options.session.handleKick(
|
||||
parsed.raw,
|
||||
() => this.options.connection.stopForKick(parsed.raw),
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 'handled-platform';
|
||||
}
|
||||
|
||||
private requireActiveGame(rpc: string): void {
|
||||
if (this.options.gameSession.state !== 'active') {
|
||||
throw new Error(`${rpc} requires an active game session`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isPlatformRoute(route: string): route is 'platform' | 'agent' | 'room' {
|
||||
return route === 'platform' || route === 'agent' || route === 'room';
|
||||
}
|
||||
|
||||
function parseSwitchTarget(address: string, path: string): string {
|
||||
if (address.length === 0 || address.trim() !== address) {
|
||||
throw new TypeError(`${path}: expected a non-empty WebSocket server address`);
|
||||
}
|
||||
const scheme = /^([A-Za-z][A-Za-z0-9+.-]*):\/\//.exec(address);
|
||||
const target = scheme ? address : `ws://${address}`;
|
||||
const authority = /^[A-Za-z][A-Za-z0-9+.-]*:\/\/([^/?#]*)/.exec(target)?.[1];
|
||||
if (authority === undefined || authority.length === 0) {
|
||||
throw new TypeError(`${path}: expected a WebSocket address with a non-empty host`);
|
||||
}
|
||||
if (target.includes('#') || authority.includes('@')) {
|
||||
throw new TypeError(`${path}: expected a WebSocket address without fragment or userinfo`);
|
||||
}
|
||||
|
||||
let parsed: URL;
|
||||
try {
|
||||
parsed = new URL(target);
|
||||
} catch {
|
||||
throw new TypeError(`${path}: expected a valid WebSocket server address`);
|
||||
}
|
||||
const protocol = parsed.protocol.toLowerCase();
|
||||
if (
|
||||
(protocol !== 'ws:' && protocol !== 'wss:')
|
||||
|| parsed.hostname.length === 0
|
||||
|| parsed.username.length > 0
|
||||
|| parsed.password.length > 0
|
||||
|| parsed.hash.length > 0
|
||||
) {
|
||||
throw new TypeError(`${path}: expected a valid WebSocket server address`);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
/** Pre-room capability: a game owns its page and validated, opaque wire configuration. */
|
||||
export interface CreateRoomPageContext {
|
||||
readonly previousRoomtype:unknown;
|
||||
submit(roomtype:readonly unknown[]):void;
|
||||
cancel():void;
|
||||
reportError(message:string):void;
|
||||
}
|
||||
export interface CreateRoomPagePort {
|
||||
open(context:CreateRoomPageContext):void;
|
||||
setBusy(busy:boolean):void;
|
||||
close():void;
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "fd4c9495-d6d5-49da-8476-a510df24f1c4",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from './game-entry.ts';
|
||||
export * from './game-host.ts';
|
||||
export * from './game-module.ts';
|
||||
export * from './game-seat-mapper.ts';
|
||||
export * from './platform-events.ts';
|
||||
export * from './snapshots.ts';
|
||||
export * from './create-room-page.ts';
|
||||
export * from './game-host.ts';
|
||||
export * from './game-module.ts';
|
||||
export * from './game-seat-mapper.ts';
|
||||
export * from './platform-events.ts';
|
||||
export * from './snapshots.ts';
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
[
|
||||
{
|
||||
"__type__": "cc.AnimationClip",
|
||||
"_name": "AnnouncementHorn",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_native": "",
|
||||
"sample": 2,
|
||||
"speed": 1,
|
||||
"wrapMode": 2,
|
||||
"enableTrsBlending": false,
|
||||
"_duration": 2,
|
||||
"_hash": 0,
|
||||
"_tracks": [
|
||||
{
|
||||
"__id__": 1
|
||||
}
|
||||
],
|
||||
"_exoticAnimation": null,
|
||||
"_events": [],
|
||||
"_embeddedPlayers": [],
|
||||
"_additiveSettings": {
|
||||
"__id__": 6
|
||||
},
|
||||
"_auxiliaryCurveEntries": []
|
||||
},
|
||||
{
|
||||
"__type__": "cc.animation.ObjectTrack",
|
||||
"_binding": {
|
||||
"__type__": "cc.animation.TrackBinding",
|
||||
"path": {
|
||||
"__id__": 2
|
||||
},
|
||||
"proxy": null
|
||||
},
|
||||
"_channel": {
|
||||
"__id__": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.animation.TrackPath",
|
||||
"_paths": [
|
||||
{
|
||||
"__id__": 3
|
||||
},
|
||||
"spriteFrame"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "cc.animation.ComponentPath",
|
||||
"component": "cc.Sprite"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.animation.Channel",
|
||||
"_curve": {
|
||||
"__id__": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.ObjectCurve",
|
||||
"_times": [
|
||||
0,
|
||||
0.5,
|
||||
1,
|
||||
1.5
|
||||
],
|
||||
"_values": [
|
||||
{
|
||||
"__uuid__": "151439f3-c9bd-4fb2-97b5-5afc295d76e8@f9941",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "5647b603-4a35-4bd8-b955-a8ceca2a3f43@f9941",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "151439f3-c9bd-4fb2-97b5-5afc295d76e8@f9941",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "151439f3-c9bd-4fb2-97b5-5afc295d76e8@f9941",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "cc.AnimationClipAdditiveSettings",
|
||||
"enabled": false,
|
||||
"refClip": null
|
||||
}
|
||||
]
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"ver": "2.0.4",
|
||||
"importer": "animation-clip",
|
||||
"imported": true,
|
||||
"uuid": "aed03d13-bce1-42a0-9d23-ebd829d9ee55",
|
||||
"files": [
|
||||
".bin"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"name": "AnnouncementHorn"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
[
|
||||
{
|
||||
"__type__": "cc.AnimationClip",
|
||||
"_name": "RequestLoading",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_native": "",
|
||||
"sample": 60,
|
||||
"speed": 1,
|
||||
"wrapMode": 2,
|
||||
"enableTrsBlending": false,
|
||||
"_duration": 1,
|
||||
"_hash": 0,
|
||||
"_tracks": [
|
||||
{
|
||||
"__id__": 1
|
||||
}
|
||||
],
|
||||
"_exoticAnimation": null,
|
||||
"_events": [],
|
||||
"_embeddedPlayers": [],
|
||||
"_additiveSettings": {
|
||||
"__id__": 5
|
||||
},
|
||||
"_auxiliaryCurveEntries": []
|
||||
},
|
||||
{
|
||||
"__type__": "cc.animation.RealTrack",
|
||||
"_binding": {
|
||||
"__type__": "cc.animation.TrackBinding",
|
||||
"path": {
|
||||
"__id__": 2
|
||||
},
|
||||
"proxy": null
|
||||
},
|
||||
"_channel": {
|
||||
"__id__": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.animation.TrackPath",
|
||||
"_paths": [
|
||||
"angle"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "cc.animation.Channel",
|
||||
"_curve": {
|
||||
"__id__": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.RealCurve",
|
||||
"_times": [
|
||||
0,
|
||||
1
|
||||
],
|
||||
"_values": [
|
||||
{
|
||||
"__type__": "cc.RealKeyframeValue",
|
||||
"interpolationMode": 0,
|
||||
"tangentWeightMode": 0,
|
||||
"value": 0,
|
||||
"rightTangent": 0,
|
||||
"rightTangentWeight": 0,
|
||||
"leftTangent": 0,
|
||||
"leftTangentWeight": 0,
|
||||
"easingMethod": 0,
|
||||
"__editorExtras__": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.RealKeyframeValue",
|
||||
"interpolationMode": 0,
|
||||
"tangentWeightMode": 0,
|
||||
"value": -360,
|
||||
"rightTangent": 0,
|
||||
"rightTangentWeight": 0,
|
||||
"leftTangent": 0,
|
||||
"leftTangentWeight": 0,
|
||||
"easingMethod": 0,
|
||||
"__editorExtras__": null
|
||||
}
|
||||
],
|
||||
"preExtrapolation": 1,
|
||||
"postExtrapolation": 1
|
||||
},
|
||||
{
|
||||
"__type__": "cc.AnimationClipAdditiveSettings",
|
||||
"enabled": false,
|
||||
"refClip": null
|
||||
}
|
||||
]
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"ver": "2.0.4",
|
||||
"importer": "animation-clip",
|
||||
"imported": true,
|
||||
"uuid": "987771c5-7f29-487a-b61a-476981cc9216",
|
||||
"files": [
|
||||
".bin"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"name": "RequestLoading"
|
||||
}
|
||||
}
|
||||
+134
-134
@@ -1,134 +1,134 @@
|
||||
{
|
||||
"ver": "1.0.27",
|
||||
"importer": "image",
|
||||
"imported": true,
|
||||
"uuid": "34daf5f4-3869-4f71-84c4-d09103701be6",
|
||||
"files": [
|
||||
".json",
|
||||
".png"
|
||||
],
|
||||
"subMetas": {
|
||||
"6c48a": {
|
||||
"importer": "texture",
|
||||
"uuid": "34daf5f4-3869-4f71-84c4-d09103701be6@6c48a",
|
||||
"displayName": "00042",
|
||||
"id": "6c48a",
|
||||
"name": "texture",
|
||||
"userData": {
|
||||
"wrapModeS": "clamp-to-edge",
|
||||
"wrapModeT": "clamp-to-edge",
|
||||
"imageUuidOrDatabaseUri": "34daf5f4-3869-4f71-84c4-d09103701be6",
|
||||
"isUuid": true,
|
||||
"visible": false,
|
||||
"minfilter": "linear",
|
||||
"magfilter": "linear",
|
||||
"mipfilter": "none",
|
||||
"anisotropy": 0
|
||||
},
|
||||
"ver": "1.0.22",
|
||||
"imported": true,
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {}
|
||||
},
|
||||
"f9941": {
|
||||
"importer": "sprite-frame",
|
||||
"uuid": "34daf5f4-3869-4f71-84c4-d09103701be6@f9941",
|
||||
"displayName": "00042",
|
||||
"id": "f9941",
|
||||
"name": "spriteFrame",
|
||||
"userData": {
|
||||
"trimThreshold": 1,
|
||||
"rotated": false,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"trimX": 0,
|
||||
"trimY": 0,
|
||||
"width": 108,
|
||||
"height": 98,
|
||||
"rawWidth": 108,
|
||||
"rawHeight": 98,
|
||||
"borderTop": 48,
|
||||
"borderBottom": 48,
|
||||
"borderLeft": 53,
|
||||
"borderRight": 53,
|
||||
"packable": true,
|
||||
"pixelsToUnit": 100,
|
||||
"pivotX": 0.5,
|
||||
"pivotY": 0.5,
|
||||
"meshType": 0,
|
||||
"vertices": {
|
||||
"rawPosition": [
|
||||
-54,
|
||||
-49,
|
||||
0,
|
||||
54,
|
||||
-49,
|
||||
0,
|
||||
-54,
|
||||
49,
|
||||
0,
|
||||
54,
|
||||
49,
|
||||
0
|
||||
],
|
||||
"indexes": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
2,
|
||||
1,
|
||||
3
|
||||
],
|
||||
"uv": [
|
||||
0,
|
||||
98,
|
||||
108,
|
||||
98,
|
||||
0,
|
||||
0,
|
||||
108,
|
||||
0
|
||||
],
|
||||
"nuv": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"minPos": [
|
||||
-54,
|
||||
-49,
|
||||
0
|
||||
],
|
||||
"maxPos": [
|
||||
54,
|
||||
49,
|
||||
0
|
||||
]
|
||||
},
|
||||
"isUuid": true,
|
||||
"imageUuidOrDatabaseUri": "34daf5f4-3869-4f71-84c4-d09103701be6@6c48a",
|
||||
"atlasUuid": "",
|
||||
"trimType": "none"
|
||||
},
|
||||
"ver": "1.0.12",
|
||||
"imported": true,
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {}
|
||||
}
|
||||
},
|
||||
"userData": {
|
||||
"type": "sprite-frame",
|
||||
"fixAlphaTransparencyArtifacts": false,
|
||||
"hasAlpha": true,
|
||||
"redirect": "34daf5f4-3869-4f71-84c4-d09103701be6@6c48a"
|
||||
}
|
||||
}
|
||||
{
|
||||
"ver": "1.0.27",
|
||||
"importer": "image",
|
||||
"imported": true,
|
||||
"uuid": "34daf5f4-3869-4f71-84c4-d09103701be6",
|
||||
"files": [
|
||||
".json",
|
||||
".png"
|
||||
],
|
||||
"subMetas": {
|
||||
"6c48a": {
|
||||
"importer": "texture",
|
||||
"uuid": "34daf5f4-3869-4f71-84c4-d09103701be6@6c48a",
|
||||
"displayName": "00042",
|
||||
"id": "6c48a",
|
||||
"name": "texture",
|
||||
"userData": {
|
||||
"wrapModeS": "clamp-to-edge",
|
||||
"wrapModeT": "clamp-to-edge",
|
||||
"imageUuidOrDatabaseUri": "34daf5f4-3869-4f71-84c4-d09103701be6",
|
||||
"isUuid": true,
|
||||
"visible": false,
|
||||
"minfilter": "linear",
|
||||
"magfilter": "linear",
|
||||
"mipfilter": "none",
|
||||
"anisotropy": 0
|
||||
},
|
||||
"ver": "1.0.22",
|
||||
"imported": true,
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {}
|
||||
},
|
||||
"f9941": {
|
||||
"importer": "sprite-frame",
|
||||
"uuid": "34daf5f4-3869-4f71-84c4-d09103701be6@f9941",
|
||||
"displayName": "00042",
|
||||
"id": "f9941",
|
||||
"name": "spriteFrame",
|
||||
"userData": {
|
||||
"trimThreshold": 1,
|
||||
"rotated": false,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"trimX": 0,
|
||||
"trimY": 0,
|
||||
"width": 108,
|
||||
"height": 98,
|
||||
"rawWidth": 108,
|
||||
"rawHeight": 98,
|
||||
"borderTop": 26,
|
||||
"borderBottom": 34,
|
||||
"borderLeft": 30,
|
||||
"borderRight": 31,
|
||||
"packable": true,
|
||||
"pixelsToUnit": 100,
|
||||
"pivotX": 0.5,
|
||||
"pivotY": 0.5,
|
||||
"meshType": 0,
|
||||
"vertices": {
|
||||
"rawPosition": [
|
||||
-54,
|
||||
-49,
|
||||
0,
|
||||
54,
|
||||
-49,
|
||||
0,
|
||||
-54,
|
||||
49,
|
||||
0,
|
||||
54,
|
||||
49,
|
||||
0
|
||||
],
|
||||
"indexes": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
2,
|
||||
1,
|
||||
3
|
||||
],
|
||||
"uv": [
|
||||
0,
|
||||
98,
|
||||
108,
|
||||
98,
|
||||
0,
|
||||
0,
|
||||
108,
|
||||
0
|
||||
],
|
||||
"nuv": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"minPos": [
|
||||
-54,
|
||||
-49,
|
||||
0
|
||||
],
|
||||
"maxPos": [
|
||||
54,
|
||||
49,
|
||||
0
|
||||
]
|
||||
},
|
||||
"isUuid": true,
|
||||
"imageUuidOrDatabaseUri": "34daf5f4-3869-4f71-84c4-d09103701be6@6c48a",
|
||||
"atlasUuid": "",
|
||||
"trimType": "none"
|
||||
},
|
||||
"ver": "1.0.12",
|
||||
"imported": true,
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {}
|
||||
}
|
||||
},
|
||||
"userData": {
|
||||
"type": "sprite-frame",
|
||||
"fixAlphaTransparencyArtifacts": false,
|
||||
"hasAlpha": true,
|
||||
"redirect": "34daf5f4-3869-4f71-84c4-d09103701be6@6c48a"
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.2 MiB |
@@ -45,9 +45,9 @@
|
||||
"offsetY": 0,
|
||||
"trimX": 0,
|
||||
"trimY": 0,
|
||||
"width": 1600,
|
||||
"width": 1280,
|
||||
"height": 720,
|
||||
"rawWidth": 1600,
|
||||
"rawWidth": 1280,
|
||||
"rawHeight": 720,
|
||||
"borderTop": 0,
|
||||
"borderBottom": 0,
|
||||
@@ -60,16 +60,16 @@
|
||||
"meshType": 0,
|
||||
"vertices": {
|
||||
"rawPosition": [
|
||||
-800,
|
||||
-640,
|
||||
-360,
|
||||
0,
|
||||
800,
|
||||
640,
|
||||
-360,
|
||||
0,
|
||||
-800,
|
||||
-640,
|
||||
360,
|
||||
0,
|
||||
800,
|
||||
640,
|
||||
360,
|
||||
0
|
||||
],
|
||||
@@ -84,11 +84,11 @@
|
||||
"uv": [
|
||||
0,
|
||||
720,
|
||||
1600,
|
||||
1280,
|
||||
720,
|
||||
0,
|
||||
0,
|
||||
1600,
|
||||
1280,
|
||||
0
|
||||
],
|
||||
"nuv": [
|
||||
@@ -102,12 +102,12 @@
|
||||
1
|
||||
],
|
||||
"minPos": [
|
||||
-800,
|
||||
-640,
|
||||
-360,
|
||||
0
|
||||
],
|
||||
"maxPos": [
|
||||
800,
|
||||
640,
|
||||
360,
|
||||
0
|
||||
]
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "ad5e871f-b5d8-439a-852b-53d4d1d9f61b",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
+538
@@ -0,0 +1,538 @@
|
||||
[
|
||||
{
|
||||
"__type__": "cc.Prefab",
|
||||
"_name": "OwnerRoomItem",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_native": "",
|
||||
"data": {
|
||||
"__id__": 1
|
||||
},
|
||||
"optimizationPolicy": 0,
|
||||
"persistent": false
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "OwnerRoomItem",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": null,
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 2
|
||||
},
|
||||
{
|
||||
"__id__": 8
|
||||
},
|
||||
{
|
||||
"__id__": 14
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 20
|
||||
},
|
||||
{
|
||||
"__id__": 22
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 24
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 33554432,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": "5dxzP/UG5Fea3nNr557ORP"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "Spirit100",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 3
|
||||
},
|
||||
{
|
||||
"__id__": 5
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 7
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 33554432,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": "9blwBxZBxC4LDYs+VIpNy1"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 2
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 4
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 264,
|
||||
"height": 80
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
},
|
||||
"_id": "90IWdamq1CaK2+fcIY8I4G"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "l3ocOicG3kovqa6+Yow7SQ"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Sprite",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 2
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 6
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
"_dstBlendFactor": 4,
|
||||
"_color": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 255,
|
||||
"g": 255,
|
||||
"b": 255,
|
||||
"a": 255
|
||||
},
|
||||
"_spriteFrame": {
|
||||
"__uuid__": "5f783910-7fd0-4482-a206-8dcb57238c74@f9941",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
"_type": 0,
|
||||
"_fillType": 0,
|
||||
"_sizeMode": 0,
|
||||
"_fillCenter": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"_fillStart": 0,
|
||||
"_fillRange": 0,
|
||||
"_isTrimmedMode": true,
|
||||
"_useGrayscale": false,
|
||||
"_atlas": null,
|
||||
"_id": "e2Q9Z8vy1NVpG+xdDhdD9s"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "VMQNp5XtZ86iR1Z+hSYFOg"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 1
|
||||
},
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "0lhvW5OWHIVi/ncFScuMKQ",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "Spirit102",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_children": [],
|
||||
"_active": false,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 9
|
||||
},
|
||||
{
|
||||
"__id__": 11
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 13
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 39,
|
||||
"y": 2.5,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 33554432,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": "89d19HpytGVox/SvGWJFDr"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 8
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 10
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 14,
|
||||
"height": 19
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
},
|
||||
"_id": "50k3QzjPhHhJYWzNgtUGBk"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "1sIXmawSIfDNdF9tSIh6ig"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Sprite",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 8
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 12
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
"_dstBlendFactor": 4,
|
||||
"_color": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 255,
|
||||
"g": 255,
|
||||
"b": 255,
|
||||
"a": 255
|
||||
},
|
||||
"_spriteFrame": {
|
||||
"__uuid__": "9d49bb70-608e-46d2-8672-5afb78bbd8d7@f9941",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
"_type": 0,
|
||||
"_fillType": 0,
|
||||
"_sizeMode": 0,
|
||||
"_fillCenter": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"_fillStart": 0,
|
||||
"_fillRange": 0,
|
||||
"_isTrimmedMode": true,
|
||||
"_useGrayscale": false,
|
||||
"_atlas": null,
|
||||
"_id": "32xJfAFBFPL57FrZT1Njpw"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "dYeagt3oXF7LLcLyBNmxcQ"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 1
|
||||
},
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "xOj6F8iDH5ro1bLGRx3c5g",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "Digits",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 15
|
||||
},
|
||||
{
|
||||
"__id__": 17
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 19
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 32,
|
||||
"y": 2.5,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 33554432,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": "85eISb6stE0oj1mZHpP6zK"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 14
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 16
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 14,
|
||||
"height": 19
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0,
|
||||
"y": 0.5
|
||||
},
|
||||
"_id": "44soAEXzBJSaDlfZI1MuoB"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "41Li0cKhzYiSg6SbbaoAGg"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Layout",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 14
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 18
|
||||
},
|
||||
"_resizeMode": 1,
|
||||
"_layoutType": 1,
|
||||
"_cellSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 40,
|
||||
"height": 40
|
||||
},
|
||||
"_startAxis": 0,
|
||||
"_paddingLeft": 0,
|
||||
"_paddingRight": 0,
|
||||
"_paddingTop": 0,
|
||||
"_paddingBottom": 0,
|
||||
"_spacingX": 0,
|
||||
"_spacingY": 0,
|
||||
"_verticalDirection": 1,
|
||||
"_horizontalDirection": 0,
|
||||
"_constraint": 0,
|
||||
"_constraintNum": 2,
|
||||
"_affectedByScale": false,
|
||||
"_isAlign": false,
|
||||
"_id": "53I4gVRnlCNo+Wqps6VeDy"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "k0owfG3taNqw+EIUHZ8V9g"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 1
|
||||
},
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "lC5WHe9437aSbTecw6lUiQ",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 21
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 264,
|
||||
"height": 80
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
},
|
||||
"_id": "77ZqoCi+xJ/7Bjxxb0o4bl"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "KLcB/F2HEtr5tYEndV8qbg"
|
||||
},
|
||||
{
|
||||
"__type__": "e40a5gQKgNA6a/yNIkJZlsD",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 23
|
||||
},
|
||||
"digitTemplate": {
|
||||
"__id__": 11
|
||||
},
|
||||
"digitsRoot": {
|
||||
"__id__": 14
|
||||
},
|
||||
"_id": "306fBHxpJHuLQI/kcAnGQS"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "RCmOaA1Wm5nUHFrcVQJpOw"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 1
|
||||
},
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "0oOd9F3D7yWBfO1u4tL3BA",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
}
|
||||
]
|
||||
+2
-2
@@ -2,12 +2,12 @@
|
||||
"ver": "1.1.50",
|
||||
"importer": "prefab",
|
||||
"imported": true,
|
||||
"uuid": "b13f487a-451e-41ce-a810-1fab0d52248f",
|
||||
"uuid": "67117675-9f6d-4def-a67c-db0e9fb19b98",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"syncNodeName": "Layer9_Feedback"
|
||||
"syncNodeName": "OwnerRoomItem"
|
||||
}
|
||||
}
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
[
|
||||
{
|
||||
"__type__": "cc.Prefab",
|
||||
"_name": "PlayerInfoTextItem",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_native": "",
|
||||
"data": {
|
||||
"__id__": 1
|
||||
},
|
||||
"optimizationPolicy": 0,
|
||||
"persistent": false
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "PlayerInfoTextItem",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": null,
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 2
|
||||
},
|
||||
{
|
||||
"__id__": 4
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 6
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 10,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 33554432,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 3
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 371,
|
||||
"height": 22.680000000000003
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0,
|
||||
"y": 1
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "JYB5ndvc0hWZFdTsDPujQg"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Label",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 5
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
"_dstBlendFactor": 4,
|
||||
"_color": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 0,
|
||||
"g": 0,
|
||||
"b": 0,
|
||||
"a": 255
|
||||
},
|
||||
"_string": "资料文字",
|
||||
"_horizontalAlign": 0,
|
||||
"_verticalAlign": 0,
|
||||
"_actualFontSize": 18.975,
|
||||
"_fontSize": 18,
|
||||
"_fontFamily": "Arial",
|
||||
"_lineHeight": 18,
|
||||
"_overflow": 3,
|
||||
"_enableWrapText": true,
|
||||
"_font": null,
|
||||
"_isSystemFontUsed": true,
|
||||
"_spacingX": 0,
|
||||
"_isItalic": false,
|
||||
"_isBold": false,
|
||||
"_isUnderline": false,
|
||||
"_underlineHeight": 2,
|
||||
"_cacheMode": 0,
|
||||
"_enableOutline": false,
|
||||
"_outlineColor": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 0,
|
||||
"g": 0,
|
||||
"b": 0,
|
||||
"a": 255
|
||||
},
|
||||
"_outlineWidth": 2,
|
||||
"_enableShadow": false,
|
||||
"_shadowColor": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 0,
|
||||
"g": 0,
|
||||
"b": 0,
|
||||
"a": 255
|
||||
},
|
||||
"_shadowOffset": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 2,
|
||||
"y": 2
|
||||
},
|
||||
"_shadowBlur": 2,
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "wX+Ww4pBz/TnLcrPEXSuAA"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 1
|
||||
},
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "Pn6tcB1uaJ1EOUf2nLgpzw",
|
||||
"instance": null,
|
||||
"targetOverrides": null
|
||||
}
|
||||
]
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"ver": "1.1.50",
|
||||
"importer": "prefab",
|
||||
"imported": true,
|
||||
"uuid": "9e6dbfc6-26c5-4eac-9cc8-8060a677d5ee",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"syncNodeName": "PlayerInfoTextItem"
|
||||
}
|
||||
}
|
||||
+1921
-1921
File diff suppressed because it is too large
Load Diff
+13
-13
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"ver": "1.1.50",
|
||||
"importer": "prefab",
|
||||
"imported": true,
|
||||
"uuid": "b9f6f400-7b1b-4e51-a841-cc84dcddca82",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"syncNodeName": "TaskItem"
|
||||
}
|
||||
}
|
||||
{
|
||||
"ver": "1.1.50",
|
||||
"importer": "prefab",
|
||||
"imported": true,
|
||||
"uuid": "b9f6f400-7b1b-4e51-a841-cc84dcddca82",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"syncNodeName": "TaskItem"
|
||||
}
|
||||
}
|
||||
+1216
File diff suppressed because it is too large
Load Diff
+13
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"ver": "1.1.50",
|
||||
"importer": "prefab",
|
||||
"imported": true,
|
||||
"uuid": "5925d1d2-f4b2-44c1-a37a-fb2bcdcf664e",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"syncNodeName": "VipRankItem"
|
||||
}
|
||||
}
|
||||
+1327
-1327
File diff suppressed because it is too large
Load Diff
+13
-13
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"ver": "1.1.50",
|
||||
"importer": "prefab",
|
||||
"imported": true,
|
||||
"uuid": "fed2e71c-22ac-4e48-a42a-68e48a1929a7",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"syncNodeName": "VipRoomItem1"
|
||||
}
|
||||
}
|
||||
{
|
||||
"ver": "1.1.50",
|
||||
"importer": "prefab",
|
||||
"imported": true,
|
||||
"uuid": "fed2e71c-22ac-4e48-a42a-68e48a1929a7",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"syncNodeName": "VipRoomItem1"
|
||||
}
|
||||
}
|
||||
+566
-566
File diff suppressed because it is too large
Load Diff
+13
-13
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"ver": "1.1.50",
|
||||
"importer": "prefab",
|
||||
"imported": true,
|
||||
"uuid": "8cb43df4-d8fa-4b3c-ab01-497b23c68435",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"syncNodeName": "VipRoomItem2"
|
||||
}
|
||||
}
|
||||
{
|
||||
"ver": "1.1.50",
|
||||
"importer": "prefab",
|
||||
"imported": true,
|
||||
"uuid": "8cb43df4-d8fa-4b3c-ab01-497b23c68435",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"syncNodeName": "VipRoomItem2"
|
||||
}
|
||||
}
|
||||
+1888
-674
File diff suppressed because it is too large
Load Diff
+692
-125
File diff suppressed because it is too large
Load Diff
+473
-1584
File diff suppressed because it is too large
Load Diff
+1029
-502
File diff suppressed because it is too large
Load Diff
+863
-1604
File diff suppressed because it is too large
Load Diff
+2084
-1201
File diff suppressed because it is too large
Load Diff
+201
-202
File diff suppressed because it is too large
Load Diff
+376
-187
File diff suppressed because it is too large
Load Diff
+41
-11
@@ -25,14 +25,14 @@
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 24
|
||||
"__id__": 25
|
||||
},
|
||||
{
|
||||
"__id__": 26
|
||||
"__id__": 27
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 28
|
||||
"__id__": 29
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -82,14 +82,14 @@
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 19
|
||||
"__id__": 20
|
||||
},
|
||||
{
|
||||
"__id__": 21
|
||||
"__id__": 22
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 23
|
||||
"__id__": 24
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -186,6 +186,7 @@
|
||||
"__id__": 5
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 1600,
|
||||
"height": 720
|
||||
},
|
||||
@@ -313,10 +314,13 @@
|
||||
},
|
||||
{
|
||||
"__id__": 16
|
||||
},
|
||||
{
|
||||
"__id__": 18
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 18
|
||||
"__id__": 19
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -360,6 +364,7 @@
|
||||
"__id__": 13
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 150,
|
||||
"height": 150
|
||||
},
|
||||
@@ -455,6 +460,29 @@
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "3wlBO4ciKrxeTYsQdlQvBg"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Animation",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 11
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": null,
|
||||
"playOnLoad": false,
|
||||
"_clips": [
|
||||
{
|
||||
"__uuid__": "987771c5-7f29-487a-b61a-476981cc9216",
|
||||
"__expectedType__": "cc.AnimationClip"
|
||||
}
|
||||
],
|
||||
"_defaultClip": {
|
||||
"__uuid__": "987771c5-7f29-487a-b61a-476981cc9216",
|
||||
"__expectedType__": "cc.AnimationClip"
|
||||
},
|
||||
"_id": "1b3VMKLpRFPKzD9o608yTZ"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
@@ -478,9 +506,10 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 20
|
||||
"__id__": 21
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 1600,
|
||||
"height": 720
|
||||
},
|
||||
@@ -505,7 +534,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 22
|
||||
"__id__": 23
|
||||
},
|
||||
"_alignFlags": 45,
|
||||
"_target": null,
|
||||
@@ -554,9 +583,10 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 25
|
||||
"__id__": 26
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 1600,
|
||||
"height": 720
|
||||
},
|
||||
@@ -581,7 +611,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 27
|
||||
"__id__": 28
|
||||
},
|
||||
"_alignFlags": 45,
|
||||
"_target": null,
|
||||
|
||||
+445
-129
File diff suppressed because it is too large
Load Diff
-6762
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -1,7 +1,7 @@
|
||||
[
|
||||
{
|
||||
"__type__": "cc.Prefab",
|
||||
"_name": "Login_Layer",
|
||||
"_name": "Login",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_native": "",
|
||||
@@ -13,7 +13,7 @@
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "Login_Layer",
|
||||
"_name": "Login",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": null,
|
||||
+14
-14
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"ver": "1.1.50",
|
||||
"importer": "prefab",
|
||||
"imported": true,
|
||||
"uuid": "b209cefb-b381-4c7c-b765-94a5ab75a89c",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"syncNodeName": "Login_Layer",
|
||||
"hasIcon": false
|
||||
}
|
||||
}
|
||||
{
|
||||
"ver": "1.1.50",
|
||||
"importer": "prefab",
|
||||
"imported": true,
|
||||
"uuid": "b209cefb-b381-4c7c-b765-94a5ab75a89c",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"syncNodeName": "Login",
|
||||
"hasIcon": false
|
||||
}
|
||||
}
|
||||
+3649
-2492
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -8,6 +8,6 @@
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"syncNodeName": "Layer4_MainMenu"
|
||||
"syncNodeName": "MainMenu"
|
||||
}
|
||||
}
|
||||
+3793
File diff suppressed because it is too large
Load Diff
+13
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"ver": "1.1.50",
|
||||
"importer": "prefab",
|
||||
"imported": true,
|
||||
"uuid": "e7a05100-7de4-4935-896e-a59061377b87",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"syncNodeName": "NoticeBoard"
|
||||
}
|
||||
}
|
||||
+1002
File diff suppressed because it is too large
Load Diff
+13
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"ver": "1.1.50",
|
||||
"importer": "prefab",
|
||||
"imported": true,
|
||||
"uuid": "260ea2dc-696c-4a65-a2fd-109dd794bb45",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"syncNodeName": "OwnedRoomList"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+13
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"ver": "1.1.50",
|
||||
"importer": "prefab",
|
||||
"imported": true,
|
||||
"uuid": "95ee9b79-8c30-4f54-a2cf-19d02083cd28",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"syncNodeName": "QrCode"
|
||||
}
|
||||
}
|
||||
+1540
File diff suppressed because it is too large
Load Diff
+13
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"ver": "1.1.50",
|
||||
"importer": "prefab",
|
||||
"imported": true,
|
||||
"uuid": "ada34847-93c7-48f2-bb0a-5c1db4b112f6",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"syncNodeName": "RedeemCode"
|
||||
}
|
||||
}
|
||||
+3
-241
@@ -1,7 +1,7 @@
|
||||
[
|
||||
{
|
||||
"__type__": "cc.Prefab",
|
||||
"_name": "Layer1_Logo",
|
||||
"_name": "ResourceLoading",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_native": "",
|
||||
@@ -13,22 +13,19 @@
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "Layer1_Logo",
|
||||
"_name": "ResourceLoading",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": null,
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 2
|
||||
},
|
||||
{
|
||||
"__id__": 50
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [],
|
||||
"_prefab": {
|
||||
"__id__": 60
|
||||
"__id__": 50
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -1130,241 +1127,6 @@
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "group-32",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 51
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [],
|
||||
"_prefab": {
|
||||
"__id__": 59
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 33554432,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "Spirit221",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 50
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 52
|
||||
},
|
||||
{
|
||||
"__id__": 54
|
||||
},
|
||||
{
|
||||
"__id__": 56
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 58
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": -1612.5,
|
||||
"y": 1099.952,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 33554432,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 51
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 53
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 63,
|
||||
"height": 63
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "Y8tq/cDuhVvttPn+x9+bww"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Sprite",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 51
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 55
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
"_dstBlendFactor": 4,
|
||||
"_color": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 255,
|
||||
"g": 255,
|
||||
"b": 255,
|
||||
"a": 255
|
||||
},
|
||||
"_spriteFrame": {
|
||||
"__uuid__": "974bf7e8-69fd-49ff-95fa-ed7ddb8d6a14@f9941",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
"_type": 0,
|
||||
"_fillType": 0,
|
||||
"_sizeMode": 0,
|
||||
"_fillCenter": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"_fillStart": 0,
|
||||
"_fillRange": 0,
|
||||
"_isTrimmedMode": true,
|
||||
"_useGrayscale": false,
|
||||
"_atlas": null,
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "t+vRkZQc/kDtmwdvT1STKA"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Widget",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 51
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 57
|
||||
},
|
||||
"_alignFlags": 16,
|
||||
"_target": null,
|
||||
"_left": 0,
|
||||
"_right": 0,
|
||||
"_top": 0,
|
||||
"_bottom": 0,
|
||||
"_horizontalCenter": -1612.5,
|
||||
"_verticalCenter": 0,
|
||||
"_isAbsLeft": true,
|
||||
"_isAbsRight": true,
|
||||
"_isAbsTop": true,
|
||||
"_isAbsBottom": true,
|
||||
"_isAbsHorizontalCenter": true,
|
||||
"_isAbsVerticalCenter": true,
|
||||
"_originalWidth": 0,
|
||||
"_originalHeight": 0,
|
||||
"_alignMode": 2,
|
||||
"_lockFlags": 0,
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "K2+PudUKVOrBYR09Lp3sAA"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 1
|
||||
},
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "J1IKV6k3EB5NkpQVaZVxiA",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 1
|
||||
},
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "JccA+NmPM7lVqq5kAuMdjQ",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
+1
-1
@@ -8,6 +8,6 @@
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"syncNodeName": "Layer1_Logo"
|
||||
"syncNodeName": "ResourceLoading"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "bfaf200b-f8f5-4fcb-b9e3-7e4e831177ce",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "c593ddf6-0173-48e8-96cb-dab1273e43d8",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import {_decorator,Button,Label,Toggle,ToggleContainer} from 'cc';
|
||||
import {CreateRoomPage} from '../../scripts/platform-login/CreateRoomPage.ts';
|
||||
import type {CreateRoomPageContext} from '../../framework/sdk/contracts/create-room-page.ts';
|
||||
import {TemplateRoomRules} from './template-room-rules.ts';
|
||||
const {ccclass,property}=_decorator;
|
||||
@ccclass('TemplateCreateRoomView')
|
||||
export class TemplateCreateRoomView extends CreateRoomPage {
|
||||
@property([ToggleContainer]) groups:ToggleContainer[]=[];
|
||||
@property([Toggle]) options:Toggle[]=[];
|
||||
@property([Label]) optionLabels:Label[]=[];
|
||||
@property(Button) submitButton:Button|null=null;
|
||||
@property([Button]) cancelButtons:Button[]=[];
|
||||
private readonly rules=new TemplateRoomRules();
|
||||
private context:CreateRoomPageContext|null=null;
|
||||
private busy=false;
|
||||
private releases:Array<()=>void>=[];
|
||||
open(context:CreateRoomPageContext):void{
|
||||
this.close();
|
||||
if(this.groups.length!==2||this.options.length!==4||this.optionLabels.length!==4||!this.submitButton||this.cancelButtons.length!==2)throw Error('TemplateCreateRoomView requires serialized controls');
|
||||
this.rules.open(context.previousRoomtype);this.context=context;
|
||||
// A saved source index may have no matching option; initialize that state explicitly.
|
||||
this.groups.forEach(group=>group.allowSwitchOff=true);
|
||||
this.optionLabels.forEach((label,i)=>label.string=this.rules.labels[i]);
|
||||
this.options.forEach((toggle,i)=>{
|
||||
toggle.isChecked=this.rules.selection[Math.floor(i/2)]===i%2;
|
||||
const changed=()=>{if(!this.busy&&toggle.isChecked)this.rules.select(Math.floor(i/2) as 0|1,i%2);};
|
||||
toggle.node.on(Toggle.EventType.TOGGLE,changed);this.releases.push(()=>toggle.node.off(Toggle.EventType.TOGGLE,changed));
|
||||
});
|
||||
this.groups.forEach(group=>group.allowSwitchOff=false);
|
||||
const bind=(button:Button,action:()=>void)=>{const fn=()=>{if(!this.busy&&this.context)try{action();}catch(error){context.reportError(error instanceof Error?error.message:String(error));}};
|
||||
button.node.on(Button.EventType.CLICK,fn);this.releases.push(()=>button.node.off(Button.EventType.CLICK,fn));};
|
||||
bind(this.submitButton,()=>context.submit(this.rules.build()));
|
||||
this.cancelButtons.forEach(button=>{const cancel=()=>context.cancel();button.node.on(Button.EventType.CLICK,cancel);this.releases.push(()=>button.node.off(Button.EventType.CLICK,cancel));});this.setBusy(false);
|
||||
}
|
||||
setBusy(busy:boolean):void{this.busy=busy;this.options.forEach(toggle=>toggle.interactable=!busy);if(this.submitButton)this.submitButton.interactable=!busy;}
|
||||
close():void{for(const release of this.releases.splice(0))release();this.context=null;}
|
||||
onDestroy():void{this.close();}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "ebbef7d9-7bda-4c61-9b0a-1ee56e7817fb",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"ver": "1.1.50",
|
||||
"importer": "prefab",
|
||||
"imported": true,
|
||||
"uuid": "f4582832-23e2-4981-be26-08dfe832aa96",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"syncNodeName": "TemplateRoom"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import {_decorator,Button,Component,Label} from 'cc';
|
||||
import type {PlatformGameSnapshot} from '../../framework/sdk/contracts/index.ts';
|
||||
import type {TemplateRoomActions,TemplateRoomPresentation} from './template-game.ts';
|
||||
const {ccclass,property}=_decorator;
|
||||
/** Template lobby-to-room handoff surface; concrete subgames replace this presentation. */
|
||||
@ccclass('TemplateRoomView')
|
||||
export class TemplateRoomView extends Component implements TemplateRoomPresentation {
|
||||
@property(Label) roomLabel:Label|null=null;
|
||||
@property(Label) playersLabel:Label|null=null;
|
||||
@property(Button) prepareButton:Button|null=null;
|
||||
@property(Button) exitButton:Button|null=null;
|
||||
private actions:TemplateRoomActions|null=null;
|
||||
private bound=false;
|
||||
private pending:'prepare'|'exit'|null=null;
|
||||
private timer:ReturnType<typeof setTimeout>|null=null;
|
||||
private snapshot:PlatformGameSnapshot|null=null;
|
||||
private readonly prepare=()=>this.invoke('prepare');
|
||||
private readonly exit=()=>this.invoke('exit');
|
||||
render(snapshot:PlatformGameSnapshot,actions:TemplateRoomActions):void{
|
||||
if(!this.roomLabel||!this.playersLabel||!this.prepareButton||!this.exitButton)throw Error('TemplateRoomView missing controls');
|
||||
this.actions=actions;this.snapshot=snapshot;
|
||||
if(!this.bound){this.prepareButton.node.on(Button.EventType.CLICK,this.prepare);this.exitButton.node.on(Button.EventType.CLICK,this.exit);this.bound=true;}
|
||||
this.roomLabel.string=`房间号:${snapshot.room.roomcode}`;
|
||||
this.playersLabel.string=snapshot.seats.map(p=>`${p.seat+1} ${p.nickname}${p.playerId===snapshot.self.playerId?'(我)':''} ${!p.online?'离线':p.ready?'已准备':'未准备'}`).join('\n');
|
||||
const self=snapshot.seats.find(p=>p.playerId===snapshot.self.playerId);
|
||||
if(self?.ready||snapshot.connection.phase!=='logged-in')this.clearPending();
|
||||
this.prepareButton.interactable=this.pending===null&&snapshot.connection.phase==='logged-in'&&snapshot.room.stage===0&&!self?.ready;
|
||||
this.exitButton.interactable=this.pending===null&&snapshot.connection.phase==='logged-in';
|
||||
}
|
||||
private invoke(kind:'prepare'|'exit'):void{
|
||||
if(!this.actions||this.pending)return;
|
||||
try{this.actions[kind]();this.pending=kind;this.prepareButton!.interactable=false;this.exitButton!.interactable=false;
|
||||
this.timer=setTimeout(()=>{this.clearPending();if(this.snapshot&&this.actions)this.render(this.snapshot,this.actions);this.node.emit('room-action-timeout');},15000);
|
||||
}catch(error){this.node.emit('room-action-error',error);}
|
||||
}
|
||||
private clearPending():void{if(this.timer!==null)clearTimeout(this.timer);this.timer=null;this.pending=null;}
|
||||
close():void{this.clearPending();if(this.bound){this.prepareButton?.node.off(Button.EventType.CLICK,this.prepare);this.exitButton?.node.off(Button.EventType.CLICK,this.exit);}this.bound=false;this.actions=null;}
|
||||
onDestroy():void{this.close();}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "e0dc1f88-a719-4b19-9827-2cf9e4452be9",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import type {GameEntry,GameHost,GameModule,PlatformGameSnapshot} from '../../framework/sdk/contracts/index.ts';
|
||||
import {TemplateRoomRules} from './template-room-rules.ts';
|
||||
export interface TemplateRoomActions {prepare():void;exit():void;}
|
||||
export interface TemplateRoomPresentation {render(snapshot:PlatformGameSnapshot,actions:TemplateRoomActions):void;close():void;}
|
||||
/** Template hooks are intentionally empty, matching 02_SubGame_Input.js. No gameplay is inferred. */
|
||||
export function createTemplateGame(gameId:string|number){
|
||||
let active:TemplateModule|null=null;
|
||||
class TemplateModule implements GameModule {
|
||||
private host:GameHost|null=null;
|
||||
private unsubscribe:(()=>void)|null=null;
|
||||
private view:TemplateRoomPresentation|null=null;
|
||||
restored:unknown=null;
|
||||
attach(host:GameHost):void{
|
||||
if(this.host||active)throw Error('Template module already attached');
|
||||
this.host=host;active=this;this.unsubscribe=host.subscribe(()=>this.render());
|
||||
}
|
||||
mount(view:TemplateRoomPresentation):void{this.view?.close();this.view=view;this.render();}
|
||||
private requireHost():GameHost{if(!this.host)throw Error('Template room session disposed');return this.host;}
|
||||
private render():void{this.view?.render(this.requireHost().getSnapshot(),{prepare:()=>this.requireHost().execute({type:'room.prepare'}),exit:()=>this.requireHost().execute({type:'room.exit'})});}
|
||||
handlePlatformEvent():void{this.render();}
|
||||
handleGameMessage():void{/* Original template _ReceiveData hook is empty. */}
|
||||
restore(deskinfo:unknown):void{this.requireHost();this.restored=deskinfo;}
|
||||
dispose():void{this.unsubscribe?.();this.unsubscribe=null;this.view?.close();this.view=null;this.host=null;this.restored=null;if(active===this)active=null;}
|
||||
}
|
||||
const rules=new TemplateRoomRules();
|
||||
const entry:GameEntry={key:'Game_Surface_3',gameId,route:'Game_Surface_3',resolveSeatCount:roomtype=>rules.seatCount(roomtype),createModule:()=>new TemplateModule()};
|
||||
return {entry,mount(view:TemplateRoomPresentation){if(!active)throw Error('Template room not attached');active.mount(view);},
|
||||
get restoredSnapshot():unknown{return active?.restored;}};
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "c6a24a57-c9c3-4eda-9f82-73f1990703fb",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/** Source: 01_SubGame_modify.js. Options are presentation state, not a rule-array encoder. */
|
||||
export class TemplateRoomRules {
|
||||
readonly labels=['10局(房卡X1)','20局(房卡X2)','房主扣卡','每人扣卡'] as const;
|
||||
private selected:[number,number]=[0,0];
|
||||
get selection():readonly number[]{return [...this.selected];}
|
||||
open(previous:unknown=null):void{
|
||||
if(previous===null){this.selected=[0,0];return;}
|
||||
if(!Array.isArray(previous)||!Number.isInteger(previous[0])||!Number.isInteger(previous[1]))throw Error('Invalid template saved roomtype');
|
||||
this.selected=[previous[0]-1,previous[1]-1];
|
||||
}
|
||||
select(group:0|1,index:number):void{
|
||||
if(!Number.isInteger(index)||index<0||index>1)throw Error('Invalid template option');
|
||||
this.selected[group]=index;
|
||||
}
|
||||
build():unknown[]{return [1,4,1,2,2,[1,1,[1,2000,10],null,null,1],[1,0,5]];}
|
||||
seatCount(roomtype:readonly unknown[]):number{
|
||||
if(!Array.isArray(roomtype))throw Error('Template roomtype must be an array');
|
||||
// Desk.Create uses Game_Config.Max.PlayerCnt because the template defines no getMaxPlayerCount hook.
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "0cad77f9-c126-4532-9ffb-70e650008a34",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
+1499
-1499
File diff suppressed because it is too large
Load Diff
@@ -52,7 +52,7 @@
|
||||
},
|
||||
"autoReleaseAssets": false,
|
||||
"_globals": {
|
||||
"__id__": 5
|
||||
"__id__": 6
|
||||
},
|
||||
"_id": "1c10c2ee-adc2-496f-9005-d3e0e6ebbb56"
|
||||
},
|
||||
@@ -72,6 +72,9 @@
|
||||
},
|
||||
{
|
||||
"__id__": 4
|
||||
},
|
||||
{
|
||||
"__id__": 5
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
@@ -182,8 +185,24 @@
|
||||
"__uuid__": "e1b53f04-3e2f-4127-9a3f-f629bed6905e",
|
||||
"__expectedType__": "cc.Prefab"
|
||||
},
|
||||
"notices": {
|
||||
"__uuid__": "b13f487a-451e-41ce-a810-1fab0d52248f",
|
||||
"redeemCode": {
|
||||
"__uuid__": "ada34847-93c7-48f2-bb0a-5c1db4b112f6",
|
||||
"__expectedType__": "cc.Prefab"
|
||||
},
|
||||
"qrCode": {
|
||||
"__uuid__": "95ee9b79-8c30-4f54-a2cf-19d02083cd28",
|
||||
"__expectedType__": "cc.Prefab"
|
||||
},
|
||||
"noticeBoard": {
|
||||
"__uuid__": "e7a05100-7de4-4935-896e-a59061377b87",
|
||||
"__expectedType__": "cc.Prefab"
|
||||
},
|
||||
"ownedRoomList": {
|
||||
"__uuid__": "260ea2dc-696c-4a65-a2fd-109dd794bb45",
|
||||
"__expectedType__": "cc.Prefab"
|
||||
},
|
||||
"invite": {
|
||||
"__uuid__": "a3db6daa-a98e-4a73-b0f7-cd122bdb11c5",
|
||||
"__expectedType__": "cc.Prefab"
|
||||
},
|
||||
"profile": {
|
||||
@@ -206,6 +225,100 @@
|
||||
"__uuid__": "f4af0cc2-2514-4bd5-a0de-7a3dbb32438b",
|
||||
"__expectedType__": "cc.Prefab"
|
||||
},
|
||||
"rank": {
|
||||
"__uuid__": "3eafe9c2-475b-4c81-b71a-37ffa2717c71",
|
||||
"__expectedType__": "cc.Prefab"
|
||||
},
|
||||
"rankItem": {
|
||||
"__uuid__": "5925d1d2-f4b2-44c1-a37a-fb2bcdcf664e",
|
||||
"__expectedType__": "cc.Prefab"
|
||||
},
|
||||
"rankMedals": [
|
||||
{
|
||||
"__uuid__": "fb86ab44-8140-4d8e-82f1-84ae4b8f3216@f9941",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "10653735-f46a-4e0f-906f-dacb093dad23@f9941",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "87749e95-72aa-435e-93bb-1f41553922f0@f9941",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
}
|
||||
],
|
||||
"rankTitle": {
|
||||
"__uuid__": "ecb18612-2066-42d2-8fa0-c0161124cf1e@f9941",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
"vipRankTitle": {
|
||||
"__uuid__": "97b4bd7f-8716-497f-a375-d9c841cf34e9@f9941",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
"ownedRoomDigits": [
|
||||
{
|
||||
"__uuid__": "9d49bb70-608e-46d2-8672-5afb78bbd8d7@f9941",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "e71157d8-1261-4917-bac1-1b48ab5b6ab9@f9941",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "bade804f-d16a-46b6-8746-600ff6b93cce@f9941",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "dae7297a-cb1a-49d7-98a9-a3008a8afc7a@f9941",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "f1365825-aa47-4efe-936c-81284b0d5e1f@f9941",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "b4d68ae8-0799-47c1-8b30-26b2daed2d59@f9941",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "be16e577-8add-4c32-a56b-948a52e0180b@f9941",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "2fd355d7-493d-474c-b753-676c7ee9e50d@f9941",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "3c1709f3-a24f-4a9e-814c-04bc1441da55@f9941",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "4dd36de5-fadb-4852-8334-e9b5834a3957@f9941",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
}
|
||||
],
|
||||
"rankSymbols": [
|
||||
{
|
||||
"__uuid__": "6b9ad554-bf4a-4493-82dd-92b4328cb8a4@f9941",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "2f1e97ec-1bad-4731-9563-d90ae5b93111@f9941",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "a1016858-72af-4a2c-8d8e-7098eb82a310@f9941",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "a6986c72-2bef-4f50-b215-86d4de943b68@f9941",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "df99cc0a-0b2a-4fdd-b271-7932b2bea237@f9941",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
}
|
||||
],
|
||||
"digits": [
|
||||
{
|
||||
"__uuid__": "fc6e9a8e-0a6d-4555-bc3e-88fd427b9f3a@f9941",
|
||||
@@ -272,32 +385,56 @@
|
||||
],
|
||||
"_id": "abZXhqPblEq57+36N2j8xH"
|
||||
},
|
||||
{
|
||||
"__type__": "ec19eRwzzZA6LQg5DFOX78H",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 2
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": null,
|
||||
"createRoom": {
|
||||
"__uuid__": "584dbb85-4acc-4bea-9d9b-e0cbeb8741da",
|
||||
"__expectedType__": "cc.Prefab"
|
||||
},
|
||||
"room": {
|
||||
"__uuid__": "f4582832-23e2-4981-be26-08dfe832aa96",
|
||||
"__expectedType__": "cc.Prefab"
|
||||
},
|
||||
"roomScene": {
|
||||
"__uuid__": "18f06ada-9d36-485c-9f44-556d6980ca7a",
|
||||
"__expectedType__": "cc.SceneAsset"
|
||||
},
|
||||
"_id": "c2sXHnthJJQoYDj00RAeBa"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.SceneGlobals",
|
||||
"ambient": {
|
||||
"__id__": 6
|
||||
},
|
||||
"shadows": {
|
||||
"__id__": 7
|
||||
},
|
||||
"_skybox": {
|
||||
"shadows": {
|
||||
"__id__": 8
|
||||
},
|
||||
"fog": {
|
||||
"_skybox": {
|
||||
"__id__": 9
|
||||
},
|
||||
"octree": {
|
||||
"fog": {
|
||||
"__id__": 10
|
||||
},
|
||||
"skin": {
|
||||
"octree": {
|
||||
"__id__": 11
|
||||
},
|
||||
"lightProbeInfo": {
|
||||
"skin": {
|
||||
"__id__": 12
|
||||
},
|
||||
"postSettings": {
|
||||
"lightProbeInfo": {
|
||||
"__id__": 13
|
||||
},
|
||||
"postSettings": {
|
||||
"__id__": 14
|
||||
},
|
||||
"bakedWithStationaryMainLight": false,
|
||||
"bakedWithHighpLightmap": false
|
||||
},
|
||||
|
||||
@@ -0,0 +1,449 @@
|
||||
[
|
||||
{
|
||||
"__type__": "cc.SceneAsset",
|
||||
"_name": "TemplateRoom",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_native": "",
|
||||
"scene": {
|
||||
"__id__": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Scene",
|
||||
"_name": "TemplateRoom",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": null,
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 2
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [],
|
||||
"_prefab": null,
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 1073741824,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"autoReleaseAssets": false,
|
||||
"_globals": {
|
||||
"__id__": 8
|
||||
},
|
||||
"_id": "18f06ada-9d36-485c-9f44-556d6980ca7a"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "Canvas",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 3
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 5
|
||||
},
|
||||
{
|
||||
"__id__": 6
|
||||
},
|
||||
{
|
||||
"__id__": 7
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 800,
|
||||
"y": 360.00000000000006,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 33554432,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": "1cgbnsENBDoLhHJwvyJnKB"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "Camera",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 2
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 4
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 1000
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 1073741824,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": "9atEuWMGdBq70ouQQ4tRnO"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Camera",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 3
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": null,
|
||||
"_projection": 0,
|
||||
"_priority": 0,
|
||||
"_fov": 45,
|
||||
"_fovAxis": 0,
|
||||
"_orthoHeight": 360,
|
||||
"_near": 0,
|
||||
"_far": 2000,
|
||||
"_color": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 0,
|
||||
"g": 0,
|
||||
"b": 0,
|
||||
"a": 255
|
||||
},
|
||||
"_depth": 1,
|
||||
"_stencil": 0,
|
||||
"_clearFlags": 7,
|
||||
"_rect": {
|
||||
"__type__": "cc.Rect",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 1,
|
||||
"height": 1
|
||||
},
|
||||
"_aperture": 19,
|
||||
"_shutter": 7,
|
||||
"_iso": 0,
|
||||
"_screenScale": 1,
|
||||
"_visibility": 1108344832,
|
||||
"_targetTexture": null,
|
||||
"_postProcess": null,
|
||||
"_usePostProcess": false,
|
||||
"_cameraType": -1,
|
||||
"_trackingType": 0,
|
||||
"_id": "4780+rx15BKqkTCexT+Fi8"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 2
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": null,
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 1600,
|
||||
"height": 720
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
},
|
||||
"_id": "9cHcVMc5JHVJoaZqnwhdaW"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Canvas",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 2
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": null,
|
||||
"_cameraComponent": {
|
||||
"__id__": 4
|
||||
},
|
||||
"_alignCanvasWithScreen": true,
|
||||
"_id": "f7IXBbP6ZNjqIM4Q5/I3dI"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Widget",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 2
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": null,
|
||||
"_alignFlags": 45,
|
||||
"_target": null,
|
||||
"_left": 0,
|
||||
"_right": 0,
|
||||
"_top": 5.684341886080802e-14,
|
||||
"_bottom": 5.684341886080802e-14,
|
||||
"_horizontalCenter": 0,
|
||||
"_verticalCenter": 0,
|
||||
"_isAbsLeft": true,
|
||||
"_isAbsRight": true,
|
||||
"_isAbsTop": true,
|
||||
"_isAbsBottom": true,
|
||||
"_isAbsHorizontalCenter": true,
|
||||
"_isAbsVerticalCenter": true,
|
||||
"_originalWidth": 0,
|
||||
"_originalHeight": 0,
|
||||
"_alignMode": 2,
|
||||
"_lockFlags": 0,
|
||||
"_id": "9fI6665VZLG5Uuoch5UpgC"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.SceneGlobals",
|
||||
"ambient": {
|
||||
"__id__": 9
|
||||
},
|
||||
"shadows": {
|
||||
"__id__": 10
|
||||
},
|
||||
"_skybox": {
|
||||
"__id__": 11
|
||||
},
|
||||
"fog": {
|
||||
"__id__": 12
|
||||
},
|
||||
"octree": {
|
||||
"__id__": 13
|
||||
},
|
||||
"skin": {
|
||||
"__id__": 14
|
||||
},
|
||||
"lightProbeInfo": {
|
||||
"__id__": 15
|
||||
},
|
||||
"postSettings": {
|
||||
"__id__": 16
|
||||
},
|
||||
"bakedWithStationaryMainLight": false,
|
||||
"bakedWithHighpLightmap": false
|
||||
},
|
||||
{
|
||||
"__type__": "cc.AmbientInfo",
|
||||
"_skyColorHDR": {
|
||||
"__type__": "cc.Vec4",
|
||||
"x": 0.2,
|
||||
"y": 0.5,
|
||||
"z": 0.8,
|
||||
"w": 1
|
||||
},
|
||||
"_skyColor": {
|
||||
"__type__": "cc.Vec4",
|
||||
"x": 0.2,
|
||||
"y": 0.5,
|
||||
"z": 0.8,
|
||||
"w": 1
|
||||
},
|
||||
"_skyIllumHDR": 20000,
|
||||
"_skyIllum": 20000,
|
||||
"_groundAlbedoHDR": {
|
||||
"__type__": "cc.Vec4",
|
||||
"x": 0.2,
|
||||
"y": 0.2,
|
||||
"z": 0.2,
|
||||
"w": 1
|
||||
},
|
||||
"_groundAlbedo": {
|
||||
"__type__": "cc.Vec4",
|
||||
"x": 0.2,
|
||||
"y": 0.2,
|
||||
"z": 0.2,
|
||||
"w": 1
|
||||
},
|
||||
"_skyColorLDR": {
|
||||
"__type__": "cc.Vec4",
|
||||
"x": 0.2,
|
||||
"y": 0.5,
|
||||
"z": 0.8,
|
||||
"w": 1
|
||||
},
|
||||
"_skyIllumLDR": 20000,
|
||||
"_groundAlbedoLDR": {
|
||||
"__type__": "cc.Vec4",
|
||||
"x": 0.2,
|
||||
"y": 0.2,
|
||||
"z": 0.2,
|
||||
"w": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.ShadowsInfo",
|
||||
"_enabled": false,
|
||||
"_type": 0,
|
||||
"_normal": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 1,
|
||||
"z": 0
|
||||
},
|
||||
"_distance": 0,
|
||||
"_planeBias": 1,
|
||||
"_shadowColor": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 0,
|
||||
"g": 0,
|
||||
"b": 0,
|
||||
"a": 76
|
||||
},
|
||||
"_maxReceived": 4,
|
||||
"_size": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 1024,
|
||||
"y": 1024
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.SkyboxInfo",
|
||||
"_envLightingType": 0,
|
||||
"_envmapHDR": null,
|
||||
"_envmap": null,
|
||||
"_envmapLDR": null,
|
||||
"_diffuseMapHDR": null,
|
||||
"_diffuseMapLDR": null,
|
||||
"_enabled": false,
|
||||
"_useHDR": true,
|
||||
"_editableMaterial": null,
|
||||
"_reflectionHDR": null,
|
||||
"_reflectionLDR": null,
|
||||
"_rotationAngle": 0
|
||||
},
|
||||
{
|
||||
"__type__": "cc.FogInfo",
|
||||
"_type": 0,
|
||||
"_fogColor": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 200,
|
||||
"g": 200,
|
||||
"b": 200,
|
||||
"a": 255
|
||||
},
|
||||
"_enabled": false,
|
||||
"_fogDensity": 0.3,
|
||||
"_fogStart": 0.5,
|
||||
"_fogEnd": 300,
|
||||
"_fogAtten": 5,
|
||||
"_fogTop": 1.5,
|
||||
"_fogRange": 1.2,
|
||||
"_accurate": false
|
||||
},
|
||||
{
|
||||
"__type__": "cc.OctreeInfo",
|
||||
"_enabled": false,
|
||||
"_minPos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": -1024,
|
||||
"y": -1024,
|
||||
"z": -1024
|
||||
},
|
||||
"_maxPos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1024,
|
||||
"y": 1024,
|
||||
"z": 1024
|
||||
},
|
||||
"_depth": 8
|
||||
},
|
||||
{
|
||||
"__type__": "cc.SkinInfo",
|
||||
"_enabled": true,
|
||||
"_blurRadius": 0.01,
|
||||
"_sssIntensity": 3
|
||||
},
|
||||
{
|
||||
"__type__": "cc.LightProbeInfo",
|
||||
"_giScale": 1,
|
||||
"_giSamples": 1024,
|
||||
"_bounces": 2,
|
||||
"_reduceRinging": 0,
|
||||
"_showProbe": true,
|
||||
"_showWireframe": true,
|
||||
"_showConvex": false,
|
||||
"_data": null,
|
||||
"_lightProbeSphereVolume": 1
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PostSettingsInfo",
|
||||
"_toneMappingType": 0
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"ver": "1.1.50",
|
||||
"importer": "scene",
|
||||
"imported": true,
|
||||
"uuid": "18f06ada-9d36-485c-9f44-556d6980ca7a",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { _decorator, Component, Label, Mask, Node, UITransform, Widget } from 'cc';
|
||||
import { announcementMotion } from '../../framework/presentation/lobby-model.ts';
|
||||
import { requireNode } from './node-bindings.ts';
|
||||
const {ccclass}=_decorator;
|
||||
function required<T>(value:T|null,name:string):T {if(value===null)throw Error(`Announcement missing ${name}`);return value;}
|
||||
|
||||
/** Native clipping and movement of the authored Announcement children. */
|
||||
@ccclass('AnnouncementView')
|
||||
export class AnnouncementView extends Component {
|
||||
private background:Node|null=null;
|
||||
private textNode:Node|null=null;
|
||||
private viewport:Node|null=null;
|
||||
private message='';
|
||||
private elapsed=0;
|
||||
private textY=0;
|
||||
|
||||
show(message:string):void {
|
||||
if(!this.viewport){
|
||||
const bg=requireNode(this.node,'Spirit219'),text=requireNode(this.node,'Spirit220');
|
||||
const bgUI=required(bg.getComponent(UITransform),'Spirit219 UITransform');
|
||||
// The text is animated; an authored Widget must not reset its X every frame.
|
||||
const widget=text.getComponent(Widget);if(widget)widget.enabled=false;
|
||||
const viewport=new Node('AnnouncementClip');viewport.layer=this.node.layer;viewport.parent=bg;
|
||||
const clipUI=viewport.addComponent(UITransform);clipUI.setAnchorPoint(0,0);
|
||||
clipUI.setContentSize(bgUI.contentSize);
|
||||
viewport.setPosition(-bgUI.width*bgUI.anchorX,-bgUI.height*bgUI.anchorY,0);
|
||||
viewport.addComponent(Mask);
|
||||
this.textY=text.position.y-bg.position.y+bgUI.height*bgUI.anchorY;
|
||||
text.parent=viewport;
|
||||
const textUI=required(text.getComponent(UITransform),'Spirit220 UITransform');
|
||||
textUI.setAnchorPoint(0,textUI.anchorY);
|
||||
const label=required(text.getComponent(Label),'Spirit220 Label');
|
||||
label.enableWrapText=false;label.overflow=Label.Overflow.NONE;label.horizontalAlign=Label.HorizontalAlign.LEFT;
|
||||
this.background=bg;this.textNode=text;this.viewport=viewport;
|
||||
}
|
||||
this.message=message;this.elapsed=0;
|
||||
required(required(this.textNode,'text').getComponent(Label),'label').string=message;
|
||||
this.draw();
|
||||
}
|
||||
|
||||
update(dt:number):void {if(this.viewport){this.elapsed+=dt;this.draw();}}
|
||||
|
||||
private draw():void {
|
||||
const text=required(this.textNode,'text'),bg=required(this.background,'background');
|
||||
const width=required(bg.getComponent(UITransform),'background UITransform').width;
|
||||
const state=announcementMotion(this.message,width,this.elapsed);
|
||||
bg.active=state.visible;text.active=state.visible;
|
||||
text.setPosition(state.left,this.textY,0);
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "4b4591ab-307a-4061-9690-7102b996a3ae",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import {_decorator,Component} from 'cc';
|
||||
import type {CreateRoomPageContext,CreateRoomPagePort} from '../../framework/sdk/contracts/create-room-page.ts';
|
||||
const {ccclass}=_decorator;
|
||||
/** Inherit this component on the subgame prefab root. Platform never inspects its children. */
|
||||
@ccclass('CreateRoomPage')
|
||||
export class CreateRoomPage extends Component implements CreateRoomPagePort {
|
||||
open(_context:CreateRoomPageContext):void{throw Error('Subgame must implement CreateRoomPage.open');}
|
||||
setBusy(_busy:boolean):void{throw Error('Subgame must implement CreateRoomPage.setBusy');}
|
||||
close():void{throw Error('Subgame must implement CreateRoomPage.close');}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "956dedf9-3650-418b-af8a-a755a26d991c",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import {_decorator,Button,Component,Label} from 'cc';
|
||||
import {JoinRoomInput} from '../../framework/presentation/join-room-input.ts';
|
||||
const {ccclass,property}=_decorator;
|
||||
export interface JoinRoomViewContext {initialCode:string;submit(code:number):void;cancel():void;paste():Promise<string>;reportError(message:string):void;}
|
||||
@ccclass('JoinRoomView')
|
||||
export class JoinRoomView extends Component {
|
||||
@property([Button]) digitButtons:Button[]=[];
|
||||
@property([Label]) slots:Label[]=[];
|
||||
@property(Button) clearButton:Button|null=null;
|
||||
@property(Button) deleteButton:Button|null=null;
|
||||
@property(Button) pasteButton:Button|null=null;
|
||||
@property(Button) submitButton:Button|null=null;
|
||||
@property([Button]) cancelButtons:Button[]=[];
|
||||
private input:JoinRoomInput|null=null;
|
||||
private releases:Array<()=>void>=[];
|
||||
private generation=0;
|
||||
private busy=false;
|
||||
private pasting=false;
|
||||
open(context:JoinRoomViewContext):void{
|
||||
this.close();const generation=this.generation;
|
||||
if(this.digitButtons.length!==10||this.slots.length!==6||!this.clearButton||!this.deleteButton||!this.pasteButton||!this.submitButton||this.cancelButtons.length!==2)throw Error('JoinRoomView requires serialized controls');
|
||||
const input=this.input=new JoinRoomInput(code=>context.submit(code));input.prefill(context.initialCode);
|
||||
const bind=(button:Button,action:()=>void)=>{const fn=()=>{if(this.busy||this.pasting)return;try{action();}catch(error){context.reportError(error instanceof Error?error.message:String(error));}finally{this.render();}};
|
||||
button.node.on(Button.EventType.CLICK,fn);this.releases.push(()=>button.node.off(Button.EventType.CLICK,fn));};
|
||||
this.digitButtons.forEach((button,digit)=>bind(button,()=>input.digit(digit)));
|
||||
bind(this.clearButton,()=>input.clear());bind(this.deleteButton,()=>input.remove());bind(this.submitButton,()=>input.confirm());
|
||||
bind(this.pasteButton,()=>{this.pasting=true;void context.paste().then(text=>{if(this.generation===generation){input.paste(text);this.render();}})
|
||||
.catch(error=>{if(this.generation===generation)context.reportError(error instanceof Error?error.message:String(error));})
|
||||
.finally(()=>{if(this.generation===generation)this.pasting=false;});});
|
||||
this.cancelButtons.forEach(button=>{const fn=()=>context.cancel();button.node.on(Button.EventType.CLICK,fn);this.releases.push(()=>button.node.off(Button.EventType.CLICK,fn));});
|
||||
this.setBusy(false);this.render();
|
||||
}
|
||||
setBusy(busy:boolean):void{this.busy=busy;this.input?.setBusy(busy);for(const b of [...this.digitButtons,this.clearButton,this.deleteButton,this.submitButton,this.pasteButton])if(b)b.interactable=!busy;}
|
||||
private render():void{const code=this.input?.code;if(code===undefined)return;this.slots.forEach((slot,i)=>{slot.node.active=i<code.length;slot.string=code[i]??'';});}
|
||||
close():void{this.generation++;this.pasting=false;for(const release of this.releases.splice(0))release();this.input=null;}
|
||||
onDestroy():void{this.close();}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "033ab9dd-e1a1-48fb-9fd6-5474c1a45eb2",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -1,17 +1,28 @@
|
||||
import { _decorator, Component, Prefab, SpriteFrame } from 'cc';
|
||||
const {ccclass,property}=_decorator;
|
||||
/** Editor-owned asset references; no runtime UUID/path guessing. */
|
||||
@ccclass('LobbyAssets')
|
||||
export class LobbyAssets extends Component {
|
||||
@property(Prefab) menu:Prefab|null=null;
|
||||
@property(Prefab) header:Prefab|null=null;
|
||||
@property(Prefab) noticeMenu:Prefab|null=null;
|
||||
@property(Prefab) notices:Prefab|null=null;
|
||||
@property(Prefab) profile:Prefab|null=null;
|
||||
@property(Prefab) settings:Prefab|null=null;
|
||||
@property(Prefab) join:Prefab|null=null;
|
||||
@property(Prefab) tasks:Prefab|null=null;
|
||||
import { _decorator, Component, Prefab, SpriteFrame } from 'cc';
|
||||
const {ccclass,property}=_decorator;
|
||||
/** Editor-owned asset references; no runtime UUID/path guessing. */
|
||||
@ccclass('LobbyAssets')
|
||||
export class LobbyAssets extends Component {
|
||||
@property(Prefab) menu:Prefab|null=null;
|
||||
@property(Prefab) header:Prefab|null=null;
|
||||
@property(Prefab) noticeMenu:Prefab|null=null;
|
||||
@property(Prefab) redeemCode:Prefab|null=null;
|
||||
@property(Prefab) qrCode:Prefab|null=null;
|
||||
@property(Prefab) noticeBoard:Prefab|null=null;
|
||||
@property(Prefab) ownedRoomList:Prefab|null=null;
|
||||
@property(Prefab) invite:Prefab|null=null;
|
||||
@property(Prefab) profile:Prefab|null=null;
|
||||
@property(Prefab) settings:Prefab|null=null;
|
||||
@property(Prefab) join:Prefab|null=null;
|
||||
@property(Prefab) tasks:Prefab|null=null;
|
||||
@property(Prefab) share:Prefab|null=null;
|
||||
@property([SpriteFrame]) digits:SpriteFrame[]=[];
|
||||
@property([SpriteFrame]) switches:SpriteFrame[]=[];
|
||||
}
|
||||
@property(Prefab) rank:Prefab|null=null;
|
||||
@property(Prefab) rankItem:Prefab|null=null;
|
||||
@property([SpriteFrame]) rankMedals:SpriteFrame[]=[];
|
||||
@property(SpriteFrame) rankTitle:SpriteFrame|null=null;
|
||||
@property(SpriteFrame) vipRankTitle:SpriteFrame|null=null;
|
||||
@property([SpriteFrame]) ownedRoomDigits:SpriteFrame[]=[];
|
||||
@property([SpriteFrame]) rankSymbols:SpriteFrame[]=[];
|
||||
@property([SpriteFrame]) digits:SpriteFrame[]=[];
|
||||
@property([SpriteFrame]) switches:SpriteFrame[]=[];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import {_decorator,Component,Layout,Sprite,SpriteFrame,Toggle,ToggleContainer} from 'cc';
|
||||
const {ccclass,property}=_decorator;
|
||||
|
||||
/** Native ToggleContainer owns selection; serialized frames own the tab appearance. */
|
||||
@ccclass('NoticeTabsView')
|
||||
export class NoticeTabsView extends Component {
|
||||
@property([Toggle]) tabs:Toggle[]=[];
|
||||
@property([Sprite]) backgrounds:Sprite[]=[];
|
||||
@property([Sprite]) titles:Sprite[]=[];
|
||||
@property(SpriteFrame) normalBackground:SpriteFrame|null=null;
|
||||
@property(SpriteFrame) selectedBackground:SpriteFrame|null=null;
|
||||
@property([SpriteFrame]) normalTitles:SpriteFrame[]=[];
|
||||
@property([SpriteFrame]) selectedTitles:SpriteFrame[]=[];
|
||||
private release:(()=>void)|null=null;
|
||||
|
||||
bindPages(visible:readonly boolean[],select:(index:number)=>void):()=>void {
|
||||
this.release?.();
|
||||
const count=this.tabs.length,container=this.getComponent(ToggleContainer),layout=this.getComponent(Layout);
|
||||
if(count===0||visible.length!==count||!visible[0]||!container||container.allowSwitchOff||!layout||layout.type!==Layout.Type.VERTICAL)
|
||||
throw Error('NoticeTabsView requires a vertical single-selection container and a visible first page');
|
||||
if(!this.normalBackground||!this.selectedBackground||[this.backgrounds,this.titles,this.normalTitles,this.selectedTitles].some(items=>items.length!==count||items.some(item=>!item)))
|
||||
throw Error('NoticeTabsView requires complete serialized tab artwork');
|
||||
this.tabs.forEach((tab,index)=>{if(tab.node.parent!==this.node)throw Error('Notice tabs must be direct ToggleContainer children');tab.node.active=visible[index];});
|
||||
this.tabs[0].setIsCheckedWithoutNotify(true);
|
||||
this.tabs.slice(1).forEach(tab=>tab.setIsCheckedWithoutNotify(false));
|
||||
const paint=()=>this.tabs.forEach((tab,index)=>{
|
||||
this.backgrounds[index].spriteFrame=tab.isChecked?this.selectedBackground:this.normalBackground;
|
||||
this.titles[index].spriteFrame=tab.isChecked?this.selectedTitles[index]:this.normalTitles[index];
|
||||
});
|
||||
const handlers=this.tabs.map((tab,index)=>()=>{paint();if(tab.isChecked)select(index);});
|
||||
this.tabs.forEach((tab,index)=>tab.node.on(Toggle.EventType.TOGGLE,handlers[index]));
|
||||
const release=()=>{this.tabs.forEach((tab,index)=>tab.node.off(Toggle.EventType.TOGGLE,handlers[index]));if(this.release===release)this.release=null;};
|
||||
this.release=release;paint();layout.updateLayout(true);select(0);
|
||||
return release;
|
||||
}
|
||||
onDestroy():void{this.release?.();}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user