From 54906bbba10bc655fad7cca3669cbc845da6d519 Mon Sep 17 00:00:00 2001 From: fretuks Date: Wed, 8 Jul 2026 11:30:32 +0200 Subject: [PATCH] Implement Java and Rust cards alongside some smaller changes --- .../.idea/workspace.xml | 145 +++++++++++------- Assets/Data/Cards/CardDatabase.asset | 6 + Assets/Data/Cards/Fedora/BorrowChecker.asset | 12 +- .../Data/Cards/Fedora/CargoBuildRelease.asset | 14 +- .../Cards/Languages/Java/JavaNewObject.asset | 29 ++++ .../Languages/Java/JavaPublicStaticMain.asset | 29 ++++ .../Languages/Java/JavaSystemOutPrintln.asset | 29 ++++ Assets/Data/Cards/Languages/JavaDeck.asset | 22 +++ .../Languages/LanguageDeckDatabase.asset | 2 + .../Data/Cards/Languages/Rust/RsBorrow.asset | 29 ++++ .../Data/Cards/Languages/Rust/RsFnMain.asset | 29 ++++ .../Data/Cards/Languages/Rust/RsUnwrap.asset | 29 ++++ Assets/Data/Cards/Languages/RustDeck.asset | 22 +++ Assets/Data/Distros/Fedora.asset | 2 +- Assets/Scripts/Combat/CardEffects.cs | 136 ++++++++++++++-- Assets/Scripts/Combat/CardInstance.cs | 9 ++ Assets/Scripts/Combat/CombatManager.cs | 49 +++++- Assets/Scripts/Combat/HandController.cs | 23 ++- Assets/Scripts/Data/LanguageCatalog.cs | 4 +- Assets/Scripts/UI/CombatSceneController.cs | 4 +- 20 files changed, 531 insertions(+), 93 deletions(-) create mode 100644 Assets/Data/Cards/Languages/Java/JavaNewObject.asset create mode 100644 Assets/Data/Cards/Languages/Java/JavaPublicStaticMain.asset create mode 100644 Assets/Data/Cards/Languages/Java/JavaSystemOutPrintln.asset create mode 100644 Assets/Data/Cards/Languages/JavaDeck.asset create mode 100644 Assets/Data/Cards/Languages/Rust/RsBorrow.asset create mode 100644 Assets/Data/Cards/Languages/Rust/RsFnMain.asset create mode 100644 Assets/Data/Cards/Languages/Rust/RsUnwrap.asset create mode 100644 Assets/Data/Cards/Languages/RustDeck.asset diff --git a/.idea/.idea.linuxdistrosbuttheyarebreedable/.idea/workspace.xml b/.idea/.idea.linuxdistrosbuttheyarebreedable/.idea/workspace.xml index 246732b..4fefd46 100644 --- a/.idea/.idea.linuxdistrosbuttheyarebreedable/.idea/workspace.xml +++ b/.idea/.idea.linuxdistrosbuttheyarebreedable/.idea/workspace.xml @@ -11,19 +11,26 @@ - - - - - + + + + + + + + - - - + + + + + + + + + + - - - + + + + @@ -80,23 +91,23 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + @@ -106,35 +117,35 @@ - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + { @@ -223,7 +234,7 @@ - + - @@ -420,7 +447,9 @@ - diff --git a/Assets/Data/Cards/CardDatabase.asset b/Assets/Data/Cards/CardDatabase.asset index 7f50cb0..d00804e 100644 --- a/Assets/Data/Cards/CardDatabase.asset +++ b/Assets/Data/Cards/CardDatabase.asset @@ -41,3 +41,9 @@ MonoBehaviour: - {fileID: 11400000, guid: a7d50000000000000000000000000004, type: 2} - {fileID: 11400000, guid: a7d50000000000000000000000000005, type: 2} - {fileID: 11400000, guid: a7d50000000000000000000000000006, type: 2} + - {fileID: 11400000, guid: a7d60000000000000000000000000001, type: 2} + - {fileID: 11400000, guid: a7d60000000000000000000000000002, type: 2} + - {fileID: 11400000, guid: a7d60000000000000000000000000003, type: 2} + - {fileID: 11400000, guid: a7d60000000000000000000000000004, type: 2} + - {fileID: 11400000, guid: a7d60000000000000000000000000005, type: 2} + - {fileID: 11400000, guid: a7d60000000000000000000000000006, type: 2} diff --git a/Assets/Data/Cards/Fedora/BorrowChecker.asset b/Assets/Data/Cards/Fedora/BorrowChecker.asset index 66a6581..8de8f8f 100644 --- a/Assets/Data/Cards/Fedora/BorrowChecker.asset +++ b/Assets/Data/Cards/Fedora/BorrowChecker.asset @@ -10,18 +10,18 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 5178747fac4dbfc448ff15d8a687e03c, type: 3} - m_Name: BorrowChecker + m_Name: DnfAutoremove 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. + id: fedora_dnf_autoremove + displayName: dnf autoremove + description: deal 7, then exhaust a junk/NOP/broken card from your hand. if you do, gain 1 Cycle. if you don't, do nothing. + flavorText: cleaning out orphaned packages. 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. + designNotes: Fedora keeps a tidy system; requires an orphaned token/junk/NOP/broken card in hand, otherwise the card has no effect. upgradeTarget: {fileID: 0} effects: [] diff --git a/Assets/Data/Cards/Fedora/CargoBuildRelease.asset b/Assets/Data/Cards/Fedora/CargoBuildRelease.asset index 143f232..744979b 100644 --- a/Assets/Data/Cards/Fedora/CargoBuildRelease.asset +++ b/Assets/Data/Cards/Fedora/CargoBuildRelease.asset @@ -10,18 +10,18 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 5178747fac4dbfc448ff15d8a687e03c, type: 3} - m_Name: CargoBuildRelease + m_Name: FirstToPackage 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 + id: fedora_first_to_package + displayName: first-to-package + description: deal 7, then Java cards cost 1 less this turn. + flavorText: Fedora ships new tech first; everything after comes cheaper. + language: 8 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. + designNotes: Java native tempo card; reduces the effective cost of all Java cards for the current turn after resolving. upgradeTarget: {fileID: 0} effects: [] diff --git a/Assets/Data/Cards/Languages/Java/JavaNewObject.asset b/Assets/Data/Cards/Languages/Java/JavaNewObject.asset new file mode 100644 index 0000000..ec9bb53 --- /dev/null +++ b/Assets/Data/Cards/Languages/Java/JavaNewObject.asset @@ -0,0 +1,29 @@ +%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: JavaNewObject + m_EditorClassIdentifier: + id: lang_java_new_object + displayName: new Object() + description: uses 2 RAM. deal 3. JIT applies. + flavorText: allocation as a love language. + language: 8 + rarity: 0 + cycleCost: 1 + resolutionTrack: 0 + distroExclusive: 0 + isToken: 0 + isRunOnly: 0 + designNotes: Java starter cheap damage; benefits from combat-wide JIT warmup. + requiresSystem: [] + upgradeTarget: {fileID: 0} + effects: [] diff --git a/Assets/Data/Cards/Languages/Java/JavaPublicStaticMain.asset b/Assets/Data/Cards/Languages/Java/JavaPublicStaticMain.asset new file mode 100644 index 0000000..8b1b583 --- /dev/null +++ b/Assets/Data/Cards/Languages/Java/JavaPublicStaticMain.asset @@ -0,0 +1,29 @@ +%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: JavaPublicStaticMain + m_EditorClassIdentifier: + id: lang_java_public_static_main + displayName: public static void main + description: uses 2 RAM. deal 7. JIT: Java cards cost 1 less this combat each time you play a Java card; the discount decays by 1 after each wave. + flavorText: verbose, but warmed up. + language: 8 + rarity: 0 + cycleCost: 3 + resolutionTrack: 0 + distroExclusive: 0 + isToken: 0 + isRunOnly: 0 + designNotes: Java starter heavy hit; JIT cost reduction is handled by combat-wide Java warmup and cools by 1 between waves. + requiresSystem: [] + upgradeTarget: {fileID: 0} + effects: [] diff --git a/Assets/Data/Cards/Languages/Java/JavaSystemOutPrintln.asset b/Assets/Data/Cards/Languages/Java/JavaSystemOutPrintln.asset new file mode 100644 index 0000000..757ab81 --- /dev/null +++ b/Assets/Data/Cards/Languages/Java/JavaSystemOutPrintln.asset @@ -0,0 +1,29 @@ +%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: JavaSystemOutPrintln + m_EditorClassIdentifier: + id: lang_java_system_out_println + displayName: System.out.println + description: uses 2 RAM. deal 5. JIT applies. + flavorText: standard output, standard damage. + language: 8 + rarity: 0 + cycleCost: 2 + resolutionTrack: 0 + distroExclusive: 0 + isToken: 0 + isRunOnly: 0 + designNotes: Java starter mid-cost damage; benefits from combat-wide JIT warmup. + requiresSystem: [] + upgradeTarget: {fileID: 0} + effects: [] diff --git a/Assets/Data/Cards/Languages/JavaDeck.asset b/Assets/Data/Cards/Languages/JavaDeck.asset new file mode 100644 index 0000000..935014d --- /dev/null +++ b/Assets/Data/Cards/Languages/JavaDeck.asset @@ -0,0 +1,22 @@ +%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: a7d10000000000000000000000000001, type: 3} + m_Name: JavaDeck + m_EditorClassIdentifier: + language: 8 + entries: + - card: {fileID: 11400000, guid: a7d60000000000000000000000000004, type: 2} + count: 2 + - card: {fileID: 11400000, guid: a7d60000000000000000000000000005, type: 2} + count: 1 + - card: {fileID: 11400000, guid: a7d60000000000000000000000000006, type: 2} + count: 1 diff --git a/Assets/Data/Cards/Languages/LanguageDeckDatabase.asset b/Assets/Data/Cards/Languages/LanguageDeckDatabase.asset index 8db2eef..f0a40a4 100644 --- a/Assets/Data/Cards/Languages/LanguageDeckDatabase.asset +++ b/Assets/Data/Cards/Languages/LanguageDeckDatabase.asset @@ -15,3 +15,5 @@ MonoBehaviour: allDecks: - {fileID: 11400000, guid: a7d50000000000000000000000000001, type: 2} - {fileID: 11400000, guid: a7d50000000000000000000000000002, type: 2} + - {fileID: 11400000, guid: a7d70000000000000000000000000001, type: 2} + - {fileID: 11400000, guid: a7d70000000000000000000000000002, type: 2} diff --git a/Assets/Data/Cards/Languages/Rust/RsBorrow.asset b/Assets/Data/Cards/Languages/Rust/RsBorrow.asset new file mode 100644 index 0000000..d6a0043 --- /dev/null +++ b/Assets/Data/Cards/Languages/Rust/RsBorrow.asset @@ -0,0 +1,29 @@ +%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: RsBorrow + m_EditorClassIdentifier: + id: lang_rs_borrow + displayName: borrow + description: gain 5 Shield. + flavorText: no ownership transfer required. + language: 2 + rarity: 0 + cycleCost: 0 + resolutionTrack: 0 + distroExclusive: 0 + isToken: 0 + isRunOnly: 0 + designNotes: Rust starter defense. + requiresSystem: [] + upgradeTarget: {fileID: 0} + effects: [] diff --git a/Assets/Data/Cards/Languages/Rust/RsFnMain.asset b/Assets/Data/Cards/Languages/Rust/RsFnMain.asset new file mode 100644 index 0000000..72366f3 --- /dev/null +++ b/Assets/Data/Cards/Languages/Rust/RsFnMain.asset @@ -0,0 +1,29 @@ +%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: RsFnMain + m_EditorClassIdentifier: + id: lang_rs_fn_main + displayName: fn main() + description: deal 6. cannot miss. overkill becomes Shield. + flavorText: the entry point has teeth. + language: 2 + rarity: 0 + cycleCost: 1 + resolutionTrack: 0 + distroExclusive: 0 + isToken: 0 + isRunOnly: 0 + designNotes: Rust starter damage; reliable hit with overkill converted into shield. + requiresSystem: [] + upgradeTarget: {fileID: 0} + effects: [] diff --git a/Assets/Data/Cards/Languages/Rust/RsUnwrap.asset b/Assets/Data/Cards/Languages/Rust/RsUnwrap.asset new file mode 100644 index 0000000..d56d545 --- /dev/null +++ b/Assets/Data/Cards/Languages/Rust/RsUnwrap.asset @@ -0,0 +1,29 @@ +%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: RsUnwrap + m_EditorClassIdentifier: + id: lang_rs_unwrap + displayName: unwrap() + description: deal 9. cannot miss. draw 1. + flavorText: confidence, weaponized. + language: 2 + rarity: 0 + cycleCost: 1 + resolutionTrack: 0 + distroExclusive: 0 + isToken: 0 + isRunOnly: 0 + designNotes: Rust starter burst plus replacement draw. + requiresSystem: [] + upgradeTarget: {fileID: 0} + effects: [] diff --git a/Assets/Data/Cards/Languages/RustDeck.asset b/Assets/Data/Cards/Languages/RustDeck.asset new file mode 100644 index 0000000..5b0369d --- /dev/null +++ b/Assets/Data/Cards/Languages/RustDeck.asset @@ -0,0 +1,22 @@ +%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: a7d10000000000000000000000000001, type: 3} + m_Name: RustDeck + m_EditorClassIdentifier: + language: 2 + entries: + - card: {fileID: 11400000, guid: a7d60000000000000000000000000001, type: 2} + count: 2 + - card: {fileID: 11400000, guid: a7d60000000000000000000000000002, type: 2} + count: 1 + - card: {fileID: 11400000, guid: a7d60000000000000000000000000003, type: 2} + count: 1 diff --git a/Assets/Data/Distros/Fedora.asset b/Assets/Data/Distros/Fedora.asset index c4bc121..f81f4df 100644 --- a/Assets/Data/Distros/Fedora.asset +++ b/Assets/Data/Distros/Fedora.asset @@ -27,7 +27,7 @@ MonoBehaviour: asciiArt: {fileID: 4900000, guid: f2373174b6954f2d9088d83d10e0d9e4, type: 3} accentColor: {r: 0.23529412, g: 0.43137255, b: 0.7058824, a: 1} baseUptime: 55 - baseRam: 5 + baseRam: 6 baseCyclesPerTurn: 3 exclusiveCards: - {fileID: 11400000, guid: 4dfd40ca206341ba9a1a486d69994611, type: 2} diff --git a/Assets/Scripts/Combat/CardEffects.cs b/Assets/Scripts/Combat/CardEffects.cs index c2d2de7..7259462 100644 --- a/Assets/Scripts/Combat/CardEffects.cs +++ b/Assets/Scripts/Combat/CardEffects.cs @@ -216,7 +216,7 @@ namespace KernelPanic.Combat public void Execute(CombatContext context) { - int room = context.HandController.RamCapacity - context.HandController.Cards.Count; + int room = context.HandController.RemainingRam; if (room <= 0) { context.CombatManager.ReportEffectResult("hand full: drew 0"); @@ -242,6 +242,10 @@ namespace KernelPanic.Combat { added++; } + else + { + context.DeckController.AddToDrawPile(drawn[i], shuffle: false); + } } string capped = drawCount < requested ? " (hand full)" : string.Empty; @@ -285,7 +289,7 @@ namespace KernelPanic.Combat { public void Execute(CombatContext context) { - if (context.HandController.Cards.Count >= context.HandController.RamCapacity) + if (context.HandController.RemainingRam <= 0) { context.CombatManager.ReportEffectResult("hand full: copied 0"); return; @@ -316,6 +320,12 @@ namespace KernelPanic.Combat } CardInstance copy = selected.CopyForCombat(); + if (!context.HandController.CanAdd(copy)) + { + context.CombatManager.ReportEffectResult("not enough RAM to copy"); + return; + } + if (context.HandController.Add(copy)) { string name = string.IsNullOrWhiteSpace(copy.Definition.DisplayName) ? copy.Definition.Id : copy.Definition.DisplayName; @@ -403,7 +413,7 @@ namespace KernelPanic.Combat public void Execute(CombatContext context) { - if (context.HandController.Cards.Count >= context.HandController.RamCapacity) + if (context.HandController.RemainingRam <= 0) { context.CombatManager.ReportEffectResult("hand full: generated 0"); return; @@ -415,7 +425,11 @@ namespace KernelPanic.Combat return; } - if (context.HandController.Add(generatedCard)) + if (!context.HandController.CanAdd(generatedCard)) + { + context.CombatManager.ReportEffectResult("not enough RAM: generated 0"); + } + else if (context.HandController.Add(generatedCard)) { context.CombatManager.ReportEffectResult($"generated {GetCardName(generatedCard)} at 0c"); } @@ -491,6 +505,92 @@ namespace KernelPanic.Combat } } + public sealed class FirstToPackageEffect : ICardEffect + { + private readonly int _amount; + + public FirstToPackageEffect(int amount) + { + _amount = amount; + } + + public void Execute(CombatContext context) + { + int amount = UpgradeMath.ScaleAmount(UpgradeMath.ApplySourceFlatBonus(_amount, context), context.Card); + new DealDamageEffect(amount, amount, Language.Java).Execute(context); + context.CombatManager.AddJavaCostDiscountThisTurn(1); + } + } + + public sealed class DnfAutoremoveEffect : ICardEffect + { + private readonly int _damageAmount; + private readonly int _cycleGain; + + public DnfAutoremoveEffect(int damageAmount, int cycleGain) + { + _damageAmount = damageAmount; + _cycleGain = cycleGain; + } + + public void Execute(CombatContext context) + { + CardInstance junk = FindJunkCard(context); + if (junk == null) + { + context.CombatManager.ReportEffectResult("dnf autoremove: no orphaned package"); + return; + } + + int damageAmount = UpgradeMath.ScaleAmount(UpgradeMath.ApplySourceFlatBonus(_damageAmount, context), context.Card); + new DealDamageEffect(damageAmount, damageAmount, Language.Rust).Execute(context); + if (context.HandController.Remove(junk)) + { + context.DeckController.Exhaust(junk); + int cycleGain = UpgradeMath.ScaleAmount(_cycleGain, context.Card); + context.Source.Cycles += cycleGain; + context.CombatManager.ReportEffectResult($"autoremove exhausted {GetCardName(junk)}: +{cycleGain} cycle"); + } + } + + private static CardInstance FindJunkCard(CombatContext context) + { + for (int i = 0; i < context.HandController.Cards.Count; i++) + { + CardInstance card = context.HandController.Cards[i]; + if (IsJunk(card)) + { + return card; + } + } + + return null; + } + + private static bool IsJunk(CardInstance card) + { + if (card == null) + { + return false; + } + + string id = card.Definition?.Id ?? string.Empty; + string name = card.Definition?.DisplayName ?? string.Empty; + return card.Definition != null && card.Definition.IsToken + || card.IsBroken + || id.IndexOf("junk", System.StringComparison.OrdinalIgnoreCase) >= 0 + || id.IndexOf("nop", System.StringComparison.OrdinalIgnoreCase) >= 0 + || name.IndexOf("junk", System.StringComparison.OrdinalIgnoreCase) >= 0 + || name.IndexOf("NOP", System.StringComparison.OrdinalIgnoreCase) >= 0 + || name.IndexOf("broken", System.StringComparison.OrdinalIgnoreCase) >= 0; + } + + private static string GetCardName(CardInstance card) + { + return string.IsNullOrWhiteSpace(card?.Definition?.DisplayName) ? card?.Definition?.Id ?? "--" : card.Definition.DisplayName; + } + } + public sealed class UpdateManagerEffect : ICardEffect { private readonly int _amount; @@ -573,6 +673,16 @@ namespace KernelPanic.Combat "lang_py_for_loop" => One(new DealDamageEffect(2, 2, Language.Python, allEnemies: true)), "lang_js_console_log" => One(new DealDamageEffect(3, 9, Language.JavaScript)), "lang_js_fetch" => One(new DealDamageEffect(5, 13, Language.JavaScript)), + "lang_rs_fn_main" => One(new OverkillToShieldDamageEffect(6, Language.Rust)), + "lang_rs_unwrap" => new ICardEffect[] + { + new DealDamageEffect(9, 9, Language.Rust), + new DrawEffect(1) + }, + "lang_rs_borrow" => One(new ShieldEffect(5)), + "lang_java_public_static_main" => One(new DealDamageEffect(7, 7, Language.Java)), + "lang_java_system_out_println" => One(new DealDamageEffect(5, 5, Language.Java)), + "lang_java_new_object" => One(new DealDamageEffect(3, 3, Language.Java)), "ubuntu_ask_ubuntu" => One(new DrawEffect(2)), "ubuntu_unattended_upgrades" => new ICardEffect[] { @@ -616,8 +726,8 @@ namespace KernelPanic.Combat }, "mint_nemo" => One(new ConditionalShieldRepeatDamageEffect(6, Language.JavaScript)), "mint_timeshift" => One(new TimeshiftSnapshotEffect()), - "fedora_borrow_checker" => One(new FirstCardShieldEffect(8, 8)), - "fedora_cargo_build" => One(new OverkillToShieldDamageEffect(10, Language.Rust)), + "fedora_dnf_autoremove" => One(new DnfAutoremoveEffect(7, 1)), + "fedora_first_to_package" => One(new FirstToPackageEffect(7)), "fedora_dnf_update" => One(new DealDamageEffect(9, 9, Language.Java)), "fedora_rawhide" => One(new RawhideEffect()), "fedora_selinux" => One(new IncomingAttackHalveEffect(2)), @@ -628,8 +738,10 @@ namespace KernelPanic.Combat public static bool RequiresSingleTarget(CardDefinition definition) { return definition?.Id is "lang_js_console_log" or "lang_js_fetch" or "lang_js_typeof" or "mint_fix_broken" + or "lang_rs_fn_main" or "lang_rs_unwrap" or "lang_java_public_static_main" + or "lang_java_system_out_println" or "lang_java_new_object" or "shop_py_list_append" or "shop_js_math_random" or "shop_js_promise_all" or "ubuntu_snap_install" - or "mint_nemo" or "fedora_cargo_build" or "fedora_dnf_update"; + or "mint_nemo" or "fedora_dnf_autoremove" or "fedora_first_to_package" or "fedora_dnf_update"; } public static bool TargetsAllEnemies(CardDefinition definition) @@ -684,6 +796,12 @@ namespace KernelPanic.Combat "lang_js_console_log" => $"deal {UpgradeMath.ScaleAmount(3, card)}-{UpgradeMath.ScaleAmount(9, card)}.{marker}", "lang_js_fetch" => $"deal {UpgradeMath.ScaleAmount(5, card)}-{UpgradeMath.ScaleAmount(13, card)}.{marker}", "lang_js_typeof" => $"75%: deal {UpgradeMath.ScaleAmount(8, card)}. 25%: deal 0.{marker}", + "lang_rs_fn_main" => $"deal {UpgradeMath.ScaleAmount(6, card)}. overkill becomes shield.{marker}", + "lang_rs_unwrap" => $"deal {UpgradeMath.ScaleAmount(9, card)}. draw {UpgradeMath.ScaleAmount(1, card)}.{marker}", + "lang_rs_borrow" => $"gain {UpgradeMath.ScaleShield(5, card)} shield.{marker}", + "lang_java_public_static_main" => $"uses 2 RAM. deal {UpgradeMath.ScaleAmount(7, card)}. JIT: Java cards cost 1 less this combat each time you play a Java card; discount decays by 1 after each wave.{marker}", + "lang_java_system_out_println" => $"uses 2 RAM. deal {UpgradeMath.ScaleAmount(5, card)}. JIT applies; discount decays by 1 after each wave.{marker}", + "lang_java_new_object" => $"uses 2 RAM. deal {UpgradeMath.ScaleAmount(3, card)}. JIT applies; discount decays by 1 after each wave.{marker}", "shop_py_list_append" => $"queue: deal {UpgradeMath.ScaleAmount(4 + UnityEngine.Mathf.Max(0, card.QueuePlayCount - 1), card)}; +1 each time re-queued.{marker}", "shop_py_async_def" => $"queue: draw {UpgradeMath.ScaleAmount(2, card)}. gain {UpgradeMath.ScaleAmount(1, card)} cycle next turn.{marker}", "shop_py_zip" => $"next {UpgradeMath.ScaleAmount(2, card)} queued cards resolve twice.{marker}", @@ -700,8 +818,8 @@ namespace KernelPanic.Combat "mint_cinnamon" => $"queue: gain {UpgradeMath.ScaleShield(3, card)} shield. draw {UpgradeMath.ScaleAmount(1, card)}.{marker}", "mint_nemo" => $"deal {UpgradeMath.ScaleAmount(6, card)}. if you have shield, deal it again.{marker}", "mint_timeshift" => $"queue: record uptime now. at end of your next turn, restore to it if lower.{marker}", - "fedora_borrow_checker" => $"gain {UpgradeMath.ScaleShield(8, card)} shield. if first card this turn, gain {UpgradeMath.ScaleShield(8, card)} more.{marker}", - "fedora_cargo_build" => $"deal {UpgradeMath.ScaleAmount(10, card)}. overkill becomes shield.{marker}", + "fedora_dnf_autoremove" => $"deal {UpgradeMath.ScaleAmount(7, card)}. exhaust a junk/NOP/broken card from hand; if you do, gain {UpgradeMath.ScaleAmount(1, card)} cycle. if not, do nothing.{marker}", + "fedora_first_to_package" => $"deal {UpgradeMath.ScaleAmount(7, card)}. Java cards cost 1 less this turn.{marker}", "fedora_dnf_update" => $"deal {UpgradeMath.ScaleAmount(9, card)}. costs 1 less per Java card played this combat.{marker}", "fedora_rawhide" => $"draw {UpgradeMath.ScaleAmount(2, card)}. next card this turn gains bleeding edge.{marker}", "fedora_selinux" => $"next {UpgradeMath.ScaleAmount(2, card)} enemy attacks deal half damage.{marker}", diff --git a/Assets/Scripts/Combat/CardInstance.cs b/Assets/Scripts/Combat/CardInstance.cs index 1235dcc..9ff0924 100644 --- a/Assets/Scripts/Combat/CardInstance.cs +++ b/Assets/Scripts/Combat/CardInstance.cs @@ -25,6 +25,7 @@ namespace KernelPanic.Combat public int UpgradeLevel { get; private set; } public int QueuePlayCount { get; private set; } public bool WasFirstCardThisTurn { get; private set; } + public bool HadFedoraNonCrashBonus { get; private set; } public bool IsBroken { get; set; } public bool IsLocked { get; set; } public bool CanUpgrade => UpgradeLevel == 0; @@ -100,6 +101,12 @@ namespace KernelPanic.Combat public void MarkPlayedThisTurn(bool wasFirst) { WasFirstCardThisTurn = wasFirst; + HadFedoraNonCrashBonus = false; + } + + public void MarkFedoraNonCrashBonus() + { + HadFedoraNonCrashBonus = true; } public CardInstance CopyForCombat() @@ -117,6 +124,7 @@ namespace KernelPanic.Combat copy.UpgradeLevel = UpgradeLevel; copy.QueuePlayCount = QueuePlayCount; copy.WasFirstCardThisTurn = WasFirstCardThisTurn; + copy.HadFedoraNonCrashBonus = HadFedoraNonCrashBonus; copy.SetTargetSnapshot(TargetSnapshot); return copy; } @@ -142,6 +150,7 @@ namespace KernelPanic.Combat TemporaryCostDelta = 0; QueuePlayCount = 0; WasFirstCardThisTurn = false; + HadFedoraNonCrashBonus = false; IsBroken = false; IsLocked = false; ClearTargetSnapshot(); diff --git a/Assets/Scripts/Combat/CombatManager.cs b/Assets/Scripts/Combat/CombatManager.cs index f816c54..87ace7c 100644 --- a/Assets/Scripts/Combat/CombatManager.cs +++ b/Assets/Scripts/Combat/CombatManager.cs @@ -40,6 +40,7 @@ namespace KernelPanic.Combat private int fedoraCrashChance = 10; private int cardsPlayedThisTurn; private int javaCardsPlayedThisCombat; + private int javaCardsDiscountThisTurn; private int rawhideBonusCharges; private int queuedRepeatCharges; private int nextTurnCycleBonus; @@ -114,6 +115,7 @@ namespace KernelPanic.Combat } awaitingWaveContinue = false; + DecayJavaWarmupForNextWave(); StartWave(preservePlayerUptime: true); SetPhase(TurnPhase.Allocate); } @@ -222,6 +224,7 @@ namespace KernelPanic.Combat } fedoraCrashChance = Mathf.Min(90, fedoraCrashChance + 10); + card.MarkFedoraNonCrashBonus(); playerState.DamageMultiplierPercent = runConfig.DistroVersion >= 2 ? 175 : 150; ApplyFedoraGrowth(card, rawhideChargeUsed); } @@ -265,9 +268,9 @@ namespace KernelPanic.Combat public int GetEffectiveCardCost(CardInstance card) { int cost = GetCardCost(card); - if (card?.Definition != null && string.Equals(card.Definition.Id, "fedora_dnf_update", StringComparison.OrdinalIgnoreCase)) + if (card?.Definition != null && card.Definition.Language == Language.Java) { - cost -= javaCardsPlayedThisCombat; + cost -= javaCardsPlayedThisCombat + javaCardsDiscountThisTurn; } return Mathf.Max(0, cost); @@ -310,6 +313,18 @@ namespace KernelPanic.Combat } } + public void AddJavaCostDiscountThisTurn(int amount) + { + int safeAmount = Mathf.Max(0, amount); + if (safeAmount <= 0) + { + return; + } + + javaCardsDiscountThisTurn += safeAmount; + ReportEffectResult($"Java cards cost -{safeAmount} this turn"); + } + public int ConsumeQueuedResolutionCount(CardInstance card) { if (queuedRepeatCharges <= 0 || card == null) @@ -514,6 +529,7 @@ namespace KernelPanic.Combat fedoraCardsDiscountedThisTurn = 0; cardsPlayedThisTurn = 0; javaCardsPlayedThisCombat = 0; + javaCardsDiscountThisTurn = 0; rawhideBonusCharges = 0; queuedRepeatCharges = 0; nextTurnCycleBonus = 0; @@ -582,6 +598,7 @@ namespace KernelPanic.Combat playerState.Cycles = playerState.MaxCycles; fedoraCardsDiscountedThisTurn = 0; cardsPlayedThisTurn = 0; + javaCardsDiscountThisTurn = 0; if (nextTurnCycleBonus > 0) { playerState.Cycles += nextTurnCycleBonus; @@ -717,14 +734,14 @@ namespace KernelPanic.Combat return; } - int room = playerState.Ram - handController.Cards.Count; + int room = handController.RemainingRam; if (room <= 0) { Log($"{label}: hand full"); return; } - int drawCount = Mathf.Min(requestedCount, room); + int drawCount = requestedCount; IReadOnlyList drawn = deckController.Draw(drawCount); int added = 0; for (int i = 0; i < drawn.Count; i++) @@ -733,6 +750,10 @@ namespace KernelPanic.Combat { added++; } + else + { + deckController.AddToDrawPile(drawn[i], shuffle: false); + } } if (added == 0 && deckController.AvailableToDrawCount == 0) @@ -751,7 +772,7 @@ namespace KernelPanic.Combat private void TryApplyUbuntuAptUpdate() { - if (!IsDistro("ubuntu") || handController == null || playerState == null || handController.Cards.Count >= handController.RamCapacity) + if (!IsDistro("ubuntu") || handController == null || playerState == null || handController.RemainingRam <= 0) { return; } @@ -771,6 +792,10 @@ namespace KernelPanic.Combat { Log($"apt update: staged {GetCardName(card)} from top {lookCount}"); } + else + { + deckController.AddToDrawPile(card, shuffle: false); + } } private void TryApplyUbuntuEmptyHandRefill() @@ -789,6 +814,17 @@ namespace KernelPanic.Combat return CanApplyFedoraPassiveBonus() || rawhideBonusCharges > 0; } + private void DecayJavaWarmupForNextWave() + { + if (javaCardsPlayedThisCombat <= 0) + { + return; + } + + javaCardsPlayedThisCombat = Mathf.Max(0, javaCardsPlayedThisCombat - 1); + Log($"Java JIT cooled: discount now -{javaCardsPlayedThisCombat}"); + } + private bool CanApplyFedoraPassiveBonus() { if (!IsDistro("fedora") || playerState == null) @@ -817,8 +853,7 @@ namespace KernelPanic.Combat private static bool IsCrashImmune(CardInstance card) { string id = card?.Definition?.Id; - return string.Equals(id, "fedora_cargo_build", StringComparison.OrdinalIgnoreCase) - || string.Equals(id, "fedora_selinux", StringComparison.OrdinalIgnoreCase); + return string.Equals(id, "fedora_selinux", StringComparison.OrdinalIgnoreCase); } private void ApplyVersionState(CombatantState state) diff --git a/Assets/Scripts/Combat/HandController.cs b/Assets/Scripts/Combat/HandController.cs index 9658eeb..b9f0303 100644 --- a/Assets/Scripts/Combat/HandController.cs +++ b/Assets/Scripts/Combat/HandController.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using KernelPanic.Core; namespace KernelPanic.Combat { @@ -16,10 +17,25 @@ namespace KernelPanic.Combat public int RamCapacity { get; private set; } public IReadOnlyList Cards => _cards; + public int UsedRam + { + get + { + int used = 0; + for (int i = 0; i < _cards.Count; i++) + { + used += GetRamCost(_cards[i]); + } + + return used; + } + } + + public int RemainingRam => System.Math.Max(0, RamCapacity - UsedRam); public bool CanAdd(CardInstance card) { - return card != null && _cards.Count < RamCapacity; + return card != null && UsedRam + GetRamCost(card) <= RamCapacity; } public bool Add(CardInstance card) @@ -42,5 +58,10 @@ namespace KernelPanic.Combat { _cards.Clear(); } + + public static int GetRamCost(CardInstance card) + { + return card?.Definition != null && card.Definition.Language == Language.Java ? 2 : 1; + } } } diff --git a/Assets/Scripts/Data/LanguageCatalog.cs b/Assets/Scripts/Data/LanguageCatalog.cs index 5921ff2..af86b94 100644 --- a/Assets/Scripts/Data/LanguageCatalog.cs +++ b/Assets/Scripts/Data/LanguageCatalog.cs @@ -13,13 +13,13 @@ namespace KernelPanic.Data { new(Language.C, "C", "bare metal", ResolutionTrack.Native, "TODO: Native cards hit immediately and reward careful resource timing. Signature mechanic copy pending.", null, Distro("arch", "Arch"), Distro("templeos", "TempleOS")), new(Language.CPlusPlus, "C++", "template arsenal", ResolutionTrack.Native, "TODO: Native cards hit immediately with complex setup payoffs. Signature mechanic copy pending.", "unlock: upgrade a C card during a run"), - new(Language.Rust, "Rust", "safe systems", ResolutionTrack.Native, "TODO: Rust resolves on the native track with reliable damage and defensive spillover. Signature mechanic copy pending.", null, Distro("arch", "Arch"), Distro("fedora", "Fedora")), + new(Language.Rust, "Rust", "safe systems", ResolutionTrack.Native, "Rust resolves on the native track with reliable damage and defensive spillover.", null, Distro("arch", "Arch"), Distro("fedora", "Fedora")), new(Language.Python, "Python", "queued scripts", ResolutionTrack.InterpreterQueue, "TODO: Python queues flexible effects that resolve at end of turn. Signature mechanic copy pending.", null, Distro("ubuntu", "Ubuntu"), Distro("kali", "Kali"), Distro("mint", "Mint")), new(Language.JavaScript, "JavaScript", "runtime chaos", ResolutionTrack.Native, "TODO: JavaScript fires instantly with swingy side effects. Signature mechanic copy pending.", null, Distro("ubuntu", "Ubuntu"), Distro("endeavouros", "EndeavourOS"), Distro("mint", "Mint")), new(Language.TypeScript, "TypeScript", "typed runtime", ResolutionTrack.Native, "TODO: TypeScript stabilizes JavaScript-style tempo with stricter setup. Signature mechanic copy pending.", "unlock: upgrade a JavaScript card during a run"), new(Language.Haskell, "Haskell", "lazy proofs", ResolutionTrack.LazyStack, "TODO: Haskell stores delayed work on the lazy stack before resolving in bursts. Signature mechanic copy pending.", null, Distro("gentoo", "Gentoo"), Distro("nixos", "NixOS")), new(Language.Assembly, "Assembly", "raw opcodes", ResolutionTrack.Native, "TODO: Assembly is immediate, low-level, and high commitment. Signature mechanic copy pending.", null, Distro("gentoo", "Gentoo"), Distro("templeos", "TempleOS")), - new(Language.Java, "Java", "warming vm", ResolutionTrack.Native, "TODO: Java starts expensive and gets cheaper as the combat warms up. Signature mechanic copy pending.", null, Distro("debian", "Debian"), Distro("fedora", "Fedora")), + new(Language.Java, "Java", "warming vm", ResolutionTrack.Native, "Java cards use 2 RAM, start expensive, and get cheaper as the combat warms up. JIT cools by 1 after each wave.", null, Distro("debian", "Debian"), Distro("fedora", "Fedora")), new(Language.Go, "Go", "small services", ResolutionTrack.Native, "TODO: Go uses direct native actions with simple scaling and concurrency hooks. Signature mechanic copy pending.", null, Distro("cachyos", "CachyOS"), Distro("nixos", "NixOS")), new(Language.Ruby, "Ruby", "sharp scripts", ResolutionTrack.InterpreterQueue, "TODO: Ruby queues expressive effects with combo-friendly timing. Signature mechanic copy pending.", null, Distro("kali", "Kali"), Distro("endeavouros", "EndeavourOS")), new(Language.Php, "PHP", "legacy glue", ResolutionTrack.InterpreterQueue, "TODO: PHP queues practical effects that improve when the board is messy. Signature mechanic copy pending.", null, Distro("debian", "Debian"), Distro("cachyos", "CachyOS")), diff --git a/Assets/Scripts/UI/CombatSceneController.cs b/Assets/Scripts/UI/CombatSceneController.cs index 152e29d..6d7a3b2 100644 --- a/Assets/Scripts/UI/CombatSceneController.cs +++ b/Assets/Scripts/UI/CombatSceneController.cs @@ -417,7 +417,7 @@ namespace KernelPanic.UI playerPanel.Add(MeterBlock("shield", state.Shield, Mathf.Max(1, state.Shield), state.Shield > 0 ? MeterTone.Beneficial : MeterTone.Muted)); playerPanel.Add(CycleBlock(state.Cycles, state.MaxCycles)); - Label ram = new($"ram hand cap {combatManager.HandController.Cards.Count}/{combatManager.HandController.RamCapacity}"); + Label ram = new($"ram hand cap {combatManager.HandController.UsedRam}/{combatManager.HandController.RamCapacity}"); ram.AddToClassList("ram-note"); playerPanel.Add(ram); playerPanel.Add(StatusBlock(state, true)); @@ -507,7 +507,7 @@ namespace KernelPanic.UI CombatantState state = combatManager.PlayerState; IReadOnlyList hand = combatManager.HandController.Cards; turnResourceGrid.Add(TurnStat("cycles", $"{state.Cycles}/{state.MaxCycles}")); - turnResourceGrid.Add(TurnStat("hand", $"{hand.Count}/{combatManager.HandController.RamCapacity}")); + turnResourceGrid.Add(TurnStat("hand", $"{combatManager.HandController.UsedRam}/{combatManager.HandController.RamCapacity}")); turnResourceGrid.Add(TurnStat("draw", combatManager.DeckController.DrawPile.Count.ToString())); turnResourceGrid.Add(TurnStat("discard", combatManager.DeckController.DiscardPile.Count.ToString())); turnResourceGrid.Add(TurnStat("exhaust", combatManager.DeckController.ExhaustPile.Count.ToString()));