using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Myra.Graphics2D; using Myra.Graphics2D.Brushes; using Myra.Graphics2D.UI; using Theriapolis.Core.Data; using Theriapolis.Core.Rules.Quests; namespace Theriapolis.Game.Screens; /// /// Phase 6 M4 — quest journal modal (J key). Two columns: active quests /// on the left (current step + waypoint hint), completed/failed quests /// on the right. A tail block shows the engine's recent journal entries. /// /// Hidden quests stay hidden until they activate; once started, they /// appear in the active list normally. /// public sealed class QuestLogScreen : IScreen { private readonly QuestEngine _engine; private readonly ContentResolver _content; private Game1 _game = null!; private Desktop _desktop = null!; private bool _jWasDown = true; private bool _escWasDown = true; public QuestLogScreen(QuestEngine engine, ContentResolver content) { _engine = engine ?? throw new System.ArgumentNullException(nameof(engine)); _content = content ?? throw new System.ArgumentNullException(nameof(content)); } public void Initialize(Game1 game) { _game = game; BuildUI(); } private void BuildUI() { var root = new VerticalStackPanel { Spacing = 8, HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Top, Margin = new Thickness(20), Padding = new Thickness(20, 12, 20, 12), Background = new SolidBrush(new Color(15, 12, 8, 235)), Width = 880, }; root.Widgets.Add(new Label { Text = "QUEST JOURNAL", HorizontalAlignment = HorizontalAlignment.Center, TextColor = new Color(255, 230, 170), }); root.Widgets.Add(new Label { Text = " " }); var twoCol = new HorizontalStackPanel { Spacing = 24 }; // Active. var leftCol = new VerticalStackPanel { Spacing = 4, Width = 420 }; leftCol.Widgets.Add(new Label { Text = "ACTIVE", TextColor = new Color(200, 180, 130) }); var active = _engine.Active.Values.OrderBy(s => s.StartedAt).ToList(); if (active.Count == 0) leftCol.Widgets.Add(new Label { Text = "(none yet)", TextColor = new Color(120, 110, 100) }); foreach (var st in active) { var def = _content.Quests.TryGetValue(st.QuestId, out var d) ? d : null; string title = def?.Title ?? st.QuestId; leftCol.Widgets.Add(new Label { Text = $" {title}", TextColor = new Color(220, 220, 200), }); // Step description if available. var step = def?.Steps.FirstOrDefault(x => string.Equals(x.Id, st.CurrentStep, System.StringComparison.OrdinalIgnoreCase)); if (step is not null && !string.IsNullOrEmpty(step.Description)) leftCol.Widgets.Add(new Label { Text = $" • {step.Description}", TextColor = new Color(170, 200, 220), Wrap = true, Width = 410, }); if (step is not null && !string.IsNullOrEmpty(step.Waypoint)) leftCol.Widgets.Add(new Label { Text = $" → {step.Waypoint}", TextColor = new Color(140, 180, 110), }); } twoCol.Widgets.Add(leftCol); // Completed / failed. var rightCol = new VerticalStackPanel { Spacing = 4, Width = 420 }; rightCol.Widgets.Add(new Label { Text = "ARCHIVE", TextColor = new Color(200, 180, 130) }); var done = _engine.Completed.Values.OrderByDescending(s => s.StartedAt).ToList(); if (done.Count == 0) rightCol.Widgets.Add(new Label { Text = "(none yet)", TextColor = new Color(120, 110, 100) }); foreach (var st in done) { var def = _content.Quests.TryGetValue(st.QuestId, out var d) ? d : null; string title = def?.Title ?? st.QuestId; string mark = st.Status == QuestStatus.Completed ? "✓" : "✗"; Color color = st.Status == QuestStatus.Completed ? new Color(140, 200, 130) : new Color(200, 130, 130); rightCol.Widgets.Add(new Label { Text = $" {mark} {title}", TextColor = color, }); } twoCol.Widgets.Add(rightCol); root.Widgets.Add(twoCol); // Recent journal tail. root.Widgets.Add(new Label { Text = " " }); root.Widgets.Add(new Label { Text = "RECENT", TextColor = new Color(200, 180, 130) }); var tail = _engine.Journal.Skip(System.Math.Max(0, _engine.Journal.Count - 8)).ToList(); if (tail.Count == 0) root.Widgets.Add(new Label { Text = " (no entries)", TextColor = new Color(120, 110, 100) }); foreach (var line in tail) root.Widgets.Add(new Label { Text = $" {line}", TextColor = new Color(180, 180, 170), Wrap = true, Width = 840, }); root.Widgets.Add(new Label { Text = " " }); root.Widgets.Add(new Label { Text = "(J / Esc to close)", HorizontalAlignment = HorizontalAlignment.Center, TextColor = new Color(120, 110, 100), }); _desktop = new Desktop { Root = root }; } public void Update(GameTime gt) { var ks = Keyboard.GetState(); bool j = ks.IsKeyDown(Keys.J); bool esc = ks.IsKeyDown(Keys.Escape); bool jPressed = j && !_jWasDown; bool escPressed = esc && !_escWasDown; _jWasDown = j; _escWasDown = esc; if (jPressed || escPressed) _game.Screens.Pop(); } public void Draw(GameTime gt, SpriteBatch sb) => _desktop.Render(); public void Deactivate() { } public void Reactivate() { BuildUI(); } }