Merge pull request #7812 from pchote/modules

Pluggable upgrade modules
This commit is contained in:
Chris Forbes
2015-04-02 05:25:48 +13:00
18 changed files with 474 additions and 208 deletions

View File

@@ -619,6 +619,8 @@
<Compile Include="UtilityCommands\CheckSequenceSprites.cs" />
<Compile Include="UtilityCommands\FixClassicTilesets.cs" />
<Compile Include="Graphics\TilesetSpecificSpriteSequence.cs" />
<Compile Include="Traits\Pluggable.cs" />
<Compile Include="Traits\Plug.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>

View File

@@ -24,6 +24,7 @@ namespace OpenRA.Mods.Common.Orders
readonly string building;
readonly BuildingInfo buildingInfo;
readonly PlaceBuildingInfo placeBuildingInfo;
readonly BuildingInfluence buildingInfluence;
readonly string race;
readonly Sprite buildOk;
readonly Sprite buildBlocked;
@@ -52,6 +53,8 @@ namespace OpenRA.Mods.Common.Orders
buildOk = map.SequenceProvider.GetSequence("overlay", "build-valid-{0}".F(tileset)).GetSprite(0);
buildBlocked = map.SequenceProvider.GetSequence("overlay", "build-invalid").GetSprite(0);
buildingInfluence = producer.World.WorldActor.Trait<BuildingInfluence>();
}
public IEnumerable<Order> Order(World world, CPos xy, MouseInput mi)
@@ -73,7 +76,21 @@ namespace OpenRA.Mods.Common.Orders
if (mi.Button == MouseButton.Left)
{
var orderType = "PlaceBuilding";
var topLeft = xy - FootprintUtils.AdjustForBuildingSize(buildingInfo);
var plugInfo = world.Map.Rules.Actors[building].Traits.GetOrDefault<PlugInfo>();
if (plugInfo != null)
{
orderType = "PlacePlug";
if (!AcceptsPlug(topLeft, plugInfo))
{
Sound.PlayNotification(world.Map.Rules, producer.Owner, "Speech", "BuildingCannotPlaceAudio", producer.Owner.Country.Race);
yield break;
}
}
else
{
if (!world.CanPlaceBuilding(building, buildingInfo, topLeft, null)
|| !buildingInfo.IsCloseEnoughToBase(world, producer.Owner, building, topLeft))
{
@@ -81,8 +98,11 @@ namespace OpenRA.Mods.Common.Orders
yield break;
}
var isLineBuild = world.Map.Rules.Actors[building].Traits.Contains<LineBuildInfo>();
yield return new Order(isLineBuild ? "LineBuild" : "PlaceBuilding", producer.Owner.PlayerActor, false)
if (world.Map.Rules.Actors[building].Traits.Contains<LineBuildInfo>())
orderType = "LineBuild";
}
yield return new Order(orderType, producer.Owner.PlayerActor, false)
{
TargetLocation = topLeft,
TargetActor = producer,
@@ -101,6 +121,16 @@ namespace OpenRA.Mods.Common.Orders
p.Tick();
}
bool AcceptsPlug(CPos cell, PlugInfo plug)
{
var host = buildingInfluence.GetBuildingAt(cell);
if (host == null)
return false;
var location = host.Location;
return host.TraitsImplementing<Pluggable>().Any(p => location + p.Info.Offset == cell && p.AcceptsPlug(host, plug.Type));
}
public IEnumerable<IRenderable> Render(WorldRenderer wr, World world) { yield break; }
public IEnumerable<IRenderable> RenderAfterWorld(WorldRenderer wr, World world)
{
@@ -116,10 +146,17 @@ namespace OpenRA.Mods.Common.Orders
var cells = new Dictionary<CPos, bool>();
// Linebuild for walls.
// Requires a 1x1 footprint
if (rules.Actors[building].Traits.Contains<LineBuildInfo>())
var plugInfo = rules.Actors[building].Traits.GetOrDefault<PlugInfo>();
if (plugInfo != null)
{
if (buildingInfo.Dimensions.X != 1 || buildingInfo.Dimensions.Y != 1)
throw new InvalidOperationException("Plug requires a 1x1 sized Building");
cells.Add(topLeft, AcceptsPlug(topLeft, plugInfo));
}
else if (rules.Actors[building].Traits.Contains<LineBuildInfo>())
{
// Linebuild for walls.
if (buildingInfo.Dimensions.X != 1 || buildingInfo.Dimensions.Y != 1)
throw new InvalidOperationException("LineBuild requires a 1x1 sized Building");

View File

@@ -18,7 +18,7 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
public abstract class AttackBaseInfo : ITraitInfo
public abstract class AttackBaseInfo : UpgradableTraitInfo, ITraitInfo
{
[Desc("Armament names")]
public readonly string[] Armaments = { "primary", "secondary" };
@@ -35,11 +35,10 @@ namespace OpenRA.Mods.Common.Traits
public abstract object Create(ActorInitializer init);
}
public abstract class AttackBase : IIssueOrder, IResolveOrder, IOrderVoice, ISync
public abstract class AttackBase : UpgradableTrait<AttackBaseInfo>, IIssueOrder, IResolveOrder, IOrderVoice, ISync
{
[Sync] public bool IsAttacking { get; internal set; }
public IEnumerable<Armament> Armaments { get { return getArmaments(); } }
public readonly AttackBaseInfo Info;
protected Lazy<IFacing> facing;
protected Lazy<Building> building;
@@ -49,9 +48,9 @@ namespace OpenRA.Mods.Common.Traits
readonly Actor self;
public AttackBase(Actor self, AttackBaseInfo info)
: base(info)
{
this.self = self;
Info = info;
var armaments = Exts.Lazy(() => self.TraitsImplementing<Armament>()
.Where(a => info.Armaments.Contains(a.Info.Name)));
@@ -65,7 +64,7 @@ namespace OpenRA.Mods.Common.Traits
protected virtual bool CanAttack(Actor self, Target target)
{
if (!self.IsInWorld)
if (!self.IsInWorld || IsTraitDisabled)
return false;
if (!HasAnyValidWeapons(target))
@@ -101,7 +100,7 @@ namespace OpenRA.Mods.Common.Traits
get
{
var armament = Armaments.FirstOrDefault(a => a.Weapon.Warheads.Any(w => (w is DamageWarhead)));
if (armament == null)
if (armament == null || IsTraitDisabled)
yield break;
var negativeDamage = (armament.Weapon.Warheads.FirstOrDefault(w => (w is DamageWarhead)) as DamageWarhead).Damage < 0;
@@ -149,6 +148,9 @@ namespace OpenRA.Mods.Common.Traits
public bool HasAnyValidWeapons(Target t)
{
if (IsTraitDisabled)
return false;
if (Info.AttackRequiresEnteringCell && !positionable.Value.CanEnterCell(t.Actor.Location, null, false))
return false;
@@ -157,14 +159,19 @@ namespace OpenRA.Mods.Common.Traits
public WRange GetMaximumRange()
{
return Armaments.Select(a => a.Weapon.Range).Append(WRange.Zero).Max();
if (IsTraitDisabled)
return WRange.Zero;
return Armaments.Where(a => !a.IsTraitDisabled)
.Select(a => a.Weapon.Range)
.Append(WRange.Zero).Max();
}
public Armament ChooseArmamentForTarget(Target t) { return Armaments.FirstOrDefault(a => a.Weapon.IsValidAgainst(t, self.World, self)); }
public void AttackTarget(Target target, bool queued, bool allowMove)
{
if (self.IsDisabled())
if (self.IsDisabled() || IsTraitDisabled)
return;
if (!target.IsValidFor(self))

View File

@@ -123,7 +123,7 @@ namespace OpenRA.Mods.Common.Traits
if (!Cloaked || self.Owner.IsAlliedWith(viewer))
return true;
return self.World.ActorsWithTrait<DetectCloaked>().Any(a => a.Actor.Owner.IsAlliedWith(viewer)
return self.World.ActorsWithTrait<DetectCloaked>().Any(a => !a.Trait.IsTraitDisabled && a.Actor.Owner.IsAlliedWith(viewer)
&& Info.CloakTypes.Intersect(a.Trait.Info.CloakTypes).Any()
&& (self.CenterPosition - a.Actor.CenterPosition).Length <= WRange.FromCells(a.Trait.Info.Range).Range);
}

View File

@@ -13,7 +13,7 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[Desc("Actor can reveal Cloak actors in a specified range.")]
public class DetectCloakedInfo : ITraitInfo
public class DetectCloakedInfo : UpgradableTraitInfo, ITraitInfo
{
[Desc("Specific cloak classifications I can reveal.")]
public readonly string[] CloakTypes = { "Cloak" };
@@ -24,13 +24,8 @@ namespace OpenRA.Mods.Common.Traits
public object Create(ActorInitializer init) { return new DetectCloaked(this); }
}
public class DetectCloaked
public class DetectCloaked : UpgradableTrait<DetectCloakedInfo>
{
public readonly DetectCloakedInfo Info;
public DetectCloaked(DetectCloakedInfo info)
{
Info = info;
}
public DetectCloaked(DetectCloakedInfo info) : base(info) { }
}
}

View File

@@ -26,7 +26,7 @@ namespace OpenRA.Mods.Common.Traits
{
public void ResolveOrder(Actor self, Order order)
{
if (order.OrderString == "PlaceBuilding" || order.OrderString == "LineBuild")
if (order.OrderString == "PlaceBuilding" || order.OrderString == "LineBuild" || order.OrderString == "PlacePlug")
{
self.World.AddFrameEndTask(w =>
{
@@ -69,6 +69,27 @@ namespace OpenRA.Mods.Common.Traits
playSounds = false;
}
}
else if (order.OrderString == "PlacePlug")
{
var host = self.World.WorldActor.Trait<BuildingInfluence>().GetBuildingAt(order.TargetLocation);
if (host == null)
return;
var plugInfo = unit.Traits.GetOrDefault<PlugInfo>();
if (plugInfo == null)
return;
var location = host.Location;
var pluggable = host.TraitsImplementing<Pluggable>()
.FirstOrDefault(p => location + p.Info.Offset == order.TargetLocation && p.AcceptsPlug(host, plugInfo.Type));
if (pluggable == null)
return;
pluggable.EnablePlug(host, plugInfo.Type);
foreach (var s in buildingInfo.BuildSounds)
Sound.PlayToPlayer(order.Player, s, host.CenterPosition);
}
else
{
if (!self.World.CanPlaceBuilding(order.TargetString, buildingInfo, order.TargetLocation, null)

View File

@@ -0,0 +1,24 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 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. For more information,
* see COPYING.
*/
#endregion
using System.Collections.Generic;
using System.Linq;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
public class PlugInfo : TraitInfo<Plug>
{
[Desc("Plug type (matched against Upgrades in Pluggable)")]
public readonly string Type = null;
}
public class Plug { }
}

View File

@@ -0,0 +1,79 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 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. For more information,
* see COPYING.
*/
#endregion
using System.Collections.Generic;
using System.Linq;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
public class PluggableInfo : ITraitInfo, Requires<UpgradeManagerInfo>
{
[Desc("Footprint cell offset where a plug can be placed.")]
public readonly CVec Offset = CVec.Zero;
[FieldLoader.LoadUsing("LoadUpgrades")]
[Desc("Upgrades to grant for each accepted plug type.")]
public readonly Dictionary<string, string[]> Upgrades = null;
static object LoadUpgrades(MiniYaml y)
{
MiniYaml upgrades;
if (!y.ToDictionary().TryGetValue("Upgrades", out upgrades))
return new Dictionary<string, string[]>();
return upgrades.Nodes.ToDictionary(
kv => kv.Key,
kv => FieldLoader.GetValue<string[]>("(value)", kv.Value.Value));
}
public object Create(ActorInitializer init) { return new Pluggable(init.Self, this); }
}
public class Pluggable
{
public readonly PluggableInfo Info;
readonly UpgradeManager upgradeManager;
string active;
public Pluggable(Actor self, PluggableInfo info)
{
Info = info;
upgradeManager = self.Trait<UpgradeManager>();
}
public bool AcceptsPlug(Actor self, string type)
{
return active == null && Info.Upgrades.ContainsKey(type);
}
public void EnablePlug(Actor self, string type)
{
string[] upgrades;
if (!Info.Upgrades.TryGetValue(type, out upgrades))
return;
foreach (var u in upgrades)
upgradeManager.GrantUpgrade(self, u, this);
active = type;
}
public void DisablePlug(Actor self, string type)
{
if (type != active)
return;
foreach (var u in Info.Upgrades[type])
upgradeManager.RevokeUpgrade(self, u, this);
}
}
}

View File

@@ -10,6 +10,7 @@
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Mods.Common.Graphics;
using OpenRA.Traits;
@@ -32,9 +33,17 @@ namespace OpenRA.Mods.Common.Traits
if (self.Owner != self.World.LocalPlayer)
yield break;
var range = self.TraitsImplementing<DetectCloaked>()
.Where(a => !a.IsTraitDisabled)
.Select(a => WRange.FromCells(a.Info.Range))
.Append(WRange.Zero).Max();
if (range == WRange.Zero)
yield break;
yield return new RangeCircleRenderable(
self.CenterPosition,
WRange.FromCells(self.Info.Traits.Get<DetectCloakedInfo>().Range),
range,
0,
Color.FromArgb(128, Color.LimeGreen),
Color.FromArgb(96, Color.Black));

View File

@@ -27,7 +27,8 @@ namespace OpenRA.Mods.Common.Traits
public IEnumerable<IRenderable> Render(WorldRenderer wr, World w, ActorInfo ai, WPos centerPosition)
{
var armaments = ai.Traits.WithInterface<ArmamentInfo>();
var armaments = ai.Traits.WithInterface<ArmamentInfo>()
.Where(a => a.UpgradeMinEnabledLevel == 0);
var range = FallbackRange;
if (armaments.Any())
@@ -69,9 +70,13 @@ namespace OpenRA.Mods.Common.Traits
if (self.Owner != self.World.LocalPlayer)
yield break;
var range = attack.GetMaximumRange();
if (range == WRange.Zero)
yield break;
yield return new RangeCircleRenderable(
self.CenterPosition,
attack.GetMaximumRange(),
range,
0,
Color.FromArgb(128, Color.Yellow),
Color.FromArgb(96, Color.Black));

View File

@@ -17,7 +17,7 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[Desc("Renders a decorative animation on units and buildings.")]
public class WithIdleOverlayInfo : ITraitInfo, IRenderActorPreviewSpritesInfo, Requires<RenderSpritesInfo>, Requires<IBodyOrientationInfo>
public class WithIdleOverlayInfo : UpgradableTraitInfo, ITraitInfo, IRenderActorPreviewSpritesInfo, Requires<RenderSpritesInfo>, Requires<IBodyOrientationInfo>
{
[Desc("Sequence name to use")]
public readonly string Sequence = "idle-overlay";
@@ -37,6 +37,9 @@ namespace OpenRA.Mods.Common.Traits
public IEnumerable<IActorPreview> RenderPreviewSprites(ActorPreviewInitializer init, RenderSpritesInfo rs, string image, int facings, PaletteReference p)
{
if (UpgradeMinEnabledLevel > 0)
yield break;
var body = init.Actor.Traits.Get<BodyOrientationInfo>();
var facing = init.Contains<FacingInit>() ? init.Get<FacingInit, int>() : 0;
var anim = new Animation(init.World, image, () => facing);
@@ -48,12 +51,13 @@ namespace OpenRA.Mods.Common.Traits
}
}
public class WithIdleOverlay : INotifyDamageStateChanged, INotifyBuildComplete, INotifySold, INotifyTransform
public class WithIdleOverlay : UpgradableTrait<WithIdleOverlayInfo>, INotifyDamageStateChanged, INotifyBuildComplete, INotifySold, INotifyTransform
{
Animation overlay;
bool buildComplete;
public WithIdleOverlay(Actor self, WithIdleOverlayInfo info)
: base(info)
{
var rs = self.Trait<RenderSprites>();
var body = self.Trait<IBodyOrientation>();
@@ -65,7 +69,7 @@ namespace OpenRA.Mods.Common.Traits
rs.Add("idle_overlay_{0}".F(info.Sequence),
new AnimationWithOffset(overlay,
() => body.LocalToWorld(info.Offset.Rotate(body.QuantizeOrientation(self, self.Orientation))),
() => !buildComplete,
() => IsTraitDisabled || !buildComplete,
() => info.PauseOnLowPower && disabled.Any(d => d.Disabled),
p => WithTurret.ZOffsetFromCenter(self, p, 1)),
info.Palette, info.IsPlayerPalette);

View File

@@ -17,7 +17,7 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[Desc("Renders the MuzzleSequence from the Armament trait.")]
class WithMuzzleFlashInfo : ITraitInfo, Requires<RenderSpritesInfo>, Requires<AttackBaseInfo>, Requires<ArmamentInfo>
class WithMuzzleFlashInfo : UpgradableTraitInfo, ITraitInfo, Requires<RenderSpritesInfo>, Requires<AttackBaseInfo>, Requires<ArmamentInfo>
{
[Desc("Ignore the weapon position, and always draw relative to the center of the actor")]
public readonly bool IgnoreOffset = false;
@@ -25,13 +25,14 @@ namespace OpenRA.Mods.Common.Traits
public object Create(ActorInitializer init) { return new WithMuzzleFlash(init.Self, this); }
}
class WithMuzzleFlash : INotifyAttack, IRender, ITick
class WithMuzzleFlash : UpgradableTrait<WithMuzzleFlashInfo>, INotifyAttack, IRender, ITick
{
Dictionary<Barrel, bool> visible = new Dictionary<Barrel, bool>();
Dictionary<Barrel, AnimationWithOffset> anims = new Dictionary<Barrel, AnimationWithOffset>();
Func<int> getFacing;
public WithMuzzleFlash(Actor self, WithMuzzleFlashInfo info)
: base(info)
{
var render = self.Trait<RenderSprites>();
var facing = self.TraitOrDefault<IFacing>();
@@ -64,7 +65,7 @@ namespace OpenRA.Mods.Common.Traits
anims.Add(barrel,
new AnimationWithOffset(muzzleFlash,
() => info.IgnoreOffset ? WVec.Zero : armClosure.MuzzleOffset(self, barrel),
() => !visible[barrel],
() => IsTraitDisabled || !visible[barrel],
() => false,
p => WithTurret.ZOffsetFromCenter(self, p, 2)));
}

View File

@@ -17,7 +17,8 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[Desc("Renders turrets for units with the Turreted trait.")]
public class WithTurretInfo : ITraitInfo, IRenderActorPreviewSpritesInfo, Requires<RenderSpritesInfo>, Requires<TurretedInfo>, Requires<IBodyOrientationInfo>
public class WithTurretInfo : UpgradableTraitInfo, ITraitInfo, IRenderActorPreviewSpritesInfo,
Requires<RenderSpritesInfo>, Requires<TurretedInfo>, Requires<IBodyOrientationInfo>
{
[Desc("Sequence name to use")]
public readonly string Sequence = "turret";
@@ -35,6 +36,9 @@ namespace OpenRA.Mods.Common.Traits
public IEnumerable<IActorPreview> RenderPreviewSprites(ActorPreviewInitializer init, RenderSpritesInfo rs, string image, int facings, PaletteReference p)
{
if (UpgradeMinEnabledLevel > 0)
yield break;
var body = init.Actor.Traits.Get<BodyOrientationInfo>();
var t = init.Actor.Traits.WithInterface<TurretedInfo>()
.First(tt => tt.Turret == Turret);
@@ -52,9 +56,8 @@ namespace OpenRA.Mods.Common.Traits
}
}
public class WithTurret : ITick
public class WithTurret : UpgradableTrait<WithTurretInfo>, ITick
{
WithTurretInfo info;
RenderSprites rs;
IBodyOrientation body;
AttackBase ab;
@@ -63,8 +66,8 @@ namespace OpenRA.Mods.Common.Traits
Animation anim;
public WithTurret(Actor self, WithTurretInfo info)
: base(info)
{
this.info = info;
rs = self.Trait<RenderSprites>();
body = self.Trait<IBodyOrientation>();
@@ -76,8 +79,8 @@ namespace OpenRA.Mods.Common.Traits
anim = new Animation(self.World, rs.GetImage(self), () => t.TurretFacing);
anim.Play(info.Sequence);
rs.Add("turret_{0}".F(info.Turret), new AnimationWithOffset(
anim, () => TurretOffset(self), null, () => false, p => ZOffsetFromCenter(self, p, 1)));
rs.Add("turret_{0}_{1}".F(info.Turret, info.Sequence), new AnimationWithOffset(
anim, () => TurretOffset(self), () => IsTraitDisabled, () => false, p => ZOffsetFromCenter(self, p, 1)));
// Restrict turret facings to match the sprite
t.QuantizedFacings = anim.CurrentSequence.Facings;
@@ -85,7 +88,7 @@ namespace OpenRA.Mods.Common.Traits
WVec TurretOffset(Actor self)
{
if (!info.Recoils)
if (!Info.Recoils)
return t.Position(self);
var recoil = arms.Aggregate(WRange.Zero, (a, b) => a + b.Recoil);
@@ -97,10 +100,10 @@ namespace OpenRA.Mods.Common.Traits
public void Tick(Actor self)
{
if (info.AimSequence == null)
if (Info.AimSequence == null)
return;
var sequence = ab.IsAttacking ? info.AimSequence : info.Sequence;
var sequence = ab.IsAttacking ? Info.AimSequence : Info.Sequence;
anim.ReplaceAnim(sequence);
}

View File

@@ -12,7 +12,7 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
public abstract class SupportPowerInfo : ITraitInfo
public abstract class SupportPowerInfo : UpgradableTraitInfo, ITraitInfo
{
[Desc("Measured in seconds.")]
public readonly int ChargeTime = 0;
@@ -53,15 +53,14 @@ namespace OpenRA.Mods.Common.Traits
public SupportPowerInfo() { OrderName = GetType().Name + "Order"; }
}
public class SupportPower
public class SupportPower : UpgradableTrait<SupportPowerInfo>
{
public readonly Actor Self;
public readonly SupportPowerInfo Info;
protected RadarPing ping;
public SupportPower(Actor self, SupportPowerInfo info)
: base(info)
{
Info = info;
Self = self;
}

View File

@@ -132,7 +132,7 @@ namespace OpenRA.Mods.Common.Traits
if (!Powers.TryGetValue(key, out sp))
return;
sp.Disabled = false;
sp.PrerequisitesAvailable(true);
}
public void PrerequisitesUnavailable(string key)
@@ -141,7 +141,7 @@ namespace OpenRA.Mods.Common.Traits
if (!Powers.TryGetValue(key, out sp))
return;
sp.Disabled = true;
sp.PrerequisitesAvailable(false);
sp.RemainingTime = sp.TotalTime;
}
@@ -158,17 +158,25 @@ namespace OpenRA.Mods.Common.Traits
public int RemainingTime;
public int TotalTime;
public bool Active { get; private set; }
public bool Disabled { get; set; }
public bool Disabled { get { return !prereqsAvailable || !upgradeAvailable; } }
public SupportPowerInfo Info { get { return Instances.Select(i => i.Info).FirstOrDefault(); } }
public bool Ready { get { return Active && RemainingTime == 0; } }
bool upgradeAvailable;
bool prereqsAvailable = true;
public SupportPowerInstance(string key, SupportPowerManager manager)
{
this.manager = manager;
this.key = key;
}
public void PrerequisitesAvailable(bool available)
{
prereqsAvailable = available;
}
static bool InstanceDisabled(SupportPower sp)
{
return sp.Self.TraitsImplementing<IDisable>().Any(d => d.Disabled);
@@ -178,6 +186,10 @@ namespace OpenRA.Mods.Common.Traits
bool notifiedReady;
public void Tick()
{
upgradeAvailable = Instances.Any(i => !i.IsTraitDisabled);
if (!upgradeAvailable)
RemainingTime = TotalTime;
Active = !Disabled && Instances.Any(i => !i.Self.IsDisabled());
if (!Active)
return;
@@ -226,7 +238,7 @@ namespace OpenRA.Mods.Common.Traits
notifiedCharging = notifiedReady = false;
if (Info.OneShot)
Disabled = true;
PrerequisitesAvailable(false);
}
}

View File

@@ -141,6 +141,13 @@
CloakDelay: 90
MustBeDestroyed:
^BuildingPlug:
Building:
BuildSounds: place2.aud
KillsSelf:
RemoveInstead: true
RenderSprites:
^Infantry:
AppearsOnRadar:
Health:

View File

@@ -86,6 +86,46 @@ GAPOWR:
TargetTypes: Ground, C4, DetonateAttack, SpyInfiltrate
ScalePowerWithHealth:
DisabledOverlay:
Pluggable@pluga:
Offset: 0,1
Upgrades:
powrup: powrup.a
Power@pluga:
UpgradeTypes: powrup.a
UpgradeMinEnabledLevel: 1
Amount: 50
WithIdleOverlay@pluga:
UpgradeTypes: powrup.a
UpgradeMinEnabledLevel: 1
Sequence: idle-powrupa
Pluggable@plugb:
Offset: 1,1
Upgrades:
powrup: powrup.b
WithIdleOverlay@plugb:
UpgradeTypes: powrup.b
UpgradeMinEnabledLevel: 1
Sequence: idle-powrupb
Power@plugb:
UpgradeTypes: powrup.b
UpgradeMinEnabledLevel: 1
Amount: 50
GAPOWRUP:
Inherits: ^BuildingPlug
Valued:
Cost: 150
Tooltip:
Name: Power Turbine
Description: Provides extra power generation
Buildable:
Queue: Defense
BuildPaletteOrder: 10
Prerequisites: gapowr, ~structures.gdi
Plug:
Type: powrup
Power:
Amount: 50
GAPILE:
Inherits: ^Building
@@ -430,7 +470,7 @@ GAWALL:
Inherits: ^Wall
Buildable:
Queue: Defense
BuildPaletteOrder: 1001
BuildPaletteOrder: 20
Prerequisites: ~structures.gdi
SoundOnDamageTransition:
DamagedSounds:
@@ -910,14 +950,13 @@ NASTLH:
Selectable:
Bounds: 124, 64, 15, 13
#TODO: Placeholder, replace with Component Tower + Vulcan Upgrade
GAVULC:
GACTWR:
Inherits: ^Building
Valued:
Cost: 600
Tooltip:
Name: Vulcan Tower
Description: Basic base defense. \nRequires no power to operate.\n Strong vs infantry and light armor\n Cannot target Aircraft
Name: Component Tower
Description: Modular tower for base defenses.
Buildable:
Queue: Defense
BuildPaletteOrder: 30
@@ -938,26 +977,60 @@ GAVULC:
RenderRangeCircle:
RenderDetectionCircle:
DetectCloaked:
UpgradeTypes: tower
UpgradeMinEnabledLevel: 1
Range: 5
AutoTarget:
Turreted:
ROT: 10
InitialFacing: 50
AttackTurreted:
WithTurret:
UpgradeTypes: tower
UpgradeMinEnabledLevel: 1
CanPowerDown:
WithTurret@VULC:
UpgradeTypes: tower.vulcan
UpgradeMinEnabledLevel: 1
Recoils: no
Armament@PRIMARY:
Sequence: turret-vulcan
WithTurret@ROCKET:
UpgradeTypes: tower.rocket
UpgradeMinEnabledLevel: 1
Recoils: no
Sequence: turret-rocket
WithTurret@SAM:
UpgradeTypes: tower.sam
UpgradeMinEnabledLevel: 1
Recoils: no
Sequence: turret-sam
Armament@VULCPRIMARY:
UpgradeTypes: tower.vulcan
UpgradeMinEnabledLevel: 1
Weapon: VulcanTower
LocalOffset: 768,85,512
MuzzleSequence: muzzle
MuzzleSplitFacings: 8
Armament@SECONDARY:
Armament@VULCSECONDARY:
UpgradeTypes: tower.vulcan
UpgradeMinEnabledLevel: 1
Name: secondary
Weapon: VulcanTower
LocalOffset: 768,-85,512
MuzzleSequence: muzzle
MuzzleSplitFacings: 8
Armament@ROCKET:
UpgradeTypes: tower.rocket
UpgradeMinEnabledLevel: 1
Weapon: RPGTower
LocalOffset: 512,-128,512
Armament@SAM:
UpgradeTypes: tower.sam
UpgradeMinEnabledLevel: 1
Weapon: SAMTower
LocalOffset: 512,0,512
WithMuzzleFlash:
UpgradeTypes: tower.vulcan
UpgradeMinEnabledLevel: 1
WithIdleOverlay@LIGHTS:
Sequence: idle-lights
LineBuildNode:
@@ -965,106 +1038,67 @@ GAVULC:
-RenderBuilding:
RenderBuildingWall:
Type: wall
Power@base:
Amount: -10
Power@turrets:
UpgradeTypes: tower
UpgradeMinEnabledLevel: 1
Amount: -20
Power@samextra:
UpgradeTypes: tower.sam
UpgradeMinEnabledLevel: 1
Amount: -10
Pluggable:
Upgrades:
tower.vulcan: tower, tower.vulcan
tower.rocket: tower, tower.rocket
tower.sam: tower, tower.sam
GAVULC:
Inherits: ^BuildingPlug
Valued:
Cost: 150
Tooltip:
Name: Vulcan Tower
Description: Basic base defense. \nRequires no power to operate.\n Strong vs infantry and light armor\n Cannot target Aircraft
Buildable:
Queue: Defense
BuildPaletteOrder: 40
Prerequisites: gactwr, gapile, ~structures.gdi
Plug:
Type: tower.vulcan
Power:
Amount: -20
#TODO: Placeholder, replace with Component Tower + RPG Upgrade
GAROCK:
Inherits: ^Building
Inherits: ^BuildingPlug
Valued:
Cost: 1000
Cost: 600
Tooltip:
Name: RPG Tower
Name: RPG Upgrade
Description: GDI Advanced base defense.\nRequires power to operate.\n Strong vs armored ground units\n Cannot target Aircraft
Buildable:
Queue: Defense
BuildPaletteOrder: 40
Prerequisites: gapile, ~structures.gdi
Building:
Selectable:
Bounds: 48, 48, 0, -12
RequiresPower:
DisabledOverlay:
-GivesBuildableArea:
Health:
HP: 500
Armor:
Type: Light
RevealsShroud:
Range: 6c0
BodyOrientation:
QuantizedFacings: 32
RenderRangeCircle:
RenderDetectionCircle:
DetectCloaked:
Range: 5
AutoTarget:
Turreted:
ROT: 10
InitialFacing: 50
AttackTurreted:
WithTurret:
Recoils: no
Armament:
Weapon: RPGTower
LocalOffset: 512,-128,512
WithIdleOverlay@LIGHTS:
Sequence: idle-lights
LineBuildNode:
Types: turret
-RenderBuilding:
RenderBuildingWall:
Type: wall
Prerequisites: gactwr, gapile, ~structures.gdi
Plug:
Type: tower.rocket
Power:
Amount: -50
Amount: -20
#TODO: Placeholder, replace with Component Tower + SAM Upgrade
GACSAM:
Inherits: ^Building
Inherits: ^BuildingPlug
Valued:
Cost: 600
Cost: 300
Tooltip:
Name: S.A.M. Tower
Name: SAM. Upgrade
Description: GDI Anti-Air base defense. \nRequires power to operate.\n Strong vs all Aircraft\n Cannot target ground units
Buildable:
Queue: Defense
BuildPaletteOrder: 60
Prerequisites: garadr, ~structures.gdi
Building:
Selectable:
Bounds: 48, 48, 0, -12
RequiresPower:
DisabledOverlay:
-GivesBuildableArea:
Health:
HP: 500
Armor:
Type: Light
RevealsShroud:
Range: 6c0
BodyOrientation:
QuantizedFacings: 32
RenderRangeCircle:
RenderDetectionCircle:
DetectCloaked:
Range: 5
AutoTarget:
Turreted:
ROT: 10
InitialFacing: 50
AttackTurreted:
WithTurret:
Recoils: no
Armament:
Weapon: SAMTower
LocalOffset: 512,0,512
WithIdleOverlay@LIGHTS:
Sequence: idle-lights
LineBuildNode:
Types: turret
-RenderBuilding:
RenderBuildingWall:
Type: wall
BuildPaletteOrder: 40
Prerequisites: gactwr, garadr, ~structures.gdi
Plug:
Type: tower.sam
Power:
Amount: -30
@@ -1291,16 +1325,16 @@ NAMISL:
GAPLUG:
Inherits: ^Building
Valued:
Cost: 1800
Cost: 1000
Tooltip:
Name: GDI Upgrade Center
Description: Provides the Orbital Ion Cannon support power.\nRequires power to operate.
Description: Can be upgraded for additional technology
Selectable:
Bounds: 115,120,0,-20
Buildable:
BuildPaletteOrder: 100
Prerequisites: proc, gatech
Queue: Defense
Queue: Building
Building:
Footprint: xxx xxx
Dimensions: 2,3
@@ -1321,6 +1355,8 @@ GAPLUG:
RevealsShroud:
Range: 6c0
IonCannonPower:
UpgradeTypes: plug.ioncannon
UpgradeMinEnabledLevel: 1
Icon: ioncannon
Effect: ionbeam
ChargeTime: 180
@@ -1336,6 +1372,42 @@ GAPLUG:
SupportPowerChargeBar:
Power:
Amount: -150
Power@ioncannon:
UpgradeTypes: plug.ioncannon
UpgradeMinEnabledLevel: 1
Amount: -100
Pluggable@pluga:
Offset: 0,2
Upgrades:
plug.ioncannon: plug.ioncannon, plug.ioncannona
WithIdleOverlay@ioncannona:
UpgradeTypes: plug.ioncannona
UpgradeMinEnabledLevel: 1
Sequence: idle-ioncannona
Pluggable@plugb:
Offset: 1,2
Upgrades:
plug.ioncannon: plug.ioncannon, plug.ioncannonb
WithIdleOverlay@ioncannonb:
UpgradeTypes: plug.ioncannonb
UpgradeMinEnabledLevel: 1
Sequence: idle-ioncannonb
GAPLUG3:
Inherits: ^BuildingPlug
Valued:
Cost: 1500
Tooltip:
Name: Ion Cannon Uplink
Description: Enables use of the Ion Cannon
Buildable:
Queue: Defense
BuildPaletteOrder: 1000
Prerequisites: gaplug, gatech, ~structures.gdi
Plug:
Type: plug.ioncannon
Power:
Amount: -100
ANYPOWER:
Tooltip:

View File

@@ -67,6 +67,22 @@ gapowr:
damaged-idle-plug: gtpowr_b
Length: 12
Tick: 200
idle-powrupa: gtpowr_b
Length: 12
Tick: 200
Offset: -48, -24
damaged-idle-powrupa: gtpowr_b
Length: 12
Tick: 200
Offset: -48, -24
idle-powrupb: gtpowr_b
Length: 12
Tick: 200
Offset: -24, -12
damaged-idle-powrupb: gtpowr_b
Length: 12
Tick: 200
Offset: -24, -12
make: gtpowrmk
Length: 20
ShadowStart: 20
@@ -74,6 +90,9 @@ gapowr:
Offset: 0, 0
UseTilesetCode: false
gapowrup:
icon: turbicon
gapile:
Defaults:
Offset: 0, -24
@@ -654,7 +673,7 @@ nastlh:
Offset: 0, 0
UseTilesetCode: false
gavulc:
gactwr:
Defaults:
Offset: 0, -12
UseTilesetCode: true
@@ -667,7 +686,21 @@ gavulc:
Start: 2
ShadowStart: 5
Tick: 400
turret: gtctwr_b
idle-lights: gtctwr_a
Length: 6
Tick: 200
damaged-idle-lights: gtctwr_a
Length: 6
Tick: 200
make: gtctwrmk
Length: 11
ShadowStart: 11
turret-vulcan: gtctwr_b
Facings: 32
turret-rocket: gtctwr_c
Facings: 32
UseTilesetCode: false
turret-sam: gtctwr_d
Facings: 32
muzzle0: mgun-n
Length: *
@@ -693,74 +726,18 @@ gavulc:
muzzle7: mgun-ne
Length: *
UseTilesetCode: false
idle-lights: gtctwr_a
Length: 6
Tick: 200
damaged-idle-lights: gtctwr_a
Length: 6
Tick: 200
make: gtctwrmk
Length: 11
ShadowStart: 11
icon: twr1icon
icon: towricon
Offset: 0, 0
UseTilesetCode: false
gavulc:
icon: twr1icon
garock:
Defaults:
Offset: 0, -12
UseTilesetCode: true
idle: gtctwr
ShadowStart: 3
damaged-idle: gtctwr
Start: 1
ShadowStart: 4
dead: gtctwr
Start: 2
ShadowStart: 5
Tick: 400
turret: gtctwr_c
Facings: 32
idle-lights: gtctwr_a
Length: 6
Tick: 200
damaged-idle-lights: gtctwr_a
Length: 6
Tick: 200
make: gtctwrmk
Length: 11
ShadowStart: 11
icon: twr2icon
Offset: 0, 0
UseTilesetCode: false
gacsam:
Defaults:
Offset: 0, -12
UseTilesetCode: true
idle: gtctwr
ShadowStart: 3
damaged-idle: gtctwr
Start: 1
ShadowStart: 4
dead: gtctwr
Start: 2
ShadowStart: 5
Tick: 400
turret: gtctwr_d
Facings: 32
idle-lights: gtctwr_a
Length: 6
Tick: 200
damaged-idle-lights: gtctwr_a
Length: 6
Tick: 200
make: gtctwrmk
Length: 11
ShadowStart: 11
icon: twr3icon
Offset: 0, 0
UseTilesetCode: false
gahpad:
Defaults:
@@ -1038,9 +1015,21 @@ gaplug:
Start: 8
Length: 8
Tick: 120
idle-ioncannona: gaplug_f
Length: 15
Tick: 120
Reverses: true
Offset: -12, -42
idle-ioncannonb: gaplug_f
Length: 15
Reverses: true
Tick: 120
make: gtplugmk
Length: 17
ShadowStart: 17
icon: plugicon
Offset: 0, 0
UseTilesetCode: false
gaplug3:
icon: rad3icon