Skip to content
2 changes: 0 additions & 2 deletions ModiBuff/ModiBuff.Benchmarks/BenchmarkModifierRecipes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,9 @@ protected override void SetupRecipes()
.Stack(WhenStackEffect.Always);

Add("InitDamage_CostMana")
.ApplyCost(CostType.Mana, 5)
.Effect(new DamageEffect(5), EffectOn.Init);

Add("InitDamage_ApplyCondition_HealthAbove100")
.ApplyCondition(StatType.Health, 100, ComparisonType.GreaterOrEqual)
.Effect(new DamageEffect(5), EffectOn.Init);
}
}
Expand Down
14 changes: 7 additions & 7 deletions ModiBuff/ModiBuff.Examples/BasicConsole/GameController.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Linq;
using ModiBuff.Core;

namespace ModiBuff.Examples.BasicConsole
Expand Down Expand Up @@ -47,9 +48,8 @@ public GameController()
//But we're adding it as an applier, and not as a normal modifier
//This means that instead of it being applier to the player
//it will be applied to a unit that the player attacks
_player.ModifierApplierController.TryAddApplier(_idManager.GetId("DoT")!.Value, false, ApplierType.Attack);
_player.ModifierApplierController.TryAddApplier(_idManager.GetId("InitHeal")!.Value, false,
ApplierType.Cast);
_player.AddApplierModifierNew(_idManager.GetId("DoT")!.Value, ApplierType.Attack);
_player.AddApplierModifierNew(_idManager.GetId("InitHeal")!.Value, ApplierType.Cast);
//_player.ModifierController.TryAddApplier(_idManager.GetId("DisarmChance"), true, ApplierType.Cast);
}

