|
|
|
|
import { _decorator, assetManager, Button, clamp01, Component, EventKeyboard, Input, input, instantiate, KeyCode, Label, macro, Node, Prefab, Sprite, sys, tween, v3, Vec3, view, Widget } from 'cc';
|
|
|
|
|
|
|
|
|
|
import { getHallWarmLongTailResPakage, getHallWarmResPakage, getHerosResPakage, getMainShellResPakage, GAudioPath, GBundle, GPath, ResPkgName, UIConfigs, UIID } from './ConfigRes';
|
|
|
|
|
import { ResPkgBuilder } from './ResPkgBuilder';
|
|
|
|
|
import { ItemData } from './ConfigProjectData';
|
|
|
|
|
import { GEvent } from '../../mx/module/event/GEvent';
|
|
|
|
|
import MTools from '../../mx/tools/MTools';
|
|
|
|
|
import {
|
|
|
|
|
createMembershipSet,
|
|
|
|
|
membershipAdd,
|
|
|
|
|
membershipToArray,
|
|
|
|
|
mapValuesToArray,
|
|
|
|
|
type MembershipSet,
|
|
|
|
|
} from '../../mx/tools/MembershipSet';
|
|
|
|
|
|
|
|
|
|
import { OtherDataType, StatisticsType, StatusType } from './GameData';
|
|
|
|
|
import { sdkConfig } from '../sdk/sdkConfig';
|
|
|
|
|
/** 用户信息 */
|
|
|
|
|
export interface IUserInfo {
|
|
|
|
|
nickName: string;
|
|
|
|
|
avatarUrl: string;
|
|
|
|
|
gender: string;
|
|
|
|
|
country: string;
|
|
|
|
|
province: string;
|
|
|
|
|
city: string;
|
|
|
|
|
language: string;
|
|
|
|
|
}
|
|
|
|
|
import type { ProjectBattleHandle } from '../battle/runtime/BattleRuntime';
|
|
|
|
|
import { PopToastParam, PopToastType } from '../ui/PopToast/UIPopToast';
|
|
|
|
|
import { PopCoinToastParam } from '../ui/PopCoinToast/UIPopCoinToast';
|
|
|
|
|
import { Player } from './Player';
|
|
|
|
|
import { DEV } from 'cc/env';
|
|
|
|
|
import { patchSpineSkeletonResetEnums } from '../../mx/module/lang/spineRuntimePatch';
|
|
|
|
|
import { BattleFlowMode } from '../../mx/module/battle/core/BattleFlowMode';
|
|
|
|
|
import { PreloadPredictor } from '../../mx/module/res/PreloadPredictor';
|
|
|
|
|
import { LoadScheduler } from '../../mx/module/res/LoadScheduler';
|
|
|
|
|
import { MemoryDowngrade } from './MemoryDowngrade';
|
|
|
|
|
import { AppLifecycle } from './AppLifecycle';
|
|
|
|
|
import { MemoryProfile } from './MemoryProfile';
|
|
|
|
|
import { PerfReport } from './PerfReport';
|
|
|
|
|
import { PerfLoadTimer } from './PerfLoadTimer';
|
|
|
|
|
import { BundleLifecycle } from '../../mx/module/res/BundleLifecycle';
|
|
|
|
|
import { ResourcePhase, ResourcePhaseGate } from '../../mx/module/res/ResourcePhaseGate';
|
|
|
|
|
import type { ResPakage } from '../../mx/module/res/ResourcesManager';
|
|
|
|
|
import { BattleAudio } from '../battle/audio/BattleAudio';
|
|
|
|
|
import { ChapterLevelBuilder } from '../battle/config/ChapterLevelBuilder';
|
|
|
|
|
import { RankDataManager } from '../manager/RankDataManager';
|
|
|
|
|
import { HeadIconManager } from '../manager/HeadIconManager';
|
|
|
|
|
import { RankBoardManager } from '../manager/RankBoardManager';
|
|
|
|
|
import { DailyShopDataManager } from '../manager/DailyShopDataManager';
|
|
|
|
|
import { EmolumentDataManager } from '../manager/EmolumentDataManager';
|
|
|
|
|
import { MatchWinStats } from '../manager/MatchWinStats';
|
|
|
|
|
import { AiBattleBridge } from '../battle/ai/bridge/AiBattleBridge';
|
|
|
|
|
import type { MatchGateHandlers } from '../ui/match/UImatch';
|
|
|
|
|
import { FrameFlowHost } from './FrameFlowHost';
|
|
|
|
|
import { awaitWithTimeout, loadWithRetry, LoadRecoveryConfig, type LoadResult } from '../../mx/module/res/LoadRecovery';
|
|
|
|
|
import { tryProactiveCacheTrimBeforePreload } from '../../mx/tools/CacheManagerPatch';
|
|
|
|
|
|
|
|
|
|
const { ccclass, property } = _decorator;
|
|
|
|
|
|
|
|
|
|
/** 进战斗附加选项(匹配页门闩) */
|
|
|
|
|
export type BattleEnterOpts = {
|
|
|
|
|
/** 匹配页已展示,跳过 loading 遮罩 */
|
|
|
|
|
skipLoading?: boolean;
|
|
|
|
|
/** 匹配页自行关闭,closeAllPanel 时保留 match */
|
|
|
|
|
deferCloseMatch?: boolean;
|
|
|
|
|
/** 战斗页 openPanel 结果 */
|
|
|
|
|
onBattleOpened?: (ok: boolean) => void;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
@ccclass('GameController')
|
|
|
|
|
export class GameController extends Component {
|
|
|
|
|
|
|
|
|
|
static instance: GameController = null;
|
|
|
|
|
|
|
|
|
|
protected onLoad(): void {
|
|
|
|
|
GameController.instance = this;
|
|
|
|
|
this._frameFlow = new FrameFlowHost();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected onDestroy(): void {
|
|
|
|
|
this._frameFlow?.dispose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**游戏总时间 */
|
|
|
|
|
GameTime: number = 0;
|
|
|
|
|
|
|
|
|
|
/**自己的称谓(主角称呼) */
|
|
|
|
|
SelfName: string = "男主";
|
|
|
|
|
|
|
|
|
|
UserInfo: IUserInfo = null;
|
|
|
|
|
|
|
|
|
|
/**暂停秒赚 */
|
|
|
|
|
PauseSecondEarn: boolean = false;
|
|
|
|
|
|
|
|
|
|
/**是否正在播放引导剧情 */
|
|
|
|
|
IsPlayingGuideStory: boolean = false;
|
|
|
|
|
/**是否正在播放新闻 */
|
|
|
|
|
IsPlayingNews: boolean = false;
|
|
|
|
|
/**是否显示障碍物 */
|
|
|
|
|
isShowObstacle: boolean = true;
|
|
|
|
|
|
|
|
|
|
/**当前玩家血量 */
|
|
|
|
|
playerHp: number = 0;
|
|
|
|
|
/**当前玩家银币数 */
|
|
|
|
|
playerCoinNum: number = 0;
|
|
|
|
|
|
|
|
|
|
//玩家玩过了游戏到结算
|
|
|
|
|
IsPlayedGame: boolean = false;
|
|
|
|
|
/** 本次回主页是否来自关卡战斗失败(UIBattleFail 领取后回大厅) */
|
|
|
|
|
returnedFromBattleFail = false;
|
|
|
|
|
/** 失败回主页时对应的关卡章节(仅 returnedFromBattleFail 为 true 时有效) */
|
|
|
|
|
returnedFromBattleFailChapterId = 0;
|
|
|
|
|
//弹出签到
|
|
|
|
|
IsShowSignPop: boolean = false;
|
|
|
|
|
|
|
|
|
|
/**当前完成后的章节 */
|
|
|
|
|
finishChapterId: number = -1;
|
|
|
|
|
/**
|
|
|
|
|
* 怪物数量倍数
|
|
|
|
|
*/
|
|
|
|
|
monsterNumScale: number = 1;
|
|
|
|
|
|
|
|
|
|
/** GM:背包刷新仅从该 Weapon2 列表中随机(null=正常权重池) */
|
|
|
|
|
gmBagWeapon2Ids: number[] | null = null;
|
|
|
|
|
/** GM:本机开局灌入 CarryStore 的 Battlebuff.id(null=不灌;开战 clear 后再写入) */
|
|
|
|
|
gmCarryBuffIds: number[] | null = null;
|
|
|
|
|
/** GM:AI 主动 Battlebuff.id(null=每局匹配随机;有值则跨局常驻覆盖直至清除) */
|
|
|
|
|
gmAiActiveBuffIds: number[] | null = null;
|
|
|
|
|
/** GM:AI 被动 Battlebuff.id(null=每局匹配随机;有值则跨局常驻覆盖直至清除) */
|
|
|
|
|
gmAiPassiveBuffIds: number[] | null = null;
|
|
|
|
|
/** GM:固定战场 Level.id;0=首页随机 */
|
|
|
|
|
gmMapId = 0;
|
|
|
|
|
/** GM:七十二变 roll 固定目标 Battlebuff.id;null=正常随机 */
|
|
|
|
|
gmRandomActiveFixBuffId: number | null = null;
|
|
|
|
|
/** GM:己方营地不掉血(仅本次登录内存,不写档) */
|
|
|
|
|
gmLocalCampImmune = false;
|
|
|
|
|
/** GM:敌方营地不掉血(仅本次登录内存,不写档) */
|
|
|
|
|
gmEnemyCampImmune = false;
|
|
|
|
|
|
|
|
|
|
playerWeaponIdArray = []
|
|
|
|
|
playerSkillIdArray = []
|
|
|
|
|
|
|
|
|
|
playerSkillGroupIdArray = []
|
|
|
|
|
playerWeaponGroupIdArray = []
|
|
|
|
|
setPlayerSkillGroupIdArray = false
|
|
|
|
|
setPlayerWeaponGroupIdArray = false
|
|
|
|
|
afkTimeOne = 60 * 60
|
|
|
|
|
|
|
|
|
|
isFinishGuide = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**武器模式最大挑战次数 */
|
|
|
|
|
// WeaponCountMax = 5;
|
|
|
|
|
// /**无尽模式最大挑战次数 */
|
|
|
|
|
// InfiniteChallengeCountMax = 20;
|
|
|
|
|
// /**道具模式最大挑战次数 */
|
|
|
|
|
// PropModeCountMax = 20;
|
|
|
|
|
// /**宝箱副本最大挑战次数 */
|
|
|
|
|
// BoxChallengeCountMax = 20;
|
|
|
|
|
// /**抽卡挑战模式最大挑战次数 */
|
|
|
|
|
// GachaChallengeCountMax = 20;
|
|
|
|
|
// /**boss挑战模式最大挑战次数 */
|
|
|
|
|
// BossChallengeCountMax = 20;
|
|
|
|
|
|
|
|
|
|
isInitAllData: boolean = false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** 当前选中准备进入的战场 Level id */
|
|
|
|
|
CurentSelectLevelId: number = 1;
|
|
|
|
|
|
|
|
|
|
/** @deprecated 使用 CurentSelectLevelId */
|
|
|
|
|
get CurentSelectChapterId(): number {
|
|
|
|
|
return this.CurentSelectLevelId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
set CurentSelectChapterId(value: number) {
|
|
|
|
|
this.CurentSelectLevelId = value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 当前正在进行中的战斗(对局 runtime + 旧 API 兼容) */
|
|
|
|
|
CurentBattle: ProjectBattleHandle | null = null;
|
|
|
|
|
/** 本地玩家上下文(PVP 时区分 local / remote) */
|
|
|
|
|
localPlayer: Player | null = null;
|
|
|
|
|
|
|
|
|
|
guideStoryId: number = 1;
|
|
|
|
|
|
|
|
|
|
guideGreatGift: boolean = false;
|
|
|
|
|
|
|
|
|
|
/**第三章失败引导角色升级 */
|
|
|
|
|
thirdChapterFailRoleUpgrade: boolean = false;
|
|
|
|
|
/**第三章失败引导武器升级 */
|
|
|
|
|
thirdChapterFailWeaponUpgrade: boolean = false;
|
|
|
|
|
/***第三章胜利榴莲弹出 */
|
|
|
|
|
thirdChapterWinGreatGift: boolean = false;
|
|
|
|
|
/**
|
|
|
|
|
* 初始化游戏
|
|
|
|
|
*/
|
|
|
|
|
init() {
|
|
|
|
|
patchSpineSkeletonResetEnums();
|
|
|
|
|
gg.battle.init();
|
|
|
|
|
macro.ENABLE_MULTI_TOUCH = false
|
|
|
|
|
this.GameTime = 0;
|
|
|
|
|
|
|
|
|
|
gg.dot.init();
|
|
|
|
|
|
|
|
|
|
gg.data.project.syncBattleVigourCost();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
gg.audio.MuteMusic = gg.data.getStatus(StatusType.MuteMusic) == 1;
|
|
|
|
|
gg.audio.MuteEffect = gg.data.getStatus(StatusType.MuteEffect) == 1;
|
|
|
|
|
|
|
|
|
|
GEvent.Ins.on(GEvent.UpdateItemNum, this.onItemUpdate, this);
|
|
|
|
|
|
|
|
|
|
gg.ui.setPanelOpenedHook((panelId) => {
|
|
|
|
|
const phase = ResourcePhaseGate.getInstance().phase;
|
|
|
|
|
if (phase === ResourcePhase.Battle || phase === ResourcePhase.BattleExit) return;
|
|
|
|
|
PreloadPredictor.getInstance().onPanelOpened(panelId);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// this.playerWeaponIdArray.push(22);
|
|
|
|
|
// let obj: IBattleSkill = {
|
|
|
|
|
// weaponId: 22,
|
|
|
|
|
// id: 2105,
|
|
|
|
|
// type: 1,
|
|
|
|
|
// limit_num: 10,
|
|
|
|
|
// };
|
|
|
|
|
// this.playerSkillIdArray.push(obj);
|
|
|
|
|
// obj = {
|
|
|
|
|
// weaponId: 22,
|
|
|
|
|
// id: 2153,
|
|
|
|
|
// type: 1,
|
|
|
|
|
// limit_num: 10,
|
|
|
|
|
// };
|
|
|
|
|
// this.playerSkillIdArray.push(obj);
|
|
|
|
|
|
|
|
|
|
this.unscheduleAllCallbacks();
|
|
|
|
|
|
|
|
|
|
//初始化每整秒更新计时器
|
|
|
|
|
this.schedule(() => {
|
|
|
|
|
this.onSecond();
|
|
|
|
|
}, 1);
|
|
|
|
|
gg.sdk.reportDY("inLevel", "进入-进入首页");
|
|
|
|
|
//首次登录
|
|
|
|
|
if (gg.data.IsFirstLogin) {
|
|
|
|
|
gg.sdk.reportDY("inLevel", "进入-首次登录");
|
|
|
|
|
gg.sdk?.sdk30r?.onRegister();
|
|
|
|
|
//测试-设置体力金币钻石战力等
|
|
|
|
|
|
|
|
|
|
gg.data.setCombatPower(50);
|
|
|
|
|
gg.data.setMoney(100);
|
|
|
|
|
//初始化30
|
|
|
|
|
gg.data.setVigour(30);
|
|
|
|
|
gg.data.setDiamond(10);
|
|
|
|
|
|
|
|
|
|
//测试-送8个已装备
|
|
|
|
|
gg.data.project.setHaveWeaponIds([1, 2, 3, 4, 11, 12, 13, 14]);
|
|
|
|
|
gg.data.project.setEquipWeaponStatus([1, 2, 3, 4, 11, 12, 13, 14]);
|
|
|
|
|
gg.data.setStatistics(StatisticsType.NoDoublePurpleSkillCountActiveRefresh, 20);
|
|
|
|
|
gg.data.setStatistics(StatisticsType.NoDoublePurpleSkillCount, 20);
|
|
|
|
|
gg.data.setStatistics(StatisticsType.ShopBuyGoldAdCount, 6)
|
|
|
|
|
gg.data.setStatistics(StatisticsType.ShopBuyItemAdCount, 6)
|
|
|
|
|
gg.data.setStatistics(StatisticsType.FreeBoxCount, 0)
|
|
|
|
|
gg.data.setStringData(OtherDataType.SignData, '0&0&1')
|
|
|
|
|
gg.data.setStatistics(StatisticsType.ActiveRefreshPurpleCount, 2)
|
|
|
|
|
gg.data.setStatistics(StatisticsType.NoDoublePurpleSkillInterval, 50)
|
|
|
|
|
gg.data.setStringData(OtherDataType.UserLevelupData, '0&10&0&0&0&0&0')
|
|
|
|
|
|
|
|
|
|
gg.data.setStringData(OtherDataType.TaskRewardGetData, '0&0&0&0&0')
|
|
|
|
|
gg.data.setStringData(OtherDataType.DayTaskRewardGetData, '0&0&0&0&0')
|
|
|
|
|
gg.data.setStringData(OtherDataType.DayTaskProgressData, '0&0&0&0&0&0&0&0&0&0&0&0&0&0')
|
|
|
|
|
gg.data.setStringData(OtherDataType.DayActiveGetRewardData, '0&0&0&0&0&0&0&0&0&0&0&0&0&0')
|
|
|
|
|
} else {
|
|
|
|
|
gg.sdk.reportDY("inLevel", "进入-第" + gg.data.LoginDays + "天登录");
|
|
|
|
|
gg.sdk.sdk30r?.track('RE_ACTIVE', {});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let arr = gg.data.project.getHaveWeaponIds();
|
|
|
|
|
if (arr.length == 0) {
|
|
|
|
|
gg.data.project.setHaveWeaponIds([1, 2, 3, 4, 11, 12, 13, 14]);
|
|
|
|
|
gg.data.project.setEquipWeaponStatus([1, 2, 3, 4, 11, 12, 13, 14]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gg.data.saveToServer();
|
|
|
|
|
|
|
|
|
|
//今天首次登录
|
|
|
|
|
if (gg.data.IsFirstLoginToday) {
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this._clearLegacyBattleSnapshot();
|
|
|
|
|
|
|
|
|
|
PerfReport.reportLoadRes(sdkConfig.resLoadFinishTime, sdkConfig.resLoadFinishTime);
|
|
|
|
|
PerfReport.reportLoadSdk(sdkConfig.sdkLoadFinishTime, sdkConfig.sdkLoadFinishTime);
|
|
|
|
|
void this._routeAfterInit();
|
|
|
|
|
|
|
|
|
|
this.isInitAllData = true
|
|
|
|
|
|
|
|
|
|
// 获客/复访首屏就绪兜底上报(幂等);避免进景失败导致测不通「场景加载完成」
|
|
|
|
|
this.scheduleOnce(() => {
|
|
|
|
|
gg.sdk.notifyFeedGameInteractive();
|
|
|
|
|
}, 0.2)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取章节id(取服务器端控制的章节id)
|
|
|
|
|
* @param chapterID 章节ID
|
|
|
|
|
* @returns 章节数据
|
|
|
|
|
*/
|
|
|
|
|
getChapter(chapterID) {
|
|
|
|
|
if (gg.sdk.levelS.length > chapterID - 1)
|
|
|
|
|
return gg.sdk.levelS[chapterID - 1];
|
|
|
|
|
return chapterID;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**获取原始章节id */
|
|
|
|
|
getOriginChapterID(chapterID) {
|
|
|
|
|
let idx = gg.sdk.levelS.findIndex((id) => id == chapterID);
|
|
|
|
|
if (idx >= 0)
|
|
|
|
|
return idx + 1;
|
|
|
|
|
return chapterID;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 抖音 inLevel 打点用战场 Level id。
|
|
|
|
|
* @param levelId 可选;传入则直接使用,不传则战斗中取当前关,否则取 CurentSelectLevelId
|
|
|
|
|
*/
|
|
|
|
|
getReportDYLevelLabel(levelId?: number): string {
|
|
|
|
|
if (levelId != null) {
|
|
|
|
|
return String(levelId);
|
|
|
|
|
}
|
|
|
|
|
const battle = this.CurentBattle;
|
|
|
|
|
if (battle) {
|
|
|
|
|
return String(battle.getReportDYLevelId?.() ?? this.CurentSelectLevelId ?? 1);
|
|
|
|
|
}
|
|
|
|
|
return String(this.CurentSelectLevelId ?? 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 战斗内 inLevel 打点前缀:章节{Level id}_{波次},波次最小为 1 */
|
|
|
|
|
getReportDYBattleLabel(waveNum?: number, levelId?: number): string {
|
|
|
|
|
const wave = Math.max(1, waveNum ?? this.CurentBattle?.CurentWaveNum ?? 0);
|
|
|
|
|
return `章节${this.getReportDYLevelLabel(levelId)}_${wave}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 抖音 inLevel:累计挑战次数区间。
|
|
|
|
|
* 0~200 传当前值;201~250→201,251~300→251…;超过 1000 传 1001。
|
|
|
|
|
*/
|
|
|
|
|
getReportDYBattleCount(): number {
|
|
|
|
|
const count = Math.max(0, Math.floor(Number(gg.data.doc?.totalBattleCount) || 0));
|
|
|
|
|
if (count > 1000) {
|
|
|
|
|
return 1001;
|
|
|
|
|
}
|
|
|
|
|
if (count <= 200) {
|
|
|
|
|
return count;
|
|
|
|
|
}
|
|
|
|
|
return 201 + Math.floor((count - 201) / 50) * 50;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @description 打点描述 挑战次数小于200场次不传波次
|
|
|
|
|
* @param curWaveNum 波次
|
|
|
|
|
* @returns
|
|
|
|
|
*/
|
|
|
|
|
getReportDYBattleCountLabel(curWaveNum: number): string {
|
|
|
|
|
const waveNum = curWaveNum == 0 ? 1 : curWaveNum;
|
|
|
|
|
const count = Math.max(0, Math.floor(Number(gg.data.doc?.totalBattleCount) || 0));
|
|
|
|
|
if (count <= 200) {
|
|
|
|
|
return `章节${this.getReportDYBattleCount()}_1-波次${waveNum}`;
|
|
|
|
|
} else {
|
|
|
|
|
return `章节${this.getReportDYBattleCount()}_1`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 抖音 inLevel:段位打点文案,格式「段位-{段位名}{星数}」,星数为总星对 5 取余(0 视为 5,最大 5)。
|
|
|
|
|
* @param stars 总星数,缺省为结算后玩家当前总星
|
|
|
|
|
*/
|
|
|
|
|
getReportDYRankLabel(stars?: number): string {
|
|
|
|
|
const totalStars = Math.max(1, Math.floor(Number(stars ?? RankDataManager.getPlayerRankStars()) || 0));
|
|
|
|
|
const row = RankDataManager.resolveTierByStars(totalStars);
|
|
|
|
|
const rankId = row?.id ?? RankDataManager.getPlayerRankId();
|
|
|
|
|
let name = RankDataManager.resolveRankLevelIconName(rankId);
|
|
|
|
|
if (!name) {
|
|
|
|
|
name = RankDataManager.rankDisplayName(RankDataManager.getRankConfig(rankId));
|
|
|
|
|
}
|
|
|
|
|
name = String(name ?? '').trim();
|
|
|
|
|
if (!name) {
|
|
|
|
|
console.warn('[GameController] getReportDYRankLabel 段位名为空', rankId);
|
|
|
|
|
}
|
|
|
|
|
const star = RankDataManager.getStarModCount(totalStars);
|
|
|
|
|
return `段位-${name}${star}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 重置游戏
|
|
|
|
|
*/
|
|
|
|
|
reset(tagStr: string = '') {
|
|
|
|
|
gg.data.reset((res) => {
|
|
|
|
|
if (res) {
|
|
|
|
|
if (gg.sdk.pf) {
|
|
|
|
|
assetManager.cacheManager.clearCache();
|
|
|
|
|
gg.sdk.restartMiniProgramSync();
|
|
|
|
|
} else {
|
|
|
|
|
this.playerHp = 0;
|
|
|
|
|
this.playerCoinNum = 0;
|
|
|
|
|
this.isShowObstacle = true
|
|
|
|
|
this.monsterNumScale = 1;
|
|
|
|
|
this.gmBagWeapon2Ids = null;
|
|
|
|
|
this.gmCarryBuffIds = null;
|
|
|
|
|
this.gmAiActiveBuffIds = null;
|
|
|
|
|
this.gmAiPassiveBuffIds = null;
|
|
|
|
|
this.gmMapId = 0;
|
|
|
|
|
this.gmRandomActiveFixBuffId = null;
|
|
|
|
|
this.gmLocalCampImmune = false;
|
|
|
|
|
this.gmEnemyCampImmune = false;
|
|
|
|
|
|
|
|
|
|
this.playerWeaponIdArray = []
|
|
|
|
|
this.playerSkillIdArray = []
|
|
|
|
|
this.playerSkillGroupIdArray = []
|
|
|
|
|
this.playerWeaponGroupIdArray = []
|
|
|
|
|
this.setPlayerSkillGroupIdArray = false
|
|
|
|
|
this.setPlayerWeaponGroupIdArray = false
|
|
|
|
|
this.unscheduleAllCallbacks();
|
|
|
|
|
GEvent.Ins.off(GEvent.UpdateItemNum, this.onItemUpdate, this);
|
|
|
|
|
gg.ui.closeAllPanel();
|
|
|
|
|
// gg.task.init();
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
this.init();
|
|
|
|
|
|
|
|
|
|
}, 10);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
gg.ui.showToast("重置游戏失败");
|
|
|
|
|
}
|
|
|
|
|
}, tagStr);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 每帧更新
|
|
|
|
|
* @param deltaTime
|
|
|
|
|
*/
|
|
|
|
|
update(deltaTime: number) {
|
|
|
|
|
this.GameTime += deltaTime;
|
|
|
|
|
gg.sdk.serverTime += deltaTime * 1000;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 每秒更新逻辑
|
|
|
|
|
*/
|
|
|
|
|
onSecond() {
|
|
|
|
|
//游戏总整秒数
|
|
|
|
|
let gs = Math.floor(this.GameTime);
|
|
|
|
|
|
|
|
|
|
let onlineTime = Math.floor(gg.data.OnlineTimeForAll);
|
|
|
|
|
|
|
|
|
|
//每3分钟打点
|
|
|
|
|
if (Math.floor(onlineTime) % 60 == 0) {
|
|
|
|
|
let index = Math.floor(onlineTime / 60);
|
|
|
|
|
if (index == 1 || index == 2) {
|
|
|
|
|
gg.sdk.reportDY("inLevel", `累积时长-${index}分钟`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (Math.floor(onlineTime) % 180 == 0) {
|
|
|
|
|
let index = Math.floor(onlineTime / 60);
|
|
|
|
|
gg.sdk.reportDY("inLevel", `累积时长-${index}分钟`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//每分钟执行的逻辑
|
|
|
|
|
if (gs % 60 == 0) {
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GEvent.Ins.emit(GEvent.SecondUpdate);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 物品更新
|
|
|
|
|
*/
|
|
|
|
|
onItemUpdate() {
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 获客 / 新号才在路由前 warm;老号冷启动进大厅不抢 silent 带宽 */
|
|
|
|
|
private _shouldWarmFrontierBeforeRoute(): boolean {
|
|
|
|
|
if (!!gg.sdk?.shouldFeedEnterPlayScene?.()) return true;
|
|
|
|
|
if (gg.data.IsFirstLogin) return true;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 配表与存档就绪后的首屏路由:直接进入 Home */
|
|
|
|
|
private _routeAfterInit(): void {
|
|
|
|
|
this.CurentSelectLevelId = 1;
|
|
|
|
|
this.enterMain();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 清除已下线的战斗快照本地数据 */
|
|
|
|
|
private _clearLegacyBattleSnapshot(): void {
|
|
|
|
|
try {
|
|
|
|
|
sys.localStorage.removeItem('mx_battle_snapshot_v1');
|
|
|
|
|
} catch { /* ignore */ }
|
|
|
|
|
gg.data.setStringDataOnlyLocal(OtherDataType.BattleGameData, '');
|
|
|
|
|
gg.data.saveLocal();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 防止 enterBattle / enterMain 重入导致双管道加载 */
|
|
|
|
|
private _battleEnterInflight = false;
|
|
|
|
|
private _enterMainInflight = false;
|
|
|
|
|
/** 退战后 BattleRes 仍保留(tier1/3 跨局复用) */
|
|
|
|
|
private _battleResRetained = false;
|
|
|
|
|
private _frameFlow: FrameFlowHost = null;
|
|
|
|
|
/** LD-HALL 场景:战斗退出 / 冷启动 */
|
|
|
|
|
private _hallLoadReason: 'battle_exit' | 'cold_boot' = 'cold_boot';
|
|
|
|
|
private _homeBundlePrefetch: Promise<void> | null = null;
|
|
|
|
|
private _herosResReady = false;
|
|
|
|
|
/** 进战斗管道代数:异步完成时校验,防止过期回调在 teardown 后仍 load */
|
|
|
|
|
private _battleEnterSession = 0;
|
|
|
|
|
/** 当前进战斗附加选项(匹配页门闩) */
|
|
|
|
|
private _enterOpts: BattleEnterOpts | null = null;
|
|
|
|
|
/** 匹配页回传句柄 */
|
|
|
|
|
private _matchGateHandlers: MatchGateHandlers | null = null;
|
|
|
|
|
/** 匹配进战失败回调 */
|
|
|
|
|
private _matchOnFail: (() => void) | null = null;
|
|
|
|
|
/** 匹配链路是否跳过体力检查(首页已校验,匹配/加载期间不再弹购买体力) */
|
|
|
|
|
private _matchNotCheckPower = false;
|
|
|
|
|
/** 首页开始→匹配:仅飞行动画,进战斗页成功后再扣体力 */
|
|
|
|
|
private _homeMatchDeferVigour = false;
|
|
|
|
|
/** 局内 BattleGame 打开成功后执行 subVigour */
|
|
|
|
|
private _enterConsumeVigour = false;
|
|
|
|
|
/** 回首页恢复连续失败次数,避免 load 挂死后再 enterMain 死循环 */
|
|
|
|
|
private _homeRecoverFails = 0;
|
|
|
|
|
|
|
|
|
|
/** 开场剧情打不开时的兜底(与关闭开场剧情时一致:进第 1 章战斗) */
|
|
|
|
|
private _enterFirstLoginFallback(): void {
|
|
|
|
|
this.CurentSelectLevelId = 1;
|
|
|
|
|
const entered = this.enterBattle(true);
|
|
|
|
|
if (entered !== 1) {
|
|
|
|
|
this.enterMain();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 进入主场景
|
|
|
|
|
* 须先结束战斗并等面板销毁,再释放 BattleRes;否则 Batcher2D 仍渲染已 decRef 的 SpriteFrame → getGFXTexture 空引用。
|
|
|
|
|
*/
|
|
|
|
|
enterMain(_pageeID: UIID = UIID.Home) {
|
|
|
|
|
if (gg.ui.hasPanel(UIID.Home) && !gg.battle.match && !this.CurentBattle) {
|
|
|
|
|
EmolumentDataManager.tryPopPanelIfNeeded();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (this._enterMainInflight) return;
|
|
|
|
|
this.showLoading(1, true);
|
|
|
|
|
if (this._isColdBootEnterMain()) {
|
|
|
|
|
void this._prefetchHomeBundle();
|
|
|
|
|
this._enterMainFromColdBoot();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
void this._enterMainAfterBattleTeardown();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 冷启动进大厅:无战斗 UI / 对局,跳过 battle-exit 管道 */
|
|
|
|
|
private _isColdBootEnterMain(): boolean {
|
|
|
|
|
return !gg.battle.match
|
|
|
|
|
&& !this.CurentBattle
|
|
|
|
|
&& !gg.ui.hasPanel(UIID.BattleGame);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _enterMainFromColdBoot(): void {
|
|
|
|
|
if (this._enterMainInflight) return;
|
|
|
|
|
this._enterMainInflight = true;
|
|
|
|
|
this._hallLoadReason = 'cold_boot';
|
|
|
|
|
AppLifecycle.setBattleKeepScreen(false);
|
|
|
|
|
ResourcePhaseGate.getInstance().setPhase(ResourcePhase.Hall);
|
|
|
|
|
this._enterMainReleaseBattleAndLoadHall();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _enterMainAfterBattleTeardown(): void {
|
|
|
|
|
if (this._enterMainInflight) return;
|
|
|
|
|
this._enterMainInflight = true;
|
|
|
|
|
PreloadPredictor.getInstance().abortHallSilentDownload();
|
|
|
|
|
this._hallLoadReason = 'battle_exit';
|
|
|
|
|
void this._prefetchHomeBundle();
|
|
|
|
|
AppLifecycle.setBattleKeepScreen(false);
|
|
|
|
|
ResourcePhaseGate.getInstance().setPhase(ResourcePhase.BattleExit);
|
|
|
|
|
PerfReport.reportSessionEnd('battle_exit');
|
|
|
|
|
this._teardownOpenBattleUi(() => {
|
|
|
|
|
gg.ui.clearAnimFlyNodes();
|
|
|
|
|
gg.ui.closeAllPanel().then(() => {
|
|
|
|
|
gg.ui.clearAnimFlyNodes();
|
|
|
|
|
this._frameFlow.waitFrames(2, () => {
|
|
|
|
|
this._endBattleSessionIfNeeded();
|
|
|
|
|
this._enterMainReleaseBattleAndLoadHall();
|
|
|
|
|
});
|
|
|
|
|
}).catch((e) => {
|
|
|
|
|
console.error('[GameController] enterMain closeAllPanel', String(e));
|
|
|
|
|
this._finishEnterMainInflight();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _prefetchHomeBundle(): Promise<void> {
|
|
|
|
|
const homeReady = !!assetManager.getBundle(GBundle.Home);
|
|
|
|
|
const commReady = !!assetManager.getBundle(GBundle.Comm);
|
|
|
|
|
if (homeReady && commReady) {
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
}
|
|
|
|
|
if (this._homeBundlePrefetch && commReady) {
|
|
|
|
|
return this._homeBundlePrefetch;
|
|
|
|
|
}
|
|
|
|
|
this._homeBundlePrefetch = Promise.all([
|
|
|
|
|
this._loadBundleIfNeeded(GBundle.Home),
|
|
|
|
|
this._loadBundleIfNeeded(GBundle.Comm),
|
|
|
|
|
]).then(() => undefined);
|
|
|
|
|
return this._homeBundlePrefetch;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _loadBundleIfNeeded(bundleName: string): Promise<void> {
|
|
|
|
|
return BundleLifecycle.getInstance().ensureLoaded(bundleName).then(() => undefined);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _loadHerosResIfNeeded(onComplete: (result?: LoadResult) => void): void {
|
|
|
|
|
if (this._herosResReady) {
|
|
|
|
|
onComplete({ ok: true, failed: [] });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
gg.res.loadResPakg(getHerosResPakage(), null, (result) => {
|
|
|
|
|
if (result.ok) {
|
|
|
|
|
this._herosResReady = true;
|
|
|
|
|
}
|
|
|
|
|
onComplete(result);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _enterMainReleaseBattleAndLoadHall(): void {
|
|
|
|
|
MemoryProfile.resolve();
|
|
|
|
|
const retainBattleRes = MemoryProfile.current.enableBattleResRetain;
|
|
|
|
|
const life = BundleLifecycle.getInstance();
|
|
|
|
|
if (retainBattleRes) {
|
|
|
|
|
// 跨局复用:用 battleRetain 钉住战斗分包,clearResPkg 只放资源引用不卸 zip
|
|
|
|
|
const battleBundles = ['battleMap', 'battleGameMonster', 'battleHero', 'popBattle', 'battleGame'];
|
|
|
|
|
for (let i = 0; i < battleBundles.length; i++) {
|
|
|
|
|
life.acquire(battleBundles[i], 'battleRetain');
|
|
|
|
|
}
|
|
|
|
|
gg.res.clearResPkg(ResPkgName.BattleRes);
|
|
|
|
|
gg.res.purgeNodePoolForBundles(battleBundles);
|
|
|
|
|
this._battleResRetained = true;
|
|
|
|
|
} else {
|
|
|
|
|
life.releaseOwner('battleRetain');
|
|
|
|
|
gg.res.clearResPkg(ResPkgName.BattleRes);
|
|
|
|
|
gg.res.clearNodePool();
|
|
|
|
|
this._battleResRetained = false;
|
|
|
|
|
life.releaseBattlePhase();
|
|
|
|
|
}
|
|
|
|
|
this._invalidateBattlePreloadCache();
|
|
|
|
|
PreloadPredictor.getInstance().resetBattlePhase();
|
|
|
|
|
PreloadPredictor.getInstance().resetHallPhase();
|
|
|
|
|
BattleAudio.releaseBattleWarmup();
|
|
|
|
|
gg.audio.release();
|
|
|
|
|
|
|
|
|
|
sdkConfig.hallLoadTimer?.dispose();
|
|
|
|
|
sdkConfig.hallLoadTimer = PerfLoadTimer.start();
|
|
|
|
|
void this._prefetchHomeBundle().then(() => {
|
|
|
|
|
tryProactiveCacheTrimBeforePreload();
|
|
|
|
|
let pending = 1;
|
|
|
|
|
const onHallAssetsReady = (result?: LoadResult) => {
|
|
|
|
|
if (result && !result.ok) {
|
|
|
|
|
PerfReport.reportLoadFail('main_shell', result.failed.length);
|
|
|
|
|
this.recoverHome('main_shell_load');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
pending--;
|
|
|
|
|
if (pending > 0) return;
|
|
|
|
|
PreloadPredictor.getInstance().markMainShellLoaded();
|
|
|
|
|
gg.ui.openPanel(UIID.Home).then((ok) => {
|
|
|
|
|
if (!ok) {
|
|
|
|
|
this.recoverHome('home_open');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this._enterMainAfterHomeOpen();
|
|
|
|
|
}).catch((e) => {
|
|
|
|
|
console.error('[GameController] enterMain open Home', String(e));
|
|
|
|
|
this.recoverHome('home_open');
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
gg.res.loadResPakg(getMainShellResPakage(), (c, t) => {
|
|
|
|
|
this.onLoadingShow(c, t);
|
|
|
|
|
if (DEV) {
|
|
|
|
|
}
|
|
|
|
|
}, onHallAssetsReady);
|
|
|
|
|
}).catch((e) => {
|
|
|
|
|
console.error('[GameController] enterMain home bundle', String(e));
|
|
|
|
|
this.recoverHome('home_bundle');
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _enterMainAfterHomeOpen(): void {
|
|
|
|
|
this._playHallBgm();
|
|
|
|
|
ResourcePhaseGate.getInstance().setPhase(ResourcePhase.Hall);
|
|
|
|
|
PreloadPredictor.getInstance().warmHighPriorityHallTabs();
|
|
|
|
|
MemoryDowngrade.reset();
|
|
|
|
|
const hallTimer = sdkConfig.hallLoadTimer;
|
|
|
|
|
sdkConfig.hallLoadTimer = null;
|
|
|
|
|
if (hallTimer) {
|
|
|
|
|
PerfReport.reportLoadHall(
|
|
|
|
|
hallTimer.foregroundMs(),
|
|
|
|
|
hallTimer.wallMs(),
|
|
|
|
|
this._hallLoadReason,
|
|
|
|
|
);
|
|
|
|
|
hallTimer.dispose();
|
|
|
|
|
}
|
|
|
|
|
PerfReport.reportSessionEnd('hall');
|
|
|
|
|
this._homeRecoverFails = 0;
|
|
|
|
|
this.onMainUiReady();
|
|
|
|
|
if (this._hallLoadReason === 'battle_exit') {
|
|
|
|
|
DailyShopDataManager.popSettleShopPanelIfNeeded();
|
|
|
|
|
}
|
|
|
|
|
this._finishEnterMainInflight();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _finishEnterMainInflight(): void {
|
|
|
|
|
gg.game.forceHideLoading();
|
|
|
|
|
this._enterMainInflight = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 先打开匹配页;预载+最短展示完成后进战斗,局内页打开后再关匹配页。
|
|
|
|
|
* 匹配页打不开则直接进战;进战失败走 onFail。
|
|
|
|
|
* @param opts.notCheckPower 跳过体力检查(与 enterBattle 一致)
|
|
|
|
|
* @param opts.onFail 匹配或进战失败
|
|
|
|
|
*/
|
|
|
|
|
enterBattleWithMatch(opts?: { notCheckPower?: boolean; onFail?: () => void }): void {
|
|
|
|
|
void this._enterBattleWithMatch(opts);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 匹配页预载:表 + 神将包 + BattleGame 预制;战斗包只点火,不阻塞门闩 */
|
|
|
|
|
async preloadBattleForMatch(): Promise<void> {
|
|
|
|
|
PreloadPredictor.getInstance().abortHallSilentDownload();
|
|
|
|
|
LoadScheduler.getInstance().beginUserLoad();
|
|
|
|
|
const levelId = this.resolveBattleLevelId();
|
|
|
|
|
this.CurentSelectLevelId = levelId;
|
|
|
|
|
try {
|
|
|
|
|
await awaitWithTimeout(
|
|
|
|
|
gg.data.table.ensureBattleTables(),
|
|
|
|
|
LoadRecoveryConfig.dirTimeoutMs,
|
|
|
|
|
'battleTables',
|
|
|
|
|
);
|
|
|
|
|
PreloadPredictor.getInstance().scheduleBattleWarm(levelId, 'interactive');
|
|
|
|
|
await Promise.all([
|
|
|
|
|
this._ensureHerosResForBattle(),
|
|
|
|
|
this._ensureBattleGamePrefab(),
|
|
|
|
|
]);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error('[GameController] preloadBattleForMatch', String(e));
|
|
|
|
|
} finally {
|
|
|
|
|
LoadScheduler.getInstance().endUserLoad();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 解析本局 Level id:GM 固定 > 首次进战读 Const > 已选或随机。
|
|
|
|
|
*/
|
|
|
|
|
resolveBattleLevelId(): number {
|
|
|
|
|
const forced = Math.floor(Number(this.gmMapId) || 0);
|
|
|
|
|
if (forced > 0) {
|
|
|
|
|
return gg.data.project.clampBattleLevelId(forced);
|
|
|
|
|
}
|
|
|
|
|
if (RankDataManager.isFirstEnterBattle()) {
|
|
|
|
|
return RankDataManager.readFirstEnterBattleLevelId();
|
|
|
|
|
}
|
|
|
|
|
if (this.CurentSelectLevelId > 0) {
|
|
|
|
|
return gg.data.project.clampBattleLevelId(this.CurentSelectLevelId);
|
|
|
|
|
}
|
|
|
|
|
return RankDataManager.pickRandomBattleLevelId();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 确保 battleGame 分包与战斗页预制已加载 */
|
|
|
|
|
private _ensureBattleGamePrefab(): Promise<void> {
|
|
|
|
|
const path = GPath.UIPrefab + 'BattleGame';
|
|
|
|
|
return BundleLifecycle.getInstance().ensureLoaded(GBundle.BattleGame).then((bundle) => {
|
|
|
|
|
if (!bundle) {
|
|
|
|
|
console.error('[GameController] battleGame bundle failed');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (bundle.get(path, Prefab)) return;
|
|
|
|
|
return new Promise<void>((resolve) => {
|
|
|
|
|
let done = false;
|
|
|
|
|
const finish = (): void => {
|
|
|
|
|
if (done) return;
|
|
|
|
|
done = true;
|
|
|
|
|
resolve();
|
|
|
|
|
};
|
|
|
|
|
const timer = setTimeout(() => {
|
|
|
|
|
console.error('[GameController] BattleGame prefab timeout');
|
|
|
|
|
finish();
|
|
|
|
|
}, LoadRecoveryConfig.prefabTimeoutMs);
|
|
|
|
|
bundle.load(path, Prefab, (loadErr) => {
|
|
|
|
|
clearTimeout(timer);
|
|
|
|
|
if (loadErr) {
|
|
|
|
|
console.error('[GameController] BattleGame prefab failed', loadErr);
|
|
|
|
|
}
|
|
|
|
|
finish();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async _enterBattleWithMatch(opts?: { notCheckPower?: boolean; onFail?: () => void }): Promise<void> {
|
|
|
|
|
MatchWinStats.ensurePendingOpponentRank();
|
|
|
|
|
this._matchOnFail = opts?.onFail ?? null;
|
|
|
|
|
this._matchNotCheckPower = !!opts?.notCheckPower;
|
|
|
|
|
this._homeMatchDeferVigour = true;
|
|
|
|
|
const ok = await gg.ui.openPanel(UIID.match, {
|
|
|
|
|
data: { onGateReady: this._onMatchGateReady },
|
|
|
|
|
});
|
|
|
|
|
if (ok) return;
|
|
|
|
|
const entered = this.enterBattle(this._matchNotCheckPower);
|
|
|
|
|
if (entered !== 1) {
|
|
|
|
|
this._homeMatchDeferVigour = false;
|
|
|
|
|
this._enterConsumeVigour = false;
|
|
|
|
|
this._matchOnFail?.();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _onMatchGateReady = (handlers: MatchGateHandlers): void => {
|
|
|
|
|
this._matchGateHandlers = handlers;
|
|
|
|
|
const ret = this.enterBattle(this._matchNotCheckPower, {
|
|
|
|
|
skipLoading: true,
|
|
|
|
|
deferCloseMatch: true,
|
|
|
|
|
onBattleOpened: this._onMatchBattleOpened,
|
|
|
|
|
});
|
|
|
|
|
if (ret !== 1) {
|
|
|
|
|
this._matchGateHandlers = null;
|
|
|
|
|
this._homeMatchDeferVigour = false;
|
|
|
|
|
this._enterConsumeVigour = false;
|
|
|
|
|
handlers.notifyEnterFailed();
|
|
|
|
|
this._matchOnFail?.();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private _onMatchBattleOpened = (battleOk: boolean): void => {
|
|
|
|
|
const handlers = this._matchGateHandlers;
|
|
|
|
|
this._matchGateHandlers = null;
|
|
|
|
|
if (battleOk) {
|
|
|
|
|
this._consumeEnterVigourIfNeeded();
|
|
|
|
|
handlers?.notifyBattleEntered();
|
|
|
|
|
this._matchOnFail = null;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this._enterConsumeVigour = false;
|
|
|
|
|
handlers?.notifyEnterFailed();
|
|
|
|
|
const fail = this._matchOnFail;
|
|
|
|
|
this._matchOnFail = null;
|
|
|
|
|
fail?.();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 进入战斗场景
|
|
|
|
|
* 重开时须先关掉旧 BattleGame 再 start:node.destroy 的 onDestroy 可能晚一帧,
|
|
|
|
|
* UIBattleGame 会按 session 所有权决定是否 end,避免误清新对局。
|
|
|
|
|
* @param notCheckPower 跳过体力检查
|
|
|
|
|
* @param opts 匹配页门闩
|
|
|
|
|
*/
|
|
|
|
|
enterBattle(notCheckPower = false, opts?: BattleEnterOpts) {
|
|
|
|
|
|
|
|
|
|
if (this._battleEnterInflight) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (gg.data.project.getEquipWeaponIds().length < 8) {
|
|
|
|
|
gg.ui.showToast(gg.lang.get("上阵武器不足8个"));
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 推荐流直玩预加载/获客进景:禁止被体力弹窗拦截,否则报不到 reportScene(7001)
|
|
|
|
|
const feedLaunch = !!gg.sdk?.isFeedDirectPlayLaunch?.();
|
|
|
|
|
if (!notCheckPower && !feedLaunch && !gg.data.project.checkVigourEnough()) {
|
|
|
|
|
gg.ui.openPanel(UIID.buyPower);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this._abortHallLevelPreload();
|
|
|
|
|
PreloadPredictor.getInstance().abortHallSilentDownload();
|
|
|
|
|
LoadScheduler.getInstance().beginUserLoad();
|
|
|
|
|
|
|
|
|
|
this._enterConsumeVigour = this._homeMatchDeferVigour || (!notCheckPower && !feedLaunch);
|
|
|
|
|
this._homeMatchDeferVigour = false;
|
|
|
|
|
if (feedLaunch && gg.sdk?.isFeedPreboot?.()) {
|
|
|
|
|
gg.sdk.markFeedPendingVigourDeduct?.();
|
|
|
|
|
this._enterConsumeVigour = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this._enterOpts = opts ?? null;
|
|
|
|
|
if (!opts?.skipLoading) {
|
|
|
|
|
this.showLoading(1, true);
|
|
|
|
|
}
|
|
|
|
|
gg.audio.release();
|
|
|
|
|
ResourcePhaseGate.getInstance().setPhase(ResourcePhase.Battle);
|
|
|
|
|
sdkConfig.battleLoadTimer?.dispose();
|
|
|
|
|
sdkConfig.battleLoadTimer = PerfLoadTimer.start();
|
|
|
|
|
sdkConfig.enterBattleTime = Date.now();
|
|
|
|
|
|
|
|
|
|
this._battleEnterInflight = true;
|
|
|
|
|
const enterSession = ++this._battleEnterSession;
|
|
|
|
|
|
|
|
|
|
const levelId = this.resolveBattleLevelId();
|
|
|
|
|
this.CurentSelectLevelId = levelId;
|
|
|
|
|
RankDataManager.markBattleEntered();
|
|
|
|
|
|
|
|
|
|
const reportLevelId = gg.sdk.get30rLevelId();
|
|
|
|
|
|
|
|
|
|
gg.data.addTotalBattleCount();
|
|
|
|
|
gg.sdk.reportDY('inLevel', `章节${this.getReportDYBattleCount()}_1-进入`);
|
|
|
|
|
gg.sdk.report30r('LEVEL_ENTER', {
|
|
|
|
|
level_id: reportLevelId,
|
|
|
|
|
stage_type: gg.sdk.get30rStageType(),
|
|
|
|
|
action_param: reportLevelId,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this._loadBattleResAfterUiTeardown(levelId, enterSession);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _failBattleEnter(session: number, reason = 'battle_enter'): void {
|
|
|
|
|
LoadScheduler.getInstance().endUserLoad();
|
|
|
|
|
this._enterConsumeVigour = false;
|
|
|
|
|
this._homeMatchDeferVigour = false;
|
|
|
|
|
const opened = this._enterOpts?.onBattleOpened;
|
|
|
|
|
this._enterOpts = null;
|
|
|
|
|
if (session === this._battleEnterSession) {
|
|
|
|
|
this._battleEnterSession++;
|
|
|
|
|
}
|
|
|
|
|
opened?.(false);
|
|
|
|
|
this.recoverHome(reason);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 加载失败统一恢复:关 loading → 回可交互大厅(或提示重启)。
|
|
|
|
|
* @param reason 恢复原因(PF-RECOVER)
|
|
|
|
|
* @param opts.restart Boot 关键路径失败时提示重启
|
|
|
|
|
*/
|
|
|
|
|
recoverHome(reason: string, opts?: { restart?: boolean }): void {
|
|
|
|
|
PerfReport.reportRecover(reason);
|
|
|
|
|
PreloadPredictor.getInstance().abortHallSilentDownload();
|
|
|
|
|
LoadScheduler.getInstance().resetUserLoad();
|
|
|
|
|
gg.ui.abortInflightOpens();
|
|
|
|
|
this._releaseBattleRuntime();
|
|
|
|
|
AppLifecycle.setBattleKeepScreen(false);
|
|
|
|
|
ResourcePhaseGate.getInstance().setPhase(ResourcePhase.Hall);
|
|
|
|
|
this._battleEnterInflight = false;
|
|
|
|
|
this._enterMainInflight = false;
|
|
|
|
|
this._enterConsumeVigour = false;
|
|
|
|
|
this._homeMatchDeferVigour = false;
|
|
|
|
|
this.forceHideLoading();
|
|
|
|
|
this._closeMatchAfterRecover();
|
|
|
|
|
if (opts?.restart) {
|
|
|
|
|
gg.ui.showToast('资源加载失败,请重启小游戏');
|
|
|
|
|
gg.sdk.restartMiniProgramSync();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const homeUi = gg.ui.getPanelBase(UIID.Home);
|
|
|
|
|
if (homeUi?.node?.isValid) {
|
|
|
|
|
this._homeRecoverFails = 0;
|
|
|
|
|
gg.ui.showToast('加载失败,请重试');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this._homeRecoverFails++;
|
|
|
|
|
if (this._homeRecoverFails >= 2) {
|
|
|
|
|
gg.ui.showToast('资源加载失败,请重启小游戏');
|
|
|
|
|
gg.sdk.restartMiniProgramSync();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
gg.ui.showToast('加载失败,正在返回首页');
|
|
|
|
|
void this.enterMain();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 进战失败时关掉匹配页并回传 onFail,避免一直停在匹配中 */
|
|
|
|
|
private _closeMatchAfterRecover(): void {
|
|
|
|
|
const handlers = this._matchGateHandlers;
|
|
|
|
|
this._matchGateHandlers = null;
|
|
|
|
|
handlers?.notifyEnterFailed();
|
|
|
|
|
const fail = this._matchOnFail;
|
|
|
|
|
this._matchOnFail = null;
|
|
|
|
|
fail?.();
|
|
|
|
|
if (gg.ui.hasPanel(UIID.match)) {
|
|
|
|
|
void gg.ui.closePanel(UIID.match);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 局内页真正打开后再扣体力,匹配/加载失败不扣 */
|
|
|
|
|
private _consumeEnterVigourIfNeeded(): void {
|
|
|
|
|
if (!this._enterConsumeVigour) return;
|
|
|
|
|
this._enterConsumeVigour = false;
|
|
|
|
|
const cost = gg.data.project.getBattleVigourCost();
|
|
|
|
|
if (!(cost > 0)) {
|
|
|
|
|
console.error('[GameController] 进战扣体力数值非法', cost);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!gg.data.project.checkVigourEnough()) {
|
|
|
|
|
console.error('[GameController] 进战成功但体力已不足,取消扣体力', gg.data.getVigour(), cost);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
gg.data.subVigour(cost);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** RuntimeLoadingGate 超时:避免永久遮罩 */
|
|
|
|
|
onLoadingWatchdogTimeout(): void {
|
|
|
|
|
this.recoverHome('loading_watchdog');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 关战斗 UI:先释放 Spine/怪物视图并 end 对局,再关页。
|
|
|
|
|
* end 必须在 closeAllPanel 之前:Pop 层 Pause 先关会 release 暂停门闩并 resume,
|
|
|
|
|
* 若对局未结束会解冻技能 Spine 并继续播「攻击」;同时 CurentBattle 须清空以免 resume。
|
|
|
|
|
*/
|
|
|
|
|
private _teardownOpenBattleUi(done: () => void): void {
|
|
|
|
|
this._releaseBattleRuntime();
|
|
|
|
|
this._frameFlow.waitFrames(1, done);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 先拆战斗视图再 end,避免 dispose 后 UI 仍同步 */
|
|
|
|
|
private _releaseBattleRuntime(): void {
|
|
|
|
|
const battleUi = gg.ui.getPanelBase(UIID.BattleGame);
|
|
|
|
|
if (battleUi?.node?.isValid) {
|
|
|
|
|
battleUi.teardownBeforeDestroy();
|
|
|
|
|
}
|
|
|
|
|
this._endBattleSessionIfNeeded();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 结束对局并清空 CurentBattle */
|
|
|
|
|
private _endBattleSessionIfNeeded(): void {
|
|
|
|
|
if (!gg.battle.match) return;
|
|
|
|
|
gg.battle.end();
|
|
|
|
|
HeadIconManager.resetBattleSession();
|
|
|
|
|
this.CurentBattle = null;
|
|
|
|
|
this.localPlayer = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 冷启动直进战斗未走 enterMain 时,须先载入 HerosRes 字形池供 HeroSkin / TextGrid 实例化 */
|
|
|
|
|
private async _ensureHerosResForBattle(): Promise<void> {
|
|
|
|
|
if (this._herosResReady && assetManager.getBundle(GBundle.Heros)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this._herosResReady = false;
|
|
|
|
|
try {
|
|
|
|
|
await awaitWithTimeout(
|
|
|
|
|
new Promise<void>((resolve) => {
|
|
|
|
|
this._loadHerosResIfNeeded((result) => {
|
|
|
|
|
if (!result?.ok) {
|
|
|
|
|
console.error('[GameController] HerosRes 预载未完全成功', result?.failed);
|
|
|
|
|
}
|
|
|
|
|
resolve();
|
|
|
|
|
});
|
|
|
|
|
}),
|
|
|
|
|
LoadRecoveryConfig.dirTimeoutMs,
|
|
|
|
|
'herosRes',
|
|
|
|
|
);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error('[GameController] HerosRes 等待超时', String(e));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 关页并等 onDestroy 完成后再 loadResPakg,避免连关(1→2 章)时
|
|
|
|
|
* clearResPkg 卸 bundle 后仍从池中取出带失效贴图的 monsterTemplate。
|
|
|
|
|
*/
|
|
|
|
|
private _loadBattleResAfterUiTeardown(levelId: number, enterSession: number): void {
|
|
|
|
|
const entryWarm = Promise.all([
|
|
|
|
|
gg.data.table.ensureBattleTables(),
|
|
|
|
|
this._ensureHerosResForBattle(),
|
|
|
|
|
]);
|
|
|
|
|
this._teardownOpenBattleUi(() => {
|
|
|
|
|
const keepIds = this._enterOpts?.deferCloseMatch ? [UIID.match] : [];
|
|
|
|
|
gg.ui.closeAllPanel(keepIds).then(() => {
|
|
|
|
|
gg.ui.clearAnimFlyNodes();
|
|
|
|
|
this._frameFlow.waitFrames(2, () => {
|
|
|
|
|
this._endBattleSessionIfNeeded();
|
|
|
|
|
void entryWarm.then(() => {
|
|
|
|
|
this._continueBattleResAfterWarm(levelId, enterSession);
|
|
|
|
|
}).catch((e) => {
|
|
|
|
|
console.error('[GameController] enterBattle warm', String(e));
|
|
|
|
|
this._continueBattleResAfterWarm(levelId, enterSession);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}).catch((e) => {
|
|
|
|
|
console.error('[GameController] enterBattle closeAllPanel', String(e));
|
|
|
|
|
this._failBattleEnter(enterSession);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* warm 等待结束(成功或超时)后走进战加载,禁止因预载超时卡在匹配页。
|
|
|
|
|
* @param levelId 关卡
|
|
|
|
|
* @param enterSession 进战世代
|
|
|
|
|
*/
|
|
|
|
|
private _continueBattleResAfterWarm(levelId: number, enterSession: number): void {
|
|
|
|
|
if (enterSession !== this._battleEnterSession) {
|
|
|
|
|
this._battleEnterInflight = false;
|
|
|
|
|
LoadScheduler.getInstance().endUserLoad();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const mapID = ChapterLevelBuilder.getLevelMapId(levelId);
|
|
|
|
|
const resPkg = ResPkgBuilder.buildBattlePackage(levelId, mapID, 0);
|
|
|
|
|
this._loadBattleResAfterPanelsClosed(resPkg, levelId, enterSession);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _loadBattleResAfterPanelsClosed(
|
|
|
|
|
resPkg: ResPakage,
|
|
|
|
|
levelId: number,
|
|
|
|
|
enterSession: number,
|
|
|
|
|
): void {
|
|
|
|
|
if (enterSession !== this._battleEnterSession) {
|
|
|
|
|
this._battleEnterInflight = false;
|
|
|
|
|
LoadScheduler.getInstance().endUserLoad();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this._invalidateHallWarmPreload();
|
|
|
|
|
PreloadPredictor.getInstance().resetHallPhase();
|
|
|
|
|
if (MemoryProfile.current.enableTabBundleUnloadOnBattle) {
|
|
|
|
|
BundleLifecycle.getInstance().releaseTabBundles();
|
|
|
|
|
}
|
|
|
|
|
const predictor = PreloadPredictor.getInstance();
|
|
|
|
|
const warmReady = predictor.isBattleWarmReady(levelId);
|
|
|
|
|
const warmInflight = predictor.isBattleWarmInflight(levelId);
|
|
|
|
|
const retained = this._battleResRetained;
|
|
|
|
|
if (warmReady) {
|
|
|
|
|
const bundleNames = createMembershipSet<string>();
|
|
|
|
|
for (const conf of resPkg.ress) {
|
|
|
|
|
if (typeof conf.bundle === 'string' && conf.bundle) {
|
|
|
|
|
membershipAdd(bundleNames, conf.bundle);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
gg.res.clearNodePool();
|
|
|
|
|
gg.res.purgeNodePoolForBundles(membershipToArray(bundleNames));
|
|
|
|
|
predictor.consumeBattleWarm(levelId);
|
|
|
|
|
} else if (!warmInflight && !retained) {
|
|
|
|
|
predictor.abortBattleWarmForChapter(levelId);
|
|
|
|
|
this._purgeBattleResBeforeLoad(resPkg);
|
|
|
|
|
}
|
|
|
|
|
// 退战保留时禁止 fresh/evict,否则会把已下缓存冲掉再下一遍
|
|
|
|
|
const fresh = !warmReady && !warmInflight && !retained;
|
|
|
|
|
this._battleResRetained = false;
|
|
|
|
|
void loadWithRetry(
|
|
|
|
|
() => new Promise<LoadResult>((resolve, reject) => {
|
|
|
|
|
// loadResPakg 无整体超时:弱网下个别回调丢失会让 Promise 永挂,进战门闩卡死
|
|
|
|
|
let settled = false;
|
|
|
|
|
const timer = setTimeout(() => {
|
|
|
|
|
if (settled) return;
|
|
|
|
|
settled = true;
|
|
|
|
|
reject(new Error(`battleRes timeout lv${levelId}`));
|
|
|
|
|
}, LoadRecoveryConfig.dirTimeoutMs);
|
|
|
|
|
try {
|
|
|
|
|
gg.res.loadResPakg(resPkg, (c, t) => {
|
|
|
|
|
this.onLoadingShow(c, t);
|
|
|
|
|
}, (result) => {
|
|
|
|
|
if (settled) return;
|
|
|
|
|
settled = true;
|
|
|
|
|
clearTimeout(timer);
|
|
|
|
|
resolve(result);
|
|
|
|
|
}, fresh);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
if (settled) return;
|
|
|
|
|
settled = true;
|
|
|
|
|
clearTimeout(timer);
|
|
|
|
|
reject(e);
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
{ label: `battleRes:lv${levelId}` },
|
|
|
|
|
).then((result) => {
|
|
|
|
|
if (enterSession !== this._battleEnterSession) {
|
|
|
|
|
this._battleEnterInflight = false;
|
|
|
|
|
LoadScheduler.getInstance().endUserLoad();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!result.ok) {
|
|
|
|
|
PerfReport.reportLoadFail(`battle_lv${levelId}`, result.failed.length);
|
|
|
|
|
this._failBattleEnter(enterSession, 'battle_res_load');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this._startBattleAfterResLoaded();
|
|
|
|
|
}).catch((e) => {
|
|
|
|
|
console.error('[GameController] battleRes load', String(e));
|
|
|
|
|
if (enterSession !== this._battleEnterSession) {
|
|
|
|
|
this._battleEnterInflight = false;
|
|
|
|
|
LoadScheduler.getInstance().endUserLoad();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this._failBattleEnter(enterSession, 'battle_res_timeout');
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _startBattleAfterResLoaded(): void {
|
|
|
|
|
LoadScheduler.getInstance().endUserLoad();
|
|
|
|
|
const levelId = gg.data.project.clampBattleLevelId(this.CurentSelectLevelId);
|
|
|
|
|
const playerRankId = RankDataManager.getPlayerRankId();
|
|
|
|
|
const pendingRank = MatchWinStats.peekPendingOpponentRankId();
|
|
|
|
|
const pendingStars = MatchWinStats.peekPendingOpponentStars();
|
|
|
|
|
MatchWinStats.takePendingOpponentRankId();
|
|
|
|
|
// 开战按当前玩家携带重抽 AI buff(匹配页 ensurePending 可能过早建档导致 buff 恒空)
|
|
|
|
|
const profile = AiBattleBridge.inst.buildMatchProfile();
|
|
|
|
|
if (profile && pendingRank > 0) {
|
|
|
|
|
AiBattleBridge.inst.applyPendingOpponent(pendingRank, pendingStars);
|
|
|
|
|
}
|
|
|
|
|
let opponentRankId = pendingRank > 0
|
|
|
|
|
? pendingRank
|
|
|
|
|
: (profile?.opponentRankId ?? 0);
|
|
|
|
|
if (opponentRankId <= 0) {
|
|
|
|
|
opponentRankId = RankDataManager.pickOpponentRankId(playerRankId);
|
|
|
|
|
}
|
|
|
|
|
const runtime = gg.battle.start({
|
|
|
|
|
levelId,
|
|
|
|
|
playerRankId,
|
|
|
|
|
opponentRankId,
|
|
|
|
|
mode: 'pvp',
|
|
|
|
|
flowMode: BattleFlowMode.Continuous,
|
|
|
|
|
});
|
|
|
|
|
if (!runtime) {
|
|
|
|
|
this._failBattleEnter(this._battleEnterSession, 'battle_init');
|
|
|
|
|
gg.ui.showToast('战斗初始化失败');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this.CurentBattle = runtime as ProjectBattleHandle;
|
|
|
|
|
this.localPlayer = runtime.player ?? null;
|
|
|
|
|
HeadIconManager.resetBattleSession();
|
|
|
|
|
AppLifecycle.setBattleKeepScreen(true);
|
|
|
|
|
|
|
|
|
|
gg.ui.openPanel(UIID.BattleGame).then((ok) => {
|
|
|
|
|
const opts = this._enterOpts;
|
|
|
|
|
this._enterOpts = null;
|
|
|
|
|
if (!ok) {
|
|
|
|
|
AppLifecycle.setBattleKeepScreen(false);
|
|
|
|
|
this._releaseBattleRuntime();
|
|
|
|
|
gg.game.flushDeferredLevelPreload();
|
|
|
|
|
gg.ui.showToast('战斗界面加载失败');
|
|
|
|
|
this._enterConsumeVigour = false;
|
|
|
|
|
opts?.onBattleOpened?.(false);
|
|
|
|
|
if (opts?.deferCloseMatch) {
|
|
|
|
|
this.recoverHome('battle_ui_open');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
MatchWinStats.markBattleStarted(opponentRankId);
|
|
|
|
|
if (!opts?.deferCloseMatch) {
|
|
|
|
|
this._consumeEnterVigourIfNeeded();
|
|
|
|
|
}
|
|
|
|
|
BattleAudio.playBgmBattle();
|
|
|
|
|
PreloadPredictor.getInstance().startBattleSilentDownload(levelId);
|
|
|
|
|
opts?.onBattleOpened?.(true);
|
|
|
|
|
}
|
|
|
|
|
if (!opts?.skipLoading) {
|
|
|
|
|
gg.game.hideLoading();
|
|
|
|
|
}
|
|
|
|
|
this._battleEnterInflight = false;
|
|
|
|
|
}).catch((e) => {
|
|
|
|
|
console.error('[GameController] enterBattle open BattleGame', String(e));
|
|
|
|
|
const opts = this._enterOpts;
|
|
|
|
|
this._enterOpts = null;
|
|
|
|
|
this._enterConsumeVigour = false;
|
|
|
|
|
this._battleEnterSession++;
|
|
|
|
|
opts?.onBattleOpened?.(false);
|
|
|
|
|
if (!opts?.skipLoading) {
|
|
|
|
|
gg.game.hideLoading();
|
|
|
|
|
}
|
|
|
|
|
this._battleEnterInflight = false;
|
|
|
|
|
this._releaseBattleRuntime();
|
|
|
|
|
if (opts?.deferCloseMatch) {
|
|
|
|
|
this.recoverHome('battle_ui_open');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onLoadingShow(c: number, t: number) {
|
|
|
|
|
this.CurentLoadingProgress = clamp01(c / t);
|
|
|
|
|
}
|
|
|
|
|
CurentLoadingProgress: number = 0;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取所有秒赚
|
|
|
|
|
* @returns
|
|
|
|
|
*/
|
|
|
|
|
getAllProfit() {
|
|
|
|
|
let num = 0;
|
|
|
|
|
return Math.floor(num);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 弹出道具不足弹窗
|
|
|
|
|
* @param skipUIidArr 需要跳转的UIID数组
|
|
|
|
|
* @param itemID 道具id
|
|
|
|
|
* @param itemNum 道具数量
|
|
|
|
|
* @param showVideoAd 是否显示视频广告获取
|
|
|
|
|
* @returns
|
|
|
|
|
*/
|
|
|
|
|
showSkipPop(itemID = 0, itemNum = 1, showVideoAd = false, skipUIidArr: UIID[] = [], title = "道具不足") {
|
|
|
|
|
gg.ui.openPanel(UIID.SkipPop, { data: { skipUIidArr: skipUIidArr, itemID: itemID, itemNum: itemNum, showVideoAd: showVideoAd, title: title } });
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getUserInfo() {
|
|
|
|
|
gg.sdk.getUserInfo().then((info) => {
|
|
|
|
|
this.UserInfo = info as IUserInfo;
|
|
|
|
|
if (this.UserInfo) {
|
|
|
|
|
gg.data.doc.uid = this.UserInfo.nickName;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
showMsgBox(content: string, title: string = "温馨提示", yesFun: Function = null, noFun: Function = null) {
|
|
|
|
|
gg.ui.openPanel(UIID.MsgBox, {
|
|
|
|
|
data: {
|
|
|
|
|
title: title,
|
|
|
|
|
content: content,
|
|
|
|
|
yesFun: yesFun,
|
|
|
|
|
noFun: noFun
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 公用 PopToast 确认弹窗
|
|
|
|
|
* @param desc 描述
|
|
|
|
|
* @param type 类型(1=删除道具)
|
|
|
|
|
* @param onOk 确定回调
|
|
|
|
|
* @param onCancel 取消回调
|
|
|
|
|
*/
|
|
|
|
|
showPopToast(
|
|
|
|
|
desc: string,
|
|
|
|
|
type: PopToastType = PopToastType.DeleteItem,
|
|
|
|
|
onOk?: () => void,
|
|
|
|
|
onCancel?: () => void,
|
|
|
|
|
): void {
|
|
|
|
|
gg.ui.openPanel(UIID.PopToast, {
|
|
|
|
|
data: {
|
|
|
|
|
desc,
|
|
|
|
|
type,
|
|
|
|
|
onOk,
|
|
|
|
|
onCancel,
|
|
|
|
|
} satisfies PopToastParam,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 金币不足确认弹窗:确定先看广告,成功后再回调。
|
|
|
|
|
* @param desc 描述
|
|
|
|
|
* @param onOk 广告成功后的确定回调
|
|
|
|
|
* @param onCancel 取消回调
|
|
|
|
|
*/
|
|
|
|
|
showPopCoinToast(desc: string, onOk?: () => void, onCancel?: () => void): void {
|
|
|
|
|
gg.ui.openPanel(UIID.PopCoinToast, {
|
|
|
|
|
data: {
|
|
|
|
|
desc,
|
|
|
|
|
onOk,
|
|
|
|
|
onCancel,
|
|
|
|
|
} satisfies PopCoinToastParam,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**获取最大免费扫荡次数 */
|
|
|
|
|
getMaxFreeSweepTimes() {
|
|
|
|
|
return 3;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**根据奖励字符串解析奖励道具(只能解析固定格式的字符串:“道具id,道具数量|道具id,道具数量|...”) */
|
|
|
|
|
parseRewardStr(rewardStr: string) {
|
|
|
|
|
let rewardArr = rewardStr.split("|");
|
|
|
|
|
let rewardList: ItemData[] = [];
|
|
|
|
|
for (let i = 0; i < rewardArr.length; i++) {
|
|
|
|
|
let str = rewardArr[i];
|
|
|
|
|
if (str == "") continue;
|
|
|
|
|
let [id, num] = rewardArr[i].split(",");
|
|
|
|
|
const itemId = Number(id);
|
|
|
|
|
const itemNum = Number(num);
|
|
|
|
|
if (!(itemId > 0) || !(itemNum > 0)) continue;
|
|
|
|
|
let itemData = new ItemData(itemId, itemNum);
|
|
|
|
|
itemData.staticData = gg.data.table.getItemData(itemId);
|
|
|
|
|
if (!itemData.staticData) continue;
|
|
|
|
|
itemData.isFragment = itemData.staticData.type == 8;
|
|
|
|
|
rewardList.push(itemData);
|
|
|
|
|
}
|
|
|
|
|
return rewardList;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**根据道具数组合并同类型道具 */
|
|
|
|
|
mergeSameTypeItem(itemList: ItemData[]) {
|
|
|
|
|
let itemMap = new Map<number, ItemData>();
|
|
|
|
|
for (let i = 0; i < itemList.length; i++) {
|
|
|
|
|
let item = itemList[i];
|
|
|
|
|
if (itemMap.has(item.id)) {
|
|
|
|
|
itemMap.get(item.id).num += item.num;
|
|
|
|
|
} else {
|
|
|
|
|
itemMap.set(item.id, item);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return mapValuesToArray(itemMap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**根据道具数组自动获得通用碎片转化的道具 */
|
|
|
|
|
autoMergeFragment(itemList: ItemData[]) {
|
|
|
|
|
const result: ItemData[] = [];
|
|
|
|
|
for (let i = 0; i < itemList.length; i++) {
|
|
|
|
|
const item = itemList[i];
|
|
|
|
|
if (!item.staticData) item.staticData = gg.data.table.getItemData(item.id);
|
|
|
|
|
if (item.staticData?.type === 4) {
|
|
|
|
|
result.push(...this._expandType4Box(item.id, item.num));
|
|
|
|
|
} else {
|
|
|
|
|
result.push(item);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return this.mergeSameTypeItem(result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** type=4 品质碎片箱 → 具体武器碎片 */
|
|
|
|
|
private _expandType4Box(itemId: number, itemNum: number): ItemData[] {
|
|
|
|
|
const boxRewardArray = gg.data.getFragmentNumArray(`${itemId},${itemNum}`);
|
|
|
|
|
const statsArray = MTools.mergeToStatsArray(boxRewardArray);
|
|
|
|
|
const result: ItemData[] = [];
|
|
|
|
|
for (let i = 0; i < statsArray.length; i++) {
|
|
|
|
|
const [idStr, numStr] = statsArray[i].split('|');
|
|
|
|
|
const id = Number(idStr);
|
|
|
|
|
const num = Number(numStr);
|
|
|
|
|
const item = new ItemData(id, num);
|
|
|
|
|
item.id = id;
|
|
|
|
|
item.num = num;
|
|
|
|
|
item.staticData = gg.data.table.getItemData(id);
|
|
|
|
|
result.push(item);
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 单条奖励 id,num 展开为可入背包的 ItemData 列表(type 4/5/8 特殊处理) */
|
|
|
|
|
private _expandSingleRewardItem(itemId: number, itemNum: number): ItemData[] {
|
|
|
|
|
const tableItem = gg.data.table.getItemData(itemId);
|
|
|
|
|
if (!tableItem) {
|
|
|
|
|
console.log('GameController._expandSingleRewardItem: Item 缺失', itemId);
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
if (tableItem.type === 4) {
|
|
|
|
|
return this._expandType4Box(itemId, itemNum);
|
|
|
|
|
}
|
|
|
|
|
if (tableItem.type === 5) {
|
|
|
|
|
const item = new ItemData(itemId, itemNum);
|
|
|
|
|
item.id = itemId;
|
|
|
|
|
item.num = itemNum;
|
|
|
|
|
item.staticData = tableItem;
|
|
|
|
|
return [item];
|
|
|
|
|
}
|
|
|
|
|
const item = new ItemData(itemId, itemNum);
|
|
|
|
|
item.id = itemId;
|
|
|
|
|
item.num = itemNum;
|
|
|
|
|
item.staticData = tableItem;
|
|
|
|
|
return [item];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getRoleLv() {
|
|
|
|
|
let curData = gg.data.getStringData(OtherDataType.UserLevelupData)
|
|
|
|
|
let curDataArr = curData.split('&')
|
|
|
|
|
let roleLevel = curDataArr[0]
|
|
|
|
|
return Number(roleLevel);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**根据奖励字符串显示获得奖励界面 */
|
|
|
|
|
showGetRewardByReardStr(rewardStr: string, callBack = null) {
|
|
|
|
|
const itemsArray: ItemData[] = [];
|
|
|
|
|
const rewardsStrArr = rewardStr.split("|");
|
|
|
|
|
for (let i = 0; i < rewardsStrArr.length; i++) {
|
|
|
|
|
const signReward = rewardsStrArr[i];
|
|
|
|
|
if (!signReward) continue;
|
|
|
|
|
const [id, num] = signReward.split(",");
|
|
|
|
|
const itemId = Number(id);
|
|
|
|
|
const itemNum = Number(num);
|
|
|
|
|
if (!(itemId > 0) || !(itemNum > 0)) continue;
|
|
|
|
|
itemsArray.push(...this._expandSingleRewardItem(itemId, itemNum));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (callBack) {
|
|
|
|
|
gg.ui.openPanel(UIID.GetReward, {
|
|
|
|
|
data: itemsArray,
|
|
|
|
|
onClose: callBack
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
gg.ui.openPanel(UIID.GetReward, {
|
|
|
|
|
data: itemsArray
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**根据道具数组显示获得奖励界面 */
|
|
|
|
|
showGetRewardByItemList(items: ItemData[]) {
|
|
|
|
|
const itemsArray: ItemData[] = [];
|
|
|
|
|
for (let i = 0; i < items.length; i++) {
|
|
|
|
|
const itemData = items[i];
|
|
|
|
|
if (!itemData.staticData) itemData.staticData = gg.data.table.getItemData(itemData.id);
|
|
|
|
|
itemsArray.push(...this._expandSingleRewardItem(itemData.id, itemData.num));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gg.ui.openPanel(UIID.GetReward, {
|
|
|
|
|
data: itemsArray
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
showLoading(index: number = 2, immediate = false) {
|
|
|
|
|
GEvent.Ins.emit(GEvent.SHOW_LOADING, index, immediate);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hideLoading() {
|
|
|
|
|
GEvent.Ins.emit(GEvent.HIDE_LOADING);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 强制关闭 loading(ref 失配等意外时兜底) */
|
|
|
|
|
forceHideLoading() {
|
|
|
|
|
GEvent.Ins.emit(GEvent.FORCE_HIDE_LOADING);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**根据id跳转到指定页面 */
|
|
|
|
|
gotoPageById(id: number) {
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
isPreLoadLevelRes: boolean = false;
|
|
|
|
|
private _pendingPreLoadLevelId: number = 0;
|
|
|
|
|
private _runningPreLoadLevelId: number = 0;
|
|
|
|
|
/** 战斗期间挂起的大厅 Level 预载,战斗结束后再执行 */
|
|
|
|
|
private _deferredPreLoadLevelId: number = 0;
|
|
|
|
|
|
|
|
|
|
/** 上次完成预载的 Level(换关 invalidate 用) */
|
|
|
|
|
private _lastPreloadedLevelId: number = 0;
|
|
|
|
|
|
|
|
|
|
private _invalidatePreloadedLevelRes(levelId: number): void {
|
|
|
|
|
if (levelId <= 0) return;
|
|
|
|
|
const levelData = gg.data.project.getLevelConfig(levelId);
|
|
|
|
|
if (!levelData) return;
|
|
|
|
|
const mapID = ChapterLevelBuilder.getLevelMapId(levelId);
|
|
|
|
|
const resPkg = ResPkgBuilder.buildBattlePackage(levelId, mapID, 0);
|
|
|
|
|
gg.res.invalidatePreloadForPackage(resPkg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 中止大厅侧 Level 预载(preLoadLevelRes)。
|
|
|
|
|
* Predictor 战斗 warm 在进关路径上 await 后复用,不在此处 abort。
|
|
|
|
|
*/
|
|
|
|
|
private _abortHallLevelPreload(): void {
|
|
|
|
|
const runningId = this._runningPreLoadLevelId;
|
|
|
|
|
if (this.isPreLoadLevelRes || runningId > 0 || this._lastPreloadedLevelId > 0) {
|
|
|
|
|
const levelId = runningId || this._lastPreloadedLevelId;
|
|
|
|
|
if (levelId > 0) {
|
|
|
|
|
this._invalidatePreloadedLevelRes(levelId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this.isPreLoadLevelRes = false;
|
|
|
|
|
this._runningPreLoadLevelId = 0;
|
|
|
|
|
this._pendingPreLoadLevelId = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** loadResPakg 前:清节点池 + invalidate + evict(fresh load 在 loadResPakg 内完成) */
|
|
|
|
|
private _purgeBattleResBeforeLoad(resPkg: ResPakage): void {
|
|
|
|
|
const bundleNames = createMembershipSet<string>();
|
|
|
|
|
for (const conf of resPkg.ress) {
|
|
|
|
|
if (typeof conf.bundle === 'string' && conf.bundle) {
|
|
|
|
|
membershipAdd(bundleNames, conf.bundle);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
gg.res.clearNodePool();
|
|
|
|
|
gg.res.purgeNodePoolForBundles(membershipToArray(bundleNames));
|
|
|
|
|
gg.res.invalidatePreloadForPackage(resPkg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 大厅就绪后播放首页 BGM(新产品直开 Home,不经 MainUI.onInit) */
|
|
|
|
|
private _playHallBgm(): void {
|
|
|
|
|
gg.audio.playMusic({ name: '首页背景音', path: GAudioPath.Bgm });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Home 可交互后再静默下载战斗包与大厅弹窗(不阻塞、失败不回首页) */
|
|
|
|
|
onMainUiReady(): void {
|
|
|
|
|
PreloadPredictor.getInstance().rescheduleHomeIdle();
|
|
|
|
|
void gg.equip.ensureTable();
|
|
|
|
|
void HeadIconManager.syncUnlocksFromProgressAsync();
|
|
|
|
|
// 只预拉总榜/日榜;定位权限放到玩家打开排行榜之后
|
|
|
|
|
RankBoardManager.warmupOnHallEnter();
|
|
|
|
|
// 推荐流复访托管:上报体力恢复就绪;订阅面板每进程最多拉起一次
|
|
|
|
|
gg.sdk.storeFeedData();
|
|
|
|
|
gg.sdk.PlatformFeedSubscribe();
|
|
|
|
|
ResourcePhaseGate.getInstance().setPhase(ResourcePhase.HallPreload);
|
|
|
|
|
this._frameFlow.waitFrames(2, this._startHallSilentAfterReady);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _startHallSilentAfterReady = (): void => {
|
|
|
|
|
PreloadPredictor.getInstance().warmHighPriorityHallTabs();
|
|
|
|
|
PreloadPredictor.getInstance().startHallSilentDownload();
|
|
|
|
|
ResourcePhaseGate.getInstance().setPhase(ResourcePhase.Hall);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** 大厅 Level 预载是否已完成(skip beforeBattle 用) */
|
|
|
|
|
isLevelPreloaded(levelId: number): boolean {
|
|
|
|
|
return this._lastPreloadedLevelId === levelId
|
|
|
|
|
&& !this.isPreLoadLevelRes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @deprecated 兼容 PreloadPredictor */
|
|
|
|
|
isChapterPreloaded(chapterId: number): boolean {
|
|
|
|
|
return this.isLevelPreloaded(chapterId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 进战释放大厅弹窗预制(zip 常驻 popCommon);长尾一并卸 */
|
|
|
|
|
private _invalidateHallWarmPreload(): void {
|
|
|
|
|
gg.res.invalidatePreloadForPackage(getHallWarmResPakage());
|
|
|
|
|
gg.res.invalidatePreloadForPackage(getHallWarmLongTailResPakage());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 退战斗时清除上一关战斗预载登记 */
|
|
|
|
|
private _invalidateBattlePreloadCache(): void {
|
|
|
|
|
const levelId = this._lastPreloadedLevelId || this.CurentSelectLevelId || 0;
|
|
|
|
|
if (levelId <= 0) return;
|
|
|
|
|
this._invalidatePreloadedLevelRes(levelId);
|
|
|
|
|
this._lastPreloadedLevelId = 0;
|
|
|
|
|
this._deferredPreLoadLevelId = 0;
|
|
|
|
|
this._pendingPreLoadLevelId = 0;
|
|
|
|
|
this._runningPreLoadLevelId = 0;
|
|
|
|
|
this.isPreLoadLevelRes = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 进大厅后预载当前 Level 战斗资源(统一入口,避免与 enterHall 规则重复) */
|
|
|
|
|
private _preloadCurrentLevelRes(): void {
|
|
|
|
|
const levelId = this.CurentSelectLevelId ?? 1;
|
|
|
|
|
this.preLoadLevelRes(levelId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 战斗结束、回到大厅后恢复挂起的 Level 预载 */
|
|
|
|
|
flushDeferredLevelPreload(): void {
|
|
|
|
|
const levelId = this._deferredPreLoadLevelId;
|
|
|
|
|
if (levelId <= 0) return;
|
|
|
|
|
this._deferredPreLoadLevelId = 0;
|
|
|
|
|
this.preLoadLevelRes(levelId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @deprecated 兼容旧调用 */
|
|
|
|
|
flushDeferredChapterPreload(): void {
|
|
|
|
|
this.flushDeferredLevelPreload();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 预加载指定 Level 战斗资源 */
|
|
|
|
|
preLoadLevelRes(levelID: number) {
|
|
|
|
|
return;
|
|
|
|
|
if (this.isPreLoadLevelRes) {
|
|
|
|
|
this._pendingPreLoadLevelId = levelID;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const levelData = gg.data.project.getLevelConfig(levelID);
|
|
|
|
|
if (!levelData) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (this._lastPreloadedLevelId > 0 && this._lastPreloadedLevelId !== levelID) {
|
|
|
|
|
this._invalidatePreloadedLevelRes(this._lastPreloadedLevelId);
|
|
|
|
|
}
|
|
|
|
|
this.isPreLoadLevelRes = true;
|
|
|
|
|
this._runningPreLoadLevelId = levelID;
|
|
|
|
|
this._pendingPreLoadLevelId = 0;
|
|
|
|
|
const preloadTimer = PerfLoadTimer.start();
|
|
|
|
|
const mapID = ChapterLevelBuilder.getLevelMapId(levelID);
|
|
|
|
|
const resPkg = ResPkgBuilder.buildBattlePackage(levelID, mapID, 0);
|
|
|
|
|
gg.res.preLoadResPakg(resPkg, (c, t) => {
|
|
|
|
|
this.onLoadingShow(c, t);
|
|
|
|
|
}, () => {
|
|
|
|
|
const finishedLevelId = this._runningPreLoadLevelId;
|
|
|
|
|
if (finishedLevelId <= 0 || finishedLevelId !== levelID) {
|
|
|
|
|
preloadTimer.dispose();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
PerfReport.reportPreloadChapter(
|
|
|
|
|
finishedLevelId,
|
|
|
|
|
preloadTimer.foregroundMs(),
|
|
|
|
|
resPkg.ress.length,
|
|
|
|
|
preloadTimer.wallMs(),
|
|
|
|
|
);
|
|
|
|
|
preloadTimer.dispose();
|
|
|
|
|
this._lastPreloadedLevelId = finishedLevelId;
|
|
|
|
|
this.isPreLoadLevelRes = false;
|
|
|
|
|
const nextLevelId = this._pendingPreLoadLevelId;
|
|
|
|
|
this._pendingPreLoadLevelId = 0;
|
|
|
|
|
if (nextLevelId > 0 && nextLevelId !== finishedLevelId) {
|
|
|
|
|
this.preLoadLevelRes(nextLevelId);
|
|
|
|
|
}
|
|
|
|
|
this._runningPreLoadLevelId = 0;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @deprecated 兼容旧调用 */
|
|
|
|
|
preLoadChapterRes(chapterID: number) {
|
|
|
|
|
this.preLoadLevelRes(chapterID);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|