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

View File

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

View File

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

View File

@@ -15,38 +15,35 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits.Render namespace OpenRA.Mods.Common.Traits.Render
{ {
[Desc("Visualizes the remaining time for an upgrade.")] [Desc("Visualizes the remaining time for an upgrade.")]
class TimedUpgradeBarInfo : ITraitInfo, Requires<UpgradeManagerInfo> class TimedConditionBarInfo : ITraitInfo, Requires<UpgradeManagerInfo>
{ {
[FieldLoader.Require] [FieldLoader.Require]
[Desc("Upgrade that this bar corresponds to")] [Desc("Condition that this bar corresponds to")]
public readonly string Upgrade = null; public readonly string Condition = null;
public readonly Color Color = Color.Red; 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; readonly Actor self;
float value; float value;
public TimedUpgradeBar(Actor self, TimedUpgradeBarInfo info) public TimedConditionBar(Actor self, TimedConditionBarInfo info)
{ {
this.self = self; this.self = self;
this.info = info; 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) string IConditionTimerWatcher.Condition { get { return info.Condition; } }
{
value = remaining * 1f / duration;
}
float ISelectionBar.GetValue() float ISelectionBar.GetValue()
{ {

View File

@@ -19,31 +19,33 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits namespace OpenRA.Mods.Common.Traits
{ {
class GrantUpgradePowerInfo : SupportPowerInfo class GrantExternalConditionPowerInfo : SupportPowerInfo
{ {
[UpgradeGrantedReference, FieldLoader.Require] [FieldLoader.Require]
[Desc("The upgrades to apply.")] [Desc("The condition to apply. Must be included in the target actor's ExternalConditions list.")]
public readonly string[] Upgrades = { }; 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; public readonly int Duration = 0;
[Desc("Cells - affects whole cells only")] [Desc("Cells - affects whole cells only")]
public readonly int Range = 1; 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.", [SequenceReference, Desc("Sequence to play for granting actor when activated.",
"This requires the actor to have the WithSpriteBody trait or one of its derivatives.")] "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) : base(self, info)
{ {
this.info = info; this.info = info;
@@ -60,30 +62,18 @@ namespace OpenRA.Mods.Common.Traits
base.Activate(self, order, manager); base.Activate(self, order, manager);
var wsb = self.TraitOrDefault<WithSpriteBody>(); var wsb = self.TraitOrDefault<WithSpriteBody>();
if (wsb != null && wsb.DefaultAnimation.HasSequence(info.GrantUpgradeSequence)) if (wsb != null && wsb.DefaultAnimation.HasSequence(info.Sequence))
wsb.PlayCustomAnimation(self, info.GrantUpgradeSequence, () => wsb.CancelCustomAnimation(self)); 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)) foreach (var a in UnitsInRange(order.TargetLocation))
{ {
var um = a.TraitOrDefault<UpgradeManager>(); var um = a.TraitOrDefault<UpgradeManager>();
if (um == null)
continue;
foreach (var u in info.Upgrades) // Condition token is ignored because we never revoke this condition.
{ if (um != null)
if (info.Duration > 0) um.GrantCondition(a, info.Condition, true, info.Duration);
{
if (um.AcknowledgesUpgrade(a, u))
um.GrantTimedUpgrade(a, u, info.Duration);
}
else
{
if (um.AcceptsUpgrade(a, u))
um.GrantUpgrade(a, u, this);
}
}
} }
} }
@@ -101,20 +91,19 @@ namespace OpenRA.Mods.Common.Traits
return false; return false;
var um = a.TraitOrDefault<UpgradeManager>(); var um = a.TraitOrDefault<UpgradeManager>();
return um != null && (info.Duration > 0 ? return um != null && um.AcceptsExternalCondition(a, info.Condition);
info.Upgrades.Any(u => um.AcknowledgesUpgrade(a, u)) : info.Upgrades.Any(u => um.AcceptsUpgrade(a, u)));
}); });
} }
class SelectUpgradeTarget : IOrderGenerator class SelectUpgradeTarget : IOrderGenerator
{ {
readonly GrantUpgradePower power; readonly GrantExternalConditionPower power;
readonly int range; readonly int range;
readonly Sprite tile; readonly Sprite tile;
readonly SupportPowerManager manager; readonly SupportPowerManager manager;
readonly string order; 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 // Clear selection if using Left-Click Orders
if (Game.Settings.Game.UseClassicMouseStyle) 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 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.")] [Desc("Attach this to a unit to enable dynamic upgrades by warheads, experience, crates, support powers, etc.")]
public class UpgradeManagerInfo : TraitInfo<UpgradeManager>, Requires<IConditionConsumerInfo> { } 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> /// <summary>Value used to represent an invalid token.</summary>
public static readonly int InvalidConditionToken = -1; public static readonly int InvalidConditionToken = -1;
class TimedCondition class ConditionTimer
{ {
public class ConditionSource public readonly int Token;
{ public readonly int Duration;
public readonly object Source;
public int Remaining; public int Remaining;
public ConditionSource(int duration, object source) public ConditionTimer(int token, int duration)
{ {
Remaining = duration; Token = token;
Source = source; Duration = Remaining = duration;
}
}
public readonly string Condition;
public readonly int Duration;
public readonly HashSet<ConditionSource> Sources;
public int Remaining; // Equal to maximum of all Sources.Remaining
public TimedCondition(string condition, int duration, object source)
{
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--;
} }
} }
@@ -69,23 +54,31 @@ namespace OpenRA.Mods.Common.Traits
public readonly HashSet<int> Tokens = new HashSet<int>(); public readonly HashSet<int> Tokens = new HashSet<int>();
/// <summary>External callbacks that are to be executed when a timed condition changes.</summary> /// <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; 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> /// <summary>Each granted condition receives a unique token that is used when revoking.</summary>
Dictionary<int, string> tokens = new Dictionary<int, string>(); 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; int nextToken = 1;
/// <summary>Temporary shim between the old and new upgrade/condition grant and revoke methods.</summary> /// <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> /// <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> /// <summary>Read-only version of conditionCache that is passed to IConditionConsumers.</summary>
IReadOnlyDictionary<string, bool> readOnlyConditionCache; IReadOnlyDictionary<string, bool> readOnlyConditionCache;
@@ -96,12 +89,19 @@ namespace OpenRA.Mods.Common.Traits
readOnlyConditionCache = new ReadOnlyDictionary<string, bool>(conditionCache); readOnlyConditionCache = new ReadOnlyDictionary<string, bool>(conditionCache);
var allConsumers = new HashSet<IConditionConsumer>(); var allConsumers = new HashSet<IConditionConsumer>();
var allWatchers = self.TraitsImplementing<IConditionTimerWatcher>().ToList();
foreach (var consumer in self.TraitsImplementing<IConditionConsumer>()) foreach (var consumer in self.TraitsImplementing<IConditionConsumer>())
{ {
allConsumers.Add(consumer); allConsumers.Add(consumer);
foreach (var condition in consumer.Conditions) 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; conditionCache[condition] = false;
} }
} }
@@ -117,6 +117,18 @@ namespace OpenRA.Mods.Common.Traits
conditionCache[kv.Value] = conditionState.Tokens.Count > 0; 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 // Update all traits with their initial condition state
foreach (var consumer in allConsumers) foreach (var consumer in allConsumers)
consumer.ConditionsChanged(self, readOnlyConditionCache); consumer.ConditionsChanged(self, readOnlyConditionCache);
@@ -137,15 +149,35 @@ namespace OpenRA.Mods.Common.Traits
foreach (var t in conditionState.Consumers) foreach (var t in conditionState.Consumers)
t.ConditionsChanged(self, readOnlyConditionCache); 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> /// <summary>Grants a specified condition.</summary>
/// <returns>The token that is used to revoke this condition.</returns> /// <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++; var token = nextToken++;
tokens.Add(token, condition); tokens.Add(token, condition);
if (duration > 0)
timers.GetOrAdd(condition).Add(new ConditionTimer(token, duration));
// Conditions may be granted before the state is initialized. // Conditions may be granted before the state is initialized.
// These conditions will be processed in INotifyCreated.Created. // These conditions will be processed in INotifyCreated.Created.
if (state != null) if (state != null)
@@ -155,7 +187,7 @@ namespace OpenRA.Mods.Common.Traits
} }
/// <summary>Revokes a previously granted condition.</summary> /// <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> /// <param name="token">The token ID returned by GrantCondition.</param>
public int RevokeCondition(Actor self, int token) public int RevokeCondition(Actor self, int token)
{ {
@@ -165,6 +197,15 @@ namespace OpenRA.Mods.Common.Traits
tokens.Remove(token); 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. // Conditions may be granted and revoked before the state is initialized.
if (state != null) if (state != null)
UpdateConditionState(self, condition, token, true); UpdateConditionState(self, condition, token, true);
@@ -172,6 +213,61 @@ namespace OpenRA.Mods.Common.Traits
return InvalidConditionToken; 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 #region Shim methods for legacy upgrade granting code
void CheckCanManageConditions() 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."); 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) public void GrantTimedUpgrade(Actor self, string upgrade, int duration, object source = null, int dupesAllowed = 1)
{ {
var timed = timedConditions.FirstOrDefault(u => u.Condition == upgrade); CheckCanManageConditions();
if (timed == null) var token = GrantCondition(self, upgrade, false, duration);
{ if (source != null)
timed = new TimedCondition(upgrade, duration, source); objectTokenShim[Pair.New(source, upgrade)] = token;
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);
} }
public void GrantUpgrade(Actor self, string upgrade, object source) public void GrantUpgrade(Actor self, string upgrade, object source)
@@ -242,39 +314,6 @@ namespace OpenRA.Mods.Common.Traits
return !enabled; 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 #endregion
} }
} }

