refactor(engine): 전투 틱 로직을 CombatTickService로 분리
- ProgressService에서 전투 처리 로직 추출 - 스킬 자동 사용, DOT, 물약 사용 로직 포함 - CombatTickResult 결과 클래스 정의
This commit is contained in:
580
lib/src/core/engine/combat_tick_service.dart
Normal file
580
lib/src/core/engine/combat_tick_service.dart
Normal file
@@ -0,0 +1,580 @@
|
||||
import 'package:asciineverdie/data/skill_data.dart';
|
||||
import 'package:asciineverdie/src/core/engine/combat_calculator.dart';
|
||||
import 'package:asciineverdie/src/core/engine/potion_service.dart';
|
||||
import 'package:asciineverdie/src/core/engine/skill_service.dart';
|
||||
import 'package:asciineverdie/src/core/model/combat_event.dart';
|
||||
import 'package:asciineverdie/src/core/model/combat_state.dart';
|
||||
import 'package:asciineverdie/src/core/model/combat_stats.dart';
|
||||
import 'package:asciineverdie/src/core/model/game_state.dart';
|
||||
import 'package:asciineverdie/src/core/model/monster_combat_stats.dart';
|
||||
import 'package:asciineverdie/src/core/model/potion.dart';
|
||||
import 'package:asciineverdie/src/core/model/skill.dart';
|
||||
import 'package:asciineverdie/src/core/util/deterministic_random.dart';
|
||||
|
||||
/// 전투 틱 처리 결과
|
||||
class CombatTickResult {
|
||||
const CombatTickResult({
|
||||
required this.combat,
|
||||
required this.skillSystem,
|
||||
this.potionInventory,
|
||||
});
|
||||
|
||||
final CombatState combat;
|
||||
final SkillSystemState skillSystem;
|
||||
final PotionInventory? potionInventory;
|
||||
}
|
||||
|
||||
/// 전투 틱 처리 서비스
|
||||
///
|
||||
/// ProgressService에서 분리된 전투 로직 담당:
|
||||
/// - 스킬 자동 사용
|
||||
/// - DOT 처리
|
||||
/// - 물약 자동 사용
|
||||
/// - 플레이어/몬스터 공격 처리
|
||||
class CombatTickService {
|
||||
CombatTickService({required this.rng});
|
||||
|
||||
final DeterministicRandom rng;
|
||||
|
||||
/// 전투 틱 처리 (스킬 자동 사용, DOT, 물약 포함)
|
||||
///
|
||||
/// [state] 현재 게임 상태
|
||||
/// [combat] 현재 전투 상태
|
||||
/// [skillSystem] 스킬 시스템 상태
|
||||
/// [elapsedMs] 경과 시간 (밀리초)
|
||||
CombatTickResult processTick({
|
||||
required GameState state,
|
||||
required CombatState combat,
|
||||
required SkillSystemState skillSystem,
|
||||
required int elapsedMs,
|
||||
}) {
|
||||
if (!combat.isActive || combat.isCombatOver) {
|
||||
return CombatTickResult(
|
||||
combat: combat,
|
||||
skillSystem: skillSystem,
|
||||
potionInventory: null,
|
||||
);
|
||||
}
|
||||
|
||||
final calculator = CombatCalculator(rng: rng);
|
||||
final skillService = SkillService(rng: rng);
|
||||
final potionService = const PotionService();
|
||||
var playerStats = combat.playerStats;
|
||||
var monsterStats = combat.monsterStats;
|
||||
var playerAccumulator = combat.playerAttackAccumulatorMs + elapsedMs;
|
||||
var monsterAccumulator = combat.monsterAttackAccumulatorMs + elapsedMs;
|
||||
var totalDamageDealt = combat.totalDamageDealt;
|
||||
var totalDamageTaken = combat.totalDamageTaken;
|
||||
var turnsElapsed = combat.turnsElapsed;
|
||||
var updatedSkillSystem = skillSystem;
|
||||
var activeDoTs = [...combat.activeDoTs];
|
||||
var usedPotionTypes = {...combat.usedPotionTypes};
|
||||
var activeDebuffs = [...combat.activeDebuffs];
|
||||
PotionInventory? updatedPotionInventory;
|
||||
|
||||
// 새 전투 이벤트 수집
|
||||
final newEvents = <CombatEvent>[];
|
||||
final timestamp = updatedSkillSystem.elapsedMs;
|
||||
|
||||
// 만료된 디버프 정리
|
||||
activeDebuffs = activeDebuffs
|
||||
.where((debuff) => !debuff.isExpired(timestamp))
|
||||
.toList();
|
||||
|
||||
// DOT 틱 처리
|
||||
final dotResult = _processDotTicks(
|
||||
activeDoTs: activeDoTs,
|
||||
monsterStats: monsterStats,
|
||||
elapsedMs: elapsedMs,
|
||||
timestamp: timestamp,
|
||||
totalDamageDealt: totalDamageDealt,
|
||||
);
|
||||
activeDoTs = dotResult.activeDoTs;
|
||||
monsterStats = dotResult.monsterStats;
|
||||
totalDamageDealt = dotResult.totalDamageDealt;
|
||||
newEvents.addAll(dotResult.events);
|
||||
|
||||
// 긴급 물약 자동 사용 (HP < 30%)
|
||||
final potionResult = _tryEmergencyPotion(
|
||||
playerStats: playerStats,
|
||||
potionInventory: state.potionInventory,
|
||||
usedPotionTypes: usedPotionTypes,
|
||||
playerLevel: state.traits.level,
|
||||
timestamp: timestamp,
|
||||
potionService: potionService,
|
||||
);
|
||||
if (potionResult != null) {
|
||||
playerStats = potionResult.playerStats;
|
||||
usedPotionTypes = potionResult.usedPotionTypes;
|
||||
updatedPotionInventory = potionResult.potionInventory;
|
||||
newEvents.addAll(potionResult.events);
|
||||
}
|
||||
|
||||
// 플레이어 공격 체크
|
||||
if (playerAccumulator >= playerStats.attackDelayMs) {
|
||||
final attackResult = _processPlayerAttack(
|
||||
state: state,
|
||||
playerStats: playerStats,
|
||||
monsterStats: monsterStats,
|
||||
updatedSkillSystem: updatedSkillSystem,
|
||||
activeDoTs: activeDoTs,
|
||||
activeDebuffs: activeDebuffs,
|
||||
totalDamageDealt: totalDamageDealt,
|
||||
timestamp: timestamp,
|
||||
calculator: calculator,
|
||||
skillService: skillService,
|
||||
);
|
||||
|
||||
playerStats = attackResult.playerStats;
|
||||
monsterStats = attackResult.monsterStats;
|
||||
updatedSkillSystem = attackResult.skillSystem;
|
||||
activeDoTs = attackResult.activeDoTs;
|
||||
activeDebuffs = attackResult.activeDebuffs;
|
||||
totalDamageDealt = attackResult.totalDamageDealt;
|
||||
newEvents.addAll(attackResult.events);
|
||||
|
||||
playerAccumulator -= playerStats.attackDelayMs;
|
||||
turnsElapsed++;
|
||||
}
|
||||
|
||||
// 몬스터가 살아있으면 반격
|
||||
if (monsterStats.isAlive &&
|
||||
monsterAccumulator >= monsterStats.attackDelayMs) {
|
||||
final monsterAttackResult = _processMonsterAttack(
|
||||
playerStats: playerStats,
|
||||
monsterStats: monsterStats,
|
||||
activeDebuffs: activeDebuffs,
|
||||
totalDamageTaken: totalDamageTaken,
|
||||
timestamp: timestamp,
|
||||
calculator: calculator,
|
||||
);
|
||||
|
||||
playerStats = monsterAttackResult.playerStats;
|
||||
totalDamageTaken = monsterAttackResult.totalDamageTaken;
|
||||
newEvents.addAll(monsterAttackResult.events);
|
||||
monsterAccumulator -= monsterStats.attackDelayMs;
|
||||
}
|
||||
|
||||
// 전투 종료 체크
|
||||
final isActive = playerStats.isAlive && monsterStats.isAlive;
|
||||
|
||||
// 기존 이벤트와 합쳐서 최대 10개 유지
|
||||
final combinedEvents = [...combat.recentEvents, ...newEvents];
|
||||
final recentEvents = combinedEvents.length > 10
|
||||
? combinedEvents.sublist(combinedEvents.length - 10)
|
||||
: combinedEvents;
|
||||
|
||||
return CombatTickResult(
|
||||
combat: combat.copyWith(
|
||||
playerStats: playerStats,
|
||||
monsterStats: monsterStats,
|
||||
playerAttackAccumulatorMs: playerAccumulator,
|
||||
monsterAttackAccumulatorMs: monsterAccumulator,
|
||||
totalDamageDealt: totalDamageDealt,
|
||||
totalDamageTaken: totalDamageTaken,
|
||||
turnsElapsed: turnsElapsed,
|
||||
isActive: isActive,
|
||||
recentEvents: recentEvents,
|
||||
activeDoTs: activeDoTs,
|
||||
usedPotionTypes: usedPotionTypes,
|
||||
activeDebuffs: activeDebuffs,
|
||||
),
|
||||
skillSystem: updatedSkillSystem,
|
||||
potionInventory: updatedPotionInventory,
|
||||
);
|
||||
}
|
||||
|
||||
/// DOT 틱 처리
|
||||
({
|
||||
List<DotEffect> activeDoTs,
|
||||
MonsterCombatStats monsterStats,
|
||||
int totalDamageDealt,
|
||||
List<CombatEvent> events,
|
||||
}) _processDotTicks({
|
||||
required List<DotEffect> activeDoTs,
|
||||
required MonsterCombatStats monsterStats,
|
||||
required int elapsedMs,
|
||||
required int timestamp,
|
||||
required int totalDamageDealt,
|
||||
}) {
|
||||
var dotDamageThisTick = 0;
|
||||
final updatedDoTs = <DotEffect>[];
|
||||
final events = <CombatEvent>[];
|
||||
var updatedMonster = monsterStats;
|
||||
|
||||
for (final dot in activeDoTs) {
|
||||
final (updatedDot, ticksTriggered) = dot.tick(elapsedMs);
|
||||
|
||||
if (ticksTriggered > 0) {
|
||||
final damage = dot.damagePerTick * ticksTriggered;
|
||||
dotDamageThisTick += damage;
|
||||
|
||||
// DOT 데미지 이벤트 생성
|
||||
final dotSkillName =
|
||||
SkillData.getSkillById(dot.skillId)?.name ?? dot.skillId;
|
||||
events.add(
|
||||
CombatEvent.dotTick(
|
||||
timestamp: timestamp,
|
||||
skillName: dotSkillName,
|
||||
damage: damage,
|
||||
targetName: updatedMonster.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 만료되지 않은 DOT만 유지
|
||||
if (updatedDot.isActive) {
|
||||
updatedDoTs.add(updatedDot);
|
||||
}
|
||||
}
|
||||
|
||||
// DOT 데미지 적용
|
||||
if (dotDamageThisTick > 0 && updatedMonster.isAlive) {
|
||||
final newMonsterHp = (updatedMonster.hpCurrent - dotDamageThisTick).clamp(
|
||||
0,
|
||||
updatedMonster.hpMax,
|
||||
);
|
||||
updatedMonster = updatedMonster.copyWith(hpCurrent: newMonsterHp);
|
||||
totalDamageDealt += dotDamageThisTick;
|
||||
}
|
||||
|
||||
return (
|
||||
activeDoTs: updatedDoTs,
|
||||
monsterStats: updatedMonster,
|
||||
totalDamageDealt: totalDamageDealt,
|
||||
events: events,
|
||||
);
|
||||
}
|
||||
|
||||
/// 긴급 물약 자동 사용
|
||||
({
|
||||
CombatStats playerStats,
|
||||
Set<PotionType> usedPotionTypes,
|
||||
PotionInventory potionInventory,
|
||||
List<CombatEvent> events,
|
||||
})? _tryEmergencyPotion({
|
||||
required CombatStats playerStats,
|
||||
required PotionInventory potionInventory,
|
||||
required Set<PotionType> usedPotionTypes,
|
||||
required int playerLevel,
|
||||
required int timestamp,
|
||||
required PotionService potionService,
|
||||
}) {
|
||||
final hpRatio = playerStats.hpCurrent / playerStats.hpMax;
|
||||
if (hpRatio > PotionService.emergencyHpThreshold) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final emergencyPotion = potionService.selectEmergencyHpPotion(
|
||||
currentHp: playerStats.hpCurrent,
|
||||
maxHp: playerStats.hpMax,
|
||||
inventory: potionInventory,
|
||||
playerLevel: playerLevel,
|
||||
);
|
||||
|
||||
if (emergencyPotion == null || usedPotionTypes.contains(PotionType.hp)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final result = potionService.usePotion(
|
||||
potionId: emergencyPotion.id,
|
||||
inventory: potionInventory,
|
||||
currentHp: playerStats.hpCurrent,
|
||||
maxHp: playerStats.hpMax,
|
||||
currentMp: playerStats.mpCurrent,
|
||||
maxMp: playerStats.mpMax,
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
playerStats: playerStats.copyWith(hpCurrent: result.newHp),
|
||||
usedPotionTypes: {...usedPotionTypes, PotionType.hp},
|
||||
potionInventory: result.newInventory!,
|
||||
events: [
|
||||
CombatEvent.playerPotion(
|
||||
timestamp: timestamp,
|
||||
potionName: emergencyPotion.name,
|
||||
healAmount: result.healedAmount,
|
||||
isHp: true,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// 플레이어 공격 처리
|
||||
({
|
||||
CombatStats playerStats,
|
||||
MonsterCombatStats monsterStats,
|
||||
SkillSystemState skillSystem,
|
||||
List<DotEffect> activeDoTs,
|
||||
List<ActiveBuff> activeDebuffs,
|
||||
int totalDamageDealt,
|
||||
List<CombatEvent> events,
|
||||
}) _processPlayerAttack({
|
||||
required GameState state,
|
||||
required CombatStats playerStats,
|
||||
required MonsterCombatStats monsterStats,
|
||||
required SkillSystemState updatedSkillSystem,
|
||||
required List<DotEffect> activeDoTs,
|
||||
required List<ActiveBuff> activeDebuffs,
|
||||
required int totalDamageDealt,
|
||||
required int timestamp,
|
||||
required CombatCalculator calculator,
|
||||
required SkillService skillService,
|
||||
}) {
|
||||
final events = <CombatEvent>[];
|
||||
var newPlayerStats = playerStats;
|
||||
var newMonsterStats = monsterStats;
|
||||
var newSkillSystem = updatedSkillSystem;
|
||||
var newActiveDoTs = [...activeDoTs];
|
||||
var newActiveBuffs = [...activeDebuffs];
|
||||
var newTotalDamageDealt = totalDamageDealt;
|
||||
|
||||
// 장착된 스킬 슬롯에서 사용 가능한 스킬 ID 목록 조회
|
||||
var availableSkillIds = state.skillSystem.equippedSkills.allSkills
|
||||
.map((s) => s.id)
|
||||
.toList();
|
||||
// 장착된 스킬이 없으면 기본 스킬 사용
|
||||
if (availableSkillIds.isEmpty) {
|
||||
availableSkillIds = SkillData.defaultSkillIds;
|
||||
}
|
||||
|
||||
final selectedSkill = skillService.selectAutoSkill(
|
||||
player: newPlayerStats,
|
||||
monster: newMonsterStats,
|
||||
skillSystem: newSkillSystem,
|
||||
availableSkillIds: availableSkillIds,
|
||||
activeDoTs: newActiveDoTs,
|
||||
activeDebuffs: newActiveBuffs,
|
||||
);
|
||||
|
||||
if (selectedSkill != null && selectedSkill.isAttack) {
|
||||
// 스킬 랭크 조회
|
||||
final skillRank = skillService.getSkillRankFromSkillBook(
|
||||
state.skillBook,
|
||||
selectedSkill.id,
|
||||
);
|
||||
// 랭크 스케일링 적용된 공격 스킬 사용
|
||||
final skillResult = skillService.useAttackSkillWithRank(
|
||||
skill: selectedSkill,
|
||||
player: newPlayerStats,
|
||||
monster: newMonsterStats,
|
||||
skillSystem: newSkillSystem,
|
||||
rank: skillRank,
|
||||
);
|
||||
newPlayerStats = skillResult.updatedPlayer;
|
||||
newMonsterStats = skillResult.updatedMonster;
|
||||
newTotalDamageDealt += skillResult.result.damage;
|
||||
newSkillSystem = skillResult.updatedSkillSystem.startGlobalCooldown();
|
||||
|
||||
events.add(
|
||||
CombatEvent.playerSkill(
|
||||
timestamp: timestamp,
|
||||
skillName: selectedSkill.name,
|
||||
damage: skillResult.result.damage,
|
||||
targetName: newMonsterStats.name,
|
||||
attackDelayMs: newPlayerStats.attackDelayMs,
|
||||
),
|
||||
);
|
||||
} else if (selectedSkill != null && selectedSkill.isDot) {
|
||||
final skillResult = skillService.useDotSkill(
|
||||
skill: selectedSkill,
|
||||
player: newPlayerStats,
|
||||
skillSystem: newSkillSystem,
|
||||
playerInt: state.stats.intelligence,
|
||||
playerWis: state.stats.wis,
|
||||
);
|
||||
newPlayerStats = skillResult.updatedPlayer;
|
||||
newSkillSystem = skillResult.updatedSkillSystem.startGlobalCooldown();
|
||||
|
||||
if (skillResult.dotEffect != null) {
|
||||
newActiveDoTs.add(skillResult.dotEffect!);
|
||||
}
|
||||
|
||||
events.add(
|
||||
CombatEvent.playerSkill(
|
||||
timestamp: timestamp,
|
||||
skillName: selectedSkill.name,
|
||||
damage: skillResult.result.damage,
|
||||
targetName: newMonsterStats.name,
|
||||
attackDelayMs: newPlayerStats.attackDelayMs,
|
||||
),
|
||||
);
|
||||
} else if (selectedSkill != null && selectedSkill.isHeal) {
|
||||
final skillResult = skillService.useHealSkill(
|
||||
skill: selectedSkill,
|
||||
player: newPlayerStats,
|
||||
skillSystem: newSkillSystem,
|
||||
);
|
||||
newPlayerStats = skillResult.updatedPlayer;
|
||||
newSkillSystem = skillResult.updatedSkillSystem.startGlobalCooldown();
|
||||
|
||||
events.add(
|
||||
CombatEvent.playerHeal(
|
||||
timestamp: timestamp,
|
||||
healAmount: skillResult.result.healedAmount,
|
||||
skillName: selectedSkill.name,
|
||||
),
|
||||
);
|
||||
} else if (selectedSkill != null && selectedSkill.isBuff) {
|
||||
final skillResult = skillService.useBuffSkill(
|
||||
skill: selectedSkill,
|
||||
player: newPlayerStats,
|
||||
skillSystem: newSkillSystem,
|
||||
);
|
||||
newPlayerStats = skillResult.updatedPlayer;
|
||||
newSkillSystem = skillResult.updatedSkillSystem.startGlobalCooldown();
|
||||
|
||||
events.add(
|
||||
CombatEvent.playerBuff(
|
||||
timestamp: timestamp,
|
||||
skillName: selectedSkill.name,
|
||||
),
|
||||
);
|
||||
} else if (selectedSkill != null && selectedSkill.isDebuff) {
|
||||
final skillResult = skillService.useDebuffSkill(
|
||||
skill: selectedSkill,
|
||||
player: newPlayerStats,
|
||||
skillSystem: newSkillSystem,
|
||||
currentDebuffs: newActiveBuffs,
|
||||
);
|
||||
newPlayerStats = skillResult.updatedPlayer;
|
||||
newSkillSystem = skillResult.updatedSkillSystem.startGlobalCooldown();
|
||||
|
||||
if (skillResult.debuffEffect != null) {
|
||||
newActiveBuffs = newActiveBuffs
|
||||
.where((d) => d.effect.id != skillResult.debuffEffect!.effect.id)
|
||||
.toList()
|
||||
..add(skillResult.debuffEffect!);
|
||||
}
|
||||
|
||||
events.add(
|
||||
CombatEvent.playerDebuff(
|
||||
timestamp: timestamp,
|
||||
skillName: selectedSkill.name,
|
||||
targetName: newMonsterStats.name,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// 일반 공격
|
||||
final attackResult = calculator.playerAttackMonster(
|
||||
attacker: newPlayerStats,
|
||||
defender: newMonsterStats,
|
||||
);
|
||||
newMonsterStats = attackResult.updatedDefender;
|
||||
newTotalDamageDealt += attackResult.result.damage;
|
||||
|
||||
final result = attackResult.result;
|
||||
if (result.isEvaded) {
|
||||
events.add(
|
||||
CombatEvent.monsterEvade(
|
||||
timestamp: timestamp,
|
||||
targetName: newMonsterStats.name,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
events.add(
|
||||
CombatEvent.playerAttack(
|
||||
timestamp: timestamp,
|
||||
damage: result.damage,
|
||||
targetName: newMonsterStats.name,
|
||||
isCritical: result.isCritical,
|
||||
attackDelayMs: newPlayerStats.attackDelayMs,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
playerStats: newPlayerStats,
|
||||
monsterStats: newMonsterStats,
|
||||
skillSystem: newSkillSystem,
|
||||
activeDoTs: newActiveDoTs,
|
||||
activeDebuffs: newActiveBuffs,
|
||||
totalDamageDealt: newTotalDamageDealt,
|
||||
events: events,
|
||||
);
|
||||
}
|
||||
|
||||
/// 몬스터 공격 처리
|
||||
({
|
||||
CombatStats playerStats,
|
||||
int totalDamageTaken,
|
||||
List<CombatEvent> events,
|
||||
}) _processMonsterAttack({
|
||||
required CombatStats playerStats,
|
||||
required MonsterCombatStats monsterStats,
|
||||
required List<ActiveBuff> activeDebuffs,
|
||||
required int totalDamageTaken,
|
||||
required int timestamp,
|
||||
required CombatCalculator calculator,
|
||||
}) {
|
||||
final events = <CombatEvent>[];
|
||||
|
||||
// 디버프 효과 적용된 몬스터 스탯 계산
|
||||
var debuffedMonster = monsterStats;
|
||||
if (activeDebuffs.isNotEmpty) {
|
||||
double atkMod = 0;
|
||||
for (final debuff in activeDebuffs) {
|
||||
if (!debuff.isExpired(timestamp)) {
|
||||
atkMod += debuff.effect.atkModifier;
|
||||
}
|
||||
}
|
||||
// ATK 감소 적용 (최소 10% ATK 유지)
|
||||
final newAtk = (monsterStats.atk * (1 + atkMod)).round().clamp(
|
||||
monsterStats.atk ~/ 10,
|
||||
monsterStats.atk,
|
||||
);
|
||||
debuffedMonster = monsterStats.copyWith(atk: newAtk);
|
||||
}
|
||||
|
||||
final attackResult = calculator.monsterAttackPlayer(
|
||||
attacker: debuffedMonster,
|
||||
defender: playerStats,
|
||||
);
|
||||
|
||||
final result = attackResult.result;
|
||||
if (result.isEvaded) {
|
||||
events.add(
|
||||
CombatEvent.playerEvade(
|
||||
timestamp: timestamp,
|
||||
attackerName: monsterStats.name,
|
||||
),
|
||||
);
|
||||
} else if (result.isBlocked) {
|
||||
events.add(
|
||||
CombatEvent.playerBlock(
|
||||
timestamp: timestamp,
|
||||
reducedDamage: result.damage,
|
||||
attackerName: monsterStats.name,
|
||||
),
|
||||
);
|
||||
} else if (result.isParried) {
|
||||
events.add(
|
||||
CombatEvent.playerParry(
|
||||
timestamp: timestamp,
|
||||
reducedDamage: result.damage,
|
||||
attackerName: monsterStats.name,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
events.add(
|
||||
CombatEvent.monsterAttack(
|
||||
timestamp: timestamp,
|
||||
damage: result.damage,
|
||||
attackerName: monsterStats.name,
|
||||
attackDelayMs: monsterStats.attackDelayMs,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
playerStats: attackResult.updatedDefender,
|
||||
totalDamageTaken: totalDamageTaken + attackResult.result.damage,
|
||||
events: events,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user