Merge pull request #12406 from pchote/external-conditions

Upgrades overhaul part 3: Timed, external, and stacked conditions.
This commit is contained in:
Paul Chote
2016-12-09 23:45:14 +00:00
committed by GitHub
24 changed files with 379 additions and 262 deletions

View File

@@ -307,7 +307,7 @@
<Compile Include="Traits\Crates\GiveCashCrateAction.cs" />
<Compile Include="Traits\Crates\GiveMcvCrateAction.cs" />
<Compile Include="Traits\Crates\GiveUnitCrateAction.cs" />
<Compile Include="Traits\Crates\GrantUpgradeCrateAction.cs" />
<Compile Include="Traits\Crates\GrantExternalConditionCrateAction.cs" />
<Compile Include="Traits\Crates\HealUnitsCrateAction.cs" />
<Compile Include="Traits\Crates\HideMapCrateAction.cs" />
<Compile Include="Traits\Crates\LevelUpCrateAction.cs" />
@@ -417,7 +417,7 @@
<Compile Include="Traits\Render\RenderVoxels.cs" />
<Compile Include="Traits\Render\ProductionBar.cs" />
<Compile Include="Traits\Render\SupportPowerChargeBar.cs" />
<Compile Include="Traits\Render\TimedUpgradeBar.cs" />
<Compile Include="Traits\Render\TimedConditionBar.cs" />
<Compile Include="Traits\Render\WithSpriteBarrel.cs" />
<Compile Include="Traits\Render\WithBuildingExplosion.cs" />
<Compile Include="Traits\Render\WithAttackAnimation.cs" />
@@ -480,7 +480,7 @@
<Compile Include="Traits\Sound\AttackSounds.cs" />
<Compile Include="Traits\SupplyTruck.cs" />
<Compile Include="Traits\SupportPowers\AirstrikePower.cs" />
<Compile Include="Traits\SupportPowers\GrantUpgradePower.cs" />
<Compile Include="Traits\SupportPowers\GrantExternalConditionPower.cs" />
<Compile Include="Traits\SupportPowers\NukePower.cs" />
<Compile Include="Traits\SupportPowers\SupportPower.cs" />
<Compile Include="Traits\SupportPowers\SupportPowerManager.cs" />
@@ -564,7 +564,7 @@
<Compile Include="Warheads\CreateResourceWarhead.cs" />
<Compile Include="Warheads\DamageWarhead.cs" />
<Compile Include="Warheads\DestroyResourceWarhead.cs" />
<Compile Include="Warheads\GrantUpgradeWarhead.cs" />
<Compile Include="Warheads\GrantExternalConditionWarhead.cs" />
<Compile Include="Warheads\HealthPercentageDamageWarhead.cs" />
<Compile Include="Warheads\LeaveSmudgeWarhead.cs" />
<Compile Include="Warheads\SpreadDamageWarhead.cs" />
@@ -775,6 +775,8 @@
<Compile Include="Traits\AutoCarryall.cs" />
<Compile Include="Traits\World\CliffBackImpassabilityLayer.cs" />
<Compile Include="Traits\Upgrades\GrantCondition.cs" />
<Compile Include="Traits\Upgrades\ExternalConditions.cs" />
<Compile Include="Traits\Upgrades\StackedCondition.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="AfterBuild">

View File

@@ -52,6 +52,8 @@ namespace OpenRA.Mods.Common.Traits
protected override void Created(Actor self)
{
upgradeManager = self.Trait<UpgradeManager>();
base.Created(self);
}
public virtual void Attached(Actor self)

View File

