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/battle/config/BattleHeroTable.ts

242 lines
8.7 KiB

3 weeks ago
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';
3 weeks ago
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 行
*/
3 weeks ago
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 [];
3 weeks ago
}
/** 指定合成等级对应的普攻技能 id */
export function heroNormalSkillId(hero: ITableHero, composeLevel: number): number {
return _skillIdAtLevel(hero.atk_id, composeLevel);
}
/**
* 英雄技能 id(`Hero.skill_id`;0=无)。
* 主动/被动由 Skill.type 决定,挂载见 TowerLoadoutService。
* @param hero Hero 行
* @param _composeLevel 保留签名(skill_id 不按等级取下标)
*/
export function heroUltSkillId(hero: ITableHero | null | undefined, _composeLevel = 1): number {
if (!hero) return 0;
return Math.floor(Number(hero.skill_id) || 0);
3 weeks ago
}
/** 是否基础单字英雄 */
export function isBasicHero(hero: ITableHero): boolean {
return Number(hero.herotype) === 1;
}
/**
* 五行字落下即可攻击来敌(对应 Hero 有普攻)。
3 weeks ago
* @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));
3 weeks ago
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;
3 weeks ago
return heroNormalSkillId(hero, 1) > 0;
}
return false;
}
2 weeks ago
/** 是否五行数字英雄(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);
2 weeks ago
if (!isElementHero(hero)) return false;
2 weeks ago
return heroNormalSkillId(hero!, 1) > 0;
}
3 weeks ago
/**
* 从塔实体解析 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;
}
/**
* 塔合成/显示等级(优先 UnitMetaComp.level,否则 Hero.level)。
* @param world 战斗世界
* @param entityId 实体
*/
export function resolveTowerComposeLevel(world: World, entityId: EntityId): number {
const fromMeta = Math.floor(Number(world.getComponent(entityId, UnitMetaComp)?.level) || 0);
if (fromMeta > 0) return fromMeta;
const hero = resolveTowerHero(world, entityId);
return Math.max(0, Math.floor(Number(hero?.level) || 0));
}
/**
* 怪物技能 target_stage=1 的默认敌人:敌方阵营五行单字(herotype 1–5 金木水火土),排除多格组合。
* 新表 herotype 表示五行,不再用旧口径「herotype≠2=非组合神将」。
3 weeks ago
* @param world 战斗世界
* @param entityId 候选目标
*/
export function isMonsterDefaultEnemyTarget(world: World, entityId: EntityId): boolean {
if (isPeachTreeTower(world, entityId)) return false;
3 weeks ago
const hero = resolveTowerHero(world, entityId);
if (!isElementHero(hero)) return false;
3 weeks ago
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:锁五行单字(非多格)。损毁态由调用方判定,本表不依赖 TowerRuinHost。
3 weeks ago
* @param world 战斗世界
* @param entityId 候选目标
*/
export function isMonsterBasicHeroTarget(world: World, entityId: EntityId): boolean {
return isMonsterDefaultEnemyTarget(world, entityId);
3 weeks ago
}
/**
* atk_type=9 按将 id 索敌(Hero.weapon 已删,不再匹配)。
* @param _world 战斗世界
* @param _entityId 候选目标
* @param _weaponType 将类型
3 weeks ago
*/
export function isHeroWeaponTarget(_world: World, _entityId: EntityId, _weaponType: number): boolean {
return false;
3 weeks ago
}
/**
* 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) */
3 weeks ago
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;
3 weeks ago
}
/** 文字块显示名(Hero.name / number) */
3 weeks ago
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);
3 weeks ago
}
/** 数组按下标取技能 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;
}