buff fedora, add elite enemies and add impact to attack
This commit is contained in:
@@ -1062,6 +1062,23 @@ namespace KernelPanic.Combat
|
||||
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 CanReceiveBleedingEdgeDamageBonus(CardDefinition definition)
|
||||
{
|
||||
if (definition == null || definition.ResolutionTrack != ResolutionTrack.Native)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
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_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_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 "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)
|
||||
{
|
||||
return definition?.Id is "lang_py_for_loop";
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace KernelPanic.Combat
|
||||
private bool _skipNextAllocateDraw;
|
||||
private bool _ubuntuEmptyHandRefillUsed;
|
||||
private int _fedoraCardsDiscountedThisTurn;
|
||||
private int _fedoraCrashChance = 10;
|
||||
private int _fedoraCrashChance = CombatTuning.FedoraBleedingEdgeBaseCrashChance;
|
||||
private int _cardsPlayedThisTurn;
|
||||
private int _cardsPlayedThisWave;
|
||||
private int _cCardsPlayedThisTurn;
|
||||
@@ -54,6 +54,7 @@ namespace KernelPanic.Combat
|
||||
private int _queuedRepeatCharges;
|
||||
private int _nextTurnCycleBonus;
|
||||
private int _nextRacePairId = 1;
|
||||
private int _activeKernelRamPenalty;
|
||||
private Coroutine _phaseCoroutine;
|
||||
|
||||
public TurnPhase CurrentPhase => currentPhase;
|
||||
@@ -71,6 +72,7 @@ namespace KernelPanic.Combat
|
||||
public bool AwaitingWaveContinue => _awaitingWaveContinue;
|
||||
public bool RunLost => _runLost;
|
||||
public int CurrentWaveNumber => _runManager == null ? 1 : _runManager.CurrentWaveNumber;
|
||||
public int ActiveKernelRamPenalty => _activeKernelRamPenalty;
|
||||
|
||||
public event Action StateChanged;
|
||||
public event Action<string> CombatLog;
|
||||
@@ -194,11 +196,11 @@ namespace KernelPanic.Combat
|
||||
|
||||
if (card.IsLocked)
|
||||
{
|
||||
Log($"{GetCardName(card)} is locked");
|
||||
Log($"{GetCardName(card)} is locked; pay {EnemyArchetypeCatalog.DrmUnlockCycleCost} Cycle to unlock");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool fedoraBonus = CanApplyFedoraBonus();
|
||||
bool fedoraBonus = CanApplyFedoraBonus(card);
|
||||
int cost = GetEffectiveCardCost(card);
|
||||
if (fedoraBonus)
|
||||
{
|
||||
@@ -248,11 +250,13 @@ namespace KernelPanic.Combat
|
||||
}
|
||||
|
||||
GameEvents.RaiseCardPlayed(new CardPlayedEvent(card, track));
|
||||
ApplyTelemetryCardPlayed();
|
||||
LogPlayIntent(card);
|
||||
card.SetTargetSnapshot(CaptureTargets(card));
|
||||
|
||||
if (fedoraBonus)
|
||||
{
|
||||
bool firstFedoraBonusThisTurn = _fedoraCardsDiscountedThisTurn == 0;
|
||||
_fedoraCardsDiscountedThisTurn++;
|
||||
bool rawhideChargeUsed = _rawhideBonusCharges > 0;
|
||||
if (rawhideChargeUsed)
|
||||
@@ -262,7 +266,7 @@ namespace KernelPanic.Combat
|
||||
|
||||
if (!IsCrashImmune(card) && RandomRoll.RollRange(1, 100, new RollContext(_playerState)) <= _fedoraCrashChance)
|
||||
{
|
||||
_fedoraCrashChance = 10;
|
||||
_fedoraCrashChance = CombatTuning.FedoraBleedingEdgeBaseCrashChance;
|
||||
if (IsDistro("fedora") && _runConfig.DistroVersion >= 4)
|
||||
{
|
||||
_playerState.Cycles += 1;
|
||||
@@ -276,10 +280,12 @@ namespace KernelPanic.Combat
|
||||
return true;
|
||||
}
|
||||
|
||||
_fedoraCrashChance = Mathf.Min(90, _fedoraCrashChance + 10);
|
||||
_fedoraCrashChance = Mathf.Min(
|
||||
CombatTuning.FedoraBleedingEdgeMaxCrashChance,
|
||||
_fedoraCrashChance + CombatTuning.FedoraBleedingEdgeCrashChanceStep);
|
||||
card.MarkFedoraNonCrashBonus();
|
||||
_playerState.DamageMultiplierPercent = _runConfig.DistroVersion >= 2 ? 175 : 150;
|
||||
ApplyFedoraGrowth(card, rawhideChargeUsed);
|
||||
ApplyFedoraGrowth(card, rawhideChargeUsed, firstFedoraBonusThisTurn);
|
||||
}
|
||||
|
||||
if (card.Definition.Language == Language.C && _cCardsPlayedThisTurn == 0 && HasPackageEffect(PackageEffectKind.FirstCThisTurnDamageMultiplier, out PackageEffectData cPackageEffect))
|
||||
@@ -372,6 +378,27 @@ namespace KernelPanic.Combat
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryUnlockCard(CardInstance card)
|
||||
{
|
||||
if (currentPhase != TurnPhase.Execute || card == null || !card.IsLocked || _playerState == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int cost = EnemyArchetypeCatalog.DrmUnlockCycleCost;
|
||||
if (_playerState.Cycles < cost)
|
||||
{
|
||||
Log($"not enough cycles to unlock {GetCardName(card)}");
|
||||
return false;
|
||||
}
|
||||
|
||||
_playerState.Cycles -= cost;
|
||||
card.IsLocked = false;
|
||||
Log($"unlocked {GetCardName(card)}");
|
||||
StateChanged?.Invoke();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ReportEffectResult(string message)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(message))
|
||||
@@ -626,7 +653,7 @@ namespace KernelPanic.Combat
|
||||
_awaitingWaveContinue = false;
|
||||
_loggedEffectTodos.Clear();
|
||||
_ubuntuEmptyHandRefillUsed = false;
|
||||
_fedoraCrashChance = 10;
|
||||
_fedoraCrashChance = CombatTuning.FedoraBleedingEdgeBaseCrashChance;
|
||||
_fedoraCardsDiscountedThisTurn = 0;
|
||||
_cardsPlayedThisTurn = 0;
|
||||
_cardsPlayedThisWave = 0;
|
||||
@@ -700,6 +727,7 @@ namespace KernelPanic.Combat
|
||||
_interpreterQueue.Clear();
|
||||
_enemies.Clear();
|
||||
SpawnStructuralEnemies();
|
||||
RecalculateKernelRamPressure();
|
||||
PickEnemyIntents();
|
||||
StateChanged?.Invoke();
|
||||
yield return DrawOpeningHandSequenced();
|
||||
@@ -715,6 +743,7 @@ namespace KernelPanic.Combat
|
||||
}
|
||||
|
||||
RevivePendingEnemies();
|
||||
UnlockHandCards("license check expired");
|
||||
_playerState.Cycles = _playerState.MaxCycles;
|
||||
_fedoraCardsDiscountedThisTurn = 0;
|
||||
_cardsPlayedThisTurn = 0;
|
||||
@@ -856,7 +885,7 @@ namespace KernelPanic.Combat
|
||||
|
||||
private IEnumerator DrawOpeningHandSequenced()
|
||||
{
|
||||
int openingDraw = Mathf.Min(CombatTuning.OpeningHandSize, _playerState.Ram);
|
||||
int openingDraw = Mathf.Min(CombatTuning.OpeningHandSize, _handController == null ? _playerState.Ram : _handController.RamCapacity);
|
||||
yield return DrawCardsToHandSequenced(openingDraw, "opening hand");
|
||||
}
|
||||
|
||||
@@ -954,9 +983,87 @@ namespace KernelPanic.Combat
|
||||
StartCoroutine(DrawCardsToHandSequenced(_playerState.Ram, "ubuntu 24.04 refill"));
|
||||
}
|
||||
|
||||
private bool CanApplyFedoraBonus()
|
||||
private bool CanApplyFedoraBonus(CardInstance card)
|
||||
{
|
||||
return CanApplyFedoraPassiveBonus() || _rawhideBonusCharges > 0;
|
||||
return CardEffectFactory.CanReceiveBleedingEdgeDamageBonus(card?.Definition)
|
||||
&& CardCanBenefitFromBleedingEdgeNow(card)
|
||||
&& (CanApplyFedoraPassiveBonus() || _rawhideBonusCharges > 0);
|
||||
}
|
||||
|
||||
private bool CardCanBenefitFromBleedingEdgeNow(CardInstance card)
|
||||
{
|
||||
string id = card?.Definition?.Id;
|
||||
if (string.IsNullOrWhiteSpace(id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (id == "arch_consult_wiki")
|
||||
{
|
||||
return _handController == null || !HasBrokenHandCard();
|
||||
}
|
||||
|
||||
if (id == "fedora_dnf_autoremove")
|
||||
{
|
||||
return HasJunkHandCard(card);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool HasBrokenHandCard()
|
||||
{
|
||||
if (_handController == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < _handController.Cards.Count; i++)
|
||||
{
|
||||
if (_handController.Cards[i]?.IsBroken == true)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool HasJunkHandCard(CardInstance playedCard)
|
||||
{
|
||||
if (_handController == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < _handController.Cards.Count; i++)
|
||||
{
|
||||
CardInstance card = _handController.Cards[i];
|
||||
if (card != playedCard && IsJunkForDnfAutoremove(card))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsJunkForDnfAutoremove(CardInstance card)
|
||||
{
|
||||
if (card == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string id = card.Definition?.Id ?? string.Empty;
|
||||
string name = card.Definition?.DisplayName ?? string.Empty;
|
||||
return card.Definition != null && card.Definition.IsToken
|
||||
|| card.IsBroken
|
||||
|| id.IndexOf("junk", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| id.IndexOf("nop", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| name.IndexOf("junk", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| name.IndexOf("NOP", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| name.IndexOf("broken", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
}
|
||||
|
||||
private void DecayJavaWarmupForNextWave()
|
||||
@@ -981,9 +1088,9 @@ namespace KernelPanic.Combat
|
||||
return _fedoraCardsDiscountedThisTurn < limit;
|
||||
}
|
||||
|
||||
private void ApplyFedoraGrowth(CardInstance card, bool rawhideChargeUsed)
|
||||
private void ApplyFedoraGrowth(CardInstance card, bool rawhideChargeUsed, bool firstFedoraBonusThisTurn)
|
||||
{
|
||||
if (!IsDistro("fedora") || _runConfig.DistroVersion < 3 || card == null || !card.WasFirstCardThisTurn)
|
||||
if (!IsDistro("fedora") || _runConfig.DistroVersion < 3 || card == null || !firstFedoraBonusThisTurn)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -1739,6 +1846,7 @@ namespace KernelPanic.Combat
|
||||
_runManager?.AddBits(CombatTuning.BitsPerKill);
|
||||
HandleEnemyDeath(enemy);
|
||||
_enemies.RemoveAt(i);
|
||||
RecalculateKernelRamPressure();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1803,6 +1911,87 @@ namespace KernelPanic.Combat
|
||||
return candidates[index];
|
||||
}
|
||||
|
||||
private void UnlockHandCards(string reason)
|
||||
{
|
||||
if (_handController == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int unlocked = 0;
|
||||
for (int i = 0; i < _handController.Cards.Count; i++)
|
||||
{
|
||||
CardInstance card = _handController.Cards[i];
|
||||
if (card != null && card.IsLocked)
|
||||
{
|
||||
card.IsLocked = false;
|
||||
unlocked++;
|
||||
}
|
||||
}
|
||||
|
||||
if (unlocked > 0)
|
||||
{
|
||||
Log($"{reason}: unlocked {unlocked} card(s)");
|
||||
}
|
||||
}
|
||||
|
||||
private void RecalculateKernelRamPressure()
|
||||
{
|
||||
if (_handController == null || _playerState == null || _runManager == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int penalty = 0;
|
||||
for (int i = 0; i < _enemies.Count; i++)
|
||||
{
|
||||
EnemyInstance enemy = _enemies[i];
|
||||
if (enemy.HasBehavior(EnemyBehaviorFlags.RamPressure) && !enemy.State.IsDefeated)
|
||||
{
|
||||
penalty += EnemyArchetypeCatalog.KernelPanicRamPenalty;
|
||||
}
|
||||
}
|
||||
|
||||
_activeKernelRamPenalty = penalty;
|
||||
int baseRam = _runManager.EffectiveRam();
|
||||
int effectiveRam = Mathf.Max(1, baseRam - penalty);
|
||||
_playerState.Ram = effectiveRam;
|
||||
_handController.SetRamCapacity(effectiveRam);
|
||||
}
|
||||
|
||||
private void ApplyTelemetryCardPlayed()
|
||||
{
|
||||
int collectors = 0;
|
||||
for (int i = 0; i < _enemies.Count; i++)
|
||||
{
|
||||
EnemyInstance enemy = _enemies[i];
|
||||
if (enemy.HasBehavior(EnemyBehaviorFlags.TelemetryCollector) && !enemy.State.IsDefeated)
|
||||
{
|
||||
enemy.AddTelemetryStack();
|
||||
collectors++;
|
||||
}
|
||||
}
|
||||
|
||||
if (collectors > 0)
|
||||
{
|
||||
Log($"telemetry collector profiled card: +{EnemyArchetypeCatalog.TelemetryDamageGrowthPerCard} damage");
|
||||
StateChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private void LockRandomHandCard(EnemyInstance source)
|
||||
{
|
||||
CardInstance card = RandomHandCard(cardInstance => cardInstance != null && !cardInstance.IsBroken && !cardInstance.IsLocked);
|
||||
if (card == null)
|
||||
{
|
||||
Log("license check: no unlockable hand card");
|
||||
return;
|
||||
}
|
||||
|
||||
card.IsLocked = true;
|
||||
Log($"{source.Name} locked {GetCardName(card)}");
|
||||
}
|
||||
|
||||
private void BreakRacePair(EnemyInstance defeated)
|
||||
{
|
||||
EnemyInstance survivor = RacePartner(defeated);
|
||||
@@ -1829,7 +2018,8 @@ namespace KernelPanic.Combat
|
||||
int count = Mathf.Clamp(CombatTuning.BaseEnemiesPerWave + countGrowth, CombatTuning.BaseEnemiesPerWave, CombatTuning.MaxEnemiesPerWave);
|
||||
int waveUptimeBonus = Mathf.Max(0, wave - 1) * CombatTuning.EnemyUptimeGrowthPerWave;
|
||||
bool rootkitSpawned = false;
|
||||
for (int i = 0; i < count; i++)
|
||||
bool eliteSpawned = TrySpawnEliteEncounter(wave, waveUptimeBonus);
|
||||
for (int i = eliteSpawned ? 1 : 0; i < count; i++)
|
||||
{
|
||||
string archetypeId = CreatePlaceholderArchetypeId(wave, i);
|
||||
if (archetypeId == "rootkit")
|
||||
@@ -1860,6 +2050,33 @@ namespace KernelPanic.Combat
|
||||
}
|
||||
}
|
||||
|
||||
private bool TrySpawnEliteEncounter(int wave, int uptimeBonus)
|
||||
{
|
||||
if (wave < 5)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool guaranteedIntro = wave == 5;
|
||||
bool rolledElite = RandomRoll.RollRange(1, 100, RollContext.None) <= 35;
|
||||
if (!guaranteedIntro && !rolledElite)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
SpawnEnemy(CreateEliteArchetypeId(), uptimeBonus);
|
||||
Log("elite process escorted by fodder");
|
||||
// TODO: Replace this single-elite chance with authored elite encounter assets.
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string CreateEliteArchetypeId()
|
||||
{
|
||||
string[] pool = { "daemon", "kernel_panic", "telemetry_collector", "drm_guardian" };
|
||||
int index = RandomRoll.RollRange(0, pool.Length - 1, RollContext.None);
|
||||
return pool[index];
|
||||
}
|
||||
|
||||
private void SpawnRacePair(int uptimeBonus)
|
||||
{
|
||||
int pairId = _nextRacePairId++;
|
||||
@@ -2002,15 +2219,24 @@ namespace KernelPanic.Combat
|
||||
Log("TODO: buff intent needs enemy buff/status support.");
|
||||
break;
|
||||
case EnemyIntentKind.Special:
|
||||
ExecuteSpecialEnemyIntent(enemy);
|
||||
ExecuteSpecialEnemyIntent(enemy, value);
|
||||
break;
|
||||
}
|
||||
|
||||
enemy.AdvanceCountdownAfterAction();
|
||||
}
|
||||
|
||||
private void ExecuteSpecialEnemyIntent(EnemyInstance enemy)
|
||||
private void ExecuteSpecialEnemyIntent(EnemyInstance enemy, int value)
|
||||
{
|
||||
if (enemy.HasBehavior(EnemyBehaviorFlags.CardLocker))
|
||||
{
|
||||
LockRandomHandCard(enemy);
|
||||
value = ApplyIncomingAttackMitigation(value);
|
||||
DamageResult result = _damagePipeline.DealDamage(new DamageRequest(enemy.State, _playerState, value, enemy.CurrentIntent.DamageType, enemy.CurrentIntent.TrueDamage, false));
|
||||
GameEvents.RaisePlayerDamaged(new PlayerDamagedEvent(enemy, result.FinalAmount));
|
||||
return;
|
||||
}
|
||||
|
||||
if (enemy.HasBehavior(EnemyBehaviorFlags.SegfaultOnDeath))
|
||||
{
|
||||
enemy.AdvanceSegfaultCountdown();
|
||||
|
||||
@@ -41,6 +41,12 @@ namespace KernelPanic.Combat
|
||||
public const int StatUpgradeHealPercent = 30;
|
||||
public const int StatUpgradeRam = 1;
|
||||
public const int UpgradeMagnitudeBonus = 2;
|
||||
public const int FedoraBleedingEdgeBaseCrashChance = 5;
|
||||
public const int FedoraBleedingEdgeCrashChanceStep = 5;
|
||||
public const int FedoraBleedingEdgeMaxCrashChance = 90;
|
||||
public const int HitMagnitudeModeratePercent = 10;
|
||||
public const int HitMagnitudeMajorPercent = 25;
|
||||
public const int HitMagnitudeMassivePercent = 45;
|
||||
|
||||
public static int ScaleStatUpgradeMaxUptime(int maxUptime)
|
||||
{
|
||||
@@ -61,4 +67,32 @@ namespace KernelPanic.Combat
|
||||
public const float EnemyTelegraphDelaySeconds = 0.35f;
|
||||
public const float EnemyActionDelaySeconds = 0.45f;
|
||||
}
|
||||
|
||||
public enum HitMagnitudeTier
|
||||
{
|
||||
Minor,
|
||||
Moderate,
|
||||
Major,
|
||||
Massive
|
||||
}
|
||||
|
||||
public static class HitMagnitude
|
||||
{
|
||||
public static HitMagnitudeTier Classify(int damageAmount, int targetMaxUptime, bool targetDefeated)
|
||||
{
|
||||
int maxUptime = UnityEngine.Mathf.Max(1, targetMaxUptime);
|
||||
int amount = UnityEngine.Mathf.Max(0, damageAmount);
|
||||
int percent = UnityEngine.Mathf.FloorToInt((amount * 100f) / maxUptime);
|
||||
|
||||
HitMagnitudeTier tier = percent >= CombatTuning.HitMagnitudeMassivePercent
|
||||
? HitMagnitudeTier.Massive
|
||||
: percent >= CombatTuning.HitMagnitudeMajorPercent
|
||||
? HitMagnitudeTier.Major
|
||||
: percent >= CombatTuning.HitMagnitudeModeratePercent
|
||||
? HitMagnitudeTier.Moderate
|
||||
: HitMagnitudeTier.Minor;
|
||||
|
||||
return targetDefeated && tier < HitMagnitudeTier.Major ? HitMagnitudeTier.Major : tier;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ namespace KernelPanic.Combat
|
||||
|
||||
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));
|
||||
GameEvents.RaiseDamageDealt(new DamageDealtEvent(request.Source, request.Target, finalAmount, request.Language, amount, absorbedAmount, wasCritical, shieldDamage, uptimeDamage, request.TrueDamage));
|
||||
|
||||
bool defeated = false;
|
||||
if (request.Target.CurrentUptime <= 0 && !request.Target.IsDefeated)
|
||||
|
||||
@@ -19,7 +19,16 @@ namespace KernelPanic.Combat
|
||||
SegfaultOnDeath = 1 << 7,
|
||||
RacePair = 1 << 8,
|
||||
RootkitMasked = 1 << 9,
|
||||
Elite = 1 << 10
|
||||
Elite = 1 << 10,
|
||||
RamPressure = 1 << 11,
|
||||
TelemetryCollector = 1 << 12,
|
||||
CardLocker = 1 << 13
|
||||
}
|
||||
|
||||
public enum EnemyTier
|
||||
{
|
||||
Fodder,
|
||||
Elite
|
||||
}
|
||||
|
||||
public sealed class EnemyArchetypeDescriptor
|
||||
@@ -30,7 +39,8 @@ namespace KernelPanic.Combat
|
||||
int baseUptimeMin,
|
||||
int baseUptimeMax,
|
||||
EnemyBehaviorFlags behaviorFlags,
|
||||
IReadOnlyList<EnemyIntent> intentPool)
|
||||
IReadOnlyList<EnemyIntent> intentPool,
|
||||
EnemyTier tier = EnemyTier.Fodder)
|
||||
{
|
||||
Id = string.IsNullOrWhiteSpace(id) ? "unknown_process" : id;
|
||||
DisplayName = string.IsNullOrWhiteSpace(displayName) ? Id : displayName;
|
||||
@@ -38,6 +48,7 @@ namespace KernelPanic.Combat
|
||||
BaseUptimeMax = Mathf.Max(BaseUptimeMin, baseUptimeMax);
|
||||
BehaviorFlags = behaviorFlags;
|
||||
IntentPool = intentPool ?? Array.Empty<EnemyIntent>();
|
||||
Tier = tier;
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
@@ -46,6 +57,7 @@ namespace KernelPanic.Combat
|
||||
public int BaseUptimeMax { get; }
|
||||
public EnemyBehaviorFlags BehaviorFlags { get; }
|
||||
public IReadOnlyList<EnemyIntent> IntentPool { get; }
|
||||
public EnemyTier Tier { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -61,6 +73,9 @@ namespace KernelPanic.Combat
|
||||
public const int ForkBombTotalCap = 5;
|
||||
public const int RootkitMaskedDamagePercent = 10;
|
||||
public const int RacePairDamageBonus = 2;
|
||||
public const int KernelPanicRamPenalty = 2;
|
||||
public const int TelemetryDamageGrowthPerCard = 1;
|
||||
public const int DrmUnlockCycleCost = 1;
|
||||
public const int CronInitialCountdown = 2;
|
||||
public const int SegfaultInitialCountdown = 2;
|
||||
|
||||
@@ -106,7 +121,8 @@ namespace KernelPanic.Combat
|
||||
new[]
|
||||
{
|
||||
new EnemyIntent(EnemyIntentKind.Attack, 6, 8, Language.C, "daemon hit", "!")
|
||||
});
|
||||
},
|
||||
EnemyTier.Elite);
|
||||
|
||||
private static readonly EnemyArchetypeDescriptor Defender = new(
|
||||
"firewalld",
|
||||
@@ -176,6 +192,42 @@ namespace KernelPanic.Combat
|
||||
new EnemyIntent(EnemyIntentKind.Attack, 1, 2, Language.C, "masked hit", "?")
|
||||
});
|
||||
|
||||
private static readonly EnemyArchetypeDescriptor KernelPanic = new(
|
||||
"kernel_panic",
|
||||
"Kernel Panic",
|
||||
25,
|
||||
30,
|
||||
EnemyBehaviorFlags.Elite | EnemyBehaviorFlags.RamPressure,
|
||||
new[]
|
||||
{
|
||||
new EnemyIntent(EnemyIntentKind.Attack, 5, 7, Language.C, "panic hit", "!")
|
||||
},
|
||||
EnemyTier.Elite);
|
||||
|
||||
private static readonly EnemyArchetypeDescriptor TelemetryCollector = new(
|
||||
"telemetry_collector",
|
||||
"Telemetry Collector",
|
||||
20,
|
||||
25,
|
||||
EnemyBehaviorFlags.Elite | EnemyBehaviorFlags.TelemetryCollector,
|
||||
new[]
|
||||
{
|
||||
new EnemyIntent(EnemyIntentKind.Attack, 2, 3, Language.C, "profile hit", "!")
|
||||
},
|
||||
EnemyTier.Elite);
|
||||
|
||||
private static readonly EnemyArchetypeDescriptor DrmGuardian = new(
|
||||
"drm_guardian",
|
||||
"DRM Guardian",
|
||||
25,
|
||||
30,
|
||||
EnemyBehaviorFlags.Elite | EnemyBehaviorFlags.CardLocker,
|
||||
new[]
|
||||
{
|
||||
new EnemyIntent(EnemyIntentKind.Special, 4, 6, Language.C, "license check", "$")
|
||||
},
|
||||
EnemyTier.Elite);
|
||||
|
||||
public static EnemyArchetypeDescriptor Get(string id)
|
||||
{
|
||||
return id switch
|
||||
@@ -189,6 +241,9 @@ namespace KernelPanic.Combat
|
||||
"segfault" => Segfault,
|
||||
"race_condition" => RaceCondition,
|
||||
"rootkit" => Rootkit,
|
||||
"kernel_panic" => KernelPanic,
|
||||
"telemetry_collector" => TelemetryCollector,
|
||||
"drm_guardian" => DrmGuardian,
|
||||
_ => ZombieProcess
|
||||
};
|
||||
}
|
||||
|
||||
@@ -46,14 +46,18 @@ namespace KernelPanic.Combat
|
||||
public bool LethalHitThisTurn { get; private set; }
|
||||
public int PairId { get; private set; } = -1;
|
||||
public int RaceEnrageStacks { get; private set; }
|
||||
public int TelemetryStacks { get; private set; }
|
||||
public bool SpawnedFromDeath { get; private set; }
|
||||
public bool HasPendingMarker => PendingRevive || HasRevived;
|
||||
public bool HasSpecialSignal => HasSplitSignal || HasSegfaultSignal || HasRaceSignal || HasRootkitSignal || HasEliteSignal || SpawnedFromDeath;
|
||||
public bool HasSpecialSignal => HasSplitSignal || HasSegfaultSignal || HasRaceSignal || HasRootkitSignal || HasEliteSignal || HasRamPressureSignal || HasTelemetrySignal || HasCardLockerSignal || SpawnedFromDeath;
|
||||
public bool HasSplitSignal => HasBehavior(EnemyBehaviorFlags.Split);
|
||||
public bool HasSegfaultSignal => HasBehavior(EnemyBehaviorFlags.SegfaultOnDeath);
|
||||
public bool HasRaceSignal => HasBehavior(EnemyBehaviorFlags.RacePair);
|
||||
public bool HasRootkitSignal => HasBehavior(EnemyBehaviorFlags.RootkitMasked);
|
||||
public bool HasEliteSignal => HasBehavior(EnemyBehaviorFlags.Elite);
|
||||
public bool HasEliteSignal => Archetype.Tier == EnemyTier.Elite || HasBehavior(EnemyBehaviorFlags.Elite);
|
||||
public bool HasRamPressureSignal => HasBehavior(EnemyBehaviorFlags.RamPressure);
|
||||
public bool HasTelemetrySignal => HasBehavior(EnemyBehaviorFlags.TelemetryCollector);
|
||||
public bool HasCardLockerSignal => HasBehavior(EnemyBehaviorFlags.CardLocker);
|
||||
public EnemyIntent DisplayIntent => BuildDisplayIntent();
|
||||
|
||||
public void PickNextIntent()
|
||||
@@ -93,6 +97,14 @@ namespace KernelPanic.Combat
|
||||
int maxValue = UnityEngine.Mathf.Min(CurrentIntent.MaxValue + growth, EnemyArchetypeCatalog.MemoryLeakAttackCapMax);
|
||||
CurrentIntent = CurrentIntent.WithValues(minValue, maxValue, "growing leak");
|
||||
}
|
||||
|
||||
if (HasBehavior(EnemyBehaviorFlags.TelemetryCollector) && CurrentIntent.Kind == EnemyIntentKind.Attack && TelemetryStacks > 0)
|
||||
{
|
||||
CurrentIntent = CurrentIntent.WithValues(
|
||||
CurrentIntent.MinValue + TelemetryStacks,
|
||||
CurrentIntent.MaxValue + TelemetryStacks,
|
||||
"profiled hit");
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasBehavior(EnemyBehaviorFlags behavior)
|
||||
@@ -128,6 +140,18 @@ namespace KernelPanic.Combat
|
||||
RaceEnrageStacks++;
|
||||
}
|
||||
|
||||
public void AddTelemetryStack()
|
||||
{
|
||||
TelemetryStacks += EnemyArchetypeCatalog.TelemetryDamageGrowthPerCard;
|
||||
if (CurrentIntent.Kind == EnemyIntentKind.Attack)
|
||||
{
|
||||
CurrentIntent = CurrentIntent.WithValues(
|
||||
CurrentIntent.MinValue + EnemyArchetypeCatalog.TelemetryDamageGrowthPerCard,
|
||||
CurrentIntent.MaxValue + EnemyArchetypeCatalog.TelemetryDamageGrowthPerCard,
|
||||
"profiled hit");
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetTurnLethalMarker()
|
||||
{
|
||||
LethalHitThisTurn = false;
|
||||
|
||||
@@ -54,6 +54,11 @@ namespace KernelPanic.Combat
|
||||
return card != null && _cards.Remove(card);
|
||||
}
|
||||
|
||||
public void SetRamCapacity(int ramCapacity)
|
||||
{
|
||||
RamCapacity = System.Math.Max(1, ramCapacity);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_cards.Clear();
|
||||
|
||||
Reference in New Issue
Block a user