@@ -15,11 +15,11 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[Desc("Grants an upgrade to the collector.")]
public class GrantUpgradeCrateActionInfo : CrateActionInfo
public class GrantExternalConditionCrateActionInfo : CrateActionInfo
{
[UpgradeGrantedReference, FieldLoader.Require]
[Desc("The upgrades to apply.")]
public readonly string[] Upgrades = { };
[FieldLoader.Require]
[Desc("The condition to apply. Must be included in the target actor's ExternalConditions list.")]
public readonly string Condition = null;
[Desc("Duration of the upgrade (in ticks). Set to 0 for a permanent upgrade.")]
public readonly int Duration = 0;
@@ -30,37 +30,36 @@ namespace OpenRA.Mods.Common.Traits
[Desc("The maximum number of extra collectors to grant the crate action to.", "-1 = no limit")]
public readonly int MaxExtraCollectors = 4;
public override object Create(ActorInitializer init) { return new GrantUpgradeCrateAction(init.Self, this); }
public override object Create(ActorInitializer init) { return new GrantExternalConditionCrateAction(init.Self, this); }
}
public class GrantUpgradeCrateAction : CrateAction
public class GrantExternalConditionCrateAction : CrateAction
{
readonly Actor self;
readonly GrantUpgradeCrateActionInfo info;
readonly GrantExternalConditionCrateActionInfo info;
public GrantUpgradeCrateAction(Actor self, GrantUpgradeCrateActionInfo info)
public GrantExternalConditionCrateAction(Actor self, GrantExternalConditionCrateActionInfo info)
: base(self, info)
{
this.self = self;
this.info = info;
}
bool AcceptsUpgrade(Actor a)
bool AcceptsCondition(Actor a)
{
var um = a.TraitOrDefault<UpgradeManager>();
return um != null && (info.Duration > 0 ?
info.Upgrades.Any(u => um.AcknowledgesUpgrade(a, u)) : info.Upgrades.Any(u => um.AcceptsUpgrade(a, u)));
return um != null && um.AcceptsExternalCondition(a, info.Condition);
}
public override int GetSelectionShares(Actor collector)
{
return AcceptsUpgrade(collector) ? info.SelectionShares : 0;
return AcceptsCondition(collector) ? info.SelectionShares : 0;
}
public override void Activate(Actor collector)
{
var actorsInRange = self.World.FindActorsInCircle(self.CenterPosition, info.Range)
.Where(a => a != self && a != collector && a.Owner == collector.Owner && AcceptsUpgrade(a));
.Where(a => a != self && a != collector && a.Owner == collector.Owner && AcceptsCondition(a));
if (info.MaxExtraCollectors > -1)
actorsInRange = actorsInRange.Take(info.MaxExtraCollectors);
@@ -73,19 +72,10 @@ namespace OpenRA.Mods.Common.Traits
continue;
var um = a.TraitOrDefault<UpgradeManager>();
foreach (var u in info.Upgrades)
{
if (info.Duration > 0)
{
if (um.AcknowledgesUpgrade(a, u))
um.GrantTimedUpgrade(a, u, info.Duration);
}
else
{
if (um.AcceptsUpgrade(a, u))
um.GrantUpgrade(a, u, this);
}
}
// Condition token is ignored because we never revoke this condition.
if (um != null)
um.GrantCondition(a, info.Condition, true, info.Duration);
}
});

View File

