impl
This commit is contained in:
@@ -26,13 +26,13 @@ public class ConsoleWriteLineCard : Card
|
||||
CodeSnippet = "target.Hit(9);";
|
||||
}
|
||||
|
||||
public override (int minDamage, int maxDamage, int hits, bool isPiercing) GetDamageProfile(CombatEngine engine, Character? target = null)
|
||||
=> (_damage, _damage, 1, false);
|
||||
|
||||
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);
|
||||
var (dealt, isCrit) = engine.CalculateAndDealDamage(player, target, _damage, isPiercing: false, Name);
|
||||
engine.Log.AddInfo($"[Console.WriteLine] {target.Name} took {dealt} dmg.");
|
||||
}
|
||||
|
||||
@@ -68,6 +68,8 @@ public class TryCatchCard : Card
|
||||
CodeSnippet = "shield += 8;";
|
||||
}
|
||||
|
||||
public override int GetShieldProfile(CombatEngine engine) => _shield;
|
||||
|
||||
public override void ApplyEffect(CombatEngine engine, Player player, Character? target)
|
||||
{
|
||||
player.GainShield(_shield);
|
||||
@@ -85,6 +87,91 @@ public class TryCatchCard : Card
|
||||
};
|
||||
}
|
||||
|
||||
public class RandomNextCard : Card
|
||||
{
|
||||
private int _min = 3;
|
||||
private int _max = 14;
|
||||
|
||||
public RandomNextCard() : base(
|
||||
"Random.Shared.Next",
|
||||
"Random.Shared.Next(3, 15);",
|
||||
"target.Hit(Random(3..14));",
|
||||
"Deal 3 to 14 random damage.",
|
||||
cost: 1,
|
||||
type: CardType.Attack,
|
||||
rarity: CardRarity.Common,
|
||||
targetType: TargetType.SingleEnemy
|
||||
) { }
|
||||
|
||||
protected override void OnUpgrade()
|
||||
{
|
||||
_min = 6;
|
||||
_max = 20;
|
||||
Description = "Deal 6 to 20 random damage.";
|
||||
CodeSnippet = "target.Hit(Random(6..20));";
|
||||
}
|
||||
|
||||
public override (int minDamage, int maxDamage, int hits, bool isPiercing) GetDamageProfile(CombatEngine engine, Character? target = null)
|
||||
=> (_min, _max, 1, false);
|
||||
|
||||
public override void ApplyEffect(CombatEngine engine, Player player, Character? target)
|
||||
{
|
||||
if (target == null) return;
|
||||
var rolledDmg = Random.Shared.Next(_min, _max + 1);
|
||||
var (dealt, isCrit) = engine.CalculateAndDealDamage(player, target, rolledDmg, isPiercing: false, Name);
|
||||
engine.Log.AddSuccess($"[Random.Shared] 🎲 ROLLED {rolledDmg} base dmg! Dealt {dealt} total to {target.Name}.");
|
||||
}
|
||||
|
||||
public override Card Clone() => new RandomNextCard
|
||||
{
|
||||
Upgraded = this.Upgraded,
|
||||
Name = this.Name,
|
||||
Description = this.Description,
|
||||
_min = this._min,
|
||||
_max = this._max,
|
||||
CodeSnippet = this.CodeSnippet
|
||||
};
|
||||
}
|
||||
|
||||
public class DynamicExpandoCard : Card
|
||||
{
|
||||
public DynamicExpandoCard() : base(
|
||||
"dynamic ExpandoObject",
|
||||
"dynamic obj = new ExpandoObject();",
|
||||
"Hand.Add(RandomCard(Cost: 0));",
|
||||
"Add a random 0-cost card to your hand.",
|
||||
cost: 1,
|
||||
type: CardType.Skill,
|
||||
rarity: CardRarity.Uncommon,
|
||||
targetType: TargetType.Self
|
||||
) { }
|
||||
|
||||
protected override void OnUpgrade()
|
||||
{
|
||||
Description = "Add a random Upgraded 0-cost card to hand.";
|
||||
CodeSnippet = "Hand.Add(RandomUpgradedCard(0));";
|
||||
}
|
||||
|
||||
public override void ApplyEffect(CombatEngine engine, Player player, Character? target)
|
||||
{
|
||||
var randomCard = CardCatalog.GetRandomCard();
|
||||
if (Upgraded) randomCard.Upgrade();
|
||||
|
||||
var cloned = randomCard.Clone();
|
||||
cloned.SetCostOverride(0);
|
||||
|
||||
player.Hand.Add(cloned);
|
||||
engine.Log.AddSuccess($"[dynamic ExpandoObject] 🎲 Runtime IL Emit generated '{cloned.Name}' (0 CPU) into hand!");
|
||||
}
|
||||
|
||||
public override Card Clone() => new DynamicExpandoCard
|
||||
{
|
||||
Upgraded = this.Upgraded,
|
||||
Name = this.Name,
|
||||
Description = this.Description
|
||||
};
|
||||
}
|
||||
|
||||
public class GcCollectCard : Card
|
||||
{
|
||||
private int _baseDamage = 8;
|
||||
@@ -107,15 +194,19 @@ public class GcCollectCard : Card
|
||||
CodeSnippet = "target.Hit(12 + Discard * 2);";
|
||||
}
|
||||
|
||||
public override (int minDamage, int maxDamage, int hits, bool isPiercing) GetDamageProfile(CombatEngine engine, Character? target = null)
|
||||
{
|
||||
var discardCount = engine.Player.DiscardPile.Count;
|
||||
var dmg = _baseDamage + (discardCount * 2);
|
||||
return (dmg, dmg, 1, false);
|
||||
}
|
||||
|
||||
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);
|
||||
var (dealt, isCrit) = engine.CalculateAndDealDamage(player, target, rawDmg, isPiercing: false, Name);
|
||||
engine.Log.AddSuccess($"[GC.Collect] Swept {discardCount} objects! Dealt {dealt} dmg.");
|
||||
}
|
||||
|
||||
@@ -151,13 +242,13 @@ public class SpanSliceCard : Card
|
||||
CodeSnippet = "target.DirectHit(11);";
|
||||
}
|
||||
|
||||
public override (int minDamage, int maxDamage, int hits, bool isPiercing) GetDamageProfile(CombatEngine engine, Character? target = null)
|
||||
=> (_damage, _damage, 1, true);
|
||||
|
||||
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);
|
||||
var (dealt, isCrit) = engine.CalculateAndDealDamage(player, target, _damage, isPiercing: true, Name);
|
||||
engine.Log.AddSuccess($"[Span.Slice] Pierced shield! Dealt {dealt} dmg.");
|
||||
}
|
||||
|
||||
@@ -193,14 +284,14 @@ public class ThrowExceptionCard : Card
|
||||
CodeSnippet = "target.Hit(22); // +1 Leak";
|
||||
}
|
||||
|
||||
public override (int minDamage, int maxDamage, int hits, bool isPiercing) GetDamageProfile(CombatEngine engine, Character? target = null)
|
||||
=> (_damage, _damage, 1, false);
|
||||
|
||||
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);
|
||||
var (dealt, isCrit) = engine.CalculateAndDealDamage(player, target, _damage, isPiercing: false, Name);
|
||||
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.");
|
||||
}
|
||||
|
||||
@@ -311,19 +402,19 @@ public class ParallelForEachCard : Card
|
||||
CodeSnippet = "target.Hit(5); // x3 hits";
|
||||
}
|
||||
|
||||
public override (int minDamage, int maxDamage, int hits, bool isPiercing) GetDamageProfile(CombatEngine engine, Character? target = null)
|
||||
=> (_dmgPerHit, _dmgPerHit, 3, false);
|
||||
|
||||
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);
|
||||
var (dealt, isCrit) = engine.CalculateAndDealDamage(player, target, _dmgPerHit, isPiercing: false, $"{Name} #{i+1}");
|
||||
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.");
|
||||
}
|
||||
@@ -360,6 +451,8 @@ public class LinqWhereCard : Card
|
||||
CodeSnippet = "shield += 9; Draw(1);";
|
||||
}
|
||||
|
||||
public override int GetShieldProfile(CombatEngine engine) => _shield;
|
||||
|
||||
public override void ApplyEffect(CombatEngine engine, Player player, Character? target)
|
||||
{
|
||||
player.GainShield(_shield);
|
||||
@@ -397,6 +490,8 @@ public class NullCoalescingCard : Card
|
||||
CodeSnippet = "ClearDebuffs(); shield += 8;";
|
||||
}
|
||||
|
||||
public override int GetShieldProfile(CombatEngine engine) => Upgraded ? 8 : 6;
|
||||
|
||||
public override void ApplyEffect(CombatEngine engine, Player player, Character? target)
|
||||
{
|
||||
var stacksToRemove = Upgraded ? 2 : 1;
|
||||
@@ -416,3 +511,51 @@ public class NullCoalescingCard : Card
|
||||
Description = this.Description
|
||||
};
|
||||
}
|
||||
|
||||
public static class CardCatalog
|
||||
{
|
||||
public static List<Func<Card>> AllCardFactories => new()
|
||||
{
|
||||
() => new ConsoleWriteLineCard(),
|
||||
() => new TryCatchCard(),
|
||||
() => new RandomNextCard(),
|
||||
() => new DynamicExpandoCard(),
|
||||
() => new GcCollectCard(),
|
||||
() => new SpanSliceCard(),
|
||||
() => new ThrowExceptionCard(),
|
||||
() => new TieredCompilationCard(),
|
||||
() => new ThreadSleepCard(),
|
||||
() => new ParallelForEachCard(),
|
||||
() => new LinqWhereCard(),
|
||||
() => new NullCoalescingCard()
|
||||
};
|
||||
|
||||
public static Card GetRandomCard()
|
||||
{
|
||||
var factories = AllCardFactories;
|
||||
var index = Random.Shared.Next(factories.Count);
|
||||
return factories[index]();
|
||||
}
|
||||
|
||||
public static List<Card> RollThreeCardRewards()
|
||||
{
|
||||
var pool = AllCardFactories;
|
||||
var chosen = new HashSet<int>();
|
||||
var result = new List<Card>();
|
||||
|
||||
while (result.Count < 3 && chosen.Count < pool.Count)
|
||||
{
|
||||
var idx = Random.Shared.Next(pool.Count);
|
||||
if (chosen.Add(idx))
|
||||
{
|
||||
var card = pool[idx]();
|
||||
if (Random.Shared.Next(100) < 25)
|
||||
{
|
||||
card.Upgrade();
|
||||
}
|
||||
result.Add(card);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,8 @@ public class NullReferenceExceptionEnemy : Enemy
|
||||
{
|
||||
private int _patternStep = 0;
|
||||
|
||||
public NullReferenceExceptionEnemy()
|
||||
: base("NullReferenceException", 45, "💥", "Unhandled null pointer reference causing sudden application crashes.")
|
||||
public NullReferenceExceptionEnemy(int hpBonus = 0)
|
||||
: base("NullReferenceException", 45 + hpBonus, "💥", "Unhandled null pointer reference causing sudden application crashes.")
|
||||
{
|
||||
}
|
||||
|
||||
@@ -71,8 +71,8 @@ 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 OffByOneBugEnemy(int hpBonus = 0)
|
||||
: base("Off-By-One Bug", 22 + hpBonus, "🐛", "Iterative boundary bug indexing `for (int i = 0; i <= arr.Length; i++)`.")
|
||||
{
|
||||
}
|
||||
|
||||
@@ -121,8 +121,8 @@ 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 MemoryLeakSpectreEnemy(int hpBonus = 0)
|
||||
: base("Memory Leak Spectre", 34 + hpBonus, "👻", "Unmanaged IDisposable and dangling event handlers slowly devouring RAM.")
|
||||
{
|
||||
}
|
||||
|
||||
@@ -167,3 +167,71 @@ public class MemoryLeakSpectreEnemy : Enemy
|
||||
DecideNextIntent(engine);
|
||||
}
|
||||
}
|
||||
|
||||
public class LegacyMonolithBossEnemy : Enemy
|
||||
{
|
||||
private int _step = 0;
|
||||
|
||||
public LegacyMonolithBossEnemy()
|
||||
: base("Legacy .NET 2.0 Monolith", 85, "🏢", "Ancient monolithic server running on Windows Server 2003 with zero async code.")
|
||||
{
|
||||
}
|
||||
|
||||
public override void DecideNextIntent(CombatEngine engine)
|
||||
{
|
||||
CurrentIntent = (_step % 4) switch
|
||||
{
|
||||
0 => new Intent(IntentType.AttackDefend, 8, 1, "Synchronous Lock (8 dmg & +12 Shield)"),
|
||||
1 => new Intent(IntentType.Debuff, 0, 1, "Thread Starvation (+2 Leak, +1 Weak)", StatusType.MemoryLeak, 2),
|
||||
2 => new Intent(IntentType.Attack, 18, 1, "GC Gen-2 Global Lockout (18 dmg)"),
|
||||
3 => new Intent(IntentType.Attack, 5, 3, "COM Interop Cascade (5x3 dmg)"),
|
||||
_ => new Intent(IntentType.Attack, 10, 1, "Monolith Strike")
|
||||
};
|
||||
}
|
||||
|
||||
public override void ExecuteIntent(CombatEngine engine)
|
||||
{
|
||||
if (CurrentIntent == null) return;
|
||||
|
||||
switch (_step % 4)
|
||||
{
|
||||
case 0:
|
||||
var dmg0 = Statuses.CalculateOutgoingDamage(8);
|
||||
var isCrit0 = engine.Player.Statuses.Get(StatusType.Vulnerable) > 0;
|
||||
var dealt0 = engine.Player.TakeDamage(dmg0);
|
||||
GainShield(12);
|
||||
engine.NotifyDamage(engine.Player.Id, engine.Player.Name, dealt0, false, isCrit0, Name);
|
||||
engine.NotifyShield(Id, Name, 12, Name);
|
||||
engine.Log.AddError($"[{Name}] locked main UI thread! Dealt {dealt0} dmg and gained +12 Shield.");
|
||||
break;
|
||||
case 1:
|
||||
engine.Player.Statuses.Add(StatusType.MemoryLeak, 2);
|
||||
engine.Player.Statuses.Add(StatusType.Weak, 1);
|
||||
GainShield(8);
|
||||
engine.NotifyShield(Id, Name, 8, Name);
|
||||
engine.Log.AddFatal($"[{Name}] starved worker thread pool! Player gained +2 Memory Leak and 1 Weak.");
|
||||
break;
|
||||
case 2:
|
||||
var dmg2 = Statuses.CalculateOutgoingDamage(18);
|
||||
var isCrit2 = engine.Player.Statuses.Get(StatusType.Vulnerable) > 0;
|
||||
var dealt2 = engine.Player.TakeDamage(dmg2);
|
||||
engine.NotifyDamage(engine.Player.Id, engine.Player.Name, dealt2, false, isCrit2, Name);
|
||||
engine.Log.AddFatal($"[{Name}] FULL CLR CRASH WAVE! Dealt massive {dealt2} damage!");
|
||||
break;
|
||||
case 3:
|
||||
int total = 0;
|
||||
var isCrit3 = engine.Player.Statuses.Get(StatusType.Vulnerable) > 0;
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
var d = Statuses.CalculateOutgoingDamage(5);
|
||||
total += engine.Player.TakeDamage(d);
|
||||
engine.NotifyDamage(engine.Player.Id, engine.Player.Name, d, false, isCrit3, $"{Name} #{i+1}");
|
||||
}
|
||||
engine.Log.AddError($"[{Name}] COM interop DLLs cascaded! Dealt total {total} damage!");
|
||||
break;
|
||||
}
|
||||
|
||||
_step++;
|
||||
DecideNextIntent(engine);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,18 +8,20 @@ public static class StarterDecks
|
||||
{
|
||||
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()
|
||||
new ConsoleWriteLineCard(), // 1 CPU
|
||||
new ConsoleWriteLineCard(), // 1 CPU
|
||||
new ConsoleWriteLineCard(), // 1 CPU
|
||||
new TryCatchCard(), // 1 CPU
|
||||
new TryCatchCard(), // 1 CPU
|
||||
new TryCatchCard(), // 1 CPU
|
||||
new RandomNextCard(), // 1 CPU - RNG (3-14 dmg)
|
||||
new DynamicExpandoCard(), // 1 CPU - RNG (Generate random 0-cost card)
|
||||
new ThreadSleepCard(), // 0 CPU - Ramp (+2 CPU)
|
||||
new SpanSliceCard(), // 1 CPU - Piercing
|
||||
new LinqWhereCard(), // 1 CPU - Shield & Draw
|
||||
new TieredCompilationCard(), // 1 CPU - Power (+2 Strength)
|
||||
new GcCollectCard(), // 2 CPU - Scaler
|
||||
new ThrowExceptionCard() // 2 CPU - Heavy Burst (16 dmg)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using SlaySharp.Core.Catalogs;
|
||||
using SlaySharp.Core.Enums;
|
||||
using SlaySharp.Core.Models;
|
||||
|
||||
@@ -11,6 +12,7 @@ public class CombatEngine
|
||||
public CombatPhase Phase { get; private set; } = CombatPhase.PlayerTurn;
|
||||
public CombatLog Log { get; } = new();
|
||||
public string? ActiveActingEnemyId { get; private set; }
|
||||
public List<Card> CardRewards { get; private set; } = new();
|
||||
|
||||
public event Action? OnStateChanged;
|
||||
public event Action<DamageEvent>? OnDamageDealt;
|
||||
@@ -27,6 +29,79 @@ public class CombatEngine
|
||||
OnShieldGained?.Invoke(new ShieldEvent(targetId, targetName, amount, sourceName));
|
||||
}
|
||||
|
||||
public (int dealtDamage, bool isCrit) CalculateAndDealDamage(Player player, Character target, int baseDamage, bool isPiercing, string sourceName)
|
||||
{
|
||||
var targetIsVulnerable = target.Statuses.Get(StatusType.Vulnerable) > 0;
|
||||
var rngCrit = Random.Shared.Next(100) < 15; // 15% JIT Branch Prediction Crit chance!
|
||||
var isCrit = targetIsVulnerable || rngCrit;
|
||||
|
||||
var modifiedDmg = player.Statuses.CalculateOutgoingDamage(baseDamage);
|
||||
if (rngCrit && !targetIsVulnerable)
|
||||
{
|
||||
modifiedDmg = (int)Math.Round(modifiedDmg * 1.5, MidpointRounding.AwayFromZero);
|
||||
Log.AddSuccess($"[JIT Branch Prediction] 🎲 CRITICAL HIT on {target.Name} (1.5x damage)!");
|
||||
}
|
||||
|
||||
var dealt = target.TakeDamage(modifiedDmg, isPiercing);
|
||||
NotifyDamage(target.Id, target.Name, dealt, isPiercing, isCrit, sourceName);
|
||||
return (dealt, isCrit);
|
||||
}
|
||||
|
||||
public DamagePreviewInfo CalculateDamagePreview(Card card, Enemy enemy)
|
||||
{
|
||||
var (minRaw, maxRaw, hits, isPiercing) = card.GetDamageProfile(this, enemy);
|
||||
if (minRaw == 0 && maxRaw == 0)
|
||||
{
|
||||
return new DamagePreviewInfo(0, 0, 0, false, 0, 0, 0, enemy.CurrentHp, false, false);
|
||||
}
|
||||
|
||||
var isCrit = enemy.Statuses.Get(StatusType.Vulnerable) > 0;
|
||||
|
||||
int minTotal = 0;
|
||||
int maxTotal = 0;
|
||||
|
||||
for (int i = 0; i < hits; i++)
|
||||
{
|
||||
var minHit = Player.Statuses.CalculateOutgoingDamage(minRaw);
|
||||
var maxHit = Player.Statuses.CalculateOutgoingDamage(maxRaw);
|
||||
|
||||
minTotal += minHit;
|
||||
maxTotal += maxHit;
|
||||
}
|
||||
|
||||
int shieldAbsorbed = 0;
|
||||
int minHpLoss = 0;
|
||||
int maxHpLoss = 0;
|
||||
|
||||
if (isPiercing)
|
||||
{
|
||||
minHpLoss = minTotal;
|
||||
maxHpLoss = maxTotal;
|
||||
}
|
||||
else
|
||||
{
|
||||
shieldAbsorbed = Math.Min(enemy.Shield, minTotal);
|
||||
minHpLoss = Math.Max(0, minTotal - enemy.Shield);
|
||||
maxHpLoss = Math.Max(0, maxTotal - enemy.Shield);
|
||||
}
|
||||
|
||||
int projectedRemaining = Math.Max(0, enemy.CurrentHp - minHpLoss);
|
||||
bool isLethal = minHpLoss >= enemy.CurrentHp;
|
||||
|
||||
return new DamagePreviewInfo(
|
||||
minTotal,
|
||||
maxTotal,
|
||||
hits,
|
||||
isPiercing,
|
||||
shieldAbsorbed,
|
||||
minHpLoss,
|
||||
maxHpLoss,
|
||||
projectedRemaining,
|
||||
isLethal,
|
||||
isCrit
|
||||
);
|
||||
}
|
||||
|
||||
public void StartCombat(Player player, List<Enemy> enemies)
|
||||
{
|
||||
Player = player;
|
||||
@@ -34,6 +109,7 @@ public class CombatEngine
|
||||
TurnNumber = 1;
|
||||
Phase = CombatPhase.PlayerTurn;
|
||||
ActiveActingEnemyId = null;
|
||||
CardRewards.Clear();
|
||||
|
||||
Log.Clear();
|
||||
Log.AddInfo($"=== Starting Incident Resolution: {enemies.Count} Bugs Detected ===");
|
||||
@@ -301,9 +377,18 @@ public class CombatEngine
|
||||
else if (Enemies.Count == 0)
|
||||
{
|
||||
Phase = CombatPhase.Victory;
|
||||
CardRewards = CardCatalog.RollThreeCardRewards();
|
||||
Log.AddSuccess("=== ALL BUGS RESOLVED! Build Deployed Successfully! ===");
|
||||
}
|
||||
}
|
||||
|
||||
public void ClaimCardReward(Card card)
|
||||
{
|
||||
Player.MasterDeck.Add(card.Clone());
|
||||
Log.AddSuccess($"[Pull Request Merged] Added '{card.Name}' to Master Deck!");
|
||||
CardRewards.Clear();
|
||||
NotifyStateChanged();
|
||||
}
|
||||
|
||||
public void NotifyStateChanged() => OnStateChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
@@ -44,8 +44,23 @@ public abstract class Card
|
||||
return engine.Player.CurrentEnergy >= Cost;
|
||||
}
|
||||
|
||||
public void SetCostOverride(int newCost)
|
||||
{
|
||||
Cost = newCost;
|
||||
}
|
||||
|
||||
public abstract void ApplyEffect(CombatEngine engine, Player player, Character? target);
|
||||
|
||||
public virtual (int minDamage, int maxDamage, int hits, bool isPiercing) GetDamageProfile(CombatEngine engine, Character? target = null)
|
||||
{
|
||||
return (0, 0, 1, false);
|
||||
}
|
||||
|
||||
public virtual int GetShieldProfile(CombatEngine engine)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public virtual void Upgrade()
|
||||
{
|
||||
if (Upgraded) return;
|
||||
@@ -57,4 +72,4 @@ public abstract class Card
|
||||
protected virtual void OnUpgrade() { }
|
||||
|
||||
public abstract Card Clone();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace SlaySharp.Core.Models;
|
||||
|
||||
public record DamagePreviewInfo(
|
||||
int MinRawDamage,
|
||||
int MaxRawDamage,
|
||||
int Hits,
|
||||
bool IsPiercing,
|
||||
int ShieldAbsorbed,
|
||||
int MinHpLoss,
|
||||
int MaxHpLoss,
|
||||
int ProjectedRemainingHp,
|
||||
bool IsLethal,
|
||||
bool IsCrit
|
||||
);
|
||||
@@ -1,4 +1,4 @@
|
||||
using SlaySharp.Core.Combat;
|
||||
using SlaySharp.Core.Combat;
|
||||
|
||||
namespace SlaySharp.Core.Relics;
|
||||
|
||||
@@ -83,3 +83,59 @@ public class SerilogRelic : Relic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class PollyRelic : Relic
|
||||
{
|
||||
private bool _triggered;
|
||||
|
||||
public PollyRelic() : base(
|
||||
"Polly",
|
||||
"Polly",
|
||||
"v8.3.0",
|
||||
"Once per combat: When taking damage, trigger resilience policy for +12 Shield.",
|
||||
"🛡️"
|
||||
) { }
|
||||
|
||||
public override void OnCombatStart(CombatEngine engine)
|
||||
{
|
||||
_triggered = false;
|
||||
}
|
||||
|
||||
public override void OnTurnStart(CombatEngine engine)
|
||||
{
|
||||
if (!_triggered && engine.Player.CurrentHp < (engine.Player.MaxHp / 2))
|
||||
{
|
||||
_triggered = true;
|
||||
engine.Player.GainShield(12);
|
||||
engine.Log.AddInfo("[Polly] Circuit breaker triggered! Resilience policy deployed +12 Shield.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DapperRelic : Relic
|
||||
{
|
||||
private bool _attackPlayedThisTurn;
|
||||
|
||||
public DapperRelic() : base(
|
||||
"Dapper",
|
||||
"Dapper",
|
||||
"v2.1.35",
|
||||
"Micro-ORM caching: The first Attack card played each turn grants +3 Shield.",
|
||||
"⚡"
|
||||
) { }
|
||||
|
||||
public override void OnTurnStart(CombatEngine engine)
|
||||
{
|
||||
_attackPlayedThisTurn = false;
|
||||
}
|
||||
|
||||
public override void OnCardPlayed(CombatEngine engine, Models.Card card)
|
||||
{
|
||||
if (!_attackPlayedThisTurn && card.Type == Enums.CardType.Attack)
|
||||
{
|
||||
_attackPlayedThisTurn = true;
|
||||
engine.Player.GainShield(3);
|
||||
engine.Log.AddInfo("[Dapper] High-speed micro-ORM query: +3 Shield gained.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
using SlaySharp.Core.Catalogs;
|
||||
using SlaySharp.Core.Combat;
|
||||
using SlaySharp.Core.Models;
|
||||
using SlaySharp.Core.Relics;
|
||||
|
||||
namespace SlaySharp.Core.Run;
|
||||
|
||||
public enum RunScreen
|
||||
{
|
||||
Title,
|
||||
Map,
|
||||
Combat,
|
||||
Event,
|
||||
RestSite,
|
||||
Shop,
|
||||
RunVictory,
|
||||
RunDefeat
|
||||
}
|
||||
|
||||
public class GameRun
|
||||
{
|
||||
public Player Player { get; set; } = null!;
|
||||
public MapGraph Map { get; set; } = null!;
|
||||
public MapNode? CurrentNode { get; set; }
|
||||
public HashSet<string> VisitedNodeIds { get; } = new();
|
||||
public int DevCredits { get; set; } = 60;
|
||||
public RunScreen CurrentScreen { get; set; } = RunScreen.Title;
|
||||
|
||||
public CombatEngine? ActiveCombat { get; set; }
|
||||
public StoryEvent? ActiveEvent { get; set; }
|
||||
|
||||
public event Action? OnRunStateChanged;
|
||||
|
||||
public void StartNewRun(int? seed = null)
|
||||
{
|
||||
Player = new Player("Senior Dev", 70, maxEnergy: 3, seed: seed);
|
||||
Player.MasterDeck.AddRange(StarterDecks.CreateCsharpArchitectDeck());
|
||||
|
||||
Player.Relics.Add(new NewtonsoftJsonRelic());
|
||||
Player.Relics.Add(new BenchmarkDotNetRelic());
|
||||
Player.Relics.Add(new SerilogRelic());
|
||||
|
||||
DevCredits = 60;
|
||||
VisitedNodeIds.Clear();
|
||||
CurrentNode = null;
|
||||
ActiveCombat = null;
|
||||
ActiveEvent = null;
|
||||
|
||||
Map = MapGraph.GenerateAct1Map(seed);
|
||||
CurrentScreen = RunScreen.Map;
|
||||
NotifyChange();
|
||||
}
|
||||
|
||||
public bool CanVisitNode(MapNode node)
|
||||
{
|
||||
if (CurrentNode == null)
|
||||
{
|
||||
// Can visit any floor 0 node
|
||||
return node.Floor == 0;
|
||||
}
|
||||
|
||||
return CurrentNode.NextNodeIds.Contains(node.Id);
|
||||
}
|
||||
|
||||
public void SelectNode(MapNode node)
|
||||
{
|
||||
if (!CanVisitNode(node)) return;
|
||||
|
||||
CurrentNode = node;
|
||||
VisitedNodeIds.Add(node.Id);
|
||||
|
||||
switch (node.Type)
|
||||
{
|
||||
case MapNodeType.Combat:
|
||||
StartCombatEncounter(isElite: false, isBoss: false);
|
||||
break;
|
||||
case MapNodeType.Elite:
|
||||
StartCombatEncounter(isElite: true, isBoss: false);
|
||||
break;
|
||||
case MapNodeType.Boss:
|
||||
StartCombatEncounter(isElite: false, isBoss: true);
|
||||
break;
|
||||
case MapNodeType.RestSite:
|
||||
CurrentScreen = RunScreen.RestSite;
|
||||
break;
|
||||
case MapNodeType.Shop:
|
||||
CurrentScreen = RunScreen.Shop;
|
||||
break;
|
||||
case MapNodeType.MysteryEvent:
|
||||
StartRandomEvent();
|
||||
break;
|
||||
}
|
||||
|
||||
NotifyChange();
|
||||
}
|
||||
|
||||
private void StartCombatEncounter(bool isElite, bool isBoss)
|
||||
{
|
||||
List<Enemy> enemies;
|
||||
if (isBoss)
|
||||
{
|
||||
enemies = new List<Enemy> { new LegacyMonolithBossEnemy() };
|
||||
}
|
||||
else if (isElite)
|
||||
{
|
||||
enemies = new List<Enemy> { new MemoryLeakSpectreEnemy(hpBonus: 6), new OffByOneBugEnemy() };
|
||||
}
|
||||
else
|
||||
{
|
||||
// Standard combat
|
||||
var roll = Random.Shared.Next(2);
|
||||
enemies = roll switch
|
||||
{
|
||||
0 => new List<Enemy> { new NullReferenceExceptionEnemy(), new OffByOneBugEnemy() },
|
||||
_ => new List<Enemy> { new MemoryLeakSpectreEnemy(), new OffByOneBugEnemy() }
|
||||
};
|
||||
}
|
||||
|
||||
ActiveCombat = new CombatEngine();
|
||||
ActiveCombat.StartCombat(Player, enemies);
|
||||
CurrentScreen = RunScreen.Combat;
|
||||
}
|
||||
|
||||
private void StartRandomEvent()
|
||||
{
|
||||
var events = StoryEvent.GetAct1Events();
|
||||
var index = Random.Shared.Next(events.Count);
|
||||
ActiveEvent = events[index];
|
||||
CurrentScreen = RunScreen.Event;
|
||||
}
|
||||
|
||||
public void CompleteCombatAndClaimRewards(int bonusCredits = 25)
|
||||
{
|
||||
DevCredits += bonusCredits;
|
||||
if (CurrentNode?.Type == MapNodeType.Boss)
|
||||
{
|
||||
CurrentScreen = RunScreen.RunVictory;
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentScreen = RunScreen.Map;
|
||||
}
|
||||
ActiveCombat = null;
|
||||
NotifyChange();
|
||||
}
|
||||
|
||||
public void ReturnToMap()
|
||||
{
|
||||
if (Player.IsDead)
|
||||
{
|
||||
CurrentScreen = RunScreen.RunDefeat;
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentScreen = RunScreen.Map;
|
||||
}
|
||||
ActiveCombat = null;
|
||||
ActiveEvent = null;
|
||||
NotifyChange();
|
||||
}
|
||||
|
||||
public void NotifyChange() => OnRunStateChanged?.Invoke();
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
namespace SlaySharp.Core.Run;
|
||||
|
||||
public class MapGraph
|
||||
{
|
||||
public List<List<MapNode>> Floors { get; } = new();
|
||||
|
||||
public static MapGraph GenerateAct1Map(int? seed = null)
|
||||
{
|
||||
var rng = seed.HasValue ? new Random(seed.Value) : Random.Shared;
|
||||
var map = new MapGraph();
|
||||
|
||||
// Floor 0: Starter Pull Requests (2 Combats)
|
||||
var floor0 = new List<MapNode>
|
||||
{
|
||||
new MapNode { Floor = 0, Column = 0, Type = MapNodeType.Combat, Title = "PR #101: Code Review", Description = "Resolve unit test failures on branch feature/auth." },
|
||||
new MapNode { Floor = 0, Column = 1, Type = MapNodeType.Combat, Title = "PR #102: API Gateway Glitch", Description = "Fix routing exceptions in reverse proxy." }
|
||||
};
|
||||
map.Floors.Add(floor0);
|
||||
|
||||
// Floor 1: 3 Nodes (Mystery, Combat, Mystery)
|
||||
var floor1 = new List<MapNode>
|
||||
{
|
||||
new MapNode { Floor = 1, Column = 0, Type = MapNodeType.MysteryEvent, Title = "Stack Overflow Thread", Description = "An enigmatic developer answered an old issue." },
|
||||
new MapNode { Floor = 1, Column = 1, Type = MapNodeType.Combat, Title = "PR #108: Cache Invalidation", Description = "Memory corruption in Redis cache layer." },
|
||||
new MapNode { Floor = 1, Column = 2, Type = MapNodeType.MysteryEvent, Title = "Junior Dev PR", Description = "A pull request with 8000 lines of code changes." }
|
||||
};
|
||||
map.Floors.Add(floor1);
|
||||
|
||||
// Floor 2: 2 Nodes (Shop, Elite)
|
||||
var floor2 = new List<MapNode>
|
||||
{
|
||||
new MapNode { Floor = 2, Column = 0, Type = MapNodeType.Shop, Title = "NuGet Marketplace Feed", Description = "Procure enterprise diagnostic libraries." },
|
||||
new MapNode { Floor = 2, Column = 1, Type = MapNodeType.Elite, Title = "SEV-1 Critical Outage", Description = "Heavy memory leak threatening pod stability." }
|
||||
};
|
||||
map.Floors.Add(floor2);
|
||||
|
||||
// Floor 3: 2 Nodes (RestSite, RestSite)
|
||||
var floor3 = new List<MapNode>
|
||||
{
|
||||
new MapNode { Floor = 3, Column = 0, Type = MapNodeType.RestSite, Title = "Espresso & Refactor Bar", Description = "Recharge SLA or optimize Roslyn AST cards." },
|
||||
new MapNode { Floor = 3, Column = 1, Type = MapNodeType.RestSite, Title = "Dev Lounge Coffee Machine", Description = "Quick break before staging deployment." }
|
||||
};
|
||||
map.Floors.Add(floor3);
|
||||
|
||||
// Floor 4: 2 Nodes (Mystery, Elite)
|
||||
var floor4 = new List<MapNode>
|
||||
{
|
||||
new MapNode { Floor = 4, Column = 0, Type = MapNodeType.MysteryEvent, Title = "Legacy COM Discovery", Description = "Ancient unmanaged DLL found in codebase." },
|
||||
new MapNode { Floor = 4, Column = 1, Type = MapNodeType.Elite, Title = "Deadlock Demon Outage", Description = "Thread pool starvation across microservices." }
|
||||
};
|
||||
map.Floors.Add(floor4);
|
||||
|
||||
// Floor 5: 1 Node (RestSite before Boss)
|
||||
var floor5 = new List<MapNode>
|
||||
{
|
||||
new MapNode { Floor = 5, Column = 0, Type = MapNodeType.RestSite, Title = "Final Staging Gate Review", Description = "Last chance to refactor cards before Release Gate." }
|
||||
};
|
||||
map.Floors.Add(floor5);
|
||||
|
||||
// Floor 6: 1 Node (Boss)
|
||||
var floor6 = new List<MapNode>
|
||||
{
|
||||
new MapNode { Floor = 6, Column = 0, Type = MapNodeType.Boss, Title = "BOSS: Friday 5 PM Monolith", Description = "The ultimate legacy .NET Framework 2.0 Monolith release." }
|
||||
};
|
||||
map.Floors.Add(floor6);
|
||||
|
||||
// Connect Nodes
|
||||
// Floor 0 -> Floor 1
|
||||
floor0[0].NextNodeIds.AddRange(new[] { floor1[0].Id, floor1[1].Id });
|
||||
floor0[1].NextNodeIds.AddRange(new[] { floor1[1].Id, floor1[2].Id });
|
||||
|
||||
// Floor 1 -> Floor 2
|
||||
floor1[0].NextNodeIds.Add(floor2[0].Id);
|
||||
floor1[1].NextNodeIds.AddRange(new[] { floor2[0].Id, floor2[1].Id });
|
||||
floor1[2].NextNodeIds.Add(floor2[1].Id);
|
||||
|
||||
// Floor 2 -> Floor 3
|
||||
floor2[0].NextNodeIds.AddRange(new[] { floor3[0].Id, floor3[1].Id });
|
||||
floor2[1].NextNodeIds.AddRange(new[] { floor3[0].Id, floor3[1].Id });
|
||||
|
||||
// Floor 3 -> Floor 4
|
||||
floor3[0].NextNodeIds.AddRange(new[] { floor4[0].Id, floor4[1].Id });
|
||||
floor3[1].NextNodeIds.AddRange(new[] { floor4[0].Id, floor4[1].Id });
|
||||
|
||||
// Floor 4 -> Floor 5
|
||||
floor4[0].NextNodeIds.Add(floor5[0].Id);
|
||||
floor4[1].NextNodeIds.Add(floor5[0].Id);
|
||||
|
||||
// Floor 5 -> Floor 6
|
||||
floor5[0].NextNodeIds.Add(floor6[0].Id);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
public MapNode? FindNode(string id)
|
||||
{
|
||||
return Floors.SelectMany(f => f).FirstOrDefault(n => n.Id == id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
namespace SlaySharp.Core.Run;
|
||||
|
||||
public enum MapNodeType
|
||||
{
|
||||
Combat,
|
||||
Elite,
|
||||
RestSite,
|
||||
Shop,
|
||||
MysteryEvent,
|
||||
Boss
|
||||
}
|
||||
|
||||
public class MapNode
|
||||
{
|
||||
public string Id { get; set; } = Guid.NewGuid().ToString("N")[..8];
|
||||
public int Floor { get; set; }
|
||||
public int Column { get; set; }
|
||||
public MapNodeType Type { get; set; }
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public List<string> NextNodeIds { get; set; } = new();
|
||||
|
||||
public string Icon => Type switch
|
||||
{
|
||||
MapNodeType.Combat => "⚔️",
|
||||
MapNodeType.Elite => "☠️",
|
||||
MapNodeType.RestSite => "☕",
|
||||
MapNodeType.Shop => "🛒",
|
||||
MapNodeType.MysteryEvent => "❓",
|
||||
MapNodeType.Boss => "👑",
|
||||
_ => "📍"
|
||||
};
|
||||
|
||||
public string BadgeColor => Type switch
|
||||
{
|
||||
MapNodeType.Combat => "#61afef",
|
||||
MapNodeType.Elite => "#e06c75",
|
||||
MapNodeType.RestSite => "#98c379",
|
||||
MapNodeType.Shop => "#e5c07b",
|
||||
MapNodeType.MysteryEvent => "#c678dd",
|
||||
MapNodeType.Boss => "#ff4757",
|
||||
_ => "#abb2bf"
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
using SlaySharp.Core.Catalogs;
|
||||
using SlaySharp.Core.Models;
|
||||
|
||||
namespace SlaySharp.Core.Run;
|
||||
|
||||
public record StoryOption(
|
||||
string ChoiceText,
|
||||
string OutcomeText,
|
||||
Action<GameRun> ApplyEffect
|
||||
);
|
||||
|
||||
public class StoryEvent
|
||||
{
|
||||
public string Id { get; set; } = string.Empty;
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Icon { get; set; } = "❓";
|
||||
public string Lore { get; set; } = string.Empty;
|
||||
public List<StoryOption> Options { get; set; } = new();
|
||||
|
||||
public static List<StoryEvent> GetAct1Events()
|
||||
{
|
||||
return new List<StoryEvent>
|
||||
{
|
||||
new StoryEvent
|
||||
{
|
||||
Id = "stackoverflow",
|
||||
Title = "The 12-Year-Old Stack Overflow Answer",
|
||||
Icon = "🌐",
|
||||
Lore = "While investigating a rare memory access bug, you find a forum answer from 2014 by a developer named 'JonSkeetFan99'. The solution uses dangerous unsafe pointer arithmetic.",
|
||||
Options = new List<StoryOption>
|
||||
{
|
||||
new StoryOption(
|
||||
"Copy-Paste Without Reading [Gain Upgraded Span.Slice+, lose 8 Max SLA]",
|
||||
"You paste the snippet into production. It executes at blinding speed, but technical debt accrues!",
|
||||
run =>
|
||||
{
|
||||
var card = new SpanSliceCard();
|
||||
card.Upgrade();
|
||||
run.Player.MasterDeck.Add(card);
|
||||
run.Player.MaxHp = Math.Max(20, run.Player.MaxHp - 8);
|
||||
run.Player.CurrentHp = Math.Min(run.Player.CurrentHp, run.Player.MaxHp);
|
||||
}
|
||||
),
|
||||
new StoryOption(
|
||||
"Read Documentation & Write Unit Tests [Heal 15 SLA, Gain 30 Dev Credits]",
|
||||
"You spend 20 minutes understanding the root cause. Clean architecture restored!",
|
||||
run =>
|
||||
{
|
||||
run.Player.Heal(15);
|
||||
run.DevCredits += 30;
|
||||
}
|
||||
),
|
||||
new StoryOption(
|
||||
"Downvote & Close Browser [Gain 20 Dev Credits]",
|
||||
"You decide not to gamble with legacy hacks and move forward.",
|
||||
run =>
|
||||
{
|
||||
run.DevCredits += 20;
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
new StoryEvent
|
||||
{
|
||||
Id = "junior_pr",
|
||||
Title = "The 8,000-Line 'Minor Bugfix' PR",
|
||||
Icon = "🌿",
|
||||
Lore = "A junior developer submits a pull request titled 'Refactored auth module (quick fix)'. It modifies 142 files across the entire solution without a single unit test.",
|
||||
Options = new List<StoryOption>
|
||||
{
|
||||
new StoryOption(
|
||||
"Approve & Ship to Staging [Gain Upgraded throw Exception+, take 10 damage]",
|
||||
"You click 'LGTM' and merge. The build explodes in flames, but unlocks sheer destructive power!",
|
||||
run =>
|
||||
{
|
||||
var card = new ThrowExceptionCard();
|
||||
card.Upgrade();
|
||||
run.Player.MasterDeck.Add(card);
|
||||
run.Player.TakeDamage(10, isPiercing: true);
|
||||
}
|
||||
),
|
||||
new StoryOption(
|
||||
"Pair-Program & Teach Clean Code [Upgrade a Card, +35 Dev Credits]",
|
||||
"You mentor the developer. A card in your Master Deck is refactored to production perfection!",
|
||||
run =>
|
||||
{
|
||||
var upgradable = run.Player.MasterDeck.FirstOrDefault(c => !c.Upgraded);
|
||||
upgradable?.Upgrade();
|
||||
run.DevCredits += 35;
|
||||
}
|
||||
),
|
||||
new StoryOption(
|
||||
"Close PR and Request Smaller Diffs [Heal 10 SLA]",
|
||||
"You enforce code hygiene policies and drink some tea.",
|
||||
run =>
|
||||
{
|
||||
run.Player.Heal(10);
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
new StoryEvent
|
||||
{
|
||||
Id = "legacy_com",
|
||||
Title = "Ancient C++ COM Interop Wrapper",
|
||||
Icon = "💾",
|
||||
Lore = "In the deep bowels of the repository, you uncover a binary file compiled in 2003 with zero source code and unmanaged memory allocations.",
|
||||
Options = new List<StoryOption>
|
||||
{
|
||||
new StoryOption(
|
||||
"Wrap in Dynamic JIT Optimization [Gain Tiered Compilation+]",
|
||||
"You wrap the legacy binary in a high-performance Roslyn wrapper!",
|
||||
run =>
|
||||
{
|
||||
var card = new TieredCompilationCard();
|
||||
card.Upgrade();
|
||||
run.Player.MasterDeck.Add(card);
|
||||
}
|
||||
),
|
||||
new StoryOption(
|
||||
"Decompile and Liquidate [Gain 80 Dev Credits]",
|
||||
"You extract the proprietary business logic and earn huge company bonuses!",
|
||||
run =>
|
||||
{
|
||||
run.DevCredits += 80;
|
||||
}
|
||||
),
|
||||
new StoryOption(
|
||||
"Seal Behind Deprecated Attribute [Heal 20 SLA]",
|
||||
"You mark it [Obsolete] and isolate it from the runtime.",
|
||||
run =>
|
||||
{
|
||||
run.Player.Heal(20);
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user