From 5c8ab0d3f4693fa8648db7c5b35b90b7c86645f5 Mon Sep 17 00:00:00 2001 From: JiWoong Sul Date: Mon, 5 Jan 2026 17:52:47 +0900 Subject: [PATCH] =?UTF-8?q?feat(core):=20=EB=AA=AC=EC=8A=A4=ED=84=B0=20?= =?UTF-8?q?=EB=93=B1=EA=B8=89=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MonsterGrade 열거형 및 색상 정의 - GameState/ItemStats 확장 - pq_logic 유틸리티 함수 추가 - ASCII 색상 상수 추가 --- lib/src/core/constants/ascii_colors.dart | 48 +++++++++++++++++++ lib/src/core/model/game_state.dart | 7 +++ lib/src/core/model/item_stats.dart | 13 +++++ lib/src/core/model/monster_grade.dart | 60 ++++++++++++++++++++++++ lib/src/core/util/pq_logic.dart | 28 +++++++++++ 5 files changed, 156 insertions(+) create mode 100644 lib/src/core/model/monster_grade.dart diff --git a/lib/src/core/constants/ascii_colors.dart b/lib/src/core/constants/ascii_colors.dart index 9864507..b7b606a 100644 --- a/lib/src/core/constants/ascii_colors.dart +++ b/lib/src/core/constants/ascii_colors.dart @@ -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) { diff --git a/lib/src/core/model/game_state.dart b/lib/src/core/model/game_state.dart index bc3c754..79a901b 100644 --- a/lib/src/core/model/game_state.dart +++ b/lib/src/core/model/game_state.dart @@ -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, ); } } diff --git a/lib/src/core/model/item_stats.dart b/lib/src/core/model/item_stats.dart index 75eff1c..56d87cd 100644 --- a/lib/src/core/model/item_stats.dart +++ b/lib/src/core/model/item_stats.dart @@ -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, + }; } /// 아이템 스탯 보정치 diff --git a/lib/src/core/model/monster_grade.dart b/lib/src/core/model/monster_grade.dart new file mode 100644 index 0000000..5ae9298 --- /dev/null +++ b/lib/src/core/model/monster_grade.dart @@ -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), // 골드 + }; +} diff --git a/lib/src/core/util/pq_logic.dart b/lib/src/core/util/pq_logic.dart index 821bd2d..9d175cc 100644 --- a/lib/src/core/util/pq_logic.dart +++ b/lib/src/core/util/pq_logic.dart @@ -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 }