diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index 5552b53883..0c47fcb548 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -510,6 +510,7 @@ + diff --git a/OpenRA.Mods.Common/Traits/Conditions/GrantConditionOnAttack.cs b/OpenRA.Mods.Common/Traits/Conditions/GrantConditionOnAttack.cs new file mode 100644 index 0000000000..f179ed1ee6 --- /dev/null +++ b/OpenRA.Mods.Common/Traits/Conditions/GrantConditionOnAttack.cs @@ -0,0 +1,168 @@ +#region Copyright & License Information +/* + * Copyright 2007-2017 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System.Collections.Generic; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits +{ + public class GrantConditionOnAttackInfo : ITraitInfo + { + [FieldLoader.Require] + [GrantedConditionReference] + [Desc("The condition type to grant.")] + public readonly string Condition = null; + + [Desc("Name of the armaments that grant this condition.")] + public readonly HashSet ArmamentNames = new HashSet() { "primary" }; + + [Desc("Shots required to apply an instance of the condition. If there are more instances of the condition granted than values listed,", + "the last value is used for all following instances beyond the defined range.")] + public readonly int[] RequiredShotsPerInstance = { 1 }; + + [Desc("Maximum instances of the condition to grant.")] + public readonly int MaximumInstances = 1; + + [Desc("Should all instances reset if the actor passes the final stage?")] + public readonly bool IsCyclic = false; + + [Desc("Amount of ticks required to pass without firing to revoke an instance.")] + public readonly int RevokeDelay = 15; + + [Desc("Should an instance be revoked if the actor changes target?")] + public readonly bool RevokeOnNewTarget = false; + + [Desc("Should all instances be revoked instead of only one?")] + public readonly bool RevokeAll = false; + + public object Create(ActorInitializer init) { return new GrantConditionOnAttack(init, this); } + } + + public class GrantConditionOnAttack : INotifyCreated, ITick, INotifyAttack + { + readonly GrantConditionOnAttackInfo info; + readonly Stack tokens = new Stack(); + + int cooldown = 0; + int shotsFired = 0; + ConditionManager manager; + + // Only tracked when RevokeOnNewTarget is true. + Target lastTarget = Target.Invalid; + + public GrantConditionOnAttack(ActorInitializer init, GrantConditionOnAttackInfo info) + { + this.info = info; + } + + void INotifyCreated.Created(Actor self) + { + manager = self.TraitOrDefault(); + } + + void GrantInstance(Actor self, string cond) + { + if (manager == null || string.IsNullOrEmpty(cond)) + return; + + tokens.Push(manager.GrantCondition(self, cond)); + } + + void RevokeInstance(Actor self, bool revokeAll) + { + shotsFired = 0; + + if (manager == null || tokens.Count == 0) + return; + + if (!revokeAll) + manager.RevokeCondition(self, tokens.Pop()); + else + while (tokens.Count > 0) + manager.RevokeCondition(self, tokens.Pop()); + } + + void ITick.Tick(Actor self) + { + if (tokens.Count > 0 && --cooldown == 0) + { + cooldown = info.RevokeDelay; + RevokeInstance(self, info.RevokeAll); + } + } + + bool TargetChanged(Target lastTarget, Target target) + { + // Invalidate reveal changing the target. + if (lastTarget.Type == TargetType.FrozenActor && target.Type == TargetType.Actor) + if (lastTarget.FrozenActor.Actor == target.Actor) + return false; + + if (lastTarget.Type == TargetType.Actor && target.Type == TargetType.FrozenActor) + if (target.FrozenActor.Actor == lastTarget.Actor) + return false; + + if (lastTarget.Type != target.Type) + return true; + + // Invalidate attacking different targets with shared target types. + if (lastTarget.Type == TargetType.Actor && target.Type == TargetType.Actor) + if (lastTarget.Actor != target.Actor) + return true; + + if (lastTarget.Type == TargetType.FrozenActor && target.Type == TargetType.FrozenActor) + if (lastTarget.FrozenActor != target.FrozenActor) + return true; + + if (lastTarget.Type == TargetType.Terrain && target.Type == TargetType.Terrain) + if (lastTarget.CenterPosition != target.CenterPosition) + return true; + + return false; + } + + void INotifyAttack.Attacking(Actor self, Target target, Armament a, Barrel barrel) + { + if (!info.ArmamentNames.Contains(a.Info.Name)) + return; + + if (info.RevokeOnNewTarget) + { + if (TargetChanged(lastTarget, target)) + RevokeInstance(self, info.RevokeAll); + + lastTarget = target; + } + + cooldown = info.RevokeDelay; + + if (!info.IsCyclic && tokens.Count >= info.MaximumInstances) + return; + + shotsFired++; + var requiredShots = tokens.Count < info.RequiredShotsPerInstance.Length + ? info.RequiredShotsPerInstance[tokens.Count] + : info.RequiredShotsPerInstance[info.RequiredShotsPerInstance.Length - 1]; + + if (shotsFired >= requiredShots) + { + if (info.IsCyclic && tokens.Count == info.MaximumInstances) + RevokeInstance(self, true); + else + GrantInstance(self, info.Condition); + + shotsFired = 0; + } + } + + void INotifyAttack.PreparingAttack(Actor self, Target target, Armament a, Barrel barrel) { } + } +}