- DamageType enum 추가 (physical/magical) - 스킬별 데미지 타입 지정 기능 구현 - 마법 스킬 데미지에 magAtk/magDef 적용 - 장비 아이템에서 magAtk/magDef 스탯 추출 - 관련 테스트 업데이트
687 lines
21 KiB
Dart
687 lines
21 KiB
Dart
import 'package:asciineverdie/data/skill_data.dart';
|
||
import 'package:asciineverdie/src/core/engine/skill_service.dart';
|
||
import 'package:asciineverdie/src/core/model/combat_stats.dart';
|
||
import 'package:asciineverdie/src/core/model/game_state.dart';
|
||
import 'package:asciineverdie/src/core/model/monster_combat_stats.dart';
|
||
import 'package:asciineverdie/src/core/model/skill.dart';
|
||
import 'package:asciineverdie/src/core/util/deterministic_random.dart';
|
||
import 'package:flutter_test/flutter_test.dart';
|
||
|
||
void main() {
|
||
group('SkillService', () {
|
||
group('canUseSkill', () {
|
||
test('GCD 활성화 시 onGlobalCooldown 반환', () {
|
||
final rng = DeterministicRandom(42);
|
||
final service = SkillService(rng: rng);
|
||
|
||
const skill = SkillData.stackTrace;
|
||
final skillSystem = SkillSystemState.empty().copyWith(
|
||
elapsedMs: 1000,
|
||
globalCooldownEndMs: 2500, // GCD 활성화 중
|
||
);
|
||
|
||
final result = service.canUseSkill(
|
||
skill: skill,
|
||
currentMp: 100,
|
||
skillSystem: skillSystem,
|
||
);
|
||
|
||
expect(result, equals(SkillFailReason.onGlobalCooldown));
|
||
});
|
||
|
||
test('MP 부족 시 notEnoughMp 반환', () {
|
||
final rng = DeterministicRandom(42);
|
||
final service = SkillService(rng: rng);
|
||
|
||
const skill = SkillData.stackTrace; // MP 10 필요
|
||
final skillSystem = SkillSystemState.empty().copyWith(elapsedMs: 5000);
|
||
|
||
final result = service.canUseSkill(
|
||
skill: skill,
|
||
currentMp: 5, // MP 부족
|
||
skillSystem: skillSystem,
|
||
);
|
||
|
||
expect(result, equals(SkillFailReason.notEnoughMp));
|
||
});
|
||
|
||
test('쿨타임 중 onCooldown 반환', () {
|
||
final rng = DeterministicRandom(42);
|
||
final service = SkillService(rng: rng);
|
||
|
||
const skill = SkillData.stackTrace; // 쿨타임 3000ms
|
||
final skillSystem = SkillSystemState.empty().copyWith(
|
||
elapsedMs: 2000,
|
||
skillStates: [
|
||
const SkillState(
|
||
skillId: 'stack_trace',
|
||
lastUsedMs: 1000, // 1초 전 사용
|
||
rank: 1,
|
||
),
|
||
],
|
||
);
|
||
|
||
final result = service.canUseSkill(
|
||
skill: skill,
|
||
currentMp: 100,
|
||
skillSystem: skillSystem,
|
||
);
|
||
|
||
expect(result, equals(SkillFailReason.onCooldown));
|
||
});
|
||
|
||
test('모든 조건 충족 시 null 반환', () {
|
||
final rng = DeterministicRandom(42);
|
||
final service = SkillService(rng: rng);
|
||
|
||
const skill = SkillData.stackTrace;
|
||
final skillSystem = SkillSystemState.empty().copyWith(elapsedMs: 5000);
|
||
|
||
final result = service.canUseSkill(
|
||
skill: skill,
|
||
currentMp: 100,
|
||
skillSystem: skillSystem,
|
||
);
|
||
|
||
expect(result, isNull);
|
||
});
|
||
});
|
||
|
||
group('useAttackSkill', () {
|
||
test('기본 공격 데미지 계산', () {
|
||
final rng = DeterministicRandom(42);
|
||
final service = SkillService(rng: rng);
|
||
|
||
const skill = SkillData.stackTrace; // 2.0x 배율
|
||
final player = CombatStats.empty().copyWith(
|
||
atk: 100,
|
||
mpCurrent: 50,
|
||
mpMax: 100,
|
||
);
|
||
final monster = MonsterCombatStats(
|
||
name: 'Test Monster',
|
||
level: 1,
|
||
atk: 10,
|
||
def: 50, // DEF 50 → 50 * 0.3 = 15 감소
|
||
magDef: 50,
|
||
hpMax: 500,
|
||
hpCurrent: 500,
|
||
criRate: 0.05,
|
||
criDamage: 1.5,
|
||
evasion: 0.0,
|
||
accuracy: 0.8,
|
||
attackDelayMs: 1000,
|
||
expReward: 100,
|
||
);
|
||
final skillSystem = SkillSystemState.empty().copyWith(elapsedMs: 5000);
|
||
|
||
final result = service.useAttackSkill(
|
||
skill: skill,
|
||
player: player,
|
||
monster: monster,
|
||
skillSystem: skillSystem,
|
||
);
|
||
|
||
// ATK 100 * 2.0 - DEF 50 * 0.3 = 200 - 15 = 185
|
||
expect(result.result.success, isTrue);
|
||
expect(result.result.damage, equals(185));
|
||
expect(result.updatedPlayer.mpCurrent, equals(20)); // 50 - 30 (mpCost 30)
|
||
expect(result.updatedMonster.hpCurrent, equals(315)); // 500 - 185
|
||
});
|
||
|
||
test('버프 적용 시 데미지 증가', () {
|
||
final rng = DeterministicRandom(42);
|
||
final service = SkillService(rng: rng);
|
||
|
||
const skill = SkillData.stackTrace;
|
||
final player = CombatStats.empty().copyWith(
|
||
atk: 100,
|
||
mpCurrent: 50,
|
||
mpMax: 100,
|
||
);
|
||
final monster = MonsterCombatStats(
|
||
name: 'Test Monster',
|
||
level: 1,
|
||
atk: 10,
|
||
def: 0,
|
||
magDef: 0,
|
||
hpMax: 500,
|
||
hpCurrent: 500,
|
||
criRate: 0.05,
|
||
criDamage: 1.5,
|
||
evasion: 0.0,
|
||
accuracy: 0.8,
|
||
attackDelayMs: 1000,
|
||
expReward: 100,
|
||
);
|
||
final skillSystem = SkillSystemState.empty().copyWith(
|
||
elapsedMs: 5000,
|
||
activeBuffs: [
|
||
const ActiveBuff(
|
||
effect: BuffEffect(
|
||
id: 'test_buff',
|
||
name: 'Test Buff',
|
||
durationMs: 10000,
|
||
atkModifier: 0.5, // +50% ATK
|
||
),
|
||
startedMs: 4000,
|
||
sourceSkillId: 'test',
|
||
),
|
||
],
|
||
);
|
||
|
||
final result = service.useAttackSkill(
|
||
skill: skill,
|
||
player: player,
|
||
monster: monster,
|
||
skillSystem: skillSystem,
|
||
);
|
||
|
||
// ATK 100 * 2.0 * 1.5 = 300
|
||
expect(result.result.damage, equals(300));
|
||
});
|
||
});
|
||
|
||
group('useAttackSkillWithRank', () {
|
||
test('랭크 1 데미지 (기본 배율)', () {
|
||
final rng = DeterministicRandom(42);
|
||
final service = SkillService(rng: rng);
|
||
|
||
const skill = SkillData.stackTrace;
|
||
final player = CombatStats.empty().copyWith(
|
||
atk: 100,
|
||
mpCurrent: 50,
|
||
mpMax: 100,
|
||
);
|
||
final monster = MonsterCombatStats(
|
||
name: 'Test Monster',
|
||
level: 1,
|
||
atk: 10,
|
||
def: 0,
|
||
magDef: 0,
|
||
hpMax: 500,
|
||
hpCurrent: 500,
|
||
criRate: 0.05,
|
||
criDamage: 1.5,
|
||
evasion: 0.0,
|
||
accuracy: 0.8,
|
||
attackDelayMs: 1000,
|
||
expReward: 100,
|
||
);
|
||
final skillSystem = SkillSystemState.empty().copyWith(elapsedMs: 5000);
|
||
|
||
final result = service.useAttackSkillWithRank(
|
||
skill: skill,
|
||
player: player,
|
||
monster: monster,
|
||
skillSystem: skillSystem,
|
||
rank: 1,
|
||
);
|
||
|
||
// 랭크 1: 1.0x → ATK 100 * 2.0 * 1.0 = 200
|
||
expect(result.result.damage, equals(200));
|
||
expect(result.updatedPlayer.mpCurrent, equals(20)); // MP 30 소모
|
||
});
|
||
|
||
test('랭크 5 데미지 스케일링', () {
|
||
final rng = DeterministicRandom(42);
|
||
final service = SkillService(rng: rng);
|
||
|
||
const skill = SkillData.stackTrace;
|
||
final player = CombatStats.empty().copyWith(
|
||
atk: 100,
|
||
mpCurrent: 50,
|
||
mpMax: 100,
|
||
);
|
||
final monster = MonsterCombatStats(
|
||
name: 'Test Monster',
|
||
level: 1,
|
||
atk: 10,
|
||
def: 0,
|
||
magDef: 0,
|
||
hpMax: 500,
|
||
hpCurrent: 500,
|
||
criRate: 0.05,
|
||
criDamage: 1.5,
|
||
evasion: 0.0,
|
||
accuracy: 0.8,
|
||
attackDelayMs: 1000,
|
||
expReward: 100,
|
||
);
|
||
final skillSystem = SkillSystemState.empty().copyWith(elapsedMs: 5000);
|
||
|
||
final result = service.useAttackSkillWithRank(
|
||
skill: skill,
|
||
player: player,
|
||
monster: monster,
|
||
skillSystem: skillSystem,
|
||
rank: 5,
|
||
);
|
||
|
||
// 랭크 5: 1.32x (1.0 + 4 * 0.08) - 랭크 배율 하향
|
||
// ATK 100 * 2.0 * 1.32 = 264
|
||
expect(result.result.damage, equals(264));
|
||
|
||
// MP 비용: 30 * (1.0 - 4 * 0.03) = 30 * 0.88 = 26 (반올림)
|
||
expect(result.updatedPlayer.mpCurrent, equals(24)); // 50 - 26
|
||
});
|
||
});
|
||
|
||
group('useHealSkill', () {
|
||
test('고정 회복량', () {
|
||
final rng = DeterministicRandom(42);
|
||
final service = SkillService(rng: rng);
|
||
|
||
const skill = SkillData.hotReload; // healAmount: 30, mpCost: 80
|
||
final player = CombatStats.empty().copyWith(
|
||
hpMax: 200,
|
||
hpCurrent: 100,
|
||
mpMax: 200,
|
||
mpCurrent: 150,
|
||
);
|
||
final skillSystem = SkillSystemState.empty().copyWith(elapsedMs: 5000);
|
||
|
||
final result = service.useHealSkill(
|
||
skill: skill,
|
||
player: player,
|
||
skillSystem: skillSystem,
|
||
);
|
||
|
||
expect(result.result.success, isTrue);
|
||
expect(result.result.healedAmount, equals(30));
|
||
expect(result.updatedPlayer.hpCurrent, equals(130)); // 100 + 30
|
||
expect(result.updatedPlayer.mpCurrent, equals(70)); // 150 - 80
|
||
});
|
||
|
||
test('퍼센트 회복량', () {
|
||
final rng = DeterministicRandom(42);
|
||
final service = SkillService(rng: rng);
|
||
|
||
const skill = SkillData.garbageCollection; // healPercent: 0.3
|
||
final player = CombatStats.empty().copyWith(
|
||
hpMax: 200,
|
||
hpCurrent: 100,
|
||
mpMax: 100,
|
||
mpCurrent: 50,
|
||
);
|
||
final skillSystem = SkillSystemState.empty().copyWith(elapsedMs: 5000);
|
||
|
||
final result = service.useHealSkill(
|
||
skill: skill,
|
||
player: player,
|
||
skillSystem: skillSystem,
|
||
);
|
||
|
||
// healPercent 0.3 * hpMax 200 = 60
|
||
expect(result.result.healedAmount, equals(60));
|
||
expect(result.updatedPlayer.hpCurrent, equals(160)); // 100 + 60
|
||
});
|
||
|
||
test('HP 캡 적용', () {
|
||
final rng = DeterministicRandom(42);
|
||
final service = SkillService(rng: rng);
|
||
|
||
const skill = SkillData.snapshotRestore; // healPercent: 0.5
|
||
final player = CombatStats.empty().copyWith(
|
||
hpMax: 200,
|
||
hpCurrent: 180, // 거의 만피
|
||
mpMax: 100,
|
||
mpCurrent: 80,
|
||
);
|
||
final skillSystem = SkillSystemState.empty().copyWith(elapsedMs: 5000);
|
||
|
||
final result = service.useHealSkill(
|
||
skill: skill,
|
||
player: player,
|
||
skillSystem: skillSystem,
|
||
);
|
||
|
||
// healPercent 0.5 * hpMax 200 = 100
|
||
// 하지만 hpMax 캡으로 200까지만
|
||
expect(result.updatedPlayer.hpCurrent, equals(200));
|
||
});
|
||
});
|
||
|
||
group('useDotSkill', () {
|
||
test('DOT 효과 생성', () {
|
||
final rng = DeterministicRandom(42);
|
||
final service = SkillService(rng: rng);
|
||
|
||
const skill = SkillData.memoryDump;
|
||
// baseDotDamage: 10, baseDotDurationMs: 6000, baseDotTickMs: 1000
|
||
final player = CombatStats.empty().copyWith(
|
||
mpMax: 100,
|
||
mpCurrent: 50,
|
||
);
|
||
final skillSystem = SkillSystemState.empty().copyWith(elapsedMs: 5000);
|
||
|
||
final result = service.useDotSkill(
|
||
skill: skill,
|
||
player: player,
|
||
skillSystem: skillSystem,
|
||
playerInt: 10, // 기본
|
||
playerWis: 10, // 기본
|
||
);
|
||
|
||
expect(result.result.success, isTrue);
|
||
expect(result.dotEffect, isNotNull);
|
||
expect(result.dotEffect!.baseDamage, equals(10));
|
||
expect(result.dotEffect!.damagePerTick, equals(10)); // INT 10 → 1.0x
|
||
expect(result.dotEffect!.tickIntervalMs, equals(1000)); // WIS 10 → 1.0x
|
||
expect(result.dotEffect!.totalDurationMs, equals(6000));
|
||
|
||
// 예상 총 데미지: 6틱 × 10 = 60
|
||
expect(result.result.damage, equals(60));
|
||
});
|
||
|
||
test('INT 보정 적용 (데미지 증가)', () {
|
||
final rng = DeterministicRandom(42);
|
||
final service = SkillService(rng: rng);
|
||
|
||
const skill = SkillData.memoryDump;
|
||
final player = CombatStats.empty().copyWith(
|
||
mpMax: 100,
|
||
mpCurrent: 50,
|
||
);
|
||
final skillSystem = SkillSystemState.empty().copyWith(elapsedMs: 5000);
|
||
|
||
final result = service.useDotSkill(
|
||
skill: skill,
|
||
player: player,
|
||
skillSystem: skillSystem,
|
||
playerInt: 20, // INT +10 → +30% 데미지
|
||
playerWis: 10,
|
||
);
|
||
|
||
// INT 20: 1.0 + (20-10) * 0.03 = 1.3x
|
||
// baseDotDamage 10 * 1.3 = 13
|
||
expect(result.dotEffect!.damagePerTick, equals(13));
|
||
});
|
||
|
||
test('WIS 보정 적용 (틱 간격 감소)', () {
|
||
final rng = DeterministicRandom(42);
|
||
final service = SkillService(rng: rng);
|
||
|
||
const skill = SkillData.memoryDump;
|
||
final player = CombatStats.empty().copyWith(
|
||
mpMax: 100,
|
||
mpCurrent: 50,
|
||
);
|
||
final skillSystem = SkillSystemState.empty().copyWith(elapsedMs: 5000);
|
||
|
||
final result = service.useDotSkill(
|
||
skill: skill,
|
||
player: player,
|
||
skillSystem: skillSystem,
|
||
playerInt: 10,
|
||
playerWis: 20, // WIS +10 → 틱 간격 감소
|
||
);
|
||
|
||
// WIS 20: 1.0 + (20-10) * 0.02 = 1.2x
|
||
// baseDotTickMs 1000 / 1.2 = 833ms
|
||
expect(result.dotEffect!.tickIntervalMs, equals(833));
|
||
});
|
||
});
|
||
|
||
group('calculateMpRegen', () {
|
||
test('비전투 중 MP 회복 (50ms당 1)', () {
|
||
final rng = DeterministicRandom(42);
|
||
final service = SkillService(rng: rng);
|
||
|
||
final result = service.calculateMpRegen(
|
||
elapsedMs: 1000, // 1초
|
||
isInCombat: false,
|
||
wis: 10,
|
||
);
|
||
|
||
// 1000ms / 50ms = 20
|
||
expect(result, equals(20));
|
||
});
|
||
|
||
test('전투 중 MP 회복 (WIS 기반)', () {
|
||
final rng = DeterministicRandom(42);
|
||
final service = SkillService(rng: rng);
|
||
|
||
final result = service.calculateMpRegen(
|
||
elapsedMs: 1000, // 1초
|
||
isInCombat: true,
|
||
wis: 10,
|
||
);
|
||
|
||
// 전투 중: 500ms당 (1 + WIS/20)
|
||
// WIS 10: 1 + 10/20 = 1 (정수 연산)
|
||
// 1000ms / 500ms = 2틱
|
||
// 2틱 * 1 = 2
|
||
expect(result, equals(2));
|
||
});
|
||
|
||
test('높은 WIS 전투 중 MP 회복', () {
|
||
final rng = DeterministicRandom(42);
|
||
final service = SkillService(rng: rng);
|
||
|
||
final result = service.calculateMpRegen(
|
||
elapsedMs: 1000,
|
||
isInCombat: true,
|
||
wis: 40,
|
||
);
|
||
|
||
// WIS 40: 1 + 40/20 = 3
|
||
// 2틱 * 3 = 6
|
||
expect(result, equals(6));
|
||
});
|
||
});
|
||
|
||
group('selectAutoSkill', () {
|
||
test('MP 부족 시 null 반환 (일반 공격)', () {
|
||
final rng = DeterministicRandom(42);
|
||
final service = SkillService(rng: rng);
|
||
|
||
final player = CombatStats.empty().copyWith(
|
||
hpMax: 100,
|
||
hpCurrent: 100,
|
||
mpMax: 100,
|
||
mpCurrent: 10, // 10% MP
|
||
);
|
||
final monster = MonsterCombatStats(
|
||
name: 'Test Monster',
|
||
level: 1,
|
||
atk: 10,
|
||
def: 10,
|
||
magDef: 10,
|
||
hpMax: 100,
|
||
hpCurrent: 100,
|
||
criRate: 0.05,
|
||
criDamage: 1.5,
|
||
evasion: 0.0,
|
||
accuracy: 0.8,
|
||
attackDelayMs: 1000,
|
||
expReward: 100,
|
||
);
|
||
final skillSystem = SkillSystemState.empty().copyWith(elapsedMs: 5000);
|
||
|
||
final result = service.selectAutoSkill(
|
||
player: player,
|
||
monster: monster,
|
||
skillSystem: skillSystem,
|
||
availableSkillIds: ['stack_trace', 'garbage_collection'],
|
||
);
|
||
|
||
expect(result, isNull);
|
||
});
|
||
|
||
test('HP 30% 미만 시 회복 스킬 우선', () {
|
||
// 시드 조정하여 일반 공격 확률을 넘어가도록
|
||
final rng = DeterministicRandom(999);
|
||
final service = SkillService(rng: rng);
|
||
|
||
final player = CombatStats.empty().copyWith(
|
||
hpMax: 100,
|
||
hpCurrent: 20, // 20% HP
|
||
mpMax: 200,
|
||
mpCurrent: 150, // 힐 스킬 사용 가능한 MP (garbageCollection: 130)
|
||
);
|
||
final monster = MonsterCombatStats(
|
||
name: 'Test Monster',
|
||
level: 1,
|
||
atk: 10,
|
||
def: 10,
|
||
magDef: 10,
|
||
hpMax: 100,
|
||
hpCurrent: 100,
|
||
criRate: 0.05,
|
||
criDamage: 1.5,
|
||
evasion: 0.0,
|
||
accuracy: 0.8,
|
||
attackDelayMs: 1000,
|
||
expReward: 100,
|
||
);
|
||
final skillSystem = SkillSystemState.empty().copyWith(elapsedMs: 5000);
|
||
|
||
final result = service.selectAutoSkill(
|
||
player: player,
|
||
monster: monster,
|
||
skillSystem: skillSystem,
|
||
availableSkillIds: ['stack_trace', 'garbage_collection'],
|
||
);
|
||
|
||
// HP < 30%이면 회복 스킬 반환 (garbage_collection)
|
||
expect(result?.type, equals(SkillType.heal));
|
||
});
|
||
});
|
||
|
||
group('useBuffSkill', () {
|
||
test('버프 적용', () {
|
||
final rng = DeterministicRandom(42);
|
||
final service = SkillService(rng: rng);
|
||
|
||
const skill = SkillData.debugMode; // ATK +25% 버프, mpCost: 100
|
||
final player = CombatStats.empty().copyWith(
|
||
mpMax: 200,
|
||
mpCurrent: 150,
|
||
);
|
||
final skillSystem = SkillSystemState.empty().copyWith(elapsedMs: 5000);
|
||
|
||
final result = service.useBuffSkill(
|
||
skill: skill,
|
||
player: player,
|
||
skillSystem: skillSystem,
|
||
);
|
||
|
||
expect(result.result.success, isTrue);
|
||
expect(result.result.appliedBuff, isNotNull);
|
||
expect(
|
||
result.result.appliedBuff!.effect.atkModifier,
|
||
equals(0.25),
|
||
);
|
||
expect(result.updatedSkillSystem.activeBuffs.length, equals(1));
|
||
expect(result.updatedPlayer.mpCurrent, equals(50)); // 150 - 100
|
||
});
|
||
|
||
test('중복 버프 제거 후 새 버프 적용', () {
|
||
final rng = DeterministicRandom(42);
|
||
final service = SkillService(rng: rng);
|
||
|
||
const skill = SkillData.debugMode;
|
||
final player = CombatStats.empty().copyWith(
|
||
mpMax: 100,
|
||
mpCurrent: 50,
|
||
);
|
||
final existingBuff = const ActiveBuff(
|
||
effect: BuffEffect(
|
||
id: 'debug_mode_buff',
|
||
name: 'Debug Mode',
|
||
durationMs: 10000,
|
||
atkModifier: 0.25,
|
||
),
|
||
startedMs: 1000,
|
||
sourceSkillId: 'debug_mode',
|
||
);
|
||
final skillSystem = SkillSystemState.empty().copyWith(
|
||
elapsedMs: 5000,
|
||
activeBuffs: [existingBuff],
|
||
);
|
||
|
||
final result = service.useBuffSkill(
|
||
skill: skill,
|
||
player: player,
|
||
skillSystem: skillSystem,
|
||
);
|
||
|
||
// 기존 버프 제거 후 새 버프 추가 → 총 1개
|
||
expect(result.updatedSkillSystem.activeBuffs.length, equals(1));
|
||
expect(
|
||
result.updatedSkillSystem.activeBuffs.first.startedMs,
|
||
equals(5000), // 새 버프
|
||
);
|
||
});
|
||
});
|
||
|
||
group('cleanupExpiredBuffs', () {
|
||
test('만료된 버프 제거', () {
|
||
final rng = DeterministicRandom(42);
|
||
final service = SkillService(rng: rng);
|
||
|
||
final skillSystem = SkillSystemState.empty().copyWith(
|
||
elapsedMs: 20000,
|
||
activeBuffs: [
|
||
const ActiveBuff(
|
||
effect: BuffEffect(
|
||
id: 'buff1',
|
||
name: 'Active Buff',
|
||
durationMs: 15000, // 아직 활성
|
||
),
|
||
startedMs: 10000,
|
||
sourceSkillId: 'test1',
|
||
),
|
||
const ActiveBuff(
|
||
effect: BuffEffect(
|
||
id: 'buff2',
|
||
name: 'Expired Buff',
|
||
durationMs: 5000, // 만료됨 (시작 5000 + 지속 5000 = 10000 < 20000)
|
||
),
|
||
startedMs: 5000,
|
||
sourceSkillId: 'test2',
|
||
),
|
||
],
|
||
);
|
||
|
||
final result = service.cleanupExpiredBuffs(skillSystem);
|
||
|
||
expect(result.activeBuffs.length, equals(1));
|
||
expect(result.activeBuffs.first.effect.id, equals('buff1'));
|
||
});
|
||
});
|
||
|
||
group('getRankMultiplier', () {
|
||
test('랭크별 배율 계산', () {
|
||
// 랭크 배율 하향: 0.15 → 0.08 per rank
|
||
expect(getRankMultiplier(1), equals(1.0));
|
||
expect(getRankMultiplier(2), closeTo(1.08, 0.001));
|
||
expect(getRankMultiplier(3), closeTo(1.16, 0.001));
|
||
expect(getRankMultiplier(5), closeTo(1.32, 0.001));
|
||
expect(getRankMultiplier(10), closeTo(1.72, 0.001));
|
||
});
|
||
});
|
||
|
||
group('getRankCooldownMultiplier', () {
|
||
test('랭크별 쿨타임 감소율', () {
|
||
expect(getRankCooldownMultiplier(1), equals(1.0));
|
||
expect(getRankCooldownMultiplier(2), closeTo(0.95, 0.001));
|
||
expect(getRankCooldownMultiplier(5), closeTo(0.80, 0.001));
|
||
// 최대 50% 감소
|
||
expect(getRankCooldownMultiplier(20), closeTo(0.5, 0.001));
|
||
});
|
||
});
|
||
|
||
group('getRankMpMultiplier', () {
|
||
test('랭크별 MP 비용 감소율', () {
|
||
expect(getRankMpMultiplier(1), equals(1.0));
|
||
expect(getRankMpMultiplier(2), closeTo(0.97, 0.001));
|
||
expect(getRankMpMultiplier(5), closeTo(0.88, 0.001));
|
||
// 최대 30% 감소
|
||
expect(getRankMpMultiplier(20), closeTo(0.7, 0.001));
|
||
});
|
||
});
|
||
});
|
||
}
|