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/combat/ExecuteRules.ts

156 lines
5.6 KiB

import type { BattleSession } from 'db://assets/mx/module/battle';
3 weeks ago
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);
3 weeks ago
}
/** 聚合已选词条 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;
}
3 weeks ago
/** 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);
}