diff --git a/src/OpenTheBox/Program.cs b/src/OpenTheBox/Program.cs
index 9b33d1d..dd9921c 100644
--- a/src/OpenTheBox/Program.cs
+++ b/src/OpenTheBox/Program.cs
@@ -35,6 +35,8 @@ public static class Program
public static async Task Main(string[] args)
{
+ UnicodeSupport.Initialize();
+
// --snapshot N: directly load snapshot_N save and start playing
int snapshotSlot = 0;
var snapshotIdx = Array.IndexOf(args, "--snapshot");
@@ -521,8 +523,8 @@ public static class Program
case ResourceChangedEvent resEvt:
var resName = _loc.Get($"resource.{resEvt.Type.ToString().ToLower()}");
- _renderer.ShowMessage($"{resName}: {resEvt.OldValue} -> {resEvt.NewValue}");
- AddEventLog($"{resName}: {resEvt.OldValue} -> {resEvt.NewValue}");
+ _renderer.ShowMessage($"{resName}: {resEvt.OldValue} {UnicodeSupport.Arrow} {resEvt.NewValue}");
+ AddEventLog($"{resName}: {resEvt.OldValue} {UnicodeSupport.Arrow} {resEvt.NewValue}");
break;
case MessageEvent msgEvt:
@@ -758,8 +760,8 @@ public static class Program
? _loc.Get("inventory.item_used_qty", itemName, remaining.ToString())
: _loc.Get("inventory.item_used", itemName);
_renderer.ShowMessage(usedMsg);
- _renderer.ShowMessage($"{resName}: {resEvt.OldValue} -> {resEvt.NewValue}");
- AddEventLog($"{itemName} -> {resName} {resEvt.OldValue}->{resEvt.NewValue}");
+ _renderer.ShowMessage($"{resName}: {resEvt.OldValue} {UnicodeSupport.Arrow} {resEvt.NewValue}");
+ AddEventLog($"{itemName} {UnicodeSupport.Arrow} {resName} {resEvt.OldValue}{UnicodeSupport.Arrow}{resEvt.NewValue}");
break;
case MessageEvent msgEvt:
_renderer.ShowMessage(_loc.Get(msgEvt.MessageKey, msgEvt.Args ?? []));
@@ -784,7 +786,7 @@ public static class Program
break;
case ResourceChangedEvent resEvt:
var cookieResName = _loc.Get($"resource.{resEvt.Type.ToString().ToLower()}");
- _renderer.ShowMessage($"{cookieResName}: {resEvt.OldValue} → {resEvt.NewValue}");
+ _renderer.ShowMessage($"{cookieResName}: {resEvt.OldValue} {UnicodeSupport.Arrow} {resEvt.NewValue}");
break;
case MessageEvent msgEvt:
_renderer.ShowMessage(_loc.Get(msgEvt.MessageKey, msgEvt.Args ?? []));
diff --git a/src/OpenTheBox/Rendering/SpectreRenderer.cs b/src/OpenTheBox/Rendering/SpectreRenderer.cs
index 9dba95d..40722d4 100644
--- a/src/OpenTheBox/Rendering/SpectreRenderer.cs
+++ b/src/OpenTheBox/Rendering/SpectreRenderer.cs
@@ -323,7 +323,8 @@ public sealed class SpectreRenderer : IRenderer
{
if (_context.HasColors)
{
- var panel = new Panel($"[bold yellow]* {Markup.Escape(featureName)} *[/]")
+ var star = UnicodeSupport.Star;
+ var panel = new Panel($"[bold yellow]{star} {Markup.Escape(featureName)} {star}[/]")
.Border(BoxBorder.Double)
.BorderStyle(new Style(Color.Yellow))
.Padding(2, 0)
@@ -333,7 +334,7 @@ public sealed class SpectreRenderer : IRenderer
else
{
Console.WriteLine("========================================");
- Console.WriteLine($" * {featureName} *");
+ Console.WriteLine($" {UnicodeSupport.Star} {featureName} {UnicodeSupport.Star}");
Console.WriteLine("========================================");
}
}
@@ -429,14 +430,18 @@ public sealed class SpectreRenderer : IRenderer
///
/// Returns rarity star prefix for Rare and above items.
///
- private static string RarityStars(string rarity) => rarity.ToLowerInvariant() switch
+ private static string RarityStars(string rarity)
{
- "rare" => "* ",
- "epic" => "** ",
- "legendary" => "*** ",
- "mythic" => "**** ",
- _ => ""
- };
+ var s = UnicodeSupport.Star;
+ return rarity.ToLowerInvariant() switch
+ {
+ "rare" => $"{s} ",
+ "epic" => $"{s}{s} ",
+ "legendary" => $"{s}{s}{s} ",
+ "mythic" => $"{s}{s}{s}{s} ",
+ _ => ""
+ };
+ }
private static Color RarityColorValue(string rarity) => rarity.ToLowerInvariant() switch
{
diff --git a/src/OpenTheBox/Rendering/UnicodeSupport.cs b/src/OpenTheBox/Rendering/UnicodeSupport.cs
new file mode 100644
index 0000000..c856e81
--- /dev/null
+++ b/src/OpenTheBox/Rendering/UnicodeSupport.cs
@@ -0,0 +1,75 @@
+using System.Text;
+
+namespace OpenTheBox.Rendering;
+
+///
+/// Detects whether the terminal supports UTF-8 output and provides
+/// glyph accessors that return Unicode or ASCII equivalents accordingly.
+/// Call once at startup.
+///
+public static class UnicodeSupport
+{
+ /// True when the terminal accepts UTF-8 characters beyond CP437.
+ public static bool IsUtf8 { get; private set; }
+
+ // ── Glyphs ──────────────────────────────────────────────────────────
+
+ /// Arrow: → or ->
+ public static string Arrow => IsUtf8 ? "→" : "->";
+
+ /// Star for rarity prefix: ★ or *
+ public static string Star => IsUtf8 ? "★" : "*";
+
+ ///
+ /// Attempts to set UTF-8 output encoding and detects whether the
+ /// terminal actually supports it (Windows Terminal, PowerShell, etc.).
+ ///
+ public static void Initialize()
+ {
+ // Windows Terminal always sets WT_SESSION
+ if (Environment.GetEnvironmentVariable("WT_SESSION") is not null)
+ {
+ EnableUtf8();
+ return;
+ }
+
+ // Modern PowerShell (pwsh) or Windows PowerShell with PSModulePath
+ // typically supports UTF-8 when we set the encoding
+ if (Environment.GetEnvironmentVariable("PSModulePath") is not null)
+ {
+ EnableUtf8();
+ return;
+ }
+
+ // Non-Windows platforms (Linux, macOS) generally support UTF-8
+ if (!OperatingSystem.IsWindows())
+ {
+ EnableUtf8();
+ return;
+ }
+
+ // Fallback: try to set UTF-8 and check if it stuck
+ try
+ {
+ Console.OutputEncoding = Encoding.UTF8;
+ IsUtf8 = Console.OutputEncoding.CodePage == 65001;
+ }
+ catch
+ {
+ IsUtf8 = false;
+ }
+ }
+
+ private static void EnableUtf8()
+ {
+ try
+ {
+ Console.OutputEncoding = Encoding.UTF8;
+ }
+ catch
+ {
+ // Ignore if setting encoding fails
+ }
+ IsUtf8 = true;
+ }
+}