improvements
This commit is contained in:
@@ -512,6 +512,196 @@ public class NullCoalescingCard : Card
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class TaskWhenAllCard : Card
|
||||||
|
{
|
||||||
|
private int _damage = 14;
|
||||||
|
|
||||||
|
public TaskWhenAllCard() : base(
|
||||||
|
"Task.WhenAll()",
|
||||||
|
"await Task.WhenAll(tasks);",
|
||||||
|
"target.Hit(14);",
|
||||||
|
"Deal 14 damage.",
|
||||||
|
cost: 2,
|
||||||
|
type: CardType.Attack,
|
||||||
|
rarity: CardRarity.Common,
|
||||||
|
targetType: TargetType.SingleEnemy
|
||||||
|
) { }
|
||||||
|
|
||||||
|
protected override void OnUpgrade()
|
||||||
|
{
|
||||||
|
_damage = 19;
|
||||||
|
Description = "Deal 19 damage.";
|
||||||
|
CodeSnippet = "target.Hit(19);";
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (dealt, isCrit) = engine.CalculateAndDealDamage(player, target, _damage, isPiercing: false, Name);
|
||||||
|
engine.Log.AddSuccess($"[Task.WhenAll] Concurrency burst completed! Dealt {dealt} dmg.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Card Clone() => new TaskWhenAllCard { Upgraded = this.Upgraded, Name = this.Name, Description = this.Description, _damage = this._damage, CodeSnippet = this.CodeSnippet };
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CancellationTokenCard : Card
|
||||||
|
{
|
||||||
|
private int _shield = 4;
|
||||||
|
private int _weak = 2;
|
||||||
|
|
||||||
|
public CancellationTokenCard() : base(
|
||||||
|
"CancellationToken.Cancel()",
|
||||||
|
"cts.Cancel();",
|
||||||
|
"AllEnemies.Weak += 2; shield += 4;",
|
||||||
|
"Apply 2 Weak to all enemies. Gain 4 Shield.",
|
||||||
|
cost: 1,
|
||||||
|
type: CardType.Skill,
|
||||||
|
rarity: CardRarity.Uncommon,
|
||||||
|
targetType: TargetType.AllEnemies
|
||||||
|
) { }
|
||||||
|
|
||||||
|
protected override void OnUpgrade()
|
||||||
|
{
|
||||||
|
_shield = 7;
|
||||||
|
_weak = 3;
|
||||||
|
Description = "Apply 3 Weak to all enemies. Gain 7 Shield.";
|
||||||
|
CodeSnippet = "AllEnemies.Weak += 3; shield += 7;";
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetShieldProfile(CombatEngine engine) => _shield;
|
||||||
|
|
||||||
|
public override void ApplyEffect(CombatEngine engine, Player player, Character? target)
|
||||||
|
{
|
||||||
|
player.GainShield(_shield);
|
||||||
|
engine.NotifyShield(player.Id, player.Name, _shield, Name);
|
||||||
|
foreach (var enemy in engine.Enemies)
|
||||||
|
{
|
||||||
|
enemy.Statuses.Add(StatusType.Weak, _weak);
|
||||||
|
}
|
||||||
|
engine.Log.AddWarning($"[CancellationToken] Operations aborted! +{_shield} Shield, {_weak} Weak applied.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Card Clone() => new CancellationTokenCard { Upgraded = this.Upgraded, Name = this.Name, Description = this.Description, _shield = this._shield, _weak = this._weak, CodeSnippet = this.CodeSnippet };
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ChannelWriteCard : Card
|
||||||
|
{
|
||||||
|
private int _shield = 5;
|
||||||
|
private int _draw = 2;
|
||||||
|
|
||||||
|
public ChannelWriteCard() : base(
|
||||||
|
"Channel.Writer.WriteAsync()",
|
||||||
|
"await channel.Writer.WriteAsync(item);",
|
||||||
|
"Draw(2); shield += 5;",
|
||||||
|
"Draw 2 cards. Gain 5 Shield.",
|
||||||
|
cost: 1,
|
||||||
|
type: CardType.Skill,
|
||||||
|
rarity: CardRarity.Common,
|
||||||
|
targetType: TargetType.Self
|
||||||
|
) { }
|
||||||
|
|
||||||
|
protected override void OnUpgrade()
|
||||||
|
{
|
||||||
|
_shield = 8;
|
||||||
|
_draw = 3;
|
||||||
|
Description = "Draw 3 cards. Gain 8 Shield.";
|
||||||
|
CodeSnippet = "Draw(3); shield += 8;";
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetShieldProfile(CombatEngine engine) => _shield;
|
||||||
|
|
||||||
|
public override void ApplyEffect(CombatEngine engine, Player player, Character? target)
|
||||||
|
{
|
||||||
|
player.GainShield(_shield);
|
||||||
|
engine.NotifyShield(player.Id, player.Name, _shield, Name);
|
||||||
|
player.DrawCards(_draw, engine.Log);
|
||||||
|
engine.Log.AddInfo($"[Channel] Emitted {_draw} items to consumer, +{_shield} Shield.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Card Clone() => new ChannelWriteCard { Upgraded = this.Upgraded, Name = this.Name, Description = this.Description, _shield = this._shield, _draw = this._draw, CodeSnippet = this.CodeSnippet };
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StackallocCard : Card
|
||||||
|
{
|
||||||
|
private int _damage = 13;
|
||||||
|
|
||||||
|
public StackallocCard() : base(
|
||||||
|
"Span<byte> stackalloc",
|
||||||
|
"Span<byte> buf = stackalloc byte[16];",
|
||||||
|
"Player.HP -= 2; target.DirectHit(13);",
|
||||||
|
"Take 2 self-damage. Deal 13 piercing damage.",
|
||||||
|
cost: 1,
|
||||||
|
type: CardType.Attack,
|
||||||
|
rarity: CardRarity.Common,
|
||||||
|
targetType: TargetType.SingleEnemy
|
||||||
|
) { }
|
||||||
|
|
||||||
|
protected override void OnUpgrade()
|
||||||
|
{
|
||||||
|
_damage = 18;
|
||||||
|
Description = "Take 2 self-damage. Deal 18 piercing damage.";
|
||||||
|
CodeSnippet = "Player.HP -= 2; target.DirectHit(18);";
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
player.TakeDamage(2, isPiercing: true);
|
||||||
|
engine.NotifyDamage(player.Id, player.Name, 2, true, false, "StackallocCost");
|
||||||
|
var (dealt, isCrit) = engine.CalculateAndDealDamage(player, target, _damage, isPiercing: true, Name);
|
||||||
|
engine.Log.AddWarning($"[stackalloc] Zero-heap unmanaged allocation! Dealt {dealt} piercing dmg (-2 SLA).");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Card Clone() => new StackallocCard { Upgraded = this.Upgraded, Name = this.Name, Description = this.Description, _damage = this._damage, CodeSnippet = this.CodeSnippet };
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FixedPointerCard : Card
|
||||||
|
{
|
||||||
|
private int _dmgPerHit = 9;
|
||||||
|
|
||||||
|
public FixedPointerCard() : base(
|
||||||
|
"fixed (byte* ptr)",
|
||||||
|
"fixed (byte* p = &data[0]) { Hit(9); Hit(9); }",
|
||||||
|
"target.DirectHit(9); // x2 hits",
|
||||||
|
"Attack 2 times for 9 piercing damage each.",
|
||||||
|
cost: 2,
|
||||||
|
type: CardType.Attack,
|
||||||
|
rarity: CardRarity.Rare,
|
||||||
|
targetType: TargetType.SingleEnemy
|
||||||
|
) { }
|
||||||
|
|
||||||
|
protected override void OnUpgrade()
|
||||||
|
{
|
||||||
|
_dmgPerHit = 13;
|
||||||
|
Description = "Attack 2 times for 13 piercing damage each.";
|
||||||
|
CodeSnippet = "fixed (byte* p = &data[0]) { Hit(13); Hit(13); }";
|
||||||
|
}
|
||||||
|
|
||||||
|
public override (int minDamage, int maxDamage, int hits, bool isPiercing) GetDamageProfile(CombatEngine engine, Character? target = null)
|
||||||
|
=> (_dmgPerHit, _dmgPerHit, 2, true);
|
||||||
|
|
||||||
|
public override void ApplyEffect(CombatEngine engine, Player player, Character? target)
|
||||||
|
{
|
||||||
|
if (target == null) return;
|
||||||
|
int total = 0;
|
||||||
|
for (int i = 0; i < 2; i++)
|
||||||
|
{
|
||||||
|
if (target.IsDead) break;
|
||||||
|
var (dealt, isCrit) = engine.CalculateAndDealDamage(player, target, _dmgPerHit, isPiercing: true, $"{Name} #{i+1}");
|
||||||
|
total += dealt;
|
||||||
|
}
|
||||||
|
engine.Log.AddSuccess($"[fixed pointer] Pinned memory hit 2 times: total {total} piercing dmg.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Card Clone() => new FixedPointerCard { Upgraded = this.Upgraded, Name = this.Name, Description = this.Description, _dmgPerHit = this._dmgPerHit, CodeSnippet = this.CodeSnippet };
|
||||||
|
}
|
||||||
|
|
||||||
public static class CardCatalog
|
public static class CardCatalog
|
||||||
{
|
{
|
||||||
public static List<Func<Card>> AllCardFactories => new()
|
public static List<Func<Card>> AllCardFactories => new()
|
||||||
@@ -527,7 +717,12 @@ public static class CardCatalog
|
|||||||
() => new ThreadSleepCard(),
|
() => new ThreadSleepCard(),
|
||||||
() => new ParallelForEachCard(),
|
() => new ParallelForEachCard(),
|
||||||
() => new LinqWhereCard(),
|
() => new LinqWhereCard(),
|
||||||
() => new NullCoalescingCard()
|
() => new NullCoalescingCard(),
|
||||||
|
() => new TaskWhenAllCard(),
|
||||||
|
() => new CancellationTokenCard(),
|
||||||
|
() => new ChannelWriteCard(),
|
||||||
|
() => new StackallocCard(),
|
||||||
|
() => new FixedPointerCard()
|
||||||
};
|
};
|
||||||
|
|
||||||
public static Card GetRandomCard()
|
public static Card GetRandomCard()
|
||||||
|
|||||||
@@ -235,3 +235,221 @@ public class LegacyMonolithBossEnemy : Enemy
|
|||||||
DecideNextIntent(engine);
|
DecideNextIntent(engine);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class ThreadDeadlockEnemy : Enemy
|
||||||
|
{
|
||||||
|
private int _step = 0;
|
||||||
|
|
||||||
|
public ThreadDeadlockEnemy() : base(
|
||||||
|
name: "Thread Deadlock",
|
||||||
|
maxHp: 36,
|
||||||
|
description: "Mutual exclusion cyclic dependency holding unmanaged lock monitors.",
|
||||||
|
icon: "🔒"
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public override void DecideNextIntent(CombatEngine engine)
|
||||||
|
{
|
||||||
|
CurrentIntent = (_step % 3) switch
|
||||||
|
{
|
||||||
|
0 => new Intent(IntentType.Attack, 9, 1, "Lock Contention (9 dmg)"),
|
||||||
|
1 => new Intent(IntentType.Debuff, 0, 1, "Starvation Lock (+2 Weak, +1 Leak)", StatusType.Weak, 2),
|
||||||
|
2 => new Intent(IntentType.AttackDefend, 14, 1, "Deadlock Spike (14 dmg & +8 Shield)"),
|
||||||
|
_ => new Intent(IntentType.Attack, 8, 1, "Contention Strike")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ExecuteIntent(CombatEngine engine)
|
||||||
|
{
|
||||||
|
if (CurrentIntent == null) return;
|
||||||
|
switch (_step % 3)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
var dmg0 = Statuses.CalculateOutgoingDamage(9);
|
||||||
|
var isCrit0 = engine.Player.Statuses.Get(StatusType.Vulnerable) > 0;
|
||||||
|
var dealt0 = engine.Player.TakeDamage(dmg0);
|
||||||
|
engine.NotifyDamage(engine.Player.Id, engine.Player.Name, dealt0, false, isCrit0, Name);
|
||||||
|
engine.Log.AddError($"[{Name}] lock contention dealt {dealt0} dmg.");
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
engine.Player.Statuses.Add(StatusType.Weak, 2);
|
||||||
|
engine.Player.Statuses.Add(StatusType.MemoryLeak, 1);
|
||||||
|
engine.Log.AddWarning($"[{Name}] starved worker threads (+2 Weak, +1 Memory Leak).");
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
var dmg2 = Statuses.CalculateOutgoingDamage(14);
|
||||||
|
var isCrit2 = engine.Player.Statuses.Get(StatusType.Vulnerable) > 0;
|
||||||
|
var dealt2 = engine.Player.TakeDamage(dmg2);
|
||||||
|
GainShield(8);
|
||||||
|
engine.NotifyDamage(engine.Player.Id, engine.Player.Name, dealt2, false, isCrit2, Name);
|
||||||
|
engine.NotifyShield(Id, Name, 8, Name);
|
||||||
|
engine.Log.AddError($"[{Name}] Deadlock spike hit for {dealt2} dmg and gained +8 Shield!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_step++;
|
||||||
|
DecideNextIntent(engine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StackOverflowEnemy : Enemy
|
||||||
|
{
|
||||||
|
private int _step = 0;
|
||||||
|
|
||||||
|
public StackOverflowEnemy() : base(
|
||||||
|
name: "StackOverflow Exception",
|
||||||
|
maxHp: 28,
|
||||||
|
description: "Unbounded recursive call graph expanding call frames infinitely.",
|
||||||
|
icon: "♾️"
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public override void DecideNextIntent(CombatEngine engine)
|
||||||
|
{
|
||||||
|
CurrentIntent = (_step % 3) switch
|
||||||
|
{
|
||||||
|
0 => new Intent(IntentType.AttackDefend, 6, 1, "RecursiveFrame() (6 dmg & +6 Shield)"),
|
||||||
|
1 => new Intent(IntentType.Buff, 0, 1, "Tail-Call Accumulation (+2 Strength)", StatusType.Strength, 2),
|
||||||
|
2 => new Intent(IntentType.Attack, 16, 1, "Stack Overflow Explosion (16 dmg)"),
|
||||||
|
_ => new Intent(IntentType.Attack, 6, 1, "Recursive Frame")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ExecuteIntent(CombatEngine engine)
|
||||||
|
{
|
||||||
|
if (CurrentIntent == null) return;
|
||||||
|
switch (_step % 3)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
var dmg0 = Statuses.CalculateOutgoingDamage(6);
|
||||||
|
var isCrit0 = engine.Player.Statuses.Get(StatusType.Vulnerable) > 0;
|
||||||
|
var dealt0 = engine.Player.TakeDamage(dmg0);
|
||||||
|
GainShield(6);
|
||||||
|
engine.NotifyDamage(engine.Player.Id, engine.Player.Name, dealt0, false, isCrit0, Name);
|
||||||
|
engine.NotifyShield(Id, Name, 6, Name);
|
||||||
|
engine.Log.AddError($"[{Name}] spawned stack frame! Dealt {dealt0} dmg, gained +6 Shield.");
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
Statuses.Add(StatusType.Strength, 2);
|
||||||
|
engine.Log.AddWarning($"[{Name}] recursive depth multiplied (+2 Strength).");
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
var dmg2 = Statuses.CalculateOutgoingDamage(16);
|
||||||
|
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}] STACK LIMIT EXCEEDED! Dealt {dealt2} fatal damage!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_step++;
|
||||||
|
DecideNextIntent(engine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HeisenbugEnemy : Enemy
|
||||||
|
{
|
||||||
|
private int _step = 0;
|
||||||
|
|
||||||
|
public HeisenbugEnemy() : base(
|
||||||
|
name: "Heisenbug Anomaly",
|
||||||
|
maxHp: 32,
|
||||||
|
description: "Disappears or mutates state when debug breakpoints are attached.",
|
||||||
|
icon: "👻"
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public override void DecideNextIntent(CombatEngine engine)
|
||||||
|
{
|
||||||
|
CurrentIntent = (_step % 2) switch
|
||||||
|
{
|
||||||
|
0 => new Intent(IntentType.Attack, 10, 1, "Quantum Shift (10 piercing dmg)"),
|
||||||
|
1 => new Intent(IntentType.Defend, 0, 1, "Observer Camouflage (+10 Shield)"),
|
||||||
|
_ => new Intent(IntentType.Attack, 8, 1, "Quantum Strike")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ExecuteIntent(CombatEngine engine)
|
||||||
|
{
|
||||||
|
if (CurrentIntent == null) return;
|
||||||
|
switch (_step % 2)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
var dmg0 = Statuses.CalculateOutgoingDamage(10);
|
||||||
|
var dealt0 = engine.Player.TakeDamage(dmg0, isPiercing: true);
|
||||||
|
engine.NotifyDamage(engine.Player.Id, engine.Player.Name, dealt0, true, false, Name);
|
||||||
|
engine.Log.AddWarning($"[{Name}] quantum phase pierced shields for {dealt0} dmg!");
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
GainShield(10);
|
||||||
|
engine.NotifyShield(Id, Name, 10, Name);
|
||||||
|
engine.Log.AddInfo($"[{Name}] masked thread state: +10 Shield.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_step++;
|
||||||
|
DecideNextIntent(engine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class KubernetesClusterBossEnemy : Enemy
|
||||||
|
{
|
||||||
|
private int _step = 0;
|
||||||
|
|
||||||
|
public KubernetesClusterBossEnemy() : base(
|
||||||
|
name: "K8s Production Cluster",
|
||||||
|
maxHp: 110,
|
||||||
|
description: "Multi-pod microservice mesh with rolling deployments and autoscaling.",
|
||||||
|
icon: "☸️"
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public override void DecideNextIntent(CombatEngine engine)
|
||||||
|
{
|
||||||
|
CurrentIntent = (_step % 4) switch
|
||||||
|
{
|
||||||
|
0 => new Intent(IntentType.AttackDefend, 10, 1, "Leader Pod Election (10 dmg & +15 Shield)"),
|
||||||
|
1 => new Intent(IntentType.Attack, 5, 4, "Rolling Pod Replica Surge (5x4 dmg)"),
|
||||||
|
2 => new Intent(IntentType.Debuff, 0, 1, "Ingress DDoS (+3 Weak, +2 Vulnerable)", StatusType.Weak, 3),
|
||||||
|
3 => new Intent(IntentType.Attack, 24, 1, "OOM Killer Pod Eviction (24 dmg)"),
|
||||||
|
_ => new Intent(IntentType.Attack, 12, 1, "Cluster Strike")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ExecuteIntent(CombatEngine engine)
|
||||||
|
{
|
||||||
|
if (CurrentIntent == null) return;
|
||||||
|
switch (_step % 4)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
var dmg0 = Statuses.CalculateOutgoingDamage(10);
|
||||||
|
var isCrit0 = engine.Player.Statuses.Get(StatusType.Vulnerable) > 0;
|
||||||
|
var dealt0 = engine.Player.TakeDamage(dmg0);
|
||||||
|
GainShield(15);
|
||||||
|
engine.NotifyDamage(engine.Player.Id, engine.Player.Name, dealt0, false, isCrit0, Name);
|
||||||
|
engine.NotifyShield(Id, Name, 15, Name);
|
||||||
|
engine.Log.AddError($"[{Name}] elected leader pod! Dealt {dealt0} dmg, gained +15 Shield.");
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
int total = 0;
|
||||||
|
var isCrit1 = engine.Player.Statuses.Get(StatusType.Vulnerable) > 0;
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
var d = Statuses.CalculateOutgoingDamage(5);
|
||||||
|
total += engine.Player.TakeDamage(d);
|
||||||
|
engine.NotifyDamage(engine.Player.Id, engine.Player.Name, d, false, isCrit1, $"{Name} Pod #{i+1}");
|
||||||
|
}
|
||||||
|
engine.Log.AddFatal($"[{Name}] rolling replicas fired! Total {total} damage across 4 pods!");
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
engine.Player.Statuses.Add(StatusType.Weak, 3);
|
||||||
|
engine.Player.Statuses.Add(StatusType.Vulnerable, 2);
|
||||||
|
GainShield(10);
|
||||||
|
engine.NotifyShield(Id, Name, 10, Name);
|
||||||
|
engine.Log.AddWarning($"[{Name}] Ingress saturated! Player gained +3 Weak, +2 Vulnerable.");
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
var dmg3 = Statuses.CalculateOutgoingDamage(24);
|
||||||
|
var isCrit3 = engine.Player.Statuses.Get(StatusType.Vulnerable) > 0;
|
||||||
|
var dealt3 = engine.Player.TakeDamage(dmg3);
|
||||||
|
engine.NotifyDamage(engine.Player.Id, engine.Player.Name, dealt3, false, isCrit3, Name);
|
||||||
|
engine.Log.AddFatal($"[{Name}] 💥 OOM KILLER MASSIVE POD EVICTION! Dealt {dealt3} heavy damage!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_step++;
|
||||||
|
DecideNextIntent(engine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,9 +19,43 @@ public static class StarterDecks
|
|||||||
new ThreadSleepCard(), // 0 CPU - Ramp (+2 CPU)
|
new ThreadSleepCard(), // 0 CPU - Ramp (+2 CPU)
|
||||||
new SpanSliceCard(), // 1 CPU - Piercing
|
new SpanSliceCard(), // 1 CPU - Piercing
|
||||||
new LinqWhereCard(), // 1 CPU - Shield & Draw
|
new LinqWhereCard(), // 1 CPU - Shield & Draw
|
||||||
new TieredCompilationCard(), // 1 CPU - Power (+2 Strength)
|
|
||||||
new GcCollectCard(), // 2 CPU - Scaler
|
new GcCollectCard(), // 2 CPU - Scaler
|
||||||
new ThrowExceptionCard() // 2 CPU - Heavy Burst (16 dmg)
|
new ThrowExceptionCard() // 2 CPU - Heavy Burst (16 dmg)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<Card> CreateAsyncMasterDeck()
|
||||||
|
{
|
||||||
|
return new List<Card>
|
||||||
|
{
|
||||||
|
new TaskWhenAllCard(),
|
||||||
|
new TaskWhenAllCard(),
|
||||||
|
new ChannelWriteCard(),
|
||||||
|
new ChannelWriteCard(),
|
||||||
|
new CancellationTokenCard(),
|
||||||
|
new ThreadSleepCard(),
|
||||||
|
new DynamicExpandoCard(),
|
||||||
|
new LinqWhereCard(),
|
||||||
|
new ParallelForEachCard(),
|
||||||
|
new TryCatchCard(),
|
||||||
|
new ConsoleWriteLineCard()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Card> CreateUnsafeHackerDeck()
|
||||||
|
{
|
||||||
|
return new List<Card>
|
||||||
|
{
|
||||||
|
new StackallocCard(),
|
||||||
|
new StackallocCard(),
|
||||||
|
new FixedPointerCard(),
|
||||||
|
new SpanSliceCard(),
|
||||||
|
new SpanSliceCard(),
|
||||||
|
new ThrowExceptionCard(),
|
||||||
|
new TryCatchCard(),
|
||||||
|
new NullCoalescingCard(),
|
||||||
|
new ConsoleWriteLineCard(),
|
||||||
|
new TieredCompilationCard()
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
namespace SlaySharp.Core.Models;
|
||||||
|
|
||||||
|
public enum DeveloperArchetype
|
||||||
|
{
|
||||||
|
PrincipalArchitect,
|
||||||
|
AsyncConcurrencyMaster,
|
||||||
|
UnsafeMemoryHacker
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CharacterClassInfo
|
||||||
|
{
|
||||||
|
public DeveloperArchetype Archetype { get; set; }
|
||||||
|
public string Title { get; set; } = string.Empty;
|
||||||
|
public string RoleBadge { get; set; } = string.Empty;
|
||||||
|
public string Icon { get; set; } = "👨💻";
|
||||||
|
public string Description { get; set; } = string.Empty;
|
||||||
|
public int StartingHp { get; set; }
|
||||||
|
public int StartingEnergy { get; set; }
|
||||||
|
public string Specialty { get; set; } = string.Empty;
|
||||||
|
public string TagColor { get; set; } = "#61afef";
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace SlaySharp.Core.Models;
|
||||||
|
|
||||||
|
public class RunRecord
|
||||||
|
{
|
||||||
|
public string CommitHash { get; set; } = string.Empty;
|
||||||
|
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
|
||||||
|
public DeveloperArchetype Archetype { get; set; } = DeveloperArchetype.PrincipalArchitect;
|
||||||
|
public bool IsVictory { get; set; }
|
||||||
|
public int FinalHp { get; set; }
|
||||||
|
public int MaxHp { get; set; }
|
||||||
|
public int DevCredits { get; set; }
|
||||||
|
public int DeckSize { get; set; }
|
||||||
|
public int FloorReached { get; set; }
|
||||||
|
public string Message { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public static RunRecord Create(DeveloperArchetype archetype, bool isVictory, int hp, int maxHp, int credits, int deckSize, int floor, string message = "")
|
||||||
|
{
|
||||||
|
var randomHex = Guid.NewGuid().ToString("N")[..7];
|
||||||
|
return new RunRecord
|
||||||
|
{
|
||||||
|
CommitHash = randomHex,
|
||||||
|
Timestamp = DateTime.UtcNow,
|
||||||
|
Archetype = archetype,
|
||||||
|
IsVictory = isVictory,
|
||||||
|
FinalHp = hp,
|
||||||
|
MaxHp = maxHp,
|
||||||
|
DevCredits = credits,
|
||||||
|
DeckSize = deckSize,
|
||||||
|
FloorReached = floor,
|
||||||
|
Message = string.IsNullOrWhiteSpace(message)
|
||||||
|
? (isVictory ? "feat(prod): deployed zero-downtime release to production 🚀" : "fix(hotfix): deployment rolled back due to fatal exception 💥")
|
||||||
|
: message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using SlaySharp.Core.Combat;
|
using SlaySharp.Core.Combat;
|
||||||
|
|
||||||
namespace SlaySharp.Core.Relics;
|
namespace SlaySharp.Core.Relics;
|
||||||
|
|
||||||
@@ -139,3 +139,37 @@ public class DapperRelic : Relic
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class UnsafeCompilerRelic : Relic
|
||||||
|
{
|
||||||
|
public UnsafeCompilerRelic() : base(
|
||||||
|
"System.Runtime.CompilerServices.Unsafe",
|
||||||
|
"System.Runtime.CompilerServices.Unsafe",
|
||||||
|
"v6.0.0",
|
||||||
|
"Direct memory access: Deal +2 bonus piercing damage on all attacks.",
|
||||||
|
"⚡"
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public override void OnCombatStart(CombatEngine engine)
|
||||||
|
{
|
||||||
|
engine.Player.Statuses.Add(Enums.StatusType.Strength, 1);
|
||||||
|
engine.Log.AddSuccess("[Unsafe] Direct memory pointers initialized (+1 Strength).");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SystemThreadingChannelsRelic : Relic
|
||||||
|
{
|
||||||
|
public SystemThreadingChannelsRelic() : base(
|
||||||
|
"System.Threading.Channels",
|
||||||
|
"System.Threading.Channels",
|
||||||
|
"v8.0.0",
|
||||||
|
"High-throughput bounded queue: Draw +1 extra card at the start of every turn.",
|
||||||
|
"🔀"
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public override void OnTurnStart(CombatEngine engine)
|
||||||
|
{
|
||||||
|
engine.Player.DrawCards(1, engine.Log);
|
||||||
|
engine.Log.AddInfo("[Channels] Bounded channel reader emitted +1 card.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,14 +31,36 @@ public class GameRun
|
|||||||
|
|
||||||
public event Action? OnRunStateChanged;
|
public event Action? OnRunStateChanged;
|
||||||
|
|
||||||
public void StartNewRun(int? seed = null)
|
public DeveloperArchetype SelectedArchetype { get; set; } = DeveloperArchetype.PrincipalArchitect;
|
||||||
{
|
|
||||||
Player = new Player("Senior Dev", 70, maxEnergy: 3, seed: seed);
|
|
||||||
Player.MasterDeck.AddRange(StarterDecks.CreateCsharpArchitectDeck());
|
|
||||||
|
|
||||||
Player.Relics.Add(new NewtonsoftJsonRelic());
|
public void StartNewRun(DeveloperArchetype archetype = DeveloperArchetype.PrincipalArchitect, int? seed = null)
|
||||||
Player.Relics.Add(new BenchmarkDotNetRelic());
|
{
|
||||||
Player.Relics.Add(new SerilogRelic());
|
SelectedArchetype = archetype;
|
||||||
|
|
||||||
|
switch (archetype)
|
||||||
|
{
|
||||||
|
case DeveloperArchetype.AsyncConcurrencyMaster:
|
||||||
|
Player = new Player("Async Master", 65, maxEnergy: 4, seed: seed);
|
||||||
|
Player.MasterDeck.AddRange(StarterDecks.CreateAsyncMasterDeck());
|
||||||
|
Player.Relics.Add(new PollyRelic());
|
||||||
|
Player.Relics.Add(new SystemThreadingChannelsRelic());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DeveloperArchetype.UnsafeMemoryHacker:
|
||||||
|
Player = new Player("Unsafe Hacker", 60, maxEnergy: 3, seed: seed);
|
||||||
|
Player.MasterDeck.AddRange(StarterDecks.CreateUnsafeHackerDeck());
|
||||||
|
Player.Relics.Add(new UnsafeCompilerRelic());
|
||||||
|
Player.Relics.Add(new DapperRelic());
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Player = new Player("Principal Architect", 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());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
DevCredits = 60;
|
DevCredits = 60;
|
||||||
VisitedNodeIds.Clear();
|
VisitedNodeIds.Clear();
|
||||||
@@ -99,20 +121,26 @@ public class GameRun
|
|||||||
List<Enemy> enemies;
|
List<Enemy> enemies;
|
||||||
if (isBoss)
|
if (isBoss)
|
||||||
{
|
{
|
||||||
enemies = new List<Enemy> { new LegacyMonolithBossEnemy() };
|
enemies = Random.Shared.Next(2) == 0
|
||||||
|
? new List<Enemy> { new LegacyMonolithBossEnemy() }
|
||||||
|
: new List<Enemy> { new KubernetesClusterBossEnemy() };
|
||||||
}
|
}
|
||||||
else if (isElite)
|
else if (isElite)
|
||||||
{
|
{
|
||||||
enemies = new List<Enemy> { new MemoryLeakSpectreEnemy(hpBonus: 6), new OffByOneBugEnemy() };
|
var roll = Random.Shared.Next(2);
|
||||||
|
enemies = roll == 0
|
||||||
|
? new List<Enemy> { new ThreadDeadlockEnemy(), new MemoryLeakSpectreEnemy() }
|
||||||
|
: new List<Enemy> { new StackOverflowEnemy(), new OffByOneBugEnemy() };
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Standard combat
|
// Standard combat
|
||||||
var roll = Random.Shared.Next(2);
|
var roll = Random.Shared.Next(3);
|
||||||
enemies = roll switch
|
enemies = roll switch
|
||||||
{
|
{
|
||||||
0 => new List<Enemy> { new NullReferenceExceptionEnemy(), new OffByOneBugEnemy() },
|
0 => new List<Enemy> { new NullReferenceExceptionEnemy(), new OffByOneBugEnemy() },
|
||||||
_ => new List<Enemy> { new MemoryLeakSpectreEnemy(), new OffByOneBugEnemy() }
|
1 => new List<Enemy> { new HeisenbugEnemy(), new OffByOneBugEnemy() },
|
||||||
|
_ => new List<Enemy> { new MemoryLeakSpectreEnemy(), new NullReferenceExceptionEnemy() }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,40 +6,43 @@
|
|||||||
@onclick="HandleCardClick"
|
@onclick="HandleCardClick"
|
||||||
@onmouseenter="HandleMouseEnter"
|
@onmouseenter="HandleMouseEnter"
|
||||||
@onmouseleave="HandleMouseLeave">
|
@onmouseleave="HandleMouseLeave">
|
||||||
<!-- VS Code Tab Header -->
|
|
||||||
|
<!-- Holographic / Gloss Highlight Sheen -->
|
||||||
|
<div class="card-gloss-sheen"></div>
|
||||||
|
|
||||||
|
<!-- Prominent Top Header with File Name & Cost -->
|
||||||
<div class="card-vsc-header">
|
<div class="card-vsc-header">
|
||||||
<div class="card-tab-label font-mono overflow-hidden">
|
<div class="card-tab-label font-mono overflow-hidden">
|
||||||
<span style="color: #61afef; font-size: 0.95rem; font-weight: 900;">#</span>
|
<span class="card-type-mini-icon">@GetActionTypeIcon()</span>
|
||||||
<span class="text-truncate" title="@Card.Name" style="font-weight: 800; font-size: 0.82rem;">
|
<span class="text-truncate card-filename @(Card.Upgraded ? "card-upgraded-title" : "")" title="@Card.Name">
|
||||||
@Card.Name.Replace(" ", "").Replace("()", "")@(Card.Upgraded ? "_v2.cs" : ".cs")
|
@Card.Name.Replace(" ", "").Replace("()", "")@(Card.Upgraded ? "_v2.cs" : ".cs")
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-center gap-1">
|
<div class="card-cost-badge font-mono" title="@($"{Card.Cost} CPU Cycles")">
|
||||||
<div class="card-cost-badge font-mono" title="@($"{Card.Cost} CPU Cycles")">
|
@Card.Cost CPU
|
||||||
@Card.Cost CPU
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Card Visual Art Window with Action Type Badge -->
|
<!-- Extra Bold Card Type Banner Ribbon -->
|
||||||
<div class="card-art-box">
|
<div class="card-type-banner @GetActionTagClass() font-mono">
|
||||||
<!-- Action Type Badge Overlay -->
|
<span class="card-banner-icon">@GetActionTypeIcon()</span>
|
||||||
<div class="position-absolute top-0 start-0 m-1">
|
<span class="card-banner-text">@GetCardTypeLabel()</span>
|
||||||
<span class="card-type-tag @GetActionTagClass() font-mono">
|
|
||||||
@GetActionTypeIcon() @Card.Type
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span class="card-art-icon">@GetCardArtIcon(Card.Name)</span>
|
|
||||||
|
|
||||||
@if (Card.Upgraded)
|
@if (Card.Upgraded)
|
||||||
{
|
{
|
||||||
<div class="position-absolute top-0 end-0 m-1 badge bg-warning text-dark font-mono" style="font-size: 0.62rem; font-weight: 900;">
|
<span class="card-banner-upgraded">TIER-1 +</span>
|
||||||
TIER-1 +
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Card Visual Art Window with Themed Watermark Background -->
|
||||||
|
<div class="card-art-box">
|
||||||
|
<!-- Giant Translucent Background Watermark Emblem -->
|
||||||
|
<div class="card-watermark-icon">@GetActionTypeIcon()</div>
|
||||||
|
|
||||||
|
<!-- Ambient Glow Aura behind icon -->
|
||||||
|
<div class="card-art-aura"></div>
|
||||||
|
<span class="card-art-icon">@GetCardArtIcon(Card.Name)</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Mini Code Editor with Gutter and Line Numbers -->
|
<!-- Mini Code Editor with Gutter and Line Numbers -->
|
||||||
<div class="card-editor-viewport font-mono">
|
<div class="card-editor-viewport font-mono">
|
||||||
<!-- Line Numbers Gutter -->
|
<!-- Line Numbers Gutter -->
|
||||||
@@ -51,10 +54,10 @@
|
|||||||
|
|
||||||
<!-- C# Code Content with Syntax Coloring -->
|
<!-- C# Code Content with Syntax Coloring -->
|
||||||
<div class="card-code-content">
|
<div class="card-code-content">
|
||||||
<div style="color: var(--syn-comment); font-size: 0.68rem;">
|
<div class="card-code-comment">
|
||||||
// @(Card.Upgraded ? "Optimized IL" : "Managed code")
|
// @(Card.Upgraded ? "Optimized Tier-1 IL" : "Managed CLR Task")
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size: 0.76rem; font-weight: 800;">
|
<div class="card-code-line">
|
||||||
@FormatCodeSnippet(Card.CodeSnippet)
|
@FormatCodeSnippet(Card.CodeSnippet)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -62,9 +65,9 @@
|
|||||||
|
|
||||||
<!-- Punchy, Large, Bold Card Effect Footer with Action Visuals -->
|
<!-- Punchy, Large, Bold Card Effect Footer with Action Visuals -->
|
||||||
<div class="card-vsc-footer font-mono">
|
<div class="card-vsc-footer font-mono">
|
||||||
<span style="font-size: 0.92rem; font-weight: 900; line-height: 1.25;">
|
<div class="card-footer-text">
|
||||||
@FormatDescription(Card.Description)
|
@FormatDescription(Card.Description)
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -101,9 +104,17 @@
|
|||||||
CardType.Skill => "type-skill",
|
CardType.Skill => "type-skill",
|
||||||
CardType.Power => "type-power",
|
CardType.Power => "type-power",
|
||||||
CardType.Curse => "type-curse",
|
CardType.Curse => "type-curse",
|
||||||
_ => ""
|
_ => "type-skill"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
classes.Add(Card.Rarity switch
|
||||||
|
{
|
||||||
|
CardRarity.Rare => "rarity-rare",
|
||||||
|
CardRarity.Uncommon => "rarity-uncommon",
|
||||||
|
_ => "rarity-common"
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Card.Upgraded) classes.Add("tier-upgraded");
|
||||||
if (IsSelected) classes.Add("selected");
|
if (IsSelected) classes.Add("selected");
|
||||||
if (IsPlayable) classes.Add("playable");
|
if (IsPlayable) classes.Add("playable");
|
||||||
else classes.Add("unaffordable");
|
else classes.Add("unaffordable");
|
||||||
@@ -127,18 +138,42 @@
|
|||||||
_ => "📜"
|
_ => "📜"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private string GetCardTypeLabel()
|
||||||
|
{
|
||||||
|
if (Card.Type == CardType.Attack) return "ATTACK";
|
||||||
|
if (Card.Type == CardType.Skill)
|
||||||
|
{
|
||||||
|
if (Card.Description.Contains("Shield", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
Card.Description.Contains("shield", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return "SHIELD / SKILL";
|
||||||
|
}
|
||||||
|
return "SKILL / UTILITY";
|
||||||
|
}
|
||||||
|
if (Card.Type == CardType.Power) return "POWER / BUFF";
|
||||||
|
if (Card.Type == CardType.Curse) return "DEBT / BUG";
|
||||||
|
return "CARD";
|
||||||
|
}
|
||||||
|
|
||||||
private string GetCardArtIcon(string name)
|
private string GetCardArtIcon(string name)
|
||||||
{
|
{
|
||||||
if (name.Contains("WriteLine", StringComparison.OrdinalIgnoreCase)) return "💻";
|
if (name.Contains("WriteLine", StringComparison.OrdinalIgnoreCase)) return "💻";
|
||||||
if (name.Contains("try", StringComparison.OrdinalIgnoreCase)) return "🛡️";
|
if (name.Contains("try", StringComparison.OrdinalIgnoreCase)) return "🛡️";
|
||||||
if (name.Contains("Collect", StringComparison.OrdinalIgnoreCase)) return "♻️";
|
if (name.Contains("Collect", StringComparison.OrdinalIgnoreCase)) return "♻️";
|
||||||
if (name.Contains("Slice", StringComparison.OrdinalIgnoreCase)) return "⚡";
|
if (name.Contains("Slice", StringComparison.OrdinalIgnoreCase)) return "🗡️";
|
||||||
if (name.Contains("Exception", StringComparison.OrdinalIgnoreCase)) return "💥";
|
if (name.Contains("Exception", StringComparison.OrdinalIgnoreCase)) return "💥";
|
||||||
if (name.Contains("Compilation", StringComparison.OrdinalIgnoreCase)) return "🚀";
|
if (name.Contains("Compilation", StringComparison.OrdinalIgnoreCase)) return "🚀";
|
||||||
if (name.Contains("Sleep", StringComparison.OrdinalIgnoreCase)) return "⏳";
|
if (name.Contains("Sleep", StringComparison.OrdinalIgnoreCase)) return "⏳";
|
||||||
if (name.Contains("Parallel", StringComparison.OrdinalIgnoreCase)) return "🔀";
|
if (name.Contains("Parallel", StringComparison.OrdinalIgnoreCase)) return "🔀";
|
||||||
if (name.Contains("Where", StringComparison.OrdinalIgnoreCase)) return "🔍";
|
if (name.Contains("Where", StringComparison.OrdinalIgnoreCase)) return "🔍";
|
||||||
if (name.Contains("fallback", StringComparison.OrdinalIgnoreCase) || name.Contains("??")) return "🛟";
|
if (name.Contains("fallback", StringComparison.OrdinalIgnoreCase) || name.Contains("??")) return "🛟";
|
||||||
|
if (name.Contains("Task", StringComparison.OrdinalIgnoreCase) || name.Contains("WhenAll", StringComparison.OrdinalIgnoreCase)) return "⚡";
|
||||||
|
if (name.Contains("Cancellation", StringComparison.OrdinalIgnoreCase)) return "🛑";
|
||||||
|
if (name.Contains("Channel", StringComparison.OrdinalIgnoreCase)) return "📡";
|
||||||
|
if (name.Contains("stackalloc", StringComparison.OrdinalIgnoreCase)) return "💾";
|
||||||
|
if (name.Contains("fixed", StringComparison.OrdinalIgnoreCase)) return "📌";
|
||||||
|
if (name.Contains("Expando", StringComparison.OrdinalIgnoreCase) || name.Contains("dynamic", StringComparison.OrdinalIgnoreCase)) return "🔮";
|
||||||
|
if (name.Contains("Random", StringComparison.OrdinalIgnoreCase)) return "🎲";
|
||||||
return "📄";
|
return "📄";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,14 +182,15 @@
|
|||||||
if (string.IsNullOrEmpty(desc)) return new MarkupString("");
|
if (string.IsNullOrEmpty(desc)) return new MarkupString("");
|
||||||
|
|
||||||
var formatted = desc
|
var formatted = desc
|
||||||
.Replace("damage", "<span style='color: #ff6b81;'>⚔️ damage</span>")
|
.Replace("damage", "<span class='desc-dmg'>⚔️ damage</span>")
|
||||||
.Replace("Damage", "<span style='color: #ff6b81;'>⚔️ Damage</span>")
|
.Replace("Damage", "<span class='desc-dmg'>⚔️ Damage</span>")
|
||||||
.Replace("Shield", "<span style='color: #70a1ff;'>🛡️ Shield</span>")
|
.Replace("Shield", "<span class='desc-shield'>🛡️ Shield</span>")
|
||||||
.Replace("piercing", "<span style='color: #00f0ff;'>⚡ piercing</span>")
|
.Replace("shield", "<span class='desc-shield'>🛡️ shield</span>")
|
||||||
.Replace("CPU", "<span style='color: #e5c07b;'>🔋 CPU</span>")
|
.Replace("piercing", "<span class='desc-pierce'>⚡ piercing</span>")
|
||||||
.Replace("Strength", "<span style='color: #2ed573;'>💪 Strength</span>")
|
.Replace("CPU", "<span class='desc-cpu'>🔋 CPU</span>")
|
||||||
.Replace("Weak", "<span style='color: #56b6c2;'>📉 Weak</span>")
|
.Replace("Strength", "<span class='desc-str'>💪 Strength</span>")
|
||||||
.Replace("Memory Leak", "<span style='color: #ffa502;'>⚠️ Memory Leak</span>");
|
.Replace("Weak", "<span class='desc-weak'>📉 Weak</span>")
|
||||||
|
.Replace("Memory Leak", "<span class='desc-leak'>⚠️ Memory Leak</span>");
|
||||||
|
|
||||||
return new MarkupString(formatted);
|
return new MarkupString(formatted);
|
||||||
}
|
}
|
||||||
@@ -164,17 +200,22 @@
|
|||||||
if (string.IsNullOrEmpty(snippet)) return new MarkupString("");
|
if (string.IsNullOrEmpty(snippet)) return new MarkupString("");
|
||||||
|
|
||||||
var highlighted = snippet
|
var highlighted = snippet
|
||||||
.Replace("public", "<span style='color: var(--syn-keyword);'>public</span>")
|
.Replace("public", "<span style='color: #ff79c6; font-weight: 900;'>public</span>")
|
||||||
.Replace("void", "<span style='color: var(--syn-keyword);'>void</span>")
|
.Replace("void", "<span style='color: #8be9fd; font-weight: 900;'>void</span>")
|
||||||
.Replace("var", "<span style='color: var(--syn-keyword);'>var</span>")
|
.Replace("var", "<span style='color: #8be9fd; font-weight: 900;'>var</span>")
|
||||||
.Replace("throw", "<span style='color: var(--syn-control);'>throw</span>")
|
.Replace("throw", "<span style='color: #ff5555; font-weight: 900;'>throw</span>")
|
||||||
.Replace("new", "<span style='color: var(--syn-keyword);'>new</span>")
|
.Replace("new", "<span style='color: #ff79c6; font-weight: 900;'>new</span>")
|
||||||
.Replace("try", "<span style='color: var(--syn-control);'>try</span>")
|
.Replace("try", "<span style='color: #ff79c6; font-weight: 900;'>try</span>")
|
||||||
.Replace("catch", "<span style='color: var(--syn-control);'>catch</span>")
|
.Replace("catch", "<span style='color: #ff79c6; font-weight: 900;'>catch</span>")
|
||||||
.Replace("await", "<span style='color: var(--syn-control);'>await</span>")
|
.Replace("await", "<span style='color: #bd93f9; font-weight: 900;'>await</span>")
|
||||||
.Replace("return", "<span style='color: var(--syn-control);'>return</span>")
|
.Replace("return", "<span style='color: #ff79c6; font-weight: 900;'>return</span>")
|
||||||
.Replace("Bug", "<span style='color: var(--syn-type);'>Bug</span>")
|
.Replace("fixed", "<span style='color: #ff79c6; font-weight: 900;'>fixed</span>")
|
||||||
.Replace("Exception", "<span style='color: var(--syn-type);'>Exception</span>");
|
.Replace("stackalloc", "<span style='color: #ffb86c; font-weight: 900;'>stackalloc</span>")
|
||||||
|
.Replace("Span", "<span style='color: #50fa7b; font-weight: 900;'>Span</span>")
|
||||||
|
.Replace("Task", "<span style='color: #50fa7b; font-weight: 900;'>Task</span>")
|
||||||
|
.Replace("Channel", "<span style='color: #50fa7b; font-weight: 900;'>Channel</span>")
|
||||||
|
.Replace("Bug", "<span style='color: #ff5555; font-weight: 900;'>Bug</span>")
|
||||||
|
.Replace("Exception", "<span style='color: #ff5555; font-weight: 900;'>Exception</span>");
|
||||||
|
|
||||||
return new MarkupString(highlighted);
|
return new MarkupString(highlighted);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,156 +1,262 @@
|
|||||||
@using SlaySharp.Core.Run
|
@using SlaySharp.Core.Run
|
||||||
@using SlaySharp.Core.Models
|
@using SlaySharp.Core.Models
|
||||||
@using SlaySharp.Core.Catalogs
|
@using SlaySharp.Core.Catalogs
|
||||||
@using SlaySharp.Core.Relics
|
@using SlaySharp.Core.Relics
|
||||||
@inject IJSRuntime JS
|
@inject IJSRuntime JS
|
||||||
|
|
||||||
<div class="d-flex flex-column h-100 font-mono" style="background-color: var(--vscode-bg-editor);">
|
<div class="d-flex flex-column h-100 font-mono" style="background-color: var(--vscode-bg-editor);">
|
||||||
<!-- Breadcrumbs -->
|
<!-- VS Code Tab Header -->
|
||||||
<div class="d-flex align-items-center justify-content-between vsc-breadcrumbs font-mono">
|
<div class="vsc-tabs-bar font-mono">
|
||||||
<div class="d-flex align-items-center gap-2">
|
<div class="vsc-tab active">
|
||||||
<span>SlaySharp</span>
|
<span style="color: #61afef; font-weight: 900; font-size: 1rem;">📦</span>
|
||||||
<span>›</span>
|
<span>NuGet: Solution Packages</span>
|
||||||
<span>nuget.org</span>
|
<span style="color: #828997; font-size: 0.75rem;">✕</span>
|
||||||
<span>›</span>
|
|
||||||
<span style="color: #61afef; font-weight: 800;">Package Manager</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="ms-auto d-flex align-items-center gap-3 pe-3">
|
||||||
<div class="d-flex align-items-center gap-3">
|
<span class="text-success fw-bolder" style="font-size: 0.85rem;">🛡️ SLA: @Run.Player.CurrentHp/@Run.Player.MaxHp HP</span>
|
||||||
<span class="text-success fw-bold">🛡️ SLA: @Run.Player.CurrentHp/@Run.Player.MaxHp</span>
|
|
||||||
<span class="badge bg-warning text-dark fs-6 px-3 py-1 fw-bolder">💰 @Run.DevCredits Dev Credits</span>
|
<span class="badge bg-warning text-dark fs-6 px-3 py-1 fw-bolder">💰 @Run.DevCredits Dev Credits</span>
|
||||||
<button class="btn btn-sm btn-outline-success font-mono fw-bold" @onclick="HandleReturnToMap">
|
<button class="btn btn-sm btn-success font-mono fw-bold px-3" @onclick="HandleReturnToMap">
|
||||||
Return to Graph ➔
|
▶ Return to Sprint Map
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Shop Main Area -->
|
<!-- NuGet Sub-Navigation & Package Feed Tabs -->
|
||||||
<div class="flex-grow-1 p-4 overflow-y-auto">
|
<div class="d-flex align-items-center justify-content-between px-3 py-2 border-bottom border-secondary" style="background-color: #21252b;">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4 pb-2 border-bottom border-secondary">
|
<div class="d-flex align-items-center gap-2">
|
||||||
<div>
|
<button class="nuget-tab-btn @(_currentTab == NuGetTab.Browse ? "active" : "")" @onclick="() => _currentTab = NuGetTab.Browse">
|
||||||
<h2 class="text-white fw-bolder mb-0 d-flex align-items-center gap-2">
|
🔍 Browse
|
||||||
<span style="color: #007acc;">📦</span>
|
</button>
|
||||||
<span>NuGet Package Feed & Marketplace</span>
|
<button class="nuget-tab-btn @(_currentTab == NuGetTab.Installed ? "active" : "")" @onclick="() => _currentTab = NuGetTab.Installed">
|
||||||
</h2>
|
📥 Installed (@(Run.Player.Relics.Count + Run.Player.MasterDeck.Count))
|
||||||
<span class="text-muted" style="font-size: 0.85rem;">Source: https://api.nuget.org/v3/index.json</span>
|
</button>
|
||||||
</div>
|
<button class="nuget-tab-btn @(_currentTab == NuGetTab.Updates ? "active" : "")" @onclick="() => _currentTab = NuGetTab.Updates">
|
||||||
|
⬆ Updates (@GetUpgradeableCards().Count)
|
||||||
<!-- Purge Card Service -->
|
</button>
|
||||||
<button class="btn btn-outline-danger font-mono fw-bold px-3 py-2"
|
<button class="nuget-tab-btn @(_currentTab == NuGetTab.Uninstall ? "active" : "")" @onclick="() => _currentTab = NuGetTab.Uninstall">
|
||||||
disabled="@(Run.DevCredits < 75 || _hasPurgedCard)"
|
🗑️ Clean Debt
|
||||||
@onclick="() => _showPurgeModal = true">
|
|
||||||
🗑️ Clean Tech Debt (-1 Card) - 75 Credits
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (!string.IsNullOrEmpty(_message))
|
<div class="d-flex align-items-center gap-2">
|
||||||
{
|
<span class="text-muted" style="font-size: 0.75rem;">Package source:</span>
|
||||||
<div class="alert alert-info py-2 px-3 mb-4 font-mono fw-bold d-flex justify-content-between align-items-center">
|
<span class="badge bg-dark border border-secondary text-info font-mono" style="font-size: 0.75rem;">
|
||||||
<span>@_message</span>
|
nuget.org (v3 API)
|
||||||
<button class="btn-close btn-close-white" @onclick="() => _message = null"></button>
|
</span>
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="row g-4 mb-4">
|
|
||||||
<!-- Section 1: Relics (NuGet Packages) -->
|
|
||||||
<div class="col-12">
|
|
||||||
<h4 class="text-info fw-bold mb-3 d-flex align-items-center gap-2">
|
|
||||||
<span>⚡</span>
|
|
||||||
<span>NuGets & Tooling:</span>
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
<div class="d-flex gap-3 flex-wrap">
|
|
||||||
@foreach (var relic in _shopRelics)
|
|
||||||
{
|
|
||||||
var isOwned = Run.Player.Relics.Any(r => r.Id == relic.Id);
|
|
||||||
var isPurchased = _purchasedRelicIds.Contains(relic.Id);
|
|
||||||
|
|
||||||
<div class="card p-3 shadow-sm flex-fill" style="min-width: 280px; max-width: 360px; background-color: #21252b; border: 2px solid #383e4a; border-radius: 10px;">
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
||||||
<span style="font-size: 2rem;">@relic.Icon</span>
|
|
||||||
<span class="badge @(isOwned || isPurchased ? "bg-secondary" : "bg-warning text-dark") fw-bold" style="font-size: 0.85rem;">
|
|
||||||
@(isOwned || isPurchased ? "INSTALLED" : $"{_relicCost} Credits")
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="fw-bolder text-white fs-5 mb-1">@relic.Name</div>
|
|
||||||
<div class="text-muted mb-2" style="font-size: 0.75rem;">@relic.PackageVersion</div>
|
|
||||||
<p class="text-light mb-3" style="font-size: 0.85rem; min-height: 48px;">
|
|
||||||
@relic.Description
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<button class="btn btn-primary font-mono fw-bold mt-auto"
|
|
||||||
disabled="@(isOwned || isPurchased || Run.DevCredits < _relicCost)"
|
|
||||||
@onclick="() => BuyRelic(relic)">
|
|
||||||
@(isOwned || isPurchased ? "✓ Installed" : "Install Package")
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Section 2: Cards (Roslyn Snippets) -->
|
|
||||||
<div class="col-12 mt-4">
|
|
||||||
<h4 class="text-success fw-bold mb-3 d-flex align-items-center gap-2">
|
|
||||||
<span>🌿</span>
|
|
||||||
<span>Roslyn Syntactic Snippets (Cards):</span>
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
<div class="d-flex gap-4 flex-wrap justify-content-start">
|
|
||||||
@for (int i = 0; i < _shopCards.Count; i++)
|
|
||||||
{
|
|
||||||
var card = _shopCards[i];
|
|
||||||
var cost = _cardCosts[i];
|
|
||||||
var isBought = _purchasedCardIds.Contains(card.Id);
|
|
||||||
|
|
||||||
<div class="d-flex flex-column align-items-center gap-2">
|
|
||||||
<CardView Card="@card" IsPlayable="false" />
|
|
||||||
<button class="btn @(isBought ? "btn-secondary" : "btn-success") font-mono fw-bold px-4 py-2 shadow"
|
|
||||||
disabled="@(isBought || Run.DevCredits < cost)"
|
|
||||||
@onclick="() => BuyCard(card, cost)">
|
|
||||||
@(isBought ? "✓ Acquired" : $"Buy ({cost} Credits)")
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Feedback Message Banner -->
|
||||||
|
@if (!string.IsNullOrEmpty(_message))
|
||||||
|
{
|
||||||
|
<div class="alert alert-info py-2 px-3 m-2 font-mono fw-bold d-flex justify-content-between align-items-center border-0 shadow-sm" style="background-color: #1f2a3d; color: #61afef;">
|
||||||
|
<span>@_message</span>
|
||||||
|
<button class="btn-close btn-close-white" @onclick="() => _message = null"></button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- Tab Content Area -->
|
||||||
|
<div class="flex-grow-1 p-4 overflow-y-auto" style="min-height: 0;">
|
||||||
|
@switch (_currentTab)
|
||||||
|
{
|
||||||
|
case NuGetTab.Browse:
|
||||||
|
<!-- Browse Packages & Syntax Modules -->
|
||||||
|
<div class="d-flex flex-column gap-4">
|
||||||
|
<!-- NuGet Packages (Relics) -->
|
||||||
|
<div>
|
||||||
|
<h4 class="text-info fw-bold mb-3 d-flex align-items-center gap-2">
|
||||||
|
<span>📦</span>
|
||||||
|
<span>Featured NuGet Packages (Tooling & Middleware):</span>
|
||||||
|
</h4>
|
||||||
|
<div class="d-flex gap-3 flex-wrap">
|
||||||
|
@foreach (var relic in _shopRelics)
|
||||||
|
{
|
||||||
|
var isOwned = Run.Player.Relics.Any(r => r.Id == relic.Id);
|
||||||
|
var isPurchased = _purchasedRelicIds.Contains(relic.Id);
|
||||||
|
|
||||||
|
<div class="card p-3 shadow-sm flex-fill" style="min-width: 280px; max-width: 360px; background-color: #21252b; border: 2px solid #383e4a; border-radius: 10px;">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<span style="font-size: 2rem;">@relic.Icon</span>
|
||||||
|
<span class="badge @(isOwned || isPurchased ? "bg-secondary" : "bg-warning text-dark") fw-bold" style="font-size: 0.85rem;">
|
||||||
|
@(isOwned || isPurchased ? "INSTALLED" : $"{_relicCost} Credits")
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="fw-bolder text-white fs-5 mb-1">@relic.Name</div>
|
||||||
|
<div class="text-muted mb-2" style="font-size: 0.75rem;">@relic.PackageVersion</div>
|
||||||
|
<p class="text-light mb-3" style="font-size: 0.85rem; min-height: 48px;">
|
||||||
|
@relic.Description
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<button class="btn btn-primary font-mono fw-bold mt-auto"
|
||||||
|
disabled="@(isOwned || isPurchased || Run.DevCredits < _relicCost)"
|
||||||
|
@onclick="() => BuyRelic(relic)">
|
||||||
|
@(isOwned || isPurchased ? "✓ Installed" : "Install Package")
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Roslyn Syntax Cards -->
|
||||||
|
<div class="mt-2">
|
||||||
|
<h4 class="text-success fw-bold mb-3 d-flex align-items-center gap-2">
|
||||||
|
<span>🌿</span>
|
||||||
|
<span>Roslyn Syntactic Snippets (Deck Additions):</span>
|
||||||
|
</h4>
|
||||||
|
<div class="d-flex gap-4 flex-wrap justify-content-start">
|
||||||
|
@for (int i = 0; i < _shopCards.Count; i++)
|
||||||
|
{
|
||||||
|
var card = _shopCards[i];
|
||||||
|
var cost = _cardCosts[i];
|
||||||
|
var isBought = _purchasedCardIds.Contains(card.Id);
|
||||||
|
|
||||||
|
<div class="d-flex flex-column align-items-center gap-2">
|
||||||
|
<CardView Card="@card" IsPlayable="false" />
|
||||||
|
<button class="btn @(isBought ? "btn-secondary" : "btn-success") font-mono fw-bold px-4 py-2 shadow"
|
||||||
|
disabled="@(isBought || Run.DevCredits < cost)"
|
||||||
|
@onclick="() => BuyCard(card, cost)">
|
||||||
|
@(isBought ? "✓ Acquired" : $"Buy ({cost} Credits)")
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NuGetTab.Installed:
|
||||||
|
<!-- Installed Packages & Cards -->
|
||||||
|
<div class="d-flex flex-column gap-4">
|
||||||
|
<div>
|
||||||
|
<h4 class="text-info fw-bold mb-3">📦 Installed Tooling & NuGet Packages (@Run.Player.Relics.Count)</h4>
|
||||||
|
<div class="d-flex flex-wrap gap-3">
|
||||||
|
@foreach (var relic in Run.Player.Relics)
|
||||||
|
{
|
||||||
|
<div class="card p-3" style="min-width: 260px; max-width: 340px; background-color: #21252b; border: 1px solid #383e4a; border-radius: 8px;">
|
||||||
|
<div class="d-flex align-items-center gap-2 mb-2">
|
||||||
|
<span style="font-size: 1.8rem;">@relic.Icon</span>
|
||||||
|
<div>
|
||||||
|
<div class="text-white fw-bold">@relic.Name</div>
|
||||||
|
<div class="text-muted" style="font-size: 0.72rem;">@relic.PackageVersion</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="text-light mb-0" style="font-size: 0.82rem;">@relic.Description</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 class="text-success fw-bold mb-3">🌿 Active Solution Deck (@Run.Player.MasterDeck.Count Cards)</h4>
|
||||||
|
<div class="d-flex flex-wrap gap-3">
|
||||||
|
@foreach (var card in Run.Player.MasterDeck)
|
||||||
|
{
|
||||||
|
<CardView Card="@card" IsPlayable="false" />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NuGetTab.Updates:
|
||||||
|
<!-- Upgrade Cards to Tier-1 '+' -->
|
||||||
|
<div>
|
||||||
|
<h4 class="text-warning fw-bold mb-2">⬆ NuGet Package Updates: JIT Optimize to Tier-1 (+) (45 Credits each)</h4>
|
||||||
|
<p class="text-muted mb-4" style="font-size: 0.85rem;">Upgrade unmanaged IL routines with Tiered Compilation AggressiveOptimization.</p>
|
||||||
|
|
||||||
|
@{
|
||||||
|
var upgradeables = GetUpgradeableCards();
|
||||||
|
}
|
||||||
|
@if (upgradeables.Any())
|
||||||
|
{
|
||||||
|
<div class="d-flex flex-wrap gap-4">
|
||||||
|
@foreach (var card in upgradeables)
|
||||||
|
{
|
||||||
|
<div class="d-flex flex-column align-items-center gap-2">
|
||||||
|
<CardView Card="@card" IsPlayable="false" />
|
||||||
|
<button class="btn btn-warning font-mono fw-bold px-3 py-1 shadow"
|
||||||
|
disabled="@(Run.DevCredits < 45)"
|
||||||
|
@onclick="() => UpgradeCard(card)">
|
||||||
|
⬆ Optimize Card (45 C)
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="card p-4 text-center text-muted" style="background-color: #21252b; border: 1px dashed #383e4a;">
|
||||||
|
✓ All packages in solution are running on the latest Tier-1 runtime version.
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NuGetTab.Uninstall:
|
||||||
|
<!-- Purge / Refactor Debt -->
|
||||||
|
<div>
|
||||||
|
<h4 class="text-danger fw-bold mb-2">🗑️ Technical Debt Refactoring (Purge 1 Card for 75 Credits)</h4>
|
||||||
|
<p class="text-muted mb-4" style="font-size: 0.85rem;">Uninstall legacy code blocks from your active solution deck.</p>
|
||||||
|
|
||||||
|
@if (_hasPurgedCard)
|
||||||
|
{
|
||||||
|
<div class="alert alert-secondary py-3 text-center">
|
||||||
|
✓ Technical debt purge quota used for this sprint site.
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="d-flex flex-wrap gap-3">
|
||||||
|
@foreach (var card in Run.Player.MasterDeck)
|
||||||
|
{
|
||||||
|
<div class="d-flex flex-column align-items-center gap-2">
|
||||||
|
<CardView Card="@card" IsPlayable="false" />
|
||||||
|
<button class="btn btn-danger btn-sm font-mono fw-bold px-3 py-1 shadow"
|
||||||
|
disabled="@(Run.DevCredits < 75)"
|
||||||
|
@onclick="() => PurgeCard(card)">
|
||||||
|
🗑️ Purge @card.Name
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Card Purge Modal -->
|
<style>
|
||||||
@if (_showPurgeModal)
|
.nuget-tab-btn {
|
||||||
{
|
background: transparent;
|
||||||
<div class="modal d-block" tabindex="-1" style="background: rgba(0,0,0,0.85); backdrop-filter: blur(6px);">
|
border: none;
|
||||||
<div class="modal-dialog modal-xl modal-dialog-scrollable">
|
color: #abb2bf;
|
||||||
<div class="modal-content text-white" style="background-color: #181a1f; border: 2px solid #383e4a; border-radius: 12px;">
|
font-size: 0.88rem;
|
||||||
<div class="modal-header border-secondary py-3" style="background-color: #21252b;">
|
font-weight: 800;
|
||||||
<h5 class="modal-title font-mono fw-bold text-danger">
|
padding: 6px 16px;
|
||||||
🗑️ Select Technical Debt Card to Purge (-75 Credits)
|
border-radius: 6px;
|
||||||
</h5>
|
cursor: pointer;
|
||||||
<button type="button" class="btn-close btn-close-white" @onclick="() => _showPurgeModal = false"></button>
|
transition: all 0.2s ease;
|
||||||
</div>
|
|
||||||
<div class="modal-body p-4">
|
|
||||||
<div class="d-flex flex-wrap justify-content-center gap-3">
|
|
||||||
@foreach (var card in Run.Player.MasterDeck)
|
|
||||||
{
|
|
||||||
<div class="d-flex flex-column align-items-center gap-2">
|
|
||||||
<CardView Card="@card" IsPlayable="false" />
|
|
||||||
<button class="btn btn-danger btn-sm font-mono fw-bold px-3 py-1 shadow"
|
|
||||||
@onclick="() => PurgeCard(card)">
|
|
||||||
🗑️ Purge @card.Name
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nuget-tab-btn:hover {
|
||||||
|
background: #282c34;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nuget-tab-btn.active {
|
||||||
|
background: #007acc;
|
||||||
|
color: #ffffff;
|
||||||
|
box-shadow: 0 0 12px rgba(0, 122, 204, 0.6);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[Parameter] public GameRun Run { get; set; } = null!;
|
[Parameter] public GameRun Run { get; set; } = null!;
|
||||||
|
|
||||||
|
private enum NuGetTab { Browse, Installed, Updates, Uninstall }
|
||||||
|
private NuGetTab _currentTab = NuGetTab.Browse;
|
||||||
|
|
||||||
private List<Card> _shopCards = new();
|
private List<Card> _shopCards = new();
|
||||||
private List<int> _cardCosts = new();
|
private List<int> _cardCosts = new();
|
||||||
private List<Relic> _shopRelics = new();
|
private List<Relic> _shopRelics = new();
|
||||||
@@ -158,7 +264,6 @@
|
|||||||
private readonly HashSet<string> _purchasedRelicIds = new();
|
private readonly HashSet<string> _purchasedRelicIds = new();
|
||||||
private int _relicCost = 110;
|
private int _relicCost = 110;
|
||||||
private bool _hasPurgedCard = false;
|
private bool _hasPurgedCard = false;
|
||||||
private bool _showPurgeModal = false;
|
|
||||||
private string? _message;
|
private string? _message;
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
@@ -174,10 +279,16 @@
|
|||||||
_shopRelics = new List<Relic>
|
_shopRelics = new List<Relic>
|
||||||
{
|
{
|
||||||
new PollyRelic(),
|
new PollyRelic(),
|
||||||
new DapperRelic()
|
new DapperRelic(),
|
||||||
|
new UnsafeCompilerRelic()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<Card> GetUpgradeableCards()
|
||||||
|
{
|
||||||
|
return Run.Player.MasterDeck.Where(c => !c.Upgraded).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
private void BuyCard(Card card, int cost)
|
private void BuyCard(Card card, int cost)
|
||||||
{
|
{
|
||||||
if (Run.DevCredits >= cost)
|
if (Run.DevCredits >= cost)
|
||||||
@@ -204,14 +315,25 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpgradeCard(Card card)
|
||||||
|
{
|
||||||
|
if (Run.DevCredits >= 45 && !card.Upgraded)
|
||||||
|
{
|
||||||
|
Run.DevCredits -= 45;
|
||||||
|
card.Upgrade();
|
||||||
|
_message = $"Upgraded {card.Name} to Tier-1 (+) Optimized IL!";
|
||||||
|
_ = JS.InvokeVoidAsync("slayAudio.playVictorySound");
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void PurgeCard(Card card)
|
private void PurgeCard(Card card)
|
||||||
{
|
{
|
||||||
if (Run.DevCredits >= 75)
|
if (Run.DevCredits >= 75 && !_hasPurgedCard)
|
||||||
{
|
{
|
||||||
Run.DevCredits -= 75;
|
Run.DevCredits -= 75;
|
||||||
Run.Player.MasterDeck.Remove(card);
|
Run.Player.MasterDeck.Remove(card);
|
||||||
_hasPurgedCard = true;
|
_hasPurgedCard = true;
|
||||||
_showPurgeModal = false;
|
|
||||||
_message = $"Purged {card.Name} from Master Deck. Technical debt reduced!";
|
_message = $"Purged {card.Name} from Master Deck. Technical debt reduced!";
|
||||||
_ = JS.InvokeVoidAsync("slayAudio.playShieldSound");
|
_ = JS.InvokeVoidAsync("slayAudio.playShieldSound");
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
|
|||||||
+425
-124
@@ -5,6 +5,7 @@
|
|||||||
@using SlaySharp.Core.Catalogs
|
@using SlaySharp.Core.Catalogs
|
||||||
@using SlaySharp.Core.Relics
|
@using SlaySharp.Core.Relics
|
||||||
@using SlaySharp.Web.Components
|
@using SlaySharp.Web.Components
|
||||||
|
@using System.Text.Json
|
||||||
@inject IJSRuntime JS
|
@inject IJSRuntime JS
|
||||||
|
|
||||||
<PageTitle>SlaySharp - Visual Studio Code Edition</PageTitle>
|
<PageTitle>SlaySharp - Visual Studio Code Edition</PageTitle>
|
||||||
@@ -101,15 +102,85 @@ else
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Animated Terminal Boot Badge & Replay Intro CTA -->
|
<!-- Animated Terminal Boot Badge, Audio Toggle & History CTA -->
|
||||||
<div class="d-flex align-items-center gap-3">
|
<div class="d-flex align-items-center gap-2">
|
||||||
<button class="btn btn-outline-secondary btn-sm font-mono fw-bold px-3 py-2" @onclick="() => _showIntroBoot = true">
|
<button class="btn btn-outline-warning btn-sm font-mono fw-bold px-3 py-2" @onclick="() => _showHistoryModal = true">
|
||||||
📺 Replay Boot Intro
|
📜 Git Log (@_runHistory.Count)
|
||||||
</button>
|
</button>
|
||||||
<div class="card p-2 px-3 text-start shadow" style="background-color: rgba(24, 26, 31, 0.92); border: 1px solid #383e4a; border-radius: 8px;">
|
<button class="btn btn-outline-info btn-sm font-mono fw-bold px-3 py-2" @onclick="ToggleAudioMute">
|
||||||
<div class="text-muted" style="font-size: 0.72rem; font-weight: 800;">RUNTIME ENVIRONMENT</div>
|
@(_isMuted ? "🔇 Audio OFF" : "🔊 Audio ON")
|
||||||
<div class="text-success font-mono" style="font-size: 0.85rem; font-weight: 800;">
|
</button>
|
||||||
🟢 .NET 8.0 CLR [AggressiveTieredOpt]
|
<button class="btn btn-outline-secondary btn-sm font-mono fw-bold px-3 py-2" @onclick="() => _showIntroBoot = true">
|
||||||
|
📺 Replay Boot
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Character Archetype Selection Bar -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<h5 class="text-white fw-bold mb-3 d-flex align-items-center gap-2">
|
||||||
|
<span style="color: #61afef;">👨💻</span>
|
||||||
|
<span>SELECT DEVELOPER ARCHETYPE:</span>
|
||||||
|
</h5>
|
||||||
|
<div class="row g-3">
|
||||||
|
<!-- Class 1: Principal Architect -->
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card p-3 h-100 class-select-card @(_selectedArchetype == DeveloperArchetype.PrincipalArchitect ? "class-selected" : "")"
|
||||||
|
@onclick="() => SelectArchetype(DeveloperArchetype.PrincipalArchitect)">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<span style="font-size: 2rem;">👨💻</span>
|
||||||
|
<span class="badge bg-primary text-white font-mono">70 SLA • 3 CPU</span>
|
||||||
|
</div>
|
||||||
|
<div class="fw-bolder text-white fs-5 mb-1">Principal Architect</div>
|
||||||
|
<div class="text-info font-mono mb-2" style="font-size: 0.76rem; font-weight: 800;">[Tiered Compilation & Serilog]</div>
|
||||||
|
<p class="text-light mb-2" style="font-size: 0.82rem;">
|
||||||
|
Balanced enterprise runtime. Scales attack strength and structured diagnostic logging shield.
|
||||||
|
</p>
|
||||||
|
<div class="d-flex flex-wrap gap-1 mt-auto">
|
||||||
|
<span class="badge bg-dark border border-secondary text-muted" style="font-size: 0.72rem;">Newtonsoft.Json</span>
|
||||||
|
<span class="badge bg-dark border border-secondary text-muted" style="font-size: 0.72rem;">BenchmarkDotNet</span>
|
||||||
|
<span class="badge bg-dark border border-secondary text-muted" style="font-size: 0.72rem;">Serilog</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Class 2: Async Concurrency Master -->
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card p-3 h-100 class-select-card @(_selectedArchetype == DeveloperArchetype.AsyncConcurrencyMaster ? "class-selected" : "")"
|
||||||
|
@onclick="() => SelectArchetype(DeveloperArchetype.AsyncConcurrencyMaster)">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<span style="font-size: 2rem;">⚡</span>
|
||||||
|
<span class="badge bg-success text-white font-mono">65 SLA • 4 CPU</span>
|
||||||
|
</div>
|
||||||
|
<div class="fw-bolder text-white fs-5 mb-1">Async Master</div>
|
||||||
|
<div class="text-success font-mono mb-2" style="font-size: 0.76rem; font-weight: 800;">[Channels & Task.WhenAll]</div>
|
||||||
|
<p class="text-light mb-2" style="font-size: 0.82rem;">
|
||||||
|
High-throughput non-blocking concurrency. 4 CPU cycles to unleash massive Task.WhenAll bursts.
|
||||||
|
</p>
|
||||||
|
<div class="d-flex flex-wrap gap-1 mt-auto">
|
||||||
|
<span class="badge bg-dark border border-secondary text-muted" style="font-size: 0.72rem;">Polly v8</span>
|
||||||
|
<span class="badge bg-dark border border-secondary text-muted" style="font-size: 0.72rem;">System.Threading.Channels</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Class 3: Low-Level Unsafe Hacker -->
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card p-3 h-100 class-select-card @(_selectedArchetype == DeveloperArchetype.UnsafeMemoryHacker ? "class-selected" : "")"
|
||||||
|
@onclick="() => SelectArchetype(DeveloperArchetype.UnsafeMemoryHacker)">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<span style="font-size: 2rem;">💀</span>
|
||||||
|
<span class="badge bg-danger text-white font-mono">60 SLA • 3 CPU</span>
|
||||||
|
</div>
|
||||||
|
<div class="fw-bolder text-white fs-5 mb-1">Unsafe Memory Hacker</div>
|
||||||
|
<div class="text-danger font-mono mb-2" style="font-size: 0.76rem; font-weight: 800;">[stackalloc & Fixed Pointers]</div>
|
||||||
|
<p class="text-light mb-2" style="font-size: 0.82rem;">
|
||||||
|
Zero-heap direct pointer manipulation. Sacrifices HP for devastating piercing crits.
|
||||||
|
</p>
|
||||||
|
<div class="d-flex flex-wrap gap-1 mt-auto">
|
||||||
|
<span class="badge bg-dark border border-secondary text-muted" style="font-size: 0.72rem;">Unsafe Compiler</span>
|
||||||
|
<span class="badge bg-dark border border-secondary text-muted" style="font-size: 0.72rem;">Dapper ORM</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -119,122 +190,108 @@ else
|
|||||||
<!-- Left Column: Start Campaigns & Encounters -->
|
<!-- Left Column: Start Campaigns & Encounters -->
|
||||||
<div class="col-lg-7">
|
<div class="col-lg-7">
|
||||||
<!-- Main Hero CTA: Act 1 Roguelike Campaign -->
|
<!-- Main Hero CTA: Act 1 Roguelike Campaign -->
|
||||||
<div class="card p-4 mb-4 shadow-lg hero-campaign-card"
|
<div class="card p-4 mb-4 shadow-lg hero-campaign-card"
|
||||||
style="background: linear-gradient(135deg, rgba(27, 47, 35, 0.92) 0%, rgba(33, 37, 43, 0.95) 100%); border: 2px solid #98c379; border-radius: 14px; cursor: pointer; backdrop-filter: blur(8px);"
|
style="background: linear-gradient(135deg, rgba(27, 47, 35, 0.92) 0%, rgba(33, 37, 43, 0.95) 100%); border: 2px solid #98c379; border-radius: 14px; cursor: pointer; backdrop-filter: blur(8px);"
|
||||||
@onclick="TriggerCampaignLaunchAsync">
|
@onclick="TriggerCampaignLaunchAsync">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
<div class="d-flex align-items-center gap-3">
|
<div class="d-flex align-items-center gap-3">
|
||||||
<span style="font-size: 2.5rem;" class="pulse-icon">🗺️</span>
|
<span style="font-size: 2.5rem;" class="pulse-icon">🗺️</span>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-success fw-bolder mb-0">START CAMPAIGN (@GetArchetypeName())</h3>
|
||||||
|
<span class="text-muted" style="font-size: 0.82rem; font-weight: 800;">Procedural CI/CD Pipeline & Production Bosses</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="badge bg-success text-white fs-6 px-4 py-3 fw-bolder shadow launch-btn">
|
||||||
|
Launch Sprint (F5) ➔
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-white mb-0" style="font-size: 0.95rem; font-weight: 700; line-height: 1.5;">
|
||||||
|
Navigate the 7-floor procedural Git branch graph with your selected archetype. Patch production outages, refactor legacy monoliths, and decouple the K8s cluster!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 class="text-white mb-3 d-flex align-items-center gap-2 fw-bold">
|
||||||
|
<span style="color: #61afef; font-size: 1.5rem;">⚡</span>
|
||||||
|
<span>Quick Incident Sandboxes (Single Encounters)</span>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<div class="d-flex flex-column gap-3">
|
||||||
|
<!-- Config 1 -->
|
||||||
|
<div class="card p-3 shadow-sm sandbox-card" style="background-color: rgba(33, 37, 43, 0.92); border: 2px solid #383e4a; border-radius: 10px; cursor: pointer; backdrop-filter: blur(6px);"
|
||||||
|
@onclick="() => StartSandboxEncounter(1)">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-1">
|
||||||
|
<span class="text-danger fw-bolder fs-6">💥 [SEV-1] NullReference & Off-By-One</span>
|
||||||
|
<span class="badge bg-danger text-white fs-6 px-3 py-1">Quick Battle ➔</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-white mb-0" style="font-size: 0.85rem; font-weight: 700;">
|
||||||
|
Patch NullReferenceException and Off-By-One Bug.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Config 2 -->
|
||||||
|
<div class="card p-3 shadow-sm sandbox-card" style="background-color: rgba(33, 37, 43, 0.92); border: 2px solid #383e4a; border-radius: 10px; cursor: pointer; backdrop-filter: blur(6px);"
|
||||||
|
@onclick="() => StartSandboxEncounter(2)">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-1">
|
||||||
|
<span class="text-warning fw-bolder fs-6">🔒 [ELITE] Thread Deadlock & Memory Leak</span>
|
||||||
|
<span class="badge bg-warning text-dark fs-6 px-3 py-1">Elite Battle ➔</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-white mb-0" style="font-size: 0.85rem; font-weight: 700;">
|
||||||
|
Overcome thread contention and memory leaks.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Config 3 -->
|
||||||
|
<div class="card p-3 shadow-sm sandbox-card" style="background-color: rgba(33, 37, 43, 0.92); border: 2px solid #383e4a; border-radius: 10px; cursor: pointer; backdrop-filter: blur(6px);"
|
||||||
|
@onclick="() => StartSandboxEncounter(3)">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-1">
|
||||||
|
<span class="text-info fw-bolder fs-6">👑 [ACT-BOSS] Kubernetes Cluster Boss (110 HP)</span>
|
||||||
|
<span class="badge bg-primary text-white fs-6 px-3 py-1">Boss Battle ➔</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-white mb-0" style="font-size: 0.85rem; font-weight: 700;">
|
||||||
|
Battle the 110 HP K8s Multi-Pod Cluster Boss with OOM Killer routines.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right Column: Workspace & Diagnostics Configuration -->
|
||||||
|
<div class="col-lg-5">
|
||||||
|
<h4 class="text-white mb-3 d-flex align-items-center gap-2 fw-bold">
|
||||||
|
<span style="color: #56b6c2; font-size: 1.5rem;">⚙️</span>
|
||||||
|
<span>Selected Archetype Specs</span>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<div class="card p-4 mb-3" style="background-color: rgba(33, 37, 43, 0.92); border: 2px solid #383e4a; border-radius: 12px; backdrop-filter: blur(6px);">
|
||||||
|
<div class="d-flex align-items-center gap-2 mb-3">
|
||||||
|
<span style="font-size: 2.2rem;">@GetArchetypeIcon()</span>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-success fw-bolder mb-0">START CAMPAIGN: ACT 1</h3>
|
<div class="text-info fw-bolder fs-5">@GetArchetypeName()</div>
|
||||||
<span class="text-muted" style="font-size: 0.82rem; font-weight: 800;">CI/CD Pipeline & Legacy Monolith Boss</span>
|
<div class="text-white" style="font-size: 0.85rem; font-weight: 800;">
|
||||||
|
@GetArchetypeEnergy() CPU Cycles / @GetArchetypeHp() SLA HP
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="badge bg-success text-white fs-6 px-4 py-3 fw-bolder shadow launch-btn">
|
|
||||||
Launch Run (F5) ➔
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<p class="text-white mb-0" style="font-size: 0.95rem; font-weight: 700; line-height: 1.5;">
|
|
||||||
Navigate the 7-floor procedural Git branch graph. Patch production bugs, resolve StackOverflow dilemmas, refactor modules at coffee breaks, and face the Legacy Monolith Boss!
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h4 class="text-white mb-3 d-flex align-items-center gap-2 fw-bold">
|
<div class="text-muted fw-bold mb-1" style="font-size: 0.78rem;">ARCHETYPE MECHANICS:</div>
|
||||||
<span style="color: #61afef; font-size: 1.5rem;">⚡</span>
|
<div class="d-flex flex-column gap-2 mb-2">
|
||||||
<span>Quick Incident Sandboxes (Single Encounters)</span>
|
<div class="badge bg-dark border border-secondary text-white p-2 d-flex align-items-center justify-content-between" style="font-size: 0.85rem; font-weight: 800;">
|
||||||
</h4>
|
<span>🎲 JIT Branch Prediction</span>
|
||||||
|
<span class="text-warning">15% Crit (1.5x Dmg)</span>
|
||||||
<div class="d-flex flex-column gap-3">
|
</div>
|
||||||
<!-- Config 1 -->
|
<div class="badge bg-dark border border-secondary text-white p-2 d-flex align-items-center justify-content-between" style="font-size: 0.85rem; font-weight: 800;">
|
||||||
<div class="card p-3 shadow-sm sandbox-card" style="background-color: rgba(33, 37, 43, 0.92); border: 2px solid #383e4a; border-radius: 10px; cursor: pointer; backdrop-filter: blur(6px);"
|
<span>📦 VS NuGet Package Manager</span>
|
||||||
@onclick="() => StartSandboxEncounter(1)">
|
<span class="text-info">4-Tab Marketplace</span>
|
||||||
<div class="d-flex justify-content-between align-items-center mb-1">
|
</div>
|
||||||
<span class="text-danger fw-bolder fs-6">💥 [SEV-1] NullReference & Off-By-One</span>
|
<div class="badge bg-dark border border-secondary text-white p-2 d-flex align-items-center justify-content-between" style="font-size: 0.85rem; font-weight: 800;">
|
||||||
<span class="badge bg-danger text-white fs-6 px-3 py-1">Quick Battle ➔</span>
|
<span>🎵 Web Audio Synthwave BGM</span>
|
||||||
|
<span class="text-success">Procedural Music</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-white mb-0" style="font-size: 0.85rem; font-weight: 700;">
|
|
||||||
Patch NullReferenceException (45 HP) and Off-By-One Bug (22 HP).
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Config 2 -->
|
|
||||||
<div class="card p-3 shadow-sm sandbox-card" style="background-color: rgba(33, 37, 43, 0.92); border: 2px solid #383e4a; border-radius: 10px; cursor: pointer; backdrop-filter: blur(6px);"
|
|
||||||
@onclick="() => StartSandboxEncounter(2)">
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-1">
|
|
||||||
<span class="text-warning fw-bolder fs-6">👻 [SEV-2] Memory Leak Spectre</span>
|
|
||||||
<span class="badge bg-warning text-dark fs-6 px-3 py-1">Quick Battle ➔</span>
|
|
||||||
</div>
|
|
||||||
<p class="text-white mb-0" style="font-size: 0.85rem; font-weight: 700;">
|
|
||||||
Dispose unmanaged memory from Memory Leak Spectre (34 HP) with Shield gain.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Config 3 -->
|
|
||||||
<div class="card p-3 shadow-sm sandbox-card" style="background-color: rgba(33, 37, 43, 0.92); border: 2px solid #383e4a; border-radius: 10px; cursor: pointer; backdrop-filter: blur(6px);"
|
|
||||||
@onclick="() => StartSandboxEncounter(3)">
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-1">
|
|
||||||
<span class="text-info fw-bolder fs-6">👑 [ACT-BOSS] Legacy Monolith (90 HP)</span>
|
|
||||||
<span class="badge bg-primary text-white fs-6 px-3 py-1">Boss Battle ➔</span>
|
|
||||||
</div>
|
|
||||||
<p class="text-white mb-0" style="font-size: 0.85rem; font-weight: 700;">
|
|
||||||
Battle the 90 HP Legacy Monolith Boss with spaghetti code and thread locks.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Right Column: Workspace & Diagnostics Configuration -->
|
|
||||||
<div class="col-lg-5">
|
|
||||||
<h4 class="text-white mb-3 d-flex align-items-center gap-2 fw-bold">
|
|
||||||
<span style="color: #56b6c2; font-size: 1.5rem;">⚙️</span>
|
|
||||||
<span>Active Profile</span>
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
<div class="card p-4 mb-3" style="background-color: rgba(33, 37, 43, 0.92); border: 2px solid #383e4a; border-radius: 12px; backdrop-filter: blur(6px);">
|
|
||||||
<div class="d-flex align-items-center gap-2 mb-3">
|
|
||||||
<span style="font-size: 2.2rem;">👨💻</span>
|
|
||||||
<div>
|
|
||||||
<div class="text-info fw-bolder fs-5">Principal .NET Architect</div>
|
|
||||||
<div class="text-white" style="font-size: 0.85rem; font-weight: 800;">3 CPU Cycles / 70 SLA HP</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-muted fw-bold mb-1" style="font-size: 0.78rem;">STARTER NUGET PACKAGES:</div>
|
|
||||||
<div class="d-flex flex-column gap-2 mb-2">
|
|
||||||
<div class="badge bg-dark border border-secondary text-white p-2 d-flex align-items-center justify-content-between" style="font-size: 0.85rem; font-weight: 800;">
|
|
||||||
<span>📜 Newtonsoft.Json</span>
|
|
||||||
<span class="text-info">+1 Card T1</span>
|
|
||||||
</div>
|
|
||||||
<div class="badge bg-dark border border-secondary text-white p-2 d-flex align-items-center justify-content-between" style="font-size: 0.85rem; font-weight: 800;">
|
|
||||||
<span>⚡ BenchmarkDotNet</span>
|
|
||||||
<span class="text-warning">+1 CPU / 2T</span>
|
|
||||||
</div>
|
|
||||||
<div class="badge bg-dark border border-secondary text-white p-2 d-flex align-items-center justify-content-between" style="font-size: 0.85rem; font-weight: 800;">
|
|
||||||
<span>📊 Serilog</span>
|
|
||||||
<span class="text-success">+2 Shield on Attack</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card p-3" style="background-color: rgba(33, 37, 43, 0.92); border: 2px solid #383e4a; border-radius: 12px; backdrop-filter: blur(6px);">
|
|
||||||
<div class="text-white fw-bolder mb-2" style="font-size: 0.88rem;">MECHANICS HIGHLIGHTS</div>
|
|
||||||
<div class="d-flex justify-content-between text-white" style="font-size: 0.85rem; font-weight: 800;">
|
|
||||||
<span>Branch Prediction Crit</span>
|
|
||||||
<span class="text-warning fw-bolder">25% chance for 2x DMG</span>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-between text-white mt-1" style="font-size: 0.85rem; font-weight: 800;">
|
|
||||||
<span>Git Procedural Map</span>
|
|
||||||
<span class="text-info fw-bolder">7-Floor Branching Paths</span>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-between text-white mt-1" style="font-size: 0.85rem; font-weight: 800;">
|
|
||||||
<span>Post-Battle PR Review</span>
|
|
||||||
<span class="text-success fw-bolder">Merge 1 of 3 Cards</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
break;
|
||||||
break;
|
|
||||||
|
|
||||||
case RunScreen.Map:
|
case RunScreen.Map:
|
||||||
<MapView Run="@_run" />
|
<MapView Run="@_run" />
|
||||||
@@ -290,7 +347,7 @@ else
|
|||||||
<button class="btn btn-success btn-lg font-mono fw-bold px-5 py-3 shadow" @onclick="TriggerCampaignLaunchAsync">
|
<button class="btn btn-success btn-lg font-mono fw-bold px-5 py-3 shadow" @onclick="TriggerCampaignLaunchAsync">
|
||||||
🚀 Start New Sprint / Run
|
🚀 Start New Sprint / Run
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-outline-secondary btn-lg font-mono fw-bold px-4 py-3" @onclick="() => _run.CurrentScreen = RunScreen.Title">
|
<button class="btn btn-outline-secondary btn-lg font-mono fw-bold px-4 py-3" @onclick="ReturnToTitle">
|
||||||
Main Dashboard
|
Main Dashboard
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -311,7 +368,7 @@ else
|
|||||||
<button class="btn btn-danger btn-lg font-mono fw-bold px-5 py-3 shadow" @onclick="TriggerCampaignLaunchAsync">
|
<button class="btn btn-danger btn-lg font-mono fw-bold px-5 py-3 shadow" @onclick="TriggerCampaignLaunchAsync">
|
||||||
🔄 Retrying Deployment (New Run)
|
🔄 Retrying Deployment (New Run)
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-outline-secondary btn-lg font-mono fw-bold px-4 py-3" @onclick="() => _run.CurrentScreen = RunScreen.Title">
|
<button class="btn btn-outline-secondary btn-lg font-mono fw-bold px-4 py-3" @onclick="ReturnToTitle">
|
||||||
Main Dashboard
|
Main Dashboard
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -321,6 +378,68 @@ else
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<!-- Git Commit Log / Run History Modal -->
|
||||||
|
@if (_showHistoryModal)
|
||||||
|
{
|
||||||
|
<div class="modal-backdrop-custom d-flex align-items-center justify-content-center" @onclick="() => _showHistoryModal = false">
|
||||||
|
<div class="card bg-dark text-light border border-secondary shadow-lg p-4 font-mono git-modal-box" @onclick:stopPropagation="true">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3 pb-2 border-bottom border-secondary">
|
||||||
|
<h4 class="text-warning mb-0 fw-bolder d-flex align-items-center gap-2">
|
||||||
|
<span>📜</span>
|
||||||
|
<span>Git Commit History (Sprint Runs)</span>
|
||||||
|
</h4>
|
||||||
|
<button class="btn btn-sm btn-outline-secondary" @onclick="() => _showHistoryModal = false">✕</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (_runHistory.Count == 0)
|
||||||
|
{
|
||||||
|
<div class="text-center py-5 text-muted">
|
||||||
|
<div style="font-size: 3rem;">📂</div>
|
||||||
|
<p class="mt-2 mb-1">No deployment records found in local git tree.</p>
|
||||||
|
<p class="small text-secondary">Complete a Sprint to log deployment telemetry!</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="overflow-y-auto mb-3 pe-1" style="max-height: 420px;">
|
||||||
|
@foreach (var record in _runHistory.OrderByDescending(r => r.Timestamp))
|
||||||
|
{
|
||||||
|
<div class="card mb-2 p-3 border @(record.IsVictory ? "border-success bg-dark" : "border-danger bg-dark") shadow-sm">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-1">
|
||||||
|
<span class="badge @(record.IsVictory ? "bg-success" : "bg-danger") font-mono">
|
||||||
|
@(record.IsVictory ? "✓ DEPLOYED" : "✗ ROLLED BACK")
|
||||||
|
</span>
|
||||||
|
<span class="text-muted font-mono" style="font-size: 0.75rem;">
|
||||||
|
@record.Timestamp.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss")
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-items-center gap-2 mb-1">
|
||||||
|
<span class="badge bg-secondary font-mono">commit @record.CommitHash</span>
|
||||||
|
<span class="text-info fw-bold">@record.Archetype</span>
|
||||||
|
<span class="text-muted font-mono" style="font-size: 0.8rem;">(Floor @record.FloorReached)</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-light small mb-2">@record.Message</div>
|
||||||
|
<div class="d-flex gap-3 text-muted font-mono" style="font-size: 0.78rem;">
|
||||||
|
<span>HP: <strong class="text-white">@record.FinalHp/@record.MaxHp</strong></span>
|
||||||
|
<span>Credits: <strong class="text-warning">@record.DevCredits</strong></span>
|
||||||
|
<span>Deck: <strong class="text-info">@record.DeckSize cards</strong></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-between align-items-center pt-2 border-top border-secondary">
|
||||||
|
<button class="btn btn-outline-danger btn-sm font-mono" @onclick="ClearRunHistoryAsync">
|
||||||
|
🗑️ Purge Commit Log
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-secondary btn-sm px-4 font-mono" @onclick="() => _showHistoryModal = false">
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* Intro Boot Screen & Terminal Styles */
|
/* Intro Boot Screen & Terminal Styles */
|
||||||
.intro-boot-screen {
|
.intro-boot-screen {
|
||||||
@@ -438,6 +557,26 @@ else
|
|||||||
border-color: #61afef !important;
|
border-color: #61afef !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.class-select-card {
|
||||||
|
background-color: rgba(33, 37, 43, 0.85);
|
||||||
|
border: 2px solid #383e4a;
|
||||||
|
border-radius: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
backdrop-filter: blur(6px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-select-card:hover {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
border-color: #61afef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-selected {
|
||||||
|
border-color: #98c379 !important;
|
||||||
|
background-color: rgba(33, 50, 43, 0.9) !important;
|
||||||
|
box-shadow: 0 0 20px rgba(152, 195, 121, 0.35) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.deploying-overlay {
|
.deploying-overlay {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
@@ -460,6 +599,23 @@ else
|
|||||||
0% { transform: scale(1); }
|
0% { transform: scale(1); }
|
||||||
100% { transform: scale(1.15); }
|
100% { transform: scale(1.15); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-backdrop-custom {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
backdrop-filter: blur(6px);
|
||||||
|
z-index: 20000;
|
||||||
|
animation: fade-in 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.git-modal-box {
|
||||||
|
width: 90%;
|
||||||
|
max-width: 650px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #1e222b !important;
|
||||||
|
border: 2px solid #4b5263 !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@@ -468,10 +624,85 @@ else
|
|||||||
private bool _isGlitchTransitioning = false;
|
private bool _isGlitchTransitioning = false;
|
||||||
private bool _isSandboxMode = false;
|
private bool _isSandboxMode = false;
|
||||||
private bool _isDeploying = false;
|
private bool _isDeploying = false;
|
||||||
|
private bool _isMuted = false;
|
||||||
|
private bool _showHistoryModal = false;
|
||||||
|
|
||||||
protected override void OnInitialized()
|
private DeveloperArchetype _selectedArchetype = DeveloperArchetype.PrincipalArchitect;
|
||||||
|
private List<RunRecord> _runHistory = new();
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
_run.OnRunStateChanged += StateHasChanged;
|
_run.OnRunStateChanged += HandleRunStateChanged;
|
||||||
|
await LoadRunHistoryAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleRunStateChanged()
|
||||||
|
{
|
||||||
|
UpdateBgmForScreen();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateBgmForScreen()
|
||||||
|
{
|
||||||
|
switch (_run.CurrentScreen)
|
||||||
|
{
|
||||||
|
case RunScreen.Combat:
|
||||||
|
var isBoss = _run.CurrentNode?.Type == MapNodeType.Boss;
|
||||||
|
_ = JS.InvokeVoidAsync("slayAudio.startBgm", isBoss ? "boss" : "combat");
|
||||||
|
break;
|
||||||
|
case RunScreen.Map:
|
||||||
|
case RunScreen.Shop:
|
||||||
|
case RunScreen.RestSite:
|
||||||
|
case RunScreen.Event:
|
||||||
|
_ = JS.InvokeVoidAsync("slayAudio.startBgm", "map");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
_ = JS.InvokeVoidAsync("slayAudio.stopBgm");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectArchetype(DeveloperArchetype archetype)
|
||||||
|
{
|
||||||
|
_selectedArchetype = archetype;
|
||||||
|
_ = JS.InvokeVoidAsync("slayAudio.playCardSound");
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetArchetypeName() => _selectedArchetype switch
|
||||||
|
{
|
||||||
|
DeveloperArchetype.AsyncConcurrencyMaster => "Async Master",
|
||||||
|
DeveloperArchetype.UnsafeMemoryHacker => "Unsafe Hacker",
|
||||||
|
_ => "Principal Architect"
|
||||||
|
};
|
||||||
|
|
||||||
|
private string GetArchetypeIcon() => _selectedArchetype switch
|
||||||
|
{
|
||||||
|
DeveloperArchetype.AsyncConcurrencyMaster => "⚡",
|
||||||
|
DeveloperArchetype.UnsafeMemoryHacker => "💀",
|
||||||
|
_ => "👨💻"
|
||||||
|
};
|
||||||
|
|
||||||
|
private int GetArchetypeHp() => _selectedArchetype switch
|
||||||
|
{
|
||||||
|
DeveloperArchetype.AsyncConcurrencyMaster => 65,
|
||||||
|
DeveloperArchetype.UnsafeMemoryHacker => 60,
|
||||||
|
_ => 70
|
||||||
|
};
|
||||||
|
|
||||||
|
private int GetArchetypeEnergy() => _selectedArchetype switch
|
||||||
|
{
|
||||||
|
DeveloperArchetype.AsyncConcurrencyMaster => 4,
|
||||||
|
_ => 3
|
||||||
|
};
|
||||||
|
|
||||||
|
private void ToggleAudioMute()
|
||||||
|
{
|
||||||
|
_isMuted = !_isMuted;
|
||||||
|
_ = JS.InvokeVoidAsync("slayAudio.toggleMute");
|
||||||
|
if (!_isMuted)
|
||||||
|
{
|
||||||
|
UpdateBgmForScreen();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task EnterTitleScreenAsync()
|
private async Task EnterTitleScreenAsync()
|
||||||
@@ -497,7 +728,8 @@ else
|
|||||||
_isDeploying = false;
|
_isDeploying = false;
|
||||||
|
|
||||||
_isSandboxMode = false;
|
_isSandboxMode = false;
|
||||||
_run.StartNewRun();
|
_run.StartNewRun(_selectedArchetype);
|
||||||
|
UpdateBgmForScreen();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -513,40 +745,109 @@ else
|
|||||||
List<Enemy> enemies = type switch
|
List<Enemy> enemies = type switch
|
||||||
{
|
{
|
||||||
1 => new List<Enemy> { new NullReferenceExceptionEnemy(), new OffByOneBugEnemy() },
|
1 => new List<Enemy> { new NullReferenceExceptionEnemy(), new OffByOneBugEnemy() },
|
||||||
2 => new List<Enemy> { new MemoryLeakSpectreEnemy(), new OffByOneBugEnemy() },
|
2 => new List<Enemy> { new ThreadDeadlockEnemy(), new MemoryLeakSpectreEnemy() },
|
||||||
3 => new List<Enemy> { new LegacyMonolithBossEnemy() },
|
3 => new List<Enemy> { new KubernetesClusterBossEnemy() },
|
||||||
_ => new List<Enemy> { new NullReferenceExceptionEnemy() }
|
_ => new List<Enemy> { new NullReferenceExceptionEnemy() }
|
||||||
};
|
};
|
||||||
|
|
||||||
_run.ActiveCombat = new CombatEngine();
|
_run.ActiveCombat = new CombatEngine();
|
||||||
_run.ActiveCombat.StartCombat(_run.Player, enemies);
|
_run.ActiveCombat.StartCombat(_run.Player, enemies);
|
||||||
_run.CurrentScreen = RunScreen.Combat;
|
_run.CurrentScreen = RunScreen.Combat;
|
||||||
|
UpdateBgmForScreen();
|
||||||
_ = JS.InvokeVoidAsync("slayAudio.playCardSound");
|
_ = JS.InvokeVoidAsync("slayAudio.playCardSound");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleCombatVictory()
|
private async void HandleCombatVictory()
|
||||||
{
|
{
|
||||||
if (_isSandboxMode)
|
if (_isSandboxMode)
|
||||||
{
|
{
|
||||||
_run.CurrentScreen = RunScreen.Title;
|
_run.CurrentScreen = RunScreen.Title;
|
||||||
|
UpdateBgmForScreen();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
_ = JS.InvokeVoidAsync("slayAudio.playVictorySound");
|
||||||
_run.CompleteCombatAndClaimRewards(25);
|
_run.CompleteCombatAndClaimRewards(25);
|
||||||
|
if (_run.CurrentScreen == RunScreen.RunVictory)
|
||||||
|
{
|
||||||
|
await SaveRunRecordAsync(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleCombatDefeat()
|
private async void HandleCombatDefeat()
|
||||||
{
|
{
|
||||||
if (_isSandboxMode)
|
if (_isSandboxMode)
|
||||||
{
|
{
|
||||||
_run.CurrentScreen = RunScreen.Title;
|
_run.CurrentScreen = RunScreen.Title;
|
||||||
|
UpdateBgmForScreen();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_run.ReturnToMap();
|
_run.ReturnToMap();
|
||||||
|
if (_run.CurrentScreen == RunScreen.RunDefeat)
|
||||||
|
{
|
||||||
|
await SaveRunRecordAsync(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ReturnToTitle()
|
||||||
|
{
|
||||||
|
_run.CurrentScreen = RunScreen.Title;
|
||||||
|
UpdateBgmForScreen();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveRunRecordAsync(bool isVictory)
|
||||||
|
{
|
||||||
|
var floor = _run.CurrentNode?.Floor ?? 1;
|
||||||
|
var record = RunRecord.Create(
|
||||||
|
_selectedArchetype,
|
||||||
|
isVictory,
|
||||||
|
_run.Player?.CurrentHp ?? 0,
|
||||||
|
_run.Player?.MaxHp ?? 70,
|
||||||
|
_run.DevCredits,
|
||||||
|
_run.Player?.MasterDeck.Count ?? 10,
|
||||||
|
floor
|
||||||
|
);
|
||||||
|
|
||||||
|
_runHistory.Insert(0, record);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var json = JsonSerializer.Serialize(_runHistory);
|
||||||
|
await JS.InvokeVoidAsync("localStorage.setItem", "slaysharp_run_history", json);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadRunHistoryAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var json = await JS.InvokeAsync<string?>("localStorage.getItem", "slaysharp_run_history");
|
||||||
|
if (!string.IsNullOrWhiteSpace(json))
|
||||||
|
{
|
||||||
|
var records = JsonSerializer.Deserialize<List<RunRecord>>(json);
|
||||||
|
if (records != null)
|
||||||
|
{
|
||||||
|
_runHistory = records;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ClearRunHistoryAsync()
|
||||||
|
{
|
||||||
|
_runHistory.Clear();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await JS.InvokeVoidAsync("localStorage.removeItem", "slaysharp_run_history");
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -453,97 +453,327 @@ code, pre, .font-mono {
|
|||||||
background-color: rgba(255, 255, 255, 0.2);
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Card Visual Window with Action Types */
|
/* ==========================================================
|
||||||
|
VIBRANT & COLORFUL SLAYSHARP CARDS (VS CODE CYBER THEME)
|
||||||
|
========================================================== */
|
||||||
.game-card {
|
.game-card {
|
||||||
width: var(--card-w);
|
width: var(--card-w);
|
||||||
height: var(--card-h);
|
height: var(--card-h);
|
||||||
background-color: #1e2227;
|
|
||||||
border: 2px solid #383e4a;
|
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.22s cubic-bezier(0.2, 0.8, 0.2, 1);
|
transition: all 0.24s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||||
box-shadow: 0 10px 28px rgba(0, 0, 0, 0.6);
|
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.7);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Diagonal Holographic Sheen Overlay */
|
||||||
|
.card-gloss-sheen {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: linear-gradient(135deg, rgba(255, 255, 255, 0.12) 0%, transparent 45%, rgba(255, 255, 255, 0.05) 100%);
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 10;
|
||||||
|
opacity: 0.7;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card:hover .card-gloss-sheen {
|
||||||
|
opacity: 1;
|
||||||
|
background: linear-gradient(135deg, rgba(255, 255, 255, 0.22) 0%, transparent 55%, rgba(255, 255, 255, 0.1) 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.game-card:hover {
|
.game-card:hover {
|
||||||
transform: translateY(-26px) scale(1.06);
|
transform: translateY(-26px) scale(1.06);
|
||||||
z-index: 50;
|
z-index: 50;
|
||||||
box-shadow: 0 20px 40px rgba(0, 122, 204, 0.5);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Action Type Colored Variants */
|
/* ================= ATTACK CARDS (CRIMSON / EMBER NEON) ================= */
|
||||||
.game-card.type-attack {
|
.game-card.type-attack {
|
||||||
border-color: rgba(224, 108, 117, 0.5);
|
background: linear-gradient(175deg, #441a25 0%, #2e1119 45%, #1f0b11 100%);
|
||||||
|
border: 3px solid #ff4757;
|
||||||
|
box-shadow: 0 8px 25px rgba(255, 71, 87, 0.45);
|
||||||
}
|
}
|
||||||
|
|
||||||
.game-card.type-attack:hover {
|
.game-card.type-attack:hover {
|
||||||
border-color: #e06c75;
|
border-color: #ff6b81;
|
||||||
box-shadow: 0 20px 40px rgba(224, 108, 117, 0.4);
|
box-shadow: 0 20px 48px rgba(255, 71, 87, 0.75), 0 0 30px rgba(255, 107, 129, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.game-card.type-attack .card-vsc-header {
|
||||||
|
background: linear-gradient(90deg, #681a29 0%, #48121d 100%);
|
||||||
|
border-bottom: 2px solid #ff4757;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card.type-attack .card-art-box {
|
||||||
|
background: radial-gradient(circle at center, rgba(255, 71, 87, 0.45) 0%, #2e1119 80%);
|
||||||
|
border-bottom: 1.5px solid rgba(255, 71, 87, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card.type-attack .card-art-aura {
|
||||||
|
position: absolute;
|
||||||
|
width: 70px;
|
||||||
|
height: 70px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: radial-gradient(circle, rgba(255, 71, 87, 0.7) 0%, transparent 70%);
|
||||||
|
filter: blur(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card.type-attack .card-editor-viewport {
|
||||||
|
background-color: #210d14;
|
||||||
|
border-bottom: 1.5px solid rgba(255, 71, 87, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card.type-attack .card-vsc-footer {
|
||||||
|
background: linear-gradient(180deg, #301019 0%, #421523 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card.type-attack .card-cost-badge {
|
||||||
|
background: linear-gradient(135deg, #ff4757 0%, #b33939 100%);
|
||||||
|
box-shadow: 0 0 12px rgba(255, 71, 87, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card.type-attack .card-watermark-icon {
|
||||||
|
color: #ff4757;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================= SKILL / DEFENSE CARDS (ELECTRIC SAPPHIRE / CYAN) ================= */
|
||||||
.game-card.type-skill {
|
.game-card.type-skill {
|
||||||
border-color: rgba(97, 175, 239, 0.5);
|
background: linear-gradient(175deg, #133a5b 0%, #0d273d 45%, #081a29 100%);
|
||||||
|
border: 3px solid #00d2ff;
|
||||||
|
box-shadow: 0 8px 25px rgba(0, 210, 255, 0.45);
|
||||||
}
|
}
|
||||||
|
|
||||||
.game-card.type-skill:hover {
|
.game-card.type-skill:hover {
|
||||||
border-color: #61afef;
|
border-color: #70a1ff;
|
||||||
box-shadow: 0 20px 40px rgba(97, 175, 239, 0.4);
|
box-shadow: 0 20px 48px rgba(0, 210, 255, 0.75), 0 0 30px rgba(112, 161, 255, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.game-card.type-skill .card-vsc-header {
|
||||||
|
background: linear-gradient(90deg, #164f80 0%, #0e3252 100%);
|
||||||
|
border-bottom: 2px solid #00d2ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card.type-skill .card-art-box {
|
||||||
|
background: radial-gradient(circle at center, rgba(0, 210, 255, 0.45) 0%, #0d273d 80%);
|
||||||
|
border-bottom: 1.5px solid rgba(0, 210, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card.type-skill .card-art-aura {
|
||||||
|
position: absolute;
|
||||||
|
width: 70px;
|
||||||
|
height: 70px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: radial-gradient(circle, rgba(0, 210, 255, 0.7) 0%, transparent 70%);
|
||||||
|
filter: blur(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card.type-skill .card-editor-viewport {
|
||||||
|
background-color: #091b2c;
|
||||||
|
border-bottom: 1.5px solid rgba(0, 210, 255, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card.type-skill .card-vsc-footer {
|
||||||
|
background: linear-gradient(180deg, #0e2940 0%, #153c5e 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card.type-skill .card-cost-badge {
|
||||||
|
background: linear-gradient(135deg, #00d2ff 0%, #0984e3 100%);
|
||||||
|
box-shadow: 0 0 12px rgba(0, 210, 255, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card.type-skill .card-watermark-icon {
|
||||||
|
color: #00d2ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================= POWER CARDS (AMETHYST / CYBER MAGENTA) ================= */
|
||||||
.game-card.type-power {
|
.game-card.type-power {
|
||||||
border-color: rgba(198, 120, 221, 0.5);
|
background: linear-gradient(175deg, #441860 0%, #2d0e40 45%, #1b0726 100%);
|
||||||
}
|
border: 3px solid #e056fd;
|
||||||
.game-card.type-power:hover {
|
box-shadow: 0 8px 25px rgba(224, 86, 253, 0.45);
|
||||||
border-color: #c678dd;
|
|
||||||
box-shadow: 0 20px 40px rgba(198, 120, 221, 0.4);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.game-card.type-power:hover {
|
||||||
|
border-color: #be2edd;
|
||||||
|
box-shadow: 0 20px 48px rgba(224, 86, 253, 0.75), 0 0 30px rgba(190, 46, 221, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card.type-power .card-vsc-header {
|
||||||
|
background: linear-gradient(90deg, #5c1e82 0%, #3d1257 100%);
|
||||||
|
border-bottom: 2px solid #e056fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card.type-power .card-art-box {
|
||||||
|
background: radial-gradient(circle at center, rgba(224, 86, 253, 0.45) 0%, #2d0e40 80%);
|
||||||
|
border-bottom: 1.5px solid rgba(224, 86, 253, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card.type-power .card-art-aura {
|
||||||
|
position: absolute;
|
||||||
|
width: 70px;
|
||||||
|
height: 70px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: radial-gradient(circle, rgba(224, 86, 253, 0.7) 0%, transparent 70%);
|
||||||
|
filter: blur(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card.type-power .card-editor-viewport {
|
||||||
|
background-color: #1b0829;
|
||||||
|
border-bottom: 1.5px solid rgba(224, 86, 253, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card.type-power .card-vsc-footer {
|
||||||
|
background: linear-gradient(180deg, #2e0d42 0%, #431460 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card.type-power .card-cost-badge {
|
||||||
|
background: linear-gradient(135deg, #e056fd 0%, #686de0 100%);
|
||||||
|
box-shadow: 0 0 12px rgba(224, 86, 253, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card.type-power .card-watermark-icon {
|
||||||
|
color: #e056fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================= CURSE / DEBT CARDS (HAZARD ORANGE) ================= */
|
||||||
|
.game-card.type-curse {
|
||||||
|
background: linear-gradient(175deg, #442a12 0%, #2e1a09 45%, #1c0e04 100%);
|
||||||
|
border: 3px solid #ffa502;
|
||||||
|
box-shadow: 0 8px 25px rgba(255, 165, 2, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card.type-curse .card-watermark-icon {
|
||||||
|
color: #ffa502;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================= FULL-WIDTH CARD TYPE RIBBON BANNER ================= */
|
||||||
|
.card-type-banner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 3px 8px;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 900;
|
||||||
|
letter-spacing: 0.8px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.6);
|
||||||
|
z-index: 6;
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-type-banner.type-tag-attack {
|
||||||
|
background: linear-gradient(90deg, #eb3b5a 0%, #ff4757 50%, #fa8231 100%);
|
||||||
|
color: #ffffff;
|
||||||
|
text-shadow: 0 1px 3px rgba(0,0,0,0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-type-banner.type-tag-skill {
|
||||||
|
background: linear-gradient(90deg, #0984e3 0%, #00d2ff 50%, #00cec9 100%);
|
||||||
|
color: #ffffff;
|
||||||
|
text-shadow: 0 1px 3px rgba(0,0,0,0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-type-banner.type-tag-power {
|
||||||
|
background: linear-gradient(90deg, #6c5ce7 0%, #a55eea 50%, #e056fd 100%);
|
||||||
|
color: #ffffff;
|
||||||
|
text-shadow: 0 1px 3px rgba(0,0,0,0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-banner-icon {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-banner-text {
|
||||||
|
font-size: 0.76rem;
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-banner-upgraded {
|
||||||
|
margin-left: auto;
|
||||||
|
background: #ffd700;
|
||||||
|
color: #000000;
|
||||||
|
padding: 1px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.62rem;
|
||||||
|
font-weight: 900;
|
||||||
|
box-shadow: 0 0 8px rgba(255, 215, 0, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Translucent Background Emblem Watermark */
|
||||||
|
.card-watermark-icon {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 5rem;
|
||||||
|
opacity: 0.18;
|
||||||
|
pointer-events: none;
|
||||||
|
transform: scale(1.35);
|
||||||
|
z-index: 1;
|
||||||
|
filter: drop-shadow(0 0 12px currentColor);
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================= TIER-1 UPGRADED GOLDEN HALO ================= */
|
||||||
|
.game-card.tier-upgraded {
|
||||||
|
border: 3.5px solid #ffd700 !important;
|
||||||
|
box-shadow: 0 0 32px rgba(255, 215, 0, 0.6), inset 0 0 18px rgba(255, 215, 0, 0.3) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-card.tier-upgraded .card-upgraded-title {
|
||||||
|
color: #ffd700 !important;
|
||||||
|
text-shadow: 0 0 10px rgba(255, 215, 0, 0.8);
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-type-mini-icon {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-tier-badge {
|
||||||
|
background: linear-gradient(135deg, #ffd700 0%, #ff9f43 100%) !important;
|
||||||
|
color: #000000 !important;
|
||||||
|
font-size: 0.65rem;
|
||||||
|
font-weight: 900;
|
||||||
|
box-shadow: 0 0 12px rgba(255, 215, 0, 0.9);
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
animation: gold-badge-shimmer 2s infinite alternate ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@keyframes gold-badge-shimmer {
|
||||||
|
0% { transform: scale(1); filter: brightness(1); }
|
||||||
|
100% { transform: scale(1.08); filter: brightness(1.25); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================= CARD SELECTION & PLAYABLE STATES ================= */
|
||||||
.game-card.playable {
|
.game-card.playable {
|
||||||
box-shadow: 0 0 16px rgba(0, 122, 204, 0.4);
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.game-card.selected {
|
.game-card.selected {
|
||||||
transform: translateY(-46px) scale(1.1) !important;
|
transform: translateY(-46px) scale(1.12) !important;
|
||||||
border-color: #98c379 !important;
|
border-color: #2ed573 !important;
|
||||||
box-shadow: 0 0 30px rgba(152, 195, 121, 0.8) !important;
|
box-shadow: 0 0 38px rgba(46, 213, 115, 0.95), 0 20px 40px rgba(0, 0, 0, 0.8) !important;
|
||||||
z-index: 60;
|
z-index: 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
.game-card.unaffordable {
|
.game-card.unaffordable {
|
||||||
opacity: 0.45;
|
opacity: 0.45;
|
||||||
filter: grayscale(80%);
|
filter: grayscale(70%) brightness(0.7);
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Card Action Type Pill Tag */
|
/* ================= CARD HEADER & LABELS ================= */
|
||||||
.card-type-tag {
|
|
||||||
font-size: 0.65rem;
|
|
||||||
font-weight: 900;
|
|
||||||
padding: 2px 7px;
|
|
||||||
border-radius: 4px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.type-tag-attack { background: rgba(224, 108, 117, 0.25); color: #e06c75; border: 1px solid rgba(224, 108, 117, 0.6); }
|
|
||||||
.type-tag-skill { background: rgba(97, 175, 239, 0.25); color: #61afef; border: 1px solid rgba(97, 175, 239, 0.6); }
|
|
||||||
.type-tag-power { background: rgba(198, 120, 221, 0.25); color: #c678dd; border: 1px solid rgba(198, 120, 221, 0.6); }
|
|
||||||
|
|
||||||
/* Card VS Code Tab Header */
|
|
||||||
.card-vsc-header {
|
.card-vsc-header {
|
||||||
background-color: #282c34;
|
height: 33px;
|
||||||
height: 32px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
border-bottom: 1.5px solid #181a1f;
|
position: relative;
|
||||||
|
z-index: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-tab-label {
|
.card-tab-label {
|
||||||
@@ -555,61 +785,80 @@ code, pre, .font-mono {
|
|||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-hash-icon {
|
||||||
|
color: #61afef;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
|
||||||
.card-cost-badge {
|
.card-cost-badge {
|
||||||
background: #007acc;
|
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
padding: 2px 8px;
|
padding: 2px 9px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
box-shadow: 0 0 8px rgba(0, 122, 204, 0.6);
|
letter-spacing: 0.2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.type-attack .card-cost-badge { background: #e06c75; box-shadow: 0 0 8px rgba(224, 108, 117, 0.6); }
|
/* ================= CARD ART BOX ================= */
|
||||||
.type-skill .card-cost-badge { background: #61afef; box-shadow: 0 0 8px rgba(97, 175, 239, 0.6); }
|
|
||||||
.type-power .card-cost-badge { background: #c678dd; box-shadow: 0 0 8px rgba(198, 120, 221, 0.6); }
|
|
||||||
|
|
||||||
/* Card Art Box (Visual Illustration) */
|
|
||||||
.card-art-box {
|
.card-art-box {
|
||||||
height: 75px;
|
height: 78px;
|
||||||
background: linear-gradient(135deg, #181a1f 0%, #21252b 100%);
|
|
||||||
border-bottom: 1px solid #383e4a;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
z-index: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-art-icon {
|
.card-art-icon {
|
||||||
font-size: 2.7rem;
|
font-size: 3.1rem;
|
||||||
filter: drop-shadow(0 4px 10px rgba(0, 0, 0, 0.7));
|
filter: drop-shadow(0 6px 14px rgba(0, 0, 0, 0.8));
|
||||||
transition: transform 0.2s ease;
|
transition: transform 0.22s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||||
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.game-card:hover .card-art-icon {
|
.game-card:hover .card-art-icon {
|
||||||
transform: scale(1.12);
|
transform: scale(1.18);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Editor Gutter and Lines inside Card */
|
.card-type-tag {
|
||||||
|
font-size: 0.65rem;
|
||||||
|
font-weight: 900;
|
||||||
|
padding: 2px 7px;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 3px;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-tag-attack { background: rgba(255, 71, 87, 0.35); color: #ff6b81; border: 1px solid #ff4757; }
|
||||||
|
.type-tag-skill { background: rgba(0, 210, 255, 0.35); color: #70a1ff; border: 1px solid #00d2ff; }
|
||||||
|
.type-tag-power { background: rgba(224, 86, 253, 0.35); color: #e056fd; border: 1px solid #be2edd; }
|
||||||
|
|
||||||
|
/* ================= CODE EDITOR VIEWPORT ================= */
|
||||||
.card-editor-viewport {
|
.card-editor-viewport {
|
||||||
background-color: #181a1f;
|
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 5px 0;
|
padding: 6px 0;
|
||||||
font-size: 0.74rem;
|
font-size: 0.74rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 1.45;
|
line-height: 1.45;
|
||||||
|
position: relative;
|
||||||
|
z-index: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-gutter {
|
.card-gutter {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
color: #5c6370;
|
color: #747d8c;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
padding-right: 5px;
|
padding-right: 6px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
border-right: 1px solid rgba(255, 255, 255, 0.08);
|
border-right: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
@@ -617,26 +866,52 @@ code, pre, .font-mono {
|
|||||||
.card-code-content {
|
.card-code-content {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
color: var(--syn-var);
|
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-vsc-footer {
|
.card-code-comment {
|
||||||
background-color: #21252b;
|
color: #7bed9f;
|
||||||
padding: 7px 10px;
|
font-size: 0.68rem;
|
||||||
border-top: 1.5px solid #383e4a;
|
font-style: italic;
|
||||||
font-size: 0.88rem;
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-code-line {
|
||||||
|
font-size: 0.77rem;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
color: #ffffff;
|
color: #f1f2f6;
|
||||||
min-height: 52px;
|
}
|
||||||
|
|
||||||
|
/* ================= CARD FOOTER DESCRIPTION ================= */
|
||||||
|
.card-vsc-footer {
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-top: 1.5px solid rgba(255, 255, 255, 0.12);
|
||||||
|
min-height: 54px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
letter-spacing: -0.2px;
|
position: relative;
|
||||||
|
z-index: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-footer-text {
|
||||||
|
font-size: 0.89rem;
|
||||||
|
font-weight: 900;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dynamic Glowing Stat Pills in Card Descriptions */
|
||||||
|
.desc-dmg { color: #ff6b81; font-weight: 900; text-shadow: 0 0 8px rgba(255, 107, 129, 0.6); }
|
||||||
|
.desc-shield { color: #70a1ff; font-weight: 900; text-shadow: 0 0 8px rgba(112, 161, 255, 0.6); }
|
||||||
|
.desc-pierce { color: #00f0ff; font-weight: 900; text-shadow: 0 0 8px rgba(0, 240, 255, 0.6); }
|
||||||
|
.desc-cpu { color: #ffd32a; font-weight: 900; }
|
||||||
|
.desc-str { color: #2ed573; font-weight: 900; text-shadow: 0 0 8px rgba(46, 213, 115, 0.6); }
|
||||||
|
.desc-weak { color: #56b6c2; font-weight: 900; }
|
||||||
|
.desc-leak { color: #ffa502; font-weight: 900; }
|
||||||
|
|
||||||
/* Enemy Bug VS Code Problem Card */
|
/* Enemy Bug VS Code Problem Card */
|
||||||
.enemy-card {
|
.enemy-card {
|
||||||
background-color: rgba(33, 37, 43, 0.94);
|
background-color: rgba(33, 37, 43, 0.94);
|
||||||
|
|||||||
@@ -150,5 +150,106 @@ window.slayAudio = {
|
|||||||
osc.start(this.ctx.currentTime + idx * 0.06);
|
osc.start(this.ctx.currentTime + idx * 0.06);
|
||||||
osc.stop(this.ctx.currentTime + idx * 0.06 + 0.5);
|
osc.stop(this.ctx.currentTime + idx * 0.06 + 0.5);
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Procedural Synthwave BGM Engine
|
||||||
|
bgmInterval: null,
|
||||||
|
currentTheme: null,
|
||||||
|
bgmStep: 0,
|
||||||
|
|
||||||
|
startBgm: function (theme) {
|
||||||
|
if (this.currentTheme === theme && this.bgmInterval) return;
|
||||||
|
this.stopBgm();
|
||||||
|
this.currentTheme = theme;
|
||||||
|
this.bgmStep = 0;
|
||||||
|
|
||||||
|
if (this.muted) return;
|
||||||
|
this.init();
|
||||||
|
if (!this.ctx) return;
|
||||||
|
|
||||||
|
const speed = theme === 'boss' ? 220 : (theme === 'combat' ? 280 : 500);
|
||||||
|
|
||||||
|
this.bgmInterval = setInterval(() => {
|
||||||
|
if (this.muted || !this.ctx) return;
|
||||||
|
this.tickBgmPattern(theme);
|
||||||
|
}, speed);
|
||||||
|
},
|
||||||
|
|
||||||
|
stopBgm: function () {
|
||||||
|
if (this.bgmInterval) {
|
||||||
|
clearInterval(this.bgmInterval);
|
||||||
|
this.bgmInterval = null;
|
||||||
|
}
|
||||||
|
this.currentTheme = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
tickBgmPattern: function (theme) {
|
||||||
|
const step = this.bgmStep++;
|
||||||
|
const now = this.ctx.currentTime;
|
||||||
|
|
||||||
|
if (theme === 'combat') {
|
||||||
|
// E minor synthwave bassline
|
||||||
|
const notes = [82.41, 82.41, 110.00, 98.00, 82.41, 123.47, 110.00, 98.00];
|
||||||
|
const freq = notes[step % notes.length];
|
||||||
|
|
||||||
|
const osc = this.ctx.createOscillator();
|
||||||
|
const gain = this.ctx.createGain();
|
||||||
|
const filter = this.ctx.createBiquadFilter();
|
||||||
|
|
||||||
|
osc.type = 'sawtooth';
|
||||||
|
osc.frequency.setValueAtTime(freq, now);
|
||||||
|
|
||||||
|
filter.type = 'lowpass';
|
||||||
|
filter.frequency.setValueAtTime(450, now);
|
||||||
|
filter.frequency.exponentialRampToValueAtTime(150, now + 0.25);
|
||||||
|
|
||||||
|
gain.gain.setValueAtTime(0.04, now);
|
||||||
|
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.25);
|
||||||
|
|
||||||
|
osc.connect(filter);
|
||||||
|
filter.connect(gain);
|
||||||
|
gain.connect(this.ctx.destination);
|
||||||
|
|
||||||
|
osc.start(now);
|
||||||
|
osc.stop(now + 0.25);
|
||||||
|
} else if (theme === 'boss') {
|
||||||
|
// D minor dark driving synthwave
|
||||||
|
const notes = [73.42, 73.42, 87.31, 73.42, 98.00, 73.42, 110.00, 87.31];
|
||||||
|
const freq = notes[step % notes.length];
|
||||||
|
|
||||||
|
const osc = this.ctx.createOscillator();
|
||||||
|
const gain = this.ctx.createGain();
|
||||||
|
|
||||||
|
osc.type = 'square';
|
||||||
|
osc.frequency.setValueAtTime(freq, now);
|
||||||
|
|
||||||
|
gain.gain.setValueAtTime(0.035, now);
|
||||||
|
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.2);
|
||||||
|
|
||||||
|
osc.connect(gain);
|
||||||
|
gain.connect(this.ctx.destination);
|
||||||
|
|
||||||
|
osc.start(now);
|
||||||
|
osc.stop(now + 0.2);
|
||||||
|
} else {
|
||||||
|
// Map ambient drone
|
||||||
|
const chords = [130.81, 164.81, 196.00, 220.00];
|
||||||
|
const freq = chords[step % chords.length];
|
||||||
|
|
||||||
|
const osc = this.ctx.createOscillator();
|
||||||
|
const gain = this.ctx.createGain();
|
||||||
|
|
||||||
|
osc.type = 'sine';
|
||||||
|
osc.frequency.setValueAtTime(freq, now);
|
||||||
|
|
||||||
|
gain.gain.setValueAtTime(0.02, now);
|
||||||
|
gain.gain.exponentialRampToValueAtTime(0.0001, now + 0.45);
|
||||||
|
|
||||||
|
osc.connect(gain);
|
||||||
|
gain.connect(this.ctx.destination);
|
||||||
|
|
||||||
|
osc.start(now);
|
||||||
|
osc.stop(now + 0.45);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using SlaySharp.Core.Catalogs;
|
using SlaySharp.Core.Catalogs;
|
||||||
using SlaySharp.Core.Combat;
|
using SlaySharp.Core.Combat;
|
||||||
using SlaySharp.Core.Enums;
|
using SlaySharp.Core.Enums;
|
||||||
using SlaySharp.Core.Models;
|
using SlaySharp.Core.Models;
|
||||||
@@ -177,4 +177,110 @@ public class CombatEngineTests
|
|||||||
Assert.Empty(engine.Enemies);
|
Assert.Empty(engine.Enemies);
|
||||||
Assert.NotEmpty(engine.CardRewards);
|
Assert.NotEmpty(engine.CardRewards);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GameRun_StartNewRun_InitializesCorrectArchetypes()
|
||||||
|
{
|
||||||
|
var run = new SlaySharp.Core.Run.GameRun();
|
||||||
|
|
||||||
|
// Principal Architect
|
||||||
|
run.StartNewRun(DeveloperArchetype.PrincipalArchitect);
|
||||||
|
Assert.Equal("Principal Architect", run.Player.Name);
|
||||||
|
Assert.Equal(70, run.Player.MaxHp);
|
||||||
|
Assert.Equal(3, run.Player.MaxEnergy);
|
||||||
|
Assert.Equal(3, run.Player.Relics.Count);
|
||||||
|
|
||||||
|
// Async Concurrency Master
|
||||||
|
run.StartNewRun(DeveloperArchetype.AsyncConcurrencyMaster);
|
||||||
|
Assert.Equal("Async Master", run.Player.Name);
|
||||||
|
Assert.Equal(65, run.Player.MaxHp);
|
||||||
|
Assert.Equal(4, run.Player.MaxEnergy);
|
||||||
|
Assert.Equal(2, run.Player.Relics.Count);
|
||||||
|
|
||||||
|
// Unsafe Memory Hacker
|
||||||
|
run.StartNewRun(DeveloperArchetype.UnsafeMemoryHacker);
|
||||||
|
Assert.Equal("Unsafe Hacker", run.Player.Name);
|
||||||
|
Assert.Equal(60, run.Player.MaxHp);
|
||||||
|
Assert.Equal(3, run.Player.MaxEnergy);
|
||||||
|
Assert.Equal(2, run.Player.Relics.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TaskWhenAllCard_DealsDamageToTargetEnemy()
|
||||||
|
{
|
||||||
|
var player = new Player("Async Master", 65, maxEnergy: 4);
|
||||||
|
var card = new TaskWhenAllCard(); // 14 dmg single target
|
||||||
|
var enemy = new NullReferenceExceptionEnemy(); // 45 HP
|
||||||
|
|
||||||
|
var engine = new CombatEngine();
|
||||||
|
engine.StartCombat(player, new List<Enemy> { enemy });
|
||||||
|
|
||||||
|
player.Hand.Clear();
|
||||||
|
player.Hand.Add(card);
|
||||||
|
player.CurrentEnergy = 2;
|
||||||
|
|
||||||
|
var success = engine.PlayCard(card, enemy);
|
||||||
|
|
||||||
|
Assert.True(success);
|
||||||
|
Assert.Equal(0, player.CurrentEnergy);
|
||||||
|
Assert.True(enemy.CurrentHp <= 31);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FixedPointerCard_DealsPiercingDamageTwoTimes()
|
||||||
|
{
|
||||||
|
var player = new Player("Unsafe Hacker", 60, maxEnergy: 3);
|
||||||
|
var card = new FixedPointerCard(); // 2 hits of 9 piercing dmg
|
||||||
|
var enemy = new MemoryLeakSpectreEnemy(); // 35 HP
|
||||||
|
|
||||||
|
var engine = new CombatEngine();
|
||||||
|
engine.StartCombat(player, new List<Enemy> { enemy });
|
||||||
|
|
||||||
|
enemy.GainShield(12);
|
||||||
|
player.Hand.Clear();
|
||||||
|
player.Hand.Add(card);
|
||||||
|
player.CurrentEnergy = 2;
|
||||||
|
|
||||||
|
var success = engine.PlayCard(card, enemy);
|
||||||
|
|
||||||
|
Assert.True(success);
|
||||||
|
Assert.Equal(12, enemy.Shield); // Pierced
|
||||||
|
Assert.True(enemy.CurrentHp <= 17);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void KubernetesBoss_ExecutesRoutines()
|
||||||
|
{
|
||||||
|
var boss = new KubernetesClusterBossEnemy();
|
||||||
|
Assert.Equal(110, boss.MaxHp);
|
||||||
|
Assert.Equal(110, boss.CurrentHp);
|
||||||
|
Assert.Equal("☸️", boss.Icon);
|
||||||
|
|
||||||
|
var player = new Player("Senior Dev", 70, maxEnergy: 3);
|
||||||
|
var engine = new CombatEngine();
|
||||||
|
engine.StartCombat(player, new List<Enemy> { boss });
|
||||||
|
|
||||||
|
// Verify intent is planned
|
||||||
|
Assert.NotNull(boss.CurrentIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RunRecord_Create_GeneratesValidGitCommitRecord()
|
||||||
|
{
|
||||||
|
var record = RunRecord.Create(
|
||||||
|
DeveloperArchetype.AsyncConcurrencyMaster,
|
||||||
|
true,
|
||||||
|
55,
|
||||||
|
65,
|
||||||
|
120,
|
||||||
|
14,
|
||||||
|
7
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.Equal(7, record.CommitHash.Length);
|
||||||
|
Assert.True(record.IsVictory);
|
||||||
|
Assert.Equal(55, record.FinalHp);
|
||||||
|
Assert.Equal(120, record.DevCredits);
|
||||||
|
Assert.Contains("feat(prod)", record.Message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user