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
+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