From 1bd408948db2825a666eaf9734e6a67c07f65c5d Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sun, 28 Sep 2014 18:06:48 +1300 Subject: [PATCH 1/6] Add WithRangeCircle trait for drawing arbitrary ranges. --- OpenRA.Mods.RA/OpenRA.Mods.RA.csproj | 1 + OpenRA.Mods.RA/WithRangeCircle.cs | 76 ++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 OpenRA.Mods.RA/WithRangeCircle.cs diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index 7b40c1c6fb..be236ea26a 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -550,6 +550,7 @@ + diff --git a/OpenRA.Mods.RA/WithRangeCircle.cs b/OpenRA.Mods.RA/WithRangeCircle.cs new file mode 100644 index 0000000000..c6f885f26a --- /dev/null +++ b/OpenRA.Mods.RA/WithRangeCircle.cs @@ -0,0 +1,76 @@ +#region Copyright & License Information +/* + * Copyright 2007-2014 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.Drawing; +using OpenRA.Graphics; +using OpenRA.Mods.Common.Graphics; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA +{ + [Desc("Renders an arbitrary circle when selected or placing a structure")] + class WithRangeCircleInfo : ITraitInfo, IPlaceBuildingDecoration + { + [Desc("Type of range circle. used to decide which circles to draw on other structures during building placement.")] + public readonly string Type = null; + + [Desc("Color of the circle")] + public readonly Color Color = Color.FromArgb(128, Color.White); + + [Desc("Range of the circle")] + public readonly WRange Range = WRange.Zero; + + public IEnumerable Render(WorldRenderer wr, World w, ActorInfo ai, WPos centerPosition) + { + yield return new RangeCircleRenderable( + centerPosition, + Range, + 0, + Color, + Color.FromArgb(96, Color.Black) + ); + + foreach (var a in w.ActorsWithTrait()) + if (a.Actor.Owner == a.Actor.World.LocalPlayer && a.Trait.Info.Type == Type) + foreach (var r in a.Trait.RenderAfterWorld(wr)) + yield return r; + } + + public object Create(ActorInitializer init) { return new WithRangeCircle(init.self, this); } + } + + class WithRangeCircle : IPostRenderSelection + { + public readonly WithRangeCircleInfo Info; + readonly Actor self; + + public WithRangeCircle(Actor self, WithRangeCircleInfo info) + { + this.self = self; + Info = info; + } + + public IEnumerable RenderAfterWorld(WorldRenderer wr) + { + if (self.Owner != self.World.LocalPlayer) + yield break; + + yield return new RangeCircleRenderable( + self.CenterPosition, + Info.Range, + 0, + Info.Color, + Color.FromArgb(96, Color.Black) + ); + } + } +} + From 0652d338f8bf0638ceba5d5cc0e246b654eb8938 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sun, 28 Sep 2014 16:44:55 +1300 Subject: [PATCH 2/6] Add proximity trigger plumbing. --- OpenRA.Game/Traits/World/ActorMap.cs | 159 +++++++++++++++++++++++++-- 1 file changed, 151 insertions(+), 8 deletions(-) diff --git a/OpenRA.Game/Traits/World/ActorMap.cs b/OpenRA.Game/Traits/World/ActorMap.cs index c560624033..585bc0322b 100644 --- a/OpenRA.Game/Traits/World/ActorMap.cs +++ b/OpenRA.Game/Traits/World/ActorMap.cs @@ -33,6 +33,12 @@ namespace OpenRA.Traits public Actor Actor; } + class Bin + { + public readonly List Actors = new List(); + public readonly List ProximityTriggers = new List(); + } + class CellTrigger { public readonly int Id; @@ -79,15 +85,84 @@ namespace OpenRA.Traits } } + class ProximityTrigger : IDisposable + { + public readonly int Id; + public WPos Position { get; private set; } + public WRange Range { get; private set; } + + public WPos TopLeft { get; private set; } + public WPos BottomRight { get; private set; } + + public bool Dirty; + + Action onActorEntered; + Action onActorExited; + + IEnumerable currentActors = Enumerable.Empty(); + + public ProximityTrigger(int id, WPos pos, WRange range, Action onActorEntered, Action onActorExited) + { + Id = id; + + this.onActorEntered = onActorEntered; + this.onActorExited = onActorExited; + + Update(pos, range); + } + + public void Update(WPos newPos, WRange newRange) + { + Position = newPos; + Range = newRange; + + var offset = new WVec(newRange, newRange, WRange.Zero); + TopLeft = newPos - offset; + BottomRight = newPos + offset; + + Dirty = true; + } + + public void Tick(ActorMap am) + { + if (!Dirty) + return; + + var oldActors = currentActors; + var delta = new WVec(Range, Range, WRange.Zero); + currentActors = am.ActorsInBox(Position - delta, Position + delta) + .Where(a => (a.CenterPosition - Position).HorizontalLengthSquared < Range.Range * Range.Range) + .ToList(); + + var entered = currentActors.Except(oldActors); + var exited = oldActors.Except(currentActors); + + foreach (var a in entered) + onActorEntered(a); + + foreach (var a in exited) + onActorExited(a); + + Dirty = false; + } + + public void Dispose() + { + foreach (var a in currentActors) + onActorExited(a); + } + } + readonly ActorMapInfo info; readonly Map map; readonly Dictionary cellTriggers = new Dictionary(); readonly Dictionary> cellTriggerInfluence = new Dictionary>(); + readonly Dictionary proximityTriggers = new Dictionary(); int nextTriggerId; readonly CellLayer influence; - readonly List[] actors; + readonly Bin[] bins; readonly int rows, cols; // Position updates are done in one pass @@ -104,10 +179,10 @@ namespace OpenRA.Traits cols = world.Map.MapSize.X / info.BinSize + 1; rows = world.Map.MapSize.Y / info.BinSize + 1; - actors = new List[rows * cols]; + bins = new Bin[rows * cols]; for (var j = 0; j < rows; j++) for (var i = 0; i < cols; i++) - actors[j * cols + i] = new List(); + bins[j * cols + i] = new Bin(); // Cache this delegate so it does not have to be allocated repeatedly. actorShouldBeRemoved = removeActorPosition.Contains; @@ -263,8 +338,13 @@ namespace OpenRA.Traits { // Position updates are done in one pass // to ensure consistency during a tick - foreach (var bin in actors) - bin.RemoveAll(actorShouldBeRemoved); + foreach (var bin in bins) + { + var removed = bin.Actors.RemoveAll(actorShouldBeRemoved); + if (removed > 0) + foreach (var t in bin.ProximityTriggers) + t.Dirty = true; + } removeActorPosition.Clear(); @@ -273,13 +353,20 @@ namespace OpenRA.Traits var pos = a.OccupiesSpace.CenterPosition; var i = (pos.X / info.BinSize).Clamp(0, cols - 1); var j = (pos.Y / info.BinSize).Clamp(0, rows - 1); - actors[j * cols + i].Add(a); + var bin = bins[j * cols + i]; + + bin.Actors.Add(a); + foreach (var t in bin.ProximityTriggers) + t.Dirty = true; } addActorPosition.Clear(); foreach (var t in cellTriggers) t.Value.Tick(this); + + foreach (var t in proximityTriggers) + t.Value.Tick(this); } public int AddCellTrigger(CPos[] cells, Action onEntry, Action onExit) @@ -317,6 +404,45 @@ namespace OpenRA.Traits } } + public int AddProximityTrigger(WPos pos, WRange range, Action onEntry, Action onExit) + { + var id = nextTriggerId++; + var t = new ProximityTrigger(id, pos, range, onEntry, onExit); + proximityTriggers.Add(id, t); + + foreach (var bin in BinsInBox(t.TopLeft, t.BottomRight)) + bin.ProximityTriggers.Add(t); + + return id; + } + + public void RemoveProximityTrigger(int id) + { + ProximityTrigger t; + if (!proximityTriggers.TryGetValue(id, out t)) + return; + + foreach (var bin in BinsInBox(t.TopLeft, t.BottomRight)) + bin.ProximityTriggers.Remove(t); + + t.Dispose(); + } + + public void UpdateProximityTrigger(int id, WPos newPos, WRange newRange) + { + ProximityTrigger t; + if (!proximityTriggers.TryGetValue(id, out t)) + return; + + foreach (var bin in BinsInBox(t.TopLeft, t.BottomRight)) + bin.ProximityTriggers.Remove(t); + + t.Update(newPos, newRange); + + foreach (var bin in BinsInBox(t.TopLeft, t.BottomRight)) + bin.ProximityTriggers.Add(t); + } + public void AddPosition(Actor a, IOccupySpace ios) { UpdatePosition(a, ios); @@ -333,6 +459,23 @@ namespace OpenRA.Traits addActorPosition.Add(a); } + IEnumerable BinsInBox(WPos a, WPos b) + { + var left = Math.Min(a.X, b.X); + var top = Math.Min(a.Y, b.Y); + var right = Math.Max(a.X, b.X); + var bottom = Math.Max(a.Y, b.Y); + var i1 = (left / info.BinSize).Clamp(0, cols - 1); + var i2 = (right / info.BinSize).Clamp(0, cols - 1); + var j1 = (top / info.BinSize).Clamp(0, rows - 1); + var j2 = (bottom / info.BinSize).Clamp(0, rows - 1); + + for (var j = j1; j <= j2; j++) + for (var i = i1; i <= i2; i++) + yield return bins[j * cols + i]; + } + + public IEnumerable ActorsInBox(WPos a, WPos b) { var left = Math.Min(a.X, b.X); @@ -348,7 +491,7 @@ namespace OpenRA.Traits { for (var i = i1; i <= i2; i++) { - foreach (var actor in actors[j * cols + i]) + foreach (var actor in bins[j * cols + i].Actors) { if (actor.IsInWorld) { @@ -363,7 +506,7 @@ namespace OpenRA.Traits public IEnumerable ActorsInWorld() { - return actors.SelectMany(bin => bin.Where(actor => actor.IsInWorld)); + return bins.SelectMany(bin => bin.Actors.Where(actor => actor.IsInWorld)); } } } From da592f0a25cae9c4521fb58a5160c004182f4908 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sun, 28 Sep 2014 17:03:48 +1300 Subject: [PATCH 3/6] Add UpgradeActorsNear trait for passive upgrades. --- OpenRA.Mods.RA/OpenRA.Mods.RA.csproj | 1 + OpenRA.Mods.RA/UpgradeActorsNear.cs | 117 +++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 OpenRA.Mods.RA/UpgradeActorsNear.cs diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index be236ea26a..ce55cb81f7 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -550,6 +550,7 @@ + diff --git a/OpenRA.Mods.RA/UpgradeActorsNear.cs b/OpenRA.Mods.RA/UpgradeActorsNear.cs new file mode 100644 index 0000000000..070805eb57 --- /dev/null +++ b/OpenRA.Mods.RA/UpgradeActorsNear.cs @@ -0,0 +1,117 @@ +#region Copyright & License Information +/* + * Copyright 2007-2014 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; +using System.Collections.Generic; +using OpenRA.GameRules; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA +{ + [Desc("Applies an upgrade to actors within a specified range.")] + public class UpgradeActorsNearInfo : ITraitInfo + { + [Desc("The upgrades to grant.")] + public readonly string[] Upgrades = { }; + + [Desc("The range to search for actors to upgrade.")] + public readonly WRange Range = WRange.FromCells(3); + + [Desc("What diplomatic stances are affected.")] + public readonly Stance ValidStances = Stance.Ally; + + [Desc("Grant the upgrades apply to this actor.")] + public readonly bool AffectsParent = false; + + public readonly string EnableSound = null; + public readonly string DisableSound = null; + + public object Create(ActorInitializer init) { return new UpgradeActorsNear(init.self, this); } + } + + public class UpgradeActorsNear : ITick, INotifyAddedToWorld, INotifyRemovedFromWorld + { + readonly UpgradeActorsNearInfo info; + readonly Actor self; + + int proximityTrigger; + WPos cachedPosition; + WRange cachedRange; + WRange desiredRange; + + bool cachedDisabled = true; + + public UpgradeActorsNear(Actor self, UpgradeActorsNearInfo info) + { + this.info = info; + this.self = self; + cachedRange = info.Range; + } + + public void AddedToWorld(Actor self) + { + cachedPosition = self.CenterPosition; + proximityTrigger = self.World.ActorMap.AddProximityTrigger(cachedPosition, cachedRange, ActorEntered, ActorExited); + } + + public void RemovedFromWorld(Actor self) + { + self.World.ActorMap.RemoveProximityTrigger(proximityTrigger); + } + + public void Tick(Actor self) + { + var disabled = self.IsDisabled(); + + if (cachedDisabled != disabled) + { + Sound.Play(disabled ? info.DisableSound : info.EnableSound, self.CenterPosition); + desiredRange = disabled ? WRange.Zero : info.Range; + cachedDisabled = disabled; + } + + if (self.CenterPosition != cachedPosition || desiredRange != cachedRange) + { + cachedPosition = self.CenterPosition; + cachedRange = desiredRange; + self.World.ActorMap.UpdateProximityTrigger(proximityTrigger, cachedPosition, cachedRange); + } + } + + void ActorEntered(Actor a) + { + if (a.Destroyed) + return; + + if (a == self && !info.AffectsParent) + return; + + var stance = self.Owner.Stances[a.Owner]; + if (!info.ValidStances.HasFlag(stance)) + return; + + var um = a.TraitOrDefault(); + if (um != null) + foreach (var u in info.Upgrades) + um.GrantUpgrade(a, u, this); + } + + void ActorExited(Actor a) + { + if (a == self || a.Destroyed) + return; + + var um = a.TraitOrDefault(); + if (um != null) + foreach (var u in info.Upgrades) + um.RevokeUpgrade(a, u, this); + } + } +} From 1a5ff440d15cd8df9d5a899d84dff135cd9a9eaa Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sun, 28 Sep 2014 17:55:47 +1300 Subject: [PATCH 4/6] Add the stealth generator to TS. --- mods/ts/rules/defaults.yaml | 22 ++++++++++++++++++ mods/ts/rules/structures.yaml | 38 +++++++++++++++++++++++++++++++ mods/ts/sequences/structures.yaml | 25 ++++++++++++++++++++ 3 files changed, 85 insertions(+) diff --git a/mods/ts/rules/defaults.yaml b/mods/ts/rules/defaults.yaml index 6b06fa2593..ab217777a8 100644 --- a/mods/ts/rules/defaults.yaml +++ b/mods/ts/rules/defaults.yaml @@ -44,6 +44,11 @@ Demolishable: ScriptTriggers: WithMakeAnimation: + UpgradeManager: + Cloak@CLOAKGENERATOR: + RequiresUpgrade: cloakgenerator + InitialDelay: 0 + CloakDelay: 90 ^Wall: AppearsOnRadar: @@ -82,6 +87,11 @@ LuaScriptEvents: Demolishable: ScriptTriggers: + UpgradeManager: + Cloak@CLOAKGENERATOR: + RequiresUpgrade: cloakgenerator + InitialDelay: 0 + CloakDelay: 90 ^Infantry: AppearsOnRadar: @@ -158,6 +168,10 @@ DeathSound: Zapped DeathTypes: 6 UpgradeManager: + Cloak@CLOAKGENERATOR: + RequiresUpgrade: cloakgenerator + InitialDelay: 0 + CloakDelay: 90 ^CivilianInfantry: Inherits: ^Infantry @@ -253,6 +267,10 @@ TimedUpgradeBar@EMPDISABLE: Upgrade: empdisable Color: 255,255,255 + Cloak@CLOAKGENERATOR: + RequiresUpgrade: cloakgenerator + InitialDelay: 0 + CloakDelay: 90 ^Tank: AppearsOnRadar: @@ -321,6 +339,10 @@ TimedUpgradeBar@EMPDISABLE: Upgrade: empdisable Color: 255,255,255 + Cloak@CLOAKGENERATOR: + RequiresUpgrade: cloakgenerator + InitialDelay: 0 + CloakDelay: 90 ^Helicopter: AppearsOnRadar: diff --git a/mods/ts/rules/structures.yaml b/mods/ts/rules/structures.yaml index 31c567107d..ba0d336dc0 100644 --- a/mods/ts/rules/structures.yaml +++ b/mods/ts/rules/structures.yaml @@ -903,6 +903,44 @@ GADEPT: Power: Amount: -30 +NASTLH: + Inherits: ^Building + Valued: + Cost: 2500 + Tooltip: + Name: Stealth Generator + Description: Generates a cloaking field + Buildable: + BuildPaletteOrder: 80 + Prerequisites: proc,natech + Owner: nod + Queue: Building + Building: + Footprint: xxx xxx + Dimensions: 3,2 + Health: + HP: 600 + Armor: + Type: Wood + RevealsShroud: + Range: 6c0 + WithIdleOverlay@pulse: + Sequence: pulse + PauseOnLowPower: true + WithRangeCircle: + Range: 12c0 + Type: cloakgenerator + Power: + Amount: -350 + RequiresPower: + CanPowerDown: + UpgradeActorsNear: + Upgrades: cloakgenerator + Range: 12c0 + EnableSound: cloak5.aud + DisableSound: cloak5.aud + AffectsParent: true + #TODO: Placeholder, replace with Component Tower + Vulcan Upgrade GAVULC: Inherits: ^Building diff --git a/mods/ts/sequences/structures.yaml b/mods/ts/sequences/structures.yaml index 5229507fc0..43efeb6f25 100644 --- a/mods/ts/sequences/structures.yaml +++ b/mods/ts/sequences/structures.yaml @@ -718,6 +718,31 @@ napuls: icon: pulsicon Start: 0 +nastlh: + idle: ntstlh + Start: 0 + ShadowStart: 3 + damaged-idle: ntstlh + Start: 1 + ShadowStart: 4 + critical-idle: ntstlh + Start: 2 + ShadowStart: 5 + pulse: ntstlh_a + Start: 0 + Length: 4 + Tick: 480 + damaged-pulse: ntstlh_a + Start: 4 + Length: 4 + Tick: 480 + make: ntstlhmk + Start: 0 + Length: 18 + ShadowStart: 20 + icon: clckicon + Start: 0 + gavulc: idle: gtctwr Start: 0 From bf2bc0c02ecde654fe56c723fd1fe90844ba8810 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sat, 4 Oct 2014 13:50:21 +1300 Subject: [PATCH 5/6] Extend INotifyOtherProduction to all actors. --- OpenRA.Mods.RA/Buildings/ClonesProducedUnits.cs | 2 +- OpenRA.Mods.RA/Production.cs | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/OpenRA.Mods.RA/Buildings/ClonesProducedUnits.cs b/OpenRA.Mods.RA/Buildings/ClonesProducedUnits.cs index f2caec1fce..6ba3647341 100644 --- a/OpenRA.Mods.RA/Buildings/ClonesProducedUnits.cs +++ b/OpenRA.Mods.RA/Buildings/ClonesProducedUnits.cs @@ -37,7 +37,7 @@ namespace OpenRA.Mods.RA public void UnitProducedByOther(Actor self, Actor producer, Actor produced) { // No recursive cloning! - if (producer.HasTrait()) + if (producer.Owner != self.Owner || producer.HasTrait()) return; var ci = produced.Info.Traits.GetOrDefault(); diff --git a/OpenRA.Mods.RA/Production.cs b/OpenRA.Mods.RA/Production.cs index e460773c50..75ce88b514 100755 --- a/OpenRA.Mods.RA/Production.cs +++ b/OpenRA.Mods.RA/Production.cs @@ -98,9 +98,7 @@ namespace OpenRA.Mods.RA foreach (var t in self.TraitsImplementing()) t.UnitProduced(self, newUnit, exit); - var notifyOthers = self.World.ActorsWithTrait() - .Where(a => a.Actor.Owner == self.Owner); - + var notifyOthers = self.World.ActorsWithTrait(); foreach (var notify in notifyOthers) notify.Trait.UnitProducedByOther(notify.Actor, self, newUnit); From 564a709a3ab0af67fa52e79036aac023c7ba991d Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sat, 4 Oct 2014 13:57:07 +1300 Subject: [PATCH 6/6] Apply upgrades to units produced within range immediately. --- OpenRA.Mods.RA/UpgradeActorsNear.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/OpenRA.Mods.RA/UpgradeActorsNear.cs b/OpenRA.Mods.RA/UpgradeActorsNear.cs index 070805eb57..3a099ef2c4 100644 --- a/OpenRA.Mods.RA/UpgradeActorsNear.cs +++ b/OpenRA.Mods.RA/UpgradeActorsNear.cs @@ -36,7 +36,7 @@ namespace OpenRA.Mods.RA public object Create(ActorInitializer init) { return new UpgradeActorsNear(init.self, this); } } - public class UpgradeActorsNear : ITick, INotifyAddedToWorld, INotifyRemovedFromWorld + public class UpgradeActorsNear : ITick, INotifyAddedToWorld, INotifyRemovedFromWorld, INotifyOtherProduction { readonly UpgradeActorsNearInfo info; readonly Actor self; @@ -103,6 +103,22 @@ namespace OpenRA.Mods.RA um.GrantUpgrade(a, u, this); } + public void UnitProducedByOther(Actor self, Actor producer, Actor produced) + { + // Work around for actors produced within the region not triggering until the second tick + if ((produced.CenterPosition - self.CenterPosition).HorizontalLengthSquared <= info.Range.Range * info.Range.Range) + { + var stance = self.Owner.Stances[produced.Owner]; + if (!info.ValidStances.HasFlag(stance)) + return; + + var um = produced.TraitOrDefault(); + if (um != null) + foreach (var u in info.Upgrades) + um.GrantTimedUpgrade(produced, u, 1); + } + } + void ActorExited(Actor a) { if (a == self || a.Destroyed)