using System.Collections.Generic; namespace KernelPanic.Combat { /// /// Runtime-only enemy placeholder used until encounter and EnemyDefinition assets are wired. /// public sealed class EnemyInstance { private readonly List _intentPool = new(); public EnemyInstance(string name, int maxUptime, IEnumerable intentPool) : this(new EnemyArchetypeDescriptor(name, name, maxUptime, maxUptime, EnemyBehaviorFlags.None, new List(intentPool ?? System.Array.Empty())), maxUptime) { } public EnemyInstance(EnemyArchetypeDescriptor archetype, int maxUptime) { Archetype = archetype ?? EnemyArchetypeCatalog.Get("zombie_process"); Name = Archetype.DisplayName; State = new CombatantState(maxUptime, 0, 0); if (Archetype.IntentPool != null) { _intentPool.AddRange(Archetype.IntentPool); } CountdownRemaining = HasBehavior(EnemyBehaviorFlags.Countdown) ? EnemyArchetypeCatalog.CronInitialCountdown : HasBehavior(EnemyBehaviorFlags.SegfaultOnDeath) ? EnemyArchetypeCatalog.SegfaultInitialCountdown : 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 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 DefeatedPendingRemoval { 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 DeathSpawnIntentRevealed { get; private set; } = true; public bool HasPendingMarker => PendingRevive || HasRevived; 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 => 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() { 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 (HasBehavior(EnemyBehaviorFlags.SegfaultOnDeath) && CountdownRemaining > 0) { CurrentIntent = new EnemyIntent(EnemyIntentKind.Special, CountdownRemaining, CountdownRemaining, KernelPanic.Core.Language.C, "segfault in", "!"); return; } if (_intentPool.Count == 0) { CurrentIntent = default; return; } int index = RandomRoll.RollRange(0, _intentPool.Count - 1, new RollContext(State)); CurrentIntent = _intentPool[index]; if (HasBehavior(EnemyBehaviorFlags.Grow)) { int growth = TurnsAlive * EnemyArchetypeCatalog.MemoryLeakAttackGrowthPerTurn; int minValue = UnityEngine.Mathf.Min(CurrentIntent.MinValue + growth, EnemyArchetypeCatalog.MemoryLeakAttackCapMin); 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) { return (Archetype.BehaviorFlags & behavior) != 0; } public void MarkDamaged() { if (HasBehavior(EnemyBehaviorFlags.ObfuscateIntent)) { IntentRevealed = true; } } public void MarkTurnSurvived() { TurnsAlive++; } public void SetPairId(int pairId) { PairId = pairId; } public void MarkSpawnedFromDeath() { SpawnedFromDeath = true; DeathSpawnIntentRevealed = false; } public void RevealDeathSpawnIntent() { DeathSpawnIntentRevealed = true; } public void EnrageRaceSurvivor() { 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; } public void MarkLethalHit() { LethalHitThisTurn = true; } public void MarkDefeatedPendingRemoval() { DefeatedPendingRemoval = true; } public void ClearDefeatedPendingRemoval() { DefeatedPendingRemoval = false; } public void MarkPendingRevive() { DefeatedPendingRemoval = false; 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); } public void AdvanceSegfaultCountdown() { if (HasBehavior(EnemyBehaviorFlags.SegfaultOnDeath)) { CountdownRemaining = 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; } } }