Compare commits
2 Commits
93f29f6c33
...
306715ca26
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
306715ca26 | ||
|
|
9e5472728f |
@@ -265,79 +265,73 @@ class CombatTickService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 우선순위 1: HP 물약 (HP <= 30%)
|
// 우선순위 1: HP 물약 (소모된 HP >= 물약 회복량)
|
||||||
final hpRatio = playerStats.hpCurrent / playerStats.hpMax;
|
final hpPotion = potionService.selectEmergencyHpPotion(
|
||||||
if (hpRatio <= PotionService.emergencyHpThreshold) {
|
currentHp: playerStats.hpCurrent,
|
||||||
final hpPotion = potionService.selectEmergencyHpPotion(
|
maxHp: playerStats.hpMax,
|
||||||
|
inventory: potionInventory,
|
||||||
|
playerLevel: playerLevel,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hpPotion != null) {
|
||||||
|
final result = potionService.usePotion(
|
||||||
|
potionId: hpPotion.id,
|
||||||
|
inventory: potionInventory,
|
||||||
currentHp: playerStats.hpCurrent,
|
currentHp: playerStats.hpCurrent,
|
||||||
maxHp: playerStats.hpMax,
|
maxHp: playerStats.hpMax,
|
||||||
inventory: potionInventory,
|
currentMp: playerStats.mpCurrent,
|
||||||
playerLevel: playerLevel,
|
maxMp: playerStats.mpMax,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (hpPotion != null) {
|
if (result.success) {
|
||||||
final result = potionService.usePotion(
|
return (
|
||||||
potionId: hpPotion.id,
|
playerStats: playerStats.copyWith(hpCurrent: result.newHp),
|
||||||
inventory: potionInventory,
|
lastPotionUsedMs: timestamp,
|
||||||
currentHp: playerStats.hpCurrent,
|
potionInventory: result.newInventory!,
|
||||||
maxHp: playerStats.hpMax,
|
events: [
|
||||||
currentMp: playerStats.mpCurrent,
|
CombatEvent.playerPotion(
|
||||||
maxMp: playerStats.mpMax,
|
timestamp: timestamp,
|
||||||
|
potionName: hpPotion.name,
|
||||||
|
healAmount: result.healedAmount,
|
||||||
|
isHp: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
return (
|
|
||||||
playerStats: playerStats.copyWith(hpCurrent: result.newHp),
|
|
||||||
lastPotionUsedMs: timestamp,
|
|
||||||
potionInventory: result.newInventory!,
|
|
||||||
events: [
|
|
||||||
CombatEvent.playerPotion(
|
|
||||||
timestamp: timestamp,
|
|
||||||
potionName: hpPotion.name,
|
|
||||||
healAmount: result.healedAmount,
|
|
||||||
isHp: true,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 우선순위 2: MP 물약 (MP <= 50%)
|
// 우선순위 2: MP 물약 (소모된 MP >= 물약 회복량)
|
||||||
final mpRatio = playerStats.mpCurrent / playerStats.mpMax;
|
final mpPotion = potionService.selectEmergencyMpPotion(
|
||||||
if (mpRatio <= PotionService.emergencyMpThreshold) {
|
currentMp: playerStats.mpCurrent,
|
||||||
final mpPotion = potionService.selectEmergencyMpPotion(
|
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,
|
currentMp: playerStats.mpCurrent,
|
||||||
maxMp: playerStats.mpMax,
|
maxMp: playerStats.mpMax,
|
||||||
inventory: potionInventory,
|
|
||||||
playerLevel: playerLevel,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (mpPotion != null) {
|
if (result.success) {
|
||||||
final result = potionService.usePotion(
|
return (
|
||||||
potionId: mpPotion.id,
|
playerStats: playerStats.copyWith(mpCurrent: result.newMp),
|
||||||
inventory: potionInventory,
|
lastPotionUsedMs: timestamp,
|
||||||
currentHp: playerStats.hpCurrent,
|
potionInventory: result.newInventory!,
|
||||||
maxHp: playerStats.hpMax,
|
events: [
|
||||||
currentMp: playerStats.mpCurrent,
|
CombatEvent.playerPotion(
|
||||||
maxMp: playerStats.mpMax,
|
timestamp: timestamp,
|
||||||
|
potionName: mpPotion.name,
|
||||||
|
healAmount: result.healedAmount,
|
||||||
|
isHp: false,
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,12 +13,6 @@ class PotionService {
|
|||||||
/// 글로벌 물약 쿨타임 (1배속 기준 3초)
|
/// 글로벌 물약 쿨타임 (1배속 기준 3초)
|
||||||
static const int globalPotionCooldownMs = 3000;
|
static const int globalPotionCooldownMs = 3000;
|
||||||
|
|
||||||
/// 긴급 물약 사용 HP 임계치 (30%)
|
|
||||||
static const double emergencyHpThreshold = 0.30;
|
|
||||||
|
|
||||||
/// 긴급 물약 사용 MP 임계치 (50%)
|
|
||||||
static const double emergencyMpThreshold = 0.50;
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 물약 사용 가능 여부
|
// 물약 사용 가능 여부
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -104,7 +98,7 @@ class PotionService {
|
|||||||
|
|
||||||
/// 긴급 HP 물약 선택
|
/// 긴급 HP 물약 선택
|
||||||
///
|
///
|
||||||
/// HP가 임계치 이하일 때 사용할 최적의 물약 선택
|
/// 소모된 HP >= 물약 회복량이면 사용할 최적의 물약 선택
|
||||||
/// [currentHp] 현재 HP
|
/// [currentHp] 현재 HP
|
||||||
/// [maxHp] 최대 HP
|
/// [maxHp] 최대 HP
|
||||||
/// [inventory] 물약 인벤토리
|
/// [inventory] 물약 인벤토리
|
||||||
@@ -115,9 +109,8 @@ class PotionService {
|
|||||||
required PotionInventory inventory,
|
required PotionInventory inventory,
|
||||||
required int playerLevel,
|
required int playerLevel,
|
||||||
}) {
|
}) {
|
||||||
// 임계치 체크
|
final hpLost = maxHp - currentHp;
|
||||||
final hpRatio = currentHp / maxHp;
|
if (hpLost <= 0) return null;
|
||||||
if (hpRatio > emergencyHpThreshold) return null;
|
|
||||||
|
|
||||||
// 적정 티어 계산
|
// 적정 티어 계산
|
||||||
final targetTier = PotionData.tierForLevel(playerLevel);
|
final targetTier = PotionData.tierForLevel(playerLevel);
|
||||||
@@ -126,7 +119,10 @@ class PotionService {
|
|||||||
for (var tier = targetTier; tier >= 1; tier--) {
|
for (var tier = targetTier; tier >= 1; tier--) {
|
||||||
final potion = PotionData.getHpPotionByTier(tier);
|
final potion = PotionData.getHpPotionByTier(tier);
|
||||||
if (potion != null && inventory.hasPotion(potion.id)) {
|
if (potion != null && inventory.hasPotion(potion.id)) {
|
||||||
return potion;
|
final healAmount = potion.calculateHeal(maxHp);
|
||||||
|
if (hpLost >= healAmount) {
|
||||||
|
return potion;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,7 +130,10 @@ class PotionService {
|
|||||||
for (var tier = targetTier + 1; tier <= 5; tier++) {
|
for (var tier = targetTier + 1; tier <= 5; tier++) {
|
||||||
final potion = PotionData.getHpPotionByTier(tier);
|
final potion = PotionData.getHpPotionByTier(tier);
|
||||||
if (potion != null && inventory.hasPotion(potion.id)) {
|
if (potion != null && inventory.hasPotion(potion.id)) {
|
||||||
return potion;
|
final healAmount = potion.calculateHeal(maxHp);
|
||||||
|
if (hpLost >= healAmount) {
|
||||||
|
return potion;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,16 +142,15 @@ class PotionService {
|
|||||||
|
|
||||||
/// 긴급 MP 물약 선택
|
/// 긴급 MP 물약 선택
|
||||||
///
|
///
|
||||||
/// MP가 임계치 이하일 때 사용할 최적의 물약 선택
|
/// 소모된 MP >= 물약 회복량이면 사용할 최적의 물약 선택
|
||||||
Potion? selectEmergencyMpPotion({
|
Potion? selectEmergencyMpPotion({
|
||||||
required int currentMp,
|
required int currentMp,
|
||||||
required int maxMp,
|
required int maxMp,
|
||||||
required PotionInventory inventory,
|
required PotionInventory inventory,
|
||||||
required int playerLevel,
|
required int playerLevel,
|
||||||
}) {
|
}) {
|
||||||
// 임계치 체크
|
final mpLost = maxMp - currentMp;
|
||||||
final mpRatio = currentMp / maxMp;
|
if (mpLost <= 0) return null;
|
||||||
if (mpRatio > emergencyMpThreshold) return null;
|
|
||||||
|
|
||||||
// 적정 티어 계산
|
// 적정 티어 계산
|
||||||
final targetTier = PotionData.tierForLevel(playerLevel);
|
final targetTier = PotionData.tierForLevel(playerLevel);
|
||||||
@@ -161,7 +159,10 @@ class PotionService {
|
|||||||
for (var tier = targetTier; tier >= 1; tier--) {
|
for (var tier = targetTier; tier >= 1; tier--) {
|
||||||
final potion = PotionData.getMpPotionByTier(tier);
|
final potion = PotionData.getMpPotionByTier(tier);
|
||||||
if (potion != null && inventory.hasPotion(potion.id)) {
|
if (potion != null && inventory.hasPotion(potion.id)) {
|
||||||
return potion;
|
final healAmount = potion.calculateHeal(maxMp);
|
||||||
|
if (mpLost >= healAmount) {
|
||||||
|
return potion;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,7 +170,10 @@ class PotionService {
|
|||||||
for (var tier = targetTier + 1; tier <= 5; tier++) {
|
for (var tier = targetTier + 1; tier <= 5; tier++) {
|
||||||
final potion = PotionData.getMpPotionByTier(tier);
|
final potion = PotionData.getMpPotionByTier(tier);
|
||||||
if (potion != null && inventory.hasPotion(potion.id)) {
|
if (potion != null && inventory.hasPotion(potion.id)) {
|
||||||
return potion;
|
final healAmount = potion.calculateHeal(maxMp);
|
||||||
|
if (mpLost >= healAmount) {
|
||||||
|
return potion;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -974,40 +974,50 @@ class ProgressService {
|
|||||||
ItemRarity? lostItemRarity;
|
ItemRarity? lostItemRarity;
|
||||||
|
|
||||||
if (!isBossDeath) {
|
if (!isBossDeath) {
|
||||||
// 무기(슬롯 0)를 제외한 장착된 장비 중 1개를 제물로 삭제
|
// 레벨 기반 장비 손실 확률 계산
|
||||||
final equippedNonWeaponSlots = <int>[];
|
// Lv 1~5: 0%, Lv 6: 10%, Lv 10: 50%, Lv 15+: 100%
|
||||||
for (var i = 1; i < Equipment.slotCount; i++) {
|
final level = state.traits.level;
|
||||||
final item = state.equipment.getItemByIndex(i);
|
final lossChancePercent = ((level - 5) * 10).clamp(0, 100);
|
||||||
// 디버그: 장비 슬롯 상태 확인
|
final roll = state.rng.nextInt(100); // 0~99
|
||||||
// ignore: avoid_print
|
final shouldLoseEquipment = roll < lossChancePercent;
|
||||||
print('[Death] Slot $i: "${item.name}" isEmpty=${item.isEmpty}');
|
|
||||||
if (item.isNotEmpty) {
|
|
||||||
equippedNonWeaponSlots.add(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 디버그: 장착된 슬롯 목록
|
|
||||||
// ignore: avoid_print
|
// ignore: avoid_print
|
||||||
print('[Death] equippedNonWeaponSlots: $equippedNonWeaponSlots');
|
print('[Death] Lv$level lossChance=$lossChancePercent% roll=$roll '
|
||||||
|
'shouldLose=$shouldLoseEquipment');
|
||||||
|
|
||||||
if (equippedNonWeaponSlots.isNotEmpty) {
|
if (shouldLoseEquipment) {
|
||||||
lostCount = 1;
|
// 무기(슬롯 0)를 제외한 장착된 장비 중 1개를 제물로 삭제
|
||||||
// 랜덤하게 1개 슬롯 선택
|
final equippedNonWeaponSlots = <int>[];
|
||||||
final sacrificeIndex =
|
for (var i = 1; i < Equipment.slotCount; i++) {
|
||||||
equippedNonWeaponSlots[state.rng.nextInt(
|
final item = state.equipment.getItemByIndex(i);
|
||||||
equippedNonWeaponSlots.length,
|
if (item.isNotEmpty) {
|
||||||
)];
|
equippedNonWeaponSlots.add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 제물로 바칠 아이템 정보 저장
|
if (equippedNonWeaponSlots.isNotEmpty) {
|
||||||
final lostItem = state.equipment.getItemByIndex(sacrificeIndex);
|
lostCount = 1;
|
||||||
lostItemName = lostItem.name;
|
// 랜덤하게 1개 슬롯 선택
|
||||||
lostItemSlot = EquipmentSlot.values[sacrificeIndex];
|
final sacrificeIndex =
|
||||||
lostItemRarity = lostItem.rarity;
|
equippedNonWeaponSlots[state.rng.nextInt(
|
||||||
|
equippedNonWeaponSlots.length,
|
||||||
|
)];
|
||||||
|
|
||||||
// 해당 슬롯을 빈 장비로 교체
|
// 제물로 바칠 아이템 정보 저장
|
||||||
newEquipment = newEquipment.setItemByIndex(
|
final lostItem = state.equipment.getItemByIndex(sacrificeIndex);
|
||||||
sacrificeIndex,
|
lostItemName = lostItem.name;
|
||||||
EquipmentItem.empty(lostItemSlot),
|
lostItemSlot = EquipmentSlot.values[sacrificeIndex];
|
||||||
);
|
lostItemRarity = lostItem.rarity;
|
||||||
|
|
||||||
|
// 해당 슬롯을 빈 장비로 교체
|
||||||
|
newEquipment = newEquipment.setItemByIndex(
|
||||||
|
sacrificeIndex,
|
||||||
|
EquipmentItem.empty(lostItemSlot),
|
||||||
|
);
|
||||||
|
|
||||||
|
// ignore: avoid_print
|
||||||
|
print('[Death] Lost item: $lostItemName (slot: $lostItemSlot)');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user