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.
224 lines
8.0 KiB
224 lines
8.0 KiB
import type { EntityId, World } from 'db://assets/mx/module/ecs';
|
|
import { GridOccupancyComp, TagComp, UnitMetaComp } from 'db://assets/mx/module/battle/ecs/Components';
|
|
import { isElementGlyphType } from 'db://assets/mx/module/merge';
|
|
import { TableNames } from '../../game/ConfigTableData';
|
|
import { PEACH_TREE_WEAPON2_ID } from '../bag/BagPrepConfig';
|
|
/** 塔 unitDefId 前缀 */
|
|
const TOWER_DEF_PREFIX = 'tower_w1_';
|
|
/** 塔 Hero 标签前缀 */
|
|
const WEAPON1_TAG_PREFIX = 'weapon1_';
|
|
|
|
/**
|
|
* 英雄对应 Text.id(五行:type+level;蟠桃:102)。
|
|
* @param hero Hero 行
|
|
*/
|
|
export function heroCharacterIds(hero: ITableHero): readonly number[] {
|
|
const ht = Math.floor(Number(hero.herotype));
|
|
if (ht >= 1 && ht <= 5) {
|
|
const lv = Math.floor(Number(hero.level));
|
|
const texts = gg.data.table.getTableList<ITableText>(TableNames.Text) ?? [];
|
|
for (let i = 0; i < texts.length; i++) {
|
|
const row = texts[i];
|
|
if (Math.floor(Number(row.type)) !== ht) continue;
|
|
if (Math.floor(Number(row.level)) !== lv) continue;
|
|
const id = Math.floor(Number(row.id));
|
|
if (id > 0) return [id];
|
|
}
|
|
console.log('[BattleHeroTable] 五行 Text 缺失', hero.id, ht, lv);
|
|
return [];
|
|
}
|
|
if (Math.floor(Number(hero.id)) === PEACH_TREE_WEAPON2_ID) {
|
|
return [PEACH_TREE_WEAPON2_ID];
|
|
}
|
|
return [];
|
|
}
|
|
|
|
/** 指定合成等级对应的普攻技能 id */
|
|
export function heroNormalSkillId(hero: ITableHero, composeLevel: number): number {
|
|
return _skillIdAtLevel(hero.atk_id, composeLevel);
|
|
}
|
|
|
|
/** 英雄主动技能 id(Hero.act_skill 已删,恒无) */
|
|
export function heroUltSkillId(_hero: ITableHero | null | undefined, _composeLevel = 1): number {
|
|
return 0;
|
|
}
|
|
|
|
/** 是否基础单字英雄 */
|
|
export function isBasicHero(hero: ITableHero): boolean {
|
|
return Number(hero.herotype) === 1;
|
|
}
|
|
|
|
/**
|
|
* 五行字落下即可攻击来敌(对应 Hero 有普攻)。
|
|
* @param textId Text.id
|
|
*/
|
|
export function isAttackGlyph(textId: number): boolean {
|
|
const id = Number(textId);
|
|
if (!(id > 0)) return false;
|
|
const text = gg.data.table.getTableData<ITableText>(TableNames.Text, id);
|
|
if (!text) return false;
|
|
const type = Math.floor(Number(text.type));
|
|
if (!isElementGlyphType(type)) return false;
|
|
const level = Math.max(1, Math.floor(Number(text.level) || 1));
|
|
const heroes = gg.data.table.getTableList<ITableHero>(TableNames.Hero) ?? [];
|
|
for (let i = 0; i < heroes.length; i++) {
|
|
const hero = heroes[i];
|
|
if (Math.floor(Number(hero.herotype)) !== type) continue;
|
|
if (Math.floor(Number(hero.level)) !== level) continue;
|
|
return heroNormalSkillId(hero, 1) > 0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** 是否五行数字英雄(herotype 1–5) */
|
|
export function isElementHero(hero: ITableHero | null | undefined): boolean {
|
|
if (!hero) return false;
|
|
const t = Math.floor(Number(hero.herotype));
|
|
return t >= 1 && t <= 5;
|
|
}
|
|
|
|
/**
|
|
* Hero.id 是否为五行战斗单位(有普攻)。
|
|
* @param heroId Hero.id
|
|
*/
|
|
export function isElementCombatHeroId(heroId: number): boolean {
|
|
const id = Math.floor(Number(heroId) || 0);
|
|
if (!(id > 0)) return false;
|
|
const hero = gg.data.table.getTableData<ITableHero>(TableNames.Hero, id);
|
|
if (!isElementHero(hero)) return false;
|
|
return heroNormalSkillId(hero!, 1) > 0;
|
|
}
|
|
|
|
/**
|
|
* 从塔实体解析 Hero.id(unitDefId / weapon1_ 标签)。
|
|
* @param world 战斗世界
|
|
* @param entityId 实体
|
|
*/
|
|
export function resolveTowerHeroId(world: World, entityId: EntityId): number {
|
|
const defId = world.getComponent(entityId, UnitMetaComp)?.unitDefId ?? '';
|
|
if (defId.startsWith(TOWER_DEF_PREFIX)) {
|
|
const id = Number(defId.slice(TOWER_DEF_PREFIX.length));
|
|
if (id > 0) return id;
|
|
}
|
|
const tags = world.getComponent(entityId, TagComp)?.tags;
|
|
if (tags) {
|
|
for (let i = 0; i < tags.length; i++) {
|
|
const t = tags[i];
|
|
if (!t.startsWith(WEAPON1_TAG_PREFIX)) continue;
|
|
const id = Number(t.slice(WEAPON1_TAG_PREFIX.length));
|
|
if (id > 0) return id;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* 从塔实体解析 Hero 行。
|
|
* @param world 战斗世界
|
|
* @param entityId 实体
|
|
*/
|
|
function resolveTowerHero(world: World, entityId: EntityId): ITableHero | null {
|
|
const heroId = resolveTowerHeroId(world, entityId);
|
|
if (!(heroId > 0)) return null;
|
|
const hero = gg.data.table.getTableData<ITableHero>(TableNames.Hero, heroId);
|
|
if (!hero) {
|
|
console.log('[BattleHeroTable] Hero 缺失', heroId);
|
|
return null;
|
|
}
|
|
return hero;
|
|
}
|
|
|
|
/**
|
|
* 怪物技能 target_stage=1 的默认敌人:敌方阵营、非组合武将(herotype≠2)、非多格组合字。
|
|
* @param world 战斗世界
|
|
* @param entityId 候选目标
|
|
*/
|
|
export function isMonsterDefaultEnemyTarget(world: World, entityId: EntityId): boolean {
|
|
const hero = resolveTowerHero(world, entityId);
|
|
if (hero == null || Number(hero.herotype) === 2) return false;
|
|
const cells = world.getComponent(entityId, GridOccupancyComp)?.cells;
|
|
return !(cells && cells.length > 1);
|
|
}
|
|
|
|
/**
|
|
* 是否蟠桃树塔(Hero/Text id=102,herotype=3)。
|
|
* @param world 战斗世界
|
|
* @param entityId 候选目标
|
|
*/
|
|
export function isPeachTreeTower(world: World, entityId: EntityId): boolean {
|
|
return resolveTowerHeroId(world, entityId) === PEACH_TREE_WEAPON2_ID;
|
|
}
|
|
|
|
/**
|
|
* atk_buff1 类型 12:只锁基础字(herotype=1),不含组合字。
|
|
* 损毁态由调用方用 TowerRuinHost 判定,本表不反向依赖战斗宿主。
|
|
* @param world 战斗世界
|
|
* @param entityId 候选目标
|
|
*/
|
|
export function isMonsterBasicHeroTarget(world: World, entityId: EntityId): boolean {
|
|
if (!isMonsterDefaultEnemyTarget(world, entityId)) return false;
|
|
const hero = resolveTowerHero(world, entityId);
|
|
return hero != null && isBasicHero(hero);
|
|
}
|
|
|
|
/**
|
|
* atk_type=9 按将 id 索敌(Hero.weapon 已删,不再匹配)。
|
|
* @param _world 战斗世界
|
|
* @param _entityId 候选目标
|
|
* @param _weaponType 将类型
|
|
*/
|
|
export function isHeroWeaponTarget(_world: World, _entityId: EntityId, _weaponType: number): boolean {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Text 对应 Hero 行(五行 type+level;蟠桃 id=102)。
|
|
* @param text Text 行
|
|
*/
|
|
export function heroForTextRow(text: ITableText | null | undefined): ITableHero | null {
|
|
if (!text) return null;
|
|
const textId = Math.floor(Number(text.id));
|
|
if (textId === PEACH_TREE_WEAPON2_ID) {
|
|
return gg.data.table.getTableData<ITableHero>(TableNames.Hero, PEACH_TREE_WEAPON2_ID);
|
|
}
|
|
const type = Math.floor(Number(text.type));
|
|
if (!isElementGlyphType(type)) return null;
|
|
const level = Math.max(1, Math.floor(Number(text.level) || 1));
|
|
const heroes = gg.data.table.getTableList<ITableHero>(TableNames.Hero) ?? [];
|
|
for (let i = 0; i < heroes.length; i++) {
|
|
const hero = heroes[i];
|
|
if (Math.floor(Number(hero.herotype)) !== type) continue;
|
|
if (Math.floor(Number(hero.level)) !== level) continue;
|
|
return hero;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/** 文字块 Spine 皮肤名(Hero.res) */
|
|
export function textIconName(text: ITableText): string {
|
|
const hero = heroForTextRow(text);
|
|
const res = (hero?.res ?? '').trim();
|
|
if (!res) {
|
|
console.log('[BattleHeroTable] textIconName 无 Hero.res', text?.id);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/** 文字块显示名(Hero.name / number) */
|
|
export function textDisplayName(text: ITableText): string {
|
|
const hero = heroForTextRow(text);
|
|
if (hero) {
|
|
const name = (hero.name ?? '').trim();
|
|
if (name) return name;
|
|
const num = Math.floor(Number(hero.number));
|
|
if (num > 0) return String(num);
|
|
}
|
|
return textIconName(text);
|
|
}
|
|
|
|
/** 数组按下标取技能 id;composeLevel 从 1 起,越界 clamp */
|
|
function _skillIdAtLevel(ids: readonly number[] | null | undefined, composeLevel: number): number {
|
|
if (!ids || ids.length <= 0) return 0;
|
|
const idx = Math.max(0, Math.min(composeLevel - 1, ids.length - 1));
|
|
return ids[idx] ?? 0;
|
|
}
|
|
|