Merge pull request #13025 from pchote/laser-fence

Implement TS Laser Fences
This commit is contained in:
atlimit8
2017-04-22 19:41:36 -05:00
committed by GitHub
20 changed files with 674 additions and 39 deletions

View File

@@ -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" />

View 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);
}
}
}

View File

@@ -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">

View File

@@ -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);

View File

@@ -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]);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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]

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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")

View File

@@ -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);
});
}

View File

@@ -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

View File

@@ -75,6 +75,7 @@ NAAPWR:
PowerTooltip:
SelectionDecorations:
VisualBounds: 100, 74, 0, -12
ProvidesPrerequisite@buildingname:
NAHAND:
Inherits: ^Building

View File

@@ -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.

View File

@@ -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

View File

@@ -33,6 +33,7 @@ Player:
SpeedUp: True
PlaceBuilding:
Palette: placebuilding
LineBuildSegmentPalette: placelinesegment
SupportPowerManager:
ScriptTriggers:
MissionObjectives:

View File

@@ -189,4 +189,5 @@ EditorWorld:
EditorResourceLayer:
EditorSelectionLayer:
Palette: placebuilding
LineBuildSegmentPalette: placelinesegment
LoadWidgetAtGameStart:

View File

@@ -21,6 +21,7 @@ poweroff:
Length: *
Tick: 160
ZOffset: 2047
Offset: 0, -12
allyrepair:
repair: wrench

View File

@@ -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

View File

@@ -198,3 +198,10 @@ TurretLaserFire:
SecondaryBeamColor: FF000030
Warhead@1Dam: SpreadDamage
Damage: 30
LaserFence:
Projectile: InstantHit
Warhead@1Dam: TargetDamage
DebugOverlayColor: FF0000
Damage: 99999
DamageTypes: FireDeath