feat(arena): 아레나 서비스 및 아이템 서비스 개선

- ArenaService 로직 확장
- ArenaMatch 모델 업데이트
- ItemService 아레나 지원 추가
This commit is contained in:
JiWoong Sul
2026-01-07 20:21:50 +09:00
parent c3a8bc305a
commit 699ae3b7f3
3 changed files with 132 additions and 15 deletions

View File

@@ -1,5 +1,6 @@
import 'package:asciineverdie/data/skill_data.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/model/arena_match.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({
required ArenaMatch match,
required bool isVictory,
}) {
final slot = match.bettingSlot;
// 도전자 장비 목록 복사
final challengerEquipment =
List<EquipmentItem>.from(match.challenger.finalEquipment ?? []);
@@ -688,13 +729,29 @@ class ArenaService {
final opponentEquipment =
List<EquipmentItem>.from(match.opponent.finalEquipment ?? []);
// 해당 슬롯의 장비 찾기
final challengerItem = _findItemBySlot(challengerEquipment, slot);
final opponentItem = _findItemBySlot(opponentEquipment, slot);
if (isVictory) {
// 도전자 승리: 도전자가 선택한 슬롯의 상대 장비 획득
final winnerSlot = match.challengerBettingSlot;
final lootedItem = _findItemBySlot(opponentEquipment, winnerSlot);
// 장비 교
_replaceItemInList(challengerEquipment, slot, opponentItem);
_replaceItemInList(opponentEquipment, slot, challengerItem);
// 도전자: 약탈한 장비
_replaceItemInList(challengerEquipment, winnerSlot, lootedItem);
// 상대: 해당 슬롯 기본 장비로 대체
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(
@@ -731,4 +788,11 @@ class ArenaService {
// 슬롯이 없으면 추가
equipment.add(newItem);
}
/// 기본 장비 생성 (Common 등급)
///
/// 패자가 장비를 잃었을 때 빈 슬롯 방지용
EquipmentItem _createDefaultEquipment(EquipmentSlot slot) {
return ItemService.createDefaultEquipmentForSlot(slot);
}
}