master
humingxing 15 hours ago
parent a550cdd9e2
commit a62b70ef4c
  1. 71
      assets/script/battle/bag/PlayerBagTextPool.ts
  2. 122
      assets/script/battle/view/AreaUnlockGuidePresenter.ts
  3. 2
      assets/script/sdk/config/DefaultConfig.ts
  4. 1
      bin/tt_slgjm1.0.3.bin

@ -204,7 +204,8 @@ export class PlayerBagTextPool {
} }
/** /**
* 属性卡:同显示等级、不同 Text.type 的字(不扣减字池)。 * 属性卡/随机牌:保留显示等级,随机换成其它五行 Text.id(不扣减字池)。
* 不按字池 Text.level 过滤——高等级由合成抬升,只要求目标属性在该显示等级有 Hero。
* @param currentWeapon2Id 当前字 * @param currentWeapon2Id 当前字
* @param level 显示等级 * @param level 显示等级
*/ */
@ -212,30 +213,19 @@ export class PlayerBagTextPool {
const curId = Math.floor(Number(currentWeapon2Id) || 0); const curId = Math.floor(Number(currentWeapon2Id) || 0);
const lv = Math.floor(Number(level) || 0); const lv = Math.floor(Number(level) || 0);
if (curId <= 0 || lv <= 0) return null; if (curId <= 0 || lv <= 0) return null;
let curType = 0; const curType = this._resolveElementType(curId);
for (let i = 0; i < this._textIds.length; i++) {
if (this._textIds[i] !== curId && this._heroIds[i] !== curId) continue;
curType = this._types[i];
break;
}
if (curType < 1 || curType > 5) {
const list = gg.data.table.getTableList<ITableText>(TableNames.Text) ?? [];
for (let i = 0; i < list.length; i++) {
if (Number(list[i].id) !== curId) continue;
curType = Math.floor(Number(list[i].type) || 0);
break;
}
}
if (curType < 1 || curType > 5) { if (curType < 1 || curType > 5) {
console.log('[PlayerBagTextPool] 属性卡无法解析当前 type', curId); console.log('[PlayerBagTextPool] 属性卡无法解析当前 type', curId);
return null; return null;
} }
const heroes = gg.data.table.getTableList<ITableHero>(TableNames.Hero) ?? [];
const candidates: number[] = []; const candidates: number[] = [];
for (let i = 0; i < this._textIds.length; i++) { for (let type = 1; type <= 5; type++) {
if (this._types[i] === curType) continue; if (type === curType) continue;
if (this._levels[i] !== lv) continue; if (!this._heroAt(heroes, type, lv)) continue;
const id = this._textIds[i]; const textId = this._anyElementTextId(type);
if (id > 0) candidates.push(id); if (!(textId > 0)) continue;
candidates.push(textId);
} }
if (candidates.length <= 0) { if (candidates.length <= 0) {
console.log('[PlayerBagTextPool] 属性卡无同级异属性候选', lv, curType); console.log('[PlayerBagTextPool] 属性卡无同级异属性候选', lv, curType);
@ -245,6 +235,47 @@ export class PlayerBagTextPool {
return { weapon2Id: pick, level: lv }; return { weapon2Id: pick, level: lv };
} }
/**
* 解析五行 Text.type(池内命中或查 Text 表)。
* @param weapon2Id Text.id / 旧池 Hero.id
*/
private _resolveElementType(weapon2Id: number): number {
const id = Math.floor(Number(weapon2Id) || 0);
if (!(id > 0)) return 0;
for (let i = 0; i < this._textIds.length; i++) {
if (this._textIds[i] !== id && this._heroIds[i] !== id) continue;
const t = this._types[i];
if (t >= 1 && t <= 5) return t;
}
const list = gg.data.table.getTableList<ITableText>(TableNames.Text) ?? [];
for (let i = 0; i < list.length; i++) {
if (Number(list[i].id) !== id) continue;
return Math.floor(Number(list[i].type) || 0);
}
return 0;
}
/**
* 取任一该五行的 Text.id(池优先,否则 Text 表)。
* @param type Text.type 1–5
*/
private _anyElementTextId(type: number): number {
const t = Math.floor(Number(type) || 0);
if (t < 1 || t > 5) return 0;
for (let i = 0; i < this._textIds.length; i++) {
if (this._types[i] !== t) continue;
const id = this._textIds[i];
if (id > 0) return id;
}
const list = gg.data.table.getTableList<ITableText>(TableNames.Text) ?? [];
for (let i = 0; i < list.length; i++) {
if (Math.floor(Number(list[i].type) || 0) !== t) continue;
const id = Math.floor(Number(list[i].id) || 0);
if (id > 0) return id;
}
return 0;
}
/** /**
* 按 id 扣减 1 次 remaining(GM/预设刷新等非 rollSlots 路径)。 * 按 id 扣减 1 次 remaining(GM/预设刷新等非 rollSlots 路径)。
* @param weapon2Id Text.id 或池输出的 Hero.id * @param weapon2Id Text.id 或池输出的 Hero.id

@ -1,4 +1,4 @@
import { Color, Graphics, Node, UITransform, Vec3, isValid, sp } from 'cc'; import { _decorator, Color, Component, Graphics, Node, UITransform, Vec3, isValid, sp } from 'cc';
import { isElementGlyphType } from 'db://assets/mx/module/merge'; import { isElementGlyphType } from 'db://assets/mx/module/merge';
import { GBundle } from '../../game/ConfigRes'; import { GBundle } from '../../game/ConfigRes';
import { AI_VIEW_LOCAL_CAMP } from '../ai/runtime/AiCampArea'; import { AI_VIEW_LOCAL_CAMP } from '../ai/runtime/AiCampArea';
@ -8,6 +8,8 @@ import type { MapManager } from '../map/legacy/MapManager';
import { BattleSpinePlayback } from '../presentation/BattleSpinePlayback'; import { BattleSpinePlayback } from '../presentation/BattleSpinePlayback';
import type { TextGrid } from './TextGrid'; import type { TextGrid } from './TextGrid';
const { ccclass } = _decorator;
/** 引导虚线框预制体路径 */ /** 引导虚线框预制体路径 */
const FRAME_PATH = 'prefab/障碍物/'; const FRAME_PATH = 'prefab/障碍物/';
/** 引导虚线框预制体名 */ /** 引导虚线框预制体名 */
@ -22,15 +24,29 @@ const DASH_LEN = 12;
const DASH_HALF_W = 2.5; const DASH_HALF_W = 2.5;
/** 描边相对半宽加宽 */ /** 描边相对半宽加宽 */
const DASH_OUTLINE_EXTRA = 1.5; const DASH_OUTLINE_EXTRA = 1.5;
/** 背包端取字块顶边中点的向上偏移 */ /** 流动速度:像素/秒(背包→地图) */
const BAG_TOP_PAD = 2; const FLOW_SPEED = 48;
/** 气泡端取底边中点的向下偏移 */
const TIP_BOTTOM_PAD = 2;
/** 交替色:黑 */ /** 交替色:黑 */
const DASH_BLACK = new Color(15, 15, 15, 255); const DASH_BLACK = new Color(15, 15, 15, 255);
/** 交替色:白 */ /** 交替色:白 */
const DASH_WHITE = new Color(255, 255, 255, 255); const DASH_WHITE = new Color(255, 255, 255, 255);
/**
* 连线层每帧推进斑马相位。
*/
@ccclass('AreaUnlockGuideFlowTick')
class AreaUnlockGuideFlowTick extends Component {
/** 宿主 */
host: AreaUnlockGuidePresenter | null = null;
/**
* @param dt 帧间隔
*/
update(dt: number): void {
this.host?.onFlowTick(dt);
}
}
/** 世界坐标复用 */ /** 世界坐标复用 */
const _bagWorld = new Vec3(); const _bagWorld = new Vec3();
/** 气泡世界坐标复用 */ /** 气泡世界坐标复用 */
@ -72,6 +88,18 @@ export class AreaUnlockGuidePresenter {
private _frame: Node | null = null; private _frame: Node | null = null;
/** 当前标识的背包槽;null 表示未展示 */ /** 当前标识的背包槽;null 表示未展示 */
private _slotIndex: number | null = null; private _slotIndex: number | null = null;
/** 连线本地起点 x */
private _fromX = 0;
/** 连线本地起点 y */
private _fromY = 0;
/** 连线本地终点 x */
private _toX = 0;
/** 连线本地终点 y */
private _toY = 0;
/** 斑马流动相位(像素) */
private _flowPhase = 0;
/** 是否正在播流动 */
private _flowing = false;
/** /**
* 绑定连线层父节点(须同时覆盖地图与背包,如 BattleGame 根)。 * 绑定连线层父节点(须同时覆盖地图与背包,如 BattleGame 根)。
@ -87,6 +115,8 @@ export class AreaUnlockGuidePresenter {
/** 释放节点与池资源 */ /** 释放节点与池资源 */
unbind(): void { unbind(): void {
this.clear(); this.clear();
const tick = this._lineLayer?.getComponent(AreaUnlockGuideFlowTick);
if (tick) tick.host = null;
if (this._lineLayer?.isValid) { if (this._lineLayer?.isValid) {
this._lineLayer.destroy(); this._lineLayer.destroy();
} }
@ -104,12 +134,30 @@ export class AreaUnlockGuidePresenter {
clear(): void { clear(): void {
const prevHost = this._frame?.parent; const prevHost = this._frame?.parent;
this._slotIndex = null; this._slotIndex = null;
this._stopFlow();
this._releaseFrame(); this._releaseFrame();
if (prevHost?.isValid) this._purgeOrphanFrames(prevHost); if (prevHost?.isValid) this._purgeOrphanFrames(prevHost);
if (this._graphics?.isValid) this._graphics.clear(); if (this._graphics?.isValid) this._graphics.clear();
if (this._lineLayer?.isValid) this._lineLayer.active = false; if (this._lineLayer?.isValid) this._lineLayer.active = false;
} }
/**
* 连线层帧 tick:推进相位并重绘。
* @param dt 帧间隔
*/
onFlowTick(dt: number): void {
if (!this._flowing) return;
if (!this._lineLayer?.isValid || !this._lineLayer.active) return;
if (!this._graphics?.isValid) return;
const step = Math.max(0, Number(dt) || 0) * FLOW_SPEED;
if (step <= 0) return;
// 相位减小:斑马段沿起点(背包)→终点(地图)滑动
this._flowPhase -= step;
const period = DASH_LEN * 2;
if (this._flowPhase < 0) this._flowPhase = (this._flowPhase % period + period) % period;
this._redrawLine();
}
/** /**
* 按背包与当前解锁条件刷新引导。 * 按背包与当前解锁条件刷新引导。
* 原槽仍可解锁时保持不变,避免频繁乱跳。 * 原槽仍可解锁时保持不变,避免频繁乱跳。
@ -295,12 +343,15 @@ export class AreaUnlockGuidePresenter {
} }
this._lineLayer = layer; this._lineLayer = layer;
this._graphics = layer.getComponent(Graphics); this._graphics = layer.getComponent(Graphics);
let tick = layer.getComponent(AreaUnlockGuideFlowTick);
if (!tick) tick = layer.addComponent(AreaUnlockGuideFlowTick);
tick.host = this;
this._disableNodeHit(layer); this._disableNodeHit(layer);
layer.active = false; layer.active = false;
} }
/** /**
* 从背包字顶边中点连到气泡底边中点。 * 背包字中心 → 地图待解锁格中心(气泡已挂在格心)。
* @param bagNode 背包字块 * @param bagNode 背包字块
* @param tip 格子锁气泡 * @param tip 格子锁气泡
*/ */
@ -311,46 +362,73 @@ export class AreaUnlockGuidePresenter {
const layerUt = layer.getComponent(UITransform); const layerUt = layer.getComponent(UITransform);
if (!layerUt) return; if (!layerUt) return;
const bagUt = bagNode.getComponent(UITransform); _bagWorld.set(bagNode.worldPosition.x, bagNode.worldPosition.y, bagNode.worldPosition.z);
const tipUt = tip.getComponent(UITransform); _tipWorld.set(tip.worldPosition.x, tip.worldPosition.y, tip.worldPosition.z);
const bagHalfH = bagUt ? bagUt.contentSize.height * 0.5 * Math.abs(bagNode.worldScale.y) : 30;
const tipHalfH = tipUt ? tipUt.contentSize.height * 0.5 * Math.abs(tip.worldScale.y) : 20;
_bagWorld.set(bagNode.worldPosition.x, bagNode.worldPosition.y + bagHalfH + BAG_TOP_PAD, bagNode.worldPosition.z);
_tipWorld.set(tip.worldPosition.x, tip.worldPosition.y - tipHalfH - TIP_BOTTOM_PAD, tip.worldPosition.z);
layerUt.convertToNodeSpaceAR(_bagWorld, _fromLocal); layerUt.convertToNodeSpaceAR(_bagWorld, _fromLocal);
layerUt.convertToNodeSpaceAR(_tipWorld, _toLocal); layerUt.convertToNodeSpaceAR(_tipWorld, _toLocal);
const nx = _fromLocal.x;
g.clear(); const ny = _fromLocal.y;
this._fillZebraDash(g, _fromLocal.x, _fromLocal.y, _toLocal.x, _toLocal.y); const mx = _toLocal.x;
const my = _toLocal.y;
const moved = Math.abs(nx - this._fromX) > 1
|| Math.abs(ny - this._fromY) > 1
|| Math.abs(mx - this._toX) > 1
|| Math.abs(my - this._toY) > 1;
this._fromX = nx;
this._fromY = ny;
this._toX = mx;
this._toY = my;
if (!this._flowing || moved) this._flowPhase = 0;
this._redrawLine();
layer.active = true; layer.active = true;
layer.setSiblingIndex(layer.parent!.children.length - 1); layer.setSiblingIndex(layer.parent!.children.length - 1);
this._flowing = true;
}
/** 按当前相位重绘斑马线 */
private _redrawLine(): void {
const g = this._graphics;
if (!g?.isValid) return;
g.clear();
this._fillZebraDash(g, this._fromX, this._fromY, this._toX, this._toY, this._flowPhase);
}
/** 停止流动(清相位,保留层节点) */
private _stopFlow(): void {
this._flowing = false;
this._flowPhase = 0;
} }
/** /**
* 黑白段交替实心绳:每段先画反色描边再画本色。 * 黑白段交替实心绳:相位沿背包→地图方向滑动。
* @param g Graphics * @param g Graphics
* @param x1 起点 x * @param x1 起点 x
* @param y1 起点 y * @param y1 起点 y
* @param x2 终点 x * @param x2 终点 x
* @param y2 终点 y * @param y2 终点 y
* @param phase 流动相位(像素)
*/ */
private _fillZebraDash(g: Graphics, x1: number, y1: number, x2: number, y2: number): void { private _fillZebraDash(g: Graphics, x1: number, y1: number, x2: number, y2: number, phase: number): void {
const dx = x2 - x1; const dx = x2 - x1;
const dy = y2 - y1; const dy = y2 - y1;
const len = Math.sqrt(dx * dx + dy * dy); const len = Math.sqrt(dx * dx + dy * dy);
if (len < 1) return; if (len < 1) return;
const ux = dx / len; const ux = dx / len;
const uy = dy / len; const uy = dy / len;
let d = 0; const period = DASH_LEN * 2;
const offset = ((phase % period) + period) % period;
let d = -offset;
let useBlack = true; let useBlack = true;
while (d < len) { while (d < len) {
const d0 = Math.max(d, 0);
const d1 = Math.min(d + DASH_LEN, len); const d1 = Math.min(d + DASH_LEN, len);
if (d1 > d0) {
const fill = useBlack ? DASH_BLACK : DASH_WHITE; const fill = useBlack ? DASH_BLACK : DASH_WHITE;
const outline = useBlack ? DASH_WHITE : DASH_BLACK; const outline = useBlack ? DASH_WHITE : DASH_BLACK;
this._fillDashSeg(g, x1, y1, ux, uy, d, d1, DASH_HALF_W + DASH_OUTLINE_EXTRA, outline); this._fillDashSeg(g, x1, y1, ux, uy, d0, d1, DASH_HALF_W + DASH_OUTLINE_EXTRA, outline);
this._fillDashSeg(g, x1, y1, ux, uy, d, d1, DASH_HALF_W, fill); this._fillDashSeg(g, x1, y1, ux, uy, d0, d1, DASH_HALF_W, fill);
d = d1; }
d += DASH_LEN;
useBlack = !useBlack; useBlack = !useBlack;
} }
} }

@ -7,7 +7,7 @@ import { IConfigData } from "./IConfigData";
export const DefaultConfig: IConfigData = { export const DefaultConfig: IConfigData = {
id: "", id: "",
name: "tt_slgjm", name: "tt_slgjm",
vsion: "1.0.2", vsion: "1.0.3",
chanId: "tt", chanId: "tt",
themeId: "1", themeId: "1",
useDoc: true, useDoc: true,

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save