import type { BattleSession } from 'db://assets/mx/module/battle'; import type { EntityId } from 'db://assets/mx/module/ecs'; import { createAbilityContext } from 'db://assets/mx/module/battle/combat/AbilityContext'; import { EntityTag, HealthComp, TagComp } from 'db://assets/mx/module/battle'; import type { SkillPresentationContext } from '../skill/SkillPresentationTypes'; import { SkillPresentationEnv } from '../skill/SkillPresentationEnv'; /** Boss 不可被秒杀/斩杀 */ export function isBossEntity(entityId: EntityId, session?: BattleSession | null): boolean { const s = session ?? SkillPresentationEnv.getBattleSession(); if (!s?.world.isAlive(entityId)) return false; return !!s.world.getComponent(entityId, TagComp)?.tags.includes(EntityTag.Boss); } /** 聚合已选词条 skill_special(含普攻 up_type=1) */ export function readSkillSpecialWan(caster: EntityId, specialType: number): number { let best = 0; for (const raw of SkillPresentationEnv.getSkillSpecials(caster)) { for (const seg of String(raw).split(';')) { const part = seg.trim(); if (!part) continue; const nums = part.split(',').map(Number); if ((nums[0] ?? 0) !== specialType || nums.length < 2) continue; best = Math.max(best, Math.max(0, Number(nums[1]) || 0)); } } return best; } function dealExecuteDamage( ctx: SkillPresentationContext, targetId: EntityId, damageType: string, ): boolean { const session = SkillPresentationEnv.getBattleSession(); if (!session?.world.isAlive(targetId)) return false; const floatPos = SkillPresentationEnv.resolveKillFloaterWorldPos(targetId); const abilityCtx = createAbilityContext(session, ctx.caster, ctx.abilityId, [targetId]); abilityCtx.level = ctx.level; const amount = session.damage.dealToTarget(ctx.caster, targetId, { percentMaxHpWan: 10000, scale: 1, damageType, skillAtkBuff1: '', skillAtkBuff2: '', }, abilityCtx); if (amount > 0 && floatPos) { SkillPresentationEnv.playKillFloater(floatPos, damageType); } return amount > 0; } /** * 会话侧斩杀/秒杀扣血。 * @param session 战斗会话 * @param caster 来源 * @param target 目标 * @param damageType 伤害类型 * @param fromPassiveResult 是否被动 result */ function dealExecuteDamageSession( session: BattleSession, caster: EntityId, target: EntityId, damageType: string, fromPassiveResult: boolean, ): boolean { if (!session.world.isAlive(target)) return false; const floatPos = SkillPresentationEnv.resolveKillFloaterWorldPos(target); const abilityCtx = createAbilityContext(session, caster, `passive_execute_${damageType}`, [target]); const amount = session.damage.dealToTarget(caster, target, { percentMaxHpWan: 10000, scale: 1, damageType, fromPassiveResult, skillAtkBuff1: '', skillAtkBuff2: '', }, abilityCtx); if (amount > 0 && floatPos) { SkillPresentationEnv.playKillFloater(floatPos, damageType); } return amount > 0; } /** skill_special 类型 10:概率秒杀(Boss 无效) */ export function tryInstantKill( ctx: SkillPresentationContext, targetId: EntityId, killWan: number, damageType = 'instant_kill', ): boolean { if (killWan <= 0 || isBossEntity(targetId)) return false; const rng = SkillPresentationEnv.getBattleRng(); const roll = (rng?.next() ?? Math.random()) * 10000; if (roll >= killWan) return false; return dealExecuteDamage(ctx, targetId, damageType); } /** skill_special 类型 11:HP 比例斩杀(Boss 无效) */ export function tryExecuteByThreshold( ctx: SkillPresentationContext, targetId: EntityId, thresholdWan: number, damageType = 'execute', ): boolean { if (thresholdWan <= 0 || isBossEntity(targetId)) return false; const session = SkillPresentationEnv.getBattleSession(); if (!session?.world.isAlive(targetId)) return false; const hp = session.world.getComponent(targetId, HealthComp); if (!hp || hp.maxHp <= 0) return false; if (hp.hp / hp.maxHp > thresholdWan / 10000) return false; return dealExecuteDamage(ctx, targetId, damageType); } /** * 被动 result 16:概率秒杀。 * @param session 会话 * @param caster 来源 * @param target 目标 * @param killWan 概率万分比 */ export function tryInstantKillSession( session: BattleSession, caster: EntityId, target: EntityId, killWan: number, ): boolean { if (killWan <= 0 || isBossEntity(target, session)) return false; if (session.rng.next() * 10000 >= killWan) return false; return dealExecuteDamageSession(session, caster, target, 'instant_kill', true); } /** * 被动 result 15:低血量斩杀(可再掷概率)。 * @param session 会话 * @param caster 来源 * @param target 目标 * @param thresholdWan 血量比例万分比 * @param chanceWan 斩杀概率万分比 */ export function tryExecuteByThresholdSession( session: BattleSession, caster: EntityId, target: EntityId, thresholdWan: number, chanceWan = 10000, ): boolean { if (thresholdWan <= 0 || isBossEntity(target, session)) return false; if (!session.world.isAlive(target)) return false; const hp = session.world.getComponent(target, HealthComp); if (!hp || hp.maxHp <= 0) return false; if (hp.hp / hp.maxHp > thresholdWan / 10000) return false; if (chanceWan < 10000 && session.rng.next() * 10000 >= chanceWan) return false; return dealExecuteDamageSession(session, caster, target, 'execute', true); }