feat(core): 몬스터 등급 시스템 추가
- MonsterGrade 열거형 및 색상 정의 - GameState/ItemStats 확장 - pq_logic 유틸리티 함수 추가 - ASCII 색상 상수 추가
This commit is contained in:
@@ -32,6 +32,26 @@ class AsciiColors {
|
|||||||
static Color backgroundOf(BuildContext context) =>
|
static Color backgroundOf(BuildContext context) =>
|
||||||
RetroColors.isDarkMode(context) ? background : _lightBackground;
|
RetroColors.isDarkMode(context) ? background : _lightBackground;
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
// 무기 등급(ItemRarity) 색상 Getter (테마 인식, Phase 9)
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
/// Uncommon 등급 색상 - 테마 인식
|
||||||
|
static Color rarityUncommonOf(BuildContext context) =>
|
||||||
|
RetroColors.isDarkMode(context) ? rarityUncommon : _lightRarityUncommon;
|
||||||
|
|
||||||
|
/// Rare 등급 색상 - 테마 인식
|
||||||
|
static Color rarityRareOf(BuildContext context) =>
|
||||||
|
RetroColors.isDarkMode(context) ? rarityRare : _lightRarityRare;
|
||||||
|
|
||||||
|
/// Epic 등급 색상 - 테마 인식
|
||||||
|
static Color rarityEpicOf(BuildContext context) =>
|
||||||
|
RetroColors.isDarkMode(context) ? rarityEpic : _lightRarityEpic;
|
||||||
|
|
||||||
|
/// Legendary 등급 색상 - 테마 인식
|
||||||
|
static Color rarityLegendaryOf(BuildContext context) =>
|
||||||
|
RetroColors.isDarkMode(context) ? rarityLegendary : _lightRarityLegendary;
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
// 라이트 모드 색상 (양피지/크림 기반)
|
// 라이트 모드 색상 (양피지/크림 기반)
|
||||||
// ═══════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
@@ -48,6 +68,18 @@ class AsciiColors {
|
|||||||
/// 라이트 모드 배경 (양피지 크림)
|
/// 라이트 모드 배경 (양피지 크림)
|
||||||
static const Color _lightBackground = Color(0xFFF5E6C8);
|
static const Color _lightBackground = Color(0xFFF5E6C8);
|
||||||
|
|
||||||
|
/// 라이트 모드 Uncommon 등급 (진한 초록)
|
||||||
|
static const Color _lightRarityUncommon = Color(0xFF008800);
|
||||||
|
|
||||||
|
/// 라이트 모드 Rare 등급 (진한 파랑)
|
||||||
|
static const Color _lightRarityRare = Color(0xFF0055AA);
|
||||||
|
|
||||||
|
/// 라이트 모드 Epic 등급 (진한 보라)
|
||||||
|
static const Color _lightRarityEpic = Color(0xFF660099);
|
||||||
|
|
||||||
|
/// 라이트 모드 Legendary 등급 (진한 금색)
|
||||||
|
static const Color _lightRarityLegendary = Color(0xFFCC7700);
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
// 레거시 정적 색상 (다크 모드 기본값 / context 없는 곳에서 사용)
|
// 레거시 정적 색상 (다크 모드 기본값 / context 없는 곳에서 사용)
|
||||||
// ═══════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
@@ -64,6 +96,22 @@ class AsciiColors {
|
|||||||
/// 배경 색상
|
/// 배경 색상
|
||||||
static const Color background = Colors.black;
|
static const Color background = Colors.black;
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
// 무기 등급(ItemRarity) 정적 색상 (다크 모드 기본값, Phase 9)
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
/// Uncommon 등급 (밝은 초록)
|
||||||
|
static const Color rarityUncommon = Color(0xFF00FF00);
|
||||||
|
|
||||||
|
/// Rare 등급 (밝은 파랑)
|
||||||
|
static const Color rarityRare = Color(0xFF0088FF);
|
||||||
|
|
||||||
|
/// Epic 등급 (밝은 보라)
|
||||||
|
static const Color rarityEpic = Color(0xFF9900FF);
|
||||||
|
|
||||||
|
/// Legendary 등급 (밝은 금색)
|
||||||
|
static const Color rarityLegendary = Color(0xFFFFAA00);
|
||||||
|
|
||||||
/// 상황에 따른 색상 반환
|
/// 상황에 따른 색상 반환
|
||||||
static Color forContext(AsciiColorContext context) {
|
static Color forContext(AsciiColorContext context) {
|
||||||
return switch (context) {
|
return switch (context) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:asciineverdie/src/core/model/combat_state.dart';
|
|||||||
import 'package:asciineverdie/src/core/model/equipment_item.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/equipment_slot.dart';
|
||||||
import 'package:asciineverdie/src/core/model/item_stats.dart';
|
import 'package:asciineverdie/src/core/model/item_stats.dart';
|
||||||
|
import 'package:asciineverdie/src/core/model/monster_grade.dart';
|
||||||
import 'package:asciineverdie/src/core/model/potion.dart';
|
import 'package:asciineverdie/src/core/model/potion.dart';
|
||||||
import 'package:asciineverdie/src/core/model/skill.dart';
|
import 'package:asciineverdie/src/core/model/skill.dart';
|
||||||
import 'package:asciineverdie/src/core/util/deterministic_random.dart';
|
import 'package:asciineverdie/src/core/util/deterministic_random.dart';
|
||||||
@@ -280,6 +281,7 @@ class TaskInfo {
|
|||||||
this.monsterBaseName,
|
this.monsterBaseName,
|
||||||
this.monsterPart,
|
this.monsterPart,
|
||||||
this.monsterLevel,
|
this.monsterLevel,
|
||||||
|
this.monsterGrade,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String caption;
|
final String caption;
|
||||||
@@ -294,6 +296,9 @@ class TaskInfo {
|
|||||||
/// 킬 태스크의 몬스터 레벨 (애니메이션 크기 결정용)
|
/// 킬 태스크의 몬스터 레벨 (애니메이션 크기 결정용)
|
||||||
final int? monsterLevel;
|
final int? monsterLevel;
|
||||||
|
|
||||||
|
/// 킬 태스크의 몬스터 등급 (Normal/Elite/Boss)
|
||||||
|
final MonsterGrade? monsterGrade;
|
||||||
|
|
||||||
factory TaskInfo.empty() =>
|
factory TaskInfo.empty() =>
|
||||||
const TaskInfo(caption: '', type: TaskType.neutral);
|
const TaskInfo(caption: '', type: TaskType.neutral);
|
||||||
|
|
||||||
@@ -303,6 +308,7 @@ class TaskInfo {
|
|||||||
String? monsterBaseName,
|
String? monsterBaseName,
|
||||||
String? monsterPart,
|
String? monsterPart,
|
||||||
int? monsterLevel,
|
int? monsterLevel,
|
||||||
|
MonsterGrade? monsterGrade,
|
||||||
}) {
|
}) {
|
||||||
return TaskInfo(
|
return TaskInfo(
|
||||||
caption: caption ?? this.caption,
|
caption: caption ?? this.caption,
|
||||||
@@ -310,6 +316,7 @@ class TaskInfo {
|
|||||||
monsterBaseName: monsterBaseName ?? this.monsterBaseName,
|
monsterBaseName: monsterBaseName ?? this.monsterBaseName,
|
||||||
monsterPart: monsterPart ?? this.monsterPart,
|
monsterPart: monsterPart ?? this.monsterPart,
|
||||||
monsterLevel: monsterLevel ?? this.monsterLevel,
|
monsterLevel: monsterLevel ?? this.monsterLevel,
|
||||||
|
monsterGrade: monsterGrade ?? this.monsterGrade,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'package:asciineverdie/src/core/animation/canvas/ascii_cell.dart';
|
||||||
|
|
||||||
/// 아이템 희귀도
|
/// 아이템 희귀도
|
||||||
enum ItemRarity {
|
enum ItemRarity {
|
||||||
common,
|
common,
|
||||||
@@ -23,6 +25,17 @@ enum ItemRarity {
|
|||||||
epic => 400,
|
epic => 400,
|
||||||
legendary => 1000,
|
legendary => 1000,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// 공격 이펙트 셀 색상 (Phase 9: 무기 등급별 이펙트)
|
||||||
|
///
|
||||||
|
/// common은 기본 positive(시안), 나머지는 등급별 고유 색상
|
||||||
|
AsciiCellColor get effectCellColor => switch (this) {
|
||||||
|
ItemRarity.common => AsciiCellColor.positive,
|
||||||
|
ItemRarity.uncommon => AsciiCellColor.rarityUncommon,
|
||||||
|
ItemRarity.rare => AsciiCellColor.rarityRare,
|
||||||
|
ItemRarity.epic => AsciiCellColor.rarityEpic,
|
||||||
|
ItemRarity.legendary => AsciiCellColor.rarityLegendary,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 아이템 스탯 보정치
|
/// 아이템 스탯 보정치
|
||||||
|
|||||||
60
lib/src/core/model/monster_grade.dart
Normal file
60
lib/src/core/model/monster_grade.dart
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// 몬스터 등급 (드랍 품질 및 UI 표시에 영향)
|
||||||
|
enum MonsterGrade {
|
||||||
|
/// 일반 몬스터 (85% 확률)
|
||||||
|
/// - 기본 드랍 확률
|
||||||
|
/// - UI: 기본 색상, 접두사 없음
|
||||||
|
normal,
|
||||||
|
|
||||||
|
/// 정예 몬스터 (12% 확률)
|
||||||
|
/// - 물약 드랍 +5%, 아이템 품질 향상
|
||||||
|
/// - UI: 파란색, ★ 접두사
|
||||||
|
elite,
|
||||||
|
|
||||||
|
/// 보스 몬스터 (3% 확률)
|
||||||
|
/// - 물약 드랍 +15%, 최고 아이템 품질
|
||||||
|
/// - UI: 금색, ★★★ 접두사
|
||||||
|
boss,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// MonsterGrade 확장 메서드
|
||||||
|
extension MonsterGradeExtension on MonsterGrade {
|
||||||
|
/// 물약 드랍 확률 보너스 (0.0 ~ 1.0)
|
||||||
|
double get potionDropBonus => switch (this) {
|
||||||
|
MonsterGrade.normal => 0.0,
|
||||||
|
MonsterGrade.elite => 0.05, // +5%
|
||||||
|
MonsterGrade.boss => 0.15, // +15%
|
||||||
|
};
|
||||||
|
|
||||||
|
/// UI 표시용 접두사 (몬스터 이름 앞에 붙음)
|
||||||
|
String get displayPrefix => switch (this) {
|
||||||
|
MonsterGrade.normal => '',
|
||||||
|
MonsterGrade.elite => '★ ',
|
||||||
|
MonsterGrade.boss => '★★★ ',
|
||||||
|
};
|
||||||
|
|
||||||
|
/// 스탯 배율 (전투력 강화)
|
||||||
|
double get statMultiplier => switch (this) {
|
||||||
|
MonsterGrade.normal => 1.0,
|
||||||
|
MonsterGrade.elite => 1.3, // +30% 스탯
|
||||||
|
MonsterGrade.boss => 1.8, // +80% 스탯
|
||||||
|
};
|
||||||
|
|
||||||
|
/// 경험치 배율
|
||||||
|
double get expMultiplier => switch (this) {
|
||||||
|
MonsterGrade.normal => 1.0,
|
||||||
|
MonsterGrade.elite => 1.5, // +50% 경험치
|
||||||
|
MonsterGrade.boss => 2.5, // +150% 경험치
|
||||||
|
};
|
||||||
|
|
||||||
|
/// UI 표시용 색상
|
||||||
|
/// - Normal: 기본 텍스트 색상 (null 반환 → 기본 스타일 사용)
|
||||||
|
/// - Elite: 파란색 (#7AA2F7)
|
||||||
|
/// - Boss: 금색 (#E0AF68)
|
||||||
|
Color? get displayColor => switch (this) {
|
||||||
|
MonsterGrade.normal => null,
|
||||||
|
MonsterGrade.elite => const Color(0xFF7AA2F7), // MP 파랑
|
||||||
|
MonsterGrade.boss => const Color(0xFFE0AF68), // 골드
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import 'dart:math' as math;
|
|||||||
import 'package:asciineverdie/data/game_text_l10n.dart' as l10n;
|
import 'package:asciineverdie/data/game_text_l10n.dart' as l10n;
|
||||||
import 'package:asciineverdie/src/core/model/equipment_slot.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/game_state.dart';
|
||||||
|
import 'package:asciineverdie/src/core/model/monster_grade.dart';
|
||||||
import 'package:asciineverdie/src/core/model/pq_config.dart';
|
import 'package:asciineverdie/src/core/model/pq_config.dart';
|
||||||
import 'package:asciineverdie/src/core/util/deterministic_random.dart';
|
import 'package:asciineverdie/src/core/util/deterministic_random.dart';
|
||||||
import 'package:asciineverdie/src/core/util/roman.dart';
|
import 'package:asciineverdie/src/core/util/roman.dart';
|
||||||
@@ -483,6 +484,25 @@ Stats winStat(Stats stats, DeterministicRandom rng) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 몬스터 등급 결정 (Normal 85%, Elite 12%, Boss 3%)
|
||||||
|
/// 몬스터 레벨이 플레이어 레벨보다 높으면 상위 등급 확률 증가
|
||||||
|
MonsterGrade _determineGrade(
|
||||||
|
int monsterLevel,
|
||||||
|
int playerLevel,
|
||||||
|
DeterministicRandom rng,
|
||||||
|
) {
|
||||||
|
// 기본 확률: Normal 85%, Elite 12%, Boss 3%
|
||||||
|
// 레벨 차이에 따른 보정
|
||||||
|
final levelDiff = monsterLevel - playerLevel;
|
||||||
|
final eliteBonus = (levelDiff * 2).clamp(0, 10); // 최대 +10%
|
||||||
|
final bossBonus = (levelDiff * 0.5).clamp(0, 3).toInt(); // 최대 +3%
|
||||||
|
|
||||||
|
final roll = rng.nextInt(100);
|
||||||
|
if (roll < 3 + bossBonus) return MonsterGrade.boss;
|
||||||
|
if (roll < 15 + eliteBonus) return MonsterGrade.elite;
|
||||||
|
return MonsterGrade.normal;
|
||||||
|
}
|
||||||
|
|
||||||
MonsterTaskResult monsterTask(
|
MonsterTaskResult monsterTask(
|
||||||
PqConfig config,
|
PqConfig config,
|
||||||
DeterministicRandom rng,
|
DeterministicRandom rng,
|
||||||
@@ -592,11 +612,15 @@ MonsterTaskResult monsterTask(
|
|||||||
name = l10n.indefiniteL10n(name, qty);
|
name = l10n.indefiniteL10n(name, qty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 몬스터 등급 결정 (level = 플레이어 레벨)
|
||||||
|
final grade = _determineGrade(monsterLevel, level, rng);
|
||||||
|
|
||||||
return MonsterTaskResult(
|
return MonsterTaskResult(
|
||||||
displayName: name,
|
displayName: name,
|
||||||
baseName: baseName,
|
baseName: baseName,
|
||||||
level: monsterLevel * qty,
|
level: monsterLevel * qty,
|
||||||
part: part,
|
part: part,
|
||||||
|
grade: grade,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -607,6 +631,7 @@ class MonsterTaskResult {
|
|||||||
required this.baseName,
|
required this.baseName,
|
||||||
required this.level,
|
required this.level,
|
||||||
required this.part,
|
required this.part,
|
||||||
|
required this.grade,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// 화면에 표시할 몬스터 이름 (형용사 포함, 예: "a sick Goblin")
|
/// 화면에 표시할 몬스터 이름 (형용사 포함, 예: "a sick Goblin")
|
||||||
@@ -620,6 +645,9 @@ class MonsterTaskResult {
|
|||||||
|
|
||||||
/// 전리품 부위 (예: "claw", "tail", "*"는 WinItem 호출)
|
/// 전리품 부위 (예: "claw", "tail", "*"는 WinItem 호출)
|
||||||
final String part;
|
final String part;
|
||||||
|
|
||||||
|
/// 몬스터 등급 (Normal/Elite/Boss)
|
||||||
|
final MonsterGrade grade;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RewardKind { spell, equip, stat, item }
|
enum RewardKind { spell, equip, stat, item }
|
||||||
|
|||||||
Reference in New Issue
Block a user