优化:天衍
This commit is contained in:
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');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user