feat: integrate platform room entry, UI migration and erqiwang documentation

This commit is contained in:
2026-09-07 21:01:08 +08:00
parent 9c4510697c
commit 30a1b4114c
259 changed files with 84995 additions and 30201 deletions
+11 -3
View File
@@ -6,7 +6,7 @@ This file provides guidance to Codex (Codex.ai/code) when working with code in t
**任何前端开发都必须完全遵循前后端数据包的协议与数据结构,做到服务器零改动。** 这是本仓库不可逾越的最高准则,优先于一切其它考量: **任何前端开发都必须完全遵循前后端数据包的协议与数据结构,做到服务器零改动。** 这是本仓库不可逾越的最高准则,优先于一切其它考量:
- 协议信封、`route`/`rpc` 命名、字段名与类型、`roomtype` 配置数组、`deskinfo` 快照结构等,**必须与现有协议逐字节对齐**(依据 `docs/protocol/`),不得为前端方便而改变格式或新增/重命名字段。 - 协议信封、`route`/`rpc` 命名、字段名与类型、`roomtype` 配置编码、`deskinfo` 快照结构等,**必须与现有协议逐字节对齐**(依据 `docs/protocol/`),不得为前端方便而改变格式或新增/重命名字段。
- 遇到协议层有疑问时,**先读 `docs/protocol/` 对应章节核对,不要臆测**;宁可前端多做适配,也绝不要求服务器配合改动。 - 遇到协议层有疑问时,**先读 `docs/protocol/` 对应章节核对,不要臆测**;宁可前端多做适配,也绝不要求服务器配合改动。
- 新的 Cocos 前端(`YouleNexus`)的目标,就是在不改服务器的前提下复刻该协议契约、实现联调兼容。 - 新的 Cocos 前端(`YouleNexus`)的目标,就是在不改服务器的前提下复刻该协议契约、实现联调兼容。
@@ -23,12 +23,20 @@ This file provides guidance to Codex (Codex.ai/code) when working with code in t
## 仓库总览 ## 仓库总览
这是友乐(Youle)棋牌游戏平台的前端工作区,核心是「**一套前端模板 + 多个子游戏**」的架构,并正在迁移到 Cocos Creator。三个顶层目录: 这是友乐(Youle)棋牌游戏平台的前端工作区,核心是「**一套前端模板 + 多个子游戏**」的架构,并正在迁移到 Cocos Creator。主要目录:
- **`projects/`** —— 旧版 HTML5 前端。`projects/Game_Surface_3` 是**前端模板(平台外壳)**,也是新前端 UI 迁移的**原工程(迁移源)**;其余目录都是**基于该模板扩展出来的子游戏**,平台层几乎不动,只改子游戏层 + 美术数据。分层结构与关键文件见 `projects/AGENTS.md`。 - **`projects/`** —— 旧版 HTML5 前端。`projects/Game_Surface_3` 是**前端模板(平台外壳)**,也是新前端 UI 迁移的**原工程(迁移源)**;其余目录都是**基于该模板扩展出来的子游戏**,平台层几乎不动,只改子游戏层 + 美术数据。分层结构与关键文件见 `projects/AGENTS.md`。
- **`docs/protocol/`** —— 前端模板框架与服务器收发包的**协议规范**(权威文档,8 篇)。改动任何网络层代码前**必须先读对应章节**,服务器不可改,前端必须严格对齐协议。 - **`docs/protocol/`** —— 前端模板框架与服务器收发包的**协议规范**(权威文档,8 篇)。改动任何网络层代码前**必须先读对应章节**,服务器不可改,前端必须严格对齐协议。
- **`cocoscreator_projects/YouleNexus/`** —— 新的 Cocos Creator 3.8+ 前端工程,目标是按 `docs/protocol` 复刻协议契约,做到服务器零改动即可联调。 - **`cocoscreator_projects/YouleNexus/`** —— 新的 Cocos Creator 3.8+ 前端工程,目标是按 `docs/protocol` 复刻协议契约,做到服务器零改动即可联调。
### 服务器文档与二七王服务端
- **`server/development-guide/`**:服务器通用开发文档。查服务端框架、子游戏接入、通信协议和开发约束时,从 [README](server/development-guide/README.md) 开始,按任务阅读对应章节。
- **`server/games/erqiwang/`**:二七王当前改动后的服务器代码。核对实际行为时读取这里的实现,不以 `projects/erqiwang` 的旧前端推断当前服务器规则。
- 二七王规则设计位于 [design.md](server/games/erqiwang/docs/design/design.md),玩法协议位于 [packet_protocol.md](server/games/erqiwang/docs/protocol/packet_protocol.md)。前端接入时同时核对设计、协议与服务端实现;发现差异应明确记录,不把设计要求冒充已实现行为。
- `roomtype` 由目标子游戏定义,可能是数组或字符串;平台通用流程不可擅自转换类型。二七王实际解析以 `class.config.js` 为入口,局数与扣卡检查 `class.export.js`;位索引只在协议文档中维护,不在本文件重复定义。
- 查阅服务器源码用于前端适配,不代表授权修改服务器代码;前端开发继续遵守“服务器零改动”。
### 原工程 `Game_Surface_3` → `YouleNexus` 的 UI 迁移 ### 原工程 `Game_Surface_3` → `YouleNexus` 的 UI 迁移
`YouleNexus` 的 UI 资源(`assets/framework/ui/prefabs/`)从**原工程 `projects/Game_Surface_3`** 逐层复刻。数据契约与流程规范: `YouleNexus` 的 UI 资源(`assets/framework/ui/prefabs/`)从**原工程 `projects/Game_Surface_3`** 逐层复刻。数据契约与流程规范:
@@ -42,7 +50,7 @@ This file provides guidance to Codex (Codex.ai/code) when working with code in t
`docs/protocol/` 是权威文档,改任何网络层代码前先读对应章节,**不要凭本文件的转述臆测**。 `docs/protocol/` 是权威文档,改任何网络层代码前先读对应章节,**不要凭本文件的转述臆测**。
**两个不可妥协的约束:** ① `roomtype` 房间配置数组结构必须与目标子游戏**逐字节一致**(服务器据此解析规则);② `deskinfo` 对局快照须可序列化/反序列化以支持 `isbattle==1` 时 `Game_Modify.Reconnect(deskinfo)` 断线重连。 **两个不可妥协的约束:** ① `roomtype` 房间配置编码的类型与结构必须与目标子游戏**逐字节一致**(服务器据此解析规则);② `deskinfo` 对局快照须可序列化/反序列化以支持 `isbattle==1` 时 `Game_Modify.Reconnect(deskinfo)` 断线重连。
## 客户端侧适配约束:远程配置 / 原生数据接口 / 原生↔H5 桥接 ## 客户端侧适配约束:远程配置 / 原生数据接口 / 原生↔H5 桥接
@@ -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,'');
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "4aeb3de8-f982-4246-95be-7dca058bc99d",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -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]);
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "ca074aba-afc9-428f-93ea-199d30dba6ef",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -11,6 +11,8 @@ export const BUILD_IDENTITY: ChannelIdentity = {
// This game's entry in config_test/update_jsonv2.txt belongs to this agent/channel. // This game's entry in config_test/update_jsonv2.txt belongs to this agent/channel.
agentid: '1B2h0ccl205c390Y28m1Ajdplkuu4wgy', agentid: '1B2h0ccl205c390Y28m1Ajdplkuu4wgy',
channelid: 'aouv0LotK0pYyQ0Pdrx0CsdcaezfzrcG', channelid: 'aouv0LotK0pYyQ0Pdrx0CsdcaezfzrcG',
// agentid: 'veRa0qrBf0df2K1G4de2tgfmVxB2jxpv',
// channelid: 'FtJf073aa0d6rI1xD8J1Y42fINTm0ziK',
gameid: 'J9r4022t80pxlb01zOK76DN1WD4QewFl', gameid: 'J9r4022t80pxlb01zOK76DN1WD4QewFl',
marketid: 4, marketid: 4,
version: 1, // 数字 versionCode(联调时改成本地服要求的版本号) version: 1, // 数字 versionCode(联调时改成本地服要求的版本号)
@@ -2,13 +2,15 @@ import type { RuntimeConfig } from '../config/runtime-config.ts';
import { buildFirstSliceEnvelope } from '../protocol/first-slice-routes.ts'; import { buildFirstSliceEnvelope } from '../protocol/first-slice-routes.ts';
import type { OutboundEnvelope } from '../core/types/envelope.ts'; import type { OutboundEnvelope } from '../core/types/envelope.ts';
import { import {
buildCreateRoomRequest,
type CreateRoomEnvironment,
buildExitRoomRequest, buildExitRoomRequest,
buildJoinRoomRequest, buildJoinRoomRequest,
buildPrepareRequest, buildPrepareRequest,
type JoinRoomEnvironment, type JoinRoomEnvironment,
type RoomIdentity, type RoomIdentity,
} from '../protocol/contracts/index.ts'; } from '../protocol/contracts/index.ts';
import { PlatformStore } from './stores/platform-store.ts'; import { cloneAndFreezeRoomtype, PlatformStore } from './stores/platform-store.ts';
import type { InsideRoomState, PlatformState } from './stores/platform-types.ts'; import type { InsideRoomState, PlatformState } from './stores/platform-types.ts';
/** The complete-envelope send capability used by platform command adapters. */ /** The complete-envelope send capability used by platform command adapters. */
@@ -23,9 +25,11 @@ export interface PlatformCommandsOptions {
} }
export interface JoinRoomCommand extends JoinRoomEnvironment { export interface JoinRoomCommand extends JoinRoomEnvironment {
readonly roomcode: string; readonly roomcode: string | number;
} }
export interface CreateRoomCommand extends CreateRoomEnvironment {}
export class UnsupportedGameHostCommandError extends Error { export class UnsupportedGameHostCommandError extends Error {
constructor(message: string) { constructor(message: string) {
super(message); super(message);
@@ -37,6 +41,23 @@ export class UnsupportedGameHostCommandError extends Error {
export class PlatformCommands { export class PlatformCommands {
constructor(private readonly options: PlatformCommandsOptions) {} 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 { getVipRooms(shortcodeList:readonly (string|number)[]):void {
this.taskPlayerId(); this.taskPlayerId();
const {agentid,gameid}=this.options.runtimeConfig.identity; const {agentid,gameid}=this.options.runtimeConfig.identity;
@@ -57,6 +78,27 @@ export class PlatformCommands {
this.options.send.send(buildFirstSliceEnvelope('get_player_task',{agentid,playerid,gameid})); 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 { claimTask(taskid:number|string):void {
const playerid=this.taskPlayerId(); const playerid=this.taskPlayerId();
const task=this.options.store.getState().tasks?.find(task=>String(task.taskid)===String(taskid)); const task=this.options.store.getState().tasks?.find(task=>String(task.taskid)===String(taskid));
@@ -93,6 +135,12 @@ export class PlatformCommands {
)); ));
} }
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 { joinRoom(command: JoinRoomCommand): void {
const state = this.options.store.getState(); const state = this.options.store.getState();
if (state.room.kind !== 'outside') { if (state.room.kind !== 'outside') {
@@ -4,13 +4,15 @@ import {
GameSessionHost, GameSessionHost,
} from '../sdk/runtime/game-session-host.ts'; } from '../sdk/runtime/game-session-host.ts';
import { import {
parseCreateRoomResponse,
parseLoginResponse, parseLoginResponse,
parseSelfJoinRoomResponse, parseSelfJoinRoomResponse,
type ParsedSelfExitPayload, type ParsedSelfExitPayload,
type ReconnectSnapshot, type ReconnectSnapshot,
} from '../protocol/contracts/index.ts'; } from '../protocol/contracts/index.ts';
import { PlatformStore } from './stores/platform-store.ts'; import { cloneAndFreezeRoomtype, PlatformStore } from './stores/platform-store.ts';
import type { PlatformState } from './stores/platform-types.ts'; import type { PlatformState } from './stores/platform-types.ts';
import type {LoginAccountIdentity} from '../protocol/contracts/login-contract.ts';
export interface GameHostLeasePort { export interface GameHostLeasePort {
readonly host: GameHost; readonly host: GameHost;
@@ -21,12 +23,13 @@ export type GameHostLeaseFactory = () => GameHostLeasePort;
/** A valid nonzero server outcome, delivered without changing canonical state. */ /** A valid nonzero server outcome, delivered without changing canonical state. */
export interface ServerDenial { export interface ServerDenial {
readonly rpc: 'player_login' | 'self_join_room'; readonly rpc: 'player_login' | 'self_join_room' | 'create_room';
readonly state: number; readonly state: number;
readonly data: Readonly<Record<string, unknown>>; readonly data: Readonly<Record<string, unknown>>;
} }
export interface RuntimeSessionOptions { export interface RuntimeSessionOptions {
readonly roomHistory?: import('../adapters/native/owned-room-history.ts').OwnedRoomHistory;
readonly store: PlatformStore; readonly store: PlatformStore;
readonly gameSession: GameSessionHost; readonly gameSession: GameSessionHost;
readonly createGameHostLease: GameHostLeaseFactory; readonly createGameHostLease: GameHostLeaseFactory;
@@ -48,6 +51,9 @@ export class RuntimeSessionFault extends Error {
/** Coordinates the canonical platform snapshot with the one active game session. */ /** Coordinates the canonical platform snapshot with the one active game session. */
export class RuntimeSession { export class RuntimeSession {
private loginIsPending = false; 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 wasKicked = false;
private terminalFault: RuntimeSessionFault | null = null; private terminalFault: RuntimeSessionFault | null = null;
private activeLease: GameHostLeasePort | null = null; private activeLease: GameHostLeasePort | null = null;
@@ -65,8 +71,10 @@ export class RuntimeSession {
get fault(): Error | null { return this.terminalFault; } get fault(): Error | null { return this.terminalFault; }
markLoginPending(): void { markLoginPending(identity?:Pick<LoginAccountIdentity,'openid'|'unionid'>|null): void {
this.requireOperational(); 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; this.loginIsPending = true;
} }
@@ -78,7 +86,8 @@ export class RuntimeSession {
this.options.onServerDenial({ rpc: 'player_login', state: parsed.state, data: parsed.raw }); this.options.onServerDenial({ rpc: 'player_login', state: parsed.state, data: parsed.raw });
return; return;
} }
const committed = this.options.store.applyLoginSuccess(parsed); this.lastJoinedRoomcode = null;
const committed = this.options.store.applyLoginSuccess(parsed,this.authorizedIdentity);
if (parsed.room === null) { if (parsed.room === null) {
this.releaseOrFault('login lobby replacement'); this.releaseOrFault('login lobby replacement');
return; return;
@@ -86,15 +95,37 @@ export class RuntimeSession {
this.openCommittedRoom(committed, parsed.reconnect, 'login room open'); 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 { handleSelfJoin(data: unknown): void {
this.requireOperational(); this.requireOperational();
const parsed = parseSelfJoinRoomResponse(data); const parsed = parseSelfJoinRoomResponse(data);
this.options.roomHistory?.joined(parsed.raw);
if (parsed.state !== 0) { if (parsed.state !== 0) {
this.options.onServerDenial({ rpc: 'self_join_room', state: parsed.state, data: parsed.raw }); this.options.onServerDenial({ rpc: 'self_join_room', state: parsed.state, data: parsed.raw });
return; return;
} }
if (parsed.room !== null && this.lastJoinedRoomcode === parsed.room.roomcode
&& this.options.gameSession.state === 'active') return;
const committed = this.options.store.replaceRoom(parsed); const committed = this.options.store.replaceRoom(parsed);
this.openCommittedRoom(committed, parsed.reconnect, 'self join room open'); this.openCommittedRoom(committed, parsed.reconnect, 'self join room open');
this.lastJoinedRoomcode = parsed.room!.roomcode;
} }
handleSelfExit(parsed: ParsedSelfExitPayload): void { handleSelfExit(parsed: ParsedSelfExitPayload): void {
@@ -122,6 +153,7 @@ export class RuntimeSession {
} }
try { try {
this.options.roomHistory?.exited(parsed.raw,state.room.infinite);
this.options.store.clearRoom(); this.options.store.clearRoom();
} catch (error) { } catch (error) {
this.throwFault('self exit state commit', [error, ...this.releaseGame()]); this.throwFault('self exit state commit', [error, ...this.releaseGame()]);
@@ -208,6 +240,7 @@ export class RuntimeSession {
} }
private releaseGame(): unknown[] { private releaseGame(): unknown[] {
this.lastJoinedRoomcode = null;
const errors: unknown[] = []; const errors: unknown[] = [];
const lease = this.activeLease; const lease = this.activeLease;
this.activeLease = null; this.activeLease = null;
@@ -14,7 +14,7 @@ import { Router } from '../protocol/router.ts';
import { isLoginResponse } from '../protocol/first-slice-routes.ts'; import { isLoginResponse } from '../protocol/first-slice-routes.ts';
import type { GameEntry, GameHost } from '../sdk/contracts/index.ts'; import type { GameEntry, GameHost } from '../sdk/contracts/index.ts';
import { GameSessionHost } from '../sdk/runtime/game-session-host.ts'; import { GameSessionHost } from '../sdk/runtime/game-session-host.ts';
import { PlatformCommands, type JoinRoomCommand } from './commands.ts'; import { PlatformCommands, type JoinRoomCommand, type CreateRoomCommand } from './commands.ts';
import type { ConnectionIntent } from './connection-intent.ts'; import type { ConnectionIntent } from './connection-intent.ts';
import { import {
createGameHostAdapter, createGameHostAdapter,
@@ -39,6 +39,7 @@ export interface PlatformRuntimeOptions {
readonly loadResources: () => Promise<void>; readonly loadResources: () => Promise<void>;
readonly waitForMinimumDisplay: () => Promise<void>; readonly waitForMinimumDisplay: () => Promise<void>;
readonly getLoginDeviceSnapshot: () => LoginDeviceSnapshot; readonly getLoginDeviceSnapshot: () => LoginDeviceSnapshot;
readonly roomHistory?: import('../adapters/native/owned-room-history.ts').OwnedRoomHistory;
readonly clock?: Clock; readonly clock?: Clock;
} }
@@ -233,6 +234,7 @@ export class PlatformRuntime {
send, send,
}); });
const session = new RuntimeSession({ const session = new RuntimeSession({
roomHistory:this.options.roomHistory,
store: this.store, store: this.store,
gameSession, gameSession,
createGameHostLease: () => this.createGameHostLease(commands, send), createGameHostLease: () => this.createGameHostLease(commands, send),
@@ -290,19 +292,24 @@ export class PlatformRuntime {
const config = this.requireConfig(); const config = this.requireConfig();
const wire = this.requireOpenWire(); const wire = this.requireOpenWire();
const session = this.requireSession(); const session = this.requireSession();
const device=this.options.getLoginDeviceSnapshot();
const envelope = buildLoginRequest( const envelope = buildLoginRequest(
config, config,
account, account,
this.options.getLoginDeviceSnapshot(), device,
); );
wire.send(envelope); wire.send(envelope);
this.loginEnvelope = envelope; this.loginEnvelope = envelope;
this.connectionIntent = { type: 'login', envelope }; this.connectionIntent = { type: 'login', envelope };
session.markLoginPending(); session.markLoginPending(device.deviceLogin.enabled?null:Object.freeze({openid:envelope.data.openid,unionid:envelope.data.unionid}));
this.startLoginGuard(); this.startLoginGuard();
} }
createRoom(command: CreateRoomCommand): void {
this.requireCommands().createRoom(command);
}
joinRoom(command: JoinRoomCommand): void { joinRoom(command: JoinRoomCommand): void {
this.requireCommands().joinRoom(command); this.requireCommands().joinRoom(command);
} }
@@ -312,8 +319,14 @@ export class PlatformRuntime {
} }
getVipRooms(codes:readonly (string|number)[]):void {this.requireCommands().getVipRooms(codes);} getVipRooms(codes:readonly (string|number)[]):void {this.requireCommands().getVipRooms(codes);}
getVipRankList(limit:number):void {this.requireCommands().getVipRankList(limit);}
joinOwnedRoom(code:number,location:unknown):void {this.requireCommands().joinOwnedRoom(code,location);}
getTreasureList():void {this.requireCommands().getTreasureList();}
redeemCard(cardno:string):void {this.requireCommands().redeemCard(cardno);}
joinVipRoom(code:string|number,location:unknown):void {this.requireCommands().joinVipRoom(code,location);} joinVipRoom(code:string|number,location:unknown):void {this.requireCommands().joinVipRoom(code,location);}
getPlayerTasks():void {this.requireCommands().getPlayerTasks();} getPlayerTasks():void {this.requireCommands().getPlayerTasks();}
getPlayerInvitecode():void {this.requireCommands().getPlayerInvitecode();}
bindInvitecode(code:string):void {this.requireCommands().bindInvitecode(code);}
claimTask(taskid:number|string):void {this.requireCommands().claimTask(taskid);} claimTask(taskid:number|string):void {this.requireCommands().claimTask(taskid);}
exitRoom(): void { exitRoom(): void {
@@ -531,8 +544,8 @@ export class PlatformRuntime {
} }
return; return;
} }
if (rpc === 'self_join_room') { if (rpc === 'self_join_room' || rpc === 'create_room') {
this.requireGameSession().showRoomAfterOpen(); if (this.store.getState().room.kind === 'inside') this.requireGameSession().showRoomAfterOpen();
return; return;
} }
if (rpc === 'self_exit_room') this.options.scene.showLobby(); if (rpc === 'self_exit_room') this.options.scene.showLobby();
@@ -9,7 +9,7 @@ export interface ScenePort {
showReconnect(): void; showReconnect(): void;
showKicked(data: unknown): void; showKicked(data: unknown): void;
/** Required when the host supports the server's generic message UI. */ /** Required when the host supports the server's generic message UI. */
showTips?(message: string, time: number, source?:'show_message'|'update_roomcard'): void; 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. */ /** Recoverable server result; the consumer interprets the original failure payload. */
showServerDenial(outcome: ServerDenial): void; showServerDenial(outcome: ServerDenial): void;
showFatal(error: Error): void; showFatal(error: Error): void;
@@ -1,6 +1,9 @@
import type { Unsubscribe } from '../../core/reactive.ts'; import type { Unsubscribe } from '../../core/reactive.ts';
import {parseVipRooms} from '../../protocol/contracts/vip-room.ts'; import {parseVipRooms} from '../../protocol/contracts/vip-room.ts';
import {parseRankList,parseVipRankList} from '../../protocol/contracts/rank-list.ts';
import type { TaskData } from '../../core/types/task.ts'; import type { TaskData } from '../../core/types/task.ts';
import type {LoginAccountIdentity} from '../../protocol/contracts/login-contract.ts';
import {requireStringOrFiniteNumber} from '../../protocol/contracts/validation.ts';
import { import {
hasOwn, hasOwn,
requireArray, requireArray,
@@ -12,6 +15,7 @@ import {
type ParsedLoginResponse, type ParsedLoginResponse,
type ParsedOnlinePayload, type ParsedOnlinePayload,
type ParsedRoomResponse, type ParsedRoomResponse,
type ParsedCreateRoomResponse,
type ParsedSeatPayload, type ParsedSeatPayload,
} from '../../protocol/contracts/index.ts'; } from '../../protocol/contracts/index.ts';
import type { import type {
@@ -148,7 +152,7 @@ function verifyRoomtypeSourceGraph(graph: readonly RoomtypeObjectSnapshot[]): vo
} }
} }
function cloneAndFreezeRoomtype(value: readonly unknown[]): readonly unknown[] { export function cloneAndFreezeRoomtype(value: readonly unknown[]): readonly unknown[] {
const copies = new WeakMap<object, object>(); const copies = new WeakMap<object, object>();
const active = new WeakSet<object>(); const active = new WeakSet<object>();
const ownedGraph: object[] = []; const ownedGraph: object[] = [];
@@ -509,6 +513,54 @@ export class PlatformStore {
applyVipRooms(data:unknown):void { applyVipRooms(data:unknown):void {
this.commit(Object.freeze({...this.state,vipRooms:parseVipRooms(data)})); this.commit(Object.freeze({...this.state,vipRooms:parseVipRooms(data)}));
} }
/** Absolute server balances; change-only purchase notices never overwrite money. */
applyBeanUpdate(data:unknown):void {
const raw=requireRecord(data,'$.data');if(raw.change!==undefined)return;
let player=this.taskPlayer();
if(raw.seat!==undefined){
const seat=requireInteger(raw.seat,'$.data.seat'),room=this.state.room;
if(room.kind!=='inside')throw Error('update_bean seat requires a room');
const id=room.seatPlayerIds[seat];
if(id===undefined||id===null||!this.state.players.entities[id])throw Error('update_bean seat has no player');
player=this.state.players.entities[id];
}
const bean=requireNumber(raw.bean,'$.data.bean');
this.commit(Object.freeze({...this.state,players:Object.freeze({...this.state.players,
entities:Object.freeze({...this.state.players.entities,[player.playerid]:Object.freeze({...player,bean})})})}));
}
applyVipRank(data:unknown):void {
const raw=requireRecord(data,'$.data');
// Desk.getVipRankList ignores a missing/falsy list. A receipt still completes this request.
this.commit(Object.freeze({...this.state,vipRank:Object.freeze({entries:raw.list?parseVipRankList(raw.list):null})}));
}
applyTreasureList(data:unknown):void {
const raw=requireRecord(data,'$.data');
this.commit(Object.freeze({...this.state,treasureList:parseRankList(raw.list)}));
}
applyInvitecode(data:unknown):void {
const raw=requireRecord(data,'$.data');
const invitecode=raw.invitecode===null?null:requireString(raw.invitecode,'$.data.invitecode');
this.commitInvite('query',invitecode);
}
applyInviteBinding(data:unknown):string {
const raw=requireRecord(data,'$.data');
// Legacy binding_invitecode compares state == 0; the service also sends integer strings.
// Normalize at this response boundary, without changing the shared integer validator.
const stateValue=typeof raw.state==='string'&&/^[+-]?\d+$/.test(raw.state)?Number(raw.state):raw.state;
const state=requireInteger(stateValue,'$.data.state'),error=requireString(raw.error,'$.data.error');
const invitecode=state===0?requireString(raw.invitecode,'$.data.invitecode'):this.taskPlayer().invitecode;
this.commitInvite('binding',invitecode);
return error;
}
private commitInvite(kind:'query'|'binding',invitecode:string|null|undefined):void {
const player=this.taskPlayer();
this.commit(Object.freeze({...this.state,inviteResponse:Object.freeze({kind}),players:Object.freeze({
...this.state.players,entities:Object.freeze({...this.state.players.entities,[player.playerid]:Object.freeze({...player,invitecode})}),
})}));
}
private commit(state: PlatformState): PlatformState { private commit(state: PlatformState): PlatformState {
if (Object.is(state, this.state)) return state; if (Object.is(state, this.state)) return state;
@@ -540,9 +592,11 @@ export class PlatformStore {
return state; return state;
} }
applyLoginSuccess(parsed: ParsedLoginResponse): PlatformState { applyLoginSuccess(parsed: ParsedLoginResponse,authorizedIdentity?:Pick<LoginAccountIdentity,'openid'|'unionid'>): PlatformState {
const input = requireRecord(parsed, '$.login'); const input = requireRecord(parsed, '$.login');
const raw = requireRecord(input.raw, '$.login.raw'); const raw = requireRecord(input.raw, '$.login.raw');
// Source selection is explicit: authorization for non-device login, response otherwise.
const identity=authorizedIdentity===undefined?raw:authorizedIdentity;
const state = requireMatchingState(input.state, raw, '$.login'); const state = requireMatchingState(input.state, raw, '$.login');
const rawHasRoom = LOGIN_ROOM_FIELDS.some((field) => hasOwn(raw, field)); const rawHasRoom = LOGIN_ROOM_FIELDS.some((field) => hasOwn(raw, field));
if (input.room === undefined || (input.room !== null) !== rawHasRoom) { if (input.room === undefined || (input.room !== null) !== rawHasRoom) {
@@ -561,6 +615,9 @@ export class PlatformStore {
const player: PlatformPlayer = Object.freeze({ const player: PlatformPlayer = Object.freeze({
playerid, playerid,
nickname: requireString(raw.nickname, '$.login.raw.nickname'), nickname: requireString(raw.nickname, '$.login.raw.nickname'),
...(hasOwn(identity,'openid')?{openid:requireString(identity.openid,'$.login.identity.openid')}:{}),
...(hasOwn(identity,'unionid')?{unionid:requireStringOrFiniteNumber(identity.unionid,'$.login.identity.unionid')}:{}),
...(hasOwn(raw,'invitecode')?{invitecode:raw.invitecode===null?null:requireString(raw.invitecode,'$.login.raw.invitecode')}:{}),
avatar: requireString(raw.avatar, '$.login.raw.avatar'), avatar: requireString(raw.avatar, '$.login.raw.avatar'),
sex: requireInteger(raw.sex, '$.login.raw.sex'), sex: requireInteger(raw.sex, '$.login.raw.sex'),
bean: requireNumber(raw.bean, '$.login.raw.bean'), bean: requireNumber(raw.bean, '$.login.raw.bean'),
@@ -592,6 +649,32 @@ export class PlatformStore {
})); }));
} }
createRoom(parsed: ParsedCreateRoomResponse, seatCount: number): PlatformState {
if (parsed.state !== 0 || parsed.room === null) throw new Error('createRoom requires success');
const count = requirePositiveInteger(seatCount, 'GameEntry.resolveSeatCount');
const {roomcode, seat, roomtype} = parsed.room;
if (seat < 0 || seat >= count) throw new RangeError('create_room seat outside GameEntry seat count');
const previous = this.getState();
const selfPlayerId = previous.players.selfPlayerId;
if (selfPlayerId === null) throw new Error('create_room requires authenticated player');
const selfPlayer = previous.players.entities[selfPlayerId];
if (selfPlayer === undefined) throw new Error('create_room missing login player');
const seatPlayerIds: (number | null)[] = Array(count).fill(null);
seatPlayerIds[seat] = selfPlayerId;
// Desk.create_room seats C_Player and resets preparation. No response.players is fabricated.
return this.commit(Object.freeze({
app: Object.freeze({phase: 'logged-in'}),
players: Object.freeze({selfPlayerId, entities: Object.freeze({
[selfPlayerId]: Object.freeze({...selfPlayer, isprepare: 0, onstate: 0}),
})}),
room: Object.freeze({kind: 'inside', roomcode, selfSeat: seat,
seatPlayerIds: Object.freeze(seatPlayerIds), readySeats: Object.freeze([]),
offlineSeats: Object.freeze([]), roomtype: cloneAndFreezeRoomtype(roomtype),
stage: 0, needprepare: parsed.needprepare, infinite: parsed.infinite,
}),
}));
}
replaceRoom(parsed: ParsedRoomResponse): PlatformState { replaceRoom(parsed: ParsedRoomResponse): PlatformState {
const input = requireRecord(parsed, '$.response'); const input = requireRecord(parsed, '$.response');
const raw = requireRecord(input.raw, '$.response.raw'); const raw = requireRecord(input.raw, '$.response.raw');
@@ -18,6 +18,9 @@ export interface PlatformPlayer {
readonly bean: number; readonly bean: number;
/** Account-only fields are absent on other room players; never default them in views. */ /** Account-only fields are absent on other room players; never default them in views. */
readonly roomcard?: number; readonly roomcard?: number;
readonly openid?: string;
readonly unionid?: string | number;
readonly invitecode?: string | null;
readonly taskstate?: number; readonly taskstate?: number;
readonly advanced?: number; readonly advanced?: number;
readonly score?: number; readonly score?: number;
@@ -52,6 +55,10 @@ export interface InsideRoomState {
export type RoomState = OutsideRoomState | InsideRoomState; export type RoomState = OutsideRoomState | InsideRoomState;
export interface PlatformState { export interface PlatformState {
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[]; readonly vipRooms?: readonly import('../../core/types/vip-room.ts').VipRoom[];
/** Absent until a task list response arrives; cleared by login replacement. */ /** Absent until a task list response arrives; cleared by login replacement. */
readonly tasks?: readonly TaskData[]; readonly tasks?: readonly TaskData[];
@@ -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": {}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "a834856f-7a53-445b-b517-6588ad346e59",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -8,8 +8,8 @@ export const LOBBY_TEMPLATE:Readonly<LobbyTemplate>=Object.freeze({
isGameHall:false,mainMenuButton:false,hideNotice:false,vipInfinite:false, isGameHall:false,mainMenuButton:false,hideNotice:false,vipInfinite:false,
}); });
export interface LobbyContext {versionState:0|1;launchMode:0|1;} 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 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({hideTaskBtn:0 as number|string,hideWxShare:0 as number|string,miniPro:0 as number|string,hideRoomCard:1,cdKey:1, 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}); ofcShortCode:null as unknown,hideSpid:null as unknown});
function object(value:unknown,name:string):Record<string,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`); if(value===null||typeof value!=='object'||Array.isArray(value))throw Error(`Invalid ${name}: expected object`);
@@ -34,6 +34,8 @@ export function resolveLobbyMenuConfig(remote:RemoteConfig,template:Readonly<Lob
for(const key of Object.keys(SYS_DEFAULTS) as (keyof typeof 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}); 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){ for(const key of ['hideTaskBtn','hideWxShare','miniPro','hideRoomCard','cdKey'] as const){
if(!Number.isFinite(Number(sys[key])))throw Error(`Invalid ${key}`); if(!Number.isFinite(Number(sys[key])))throw Error(`Invalid ${key}`);
if(key==='hideRoomCard'||key==='cdKey')sys[key]=Number(sys[key]); if(key==='hideRoomCard'||key==='cdKey')sys[key]=Number(sys[key]);
@@ -42,9 +44,18 @@ export function resolveLobbyMenuConfig(remote:RemoteConfig,template:Readonly<Lob
const rawAgent=remote.getValue('agentmode'); const rawAgent=remote.getValue('agentmode');
const agentmode=rawAgent==null?1:Number(rawAgent); const agentmode=rawAgent==null?1:Number(rawAgent);
if(![1,2,3].includes(agentmode))throw Error('Invalid agentmode'); if(![1,2,3].includes(agentmode))throw Error('Invalid agentmode');
return Object.freeze({agentmode,hall:Object.freeze(hall),sys:Object.freeze(sys),template}); // 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 type LobbyMenuConfig=ReturnType<typeof resolveLobbyMenuConfig>;
/** 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;
}
export interface LobbyMenuState {visible:Record<number,boolean>;left:Record<number,number>;top:Record<number,number>;} 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. */ /** Ordered projection of JumpMenuScene. Unmentioned nodes retain their authored state. */
@@ -111,11 +122,10 @@ export const LOBBY_MENU_ACTIONS=Object.freeze({
export type LobbyAction=typeof LOBBY_MENU_ACTIONS[keyof typeof LOBBY_MENU_ACTIONS]; export type LobbyAction=typeof LOBBY_MENU_ACTIONS[keyof typeof LOBBY_MENU_ACTIONS];
export const LOBBY_UNAVAILABLE:Readonly<Partial<Record<LobbyAction,string>>>=Object.freeze({ export const LOBBY_UNAVAILABLE:Readonly<Partial<Record<LobbyAction,string>>>=Object.freeze({
'create-room':'创建房间:尚未接入目标子游戏规则','records':'战绩:尚未接入子游戏战绩模块', 'create-room':'创建房间:尚未接入目标子游戏规则','records':'战绩:尚未接入子游戏战绩模块',
'buy-roomcard':'购买房卡:尚未接入支付模块','buy-bean':'购买星星:尚未接入支付模块',
match:'比赛:尚未接入比赛模块','world-rooms':'星星场:尚未接入房间列表',store:'商城:尚未接入商城身份参数与原生接口', match:'比赛:尚未接入比赛模块','world-rooms':'星星场:尚未接入房间列表',store:'商城:尚未接入商城身份参数与原生接口',
activity:'活动:尚未接入活动身份参数与原生接口','my-rooms':'我的房间:尚未接入房间记录与进房',qrcode:'二维码:尚未接入分享模块', activity:'活动:尚未接入活动身份参数与原生接口',
'official-rooms':'官方试玩:尚未接入微房号列表','more-games':'更多游戏:尚未接入返回大厅与下载入口', 'official-rooms':'官方试玩:尚未接入微房号列表','more-games':'更多游戏:尚未接入返回大厅与下载入口',
rank:'排行:尚未接入排行榜', 'vip-rank':'VIP 排行:尚未接入排行榜','bind-phone':'绑定手机:尚未接入绑定流程', 'bind-phone':'绑定手机:尚未接入绑定流程',
cdkey:'兑换码:尚未接入兑换请求',bag:'背包:尚未接入背包身份参数与原生接口',agent:'代理:尚未接入代理入口', bag:'背包:尚未接入背包身份参数与原生接口',agent:'代理:尚未接入代理入口',
warehouse:'保险箱:尚未接入仓库模块',feedback:'反馈:尚未接入意见提交', warehouse:'保险箱:尚未接入仓库模块',feedback:'反馈:尚未接入意见提交',
}); });
@@ -19,6 +19,14 @@ export function selectLobbyModel(state:PlatformState) {
}); });
} }
export type LobbyModel=ReturnType<typeof selectLobbyModel>; export type LobbyModel=ReturnType<typeof selectLobbyModel>;
/** 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 Notice {type:0|1; text:string; image:string; url:string; height:number;}
export interface LobbyPolicy {noticeHours:number; notices:readonly Notice[]; scroll:string; export interface LobbyPolicy {noticeHours:number; notices:readonly Notice[]; scroll:string;
starName:string; roomCardName:string; contacts:readonly {label:string;value:string}[];} starName:string; roomCardName:string; contacts:readonly {label:string;value:string}[];}
@@ -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,4 +1,12 @@
import type { RuntimeConfig } from '../config/runtime-config.ts'; import type { RuntimeConfig } from '../config/runtime-config.ts';
/** 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. */ /** Source: 12_Logic.getConfig_Succ then 05_Func.sharefriend. */
export function resolveSharePolicy(config:RuntimeConfig){ export function resolveSharePolicy(config:RuntimeConfig){
const remote=config.remoteConfig,raw=remote.getValue('hall_config'); const remote=config.remoteConfig,raw=remote.getValue('hall_config');
@@ -22,6 +22,10 @@ export {
type ParsedLoginResponse, type ParsedLoginResponse,
} from './login-contract.ts'; } from './login-contract.ts';
export { export {
buildCreateRoomRequest,
parseCreateRoomResponse,
type ParsedCreateRoomResponse,
type CreateRoomEnvironment,
buildExitRoomRequest, buildExitRoomRequest,
buildJoinRoomRequest, buildJoinRoomRequest,
buildPrepareRequest, buildPrepareRequest,
@@ -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": {}
}
@@ -98,7 +98,8 @@ export interface JoinRoomEnvironment {
readonly match_id?: string | number; readonly match_id?: string | number;
} }
export interface JoinRoomPayload extends RoomActionPayload { export interface JoinRoomPayload extends Omit<RoomActionPayload, 'roomcode'> {
readonly roomcode: string | number;
readonly location: unknown; readonly location: unknown;
readonly ip: string; readonly ip: string;
readonly vipMatch?: 1; readonly vipMatch?: 1;
@@ -107,7 +108,7 @@ export interface JoinRoomPayload extends RoomActionPayload {
export function buildJoinRoomRequest( export function buildJoinRoomRequest(
runtimeConfig: FirstSliceRuntimeConfig, runtimeConfig: FirstSliceRuntimeConfig,
room: RoomIdentity, room: { readonly playerid: number; readonly roomcode: string | number },
environment: JoinRoomEnvironment, environment: JoinRoomEnvironment,
): FirstSliceEnvelope<'self_join_room', JoinRoomPayload> { ): FirstSliceEnvelope<'self_join_room', JoinRoomPayload> {
const environmentRecord = requireRecord(environment, '$.environment'); const environmentRecord = requireRecord(environment, '$.environment');
@@ -118,7 +119,8 @@ export function buildJoinRoomRequest(
requireRecord(environmentRecord.location, '$.environment.location'); requireRecord(environmentRecord.location, '$.environment.location');
} }
const data: Record<string, unknown> = { const data: Record<string, unknown> = {
...baseRoomAction(runtimeConfig, room), ...baseRoomAction(runtimeConfig, {playerid: room.playerid, roomcode: String(room.roomcode)}),
roomcode: requireStringOrFiniteNumber(room.roomcode, '$.room.roomcode'),
location: environmentRecord.location, location: environmentRecord.location,
ip: requireString(environmentRecord.ip, '$.environment.ip'), ip: requireString(environmentRecord.ip, '$.environment.ip'),
}; };
@@ -241,3 +243,60 @@ export function parseKickServerPayload(value: unknown): ParsedKickPayload {
const raw = requireRecord(value, '$.data'); const raw = requireRecord(value, '$.data');
return { msg: requireString(raw.msg, '$.data.msg'), raw }; 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,
};
}
@@ -7,12 +7,19 @@ export function isLoginResponse(message:{route:string;rpc:string}):boolean{
} }
export type FirstSliceRpc = export type FirstSliceRpc =
| 'getVipRankList'
| 'get_treasurelist'
| 'topup_card'
| 'get_player_invitecode'
| 'binding_invitecode'
| 'getInfoByShortCode' | 'getInfoByShortCode'
| 'get_player_task' | 'get_player_task'
| 'get_task_award' | 'get_task_award'
| 'update_bean'
| 'update_roomcard' | 'update_roomcard'
| 'player_finish_task' | 'player_finish_task'
| 'player_login' | 'player_login'
| 'create_room'
| 'self_join_room' | 'self_join_room'
| 'player_prepare' | 'player_prepare'
| 'self_exit_room' | 'self_exit_room'
@@ -30,12 +37,19 @@ export type FirstSliceRoute = 'platform' | 'agent' | 'room';
/** 第一纵向切片 rpc → route 的唯一权威映射。 */ /** 第一纵向切片 rpc → route 的唯一权威映射。 */
export const FIRST_SLICE_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', getInfoByShortCode: 'agent',
get_player_task: 'agent', get_player_task: 'agent',
get_task_award: 'agent', get_task_award: 'agent',
update_bean: 'agent',
update_roomcard: 'agent', update_roomcard: 'agent',
player_finish_task: 'agent', player_finish_task: 'agent',
player_login: 'agent', player_login: 'agent',
create_room: 'agent',
self_join_room: 'agent', self_join_room: 'agent',
player_prepare: 'room', player_prepare: 'room',
self_exit_room: 'room', self_exit_room: 'room',
@@ -28,7 +28,7 @@ export interface PlatformHandlersOptions {
readonly gameSession: GameSessionHost; readonly gameSession: GameSessionHost;
readonly session: RuntimeSession; readonly session: RuntimeSession;
readonly connection: ConnectionControlPort; readonly connection: ConnectionControlPort;
readonly showMessage?: (message:string,time:number,source:'show_message'|'update_roomcard')=>void; readonly showMessage?: (message:string,time:number,source:'show_message'|'update_roomcard'|'update_bean'|'binding_invitecode')=>void;
} }
export type PlatformDispatchResult = 'handled-platform'; export type PlatformDispatchResult = 'handled-platform';
@@ -50,14 +50,33 @@ export class PlatformHandlers {
} }
const rpc = message.rpc as FirstSliceRpc; const rpc = message.rpc as FirstSliceRpc;
const expectedRoute = routeForFirstSlice(rpc); const expectedRoute = routeForFirstSlice(rpc);
if (message.route !== expectedRoute && !isLoginResponse(message)) { if (message.route !== expectedRoute && !isLoginResponse(message) && !(rpc==='update_bean'&&message.route==='room')) {
throw new Error( throw new Error(
`Platform rpc route mismatch: rpc=${rpc} expected=${expectedRoute} received=${message.route}`, `Platform rpc route mismatch: rpc=${rpc} expected=${expectedRoute} received=${message.route}`,
); );
} }
switch (rpc) { 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 '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': { case 'update_roomcard': {
const raw=requireRecord(message.data,'$.data'); const raw=requireRecord(message.data,'$.data');
const text=raw.text?requireString(raw.text,'$.data.text'):null; const text=raw.text?requireString(raw.text,'$.data.text'):null;
@@ -74,6 +93,9 @@ export class PlatformHandlers {
case 'player_login': case 'player_login':
this.options.session.handleLogin(message.data); this.options.session.handleLogin(message.data);
break; break;
case 'create_room':
this.options.session.handleCreateRoom(message.data);
break;
case 'self_join_room': case 'self_join_room':
this.options.session.handleSelfJoin(message.data); this.options.session.handleSelfJoin(message.data);
break; break;
@@ -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;
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "fd4c9495-d6d5-49da-8476-a510df24f1c4",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -1,4 +1,5 @@
export * from './game-entry.ts'; export * from './game-entry.ts';
export * from './create-room-page.ts';
export * from './game-host.ts'; export * from './game-host.ts';
export * from './game-module.ts'; export * from './game-module.ts';
export * from './game-seat-mapper.ts'; export * from './game-seat-mapper.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
}
]
@@ -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
}
]
@@ -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"
}
}
@@ -49,10 +49,10 @@
"height": 98, "height": 98,
"rawWidth": 108, "rawWidth": 108,
"rawHeight": 98, "rawHeight": 98,
"borderTop": 48, "borderTop": 26,
"borderBottom": 48, "borderBottom": 34,
"borderLeft": 53, "borderLeft": 30,
"borderRight": 53, "borderRight": 31,
"packable": true, "packable": true,
"pixelsToUnit": 100, "pixelsToUnit": 100,
"pivotX": 0.5, "pivotX": 0.5,
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

@@ -45,9 +45,9 @@
"offsetY": 0, "offsetY": 0,
"trimX": 0, "trimX": 0,
"trimY": 0, "trimY": 0,
"width": 1600, "width": 1280,
"height": 720, "height": 720,
"rawWidth": 1600, "rawWidth": 1280,
"rawHeight": 720, "rawHeight": 720,
"borderTop": 0, "borderTop": 0,
"borderBottom": 0, "borderBottom": 0,
@@ -60,16 +60,16 @@
"meshType": 0, "meshType": 0,
"vertices": { "vertices": {
"rawPosition": [ "rawPosition": [
-800, -640,
-360, -360,
0, 0,
800, 640,
-360, -360,
0, 0,
-800, -640,
360, 360,
0, 0,
800, 640,
360, 360,
0 0
], ],
@@ -84,11 +84,11 @@
"uv": [ "uv": [
0, 0,
720, 720,
1600, 1280,
720, 720,
0, 0,
0, 0,
1600, 1280,
0 0
], ],
"nuv": [ "nuv": [
@@ -102,12 +102,12 @@
1 1
], ],
"minPos": [ "minPos": [
-800, -640,
-360, -360,
0 0
], ],
"maxPos": [ "maxPos": [
800, 640,
360, 360,
0 0
] ]
@@ -0,0 +1,9 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "ad5e871f-b5d8-439a-852b-53d4d1d9f61b",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -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,12 +2,12 @@
"ver": "1.1.50", "ver": "1.1.50",
"importer": "prefab", "importer": "prefab",
"imported": true, "imported": true,
"uuid": "b13f487a-451e-41ce-a810-1fab0d52248f", "uuid": "67117675-9f6d-4def-a67c-db0e9fb19b98",
"files": [ "files": [
".json" ".json"
], ],
"subMetas": {}, "subMetas": {},
"userData": { "userData": {
"syncNodeName": "Layer9_Feedback" "syncNodeName": "OwnerRoomItem"
} }
} }
@@ -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
}
]
@@ -0,0 +1,13 @@
{
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": "9e6dbfc6-26c5-4eac-9cc8-8060a677d5ee",
"files": [
".json"
],
"subMetas": {},
"userData": {
"syncNodeName": "PlayerInfoTextItem"
}
}
@@ -0,0 +1,13 @@
{
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": "5925d1d2-f4b2-44c1-a37a-fb2bcdcf664e",
"files": [
".json"
],
"subMetas": {},
"userData": {
"syncNodeName": "VipRankItem"
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -25,14 +25,14 @@
"_active": true, "_active": true,
"_components": [ "_components": [
{ {
"__id__": 24 "__id__": 25
}, },
{ {
"__id__": 26 "__id__": 27
} }
], ],
"_prefab": { "_prefab": {
"__id__": 28 "__id__": 29
}, },
"_lpos": { "_lpos": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
@@ -82,14 +82,14 @@
"_active": true, "_active": true,
"_components": [ "_components": [
{ {
"__id__": 19 "__id__": 20
}, },
{ {
"__id__": 21 "__id__": 22
} }
], ],
"_prefab": { "_prefab": {
"__id__": 23 "__id__": 24
}, },
"_lpos": { "_lpos": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
@@ -186,6 +186,7 @@
"__id__": 5 "__id__": 5
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size",
"width": 1600, "width": 1600,
"height": 720 "height": 720
}, },
@@ -313,10 +314,13 @@
}, },
{ {
"__id__": 16 "__id__": 16
},
{
"__id__": 18
} }
], ],
"_prefab": { "_prefab": {
"__id__": 18 "__id__": 19
}, },
"_lpos": { "_lpos": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
@@ -360,6 +364,7 @@
"__id__": 13 "__id__": 13
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size",
"width": 150, "width": 150,
"height": 150 "height": 150
}, },
@@ -455,6 +460,29 @@
"__type__": "cc.CompPrefabInfo", "__type__": "cc.CompPrefabInfo",
"fileId": "3wlBO4ciKrxeTYsQdlQvBg" "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", "__type__": "cc.PrefabInfo",
"root": { "root": {
@@ -478,9 +506,10 @@
}, },
"_enabled": true, "_enabled": true,
"__prefab": { "__prefab": {
"__id__": 20 "__id__": 21
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size",
"width": 1600, "width": 1600,
"height": 720 "height": 720
}, },
@@ -505,7 +534,7 @@
}, },
"_enabled": true, "_enabled": true,
"__prefab": { "__prefab": {
"__id__": 22 "__id__": 23
}, },
"_alignFlags": 45, "_alignFlags": 45,
"_target": null, "_target": null,
@@ -554,9 +583,10 @@
}, },
"_enabled": true, "_enabled": true,
"__prefab": { "__prefab": {
"__id__": 25 "__id__": 26
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size",
"width": 1600, "width": 1600,
"height": 720 "height": 720
}, },
@@ -581,7 +611,7 @@
}, },
"_enabled": true, "_enabled": true,
"__prefab": { "__prefab": {
"__id__": 27 "__id__": 28
}, },
"_alignFlags": 45, "_alignFlags": 45,
"_target": null, "_target": null,
@@ -1,7 +1,7 @@
[ [
{ {
"__type__": "cc.Prefab", "__type__": "cc.Prefab",
"_name": "Login_Layer", "_name": "Login",
"_objFlags": 0, "_objFlags": 0,
"__editorExtras__": {}, "__editorExtras__": {},
"_native": "", "_native": "",
@@ -13,7 +13,7 @@
}, },
{ {
"__type__": "cc.Node", "__type__": "cc.Node",
"_name": "Login_Layer", "_name": "Login",
"_objFlags": 0, "_objFlags": 0,
"__editorExtras__": {}, "__editorExtras__": {},
"_parent": null, "_parent": null,
@@ -8,7 +8,7 @@
], ],
"subMetas": {}, "subMetas": {},
"userData": { "userData": {
"syncNodeName": "Login_Layer", "syncNodeName": "Login",
"hasIcon": false "hasIcon": false
} }
} }
@@ -8,6 +8,6 @@
], ],
"subMetas": {}, "subMetas": {},
"userData": { "userData": {
"syncNodeName": "Layer4_MainMenu" "syncNodeName": "MainMenu"
} }
} }
@@ -0,0 +1,13 @@
{
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": "e7a05100-7de4-4935-896e-a59061377b87",
"files": [
".json"
],
"subMetas": {},
"userData": {
"syncNodeName": "NoticeBoard"
}
}
@@ -0,0 +1,13 @@
{
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": "260ea2dc-696c-4a65-a2fd-109dd794bb45",
"files": [
".json"
],
"subMetas": {},
"userData": {
"syncNodeName": "OwnedRoomList"
}
}
@@ -0,0 +1,13 @@
{
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": "95ee9b79-8c30-4f54-a2cf-19d02083cd28",
"files": [
".json"
],
"subMetas": {},
"userData": {
"syncNodeName": "QrCode"
}
}
@@ -0,0 +1,13 @@
{
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": "ada34847-93c7-48f2-bb0a-5c1db4b112f6",
"files": [
".json"
],
"subMetas": {},
"userData": {
"syncNodeName": "RedeemCode"
}
}
@@ -1,7 +1,7 @@
[ [
{ {
"__type__": "cc.Prefab", "__type__": "cc.Prefab",
"_name": "Layer1_Logo", "_name": "ResourceLoading",
"_objFlags": 0, "_objFlags": 0,
"__editorExtras__": {}, "__editorExtras__": {},
"_native": "", "_native": "",
@@ -13,22 +13,19 @@
}, },
{ {
"__type__": "cc.Node", "__type__": "cc.Node",
"_name": "Layer1_Logo", "_name": "ResourceLoading",
"_objFlags": 0, "_objFlags": 0,
"__editorExtras__": {}, "__editorExtras__": {},
"_parent": null, "_parent": null,
"_children": [ "_children": [
{ {
"__id__": 2 "__id__": 2
},
{
"__id__": 50
} }
], ],
"_active": true, "_active": true,
"_components": [], "_components": [],
"_prefab": { "_prefab": {
"__id__": 60 "__id__": 50
}, },
"_lpos": { "_lpos": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
@@ -1130,241 +1127,6 @@
"targetOverrides": null, "targetOverrides": null,
"nestedPrefabInstanceRoots": 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", "__type__": "cc.PrefabInfo",
"root": { "root": {
@@ -8,6 +8,6 @@
], ],
"subMetas": {}, "subMetas": {},
"userData": { "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": {}
}
@@ -1249,7 +1249,7 @@
"shareh5desc": "一起来嗨~", "shareh5desc": "一起来嗨~",
"gameNoticeShow": 0, "gameNoticeShow": 0,
"shareType":1, "shareType":1,
"showVisitor":1, "showVisitor":0,
"shareImgUrl":"https://tsgames.daoqi88.cn/image/match.png", "shareImgUrl":"https://tsgames.daoqi88.cn/image/match.png",
"hallNoticeShow": 0, "hallNoticeShow": 0,
"gameHallConfig": { "gameHallConfig": {
@@ -52,7 +52,7 @@
}, },
"autoReleaseAssets": false, "autoReleaseAssets": false,
"_globals": { "_globals": {
"__id__": 5 "__id__": 6
}, },
"_id": "1c10c2ee-adc2-496f-9005-d3e0e6ebbb56" "_id": "1c10c2ee-adc2-496f-9005-d3e0e6ebbb56"
}, },
@@ -72,6 +72,9 @@
}, },
{ {
"__id__": 4 "__id__": 4
},
{
"__id__": 5
} }
], ],
"_prefab": null, "_prefab": null,
@@ -182,8 +185,24 @@
"__uuid__": "e1b53f04-3e2f-4127-9a3f-f629bed6905e", "__uuid__": "e1b53f04-3e2f-4127-9a3f-f629bed6905e",
"__expectedType__": "cc.Prefab" "__expectedType__": "cc.Prefab"
}, },
"notices": { "redeemCode": {
"__uuid__": "b13f487a-451e-41ce-a810-1fab0d52248f", "__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" "__expectedType__": "cc.Prefab"
}, },
"profile": { "profile": {
@@ -206,6 +225,100 @@
"__uuid__": "f4af0cc2-2514-4bd5-a0de-7a3dbb32438b", "__uuid__": "f4af0cc2-2514-4bd5-a0de-7a3dbb32438b",
"__expectedType__": "cc.Prefab" "__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": [ "digits": [
{ {
"__uuid__": "fc6e9a8e-0a6d-4555-bc3e-88fd427b9f3a@f9941", "__uuid__": "fc6e9a8e-0a6d-4555-bc3e-88fd427b9f3a@f9941",
@@ -272,32 +385,56 @@
], ],
"_id": "abZXhqPblEq57+36N2j8xH" "_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", "__type__": "cc.SceneGlobals",
"ambient": { "ambient": {
"__id__": 6
},
"shadows": {
"__id__": 7 "__id__": 7
}, },
"_skybox": { "shadows": {
"__id__": 8 "__id__": 8
}, },
"fog": { "_skybox": {
"__id__": 9 "__id__": 9
}, },
"octree": { "fog": {
"__id__": 10 "__id__": 10
}, },
"skin": { "octree": {
"__id__": 11 "__id__": 11
}, },
"lightProbeInfo": { "skin": {
"__id__": 12 "__id__": 12
}, },
"postSettings": { "lightProbeInfo": {
"__id__": 13 "__id__": 13
}, },
"postSettings": {
"__id__": 14
},
"bakedWithStationaryMainLight": false, "bakedWithStationaryMainLight": false,
"bakedWithHighpLightmap": 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);
}
}
@@ -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": {}
}
@@ -6,12 +6,23 @@ export class LobbyAssets extends Component {
@property(Prefab) menu:Prefab|null=null; @property(Prefab) menu:Prefab|null=null;
@property(Prefab) header:Prefab|null=null; @property(Prefab) header:Prefab|null=null;
@property(Prefab) noticeMenu:Prefab|null=null; @property(Prefab) noticeMenu:Prefab|null=null;
@property(Prefab) notices: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) profile:Prefab|null=null;
@property(Prefab) settings:Prefab|null=null; @property(Prefab) settings:Prefab|null=null;
@property(Prefab) join:Prefab|null=null; @property(Prefab) join:Prefab|null=null;
@property(Prefab) tasks:Prefab|null=null; @property(Prefab) tasks:Prefab|null=null;
@property(Prefab) share:Prefab|null=null; @property(Prefab) share:Prefab|null=null;
@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]) digits:SpriteFrame[]=[];
@property([SpriteFrame]) switches:SpriteFrame[]=[]; @property([SpriteFrame]) switches:SpriteFrame[]=[];
} }

Some files were not shown because too many files have changed in this diff Show More