@@ -15,38 +15,35 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits.Render
{
[Desc("Visualizes the remaining time for an upgrade.")]
class TimedUpgradeBarInfo : ITraitInfo, Requires<UpgradeManagerInfo>
class TimedConditionBarInfo : ITraitInfo, Requires<UpgradeManagerInfo>
{
[FieldLoader.Require]
[Desc("Upgrade that this bar corresponds to")]
public readonly string Upgrade = null;
[Desc("Condition that this bar corresponds to")]
public readonly string Condition = null;
public readonly Color Color = Color.Red;
public object Create(ActorInitializer init) { return new TimedUpgradeBar(init.Self, this); }
public object Create(ActorInitializer init) { return new TimedConditionBar(init.Self, this); }
}
class TimedUpgradeBar : ISelectionBar, INotifyCreated
class TimedConditionBar : ISelectionBar, IConditionTimerWatcher
{
readonly TimedUpgradeBarInfo info;
readonly TimedConditionBarInfo info;
readonly Actor self;
float value;
public TimedUpgradeBar(Actor self, TimedUpgradeBarInfo info)
public TimedConditionBar(Actor self, TimedConditionBarInfo info)
{
this.self = self;
this.info = info;
}
public void Created(Actor self)
void IConditionTimerWatcher.Update(int duration, int remaining)
{
self.Trait<UpgradeManager>().RegisterWatcher(info.Upgrade, Update);
value = duration > 0 ? remaining * 1f / duration : 0;
}
public void Update(int duration, int remaining)
{
value = remaining * 1f / duration;
}
string IConditionTimerWatcher.Condition { get { return info.Condition; } }
float ISelectionBar.GetValue()
{

View File

@@ -19,31 +19,33 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
class GrantUpgradePowerInfo : SupportPowerInfo
class GrantExternalConditionPowerInfo : SupportPowerInfo
{
[UpgradeGrantedReference, FieldLoader.Require]
[Desc("The upgrades to apply.")]
public readonly string[] Upgrades = { };
[FieldLoader.Require]
[Desc("The condition to apply. Must be included in the target actor's ExternalConditions list.")]
public readonly string Condition = null;
[Desc("Duration of the upgrade (in ticks). Set to 0 for a permanent upgrade.")]
[Desc("Duration of the upgrade (in ticks). Set to 0 for a permanent condition.")]
public readonly int Duration = 0;
[Desc("Cells - affects whole cells only")]
public readonly int Range = 1;
public readonly string GrantUpgradeSound = "ironcur9.aud";
[Desc("Sound to instantly play at the targeted area.")]
public readonly string OnFireSound = null;
[SequenceReference, Desc("Sequence to play for granting actor when activated.",
"This requires the actor to have the WithSpriteBody trait or one of its derivatives.")]
public readonly string GrantUpgradeSequence = "active";
public readonly string Sequence = "active";
public override object Create(ActorInitializer init) { return new GrantUpgradePower(init.Self, this); }
public override object Create(ActorInitializer init) { return new GrantExternalConditionPower(init.Self, this); }
}
class GrantUpgradePower : SupportPower
class GrantExternalConditionPower : SupportPower
{
GrantUpgradePowerInfo info;
readonly GrantExternalConditionPowerInfo info;
public GrantUpgradePower(Actor self, GrantUpgradePowerInfo info)
public GrantExternalConditionPower(Actor self, GrantExternalConditionPowerInfo info)
: base(self, info)
{
this.info = info;
@@ -60,30 +62,18 @@ namespace OpenRA.Mods.Common.Traits
base.Activate(self, order, manager);
var wsb = self.TraitOrDefault<WithSpriteBody>();
if (wsb != null && wsb.DefaultAnimation.HasSequence(info.GrantUpgradeSequence))
wsb.PlayCustomAnimation(self, info.GrantUpgradeSequence, () => wsb.CancelCustomAnimation(self));
if (wsb != null && wsb.DefaultAnimation.HasSequence(info.Sequence))
wsb.PlayCustomAnimation(self, info.Sequence, () => wsb.CancelCustomAnimation(self));
Game.Sound.Play(info.GrantUpgradeSound, self.World.Map.CenterOfCell(order.TargetLocation));
Game.Sound.Play(info.OnFireSound, self.World.Map.CenterOfCell(order.TargetLocation));
foreach (var a in UnitsInRange(order.TargetLocation))
{
var um = a.TraitOrDefault<UpgradeManager>();
if (um == null)
continue;
foreach (var u in info.Upgrades)
{
if (info.Duration > 0)
{
if (um.AcknowledgesUpgrade(a, u))
um.GrantTimedUpgrade(a, u, info.Duration);
}
else
{
if (um.AcceptsUpgrade(a, u))
um.GrantUpgrade(a, u, this);
}
}
// Condition token is ignored because we never revoke this condition.
if (um != null)
um.GrantCondition(a, info.Condition, true, info.Duration);
}
}
@@ -101,20 +91,19 @@ namespace OpenRA.Mods.Common.Traits
return false;
var um = a.TraitOrDefault<UpgradeManager>();
return um != null && (info.Duration > 0 ?
info.Upgrades.Any(u => um.AcknowledgesUpgrade(a, u)) : info.Upgrades.Any(u => um.AcceptsUpgrade(a, u)));
return um != null && um.AcceptsExternalCondition(a, info.Condition);
});
}
class SelectUpgradeTarget : IOrderGenerator
{
readonly GrantUpgradePower power;
readonly GrantExternalConditionPower power;
readonly int range;
readonly Sprite tile;
readonly SupportPowerManager manager;
readonly string order;
public SelectUpgradeTarget(World world, string order, SupportPowerManager manager, GrantUpgradePower power)
public SelectUpgradeTarget(World world, string order, SupportPowerManager manager, GrantExternalConditionPower power)
{
// Clear selection if using Left-Click Orders
if (Game.Settings.Game.UseClassicMouseStyle)

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>
{
[UpgradeGrantedReference]
public readonly string[] Conditions = { };
}
public class ExternalConditions { }
}

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]
[UpgradeUsedReference]
[Desc("Condition to monitor.")]
public readonly string Condition = null;
[FieldLoader.Require]
[UpgradeGrantedReference]
[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 { }
}

View File

@@ -17,6 +17,13 @@ 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 upgrades by warheads, experience, crates, support powers, etc.")]
public class UpgradeManagerInfo : TraitInfo<UpgradeManager>, Requires<IConditionConsumerInfo> { }
@@ -25,38 +32,16 @@ namespace OpenRA.Mods.Common.Traits
/// <summary>Value used to represent an invalid token.</summary>
public static readonly int InvalidConditionToken = -1;
class TimedCondition
class ConditionTimer
{
public class ConditionSource
{
public readonly object Source;
public int Remaining;
public ConditionSource(int duration, object source)
{
Remaining = duration;
Source = source;
}
}
public readonly string Condition;
public readonly int Token;
public readonly int Duration;
public readonly HashSet<ConditionSource> Sources;
public int Remaining; // Equal to maximum of all Sources.Remaining
public int Remaining;
public TimedCondition(string condition, int duration, object source)
public ConditionTimer(int token, int duration)
{
Condition = condition;
Duration = duration;
Remaining = duration;
Sources = new HashSet<ConditionSource> { new ConditionSource(duration, source) };
}
public void Tick()
{
Remaining--;
foreach (var source in Sources)
source.Remaining--;
Token = token;
Duration = Remaining = duration;
}
}
@@ -69,23 +54,31 @@ namespace OpenRA.Mods.Common.Traits
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<Action<int, int>> Watchers = new List<Action<int, int>>();
public readonly List<IConditionTimerWatcher> Watchers = new List<IConditionTimerWatcher>();
}
readonly List<TimedCondition> timedConditions = new List<TimedCondition>();
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>Temporary shim between the old and new upgrade/condition grant and revoke methods.</summary>
Dictionary<Pair<object, string>, int> objectTokenShim = new Dictionary<Pair<object, string>, int>();
readonly Dictionary<Pair<object, string>, int> objectTokenShim = new Dictionary<Pair<object, string>, int>();
/// <summary>Cache of condition -> enabled state for quick evaluation of boolean conditions.</summary>
Dictionary<string, bool> conditionCache = new Dictionary<string, bool>();
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;
@@ -96,12 +89,19 @@ namespace OpenRA.Mods.Common.Traits
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)
{
state.GetOrAdd(condition).Consumers.Add(consumer);
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;
}
}
@@ -117,6 +117,18 @@ 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;
stackedTokens[sc.Condition] = new Stack<int>();
}
// Update all traits with their initial condition state
foreach (var consumer in allConsumers)
consumer.ConditionsChanged(self, readOnlyConditionCache);
@@ -137,15 +149,35 @@ namespace OpenRA.Mods.Common.Traits
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>
public int GrantCondition(Actor self, string condition)
/// <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)
@@ -155,7 +187,7 @@ namespace OpenRA.Mods.Common.Traits
}
/// <summary>Revokes a previously granted condition.</summary>
/// <returns>The invalid token ID</returns>
/// <returns>The invalid token ID.</returns>
/// <param name="token">The token ID returned by GrantCondition.</param>
public int RevokeCondition(Actor self, int token)
{
@@ -165,6 +197,15 @@ namespace OpenRA.Mods.Common.Traits
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);
@@ -172,6 +213,61 @@ 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)
{
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();
}
#region Shim methods for legacy upgrade granting code
void CheckCanManageConditions()
@@ -180,36 +276,12 @@ namespace OpenRA.Mods.Common.Traits
throw new InvalidOperationException("Conditions cannot be managed until the actor has been fully created.");
}
/// <summary>Upgrade level increments are limited to dupesAllowed per source, i.e., if a single
/// source attempts granting more upgrades than dupesAllowed, they will not accumulate. They will
/// replace each other instead, leaving only the most recently granted upgrade active. Each new
/// upgrade granting request will increment the upgrade's level until AcceptsUpgrade starts
/// returning false. Then, when no new levels are accepted, the upgrade source with the shortest
/// remaining upgrade duration will be replaced by the new source.</summary>
public void GrantTimedUpgrade(Actor self, string upgrade, int duration, object source = null, int dupesAllowed = 1)
{
var timed = timedConditions.FirstOrDefault(u => u.Condition == upgrade);
if (timed == null)
{
timed = new TimedCondition(upgrade, duration, source);
timedConditions.Add(timed);
GrantUpgrade(self, upgrade, timed);
return;
}
var srcs = timed.Sources.Where(s => s.Source == source);
if (srcs.Count() < dupesAllowed)
{
timed.Sources.Add(new TimedCondition.ConditionSource(duration, source));
if (AcceptsUpgrade(self, upgrade))
GrantUpgrade(self, upgrade, timed);
else
timed.Sources.Remove(timed.Sources.MinBy(s => s.Remaining));
}
else
srcs.MinBy(s => s.Remaining).Remaining = duration;
timed.Remaining = Math.Max(duration, timed.Remaining);
CheckCanManageConditions();
var token = GrantCondition(self, upgrade, false, duration);
if (source != null)
objectTokenShim[Pair.New(source, upgrade)] = token;
}
public void GrantUpgrade(Actor self, string upgrade, object source)
@@ -242,39 +314,6 @@ namespace OpenRA.Mods.Common.Traits
return !enabled;
}
public void RegisterWatcher(string upgrade, Action<int, int> action)
{
CheckCanManageConditions();
ConditionState s;
if (!state.TryGetValue(upgrade, out s))
return;
s.Watchers.Add(action);
}
/// <summary>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 in ticks of the timed upgrade (provided in the first call to
/// GrantTimedUpgrade).</summary>
void ITick.Tick(Actor self)
{
foreach (var u in timedConditions)
{
u.Tick();
foreach (var source in u.Sources)
if (source.Remaining <= 0)
RevokeUpgrade(self, u.Condition, u);
u.Sources.RemoveWhere(source => source.Remaining <= 0);
foreach (var a in state[u.Condition].Watchers)
a(u.Duration, u.Remaining);
}
timedConditions.RemoveAll(u => u.Remaining <= 0);
}
#endregion
}
}

