Make multiple UX improvements in distro -> packages

This commit is contained in:
2026-07-09 15:30:48 +02:00
parent afea2eaed4
commit 95cebf8df0
4 changed files with 758 additions and 97 deletions
+99 -9
View File
@@ -191,35 +191,50 @@ namespace KernelPanic.Run
return Mathf.Max(1, (CurrentConfig?.Distro?.BaseCyclesPerTurn ?? 1) + maxCyclesBonus + packageMaxCyclesBonus);
}
private void ApplyKernelPackageBonuses(RunConfig config)
public static PackageStatImpact CalculatePackageStatImpact(DistroDefinition distro, IReadOnlyList<PackageInstance> packages)
{
if (config?.EquippedPackages == null)
PackageStatImpact impact = new(
Mathf.Max(1, distro == null ? 1 : distro.BaseUptime),
Mathf.Max(1, distro == null ? 1 : distro.BaseRam),
Mathf.Max(1, distro == null ? 1 : distro.BaseCyclesPerTurn));
if (packages == null)
{
return;
return impact;
}
for (int i = 0; i < config.EquippedPackages.Count; i++)
for (int i = 0; i < packages.Count; i++)
{
PackageInstance package = config.EquippedPackages[i];
PackageInstance package = packages[i];
if (package?.Definition == null || package.Definition.Slot != PackageSlot.Kernel)
{
continue;
}
PackageEffectData effect = package.EffectFor(config.Distro?.Id);
PackageEffectData effect = package.EffectFor(distro?.Id);
int amount = Mathf.Max(0, effect.Amount);
switch (effect.Kind)
{
case PackageEffectKind.MaxUptime:
packageMaxUptimeBonus += Mathf.Max(0, effect.Amount);
impact.AddUptime(amount, package.Definition);
break;
case PackageEffectKind.MaxCycles:
packageMaxCyclesBonus += Mathf.Max(0, effect.Amount);
impact.AddCycles(amount, package.Definition);
break;
case PackageEffectKind.MaxRam:
packageRamBonus += Mathf.Max(0, effect.Amount);
impact.AddRam(amount, package.Definition);
break;
}
}
return impact;
}
private void ApplyKernelPackageBonuses(RunConfig config)
{
PackageStatImpact impact = CalculatePackageStatImpact(config?.Distro, config?.EquippedPackages);
packageMaxUptimeBonus = impact.UptimeBonus;
packageMaxCyclesBonus = impact.CyclesBonus;
packageRamBonus = impact.RamBonus;
}
public bool TrySettleRunRewards(out int bandwidth, out int entropy)
@@ -507,4 +522,79 @@ namespace KernelPanic.Run
}
}
}
public struct PackageStatImpact
{
private readonly List<string> uptimeSources;
private readonly List<string> ramSources;
private readonly List<string> cyclesSources;
public PackageStatImpact(int baseUptime, int baseRam, int baseCycles)
{
BaseUptime = baseUptime;
BaseRam = baseRam;
BaseCycles = baseCycles;
UptimeBonus = 0;
RamBonus = 0;
CyclesBonus = 0;
uptimeSources = new List<string>();
ramSources = new List<string>();
cyclesSources = new List<string>();
}
public int BaseUptime { get; }
public int BaseRam { get; }
public int BaseCycles { get; }
public int UptimeBonus { get; private set; }
public int RamBonus { get; private set; }
public int CyclesBonus { get; private set; }
public int FinalUptime => Mathf.Max(1, BaseUptime + UptimeBonus);
public int FinalRam => Mathf.Max(1, BaseRam + RamBonus);
public int FinalCycles => Mathf.Max(1, BaseCycles + CyclesBonus);
public IReadOnlyList<string> UptimeSources => uptimeSources;
public IReadOnlyList<string> RamSources => ramSources;
public IReadOnlyList<string> CyclesSources => cyclesSources;
public void AddUptime(int amount, PackageDefinition source)
{
if (amount <= 0)
{
return;
}
UptimeBonus += amount;
AddSource(source, uptimeSources);
}
public void AddRam(int amount, PackageDefinition source)
{
if (amount <= 0)
{
return;
}
RamBonus += amount;
AddSource(source, ramSources);
}
public void AddCycles(int amount, PackageDefinition source)
{
if (amount <= 0)
{
return;
}
CyclesBonus += amount;
AddSource(source, cyclesSources);
}
private static void AddSource(PackageDefinition source, List<string> sources)
{
string name = source == null ? null : string.IsNullOrWhiteSpace(source.DisplayName) ? source.Id : source.DisplayName;
if (!string.IsNullOrWhiteSpace(name))
{
sources.Add(name);
}
}
}
}
+395 -36
View File
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using KernelPanic.Core;
using KernelPanic.Data;
using KernelPanic.Meta;
using KernelPanic.Run;
using UnityEngine;
using UnityEngine.TextCore.Text;
using UnityEngine.UIElements;
@@ -20,6 +21,9 @@ namespace KernelPanic.UI
private VisualElement _list;
private ScrollView _detailScroll;
private VisualElement _detail;
private VisualElement _root;
private VisualElement _packageEquipOverlay;
private VisualElement _packageEquipDialog;
private FontAsset _monospaceFont;
private LanguageDeckDatabase _languageDeckDatabase;
private CardDatabase _cardDatabase;
@@ -40,11 +44,14 @@ namespace KernelPanic.UI
private UnitDetailTab _unitDetailTab = UnitDetailTab.Overview;
private PackageSlot? _packagePickerSlot;
private bool _unitDetailReturnsToOverview = true;
private DistroDefinition _equipWindowUnit;
private PackageSlot _equipWindowSlot;
private int _selectedUnitIndex;
private int _selectedLanguageIndex;
private int _selectedCardIndex;
private string _emptyCardMessage = "no cards installed";
private string _pendingScrapPackageId;
private string _packageSlotNotice;
private IReadOnlyList<OwnedPackageInstance> _packageInventory = Array.Empty<OwnedPackageInstance>();
private int _selectedPackageInventoryIndex;
private PackageSlot? _packageInventoryFilter;
@@ -54,6 +61,7 @@ namespace KernelPanic.UI
public void Bind(VisualElement root, FontAsset artFont, LanguageDeckDatabase deckDatabase, CardDatabase cardsDatabase, PlayerCollection collection, Func<DistroDefinition, int> getMergesBalance = null, Func<DistroDefinition, VersionUpgradeResult> tryUpgradeUnit = null, PackageDatabase packageDatabase = null, PackageLoadout packageLoadout = null, Action packageLoadoutChanged = null, Func<SaveData> getSaveData = null, Func<OwnedPackageInstance, PackageUpgradeResult> tryUpgradePackage = null, Func<OwnedPackageInstance, PackageScrapResult> tryScrapPackage = null)
{
_monospaceFont = artFont;
_root = root;
_languageDeckDatabase = deckDatabase;
_cardDatabase = cardsDatabase;
_playerCollection = collection;
@@ -68,6 +76,7 @@ namespace KernelPanic.UI
_list = root.Q<VisualElement>("CollectionList");
_detailScroll = root.Q<ScrollView>("CollectionDetail");
_detail = _detailScroll?.contentContainer;
EnsurePackageEquipOverlay();
}
public bool IsCardSubview => _mode == CollectionMode.CardSubview;
@@ -85,6 +94,7 @@ namespace KernelPanic.UI
public void ShowUnits()
{
ClosePackageEquipWindow();
_mode = CollectionMode.Units;
CurrentTitle = "$ ls ~/collection";
CurrentHint = "[esc] back [left/right] tabs [tab] tabs [arrows] navigate [enter] select";
@@ -94,6 +104,7 @@ namespace KernelPanic.UI
public void ShowLanguages()
{
ClosePackageEquipWindow();
_mode = CollectionMode.Languages;
CurrentTitle = "$ ls ~/collection/languages";
CurrentHint = "[esc] back [left/right] tabs [tab] tabs [arrows] navigate [enter] select";
@@ -103,6 +114,7 @@ namespace KernelPanic.UI
public void ShowPackages()
{
ClosePackageEquipWindow();
_mode = CollectionMode.Packages;
_pendingScrapPackageId = null;
CurrentTitle = "$ ls ~/packages";
@@ -113,9 +125,16 @@ namespace KernelPanic.UI
public bool BackFromSubview()
{
if (_packageEquipOverlay != null && !_packageEquipOverlay.ClassListContains("hidden"))
{
ClosePackageEquipWindow();
return true;
}
if (_mode == CollectionMode.UnitDetail)
{
_packagePickerSlot = null;
_packageSlotNotice = null;
if (_unitDetailReturnsToOverview)
{
ShowUnits();
@@ -201,8 +220,7 @@ namespace KernelPanic.UI
break;
case UnitDetailTab.Packages:
_packagePickerSlot ??= PackageSlot.Kernel;
RenderUnitDetail(unit);
ViewChanged?.Invoke();
OpenPackageEquipWindow(unit, _packagePickerSlot.Value);
break;
case UnitDetailTab.Cards:
OpenUnitCards(unit);
@@ -221,6 +239,7 @@ namespace KernelPanic.UI
int count = Enum.GetValues(typeof(UnitDetailTab)).Length;
_unitDetailTab = (UnitDetailTab)(((int)_unitDetailTab + delta + count) % count);
_packagePickerSlot = null;
_packageSlotNotice = null;
RenderUnitDetail(_units[_selectedUnitIndex]);
ViewChanged?.Invoke();
}
@@ -778,6 +797,7 @@ namespace KernelPanic.UI
{
_unitDetailTab = UnitDetailTab.Overview;
_packagePickerSlot = null;
_packageSlotNotice = null;
}
_unitDetailReturnsToOverview = returnToOverview;
@@ -872,6 +892,7 @@ namespace KernelPanic.UI
{
_unitDetailTab = tab;
_packagePickerSlot = null;
_packageSlotNotice = null;
RenderUnitDetail(unit);
ViewChanged?.Invoke();
})
@@ -944,27 +965,22 @@ namespace KernelPanic.UI
Label title = new("packages");
title.AddToClassList("detail-section-title");
target.Add(title);
SaveData saveData = _getSaveData?.Invoke();
int cache = Math.Max(0, saveData?.cacheBalance ?? 0);
int bandwidth = Math.Max(0, saveData?.bandwidthBalance ?? 0);
_packagePickerSlot ??= PackageSlot.Kernel;
VisualElement layout = new();
layout.AddToClassList("packages-master-detail");
layout.AddToClassList("packages-summary-layout");
VisualElement slots = new();
slots.AddToClassList("packages-slot-list");
slots.Add(BuildDetailLine("wallet", $"Cache={cache} Bandwidth={bandwidth}"));
AddPackageSlotSummaryRow(slots, unit, PackageSlot.Kernel);
AddPackageSlotSummaryRow(slots, unit, PackageSlot.Runtime);
AddPackageSlotSummaryRow(slots, unit, PackageSlot.Daemon);
layout.Add(slots);
VisualElement detail = new();
detail.AddToClassList("packages-detail-panel");
AddSelectedPackageSlotActions(detail, unit, _packagePickerSlot.Value, cache, bandwidth);
AddPackagePicker(detail, unit, _packagePickerSlot.Value);
layout.Add(detail);
VisualElement impact = new();
impact.AddToClassList("packages-impact-panel");
AddPackageStatImpactPanel(impact, unit);
layout.Add(impact);
target.Add(layout);
}
@@ -978,9 +994,20 @@ namespace KernelPanic.UI
row.RegisterCallback<ClickEvent>(_ =>
{
_packagePickerSlot = slot;
RenderUnitDetail(unit);
ViewChanged?.Invoke();
_packageSlotNotice = null;
OpenPackageEquipWindow(unit, slot);
});
row.RegisterCallback<KeyDownEvent>(evt =>
{
if (evt.keyCode == KeyCode.Return || evt.keyCode == KeyCode.KeypadEnter)
{
_packagePickerSlot = slot;
_packageSlotNotice = null;
OpenPackageEquipWindow(unit, slot);
evt.StopPropagation();
}
});
row.focusable = true;
Label slotLabel = new(slot.ToString().ToUpperInvariant());
slotLabel.AddToClassList("package-slot-label");
@@ -1011,9 +1038,100 @@ namespace KernelPanic.UI
Label effect = new(equipped == null ? string.Empty : OneLineEffect(equipped, owned?.UpgradeLevel ?? 0, unit.Id));
effect.AddToClassList("package-slot-effect");
row.Add(effect);
Label action = new("[enter] equip");
action.AddToClassList("package-slot-action");
row.Add(action);
target.Add(row);
}
private void AddPackageStatImpactPanel(VisualElement target, DistroDefinition unit)
{
Label title = new("stat impact");
title.AddToClassList("detail-section-title");
target.Add(title);
IReadOnlyList<PackageInstance> packages = _packageLoadout.GetEquippedPackageInstances(unit.Id);
PackageStatImpact impact = RunManager.CalculatePackageStatImpact(unit, packages);
target.Add(BuildStatImpactLine("Uptime", impact.BaseUptime, impact.FinalUptime, impact.UptimeBonus, impact.UptimeSources));
target.Add(BuildStatImpactLine("RAM", impact.BaseRam, impact.FinalRam, impact.RamBonus, impact.RamSources));
target.Add(BuildStatImpactLine("Cycles", impact.BaseCycles, impact.FinalCycles, impact.CyclesBonus, impact.CyclesSources));
VisualElement active = new();
active.AddToClassList("packages-active-effects");
Label activeTitle = new("active effects");
activeTitle.AddToClassList("detail-section-title");
active.Add(activeTitle);
int count = 0;
AddActivePackageEffect(active, unit, PackageSlot.Kernel, ref count);
AddActivePackageEffect(active, unit, PackageSlot.Runtime, ref count);
AddActivePackageEffect(active, unit, PackageSlot.Daemon, ref count);
if (count == 0)
{
Label empty = new("none");
empty.AddToClassList("collection-detail-muted");
active.Add(empty);
}
target.Add(active);
}
private VisualElement BuildStatImpactLine(string label, int before, int after, int delta, IReadOnlyList<string> sources)
{
VisualElement row = new();
row.AddToClassList("package-stat-line");
Label key = new(label);
key.AddToClassList("package-stat-key");
Label values = new($"{before} -> {after}");
values.AddToClassList("package-stat-value");
Label bonus = new(delta > 0 ? $"(+{delta}{FormatSources(sources)})" : string.Empty);
bonus.AddToClassList("package-stat-bonus");
row.Add(key);
row.Add(values);
row.Add(bonus);
return row;
}
private void AddActivePackageEffect(VisualElement target, DistroDefinition unit, PackageSlot slot, ref int count)
{
PackageDefinition package = _packageLoadout.GetEquippedPackage(unit.Id, slot);
OwnedPackageInstance owned = package == null ? null : _playerCollection.GetOwnedPackage(package.Id);
if (package == null || owned == null)
{
return;
}
PackageEffectData effect = package.EffectFor(unit.Id);
if (package.Slot == PackageSlot.Kernel && IsStatPackageEffect(effect.Kind))
{
return;
}
string summary = OneLineEffect(package, owned.UpgradeLevel, unit.Id);
if (string.IsNullOrWhiteSpace(summary))
{
return;
}
Label line = new($"{PackageName(package)}: {summary}");
line.AddToClassList("package-active-effect");
target.Add(line);
count++;
}
private static bool IsStatPackageEffect(PackageEffectKind kind)
{
return kind == PackageEffectKind.MaxUptime
|| kind == PackageEffectKind.MaxRam
|| kind == PackageEffectKind.MaxCycles;
}
private static string FormatSources(IReadOnlyList<string> sources)
{
return sources == null || sources.Count == 0 ? string.Empty : $", {string.Join(", ", sources)}";
}
private void AddSelectedPackageSlotActions(VisualElement target, DistroDefinition unit, PackageSlot slot, int cache, int bandwidth)
{
PackageDefinition equipped = _packageLoadout.GetEquippedPackage(unit.Id, slot);
@@ -1021,6 +1139,13 @@ namespace KernelPanic.UI
Label title = new($"{slot.ToString().ToLowerInvariant()} actions");
title.AddToClassList("detail-section-title");
target.Add(title);
if (!string.IsNullOrWhiteSpace(_packageSlotNotice))
{
Label notice = new(_packageSlotNotice);
notice.AddToClassList("package-notice");
notice.AddToClassList("package-slot-notice");
target.Add(notice);
}
if (owned == null || equipped == null)
{
@@ -1030,7 +1155,6 @@ namespace KernelPanic.UI
}
target.Add(BuildDetailLine("level", $"Lv {owned.UpgradeLevel}/{PackageTuning.MaxPackageLevel}"));
target.Add(BuildDetailLine("current", FormatPackageEffect(equipped, owned.UpgradeLevel, unit.Id)));
int nextLevel = owned.UpgradeLevel + 1;
bool maxLevel = owned.UpgradeLevel >= PackageTuning.MaxPackageLevel;
@@ -1038,10 +1162,10 @@ namespace KernelPanic.UI
int bandwidthCost = maxLevel ? 0 : PackageTuning.GetUpgradeBandwidthCost(nextLevel, equipped.Rarity);
bool canUpgrade = !maxLevel && cache >= cacheCost && bandwidth >= bandwidthCost && _tryUpgradePackage != null;
string upgradeState = maxLevel ? "max level" : cache < cacheCost ? "insufficient cache" : bandwidth < bandwidthCost ? "insufficient bandwidth" : "ready";
string nextPreview = maxLevel
? "max level reached"
: $"{FormatPackageEffect(equipped, owned.UpgradeLevel, unit.Id)} -> {FormatPackageEffect(equipped, nextLevel, unit.Id)}";
target.Add(BuildDetailLine("next", nextPreview));
string currentEffect = FormatPackageEffect(equipped, owned.UpgradeLevel, unit.Id);
string nextPreview = maxLevel ? "max level reached" : $"{currentEffect} -> {FormatPackageEffect(equipped, nextLevel, unit.Id)}";
target.Add(BuildPackagePreviewLine("current", currentEffect));
target.Add(BuildPackagePreviewLine("next", nextPreview));
target.Add(BuildCompactUpgradeCommand($"> make upgrade (Cache {cacheCost} / Bandwidth {bandwidthCost})", upgradeState, canUpgrade, () => UpgradePackage(owned)));
}
@@ -1061,8 +1185,10 @@ namespace KernelPanic.UI
}
bool isEquipped = equipped != null && string.Equals(equipped.Id, package.Id, StringComparison.OrdinalIgnoreCase);
int packageLevel = _playerCollection.GetOwnedPackage(package.Id)?.UpgradeLevel ?? 0;
VisualElement row = new();
row.AddToClassList("package-row");
row.AddToClassList("package-picker-row");
row.EnableInClassList("equipped", isEquipped);
row.EnableInClassList($"rarity-{package.Rarity}", true);
row.RegisterCallback<ClickEvent>(_ => TogglePackage(unit, slot, package, isEquipped));
@@ -1073,33 +1199,25 @@ namespace KernelPanic.UI
marker.AddToClassList("package-marker");
Label name = new(PackageName(package));
name.AddToClassList("package-name");
Label meta = new($"{package.Rarity}* / {package.Slot}");
Label effect = new(OneLineEffect(package, packageLevel, unit.Id));
effect.AddToClassList("package-picker-effect");
Label meta = new(package.IsIntendedFor(unit.Id) ? $"{package.Rarity}* on" : $"{package.Rarity}*");
meta.AddToClassList("package-meta");
summary.Add(marker);
summary.Add(name);
summary.Add(effect);
summary.Add(meta);
row.Add(summary);
Label description = new(package.Description);
description.AddToClassList("package-description");
row.Add(description);
if (package.IsIntendedFor(unit.Id))
{
Label intended = new("on-distro");
intended.AddToClassList("package-notice");
row.Add(intended);
}
target.Add(row);
}
}
private void TogglePackage(DistroDefinition unit, PackageSlot slot, PackageDefinition package, bool isEquipped)
private bool TogglePackage(DistroDefinition unit, PackageSlot slot, PackageDefinition package, bool isEquipped)
{
if (unit == null || package == null || _packageLoadout == null)
{
return;
return false;
}
PackageLoadoutFailureReason reason;
@@ -1109,14 +1227,240 @@ namespace KernelPanic.UI
if (changed)
{
_packageSlotNotice = null;
_packageLoadoutChanged?.Invoke();
}
RenderUnitDetail(unit);
if (!changed)
{
_detail.Add(BuildDetailLine("package", FormatPackageFailure(reason)));
_packageSlotNotice = FormatPackageFailure(reason);
}
RenderUnitDetail(unit);
return changed;
}
private void EnsurePackageEquipOverlay()
{
if (_root == null || _packageEquipOverlay != null)
{
return;
}
_packageEquipOverlay = new VisualElement();
_packageEquipOverlay.AddToClassList("modal-overlay");
_packageEquipOverlay.AddToClassList("package-equip-overlay");
_packageEquipOverlay.AddToClassList("hidden");
_packageEquipDialog = new VisualElement();
_packageEquipDialog.AddToClassList("package-equip-dialog");
_packageEquipOverlay.Add(_packageEquipDialog);
_root.Add(_packageEquipOverlay);
}
private void OpenPackageEquipWindow(DistroDefinition unit, PackageSlot slot)
{
if (unit == null)
{
return;
}
EnsurePackageEquipOverlay();
_equipWindowUnit = unit;
_equipWindowSlot = slot;
_packageSlotNotice = null;
RenderPackageEquipWindow();
_packageEquipOverlay.RemoveFromClassList("hidden");
_packageEquipOverlay.AddToClassList("modal-open");
}
private void ClosePackageEquipWindow()
{
_packageEquipOverlay?.RemoveFromClassList("modal-open");
_packageEquipOverlay?.AddToClassList("hidden");
_equipWindowUnit = null;
_packageSlotNotice = null;
}
private void RenderPackageEquipWindow()
{
if (_packageEquipDialog == null || _equipWindowUnit == null)
{
return;
}
_packageEquipDialog.Clear();
PackageDefinition equipped = _packageLoadout.GetEquippedPackage(_equipWindowUnit.Id, _equipWindowSlot);
VisualElement header = new();
header.AddToClassList("package-equip-header");
Label title = new($"equip: {_equipWindowSlot.ToString().ToLowerInvariant()} - {DistroPresentation.DisplayName(_equipWindowUnit)}");
title.AddToClassList("collection-detail-name");
title.style.color = new StyleColor(_equipWindowUnit.AccentColor);
header.Add(title);
Button close = new(ClosePackageEquipWindow) { text = "x", focusable = false };
close.AddToClassList("modal-close-button");
header.Add(close);
_packageEquipDialog.Add(header);
if (!string.IsNullOrWhiteSpace(_packageSlotNotice))
{
Label notice = new(_packageSlotNotice);
notice.AddToClassList("package-notice");
_packageEquipDialog.Add(notice);
}
string equippedName = equipped == null ? "empty" : PackageName(equipped);
_packageEquipDialog.Add(BuildDetailLine("current", equippedName));
if (equipped != null)
{
_packageEquipDialog.Add(BuildCompactUpgradeCommand("> unlink current", "unequip", true, () => UnequipPackageFromWindow(_equipWindowUnit, _equipWindowSlot)));
}
ScrollView list = new(ScrollViewMode.Vertical);
list.AddToClassList("package-equip-list");
int count = 0;
for (int i = 0; i < _playerCollection.OwnedPackageInstances.Count; i++)
{
OwnedPackageInstance owned = _playerCollection.OwnedPackageInstances[i];
PackageDefinition package = owned?.Definition;
if (package == null || package.Slot != _equipWindowSlot)
{
continue;
}
list.Add(BuildEquipWindowPackageRow(owned, equipped));
count++;
}
if (count == 0)
{
Label empty = new($"no owned {_equipWindowSlot.ToString().ToLowerInvariant()} packages");
empty.AddToClassList("package-empty");
list.Add(empty);
}
_packageEquipDialog.Add(list);
_packageEquipDialog.Add(BuildCompactUpgradeCommand("> cancel", "back", true, ClosePackageEquipWindow));
}
private VisualElement BuildEquipWindowPackageRow(OwnedPackageInstance owned, PackageDefinition equipped)
{
PackageDefinition package = owned.Definition;
bool isEquippedHere = equipped != null && string.Equals(equipped.Id, package.Id, StringComparison.OrdinalIgnoreCase);
VisualElement row = new();
row.AddToClassList("package-row");
row.AddToClassList("package-equip-row");
row.EnableInClassList("equipped", isEquippedHere);
row.EnableInClassList($"rarity-{package.Rarity}", true);
row.EnableInClassList("on-distro", package.IsIntendedFor(_equipWindowUnit.Id));
row.RegisterCallback<ClickEvent>(_ => EquipPackageFromWindow(_equipWindowUnit, _equipWindowSlot, package, isEquippedHere));
VisualElement summary = new();
summary.AddToClassList("package-summary");
Label marker = new(isEquippedHere ? "[x]" : "[ ]");
marker.AddToClassList("package-marker");
Label name = new(PackageName(package));
name.AddToClassList("package-name");
Label meta = new($"{RarityPresentation.ForStars(package.Rarity).Badge} Lv {owned.UpgradeLevel}/{PackageTuning.MaxPackageLevel}");
meta.AddToClassList("package-meta");
summary.Add(marker);
summary.Add(name);
summary.Add(meta);
row.Add(summary);
Label effect = new(FormatPackageEffect(package, owned.UpgradeLevel, _equipWindowUnit.Id));
effect.AddToClassList("package-description");
row.Add(effect);
VisualElement tags = new();
tags.AddToClassList("package-equip-tags");
string equippedOn = FindEquippedDistroName(package.Id);
if (!string.IsNullOrWhiteSpace(equippedOn))
{
Label equippedTag = new($"equipped on {equippedOn}");
equippedTag.AddToClassList("package-notice");
tags.Add(equippedTag);
}
if (package.IsIntendedFor(_equipWindowUnit.Id))
{
Label intended = new("on-distro");
intended.AddToClassList("package-notice");
tags.Add(intended);
}
if (tags.childCount > 0)
{
row.Add(tags);
}
return row;
}
private void EquipPackageFromWindow(DistroDefinition unit, PackageSlot slot, PackageDefinition package, bool isEquipped)
{
if (isEquipped)
{
UnequipPackageFromWindow(unit, slot);
return;
}
if (EquipOrSwapPackage(unit, slot, package))
{
ClosePackageEquipWindow();
RenderUnitDetail(unit);
ViewChanged?.Invoke();
return;
}
RenderPackageEquipWindow();
}
private bool EquipOrSwapPackage(DistroDefinition unit, PackageSlot slot, PackageDefinition package)
{
if (unit == null || package == null || _packageLoadout == null)
{
return false;
}
PackageDefinition currentlyEquipped = _packageLoadout.GetEquippedPackage(unit.Id, slot);
if (currentlyEquipped != null && !string.Equals(currentlyEquipped.Id, package.Id, StringComparison.OrdinalIgnoreCase))
{
if (!_packageLoadout.TryUnequip(unit.Id, slot, out PackageLoadoutFailureReason unequipReason))
{
_packageSlotNotice = FormatPackageFailure(unequipReason);
return false;
}
}
if (!_packageLoadout.TryEquip(unit.Id, slot, package.Id, out PackageLoadoutFailureReason equipReason))
{
_packageSlotNotice = FormatPackageFailure(equipReason);
return false;
}
_packageSlotNotice = null;
_packageLoadoutChanged?.Invoke();
return true;
}
private void UnequipPackageFromWindow(DistroDefinition unit, PackageSlot slot)
{
PackageDefinition equipped = _packageLoadout.GetEquippedPackage(unit.Id, slot);
if (equipped == null)
{
return;
}
if (TogglePackage(unit, slot, equipped, true))
{
ClosePackageEquipWindow();
RenderUnitDetail(unit);
ViewChanged?.Invoke();
return;
}
RenderPackageEquipWindow();
}
private void AddVersionUpgradePanel(VisualElement target, DistroDefinition unit)
@@ -1406,6 +1750,21 @@ namespace KernelPanic.UI
return row;
}
private static VisualElement BuildPackagePreviewLine(string key, string value)
{
VisualElement row = new();
row.AddToClassList("package-preview-line");
Label keyLabel = new(key);
keyLabel.AddToClassList("package-preview-key");
Label valueLabel = new(value);
valueLabel.AddToClassList("package-preview-value");
row.Add(keyLabel);
row.Add(valueLabel);
return row;
}
private VisualElement BuildSubviewCommand(string command, string description, bool enabled, Action action, string disabledDescription)
{
VisualElement row = new();
@@ -1688,7 +2047,7 @@ namespace KernelPanic.UI
{
PackageLoadoutFailureReason.WrongSlot => "wrong slot",
PackageLoadoutFailureReason.NotOwned => "package not owned",
PackageLoadoutFailureReason.SlotOccupied => "slot occupied",
PackageLoadoutFailureReason.SlotOccupied => "slot occupied: unequip current package first",
PackageLoadoutFailureReason.NotEquipped => "package not equipped",
_ => "package change failed"
};