Fix C, more enemies and less scaling for enemies.
This commit is contained in:
@@ -35,6 +35,7 @@ namespace KernelPanic.Combat
|
||||
targets = ShuffleTargets(targets, context.Source);
|
||||
}
|
||||
|
||||
bool dealtDamage = false;
|
||||
for (int i = 0; i < targets.Count; i++)
|
||||
{
|
||||
CombatantState target = targets[i];
|
||||
@@ -54,11 +55,14 @@ namespace KernelPanic.Combat
|
||||
? _trueDamage ? target.CurrentUptime : target.CurrentUptime + target.Shield
|
||||
: 0;
|
||||
DamageResult result = context.DamagePipeline.DealDamage(new DamageRequest(context.Source, target, amount, _language, _trueDamage, _canCrit));
|
||||
dealtDamage |= result.IncomingAmount > 0;
|
||||
if (rustOverkillToShield)
|
||||
{
|
||||
GrantRustOverkillShield(context, result, survivable);
|
||||
}
|
||||
}
|
||||
|
||||
SegfaultRecoil.TryApply(context, _language, dealtDamage);
|
||||
}
|
||||
|
||||
private static void GrantRustOverkillShield(CombatContext context, DamageResult result, int survivable)
|
||||
@@ -164,7 +168,8 @@ namespace KernelPanic.Combat
|
||||
int roll = RandomRoll.RollRange(1, 100, new RollContext(context.Source));
|
||||
int damageAmount = UpgradeMath.ScaleAmount(UpgradeMath.ApplySourceFlatBonus(_damageAmount, context), context.Card);
|
||||
int amount = roll <= _successPercent ? damageAmount : 0;
|
||||
context.DamagePipeline.DealDamage(new DamageRequest(context.Source, target, amount, _language, false, true));
|
||||
DamageResult result = context.DamagePipeline.DealDamage(new DamageRequest(context.Source, target, amount, _language, false, true));
|
||||
SegfaultRecoil.TryApply(context, _language, result.IncomingAmount > 0);
|
||||
context.CombatManager.ReportEffectResult(amount == 0 ? "typeof -> NaN (0)" : $"typeof -> {amount}");
|
||||
}
|
||||
}
|
||||
@@ -189,41 +194,6 @@ namespace KernelPanic.Combat
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class SegfaultDamageEffect : ICardEffect
|
||||
{
|
||||
private readonly int _amount;
|
||||
private readonly int _selfDamage;
|
||||
private readonly int _segfaultPercent;
|
||||
private readonly bool _trueDamage;
|
||||
private readonly bool _drawOnSegfault;
|
||||
|
||||
public SegfaultDamageEffect(int amount, int selfDamage, int segfaultPercent, bool trueDamage = false, bool drawOnSegfault = false)
|
||||
{
|
||||
_amount = amount;
|
||||
_selfDamage = selfDamage;
|
||||
_segfaultPercent = segfaultPercent;
|
||||
_trueDamage = trueDamage;
|
||||
_drawOnSegfault = drawOnSegfault;
|
||||
}
|
||||
|
||||
public void Execute(CombatContext context)
|
||||
{
|
||||
new DealDamageEffect(_amount, _amount, Language.C, _trueDamage).Execute(context);
|
||||
bool segfault = RandomRoll.RollRange(1, 100, new RollContext(context.Source)) <= _segfaultPercent;
|
||||
if (!segfault)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
context.DamagePipeline.DealDamage(new DamageRequest(context.Source, context.Source, _selfDamage, Language.C, false, false));
|
||||
context.CombatManager.ReportEffectResult($"segfault: took {_selfDamage}");
|
||||
if (_drawOnSegfault)
|
||||
{
|
||||
context.CombatManager.DrawCards(2, "free() recoil");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class PreviousLanguageDamageEffect : ICardEffect
|
||||
{
|
||||
private readonly int _baseAmount;
|
||||
@@ -687,6 +657,7 @@ namespace KernelPanic.Combat
|
||||
int amount = UpgradeMath.ScaleAmount(UpgradeMath.ApplySourceFlatBonus(_amount, context), context.Card);
|
||||
int survivable = target.CurrentUptime + target.Shield;
|
||||
DamageResult result = context.DamagePipeline.DealDamage(new DamageRequest(context.Source, target, amount, _language, false, true));
|
||||
SegfaultRecoil.TryApply(context, _language, result.IncomingAmount > 0);
|
||||
int overkill = UnityEngine.Mathf.Max(0, result.IncomingAmount - survivable);
|
||||
if (overkill > 0)
|
||||
{
|
||||
@@ -875,6 +846,58 @@ namespace KernelPanic.Combat
|
||||
}
|
||||
}
|
||||
|
||||
internal static class SegfaultRecoil
|
||||
{
|
||||
private const int DefaultPercent = 20;
|
||||
private const int DefaultUptimeDamage = 3;
|
||||
|
||||
public static void TryApply(CombatContext context, Language damageLanguage, bool dealtDamage)
|
||||
{
|
||||
if (!dealtDamage
|
||||
|| context == null
|
||||
|| context.SegfaultRecoilRolled
|
||||
|| context.Source == null
|
||||
|| context.Source.IsDefeated
|
||||
|| context.Card?.Definition?.Language != Language.C
|
||||
|| damageLanguage != Language.C)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
context.SegfaultRecoilRolled = true;
|
||||
GetProfile(context.Card, out int percent, out int uptimeDamage, out bool drawOnSegfault);
|
||||
if (RandomRoll.RollRange(1, 100, new RollContext(context.Source)) > percent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DamageResult result = context.DamagePipeline.DealDamage(new DamageRequest(null, context.Source, uptimeDamage, Language.C, true, false));
|
||||
context.CombatManager.ReportEffectResult($"segfault: uptime -{result.FinalAmount}");
|
||||
if (drawOnSegfault)
|
||||
{
|
||||
context.CombatManager.DrawCards(2, "free() recoil");
|
||||
}
|
||||
}
|
||||
|
||||
private static void GetProfile(CardInstance card, out int percent, out int uptimeDamage, out bool drawOnSegfault)
|
||||
{
|
||||
percent = DefaultPercent;
|
||||
uptimeDamage = DefaultUptimeDamage;
|
||||
drawOnSegfault = false;
|
||||
|
||||
switch (card?.Definition?.Id)
|
||||
{
|
||||
case "shop_c_free":
|
||||
drawOnSegfault = true;
|
||||
break;
|
||||
case "shop_c_kernel_exploit":
|
||||
percent = 50;
|
||||
uptimeDamage = 8;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class CardEffectFactory
|
||||
{
|
||||
public static IReadOnlyList<ICardEffect> CreateEffects(CardDefinition definition)
|
||||
@@ -899,7 +922,7 @@ namespace KernelPanic.Combat
|
||||
new DrawEffect(1)
|
||||
},
|
||||
"lang_rs_borrow" => One(new ShieldEffect(5)),
|
||||
"lang_c_malloc" => One(new SegfaultDamageEffect(8, 3, 20)),
|
||||
"lang_c_malloc" => One(new DealDamageEffect(8, 8, Language.C)),
|
||||
"lang_c_printf" => One(new DealDamageEffect(4, 4, Language.C)),
|
||||
"lang_c_pointer" => One(new PreviousLanguageDamageEffect(6, 10, Language.C)),
|
||||
"lang_java_public_static_main" => One(new DealDamageEffect(7, 7, Language.Java)),
|
||||
@@ -958,9 +981,9 @@ namespace KernelPanic.Combat
|
||||
"arch_makepkg" => One(new BtwScaledDamageEffect(4, 2, Language.C)),
|
||||
"arch_aur_helper" => One(new RandomCommonLanguageCardEffect()),
|
||||
"arch_minimal_install" => One(new MinimalInstallEffect()),
|
||||
"shop_c_free" => One(new SegfaultDamageEffect(12, 3, 20, drawOnSegfault: true)),
|
||||
"shop_c_free" => One(new DealDamageEffect(12, 12, Language.C)),
|
||||
"shop_c_sizeof" => One(new SizeofDamageEffect()),
|
||||
"shop_c_kernel_exploit" => One(new SegfaultDamageEffect(25, 8, 50, true)),
|
||||
"shop_c_kernel_exploit" => One(new DealDamageEffect(25, 25, Language.C, true)),
|
||||
_ => Todo("card effect is not authored")
|
||||
};
|
||||
}
|
||||
@@ -1030,9 +1053,9 @@ namespace KernelPanic.Combat
|
||||
"lang_rs_fn_main" => $"deal {UpgradeMath.ScaleAmount(6, card)}. overkill becomes shield.{marker}",
|
||||
"lang_rs_unwrap" => $"deal {UpgradeMath.ScaleAmount(9, card)}. overkill becomes shield. draw {UpgradeMath.ScaleAmount(1, card)}.{marker}",
|
||||
"lang_rs_borrow" => $"gain {UpgradeMath.ScaleShield(5, card)} shield.{marker}",
|
||||
"lang_c_malloc" => $"deal {UpgradeMath.ScaleAmount(8, card)}. 20%: segfault, take 3.{marker}",
|
||||
"lang_c_printf" => $"deal {UpgradeMath.ScaleAmount(4, card)}.{marker}",
|
||||
"lang_c_pointer" => $"deal {UpgradeMath.ScaleAmount(6, card)}; {UpgradeMath.ScaleAmount(10, card)} if the previous card was C.{marker}",
|
||||
"lang_c_malloc" => $"deal {UpgradeMath.ScaleAmount(8, card)}. 20%: segfault, uptime -3.{marker}",
|
||||
"lang_c_printf" => $"deal {UpgradeMath.ScaleAmount(4, card)}. 20%: segfault, uptime -3.{marker}",
|
||||
"lang_c_pointer" => $"deal {UpgradeMath.ScaleAmount(6, card)}; {UpgradeMath.ScaleAmount(10, card)} if the previous card was C. 20%: segfault, uptime -3.{marker}",
|
||||
"lang_java_public_static_main" => $"uses 2 RAM. deal {UpgradeMath.ScaleAmount(7, card)}. JIT: Java cards cost 1 less this combat each time you play a Java card; discount decays by 1 after each wave.{marker}",
|
||||
"lang_java_system_out_println" => $"uses 2 RAM. deal {UpgradeMath.ScaleAmount(5, card)}. JIT applies; discount decays by 1 after each wave.{marker}",
|
||||
"lang_java_new_object" => $"uses 2 RAM. deal {UpgradeMath.ScaleAmount(3, card)}. JIT applies; discount decays by 1 after each wave.{marker}",
|
||||
@@ -1057,14 +1080,14 @@ namespace KernelPanic.Combat
|
||||
"fedora_dnf_update" => $"deal {UpgradeMath.ScaleAmount(9, card)}. costs 1 less per Java card played this combat.{marker}",
|
||||
"fedora_rawhide" => $"draw {UpgradeMath.ScaleAmount(2, card)}. next card this turn gains bleeding edge.{marker}",
|
||||
"fedora_selinux" => $"next {UpgradeMath.ScaleAmount(2, card)} enemy attacks deal half damage.{marker}",
|
||||
"arch_pacman_syu" => $"deal {UpgradeMath.ScaleAmount(8, card)}. TODO: 20% break a random hand card.{marker}",
|
||||
"arch_pacman_syu" => $"deal {UpgradeMath.ScaleAmount(8, card)}. 20%: segfault, uptime -3. TODO: 20% break a random hand card.{marker}",
|
||||
"arch_consult_wiki" => $"fix a broken card and draw {UpgradeMath.ScaleAmount(1, card)}; if none, deal {UpgradeMath.ScaleAmount(6, card)}.{marker}",
|
||||
"arch_makepkg" => $"deal {UpgradeMath.ScaleAmount(4, card)} + btw x2; x3 at Arch v3+.{marker}",
|
||||
"arch_makepkg" => $"deal {UpgradeMath.ScaleAmount(4, card)} + btw x2; x3 at Arch v3+. 20%: segfault, uptime -3.{marker}",
|
||||
"arch_aur_helper" => $"add a random C/Rust common card to hand; it costs 0 this turn.{marker}",
|
||||
"arch_minimal_install" => $"exhaust up to 2 hand cards. per card: gain 3 Shield and 1 Cycle.{marker}",
|
||||
"shop_c_free" => $"deal {UpgradeMath.ScaleAmount(12, card)}. segfault recoil draws 2.{marker}",
|
||||
"shop_c_sizeof" => $"deal 3 x C cards in hand.{marker}",
|
||||
"shop_c_kernel_exploit" => $"deal {UpgradeMath.ScaleAmount(25, card)} ignoring Shield. 50%: take 8.{marker}",
|
||||
"shop_c_free" => $"deal {UpgradeMath.ScaleAmount(12, card)}. 20%: segfault, uptime -3 and draw 2.{marker}",
|
||||
"shop_c_sizeof" => $"deal 3 x C cards in hand. 20%: segfault, uptime -3.{marker}",
|
||||
"shop_c_kernel_exploit" => $"deal {UpgradeMath.ScaleAmount(25, card)} ignoring Shield. 50%: segfault, uptime -8.{marker}",
|
||||
_ => string.IsNullOrWhiteSpace(card.Definition.Description) ? "effect not authored." : $"{card.Definition.Description}{marker}"
|
||||
};
|
||||
}
|
||||
|
||||
@@ -38,5 +38,7 @@ namespace KernelPanic.Combat
|
||||
public DeckController DeckController { get; }
|
||||
public HandController HandController { get; }
|
||||
public IReadOnlyList<EnemyInstance> Enemies { get; }
|
||||
|
||||
internal bool SegfaultRecoilRolled { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,7 @@ namespace KernelPanic.Combat
|
||||
|
||||
public const int BaseEnemyUptimeMin = 5;
|
||||
public const int BaseEnemyUptimeMax = 10;
|
||||
public const int EnemyUptimeGrowthPerWave = 2;
|
||||
public const int EnemyUptimeGrowthPerWave = 1;
|
||||
|
||||
public const int BaseEnemyAttack = 3;
|
||||
public const int EnemyAttackGrowthEveryWaves = 2;
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace KernelPanic.Combat
|
||||
public int Cycles { get; set; }
|
||||
public int MaxCycles { get; set; }
|
||||
public bool IsDefeated { get; set; }
|
||||
public bool forceMaxRolls { get; set; }
|
||||
public bool ForceMaxRolls { get; set; }
|
||||
public bool IgnoreDamageMultipliers { get; set; }
|
||||
public bool AllowFlatDamageBuffs { get; set; }
|
||||
public int FlatEffectBonus { get; set; }
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace KernelPanic.Combat
|
||||
private static int ApplyCrit(int amount, DamageRequest request, out bool wasCritical)
|
||||
{
|
||||
wasCritical = false;
|
||||
if (!request.CanCrit || amount <= 0 || request.Source != null && request.Source.forceMaxRolls)
|
||||
if (!request.CanCrit || amount <= 0 || request.Source != null && request.Source.ForceMaxRolls)
|
||||
{
|
||||
return amount;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using KernelPanic.Core;
|
||||
using UnityEngine;
|
||||
|
||||
namespace KernelPanic.Combat
|
||||
{
|
||||
[Flags]
|
||||
public enum EnemyBehaviorFlags
|
||||
{
|
||||
None = 0,
|
||||
Revive = 1 << 0,
|
||||
Split = 1 << 1,
|
||||
Grow = 1 << 2,
|
||||
ObfuscateIntent = 1 << 3,
|
||||
DefendAllies = 1 << 4,
|
||||
Countdown = 1 << 5
|
||||
}
|
||||
|
||||
public sealed class EnemyArchetypeDescriptor
|
||||
{
|
||||
public EnemyArchetypeDescriptor(
|
||||
string id,
|
||||
string displayName,
|
||||
int baseUptimeMin,
|
||||
int baseUptimeMax,
|
||||
EnemyBehaviorFlags behaviorFlags,
|
||||
IReadOnlyList<EnemyIntent> intentPool)
|
||||
{
|
||||
Id = string.IsNullOrWhiteSpace(id) ? "unknown_process" : id;
|
||||
DisplayName = string.IsNullOrWhiteSpace(displayName) ? Id : displayName;
|
||||
BaseUptimeMin = Mathf.Max(1, baseUptimeMin);
|
||||
BaseUptimeMax = Mathf.Max(BaseUptimeMin, baseUptimeMax);
|
||||
BehaviorFlags = behaviorFlags;
|
||||
IntentPool = intentPool ?? Array.Empty<EnemyIntent>();
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
public string DisplayName { get; }
|
||||
public int BaseUptimeMin { get; }
|
||||
public int BaseUptimeMax { get; }
|
||||
public EnemyBehaviorFlags BehaviorFlags { get; }
|
||||
public IReadOnlyList<EnemyIntent> IntentPool { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runtime archetype construction site. Future EnemyDefinition assets should replace this table
|
||||
/// by providing the same descriptor data: id, display name, base Uptime range, intents, and flags.
|
||||
/// </summary>
|
||||
public static class EnemyArchetypeCatalog
|
||||
{
|
||||
public const int ZombieReapThresholdPercent = 30;
|
||||
public const int MemoryLeakAttackGrowthPerTurn = 1;
|
||||
public const int ForkBombTotalCap = 5;
|
||||
public const int CronInitialCountdown = 2;
|
||||
|
||||
private static readonly EnemyArchetypeDescriptor ZombieProcess = new(
|
||||
"zombie_process",
|
||||
"Zombie Process",
|
||||
6,
|
||||
10,
|
||||
EnemyBehaviorFlags.Revive,
|
||||
new[]
|
||||
{
|
||||
new EnemyIntent(EnemyIntentKind.Attack, 3, 4, Language.C, "attack", "!")
|
||||
});
|
||||
|
||||
private static readonly EnemyArchetypeDescriptor MemoryLeak = new(
|
||||
"memory_leak",
|
||||
"Memory Leak",
|
||||
4,
|
||||
7,
|
||||
EnemyBehaviorFlags.Grow,
|
||||
new[]
|
||||
{
|
||||
new EnemyIntent(EnemyIntentKind.StatusAttack, 1, 1, Language.C, "leak", "!", false, StatusType.MemoryLeak, 1, CombatTuning.EnemyStatusDuration)
|
||||
});
|
||||
|
||||
private static readonly EnemyArchetypeDescriptor ForkBomb = new(
|
||||
"fork_bomb",
|
||||
"Fork Bomb",
|
||||
3,
|
||||
5,
|
||||
EnemyBehaviorFlags.Split,
|
||||
new[]
|
||||
{
|
||||
new EnemyIntent(EnemyIntentKind.Special, 0, 0, Language.C, "fork", ":", false, StatusType.MemoryLeak, 0, -1, "split")
|
||||
});
|
||||
|
||||
private static readonly EnemyArchetypeDescriptor Daemon = new(
|
||||
"daemon",
|
||||
"Daemon",
|
||||
10,
|
||||
14,
|
||||
EnemyBehaviorFlags.ObfuscateIntent,
|
||||
new[]
|
||||
{
|
||||
new EnemyIntent(EnemyIntentKind.Attack, 6, 8, Language.C, "daemon hit", "!")
|
||||
});
|
||||
|
||||
private static readonly EnemyArchetypeDescriptor Defender = new(
|
||||
"firewalld",
|
||||
"firewalld",
|
||||
5,
|
||||
8,
|
||||
EnemyBehaviorFlags.DefendAllies,
|
||||
new[]
|
||||
{
|
||||
new EnemyIntent(EnemyIntentKind.Defend, 3, 5, Language.C, "shield adjacent", "#"),
|
||||
new EnemyIntent(EnemyIntentKind.Defend, 3, 5, Language.C, "shield adjacent", "#"),
|
||||
new EnemyIntent(EnemyIntentKind.Attack, 1, 2, Language.C, "poke", "!")
|
||||
});
|
||||
|
||||
private static readonly EnemyArchetypeDescriptor CronJob = new(
|
||||
"cron_job",
|
||||
"Cron Job",
|
||||
6,
|
||||
9,
|
||||
EnemyBehaviorFlags.Countdown,
|
||||
new[]
|
||||
{
|
||||
new EnemyIntent(EnemyIntentKind.Attack, 10, 12, Language.C, "cron fire", "@")
|
||||
});
|
||||
|
||||
public static EnemyArchetypeDescriptor Get(string id)
|
||||
{
|
||||
return id switch
|
||||
{
|
||||
"memory_leak" => MemoryLeak,
|
||||
"fork_bomb" => ForkBomb,
|
||||
"daemon" => Daemon,
|
||||
"firewalld" => Defender,
|
||||
"cron_job" => CronJob,
|
||||
_ => ZombieProcess
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,24 +10,57 @@ namespace KernelPanic.Combat
|
||||
private readonly List<EnemyIntent> _intentPool = new();
|
||||
|
||||
public EnemyInstance(string name, int maxUptime, IEnumerable<EnemyIntent> intentPool)
|
||||
: this(new EnemyArchetypeDescriptor(name, name, maxUptime, maxUptime, EnemyBehaviorFlags.None, new List<EnemyIntent>(intentPool ?? System.Array.Empty<EnemyIntent>())), maxUptime)
|
||||
{
|
||||
Name = string.IsNullOrWhiteSpace(name) ? "unknown_process" : name;
|
||||
}
|
||||
|
||||
public EnemyInstance(EnemyArchetypeDescriptor archetype, int maxUptime)
|
||||
{
|
||||
Archetype = archetype ?? EnemyArchetypeCatalog.Get("zombie_process");
|
||||
Name = Archetype.DisplayName;
|
||||
State = new CombatantState(maxUptime, 0, 0);
|
||||
if (intentPool != null)
|
||||
if (Archetype.IntentPool != null)
|
||||
{
|
||||
_intentPool.AddRange(intentPool);
|
||||
_intentPool.AddRange(Archetype.IntentPool);
|
||||
}
|
||||
|
||||
CountdownRemaining = HasBehavior(EnemyBehaviorFlags.Countdown) ? EnemyArchetypeCatalog.CronInitialCountdown : 0;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
public EnemyArchetypeDescriptor Archetype { get; }
|
||||
public string ArchetypeId => Archetype.Id;
|
||||
public int CurrentUptime => State.CurrentUptime;
|
||||
public int MaxUptime => State.MaxUptime;
|
||||
public CombatantState State { get; }
|
||||
public IReadOnlyList<EnemyIntent> IntentPool => _intentPool;
|
||||
public EnemyIntent CurrentIntent { get; private set; }
|
||||
public int TurnsAlive { get; private set; }
|
||||
public int CountdownRemaining { get; private set; }
|
||||
public bool IntentRevealed { get; private set; }
|
||||
public bool HasRevived { get; private set; }
|
||||
public bool PendingRevive { get; private set; }
|
||||
public bool Reaped { get; private set; }
|
||||
public bool LethalHitThisTurn { get; private set; }
|
||||
public bool HasPendingMarker => PendingRevive || HasRevived;
|
||||
public EnemyIntent DisplayIntent => BuildDisplayIntent();
|
||||
|
||||
public void PickNextIntent()
|
||||
{
|
||||
if (PendingRevive)
|
||||
{
|
||||
CurrentIntent = new EnemyIntent(EnemyIntentKind.Special, 0, 0, KernelPanic.Core.Language.C, "reviving", "~", false, KernelPanic.Core.StatusType.MemoryLeak, 0, -1, "next");
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasBehavior(EnemyBehaviorFlags.Countdown))
|
||||
{
|
||||
CurrentIntent = CountdownRemaining > 0
|
||||
? new EnemyIntent(EnemyIntentKind.Special, CountdownRemaining, CountdownRemaining, KernelPanic.Core.Language.C, "countdown", "@")
|
||||
: _intentPool.Count > 0 ? _intentPool[0] : default;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_intentPool.Count == 0)
|
||||
{
|
||||
CurrentIntent = default;
|
||||
@@ -36,6 +69,92 @@ namespace KernelPanic.Combat
|
||||
|
||||
int index = RandomRoll.RollRange(0, _intentPool.Count - 1, new RollContext(State));
|
||||
CurrentIntent = _intentPool[index];
|
||||
if (HasBehavior(EnemyBehaviorFlags.Grow))
|
||||
{
|
||||
int growth = TurnsAlive * EnemyArchetypeCatalog.MemoryLeakAttackGrowthPerTurn;
|
||||
CurrentIntent = CurrentIntent.WithValues(CurrentIntent.MinValue + growth, CurrentIntent.MaxValue + growth, "growing leak");
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasBehavior(EnemyBehaviorFlags behavior)
|
||||
{
|
||||
return (Archetype.BehaviorFlags & behavior) != 0;
|
||||
}
|
||||
|
||||
public void MarkDamaged()
|
||||
{
|
||||
if (HasBehavior(EnemyBehaviorFlags.ObfuscateIntent))
|
||||
{
|
||||
IntentRevealed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void MarkTurnSurvived()
|
||||
{
|
||||
TurnsAlive++;
|
||||
}
|
||||
|
||||
public void ResetTurnLethalMarker()
|
||||
{
|
||||
LethalHitThisTurn = false;
|
||||
}
|
||||
|
||||
public void MarkLethalHit()
|
||||
{
|
||||
LethalHitThisTurn = true;
|
||||
}
|
||||
|
||||
public void MarkPendingRevive()
|
||||
{
|
||||
PendingRevive = true;
|
||||
State.IsDefeated = false;
|
||||
State.CurrentUptime = 0;
|
||||
State.Shield = 0;
|
||||
CurrentIntent = new EnemyIntent(EnemyIntentKind.Special, 0, 0, KernelPanic.Core.Language.C, "reviving", "~", false, KernelPanic.Core.StatusType.MemoryLeak, 0, -1, "next");
|
||||
}
|
||||
|
||||
public void ReviveAtHalfUptime()
|
||||
{
|
||||
PendingRevive = false;
|
||||
HasRevived = true;
|
||||
State.IsDefeated = false;
|
||||
State.CurrentUptime = UnityEngine.Mathf.Max(1, UnityEngine.Mathf.CeilToInt(State.MaxUptime * 0.5f));
|
||||
PickNextIntent();
|
||||
}
|
||||
|
||||
public void MarkReaped()
|
||||
{
|
||||
PendingRevive = false;
|
||||
Reaped = true;
|
||||
State.IsDefeated = true;
|
||||
State.CurrentUptime = 0;
|
||||
}
|
||||
|
||||
public void AdvanceCountdownAfterAction()
|
||||
{
|
||||
if (!HasBehavior(EnemyBehaviorFlags.Countdown))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CountdownRemaining = CurrentIntent.Kind == EnemyIntentKind.Attack
|
||||
? EnemyArchetypeCatalog.CronInitialCountdown
|
||||
: UnityEngine.Mathf.Max(0, CountdownRemaining - 1);
|
||||
}
|
||||
|
||||
private EnemyIntent BuildDisplayIntent()
|
||||
{
|
||||
if (PendingRevive)
|
||||
{
|
||||
return new EnemyIntent(EnemyIntentKind.Special, 0, 0, KernelPanic.Core.Language.C, "reviving", "~", false, KernelPanic.Core.StatusType.MemoryLeak, 0, -1, "next");
|
||||
}
|
||||
|
||||
if (HasBehavior(EnemyBehaviorFlags.ObfuscateIntent) && !IntentRevealed)
|
||||
{
|
||||
return new EnemyIntent(EnemyIntentKind.Special, 0, 0, KernelPanic.Core.Language.C, "unknown", "?", false, KernelPanic.Core.StatusType.MemoryLeak, 0, -1, "?");
|
||||
}
|
||||
|
||||
return CurrentIntent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,8 @@ namespace KernelPanic.Combat
|
||||
bool trueDamage = false,
|
||||
StatusType statusType = StatusType.MemoryLeak,
|
||||
int statusStacks = 0,
|
||||
int statusDuration = -1)
|
||||
int statusDuration = -1,
|
||||
string valueTextOverride = null)
|
||||
{
|
||||
Kind = kind;
|
||||
MinValue = minValue;
|
||||
@@ -38,6 +39,7 @@ namespace KernelPanic.Combat
|
||||
StatusType = statusType;
|
||||
StatusStacks = statusStacks;
|
||||
StatusDuration = statusDuration;
|
||||
ValueTextOverride = valueTextOverride;
|
||||
}
|
||||
|
||||
public EnemyIntentKind Kind { get; }
|
||||
@@ -50,8 +52,27 @@ namespace KernelPanic.Combat
|
||||
public StatusType StatusType { get; }
|
||||
public int StatusStacks { get; }
|
||||
public int StatusDuration { get; }
|
||||
public string ValueTextOverride { get; }
|
||||
|
||||
public string ValueText => MinValue == MaxValue ? MinValue.ToString() : $"{MinValue}-{MaxValue}";
|
||||
public string ValueText => string.IsNullOrWhiteSpace(ValueTextOverride)
|
||||
? MinValue == MaxValue ? MinValue.ToString() : $"{MinValue}-{MaxValue}"
|
||||
: ValueTextOverride;
|
||||
public string DisplayText => $"{IconKey} {DisplayLabel} {ValueText}";
|
||||
|
||||
public EnemyIntent WithValues(int minValue, int maxValue, string displayLabel = null, string iconKey = null, string valueTextOverride = null)
|
||||
{
|
||||
return new EnemyIntent(
|
||||
Kind,
|
||||
minValue,
|
||||
maxValue,
|
||||
DamageType,
|
||||
displayLabel ?? DisplayLabel,
|
||||
iconKey ?? IconKey,
|
||||
TrueDamage,
|
||||
StatusType,
|
||||
StatusStacks,
|
||||
StatusDuration,
|
||||
valueTextOverride);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace KernelPanic.Combat
|
||||
}
|
||||
|
||||
// Mint's passive will flip this bool; no passive sets it in this scaffold.
|
||||
if (context.Source != null && context.Source.forceMaxRolls)
|
||||
if (context.Source != null && context.Source.ForceMaxRolls)
|
||||
{
|
||||
return max;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user