View File

@@ -92,6 +92,28 @@ namespace OpenRA.Mods.Common.UtilityCommands
catch { }
}
static void RenameNodeKey(MiniYamlNode node, string key)
{
var parts = node.Key.Split('@');
node.Key = key;
if (parts.Length > 1)
node.Key += "@" + parts[1];
}
static void ConvertUpgradesToCondition(MiniYamlNode parent, MiniYamlNode node, string upgradesKey, string conditionKey)
{
var upgradesNode = node.Value.Nodes.FirstOrDefault(n => n.Key == upgradesKey);
if (upgradesNode != null)
{
var conditions = FieldLoader.GetValue<string[]>("", upgradesNode.Value.Value);
if (conditions.Length > 1)
Console.WriteLine("Unable to automatically migrate {0}:{1} {2} to {3}. This must be corrected manually",
parent.Key, node.Key, upgradesKey, conditionKey);
else
upgradesNode.Key = conditionKey;
}
}
internal static void UpgradeActorRules(ModData modData, int engineVersion, ref List<MiniYamlNode> nodes, MiniYamlNode parent, int depth)
{
var addNodes = new List<MiniYamlNode>();
@@ -118,10 +140,7 @@ namespace OpenRA.Mods.Common.UtilityCommands
if (s != null)
s.Key = "Image";
var parts = node.Key.Split('@');
node.Key = "WithDamageOverlay";
if (parts.Length > 1)
node.Key += "@" + parts[1];
RenameNodeKey(node, "WithDamageOverlay");
}
}
@@ -135,13 +154,9 @@ namespace OpenRA.Mods.Common.UtilityCommands
if (engineVersion < 20160611)
{
// Deprecated WithSpriteRotorOverlay
if (depth == 1 && node.Key.StartsWith("WithSpriteRotorOverlay"))
if (depth == 1 && node.Key.StartsWith("WithSpriteRotorOverlay", StringComparison.Ordinal))
{
var parts = node.Key.Split('@');
node.Key = "WithIdleOverlay";
if (parts.Length > 1)
node.Key += "@" + parts[1];
RenameNodeKey(node, "WithIdleOverlay");
Console.WriteLine("The 'WithSpriteRotorOverlay' trait has been removed.");
Console.WriteLine("Its functionality can be fully replicated with 'WithIdleOverlay' + upgrades.");
Console.WriteLine("Look at the helicopters in our RA / C&C1 mods for implementation details.");
@@ -282,13 +297,8 @@ namespace OpenRA.Mods.Common.UtilityCommands
if (engineVersion < 20160818)
{
if (depth == 1 && node.Key.StartsWith("UpgradeOnDamage"))
{
var parts = node.Key.Split('@');
node.Key = "UpgradeOnDamageState";
if (parts.Length > 1)
node.Key += "@" + parts[1];
}
if (depth == 1 && node.Key.StartsWith("UpgradeOnDamage", StringComparison.Ordinal))
RenameNodeKey(node, "UpgradeOnDamageState");
}
// DisplayTimer was replaced by DisplayTimerStances
@@ -435,7 +445,7 @@ namespace OpenRA.Mods.Common.UtilityCommands
}
}
// Rename Replaced upgrade consumers with conditions
// Replaced upgrade consumers with conditions
if (engineVersion < 20161117)
{
var upgradeTypesNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "UpgradeTypes");
@@ -480,16 +490,7 @@ namespace OpenRA.Mods.Common.UtilityCommands
if (engineVersion < 20161119)
{
// Migrated carryalls over to new conditions system
var carryableUpgradesNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "CarryableUpgrades");
if (carryableUpgradesNode != null)
{
var conditions = FieldLoader.GetValue<string[]>("", carryableUpgradesNode.Value.Value);
if (conditions.Length > 1)
Console.WriteLine("Unable to automatically migrate {0}:{1} CarryableUpgrades to CarriedCondition. This must be corrected manually",
parent.Key, node.Key);
else
carryableUpgradesNode.Key = "CarriedCondition";
}
ConvertUpgradesToCondition(parent, node, "CarryableUpgrades", "CarriedCondition");
if (node.Key == "WithDecorationCarryable")
{
@@ -498,6 +499,39 @@ namespace OpenRA.Mods.Common.UtilityCommands
}
}
if (engineVersion < 20161120)
{
if (node.Key.StartsWith("TimedUpgradeBar", StringComparison.Ordinal))
{
RenameNodeKey(node, "TimedConditionBar");
ConvertUpgradesToCondition(parent, node, "Upgrade", "Condition");
}
if (node.Key.StartsWith("GrantUpgradePower", StringComparison.Ordinal))
{
Console.WriteLine("GrantUpgradePower Condition must be manually added to all target actor's ExternalConditions list.");
RenameNodeKey(node, "GrantExternalConditionPower");
ConvertUpgradesToCondition(parent, node, "Upgrades", "Condition");
var soundNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "GrantUpgradeSound");
if (soundNode != null)
soundNode.Key = "OnFireSound";
else
node.Value.Nodes.Add(new MiniYamlNode("OnFireSound", "ironcur9.aud"));
var sequenceNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "GrantUpgradeSequence");
if (sequenceNode != null)
sequenceNode.Key = "Sequence";
}
if (node.Key.StartsWith("GrantUpgradeCrateAction", StringComparison.Ordinal))
{
Console.WriteLine("GrantUpgradeCrateAction Condition must be manually added to all target actor's ExternalConditions list.");
RenameNodeKey(node, "GrantExternalConditionCrateAction");
ConvertUpgradesToCondition(parent, node, "Upgrades", "Condition");
}
}
UpgradeActorRules(modData, engineVersion, ref node.Value.Nodes, node, depth + 1);
}
@@ -567,6 +601,16 @@ namespace OpenRA.Mods.Common.UtilityCommands
node.Key = "LaunchAngle";
}
if (engineVersion < 20161120)
{
if (node.Key.StartsWith("Warhead", StringComparison.Ordinal) && node.Value.Value == "GrantUpgrade")
{
node.Value.Value = "GrantExternalCondition";
Console.WriteLine("GrantExternalCondition Condition must be manually added to all target actor's ExternalConditions list.");
ConvertUpgradesToCondition(parent, node, "Upgrades", "Condition");
}
}
UpgradeWeaponRules(modData, engineVersion, ref node.Value.Nodes, node, depth + 1);
}
}

