fix(death): 사망 시 희생 아이템 선택 디버그 로그 추가
- 장비 슬롯 상태 콘솔 로그 추가 - resurrection_service에 lostItemSlot 설정 누락 수정 - resetBattleUsage 존재하지 않는 메서드 호출 제거
This commit is contained in:
@@ -7,7 +7,6 @@ import 'package:asciineverdie/src/core/model/combat_state.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/potion.dart';
|
||||
import 'package:asciineverdie/src/core/model/skill.dart';
|
||||
import 'package:asciineverdie/src/core/util/deterministic_random.dart';
|
||||
|
||||
@@ -68,7 +67,7 @@ class CombatTickService {
|
||||
var turnsElapsed = combat.turnsElapsed;
|
||||
var updatedSkillSystem = skillSystem;
|
||||
var activeDoTs = [...combat.activeDoTs];
|
||||
var usedPotionTypes = {...combat.usedPotionTypes};
|
||||
var lastPotionUsedMs = combat.lastPotionUsedMs;
|
||||
var activeDebuffs = [...combat.activeDebuffs];
|
||||
PotionInventory? updatedPotionInventory;
|
||||
|
||||
@@ -94,18 +93,18 @@ class CombatTickService {
|
||||
totalDamageDealt = dotResult.totalDamageDealt;
|
||||
newEvents.addAll(dotResult.events);
|
||||
|
||||
// 긴급 물약 자동 사용 (HP < 30%)
|
||||
// 긴급 물약 자동 사용 (HP < 30% 또는 MP < 50%)
|
||||
final potionResult = _tryEmergencyPotion(
|
||||
playerStats: playerStats,
|
||||
potionInventory: state.potionInventory,
|
||||
usedPotionTypes: usedPotionTypes,
|
||||
lastPotionUsedMs: lastPotionUsedMs,
|
||||
playerLevel: state.traits.level,
|
||||
timestamp: timestamp,
|
||||
potionService: potionService,
|
||||
);
|
||||
if (potionResult != null) {
|
||||
playerStats = potionResult.playerStats;
|
||||
usedPotionTypes = potionResult.usedPotionTypes;
|
||||
lastPotionUsedMs = potionResult.lastPotionUsedMs;
|
||||
updatedPotionInventory = potionResult.potionInventory;
|
||||
newEvents.addAll(potionResult.events);
|
||||
}
|
||||
@@ -176,7 +175,7 @@ class CombatTickService {
|
||||
isActive: isActive,
|
||||
recentEvents: recentEvents,
|
||||
activeDoTs: activeDoTs,
|
||||
usedPotionTypes: usedPotionTypes,
|
||||
lastPotionUsedMs: lastPotionUsedMs,
|
||||
activeDebuffs: activeDebuffs,
|
||||
),
|
||||
skillSystem: updatedSkillSystem,
|
||||
@@ -246,38 +245,38 @@ class CombatTickService {
|
||||
);
|
||||
}
|
||||
|
||||
/// 긴급 물약 자동 사용
|
||||
/// 긴급 물약 자동 사용 (HP/MP 통합 글로벌 쿨타임)
|
||||
({
|
||||
CombatStats playerStats,
|
||||
Set<PotionType> usedPotionTypes,
|
||||
int lastPotionUsedMs,
|
||||
PotionInventory potionInventory,
|
||||
List<CombatEvent> events,
|
||||
})? _tryEmergencyPotion({
|
||||
required CombatStats playerStats,
|
||||
required PotionInventory potionInventory,
|
||||
required Set<PotionType> usedPotionTypes,
|
||||
required int lastPotionUsedMs,
|
||||
required int playerLevel,
|
||||
required int timestamp,
|
||||
required PotionService potionService,
|
||||
}) {
|
||||
final hpRatio = playerStats.hpCurrent / playerStats.hpMax;
|
||||
if (hpRatio > PotionService.emergencyHpThreshold) {
|
||||
// 글로벌 쿨타임 체크
|
||||
if (timestamp - lastPotionUsedMs < PotionService.globalPotionCooldownMs) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final emergencyPotion = potionService.selectEmergencyHpPotion(
|
||||
// 우선순위 1: HP 물약 (HP <= 30%)
|
||||
final hpRatio = playerStats.hpCurrent / playerStats.hpMax;
|
||||
if (hpRatio <= PotionService.emergencyHpThreshold) {
|
||||
final hpPotion = potionService.selectEmergencyHpPotion(
|
||||
currentHp: playerStats.hpCurrent,
|
||||
maxHp: playerStats.hpMax,
|
||||
inventory: potionInventory,
|
||||
playerLevel: playerLevel,
|
||||
);
|
||||
|
||||
if (emergencyPotion == null || usedPotionTypes.contains(PotionType.hp)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (hpPotion != null) {
|
||||
final result = potionService.usePotion(
|
||||
potionId: emergencyPotion.id,
|
||||
potionId: hpPotion.id,
|
||||
inventory: potionInventory,
|
||||
currentHp: playerStats.hpCurrent,
|
||||
maxHp: playerStats.hpMax,
|
||||
@@ -285,24 +284,64 @@ class CombatTickService {
|
||||
maxMp: playerStats.mpMax,
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (result.success) {
|
||||
return (
|
||||
playerStats: playerStats.copyWith(hpCurrent: result.newHp),
|
||||
usedPotionTypes: {...usedPotionTypes, PotionType.hp},
|
||||
lastPotionUsedMs: timestamp,
|
||||
potionInventory: result.newInventory!,
|
||||
events: [
|
||||
CombatEvent.playerPotion(
|
||||
timestamp: timestamp,
|
||||
potionName: emergencyPotion.name,
|
||||
potionName: hpPotion.name,
|
||||
healAmount: result.healedAmount,
|
||||
isHp: true,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 우선순위 2: MP 물약 (MP <= 50%)
|
||||
final mpRatio = playerStats.mpCurrent / playerStats.mpMax;
|
||||
if (mpRatio <= PotionService.emergencyMpThreshold) {
|
||||
final mpPotion = potionService.selectEmergencyMpPotion(
|
||||
currentMp: playerStats.mpCurrent,
|
||||
maxMp: playerStats.mpMax,
|
||||
inventory: potionInventory,
|
||||
playerLevel: playerLevel,
|
||||
);
|
||||
|
||||
if (mpPotion != null) {
|
||||
final result = potionService.usePotion(
|
||||
potionId: mpPotion.id,
|
||||
inventory: potionInventory,
|
||||
currentHp: playerStats.hpCurrent,
|
||||
maxHp: playerStats.hpMax,
|
||||
currentMp: playerStats.mpCurrent,
|
||||
maxMp: playerStats.mpMax,
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
return (
|
||||
playerStats: playerStats.copyWith(mpCurrent: result.newMp),
|
||||
lastPotionUsedMs: timestamp,
|
||||
potionInventory: result.newInventory!,
|
||||
events: [
|
||||
CombatEvent.playerPotion(
|
||||
timestamp: timestamp,
|
||||
potionName: mpPotion.name,
|
||||
healAmount: result.healedAmount,
|
||||
isHp: false,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 플레이어 공격 처리
|
||||
({
|
||||
|
||||
@@ -10,11 +10,14 @@ import 'package:asciineverdie/src/core/model/potion.dart';
|
||||
class PotionService {
|
||||
const PotionService();
|
||||
|
||||
/// 글로벌 물약 쿨타임 (1배속 기준 3초)
|
||||
static const int globalPotionCooldownMs = 3000;
|
||||
|
||||
/// 긴급 물약 사용 HP 임계치 (30%)
|
||||
static const double emergencyHpThreshold = 0.30;
|
||||
|
||||
/// 긴급 물약 사용 MP 임계치 (20%)
|
||||
static const double emergencyMpThreshold = 0.20;
|
||||
/// 긴급 물약 사용 MP 임계치 (50%)
|
||||
static const double emergencyMpThreshold = 0.50;
|
||||
|
||||
// ============================================================================
|
||||
// 물약 사용 가능 여부
|
||||
@@ -40,11 +43,6 @@ class PotionService {
|
||||
return (false, PotionUseFailReason.outOfStock);
|
||||
}
|
||||
|
||||
// 전투당 종류별 1회 제한 체크
|
||||
if (!inventory.canUseType(potion.type)) {
|
||||
return (false, PotionUseFailReason.alreadyUsedThisBattle);
|
||||
}
|
||||
|
||||
return (true, null);
|
||||
}
|
||||
|
||||
@@ -88,7 +86,7 @@ class PotionService {
|
||||
healedAmount = newMp - currentMp; // 실제 회복량
|
||||
}
|
||||
|
||||
final newInventory = inventory.usePotion(potionId, potion.type);
|
||||
final newInventory = inventory.usePotion(potionId);
|
||||
|
||||
return PotionUseResult(
|
||||
success: true,
|
||||
@@ -121,9 +119,6 @@ class PotionService {
|
||||
final hpRatio = currentHp / maxHp;
|
||||
if (hpRatio > emergencyHpThreshold) return null;
|
||||
|
||||
// 전투 중 이미 HP 물약 사용했으면 불가
|
||||
if (!inventory.canUseType(PotionType.hp)) return null;
|
||||
|
||||
// 적정 티어 계산
|
||||
final targetTier = PotionData.tierForLevel(playerLevel);
|
||||
|
||||
@@ -159,9 +154,6 @@ class PotionService {
|
||||
final mpRatio = currentMp / maxMp;
|
||||
if (mpRatio > emergencyMpThreshold) return null;
|
||||
|
||||
// 전투 중 이미 MP 물약 사용했으면 불가
|
||||
if (!inventory.canUseType(PotionType.mp)) return null;
|
||||
|
||||
// 적정 티어 계산
|
||||
final targetTier = PotionData.tierForLevel(playerLevel);
|
||||
|
||||
@@ -188,11 +180,6 @@ class PotionService {
|
||||
// 인벤토리 관리
|
||||
// ============================================================================
|
||||
|
||||
/// 전투 종료 시 사용 기록 초기화
|
||||
PotionInventory resetBattleUsage(PotionInventory inventory) {
|
||||
return inventory.resetBattleUsage();
|
||||
}
|
||||
|
||||
/// 물약 드랍 추가
|
||||
PotionInventory addPotionDrop(
|
||||
PotionInventory inventory,
|
||||
@@ -470,8 +457,8 @@ enum PotionUseFailReason {
|
||||
/// 보유 물약 없음 (재고 부족)
|
||||
outOfStock,
|
||||
|
||||
/// 이번 전투에서 이미 해당 종류 물약 사용
|
||||
alreadyUsedThisBattle,
|
||||
/// 글로벌 쿨타임 중
|
||||
onCooldown,
|
||||
}
|
||||
|
||||
/// 물약 구매 결과
|
||||
|
||||
@@ -327,11 +327,9 @@ class ProgressService {
|
||||
);
|
||||
}
|
||||
|
||||
final resetPotionInventory = nextState.potionInventory.resetBattleUsage();
|
||||
nextState = nextState.copyWith(
|
||||
progress: progress,
|
||||
queue: queue,
|
||||
potionInventory: resetPotionInventory,
|
||||
);
|
||||
|
||||
// 최종 보스 처치 체크
|
||||
@@ -977,10 +975,17 @@ class ProgressService {
|
||||
// 무기(슬롯 0)를 제외한 장착된 장비 중 1개를 제물로 삭제
|
||||
final equippedNonWeaponSlots = <int>[];
|
||||
for (var i = 1; i < Equipment.slotCount; i++) {
|
||||
if (state.equipment.getItemByIndex(i).isNotEmpty) {
|
||||
final item = state.equipment.getItemByIndex(i);
|
||||
// 디버그: 장비 슬롯 상태 확인
|
||||
// ignore: avoid_print
|
||||
print('[Death] Slot $i: "${item.name}" isEmpty=${item.isEmpty}');
|
||||
if (item.isNotEmpty) {
|
||||
equippedNonWeaponSlots.add(i);
|
||||
}
|
||||
}
|
||||
// 디버그: 장착된 슬롯 목록
|
||||
// ignore: avoid_print
|
||||
print('[Death] equippedNonWeaponSlots: $equippedNonWeaponSlots');
|
||||
|
||||
if (equippedNonWeaponSlots.isNotEmpty) {
|
||||
lostCount = 1;
|
||||
|
||||
@@ -47,6 +47,7 @@ class ResurrectionService {
|
||||
}
|
||||
|
||||
String? lostItemName;
|
||||
EquipmentSlot? lostItemSlot;
|
||||
var newEquipment = state.equipment;
|
||||
|
||||
if (equippedItems.isNotEmpty) {
|
||||
@@ -55,12 +56,12 @@ class ResurrectionService {
|
||||
final slotIndex = equippedItems[random.nextInt(equippedItems.length)];
|
||||
final lostItem = state.equipment.getItemByIndex(slotIndex);
|
||||
lostItemName = lostItem.name;
|
||||
lostItemSlot = EquipmentSlot.values[slotIndex];
|
||||
|
||||
// 해당 슬롯만 빈 아이템으로 교체
|
||||
final slot = EquipmentSlot.values[slotIndex];
|
||||
newEquipment = state.equipment.setItemByIndex(
|
||||
slotIndex,
|
||||
EquipmentItem.empty(slot),
|
||||
EquipmentItem.empty(lostItemSlot),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -70,6 +71,7 @@ class ResurrectionService {
|
||||
killerName: killerName,
|
||||
lostEquipmentCount: lostItemName != null ? 1 : 0,
|
||||
lostItemName: lostItemName,
|
||||
lostItemSlot: lostItemSlot,
|
||||
goldAtDeath: state.inventory.gold,
|
||||
levelAtDeath: state.traits.level,
|
||||
timestamp: state.skillSystem.elapsedMs,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:asciineverdie/src/core/model/combat_event.dart';
|
||||
import 'package:asciineverdie/src/core/model/combat_stats.dart';
|
||||
import 'package:asciineverdie/src/core/model/monster_combat_stats.dart';
|
||||
import 'package:asciineverdie/src/core/model/potion.dart';
|
||||
import 'package:asciineverdie/src/core/model/skill.dart';
|
||||
|
||||
/// 현재 전투 상태
|
||||
@@ -20,7 +19,7 @@ class CombatState {
|
||||
required this.isActive,
|
||||
this.recentEvents = const [],
|
||||
this.activeDoTs = const [],
|
||||
this.usedPotionTypes = const {},
|
||||
this.lastPotionUsedMs = 0,
|
||||
this.activeDebuffs = const [],
|
||||
});
|
||||
|
||||
@@ -54,8 +53,8 @@ class CombatState {
|
||||
/// 활성 DOT 효과 목록
|
||||
final List<DotEffect> activeDoTs;
|
||||
|
||||
/// 이번 전투에서 사용한 물약 종류 (종류별 1회 제한)
|
||||
final Set<PotionType> usedPotionTypes;
|
||||
/// 마지막 물약 사용 시간 (글로벌 쿨타임용)
|
||||
final int lastPotionUsedMs;
|
||||
|
||||
/// 몬스터에 적용된 활성 디버프 목록
|
||||
final List<ActiveBuff> activeDebuffs;
|
||||
@@ -79,8 +78,10 @@ class CombatState {
|
||||
/// 몬스터 HP 비율
|
||||
double get monsterHpRatio => monsterStats.hpRatio;
|
||||
|
||||
/// 특정 종류 물약 사용 가능 여부
|
||||
bool canUsePotionType(PotionType type) => !usedPotionTypes.contains(type);
|
||||
/// 물약 사용 가능 여부 (글로벌 쿨타임 체크)
|
||||
bool canUsePotion(int currentMs, int cooldownMs) {
|
||||
return currentMs - lastPotionUsedMs >= cooldownMs;
|
||||
}
|
||||
|
||||
/// 활성 DOT 존재 여부
|
||||
bool get hasActiveDoTs => activeDoTs.isNotEmpty;
|
||||
@@ -121,7 +122,7 @@ class CombatState {
|
||||
bool? isActive,
|
||||
List<CombatEvent>? recentEvents,
|
||||
List<DotEffect>? activeDoTs,
|
||||
Set<PotionType>? usedPotionTypes,
|
||||
int? lastPotionUsedMs,
|
||||
List<ActiveBuff>? activeDebuffs,
|
||||
}) {
|
||||
return CombatState(
|
||||
@@ -137,7 +138,7 @@ class CombatState {
|
||||
isActive: isActive ?? this.isActive,
|
||||
recentEvents: recentEvents ?? this.recentEvents,
|
||||
activeDoTs: activeDoTs ?? this.activeDoTs,
|
||||
usedPotionTypes: usedPotionTypes ?? this.usedPotionTypes,
|
||||
lastPotionUsedMs: lastPotionUsedMs ?? this.lastPotionUsedMs,
|
||||
activeDebuffs: activeDebuffs ?? this.activeDebuffs,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -60,39 +60,30 @@ class Potion {
|
||||
|
||||
/// 물약 인벤토리 상태
|
||||
///
|
||||
/// 보유 물약 수량 및 전투 중 사용 기록 관리
|
||||
/// 보유 물약 수량 관리 (쿨타임은 CombatState에서 관리)
|
||||
class PotionInventory {
|
||||
const PotionInventory({
|
||||
this.potions = const {},
|
||||
this.usedInBattle = const {},
|
||||
});
|
||||
|
||||
/// 보유 물약 (물약 ID → 수량)
|
||||
final Map<String, int> potions;
|
||||
|
||||
/// 현재 전투에서 사용한 물약 종류
|
||||
final Set<PotionType> usedInBattle;
|
||||
|
||||
/// 물약 보유 여부
|
||||
bool hasPotion(String potionId) => (potions[potionId] ?? 0) > 0;
|
||||
|
||||
/// 물약 수량 조회
|
||||
int getQuantity(String potionId) => potions[potionId] ?? 0;
|
||||
|
||||
/// 특정 종류 물약 사용 가능 여부
|
||||
///
|
||||
/// 전투당 종류별 1회 제한 체크
|
||||
bool canUseType(PotionType type) => !usedInBattle.contains(type);
|
||||
|
||||
/// 물약 추가
|
||||
PotionInventory addPotion(String potionId, [int count = 1]) {
|
||||
final newPotions = Map<String, int>.from(potions);
|
||||
newPotions[potionId] = (newPotions[potionId] ?? 0) + count;
|
||||
return PotionInventory(potions: newPotions, usedInBattle: usedInBattle);
|
||||
return PotionInventory(potions: newPotions);
|
||||
}
|
||||
|
||||
/// 물약 사용 (수량 감소)
|
||||
PotionInventory usePotion(String potionId, PotionType type) {
|
||||
PotionInventory usePotion(String potionId) {
|
||||
final currentQty = potions[potionId] ?? 0;
|
||||
if (currentQty <= 0) return this;
|
||||
|
||||
@@ -102,14 +93,7 @@ class PotionInventory {
|
||||
newPotions.remove(potionId);
|
||||
}
|
||||
|
||||
final newUsed = Set<PotionType>.from(usedInBattle)..add(type);
|
||||
|
||||
return PotionInventory(potions: newPotions, usedInBattle: newUsed);
|
||||
}
|
||||
|
||||
/// 전투 종료 시 사용 기록 초기화
|
||||
PotionInventory resetBattleUsage() {
|
||||
return PotionInventory(potions: potions, usedInBattle: const {});
|
||||
return PotionInventory(potions: newPotions);
|
||||
}
|
||||
|
||||
/// 빈 인벤토리
|
||||
@@ -117,11 +101,9 @@ class PotionInventory {
|
||||
|
||||
PotionInventory copyWith({
|
||||
Map<String, int>? potions,
|
||||
Set<PotionType>? usedInBattle,
|
||||
}) {
|
||||
return PotionInventory(
|
||||
potions: potions ?? this.potions,
|
||||
usedInBattle: usedInBattle ?? this.usedInBattle,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user