diff --git a/.idea/.idea.linuxdistrosbuttheyarebreedable/.idea/workspace.xml b/.idea/.idea.linuxdistrosbuttheyarebreedable/.idea/workspace.xml
index 889bf0a..315ec41 100644
--- a/.idea/.idea.linuxdistrosbuttheyarebreedable/.idea/workspace.xml
+++ b/.idea/.idea.linuxdistrosbuttheyarebreedable/.idea/workspace.xml
@@ -11,37 +11,30 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
-
-
+
-
@@ -49,7 +42,7 @@
-
+
@@ -118,23 +111,23 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -145,34 +138,34 @@
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{
@@ -264,7 +257,8 @@
-
+
+
@@ -482,7 +476,15 @@
1783603850701
-
+
+
+ 1783614775509
+
+
+
+ 1783614775509
+
+
@@ -498,7 +500,6 @@
-
@@ -523,7 +524,8 @@
-
+
+
diff --git a/Assets/Data/Cards/Languages/C/Malloc.asset b/Assets/Data/Cards/Languages/C/Malloc.asset
index c674d58..383fbf5 100644
--- a/Assets/Data/Cards/Languages/C/Malloc.asset
+++ b/Assets/Data/Cards/Languages/C/Malloc.asset
@@ -14,7 +14,7 @@ MonoBehaviour:
m_EditorClassIdentifier:
id: lang_c_malloc
displayName: malloc()
- description: deal 8. 20% segfault: take 3.
+ description: deal 8. 20% segfault: uptime -3.
flavorText: memory acquired. ownership emotionally unresolved.
language: 0
rarity: 0
diff --git a/Assets/Data/Cards/Languages/C/Pointer.asset b/Assets/Data/Cards/Languages/C/Pointer.asset
index 60782f6..3d68b5c 100644
--- a/Assets/Data/Cards/Languages/C/Pointer.asset
+++ b/Assets/Data/Cards/Languages/C/Pointer.asset
@@ -14,7 +14,7 @@ MonoBehaviour:
m_EditorClassIdentifier:
id: lang_c_pointer
displayName: pointer
- description: deal 6. if the previous card was C, deal 10 instead.
+ description: deal 6. if the previous card was C, deal 10 instead. 20% segfault: uptime -3.
flavorText: points at the bug with suspicious confidence.
language: 0
rarity: 0
diff --git a/Assets/Data/Cards/Languages/C/Printf.asset b/Assets/Data/Cards/Languages/C/Printf.asset
index a959fc3..1503090 100644
--- a/Assets/Data/Cards/Languages/C/Printf.asset
+++ b/Assets/Data/Cards/Languages/C/Printf.asset
@@ -14,7 +14,7 @@ MonoBehaviour:
m_EditorClassIdentifier:
id: lang_c_printf
displayName: printf()
- description: deal 4.
+ description: deal 4. 20% segfault: uptime -3.
flavorText: debugging by yelling into the void.
language: 0
rarity: 0
diff --git a/Assets/Data/Cards/Shop/Free.asset b/Assets/Data/Cards/Shop/Free.asset
index 6eda7de..0e92e60 100644
--- a/Assets/Data/Cards/Shop/Free.asset
+++ b/Assets/Data/Cards/Shop/Free.asset
@@ -14,7 +14,7 @@ MonoBehaviour:
m_EditorClassIdentifier:
id: shop_c_free
displayName: free()
- description: deal 12. if segfault recoil happens, draw 2.
+ description: deal 12. 20% segfault: uptime -3 and draw 2.
flavorText: double-free your schedule, not your heap.
language: 0
rarity: 0
diff --git a/Assets/Data/Cards/Shop/KernelExploit.asset b/Assets/Data/Cards/Shop/KernelExploit.asset
index 373691d..04ef461 100644
--- a/Assets/Data/Cards/Shop/KernelExploit.asset
+++ b/Assets/Data/Cards/Shop/KernelExploit.asset
@@ -14,7 +14,7 @@ MonoBehaviour:
m_EditorClassIdentifier:
id: shop_c_kernel_exploit
displayName: kernel_exploit
- description: deal 25 ignoring Shield. 50% segfault: take 8.
+ description: deal 25 ignoring Shield. 50% segfault: uptime -8.
flavorText: privilege escalated. heartbeat also escalated.
language: 0
rarity: 1
diff --git a/Assets/Data/Cards/Shop/Sizeof.asset b/Assets/Data/Cards/Shop/Sizeof.asset
index e56ced4..e24862f 100644
--- a/Assets/Data/Cards/Shop/Sizeof.asset
+++ b/Assets/Data/Cards/Shop/Sizeof.asset
@@ -14,7 +14,7 @@ MonoBehaviour:
m_EditorClassIdentifier:
id: shop_c_sizeof
displayName: sizeof()
- description: deal 3 x C cards in hand.
+ description: deal 3 x C cards in hand. 20% segfault: uptime -3.
flavorText: compile-time confidence, runtime consequences.
language: 0
rarity: 1
diff --git a/Assets/Scripts/Combat/CardEffects.cs b/Assets/Scripts/Combat/CardEffects.cs
index 4c67810..c6f982e 100644
--- a/Assets/Scripts/Combat/CardEffects.cs
+++ b/Assets/Scripts/Combat/CardEffects.cs
@@ -35,6 +35,7 @@ namespace KernelPanic.Combat
targets = ShuffleTargets(targets, context.Source);
}
+ bool dealtDamage = false;
for (int i = 0; i < targets.Count; i++)
{
CombatantState target = targets[i];
@@ -54,11 +55,14 @@ namespace KernelPanic.Combat
? _trueDamage ? target.CurrentUptime : target.CurrentUptime + target.Shield
: 0;
DamageResult result = context.DamagePipeline.DealDamage(new DamageRequest(context.Source, target, amount, _language, _trueDamage, _canCrit));
+ dealtDamage |= result.IncomingAmount > 0;
if (rustOverkillToShield)
{
GrantRustOverkillShield(context, result, survivable);
}
}
+
+ SegfaultRecoil.TryApply(context, _language, dealtDamage);
}
private static void GrantRustOverkillShield(CombatContext context, DamageResult result, int survivable)
@@ -164,7 +168,8 @@ namespace KernelPanic.Combat
int roll = RandomRoll.RollRange(1, 100, new RollContext(context.Source));
int damageAmount = UpgradeMath.ScaleAmount(UpgradeMath.ApplySourceFlatBonus(_damageAmount, context), context.Card);
int amount = roll <= _successPercent ? damageAmount : 0;
- context.DamagePipeline.DealDamage(new DamageRequest(context.Source, target, amount, _language, false, true));
+ DamageResult result = context.DamagePipeline.DealDamage(new DamageRequest(context.Source, target, amount, _language, false, true));
+ SegfaultRecoil.TryApply(context, _language, result.IncomingAmount > 0);
context.CombatManager.ReportEffectResult(amount == 0 ? "typeof -> NaN (0)" : $"typeof -> {amount}");
}
}
@@ -189,41 +194,6 @@ namespace KernelPanic.Combat
}
}
- public sealed class SegfaultDamageEffect : ICardEffect
- {
- private readonly int _amount;
- private readonly int _selfDamage;
- private readonly int _segfaultPercent;
- private readonly bool _trueDamage;
- private readonly bool _drawOnSegfault;
-
- public SegfaultDamageEffect(int amount, int selfDamage, int segfaultPercent, bool trueDamage = false, bool drawOnSegfault = false)
- {
- _amount = amount;
- _selfDamage = selfDamage;
- _segfaultPercent = segfaultPercent;
- _trueDamage = trueDamage;
- _drawOnSegfault = drawOnSegfault;
- }
-
- public void Execute(CombatContext context)
- {
- new DealDamageEffect(_amount, _amount, Language.C, _trueDamage).Execute(context);
- bool segfault = RandomRoll.RollRange(1, 100, new RollContext(context.Source)) <= _segfaultPercent;
- if (!segfault)
- {
- return;
- }
-
- context.DamagePipeline.DealDamage(new DamageRequest(context.Source, context.Source, _selfDamage, Language.C, false, false));
- context.CombatManager.ReportEffectResult($"segfault: took {_selfDamage}");
- if (_drawOnSegfault)
- {
- context.CombatManager.DrawCards(2, "free() recoil");
- }
- }
- }
-
public sealed class PreviousLanguageDamageEffect : ICardEffect
{
private readonly int _baseAmount;
@@ -687,6 +657,7 @@ namespace KernelPanic.Combat
int amount = UpgradeMath.ScaleAmount(UpgradeMath.ApplySourceFlatBonus(_amount, context), context.Card);
int survivable = target.CurrentUptime + target.Shield;
DamageResult result = context.DamagePipeline.DealDamage(new DamageRequest(context.Source, target, amount, _language, false, true));
+ SegfaultRecoil.TryApply(context, _language, result.IncomingAmount > 0);
int overkill = UnityEngine.Mathf.Max(0, result.IncomingAmount - survivable);
if (overkill > 0)
{
@@ -875,6 +846,58 @@ namespace KernelPanic.Combat
}
}
+ internal static class SegfaultRecoil
+ {
+ private const int DefaultPercent = 20;
+ private const int DefaultUptimeDamage = 3;
+
+ public static void TryApply(CombatContext context, Language damageLanguage, bool dealtDamage)
+ {
+ if (!dealtDamage
+ || context == null
+ || context.SegfaultRecoilRolled
+ || context.Source == null
+ || context.Source.IsDefeated
+ || context.Card?.Definition?.Language != Language.C
+ || damageLanguage != Language.C)
+ {
+ return;
+ }
+
+ context.SegfaultRecoilRolled = true;
+ GetProfile(context.Card, out int percent, out int uptimeDamage, out bool drawOnSegfault);
+ if (RandomRoll.RollRange(1, 100, new RollContext(context.Source)) > percent)
+ {
+ return;
+ }
+
+ DamageResult result = context.DamagePipeline.DealDamage(new DamageRequest(null, context.Source, uptimeDamage, Language.C, true, false));
+ context.CombatManager.ReportEffectResult($"segfault: uptime -{result.FinalAmount}");
+ if (drawOnSegfault)
+ {
+ context.CombatManager.DrawCards(2, "free() recoil");
+ }
+ }
+
+ private static void GetProfile(CardInstance card, out int percent, out int uptimeDamage, out bool drawOnSegfault)
+ {
+ percent = DefaultPercent;
+ uptimeDamage = DefaultUptimeDamage;
+ drawOnSegfault = false;
+
+ switch (card?.Definition?.Id)
+ {
+ case "shop_c_free":
+ drawOnSegfault = true;
+ break;
+ case "shop_c_kernel_exploit":
+ percent = 50;
+ uptimeDamage = 8;
+ break;
+ }
+ }
+ }
+
public static class CardEffectFactory
{
public static IReadOnlyList CreateEffects(CardDefinition definition)
@@ -899,7 +922,7 @@ namespace KernelPanic.Combat
new DrawEffect(1)
},
"lang_rs_borrow" => One(new ShieldEffect(5)),
- "lang_c_malloc" => One(new SegfaultDamageEffect(8, 3, 20)),
+ "lang_c_malloc" => One(new DealDamageEffect(8, 8, Language.C)),
"lang_c_printf" => One(new DealDamageEffect(4, 4, Language.C)),
"lang_c_pointer" => One(new PreviousLanguageDamageEffect(6, 10, Language.C)),
"lang_java_public_static_main" => One(new DealDamageEffect(7, 7, Language.Java)),
@@ -958,9 +981,9 @@ namespace KernelPanic.Combat
"arch_makepkg" => One(new BtwScaledDamageEffect(4, 2, Language.C)),
"arch_aur_helper" => One(new RandomCommonLanguageCardEffect()),
"arch_minimal_install" => One(new MinimalInstallEffect()),
- "shop_c_free" => One(new SegfaultDamageEffect(12, 3, 20, drawOnSegfault: true)),
+ "shop_c_free" => One(new DealDamageEffect(12, 12, Language.C)),
"shop_c_sizeof" => One(new SizeofDamageEffect()),
- "shop_c_kernel_exploit" => One(new SegfaultDamageEffect(25, 8, 50, true)),
+ "shop_c_kernel_exploit" => One(new DealDamageEffect(25, 25, Language.C, true)),
_ => Todo("card effect is not authored")
};
}
@@ -1030,9 +1053,9 @@ namespace KernelPanic.Combat
"lang_rs_fn_main" => $"deal {UpgradeMath.ScaleAmount(6, card)}. overkill becomes shield.{marker}",
"lang_rs_unwrap" => $"deal {UpgradeMath.ScaleAmount(9, card)}. overkill becomes shield. draw {UpgradeMath.ScaleAmount(1, card)}.{marker}",
"lang_rs_borrow" => $"gain {UpgradeMath.ScaleShield(5, card)} shield.{marker}",
- "lang_c_malloc" => $"deal {UpgradeMath.ScaleAmount(8, card)}. 20%: segfault, take 3.{marker}",
- "lang_c_printf" => $"deal {UpgradeMath.ScaleAmount(4, card)}.{marker}",
- "lang_c_pointer" => $"deal {UpgradeMath.ScaleAmount(6, card)}; {UpgradeMath.ScaleAmount(10, card)} if the previous card was C.{marker}",
+ "lang_c_malloc" => $"deal {UpgradeMath.ScaleAmount(8, card)}. 20%: segfault, uptime -3.{marker}",
+ "lang_c_printf" => $"deal {UpgradeMath.ScaleAmount(4, card)}. 20%: segfault, uptime -3.{marker}",
+ "lang_c_pointer" => $"deal {UpgradeMath.ScaleAmount(6, card)}; {UpgradeMath.ScaleAmount(10, card)} if the previous card was C. 20%: segfault, uptime -3.{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}",
@@ -1057,14 +1080,14 @@ namespace KernelPanic.Combat
"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}",
- "arch_pacman_syu" => $"deal {UpgradeMath.ScaleAmount(8, card)}. TODO: 20% break a random hand card.{marker}",
+ "arch_pacman_syu" => $"deal {UpgradeMath.ScaleAmount(8, card)}. 20%: segfault, uptime -3. TODO: 20% break a random hand card.{marker}",
"arch_consult_wiki" => $"fix a broken card and draw {UpgradeMath.ScaleAmount(1, card)}; if none, deal {UpgradeMath.ScaleAmount(6, card)}.{marker}",
- "arch_makepkg" => $"deal {UpgradeMath.ScaleAmount(4, card)} + btw x2; x3 at Arch v3+.{marker}",
+ "arch_makepkg" => $"deal {UpgradeMath.ScaleAmount(4, card)} + btw x2; x3 at Arch v3+. 20%: segfault, uptime -3.{marker}",
"arch_aur_helper" => $"add a random C/Rust common card to hand; it costs 0 this turn.{marker}",
"arch_minimal_install" => $"exhaust up to 2 hand cards. per card: gain 3 Shield and 1 Cycle.{marker}",
- "shop_c_free" => $"deal {UpgradeMath.ScaleAmount(12, card)}. segfault recoil draws 2.{marker}",
- "shop_c_sizeof" => $"deal 3 x C cards in hand.{marker}",
- "shop_c_kernel_exploit" => $"deal {UpgradeMath.ScaleAmount(25, card)} ignoring Shield. 50%: take 8.{marker}",
+ "shop_c_free" => $"deal {UpgradeMath.ScaleAmount(12, card)}. 20%: segfault, uptime -3 and draw 2.{marker}",
+ "shop_c_sizeof" => $"deal 3 x C cards in hand. 20%: segfault, uptime -3.{marker}",
+ "shop_c_kernel_exploit" => $"deal {UpgradeMath.ScaleAmount(25, card)} ignoring Shield. 50%: segfault, uptime -8.{marker}",
_ => string.IsNullOrWhiteSpace(card.Definition.Description) ? "effect not authored." : $"{card.Definition.Description}{marker}"
};
}
diff --git a/Assets/Scripts/Combat/CombatContext.cs b/Assets/Scripts/Combat/CombatContext.cs
index 327d5cd..2331e61 100644
--- a/Assets/Scripts/Combat/CombatContext.cs
+++ b/Assets/Scripts/Combat/CombatContext.cs
@@ -38,5 +38,7 @@ namespace KernelPanic.Combat
public DeckController DeckController { get; }
public HandController HandController { get; }
public IReadOnlyList Enemies { get; }
+
+ internal bool SegfaultRecoilRolled { get; set; }
}
}
diff --git a/Assets/Scripts/Combat/CombatManager.cs b/Assets/Scripts/Combat/CombatManager.cs
index 0bae309..25bf43c 100644
--- a/Assets/Scripts/Combat/CombatManager.cs
+++ b/Assets/Scripts/Combat/CombatManager.cs
@@ -18,65 +18,65 @@ namespace KernelPanic.Combat
{
[SerializeField] private TurnPhase currentPhase = TurnPhase.Boot;
- private readonly DeckController deckController = new();
- private readonly InterpreterQueue interpreterQueue = new();
- private readonly LazyStack lazyStack = new();
- private readonly NativeTrack nativeTrack = new();
- private readonly StatusEffectController statusEffects = new();
- private readonly DamagePipeline damagePipeline = new();
- private readonly List enemies = new();
- private readonly HashSet loggedEffectTodos = new();
- private readonly List generatedCardPool = new();
- private readonly List delayedEffects = new();
- private RunManager runManager;
- private RunConfig runConfig;
- private HandController handController;
- private CombatantState playerState;
- private int selectedEnemyIndex = -1;
- private CardInstance pendingTargetCard;
- private bool awaitingWaveContinue;
- private bool runLost;
- private bool skipNextAllocateDraw;
- private bool ubuntuEmptyHandRefillUsed;
- private int fedoraCardsDiscountedThisTurn;
- private int fedoraCrashChance = 10;
- private int cardsPlayedThisTurn;
- private int cardsPlayedThisWave;
- private int cCardsPlayedThisTurn;
- private int turnNumberThisWave;
- private bool packageShieldBonusTriggeredThisTurn;
- private bool packageTimeshiftTriggeredThisWave;
- private bool packageInterpreterQueueShieldTriggeredThisWave;
- private bool packageNativeDamageTriggeredThisWave;
- private int javaCardsPlayedThisCombat;
- private int javaCardsDiscountThisTurn;
- private int rawhideBonusCharges;
- private int queuedRepeatCharges;
- private int nextTurnCycleBonus;
- private Coroutine phaseCoroutine;
+ private readonly DeckController _deckController = new();
+ private readonly InterpreterQueue _interpreterQueue = new();
+ private readonly LazyStack _lazyStack = new();
+ private readonly NativeTrack _nativeTrack = new();
+ private readonly StatusEffectController _statusEffects = new();
+ private readonly DamagePipeline _damagePipeline = new();
+ private readonly List _enemies = new();
+ private readonly HashSet _loggedEffectTodos = new();
+ private readonly List _generatedCardPool = new();
+ private readonly List _delayedEffects = new();
+ private RunManager _runManager;
+ private RunConfig _runConfig;
+ private HandController _handController;
+ private CombatantState _playerState;
+ private int _selectedEnemyIndex = -1;
+ private CardInstance _pendingTargetCard;
+ private bool _awaitingWaveContinue;
+ private bool _runLost;
+ private bool _skipNextAllocateDraw;
+ private bool _ubuntuEmptyHandRefillUsed;
+ private int _fedoraCardsDiscountedThisTurn;
+ private int _fedoraCrashChance = 10;
+ private int _cardsPlayedThisTurn;
+ private int _cardsPlayedThisWave;
+ private int _cCardsPlayedThisTurn;
+ private int _turnNumberThisWave;
+ private bool _packageShieldBonusTriggeredThisTurn;
+ private bool _packageTimeshiftTriggeredThisWave;
+ private bool _packageInterpreterQueueShieldTriggeredThisWave;
+ private bool _packageNativeDamageTriggeredThisWave;
+ private int _javaCardsPlayedThisCombat;
+ private int _javaCardsDiscountThisTurn;
+ private int _rawhideBonusCharges;
+ private int _queuedRepeatCharges;
+ private int _nextTurnCycleBonus;
+ private Coroutine _phaseCoroutine;
public TurnPhase CurrentPhase => currentPhase;
- public RunConfig RunConfig => runConfig;
- public DeckController DeckController => deckController;
- public HandController HandController => handController;
- public CombatantState PlayerState => playerState;
- public InterpreterQueue InterpreterQueue => interpreterQueue;
- public LazyStack LazyStack => lazyStack;
- public NativeTrack NativeTrack => nativeTrack;
- public DamagePipeline DamagePipeline => damagePipeline;
- public IReadOnlyList Enemies => enemies;
- public int SelectedEnemyIndex => selectedEnemyIndex;
- public CardInstance PendingTargetCard => pendingTargetCard;
- public bool AwaitingWaveContinue => awaitingWaveContinue;
- public bool RunLost => runLost;
- public int CurrentWaveNumber => runManager == null ? 1 : runManager.CurrentWaveNumber;
+ public RunConfig RunConfig => _runConfig;
+ public DeckController DeckController => _deckController;
+ public HandController HandController => _handController;
+ public CombatantState PlayerState => _playerState;
+ public InterpreterQueue InterpreterQueue => _interpreterQueue;
+ public LazyStack LazyStack => _lazyStack;
+ public NativeTrack NativeTrack => _nativeTrack;
+ public DamagePipeline DamagePipeline => _damagePipeline;
+ public IReadOnlyList Enemies => _enemies;
+ public int SelectedEnemyIndex => _selectedEnemyIndex;
+ public CardInstance PendingTargetCard => _pendingTargetCard;
+ public bool AwaitingWaveContinue => _awaitingWaveContinue;
+ public bool RunLost => _runLost;
+ public int CurrentWaveNumber => _runManager == null ? 1 : _runManager.CurrentWaveNumber;
public event Action StateChanged;
public event Action CombatLog;
private void Awake()
{
- runManager = GetComponent();
+ _runManager = GetComponent();
}
private void OnEnable()
@@ -91,7 +91,7 @@ namespace KernelPanic.Combat
public void StartCombat()
{
- if (runConfig == null)
+ if (_runConfig == null)
{
Debug.LogWarning("CombatManager.StartCombat called without a RunConfig.");
return;
@@ -102,7 +102,7 @@ namespace KernelPanic.Combat
public void StartCombat(RunConfig config)
{
- runConfig = config;
+ _runConfig = config;
StartCombat();
}
@@ -121,20 +121,20 @@ namespace KernelPanic.Combat
{
if (currentPhase == TurnPhase.Execute && !IsCombatPaused)
{
- pendingTargetCard = null;
- selectedEnemyIndex = -1;
+ _pendingTargetCard = null;
+ _selectedEnemyIndex = -1;
SetPhase(TurnPhase.Interpret);
}
}
public void ContinueToNextWave()
{
- if (!awaitingWaveContinue || runLost)
+ if (!_awaitingWaveContinue || _runLost)
{
return;
}
- awaitingWaveContinue = false;
+ _awaitingWaveContinue = false;
DecayJavaWarmupForNextWave();
StartCoroutine(ContinueToNextWaveSequenced());
}
@@ -152,18 +152,18 @@ namespace KernelPanic.Combat
return;
}
- if (index < 0 || index >= enemies.Count)
+ if (index < 0 || index >= _enemies.Count)
{
- selectedEnemyIndex = -1;
+ _selectedEnemyIndex = -1;
StateChanged?.Invoke();
return;
}
- selectedEnemyIndex = index;
- if (pendingTargetCard != null)
+ _selectedEnemyIndex = index;
+ if (_pendingTargetCard != null)
{
- CardInstance card = pendingTargetCard;
- pendingTargetCard = null;
+ CardInstance card = _pendingTargetCard;
+ _pendingTargetCard = null;
PlayCard(card);
return;
}
@@ -173,7 +173,7 @@ namespace KernelPanic.Combat
public bool PlayCard(CardInstance card)
{
- if (currentPhase != TurnPhase.Execute || IsCombatPaused || card == null || handController == null || playerState == null)
+ if (currentPhase != TurnPhase.Execute || IsCombatPaused || card == null || _handController == null || _playerState == null)
{
return false;
}
@@ -191,46 +191,46 @@ namespace KernelPanic.Combat
cost = Mathf.Max(0, cost - 1);
}
- if (playerState.Cycles < cost)
+ if (_playerState.Cycles < cost)
{
Log($"not enough cycles for {GetCardName(card)}");
return false;
}
- if (RequiresTarget(card) && selectedEnemyIndex < 0)
+ if (RequiresTarget(card) && _selectedEnemyIndex < 0)
{
- pendingTargetCard = card;
+ _pendingTargetCard = card;
Log($"select target for {GetCardName(card)}");
StateChanged?.Invoke();
return false;
}
- if (!handController.Remove(card))
+ if (!_handController.Remove(card))
{
return false;
}
- playerState.Cycles -= cost;
+ _playerState.Cycles -= cost;
ResolutionTrack track = card.Definition.ResolutionTrack;
- card.MarkPlayedThisTurn(cardsPlayedThisTurn == 0);
- cardsPlayedThisTurn++;
- cardsPlayedThisWave++;
+ card.MarkPlayedThisTurn(_cardsPlayedThisTurn == 0);
+ _cardsPlayedThisTurn++;
+ _cardsPlayedThisWave++;
if (IsDistro("arch"))
{
- playerState.ArchBtwStacks++;
+ _playerState.ArchBtwStacks++;
}
- playerState.HasPreviousPlayedCardLanguage = playerState.HasLastPlayedCardLanguage;
- if (playerState.HasLastPlayedCardLanguage)
+ _playerState.HasPreviousPlayedCardLanguage = _playerState.HasLastPlayedCardLanguage;
+ if (_playerState.HasLastPlayedCardLanguage)
{
- playerState.PreviousPlayedCardLanguage = playerState.LastPlayedCardLanguage;
+ _playerState.PreviousPlayedCardLanguage = _playerState.LastPlayedCardLanguage;
}
- playerState.LastPlayedCardLanguage = card.Definition.Language;
- playerState.HasLastPlayedCardLanguage = true;
+ _playerState.LastPlayedCardLanguage = card.Definition.Language;
+ _playerState.HasLastPlayedCardLanguage = true;
if (card.Definition.Language == Language.Java)
{
- javaCardsPlayedThisCombat++;
+ _javaCardsPlayedThisCombat++;
}
GameEvents.RaiseCardPlayed(new CardPlayedEvent(card, track));
@@ -239,68 +239,68 @@ namespace KernelPanic.Combat
if (fedoraBonus)
{
- fedoraCardsDiscountedThisTurn++;
- bool rawhideChargeUsed = rawhideBonusCharges > 0;
+ _fedoraCardsDiscountedThisTurn++;
+ bool rawhideChargeUsed = _rawhideBonusCharges > 0;
if (rawhideChargeUsed)
{
- rawhideBonusCharges--;
+ _rawhideBonusCharges--;
}
- if (!IsCrashImmune(card) && RandomRoll.RollRange(1, 100, new RollContext(playerState)) <= fedoraCrashChance)
+ if (!IsCrashImmune(card) && RandomRoll.RollRange(1, 100, new RollContext(_playerState)) <= _fedoraCrashChance)
{
- fedoraCrashChance = 10;
- if (IsDistro("fedora") && runConfig.DistroVersion >= 4)
+ _fedoraCrashChance = 10;
+ if (IsDistro("fedora") && _runConfig.DistroVersion >= 4)
{
- playerState.Cycles += 1;
+ _playerState.Cycles += 1;
}
- deckController.Discard(card);
- pendingTargetCard = null;
- selectedEnemyIndex = -1;
+ _deckController.Discard(card);
+ _pendingTargetCard = null;
+ _selectedEnemyIndex = -1;
Log($"{GetCardName(card)} crashed under bleeding edge");
StateChanged?.Invoke();
return true;
}
- fedoraCrashChance = Mathf.Min(90, fedoraCrashChance + 10);
+ _fedoraCrashChance = Mathf.Min(90, _fedoraCrashChance + 10);
card.MarkFedoraNonCrashBonus();
- playerState.DamageMultiplierPercent = runConfig.DistroVersion >= 2 ? 175 : 150;
+ _playerState.DamageMultiplierPercent = _runConfig.DistroVersion >= 2 ? 175 : 150;
ApplyFedoraGrowth(card, rawhideChargeUsed);
}
- if (card.Definition.Language == Language.C && cCardsPlayedThisTurn == 0 && HasPackageEffect(PackageEffectKind.FirstCThisTurnDamageMultiplier, out PackageEffectData cPackageEffect))
+ if (card.Definition.Language == Language.C && _cCardsPlayedThisTurn == 0 && HasPackageEffect(PackageEffectKind.FirstCThisTurnDamageMultiplier, out PackageEffectData cPackageEffect))
{
- playerState.CurrentCardDamageMultiplierPercent = Mathf.Max(100, cPackageEffect.Amount);
+ _playerState.CurrentCardDamageMultiplierPercent = Mathf.Max(100, cPackageEffect.Amount);
}
switch (track)
{
case ResolutionTrack.InterpreterQueue:
card.MarkQueued();
- interpreterQueue.Enqueue(card);
+ _interpreterQueue.Enqueue(card);
break;
case ResolutionTrack.LazyStack:
- lazyStack.Enqueue(card);
+ _lazyStack.Enqueue(card);
break;
default:
ExecuteCardEffects(card, ResolveTargets(card));
ResolveCardRiders(card);
- deckController.Discard(card);
+ _deckController.Discard(card);
GameEvents.RaiseCardResolved(new CardResolvedEvent(card, ResolutionTrack.Native));
- selectedEnemyIndex = -1;
+ _selectedEnemyIndex = -1;
break;
}
- playerState.DamageMultiplierPercent = 100;
- playerState.CurrentCardDamageMultiplierPercent = 100;
+ _playerState.DamageMultiplierPercent = 100;
+ _playerState.CurrentCardDamageMultiplierPercent = 100;
if (card.Definition.Language == Language.C)
{
- cCardsPlayedThisTurn++;
+ _cCardsPlayedThisTurn++;
}
ApplyPackageCardPlayedEffects(card);
- pendingTargetCard = null;
+ _pendingTargetCard = null;
TryApplyUbuntuEmptyHandRefill();
StateChanged?.Invoke();
return true;
@@ -321,7 +321,7 @@ namespace KernelPanic.Combat
int cost = GetCardCost(card);
if (card?.Definition != null && card.Definition.Language == Language.Java)
{
- cost -= javaCardsPlayedThisCombat + javaCardsDiscountThisTurn;
+ cost -= _javaCardsPlayedThisCombat + _javaCardsDiscountThisTurn;
}
cost = ApplyPackageCostRules(card, cost);
@@ -337,7 +337,7 @@ namespace KernelPanic.Combat
{
string cardId = card?.Definition == null ? string.Empty : card.Definition.Id;
string key = $"{cardId}:{message}";
- if (loggedEffectTodos.Add(key))
+ if (_loggedEffectTodos.Add(key))
{
Log(message);
}
@@ -353,12 +353,12 @@ namespace KernelPanic.Combat
public void AddNextTurnCycleBonus(int amount)
{
- nextTurnCycleBonus += Mathf.Max(0, amount);
+ _nextTurnCycleBonus += Mathf.Max(0, amount);
}
public void AddQueuedRepeatCharges(int amount)
{
- queuedRepeatCharges += Mathf.Max(0, amount);
+ _queuedRepeatCharges += Mathf.Max(0, amount);
if (amount > 0)
{
ReportEffectResult($"next {amount} queued card(s) resolve twice");
@@ -373,34 +373,34 @@ namespace KernelPanic.Combat
return;
}
- javaCardsDiscountThisTurn += safeAmount;
+ _javaCardsDiscountThisTurn += safeAmount;
ReportEffectResult($"Java cards cost -{safeAmount} this turn");
}
public int ConsumeQueuedResolutionCount(CardInstance card)
{
- if (queuedRepeatCharges <= 0 || card == null)
+ if (_queuedRepeatCharges <= 0 || card == null)
{
return 1;
}
- queuedRepeatCharges--;
+ _queuedRepeatCharges--;
return 2;
}
public void GrantRawhideBonus(int charges)
{
- rawhideBonusCharges += Mathf.Max(0, charges);
+ _rawhideBonusCharges += Mathf.Max(0, charges);
}
public void AddIncomingAttackHalfCharges(int charges)
{
- if (playerState == null)
+ if (_playerState == null)
{
return;
}
- playerState.IncomingAttackHalfCharges += Mathf.Max(0, charges);
+ _playerState.IncomingAttackHalfCharges += Mathf.Max(0, charges);
ReportEffectResult($"next {charges} enemy attack(s) halved");
}
@@ -416,13 +416,13 @@ namespace KernelPanic.Combat
public void ScheduleUpdateManagerRepeat(int damageAmount)
{
- delayedEffects.Add(DelayedCombatEffect.UpdateManagerRepeat(damageAmount, 2));
+ _delayedEffects.Add(DelayedCombatEffect.UpdateManagerRepeat(damageAmount, 2));
ReportEffectResult("update manager scheduled repeat");
}
public void ScheduleTimeshiftRestore(int uptime)
{
- delayedEffects.Add(DelayedCombatEffect.TimeshiftRestore(uptime, 2));
+ _delayedEffects.Add(DelayedCombatEffect.TimeshiftRestore(uptime, 2));
ReportEffectResult($"timeshift snapshot recorded {uptime} uptime");
}
@@ -434,7 +434,7 @@ namespace KernelPanic.Combat
return false;
}
- IReadOnlyList source = generatedCardPool.Count > 0 ? generatedCardPool : runConfig?.StartingDeck;
+ IReadOnlyList source = _generatedCardPool.Count > 0 ? _generatedCardPool : _runConfig?.StartingDeck;
if (source == null)
{
return false;
@@ -455,7 +455,7 @@ namespace KernelPanic.Combat
public void SetGeneratedCardPool(IEnumerable cardPool)
{
- generatedCardPool.Clear();
+ _generatedCardPool.Clear();
if (cardPool == null)
{
return;
@@ -465,7 +465,7 @@ namespace KernelPanic.Combat
{
if (card != null)
{
- generatedCardPool.Add(card);
+ _generatedCardPool.Add(card);
}
}
}
@@ -476,7 +476,7 @@ namespace KernelPanic.Combat
List pool = new();
HashSet seenIds = new();
- IReadOnlyList source = generatedCardPool.Count > 0 ? generatedCardPool : runConfig?.StartingDeck;
+ IReadOnlyList source = _generatedCardPool.Count > 0 ? _generatedCardPool : _runConfig?.StartingDeck;
if (source == null)
{
return false;
@@ -501,7 +501,7 @@ namespace KernelPanic.Combat
return false;
}
- int index = RandomRoll.RollRange(0, pool.Count - 1, new RollContext(playerState));
+ int index = RandomRoll.RollRange(0, pool.Count - 1, new RollContext(_playerState));
CardDefinition selected = pool[index];
generatedCard = new CardInstance(selected)
{
@@ -531,12 +531,12 @@ namespace KernelPanic.Combat
currentPhase = nextPhase;
GameEvents.RaisePhaseChanged(new PhaseChangedEvent(previousPhase, nextPhase));
- if (phaseCoroutine != null)
+ if (_phaseCoroutine != null)
{
- StopCoroutine(phaseCoroutine);
+ StopCoroutine(_phaseCoroutine);
}
- phaseCoroutine = StartCoroutine(EnterPhase(nextPhase));
+ _phaseCoroutine = StartCoroutine(EnterPhase(nextPhase));
}
private static TurnPhase GetNextPhase(TurnPhase phase)
@@ -593,72 +593,72 @@ namespace KernelPanic.Combat
private IEnumerator BootCombatSequenced()
{
- runLost = false;
- awaitingWaveContinue = false;
- loggedEffectTodos.Clear();
- ubuntuEmptyHandRefillUsed = false;
- fedoraCrashChance = 10;
- fedoraCardsDiscountedThisTurn = 0;
- cardsPlayedThisTurn = 0;
- cardsPlayedThisWave = 0;
- cCardsPlayedThisTurn = 0;
- turnNumberThisWave = 0;
- packageShieldBonusTriggeredThisTurn = false;
- packageTimeshiftTriggeredThisWave = false;
- packageInterpreterQueueShieldTriggeredThisWave = false;
- packageNativeDamageTriggeredThisWave = false;
- javaCardsPlayedThisCombat = 0;
- javaCardsDiscountThisTurn = 0;
- rawhideBonusCharges = 0;
- queuedRepeatCharges = 0;
- nextTurnCycleBonus = 0;
- delayedEffects.Clear();
- RandomRoll.Seed(runConfig.RunSeed);
- playerState = new CombatantState(runManager.EffectiveMaxUptime(), runManager.EffectiveRam(), runManager.EffectiveMaxCycles());
- ApplyVersionState(playerState);
- ApplyPackageState(playerState);
+ _runLost = false;
+ _awaitingWaveContinue = false;
+ _loggedEffectTodos.Clear();
+ _ubuntuEmptyHandRefillUsed = false;
+ _fedoraCrashChance = 10;
+ _fedoraCardsDiscountedThisTurn = 0;
+ _cardsPlayedThisTurn = 0;
+ _cardsPlayedThisWave = 0;
+ _cCardsPlayedThisTurn = 0;
+ _turnNumberThisWave = 0;
+ _packageShieldBonusTriggeredThisTurn = false;
+ _packageTimeshiftTriggeredThisWave = false;
+ _packageInterpreterQueueShieldTriggeredThisWave = false;
+ _packageNativeDamageTriggeredThisWave = false;
+ _javaCardsPlayedThisCombat = 0;
+ _javaCardsDiscountThisTurn = 0;
+ _rawhideBonusCharges = 0;
+ _queuedRepeatCharges = 0;
+ _nextTurnCycleBonus = 0;
+ _delayedEffects.Clear();
+ RandomRoll.Seed(_runConfig.RunSeed);
+ _playerState = new CombatantState(_runManager.EffectiveMaxUptime(), _runManager.EffectiveRam(), _runManager.EffectiveMaxCycles());
+ ApplyVersionState(_playerState);
+ ApplyPackageState(_playerState);
yield return StartWaveSequenced(preservePlayerUptime: false);
- Log($"booted {runConfig.Distro.DisplayName} with {runManager.RunDeck.Count} cards");
+ Log($"booted {_runConfig.Distro.DisplayName} with {_runManager.RunDeck.Count} cards");
StateChanged?.Invoke();
}
private IEnumerator StartWaveSequenced(bool preservePlayerUptime)
{
- int carriedUptime = playerState == null ? 0 : playerState.CurrentUptime;
- if (!preservePlayerUptime || playerState == null)
+ int carriedUptime = _playerState == null ? 0 : _playerState.CurrentUptime;
+ if (!preservePlayerUptime || _playerState == null)
{
- playerState = new CombatantState(runManager.EffectiveMaxUptime(), runManager.EffectiveRam(), runManager.EffectiveMaxCycles());
- ApplyVersionState(playerState);
- ApplyPackageState(playerState);
+ _playerState = new CombatantState(_runManager.EffectiveMaxUptime(), _runManager.EffectiveRam(), _runManager.EffectiveMaxCycles());
+ ApplyVersionState(_playerState);
+ ApplyPackageState(_playerState);
}
else
{
- playerState.MaxUptime = runManager.EffectiveMaxUptime();
- playerState.CurrentUptime = Mathf.Clamp(carriedUptime, 1, playerState.MaxUptime);
- playerState.Ram = runManager.EffectiveRam();
- playerState.MaxCycles = runManager.EffectiveMaxCycles();
- playerState.Cycles = 0;
- playerState.Shield = 0;
- playerState.IncomingAttackHalfCharges = 0;
- playerState.IsDefeated = false;
- playerState.MutableStatuses.Clear();
- ApplyVersionState(playerState);
- ApplyPackageState(playerState);
+ _playerState.MaxUptime = _runManager.EffectiveMaxUptime();
+ _playerState.CurrentUptime = Mathf.Clamp(carriedUptime, 1, _playerState.MaxUptime);
+ _playerState.Ram = _runManager.EffectiveRam();
+ _playerState.MaxCycles = _runManager.EffectiveMaxCycles();
+ _playerState.Cycles = 0;
+ _playerState.Shield = 0;
+ _playerState.IncomingAttackHalfCharges = 0;
+ _playerState.IsDefeated = false;
+ _playerState.MutableStatuses.Clear();
+ ApplyVersionState(_playerState);
+ ApplyPackageState(_playerState);
}
- handController = new HandController(playerState.Ram);
- selectedEnemyIndex = -1;
- pendingTargetCard = null;
- cardsPlayedThisWave = 0;
- turnNumberThisWave = 0;
- packageShieldBonusTriggeredThisTurn = false;
- packageTimeshiftTriggeredThisWave = false;
+ _handController = new HandController(_playerState.Ram);
+ _selectedEnemyIndex = -1;
+ _pendingTargetCard = null;
+ _cardsPlayedThisWave = 0;
+ _turnNumberThisWave = 0;
+ _packageShieldBonusTriggeredThisTurn = false;
+ _packageTimeshiftTriggeredThisWave = false;
ResetArchWaveState();
List startingCards = new();
- for (int i = 0; i < runManager.RunDeck.Count; i++)
+ for (int i = 0; i < _runManager.RunDeck.Count; i++)
{
- CardInstance card = runManager.RunDeck[i];
+ CardInstance card = _runManager.RunDeck[i];
if (card != null)
{
card.ResetCombatState();
@@ -666,15 +666,15 @@ namespace KernelPanic.Combat
}
}
- deckController.Initialize(startingCards);
- interpreterQueue.Clear();
- enemies.Clear();
+ _deckController.Initialize(startingCards);
+ _interpreterQueue.Clear();
+ _enemies.Clear();
SpawnStructuralEnemies();
PickEnemyIntents();
StateChanged?.Invoke();
yield return DrawOpeningHandSequenced();
ApplyPackageWaveStartEffects();
- skipNextAllocateDraw = true;
+ _skipNextAllocateDraw = true;
}
private IEnumerator AllocateTurnSequenced()
@@ -684,21 +684,22 @@ namespace KernelPanic.Combat
yield break;
}
- playerState.Cycles = playerState.MaxCycles;
- fedoraCardsDiscountedThisTurn = 0;
- cardsPlayedThisTurn = 0;
- cCardsPlayedThisTurn = 0;
- turnNumberThisWave++;
- packageShieldBonusTriggeredThisTurn = false;
- javaCardsDiscountThisTurn = 0;
+ RevivePendingEnemies();
+ _playerState.Cycles = _playerState.MaxCycles;
+ _fedoraCardsDiscountedThisTurn = 0;
+ _cardsPlayedThisTurn = 0;
+ _cCardsPlayedThisTurn = 0;
+ _turnNumberThisWave++;
+ _packageShieldBonusTriggeredThisTurn = false;
+ _javaCardsDiscountThisTurn = 0;
ApplyPackageTurnStartEffects();
- if (nextTurnCycleBonus > 0)
+ if (_nextTurnCycleBonus > 0)
{
- playerState.Cycles += nextTurnCycleBonus;
- Log($"deferred cycle grant: +{nextTurnCycleBonus}");
- nextTurnCycleBonus = 0;
+ _playerState.Cycles += _nextTurnCycleBonus;
+ Log($"deferred cycle grant: +{_nextTurnCycleBonus}");
+ _nextTurnCycleBonus = 0;
}
- statusEffects.Tick(playerState, StatusTickTiming.StartOfTurn, playerState, damagePipeline);
+ _statusEffects.Tick(_playerState, StatusTickTiming.StartOfTurn, _playerState, _damagePipeline);
StateChanged?.Invoke();
if (CheckLoss())
{
@@ -707,9 +708,9 @@ namespace KernelPanic.Combat
TryApplyUbuntuAptUpdate();
- if (skipNextAllocateDraw)
+ if (_skipNextAllocateDraw)
{
- skipNextAllocateDraw = false;
+ _skipNextAllocateDraw = false;
}
else
{
@@ -721,7 +722,7 @@ namespace KernelPanic.Combat
private IEnumerator InterpretQueueSequenced()
{
- while (interpreterQueue.TryDequeue(out CardInstance card))
+ while (_interpreterQueue.TryDequeue(out CardInstance card))
{
LogPlayIntent(card);
int resolutionCount = ConsumeQueuedResolutionCount(card);
@@ -735,7 +736,7 @@ namespace KernelPanic.Combat
}
ResolveCardRiders(card);
- deckController.Discard(card);
+ _deckController.Discard(card);
GameEvents.RaiseCardResolved(new CardResolvedEvent(card, ResolutionTrack.InterpreterQueue));
StateChanged?.Invoke();
if (CheckWinOrLoss())
@@ -752,15 +753,16 @@ namespace KernelPanic.Combat
private IEnumerator ProcessEnemiesSequenced()
{
- for (int i = 0; i < enemies.Count; i++)
+ int enemyCountAtPhaseStart = _enemies.Count;
+ for (int i = 0; i < enemyCountAtPhaseStart && i < _enemies.Count; i++)
{
- EnemyInstance enemy = enemies[i];
+ EnemyInstance enemy = _enemies[i];
if (enemy.State.IsDefeated)
{
continue;
}
- statusEffects.Tick(enemy.State, StatusTickTiming.StartOfTurn, playerState, damagePipeline);
+ _statusEffects.Tick(enemy.State, StatusTickTiming.StartOfTurn, _playerState, _damagePipeline);
RemoveDefeatedEnemies();
if (CheckWinOrLoss())
{
@@ -772,12 +774,13 @@ namespace KernelPanic.Combat
continue;
}
- Log($"{enemy.Name} executes {enemy.CurrentIntent.DisplayText}");
+ Log($"{enemy.Name} executes {enemy.DisplayIntent.DisplayText}");
GameEvents.RaiseEnemyWouldAct(new EnemyWouldActEvent(enemy));
StateChanged?.Invoke();
yield return Wait(CombatTuning.EnemyTelegraphDelaySeconds);
- ExecuteEnemyIntent(enemy);
+ ExecuteEnemyIntent(enemy, i);
+ enemy.MarkTurnSurvived();
GameEvents.RaiseEnemyActed(new EnemyActedEvent(enemy, enemy.CurrentIntent));
StateChanged?.Invoke();
if (CheckLoss())
@@ -789,14 +792,15 @@ namespace KernelPanic.Combat
}
PickEnemyIntents();
+ ResetEnemyTurnLethalMarkers();
StateChanged?.Invoke();
}
private void GarbageCollect()
{
- selectedEnemyIndex = -1;
- pendingTargetCard = null;
- statusEffects.Tick(playerState, StatusTickTiming.EndOfTurn, playerState, damagePipeline);
+ _selectedEnemyIndex = -1;
+ _pendingTargetCard = null;
+ _statusEffects.Tick(_playerState, StatusTickTiming.EndOfTurn, _playerState, _damagePipeline);
if (CheckLoss())
{
return;
@@ -816,16 +820,16 @@ namespace KernelPanic.Combat
private IEnumerator DrawOpeningHandSequenced()
{
- int openingDraw = Mathf.Min(CombatTuning.OpeningHandSize, playerState.Ram);
+ int openingDraw = Mathf.Min(CombatTuning.OpeningHandSize, _playerState.Ram);
yield return DrawCardsToHandSequenced(openingDraw, "opening hand");
}
private IEnumerator DrawForTurnSequenced()
{
int requested = CombatTuning.DrawPerTurn;
- if (CombatTuning.MinimumHandFloor > 0 && handController.Cards.Count < CombatTuning.MinimumHandFloor && deckController.AvailableToDrawCount > 0)
+ if (CombatTuning.MinimumHandFloor > 0 && _handController.Cards.Count < CombatTuning.MinimumHandFloor && _deckController.AvailableToDrawCount > 0)
{
- requested = Mathf.Max(requested, CombatTuning.MinimumHandFloor - handController.Cards.Count);
+ requested = Mathf.Max(requested, CombatTuning.MinimumHandFloor - _handController.Cards.Count);
}
yield return DrawCardsToHandSequenced(requested, "turn draw");
@@ -833,23 +837,23 @@ namespace KernelPanic.Combat
private IEnumerator DrawCardsToHandSequenced(int requestedCount, string label)
{
- if (handController == null || playerState == null || requestedCount <= 0)
+ if (_handController == null || _playerState == null || requestedCount <= 0)
{
yield break;
}
- int room = handController.RemainingRam;
+ int room = _handController.RemainingRam;
if (room <= 0)
{
Log($"{label}: hand full");
yield break;
}
- IReadOnlyList drawn = deckController.Draw(requestedCount);
+ IReadOnlyList drawn = _deckController.Draw(requestedCount);
int added = 0;
for (int i = 0; i < drawn.Count; i++)
{
- if (handController.Add(drawn[i]))
+ if (_handController.Add(drawn[i]))
{
added++;
StateChanged?.Invoke();
@@ -857,11 +861,11 @@ namespace KernelPanic.Combat
}
else
{
- deckController.AddToDrawPile(drawn[i], shuffle: false);
+ _deckController.AddToDrawPile(drawn[i], shuffle: false);
}
}
- if (added == 0 && deckController.AvailableToDrawCount == 0)
+ if (added == 0 && _deckController.AvailableToDrawCount == 0)
{
Log($"{label}: no cards to draw");
}
@@ -877,73 +881,73 @@ namespace KernelPanic.Combat
private void TryApplyUbuntuAptUpdate()
{
- if (!IsDistro("ubuntu") || handController == null || playerState == null || handController.RemainingRam <= 0)
+ if (!IsDistro("ubuntu") || _handController == null || _playerState == null || _handController.RemainingRam <= 0)
{
return;
}
- int lookCount = runConfig.DistroVersion >= 2 ? 3 : 2;
- if (!deckController.TryDrawCheapestFromTop(lookCount, out CardInstance card))
+ int lookCount = _runConfig.DistroVersion >= 2 ? 3 : 2;
+ if (!_deckController.TryDrawCheapestFromTop(lookCount, out CardInstance card))
{
return;
}
- if (runConfig.DistroVersion >= 4)
+ if (_runConfig.DistroVersion >= 4)
{
card.TemporaryCostDelta -= 1;
}
- if (handController.Add(card))
+ if (_handController.Add(card))
{
Log($"apt update: staged {GetCardName(card)} from top {lookCount}");
}
else
{
- deckController.AddToDrawPile(card, shuffle: false);
+ _deckController.AddToDrawPile(card, shuffle: false);
}
}
private void TryApplyUbuntuEmptyHandRefill()
{
- if (!IsDistro("ubuntu") || runConfig.DistroVersion < 5 || ubuntuEmptyHandRefillUsed || handController == null || handController.Cards.Count > 0)
+ if (!IsDistro("ubuntu") || _runConfig.DistroVersion < 5 || _ubuntuEmptyHandRefillUsed || _handController == null || _handController.Cards.Count > 0)
{
return;
}
- ubuntuEmptyHandRefillUsed = true;
- StartCoroutine(DrawCardsToHandSequenced(playerState.Ram, "ubuntu 24.04 refill"));
+ _ubuntuEmptyHandRefillUsed = true;
+ StartCoroutine(DrawCardsToHandSequenced(_playerState.Ram, "ubuntu 24.04 refill"));
}
private bool CanApplyFedoraBonus()
{
- return CanApplyFedoraPassiveBonus() || rawhideBonusCharges > 0;
+ return CanApplyFedoraPassiveBonus() || _rawhideBonusCharges > 0;
}
private void DecayJavaWarmupForNextWave()
{
- if (javaCardsPlayedThisCombat <= 0)
+ if (_javaCardsPlayedThisCombat <= 0)
{
return;
}
- javaCardsPlayedThisCombat = Mathf.Max(0, javaCardsPlayedThisCombat - 1);
- Log($"Java JIT cooled: discount now -{javaCardsPlayedThisCombat}");
+ _javaCardsPlayedThisCombat = Mathf.Max(0, _javaCardsPlayedThisCombat - 1);
+ Log($"Java JIT cooled: discount now -{_javaCardsPlayedThisCombat}");
}
private bool CanApplyFedoraPassiveBonus()
{
- if (!IsDistro("fedora") || playerState == null)
+ if (!IsDistro("fedora") || _playerState == null)
{
return false;
}
- int limit = runConfig.DistroVersion >= 5 || HasOnDistroPackageEffect(PackageEffectKind.DnfFedoraPassive, out PackageEffectData dnfEffect) && dnfEffect.EnableFedoraSecondCardPassive ? 2 : 1;
- return fedoraCardsDiscountedThisTurn < limit;
+ int limit = _runConfig.DistroVersion >= 5 || HasOnDistroPackageEffect(PackageEffectKind.DnfFedoraPassive, out PackageEffectData dnfEffect) && dnfEffect.EnableFedoraSecondCardPassive ? 2 : 1;
+ return _fedoraCardsDiscountedThisTurn < limit;
}
private void ApplyFedoraGrowth(CardInstance card, bool rawhideChargeUsed)
{
- if (!IsDistro("fedora") || runConfig.DistroVersion < 3 || card == null || !card.WasFirstCardThisTurn)
+ if (!IsDistro("fedora") || _runConfig.DistroVersion < 3 || card == null || !card.WasFirstCardThisTurn)
{
return;
}
@@ -962,14 +966,14 @@ namespace KernelPanic.Combat
return cost;
}
- IReadOnlyList packages = runConfig?.EquippedPackages;
+ IReadOnlyList packages = _runConfig?.EquippedPackages;
if (packages == null)
{
return cost;
}
- int nextTurnCard = cardsPlayedThisTurn + 1;
- int nextWaveCard = cardsPlayedThisWave + 1;
+ int nextTurnCard = _cardsPlayedThisTurn + 1;
+ int nextWaveCard = _cardsPlayedThisWave + 1;
for (int i = 0; i < packages.Count; i++)
{
PackageInstance package = packages[i];
@@ -978,7 +982,7 @@ namespace KernelPanic.Combat
continue;
}
- PackageEffectData effect = package.EffectFor(runConfig?.Distro?.Id);
+ PackageEffectData effect = package.EffectFor(_runConfig?.Distro?.Id);
switch (effect.Kind)
{
case PackageEffectKind.FirstCardEachWaveCostReduction:
@@ -1013,7 +1017,7 @@ namespace KernelPanic.Combat
}
state.JavaScriptFlatDamageBonus = 0;
- IReadOnlyList packages = runConfig?.EquippedPackages;
+ IReadOnlyList packages = _runConfig?.EquippedPackages;
if (packages == null)
{
return;
@@ -1021,7 +1025,7 @@ namespace KernelPanic.Combat
for (int i = 0; i < packages.Count; i++)
{
- PackageEffectData effect = packages[i] == null ? default : packages[i].EffectFor(runConfig?.Distro?.Id);
+ PackageEffectData effect = packages[i] == null ? default : packages[i].EffectFor(_runConfig?.Distro?.Id);
if (effect.Kind == PackageEffectKind.JavaScriptFlatDamage)
{
state.JavaScriptFlatDamageBonus += Mathf.Max(0, effect.Amount);
@@ -1031,7 +1035,7 @@ namespace KernelPanic.Combat
private void ApplyPackageWaveStartEffects()
{
- IReadOnlyList packages = runConfig?.EquippedPackages;
+ IReadOnlyList packages = _runConfig?.EquippedPackages;
if (packages == null)
{
return;
@@ -1045,7 +1049,7 @@ namespace KernelPanic.Combat
continue;
}
- PackageEffectData effect = package.EffectFor(runConfig?.Distro?.Id);
+ PackageEffectData effect = package.EffectFor(_runConfig?.Distro?.Id);
switch (effect.Kind)
{
case PackageEffectKind.WaveDraw:
@@ -1055,13 +1059,13 @@ namespace KernelPanic.Combat
GrantPlayerShield(Mathf.Max(0, effect.Amount), package.Definition.DisplayName);
break;
case PackageEffectKind.FirstTurnEachWaveShield:
- if (turnNumberThisWave == 0)
+ if (_turnNumberThisWave == 0)
{
GrantPlayerShield(Mathf.Max(0, effect.Amount), package.Definition.DisplayName);
}
break;
case PackageEffectKind.FirstTurnFirstWaveDraw:
- if (CurrentWaveNumber == 1 && turnNumberThisWave == 0)
+ if (CurrentWaveNumber == 1 && _turnNumberThisWave == 0)
{
StartCoroutine(DrawCardsToHandSequenced(Mathf.Max(0, effect.Amount), package.Definition.DisplayName));
}
@@ -1077,28 +1081,28 @@ namespace KernelPanic.Combat
{
if (HasPackageEffect(PackageEffectKind.EveryNthTurnShield, out PackageEffectData effect)
&& effect.Threshold > 0
- && turnNumberThisWave % effect.Threshold == 0)
+ && _turnNumberThisWave % effect.Threshold == 0)
{
GrantPlayerShield(Mathf.Max(0, effect.Amount), "cron");
}
if (HasPackageEffect(PackageEffectKind.EveryNthTurnDraw, out effect)
&& effect.Threshold > 0
- && turnNumberThisWave % effect.Threshold == 0)
+ && _turnNumberThisWave % effect.Threshold == 0)
{
StartCoroutine(DrawCardsToHandSequenced(Mathf.Max(0, effect.Amount), "udev"));
}
- if (turnNumberThisWave == 1
+ if (_turnNumberThisWave == 1
&& HasPackageEffect(PackageEffectKind.FirstTurnEachWaveCycle, out effect)
&& effect.Amount > 0)
{
- playerState.Cycles += effect.Amount;
+ _playerState.Cycles += effect.Amount;
Log($"apt-fast: gained {effect.Amount} Cycle");
}
if (HasPackageEffect(PackageEffectKind.StartTurnNoDebuffShield, out effect)
- && !HasHarmfulStatus(playerState))
+ && !HasHarmfulStatus(_playerState))
{
GrantPlayerShield(Mathf.Max(0, effect.Amount), "auditd");
}
@@ -1111,79 +1115,79 @@ namespace KernelPanic.Combat
private void ApplyPackageCardPlayedEffects(CardInstance card)
{
- if (card == null || playerState == null)
+ if (card == null || _playerState == null)
{
return;
}
if (HasPackageEffect(PackageEffectKind.ThirdCardEachTurnGenerate, out PackageEffectData aptEffect)
- && cardsPlayedThisTurn == Mathf.Max(1, aptEffect.Threshold))
+ && _cardsPlayedThisTurn == Mathf.Max(1, aptEffect.Threshold))
{
GeneratePackageLanguageCard("apt");
if (aptEffect.RefundCycle)
{
- playerState.Cycles += 1;
+ _playerState.Cycles += 1;
Log("apt refunded 1 Cycle");
}
}
if (HasPackageEffect(PackageEffectKind.DnfFedoraPassive, out PackageEffectData dnfEffect)
- && cardsPlayedThisTurn == 1
+ && _cardsPlayedThisTurn == 1
&& dnfEffect.Amount > 0)
{
- playerState.FlatEffectBonus += dnfEffect.Amount;
+ _playerState.FlatEffectBonus += dnfEffect.Amount;
Log($"DNF wave buff +{dnfEffect.Amount} damage");
}
- if (!packageInterpreterQueueShieldTriggeredThisWave
+ if (!_packageInterpreterQueueShieldTriggeredThisWave
&& card.Definition.ResolutionTrack == ResolutionTrack.InterpreterQueue
&& HasPackageEffect(PackageEffectKind.FirstInterpreterQueueCardEachWaveShield, out PackageEffectData queueEffect))
{
- packageInterpreterQueueShieldTriggeredThisWave = true;
+ _packageInterpreterQueueShieldTriggeredThisWave = true;
GrantPlayerShield(Mathf.Max(0, queueEffect.Amount), "dbus");
}
- if (!packageNativeDamageTriggeredThisWave
+ if (!_packageNativeDamageTriggeredThisWave
&& card.Definition.ResolutionTrack == ResolutionTrack.Native
&& HasPackageEffect(PackageEffectKind.FirstNativeCardEachWaveFlatDamage, out PackageEffectData nativeEffect)
&& nativeEffect.Amount > 0)
{
- packageNativeDamageTriggeredThisWave = true;
- playerState.FlatEffectBonus += nativeEffect.Amount;
+ _packageNativeDamageTriggeredThisWave = true;
+ _playerState.FlatEffectBonus += nativeEffect.Amount;
Log($"make: first Native card +{nativeEffect.Amount} damage");
}
if (HasPackageEffect(PackageEffectKind.EveryNthCardEachWaveCycle, out PackageEffectData cycleEffect)
&& cycleEffect.Threshold > 0
- && cardsPlayedThisWave % cycleEffect.Threshold == 0
+ && _cardsPlayedThisWave % cycleEffect.Threshold == 0
&& cycleEffect.Amount > 0)
{
- playerState.Cycles += cycleEffect.Amount;
+ _playerState.Cycles += cycleEffect.Amount;
Log($"systemd-timer: gained {cycleEffect.Amount} Cycle");
}
}
public void GrantPlayerShield(int amount, string label)
{
- if (playerState == null || amount <= 0)
+ if (_playerState == null || amount <= 0)
{
return;
}
int total = amount;
- if (!packageShieldBonusTriggeredThisTurn
+ if (!_packageShieldBonusTriggeredThisTurn
&& HasPackageEffect(PackageEffectKind.FirstShieldEachTurnBonus, out PackageEffectData effect))
{
int bonus = Mathf.Max(0, effect.Amount);
if (bonus > 0)
{
total += bonus;
- packageShieldBonusTriggeredThisTurn = true;
+ _packageShieldBonusTriggeredThisTurn = true;
Log($"SELinux shield +{bonus}");
}
}
- playerState.Shield += total;
+ _playerState.Shield += total;
if (!string.IsNullOrWhiteSpace(label))
{
Log($"{label}: gained {total} Shield");
@@ -1192,14 +1196,14 @@ namespace KernelPanic.Combat
private void GeneratePackageLanguageCard(string label)
{
- if (handController == null || handController.RemainingRam <= 0)
+ if (_handController == null || _handController.RemainingRam <= 0)
{
return;
}
- Language language = RandomRoll.RollRange(0, 1, new RollContext(playerState)) == 0
- ? runConfig.PrimaryLanguage
- : runConfig.SecondaryLanguage;
+ Language language = RandomRoll.RollRange(0, 1, new RollContext(_playerState)) == 0
+ ? _runConfig.PrimaryLanguage
+ : _runConfig.SecondaryLanguage;
if (!TryCreateGeneratedCard(language, Rarity.Common, out CardInstance generated))
{
@@ -1207,7 +1211,7 @@ namespace KernelPanic.Combat
return;
}
- if (handController.Add(generated))
+ if (_handController.Add(generated))
{
Log($"{label}: generated {GetCardName(generated)} at 0c");
StateChanged?.Invoke();
@@ -1216,7 +1220,7 @@ namespace KernelPanic.Combat
private void GenerateAurPackageCard(PackageEffectData effect)
{
- if (handController == null || handController.RemainingRam <= 0)
+ if (_handController == null || _handController.RemainingRam <= 0)
{
return;
}
@@ -1224,7 +1228,7 @@ namespace KernelPanic.Combat
int maxRarity = Mathf.Clamp(effect.Threshold, 0, (int)Rarity.Legendary);
List pool = new();
HashSet seenIds = new(StringComparer.OrdinalIgnoreCase);
- IReadOnlyList source = generatedCardPool.Count > 0 ? generatedCardPool : runConfig?.StartingDeck;
+ IReadOnlyList source = _generatedCardPool.Count > 0 ? _generatedCardPool : _runConfig?.StartingDeck;
if (source == null)
{
Log("AUR: TODO card-generation pool unavailable");
@@ -1255,14 +1259,14 @@ namespace KernelPanic.Combat
return;
}
- int index = RandomRoll.RollRange(0, pool.Count - 1, new RollContext(playerState));
+ int index = RandomRoll.RollRange(0, pool.Count - 1, new RollContext(_playerState));
CardDefinition selected = pool[index];
CardInstance generated = new(selected)
{
TemporaryCostDelta = -Mathf.Clamp(effect.Amount, 0, selected.CycleCost)
};
- if (handController.Add(generated))
+ if (_handController.Add(generated))
{
string discount = effect.Amount > 0 ? " at -1c" : string.Empty;
Log($"AUR: generated {GetCardName(generated)}{discount}");
@@ -1272,74 +1276,153 @@ namespace KernelPanic.Combat
private void HandlePackageDamageDealt(DamageDealtEvent payload)
{
- if (payload.Target == playerState && playerState != null && playerState.ArchRollingReleaseRecoveredThisHit)
+ HandleEnemyDamageDealt(payload);
+
+ if (payload.Target == _playerState && _playerState != null && _playerState.ArchRollingReleaseRecoveredThisHit)
{
- playerState.ArchRollingReleaseRecoveredThisHit = false;
+ _playerState.ArchRollingReleaseRecoveredThisHit = false;
string recovery = "rolling release: recovered at 1 Uptime";
- if (playerState.ArchRollingReleaseShieldOnSave > 0 || playerState.ArchRollingReleaseCyclesOnSave > 0)
+ if (_playerState.ArchRollingReleaseShieldOnSave > 0 || _playerState.ArchRollingReleaseCyclesOnSave > 0)
{
- recovery += $", +{playerState.ArchRollingReleaseShieldOnSave} Shield, +{playerState.ArchRollingReleaseCyclesOnSave} Cycle";
+ recovery += $", +{_playerState.ArchRollingReleaseShieldOnSave} Shield, +{_playerState.ArchRollingReleaseCyclesOnSave} Cycle";
}
Log(recovery);
StateChanged?.Invoke();
}
- if (payload.Target != playerState
+ if (payload.Target != _playerState
|| payload.UptimeDamage <= 0
- || packageTimeshiftTriggeredThisWave
+ || _packageTimeshiftTriggeredThisWave
|| !HasPackageEffect(PackageEffectKind.WaveThresholdRestore, out PackageEffectData effect))
{
return;
}
int thresholdPercent = Mathf.Clamp(effect.Threshold, 1, 100);
- int restoreTarget = Mathf.CeilToInt(playerState.MaxUptime * (thresholdPercent / 100f));
- if (playerState.CurrentUptime >= restoreTarget)
+ int restoreTarget = Mathf.CeilToInt(_playerState.MaxUptime * (thresholdPercent / 100f));
+ if (_playerState.CurrentUptime >= restoreTarget)
{
return;
}
- packageTimeshiftTriggeredThisWave = true;
- playerState.CurrentUptime = restoreTarget;
+ _packageTimeshiftTriggeredThisWave = true;
+ _playerState.CurrentUptime = restoreTarget;
if (effect.CleanseDebuffs)
{
- statusEffects.CleanseHarmful(playerState);
+ _statusEffects.CleanseHarmful(_playerState);
}
Log($"Timeshift restored uptime to {thresholdPercent}%");
StateChanged?.Invoke();
}
- private void ResetArchBtwAtEndOfTurn()
+ private void HandleEnemyDamageDealt(DamageDealtEvent payload)
{
- if (!IsDistro("arch") || playerState == null || playerState.ArchBtwStacks <= 0 || ArchBtwPersistsThisWave())
+ EnemyInstance enemy = FindEnemy(payload.Target);
+ if (enemy == null)
{
return;
}
- playerState.ArchBtwStacks = 0;
+ if (payload.Amount > 0)
+ {
+ enemy.MarkDamaged();
+ }
+
+ if (!enemy.HasBehavior(EnemyBehaviorFlags.Revive)
+ || payload.UptimeDamage <= 0
+ || enemy.State.CurrentUptime > 0)
+ {
+ return;
+ }
+
+ int uptimeBeforeHit = enemy.State.CurrentUptime + payload.UptimeDamage;
+ int reapThreshold = Mathf.CeilToInt(enemy.MaxUptime * (EnemyArchetypeCatalog.ZombieReapThresholdPercent / 100f));
+ bool reaped = enemy.HasRevived || enemy.LethalHitThisTurn || uptimeBeforeHit <= reapThreshold;
+ enemy.MarkLethalHit();
+ if (reaped)
+ {
+ enemy.MarkReaped();
+ Log($"{enemy.Name} reaped");
+ return;
+ }
+
+ enemy.MarkPendingRevive();
+ Log($"{enemy.Name} dropped to 0 uptime; reviving next exchange");
+ StateChanged?.Invoke();
+ }
+
+ private EnemyInstance FindEnemy(CombatantState state)
+ {
+ if (state == null)
+ {
+ return null;
+ }
+
+ for (int i = 0; i < _enemies.Count; i++)
+ {
+ if (_enemies[i].State == state)
+ {
+ return _enemies[i];
+ }
+ }
+
+ return null;
+ }
+
+ private void RevivePendingEnemies()
+ {
+ for (int i = 0; i < _enemies.Count; i++)
+ {
+ EnemyInstance enemy = _enemies[i];
+ if (!enemy.PendingRevive)
+ {
+ continue;
+ }
+
+ enemy.ReviveAtHalfUptime();
+ Log($"{enemy.Name} revived at {enemy.CurrentUptime} uptime");
+ }
+ }
+
+ private void ResetEnemyTurnLethalMarkers()
+ {
+ for (int i = 0; i < _enemies.Count; i++)
+ {
+ _enemies[i].ResetTurnLethalMarker();
+ }
+ }
+
+ private void ResetArchBtwAtEndOfTurn()
+ {
+ if (!IsDistro("arch") || _playerState == null || _playerState.ArchBtwStacks <= 0 || ArchBtwPersistsThisWave())
+ {
+ return;
+ }
+
+ _playerState.ArchBtwStacks = 0;
}
private void ResetArchWaveState()
{
- if (playerState == null)
+ if (_playerState == null)
{
return;
}
- playerState.ArchBtwStacks = 0;
- playerState.ArchRollingReleaseSavesRemaining = IsDistro("arch") ? runConfig.DistroVersion >= 5 ? 2 : 1 : 0;
- playerState.ArchRollingReleaseAvailableThisWave = playerState.ArchRollingReleaseSavesRemaining > 0;
- playerState.ArchRollingReleaseRecoveredThisHit = false;
- playerState.HasLastPlayedCardLanguage = false;
- playerState.HasPreviousPlayedCardLanguage = false;
+ _playerState.ArchBtwStacks = 0;
+ _playerState.ArchRollingReleaseSavesRemaining = IsDistro("arch") ? _runConfig.DistroVersion >= 5 ? 2 : 1 : 0;
+ _playerState.ArchRollingReleaseAvailableThisWave = _playerState.ArchRollingReleaseSavesRemaining > 0;
+ _playerState.ArchRollingReleaseRecoveredThisHit = false;
+ _playerState.HasLastPlayedCardLanguage = false;
+ _playerState.HasPreviousPlayedCardLanguage = false;
}
private bool ArchBtwPersistsThisWave()
{
return IsDistro("arch")
- && (runConfig.DistroVersion >= 5
+ && (_runConfig.DistroVersion >= 5
|| HasOnDistroPackageEffect(PackageEffectKind.FirstCThisTurnDamageMultiplier, out PackageEffectData effect)
&& effect.PersistArchBtw);
}
@@ -1347,7 +1430,7 @@ namespace KernelPanic.Combat
private bool HasOnDistroPackageEffect(PackageEffectKind kind, out PackageEffectData effect)
{
effect = default;
- IReadOnlyList packages = runConfig?.EquippedPackages;
+ IReadOnlyList packages = _runConfig?.EquippedPackages;
if (packages == null)
{
return false;
@@ -1356,12 +1439,12 @@ namespace KernelPanic.Combat
for (int i = 0; i < packages.Count; i++)
{
PackageInstance package = packages[i];
- if (package?.Definition == null || !package.Definition.IsIntendedFor(runConfig?.Distro?.Id))
+ if (package?.Definition == null || !package.Definition.IsIntendedFor(_runConfig?.Distro?.Id))
{
continue;
}
- effect = package.EffectFor(runConfig?.Distro?.Id);
+ effect = package.EffectFor(_runConfig?.Distro?.Id);
if (effect.Kind == kind)
{
return true;
@@ -1374,7 +1457,7 @@ namespace KernelPanic.Combat
private bool HasPackageEffect(PackageEffectKind kind, out PackageEffectData effect)
{
effect = default;
- IReadOnlyList packages = runConfig?.EquippedPackages;
+ IReadOnlyList packages = _runConfig?.EquippedPackages;
if (packages == null)
{
return false;
@@ -1388,7 +1471,7 @@ namespace KernelPanic.Combat
continue;
}
- effect = package.EffectFor(runConfig?.Distro?.Id);
+ effect = package.EffectFor(_runConfig?.Distro?.Id);
if (effect.Kind == kind)
{
return true;
@@ -1429,21 +1512,21 @@ namespace KernelPanic.Combat
return;
}
- state.forceMaxRolls = IsDistro("mint");
+ state.ForceMaxRolls = IsDistro("mint");
state.IgnoreDamageMultipliers = IsDistro("mint");
- state.AllowFlatDamageBuffs = IsDistro("mint") && runConfig.DistroVersion >= 4;
- state.FlatEffectBonus = IsDistro("mint") && runConfig.DistroVersion >= 2 ? 2 : 0;
+ state.AllowFlatDamageBuffs = IsDistro("mint") && _runConfig.DistroVersion >= 4;
+ state.FlatEffectBonus = IsDistro("mint") && _runConfig.DistroVersion >= 2 ? 2 : 0;
state.DamageMultiplierPercent = 100;
state.CurrentCardDamageMultiplierPercent = 100;
- state.ArchBtwDamagePerStack = IsDistro("arch") && runConfig.DistroVersion >= 2 ? 2 : 1;
- state.ArchMakepkgBtwMultiplier = IsDistro("arch") && runConfig.DistroVersion >= 3 ? 3 : 2;
- state.ArchRollingReleaseShieldOnSave = IsDistro("arch") && runConfig.DistroVersion >= 4 ? 15 : 0;
- state.ArchRollingReleaseCyclesOnSave = IsDistro("arch") && runConfig.DistroVersion >= 4 ? 2 : 0;
+ state.ArchBtwDamagePerStack = IsDistro("arch") && _runConfig.DistroVersion >= 2 ? 2 : 1;
+ state.ArchMakepkgBtwMultiplier = IsDistro("arch") && _runConfig.DistroVersion >= 3 ? 3 : 2;
+ state.ArchRollingReleaseShieldOnSave = IsDistro("arch") && _runConfig.DistroVersion >= 4 ? 15 : 0;
+ state.ArchRollingReleaseCyclesOnSave = IsDistro("arch") && _runConfig.DistroVersion >= 4 ? 2 : 0;
}
private bool IsDistro(string id)
{
- return string.Equals(runConfig?.Distro?.Id, id, StringComparison.OrdinalIgnoreCase);
+ return string.Equals(_runConfig?.Distro?.Id, id, StringComparison.OrdinalIgnoreCase);
}
public bool CanPlayerReceiveCardShield(CardInstance card)
@@ -1464,7 +1547,7 @@ namespace KernelPanic.Combat
private bool IsCurrentDistroExclusiveCard(CardInstance card)
{
CardDefinition definition = card?.Definition;
- IReadOnlyList exclusiveCards = runConfig?.Distro?.ExclusiveCards;
+ IReadOnlyList exclusiveCards = _runConfig?.Distro?.ExclusiveCards;
if (definition == null || exclusiveCards == null)
{
return false;
@@ -1490,7 +1573,7 @@ namespace KernelPanic.Combat
private CombatContext BuildContext(CardInstance card, IReadOnlyList targets)
{
- return new CombatContext(card, playerState, targets, this, damagePipeline, statusEffects, deckController, handController, enemies);
+ return new CombatContext(card, _playerState, targets, this, _damagePipeline, _statusEffects, _deckController, _handController, _enemies);
}
private void ExecuteCardEffects(CardInstance card, IReadOnlyList targets)
@@ -1510,7 +1593,7 @@ namespace KernelPanic.Combat
private void ResolveCardRiders(CardInstance card)
{
- if (card == null || card.DrawRider <= 0 || handController == null || playerState == null)
+ if (card == null || card.DrawRider <= 0 || _handController == null || _playerState == null)
{
return;
}
@@ -1530,9 +1613,9 @@ namespace KernelPanic.Combat
return LivingEnemyStates();
}
- if (selectedEnemyIndex >= 0 && selectedEnemyIndex < enemies.Count && !enemies[selectedEnemyIndex].State.IsDefeated)
+ if (_selectedEnemyIndex >= 0 && _selectedEnemyIndex < _enemies.Count && !_enemies[_selectedEnemyIndex].State.IsDefeated)
{
- return new[] { enemies[selectedEnemyIndex].State };
+ return new[] { _enemies[_selectedEnemyIndex].State };
}
if (RequiresEnemyTarget(card))
@@ -1585,7 +1668,7 @@ namespace KernelPanic.Combat
private List LivingEnemyStates()
{
- return enemies
+ return _enemies
.Where(enemy => enemy.State != null && !enemy.State.IsDefeated)
.Select(enemy => enemy.State)
.ToList();
@@ -1593,11 +1676,11 @@ namespace KernelPanic.Combat
private CombatantState FirstLivingEnemyState()
{
- for (int i = 0; i < enemies.Count; i++)
+ for (int i = 0; i < _enemies.Count; i++)
{
- if (enemies[i].State != null && !enemies[i].State.IsDefeated)
+ if (_enemies[i].State != null && !_enemies[i].State.IsDefeated)
{
- return enemies[i].State;
+ return _enemies[i].State;
}
}
@@ -1606,68 +1689,83 @@ namespace KernelPanic.Combat
private void RemoveDefeatedEnemies()
{
- for (int i = enemies.Count - 1; i >= 0; i--)
+ for (int i = _enemies.Count - 1; i >= 0; i--)
{
- if (enemies[i].State.IsDefeated)
+ if (_enemies[i].State.IsDefeated)
{
- Log($"{enemies[i].Name} exited");
- runManager?.AddBits(CombatTuning.BitsPerKill);
- enemies.RemoveAt(i);
+ if (_enemies[i].PendingRevive)
+ {
+ continue;
+ }
+
+ Log($"{_enemies[i].Name} exited");
+ _runManager?.AddBits(CombatTuning.BitsPerKill);
+ _enemies.RemoveAt(i);
}
}
- if (selectedEnemyIndex >= enemies.Count)
+ if (_selectedEnemyIndex >= _enemies.Count)
{
- selectedEnemyIndex = -1;
+ _selectedEnemyIndex = -1;
}
}
private void SpawnStructuralEnemies()
{
int wave = CurrentWaveNumber;
- int count = Mathf.Clamp(CombatTuning.BaseEnemiesPerWave + ((wave - 1) * CombatTuning.AdditionalEnemiesPerWave), CombatTuning.BaseEnemiesPerWave, CombatTuning.MaxEnemiesPerWave);
+ int countGrowth = ((wave - 1) / 2) * CombatTuning.AdditionalEnemiesPerWave;
+ int count = Mathf.Clamp(CombatTuning.BaseEnemiesPerWave + countGrowth, CombatTuning.BaseEnemiesPerWave, CombatTuning.MaxEnemiesPerWave);
int waveUptimeBonus = Mathf.Max(0, wave - 1) * CombatTuning.EnemyUptimeGrowthPerWave;
for (int i = 0; i < count; i++)
{
- // TODO: Replace this low-HP additive wave stub with the real encounter/difficulty system.
- int baseUptime = RandomRoll.RollRange(CombatTuning.BaseEnemyUptimeMin, CombatTuning.BaseEnemyUptimeMax, RollContext.None);
- int uptime = baseUptime + waveUptimeBonus;
- enemies.Add(new EnemyInstance("zombie_process", uptime, CreateZombieProcessIntentPool(wave, i)));
+ SpawnEnemy(CreatePlaceholderArchetypeId(wave, i), waveUptimeBonus);
}
}
- private static IReadOnlyList CreateZombieProcessIntentPool(int wave, int slotIndex)
+ private void SpawnEnemy(string archetypeId, int uptimeBonus)
{
- // TODO: Replace this code-shaped pool with EnemyDefinition-provided intent data.
- int attack = GetZombieProcessAttack(wave, slotIndex);
- return new[]
- {
- new EnemyIntent(EnemyIntentKind.Attack, attack, attack, Language.C, "attack", ">"),
- new EnemyIntent(EnemyIntentKind.Defend, 3 + wave, 3 + wave, Language.C, "defend", "#"),
- new EnemyIntent(EnemyIntentKind.StatusAttack, CombatTuning.EnemyStatusAttackDamage, CombatTuning.EnemyStatusAttackDamage, Language.C, "leak", "!", false, StatusType.MemoryLeak, 1, CombatTuning.EnemyStatusDuration)
- };
+ EnemyArchetypeDescriptor archetype = EnemyArchetypeCatalog.Get(archetypeId);
+ int baseUptime = RandomRoll.RollRange(archetype.BaseUptimeMin, archetype.BaseUptimeMax, RollContext.None);
+ _enemies.Add(new EnemyInstance(archetype, baseUptime + Mathf.Max(0, uptimeBonus)));
}
- private static int GetZombieProcessAttack(int wave, int slotIndex)
+ private static string CreatePlaceholderArchetypeId(int wave, int slotIndex)
{
- int safeWave = Mathf.Max(1, wave);
- int waveGrowth = (safeWave - 1) / Mathf.Max(1, CombatTuning.EnemyAttackGrowthEveryWaves);
- int slotVariance = CombatTuning.EnemySlotAttackVariance <= 0 ? 0 : slotIndex % (CombatTuning.EnemySlotAttackVariance + 1);
- return CombatTuning.BaseEnemyAttack + waveGrowth + slotVariance;
+ // TODO: Replace this structural mix with encounter/difficulty assets.
+ string[] pool = wave switch
+ {
+ <= 1 => new[] { "zombie_process", "memory_leak" },
+ 2 => new[] { "zombie_process", "memory_leak", "fork_bomb", "firewalld" },
+ 3 => new[] { "zombie_process", "memory_leak", "fork_bomb", "firewalld", "daemon" },
+ _ => new[] { "zombie_process", "memory_leak", "fork_bomb", "firewalld", "daemon", "cron_job" }
+ };
+
+ if (slotIndex == 0)
+ {
+ return wave % 2 == 0 ? "firewalld" : "zombie_process";
+ }
+
+ if (slotIndex == 1 && wave >= 2)
+ {
+ return wave % 3 == 0 ? "fork_bomb" : "memory_leak";
+ }
+
+ int index = RandomRoll.RollRange(0, pool.Length - 1, RollContext.None);
+ return pool[index];
}
private void PickEnemyIntents()
{
- for (int i = 0; i < enemies.Count; i++)
+ for (int i = 0; i < _enemies.Count; i++)
{
- if (!enemies[i].State.IsDefeated)
+ if (!_enemies[i].State.IsDefeated)
{
- enemies[i].PickNextIntent();
+ _enemies[i].PickNextIntent();
}
}
}
- private void ExecuteEnemyIntent(EnemyInstance enemy)
+ private void ExecuteEnemyIntent(EnemyInstance enemy, int enemyIndex)
{
EnemyIntent intent = enemy.CurrentIntent;
int value = intent.MinValue == intent.MaxValue
@@ -1678,37 +1776,91 @@ namespace KernelPanic.Combat
{
case EnemyIntentKind.Attack:
value = ApplyIncomingAttackMitigation(value);
- DamageResult attackResult = damagePipeline.DealDamage(new DamageRequest(enemy.State, playerState, value, intent.DamageType, intent.TrueDamage, false));
+ DamageResult attackResult = _damagePipeline.DealDamage(new DamageRequest(enemy.State, _playerState, value, intent.DamageType, intent.TrueDamage, false));
GameEvents.RaisePlayerDamaged(new PlayerDamagedEvent(enemy, attackResult.FinalAmount));
break;
case EnemyIntentKind.Defend:
- enemy.State.Shield += value;
+ if (enemy.HasBehavior(EnemyBehaviorFlags.DefendAllies))
+ {
+ DefendSelfAndAdjacent(enemyIndex, value);
+ }
+ else
+ {
+ enemy.State.Shield += value;
+ }
break;
case EnemyIntentKind.StatusAttack:
- DamageResult statusResult = damagePipeline.DealDamage(new DamageRequest(enemy.State, playerState, value, intent.DamageType, intent.TrueDamage, false));
+ DamageResult statusResult = _damagePipeline.DealDamage(new DamageRequest(enemy.State, _playerState, value, intent.DamageType, intent.TrueDamage, false));
GameEvents.RaisePlayerDamaged(new PlayerDamagedEvent(enemy, statusResult.FinalAmount));
if (intent.StatusStacks > 0)
{
- statusEffects.Apply(playerState, intent.StatusType, intent.StatusStacks, intent.StatusDuration, enemy.State, skipNextTick: true);
+ _statusEffects.Apply(_playerState, intent.StatusType, intent.StatusStacks, intent.StatusDuration, enemy.State, skipNextTick: true);
}
break;
case EnemyIntentKind.Buff:
Log("TODO: buff intent needs enemy buff/status support.");
break;
case EnemyIntentKind.Special:
- Log("TODO: special intent needs enemy-specific behavior hooks.");
+ ExecuteSpecialEnemyIntent(enemy);
break;
}
+
+ enemy.AdvanceCountdownAfterAction();
+ }
+
+ private void ExecuteSpecialEnemyIntent(EnemyInstance enemy)
+ {
+ if (enemy.HasBehavior(EnemyBehaviorFlags.Split))
+ {
+ TrySplitForkBomb(enemy);
+ return;
+ }
+
+ Log("TODO: special intent needs enemy-specific behavior hooks.");
+ }
+
+ private void TrySplitForkBomb(EnemyInstance source)
+ {
+ int totalForkBombs = _enemies.Count(enemy => enemy.ArchetypeId == source.ArchetypeId && !enemy.State.IsDefeated);
+ if (totalForkBombs >= EnemyArchetypeCatalog.ForkBombTotalCap)
+ {
+ Log("fork bomb: split capped");
+ return;
+ }
+
+ EnemyArchetypeDescriptor archetype = EnemyArchetypeCatalog.Get(source.ArchetypeId);
+ int uptime = Mathf.Max(1, Mathf.CeilToInt(source.MaxUptime * 0.5f));
+ EnemyInstance copy = new(archetype, uptime);
+ copy.PickNextIntent();
+ _enemies.Add(copy);
+ Log("fork bomb split");
+ }
+
+ private void DefendSelfAndAdjacent(int enemyIndex, int amount)
+ {
+ int applied = 0;
+ for (int i = enemyIndex - 1; i <= enemyIndex + 1; i++)
+ {
+ if (i < 0 || i >= _enemies.Count || _enemies[i].State.IsDefeated)
+ {
+ continue;
+ }
+
+ _enemies[i].State.Shield += amount;
+ applied++;
+ }
+
+ Log($"firewalld shielded {applied} process(es) for {amount}");
}
private int ApplyIncomingAttackMitigation(int value)
{
- if (playerState == null || playerState.IncomingAttackHalfCharges <= 0)
+ if (_playerState == null || _playerState.IncomingAttackHalfCharges <= 0)
{
return value;
}
- playerState.IncomingAttackHalfCharges--;
+ _playerState.IncomingAttackHalfCharges--;
int mitigated = Mathf.CeilToInt(Mathf.Max(0, value) * 0.5f);
Log($"SELinux enforcing halved attack {value}->{mitigated}");
return mitigated;
@@ -1716,17 +1868,17 @@ namespace KernelPanic.Combat
private void ResolveDelayedEndOfTurnEffects()
{
- for (int i = delayedEffects.Count - 1; i >= 0; i--)
+ for (int i = _delayedEffects.Count - 1; i >= 0; i--)
{
- DelayedCombatEffect effect = delayedEffects[i];
+ DelayedCombatEffect effect = _delayedEffects[i];
effect.GarbageCollectionsRemaining--;
if (effect.GarbageCollectionsRemaining > 0)
{
- delayedEffects[i] = effect;
+ _delayedEffects[i] = effect;
continue;
}
- delayedEffects.RemoveAt(i);
+ _delayedEffects.RemoveAt(i);
switch (effect.Kind)
{
case DelayedCombatEffectKind.UpdateManagerRepeat:
@@ -1744,7 +1896,7 @@ namespace KernelPanic.Combat
List targets = LivingEnemyStates();
for (int i = 0; i < targets.Count; i++)
{
- damagePipeline.DealDamage(new DamageRequest(playerState, targets[i], amount, language, false, true));
+ _damagePipeline.DealDamage(new DamageRequest(_playerState, targets[i], amount, language, false, true));
}
Log($"{label}: dealt {amount} to all enemies");
@@ -1753,24 +1905,24 @@ namespace KernelPanic.Combat
private void RestoreTimeshift(int snapshot)
{
- if (playerState == null || playerState.IsDefeated || playerState.CurrentUptime >= snapshot)
+ if (_playerState == null || _playerState.IsDefeated || _playerState.CurrentUptime >= snapshot)
{
return;
}
int target = snapshot;
- if (IsDistro("mint") && runConfig.DistroVersion >= 3)
+ if (IsDistro("mint") && _runConfig.DistroVersion >= 3)
{
- target = Mathf.Min(playerState.MaxUptime, snapshot + 2);
+ target = Mathf.Min(_playerState.MaxUptime, snapshot + 2);
}
- if (IsDistro("mint") && runConfig.DistroVersion >= 5)
+ if (IsDistro("mint") && _runConfig.DistroVersion >= 5)
{
- target = playerState.MaxUptime;
+ target = _playerState.MaxUptime;
}
- int restored = target - playerState.CurrentUptime;
- playerState.CurrentUptime = target;
+ int restored = target - _playerState.CurrentUptime;
+ _playerState.CurrentUptime = target;
Log($"timeshift restored {restored} uptime");
}
@@ -1781,15 +1933,15 @@ namespace KernelPanic.Combat
private bool CheckLoss()
{
- if (runLost || playerState == null || !playerState.IsDefeated)
+ if (_runLost || _playerState == null || !_playerState.IsDefeated)
{
- return runLost;
+ return _runLost;
}
- runLost = true;
- awaitingWaveContinue = false;
- selectedEnemyIndex = -1;
- pendingTargetCard = null;
+ _runLost = true;
+ _awaitingWaveContinue = false;
+ _selectedEnemyIndex = -1;
+ _pendingTargetCard = null;
GameEvents.RaiseEncounterLost(new EncounterLostEvent(CurrentWaveNumber));
GameEvents.RaiseRunEnded(new RunEndedEvent(playerDied: true, entropyEarned: 0));
Log("kernel panic: player uptime reached 0");
@@ -1799,14 +1951,14 @@ namespace KernelPanic.Combat
private bool CheckWin()
{
- if (awaitingWaveContinue || runLost || enemies.Count > 0)
+ if (_awaitingWaveContinue || _runLost || _enemies.Count > 0)
{
- return awaitingWaveContinue;
+ return _awaitingWaveContinue;
}
- awaitingWaveContinue = true;
- selectedEnemyIndex = -1;
- pendingTargetCard = null;
+ _awaitingWaveContinue = true;
+ _selectedEnemyIndex = -1;
+ _pendingTargetCard = null;
int clearedWave = CurrentWaveNumber;
GameEvents.RaiseEncounterWon(new EncounterWonEvent(clearedWave));
GameEvents.RaiseWaveCleared(new WaveClearedEvent(clearedWave));
@@ -1838,7 +1990,7 @@ namespace KernelPanic.Combat
return string.IsNullOrWhiteSpace(card.Definition.DisplayName) ? card.Definition.Id : card.Definition.DisplayName;
}
- private bool IsCombatPaused => awaitingWaveContinue || runLost;
+ private bool IsCombatPaused => _awaitingWaveContinue || _runLost;
private enum DelayedCombatEffectKind
{
diff --git a/Assets/Scripts/Combat/CombatTuning.cs b/Assets/Scripts/Combat/CombatTuning.cs
index 0d617f5..c5eb8c7 100644
--- a/Assets/Scripts/Combat/CombatTuning.cs
+++ b/Assets/Scripts/Combat/CombatTuning.cs
@@ -12,7 +12,7 @@ namespace KernelPanic.Combat
public const int BaseEnemyUptimeMin = 5;
public const int BaseEnemyUptimeMax = 10;
- public const int EnemyUptimeGrowthPerWave = 2;
+ public const int EnemyUptimeGrowthPerWave = 1;
public const int BaseEnemyAttack = 3;
public const int EnemyAttackGrowthEveryWaves = 2;
diff --git a/Assets/Scripts/Combat/CombatantState.cs b/Assets/Scripts/Combat/CombatantState.cs
index 3f4c95d..2e48d3b 100644
--- a/Assets/Scripts/Combat/CombatantState.cs
+++ b/Assets/Scripts/Combat/CombatantState.cs
@@ -26,7 +26,7 @@ namespace KernelPanic.Combat
public int Cycles { get; set; }
public int MaxCycles { get; set; }
public bool IsDefeated { get; set; }
- public bool forceMaxRolls { get; set; }
+ public bool ForceMaxRolls { get; set; }
public bool IgnoreDamageMultipliers { get; set; }
public bool AllowFlatDamageBuffs { get; set; }
public int FlatEffectBonus { get; set; }
diff --git a/Assets/Scripts/Combat/DamagePipeline.cs b/Assets/Scripts/Combat/DamagePipeline.cs
index b44f21b..307a93b 100644
--- a/Assets/Scripts/Combat/DamagePipeline.cs
+++ b/Assets/Scripts/Combat/DamagePipeline.cs
@@ -116,7 +116,7 @@ namespace KernelPanic.Combat
private static int ApplyCrit(int amount, DamageRequest request, out bool wasCritical)
{
wasCritical = false;
- if (!request.CanCrit || amount <= 0 || request.Source != null && request.Source.forceMaxRolls)
+ if (!request.CanCrit || amount <= 0 || request.Source != null && request.Source.ForceMaxRolls)
{
return amount;
}
diff --git a/Assets/Scripts/Combat/EnemyArchetype.cs b/Assets/Scripts/Combat/EnemyArchetype.cs
new file mode 100644
index 0000000..e099803
--- /dev/null
+++ b/Assets/Scripts/Combat/EnemyArchetype.cs
@@ -0,0 +1,138 @@
+using System;
+using System.Collections.Generic;
+using KernelPanic.Core;
+using UnityEngine;
+
+namespace KernelPanic.Combat
+{
+ [Flags]
+ public enum EnemyBehaviorFlags
+ {
+ None = 0,
+ Revive = 1 << 0,
+ Split = 1 << 1,
+ Grow = 1 << 2,
+ ObfuscateIntent = 1 << 3,
+ DefendAllies = 1 << 4,
+ Countdown = 1 << 5
+ }
+
+ public sealed class EnemyArchetypeDescriptor
+ {
+ public EnemyArchetypeDescriptor(
+ string id,
+ string displayName,
+ int baseUptimeMin,
+ int baseUptimeMax,
+ EnemyBehaviorFlags behaviorFlags,
+ IReadOnlyList intentPool)
+ {
+ Id = string.IsNullOrWhiteSpace(id) ? "unknown_process" : id;
+ DisplayName = string.IsNullOrWhiteSpace(displayName) ? Id : displayName;
+ BaseUptimeMin = Mathf.Max(1, baseUptimeMin);
+ BaseUptimeMax = Mathf.Max(BaseUptimeMin, baseUptimeMax);
+ BehaviorFlags = behaviorFlags;
+ IntentPool = intentPool ?? Array.Empty();
+ }
+
+ public string Id { get; }
+ public string DisplayName { get; }
+ public int BaseUptimeMin { get; }
+ public int BaseUptimeMax { get; }
+ public EnemyBehaviorFlags BehaviorFlags { get; }
+ public IReadOnlyList IntentPool { get; }
+ }
+
+ ///
+ /// Runtime archetype construction site. Future EnemyDefinition assets should replace this table
+ /// by providing the same descriptor data: id, display name, base Uptime range, intents, and flags.
+ ///
+ public static class EnemyArchetypeCatalog
+ {
+ public const int ZombieReapThresholdPercent = 30;
+ public const int MemoryLeakAttackGrowthPerTurn = 1;
+ public const int ForkBombTotalCap = 5;
+ public const int CronInitialCountdown = 2;
+
+ private static readonly EnemyArchetypeDescriptor ZombieProcess = new(
+ "zombie_process",
+ "Zombie Process",
+ 6,
+ 10,
+ EnemyBehaviorFlags.Revive,
+ new[]
+ {
+ new EnemyIntent(EnemyIntentKind.Attack, 3, 4, Language.C, "attack", "!")
+ });
+
+ private static readonly EnemyArchetypeDescriptor MemoryLeak = new(
+ "memory_leak",
+ "Memory Leak",
+ 4,
+ 7,
+ EnemyBehaviorFlags.Grow,
+ new[]
+ {
+ new EnemyIntent(EnemyIntentKind.StatusAttack, 1, 1, Language.C, "leak", "!", false, StatusType.MemoryLeak, 1, CombatTuning.EnemyStatusDuration)
+ });
+
+ private static readonly EnemyArchetypeDescriptor ForkBomb = new(
+ "fork_bomb",
+ "Fork Bomb",
+ 3,
+ 5,
+ EnemyBehaviorFlags.Split,
+ new[]
+ {
+ new EnemyIntent(EnemyIntentKind.Special, 0, 0, Language.C, "fork", ":", false, StatusType.MemoryLeak, 0, -1, "split")
+ });
+
+ private static readonly EnemyArchetypeDescriptor Daemon = new(
+ "daemon",
+ "Daemon",
+ 10,
+ 14,
+ EnemyBehaviorFlags.ObfuscateIntent,
+ new[]
+ {
+ new EnemyIntent(EnemyIntentKind.Attack, 6, 8, Language.C, "daemon hit", "!")
+ });
+
+ private static readonly EnemyArchetypeDescriptor Defender = new(
+ "firewalld",
+ "firewalld",
+ 5,
+ 8,
+ EnemyBehaviorFlags.DefendAllies,
+ new[]
+ {
+ new EnemyIntent(EnemyIntentKind.Defend, 3, 5, Language.C, "shield adjacent", "#"),
+ new EnemyIntent(EnemyIntentKind.Defend, 3, 5, Language.C, "shield adjacent", "#"),
+ new EnemyIntent(EnemyIntentKind.Attack, 1, 2, Language.C, "poke", "!")
+ });
+
+ private static readonly EnemyArchetypeDescriptor CronJob = new(
+ "cron_job",
+ "Cron Job",
+ 6,
+ 9,
+ EnemyBehaviorFlags.Countdown,
+ new[]
+ {
+ new EnemyIntent(EnemyIntentKind.Attack, 10, 12, Language.C, "cron fire", "@")
+ });
+
+ public static EnemyArchetypeDescriptor Get(string id)
+ {
+ return id switch
+ {
+ "memory_leak" => MemoryLeak,
+ "fork_bomb" => ForkBomb,
+ "daemon" => Daemon,
+ "firewalld" => Defender,
+ "cron_job" => CronJob,
+ _ => ZombieProcess
+ };
+ }
+ }
+}
diff --git a/Assets/Scripts/Combat/EnemyInstance.cs b/Assets/Scripts/Combat/EnemyInstance.cs
index e16d047..b5bba6e 100644
--- a/Assets/Scripts/Combat/EnemyInstance.cs
+++ b/Assets/Scripts/Combat/EnemyInstance.cs
@@ -10,24 +10,57 @@ namespace KernelPanic.Combat
private readonly List _intentPool = new();
public EnemyInstance(string name, int maxUptime, IEnumerable intentPool)
+ : this(new EnemyArchetypeDescriptor(name, name, maxUptime, maxUptime, EnemyBehaviorFlags.None, new List(intentPool ?? System.Array.Empty())), maxUptime)
{
- Name = string.IsNullOrWhiteSpace(name) ? "unknown_process" : name;
+ }
+
+ public EnemyInstance(EnemyArchetypeDescriptor archetype, int maxUptime)
+ {
+ Archetype = archetype ?? EnemyArchetypeCatalog.Get("zombie_process");
+ Name = Archetype.DisplayName;
State = new CombatantState(maxUptime, 0, 0);
- if (intentPool != null)
+ if (Archetype.IntentPool != null)
{
- _intentPool.AddRange(intentPool);
+ _intentPool.AddRange(Archetype.IntentPool);
}
+
+ CountdownRemaining = HasBehavior(EnemyBehaviorFlags.Countdown) ? EnemyArchetypeCatalog.CronInitialCountdown : 0;
}
public string Name { get; }
+ public EnemyArchetypeDescriptor Archetype { get; }
+ public string ArchetypeId => Archetype.Id;
public int CurrentUptime => State.CurrentUptime;
public int MaxUptime => State.MaxUptime;
public CombatantState State { get; }
public IReadOnlyList IntentPool => _intentPool;
public EnemyIntent CurrentIntent { get; private set; }
+ public int TurnsAlive { get; private set; }
+ public int CountdownRemaining { get; private set; }
+ public bool IntentRevealed { get; private set; }
+ public bool HasRevived { get; private set; }
+ public bool PendingRevive { get; private set; }
+ public bool Reaped { get; private set; }
+ public bool LethalHitThisTurn { get; private set; }
+ public bool HasPendingMarker => PendingRevive || HasRevived;
+ public EnemyIntent DisplayIntent => BuildDisplayIntent();
public void PickNextIntent()
{
+ if (PendingRevive)
+ {
+ CurrentIntent = new EnemyIntent(EnemyIntentKind.Special, 0, 0, KernelPanic.Core.Language.C, "reviving", "~", false, KernelPanic.Core.StatusType.MemoryLeak, 0, -1, "next");
+ return;
+ }
+
+ if (HasBehavior(EnemyBehaviorFlags.Countdown))
+ {
+ CurrentIntent = CountdownRemaining > 0
+ ? new EnemyIntent(EnemyIntentKind.Special, CountdownRemaining, CountdownRemaining, KernelPanic.Core.Language.C, "countdown", "@")
+ : _intentPool.Count > 0 ? _intentPool[0] : default;
+ return;
+ }
+
if (_intentPool.Count == 0)
{
CurrentIntent = default;
@@ -36,6 +69,92 @@ namespace KernelPanic.Combat
int index = RandomRoll.RollRange(0, _intentPool.Count - 1, new RollContext(State));
CurrentIntent = _intentPool[index];
+ if (HasBehavior(EnemyBehaviorFlags.Grow))
+ {
+ int growth = TurnsAlive * EnemyArchetypeCatalog.MemoryLeakAttackGrowthPerTurn;
+ CurrentIntent = CurrentIntent.WithValues(CurrentIntent.MinValue + growth, CurrentIntent.MaxValue + growth, "growing leak");
+ }
+ }
+
+ public bool HasBehavior(EnemyBehaviorFlags behavior)
+ {
+ return (Archetype.BehaviorFlags & behavior) != 0;
+ }
+
+ public void MarkDamaged()
+ {
+ if (HasBehavior(EnemyBehaviorFlags.ObfuscateIntent))
+ {
+ IntentRevealed = true;
+ }
+ }
+
+ public void MarkTurnSurvived()
+ {
+ TurnsAlive++;
+ }
+
+ public void ResetTurnLethalMarker()
+ {
+ LethalHitThisTurn = false;
+ }
+
+ public void MarkLethalHit()
+ {
+ LethalHitThisTurn = true;
+ }
+
+ public void MarkPendingRevive()
+ {
+ PendingRevive = true;
+ State.IsDefeated = false;
+ State.CurrentUptime = 0;
+ State.Shield = 0;
+ CurrentIntent = new EnemyIntent(EnemyIntentKind.Special, 0, 0, KernelPanic.Core.Language.C, "reviving", "~", false, KernelPanic.Core.StatusType.MemoryLeak, 0, -1, "next");
+ }
+
+ public void ReviveAtHalfUptime()
+ {
+ PendingRevive = false;
+ HasRevived = true;
+ State.IsDefeated = false;
+ State.CurrentUptime = UnityEngine.Mathf.Max(1, UnityEngine.Mathf.CeilToInt(State.MaxUptime * 0.5f));
+ PickNextIntent();
+ }
+
+ public void MarkReaped()
+ {
+ PendingRevive = false;
+ Reaped = true;
+ State.IsDefeated = true;
+ State.CurrentUptime = 0;
+ }
+
+ public void AdvanceCountdownAfterAction()
+ {
+ if (!HasBehavior(EnemyBehaviorFlags.Countdown))
+ {
+ return;
+ }
+
+ CountdownRemaining = CurrentIntent.Kind == EnemyIntentKind.Attack
+ ? EnemyArchetypeCatalog.CronInitialCountdown
+ : UnityEngine.Mathf.Max(0, CountdownRemaining - 1);
+ }
+
+ private EnemyIntent BuildDisplayIntent()
+ {
+ if (PendingRevive)
+ {
+ return new EnemyIntent(EnemyIntentKind.Special, 0, 0, KernelPanic.Core.Language.C, "reviving", "~", false, KernelPanic.Core.StatusType.MemoryLeak, 0, -1, "next");
+ }
+
+ if (HasBehavior(EnemyBehaviorFlags.ObfuscateIntent) && !IntentRevealed)
+ {
+ return new EnemyIntent(EnemyIntentKind.Special, 0, 0, KernelPanic.Core.Language.C, "unknown", "?", false, KernelPanic.Core.StatusType.MemoryLeak, 0, -1, "?");
+ }
+
+ return CurrentIntent;
}
}
}
diff --git a/Assets/Scripts/Combat/EnemyIntent.cs b/Assets/Scripts/Combat/EnemyIntent.cs
index 4ae2a30..bcf51f9 100644
--- a/Assets/Scripts/Combat/EnemyIntent.cs
+++ b/Assets/Scripts/Combat/EnemyIntent.cs
@@ -26,7 +26,8 @@ namespace KernelPanic.Combat
bool trueDamage = false,
StatusType statusType = StatusType.MemoryLeak,
int statusStacks = 0,
- int statusDuration = -1)
+ int statusDuration = -1,
+ string valueTextOverride = null)
{
Kind = kind;
MinValue = minValue;
@@ -38,6 +39,7 @@ namespace KernelPanic.Combat
StatusType = statusType;
StatusStacks = statusStacks;
StatusDuration = statusDuration;
+ ValueTextOverride = valueTextOverride;
}
public EnemyIntentKind Kind { get; }
@@ -50,8 +52,27 @@ namespace KernelPanic.Combat
public StatusType StatusType { get; }
public int StatusStacks { get; }
public int StatusDuration { get; }
+ public string ValueTextOverride { get; }
- public string ValueText => MinValue == MaxValue ? MinValue.ToString() : $"{MinValue}-{MaxValue}";
+ public string ValueText => string.IsNullOrWhiteSpace(ValueTextOverride)
+ ? MinValue == MaxValue ? MinValue.ToString() : $"{MinValue}-{MaxValue}"
+ : ValueTextOverride;
public string DisplayText => $"{IconKey} {DisplayLabel} {ValueText}";
+
+ public EnemyIntent WithValues(int minValue, int maxValue, string displayLabel = null, string iconKey = null, string valueTextOverride = null)
+ {
+ return new EnemyIntent(
+ Kind,
+ minValue,
+ maxValue,
+ DamageType,
+ displayLabel ?? DisplayLabel,
+ iconKey ?? IconKey,
+ TrueDamage,
+ StatusType,
+ StatusStacks,
+ StatusDuration,
+ valueTextOverride);
+ }
}
}
diff --git a/Assets/Scripts/Combat/RandomRoll.cs b/Assets/Scripts/Combat/RandomRoll.cs
index 3d1da91..9a17b4b 100644
--- a/Assets/Scripts/Combat/RandomRoll.cs
+++ b/Assets/Scripts/Combat/RandomRoll.cs
@@ -36,7 +36,7 @@ namespace KernelPanic.Combat
}
// Mint's passive will flip this bool; no passive sets it in this scaffold.
- if (context.Source != null && context.Source.forceMaxRolls)
+ if (context.Source != null && context.Source.ForceMaxRolls)
{
return max;
}
diff --git a/Assets/Scripts/Meta/GachaPullContext.cs b/Assets/Scripts/Meta/GachaPullContext.cs
index 614f234..6c93b9c 100644
--- a/Assets/Scripts/Meta/GachaPullContext.cs
+++ b/Assets/Scripts/Meta/GachaPullContext.cs
@@ -10,34 +10,34 @@ namespace KernelPanic.Meta
///
public static class GachaPullContext
{
- private static PendingGachaPull pendingPull;
- private static CompletedGachaPull completedPull;
+ private static PendingGachaPull _pendingPull;
+ private static CompletedGachaPull _completedPull;
- public static bool HasPendingPull => pendingPull != null;
- public static bool HasCompletedPull => completedPull != null;
+ public static bool HasPendingPull => _pendingPull != null;
+ public static bool HasCompletedPull => _completedPull != null;
public static void SetPending(string bannerId, int pullCount, int entropyTokenCount, DistroDatabase distroDatabase, PackageDatabase packageDatabase, string focusUnitId)
{
- pendingPull = new PendingGachaPull(bannerId, pullCount, entropyTokenCount, distroDatabase, packageDatabase, focusUnitId);
- completedPull = null;
+ _pendingPull = new PendingGachaPull(bannerId, pullCount, entropyTokenCount, distroDatabase, packageDatabase, focusUnitId);
+ _completedPull = null;
}
public static bool TryConsumePending(out PendingGachaPull pull)
{
- pull = pendingPull;
- pendingPull = null;
+ pull = _pendingPull;
+ _pendingPull = null;
return pull != null;
}
public static void SetCompleted(CompletedGachaPull pull)
{
- completedPull = pull;
+ _completedPull = pull;
}
public static bool TryConsumeCompleted(out CompletedGachaPull pull)
{
- pull = completedPull;
- completedPull = null;
+ pull = _completedPull;
+ _completedPull = null;
return pull != null;
}
}
diff --git a/Assets/Scripts/Meta/SaveService.cs b/Assets/Scripts/Meta/SaveService.cs
index 1c2b0f1..7e02e13 100644
--- a/Assets/Scripts/Meta/SaveService.cs
+++ b/Assets/Scripts/Meta/SaveService.cs
@@ -90,7 +90,7 @@ namespace KernelPanic.Meta
// with access to PackageDatabase) consumes this once per load to grant Cache for the extras,
// then clears it. See MainMenuController.LoadMetaState.
[NonSerialized]
- public List collapsedPackageDuplicates = new();
+ public List CollapsedPackageDuplicates = new();
public static SaveData CreateDefault()
{
@@ -236,7 +236,7 @@ namespace KernelPanic.Meta
private void NormalizeOwnedPackages()
{
- collapsedPackageDuplicates.Clear();
+ CollapsedPackageDuplicates.Clear();
Dictionary highestById = new(StringComparer.OrdinalIgnoreCase);
Dictionary extraCountById = new(StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < ownedPackages.Count; i++)
@@ -265,7 +265,7 @@ namespace KernelPanic.Meta
ownedPackages = new List(highestById.Values);
foreach (KeyValuePair extra in extraCountById)
{
- collapsedPackageDuplicates.Add(new CollapsedPackageDuplicate(extra.Key, extra.Value));
+ CollapsedPackageDuplicates.Add(new CollapsedPackageDuplicate(extra.Key, extra.Value));
}
ownedPackageIds.Clear();
diff --git a/Assets/Scripts/Run/RunContext.cs b/Assets/Scripts/Run/RunContext.cs
index 6ca03b5..538050a 100644
--- a/Assets/Scripts/Run/RunContext.cs
+++ b/Assets/Scripts/Run/RunContext.cs
@@ -12,9 +12,9 @@ namespace KernelPanic.Run
///
public static class RunContext
{
- private static PendingRun pendingRun;
+ private static PendingRun _pendingRun;
- public static bool HasPendingRun => pendingRun != null;
+ public static bool HasPendingRun => _pendingRun != null;
public static void Set(DistroDefinition distro, IReadOnlyList equippedCards, Language primaryLanguage, Language secondaryLanguage)
{
@@ -28,44 +28,44 @@ namespace KernelPanic.Run
public static void Set(DistroDefinition distro, IReadOnlyList equippedCards, IReadOnlyList equippedPackages, Language primaryLanguage, Language secondaryLanguage, int distroVersion)
{
- pendingRun = new PendingRun(distro, equippedCards, ToInstances(equippedPackages), primaryLanguage, secondaryLanguage, Environment.TickCount, distroVersion);
+ _pendingRun = new PendingRun(distro, equippedCards, ToInstances(equippedPackages), primaryLanguage, secondaryLanguage, Environment.TickCount, distroVersion);
}
public static void Set(DistroDefinition distro, IReadOnlyList equippedCards, IReadOnlyList equippedPackages, Language primaryLanguage, Language secondaryLanguage, int distroVersion)
{
- pendingRun = new PendingRun(distro, equippedCards, equippedPackages, primaryLanguage, secondaryLanguage, Environment.TickCount, distroVersion);
+ _pendingRun = new PendingRun(distro, equippedCards, equippedPackages, primaryLanguage, secondaryLanguage, Environment.TickCount, distroVersion);
}
public static bool TryCreateRunConfig(LanguageDeckDatabase languageDeckDatabase, out RunConfig config)
{
- if (pendingRun == null || pendingRun.Distro == null)
+ if (_pendingRun == null || _pendingRun.Distro == null)
{
config = null;
return false;
}
- config = pendingRun.CreateRunConfig(languageDeckDatabase);
- pendingRun = null;
+ config = _pendingRun.CreateRunConfig(languageDeckDatabase);
+ _pendingRun = null;
return true;
}
private sealed class PendingRun
{
- private readonly DistroDefinition distro;
- private readonly List equippedCards = new();
- private readonly List equippedPackages = new();
- private readonly Language primaryLanguage;
- private readonly Language secondaryLanguage;
- private readonly int runSeed;
- private readonly int distroVersion;
+ private readonly DistroDefinition _distro;
+ private readonly List _equippedCards = new();
+ private readonly List _equippedPackages = new();
+ private readonly Language _primaryLanguage;
+ private readonly Language _secondaryLanguage;
+ private readonly int _runSeed;
+ private readonly int _distroVersion;
public PendingRun(DistroDefinition distro, IReadOnlyList cards, IReadOnlyList packages, Language primaryLanguage, Language secondaryLanguage, int runSeed, int distroVersion)
{
- this.distro = distro;
- this.primaryLanguage = primaryLanguage;
- this.secondaryLanguage = secondaryLanguage;
- this.runSeed = runSeed;
- this.distroVersion = distroVersion;
+ this._distro = distro;
+ this._primaryLanguage = primaryLanguage;
+ this._secondaryLanguage = secondaryLanguage;
+ this._runSeed = runSeed;
+ this._distroVersion = distroVersion;
if (cards == null)
{
@@ -76,7 +76,7 @@ namespace KernelPanic.Run
{
if (cards[i] != null && !cards[i].IsRunOnly)
{
- equippedCards.Add(cards[i]);
+ _equippedCards.Add(cards[i]);
}
}
@@ -89,19 +89,19 @@ namespace KernelPanic.Run
{
if (packages[i]?.Definition != null)
{
- equippedPackages.Add(packages[i]);
+ _equippedPackages.Add(packages[i]);
}
}
}
- public DistroDefinition Distro => distro;
+ public DistroDefinition Distro => _distro;
public RunConfig CreateRunConfig(LanguageDeckDatabase languageDeckDatabase)
{
- List startingDeck = new(equippedCards);
- AddLanguageDeck(languageDeckDatabase, primaryLanguage, startingDeck);
- AddLanguageDeck(languageDeckDatabase, secondaryLanguage, startingDeck);
- return new RunConfig(distro, primaryLanguage, secondaryLanguage, startingDeck, equippedPackages, runSeed, distroVersion);
+ List startingDeck = new(_equippedCards);
+ AddLanguageDeck(languageDeckDatabase, _primaryLanguage, startingDeck);
+ AddLanguageDeck(languageDeckDatabase, _secondaryLanguage, startingDeck);
+ return new RunConfig(_distro, _primaryLanguage, _secondaryLanguage, startingDeck, _equippedPackages, _runSeed, _distroVersion);
}
private static void AddLanguageDeck(LanguageDeckDatabase languageDeckDatabase, Language language, List target)
diff --git a/Assets/Scripts/Run/RunManager.cs b/Assets/Scripts/Run/RunManager.cs
index aa3649a..b33593f 100644
--- a/Assets/Scripts/Run/RunManager.cs
+++ b/Assets/Scripts/Run/RunManager.cs
@@ -18,39 +18,39 @@ namespace KernelPanic.Run
[SerializeField] private bool isRunActive;
[SerializeField] private bool isPlayerDead;
- private readonly List runDeck = new();
- private readonly List repositoryOffers = new();
- private readonly HashSet soldOfferKeys = new(StringComparer.OrdinalIgnoreCase);
- private int bits;
- private int rerollsThisVisit;
- private int maxUptimeBonus;
- private int maxCyclesBonus;
- private int ramBonus;
- private int packageMaxUptimeBonus;
- private int packageMaxCyclesBonus;
- private int packageRamBonus;
- private int wavesCleared;
- private int accruedBandwidth;
- private int accruedEntropy;
- private bool repositoryVisitActive;
- private bool rewardsSettled;
+ private readonly List _runDeck = new();
+ private readonly List _repositoryOffers = new();
+ private readonly HashSet _soldOfferKeys = new(StringComparer.OrdinalIgnoreCase);
+ private int _bits;
+ private int _rerollsThisVisit;
+ private int _maxUptimeBonus;
+ private int _maxCyclesBonus;
+ private int _ramBonus;
+ private int _packageMaxUptimeBonus;
+ private int _packageMaxCyclesBonus;
+ private int _packageRamBonus;
+ private int _wavesCleared;
+ private int _accruedBandwidth;
+ private int _accruedEntropy;
+ private bool _repositoryVisitActive;
+ private bool _rewardsSettled;
public int CurrentWaveNumber => currentWaveNumber;
public bool IsRunActive => isRunActive;
public bool IsPlayerDead => isPlayerDead;
public RunConfig CurrentConfig { get; private set; }
- public IReadOnlyList RunDeck => runDeck;
- public IReadOnlyList RepositoryOffers => repositoryOffers;
- public bool RepositoryVisitActive => repositoryVisitActive;
- public int Bits => bits;
- public int RerollCost => CombatTuning.RerollBaseCost + (rerollsThisVisit * CombatTuning.RerollCostStep);
- public int MaxUptimeBonus => maxUptimeBonus;
- public int MaxCyclesBonus => maxCyclesBonus;
- public int RamBonus => ramBonus;
- public int WavesCleared => wavesCleared;
- public int AccruedBandwidth => accruedBandwidth;
- public int AccruedEntropy => accruedEntropy;
- public bool RewardsSettled => rewardsSettled;
+ public IReadOnlyList RunDeck => _runDeck;
+ public IReadOnlyList RepositoryOffers => _repositoryOffers;
+ public bool RepositoryVisitActive => _repositoryVisitActive;
+ public int Bits => _bits;
+ public int RerollCost => CombatTuning.RerollBaseCost + (_rerollsThisVisit * CombatTuning.RerollCostStep);
+ public int MaxUptimeBonus => _maxUptimeBonus;
+ public int MaxCyclesBonus => _maxCyclesBonus;
+ public int RamBonus => _ramBonus;
+ public int WavesCleared => _wavesCleared;
+ public int AccruedBandwidth => _accruedBandwidth;
+ public int AccruedEntropy => _accruedEntropy;
+ public bool RewardsSettled => _rewardsSettled;
public event Action RepositoryChanged;
@@ -72,18 +72,18 @@ namespace KernelPanic.Run
isRunActive = true;
isPlayerDead = false;
currentWaveNumber = 1;
- bits = 0;
- rerollsThisVisit = 0;
- maxUptimeBonus = 0;
- maxCyclesBonus = 0;
- ramBonus = 0;
- packageMaxUptimeBonus = 0;
- packageMaxCyclesBonus = 0;
- packageRamBonus = 0;
- wavesCleared = 0;
- accruedBandwidth = 0;
- accruedEntropy = 0;
- rewardsSettled = false;
+ _bits = 0;
+ _rerollsThisVisit = 0;
+ _maxUptimeBonus = 0;
+ _maxCyclesBonus = 0;
+ _ramBonus = 0;
+ _packageMaxUptimeBonus = 0;
+ _packageMaxCyclesBonus = 0;
+ _packageRamBonus = 0;
+ _wavesCleared = 0;
+ _accruedBandwidth = 0;
+ _accruedEntropy = 0;
+ _rewardsSettled = false;
ApplyKernelPackageBonuses(config);
InitializeRunDeck(config);
StartCombat();
@@ -101,40 +101,40 @@ namespace KernelPanic.Run
return;
}
- bits += amount;
+ _bits += amount;
RepositoryChanged?.Invoke();
}
public void GenerateRepositoryOffers(CardDatabase cardDatabase, LanguageDeckDatabase languageDeckDatabase)
{
- repositoryOffers.Clear();
- rerollsThisVisit = 0;
- soldOfferKeys.Clear();
+ _repositoryOffers.Clear();
+ _rerollsThisVisit = 0;
+ _soldOfferKeys.Clear();
FillRepositoryOffers(cardDatabase, languageDeckDatabase);
- repositoryVisitActive = true;
+ _repositoryVisitActive = true;
RepositoryChanged?.Invoke();
}
public bool RerollRepositoryOffers(CardDatabase cardDatabase, LanguageDeckDatabase languageDeckDatabase)
{
int cost = RerollCost;
- if (bits < cost)
+ if (_bits < cost)
{
return false;
}
- bits -= cost;
- rerollsThisVisit++;
- repositoryOffers.Clear();
+ _bits -= cost;
+ _rerollsThisVisit++;
+ _repositoryOffers.Clear();
FillRepositoryOffers(cardDatabase, languageDeckDatabase);
- repositoryVisitActive = true;
+ _repositoryVisitActive = true;
RepositoryChanged?.Invoke();
return true;
}
public bool BuyOffer(RepositoryOffer offer, CombatantState playerState)
{
- if (offer == null || offer.Sold || bits < offer.Price)
+ if (offer == null || offer.Sold || _bits < offer.Price)
{
return false;
}
@@ -152,43 +152,43 @@ namespace KernelPanic.Run
return false;
}
- bits -= offer.Price;
+ _bits -= offer.Price;
offer.MarkSold();
- soldOfferKeys.Add(OfferKey(offer));
+ _soldOfferKeys.Add(OfferKey(offer));
RepositoryChanged?.Invoke();
return true;
}
public bool RemoveCard(CardInstance card)
{
- if (card == null || bits < CombatTuning.RemoveCardCost || runDeck.Count <= 1)
+ if (card == null || _bits < CombatTuning.RemoveCardCost || _runDeck.Count <= 1)
{
return false;
}
- if (!runDeck.Remove(card))
+ if (!_runDeck.Remove(card))
{
return false;
}
- bits -= CombatTuning.RemoveCardCost;
+ _bits -= CombatTuning.RemoveCardCost;
RepositoryChanged?.Invoke();
return true;
}
public int EffectiveMaxUptime()
{
- return Mathf.Max(1, (CurrentConfig?.Distro?.BaseUptime ?? 1) + maxUptimeBonus + packageMaxUptimeBonus);
+ return Mathf.Max(1, (CurrentConfig?.Distro?.BaseUptime ?? 1) + _maxUptimeBonus + _packageMaxUptimeBonus);
}
public int EffectiveRam()
{
- return Mathf.Max(1, (CurrentConfig?.Distro?.BaseRam ?? 1) + ramBonus + packageRamBonus);
+ return Mathf.Max(1, (CurrentConfig?.Distro?.BaseRam ?? 1) + _ramBonus + _packageRamBonus);
}
public int EffectiveMaxCycles()
{
- return Mathf.Max(1, (CurrentConfig?.Distro?.BaseCyclesPerTurn ?? 1) + maxCyclesBonus + packageMaxCyclesBonus);
+ return Mathf.Max(1, (CurrentConfig?.Distro?.BaseCyclesPerTurn ?? 1) + _maxCyclesBonus + _packageMaxCyclesBonus);
}
public static PackageStatImpact CalculatePackageStatImpact(DistroDefinition distro, IReadOnlyList packages)
@@ -232,35 +232,35 @@ namespace KernelPanic.Run
private void ApplyKernelPackageBonuses(RunConfig config)
{
PackageStatImpact impact = CalculatePackageStatImpact(config?.Distro, config?.EquippedPackages);
- packageMaxUptimeBonus = impact.UptimeBonus;
- packageMaxCyclesBonus = impact.CyclesBonus;
- packageRamBonus = impact.RamBonus;
+ _packageMaxUptimeBonus = impact.UptimeBonus;
+ _packageMaxCyclesBonus = impact.CyclesBonus;
+ _packageRamBonus = impact.RamBonus;
}
public bool TrySettleRunRewards(out int bandwidth, out int entropy)
{
- bandwidth = accruedBandwidth;
- entropy = accruedEntropy;
- if (rewardsSettled)
+ bandwidth = _accruedBandwidth;
+ entropy = _accruedEntropy;
+ if (_rewardsSettled)
{
return false;
}
- rewardsSettled = true;
+ _rewardsSettled = true;
return bandwidth > 0 || entropy > 0;
}
private void HandleWaveCleared(WaveClearedEvent payload)
{
int clearedWave = Mathf.Max(1, payload.WaveNumber);
- wavesCleared = Mathf.Max(wavesCleared, clearedWave);
- accruedBandwidth += CalculateBandwidthReward(clearedWave);
- accruedEntropy += CalculateEntropyReward(clearedWave);
+ _wavesCleared = Mathf.Max(_wavesCleared, clearedWave);
+ _accruedBandwidth += CalculateBandwidthReward(clearedWave);
+ _accruedEntropy += CalculateEntropyReward(clearedWave);
currentWaveNumber = Mathf.Max(currentWaveNumber + 1, payload.WaveNumber + 1);
- repositoryOffers.Clear();
- rerollsThisVisit = 0;
- soldOfferKeys.Clear();
- repositoryVisitActive = false;
+ _repositoryOffers.Clear();
+ _rerollsThisVisit = 0;
+ _soldOfferKeys.Clear();
+ _repositoryVisitActive = false;
GameEvents.RaiseWaveAdvanced(new WaveAdvancedEvent(currentWaveNumber));
}
@@ -268,13 +268,13 @@ namespace KernelPanic.Run
{
isRunActive = false;
isPlayerDead = payload.PlayerDied;
- bits = 0;
- repositoryOffers.Clear();
- repositoryVisitActive = false;
- runDeck.Clear();
- maxUptimeBonus = 0;
- maxCyclesBonus = 0;
- ramBonus = 0;
+ _bits = 0;
+ _repositoryOffers.Clear();
+ _repositoryVisitActive = false;
+ _runDeck.Clear();
+ _maxUptimeBonus = 0;
+ _maxCyclesBonus = 0;
+ _ramBonus = 0;
RepositoryChanged?.Invoke();
}
@@ -291,7 +291,7 @@ namespace KernelPanic.Run
private void InitializeRunDeck(RunConfig config)
{
- runDeck.Clear();
+ _runDeck.Clear();
if (config?.StartingDeck == null)
{
return;
@@ -302,7 +302,7 @@ namespace KernelPanic.Run
CardDefinition definition = config.StartingDeck[i];
if (definition != null && !definition.IsRunOnly)
{
- runDeck.Add(new CardInstance(definition));
+ _runDeck.Add(new CardInstance(definition));
}
}
}
@@ -310,16 +310,16 @@ namespace KernelPanic.Run
private void FillRepositoryOffers(CardDatabase cardDatabase, LanguageDeckDatabase languageDeckDatabase)
{
int guard = 0;
- while (repositoryOffers.Count < CombatTuning.ShopSize && guard < 80)
+ while (_repositoryOffers.Count < CombatTuning.ShopSize && guard < 80)
{
guard++;
RepositoryOffer offer = CreateRandomOffer(cardDatabase, languageDeckDatabase);
- if (offer == null || soldOfferKeys.Contains(OfferKey(offer)) || repositoryOffers.Any(existing => IsDuplicateOffer(existing, offer)))
+ if (offer == null || _soldOfferKeys.Contains(OfferKey(offer)) || _repositoryOffers.Any(existing => IsDuplicateOffer(existing, offer)))
{
continue;
}
- repositoryOffers.Add(offer);
+ _repositoryOffers.Add(offer);
}
}
@@ -353,7 +353,7 @@ namespace KernelPanic.Run
private RepositoryOffer CreateUpgradeOffer()
{
- List candidates = runDeck.Where(card => card != null && card.CanReceiveRepositoryUpgrade).ToList();
+ List candidates = _runDeck.Where(card => card != null && card.CanReceiveRepositoryUpgrade).ToList();
if (candidates.Count == 0)
{
return CreateStatOffer();
@@ -463,7 +463,7 @@ namespace KernelPanic.Run
return false;
}
- runDeck.Add(new CardInstance(card));
+ _runDeck.Add(new CardInstance(card));
return true;
}
@@ -488,7 +488,7 @@ namespace KernelPanic.Run
switch (kind)
{
case RunStatUpgradeKind.MaxCycles:
- maxCyclesBonus += CombatTuning.StatUpgradeMaxCycles;
+ _maxCyclesBonus += CombatTuning.StatUpgradeMaxCycles;
if (playerState != null)
{
playerState.MaxCycles += CombatTuning.StatUpgradeMaxCycles;
@@ -496,7 +496,7 @@ namespace KernelPanic.Run
}
return true;
case RunStatUpgradeKind.MaxUptime:
- maxUptimeBonus += CombatTuning.StatUpgradeMaxUptime;
+ _maxUptimeBonus += CombatTuning.StatUpgradeMaxUptime;
if (playerState != null)
{
playerState.MaxUptime += CombatTuning.StatUpgradeMaxUptime;
@@ -511,7 +511,7 @@ namespace KernelPanic.Run
playerState.CurrentUptime = Mathf.Min(playerState.MaxUptime, playerState.CurrentUptime + CombatTuning.StatUpgradeHeal);
return true;
case RunStatUpgradeKind.Ram:
- ramBonus += CombatTuning.StatUpgradeRam;
+ _ramBonus += CombatTuning.StatUpgradeRam;
if (playerState != null)
{
playerState.Ram += CombatTuning.StatUpgradeRam;
@@ -525,9 +525,9 @@ namespace KernelPanic.Run
public struct PackageStatImpact
{
- private readonly List uptimeSources;
- private readonly List ramSources;
- private readonly List cyclesSources;
+ private readonly List _uptimeSources;
+ private readonly List _ramSources;
+ private readonly List _cyclesSources;
public PackageStatImpact(int baseUptime, int baseRam, int baseCycles)
{
@@ -537,9 +537,9 @@ namespace KernelPanic.Run
UptimeBonus = 0;
RamBonus = 0;
CyclesBonus = 0;
- uptimeSources = new List();
- ramSources = new List();
- cyclesSources = new List();
+ _uptimeSources = new List();
+ _ramSources = new List();
+ _cyclesSources = new List();
}
public int BaseUptime { get; }
@@ -551,9 +551,9 @@ namespace KernelPanic.Run
public int FinalUptime => Mathf.Max(1, BaseUptime + UptimeBonus);
public int FinalRam => Mathf.Max(1, BaseRam + RamBonus);
public int FinalCycles => Mathf.Max(1, BaseCycles + CyclesBonus);
- public IReadOnlyList UptimeSources => uptimeSources;
- public IReadOnlyList RamSources => ramSources;
- public IReadOnlyList CyclesSources => cyclesSources;
+ public IReadOnlyList UptimeSources => _uptimeSources;
+ public IReadOnlyList RamSources => _ramSources;
+ public IReadOnlyList CyclesSources => _cyclesSources;
public void AddUptime(int amount, PackageDefinition source)
{
@@ -563,7 +563,7 @@ namespace KernelPanic.Run
}
UptimeBonus += amount;
- AddSource(source, uptimeSources);
+ AddSource(source, _uptimeSources);
}
public void AddRam(int amount, PackageDefinition source)
@@ -574,7 +574,7 @@ namespace KernelPanic.Run
}
RamBonus += amount;
- AddSource(source, ramSources);
+ AddSource(source, _ramSources);
}
public void AddCycles(int amount, PackageDefinition source)
@@ -585,7 +585,7 @@ namespace KernelPanic.Run
}
CyclesBonus += amount;
- AddSource(source, cyclesSources);
+ AddSource(source, _cyclesSources);
}
private static void AddSource(PackageDefinition source, List sources)
diff --git a/Assets/Scripts/UI/CombatSceneController.cs b/Assets/Scripts/UI/CombatSceneController.cs
index ee947d2..4a9eec5 100644
--- a/Assets/Scripts/UI/CombatSceneController.cs
+++ b/Assets/Scripts/UI/CombatSceneController.cs
@@ -12,7 +12,7 @@ namespace KernelPanic.UI
{
///
/// Runtime-built GameScene combat UI. Intent icons use a compact terminal vocabulary:
- /// ! attack/status attack, # defend, + buff, * special.
+ /// ! attack/status attack, # defend, + buff, * special, ? hidden, ~ reviving, : split, @ countdown.
///
[RequireComponent(typeof(UIDocument))]
[RequireComponent(typeof(RunManager))]
@@ -29,56 +29,56 @@ namespace KernelPanic.UI
[SerializeField] private CardDatabase cardDatabase;
[SerializeField] private LanguageDeckDatabase languageDeckDatabase;
- private UIDocument document;
- private RunManager runManager;
- private CombatManager combatManager;
- private SaveService saveService;
- private VisualElement root;
- private VisualElement statusBar;
- private Label waveLabel;
- private Label phaseLabel;
- private Label entropyLabel;
- private Label bitsLabel;
- private Label bandwidthLabel;
- private Label rootCreditsLabel;
- private Label seedLabel;
- private VisualElement playerPanel;
- private VisualElement interpreterStrip;
- private VisualElement lazyStackPile;
- private VisualElement tokenArea;
- private VisualElement enemyRow;
- private VisualElement handRow;
- private VisualElement turnResourceGrid;
- private Label logLabel;
- private VisualElement feedbackLayer;
- private VisualElement damageVignette;
- private VisualElement overlay;
- private Color distroAccent;
- private readonly Dictionary combatantElements = new();
- private readonly Dictionary handCardElements = new();
- private readonly Dictionary queueChipElements = new();
- private readonly Dictionary stackChipElements = new();
- private readonly Dictionary previousUptime = new();
- private readonly Dictionary previousShield = new();
- private readonly Dictionary feedbackBeatVersions = new();
- private readonly HashSet knownHandCards = new();
- private int queueCascadeIndex;
- private int feedbackCascadeIndex;
- private int feedbackBeatVersion;
+ private UIDocument _document;
+ private RunManager _runManager;
+ private CombatManager _combatManager;
+ private SaveService _saveService;
+ private VisualElement _root;
+ private VisualElement _statusBar;
+ private Label _waveLabel;
+ private Label _phaseLabel;
+ private Label _entropyLabel;
+ private Label _bitsLabel;
+ private Label _bandwidthLabel;
+ private Label _rootCreditsLabel;
+ private Label _seedLabel;
+ private VisualElement _playerPanel;
+ private VisualElement _interpreterStrip;
+ private VisualElement _lazyStackPile;
+ private VisualElement _tokenArea;
+ private VisualElement _enemyRow;
+ private VisualElement _handRow;
+ private VisualElement _turnResourceGrid;
+ private Label _logLabel;
+ private VisualElement _feedbackLayer;
+ private VisualElement _damageVignette;
+ private VisualElement _overlay;
+ private Color _distroAccent;
+ private readonly Dictionary _combatantElements = new();
+ private readonly Dictionary _handCardElements = new();
+ private readonly Dictionary _queueChipElements = new();
+ private readonly Dictionary _stackChipElements = new();
+ private readonly Dictionary _previousUptime = new();
+ private readonly Dictionary _previousShield = new();
+ private readonly Dictionary _feedbackBeatVersions = new();
+ private readonly HashSet _knownHandCards = new();
+ private int _queueCascadeIndex;
+ private int _feedbackCascadeIndex;
+ private int _feedbackBeatVersion;
private void Awake()
{
- document = GetComponent();
- runManager = GetComponent();
- combatManager = GetComponent();
- saveService = new SaveService();
- root = document.rootVisualElement;
+ _document = GetComponent();
+ _runManager = GetComponent();
+ _combatManager = GetComponent();
+ _saveService = new SaveService();
+ _root = _document.rootVisualElement;
LoadStyles();
ApplyTerminalFont();
BuildLayout();
- root.AddToClassList("scene-fade-hidden");
- root.schedule.Execute(() => root.RemoveFromClassList("scene-fade-hidden")).StartingIn(0);
+ _root.AddToClassList("scene-fade-hidden");
+ _root.schedule.Execute(() => _root.RemoveFromClassList("scene-fade-hidden")).StartingIn(0);
}
private void ApplyTerminalFont()
@@ -86,16 +86,16 @@ namespace KernelPanic.UI
var font = TerminalFontResolver.Resolve(null);
if (font != null)
{
- root.style.unityFontDefinition = new StyleFontDefinition(font);
+ _root.style.unityFontDefinition = new StyleFontDefinition(font);
}
}
private void OnEnable()
{
- root?.EnableInClassList("reduced-motion", UIPreferences.ReducedMotion);
- combatManager.StateChanged += Refresh;
- combatManager.CombatLog += HandleCombatLog;
- runManager.RepositoryChanged += Refresh;
+ _root?.EnableInClassList("reduced-motion", UIPreferences.ReducedMotion);
+ _combatManager.StateChanged += Refresh;
+ _combatManager.CombatLog += HandleCombatLog;
+ _runManager.RepositoryChanged += Refresh;
GameEvents.CardPlayed += HandleCardPlayed;
GameEvents.CardResolved += HandleCardResolved;
GameEvents.PhaseChanged += HandlePhaseChanged;
@@ -116,9 +116,9 @@ namespace KernelPanic.UI
return;
}
- distroAccent = config.Distro == null ? Color.white : config.Distro.AccentColor;
- combatManager.SetGeneratedCardPool(BuildGeneratedCardPool());
- runManager.StartRun(config);
+ _distroAccent = config.Distro == null ? Color.white : config.Distro.AccentColor;
+ _combatManager.SetGeneratedCardPool(BuildGeneratedCardPool());
+ _runManager.StartRun(config);
Refresh();
}
@@ -143,9 +143,9 @@ namespace KernelPanic.UI
private void OnDisable()
{
SettleRunRewardsIfNeeded();
- combatManager.StateChanged -= Refresh;
- combatManager.CombatLog -= HandleCombatLog;
- runManager.RepositoryChanged -= Refresh;
+ _combatManager.StateChanged -= Refresh;
+ _combatManager.CombatLog -= HandleCombatLog;
+ _runManager.RepositoryChanged -= Refresh;
GameEvents.CardPlayed -= HandleCardPlayed;
GameEvents.CardResolved -= HandleCardResolved;
GameEvents.PhaseChanged -= HandlePhaseChanged;
@@ -222,11 +222,11 @@ namespace KernelPanic.UI
private void HandleMissingRunConfig()
{
- root.Clear();
- root.AddToClassList("combat-root");
+ _root.Clear();
+ _root.AddToClassList("combat-root");
Label error = new("combat bootstrap failed: no RunContext and no editor fallback data");
error.AddToClassList("overlay-line");
- root.Add(error);
+ _root.Add(error);
Debug.LogError("GameScene could not start: no RunContext and no Ubuntu editor fallback data.");
}
@@ -235,91 +235,91 @@ namespace KernelPanic.UI
StyleSheet rarityStyleSheet = Resources.Load(SharedRarityStyleResourcePath);
if (rarityStyleSheet != null)
{
- root.styleSheets.Add(rarityStyleSheet);
+ _root.styleSheets.Add(rarityStyleSheet);
}
StyleSheet styleSheet = Resources.Load(StyleResourcePath);
if (styleSheet != null)
{
- root.styleSheets.Add(styleSheet);
+ _root.styleSheets.Add(styleSheet);
}
StyleSheet scrollbarStyleSheet = Resources.Load(SharedScrollbarStyleResourcePath);
if (scrollbarStyleSheet != null)
{
- root.styleSheets.Add(scrollbarStyleSheet);
+ _root.styleSheets.Add(scrollbarStyleSheet);
}
}
private void BuildLayout()
{
- root.Clear();
- root.AddToClassList("combat-root");
- root.style.flexGrow = 1;
+ _root.Clear();
+ _root.AddToClassList("combat-root");
+ _root.style.flexGrow = 1;
- statusBar = BuildStatusBar();
- root.Add(statusBar);
+ _statusBar = BuildStatusBar();
+ _root.Add(_statusBar);
VisualElement main = new();
main.AddToClassList("main-layout");
- root.Add(main);
+ _root.Add(main);
- playerPanel = CreatePanel("player", "htop process monitor", 242);
- main.Add(playerPanel);
+ _playerPanel = CreatePanel("player", "htop process monitor", 242);
+ main.Add(_playerPanel);
VisualElement fieldPanel = CreatePanel("field", "resolution tracks", 0);
fieldPanel.AddToClassList("field-panel");
main.Add(fieldPanel);
- interpreterStrip = AddTrackZone(fieldPanel, "interpreter queue", "FIFO queued scripts", "track-zone-queue");
- lazyStackPile = AddTrackZone(fieldPanel, "lazy stack", "LIFO delayed work", "track-zone-stack");
- tokenArea = AddTrackZone(fieldPanel, "goroutines", "token lane", "track-zone-token");
+ _interpreterStrip = AddTrackZone(fieldPanel, "interpreter queue", "FIFO queued scripts", "track-zone-queue");
+ _lazyStackPile = AddTrackZone(fieldPanel, "lazy stack", "LIFO delayed work", "track-zone-stack");
+ _tokenArea = AddTrackZone(fieldPanel, "goroutines", "token lane", "track-zone-token");
VisualElement enemiesPanel = CreatePanel("enemy processes", "intent telemetry", 438);
main.Add(enemiesPanel);
- enemyRow = new();
- enemyRow.AddToClassList("enemy-row");
- enemiesPanel.Add(enemyRow);
+ _enemyRow = new();
+ _enemyRow.AddToClassList("enemy-row");
+ enemiesPanel.Add(_enemyRow);
VisualElement bottom = new();
bottom.AddToClassList("bottom-layout");
- root.Add(bottom);
+ _root.Add(bottom);
VisualElement handPanel = CreatePanel("hand", "executable cards", 0);
handPanel.AddToClassList("hand-panel");
bottom.Add(handPanel);
- handRow = new();
- handRow.AddToClassList("hand-row");
- handPanel.Add(handRow);
+ _handRow = new();
+ _handRow.AddToClassList("hand-row");
+ handPanel.Add(_handRow);
VisualElement commandPanel = CreatePanel("turn", "command", 240);
commandPanel.AddToClassList("turn-panel");
bottom.Add(commandPanel);
- turnResourceGrid = new();
- turnResourceGrid.AddToClassList("turn-resource-grid");
- commandPanel.Add(turnResourceGrid);
+ _turnResourceGrid = new();
+ _turnResourceGrid.AddToClassList("turn-resource-grid");
+ commandPanel.Add(_turnResourceGrid);
- Button endTurn = new(() => combatManager.EndPlayerTurn()) { text = "> end-turn" };
+ Button endTurn = new(() => _combatManager.EndPlayerTurn()) { text = "> end-turn" };
endTurn.AddToClassList("primary-action");
commandPanel.Add(endTurn);
- logLabel = new();
- logLabel.AddToClassList("log-line");
- commandPanel.Add(logLabel);
+ _logLabel = new();
+ _logLabel.AddToClassList("log-line");
+ commandPanel.Add(_logLabel);
- feedbackLayer = new();
- feedbackLayer.AddToClassList("feedback-layer");
- feedbackLayer.pickingMode = PickingMode.Ignore;
- root.Add(feedbackLayer);
+ _feedbackLayer = new();
+ _feedbackLayer.AddToClassList("feedback-layer");
+ _feedbackLayer.pickingMode = PickingMode.Ignore;
+ _root.Add(_feedbackLayer);
- damageVignette = new();
- damageVignette.AddToClassList("damage-vignette");
- damageVignette.pickingMode = PickingMode.Ignore;
- root.Add(damageVignette);
+ _damageVignette = new();
+ _damageVignette.AddToClassList("damage-vignette");
+ _damageVignette.pickingMode = PickingMode.Ignore;
+ _root.Add(_damageVignette);
- overlay = new();
- overlay.AddToClassList("overlay");
- overlay.style.display = DisplayStyle.None;
- root.Add(overlay);
+ _overlay = new();
+ _overlay.AddToClassList("overlay");
+ _overlay.style.display = DisplayStyle.None;
+ _root.Add(_overlay);
}
private VisualElement BuildStatusBar()
@@ -327,20 +327,20 @@ namespace KernelPanic.UI
VisualElement bar = new();
bar.AddToClassList("combat-status-bar");
- waveLabel = AddStatusReadout(bar, "wave", "--", false);
- phaseLabel = AddStatusReadout(bar, "phase", "--", true);
- bitsLabel = AddStatusReadout(bar, "Bits", "0", false);
- entropyLabel = AddStatusReadout(bar, "Entropy", "0", false);
- bandwidthLabel = AddStatusReadout(bar, "Bandwidth", "0", false);
- rootCreditsLabel = AddStatusReadout(bar, "Root Credits", "0", false);
+ _waveLabel = AddStatusReadout(bar, "wave", "--", false);
+ _phaseLabel = AddStatusReadout(bar, "phase", "--", true);
+ _bitsLabel = AddStatusReadout(bar, "Bits", "0", false);
+ _entropyLabel = AddStatusReadout(bar, "Entropy", "0", false);
+ _bandwidthLabel = AddStatusReadout(bar, "Bandwidth", "0", false);
+ _rootCreditsLabel = AddStatusReadout(bar, "Root Credits", "0", false);
VisualElement spacer = new();
spacer.AddToClassList("status-spacer");
bar.Add(spacer);
- seedLabel = new("--");
- seedLabel.AddToClassList("seed-value");
- bar.Add(seedLabel);
+ _seedLabel = new("--");
+ _seedLabel.AddToClassList("seed-value");
+ bar.Add(_seedLabel);
return bar;
}
@@ -383,7 +383,7 @@ namespace KernelPanic.UI
private void Refresh()
{
- if (combatManager.PlayerState == null)
+ if (_combatManager.PlayerState == null)
{
return;
}
@@ -399,65 +399,65 @@ namespace KernelPanic.UI
private void RefreshStatus()
{
- SaveData data = saveService.Load();
- RunConfig config = runManager.CurrentConfig;
- waveLabel.text = runManager.CurrentWaveNumber.ToString();
- phaseLabel.text = PhaseText(combatManager.CurrentPhase);
- bitsLabel.text = runManager.Bits.ToString();
- entropyLabel.text = FormatWalletWithAccrual(data.entropyBalance, runManager.AccruedEntropy);
- bandwidthLabel.text = FormatWalletWithAccrual(data.bandwidthBalance, runManager.AccruedBandwidth);
- rootCreditsLabel.text = data.rootCredits.ToString();
- seedLabel.text = $"seed {config?.RunSeed ?? 0}";
+ SaveData data = _saveService.Load();
+ RunConfig config = _runManager.CurrentConfig;
+ _waveLabel.text = _runManager.CurrentWaveNumber.ToString();
+ _phaseLabel.text = PhaseText(_combatManager.CurrentPhase);
+ _bitsLabel.text = _runManager.Bits.ToString();
+ _entropyLabel.text = FormatWalletWithAccrual(data.entropyBalance, _runManager.AccruedEntropy);
+ _bandwidthLabel.text = FormatWalletWithAccrual(data.bandwidthBalance, _runManager.AccruedBandwidth);
+ _rootCreditsLabel.text = data.rootCredits.ToString();
+ _seedLabel.text = $"seed {config?.RunSeed ?? 0}";
}
private void RefreshPlayer()
{
- playerPanel.Clear();
- combatantElements.Remove(combatManager.PlayerState);
- combatantElements[combatManager.PlayerState] = playerPanel;
- playerPanel.Add(PanelHeader(DisplayName(runManager.CurrentConfig.Distro), "htop"));
- ApplyAccent(playerPanel);
+ _playerPanel.Clear();
+ _combatantElements.Remove(_combatManager.PlayerState);
+ _combatantElements[_combatManager.PlayerState] = _playerPanel;
+ _playerPanel.Add(PanelHeader(DisplayName(_runManager.CurrentConfig.Distro), "htop"));
+ ApplyAccent(_playerPanel);
- CombatantState state = combatManager.PlayerState;
- DetectBeneficialResourceFeedback(state, playerPanel);
- ApplyStatusStateClasses(playerPanel, state);
- playerPanel.Add(MeterBlock("uptime", state.CurrentUptime, state.MaxUptime, MeterTone.Uptime));
- 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));
+ CombatantState state = _combatManager.PlayerState;
+ DetectBeneficialResourceFeedback(state, _playerPanel);
+ ApplyStatusStateClasses(_playerPanel, state);
+ _playerPanel.Add(MeterBlock("uptime", state.CurrentUptime, state.MaxUptime, MeterTone.Uptime));
+ _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.UsedRam}/{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));
+ _playerPanel.Add(ram);
+ _playerPanel.Add(StatusBlock(state, true));
}
private void RefreshTracks()
{
- queueChipElements.Clear();
- stackChipElements.Clear();
- FillCardStrip(interpreterStrip, combatManager.InterpreterQueue.Cards, "queue empty");
- FillCardStrip(lazyStackPile, combatManager.LazyStack.Cards, "stack empty");
- tokenArea.Clear();
- tokenArea.Add(EmptyState("no goroutines or tokens"));
+ _queueChipElements.Clear();
+ _stackChipElements.Clear();
+ FillCardStrip(_interpreterStrip, _combatManager.InterpreterQueue.Cards, "queue empty");
+ FillCardStrip(_lazyStackPile, _combatManager.LazyStack.Cards, "stack empty");
+ _tokenArea.Clear();
+ _tokenArea.Add(EmptyState("no goroutines or tokens"));
}
private void RefreshEnemies()
{
- enemyRow.Clear();
- combatantElements.Clear();
- if (combatManager.PlayerState != null)
+ _enemyRow.Clear();
+ _combatantElements.Clear();
+ if (_combatManager.PlayerState != null)
{
- combatantElements[combatManager.PlayerState] = playerPanel;
+ _combatantElements[_combatManager.PlayerState] = _playerPanel;
}
- for (int i = 0; i < combatManager.Enemies.Count; i++)
+ for (int i = 0; i < _combatManager.Enemies.Count; i++)
{
int index = i;
- EnemyInstance enemy = combatManager.Enemies[i];
- Button card = new(() => combatManager.SelectEnemy(index));
+ EnemyInstance enemy = _combatManager.Enemies[i];
+ Button card = new(() => _combatManager.SelectEnemy(index));
card.text = string.Empty;
card.AddToClassList("enemy-card");
- bool highlighted = combatManager.PendingTargetCard != null || combatManager.SelectedEnemyIndex == index;
+ bool highlighted = _combatManager.PendingTargetCard != null || _combatManager.SelectedEnemyIndex == index;
if (highlighted)
{
card.AddToClassList("enemy-card-targeted");
@@ -466,7 +466,14 @@ namespace KernelPanic.UI
Label name = new(enemy.Name);
name.AddToClassList("enemy-name");
card.Add(name);
- combatantElements[enemy.State] = card;
+ if (enemy.HasPendingMarker)
+ {
+ Label marker = new(enemy.PendingRevive ? "~ reviving" : "~ revived once");
+ marker.AddToClassList("status-label");
+ card.Add(marker);
+ }
+
+ _combatantElements[enemy.State] = card;
DetectBeneficialResourceFeedback(enemy.State, card);
ApplyStatusStateClasses(card, enemy.State);
card.Add(MeterBlock("uptime", enemy.CurrentUptime, enemy.MaxUptime, MeterTone.Uptime));
@@ -478,107 +485,107 @@ namespace KernelPanic.UI
}
card.Add(StatusPipRow(enemy.State));
- card.Add(IntentPanel(enemy.CurrentIntent));
- enemyRow.Add(card);
+ card.Add(IntentPanel(enemy.DisplayIntent));
+ _enemyRow.Add(card);
}
}
private void RefreshHand()
{
- handRow.Clear();
- handCardElements.Clear();
+ _handRow.Clear();
+ _handCardElements.Clear();
HashSet currentHandCards = new();
- IReadOnlyList hand = combatManager.HandController.Cards;
+ IReadOnlyList hand = _combatManager.HandController.Cards;
for (int i = 0; i < hand.Count; i++)
{
CardInstance card = hand[i];
currentHandCards.Add(card);
VisualElement face = CardFace(card);
- handCardElements[card] = face;
- if (!knownHandCards.Contains(card))
+ _handCardElements[card] = face;
+ if (!_knownHandCards.Contains(card))
{
PlayElementBeat(face, "hand-card-drawn", 260);
}
- handRow.Add(face);
+ _handRow.Add(face);
}
- knownHandCards.Clear();
+ _knownHandCards.Clear();
foreach (CardInstance card in currentHandCards)
{
- knownHandCards.Add(card);
+ _knownHandCards.Add(card);
}
}
private void RefreshTurnPanel()
{
- turnResourceGrid.Clear();
- CombatantState state = combatManager.PlayerState;
- IReadOnlyList hand = combatManager.HandController.Cards;
- turnResourceGrid.Add(TurnStat("cycles", $"{state.Cycles}/{state.MaxCycles}"));
- 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()));
+ _turnResourceGrid.Clear();
+ CombatantState state = _combatManager.PlayerState;
+ IReadOnlyList hand = _combatManager.HandController.Cards;
+ _turnResourceGrid.Add(TurnStat("cycles", $"{state.Cycles}/{state.MaxCycles}"));
+ _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()));
}
private void RefreshOverlay()
{
- if (overlay == null)
+ if (_overlay == null)
{
return;
}
- overlay.Clear();
- if (combatManager.RunLost)
+ _overlay.Clear();
+ if (_combatManager.RunLost)
{
SettleRunRewardsIfNeeded();
- overlay.style.display = DisplayStyle.Flex;
- overlay.Add(OverlayTitle("kernel panic"));
- overlay.Add(OverlayLine("fatal: player uptime reached 0"));
- overlay.Add(OverlayLine($"wave {runManager.CurrentWaveNumber} reached seed {runManager.CurrentConfig?.RunSeed ?? 0}"));
- overlay.Add(OverlayLine($"waves cleared: {runManager.WavesCleared}"));
- overlay.Add(OverlayLine($"+{runManager.AccruedBandwidth} bandwidth"));
- overlay.Add(OverlayLine($"+{runManager.AccruedEntropy} entropy"));
- Button returnButton = new(() => SceneLoader.LoadMainMenu(root)) { text = "> return to menu" };
+ _overlay.style.display = DisplayStyle.Flex;
+ _overlay.Add(OverlayTitle("kernel panic"));
+ _overlay.Add(OverlayLine("fatal: player uptime reached 0"));
+ _overlay.Add(OverlayLine($"wave {_runManager.CurrentWaveNumber} reached seed {_runManager.CurrentConfig?.RunSeed ?? 0}"));
+ _overlay.Add(OverlayLine($"waves cleared: {_runManager.WavesCleared}"));
+ _overlay.Add(OverlayLine($"+{_runManager.AccruedBandwidth} bandwidth"));
+ _overlay.Add(OverlayLine($"+{_runManager.AccruedEntropy} entropy"));
+ Button returnButton = new(() => SceneLoader.LoadMainMenu(_root)) { text = "> return to menu" };
returnButton.AddToClassList("primary-action");
- overlay.Add(returnButton);
+ _overlay.Add(returnButton);
return;
}
- if (combatManager.AwaitingWaveContinue)
+ if (_combatManager.AwaitingWaveContinue)
{
- if (!runManager.RepositoryVisitActive)
+ if (!_runManager.RepositoryVisitActive)
{
- runManager.GenerateRepositoryOffers(cardDatabase, languageDeckDatabase);
+ _runManager.GenerateRepositoryOffers(cardDatabase, languageDeckDatabase);
return;
}
- overlay.style.display = DisplayStyle.Flex;
- overlay.Add(BuildRepositoryView());
+ _overlay.style.display = DisplayStyle.Flex;
+ _overlay.Add(BuildRepositoryView());
return;
}
- overlay.style.display = DisplayStyle.None;
+ _overlay.style.display = DisplayStyle.None;
}
private void SettleRunRewardsIfNeeded()
{
- if (runManager == null || saveService == null)
+ if (_runManager == null || _saveService == null)
{
return;
}
- if (!runManager.TrySettleRunRewards(out int bandwidth, out int entropy))
+ if (!_runManager.TrySettleRunRewards(out int bandwidth, out int entropy))
{
return;
}
- SaveData data = saveService.Load();
+ SaveData data = _saveService.Load();
data.bandwidthBalance = Math.Max(0, data.bandwidthBalance) + bandwidth;
data.entropyBalance = Math.Max(0, data.entropyBalance) + entropy;
- data.RecordRunStats(runManager.CurrentConfig?.Distro?.Id, runManager.CurrentWaveNumber);
- saveService.Save(data);
+ data.RecordRunStats(_runManager.CurrentConfig?.Distro?.Id, _runManager.CurrentWaveNumber);
+ _saveService.Save(data);
}
private VisualElement BuildRepositoryView()
@@ -592,15 +599,15 @@ namespace KernelPanic.UI
VisualElement offers = new();
offers.AddToClassList("repository-offers");
- if (runManager.RepositoryOffers.Count == 0)
+ if (_runManager.RepositoryOffers.Count == 0)
{
offers.Add(EmptyState("repository clean: no offers left this visit"));
}
else
{
- for (int i = 0; i < runManager.RepositoryOffers.Count; i++)
+ for (int i = 0; i < _runManager.RepositoryOffers.Count; i++)
{
- offers.Add(RepositoryOfferTile(runManager.RepositoryOffers[i]));
+ offers.Add(RepositoryOfferTile(_runManager.RepositoryOffers[i]));
}
}
@@ -610,23 +617,23 @@ namespace KernelPanic.UI
VisualElement footer = new();
footer.AddToClassList("repository-footer");
- Button reroll = new(() => runManager.RerollRepositoryOffers(cardDatabase, languageDeckDatabase))
+ Button reroll = new(() => _runManager.RerollRepositoryOffers(cardDatabase, languageDeckDatabase))
{
- text = $"apt update - reroll ({runManager.RerollCost} bits)"
+ text = $"apt update - reroll ({_runManager.RerollCost} bits)"
};
reroll.AddToClassList("primary-action");
reroll.AddToClassList("repository-footer-action");
- reroll.EnableInClassList("repo-unaffordable", runManager.Bits < runManager.RerollCost);
- reroll.SetEnabled(runManager.Bits >= runManager.RerollCost);
+ reroll.EnableInClassList("repo-unaffordable", _runManager.Bits < _runManager.RerollCost);
+ reroll.SetEnabled(_runManager.Bits >= _runManager.RerollCost);
footer.Add(reroll);
- Button leave = new(() => combatManager.ContinueToNextWave()) { text = "> boot next-wave" };
+ Button leave = new(() => _combatManager.ContinueToNextWave()) { text = "> boot next-wave" };
leave.AddToClassList("primary-action");
leave.AddToClassList("repository-leave-action");
VisualElement leaveBlock = new();
leaveBlock.AddToClassList("repository-leave-block");
leaveBlock.Add(leave);
- Label banked = new($"{runManager.Bits} bits banked");
+ Label banked = new($"{_runManager.Bits} bits banked");
banked.AddToClassList("repository-banked-hint");
leaveBlock.Add(banked);
footer.Add(leaveBlock);
@@ -641,9 +648,9 @@ namespace KernelPanic.UI
VisualElement copy = new();
copy.AddToClassList("repository-header-copy");
- Label title = new($"repository :: wave {runManager.CurrentWaveNumber - 1} cleared");
+ Label title = new($"repository :: wave {_runManager.CurrentWaveNumber - 1} cleared");
title.AddToClassList("overlay-title");
- Label subtitle = new($"$ apt browse {runManager.RepositoryOffers.Count}/{CombatTuning.ShopSize} offers");
+ Label subtitle = new($"$ apt browse {_runManager.RepositoryOffers.Count}/{CombatTuning.ShopSize} offers");
subtitle.AddToClassList("overlay-line");
copy.Add(title);
copy.Add(subtitle);
@@ -652,7 +659,7 @@ namespace KernelPanic.UI
wallet.AddToClassList("repository-wallet");
Label walletLabel = new("bits wallet");
walletLabel.AddToClassList("status-label");
- Label walletValue = new($"◆ {runManager.Bits}");
+ Label walletValue = new($"◆ {_runManager.Bits}");
walletValue.AddToClassList("repository-wallet-value");
wallet.Add(walletLabel);
wallet.Add(walletValue);
@@ -667,7 +674,7 @@ namespace KernelPanic.UI
VisualElement tile = new();
tile.AddToClassList("repository-tile");
tile.AddToClassList(OfferTileClass(offer.Kind));
- bool unavailable = offer.Sold || runManager.Bits < offer.Price;
+ bool unavailable = offer.Sold || _runManager.Bits < offer.Price;
tile.EnableInClassList("repo-unaffordable", unavailable);
tile.EnableInClassList("repo-sold", offer.Sold);
@@ -688,12 +695,12 @@ namespace KernelPanic.UI
};
tile.Add(content);
- Button buy = new(() => runManager.BuyOffer(offer, combatManager.PlayerState))
+ Button buy = new(() => _runManager.BuyOffer(offer, _combatManager.PlayerState))
{
text = offer.Sold ? "installed" : OfferCommandText(offer)
};
buy.AddToClassList("repository-action");
- buy.SetEnabled(!offer.Sold && runManager.Bits >= offer.Price);
+ buy.SetEnabled(!offer.Sold && _runManager.Bits >= offer.Price);
tile.Add(buy);
return tile;
}
@@ -784,16 +791,16 @@ namespace KernelPanic.UI
VisualElement rows = new();
rows.AddToClassList("repository-remove-list");
- for (int i = 0; i < runManager.RunDeck.Count; i++)
+ for (int i = 0; i < _runManager.RunDeck.Count; i++)
{
- CardInstance card = runManager.RunDeck[i];
- Button remove = new(() => runManager.RemoveCard(card))
+ CardInstance card = _runManager.RunDeck[i];
+ Button remove = new(() => _runManager.RemoveCard(card))
{
text = $"apt remove {DisplayName(card.Definition)}"
};
remove.AddToClassList("repo-remove-button");
- remove.EnableInClassList("repo-unaffordable", runManager.Bits < CombatTuning.RemoveCardCost || runManager.RunDeck.Count <= 1);
- remove.SetEnabled(runManager.Bits >= CombatTuning.RemoveCardCost && runManager.RunDeck.Count > 1);
+ remove.EnableInClassList("repo-unaffordable", _runManager.Bits < CombatTuning.RemoveCardCost || _runManager.RunDeck.Count <= 1);
+ remove.SetEnabled(_runManager.Bits >= CombatTuning.RemoveCardCost && _runManager.RunDeck.Count > 1);
rows.Add(remove);
}
@@ -806,12 +813,12 @@ namespace KernelPanic.UI
Button button = new(() => PlayCardWithFeedback(card));
button.text = string.Empty;
button.AddToClassList("hand-card");
- if (combatManager.PendingTargetCard == card)
+ if (_combatManager.PendingTargetCard == card)
{
button.AddToClassList("hand-card-selected");
}
- if (GetDisplayCardCost(card) > combatManager.PlayerState.Cycles)
+ if (GetDisplayCardCost(card) > _combatManager.PlayerState.Cycles)
{
button.AddToClassList("hand-card-unaffordable");
}
@@ -867,7 +874,7 @@ namespace KernelPanic.UI
Label label = new("intent");
label.AddToClassList("intent-label");
- Label value = new($"{IntentIcon(intent.Kind)} {intent.ValueText}");
+ Label value = new($"{IntentIcon(intent)} {intent.ValueText}");
value.AddToClassList("intent-value");
value.AddToClassList(className);
Label detail = new(IntentText(intent));
@@ -995,13 +1002,13 @@ namespace KernelPanic.UI
for (int i = 0; i < cards.Count; i++)
{
VisualElement chip = CardChip(cards[i]);
- if (target == interpreterStrip)
+ if (target == _interpreterStrip)
{
- queueChipElements[cards[i]] = chip;
+ _queueChipElements[cards[i]] = chip;
}
- else if (target == lazyStackPile)
+ else if (target == _lazyStackPile)
{
- stackChipElements[cards[i]] = chip;
+ _stackChipElements[cards[i]] = chip;
}
target.Add(chip);
@@ -1010,20 +1017,20 @@ namespace KernelPanic.UI
private bool PlayCardWithFeedback(CardInstance card)
{
- int cyclesBefore = combatManager.PlayerState == null ? 0 : combatManager.PlayerState.Cycles;
- bool played = combatManager.PlayCard(card);
+ int cyclesBefore = _combatManager.PlayerState == null ? 0 : _combatManager.PlayerState.Cycles;
+ bool played = _combatManager.PlayCard(card);
if (played)
{
- int spent = Mathf.Max(0, cyclesBefore - (combatManager.PlayerState == null ? cyclesBefore : combatManager.PlayerState.Cycles));
+ int spent = Mathf.Max(0, cyclesBefore - (_combatManager.PlayerState == null ? cyclesBefore : _combatManager.PlayerState.Cycles));
if (spent > 0)
{
- PlayElementBeat(turnResourceGrid, "cycles-spent-beat", 220);
+ PlayElementBeat(_turnResourceGrid, "cycles-spent-beat", 220);
}
return true;
}
- if (combatManager.PendingTargetCard != card && handCardElements.TryGetValue(card, out VisualElement face))
+ if (_combatManager.PendingTargetCard != card && _handCardElements.TryGetValue(card, out VisualElement face))
{
PlayElementBeat(face, "feedback-denied", 220);
}
@@ -1038,11 +1045,11 @@ namespace KernelPanic.UI
return;
}
- VisualElement source = handCardElements.TryGetValue(payload.Card, out VisualElement cardElement) ? cardElement : null;
+ VisualElement source = _handCardElements.TryGetValue(payload.Card, out VisualElement cardElement) ? cardElement : null;
VisualElement destination = payload.Track switch
{
- ResolutionTrack.InterpreterQueue => interpreterStrip,
- ResolutionTrack.LazyStack => lazyStackPile,
+ ResolutionTrack.InterpreterQueue => _interpreterStrip,
+ ResolutionTrack.LazyStack => _lazyStackPile,
_ => FirstTargetElement(payload.Card)
};
@@ -1058,22 +1065,22 @@ namespace KernelPanic.UI
if (payload.Track == ResolutionTrack.InterpreterQueue)
{
- int delay = UIPreferences.ReducedMotion ? 0 : Mathf.Min(queueCascadeIndex * 120, 600);
- queueCascadeIndex++;
+ int delay = UIPreferences.ReducedMotion ? 0 : Mathf.Min(_queueCascadeIndex * 120, 600);
+ _queueCascadeIndex++;
ScheduleFeedback(() =>
{
- VisualElement chip = queueChipElements.TryGetValue(payload.Card, out VisualElement queuedChip) ? queuedChip : interpreterStrip;
+ VisualElement chip = _queueChipElements.TryGetValue(payload.Card, out VisualElement queuedChip) ? queuedChip : _interpreterStrip;
PlayElementBeat(chip, "queue-chip-resolve", 260);
- FlyCardGhost(payload.Card, chip, turnResourceGrid, "feedback-card-discard");
+ FlyCardGhost(payload.Card, chip, _turnResourceGrid, "feedback-card-discard");
}, delay);
return;
}
if (payload.Track == ResolutionTrack.LazyStack)
{
- VisualElement chip = stackChipElements.TryGetValue(payload.Card, out VisualElement stackChip) ? stackChip : lazyStackPile;
+ VisualElement chip = _stackChipElements.TryGetValue(payload.Card, out VisualElement stackChip) ? stackChip : _lazyStackPile;
PlayElementBeat(chip, "stack-chip-resolve", 320);
- FlyCardGhost(payload.Card, chip, turnResourceGrid, "feedback-card-discard");
+ FlyCardGhost(payload.Card, chip, _turnResourceGrid, "feedback-card-discard");
}
}
@@ -1081,16 +1088,16 @@ namespace KernelPanic.UI
{
if (payload.NextPhase == TurnPhase.Interpret)
{
- queueCascadeIndex = 0;
+ _queueCascadeIndex = 0;
}
- feedbackCascadeIndex = 0;
- ScheduleFeedback(() => PlayElementBeat(phaseLabel, "phase-pulse", 260), 0);
+ _feedbackCascadeIndex = 0;
+ ScheduleFeedback(() => PlayElementBeat(_phaseLabel, "phase-pulse", 260), 0);
}
private void HandleDamageDealt(DamageDealtEvent payload)
{
- bool isPlayer = payload.Target == combatManager.PlayerState;
+ bool isPlayer = payload.Target == _combatManager.PlayerState;
VisualElement target = CombatantElement(payload.Target);
ScheduleCombatBeat(beatIndex =>
{
@@ -1122,7 +1129,7 @@ namespace KernelPanic.UI
{
SpawnFloatingText(target, "killed", "float-kill float-large", 0);
SpawnDeathGhost(targetBounds);
- if (target != enemyRow)
+ if (target != _enemyRow)
{
PlayElementBeat(target, "feedback-killed", 420);
}
@@ -1168,37 +1175,37 @@ namespace KernelPanic.UI
return;
}
- if (previousUptime.TryGetValue(state, out int oldUptime) && state.CurrentUptime > oldUptime)
+ if (_previousUptime.TryGetValue(state, out int oldUptime) && state.CurrentUptime > oldUptime)
{
SpawnFloatingText(element, $"+{state.CurrentUptime - oldUptime}", "float-heal");
PlayElementBeat(element, "feedback-boost", 220);
}
- if (previousShield.TryGetValue(state, out int oldShield) && state.Shield > oldShield)
+ if (_previousShield.TryGetValue(state, out int oldShield) && state.Shield > oldShield)
{
SpawnFloatingText(element, $"+{state.Shield - oldShield} shield", "float-heal");
PlayElementBeat(element, "feedback-block", 220);
}
- previousUptime[state] = state.CurrentUptime;
- previousShield[state] = state.Shield;
+ _previousUptime[state] = state.CurrentUptime;
+ _previousShield[state] = state.Shield;
}
private VisualElement CombatantElement(CombatantState state)
{
- if (state != null && combatantElements.TryGetValue(state, out VisualElement element))
+ if (state != null && _combatantElements.TryGetValue(state, out VisualElement element))
{
return element;
}
- return state == combatManager.PlayerState ? playerPanel : enemyRow;
+ return state == _combatManager.PlayerState ? _playerPanel : _enemyRow;
}
private VisualElement FirstTargetElement(CardInstance card)
{
if (card?.TargetSnapshot == null)
{
- return enemyRow;
+ return _enemyRow;
}
for (int i = 0; i < card.TargetSnapshot.Count; i++)
@@ -1210,7 +1217,7 @@ namespace KernelPanic.UI
}
}
- return enemyRow;
+ return _enemyRow;
}
private void PlayElementBeat(VisualElement element, string className, int durationMs)
@@ -1220,12 +1227,12 @@ namespace KernelPanic.UI
return;
}
- int version = ++feedbackBeatVersion;
- feedbackBeatVersions[element] = version;
+ int version = ++_feedbackBeatVersion;
+ _feedbackBeatVersions[element] = version;
RemoveFeedbackBeatClasses(element);
ScheduleFeedback(() =>
{
- if (!feedbackBeatVersions.TryGetValue(element, out int currentVersion) || currentVersion != version)
+ if (!_feedbackBeatVersions.TryGetValue(element, out int currentVersion) || currentVersion != version)
{
return;
}
@@ -1233,26 +1240,26 @@ namespace KernelPanic.UI
element.AddToClassList(className);
ScheduleFeedback(() =>
{
- if (!feedbackBeatVersions.TryGetValue(element, out int removeVersion) || removeVersion != version)
+ if (!_feedbackBeatVersions.TryGetValue(element, out int removeVersion) || removeVersion != version)
{
return;
}
element.RemoveFromClassList(className);
- feedbackBeatVersions.Remove(element);
+ _feedbackBeatVersions.Remove(element);
}, UIPreferences.ReducedMotion ? 80 : durationMs);
}, 0);
}
private void SpawnFloatingText(VisualElement anchor, string text, string className, int cascadeOffset = 0)
{
- if (feedbackLayer == null || anchor == null)
+ if (_feedbackLayer == null || anchor == null)
{
return;
}
Rect anchorRect = anchor.worldBound;
- Rect rootRect = root.worldBound;
+ Rect rootRect = _root.worldBound;
Label label = new(text);
label.AddToClassList("float-number");
if (!string.IsNullOrWhiteSpace(className))
@@ -1271,7 +1278,7 @@ namespace KernelPanic.UI
float offsetY = ((cascadeOffset % 3) * 5f);
label.style.left = anchorRect.center.x - rootRect.x - 30f + offsetX;
label.style.top = anchorRect.center.y - rootRect.y - 16f + offsetY;
- feedbackLayer.Add(label);
+ _feedbackLayer.Add(label);
if (UIPreferences.ReducedMotion)
{
@@ -1290,13 +1297,13 @@ namespace KernelPanic.UI
private void SpawnImpactMarker(VisualElement anchor, bool critical, bool mitigated, int cascadeOffset)
{
- if (feedbackLayer == null || anchor == null)
+ if (_feedbackLayer == null || anchor == null)
{
return;
}
Rect anchorRect = anchor.worldBound;
- Rect rootRect = root.worldBound;
+ Rect rootRect = _root.worldBound;
Label marker = new(critical ? "crit" : mitigated ? "clang" : "hit");
marker.AddToClassList("impact-marker");
marker.AddToClassList(critical ? "impact-marker-crit" : mitigated ? "impact-marker-clang" : "impact-marker-hit");
@@ -1304,7 +1311,7 @@ namespace KernelPanic.UI
float lane = (cascadeOffset % 4) * 18f;
marker.style.left = anchorRect.xMax - rootRect.x - 54f;
marker.style.top = anchorRect.y - rootRect.y + 8f + lane;
- feedbackLayer.Add(marker);
+ _feedbackLayer.Add(marker);
if (UIPreferences.ReducedMotion)
{
@@ -1322,12 +1329,12 @@ namespace KernelPanic.UI
private void FlyCardGhost(CardInstance card, VisualElement source, VisualElement destination, string className)
{
- if (feedbackLayer == null || card == null)
+ if (_feedbackLayer == null || card == null)
{
return;
}
- Rect rootRect = root.worldBound;
+ Rect rootRect = _root.worldBound;
Rect sourceRect = source == null ? new Rect(rootRect.center.x - 70f, rootRect.center.y - 50f, 140f, 110f) : source.worldBound;
Rect destinationRect = destination == null ? sourceRect : destination.worldBound;
@@ -1339,7 +1346,7 @@ namespace KernelPanic.UI
ghost.style.top = sourceRect.y - rootRect.y;
ghost.style.width = Mathf.Max(110f, sourceRect.width);
ghost.style.height = Mathf.Max(94f, sourceRect.height);
- feedbackLayer.Add(ghost);
+ _feedbackLayer.Add(ghost);
if (UIPreferences.ReducedMotion)
{
@@ -1359,12 +1366,12 @@ namespace KernelPanic.UI
private void SpawnDeathGhost(Rect sourceRect)
{
- if (feedbackLayer == null || sourceRect.width <= 0f || sourceRect.height <= 0f)
+ if (_feedbackLayer == null || sourceRect.width <= 0f || sourceRect.height <= 0f)
{
return;
}
- Rect rootRect = root.worldBound;
+ Rect rootRect = _root.worldBound;
float ghostWidth = Mathf.Min(sourceRect.width, 156f);
float ghostHeight = Mathf.Min(sourceRect.height, 132f);
VisualElement ghost = new();
@@ -1374,7 +1381,7 @@ namespace KernelPanic.UI
ghost.style.top = sourceRect.center.y - rootRect.y - (ghostHeight * 0.5f);
ghost.style.width = ghostWidth;
ghost.style.height = ghostHeight;
- feedbackLayer.Add(ghost);
+ _feedbackLayer.Add(ghost);
if (UIPreferences.ReducedMotion)
{
@@ -1392,12 +1399,12 @@ namespace KernelPanic.UI
private void FlashDamageVignette(bool critical)
{
- if (damageVignette == null)
+ if (_damageVignette == null)
{
return;
}
- PlayElementBeat(damageVignette, critical ? "damage-vignette-crit" : "damage-vignette-on", UIPreferences.ReducedMotion ? 120 : critical ? 320 : 240);
+ PlayElementBeat(_damageVignette, critical ? "damage-vignette-crit" : "damage-vignette-on", UIPreferences.ReducedMotion ? 120 : critical ? 320 : 240);
}
private static void RemoveFeedbackBeatClasses(VisualElement element)
@@ -1433,19 +1440,19 @@ namespace KernelPanic.UI
return;
}
- int beatIndex = feedbackCascadeIndex++;
+ int beatIndex = _feedbackCascadeIndex++;
int delay = UIPreferences.ReducedMotion ? 0 : Mathf.Min(beatIndex * 120, 840);
ScheduleFeedback(() => action(beatIndex), delay + (UIPreferences.ReducedMotion ? 0 : holdMs));
}
private void ScheduleFeedback(Action action, int delayMs)
{
- if (action == null || root == null)
+ if (action == null || _root == null)
{
return;
}
- root.schedule.Execute(action).StartingIn(Mathf.Max(0, delayMs));
+ _root.schedule.Execute(action).StartingIn(Mathf.Max(0, delayMs));
}
private static void ApplyStatusStateClasses(VisualElement element, CombatantState state)
@@ -1491,9 +1498,9 @@ namespace KernelPanic.UI
private void HandleCombatLog(string message)
{
- if (logLabel != null)
+ if (_logLabel != null)
{
- logLabel.text = message ?? string.Empty;
+ _logLabel.text = message ?? string.Empty;
}
}
@@ -1520,7 +1527,7 @@ namespace KernelPanic.UI
private int GetDisplayCardCost(CardInstance card)
{
- return combatManager == null ? CombatManager.GetCardCost(card) : combatManager.GetEffectiveCardCost(card);
+ return _combatManager == null ? CombatManager.GetCardCost(card) : _combatManager.GetEffectiveCardCost(card);
}
private static VisualElement CreatePanel(string title, string subtitle, float width)
@@ -1552,7 +1559,7 @@ namespace KernelPanic.UI
private void ApplyAccent(VisualElement element)
{
- element.style.borderTopColor = distroAccent;
+ element.style.borderTopColor = _distroAccent;
}
private static VisualElement TurnStat(string labelText, string valueText)
@@ -1652,9 +1659,14 @@ namespace KernelPanic.UI
};
}
- private static string IntentIcon(EnemyIntentKind kind)
+ private static string IntentIcon(EnemyIntent intent)
{
- return kind switch
+ if (!string.IsNullOrWhiteSpace(intent.IconKey))
+ {
+ return intent.IconKey;
+ }
+
+ return intent.Kind switch
{
EnemyIntentKind.Attack => "!",
EnemyIntentKind.StatusAttack => "!",
@@ -1673,7 +1685,7 @@ namespace KernelPanic.UI
EnemyIntentKind.StatusAttack => $"{intent.DisplayLabel} x{intent.StatusStacks}",
EnemyIntentKind.Defend => "defend",
EnemyIntentKind.Buff => "buff",
- EnemyIntentKind.Special => "special",
+ EnemyIntentKind.Special => intent.DisplayLabel,
_ => intent.DisplayLabel
};
}
diff --git a/Assets/Scripts/UI/GamblingSceneController.cs b/Assets/Scripts/UI/GamblingSceneController.cs
index cb4cd0d..dedaa0c 100644
--- a/Assets/Scripts/UI/GamblingSceneController.cs
+++ b/Assets/Scripts/UI/GamblingSceneController.cs
@@ -33,53 +33,53 @@ namespace KernelPanic.UI
private const int SummaryTickSteps = 8;
private const int SummaryTickMs = 220;
- private readonly List rewards = new();
- private readonly List cardViews = new();
- private UIDocument document;
- private VisualElement root;
- private Label commandLabel;
- private Label costLabel;
- private Label flourishLabel;
- private Label skipHint;
- private VisualElement resultGrid;
- private VisualElement effectLayer;
- private VisualElement goldShimmer;
- private VisualElement summaryPanel;
- private Label summaryTotalsLabel;
- private Button continueButton;
- private string completedHeader;
- private bool skipRequested;
- private bool revealComplete;
- private bool failed;
+ private readonly List _rewards = new();
+ private readonly List _cardViews = new();
+ private UIDocument _document;
+ private VisualElement _root;
+ private Label _commandLabel;
+ private Label _costLabel;
+ private Label _flourishLabel;
+ private Label _skipHint;
+ private VisualElement _resultGrid;
+ private VisualElement _effectLayer;
+ private VisualElement _goldShimmer;
+ private VisualElement _summaryPanel;
+ private Label _summaryTotalsLabel;
+ private Button _continueButton;
+ private string _completedHeader;
+ private bool _skipRequested;
+ private bool _revealComplete;
+ private bool _failed;
private void Awake()
{
- document = GetComponent();
- root = document.rootVisualElement;
- root.Clear();
- root.focusable = true;
- root.AddToClassList("gambling-root");
- root.EnableInClassList("reduced-motion", UIPreferences.ReducedMotion);
+ _document = GetComponent();
+ _root = _document.rootVisualElement;
+ _root.Clear();
+ _root.focusable = true;
+ _root.AddToClassList("gambling-root");
+ _root.EnableInClassList("reduced-motion", UIPreferences.ReducedMotion);
LoadStyles();
BuildLayout();
- root.RegisterCallback(HandleKeyDown);
- root.RegisterCallback(HandlePointerDown);
+ _root.RegisterCallback(HandleKeyDown);
+ _root.RegisterCallback(HandlePointerDown);
}
private void OnDestroy()
{
- if (root == null)
+ if (_root == null)
{
return;
}
- root.UnregisterCallback(HandleKeyDown);
- root.UnregisterCallback(HandlePointerDown);
+ _root.UnregisterCallback(HandleKeyDown);
+ _root.UnregisterCallback(HandlePointerDown);
}
private void Start()
{
- root.Focus();
+ _root.Focus();
StartCoroutine(RunCutscene());
}
@@ -95,7 +95,7 @@ namespace KernelPanic.UI
StyleSheet styleSheet = Resources.Load(resourcePath);
if (styleSheet != null)
{
- root.styleSheets.Add(styleSheet);
+ _root.styleSheets.Add(styleSheet);
}
}
@@ -103,7 +103,7 @@ namespace KernelPanic.UI
{
VisualElement shell = new();
shell.AddToClassList("gambling-shell");
- root.Add(shell);
+ _root.Add(shell);
VisualElement frame = new();
frame.AddToClassList("gambling-frame");
@@ -117,76 +117,76 @@ namespace KernelPanic.UI
titleBlock.AddToClassList("gambling-title-block");
header.Add(titleBlock);
- commandLabel = new("git pull");
- commandLabel.AddToClassList("gambling-command");
- titleBlock.Add(commandLabel);
+ _commandLabel = new("git pull");
+ _commandLabel.AddToClassList("gambling-command");
+ titleBlock.Add(_commandLabel);
- costLabel = new();
- costLabel.AddToClassList("gambling-cost");
- titleBlock.Add(costLabel);
+ _costLabel = new();
+ _costLabel.AddToClassList("gambling-cost");
+ titleBlock.Add(_costLabel);
- skipHint = new("[tap/key] reveal all");
- skipHint.AddToClassList("gambling-skip");
- header.Add(skipHint);
+ _skipHint = new("[tap/key] reveal all");
+ _skipHint.AddToClassList("gambling-skip");
+ header.Add(_skipHint);
- flourishLabel = new();
- flourishLabel.AddToClassList("gambling-flourish");
- frame.Add(flourishLabel);
+ _flourishLabel = new();
+ _flourishLabel.AddToClassList("gambling-flourish");
+ frame.Add(_flourishLabel);
- resultGrid = new VisualElement();
- resultGrid.AddToClassList("gambling-results");
- frame.Add(resultGrid);
+ _resultGrid = new VisualElement();
+ _resultGrid.AddToClassList("gambling-results");
+ frame.Add(_resultGrid);
- goldShimmer = new VisualElement();
- goldShimmer.AddToClassList("gold-shimmer");
- goldShimmer.pickingMode = PickingMode.Ignore;
- frame.Add(goldShimmer);
+ _goldShimmer = new VisualElement();
+ _goldShimmer.AddToClassList("gold-shimmer");
+ _goldShimmer.pickingMode = PickingMode.Ignore;
+ frame.Add(_goldShimmer);
- effectLayer = new VisualElement();
- effectLayer.AddToClassList("effect-layer");
- effectLayer.pickingMode = PickingMode.Ignore;
- frame.Add(effectLayer);
+ _effectLayer = new VisualElement();
+ _effectLayer.AddToClassList("effect-layer");
+ _effectLayer.pickingMode = PickingMode.Ignore;
+ frame.Add(_effectLayer);
- summaryPanel = new VisualElement();
- summaryPanel.AddToClassList("summary-panel");
- summaryPanel.AddToClassList("hidden");
- frame.Add(summaryPanel);
+ _summaryPanel = new VisualElement();
+ _summaryPanel.AddToClassList("summary-panel");
+ _summaryPanel.AddToClassList("hidden");
+ frame.Add(_summaryPanel);
- continueButton = new Button(ContinueToMenu) { text = "> continue" };
- continueButton.AddToClassList("continue-button");
- continueButton.AddToClassList("continue-button-pending");
- continueButton.SetEnabled(false);
- frame.Add(continueButton);
+ _continueButton = new Button(ContinueToMenu) { text = "> continue" };
+ _continueButton.AddToClassList("continue-button");
+ _continueButton.AddToClassList("continue-button-pending");
+ _continueButton.SetEnabled(false);
+ frame.Add(_continueButton);
}
private IEnumerator RunCutscene()
{
if (!GachaPullContext.TryConsumePending(out PendingGachaPull pending))
{
- failed = true;
- commandLabel.text = "git pull failed";
- costLabel.text = "no pending pull";
+ _failed = true;
+ _commandLabel.text = "git pull failed";
+ _costLabel.text = "no pending pull";
ShowContinue();
yield break;
}
- commandLabel.text = pending.PullCount == 10 ? "git pull --ten" : "git pull --single";
+ _commandLabel.text = pending.PullCount == 10 ? "git pull --ten" : "git pull --single";
int pullCost = pending.PullCount == 10 ? GachaService.BeginnerTenPullCost : GachaService.BeginnerSinglePullCost;
- costLabel.text = pending.EntropyTokenCount > 0
+ _costLabel.text = pending.EntropyTokenCount > 0
? $"cost: {pending.EntropyTokenCount * GachaService.EntropyPerCommit} Entropy"
: $"cost: {pullCost} Commits / {pullCost * GachaService.EntropyPerCommit} Entropy";
- flourishLabel.text = "$ fetch-pack | verify | unpack";
+ _flourishLabel.text = "$ fetch-pack | verify | unpack";
CompletedGachaPull completed = ResolvePull(pending);
GachaPullContext.SetCompleted(completed);
- completedHeader = completed.HeaderText;
- rewards.Clear();
- rewards.AddRange(completed.Rewards);
+ _completedHeader = completed.HeaderText;
+ _rewards.Clear();
+ _rewards.AddRange(completed.Rewards);
- if (rewards.Count == 0)
+ if (_rewards.Count == 0)
{
- failed = true;
- costLabel.text = completed.HeaderText;
+ _failed = true;
+ _costLabel.text = completed.HeaderText;
ShowContinue();
yield break;
}
@@ -199,24 +199,24 @@ namespace KernelPanic.UI
}
yield return WaitOrSkip(FlourishMs);
- flourishLabel.text = "$ unpacking sealed objects";
- for (int i = 0; i < rewards.Count; i++)
+ _flourishLabel.text = "$ unpacking sealed objects";
+ for (int i = 0; i < _rewards.Count; i++)
{
- if (skipRequested)
+ if (_skipRequested)
{
RevealAll();
yield break;
}
- CompletedGachaReward reward = rewards[i];
- PullCardView card = cardViews[i];
+ CompletedGachaReward reward = _rewards[i];
+ PullCardView card = _cardViews[i];
int holdMs = GetAnticipationMs(reward.Stars);
if (holdMs > 0)
{
ApplyGridFocus(i, reward.Stars);
card.ShowHold(reward.Stars >= 5 ? "........ signed tag" : "... promoted object", reward.Stars);
yield return WaitOrSkip(holdMs);
- if (skipRequested)
+ if (_skipRequested)
{
RevealAll();
yield break;
@@ -315,25 +315,25 @@ namespace KernelPanic.UI
private void BuildRows()
{
- resultGrid.Clear();
- cardViews.Clear();
- for (int i = 0; i < rewards.Count; i++)
+ _resultGrid.Clear();
+ _cardViews.Clear();
+ for (int i = 0; i < _rewards.Count; i++)
{
- PullCardView card = new(rewards[i]);
- cardViews.Add(card);
- resultGrid.Add(card.Root);
+ PullCardView card = new(_rewards[i]);
+ _cardViews.Add(card);
+ _resultGrid.Add(card.Root);
}
}
private void RevealAll()
{
- skipRequested = true;
+ _skipRequested = true;
ClearGridFocus();
- goldShimmer.RemoveFromClassList("gold-shimmer-on");
- for (int i = 0; i < cardViews.Count; i++)
+ _goldShimmer.RemoveFromClassList("gold-shimmer-on");
+ for (int i = 0; i < _cardViews.Count; i++)
{
- cardViews[i].HideHold();
- cardViews[i].Reveal(allowFlash: false, persistentMotion: !UIPreferences.ReducedMotion);
+ _cardViews[i].HideHold();
+ _cardViews[i].Reveal(allowFlash: false, persistentMotion: !UIPreferences.ReducedMotion);
}
CompleteReveal();
@@ -341,49 +341,49 @@ namespace KernelPanic.UI
private void CompleteReveal()
{
- if (revealComplete)
+ if (_revealComplete)
{
return;
}
- revealComplete = true;
- skipHint.text = "complete";
- flourishLabel.text = completedHeader;
- flourishLabel.AddToClassList("gambling-flourish-complete");
- BuildSummary(animate: !skipRequested && !UIPreferences.ReducedMotion);
+ _revealComplete = true;
+ _skipHint.text = "complete";
+ _flourishLabel.text = _completedHeader;
+ _flourishLabel.AddToClassList("gambling-flourish-complete");
+ BuildSummary(animate: !_skipRequested && !UIPreferences.ReducedMotion);
ShowContinue();
}
private void BuildSummary(bool animate)
{
- summaryPanel.Clear();
- summaryPanel.RemoveFromClassList("hidden");
- summaryTotalsLabel = new Label { name = "PullSummaryTotals" };
- summaryTotalsLabel.AddToClassList("summary-totals");
- summaryPanel.Add(summaryTotalsLabel);
+ _summaryPanel.Clear();
+ _summaryPanel.RemoveFromClassList("hidden");
+ _summaryTotalsLabel = new Label { name = "PullSummaryTotals" };
+ _summaryTotalsLabel.AddToClassList("summary-totals");
+ _summaryPanel.Add(_summaryTotalsLabel);
SummaryTotals totals = CalculateTotals();
if (!animate)
{
- summaryTotalsLabel.text = FormatTotals(totals.Units, totals.Merges, totals.Cache, totals.LanguagesText);
+ _summaryTotalsLabel.text = FormatTotals(totals.Units, totals.Merges, totals.Cache, totals.LanguagesText);
return;
}
- summaryTotalsLabel.AddToClassList("summary-tick");
+ _summaryTotalsLabel.AddToClassList("summary-tick");
for (int step = 0; step <= SummaryTickSteps; step++)
{
int capturedStep = step;
int delay = Mathf.RoundToInt((SummaryTickMs / (float)SummaryTickSteps) * capturedStep);
- summaryTotalsLabel.schedule.Execute(() =>
+ _summaryTotalsLabel.schedule.Execute(() =>
{
float t = capturedStep / (float)SummaryTickSteps;
int units = Mathf.RoundToInt(Mathf.Lerp(0, totals.Units, t));
int merges = Mathf.RoundToInt(Mathf.Lerp(0, totals.Merges, t));
int cache = Mathf.RoundToInt(Mathf.Lerp(0, totals.Cache, t));
- summaryTotalsLabel.text = FormatTotals(units, merges, cache, capturedStep == SummaryTickSteps ? totals.LanguagesText : "...");
+ _summaryTotalsLabel.text = FormatTotals(units, merges, cache, capturedStep == SummaryTickSteps ? totals.LanguagesText : "...");
if (capturedStep == SummaryTickSteps)
{
- summaryTotalsLabel.RemoveFromClassList("summary-tick");
+ _summaryTotalsLabel.RemoveFromClassList("summary-tick");
}
}).StartingIn(delay);
}
@@ -395,25 +395,25 @@ namespace KernelPanic.UI
int merges = 0;
int cache = 0;
HashSet languages = new();
- for (int i = 0; i < rewards.Count; i++)
+ for (int i = 0; i < _rewards.Count; i++)
{
- if (rewards[i].OutcomeKind == PullOutcomeKind.Granted && rewards[i].IsCharacter)
+ if (_rewards[i].OutcomeKind == PullOutcomeKind.Granted && _rewards[i].IsCharacter)
{
units++;
}
- if (rewards[i].RewardType == GachaRewardType.Package)
+ if (_rewards[i].RewardType == GachaRewardType.Package)
{
- cache += rewards[i].MergesAwarded;
+ cache += _rewards[i].MergesAwarded;
}
else
{
- merges += rewards[i].MergesAwarded;
+ merges += _rewards[i].MergesAwarded;
}
- for (int languageIndex = 0; languageIndex < rewards[i].LanguagesUnlocked.Count; languageIndex++)
+ for (int languageIndex = 0; languageIndex < _rewards[i].LanguagesUnlocked.Count; languageIndex++)
{
- languages.Add(rewards[i].LanguagesUnlocked[languageIndex]);
+ languages.Add(_rewards[i].LanguagesUnlocked[languageIndex]);
}
}
@@ -591,7 +591,7 @@ namespace KernelPanic.UI
{
float elapsed = 0f;
float duration = Mathf.Max(0f, milliseconds / 1000f);
- while (!skipRequested && elapsed < duration)
+ while (!_skipRequested && elapsed < duration)
{
elapsed += Time.deltaTime;
yield return null;
@@ -615,17 +615,17 @@ namespace KernelPanic.UI
return;
}
- for (int i = 0; i < cardViews.Count; i++)
+ for (int i = 0; i < _cardViews.Count; i++)
{
- cardViews[i].SetDimmed(i != focusedIndex);
+ _cardViews[i].SetDimmed(i != focusedIndex);
}
}
private void ClearGridFocus()
{
- for (int i = 0; i < cardViews.Count; i++)
+ for (int i = 0; i < _cardViews.Count; i++)
{
- cardViews[i].SetDimmed(false);
+ _cardViews[i].SetDimmed(false);
}
}
@@ -656,32 +656,32 @@ namespace KernelPanic.UI
private void PlayGoldShimmer(VisualElement source)
{
- if (source == null || goldShimmer?.parent == null)
+ if (source == null || _goldShimmer?.parent == null)
{
return;
}
Rect sourceBounds = source.worldBound;
- Rect parentBounds = goldShimmer.parent.worldBound;
+ Rect parentBounds = _goldShimmer.parent.worldBound;
float left = Mathf.Max(0f, sourceBounds.x - parentBounds.x);
float top = Mathf.Max(0f, sourceBounds.y - parentBounds.y);
- goldShimmer.style.left = left;
- goldShimmer.style.top = top;
- goldShimmer.style.width = sourceBounds.width;
- goldShimmer.style.height = sourceBounds.height;
- goldShimmer.AddToClassList("gold-shimmer-on");
- goldShimmer.schedule.Execute(() => goldShimmer.RemoveFromClassList("gold-shimmer-on")).StartingIn(LegendaryShimmerMs);
+ _goldShimmer.style.left = left;
+ _goldShimmer.style.top = top;
+ _goldShimmer.style.width = sourceBounds.width;
+ _goldShimmer.style.height = sourceBounds.height;
+ _goldShimmer.AddToClassList("gold-shimmer-on");
+ _goldShimmer.schedule.Execute(() => _goldShimmer.RemoveFromClassList("gold-shimmer-on")).StartingIn(LegendaryShimmerMs);
}
private void EmitSparks(VisualElement source, Color color, int count, float radius)
{
- if (effectLayer == null || source == null)
+ if (_effectLayer == null || source == null)
{
return;
}
Rect sourceBounds = source.worldBound;
- Rect layerBounds = effectLayer.worldBound;
+ Rect layerBounds = _effectLayer.worldBound;
float centerX = sourceBounds.center.x - layerBounds.x;
float centerY = sourceBounds.center.y - layerBounds.y;
float halfWidth = sourceBounds.width * 0.5f;
@@ -728,41 +728,41 @@ namespace KernelPanic.UI
VisualElement spark = new();
spark.pickingMode = PickingMode.Ignore;
spark.AddToClassList("spark");
- effectLayer.Add(spark);
+ _effectLayer.Add(spark);
return spark;
}
private void HandleKeyDown(KeyDownEvent evt)
{
- if (revealComplete)
+ if (_revealComplete)
{
ContinueToMenu();
evt.StopPropagation();
return;
}
- skipRequested = true;
+ _skipRequested = true;
evt.StopPropagation();
}
private void HandlePointerDown(PointerDownEvent evt)
{
- if (revealComplete)
+ if (_revealComplete)
{
return;
}
- skipRequested = true;
+ _skipRequested = true;
evt.StopPropagation();
}
private void ShowContinue()
{
- continueButton.SetEnabled(true);
- continueButton.RemoveFromClassList("continue-button-pending");
- if (failed)
+ _continueButton.SetEnabled(true);
+ _continueButton.RemoveFromClassList("continue-button-pending");
+ if (_failed)
{
- skipHint.text = "failed";
+ _skipHint.text = "failed";
}
}
@@ -789,17 +789,17 @@ namespace KernelPanic.UI
private sealed class PullCardView
{
- private readonly CompletedGachaReward reward;
- private readonly Label rarityLabel;
- private readonly Label nameLabel;
- private readonly Label statusLabel;
- private readonly Label typeIconLabel;
- private IVisualElementScheduledItem glowPulse;
- private bool glowOn;
+ private readonly CompletedGachaReward _reward;
+ private readonly Label _rarityLabel;
+ private readonly Label _nameLabel;
+ private readonly Label _statusLabel;
+ private readonly Label _typeIconLabel;
+ private IVisualElementScheduledItem _glowPulse;
+ private bool _glowOn;
public PullCardView(CompletedGachaReward reward)
{
- this.reward = reward;
+ this._reward = reward;
RarityStyle rarity = RarityPresentation.ForStars(reward.Stars);
Root = new VisualElement();
Root.AddToClassList("pull-card");
@@ -814,27 +814,27 @@ namespace KernelPanic.UI
indexLabel.AddToClassList("pull-index");
top.Add(indexLabel);
- rarityLabel = new(rarity.Stars);
- rarityLabel.AddToClassList("pull-rarity");
- rarityLabel.AddToClassList(rarity.ClassName);
- top.Add(rarityLabel);
+ _rarityLabel = new(rarity.Stars);
+ _rarityLabel.AddToClassList("pull-rarity");
+ _rarityLabel.AddToClassList(rarity.ClassName);
+ top.Add(_rarityLabel);
- typeIconLabel = new(reward.IsCharacter ? "@" : "#");
- typeIconLabel.tooltip = reward.TypeText;
- typeIconLabel.AddToClassList("pull-type-icon");
- top.Add(typeIconLabel);
+ _typeIconLabel = new(reward.IsCharacter ? "@" : "#");
+ _typeIconLabel.tooltip = reward.TypeText;
+ _typeIconLabel.AddToClassList("pull-type-icon");
+ top.Add(_typeIconLabel);
- nameLabel = new(reward.DisplayName);
- nameLabel.AddToClassList("pull-name");
- Root.Add(nameLabel);
+ _nameLabel = new(reward.DisplayName);
+ _nameLabel.AddToClassList("pull-name");
+ Root.Add(_nameLabel);
- statusLabel = new(reward.StatusText);
- statusLabel.AddToClassList("pull-status");
- statusLabel.EnableInClassList("pull-status-new", reward.OutcomeKind == PullOutcomeKind.Granted);
- statusLabel.visible = !string.IsNullOrWhiteSpace(reward.StatusText);
- if (statusLabel.visible)
+ _statusLabel = new(reward.StatusText);
+ _statusLabel.AddToClassList("pull-status");
+ _statusLabel.EnableInClassList("pull-status-new", reward.OutcomeKind == PullOutcomeKind.Granted);
+ _statusLabel.visible = !string.IsNullOrWhiteSpace(reward.StatusText);
+ if (_statusLabel.visible)
{
- Root.Add(statusLabel);
+ Root.Add(_statusLabel);
}
}
@@ -846,11 +846,11 @@ namespace KernelPanic.UI
Root.AddToClassList("pull-card-held");
Root.EnableInClassList("pull-card-held-rare", stars == 4);
Root.EnableInClassList("pull-card-held-legendary", stars >= 5);
- rarityLabel.text = "...";
- nameLabel.text = holdText;
- statusLabel.text = "";
- statusLabel.visible = false;
- typeIconLabel.visible = false;
+ _rarityLabel.text = "...";
+ _nameLabel.text = holdText;
+ _statusLabel.text = "";
+ _statusLabel.visible = false;
+ _typeIconLabel.visible = false;
}
public void HideHold()
@@ -862,21 +862,21 @@ namespace KernelPanic.UI
public void Reveal(bool allowFlash, bool persistentMotion)
{
- RarityStyle rarity = RarityPresentation.ForStars(reward.Stars);
+ RarityStyle rarity = RarityPresentation.ForStars(_reward.Stars);
Root.RemoveFromClassList("pull-card-hidden");
Root.RemoveFromClassList("pull-card-held");
Root.RemoveFromClassList("pull-card-held-rare");
Root.RemoveFromClassList("pull-card-held-legendary");
Root.AddToClassList("pull-card-revealed");
- Root.EnableInClassList("pull-card-glow-4", persistentMotion && reward.Stars == 4);
- Root.EnableInClassList("pull-card-glow-5", persistentMotion && reward.Stars >= 5);
- SetPersistentGlow(persistentMotion && reward.Stars >= 5);
- rarityLabel.text = reward.Stars >= 5 ? $"{rarity.Badge} {rarity.Stars}" : rarity.Stars;
- nameLabel.text = reward.DisplayName;
- typeIconLabel.text = reward.IsCharacter ? "@" : "#";
- typeIconLabel.visible = true;
- statusLabel.text = reward.StatusText;
- statusLabel.visible = !string.IsNullOrWhiteSpace(reward.StatusText);
+ Root.EnableInClassList("pull-card-glow-4", persistentMotion && _reward.Stars == 4);
+ Root.EnableInClassList("pull-card-glow-5", persistentMotion && _reward.Stars >= 5);
+ SetPersistentGlow(persistentMotion && _reward.Stars >= 5);
+ _rarityLabel.text = _reward.Stars >= 5 ? $"{rarity.Badge} {rarity.Stars}" : rarity.Stars;
+ _nameLabel.text = _reward.DisplayName;
+ _typeIconLabel.text = _reward.IsCharacter ? "@" : "#";
+ _typeIconLabel.visible = true;
+ _statusLabel.text = _reward.StatusText;
+ _statusLabel.visible = !string.IsNullOrWhiteSpace(_reward.StatusText);
if (allowFlash)
{
Root.AddToClassList("pull-card-flash");
@@ -897,34 +897,34 @@ namespace KernelPanic.UI
public void PulseStatus(int durationMs)
{
- if (!statusLabel.visible)
+ if (!_statusLabel.visible)
{
return;
}
- statusLabel.AddToClassList("pull-status-pop");
- statusLabel.schedule.Execute(() => statusLabel.RemoveFromClassList("pull-status-pop")).StartingIn(durationMs);
+ _statusLabel.AddToClassList("pull-status-pop");
+ _statusLabel.schedule.Execute(() => _statusLabel.RemoveFromClassList("pull-status-pop")).StartingIn(durationMs);
}
private void SetPersistentGlow(bool enabled)
{
if (!enabled)
{
- glowPulse?.Pause();
+ _glowPulse?.Pause();
Root.RemoveFromClassList("pull-card-glow-on");
return;
}
- if (glowPulse != null)
+ if (_glowPulse != null)
{
- glowPulse.Resume();
+ _glowPulse.Resume();
return;
}
- glowPulse = Root.schedule.Execute(() =>
+ _glowPulse = Root.schedule.Execute(() =>
{
- glowOn = !glowOn;
- Root.EnableInClassList("pull-card-glow-on", glowOn);
+ _glowOn = !_glowOn;
+ Root.EnableInClassList("pull-card-glow-on", _glowOn);
}).Every(900);
}
}
diff --git a/Assets/Scripts/UI/MainMenuController.cs b/Assets/Scripts/UI/MainMenuController.cs
index 910729c..eaf2d42 100644
--- a/Assets/Scripts/UI/MainMenuController.cs
+++ b/Assets/Scripts/UI/MainMenuController.cs
@@ -28,8 +28,8 @@ namespace KernelPanic.UI
"Courier New"
};
- private static FontAsset runtimeMonospaceFont;
- private static bool warnedMissingFont;
+ private static FontAsset _runtimeMonospaceFont;
+ private static bool _warnedMissingFont;
public static FontAsset Resolve(FontAsset configuredFont)
{
@@ -38,25 +38,25 @@ namespace KernelPanic.UI
return configuredFont;
}
- if (runtimeMonospaceFont != null)
+ if (_runtimeMonospaceFont != null)
{
- return runtimeMonospaceFont;
+ return _runtimeMonospaceFont;
}
Font dynamicFont = Font.CreateDynamicFontFromOSFont(PreferredMonospaceFontFamilies, 16);
if (dynamicFont == null)
{
- if (!warnedMissingFont)
+ if (!_warnedMissingFont)
{
Debug.LogWarning("No monospace OS font found; terminal UI will use Unity's default font.");
- warnedMissingFont = true;
+ _warnedMissingFont = true;
}
return null;
}
- runtimeMonospaceFont = FontAsset.CreateFontAsset(dynamicFont);
- return runtimeMonospaceFont;
+ _runtimeMonospaceFont = FontAsset.CreateFontAsset(dynamicFont);
+ return _runtimeMonospaceFont;
}
}
@@ -667,7 +667,7 @@ namespace KernelPanic.UI
_saveData = _saveService.Load();
// Snapshot before the next EnsureLists() call below, which re-runs NormalizeOwnedPackages
// and would otherwise clear this (no new duplicates survive a second normalization pass).
- List collapsedPackageDuplicates = new(_saveData.collapsedPackageDuplicates);
+ List collapsedPackageDuplicates = new(_saveData.CollapsedPackageDuplicates);
_saveData.EnsureLists();
_wallet.SetBalance(_saveData.entropyBalance);