diff --git a/OpenRA.Game/GameRules/WeaponInfo.cs b/OpenRA.Game/GameRules/WeaponInfo.cs index ae67d887e7..37962ee8f4 100644 --- a/OpenRA.Game/GameRules/WeaponInfo.cs +++ b/OpenRA.Game/GameRules/WeaponInfo.cs @@ -172,6 +172,9 @@ namespace OpenRA.GameRules if (target.Type == TargetType.Terrain) { var cell = target.CenterPosition.ToCPos(); + if (!world.Map.IsInMap(cell)) + return false; + if (ValidTargets.Contains("Ground") && world.GetTerrainType(cell) != "Water") return true; diff --git a/OpenRA.Game/WorldUtils.cs b/OpenRA.Game/WorldUtils.cs index eb5490e097..e52195228f 100755 --- a/OpenRA.Game/WorldUtils.cs +++ b/OpenRA.Game/WorldUtils.cs @@ -124,6 +124,15 @@ namespace OpenRA r.Next(w.Map.Bounds.Top, w.Map.Bounds.Bottom)); } + public static WRange DistanceToMapEdge(this World w, WPos pos, WVec dir) + { + var tl = w.Map.Bounds.TopLeftAsCPos().TopLeft; + var br = w.Map.Bounds.BottomRightAsCPos().BottomRight; + var x = dir.X == 0 ? int.MaxValue : ((dir.X < 0 ? tl.X : br.X) - pos.X) / dir.X; + var y = dir.Y == 0 ? int.MaxValue : ((dir.Y < 0 ? tl.Y : br.Y) - pos.Y) / dir.Y; + return new WRange(Math.Min(x, y) * dir.Length); + } + public static bool HasVoices(this Actor a) { var selectable = a.Info.Traits.GetOrDefault(); diff --git a/OpenRA.Mods.RA/Air/Aircraft.cs b/OpenRA.Mods.RA/Air/Aircraft.cs index 73bffe7911..e96664438a 100755 --- a/OpenRA.Mods.RA/Air/Aircraft.cs +++ b/OpenRA.Mods.RA/Air/Aircraft.cs @@ -57,13 +57,17 @@ namespace OpenRA.Mods.RA.Air if (init.Contains()) SetPosition(self, init.Get()); - this.Facing = init.Contains() ? init.Get() : info.InitialFacing; if (init.Contains()) { var z = init.Get() * 1024 / Game.CellSize; SetPosition(self, CenterPosition + new WVec(0, 0, z - CenterPosition.Z)); } + + if (init.Contains()) + SetPosition(self, init.Get()); + + this.Facing = init.Contains() ? init.Get() : info.InitialFacing; } public Actor GetActorBelow() diff --git a/OpenRA.Mods.RA/Armament.cs b/OpenRA.Mods.RA/Armament.cs index a4c1914c3a..6a2dcda31a 100644 --- a/OpenRA.Mods.RA/Armament.cs +++ b/OpenRA.Mods.RA/Armament.cs @@ -101,7 +101,8 @@ namespace OpenRA.Mods.RA // The world coordinate model uses Actor.Orientation public void CheckFire(Actor self, AttackBase attack, IFacing facing, Target target) { - if (FireDelay > 0) return; + if (FireDelay > 0) + return; var limitedAmmo = self.TraitOrDefault(); if (limitedAmmo != null && !limitedAmmo.HasAmmo()) @@ -113,7 +114,7 @@ namespace OpenRA.Mods.RA if (!target.IsInRange(self.CenterPosition, range)) return; - if (target.IsInRange(self.CenterPosition, minRange)) + if (minRange != WRange.Zero && target.IsInRange(self.CenterPosition, minRange)) return; if (!Weapon.IsValidAgainst(target, self.World)) diff --git a/OpenRA.Mods.RA/AttackBomber.cs b/OpenRA.Mods.RA/AttackBomber.cs new file mode 100644 index 0000000000..29b020d0da --- /dev/null +++ b/OpenRA.Mods.RA/AttackBomber.cs @@ -0,0 +1,85 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using OpenRA.FileFormats; +using OpenRA.GameRules; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA +{ + class AttackBomberInfo : AttackBaseInfo + { + [Desc("Armament name")] + public readonly string Bombs = "primary"; + + [Desc("Armament name")] + public readonly string Guns = "secondary"; + public readonly int FacingTolerance = 2; + + public override object Create(ActorInitializer init) { return new AttackBomber(init.self, this); } + } + + class AttackBomber : AttackBase, ISync + { + AttackBomberInfo info; + [Sync] Target target; + + public AttackBomber(Actor self, AttackBomberInfo info) + : base(self) + { + this.info = info; + } + + public override void Tick(Actor self) + { + base.Tick(self); + + var facing = self.TraitOrDefault(); + var cp = self.CenterPosition; + var bombTarget = Target.FromPos(cp - new WVec(0, 0, cp.Z)); + + // Bombs drop anywhere in range + foreach (var a in Armaments.Where(a => a.Info.Name == info.Bombs)) + { + var range = new WRange((int)(1024 * a.Weapon.Range)); + if (!target.IsInRange(self.CenterPosition, range)) + continue; + + a.CheckFire(self, this, facing, bombTarget); + } + + // Guns only fire when approaching the target + var facingToTarget = Util.GetFacing(target.CenterPosition - self.CenterPosition, facing.Facing); + if (Math.Abs(facingToTarget - facing.Facing) % 256 > info.FacingTolerance) + return; + + foreach (var a in Armaments.Where(a => a.Info.Name == info.Guns)) + { + var range = new WRange((int)(1024 * a.Weapon.Range)); + if (!target.IsInRange(self.CenterPosition, range)) + continue; + + var t = Target.FromPos(cp - new WVec(0, range.Range / 2, cp.Z).Rotate(WRot.FromFacing(facing.Facing))); + a.CheckFire(self, this, facing, t); + } + } + + public void SetTarget(WPos pos) { target = Target.FromPos(pos); } + + public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove) + { + // TODO: Player controlled units want this too! + throw new NotImplementedException("CarpetBomb requires a scripted target"); + } + } +} diff --git a/OpenRA.Mods.RA/CarpetBomb.cs b/OpenRA.Mods.RA/CarpetBomb.cs deleted file mode 100644 index c6c34c5627..0000000000 --- a/OpenRA.Mods.RA/CarpetBomb.cs +++ /dev/null @@ -1,77 +0,0 @@ -#region Copyright & License Information -/* - * Copyright 2007-2011 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.Linq; -using OpenRA.GameRules; -using OpenRA.Traits; - -namespace OpenRA.Mods.RA -{ - class CarpetBombInfo : ITraitInfo - { - [WeaponReference] - public readonly string Weapon = null; - public readonly int Range = 3; - - public object Create(ActorInitializer init) { return new CarpetBomb(this); } - } - - // TODO: maybe integrate this better with the normal weapons system? - class CarpetBomb : ITick, ISync - { - CarpetBombInfo info; - Target target; - - [Sync] int dropDelay; - [Sync] WRange range; - - public CarpetBomb(CarpetBombInfo info) - { - this.info = info; - - // TODO: Push this conversion into the yaml - range = WRange.FromCells(info.Range); - } - - public void SetTarget(CPos targetCell) { target = Target.FromCell(targetCell); } - - public void Tick(Actor self) - { - if (!target.IsInRange(self.CenterPosition, range)) - return; - - var limitedAmmo = self.TraitOrDefault(); - if (limitedAmmo != null && !limitedAmmo.HasAmmo()) - return; - - if (--dropDelay <= 0) - { - var weapon = Rules.Weapons[info.Weapon.ToLowerInvariant()]; - dropDelay = weapon.ROF; - - var pos = self.CenterPosition; - var args = new ProjectileArgs - { - Weapon = weapon, - Facing = self.Trait().Facing, - - Source = pos, - SourceActor = self, - PassiveTarget = pos - new WVec(0, 0, pos.Z) - }; - - self.World.Add(args.Weapon.Projectile.Create(args)); - - if (args.Weapon.Report != null && args.Weapon.Report.Any()) - Sound.Play(args.Weapon.Report.Random(self.World.SharedRandom), self.CenterPosition); - } - } - } -} diff --git a/OpenRA.Mods.RA/Effects/GravityBomb.cs b/OpenRA.Mods.RA/Effects/GravityBomb.cs index edeb784b8e..e021df4a72 100644 --- a/OpenRA.Mods.RA/Effects/GravityBomb.cs +++ b/OpenRA.Mods.RA/Effects/GravityBomb.cs @@ -54,8 +54,9 @@ namespace OpenRA.Mods.RA.Effects if (pos.Z <= args.PassiveTarget.Z) { + pos += new WVec(0, 0, args.PassiveTarget.Z - pos.Z); world.AddFrameEndTask(w => w.Remove(this)); - Combat.DoImpacts(args.PassiveTarget, args.SourceActor, args.Weapon, args.FirepowerModifier); + Combat.DoImpacts(pos, args.SourceActor, args.Weapon, args.FirepowerModifier); } anim.Tick(); diff --git a/OpenRA.Mods.RA/Missions/MissionUtils.cs b/OpenRA.Mods.RA/Missions/MissionUtils.cs index 75d6ddbfc9..5b8cf38044 100644 --- a/OpenRA.Mods.RA/Missions/MissionUtils.cs +++ b/OpenRA.Mods.RA/Missions/MissionUtils.cs @@ -98,7 +98,7 @@ namespace OpenRA.Mods.RA.Missions new FacingInit(Util.GetFacing(location - entry, 0)), new AltitudeInit(Rules.Info["badr.bomber"].Traits.Get().CruiseAltitude), }); - badger.Trait().SetTarget(location); + badger.Trait().SetTarget(location.CenterPosition); badger.QueueActivity(Fly.ToCell(location)); badger.QueueActivity(new FlyOffMap()); badger.QueueActivity(new RemoveSelf()); diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index 50f6ce7bbb..c627a2aef8 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -179,7 +179,6 @@ - @@ -472,6 +471,7 @@ + diff --git a/OpenRA.Mods.RA/SupportPowers/AirstrikePower.cs b/OpenRA.Mods.RA/SupportPowers/AirstrikePower.cs index be55c46731..a04eb7ebf3 100755 --- a/OpenRA.Mods.RA/SupportPowers/AirstrikePower.cs +++ b/OpenRA.Mods.RA/SupportPowers/AirstrikePower.cs @@ -1,6 +1,6 @@ #region Copyright & License Information /* - * Copyright 2007-2011 The OpenRA Developers (see AUTHORS) + * Copyright 2007-2013 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, @@ -8,6 +8,7 @@ */ #endregion +using System; using OpenRA.FileFormats; using OpenRA.Mods.RA.Activities; using OpenRA.Mods.RA.Air; @@ -19,59 +20,81 @@ namespace OpenRA.Mods.RA { [ActorReference] public readonly string UnitType = "badr.bomber"; + public readonly int SquadSize = 1; + public readonly WVec SquadOffset = new WVec(-1536, 1536, 0); + + public readonly int QuantizedFacings = 32; + public readonly WRange Cordon = new WRange(5120); + [ActorReference] public readonly string FlareType = null; - - public readonly int FlareTime = 25 * 60 * 2; // 2 minutes + public readonly int FlareTime = 3000; // 2 minutes public override object Create(ActorInitializer init) { return new AirstrikePower(init.self, this); } } class AirstrikePower : SupportPower { - public AirstrikePower(Actor self, AirstrikePowerInfo info) : base(self, info) { } + public AirstrikePower(Actor self, AirstrikePowerInfo info) + : base(self, info) { } public override void Activate(Actor self, Order order) { - var startPos = self.World.ChooseRandomEdgeCell(); + var info = Info as AirstrikePowerInfo; + + var attackFacing = Util.QuantizeFacing(self.World.SharedRandom.Next(256), info.QuantizedFacings) * (256 / info.QuantizedFacings); + var attackRotation = WRot.FromFacing(attackFacing); + var delta = new WVec(0, -1024, 0).Rotate(attackRotation); + + var altitude = Rules.Info[info.UnitType].Traits.Get().CruiseAltitude * 1024 / Game.CellSize; + var target = order.TargetLocation.CenterPosition + new WVec(0, 0, altitude); + var startEdge = target - (self.World.DistanceToMapEdge(target, -delta) + info.Cordon).Range * delta / 1024; + var finishEdge = target + (self.World.DistanceToMapEdge(target, delta) + info.Cordon).Range * delta / 1024; + self.World.AddFrameEndTask(w => { - var info = (Info as AirstrikePowerInfo); + var notification = self.Owner.IsAlliedWith(self.World.RenderPlayer) ? Info.LaunchSound : Info.IncomingSound; + Sound.Play(notification); - if (self.Owner.IsAlliedWith(self.World.RenderPlayer)) - Sound.Play(Info.LaunchSound); - else - Sound.Play(Info.IncomingSound); - - var flare = info.FlareType != null ? w.CreateActor(info.FlareType, new TypeDictionary + Actor flare = null; + if (info.FlareType != null) { - new LocationInit( order.TargetLocation ), - new OwnerInit( self.Owner ), - }) : null; + flare = w.CreateActor(info.FlareType, new TypeDictionary + { + new LocationInit(order.TargetLocation), + new OwnerInit(self.Owner), + }); - if (flare != null) - { flare.QueueActivity(new Wait(info.FlareTime)); flare.QueueActivity(new RemoveSelf()); } - var a = w.CreateActor(info.UnitType, new TypeDictionary + for (var i = -info.SquadSize / 2; i <= info.SquadSize / 2; i++) { - new LocationInit( startPos ), - new OwnerInit( self.Owner ), - new FacingInit( Util.GetFacing(order.TargetLocation - startPos, 0) ), - new AltitudeInit( Rules.Info[info.UnitType].Traits.Get().CruiseAltitude ), - }); - a.Trait().SetTarget(order.TargetLocation); + // Even-sized squads skip the lead plane + if (i == 0 && (info.SquadSize & 1) == 0) + continue; - a.CancelActivity(); - a.QueueActivity(Fly.ToCell(order.TargetLocation)); + // Includes the 90 degree rotation between body and world coordinates + var so = info.SquadOffset; + var spawnOffset = new WVec(i*so.Y, -Math.Abs(i)*so.X, 0).Rotate(attackRotation); + var targetOffset = new WVec(i*so.Y, 0, 0).Rotate(attackRotation); - if (flare != null) - a.QueueActivity(new CallFunc(() => flare.Destroy())); + var a = w.CreateActor(info.UnitType, new TypeDictionary + { + new CenterPositionInit(startEdge + spawnOffset), + new OwnerInit(self.Owner), + new FacingInit(attackFacing), + }); - a.QueueActivity(new FlyOffMap()); - a.QueueActivity(new RemoveSelf()); + a.Trait().SetTarget(target + targetOffset); + + if (flare != null) + a.QueueActivity(new CallFunc(() => flare.Destroy())); + + a.QueueActivity(Fly.ToPos(finishEdge + spawnOffset)); + a.QueueActivity(new RemoveSelf()); + } }); } } diff --git a/mods/cnc/rules/aircraft.yaml b/mods/cnc/rules/aircraft.yaml index 1608909af2..1a1e979880 100644 --- a/mods/cnc/rules/aircraft.yaml +++ b/mods/cnc/rules/aircraft.yaml @@ -166,6 +166,18 @@ C17: -GainsExperience: FlyAwayOnIdle: RejectsOrders: + Contrail@1: + Offset: -261,-650,0 + TrailLength: 15 + Contrail@2: + Offset: -85,-384,0 + TrailLength: 16 + Contrail@3: + Offset: -85,384,0 + TrailLength: 16 + Contrail@4: + Offset: -261,650,0 + TrailLength: 15 A10: Inherits: ^Plane @@ -185,15 +197,29 @@ A10: Range: 12 RenderUnit: WithShadow: - LimitedAmmo: - Ammo: 10 - CarpetBomb: + AttackBomber: + Guns: gun + Bombs: bombs + Armament@GUNS: + Name: gun + Weapon: Vulcan + LocalOffset: 1024,0,-85 + WithMuzzleFlash@SECONDARY: + Armament: gun + Armament@BOMBS: + Name: bombs Weapon: Napalm - Range: 3 + LocalOffset: 0,-256,-43, 0,256,-43 -Selectable: -GainsExperience: FlyAwayOnIdle: RejectsOrders: + Contrail@1: + Offset: -640,171,0 + TrailLength: 15 + Contrail@2: + Offset: -640,-171,0 + TrailLength: 15 TRAN.Husk: Inherits: ^HelicopterHusk diff --git a/mods/cnc/rules/structures.yaml b/mods/cnc/rules/structures.yaml index 976fe41ef7..aab0d9fb29 100644 --- a/mods/cnc/rules/structures.yaml +++ b/mods/cnc/rules/structures.yaml @@ -375,6 +375,8 @@ HQ: AirstrikePower: Icon: airstrike ChargeTime: 180 + SquadSize: 3 + QuantizedFacings: 8 Description: Air Strike LongDesc: Deploy an aerial napalm strike.\nBurns buildings and infantry along a line. EndChargeSound: airredy1.aud diff --git a/mods/cnc/sequences/aircraft.yaml b/mods/cnc/sequences/aircraft.yaml index e85b0ac724..324d9f93e2 100644 --- a/mods/cnc/sequences/aircraft.yaml +++ b/mods/cnc/sequences/aircraft.yaml @@ -60,5 +60,9 @@ a10: idle: Start: 0 Facings: 32 + muzzle: minigun + Start: 0 + Length: 6 + Facings: 8 icon: a10icnh Start: 0 \ No newline at end of file diff --git a/mods/cnc/weapons.yaml b/mods/cnc/weapons.yaml index 418253060e..4be159906f 100644 --- a/mods/cnc/weapons.yaml +++ b/mods/cnc/weapons.yaml @@ -725,8 +725,30 @@ TowerMissle: SmudgeType: Crater Damage: 45 -Napalm: +Vulcan: + ValidTargets: Ground, Water ROF: 2 + Range: 6 + Report: gun5.aud + Projectile: Bullet + Speed: 100 + Warhead: + Damage: 75 + Spread: 10 + Versus: + None: 100% + Wood: 50% + Light: 100% + Heavy: 25% + InfDeath: 2 + Explosion: piffs + +Napalm: + ValidTargets: Ground, Water + ROF: 4 + Range: 2 + Burst: 2 + BurstDelay: 2 Projectile: GravityBomb Image: BOMBLET Warhead: @@ -738,9 +760,10 @@ Napalm: Heavy: 55% InfDeath: 5 Explosion: med_napalm + WaterExplosion: med_napalm ImpactSound: flamer2.aud SmudgeType: Scorch - Damage: 130 + Damage: 50 Napalm.Crate: Warhead: diff --git a/mods/d2k/rules/aircraft.yaml b/mods/d2k/rules/aircraft.yaml index 132e5ffb12..1072ee9a93 100644 --- a/mods/d2k/rules/aircraft.yaml +++ b/mods/d2k/rules/aircraft.yaml @@ -94,8 +94,8 @@ ORNI: HuskActor: ORNI.Husk ORNI.bomber: - CarpetBomb: - Range: 3 + AttackBomber: + Armament: Weapon: Napalm Inherits: ^Plane Health: diff --git a/mods/d2k/weapons.yaml b/mods/d2k/weapons.yaml index 4c46be0add..268520cb8e 100644 --- a/mods/d2k/weapons.yaml +++ b/mods/d2k/weapons.yaml @@ -482,6 +482,7 @@ ParaBomb: Napalm: ROF: 2 + Range: 3 Projectile: GravityBomb Image: BOMBS Warhead: diff --git a/mods/ra/maps/Fort-Lonestar/map.yaml b/mods/ra/maps/Fort-Lonestar/map.yaml index a90523ad52..288d4ed84c 100644 --- a/mods/ra/maps/Fort-Lonestar/map.yaml +++ b/mods/ra/maps/Fort-Lonestar/map.yaml @@ -1002,8 +1002,8 @@ Rules: Armor: Type: Concrete BADR.Bomber: - CarpetBomb: - Range: 3 + AttackBomber: + Armament: Weapon: ParaBomb Inherits: ^Plane Health: diff --git a/mods/ra/rules/aircraft.yaml b/mods/ra/rules/aircraft.yaml index fa1da4cb27..50b6ecfa78 100644 --- a/mods/ra/rules/aircraft.yaml +++ b/mods/ra/rules/aircraft.yaml @@ -36,8 +36,8 @@ BADR: RejectsOrders: BADR.Bomber: - CarpetBomb: - Range: 3 + AttackBomber: + Armament: Weapon: ParaBomb Inherits: ^Plane Health: diff --git a/mods/ra/weapons.yaml b/mods/ra/weapons.yaml index e6ef429de8..219395caab 100644 --- a/mods/ra/weapons.yaml +++ b/mods/ra/weapons.yaml @@ -898,7 +898,7 @@ DepthCharge: ParaBomb: ROF: 10 - Range: 4.5 + Range: 3 Report: CHUTE1.AUD Projectile: GravityBomb Image: PARABOMB