164 lines
6.2 KiB
C#
164 lines
6.2 KiB
C#
|
|
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;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 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.
|
||
|
|
/// </summary>
|
||
|
|
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(); }
|
||
|
|
}
|