feat(game): 포션 시스템 및 UI 패널 추가
- 포션 시스템 구현 (PotionService, Potion 모델) - 포션 인벤토리 패널 위젯 - 활성 버프 패널 위젯 - 장비 스탯 패널 위젯 - 스킬 시스템 확장 - 일본어 번역 추가 - 전투 이벤트/상태 모델 개선
This commit is contained in:
@@ -13,6 +13,42 @@ enum SkillType {
|
||||
debuff,
|
||||
}
|
||||
|
||||
/// 스킬 속성 (하이브리드: 코드 + 시스템)
|
||||
enum SkillElement {
|
||||
/// 논리 (Logic) - 순수 데미지
|
||||
logic,
|
||||
|
||||
/// 메모리 (Memory) - DoT 특화
|
||||
memory,
|
||||
|
||||
/// 네트워크 (Network) - 다중 타격
|
||||
network,
|
||||
|
||||
/// 화염 (Overheat) - 높은 순간 데미지
|
||||
fire,
|
||||
|
||||
/// 빙결 (Freeze) - 슬로우 효과
|
||||
ice,
|
||||
|
||||
/// 전기 (Surge) - 빠른 연속 타격
|
||||
lightning,
|
||||
|
||||
/// 공허 (Null) - 방어 무시
|
||||
voidElement,
|
||||
|
||||
/// 혼돈 (Glitch) - 랜덤 효과
|
||||
chaos,
|
||||
}
|
||||
|
||||
/// 공격 방식
|
||||
enum AttackMode {
|
||||
/// 단발성 - 즉시 데미지
|
||||
instant,
|
||||
|
||||
/// 지속 피해 - N초간 틱당 데미지
|
||||
dot,
|
||||
}
|
||||
|
||||
/// 버프 효과
|
||||
class BuffEffect {
|
||||
const BuffEffect({
|
||||
@@ -62,6 +98,11 @@ class Skill {
|
||||
this.buff,
|
||||
this.selfDamagePercent = 0.0,
|
||||
this.targetDefReduction = 0.0,
|
||||
this.element,
|
||||
this.attackMode = AttackMode.instant,
|
||||
this.baseDotDamage,
|
||||
this.baseDotDurationMs,
|
||||
this.baseDotTickMs,
|
||||
});
|
||||
|
||||
/// 스킬 ID
|
||||
@@ -100,6 +141,21 @@ class Skill {
|
||||
/// 적 방어력 감소 % (일부 공격 스킬)
|
||||
final double targetDefReduction;
|
||||
|
||||
/// 스킬 속성 (element) - 하이브리드 시스템
|
||||
final SkillElement? element;
|
||||
|
||||
/// 공격 방식 (instant: 단발성, dot: 지속 피해)
|
||||
final AttackMode attackMode;
|
||||
|
||||
/// DOT 기본 틱당 데미지 (스킬 레벨로 결정)
|
||||
final int? baseDotDamage;
|
||||
|
||||
/// DOT 기본 지속시간 (밀리초, 스킬 레벨로 결정)
|
||||
final int? baseDotDurationMs;
|
||||
|
||||
/// DOT 기본 틱 간격 (밀리초, 스킬 레벨로 결정)
|
||||
final int? baseDotTickMs;
|
||||
|
||||
/// 공격 스킬 여부
|
||||
bool get isAttack => type == SkillType.attack;
|
||||
|
||||
@@ -112,6 +168,9 @@ class Skill {
|
||||
/// 디버프 스킬 여부
|
||||
bool get isDebuff => type == SkillType.debuff;
|
||||
|
||||
/// DOT 스킬 여부
|
||||
bool get isDot => attackMode == AttackMode.dot;
|
||||
|
||||
/// MP 효율 (데미지 당 MP 비용)
|
||||
double get mpEfficiency {
|
||||
if (type != SkillType.attack || damageMultiplier <= 0) return 0;
|
||||
@@ -265,3 +324,140 @@ enum SkillFailReason {
|
||||
/// 사용 불가 상태
|
||||
invalidState,
|
||||
}
|
||||
|
||||
/// DOT (지속 피해) 효과
|
||||
///
|
||||
/// 스킬 사용 시 생성되어 전투 중 틱마다 데미지를 적용.
|
||||
/// - INT: 틱당 데미지 증가
|
||||
/// - WIS: 틱 간격 감소 (더 빠른 피해)
|
||||
/// - 스킬 레벨: 기본 데미지, 지속시간, 틱 간격 결정
|
||||
class DotEffect {
|
||||
const DotEffect({
|
||||
required this.skillId,
|
||||
required this.baseDamage,
|
||||
required this.damagePerTick,
|
||||
required this.tickIntervalMs,
|
||||
required this.totalDurationMs,
|
||||
this.remainingDurationMs = 0,
|
||||
this.tickAccumulatorMs = 0,
|
||||
this.element,
|
||||
});
|
||||
|
||||
/// 원본 스킬 ID
|
||||
final String skillId;
|
||||
|
||||
/// 스킬 기본 데미지 (스킬 레벨로 결정)
|
||||
final int baseDamage;
|
||||
|
||||
/// INT 보정 적용된 틱당 실제 데미지
|
||||
final int damagePerTick;
|
||||
|
||||
/// WIS 보정 적용된 틱 간격 (밀리초)
|
||||
final int tickIntervalMs;
|
||||
|
||||
/// 총 지속시간 (밀리초, 스킬 레벨로 결정)
|
||||
final int totalDurationMs;
|
||||
|
||||
/// 남은 지속시간 (밀리초)
|
||||
final int remainingDurationMs;
|
||||
|
||||
/// 다음 틱까지 누적 시간 (밀리초)
|
||||
final int tickAccumulatorMs;
|
||||
|
||||
/// 속성 (선택)
|
||||
final SkillElement? element;
|
||||
|
||||
/// DOT 만료 여부
|
||||
bool get isExpired => remainingDurationMs <= 0;
|
||||
|
||||
/// DOT 활성 여부
|
||||
bool get isActive => remainingDurationMs > 0;
|
||||
|
||||
/// 예상 남은 틱 수
|
||||
int get remainingTicks {
|
||||
if (tickIntervalMs <= 0) return 0;
|
||||
return (remainingDurationMs / tickIntervalMs).ceil();
|
||||
}
|
||||
|
||||
/// 스킬과 플레이어 스탯으로 DotEffect 생성
|
||||
///
|
||||
/// [skill] DOT 스킬
|
||||
/// [playerInt] 플레이어 INT (틱당 데미지 보정)
|
||||
/// [playerWis] 플레이어 WIS (틱 간격 보정)
|
||||
factory DotEffect.fromSkill(Skill skill, {int playerInt = 10, int playerWis = 10}) {
|
||||
assert(skill.isDot, 'DOT 스킬만 DotEffect 생성 가능');
|
||||
assert(skill.baseDotDamage != null, 'baseDotDamage 필수');
|
||||
assert(skill.baseDotDurationMs != null, 'baseDotDurationMs 필수');
|
||||
assert(skill.baseDotTickMs != null, 'baseDotTickMs 필수');
|
||||
|
||||
// INT → 데미지 보정 (INT 10 기준, ±3%/포인트)
|
||||
final intMod = 1.0 + (playerInt - 10) * 0.03;
|
||||
final actualDamage = (skill.baseDotDamage! * intMod).round();
|
||||
|
||||
// WIS → 틱 간격 보정 (WIS 10 기준, ±2%/포인트, 빨라짐)
|
||||
final wisMod = 1.0 + (playerWis - 10) * 0.02;
|
||||
final actualTickMs = (skill.baseDotTickMs! / wisMod).clamp(200, 2000).round();
|
||||
|
||||
return DotEffect(
|
||||
skillId: skill.id,
|
||||
baseDamage: skill.baseDotDamage!,
|
||||
damagePerTick: actualDamage.clamp(1, 9999),
|
||||
tickIntervalMs: actualTickMs,
|
||||
totalDurationMs: skill.baseDotDurationMs!,
|
||||
remainingDurationMs: skill.baseDotDurationMs!,
|
||||
tickAccumulatorMs: 0,
|
||||
element: skill.element,
|
||||
);
|
||||
}
|
||||
|
||||
/// 시간 경과 후 새 DotEffect 반환
|
||||
///
|
||||
/// [elapsedMs] 경과 시간 (밀리초)
|
||||
/// Returns: (새 DotEffect, 이번에 발생한 틱 수)
|
||||
(DotEffect, int) tick(int elapsedMs) {
|
||||
var newAccumulator = tickAccumulatorMs + elapsedMs;
|
||||
var newRemaining = remainingDurationMs - elapsedMs;
|
||||
var ticksTriggered = 0;
|
||||
|
||||
// 틱 발생 체크
|
||||
while (newAccumulator >= tickIntervalMs && newRemaining > 0) {
|
||||
newAccumulator -= tickIntervalMs;
|
||||
ticksTriggered++;
|
||||
}
|
||||
|
||||
final updated = DotEffect(
|
||||
skillId: skillId,
|
||||
baseDamage: baseDamage,
|
||||
damagePerTick: damagePerTick,
|
||||
tickIntervalMs: tickIntervalMs,
|
||||
totalDurationMs: totalDurationMs,
|
||||
remainingDurationMs: newRemaining.clamp(0, totalDurationMs),
|
||||
tickAccumulatorMs: newRemaining > 0 ? newAccumulator : 0,
|
||||
element: element,
|
||||
);
|
||||
|
||||
return (updated, ticksTriggered);
|
||||
}
|
||||
|
||||
DotEffect copyWith({
|
||||
String? skillId,
|
||||
int? baseDamage,
|
||||
int? damagePerTick,
|
||||
int? tickIntervalMs,
|
||||
int? totalDurationMs,
|
||||
int? remainingDurationMs,
|
||||
int? tickAccumulatorMs,
|
||||
SkillElement? element,
|
||||
}) {
|
||||
return DotEffect(
|
||||
skillId: skillId ?? this.skillId,
|
||||
baseDamage: baseDamage ?? this.baseDamage,
|
||||
damagePerTick: damagePerTick ?? this.damagePerTick,
|
||||
tickIntervalMs: tickIntervalMs ?? this.tickIntervalMs,
|
||||
totalDurationMs: totalDurationMs ?? this.totalDurationMs,
|
||||
remainingDurationMs: remainingDurationMs ?? this.remainingDurationMs,
|
||||
tickAccumulatorMs: tickAccumulatorMs ?? this.tickAccumulatorMs,
|
||||
element: element ?? this.element,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user