Expand Down Expand Up @@ -101,12 +101,12 @@ private bool PlayerAction()
private bool PlayerCastAction()
{
//Display all possible modifiers to cast, then when one was chosen, choose the target
var modifierIds = _player.ModifierApplierController.GetApplierCastModifierIds();
int[] modifierIds = _player.GetApplierCastModifierIds().ToArray();

while (true)
{
Console.GameMessage("Choose modifier to cast, or c to cancel");
for (int i = 0; i < modifierIds.Count; i++)
for (int i = 0; i < modifierIds.Length; i++)
{
var modifierInfo = _recipes.GetModifierInfo(modifierIds[i]);
Console.GameMessage($"{i + 1} - {modifierInfo.DisplayName} - {modifierInfo.Description}");
Expand All @@ -115,10 +115,10 @@ private bool PlayerCastAction()
string castAction = System.Console.ReadLine();
if (int.TryParse(castAction, out int castActionInt))
{
if (castActionInt > 0 && castActionInt <= modifierIds.Count)
if (castActionInt > 0 && castActionInt <= modifierIds.Length)
{
//TODO: choosing target
_player.TryCast(modifierIds[castActionInt - 1], _player);
_player.TryApply(modifierIds[castActionInt - 1], _player);
break;
}
}
Expand Down
4 changes: 1 addition & 3 deletions ModiBuff/ModiBuff.Examples/BasicConsole/ModifierRecipes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,8 @@ protected override void SetupRecipes()
.Effect(new DamageEffect(1), EffectOn.Interval);

//Here we introduce a new effect, and a chance for the modifier to be applied
//Chance needs to be applied when adding the applier
Add("DisarmChance", "Disarm", "Disarms target for 1 second, 20% chance to apply")
//When applying a modifier (through attacking or casting it)
//It will have 20% chance to apply the modifier to the unit
.ApplyChance(0.2f)
//Disarms (can't attack) the target unit for 1 second when applied
.Effect(new StatusEffectEffect(StatusEffectType.Disarm, 1f), EffectOn.Init)
.Remove(1f).Refresh();
Expand Down
14 changes: 7 additions & 7 deletions ModiBuff/ModiBuff.Examples/BasicConsole/UIExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Linq;
using ModiBuff.Core;
using ModiBuff.Core.Units;
using ModiBuff.Core.Units.Interfaces.NonGeneric;
Expand All @@ -10,30 +11,29 @@ public static class UIExtensions
public static void PrintStateAndModifiers(this IModifierApplierOwner owner, IModifierRecipes modifierRecipes)
{
var modifierController = ((IModifierOwner)owner).ModifierController;
var modifierApplierController = owner.ModifierApplierController;
//Stats, ApplyModifiers, Normal modifiers.
var damagable = (IDamagable)owner;
var attacker = (IAttacker)owner;
Console.GameMessage($"Player stats: {damagable.Health}/{damagable.MaxHealth} HP, " +
$"{attacker.Damage} Damage");
//Appliers
//Name, description, checks, (states, like cooldown)
var applierAttackIds = modifierApplierController.GetApplierAttackModifierIds();
if (applierAttackIds != null && applierAttackIds.Count > 0)
var applierAttackIds = ((Unit)owner).GetApplierCastModifierIds().ToArray();
if (applierAttackIds.Length > 0)
{
Console.GameMessage("Player attack appliers:");
for (int i = 0; i < applierAttackIds.Count; i++)
for (int i = 0; i < applierAttackIds.Length; i++)
{
var modifierInfo = modifierRecipes.GetModifierInfo(applierAttackIds[i]);
Console.GameMessage($"{i + 1} - {modifierInfo.DisplayName} - {modifierInfo.Description}");
}
}

var applierCastIds = modifierApplierController.GetApplierCastModifierIds();
if (applierCastIds != null && applierCastIds.Count > 0)
var applierCastIds = ((Unit)owner).GetApplierCastModifierIds().ToArray();
if (applierCastIds.Length > 0)
{
Console.GameMessage("Player cast appliers:");
for (int i = 0; i < applierCastIds.Count; i++)
for (int i = 0; i < applierCastIds.Length; i++)
{
var modifierInfo = modifierRecipes.GetModifierInfo(applierCastIds[i]);
Console.GameMessage($"{i + 1} - {modifierInfo.DisplayName} - {modifierInfo.Description}");
Expand Down
114 changes: 109 additions & 5 deletions ModiBuff/ModiBuff.Examples/BasicConsole/Unit.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using ModiBuff.Core;
using ModiBuff.Core.Units;
using ModiBuff.Core.Units.Interfaces.NonGeneric;
Expand All @@ -21,7 +22,7 @@ public sealed class Unit : IModifierOwner, IUpdatable, IDamagable, IAttacker, IH
public ModifierController ModifierController { get; }

//TODO Explain
public ModifierApplierController ModifierApplierController { get; }
private readonly Dictionary<ApplierType, List<(int Id, ICheck[] Checks)>> _modifierAppliers;

//Basic implementation of status effects, unit can't attack when it's disarmed
//Move when it's rooted/frozen/stunned, etc.
Expand All @@ -47,7 +48,11 @@ public Unit(string name, float health, float damage)

//Remember to rent the modifier controllers in the constructor
ModifierController = ModifierControllerPool.Instance.Rent();
ModifierApplierController = ModifierControllerPool.Instance.RentApplier();
_modifierAppliers = new Dictionary<ApplierType, List<(int, ICheck[])>>
{
{ ApplierType.Attack, new List<(int, ICheck[])>() },
{ ApplierType.Cast, new List<(int, ICheck[])>() }
};
StatusEffectController = new StatusEffectController();
_targetingSystem = new TargetingSystem();
}
Expand All @@ -60,7 +65,6 @@ public void Update(float deltaTime)
//We need to update the modifier controller each frame/tick
//To update the modifier timers (interval, duration)
ModifierController.Update(deltaTime);
ModifierApplierController.Update(deltaTime);
StatusEffectController.Update(deltaTime);
}

Expand Down Expand Up @@ -95,14 +99,71 @@ public float Attack(Unit target)
if (!StatusEffectController.HasLegalAction(LegalAction.Act))
return 0;

//This method will try to apply all our applier attack modifiers to the target
this.ApplyAllAttackModifier(target);
foreach ((int id, ICheck[] checks) in _modifierAppliers[ApplierType.Attack])
{
bool checksPassed = true;
if (checks != null)
foreach (var check in checks)
{
if (!check.Check(this))
{
checksPassed = false;
break;
}
}

if (!checksPassed)
continue;

if (checks != null)
foreach (var check in checks)
check.Use(this);

target.ModifierController.Add(id, target, this);
}

float damageDealt = target.TakeDamage(Damage, this);

return damageDealt;
}

public bool TryApply(int modifierId, IUnit target)
{
if (!(target is IModifierOwner modifierTarget))
return false;
if (!StatusEffectController.HasLegalAction(LegalAction.Cast))
return false;
if (!_modifierAppliers.TryGetValue(ApplierType.Cast, out var appliers))
return false;

(int Id, ICheck[] Checks)? applier = null;
for (int i = 0; i < appliers.Count; i++)
{
if (appliers[i].Id == modifierId)
{
applier = appliers[i];
break;
}
}

if (applier == null)
return false;

if (applier.Value.Checks != null)
{
foreach (var check in applier.Value.Checks)
if (!check.Check(this))
return false;

for (int i = 0; i < applier.Value.Checks.Length; i++)
applier.Value.Checks[i].Use(this);
}

modifierTarget.ModifierController.Add(modifierId, modifierTarget, this);

return true;
}

public float TakeDamage(float damage, IUnit source)
{
if (IsDead)
Expand Down Expand Up @@ -141,6 +202,49 @@ public float Heal(float heal, IUnit source)
return Health - originalHealth;
}

public bool ContainsApplier(int modifierId, ApplierType applierType)
{
return _modifierAppliers.TryGetValue(applierType, out var list) && list.Exists(c => c.Id == modifierId);
}

public bool RemoveApplier(int id, ApplierType applierType)
{
if (!_modifierAppliers.TryGetValue(applierType, out var list))
return false;

int index = list.FindIndex(c => c.Id == id);
if (index == -1)
return false;

list.RemoveAt(index);
return true;
}

public void AddApplierModifierNew(int modifierId, ApplierType applierType, ICheck[] checks = null)
{
if (checks?.Length > 0)
{
if (_modifierAppliers.TryGetValue(applierType, out var list))
{
list.Add((modifierId, checks));
return;
}

_modifierAppliers[applierType] =
new List<(int Id, ICheck[] Checks)>(new[] { (modifierId, checks) });
return;
}

_modifierAppliers[applierType].Add((modifierId, null));
}

public IEnumerable<int> GetApplierCastModifierIds()
{
if (_modifierAppliers.TryGetValue(ApplierType.Cast, out var list))
foreach ((int id, ICheck[] _) in list)
yield return id;
}

public string GetDebugString()
{
return $"Unit, health: {Health}/{MaxHealth}, damage: {Damage}";
Expand Down
61 changes: 41 additions & 20 deletions ModiBuff/ModiBuff.Tests/ApplierTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ public void DamageApplier_Attack_Damage()
{
Setup();

var applier = Recipes.GetGenerator("InitDamage");
Unit.AddApplierModifier(applier, ApplierType.Attack);
Unit.AddApplierModifierNew(IdManager.GetId("InitDamage").Value, ApplierType.Attack);

Unit.Attack(Enemy);

Expand All @@ -40,8 +39,7 @@ public void HealApplier_Attack_Heal()
.Effect(new HealEffect(10), EffectOn.Init);
Setup();

var applier = Recipes.GetGenerator("InitStrongHeal");
Unit.AddApplierModifier(applier, ApplierType.Attack);
Unit.AddApplierModifierNew(IdManager.GetId("InitStrongHeal").Value, ApplierType.Attack);

Enemy.TakeDamage(10, Enemy);
Unit.Attack(Enemy); //Heal appliers triggers first, then attack damage
Expand All @@ -56,8 +54,8 @@ public void DamageSelfApplier_Attack_DamageSelf()
.Effect(new DamageEffect(5, targeting: Targeting.SourceTarget), EffectOn.Init);
Setup();

Unit.AddApplierModifier(Recipes.GetGenerator("InitDamageSelf"), ApplierType.Attack);
Unit.AddApplierModifier(Recipes.GetGenerator("InitDamage"), ApplierType.Attack);
Unit.AddApplierModifierNew(IdManager.GetId("InitDamageSelf").Value, ApplierType.Attack);
Unit.AddApplierModifierNew(IdManager.GetId("InitDamage").Value, ApplierType.Attack);

Unit.Attack(Enemy);

Expand All @@ -69,10 +67,10 @@ public void DamageApplier_Cast_Damage()
{
Setup();

var applier = Recipes.GetGenerator("InitDamage");
Unit.AddApplierModifier(applier, ApplierType.Cast);
int id = IdManager.GetId("InitDamage").Value;
Unit.AddApplierModifierNew(IdManager.GetId("InitDamage").Value, ApplierType.Cast);

Unit.TryCast(applier.Id, Enemy);
Unit.TryCast(id, Enemy);

Assert.AreEqual(EnemyHealth - 5, Enemy.Health);
}
Expand Down Expand Up @@ -100,15 +98,13 @@ public void DamageApplier_Interval()
public void InitDamageCostMana()
{
AddRecipe("InitDamage_CostMana")
.ApplyCost(CostType.Mana, 5)
.Effect(new DamageEffect(5), EffectOn.Init);
Setup();

var generator = Recipes.GetGenerator("InitDamage_CostMana");
int id = IdManager.GetId("InitDamage_CostMana").Value;
Unit.AddApplierModifierNew(id, ApplierType.Cast, new ICheck[] { new CostCheck(CostType.Mana, 5) });

Unit.AddApplierModifier(generator, ApplierType.Cast);

Unit.TryCast(generator.Id, Enemy);
Unit.TryCast(id, Enemy);

Assert.AreEqual(UnitMana - 5, Unit.Mana);
Assert.AreEqual(EnemyHealth - 5, Enemy.Health);
Expand Down Expand Up @@ -241,19 +237,20 @@ public void Cast_AddApplier()
{
AddRecipe("AddApplier_Effect")
.Effect(new ApplierEffect("InitDamage"), EffectOn.Init)
.RemoveApplier(5, ApplierType.Cast, false);
AddEffect("AddApplier_ApplierEffect", new ApplierEffect("AddApplier_Effect", ApplierType.Cast, false));
.RemoveApplier(ApplierType.Cast, 5);
AddRecipe("AddApplier_ApplierEffect")
.Effect(new ApplierEffect("AddApplier_Effect", ApplierType.Cast), EffectOn.Init);
Setup();

Unit.AddEffectApplier("AddApplier_ApplierEffect");
Unit.TryCast("AddApplier_Effect", Enemy);
Unit.TryCastEffect("AddApplier_ApplierEffect", Unit);
Unit.AddModifierSelf("AddApplier_ApplierEffect");
Unit.TryCast("AddApplier_ApplierEffect", Unit);

Unit.TryCast("AddApplier_Effect", Enemy);
Assert.AreEqual(EnemyHealth - 5, Enemy.Health);

Unit.Update(5);
Assert.False(Unit.ContainsApplier("AddApplier_Effect"));
Assert.False(Unit.ContainsApplier("AddApplier_Effect", ApplierType.Cast));
Assert.False(Unit.ContainsModifier("AddApplier_Effect"));
}

Expand All @@ -273,7 +270,7 @@ public void ConditionalApplierBasedOnUnitType()
.Remove(5).Refresh();
Setup();

Unit.AddApplierModifier(Recipes.GetGenerator("ConditionalApplierBasedOnUnitType"), ApplierType.Cast);
Unit.AddApplierModifierNew(IdManager.GetId("ConditionalApplierBasedOnUnitType").Value, ApplierType.Cast);

Enemy.TakeDamage(5, Enemy);
Ally.TakeDamage(5, Ally);
Expand All @@ -283,5 +280,29 @@ public void ConditionalApplierBasedOnUnitType()
Unit.TryCast("ConditionalApplierBasedOnUnitType", Ally);
Assert.AreEqual(AllyHealth - 5 + 5, Ally.Health);
}

//[Test
public void AddApplier_DurationRemove()
{
AddRecipe("AddApplier_Effect")
.Effect(new ApplierEffect("InitDamage"), EffectOn.Init)
.RemoveApplier(ApplierType.Cast, 5);
//TODO Issue that modifier is not being added, so the duration doesn't work
//We probably need to have two modifiers for applier removal, one that is used as the applier by the unit
//Second as the duration remover that removes self and the applier in unit
Setup();

Unit.AddApplierModifierNew("AddApplier_Effect", ApplierType.Cast);
Assert.False(Unit.ContainsModifier("AddApplier_Effect"));

Unit.TryCast("AddApplier_Effect", Enemy);
Unit.Update(1);
Assert.AreEqual(EnemyHealth - 5, Enemy.Health);

Unit.Update(4);
Assert.False(Unit.ContainsApplier("AddApplier_Effect", ApplierType.Cast));
Assert.False(Unit.ContainsModifier("AddApplier_Effect"));
Assert.False(Unit.TryCast("AddApplier_Effect", Enemy));
}
}
}
Loading