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.
967 lines
36 KiB
967 lines
36 KiB
import {
|
|
Button, Color, Graphics, 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 { battleBuffSlotType } from '../manager/BattleBuffParse';
|
|
import { BattleBuffSlotType } from '../manager/battleBuff/BattleBuffTypes';
|
|
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 面板数据。
|
|
* 新表已无 type 列时 expectType 请传 0,否则列表会被滤空。
|
|
*/
|
|
export interface IGmBuffPickerData {
|
|
/** BattleBuffSlotType:1主动 2被动 3自动;0=不限(推荐) */
|
|
expectType: number;
|
|
/** 写入目标 */
|
|
mode: 'carryPassive' | 'aiActive' | 'aiPassive';
|
|
}
|
|
|
|
const BUFF_C = {
|
|
text: new Color(255, 255, 255, 255),
|
|
dim: new Color(180, 190, 210, 255),
|
|
accent: new Color(255, 220, 70, 255),
|
|
rowBg: new Color(28, 36, 56, 255),
|
|
rowLine: new Color(100, 160, 240, 160),
|
|
rowOn: new Color(255, 220, 70, 255),
|
|
btn: new Color(45, 105, 190, 255),
|
|
btnClear: new Color(210, 120, 35, 255),
|
|
btnBack: new Color(70, 78, 96, 255),
|
|
};
|
|
|
|
const QUALITY_COLOR: { [q: number]: Color } = {
|
|
1: new Color(90, 210, 120, 255),
|
|
2: new Color(80, 160, 255, 255),
|
|
3: new Color(190, 110, 255, 255),
|
|
4: new Color(255, 170, 60, 255),
|
|
};
|
|
|
|
const QUALITY_NAME: { [q: number]: string } = {
|
|
1: '绿',
|
|
2: '蓝',
|
|
3: '紫',
|
|
4: '橙',
|
|
};
|
|
|
|
const SLOT_NAME: { [t: number]: string } = {
|
|
[BattleBuffSlotType.Active]: '主动',
|
|
[BattleBuffSlotType.Passive]: '被动',
|
|
[BattleBuffSlotType.Auto]: '自动',
|
|
};
|
|
|
|
/**
|
|
* 创建文字按钮。
|
|
*/
|
|
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 };
|
|
}
|
|
|
|
const ITEM_C = {
|
|
text: new Color(255, 255, 255, 255),
|
|
dim: new Color(170, 185, 210, 255),
|
|
accent: new Color(255, 220, 70, 255),
|
|
card: new Color(28, 36, 56, 255),
|
|
cellBg: new Color(18, 24, 40, 255),
|
|
cellLine: new Color(90, 120, 170, 160),
|
|
selLine: new Color(255, 220, 70, 255),
|
|
selBg: new Color(48, 42, 18, 255),
|
|
btn: new Color(45, 105, 190, 255),
|
|
btnHot: new Color(18, 92, 78, 255),
|
|
};
|
|
|
|
/**
|
|
* 道具图标点选 + 数量步进。
|
|
*/
|
|
class GmItemPickerPanel implements IGmContentPanel {
|
|
private _root: Node | null = null;
|
|
private _ctx: IGmPanelContext | null = null;
|
|
private _selectedId = 0;
|
|
private _selectedName = '';
|
|
private _lbHead: Label | null = null;
|
|
private _lbQty: Label | null = null;
|
|
private _cells: { [id: number]: Node } = {};
|
|
private _cellSz = 76;
|
|
|
|
open(parent: Node, ctx: IGmPanelContext): void {
|
|
this.close();
|
|
this._ctx = ctx;
|
|
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 headH = 88;
|
|
const barH = 108;
|
|
const head = new Node('head');
|
|
head.layer = Layers.Enum.UI_2D;
|
|
head.addComponent(UITransform).setContentSize(W - 16, headH);
|
|
this._drawRound(head, ITEM_C.card, W - 16, headH, 14, ITEM_C.selLine);
|
|
head.setPosition(0, H / 2 - headH / 2 - 4);
|
|
this._root.addChild(head);
|
|
|
|
this._lbHead = this._makeLb(head, '点击道具图标:选中并 +1', W - 40, 32, 24, ITEM_C.accent);
|
|
this._lbHead.node.setPosition(0, 18);
|
|
this._lbQty = this._makeLb(head, '底部可一次 +10 / +100', W - 40, 28, 20, ITEM_C.dim);
|
|
this._lbQty.node.setPosition(0, -18);
|
|
|
|
const scrollH = H - headH - barH - 16;
|
|
const { host, content } = makeVScroll(this._root, W - 16, scrollH);
|
|
host.setPosition(0, (barH - headH) / 2);
|
|
|
|
const items = gg.data.table.getTableList<ITableItem>(TableNames.Item) ?? [];
|
|
items.sort((a, b) => Number(a.id) - Number(b.id));
|
|
const gap = 8;
|
|
this._cellSz = 76;
|
|
const cols = Math.max(4, Math.floor((W - 24) / (this._cellSz + gap)));
|
|
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, this._cellSz);
|
|
const lay = rowNode.addComponent(Layout);
|
|
lay.type = Layout.Type.HORIZONTAL;
|
|
lay.spacingX = gap;
|
|
content.addChild(rowNode);
|
|
}
|
|
const row = items[i];
|
|
const id = Number(row.id);
|
|
const name = row.name ?? '';
|
|
const cellN = this._makeCell(id, name, row.icon);
|
|
rowNode!.addChild(cellN);
|
|
this._cells[id] = cellN;
|
|
}
|
|
content.getComponent(Layout)?.updateLayout();
|
|
|
|
const bar = new Node('bar');
|
|
bar.layer = Layers.Enum.UI_2D;
|
|
bar.addComponent(UITransform).setContentSize(W - 16, barH - 12);
|
|
this._drawRound(bar, ITEM_C.card, W - 16, barH - 12, 14, ITEM_C.cellLine);
|
|
bar.setPosition(0, -H / 2 + barH / 2);
|
|
this._root.addChild(bar);
|
|
|
|
const embedded = !!ctx.embedded;
|
|
const btnY = 0;
|
|
this._pill(bar, '+10', 120, 48, ITEM_C.btnHot, () => this._addQty(10)).setPosition(embedded ? -140 : -180, btnY);
|
|
this._pill(bar, '+100', 120, 48, ITEM_C.btn, () => this._addQty(100)).setPosition(embedded ? 140 : -40, btnY);
|
|
if (!embedded) {
|
|
this._pill(bar, '返回', 110, 48, new Color(70, 78, 96, 255), () => {
|
|
ctx.refreshList();
|
|
ctx.goBack();
|
|
}).setPosition(160, btnY);
|
|
}
|
|
|
|
this._paintSel();
|
|
}
|
|
|
|
close(): void {
|
|
if (this._root?.isValid) this._root.destroy();
|
|
this._root = null;
|
|
this._ctx = null;
|
|
this._lbHead = null;
|
|
this._lbQty = null;
|
|
this._cells = {};
|
|
}
|
|
|
|
private _makeCell(id: number, name: string, icon: string | undefined): Node {
|
|
const sz = this._cellSz;
|
|
const cellN = new Node(`i${id}`);
|
|
cellN.layer = Layers.Enum.UI_2D;
|
|
cellN.addComponent(UITransform).setContentSize(sz, sz);
|
|
this._drawRound(cellN, ITEM_C.cellBg, sz, sz, 12, ITEM_C.cellLine);
|
|
|
|
const iconN = new Node('icon');
|
|
iconN.layer = Layers.Enum.UI_2D;
|
|
iconN.addComponent(UITransform).setContentSize(sz - 16, sz - 16);
|
|
const sp = iconN.addComponent(Sprite);
|
|
sp.sizeMode = Sprite.SizeMode.CUSTOM;
|
|
const loader = iconN.addComponent(SpriteLoad);
|
|
if (icon) loader.setSprite(GPath.Item(icon), GBundle.Items);
|
|
cellN.addChild(iconN);
|
|
|
|
const badge = new Node('qty');
|
|
badge.layer = Layers.Enum.UI_2D;
|
|
badge.addComponent(UITransform).setContentSize(sz - 8, 20);
|
|
const bl = badge.addComponent(Label);
|
|
bl.string = `${gg.data.getItemNum(id)}`;
|
|
bl.fontSize = 16;
|
|
bl.isBold = true;
|
|
bl.color = ITEM_C.accent;
|
|
bl.horizontalAlign = HorizontalTextAlignment.RIGHT;
|
|
bl.verticalAlign = VerticalTextAlignment.CENTER;
|
|
bl.overflow = Overflow.SHRINK;
|
|
badge.setPosition(0, -sz / 2 + 12);
|
|
cellN.addChild(badge);
|
|
|
|
cellN.addComponent(Button);
|
|
cellN.on(Button.EventType.CLICK, () => {
|
|
this._selectedId = id;
|
|
this._selectedName = name;
|
|
ProjectGmActions.addItemNum(id, 1);
|
|
this._paintSel();
|
|
this._ctx?.host.toast(`${name} +1 → ${gg.data.getItemNum(id)}`);
|
|
}, this);
|
|
return cellN;
|
|
}
|
|
|
|
private _addQty(delta: number): void {
|
|
if (!this._selectedId) {
|
|
this._ctx?.host.toast('先点选一个道具');
|
|
return;
|
|
}
|
|
ProjectGmActions.addItemNum(this._selectedId, delta);
|
|
this._paintSel();
|
|
this._ctx?.host.toast(`${this._selectedName || this._selectedId} +${delta}`);
|
|
}
|
|
|
|
private _paintSel(): void {
|
|
const ids = Object.keys(this._cells);
|
|
for (let i = 0; i < ids.length; i++) {
|
|
const id = Number(ids[i]);
|
|
const cell = this._cells[id];
|
|
if (!cell?.isValid) continue;
|
|
const on = id === this._selectedId;
|
|
this._drawRound(cell, on ? ITEM_C.selBg : ITEM_C.cellBg, this._cellSz, this._cellSz, 12, on ? ITEM_C.selLine : ITEM_C.cellLine);
|
|
const qtyLb = cell.getChildByName('qty')?.getComponent(Label);
|
|
if (qtyLb) qtyLb.string = `${gg.data.getItemNum(id)}`;
|
|
}
|
|
if (this._lbHead) {
|
|
this._lbHead.string = this._selectedId
|
|
? `已选 · ${this._selectedId} ${this._selectedName}`
|
|
: '点击道具图标:选中并 +1';
|
|
}
|
|
if (this._lbQty) {
|
|
this._lbQty.string = this._selectedId
|
|
? `持有 ${gg.data.getItemNum(this._selectedId)} · 点图标再 +1`
|
|
: '底部可一次 +10 / +100';
|
|
}
|
|
}
|
|
|
|
private _makeLb(parent: Node, text: string, w: number, h: number, size: number, color: Color): Label {
|
|
const n = new Node('lb');
|
|
n.layer = Layers.Enum.UI_2D;
|
|
n.addComponent(UITransform).setContentSize(w, h);
|
|
const lb = n.addComponent(Label);
|
|
lb.string = text;
|
|
lb.fontSize = size;
|
|
lb.isBold = true;
|
|
lb.color = color;
|
|
lb.horizontalAlign = HorizontalTextAlignment.CENTER;
|
|
lb.verticalAlign = VerticalTextAlignment.CENTER;
|
|
lb.overflow = Overflow.SHRINK;
|
|
parent.addChild(n);
|
|
return lb;
|
|
}
|
|
|
|
private _pill(parent: Node, text: string, w: number, h: number, bg: Color, onClick: () => void): Node {
|
|
const n = new Node(text);
|
|
n.layer = Layers.Enum.UI_2D;
|
|
n.addComponent(UITransform).setContentSize(w, h);
|
|
this._drawRound(n, bg, w, h, 12);
|
|
const lbN = new Node('lb');
|
|
lbN.layer = Layers.Enum.UI_2D;
|
|
lbN.addComponent(UITransform).setContentSize(w - 6, h - 4);
|
|
const lb = lbN.addComponent(Label);
|
|
lb.string = text;
|
|
lb.fontSize = 24;
|
|
lb.isBold = true;
|
|
lb.color = ITEM_C.text;
|
|
lb.horizontalAlign = HorizontalTextAlignment.CENTER;
|
|
lb.verticalAlign = VerticalTextAlignment.CENTER;
|
|
n.addChild(lbN);
|
|
n.addComponent(Button);
|
|
n.on(Button.EventType.CLICK, onClick, this);
|
|
parent.addChild(n);
|
|
return n;
|
|
}
|
|
|
|
private _drawRound(node: Node, fill: Color, w: number, h: number, r: number, stroke?: Color): void {
|
|
const g = node.getComponent(Graphics) || node.addComponent(Graphics);
|
|
g.clear();
|
|
g.fillColor = fill;
|
|
g.roundRect(-w / 2, -h / 2, w, h, r);
|
|
g.fill();
|
|
if (stroke) {
|
|
g.strokeColor = stroke;
|
|
g.lineWidth = stroke === ITEM_C.selLine ? 3 : 2;
|
|
g.roundRect(-w / 2, -h / 2, w, h, r);
|
|
g.stroke();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 词条页:上=未选、下=已选;点击即对调并立刻写入。
|
|
*/
|
|
class GmBuffPickerPanel implements IGmContentPanel {
|
|
private _root: Node | null = null;
|
|
private _ctx: IGmPanelContext | null = null;
|
|
private _data: IGmBuffPickerData | null = null;
|
|
private _listW = 640;
|
|
private _allRows: ITableBattlebuff[] = [];
|
|
private _selected: number[] = [];
|
|
private _availContent: Node | null = null;
|
|
private _selContent: Node | null = null;
|
|
private _lbAvail: Label | null = null;
|
|
private _lbSel: Label | null = null;
|
|
|
|
open(parent: Node, ctx: IGmPanelContext): void {
|
|
this.close();
|
|
this._ctx = ctx;
|
|
const raw = ctx.command.panelData as IGmBuffPickerData | undefined;
|
|
const data: IGmBuffPickerData = {
|
|
expectType: raw?.expectType ?? 0,
|
|
mode: raw?.mode ?? 'carryPassive',
|
|
};
|
|
this._data = data;
|
|
this._selected = this._loadCurrentIds();
|
|
|
|
const pUt = parent.getComponent(UITransform);
|
|
const W = pUt?.width || 660;
|
|
const H = pUt?.height || 900;
|
|
this._listW = W - 16;
|
|
|
|
this._root = new Node('buffPicker');
|
|
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 headH = 36;
|
|
const gap = 10;
|
|
const clearH = 44;
|
|
const usable = Math.max(200, H - headH * 2 - gap - clearH - 8);
|
|
const availH = Math.floor(usable * 0.58);
|
|
const selH = usable - availH;
|
|
|
|
this._lbAvail = this._addHead(this._root, '未选择(点选加入)', this._listW, headH);
|
|
this._lbAvail.node.setPosition(0, H / 2 - headH / 2 - 2);
|
|
|
|
const { host: availHost, content: availContent } = makeVScroll(this._root, this._listW, availH);
|
|
this._availContent = availContent;
|
|
const availLay = availContent.getComponent(Layout);
|
|
if (availLay) {
|
|
availLay.spacingY = 8;
|
|
availLay.paddingBottom = 12;
|
|
}
|
|
availHost.setPosition(0, H / 2 - headH - availH / 2 - 4);
|
|
|
|
const selTopY = -H / 2 + clearH + selH + headH + 4;
|
|
this._lbSel = this._addHead(this._root, '已选择', this._listW, headH);
|
|
this._lbSel.node.setPosition(0, selTopY - headH / 2);
|
|
|
|
const { host: selHost, content: selContent } = makeVScroll(this._root, this._listW, selH);
|
|
this._selContent = selContent;
|
|
const selLay = selContent.getComponent(Layout);
|
|
if (selLay) {
|
|
selLay.spacingY = 8;
|
|
selLay.paddingBottom = 12;
|
|
}
|
|
selHost.setPosition(0, -H / 2 + clearH + selH / 2);
|
|
|
|
this._pillBtn(this._root, '清空已选', 200, 40, BUFF_C.btnClear, () => this._clearSelected())
|
|
.setPosition(0, -H / 2 + clearH / 2);
|
|
|
|
this._allRows = [];
|
|
const list = gg.data.table.getTableList<ITableBattlebuff>(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 id = Math.floor(Number(row.id) || 0);
|
|
if (id <= 0) continue;
|
|
const slot = battleBuffSlotType(row);
|
|
if (data.expectType > 0 && slot !== data.expectType) continue;
|
|
this._allRows.push(row);
|
|
}
|
|
if (this._allRows.length <= 0) {
|
|
console.warn('[GM] buffPicker 列表为空', data);
|
|
}
|
|
this._rebuildLists();
|
|
}
|
|
|
|
close(): void {
|
|
if (this._root?.isValid) this._root.destroy();
|
|
this._root = null;
|
|
this._ctx = null;
|
|
this._data = null;
|
|
this._allRows = [];
|
|
this._selected = [];
|
|
this._availContent = null;
|
|
this._selContent = null;
|
|
this._lbAvail = null;
|
|
this._lbSel = null;
|
|
}
|
|
|
|
/** 读取当前已生效 id */
|
|
private _loadCurrentIds(): number[] {
|
|
if (!this._data) return [];
|
|
if (this._data.mode === 'carryPassive') return ProjectGmActions.getCarryBuffIds();
|
|
const cur = this._data.mode === 'aiActive'
|
|
? gg.game.gmAiActiveBuffIds
|
|
: gg.game.gmAiPassiveBuffIds;
|
|
return cur?.length ? cur.slice() : [];
|
|
}
|
|
|
|
private _hasId(id: number): boolean {
|
|
for (let i = 0; i < this._selected.length; i++) {
|
|
if (this._selected[i] === id) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** 点击未选 → 加入;点击已选 → 移除;立刻写入 */
|
|
private _move(id: number, toSelected: boolean): void {
|
|
if (toSelected) {
|
|
if (!this._hasId(id)) this._selected.push(id);
|
|
} else {
|
|
const next: number[] = [];
|
|
for (let i = 0; i < this._selected.length; i++) {
|
|
if (this._selected[i] !== id) next.push(this._selected[i]);
|
|
}
|
|
this._selected = next;
|
|
}
|
|
this._applyNow();
|
|
this._rebuildLists();
|
|
}
|
|
|
|
private _clearSelected(): void {
|
|
this._selected = [];
|
|
this._applyNow();
|
|
this._rebuildLists();
|
|
}
|
|
|
|
private _applyNow(): void {
|
|
if (!this._data) return;
|
|
const ids = this._selected.slice();
|
|
if (this._data.mode === 'carryPassive') {
|
|
ProjectGmActions.setCarryBuffIds(ids, true);
|
|
} else if (this._data.mode === 'aiActive') {
|
|
ProjectGmActions.setAiBuffIds(ids.length ? ids : null, true);
|
|
} else {
|
|
ProjectGmActions.setAiBuffIds(ids.length ? ids : null, false);
|
|
}
|
|
}
|
|
|
|
private _rebuildLists(): void {
|
|
if (!this._availContent?.isValid || !this._selContent?.isValid) return;
|
|
this._availContent.removeAllChildren();
|
|
this._selContent.removeAllChildren();
|
|
|
|
let availN = 0;
|
|
let selN = 0;
|
|
for (let i = 0; i < this._allRows.length; i++) {
|
|
const row = this._allRows[i];
|
|
const id = Math.floor(Number(row.id) || 0);
|
|
if (this._hasId(id)) {
|
|
this._addBuffCard(this._selContent, this._listW, row, false);
|
|
selN++;
|
|
} else {
|
|
this._addBuffCard(this._availContent, this._listW, row, true);
|
|
availN++;
|
|
}
|
|
}
|
|
if (availN <= 0) this._addEmptyHint(this._availContent, '已全部选中');
|
|
if (selN <= 0) this._addEmptyHint(this._selContent, '点击上方加入');
|
|
|
|
if (this._lbAvail) this._lbAvail.string = `未选择 ${availN}(点选加入)`;
|
|
if (this._lbSel) this._lbSel.string = `已选择 ${selN}(点选移除)`;
|
|
|
|
this._availContent.getComponent(Layout)?.updateLayout();
|
|
this._selContent.getComponent(Layout)?.updateLayout();
|
|
}
|
|
|
|
private _addEmptyHint(parent: Node, text: string): void {
|
|
const n = new Node('empty');
|
|
n.layer = Layers.Enum.UI_2D;
|
|
n.addComponent(UITransform).setContentSize(this._listW, 48);
|
|
const lb = n.addComponent(Label);
|
|
lb.string = text;
|
|
lb.fontSize = 22;
|
|
lb.color = BUFF_C.dim;
|
|
lb.horizontalAlign = HorizontalTextAlignment.CENTER;
|
|
parent.addChild(n);
|
|
}
|
|
|
|
private _addHead(parent: Node, text: string, w: number, h: number): Label {
|
|
const n = new Node('head');
|
|
n.layer = Layers.Enum.UI_2D;
|
|
n.addComponent(UITransform).setContentSize(w, h);
|
|
const lb = n.addComponent(Label);
|
|
lb.string = text;
|
|
lb.fontSize = 24;
|
|
lb.isBold = true;
|
|
lb.color = BUFF_C.accent;
|
|
lb.horizontalAlign = HorizontalTextAlignment.LEFT;
|
|
lb.verticalAlign = VerticalTextAlignment.CENTER;
|
|
lb.overflow = Overflow.CLAMP;
|
|
parent.addChild(n);
|
|
return lb;
|
|
}
|
|
|
|
/**
|
|
* @param toSelected true=点后加入已选;false=点后移回未选
|
|
*/
|
|
private _addBuffCard(parent: Node, w: number, row: ITableBattlebuff, toSelected: boolean): void {
|
|
const h = 84;
|
|
const id = Math.floor(Number(row.id) || 0);
|
|
const name = String(row.name ?? '');
|
|
const desc = String(row.desc ?? '');
|
|
const q = Math.max(0, Math.floor(Number(row.quality) || 0));
|
|
const slot = battleBuffSlotType(row);
|
|
const qColor = QUALITY_COLOR[q] || BUFF_C.dim;
|
|
const qName = QUALITY_NAME[q] || `Q${q}`;
|
|
const slotName = slot != null ? (SLOT_NAME[slot] || `t${slot}`) : '?';
|
|
|
|
const card = new Node(`buff${id}`);
|
|
card.layer = Layers.Enum.UI_2D;
|
|
card.addComponent(UITransform).setContentSize(w, h);
|
|
this._drawRound(card, BUFF_C.rowBg, w, h, 12, toSelected ? BUFF_C.rowLine : BUFF_C.rowOn);
|
|
|
|
const iconSz = 56;
|
|
const iconN = new Node('icon');
|
|
iconN.layer = Layers.Enum.UI_2D;
|
|
iconN.addComponent(UITransform).setContentSize(iconSz, iconSz);
|
|
const sp = iconN.addComponent(Sprite);
|
|
sp.sizeMode = Sprite.SizeMode.CUSTOM;
|
|
const loader = iconN.addComponent(SpriteLoad);
|
|
const iconName = String(row.icon || row.name || '').trim();
|
|
if (iconName) loader.setSprite(GPath.ShopSkill(iconName), GBundle.Items);
|
|
iconN.setPosition(-w / 2 + iconSz / 2 + 10, 0);
|
|
card.addChild(iconN);
|
|
|
|
const textW = w - iconSz - 28;
|
|
const head = new Node('head');
|
|
head.layer = Layers.Enum.UI_2D;
|
|
head.addComponent(UITransform).setContentSize(textW, 30);
|
|
const hl = head.addComponent(Label);
|
|
hl.string = `${id} ${name} · ${qName}/${slotName}`;
|
|
hl.fontSize = 22;
|
|
hl.isBold = true;
|
|
hl.color = qColor;
|
|
hl.horizontalAlign = HorizontalTextAlignment.LEFT;
|
|
hl.verticalAlign = VerticalTextAlignment.CENTER;
|
|
hl.overflow = Overflow.CLAMP;
|
|
hl.enableWrapText = false;
|
|
head.setPosition(iconSz / 2 + 4, 16);
|
|
card.addChild(head);
|
|
|
|
const body = new Node('desc');
|
|
body.layer = Layers.Enum.UI_2D;
|
|
body.addComponent(UITransform).setContentSize(textW, 34);
|
|
const bl = body.addComponent(Label);
|
|
bl.string = desc || '(无描述)';
|
|
bl.fontSize = 17;
|
|
bl.color = BUFF_C.dim;
|
|
bl.horizontalAlign = HorizontalTextAlignment.LEFT;
|
|
bl.verticalAlign = VerticalTextAlignment.TOP;
|
|
bl.overflow = Overflow.CLAMP;
|
|
bl.enableWrapText = true;
|
|
body.setPosition(iconSz / 2 + 4, -16);
|
|
card.addChild(body);
|
|
|
|
card.addComponent(Button);
|
|
card.on(Button.EventType.CLICK, () => this._move(id, toSelected), this);
|
|
parent.addChild(card);
|
|
}
|
|
|
|
private _pillBtn(parent: Node, text: string, w: number, h: number, bg: Color, onClick: () => void): Node {
|
|
const n = new Node(text);
|
|
n.layer = Layers.Enum.UI_2D;
|
|
n.addComponent(UITransform).setContentSize(w, h);
|
|
this._drawRound(n, bg, w, h, 10);
|
|
const lbN = new Node('lb');
|
|
lbN.layer = Layers.Enum.UI_2D;
|
|
lbN.addComponent(UITransform).setContentSize(w - 6, h - 4);
|
|
const lb = lbN.addComponent(Label);
|
|
lb.string = text;
|
|
lb.fontSize = 22;
|
|
lb.isBold = true;
|
|
lb.color = BUFF_C.text;
|
|
lb.horizontalAlign = HorizontalTextAlignment.CENTER;
|
|
lb.verticalAlign = VerticalTextAlignment.CENTER;
|
|
n.addChild(lbN);
|
|
n.addComponent(Button);
|
|
n.on(Button.EventType.CLICK, onClick, this);
|
|
parent.addChild(n);
|
|
return n;
|
|
}
|
|
|
|
private _drawRound(node: Node, fill: Color, w: number, h: number, r: number, stroke?: Color): void {
|
|
const g = node.getComponent(Graphics) || node.addComponent(Graphics);
|
|
g.clear();
|
|
g.fillColor = fill;
|
|
g.roundRect(-w / 2, -h / 2, w, h, r);
|
|
g.fill();
|
|
if (stroke) {
|
|
g.strokeColor = stroke;
|
|
g.lineWidth = stroke === BUFF_C.rowOn ? 3 : 2;
|
|
g.roundRect(-w / 2, -h / 2, w, h, r);
|
|
g.stroke();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 关卡列表点选。
|
|
*/
|
|
class GmLevelPickerPanel implements IGmContentPanel {
|
|
private _root: Node | 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;
|
|
const cur = ProjectGmActions.fixedMapSummary();
|
|
|
|
this._root = new Node('levelPicker');
|
|
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 head = new Node('head');
|
|
head.layer = Layers.Enum.UI_2D;
|
|
head.addComponent(UITransform).setContentSize(W - 16, 72);
|
|
const hl = head.addComponent(Label);
|
|
hl.string = `固定地图 · 当前 ${cur}`;
|
|
hl.fontSize = 26;
|
|
hl.isBold = true;
|
|
hl.color = new Color(255, 220, 70, 255);
|
|
hl.horizontalAlign = HorizontalTextAlignment.CENTER;
|
|
hl.verticalAlign = VerticalTextAlignment.CENTER;
|
|
head.setPosition(0, H / 2 - 40);
|
|
this._root.addChild(head);
|
|
|
|
const footH = 72;
|
|
const { host, content } = makeVScroll(this._root, W - 16, H - 120 - footH);
|
|
const lay = content.getComponent(Layout);
|
|
if (lay) lay.spacingY = 8;
|
|
host.setPosition(0, footH / 2 - 20);
|
|
|
|
makeLabelBtn(content, '0 关闭固定(随机)', W - 40, 52, () => {
|
|
ProjectGmActions.setFixedMap(0);
|
|
ctx.refreshList();
|
|
ctx.goBack();
|
|
});
|
|
|
|
const list = gg.data.table.getTableList<ITableLevel>(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}`, W - 40, 52, () => {
|
|
ProjectGmActions.setFixedMap(id);
|
|
ctx.refreshList();
|
|
ctx.goBack();
|
|
});
|
|
}
|
|
content.getComponent(Layout)?.updateLayout();
|
|
makeLabelBtn(this._root, '返回', 160, 48, () => ctx.goBack()).setPosition(0, -H / 2 + footH / 2);
|
|
}
|
|
|
|
close(): void {
|
|
if (this._root?.isValid) this._root.destroy();
|
|
this._root = null;
|
|
}
|
|
}
|
|
|
|
const RANK_C = {
|
|
text: new Color(255, 255, 255, 255),
|
|
dim: new Color(170, 185, 210, 255),
|
|
accent: new Color(255, 220, 70, 255),
|
|
card: new Color(28, 36, 56, 255),
|
|
row: new Color(22, 30, 48, 255),
|
|
rowLine: new Color(90, 120, 170, 140),
|
|
selBg: new Color(48, 42, 18, 255),
|
|
selLine: new Color(255, 220, 70, 255),
|
|
btn: new Color(45, 105, 190, 255),
|
|
btnHot: new Color(18, 92, 78, 255),
|
|
starOn: new Color(255, 200, 60, 255),
|
|
starOff: new Color(80, 88, 110, 255),
|
|
};
|
|
|
|
/**
|
|
* 段位点选 + 星级步进。
|
|
*/
|
|
class GmRankPickerPanel implements IGmContentPanel {
|
|
private _root: Node | null = null;
|
|
private _rankId = 1;
|
|
private _star = 1;
|
|
private _rankName = '';
|
|
private _lbHead: Label | null = null;
|
|
private _lbStar: Label | null = null;
|
|
private _listW = 640;
|
|
private _rows: { [id: number]: Node } = {};
|
|
|
|
open(parent: Node, ctx: IGmPanelContext): void {
|
|
this.close();
|
|
this._rankId = RankDataManager.getPlayerRankId() || 1;
|
|
this._star = RankDataManager.getIntraTierStars() || 1;
|
|
|
|
const pUt = parent.getComponent(UITransform);
|
|
const W = pUt?.width || 660;
|
|
const H = pUt?.height || 900;
|
|
this._listW = W - 16;
|
|
|
|
this._root = new Node('rankPicker');
|
|
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 headH = 100;
|
|
const footH = 120;
|
|
const head = new Node('head');
|
|
head.layer = Layers.Enum.UI_2D;
|
|
head.addComponent(UITransform).setContentSize(this._listW, headH);
|
|
this._drawRound(head, RANK_C.card, this._listW, headH, 14, RANK_C.selLine);
|
|
head.setPosition(0, H / 2 - headH / 2 - 4);
|
|
this._root.addChild(head);
|
|
|
|
this._lbHead = this._makeLb(head, '', this._listW - 24, 36, 26, RANK_C.accent);
|
|
this._lbHead.node.setPosition(0, 22);
|
|
this._lbStar = this._makeLb(head, '', this._listW - 24, 32, 28, RANK_C.starOn);
|
|
this._lbStar.node.setPosition(0, -22);
|
|
|
|
const scrollH = H - headH - footH - 16;
|
|
const { host, content } = makeVScroll(this._root, this._listW, scrollH);
|
|
const lay = content.getComponent(Layout);
|
|
if (lay) {
|
|
lay.spacingY = 8;
|
|
lay.paddingBottom = 12;
|
|
}
|
|
host.setPosition(0, (footH - headH) / 2);
|
|
|
|
const list = gg.data.table.getTableList<ITableRank>(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 ?? '';
|
|
if (id === this._rankId) this._rankName = name;
|
|
const card = this._makeRankRow(id, name);
|
|
content.addChild(card);
|
|
this._rows[id] = card;
|
|
}
|
|
content.getComponent(Layout)?.updateLayout();
|
|
|
|
const foot = new Node('foot');
|
|
foot.layer = Layers.Enum.UI_2D;
|
|
foot.addComponent(UITransform).setContentSize(this._listW, footH - 12);
|
|
this._drawRound(foot, RANK_C.card, this._listW, footH - 12, 14, RANK_C.rowLine);
|
|
foot.setPosition(0, -H / 2 + footH / 2);
|
|
this._root.addChild(foot);
|
|
|
|
this._pill(foot, '星 -1', 110, 48, new Color(70, 78, 96, 255), () => {
|
|
this._star = Math.max(1, this._star - 1);
|
|
this._paint();
|
|
}).setPosition(-200, 0);
|
|
this._pill(foot, '星 +1', 110, 48, new Color(70, 78, 96, 255), () => {
|
|
this._star = Math.min(5, this._star + 1);
|
|
this._paint();
|
|
}).setPosition(-70, 0);
|
|
this._pill(foot, '应用', 140, 52, RANK_C.btnHot, () => {
|
|
ProjectGmActions.setRankAndStar(this._rankId, this._star);
|
|
ctx.host.toast(`已设段位 ${this._rankId} 星 ${this._star}`);
|
|
ctx.refreshList();
|
|
if (!ctx.embedded) ctx.goBack();
|
|
}).setPosition(120, 0);
|
|
|
|
this._paint();
|
|
}
|
|
|
|
close(): void {
|
|
if (this._root?.isValid) this._root.destroy();
|
|
this._root = null;
|
|
this._lbHead = null;
|
|
this._lbStar = null;
|
|
this._rows = {};
|
|
}
|
|
|
|
private _makeRankRow(id: number, name: string): Node {
|
|
const h = 64;
|
|
const w = this._listW;
|
|
const card = new Node(`rank${id}`);
|
|
card.layer = Layers.Enum.UI_2D;
|
|
card.addComponent(UITransform).setContentSize(w, h);
|
|
this._drawRound(card, RANK_C.row, w, h, 12, RANK_C.rowLine);
|
|
|
|
const idLb = this._makeLb(card, `${id}`, 56, 36, 24, RANK_C.accent);
|
|
idLb.node.setPosition(-w / 2 + 40, 0);
|
|
const nameLb = this._makeLb(card, name, w - 120, 36, 24, RANK_C.text);
|
|
nameLb.horizontalAlign = HorizontalTextAlignment.LEFT;
|
|
nameLb.node.setPosition(20, 0);
|
|
|
|
card.addComponent(Button);
|
|
card.on(Button.EventType.CLICK, () => {
|
|
this._rankId = id;
|
|
this._rankName = name;
|
|
this._paint();
|
|
}, this);
|
|
return card;
|
|
}
|
|
|
|
private _paint(): void {
|
|
const ids = Object.keys(this._rows);
|
|
for (let i = 0; i < ids.length; i++) {
|
|
const id = Number(ids[i]);
|
|
const row = this._rows[id];
|
|
if (!row?.isValid) continue;
|
|
const on = id === this._rankId;
|
|
this._drawRound(row, on ? RANK_C.selBg : RANK_C.row, this._listW, 64, 12, on ? RANK_C.selLine : RANK_C.rowLine);
|
|
}
|
|
if (this._lbHead) {
|
|
this._lbHead.string = `当前选中 · ${this._rankId} ${this._rankName || ''}`.trim();
|
|
}
|
|
if (this._lbStar) {
|
|
let stars = '';
|
|
for (let i = 1; i <= 5; i++) stars += i <= this._star ? '★' : '☆';
|
|
this._lbStar.string = `${stars} 星级 ${this._star}`;
|
|
}
|
|
}
|
|
|
|
private _makeLb(parent: Node, text: string, w: number, h: number, size: number, color: Color): Label {
|
|
const n = new Node('lb');
|
|
n.layer = Layers.Enum.UI_2D;
|
|
n.addComponent(UITransform).setContentSize(w, h);
|
|
const lb = n.addComponent(Label);
|
|
lb.string = text;
|
|
lb.fontSize = size;
|
|
lb.isBold = true;
|
|
lb.color = color;
|
|
lb.horizontalAlign = HorizontalTextAlignment.CENTER;
|
|
lb.verticalAlign = VerticalTextAlignment.CENTER;
|
|
lb.overflow = Overflow.SHRINK;
|
|
parent.addChild(n);
|
|
return lb;
|
|
}
|
|
|
|
private _pill(parent: Node, text: string, w: number, h: number, bg: Color, onClick: () => void): Node {
|
|
const n = new Node(text);
|
|
n.layer = Layers.Enum.UI_2D;
|
|
n.addComponent(UITransform).setContentSize(w, h);
|
|
this._drawRound(n, bg, w, h, 12);
|
|
const lbN = new Node('lb');
|
|
lbN.layer = Layers.Enum.UI_2D;
|
|
lbN.addComponent(UITransform).setContentSize(w - 6, h - 4);
|
|
const lb = lbN.addComponent(Label);
|
|
lb.string = text;
|
|
lb.fontSize = 22;
|
|
lb.isBold = true;
|
|
lb.color = RANK_C.text;
|
|
lb.horizontalAlign = HorizontalTextAlignment.CENTER;
|
|
lb.verticalAlign = VerticalTextAlignment.CENTER;
|
|
n.addChild(lbN);
|
|
n.addComponent(Button);
|
|
n.on(Button.EventType.CLICK, onClick, this);
|
|
parent.addChild(n);
|
|
return n;
|
|
}
|
|
|
|
private _drawRound(node: Node, fill: Color, w: number, h: number, r: number, stroke?: Color): void {
|
|
const g = node.getComponent(Graphics) || node.addComponent(Graphics);
|
|
g.clear();
|
|
g.fillColor = fill;
|
|
g.roundRect(-w / 2, -h / 2, w, h, r);
|
|
g.fill();
|
|
if (stroke) {
|
|
g.strokeColor = stroke;
|
|
g.lineWidth = stroke === RANK_C.selLine ? 3 : 2;
|
|
g.roundRect(-w / 2, -h / 2, w, h, r);
|
|
g.stroke();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 注册本项目内容面板。
|
|
*/
|
|
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());
|
|
}
|
|
}
|
|
|