import { Button, Color, Label, Layout, Mask, Node, ScrollView, Sprite, UITransform, Widget, HorizontalTextAlignment, VerticalTextAlignment, Layers, Overflow, } from 'cc'; import { SpriteLoad } from '../../mx/components/SpriteLoad'; import type { IGmContentPanel, IGmPanelContext } from '../../mx/module/gm'; import { GmRegistry } from '../../mx/module/gm'; import { GBundle, GPath } from '../game/ConfigRes'; import { TableNames } from '../game/ConfigTableData'; import { RankDataManager } from '../manager/RankDataManager'; import { ProjectGmActions } from './ProjectGmActions'; import { GmSaveEditorPanel } from './ProjectGmSavePanel'; /** 本项目内容面板 id */ export const ProjectGmPanelId = { itemPicker: 'itemPicker', buffPicker: 'buffPicker', levelPicker: 'levelPicker', rankPicker: 'rankPicker', saveEditor: 'saveEditor', } as const; /** * buffPicker 面板数据。 */ export interface IGmBuffPickerData { /** 1=主动 2=被动;0=不限 */ expectType: number; /** 写入目标 */ mode: 'carryPassive' | 'aiActive' | 'aiPassive'; } /** * 创建文字按钮。 */ function makeLabelBtn(parent: Node, text: string, w: number, h: number, onClick: () => void): Node { const n = new Node('btn'); n.layer = Layers.Enum.UI_2D; n.addComponent(UITransform).setContentSize(w, h); const lb = n.addComponent(Label); lb.string = text; lb.fontSize = 22; lb.color = Color.WHITE; lb.horizontalAlign = HorizontalTextAlignment.CENTER; lb.verticalAlign = VerticalTextAlignment.CENTER; n.addComponent(Button); n.on(Button.EventType.CLICK, onClick, null); parent.addChild(n); return n; } /** * 垂直滚动列表宿主。 */ function makeVScroll(parent: Node, w: number, h: number): { host: Node; content: Node } { const host = new Node('scroll'); host.layer = Layers.Enum.UI_2D; host.addComponent(UITransform).setContentSize(w, h); const view = new Node('view'); view.layer = Layers.Enum.UI_2D; view.addComponent(UITransform).setContentSize(w, h); view.addComponent(Mask); host.addChild(view); const content = new Node('content'); content.layer = Layers.Enum.UI_2D; const cut = content.addComponent(UITransform); cut.setContentSize(w, h); cut.setAnchorPoint(0.5, 1); content.setPosition(0, h / 2); const layout = content.addComponent(Layout); layout.type = Layout.Type.VERTICAL; layout.resizeMode = Layout.ResizeMode.CONTAINER; layout.spacingY = 4; layout.paddingTop = 4; view.addChild(content); const sv = host.addComponent(ScrollView); sv.content = content; sv.horizontal = false; sv.vertical = true; parent.addChild(host); return { host, content }; } /** * 道具图标点选 + 数量步进。 */ class GmItemPickerPanel implements IGmContentPanel { private _root: Node | null = null; private _selectedId = 0; private _qty = 1; private _lbSel: Label | null = null; open(parent: Node, ctx: IGmPanelContext): void { this.close(); const pUt = parent.getComponent(UITransform); const W = pUt?.width || 660; const H = pUt?.height || 900; this._root = new Node('itemPicker'); this._root.layer = Layers.Enum.UI_2D; this._root.addComponent(UITransform).setContentSize(W, H); const rw = this._root.addComponent(Widget); rw.isAlignLeft = rw.isAlignRight = rw.isAlignTop = rw.isAlignBottom = true; rw.left = rw.right = rw.top = rw.bottom = 0; parent.addChild(this._root); const tip = new Node('tip'); tip.layer = Layers.Enum.UI_2D; tip.addComponent(UITransform).setContentSize(W - 24, 40); const tl = tip.addComponent(Label); tl.string = '点图标立刻 +1 · 底部可一次 +10/+100'; tl.fontSize = 22; tl.color = new Color(255, 196, 72, 255); tl.horizontalAlign = HorizontalTextAlignment.CENTER; tip.setPosition(0, H / 2 - 28); this._root.addChild(tip); const barH = 120; const scrollH = H - 80 - barH; const { host, content } = makeVScroll(this._root, W - 16, scrollH); host.setPosition(0, barH / 2 - 10); const items = gg.data.table.getTableList(TableNames.Item) ?? []; items.sort((a, b) => Number(a.id) - Number(b.id)); const cell = 80; const cols = Math.max(4, Math.floor((W - 24) / (cell + 6))); let rowNode: Node | null = null; for (let i = 0; i < items.length; i++) { if (i % cols === 0) { rowNode = new Node(`row${i}`); rowNode.layer = Layers.Enum.UI_2D; rowNode.addComponent(UITransform).setContentSize(W - 24, cell); const lay = rowNode.addComponent(Layout); lay.type = Layout.Type.HORIZONTAL; lay.spacingX = 6; content.addChild(rowNode); } const row = items[i]; const cellN = new Node(`i${row.id}`); cellN.layer = Layers.Enum.UI_2D; cellN.addComponent(UITransform).setContentSize(cell - 4, cell - 4); const sp = cellN.addComponent(Sprite); sp.sizeMode = Sprite.SizeMode.CUSTOM; const loader = cellN.addComponent(SpriteLoad); if (row.icon) loader.setSprite(GPath.Item(row.icon), GBundle.Items); cellN.addComponent(Button); const id = Number(row.id); const name = row.name ?? ''; cellN.on(Button.EventType.CLICK, () => { this._selectedId = id; ProjectGmActions.addItemNum(id, 1); this._qty = gg.data.getItemNum(id); this._paint(); ctx.host.toast(`${name} +1 → ${this._qty}`); }, this); rowNode!.addChild(cellN); } content.getComponent(Layout)?.updateLayout(); const bar = new Node('bar'); bar.layer = Layers.Enum.UI_2D; bar.addComponent(UITransform).setContentSize(W - 16, barH - 16); bar.setPosition(0, -H / 2 + barH / 2); this._root.addChild(bar); this._lbSel = (() => { const sel = new Node('sel'); sel.layer = Layers.Enum.UI_2D; sel.addComponent(UITransform).setContentSize(W - 40, 28); const lb = sel.addComponent(Label); lb.fontSize = 20; lb.color = new Color(180, 220, 255, 255); lb.horizontalAlign = HorizontalTextAlignment.CENTER; lb.overflow = Overflow.SHRINK; sel.setPosition(0, 28); bar.addChild(sel); return lb; })(); makeLabelBtn(bar, '+10', 100, 44, () => { if (!this._selectedId) { ctx.host.toast('先点一个图标'); return; } ProjectGmActions.addItemNum(this._selectedId, 10); this._qty = gg.data.getItemNum(this._selectedId); this._paint(); }).setPosition(-160, -16); makeLabelBtn(bar, '+100', 100, 44, () => { if (!this._selectedId) { ctx.host.toast('先点一个图标'); return; } ProjectGmActions.addItemNum(this._selectedId, 100); this._qty = gg.data.getItemNum(this._selectedId); this._paint(); }).setPosition(-40, -16); makeLabelBtn(bar, '返回', 120, 44, () => { ctx.refreshList(); ctx.goBack(); }).setPosition(120, -16); this._paint(); } close(): void { if (this._root?.isValid) this._root.destroy(); this._root = null; this._lbSel = null; } private _paint(): void { if (this._lbSel) { this._lbSel.string = this._selectedId ? `最近:${this._selectedId} 持有 ${gg.data.getItemNum(this._selectedId)}` : '点图标立刻 +1'; } } } /** * BUFF 列表点选。 */ class GmBuffPickerPanel implements IGmContentPanel { private _root: Node | null = null; open(parent: Node, ctx: IGmPanelContext): void { this.close(); const data = ctx.command.panelData as IGmBuffPickerData; if (!data?.mode) { console.warn('[GM] buffPicker 缺少 panelData'); ctx.host.toast('面板配置错误'); ctx.goBack(); return; } this._root = new Node('buffPicker'); this._root.layer = Layers.Enum.UI_2D; this._root.addComponent(UITransform).setContentSize(660, 900); parent.addChild(this._root); const title = new Node('t'); title.layer = Layers.Enum.UI_2D; title.addComponent(UITransform).setContentSize(600, 36); const tl = title.addComponent(Label); tl.string = ctx.command.title; tl.fontSize = 26; tl.color = Color.WHITE; tl.horizontalAlign = HorizontalTextAlignment.CENTER; title.setPosition(0, 420); this._root.addChild(title); const { host, content } = makeVScroll(this._root, 640, 700); host.setPosition(0, 20); const list = gg.data.table.getTableList(TableNames.Battlebuff) ?? []; list.sort((a, b) => Number(a.id) - Number(b.id)); for (let i = 0; i < list.length; i++) { const row = list[i]; const t = Math.floor(Number((row as { type?: number }).type) || 0); if (data.expectType > 0 && t !== data.expectType) continue; const id = Number(row.id); const label = `${id} ${row.name ?? ''} (t${t})`; makeLabelBtn(content, label, 620, 44, () => { if (data.mode === 'carryPassive') ProjectGmActions.setPassiveCarry(id); else if (data.mode === 'aiActive') ProjectGmActions.setAiBuffIds([id], true); else ProjectGmActions.setAiBuffIds([id], false); ctx.refreshList(); ctx.goBack(); }); } makeLabelBtn(content, '清除覆盖/空', 620, 44, () => { if (data.mode === 'carryPassive') { ctx.host.toast('被动携带请选具体 id'); return; } ProjectGmActions.setAiBuffIds(null, data.mode === 'aiActive'); ctx.refreshList(); ctx.goBack(); }); content.getComponent(Layout)?.updateLayout(); makeLabelBtn(this._root, '返回', 140, 44, () => ctx.goBack()).setPosition(0, -400); } close(): void { if (this._root?.isValid) this._root.destroy(); this._root = null; } } /** * 关卡列表点选。 */ class GmLevelPickerPanel implements IGmContentPanel { private _root: Node | null = null; open(parent: Node, ctx: IGmPanelContext): void { this.close(); this._root = new Node('levelPicker'); this._root.layer = Layers.Enum.UI_2D; this._root.addComponent(UITransform).setContentSize(660, 900); parent.addChild(this._root); const title = new Node('t'); title.layer = Layers.Enum.UI_2D; title.addComponent(UITransform).setContentSize(600, 36); const tl = title.addComponent(Label); tl.string = ctx.command.title; tl.fontSize = 26; tl.color = Color.WHITE; tl.horizontalAlign = HorizontalTextAlignment.CENTER; title.setPosition(0, 420); this._root.addChild(title); const { host, content } = makeVScroll(this._root, 640, 700); host.setPosition(0, 20); makeLabelBtn(content, '0 关闭固定(随机)', 620, 44, () => { ProjectGmActions.setFixedMap(0); ctx.refreshList(); ctx.goBack(); }); const list = gg.data.table.getTableList(TableNames.Level) ?? []; list.sort((a, b) => Number(a.id) - Number(b.id)); for (let i = 0; i < list.length; i++) { const row = list[i]; const id = Number(row.id); const name = row.name ?? ''; makeLabelBtn(content, `${id} ${name}`, 620, 44, () => { ProjectGmActions.setFixedMap(id); ctx.refreshList(); ctx.goBack(); }); } content.getComponent(Layout)?.updateLayout(); makeLabelBtn(this._root, '返回', 140, 44, () => ctx.goBack()).setPosition(0, -400); } close(): void { if (this._root?.isValid) this._root.destroy(); this._root = null; } } /** * 段位点选 + 星级步进。 */ class GmRankPickerPanel implements IGmContentPanel { private _root: Node | null = null; private _rankId = 1; private _star = 1; private _lb: Label | null = null; open(parent: Node, ctx: IGmPanelContext): void { this.close(); this._rankId = RankDataManager.getPlayerRankId() || 1; this._star = RankDataManager.getIntraTierStars() || 1; this._root = new Node('rankPicker'); this._root.layer = Layers.Enum.UI_2D; this._root.addComponent(UITransform).setContentSize(660, 900); parent.addChild(this._root); const title = new Node('t'); title.layer = Layers.Enum.UI_2D; title.addComponent(UITransform).setContentSize(600, 36); const tl = title.addComponent(Label); tl.string = ctx.command.title; tl.fontSize = 26; tl.color = Color.WHITE; tl.horizontalAlign = HorizontalTextAlignment.CENTER; title.setPosition(0, 420); this._root.addChild(title); const cur = new Node('cur'); cur.layer = Layers.Enum.UI_2D; cur.addComponent(UITransform).setContentSize(600, 36); this._lb = cur.addComponent(Label); this._lb.fontSize = 24; this._lb.color = Color.YELLOW; this._lb.horizontalAlign = HorizontalTextAlignment.CENTER; cur.setPosition(0, 360); this._root.addChild(cur); const { host, content } = makeVScroll(this._root, 640, 480); host.setPosition(0, 40); const list = gg.data.table.getTableList(TableNames.Rank) ?? []; list.sort((a, b) => Number(a.id) - Number(b.id)); for (let i = 0; i < list.length; i++) { const row = list[i]; const id = Number(row.id); const name = row.name ?? ''; makeLabelBtn(content, `${id} ${name}`, 620, 44, () => { this._rankId = id; this._paint(); }); } content.getComponent(Layout)?.updateLayout(); makeLabelBtn(this._root, '星-1', 100, 40, () => { this._star = Math.max(1, this._star - 1); this._paint(); }).setPosition(-160, -260); makeLabelBtn(this._root, '星+1', 100, 40, () => { this._star = Math.min(5, this._star + 1); this._paint(); }).setPosition(-40, -260); makeLabelBtn(this._root, '应用', 120, 44, () => { ProjectGmActions.setRankAndStar(this._rankId, this._star); ctx.refreshList(); ctx.goBack(); }).setPosition(100, -260); makeLabelBtn(this._root, '返回', 120, 44, () => ctx.goBack()).setPosition(0, -320); this._paint(); } close(): void { if (this._root?.isValid) this._root.destroy(); this._root = null; this._lb = null; } private _paint(): void { if (this._lb) this._lb.string = `段位 ${this._rankId} 星 ${this._star}`; } } /** * 注册本项目内容面板。 */ export class ProjectGmPanels { private static _done = false; /** 幂等注册 */ static ensureRegistered(): void { if (this._done) return; this._done = true; GmRegistry.registerPanel(ProjectGmPanelId.itemPicker, () => new GmItemPickerPanel()); GmRegistry.registerPanel(ProjectGmPanelId.buffPicker, () => new GmBuffPickerPanel()); GmRegistry.registerPanel(ProjectGmPanelId.levelPicker, () => new GmLevelPickerPanel()); GmRegistry.registerPanel(ProjectGmPanelId.rankPicker, () => new GmRankPickerPanel()); GmRegistry.registerPanel(ProjectGmPanelId.saveEditor, () => new GmSaveEditorPanel()); } }