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.
161 lines
6.0 KiB
161 lines
6.0 KiB
|
1 week ago
|
import type { BattleSession } from 'db://assets/mx/module/battle';
|
||
|
|
import type { EntityId } from 'db://assets/mx/module/ecs';
|
||
|
|
import { AttrId, AttributeComp } from 'db://assets/mx/module/battle/ecs/Components';
|
||
|
|
import type { Player } from '../../game/Player';
|
||
|
|
import { TableNames } from '../../game/ConfigTableData';
|
||
|
|
import { parseEntryBaseAttr } from '../skill/SkillEntryParse';
|
||
|
|
|
||
|
|
const LOG = '[GemBattle]';
|
||
|
|
/** modifier.source 前缀,刷新时整批清除 */
|
||
|
|
const SOURCE_PREFIX = 'gem_';
|
||
|
|
|
||
|
|
/** 五行镶嵌解析结果(局内用) */
|
||
|
|
export interface IGemBattleResolve {
|
||
|
|
/** 提供技能的宝石 id(0=无) */
|
||
|
|
skillGemId: number;
|
||
|
|
/** Skill.id(0=无) */
|
||
|
|
skillId: number;
|
||
|
|
/** 各槽属性条目(含来源槽,便于 source 去重) */
|
||
|
|
attrs: IGemBattleAttr[];
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 单条宝石属性 */
|
||
|
|
export interface IGemBattleAttr {
|
||
|
|
/** Weapon.attritubes 类型 1~5 */
|
||
|
|
type: number;
|
||
|
|
/** 万分比值 */
|
||
|
|
value: number;
|
||
|
|
/** 宝石 id */
|
||
|
|
gemId: number;
|
||
|
|
/** 槽位 0~3 */
|
||
|
|
slot: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 局内宝石生效:读镶嵌 → 属性 modifier → 技能宝石 id。
|
||
|
|
* 仅本机玩家;AI 返回空。
|
||
|
|
*/
|
||
|
|
export class GemBattleEffect {
|
||
|
|
/**
|
||
|
|
* 解析某五行已镶嵌宝石。
|
||
|
|
* @param element Hero.herotype / 镶嵌 element(1~5)
|
||
|
|
* @param player 部署所属玩家
|
||
|
|
*/
|
||
|
|
static resolve(element: number, player: Player | null | undefined): IGemBattleResolve {
|
||
|
|
const empty: IGemBattleResolve = { skillGemId: 0, skillId: 0, attrs: [] };
|
||
|
|
if (!player || !player.isLocal) return empty;
|
||
|
|
const e = Math.floor(Number(element) || 0);
|
||
|
|
if (e < 1 || e > 5) return empty;
|
||
|
|
|
||
|
|
let slots: number[];
|
||
|
|
try {
|
||
|
|
slots = gg.gem.getElementSlots(e);
|
||
|
|
} catch (err) {
|
||
|
|
console.warn(LOG, 'getElementSlots 失败', e, err);
|
||
|
|
return empty;
|
||
|
|
}
|
||
|
|
|
||
|
|
const attrs: IGemBattleAttr[] = [];
|
||
|
|
let skillGemId = 0;
|
||
|
|
let skillId = 0;
|
||
|
|
let skillQuality = -1;
|
||
|
|
|
||
|
|
for (let slot = 0; slot < slots.length; slot++) {
|
||
|
|
const gemId = Math.floor(Number(slots[slot]) || 0);
|
||
|
|
if (gemId <= 0) continue;
|
||
|
|
const row = gg.data.table.getTableData<ITableWeapon>(TableNames.Weapon, gemId);
|
||
|
|
if (!row) {
|
||
|
|
console.warn(LOG, '宝石配表缺失', gemId, 'element', e, 'slot', slot);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
const parsed = parseEntryBaseAttr(String(row.attritubes ?? ''));
|
||
|
|
for (let i = 0; i < parsed.length; i++) {
|
||
|
|
const p = parsed[i];
|
||
|
|
if (p.type < 1 || p.type > 5) {
|
||
|
|
console.warn(LOG, 'attritubes 类型非法', gemId, p.type, row.attritubes);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
if (!(p.value > 0)) {
|
||
|
|
console.warn(LOG, 'attritubes 参数非法', gemId, row.attritubes);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
attrs.push({ type: p.type, value: p.value, gemId, slot });
|
||
|
|
}
|
||
|
|
const sid = Math.floor(Number(row.act_skill) || 0);
|
||
|
|
if (sid <= 0) continue;
|
||
|
|
const q = Math.floor(Number(row.quality) || 0);
|
||
|
|
if (skillId > 0) {
|
||
|
|
console.warn(LOG, '同五行多颗技能宝石,取品质更高者', e, 'prev', skillGemId, 'next', gemId);
|
||
|
|
}
|
||
|
|
if (skillId <= 0 || q > skillQuality || (q === skillQuality && gemId > skillGemId)) {
|
||
|
|
skillGemId = gemId;
|
||
|
|
skillId = sid;
|
||
|
|
skillQuality = q;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return { skillGemId, skillId, attrs };
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 清除实体上全部宝石属性 modifier。
|
||
|
|
* @param session 战斗会话
|
||
|
|
* @param entityId 塔实体
|
||
|
|
*/
|
||
|
|
static clear(session: BattleSession, entityId: EntityId): void {
|
||
|
|
const attr = session.world.getComponent(entityId, AttributeComp);
|
||
|
|
if (!attr) return;
|
||
|
|
attr.modifiers = attr.modifiers.filter(m => !String(m.source ?? '').startsWith(SOURCE_PREFIX));
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 将解析结果写入属性(先 clear 再挂)。
|
||
|
|
* @param session 战斗会话
|
||
|
|
* @param entityId 塔实体
|
||
|
|
* @param resolved 镶嵌解析
|
||
|
|
*/
|
||
|
|
static apply(session: BattleSession, entityId: EntityId, resolved: IGemBattleResolve): void {
|
||
|
|
this.clear(session, entityId);
|
||
|
|
if (!resolved.attrs.length) return;
|
||
|
|
if (!session.world.isAlive(entityId)) return;
|
||
|
|
if (!session.world.getComponent(entityId, AttributeComp)) {
|
||
|
|
session.world.addComponent(entityId, new AttributeComp());
|
||
|
|
}
|
||
|
|
const attrSvc = session.attributes;
|
||
|
|
for (let i = 0; i < resolved.attrs.length; i++) {
|
||
|
|
const a = resolved.attrs[i];
|
||
|
|
const source = `${SOURCE_PREFIX}${a.gemId}_s${a.slot}_${i}`;
|
||
|
|
const wan = a.value;
|
||
|
|
switch (a.type) {
|
||
|
|
case 1:
|
||
|
|
attrSvc.addModifier(entityId, {
|
||
|
|
attr: AttrId.Attack, op: 'Mul', value: 1 + wan / 10000, source,
|
||
|
|
});
|
||
|
|
break;
|
||
|
|
case 2:
|
||
|
|
attrSvc.addModifier(entityId, {
|
||
|
|
attr: AttrId.CritRate, op: 'Add', value: wan / 10000, source,
|
||
|
|
});
|
||
|
|
break;
|
||
|
|
case 3:
|
||
|
|
attrSvc.addModifier(entityId, {
|
||
|
|
attr: AttrId.CritDamage, op: 'Add', value: wan / 10000, source,
|
||
|
|
});
|
||
|
|
break;
|
||
|
|
case 4:
|
||
|
|
attrSvc.addModifier(entityId, {
|
||
|
|
attr: 'aspd_add', op: 'Add', value: wan, source,
|
||
|
|
});
|
||
|
|
break;
|
||
|
|
case 5:
|
||
|
|
attrSvc.addModifier(entityId, {
|
||
|
|
attr: 'dmg_mul', op: 'Add', value: wan / 10000, source,
|
||
|
|
});
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
console.warn(LOG, '未处理属性类型', a.type, a.gemId);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|