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\CheckSequenceSprites.cs" />
<Compile Include="UtilityCommands\FixClassicTilesets.cs" /> <Compile Include="UtilityCommands\FixClassicTilesets.cs" />
<Compile Include="Graphics\TilesetSpecificSpriteSequence.cs" /> <Compile Include="Graphics\TilesetSpecificSpriteSequence.cs" />
<Compile Include="Traits\Pluggable.cs" />
<Compile Include="Traits\Plug.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup> <PropertyGroup>

View File

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

View File

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

View File

@@ -123,7 +123,7 @@ namespace OpenRA.Mods.Common.Traits
if (!Cloaked || self.Owner.IsAlliedWith(viewer)) if (!Cloaked || self.Owner.IsAlliedWith(viewer))
return true; 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() && Info.CloakTypes.Intersect(a.Trait.Info.CloakTypes).Any()
&& (self.CenterPosition - a.Actor.CenterPosition).Length <= WRange.FromCells(a.Trait.Info.Range).Range); && (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 namespace OpenRA.Mods.Common.Traits
{ {
[Desc("Actor can reveal Cloak actors in a specified range.")] [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.")] [Desc("Specific cloak classifications I can reveal.")]
public readonly string[] CloakTypes = { "Cloak" }; public readonly string[] CloakTypes = { "Cloak" };
@@ -24,13 +24,8 @@ namespace OpenRA.Mods.Common.Traits
public object Create(ActorInitializer init) { return new DetectCloaked(this); } 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) : base(info) { }
public DetectCloaked(DetectCloakedInfo info)
{
Info = info;
}
} }
} }

View File

