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 @@
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
@@ -55,7 +62,11 @@
}
}
+
+
+
+
@@ -80,23 +91,23 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -106,35 +117,35 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{
@@ -223,7 +234,7 @@
-
+
@@ -385,7 +396,23 @@
1783493370994
-
+
+
+ 1783499391622
+
+
+
+ 1783499391622
+
+
+
+ 1783500547647
+
+
+
+ 1783500547647
+
+
@@ -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()));