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