You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
478 lines
18 KiB
478 lines
18 KiB
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 { EmolumentDataManager } from '../manager/EmolumentDataManager';
|
|
import { HeadIconManager } from '../manager/HeadIconManager';
|
|
import { RankBoardManager } from '../manager/RankBoardManager';
|
|
import { RankDataManager } from '../manager/RankDataManager';
|
|
|
|
/** 俸禄 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<void> {
|
|
const added = await gg.gem.gmGrantAllGems();
|
|
gg.data.saveToServer();
|
|
gg.ui.showToast(added > 0 ? `已再发宝石 ${added} 种` : 'Weapon 表无宝石');
|
|
}
|
|
|
|
/** 一键成品武器 */
|
|
static async grantAllWeapons(): Promise<void> {
|
|
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<ITableLevel>(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<ITableLevel>(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('已重置每日商店/抽奖(保留携带道具)');
|
|
}
|
|
|
|
/** 清空 GM 词条覆盖 */
|
|
static clearCarriedBuffs(): void {
|
|
gg.game.gmCarryBuffIds = null;
|
|
DailyShopDataManager.gmClearCarriedBuffs();
|
|
gg.ui.showToast('已清空 GM 词条');
|
|
}
|
|
|
|
/** 清除 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;
|
|
if (!cur?.length) return '每局随机';
|
|
const parts: string[] = [];
|
|
for (let i = 0; i < cur.length; i++) {
|
|
const id = cur[i];
|
|
const row = gg.data.table.getTableData<ITableBattlebuff>(TableNames.Battlebuff, id);
|
|
parts.push(row?.name ? `${id}:${row.name}` : `${id}`);
|
|
}
|
|
return parts.join(', ');
|
|
}
|
|
|
|
/** 写入被动携带(追加单个,兼容旧调用) */
|
|
static setPassiveCarry(buffId: number): void {
|
|
this.setCarryBuffIds([buffId]);
|
|
}
|
|
|
|
/**
|
|
* 覆盖 GM 词条(跨局常驻,开战灌入 CarryStore)。
|
|
* @param ids Battlebuff.id 列表
|
|
* @param quiet 为 true 时不弹 toast(列表即时写入用)
|
|
*/
|
|
static setCarryBuffIds(ids: number[], quiet = false): void {
|
|
const cleaned: number[] = [];
|
|
for (let i = 0; i < ids.length; i++) {
|
|
const id = Math.floor(Number(ids[i]) || 0);
|
|
if (id <= 0) continue;
|
|
const row = gg.data.table.getTableData<ITableBattlebuff>(TableNames.Battlebuff, id);
|
|
if (!row) {
|
|
console.warn('[GM] 词条 id 不存在', id);
|
|
continue;
|
|
}
|
|
cleaned.push(id);
|
|
}
|
|
gg.game.gmCarryBuffIds = cleaned.length > 0 ? cleaned : null;
|
|
if (!quiet) {
|
|
gg.ui.showToast(cleaned.length > 0 ? `已设置 ${cleaned.length} 个词条(开战生效)` : '已清空词条');
|
|
}
|
|
}
|
|
|
|
/** 当前 GM 词条 id 列表 */
|
|
static getCarryBuffIds(): number[] {
|
|
const cur = gg.game.gmCarryBuffIds;
|
|
return cur?.length ? cur.slice() : [];
|
|
}
|
|
|
|
/** 被动携带摘要 */
|
|
static passiveCarrySummary(): string {
|
|
const ids = this.getCarryBuffIds();
|
|
if (!ids.length) return '空';
|
|
const parts: string[] = [];
|
|
for (let i = 0; i < ids.length; i++) {
|
|
const id = ids[i];
|
|
const row = gg.data.table.getTableData<ITableBattlebuff>(TableNames.Battlebuff, id);
|
|
parts.push(row?.name ? `${id}:${row.name}` : `${id}`);
|
|
}
|
|
return parts.join(', ');
|
|
}
|
|
|
|
/** 排行星级&胜场测试 */
|
|
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)}`);
|
|
}
|
|
}
|
|
|