impl
This commit is contained in:
@@ -0,0 +1,418 @@
|
||||
using SlaySharp.Core.Combat;
|
||||
using SlaySharp.Core.Enums;
|
||||
using SlaySharp.Core.Models;
|
||||
|
||||
namespace SlaySharp.Core.Catalogs;
|
||||
|
||||
public class ConsoleWriteLineCard : Card
|
||||
{
|
||||
private int _damage = 6;
|
||||
|
||||
public ConsoleWriteLineCard() : base(
|
||||
"Console.WriteLine",
|
||||
"Console.WriteLine(target);",
|
||||
"target.Hit(6);",
|
||||
"Deal 6 damage.",
|
||||
cost: 1,
|
||||
type: CardType.Attack,
|
||||
rarity: CardRarity.Basic,
|
||||
targetType: TargetType.SingleEnemy
|
||||
) { }
|
||||
|
||||
protected override void OnUpgrade()
|
||||
{
|
||||
_damage = 9;
|
||||
Description = "Deal 9 damage.";
|
||||
CodeSnippet = "target.Hit(9);";
|
||||
}
|
||||
|
||||
public override void ApplyEffect(CombatEngine engine, Player player, Character? target)
|
||||
{
|
||||
if (target == null) return;
|
||||
var isCrit = target.Statuses.Get(StatusType.Vulnerable) > 0;
|
||||
var totalDmg = player.Statuses.CalculateOutgoingDamage(_damage);
|
||||
var dealt = target.TakeDamage(totalDmg);
|
||||
engine.NotifyDamage(target.Id, target.Name, dealt, isPiercing: false, isCrit: isCrit, Name);
|
||||
engine.Log.AddInfo($"[Console.WriteLine] {target.Name} took {dealt} dmg.");
|
||||
}
|
||||
|
||||
public override Card Clone() => new ConsoleWriteLineCard
|
||||
{
|
||||
Upgraded = this.Upgraded,
|
||||
Name = this.Name,
|
||||
Description = this.Description,
|
||||
_damage = this._damage,
|
||||
CodeSnippet = this.CodeSnippet
|
||||
};
|
||||
}
|
||||
|
||||
public class TryCatchCard : Card
|
||||
{
|
||||
private int _shield = 5;
|
||||
|
||||
public TryCatchCard() : base(
|
||||
"try { ... } catch",
|
||||
"try { Guard(); } catch { }",
|
||||
"shield += 5;",
|
||||
"Gain 5 Shield.",
|
||||
cost: 1,
|
||||
type: CardType.Skill,
|
||||
rarity: CardRarity.Basic,
|
||||
targetType: TargetType.Self
|
||||
) { }
|
||||
|
||||
protected override void OnUpgrade()
|
||||
{
|
||||
_shield = 8;
|
||||
Description = "Gain 8 Shield.";
|
||||
CodeSnippet = "shield += 8;";
|
||||
}
|
||||
|
||||
public override void ApplyEffect(CombatEngine engine, Player player, Character? target)
|
||||
{
|
||||
player.GainShield(_shield);
|
||||
engine.NotifyShield(player.Id, player.Name, _shield, Name);
|
||||
engine.Log.AddInfo($"[try/catch] +{_shield} Shield.");
|
||||
}
|
||||
|
||||
public override Card Clone() => new TryCatchCard
|
||||
{
|
||||
Upgraded = this.Upgraded,
|
||||
Name = this.Name,
|
||||
Description = this.Description,
|
||||
_shield = this._shield,
|
||||
CodeSnippet = this.CodeSnippet
|
||||
};
|
||||
}
|
||||
|
||||
public class GcCollectCard : Card
|
||||
{
|
||||
private int _baseDamage = 8;
|
||||
|
||||
public GcCollectCard() : base(
|
||||
"GC.Collect()",
|
||||
"GC.Collect(2);",
|
||||
"target.Hit(8 + Discard * 2);",
|
||||
"Deal 8 damage + 2 per Discard card.",
|
||||
cost: 2,
|
||||
type: CardType.Attack,
|
||||
rarity: CardRarity.Common,
|
||||
targetType: TargetType.SingleEnemy
|
||||
) { }
|
||||
|
||||
protected override void OnUpgrade()
|
||||
{
|
||||
_baseDamage = 12;
|
||||
Description = "Deal 12 damage + 2 per Discard card.";
|
||||
CodeSnippet = "target.Hit(12 + Discard * 2);";
|
||||
}
|
||||
|
||||
public override void ApplyEffect(CombatEngine engine, Player player, Character? target)
|
||||
{
|
||||
if (target == null) return;
|
||||
var discardCount = player.DiscardPile.Count;
|
||||
var rawDmg = _baseDamage + (discardCount * 2);
|
||||
var isCrit = target.Statuses.Get(StatusType.Vulnerable) > 0;
|
||||
var totalDmg = player.Statuses.CalculateOutgoingDamage(rawDmg);
|
||||
var dealt = target.TakeDamage(totalDmg);
|
||||
engine.NotifyDamage(target.Id, target.Name, dealt, isPiercing: false, isCrit: isCrit, Name);
|
||||
engine.Log.AddSuccess($"[GC.Collect] Swept {discardCount} objects! Dealt {dealt} dmg.");
|
||||
}
|
||||
|
||||
public override Card Clone() => new GcCollectCard
|
||||
{
|
||||
Upgraded = this.Upgraded,
|
||||
Name = this.Name,
|
||||
Description = this.Description,
|
||||
_baseDamage = this._baseDamage,
|
||||
CodeSnippet = this.CodeSnippet
|
||||
};
|
||||
}
|
||||
|
||||
public class SpanSliceCard : Card
|
||||
{
|
||||
private int _damage = 8;
|
||||
|
||||
public SpanSliceCard() : base(
|
||||
"Span<T>.Slice()",
|
||||
"span.Slice(0, 8);",
|
||||
"target.DirectHit(8);",
|
||||
"Deal 8 piercing damage (ignores shield).",
|
||||
cost: 1,
|
||||
type: CardType.Attack,
|
||||
rarity: CardRarity.Common,
|
||||
targetType: TargetType.SingleEnemy
|
||||
) { }
|
||||
|
||||
protected override void OnUpgrade()
|
||||
{
|
||||
_damage = 11;
|
||||
Description = "Deal 11 piercing damage (ignores shield).";
|
||||
CodeSnippet = "target.DirectHit(11);";
|
||||
}
|
||||
|
||||
public override void ApplyEffect(CombatEngine engine, Player player, Character? target)
|
||||
{
|
||||
if (target == null) return;
|
||||
var isCrit = target.Statuses.Get(StatusType.Vulnerable) > 0;
|
||||
var totalDmg = player.Statuses.CalculateOutgoingDamage(_damage);
|
||||
var dealt = target.TakeDamage(totalDmg, isPiercing: true);
|
||||
engine.NotifyDamage(target.Id, target.Name, dealt, isPiercing: true, isCrit: isCrit, Name);
|
||||
engine.Log.AddSuccess($"[Span.Slice] Pierced shield! Dealt {dealt} dmg.");
|
||||
}
|
||||
|
||||
public override Card Clone() => new SpanSliceCard
|
||||
{
|
||||
Upgraded = this.Upgraded,
|
||||
Name = this.Name,
|
||||
Description = this.Description,
|
||||
_damage = this._damage,
|
||||
CodeSnippet = this.CodeSnippet
|
||||
};
|
||||
}
|
||||
|
||||
public class ThrowExceptionCard : Card
|
||||
{
|
||||
private int _damage = 16;
|
||||
|
||||
public ThrowExceptionCard() : base(
|
||||
"throw Exception()",
|
||||
"throw new PanicException();",
|
||||
"target.Hit(16); // +1 Leak",
|
||||
"Deal 16 damage. Gain 1 Memory Leak.",
|
||||
cost: 2,
|
||||
type: CardType.Attack,
|
||||
rarity: CardRarity.Uncommon,
|
||||
targetType: TargetType.SingleEnemy
|
||||
) { }
|
||||
|
||||
protected override void OnUpgrade()
|
||||
{
|
||||
_damage = 22;
|
||||
Description = "Deal 22 damage. Gain 1 Memory Leak.";
|
||||
CodeSnippet = "target.Hit(22); // +1 Leak";
|
||||
}
|
||||
|
||||
public override void ApplyEffect(CombatEngine engine, Player player, Character? target)
|
||||
{
|
||||
if (target == null) return;
|
||||
var isCrit = target.Statuses.Get(StatusType.Vulnerable) > 0;
|
||||
var totalDmg = player.Statuses.CalculateOutgoingDamage(_damage);
|
||||
var dealt = target.TakeDamage(totalDmg);
|
||||
player.Statuses.Add(StatusType.MemoryLeak, 1);
|
||||
engine.NotifyDamage(target.Id, target.Name, dealt, isPiercing: false, isCrit: isCrit, Name);
|
||||
engine.Log.AddWarning($"[throw Exception] Dealt {dealt} dmg. +1 Memory Leak.");
|
||||
}
|
||||
|
||||
public override Card Clone() => new ThrowExceptionCard
|
||||
{
|
||||
Upgraded = this.Upgraded,
|
||||
Name = this.Name,
|
||||
Description = this.Description,
|
||||
_damage = this._damage,
|
||||
CodeSnippet = this.CodeSnippet
|
||||
};
|
||||
}
|
||||
|
||||
public class TieredCompilationCard : Card
|
||||
{
|
||||
private int _strength = 2;
|
||||
|
||||
public TieredCompilationCard() : base(
|
||||
"Tiered Compilation",
|
||||
"[AggressiveOptimization]",
|
||||
"Player.Strength += 2;",
|
||||
"Power: +2 Strength (All attacks deal +2 damage).",
|
||||
cost: 1,
|
||||
type: CardType.Power,
|
||||
rarity: CardRarity.Rare,
|
||||
targetType: TargetType.Self,
|
||||
exhausts: true
|
||||
) { }
|
||||
|
||||
protected override void OnUpgrade()
|
||||
{
|
||||
_strength = 3;
|
||||
Description = "Power: +3 Strength (All attacks deal +3 damage).";
|
||||
CodeSnippet = "Player.Strength += 3;";
|
||||
}
|
||||
|
||||
public override void ApplyEffect(CombatEngine engine, Player player, Character? target)
|
||||
{
|
||||
player.Statuses.Add(StatusType.Strength, _strength);
|
||||
engine.Log.AddSuccess($"[JIT] +{_strength} Strength permanently.");
|
||||
}
|
||||
|
||||
public override Card Clone() => new TieredCompilationCard
|
||||
{
|
||||
Upgraded = this.Upgraded,
|
||||
Name = this.Name,
|
||||
Description = this.Description,
|
||||
_strength = this._strength,
|
||||
CodeSnippet = this.CodeSnippet
|
||||
};
|
||||
}
|
||||
|
||||
public class ThreadSleepCard : Card
|
||||
{
|
||||
public ThreadSleepCard() : base(
|
||||
"Thread.Sleep()",
|
||||
"await Task.Yield();",
|
||||
"CPU += 2; Weak += 1;",
|
||||
"Gain +2 CPU this turn. Gain 1 Weak.",
|
||||
cost: 0,
|
||||
type: CardType.Skill,
|
||||
rarity: CardRarity.Common,
|
||||
targetType: TargetType.Self
|
||||
) { }
|
||||
|
||||
protected override void OnUpgrade()
|
||||
{
|
||||
Description = "Gain +3 CPU this turn. Gain 1 Weak.";
|
||||
CodeSnippet = "CPU += 3; Weak += 1;";
|
||||
}
|
||||
|
||||
public override void ApplyEffect(CombatEngine engine, Player player, Character? target)
|
||||
{
|
||||
var energyBonus = Upgraded ? 3 : 2;
|
||||
player.CurrentEnergy += energyBonus;
|
||||
player.Statuses.Add(StatusType.Weak, 1);
|
||||
engine.Log.AddInfo($"[Thread.Sleep] +{energyBonus} CPU, +1 Weak.");
|
||||
}
|
||||
|
||||
public override Card Clone() => new ThreadSleepCard
|
||||
{
|
||||
Upgraded = this.Upgraded,
|
||||
Name = this.Name,
|
||||
Description = this.Description,
|
||||
CodeSnippet = this.CodeSnippet
|
||||
};
|
||||
}
|
||||
|
||||
public class ParallelForEachCard : Card
|
||||
{
|
||||
private int _dmgPerHit = 4;
|
||||
|
||||
public ParallelForEachCard() : base(
|
||||
"Parallel.ForEach()",
|
||||
"Parallel.For(0, 3);",
|
||||
"target.Hit(4); // x3 hits",
|
||||
"Attack 3 times for 4 damage each.",
|
||||
cost: 2,
|
||||
type: CardType.Attack,
|
||||
rarity: CardRarity.Uncommon,
|
||||
targetType: TargetType.SingleEnemy
|
||||
) { }
|
||||
|
||||
protected override void OnUpgrade()
|
||||
{
|
||||
_dmgPerHit = 5;
|
||||
Description = "Attack 3 times for 5 damage each.";
|
||||
CodeSnippet = "target.Hit(5); // x3 hits";
|
||||
}
|
||||
|
||||
public override void ApplyEffect(CombatEngine engine, Player player, Character? target)
|
||||
{
|
||||
if (target == null) return;
|
||||
int totalHits = 3;
|
||||
int totalDamage = 0;
|
||||
var isCrit = target.Statuses.Get(StatusType.Vulnerable) > 0;
|
||||
for (int i = 0; i < totalHits; i++)
|
||||
{
|
||||
if (target.IsDead) break;
|
||||
var hitDmg = player.Statuses.CalculateOutgoingDamage(_dmgPerHit);
|
||||
var dealt = target.TakeDamage(hitDmg);
|
||||
totalDamage += dealt;
|
||||
engine.NotifyDamage(target.Id, target.Name, dealt, isPiercing: false, isCrit: isCrit, $"{Name} #{i+1}");
|
||||
}
|
||||
engine.Log.AddSuccess($"[Parallel.ForEach] 3 hits: total {totalDamage} dmg.");
|
||||
}
|
||||
|
||||
public override Card Clone() => new ParallelForEachCard
|
||||
{
|
||||
Upgraded = this.Upgraded,
|
||||
Name = this.Name,
|
||||
Description = this.Description,
|
||||
_dmgPerHit = this._dmgPerHit,
|
||||
CodeSnippet = this.CodeSnippet
|
||||
};
|
||||
}
|
||||
|
||||
public class LinqWhereCard : Card
|
||||
{
|
||||
private int _shield = 6;
|
||||
|
||||
public LinqWhereCard() : base(
|
||||
"collection.Where()",
|
||||
"data.Where(x => x.IsSafe);",
|
||||
"shield += 6; Draw(1);",
|
||||
"Gain 6 Shield. Draw 1 card.",
|
||||
cost: 1,
|
||||
type: CardType.Skill,
|
||||
rarity: CardRarity.Common,
|
||||
targetType: TargetType.Self
|
||||
) { }
|
||||
|
||||
protected override void OnUpgrade()
|
||||
{
|
||||
_shield = 9;
|
||||
Description = "Gain 9 Shield. Draw 1 card.";
|
||||
CodeSnippet = "shield += 9; Draw(1);";
|
||||
}
|
||||
|
||||
public override void ApplyEffect(CombatEngine engine, Player player, Character? target)
|
||||
{
|
||||
player.GainShield(_shield);
|
||||
engine.NotifyShield(player.Id, player.Name, _shield, Name);
|
||||
player.DrawCards(1, engine.Log);
|
||||
engine.Log.AddInfo($"[LINQ .Where()] +{_shield} Shield, drew 1 card.");
|
||||
}
|
||||
|
||||
public override Card Clone() => new LinqWhereCard
|
||||
{
|
||||
Upgraded = this.Upgraded,
|
||||
Name = this.Name,
|
||||
Description = this.Description,
|
||||
_shield = this._shield,
|
||||
CodeSnippet = this.CodeSnippet
|
||||
};
|
||||
}
|
||||
|
||||
public class NullCoalescingCard : Card
|
||||
{
|
||||
public NullCoalescingCard() : base(
|
||||
"fallback ?? default",
|
||||
"val ?? FallbackState;",
|
||||
"ClearDebuffs(); shield += 6;",
|
||||
"Remove all debuffs. Gain 6 Shield.",
|
||||
cost: 1,
|
||||
type: CardType.Skill,
|
||||
rarity: CardRarity.Uncommon,
|
||||
targetType: TargetType.Self
|
||||
) { }
|
||||
|
||||
protected override void OnUpgrade()
|
||||
{
|
||||
Description = "Remove all debuffs. Gain 8 Shield.";
|
||||
CodeSnippet = "ClearDebuffs(); shield += 8;";
|
||||
}
|
||||
|
||||
public override void ApplyEffect(CombatEngine engine, Player player, Character? target)
|
||||
{
|
||||
var stacksToRemove = Upgraded ? 2 : 1;
|
||||
var shield = Upgraded ? 8 : 6;
|
||||
player.Statuses.Remove(StatusType.Vulnerable, stacksToRemove);
|
||||
player.Statuses.Remove(StatusType.Weak, stacksToRemove);
|
||||
player.Statuses.Remove(StatusType.MemoryLeak, stacksToRemove);
|
||||
player.GainShield(shield);
|
||||
engine.NotifyShield(player.Id, player.Name, shield, Name);
|
||||
engine.Log.AddSuccess($"[Null Coalescing ??] Cleared debuffs, +{shield} Shield.");
|
||||
}
|
||||
|
||||
public override Card Clone() => new NullCoalescingCard
|
||||
{
|
||||
Upgraded = this.Upgraded,
|
||||
Name = this.Name,
|
||||
Description = this.Description
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
using SlaySharp.Core.Combat;
|
||||
using SlaySharp.Core.Enums;
|
||||
using SlaySharp.Core.Models;
|
||||
|
||||
namespace SlaySharp.Core.Catalogs;
|
||||
|
||||
public class NullReferenceExceptionEnemy : Enemy
|
||||
{
|
||||
private int _patternStep = 0;
|
||||
|
||||
public NullReferenceExceptionEnemy()
|
||||
: base("NullReferenceException", 45, "💥", "Unhandled null pointer reference causing sudden application crashes.")
|
||||
{
|
||||
}
|
||||
|
||||
public override void DecideNextIntent(CombatEngine engine)
|
||||
{
|
||||
CurrentIntent = _patternStep switch
|
||||
{
|
||||
0 => new Intent(IntentType.Attack, 7, 1, "Dereference Null Pointer (7 dmg)"),
|
||||
1 => new Intent(IntentType.AttackDefend, 6, 1, "Corrupt Stack Frame (6 dmg & +7 Shield)"),
|
||||
2 => new Intent(IntentType.Debuff, 0, 1, "Expose Null Pointer (+2 Vulnerable)", StatusType.Vulnerable, 2),
|
||||
3 => new Intent(IntentType.Attack, 14, 1, "FATAL PROCESS TERMINATION (14 dmg)"),
|
||||
_ => new Intent(IntentType.Attack, 8, 1, "Null Strike (8 dmg)")
|
||||
};
|
||||
}
|
||||
|
||||
public override void ExecuteIntent(CombatEngine engine)
|
||||
{
|
||||
if (CurrentIntent == null) return;
|
||||
|
||||
switch (_patternStep)
|
||||
{
|
||||
case 0:
|
||||
var dmg0 = Statuses.CalculateOutgoingDamage(CurrentIntent.PrimaryValue);
|
||||
var isCrit0 = engine.Player.Statuses.Get(StatusType.Vulnerable) > 0;
|
||||
var dealt0 = engine.Player.TakeDamage(dmg0);
|
||||
engine.NotifyDamage(engine.Player.Id, engine.Player.Name, dealt0, isPiercing: false, isCrit: isCrit0, Name);
|
||||
engine.Log.AddError($"[{Name}] dereferenced null pointer! Dealt {dealt0} damage to Player.");
|
||||
break;
|
||||
case 1:
|
||||
var dmg1 = Statuses.CalculateOutgoingDamage(CurrentIntent.PrimaryValue);
|
||||
var isCrit1 = engine.Player.Statuses.Get(StatusType.Vulnerable) > 0;
|
||||
var dealt1 = engine.Player.TakeDamage(dmg1);
|
||||
engine.NotifyDamage(engine.Player.Id, engine.Player.Name, dealt1, isPiercing: false, isCrit: isCrit1, Name);
|
||||
GainShield(7);
|
||||
engine.NotifyShield(Id, Name, 7, Name);
|
||||
engine.Log.AddError($"[{Name}] corrupted call stack! Dealt {dealt1} damage and gained +7 Shield.");
|
||||
break;
|
||||
case 2:
|
||||
engine.Player.Statuses.Add(StatusType.Vulnerable, 2);
|
||||
GainShield(5);
|
||||
engine.NotifyShield(Id, Name, 5, Name);
|
||||
engine.Log.AddWarning($"[{Name}] exposed unhandled pointer! Player is now Vulnerable for 2 turns.");
|
||||
break;
|
||||
case 3:
|
||||
var dmg3 = Statuses.CalculateOutgoingDamage(CurrentIntent.PrimaryValue);
|
||||
var isCrit3 = engine.Player.Statuses.Get(StatusType.Vulnerable) > 0;
|
||||
var dealt3 = engine.Player.TakeDamage(dmg3);
|
||||
engine.NotifyDamage(engine.Player.Id, engine.Player.Name, dealt3, isPiercing: false, isCrit: isCrit3, Name);
|
||||
engine.Log.AddFatal($"[{Name}] TRIGGERED PROCESS CRASH! Massive {dealt3} damage dealt!");
|
||||
break;
|
||||
}
|
||||
|
||||
_patternStep = (_patternStep + 1) % 4;
|
||||
DecideNextIntent(engine);
|
||||
}
|
||||
}
|
||||
|
||||
public class OffByOneBugEnemy : Enemy
|
||||
{
|
||||
private int _patternStep = 0;
|
||||
|
||||
public OffByOneBugEnemy()
|
||||
: base("Off-By-One Bug", 22, "🐛", "Iterative boundary bug indexing `for (int i = 0; i <= arr.Length; i++)`.")
|
||||
{
|
||||
}
|
||||
|
||||
public override void DecideNextIntent(CombatEngine engine)
|
||||
{
|
||||
CurrentIntent = _patternStep switch
|
||||
{
|
||||
0 => new Intent(IntentType.Attack, 3, 3, "IndexOutOfRangeException (3x3 dmg)"),
|
||||
1 => new Intent(IntentType.Defend, 6, 1, "Loop Boundary Shield (+6 Shield)"),
|
||||
_ => new Intent(IntentType.Attack, 4, 2, "Array Overflow (4x2 dmg)")
|
||||
};
|
||||
}
|
||||
|
||||
public override void ExecuteIntent(CombatEngine engine)
|
||||
{
|
||||
if (CurrentIntent == null) return;
|
||||
|
||||
switch (_patternStep)
|
||||
{
|
||||
case 0:
|
||||
int totalDmg = 0;
|
||||
var isCrit = engine.Player.Statuses.Get(StatusType.Vulnerable) > 0;
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
var dmg = Statuses.CalculateOutgoingDamage(3);
|
||||
var dealt = engine.Player.TakeDamage(dmg);
|
||||
totalDmg += dealt;
|
||||
engine.NotifyDamage(engine.Player.Id, engine.Player.Name, dealt, isPiercing: false, isCrit: isCrit, $"{Name} #{i+1}");
|
||||
}
|
||||
engine.Log.AddWarning($"[{Name}] looped past boundary! 3 hits for total {totalDmg} damage!");
|
||||
break;
|
||||
case 1:
|
||||
GainShield(6);
|
||||
Statuses.Add(StatusType.Strength, 1);
|
||||
engine.NotifyShield(Id, Name, 6, Name);
|
||||
engine.Log.AddInfo($"[{Name}] reinforced iteration bound! Gained +6 Shield and +1 Strength.");
|
||||
break;
|
||||
}
|
||||
|
||||
_patternStep = (_patternStep + 1) % 2;
|
||||
DecideNextIntent(engine);
|
||||
}
|
||||
}
|
||||
|
||||
public class MemoryLeakSpectreEnemy : Enemy
|
||||
{
|
||||
private int _turn = 0;
|
||||
|
||||
public MemoryLeakSpectreEnemy()
|
||||
: base("Memory Leak Spectre", 34, "👻", "Unmanaged IDisposable and dangling event handlers slowly devouring RAM.")
|
||||
{
|
||||
}
|
||||
|
||||
public override void DecideNextIntent(CombatEngine engine)
|
||||
{
|
||||
CurrentIntent = (_turn % 3) switch
|
||||
{
|
||||
0 => new Intent(IntentType.Attack, 6, 1, "Heap Allocation Spurt (6 dmg)"),
|
||||
1 => new Intent(IntentType.Debuff, 0, 1, "Dangling Event Handler (+1 Memory Leak)", StatusType.MemoryLeak, 1),
|
||||
2 => new Intent(IntentType.Attack, 11, 1, "Out Of Memory Wave (11 dmg)"),
|
||||
_ => new Intent(IntentType.Attack, 6, 1, "Heap Bloat (6 dmg)")
|
||||
};
|
||||
}
|
||||
|
||||
public override void ExecuteIntent(CombatEngine engine)
|
||||
{
|
||||
if (CurrentIntent == null) return;
|
||||
|
||||
switch (_turn % 3)
|
||||
{
|
||||
case 0:
|
||||
var dmg0 = Statuses.CalculateOutgoingDamage(6);
|
||||
var isCrit0 = engine.Player.Statuses.Get(StatusType.Vulnerable) > 0;
|
||||
var dealt0 = engine.Player.TakeDamage(dmg0);
|
||||
engine.NotifyDamage(engine.Player.Id, engine.Player.Name, dealt0, isPiercing: false, isCrit: isCrit0, Name);
|
||||
engine.Log.AddWarning($"[{Name}] allocated 200MB unmanaged memory! Dealt {dealt0} dmg.");
|
||||
break;
|
||||
case 1:
|
||||
engine.Player.Statuses.Add(StatusType.MemoryLeak, 1);
|
||||
engine.Log.AddError($"[{Name}] hooked persistent event handler! Player gained +1 Memory Leak.");
|
||||
break;
|
||||
case 2:
|
||||
var dmg2 = Statuses.CalculateOutgoingDamage(11);
|
||||
var isCrit2 = engine.Player.Statuses.Get(StatusType.Vulnerable) > 0;
|
||||
var dealt2 = engine.Player.TakeDamage(dmg2);
|
||||
engine.NotifyDamage(engine.Player.Id, engine.Player.Name, dealt2, isPiercing: false, isCrit: isCrit2, Name);
|
||||
engine.Log.AddFatal($"[{Name}] triggered GC Thrashing OOM! Dealt {dealt2} damage.");
|
||||
break;
|
||||
}
|
||||
|
||||
_turn++;
|
||||
DecideNextIntent(engine);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using SlaySharp.Core.Models;
|
||||
|
||||
namespace SlaySharp.Core.Catalogs;
|
||||
|
||||
public static class StarterDecks
|
||||
{
|
||||
public static List<Card> CreateCsharpArchitectDeck()
|
||||
{
|
||||
return new List<Card>
|
||||
{
|
||||
new ConsoleWriteLineCard(),
|
||||
new ConsoleWriteLineCard(),
|
||||
new ConsoleWriteLineCard(),
|
||||
new ConsoleWriteLineCard(),
|
||||
new TryCatchCard(),
|
||||
new TryCatchCard(),
|
||||
new TryCatchCard(),
|
||||
new TryCatchCard(),
|
||||
new GcCollectCard(),
|
||||
new SpanSliceCard(),
|
||||
new LinqWhereCard(),
|
||||
new TieredCompilationCard()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
using SlaySharp.Core.Enums;
|
||||
using SlaySharp.Core.Models;
|
||||
|
||||
namespace SlaySharp.Core.Combat;
|
||||
|
||||
public class CombatEngine
|
||||
{
|
||||
public Player Player { get; private set; } = null!;
|
||||
public List<Enemy> Enemies { get; private set; } = new();
|
||||
public int TurnNumber { get; private set; }
|
||||
public CombatPhase Phase { get; private set; } = CombatPhase.PlayerTurn;
|
||||
public CombatLog Log { get; } = new();
|
||||
public string? ActiveActingEnemyId { get; private set; }
|
||||
|
||||
public event Action? OnStateChanged;
|
||||
public event Action<DamageEvent>? OnDamageDealt;
|
||||
public event Action<ShieldEvent>? OnShieldGained;
|
||||
public event Action<CardPlayAnimationEvent>? OnCardPlayAnimation;
|
||||
|
||||
public void NotifyDamage(string targetId, string targetName, int amount, bool isPiercing, bool isCrit, string sourceName)
|
||||
{
|
||||
OnDamageDealt?.Invoke(new DamageEvent(targetId, targetName, amount, isPiercing, isCrit, sourceName));
|
||||
}
|
||||
|
||||
public void NotifyShield(string targetId, string targetName, int amount, string sourceName)
|
||||
{
|
||||
OnShieldGained?.Invoke(new ShieldEvent(targetId, targetName, amount, sourceName));
|
||||
}
|
||||
|
||||
public void StartCombat(Player player, List<Enemy> enemies)
|
||||
{
|
||||
Player = player;
|
||||
Enemies = enemies;
|
||||
TurnNumber = 1;
|
||||
Phase = CombatPhase.PlayerTurn;
|
||||
ActiveActingEnemyId = null;
|
||||
|
||||
Log.Clear();
|
||||
Log.AddInfo($"=== Starting Incident Resolution: {enemies.Count} Bugs Detected ===");
|
||||
|
||||
Player.InitCombatDeck();
|
||||
|
||||
foreach (var enemy in Enemies)
|
||||
{
|
||||
enemy.Statuses.Clear();
|
||||
enemy.Shield = 0;
|
||||
enemy.DecideNextIntent(this);
|
||||
}
|
||||
|
||||
foreach (var relic in Player.Relics)
|
||||
{
|
||||
relic.OnCombatStart(this);
|
||||
}
|
||||
|
||||
StartPlayerTurn(isFirstTurn: true);
|
||||
}
|
||||
|
||||
public void StartPlayerTurn(bool isFirstTurn = false)
|
||||
{
|
||||
Phase = CombatPhase.PlayerTurn;
|
||||
ActiveActingEnemyId = null;
|
||||
Player.ResetShield();
|
||||
Player.ResetTurnEnergy();
|
||||
|
||||
Log.AddInfo($"--- Turn {TurnNumber}: Player Execution Phase ---");
|
||||
|
||||
foreach (var relic in Player.Relics)
|
||||
{
|
||||
relic.OnTurnStart(this);
|
||||
}
|
||||
|
||||
int drawCount = 5;
|
||||
Player.DrawCards(drawCount, Log);
|
||||
|
||||
NotifyStateChanged();
|
||||
}
|
||||
|
||||
public bool PlayCard(Card card, Character? target = null)
|
||||
{
|
||||
if (Phase != CombatPhase.PlayerTurn)
|
||||
{
|
||||
Log.AddWarning("Cannot play cards outside of player turn.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Player.Hand.Contains(card))
|
||||
{
|
||||
Log.AddError($"Card '{card.Name}' is not in player hand.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Player.CurrentEnergy < card.Cost)
|
||||
{
|
||||
Log.AddWarning($"Insufficient CPU cycles! Card requires {card.Cost} CPU, available: {Player.CurrentEnergy}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (card.TargetType == TargetType.SingleEnemy)
|
||||
{
|
||||
if (target is not Enemy enemyTarget || enemyTarget.IsDead || !Enemies.Contains(enemyTarget))
|
||||
{
|
||||
Log.AddWarning("Please select a valid bug target for this card.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Player.CurrentEnergy -= card.Cost;
|
||||
Player.Hand.Remove(card);
|
||||
|
||||
OnCardPlayAnimation?.Invoke(new CardPlayAnimationEvent(
|
||||
card.Id,
|
||||
card.Name,
|
||||
target?.Id,
|
||||
card.TargetType == TargetType.AllEnemies
|
||||
));
|
||||
|
||||
try
|
||||
{
|
||||
card.ApplyEffect(this, Player, target);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.AddFatal($"Runtime Exception while executing card effect: {ex.Message}");
|
||||
}
|
||||
|
||||
foreach (var relic in Player.Relics)
|
||||
{
|
||||
relic.OnCardPlayed(this, card);
|
||||
}
|
||||
|
||||
if (card.Exhausts)
|
||||
{
|
||||
Player.ExhaustPile.Add(card);
|
||||
Log.AddInfo($"[Exhaust] Card '{card.Name}' was finalized and unallocated (Exhaust Pile).");
|
||||
}
|
||||
else
|
||||
{
|
||||
Player.DiscardPile.Add(card);
|
||||
}
|
||||
|
||||
CleanupDeadEnemies();
|
||||
CheckBattleConditions();
|
||||
NotifyStateChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void EndPlayerTurn()
|
||||
{
|
||||
_ = EndPlayerTurnAsync(stepDelayMs: 0);
|
||||
}
|
||||
|
||||
public async Task EndPlayerTurnAsync(
|
||||
Func<Enemy, Task>? onBeforeEnemyActs = null,
|
||||
Func<Enemy, Task>? onAfterEnemyActs = null,
|
||||
int stepDelayMs = 1000)
|
||||
{
|
||||
if (Phase != CombatPhase.PlayerTurn) return;
|
||||
|
||||
Log.AddInfo("Ending player turn. Discarding active memory...");
|
||||
Player.DiscardHand(Log);
|
||||
NotifyStateChanged();
|
||||
|
||||
if (stepDelayMs > 0)
|
||||
{
|
||||
await Task.Delay(300);
|
||||
}
|
||||
|
||||
Player.Statuses.OnTurnEnd(
|
||||
logMsg => Log.AddWarning(logMsg),
|
||||
dmg =>
|
||||
{
|
||||
var taken = Player.TakeDamage(dmg, isPiercing: true);
|
||||
NotifyDamage(Player.Id, Player.Name, taken, isPiercing: true, isCrit: false, "MemoryLeak");
|
||||
}
|
||||
);
|
||||
|
||||
foreach (var relic in Player.Relics)
|
||||
{
|
||||
relic.OnTurnEnd(this);
|
||||
}
|
||||
|
||||
if (Player.IsDead)
|
||||
{
|
||||
Phase = CombatPhase.Defeat;
|
||||
Log.AddFatal("=== PRODUCTION CRASH: Uptime 0%, All Services Offline ===");
|
||||
NotifyStateChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
CheckBattleConditions();
|
||||
if (Phase == CombatPhase.Victory) return;
|
||||
|
||||
await ExecuteEnemyTurnsAsync(onBeforeEnemyActs, onAfterEnemyActs, stepDelayMs);
|
||||
}
|
||||
|
||||
private async Task ExecuteEnemyTurnsAsync(
|
||||
Func<Enemy, Task>? onBeforeEnemyActs = null,
|
||||
Func<Enemy, Task>? onAfterEnemyActs = null,
|
||||
int stepDelayMs = 1000)
|
||||
{
|
||||
Phase = CombatPhase.EnemyTurn;
|
||||
Log.AddInfo($"--- Turn {TurnNumber}: Bug Execution Phase ---");
|
||||
NotifyStateChanged();
|
||||
|
||||
if (stepDelayMs > 0)
|
||||
{
|
||||
await Task.Delay(400);
|
||||
}
|
||||
|
||||
foreach (var enemy in Enemies.ToList())
|
||||
{
|
||||
if (enemy.IsDead) continue;
|
||||
|
||||
ActiveActingEnemyId = enemy.Id;
|
||||
NotifyStateChanged();
|
||||
|
||||
if (onBeforeEnemyActs != null)
|
||||
{
|
||||
await onBeforeEnemyActs(enemy);
|
||||
}
|
||||
|
||||
if (stepDelayMs > 0)
|
||||
{
|
||||
await Task.Delay(stepDelayMs / 2);
|
||||
}
|
||||
|
||||
enemy.ResetShield();
|
||||
enemy.Statuses.OnTurnEnd(
|
||||
logMsg => Log.AddInfo($"[{enemy.Name}] {logMsg}"),
|
||||
dmg =>
|
||||
{
|
||||
var taken = enemy.TakeDamage(dmg, isPiercing: true);
|
||||
NotifyDamage(enemy.Id, enemy.Name, taken, isPiercing: true, isCrit: false, "Debuff");
|
||||
}
|
||||
);
|
||||
|
||||
if (enemy.IsDead)
|
||||
{
|
||||
CleanupDeadEnemies();
|
||||
ActiveActingEnemyId = null;
|
||||
NotifyStateChanged();
|
||||
continue;
|
||||
}
|
||||
|
||||
enemy.ExecuteIntent(this);
|
||||
NotifyStateChanged();
|
||||
|
||||
if (onAfterEnemyActs != null)
|
||||
{
|
||||
await onAfterEnemyActs(enemy);
|
||||
}
|
||||
|
||||
if (stepDelayMs > 0)
|
||||
{
|
||||
await Task.Delay(stepDelayMs / 2);
|
||||
}
|
||||
|
||||
if (Player.IsDead)
|
||||
{
|
||||
Phase = CombatPhase.Defeat;
|
||||
ActiveActingEnemyId = null;
|
||||
Log.AddFatal("=== PRODUCTION CRASH: Uptime 0%, All Services Offline ===");
|
||||
NotifyStateChanged();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ActiveActingEnemyId = null;
|
||||
CleanupDeadEnemies();
|
||||
CheckBattleConditions();
|
||||
|
||||
if (Phase != CombatPhase.Victory && Phase != CombatPhase.Defeat)
|
||||
{
|
||||
if (stepDelayMs > 0)
|
||||
{
|
||||
await Task.Delay(350);
|
||||
}
|
||||
TurnNumber++;
|
||||
StartPlayerTurn();
|
||||
}
|
||||
}
|
||||
|
||||
private void CleanupDeadEnemies()
|
||||
{
|
||||
for (int i = Enemies.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (Enemies[i].IsDead)
|
||||
{
|
||||
Log.AddSuccess($"[RESOLVED] Bug '{Enemies[i].Name}' was successfully patched and disposed!");
|
||||
Enemies.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckBattleConditions()
|
||||
{
|
||||
if (Player.IsDead)
|
||||
{
|
||||
Phase = CombatPhase.Defeat;
|
||||
}
|
||||
else if (Enemies.Count == 0)
|
||||
{
|
||||
Phase = CombatPhase.Victory;
|
||||
Log.AddSuccess("=== ALL BUGS RESOLVED! Build Deployed Successfully! ===");
|
||||
}
|
||||
}
|
||||
|
||||
public void NotifyStateChanged() => OnStateChanged?.Invoke();
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace SlaySharp.Core.Combat;
|
||||
|
||||
public record DamageEvent(
|
||||
string TargetId,
|
||||
string TargetName,
|
||||
int Amount,
|
||||
bool IsPiercing,
|
||||
bool IsCrit,
|
||||
string SourceName
|
||||
);
|
||||
|
||||
public record ShieldEvent(
|
||||
string TargetId,
|
||||
string TargetName,
|
||||
int Amount,
|
||||
string SourceName
|
||||
);
|
||||
|
||||
public record CardPlayAnimationEvent(
|
||||
string CardId,
|
||||
string CardName,
|
||||
string? TargetId,
|
||||
bool IsAoe
|
||||
);
|
||||
@@ -0,0 +1,41 @@
|
||||
namespace SlaySharp.Core.Combat;
|
||||
|
||||
public enum LogLevel
|
||||
{
|
||||
Info,
|
||||
Success,
|
||||
Warning,
|
||||
Error,
|
||||
Fatal
|
||||
}
|
||||
|
||||
public record LogEntry(DateTime Timestamp, LogLevel Level, string Message)
|
||||
{
|
||||
public string FormattedTime => Timestamp.ToString("HH:mm:ss.fff");
|
||||
}
|
||||
|
||||
public class CombatLog
|
||||
{
|
||||
private readonly List<LogEntry> _entries = new();
|
||||
public IReadOnlyList<LogEntry> Entries => _entries;
|
||||
|
||||
public event Action? OnLogAdded;
|
||||
|
||||
public void Add(LogLevel level, string message)
|
||||
{
|
||||
_entries.Add(new LogEntry(DateTime.Now, level, message));
|
||||
OnLogAdded?.Invoke();
|
||||
}
|
||||
|
||||
public void AddInfo(string message) => Add(LogLevel.Info, message);
|
||||
public void AddSuccess(string message) => Add(LogLevel.Success, message);
|
||||
public void AddWarning(string message) => Add(LogLevel.Warning, message);
|
||||
public void AddError(string message) => Add(LogLevel.Error, message);
|
||||
public void AddFatal(string message) => Add(LogLevel.Fatal, message);
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_entries.Clear();
|
||||
OnLogAdded?.Invoke();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
namespace SlaySharp.Core.Enums;
|
||||
|
||||
public enum CardType
|
||||
{
|
||||
Attack,
|
||||
Skill,
|
||||
Power,
|
||||
Curse
|
||||
}
|
||||
|
||||
public enum CardRarity
|
||||
{
|
||||
Basic,
|
||||
Common,
|
||||
Uncommon,
|
||||
Rare,
|
||||
Curse
|
||||
}
|
||||
|
||||
public enum TargetType
|
||||
{
|
||||
SingleEnemy,
|
||||
AllEnemies,
|
||||
Self,
|
||||
None
|
||||
}
|
||||
|
||||
public enum StatusType
|
||||
{
|
||||
Strength,
|
||||
Vulnerable,
|
||||
Weak,
|
||||
MemoryLeak,
|
||||
JitTier,
|
||||
ShieldRetain,
|
||||
Overclocked
|
||||
}
|
||||
|
||||
public enum IntentType
|
||||
{
|
||||
Attack,
|
||||
Defend,
|
||||
Buff,
|
||||
Debuff,
|
||||
AttackDefend,
|
||||
Special
|
||||
}
|
||||
|
||||
public enum CombatPhase
|
||||
{
|
||||
PlayerTurn,
|
||||
EnemyTurn,
|
||||
Victory,
|
||||
Defeat
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using SlaySharp.Core.Combat;
|
||||
using SlaySharp.Core.Enums;
|
||||
|
||||
namespace SlaySharp.Core.Models;
|
||||
|
||||
public abstract class Card
|
||||
{
|
||||
public string Id { get; } = Guid.NewGuid().ToString("N")[..8];
|
||||
public string Name { get; protected set; }
|
||||
public string SyntaxSignature { get; protected set; }
|
||||
public string CodeSnippet { get; protected set; }
|
||||
public string Description { get; protected set; }
|
||||
public int Cost { get; protected set; }
|
||||
public CardType Type { get; }
|
||||
public CardRarity Rarity { get; }
|
||||
public TargetType TargetType { get; }
|
||||
public bool Upgraded { get; protected set; }
|
||||
public bool Exhausts { get; protected set; }
|
||||
|
||||
protected Card(
|
||||
string name,
|
||||
string syntaxSignature,
|
||||
string codeSnippet,
|
||||
string description,
|
||||
int cost,
|
||||
CardType type,
|
||||
CardRarity rarity,
|
||||
TargetType targetType,
|
||||
bool exhausts = false)
|
||||
{
|
||||
Name = name;
|
||||
SyntaxSignature = syntaxSignature;
|
||||
CodeSnippet = codeSnippet;
|
||||
Description = description;
|
||||
Cost = cost;
|
||||
Type = type;
|
||||
Rarity = rarity;
|
||||
TargetType = targetType;
|
||||
Exhausts = exhausts;
|
||||
}
|
||||
|
||||
public virtual bool CanPlay(CombatEngine engine)
|
||||
{
|
||||
return engine.Player.CurrentEnergy >= Cost;
|
||||
}
|
||||
|
||||
public abstract void ApplyEffect(CombatEngine engine, Player player, Character? target);
|
||||
|
||||
public virtual void Upgrade()
|
||||
{
|
||||
if (Upgraded) return;
|
||||
Upgraded = true;
|
||||
Name += "+";
|
||||
OnUpgrade();
|
||||
}
|
||||
|
||||
protected virtual void OnUpgrade() { }
|
||||
|
||||
public abstract Card Clone();
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using SlaySharp.Core.Enums;
|
||||
|
||||
namespace SlaySharp.Core.Models;
|
||||
|
||||
public abstract class Character
|
||||
{
|
||||
public string Id { get; } = Guid.NewGuid().ToString("N")[..8];
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public int MaxHp { get; set; }
|
||||
public int CurrentHp { get; set; }
|
||||
public int Shield { get; set; }
|
||||
public StatusCollection Statuses { get; } = new();
|
||||
|
||||
public bool IsDead => CurrentHp <= 0;
|
||||
|
||||
protected Character(string name, int maxHp)
|
||||
{
|
||||
Name = name;
|
||||
MaxHp = maxHp;
|
||||
CurrentHp = maxHp;
|
||||
}
|
||||
|
||||
public virtual int TakeDamage(int rawAmount, bool isPiercing = false)
|
||||
{
|
||||
if (IsDead || rawAmount <= 0) return 0;
|
||||
|
||||
var modifiedAmount = Statuses.CalculateIncomingDamage(rawAmount);
|
||||
var damageToHp = modifiedAmount;
|
||||
|
||||
if (!isPiercing && Shield > 0)
|
||||
{
|
||||
if (Shield >= modifiedAmount)
|
||||
{
|
||||
Shield -= modifiedAmount;
|
||||
damageToHp = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
damageToHp = modifiedAmount - Shield;
|
||||
Shield = 0;
|
||||
}
|
||||
}
|
||||
|
||||
CurrentHp = Math.Max(0, CurrentHp - damageToHp);
|
||||
return damageToHp;
|
||||
}
|
||||
|
||||
public virtual void GainShield(int amount)
|
||||
{
|
||||
if (IsDead || amount <= 0) return;
|
||||
Shield += amount;
|
||||
}
|
||||
|
||||
public virtual void ResetShield()
|
||||
{
|
||||
if (Statuses.Get(StatusType.ShieldRetain) == 0)
|
||||
{
|
||||
Shield = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Heal(int amount)
|
||||
{
|
||||
if (IsDead || amount <= 0) return;
|
||||
CurrentHp = Math.Min(MaxHp, CurrentHp + amount);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using SlaySharp.Core.Combat;
|
||||
|
||||
namespace SlaySharp.Core.Models;
|
||||
|
||||
public abstract class Enemy : Character
|
||||
{
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public string Icon { get; set; } = "👾";
|
||||
public Intent? CurrentIntent { get; protected set; }
|
||||
|
||||
protected Enemy(string name, int maxHp, string icon = "👾", string description = "")
|
||||
: base(name, maxHp)
|
||||
{
|
||||
Icon = icon;
|
||||
Description = description;
|
||||
}
|
||||
|
||||
public abstract void DecideNextIntent(CombatEngine engine);
|
||||
public abstract void ExecuteIntent(CombatEngine engine);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using SlaySharp.Core.Enums;
|
||||
|
||||
namespace SlaySharp.Core.Models;
|
||||
|
||||
public record Intent(
|
||||
IntentType Type,
|
||||
int PrimaryValue,
|
||||
int Hits = 1,
|
||||
string Description = "",
|
||||
StatusType? DebuffToApply = null,
|
||||
int DebuffStacks = 0
|
||||
)
|
||||
{
|
||||
public string DisplayText
|
||||
{
|
||||
get
|
||||
{
|
||||
return Type switch
|
||||
{
|
||||
IntentType.Attack => Hits > 1 ? $"{PrimaryValue} x {Hits} dmg" : $"{PrimaryValue} dmg",
|
||||
IntentType.Defend => $"+{PrimaryValue} Shield",
|
||||
IntentType.AttackDefend => $"{PrimaryValue} dmg & +{PrimaryValue} Shield",
|
||||
IntentType.Debuff => DebuffToApply.HasValue ? $"Debuff: {DebuffToApply.Value} ({DebuffStacks})" : "Debuff",
|
||||
IntentType.Buff => $"+{PrimaryValue} Buff",
|
||||
IntentType.Special => Description,
|
||||
_ => Description
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using SlaySharp.Core.Combat;
|
||||
using SlaySharp.Core.Enums;
|
||||
using SlaySharp.Core.Relics;
|
||||
|
||||
namespace SlaySharp.Core.Models;
|
||||
|
||||
public class Player : Character
|
||||
{
|
||||
public int MaxEnergy { get; set; } = 3;
|
||||
public int CurrentEnergy { get; set; } = 3;
|
||||
|
||||
public List<Card> MasterDeck { get; } = new();
|
||||
public List<Card> DrawPile { get; } = new();
|
||||
public List<Card> Hand { get; } = new();
|
||||
public List<Card> DiscardPile { get; } = new();
|
||||
public List<Card> ExhaustPile { get; } = new();
|
||||
public List<Relic> Relics { get; } = new();
|
||||
|
||||
private readonly Random _rng;
|
||||
|
||||
public Player(string name, int maxHp, int maxEnergy = 3, int? seed = null)
|
||||
: base(name, maxHp)
|
||||
{
|
||||
MaxEnergy = maxEnergy;
|
||||
CurrentEnergy = maxEnergy;
|
||||
_rng = seed.HasValue ? new Random(seed.Value) : new Random();
|
||||
}
|
||||
|
||||
public void InitCombatDeck()
|
||||
{
|
||||
DrawPile.Clear();
|
||||
Hand.Clear();
|
||||
DiscardPile.Clear();
|
||||
ExhaustPile.Clear();
|
||||
Shield = 0;
|
||||
Statuses.Clear();
|
||||
|
||||
foreach (var card in MasterDeck)
|
||||
{
|
||||
DrawPile.Add(card.Clone());
|
||||
}
|
||||
|
||||
ShuffleDrawPile();
|
||||
}
|
||||
|
||||
public void ShuffleDrawPile()
|
||||
{
|
||||
int n = DrawPile.Count;
|
||||
while (n > 1)
|
||||
{
|
||||
n--;
|
||||
int k = _rng.Next(n + 1);
|
||||
(DrawPile[k], DrawPile[n]) = (DrawPile[n], DrawPile[k]);
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawCards(int count, CombatLog? log = null)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (Hand.Count >= 10)
|
||||
{
|
||||
log?.AddWarning("Hand buffer overflow! Max 10 cards in hand.");
|
||||
break;
|
||||
}
|
||||
|
||||
if (DrawPile.Count == 0)
|
||||
{
|
||||
if (DiscardPile.Count == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
log?.AddInfo("Recompiling draw pile from deallocated heap (Discard Pile)...");
|
||||
DrawPile.AddRange(DiscardPile);
|
||||
DiscardPile.Clear();
|
||||
ShuffleDrawPile();
|
||||
}
|
||||
|
||||
if (DrawPile.Count > 0)
|
||||
{
|
||||
var card = DrawPile[0];
|
||||
DrawPile.RemoveAt(0);
|
||||
Hand.Add(card);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DiscardHand(CombatLog? log = null)
|
||||
{
|
||||
foreach (var card in Hand)
|
||||
{
|
||||
DiscardPile.Add(card);
|
||||
}
|
||||
Hand.Clear();
|
||||
}
|
||||
|
||||
public void ResetTurnEnergy()
|
||||
{
|
||||
CurrentEnergy = MaxEnergy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
using SlaySharp.Core.Enums;
|
||||
|
||||
namespace SlaySharp.Core.Models;
|
||||
|
||||
public class StatusCollection
|
||||
{
|
||||
private readonly Dictionary<StatusType, int> _statuses = new();
|
||||
|
||||
public IReadOnlyDictionary<StatusType, int> All => _statuses;
|
||||
|
||||
public int Get(StatusType type) => _statuses.TryGetValue(type, out var val) ? val : 0;
|
||||
|
||||
public void Add(StatusType type, int amount)
|
||||
{
|
||||
if (amount <= 0) return;
|
||||
_statuses[type] = Get(type) + amount;
|
||||
}
|
||||
|
||||
public void Remove(StatusType type, int amount)
|
||||
{
|
||||
if (!_statuses.TryGetValue(type, out var current)) return;
|
||||
var newVal = current - amount;
|
||||
if (newVal <= 0)
|
||||
{
|
||||
_statuses.Remove(type);
|
||||
}
|
||||
else
|
||||
{
|
||||
_statuses[type] = newVal;
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear() => _statuses.Clear();
|
||||
|
||||
public int CalculateOutgoingDamage(int baseDamage)
|
||||
{
|
||||
var dmg = (double)baseDamage;
|
||||
dmg += Get(StatusType.Strength);
|
||||
|
||||
if (Get(StatusType.Weak) > 0)
|
||||
{
|
||||
dmg *= 0.75;
|
||||
}
|
||||
|
||||
return Math.Max(0, (int)Math.Round(dmg, MidpointRounding.AwayFromZero));
|
||||
}
|
||||
|
||||
public int CalculateIncomingDamage(int incomingDamage)
|
||||
{
|
||||
var dmg = (double)incomingDamage;
|
||||
if (Get(StatusType.Vulnerable) > 0)
|
||||
{
|
||||
dmg *= 1.5;
|
||||
}
|
||||
|
||||
return Math.Max(0, (int)Math.Round(dmg, MidpointRounding.AwayFromZero));
|
||||
}
|
||||
|
||||
public void OnTurnEnd(Action<string>? logAction = null, Action<int>? takeDamageAction = null)
|
||||
{
|
||||
// Decay standard turn debuffs
|
||||
if (Get(StatusType.Vulnerable) > 0)
|
||||
{
|
||||
Remove(StatusType.Vulnerable, 1);
|
||||
}
|
||||
if (Get(StatusType.Weak) > 0)
|
||||
{
|
||||
Remove(StatusType.Weak, 1);
|
||||
}
|
||||
|
||||
// Memory leak causes damage over time
|
||||
var leaks = Get(StatusType.MemoryLeak);
|
||||
if (leaks > 0)
|
||||
{
|
||||
logAction?.Invoke($"MemoryLeak triggered! Taking {leaks} damage from unmanaged resources.");
|
||||
takeDamageAction?.Invoke(leaks);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using SlaySharp.Core.Combat;
|
||||
|
||||
namespace SlaySharp.Core.Relics;
|
||||
|
||||
public abstract class Relic
|
||||
{
|
||||
public string Id { get; }
|
||||
public string Name { get; }
|
||||
public string PackageVersion { get; }
|
||||
public string Description { get; }
|
||||
public string Icon { get; }
|
||||
|
||||
protected Relic(string id, string name, string packageVersion, string description, string icon = "📦")
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
PackageVersion = packageVersion;
|
||||
Description = description;
|
||||
Icon = icon;
|
||||
}
|
||||
|
||||
public virtual void OnCombatStart(CombatEngine engine) { }
|
||||
public virtual void OnTurnStart(CombatEngine engine) { }
|
||||
public virtual void OnTurnEnd(CombatEngine engine) { }
|
||||
public virtual void OnCardPlayed(CombatEngine engine, Models.Card card) { }
|
||||
}
|
||||
|
||||
public class NewtonsoftJsonRelic : Relic
|
||||
{
|
||||
public NewtonsoftJsonRelic() : base(
|
||||
"Newtonsoft.Json",
|
||||
"Newtonsoft.Json",
|
||||
"v13.0.3",
|
||||
"Draw +1 extra card at the start of combat (Memory deserialization buffer).",
|
||||
"📜"
|
||||
) { }
|
||||
|
||||
public override void OnCombatStart(CombatEngine engine)
|
||||
{
|
||||
engine.Player.DrawCards(1, engine.Log);
|
||||
}
|
||||
}
|
||||
|
||||
public class BenchmarkDotNetRelic : Relic
|
||||
{
|
||||
private int _turnCounter;
|
||||
|
||||
public BenchmarkDotNetRelic() : base(
|
||||
"BenchmarkDotNet",
|
||||
"BenchmarkDotNet",
|
||||
"v0.13.12",
|
||||
"Every 2nd turn, micro-benchmark optimizations grant +1 extra CPU Cycle.",
|
||||
"⚡"
|
||||
) { }
|
||||
|
||||
public override void OnTurnStart(CombatEngine engine)
|
||||
{
|
||||
_turnCounter++;
|
||||
if (_turnCounter % 2 == 0)
|
||||
{
|
||||
engine.Player.CurrentEnergy += 1;
|
||||
engine.Log.AddInfo($"[BenchmarkDotNet] Micro-benchmark boost! +1 CPU Cycle (Available: {engine.Player.CurrentEnergy})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class SerilogRelic : Relic
|
||||
{
|
||||
public SerilogRelic() : base(
|
||||
"Serilog",
|
||||
"Serilog",
|
||||
"v4.0.0",
|
||||
"Whenever you play an Attack card, gain +2 Shield (Structured diagnostic logging).",
|
||||
"📊"
|
||||
) { }
|
||||
|
||||
public override void OnCardPlayed(CombatEngine engine, Models.Card card)
|
||||
{
|
||||
if (card.Type == Enums.CardType.Attack)
|
||||
{
|
||||
engine.Player.GainShield(2);
|
||||
engine.Log.AddInfo("[Serilog] Diagnostic trace emitted: +2 Shield.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user