Renamed Upgrades directory to Conditions.

This commit is contained in:
Paul Chote
2016-12-23 23:29:40 +00:00
parent 268ed016ab
commit 2322d40395
12 changed files with 11 additions and 11 deletions

View 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();
}
}
}

View 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) { }
}
}

View 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; } }
}
}

View 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 { }
}

View 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);
}
}
}

View File

@@ -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);
}
}
}
}

View 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; }
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View 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 { }
}