fix some stuff

This commit is contained in:
2026-07-06 23:28:21 +02:00
parent c98b0d378f
commit 8bd51e17b1
+355 -9
View File
@@ -42,6 +42,7 @@ namespace KernelPanic.UI
[SerializeField] private FeaturedUnitPanel featuredUnitPanel = new(); [SerializeField] private FeaturedUnitPanel featuredUnitPanel = new();
[SerializeField] private CollectionScreenController collectionScreen = new(); [SerializeField] private CollectionScreenController collectionScreen = new();
[SerializeField] private StarterSelectionController starterSelection = new(); [SerializeField] private StarterSelectionController starterSelection = new();
[SerializeField] private GachaScreenController gachaScreen = new();
private readonly List<CommandMenuEntry> commandEntries = new(); private readonly List<CommandMenuEntry> commandEntries = new();
private readonly List<VisualElement> packageRows = new(); private readonly List<VisualElement> packageRows = new();
@@ -69,6 +70,20 @@ namespace KernelPanic.UI
private Label bootIntroLogLabel; private Label bootIntroLogLabel;
private Label motdBodyLabel; private Label motdBodyLabel;
private VisualElement motdBlock; private VisualElement motdBlock;
private Button rootCreditsToEntropyButton;
private VisualElement rootCreditExchangeModal;
private Label rootCreditExchangeBalanceLabel;
private Label rootCreditExchangePreviewLabel;
private Label rootCreditExchangeMessageLabel;
private TextField rootCreditExchangeInput;
private Button rootCreditExchangeMinusHundredButton;
private Button rootCreditExchangeMinusOneButton;
private Button rootCreditExchangeNoneButton;
private Button rootCreditExchangePlusOneButton;
private Button rootCreditExchangePlusHundredButton;
private Button rootCreditExchangeMaxButton;
private Button rootCreditExchangeConfirmButton;
private Button rootCreditExchangeCancelButton;
private Button collectionUnitsButton; private Button collectionUnitsButton;
private Button collectionLanguagesButton; private Button collectionLanguagesButton;
private readonly ScreenFrameController runSetupFrame = new(); private readonly ScreenFrameController runSetupFrame = new();
@@ -92,7 +107,12 @@ namespace KernelPanic.UI
private bool cursorVisible; private bool cursorVisible;
private bool suppressNextClick; private bool suppressNextClick;
private bool warnedUnresolvedSaveId; private bool warnedUnresolvedSaveId;
private bool rootCreditExchangeOpen;
private string bootIntroCopy; private string bootIntroCopy;
private Action rootCreditExchangeMinusHundredClicked;
private Action rootCreditExchangeMinusOneClicked;
private Action rootCreditExchangePlusOneClicked;
private Action rootCreditExchangePlusHundredClicked;
private IVisualElementScheduledItem blinkSchedule; private IVisualElementScheduledItem blinkSchedule;
private IVisualElementScheduledItem bootIntroSchedule; private IVisualElementScheduledItem bootIntroSchedule;
@@ -117,9 +137,10 @@ namespace KernelPanic.UI
collectionScreen.Bind(root, monospaceFont, languageDeckDatabase, cardDatabase, playerCollection); collectionScreen.Bind(root, monospaceFont, languageDeckDatabase, cardDatabase, playerCollection);
BindScreenFrames(); BindScreenFrames();
starterSelection.Bind(root, distroDatabase, HandleStarterConfirmed); starterSelection.Bind(root, distroDatabase, HandleStarterConfirmed);
gachaScreen.Bind(root, distroDatabase, monospaceFont, gachaService, playerCollection, wallet, HandleMetaStateChanged, OpenRootCreditExchange);
LoadMetaState(); LoadMetaState();
playerCollection.Changed += HandleMetaStateChanged; playerCollection.Changed += HandleMetaStateChanged;
gachaService.BannerPoolChanged += HandleMetaStateChanged; gachaService.Changed += HandleMetaStateChanged;
ApplyOptionalFont(); ApplyOptionalFont();
} }
@@ -133,6 +154,7 @@ namespace KernelPanic.UI
RefreshEventBanner(); RefreshEventBanner();
featuredUnitPanel.Refresh(playerCollection.OwnedUnits, playerCollection.FeaturedUnit); featuredUnitPanel.Refresh(playerCollection.OwnedUnits, playerCollection.FeaturedUnit);
collectionScreen.RefreshUnits(playerCollection.OwnedUnits); collectionScreen.RefreshUnits(playerCollection.OwnedUnits);
gachaScreen.Refresh();
SelectCommand(0); SelectCommand(0);
ShowMainMenu(); ShowMainMenu();
StartAmbientSchedules(); StartAmbientSchedules();
@@ -173,6 +195,25 @@ namespace KernelPanic.UI
bootIntroLogLabel = root.Q<Label>("BootIntroLogLabel"); bootIntroLogLabel = root.Q<Label>("BootIntroLogLabel");
motdBodyLabel = root.Q<Label>("MotdBodyLabel"); motdBodyLabel = root.Q<Label>("MotdBodyLabel");
rootCreditsToEntropyButton = root.Q<Button>("RootCreditsToEntropyButton");
if (rootCreditsToEntropyButton != null)
{
rootCreditsToEntropyButton.focusable = false;
}
rootCreditExchangeModal = root.Q<VisualElement>("RootCreditExchangeModal");
rootCreditExchangeBalanceLabel = root.Q<Label>("RootCreditExchangeBalanceLabel");
rootCreditExchangePreviewLabel = root.Q<Label>("RootCreditExchangePreviewLabel");
rootCreditExchangeMessageLabel = root.Q<Label>("RootCreditExchangeMessageLabel");
rootCreditExchangeInput = root.Q<TextField>("RootCreditExchangeInput");
rootCreditExchangeMinusHundredButton = root.Q<Button>("RootCreditExchangeMinusHundredButton");
rootCreditExchangeMinusOneButton = root.Q<Button>("RootCreditExchangeMinusOneButton");
rootCreditExchangeNoneButton = root.Q<Button>("RootCreditExchangeNoneButton");
rootCreditExchangePlusOneButton = root.Q<Button>("RootCreditExchangePlusOneButton");
rootCreditExchangePlusHundredButton = root.Q<Button>("RootCreditExchangePlusHundredButton");
rootCreditExchangeMaxButton = root.Q<Button>("RootCreditExchangeMaxButton");
rootCreditExchangeConfirmButton = root.Q<Button>("RootCreditExchangeConfirmButton");
rootCreditExchangeCancelButton = root.Q<Button>("RootCreditExchangeCancelButton");
collectionUnitsButton = root.Q<Button>("CollectionUnitsButton"); collectionUnitsButton = root.Q<Button>("CollectionUnitsButton");
collectionLanguagesButton = root.Q<Button>("CollectionLanguagesButton"); collectionLanguagesButton = root.Q<Button>("CollectionLanguagesButton");
} }
@@ -181,7 +222,7 @@ namespace KernelPanic.UI
{ {
runSetupFrame.Bind(runSetupPanel, "$ ./start_run --configure", "[esc] back [arrows] navigate [enter] select [b] boot run", ShowMainMenu); runSetupFrame.Bind(runSetupPanel, "$ ./start_run --configure", "[esc] back [arrows] navigate [enter] select [b] boot run", ShowMainMenu);
collectionFrame.Bind(collectionPanel, "$ ls ~/collection", "[esc] back [left/right] tabs [tab] tabs [arrows] navigate [enter] select", HandleCollectionBack); collectionFrame.Bind(collectionPanel, "$ ls ~/collection", "[esc] back [left/right] tabs [tab] tabs [arrows] navigate [enter] select", HandleCollectionBack);
gachaFrame.Bind(gachaPanel, "$ curl gacha.sh | sh", "[esc] back", ShowMainMenu); gachaFrame.Bind(gachaPanel, "$ curl gacha.sh | sh", "[esc] back click banner click pull", ShowMainMenu);
settingsFrame.Bind(settingsPanel, "$ dpkg-reconfigure kernel-panic", "[esc] back", ShowMainMenu); settingsFrame.Bind(settingsPanel, "$ dpkg-reconfigure kernel-panic", "[esc] back", ShowMainMenu);
} }
@@ -209,6 +250,60 @@ namespace KernelPanic.UI
{ {
root.RegisterCallback<KeyDownEvent>(HandleKeyDown); root.RegisterCallback<KeyDownEvent>(HandleKeyDown);
root.RegisterCallback<PointerDownEvent>(HandlePointerDown); root.RegisterCallback<PointerDownEvent>(HandlePointerDown);
if (rootCreditsToEntropyButton != null)
{
rootCreditsToEntropyButton.clicked += HandleRootCreditsToEntropyClicked;
}
if (rootCreditExchangeInput != null)
{
rootCreditExchangeInput.RegisterValueChangedCallback(HandleRootCreditExchangeInputChanged);
}
if (rootCreditExchangeMinusHundredButton != null)
{
rootCreditExchangeMinusHundredClicked ??= () => AddRootCreditExchangeAmount(-100);
rootCreditExchangeMinusHundredButton.clicked += rootCreditExchangeMinusHundredClicked;
}
if (rootCreditExchangeMinusOneButton != null)
{
rootCreditExchangeMinusOneClicked ??= () => AddRootCreditExchangeAmount(-1);
rootCreditExchangeMinusOneButton.clicked += rootCreditExchangeMinusOneClicked;
}
if (rootCreditExchangeNoneButton != null)
{
rootCreditExchangeNoneButton.clicked += SetRootCreditExchangeNone;
}
if (rootCreditExchangePlusOneButton != null)
{
rootCreditExchangePlusOneClicked ??= () => AddRootCreditExchangeAmount(1);
rootCreditExchangePlusOneButton.clicked += rootCreditExchangePlusOneClicked;
}
if (rootCreditExchangePlusHundredButton != null)
{
rootCreditExchangePlusHundredClicked ??= () => AddRootCreditExchangeAmount(100);
rootCreditExchangePlusHundredButton.clicked += rootCreditExchangePlusHundredClicked;
}
if (rootCreditExchangeMaxButton != null)
{
rootCreditExchangeMaxButton.clicked += SetRootCreditExchangeMax;
}
if (rootCreditExchangeConfirmButton != null)
{
rootCreditExchangeConfirmButton.clicked += ConfirmRootCreditExchange;
}
if (rootCreditExchangeCancelButton != null)
{
rootCreditExchangeCancelButton.clicked += CloseRootCreditExchange;
}
collectionScreen.ViewChanged += SyncCollectionFrame; collectionScreen.ViewChanged += SyncCollectionFrame;
collectionUnitsButton.RegisterCallback<ClickEvent>(HandleCollectionUnitsTabClicked); collectionUnitsButton.RegisterCallback<ClickEvent>(HandleCollectionUnitsTabClicked);
collectionLanguagesButton.RegisterCallback<ClickEvent>(HandleCollectionLanguagesTabClicked); collectionLanguagesButton.RegisterCallback<ClickEvent>(HandleCollectionLanguagesTabClicked);
@@ -218,11 +313,195 @@ namespace KernelPanic.UI
{ {
root.UnregisterCallback<KeyDownEvent>(HandleKeyDown); root.UnregisterCallback<KeyDownEvent>(HandleKeyDown);
root.UnregisterCallback<PointerDownEvent>(HandlePointerDown); root.UnregisterCallback<PointerDownEvent>(HandlePointerDown);
if (rootCreditsToEntropyButton != null)
{
rootCreditsToEntropyButton.clicked -= HandleRootCreditsToEntropyClicked;
}
if (rootCreditExchangeInput != null)
{
rootCreditExchangeInput.UnregisterValueChangedCallback(HandleRootCreditExchangeInputChanged);
}
if (rootCreditExchangeMinusHundredButton != null)
{
rootCreditExchangeMinusHundredButton.clicked -= rootCreditExchangeMinusHundredClicked;
}
if (rootCreditExchangeMinusOneButton != null)
{
rootCreditExchangeMinusOneButton.clicked -= rootCreditExchangeMinusOneClicked;
}
if (rootCreditExchangeNoneButton != null)
{
rootCreditExchangeNoneButton.clicked -= SetRootCreditExchangeNone;
}
if (rootCreditExchangePlusOneButton != null)
{
rootCreditExchangePlusOneButton.clicked -= rootCreditExchangePlusOneClicked;
}
if (rootCreditExchangePlusHundredButton != null)
{
rootCreditExchangePlusHundredButton.clicked -= rootCreditExchangePlusHundredClicked;
}
if (rootCreditExchangeMaxButton != null)
{
rootCreditExchangeMaxButton.clicked -= SetRootCreditExchangeMax;
}
if (rootCreditExchangeConfirmButton != null)
{
rootCreditExchangeConfirmButton.clicked -= ConfirmRootCreditExchange;
}
if (rootCreditExchangeCancelButton != null)
{
rootCreditExchangeCancelButton.clicked -= CloseRootCreditExchange;
}
collectionScreen.ViewChanged -= SyncCollectionFrame; collectionScreen.ViewChanged -= SyncCollectionFrame;
collectionUnitsButton.UnregisterCallback<ClickEvent>(HandleCollectionUnitsTabClicked); collectionUnitsButton.UnregisterCallback<ClickEvent>(HandleCollectionUnitsTabClicked);
collectionLanguagesButton.UnregisterCallback<ClickEvent>(HandleCollectionLanguagesTabClicked); collectionLanguagesButton.UnregisterCallback<ClickEvent>(HandleCollectionLanguagesTabClicked);
} }
private void HandleRootCreditsToEntropyClicked()
{
if (gachaService == null || wallet == null)
{
return;
}
OpenRootCreditExchange();
}
private void OpenRootCreditExchange()
{
if (rootCreditExchangeModal == null || gachaService == null || wallet == null)
{
return;
}
rootCreditExchangeOpen = true;
rootCreditExchangeModal.RemoveFromClassList(HiddenClassName);
rootCreditExchangeMessageLabel?.AddToClassList(HiddenClassName);
SetRootCreditExchangeAmount(Math.Min(100, gachaService.RootCredits));
RefreshRootCreditExchange();
rootCreditExchangeInput?.Focus();
}
private void CloseRootCreditExchange()
{
rootCreditExchangeOpen = false;
rootCreditExchangeModal?.AddToClassList(HiddenClassName);
root.Focus();
}
private void HandleRootCreditExchangeInputChanged(ChangeEvent<string> evt)
{
RefreshRootCreditExchange();
}
private void AddRootCreditExchangeAmount(int amount)
{
SetRootCreditExchangeAmount(GetRootCreditExchangeAmount() + amount);
}
private void SetRootCreditExchangeMax()
{
SetRootCreditExchangeAmount(gachaService == null ? 0 : gachaService.RootCredits);
}
private void SetRootCreditExchangeNone()
{
SetRootCreditExchangeAmount(0);
}
private void SetRootCreditExchangeAmount(int amount)
{
int max = gachaService == null ? 0 : gachaService.RootCredits;
int clamped = Mathf.Clamp(amount, 0, max);
if (rootCreditExchangeInput != null)
{
rootCreditExchangeInput.value = clamped.ToString();
}
RefreshRootCreditExchange();
}
private int GetRootCreditExchangeAmount()
{
if (rootCreditExchangeInput == null || string.IsNullOrWhiteSpace(rootCreditExchangeInput.value))
{
return 0;
}
if (!int.TryParse(rootCreditExchangeInput.value, out int amount))
{
return 0;
}
int max = gachaService == null ? 0 : gachaService.RootCredits;
return Mathf.Clamp(amount, 0, max);
}
private void RefreshRootCreditExchange()
{
if (gachaService == null)
{
return;
}
int amount = GetRootCreditExchangeAmount();
if (rootCreditExchangeInput != null && rootCreditExchangeInput.value != amount.ToString())
{
rootCreditExchangeInput.SetValueWithoutNotify(amount.ToString());
}
if (rootCreditExchangeBalanceLabel != null)
{
rootCreditExchangeBalanceLabel.text = gachaService.RootCredits.ToString();
}
if (rootCreditExchangePreviewLabel != null)
{
rootCreditExchangePreviewLabel.text = $"+{amount} entropy";
}
int max = gachaService.RootCredits;
rootCreditExchangeMinusHundredButton?.SetEnabled(amount > 0);
rootCreditExchangeMinusOneButton?.SetEnabled(amount > 0);
rootCreditExchangeNoneButton?.SetEnabled(amount > 0);
rootCreditExchangePlusOneButton?.SetEnabled(amount < max);
rootCreditExchangePlusHundredButton?.SetEnabled(amount < max);
rootCreditExchangeMaxButton?.SetEnabled(max > 0 && amount < max);
rootCreditExchangeConfirmButton?.SetEnabled(amount > 0);
}
private void ConfirmRootCreditExchange()
{
int amount = GetRootCreditExchangeAmount();
if (!gachaService.ConvertRootCreditsToEntropy(wallet, amount, out string failureReason))
{
if (rootCreditExchangeMessageLabel != null)
{
rootCreditExchangeMessageLabel.text = $"exchange failed: {failureReason}";
rootCreditExchangeMessageLabel.RemoveFromClassList(HiddenClassName);
}
RefreshRootCreditExchange();
return;
}
SaveCurrentState();
RefreshCurrencyReadouts();
gachaScreen.Refresh();
CloseRootCreditExchange();
}
private void HandleCollectionUnitsTabClicked(ClickEvent evt) private void HandleCollectionUnitsTabClicked(ClickEvent evt)
{ {
ShowCollectionUnits(); ShowCollectionUnits();
@@ -239,6 +518,7 @@ namespace KernelPanic.UI
{ {
saveData = saveService.Load(); saveData = saveService.Load();
saveData.EnsureLists(); saveData.EnsureLists();
wallet.SetBalance(saveData.entropyBalance);
for (int i = 0; i < saveData.ownedUnitIds.Count; i++) for (int i = 0; i < saveData.ownedUnitIds.Count; i++)
{ {
@@ -258,9 +538,35 @@ namespace KernelPanic.UI
} }
} }
gachaService.LoadProgress(saveData);
if (saveData.starterChosen)
{
AddAllFourStarDistrosToBeginnerBannerPool();
}
if (gachaService.BeginnerState.guaranteedDistroIds.Count == 0 && saveData.bannerPoolIds.Count > 0)
{
gachaService.BeginnerState.guaranteedDistroIds.AddRange(saveData.bannerPoolIds);
}
SaveCurrentState(); SaveCurrentState();
} }
private void AddAllFourStarDistrosToBeginnerBannerPool()
{
if (distroDatabase == null)
{
return;
}
IReadOnlyList<DistroDefinition> distros = distroDatabase.AllDistros;
for (int i = 0; i < distros.Count; i++)
{
gachaService.AddToBannerPool(distros[i]);
}
}
private DistroDefinition ResolveSavedDistro(string id) private DistroDefinition ResolveSavedDistro(string id)
{ {
DistroDefinition unit = distroDatabase == null ? null : distroDatabase.FindById(id); DistroDefinition unit = distroDatabase == null ? null : distroDatabase.FindById(id);
@@ -278,28 +584,30 @@ namespace KernelPanic.UI
SaveCurrentState(); SaveCurrentState();
featuredUnitPanel.Refresh(playerCollection.OwnedUnits, playerCollection.FeaturedUnit); featuredUnitPanel.Refresh(playerCollection.OwnedUnits, playerCollection.FeaturedUnit);
collectionScreen.RefreshUnits(playerCollection.OwnedUnits); collectionScreen.RefreshUnits(playerCollection.OwnedUnits);
gachaScreen.Refresh();
RefreshCurrencyReadouts();
} }
private void HandleStarterConfirmed(DistroDefinition picked, IReadOnlyList<DistroDefinition> remaining) private void HandleStarterConfirmed(DistroDefinition picked, IReadOnlyList<DistroDefinition> remaining)
{ {
playerCollection.Add(picked); playerCollection.Add(picked);
for (int i = 0; i < remaining.Count; i++) AddAllFourStarDistrosToBeginnerBannerPool();
{ gachaService.SetBeginnerGuaranteedDistros(remaining);
gachaService.AddToBannerPool(remaining[i]);
}
saveData.starterChosen = true; saveData.starterChosen = true;
SaveCurrentState(); SaveCurrentState();
featuredUnitPanel.Refresh(playerCollection.OwnedUnits, playerCollection.FeaturedUnit); featuredUnitPanel.Refresh(playerCollection.OwnedUnits, playerCollection.FeaturedUnit);
collectionScreen.RefreshUnits(playerCollection.OwnedUnits); collectionScreen.RefreshUnits(playerCollection.OwnedUnits);
gachaScreen.Refresh();
} }
private void SaveCurrentState() private void SaveCurrentState()
{ {
saveData ??= SaveData.CreateDefault(); saveData ??= SaveData.CreateDefault();
saveData.EnsureLists(); saveData.EnsureLists();
saveData.entropyBalance = wallet == null ? 0 : wallet.Balance;
saveData.ownedUnitIds.Clear(); saveData.ownedUnitIds.Clear();
saveData.bannerPoolIds.Clear(); saveData.bannerPoolIds.Clear();
gachaService.WriteProgress(saveData);
for (int i = 0; i < playerCollection.OwnedUnits.Count; i++) for (int i = 0; i < playerCollection.OwnedUnits.Count; i++)
{ {
@@ -363,7 +671,12 @@ namespace KernelPanic.UI
} }
entropyLabel.text = $"entropy={wallet.Balance}"; entropyLabel.text = $"entropy={wallet.Balance}";
pullTokensLabel.text = $"pulls={gachaService.PullTokens}"; rootCreditsToEntropyButton?.SetEnabled(true);
bool showPullTokens = IsGachaVisible();
pullTokensLabel.text = showPullTokens
? $"stable-pull-token={gachaService.PullTokens} feature-pull-token={gachaService.LimitedPullTokens}"
: string.Empty;
pullTokensLabel.EnableInClassList(HiddenClassName, !showPullTokens);
} }
private void RefreshEventBanner() private void RefreshEventBanner()
@@ -473,6 +786,25 @@ namespace KernelPanic.UI
return; return;
} }
if (rootCreditExchangeOpen)
{
if (evt.keyCode == KeyCode.Escape)
{
CloseRootCreditExchange();
evt.StopPropagation();
return;
}
if (evt.keyCode == KeyCode.Return || evt.keyCode == KeyCode.KeypadEnter)
{
ConfirmRootCreditExchange();
evt.StopPropagation();
return;
}
return;
}
if (evt.keyCode == KeyCode.Escape && IsCollectionVisible() && collectionScreen.BackFromSubview()) if (evt.keyCode == KeyCode.Escape && IsCollectionVisible() && collectionScreen.BackFromSubview())
{ {
SyncCollectionFrame(); SyncCollectionFrame();
@@ -544,6 +876,12 @@ namespace KernelPanic.UI
} }
} }
if (IsGachaVisible())
{
gachaScreen.HandleKeyDown(evt);
return;
}
if (evt.keyCode == KeyCode.UpArrow) if (evt.keyCode == KeyCode.UpArrow)
{ {
SelectCommand(selectedCommandIndex - 1); SelectCommand(selectedCommandIndex - 1);
@@ -592,10 +930,15 @@ namespace KernelPanic.UI
return collectionPanel != null && !collectionPanel.ClassListContains(HiddenClassName); return collectionPanel != null && !collectionPanel.ClassListContains(HiddenClassName);
} }
private bool IsGachaVisible()
{
return gachaPanel != null && !gachaPanel.ClassListContains(HiddenClassName);
}
private bool IsSubScreenVisible() private bool IsSubScreenVisible()
{ {
return IsRunSetupVisible() || IsCollectionVisible() || return IsRunSetupVisible() || IsCollectionVisible() ||
(gachaPanel != null && !gachaPanel.ClassListContains(HiddenClassName)) || IsGachaVisible() ||
(settingsPanel != null && !settingsPanel.ClassListContains(HiddenClassName)); (settingsPanel != null && !settingsPanel.ClassListContains(HiddenClassName));
} }
@@ -1187,6 +1530,7 @@ namespace KernelPanic.UI
private void ShowGacha() private void ShowGacha()
{ {
gachaScreen.Open();
ShowPanel(gachaPanel); ShowPanel(gachaPanel);
} }
@@ -1215,6 +1559,8 @@ namespace KernelPanic.UI
{ {
eventBanner.AddToClassList(HiddenClassName); eventBanner.AddToClassList(HiddenClassName);
} }
RefreshCurrencyReadouts();
} }
private sealed class CommandMenuEntry private sealed class CommandMenuEntry