View File

@@ -10,19 +10,18 @@
#endregion
using System.Collections.Generic;
using System.Linq;
using OpenRA.Mods.Common.Traits;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Warheads
{
public class GrantUpgradeWarhead : Warhead
public class GrantExternalConditionWarhead : Warhead
{
[UpgradeGrantedReference]
[Desc("The upgrades to apply.")]
public readonly string[] Upgrades = { };
[FieldLoader.Require]
[Desc("The condition to apply. Must be included in the target actor's ExternalConditions list.")]
public readonly string Condition = null;
[Desc("Duration of the upgrade (in ticks). Set to 0 for a permanent upgrade.")]
[Desc("Duration of the condition (in ticks). Set to 0 for a permanent condition.")]
public readonly int Duration = 0;
public readonly WDist Range = WDist.FromCells(1);
@@ -38,22 +37,10 @@ namespace OpenRA.Mods.Common.Warheads
continue;
var um = a.TraitOrDefault<UpgradeManager>();
if (um == null)
continue;
foreach (var u in Upgrades)
{
if (Duration > 0)
{
if (um.AcknowledgesUpgrade(a, u))
um.GrantTimedUpgrade(a, u, Duration, firedBy, Upgrades.Count(upg => upg == u));
}
else
{
if (um.AcceptsUpgrade(a, u))
um.GrantUpgrade(a, u, this);
}
}
// Condition token is ignored because we never revoke this condition.
if (um != null && um.AcceptsExternalCondition(a, Condition))
um.GrantCondition(a, Condition, true, Duration);
}
}
}

