ported from perforce
This commit is contained in:
24
RobotAndDonkey.Game/Modifiers/AnalystModifier.cs
Normal file
24
RobotAndDonkey.Game/Modifiers/AnalystModifier.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
|
||||
namespace RobotAndDonkey.Game.Modifiers;
|
||||
|
||||
public record AnalystModifier() : Modifier(EModifierKind.Robot, EModifierId.Analytic, EModifierDuration.Permanent)
|
||||
{
|
||||
protected override AnalystModifier CreateInstance() => new();
|
||||
|
||||
public override void Before(Guid requestId, ReadOnlySpan<Intent> intents, CoreLoop coreLoop, Entity owner, List<Intent> newIntents, List<Result> results)
|
||||
{
|
||||
for (var i = 0; i < intents.Length; i++)
|
||||
{
|
||||
if (intents[i] is DeferGlitch deferGlitch && coreLoop.DeferGlitchCount == 0)
|
||||
deferGlitch.EnergyCost += Balancing.Instance.AnalysisEnergyDelta;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToolTip => $"Reduces the energy cost for deferring a glitch by {Balancing.Instance.AnalysisEnergyDelta}";
|
||||
}
|
||||
116
RobotAndDonkey.Game/Modifiers/CorruptModifier.cs
Normal file
116
RobotAndDonkey.Game/Modifiers/CorruptModifier.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using RobotAndDonkey.Game.Cards;
|
||||
using RobotAndDonkey.Game.Execution;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
using RobotAndDonkey.Game.Pois;
|
||||
using RobotAndDonkey.Game.Utils;
|
||||
using Interact = RobotAndDonkey.Game.Intents.Interact;
|
||||
using Move = RobotAndDonkey.Game.Intents.Move;
|
||||
using Rest = RobotAndDonkey.Game.Intents.Rest;
|
||||
|
||||
namespace RobotAndDonkey.Game.Modifiers;
|
||||
|
||||
public record CorruptCellModifier(EModifierDuration Duration) : CorruptModifierBase(EModifierKind.Cell, Duration)
|
||||
{
|
||||
protected override CorruptCellModifier CreateInstance() => new(Duration);
|
||||
|
||||
public override string ToolTip => "While in this area, instructions will have the opposite effect.";
|
||||
}
|
||||
|
||||
public record CorruptCardModifier(EModifierDuration Duration) : CorruptModifierBase(EModifierKind.Card, Duration)
|
||||
{
|
||||
protected override CorruptCardModifier CreateInstance() => new(Duration);
|
||||
|
||||
public override string ToolTip => "The instruction will have the opposite effect.";
|
||||
}
|
||||
|
||||
public abstract record CorruptModifierBase(EModifierKind Kind, EModifierDuration Duration) : Modifier(Kind, EModifierId.Corrupt, Duration)
|
||||
{
|
||||
public override void Before(Guid requestId, ReadOnlySpan<Intent> intents, CoreLoop coreLoop, Entity owner, List<Intent> newIntents, List<Result> results)
|
||||
{
|
||||
if (coreLoop.RunPhase != ERunPhase.ExecuteProgram)
|
||||
return;
|
||||
|
||||
foreach (var intent in intents)
|
||||
{
|
||||
switch (intent)
|
||||
{
|
||||
case Move m:
|
||||
{
|
||||
m.Direction = m.Direction.Opposite();
|
||||
break;
|
||||
}
|
||||
case Turn t:
|
||||
{
|
||||
t.Delta = -t.Delta;
|
||||
break;
|
||||
}
|
||||
case Rest rest when owner is Card card:
|
||||
{
|
||||
rest.DebuffSources.Add(this);
|
||||
var board = coreLoop.Board;
|
||||
var avatarCell = board.Cells.First(c => c.Poi is Avatar);
|
||||
newIntents.Add(new Move(avatarCell, card, ((Avatar)avatarCell.Poi!).Direction.Opposite(), false, 1));
|
||||
break;
|
||||
}
|
||||
case Interact interact:
|
||||
{
|
||||
var board = coreLoop.Board;
|
||||
var avatarCell = board.Cells.First(c => c.Poi is Avatar);
|
||||
var targetHex = avatarCell.Hex.GetNeighbour(interact.Direction);
|
||||
var cellIndex = board.FindCellIndex(targetHex);
|
||||
var cell = cellIndex >= 0 ? board.Cells[cellIndex] : null;
|
||||
if (cell != null)
|
||||
{
|
||||
interact.DebuffSources.Add(this);
|
||||
|
||||
if (cell.Poi is Shed)
|
||||
{
|
||||
results.Add(new InvalidInstructionResult(requestId, EInvalidReason.NoTarget, owner as Card, cell));
|
||||
newIntents.Add(new ModifyCurrency(new(-interact.EnergyCost, 0, 0, 0, 0, 0)));
|
||||
}
|
||||
else if (cell.Poi is Crate crate)
|
||||
{
|
||||
var carrying = coreLoop.Currency.Carry;
|
||||
newIntents.Add(new PickUp(cell, crate, -carrying, interact.EnergyCost));
|
||||
}
|
||||
else if (cell.Poi == null && coreLoop.HasDonkey && owner is Card card)
|
||||
newIntents.Add(new DonkeyIntent(card, cell, interact.EnergyCost));
|
||||
else if (cell.Poi is Tower tower)
|
||||
newIntents.Add(new TowerIntent(cell, tower, true, interact.EnergyCost));
|
||||
else
|
||||
{
|
||||
results.Add(new InvalidInstructionResult(requestId, EInvalidReason.NoTarget, owner as Card, cell));
|
||||
newIntents.Add(new ModifyCurrency(new(-interact.EnergyCost, 0, 0, 0, 0, 0)));
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ImmunizeCard { CanCorrupt: true } immunizeCard:
|
||||
{
|
||||
immunizeCard.DebuffState = false;
|
||||
break;
|
||||
}
|
||||
case TowerIntent tower:
|
||||
{
|
||||
tower.Active = true;
|
||||
break;
|
||||
}
|
||||
case ModifyCurrency { CanCorrupt: true, Delta: { } delta } modifyCurrency:
|
||||
{
|
||||
modifyCurrency.Delta = new(delta.Energy * -1, delta.MaxCarry * -1, delta.Carry * -1, delta.Delivery * -1, delta.TapeLength * -1, delta.HandSize * -1);
|
||||
break;
|
||||
}
|
||||
case DetoxiumPrime detoxiumPrime:
|
||||
{
|
||||
detoxiumPrime.Heal = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// #Next: handled in the cards that reference tape order (Repeat / Stabilize)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
34
RobotAndDonkey.Game/Modifiers/CourierOverspillModifier.cs
Normal file
34
RobotAndDonkey.Game/Modifiers/CourierOverspillModifier.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
|
||||
namespace RobotAndDonkey.Game.Modifiers;
|
||||
|
||||
public record CourierOverspillModifier() : Modifier(EModifierKind.Robot, EModifierId.CourierOverspill, EModifierDuration.Permanent)
|
||||
{
|
||||
protected override CourierOverspillModifier CreateInstance() => new();
|
||||
|
||||
public override void After(Guid requestId, ReadOnlySpan<Intent> intents, CoreLoop coreLoop, Entity owner, List<Intent> newIntents, List<Result> results)
|
||||
{
|
||||
var deliveryPresent = false;
|
||||
foreach (var intent in intents)
|
||||
{
|
||||
if (intent is not ModifyCurrency modifyCurrency)
|
||||
return;
|
||||
|
||||
if (modifyCurrency.Delta is not { Delivery: > 0 })
|
||||
continue;
|
||||
|
||||
deliveryPresent = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (deliveryPresent)
|
||||
newIntents.Add(new ModifyCurrency(new(Balancing.Instance.CourierEnergyReplenishOnDelivery, 0, 0, 0, 0, 0)));
|
||||
}
|
||||
|
||||
public override string ToolTip => $"Replenishes {Balancing.Instance.CourierEnergyReplenishOnDelivery} energy with every delivery";
|
||||
}
|
||||
41
RobotAndDonkey.Game/Modifiers/DroughtModifier.cs
Normal file
41
RobotAndDonkey.Game/Modifiers/DroughtModifier.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
|
||||
namespace RobotAndDonkey.Game.Modifiers;
|
||||
|
||||
public record DroughtModifier(EModifierDuration Duration) : Modifier(EModifierKind.Cell, EModifierId.Drought, Duration)
|
||||
{
|
||||
protected override DroughtModifier CreateInstance() => new(Duration);
|
||||
|
||||
public override void OnAdded(Entity owner, List<Intent> newIntents)
|
||||
{
|
||||
if (owner is not Cell cell)
|
||||
throw new NotSupportedException("Drought is only allowed in cells.");
|
||||
|
||||
cell.Type = cell.Type switch
|
||||
{
|
||||
ECellType.Fertile => ECellType.Grass,
|
||||
ECellType.Grass => ECellType.Dry,
|
||||
_ => throw new NotSupportedException("Drought only works on Fertile and Grass")
|
||||
};
|
||||
}
|
||||
|
||||
public override void OnRemoved(Entity owner, List<Intent> newIntents)
|
||||
{
|
||||
if (owner is not Cell cell)
|
||||
throw new NotSupportedException("Drought is only allowed in cells.");
|
||||
|
||||
cell.Type = cell.Type switch
|
||||
{
|
||||
ECellType.Grass => ECellType.Fertile,
|
||||
ECellType.Dry => ECellType.Grass,
|
||||
_ => throw new NotSupportedException("Drought can only be removed from Dry and Grass")
|
||||
};
|
||||
}
|
||||
|
||||
public override string ToolTip => "Humidity was reduced from this land, reducing fertility and maybe drying out.";
|
||||
}
|
||||
24
RobotAndDonkey.Game/Modifiers/EffectiveModifier.cs
Normal file
24
RobotAndDonkey.Game/Modifiers/EffectiveModifier.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using RobotAndDonkey.Game.Cards;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
|
||||
namespace RobotAndDonkey.Game.Modifiers;
|
||||
|
||||
public record EffectiveModifier(EModifierDuration Duration) : Modifier(EModifierKind.Card, EModifierId.Effective, Duration)
|
||||
{
|
||||
protected override EffectiveModifier CreateInstance() => new(Duration);
|
||||
|
||||
public override void Before(Guid requestId, ReadOnlySpan<Intent> intents, CoreLoop coreLoop, Entity owner, List<Intent> newIntents, List<Result> results)
|
||||
{
|
||||
if (coreLoop.Random.NextSingle() <= Balancing.Instance.EffectiveChance)
|
||||
{
|
||||
if (owner is Card card)
|
||||
results.Add(new ModifyCardResult(requestId, card.DeepClone(), EModifierId.Effective, ECardLocation.Tape));
|
||||
newIntents.AddRange(intents);
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToolTip => $"Has a {Balancing.Instance.EffectiveChance * 100:N0}% chance of working twice";
|
||||
}
|
||||
12
RobotAndDonkey.Game/Modifiers/EfficientModifier.cs
Normal file
12
RobotAndDonkey.Game/Modifiers/EfficientModifier.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
|
||||
namespace RobotAndDonkey.Game.Modifiers;
|
||||
|
||||
public record EfficientModifier(EModifierDuration Duration) : Modifier(EModifierKind.Card, EModifierId.Efficient, Duration)
|
||||
{
|
||||
protected override EfficientModifier CreateInstance() => new(Duration);
|
||||
|
||||
public override string ToolTip => "This instruction consumes no tape space";
|
||||
}
|
||||
48
RobotAndDonkey.Game/Modifiers/Entity.cs
Normal file
48
RobotAndDonkey.Game/Modifiers/Entity.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
|
||||
namespace RobotAndDonkey.Game.Modifiers;
|
||||
|
||||
public record Entity()
|
||||
{
|
||||
protected Entity(Entity clone)
|
||||
{
|
||||
m_Modifiers = [];
|
||||
foreach (var modifier in clone.Modifiers)
|
||||
AddModifier(modifier.DeepClone(), []);
|
||||
}
|
||||
|
||||
public void InsertModifier(Modifier modifier, List<Intent> newIntents)
|
||||
{
|
||||
if (m_Modifiers.Any(m => m.Id == modifier.Id))
|
||||
throw new InvalidOperationException();
|
||||
|
||||
m_Modifiers.Insert(0, modifier);
|
||||
modifier.OnAdded(this, newIntents);
|
||||
}
|
||||
|
||||
public void AddModifier(Modifier modifier, List<Intent> newIntents)
|
||||
{
|
||||
if (m_Modifiers.Any(m => m.Id == modifier.Id))
|
||||
throw new InvalidOperationException();
|
||||
|
||||
m_Modifiers.Add(modifier);
|
||||
modifier.OnAdded(this, newIntents);
|
||||
}
|
||||
|
||||
public void RemoveModifier(Modifier modifier, List<Intent> newIntents)
|
||||
{
|
||||
if (!m_Modifiers.Remove(modifier))
|
||||
throw new InvalidOperationException();
|
||||
|
||||
modifier.OnRemoved(this, newIntents);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "[" + string.Join(", ", m_Modifiers) + "]";
|
||||
}
|
||||
|
||||
public IReadOnlyList<Modifier> Modifiers => m_Modifiers;
|
||||
|
||||
private readonly List<Modifier> m_Modifiers = [];
|
||||
}
|
||||
75
RobotAndDonkey.Game/Modifiers/GlobalImmunityModifier.cs
Normal file
75
RobotAndDonkey.Game/Modifiers/GlobalImmunityModifier.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using RobotAndDonkey.Game.Cards;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
|
||||
namespace RobotAndDonkey.Game.Modifiers;
|
||||
|
||||
public record GlobalImmunityModifier(EModifierDuration Duration) : Modifier(EModifierKind.Card, EModifierId.GlobalImmunity, Duration)
|
||||
{
|
||||
public override void Before(Guid requestId, ReadOnlySpan<Intent> intents, CoreLoop coreLoop, Entity owner, List<Intent> newIntents, List<Result> results)
|
||||
{
|
||||
foreach (var (entity, location) in RunProgram.CollectEntities(coreLoop))
|
||||
{
|
||||
foreach (var existingModifier in entity.Modifiers)
|
||||
{
|
||||
if (existingModifier == this)
|
||||
continue;
|
||||
|
||||
foreach (var (source, immuneModifier) in Modifiers)
|
||||
{
|
||||
if (immuneModifier == EModifierId.GlobalImmunity)
|
||||
continue;
|
||||
|
||||
if (immuneModifier == EModifierId._Invalid || immuneModifier == existingModifier.Id)
|
||||
{
|
||||
if (existingModifier.DebuffSources.Add(source))
|
||||
{
|
||||
if (entity is Card card)
|
||||
results.Add(new ModifyCardResult(requestId, card.DeepClone(), immuneModifier, location));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void After(Guid requestId, ReadOnlySpan<Intent> intents, CoreLoop coreLoop, Entity owner, List<Intent> newIntents, List<Result> results)
|
||||
{
|
||||
foreach (var (entity, location) in RunProgram.CollectEntities(coreLoop))
|
||||
{
|
||||
foreach (var existingModifier in entity.Modifiers)
|
||||
{
|
||||
if (existingModifier == this)
|
||||
continue;
|
||||
|
||||
foreach (var (source, immuneModifier) in Modifiers)
|
||||
{
|
||||
if (immuneModifier == EModifierId.GlobalImmunity)
|
||||
continue;
|
||||
|
||||
if (immuneModifier == EModifierId._Invalid || immuneModifier == existingModifier.Id)
|
||||
{
|
||||
if (existingModifier.DebuffSources.Remove(source))
|
||||
{
|
||||
if (entity is Card card)
|
||||
results.Add(new ModifyCardResult(requestId, card.DeepClone(), immuneModifier, location));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override GlobalImmunityModifier CreateInstance()
|
||||
{
|
||||
var clone = new GlobalImmunityModifier(Duration);
|
||||
foreach (var tuple in Modifiers)
|
||||
clone.Modifiers.Add(tuple);
|
||||
return clone;
|
||||
}
|
||||
|
||||
public readonly List<(Entity Source, EModifierId Modifier)> Modifiers = [];
|
||||
|
||||
public override string ToolTip => $"Immune against {string.Join(", ", Modifiers.Select(m => m.Modifier))}, {Duration}";
|
||||
}
|
||||
34
RobotAndDonkey.Game/Modifiers/GravityModifier.cs
Normal file
34
RobotAndDonkey.Game/Modifiers/GravityModifier.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
|
||||
namespace RobotAndDonkey.Game.Modifiers;
|
||||
|
||||
public record GravityModifier(EModifierDuration Duration) : Modifier(EModifierKind.Robot, EModifierId.Gravity, Duration)
|
||||
{
|
||||
protected override GravityModifier CreateInstance() => new(Duration);
|
||||
|
||||
public override void OnAdded(Entity owner, List<Intent> newIntents)
|
||||
{
|
||||
newIntents.Add(new ModifyCurrency(new(0, -Balancing.Instance.GravityMaxCarryPenalty, 0, 0, 0, 0)));
|
||||
}
|
||||
|
||||
public override void Before(Guid requestId, ReadOnlySpan<Intent> intents, CoreLoop coreLoop, Entity owner, List<Intent> newIntents, List<Result> results)
|
||||
{
|
||||
foreach (var intent in intents)
|
||||
{
|
||||
if (intent is Move)
|
||||
intent.EnergyCost += Balancing.Instance.GravityExtraMoveCost;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnRemoved(Entity owner, List<Intent> newIntents)
|
||||
{
|
||||
newIntents.Add(new ModifyCurrency(new(0, Balancing.Instance.GravityMaxCarryPenalty, 0, 0, 0, 0)));
|
||||
}
|
||||
|
||||
public override string ToolTip => $"Gravity: −{Balancing.Instance.GravityMaxCarryPenalty} Max Carry; Move costs +{Balancing.Instance.GravityExtraMoveCost} energy (temporary).";
|
||||
}
|
||||
25
RobotAndDonkey.Game/Modifiers/HeatWaveModifier.cs
Normal file
25
RobotAndDonkey.Game/Modifiers/HeatWaveModifier.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
|
||||
namespace RobotAndDonkey.Game.Modifiers;
|
||||
|
||||
public record HeatWaveModifier(EModifierDuration Duration) : Modifier(EModifierKind.Robot, EModifierId.HeatWave, Duration)
|
||||
{
|
||||
protected override HeatWaveModifier CreateInstance() => new(Duration);
|
||||
|
||||
public override void OnAdded(Entity owner, List<Intent> newIntents)
|
||||
{
|
||||
newIntents.Add(new ModifyCurrency(new(-Balancing.Instance.HeatWaveEnergyPenalty, 0, 0, 0, 0, 0)));
|
||||
}
|
||||
|
||||
public override void OnRemoved(Entity owner, List<Intent> newIntents)
|
||||
{
|
||||
newIntents.Add(new ModifyCurrency(new(Balancing.Instance.HeatWaveEnergyPenalty, 0, 0, 0, 0, 0)));
|
||||
}
|
||||
|
||||
public override string ToolTip => $"Heat wave: immediate −{Balancing.Instance.HeatWaveEnergyPenalty} Energy (temporary).";
|
||||
}
|
||||
43
RobotAndDonkey.Game/Modifiers/Modifier.cs
Normal file
43
RobotAndDonkey.Game/Modifiers/Modifier.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
|
||||
namespace RobotAndDonkey.Game.Modifiers;
|
||||
|
||||
public abstract record Modifier(EModifierKind Kind, EModifierId Id, EModifierDuration Duration)
|
||||
{
|
||||
public Modifier DeepClone()
|
||||
{
|
||||
var result = CreateInstance();
|
||||
foreach (var debuffSource in DebuffSources)
|
||||
result.DebuffSources.Add(debuffSource);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected abstract Modifier CreateInstance();
|
||||
|
||||
public virtual void OnAdded(Entity owner, List<Intent> newIntents)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Before(Guid requestId, ReadOnlySpan<Intent> intents, CoreLoop coreLoop, Entity owner, List<Intent> newIntents, List<Result> results)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void After(Guid requestId, ReadOnlySpan<Intent> intents, CoreLoop coreLoop, Entity owner, List<Intent> newIntents, List<Result> results)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void OnRemoved(Entity owner, List<Intent> newIntents)
|
||||
{
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "<" + $"{Id}, {Duration}" + (DebuffSources.Count > 0 ? ", debuffed" : "") + ">";
|
||||
}
|
||||
|
||||
public HashSet<Entity> DebuffSources { get; } = [];
|
||||
|
||||
public abstract string ToolTip { get; }
|
||||
}
|
||||
79
RobotAndDonkey.Game/Modifiers/ModifierStack.cs
Normal file
79
RobotAndDonkey.Game/Modifiers/ModifierStack.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
|
||||
namespace RobotAndDonkey.Game.Modifiers;
|
||||
|
||||
public class ModifierStack
|
||||
{
|
||||
public void Push(Entity entity)
|
||||
{
|
||||
m_Stack.Push((entity, entity.Modifiers));
|
||||
}
|
||||
|
||||
public void Pop()
|
||||
{
|
||||
_ = m_Stack.Pop();
|
||||
}
|
||||
|
||||
private IEnumerable<(Entity Owner, Modifier Modifier)> GetModifiers()
|
||||
{
|
||||
foreach (var (owner, modifiers) in m_Stack)
|
||||
{
|
||||
foreach (var modifier in modifiers)
|
||||
{
|
||||
if (modifier.DebuffSources.Count == 0)
|
||||
yield return (owner, modifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Execute(Guid requestId, CoreLoop coreLoop, List<Intent> intents, List<Result> results, bool force, bool verbose)
|
||||
{
|
||||
var newIntents = new List<Intent>();
|
||||
var index = 0;
|
||||
do
|
||||
{
|
||||
var currentIntents = intents[index..];
|
||||
var modifiableIntents = currentIntents.Where(i => !i.Immune).ToArray();
|
||||
foreach (var (owner, modifier) in GetModifiers())
|
||||
{
|
||||
if (modifier.DebuffSources.Count == 0)
|
||||
{
|
||||
if (verbose)
|
||||
Debug.WriteLine($"Before {modifier}");
|
||||
modifier.Before(requestId, modifiableIntents, coreLoop, owner, newIntents, results);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var intent in currentIntents)
|
||||
{
|
||||
var isValid = intent.IsValid(coreLoop);
|
||||
if (force || isValid)
|
||||
{
|
||||
if (verbose)
|
||||
Debug.WriteLine($"Run {(isValid ? "Valid" : "Invalid")} intent: {intent}");
|
||||
intent.Run(requestId, coreLoop, newIntents, results);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (owner, modifier) in GetModifiers())
|
||||
{
|
||||
if (modifier.DebuffSources.Count == 0)
|
||||
{
|
||||
if (verbose)
|
||||
Debug.WriteLine($"After {modifier}");
|
||||
modifier.After(requestId, modifiableIntents, coreLoop, owner, newIntents, results);
|
||||
}
|
||||
}
|
||||
|
||||
index = intents.Count;
|
||||
intents.AddRange(newIntents);
|
||||
newIntents.Clear();
|
||||
} while (index < intents.Count);
|
||||
}
|
||||
|
||||
private readonly Stack<(Entity Owner, IReadOnlyList<Modifier> Modifiers)> m_Stack = [];
|
||||
}
|
||||
24
RobotAndDonkey.Game/Modifiers/OptimizedModifier.cs
Normal file
24
RobotAndDonkey.Game/Modifiers/OptimizedModifier.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Execution;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
|
||||
namespace RobotAndDonkey.Game.Modifiers;
|
||||
|
||||
public record OptimizedModifier(EModifierDuration Duration) : Modifier(EModifierKind.Card, EModifierId.Optimized, Duration)
|
||||
{
|
||||
protected override OptimizedModifier CreateInstance() => new(Duration);
|
||||
|
||||
public override void Before(Guid requestId, ReadOnlySpan<Intent> intents, CoreLoop coreLoop, Entity owner, List<Intent> newIntents, List<Result> results)
|
||||
{
|
||||
foreach (var intent in intents)
|
||||
{
|
||||
if (intent is CardCostIntent cardCostIntent)
|
||||
cardCostIntent.EnergyCost -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToolTip => "Energy cost reduced by 1";
|
||||
}
|
||||
14
RobotAndDonkey.Game/Modifiers/PersistentModifier.cs
Normal file
14
RobotAndDonkey.Game/Modifiers/PersistentModifier.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
|
||||
namespace RobotAndDonkey.Game.Modifiers;
|
||||
|
||||
public record PersistentModifier(EModifierDuration Duration) : Modifier(EModifierKind.Card, EModifierId.Persistent, Duration)
|
||||
{
|
||||
protected override PersistentModifier CreateInstance() => new(Duration);
|
||||
|
||||
public override string ToolTip => "This instruction is not discarded after execution.";
|
||||
}
|
||||
29
RobotAndDonkey.Game/Modifiers/PestModifier.cs
Normal file
29
RobotAndDonkey.Game/Modifiers/PestModifier.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
|
||||
namespace RobotAndDonkey.Game.Modifiers;
|
||||
|
||||
public record PestModifier(EModifierDuration Duration) : Modifier(EModifierKind.Robot, EModifierId.Pest, Duration)
|
||||
{
|
||||
protected override PestModifier CreateInstance() => new(Duration);
|
||||
|
||||
public override void Before(Guid requestId, ReadOnlySpan<Intent> intents, CoreLoop coreLoop, Entity owner, List<Intent> newIntents, List<Result> results)
|
||||
{
|
||||
foreach (var intent in intents)
|
||||
{
|
||||
if (intent is ModifyCurrency { Delta.Delivery: > 0 } modifyCurrency)
|
||||
{
|
||||
var original = modifyCurrency.Delta;
|
||||
var reduced = (int)MathF.Floor(original.Delivery * Balancing.Instance.PestDeliveryMultiplier);
|
||||
modifyCurrency.Delta = original with { Delivery = reduced };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToolTip => $"Deliveries are reduced to {Balancing.Instance.PestDeliveryMultiplier * 100:N0}% (temporary).";
|
||||
}
|
||||
17
RobotAndDonkey.Game/Modifiers/RaceCondition.cs
Normal file
17
RobotAndDonkey.Game/Modifiers/RaceCondition.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Cards;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
using RobotAndDonkey.Game.Utils;
|
||||
|
||||
namespace RobotAndDonkey.Game.Modifiers;
|
||||
|
||||
public record RaceConditionModifier(EModifierDuration Duration) : Modifier(EModifierKind.Card, EModifierId.RaceCondition, Duration)
|
||||
{
|
||||
protected override RaceConditionModifier CreateInstance() => new(Duration);
|
||||
|
||||
public override string ToolTip => $"Has a {Balancing.Instance.RaceConditionChance * 100:N0}% chance of switching places with the next card instead of executing.";
|
||||
}
|
||||
42
RobotAndDonkey.Game/Modifiers/RainModifier.cs
Normal file
42
RobotAndDonkey.Game/Modifiers/RainModifier.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Execution;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
|
||||
namespace RobotAndDonkey.Game.Modifiers;
|
||||
|
||||
public record RainModifier(EModifierDuration Duration) : Modifier(EModifierKind.Cell, EModifierId.Rain, Duration)
|
||||
{
|
||||
protected override RainModifier CreateInstance() => new(Duration);
|
||||
|
||||
public override void OnAdded(Entity owner, List<Intent> newIntents)
|
||||
{
|
||||
if (owner is not Cell cell)
|
||||
throw new NotSupportedException("Rain is only allowed in cells.");
|
||||
|
||||
cell.Type = cell.Type switch
|
||||
{
|
||||
ECellType.Grass => ECellType.Mud,
|
||||
ECellType.Rocky => ECellType.Dry,
|
||||
_ => throw new NotSupportedException("Rain only works on Rocky and Grass")
|
||||
};
|
||||
}
|
||||
|
||||
public override void OnRemoved(Entity owner, List<Intent> newIntents)
|
||||
{
|
||||
if (owner is not Cell cell)
|
||||
throw new NotSupportedException("Rain is only allowed in cells.");
|
||||
|
||||
cell.Type = cell.Type switch
|
||||
{
|
||||
ECellType.Mud => ECellType.Grass,
|
||||
ECellType.Dry => ECellType.Rocky,
|
||||
_ => throw new NotSupportedException("Rain can only be removed from Dry and Mud")
|
||||
};
|
||||
}
|
||||
|
||||
public override string ToolTip => "Humidity was reduced from this land, reducing fertility and maybe drying out.";
|
||||
}
|
||||
34
RobotAndDonkey.Game/Modifiers/RangerFertileRestModifier.cs
Normal file
34
RobotAndDonkey.Game/Modifiers/RangerFertileRestModifier.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
using RobotAndDonkey.Game.Pois;
|
||||
|
||||
namespace RobotAndDonkey.Game.Modifiers;
|
||||
|
||||
public record RangerFertileRestModifier() : Modifier(EModifierKind.Robot, EModifierId.RangerFertileRest, EModifierDuration.Permanent)
|
||||
{
|
||||
protected override RangerFertileRestModifier CreateInstance() => new();
|
||||
|
||||
public override void Before(Guid requestId, ReadOnlySpan<Intent> intents, CoreLoop coreLoop, Entity owner, List<Intent> newIntents, List<Result> results)
|
||||
{
|
||||
var avatarCell = coreLoop.Board.Cells.First(c => c.Poi is Avatar);
|
||||
if (avatarCell.Type != ECellType.Fertile)
|
||||
return;
|
||||
|
||||
foreach (var intent in intents)
|
||||
{
|
||||
if (intent is ModifyCurrency { Delta.Energy: > 0 })
|
||||
{
|
||||
newIntents.Add(new ModifyCurrency(new(Balancing.Instance.RangerFertileRestEnergyDelta, 0, 0, 0, 0, 0)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToolTip => $"Replenishes an extra {Balancing.Instance.RangerFertileRestEnergyDelta} energy when resting on fertile ground";
|
||||
}
|
||||
24
RobotAndDonkey.Game/Modifiers/ThrottledModifier.cs
Normal file
24
RobotAndDonkey.Game/Modifiers/ThrottledModifier.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game.Execution;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
|
||||
namespace RobotAndDonkey.Game.Modifiers;
|
||||
|
||||
public record ThrottledModifier(EModifierDuration Duration) : Modifier(EModifierKind.Card, EModifierId.Throttled, Duration)
|
||||
{
|
||||
protected override ThrottledModifier CreateInstance() => new(Duration);
|
||||
|
||||
public override void Before(Guid requestId, ReadOnlySpan<Intent> intents, CoreLoop coreLoop, Entity owner, List<Intent> newIntents, List<Result> results)
|
||||
{
|
||||
foreach (var intent in intents)
|
||||
{
|
||||
if (intent is CardCostIntent cardCostIntent)
|
||||
cardCostIntent.EnergyCost += 1;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToolTip => "Increases the energy cost by 1";
|
||||
}
|
||||
49
RobotAndDonkey.Game/Modifiers/UnreliableModifier.cs
Normal file
49
RobotAndDonkey.Game/Modifiers/UnreliableModifier.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Cards;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Intents;
|
||||
|
||||
namespace RobotAndDonkey.Game.Modifiers;
|
||||
|
||||
|
||||
public record UnreliableCardModifier(EModifierDuration Duration) : UnreliableModifierBase(EModifierKind.Card, Duration)
|
||||
{
|
||||
protected override UnreliableCardModifier CreateInstance() => new(Duration);
|
||||
|
||||
public override string ToolTip => $"Has a {Balancing.Instance.UnreliableChance * 100:N0}% chance of doing nothing";
|
||||
}
|
||||
|
||||
public record UnreliableCellModifier(EModifierDuration Duration) : UnreliableModifierBase(EModifierKind.Cell, Duration)
|
||||
{
|
||||
protected override UnreliableCellModifier CreateInstance() => new(Duration);
|
||||
|
||||
public override string ToolTip => $"While in this area, instructions have a {Balancing.Instance.UnreliableChance * 100:N0}% chance of doing nothing";
|
||||
}
|
||||
|
||||
public abstract record UnreliableModifierBase(EModifierKind Kind, EModifierDuration Duration) : Modifier(Kind, EModifierId.Unreliable, Duration)
|
||||
{
|
||||
public override void Before(Guid requestId, ReadOnlySpan<Intent> intents, CoreLoop coreLoop, Entity owner, List<Intent> newIntents, List<Result> results)
|
||||
{
|
||||
if (coreLoop.RunPhase != ERunPhase.ExecuteProgram)
|
||||
return;
|
||||
|
||||
if (coreLoop.Random.NextSingle() <= Balancing.Instance.UnreliableChance)
|
||||
{
|
||||
if (owner is Card card)
|
||||
results.Add(new ModifyCardResult(requestId, card.DeepClone(), EModifierId.Unreliable, ECardLocation.Tape));
|
||||
if (owner is Cell cell)
|
||||
results.Add(new ModifyCellResult(requestId, new(coreLoop.Board), EModifierId.Unreliable, cell.Hex));
|
||||
foreach (var intent in intents)
|
||||
intent.DebuffSources.Add(this);
|
||||
}
|
||||
}
|
||||
|
||||
public override void After(Guid requestId, ReadOnlySpan<Intent> intents, CoreLoop coreLoop, Entity owner, List<Intent> newIntents, List<Result> results)
|
||||
{
|
||||
foreach (var intent in intents)
|
||||
intent.DebuffSources.Remove(this);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user