@@ -26,7 +26,7 @@ namespace OpenRA.Mods.Common.Traits
{ {
public void ResolveOrder(Actor self, Order order) 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 => self.World.AddFrameEndTask(w =>
{ {
@@ -69,6 +69,27 @@ namespace OpenRA.Mods.Common.Traits
playSounds = false; 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 else
{ {
if (!self.World.CanPlaceBuilding(order.TargetString, buildingInfo, order.TargetLocation, null) 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.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.Linq;
using OpenRA.Graphics; using OpenRA.Graphics;
using OpenRA.Mods.Common.Graphics; using OpenRA.Mods.Common.Graphics;
using OpenRA.Traits; using OpenRA.Traits;
@@ -32,9 +33,17 @@ namespace OpenRA.Mods.Common.Traits
if (self.Owner != self.World.LocalPlayer) if (self.Owner != self.World.LocalPlayer)
yield break; 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( yield return new RangeCircleRenderable(
self.CenterPosition, self.CenterPosition,
WRange.FromCells(self.Info.Traits.Get<DetectCloakedInfo>().Range), range,
0, 0,
Color.FromArgb(128, Color.LimeGreen), Color.FromArgb(128, Color.LimeGreen),
Color.FromArgb(96, Color.Black)); 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) 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; var range = FallbackRange;
if (armaments.Any()) if (armaments.Any())
@@ -69,9 +70,13 @@ namespace OpenRA.Mods.Common.Traits
if (self.Owner != self.World.LocalPlayer) if (self.Owner != self.World.LocalPlayer)
yield break; yield break;
var range = attack.GetMaximumRange();
if (range == WRange.Zero)
yield break;
yield return new RangeCircleRenderable( yield return new RangeCircleRenderable(
self.CenterPosition, self.CenterPosition,
attack.GetMaximumRange(), range,
0, 0,
Color.FromArgb(128, Color.Yellow), Color.FromArgb(128, Color.Yellow),
Color.FromArgb(96, Color.Black)); Color.FromArgb(96, Color.Black));

View File

@@ -17,7 +17,7 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits namespace OpenRA.Mods.Common.Traits
{ {
[Desc("Renders a decorative animation on units and buildings.")] [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")] [Desc("Sequence name to use")]
public readonly string Sequence = "idle-overlay"; 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) 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 body = init.Actor.Traits.Get<BodyOrientationInfo>();
var facing = init.Contains<FacingInit>() ? init.Get<FacingInit, int>() : 0; var facing = init.Contains<FacingInit>() ? init.Get<FacingInit, int>() : 0;
var anim = new Animation(init.World, image, () => facing); 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; Animation overlay;
bool buildComplete; bool buildComplete;
public WithIdleOverlay(Actor self, WithIdleOverlayInfo info) public WithIdleOverlay(Actor self, WithIdleOverlayInfo info)
: base(info)
{ {
var rs = self.Trait<RenderSprites>(); var rs = self.Trait<RenderSprites>();
var body = self.Trait<IBodyOrientation>(); var body = self.Trait<IBodyOrientation>();
@@ -65,7 +69,7 @@ namespace OpenRA.Mods.Common.Traits
rs.Add("idle_overlay_{0}".F(info.Sequence), rs.Add("idle_overlay_{0}".F(info.Sequence),
new AnimationWithOffset(overlay, new AnimationWithOffset(overlay,
() => body.LocalToWorld(info.Offset.Rotate(body.QuantizeOrientation(self, self.Orientation))), () => body.LocalToWorld(info.Offset.Rotate(body.QuantizeOrientation(self, self.Orientation))),
() => !buildComplete, () => IsTraitDisabled || !buildComplete,
() => info.PauseOnLowPower && disabled.Any(d => d.Disabled), () => info.PauseOnLowPower && disabled.Any(d => d.Disabled),
p => WithTurret.ZOffsetFromCenter(self, p, 1)), p => WithTurret.ZOffsetFromCenter(self, p, 1)),
info.Palette, info.IsPlayerPalette); info.Palette, info.IsPlayerPalette);

View File

@@ -17,7 +17,7 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits namespace OpenRA.Mods.Common.Traits
{ {
[Desc("Renders the MuzzleSequence from the Armament trait.")] [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")] [Desc("Ignore the weapon position, and always draw relative to the center of the actor")]
public readonly bool IgnoreOffset = false; 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); } 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, bool> visible = new Dictionary<Barrel, bool>();
Dictionary<Barrel, AnimationWithOffset> anims = new Dictionary<Barrel, AnimationWithOffset>(); Dictionary<Barrel, AnimationWithOffset> anims = new Dictionary<Barrel, AnimationWithOffset>();
Func<int> getFacing; Func<int> getFacing;
public WithMuzzleFlash(Actor self, WithMuzzleFlashInfo info) public WithMuzzleFlash(Actor self, WithMuzzleFlashInfo info)
: base(info)
{ {
var render = self.Trait<RenderSprites>(); var render = self.Trait<RenderSprites>();
var facing = self.TraitOrDefault<IFacing>(); var facing = self.TraitOrDefault<IFacing>();
@@ -64,7 +65,7 @@ namespace OpenRA.Mods.Common.Traits
anims.Add(barrel, anims.Add(barrel,
new AnimationWithOffset(muzzleFlash, new AnimationWithOffset(muzzleFlash,
() => info.IgnoreOffset ? WVec.Zero : armClosure.MuzzleOffset(self, barrel), () => info.IgnoreOffset ? WVec.Zero : armClosure.MuzzleOffset(self, barrel),
() => !visible[barrel], () => IsTraitDisabled || !visible[barrel],
() => false, () => false,
p => WithTurret.ZOffsetFromCenter(self, p, 2))); p => WithTurret.ZOffsetFromCenter(self, p, 2)));
} }

View File

@@ -17,7 +17,8 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits namespace OpenRA.Mods.Common.Traits
{ {
[Desc("Renders turrets for units with the Turreted trait.")] [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")] [Desc("Sequence name to use")]
public readonly string Sequence = "turret"; 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) 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 body = init.Actor.Traits.Get<BodyOrientationInfo>();
var t = init.Actor.Traits.WithInterface<TurretedInfo>() var t = init.Actor.Traits.WithInterface<TurretedInfo>()
.First(tt => tt.Turret == Turret); .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; RenderSprites rs;
IBodyOrientation body; IBodyOrientation body;
AttackBase ab; AttackBase ab;
@@ -63,8 +66,8 @@ namespace OpenRA.Mods.Common.Traits
Animation anim; Animation anim;
public WithTurret(Actor self, WithTurretInfo info) public WithTurret(Actor self, WithTurretInfo info)
: base(info)
{ {
this.info = info;
rs = self.Trait<RenderSprites>(); rs = self.Trait<RenderSprites>();
body = self.Trait<IBodyOrientation>(); body = self.Trait<IBodyOrientation>();
@@ -76,8 +79,8 @@ namespace OpenRA.Mods.Common.Traits
anim = new Animation(self.World, rs.GetImage(self), () => t.TurretFacing); anim = new Animation(self.World, rs.GetImage(self), () => t.TurretFacing);
anim.Play(info.Sequence); anim.Play(info.Sequence);
rs.Add("turret_{0}".F(info.Turret), new AnimationWithOffset( rs.Add("turret_{0}_{1}".F(info.Turret, info.Sequence), new AnimationWithOffset(
anim, () => TurretOffset(self), null, () => false, p => ZOffsetFromCenter(self, p, 1))); anim, () => TurretOffset(self), () => IsTraitDisabled, () => false, p => ZOffsetFromCenter(self, p, 1)));
// Restrict turret facings to match the sprite // Restrict turret facings to match the sprite
t.QuantizedFacings = anim.CurrentSequence.Facings; t.QuantizedFacings = anim.CurrentSequence.Facings;
@@ -85,7 +88,7 @@ namespace OpenRA.Mods.Common.Traits
WVec TurretOffset(Actor self) WVec TurretOffset(Actor self)
{ {
if (!info.Recoils) if (!Info.Recoils)
return t.Position(self); return t.Position(self);
var recoil = arms.Aggregate(WRange.Zero, (a, b) => a + b.Recoil); 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) public void Tick(Actor self)
{ {
if (info.AimSequence == null) if (Info.AimSequence == null)
return; return;
var sequence = ab.IsAttacking ? info.AimSequence : info.Sequence; var sequence = ab.IsAttacking ? Info.AimSequence : Info.Sequence;
anim.ReplaceAnim(sequence); anim.ReplaceAnim(sequence);
} }

View File

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

View File

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

View File

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

View File

@@ -86,6 +86,46 @@ GAPOWR:
TargetTypes: Ground, C4, DetonateAttack, SpyInfiltrate TargetTypes: Ground, C4, DetonateAttack, SpyInfiltrate
ScalePowerWithHealth: ScalePowerWithHealth:
DisabledOverlay: 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: GAPILE:
Inherits: ^Building Inherits: ^Building
@@ -430,7 +470,7 @@ GAWALL:
Inherits: ^Wall Inherits: ^Wall
Buildable: Buildable:
Queue: Defense Queue: Defense
BuildPaletteOrder: 1001 BuildPaletteOrder: 20
Prerequisites: ~structures.gdi Prerequisites: ~structures.gdi
SoundOnDamageTransition: SoundOnDamageTransition:
DamagedSounds: DamagedSounds:
@@ -910,14 +950,13 @@ NASTLH:
Selectable: Selectable:
Bounds: 124, 64, 15, 13 Bounds: 124, 64, 15, 13
#TODO: Placeholder, replace with Component Tower + Vulcan Upgrade GACTWR:
GAVULC:
Inherits: ^Building Inherits: ^Building
Valued: Valued:
Cost: 600 Cost: 600
Tooltip: Tooltip:
Name: Vulcan Tower Name: Component Tower
Description: Basic base defense. \nRequires no power to operate.\n Strong vs infantry and light armor\n Cannot target Aircraft Description: Modular tower for base defenses.
Buildable: Buildable:
Queue: Defense Queue: Defense
BuildPaletteOrder: 30 BuildPaletteOrder: 30
@@ -938,26 +977,60 @@ GAVULC:
RenderRangeCircle: RenderRangeCircle:
RenderDetectionCircle: RenderDetectionCircle:
DetectCloaked: DetectCloaked:
UpgradeTypes: tower
UpgradeMinEnabledLevel: 1
Range: 5 Range: 5
AutoTarget: AutoTarget:
Turreted: Turreted:
ROT: 10 ROT: 10
InitialFacing: 50 InitialFacing: 50
AttackTurreted: AttackTurreted:
WithTurret: UpgradeTypes: tower
UpgradeMinEnabledLevel: 1
CanPowerDown:
WithTurret@VULC:
UpgradeTypes: tower.vulcan
UpgradeMinEnabledLevel: 1
Recoils: no 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 Weapon: VulcanTower
LocalOffset: 768,85,512 LocalOffset: 768,85,512
MuzzleSequence: muzzle MuzzleSequence: muzzle
MuzzleSplitFacings: 8 MuzzleSplitFacings: 8
Armament@SECONDARY: Armament@VULCSECONDARY:
UpgradeTypes: tower.vulcan
UpgradeMinEnabledLevel: 1
Name: secondary Name: secondary
Weapon: VulcanTower Weapon: VulcanTower
LocalOffset: 768,-85,512 LocalOffset: 768,-85,512
MuzzleSequence: muzzle MuzzleSequence: muzzle
MuzzleSplitFacings: 8 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: WithMuzzleFlash:
UpgradeTypes: tower.vulcan
UpgradeMinEnabledLevel: 1
WithIdleOverlay@LIGHTS: WithIdleOverlay@LIGHTS:
Sequence: idle-lights Sequence: idle-lights
LineBuildNode: LineBuildNode:
@@ -965,106 +1038,67 @@ GAVULC:
-RenderBuilding: -RenderBuilding:
RenderBuildingWall: RenderBuildingWall:
Type: wall 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: Power:
Amount: -20 Amount: -20
#TODO: Placeholder, replace with Component Tower + RPG Upgrade
GAROCK: GAROCK:
Inherits: ^Building Inherits: ^BuildingPlug
Valued: Valued:
Cost: 1000 Cost: 600
Tooltip: 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 Description: GDI Advanced base defense.\nRequires power to operate.\n Strong vs armored ground units\n Cannot target Aircraft
Buildable: Buildable:
Queue: Defense Queue: Defense
BuildPaletteOrder: 40 BuildPaletteOrder: 40
Prerequisites: gapile, ~structures.gdi Prerequisites: gactwr, gapile, ~structures.gdi
Building: Plug:
Selectable: Type: tower.rocket
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
Power: Power:
Amount: -50 Amount: -20
#TODO: Placeholder, replace with Component Tower + SAM Upgrade
GACSAM: GACSAM:
Inherits: ^Building Inherits: ^BuildingPlug
Valued: Valued:
Cost: 600 Cost: 300
Tooltip: 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 Description: GDI Anti-Air base defense. \nRequires power to operate.\n Strong vs all Aircraft\n Cannot target ground units
Buildable: Buildable:
Queue: Defense Queue: Defense
BuildPaletteOrder: 60 BuildPaletteOrder: 40
Prerequisites: garadr, ~structures.gdi Prerequisites: gactwr, garadr, ~structures.gdi
Building: Plug:
Selectable: Type: tower.sam
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
Power: Power:
Amount: -30 Amount: -30
@@ -1291,16 +1325,16 @@ NAMISL:
GAPLUG: GAPLUG:
Inherits: ^Building Inherits: ^Building
Valued: Valued:
Cost: 1800 Cost: 1000
Tooltip: Tooltip:
Name: GDI Upgrade Center Name: GDI Upgrade Center
Description: Provides the Orbital Ion Cannon support power.\nRequires power to operate. Description: Can be upgraded for additional technology
Selectable: Selectable:
Bounds: 115,120,0,-20 Bounds: 115,120,0,-20
Buildable: Buildable:
BuildPaletteOrder: 100 BuildPaletteOrder: 100
Prerequisites: proc, gatech Prerequisites: proc, gatech
Queue: Defense Queue: Building
Building: Building:
Footprint: xxx xxx Footprint: xxx xxx
Dimensions: 2,3 Dimensions: 2,3
@@ -1321,6 +1355,8 @@ GAPLUG:
RevealsShroud: RevealsShroud:
Range: 6c0 Range: 6c0
IonCannonPower: IonCannonPower:
UpgradeTypes: plug.ioncannon
UpgradeMinEnabledLevel: 1
Icon: ioncannon Icon: ioncannon
Effect: ionbeam Effect: ionbeam
ChargeTime: 180 ChargeTime: 180
@@ -1336,6 +1372,42 @@ GAPLUG:
SupportPowerChargeBar: SupportPowerChargeBar:
Power: Power:
Amount: -150 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: ANYPOWER:
Tooltip: Tooltip:

View File

@@ -67,6 +67,22 @@ gapowr:
damaged-idle-plug: gtpowr_b damaged-idle-plug: gtpowr_b
Length: 12 Length: 12
Tick: 200 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 make: gtpowrmk
Length: 20 Length: 20
ShadowStart: 20 ShadowStart: 20
@@ -74,6 +90,9 @@ gapowr:
Offset: 0, 0 Offset: 0, 0
UseTilesetCode: false UseTilesetCode: false
gapowrup:
icon: turbicon
gapile: gapile:
Defaults: Defaults:
Offset: 0, -24 Offset: 0, -24
@@ -654,7 +673,7 @@ nastlh:
Offset: 0, 0 Offset: 0, 0
UseTilesetCode: false UseTilesetCode: false
gavulc: gactwr:
Defaults: Defaults:
Offset: 0, -12 Offset: 0, -12
UseTilesetCode: true UseTilesetCode: true
@@ -667,7 +686,21 @@ gavulc:
Start: 2 Start: 2
ShadowStart: 5 ShadowStart: 5
Tick: 400 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 Facings: 32
muzzle0: mgun-n muzzle0: mgun-n
Length: * Length: *
@@ -693,74 +726,18 @@ gavulc:
muzzle7: mgun-ne muzzle7: mgun-ne
Length: * Length: *
UseTilesetCode: false UseTilesetCode: false
idle-lights: gtctwr_a icon: towricon
Length: 6
Tick: 200
damaged-idle-lights: gtctwr_a
Length: 6
Tick: 200
make: gtctwrmk
Length: 11
ShadowStart: 11
icon: twr1icon
Offset: 0, 0 Offset: 0, 0
UseTilesetCode: false UseTilesetCode: false
gavulc:
icon: twr1icon
garock: 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 icon: twr2icon
Offset: 0, 0
UseTilesetCode: false
gacsam: 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 icon: twr3icon
Offset: 0, 0
UseTilesetCode: false
gahpad: gahpad:
Defaults: Defaults:
@@ -1038,9 +1015,21 @@ gaplug:
Start: 8 Start: 8
Length: 8 Length: 8
Tick: 120 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 make: gtplugmk
Length: 17 Length: 17
ShadowStart: 17 ShadowStart: 17
icon: plugicon icon: plugicon
Offset: 0, 0 Offset: 0, 0
UseTilesetCode: false UseTilesetCode: false
gaplug3:
icon: rad3icon