View File

@@ -159,6 +159,8 @@
CloakSound: trans1.aud
UncloakSound: trans1.aud
RequiresCondition: cloak
ExternalConditions@CLOAK:
Conditions: cloak
MustBeDestroyed:
Voiced:
VoiceSet: VehicleVoice

View File

@@ -16,10 +16,10 @@ CRATE:
ExplodeCrateAction@fire:
Weapon: Napalm.Crate
SelectionShares: 5
GrantUpgradeCrateAction@cloak:
GrantExternalConditionCrateAction@cloak:
SelectionShares: 5
Effect: cloak
Upgrades: cloak
Condition: cloak
GiveMcvCrateAction:
SelectionShares: 0
NoBaseSelectionShares: 120

View File

@@ -119,7 +119,7 @@ FTUR:
BeginChargeSound: chrochr1.aud
EndChargeSound: chrordy1.aud
Range: 3
GrantUpgradePower@IRONCURTAIN:
GrantExternalConditionPower@IRONCURTAIN:
Icon: invuln
ChargeTime: 30
Description: Invulnerability
@@ -129,8 +129,9 @@ FTUR:
BeginChargeSound: ironchg1.aud
EndChargeSound: ironrdy1.aud
Range: 1
Upgrades: invulnerability
GrantUpgradeSequence: idle
Condition: invulnerability
Sequence: idle
OnFireSound: ironcur9.aud
Power:
Amount: 0
@@ -186,7 +187,7 @@ T17:
Duration: 999999
KillCargo: yes
Range: 3
GrantUpgradePower@IRONCURTAIN:
GrantExternalConditionPower@IRONCURTAIN:
Icon: invuln
ChargeTime: 30
Description: Invulnerability
@@ -196,5 +197,6 @@ T17:
BeginChargeSound: ironchg1.aud
EndChargeSound: ironrdy1.aud
Range: 1
Upgrades: invulnerability
GrantUpgradeSequence: idle
Condition: invulnerability
Sequence: idle
OnFireSound: ironcur9.aud