View File

@@ -92,6 +92,28 @@ namespace OpenRA.Mods.Common.UtilityCommands
catch { } 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) internal static void UpgradeActorRules(ModData modData, int engineVersion, ref List<MiniYamlNode> nodes, MiniYamlNode parent, int depth)
{ {
var addNodes = new List<MiniYamlNode>(); var addNodes = new List<MiniYamlNode>();
@@ -118,10 +140,7 @@ namespace OpenRA.Mods.Common.UtilityCommands
if (s != null) if (s != null)
s.Key = "Image"; s.Key = "Image";
var parts = node.Key.Split('@'); RenameNodeKey(node, "WithDamageOverlay");
node.Key = "WithDamageOverlay";
if (parts.Length > 1)
node.Key += "@" + parts[1];
} }
} }
@@ -135,13 +154,9 @@ namespace OpenRA.Mods.Common.UtilityCommands
if (engineVersion < 20160611) if (engineVersion < 20160611)
{ {
// Deprecated WithSpriteRotorOverlay // Deprecated WithSpriteRotorOverlay
if (depth == 1 && node.Key.StartsWith("WithSpriteRotorOverlay")) if (depth == 1 && node.Key.StartsWith("WithSpriteRotorOverlay", StringComparison.Ordinal))
{ {
var parts = node.Key.Split('@'); RenameNodeKey(node, "WithIdleOverlay");
node.Key = "WithIdleOverlay";
if (parts.Length > 1)
node.Key += "@" + parts[1];
Console.WriteLine("The 'WithSpriteRotorOverlay' trait has been removed."); Console.WriteLine("The 'WithSpriteRotorOverlay' trait has been removed.");
Console.WriteLine("Its functionality can be fully replicated with 'WithIdleOverlay' + upgrades."); 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."); 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 (engineVersion < 20160818)
{ {
if (depth == 1 && node.Key.StartsWith("UpgradeOnDamage")) if (depth == 1 && node.Key.StartsWith("UpgradeOnDamage", StringComparison.Ordinal))
{ RenameNodeKey(node, "UpgradeOnDamageState");
var parts = node.Key.Split('@');
node.Key = "UpgradeOnDamageState";
if (parts.Length > 1)
node.Key += "@" + parts[1];
}
} }
// DisplayTimer was replaced by DisplayTimerStances // 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) if (engineVersion < 20161117)
{ {
var upgradeTypesNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "UpgradeTypes"); var upgradeTypesNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "UpgradeTypes");
@@ -480,16 +490,7 @@ namespace OpenRA.Mods.Common.UtilityCommands
if (engineVersion < 20161119) if (engineVersion < 20161119)
{ {
// Migrated carryalls over to new conditions system // Migrated carryalls over to new conditions system
var carryableUpgradesNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "CarryableUpgrades"); ConvertUpgradesToCondition(parent, node, "CarryableUpgrades", "CarriedCondition");
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";
}
if (node.Key == "WithDecorationCarryable") 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); UpgradeActorRules(modData, engineVersion, ref node.Value.Nodes, node, depth + 1);
} }
@@ -567,6 +601,16 @@ namespace OpenRA.Mods.Common.UtilityCommands
node.Key = "LaunchAngle"; 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); UpgradeWeaponRules(modData, engineVersion, ref node.Value.Nodes, node, depth + 1);
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -121,8 +121,10 @@
DamageMultiplier@IRONCURTAIN: DamageMultiplier@IRONCURTAIN:
RequiresCondition: invulnerability RequiresCondition: invulnerability
Modifier: 0 Modifier: 0
TimedUpgradeBar: TimedConditionBar:
Upgrade: invulnerability Condition: invulnerability
ExternalConditions@INVULNERABILITY:
Conditions: invulnerability
^Vehicle: ^Vehicle:
Inherits@1: ^ExistsInWorld Inherits@1: ^ExistsInWorld

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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