feat(balance): CHA(매력) 스탯 활성화 — 상점 할인 + 드롭률 보정

- shop_service: CHA 기반 구매 할인 (CHA-10)×1%, 최대 15%
- item_service: CHA 기반 희귀 아이템 드롭률 보정 (CHA-10)×0.5%
- 호출부(game_mutations, market_service, resurrection_service)에 CHA 전달
- 기존 데드 스탯이었던 CHA가 게임 로직에 반영됨
This commit is contained in:
JiWoong Sul
2026-03-31 00:12:52 +09:00
parent ea54b4c501
commit 69e7695cb7
5 changed files with 43 additions and 16 deletions

View File

@@ -26,6 +26,7 @@ class GameMutations {
name: name,
slot: slot,
level: level,
cha: state.stats.cha,
);
final updatedEquip = state.equipment

View File

@@ -60,23 +60,34 @@ class ItemService {
// 희귀도 결정
// ============================================================================
/// 희귀도 결정 (고정 확률)
/// 희귀도 결정 (고정 확률 + CHA 보정)
///
/// 확률 분포:
/// 기본 확률 분포:
/// - Common: 34%
/// - Uncommon: 40%
/// - Rare: 20%
/// - Epic: 5%
/// - Legendary: 1%
ItemRarity determineRarity(int level) {
///
/// CHA 보정(charisma bonus): (CHA - 10) * 0.5% 추가 희귀 확률.
/// 보정값만큼 Common 확률이 줄고, Rare 이상 확률이 증가.
ItemRarity determineRarity(int level, {int cha = 0}) {
final roll = rng.nextInt(100);
// Legendary: 0-0 (1%)
if (roll < 1) return ItemRarity.legendary;
// Epic: 1-5 (5%)
if (roll < 6) return ItemRarity.epic;
// Rare: 6-25 (20%)
if (roll < 26) return ItemRarity.rare;
// CHA 보정: (CHA - 10) * 0.5, 0~10 범위
final chaBonus = ((cha - 10) * 0.5).clamp(0.0, 10.0);
// 보정된 임계값 (chaBonus만큼 희귀 쪽으로 이동)
final legendaryThreshold = 1.0 + chaBonus * 0.1; // 최대 2%
final epicThreshold = 6.0 + chaBonus * 0.3; // 최대 9%
final rareThreshold = 26.0 + chaBonus * 0.6; // 최대 32%
// Legendary
if (roll < legendaryThreshold) return ItemRarity.legendary;
// Epic
if (roll < epicThreshold) return ItemRarity.epic;
// Rare
if (roll < rareThreshold) return ItemRarity.rare;
// Uncommon: 26-65 (40%)
if (roll < 66) return ItemRarity.uncommon;
// Common: 66-99 (34%)
@@ -331,8 +342,9 @@ class ItemService {
required EquipmentSlot slot,
required int level,
ItemRarity? rarity,
int cha = 0,
}) {
final itemRarity = rarity ?? determineRarity(level);
final itemRarity = rarity ?? determineRarity(level, cha: cha);
final stats = generateItemStats(
level: level,
rarity: itemRarity,

View File

@@ -56,7 +56,7 @@ class MarketService {
slot: slot,
targetRarity: ItemRarity.common,
);
final price = shopService.calculateBuyPrice(item);
final price = shopService.calculateBuyPrice(item, cha: state.stats.cha);
if (nextState.inventory.gold >= price) {
nextState = nextState.copyWith(

View File

@@ -105,6 +105,7 @@ class ResurrectionService {
playerLevel: state.traits.level,
currentGold: state.inventory.gold,
currentEquipment: state.equipment,
cha: state.stats.cha,
);
// 장비 적용

View File

@@ -18,10 +18,21 @@ class ShopService {
/// 장비 구매 가격 계산
///
/// 가격 = 아이템 레벨 * 50 * 희귀도 배율
int calculateBuyPrice(EquipmentItem item) {
/// 가격 = 아이템 레벨 * 50 * 희귀도 배율 * (1 - CHA 할인율)
/// CHA 할인율(charisma discount): (CHA - 10) * 1%, 최대 15%, 최소 0%
int calculateBuyPrice(EquipmentItem item, {int cha = 0}) {
if (item.isEmpty) return 0;
return (item.level * 50 * _getRarityPriceMultiplier(item.rarity)).round();
final basePrice = (item.level * 50 * _getRarityPriceMultiplier(item.rarity))
.round();
final discount = chaDiscount(cha);
return (basePrice * (1.0 - discount)).round();
}
/// CHA 기반 할인율 계산
///
/// (CHA - 10) * 0.01, 0~0.15 범위로 클램프
static double chaDiscount(int cha) {
return ((cha - 10) * 0.01).clamp(0.0, 0.15);
}
/// 장비 판매 가격 계산
@@ -211,6 +222,7 @@ class ShopService {
required int playerLevel,
required int currentGold,
required Equipment currentEquipment,
int cha = 0,
}) {
var remainingGold = currentGold;
final purchasedItems = <EquipmentItem>[];
@@ -230,7 +242,7 @@ class ShopService {
targetRarity: ItemRarity.common, // 부활 시 Common만 구매
);
final price = calculateBuyPrice(shopItem);
final price = calculateBuyPrice(shopItem, cha: cha);
if (price <= remainingGold) {
remainingGold -= price;
purchasedItems.add(shopItem);
@@ -254,6 +266,7 @@ class ShopService {
required int currentGold,
required EquipmentSlot slot,
ItemRarity? preferredRarity,
int cha = 0,
}) {
final item = generateShopItem(
playerLevel: playerLevel,
@@ -261,7 +274,7 @@ class ShopService {
targetRarity: preferredRarity,
);
final price = calculateBuyPrice(item);
final price = calculateBuyPrice(item, cha: cha);
if (price > currentGold) return null;
return PurchaseResult(