View File

@@ -69,11 +69,11 @@ FORTCRATE:
GiveUnitCrateAction@e7:
Units: e7
SelectionShares: 10
GrantUpgradeCrateAction@ironcurtain:
GrantExternalConditionCrateAction@ironcurtain:
SelectionShares: 10
Effect: invuln
Notification: ironcur9.aud
Upgrades: invulnerability
Condition: invulnerability
Duration: 1200
ExplodeCrateAction@bigboom:
Weapon: SCUD

View File

@@ -15,7 +15,7 @@
^GainsExperience:
GainsExperience:
Upgrades:
Upgrades:
200: rank-veteran-1
400: rank-veteran-2
800: rank-veteran-3
@@ -121,8 +121,10 @@
DamageMultiplier@IRONCURTAIN:
RequiresCondition: invulnerability
Modifier: 0
TimedUpgradeBar:
Upgrade: invulnerability
TimedConditionBar:
Condition: invulnerability
ExternalConditions@INVULNERABILITY:
Conditions: invulnerability
^Vehicle:
Inherits@1: ^ExistsInWorld

View File

@@ -90,11 +90,11 @@ CRATE:
Units: e1,e1,e4,e4,e3,e3,e3
ValidFactions: soviet, russia, ukraine
TimeDelay: 4500
GrantUpgradeCrateAction@invuln:
GrantExternalConditionCrateAction@invuln:
SelectionShares: 5
Effect: invuln
Notification: ironcur9.aud
Upgrades: invulnerability
Condition: invulnerability
Duration: 600
MONEYCRATE:

View File

@@ -303,7 +303,7 @@ IRON:
Range: 10c0
Bib:
HasMinibib: Yes
GrantUpgradePower@IRONCURTAIN:
GrantExternalConditionPower@IRONCURTAIN:
Icon: invuln
ChargeTime: 120
Description: Invulnerability
@@ -314,7 +314,8 @@ IRON:
BeginChargeSpeechNotification: IronCurtainCharging
EndChargeSpeechNotification: IronCurtainReady
DisplayRadarPing: True
Upgrades: invulnerability
Condition: invulnerability
OnFireSound: ironcur9.aud
SupportPowerChargeBar:
Power:
Amount: -200

View File

@@ -102,7 +102,7 @@ BUS:
MaxWeight: 20
PipCount: 5
UnloadVoice: Unload
LoadingUpgrades: notmobile
LoadingUpgrades: loading
EjectOnDeath: true
PICK:
@@ -126,7 +126,7 @@ PICK:
MaxWeight: 2
PipCount: 5
UnloadVoice: Unload
LoadingUpgrades: notmobile
LoadingUpgrades: loading
EjectOnDeath: true
CAR:
@@ -150,7 +150,7 @@ CAR:
MaxWeight: 4
PipCount: 5
UnloadVoice: Unload
LoadingUpgrades: notmobile
LoadingUpgrades: loading
EjectOnDeath: true
WINI:
@@ -174,7 +174,7 @@ WINI:
MaxWeight: 5
PipCount: 5
UnloadVoice: Unload
LoadingUpgrades: notmobile
LoadingUpgrades: loading
EjectOnDeath: true
LOCOMOTIVE:

