Implement Arch and some additional changes
This commit is contained in:
@@ -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}"
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user