Add support for per-source and total external condition caps.
This commit is contained in:
@@ -63,9 +63,6 @@ namespace OpenRA.Mods.Common.Traits
|
||||
/// <summary>Each granted condition receives a unique token that is used when revoking.</summary>
|
||||
Dictionary<int, string> tokens = new Dictionary<int, string>();
|
||||
|
||||
/// <summary>Set of whitelisted externally grantable conditions cached from ExternalConditions traits.</summary>
|
||||
string[] externalConditions = { };
|
||||
|
||||
/// <summary>Set of conditions that are monitored for stacked bonuses, and the bonus conditions that they grant.</summary>
|
||||
readonly Dictionary<string, string[]> stackedConditions = new Dictionary<string, string[]>();
|
||||
|
||||
@@ -114,12 +111,6 @@ namespace OpenRA.Mods.Common.Traits
|
||||
conditionCache[kv.Value] = conditionState.Tokens.Count > 0;
|
||||
}
|
||||
|
||||
// Build external condition whitelist
|
||||
externalConditions = self.Info.TraitInfos<ExternalConditionsInfo>()
|
||||
.SelectMany(t => t.Conditions)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
foreach (var sc in self.Info.TraitInfos<StackedConditionInfo>())
|
||||
{
|
||||
stackedConditions[sc.Condition] = sc.StackedConditions;
|
||||
@@ -172,11 +163,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
/// <returns>The token that is used to revoke this condition.</returns>
|
||||
/// <param name="external">Validate against the external condition whitelist.</param>
|
||||
/// <param name="duration">Automatically revoke condition after this delay if non-zero.</param>
|
||||
public int GrantCondition(Actor self, string condition, bool external = false, int duration = 0)
|
||||
public int GrantCondition(Actor self, string condition, int duration = 0)
|
||||
{
|
||||
if (external && !externalConditions.Contains(condition))
|
||||
return InvalidConditionToken;
|
||||
|
||||
var token = nextToken++;
|
||||
tokens.Add(token, condition);
|
||||
|
||||
@@ -218,26 +206,6 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return InvalidConditionToken;
|
||||
}
|
||||
|
||||
/// <summary>Returns true if the given external condition will have an effect on this actor.</summary>
|
||||
public bool AcceptsExternalCondition(Actor self, string condition, bool timed = false)
|
||||
{
|
||||
if (state == null)
|
||||
throw new InvalidOperationException("AcceptsExternalCondition cannot be queried before the actor has been fully created.");
|
||||
|
||||
if (!externalConditions.Contains(condition))
|
||||
return false;
|
||||
|
||||
// A timed condition can always replace an existing timed condition (resetting its duration)
|
||||
if (timed && timers.ContainsKey(condition))
|
||||
return true;
|
||||
|
||||
string[] sc;
|
||||
if (stackedConditions.TryGetValue(condition, out sc))
|
||||
return stackedTokens[condition].Count < sc.Length;
|
||||
|
||||
return !conditionCache[condition];
|
||||
}
|
||||
|
||||
/// <summary>Returns whether the specified token is valid for RevokeCondition</summary>
|
||||
public bool TokenValid(Actor self, int token)
|
||||
{
|
||||
|
||||
142
OpenRA.Mods.Common/Traits/Conditions/ExternalCondition.cs
Normal file
142
OpenRA.Mods.Common/Traits/Conditions/ExternalCondition.cs
Normal file
@@ -0,0 +1,142 @@
|
||||
#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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Allows a condition to be granted from an external source (Lua, warheads, etc).")]
|
||||
public class ExternalConditionInfo : ITraitInfo, Requires<ConditionManagerInfo>
|
||||
{
|
||||
[GrantedConditionReference]
|
||||
[FieldLoader.Require]
|
||||
public readonly string Condition = null;
|
||||
|
||||
[Desc("If > 0, restrict the number of times that this condition can be granted by a single source.")]
|
||||
public readonly int SourceCap = 0;
|
||||
|
||||
[Desc("If > 0, restrict the number of times that this condition can be granted by any source.")]
|
||||
public readonly int TotalCap = 0;
|
||||
|
||||
public object Create(ActorInitializer init) { return new ExternalCondition(init.Self, this); }
|
||||
}
|
||||
|
||||
public class ExternalCondition
|
||||
{
|
||||
class TimedToken
|
||||
{
|
||||
public int Token;
|
||||
public int Expires;
|
||||
}
|
||||
|
||||
public readonly ExternalConditionInfo Info;
|
||||
readonly ConditionManager conditionManager;
|
||||
readonly Dictionary<object, HashSet<int>> permanentTokens = new Dictionary<object, HashSet<int>>();
|
||||
readonly Dictionary<object, HashSet<TimedToken>> timedTokens = new Dictionary<object, HashSet<TimedToken>>();
|
||||
|
||||
public ExternalCondition(Actor self, ExternalConditionInfo info)
|
||||
{
|
||||
Info = info;
|
||||
conditionManager = self.Trait<ConditionManager>();
|
||||
}
|
||||
|
||||
public bool CanGrantCondition(Actor self, object source)
|
||||
{
|
||||
if (conditionManager == null || source == null)
|
||||
return false;
|
||||
|
||||
// Timed tokens do not count towards the source cap: the condition with the shortest
|
||||
// remaining duration can always be revoked to make room.
|
||||
if (Info.SourceCap > 0 && permanentTokens.GetOrAdd(source).Count >= Info.SourceCap)
|
||||
return false;
|
||||
|
||||
if (Info.TotalCap > 0 && permanentTokens.Values.SelectMany(t => t).Count() >= Info.TotalCap)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public int GrantCondition(Actor self, object source, int duration = 0)
|
||||
{
|
||||
if (conditionManager == null || source == null || !CanGrantCondition(self, source))
|
||||
return ConditionManager.InvalidConditionToken;
|
||||
|
||||
var token = conditionManager.GrantCondition(self, Info.Condition, duration);
|
||||
var permanent = permanentTokens.GetOrAdd(source);
|
||||
|
||||
if (duration > 0)
|
||||
{
|
||||
var timed = timedTokens.GetOrAdd(source);
|
||||
|
||||
// Remove expired tokens
|
||||
timed.RemoveWhere(t => t.Expires < self.World.WorldTick);
|
||||
|
||||
// Check level caps
|
||||
if (Info.SourceCap > 0)
|
||||
{
|
||||
if (permanent.Count + timed.Count >= Info.SourceCap)
|
||||
{
|
||||
var expire = timed.MinByOrDefault(t => t.Expires);
|
||||
if (expire != null)
|
||||
{
|
||||
timed.Remove(expire);
|
||||
if (conditionManager.TokenValid(self, expire.Token))
|
||||
conditionManager.RevokeCondition(self, expire.Token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Info.TotalCap > 0)
|
||||
{
|
||||
var totalCount = permanentTokens.Values.SelectMany(t => t).Count() + timedTokens.Values.SelectMany(t => t).Count();
|
||||
if (totalCount >= Info.TotalCap)
|
||||
{
|
||||
// Prefer tokens from the same source
|
||||
var expire = timedTokens.SelectMany(t => t.Value.Select(tt => new Tuple<object, TimedToken>(t.Key, tt)))
|
||||
.MinByOrDefault(t => t.Item2.Expires);
|
||||
if (expire != null)
|
||||
{
|
||||
if (conditionManager.TokenValid(self, expire.Item2.Token))
|
||||
conditionManager.RevokeCondition(self, expire.Item2.Token);
|
||||
|
||||
timedTokens[expire.Item1].Remove(expire.Item2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
timed.Add(new TimedToken { Expires = self.World.WorldTick + duration, Token = token });
|
||||
}
|
||||
else
|
||||
permanent.Add(token);
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
/// <summary>Revokes the external condition with the given token if it was granted by this trait.</summary>
|
||||
/// <returns><c>true</c> if the now-revoked condition was originally granted by this trait.</returns>
|
||||
public bool TryRevokeCondition(Actor self, object source, int token)
|
||||
{
|
||||
if (conditionManager == null || source == null)
|
||||
return false;
|
||||
|
||||
var removed = permanentTokens.GetOrAdd(source).Remove(token) ||
|
||||
timedTokens.GetOrAdd(source).RemoveWhere(t => t.Token == token) > 0;
|
||||
|
||||
if (removed && conditionManager.TokenValid(self, token))
|
||||
conditionManager.RevokeCondition(self, token);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
#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 OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Lists conditions that are accepted from external sources (Lua, warheads, etc).",
|
||||
"Externally granted conditions that aren't explicitly whitelisted will be silently ignored.")]
|
||||
public class ExternalConditionsInfo : TraitInfo<ExternalConditions>
|
||||
{
|
||||
[GrantedConditionReference]
|
||||
public readonly string[] Conditions = { };
|
||||
}
|
||||
|
||||
public class ExternalConditions { }
|
||||
}
|
||||
@@ -10,6 +10,7 @@
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
@@ -104,13 +105,18 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (a == self && !info.AffectsParent)
|
||||
return;
|
||||
|
||||
if (tokens.ContainsKey(a))
|
||||
return;
|
||||
|
||||
var stance = self.Owner.Stances[a.Owner];
|
||||
if (!info.ValidStances.HasStance(stance))
|
||||
return;
|
||||
|
||||
var cm = a.TraitOrDefault<ConditionManager>();
|
||||
if (cm != null && !tokens.ContainsKey(a) && cm.AcceptsExternalCondition(a, info.Condition))
|
||||
tokens[a] = cm.GrantCondition(a, info.Condition, true);
|
||||
var external = a.TraitsImplementing<ExternalCondition>()
|
||||
.FirstOrDefault(t => t.Info.Condition == info.Condition && t.CanGrantCondition(a, self));
|
||||
|
||||
if (external != null)
|
||||
tokens[a] = external.GrantCondition(a, self);
|
||||
}
|
||||
|
||||
public void UnitProducedByOther(Actor self, Actor producer, Actor produced)
|
||||
@@ -130,9 +136,11 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (!info.ValidStances.HasStance(stance))
|
||||
return;
|
||||
|
||||
var cm = produced.TraitOrDefault<ConditionManager>();
|
||||
if (cm != null && cm.AcceptsExternalCondition(produced, info.Condition))
|
||||
tokens[produced] = cm.GrantCondition(produced, info.Condition, true);
|
||||
var external = produced.TraitsImplementing<ExternalCondition>()
|
||||
.FirstOrDefault(t => t.Info.Condition == info.Condition && t.CanGrantCondition(produced, self));
|
||||
|
||||
if (external != null)
|
||||
tokens[produced] = external.GrantCondition(produced, self);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,9 +154,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return;
|
||||
|
||||
tokens.Remove(a);
|
||||
var cm = a.TraitOrDefault<ConditionManager>();
|
||||
if (cm != null)
|
||||
cm.RevokeCondition(a, token);
|
||||
foreach (var external in a.TraitsImplementing<ExternalCondition>())
|
||||
external.TryRevokeCondition(a, self, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user