View File

@@ -64,6 +64,8 @@
ReferencePoint: Bottom, Right
RequiresCondition: rank-elite
ZOffset: 256
ExternalConditions@CRATES:
Conditions: crate-firepower, crate-damage, crate-speed, crate-cloak
^EmpDisable:
UpgradeOverlay@EMPDISABLE:
@@ -71,8 +73,8 @@
Palette: disabled
DisableOnUpgrade@EMPDISABLE:
RequiresCondition: empdisable
TimedUpgradeBar@EMPDISABLE:
Upgrade: empdisable
TimedConditionBar@EMPDISABLE:
Condition: empdisable
Color: FFFFFF
WithIdleOverlay@EMPDISABLE:
Sequence: emp-overlay
@@ -83,11 +85,13 @@
PowerMultiplier@EMPDISABLE:
RequiresCondition: empdisable
Modifier: 0
ExternalConditions@EMPDISABLE:
Conditions: empdisable
^EmpDisableMobile:
Inherits: ^EmpDisable
Mobile:
RequiresCondition: !notmobile
RequiresCondition: !empdisable && !deployed && !loading
^Cloakable:
Cloak@CLOAKGENERATOR:
@@ -639,7 +643,6 @@
Mobile:
Speed: 113
TurnSpeed: 16
Crushes: crate
SharesCell: no
TerrainSpeeds:
Clear: 90
@@ -795,7 +798,7 @@
Cargo:
Types: Infantry
UnloadVoice: Unload
LoadingUpgrades: notmobile
LoadingUpgrades: loading
Health:
HP: 100
Armor:

View File

@@ -28,7 +28,7 @@ APC:
MaxWeight: 5
PipCount: 5
UnloadVoice: Unload
LoadingUpgrades: notmobile
LoadingUpgrades: loading
EjectOnDeath: true
UpgradeOnTerrain:
Upgrades: inwater

View File

@@ -74,24 +74,24 @@ CRATE:
SelectionShares: 0
NoBaseSelectionShares: 100
Units: mcv
GrantUpgradeCrateAction@cloak:
GrantExternalConditionCrateAction@cloak:
SelectionShares: 5
Effect: stealth
Upgrades: crate-cloak
Condition: crate-cloak
Notification: cloak5.aud
GrantUpgradeCrateAction@firepower:
GrantExternalConditionCrateAction@firepower:
SelectionShares: 5
Effect: firepower
Upgrades: crate-firepower
Condition: crate-firepower
Notification: 00-i070.aud
GrantUpgradeCrateAction@armor:
GrantExternalConditionCrateAction@armor:
SelectionShares: 5
Effect: armor
Upgrades: crate-damage
Condition: crate-damage
Notification: 00-i068.aud
GrantUpgradeCrateAction@speed:
GrantExternalConditionCrateAction@speed:
SelectionShares: 5
Upgrades: crate-speed
Condition: crate-speed
Notification: 00-i080.aud
SROCK01:

View File

@@ -106,7 +106,7 @@ TTNK:
RenderSprites:
Image: ttnk
DeployToUpgrade:
DeployedUpgrades: deployed, notmobile
DeployedUpgrades: deployed
UndeployedUpgrades: undeployed
DeployAnimation: make
Facing: 160
@@ -285,7 +285,7 @@ SAPC:
MaxWeight: 5
PipCount: 5
UnloadVoice: Unload
LoadingUpgrades: notmobile
LoadingUpgrades: loading
EjectOnDeath: true
SUBTANK:

View File

@@ -138,7 +138,7 @@ LPST:
gdi: lpst.gdi
nod: lpst.nod
DeployToUpgrade:
DeployedUpgrades: deployed, notmobile
DeployedUpgrades: deployed
UndeployedUpgrades: undeployed
DeployAnimation: make
Facing: 160

View File

@@ -107,10 +107,10 @@ EMPulseCannon:
Image: pulsball
Warhead@1Eff: CreateEffect
Explosions: pulse_explosion
Warhead@emp: GrantUpgrade
Warhead@emp: GrantExternalCondition
Range: 4c0
Duration: 250
Upgrades: empdisable, notmobile
Condition: empdisable
ClusterMissile:
ValidTargets: Ground, Water, Air