Renamed Upgrades directory to Conditions.
This commit is contained in:
268
OpenRA.Mods.Common/Traits/Conditions/ConditionManager.cs
Normal file
268
OpenRA.Mods.Common/Traits/Conditions/ConditionManager.cs
Normal file
@@ -0,0 +1,268 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 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.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[RequireExplicitImplementation]
|
||||
public interface IConditionTimerWatcher
|
||||
{
|
||||
string Condition { get; }
|
||||
void Update(int duration, int remaining);
|
||||
}
|
||||
|
||||
[Desc("Attach this to a unit to enable dynamic conditions by warheads, experience, crates, support powers, etc.")]
|
||||
public class ConditionManagerInfo : TraitInfo<ConditionManager>, Requires<IConditionConsumerInfo> { }
|
||||
|
||||
public class ConditionManager : INotifyCreated, ITick
|
||||
{
|
||||
/// <summary>Value used to represent an invalid token.</summary>
|
||||
public static readonly int InvalidConditionToken = -1;
|
||||
|
||||
class ConditionTimer
|
||||
{
|
||||
public readonly int Token;
|
||||
public readonly int Duration;
|
||||
public int Remaining;
|
||||
|
||||
public ConditionTimer(int token, int duration)
|
||||
{
|
||||
Token = token;
|
||||
Duration = Remaining = duration;
|
||||
}
|
||||
}
|
||||
|
||||
class ConditionState
|
||||
{
|
||||
/// <summary>Traits that have registered to be notified when this condition changes.</summary>
|
||||
public readonly List<IConditionConsumer> Consumers = new List<IConditionConsumer>();
|
||||
|
||||
/// <summary>Unique integers identifying granted instances of the condition.</summary>
|
||||
public readonly HashSet<int> Tokens = new HashSet<int>();
|
||||
|
||||
/// <summary>External callbacks that are to be executed when a timed condition changes.</summary>
|
||||
public readonly List<IConditionTimerWatcher> Watchers = new List<IConditionTimerWatcher>();
|
||||
}
|
||||
|
||||
Dictionary<string, ConditionState> state;
|
||||
readonly Dictionary<string, List<ConditionTimer>> timers = new Dictionary<string, List<ConditionTimer>>();
|
||||
|
||||
/// <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[]>();
|
||||
|
||||
/// <summary>Tokens granted by the stacked condition bonuses defined in stackedConditions.</summary>
|
||||
readonly Dictionary<string, Stack<int>> stackedTokens = new Dictionary<string, Stack<int>>();
|
||||
|
||||
int nextToken = 1;
|
||||
|
||||
/// <summary>Cache of condition -> enabled state for quick evaluation of boolean conditions.</summary>
|
||||
readonly Dictionary<string, bool> conditionCache = new Dictionary<string, bool>();
|
||||
|
||||
/// <summary>Read-only version of conditionCache that is passed to IConditionConsumers.</summary>
|
||||
IReadOnlyDictionary<string, bool> readOnlyConditionCache;
|
||||
|
||||
void INotifyCreated.Created(Actor self)
|
||||
{
|
||||
state = new Dictionary<string, ConditionState>();
|
||||
readOnlyConditionCache = new ReadOnlyDictionary<string, bool>(conditionCache);
|
||||
|
||||
var allConsumers = new HashSet<IConditionConsumer>();
|
||||
var allWatchers = self.TraitsImplementing<IConditionTimerWatcher>().ToList();
|
||||
|
||||
foreach (var consumer in self.TraitsImplementing<IConditionConsumer>())
|
||||
{
|
||||
allConsumers.Add(consumer);
|
||||
foreach (var condition in consumer.Conditions)
|
||||
{
|
||||
var cs = state.GetOrAdd(condition);
|
||||
cs.Consumers.Add(consumer);
|
||||
foreach (var w in allWatchers)
|
||||
if (w.Condition == condition)
|
||||
cs.Watchers.Add(w);
|
||||
|
||||
conditionCache[condition] = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Enable any conditions granted during trait setup
|
||||
foreach (var kv in tokens)
|
||||
{
|
||||
ConditionState conditionState;
|
||||
if (!state.TryGetValue(kv.Value, out conditionState))
|
||||
continue;
|
||||
|
||||
conditionState.Tokens.Add(kv.Key);
|
||||
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;
|
||||
stackedTokens[sc.Condition] = new Stack<int>();
|
||||
}
|
||||
|
||||
// Update all traits with their initial condition state
|
||||
foreach (var consumer in allConsumers)
|
||||
consumer.ConditionsChanged(self, readOnlyConditionCache);
|
||||
}
|
||||
|
||||
void UpdateConditionState(Actor self, string condition, int token, bool isRevoke)
|
||||
{
|
||||
ConditionState conditionState;
|
||||
if (!state.TryGetValue(condition, out conditionState))
|
||||
return;
|
||||
|
||||
if (isRevoke)
|
||||
conditionState.Tokens.Remove(token);
|
||||
else
|
||||
conditionState.Tokens.Add(token);
|
||||
|
||||
conditionCache[condition] = conditionState.Tokens.Count > 0;
|
||||
|
||||
foreach (var t in conditionState.Consumers)
|
||||
t.ConditionsChanged(self, readOnlyConditionCache);
|
||||
|
||||
string[] sc;
|
||||
if (stackedConditions.TryGetValue(condition, out sc))
|
||||
{
|
||||
var target = (conditionState.Tokens.Count - 1).Clamp(0, sc.Length);
|
||||
var st = stackedTokens[condition];
|
||||
for (var i = st.Count; i < target; i++)
|
||||
st.Push(GrantCondition(self, sc[i]));
|
||||
|
||||
for (var i = st.Count; i > target; i--)
|
||||
RevokeCondition(self, st.Pop());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Grants a specified condition.</summary>
|
||||
/// <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)
|
||||
{
|
||||
if (external && !externalConditions.Contains(condition))
|
||||
return InvalidConditionToken;
|
||||
|
||||
var token = nextToken++;
|
||||
tokens.Add(token, condition);
|
||||
|
||||
if (duration > 0)
|
||||
timers.GetOrAdd(condition).Add(new ConditionTimer(token, duration));
|
||||
|
||||
// Conditions may be granted before the state is initialized.
|
||||
// These conditions will be processed in INotifyCreated.Created.
|
||||
if (state != null)
|
||||
UpdateConditionState(self, condition, token, false);
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
/// <summary>Revokes a previously granted condition.</summary>
|
||||
/// <returns>The invalid token ID.</returns>
|
||||
/// <param name="token">The token ID returned by GrantCondition.</param>
|
||||
public int RevokeCondition(Actor self, int token)
|
||||
{
|
||||
string condition;
|
||||
if (!tokens.TryGetValue(token, out condition))
|
||||
throw new InvalidOperationException("Attempting to revoke condition with invalid token {0} for {1}.".F(token, self));
|
||||
|
||||
tokens.Remove(token);
|
||||
|
||||
// Clean up timers
|
||||
List<ConditionTimer> ct;
|
||||
if (timers.TryGetValue(condition, out ct))
|
||||
{
|
||||
ct.RemoveAll(t => t.Token == token);
|
||||
if (!ct.Any())
|
||||
timers.Remove(condition);
|
||||
}
|
||||
|
||||
// Conditions may be granted and revoked before the state is initialized.
|
||||
if (state != null)
|
||||
UpdateConditionState(self, condition, token, true);
|
||||
|
||||
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)
|
||||
{
|
||||
if (state == null)
|
||||
throw new InvalidOperationException("AcceptsExternalCondition cannot be queried before the actor has been fully created.");
|
||||
|
||||
if (!externalConditions.Contains(condition))
|
||||
return false;
|
||||
|
||||
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)
|
||||
{
|
||||
return tokens.ContainsKey(token);
|
||||
}
|
||||
|
||||
readonly HashSet<int> timersToRemove = new HashSet<int>();
|
||||
void ITick.Tick(Actor self)
|
||||
{
|
||||
// Watchers will be receiving notifications while the condition is enabled.
|
||||
// They will also be provided with the number of ticks before the condition is disabled,
|
||||
// as well as the duration of the longest active instance.
|
||||
foreach (var kv in timers)
|
||||
{
|
||||
var duration = 0;
|
||||
var remaining = 0;
|
||||
foreach (var t in kv.Value)
|
||||
{
|
||||
if (--t.Remaining <= 0)
|
||||
timersToRemove.Add(t.Token);
|
||||
|
||||
// Track the duration and remaining time for the longest remaining timer
|
||||
if (t.Remaining > remaining)
|
||||
{
|
||||
duration = t.Duration;
|
||||
remaining = t.Remaining;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var w in state[kv.Key].Watchers)
|
||||
w.Update(duration, remaining);
|
||||
}
|
||||
|
||||
foreach (var t in timersToRemove)
|
||||
RevokeCondition(self, t);
|
||||
|
||||
timersToRemove.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
101
OpenRA.Mods.Common/Traits/Conditions/ConditionalTrait.cs
Normal file
101
OpenRA.Mods.Common/Traits/Conditions/ConditionalTrait.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 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 System.Linq;
|
||||
using OpenRA.Support;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
/// <summary>Use as base class for *Info to subclass of UpgradableTrait. (See UpgradableTrait.)</summary>
|
||||
public abstract class ConditionalTraitInfo : IConditionConsumerInfo, IRulesetLoaded
|
||||
{
|
||||
static readonly IReadOnlyDictionary<string, bool> NoConditions = new ReadOnlyDictionary<string, bool>(new Dictionary<string, bool>());
|
||||
|
||||
[ConsumedConditionReference]
|
||||
[Desc("Boolean expression defining the condition to enable this trait.")]
|
||||
public readonly BooleanExpression RequiresCondition = null;
|
||||
|
||||
public abstract object Create(ActorInitializer init);
|
||||
|
||||
// HACK: A shim for all the ActorPreview code that used to query UpgradeMinEnabledLevel directly
|
||||
// This can go away after we introduce an InitialConditions ActorInit and have the traits query the
|
||||
// condition directly
|
||||
public bool EnabledByDefault { get; private set; }
|
||||
|
||||
public virtual void RulesetLoaded(Ruleset rules, ActorInfo ai)
|
||||
{
|
||||
EnabledByDefault = RequiresCondition != null ? RequiresCondition.Evaluate(NoConditions) : true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Abstract base for enabling and disabling trait using conditions.
|
||||
/// Requires basing *Info on UpgradableTraitInfo and using base(info) constructor.
|
||||
/// TraitEnabled will be called at creation if the trait starts enabled or does not use conditions.
|
||||
/// </summary>
|
||||
public abstract class ConditionalTrait<InfoType> : IConditionConsumer, IDisabledTrait, INotifyCreated, ISync where InfoType : ConditionalTraitInfo
|
||||
{
|
||||
public readonly InfoType Info;
|
||||
|
||||
IEnumerable<string> IConditionConsumer.Conditions
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Info.RequiresCondition != null)
|
||||
return Info.RequiresCondition.Variables;
|
||||
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
||||
[Sync] public bool IsTraitDisabled { get; private set; }
|
||||
|
||||
public ConditionalTrait(InfoType info)
|
||||
{
|
||||
Info = info;
|
||||
|
||||
// Conditional traits will be enabled (if appropriate) by the ConditionManager
|
||||
// calling IConditionConsumer.ConditionsChanged at the end of INotifyCreated.
|
||||
IsTraitDisabled = Info.RequiresCondition != null;
|
||||
}
|
||||
|
||||
protected virtual void Created(Actor self)
|
||||
{
|
||||
if (Info.RequiresCondition == null)
|
||||
TraitEnabled(self);
|
||||
}
|
||||
|
||||
void INotifyCreated.Created(Actor self) { Created(self); }
|
||||
|
||||
void IConditionConsumer.ConditionsChanged(Actor self, IReadOnlyDictionary<string, bool> conditions)
|
||||
{
|
||||
if (Info.RequiresCondition == null)
|
||||
return;
|
||||
|
||||
var wasDisabled = IsTraitDisabled;
|
||||
IsTraitDisabled = !Info.RequiresCondition.Evaluate(conditions);
|
||||
|
||||
if (IsTraitDisabled != wasDisabled)
|
||||
{
|
||||
if (wasDisabled)
|
||||
TraitEnabled(self);
|
||||
else
|
||||
TraitDisabled(self);
|
||||
}
|
||||
}
|
||||
|
||||
// Subclasses can add condition support by querying IsTraitDisabled and/or overriding these methods.
|
||||
protected virtual void TraitEnabled(Actor self) { }
|
||||
protected virtual void TraitDisabled(Actor self) { }
|
||||
}
|
||||
}
|
||||
29
OpenRA.Mods.Common/Traits/Conditions/DisableOnCondition.cs
Normal file
29
OpenRA.Mods.Common/Traits/Conditions/DisableOnCondition.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 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("Disable the actor when this trait is enabled by a condition.")]
|
||||
public class DisableOnConditionInfo : ConditionalTraitInfo
|
||||
{
|
||||
public override object Create(ActorInitializer init) { return new DisableOnCondition(this); }
|
||||
}
|
||||
|
||||
public class DisableOnCondition : ConditionalTrait<DisableOnConditionInfo>, IDisable
|
||||
{
|
||||
public DisableOnCondition(DisableOnConditionInfo info)
|
||||
: base(info) { }
|
||||
|
||||
public bool Disabled { get { return !IsTraitDisabled; } }
|
||||
}
|
||||
}
|
||||
25
OpenRA.Mods.Common/Traits/Conditions/ExternalConditions.cs
Normal file
25
OpenRA.Mods.Common/Traits/Conditions/ExternalConditions.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 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 { }
|
||||
}
|
||||
56
OpenRA.Mods.Common/Traits/Conditions/GrantCondition.cs
Normal file
56
OpenRA.Mods.Common/Traits/Conditions/GrantCondition.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 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("Grants a condition while the trait is active.")]
|
||||
class GrantConditionInfo : ConditionalTraitInfo
|
||||
{
|
||||
[FieldLoader.Require]
|
||||
[GrantedConditionReference]
|
||||
[Desc("Condition to grant.")]
|
||||
public readonly string Condition = null;
|
||||
|
||||
public override object Create(ActorInitializer init) { return new GrantCondition(this); }
|
||||
}
|
||||
|
||||
class GrantCondition : ConditionalTrait<GrantConditionInfo>
|
||||
{
|
||||
ConditionManager conditionManager;
|
||||
int conditionToken = ConditionManager.InvalidConditionToken;
|
||||
|
||||
public GrantCondition(GrantConditionInfo info)
|
||||
: base(info) { }
|
||||
|
||||
protected override void Created(Actor self)
|
||||
{
|
||||
conditionManager = self.Trait<ConditionManager>();
|
||||
|
||||
base.Created(self);
|
||||
}
|
||||
|
||||
protected override void TraitEnabled(Actor self)
|
||||
{
|
||||
if (conditionToken == ConditionManager.InvalidConditionToken)
|
||||
conditionToken = conditionManager.GrantCondition(self, Info.Condition);
|
||||
}
|
||||
|
||||
protected override void TraitDisabled(Actor self)
|
||||
{
|
||||
if (conditionToken == ConditionManager.InvalidConditionToken)
|
||||
return;
|
||||
|
||||
conditionToken = conditionManager.RevokeCondition(self, conditionToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 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("Applies a condition to the actor at specified damage states.")]
|
||||
public class GrantConditionOnDamageStateInfo : ITraitInfo, Requires<HealthInfo>
|
||||
{
|
||||
[FieldLoader.Require]
|
||||
[GrantedConditionReference]
|
||||
[Desc("Condition to grant.")]
|
||||
public readonly string Condition = null;
|
||||
|
||||
[Desc("Play a random sound from this list when enabled.")]
|
||||
public readonly string[] EnabledSounds = { };
|
||||
|
||||
[Desc("Play a random sound from this list when disabled.")]
|
||||
public readonly string[] DisabledSounds = { };
|
||||
|
||||
[Desc("Levels of damage at which to grant the condition.")]
|
||||
public readonly DamageState ValidDamageStates = DamageState.Heavy | DamageState.Critical;
|
||||
|
||||
[Desc("Is the condition irrevocable once it has been activated?")]
|
||||
public readonly bool GrantPermanently = false;
|
||||
|
||||
public object Create(ActorInitializer init) { return new GrantConditionOnDamageState(init.Self, this); }
|
||||
}
|
||||
|
||||
public class GrantConditionOnDamageState : INotifyDamageStateChanged, INotifyCreated
|
||||
{
|
||||
readonly GrantConditionOnDamageStateInfo info;
|
||||
readonly Health health;
|
||||
|
||||
ConditionManager conditionManager;
|
||||
int conditionToken = ConditionManager.InvalidConditionToken;
|
||||
|
||||
public GrantConditionOnDamageState(Actor self, GrantConditionOnDamageStateInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
health = self.Trait<Health>();
|
||||
}
|
||||
|
||||
void INotifyCreated.Created(Actor self)
|
||||
{
|
||||
conditionManager = self.TraitOrDefault<ConditionManager>();
|
||||
GrantConditionOnValidDamageState(self, health.DamageState);
|
||||
}
|
||||
|
||||
void GrantConditionOnValidDamageState(Actor self, DamageState state)
|
||||
{
|
||||
if (!info.ValidDamageStates.HasFlag(state) || conditionToken != ConditionManager.InvalidConditionToken)
|
||||
return;
|
||||
|
||||
conditionToken = conditionManager.GrantCondition(self, info.Condition);
|
||||
|
||||
var sound = info.EnabledSounds.RandomOrDefault(Game.CosmeticRandom);
|
||||
Game.Sound.Play(SoundType.World, sound, self.CenterPosition);
|
||||
}
|
||||
|
||||
void INotifyDamageStateChanged.DamageStateChanged(Actor self, AttackInfo e)
|
||||
{
|
||||
var granted = conditionToken != ConditionManager.InvalidConditionToken;
|
||||
if ((granted && info.GrantPermanently) || conditionManager == null)
|
||||
return;
|
||||
|
||||
if (!granted && !info.ValidDamageStates.HasFlag(e.PreviousDamageState))
|
||||
GrantConditionOnValidDamageState(self, health.DamageState);
|
||||
else if (granted && !info.ValidDamageStates.HasFlag(e.DamageState) && info.ValidDamageStates.HasFlag(e.PreviousDamageState))
|
||||
{
|
||||
conditionToken = conditionManager.RevokeCondition(self, conditionToken);
|
||||
|
||||
var sound = info.DisabledSounds.RandomOrDefault(Game.CosmeticRandom);
|
||||
Game.Sound.Play(SoundType.World, sound, self.CenterPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
284
OpenRA.Mods.Common/Traits/Conditions/GrantConditionOnDeploy.cs
Normal file
284
OpenRA.Mods.Common/Traits/Conditions/GrantConditionOnDeploy.cs
Normal file
@@ -0,0 +1,284 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 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 OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Mods.Common.Orders;
|
||||
using OpenRA.Mods.Common.Traits.Render;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
public class GrantConditionOnDeployInfo : ITraitInfo
|
||||
{
|
||||
[GrantedConditionReference]
|
||||
[Desc("The condition to grant while the actor is undeployed.")]
|
||||
public readonly string UndeployedCondition = null;
|
||||
|
||||
[FieldLoader.Require]
|
||||
[GrantedConditionReference]
|
||||
[Desc("The condition to grant after deploying and revoke before undeploying.")]
|
||||
public readonly string DeployedCondition = null;
|
||||
|
||||
[Desc("The terrain types that this actor can deploy on. Leave empty to allow any.")]
|
||||
public readonly HashSet<string> AllowedTerrainTypes = new HashSet<string>();
|
||||
|
||||
[Desc("Can this actor deploy on slopes?")]
|
||||
public readonly bool CanDeployOnRamps = false;
|
||||
|
||||
[Desc("Cursor to display when able to (un)deploy the actor.")]
|
||||
public readonly string DeployCursor = "deploy";
|
||||
|
||||
[Desc("Cursor to display when unable to (un)deploy the actor.")]
|
||||
public readonly string DeployBlockedCursor = "deploy-blocked";
|
||||
|
||||
[SequenceReference, Desc("Animation to play for deploying/undeploying.")]
|
||||
public readonly string DeployAnimation = null;
|
||||
|
||||
[Desc("Facing that the actor must face before deploying. Set to -1 to deploy regardless of facing.")]
|
||||
public readonly int Facing = -1;
|
||||
|
||||
[Desc("Sound to play when deploying.")]
|
||||
public readonly string DeploySound = null;
|
||||
|
||||
[Desc("Sound to play when undeploying.")]
|
||||
public readonly string UndeploySound = null;
|
||||
|
||||
[Desc("Can this actor undeploy?")]
|
||||
public readonly bool CanUndeploy = true;
|
||||
|
||||
public object Create(ActorInitializer init) { return new GrantConditionOnDeploy(init, this); }
|
||||
}
|
||||
|
||||
public enum DeployState { Undeployed, Deploying, Deployed, Undeploying }
|
||||
|
||||
public class GrantConditionOnDeploy : IResolveOrder, IIssueOrder, INotifyCreated
|
||||
{
|
||||
readonly Actor self;
|
||||
readonly GrantConditionOnDeployInfo info;
|
||||
readonly bool checkTerrainType;
|
||||
readonly bool canTurn;
|
||||
readonly Lazy<WithSpriteBody> body;
|
||||
|
||||
DeployState deployState;
|
||||
ConditionManager conditionManager;
|
||||
int deployedToken = ConditionManager.InvalidConditionToken;
|
||||
int undeployedToken = ConditionManager.InvalidConditionToken;
|
||||
|
||||
public GrantConditionOnDeploy(ActorInitializer init, GrantConditionOnDeployInfo info)
|
||||
{
|
||||
self = init.Self;
|
||||
this.info = info;
|
||||
checkTerrainType = info.AllowedTerrainTypes.Count > 0;
|
||||
canTurn = self.Info.HasTraitInfo<IFacingInfo>();
|
||||
body = Exts.Lazy(self.TraitOrDefault<WithSpriteBody>);
|
||||
if (init.Contains<DeployStateInit>())
|
||||
deployState = init.Get<DeployStateInit, DeployState>();
|
||||
}
|
||||
|
||||
public void Created(Actor self)
|
||||
{
|
||||
conditionManager = self.TraitOrDefault<ConditionManager>();
|
||||
|
||||
switch (deployState)
|
||||
{
|
||||
case DeployState.Undeployed:
|
||||
OnUndeployCompleted();
|
||||
break;
|
||||
case DeployState.Deploying:
|
||||
if (canTurn)
|
||||
self.Trait<IFacing>().Facing = info.Facing;
|
||||
|
||||
Deploy(true);
|
||||
break;
|
||||
case DeployState.Deployed:
|
||||
if (canTurn)
|
||||
self.Trait<IFacing>().Facing = info.Facing;
|
||||
|
||||
OnDeployCompleted();
|
||||
break;
|
||||
case DeployState.Undeploying:
|
||||
if (canTurn)
|
||||
self.Trait<IFacing>().Facing = info.Facing;
|
||||
|
||||
Undeploy(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IOrderTargeter> Orders
|
||||
{
|
||||
get { yield return new DeployOrderTargeter("GrantConditionOnDeploy", 5,
|
||||
() => IsCursorBlocked() ? info.DeployBlockedCursor : info.DeployCursor); }
|
||||
}
|
||||
|
||||
public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)
|
||||
{
|
||||
if (order.OrderID == "GrantConditionOnDeploy")
|
||||
return new Order(order.OrderID, self, queued);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
if (order.OrderString != "GrantConditionOnDeploy" || deployState == DeployState.Deploying || deployState == DeployState.Undeploying)
|
||||
return;
|
||||
|
||||
if (!order.Queued)
|
||||
self.CancelActivity();
|
||||
|
||||
if (deployState == DeployState.Deployed && info.CanUndeploy)
|
||||
{
|
||||
self.QueueActivity(new CallFunc(Undeploy));
|
||||
}
|
||||
else if (deployState == DeployState.Undeployed)
|
||||
{
|
||||
// Turn to the required facing.
|
||||
if (info.Facing != -1 && canTurn)
|
||||
self.QueueActivity(new Turn(self, info.Facing));
|
||||
|
||||
self.QueueActivity(new CallFunc(Deploy));
|
||||
}
|
||||
}
|
||||
|
||||
bool IsCursorBlocked()
|
||||
{
|
||||
return ((deployState == DeployState.Deployed) && !info.CanUndeploy) || (!IsOnValidTerrain() && (deployState != DeployState.Deployed));
|
||||
}
|
||||
|
||||
bool IsOnValidTerrain()
|
||||
{
|
||||
return IsOnValidTerrainType() && IsOnValidRampType();
|
||||
}
|
||||
|
||||
bool IsOnValidTerrainType()
|
||||
{
|
||||
if (!self.World.Map.Contains(self.Location))
|
||||
return false;
|
||||
|
||||
if (!checkTerrainType)
|
||||
return true;
|
||||
|
||||
var terrainType = self.World.Map.GetTerrainInfo(self.Location).Type;
|
||||
|
||||
return info.AllowedTerrainTypes.Contains(terrainType);
|
||||
}
|
||||
|
||||
bool IsOnValidRampType()
|
||||
{
|
||||
if (info.CanDeployOnRamps)
|
||||
return true;
|
||||
|
||||
var ramp = 0;
|
||||
if (self.World.Map.Contains(self.Location))
|
||||
{
|
||||
var tile = self.World.Map.Tiles[self.Location];
|
||||
var ti = self.World.Map.Rules.TileSet.GetTileInfo(tile);
|
||||
if (ti != null)
|
||||
ramp = ti.RampType;
|
||||
}
|
||||
|
||||
return ramp == 0;
|
||||
}
|
||||
|
||||
/// <summary>Play deploy sound and animation.</summary>
|
||||
void Deploy() { Deploy(false); }
|
||||
void Deploy(bool init)
|
||||
{
|
||||
// Something went wrong, most likely due to deploy order spam and the fact that this is a delayed action.
|
||||
if (!init && deployState != DeployState.Undeployed)
|
||||
return;
|
||||
|
||||
if (!IsOnValidTerrain())
|
||||
return;
|
||||
|
||||
if (!string.IsNullOrEmpty(info.DeploySound))
|
||||
Game.Sound.Play(SoundType.World, info.DeploySound, self.CenterPosition);
|
||||
|
||||
// Revoke condition that is applied while undeployed.
|
||||
if (!init)
|
||||
OnDeployStarted();
|
||||
|
||||
// If there is no animation to play just grant the condition that is used while deployed.
|
||||
// Alternatively, play the deploy animation and then grant the condition.
|
||||
if (string.IsNullOrEmpty(info.DeployAnimation) || body.Value == null)
|
||||
OnDeployCompleted();
|
||||
else
|
||||
body.Value.PlayCustomAnimation(self, info.DeployAnimation, OnDeployCompleted);
|
||||
}
|
||||
|
||||
/// <summary>Play undeploy sound and animation and after that revoke the condition.</summary>
|
||||
void Undeploy() { Undeploy(false); }
|
||||
void Undeploy(bool init)
|
||||
{
|
||||
// Something went wrong, most likely due to deploy order spam and the fact that this is a delayed action.
|
||||
if (!init && deployState != DeployState.Deployed)
|
||||
return;
|
||||
|
||||
if (!string.IsNullOrEmpty(info.UndeploySound))
|
||||
Game.Sound.Play(SoundType.World, info.UndeploySound, self.CenterPosition);
|
||||
|
||||
if (!init)
|
||||
OnUndeployStarted();
|
||||
|
||||
// If there is no animation to play just grant the condition that is used while undeployed.
|
||||
// Alternatively, play the undeploy animation and then grant the condition.
|
||||
if (string.IsNullOrEmpty(info.DeployAnimation) || body.Value == null)
|
||||
OnUndeployCompleted();
|
||||
else
|
||||
body.Value.PlayCustomAnimationBackwards(self, info.DeployAnimation, OnUndeployCompleted);
|
||||
}
|
||||
|
||||
void OnDeployStarted()
|
||||
{
|
||||
if (undeployedToken != ConditionManager.InvalidConditionToken)
|
||||
undeployedToken = conditionManager.RevokeCondition(self, undeployedToken);
|
||||
|
||||
deployState = DeployState.Deploying;
|
||||
}
|
||||
|
||||
void OnDeployCompleted()
|
||||
{
|
||||
if (conditionManager != null && !string.IsNullOrEmpty(info.DeployedCondition) && deployedToken == ConditionManager.InvalidConditionToken)
|
||||
deployedToken = conditionManager.GrantCondition(self, info.DeployedCondition);
|
||||
|
||||
deployState = DeployState.Deployed;
|
||||
}
|
||||
|
||||
void OnUndeployStarted()
|
||||
{
|
||||
if (deployedToken != ConditionManager.InvalidConditionToken)
|
||||
deployedToken = conditionManager.RevokeCondition(self, deployedToken);
|
||||
|
||||
deployState = DeployState.Deploying;
|
||||
}
|
||||
|
||||
void OnUndeployCompleted()
|
||||
{
|
||||
if (conditionManager != null && !string.IsNullOrEmpty(info.UndeployedCondition) && undeployedToken == ConditionManager.InvalidConditionToken)
|
||||
undeployedToken = conditionManager.GrantCondition(self, info.UndeployedCondition);
|
||||
|
||||
deployState = DeployState.Undeployed;
|
||||
}
|
||||
}
|
||||
|
||||
public class DeployStateInit : IActorInit<DeployState>
|
||||
{
|
||||
[FieldFromYamlKey]
|
||||
readonly DeployState value = DeployState.Deployed;
|
||||
public DeployStateInit() { }
|
||||
public DeployStateInit(DeployState init) { value = init; }
|
||||
public DeployState Value(World world) { return value; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 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.Linq;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
public class GrantConditionOnMovementInfo : ConditionalTraitInfo, Requires<IMoveInfo>
|
||||
{
|
||||
[FieldLoader.Require]
|
||||
[GrantedConditionReference]
|
||||
[Desc("Condition to grant.")]
|
||||
public readonly string Condition = null;
|
||||
|
||||
[Desc("Apply condition on straight vertical movement as well.")]
|
||||
public readonly bool ConsiderVerticalMovement = false;
|
||||
|
||||
public override object Create(ActorInitializer init) { return new GrantConditionOnMovement(init.Self, this); }
|
||||
}
|
||||
|
||||
public class GrantConditionOnMovement : ConditionalTrait<GrantConditionOnMovementInfo>, ITick
|
||||
{
|
||||
readonly IMove movement;
|
||||
|
||||
ConditionManager conditionManager;
|
||||
int conditionToken = ConditionManager.InvalidConditionToken;
|
||||
|
||||
public GrantConditionOnMovement(Actor self, GrantConditionOnMovementInfo info)
|
||||
: base(info)
|
||||
{
|
||||
movement = self.Trait<IMove>();
|
||||
}
|
||||
|
||||
protected override void Created(Actor self)
|
||||
{
|
||||
conditionManager = self.TraitOrDefault<ConditionManager>();
|
||||
base.Created(self);
|
||||
}
|
||||
|
||||
void ITick.Tick(Actor self)
|
||||
{
|
||||
if (conditionManager == null)
|
||||
return;
|
||||
|
||||
var isMovingVertically = Info.ConsiderVerticalMovement ? movement.IsMovingVertically : false;
|
||||
var isMoving = !IsTraitDisabled && !self.IsDead && (movement.IsMoving || isMovingVertically);
|
||||
if (isMoving && conditionToken == ConditionManager.InvalidConditionToken)
|
||||
conditionToken = conditionManager.GrantCondition(self, Info.Condition);
|
||||
else if (!isMoving && conditionToken != ConditionManager.InvalidConditionToken)
|
||||
conditionToken = conditionManager.RevokeCondition(self, conditionToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 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.Linq;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
public class GrantConditionOnTerrainInfo : ITraitInfo
|
||||
{
|
||||
[FieldLoader.Require]
|
||||
[GrantedConditionReference]
|
||||
[Desc("Condition to grant.")]
|
||||
public readonly string Condition = null;
|
||||
|
||||
[FieldLoader.Require]
|
||||
[Desc("Terrain names to trigger the condition.")]
|
||||
public readonly string[] TerrainTypes = { };
|
||||
|
||||
public object Create(ActorInitializer init) { return new GrantConditionOnTerrain(init, this); }
|
||||
}
|
||||
|
||||
public class GrantConditionOnTerrain : INotifyCreated, ITick
|
||||
{
|
||||
readonly GrantConditionOnTerrainInfo info;
|
||||
|
||||
ConditionManager conditionManager;
|
||||
int conditionToken = ConditionManager.InvalidConditionToken;
|
||||
string previousTerrain;
|
||||
|
||||
public GrantConditionOnTerrain(ActorInitializer init, GrantConditionOnTerrainInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
void INotifyCreated.Created(Actor self)
|
||||
{
|
||||
conditionManager = self.TraitOrDefault<ConditionManager>();
|
||||
}
|
||||
|
||||
public void Tick(Actor self)
|
||||
{
|
||||
if (conditionManager == null)
|
||||
return;
|
||||
|
||||
var currentTerrain = self.World.Map.GetTerrainInfo(self.Location).Type;
|
||||
var wantsGranted = info.TerrainTypes.Contains(currentTerrain);
|
||||
if (currentTerrain != previousTerrain)
|
||||
{
|
||||
if (wantsGranted && conditionToken == ConditionManager.InvalidConditionToken)
|
||||
conditionToken = conditionManager.GrantCondition(self, info.Condition);
|
||||
else if (!wantsGranted && conditionToken != ConditionManager.InvalidConditionToken)
|
||||
conditionToken = conditionManager.RevokeCondition(self, conditionToken);
|
||||
}
|
||||
|
||||
previousTerrain = currentTerrain;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 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
|
||||
{
|
||||
[Desc("Applies a condition to actors within a specified range.")]
|
||||
public class ProximityExternalConditionInfo : ITraitInfo
|
||||
{
|
||||
[FieldLoader.Require]
|
||||
[Desc("The condition to apply. Must be included in the target actor's ExternalConditions list.")]
|
||||
public readonly string Condition = null;
|
||||
|
||||
[Desc("The range to search for actors.")]
|
||||
public readonly WDist Range = WDist.FromCells(3);
|
||||
|
||||
[Desc("The maximum vertical range above terrain to search for actors.",
|
||||
"Ignored if 0 (actors are selected regardless of vertical distance).")]
|
||||
public readonly WDist MaximumVerticalOffset = WDist.Zero;
|
||||
|
||||
[Desc("What diplomatic stances are affected.")]
|
||||
public readonly Stance ValidStances = Stance.Ally;
|
||||
|
||||
[Desc("Condition is applied permanently to this actor.")]
|
||||
public readonly bool AffectsParent = false;
|
||||
|
||||
public readonly string EnableSound = null;
|
||||
public readonly string DisableSound = null;
|
||||
|
||||
public object Create(ActorInitializer init) { return new ProximityExternalCondition(init.Self, this); }
|
||||
}
|
||||
|
||||
public class ProximityExternalCondition : ITick, INotifyAddedToWorld, INotifyRemovedFromWorld, INotifyOtherProduction
|
||||
{
|
||||
readonly ProximityExternalConditionInfo info;
|
||||
readonly Actor self;
|
||||
|
||||
readonly Dictionary<Actor, int> tokens = new Dictionary<Actor, int>();
|
||||
|
||||
int proximityTrigger;
|
||||
WPos cachedPosition;
|
||||
WDist cachedRange;
|
||||
WDist desiredRange;
|
||||
WDist cachedVRange;
|
||||
WDist desiredVRange;
|
||||
|
||||
bool cachedDisabled = true;
|
||||
|
||||
public ProximityExternalCondition(Actor self, ProximityExternalConditionInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
this.self = self;
|
||||
cachedRange = info.Range;
|
||||
cachedVRange = info.MaximumVerticalOffset;
|
||||
}
|
||||
|
||||
public void AddedToWorld(Actor self)
|
||||
{
|
||||
cachedPosition = self.CenterPosition;
|
||||
proximityTrigger = self.World.ActorMap.AddProximityTrigger(cachedPosition, cachedRange, cachedVRange, ActorEntered, ActorExited);
|
||||
}
|
||||
|
||||
public void RemovedFromWorld(Actor self)
|
||||
{
|
||||
self.World.ActorMap.RemoveProximityTrigger(proximityTrigger);
|
||||
}
|
||||
|
||||
public void Tick(Actor self)
|
||||
{
|
||||
var disabled = self.IsDisabled();
|
||||
|
||||
if (cachedDisabled != disabled)
|
||||
{
|
||||
Game.Sound.Play(SoundType.World, disabled ? info.DisableSound : info.EnableSound, self.CenterPosition);
|
||||
desiredRange = disabled ? WDist.Zero : info.Range;
|
||||
desiredVRange = disabled ? WDist.Zero : info.MaximumVerticalOffset;
|
||||
cachedDisabled = disabled;
|
||||
}
|
||||
|
||||
if (self.CenterPosition != cachedPosition || desiredRange != cachedRange || desiredVRange != cachedVRange)
|
||||
{
|
||||
cachedPosition = self.CenterPosition;
|
||||
cachedRange = desiredRange;
|
||||
cachedVRange = desiredVRange;
|
||||
self.World.ActorMap.UpdateProximityTrigger(proximityTrigger, cachedPosition, cachedRange, cachedVRange);
|
||||
}
|
||||
}
|
||||
|
||||
void ActorEntered(Actor a)
|
||||
{
|
||||
if (a.Disposed || self.Disposed)
|
||||
return;
|
||||
|
||||
if (a == self && !info.AffectsParent)
|
||||
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);
|
||||
}
|
||||
|
||||
public void UnitProducedByOther(Actor self, Actor producer, Actor produced)
|
||||
{
|
||||
// If the produced Actor doesn't occupy space, it can't be in range
|
||||
if (produced.OccupiesSpace == null)
|
||||
return;
|
||||
|
||||
// We don't grant conditions when disabled
|
||||
if (self.IsDisabled())
|
||||
return;
|
||||
|
||||
// Work around for actors produced within the region not triggering until the second tick
|
||||
if ((produced.CenterPosition - self.CenterPosition).HorizontalLengthSquared <= info.Range.LengthSquared)
|
||||
{
|
||||
var stance = self.Owner.Stances[produced.Owner];
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void ActorExited(Actor a)
|
||||
{
|
||||
if (a.Disposed)
|
||||
return;
|
||||
|
||||
int token;
|
||||
if (!tokens.TryGetValue(a, out token))
|
||||
return;
|
||||
|
||||
tokens.Remove(a);
|
||||
var cm = a.TraitOrDefault<ConditionManager>();
|
||||
if (cm != null)
|
||||
cm.RevokeCondition(a, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
32
OpenRA.Mods.Common/Traits/Conditions/StackedCondition.cs
Normal file
32
OpenRA.Mods.Common/Traits/Conditions/StackedCondition.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 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("Grant additional conditions when a specified condition has been granted multiple times.")]
|
||||
public class StackedConditionInfo : TraitInfo<StackedCondition>
|
||||
{
|
||||
[FieldLoader.Require]
|
||||
[ConsumedConditionReference]
|
||||
[Desc("Condition to monitor.")]
|
||||
public readonly string Condition = null;
|
||||
|
||||
[FieldLoader.Require]
|
||||
[GrantedConditionReference]
|
||||
[Desc("Conditions to grant when the monitored condition is granted multiple times.",
|
||||
"The first entry is activated at 2x grants, second entry at 3x grants, and so on.")]
|
||||
public readonly string[] StackedConditions = { };
|
||||
}
|
||||
|
||||
public class StackedCondition { }
|
||||
}
|
||||
Reference in New Issue
Block a user