From ac53bc502ea5cdb28d0f239d7afae21c4ccce3d4 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sun, 26 Mar 2017 15:45:58 +0100 Subject: [PATCH 01/12] Remove bogus Requires from GrantConditionOnDisabled. --- .../Traits/Conditions/GrantConditionOnDisabled.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenRA.Mods.Common/Traits/Conditions/GrantConditionOnDisabled.cs b/OpenRA.Mods.Common/Traits/Conditions/GrantConditionOnDisabled.cs index 0b5c16226d..aa778ba772 100644 --- a/OpenRA.Mods.Common/Traits/Conditions/GrantConditionOnDisabled.cs +++ b/OpenRA.Mods.Common/Traits/Conditions/GrantConditionOnDisabled.cs @@ -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 + public class GrantConditionOnDisabledInfo : ITraitInfo { [FieldLoader.Require] [GrantedConditionReference] From 227655f9aaa70ab5450b0f20a67fb130e12b3984 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sun, 26 Mar 2017 20:49:38 +0100 Subject: [PATCH 02/12] Fix wall artwork interaction with conditions. --- OpenRA.Mods.Common/Traits/Render/WithWallSpriteBody.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OpenRA.Mods.Common/Traits/Render/WithWallSpriteBody.cs b/OpenRA.Mods.Common/Traits/Render/WithWallSpriteBody.cs index 179522dfc7..eb17f98b95 100644 --- a/OpenRA.Mods.Common/Traits/Render/WithWallSpriteBody.cs +++ b/OpenRA.Mods.Common/Traits/Render/WithWallSpriteBody.cs @@ -159,6 +159,8 @@ namespace OpenRA.Mods.Common.Traits.Render { UpdateNeighbours(self); } + + protected override void TraitEnabled(Actor self) { dirty = true; } } public class RuntimeNeighbourInit : IActorInit>, ISuppressInitExport From 2b9b9bb9848d1c7ed19c8c63e783d476b8b5c1e6 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sun, 26 Mar 2017 22:21:25 +0100 Subject: [PATCH 03/12] Fix powerdown indicator position. --- mods/ts/sequences/misc.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/mods/ts/sequences/misc.yaml b/mods/ts/sequences/misc.yaml index 7f9e95257c..555bdaca70 100644 --- a/mods/ts/sequences/misc.yaml +++ b/mods/ts/sequences/misc.yaml @@ -21,6 +21,7 @@ poweroff: Length: * Tick: 160 ZOffset: 2047 + Offset: 0, -12 allyrepair: repair: wrench From 62603b324da51b6b7a276f6360d3fcef2ba1a02f Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sat, 22 Apr 2017 15:59:28 +0000 Subject: [PATCH 04/12] Add support for granting a condition while the make animation plays. --- .../Traits/Render/WithMakeAnimation.cs | 60 ++++++++++++++++--- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/OpenRA.Mods.Common/Traits/Render/WithMakeAnimation.cs b/OpenRA.Mods.Common/Traits/Render/WithMakeAnimation.cs index 0382db908c..be03da03e6 100644 --- a/OpenRA.Mods.Common/Traits/Render/WithMakeAnimation.cs +++ b/OpenRA.Mods.Common/Traits/Render/WithMakeAnimation.cs @@ -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(); + } + void INotifyCreated.Created(Actor self) + { + conditionManager = self.TraitOrDefault(); var building = self.TraitOrDefault(); 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); }); } From 304e3ef9f9c0fca18913c8984ec57a28c26c95df Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sat, 25 Mar 2017 17:19:38 +0000 Subject: [PATCH 05/12] Distinguish between line build nodes and segments. --- .../Orders/PlaceBuildingOrderGenerator.cs | 2 +- .../Traits/Buildings/BuildingUtils.cs | 15 ++-- .../Traits/Buildings/LineBuild.cs | 76 ++++++++++++++++++- .../Traits/Player/PlaceBuilding.cs | 33 +++++--- 4 files changed, 107 insertions(+), 19 deletions(-) diff --git a/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs b/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs index babeb42abf..41661f75c3 100644 --- a/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs +++ b/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs @@ -178,7 +178,7 @@ 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)); + cells.Add(t.First, buildingInfo.IsCloseEnoughToBase(world, world.LocalPlayer, building, t.First)); else cells.Add(topLeft, buildingInfo.IsCloseEnoughToBase(world, world.LocalPlayer, building, topLeft)); } diff --git a/OpenRA.Mods.Common/Traits/Buildings/BuildingUtils.cs b/OpenRA.Mods.Common/Traits/Buildings/BuildingUtils.cs index 64807449b1..8bae043e68 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/BuildingUtils.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/BuildingUtils.cs @@ -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 GetLineBuildCells(World world, CPos location, string name, BuildingInfo bi) + public static IEnumerable> GetLineBuildCells(World world, CPos location, string name, BuildingInfo bi) { var lbi = world.Map.Rules.Actors[name].TraitInfo(); var topLeft = location; // 1x1 assumption! if (world.IsCellBuildable(topLeft, bi)) - yield return topLeft; + yield return Pair.New(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() + connectors[d] = world.ActorMap.GetActorsAt(cell) + .FirstOrDefault(a => a.Info.TraitInfos() .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]); } } } diff --git a/OpenRA.Mods.Common/Traits/Buildings/LineBuild.cs b/OpenRA.Mods.Common/Traits/Buildings/LineBuild.cs index 255f0a312b..a66fed98d5 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/LineBuild.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/LineBuild.cs @@ -10,19 +10,91 @@ #endregion using System.Collections.Generic; +using System.Linq; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { + public class LineBuildParentInit : IActorInit + { + [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(); + 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 + 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 NodeTypes = new HashSet { "wall" }; + + [ActorReference(typeof(LineBuildInfo))] + [Desc("Actor type for line-built segments (defaults to same actor).")] + public readonly string SegmentType = null; + + public object Create(ActorInitializer init) { return new LineBuild(init, this); } } - public class LineBuild { } + public class LineBuild : INotifyAddedToWorld, INotifyRemovedFromWorld, INotifyLineBuildSegmentsChanged + { + readonly Actor[] parentNodes = new Actor[0]; + HashSet segments; + + public LineBuild(ActorInitializer init, LineBuildInfo info) + { + if (init.Contains()) + parentNodes = init.Get().Value(init.World); + } + + void INotifyLineBuildSegmentsChanged.SegmentAdded(Actor self, Actor segment) + { + if (segments == null) + segments = new HashSet(); + + 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()) + n.SegmentAdded(parent, self); + } + + void INotifyRemovedFromWorld.RemovedFromWorld(Actor self) + { + foreach (var parent in parentNodes) + if (!parent.Disposed) + foreach (var n in parent.TraitsImplementing()) + n.SegmentRemoved(parent, self); + } + } } diff --git a/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs b/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs index a05b643d6a..57d36c582b 100644 --- a/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs +++ b/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs @@ -74,21 +74,34 @@ 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().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 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") From 227b80d6c2336e686c0f82d6bd15533dcd3d7f7c Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Mon, 27 Mar 2017 18:14:24 +0100 Subject: [PATCH 06/12] Add support for removing segments when parent nodes are removed. --- .../Traits/Buildings/LineBuild.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/OpenRA.Mods.Common/Traits/Buildings/LineBuild.cs b/OpenRA.Mods.Common/Traits/Buildings/LineBuild.cs index a66fed98d5..ee52a2dbd1 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/LineBuild.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/LineBuild.cs @@ -51,16 +51,21 @@ namespace OpenRA.Mods.Common.Traits [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 : INotifyAddedToWorld, INotifyRemovedFromWorld, INotifyLineBuildSegmentsChanged + public class LineBuild : INotifyKilled, INotifyAddedToWorld, INotifyRemovedFromWorld, INotifyLineBuildSegmentsChanged { + readonly LineBuildInfo info; readonly Actor[] parentNodes = new Actor[0]; HashSet segments; public LineBuild(ActorInitializer init, LineBuildInfo info) { + this.info = info; if (init.Contains()) parentNodes = init.Get().Value(init.World); } @@ -95,6 +100,17 @@ namespace OpenRA.Mods.Common.Traits if (!parent.Disposed) foreach (var n in parent.TraitsImplementing()) 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); } } } From a8bb03aa3431bfbf84b46b85a3396d5146d21095 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sun, 26 Mar 2017 21:15:29 +0000 Subject: [PATCH 07/12] Draw TS line-build cell previews with increased translucency. --- .../Orders/PlaceBuildingOrderGenerator.cs | 30 ++++++++++++++----- .../Traits/Player/PlaceBuilding.cs | 3 ++ mods/ts/rules/palettes.yaml | 4 +++ mods/ts/rules/player.yaml | 1 + mods/ts/rules/world.yaml | 1 + 5 files changed, 31 insertions(+), 8 deletions(-) diff --git a/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs b/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs index 41661f75c3..a25706fd36 100644 --- a/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs +++ b/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs @@ -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(); } + CellType MakeCellType(bool valid, bool lineBuild = false) + { + var cell = valid ? CellType.Valid : CellType.Invalid; + if (lineBuild) + cell |= CellType.LineBuild; + + return cell; + } + public IEnumerable 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(); + var cells = new Dictionary(); var plugInfo = rules.Actors[building].TraitInfoOrDefault(); 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()) { @@ -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.First, buildingInfo.IsCloseEnoughToBase(world, world.LocalPlayer, building, t.First)); - 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(); 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); diff --git a/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs b/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs index 57d36c582b..2fe669ee23 100644 --- a/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs +++ b/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs @@ -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; diff --git a/mods/ts/rules/palettes.yaml b/mods/ts/rules/palettes.yaml index 555467c58f..5c96fd2e1f 100644 --- a/mods/ts/rules/palettes.yaml +++ b/mods/ts/rules/palettes.yaml @@ -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 diff --git a/mods/ts/rules/player.yaml b/mods/ts/rules/player.yaml index e226adf553..75d30071d9 100644 --- a/mods/ts/rules/player.yaml +++ b/mods/ts/rules/player.yaml @@ -33,6 +33,7 @@ Player: SpeedUp: True PlaceBuilding: Palette: placebuilding + LineBuildSegmentPalette: placelinesegment SupportPowerManager: ScriptTriggers: MissionObjectives: diff --git a/mods/ts/rules/world.yaml b/mods/ts/rules/world.yaml index e1bc34cc61..131bcac616 100644 --- a/mods/ts/rules/world.yaml +++ b/mods/ts/rules/world.yaml @@ -189,4 +189,5 @@ EditorWorld: EditorResourceLayer: EditorSelectionLayer: Palette: placebuilding + LineBuildSegmentPalette: placelinesegment LoadWidgetAtGameStart: From f8af51643dc34850fa8845de5b36430fb0ed9549 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Mon, 27 Mar 2017 17:20:54 +0000 Subject: [PATCH 08/12] Add LineBuildSegmentExternalCondition. --- OpenRA.Mods.Common/OpenRA.Mods.Common.csproj | 1 + .../LineBuildSegmentExternalCondition.cs | 88 +++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 OpenRA.Mods.Common/Traits/Conditions/LineBuildSegmentExternalCondition.cs diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index 61f03891a0..1d672dff30 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -800,6 +800,7 @@ + diff --git a/OpenRA.Mods.Common/Traits/Conditions/LineBuildSegmentExternalCondition.cs b/OpenRA.Mods.Common/Traits/Conditions/LineBuildSegmentExternalCondition.cs new file mode 100644 index 0000000000..c78414ef0d --- /dev/null +++ b/OpenRA.Mods.Common/Traits/Conditions/LineBuildSegmentExternalCondition.cs @@ -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 + { + [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, INotifyLineBuildSegmentsChanged + { + readonly HashSet segments = new HashSet(); + readonly Dictionary tokens = new Dictionary(); + + public LineBuildSegmentExternalCondition(Actor self, LineBuildSegmentExternalConditionInfo info) + : base(info) { } + + void GrantCondition(Actor self, Actor segment) + { + if (tokens.ContainsKey(segment)) + return; + + var external = segment.TraitsImplementing() + .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()) + 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); + } + } +} From a83c0f96dddd5270eb70fac8317f5c13cc5fb169 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Mon, 27 Mar 2017 18:20:58 +0100 Subject: [PATCH 09/12] Add GrantConditionOnLineBuildDirection trait. --- OpenRA.Mods.Common/OpenRA.Mods.Common.csproj | 1 + .../Traits/Buildings/LineBuild.cs | 9 ++++ .../GrantConditionOnLineBuildDirection.cs | 51 +++++++++++++++++++ .../Traits/Player/PlaceBuilding.cs | 1 + 4 files changed, 62 insertions(+) create mode 100644 OpenRA.Mods.Common/Traits/Conditions/GrantConditionOnLineBuildDirection.cs diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index 1d672dff30..ce4c980fff 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -800,6 +800,7 @@ + diff --git a/OpenRA.Mods.Common/Traits/Buildings/LineBuild.cs b/OpenRA.Mods.Common/Traits/Buildings/LineBuild.cs index ee52a2dbd1..f31642e77c 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/LineBuild.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/LineBuild.cs @@ -15,6 +15,15 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { + public enum LineBuildDirection { Unset, X, Y } + public class LineBuildDirectionInit : IActorInit + { + [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 { [FieldFromYamlKey] public readonly string[] ParentNames = new string[0]; diff --git a/OpenRA.Mods.Common/Traits/Conditions/GrantConditionOnLineBuildDirection.cs b/OpenRA.Mods.Common/Traits/Conditions/GrantConditionOnLineBuildDirection.cs new file mode 100644 index 0000000000..e8ee3eaf8a --- /dev/null +++ b/OpenRA.Mods.Common/Traits/Conditions/GrantConditionOnLineBuildDirection.cs @@ -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 + { + [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().Value(init.World); + } + + void INotifyCreated.Created(Actor self) + { + if (direction != info.Direction) + return; + + var conditionManager = self.TraitOrDefault(); + if (conditionManager != null && !string.IsNullOrEmpty(info.Condition)) + conditionManager.GrantCondition(self, info.Condition); + } + } +} diff --git a/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs b/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs index 2fe669ee23..8f7eac28c3 100644 --- a/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs +++ b/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs @@ -103,6 +103,7 @@ namespace OpenRA.Mods.Common.Traits new LocationInit(t.First), new OwnerInit(order.Player), new FactionInit(faction), + new LineBuildDirectionInit(t.First.X == order.TargetLocation.X ? LineBuildDirection.Y : LineBuildDirection.X), new LineBuildParentInit(new[] { t.Second, placed }) }); } From 64896eb73dac78a9a9de561ef3900b1f528a26b0 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sun, 26 Mar 2017 18:27:11 +0000 Subject: [PATCH 10/12] Add EnergyWall trait. --- OpenRA.Mods.Cnc/OpenRA.Mods.Cnc.csproj | 1 + OpenRA.Mods.Cnc/Traits/EnergyWall.cs | 112 +++++++++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 OpenRA.Mods.Cnc/Traits/EnergyWall.cs diff --git a/OpenRA.Mods.Cnc/OpenRA.Mods.Cnc.csproj b/OpenRA.Mods.Cnc/OpenRA.Mods.Cnc.csproj index 6ba96ea6dc..b648538d9d 100644 --- a/OpenRA.Mods.Cnc/OpenRA.Mods.Cnc.csproj +++ b/OpenRA.Mods.Cnc/OpenRA.Mods.Cnc.csproj @@ -109,6 +109,7 @@ + diff --git a/OpenRA.Mods.Cnc/Traits/EnergyWall.cs b/OpenRA.Mods.Cnc/Traits/EnergyWall.cs new file mode 100644 index 0000000000..067c0fd88b --- /dev/null +++ b/OpenRA.Mods.Cnc/Traits/EnergyWall.cs @@ -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 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 GetVariableObservers() + { + if (info.ActiveCondition != null) + yield return new VariableObserver(ActiveConditionChanged, info.ActiveCondition.Variables); + } + + void ActiveConditionChanged(Actor self, IReadOnlyDictionary 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()); + } + } + + 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); + } + } +} From 0ddb63111723db70e4fc77f51e7ff962539392f4 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sat, 22 Apr 2017 16:15:57 +0000 Subject: [PATCH 11/12] Add TS Laser Fences. --- mods/ts/rules/nod-structures.yaml | 1 + mods/ts/rules/nod-support.yaml | 105 +++++++++++++++++++++++++++++ mods/ts/sequences/structures.yaml | 84 +++++++++++++++++++++++ mods/ts/weapons/energyweapons.yaml | 7 ++ 4 files changed, 197 insertions(+) diff --git a/mods/ts/rules/nod-structures.yaml b/mods/ts/rules/nod-structures.yaml index c7664a5d2e..3062ce436a 100644 --- a/mods/ts/rules/nod-structures.yaml +++ b/mods/ts/rules/nod-structures.yaml @@ -75,6 +75,7 @@ NAAPWR: PowerTooltip: SelectionDecorations: VisualBounds: 100, 74, 0, -12 + ProvidesPrerequisite@buildingname: NAHAND: Inherits: ^Building diff --git a/mods/ts/rules/nod-support.yaml b/mods/ts/rules/nod-support.yaml index 2ded7c09ab..19ad59ee08 100644 --- a/mods/ts/rules/nod-support.yaml +++ b/mods/ts/rules/nod-support.yaml @@ -41,6 +41,111 @@ NAGATE_B: Tooltip: Name: Nod Gate +NAPOST: + Inherits: ^Building + Buildable: + Queue: Defense + BuildPaletteOrder: 1001 + 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: diff --git a/mods/ts/sequences/structures.yaml b/mods/ts/sequences/structures.yaml index e4fe52fd68..b96c8a2d0f 100644 --- a/mods/ts/sequences/structures.yaml +++ b/mods/ts/sequences/structures.yaml @@ -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 diff --git a/mods/ts/weapons/energyweapons.yaml b/mods/ts/weapons/energyweapons.yaml index aae4a7e719..5f61791298 100644 --- a/mods/ts/weapons/energyweapons.yaml +++ b/mods/ts/weapons/energyweapons.yaml @@ -198,3 +198,10 @@ TurretLaserFire: SecondaryBeamColor: FF000030 Warhead@1Dam: SpreadDamage Damage: 30 + +LaserFence: + Projectile: InstantHit + Warhead@1Dam: TargetDamage + DebugOverlayColor: FF0000 + Damage: 99999 + DamageTypes: FireDeath From fcbdb147edef21ecc7dd5d9c5009b54aee1b1f79 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Mon, 17 Apr 2017 13:04:36 +0100 Subject: [PATCH 12/12] Update build icon order. --- mods/ts/rules/nod-support.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mods/ts/rules/nod-support.yaml b/mods/ts/rules/nod-support.yaml index 19ad59ee08..c7d6536aec 100644 --- a/mods/ts/rules/nod-support.yaml +++ b/mods/ts/rules/nod-support.yaml @@ -45,7 +45,7 @@ NAPOST: Inherits: ^Building Buildable: Queue: Defense - BuildPaletteOrder: 1001 + BuildPaletteOrder: 150 Prerequisites: naapwr, ~structures.nod Description: Stops infantry and blocks enemy fire.\nCan NOT be crushed by tanks. Valued: @@ -194,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: @@ -275,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: @@ -316,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. @@ -377,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.