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 0000000000..efefdfe496 Binary files /dev/null and b/mods/d2k/bits/wormspawner.shp differ diff --git a/mods/d2k/maps/death-depths.oramap b/mods/d2k/maps/death-depths.oramap index 1603fe195f..fd3b9bdfe7 100644 Binary files a/mods/d2k/maps/death-depths.oramap and b/mods/d2k/maps/death-depths.oramap differ diff --git a/mods/d2k/maps/shellmap/map.yaml b/mods/d2k/maps/shellmap/map.yaml index c0c57039ed..686209c4be 100644 --- a/mods/d2k/maps/shellmap/map.yaml +++ b/mods/d2k/maps/shellmap/map.yaml @@ -43,7 +43,7 @@ Players: Name: Creeps NonCombatant: True Race: atreides - Enemies: Atreides,Harkonnen + Enemies: Atreides, Harkonnen Actors: Actor4: spicebloom @@ -106,7 +106,7 @@ Actors: Actor41: guntowera Location: 46,39 Owner: Harkonnen - Actor42: WormSpawnLocation + Actor42: wormspawner Location: 46,64 Owner: Creeps diff --git a/mods/d2k/rules/arrakis.yaml b/mods/d2k/rules/arrakis.yaml index 9c2ae3560c..bd549fbc84 100644 --- a/mods/d2k/rules/arrakis.yaml +++ b/mods/d2k/rules/arrakis.yaml @@ -34,30 +34,21 @@ SANDWORM: Sand: 100 Dune: 100 Spice: 100 - Rock: 100 # TEMP TargetableUnit: TargetTypes: Underground RevealsShroud: Range: 32c0 RenderUnit: BodyOrientation: - BelowUnits: HiddenUnderFog: Sandworm: - WanderMoveRadius: 10 - SelectionDecorations: # TEMP - Selectable: # TEMP - Voice: WormVoice # TEMP AppearsOnRadar: UseLocation: yes AttackSwallow: - AttackRequiresEnteringCell: TRUE + AttackRequiresEnteringCell: true AttackMove: + AttackWander: AutoTarget: ScanRadius: 32 Armament: - Weapon: WormJaw - -WormSpawnLocation: - Immobile: - WormSpawner: \ No newline at end of file + Weapon: WormJaw \ No newline at end of file diff --git a/mods/d2k/rules/misc.yaml b/mods/d2k/rules/misc.yaml index 42ae5e839c..57084a2a1c 100644 --- a/mods/d2k/rules/misc.yaml +++ b/mods/d2k/rules/misc.yaml @@ -149,3 +149,9 @@ CAMERA: Range: 8c0 BodyOrientation: +wormspawner: + Immobile: + OccupiesSpace: false + RenderEditorOnly: + BodyOrientation: + WormSpawner: diff --git a/mods/d2k/rules/world.yaml b/mods/d2k/rules/world.yaml index 7568cfd118..7b70b48083 100644 --- a/mods/d2k/rules/world.yaml +++ b/mods/d2k/rules/world.yaml @@ -11,7 +11,6 @@ World: BuildingInfluence: ChooseBuildTabOnSelect: WormManager: - SpawnInterval: 10 CrateSpawner: Minimum: 0 Maximum: 2 diff --git a/mods/d2k/sequences/misc.yaml b/mods/d2k/sequences/misc.yaml index 45530895db..a4dcf64b14 100644 --- a/mods/d2k/sequences/misc.yaml +++ b/mods/d2k/sequences/misc.yaml @@ -267,6 +267,11 @@ waypoint: Start: 0 Length: * +wormspawner: + idle: + Start: 0 + Length: * + sietch: idle: DATA Start: 2998