feat(arena): 아레나 서비스 및 아이템 서비스 개선
- ArenaService 로직 확장 - ArenaMatch 모델 업데이트 - ItemService 아레나 지원 추가
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import 'package:asciineverdie/data/skill_data.dart';
|
import 'package:asciineverdie/data/skill_data.dart';
|
||||||
import 'package:asciineverdie/src/core/engine/combat_calculator.dart';
|
import 'package:asciineverdie/src/core/engine/combat_calculator.dart';
|
||||||
|
import 'package:asciineverdie/src/core/engine/item_service.dart';
|
||||||
import 'package:asciineverdie/src/core/engine/skill_service.dart';
|
import 'package:asciineverdie/src/core/engine/skill_service.dart';
|
||||||
import 'package:asciineverdie/src/core/model/arena_match.dart';
|
import 'package:asciineverdie/src/core/model/arena_match.dart';
|
||||||
import 'package:asciineverdie/src/core/model/equipment_item.dart';
|
import 'package:asciineverdie/src/core/model/equipment_item.dart';
|
||||||
@@ -668,18 +669,58 @@ class ArenaService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 장비 교환
|
// AI 베팅 슬롯 선택
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
/// 장비 교환 (같은 슬롯끼리)
|
/// AI가 도전자에게서 약탈할 슬롯 자동 선택
|
||||||
///
|
///
|
||||||
/// 승자가 선택한 슬롯의 장비를 서로 교환
|
/// 도전자의 가장 좋은 장비 슬롯 선택 (무기 제외)
|
||||||
|
EquipmentSlot selectOpponentBettingSlot(HallOfFameEntry challenger) {
|
||||||
|
final equipment = challenger.finalEquipment ?? [];
|
||||||
|
if (equipment.isEmpty) {
|
||||||
|
// 장비가 없으면 기본 슬롯 (투구)
|
||||||
|
return EquipmentSlot.helm;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 무기를 제외한 장비 중 가장 높은 점수의 슬롯 선택
|
||||||
|
EquipmentSlot? bestSlot;
|
||||||
|
int bestScore = -1;
|
||||||
|
|
||||||
|
for (final item in equipment) {
|
||||||
|
// 무기는 약탈 불가
|
||||||
|
if (item.slot == EquipmentSlot.weapon) continue;
|
||||||
|
if (item.isEmpty) continue;
|
||||||
|
|
||||||
|
final score = ItemService.calculateEquipmentScore(item);
|
||||||
|
if (score > bestScore) {
|
||||||
|
bestScore = score;
|
||||||
|
bestSlot = item.slot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 유효한 슬롯이 없으면 투구 선택
|
||||||
|
return bestSlot ?? EquipmentSlot.helm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 베팅 가능한 슬롯 목록 반환 (무기 제외)
|
||||||
|
List<EquipmentSlot> getBettableSlots() {
|
||||||
|
return EquipmentSlot.values
|
||||||
|
.where((slot) => slot != EquipmentSlot.weapon)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 장비 약탈
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/// 장비 약탈 (승자가 패자의 베팅 슬롯 장비 획득)
|
||||||
|
///
|
||||||
|
/// - 승자: 자신이 선택한 슬롯의 패자 장비 획득
|
||||||
|
/// - 패자: 해당 슬롯 장비 손실 → 기본 장비로 대체
|
||||||
(HallOfFameEntry, HallOfFameEntry) _exchangeEquipment({
|
(HallOfFameEntry, HallOfFameEntry) _exchangeEquipment({
|
||||||
required ArenaMatch match,
|
required ArenaMatch match,
|
||||||
required bool isVictory,
|
required bool isVictory,
|
||||||
}) {
|
}) {
|
||||||
final slot = match.bettingSlot;
|
|
||||||
|
|
||||||
// 도전자 장비 목록 복사
|
// 도전자 장비 목록 복사
|
||||||
final challengerEquipment =
|
final challengerEquipment =
|
||||||
List<EquipmentItem>.from(match.challenger.finalEquipment ?? []);
|
List<EquipmentItem>.from(match.challenger.finalEquipment ?? []);
|
||||||
@@ -688,13 +729,29 @@ class ArenaService {
|
|||||||
final opponentEquipment =
|
final opponentEquipment =
|
||||||
List<EquipmentItem>.from(match.opponent.finalEquipment ?? []);
|
List<EquipmentItem>.from(match.opponent.finalEquipment ?? []);
|
||||||
|
|
||||||
// 해당 슬롯의 장비 찾기
|
if (isVictory) {
|
||||||
final challengerItem = _findItemBySlot(challengerEquipment, slot);
|
// 도전자 승리: 도전자가 선택한 슬롯의 상대 장비 획득
|
||||||
final opponentItem = _findItemBySlot(opponentEquipment, slot);
|
final winnerSlot = match.challengerBettingSlot;
|
||||||
|
final lootedItem = _findItemBySlot(opponentEquipment, winnerSlot);
|
||||||
|
|
||||||
// 장비 교환
|
// 도전자: 약탈한 장비로 교체
|
||||||
_replaceItemInList(challengerEquipment, slot, opponentItem);
|
_replaceItemInList(challengerEquipment, winnerSlot, lootedItem);
|
||||||
_replaceItemInList(opponentEquipment, slot, challengerItem);
|
|
||||||
|
// 상대: 해당 슬롯 기본 장비로 대체
|
||||||
|
final defaultItem = _createDefaultEquipment(winnerSlot);
|
||||||
|
_replaceItemInList(opponentEquipment, winnerSlot, defaultItem);
|
||||||
|
} else {
|
||||||
|
// 상대 승리: 상대가 선택한 슬롯의 도전자 장비 획득
|
||||||
|
final winnerSlot = match.opponentBettingSlot;
|
||||||
|
final lootedItem = _findItemBySlot(challengerEquipment, winnerSlot);
|
||||||
|
|
||||||
|
// 상대: 약탈한 장비로 교체
|
||||||
|
_replaceItemInList(opponentEquipment, winnerSlot, lootedItem);
|
||||||
|
|
||||||
|
// 도전자: 해당 슬롯 기본 장비로 대체
|
||||||
|
final defaultItem = _createDefaultEquipment(winnerSlot);
|
||||||
|
_replaceItemInList(challengerEquipment, winnerSlot, defaultItem);
|
||||||
|
}
|
||||||
|
|
||||||
// 업데이트된 엔트리 생성
|
// 업데이트된 엔트리 생성
|
||||||
final updatedChallenger = match.challenger.copyWith(
|
final updatedChallenger = match.challenger.copyWith(
|
||||||
@@ -731,4 +788,11 @@ class ArenaService {
|
|||||||
// 슬롯이 없으면 추가
|
// 슬롯이 없으면 추가
|
||||||
equipment.add(newItem);
|
equipment.add(newItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 기본 장비 생성 (Common 등급)
|
||||||
|
///
|
||||||
|
/// 패자가 장비를 잃었을 때 빈 슬롯 방지용
|
||||||
|
EquipmentItem _createDefaultEquipment(EquipmentSlot slot) {
|
||||||
|
return ItemService.createDefaultEquipmentForSlot(slot);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -279,6 +279,51 @@ class ItemService {
|
|||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 기본 장비 생성
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/// 슬롯별 기본 장비 생성 (Common 등급, 레벨 1)
|
||||||
|
///
|
||||||
|
/// 아레나에서 패배하여 장비를 잃었을 때 빈 슬롯 방지용
|
||||||
|
static EquipmentItem createDefaultEquipmentForSlot(EquipmentSlot slot) {
|
||||||
|
final name = _getDefaultItemName(slot);
|
||||||
|
const rarity = ItemRarity.common;
|
||||||
|
|
||||||
|
// 기본 스탯 (레벨 1 기준)
|
||||||
|
final stats = switch (slot) {
|
||||||
|
EquipmentSlot.weapon => const ItemStats(atk: 2, attackSpeed: 1000),
|
||||||
|
EquipmentSlot.shield => const ItemStats(def: 1, blockRate: 0.05),
|
||||||
|
_ => const ItemStats(def: 1),
|
||||||
|
};
|
||||||
|
|
||||||
|
return EquipmentItem(
|
||||||
|
name: name,
|
||||||
|
slot: slot,
|
||||||
|
level: 1,
|
||||||
|
weight: 1,
|
||||||
|
stats: stats,
|
||||||
|
rarity: rarity,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 슬롯별 기본 장비 이름
|
||||||
|
static String _getDefaultItemName(EquipmentSlot slot) {
|
||||||
|
return switch (slot) {
|
||||||
|
EquipmentSlot.weapon => 'Wooden Stick',
|
||||||
|
EquipmentSlot.shield => 'Wooden Shield',
|
||||||
|
EquipmentSlot.helm => 'Cloth Cap',
|
||||||
|
EquipmentSlot.hauberk => 'Torn Shirt',
|
||||||
|
EquipmentSlot.brassairts => 'Cloth Wraps',
|
||||||
|
EquipmentSlot.vambraces => 'Worn Bracers',
|
||||||
|
EquipmentSlot.gauntlets => 'Tattered Gloves',
|
||||||
|
EquipmentSlot.gambeson => 'Ragged Tunic',
|
||||||
|
EquipmentSlot.cuisses => 'Worn Pants',
|
||||||
|
EquipmentSlot.greaves => 'Cloth Leggings',
|
||||||
|
EquipmentSlot.sollerets => 'Worn Sandals',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 자동 장착
|
// 자동 장착
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
@@ -3,12 +3,13 @@ import 'package:asciineverdie/src/core/model/hall_of_fame.dart';
|
|||||||
|
|
||||||
/// 아레나 대전 정보
|
/// 아레나 대전 정보
|
||||||
///
|
///
|
||||||
/// 도전자와 상대의 정보, 베팅 슬롯을 포함
|
/// 도전자와 상대의 정보, 양방향 베팅 슬롯을 포함
|
||||||
class ArenaMatch {
|
class ArenaMatch {
|
||||||
const ArenaMatch({
|
const ArenaMatch({
|
||||||
required this.challenger,
|
required this.challenger,
|
||||||
required this.opponent,
|
required this.opponent,
|
||||||
required this.bettingSlot,
|
required this.challengerBettingSlot,
|
||||||
|
required this.opponentBettingSlot,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// 도전자 (내 캐릭터)
|
/// 도전자 (내 캐릭터)
|
||||||
@@ -17,14 +18,21 @@ class ArenaMatch {
|
|||||||
/// 상대 캐릭터
|
/// 상대 캐릭터
|
||||||
final HallOfFameEntry opponent;
|
final HallOfFameEntry opponent;
|
||||||
|
|
||||||
/// 베팅 슬롯 (같은 슬롯 교환)
|
/// 도전자 베팅 슬롯 (승리 시 상대에게서 빼앗을 슬롯)
|
||||||
final EquipmentSlot bettingSlot;
|
final EquipmentSlot challengerBettingSlot;
|
||||||
|
|
||||||
|
/// 상대 베팅 슬롯 (상대 승리 시 도전자에게서 빼앗을 슬롯)
|
||||||
|
final EquipmentSlot opponentBettingSlot;
|
||||||
|
|
||||||
/// 도전자 순위
|
/// 도전자 순위
|
||||||
int get challengerRank => 0; // ArenaService에서 계산
|
int get challengerRank => 0; // ArenaService에서 계산
|
||||||
|
|
||||||
/// 상대 순위
|
/// 상대 순위
|
||||||
int get opponentRank => 0; // ArenaService에서 계산
|
int get opponentRank => 0; // ArenaService에서 계산
|
||||||
|
|
||||||
|
/// 기존 bettingSlot 호환용 (deprecated)
|
||||||
|
@Deprecated('Use challengerBettingSlot instead')
|
||||||
|
EquipmentSlot get bettingSlot => challengerBettingSlot;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 아레나 대전 결과
|
/// 아레나 대전 결과
|
||||||
|
|||||||
Reference in New Issue
Block a user