import { GEvent } from '../../mx/module/event/GEvent'; import MTools from '../../mx/tools/MTools'; import { BattleRuntime } from '../battle/runtime/BattleRuntime'; import { ItemNumType, StatisticsType } from '../game/GameData'; import { TableNames } from '../game/ConfigTableData'; import { UIID } from '../game/ConfigRes'; import { sdkConfig } from '../sdk/sdkConfig'; import { DailyShopDataManager } from '../manager/DailyShopDataManager'; import { DailyShopKind } from '../manager/DailyShopTypes'; import { EmolumentDataManager } from '../manager/EmolumentDataManager'; import { HeadIconManager } from '../manager/HeadIconManager'; import { RankBoardManager } from '../manager/RankBoardManager'; import { RankDataManager } from '../manager/RankDataManager'; import { BattleBuffCarryStore } from '../manager/battleBuff/BattleBuffCarryStore'; /** 俸禄 GM 测试快照星级 */ const GM_EMOLUMENT_SNAP_STARS = 50; /** 俸禄 GM 测试快照胜场 */ const GM_EMOLUMENT_SNAP_WINS = 50; /** * 本项目 GM 业务动作(壳层禁止直接调这些 Manager)。 */ export class ProjectGmActions { /** 今日首次登录 / 每日重置 */ static firstLoginToday(): void { gg.data.onFirstLoginToday(); gg.ui.showToast('今日首次登录(每日重置)成功'); } /** 复制存档 JSON */ static copySave(): void { MTools.CopyTextEvent(JSON.stringify(gg.data.doc)); gg.ui.showToast('复制存档成功'); } /** * 从剪切板读取并设置存档。 * @returns ok=成功;needPaste=需手动粘贴兜底 */ static async applySaveFromClipboard(): Promise<{ ok: boolean; needPaste: boolean; tip: string }> { const r = await MTools.readClipboardResult(); if (r.err) { return { ok: false, needPaste: true, tip: `无法读剪切板:${r.err}` }; } if (!r.text || !String(r.text).trim()) { return { ok: false, needPaste: true, tip: '剪切板无存档,设置失败' }; } const ok = this.applySaveJson(String(r.text), true); return { ok, needPaste: !ok, tip: ok ? '设置成功' : '剪切板无存档,设置失败' }; } /** * 校验对象是否像本项目存档 Doc。 */ static isValidSaveDoc(doc: unknown): boolean { if (!doc || typeof doc !== 'object' || Array.isArray(doc)) return false; const d = doc as { itemCount?: unknown; rankId?: unknown; dataVersion?: unknown; uid?: unknown }; if (Array.isArray(d.itemCount)) return true; if (typeof d.rankId === 'number') return true; if ('dataVersion' in d && ('uid' in d || 'key' in d)) return true; return false; } /** 一键宝石 */ static async grantAllGems(): Promise { const added = await gg.gem.gmGrantAllGems(); gg.data.saveToServer(); gg.ui.showToast(added > 0 ? `已再发宝石 ${added} 种` : 'Weapon 表无宝石'); } /** 一键成品武器 */ static async grantAllWeapons(): Promise { const added = await gg.equip.gmGrantAllBagEquips(); gg.data.saveToServer(); GEvent.Ins.emit(GEvent.UpdateCombatPower); gg.ui.showToast(added > 0 ? `已发放成品武器 ${added} 种` : 'Weapon 表成品已齐'); } /** 清除固定地图 */ static clearFixedMap(): void { gg.game.gmMapId = 0; gg.ui.showToast('已恢复地图随机'); } /** 固定地图 */ static setFixedMap(levelId: number): void { if (levelId <= 0) { this.clearFixedMap(); return; } const clamped = gg.data.project.clampBattleLevelId(levelId); if (clamped !== levelId) { console.warn('[GM] 固定地图 id 不在 Level 表,已钳到', levelId, clamped); } gg.game.gmMapId = clamped; const row = gg.data.table.getTableData(TableNames.Level, clamped); const name = typeof row?.name === 'string' ? row.name.trim() : ''; gg.ui.showToast(name ? `下局固定关卡${clamped}(${name})` : `下局固定关卡${clamped}`); } /** 固定地图摘要 */ static fixedMapSummary(): string { const id = Math.floor(Number(gg.game.gmMapId) || 0); if (id <= 0) return '随机'; const row = gg.data.table.getTableData(TableNames.Level, id); const name = typeof row?.name === 'string' ? row.name.trim() : ''; return name ? `${id}(${name})` : `${id}`; } /** 广告开关 */ static getShowAd(): boolean { return !!gg.sdk.showAd; } /** 设置广告 */ static setShowAd(on: boolean): void { gg.sdk.showAd = on; gg.ui.showToast(on ? '已开启广告' : '已关闭广告'); } /** 内购开关 */ static getIap(): boolean { return !!sdkConfig.isIAPVersion; } /** 设置内购 */ static setIap(on: boolean): void { sdkConfig.isIAPVersion = on; gg.ui.showToast(on ? '已打开内购版本' : '已关闭内购版本'); } /** 每日商店无限 */ static getShopUnlimited(): boolean { return !!DailyShopDataManager.GM_UNLIMITED_DAILY_SHOP; } /** 设置每日商店无限 */ static setShopUnlimited(on: boolean): void { DailyShopDataManager.GM_UNLIMITED_DAILY_SHOP = on; if (!on) DailyShopDataManager.gmResetShopAndLotteryKeepCarry(); gg.ui.showToast(on ? '已打开每日商店无限次数' : '已关闭每日商店无限次数并重置货架'); } /** 快速胜利是否开启 */ static getFastWin(): boolean { return sdkConfig.isDebugFastWinMonsterNum > 0; } /** 快速胜利开关(开=1) */ static setFastWin(on: boolean): void { sdkConfig.isDebugFastWinMonsterNum = on ? 1 : 0; gg.ui.showToast(on ? '快速胜利:击杀1怪后通关' : '已关闭快速胜利'); } /** 快速胜利击杀数 */ static getFastWinNum(): number { return Math.floor(Number(sdkConfig.isDebugFastWinMonsterNum) || 0); } /** 设置快速胜利击杀数 */ static setFastWinNum(n: number): void { const v = Math.max(0, Math.floor(n)); sdkConfig.isDebugFastWinMonsterNum = v; gg.ui.showToast(v > 0 ? `快速胜利:击杀${v}怪后通关` : '已关闭快速胜利'); } /** 己方营地免伤 */ static getLocalCampImmune(): boolean { return !!gg.game.gmLocalCampImmune; } /** 敌方营地免伤 */ static getEnemyCampImmune(): boolean { return !!gg.game.gmEnemyCampImmune; } /** 设置营地免伤 */ static setCampImmune(local: boolean, immune: boolean): void { if (local) gg.game.gmLocalCampImmune = immune; else gg.game.gmEnemyCampImmune = immune; const runtime = gg.battle.match; if (runtime instanceof BattleRuntime) runtime.syncGmCampImmune(); const side = local ? '己方' : '敌方'; gg.ui.showToast(immune ? `已开启${side}营地不掉血(本次登录)` : `已关闭${side}营地不掉血`); } /** 重置每日商店/抽奖 */ static resetDailyShop(): void { DailyShopDataManager.gmResetShopAndLotteryKeepCarry(); gg.ui.showToast('已重置每日商店/抽奖(保留携带道具)'); } /** 清空携带 BUFF */ static clearCarriedBuffs(): void { DailyShopDataManager.gmClearCarriedBuffs(); gg.ui.showToast('已清空携带的每日商店BUFF'); } /** 清除 AI 主动 BUFF */ static clearAiActiveBuff(): void { gg.game.gmAiActiveBuffIds = null; gg.ui.showToast('已恢复 AI 主动匹配随机'); } /** 清除 AI 被动 BUFF */ static clearAiPassiveBuff(): void { gg.game.gmAiPassiveBuffIds = null; gg.ui.showToast('已恢复 AI 被动匹配随机'); } /** 设置 AI BUFF 覆盖 */ static setAiBuffIds(ids: number[] | null, active: boolean): void { if (active) gg.game.gmAiActiveBuffIds = ids; else gg.game.gmAiPassiveBuffIds = ids; const label = active ? 'AI主动' : 'AI被动'; gg.ui.showToast(ids?.length ? `${label}常驻: ${ids.join(',')}` : `已关闭 ${label}覆盖`); } /** AI BUFF 摘要 */ static aiBuffSummary(active: boolean): string { const cur = active ? gg.game.gmAiActiveBuffIds : gg.game.gmAiPassiveBuffIds; return cur?.length ? cur.join(',') : '每局随机'; } /** 写入被动携带第一槽 */ static setPassiveCarry(buffId: number): void { const result = DailyShopDataManager.gmSetFirstCarrySlot(DailyShopKind.Passive, String(buffId)); if (!result.ok) { gg.ui.showToast(result.message ?? '写入失败'); return; } gg.data.saveToServer(); gg.ui.showToast(`被动 buff 已写入 ${buffId}`); } /** 被动携带摘要 */ static passiveCarrySummary(): string { const first = BattleBuffCarryStore.getPassives(0)[0]; return first ? `${first.id}(${first.name})` : '空'; } /** 排行星级&胜场测试 */ static prepareEmolumentPop(): void { const ok = EmolumentDataManager.gmPrepareFirstLoginPopTest(GM_EMOLUMENT_SNAP_STARS, GM_EMOLUMENT_SNAP_WINS); if (!ok) { gg.ui.showToast('俸禄待领星级须 > 0'); return; } gg.data.saveToServer(); gg.ui.showToast(`俸禄待领 星${GM_EMOLUMENT_SNAP_STARS} 胜${GM_EMOLUMENT_SNAP_WINS},请重新进 Home`); } /** 清除副本次数难度全开 */ static clearReplicaCounts(): void { gg.data.setStatistics(StatisticsType.BoxChallengeCount, 0); gg.data.setStatistics(StatisticsType.gachaChallengeModeCount, 0); gg.data.setStatistics(StatisticsType.InfiniteChallengeCount, 0); gg.data.setStatistics(StatisticsType.PropChallengeCount, 0); gg.data.setStatistics(StatisticsType.WeaponChallengeCount, 0); gg.data.setStatistics(StatisticsType.BoxModelLevel, 5); gg.data.setStatistics(StatisticsType.InfiniteModelLevel, 5); gg.data.setStatistics(StatisticsType.PropModelLevel, 5); gg.data.setStatistics(StatisticsType.WeaponModelLevel, 5); gg.data.setStatistics(StatisticsType.GachaChallengeModeLevel, 5); gg.data.setStatistics(StatisticsType.BossChallengeCount, 0); gg.data.resetReplicaEnterAndSweepCountsToday(); gg.data.saveToServer(); gg.ui.showToast('副本次数已清零,难度已全开'); } /** 设置道具数量 */ static setItemNum(itemId: number, num: number): void { gg.data.setItemNum(itemId, num); gg.data.saveToServer(); gg.ui.showToast(`道具 ${itemId} 已设为 ${num}`); } /** 增加道具 */ static addItemNum(itemId: number, delta: number): void { const cur = gg.data.getItemNum(itemId); this.setItemNum(itemId, cur + delta); } /** 货币 get */ static getCurrency(type: ItemNumType): number { return gg.data.getItemNum(type); } /** 货币 set */ static setCurrency(type: ItemNumType, value: number, mode: 'set' | 'add'): void { const next = mode === 'add' ? this.getCurrency(type) + value : value; gg.data.setItemNum(type, Math.max(0, Math.floor(next))); gg.data.saveToServer(); gg.ui.showToast(`已更新 ${type}=${gg.data.getItemNum(type)}`); } /** 设置段位星级 */ static setRankAndStar(rankId: number, intra: number): void { const total = RankDataManager.applyGmRankAndStar(rankId, intra); if (!(total > 0)) { gg.ui.showToast('段位/星级无效'); return; } HeadIconManager.onRankStarsChanged(); gg.data.saveToServer(); void RankBoardManager.syncSelfScore(); GEvent.Ins.emit(GEvent.UpdateRankStars); const name = RankDataManager.getRankConfig(rankId)?.name?.trim() ?? ''; gg.ui.showToast(name ? `段位 ${rankId}(${name}) 星${intra}` : `段位 ${rankId} 星${intra}`); } /** 段位摘要 */ static rankSummary(): string { const rid = RankDataManager.getPlayerRankId(); const intra = RankDataManager.getIntraTierStars(); const row = RankDataManager.getRankConfig(rid); const name = row?.name?.trim() ?? ''; return name ? `${rid} ${intra}(${name})` : `${rid} ${intra}`; } /** 城墙血量 */ static getWallHp(): number { const runtime = gg.battle.match; if (runtime instanceof BattleRuntime && runtime.WallMaxHp > 0) return runtime.WallMaxHp; return Math.floor(Number(gg.game.playerHp) || 0); } /** 设置城墙血量 */ static setWallHp(hp: number): void { const v = Math.max(1, Math.floor(hp)); gg.game.playerHp = v; const runtime = gg.battle.match; if (runtime instanceof BattleRuntime) { const slot = runtime.localSlot; if (slot) { slot.wallMaxHp = v; slot.wallHp = v; } } gg.ui.showToast(`城墙血量 ${v}${gg.battle.match ? '(局内已同步)' : '(下局生效)'}`); } /** * 应用存档 JSON。 * @param raw JSON 字符串 * @param fromClipboard 来自剪切板时用专用失败文案 * @returns 是否成功 */ static applySaveJson(raw: string, fromClipboard = false): boolean { try { const doc = JSON.parse(raw); if (!this.isValidSaveDoc(doc)) { gg.ui.showToast(fromClipboard ? '剪切板无存档,设置失败' : '存档格式错误'); return false; } gg.data.doc = doc; gg.data['_IsFirstLogin'] = false; gg.game.init(); GEvent.Ins.emit(GEvent.UpdateItemNum); gg.data.saveLocal(); gg.ui.showToast('设置成功'); return true; } catch { gg.ui.showToast(fromClipboard ? '剪切板无存档,设置失败' : '存档格式错误'); return false; } } /** * 将可视化编辑的字段写回当前存档并落盘(覆盖 Doc 全部已展示键)。 * @param fields key → 编辑框原文(数组字段为 JSON 文本) */ static applySaveFieldEdits(fields: { [key: string]: string }): boolean { const doc = gg.data.doc as unknown as { [key: string]: unknown }; const keys = Object.keys(fields); for (let i = 0; i < keys.length; i++) { const k = keys[i]; if (!(k in doc)) { console.warn('[GM] 存档无字段', k); continue; } const cur = doc[k]; const raw = fields[k]; if (Array.isArray(cur)) { try { const parsed = JSON.parse(raw === '' ? '[]' : raw); if (!Array.isArray(parsed)) { gg.ui.showToast(`${k} 须为 JSON 数组`); return false; } doc[k] = parsed; } catch { gg.ui.showToast(`${k} JSON 无效`); return false; } } else if (typeof cur === 'number') { const n = Number(raw); if (!Number.isFinite(n)) { gg.ui.showToast(`${k} 须为数字`); return false; } doc[k] = Math.floor(n) === n ? Math.floor(n) : n; } else if (typeof cur === 'string') { doc[k] = String(raw ?? ''); } else if (typeof cur === 'boolean') { doc[k] = raw === '1' || raw === 'true' || raw === 'TRUE'; } else { console.warn('[GM] 跳过不支持类型', k, typeof cur); } } GEvent.Ins.emit(GEvent.UpdateItemNum); GEvent.Ins.emit(GEvent.UpdateRankStars); gg.data.saveLocal(); gg.ui.showToast('存档字段已保存'); return true; } /** 打开 PopGM */ static openPopGm(mode: 1 | 2): void { gg.ui.openPanel(UIID.PopGM, { data: mode }); } /** GM 清档重启 */ static hardReset(): void { gg.ui.closeAllPanel(); GEvent.Ins.emit(GEvent.GameRestart); gg.game.reset('GM重置'); } /** 设置武器库存 */ static setWeaponCount(equipId: number, num: number): void { const conf = gg.equip.getEquip(equipId); if (!conf) { gg.ui.showToast(`武器ID ${equipId} 无效`); return; } gg.equip.setHaveEquipCount(equipId, num); gg.data.saveToServer(); gg.ui.showToast(`武器 ${equipId} 已设为 ${gg.equip.getHaveEquipCount(equipId)}`); } }