From 1057f8ff3b05f0ac47f22c7935e9f497dbff26aa Mon Sep 17 00:00:00 2001 From: penev92 Date: Sun, 2 Nov 2014 14:17:52 +0200 Subject: [PATCH] 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