feat(core): 몬스터 등급 시스템 추가

- MonsterGrade 열거형 및 색상 정의
- GameState/ItemStats 확장
- pq_logic 유틸리티 함수 추가
- ASCII 색상 상수 추가
This commit is contained in:
JiWoong Sul
2026-01-05 17:52:47 +09:00
parent e112378ad2
commit 5c8ab0d3f4
5 changed files with 156 additions and 0 deletions

View File

@@ -32,6 +32,26 @@ class AsciiColors {
static Color backgroundOf(BuildContext context) =>
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);
/// 라이트 모드 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 없는 곳에서 사용)
// ═══════════════════════════════════════════════════════════════════════
@@ -64,6 +96,22 @@ class AsciiColors {
/// 배경 색상
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) {
return switch (context) {

View File

@@ -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_slot.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/skill.dart';
import 'package:asciineverdie/src/core/util/deterministic_random.dart';
@@ -280,6 +281,7 @@ class TaskInfo {
this.monsterBaseName,
this.monsterPart,
this.monsterLevel,
this.monsterGrade,
});
final String caption;
@@ -294,6 +296,9 @@ class TaskInfo {
/// 킬 태스크의 몬스터 레벨 (애니메이션 크기 결정용)
final int? monsterLevel;
/// 킬 태스크의 몬스터 등급 (Normal/Elite/Boss)
final MonsterGrade? monsterGrade;
factory TaskInfo.empty() =>
const TaskInfo(caption: '', type: TaskType.neutral);
@@ -303,6 +308,7 @@ class TaskInfo {
String? monsterBaseName,
String? monsterPart,
int? monsterLevel,
MonsterGrade? monsterGrade,
}) {
return TaskInfo(
caption: caption ?? this.caption,
@@ -310,6 +316,7 @@ class TaskInfo {
monsterBaseName: monsterBaseName ?? this.monsterBaseName,
monsterPart: monsterPart ?? this.monsterPart,
monsterLevel: monsterLevel ?? this.monsterLevel,
monsterGrade: monsterGrade ?? this.monsterGrade,
);
}
}

View File

@@ -1,3 +1,5 @@
import 'package:asciineverdie/src/core/animation/canvas/ascii_cell.dart';
/// 아이템 희귀도
enum ItemRarity {
common,
@@ -23,6 +25,17 @@ enum ItemRarity {
epic => 400,
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,
};
}
/// 아이템 스탯 보정치

View 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), // 골드
};
}

View File

@@ -4,6 +4,7 @@ import 'dart:math' as math;
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/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/util/deterministic_random.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(
PqConfig config,
DeterministicRandom rng,
@@ -592,11 +612,15 @@ MonsterTaskResult monsterTask(
name = l10n.indefiniteL10n(name, qty);
}
// 몬스터 등급 결정 (level = 플레이어 레벨)
final grade = _determineGrade(monsterLevel, level, rng);
return MonsterTaskResult(
displayName: name,
baseName: baseName,
level: monsterLevel * qty,
part: part,
grade: grade,
);
}
@@ -607,6 +631,7 @@ class MonsterTaskResult {
required this.baseName,
required this.level,
required this.part,
required this.grade,
});
/// 화면에 표시할 몬스터 이름 (형용사 포함, 예: "a sick Goblin")
@@ -620,6 +645,9 @@ class MonsterTaskResult {
/// 전리품 부위 (예: "claw", "tail", "*"는 WinItem 호출)
final String part;
/// 몬스터 등급 (Normal/Elite/Boss)
final MonsterGrade grade;
}
enum RewardKind { spell, equip, stat, item }