style: dart format 적용
- 전체 Dart 소스 및 테스트 파일 포매팅 통일 - trailing comma, 줄바꿈, 인덴트 정리
This commit is contained in:
@@ -172,8 +172,7 @@ String returnRewardHoursAway(String time) =>
|
||||
String returnRewardChests(int count) =>
|
||||
_l('$count Treasure Chest(s)', '보물 상자 $count개', '宝箱 $count個');
|
||||
String get returnRewardOpenChests => _l('Open Chests', '상자 열기', '宝箱を開ける');
|
||||
String get returnRewardBonusChests =>
|
||||
_l('Bonus Chests', '보너스 상자', 'ボーナス宝箱');
|
||||
String get returnRewardBonusChests => _l('Bonus Chests', '보너스 상자', 'ボーナス宝箱');
|
||||
String get returnRewardClaimBonus =>
|
||||
_l('Get Bonus (AD)', '보너스 받기 (광고)', 'ボーナス受取 (広告)');
|
||||
String get returnRewardClaimBonusFree =>
|
||||
@@ -193,8 +192,7 @@ String chestRewardExpAmount(int exp) =>
|
||||
_l('+$exp EXP', '+$exp 경험치', '+$exp 経験値');
|
||||
String chestRewardPotionAmount(String name, int count) =>
|
||||
_l('$name x$count', '$name x$count', '$name x$count');
|
||||
String get chestRewardEquipped =>
|
||||
_l('Equipped!', '장착됨!', '装備しました!');
|
||||
String get chestRewardEquipped => _l('Equipped!', '장착됨!', '装備しました!');
|
||||
String get chestRewardBetterItem =>
|
||||
_l('Better than current!', '현재보다 좋습니다!', '現在より良い!');
|
||||
|
||||
@@ -1143,24 +1141,15 @@ String get uiSaveBattleLog => _l('Save Battle Log', '배틀로그 저장', 'バ
|
||||
String get iapRemoveAds => _l('Remove Ads', '광고 제거', '広告削除');
|
||||
String get iapRemoveAdsDesc =>
|
||||
_l('Enjoy ad-free experience', '광고 없이 플레이', '広告なしでプレイ');
|
||||
String get iapBenefitTitle =>
|
||||
_l('Premium Benefits', '프리미엄 혜택', 'プレミアム特典');
|
||||
String get iapBenefit1 =>
|
||||
_l('Ad-free gameplay', '광고 없는 쾌적한 플레이', '広告なしの快適プレイ');
|
||||
String get iapBenefitTitle => _l('Premium Benefits', '프리미엄 혜택', 'プレミアム特典');
|
||||
String get iapBenefit1 => _l('Ad-free gameplay', '광고 없는 쾌적한 플레이', '広告なしの快適プレイ');
|
||||
String get iapBenefit2 =>
|
||||
_l('Unlimited speed boost', '속도 부스트 무제한', 'スピードブースト無制限');
|
||||
String get iapBenefit3 => _l(
|
||||
'Stat reroll undo: 3 times',
|
||||
'신규 캐릭터 스탯 가챠 되돌리기 3회',
|
||||
'新キャラステ振り直し3回',
|
||||
);
|
||||
String get iapBenefit4 =>
|
||||
_l('Unlimited rerolls', '굴리기 무제한', 'リロール無制限');
|
||||
String get iapBenefit5 => _l(
|
||||
'2x offline time credited',
|
||||
'오프라인 시간 2배 인정',
|
||||
'オフライン時間2倍適用',
|
||||
);
|
||||
String get iapBenefit3 =>
|
||||
_l('Stat reroll undo: 3 times', '신규 캐릭터 스탯 가챠 되돌리기 3회', '新キャラステ振り直し3回');
|
||||
String get iapBenefit4 => _l('Unlimited rerolls', '굴리기 무제한', 'リロール無制限');
|
||||
String get iapBenefit5 =>
|
||||
_l('2x offline time credited', '오프라인 시간 2배 인정', 'オフライン時間2倍適用');
|
||||
String get iapBenefit6 =>
|
||||
_l('Return chests: 10 max', '복귀 상자 최대 10개', '帰還ボックス最大10個');
|
||||
String get iapPurchaseButton => _l('Purchase', '구매하기', '購入する');
|
||||
@@ -1232,17 +1221,12 @@ String get skillNoDetails => _l('No details', '상세 정보 없음', '詳細情
|
||||
// ============================================================================
|
||||
|
||||
String get notifyLevelUp => _l('LEVEL UP!', '레벨 업!', 'レベルアップ!');
|
||||
String notifyLevel(int level) =>
|
||||
_l('Level $level', '레벨 $level', 'レベル $level');
|
||||
String get notifyQuestComplete =>
|
||||
_l('QUEST COMPLETE!', '퀘스트 완료!', 'クエスト完了!');
|
||||
String notifyLevel(int level) => _l('Level $level', '레벨 $level', 'レベル $level');
|
||||
String get notifyQuestComplete => _l('QUEST COMPLETE!', '퀘스트 완료!', 'クエスト完了!');
|
||||
String get notifyPrologueComplete =>
|
||||
_l('PROLOGUE COMPLETE!', '프롤로그 완료!', 'プロローグ完了!');
|
||||
String notifyActComplete(int actNumber) => _l(
|
||||
'ACT $actNumber COMPLETE!',
|
||||
'${actNumber}막 완료!',
|
||||
'第${actNumber}幕完了!',
|
||||
);
|
||||
String notifyActComplete(int actNumber) =>
|
||||
_l('ACT $actNumber COMPLETE!', '${actNumber}막 완료!', '第${actNumber}幕完了!');
|
||||
String get notifyNewSpell => _l('NEW SPELL!', '새 주문!', '新しい呪文!');
|
||||
String get notifyNewEquipment => _l('NEW EQUIPMENT!', '새 장비!', '新しい装備!');
|
||||
String get notifyBossDefeated => _l('BOSS DEFEATED!', '보스 처치!', 'ボス撃破!');
|
||||
|
||||
@@ -16,9 +16,7 @@ import 'package:asciineverdie/src/core/util/pq_logic.dart' as pq_logic;
|
||||
///
|
||||
/// ProgressService에서 추출된 Act 완료, 보스 생성 등의 로직 담당.
|
||||
class ActProgressionService {
|
||||
const ActProgressionService({
|
||||
required this.config,
|
||||
});
|
||||
const ActProgressionService({required this.config});
|
||||
|
||||
final PqConfig config;
|
||||
|
||||
|
||||
@@ -64,8 +64,10 @@ class CharacterRollService {
|
||||
_resetUndoForNewSession();
|
||||
|
||||
_isInitialized = true;
|
||||
debugPrint('[CharacterRollService] Initialized: '
|
||||
'rolls=$_rollsRemaining, undo=$_undoRemaining');
|
||||
debugPrint(
|
||||
'[CharacterRollService] Initialized: '
|
||||
'rolls=$_rollsRemaining, undo=$_undoRemaining',
|
||||
);
|
||||
}
|
||||
|
||||
/// 저장된 상태 로드
|
||||
@@ -148,8 +150,10 @@ class CharacterRollService {
|
||||
// - 무료 유저: 1회 (광고 시청 필요)
|
||||
_undoRemaining = _isPaidUser ? maxUndoPaidUser : maxUndoFreeUser;
|
||||
|
||||
debugPrint('[CharacterRollService] Rolled: remaining=$_rollsRemaining, '
|
||||
'history=${_rollHistory.length}, undo=$_undoRemaining');
|
||||
debugPrint(
|
||||
'[CharacterRollService] Rolled: remaining=$_rollsRemaining, '
|
||||
'history=${_rollHistory.length}, undo=$_undoRemaining',
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -214,8 +218,10 @@ class CharacterRollService {
|
||||
final snapshot = _rollHistory.removeAt(0);
|
||||
_undoRemaining--;
|
||||
|
||||
debugPrint('[CharacterRollService] Undo (paid): '
|
||||
'remaining=$_undoRemaining, history=${_rollHistory.length}');
|
||||
debugPrint(
|
||||
'[CharacterRollService] Undo (paid): '
|
||||
'remaining=$_undoRemaining, history=${_rollHistory.length}',
|
||||
);
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
@@ -241,8 +247,10 @@ class CharacterRollService {
|
||||
);
|
||||
|
||||
if (adResult == AdResult.completed || adResult == AdResult.debugSkipped) {
|
||||
debugPrint('[CharacterRollService] Undo (free with ad): '
|
||||
'remaining=$_undoRemaining, history=${_rollHistory.length}');
|
||||
debugPrint(
|
||||
'[CharacterRollService] Undo (free with ad): '
|
||||
'remaining=$_undoRemaining, history=${_rollHistory.length}',
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,9 @@ class ChestService {
|
||||
rarity: rarity,
|
||||
);
|
||||
|
||||
debugPrint('[ChestService] Equipment reward: ${item.name} (${rarity.name})');
|
||||
debugPrint(
|
||||
'[ChestService] Equipment reward: ${item.name} (${rarity.name})',
|
||||
);
|
||||
return ChestReward.equipment(item);
|
||||
}
|
||||
|
||||
@@ -132,7 +134,10 @@ class ChestService {
|
||||
ChestReward _generateGoldReward(int playerLevel) {
|
||||
final baseGold = playerLevel * _goldPerLevel;
|
||||
final variance = _rng.nextInt(_goldVariance * 2 + 1) - _goldVariance;
|
||||
final gold = (baseGold + (baseGold * variance / 100)).round().clamp(10, 99999);
|
||||
final gold = (baseGold + (baseGold * variance / 100)).round().clamp(
|
||||
10,
|
||||
99999,
|
||||
);
|
||||
|
||||
debugPrint('[ChestService] Gold reward: $gold');
|
||||
return ChestReward.gold(gold);
|
||||
@@ -142,7 +147,10 @@ class ChestService {
|
||||
ChestReward _generateExperienceReward(int playerLevel) {
|
||||
final baseExp = playerLevel * _expPerLevel;
|
||||
final variance = _rng.nextInt(_expVariance * 2 + 1) - _expVariance;
|
||||
final exp = (baseExp + (baseExp * variance / 100)).round().clamp(10, 999999);
|
||||
final exp = (baseExp + (baseExp * variance / 100)).round().clamp(
|
||||
10,
|
||||
999999,
|
||||
);
|
||||
|
||||
debugPrint('[ChestService] Experience reward: $exp');
|
||||
return ChestReward.experience(exp);
|
||||
|
||||
@@ -210,7 +210,8 @@ class CombatTickService {
|
||||
MonsterCombatStats monsterStats,
|
||||
int totalDamageDealt,
|
||||
List<CombatEvent> events,
|
||||
}) _processDotTicks({
|
||||
})
|
||||
_processDotTicks({
|
||||
required List<DotEffect> activeDoTs,
|
||||
required MonsterCombatStats monsterStats,
|
||||
required int elapsedMs,
|
||||
@@ -272,7 +273,8 @@ class CombatTickService {
|
||||
int lastPotionUsedMs,
|
||||
PotionInventory potionInventory,
|
||||
List<CombatEvent> events,
|
||||
})? _tryEmergencyPotion({
|
||||
})?
|
||||
_tryEmergencyPotion({
|
||||
required CombatStats playerStats,
|
||||
required PotionInventory potionInventory,
|
||||
required int lastPotionUsedMs,
|
||||
@@ -371,7 +373,8 @@ class CombatTickService {
|
||||
int totalDamageDealt,
|
||||
List<CombatEvent> events,
|
||||
bool isFirstPlayerAttack,
|
||||
}) _processPlayerAttack({
|
||||
})
|
||||
_processPlayerAttack({
|
||||
required GameState state,
|
||||
required CombatStats playerStats,
|
||||
required MonsterCombatStats monsterStats,
|
||||
@@ -508,8 +511,11 @@ class CombatTickService {
|
||||
newSkillSystem = skillResult.updatedSkillSystem.startGlobalCooldown();
|
||||
|
||||
if (skillResult.debuffEffect != null) {
|
||||
newActiveBuffs = newActiveBuffs
|
||||
.where((d) => d.effect.id != skillResult.debuffEffect!.effect.id)
|
||||
newActiveBuffs =
|
||||
newActiveBuffs
|
||||
.where(
|
||||
(d) => d.effect.id != skillResult.debuffEffect!.effect.id,
|
||||
)
|
||||
.toList()
|
||||
..add(skillResult.debuffEffect!);
|
||||
}
|
||||
@@ -601,11 +607,8 @@ class CombatTickService {
|
||||
}
|
||||
|
||||
/// 몬스터 공격 처리
|
||||
({
|
||||
CombatStats playerStats,
|
||||
int totalDamageTaken,
|
||||
List<CombatEvent> events,
|
||||
}) _processMonsterAttack({
|
||||
({CombatStats playerStats, int totalDamageTaken, List<CombatEvent> events})
|
||||
_processMonsterAttack({
|
||||
required CombatStats playerStats,
|
||||
required MonsterCombatStats monsterStats,
|
||||
required List<ActiveBuff> activeDebuffs,
|
||||
|
||||
@@ -131,9 +131,7 @@ class IAPService {
|
||||
final response = await _iap.queryProductDetails(IAPProductIds.all);
|
||||
|
||||
if (response.notFoundIDs.isNotEmpty) {
|
||||
debugPrint(
|
||||
'[IAPService] Products not found: ${response.notFoundIDs}',
|
||||
);
|
||||
debugPrint('[IAPService] Products not found: ${response.notFoundIDs}');
|
||||
}
|
||||
|
||||
for (final product in response.productDetails) {
|
||||
@@ -238,14 +236,10 @@ class IAPService {
|
||||
}
|
||||
|
||||
// 구매 요청
|
||||
final purchaseParam = PurchaseParam(
|
||||
productDetails: _removeAdsProduct!,
|
||||
);
|
||||
final purchaseParam = PurchaseParam(productDetails: _removeAdsProduct!);
|
||||
|
||||
try {
|
||||
final success = await _iap.buyNonConsumable(
|
||||
purchaseParam: purchaseParam,
|
||||
);
|
||||
final success = await _iap.buyNonConsumable(purchaseParam: purchaseParam);
|
||||
debugPrint('[IAPService] Purchase initiated: $success');
|
||||
return success ? IAPResult.success : IAPResult.failed;
|
||||
} catch (e) {
|
||||
|
||||
@@ -164,8 +164,9 @@ class ItemService {
|
||||
final magDef = hasMagDef ? (def * 0.7).round() : 0;
|
||||
|
||||
// HP 보너스 (Uncommon 이상)
|
||||
final hpBonus =
|
||||
rarity.index >= ItemRarity.uncommon.index ? baseValue ~/ 3 : 0;
|
||||
final hpBonus = rarity.index >= ItemRarity.uncommon.index
|
||||
? baseValue ~/ 3
|
||||
: 0;
|
||||
|
||||
// CON 보너스 (Rare 이상)
|
||||
final conBonus = rarity.index >= ItemRarity.rare.index ? rarity.index : 0;
|
||||
@@ -273,8 +274,7 @@ class ItemService {
|
||||
EquipmentSlot.greaves => ItemStats(
|
||||
def: def,
|
||||
hpBonus: rarity.index >= ItemRarity.rare.index ? baseValue ~/ 3 : 0,
|
||||
conBonus:
|
||||
rarity.index >= ItemRarity.rare.index ? rarity.index - 1 : 0,
|
||||
conBonus: rarity.index >= ItemRarity.rare.index ? rarity.index - 1 : 0,
|
||||
evasion: rarity.index >= ItemRarity.epic.index ? 0.01 : 0.0,
|
||||
),
|
||||
|
||||
|
||||
@@ -9,10 +9,7 @@ import 'package:asciineverdie/src/core/util/pq_logic.dart' as pq_logic;
|
||||
|
||||
/// 판매 처리 결과
|
||||
class SellResult {
|
||||
const SellResult({
|
||||
required this.state,
|
||||
required this.continuesSelling,
|
||||
});
|
||||
const SellResult({required this.state, required this.continuesSelling});
|
||||
|
||||
final GameState state;
|
||||
final bool continuesSelling;
|
||||
|
||||
@@ -193,7 +193,11 @@ class ProgressService {
|
||||
}
|
||||
|
||||
// 5. 시장/판매/구매 태스크 완료 처리
|
||||
final marketResult = _handleMarketTaskCompletion(nextState, progress, queue);
|
||||
final marketResult = _handleMarketTaskCompletion(
|
||||
nextState,
|
||||
progress,
|
||||
queue,
|
||||
);
|
||||
if (marketResult.earlyReturn != null) return marketResult.earlyReturn!;
|
||||
nextState = marketResult.state;
|
||||
progress = marketResult.progress;
|
||||
@@ -209,7 +213,11 @@ class ProgressService {
|
||||
|
||||
// 7. 퀘스트 진행 처리
|
||||
final questResult = _handleQuestProgress(
|
||||
nextState, progress, queue, gain, incrementSeconds,
|
||||
nextState,
|
||||
progress,
|
||||
queue,
|
||||
gain,
|
||||
incrementSeconds,
|
||||
);
|
||||
nextState = questResult.state;
|
||||
progress = questResult.progress;
|
||||
@@ -217,9 +225,7 @@ class ProgressService {
|
||||
questDone = questResult.completed;
|
||||
|
||||
// 8. 플롯 진행 및 Act Boss 소환 처리
|
||||
progress = _handlePlotProgress(
|
||||
nextState, progress, gain, incrementSeconds,
|
||||
);
|
||||
progress = _handlePlotProgress(nextState, progress, gain, incrementSeconds);
|
||||
|
||||
// 9. 다음 태스크 디큐/생성
|
||||
final dequeueResult = _handleTaskDequeue(nextState, progress, queue);
|
||||
@@ -341,7 +347,8 @@ class ProgressService {
|
||||
ProgressState progress,
|
||||
QueueState queue,
|
||||
ProgressTickResult? earlyReturn,
|
||||
}) _handleKillTaskCompletion(
|
||||
})
|
||||
_handleKillTaskCompletion(
|
||||
GameState state,
|
||||
ProgressState progress,
|
||||
QueueState queue,
|
||||
@@ -358,8 +365,9 @@ class ProgressService {
|
||||
|
||||
final klass = ClassData.findById(nextState.traits.classId);
|
||||
if (klass != null) {
|
||||
final postCombatHealRate =
|
||||
klass.getPassiveValue(ClassPassiveType.postCombatHeal);
|
||||
final postCombatHealRate = klass.getPassiveValue(
|
||||
ClassPassiveType.postCombatHeal,
|
||||
);
|
||||
if (postCombatHealRate > 0) {
|
||||
healAmount += (maxHp * postCombatHealRate).round();
|
||||
}
|
||||
@@ -446,7 +454,8 @@ class ProgressService {
|
||||
ProgressState progress,
|
||||
QueueState queue,
|
||||
ProgressTickResult? earlyReturn,
|
||||
}) _handleMarketTaskCompletion(
|
||||
})
|
||||
_handleMarketTaskCompletion(
|
||||
GameState state,
|
||||
ProgressState progress,
|
||||
QueueState queue,
|
||||
@@ -520,12 +529,8 @@ class ProgressService {
|
||||
}
|
||||
|
||||
/// 퀘스트 진행 처리
|
||||
({
|
||||
GameState state,
|
||||
ProgressState progress,
|
||||
QueueState queue,
|
||||
bool completed,
|
||||
}) _handleQuestProgress(
|
||||
({GameState state, ProgressState progress, QueueState queue, bool completed})
|
||||
_handleQuestProgress(
|
||||
GameState state,
|
||||
ProgressState progress,
|
||||
QueueState queue,
|
||||
@@ -603,7 +608,8 @@ class ProgressService {
|
||||
QueueState queue,
|
||||
bool actDone,
|
||||
bool gameComplete,
|
||||
}) _handleTaskDequeue(
|
||||
})
|
||||
_handleTaskDequeue(
|
||||
GameState state,
|
||||
ProgressState progress,
|
||||
QueueState queue,
|
||||
@@ -705,10 +711,7 @@ class ProgressService {
|
||||
4 * 1000,
|
||||
);
|
||||
final updatedProgress = taskResult.progress.copyWith(
|
||||
currentTask: TaskInfo(
|
||||
caption: taskResult.caption,
|
||||
type: TaskType.market,
|
||||
),
|
||||
currentTask: TaskInfo(caption: taskResult.caption, type: TaskType.market),
|
||||
currentCombat: null,
|
||||
);
|
||||
return (progress: updatedProgress, queue: queue);
|
||||
@@ -1171,8 +1174,10 @@ class ProgressService {
|
||||
final shouldLoseEquipment = roll < lossChancePercent;
|
||||
|
||||
// ignore: avoid_print
|
||||
print('[Death] Lv$level lossChance=$lossChancePercent% roll=$roll '
|
||||
'shouldLose=$shouldLoseEquipment');
|
||||
print(
|
||||
'[Death] Lv$level lossChance=$lossChancePercent% roll=$roll '
|
||||
'shouldLose=$shouldLoseEquipment',
|
||||
);
|
||||
|
||||
if (shouldLoseEquipment) {
|
||||
// 무기(슬롯 0)를 제외한 장착된 장비 중 1개를 제물로 삭제
|
||||
|
||||
@@ -346,7 +346,10 @@ class ResurrectionService {
|
||||
|
||||
// 해당 슬롯에 아이템 복원
|
||||
final slotIndex = lostSlot.index;
|
||||
final updatedEquipment = state.equipment.setItemByIndex(slotIndex, lostItem);
|
||||
final updatedEquipment = state.equipment.setItemByIndex(
|
||||
slotIndex,
|
||||
lostItem,
|
||||
);
|
||||
|
||||
// DeathInfo에서 상실 아이템 정보 제거 (복구 완료)
|
||||
final updatedDeathInfo = deathInfo.copyWith(
|
||||
|
||||
@@ -94,8 +94,10 @@ class ReturnRewardsService {
|
||||
// 보너스 상자 (광고 시청 시 동일 개수 추가)
|
||||
final bonusChestCount = chestCount;
|
||||
|
||||
debugPrint('[ReturnRewards] $hoursAway hours away, '
|
||||
'chests=$chestCount, bonus=$bonusChestCount, paid=$isPaidUser');
|
||||
debugPrint(
|
||||
'[ReturnRewards] $hoursAway hours away, '
|
||||
'chests=$chestCount, bonus=$bonusChestCount, paid=$isPaidUser',
|
||||
);
|
||||
|
||||
return ReturnChestReward(
|
||||
hoursAway: hoursAway,
|
||||
@@ -125,9 +127,14 @@ class ReturnRewardsService {
|
||||
/// [reward] 복귀 보상 데이터
|
||||
/// [playerLevel] 플레이어 레벨
|
||||
/// Returns: 오픈된 상자 보상 목록
|
||||
List<ChestReward> claimBasicReward(ReturnChestReward reward, int playerLevel) {
|
||||
List<ChestReward> claimBasicReward(
|
||||
ReturnChestReward reward,
|
||||
int playerLevel,
|
||||
) {
|
||||
if (!reward.hasReward) return [];
|
||||
debugPrint('[ReturnRewards] Basic reward claimed: ${reward.chestCount} chests');
|
||||
debugPrint(
|
||||
'[ReturnRewards] Basic reward claimed: ${reward.chestCount} chests',
|
||||
);
|
||||
return openChests(reward.chestCount, playerLevel);
|
||||
}
|
||||
|
||||
@@ -146,8 +153,10 @@ class ReturnRewardsService {
|
||||
|
||||
// 유료 유저는 무료 보너스
|
||||
if (IAPService.instance.isAdRemovalPurchased) {
|
||||
debugPrint('[ReturnRewards] Bonus claimed (paid user): '
|
||||
'${reward.bonusChestCount} chests');
|
||||
debugPrint(
|
||||
'[ReturnRewards] Bonus claimed (paid user): '
|
||||
'${reward.bonusChestCount} chests',
|
||||
);
|
||||
return openChests(reward.bonusChestCount, playerLevel);
|
||||
}
|
||||
|
||||
@@ -161,8 +170,10 @@ class ReturnRewardsService {
|
||||
);
|
||||
|
||||
if (adResult == AdResult.completed || adResult == AdResult.debugSkipped) {
|
||||
debugPrint('[ReturnRewards] Bonus claimed (free user with ad): '
|
||||
'${bonusRewards.length} chests');
|
||||
debugPrint(
|
||||
'[ReturnRewards] Bonus claimed (free user with ad): '
|
||||
'${bonusRewards.length} chests',
|
||||
);
|
||||
return bonusRewards;
|
||||
}
|
||||
|
||||
|
||||
@@ -57,46 +57,64 @@ class CombatStats with _$CombatStats {
|
||||
// 기본 스탯
|
||||
/// 힘: 물리 공격력 보정
|
||||
required int str,
|
||||
|
||||
/// 체력: HP, 방어력 보정
|
||||
required int con,
|
||||
|
||||
/// 민첩: 회피율, 크리티컬율, 명중률, 공격 속도
|
||||
required int dex,
|
||||
|
||||
/// 지능: 마법 공격력, MP
|
||||
required int intelligence,
|
||||
|
||||
/// 지혜: 마법 방어력, MP 회복
|
||||
required int wis,
|
||||
|
||||
/// 매력: 상점 가격, 드롭률 보정
|
||||
required int cha,
|
||||
// 파생 스탯 (전투용)
|
||||
/// 물리 공격력
|
||||
required int atk,
|
||||
|
||||
/// 물리 방어력
|
||||
required int def,
|
||||
|
||||
/// 마법 공격력
|
||||
required int magAtk,
|
||||
|
||||
/// 마법 방어력
|
||||
required int magDef,
|
||||
|
||||
/// 크리티컬 확률 (0.0 ~ 1.0)
|
||||
required double criRate,
|
||||
|
||||
/// 크리티컬 데미지 배율 (1.5 ~ 3.0)
|
||||
required double criDamage,
|
||||
|
||||
/// 회피율 (0.0 ~ 0.5)
|
||||
required double evasion,
|
||||
|
||||
/// 명중률 (0.8 ~ 1.0)
|
||||
required double accuracy,
|
||||
|
||||
/// 방패 방어율 (0.0 ~ 0.4)
|
||||
required double blockRate,
|
||||
|
||||
/// 무기로 쳐내기 확률 (0.0 ~ 0.3)
|
||||
required double parryRate,
|
||||
|
||||
/// 공격 딜레이 (밀리초)
|
||||
required int attackDelayMs,
|
||||
// 자원
|
||||
/// 최대 HP
|
||||
required int hpMax,
|
||||
|
||||
/// 현재 HP
|
||||
required int hpCurrent,
|
||||
|
||||
/// 최대 MP
|
||||
required int mpMax,
|
||||
|
||||
/// 현재 MP
|
||||
required int mpCurrent,
|
||||
}) = _CombatStats;
|
||||
|
||||
@@ -41,36 +41,52 @@ class ItemStats with _$ItemStats {
|
||||
const factory ItemStats({
|
||||
/// 물리 공격력 보정
|
||||
@Default(0) int atk,
|
||||
|
||||
/// 물리 방어력 보정
|
||||
@Default(0) int def,
|
||||
|
||||
/// 마법 공격력 보정
|
||||
@Default(0) int magAtk,
|
||||
|
||||
/// 마법 방어력 보정
|
||||
@Default(0) int magDef,
|
||||
|
||||
/// 크리티컬 확률 보정 (0.0 ~ 1.0)
|
||||
@Default(0.0) double criRate,
|
||||
|
||||
/// 회피율 보정 (0.0 ~ 1.0)
|
||||
@Default(0.0) double evasion,
|
||||
|
||||
/// 방패 방어율 (방패 전용, 0.0 ~ 1.0)
|
||||
@Default(0.0) double blockRate,
|
||||
|
||||
/// 무기 쳐내기 확률 (무기 전용, 0.0 ~ 1.0)
|
||||
@Default(0.0) double parryRate,
|
||||
|
||||
/// HP 보너스
|
||||
@Default(0) int hpBonus,
|
||||
|
||||
/// MP 보너스
|
||||
@Default(0) int mpBonus,
|
||||
|
||||
/// STR 보너스
|
||||
@Default(0) int strBonus,
|
||||
|
||||
/// CON 보너스
|
||||
@Default(0) int conBonus,
|
||||
|
||||
/// DEX 보너스
|
||||
@Default(0) int dexBonus,
|
||||
|
||||
/// INT 보너스
|
||||
@Default(0) int intBonus,
|
||||
|
||||
/// WIS 보너스
|
||||
@Default(0) int wisBonus,
|
||||
|
||||
/// CHA 보너스
|
||||
@Default(0) int chaBonus,
|
||||
|
||||
/// 무기 공격속도 (밀리초, 무기 전용)
|
||||
///
|
||||
/// 0이면 기본값(1000ms) 사용, 값이 클수록 느린 공격.
|
||||
|
||||
@@ -121,7 +121,8 @@ class MonetizationState with _$MonetizationState {
|
||||
List<Map<String, dynamic>>? _statsListToJson(List<Stats>? stats) {
|
||||
if (stats == null) return null;
|
||||
return stats
|
||||
.map((s) => {
|
||||
.map(
|
||||
(s) => {
|
||||
'str': s.str,
|
||||
'con': s.con,
|
||||
'dex': s.dex,
|
||||
@@ -130,7 +131,8 @@ List<Map<String, dynamic>>? _statsListToJson(List<Stats>? stats) {
|
||||
'cha': s.cha,
|
||||
'hpMax': s.hpMax,
|
||||
'mpMax': s.mpMax,
|
||||
})
|
||||
},
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
|
||||
@@ -62,9 +62,7 @@ class Potion {
|
||||
///
|
||||
/// 보유 물약 수량 관리 (쿨타임은 CombatState에서 관리)
|
||||
class PotionInventory {
|
||||
const PotionInventory({
|
||||
this.potions = const {},
|
||||
});
|
||||
const PotionInventory({this.potions = const {}});
|
||||
|
||||
/// 보유 물약 (물약 ID → 수량)
|
||||
final Map<String, int> potions;
|
||||
@@ -99,11 +97,7 @@ class PotionInventory {
|
||||
/// 빈 인벤토리
|
||||
static const empty = PotionInventory();
|
||||
|
||||
PotionInventory copyWith({
|
||||
Map<String, int>? potions,
|
||||
}) {
|
||||
return PotionInventory(
|
||||
potions: potions ?? this.potions,
|
||||
);
|
||||
PotionInventory copyWith({Map<String, int>? potions}) {
|
||||
return PotionInventory(potions: potions ?? this.potions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,10 +28,7 @@ class ChestReward {
|
||||
|
||||
/// 장비 보상 생성
|
||||
factory ChestReward.equipment(EquipmentItem item) {
|
||||
return ChestReward._(
|
||||
type: ChestRewardType.equipment,
|
||||
equipment: item,
|
||||
);
|
||||
return ChestReward._(type: ChestRewardType.equipment, equipment: item);
|
||||
}
|
||||
|
||||
/// 포션 보상 생성
|
||||
@@ -45,18 +42,12 @@ class ChestReward {
|
||||
|
||||
/// 골드 보상 생성
|
||||
factory ChestReward.gold(int amount) {
|
||||
return ChestReward._(
|
||||
type: ChestRewardType.gold,
|
||||
gold: amount,
|
||||
);
|
||||
return ChestReward._(type: ChestRewardType.gold, gold: amount);
|
||||
}
|
||||
|
||||
/// 경험치 보상 생성
|
||||
factory ChestReward.experience(int amount) {
|
||||
return ChestReward._(
|
||||
type: ChestRewardType.experience,
|
||||
experience: amount,
|
||||
);
|
||||
return ChestReward._(type: ChestRewardType.experience, experience: amount);
|
||||
}
|
||||
|
||||
/// 보상 타입
|
||||
|
||||
@@ -714,7 +714,10 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
}
|
||||
|
||||
/// 데스크톱 앱바
|
||||
PreferredSizeWidget _buildDesktopAppBar(BuildContext context, GameState state) {
|
||||
PreferredSizeWidget _buildDesktopAppBar(
|
||||
BuildContext context,
|
||||
GameState state,
|
||||
) {
|
||||
return AppBar(
|
||||
backgroundColor: RetroColors.darkBrown,
|
||||
title: Text(
|
||||
@@ -969,9 +972,7 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
// Potions (물약 인벤토리)
|
||||
_buildSectionHeader(game_l10n.uiPotions),
|
||||
Expanded(
|
||||
child: PotionInventoryPanel(
|
||||
inventory: state.potionInventory,
|
||||
),
|
||||
child: PotionInventoryPanel(inventory: state.potionInventory),
|
||||
),
|
||||
|
||||
// Encumbrance 바
|
||||
|
||||
@@ -472,8 +472,8 @@ class GameSessionController extends ChangeNotifier {
|
||||
|
||||
/// 속도 부스트 활성화 (광고 시청 후)
|
||||
Future<bool> activateSpeedBoost() async {
|
||||
final (success, updatedMonetization) =
|
||||
await _speedBoostManager.activateSpeedBoost(
|
||||
final (success, updatedMonetization) = await _speedBoostManager
|
||||
.activateSpeedBoost(
|
||||
loop: _loop,
|
||||
monetization: _monetization,
|
||||
currentElapsedMs: _state?.skillSystem.elapsedMs ?? 0,
|
||||
|
||||
@@ -308,15 +308,14 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
|
||||
// 핸들 바
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8, bottom: 4),
|
||||
child: Container(
|
||||
width: 60,
|
||||
height: 4,
|
||||
color: border,
|
||||
),
|
||||
child: Container(width: 60, height: 4, color: border),
|
||||
),
|
||||
// 헤더
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 8,
|
||||
),
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: RetroColors.panelBgOf(context),
|
||||
@@ -515,7 +514,8 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
|
||||
],
|
||||
|
||||
// === 디버그 도구 섹션 ===
|
||||
if (kDebugMode && widget.onCreateTestCharacter != null) ...[
|
||||
if (kDebugMode &&
|
||||
widget.onCreateTestCharacter != null) ...[
|
||||
const SizedBox(height: 16),
|
||||
RetroMenuSection(
|
||||
title: L10n.of(context).debugToolsTitle,
|
||||
@@ -526,7 +526,9 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
|
||||
icon: Icons.science,
|
||||
iconColor: RetroColors.warningOf(context),
|
||||
label: L10n.of(context).debugCreateTestCharacter,
|
||||
subtitle: L10n.of(context).debugCreateTestCharacterDesc,
|
||||
subtitle: L10n.of(
|
||||
context,
|
||||
).debugCreateTestCharacterDesc,
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
_showTestCharacterDialog(context);
|
||||
|
||||
@@ -9,9 +9,8 @@ import 'package:asciineverdie/src/core/storage/statistics_storage.dart';
|
||||
/// 세션 통계와 누적 통계를 관리하고, 게임 상태 변화에 따라
|
||||
/// 통계를 자동 업데이트합니다.
|
||||
class GameStatisticsManager {
|
||||
GameStatisticsManager({
|
||||
StatisticsStorage? statisticsStorage,
|
||||
}) : _statisticsStorage = statisticsStorage ?? StatisticsStorage();
|
||||
GameStatisticsManager({StatisticsStorage? statisticsStorage})
|
||||
: _statisticsStorage = statisticsStorage ?? StatisticsStorage();
|
||||
|
||||
final StatisticsStorage _statisticsStorage;
|
||||
|
||||
|
||||
@@ -11,9 +11,8 @@ import 'package:flutter/foundation.dart';
|
||||
///
|
||||
/// 게임 클리어 시 캐릭터 등록, 테스트 캐릭터 생성 등을 담당합니다.
|
||||
class HallOfFameManager {
|
||||
HallOfFameManager({
|
||||
HallOfFameStorage? hallOfFameStorage,
|
||||
}) : _hallOfFameStorage = hallOfFameStorage ?? HallOfFameStorage();
|
||||
HallOfFameManager({HallOfFameStorage? hallOfFameStorage})
|
||||
: _hallOfFameStorage = hallOfFameStorage ?? HallOfFameStorage();
|
||||
|
||||
final HallOfFameStorage _hallOfFameStorage;
|
||||
|
||||
|
||||
@@ -107,7 +107,8 @@ class ResurrectionManager {
|
||||
updatedMonetization = monetization.copyWith(autoReviveEndMs: buffEndMs);
|
||||
|
||||
debugPrint(
|
||||
'[Resurrection] Ad revive complete, auto-revive buff until $buffEndMs ms');
|
||||
'[Resurrection] Ad revive complete, auto-revive buff until $buffEndMs ms',
|
||||
);
|
||||
}
|
||||
|
||||
// 유료 유저는 광고 없이 부활
|
||||
|
||||
@@ -49,8 +49,10 @@ class ReturnRewardsManager {
|
||||
|
||||
if (reward.hasReward) {
|
||||
_pendingReturnReward = reward;
|
||||
debugPrint('[ReturnRewards] Reward available: ${reward.chestCount} chests, '
|
||||
'${reward.hoursAway} hours away');
|
||||
debugPrint(
|
||||
'[ReturnRewards] Reward available: ${reward.chestCount} chests, '
|
||||
'${reward.hoursAway} hours away',
|
||||
);
|
||||
|
||||
// UI에서 다이얼로그 표시를 위해 콜백 호출
|
||||
// startNew 후에 호출하도록 딜레이
|
||||
@@ -91,11 +93,13 @@ class ReturnRewardsManager {
|
||||
loop?.replaceState(updatedState); // ProgressLoop 상태도 업데이트
|
||||
|
||||
// 저장
|
||||
unawaited(saveManager.saveState(
|
||||
unawaited(
|
||||
saveManager.saveState(
|
||||
updatedState,
|
||||
cheatsEnabled: cheatsEnabled,
|
||||
monetization: monetization,
|
||||
));
|
||||
),
|
||||
);
|
||||
|
||||
_pendingReturnReward = null;
|
||||
|
||||
@@ -129,18 +133,19 @@ class ReturnRewardsManager {
|
||||
reward.equipment!.itemWeight > currentItem.itemWeight) {
|
||||
debugPrint('[ReturnRewards] Equipped: ${reward.equipment!.name}');
|
||||
return state.copyWith(
|
||||
equipment: state.equipment.setItemByIndex(
|
||||
slotIndex,
|
||||
reward.equipment!,
|
||||
),
|
||||
equipment: state.equipment.setItemByIndex(slotIndex, reward.equipment!),
|
||||
);
|
||||
}
|
||||
|
||||
// 더 좋지 않으면 판매 (골드로 변환)
|
||||
final sellPrice =
|
||||
(reward.equipment!.level * 50 * 0.3).round().clamp(1, 99999);
|
||||
debugPrint('[ReturnRewards] Sold: ${reward.equipment!.name} '
|
||||
'for $sellPrice gold');
|
||||
final sellPrice = (reward.equipment!.level * 50 * 0.3).round().clamp(
|
||||
1,
|
||||
99999,
|
||||
);
|
||||
debugPrint(
|
||||
'[ReturnRewards] Sold: ${reward.equipment!.name} '
|
||||
'for $sellPrice gold',
|
||||
);
|
||||
return state.copyWith(
|
||||
inventory: state.inventory.copyWith(
|
||||
gold: state.inventory.gold + sellPrice,
|
||||
@@ -152,8 +157,10 @@ class ReturnRewardsManager {
|
||||
GameState _applyPotionReward(GameState state, ChestReward reward) {
|
||||
if (reward.potionId == null) return state;
|
||||
|
||||
debugPrint('[ReturnRewards] Added potion: ${reward.potionId} '
|
||||
'x${reward.potionCount}');
|
||||
debugPrint(
|
||||
'[ReturnRewards] Added potion: ${reward.potionId} '
|
||||
'x${reward.potionCount}',
|
||||
);
|
||||
return state.copyWith(
|
||||
potionInventory: state.potionInventory.addPotion(
|
||||
reward.potionId!,
|
||||
|
||||
@@ -52,7 +52,10 @@ class SpeedBoostManager {
|
||||
int get speedBoostDuration => _speedBoostDuration;
|
||||
|
||||
/// 속도 부스트 남은 시간 (초) - 게임 시간(elapsedMs) 기준 계산
|
||||
int getRemainingSeconds(MonetizationState monetization, int currentElapsedMs) {
|
||||
int getRemainingSeconds(
|
||||
MonetizationState monetization,
|
||||
int currentElapsedMs,
|
||||
) {
|
||||
if (!_isSpeedBoostActive) return 0;
|
||||
final endMs = monetization.speedBoostEndMs;
|
||||
if (endMs == null) return 0;
|
||||
@@ -203,7 +206,10 @@ class SpeedBoostManager {
|
||||
if (_isSpeedBoostActive) {
|
||||
// 부스트 상태: 부스트 배속만 사용, 기본 배속 저장
|
||||
savedSpeedMultiplier = baseSpeed;
|
||||
return (speeds: [speedBoostMultiplier], initialSpeed: speedBoostMultiplier);
|
||||
return (
|
||||
speeds: [speedBoostMultiplier],
|
||||
initialSpeed: speedBoostMultiplier,
|
||||
);
|
||||
}
|
||||
// 일반 상태: 기본 배속 사용
|
||||
return (speeds: baseAvailableSpeeds, initialSpeed: baseSpeed);
|
||||
|
||||
@@ -37,9 +37,7 @@ class InventoryPage extends StatelessWidget {
|
||||
_buildSectionHeader(context, l10n.uiPotions),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: PotionInventoryPanel(
|
||||
inventory: potionInventory,
|
||||
),
|
||||
child: PotionInventoryPanel(inventory: potionInventory),
|
||||
),
|
||||
|
||||
// 무게 (Encumbrance)
|
||||
|
||||
@@ -147,7 +147,11 @@ class _SkillTile extends StatelessWidget {
|
||||
if (isOnCooldown)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(right: 8),
|
||||
child: Icon(Icons.hourglass_empty, size: 14, color: Colors.orange),
|
||||
child: Icon(
|
||||
Icons.hourglass_empty,
|
||||
size: 14,
|
||||
color: Colors.orange,
|
||||
),
|
||||
),
|
||||
_RankBadge(rank: rank),
|
||||
],
|
||||
@@ -273,10 +277,12 @@ class _SkillStatsGrid extends StatelessWidget {
|
||||
|
||||
// 공통: MP, 쿨타임
|
||||
entries.add(_StatEntry(l10n.skillMpCost, '${skill.mpCost}'));
|
||||
entries.add(_StatEntry(
|
||||
entries.add(
|
||||
_StatEntry(
|
||||
l10n.skillCooldown,
|
||||
'${(skill.cooldownMs / 1000).toStringAsFixed(1)}${l10n.skillSeconds}',
|
||||
));
|
||||
),
|
||||
);
|
||||
|
||||
// 타입별 스탯 추가
|
||||
switch (skill.type) {
|
||||
@@ -309,33 +315,40 @@ class _SkillStatsGrid extends StatelessWidget {
|
||||
|
||||
// DOT 정보
|
||||
if (skill.isDot && skill.baseDotDamage != null) {
|
||||
final dotDps = skill.baseDotDamage! *
|
||||
final dotDps =
|
||||
skill.baseDotDamage! *
|
||||
(skill.baseDotDurationMs! / skill.baseDotTickMs!);
|
||||
entries.add(_StatEntry(l10n.skillDot, '${dotDps.round()}'));
|
||||
}
|
||||
|
||||
// HP 흡수
|
||||
if (skill.lifestealPercent > 0) {
|
||||
entries.add(_StatEntry(
|
||||
entries.add(
|
||||
_StatEntry(
|
||||
l10n.skillLifesteal,
|
||||
'${(skill.lifestealPercent * 100).round()}%',
|
||||
));
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 방어 무시
|
||||
if (skill.targetDefReduction > 0) {
|
||||
entries.add(_StatEntry(
|
||||
entries.add(
|
||||
_StatEntry(
|
||||
l10n.skillDefPen,
|
||||
'${(skill.targetDefReduction * 100).round()}%',
|
||||
));
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 자해 데미지
|
||||
if (skill.selfDamagePercent > 0) {
|
||||
entries.add(_StatEntry(
|
||||
entries.add(
|
||||
_StatEntry(
|
||||
l10n.skillSelfDmg,
|
||||
'${(skill.selfDamagePercent * 100).round()}%',
|
||||
));
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,10 +360,12 @@ class _SkillStatsGrid extends StatelessWidget {
|
||||
|
||||
// % 회복
|
||||
if (skill.healPercent > 0) {
|
||||
entries.add(_StatEntry(
|
||||
entries.add(
|
||||
_StatEntry(
|
||||
l10n.skillHealPercent,
|
||||
'${(skill.healPercent * 100).round()}%',
|
||||
));
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// MP 회복
|
||||
@@ -361,10 +376,12 @@ class _SkillStatsGrid extends StatelessWidget {
|
||||
// 부가 버프
|
||||
if (skill.buff != null) {
|
||||
final buff = skill.buff!;
|
||||
entries.add(_StatEntry(
|
||||
entries.add(
|
||||
_StatEntry(
|
||||
l10n.skillBuffDuration,
|
||||
'${(buff.durationMs / 1000).round()}${l10n.skillSeconds}',
|
||||
));
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,39 +390,49 @@ class _SkillStatsGrid extends StatelessWidget {
|
||||
final buff = skill.buff!;
|
||||
|
||||
// 지속시간
|
||||
entries.add(_StatEntry(
|
||||
entries.add(
|
||||
_StatEntry(
|
||||
l10n.skillBuffDuration,
|
||||
'${(buff.durationMs / 1000).round()}${l10n.skillSeconds}',
|
||||
));
|
||||
),
|
||||
);
|
||||
|
||||
// 각 보정치
|
||||
if (buff.atkModifier != 0) {
|
||||
final sign = buff.atkModifier > 0 ? '+' : '';
|
||||
entries.add(_StatEntry(
|
||||
entries.add(
|
||||
_StatEntry(
|
||||
l10n.skillAtkMod,
|
||||
'$sign${(buff.atkModifier * 100).round()}%',
|
||||
));
|
||||
),
|
||||
);
|
||||
}
|
||||
if (buff.defModifier != 0) {
|
||||
final sign = buff.defModifier > 0 ? '+' : '';
|
||||
entries.add(_StatEntry(
|
||||
entries.add(
|
||||
_StatEntry(
|
||||
l10n.skillDefMod,
|
||||
'$sign${(buff.defModifier * 100).round()}%',
|
||||
));
|
||||
),
|
||||
);
|
||||
}
|
||||
if (buff.criRateModifier != 0) {
|
||||
final sign = buff.criRateModifier > 0 ? '+' : '';
|
||||
entries.add(_StatEntry(
|
||||
entries.add(
|
||||
_StatEntry(
|
||||
l10n.skillCriMod,
|
||||
'$sign${(buff.criRateModifier * 100).round()}%',
|
||||
));
|
||||
),
|
||||
);
|
||||
}
|
||||
if (buff.evasionModifier != 0) {
|
||||
final sign = buff.evasionModifier > 0 ? '+' : '';
|
||||
entries.add(_StatEntry(
|
||||
entries.add(
|
||||
_StatEntry(
|
||||
l10n.skillEvaMod,
|
||||
'$sign${(buff.evasionModifier * 100).round()}%',
|
||||
));
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,23 +441,23 @@ class _SkillStatsGrid extends StatelessWidget {
|
||||
final buff = skill.buff!;
|
||||
|
||||
// 지속시간
|
||||
entries.add(_StatEntry(
|
||||
entries.add(
|
||||
_StatEntry(
|
||||
l10n.skillBuffDuration,
|
||||
'${(buff.durationMs / 1000).round()}${l10n.skillSeconds}',
|
||||
));
|
||||
),
|
||||
);
|
||||
|
||||
// 디버프 효과 (보통 음수)
|
||||
if (buff.atkModifier != 0) {
|
||||
entries.add(_StatEntry(
|
||||
l10n.skillAtkMod,
|
||||
'${(buff.atkModifier * 100).round()}%',
|
||||
));
|
||||
entries.add(
|
||||
_StatEntry(l10n.skillAtkMod, '${(buff.atkModifier * 100).round()}%'),
|
||||
);
|
||||
}
|
||||
if (buff.defModifier != 0) {
|
||||
entries.add(_StatEntry(
|
||||
l10n.skillDefMod,
|
||||
'${(buff.defModifier * 100).round()}%',
|
||||
));
|
||||
entries.add(
|
||||
_StatEntry(l10n.skillDefMod, '${(buff.defModifier * 100).round()}%'),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,7 +333,8 @@ class DeathOverlay extends StatelessWidget {
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '[${_getSlotName(deathInfo.lostItemSlot)}] ',
|
||||
text:
|
||||
'[${_getSlotName(deathInfo.lostItemSlot)}] ',
|
||||
style: TextStyle(color: muted),
|
||||
),
|
||||
TextSpan(
|
||||
@@ -485,7 +486,10 @@ class DeathOverlay extends StatelessWidget {
|
||||
border: Border(
|
||||
top: BorderSide(color: gold, width: 3),
|
||||
left: BorderSide(color: gold, width: 3),
|
||||
bottom: BorderSide(color: goldDark.withValues(alpha: 0.8), width: 3),
|
||||
bottom: BorderSide(
|
||||
color: goldDark.withValues(alpha: 0.8),
|
||||
width: 3,
|
||||
),
|
||||
right: BorderSide(color: goldDark.withValues(alpha: 0.8), width: 3),
|
||||
),
|
||||
),
|
||||
@@ -495,10 +499,7 @@ class DeathOverlay extends StatelessWidget {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'✨',
|
||||
style: TextStyle(fontSize: 20, color: gold),
|
||||
),
|
||||
Text('✨', style: TextStyle(fontSize: 20, color: gold)),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
l10n.deathAdRevive.toUpperCase(),
|
||||
@@ -551,7 +552,8 @@ class DeathOverlay extends StatelessWidget {
|
||||
_buildBenefitRow(
|
||||
context,
|
||||
icon: '🔄',
|
||||
text: '${l10n.deathAdReviveItem}: ${deathInfo.lostItemName}',
|
||||
text:
|
||||
'${l10n.deathAdReviveItem}: ${deathInfo.lostItemName}',
|
||||
color: itemRarityColor,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
|
||||
@@ -83,7 +83,9 @@ class RetroOptionItem extends StatelessWidget {
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? gold.withValues(alpha: 0.15) : Colors.transparent,
|
||||
color: isSelected
|
||||
? gold.withValues(alpha: 0.15)
|
||||
: Colors.transparent,
|
||||
border: Border.all(
|
||||
color: isSelected ? gold : border,
|
||||
width: isSelected ? 2 : 1,
|
||||
@@ -101,7 +103,9 @@ class RetroOptionItem extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 18,
|
||||
color: isSelected ? gold : RetroColors.textPrimaryOf(context),
|
||||
color: isSelected
|
||||
? gold
|
||||
: RetroColors.textPrimaryOf(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -300,7 +300,9 @@ class _EnhancedAnimationPanelState extends State<EnhancedAnimationPanel>
|
||||
child: _buildBuffChip(
|
||||
icon: '⚡',
|
||||
label: '${widget.adSpeedMultiplier}x',
|
||||
remainingMs: widget.isPaidUser ? -1 : _speedBoostRemainingMs,
|
||||
remainingMs: widget.isPaidUser
|
||||
? -1
|
||||
: _speedBoostRemainingMs,
|
||||
color: Colors.orange,
|
||||
isPermanent: widget.isPaidUser,
|
||||
),
|
||||
@@ -401,7 +403,9 @@ class _EnhancedAnimationPanelState extends State<EnhancedAnimationPanel>
|
||||
child: SizedBox.expand(
|
||||
child: LinearProgressIndicator(
|
||||
value: ratio.clamp(0.0, 1.0),
|
||||
backgroundColor: Colors.red.withValues(alpha: 0.2),
|
||||
backgroundColor: Colors.red.withValues(
|
||||
alpha: 0.2,
|
||||
),
|
||||
valueColor: AlwaysStoppedAnimation(
|
||||
isLow ? Colors.red : Colors.red.shade600,
|
||||
),
|
||||
@@ -502,7 +506,9 @@ class _EnhancedAnimationPanelState extends State<EnhancedAnimationPanel>
|
||||
child: SizedBox.expand(
|
||||
child: LinearProgressIndicator(
|
||||
value: ratio.clamp(0.0, 1.0),
|
||||
backgroundColor: Colors.blue.withValues(alpha: 0.2),
|
||||
backgroundColor: Colors.blue.withValues(
|
||||
alpha: 0.2,
|
||||
),
|
||||
valueColor: AlwaysStoppedAnimation(
|
||||
Colors.blue.shade600,
|
||||
),
|
||||
@@ -619,7 +625,10 @@ class _EnhancedAnimationPanelState extends State<EnhancedAnimationPanel>
|
||||
color: Colors.black.withValues(alpha: 0.8),
|
||||
blurRadius: 2,
|
||||
),
|
||||
const Shadow(color: Colors.black, blurRadius: 4),
|
||||
const Shadow(
|
||||
color: Colors.black,
|
||||
blurRadius: 4,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -783,8 +792,9 @@ class _EnhancedAnimationPanelState extends State<EnhancedAnimationPanel>
|
||||
),
|
||||
TextSpan(
|
||||
text: _getStatusMessage(),
|
||||
style:
|
||||
gradeColor != null ? TextStyle(color: gradeColor) : null,
|
||||
style: gradeColor != null
|
||||
? TextStyle(color: gradeColor)
|
||||
: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -844,10 +854,7 @@ class _EnhancedAnimationPanelState extends State<EnhancedAnimationPanel>
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
icon,
|
||||
style: TextStyle(fontSize: 12, color: color),
|
||||
),
|
||||
Text(icon, style: TextStyle(fontSize: 12, color: color)),
|
||||
if (label != null) ...[
|
||||
const SizedBox(width: 2),
|
||||
Text(
|
||||
|
||||
@@ -326,8 +326,10 @@ class _HpMpBarState extends State<HpMpBar> with TickerProviderStateMixin {
|
||||
height: 14,
|
||||
decoration: BoxDecoration(
|
||||
color: emptyColor.withValues(alpha: 0.3),
|
||||
border:
|
||||
Border.all(color: RetroColors.panelBorderOuter, width: 1),
|
||||
border: Border.all(
|
||||
color: RetroColors.panelBorderOuter,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: List.generate(segmentCount, (index) {
|
||||
@@ -341,10 +343,8 @@ class _HpMpBarState extends State<HpMpBar> with TickerProviderStateMixin {
|
||||
border: Border(
|
||||
right: index < segmentCount - 1
|
||||
? BorderSide(
|
||||
color:
|
||||
RetroColors.panelBorderOuter.withValues(
|
||||
alpha: 0.3,
|
||||
),
|
||||
color: RetroColors.panelBorderOuter
|
||||
.withValues(alpha: 0.3),
|
||||
width: 1,
|
||||
)
|
||||
: BorderSide.none,
|
||||
|
||||
@@ -4,11 +4,7 @@ import 'package:asciineverdie/src/shared/retro_colors.dart';
|
||||
|
||||
/// 메뉴 섹션 타이틀
|
||||
class RetroMenuSection extends StatelessWidget {
|
||||
const RetroMenuSection({
|
||||
super.key,
|
||||
required this.title,
|
||||
this.color,
|
||||
});
|
||||
const RetroMenuSection({super.key, required this.title, this.color});
|
||||
|
||||
final String title;
|
||||
final Color? color;
|
||||
@@ -182,10 +178,7 @@ class RetroSpeedChip extends StatelessWidget {
|
||||
if (isAdBased && !isSelected && !isDisabled)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 2),
|
||||
child: Text(
|
||||
'▶',
|
||||
style: TextStyle(fontSize: 7, color: warning),
|
||||
),
|
||||
child: Text('▶', style: TextStyle(fontSize: 7, color: warning)),
|
||||
),
|
||||
Text(
|
||||
'${speed}x',
|
||||
|
||||
@@ -9,10 +9,7 @@ import 'package:asciineverdie/src/core/model/potion.dart';
|
||||
/// 보유 중인 물약 목록과 수량을 표시.
|
||||
/// HP 물약은 빨간색, MP 물약은 파란색으로 구분.
|
||||
class PotionInventoryPanel extends StatelessWidget {
|
||||
const PotionInventoryPanel({
|
||||
super.key,
|
||||
required this.inventory,
|
||||
});
|
||||
const PotionInventoryPanel({super.key, required this.inventory});
|
||||
|
||||
final PotionInventory inventory;
|
||||
|
||||
@@ -38,10 +35,7 @@ class PotionInventoryPanel extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
itemBuilder: (context, index) {
|
||||
final entry = potionEntries[index];
|
||||
return _PotionRow(
|
||||
potion: entry.potion,
|
||||
quantity: entry.quantity,
|
||||
);
|
||||
return _PotionRow(potion: entry.potion, quantity: entry.quantity);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -82,10 +76,7 @@ class _PotionEntry {
|
||||
|
||||
/// 물약 행 위젯
|
||||
class _PotionRow extends StatelessWidget {
|
||||
const _PotionRow({
|
||||
required this.potion,
|
||||
required this.quantity,
|
||||
});
|
||||
const _PotionRow({required this.potion, required this.quantity});
|
||||
|
||||
final Potion potion;
|
||||
final int quantity;
|
||||
|
||||
@@ -264,7 +264,8 @@ class _ReturnRewardsDialogState extends State<ReturnRewardsDialog>
|
||||
return Transform.translate(
|
||||
offset: isOpening
|
||||
? Offset(
|
||||
_shakeAnimation.value * 2 *
|
||||
_shakeAnimation.value *
|
||||
2 *
|
||||
((_animController.value * 10).round() % 2 == 0
|
||||
? 1
|
||||
: -1),
|
||||
@@ -365,10 +366,7 @@ class _ReturnRewardsDialogState extends State<ReturnRewardsDialog>
|
||||
count,
|
||||
(index) => Text(
|
||||
'📦',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
color: enabled ? null : muted,
|
||||
),
|
||||
style: TextStyle(fontSize: 24, color: enabled ? null : muted),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -387,7 +385,9 @@ class _ReturnRewardsDialogState extends State<ReturnRewardsDialog>
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: rewards.map((reward) => _buildRewardItem(context, reward)).toList(),
|
||||
children: rewards
|
||||
.map((reward) => _buildRewardItem(context, reward))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -65,10 +65,7 @@ class SpeedBoostButton extends StatelessWidget {
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'⚡',
|
||||
style: TextStyle(fontSize: 18, color: expColor),
|
||||
),
|
||||
Text('⚡', style: TextStyle(fontSize: 18, color: expColor)),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'${boostMultiplier}x',
|
||||
@@ -113,10 +110,7 @@ class SpeedBoostButton extends StatelessWidget {
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'⚡',
|
||||
style: TextStyle(fontSize: 18, color: gold),
|
||||
),
|
||||
Text('⚡', style: TextStyle(fontSize: 18, color: gold)),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'${boostMultiplier}x',
|
||||
@@ -130,10 +124,7 @@ class SpeedBoostButton extends StatelessWidget {
|
||||
if (!isPaidUser) ...[
|
||||
const SizedBox(width: 6),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4,
|
||||
vertical: 2,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.2),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
|
||||
@@ -257,7 +257,11 @@ class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (!isPaidUser) ...[
|
||||
const Icon(Icons.play_circle, size: 14, color: RetroColors.gold),
|
||||
const Icon(
|
||||
Icons.play_circle,
|
||||
size: 14,
|
||||
color: RetroColors.gold,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
],
|
||||
Text(
|
||||
|
||||
@@ -105,8 +105,9 @@ class _ClassInfo extends StatelessWidget {
|
||||
final percent = (passive.value * 100).round();
|
||||
return switch (passive.type) {
|
||||
ClassPassiveType.hpBonus => game_l10n.passiveHpBonus(percent),
|
||||
ClassPassiveType.physicalDamageBonus =>
|
||||
game_l10n.passivePhysicalBonus(percent),
|
||||
ClassPassiveType.physicalDamageBonus => game_l10n.passivePhysicalBonus(
|
||||
percent,
|
||||
),
|
||||
ClassPassiveType.defenseBonus => game_l10n.passiveDefenseBonus(percent),
|
||||
ClassPassiveType.magicDamageBonus => game_l10n.passiveMagicBonus(percent),
|
||||
ClassPassiveType.evasionBonus => game_l10n.passiveEvasionBonus(percent),
|
||||
|
||||
@@ -62,9 +62,15 @@ class StatsSection extends StatelessWidget {
|
||||
// 스탯 그리드
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: _StatTile(label: l10n.statStr, value: str)),
|
||||
Expanded(child: _StatTile(label: l10n.statCon, value: con)),
|
||||
Expanded(child: _StatTile(label: l10n.statDex, value: dex)),
|
||||
Expanded(
|
||||
child: _StatTile(label: l10n.statStr, value: str),
|
||||
),
|
||||
Expanded(
|
||||
child: _StatTile(label: l10n.statCon, value: con),
|
||||
),
|
||||
Expanded(
|
||||
child: _StatTile(label: l10n.statDex, value: dex),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
@@ -73,8 +79,12 @@ class StatsSection extends StatelessWidget {
|
||||
Expanded(
|
||||
child: _StatTile(label: l10n.statInt, value: intelligence),
|
||||
),
|
||||
Expanded(child: _StatTile(label: l10n.statWis, value: wis)),
|
||||
Expanded(child: _StatTile(label: l10n.statCha, value: cha)),
|
||||
Expanded(
|
||||
child: _StatTile(label: l10n.statWis, value: wis),
|
||||
),
|
||||
Expanded(
|
||||
child: _StatTile(label: l10n.statCha, value: cha),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
@@ -116,10 +126,7 @@ class StatsSection extends StatelessWidget {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
_UndoButton(
|
||||
canUndo: canUndo,
|
||||
onPressed: onUndo,
|
||||
),
|
||||
_UndoButton(canUndo: canUndo, onPressed: onUndo),
|
||||
const SizedBox(width: 16),
|
||||
_RollButton(
|
||||
canRoll: canRoll,
|
||||
@@ -222,11 +229,7 @@ class _UndoButton extends StatelessWidget {
|
||||
children: [
|
||||
// 무료 유저는 광고 아이콘 표시
|
||||
if (!isPaidUser && canUndo) ...[
|
||||
const Icon(
|
||||
Icons.play_circle,
|
||||
size: 14,
|
||||
color: RetroColors.gold,
|
||||
),
|
||||
const Icon(Icons.play_circle, size: 14, color: RetroColors.gold),
|
||||
const SizedBox(width: 4),
|
||||
],
|
||||
Icon(
|
||||
@@ -240,7 +243,9 @@ class _UndoButton extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 11,
|
||||
color: canUndo ? RetroColors.textLight : RetroColors.textDisabled,
|
||||
color: canUndo
|
||||
? RetroColors.textLight
|
||||
: RetroColors.textDisabled,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -9,7 +9,7 @@ void main() {
|
||||
group('playerAttackMonster', () {
|
||||
test('데미지 = (ATK * variation) - (DEF * 0.4)', () {
|
||||
// 고정 시드로 예측 가능한 결과
|
||||
final rng = DeterministicRandom( 42);
|
||||
final rng = DeterministicRandom(42);
|
||||
final calculator = CombatCalculator(rng: rng);
|
||||
|
||||
final player = CombatStats.empty().copyWith(
|
||||
@@ -49,7 +49,7 @@ void main() {
|
||||
|
||||
test('크리티컬 발동 시 데미지 배율 적용', () {
|
||||
// 크리티컬이 항상 발동하도록 criRate = 1.0
|
||||
final rng = DeterministicRandom( 123);
|
||||
final rng = DeterministicRandom(123);
|
||||
final calculator = CombatCalculator(rng: rng);
|
||||
|
||||
final player = CombatStats.empty().copyWith(
|
||||
@@ -87,7 +87,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('회피 발동 시 0 데미지', () {
|
||||
final rng = DeterministicRandom( 42);
|
||||
final rng = DeterministicRandom(42);
|
||||
final calculator = CombatCalculator(rng: rng);
|
||||
|
||||
final player = CombatStats.empty().copyWith(
|
||||
@@ -130,7 +130,7 @@ void main() {
|
||||
test('블록 발동 시 70% 감소', () {
|
||||
// 블록이 발동하는 시드 찾기
|
||||
// blockRate = 1.0으로 항상 블록
|
||||
final rng = DeterministicRandom( 99);
|
||||
final rng = DeterministicRandom(99);
|
||||
final calculator = CombatCalculator(rng: rng);
|
||||
|
||||
final monster = MonsterCombatStats(
|
||||
@@ -170,7 +170,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('패리 발동 시 50% 감소', () {
|
||||
final rng = DeterministicRandom( 77);
|
||||
final rng = DeterministicRandom(77);
|
||||
final calculator = CombatCalculator(rng: rng);
|
||||
|
||||
final monster = MonsterCombatStats(
|
||||
@@ -212,7 +212,7 @@ void main() {
|
||||
|
||||
group('estimateCombatDurationMs', () {
|
||||
test('범위 2000~30000ms 내 반환', () {
|
||||
final rng = DeterministicRandom( 42);
|
||||
final rng = DeterministicRandom(42);
|
||||
final calculator = CombatCalculator(rng: rng);
|
||||
|
||||
final player = CombatStats.empty().copyWith(
|
||||
@@ -247,7 +247,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('고레벨 몬스터는 더 긴 전투 시간', () {
|
||||
final rng = DeterministicRandom( 42);
|
||||
final rng = DeterministicRandom(42);
|
||||
final calculator = CombatCalculator(rng: rng);
|
||||
|
||||
final player = CombatStats.empty().copyWith(
|
||||
@@ -305,7 +305,7 @@ void main() {
|
||||
|
||||
group('evaluateDifficulty', () {
|
||||
test('범위 0.0~1.0 내 반환', () {
|
||||
final rng = DeterministicRandom( 42);
|
||||
final rng = DeterministicRandom(42);
|
||||
final calculator = CombatCalculator(rng: rng);
|
||||
|
||||
final player = CombatStats.empty().copyWith(
|
||||
|
||||
@@ -125,7 +125,10 @@ void main() {
|
||||
// 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.updatedPlayer.mpCurrent,
|
||||
equals(20),
|
||||
); // 50 - 30 (mpCost 30)
|
||||
expect(result.updatedMonster.hpCurrent, equals(315)); // 500 - 185
|
||||
});
|
||||
|
||||
@@ -349,10 +352,7 @@ void main() {
|
||||
|
||||
const skill = SkillData.memoryDump;
|
||||
// baseDotDamage: 10, baseDotDurationMs: 6000, baseDotTickMs: 1000
|
||||
final player = CombatStats.empty().copyWith(
|
||||
mpMax: 100,
|
||||
mpCurrent: 50,
|
||||
);
|
||||
final player = CombatStats.empty().copyWith(mpMax: 100, mpCurrent: 50);
|
||||
final skillSystem = SkillSystemState.empty().copyWith(elapsedMs: 5000);
|
||||
|
||||
final result = service.useDotSkill(
|
||||
@@ -379,10 +379,7 @@ void main() {
|
||||
final service = SkillService(rng: rng);
|
||||
|
||||
const skill = SkillData.memoryDump;
|
||||
final player = CombatStats.empty().copyWith(
|
||||
mpMax: 100,
|
||||
mpCurrent: 50,
|
||||
);
|
||||
final player = CombatStats.empty().copyWith(mpMax: 100, mpCurrent: 50);
|
||||
final skillSystem = SkillSystemState.empty().copyWith(elapsedMs: 5000);
|
||||
|
||||
final result = service.useDotSkill(
|
||||
@@ -403,10 +400,7 @@ void main() {
|
||||
final service = SkillService(rng: rng);
|
||||
|
||||
const skill = SkillData.memoryDump;
|
||||
final player = CombatStats.empty().copyWith(
|
||||
mpMax: 100,
|
||||
mpCurrent: 50,
|
||||
);
|
||||
final player = CombatStats.empty().copyWith(mpMax: 100, mpCurrent: 50);
|
||||
final skillSystem = SkillSystemState.empty().copyWith(elapsedMs: 5000);
|
||||
|
||||
final result = service.useDotSkill(
|
||||
@@ -555,10 +549,7 @@ void main() {
|
||||
final service = SkillService(rng: rng);
|
||||
|
||||
const skill = SkillData.debugMode; // ATK +25% 버프, mpCost: 100
|
||||
final player = CombatStats.empty().copyWith(
|
||||
mpMax: 200,
|
||||
mpCurrent: 150,
|
||||
);
|
||||
final player = CombatStats.empty().copyWith(mpMax: 200, mpCurrent: 150);
|
||||
final skillSystem = SkillSystemState.empty().copyWith(elapsedMs: 5000);
|
||||
|
||||
final result = service.useBuffSkill(
|
||||
@@ -569,10 +560,7 @@ void main() {
|
||||
|
||||
expect(result.result.success, isTrue);
|
||||
expect(result.result.appliedBuff, isNotNull);
|
||||
expect(
|
||||
result.result.appliedBuff!.effect.atkModifier,
|
||||
equals(0.25),
|
||||
);
|
||||
expect(result.result.appliedBuff!.effect.atkModifier, equals(0.25));
|
||||
expect(result.updatedSkillSystem.activeBuffs.length, equals(1));
|
||||
expect(result.updatedPlayer.mpCurrent, equals(50)); // 150 - 100
|
||||
});
|
||||
@@ -582,10 +570,7 @@ void main() {
|
||||
final service = SkillService(rng: rng);
|
||||
|
||||
const skill = SkillData.debugMode;
|
||||
final player = CombatStats.empty().copyWith(
|
||||
mpMax: 100,
|
||||
mpCurrent: 50,
|
||||
);
|
||||
final player = CombatStats.empty().copyWith(mpMax: 100, mpCurrent: 50);
|
||||
final existingBuff = const ActiveBuff(
|
||||
effect: BuffEffect(
|
||||
id: 'debug_mode_buff',
|
||||
|
||||
@@ -26,10 +26,7 @@ void main() {
|
||||
final race = RaceTraits(
|
||||
raceId: 'test_race',
|
||||
name: 'Test Race',
|
||||
statModifiers: const {
|
||||
StatType.str: 2,
|
||||
StatType.intelligence: -1,
|
||||
},
|
||||
statModifiers: const {StatType.str: 2, StatType.intelligence: -1},
|
||||
);
|
||||
|
||||
// 보정 없는 클래스
|
||||
@@ -73,10 +70,7 @@ void main() {
|
||||
const klass = ClassTraits(
|
||||
classId: 'test_class',
|
||||
name: 'Test Class',
|
||||
statModifiers: {
|
||||
StatType.con: 3,
|
||||
StatType.dex: 1,
|
||||
},
|
||||
statModifiers: {StatType.con: 3, StatType.dex: 1},
|
||||
);
|
||||
|
||||
final result = calculator.applyModifiers(
|
||||
@@ -248,10 +242,7 @@ void main() {
|
||||
name: 'Test Class',
|
||||
statModifiers: {},
|
||||
passives: [
|
||||
ClassPassive(
|
||||
type: ClassPassiveType.evasionBonus,
|
||||
value: 0.15,
|
||||
),
|
||||
ClassPassive(type: ClassPassiveType.evasionBonus, value: 0.15),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -265,10 +256,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('물리 공격력 보너스 적용', () {
|
||||
final combatStats = CombatStats.empty().copyWith(
|
||||
atk: 100,
|
||||
def: 20,
|
||||
);
|
||||
final combatStats = CombatStats.empty().copyWith(atk: 100, def: 20);
|
||||
|
||||
const race = RaceTraits(
|
||||
raceId: 'test_race',
|
||||
@@ -322,10 +310,7 @@ void main() {
|
||||
name: 'Test Class',
|
||||
statModifiers: {},
|
||||
passives: [
|
||||
ClassPassive(
|
||||
type: ClassPassiveType.postCombatHeal,
|
||||
value: 0.05,
|
||||
),
|
||||
ClassPassive(type: ClassPassiveType.postCombatHeal, value: 0.05),
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -80,7 +80,8 @@ void main() {
|
||||
test('loadAndStart surfaces save load errors', () {
|
||||
fakeAsync((async) {
|
||||
final saveManager = FakeSaveManager()
|
||||
..onLoad = (_) => (const SaveOutcome.failure('boom'), null, false, null);
|
||||
..onLoad = (_) =>
|
||||
(const SaveOutcome.failure('boom'), null, false, null);
|
||||
final controller = buildController(async, saveManager);
|
||||
|
||||
controller.loadAndStart(fileName: 'bad.pqf');
|
||||
|
||||
@@ -95,10 +95,7 @@ class MockFactories {
|
||||
///
|
||||
/// [seed]: 결정론적 랜덤 시드
|
||||
/// [level]: 캐릭터 레벨
|
||||
static GameState createGameState({
|
||||
int seed = 42,
|
||||
int level = 1,
|
||||
}) {
|
||||
static GameState createGameState({int seed = 42, int level = 1}) {
|
||||
return GameState.withSeed(seed: seed);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user