feat(skill): Phase 3 MP 기반 스킬 시스템 구현
- Skill, SkillType, BuffEffect, SkillState, SkillUseResult 클래스 정의 (skill.dart) - SkillSystemState를 GameState에 통합 (activeBuffs, skillStates, elapsedMs) - 프로그래밍 테마 스킬 데이터 정의 (skill_data.dart) - 공격: Debug Strike, Memory Leak, Core Dump, Kernel Panic 등 - 회복: Hot Reload, Garbage Collection, Quick Fix - 버프: Safe Mode, Overclock, Firewall - SkillService 구현 (skill_service.dart) - 스킬 사용 가능 여부 확인 (MP, 쿨타임) - 공격/회복/버프 스킬 사용 로직 - 자동 스킬 선택 (HP < 30% → 회복, 보스전 → 강력한 공격, 일반 → MP 효율) - MP 자연 회복 (비전투: 50ms당 1, 전투: WIS 기반) - progress_service.dart에 스킬 시스템 통합 - tick()에서 스킬 시간 업데이트 및 버프 만료 처리 - _processCombatTickWithSkills()로 전투 중 자동 스킬 사용
This commit is contained in:
@@ -4,6 +4,7 @@ import 'package:askiineverdie/src/core/model/combat_state.dart';
|
||||
import 'package:askiineverdie/src/core/model/equipment_item.dart';
|
||||
import 'package:askiineverdie/src/core/model/equipment_slot.dart';
|
||||
import 'package:askiineverdie/src/core/model/item_stats.dart';
|
||||
import 'package:askiineverdie/src/core/model/skill.dart';
|
||||
import 'package:askiineverdie/src/core/util/deterministic_random.dart';
|
||||
|
||||
/// Minimal skeletal state to mirror Progress Quest structures.
|
||||
@@ -20,6 +21,7 @@ class GameState {
|
||||
SpellBook? spellBook,
|
||||
ProgressState? progress,
|
||||
QueueState? queue,
|
||||
SkillSystemState? skillSystem,
|
||||
}) : rng = DeterministicRandom.clone(rng),
|
||||
traits = traits ?? Traits.empty(),
|
||||
stats = stats ?? Stats.empty(),
|
||||
@@ -27,7 +29,8 @@ class GameState {
|
||||
equipment = equipment ?? Equipment.empty(),
|
||||
spellBook = spellBook ?? SpellBook.empty(),
|
||||
progress = progress ?? ProgressState.empty(),
|
||||
queue = queue ?? QueueState.empty();
|
||||
queue = queue ?? QueueState.empty(),
|
||||
skillSystem = skillSystem ?? SkillSystemState.empty();
|
||||
|
||||
factory GameState.withSeed({
|
||||
required int seed,
|
||||
@@ -38,6 +41,7 @@ class GameState {
|
||||
SpellBook? spellBook,
|
||||
ProgressState? progress,
|
||||
QueueState? queue,
|
||||
SkillSystemState? skillSystem,
|
||||
}) {
|
||||
return GameState(
|
||||
rng: DeterministicRandom(seed),
|
||||
@@ -48,6 +52,7 @@ class GameState {
|
||||
spellBook: spellBook,
|
||||
progress: progress,
|
||||
queue: queue,
|
||||
skillSystem: skillSystem,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -60,6 +65,9 @@ class GameState {
|
||||
final ProgressState progress;
|
||||
final QueueState queue;
|
||||
|
||||
/// 스킬 시스템 상태 (Phase 3)
|
||||
final SkillSystemState skillSystem;
|
||||
|
||||
GameState copyWith({
|
||||
DeterministicRandom? rng,
|
||||
Traits? traits,
|
||||
@@ -69,6 +77,7 @@ class GameState {
|
||||
SpellBook? spellBook,
|
||||
ProgressState? progress,
|
||||
QueueState? queue,
|
||||
SkillSystemState? skillSystem,
|
||||
}) {
|
||||
return GameState(
|
||||
rng: rng ?? DeterministicRandom.clone(this.rng),
|
||||
@@ -79,6 +88,76 @@ class GameState {
|
||||
spellBook: spellBook ?? this.spellBook,
|
||||
progress: progress ?? this.progress,
|
||||
queue: queue ?? this.queue,
|
||||
skillSystem: skillSystem ?? this.skillSystem,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 스킬 시스템 상태 (Phase 3)
|
||||
///
|
||||
/// 스킬 쿨타임, 활성 버프, 게임 경과 시간 등을 관리
|
||||
class SkillSystemState {
|
||||
const SkillSystemState({
|
||||
required this.skillStates,
|
||||
required this.activeBuffs,
|
||||
required this.elapsedMs,
|
||||
});
|
||||
|
||||
/// 스킬별 쿨타임 상태
|
||||
final List<SkillState> skillStates;
|
||||
|
||||
/// 현재 활성화된 버프 목록
|
||||
final List<ActiveBuff> activeBuffs;
|
||||
|
||||
/// 게임 진행 경과 시간 (밀리초, 스킬 쿨타임 계산용)
|
||||
final int elapsedMs;
|
||||
|
||||
factory SkillSystemState.empty() => const SkillSystemState(
|
||||
skillStates: [],
|
||||
activeBuffs: [],
|
||||
elapsedMs: 0,
|
||||
);
|
||||
|
||||
/// 특정 스킬 상태 가져오기
|
||||
SkillState? getSkillState(String skillId) {
|
||||
for (final state in skillStates) {
|
||||
if (state.skillId == skillId) return state;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 버프 효과 합산 (동일 버프는 중복 적용 안 됨)
|
||||
({double atkMod, double defMod, double criMod, double evasionMod}) get totalBuffModifiers {
|
||||
double atkMod = 0;
|
||||
double defMod = 0;
|
||||
double criMod = 0;
|
||||
double evasionMod = 0;
|
||||
|
||||
final seenBuffIds = <String>{};
|
||||
for (final buff in activeBuffs) {
|
||||
if (seenBuffIds.contains(buff.effect.id)) continue;
|
||||
seenBuffIds.add(buff.effect.id);
|
||||
|
||||
if (!buff.isExpired(elapsedMs)) {
|
||||
atkMod += buff.effect.atkModifier;
|
||||
defMod += buff.effect.defModifier;
|
||||
criMod += buff.effect.criRateModifier;
|
||||
evasionMod += buff.effect.evasionModifier;
|
||||
}
|
||||
}
|
||||
|
||||
return (atkMod: atkMod, defMod: defMod, criMod: criMod, evasionMod: evasionMod);
|
||||
}
|
||||
|
||||
SkillSystemState copyWith({
|
||||
List<SkillState>? skillStates,
|
||||
List<ActiveBuff>? activeBuffs,
|
||||
int? elapsedMs,
|
||||
}) {
|
||||
return SkillSystemState(
|
||||
skillStates: skillStates ?? this.skillStates,
|
||||
activeBuffs: activeBuffs ?? this.activeBuffs,
|
||||
elapsedMs: elapsedMs ?? this.elapsedMs,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
267
lib/src/core/model/skill.dart
Normal file
267
lib/src/core/model/skill.dart
Normal file
@@ -0,0 +1,267 @@
|
||||
/// 스킬 타입
|
||||
enum SkillType {
|
||||
/// 공격 스킬
|
||||
attack,
|
||||
|
||||
/// 회복 스킬
|
||||
heal,
|
||||
|
||||
/// 버프 스킬
|
||||
buff,
|
||||
|
||||
/// 디버프 스킬
|
||||
debuff,
|
||||
}
|
||||
|
||||
/// 버프 효과
|
||||
class BuffEffect {
|
||||
const BuffEffect({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.durationMs,
|
||||
this.atkModifier = 0.0,
|
||||
this.defModifier = 0.0,
|
||||
this.criRateModifier = 0.0,
|
||||
this.evasionModifier = 0.0,
|
||||
});
|
||||
|
||||
/// 버프 ID
|
||||
final String id;
|
||||
|
||||
/// 버프 이름
|
||||
final String name;
|
||||
|
||||
/// 지속 시간 (밀리초)
|
||||
final int durationMs;
|
||||
|
||||
/// 공격력 배율 보정 (0.0 = 변화 없음, 0.5 = +50%)
|
||||
final double atkModifier;
|
||||
|
||||
/// 방어력 배율 보정
|
||||
final double defModifier;
|
||||
|
||||
/// 크리티컬 확률 보정
|
||||
final double criRateModifier;
|
||||
|
||||
/// 회피율 보정
|
||||
final double evasionModifier;
|
||||
}
|
||||
|
||||
/// 스킬 정의
|
||||
class Skill {
|
||||
const Skill({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.type,
|
||||
required this.mpCost,
|
||||
required this.cooldownMs,
|
||||
required this.power,
|
||||
this.damageMultiplier = 1.0,
|
||||
this.healAmount = 0,
|
||||
this.healPercent = 0.0,
|
||||
this.buff,
|
||||
this.selfDamagePercent = 0.0,
|
||||
this.targetDefReduction = 0.0,
|
||||
});
|
||||
|
||||
/// 스킬 ID
|
||||
final String id;
|
||||
|
||||
/// 스킬 이름
|
||||
final String name;
|
||||
|
||||
/// 스킬 타입
|
||||
final SkillType type;
|
||||
|
||||
/// MP 소모량
|
||||
final int mpCost;
|
||||
|
||||
/// 쿨타임 (밀리초)
|
||||
final int cooldownMs;
|
||||
|
||||
/// 스킬 위력 (기본 값)
|
||||
final int power;
|
||||
|
||||
/// 데미지 배율 (공격 스킬용)
|
||||
final double damageMultiplier;
|
||||
|
||||
/// 고정 회복량 (회복 스킬용)
|
||||
final int healAmount;
|
||||
|
||||
/// HP% 회복 (회복 스킬용, 0.0 ~ 1.0)
|
||||
final double healPercent;
|
||||
|
||||
/// 버프 효과 (버프/디버프 스킬용)
|
||||
final BuffEffect? buff;
|
||||
|
||||
/// 자해 데미지 % (일부 강력한 스킬)
|
||||
final double selfDamagePercent;
|
||||
|
||||
/// 적 방어력 감소 % (일부 공격 스킬)
|
||||
final double targetDefReduction;
|
||||
|
||||
/// 공격 스킬 여부
|
||||
bool get isAttack => type == SkillType.attack;
|
||||
|
||||
/// 회복 스킬 여부
|
||||
bool get isHeal => type == SkillType.heal;
|
||||
|
||||
/// 버프 스킬 여부
|
||||
bool get isBuff => type == SkillType.buff;
|
||||
|
||||
/// 디버프 스킬 여부
|
||||
bool get isDebuff => type == SkillType.debuff;
|
||||
|
||||
/// MP 효율 (데미지 당 MP 비용)
|
||||
double get mpEfficiency {
|
||||
if (type != SkillType.attack || damageMultiplier <= 0) return 0;
|
||||
return damageMultiplier / mpCost;
|
||||
}
|
||||
}
|
||||
|
||||
/// 스킬 사용 상태 (쿨타임 추적)
|
||||
class SkillState {
|
||||
const SkillState({
|
||||
required this.skillId,
|
||||
required this.lastUsedMs,
|
||||
required this.rank,
|
||||
});
|
||||
|
||||
/// 스킬 ID
|
||||
final String skillId;
|
||||
|
||||
/// 마지막 사용 시간 (게임 내 경과 시간, 밀리초)
|
||||
final int lastUsedMs;
|
||||
|
||||
/// 스킬 랭크 (레벨)
|
||||
final int rank;
|
||||
|
||||
/// 쿨타임 완료 여부
|
||||
bool isReady(int currentMs, int cooldownMs) {
|
||||
return currentMs - lastUsedMs >= cooldownMs;
|
||||
}
|
||||
|
||||
/// 남은 쿨타임 (밀리초)
|
||||
int remainingCooldown(int currentMs, int cooldownMs) {
|
||||
final elapsed = currentMs - lastUsedMs;
|
||||
if (elapsed >= cooldownMs) return 0;
|
||||
return cooldownMs - elapsed;
|
||||
}
|
||||
|
||||
SkillState copyWith({
|
||||
String? skillId,
|
||||
int? lastUsedMs,
|
||||
int? rank,
|
||||
}) {
|
||||
return SkillState(
|
||||
skillId: skillId ?? this.skillId,
|
||||
lastUsedMs: lastUsedMs ?? this.lastUsedMs,
|
||||
rank: rank ?? this.rank,
|
||||
);
|
||||
}
|
||||
|
||||
/// 새 스킬 상태 생성 (쿨타임 0)
|
||||
factory SkillState.fresh(String skillId, {int rank = 1}) {
|
||||
return SkillState(
|
||||
skillId: skillId,
|
||||
lastUsedMs: -999999, // 즉시 사용 가능하도록 먼 과거
|
||||
rank: rank,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 활성 버프 상태
|
||||
class ActiveBuff {
|
||||
const ActiveBuff({
|
||||
required this.effect,
|
||||
required this.startedMs,
|
||||
required this.sourceSkillId,
|
||||
});
|
||||
|
||||
/// 버프 효과
|
||||
final BuffEffect effect;
|
||||
|
||||
/// 버프 시작 시간 (게임 내 경과 시간)
|
||||
final int startedMs;
|
||||
|
||||
/// 버프를 발동한 스킬 ID
|
||||
final String sourceSkillId;
|
||||
|
||||
/// 버프 만료 여부
|
||||
bool isExpired(int currentMs) {
|
||||
return currentMs - startedMs >= effect.durationMs;
|
||||
}
|
||||
|
||||
/// 남은 지속 시간 (밀리초)
|
||||
int remainingDuration(int currentMs) {
|
||||
final elapsed = currentMs - startedMs;
|
||||
if (elapsed >= effect.durationMs) return 0;
|
||||
return effect.durationMs - elapsed;
|
||||
}
|
||||
|
||||
ActiveBuff copyWith({
|
||||
BuffEffect? effect,
|
||||
int? startedMs,
|
||||
String? sourceSkillId,
|
||||
}) {
|
||||
return ActiveBuff(
|
||||
effect: effect ?? this.effect,
|
||||
startedMs: startedMs ?? this.startedMs,
|
||||
sourceSkillId: sourceSkillId ?? this.sourceSkillId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 스킬 사용 결과
|
||||
class SkillUseResult {
|
||||
const SkillUseResult({
|
||||
required this.skill,
|
||||
required this.success,
|
||||
this.damage = 0,
|
||||
this.healedAmount = 0,
|
||||
this.appliedBuff,
|
||||
this.failReason,
|
||||
});
|
||||
|
||||
/// 사용한 스킬
|
||||
final Skill skill;
|
||||
|
||||
/// 성공 여부
|
||||
final bool success;
|
||||
|
||||
/// 데미지 (공격 스킬)
|
||||
final int damage;
|
||||
|
||||
/// 회복량 (회복 스킬)
|
||||
final int healedAmount;
|
||||
|
||||
/// 적용된 버프 (버프 스킬)
|
||||
final ActiveBuff? appliedBuff;
|
||||
|
||||
/// 실패 사유
|
||||
final SkillFailReason? failReason;
|
||||
|
||||
/// 실패 결과 생성
|
||||
factory SkillUseResult.failed(Skill skill, SkillFailReason reason) {
|
||||
return SkillUseResult(
|
||||
skill: skill,
|
||||
success: false,
|
||||
failReason: reason,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 스킬 실패 사유
|
||||
enum SkillFailReason {
|
||||
/// MP 부족
|
||||
notEnoughMp,
|
||||
|
||||
/// 쿨타임 중
|
||||
onCooldown,
|
||||
|
||||
/// 스킬 없음
|
||||
skillNotFound,
|
||||
|
||||
/// 사용 불가 상태
|
||||
invalidState,
|
||||
}
|
||||
Reference in New Issue
Block a user