139 lines
7.8 KiB
TypeScript
139 lines
7.8 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { calculateBoardEffects, type BoardSummarySource } from './board-effects.ts';
|
|
|
|
const summary: BoardSummarySource = {
|
|
members: [{ id: 'a', positionGroupId: 'model-a' }],
|
|
models: { 'model-a': [{ groupId: 'S-1', x: 0, y: 0 }, { groupId: 'B-1', x: 1, y: 0 }] },
|
|
nodeGroups: {
|
|
'B-1': [{ characterIds: [], effect: { type: 'performance_up', value: 100, targetId: 'skill_tree_effect_target-skill_tree_character', trigger: 'always' } }],
|
|
'B-2': [{ characterIds: [], effect: { type: 'live_active_skill_cool_time_shorten_permil_up', value: 40, targetId: 'skill_tree_effect_target-skill_tree_character', trigger: 'always' } }],
|
|
'R-1': [{ characterIds: [], effect: { type: 'all_parameter_up_permil_up', value: 30, targetId: 'skill_tree_effect_target-all', trigger: 'set_live_leader' } }],
|
|
'R-2': [{ characterIds: [], effect: {
|
|
id: 'skill_tree_effect-live_active_skill_effect_up_permil_up-100-skill_tree_effect_target-all-skill_tree_effect_passive_trigger-music_skill_tree_character-set_live_leader',
|
|
type: 'live_active_skill_effect_up_permil_up', value: 100, targetId: 'skill_tree_effect_target-all', trigger: 'set_live_leader'
|
|
} }],
|
|
'G-1': [{ characterIds: [], effect: { type: 'all_parameter_up', value: 5, targetId: 'skill_tree_effect_target-all', trigger: 'always' } }],
|
|
'G-2': [{ characterIds: [], effect: { type: 'all_parameter_up_for_character_grouping', value: 50, targetId: 'skill_tree_effect_target-character_grouping-grp-gen_0', trigger: 'always' } }],
|
|
'Y-1': [{ characterIds: [], effect: {
|
|
id: 'skill_tree_effect-live_score_bonus_add_permil_up_by_music_skill_tree_character_and_music_singer_type-5-skill_tree_effect_passive_trigger-music_skill_tree_character_and_music_singer_type-solo-always-solo',
|
|
type: 'live_score_bonus_add_permil_up_by_music_skill_tree_character_and_music_singer_type', value: 5, trigger: 'always'
|
|
} }]
|
|
}
|
|
};
|
|
|
|
const cards = [
|
|
{ id: 'card-a', characterId: 'a', type: '큐트', generation: '0기생' },
|
|
{ id: 'card-b', characterId: 'b', type: '퓨어', generation: '1기생' }
|
|
];
|
|
|
|
test('JP 3기생 보너스가 코보의 ID 3기생 보너스 상한을 소모하지 않는다', () => {
|
|
const summary: BoardSummarySource = {
|
|
nodeGroups: {
|
|
'G-jp': [{ characterIds: [], effect: { type: 'all_parameter_up_for_character_grouping', value: 900, targetId: 'skill_tree_effect_target-character_grouping-grp-gen_3' } }],
|
|
'G-id': [{ characterIds: [], effect: { type: 'all_parameter_up_for_character_grouping', value: 150, targetId: 'skill_tree_effect_target-character_grouping-grp-indonesia-gen_3' } }]
|
|
}
|
|
};
|
|
const kobo = [{ id: 'kobo', characterId: 'chr-03009', type: '퓨어', generation: 'hololive ID 3기생' }];
|
|
const before = calculateBoardEffects(summary, { account: ['G-jp'] }, kobo, '');
|
|
const after = calculateBoardEffects(summary, { account: ['G-jp', 'G-id'] }, kobo, '');
|
|
assert.deepEqual(before.flatByCardId.kobo, { performance: 0, technique: 0, sense: 0 });
|
|
assert.deepEqual(after.flatByCardId.kobo, { performance: 150, technique: 150, sense: 150 });
|
|
});
|
|
|
|
test('편성 멤버의 파란 보드, 리더의 빨간 보드와 공통 초록 보드를 합산한다', () => {
|
|
const result = calculateBoardEffects(summary, {
|
|
a: ['B-1', 'B-2', 'Y-1'],
|
|
leader: ['R-1', 'R-2'],
|
|
account: ['G-1']
|
|
}, cards, 'leader', { singerType: 'solo', singerCharacterIds: ['a'] });
|
|
|
|
assert.equal(result.flatByCardId['card-a'].performance, 105);
|
|
assert.equal(result.flatByCardId['card-b'].performance, 5);
|
|
assert.equal(result.rateByCardId['card-a'].sense, 3);
|
|
assert.deepEqual(result.allowedCooldownReductionsByCardId['card-a'], [0, 4, 8, 12]);
|
|
assert.deepEqual(result.allowedCooldownReductionsByCardId['card-b'], [0, 4, 8, 12]);
|
|
assert.equal(result.directSongScoreBonusPercent, 0.5);
|
|
assert.equal(result.scoreSupportPercent, 0);
|
|
});
|
|
|
|
test('리더가 악곡 가창자이면 조건부 빨간 스코어 서포트를 적용한다', () => {
|
|
const result = calculateBoardEffects(summary, { leader: ['R-2'] }, cards, 'leader', {
|
|
singerType: 'group',
|
|
singerCharacterIds: ['leader']
|
|
});
|
|
|
|
assert.equal(result.scoreSupportPercent, 10);
|
|
});
|
|
|
|
test('연결 카드가 범위 안의 활성 노드 효과를 증폭한다', () => {
|
|
const result = calculateBoardEffects(summary, { a: ['B-1'] }, cards, 'a', null, {
|
|
placements: { a: { 'S-1': 'connect-card' } },
|
|
cards: [{
|
|
id: 'connect-card',
|
|
awakening: 0,
|
|
connect: [{ level: 1, effectPermilUp: 2000, extent: [{ x: 1, y: 0 }] }]
|
|
}]
|
|
});
|
|
|
|
assert.equal(result.flatByCardId['card-a'].performance, 300);
|
|
});
|
|
|
|
test('조합 탐색용 기수 보너스와 악곡 보너스는 실제 노드 합계를 대체한다', () => {
|
|
const result = calculateBoardEffects(summary, { account: ['G-2', 'Y-1'] }, cards, 'a', {
|
|
singerType: 'solo', singerCharacterIds: ['a']
|
|
}, null, {
|
|
generationBonusByCardId: { 'card-a': 900 },
|
|
directSongScoreBonusPercent: 10
|
|
});
|
|
|
|
assert.equal(result.flatByCardId['card-a'].performance, 900);
|
|
assert.equal(result.flatByCardId['card-a'].technique, 900);
|
|
assert.equal(result.directSongScoreBonusPercent, 10);
|
|
|
|
const zeroOverride = calculateBoardEffects(summary, { account: ['G-2'] }, cards, 'a', null, null, {
|
|
generationBonusByCardId: { 'card-a': 0 }
|
|
});
|
|
const customOverride = calculateBoardEffects(summary, { account: ['G-2'] }, cards, 'a', null, null, {
|
|
generationBonusByCardId: { 'card-a': 450 }
|
|
});
|
|
|
|
assert.equal(zeroOverride.flatByCardId['card-a'].performance, 0);
|
|
assert.equal(customOverride.flatByCardId['card-a'].performance, 450);
|
|
});
|
|
|
|
test('같은 기수의 보드 보너스는 카드별로 최대 900까지만 적용한다', () => {
|
|
const cappedSummary: BoardSummarySource = {
|
|
nodeGroups: {
|
|
'G-10': [{ characterIds: [], effect: { type: 'all_parameter_up_for_character_grouping', value: 600, targetId: 'skill_tree_effect_target-character_grouping-grp-gen_0' } }],
|
|
'G-11': [{ characterIds: [], effect: { type: 'all_parameter_up_for_character_grouping', value: 500, targetId: 'skill_tree_effect_target-character_grouping-grp-gen_0' } }]
|
|
}
|
|
};
|
|
|
|
const result = calculateBoardEffects(cappedSummary, { account: ['G-10', 'G-11'] }, cards, 'a');
|
|
|
|
assert.equal(result.flatByCardId['card-a'].performance, 900);
|
|
assert.equal(result.flatByCardId['card-a'].technique, 900);
|
|
assert.equal(result.flatByCardId['card-a'].sense, 900);
|
|
assert.equal(result.flatByCardId['card-b'].performance, 0);
|
|
});
|
|
|
|
test('복수 소속 카드도 모든 기수 보너스를 합쳐 카드별 900까지만 적용한다', () => {
|
|
const multiGroupingSummary: BoardSummarySource = {
|
|
nodeGroups: {
|
|
'G-20': [{ characterIds: [], effect: { type: 'all_parameter_up_for_character_grouping', value: 900, targetId: 'skill_tree_effect_target-character_grouping-grp-gen_1' } }],
|
|
'G-21': [{ characterIds: [], effect: { type: 'all_parameter_up_for_character_grouping', value: 900, targetId: 'skill_tree_effect_target-character_grouping-grp-gamers' } }],
|
|
'G-22': [{ characterIds: [], effect: { type: 'all_parameter_up_for_character_grouping', value: 100, targetId: 'skill_tree_effect_target-character_grouping-grp-gen_1' } }]
|
|
}
|
|
};
|
|
const multiGroupingCards = [{ id: 'fubuki', characterId: 'fubuki', type: '퓨어', generation: 'hololive 1기생 / GAMERS' }];
|
|
|
|
const firstGenerationMaxed = calculateBoardEffects(multiGroupingSummary, { account: ['G-20'] }, multiGroupingCards, 'fubuki');
|
|
const gamersAdded = calculateBoardEffects(multiGroupingSummary, { account: ['G-20', 'G-21'] }, multiGroupingCards, 'fubuki');
|
|
const result = calculateBoardEffects(multiGroupingSummary, { account: ['G-20', 'G-21', 'G-22'] }, multiGroupingCards, 'fubuki');
|
|
|
|
assert.equal(firstGenerationMaxed.flatByCardId.fubuki.performance, 900);
|
|
assert.equal(gamersAdded.flatByCardId.fubuki.performance, 900);
|
|
assert.equal(result.flatByCardId.fubuki.performance, 900);
|
|
});
|