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.
AdLike2048/assets/script/gm/ProjectGmActions.ts

443 lines
16 KiB

1 day ago
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<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('已重置每日商店/抽奖(保留携带道具)');
}
/** 清空携带 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)}`);
}
}