Implement Arch and some additional changes

This commit is contained in:
2026-07-09 18:32:52 +02:00
parent 95cebf8df0
commit 1e53968c36
31 changed files with 980 additions and 79 deletions
+11
View File
@@ -47,3 +47,14 @@ MonoBehaviour:
- {fileID: 11400000, guid: a7d60000000000000000000000000004, type: 2}
- {fileID: 11400000, guid: a7d60000000000000000000000000005, type: 2}
- {fileID: 11400000, guid: a7d60000000000000000000000000006, type: 2}
- {fileID: 11400000, guid: aa110000000000000000000000000001, type: 2}
- {fileID: 11400000, guid: aa110000000000000000000000000002, type: 2}
- {fileID: 11400000, guid: aa110000000000000000000000000003, type: 2}
- {fileID: 11400000, guid: aa110000000000000000000000000004, type: 2}
- {fileID: 11400000, guid: aa110000000000000000000000000005, type: 2}
- {fileID: 11400000, guid: aa120000000000000000000000000001, type: 2}
- {fileID: 11400000, guid: aa120000000000000000000000000002, type: 2}
- {fileID: 11400000, guid: aa120000000000000000000000000003, type: 2}
- {fileID: 11400000, guid: aa120000000000000000000000000004, type: 2}
- {fileID: 11400000, guid: aa120000000000000000000000000005, type: 2}
- {fileID: 11400000, guid: aa120000000000000000000000000006, type: 2}
@@ -0,0 +1,29 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5178747fac4dbfc448ff15d8a687e03c, type: 3}
m_Name: Malloc
m_EditorClassIdentifier:
id: lang_c_malloc
displayName: malloc()
description: deal 8. 20% segfault: take 3.
flavorText: memory acquired. ownership emotionally unresolved.
language: 0
rarity: 0
cycleCost: 1
resolutionTrack: 0
distroExclusive: 0
isToken: 0
isRunOnly: 0
designNotes: C starter. Segfault rolls through RandomRoll; damage and recoil route through DamagePipeline.
requiresSystem: []
upgradeTarget: {fileID: 0}
effects: []
@@ -0,0 +1,29 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5178747fac4dbfc448ff15d8a687e03c, type: 3}
m_Name: Pointer
m_EditorClassIdentifier:
id: lang_c_pointer
displayName: pointer
description: deal 6. if the previous card was C, deal 10 instead.
flavorText: points at the bug with suspicious confidence.
language: 0
rarity: 0
cycleCost: 1
resolutionTrack: 0
distroExclusive: 0
isToken: 0
isRunOnly: 0
designNotes: C starter. Uses existing last-played-language tracking on CombatantState; damage routes through DamagePipeline.
requiresSystem: []
upgradeTarget: {fileID: 0}
effects: []
@@ -0,0 +1,29 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5178747fac4dbfc448ff15d8a687e03c, type: 3}
m_Name: Printf
m_EditorClassIdentifier:
id: lang_c_printf
displayName: printf()
description: deal 4.
flavorText: debugging by yelling into the void.
language: 0
rarity: 0
cycleCost: 0
resolutionTrack: 0
distroExclusive: 0
isToken: 0
isRunOnly: 0
designNotes: C starter. Clean low-cost damage routed through DamagePipeline.
requiresSystem: []
upgradeTarget: {fileID: 0}
effects: []
+22
View File
@@ -0,0 +1,22 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: a7d10000000000000000000000000001, type: 3}
m_Name: CDeck
m_EditorClassIdentifier:
language: 0
entries:
- card: {fileID: 11400000, guid: aa120000000000000000000000000001, type: 2}
count: 2
- card: {fileID: 11400000, guid: aa120000000000000000000000000002, type: 2}
count: 1
- card: {fileID: 11400000, guid: aa120000000000000000000000000003, type: 2}
count: 1
@@ -17,3 +17,4 @@ MonoBehaviour:
- {fileID: 11400000, guid: a7d50000000000000000000000000002, type: 2}
- {fileID: 11400000, guid: a7d70000000000000000000000000001, type: 2}
- {fileID: 11400000, guid: a7d70000000000000000000000000002, type: 2}
- {fileID: 11400000, guid: aa130000000000000000000000000001, type: 2}
+29
View File
@@ -0,0 +1,29 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5178747fac4dbfc448ff15d8a687e03c, type: 3}
m_Name: Free
m_EditorClassIdentifier:
id: shop_c_free
displayName: free()
description: deal 12. if segfault recoil happens, draw 2.
flavorText: double-free your schedule, not your heap.
language: 0
rarity: 0
cycleCost: 2
resolutionTrack: 0
distroExclusive: 0
isToken: 0
isRunOnly: 1
designNotes: C run-only shop card. Uses segfault recoil hook through RandomRoll and DamagePipeline; draw uses existing draw pipeline.
requiresSystem: []
upgradeTarget: {fileID: 0}
effects: []
@@ -0,0 +1,29 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5178747fac4dbfc448ff15d8a687e03c, type: 3}
m_Name: KernelExploit
m_EditorClassIdentifier:
id: shop_c_kernel_exploit
displayName: kernel_exploit
description: deal 25 ignoring Shield. 50% segfault: take 8.
flavorText: privilege escalated. heartbeat also escalated.
language: 0
rarity: 1
cycleCost: 3
resolutionTrack: 0
distroExclusive: 0
isToken: 0
isRunOnly: 1
designNotes: C run-only shop card. Ignore-Shield damage uses the true-damage pipeline flag; segfault recoil uses RandomRoll and DamagePipeline.
requiresSystem: []
upgradeTarget: {fileID: 0}
effects: []
+29
View File
@@ -0,0 +1,29 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5178747fac4dbfc448ff15d8a687e03c, type: 3}
m_Name: Sizeof
m_EditorClassIdentifier:
id: shop_c_sizeof
displayName: sizeof()
description: deal 3 x C cards in hand.
flavorText: compile-time confidence, runtime consequences.
language: 0
rarity: 1
cycleCost: 1
resolutionTrack: 0
distroExclusive: 0
isToken: 0
isRunOnly: 1
designNotes: C run-only shop card. Counts current C cards in hand and deals through DamagePipeline.
requiresSystem: []
upgradeTarget: {fileID: 0}
effects: []
+37
View File
@@ -0,0 +1,37 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: bd550368fd0c75d43a53aa4fb3b6541e, type: 3}
m_Name: Arch
m_EditorClassIdentifier:
id: arch
displayName: Arch
primaryLanguage: 0
secondaryLanguage: 2
passive: {fileID: 11400000, guid: aa100000000000000000000000000001, type: 2}
description: The DIY power-user. Maximum control, maximum fragility. She can do things no one else can - and break herself doing it.
playstyleSummary: volatile C/Rust tempo. every played card adds btw, which makes C and Rust hits bigger until the turn ends.
languageBlurbs:
- lang: 0
blurb: sharp native damage with a higher crit rate and occasional segfault recoil.
- lang: 2
blurb: reliable native damage; overkill converts into shield.
asciiArt: {fileID: 4900000, guid: 004c732ec64a45fd82c5a47ae7ffed43, type: 3}
accentColor: {r: 0.09019608, g: 0.5764706, b: 0.81960785, a: 1}
baseUptime: 50
baseRam: 6
baseCyclesPerTurn: 4
exclusiveCards:
- {fileID: 11400000, guid: aa110000000000000000000000000001, type: 2}
- {fileID: 11400000, guid: aa110000000000000000000000000002, type: 2}
- {fileID: 11400000, guid: aa110000000000000000000000000003, type: 2}
- {fileID: 11400000, guid: aa110000000000000000000000000004, type: 2}
- {fileID: 11400000, guid: aa110000000000000000000000000005, type: 2}
+16
View File
@@ -0,0 +1,16 @@
++
++++
+++++=
+++++++=
+ ++++++=
====+++++++
==========+++
==++++++++++++
+++++++ +++++++
++++++ ++++++
+++++++ +++++++
++++++++ +++++++
+++++++ +++++++
+++ +++
+ +
+7
View File
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 004c732ec64a45fd82c5a47ae7ffed43
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+1
View File
@@ -16,3 +16,4 @@ MonoBehaviour:
- {fileID: 11400000, guid: 1b0892a0d4c34b03a54c3d511ae59356, type: 2}
- {fileID: 11400000, guid: 245dfb73afe047d5a3a3ef54c30d5135, type: 2}
- {fileID: 11400000, guid: 366e71361387449abcfaa1a50c9af87b, type: 2}
- {fileID: 11400000, guid: aa101000000000000000000000000001, type: 2}
+26
View File
@@ -0,0 +1,26 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 48447d3db18343398488d3064324147d, type: 3}
m_Name: Aur
m_EditorClassIdentifier:
id: pkg_aur
displayName: AUR
slot: 1
rarity: 4
description: start each TURN with a generated C/Rust card. on Arch, any rarity and costs 1 less this turn; off-distro, common only with no discount.
flavourText: probably maintained by one person and a shell script named pls-work.sh.
designNotes: Arch 4-star Runtime package. Scope TURN. On Arch: generate random C/Rust card up to Legendary rarity and apply -1 temporary cost. Off-distro: generate Common C/Rust only with no discount. Uses existing generated-card pool; requiresSystem card-generation if no eligible pool is available. Runtime slot intentionally does not conflict with systemd Daemon.
intendedDistroId: arch
requiresSystem:
- card-generation
offDistroEffect: {kind: 26, amount: 0, threshold: 0, refundCycle: 0, cleanseDebuffs: 0, enableFedoraSecondCardPassive: 0, persistArchBtw: 0}
onDistroEffect: {kind: 26, amount: 1, threshold: 3, refundCycle: 0, cleanseDebuffs: 0, enableFedoraSecondCardPassive: 0, persistArchBtw: 0}
@@ -44,3 +44,5 @@ MonoBehaviour:
- {fileID: 11400000, guid: 159fca0abb7249e1be485aaac5f83fbe, type: 2}
- {fileID: 11400000, guid: ac481beaf7e6453d82a439d54c2fb2f3, type: 2}
- {fileID: 11400000, guid: a8e4cfd86f554fa19198551a7d5ed8ab, type: 2}
- {fileID: 11400000, guid: aa140000000000000000000000000001, type: 2}
- {fileID: 11400000, guid: aa140000000000000000000000000002, type: 2}
+25
View File
@@ -0,0 +1,25 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 48447d3db18343398488d3064324147d, type: 3}
m_Name: Systemd
m_EditorClassIdentifier:
id: pkg_systemd
displayName: systemd
slot: 2
rarity: 5
description: first C card each TURN deals 50% more damage. on Arch, btw stacks persist for the whole WAVE.
flavourText: one daemon to start them all, stop them all, and explain none of it.
designNotes: Arch signature Daemon. Off-distro grants only the first-C-card DamagePipeline multiplier. On Arch also sets persistArchBtw, so the Arch passive skips end-of-TURN btw reset and clears only on WAVE start or rolling-release recovery.
intendedDistroId: arch
requiresSystem: []
offDistroEffect: {kind: 25, amount: 150, threshold: 1, refundCycle: 0, cleanseDebuffs: 0, enableFedoraSecondCardPassive: 0, persistArchBtw: 0}
onDistroEffect: {kind: 25, amount: 150, threshold: 1, refundCycle: 0, cleanseDebuffs: 0, enableFedoraSecondCardPassive: 0, persistArchBtw: 1}
+19
View File
@@ -0,0 +1,19 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 9b1f51fb7c354203a2f93ad22fbeb101, type: 3}
m_Name: IUseArchBtw
m_EditorClassIdentifier:
id: arch_i_use_arch_btw
displayName: I use Arch, btw
rulesText: Each card played adds 1 btw until end of turn. C and Rust damage gains +1 per btw. Once per wave, lethal uptime sets Arch to 1 instead and clears btw.
flavorText: Compiled from source, emotionally and otherwise.
designNotes: V1 base passive. Increment btw before resolving each played card. End of TURN resets btw unless Arch has systemd equipped on-distro. First lethal damage each WAVE consumes rolling-release save, sets uptime to 1, clears btw, and logs a recovery beat.
+224 -5
View File
@@ -49,10 +49,35 @@ namespace KernelPanic.Combat
? minAmount
: RandomRoll.RollRange(minAmount, maxAmount, new RollContext(context.Source));
context.DamagePipeline.DealDamage(new DamageRequest(context.Source, target, amount, _language, _trueDamage, _canCrit));
bool rustOverkillToShield = _language == Language.Rust || context.Card?.Definition?.Language == Language.Rust;
int survivable = rustOverkillToShield
? _trueDamage ? target.CurrentUptime : target.CurrentUptime + target.Shield
: 0;
DamageResult result = context.DamagePipeline.DealDamage(new DamageRequest(context.Source, target, amount, _language, _trueDamage, _canCrit));
if (rustOverkillToShield)
{
GrantRustOverkillShield(context, result, survivable);
}
}
}
private static void GrantRustOverkillShield(CombatContext context, DamageResult result, int survivable)
{
int overkill = UnityEngine.Mathf.Max(0, result.IncomingAmount - survivable);
if (overkill <= 0)
{
return;
}
if (!context.CombatManager.CanPlayerReceiveCardShield(context.Card))
{
context.CombatManager.ReportEffectResult($"overkill shield blocked: {overkill}");
return;
}
context.CombatManager.GrantPlayerShield(overkill, "rust overkill");
}
private static IReadOnlyList<CombatantState> ShuffleTargets(IReadOnlyList<CombatantState> targets, CombatantState source)
{
List<CombatantState> shuffled = targets.ToList();
@@ -164,6 +189,177 @@ 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;
private readonly int _matchingAmount;
private readonly Language _language;
public PreviousLanguageDamageEffect(int baseAmount, int matchingAmount, Language language)
{
_baseAmount = baseAmount;
_matchingAmount = matchingAmount;
_language = language;
}
public void Execute(CombatContext context)
{
bool matches = context.Source != null
&& context.Source.HasPreviousPlayedCardLanguage
&& context.Source.PreviousPlayedCardLanguage == _language;
int amount = matches ? _matchingAmount : _baseAmount;
new DealDamageEffect(amount, amount, _language).Execute(context);
}
}
public sealed class BtwScaledDamageEffect : ICardEffect
{
private readonly int _baseAmount;
private readonly int _perBtw;
private readonly Language _language;
public BtwScaledDamageEffect(int baseAmount, int perBtw, Language language)
{
_baseAmount = baseAmount;
_perBtw = perBtw;
_language = language;
}
public void Execute(CombatContext context)
{
int btw = UnityEngine.Mathf.Max(0, context.Source?.ArchBtwStacks ?? 0);
int perBtw = UnityEngine.Mathf.Max(_perBtw, context.Source?.ArchMakepkgBtwMultiplier ?? _perBtw);
int amount = _baseAmount + (btw * perBtw);
new DealDamageEffect(amount, amount, _language).Execute(context);
}
}
public sealed class RandomCommonLanguageCardEffect : ICardEffect
{
public void Execute(CombatContext context)
{
Language language = RandomRoll.RollRange(0, 1, new RollContext(context.Source)) == 0 ? Language.C : Language.Rust;
if (!context.CombatManager.TryCreateGeneratedCard(language, Rarity.Common, out CardInstance generated))
{
context.CombatManager.ReportEffectResult("TODO card-generation: no C/Rust common available");
return;
}
generated.TemporaryCostDelta -= CombatManager.GetCardCost(generated);
if (context.HandController.Add(generated))
{
context.CombatManager.ReportEffectResult($"AUR helper: generated {GetCardName(generated)} at 0c");
}
else
{
context.CombatManager.ReportEffectResult("AUR helper: hand full");
}
}
private static string GetCardName(CardInstance card)
{
return card?.Definition == null || string.IsNullOrWhiteSpace(card.Definition.DisplayName) ? card?.Definition?.Id ?? "--" : card.Definition.DisplayName;
}
}
public sealed class MinimalInstallEffect : ICardEffect
{
public void Execute(CombatContext context)
{
int exhausted = 0;
List<CardInstance> candidates = context.HandController.Cards.Where(card => card != null).Take(2).ToList();
for (int i = 0; i < candidates.Count; i++)
{
CardInstance card = candidates[i];
if (!context.HandController.Remove(card))
{
continue;
}
context.DeckController.Exhaust(card);
context.CombatManager.HandleCardExhausted(card);
exhausted++;
}
if (exhausted <= 0)
{
context.CombatManager.ReportEffectResult("minimal install: no hand cards to exhaust");
return;
}
int shield = exhausted * 3;
if (context.CombatManager.CanPlayerReceiveCardShield(context.Card))
{
context.CombatManager.GrantPlayerShield(shield, "minimal install");
}
context.Source.Cycles += exhausted;
context.CombatManager.ReportEffectResult($"minimal install: exhausted {exhausted}, +{exhausted} Cycle");
}
}
public sealed class SizeofDamageEffect : ICardEffect
{
public void Execute(CombatContext context)
{
int cCards = context.HandController.Cards.Count(card => card?.Definition != null && card.Definition.Language == Language.C);
int amount = 3 * cCards;
new DealDamageEffect(amount, amount, Language.C).Execute(context);
}
}
public sealed class ConsultWikiEffect : ICardEffect
{
public void Execute(CombatContext context)
{
CardInstance broken = context.HandController.Cards.FirstOrDefault(card => card != null && card.IsBroken);
if (broken != null)
{
broken.IsBroken = false;
context.CombatManager.DrawCards(1, "consult the wiki");
context.CombatManager.ReportEffectResult("fixed a broken card");
return;
}
new DealDamageEffect(6, 6, Language.Rust).Execute(context);
}
}
public sealed class ApplyStatusEffect : ICardEffect
{
@@ -696,13 +892,16 @@ namespace KernelPanic.Combat
"lang_py_for_loop" => One(new DealDamageEffect(2, 2, Language.Python, allEnemies: true)),
"lang_js_console_log" => One(new DealDamageEffect(3, 9, Language.JavaScript)),
"lang_js_fetch" => One(new DealDamageEffect(5, 13, Language.JavaScript)),
"lang_rs_fn_main" => One(new OverkillToShieldDamageEffect(6, Language.Rust)),
"lang_rs_fn_main" => One(new DealDamageEffect(6, 6, Language.Rust)),
"lang_rs_unwrap" => new ICardEffect[]
{
new DealDamageEffect(9, 9, Language.Rust),
new DrawEffect(1)
},
"lang_rs_borrow" => One(new ShieldEffect(5)),
"lang_c_malloc" => One(new SegfaultDamageEffect(8, 3, 20)),
"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)),
"lang_java_system_out_println" => One(new DealDamageEffect(5, 5, Language.Java)),
"lang_java_new_object" => One(new DealDamageEffect(3, 3, Language.Java)),
@@ -754,6 +953,14 @@ namespace KernelPanic.Combat
"fedora_dnf_update" => One(new DealDamageEffect(9, 9, Language.Java)),
"fedora_rawhide" => One(new RawhideEffect()),
"fedora_selinux" => One(new IncomingAttackHalveEffect(2)),
"arch_pacman_syu" => One(new DealDamageEffect(8, 8, Language.C)),
"arch_consult_wiki" => One(new ConsultWikiEffect()),
"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_sizeof" => One(new SizeofDamageEffect()),
"shop_c_kernel_exploit" => One(new SegfaultDamageEffect(25, 8, 50, true)),
_ => Todo("card effect is not authored")
};
}
@@ -761,10 +968,11 @@ namespace KernelPanic.Combat
public static bool RequiresSingleTarget(CardDefinition definition)
{
return definition?.Id is "lang_js_console_log" or "lang_js_fetch" or "lang_js_typeof" or "mint_fix_broken"
or "lang_rs_fn_main" or "lang_rs_unwrap" or "lang_java_public_static_main"
or "lang_rs_fn_main" or "lang_rs_unwrap" or "lang_c_malloc" or "lang_c_printf" or "lang_c_pointer" or "lang_java_public_static_main"
or "lang_java_system_out_println" or "lang_java_new_object"
or "shop_py_list_append" or "shop_js_math_random" or "shop_js_promise_all" or "ubuntu_snap_install"
or "mint_nemo" or "fedora_dnf_autoremove" or "fedora_first_to_package" or "fedora_dnf_update";
or "mint_nemo" or "fedora_dnf_autoremove" or "fedora_first_to_package" or "fedora_dnf_update"
or "arch_pacman_syu" or "arch_consult_wiki" or "arch_makepkg" or "shop_c_free" or "shop_c_sizeof" or "shop_c_kernel_exploit";
}
public static bool TargetsAllEnemies(CardDefinition definition)
@@ -820,8 +1028,11 @@ namespace KernelPanic.Combat
"lang_js_fetch" => $"deal {UpgradeMath.ScaleAmount(5, card)}-{UpgradeMath.ScaleAmount(13, card)}.{marker}",
"lang_js_typeof" => $"75%: deal {UpgradeMath.ScaleAmount(8, card)}. 25%: deal 0.{marker}",
"lang_rs_fn_main" => $"deal {UpgradeMath.ScaleAmount(6, card)}. overkill becomes shield.{marker}",
"lang_rs_unwrap" => $"deal {UpgradeMath.ScaleAmount(9, card)}. draw {UpgradeMath.ScaleAmount(1, card)}.{marker}",
"lang_rs_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_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}",
@@ -846,6 +1057,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_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_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}",
_ => string.IsNullOrWhiteSpace(card.Definition.Description) ? "effect not authored." : $"{card.Definition.Description}{marker}"
};
}
+151
View File
@@ -42,6 +42,7 @@ namespace KernelPanic.Combat
private int fedoraCrashChance = 10;
private int cardsPlayedThisTurn;
private int cardsPlayedThisWave;
private int cCardsPlayedThisTurn;
private int turnNumberThisWave;
private bool packageShieldBonusTriggeredThisTurn;
private bool packageTimeshiftTriggeredThisWave;
@@ -214,6 +215,19 @@ namespace KernelPanic.Combat
card.MarkPlayedThisTurn(cardsPlayedThisTurn == 0);
cardsPlayedThisTurn++;
cardsPlayedThisWave++;
if (IsDistro("arch"))
{
playerState.ArchBtwStacks++;
}
playerState.HasPreviousPlayedCardLanguage = playerState.HasLastPlayedCardLanguage;
if (playerState.HasLastPlayedCardLanguage)
{
playerState.PreviousPlayedCardLanguage = playerState.LastPlayedCardLanguage;
}
playerState.LastPlayedCardLanguage = card.Definition.Language;
playerState.HasLastPlayedCardLanguage = true;
if (card.Definition.Language == Language.Java)
{
javaCardsPlayedThisCombat++;
@@ -254,6 +268,11 @@ namespace KernelPanic.Combat
ApplyFedoraGrowth(card, rawhideChargeUsed);
}
if (card.Definition.Language == Language.C && cCardsPlayedThisTurn == 0 && HasPackageEffect(PackageEffectKind.FirstCThisTurnDamageMultiplier, out PackageEffectData cPackageEffect))
{
playerState.CurrentCardDamageMultiplierPercent = Mathf.Max(100, cPackageEffect.Amount);
}
switch (track)
{
case ResolutionTrack.InterpreterQueue:
@@ -273,6 +292,12 @@ namespace KernelPanic.Combat
}
playerState.DamageMultiplierPercent = 100;
playerState.CurrentCardDamageMultiplierPercent = 100;
if (card.Definition.Language == Language.C)
{
cCardsPlayedThisTurn++;
}
ApplyPackageCardPlayedEffects(card);
pendingTargetCard = null;
@@ -485,6 +510,16 @@ namespace KernelPanic.Combat
return true;
}
public void DrawCards(int count, string label)
{
if (count <= 0)
{
return;
}
StartCoroutine(DrawCardsToHandSequenced(count, label));
}
private void SetPhase(TurnPhase nextPhase)
{
if (IsCombatPaused && nextPhase != TurnPhase.Boot)
@@ -566,6 +601,7 @@ namespace KernelPanic.Combat
fedoraCardsDiscountedThisTurn = 0;
cardsPlayedThisTurn = 0;
cardsPlayedThisWave = 0;
cCardsPlayedThisTurn = 0;
turnNumberThisWave = 0;
packageShieldBonusTriggeredThisTurn = false;
packageTimeshiftTriggeredThisWave = false;
@@ -617,6 +653,7 @@ namespace KernelPanic.Combat
turnNumberThisWave = 0;
packageShieldBonusTriggeredThisTurn = false;
packageTimeshiftTriggeredThisWave = false;
ResetArchWaveState();
List<CardInstance> startingCards = new();
for (int i = 0; i < runManager.RunDeck.Count; i++)
@@ -650,6 +687,7 @@ namespace KernelPanic.Combat
playerState.Cycles = playerState.MaxCycles;
fedoraCardsDiscountedThisTurn = 0;
cardsPlayedThisTurn = 0;
cCardsPlayedThisTurn = 0;
turnNumberThisWave++;
packageShieldBonusTriggeredThisTurn = false;
javaCardsDiscountThisTurn = 0;
@@ -770,6 +808,7 @@ namespace KernelPanic.Combat
return;
}
ResetArchBtwAtEndOfTurn();
RemoveDefeatedEnemies();
CheckWinOrLoss();
StateChanged?.Invoke();
@@ -1063,6 +1102,11 @@ namespace KernelPanic.Combat
{
GrantPlayerShield(Mathf.Max(0, effect.Amount), "auditd");
}
if (HasPackageEffect(PackageEffectKind.TurnStartGenerateLanguageCard, out effect))
{
GenerateAurPackageCard(effect);
}
}
private void ApplyPackageCardPlayedEffects(CardInstance card)
@@ -1170,8 +1214,77 @@ namespace KernelPanic.Combat
}
}
private void GenerateAurPackageCard(PackageEffectData effect)
{
if (handController == null || handController.RemainingRam <= 0)
{
return;
}
int maxRarity = Mathf.Clamp(effect.Threshold, 0, (int)Rarity.Legendary);
List<CardDefinition> pool = new();
HashSet<string> seenIds = new(StringComparer.OrdinalIgnoreCase);
IReadOnlyList<CardDefinition> source = generatedCardPool.Count > 0 ? generatedCardPool : runConfig?.StartingDeck;
if (source == null)
{
Log("AUR: TODO card-generation pool unavailable");
return;
}
for (int i = 0; i < source.Count; i++)
{
CardDefinition definition = source[i];
if (definition == null
|| definition.IsToken
|| definition.IsRunOnly
|| (int)definition.Rarity > maxRarity
|| definition.Language != Language.C && definition.Language != Language.Rust)
{
continue;
}
if (seenIds.Add(definition.Id))
{
pool.Add(definition);
}
}
if (pool.Count == 0)
{
Log("AUR: TODO card-generation pool unavailable");
return;
}
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))
{
string discount = effect.Amount > 0 ? " at -1c" : string.Empty;
Log($"AUR: generated {GetCardName(generated)}{discount}");
StateChanged?.Invoke();
}
}
private void HandlePackageDamageDealt(DamageDealtEvent payload)
{
if (payload.Target == playerState && playerState != null && playerState.ArchRollingReleaseRecoveredThisHit)
{
playerState.ArchRollingReleaseRecoveredThisHit = false;
string recovery = "rolling release: recovered at 1 Uptime";
if (playerState.ArchRollingReleaseShieldOnSave > 0 || playerState.ArchRollingReleaseCyclesOnSave > 0)
{
recovery += $", +{playerState.ArchRollingReleaseShieldOnSave} Shield, +{playerState.ArchRollingReleaseCyclesOnSave} Cycle";
}
Log(recovery);
StateChanged?.Invoke();
}
if (payload.Target != playerState
|| payload.UptimeDamage <= 0
|| packageTimeshiftTriggeredThisWave
@@ -1198,6 +1311,39 @@ namespace KernelPanic.Combat
StateChanged?.Invoke();
}
private void ResetArchBtwAtEndOfTurn()
{
if (!IsDistro("arch") || playerState == null || playerState.ArchBtwStacks <= 0 || ArchBtwPersistsThisWave())
{
return;
}
playerState.ArchBtwStacks = 0;
}
private void ResetArchWaveState()
{
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;
}
private bool ArchBtwPersistsThisWave()
{
return IsDistro("arch")
&& (runConfig.DistroVersion >= 5
|| HasOnDistroPackageEffect(PackageEffectKind.FirstCThisTurnDamageMultiplier, out PackageEffectData effect)
&& effect.PersistArchBtw);
}
private bool HasOnDistroPackageEffect(PackageEffectKind kind, out PackageEffectData effect)
{
effect = default;
@@ -1288,6 +1434,11 @@ namespace KernelPanic.Combat
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;
}
private bool IsDistro(string id)
+13
View File
@@ -32,7 +32,20 @@ namespace KernelPanic.Combat
public int FlatEffectBonus { get; set; }
public int JavaScriptFlatDamageBonus { get; set; }
public int DamageMultiplierPercent { get; set; } = 100;
public int CurrentCardDamageMultiplierPercent { get; set; } = 100;
public int IncomingAttackHalfCharges { get; set; }
public int ArchBtwStacks { get; set; }
public int ArchBtwDamagePerStack { get; set; } = 1;
public int ArchMakepkgBtwMultiplier { get; set; } = 2;
public int ArchRollingReleaseSavesRemaining { get; set; }
public int ArchRollingReleaseShieldOnSave { get; set; }
public int ArchRollingReleaseCyclesOnSave { get; set; }
public bool ArchRollingReleaseAvailableThisWave { get; set; }
public bool ArchRollingReleaseRecoveredThisHit { get; set; }
public bool HasLastPlayedCardLanguage { get; set; }
public Language LastPlayedCardLanguage { get; set; }
public bool HasPreviousPlayedCardLanguage { get; set; }
public Language PreviousPlayedCardLanguage { get; set; }
public IReadOnlyList<StatusInstance> Statuses => _statuses;
public IReadOnlyDictionary<StatusType, int> ActiveStatuses => BuildStatusSnapshot();
+28 -2
View File
@@ -70,6 +70,7 @@ namespace KernelPanic.Combat
amount = ApplyResistancesAndWeaknesses(amount, request);
int finalAmount = ApplyShieldAndUptime(amount, request, out int absorbedAmount, out int shieldDamage, out int uptimeDamage);
ApplyArchRollingReleaseSave(request);
GameEvents.RaiseDamageDealt(new DamageDealtEvent(request.Source, request.Target, finalAmount, request.Language, amount, absorbedAmount, wasCritical, shieldDamage, uptimeDamage));
bool defeated = false;
@@ -93,7 +94,8 @@ namespace KernelPanic.Combat
}
int multiplier = request.Source == null ? 100 : Mathf.Max(0, request.Source.DamageMultiplierPercent);
return Mathf.RoundToInt(amount * (multiplier / 100f));
int currentCardMultiplier = request.Source == null ? 100 : Mathf.Max(0, request.Source.CurrentCardDamageMultiplierPercent);
return Mathf.RoundToInt(amount * (multiplier / 100f) * (currentCardMultiplier / 100f));
}
private static int ApplyFlatAdditions(int amount, DamageRequest request)
@@ -103,6 +105,11 @@ namespace KernelPanic.Combat
amount += Mathf.Max(0, request.Source.JavaScriptFlatDamageBonus);
}
if (request.Source != null && (request.Language == Language.C || request.Language == Language.Rust))
{
amount += Mathf.Max(0, request.Source.ArchBtwStacks) * Mathf.Max(1, request.Source.ArchBtwDamagePerStack);
}
return amount;
}
@@ -114,10 +121,29 @@ namespace KernelPanic.Combat
return amount;
}
wasCritical = RandomRoll.RollRange(1, 100, new RollContext(request.Source)) <= 25;
int critChance = request.Language == Language.C ? 50 : 25;
wasCritical = RandomRoll.RollRange(1, 100, new RollContext(request.Source)) <= critChance;
return wasCritical ? Mathf.CeilToInt(amount * 1.5f) : amount;
}
private static void ApplyArchRollingReleaseSave(DamageRequest request)
{
CombatantState target = request.Target;
if (target == null || target.CurrentUptime > 0 || target.ArchRollingReleaseSavesRemaining <= 0)
{
return;
}
target.ArchRollingReleaseSavesRemaining--;
target.ArchRollingReleaseAvailableThisWave = target.ArchRollingReleaseSavesRemaining > 0;
target.ArchBtwStacks = 0;
target.CurrentUptime = 1;
target.Shield += Mathf.Max(0, target.ArchRollingReleaseShieldOnSave);
target.Cycles += Mathf.Max(0, target.ArchRollingReleaseCyclesOnSave);
target.IsDefeated = false;
target.ArchRollingReleaseRecoveredThisHit = true;
}
private static int ApplyResistancesAndWeaknesses(int amount, DamageRequest request)
{
// TODO: Enemy resistances/reductions are not authored yet. Mint V5 should re-resolve
+9 -3
View File
@@ -37,7 +37,9 @@ namespace KernelPanic.Data
FirstInterpreterQueueCardEachWaveShield = 21,
EveryNthTurnDraw = 22,
StartTurnNoDebuffShield = 23,
EveryNthCardEachWaveCycle = 24
EveryNthCardEachWaveCycle = 24,
FirstCThisTurnDamageMultiplier = 25,
TurnStartGenerateLanguageCard = 26
}
[Serializable]
@@ -49,8 +51,9 @@ namespace KernelPanic.Data
[SerializeField] private bool refundCycle;
[SerializeField] private bool cleanseDebuffs;
[SerializeField] private bool enableFedoraSecondCardPassive;
[SerializeField] private bool persistArchBtw;
public PackageEffectData(PackageEffectKind kind, int amount, int threshold, bool refundCycle, bool cleanseDebuffs, bool enableFedoraSecondCardPassive)
public PackageEffectData(PackageEffectKind kind, int amount, int threshold, bool refundCycle, bool cleanseDebuffs, bool enableFedoraSecondCardPassive, bool persistArchBtw = false)
{
this.kind = kind;
this.amount = amount;
@@ -58,6 +61,7 @@ namespace KernelPanic.Data
this.refundCycle = refundCycle;
this.cleanseDebuffs = cleanseDebuffs;
this.enableFedoraSecondCardPassive = enableFedoraSecondCardPassive;
this.persistArchBtw = persistArchBtw;
}
public PackageEffectKind Kind => kind;
@@ -66,6 +70,7 @@ namespace KernelPanic.Data
public bool RefundCycle => refundCycle;
public bool CleanseDebuffs => cleanseDebuffs;
public bool EnableFedoraSecondCardPassive => enableFedoraSecondCardPassive;
public bool PersistArchBtw => persistArchBtw;
}
/// <summary>
@@ -142,7 +147,7 @@ namespace KernelPanic.Data
}
int amount = effect.Amount + GetAmountPerLevel(effect.Kind, effect.Amount) * level;
return new PackageEffectData(effect.Kind, amount, effect.Threshold, effect.RefundCycle, effect.CleanseDebuffs, effect.EnableFedoraSecondCardPassive);
return new PackageEffectData(effect.Kind, amount, effect.Threshold, effect.RefundCycle, effect.CleanseDebuffs, effect.EnableFedoraSecondCardPassive, effect.PersistArchBtw);
}
private static int GetAmountPerLevel(PackageEffectKind kind, int baseAmount)
@@ -159,6 +164,7 @@ namespace KernelPanic.Data
PackageEffectKind.FirstCardEachWaveCostReduction => 1,
PackageEffectKind.FirstNativeCardEachWaveFlatDamage => 1,
PackageEffectKind.JavaScriptFlatDamage => 1,
PackageEffectKind.TurnStartGenerateLanguageCard => 0,
_ => 1
};
}
+11 -1
View File
@@ -9,7 +9,8 @@ namespace KernelPanic.Meta
{
["ubuntu"] = new[] { "20.04", "21.04", "22.04", "23.04", "24.04" },
["fedora"] = new[] { "38", "39", "40", "41", "42" },
["mint"] = new[] { "21", "21.2", "21.3", "22", "22.1" }
["mint"] = new[] { "21", "21.2", "21.3", "22", "22.1" },
["arch"] = new[] { "2024.01", "2024.06", "2024.12", "2025.06", "2025.12" }
};
public static string GetReleaseLabel(string unitId, int version)
@@ -55,6 +56,15 @@ namespace KernelPanic.Meta
5 => "timeshift snapshot restores to full uptime if lower",
_ => "--"
},
"arch" => safeVersion switch
{
1 => "btw stacks add C/Rust damage; once per wave, lethal uptime recovers to 1",
2 => "btw stacks add +2 C/Rust damage instead of +1",
3 => "makepkg scales at btw x3 instead of x2",
4 => "rolling-release recovery also grants 15 Shield and 2 Cycles",
5 => "btw persists for the wave and rolling release can save twice per wave",
_ => "--"
},
_ => safeVersion == 1 ? "base release" : $"V{safeVersion} release effect"
};
}
+80 -9
View File
@@ -309,6 +309,21 @@ namespace KernelPanic.Meta
};
}
public static bool IsBeginnerInstallMediaDistro(DistroDefinition distro)
{
return distro != null
&& (string.Equals(distro.Id, "ubuntu", StringComparison.OrdinalIgnoreCase)
|| string.Equals(distro.Id, "fedora", StringComparison.OrdinalIgnoreCase)
|| string.Equals(distro.Id, "mint", StringComparison.OrdinalIgnoreCase)
|| string.Equals(distro.Id, "arch", StringComparison.OrdinalIgnoreCase));
}
public static bool IsBeginnerStarterDistro(DistroDefinition distro)
{
return IsBeginnerInstallMediaDistro(distro)
&& !string.Equals(distro.Id, "arch", StringComparison.OrdinalIgnoreCase);
}
public static double GetFiveStarChance(GachaBannerState state)
{
return GetFiveStarChance(state == null ? 0 : state.fiveStarPityCounter);
@@ -353,7 +368,8 @@ namespace KernelPanic.Meta
if (pullNumber == 50)
{
_beginnerState.pityCounter = 0;
return GachaReward.FutureStandardFiveStar("future standard 5-star distro", "50-pull beginner capstone");
_beginnerState.fiveStarPityCounter = 0;
return RewardGuaranteedBeginnerFiveStar("50-pull beginner guarantee");
}
if (TryRollFiveStar(_beginnerState, out bool fiveStarPityTriggered))
@@ -372,8 +388,11 @@ namespace KernelPanic.Meta
_beginnerState.pityCounter = 0;
if (_bannerPool.Count > 0 && _random.NextDouble() < DistroChanceOnFeaturedTier)
{
DistroDefinition distro = _bannerPool[_random.Next(_bannerPool.Count)];
return GachaReward.DistroReward(distro, 4, pityTriggered, false);
DistroDefinition distro = FindRandomBannerPoolDistroByStars(4);
if (distro != null)
{
return GachaReward.DistroReward(distro, 4, pityTriggered, false);
}
}
return GachaReward.Package(4, "4-star package", pityTriggered, false);
@@ -413,8 +432,18 @@ namespace KernelPanic.Meta
return GachaReward.Package(5, "5-star package", pityTriggered, false);
}
DistroDefinition distro = _bannerPool.Count == 0 ? null : _bannerPool[_random.Next(_bannerPool.Count)];
return GachaReward.DistroReward(distro, 5, pityTriggered, false);
DistroDefinition distro = FindRandomBannerPoolDistroByStars(5);
return distro == null
? GachaReward.Package(5, "5-star package", pityTriggered, false)
: GachaReward.DistroReward(distro, 5, pityTriggered, false);
}
private GachaReward RewardGuaranteedBeginnerFiveStar(string reason)
{
DistroDefinition distro = FindRandomBannerPoolDistroByStars(5);
return distro == null
? GachaReward.Package(5, "5-star package", true, true)
: GachaReward.DistroReward(distro, 5, true, true, reason);
}
public bool ResolveFeaturedTierIsDistro()
@@ -446,16 +475,16 @@ namespace KernelPanic.Meta
DistroDefinition distro = null;
if (guaranteeIndex >= 0 && guaranteeIndex < _beginnerState.guaranteedDistroIds.Count)
{
distro = FindBannerPoolDistro(_beginnerState.guaranteedDistroIds[guaranteeIndex]);
distro = FindBannerPoolDistroByStars(_beginnerState.guaranteedDistroIds[guaranteeIndex], 4);
}
distro ??= _bannerPool.Count == 0 ? null : _bannerPool[Math.Min(guaranteeIndex, _bannerPool.Count - 1)];
distro ??= FindBannerPoolDistroByStars(guaranteeIndex, 4);
return distro == null
? GachaReward.Package(4, "4-star package", true, true)
: GachaReward.DistroReward(distro, 4, true, true, reason);
}
private DistroDefinition FindBannerPoolDistro(string id)
private DistroDefinition FindBannerPoolDistroByStars(string id, int stars)
{
if (string.IsNullOrWhiteSpace(id))
{
@@ -465,7 +494,7 @@ namespace KernelPanic.Meta
for (int i = 0; i < _bannerPool.Count; i++)
{
DistroDefinition distro = _bannerPool[i];
if (distro != null && string.Equals(distro.Id, id, StringComparison.OrdinalIgnoreCase))
if (distro != null && GetDistroStars(distro) == stars && string.Equals(distro.Id, id, StringComparison.OrdinalIgnoreCase))
{
return distro;
}
@@ -474,6 +503,48 @@ namespace KernelPanic.Meta
return null;
}
private DistroDefinition FindBannerPoolDistroByStars(int index, int stars)
{
int seen = 0;
for (int i = 0; i < _bannerPool.Count; i++)
{
DistroDefinition distro = _bannerPool[i];
if (distro == null || GetDistroStars(distro) != stars)
{
continue;
}
if (seen == index)
{
return distro;
}
seen++;
}
return null;
}
private DistroDefinition FindRandomBannerPoolDistroByStars(int stars)
{
List<DistroDefinition> eligible = new();
for (int i = 0; i < _bannerPool.Count; i++)
{
DistroDefinition distro = _bannerPool[i];
if (distro != null && GetDistroStars(distro) == stars)
{
eligible.Add(distro);
}
}
return eligible.Count == 0 ? null : eligible[_random.Next(eligible.Count)];
}
private static int GetDistroStars(DistroDefinition distro)
{
return distro != null && string.Equals(distro.Id, "arch", StringComparison.OrdinalIgnoreCase) ? 5 : 4;
}
private void SetCurrencyBalanceSilently(GachaCurrencyType currencyType, int amount)
{
_currencyBalances[currencyType] = Math.Max(0, amount);
+1 -1
View File
@@ -391,7 +391,7 @@ namespace KernelPanic.Run
continue;
}
if (card.IsRunOnly || card.Language == CurrentConfig.PrimaryLanguage || card.Language == CurrentConfig.SecondaryLanguage)
if (card.Language == CurrentConfig.PrimaryLanguage || card.Language == CurrentConfig.SecondaryLanguage)
{
pool.Add(card);
}
@@ -707,7 +707,7 @@ namespace KernelPanic.UI
private static int GetDistroStars(DistroDefinition unit)
{
return 4;
return unit != null && string.Equals(unit.Id, "arch", StringComparison.OrdinalIgnoreCase) ? 5 : 4;
}
private static Color GetLanguageColor(Language language)
@@ -2024,6 +2024,8 @@ namespace KernelPanic.UI
PackageEffectKind.JavaScriptFlatDamage => $"+{amount} JavaScript damage",
PackageEffectKind.WaveThresholdRestore => $"restore {amount} Uptime after wave {effect.Threshold}",
PackageEffectKind.DnfFedoraPassive => "Fedora passive can trigger twice",
PackageEffectKind.FirstCThisTurnDamageMultiplier => $"first C card each turn deals {amount}% damage",
PackageEffectKind.TurnStartGenerateLanguageCard => amount > 0 ? "turn start: generate C/Rust card at -1c" : "turn start: generate common C/Rust card",
_ => amount > 0 ? $"+{amount} package effect" : "package effect"
};
}
+1 -1
View File
@@ -607,7 +607,7 @@ namespace KernelPanic.UI
IReadOnlyList<string> ids = _gachaService.BeginnerState.guaranteedDistroIds;
string first = ids.Count > 0 ? ids[0] : "--";
string second = ids.Count > 1 ? ids[1] : "--";
return $"20={first}, 40={second}, 50=future standard 5-star";
return $"20={first}, 40={second}, 50=guaranteed 5-star";
}
private static VisualElement BuildInfoLine(string key, string value)
+6 -2
View File
@@ -517,12 +517,16 @@ namespace KernelPanic.UI
}
}
if (saveData.starterChosen && gacha.BannerPool.Count == 0)
if (saveData.starterChosen)
{
IReadOnlyList<DistroDefinition> distros = distroDatabase.AllDistros;
for (int i = 0; i < distros.Count; i++)
{
gacha.AddToBannerPool(distros[i]);
DistroDefinition distro = distros[i];
if (GachaService.IsBeginnerInstallMediaDistro(distro))
{
gacha.AddToBannerPool(distro);
}
}
}
}
+8 -4
View File
@@ -707,7 +707,7 @@ namespace KernelPanic.UI
if (_saveData.starterChosen)
{
AddAllFourStarDistrosToBeginnerBannerPool();
AddBeginnerInstallMediaDistrosToBannerPool();
}
if (_gachaService.BeginnerState.guaranteedDistroIds.Count == 0 && _saveData.bannerPoolIds.Count > 0)
@@ -730,7 +730,7 @@ namespace KernelPanic.UI
}
}
private void AddAllFourStarDistrosToBeginnerBannerPool()
private void AddBeginnerInstallMediaDistrosToBannerPool()
{
if (distroDatabase == null)
{
@@ -740,7 +740,11 @@ namespace KernelPanic.UI
IReadOnlyList<DistroDefinition> distros = distroDatabase.AllDistros;
for (int i = 0; i < distros.Count; i++)
{
_gachaService.AddToBannerPool(distros[i]);
DistroDefinition distro = distros[i];
if (GachaService.IsBeginnerInstallMediaDistro(distro))
{
_gachaService.AddToBannerPool(distro);
}
}
}
@@ -806,7 +810,7 @@ namespace KernelPanic.UI
private void HandleStarterConfirmed(DistroDefinition picked, IReadOnlyList<DistroDefinition> remaining)
{
_playerCollection.Add(picked, 1);
AddAllFourStarDistrosToBeginnerBannerPool();
AddBeginnerInstallMediaDistrosToBannerPool();
_gachaService.SetBeginnerGuaranteedDistros(remaining);
_saveData.starterChosen = true;
SaveCurrentState();
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using KernelPanic.Data;
using KernelPanic.Meta;
using UnityEngine;
using UnityEngine.UIElements;
@@ -196,7 +197,24 @@ namespace KernelPanic.UI
return false;
}
starters = allDistros;
List<DistroDefinition> starterDistros = new();
for (int i = 0; i < allDistros.Count; i++)
{
DistroDefinition distro = allDistros[i];
if (GachaService.IsBeginnerStarterDistro(distro))
{
starterDistros.Add(distro);
}
}
if (starterDistros.Count < StarterCount)
{
starters = Array.Empty<DistroDefinition>();
error = $"DistroDatabase needs at least {StarterCount} starter distros (found {starterDistros.Count})";
return false;
}
starters = starterDistros;
error = null;
return true;
}