/** * TeamRoomScene.js * 3v3 Team room UI scene. * Supports team creation, joining, ready state, leader controls, * matchmaking, and WeChat friend invitation. */ const { SCREEN_WIDTH, SCREEN_HEIGHT, COLORS, SCENE, NET_MSG, TEAM_SIZE, SERVER_URL, } = require('../base/GameGlobal'); const { t } = require('../i18n/I18n'); // ============================================================ // Layout Constants // ============================================================ const BTN_WIDTH = Math.min(SCREEN_WIDTH * 0.35, 180); const BTN_HEIGHT = Math.min(36, SCREEN_HEIGHT * 0.07); const BTN_GAP = 10; const CENTER_X = SCREEN_WIDTH / 2; const SLOT_WIDTH = Math.min(SCREEN_WIDTH * 0.15, 80); const SLOT_HEIGHT = Math.min(SCREEN_HEIGHT * 0.18, 90); const SLOT_GAP = 8; // ============================================================ // Team Room States // ============================================================ const TEAM_STATE = { MODE_SELECT: 'mode_select', // Choose: create team or solo match JOINING: 'joining', // Auto-joining a team from invite FORMING: 'forming', // Team room, waiting for members MATCHING: 'matching', // In matchmaking queue COUNTDOWN: 'countdown', // Match found, counting down ERROR: 'error', // Error state }; // ============================================================ // Team Room Scene // ============================================================ const TeamRoomScene = { _state: TEAM_STATE.MODE_SELECT, _teamData: null, // { teamId, state, leaderId, teamA, teamB } _errorMsg: '', _animTimer: 0, _matchTimer: 0, // Seconds elapsed in matching _countdown: 3, _countdownTimer: 0, _networkManager: null, _unsubscribers: [], _isLeader: false, _myPlayerId: null, // Server URL (from global config) _serverUrl: SERVER_URL, // Button rects _createTeamBtnRect: null, _soloMatchBtnRect: null, _backBtnRect: null, _inviteBtnRect: null, _matchBtnRect: null, _readyBtnRect: null, _disbandBtnRect: null, _leaveBtnRect: null, _cancelMatchBtnRect: null, _slotRects: [], _kickBtnRects: [], enter(params) { this._state = TEAM_STATE.MODE_SELECT; this._teamData = null; this._errorMsg = ''; this._animTimer = 0; this._matchTimer = 0; this._countdown = 3; this._countdownTimer = 0; this._networkManager = GameGlobal.networkManager; this._myPlayerId = this._networkManager ? this._networkManager.playerId : 'player_' + Date.now(); this._isLeader = false; this._buildLayout(); // Setup network events BEFORE auto-join so listeners are ready this._setupNetworkEvents(); // If entering with a teamId (from invite card), auto-join if (params && params.teamId) { this._autoJoinTeam(params.teamId); } }, /** * Update share content so that any share from the top-right menu * always carries the current teamId. * @private */ _updateShareContent() { if (!this._teamData || !this._teamData.teamId) return; const shareManager = GameGlobal.shareManager; if (shareManager) { shareManager.setShareContent({ title: t('teamRoom.shareTitle'), imageUrl: '', query: `teamId=${this._teamData.teamId}`, }); } }, exit() { this._cleanupNetworkEvents(); // Reset share content when leaving team room const shareManager = GameGlobal.shareManager; if (shareManager) { shareManager.resetShareContent(); } }, _buildLayout() { const modeY = SCREEN_HEIGHT * 0.4; this._createTeamBtnRect = { x: CENTER_X - BTN_WIDTH / 2, y: modeY, w: BTN_WIDTH, h: BTN_HEIGHT, }; this._soloMatchBtnRect = { x: CENTER_X - BTN_WIDTH / 2, y: modeY + BTN_HEIGHT + BTN_GAP, w: BTN_WIDTH, h: BTN_HEIGHT, }; this._backBtnRect = { x: 10, y: 10, w: 60, h: 30, }; // Team member slots (5 slots in a row) const totalSlotsWidth = TEAM_SIZE * SLOT_WIDTH + (TEAM_SIZE - 1) * SLOT_GAP; const slotsStartX = CENTER_X - totalSlotsWidth / 2; const slotsY = SCREEN_HEIGHT * 0.25; this._slotRects = []; this._kickBtnRects = []; for (let i = 0; i < TEAM_SIZE; i++) { const x = slotsStartX + i * (SLOT_WIDTH + SLOT_GAP); this._slotRects.push({ x, y: slotsY, w: SLOT_WIDTH, h: SLOT_HEIGHT, }); // Kick button (small X at top-right of slot) this._kickBtnRects.push({ x: x + SLOT_WIDTH - 18, y: slotsY + 2, w: 16, h: 16, }); } // Action buttons (below slots) const actionY = SCREEN_HEIGHT * 0.58; const smallBtnW = BTN_WIDTH * 0.8; this._inviteBtnRect = { x: CENTER_X - smallBtnW - BTN_GAP / 2, y: actionY, w: smallBtnW, h: BTN_HEIGHT, }; this._matchBtnRect = { x: CENTER_X + BTN_GAP / 2, y: actionY, w: smallBtnW, h: BTN_HEIGHT, }; this._readyBtnRect = { x: CENTER_X - smallBtnW / 2, y: actionY, w: smallBtnW, h: BTN_HEIGHT, }; this._disbandBtnRect = { x: CENTER_X - smallBtnW - BTN_GAP / 2, y: actionY + BTN_HEIGHT + BTN_GAP, w: smallBtnW, h: BTN_HEIGHT, }; this._leaveBtnRect = { x: CENTER_X - smallBtnW / 2, y: actionY + BTN_HEIGHT + BTN_GAP, w: smallBtnW, h: BTN_HEIGHT, }; this._cancelMatchBtnRect = { x: CENTER_X - BTN_WIDTH / 2, y: SCREEN_HEIGHT * 0.7, w: BTN_WIDTH, h: BTN_HEIGHT, }; }, _setupNetworkEvents() { this._cleanupNetworkEvents(); const nm = this._networkManager; if (!nm) return; const unsubs = []; unsubs.push(nm.on(NET_MSG.TEAM_STATE, (data) => { this._teamData = data; this._isLeader = data.leaderId === this._myPlayerId; if (data.state === 'forming') { this._state = TEAM_STATE.FORMING; } else if (data.state === 'matching') { this._state = TEAM_STATE.MATCHING; } // Keep share content up-to-date with current teamId this._updateShareContent(); })); unsubs.push(nm.on(NET_MSG.TEAM_DISBAND, (data) => { this._teamData = null; this._state = TEAM_STATE.MODE_SELECT; if (data.reason === 'kicked') { this._errorMsg = t('common.kicked'); this._state = TEAM_STATE.ERROR; } })); unsubs.push(nm.on(NET_MSG.MATCH_FOUND, () => { this._state = TEAM_STATE.COUNTDOWN; this._countdown = 3; this._countdownTimer = 0; })); unsubs.push(nm.on(NET_MSG.TEAM_GAME_START, (data) => { this._startTeamGame(data); })); unsubs.push(nm.on(NET_MSG.ROOM_ERROR, (data) => { this._errorMsg = data.message || 'Unknown error'; // Only switch to error state if not already in game transition if (this._state !== TEAM_STATE.COUNTDOWN) { this._state = TEAM_STATE.ERROR; } })); unsubs.push(nm.on('error', () => { this._errorMsg = t('common.connectFailed'); this._state = TEAM_STATE.ERROR; })); unsubs.push(nm.on('disconnected', () => { if (this._state !== TEAM_STATE.MODE_SELECT) { this._errorMsg = t('common.disconnected'); this._state = TEAM_STATE.ERROR; } })); this._unsubscribers = unsubs; }, _cleanupNetworkEvents() { for (const unsub of this._unsubscribers) { if (typeof unsub === 'function') unsub(); } this._unsubscribers = []; }, update(dt) { this._animTimer += dt; if (this._state === TEAM_STATE.MATCHING) { this._matchTimer += dt; } if (this._state === TEAM_STATE.COUNTDOWN) { this._countdownTimer += dt; if (this._countdownTimer >= 1) { this._countdownTimer -= 1; this._countdown--; } } }, _startTeamGame(data) { const sm = GameGlobal.sceneManager; if (!sm._scenes.has(SCENE.TEAM_GAME)) { const TeamGameScene = require('./TeamGameScene'); sm.register(SCENE.TEAM_GAME, TeamGameScene); } sm.switchTo(SCENE.TEAM_GAME, { teamId: this._teamData ? this._teamData.teamId : null, mapId: data.mapId, teamA: data.teamA, teamB: data.teamB, teamABaseHp: data.teamABaseHp, teamBBaseHp: data.teamBBaseHp, myPlayerId: this._myPlayerId, }); }, render(ctx) { // Background ctx.fillStyle = COLORS.MENU_BG; ctx.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); // Top accent bar const gradient = ctx.createLinearGradient(0, 0, SCREEN_WIDTH, 0); gradient.addColorStop(0, '#0f3460'); gradient.addColorStop(0.5, '#e94560'); gradient.addColorStop(1, '#0f3460'); ctx.fillStyle = gradient; ctx.fillRect(0, 0, SCREEN_WIDTH, 4); // Back button this._drawButton(ctx, this._backBtnRect, t('common.back'), false, 12); // Title ctx.fillStyle = COLORS.MENU_TITLE; ctx.font = 'bold 22px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(t('teamRoom.title'), CENTER_X, SCREEN_HEIGHT * 0.08); switch (this._state) { case TEAM_STATE.MODE_SELECT: this._renderModeSelect(ctx); break; case TEAM_STATE.JOINING: this._renderJoining(ctx); break; case TEAM_STATE.FORMING: this._renderForming(ctx); break; case TEAM_STATE.MATCHING: this._renderMatching(ctx); break; case TEAM_STATE.COUNTDOWN: this._renderCountdown(ctx); break; case TEAM_STATE.ERROR: this._renderError(ctx); break; } }, _renderJoining(ctx) { const dots = '.'.repeat(Math.floor(this._animTimer * 3) % 4); ctx.fillStyle = '#FFFFFF'; ctx.font = '18px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(t('teamRoom.joining') + dots, CENTER_X, SCREEN_HEIGHT * 0.45); }, _renderModeSelect(ctx) { ctx.fillStyle = '#AAAAAA'; ctx.font = '14px Arial'; ctx.textAlign = 'center'; ctx.fillText(t('teamRoom.chooseMode'), CENTER_X, SCREEN_HEIGHT * 0.28); this._drawButton(ctx, this._createTeamBtnRect, t('teamRoom.createTeam')); this._drawButton(ctx, this._soloMatchBtnRect, t('teamRoom.soloMatch')); }, _renderForming(ctx) { if (!this._teamData) return; // Team ID display ctx.fillStyle = '#AAAAAA'; ctx.font = '12px Arial'; ctx.textAlign = 'center'; ctx.fillText(t('teamRoom.teamId', { id: this._teamData.teamId }), CENTER_X, SCREEN_HEIGHT * 0.16); // Render team member slots const members = this._teamData.teamA || []; for (let i = 0; i < TEAM_SIZE; i++) { const rect = this._slotRects[i]; const member = members[i]; // Slot background ctx.fillStyle = member ? '#1e3a5f' : '#0d1b2a'; ctx.strokeStyle = member ? (member.isLeader ? '#FFD700' : '#0f3460') : '#333333'; ctx.lineWidth = member && member.isLeader ? 3 : 1; this._drawRoundRect(ctx, rect.x, rect.y, rect.w, rect.h, 8); ctx.fill(); ctx.stroke(); if (member) { // Avatar placeholder (circle) const avatarR = Math.min(rect.w, rect.h) * 0.22; const avatarCX = rect.x + rect.w / 2; const avatarCY = rect.y + rect.h * 0.3; ctx.fillStyle = member.isLeader ? '#FFD700' : '#4a90d9'; ctx.beginPath(); ctx.arc(avatarCX, avatarCY, avatarR, 0, Math.PI * 2); ctx.fill(); // Player icon ctx.fillStyle = '#FFFFFF'; ctx.font = `${avatarR}px Arial`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('🎖', avatarCX, avatarCY); // Leader badge if (member.isLeader) { ctx.fillStyle = '#FFD700'; ctx.font = 'bold 9px Arial'; ctx.fillText(t('teamRoom.leader'), avatarCX, avatarCY + avatarR + 10); } // Player name (truncated) ctx.fillStyle = '#FFFFFF'; ctx.font = '10px Arial'; const name = this._getDisplayName(member); ctx.fillText(name, avatarCX, rect.y + rect.h * 0.7); // Ready state if (!member.isLeader) { ctx.fillStyle = member.ready ? '#00FF00' : '#FF6347'; ctx.font = 'bold 10px Arial'; ctx.fillText(member.ready ? t('teamRoom.ready') : t('teamRoom.notReady'), avatarCX, rect.y + rect.h * 0.88); } // Kick button (only for leader, not on self) if (this._isLeader && !member.isLeader && member.playerId !== this._myPlayerId) { const kickRect = this._kickBtnRects[i]; ctx.fillStyle = '#FF4444'; ctx.font = 'bold 12px Arial'; ctx.fillText('✕', kickRect.x + kickRect.w / 2, kickRect.y + kickRect.h / 2); } } else { // Empty slot ctx.fillStyle = '#555555'; ctx.font = '24px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('+', rect.x + rect.w / 2, rect.y + rect.h / 2); ctx.fillStyle = '#666666'; ctx.font = '10px Arial'; ctx.fillText(t('teamRoom.emptySlot'), rect.x + rect.w / 2, rect.y + rect.h * 0.78); } } // Action buttons based on role if (this._isLeader) { this._drawButton(ctx, this._inviteBtnRect, t('teamRoom.invite')); // Match button: only enabled if all ready const allReady = members.length > 0 && members.every(m => m.ready || m.isLeader); this._drawButton(ctx, this._matchBtnRect, t('teamRoom.startMatch'), false, 14, allReady ? null : '#555555'); this._drawButton(ctx, this._disbandBtnRect, t('teamRoom.disband'), false, 12, '#8B0000'); } else { // Member: ready/unready button const myMember = members.find(m => m.playerId === this._myPlayerId); const readyLabel = myMember && myMember.ready ? t('teamRoom.cancelReady') : t('teamRoom.readyBtn'); this._drawButton(ctx, this._readyBtnRect, readyLabel); this._drawButton(ctx, this._leaveBtnRect, t('teamRoom.leaveTeam'), false, 12, '#8B0000'); } }, _renderMatching(ctx) { if (!this._teamData) return; // Render team slots (smaller, at top) const members = this._teamData.teamA || []; for (let i = 0; i < TEAM_SIZE; i++) { const rect = this._slotRects[i]; const member = members[i]; ctx.fillStyle = member ? '#1e3a5f' : '#0d1b2a'; ctx.strokeStyle = '#0f3460'; ctx.lineWidth = 1; this._drawRoundRect(ctx, rect.x, rect.y, rect.w, rect.h, 8); ctx.fill(); ctx.stroke(); if (member) { ctx.fillStyle = '#FFFFFF'; ctx.font = '10px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; const name = this._getDisplayName(member); ctx.fillText(name, rect.x + rect.w / 2, rect.y + rect.h / 2); } } // Matching animation const elapsed = Math.floor(this._matchTimer); const dots = '.'.repeat(Math.floor(this._animTimer * 3) % 4); ctx.fillStyle = '#FFFFFF'; ctx.font = '18px Arial'; ctx.textAlign = 'center'; ctx.fillText(t('teamRoom.matching', { dots }), CENTER_X, SCREEN_HEIGHT * 0.55); ctx.fillStyle = '#AAAAAA'; ctx.font = '14px Arial'; ctx.fillText(t('teamRoom.waitTime', { seconds: elapsed }), CENTER_X, SCREEN_HEIGHT * 0.62); // Cancel button (leader only) if (this._isLeader) { this._drawButton(ctx, this._cancelMatchBtnRect, t('teamRoom.cancelMatch')); } }, _renderCountdown(ctx) { ctx.fillStyle = '#00FF00'; ctx.font = '16px Arial'; ctx.textAlign = 'center'; ctx.fillText(t('teamRoom.matchFound'), CENTER_X, SCREEN_HEIGHT * 0.35); ctx.fillStyle = COLORS.MENU_TITLE; ctx.font = 'bold 64px Arial'; ctx.fillText(String(Math.max(1, this._countdown)), CENTER_X, SCREEN_HEIGHT * 0.52); ctx.fillStyle = '#AAAAAA'; ctx.font = '14px Arial'; ctx.fillText(t('teamRoom.enterBattle'), CENTER_X, SCREEN_HEIGHT * 0.65); }, _renderError(ctx) { ctx.fillStyle = '#FF4444'; ctx.font = '16px Arial'; ctx.textAlign = 'center'; ctx.fillText(this._errorMsg, CENTER_X, SCREEN_HEIGHT * 0.45); ctx.fillStyle = '#AAAAAA'; ctx.font = '14px Arial'; ctx.fillText(t('teamRoom.tapBack'), CENTER_X, SCREEN_HEIGHT * 0.55); }, _drawButton(ctx, rect, label, pressed, fontSize, bgColor) { if (!rect) return; const fs = fontSize || 14; ctx.fillStyle = bgColor || (pressed ? '#0f3460' : COLORS.MENU_BTN); ctx.strokeStyle = COLORS.MENU_BTN_BORDER; ctx.lineWidth = 2; this._drawRoundRect(ctx, rect.x, rect.y, rect.w, rect.h, 6); ctx.fill(); ctx.stroke(); ctx.fillStyle = pressed ? COLORS.MENU_TITLE : COLORS.MENU_BTN_TEXT; ctx.font = `bold ${fs}px Arial`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(label, rect.x + rect.w / 2, rect.y + rect.h / 2); }, _drawRoundRect(ctx, x, y, w, h, r) { ctx.beginPath(); ctx.moveTo(x + r, y); ctx.lineTo(x + w - r, y); ctx.arcTo(x + w, y, x + w, y + r, r); ctx.lineTo(x + w, y + h - r); ctx.arcTo(x + w, y + h, x + w - r, y + h, r); ctx.lineTo(x + r, y + h); ctx.arcTo(x, y + h, x, y + h - r, r); ctx.lineTo(x, y + r); ctx.arcTo(x, y, x + r, y, r); ctx.closePath(); }, _hitTest(tx, ty, rect) { if (!rect) return false; return tx >= rect.x && tx <= rect.x + rect.w && ty >= rect.y && ty <= rect.y + rect.h; }, /** * Compute a display name for a team member entry. * Uses real WeChat nickname when available, otherwise a stable fallback. * Truncated to 4 Chinese-equivalent chars to fit the slot UI. * @private */ _getDisplayName(member) { if (!member) return ''; const profile = GameGlobal.playerProfile; let raw = member.nickname || ''; if (!raw) { if (profile && typeof profile.getDisplayName === 'function') { raw = profile.getDisplayName(member.playerId); } else { raw = member.playerId || ''; } } if (profile && typeof profile.truncate === 'function') { return profile.truncate(raw, 4); } return raw.length > 8 ? raw.substring(0, 8) + '..' : raw; }, handleTouch(eventType, e) { if (eventType !== 'touchstart') return; const touch = e.touches[0]; const tx = touch.clientX; const ty = touch.clientY; // Back button if (this._hitTest(tx, ty, this._backBtnRect)) { this._goBack(); return; } switch (this._state) { case TEAM_STATE.MODE_SELECT: if (this._hitTest(tx, ty, this._createTeamBtnRect)) { this._handleCreateTeam(); } else if (this._hitTest(tx, ty, this._soloMatchBtnRect)) { this._handleSoloMatch(); } break; case TEAM_STATE.FORMING: this._handleFormingTouch(tx, ty); break; case TEAM_STATE.MATCHING: if (this._isLeader && this._hitTest(tx, ty, this._cancelMatchBtnRect)) { this._handleCancelMatch(); } break; case TEAM_STATE.ERROR: this._state = TEAM_STATE.MODE_SELECT; this._errorMsg = ''; break; } }, _handleFormingTouch(tx, ty) { if (!this._teamData) return; if (this._isLeader) { // Invite button if (this._hitTest(tx, ty, this._inviteBtnRect)) { this._handleInvite(); return; } // Match button if (this._hitTest(tx, ty, this._matchBtnRect)) { this._handleStartMatch(); return; } // Disband button if (this._hitTest(tx, ty, this._disbandBtnRect)) { this._handleDisband(); return; } // Kick buttons const members = this._teamData.teamA || []; for (let i = 0; i < members.length; i++) { const member = members[i]; if (member && !member.isLeader && member.playerId !== this._myPlayerId) { if (this._hitTest(tx, ty, this._kickBtnRects[i])) { this._handleKick(member.playerId); return; } } } } else { // Ready button if (this._hitTest(tx, ty, this._readyBtnRect)) { this._handleReady(); return; } // Leave button if (this._hitTest(tx, ty, this._leaveBtnRect)) { this._handleLeave(); return; } } }, async _handleCreateTeam() { const nm = this._networkManager; if (!nm) return; if (!nm.connected) { const ok = await nm.connect(this._serverUrl); if (!ok) { this._errorMsg = t('common.cannotConnect'); this._state = TEAM_STATE.ERROR; return; } } this._myPlayerId = nm.playerId; nm.send(NET_MSG.CREATE_TEAM, {}); }, async _handleSoloMatch() { const nm = this._networkManager; if (!nm) return; if (!nm.connected) { const ok = await nm.connect(this._serverUrl); if (!ok) { this._errorMsg = t('common.cannotConnect'); this._state = TEAM_STATE.ERROR; return; } } this._myPlayerId = nm.playerId; this._matchTimer = 0; // State will be updated by TEAM_STATE event from server nm.send(NET_MSG.SOLO_MATCH, {}); }, async _autoJoinTeam(teamId) { const nm = this._networkManager; if (!nm) { this._errorMsg = t('common.cannotConnect'); this._state = TEAM_STATE.ERROR; return; } // Show a joining indicator this._state = TEAM_STATE.JOINING; this._errorMsg = ''; try { if (!nm.connected) { const ok = await nm.connect(this._serverUrl); if (!ok) { this._errorMsg = t('common.cannotConnect'); this._state = TEAM_STATE.ERROR; return; } } this._myPlayerId = nm.playerId; console.log(`[TeamRoom] Auto-joining team ${teamId} as ${this._myPlayerId}`); nm.send(NET_MSG.JOIN_TEAM, { teamId }); } catch (e) { console.error('[TeamRoom] Auto-join failed:', e); this._errorMsg = t('common.cannotConnect'); this._state = TEAM_STATE.ERROR; } }, _handleInvite() { if (!this._teamData) return; const teamId = this._teamData.teamId; const shareData = { title: t('teamRoom.shareTitle'), imageUrl: '', query: `teamId=${teamId}`, }; console.log(`[TeamRoom] Sharing invite with query: teamId=${teamId}`); // WeChat mini-game policy: direct wx.shareAppMessage() calls are forbidden. // Must use passive sharing via onShareAppMessage callback. const shareManager = GameGlobal.shareManager; if (shareManager) { shareManager.triggerShare(shareData); } else { try { wx.showToast({ title: '请点击右上角 ··· 转发给好友', icon: 'none', duration: 2500, }); } catch (e) { console.log('[TeamRoom] Share not available, teamId:', teamId); } } }, _handleStartMatch() { const nm = this._networkManager; if (!nm || !this._teamData) return; // Check all members are ready before sending const members = this._teamData.teamA || []; const allReady = members.length > 0 && members.every(m => m.ready || m.isLeader); if (!allReady) return; this._matchTimer = 0; nm.send(NET_MSG.MATCH_START, {}); }, _handleCancelMatch() { const nm = this._networkManager; if (!nm) return; nm.send(NET_MSG.MATCH_CANCEL, {}); }, _handleReady() { const nm = this._networkManager; if (!nm || !this._teamData) return; const myMember = (this._teamData.teamA || []).find(m => m.playerId === this._myPlayerId); nm.send(NET_MSG.TEAM_READY, { ready: myMember ? !myMember.ready : true }); }, _handleKick(playerId) { const nm = this._networkManager; if (!nm) return; nm.send(NET_MSG.TEAM_KICK, { playerId }); }, _handleDisband() { const nm = this._networkManager; if (!nm) return; nm.send(NET_MSG.TEAM_DISBAND, {}); }, _handleLeave() { const nm = this._networkManager; if (!nm) return; nm.send(NET_MSG.LEAVE_TEAM, {}); this._teamData = null; this._state = TEAM_STATE.MODE_SELECT; }, _goBack() { // Leave team if in one if (this._teamData) { const nm = this._networkManager; if (nm) { if (this._state === TEAM_STATE.MATCHING && this._isLeader) { // Cancel match first, then disband nm.send(NET_MSG.MATCH_CANCEL, {}); } if (this._isLeader) { nm.send(NET_MSG.TEAM_DISBAND, {}); } else { nm.send(NET_MSG.LEAVE_TEAM, {}); } } } const sm = GameGlobal.sceneManager; sm.switchTo(SCENE.MENU); }, }; module.exports = TeamRoomScene;