优化:天衍

This commit is contained in:
taojiacheng committed 2026-07-22 14:51:49 +08:00
1 parent b49be5130c
commit db09bf945d
6 files changed
+136 -38

No files matched your search

@@ -42,6 +42,7 @@ import {
TIANYAN_SECRET_ART,
TIANYAN_SECT_ID,
TIANYAN_SHATTER_COOLDOWN,
TIANYAN_STRATEGY_ELEMENT_HISTORY,
TIANYAN_TECHNIQUE,
type TianyanLandingAbilityId,
} from '../ids';
@@ -52,6 +53,7 @@ import {
import {
TIANYAN_ELEMENTS,
TIANYAN_ELEMENT_ABILITY_TAGS,
TIANYAN_LANDING_BASE_DAMAGE,
TIANYAN_ELEMENT_NAMES,
getTianyanReaction,
nextGeneratingElement,
@@ -884,7 +886,7 @@ function commonReactionPrelude(
effects.push({
type: 'element_history',
params: {
key: 'sect.tianyan.strategy.recent-elements',
key: TIANYAN_STRATEGY_ELEMENT_HISTORY,
threshold: 3,
effects: [],
},
@@ -1232,7 +1234,7 @@ function compileLandingAbility(
mismatchImmunityTag,
],
selectionProfile: { intents: ['damage'] },
detailRows: [
notes: [
`落印术·${TIANYAN_ELEMENT_NAMES[spec.element]}`,
`法印持续:${settings.sealDuration}回合`,
],
@@ -1331,6 +1333,7 @@ function compileUtilityAbilities(
],
extraTags: [innerArtTag, TIANYAN_ELEMENT_ABILITY_TAGS.wood],
selectionProfile: { intents: ['heal_hp'] },
notes: ['内景法·木'],
}),
);
@@ -1367,6 +1370,7 @@ function compileUtilityAbilities(
],
extraTags: [innerArtTag, TIANYAN_ELEMENT_ABILITY_TAGS.fire],
selectionProfile: { intents: ['buff'] },
notes: ['内景法·火'],
}),
);
@@ -1395,6 +1399,7 @@ function compileUtilityAbilities(
],
extraTags: [innerArtTag, TIANYAN_ELEMENT_ABILITY_TAGS.earth],
selectionProfile: { intents: ['defensive'] },
notes: ['内景法·土'],
}),
);
@@ -1429,6 +1434,7 @@ function compileUtilityAbilities(
],
extraTags: [innerArtTag, TIANYAN_ELEMENT_ABILITY_TAGS.water],
selectionProfile: { intents: ['restore_mp', 'buff'] },
notes: ['内景法·水'],
}),
);
}
@@ -1487,6 +1493,7 @@ function compileSecrets(
})),
extraTags: [secretArtTag],
selectionProfile: { intents: ['buff'] },
notes: ['天衍秘法'],
}),
);
@@ -1546,6 +1553,7 @@ function compileSecrets(
})),
extraTags: [secretArtTag],
selectionProfile: { intents: ['buff', 'heal_hp', 'restore_mp'] },
notes: ['天衍秘法'],
}),
);
}
@@ -1777,7 +1785,7 @@ const LANDING_SPECS: LandingSpec[] = [
{
id: 'verdant-pulse',
element: 'wood',
coefficient: 0.68,
coefficient: TIANYAN_LANDING_BASE_DAMAGE['verdant-pulse'],
baseEffects: (_reaction, settings) => [
healHp(
0.03 * settings.woodHealingMultiplier,
@@ -1789,7 +1797,7 @@ const LANDING_SPECS: LandingSpec[] = [
{
id: 'flowing-flame',
element: 'fire',
coefficient: 0.82,
coefficient: TIANYAN_LANDING_BASE_DAMAGE['flowing-flame'],
baseEffects: () => [
targetBuff(periodicDamageBuff(TIANYAN_BURN, '灼烧', 0.16, 'fire')),
],
@@ -1797,7 +1805,7 @@ const LANDING_SPECS: LandingSpec[] = [
{
id: 'earth-bearing-seal',
element: 'earth',
coefficient: 0.62,
coefficient: TIANYAN_LANDING_BASE_DAMAGE['earth-bearing-seal'],
baseEffects: (_reaction, settings) => [
shieldMagic(0.32, undefined, settings.loadoutMultiplier),
],
@@ -1805,7 +1813,7 @@ const LANDING_SPECS: LandingSpec[] = [
{
id: 'metal-cloud-cutter',
element: 'metal',
coefficient: 0.88,
coefficient: TIANYAN_LANDING_BASE_DAMAGE['metal-cloud-cutter'],
baseEffects: () => [
targetBuff(
buff('sect.tianyan.metal-cut', '破锋', BuffType.DEBUFF, 2, {
@@ -1822,7 +1830,7 @@ const LANDING_SPECS: LandingSpec[] = [
{
id: 'white-star-breaker',
element: 'metal',
coefficient: 0.50,
coefficient: TIANYAN_LANDING_BASE_DAMAGE['white-star-breaker'],
baseEffects: () => [
{ type: 'dispel', params: { maxCount: 1, status: 'positive' } },
],
@@ -1830,7 +1838,7 @@ const LANDING_SPECS: LandingSpec[] = [
{
id: 'dark-water-return',
element: 'water',
coefficient: 0.72,
coefficient: TIANYAN_LANDING_BASE_DAMAGE['dark-water-return'],
baseEffects: (reaction, settings) => {
if (reaction?.id !== 'cold-spring') {
return [
@@ -53,6 +53,8 @@ export const TIANYAN_CHAIN_CONTROL = 'sect.tianyan.chain-control';
export const TIANYAN_FIRST_CHANGE = 'sect.tianyan.first-change';
export const TIANYAN_SHATTER_COOLDOWN = 'sect.tianyan.shatter-cooldown';
export const TIANYAN_DISPEL_TRUTH_COOLDOWN = 'sect.tianyan.dispel-truth-cooldown';
export const TIANYAN_STRATEGY_ELEMENT_HISTORY =
'sect.tianyan.strategy.recent-elements';
export const TIANYAN_TECHNIQUE = 'landing-technique';
export const TIANYAN_INNER_ART = 'inner-art';
@@ -1,4 +1,8 @@
import { GameplayTags } from '@shared/engine/shared/tag-domain';
import {
TIANYAN_SECT_ID,
type TianyanLandingAbilityId,
} from '../ids';
export const TIANYAN_ELEMENTS = [
'wood',
@@ -51,6 +55,18 @@ export const TIANYAN_ELEMENT_ABILITY_TAGS: Record<TianyanElement, string> = {
water: GameplayTags.ABILITY.ELEMENT.WATER,
};
export const TIANYAN_LANDING_BASE_DAMAGE: Record<
TianyanLandingAbilityId,
number
> = {
'verdant-pulse': 0.68,
'flowing-flame': 0.82,
'earth-bearing-seal': 0.62,
'metal-cloud-cutter': 0.88,
'white-star-breaker': 0.50,
'dark-water-return': 0.72,
};
export const TIANYAN_ELEMENT_BUFF_TAGS: Record<TianyanElement, string> = {
wood: GameplayTags.BUFF.ELEMENT.WOOD,
fire: GameplayTags.BUFF.ELEMENT.FIRE,
@@ -60,15 +76,15 @@ export const TIANYAN_ELEMENT_BUFF_TAGS: Record<TianyanElement, string> = {
};
export const TIANYAN_SEAL_STATE_TAGS: Record<TianyanElement, string> = {
wood: GameplayTags.STATUS.SECT.state('tianyan', 'ElementSeal.Wood'),
fire: GameplayTags.STATUS.SECT.state('tianyan', 'ElementSeal.Fire'),
earth: GameplayTags.STATUS.SECT.state('tianyan', 'ElementSeal.Earth'),
metal: GameplayTags.STATUS.SECT.state('tianyan', 'ElementSeal.Metal'),
water: GameplayTags.STATUS.SECT.state('tianyan', 'ElementSeal.Water'),
wood: GameplayTags.STATUS.SECT.state(TIANYAN_SECT_ID, 'ElementSeal.Wood'),
fire: GameplayTags.STATUS.SECT.state(TIANYAN_SECT_ID, 'ElementSeal.Fire'),
earth: GameplayTags.STATUS.SECT.state(TIANYAN_SECT_ID, 'ElementSeal.Earth'),
metal: GameplayTags.STATUS.SECT.state(TIANYAN_SECT_ID, 'ElementSeal.Metal'),
water: GameplayTags.STATUS.SECT.state(TIANYAN_SECT_ID, 'ElementSeal.Water'),
};
export const TIANYAN_ANY_SEAL_STATE_TAG = GameplayTags.STATUS.SECT.state(
'tianyan',
TIANYAN_SECT_ID,
'ElementSeal',
);
@@ -7,9 +7,14 @@ import { BuffType } from '@shared/engine/battle-v5/core/types';
import { readElementHistory } from '@shared/engine/battle-v5/core/runtimeState';
import { GameplayTags } from '@shared/engine/shared/tag-domain';
import { SectStrategyCandidates, type SectTacticId } from '../../core';
import { TIANYAN_SECT_ID } from './ids';
import {
TIANYAN_SECT_ID,
TIANYAN_STRATEGY_ELEMENT_HISTORY,
} from './ids';
import {
TIANYAN_ELEMENT_ABILITY_TAGS,
TIANYAN_ELEMENTS,
TIANYAN_LANDING_BASE_DAMAGE,
TIANYAN_SEAL_STATE_TAGS,
getTianyanReaction,
type TianyanElement,
@@ -24,16 +29,6 @@ const ABILITY_ELEMENTS: Partial<Record<string, TianyanElement>> = {
'dark-water-return': 'water',
};
const STRATEGY_ELEMENT_HISTORY = 'sect.tianyan.strategy.recent-elements';
const ELEMENT_TAGS: Record<TianyanElement, string> = {
wood: GameplayTags.ABILITY.ELEMENT.WOOD,
fire: GameplayTags.ABILITY.ELEMENT.FIRE,
earth: GameplayTags.ABILITY.ELEMENT.EARTH,
metal: GameplayTags.ABILITY.ELEMENT.METAL,
water: GameplayTags.ABILITY.ELEMENT.WATER,
};
function contentAbilityId(runtimeId: string): string {
const prefix = `sect.${TIANYAN_SECT_ID}.`;
return runtimeId.startsWith(prefix) ? runtimeId.slice(prefix.length) : runtimeId;
@@ -77,6 +72,21 @@ function abilityIdsByReaction(
.filter((id): id is string => Boolean(id));
}
function expectedReactionDamage(
abilityId: string,
seal: TianyanElement,
): number {
const element = ABILITY_ELEMENTS[abilityId];
const base = TIANYAN_LANDING_BASE_DAMAGE[
abilityId as keyof typeof TIANYAN_LANDING_BASE_DAMAGE
];
if (!element || base === undefined) return 0;
const reaction = getTianyanReaction(seal, element);
return base * (
1 + (reaction.mainDamageBonus ?? reaction.followUpRatio ?? 0)
);
}
abstract class TianyanSelectionStrategy implements AbilitySelectionStrategy {
constructor(protected readonly tacticId: SectTacticId) {}
@@ -164,11 +174,13 @@ export class HetuSelectionStrategy extends TianyanSelectionStrategy {
if (reactions.length > 0) {
const recentElements = readElementHistory(
context.caster,
STRATEGY_ELEMENT_HISTORY,
TIANYAN_STRATEGY_ELEMENT_HISTORY,
);
const missingElementReactions = reactions.filter((id) => {
const element = ABILITY_ELEMENTS[id];
return element ? !recentElements.has(ELEMENT_TAGS[element]) : false;
return element
? !recentElements.has(TIANYAN_ELEMENT_ABILITY_TAGS[element])
: false;
});
if (missingElementReactions.length > 0) {
return this.pick(context, missingElementReactions, 740);
@@ -204,7 +216,13 @@ export class LuoshuSelectionStrategy extends TianyanSelectionStrategy {
id === 'earth-bearing-seal',
);
if (!controlImmune && lock.length > 0) return this.pick(context, lock, 780);
if (currentSeal(context)) {
const hasControlLanding = context.candidates.some((candidate) => {
const id = contentAbilityId(candidate.ability.id);
return id === 'earth-bearing-seal' ||
id === 'metal-cloud-cutter' ||
id === 'white-star-breaker';
});
if (currentSeal(context) && hasControlLanding) {
return this.pick(context, ['shift-palace', ...anyReaction], 720);
}
}
@@ -224,17 +242,22 @@ export class LuoshuSelectionStrategy extends TianyanSelectionStrategy {
}
if (this.tacticId === 'decisive-derivation') {
if (anyReaction.length > 0) {
const expectedDamageOrder = [
'metal-cloud-cutter',
'flowing-flame',
'dark-water-return',
'earth-bearing-seal',
'verdant-pulse',
'white-star-breaker',
].filter((id) => anyReaction.includes(id));
const seal = currentSeal(context)!;
const expectedDamageOrder = anyReaction
.map((id, order) => ({
id,
order,
damage: expectedReactionDamage(id, seal),
}))
.sort((left, right) =>
right.damage - left.damage ||
left.order - right.order ||
left.id.localeCompare(right.id),
)
.map(({ id }) => id);
return this.pick(
context,
[...expectedDamageOrder, ...anyReaction],
expectedDamageOrder,
760,
);
}
@@ -203,4 +203,18 @@ describe('天衍圣地战斗投影', () => {
ability.id as (typeof TIANYAN_VISIBLE_ABILITY_IDS)[number],
))).toHaveLength(13);
});
it('技能详情区分落印术、内景法与天衍秘法', () => {
const sect = tianyanState(TIANYAN_HETU_PATH_ID);
const notes = (abilityId: string) =>
resolveSectAbility({ sect, realm: '化神', abilityId }).notes;
expect(notes('verdant-pulse')).toContain('落印术·木');
expect(notes('myriad-wood-renewal')).toContain('内景法·木');
expect(notes('lotus-in-fire')).toContain('内景法·火');
expect(notes('boundless-earth')).toContain('内景法·土');
expect(notes('heavenly-river-cleansing')).toContain('内景法·水');
expect(notes('shift-palace')).toContain('天衍秘法');
expect(notes('five-qi-repository')).toContain('天衍秘法');
});
});
@@ -11,6 +11,7 @@ import { resolveSectAbility } from '../..';
import {
TIANYAN_HETU_PATH_ID,
TIANYAN_LUOSHU_PATH_ID,
TIANYAN_STRATEGY_ELEMENT_HISTORY,
} from '../ids';
import {
TIANYAN_HETU_PATH_MODULE,
@@ -107,7 +108,7 @@ describe('天衍六套自动战术', () => {
);
rememberElement(
battle.caster,
'sect.tianyan.strategy.recent-elements',
TIANYAN_STRATEGY_ELEMENT_HISTORY,
GameplayTags.ABILITY.ELEMENT.WATER,
);
@@ -211,6 +212,23 @@ describe('天衍六套自动战术', () => {
expect(selectedId(result)).toBe('shift-palace');
});
it('锁机未装配土金落印术时不为空转法印,改用当前合法反应', () => {
const battle = context(TIANYAN_LUOSHU_PATH_ID, [
'shift-palace',
'flowing-flame',
]);
battle.opponent.buffs.addBuff(
BuffFactory.create(createElementSeal('wood', 2)),
battle.caster,
);
const result = TIANYAN_LUOSHU_PATH_MODULE
.createSelectionStrategy('lock-meridian')
.select(battle);
expect(selectedId(result)).toBe('flowing-flame');
});
it('断局只在实际可触发的反应中比较伤害,不误选无反应技能', () => {
const battle = context(TIANYAN_LUOSHU_PATH_ID, [
'metal-cloud-cutter',
@@ -227,4 +245,21 @@ describe('天衍六套自动战术', () => {
expect(selectedId(result)).toBe('earth-bearing-seal');
});
it('断局在水印下选择预期伤害更高的滋荣而非泥沼', () => {
const battle = context(TIANYAN_LUOSHU_PATH_ID, [
'earth-bearing-seal',
'verdant-pulse',
]);
battle.opponent.buffs.addBuff(
BuffFactory.create(createElementSeal('water', 2)),
battle.caster,
);
const result = TIANYAN_LUOSHU_PATH_MODULE
.createSelectionStrategy('decisive-derivation')
.select(battle);
expect(selectedId(result)).toBe('verdant-pulse');
});
});