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'; import 'package:asciineverdie/src/core/model/equipment_slot.dart'; import 'package:asciineverdie/src/core/model/game_state.dart'; import 'package:asciineverdie/src/core/model/hall_of_fame.dart'; import 'package:asciineverdie/src/core/model/monster_combat_stats.dart'; import 'package:asciineverdie/src/core/model/skill.dart'; import 'package:asciineverdie/src/core/util/deterministic_random.dart'; /// 아레나 서비스 /// /// 로컬 아레나 대전 시스템의 핵심 로직 담당: /// - 순위 계산 및 상대 결정 /// - 전투 실행 /// - 장비 교환 class ArenaService { ArenaService({DeterministicRandom? rng}) : _rng = rng ?? DeterministicRandom(DateTime.now().millisecondsSinceEpoch); final DeterministicRandom _rng; late final SkillService _skillService = SkillService(rng: _rng); // ============================================================================ // 스킬 시스템 헬퍼 // ============================================================================ /// HallOfFameEntry의 finalSkills에서 Skill 목록 추출 List _getSkillsFromEntry(HallOfFameEntry entry) { final skillData = entry.finalSkills; if (skillData == null || skillData.isEmpty) return []; final skills = []; for (final data in skillData) { final skillName = data['name']; if (skillName != null) { final skill = SkillData.getSkillBySpellName(skillName); if (skill != null) { skills.add(skill); } } } return skills; } /// 스킬 ID 목록 추출 (HallOfFameEntry에서) List _getSkillIdsFromEntry(HallOfFameEntry entry) { return _getSkillsFromEntry(entry).map((s) => s.id).toList(); } /// 스킬 랭크 조회 (HallOfFameEntry의 finalSkills에서) int _getSkillRankFromEntry(HallOfFameEntry entry, String skillId) { final skill = SkillData.getSkillById(skillId); if (skill == null) return 1; final skillData = entry.finalSkills; if (skillData == null || skillData.isEmpty) return 1; for (final data in skillData) { if (data['name'] == skill.name) { final rankStr = data['rank'] ?? 'I'; return _romanToInt(rankStr); } } return 1; } /// 로마 숫자 → 정수 변환 int _romanToInt(String roman) { return switch (roman) { 'I' => 1, 'II' => 2, 'III' => 3, 'IV' => 4, 'V' => 5, _ => 1, }; } // ============================================================================ // 상대 결정 // ============================================================================ /// 상대 결정 (바로 위 순위, 1위면 2위와 대결) /// /// [hallOfFame] 명예의 전당 /// [challengerId] 도전자 캐릭터 ID /// Returns: 상대 캐릭터 (없으면 null) HallOfFameEntry? findOpponent(HallOfFame hallOfFame, String challengerId) { final ranked = hallOfFame.rankedEntries; if (ranked.length < 2) return null; final currentRank = hallOfFame.getRank(challengerId); if (currentRank <= 0) return null; // 1위면 2위와 대결 if (currentRank == 1) { return ranked[1]; } // 그 외는 바로 위 순위와 대결 return ranked[currentRank - 2]; } // ============================================================================ // 전투 실행 // ============================================================================ /// 아레나 전투 실행 /// /// [match] 대전 정보 /// Returns: 대전 결과 (승패, 장비 교환 후 캐릭터) ArenaMatchResult executeCombat(ArenaMatch match) { final calculator = CombatCalculator(rng: _rng); // 도전자 스탯 (풀 HP로 시작) final challengerStats = match.challenger.finalStats; final opponentStats = match.opponent.finalStats; if (challengerStats == null || opponentStats == null) { // 스탯이 없으면 도전자 패배 처리 return ArenaMatchResult( match: match, isVictory: false, turns: 0, updatedChallenger: match.challenger, updatedOpponent: match.opponent, ); } // 플레이어 스탯 (풀 HP로 초기화) var playerCombatStats = challengerStats.copyWith( hpCurrent: challengerStats.hpMax, mpCurrent: challengerStats.mpMax, ); // 상대를 몬스터 형태로 변환 var opponentMonsterStats = MonsterCombatStats.fromCombatStats( opponentStats, match.opponent.characterName, ); // 전투 시뮬레이션 int turns = 0; int playerAccum = 0; int opponentAccum = 0; const tickMs = 200; while (playerCombatStats.isAlive && opponentMonsterStats.isAlive) { playerAccum += tickMs; opponentAccum += tickMs; // 플레이어 공격 if (playerAccum >= playerCombatStats.attackDelayMs) { final result = calculator.playerAttackMonster( attacker: playerCombatStats, defender: opponentMonsterStats, ); opponentMonsterStats = result.updatedDefender; playerAccum = 0; turns++; } // 상대 공격 (살아있을 때만) if (opponentMonsterStats.isAlive && opponentAccum >= opponentMonsterStats.attackDelayMs) { final result = calculator.monsterAttackPlayer( attacker: opponentMonsterStats, defender: playerCombatStats, ); playerCombatStats = result.updatedDefender; opponentAccum = 0; } // 무한 루프 방지 if (turns > 1000) break; } final isVictory = playerCombatStats.isAlive; // 장비 교환 final (updatedChallenger, updatedOpponent) = _exchangeEquipment( match: match, isVictory: isVictory, ); return ArenaMatchResult( match: match, isVictory: isVictory, turns: turns, updatedChallenger: updatedChallenger, updatedOpponent: updatedOpponent, ); } /// 시뮬레이션 결과를 기반으로 전투 결과 생성 /// /// [match] 대전 정보 /// [challengerHp] 도전자 최종 HP /// [opponentHp] 상대 최종 HP /// [turns] 총 턴 수 /// Returns: 대전 결과 (승패, 장비 교환 후 캐릭터) ArenaMatchResult createResultFromSimulation({ required ArenaMatch match, required int challengerHp, required int opponentHp, required int turns, }) { // 도전자 HP가 0보다 크면 승리 final isVictory = challengerHp > 0 && opponentHp <= 0; // 장비 교환 final (updatedChallenger, updatedOpponent) = _exchangeEquipment( match: match, isVictory: isVictory, ); return ArenaMatchResult( match: match, isVictory: isVictory, turns: turns, updatedChallenger: updatedChallenger, updatedOpponent: updatedOpponent, ); } /// 전투 시뮬레이션 (애니메이션용 스트림) /// /// progress_service._processCombatTickWithSkills()와 동일한 로직 사용 /// [match] 대전 정보 /// Returns: 턴별 전투 상황 스트림 Stream simulateCombat(ArenaMatch match) async* { final calculator = CombatCalculator(rng: _rng); final challengerStats = match.challenger.finalStats; final opponentStats = match.opponent.finalStats; if (challengerStats == null || opponentStats == null) { return; } // 스킬 ID 목록 로드 (SkillBook과 동일한 방식) var challengerSkillIds = _getSkillIdsFromEntry(match.challenger); var opponentSkillIds = _getSkillIdsFromEntry(match.opponent); // 스킬이 없으면 기본 스킬 사용 if (challengerSkillIds.isEmpty) { challengerSkillIds = SkillData.defaultSkillIds; } if (opponentSkillIds.isEmpty) { opponentSkillIds = SkillData.defaultSkillIds; } // 스킬 시스템 상태 초기화 var challengerSkillSystem = SkillSystemState.empty(); var opponentSkillSystem = SkillSystemState.empty(); // DOT 및 디버프 추적 (일반 전투와 동일) var challengerDoTs = []; var opponentDoTs = []; var challengerDebuffs = []; var opponentDebuffs = []; var playerCombatStats = challengerStats.copyWith( hpCurrent: challengerStats.hpMax, mpCurrent: challengerStats.mpMax, ); var opponentCombatStats = opponentStats.copyWith( hpCurrent: opponentStats.hpMax, mpCurrent: opponentStats.mpMax, ); var opponentMonsterStats = MonsterCombatStats.fromCombatStats( opponentCombatStats, match.opponent.characterName, ); var challengerMonsterStats = MonsterCombatStats.fromCombatStats( playerCombatStats, match.challenger.characterName, ); int playerAccum = 0; int opponentAccum = 0; int elapsedMs = 0; const tickMs = 200; int turns = 0; // 초기 상태 전송 yield ArenaCombatTurn( challengerHp: playerCombatStats.hpCurrent, opponentHp: opponentCombatStats.hpCurrent, challengerHpMax: playerCombatStats.hpMax, opponentHpMax: opponentCombatStats.hpMax, challengerMp: playerCombatStats.mpCurrent, opponentMp: opponentCombatStats.mpCurrent, challengerMpMax: playerCombatStats.mpMax, opponentMpMax: opponentCombatStats.mpMax, ); while (playerCombatStats.isAlive && opponentCombatStats.hpCurrent > 0) { playerAccum += tickMs; opponentAccum += tickMs; elapsedMs += tickMs; // 스킬 시스템 시간 업데이트 challengerSkillSystem = challengerSkillSystem.copyWith(elapsedMs: elapsedMs); opponentSkillSystem = opponentSkillSystem.copyWith(elapsedMs: elapsedMs); int? challengerDamage; int? opponentDamage; bool isChallengerCritical = false; bool isOpponentCritical = false; bool isChallengerEvaded = false; bool isOpponentEvaded = false; bool isChallengerBlocked = false; bool isOpponentBlocked = false; String? challengerSkillUsed; String? opponentSkillUsed; int? challengerHealAmount; int? opponentHealAmount; // ========================================================================= // DOT 틱 처리 (도전자 → 상대에게 적용된 DOT) // ========================================================================= var dotDamageToOpponent = 0; final updatedChallengerDoTs = []; for (final dot in challengerDoTs) { final (updatedDot, ticksTriggered) = dot.tick(tickMs); if (ticksTriggered > 0) { dotDamageToOpponent += dot.damagePerTick * ticksTriggered; } if (updatedDot.isActive) { updatedChallengerDoTs.add(updatedDot); } } challengerDoTs = updatedChallengerDoTs; if (dotDamageToOpponent > 0 && opponentCombatStats.hpCurrent > 0) { opponentCombatStats = opponentCombatStats.copyWith( hpCurrent: (opponentCombatStats.hpCurrent - dotDamageToOpponent) .clamp(0, opponentCombatStats.hpMax), ); } // DOT 틱 처리 (상대 → 도전자에게 적용된 DOT) var dotDamageToChallenger = 0; final updatedOpponentDoTs = []; for (final dot in opponentDoTs) { final (updatedDot, ticksTriggered) = dot.tick(tickMs); if (ticksTriggered > 0) { dotDamageToChallenger += dot.damagePerTick * ticksTriggered; } if (updatedDot.isActive) { updatedOpponentDoTs.add(updatedDot); } } opponentDoTs = updatedOpponentDoTs; if (dotDamageToChallenger > 0 && playerCombatStats.isAlive) { playerCombatStats = playerCombatStats.copyWith( hpCurrent: (playerCombatStats.hpCurrent - dotDamageToChallenger) .clamp(0, playerCombatStats.hpMax), ); } // ========================================================================= // 만료된 디버프 정리 // ========================================================================= challengerDebuffs = challengerDebuffs .where((ActiveBuff d) => !d.isExpired(elapsedMs)) .toList(); opponentDebuffs = opponentDebuffs .where((ActiveBuff d) => !d.isExpired(elapsedMs)) .toList(); // ========================================================================= // 도전자 턴 (selectAutoSkill 사용 - 일반 전투와 동일) // ========================================================================= if (playerAccum >= playerCombatStats.attackDelayMs) { playerAccum = 0; // 상대 몬스터 스탯 동기화 opponentMonsterStats = MonsterCombatStats.fromCombatStats( opponentCombatStats, match.opponent.characterName, ); // 스킬 자동 선택 (progress_service와 동일한 로직) final selectedSkill = _skillService.selectAutoSkill( player: playerCombatStats, monster: opponentMonsterStats, skillSystem: challengerSkillSystem, availableSkillIds: challengerSkillIds, activeDoTs: challengerDoTs, activeDebuffs: opponentDebuffs, ); if (selectedSkill != null && selectedSkill.isAttack) { // 스킬 랭크 조회 및 적용 final skillRank = _getSkillRankFromEntry( match.challenger, selectedSkill.id, ); final skillResult = _skillService.useAttackSkillWithRank( skill: selectedSkill, player: playerCombatStats, monster: opponentMonsterStats, skillSystem: challengerSkillSystem, rank: skillRank, ); playerCombatStats = skillResult.updatedPlayer; opponentCombatStats = opponentCombatStats.copyWith( hpCurrent: skillResult.updatedMonster.hpCurrent, ); challengerSkillSystem = skillResult.updatedSkillSystem; challengerSkillUsed = selectedSkill.name; challengerDamage = skillResult.result.damage; } else if (selectedSkill != null && selectedSkill.isDot) { // DOT 스킬 사용 final skillResult = _skillService.useDotSkill( skill: selectedSkill, player: playerCombatStats, skillSystem: challengerSkillSystem, playerInt: playerCombatStats.atk ~/ 10, playerWis: playerCombatStats.def ~/ 10, ); playerCombatStats = skillResult.updatedPlayer; challengerSkillSystem = skillResult.updatedSkillSystem; if (skillResult.dotEffect != null) { challengerDoTs.add(skillResult.dotEffect!); } challengerSkillUsed = selectedSkill.name; } else if (selectedSkill != null && selectedSkill.isHeal) { // 회복 스킬 사용 final skillResult = _skillService.useHealSkill( skill: selectedSkill, player: playerCombatStats, skillSystem: challengerSkillSystem, ); playerCombatStats = skillResult.updatedPlayer; challengerSkillSystem = skillResult.updatedSkillSystem; challengerSkillUsed = selectedSkill.name; challengerHealAmount = skillResult.result.healedAmount; } else if (selectedSkill != null && selectedSkill.isBuff) { // 버프 스킬 사용 final skillResult = _skillService.useBuffSkill( skill: selectedSkill, player: playerCombatStats, skillSystem: challengerSkillSystem, ); playerCombatStats = skillResult.updatedPlayer; challengerSkillSystem = skillResult.updatedSkillSystem; challengerSkillUsed = selectedSkill.name; } else if (selectedSkill != null && selectedSkill.isDebuff) { // 디버프 스킬 사용 final skillResult = _skillService.useDebuffSkill( skill: selectedSkill, player: playerCombatStats, skillSystem: challengerSkillSystem, currentDebuffs: opponentDebuffs, ); playerCombatStats = skillResult.updatedPlayer; challengerSkillSystem = skillResult.updatedSkillSystem; final debuffEffect = skillResult.debuffEffect; if (debuffEffect != null) { opponentDebuffs = opponentDebuffs .where((ActiveBuff d) => d.effect.id != debuffEffect.effect.id) .toList() ..add(debuffEffect); } challengerSkillUsed = selectedSkill.name; } else { // 일반 공격 final result = calculator.playerAttackMonster( attacker: playerCombatStats, defender: opponentMonsterStats, ); opponentCombatStats = opponentCombatStats.copyWith( hpCurrent: result.updatedDefender.hpCurrent, ); if (result.result.isHit) { challengerDamage = result.result.damage; isChallengerCritical = result.result.isCritical; } else { isOpponentEvaded = true; } } } // ========================================================================= // 상대 턴 (selectAutoSkill 사용 - 일반 전투와 동일) // ========================================================================= if (opponentCombatStats.hpCurrent > 0 && opponentAccum >= opponentCombatStats.attackDelayMs) { opponentAccum = 0; // 도전자 몬스터 스탯 동기화 challengerMonsterStats = MonsterCombatStats.fromCombatStats( playerCombatStats, match.challenger.characterName, ); // 스킬 자동 선택 (progress_service와 동일한 로직) final selectedSkill = _skillService.selectAutoSkill( player: opponentCombatStats, monster: challengerMonsterStats, skillSystem: opponentSkillSystem, availableSkillIds: opponentSkillIds, activeDoTs: opponentDoTs, activeDebuffs: challengerDebuffs, ); if (selectedSkill != null && selectedSkill.isAttack) { // 스킬 랭크 조회 및 적용 final skillRank = _getSkillRankFromEntry( match.opponent, selectedSkill.id, ); final skillResult = _skillService.useAttackSkillWithRank( skill: selectedSkill, player: opponentCombatStats, monster: challengerMonsterStats, skillSystem: opponentSkillSystem, rank: skillRank, ); opponentCombatStats = skillResult.updatedPlayer; playerCombatStats = playerCombatStats.copyWith( hpCurrent: skillResult.updatedMonster.hpCurrent, ); opponentSkillSystem = skillResult.updatedSkillSystem; opponentSkillUsed = selectedSkill.name; opponentDamage = skillResult.result.damage; } else if (selectedSkill != null && selectedSkill.isDot) { // DOT 스킬 사용 final skillResult = _skillService.useDotSkill( skill: selectedSkill, player: opponentCombatStats, skillSystem: opponentSkillSystem, playerInt: opponentCombatStats.atk ~/ 10, playerWis: opponentCombatStats.def ~/ 10, ); opponentCombatStats = skillResult.updatedPlayer; opponentSkillSystem = skillResult.updatedSkillSystem; if (skillResult.dotEffect != null) { opponentDoTs.add(skillResult.dotEffect!); } opponentSkillUsed = selectedSkill.name; } else if (selectedSkill != null && selectedSkill.isHeal) { // 회복 스킬 사용 final skillResult = _skillService.useHealSkill( skill: selectedSkill, player: opponentCombatStats, skillSystem: opponentSkillSystem, ); opponentCombatStats = skillResult.updatedPlayer; opponentSkillSystem = skillResult.updatedSkillSystem; opponentSkillUsed = selectedSkill.name; opponentHealAmount = skillResult.result.healedAmount; } else if (selectedSkill != null && selectedSkill.isBuff) { // 버프 스킬 사용 final skillResult = _skillService.useBuffSkill( skill: selectedSkill, player: opponentCombatStats, skillSystem: opponentSkillSystem, ); opponentCombatStats = skillResult.updatedPlayer; opponentSkillSystem = skillResult.updatedSkillSystem; opponentSkillUsed = selectedSkill.name; } else if (selectedSkill != null && selectedSkill.isDebuff) { // 디버프 스킬 사용 final skillResult = _skillService.useDebuffSkill( skill: selectedSkill, player: opponentCombatStats, skillSystem: opponentSkillSystem, currentDebuffs: challengerDebuffs, ); opponentCombatStats = skillResult.updatedPlayer; opponentSkillSystem = skillResult.updatedSkillSystem; final debuffEffect = skillResult.debuffEffect; if (debuffEffect != null) { challengerDebuffs = challengerDebuffs .where((ActiveBuff d) => d.effect.id != debuffEffect.effect.id) .toList() ..add(debuffEffect); } opponentSkillUsed = selectedSkill.name; } else { // 일반 공격 (디버프 효과 적용) var debuffedOpponent = opponentCombatStats; if (challengerDebuffs.isNotEmpty) { double atkMod = 0; for (final debuff in challengerDebuffs) { if (!debuff.isExpired(elapsedMs)) { atkMod += debuff.effect.atkModifier; } } final newAtk = (opponentCombatStats.atk * (1 + atkMod)) .round() .clamp(opponentCombatStats.atk ~/ 10, opponentCombatStats.atk); debuffedOpponent = opponentCombatStats.copyWith(atk: newAtk); } opponentMonsterStats = MonsterCombatStats.fromCombatStats( debuffedOpponent, match.opponent.characterName, ); final result = calculator.monsterAttackPlayer( attacker: opponentMonsterStats, defender: playerCombatStats, ); playerCombatStats = result.updatedDefender; if (result.result.isHit) { opponentDamage = result.result.damage; isOpponentCritical = result.result.isCritical; isChallengerBlocked = result.result.isBlocked; } else { isChallengerEvaded = true; } } } // 액션이 발생했을 때만 턴 전송 final hasAction = challengerDamage != null || opponentDamage != null || challengerHealAmount != null || opponentHealAmount != null || challengerSkillUsed != null || opponentSkillUsed != null; if (hasAction) { turns++; yield ArenaCombatTurn( challengerDamage: challengerDamage, opponentDamage: opponentDamage, challengerHp: playerCombatStats.hpCurrent, opponentHp: opponentCombatStats.hpCurrent, challengerHpMax: playerCombatStats.hpMax, opponentHpMax: opponentCombatStats.hpMax, challengerMp: playerCombatStats.mpCurrent, opponentMp: opponentCombatStats.mpCurrent, challengerMpMax: playerCombatStats.mpMax, opponentMpMax: opponentCombatStats.mpMax, isChallengerCritical: isChallengerCritical, isOpponentCritical: isOpponentCritical, isChallengerEvaded: isChallengerEvaded, isOpponentEvaded: isOpponentEvaded, isChallengerBlocked: isChallengerBlocked, isOpponentBlocked: isOpponentBlocked, challengerSkillUsed: challengerSkillUsed, opponentSkillUsed: opponentSkillUsed, challengerHealAmount: challengerHealAmount, opponentHealAmount: opponentHealAmount, ); // 애니메이션을 위한 딜레이 await Future.delayed(const Duration(milliseconds: 100)); } // 무한 루프 방지 if (turns > 1000) break; } } // ============================================================================ // 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 getBettableSlots() { return EquipmentSlot.values .where((slot) => slot != EquipmentSlot.weapon) .toList(); } // ============================================================================ // 장비 약탈 // ============================================================================ /// 장비 약탈 (승자가 패자의 베팅 슬롯 장비 획득) /// /// - 승자: 자신이 선택한 슬롯의 패자 장비 획득 /// - 패자: 해당 슬롯 장비 손실 → 기본 장비로 대체 (HallOfFameEntry, HallOfFameEntry) _exchangeEquipment({ required ArenaMatch match, required bool isVictory, }) { // 도전자 장비 목록 복사 final challengerEquipment = List.from(match.challenger.finalEquipment ?? []); // 상대 장비 목록 복사 final opponentEquipment = List.from(match.opponent.finalEquipment ?? []); if (isVictory) { // 도전자 승리: 도전자가 선택한 슬롯의 상대 장비 획득 final winnerSlot = match.challengerBettingSlot; final lootedItem = _findItemBySlot(opponentEquipment, winnerSlot); // 도전자: 약탈한 장비로 교체 _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( finalEquipment: challengerEquipment, ); final updatedOpponent = match.opponent.copyWith( finalEquipment: opponentEquipment, ); return (updatedChallenger, updatedOpponent); } /// 슬롯으로 장비 찾기 EquipmentItem _findItemBySlot( List equipment, EquipmentSlot slot) { for (final item in equipment) { if (item.slot == slot) return item; } return EquipmentItem.empty(slot); } /// 장비 목록에서 특정 슬롯의 아이템 교체 void _replaceItemInList( List equipment, EquipmentSlot slot, EquipmentItem newItem, ) { for (var i = 0; i < equipment.length; i++) { if (equipment[i].slot == slot) { equipment[i] = newItem; return; } } // 슬롯이 없으면 추가 equipment.add(newItem); } /// 기본 장비 생성 (Common 등급) /// /// 패자가 장비를 잃었을 때 빈 슬롯 방지용 EquipmentItem _createDefaultEquipment(EquipmentSlot slot) { return ItemService.createDefaultEquipmentForSlot(slot); } }