diff --git a/Assets/Data/Cards/CardDatabase.asset b/Assets/Data/Cards/CardDatabase.asset
index 14a5f4c..9973287 100644
--- a/Assets/Data/Cards/CardDatabase.asset
+++ b/Assets/Data/Cards/CardDatabase.asset
@@ -19,3 +19,13 @@ MonoBehaviour:
- {fileID: 11400000, guid: 3d57857f3f30419c8be4c44914c5abbe, type: 2}
- {fileID: 11400000, guid: 4cae271a0f4146c59aab74e4c9d6d0f8, type: 2}
- {fileID: 11400000, guid: 5fef058a72b54b4aa2b2dd8f4d08bfe5, type: 2}
+ - {fileID: 11400000, guid: feda0000000000000000000000000001, type: 2}
+ - {fileID: 11400000, guid: feda0000000000000000000000000002, type: 2}
+ - {fileID: 11400000, guid: feda0000000000000000000000000003, type: 2}
+ - {fileID: 11400000, guid: feda0000000000000000000000000004, type: 2}
+ - {fileID: 11400000, guid: feda0000000000000000000000000005, type: 2}
+ - {fileID: 11400000, guid: c1a50000000000000000000000000001, type: 2}
+ - {fileID: 11400000, guid: c1a50000000000000000000000000002, type: 2}
+ - {fileID: 11400000, guid: c1a50000000000000000000000000003, type: 2}
+ - {fileID: 11400000, guid: c1a50000000000000000000000000004, type: 2}
+ - {fileID: 11400000, guid: c1a50000000000000000000000000005, type: 2}
diff --git a/Assets/Data/Cards/Fedora/BorrowChecker.asset b/Assets/Data/Cards/Fedora/BorrowChecker.asset
new file mode 100644
index 0000000..66a6581
--- /dev/null
+++ b/Assets/Data/Cards/Fedora/BorrowChecker.asset
@@ -0,0 +1,27 @@
+%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_GameObject: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 5178747fac4dbfc448ff15d8a687e03c, type: 3}
+ m_Name: BorrowChecker
+ m_EditorClassIdentifier:
+ id: fedora_borrow_checker
+ displayName: borrow checker
+ description: gain 8 shield. if this is the first card played this turn, gain 8 more.
+ flavorText: no aliasing, no panic.
+ language: 2
+ rarity: 0
+ cycleCost: 1
+ resolutionTrack: 0
+ distroExclusive: 1
+ isToken: 0
+ designNotes: defensive first-card payoff; should benefit from Bleeding Edge cost discount but not damage multiplier.
+ upgradeTarget: {fileID: 0}
+ effects: []
diff --git a/Assets/Data/Cards/Fedora/CargoBuildRelease.asset b/Assets/Data/Cards/Fedora/CargoBuildRelease.asset
new file mode 100644
index 0000000..143f232
--- /dev/null
+++ b/Assets/Data/Cards/Fedora/CargoBuildRelease.asset
@@ -0,0 +1,27 @@
+%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: CargoBuildRelease
+ m_EditorClassIdentifier:
+ id: fedora_cargo_build
+ displayName: cargo build --release
+ description: deal 10. never misses. overkill becomes shield.
+ flavorText: optimized, audited, shipped hot.
+ language: 2
+ rarity: 0
+ cycleCost: 1
+ resolutionTrack: 0
+ distroExclusive: 1
+ isToken: 0
+ designNotes: crash-immune Fedora finisher; Rust guaranteed hit; convert excess damage into shield after lethal calculation.
+ upgradeTarget: {fileID: 0}
+ effects: []
diff --git a/Assets/Data/Cards/Fedora/DnfUpdate.asset b/Assets/Data/Cards/Fedora/DnfUpdate.asset
new file mode 100644
index 0000000..2486c05
--- /dev/null
+++ b/Assets/Data/Cards/Fedora/DnfUpdate.asset
@@ -0,0 +1,27 @@
+%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: DnfUpdate
+ m_EditorClassIdentifier:
+ id: fedora_dnf_update
+ displayName: dnf update
+ description: deal 9. costs 1 less per java card played this combat (min 0).
+ flavorText: today everything gets newer.
+ language: 8
+ rarity: 0
+ cycleCost: 2
+ resolutionTrack: 0
+ distroExclusive: 1
+ isToken: 0
+ designNotes: Java ramp card; cost reduction counts prior Java cards played this combat and clamps at 0.
+ upgradeTarget: {fileID: 0}
+ effects: []
diff --git a/Assets/Data/Cards/Fedora/Rawhide.asset b/Assets/Data/Cards/Fedora/Rawhide.asset
new file mode 100644
index 0000000..e439368
--- /dev/null
+++ b/Assets/Data/Cards/Fedora/Rawhide.asset
@@ -0,0 +1,27 @@
+%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: Rawhide
+ m_EditorClassIdentifier:
+ id: fedora_rawhide
+ displayName: rawhide
+ description: draw 2. the next card you play this turn gains bleeding edge's discount and crash risk.
+ flavorText: straight from tomorrow's mirror.
+ language: 8
+ rarity: 1
+ cycleCost: 0
+ resolutionTrack: 0
+ distroExclusive: 1
+ isToken: 0
+ designNotes: Rare setup; grants next-card temporary Bleeding Edge discount plus crash-risk hook even if first-card window has passed.
+ upgradeTarget: {fileID: 0}
+ effects: []
diff --git a/Assets/Data/Cards/Fedora/SELinuxEnforcing.asset b/Assets/Data/Cards/Fedora/SELinuxEnforcing.asset
new file mode 100644
index 0000000..5a95a3e
--- /dev/null
+++ b/Assets/Data/Cards/Fedora/SELinuxEnforcing.asset
@@ -0,0 +1,27 @@
+%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: SELinuxEnforcing
+ m_EditorClassIdentifier:
+ id: fedora_selinux
+ displayName: SELinux enforcing
+ description: the next 2 enemy attacks deal half damage.
+ flavorText: denied means denied.
+ language: 2
+ rarity: 1
+ cycleCost: 2
+ resolutionTrack: 0
+ distroExclusive: 1
+ isToken: 0
+ designNotes: crash-immune Rare mitigation; apply two attack-damage-halving charges to incoming enemy attacks.
+ upgradeTarget: {fileID: 0}
+ effects: []
diff --git a/Assets/Data/Cards/Mint/Cinnamon.asset b/Assets/Data/Cards/Mint/Cinnamon.asset
new file mode 100644
index 0000000..52b3480
--- /dev/null
+++ b/Assets/Data/Cards/Mint/Cinnamon.asset
@@ -0,0 +1,27 @@
+%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: Cinnamon
+ m_EditorClassIdentifier:
+ id: mint_cinnamon
+ displayName: cinnamon
+ description: "queue: gain 3 shield. draw 1."
+ flavorText: a familiar desktop, a calmer turn.
+ language: 3
+ rarity: 0
+ cycleCost: 0
+ resolutionTrack: 1
+ distroExclusive: 1
+ isToken: 0
+ designNotes: simple deterministic queue value; shield plus card replacement on resolve.
+ upgradeTarget: {fileID: 0}
+ effects: []
diff --git a/Assets/Data/Cards/Mint/FixBroken.asset b/Assets/Data/Cards/Mint/FixBroken.asset
new file mode 100644
index 0000000..7c3b795
--- /dev/null
+++ b/Assets/Data/Cards/Mint/FixBroken.asset
@@ -0,0 +1,27 @@
+%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: FixBroken
+ m_EditorClassIdentifier:
+ id: mint_fix_broken
+ displayName: apt --fix-broken
+ description: deal exactly 8. remove one debuff from mint.
+ flavorText: repair the package, keep moving.
+ language: 4
+ rarity: 0
+ cycleCost: 1
+ resolutionTrack: 0
+ distroExclusive: 1
+ isToken: 0
+ designNotes: fixed JavaScript damage for Mint plus one self-cleanse; bypass spike-damage systems.
+ upgradeTarget: {fileID: 0}
+ effects: []
diff --git a/Assets/Data/Cards/Mint/Nemo.asset b/Assets/Data/Cards/Mint/Nemo.asset
new file mode 100644
index 0000000..7ec9786
--- /dev/null
+++ b/Assets/Data/Cards/Mint/Nemo.asset
@@ -0,0 +1,27 @@
+%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: Nemo
+ m_EditorClassIdentifier:
+ id: mint_nemo
+ displayName: nemo
+ description: deal exactly 6. if mint has any shield, deal exactly 6 again.
+ flavorText: open folder, solve problem.
+ language: 4
+ rarity: 0
+ cycleCost: 0
+ resolutionTrack: 0
+ distroExclusive: 1
+ isToken: 0
+ designNotes: fixed JavaScript damage; conditional second fixed hit if Mint currently has shield.
+ upgradeTarget: {fileID: 0}
+ effects: []
diff --git a/Assets/Data/Cards/Mint/TimeshiftSnapshot.asset b/Assets/Data/Cards/Mint/TimeshiftSnapshot.asset
new file mode 100644
index 0000000..ded6b97
--- /dev/null
+++ b/Assets/Data/Cards/Mint/TimeshiftSnapshot.asset
@@ -0,0 +1,27 @@
+%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: TimeshiftSnapshot
+ m_EditorClassIdentifier:
+ id: mint_timeshift
+ displayName: timeshift snapshot
+ description: "queue: record uptime now. at end of your next turn, restore to it if lower."
+ flavorText: save point created before touching anything scary.
+ language: 3
+ rarity: 1
+ cycleCost: 1
+ resolutionTrack: 1
+ distroExclusive: 1
+ isToken: 0
+ designNotes: deterministic safety queue; record current uptime and restore at delayed checkpoint only if current uptime is lower.
+ upgradeTarget: {fileID: 0}
+ effects: []
diff --git a/Assets/Data/Cards/Mint/UpdateManager.asset b/Assets/Data/Cards/Mint/UpdateManager.asset
new file mode 100644
index 0000000..670122b
--- /dev/null
+++ b/Assets/Data/Cards/Mint/UpdateManager.asset
@@ -0,0 +1,27 @@
+%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: UpdateManager
+ m_EditorClassIdentifier:
+ id: mint_update_manager
+ displayName: update manager
+ description: "queue: deal exactly 5 to all enemies. repeats at the end of your next turn."
+ flavorText: green checks all the way down.
+ language: 3
+ rarity: 1
+ cycleCost: 1
+ resolutionTrack: 1
+ distroExclusive: 1
+ isToken: 0
+ designNotes: Rare deterministic queue aoe; schedules one identical delayed repeat at next-turn end.
+ upgradeTarget: {fileID: 0}
+ effects: []
diff --git a/Assets/Data/Cards/Ubuntu/AskUbuntu.asset b/Assets/Data/Cards/Ubuntu/AskUbuntu.asset
index 5b1d630..9806cf5 100644
--- a/Assets/Data/Cards/Ubuntu/AskUbuntu.asset
+++ b/Assets/Data/Cards/Ubuntu/AskUbuntu.asset
@@ -15,6 +15,7 @@ MonoBehaviour:
id: ubuntu_ask_ubuntu
displayName: ask ubuntu
description: "queue: draw 2 cards."
+ flavorText: somebody solved this in 2014.
language: 3
rarity: 0
cycleCost: 0
diff --git a/Assets/Data/Cards/Ubuntu/DoReleaseUpgrade.asset b/Assets/Data/Cards/Ubuntu/DoReleaseUpgrade.asset
index c0f8d78..e88d949 100644
--- a/Assets/Data/Cards/Ubuntu/DoReleaseUpgrade.asset
+++ b/Assets/Data/Cards/Ubuntu/DoReleaseUpgrade.asset
@@ -15,6 +15,7 @@ MonoBehaviour:
id: ubuntu_do_release_upgrade
displayName: do-release-upgrade
description: upgrade 1-3 random cards in your hand this combat.
+ flavorText: this will probably be fine.
language: 4
rarity: 1
cycleCost: 2
diff --git a/Assets/Data/Cards/Ubuntu/SnapInstall.asset b/Assets/Data/Cards/Ubuntu/SnapInstall.asset
index 0657442..974d7d6 100644
--- a/Assets/Data/Cards/Ubuntu/SnapInstall.asset
+++ b/Assets/Data/Cards/Ubuntu/SnapInstall.asset
@@ -15,6 +15,7 @@ MonoBehaviour:
id: ubuntu_snap_install
displayName: snap install
description: deal 6-14 damage. shuffle an ubuntu pro trial into your draw pile.
+ flavorText: convenient, somehow heavier than expected.
language: 4
rarity: 0
cycleCost: 0
diff --git a/Assets/Data/Cards/Ubuntu/SudoAptInstall.asset b/Assets/Data/Cards/Ubuntu/SudoAptInstall.asset
index 883dab3..8f46627 100644
--- a/Assets/Data/Cards/Ubuntu/SudoAptInstall.asset
+++ b/Assets/Data/Cards/Ubuntu/SudoAptInstall.asset
@@ -15,6 +15,7 @@ MonoBehaviour:
id: ubuntu_apt_install
displayName: sudo apt install
description: "queue: add a random common python card to your hand. it costs 0 this combat."
+ flavorText: pull it from the repo and pretend it was always there.
language: 3
rarity: 0
cycleCost: 1
diff --git a/Assets/Data/Cards/Ubuntu/UbuntuProTrial.asset b/Assets/Data/Cards/Ubuntu/UbuntuProTrial.asset
index 285c4f5..09aaa63 100644
--- a/Assets/Data/Cards/Ubuntu/UbuntuProTrial.asset
+++ b/Assets/Data/Cards/Ubuntu/UbuntuProTrial.asset
@@ -15,6 +15,7 @@ MonoBehaviour:
id: ubuntu_pro_trial
displayName: ubuntu pro trial
description: unplayable. exhausts at end of combat.
+ flavorText: upgrade to unlock this feature.
language: 3
rarity: 0
cycleCost: 0
diff --git a/Assets/Data/Cards/Ubuntu/UnattendedUpgrades.asset b/Assets/Data/Cards/Ubuntu/UnattendedUpgrades.asset
index d15d2be..3a2f316 100644
--- a/Assets/Data/Cards/Ubuntu/UnattendedUpgrades.asset
+++ b/Assets/Data/Cards/Ubuntu/UnattendedUpgrades.asset
@@ -15,6 +15,7 @@ MonoBehaviour:
id: ubuntu_unattended_upgrades
displayName: unattended-upgrades
description: gain 4 shield now and at the end of your next 2 turns.
+ flavorText: the machine fixes itself while you sleep.
language: 3
rarity: 0
cycleCost: 1
diff --git a/Assets/Data/Distros/Fedora.asset b/Assets/Data/Distros/Fedora.asset
index 69cf57b..799f8ae 100644
--- a/Assets/Data/Distros/Fedora.asset
+++ b/Assets/Data/Distros/Fedora.asset
@@ -16,11 +16,22 @@ MonoBehaviour:
displayName: Fedora
primaryLanguage: 2
secondaryLanguage: 8
- passiveName: Bleeding Edge
+ passive: {fileID: 11400000, guid: a1100000000000000000000000000002, type: 2}
description: First to every new feature. Occasionally first to the bugs that come with it.
+ playstyleSummary: high-tempo gambler. her first play each turn is pushed hard, but bleeding edge bites back. rust cards are her safety net.
+ languageBlurbs:
+ - lang: 2
+ blurb: guaranteed damage that never misses; overkill turns to shield.
+ - lang: 8
+ blurb: expensive early, ramps cheaper the longer the fight runs.
asciiArt: {fileID: 4900000, guid: f2373174b6954f2d9088d83d10e0d9e4, type: 3}
accentColor: {r: 0.23529412, g: 0.43137255, b: 0.7058824, a: 1}
baseUptime: 55
baseRam: 5
baseCyclesPerTurn: 3
- exclusiveCards: []
+ exclusiveCards:
+ - {fileID: 11400000, guid: feda0000000000000000000000000001, type: 2}
+ - {fileID: 11400000, guid: feda0000000000000000000000000002, type: 2}
+ - {fileID: 11400000, guid: feda0000000000000000000000000003, type: 2}
+ - {fileID: 11400000, guid: feda0000000000000000000000000004, type: 2}
+ - {fileID: 11400000, guid: feda0000000000000000000000000005, type: 2}
diff --git a/Assets/Data/Distros/Mint.asset b/Assets/Data/Distros/Mint.asset
index 2cb1df9..ec81959 100644
--- a/Assets/Data/Distros/Mint.asset
+++ b/Assets/Data/Distros/Mint.asset
@@ -16,11 +16,22 @@ MonoBehaviour:
displayName: Mint
primaryLanguage: 3
secondaryLanguage: 4
- passiveName: It Just Works
+ passive: {fileID: 11400000, guid: a1100000000000000000000000000003, type: 2}
description: Comfort incarnate. No surprises, no drama, ever.
+ playstyleSummary: zero-variance comfort. every number lands at its ceiling, so you always know exactly what a turn does -- at the cost of a high-roll upside.
+ languageBlurbs:
+ - lang: 3
+ blurb: cheap, flexible cards that resolve at end of turn.
+ - lang: 4
+ blurb: for mint, fixed and reliable -- no chaos, no ranges.
asciiArt: {fileID: 4900000, guid: 412c9be402ec4d8c8510e71066ec22ff, type: 3}
accentColor: {r: 0.5294118, g: 0.8117647, b: 0.24313726, a: 1}
baseUptime: 65
baseRam: 5
baseCyclesPerTurn: 3
- exclusiveCards: []
+ exclusiveCards:
+ - {fileID: 11400000, guid: c1a50000000000000000000000000001, type: 2}
+ - {fileID: 11400000, guid: c1a50000000000000000000000000002, type: 2}
+ - {fileID: 11400000, guid: c1a50000000000000000000000000003, type: 2}
+ - {fileID: 11400000, guid: c1a50000000000000000000000000004, type: 2}
+ - {fileID: 11400000, guid: c1a50000000000000000000000000005, type: 2}
diff --git a/Assets/Data/Distros/Ubuntu.asset b/Assets/Data/Distros/Ubuntu.asset
index eb51237..7189990 100644
--- a/Assets/Data/Distros/Ubuntu.asset
+++ b/Assets/Data/Distros/Ubuntu.asset
@@ -16,8 +16,14 @@ MonoBehaviour:
displayName: Ubuntu
primaryLanguage: 3
secondaryLanguage: 4
- passiveName: Long Term Support
+ passive: {fileID: 11400000, guid: a1100000000000000000000000000001, type: 2}
description: The friendly one. Everything works out of the box, and she will not stop helping.
+ playstyleSummary: an accessible engine that never runs dry -- cheap python cards and a passive that always finds you the right package.
+ languageBlurbs:
+ - lang: 3
+ blurb: cheap, flexible cards that resolve at end of turn.
+ - lang: 4
+ blurb: instant hits with wild variance and odd side effects.
asciiArt: {fileID: 4900000, guid: 975f1f5de289435eb81c91c3eddd31dc, type: 3}
accentColor: {r: 0.9137255, g: 0.32941177, b: 0.1254902, a: 1}
baseUptime: 60
diff --git a/Assets/Scripts/Data/CardDatabase.cs b/Assets/Scripts/Data/CardDatabase.cs
new file mode 100644
index 0000000..467bd59
--- /dev/null
+++ b/Assets/Scripts/Data/CardDatabase.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace KernelPanic.Data
+{
+ ///
+ /// Resolves persisted card ids to immutable card definitions.
+ ///
+ public sealed class CardDatabase : ScriptableObject
+ {
+ [SerializeField] private List allCards = new();
+
+ public IReadOnlyList AllCards => allCards;
+
+ public CardDefinition FindById(string id)
+ {
+ if (string.IsNullOrWhiteSpace(id))
+ {
+ return null;
+ }
+
+ for (int i = 0; i < allCards.Count; i++)
+ {
+ CardDefinition card = allCards[i];
+ if (card != null && string.Equals(card.Id, id, StringComparison.OrdinalIgnoreCase))
+ {
+ return card;
+ }
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/Assets/Scripts/Data/CardDefinition.cs b/Assets/Scripts/Data/CardDefinition.cs
index 37dfd79..199fed4 100644
--- a/Assets/Scripts/Data/CardDefinition.cs
+++ b/Assets/Scripts/Data/CardDefinition.cs
@@ -12,6 +12,7 @@ namespace KernelPanic.Data
[SerializeField] private string id;
[SerializeField] private string displayName;
[SerializeField] private string description;
+ [SerializeField] private string flavorText;
[SerializeField] private Language language;
[SerializeField] private Rarity rarity;
[SerializeField] private int cycleCost;
@@ -25,6 +26,7 @@ namespace KernelPanic.Data
public string Id => id;
public string DisplayName => displayName;
public string Description => description;
+ public string FlavorText => flavorText;
public Language Language => language;
public Rarity Rarity => rarity;
public int CycleCost => cycleCost;
diff --git a/Assets/Scripts/Data/DistroDefinition.cs b/Assets/Scripts/Data/DistroDefinition.cs
index 8adda18..dc00df3 100644
--- a/Assets/Scripts/Data/DistroDefinition.cs
+++ b/Assets/Scripts/Data/DistroDefinition.cs
@@ -9,12 +9,24 @@ namespace KernelPanic.Data
///
public sealed class DistroDefinition : ScriptableObject
{
+ [System.Serializable]
+ public struct LanguageBlurb
+ {
+ [SerializeField] private Language lang;
+ [SerializeField] private string blurb;
+
+ public Language Lang => lang;
+ public string Blurb => blurb;
+ }
+
[SerializeField] private string id;
[SerializeField] private string displayName;
[SerializeField] private Language primaryLanguage;
[SerializeField] private Language secondaryLanguage;
- [SerializeField] private string passiveName;
+ [SerializeField] private PassiveDefinition passive;
[SerializeField, TextArea] private string description;
+ [SerializeField, TextArea] private string playstyleSummary;
+ [SerializeField] private List languageBlurbs = new();
[SerializeField] private TextAsset asciiArt;
[SerializeField] private Color accentColor = new(0.36f, 1f, 0.57f);
[SerializeField] private int baseUptime;
@@ -26,8 +38,10 @@ namespace KernelPanic.Data
public string DisplayName => displayName;
public Language PrimaryLanguage => primaryLanguage;
public Language SecondaryLanguage => secondaryLanguage;
- public string PassiveName => passiveName;
+ public PassiveDefinition Passive => passive;
public string Description => description;
+ public string PlaystyleSummary => playstyleSummary;
+ public IReadOnlyList LanguageBlurbs => languageBlurbs;
public TextAsset AsciiArt => asciiArt;
public Color AccentColor => accentColor;
public int BaseUptime => baseUptime;
diff --git a/Assets/Scripts/Data/PassiveDefinition.cs b/Assets/Scripts/Data/PassiveDefinition.cs
new file mode 100644
index 0000000..2d42236
--- /dev/null
+++ b/Assets/Scripts/Data/PassiveDefinition.cs
@@ -0,0 +1,22 @@
+using UnityEngine;
+
+namespace KernelPanic.Data
+{
+ ///
+ /// Defines immutable passive rules and presentation text for a distro.
+ ///
+ public sealed class PassiveDefinition : ScriptableObject
+ {
+ [SerializeField] private string id;
+ [SerializeField] private string displayName;
+ [SerializeField, TextArea] private string rulesText;
+ [SerializeField] private string flavorText;
+ [SerializeField, TextArea] private string designNotes;
+
+ public string Id => id;
+ public string Name => displayName;
+ public string RulesText => rulesText;
+ public string FlavorText => flavorText;
+ public string DesignNotes => designNotes;
+ }
+}
diff --git a/Assets/Scripts/Meta/CardLoadout.cs b/Assets/Scripts/Meta/CardLoadout.cs
new file mode 100644
index 0000000..572e918
--- /dev/null
+++ b/Assets/Scripts/Meta/CardLoadout.cs
@@ -0,0 +1,204 @@
+using System;
+using System.Collections.Generic;
+using KernelPanic.Data;
+
+namespace KernelPanic.Meta
+{
+ public enum CardLoadoutFailureReason
+ {
+ None,
+ Full,
+ NotOwned,
+ Token,
+ Duplicate,
+ NotEquipped
+ }
+
+ ///
+ /// Tracks equipped exclusive cards per owned distro without persistence or logging.
+ ///
+ public sealed class CardLoadout
+ {
+ public const int MaxEquippedCards = 4;
+
+ private readonly IReadOnlyList ownedUnits;
+ private readonly Dictionary> equippedByDistro = new(StringComparer.OrdinalIgnoreCase);
+
+ public CardLoadout(IReadOnlyList ownedUnits)
+ {
+ this.ownedUnits = ownedUnits ?? Array.Empty();
+ }
+
+ public IReadOnlyList GetEquippedCardIds(string distroId)
+ {
+ return equippedByDistro.TryGetValue(distroId, out List equipped)
+ ? equipped
+ : Array.Empty();
+ }
+
+ public bool HasLoadout(string distroId)
+ {
+ return !string.IsNullOrWhiteSpace(distroId) && equippedByDistro.ContainsKey(distroId);
+ }
+
+ public void EnsureLoadout(DistroDefinition distro)
+ {
+ if (distro == null || string.IsNullOrWhiteSpace(distro.Id) || HasLoadout(distro.Id))
+ {
+ return;
+ }
+
+ equippedByDistro[distro.Id] = new List();
+ }
+
+ public void ClearAll()
+ {
+ equippedByDistro.Clear();
+ }
+
+ public void ClearLoadout(DistroDefinition distro)
+ {
+ if (distro == null || string.IsNullOrWhiteSpace(distro.Id))
+ {
+ return;
+ }
+
+ equippedByDistro[distro.Id] = new List();
+ }
+
+ public bool TryLoad(string distroId, IEnumerable cardIds, out bool skippedInvalid)
+ {
+ skippedInvalid = false;
+ DistroDefinition distro = FindOwnedDistro(distroId);
+ if (distro == null)
+ {
+ skippedInvalid = true;
+ return false;
+ }
+
+ List equipped = new();
+ foreach (string cardId in cardIds ?? Array.Empty())
+ {
+ CardDefinition card = FindExclusiveCard(distro, cardId);
+ if (card == null || card.IsToken || equipped.Contains(card.Id))
+ {
+ skippedInvalid = true;
+ continue;
+ }
+
+ if (equipped.Count < MaxEquippedCards)
+ {
+ equipped.Add(card.Id);
+ }
+ else
+ {
+ skippedInvalid = true;
+ }
+ }
+
+ equippedByDistro[distro.Id] = equipped;
+ return true;
+ }
+
+ public bool TryEquip(string distroId, string cardId, out CardLoadoutFailureReason reason)
+ {
+ DistroDefinition distro = FindOwnedDistro(distroId);
+ if (distro == null)
+ {
+ reason = CardLoadoutFailureReason.NotOwned;
+ return false;
+ }
+
+ CardDefinition card = FindExclusiveCard(distro, cardId);
+ if (card == null)
+ {
+ reason = CardLoadoutFailureReason.NotOwned;
+ return false;
+ }
+
+ if (card.IsToken)
+ {
+ reason = CardLoadoutFailureReason.Token;
+ return false;
+ }
+
+ EnsureLoadout(distro);
+ List equipped = equippedByDistro[distro.Id];
+ if (equipped.Contains(card.Id))
+ {
+ reason = CardLoadoutFailureReason.Duplicate;
+ return false;
+ }
+
+ if (equipped.Count >= MaxEquippedCards)
+ {
+ reason = CardLoadoutFailureReason.Full;
+ return false;
+ }
+
+ equipped.Add(card.Id);
+ reason = CardLoadoutFailureReason.None;
+ return true;
+ }
+
+ public bool TryUnequip(string distroId, string cardId, out CardLoadoutFailureReason reason)
+ {
+ DistroDefinition distro = FindOwnedDistro(distroId);
+ if (distro == null)
+ {
+ reason = CardLoadoutFailureReason.NotOwned;
+ return false;
+ }
+
+ EnsureLoadout(distro);
+ List equipped = equippedByDistro[distro.Id];
+ if (!equipped.Remove(cardId))
+ {
+ reason = CardLoadoutFailureReason.NotEquipped;
+ return false;
+ }
+
+ reason = CardLoadoutFailureReason.None;
+ return true;
+ }
+
+ private DistroDefinition FindOwnedDistro(string distroId)
+ {
+ if (string.IsNullOrWhiteSpace(distroId))
+ {
+ return null;
+ }
+
+ for (int i = 0; i < ownedUnits.Count; i++)
+ {
+ DistroDefinition distro = ownedUnits[i];
+ if (distro != null && string.Equals(distro.Id, distroId, StringComparison.OrdinalIgnoreCase))
+ {
+ return distro;
+ }
+ }
+
+ return null;
+ }
+
+ private static CardDefinition FindExclusiveCard(DistroDefinition distro, string cardId)
+ {
+ if (distro == null || string.IsNullOrWhiteSpace(cardId))
+ {
+ return null;
+ }
+
+ for (int i = 0; i < distro.ExclusiveCards.Count; i++)
+ {
+ CardDefinition card = distro.ExclusiveCards[i];
+ if (card != null && string.Equals(card.Id, cardId, StringComparison.OrdinalIgnoreCase))
+ {
+ return card;
+ }
+ }
+
+ return null;
+ }
+
+ }
+}
diff --git a/Assets/Scripts/UI/CollectionScreenController.cs b/Assets/Scripts/UI/CollectionScreenController.cs
index bf849b5..6dadf76 100644
--- a/Assets/Scripts/UI/CollectionScreenController.cs
+++ b/Assets/Scripts/UI/CollectionScreenController.cs
@@ -167,6 +167,7 @@ namespace KernelPanic.UI
Label description = new(string.IsNullOrWhiteSpace(unit.Description) ? "--" : unit.Description);
description.AddToClassList("collection-detail-description");
detail.Add(description);
+ AddPassiveDetails(detail, unit);
VisualElement readout = new();
readout.AddToClassList("collection-detail-readout");
@@ -183,7 +184,6 @@ namespace KernelPanic.UI
details.AddToClassList("collection-detail-values");
details.Add(BuildDetailLine("lang", DistroPresentation.FormatLanguages(unit)));
- details.Add(BuildDetailLine("passive", string.IsNullOrWhiteSpace(unit.PassiveName) ? "--" : unit.PassiveName));
details.Add(BuildDetailLine("uptime", unit.BaseUptime.ToString()));
details.Add(BuildDetailLine("ram", unit.BaseRam.ToString()));
details.Add(BuildDetailLine("cycles", unit.BaseCyclesPerTurn.ToString()));
@@ -204,6 +204,12 @@ namespace KernelPanic.UI
Label description = new(card == null || string.IsNullOrWhiteSpace(card.Description) ? "--" : card.Description);
description.AddToClassList("collection-detail-description");
detail.Add(description);
+ if (card != null && !string.IsNullOrWhiteSpace(card.FlavorText))
+ {
+ Label flavor = new(card.FlavorText);
+ flavor.AddToClassList("flavor-text");
+ detail.Add(flavor);
+ }
detail.Add(BuildDetailLine("owner", DistroPresentation.DisplayName(entry.Owner)));
detail.Add(BuildDetailLine("lang", card == null ? "--" : card.Language.ToString()));
@@ -227,6 +233,29 @@ namespace KernelPanic.UI
return row;
}
+ private static void AddPassiveDetails(VisualElement target, DistroDefinition unit)
+ {
+ if (unit.Passive == null)
+ {
+ return;
+ }
+
+ Label passiveTitle = new(unit.Passive.Name);
+ passiveTitle.AddToClassList("detail-section-title");
+ target.Add(passiveTitle);
+
+ Label passiveRules = new(unit.Passive.RulesText);
+ passiveRules.AddToClassList("collection-detail-description");
+ target.Add(passiveRules);
+
+ if (!string.IsNullOrWhiteSpace(unit.Passive.FlavorText))
+ {
+ Label passiveFlavor = new(unit.Passive.FlavorText);
+ passiveFlavor.AddToClassList("flavor-text");
+ target.Add(passiveFlavor);
+ }
+ }
+
private static IReadOnlyList BuildCardEntries(DistroDefinition featuredUnit)
{
List entries = new();
diff --git a/Assets/Scripts/UI/FeaturedUnitPanel.cs b/Assets/Scripts/UI/FeaturedUnitPanel.cs
index 6b874c2..481f45d 100644
--- a/Assets/Scripts/UI/FeaturedUnitPanel.cs
+++ b/Assets/Scripts/UI/FeaturedUnitPanel.cs
@@ -130,7 +130,7 @@ namespace KernelPanic.UI
asciiFitter.SetArt(DistroArtPresenter.Render(asciiLabel, asciiPlaceholder, unit));
unitNameLabel.text = DistroPresentation.DisplayName(unit);
languagesLabel.text = DistroPresentation.FormatLanguages(unit);
- passiveLabel.text = string.IsNullOrWhiteSpace(unit.PassiveName) ? "--" : unit.PassiveName;
+ passiveLabel.text = unit.Passive == null || string.IsNullOrWhiteSpace(unit.Passive.Name) ? "--" : unit.Passive.Name;
bestWaveLabel.text = "--"; // TODO: Bind best-wave stats from SaveService when stats exist.
SetAccent(ColorUtility.ToHtmlStringRGB(unit.AccentColor));
}
diff --git a/Assets/Scripts/UI/MainMenuController.cs b/Assets/Scripts/UI/MainMenuController.cs
index 461026f..c02d915 100644
--- a/Assets/Scripts/UI/MainMenuController.cs
+++ b/Assets/Scripts/UI/MainMenuController.cs
@@ -206,16 +206,28 @@ namespace KernelPanic.UI
{
root.RegisterCallback(HandleKeyDown);
root.RegisterCallback(HandlePointerDown);
- collectionUnitsButton.clicked += ShowCollectionUnits;
- collectionCardsButton.clicked += ShowCollectionCards;
+ collectionUnitsButton.RegisterCallback(HandleCollectionUnitsTabClicked);
+ collectionCardsButton.RegisterCallback(HandleCollectionCardsTabClicked);
}
private void UnregisterCallbacks()
{
root.UnregisterCallback(HandleKeyDown);
root.UnregisterCallback(HandlePointerDown);
- collectionUnitsButton.clicked -= ShowCollectionUnits;
- collectionCardsButton.clicked -= ShowCollectionCards;
+ collectionUnitsButton.UnregisterCallback(HandleCollectionUnitsTabClicked);
+ collectionCardsButton.UnregisterCallback(HandleCollectionCardsTabClicked);
+ }
+
+ private void HandleCollectionUnitsTabClicked(ClickEvent evt)
+ {
+ ShowCollectionUnits();
+ evt.StopPropagation();
+ }
+
+ private void HandleCollectionCardsTabClicked(ClickEvent evt)
+ {
+ ShowCollectionCards();
+ evt.StopPropagation();
}
private void LoadMetaState()
@@ -599,7 +611,8 @@ namespace KernelPanic.UI
summary.Add(meta);
row.Add(summary);
- row.Add(new Label(string.IsNullOrWhiteSpace(unit.PassiveName) ? "passive: --" : $"passive: {unit.PassiveName}")
+ string passiveTitle = unit.Passive == null || string.IsNullOrWhiteSpace(unit.Passive.Name) ? "--" : unit.Passive.Name;
+ row.Add(new Label($"passive: {passiveTitle}")
{
name = $"RunSetupDescription{index}"
});
@@ -724,10 +737,46 @@ namespace KernelPanic.UI
stats.AddToClassList("collection-detail-muted");
copy.Add(stats);
+ AddDistroTeachingDetails(copy, unit);
+
readout.Add(copy);
return readout;
}
+ private static void AddDistroTeachingDetails(VisualElement target, DistroDefinition unit)
+ {
+ if (unit.Passive != null)
+ {
+ Label passiveTitle = new(unit.Passive.Name);
+ passiveTitle.AddToClassList("detail-section-title");
+ target.Add(passiveTitle);
+
+ Label passiveRules = new(unit.Passive.RulesText);
+ passiveRules.AddToClassList("collection-detail-description");
+ target.Add(passiveRules);
+
+ if (!string.IsNullOrWhiteSpace(unit.Passive.FlavorText))
+ {
+ Label passiveFlavor = new(unit.Passive.FlavorText);
+ passiveFlavor.AddToClassList("flavor-text");
+ target.Add(passiveFlavor);
+ }
+ }
+
+ if (!string.IsNullOrWhiteSpace(unit.PlaystyleSummary))
+ {
+ Label playstyle = new(unit.PlaystyleSummary);
+ playstyle.AddToClassList("collection-detail-description");
+ target.Add(playstyle);
+ }
+
+ for (int i = 0; i < unit.LanguageBlurbs.Count; i++)
+ {
+ DistroDefinition.LanguageBlurb blurb = unit.LanguageBlurbs[i];
+ target.Add(BuildDetailLine(blurb.Lang.ToString(), blurb.Blurb));
+ }
+ }
+
private static VisualElement BuildDetailLine(string key, string value)
{
VisualElement row = new();
@@ -767,6 +816,12 @@ namespace KernelPanic.UI
row.ElementAt(1).AddToClassList("package-description");
}
+ if (!string.IsNullOrWhiteSpace(card.FlavorText))
+ {
+ row.Add(new Label(card.FlavorText) { name = "RunSetupCardFlavor" });
+ row.ElementAt(row.childCount - 1).AddToClassList("package-flavor");
+ }
+
return row;
}
diff --git a/Assets/Scripts/UI/ScreenFrameController.cs b/Assets/Scripts/UI/ScreenFrameController.cs
new file mode 100644
index 0000000..3f310c2
--- /dev/null
+++ b/Assets/Scripts/UI/ScreenFrameController.cs
@@ -0,0 +1,47 @@
+using System;
+using UnityEngine.UIElements;
+
+namespace KernelPanic.UI
+{
+ ///
+ /// Binds shared command-style chrome used by terminal sub-screens.
+ ///
+ public sealed class ScreenFrameController
+ {
+ private Label titleLabel;
+ private Label hintLabel;
+
+ public void Bind(VisualElement panel, string title, string hint, Action onBack)
+ {
+ titleLabel = panel.Q