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 int PairId { get; private set; } = -1;
public int RaceEnrageStacks { 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 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 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");
}
}
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;
}
public void EnrageRaceSurvivor()
{
RaceEnrageStacks++;
}
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);
}
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;
}
}
}