Add actual carts and shit
Upgrade UI to Level 6 Balls
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 85f230365a0e4f28a4bfbdb0a15115a8, type: 3}
|
||||
m_Name: CardDatabase
|
||||
m_EditorClassIdentifier:
|
||||
allCards:
|
||||
- {fileID: 11400000, guid: 0a4f1d1d84564f3cad58b8863e941201, type: 2}
|
||||
- {fileID: 11400000, guid: 14c5d40f866a4cb6b0a6316e01f42891, type: 2}
|
||||
- {fileID: 11400000, guid: 2a0f50d541f046fc99c8fb6deef4f1aa, type: 2}
|
||||
- {fileID: 11400000, guid: 3d57857f3f30419c8be4c44914c5abbe, type: 2}
|
||||
- {fileID: 11400000, guid: 4cae271a0f4146c59aab74e4c9d6d0f8, type: 2}
|
||||
- {fileID: 11400000, guid: 5fef058a72b54b4aa2b2dd8f4d08bfe5, type: 2}
|
||||
@@ -0,0 +1,26 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 5178747fac4dbfc448ff15d8a687e03c, type: 3}
|
||||
m_Name: AskUbuntu
|
||||
m_EditorClassIdentifier:
|
||||
id: ubuntu_ask_ubuntu
|
||||
displayName: ask ubuntu
|
||||
description: "queue: draw 2 cards."
|
||||
language: 3
|
||||
rarity: 0
|
||||
cycleCost: 0
|
||||
resolutionTrack: 1
|
||||
distroExclusive: 1
|
||||
isToken: 0
|
||||
designNotes: pure draw on resolve.
|
||||
upgradeTarget: {fileID: 0}
|
||||
effects: []
|
||||
@@ -0,0 +1,26 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 5178747fac4dbfc448ff15d8a687e03c, type: 3}
|
||||
m_Name: DoReleaseUpgrade
|
||||
m_EditorClassIdentifier:
|
||||
id: ubuntu_do_release_upgrade
|
||||
displayName: do-release-upgrade
|
||||
description: upgrade 1-3 random cards in your hand this combat.
|
||||
language: 4
|
||||
rarity: 1
|
||||
cycleCost: 2
|
||||
resolutionTrack: 0
|
||||
distroExclusive: 1
|
||||
isToken: 0
|
||||
designNotes: count rolled on resolution (JS variance).
|
||||
upgradeTarget: {fileID: 0}
|
||||
effects: []
|
||||
@@ -0,0 +1,26 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 5178747fac4dbfc448ff15d8a687e03c, type: 3}
|
||||
m_Name: SnapInstall
|
||||
m_EditorClassIdentifier:
|
||||
id: ubuntu_snap_install
|
||||
displayName: snap install
|
||||
description: deal 6-14 damage. shuffle an ubuntu pro trial into your draw pile.
|
||||
language: 4
|
||||
rarity: 0
|
||||
cycleCost: 0
|
||||
resolutionTrack: 0
|
||||
distroExclusive: 1
|
||||
isToken: 0
|
||||
designNotes: "JS variance roll at resolution; junk-token insertion; references token by id ubuntu_pro_trial."
|
||||
upgradeTarget: {fileID: 0}
|
||||
effects: []
|
||||
@@ -0,0 +1,26 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 5178747fac4dbfc448ff15d8a687e03c, type: 3}
|
||||
m_Name: SudoAptInstall
|
||||
m_EditorClassIdentifier:
|
||||
id: ubuntu_apt_install
|
||||
displayName: sudo apt install
|
||||
description: "queue: add a random common python card to your hand. it costs 0 this combat."
|
||||
language: 3
|
||||
rarity: 0
|
||||
cycleCost: 1
|
||||
resolutionTrack: 1
|
||||
distroExclusive: 1
|
||||
isToken: 0
|
||||
designNotes: on-resolve card generation from the common Python pool.
|
||||
upgradeTarget: {fileID: 0}
|
||||
effects: []
|
||||
@@ -0,0 +1,26 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 5178747fac4dbfc448ff15d8a687e03c, type: 3}
|
||||
m_Name: UbuntuProTrial
|
||||
m_EditorClassIdentifier:
|
||||
id: ubuntu_pro_trial
|
||||
displayName: ubuntu pro trial
|
||||
description: unplayable. exhausts at end of combat.
|
||||
language: 3
|
||||
rarity: 0
|
||||
cycleCost: 0
|
||||
resolutionTrack: 0
|
||||
distroExclusive: 0
|
||||
isToken: 1
|
||||
designNotes: "junk token; unplayable flag TBD in combat model."
|
||||
upgradeTarget: {fileID: 0}
|
||||
effects: []
|
||||
@@ -0,0 +1,26 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 5178747fac4dbfc448ff15d8a687e03c, type: 3}
|
||||
m_Name: UnattendedUpgrades
|
||||
m_EditorClassIdentifier:
|
||||
id: ubuntu_unattended_upgrades
|
||||
displayName: unattended-upgrades
|
||||
description: gain 4 shield now and at the end of your next 2 turns.
|
||||
language: 3
|
||||
rarity: 0
|
||||
cycleCost: 1
|
||||
resolutionTrack: 1
|
||||
distroExclusive: 1
|
||||
isToken: 0
|
||||
designNotes: immediate shield + 2-turn delayed shield ticks.
|
||||
upgradeTarget: {fileID: 0}
|
||||
effects: []
|
||||
@@ -10,10 +10,12 @@ namespace KernelPanic.Meta
|
||||
public sealed class PlayerCollection
|
||||
{
|
||||
private readonly List<DistroDefinition> ownedUnits = new();
|
||||
private int featuredIndex;
|
||||
|
||||
// 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 DistroDefinition FeaturedUnit => ownedUnits.Count == 0 ? null : ownedUnits[Math.Clamp(featuredIndex, 0, ownedUnits.Count - 1)];
|
||||
|
||||
public event Action Changed;
|
||||
|
||||
@@ -26,7 +28,18 @@ namespace KernelPanic.Meta
|
||||
|
||||
// Persistence is wired by the owner so this domain class stays IO-free.
|
||||
ownedUnits.Add(unit);
|
||||
featuredIndex = Math.Clamp(featuredIndex, 0, ownedUnits.Count - 1);
|
||||
Changed?.Invoke();
|
||||
}
|
||||
|
||||
public void SelectNextFeatured()
|
||||
{
|
||||
if (ownedUnits.Count < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
featuredIndex = (featuredIndex + 1) % ownedUnits.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace KernelPanic.Meta
|
||||
public bool starterChosen;
|
||||
public List<string> ownedUnitIds = new();
|
||||
public List<string> bannerPoolIds = new();
|
||||
public List<CardLoadoutSaveEntry> cardLoadouts = new();
|
||||
public LastRunLoadoutSaveEntry lastRunLoadout = new();
|
||||
|
||||
public static SaveData CreateDefault()
|
||||
{
|
||||
@@ -71,24 +71,20 @@ namespace KernelPanic.Meta
|
||||
{
|
||||
ownedUnitIds ??= new List<string>();
|
||||
bannerPoolIds ??= new List<string>();
|
||||
cardLoadouts ??= new List<CardLoadoutSaveEntry>();
|
||||
for (int i = 0; i < cardLoadouts.Count; i++)
|
||||
{
|
||||
cardLoadouts[i] ??= new CardLoadoutSaveEntry();
|
||||
cardLoadouts[i].EnsureLists();
|
||||
}
|
||||
lastRunLoadout ??= new LastRunLoadoutSaveEntry();
|
||||
lastRunLoadout.EnsureLists();
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class CardLoadoutSaveEntry
|
||||
public sealed class LastRunLoadoutSaveEntry
|
||||
{
|
||||
public string distroId;
|
||||
public List<string> equippedCardIds = new();
|
||||
public List<string> cardIds = new();
|
||||
|
||||
public void EnsureLists()
|
||||
{
|
||||
equippedCardIds ??= new List<string>();
|
||||
cardIds ??= new List<string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace KernelPanic.UI
|
||||
private VisualElement detail;
|
||||
private FontAsset monospaceFont;
|
||||
private IReadOnlyList<DistroDefinition> units = Array.Empty<DistroDefinition>();
|
||||
private IReadOnlyList<CardEntry> cards = Array.Empty<CardEntry>();
|
||||
private int selectedIndex;
|
||||
|
||||
public void Bind(VisualElement root, FontAsset artFont)
|
||||
@@ -28,7 +29,7 @@ namespace KernelPanic.UI
|
||||
detail = root.Q<VisualElement>("CollectionDetail");
|
||||
}
|
||||
|
||||
public void Refresh(IReadOnlyList<DistroDefinition> ownedUnits)
|
||||
public void RefreshUnits(IReadOnlyList<DistroDefinition> ownedUnits)
|
||||
{
|
||||
units = ownedUnits ?? Array.Empty<DistroDefinition>();
|
||||
if (list == null || detail == null)
|
||||
@@ -73,6 +74,55 @@ namespace KernelPanic.UI
|
||||
SelectUnit(selectedIndex);
|
||||
}
|
||||
|
||||
public void RefreshCards(DistroDefinition featuredUnit)
|
||||
{
|
||||
cards = BuildCardEntries(featuredUnit);
|
||||
if (list == null || detail == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
rows.Clear();
|
||||
list.Clear();
|
||||
|
||||
if (cards.Count == 0)
|
||||
{
|
||||
detail.Clear();
|
||||
detail.Add(new Label("no cards installed") { name = "CollectionEmptyTitle" });
|
||||
detail.Add(new Label("featured distro cards appear here") { name = "CollectionEmptyHint" });
|
||||
return;
|
||||
}
|
||||
|
||||
selectedIndex = Mathf.Clamp(selectedIndex, 0, cards.Count - 1);
|
||||
for (int i = 0; i < cards.Count; i++)
|
||||
{
|
||||
int index = i;
|
||||
CardEntry entry = cards[i];
|
||||
VisualElement row = new();
|
||||
row.AddToClassList("collection-row");
|
||||
row.RegisterCallback<PointerEnterEvent>(_ => SelectCard(index));
|
||||
row.RegisterCallback<ClickEvent>(_ => SelectCard(index));
|
||||
|
||||
Label name = new(GetCardDisplayName(entry.Card));
|
||||
name.AddToClassList("collection-row-name");
|
||||
|
||||
Label meta = new($"{DistroPresentation.DisplayName(entry.Owner)} / {FormatCardMeta(entry.Card)}");
|
||||
meta.AddToClassList("collection-row-languages");
|
||||
|
||||
row.Add(name);
|
||||
row.Add(meta);
|
||||
list.Add(row);
|
||||
rows.Add(row);
|
||||
}
|
||||
|
||||
SelectCard(selectedIndex);
|
||||
}
|
||||
|
||||
public void Refresh(IReadOnlyList<DistroDefinition> ownedUnits)
|
||||
{
|
||||
RefreshUnits(ownedUnits);
|
||||
}
|
||||
|
||||
private void SelectUnit(int index)
|
||||
{
|
||||
if (units.Count == 0)
|
||||
@@ -89,10 +139,35 @@ namespace KernelPanic.UI
|
||||
RenderDetail(units[selectedIndex]);
|
||||
}
|
||||
|
||||
private void SelectCard(int index)
|
||||
{
|
||||
if (cards.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
selectedIndex = Mathf.Clamp(index, 0, cards.Count - 1);
|
||||
for (int i = 0; i < rows.Count; i++)
|
||||
{
|
||||
rows[i].EnableInClassList("selected", i == selectedIndex);
|
||||
}
|
||||
|
||||
RenderCardDetail(cards[selectedIndex]);
|
||||
}
|
||||
|
||||
private void RenderDetail(DistroDefinition unit)
|
||||
{
|
||||
detail.Clear();
|
||||
|
||||
Label name = new(DistroPresentation.DisplayName(unit));
|
||||
name.AddToClassList("collection-detail-name");
|
||||
name.style.color = new StyleColor(unit.AccentColor);
|
||||
detail.Add(name);
|
||||
|
||||
Label description = new(string.IsNullOrWhiteSpace(unit.Description) ? "--" : unit.Description);
|
||||
description.AddToClassList("collection-detail-description");
|
||||
detail.Add(description);
|
||||
|
||||
VisualElement readout = new();
|
||||
readout.AddToClassList("collection-detail-readout");
|
||||
|
||||
@@ -107,11 +182,6 @@ namespace KernelPanic.UI
|
||||
VisualElement details = new();
|
||||
details.AddToClassList("collection-detail-values");
|
||||
|
||||
Label name = new(DistroPresentation.DisplayName(unit));
|
||||
name.AddToClassList("collection-detail-name");
|
||||
name.style.color = new StyleColor(unit.AccentColor);
|
||||
details.Add(name);
|
||||
|
||||
details.Add(BuildDetailLine("lang", DistroPresentation.FormatLanguages(unit)));
|
||||
details.Add(BuildDetailLine("passive", string.IsNullOrWhiteSpace(unit.PassiveName) ? "--" : unit.PassiveName));
|
||||
details.Add(BuildDetailLine("uptime", unit.BaseUptime.ToString()));
|
||||
@@ -120,10 +190,26 @@ namespace KernelPanic.UI
|
||||
|
||||
readout.Add(details);
|
||||
detail.Add(readout);
|
||||
}
|
||||
|
||||
Label description = new(string.IsNullOrWhiteSpace(unit.Description) ? "--" : unit.Description);
|
||||
private void RenderCardDetail(CardEntry entry)
|
||||
{
|
||||
detail.Clear();
|
||||
|
||||
CardDefinition card = entry.Card;
|
||||
Label name = new(GetCardDisplayName(card));
|
||||
name.AddToClassList("collection-detail-name");
|
||||
detail.Add(name);
|
||||
|
||||
Label description = new(card == null || string.IsNullOrWhiteSpace(card.Description) ? "--" : card.Description);
|
||||
description.AddToClassList("collection-detail-description");
|
||||
detail.Add(description);
|
||||
|
||||
detail.Add(BuildDetailLine("owner", DistroPresentation.DisplayName(entry.Owner)));
|
||||
detail.Add(BuildDetailLine("lang", card == null ? "--" : card.Language.ToString()));
|
||||
detail.Add(BuildDetailLine("rarity", card == null ? "--" : card.Rarity.ToString()));
|
||||
detail.Add(BuildDetailLine("cost", card == null ? "--" : card.CycleCost.ToString()));
|
||||
detail.Add(BuildDetailLine("type", card != null && card.IsToken ? "token" : "loadout"));
|
||||
}
|
||||
|
||||
private static VisualElement BuildDetailLine(string key, string value)
|
||||
@@ -140,5 +226,54 @@ namespace KernelPanic.UI
|
||||
row.Add(valueLabel);
|
||||
return row;
|
||||
}
|
||||
|
||||
private static IReadOnlyList<CardEntry> BuildCardEntries(DistroDefinition featuredUnit)
|
||||
{
|
||||
List<CardEntry> entries = new();
|
||||
if (featuredUnit == null)
|
||||
{
|
||||
return entries;
|
||||
}
|
||||
|
||||
for (int cardIndex = 0; cardIndex < featuredUnit.ExclusiveCards.Count; cardIndex++)
|
||||
{
|
||||
CardDefinition card = featuredUnit.ExclusiveCards[cardIndex];
|
||||
if (card == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
entries.Add(new CardEntry(featuredUnit, card));
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
private static string GetCardDisplayName(CardDefinition card)
|
||||
{
|
||||
if (card == null)
|
||||
{
|
||||
return "--";
|
||||
}
|
||||
|
||||
return string.IsNullOrWhiteSpace(card.DisplayName) ? card.Id : card.DisplayName;
|
||||
}
|
||||
|
||||
private static string FormatCardMeta(CardDefinition card)
|
||||
{
|
||||
return card == null ? "--" : $"{card.Language} / {card.CycleCost}c";
|
||||
}
|
||||
|
||||
private readonly struct CardEntry
|
||||
{
|
||||
public CardEntry(DistroDefinition owner, CardDefinition card)
|
||||
{
|
||||
Owner = owner;
|
||||
Card = card;
|
||||
}
|
||||
|
||||
public DistroDefinition Owner { get; }
|
||||
public CardDefinition Card { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,12 +55,22 @@ namespace KernelPanic.UI
|
||||
}
|
||||
|
||||
public void Refresh(IReadOnlyList<DistroDefinition> ownedUnits)
|
||||
{
|
||||
Refresh(ownedUnits, null);
|
||||
}
|
||||
|
||||
public void Refresh(IReadOnlyList<DistroDefinition> ownedUnits, DistroDefinition featuredUnit)
|
||||
{
|
||||
units = ownedUnits ?? Array.Empty<DistroDefinition>();
|
||||
Refresh();
|
||||
Refresh(featuredUnit);
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
Refresh((DistroDefinition)null);
|
||||
}
|
||||
|
||||
private void Refresh(DistroDefinition featuredUnit)
|
||||
{
|
||||
if (units == null || units.Count == 0)
|
||||
{
|
||||
@@ -68,7 +78,7 @@ namespace KernelPanic.UI
|
||||
return;
|
||||
}
|
||||
|
||||
selectedIndex = Mathf.Clamp(selectedIndex, 0, units.Count - 1);
|
||||
selectedIndex = ResolveSelectedIndex(featuredUnit);
|
||||
ShowUnit(units[selectedIndex]);
|
||||
}
|
||||
|
||||
@@ -84,6 +94,22 @@ namespace KernelPanic.UI
|
||||
Refresh();
|
||||
}
|
||||
|
||||
private int ResolveSelectedIndex(DistroDefinition featuredUnit)
|
||||
{
|
||||
if (featuredUnit != null)
|
||||
{
|
||||
for (int i = 0; i < units.Count; i++)
|
||||
{
|
||||
if (units[i] == featuredUnit)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Mathf.Clamp(selectedIndex, 0, units.Count - 1);
|
||||
}
|
||||
|
||||
private void ShowEmptyState()
|
||||
{
|
||||
populatedState.AddToClassList("hidden");
|
||||
|
||||
@@ -43,6 +43,8 @@ namespace KernelPanic.UI
|
||||
[SerializeField] private StarterSelectionController starterSelection = new();
|
||||
|
||||
private readonly List<CommandMenuEntry> commandEntries = new();
|
||||
private readonly List<VisualElement> packageRows = new();
|
||||
private readonly List<VisualElement> loadoutRows = new();
|
||||
private UIDocument document;
|
||||
private VisualElement root;
|
||||
private VisualElement shellRoot;
|
||||
@@ -54,6 +56,8 @@ namespace KernelPanic.UI
|
||||
private VisualElement settingsPanel;
|
||||
private VisualElement eventBanner;
|
||||
private VisualElement backgroundLogLayer;
|
||||
private ScrollView runSetupList;
|
||||
private VisualElement runSetupDetail;
|
||||
private Label appIdLabel;
|
||||
private Label entropyLabel;
|
||||
private Label pullTokensLabel;
|
||||
@@ -62,12 +66,12 @@ namespace KernelPanic.UI
|
||||
private Label bootIntroLogLabel;
|
||||
private Label motdBodyLabel;
|
||||
private VisualElement motdBlock;
|
||||
private Button collectionBackButton;
|
||||
private Button collectionUnitsButton;
|
||||
private Button collectionCardsButton;
|
||||
private Button runSetupBackButton;
|
||||
private Button gachaBackButton;
|
||||
private Button settingsBackButton;
|
||||
private readonly ScreenFrameController runSetupFrame = new();
|
||||
private readonly ScreenFrameController collectionFrame = new();
|
||||
private readonly ScreenFrameController gachaFrame = new();
|
||||
private readonly ScreenFrameController settingsFrame = new();
|
||||
private SaveService saveService;
|
||||
private SaveData saveData;
|
||||
private EntropyWallet wallet;
|
||||
@@ -77,12 +81,14 @@ namespace KernelPanic.UI
|
||||
private BackgroundLogRingBuffer backgroundLog;
|
||||
private IEventBannerSource eventBannerSource;
|
||||
private int selectedCommandIndex;
|
||||
private int selectedPackageIndex;
|
||||
private string runSetupNotice;
|
||||
private bool collectionShowingCards;
|
||||
private float bootIntroElapsed;
|
||||
private int bootIntroCharacterCount;
|
||||
private bool cursorVisible;
|
||||
private bool suppressNextClick;
|
||||
private bool warnedUnresolvedSaveId;
|
||||
private bool warnedInvalidLoadoutId;
|
||||
private string bootIntroCopy;
|
||||
private IVisualElementScheduledItem blinkSchedule;
|
||||
private IVisualElementScheduledItem bootIntroSchedule;
|
||||
@@ -106,6 +112,7 @@ namespace KernelPanic.UI
|
||||
RegisterCommandEntryCallbacks();
|
||||
featuredUnitPanel.Bind(root, monospaceFont);
|
||||
collectionScreen.Bind(root, monospaceFont);
|
||||
BindScreenFrames();
|
||||
starterSelection.Bind(root, distroDatabase, HandleStarterConfirmed);
|
||||
LoadMetaState();
|
||||
playerCollection.Changed += HandleMetaStateChanged;
|
||||
@@ -121,8 +128,8 @@ namespace KernelPanic.UI
|
||||
RefreshStaticText();
|
||||
RefreshCurrencyReadouts();
|
||||
RefreshEventBanner();
|
||||
featuredUnitPanel.Refresh(playerCollection.OwnedUnits);
|
||||
collectionScreen.Refresh(playerCollection.OwnedUnits);
|
||||
featuredUnitPanel.Refresh(playerCollection.OwnedUnits, playerCollection.FeaturedUnit);
|
||||
collectionScreen.RefreshUnits(playerCollection.OwnedUnits);
|
||||
SelectCommand(0);
|
||||
ShowMainMenu();
|
||||
StartAmbientSchedules();
|
||||
@@ -151,6 +158,8 @@ namespace KernelPanic.UI
|
||||
eventBanner = root.Q<VisualElement>("EventBanner");
|
||||
motdBlock = root.Q<VisualElement>("MotdBlock");
|
||||
backgroundLogLayer = root.Q<VisualElement>("BackgroundLogLayer");
|
||||
runSetupList = root.Q<ScrollView>("RunSetupList");
|
||||
runSetupDetail = root.Q<VisualElement>("RunSetupDetail");
|
||||
backgroundLog = new BackgroundLogRingBuffer(backgroundLogLayer, BootLogCopy.Lines);
|
||||
|
||||
appIdLabel = root.Q<Label>("AppIdLabel");
|
||||
@@ -161,12 +170,16 @@ namespace KernelPanic.UI
|
||||
bootIntroLogLabel = root.Q<Label>("BootIntroLogLabel");
|
||||
motdBodyLabel = root.Q<Label>("MotdBodyLabel");
|
||||
|
||||
collectionBackButton = root.Q<Button>("CollectionBackButton");
|
||||
collectionUnitsButton = root.Q<Button>("CollectionUnitsButton");
|
||||
collectionCardsButton = root.Q<Button>("CollectionCardsButton");
|
||||
runSetupBackButton = root.Q<Button>("RunSetupBackButton");
|
||||
gachaBackButton = root.Q<Button>("GachaBackButton");
|
||||
settingsBackButton = root.Q<Button>("SettingsBackButton");
|
||||
}
|
||||
|
||||
private void BindScreenFrames()
|
||||
{
|
||||
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", ShowMainMenu);
|
||||
gachaFrame.Bind(gachaPanel, "$ curl gacha.sh | sh", "[esc] back", ShowMainMenu);
|
||||
settingsFrame.Bind(settingsPanel, "$ dpkg-reconfigure kernel-panic", "[esc] back", ShowMainMenu);
|
||||
}
|
||||
|
||||
private void BindCommandEntries()
|
||||
@@ -193,24 +206,16 @@ namespace KernelPanic.UI
|
||||
{
|
||||
root.RegisterCallback<KeyDownEvent>(HandleKeyDown);
|
||||
root.RegisterCallback<PointerDownEvent>(HandlePointerDown);
|
||||
collectionBackButton.clicked += ShowMainMenu;
|
||||
collectionUnitsButton.clicked += ShowCollectionUnits;
|
||||
collectionCardsButton.clicked += ShowCollectionCards;
|
||||
runSetupBackButton.clicked += ShowMainMenu;
|
||||
gachaBackButton.clicked += ShowMainMenu;
|
||||
settingsBackButton.clicked += ShowMainMenu;
|
||||
}
|
||||
|
||||
private void UnregisterCallbacks()
|
||||
{
|
||||
root.UnregisterCallback<KeyDownEvent>(HandleKeyDown);
|
||||
root.UnregisterCallback<PointerDownEvent>(HandlePointerDown);
|
||||
collectionBackButton.clicked -= ShowMainMenu;
|
||||
collectionUnitsButton.clicked -= ShowCollectionUnits;
|
||||
collectionCardsButton.clicked -= ShowCollectionCards;
|
||||
runSetupBackButton.clicked -= ShowMainMenu;
|
||||
gachaBackButton.clicked -= ShowMainMenu;
|
||||
settingsBackButton.clicked -= ShowMainMenu;
|
||||
}
|
||||
|
||||
private void LoadMetaState()
|
||||
@@ -236,27 +241,6 @@ namespace KernelPanic.UI
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < saveData.cardLoadouts.Count; i++)
|
||||
{
|
||||
CardLoadoutSaveEntry entry = saveData.cardLoadouts[i];
|
||||
entry.EnsureLists();
|
||||
List<string> resolvedIds = new();
|
||||
for (int cardIndex = 0; cardIndex < entry.equippedCardIds.Count; cardIndex++)
|
||||
{
|
||||
string resolvedId = ResolveSavedCardId(entry.equippedCardIds[cardIndex]);
|
||||
if (!string.IsNullOrWhiteSpace(resolvedId))
|
||||
{
|
||||
resolvedIds.Add(resolvedId);
|
||||
}
|
||||
}
|
||||
|
||||
if (!cardLoadout.TryLoad(entry.distroId, resolvedIds, out bool skippedInvalid) || skippedInvalid)
|
||||
{
|
||||
WarnInvalidLoadoutId();
|
||||
}
|
||||
}
|
||||
|
||||
EnsureLoadoutsForOwnedUnits();
|
||||
SaveCurrentState();
|
||||
}
|
||||
|
||||
@@ -272,40 +256,11 @@ namespace KernelPanic.UI
|
||||
return unit;
|
||||
}
|
||||
|
||||
private string ResolveSavedCardId(string id)
|
||||
{
|
||||
if (cardDatabase == null)
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
CardDefinition card = cardDatabase.FindById(id);
|
||||
if (card == null)
|
||||
{
|
||||
WarnInvalidLoadoutId();
|
||||
return null;
|
||||
}
|
||||
|
||||
return card.Id;
|
||||
}
|
||||
|
||||
private void WarnInvalidLoadoutId()
|
||||
{
|
||||
if (warnedInvalidLoadoutId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
warnedInvalidLoadoutId = true;
|
||||
Debug.LogWarning("Save references an invalid card loadout id; restoring a valid default loadout.");
|
||||
}
|
||||
|
||||
private void HandleMetaStateChanged()
|
||||
{
|
||||
EnsureLoadoutsForOwnedUnits();
|
||||
SaveCurrentState();
|
||||
featuredUnitPanel.Refresh(playerCollection.OwnedUnits);
|
||||
collectionScreen.Refresh(playerCollection.OwnedUnits);
|
||||
featuredUnitPanel.Refresh(playerCollection.OwnedUnits, playerCollection.FeaturedUnit);
|
||||
collectionScreen.RefreshUnits(playerCollection.OwnedUnits);
|
||||
}
|
||||
|
||||
private void HandleStarterConfirmed(DistroDefinition picked, IReadOnlyList<DistroDefinition> remaining)
|
||||
@@ -318,8 +273,8 @@ namespace KernelPanic.UI
|
||||
|
||||
saveData.starterChosen = true;
|
||||
SaveCurrentState();
|
||||
featuredUnitPanel.Refresh(playerCollection.OwnedUnits);
|
||||
collectionScreen.Refresh(playerCollection.OwnedUnits);
|
||||
featuredUnitPanel.Refresh(playerCollection.OwnedUnits, playerCollection.FeaturedUnit);
|
||||
collectionScreen.RefreshUnits(playerCollection.OwnedUnits);
|
||||
}
|
||||
|
||||
private void SaveCurrentState()
|
||||
@@ -347,11 +302,24 @@ namespace KernelPanic.UI
|
||||
}
|
||||
}
|
||||
|
||||
cardLoadout.WriteTo(saveData.cardLoadouts);
|
||||
|
||||
saveService.Save(saveData);
|
||||
}
|
||||
|
||||
private void SaveLastRunLoadout(DistroDefinition unit, IReadOnlyList<string> cardIds)
|
||||
{
|
||||
saveData ??= SaveData.CreateDefault();
|
||||
saveData.EnsureLists();
|
||||
saveData.lastRunLoadout.distroId = unit == null ? null : unit.Id;
|
||||
saveData.lastRunLoadout.cardIds.Clear();
|
||||
|
||||
for (int i = 0; i < cardIds.Count; i++)
|
||||
{
|
||||
saveData.lastRunLoadout.cardIds.Add(cardIds[i]);
|
||||
}
|
||||
|
||||
SaveCurrentState();
|
||||
}
|
||||
|
||||
private void RefreshStaticText()
|
||||
{
|
||||
appIdLabel.text = $"kernel-panic v{Application.version} - tty1";
|
||||
@@ -488,6 +456,13 @@ namespace KernelPanic.UI
|
||||
return;
|
||||
}
|
||||
|
||||
if (evt.keyCode == KeyCode.Escape && IsSubScreenVisible())
|
||||
{
|
||||
ShowMainMenu();
|
||||
evt.StopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsRunSetupVisible())
|
||||
{
|
||||
if (evt.keyCode == KeyCode.UpArrow)
|
||||
@@ -504,9 +479,19 @@ namespace KernelPanic.UI
|
||||
return;
|
||||
}
|
||||
|
||||
if (evt.keyCode == KeyCode.Return || evt.keyCode == KeyCode.KeypadEnter)
|
||||
if (evt.keyCode == KeyCode.Return || evt.keyCode == KeyCode.KeypadEnter || evt.keyCode == KeyCode.B)
|
||||
{
|
||||
ToggleSelectedPackage();
|
||||
BootSelectedPackage();
|
||||
evt.StopPropagation();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (IsCollectionVisible())
|
||||
{
|
||||
if (evt.keyCode == KeyCode.LeftArrow || evt.keyCode == KeyCode.RightArrow || evt.keyCode == KeyCode.Tab)
|
||||
{
|
||||
ToggleCollectionTab();
|
||||
evt.StopPropagation();
|
||||
return;
|
||||
}
|
||||
@@ -535,7 +520,8 @@ namespace KernelPanic.UI
|
||||
|
||||
if (evt.keyCode == KeyCode.Tab)
|
||||
{
|
||||
featuredUnitPanel.SelectNext();
|
||||
playerCollection.SelectNextFeatured();
|
||||
featuredUnitPanel.Refresh(playerCollection.OwnedUnits, playerCollection.FeaturedUnit);
|
||||
evt.StopPropagation();
|
||||
return;
|
||||
}
|
||||
@@ -554,6 +540,78 @@ namespace KernelPanic.UI
|
||||
return runSetupPanel != null && !runSetupPanel.ClassListContains(HiddenClassName);
|
||||
}
|
||||
|
||||
private bool IsCollectionVisible()
|
||||
{
|
||||
return collectionPanel != null && !collectionPanel.ClassListContains(HiddenClassName);
|
||||
}
|
||||
|
||||
private bool IsSubScreenVisible()
|
||||
{
|
||||
return IsRunSetupVisible() || IsCollectionVisible() ||
|
||||
(gachaPanel != null && !gachaPanel.ClassListContains(HiddenClassName)) ||
|
||||
(settingsPanel != null && !settingsPanel.ClassListContains(HiddenClassName));
|
||||
}
|
||||
|
||||
private void RefreshRunSetup()
|
||||
{
|
||||
packageRows.Clear();
|
||||
loadoutRows.Clear();
|
||||
cardLoadout.ClearAll();
|
||||
runSetupList?.Clear();
|
||||
runSetupDetail?.Clear();
|
||||
runSetupNotice = null;
|
||||
|
||||
if (runSetupList == null || runSetupDetail == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (playerCollection.OwnedUnits.Count == 0)
|
||||
{
|
||||
runSetupDetail.Add(new Label("no units installed") { name = "RunSetupEmptyTitle" });
|
||||
runSetupDetail.Add(new Label("install a starter or summon a unit before starting a run") { name = "RunSetupEmptyHint" });
|
||||
return;
|
||||
}
|
||||
|
||||
selectedPackageIndex = Mathf.Clamp(selectedPackageIndex, 0, playerCollection.OwnedUnits.Count - 1);
|
||||
|
||||
for (int i = 0; i < playerCollection.OwnedUnits.Count; i++)
|
||||
{
|
||||
int index = i;
|
||||
DistroDefinition unit = playerCollection.OwnedUnits[i];
|
||||
VisualElement row = new();
|
||||
row.AddToClassList("package-row");
|
||||
row.RegisterCallback<PointerEnterEvent>(_ => SelectPackage(index));
|
||||
row.RegisterCallback<ClickEvent>(_ => SelectPackage(index));
|
||||
|
||||
VisualElement summary = new();
|
||||
summary.AddToClassList("package-summary");
|
||||
summary.Add(new Label(">") { name = $"RunSetupMarker{index}" });
|
||||
summary.ElementAt(0).AddToClassList("package-marker");
|
||||
|
||||
Label name = new(DistroPresentation.DisplayName(unit));
|
||||
name.AddToClassList("package-name");
|
||||
name.style.color = new StyleColor(unit.AccentColor);
|
||||
summary.Add(name);
|
||||
|
||||
Label meta = new(DistroPresentation.FormatLanguages(unit));
|
||||
meta.AddToClassList("package-meta");
|
||||
summary.Add(meta);
|
||||
|
||||
row.Add(summary);
|
||||
row.Add(new Label(string.IsNullOrWhiteSpace(unit.PassiveName) ? "passive: --" : $"passive: {unit.PassiveName}")
|
||||
{
|
||||
name = $"RunSetupDescription{index}"
|
||||
});
|
||||
row.ElementAt(1).AddToClassList("package-description");
|
||||
|
||||
runSetupList.Add(row);
|
||||
packageRows.Add(row);
|
||||
}
|
||||
|
||||
RefreshPackageSelection();
|
||||
}
|
||||
|
||||
private void SelectPackage(int index)
|
||||
{
|
||||
if (packageRows.Count == 0)
|
||||
@@ -562,9 +620,242 @@ namespace KernelPanic.UI
|
||||
}
|
||||
|
||||
selectedPackageIndex = (index + packageRows.Count) % packageRows.Count;
|
||||
cardLoadout.ClearLoadout(playerCollection.OwnedUnits[selectedPackageIndex]);
|
||||
runSetupNotice = null;
|
||||
RefreshPackageSelection();
|
||||
}
|
||||
|
||||
private void RefreshPackageSelection()
|
||||
{
|
||||
for (int i = 0; i < packageRows.Count; i++)
|
||||
{
|
||||
packageRows[i].EnableInClassList(SelectedClassName, i == selectedPackageIndex);
|
||||
}
|
||||
|
||||
RenderSelectedPackageDetail();
|
||||
}
|
||||
|
||||
private void RenderSelectedPackageDetail()
|
||||
{
|
||||
runSetupDetail?.Clear();
|
||||
loadoutRows.Clear();
|
||||
if (runSetupDetail == null || selectedPackageIndex < 0 || selectedPackageIndex >= playerCollection.OwnedUnits.Count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DistroDefinition unit = playerCollection.OwnedUnits[selectedPackageIndex];
|
||||
runSetupDetail.Add(BuildRunSetupReadout(unit));
|
||||
|
||||
ScrollView packageScroll = new(ScrollViewMode.Vertical);
|
||||
packageScroll.AddToClassList("package-scroll");
|
||||
|
||||
VisualElement packageList = new();
|
||||
packageList.AddToClassList("package-list");
|
||||
packageList.Add(new Label("equipped cards") { name = "RunSetupCardsHeader" });
|
||||
packageList.ElementAt(0).AddToClassList("package-header");
|
||||
|
||||
IReadOnlyList<string> equippedCardIds = cardLoadout.GetEquippedCardIds(unit.Id);
|
||||
if (!string.IsNullOrWhiteSpace(runSetupNotice))
|
||||
{
|
||||
packageList.Add(new Label(runSetupNotice) { name = "RunSetupNotice" });
|
||||
packageList.ElementAt(packageList.childCount - 1).AddToClassList("package-notice");
|
||||
}
|
||||
|
||||
if (equippedCardIds.Count < CardLoadout.MaxEquippedCards)
|
||||
{
|
||||
packageList.Add(new Label($"select {CardLoadout.MaxEquippedCards - equippedCardIds.Count} more card(s) before booting") { name = "RunSetupIncompleteNotice" });
|
||||
packageList.ElementAt(packageList.childCount - 1).AddToClassList("package-notice");
|
||||
}
|
||||
|
||||
bool addedCards = false;
|
||||
for (int i = 0; i < unit.ExclusiveCards.Count; i++)
|
||||
{
|
||||
CardDefinition card = unit.ExclusiveCards[i];
|
||||
if (card == null || card.IsToken)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bool equipped = ContainsCardId(equippedCardIds, card.Id);
|
||||
VisualElement row = BuildCardLoadoutRow(unit, card, equipped);
|
||||
packageList.Add(row);
|
||||
loadoutRows.Add(row);
|
||||
addedCards = true;
|
||||
}
|
||||
|
||||
if (!addedCards)
|
||||
{
|
||||
packageList.Add(new Label("no compatible cards available") { name = "RunSetupCardsEmpty" });
|
||||
packageList.ElementAt(packageList.childCount - 1).AddToClassList("package-empty");
|
||||
}
|
||||
|
||||
packageScroll.Add(packageList);
|
||||
runSetupDetail.Add(packageScroll);
|
||||
runSetupDetail.Add(BuildBootRow(unit, equippedCardIds.Count > 0));
|
||||
}
|
||||
|
||||
private VisualElement BuildRunSetupReadout(DistroDefinition unit)
|
||||
{
|
||||
VisualElement readout = new();
|
||||
readout.AddToClassList("run-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 copy = new();
|
||||
copy.AddToClassList("run-readout-copy");
|
||||
|
||||
Label name = new(DistroPresentation.DisplayName(unit));
|
||||
name.AddToClassList("collection-detail-name");
|
||||
name.style.color = new StyleColor(unit.AccentColor);
|
||||
copy.Add(name);
|
||||
|
||||
Label languages = new(DistroPresentation.FormatLanguages(unit));
|
||||
languages.AddToClassList("collection-detail-description");
|
||||
copy.Add(languages);
|
||||
|
||||
Label stats = new($"uptime {unit.BaseUptime} · ram {unit.BaseRam} · cycles {unit.BaseCyclesPerTurn}");
|
||||
stats.AddToClassList("collection-detail-muted");
|
||||
copy.Add(stats);
|
||||
|
||||
readout.Add(copy);
|
||||
return readout;
|
||||
}
|
||||
|
||||
private static VisualElement BuildDetailLine(string key, string value)
|
||||
{
|
||||
VisualElement row = new();
|
||||
row.AddToClassList("kv-row");
|
||||
row.Add(new Label(key) { name = $"RunSetup{key}Key" });
|
||||
row.ElementAt(0).AddToClassList("kv-key");
|
||||
row.Add(new Label(value) { name = $"RunSetup{key}Value" });
|
||||
row.ElementAt(1).AddToClassList("kv-value");
|
||||
return row;
|
||||
}
|
||||
|
||||
private VisualElement BuildCardLoadoutRow(DistroDefinition unit, CardDefinition card, bool equipped)
|
||||
{
|
||||
VisualElement row = new();
|
||||
row.AddToClassList("package-row");
|
||||
row.AddToClassList("loadout-card-row");
|
||||
row.EnableInClassList("equipped", equipped);
|
||||
row.RegisterCallback<ClickEvent>(_ => ToggleCardInSelectedLoadout(unit, card));
|
||||
|
||||
VisualElement summary = new();
|
||||
summary.AddToClassList("package-summary");
|
||||
summary.Add(new Label(equipped ? "[x]" : "[ ]") { name = "RunSetupCardMarker" });
|
||||
summary.ElementAt(0).AddToClassList("package-marker");
|
||||
|
||||
string displayName = string.IsNullOrWhiteSpace(card.DisplayName) ? card.Id : card.DisplayName;
|
||||
summary.Add(new Label(displayName) { name = "RunSetupCardName" });
|
||||
summary.ElementAt(1).AddToClassList("package-name");
|
||||
|
||||
string metaText = $"{card.Language} / {card.CycleCost}c";
|
||||
summary.Add(new Label(metaText) { name = "RunSetupCardMeta" });
|
||||
summary.ElementAt(2).AddToClassList("package-meta");
|
||||
row.Add(summary);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(card.Description))
|
||||
{
|
||||
row.Add(new Label(card.Description) { name = "RunSetupCardDescription" });
|
||||
row.ElementAt(1).AddToClassList("package-description");
|
||||
}
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
private void ToggleCardInSelectedLoadout(DistroDefinition unit, CardDefinition card)
|
||||
{
|
||||
runSetupNotice = TryToggleLoadoutCard(unit, card);
|
||||
RenderSelectedPackageDetail();
|
||||
}
|
||||
|
||||
private string TryToggleLoadoutCard(DistroDefinition unit, CardDefinition card)
|
||||
{
|
||||
if (unit == null || card == null)
|
||||
{
|
||||
return "loadout change failed";
|
||||
}
|
||||
|
||||
IReadOnlyList<string> equippedCardIds = cardLoadout.GetEquippedCardIds(unit.Id);
|
||||
bool equipped = ContainsCardId(equippedCardIds, card.Id);
|
||||
CardLoadoutFailureReason reason;
|
||||
bool changed = equipped
|
||||
? cardLoadout.TryUnequip(unit.Id, card.Id, out reason)
|
||||
: cardLoadout.TryEquip(unit.Id, card.Id, out reason);
|
||||
|
||||
return changed ? null : FormatLoadoutFailure(reason);
|
||||
}
|
||||
|
||||
private static bool ContainsCardId(IReadOnlyList<string> cardIds, string cardId)
|
||||
{
|
||||
for (int i = 0; i < cardIds.Count; i++)
|
||||
{
|
||||
if (string.Equals(cardIds[i], cardId, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string FormatLoadoutFailure(CardLoadoutFailureReason reason)
|
||||
{
|
||||
return reason switch
|
||||
{
|
||||
CardLoadoutFailureReason.Full => $"loadout already has {CardLoadout.MaxEquippedCards} cards",
|
||||
CardLoadoutFailureReason.Duplicate => "card is already equipped",
|
||||
CardLoadoutFailureReason.Token => "token cards cannot be equipped",
|
||||
CardLoadoutFailureReason.NotEquipped => "card is not equipped",
|
||||
CardLoadoutFailureReason.NotOwned => "card is not owned by this distro",
|
||||
_ => "loadout change failed"
|
||||
};
|
||||
}
|
||||
|
||||
private VisualElement BuildBootRow(DistroDefinition unit, bool enabled)
|
||||
{
|
||||
VisualElement row = new();
|
||||
row.AddToClassList("command-row");
|
||||
row.AddToClassList("boot-command-row");
|
||||
row.EnableInClassList("disabled-row", !enabled);
|
||||
row.RegisterCallback<ClickEvent>(_ => BootSelectedPackage());
|
||||
|
||||
row.Add(new Label(">") { name = "BootCommandCursor" });
|
||||
row.ElementAt(0).AddToClassList("command-cursor");
|
||||
row.Add(new Label($"./boot --distro={unit.Id}") { name = "BootCommandText" });
|
||||
row.ElementAt(1).AddToClassList("command-text");
|
||||
row.Add(new Label("start run") { name = "BootCommandDescription" });
|
||||
row.ElementAt(2).AddToClassList("command-description");
|
||||
return row;
|
||||
}
|
||||
|
||||
private void BootSelectedPackage()
|
||||
{
|
||||
if (selectedPackageIndex < 0 || selectedPackageIndex >= playerCollection.OwnedUnits.Count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DistroDefinition unit = playerCollection.OwnedUnits[selectedPackageIndex];
|
||||
IReadOnlyList<string> equippedCardIds = cardLoadout.GetEquippedCardIds(unit.Id);
|
||||
if (equippedCardIds.Count < 1)
|
||||
{
|
||||
runSetupNotice = "boot: no packages staged";
|
||||
RenderSelectedPackageDetail();
|
||||
return;
|
||||
}
|
||||
|
||||
SaveLastRunLoadout(unit, equippedCardIds);
|
||||
SceneLoader.LoadGame();
|
||||
}
|
||||
|
||||
private void HandlePointerDown(PointerDownEvent evt)
|
||||
{
|
||||
root.Focus();
|
||||
@@ -613,26 +904,47 @@ namespace KernelPanic.UI
|
||||
|
||||
private void ShowMainMenu()
|
||||
{
|
||||
cardLoadout.ClearAll();
|
||||
runSetupNotice = null;
|
||||
ShowPanel(mainMenuPanel);
|
||||
root.Focus();
|
||||
}
|
||||
|
||||
private void ShowCollection()
|
||||
{
|
||||
collectionScreen.Refresh(playerCollection.OwnedUnits);
|
||||
ShowCollectionUnits();
|
||||
ShowPanel(collectionPanel);
|
||||
}
|
||||
|
||||
private void ShowCollectionUnits()
|
||||
{
|
||||
collectionShowingCards = false;
|
||||
RefreshCollection();
|
||||
collectionUnitsButton.EnableInClassList(SelectedClassName, true);
|
||||
collectionCardsButton.EnableInClassList(SelectedClassName, false);
|
||||
collectionFrame.SetTitle("$ ls ~/collection");
|
||||
collectionScreen.RefreshUnits(playerCollection.OwnedUnits);
|
||||
}
|
||||
|
||||
private void ShowCollectionCards()
|
||||
{
|
||||
collectionShowingCards = true;
|
||||
RefreshCollection();
|
||||
collectionUnitsButton.EnableInClassList(SelectedClassName, false);
|
||||
collectionCardsButton.EnableInClassList(SelectedClassName, true);
|
||||
DistroDefinition featuredUnit = playerCollection.FeaturedUnit;
|
||||
string distroId = featuredUnit == null ? "--" : featuredUnit.Id;
|
||||
collectionFrame.SetTitle($"$ dpkg -l --distro={distroId}");
|
||||
collectionScreen.RefreshCards(playerCollection.FeaturedUnit);
|
||||
}
|
||||
|
||||
private void ToggleCollectionTab()
|
||||
{
|
||||
if (collectionShowingCards)
|
||||
{
|
||||
ShowCollectionUnits();
|
||||
return;
|
||||
}
|
||||
|
||||
ShowCollectionCards();
|
||||
}
|
||||
|
||||
private void ShowGacha()
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">
|
||||
<ui:VisualElement name="CollectionPanel" class="sub-panel hidden">
|
||||
<ui:Label text="COLLECTION" class="screen-title" />
|
||||
<ui:VisualElement name="CollectionPanel" class="screen-panel hidden">
|
||||
<ui:VisualElement class="screen-frame">
|
||||
<ui:VisualElement class="screen-frame-header">
|
||||
<ui:Label class="screen-frame-title" />
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement class="screen-frame-content">
|
||||
<ui:VisualElement class="collection-actions">
|
||||
<ui:Button name="CollectionUnitsButton" text="units" class="terminal-button collection-action-button" />
|
||||
<ui:Button name="CollectionCardsButton" text="cards" class="terminal-button collection-action-button" />
|
||||
<ui:Button name="CollectionUnitsButton" text="units" class="collection-tab" />
|
||||
<ui:Button name="CollectionCardsButton" text="cards" class="collection-tab" />
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement name="CollectionContent" class="collection-shell">
|
||||
<ui:ScrollView name="CollectionList" class="collection-list" />
|
||||
<ui:VisualElement name="CollectionDetail" class="collection-detail" />
|
||||
</ui:VisualElement>
|
||||
<ui:Button name="CollectionBackButton" text="Back" class="terminal-button back-button" />
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement class="screen-frame-footer">
|
||||
<ui:Label class="screen-frame-hint" />
|
||||
</ui:VisualElement>
|
||||
</ui:VisualElement>
|
||||
</ui:VisualElement>
|
||||
</ui:UXML>
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">
|
||||
<ui:VisualElement name="GachaPanel" class="sub-panel hidden">
|
||||
<ui:Label text="GACHA" class="screen-title" />
|
||||
<ui:VisualElement name="GachaPanel" class="screen-panel hidden">
|
||||
<ui:VisualElement class="screen-frame">
|
||||
<ui:VisualElement class="screen-frame-header">
|
||||
<ui:Label class="screen-frame-title" />
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement class="screen-frame-content">
|
||||
<ui:VisualElement name="GachaContent" class="stub-box">
|
||||
<!-- TODO: Render summon options and results here when gacha logic exists. -->
|
||||
</ui:VisualElement>
|
||||
<ui:Button name="GachaBackButton" text="Back" class="terminal-button back-button" />
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement class="screen-frame-footer">
|
||||
<ui:Label class="screen-frame-hint" />
|
||||
</ui:VisualElement>
|
||||
</ui:VisualElement>
|
||||
</ui:VisualElement>
|
||||
</ui:UXML>
|
||||
|
||||
+160
-39
@@ -239,13 +239,6 @@ Button {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.screen-title {
|
||||
color: var(--kp-primary);
|
||||
font-size: 24px;
|
||||
-unity-font-style: bold;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.featured-empty {
|
||||
flex-grow: 1;
|
||||
flex-direction: row;
|
||||
@@ -449,17 +442,53 @@ Button {
|
||||
color: var(--kp-amber);
|
||||
}
|
||||
|
||||
.sub-panel {
|
||||
.screen-panel {
|
||||
flex-grow: 1;
|
||||
align-items: center;
|
||||
min-height: 0;
|
||||
background-color: var(--kp-bg-solid);
|
||||
}
|
||||
|
||||
.screen-frame {
|
||||
flex-grow: 1;
|
||||
min-height: 0;
|
||||
background-color: var(--kp-bg-solid);
|
||||
padding: 30px 0 18px 0;
|
||||
}
|
||||
|
||||
.screen-frame-header {
|
||||
min-height: 34px;
|
||||
justify-content: center;
|
||||
padding: 48px;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: var(--kp-border);
|
||||
}
|
||||
|
||||
.screen-frame-title {
|
||||
color: var(--kp-muted);
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.screen-frame-content {
|
||||
flex-grow: 1;
|
||||
min-height: 0;
|
||||
padding-top: 18px;
|
||||
padding-bottom: 14px;
|
||||
}
|
||||
|
||||
.screen-frame-footer {
|
||||
min-height: 32px;
|
||||
justify-content: center;
|
||||
border-top-width: 1px;
|
||||
border-top-color: var(--kp-border);
|
||||
}
|
||||
|
||||
.screen-frame-hint {
|
||||
color: var(--kp-muted);
|
||||
}
|
||||
|
||||
.stub-scroll,
|
||||
.stub-box {
|
||||
width: 560px;
|
||||
height: 300px;
|
||||
flex-grow: 1;
|
||||
min-height: 0;
|
||||
border-left-width: 1px;
|
||||
border-right-width: 1px;
|
||||
border-top-width: 1px;
|
||||
@@ -476,38 +505,55 @@ Button {
|
||||
}
|
||||
|
||||
.collection-shell {
|
||||
width: 760px;
|
||||
height: 330px;
|
||||
flex-grow: 1;
|
||||
min-height: 0;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
#RunSetupContent {
|
||||
flex-grow: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.run-setup-shell {
|
||||
flex-grow: 1;
|
||||
min-height: 0;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.collection-actions {
|
||||
width: 760px;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
justify-content: flex-start;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.collection-action-button {
|
||||
width: 110px;
|
||||
height: 34px;
|
||||
margin-top: 0;
|
||||
margin-left: 8px;
|
||||
.collection-tab {
|
||||
width: 92px;
|
||||
height: 30px;
|
||||
margin-right: 10px;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: transparent;
|
||||
background-color: transparent;
|
||||
color: var(--kp-muted);
|
||||
}
|
||||
|
||||
.collection-action-button.selected {
|
||||
background-color: var(--kp-primary);
|
||||
color: var(--kp-bg);
|
||||
.collection-tab.selected {
|
||||
color: var(--kp-primary);
|
||||
border-bottom-color: var(--kp-primary);
|
||||
}
|
||||
|
||||
.collection-tab:hover {
|
||||
color: var(--kp-primary);
|
||||
}
|
||||
|
||||
.run-setup-hint {
|
||||
width: 760px;
|
||||
margin-top: -12px;
|
||||
margin-top: 0;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.collection-list {
|
||||
width: 300px;
|
||||
width: 38%;
|
||||
min-width: 330px;
|
||||
margin-right: 16px;
|
||||
border-left-width: 1px;
|
||||
border-right-width: 1px;
|
||||
@@ -522,6 +568,8 @@ Button {
|
||||
|
||||
.collection-detail {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
padding: 16px;
|
||||
border-left-width: 1px;
|
||||
border-right-width: 1px;
|
||||
@@ -536,6 +584,7 @@ Button {
|
||||
|
||||
.collection-detail-readout {
|
||||
flex-direction: row;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.collection-detail-values {
|
||||
@@ -543,6 +592,14 @@ Button {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.collection-detail .kv-value {
|
||||
max-width: none;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.collection-row {
|
||||
min-height: 44px;
|
||||
padding: 8px 10px;
|
||||
@@ -574,12 +631,19 @@ Button {
|
||||
.collection-detail-description {
|
||||
color: var(--kp-primary);
|
||||
white-space: normal;
|
||||
margin-top: 14px;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.package-list {
|
||||
margin-top: 18px;
|
||||
min-width: 0;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.package-scroll {
|
||||
flex-grow: 1;
|
||||
min-height: 0;
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.package-header {
|
||||
@@ -633,18 +697,25 @@ Button {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
color: var(--kp-primary);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
-unity-text-overflow-position: end;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.package-meta {
|
||||
min-width: 96px;
|
||||
width: 92px;
|
||||
min-width: 92px;
|
||||
color: var(--kp-muted);
|
||||
-unity-text-align: upper-right;
|
||||
}
|
||||
|
||||
.loadout-card-row .package-name {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.loadout-card-row .package-meta {
|
||||
width: 104px;
|
||||
min-width: 104px;
|
||||
}
|
||||
|
||||
.package-description {
|
||||
min-width: 0;
|
||||
color: var(--kp-muted);
|
||||
@@ -653,6 +724,60 @@ Button {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.run-readout {
|
||||
min-height: 110px;
|
||||
max-height: 128px;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.run-readout .ascii-art,
|
||||
.run-readout .ascii-placeholder {
|
||||
width: 100px;
|
||||
min-width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.run-readout-copy {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
padding-left: 14px;
|
||||
}
|
||||
|
||||
.boot-command-row {
|
||||
min-height: 42px;
|
||||
margin-top: 12px;
|
||||
border-left-color: var(--kp-primary);
|
||||
}
|
||||
|
||||
.boot-command-row.disabled-row {
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
.collection-equip-row {
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
ScrollView .unity-scroll-view__vertical-scroller,
|
||||
ScrollView .unity-scroll-view__horizontal-scroller {
|
||||
width: 6px;
|
||||
background-color: var(--kp-panel);
|
||||
}
|
||||
|
||||
ScrollView .unity-scroller__low-button,
|
||||
ScrollView .unity-scroller__high-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
ScrollView .unity-base-slider__tracker {
|
||||
background-color: var(--kp-panel);
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
ScrollView .unity-base-slider__dragger {
|
||||
background-color: var(--kp-muted);
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.modal-overlay {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
@@ -762,12 +887,8 @@ Button {
|
||||
color: var(--kp-bg);
|
||||
}
|
||||
|
||||
.back-button {
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
.settings-row {
|
||||
width: 420px;
|
||||
width: 100%;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
+14
-5
@@ -1,5 +1,6 @@
|
||||
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">
|
||||
<Style src="MainMenu.uss" />
|
||||
<ui:Template name="ScreenFrame" src="ScreenFrame.uxml" />
|
||||
<ui:Template name="CollectionScreen" src="CollectionScreen.uxml" />
|
||||
<ui:Template name="GachaScreen" src="GachaScreen.uxml" />
|
||||
<ui:Template name="SettingsScreen" src="SettingsScreen.uxml" />
|
||||
@@ -124,14 +125,22 @@
|
||||
</ui:VisualElement>
|
||||
|
||||
<ui:Instance template="CollectionScreen" />
|
||||
<ui:VisualElement name="RunSetupPanel" class="sub-panel hidden">
|
||||
<ui:Label text="NEW RUN" class="screen-title" />
|
||||
<ui:Label text="choose 4 distro cards before booting a run" class="collection-detail-muted run-setup-hint" />
|
||||
<ui:VisualElement name="RunSetupContent" class="collection-shell">
|
||||
<ui:VisualElement name="RunSetupPanel" class="screen-panel hidden">
|
||||
<ui:VisualElement class="screen-frame">
|
||||
<ui:VisualElement class="screen-frame-header">
|
||||
<ui:Label class="screen-frame-title" />
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement class="screen-frame-content">
|
||||
<ui:Label text="choose distro packages before booting a run" class="collection-detail-muted run-setup-hint" />
|
||||
<ui:VisualElement name="RunSetupContent" class="run-setup-shell">
|
||||
<ui:ScrollView name="RunSetupList" class="collection-list" />
|
||||
<ui:VisualElement name="RunSetupDetail" class="collection-detail" />
|
||||
</ui:VisualElement>
|
||||
<ui:Button name="RunSetupBackButton" text="Back" class="terminal-button back-button" />
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement class="screen-frame-footer">
|
||||
<ui:Label class="screen-frame-hint" />
|
||||
</ui:VisualElement>
|
||||
</ui:VisualElement>
|
||||
</ui:VisualElement>
|
||||
<ui:Instance template="GachaScreen" />
|
||||
<ui:Instance template="SettingsScreen" />
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">
|
||||
<ui:VisualElement name="SettingsPanel" class="sub-panel hidden">
|
||||
<ui:Label text="SETTINGS" class="screen-title" />
|
||||
<ui:VisualElement name="SettingsPanel" class="screen-panel hidden">
|
||||
<ui:VisualElement class="screen-frame">
|
||||
<ui:VisualElement class="screen-frame-header">
|
||||
<ui:Label class="screen-frame-title" />
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement class="screen-frame-content">
|
||||
<ui:VisualElement class="settings-row disabled-row">
|
||||
<ui:Label text="Audio" class="settings-label" />
|
||||
<ui:Toggle name="AudioToggle" text="TODO" enabled="false" class="settings-toggle" />
|
||||
</ui:VisualElement>
|
||||
<ui:Button name="SettingsBackButton" text="Back" class="terminal-button back-button" />
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement class="screen-frame-footer">
|
||||
<ui:Label class="screen-frame-hint" />
|
||||
</ui:VisualElement>
|
||||
</ui:VisualElement>
|
||||
</ui:VisualElement>
|
||||
</ui:UXML>
|
||||
|
||||
Reference in New Issue
Block a user