Add Packages inventory screen and auto-scrap duplicate package pulls

Packages are unique-per-type; duplicate pulls now auto-scrap into Cache
(mirroring the distro dupe->Merges path) instead of a dead-end merges
pool, and the pull reveal shows "-> N cache" for the dupe card. Save
loading collapses any pre-existing duplicate package entries to the
highest-level instance and grants Cache for the extras, logging the
migration.

Also adds a dedicated Packages tab in the Collection screen (master
list with slot filter/sort + affordability hints, detail panel with
upgrade/scrap actions) so the whole inventory can be managed from one
place, sharing state with the existing per-distro Packages sub-tab.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MQmck8EqqWxSq5uVZ4E7yN
This commit is contained in:
Claude
2026-07-09 09:04:46 +00:00
parent d0722c6bf9
commit 8c08b94d2b
6 changed files with 454 additions and 183 deletions
+65 -15
View File
@@ -140,6 +140,7 @@ namespace KernelPanic.UI
private Button _rootCreditExchangeCloseButton;
private Button _collectionUnitsButton;
private Button _collectionLanguagesButton;
private Button _collectionPackagesButton;
private readonly ScreenFrameController _runSetupFrame = new();
private readonly ScreenFrameController _collectionFrame = new();
private readonly ScreenFrameController _gachaFrame = new();
@@ -156,7 +157,7 @@ namespace KernelPanic.UI
private int _selectedCommandIndex;
private int _selectedPackageIndex;
private string _runSetupNotice;
private bool _collectionShowingLanguages;
private CollectionTopTab _collectionTab;
private float _bootIntroElapsed;
private int _bootIntroCharacterCount;
private bool _cursorVisible;
@@ -293,6 +294,7 @@ namespace KernelPanic.UI
_rootCreditExchangeCloseButton = _root.Q<Button>("RootCreditExchangeCloseButton");
_collectionUnitsButton = _root.Q<Button>("CollectionUnitsButton");
_collectionLanguagesButton = _root.Q<Button>("CollectionLanguagesButton");
_collectionPackagesButton = _root.Q<Button>("CollectionPackagesButton");
}
private void LoadSharedStyles()
@@ -415,6 +417,7 @@ namespace KernelPanic.UI
collectionScreen.ViewChanged += SyncCollectionFrame;
_collectionUnitsButton.RegisterCallback<ClickEvent>(HandleCollectionUnitsTabClicked);
_collectionLanguagesButton.RegisterCallback<ClickEvent>(HandleCollectionLanguagesTabClicked);
_collectionPackagesButton.RegisterCallback<ClickEvent>(HandleCollectionPackagesTabClicked);
}
private void UnregisterCallbacks()
@@ -484,6 +487,7 @@ namespace KernelPanic.UI
collectionScreen.ViewChanged -= SyncCollectionFrame;
_collectionUnitsButton.UnregisterCallback<ClickEvent>(HandleCollectionUnitsTabClicked);
_collectionLanguagesButton.UnregisterCallback<ClickEvent>(HandleCollectionLanguagesTabClicked);
_collectionPackagesButton.UnregisterCallback<ClickEvent>(HandleCollectionPackagesTabClicked);
}
private void HandleRootCreditsToEntropyClicked()
@@ -652,9 +656,18 @@ namespace KernelPanic.UI
evt.StopPropagation();
}
private void HandleCollectionPackagesTabClicked(ClickEvent evt)
{
ShowCollectionPackages();
evt.StopPropagation();
}
private void LoadMetaState()
{
_saveData = _saveService.Load();
// Snapshot before the next EnsureLists() call below, which re-runs NormalizeOwnedPackages
// and would otherwise clear this (no new duplicates survive a second normalization pass).
List<CollapsedPackageDuplicate> collapsedPackageDuplicates = new(_saveData.collapsedPackageDuplicates);
_saveData.EnsureLists();
_wallet.SetBalance(_saveData.entropyBalance);
@@ -678,6 +691,7 @@ namespace KernelPanic.UI
}
}
MigrateCollapsedPackageDuplicatesToCache(collapsedPackageDuplicates);
LoadPackageLoadoutsFromSave();
for (int i = 0; i < _saveData.bannerPoolIds.Count; i++)
@@ -704,6 +718,18 @@ namespace KernelPanic.UI
SaveCurrentState();
}
private void MigrateCollapsedPackageDuplicatesToCache(List<CollapsedPackageDuplicate> collapsedPackageDuplicates)
{
foreach (CollapsedPackageDuplicate duplicate in collapsedPackageDuplicates)
{
PackageDefinition package = packageDatabase == null ? null : packageDatabase.FindById(duplicate.Id);
int rarity = package == null ? 1 : package.Rarity;
int cache = PackageTuning.GetCacheForRarity(rarity) * duplicate.ExtraCount;
_saveData.cacheBalance += cache;
Debug.Log($"Migrated {duplicate.ExtraCount} duplicate save copies of package '{duplicate.Id}' into +{cache} Cache (packages are unique-per-type; kept the highest-level instance).");
}
}
private void AddAllFourStarDistrosToBeginnerBannerPool()
{
if (distroDatabase == null)
@@ -769,9 +795,8 @@ namespace KernelPanic.UI
return;
}
_collectionShowingLanguages = false;
_collectionUnitsButton.EnableInClassList(SelectedClassName, true);
_collectionLanguagesButton.EnableInClassList(SelectedClassName, false);
_collectionTab = CollectionTopTab.Units;
ApplyCollectionTabButtons();
collectionScreen.RefreshUnits(_playerCollection.OwnedUnits);
collectionScreen.ShowUnitDetail(unit, false);
SyncCollectionFrame();
@@ -1917,31 +1942,49 @@ namespace KernelPanic.UI
private void ShowCollectionUnits()
{
_collectionShowingLanguages = false;
_collectionUnitsButton.EnableInClassList(SelectedClassName, true);
_collectionLanguagesButton.EnableInClassList(SelectedClassName, false);
_collectionTab = CollectionTopTab.Units;
ApplyCollectionTabButtons();
collectionScreen.RefreshUnits(_playerCollection.OwnedUnits);
SyncCollectionFrame();
}
private void ShowCollectionLanguages()
{
_collectionShowingLanguages = true;
_collectionUnitsButton.EnableInClassList(SelectedClassName, false);
_collectionLanguagesButton.EnableInClassList(SelectedClassName, true);
_collectionTab = CollectionTopTab.Languages;
ApplyCollectionTabButtons();
collectionScreen.ShowLanguages();
SyncCollectionFrame();
}
private void ShowCollectionPackages()
{
_collectionTab = CollectionTopTab.Packages;
ApplyCollectionTabButtons();
collectionScreen.ShowPackages();
SyncCollectionFrame();
}
private void ApplyCollectionTabButtons()
{
_collectionUnitsButton.EnableInClassList(SelectedClassName, _collectionTab == CollectionTopTab.Units);
_collectionLanguagesButton.EnableInClassList(SelectedClassName, _collectionTab == CollectionTopTab.Languages);
_collectionPackagesButton.EnableInClassList(SelectedClassName, _collectionTab == CollectionTopTab.Packages);
}
private void ToggleCollectionTab()
{
if (_collectionShowingLanguages)
switch (_collectionTab)
{
ShowCollectionUnits();
return;
case CollectionTopTab.Units:
ShowCollectionLanguages();
return;
case CollectionTopTab.Languages:
ShowCollectionPackages();
return;
default:
ShowCollectionUnits();
return;
}
ShowCollectionLanguages();
}
private void SyncCollectionFrame()
@@ -2034,6 +2077,13 @@ namespace KernelPanic.UI
Settings
}
private enum CollectionTopTab
{
Units,
Languages,
Packages
}
private sealed class CommandMenuEntry
{
private readonly Label _cursor;