Implement Java and Rust cards alongside some smaller changes

This commit is contained in:
2026-07-08 11:30:32 +02:00
parent 50135e2e38
commit 54906bbba1
20 changed files with 531 additions and 93 deletions
+6
View File
@@ -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}
+6 -6
View File
@@ -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: []
@@ -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: []
@@ -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: []
@@ -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: []
@@ -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: []
@@ -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
@@ -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}
@@ -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: []
@@ -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: []
@@ -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: []
@@ -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
+1 -1
View File
@@ -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}
+127 -9
View File
@@ -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}",
+9
View File
@@ -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();
+42 -7
View File
@@ -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<CardInstance> 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)
+22 -1
View File
@@ -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<CardInstance> 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;
}
}
}
+2 -2
View File
@@ -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")),
+2 -2
View File
@@ -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<CardInstance> 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()));