From 1057f8ff3b05f0ac47f22c7935e9f497dbff26aa Mon Sep 17 00:00:00 2001 From: penev92 Date: Sun, 2 Nov 2014 14:17:52 +0200 Subject: [PATCH 1/5] Second implementation of Sandworms Fix tabs in AttackSwallow.cs --- OpenRA.Mods.D2k/AttackSwallow.cs | 54 ++++++++++ OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj | 12 +++ OpenRA.Mods.D2k/Sandworm.cs | 70 +++++++++++++ OpenRA.Mods.D2k/SwallowActor.cs | 136 +++++++++++++++++++++++++ OpenRA.Mods.D2k/WormManager.cs | 87 ++++++++++++++++ OpenRA.Mods.D2k/WormSpawner.cs | 24 +++++ OpenRA.Mods.RA/Attack/AttackBase.cs | 14 +-- OpenRA.Mods.RA/Attack/AttackWander.cs | 1 + OpenRA.Mods.RA/AutoTarget.cs | 12 ++- mods/d2k/bits/wormicon.shp | Bin 0 -> 2904 bytes mods/d2k/maps/shellmap/map.yaml | 9 +- mods/d2k/mod.yaml | 1 + mods/d2k/notifications.yaml | 2 + mods/d2k/rules/arrakis.yaml | 63 ++++++++++++ mods/d2k/rules/misc.yaml | 21 ---- mods/d2k/rules/world.yaml | 2 + mods/d2k/sequences/infantry.yaml | 18 +++- mods/d2k/voices.yaml | 8 +- 18 files changed, 500 insertions(+), 34 deletions(-) create mode 100644 OpenRA.Mods.D2k/AttackSwallow.cs create mode 100644 OpenRA.Mods.D2k/Sandworm.cs create mode 100644 OpenRA.Mods.D2k/SwallowActor.cs create mode 100644 OpenRA.Mods.D2k/WormManager.cs create mode 100644 OpenRA.Mods.D2k/WormSpawner.cs create mode 100644 mods/d2k/bits/wormicon.shp create mode 100644 mods/d2k/rules/arrakis.yaml diff --git a/OpenRA.Mods.D2k/AttackSwallow.cs b/OpenRA.Mods.D2k/AttackSwallow.cs new file mode 100644 index 0000000000..f0a2b6dcc7 --- /dev/null +++ b/OpenRA.Mods.D2k/AttackSwallow.cs @@ -0,0 +1,54 @@ +#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 OpenRA.Mods.RA; +using OpenRA.Traits; + +namespace OpenRA.Mods.D2k +{ + // TODO: This is a copy of AttackLeap. Maybe combine them in AttackMelee trait when the code is finalized? + [Desc("Sandworms use this attack model.")] + class AttackSwallowInfo : AttackFrontalInfo, Requires + { + public override object Create(ActorInitializer init) { return new AttackSwallow(init.self, this); } + } + class AttackSwallow : AttackFrontal + { + readonly Sandworm sandworm; + + public AttackSwallow(Actor self, AttackSwallowInfo attackSwallowInfo) + : base(self, attackSwallowInfo) + { + sandworm = self.Trait(); + } + + public override void DoAttack(Actor self, Target target) + { + // TODO: Worm should ignore Fremen as targets unless they are firing/being fired upon (even moving fremen do not attract worms) + + if (target.Type != TargetType.Actor || !CanAttack(self, target) || !sandworm.CanAttackAtLocation(self, target.Actor.Location)) + // this is so that the worm does not launch an attack against a target that has reached solid rock + { + self.CancelActivity(); + return; + } + + var a = ChooseArmamentForTarget(target); + if (a == null) + return; + + if (!target.IsInRange(self.CenterPosition, a.Weapon.Range)) + return; + + self.CancelActivity(); + self.QueueActivity(new SwallowActor(self, target.Actor, a.Weapon)); + } + } +} diff --git a/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj b/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj index 1cb2379b4f..ad6db3439f 100644 --- a/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj +++ b/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj @@ -68,6 +68,11 @@ + + + + + @@ -89,6 +94,13 @@ + + + + + + + diff --git a/OpenRA.Mods.D2k/Sandworm.cs b/OpenRA.Mods.D2k/Sandworm.cs new file mode 100644 index 0000000000..2bc5c08b38 --- /dev/null +++ b/OpenRA.Mods.D2k/Sandworm.cs @@ -0,0 +1,70 @@ +#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 OpenRA.Mods.RA; +using OpenRA.Mods.RA.Move; +using OpenRA.Mods.RA.Render; +using OpenRA.Traits; + +namespace OpenRA.Mods.D2k +{ + class SandwormInfo : Requires, Requires, IOccupySpaceInfo + { + readonly public int WanderMoveRadius = 20; + readonly public string WormSignNotification = "WormSign"; + + public object Create(ActorInitializer init) { return new Sandworm(this); } + } + + class Sandworm : INotifyIdle + { + int ticksIdle; + int effectiveMoveRadius; + readonly int maxMoveRadius; + + public Sandworm(SandwormInfo info) + { + maxMoveRadius = info.WanderMoveRadius; + effectiveMoveRadius = info.WanderMoveRadius; + + // TODO: Someone familiar with how the sounds work should fix this: + // TODO: This should not be here. It should be same as "Enemy unit sighted". + //Sound.PlayNotification(self.Owner, "Speech", info.WormSignNotification, self.Owner.Country.Race); + } + + // TODO: This copies AttackWander and builds on top of it. AttackWander should be revised. + public void TickIdle(Actor self) + { + var globalOffset = new WVec(0, -1024 * effectiveMoveRadius, 0).Rotate(WRot.FromFacing(self.World.SharedRandom.Next(255))); + var offset = new CVec(globalOffset.X/1024, globalOffset.Y/1024); + var targetlocation = self.Location + offset; + + if (!self.World.Map.Bounds.Contains(targetlocation.X, targetlocation.Y)) + { + // If MoveRadius is too big there might not be a valid cell to order the attack to (if actor is on a small island and can't leave) + if (++ticksIdle % 10 == 0) // completely random number + { + effectiveMoveRadius--; + } + return; // We'll be back the next tick; better to sit idle for a few seconds than prolongue this tick indefinitely with a loop + } + + self.World.IssueOrder(new Order("AttackMove", self, false) { TargetLocation = targetlocation }); + + ticksIdle = 0; + effectiveMoveRadius = maxMoveRadius; + } + + public bool CanAttackAtLocation(Actor self, CPos targetLocation) + { + return self.Trait().MovementSpeedForCell(self, targetLocation) != 0; + } + } +} diff --git a/OpenRA.Mods.D2k/SwallowActor.cs b/OpenRA.Mods.D2k/SwallowActor.cs new file mode 100644 index 0000000000..cb9c18ccec --- /dev/null +++ b/OpenRA.Mods.D2k/SwallowActor.cs @@ -0,0 +1,136 @@ +#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.Linq; +using OpenRA.GameRules; +using OpenRA.Mods.RA.Move; +using OpenRA.Mods.RA.Render; +using OpenRA.Traits; + +namespace OpenRA.Mods.D2k +{ + public enum AttackState { Burrowed, EmergingAboveGround, ReturningUndergrown } + + class SwallowActor : Activity + { + readonly Actor target; + readonly Mobile mobile; + readonly Sandworm sandworm; + readonly WeaponInfo weapon; + + int countdown; + AttackState stance = AttackState.Burrowed; + + // TODO: random numbers to make it look ok + [Desc("The number of ticks it takes to return underground.")] + const int ReturnTime = 60; + [Desc("The number of ticks it takes to get in place under the target to attack.")] + const int AttackTime = 30; + + public SwallowActor(Actor self, Actor target, WeaponInfo weapon) + { + if (!target.HasTrait()) + throw new InvalidOperationException("SwallowActor requires a target actor with the Mobile trait"); + + this.target = target; + this.weapon = weapon; + mobile = self.TraitOrDefault(); + sandworm = self.TraitOrDefault(); + countdown = AttackTime; + } + + bool WormAttack(Actor worm) + { + var targetLocation = target.Location; + + var lunch = worm.World.ActorMap.GetUnitsAt(targetLocation) + .Except(new[] { worm }) + .Where(t => weapon.IsValidAgainst(t, worm)); + if (!lunch.Any()) + return false; + + stance = AttackState.EmergingAboveGround; + + lunch.Do(t => t.World.AddFrameEndTask(_ => { t.World.Remove(t); t.Kill(t); })); // dispose of the evidence (we don't want husks) + + mobile.SetPosition(worm, targetLocation); + PlayAttackAnimation(worm); + + return true; + } + + public bool PlayAttackAnimation(Actor self) + { + var renderUnit = self.Trait(); + renderUnit.PlayCustomAnim(self, "sand"); + renderUnit.PlayCustomAnim(self, "mouth"); + + // TODO: Someone familiar with how the sounds work should fix this: + //Sound.PlayNotification(self.Owner, "Speech", "WormAttack", self.Owner.Country.Race); + + return true; + } + + public override Activity Tick(Actor self) + { + if (countdown > 0) + { + countdown--; + return this; + } + + if (stance == AttackState.ReturningUndergrown) // wait for the worm to get back underground + { + #region DisappearToMapEdge + + // More random numbers used for min and max bounds + var rand = self.World.SharedRandom.Next(200, 400); + if (rand % 2 == 0) // there is a 50-50 chance that the worm would just go away + { + self.CancelActivity(); + //self.World.WorldActor.QueueActivity(new DisappearToMapEdge(self, rand)); + self.World.AddFrameEndTask(w => w.Remove(self)); + var wormManager = self.World.WorldActor.TraitOrDefault(); + if (wormManager != null) + wormManager.DecreaseWorms(); + } + + #endregion + + // TODO: if the worm did not disappear, make the animation reappear here + + return NextActivity; + } + + if (stance == AttackState.Burrowed) // wait for the worm to get in position + { + // TODO: make the worm animation (currenty the lightning) disappear here + + // this is so that the worm cancels an attack against a target that has reached solid rock + if (sandworm == null || !sandworm.CanAttackAtLocation(self, target.Location)) + { + return NextActivity; + } + + var success = WormAttack(self); + if (!success) + { + return NextActivity; + } + + countdown = ReturnTime; + stance = AttackState.ReturningUndergrown; + } + + return this; + } + } +} diff --git a/OpenRA.Mods.D2k/WormManager.cs b/OpenRA.Mods.D2k/WormManager.cs new file mode 100644 index 0000000000..af36094fe7 --- /dev/null +++ b/OpenRA.Mods.D2k/WormManager.cs @@ -0,0 +1,87 @@ +#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.Linq; +using OpenRA.Primitives; +using OpenRA.Traits; + +namespace OpenRA.Mods.D2k +{ + [Desc("Controls the spawning of sandworms. Attach this to the world actor.")] + class WormManagerInfo : ITraitInfo + { + [Desc("Minimum number of worms")] + public readonly int Minimum = 1; + + [Desc("Maximum number of worms")] + public readonly int Maximum = 5; + + [Desc("Average time (seconds) between crate spawn")] + public readonly int SpawnInterval = 180; + + public readonly string WormSignature = "sandworm"; + public readonly string WormOwnerPlayer = "Creeps"; + + public object Create (ActorInitializer init) { return new WormManager(this, init.self); } + } + + class WormManager : ITick + { + int countdown; + int wormsPresent; + readonly WormManagerInfo info; + readonly Lazy spawnPoints; + + public WormManager(WormManagerInfo info, Actor self) + { + this.info = info; + spawnPoints = Exts.Lazy(() => self.World.ActorsWithTrait().Select(x => x.Actor).ToArray()); + } + + public void Tick(Actor self) + { + // TODO: Add a lobby option to disable worms just like crates + + if (--countdown > 0) + return; + + countdown = info.SpawnInterval * 25; + if (wormsPresent < info.Maximum) + SpawnWorm(self); + } + + private void SpawnWorm (Actor self) + { + var spawnLocation = GetRandomSpawnPosition(self); + self.World.AddFrameEndTask(w => + w.CreateActor(info.WormSignature, new TypeDictionary + { + new OwnerInit(w.Players.First(x => x.PlayerName == info.WormOwnerPlayer)), + new LocationInit(spawnLocation) + })); + wormsPresent++; + } + + private CPos GetRandomSpawnPosition(Actor self) + { + // TODO: This is here only for testing, while the maps don't have valid spawn points + if (!spawnPoints.Value.Any()) + return self.World.Map.ChooseRandomEdgeCell(self.World.SharedRandom); + + return spawnPoints.Value[self.World.SharedRandom.Next(0, spawnPoints.Value.Count() - 1)].Location; + } + + public void DecreaseWorms() + { + wormsPresent--; + } + } +} diff --git a/OpenRA.Mods.D2k/WormSpawner.cs b/OpenRA.Mods.D2k/WormSpawner.cs new file mode 100644 index 0000000000..99730e79b4 --- /dev/null +++ b/OpenRA.Mods.D2k/WormSpawner.cs @@ -0,0 +1,24 @@ +#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 OpenRA.Traits; + +namespace OpenRA.Mods.D2k +{ + [Desc("An actor with this trait indicates a valid spawn point for sandworms.")] + class WormSpawnerInfo : ITraitInfo + { + public object Create(ActorInitializer init) { return new WormSpawner(); } + } + + class WormSpawner + { + } +} diff --git a/OpenRA.Mods.RA/Attack/AttackBase.cs b/OpenRA.Mods.RA/Attack/AttackBase.cs index b96321daad..1e5971d7c9 100644 --- a/OpenRA.Mods.RA/Attack/AttackBase.cs +++ b/OpenRA.Mods.RA/Attack/AttackBase.cs @@ -25,6 +25,8 @@ namespace OpenRA.Mods.RA public readonly string Cursor = "attack"; public readonly string OutsideRangeCursor = "attackoutsiderange"; + [Desc("Does the attack type requires the attacker to enter the target's cell?")] + public readonly bool AttackRequiresEnteringCell = false; public abstract object Create(ActorInitializer init); } @@ -38,12 +40,12 @@ namespace OpenRA.Mods.RA protected Func> GetArmaments; readonly Actor self; - readonly AttackBaseInfo info; + public readonly AttackBaseInfo Info; public AttackBase(Actor self, AttackBaseInfo info) { this.self = self; - this.info = info; + Info = info; var armaments = Exts.Lazy(() => self.TraitsImplementing() .Where(a => info.Armaments.Contains(a.Info.Name))); @@ -179,8 +181,8 @@ namespace OpenRA.Mods.RA var a = ab.ChooseArmamentForTarget(target); cursor = a != null && !target.IsInRange(self.CenterPosition, a.Weapon.Range) - ? ab.info.OutsideRangeCursor - : ab.info.Cursor; + ? ab.Info.OutsideRangeCursor + : ab.Info.Cursor; if (target.Type == TargetType.Actor && target.Actor == self) return false; @@ -210,7 +212,7 @@ namespace OpenRA.Mods.RA IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue); - cursor = ab.info.Cursor; + cursor = ab.Info.Cursor; if (negativeDamage) return false; @@ -223,7 +225,7 @@ namespace OpenRA.Mods.RA var maxRange = ab.GetMaximumRange().Range; var targetRange = (self.World.Map.CenterOfCell(location) - self.CenterPosition).HorizontalLengthSquared; if (targetRange > maxRange * maxRange) - cursor = ab.info.OutsideRangeCursor; + cursor = ab.Info.OutsideRangeCursor; return true; } diff --git a/OpenRA.Mods.RA/Attack/AttackWander.cs b/OpenRA.Mods.RA/Attack/AttackWander.cs index bb173f1509..e48528e1cf 100644 --- a/OpenRA.Mods.RA/Attack/AttackWander.cs +++ b/OpenRA.Mods.RA/Attack/AttackWander.cs @@ -33,6 +33,7 @@ namespace OpenRA.Mods.RA public void TickIdle(Actor self) { var target = self.CenterPosition + new WVec(0, -1024*Info.MoveRadius, 0).Rotate(WRot.FromFacing(self.World.SharedRandom.Next(255))); + // TODO: This needs to be looked into again. The bigger MoveRadius is, the bigger chance that the selected coordinates will be invalid. self.Trait().ResolveOrder(self, new Order("AttackMove", self, false) { TargetLocation = self.World.Map.CellContaining(target) }); } } diff --git a/OpenRA.Mods.RA/AutoTarget.cs b/OpenRA.Mods.RA/AutoTarget.cs index 4b887faec2..151a02a9c7 100644 --- a/OpenRA.Mods.RA/AutoTarget.cs +++ b/OpenRA.Mods.RA/AutoTarget.cs @@ -10,6 +10,7 @@ using System.Drawing; using System.Linq; +using OpenRA.Mods.RA.Move; using OpenRA.Traits; namespace OpenRA.Mods.RA @@ -163,12 +164,15 @@ namespace OpenRA.Mods.RA nextScanTime = self.World.SharedRandom.Next(info.MinimumScanTimeInterval, info.MaximumScanTimeInterval); var inRange = self.World.FindActorsInCircle(self.CenterPosition, range); + var mobile = self.TraitOrDefault(); return inRange .Where(a => - a.AppearsHostileTo(self) && - !a.HasTrait() && - attack.HasAnyValidWeapons(Target.FromActor(a)) && - self.Owner.Shroud.IsTargetable(a)) + a.AppearsHostileTo(self) && + !a.HasTrait() && + attack.HasAnyValidWeapons(Target.FromActor(a)) && + self.Owner.Shroud.IsTargetable(a) && + (!attack.Info.AttackRequiresEnteringCell || (mobile != null && mobile.MovementSpeedForCell(self, a.Location) != 0)) + ) .ClosestTo(self); } } diff --git a/mods/d2k/bits/wormicon.shp b/mods/d2k/bits/wormicon.shp new file mode 100644 index 0000000000000000000000000000000000000000..fcc29f93f715d023e8a94aecfc56ceae1a785c69 GIT binary patch literal 2904 zcmY+FPjB1U6~*IiXVXQ!OuOqQiwFT^z(obU}D z*+tVp*>*8spo@Nkhi=+Vo3yf%Iu)Fm0sIY2$`Zvt_MA(}DS8uAl*r#Z=ic|;eQWn9 zetGX<`T6sE_r9W=r=OHxKVEphe|UI)ety`WOeVNJJnSRd?aAfg<)j^KR_b=-IB^@l zwA(=$xL@6Y<$wWQzAr~PKfm1Sw#&?~uHeP|23k9*ndxz+}Q{m`j-b?C-zEPwk+xA{{4k`gR}m%J`kIhgjhl zi_`6+8C`=-FYc2hbSY;C18=P|#d8YJb3O0kB0x1DMFW9)Z~a*tW|; zm$2giCa23Naoj={mo}~k&1O@Q8VyB{lQ`zYE^A#{cS&%GKB15D+r`C)zk*4eY~LkKG>Jp`gLiO&8(5YkT^)jrfZ9RQYPmdfm$s2J)%5?vYhQf$ z_CsKU-7l5(BzAx%Nw7+mbCQB717sngpu5WKhFPlDh5%iJ@Ouhjgs6FD$l&JSY@P#Id6t9a}eMUmG5VF)td z_2cPZhktAQ@T$_=;F<71xria;d22c|EVYd7e=go;&=>)2H)HQrCZD z7^1$9wbUgNC}Vf1rjac_uXDE!@AbUMecvy#{KqzyU7?CE-vGYBLM~ZCOL!cPF@%G6 zn~-#KmzS_FsjB+8@v95Z%~aWV1ZBAg77+4WiH76xI1Ib?GFvJbba_$uv?Tl@&zq7e z$l_184RQaD{hwdZnr7?tA{Dz1%NWS#piw*&Ne~3lkcZm}7KC=Ex#US$W2p?2P^x1c z>4G8%I&?YyWT1j98|qNW?46Je2HL}igF&c;p{9{%ZR`sof+hp+3yNjc5SDCN96!pk zD5_RXj~!TK&Ta2q_q0!3NUY`vHG;)G;KGmO5;Z79$Z}aURHas}u&Y7n_1>Rbjd#zp zAy9(H*72)XxA`e0 ze9BlnW|*(!b9sIJf&AkBKOc*GhH-RcR1L7ApeVY+m1;b{(a*cxx7OFgbaY((It)kS z@ajqnN25U*E-Z_$U*0#2YIS3yTC-3IMd|E9xys{&p-y4=J{|t&-Dz(-9FHXs8q`m_qmA$X`*su_hv9(CI3+#Oyj`ify?wL+X=7NkYKn5(>=3Nq zpik4{;A1WLrK1sT%il>U^ne>c6xmL z7SmpC5YX|~XrMXWz&+?y$S(y7eS-}KxEV0a%~@bv=^e%pEW?K6j}h5aR3vTUkFKDB4~XLrLB0BP20<@4(q?tDQh z68GeUudO*jZr~wNw_dO(iI$W|5|L!knaqUX!P-q?H!f{?gVCV?p9(uDg%@Bg?4|Lf56HU||rz zMIoiWnPmyi^f*(g!I>%Cqfs%o^Ow}kz`A;^rkZ@&lhDd^nrW;Qbf^qX4lt^YG$2@* zX0kZbAPX#WvxU1^sidZWXI!Gb4VZ%Z7*-0*XRUO0U9zfVwx(ENzXeIMI?!2$LH#^m z_Rk!>6VjF-3o7_LOVjjo6BU$5TQ%VZC&LXkonDRReyM?%k~Gb-l Date: Mon, 3 Nov 2014 00:14:53 +0200 Subject: [PATCH 2/5] Step three in implementing sandworms Removed/fixed comments Code style fixes Fixed AttackWander, addressed style nits Fix Travis crash --- OpenRA.Mods.D2k/AttackSwallow.cs | 18 ++-- OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj | 1 - OpenRA.Mods.D2k/Sandworm.cs | 41 +-------- OpenRA.Mods.D2k/SwallowActor.cs | 68 +++++---------- OpenRA.Mods.D2k/WormManager.cs | 115 +++++++++++++------------ OpenRA.Mods.D2k/WormSpawner.cs | 24 ------ OpenRA.Mods.RA/Attack/AttackBase.cs | 20 ++++- OpenRA.Mods.RA/Attack/AttackWander.cs | 27 +++++- OpenRA.Mods.RA/AutoTarget.cs | 12 +-- mods/d2k/bits/wormspawner.shp | Bin 0 -> 92 bytes mods/d2k/maps/death-depths.oramap | Bin 13154 -> 13205 bytes mods/d2k/maps/shellmap/map.yaml | 4 +- mods/d2k/rules/arrakis.yaml | 15 +--- mods/d2k/rules/misc.yaml | 6 ++ mods/d2k/rules/world.yaml | 1 - mods/d2k/sequences/misc.yaml | 5 ++ 16 files changed, 154 insertions(+), 203 deletions(-) delete mode 100644 OpenRA.Mods.D2k/WormSpawner.cs create mode 100644 mods/d2k/bits/wormspawner.shp diff --git a/OpenRA.Mods.D2k/AttackSwallow.cs b/OpenRA.Mods.D2k/AttackSwallow.cs index f0a2b6dcc7..1f9adea519 100644 --- a/OpenRA.Mods.D2k/AttackSwallow.cs +++ b/OpenRA.Mods.D2k/AttackSwallow.cs @@ -17,24 +17,28 @@ namespace OpenRA.Mods.D2k [Desc("Sandworms use this attack model.")] class AttackSwallowInfo : AttackFrontalInfo, Requires { + [Desc("The number of ticks it takes to return underground.")] + public int ReturnTime = 60; + [Desc("The number of ticks it takes to get in place under the target to attack.")] + public int AttackTime = 30; + public override object Create(ActorInitializer init) { return new AttackSwallow(init.self, this); } } + class AttackSwallow : AttackFrontal { - readonly Sandworm sandworm; + public readonly AttackSwallowInfo AttackSwallowInfo; public AttackSwallow(Actor self, AttackSwallowInfo attackSwallowInfo) : base(self, attackSwallowInfo) { - sandworm = self.Trait(); + AttackSwallowInfo = attackSwallowInfo; } public override void DoAttack(Actor self, Target target) { - // TODO: Worm should ignore Fremen as targets unless they are firing/being fired upon (even moving fremen do not attract worms) - - if (target.Type != TargetType.Actor || !CanAttack(self, target) || !sandworm.CanAttackAtLocation(self, target.Actor.Location)) - // this is so that the worm does not launch an attack against a target that has reached solid rock + // This is so that the worm does not launch an attack against a target that has reached solid rock + if (target.Type != TargetType.Actor || !CanAttack(self, target)) { self.CancelActivity(); return; @@ -48,7 +52,7 @@ namespace OpenRA.Mods.D2k return; self.CancelActivity(); - self.QueueActivity(new SwallowActor(self, target.Actor, a.Weapon)); + self.QueueActivity(new SwallowActor(self, target, a.Weapon)); } } } diff --git a/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj b/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj index ad6db3439f..a1ec8a43ba 100644 --- a/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj +++ b/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj @@ -100,7 +100,6 @@ - diff --git a/OpenRA.Mods.D2k/Sandworm.cs b/OpenRA.Mods.D2k/Sandworm.cs index 2bc5c08b38..33b022c28f 100644 --- a/OpenRA.Mods.D2k/Sandworm.cs +++ b/OpenRA.Mods.D2k/Sandworm.cs @@ -17,54 +17,15 @@ namespace OpenRA.Mods.D2k { class SandwormInfo : Requires, Requires, IOccupySpaceInfo { - readonly public int WanderMoveRadius = 20; readonly public string WormSignNotification = "WormSign"; public object Create(ActorInitializer init) { return new Sandworm(this); } } - class Sandworm : INotifyIdle + class Sandworm { - int ticksIdle; - int effectiveMoveRadius; - readonly int maxMoveRadius; - public Sandworm(SandwormInfo info) { - maxMoveRadius = info.WanderMoveRadius; - effectiveMoveRadius = info.WanderMoveRadius; - - // TODO: Someone familiar with how the sounds work should fix this: - // TODO: This should not be here. It should be same as "Enemy unit sighted". - //Sound.PlayNotification(self.Owner, "Speech", info.WormSignNotification, self.Owner.Country.Race); - } - - // TODO: This copies AttackWander and builds on top of it. AttackWander should be revised. - public void TickIdle(Actor self) - { - var globalOffset = new WVec(0, -1024 * effectiveMoveRadius, 0).Rotate(WRot.FromFacing(self.World.SharedRandom.Next(255))); - var offset = new CVec(globalOffset.X/1024, globalOffset.Y/1024); - var targetlocation = self.Location + offset; - - if (!self.World.Map.Bounds.Contains(targetlocation.X, targetlocation.Y)) - { - // If MoveRadius is too big there might not be a valid cell to order the attack to (if actor is on a small island and can't leave) - if (++ticksIdle % 10 == 0) // completely random number - { - effectiveMoveRadius--; - } - return; // We'll be back the next tick; better to sit idle for a few seconds than prolongue this tick indefinitely with a loop - } - - self.World.IssueOrder(new Order("AttackMove", self, false) { TargetLocation = targetlocation }); - - ticksIdle = 0; - effectiveMoveRadius = maxMoveRadius; - } - - public bool CanAttackAtLocation(Actor self, CPos targetLocation) - { - return self.Trait().MovementSpeedForCell(self, targetLocation) != 0; } } } diff --git a/OpenRA.Mods.D2k/SwallowActor.cs b/OpenRA.Mods.D2k/SwallowActor.cs index cb9c18ccec..345826b956 100644 --- a/OpenRA.Mods.D2k/SwallowActor.cs +++ b/OpenRA.Mods.D2k/SwallowActor.cs @@ -17,66 +17,53 @@ using OpenRA.Traits; namespace OpenRA.Mods.D2k { - public enum AttackState { Burrowed, EmergingAboveGround, ReturningUndergrown } + public enum AttackState { Burrowed, EmergingAboveGround, ReturningUnderground } class SwallowActor : Activity { - readonly Actor target; - readonly Mobile mobile; + readonly Target target; readonly Sandworm sandworm; readonly WeaponInfo weapon; + readonly AttackSwallow swallow; + readonly IPositionable positionable; int countdown; AttackState stance = AttackState.Burrowed; - // TODO: random numbers to make it look ok - [Desc("The number of ticks it takes to return underground.")] - const int ReturnTime = 60; - [Desc("The number of ticks it takes to get in place under the target to attack.")] - const int AttackTime = 30; - - public SwallowActor(Actor self, Actor target, WeaponInfo weapon) + public SwallowActor(Actor self, Target target, WeaponInfo weapon) { - if (!target.HasTrait()) - throw new InvalidOperationException("SwallowActor requires a target actor with the Mobile trait"); - this.target = target; this.weapon = weapon; - mobile = self.TraitOrDefault(); + positionable = self.TraitOrDefault(); sandworm = self.TraitOrDefault(); - countdown = AttackTime; + swallow = self.TraitOrDefault(); + countdown = swallow.AttackSwallowInfo.AttackTime; } bool WormAttack(Actor worm) { - var targetLocation = target.Location; + var targetLocation = target.Actor.Location; var lunch = worm.World.ActorMap.GetUnitsAt(targetLocation) - .Except(new[] { worm }) - .Where(t => weapon.IsValidAgainst(t, worm)); + .Where(t => !t.Equals(worm) && weapon.IsValidAgainst(t, worm)); if (!lunch.Any()) return false; stance = AttackState.EmergingAboveGround; - lunch.Do(t => t.World.AddFrameEndTask(_ => { t.World.Remove(t); t.Kill(t); })); // dispose of the evidence (we don't want husks) + lunch.Do(t => t.World.AddFrameEndTask(_ => { t.World.Remove(t); t.Kill(t); })); // Dispose of the evidence (we don't want husks) - mobile.SetPosition(worm, targetLocation); + positionable.SetPosition(worm, targetLocation); PlayAttackAnimation(worm); return true; } - public bool PlayAttackAnimation(Actor self) + void PlayAttackAnimation(Actor self) { var renderUnit = self.Trait(); renderUnit.PlayCustomAnim(self, "sand"); renderUnit.PlayCustomAnim(self, "mouth"); - - // TODO: Someone familiar with how the sounds work should fix this: - //Sound.PlayNotification(self.Owner, "Speech", "WormAttack", self.Owner.Country.Race); - - return true; } public override Activity Tick(Actor self) @@ -87,47 +74,36 @@ namespace OpenRA.Mods.D2k return this; } - if (stance == AttackState.ReturningUndergrown) // wait for the worm to get back underground + if (stance == AttackState.ReturningUnderground) // Wait for the worm to get back underground { - #region DisappearToMapEdge - - // More random numbers used for min and max bounds - var rand = self.World.SharedRandom.Next(200, 400); - if (rand % 2 == 0) // there is a 50-50 chance that the worm would just go away + if (self.World.SharedRandom.Next() % 2 == 0) // There is a 50-50 chance that the worm would just go away { self.CancelActivity(); - //self.World.WorldActor.QueueActivity(new DisappearToMapEdge(self, rand)); self.World.AddFrameEndTask(w => w.Remove(self)); var wormManager = self.World.WorldActor.TraitOrDefault(); if (wormManager != null) wormManager.DecreaseWorms(); } - #endregion - - // TODO: if the worm did not disappear, make the animation reappear here + // TODO: If the worm did not disappear, make the animation reappear here return NextActivity; } - if (stance == AttackState.Burrowed) // wait for the worm to get in position + if (stance == AttackState.Burrowed) // Wait for the worm to get in position { - // TODO: make the worm animation (currenty the lightning) disappear here + // TODO: Make the worm animation (currenty the lightning) disappear here - // this is so that the worm cancels an attack against a target that has reached solid rock - if (sandworm == null || !sandworm.CanAttackAtLocation(self, target.Location)) - { + // This is so that the worm cancels an attack against a target that has reached solid rock + if (sandworm == null || positionable == null || !positionable.CanEnterCell(target.Actor.Location, null, false)) return NextActivity; - } var success = WormAttack(self); if (!success) - { return NextActivity; - } - countdown = ReturnTime; - stance = AttackState.ReturningUndergrown; + countdown = swallow.AttackSwallowInfo.ReturnTime; + stance = AttackState.ReturningUnderground; } return this; diff --git a/OpenRA.Mods.D2k/WormManager.cs b/OpenRA.Mods.D2k/WormManager.cs index af36094fe7..7e04c93891 100644 --- a/OpenRA.Mods.D2k/WormManager.cs +++ b/OpenRA.Mods.D2k/WormManager.cs @@ -15,73 +15,76 @@ using OpenRA.Traits; namespace OpenRA.Mods.D2k { - [Desc("Controls the spawning of sandworms. Attach this to the world actor.")] - class WormManagerInfo : ITraitInfo - { - [Desc("Minimum number of worms")] - public readonly int Minimum = 1; + [Desc("Controls the spawning of sandworms. Attach this to the world actor.")] + class WormManagerInfo : ITraitInfo + { + [Desc("Minimum number of worms")] + public readonly int Minimum = 1; - [Desc("Maximum number of worms")] - public readonly int Maximum = 5; + [Desc("Maximum number of worms")] + public readonly int Maximum = 5; - [Desc("Average time (seconds) between crate spawn")] - public readonly int SpawnInterval = 180; + [Desc("Average time (seconds) between worm spawn")] + public readonly int SpawnInterval = 180; - public readonly string WormSignature = "sandworm"; - public readonly string WormOwnerPlayer = "Creeps"; + public readonly string WormSignature = "sandworm"; + public readonly string WormOwnerPlayer = "Creeps"; - public object Create (ActorInitializer init) { return new WormManager(this, init.self); } - } + public object Create (ActorInitializer init) { return new WormManager(this, init.self); } + } - class WormManager : ITick - { - int countdown; - int wormsPresent; - readonly WormManagerInfo info; - readonly Lazy spawnPoints; + class WormManager : ITick + { + int countdown; + int wormsPresent; + readonly WormManagerInfo info; + readonly Lazy spawnPoints; - public WormManager(WormManagerInfo info, Actor self) - { - this.info = info; - spawnPoints = Exts.Lazy(() => self.World.ActorsWithTrait().Select(x => x.Actor).ToArray()); - } + public WormManager(WormManagerInfo info, Actor self) + { + this.info = info; + spawnPoints = Exts.Lazy(() => self.World.ActorsWithTrait().Select(x => x.Actor).ToArray()); + } - public void Tick(Actor self) - { - // TODO: Add a lobby option to disable worms just like crates + public void Tick(Actor self) + { + // TODO: Add a lobby option to disable worms just like crates - if (--countdown > 0) - return; + // TODO: It would be even better to stop + if (!spawnPoints.Value.Any()) + return; - countdown = info.SpawnInterval * 25; - if (wormsPresent < info.Maximum) - SpawnWorm(self); - } + if (--countdown > 0) + return; - private void SpawnWorm (Actor self) - { - var spawnLocation = GetRandomSpawnPosition(self); - self.World.AddFrameEndTask(w => - w.CreateActor(info.WormSignature, new TypeDictionary - { - new OwnerInit(w.Players.First(x => x.PlayerName == info.WormOwnerPlayer)), - new LocationInit(spawnLocation) - })); - wormsPresent++; - } + countdown = info.SpawnInterval * 25; + if (wormsPresent < info.Maximum) + SpawnWorm(self); + } - private CPos GetRandomSpawnPosition(Actor self) - { - // TODO: This is here only for testing, while the maps don't have valid spawn points - if (!spawnPoints.Value.Any()) - return self.World.Map.ChooseRandomEdgeCell(self.World.SharedRandom); + void SpawnWorm (Actor self) + { + var spawnLocation = GetRandomSpawnPosition(self); + self.World.AddFrameEndTask(w => w.CreateActor(info.WormSignature, new TypeDictionary + { + new OwnerInit(w.Players.First(x => x.PlayerName == info.WormOwnerPlayer)), + new LocationInit(spawnLocation) + })); + wormsPresent++; + } - return spawnPoints.Value[self.World.SharedRandom.Next(0, spawnPoints.Value.Count() - 1)].Location; - } + CPos GetRandomSpawnPosition(Actor self) + { + return spawnPoints.Value.Random(self.World.SharedRandom).Location; + } - public void DecreaseWorms() - { - wormsPresent--; - } - } + public void DecreaseWorms() + { + wormsPresent--; + } + } + + [Desc("An actor with this trait indicates a valid spawn point for sandworms.")] + class WormSpawnerInfo : TraitInfo { } + class WormSpawner { } } diff --git a/OpenRA.Mods.D2k/WormSpawner.cs b/OpenRA.Mods.D2k/WormSpawner.cs deleted file mode 100644 index 99730e79b4..0000000000 --- a/OpenRA.Mods.D2k/WormSpawner.cs +++ /dev/null @@ -1,24 +0,0 @@ -#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 OpenRA.Traits; - -namespace OpenRA.Mods.D2k -{ - [Desc("An actor with this trait indicates a valid spawn point for sandworms.")] - class WormSpawnerInfo : ITraitInfo - { - public object Create(ActorInitializer init) { return new WormSpawner(); } - } - - class WormSpawner - { - } -} diff --git a/OpenRA.Mods.RA/Attack/AttackBase.cs b/OpenRA.Mods.RA/Attack/AttackBase.cs index 1e5971d7c9..4a3f54a24b 100644 --- a/OpenRA.Mods.RA/Attack/AttackBase.cs +++ b/OpenRA.Mods.RA/Attack/AttackBase.cs @@ -14,6 +14,7 @@ using System.Drawing; using System.Linq; using OpenRA.GameRules; using OpenRA.Mods.RA.Buildings; +using OpenRA.Mods.RA.Move; using OpenRA.Traits; namespace OpenRA.Mods.RA @@ -25,7 +26,8 @@ namespace OpenRA.Mods.RA public readonly string Cursor = "attack"; public readonly string OutsideRangeCursor = "attackoutsiderange"; - [Desc("Does the attack type requires the attacker to enter the target's cell?")] + + [Desc("Does the attack type require the attacker to enter the target's cell?")] public readonly bool AttackRequiresEnteringCell = false; public abstract object Create(ActorInitializer init); @@ -61,6 +63,9 @@ namespace OpenRA.Mods.RA if (!self.IsInWorld) return false; + if (!HasAnyValidWeapons(target)) + return false; + // Building is under construction or is being sold if (building.Value != null && !building.Value.BuildComplete) return false; @@ -137,7 +142,18 @@ namespace OpenRA.Mods.RA public abstract Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove); - public bool HasAnyValidWeapons(Target t) { return Armaments.Any(a => a.Weapon.IsValidAgainst(t, self.World, self)); } + public bool HasAnyValidWeapons(Target t) + { + if (Info.AttackRequiresEnteringCell) + { + var positionable = self.TraitOrDefault(); + if (positionable == null || !positionable.CanEnterCell(t.Actor.Location, null, false)) + return false; + } + + return Armaments.Any(a => a.Weapon.IsValidAgainst(t, self.World, self)); + } + public WRange GetMaximumRange() { return Armaments.Select(a => a.Weapon.Range).Append(WRange.Zero).Max(); diff --git a/OpenRA.Mods.RA/Attack/AttackWander.cs b/OpenRA.Mods.RA/Attack/AttackWander.cs index e48528e1cf..06e194993e 100644 --- a/OpenRA.Mods.RA/Attack/AttackWander.cs +++ b/OpenRA.Mods.RA/Attack/AttackWander.cs @@ -17,14 +17,20 @@ namespace OpenRA.Mods.RA "This conflicts with player orders and should only be added to animal creeps.")] class AttackWanderInfo : ITraitInfo { - public readonly int MoveRadius = 4; + readonly public int WanderMoveRadius = 10; + + [Desc("Number of ticks to wait until decreasing the effective move radius.")] + public readonly int MoveReductionRadiusScale = 5; public object Create(ActorInitializer init) { return new AttackWander(init.self, this); } } class AttackWander : INotifyIdle { + int ticksIdle; + int effectiveMoveRadius; readonly AttackWanderInfo Info; + public AttackWander(Actor self, AttackWanderInfo info) { Info = info; @@ -32,9 +38,22 @@ namespace OpenRA.Mods.RA public void TickIdle(Actor self) { - var target = self.CenterPosition + new WVec(0, -1024*Info.MoveRadius, 0).Rotate(WRot.FromFacing(self.World.SharedRandom.Next(255))); - // TODO: This needs to be looked into again. The bigger MoveRadius is, the bigger chance that the selected coordinates will be invalid. - self.Trait().ResolveOrder(self, new Order("AttackMove", self, false) { TargetLocation = self.World.Map.CellContaining(target) }); + var target = self.CenterPosition + new WVec(0, -1024 * effectiveMoveRadius, 0).Rotate(WRot.FromFacing(self.World.SharedRandom.Next(255))); + var targetCell = self.World.Map.CellContaining(target); + + if (!self.World.Map.Contains(targetCell)) + { + // If MoveRadius is too big there might not be a valid cell to order the attack to (if actor is on a small island and can't leave) + if (++ticksIdle % Info.MoveReductionRadiusScale == 0) + effectiveMoveRadius--; + + return; // We'll be back the next tick; better to sit idle for a few seconds than prolongue this tick indefinitely with a loop + } + + self.Trait().ResolveOrder(self, new Order("AttackMove", self, false) { TargetLocation = targetCell }); + + ticksIdle = 0; + effectiveMoveRadius = Info.WanderMoveRadius; } } } diff --git a/OpenRA.Mods.RA/AutoTarget.cs b/OpenRA.Mods.RA/AutoTarget.cs index 151a02a9c7..4b887faec2 100644 --- a/OpenRA.Mods.RA/AutoTarget.cs +++ b/OpenRA.Mods.RA/AutoTarget.cs @@ -10,7 +10,6 @@ using System.Drawing; using System.Linq; -using OpenRA.Mods.RA.Move; using OpenRA.Traits; namespace OpenRA.Mods.RA @@ -164,15 +163,12 @@ namespace OpenRA.Mods.RA nextScanTime = self.World.SharedRandom.Next(info.MinimumScanTimeInterval, info.MaximumScanTimeInterval); var inRange = self.World.FindActorsInCircle(self.CenterPosition, range); - var mobile = self.TraitOrDefault(); return inRange .Where(a => - a.AppearsHostileTo(self) && - !a.HasTrait() && - attack.HasAnyValidWeapons(Target.FromActor(a)) && - self.Owner.Shroud.IsTargetable(a) && - (!attack.Info.AttackRequiresEnteringCell || (mobile != null && mobile.MovementSpeedForCell(self, a.Location) != 0)) - ) + a.AppearsHostileTo(self) && + !a.HasTrait() && + attack.HasAnyValidWeapons(Target.FromActor(a)) && + self.Owner.Shroud.IsTargetable(a)) .ClosestTo(self); } } diff --git a/mods/d2k/bits/wormspawner.shp b/mods/d2k/bits/wormspawner.shp new file mode 100644 index 0000000000000000000000000000000000000000..efefdfe49617c81fd497ab06fd8a3f80062265e4 GIT binary patch literal 92 zcmZQ%009Xg1OYV$h6WHF10s;Xzf%m%jsG23F6?ADd5+;9C&T|MXBf^tWVnBj;oe$? d|6UB@6aWAK2mny8VzCW1Fn>_5VnwmiR_L|@003eO000O8003=a za4vaaZEUTUUvt|a48Whqr|<`$cLt2H{j{uI?)InY&dJ(m8mo(MwsFB;*M0h7yIH!e z;`Xq+HbI1+BqSl&LIe9j-!@Q0J39g)m%3jY+v=f+QbJ0&A8QNl(o{uM@vnsJ>S0=Z z0e^Zp_|JYgI6^MK?QPu;wdsmTd=rte(R#fiWHk;4V~gnAw6}E!=bMs7XYawCO^O&mD?VmX!LCB{@KZ5n6SIek`JAbgy?cwzX#=+{w|MF_1TaQ0Yc=_s~b2r8| z)l*>n3e!#7(1Y%VUz4TYdrS`&>I&T77B0tTsN<7?MfT#f@LAlCno8YOruE|64%!<0 z-p?&Sk7;IqXlDOI^D=+Zd|dZ$5jg2z_DYYQk=1@Ma~kGBTtsc}di~IO=A+r`rGI*o zgi5}|AMCS)%nW;(VVqMbFeoh|*VlWvZOpv?6DM;jGYnHGmNF`149ifA#ga;iae07K zG*M_sVjL8TW;qIS6ci%F3EW^t6`s2!#0i{}xSxtLG=D4&1||UFnP*Z^k)R-+dnIX- zq8y%k#R8{+Ga9)Bc`S4{5xL@S2l6_fMO{_u*)QktN7;CSZqB#?NH zS6Z4=hVKtA7!}hDw};nDo(4`J$Lodr?uq7|;d$qApn-}&i;M&P9;I2CC36*ACdyrbwfAwu0Lp#o2S5k2_#*?b9t{rx&)G-k(lh;#mF zh2LjMz@G50GK`Y}d%oImP^bVYWuQNaEI_jS#o&-_J62y{dM1`*Gk@6aWAK2mokWQ?U&-Fn?%TQ$_4vfu@53000dN000O8003=a za4vaaZEUSp+j5&Q5Pgomf)7w;kOT;Ls+-g^y~OEYH+fcIQ!@obB+)ee`U(@*nKrBG zLuYKPUF+=atQKvdgW9RP4$5d}CLrWWjcaXs<;o}{q=JX3HDIrFQ$`K{M#!#p^X5xX zZh!BequX0TF2UBO9bK!3G7{fKq)oJ5uLxO9Zm&%lUFiO<9pGY9(dhgG*z?&?2Nt}W zRaLD%zTX~PZ`+^VIFlLmUqasMX=tohc-eoFn9kZS7FKq}0_AuoPv<=w5JzPzlYvYrHMfT#n@kQN_mMS$gy7%I{0s0nv?iU7N z#4NKvEVKV%dC{LNANT!R1y1&tT&$RF8wleA2aAx+h6!%72&o zgLy&70mEL$FwSWzFi=)Tc5G|7>+~W26D}=inqx4D!ZJqF7=z^~jKwKUQw&!GaFQkx zElG?6g`|0b0yzp4B7_sT!<)SxlqMO9 z!?~9%a2hyHy-aAvP#i;XxyPXgiGSlAQ}{U1j^TL66g-q4-Z6Pb6O;#zGhZYD;yK=F zSwR_|AKoxBraA5p@0TJAoIsBE3-{9#<(=WY^Efa-MPS6Fp#~MWLzc+EFcds^1SK9S z-fAMH5-p9uTTO5((CI7Ez>x8QqOS=NnobE7uaHl+{nUJg`9@Ps-QjoZuQO-!D|=qw z=pWF%RNR6ZmkZF1ZWq-?yY{|a9L@61sKIte@fT1_0Rl4s6aWAK2mokWQ Date: Sun, 30 Nov 2014 01:39:07 +0200 Subject: [PATCH 3/5] Improved visuals, added WormSign norification --- OpenRA.Mods.D2k/AttackSwallow.cs | 3 +- OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj | 8 ----- OpenRA.Mods.D2k/Sandworm.cs | 31 ------------------- OpenRA.Mods.D2k/SwallowActor.cs | 41 +++++++++++++------------- OpenRA.Mods.D2k/WormManager.cs | 10 +++++++ OpenRA.Mods.RA/Attack/AttackWander.cs | 2 +- mods/cnc/rules/civilian.yaml | 1 + mods/d2k/rules/arrakis.yaml | 1 - mods/d2k/sequences/infantry.yaml | 2 ++ mods/ts/rules/infantry.yaml | 1 + 10 files changed, 37 insertions(+), 63 deletions(-) delete mode 100644 OpenRA.Mods.D2k/Sandworm.cs diff --git a/OpenRA.Mods.D2k/AttackSwallow.cs b/OpenRA.Mods.D2k/AttackSwallow.cs index 1f9adea519..16616e5cf0 100644 --- a/OpenRA.Mods.D2k/AttackSwallow.cs +++ b/OpenRA.Mods.D2k/AttackSwallow.cs @@ -13,9 +13,8 @@ using OpenRA.Traits; namespace OpenRA.Mods.D2k { - // TODO: This is a copy of AttackLeap. Maybe combine them in AttackMelee trait when the code is finalized? [Desc("Sandworms use this attack model.")] - class AttackSwallowInfo : AttackFrontalInfo, Requires + class AttackSwallowInfo : AttackFrontalInfo { [Desc("The number of ticks it takes to return underground.")] public int ReturnTime = 60; diff --git a/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj b/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj index a1ec8a43ba..3edfdb132e 100644 --- a/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj +++ b/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj @@ -69,10 +69,7 @@ - - - @@ -94,11 +91,6 @@ - - - - - diff --git a/OpenRA.Mods.D2k/Sandworm.cs b/OpenRA.Mods.D2k/Sandworm.cs deleted file mode 100644 index 33b022c28f..0000000000 --- a/OpenRA.Mods.D2k/Sandworm.cs +++ /dev/null @@ -1,31 +0,0 @@ -#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 OpenRA.Mods.RA; -using OpenRA.Mods.RA.Move; -using OpenRA.Mods.RA.Render; -using OpenRA.Traits; - -namespace OpenRA.Mods.D2k -{ - class SandwormInfo : Requires, Requires, IOccupySpaceInfo - { - readonly public string WormSignNotification = "WormSign"; - - public object Create(ActorInitializer init) { return new Sandworm(this); } - } - - class Sandworm - { - public Sandworm(SandwormInfo info) - { - } - } -} diff --git a/OpenRA.Mods.D2k/SwallowActor.cs b/OpenRA.Mods.D2k/SwallowActor.cs index 345826b956..6c4798375c 100644 --- a/OpenRA.Mods.D2k/SwallowActor.cs +++ b/OpenRA.Mods.D2k/SwallowActor.cs @@ -17,27 +17,30 @@ using OpenRA.Traits; namespace OpenRA.Mods.D2k { - public enum AttackState { Burrowed, EmergingAboveGround, ReturningUnderground } + enum AttackState { Burrowed, EmergingAboveGround, ReturningUnderground } class SwallowActor : Activity { readonly Target target; - readonly Sandworm sandworm; readonly WeaponInfo weapon; + readonly RenderUnit renderUnit; readonly AttackSwallow swallow; readonly IPositionable positionable; int countdown; - AttackState stance = AttackState.Burrowed; + AttackState stance; public SwallowActor(Actor self, Target target, WeaponInfo weapon) { this.target = target; this.weapon = weapon; positionable = self.TraitOrDefault(); - sandworm = self.TraitOrDefault(); swallow = self.TraitOrDefault(); + renderUnit = self.TraitOrDefault(); countdown = swallow.AttackSwallowInfo.AttackTime; + + renderUnit.DefaultAnimation.ReplaceAnim("burrowed"); + stance = AttackState.Burrowed; } bool WormAttack(Actor worm) @@ -45,14 +48,14 @@ namespace OpenRA.Mods.D2k var targetLocation = target.Actor.Location; var lunch = worm.World.ActorMap.GetUnitsAt(targetLocation) - .Where(t => !t.Equals(worm) && weapon.IsValidAgainst(t, worm)); + .Where(t => !t.Equals(worm) && weapon.IsValidAgainst(t, worm)); if (!lunch.Any()) return false; stance = AttackState.EmergingAboveGround; lunch.Do(t => t.World.AddFrameEndTask(_ => { t.World.Remove(t); t.Kill(t); })); // Dispose of the evidence (we don't want husks) - + positionable.SetPosition(worm, targetLocation); PlayAttackAnimation(worm); @@ -61,7 +64,6 @@ namespace OpenRA.Mods.D2k void PlayAttackAnimation(Actor self) { - var renderUnit = self.Trait(); renderUnit.PlayCustomAnim(self, "sand"); renderUnit.PlayCustomAnim(self, "mouth"); } @@ -74,28 +76,27 @@ namespace OpenRA.Mods.D2k return this; } - if (stance == AttackState.ReturningUnderground) // Wait for the worm to get back underground + if (stance == AttackState.ReturningUnderground) // Wait for the worm to get back underground { - if (self.World.SharedRandom.Next() % 2 == 0) // There is a 50-50 chance that the worm would just go away + if (self.World.SharedRandom.Next()%2 == 0) // There is a 50-50 chance that the worm would just go away { - self.CancelActivity(); - self.World.AddFrameEndTask(w => w.Remove(self)); - var wormManager = self.World.WorldActor.TraitOrDefault(); - if (wormManager != null) - wormManager.DecreaseWorms(); + self.CancelActivity(); + self.World.AddFrameEndTask(w => w.Remove(self)); + var wormManager = self.World.WorldActor.TraitOrDefault(); + if (wormManager != null) + wormManager.DecreaseWorms(); + } + else + { + renderUnit.DefaultAnimation.ReplaceAnim("idle"); } - - // TODO: If the worm did not disappear, make the animation reappear here - return NextActivity; } if (stance == AttackState.Burrowed) // Wait for the worm to get in position { - // TODO: Make the worm animation (currenty the lightning) disappear here - // This is so that the worm cancels an attack against a target that has reached solid rock - if (sandworm == null || positionable == null || !positionable.CanEnterCell(target.Actor.Location, null, false)) + if (positionable == null || !positionable.CanEnterCell(target.Actor.Location, null, false)) return NextActivity; var success = WormAttack(self); diff --git a/OpenRA.Mods.D2k/WormManager.cs b/OpenRA.Mods.D2k/WormManager.cs index 7e04c93891..fa44011c8f 100644 --- a/OpenRA.Mods.D2k/WormManager.cs +++ b/OpenRA.Mods.D2k/WormManager.cs @@ -27,6 +27,8 @@ namespace OpenRA.Mods.D2k [Desc("Average time (seconds) between worm spawn")] public readonly int SpawnInterval = 180; + public readonly string WormSignNotification = "WormSign"; + public readonly string WormSignature = "sandworm"; public readonly string WormOwnerPlayer = "Creeps"; @@ -71,6 +73,8 @@ namespace OpenRA.Mods.D2k new LocationInit(spawnLocation) })); wormsPresent++; + + AnnounceWormSign(self); } CPos GetRandomSpawnPosition(Actor self) @@ -82,6 +86,12 @@ namespace OpenRA.Mods.D2k { wormsPresent--; } + + void AnnounceWormSign(Actor self) + { + if (self.World.LocalPlayer != null) + Sound.PlayNotification(self.World.Map.Rules, self.World.LocalPlayer, "Speech", info.WormSignNotification, self.World.LocalPlayer.Country.Race); + } } [Desc("An actor with this trait indicates a valid spawn point for sandworms.")] diff --git a/OpenRA.Mods.RA/Attack/AttackWander.cs b/OpenRA.Mods.RA/Attack/AttackWander.cs index 06e194993e..e6ec96baa3 100644 --- a/OpenRA.Mods.RA/Attack/AttackWander.cs +++ b/OpenRA.Mods.RA/Attack/AttackWander.cs @@ -17,7 +17,7 @@ namespace OpenRA.Mods.RA "This conflicts with player orders and should only be added to animal creeps.")] class AttackWanderInfo : ITraitInfo { - readonly public int WanderMoveRadius = 10; + public readonly int WanderMoveRadius = 10; [Desc("Number of ticks to wait until decreasing the effective move radius.")] public readonly int MoveReductionRadiusScale = 5; diff --git a/mods/cnc/rules/civilian.yaml b/mods/cnc/rules/civilian.yaml index 34871fa770..f82793b58d 100644 --- a/mods/cnc/rules/civilian.yaml +++ b/mods/cnc/rules/civilian.yaml @@ -458,6 +458,7 @@ VICE: MuzzleSplitFacings: 8 AttackFrontal: AttackWander: + WanderMoveRadius: 4 RenderUnit: WithMuzzleFlash: SplitFacings: true diff --git a/mods/d2k/rules/arrakis.yaml b/mods/d2k/rules/arrakis.yaml index bd549fbc84..43e718f53e 100644 --- a/mods/d2k/rules/arrakis.yaml +++ b/mods/d2k/rules/arrakis.yaml @@ -41,7 +41,6 @@ SANDWORM: RenderUnit: BodyOrientation: HiddenUnderFog: - Sandworm: AppearsOnRadar: UseLocation: yes AttackSwallow: diff --git a/mods/d2k/sequences/infantry.yaml b/mods/d2k/sequences/infantry.yaml index 5aff4af1d3..e828e28d57 100644 --- a/mods/d2k/sequences/infantry.yaml +++ b/mods/d2k/sequences/infantry.yaml @@ -456,5 +456,7 @@ sandworm: Length: 35 Tick: 180 BlendMode: Additive + burrowed: DATA + Start: 39 icon: wormicon Start: 0 \ No newline at end of file diff --git a/mods/ts/rules/infantry.yaml b/mods/ts/rules/infantry.yaml index 476c4fda9a..b70348c923 100644 --- a/mods/ts/rules/infantry.yaml +++ b/mods/ts/rules/infantry.yaml @@ -583,6 +583,7 @@ DOGGIE: Weapon: FiendShard AttackFrontal: AttackWander: + WanderMoveRadius: 4 VISSML: Inherits: ^Infantry From b4d1a605da3d4eb58d1ac7cc3d488d7d87e4545f Mon Sep 17 00:00:00 2001 From: penev92 Date: Sun, 30 Nov 2014 15:39:47 +0200 Subject: [PATCH 4/5] Add RadarPing on worm spawn, fix wandering issue, add new Wormspawner.shp --- OpenRA.Mods.D2k/WormManager.cs | 32 +++++++++++++++++++------- OpenRA.Mods.RA/Attack/AttackWander.cs | 7 ++++-- OpenRA.Mods.RA/Traits/AttackMove.cs | 3 ++- mods/d2k/bits/wormspawner.shp | Bin 92 -> 331 bytes 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/OpenRA.Mods.D2k/WormManager.cs b/OpenRA.Mods.D2k/WormManager.cs index fa44011c8f..70cc02d031 100644 --- a/OpenRA.Mods.D2k/WormManager.cs +++ b/OpenRA.Mods.D2k/WormManager.cs @@ -9,7 +9,9 @@ #endregion using System; +using System.Drawing; using System.Linq; +using OpenRA.Mods.Common; using OpenRA.Primitives; using OpenRA.Traits; @@ -22,7 +24,7 @@ namespace OpenRA.Mods.D2k public readonly int Minimum = 1; [Desc("Maximum number of worms")] - public readonly int Maximum = 5; + public readonly int Maximum = 8; [Desc("Average time (seconds) between worm spawn")] public readonly int SpawnInterval = 180; @@ -39,6 +41,7 @@ namespace OpenRA.Mods.D2k { int countdown; int wormsPresent; + RadarPings radarPings; readonly WormManagerInfo info; readonly Lazy spawnPoints; @@ -66,7 +69,8 @@ namespace OpenRA.Mods.D2k void SpawnWorm (Actor self) { - var spawnLocation = GetRandomSpawnPosition(self); + var spawnPosition = GetRandomSpawnPosition(self); + var spawnLocation = self.World.Map.CellContaining(spawnPosition); self.World.AddFrameEndTask(w => w.CreateActor(info.WormSignature, new TypeDictionary { new OwnerInit(w.Players.First(x => x.PlayerName == info.WormOwnerPlayer)), @@ -74,12 +78,12 @@ namespace OpenRA.Mods.D2k })); wormsPresent++; - AnnounceWormSign(self); + AnnounceWormSign(self, spawnPosition); } - CPos GetRandomSpawnPosition(Actor self) + WPos GetRandomSpawnPosition(Actor self) { - return spawnPoints.Value.Random(self.World.SharedRandom).Location; + return spawnPoints.Value.Random(self.World.SharedRandom).CenterPosition; } public void DecreaseWorms() @@ -87,10 +91,22 @@ namespace OpenRA.Mods.D2k wormsPresent--; } - void AnnounceWormSign(Actor self) + void AnnounceWormSign(Actor self, WPos wormSpawnPosition) { - if (self.World.LocalPlayer != null) - Sound.PlayNotification(self.World.Map.Rules, self.World.LocalPlayer, "Speech", info.WormSignNotification, self.World.LocalPlayer.Country.Race); + if (self.World.LocalPlayer == null) + return; + + Sound.PlayNotification(self.World.Map.Rules, self.World.LocalPlayer, "Speech", info.WormSignNotification, self.World.LocalPlayer.Country.Race); + + if (radarPings == null) + { + if (self.World.WorldActor == null) + return; + + radarPings = self.World.WorldActor.TraitOrDefault(); + } + + radarPings.Add(() => true, wormSpawnPosition, Color.Red, 50); } } diff --git a/OpenRA.Mods.RA/Attack/AttackWander.cs b/OpenRA.Mods.RA/Attack/AttackWander.cs index e6ec96baa3..b9174fe3e2 100644 --- a/OpenRA.Mods.RA/Attack/AttackWander.cs +++ b/OpenRA.Mods.RA/Attack/AttackWander.cs @@ -15,7 +15,7 @@ namespace OpenRA.Mods.RA { [Desc("Will AttackMove to a random location within MoveRadius when idle.", "This conflicts with player orders and should only be added to animal creeps.")] - class AttackWanderInfo : ITraitInfo + class AttackWanderInfo : ITraitInfo, Requires { public readonly int WanderMoveRadius = 10; @@ -29,11 +29,14 @@ namespace OpenRA.Mods.RA { int ticksIdle; int effectiveMoveRadius; + readonly AttackMove attackMove; readonly AttackWanderInfo Info; public AttackWander(Actor self, AttackWanderInfo info) { Info = info; + effectiveMoveRadius = info.WanderMoveRadius; + attackMove = self.TraitOrDefault(); } public void TickIdle(Actor self) @@ -50,7 +53,7 @@ namespace OpenRA.Mods.RA return; // We'll be back the next tick; better to sit idle for a few seconds than prolongue this tick indefinitely with a loop } - self.Trait().ResolveOrder(self, new Order("AttackMove", self, false) { TargetLocation = targetCell }); + attackMove.ResolveOrder(self, new Order("AttackMove", self, false) { TargetLocation = targetCell }); ticksIdle = 0; effectiveMoveRadius = Info.WanderMoveRadius; diff --git a/OpenRA.Mods.RA/Traits/AttackMove.cs b/OpenRA.Mods.RA/Traits/AttackMove.cs index 8be8e7b75d..4e750ad777 100644 --- a/OpenRA.Mods.RA/Traits/AttackMove.cs +++ b/OpenRA.Mods.RA/Traits/AttackMove.cs @@ -49,7 +49,8 @@ namespace OpenRA.Mods.RA.Traits public void TickIdle(Actor self) { - if (TargetLocation.HasValue) + // This might cause the actor to be stuck if the target location is unreachable + if (TargetLocation.HasValue && self.Location != TargetLocation.Value) Activate(self); } diff --git a/mods/d2k/bits/wormspawner.shp b/mods/d2k/bits/wormspawner.shp index efefdfe49617c81fd497ab06fd8a3f80062265e4..cd1a7e79aede8304434a3d613f3b6e01318b6b13 100644 GIT binary patch literal 331 zcmZ{fZ3=)e2!I2~w=@-!xZpgKFvIcNdg zn5hCJ52o7IMYoBrPpqi2?Vo!wBci5MDEc)!!1IBkQeU(VnaVy{n<~Ldo*ZM89W><6 Q{`EV@i%o@p^($bw0j2$n`2YX_ delta 76 zcmX@j6f;50ErtOV{5!?K-1y&t<-$&eljj)zaWeeBa)#mTLx%eY8SbrR`0vHw&3Jhd W;}u~>fj5^=GG4yR_|KGqxd8x(5*vd6 From f57625f9e652db42e7e13b1ffb47e66657482972 Mon Sep 17 00:00:00 2001 From: penev92 Date: Wed, 3 Dec 2014 06:29:49 +0200 Subject: [PATCH 5/5] Add WormAttack notification, fix worm min count and spawn interval Enhance usage of WormManager Minimum and Maximum properties, add escape radius to AttackSwallow --- OpenRA.Mods.D2k/AttackSwallow.cs | 10 +++-- OpenRA.Mods.D2k/SwallowActor.cs | 57 ++++++++++++++++++------- OpenRA.Mods.D2k/WormManager.cs | 64 +++++++++++++++------------- OpenRA.Mods.RA/Attack/AttackBase.cs | 6 +-- mods/cnc/rules/civilian.yaml | 2 +- mods/d2k/maps/death-depths.oramap | Bin 13205 -> 13205 bytes mods/d2k/maps/shellmap/map.yaml | 2 + mods/ts/rules/infantry.yaml | 2 +- 8 files changed, 89 insertions(+), 54 deletions(-) diff --git a/OpenRA.Mods.D2k/AttackSwallow.cs b/OpenRA.Mods.D2k/AttackSwallow.cs index 16616e5cf0..a5c56bc4bf 100644 --- a/OpenRA.Mods.D2k/AttackSwallow.cs +++ b/OpenRA.Mods.D2k/AttackSwallow.cs @@ -21,17 +21,19 @@ namespace OpenRA.Mods.D2k [Desc("The number of ticks it takes to get in place under the target to attack.")] public int AttackTime = 30; + public readonly string WormAttackNotification = "WormAttack"; + public override object Create(ActorInitializer init) { return new AttackSwallow(init.self, this); } } class AttackSwallow : AttackFrontal { - public readonly AttackSwallowInfo AttackSwallowInfo; + new public readonly AttackSwallowInfo Info; - public AttackSwallow(Actor self, AttackSwallowInfo attackSwallowInfo) - : base(self, attackSwallowInfo) + public AttackSwallow(Actor self, AttackSwallowInfo info) + : base(self, info) { - AttackSwallowInfo = attackSwallowInfo; + Info = info; } public override void DoAttack(Actor self, Target target) diff --git a/OpenRA.Mods.D2k/SwallowActor.cs b/OpenRA.Mods.D2k/SwallowActor.cs index 6c4798375c..db2c49baea 100644 --- a/OpenRA.Mods.D2k/SwallowActor.cs +++ b/OpenRA.Mods.D2k/SwallowActor.cs @@ -8,11 +8,12 @@ */ #endregion -using System; +using System.Drawing; using System.Linq; using OpenRA.GameRules; +using OpenRA.Mods.Common; +using OpenRA.Mods.Common.Traits; using OpenRA.Mods.RA.Move; -using OpenRA.Mods.RA.Render; using OpenRA.Traits; namespace OpenRA.Mods.D2k @@ -21,9 +22,13 @@ namespace OpenRA.Mods.D2k class SwallowActor : Activity { + const int NearEnough = 1; + + readonly CPos location; readonly Target target; readonly WeaponInfo weapon; readonly RenderUnit renderUnit; + readonly RadarPings radarPings; readonly AttackSwallow swallow; readonly IPositionable positionable; @@ -34,18 +39,23 @@ namespace OpenRA.Mods.D2k { this.target = target; this.weapon = weapon; - positionable = self.TraitOrDefault(); - swallow = self.TraitOrDefault(); - renderUnit = self.TraitOrDefault(); - countdown = swallow.AttackSwallowInfo.AttackTime; + positionable = self.Trait(); + swallow = self.Trait(); + renderUnit = self.Trait(); + radarPings = self.World.WorldActor.TraitOrDefault(); + countdown = swallow.Info.AttackTime; renderUnit.DefaultAnimation.ReplaceAnim("burrowed"); stance = AttackState.Burrowed; + location = target.Actor.Location; } bool WormAttack(Actor worm) { var targetLocation = target.Actor.Location; + // The target has moved too far away + if ((location - targetLocation).Length > NearEnough) + return false; var lunch = worm.World.ActorMap.GetUnitsAt(targetLocation) .Where(t => !t.Equals(worm) && weapon.IsValidAgainst(t, worm)); @@ -54,11 +64,17 @@ namespace OpenRA.Mods.D2k stance = AttackState.EmergingAboveGround; - lunch.Do(t => t.World.AddFrameEndTask(_ => { t.World.Remove(t); t.Kill(t); })); // Dispose of the evidence (we don't want husks) + foreach (var actor in lunch) + actor.World.AddFrameEndTask(_ => actor.Destroy()); positionable.SetPosition(worm, targetLocation); PlayAttackAnimation(worm); + var attackPosition = worm.CenterPosition; + var affectedPlayers = lunch.Select(x => x.Owner).Distinct(); + foreach (var affectedPlayer in affectedPlayers) + NotifyPlayer(affectedPlayer, attackPosition); + return true; } @@ -68,6 +84,12 @@ namespace OpenRA.Mods.D2k renderUnit.PlayCustomAnim(self, "mouth"); } + void NotifyPlayer(Player player, WPos location) + { + Sound.PlayNotification(player.World.Map.Rules, player, "Speech", swallow.Info.WormAttackNotification, player.Country.Race); + radarPings.Add(() => true, location, Color.Red, 50); + } + public override Activity Tick(Actor self) { if (countdown > 0) @@ -78,17 +100,17 @@ namespace OpenRA.Mods.D2k if (stance == AttackState.ReturningUnderground) // Wait for the worm to get back underground { - if (self.World.SharedRandom.Next()%2 == 0) // There is a 50-50 chance that the worm would just go away + if (self.World.SharedRandom.Next() % 2 == 0) // There is a 50-50 chance that the worm would just go away { - self.CancelActivity(); - self.World.AddFrameEndTask(w => w.Remove(self)); - var wormManager = self.World.WorldActor.TraitOrDefault(); - if (wormManager != null) - wormManager.DecreaseWorms(); + self.CancelActivity(); + self.World.AddFrameEndTask(w => w.Remove(self)); + var wormManager = self.World.WorldActor.TraitOrDefault(); + if (wormManager != null) + wormManager.DecreaseWorms(); } else { - renderUnit.DefaultAnimation.ReplaceAnim("idle"); + renderUnit.DefaultAnimation.ReplaceAnim("idle"); } return NextActivity; } @@ -96,14 +118,17 @@ namespace OpenRA.Mods.D2k if (stance == AttackState.Burrowed) // Wait for the worm to get in position { // This is so that the worm cancels an attack against a target that has reached solid rock - if (positionable == null || !positionable.CanEnterCell(target.Actor.Location, null, false)) + if (!positionable.CanEnterCell(target.Actor.Location, null, false)) return NextActivity; var success = WormAttack(self); if (!success) + { + renderUnit.DefaultAnimation.ReplaceAnim("idle"); return NextActivity; + } - countdown = swallow.AttackSwallowInfo.ReturnTime; + countdown = swallow.Info.ReturnTime; stance = AttackState.ReturningUnderground; } diff --git a/OpenRA.Mods.D2k/WormManager.cs b/OpenRA.Mods.D2k/WormManager.cs index 70cc02d031..867d68e5c8 100644 --- a/OpenRA.Mods.D2k/WormManager.cs +++ b/OpenRA.Mods.D2k/WormManager.cs @@ -9,9 +9,10 @@ #endregion using System; +using System.Collections.Generic; using System.Drawing; using System.Linq; -using OpenRA.Mods.Common; +using OpenRA.Mods.Common.Traits; using OpenRA.Primitives; using OpenRA.Traits; @@ -21,33 +22,34 @@ namespace OpenRA.Mods.D2k class WormManagerInfo : ITraitInfo { [Desc("Minimum number of worms")] - public readonly int Minimum = 1; + public readonly int Minimum = 2; [Desc("Maximum number of worms")] - public readonly int Maximum = 8; + public readonly int Maximum = 4; [Desc("Average time (seconds) between worm spawn")] - public readonly int SpawnInterval = 180; + public readonly int SpawnInterval = 120; public readonly string WormSignNotification = "WormSign"; public readonly string WormSignature = "sandworm"; public readonly string WormOwnerPlayer = "Creeps"; - public object Create (ActorInitializer init) { return new WormManager(this, init.self); } + public object Create(ActorInitializer init) { return new WormManager(this, init.self); } } class WormManager : ITick { int countdown; int wormsPresent; - RadarPings radarPings; readonly WormManagerInfo info; readonly Lazy spawnPoints; + readonly Lazy radarPings; public WormManager(WormManagerInfo info, Actor self) { this.info = info; + radarPings = Exts.Lazy(() => self.World.WorldActor.Trait()); spawnPoints = Exts.Lazy(() => self.World.ActorsWithTrait().Select(x => x.Actor).ToArray()); } @@ -59,31 +61,40 @@ namespace OpenRA.Mods.D2k if (!spawnPoints.Value.Any()) return; - if (--countdown > 0) + // Apparantly someone doesn't want worms or the maximum number of worms has been reached + if (info.Maximum < 1 || wormsPresent >= info.Maximum) + return; + + if (--countdown > 0 && wormsPresent >= info.Minimum) return; countdown = info.SpawnInterval * 25; - if (wormsPresent < info.Maximum) - SpawnWorm(self); + + var wormLocations = new List(); + + wormLocations.Add(SpawnWorm(self)); + while (wormsPresent < info.Minimum) + wormLocations.Add(SpawnWorm(self)); + + AnnounceWormSign(self, wormLocations); } - void SpawnWorm (Actor self) + WPos SpawnWorm(Actor self) { - var spawnPosition = GetRandomSpawnPosition(self); - var spawnLocation = self.World.Map.CellContaining(spawnPosition); + var spawnPoint = GetRandomSpawnPoint(self); self.World.AddFrameEndTask(w => w.CreateActor(info.WormSignature, new TypeDictionary { new OwnerInit(w.Players.First(x => x.PlayerName == info.WormOwnerPlayer)), - new LocationInit(spawnLocation) + new LocationInit(spawnPoint.Location) })); wormsPresent++; - - AnnounceWormSign(self, spawnPosition); + + return spawnPoint.CenterPosition; } - WPos GetRandomSpawnPosition(Actor self) + Actor GetRandomSpawnPoint(Actor self) { - return spawnPoints.Value.Random(self.World.SharedRandom).CenterPosition; + return spawnPoints.Value.Random(self.World.SharedRandom); } public void DecreaseWorms() @@ -91,22 +102,17 @@ namespace OpenRA.Mods.D2k wormsPresent--; } - void AnnounceWormSign(Actor self, WPos wormSpawnPosition) + void AnnounceWormSign(Actor self, IEnumerable wormLocations) { - if (self.World.LocalPlayer == null) + if (self.World.LocalPlayer != null) + Sound.PlayNotification(self.World.Map.Rules, self.World.LocalPlayer, "Speech", info.WormSignNotification, self.World.LocalPlayer.Country.Race); + + if (radarPings.Value == null) return; - - Sound.PlayNotification(self.World.Map.Rules, self.World.LocalPlayer, "Speech", info.WormSignNotification, self.World.LocalPlayer.Country.Race); - if (radarPings == null) - { - if (self.World.WorldActor == null) - return; - - radarPings = self.World.WorldActor.TraitOrDefault(); - } + foreach (var wormLocation in wormLocations) + radarPings.Value.Add(() => true, wormLocation, Color.Red, 50); - radarPings.Add(() => true, wormSpawnPosition, Color.Red, 50); } } diff --git a/OpenRA.Mods.RA/Attack/AttackBase.cs b/OpenRA.Mods.RA/Attack/AttackBase.cs index 4a3f54a24b..89ba53efc7 100644 --- a/OpenRA.Mods.RA/Attack/AttackBase.cs +++ b/OpenRA.Mods.RA/Attack/AttackBase.cs @@ -14,7 +14,6 @@ using System.Drawing; using System.Linq; using OpenRA.GameRules; using OpenRA.Mods.RA.Buildings; -using OpenRA.Mods.RA.Move; using OpenRA.Traits; namespace OpenRA.Mods.RA @@ -39,6 +38,7 @@ namespace OpenRA.Mods.RA public IEnumerable Armaments { get { return GetArmaments(); } } protected Lazy facing; protected Lazy building; + protected Lazy positionable; protected Func> GetArmaments; readonly Actor self; @@ -56,6 +56,7 @@ namespace OpenRA.Mods.RA facing = Exts.Lazy(() => self.TraitOrDefault()); building = Exts.Lazy(() => self.TraitOrDefault()); + positionable = Exts.Lazy(() => self.Trait()); } protected virtual bool CanAttack(Actor self, Target target) @@ -146,8 +147,7 @@ namespace OpenRA.Mods.RA { if (Info.AttackRequiresEnteringCell) { - var positionable = self.TraitOrDefault(); - if (positionable == null || !positionable.CanEnterCell(t.Actor.Location, null, false)) + if (!positionable.Value.CanEnterCell(t.Actor.Location, null, false)) return false; } diff --git a/mods/cnc/rules/civilian.yaml b/mods/cnc/rules/civilian.yaml index f82793b58d..2e07980ef2 100644 --- a/mods/cnc/rules/civilian.yaml +++ b/mods/cnc/rules/civilian.yaml @@ -458,7 +458,7 @@ VICE: MuzzleSplitFacings: 8 AttackFrontal: AttackWander: - WanderMoveRadius: 4 + WanderMoveRadius: 2 RenderUnit: WithMuzzleFlash: SplitFacings: true diff --git a/mods/d2k/maps/death-depths.oramap b/mods/d2k/maps/death-depths.oramap index fd3b9bdfe761fa2dccd4d060b0870da1a35f16d6..4b5832580d31e5cdcb475caba352634392ae9dd0 100644 GIT binary patch delta 688 zcmV;h0#E&wXO(BLJ}?IY00;n)G=q~xFgJhdsEcp5aSdMAefnX$S-P#__OQD&MTDOu zBq7*B1C`Ua4HVIC7=e&W-LK8iYF9)lAtl_8^#JzL?2Bm6zY?;m-L&`uv^)6k-W@C< z7htQQ?pe>i-CNW5#Z8{e+kOCyD7a``N=hWMCJOy*Mp=7Wbp3Qg?gPdhu-sZ4G|!=L3_GF&ckW z<;H! zykc@5+8j^6aW+e0ssgAku-xvvC>xPwgLbE WVhaEO2mk;800000001D9a5E*h7DD*| delta 688 zcmV;h0#E&wXO(BLJ}?IW00;n3uVRx$FgJf1tBY^8alu~KefnX$S-P#__OQD)L4=x?daH>Y<2ILQ1$FYYXnuR7F(ruY~OCVOo3vdN}ycemFQn zF2L<=-4C_tib#ADk+IQwy&_~a4hLh4=-jlobqD90l169m!JSQt8gSsvtjcor;oX0B zHodKXc;!r`)c+Im#*AI%{DhbNUlP-4_OpYP+a924THP1XS~t!+zI*IpGSuCV;08jj zW_Mgc$frg>g7u?U%cz7qu+Z({^#;bl>c;=_YNK0^KTUY~>Y;Nt#x~VcVEhWxP213e z?uK8JrQUl?4;JbQ+}{>1$7ZPGlYtjS_TsegS=^7BO5Iha_2SzO+8X@c&n=UYF&cl> zelT+y=0RLUZSQ*h(0S&g+3Tfxl7vdW#2@Ulgv<$x@o2dEj{F^CXaXj#pZmQ-<#kFBlcm47Z2ZOP&T!Ajj*4`|gS6o#A=s zaiD>UK#P%~0u{JHmZ(586g+qY749ltYC=+lhDP9}CO8#n_Z2eGWW1y3H6cVo-6^30 zPks?S`1jd-34;CoI%71)cZhTTX@%csNx+`)uQH620eimMa8RfKDP^EPi7Y^}{Kepq zZ97(9V0tE&V>5q~?J^k)hWf5v3{CRR>drNj5i>j^GXN9-000O8P_JS|vC>xPwgLbE WVhaEO2mk;8000000001!a5E*DEJ+yv diff --git a/mods/d2k/maps/shellmap/map.yaml b/mods/d2k/maps/shellmap/map.yaml index 686209c4be..0d28aee1ae 100644 --- a/mods/d2k/maps/shellmap/map.yaml +++ b/mods/d2k/maps/shellmap/map.yaml @@ -121,6 +121,8 @@ Rules: -MPStartLocations: ResourceType@Spice: ValuePerUnit: 0 + WormManager: + Minimum: 1 Sequences: diff --git a/mods/ts/rules/infantry.yaml b/mods/ts/rules/infantry.yaml index b70348c923..78422e3035 100644 --- a/mods/ts/rules/infantry.yaml +++ b/mods/ts/rules/infantry.yaml @@ -583,7 +583,7 @@ DOGGIE: Weapon: FiendShard AttackFrontal: AttackWander: - WanderMoveRadius: 4 + WanderMoveRadius: 2 VISSML: Inherits: ^Infantry