Merge pull request #13025 from pchote/laser-fence
Implement TS Laser Fences
This commit is contained in:
@@ -109,6 +109,7 @@
|
||||
<Compile Include="Traits\Chronoshiftable.cs" />
|
||||
<Compile Include="Traits\Cloneable.cs" />
|
||||
<Compile Include="Traits\Disguise.cs" />
|
||||
<Compile Include="Traits\EnergyWall.cs" />
|
||||
<Compile Include="Traits\FrozenUnderFogUpdatedByGps.cs" />
|
||||
<Compile Include="Traits\HarvesterHuskModifier.cs" />
|
||||
<Compile Include="Traits\Infiltration\InfiltrateForCash.cs" />
|
||||
|
||||
112
OpenRA.Mods.Cnc/Traits/EnergyWall.cs
Normal file
112
OpenRA.Mods.Cnc/Traits/EnergyWall.cs
Normal file
@@ -0,0 +1,112 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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.GameRules;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Support;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
[Desc("Will open and be passable for actors that appear friendly when there are no enemies in range.")]
|
||||
public class EnergyWallInfo : BuildingInfo, IObservesVariablesInfo, IRulesetLoaded
|
||||
{
|
||||
[FieldLoader.Require]
|
||||
[WeaponReference]
|
||||
[Desc("The weapon to attack units on top of the wall with when activated.")]
|
||||
public readonly string Weapon = null;
|
||||
|
||||
[ConsumedConditionReference]
|
||||
[Desc("Boolean expression defining the condition to activate this trait.")]
|
||||
public readonly BooleanExpression ActiveCondition = null;
|
||||
|
||||
public override object Create(ActorInitializer init) { return new EnergyWall(init, this); }
|
||||
|
||||
public WeaponInfo WeaponInfo { get; private set; }
|
||||
|
||||
public void RulesetLoaded(Ruleset rules, ActorInfo ai)
|
||||
{
|
||||
WeaponInfo weaponInfo;
|
||||
|
||||
var weaponToLower = Weapon.ToLowerInvariant();
|
||||
if (!rules.Weapons.TryGetValue(weaponToLower, out weaponInfo))
|
||||
throw new YamlException("Weapons Ruleset does not contain an entry '{0}'".F(weaponToLower));
|
||||
|
||||
WeaponInfo = weaponInfo;
|
||||
}
|
||||
}
|
||||
|
||||
public class EnergyWall : Building, IObservesVariables, ITick, ITemporaryBlocker
|
||||
{
|
||||
readonly EnergyWallInfo info;
|
||||
IEnumerable<CPos> blockedPositions;
|
||||
|
||||
// Initial state is active to match Building adding the influence to the ActorMap
|
||||
// This will be updated by ConditionsChanged at actor creation.
|
||||
bool active = true;
|
||||
|
||||
public EnergyWall(ActorInitializer init, EnergyWallInfo info)
|
||||
: base(init, info)
|
||||
{
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
public virtual IEnumerable<VariableObserver> GetVariableObservers()
|
||||
{
|
||||
if (info.ActiveCondition != null)
|
||||
yield return new VariableObserver(ActiveConditionChanged, info.ActiveCondition.Variables);
|
||||
}
|
||||
|
||||
void ActiveConditionChanged(Actor self, IReadOnlyDictionary<string, int> conditions)
|
||||
{
|
||||
if (info.ActiveCondition == null)
|
||||
return;
|
||||
|
||||
var wasActive = active;
|
||||
active = info.ActiveCondition.Evaluate(conditions);
|
||||
|
||||
if (!wasActive && active)
|
||||
self.World.ActorMap.AddInfluence(self, this);
|
||||
else if (wasActive && !active)
|
||||
self.World.ActorMap.RemoveInfluence(self, this);
|
||||
}
|
||||
|
||||
void ITick.Tick(Actor self)
|
||||
{
|
||||
if (!active)
|
||||
return;
|
||||
|
||||
foreach (var loc in blockedPositions)
|
||||
{
|
||||
var blockers = self.World.ActorMap.GetActorsAt(loc).Where(a => !a.IsDead && a != self);
|
||||
foreach (var blocker in blockers)
|
||||
info.WeaponInfo.Impact(Target.FromActor(blocker), self, Enumerable.Empty<int>());
|
||||
}
|
||||
}
|
||||
|
||||
bool ITemporaryBlocker.IsBlocking(Actor self, CPos cell)
|
||||
{
|
||||
return active && blockedPositions.Contains(cell);
|
||||
}
|
||||
|
||||
bool ITemporaryBlocker.CanRemoveBlockage(Actor self, Actor blocking)
|
||||
{
|
||||
return !active;
|
||||
}
|
||||
|
||||
public override void AddedToWorld(Actor self)
|
||||
{
|
||||
base.AddedToWorld(self);
|
||||
blockedPositions = FootprintUtils.Tiles(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -800,6 +800,8 @@
|
||||
<Compile Include="Traits\Render\WithChargeAnimation.cs" />
|
||||
<Compile Include="Traits\Render\WithChargeOverlay.cs" />
|
||||
<Compile Include="WebServices.cs" />
|
||||
<Compile Include="Traits\Conditions\GrantConditionOnLineBuildDirection.cs" />
|
||||
<Compile Include="Traits\Conditions\LineBuildSegmentExternalCondition.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Target Name="AfterBuild">
|
||||
|
||||
@@ -22,6 +22,9 @@ namespace OpenRA.Mods.Common.Orders
|
||||
{
|
||||
public class PlaceBuildingOrderGenerator : IOrderGenerator
|
||||
{
|
||||
[Flags]
|
||||
enum CellType { Valid = 0, Invalid = 1, LineBuild = 2 }
|
||||
|
||||
readonly ProductionQueue queue;
|
||||
readonly string building;
|
||||
readonly BuildingInfo buildingInfo;
|
||||
@@ -62,6 +65,15 @@ namespace OpenRA.Mods.Common.Orders
|
||||
buildingInfluence = world.WorldActor.Trait<BuildingInfluence>();
|
||||
}
|
||||
|
||||
CellType MakeCellType(bool valid, bool lineBuild = false)
|
||||
{
|
||||
var cell = valid ? CellType.Valid : CellType.Invalid;
|
||||
if (lineBuild)
|
||||
cell |= CellType.LineBuild;
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
public IEnumerable<Order> Order(World world, CPos cell, int2 worldPixel, MouseInput mi)
|
||||
{
|
||||
if (mi.Button == MouseButton.Right)
|
||||
@@ -160,7 +172,7 @@ namespace OpenRA.Mods.Common.Orders
|
||||
foreach (var r in dec.Render(wr, world, actorInfo, offset))
|
||||
yield return r;
|
||||
|
||||
var cells = new Dictionary<CPos, bool>();
|
||||
var cells = new Dictionary<CPos, CellType>();
|
||||
|
||||
var plugInfo = rules.Actors[building].TraitInfoOrDefault<PlugInfo>();
|
||||
if (plugInfo != null)
|
||||
@@ -168,7 +180,7 @@ namespace OpenRA.Mods.Common.Orders
|
||||
if (buildingInfo.Dimensions.X != 1 || buildingInfo.Dimensions.Y != 1)
|
||||
throw new InvalidOperationException("Plug requires a 1x1 sized Building");
|
||||
|
||||
cells.Add(topLeft, AcceptsPlug(topLeft, plugInfo));
|
||||
cells.Add(topLeft, MakeCellType(AcceptsPlug(topLeft, plugInfo)));
|
||||
}
|
||||
else if (rules.Actors[building].HasTraitInfo<LineBuildInfo>())
|
||||
{
|
||||
@@ -178,9 +190,9 @@ namespace OpenRA.Mods.Common.Orders
|
||||
|
||||
if (!Game.GetModifierKeys().HasModifier(Modifiers.Shift))
|
||||
foreach (var t in BuildingUtils.GetLineBuildCells(world, topLeft, building, buildingInfo))
|
||||
cells.Add(t, buildingInfo.IsCloseEnoughToBase(world, world.LocalPlayer, building, t));
|
||||
else
|
||||
cells.Add(topLeft, buildingInfo.IsCloseEnoughToBase(world, world.LocalPlayer, building, topLeft));
|
||||
cells.Add(t.First, MakeCellType(buildingInfo.IsCloseEnoughToBase(world, world.LocalPlayer, building, t.First), true));
|
||||
|
||||
cells[topLeft] = MakeCellType(buildingInfo.IsCloseEnoughToBase(world, world.LocalPlayer, building, topLeft));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -211,14 +223,16 @@ namespace OpenRA.Mods.Common.Orders
|
||||
var res = world.WorldActor.Trait<ResourceLayer>();
|
||||
var isCloseEnough = buildingInfo.IsCloseEnoughToBase(world, world.LocalPlayer, building, topLeft);
|
||||
foreach (var t in FootprintUtils.Tiles(rules, building, buildingInfo, topLeft))
|
||||
cells.Add(t, isCloseEnough && world.IsCellBuildable(t, buildingInfo) && res.GetResource(t) == null);
|
||||
cells.Add(t, MakeCellType(isCloseEnough && world.IsCellBuildable(t, buildingInfo) && res.GetResource(t) == null));
|
||||
}
|
||||
|
||||
var pal = wr.Palette(placeBuildingInfo.Palette);
|
||||
var cellPalette = wr.Palette(placeBuildingInfo.Palette);
|
||||
var linePalette = wr.Palette(placeBuildingInfo.LineBuildSegmentPalette);
|
||||
var topLeftPos = world.Map.CenterOfCell(topLeft);
|
||||
foreach (var c in cells)
|
||||
{
|
||||
var tile = c.Value ? buildOk : buildBlocked;
|
||||
var tile = !c.Value.HasFlag(CellType.Invalid) ? buildOk : buildBlocked;
|
||||
var pal = c.Value.HasFlag(CellType.LineBuild) ? linePalette : cellPalette;
|
||||
var pos = world.Map.CenterOfCell(c.Key);
|
||||
yield return new SpriteRenderable(tile, pos, new WVec(0, 0, topLeftPos.Z - pos.Z),
|
||||
-511, pal, 1f, true);
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
@@ -48,18 +49,20 @@ namespace OpenRA.Mods.Common.Traits
|
||||
world.IsCellBuildable(t, building, toIgnore));
|
||||
}
|
||||
|
||||
public static IEnumerable<CPos> GetLineBuildCells(World world, CPos location, string name, BuildingInfo bi)
|
||||
public static IEnumerable<Pair<CPos, Actor>> GetLineBuildCells(World world, CPos location, string name, BuildingInfo bi)
|
||||
{
|
||||
var lbi = world.Map.Rules.Actors[name].TraitInfo<LineBuildInfo>();
|
||||
var topLeft = location; // 1x1 assumption!
|
||||
|
||||
if (world.IsCellBuildable(topLeft, bi))
|
||||
yield return topLeft;
|
||||
yield return Pair.New<CPos, Actor>(topLeft, null);
|
||||
|
||||
// Start at place location, search outwards
|
||||
// TODO: First make it work, then make it nice
|
||||
var vecs = new[] { new CVec(1, 0), new CVec(0, 1), new CVec(-1, 0), new CVec(0, -1) };
|
||||
int[] dirs = { 0, 0, 0, 0 };
|
||||
Actor[] connectors = { null, null, null, null };
|
||||
|
||||
for (var d = 0; d < 4; d++)
|
||||
{
|
||||
for (var i = 1; i < lbi.Range; i++)
|
||||
@@ -72,17 +75,17 @@ namespace OpenRA.Mods.Common.Traits
|
||||
continue; // Cell is empty; continue search
|
||||
|
||||
// Cell contains an actor. Is it the type we want?
|
||||
var hasConnector = world.ActorMap.GetActorsAt(cell)
|
||||
.Any(a => a.Info.TraitInfos<LineBuildNodeInfo>()
|
||||
connectors[d] = world.ActorMap.GetActorsAt(cell)
|
||||
.FirstOrDefault(a => a.Info.TraitInfos<LineBuildNodeInfo>()
|
||||
.Any(info => info.Types.Overlaps(lbi.NodeTypes) && info.Connections.Contains(vecs[d])));
|
||||
|
||||
dirs[d] = hasConnector ? i : -1;
|
||||
dirs[d] = connectors[d] != null ? i : -1;
|
||||
}
|
||||
|
||||
// Place intermediate-line sections
|
||||
if (dirs[d] > 0)
|
||||
for (var i = 1; i < dirs[d]; i++)
|
||||
yield return topLeft + i * vecs[d];
|
||||
yield return Pair.New(topLeft + i * vecs[d], connectors[d]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,19 +10,116 @@
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
public enum LineBuildDirection { Unset, X, Y }
|
||||
public class LineBuildDirectionInit : IActorInit<LineBuildDirection>
|
||||
{
|
||||
[FieldFromYamlKey] readonly LineBuildDirection value = LineBuildDirection.Unset;
|
||||
public LineBuildDirectionInit() { }
|
||||
public LineBuildDirectionInit(LineBuildDirection init) { value = init; }
|
||||
public LineBuildDirection Value(World world) { return value; }
|
||||
}
|
||||
|
||||
public class LineBuildParentInit : IActorInit<Actor[]>
|
||||
{
|
||||
[FieldFromYamlKey] public readonly string[] ParentNames = new string[0];
|
||||
readonly Actor[] parents = null;
|
||||
|
||||
public LineBuildParentInit() { }
|
||||
public LineBuildParentInit(Actor[] init) { parents = init; }
|
||||
public Actor[] Value(World world)
|
||||
{
|
||||
if (parents != null)
|
||||
return parents;
|
||||
|
||||
var sma = world.WorldActor.Trait<SpawnMapActors>();
|
||||
return ParentNames.Select(n => sma.Actors[n]).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public interface INotifyLineBuildSegmentsChanged
|
||||
{
|
||||
void SegmentAdded(Actor self, Actor segment);
|
||||
void SegmentRemoved(Actor self, Actor segment);
|
||||
}
|
||||
|
||||
[Desc("Place the second actor in line to build more of the same at once (used for walls).")]
|
||||
public class LineBuildInfo : TraitInfo<LineBuild>
|
||||
public class LineBuildInfo : ITraitInfo
|
||||
{
|
||||
[Desc("The maximum allowed length of the line.")]
|
||||
public readonly int Range = 5;
|
||||
|
||||
[Desc("LineBuildNode 'Types' to attach to.")]
|
||||
public readonly HashSet<string> NodeTypes = new HashSet<string> { "wall" };
|
||||
|
||||
[ActorReference(typeof(LineBuildInfo))]
|
||||
[Desc("Actor type for line-built segments (defaults to same actor).")]
|
||||
public readonly string SegmentType = null;
|
||||
|
||||
[Desc("Delete generated segments when destroyed or sold.")]
|
||||
public readonly bool SegmentsRequireNode = false;
|
||||
|
||||
public object Create(ActorInitializer init) { return new LineBuild(init, this); }
|
||||
}
|
||||
|
||||
public class LineBuild { }
|
||||
public class LineBuild : INotifyKilled, INotifyAddedToWorld, INotifyRemovedFromWorld, INotifyLineBuildSegmentsChanged
|
||||
{
|
||||
readonly LineBuildInfo info;
|
||||
readonly Actor[] parentNodes = new Actor[0];
|
||||
HashSet<Actor> segments;
|
||||
|
||||
public LineBuild(ActorInitializer init, LineBuildInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
if (init.Contains<LineBuildParentInit>())
|
||||
parentNodes = init.Get<LineBuildParentInit>().Value(init.World);
|
||||
}
|
||||
|
||||
void INotifyLineBuildSegmentsChanged.SegmentAdded(Actor self, Actor segment)
|
||||
{
|
||||
if (segments == null)
|
||||
segments = new HashSet<Actor>();
|
||||
|
||||
segments.Add(segment);
|
||||
}
|
||||
|
||||
void INotifyLineBuildSegmentsChanged.SegmentRemoved(Actor self, Actor segment)
|
||||
{
|
||||
if (segments == null)
|
||||
return;
|
||||
|
||||
segments.Remove(segment);
|
||||
}
|
||||
|
||||
void INotifyAddedToWorld.AddedToWorld(Actor self)
|
||||
{
|
||||
foreach (var parent in parentNodes)
|
||||
if (!parent.Disposed)
|
||||
foreach (var n in parent.TraitsImplementing<INotifyLineBuildSegmentsChanged>())
|
||||
n.SegmentAdded(parent, self);
|
||||
}
|
||||
|
||||
void INotifyRemovedFromWorld.RemovedFromWorld(Actor self)
|
||||
{
|
||||
foreach (var parent in parentNodes)
|
||||
if (!parent.Disposed)
|
||||
foreach (var n in parent.TraitsImplementing<INotifyLineBuildSegmentsChanged>())
|
||||
n.SegmentRemoved(parent, self);
|
||||
|
||||
if (info.SegmentsRequireNode && segments != null)
|
||||
foreach (var s in segments)
|
||||
s.Dispose();
|
||||
}
|
||||
|
||||
void INotifyKilled.Killed(Actor self, AttackInfo e)
|
||||
{
|
||||
if (info.SegmentsRequireNode && segments != null)
|
||||
foreach (var s in segments)
|
||||
s.Kill(e.Attacker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Applies a condition to the actor when it is disabled.",
|
||||
"This is a temporary shim to help migration away from the legacy IDisable code")]
|
||||
public class GrantConditionOnDisabledInfo : ITraitInfo, Requires<HealthInfo>
|
||||
public class GrantConditionOnDisabledInfo : ITraitInfo
|
||||
{
|
||||
[FieldLoader.Require]
|
||||
[GrantedConditionReference]
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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
|
||||
{
|
||||
public class GrantConditionOnLineBuildDirectionInfo : ITraitInfo, Requires<LineBuildInfo>
|
||||
{
|
||||
[FieldLoader.Require]
|
||||
[GrantedConditionReference]
|
||||
[Desc("Condition to grant.")]
|
||||
public readonly string Condition = null;
|
||||
|
||||
[FieldLoader.Require]
|
||||
[Desc("Line build direction to trigger the condition.")]
|
||||
public readonly LineBuildDirection Direction = LineBuildDirection.X;
|
||||
|
||||
public object Create(ActorInitializer init) { return new GrantConditionOnLineBuildDirection(init, this); }
|
||||
}
|
||||
|
||||
public class GrantConditionOnLineBuildDirection : INotifyCreated
|
||||
{
|
||||
readonly GrantConditionOnLineBuildDirectionInfo info;
|
||||
readonly LineBuildDirection direction;
|
||||
|
||||
public GrantConditionOnLineBuildDirection(ActorInitializer init, GrantConditionOnLineBuildDirectionInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
direction = init.Get<LineBuildDirectionInit>().Value(init.World);
|
||||
}
|
||||
|
||||
void INotifyCreated.Created(Actor self)
|
||||
{
|
||||
if (direction != info.Direction)
|
||||
return;
|
||||
|
||||
var conditionManager = self.TraitOrDefault<ConditionManager>();
|
||||
if (conditionManager != null && !string.IsNullOrEmpty(info.Condition))
|
||||
conditionManager.GrantCondition(self, info.Condition);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Applies a condition to connected line build segments.")]
|
||||
public class LineBuildSegmentExternalConditionInfo : ConditionalTraitInfo, Requires<LineBuildInfo>
|
||||
{
|
||||
[FieldLoader.Require]
|
||||
[Desc("The condition to apply. Must be included in the target actor's ExternalConditions list.")]
|
||||
public readonly string Condition = null;
|
||||
|
||||
public override object Create(ActorInitializer init) { return new LineBuildSegmentExternalCondition(init.Self, this); }
|
||||
}
|
||||
|
||||
public class LineBuildSegmentExternalCondition : ConditionalTrait<LineBuildSegmentExternalConditionInfo>, INotifyLineBuildSegmentsChanged
|
||||
{
|
||||
readonly HashSet<Actor> segments = new HashSet<Actor>();
|
||||
readonly Dictionary<Actor, int> tokens = new Dictionary<Actor, int>();
|
||||
|
||||
public LineBuildSegmentExternalCondition(Actor self, LineBuildSegmentExternalConditionInfo info)
|
||||
: base(info) { }
|
||||
|
||||
void GrantCondition(Actor self, Actor segment)
|
||||
{
|
||||
if (tokens.ContainsKey(segment))
|
||||
return;
|
||||
|
||||
var external = segment.TraitsImplementing<ExternalCondition>()
|
||||
.FirstOrDefault(t => t.Info.Condition == Info.Condition && t.CanGrantCondition(segment, self));
|
||||
|
||||
if (external != null)
|
||||
tokens[segment] = external.GrantCondition(segment, self);
|
||||
}
|
||||
|
||||
void RevokeCondition(Actor self, Actor segment)
|
||||
{
|
||||
int token;
|
||||
if (!tokens.TryGetValue(segment, out token))
|
||||
return;
|
||||
|
||||
tokens.Remove(segment);
|
||||
if (segment.Disposed)
|
||||
return;
|
||||
|
||||
foreach (var external in segment.TraitsImplementing<ExternalCondition>())
|
||||
external.TryRevokeCondition(segment, self, token);
|
||||
}
|
||||
|
||||
void INotifyLineBuildSegmentsChanged.SegmentAdded(Actor self, Actor segment)
|
||||
{
|
||||
segments.Add(segment);
|
||||
if (!IsTraitDisabled)
|
||||
GrantCondition(self, segment);
|
||||
}
|
||||
|
||||
void INotifyLineBuildSegmentsChanged.SegmentRemoved(Actor self, Actor segment)
|
||||
{
|
||||
if (!IsTraitDisabled)
|
||||
RevokeCondition(self, segment);
|
||||
segments.Remove(segment);
|
||||
}
|
||||
|
||||
protected override void TraitEnabled(Actor self)
|
||||
{
|
||||
foreach (var s in segments)
|
||||
GrantCondition(self, s);
|
||||
}
|
||||
|
||||
protected override void TraitDisabled(Actor self)
|
||||
{
|
||||
foreach (var s in segments)
|
||||
RevokeCondition(self, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,9 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[Desc("Palette to use for rendering the placement sprite.")]
|
||||
[PaletteReference] public readonly string Palette = TileSet.TerrainPaletteInternalName;
|
||||
|
||||
[Desc("Palette to use for rendering the placement sprite for line build segments.")]
|
||||
[PaletteReference] public readonly string LineBuildSegmentPalette = TileSet.TerrainPaletteInternalName;
|
||||
|
||||
[Desc("Play NewOptionsNotification this many ticks after building placement.")]
|
||||
public readonly int NewOptionsNotificationDelay = 10;
|
||||
|
||||
@@ -74,21 +77,35 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
if (os == "LineBuild")
|
||||
{
|
||||
var playSounds = true;
|
||||
// Build the parent actor first
|
||||
var placed = w.CreateActor(order.TargetString, new TypeDictionary
|
||||
{
|
||||
new LocationInit(order.TargetLocation),
|
||||
new OwnerInit(order.Player),
|
||||
new FactionInit(faction),
|
||||
});
|
||||
|
||||
foreach (var s in buildingInfo.BuildSounds)
|
||||
Game.Sound.PlayToPlayer(SoundType.World, order.Player, s, placed.CenterPosition);
|
||||
|
||||
// Build the connection segments
|
||||
var segmentType = unit.TraitInfo<LineBuildInfo>().SegmentType;
|
||||
if (string.IsNullOrEmpty(segmentType))
|
||||
segmentType = order.TargetString;
|
||||
|
||||
foreach (var t in BuildingUtils.GetLineBuildCells(w, order.TargetLocation, order.TargetString, buildingInfo))
|
||||
{
|
||||
var building = w.CreateActor(order.TargetString, new TypeDictionary
|
||||
if (t.First == order.TargetLocation)
|
||||
continue;
|
||||
|
||||
w.CreateActor(t.First == order.TargetLocation ? order.TargetString : segmentType, new TypeDictionary
|
||||
{
|
||||
new LocationInit(t),
|
||||
new LocationInit(t.First),
|
||||
new OwnerInit(order.Player),
|
||||
new FactionInit(faction)
|
||||
new FactionInit(faction),
|
||||
new LineBuildDirectionInit(t.First.X == order.TargetLocation.X ? LineBuildDirection.Y : LineBuildDirection.X),
|
||||
new LineBuildParentInit(new[] { t.Second, placed })
|
||||
});
|
||||
|
||||
if (playSounds)
|
||||
foreach (var s in buildingInfo.BuildSounds)
|
||||
Game.Sound.PlayToPlayer(SoundType.World, order.Player, s, building.CenterPosition);
|
||||
|
||||
playSounds = false;
|
||||
}
|
||||
}
|
||||
else if (os == "PlacePlug")
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Traits;
|
||||
|
||||
@@ -20,36 +21,79 @@ namespace OpenRA.Mods.Common.Traits.Render
|
||||
[Desc("Sequence name to use")]
|
||||
[SequenceReference] public readonly string Sequence = "make";
|
||||
|
||||
[GrantedConditionReference]
|
||||
[Desc("The condition to grant to self while the make animation is playing.")]
|
||||
public readonly string Condition = null;
|
||||
|
||||
public object Create(ActorInitializer init) { return new WithMakeAnimation(init, this); }
|
||||
}
|
||||
|
||||
public class WithMakeAnimation
|
||||
public class WithMakeAnimation : INotifyCreated
|
||||
{
|
||||
readonly WithMakeAnimationInfo info;
|
||||
readonly WithSpriteBody wsb;
|
||||
|
||||
ConditionManager conditionManager;
|
||||
int token = ConditionManager.InvalidConditionToken;
|
||||
|
||||
public WithMakeAnimation(ActorInitializer init, WithMakeAnimationInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
var self = init.Self;
|
||||
wsb = self.Trait<WithSpriteBody>();
|
||||
}
|
||||
|
||||
void INotifyCreated.Created(Actor self)
|
||||
{
|
||||
conditionManager = self.TraitOrDefault<ConditionManager>();
|
||||
var building = self.TraitOrDefault<Building>();
|
||||
if (building != null && !building.SkipMakeAnimation)
|
||||
Forward(self, () => building.NotifyBuildingComplete(self));
|
||||
}
|
||||
|
||||
void Forward(Actor self, Action onComplete)
|
||||
{
|
||||
if (conditionManager != null && !string.IsNullOrEmpty(info.Condition) && token == ConditionManager.InvalidConditionToken)
|
||||
token = conditionManager.GrantCondition(self, info.Condition);
|
||||
|
||||
wsb.PlayCustomAnimation(self, info.Sequence, () =>
|
||||
{
|
||||
wsb.PlayCustomAnimation(self, info.Sequence, () =>
|
||||
{
|
||||
building.NotifyBuildingComplete(self);
|
||||
});
|
||||
}
|
||||
if (token != ConditionManager.InvalidConditionToken)
|
||||
token = conditionManager.RevokeCondition(self, token);
|
||||
|
||||
// TODO: Rewrite this to use a trait notification for save game support
|
||||
onComplete();
|
||||
});
|
||||
}
|
||||
|
||||
void Reverse(Actor self, Action onComplete)
|
||||
{
|
||||
if (conditionManager != null && !string.IsNullOrEmpty(info.Condition) && token == ConditionManager.InvalidConditionToken)
|
||||
token = conditionManager.GrantCondition(self, info.Condition);
|
||||
|
||||
wsb.PlayCustomAnimationBackwards(self, info.Sequence, () =>
|
||||
{
|
||||
if (token != ConditionManager.InvalidConditionToken)
|
||||
token = conditionManager.RevokeCondition(self, token);
|
||||
|
||||
// TODO: Rewrite this to use a trait notification for save game support
|
||||
onComplete();
|
||||
});
|
||||
}
|
||||
|
||||
public void Reverse(Actor self, Activity activity, bool queued = true)
|
||||
{
|
||||
wsb.PlayCustomAnimationBackwards(self, info.Sequence, () =>
|
||||
Reverse(self, () =>
|
||||
{
|
||||
// avoids visual glitches as we wait for the actor to get destroyed
|
||||
// HACK: The actor remains alive and active for one tick before the followup activity
|
||||
// (sell/transform/etc) runs. This causes visual glitches that we attempt to minimize
|
||||
// by forcing the animation to frame 0 and regranting the make condition.
|
||||
// These workarounds will break the actor if the followup activity doesn't dispose it!
|
||||
wsb.DefaultAnimation.PlayFetchIndex(info.Sequence, () => 0);
|
||||
|
||||
if (conditionManager != null && !string.IsNullOrEmpty(info.Condition))
|
||||
token = conditionManager.GrantCondition(self, info.Condition);
|
||||
|
||||
self.QueueActivity(queued, activity);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -159,6 +159,8 @@ namespace OpenRA.Mods.Common.Traits.Render
|
||||
{
|
||||
UpdateNeighbours(self);
|
||||
}
|
||||
|
||||
protected override void TraitEnabled(Actor self) { dirty = true; }
|
||||
}
|
||||
|
||||
public class RuntimeNeighbourInit : IActorInit<Dictionary<CPos, string[]>>, ISuppressInitExport
|
||||
|
||||
@@ -75,6 +75,7 @@ NAAPWR:
|
||||
PowerTooltip:
|
||||
SelectionDecorations:
|
||||
VisualBounds: 100, 74, 0, -12
|
||||
ProvidesPrerequisite@buildingname:
|
||||
|
||||
NAHAND:
|
||||
Inherits: ^Building
|
||||
|
||||
@@ -41,6 +41,111 @@ NAGATE_B:
|
||||
Tooltip:
|
||||
Name: Nod Gate
|
||||
|
||||
NAPOST:
|
||||
Inherits: ^Building
|
||||
Buildable:
|
||||
Queue: Defense
|
||||
BuildPaletteOrder: 150
|
||||
Prerequisites: naapwr, ~structures.nod
|
||||
Description: Stops infantry and blocks enemy fire.\nCan NOT be crushed by tanks.
|
||||
Valued:
|
||||
Cost: 200
|
||||
Tooltip:
|
||||
Name: Laser Fence
|
||||
Health:
|
||||
HP: 300
|
||||
Armor:
|
||||
Type: Concrete
|
||||
LineBuild:
|
||||
Range: 10
|
||||
NodeTypes: laserfencenode
|
||||
SegmentType: nafnce
|
||||
SegmentsRequireNode: true
|
||||
WithMakeAnimation:
|
||||
Condition: make-animation-playing
|
||||
Selectable:
|
||||
Bounds: 42, 44, 0, -12
|
||||
LineBuildNode:
|
||||
Types: laserfencenode
|
||||
Power:
|
||||
Amount: -25
|
||||
RevealsShroud:
|
||||
Range: 4c0
|
||||
WithIdleOverlay@LIGHTS:
|
||||
RequiresCondition: !disabled
|
||||
Sequence: lights
|
||||
WithIdleOverlay@CHAINOFLIGHTS:
|
||||
RequiresCondition: !disabled
|
||||
Sequence: chainoflights
|
||||
CanPowerDown:
|
||||
IndicatorPalette: mouse
|
||||
PowerupSpeech: EnablePower
|
||||
PowerdownSpeech: DisablePower
|
||||
RequiresPower:
|
||||
GrantConditionOnDisabled:
|
||||
Condition: disabled
|
||||
LineBuildSegmentExternalCondition:
|
||||
RequiresCondition: !disabled && !make-animation-playing
|
||||
Condition: active-posts
|
||||
|
||||
NAFNCE:
|
||||
Inherits: ^Wall
|
||||
Valued:
|
||||
Cost: 50
|
||||
CustomSellValue:
|
||||
Value: 0
|
||||
Tooltip:
|
||||
Name: Laser Fence
|
||||
LineBuild:
|
||||
NodeTypes: laserfence
|
||||
LineBuildNode:
|
||||
Types: laserfence
|
||||
-Crushable:
|
||||
-Sellable:
|
||||
-Targetable:
|
||||
-Building:
|
||||
EnergyWall:
|
||||
ActiveCondition: active-posts == 2
|
||||
Weapon: LaserFence
|
||||
GrantConditionOnLineBuildDirection@X:
|
||||
Direction: X
|
||||
Condition: laserfence-direction-x
|
||||
GrantConditionOnLineBuildDirection@Y:
|
||||
Direction: Y
|
||||
Condition: laserfence-direction-y
|
||||
-WithWallSpriteBody:
|
||||
CustomSelectionSize:
|
||||
CustomBounds: 48, 24
|
||||
ExternalCondition@ACTIVE:
|
||||
Condition: active-posts
|
||||
WithWallSpriteBody@XENABLED:
|
||||
RequiresCondition: laserfence-direction-x && active-posts == 2
|
||||
Type: laserfence
|
||||
Sequence: enabled-x
|
||||
WithWallSpriteBody@YENABLED:
|
||||
RequiresCondition: laserfence-direction-y && active-posts == 2
|
||||
Type: laserfence
|
||||
Sequence: enabled-y
|
||||
WithSpriteBody@XDISABLED:
|
||||
RequiresCondition: laserfence-direction-x && active-posts < 2
|
||||
Sequence: disabled-x
|
||||
WithSpriteBody@YDISABLED:
|
||||
RequiresCondition: laserfence-direction-y && active-posts < 2
|
||||
Sequence: disabled-y
|
||||
BlocksProjectiles:
|
||||
RequiresCondition: active-posts == 2
|
||||
Height: 640
|
||||
DamageMultiplier: # Prevent all normal damage, but still allows direct kills from the post
|
||||
Modifier: 0
|
||||
Explodes:
|
||||
Weapon: BuildingExplosions
|
||||
EmptyWeapon: BuildingExplosions
|
||||
Type: Footprint
|
||||
ThrowsShrapnel@SMALL:
|
||||
Weapons: SmallDebris
|
||||
Pieces: 0, 1
|
||||
Range: 2c0, 5c0
|
||||
|
||||
NALASR:
|
||||
Inherits: ^Defense
|
||||
Valued:
|
||||
@@ -89,7 +194,7 @@ NAOBEL:
|
||||
Name: Obelisk of Light
|
||||
Buildable:
|
||||
Queue: Defense
|
||||
BuildPaletteOrder: 150
|
||||
BuildPaletteOrder: 160
|
||||
Prerequisites: natech, ~structures.nod, ~techlevel.high
|
||||
Description: Advanced base defense.\nRequires power to operate.\n Strong vs Ground units\n Weak vs Aircraft
|
||||
Building:
|
||||
@@ -170,7 +275,7 @@ NASTLH:
|
||||
Name: Stealth Generator
|
||||
Buildable:
|
||||
Queue: Defense
|
||||
BuildPaletteOrder: 160
|
||||
BuildPaletteOrder: 170
|
||||
Prerequisites: proc, natech, ~structures.nod, ~techlevel.high
|
||||
Description: Generates a cloaking field\nto hide your forces from the enemy.
|
||||
Building:
|
||||
@@ -211,7 +316,7 @@ NAMISL:
|
||||
Inherits: ^Building
|
||||
Buildable:
|
||||
Queue: Defense
|
||||
BuildPaletteOrder: 190
|
||||
BuildPaletteOrder: 180
|
||||
Prerequisites: natech, ~structures.nod, ~techlevel.superweapons
|
||||
BuildLimit: 1
|
||||
Description: Launches a devastating missile\nat a target location.\nRequires power to operate.\nMaximum 1 can be built.
|
||||
@@ -272,7 +377,7 @@ NAWAST:
|
||||
Name: Waste Refinery
|
||||
Buildable:
|
||||
Queue: Defense
|
||||
BuildPaletteOrder: 200
|
||||
BuildPaletteOrder: 190
|
||||
Prerequisites: namisl, ~structures.nod, ~techlevel.superweapons
|
||||
BuildLimit: 1
|
||||
Description: Processes Veins\ninto useable resources.\nMaximum 1 can be built.
|
||||
|
||||
@@ -113,6 +113,10 @@
|
||||
Name: placebuilding
|
||||
BasePalette: ra
|
||||
Alpha: 0.75
|
||||
PaletteFromPaletteWithAlpha@placelinesegment:
|
||||
Name: placelinesegment
|
||||
BasePalette: ra
|
||||
Alpha: 0.4
|
||||
TSShroudPalette@shroud:
|
||||
Name: shroud
|
||||
Type: Shroud
|
||||
|
||||
@@ -33,6 +33,7 @@ Player:
|
||||
SpeedUp: True
|
||||
PlaceBuilding:
|
||||
Palette: placebuilding
|
||||
LineBuildSegmentPalette: placelinesegment
|
||||
SupportPowerManager:
|
||||
ScriptTriggers:
|
||||
MissionObjectives:
|
||||
|
||||
@@ -189,4 +189,5 @@ EditorWorld:
|
||||
EditorResourceLayer:
|
||||
EditorSelectionLayer:
|
||||
Palette: placebuilding
|
||||
LineBuildSegmentPalette: placelinesegment
|
||||
LoadWidgetAtGameStart:
|
||||
|
||||
@@ -21,6 +21,7 @@ poweroff:
|
||||
Length: *
|
||||
Tick: 160
|
||||
ZOffset: 2047
|
||||
Offset: 0, -12
|
||||
|
||||
allyrepair:
|
||||
repair: wrench
|
||||
|
||||
@@ -859,6 +859,90 @@ nagate_b:
|
||||
UseTilesetCode: false
|
||||
-DepthSprite:
|
||||
|
||||
napost:
|
||||
Defaults:
|
||||
Offset: 0, -12, 13
|
||||
UseTilesetCode: true
|
||||
DepthSprite: isodepth.shp
|
||||
idle:
|
||||
Start: 0
|
||||
ShadowStart: 3
|
||||
damaged-idle:
|
||||
Start: 1
|
||||
ShadowStart: 4
|
||||
lights: napost_b
|
||||
Start: 0
|
||||
Length: 7
|
||||
ShadowStart: 14
|
||||
Tick: 80
|
||||
damaged-lights: napost_b
|
||||
Start: 7
|
||||
Length: 7
|
||||
ShadowStart: 21
|
||||
Tick: 80
|
||||
chainoflights: napost_a
|
||||
Start: 0
|
||||
Length: 12
|
||||
ShadowStart: 24
|
||||
Tick: 80
|
||||
damaged-chainoflights: napost_a
|
||||
Start: 12
|
||||
Length: 12
|
||||
ShadowStart: 36
|
||||
Tick: 80
|
||||
dead:
|
||||
Start: 2
|
||||
Tick: 400
|
||||
ShadowStart: 5
|
||||
make: napostmk
|
||||
Start: 0
|
||||
Length: 12
|
||||
ShadowStart: 12
|
||||
emp-overlay: emp_fx01
|
||||
Length: *
|
||||
Offset: 0, 0, 25
|
||||
UseTilesetCode: false
|
||||
ZOffset: 512
|
||||
BlendMode: Additive
|
||||
-DepthSprite:
|
||||
icon: lasricon
|
||||
Offset: 0, 0
|
||||
UseTilesetCode: false
|
||||
-DepthSprite:
|
||||
|
||||
nafnce:
|
||||
Defaults:
|
||||
Offset: 0, -12, 13
|
||||
UseTilesetCode: true
|
||||
DepthSprite: isodepth.shp
|
||||
idle:
|
||||
Length: 1
|
||||
disabled-x:
|
||||
Start: 8
|
||||
Length: 1
|
||||
Offset: 0, -12, 1
|
||||
ZRamp: 1
|
||||
-DepthSprite:
|
||||
disabled-y:
|
||||
Start: 12
|
||||
Length: 1
|
||||
Offset: 0, -12, 1
|
||||
ZRamp: 1
|
||||
-DepthSprite:
|
||||
enabled-x:
|
||||
Frames: 3,3,1,1,3,3,1,1,2,2,0,0,2,2,0,0
|
||||
Length: 16
|
||||
enabled-y:
|
||||
Frames: 7,5,7,5,6,4,6,4,7,5,7,5,6,4,6,4
|
||||
Length: 16
|
||||
emp-overlay: emp_fx01
|
||||
Length: *
|
||||
Offset: 0, 0, 25
|
||||
UseTilesetCode: false
|
||||
ZOffset: 512
|
||||
BlendMode: Additive
|
||||
-DepthSprite:
|
||||
|
||||
nawall:
|
||||
Defaults:
|
||||
Offset: 0, -12, 12
|
||||
|
||||
@@ -198,3 +198,10 @@ TurretLaserFire:
|
||||
SecondaryBeamColor: FF000030
|
||||
Warhead@1Dam: SpreadDamage
|
||||
Damage: 30
|
||||
|
||||
LaserFence:
|
||||
Projectile: InstantHit
|
||||
Warhead@1Dam: TargetDamage
|
||||
DebugOverlayColor: FF0000
|
||||
Damage: 99999
|
||||
DamageTypes: FireDeath
|
||||
|
||||
Reference in New Issue
Block a user