Add actual ASCII-Art for distros, add first three distros and being able to choose a starting-distro. Update UI to fix multiple small bugs.
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace KernelPanic.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves persisted distro ids to playable distro definitions.
|
||||
/// </summary>
|
||||
public sealed class DistroDatabase : ScriptableObject
|
||||
{
|
||||
[SerializeField] private List<DistroDefinition> allDistros = new();
|
||||
|
||||
public IReadOnlyList<DistroDefinition> AllDistros => allDistros;
|
||||
|
||||
public DistroDefinition FindById(string id)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(id))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
for (int i = 0; i < allDistros.Count; i++)
|
||||
{
|
||||
DistroDefinition distro = allDistros[i];
|
||||
if (distro != null && string.Equals(distro.Id, id, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return distro;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5af93d291e7d4d51b312bdf1e47dc6ef
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -14,6 +14,8 @@ namespace KernelPanic.Data
|
||||
[SerializeField] private Language primaryLanguage;
|
||||
[SerializeField] private Language secondaryLanguage;
|
||||
[SerializeField] private string passiveName;
|
||||
[SerializeField, TextArea] private string description;
|
||||
[SerializeField] private TextAsset asciiArt;
|
||||
[SerializeField] private Color accentColor = new(0.36f, 1f, 0.57f);
|
||||
[SerializeField] private int baseUptime;
|
||||
[SerializeField] private int baseRam;
|
||||
@@ -25,6 +27,8 @@ namespace KernelPanic.Data
|
||||
public Language PrimaryLanguage => primaryLanguage;
|
||||
public Language SecondaryLanguage => secondaryLanguage;
|
||||
public string PassiveName => passiveName;
|
||||
public string Description => description;
|
||||
public TextAsset AsciiArt => asciiArt;
|
||||
public Color AccentColor => accentColor;
|
||||
public int BaseUptime => baseUptime;
|
||||
public int BaseRam => baseRam;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using KernelPanic.Data;
|
||||
|
||||
namespace KernelPanic.Meta
|
||||
{
|
||||
@@ -7,7 +9,23 @@ namespace KernelPanic.Meta
|
||||
/// </summary>
|
||||
public sealed class GachaService
|
||||
{
|
||||
private readonly List<DistroDefinition> bannerPool = new();
|
||||
|
||||
public int PullTokens => 0; // TODO: Replace with the real pull-token wallet when that meta system exists.
|
||||
public IReadOnlyList<DistroDefinition> BannerPool => bannerPool;
|
||||
|
||||
public event Action BannerPoolChanged;
|
||||
|
||||
public void AddToBannerPool(DistroDefinition distro)
|
||||
{
|
||||
if (distro == null || bannerPool.Contains(distro))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bannerPool.Add(distro);
|
||||
BannerPoolChanged?.Invoke();
|
||||
}
|
||||
|
||||
public GachaPullResult PerformPull(EntropyWallet wallet, int cost)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
using System.Collections.Generic;
|
||||
using KernelPanic.Data;
|
||||
using System;
|
||||
|
||||
namespace KernelPanic.Meta
|
||||
{
|
||||
/// <summary>
|
||||
/// Tracks player-owned persistent unlocks for menu presentation and collection views.
|
||||
/// </summary>
|
||||
public sealed class PlayerCollection
|
||||
{
|
||||
private readonly List<DistroDefinition> ownedUnits = new();
|
||||
|
||||
// TODO: Populate owned units from SaveService during bootstrap.
|
||||
// TODO: Add newly unlocked units from GachaService rewards when pulls exist.
|
||||
public IReadOnlyList<DistroDefinition> OwnedUnits => ownedUnits;
|
||||
|
||||
public event Action Changed;
|
||||
|
||||
public void Add(DistroDefinition unit)
|
||||
{
|
||||
if (unit == null || ownedUnits.Contains(unit))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Persistence is wired by the owner so this domain class stays IO-free.
|
||||
ownedUnits.Add(unit);
|
||||
Changed?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4a10ff65b6e9478d9fe638f8a39f8144
|
||||
@@ -1,4 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
namespace KernelPanic.Meta
|
||||
{
|
||||
@@ -7,22 +10,66 @@ namespace KernelPanic.Meta
|
||||
/// </summary>
|
||||
public sealed class SaveService
|
||||
{
|
||||
private const string SaveFileName = "kernel-panic-save.json";
|
||||
|
||||
public string SavePath => Path.Combine(Application.persistentDataPath, SaveFileName);
|
||||
|
||||
public void Save(SaveData data)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
data ??= new SaveData();
|
||||
data.EnsureLists();
|
||||
|
||||
string directory = Path.GetDirectoryName(SavePath);
|
||||
if (!string.IsNullOrEmpty(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
string json = JsonUtility.ToJson(data, true);
|
||||
File.WriteAllText(SavePath, json);
|
||||
}
|
||||
|
||||
public SaveData Load()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (!File.Exists(SavePath))
|
||||
{
|
||||
return SaveData.CreateDefault();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
SaveData data = JsonUtility.FromJson<SaveData>(File.ReadAllText(SavePath));
|
||||
data ??= SaveData.CreateDefault();
|
||||
data.EnsureLists();
|
||||
return data;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"Save file could not be loaded; starting fresh. {ex.Message}");
|
||||
return SaveData.CreateDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores persistent progression data for serialization.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class SaveData
|
||||
{
|
||||
public int EntropyBalance { get; set; }
|
||||
public bool starterChosen;
|
||||
public List<string> ownedUnitIds = new();
|
||||
public List<string> bannerPoolIds = new();
|
||||
|
||||
public static SaveData CreateDefault()
|
||||
{
|
||||
return new SaveData();
|
||||
}
|
||||
|
||||
public void EnsureLists()
|
||||
{
|
||||
ownedUnitIds ??= new List<string>();
|
||||
bannerPoolIds ??= new List<string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TextCore.Text;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace KernelPanic.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Fits monospace ASCII art into a fixed UI Toolkit label region.
|
||||
/// </summary>
|
||||
public sealed class AsciiArtFitter
|
||||
{
|
||||
private const float ReferenceFontSize = 20f;
|
||||
private const float LineHeightRatio = 1.05f;
|
||||
private const float MinFontSize = 6f;
|
||||
private const float MaxFontSize = 24f;
|
||||
private const float MonospaceTolerance = 0.02f;
|
||||
private static readonly Dictionary<FontAsset, float> AdvanceRatioByFont = new();
|
||||
private static readonly HashSet<FontAsset> WarnedFonts = new();
|
||||
private static bool hasDefaultAdvanceRatio;
|
||||
private static float defaultAdvanceRatio;
|
||||
|
||||
private readonly Label label;
|
||||
private readonly FontAsset font;
|
||||
private string art;
|
||||
|
||||
public AsciiArtFitter(Label label, FontAsset font)
|
||||
{
|
||||
this.label = label;
|
||||
this.font = font;
|
||||
label.RegisterCallback<GeometryChangedEvent>(_ => Fit());
|
||||
}
|
||||
|
||||
public void SetArt(string value)
|
||||
{
|
||||
art = value;
|
||||
Fit();
|
||||
}
|
||||
|
||||
private void Fit()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(art))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Rect region = label.layout;
|
||||
if (!IsUsable(region.width) || !IsUsable(region.height))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GetArtDimensions(art, out int rows, out int columns);
|
||||
if (rows <= 0 || columns <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float advanceRatio = GetAdvanceRatio();
|
||||
float widthFit = region.width / (columns * advanceRatio);
|
||||
float heightFit = region.height / (rows * LineHeightRatio);
|
||||
float fontSize = Mathf.Clamp(Mathf.Floor(Mathf.Min(widthFit, heightFit)), MinFontSize, MaxFontSize);
|
||||
float blockWidth = columns * fontSize * advanceRatio;
|
||||
float blockHeight = rows * fontSize * LineHeightRatio;
|
||||
|
||||
label.style.fontSize = fontSize;
|
||||
label.style.paddingLeft = Mathf.Max(0f, (region.width - blockWidth) * 0.5f);
|
||||
label.style.paddingTop = Mathf.Max(0f, (region.height - blockHeight) * 0.5f);
|
||||
label.style.paddingRight = 0f;
|
||||
label.style.paddingBottom = 0f;
|
||||
}
|
||||
|
||||
private float GetAdvanceRatio()
|
||||
{
|
||||
if (font == null && hasDefaultAdvanceRatio)
|
||||
{
|
||||
return defaultAdvanceRatio;
|
||||
}
|
||||
|
||||
if (font != null && AdvanceRatioByFont.TryGetValue(font, out float cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
float previousFontSize = label.resolvedStyle.fontSize;
|
||||
label.style.fontSize = ReferenceFontSize;
|
||||
|
||||
float mWidth = label.MeasureTextSize("MMMMMMMMMM", 0f, VisualElement.MeasureMode.Undefined, 0f, VisualElement.MeasureMode.Undefined).x;
|
||||
float iWidth = label.MeasureTextSize("iiiiiiiiii", 0f, VisualElement.MeasureMode.Undefined, 0f, VisualElement.MeasureMode.Undefined).x;
|
||||
label.style.fontSize = previousFontSize;
|
||||
|
||||
float advanceRatio = mWidth > 0f ? mWidth / (10f * ReferenceFontSize) : 0.6f;
|
||||
if (font == null)
|
||||
{
|
||||
defaultAdvanceRatio = advanceRatio;
|
||||
hasDefaultAdvanceRatio = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
AdvanceRatioByFont[font] = advanceRatio;
|
||||
}
|
||||
|
||||
if (font != null && mWidth > 0f && iWidth > 0f)
|
||||
{
|
||||
float difference = Mathf.Abs(mWidth - iWidth) / Mathf.Max(mWidth, iWidth);
|
||||
if (difference > MonospaceTolerance && WarnedFonts.Add(font))
|
||||
{
|
||||
Debug.LogWarning($"ASCII art font '{font.name}' is not monospace; art may misalign.");
|
||||
}
|
||||
}
|
||||
|
||||
return advanceRatio;
|
||||
}
|
||||
|
||||
private static void GetArtDimensions(string value, out int rows, out int columns)
|
||||
{
|
||||
string normalized = value.Replace("\r\n", "\n").Replace('\r', '\n');
|
||||
if (normalized.EndsWith("\n", StringComparison.Ordinal))
|
||||
{
|
||||
normalized = normalized[..^1];
|
||||
}
|
||||
|
||||
rows = 0;
|
||||
columns = 0;
|
||||
string[] lines = normalized.Split('\n');
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
{
|
||||
rows++;
|
||||
columns = Mathf.Max(columns, lines[i].Length);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsUsable(float value)
|
||||
{
|
||||
return value > 0f && !float.IsNaN(value) && !float.IsInfinity(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6f677d4ac6de4dca8e92a3efa687ff1b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,134 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace KernelPanic.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Keeps the ambient boot-log background filled by rewriting a fixed stack of labels.
|
||||
/// </summary>
|
||||
public sealed class BackgroundLogRingBuffer
|
||||
{
|
||||
private const float LineHeight = 18f;
|
||||
private const int ExtraLines = 2;
|
||||
private const int TickMilliseconds = 450;
|
||||
|
||||
private readonly VisualElement layer;
|
||||
private readonly IReadOnlyList<string> sourceLines;
|
||||
private readonly List<Label> labels = new();
|
||||
private IVisualElementScheduledItem tickSchedule;
|
||||
private float lastRegionHeight;
|
||||
private int cursor;
|
||||
private bool reducedMotion;
|
||||
private bool started;
|
||||
|
||||
public BackgroundLogRingBuffer(VisualElement layer, IReadOnlyList<string> sourceLines)
|
||||
{
|
||||
this.layer = layer;
|
||||
this.sourceLines = sourceLines ?? Array.Empty<string>();
|
||||
this.layer.RegisterCallback<GeometryChangedEvent>(HandleGeometryChanged);
|
||||
}
|
||||
|
||||
public void Start(bool useReducedMotion)
|
||||
{
|
||||
reducedMotion = useReducedMotion;
|
||||
started = true;
|
||||
cursor = 0;
|
||||
|
||||
if (lastRegionHeight > 0f)
|
||||
{
|
||||
RebuildLabelsForHeight(lastRegionHeight);
|
||||
Render();
|
||||
}
|
||||
|
||||
RestartTick();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
started = false;
|
||||
tickSchedule?.Pause();
|
||||
tickSchedule = null;
|
||||
}
|
||||
|
||||
private void HandleGeometryChanged(GeometryChangedEvent evt)
|
||||
{
|
||||
if (Mathf.Approximately(evt.newRect.height, evt.oldRect.height))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lastRegionHeight = evt.newRect.height;
|
||||
if (!started)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RebuildLabelsForHeight(evt.newRect.height);
|
||||
Render();
|
||||
}
|
||||
|
||||
private void RebuildLabelsForHeight(float regionHeight)
|
||||
{
|
||||
int targetCount = Mathf.Max(ExtraLines, Mathf.CeilToInt(regionHeight / LineHeight) + ExtraLines);
|
||||
if (labels.Count == targetCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
layer.Clear();
|
||||
labels.Clear();
|
||||
|
||||
for (int i = 0; i < targetCount; i++)
|
||||
{
|
||||
Label label = new() { pickingMode = PickingMode.Ignore };
|
||||
label.AddToClassList("background-log-line");
|
||||
layer.Add(label);
|
||||
labels.Add(label);
|
||||
}
|
||||
}
|
||||
|
||||
private void RestartTick()
|
||||
{
|
||||
tickSchedule?.Pause();
|
||||
tickSchedule = null;
|
||||
|
||||
if (reducedMotion || sourceLines.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
tickSchedule = layer.schedule.Execute(Advance).Every(TickMilliseconds);
|
||||
}
|
||||
|
||||
private void Advance()
|
||||
{
|
||||
if (sourceLines.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cursor = (cursor + 1) % sourceLines.Count;
|
||||
Render();
|
||||
}
|
||||
|
||||
private void Render()
|
||||
{
|
||||
if (sourceLines.Count == 0)
|
||||
{
|
||||
foreach (Label label in labels)
|
||||
{
|
||||
label.text = string.Empty;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < labels.Count; i++)
|
||||
{
|
||||
labels[i].text = sourceLines[(cursor + i) % sourceLines.Count];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 03ad9dd5bf1d46ee9cd2a6f54d17b5fa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,108 @@
|
||||
using KernelPanic.Data;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TextCore.Text;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace KernelPanic.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Applies terminal-safe rendering rules for distro ASCII art.
|
||||
/// </summary>
|
||||
public static class DistroArtPresenter
|
||||
{
|
||||
private static readonly string[][] DashClasses =
|
||||
{
|
||||
new[] { "dash", "dash-top", "dash-01" },
|
||||
new[] { "dash", "dash-top", "dash-02" },
|
||||
new[] { "dash", "dash-top", "dash-03" },
|
||||
new[] { "dash", "dash-top", "dash-04" },
|
||||
new[] { "dash", "dash-top", "dash-05" },
|
||||
new[] { "dash", "dash-top", "dash-06" },
|
||||
new[] { "dash", "dash-top", "dash-07" },
|
||||
new[] { "dash", "dash-top", "dash-08" },
|
||||
new[] { "dash", "dash-bottom", "dash-01" },
|
||||
new[] { "dash", "dash-bottom", "dash-02" },
|
||||
new[] { "dash", "dash-bottom", "dash-03" },
|
||||
new[] { "dash", "dash-bottom", "dash-04" },
|
||||
new[] { "dash", "dash-bottom", "dash-05" },
|
||||
new[] { "dash", "dash-bottom", "dash-06" },
|
||||
new[] { "dash", "dash-bottom", "dash-07" },
|
||||
new[] { "dash", "dash-bottom", "dash-08" },
|
||||
new[] { "dash", "dash-left", "dash-v01" },
|
||||
new[] { "dash", "dash-left", "dash-v02" },
|
||||
new[] { "dash", "dash-left", "dash-v03" },
|
||||
new[] { "dash", "dash-left", "dash-v04" },
|
||||
new[] { "dash", "dash-left", "dash-v05" },
|
||||
new[] { "dash", "dash-left", "dash-v06" },
|
||||
new[] { "dash", "dash-right", "dash-v01" },
|
||||
new[] { "dash", "dash-right", "dash-v02" },
|
||||
new[] { "dash", "dash-right", "dash-v03" },
|
||||
new[] { "dash", "dash-right", "dash-v04" },
|
||||
new[] { "dash", "dash-right", "dash-v05" },
|
||||
new[] { "dash", "dash-right", "dash-v06" }
|
||||
};
|
||||
|
||||
public static VisualElement CreatePlaceholder()
|
||||
{
|
||||
VisualElement placeholder = new();
|
||||
placeholder.AddToClassList("ascii-placeholder");
|
||||
|
||||
for (int i = 0; i < DashClasses.Length; i++)
|
||||
{
|
||||
VisualElement dash = new();
|
||||
for (int classIndex = 0; classIndex < DashClasses[i].Length; classIndex++)
|
||||
{
|
||||
dash.AddToClassList(DashClasses[i][classIndex]);
|
||||
}
|
||||
|
||||
placeholder.Add(dash);
|
||||
}
|
||||
|
||||
return placeholder;
|
||||
}
|
||||
|
||||
public static void ConfigureArtLabel(Label label, FontAsset font)
|
||||
{
|
||||
label.enableRichText = false;
|
||||
label.AddToClassList("ascii-art");
|
||||
if (font != null)
|
||||
{
|
||||
label.style.unityFontDefinition = new StyleFontDefinition(font);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Render(Label label, VisualElement placeholder, DistroDefinition unit)
|
||||
{
|
||||
string art = TryGetArtText(unit);
|
||||
bool hasArt = !string.IsNullOrWhiteSpace(art);
|
||||
|
||||
label.text = hasArt ? art : string.Empty;
|
||||
label.EnableInClassList("hidden", !hasArt);
|
||||
placeholder.EnableInClassList("hidden", hasArt);
|
||||
return hasArt ? art : null;
|
||||
}
|
||||
|
||||
private static string TryGetArtText(DistroDefinition unit)
|
||||
{
|
||||
if (unit == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
UnityEngine.TextAsset asciiArt = unit.AsciiArt;
|
||||
if (asciiArt == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return asciiArt.text;
|
||||
}
|
||||
catch (MissingReferenceException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4bf334307f22465cad853cb6b3757468
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using KernelPanic.Data;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TextCore.Text;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace KernelPanic.UI
|
||||
@@ -13,11 +14,11 @@ namespace KernelPanic.UI
|
||||
public sealed class FeaturedUnitPanel
|
||||
{
|
||||
private const string EmptyAccent = "5cff91";
|
||||
private const string EmptyAscii = " _\n | |\n ___| |__\n / __| '_ \\\n \\__ \\ | | |\n |___/_| |_|";
|
||||
|
||||
private VisualElement panel;
|
||||
private Label titleLabel;
|
||||
private Label asciiLabel;
|
||||
private VisualElement asciiPlaceholder;
|
||||
private Label unitNameLabel;
|
||||
private Label languagesLabel;
|
||||
private Label passiveLabel;
|
||||
@@ -26,11 +27,14 @@ namespace KernelPanic.UI
|
||||
private Label emptyHintLabel;
|
||||
private VisualElement populatedState;
|
||||
private VisualElement emptyState;
|
||||
private FontAsset monospaceFont;
|
||||
private AsciiArtFitter asciiFitter;
|
||||
private IReadOnlyList<DistroDefinition> units = Array.Empty<DistroDefinition>();
|
||||
private int selectedIndex;
|
||||
|
||||
public void Bind(VisualElement root)
|
||||
public void Bind(VisualElement root, FontAsset artFont)
|
||||
{
|
||||
monospaceFont = artFont;
|
||||
panel = root.Q<VisualElement>("FeaturedUnitPanel");
|
||||
titleLabel = root.Q<Label>("FeaturedUnitTitle");
|
||||
asciiLabel = root.Q<Label>("FeaturedUnitAscii");
|
||||
@@ -42,6 +46,12 @@ namespace KernelPanic.UI
|
||||
emptyHintLabel = root.Q<Label>("FeaturedUnitEmptyHint");
|
||||
populatedState = root.Q<VisualElement>("FeaturedUnitPopulatedState");
|
||||
emptyState = root.Q<VisualElement>("FeaturedUnitEmptyState");
|
||||
|
||||
DistroArtPresenter.ConfigureArtLabel(asciiLabel, monospaceFont);
|
||||
asciiFitter = new AsciiArtFitter(asciiLabel, monospaceFont);
|
||||
asciiPlaceholder = DistroArtPresenter.CreatePlaceholder();
|
||||
asciiPlaceholder.AddToClassList("hidden");
|
||||
populatedState.Insert(0, asciiPlaceholder);
|
||||
}
|
||||
|
||||
public void Refresh(IReadOnlyList<DistroDefinition> ownedUnits)
|
||||
@@ -92,7 +102,7 @@ namespace KernelPanic.UI
|
||||
|
||||
string displayName = string.IsNullOrWhiteSpace(unit.DisplayName) ? unit.name : unit.DisplayName;
|
||||
titleLabel.text = "[ neofetch ]";
|
||||
asciiLabel.text = EmptyAscii; // TODO: Replace with unit-specific ASCII copy when unit presentation data exists.
|
||||
asciiFitter.SetArt(DistroArtPresenter.Render(asciiLabel, asciiPlaceholder, unit));
|
||||
unitNameLabel.text = displayName;
|
||||
languagesLabel.text = $"{unit.PrimaryLanguage} / {unit.SecondaryLanguage}";
|
||||
passiveLabel.text = string.IsNullOrWhiteSpace(unit.PassiveName) ? "--" : unit.PassiveName;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using KernelPanic.Core;
|
||||
using KernelPanic.Data;
|
||||
using KernelPanic.Meta;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TextCore.Text;
|
||||
@@ -26,9 +27,15 @@ namespace KernelPanic.UI
|
||||
|
||||
[SerializeField] private FontAsset monospaceFont; // TODO: Assign a real monospace FontAsset when typography assets exist.
|
||||
[SerializeField] private string motdBody = "unstable userspace detected; keep a rollback shell open.";
|
||||
[SerializeField] private DistroDatabase distroDatabase;
|
||||
[SerializeField] private FeaturedUnitPanel featuredUnitPanel = new();
|
||||
|
||||
private readonly List<CommandMenuEntry> commandEntries = new();
|
||||
private readonly List<VisualElement> starterCards = new();
|
||||
private readonly List<Label> starterNames = new();
|
||||
private readonly List<Label> starterLanguages = new();
|
||||
private readonly List<Label> starterDescriptions = new();
|
||||
private readonly List<VisualElement> collectionRows = new();
|
||||
private UIDocument document;
|
||||
private VisualElement root;
|
||||
private VisualElement shellRoot;
|
||||
@@ -39,6 +46,9 @@ namespace KernelPanic.UI
|
||||
private VisualElement settingsPanel;
|
||||
private VisualElement eventBanner;
|
||||
private VisualElement backgroundLogLayer;
|
||||
private VisualElement starterModal;
|
||||
private VisualElement collectionList;
|
||||
private VisualElement collectionDetail;
|
||||
private Label appIdLabel;
|
||||
private Label entropyLabel;
|
||||
private Label pullTokensLabel;
|
||||
@@ -46,23 +56,32 @@ namespace KernelPanic.UI
|
||||
private Label promptCursorLabel;
|
||||
private Label bootIntroLogLabel;
|
||||
private Label motdBodyLabel;
|
||||
private Label starterConfirmLabel;
|
||||
private VisualElement motdBlock;
|
||||
private Button collectionBackButton;
|
||||
private Button gachaBackButton;
|
||||
private Button settingsBackButton;
|
||||
private SaveService saveService;
|
||||
private SaveData saveData;
|
||||
private EntropyWallet wallet;
|
||||
private GachaService gachaService;
|
||||
private PlayerCollection playerCollection;
|
||||
private BackgroundLogRingBuffer backgroundLog;
|
||||
private IEventBannerSource eventBannerSource;
|
||||
private int selectedCommandIndex;
|
||||
private int selectedStarterIndex;
|
||||
private int selectedCollectionIndex;
|
||||
private float bootIntroElapsed;
|
||||
private int bootIntroCharacterCount;
|
||||
private bool cursorVisible;
|
||||
private bool suppressNextClick;
|
||||
private bool starterModalActive;
|
||||
private bool starterConfirming;
|
||||
private bool warnedUnresolvedSaveId;
|
||||
private string bootIntroCopy;
|
||||
private IVisualElementScheduledItem blinkSchedule;
|
||||
private IVisualElementScheduledItem bootIntroSchedule;
|
||||
private IVisualElementScheduledItem starterCloseSchedule;
|
||||
|
||||
public void Initialize(EntropyWallet initializedWallet)
|
||||
{
|
||||
@@ -73,13 +92,18 @@ namespace KernelPanic.UI
|
||||
private void Awake()
|
||||
{
|
||||
document = GetComponent<UIDocument>();
|
||||
saveService = new SaveService();
|
||||
gachaService = new GachaService();
|
||||
playerCollection = new PlayerCollection(); // TODO: Replace with persistent player-collection service composition.
|
||||
Initialize(new EntropyWallet()); // TODO: Replace with persistent wallet service composition.
|
||||
BindElements();
|
||||
BindCommandEntries();
|
||||
RegisterCommandEntryCallbacks();
|
||||
featuredUnitPanel.Bind(root);
|
||||
RegisterStarterCallbacks();
|
||||
featuredUnitPanel.Bind(root, monospaceFont);
|
||||
LoadMetaState();
|
||||
playerCollection.Changed += HandleMetaStateChanged;
|
||||
gachaService.BannerPoolChanged += HandleMetaStateChanged;
|
||||
ApplyOptionalFont();
|
||||
}
|
||||
|
||||
@@ -92,6 +116,7 @@ namespace KernelPanic.UI
|
||||
RefreshCurrencyReadouts();
|
||||
RefreshEventBanner();
|
||||
featuredUnitPanel.Refresh(playerCollection.OwnedUnits);
|
||||
RefreshCollection();
|
||||
SelectCommand(0);
|
||||
ShowMainMenu();
|
||||
StartAmbientSchedules();
|
||||
@@ -104,6 +129,7 @@ namespace KernelPanic.UI
|
||||
blinkSchedule?.Pause();
|
||||
backgroundLog?.Stop();
|
||||
bootIntroSchedule?.Pause();
|
||||
starterCloseSchedule?.Pause();
|
||||
}
|
||||
|
||||
private void BindElements()
|
||||
@@ -119,6 +145,9 @@ namespace KernelPanic.UI
|
||||
motdBlock = root.Q<VisualElement>("MotdBlock");
|
||||
backgroundLogLayer = root.Q<VisualElement>("BackgroundLogLayer");
|
||||
backgroundLog = new BackgroundLogRingBuffer(backgroundLogLayer, BootLogCopy.Lines);
|
||||
starterModal = root.Q<VisualElement>("StarterModal");
|
||||
collectionList = root.Q<VisualElement>("CollectionList");
|
||||
collectionDetail = root.Q<VisualElement>("CollectionDetail");
|
||||
|
||||
appIdLabel = root.Q<Label>("AppIdLabel");
|
||||
entropyLabel = root.Q<Label>("EntropyLabel");
|
||||
@@ -127,6 +156,15 @@ namespace KernelPanic.UI
|
||||
promptCursorLabel = root.Q<Label>("PromptCursorLabel");
|
||||
bootIntroLogLabel = root.Q<Label>("BootIntroLogLabel");
|
||||
motdBodyLabel = root.Q<Label>("MotdBodyLabel");
|
||||
starterConfirmLabel = root.Q<Label>("StarterConfirmLabel");
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
starterCards.Add(root.Q<VisualElement>($"StarterOption{i}"));
|
||||
starterNames.Add(root.Q<Label>($"StarterName{i}"));
|
||||
starterLanguages.Add(root.Q<Label>($"StarterLanguages{i}"));
|
||||
starterDescriptions.Add(root.Q<Label>($"StarterDescription{i}"));
|
||||
}
|
||||
|
||||
collectionBackButton = root.Q<Button>("CollectionBackButton");
|
||||
gachaBackButton = root.Q<Button>("GachaBackButton");
|
||||
@@ -171,6 +209,77 @@ namespace KernelPanic.UI
|
||||
settingsBackButton.clicked -= ShowMainMenu;
|
||||
}
|
||||
|
||||
private void LoadMetaState()
|
||||
{
|
||||
saveData = saveService.Load();
|
||||
saveData.EnsureLists();
|
||||
|
||||
for (int i = 0; i < saveData.ownedUnitIds.Count; i++)
|
||||
{
|
||||
DistroDefinition unit = ResolveSavedDistro(saveData.ownedUnitIds[i]);
|
||||
if (unit != null)
|
||||
{
|
||||
playerCollection.Add(unit);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < saveData.bannerPoolIds.Count; i++)
|
||||
{
|
||||
DistroDefinition unit = ResolveSavedDistro(saveData.bannerPoolIds[i]);
|
||||
if (unit != null)
|
||||
{
|
||||
gachaService.AddToBannerPool(unit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private DistroDefinition ResolveSavedDistro(string id)
|
||||
{
|
||||
DistroDefinition unit = distroDatabase == null ? null : distroDatabase.FindById(id);
|
||||
if (unit == null && !warnedUnresolvedSaveId)
|
||||
{
|
||||
warnedUnresolvedSaveId = true;
|
||||
Debug.LogWarning($"Save references a distro id that is not in DistroDatabase: {id}");
|
||||
}
|
||||
|
||||
return unit;
|
||||
}
|
||||
|
||||
private void HandleMetaStateChanged()
|
||||
{
|
||||
SaveCurrentState();
|
||||
featuredUnitPanel.Refresh(playerCollection.OwnedUnits);
|
||||
RefreshCollection();
|
||||
}
|
||||
|
||||
private void SaveCurrentState()
|
||||
{
|
||||
saveData ??= SaveData.CreateDefault();
|
||||
saveData.EnsureLists();
|
||||
saveData.ownedUnitIds.Clear();
|
||||
saveData.bannerPoolIds.Clear();
|
||||
|
||||
for (int i = 0; i < playerCollection.OwnedUnits.Count; i++)
|
||||
{
|
||||
DistroDefinition unit = playerCollection.OwnedUnits[i];
|
||||
if (unit != null && !string.IsNullOrWhiteSpace(unit.Id))
|
||||
{
|
||||
saveData.ownedUnitIds.Add(unit.Id);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < gachaService.BannerPool.Count; i++)
|
||||
{
|
||||
DistroDefinition unit = gachaService.BannerPool[i];
|
||||
if (unit != null && !string.IsNullOrWhiteSpace(unit.Id))
|
||||
{
|
||||
saveData.bannerPoolIds.Add(unit.Id);
|
||||
}
|
||||
}
|
||||
|
||||
saveService.Save(saveData);
|
||||
}
|
||||
|
||||
private void RefreshStaticText()
|
||||
{
|
||||
appIdLabel.text = $"kernel-panic v{Application.version} - tty1";
|
||||
@@ -189,6 +298,20 @@ namespace KernelPanic.UI
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterStarterCallbacks()
|
||||
{
|
||||
for (int i = 0; i < starterCards.Count; i++)
|
||||
{
|
||||
int index = i;
|
||||
starterCards[i].RegisterCallback<PointerEnterEvent>(_ => SelectStarter(index));
|
||||
starterCards[i].RegisterCallback<ClickEvent>(_ =>
|
||||
{
|
||||
SelectStarter(index);
|
||||
ConfirmStarterSelectionIntent();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshCurrencyReadouts()
|
||||
{
|
||||
if (entropyLabel == null || pullTokensLabel == null || gachaService == null || wallet == null)
|
||||
@@ -215,6 +338,131 @@ namespace KernelPanic.UI
|
||||
: banner.Text;
|
||||
}
|
||||
|
||||
private void RefreshCollection()
|
||||
{
|
||||
if (collectionList == null || collectionDetail == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
collectionRows.Clear();
|
||||
collectionList.Clear();
|
||||
|
||||
IReadOnlyList<DistroDefinition> units = playerCollection.OwnedUnits;
|
||||
if (units.Count == 0)
|
||||
{
|
||||
collectionDetail.Clear();
|
||||
collectionDetail.Add(new Label("no units installed") { name = "CollectionEmptyTitle" });
|
||||
collectionDetail.Add(new Label("run: curl gacha.sh | sh") { name = "CollectionEmptyHint" });
|
||||
return;
|
||||
}
|
||||
|
||||
selectedCollectionIndex = Mathf.Clamp(selectedCollectionIndex, 0, units.Count - 1);
|
||||
for (int i = 0; i < units.Count; i++)
|
||||
{
|
||||
int index = i;
|
||||
DistroDefinition unit = units[i];
|
||||
VisualElement row = new();
|
||||
row.AddToClassList("collection-row");
|
||||
row.RegisterCallback<PointerEnterEvent>(_ => SelectCollectionUnit(index));
|
||||
row.RegisterCallback<ClickEvent>(_ => SelectCollectionUnit(index));
|
||||
|
||||
Label name = new(DisplayName(unit));
|
||||
name.AddToClassList("collection-row-name");
|
||||
name.style.color = new StyleColor(unit.AccentColor);
|
||||
|
||||
Label languages = new(FormatLanguages(unit));
|
||||
languages.AddToClassList("collection-row-languages");
|
||||
|
||||
row.Add(name);
|
||||
row.Add(languages);
|
||||
collectionList.Add(row);
|
||||
collectionRows.Add(row);
|
||||
}
|
||||
|
||||
SelectCollectionUnit(selectedCollectionIndex);
|
||||
}
|
||||
|
||||
private void SelectCollectionUnit(int index)
|
||||
{
|
||||
IReadOnlyList<DistroDefinition> units = playerCollection.OwnedUnits;
|
||||
if (units.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
selectedCollectionIndex = Mathf.Clamp(index, 0, units.Count - 1);
|
||||
for (int i = 0; i < collectionRows.Count; i++)
|
||||
{
|
||||
collectionRows[i].EnableInClassList(SelectedClassName, i == selectedCollectionIndex);
|
||||
}
|
||||
|
||||
RenderCollectionDetail(units[selectedCollectionIndex]);
|
||||
}
|
||||
|
||||
private void RenderCollectionDetail(DistroDefinition unit)
|
||||
{
|
||||
collectionDetail.Clear();
|
||||
|
||||
// TODO: Unify this readout with FeaturedUnitPanel when unit presentation grows beyond labels.
|
||||
VisualElement readout = new();
|
||||
readout.AddToClassList("collection-detail-readout");
|
||||
|
||||
Label artLabel = new();
|
||||
DistroArtPresenter.ConfigureArtLabel(artLabel, monospaceFont);
|
||||
AsciiArtFitter artFitter = new(artLabel, monospaceFont);
|
||||
VisualElement artPlaceholder = DistroArtPresenter.CreatePlaceholder();
|
||||
artFitter.SetArt(DistroArtPresenter.Render(artLabel, artPlaceholder, unit));
|
||||
readout.Add(artPlaceholder);
|
||||
readout.Add(artLabel);
|
||||
|
||||
VisualElement details = new();
|
||||
details.AddToClassList("collection-detail-values");
|
||||
|
||||
Label name = new(DisplayName(unit));
|
||||
name.AddToClassList("collection-detail-name");
|
||||
name.style.color = new StyleColor(unit.AccentColor);
|
||||
details.Add(name);
|
||||
|
||||
details.Add(BuildDetailLine("lang", FormatLanguages(unit)));
|
||||
details.Add(BuildDetailLine("passive", string.IsNullOrWhiteSpace(unit.PassiveName) ? "--" : unit.PassiveName));
|
||||
details.Add(BuildDetailLine("uptime", unit.BaseUptime.ToString()));
|
||||
details.Add(BuildDetailLine("ram", unit.BaseRam.ToString()));
|
||||
details.Add(BuildDetailLine("cycles", unit.BaseCyclesPerTurn.ToString()));
|
||||
|
||||
readout.Add(details);
|
||||
collectionDetail.Add(readout);
|
||||
|
||||
Label description = new(string.IsNullOrWhiteSpace(unit.Description) ? "--" : unit.Description);
|
||||
description.AddToClassList("collection-detail-description");
|
||||
collectionDetail.Add(description);
|
||||
}
|
||||
|
||||
private static VisualElement BuildDetailLine(string key, string value)
|
||||
{
|
||||
VisualElement row = new();
|
||||
row.AddToClassList("kv-row");
|
||||
|
||||
Label keyLabel = new(key);
|
||||
keyLabel.AddToClassList("kv-key");
|
||||
Label valueLabel = new(value);
|
||||
valueLabel.AddToClassList("kv-value");
|
||||
|
||||
row.Add(keyLabel);
|
||||
row.Add(valueLabel);
|
||||
return row;
|
||||
}
|
||||
|
||||
private static string DisplayName(DistroDefinition unit)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(unit.DisplayName) ? unit.name : unit.DisplayName;
|
||||
}
|
||||
|
||||
private static string FormatLanguages(DistroDefinition unit)
|
||||
{
|
||||
return $"{unit.PrimaryLanguage} / {unit.SecondaryLanguage}";
|
||||
}
|
||||
|
||||
private void StartAmbientSchedules()
|
||||
{
|
||||
blinkSchedule?.Pause();
|
||||
@@ -277,6 +525,7 @@ namespace KernelPanic.UI
|
||||
shellRoot.RemoveFromClassList("boot-hidden");
|
||||
shellRoot.AddToClassList("boot-visible");
|
||||
backgroundLog?.Start(UIPreferences.ReducedMotion);
|
||||
ShowStarterModalIfNeeded();
|
||||
root.Focus();
|
||||
}
|
||||
|
||||
@@ -299,6 +548,13 @@ namespace KernelPanic.UI
|
||||
return;
|
||||
}
|
||||
|
||||
if (starterModalActive)
|
||||
{
|
||||
HandleStarterKeyDown(evt);
|
||||
evt.StopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
if (evt.keyCode == KeyCode.UpArrow)
|
||||
{
|
||||
SelectCommand(selectedCommandIndex - 1);
|
||||
@@ -342,6 +598,181 @@ namespace KernelPanic.UI
|
||||
suppressNextClick = SkipBootIntro();
|
||||
}
|
||||
|
||||
private void ShowStarterModalIfNeeded()
|
||||
{
|
||||
if (saveData == null || saveData.starterChosen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IReadOnlyList<DistroDefinition> starters = GetStarterDistros();
|
||||
if (starters.Count < 3)
|
||||
{
|
||||
Debug.LogWarning("Starter distro selection needs three distros in DistroDatabase.");
|
||||
return;
|
||||
}
|
||||
|
||||
starterModalActive = true;
|
||||
starterConfirming = false;
|
||||
starterConfirmLabel.AddToClassList(HiddenClassName);
|
||||
starterModal.RemoveFromClassList(HiddenClassName);
|
||||
|
||||
for (int i = 0; i < starterCards.Count; i++)
|
||||
{
|
||||
DistroDefinition unit = starters[i];
|
||||
starterNames[i].text = DisplayName(unit);
|
||||
starterNames[i].style.color = new StyleColor(unit.AccentColor);
|
||||
starterLanguages[i].text = FormatLanguages(unit);
|
||||
starterDescriptions[i].text = unit.Description;
|
||||
}
|
||||
|
||||
SelectStarter(0);
|
||||
root.Focus();
|
||||
}
|
||||
|
||||
private IReadOnlyList<DistroDefinition> GetStarterDistros()
|
||||
{
|
||||
return distroDatabase == null ? Array.Empty<DistroDefinition>() : distroDatabase.AllDistros;
|
||||
}
|
||||
|
||||
private void SelectStarter(int index)
|
||||
{
|
||||
IReadOnlyList<DistroDefinition> starters = GetStarterDistros();
|
||||
if (starters.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
selectedStarterIndex = (index + Mathf.Min(starters.Count, starterCards.Count)) % Mathf.Min(starters.Count, starterCards.Count);
|
||||
starterConfirming = false;
|
||||
starterConfirmLabel.AddToClassList(HiddenClassName);
|
||||
|
||||
for (int i = 0; i < starterCards.Count; i++)
|
||||
{
|
||||
bool selected = i == selectedStarterIndex;
|
||||
starterCards[i].EnableInClassList(SelectedClassName, selected);
|
||||
if (i < starters.Count && selected)
|
||||
{
|
||||
StyleColor accent = new(starters[i].AccentColor);
|
||||
starterCards[i].style.borderTopColor = accent;
|
||||
starterCards[i].style.borderRightColor = accent;
|
||||
starterCards[i].style.borderBottomColor = accent;
|
||||
starterCards[i].style.borderLeftColor = accent;
|
||||
}
|
||||
else
|
||||
{
|
||||
starterCards[i].style.borderTopColor = StyleKeyword.Null;
|
||||
starterCards[i].style.borderRightColor = StyleKeyword.Null;
|
||||
starterCards[i].style.borderBottomColor = StyleKeyword.Null;
|
||||
starterCards[i].style.borderLeftColor = StyleKeyword.Null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleStarterKeyDown(KeyDownEvent evt)
|
||||
{
|
||||
if (evt.keyCode == KeyCode.LeftArrow)
|
||||
{
|
||||
SelectStarter(selectedStarterIndex - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (evt.keyCode == KeyCode.RightArrow)
|
||||
{
|
||||
SelectStarter(selectedStarterIndex + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int digitIndex = GetDigitCommandIndex(evt.keyCode);
|
||||
if (digitIndex >= 0 && digitIndex < 3)
|
||||
{
|
||||
SelectStarter(digitIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (evt.keyCode == KeyCode.Return || evt.keyCode == KeyCode.KeypadEnter)
|
||||
{
|
||||
if (starterConfirming)
|
||||
{
|
||||
ConfirmStarterSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
ConfirmStarterSelectionIntent();
|
||||
return;
|
||||
}
|
||||
|
||||
if (evt.keyCode == KeyCode.Y && starterConfirming)
|
||||
{
|
||||
ConfirmStarterSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
if (evt.keyCode == KeyCode.Escape || evt.keyCode == KeyCode.N)
|
||||
{
|
||||
starterConfirming = false;
|
||||
starterConfirmLabel.AddToClassList(HiddenClassName);
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfirmStarterSelectionIntent()
|
||||
{
|
||||
IReadOnlyList<DistroDefinition> starters = GetStarterDistros();
|
||||
if (selectedStarterIndex >= starters.Count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
starterConfirming = true;
|
||||
starterConfirmLabel.text = $"install {DisplayName(starters[selectedStarterIndex])}? this cannot be undone [Y/n]";
|
||||
starterConfirmLabel.RemoveFromClassList(HiddenClassName);
|
||||
}
|
||||
|
||||
private void ConfirmStarterSelection()
|
||||
{
|
||||
IReadOnlyList<DistroDefinition> starters = GetStarterDistros();
|
||||
if (selectedStarterIndex >= starters.Count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DistroDefinition picked = starters[selectedStarterIndex];
|
||||
playerCollection.Add(picked);
|
||||
for (int i = 0; i < starters.Count; i++)
|
||||
{
|
||||
if (i != selectedStarterIndex)
|
||||
{
|
||||
gachaService.AddToBannerPool(starters[i]);
|
||||
}
|
||||
}
|
||||
|
||||
saveData.starterChosen = true;
|
||||
SaveCurrentState();
|
||||
featuredUnitPanel.Refresh(playerCollection.OwnedUnits);
|
||||
RefreshCollection();
|
||||
|
||||
starterConfirmLabel.text = $"installing {picked.Id} (1/1)... done";
|
||||
starterConfirmLabel.RemoveFromClassList(HiddenClassName);
|
||||
if (UIPreferences.ReducedMotion)
|
||||
{
|
||||
CloseStarterModal();
|
||||
return;
|
||||
}
|
||||
|
||||
starterCloseSchedule?.Pause();
|
||||
starterCloseSchedule = root.schedule.Execute(CloseStarterModal).StartingIn(600);
|
||||
}
|
||||
|
||||
private void CloseStarterModal()
|
||||
{
|
||||
starterCloseSchedule?.Pause();
|
||||
starterCloseSchedule = null;
|
||||
starterModalActive = false;
|
||||
starterConfirming = false;
|
||||
starterModal.AddToClassList(HiddenClassName);
|
||||
root.Focus();
|
||||
}
|
||||
|
||||
private static int GetDigitCommandIndex(KeyCode keyCode)
|
||||
{
|
||||
return keyCode switch
|
||||
@@ -402,6 +833,7 @@ namespace KernelPanic.UI
|
||||
|
||||
private void ShowCollection()
|
||||
{
|
||||
RefreshCollection();
|
||||
ShowPanel(collectionPanel);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user