diff --git a/OpenRa.Game/OpenRa.Game.csproj b/OpenRa.Game/OpenRa.Game.csproj index 57c74ffc8b..75a5864a8e 100644 --- a/OpenRa.Game/OpenRa.Game.csproj +++ b/OpenRa.Game/OpenRa.Game.csproj @@ -110,10 +110,13 @@ - + + + + @@ -173,7 +176,9 @@ + + diff --git a/OpenRa.Game/Traits/Activities/FlyAttack.cs b/OpenRa.Game/Traits/Activities/FlyAttack.cs new file mode 100644 index 0000000000..21baf5cba4 --- /dev/null +++ b/OpenRa.Game/Traits/Activities/FlyAttack.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OpenRa.Game.Traits.Activities +{ + class FlyAttack : IActivity + { + public IActivity NextActivity { get; set; } + Actor Target; + + public FlyAttack(Actor target) { Target = target; } + + public IActivity Tick(Actor self) + { + if (Target == null || Target.IsDead) + return NextActivity; + + var limitedAmmo = self.traits.GetOrDefault(); + if (limitedAmmo != null && !limitedAmmo.HasAmmo()) + return NextActivity; + + return Util.SequenceActivities( + new Fly(Target.CenterLocation), + new FlyTimed(50, 20), + this); + } + + public void Cancel(Actor self) { Target = null; NextActivity = null; } + } +} diff --git a/OpenRa.Game/Traits/Activities/HeliAttack.cs b/OpenRa.Game/Traits/Activities/HeliAttack.cs new file mode 100644 index 0000000000..65c6f44ef3 --- /dev/null +++ b/OpenRa.Game/Traits/Activities/HeliAttack.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OpenRa.Game.Traits.Activities +{ + class HeliAttack : IActivity + { + Actor target; + const int CruiseAltitude = 20; + public HeliAttack( Actor target ) { this.target = target; } + + public IActivity NextActivity { get; set; } + + public IActivity Tick(Actor self) + { + if (target == null || target.IsDead) + return NextActivity; + + var limitedAmmo = self.traits.GetOrDefault(); + if (limitedAmmo != null && !limitedAmmo.HasAmmo()) + return NextActivity; + + var unit = self.traits.Get(); + + if (unit.Altitude != CruiseAltitude) + { + unit.Altitude += Math.Sign(CruiseAltitude - unit.Altitude); + return this; + } + + var range = Rules.WeaponInfo[ self.Info.Primary ].Range - 1; + var dist = target.CenterLocation - self.CenterLocation; + + var desiredFacing = Util.GetFacing(dist, unit.Facing); + Util.TickFacing(ref unit.Facing, desiredFacing, self.Info.ROT); + + if (!float2.WithinEpsilon(float2.Zero, dist, range * Game.CellSize)) + { + var rawSpeed = .2f * Util.GetEffectiveSpeed(self); + self.CenterLocation += (rawSpeed / dist.Length) * dist; + self.Location = ((1 / 24f) * self.CenterLocation).ToInt2(); + } + + /* todo: maintain seperation wrt other helis */ + return this; + } + + public void Cancel(Actor self) { target = null; NextActivity = null; } + } +} diff --git a/OpenRa.Game/Traits/Activities/HeliFly.cs b/OpenRa.Game/Traits/Activities/HeliFly.cs new file mode 100644 index 0000000000..c27de496f1 --- /dev/null +++ b/OpenRa.Game/Traits/Activities/HeliFly.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OpenRa.Game.Traits.Activities +{ + class HeliFly : IActivity + { + const int CruiseAltitude = 20; + readonly float2 Dest; + public HeliFly(float2 dest) + { + Dest = dest; + } + + public IActivity NextActivity { get; set; } + bool isCanceled; + + public IActivity Tick(Actor self) + { + if (isCanceled) + return NextActivity; + + var unit = self.traits.Get(); + + if (unit.Altitude != CruiseAltitude) + { + unit.Altitude += Math.Sign(CruiseAltitude - unit.Altitude); + return this; + } + + var dist = Dest - self.CenterLocation; + if (float2.WithinEpsilon(float2.Zero, dist, 10)) + return NextActivity; + + var desiredFacing = Util.GetFacing(dist, unit.Facing); + Util.TickFacing(ref unit.Facing, desiredFacing, self.Info.ROT); + + var rawSpeed = .2f * Util.GetEffectiveSpeed(self); + self.CenterLocation += (rawSpeed / dist.Length) * dist; + self.Location = ((1 / 24f) * self.CenterLocation).ToInt2(); + + return this; + } + + public void Cancel(Actor self) { isCanceled = true; NextActivity = null; } + } +} diff --git a/OpenRa.Game/Traits/Activities/Circle.cs b/OpenRa.Game/Traits/Activities/HeliLand.cs similarity index 52% rename from OpenRa.Game/Traits/Activities/Circle.cs rename to OpenRa.Game/Traits/Activities/HeliLand.cs index 6f8eaefffd..eea8171415 100644 --- a/OpenRa.Game/Traits/Activities/Circle.cs +++ b/OpenRa.Game/Traits/Activities/HeliLand.cs @@ -5,26 +5,26 @@ using System.Text; namespace OpenRa.Game.Traits.Activities { - class Circle : IActivity + class HeliLand : IActivity { - public IActivity NextActivity { get; set; } - bool isCanceled; - readonly int2 Cell; + public HeliLand(bool requireSpace) { this.requireSpace = requireSpace; } - public Circle(int2 cell) { Cell = cell; } + bool requireSpace; + bool isCanceled; + public IActivity NextActivity { get; set; } public IActivity Tick(Actor self) { if (isCanceled) return NextActivity; var unit = self.traits.Get(); - return new Fly(Util.CenterOfCell(Cell)) - { - NextActivity = - new FlyTimed(50, 20) - { - NextActivity = this - } - }; + if (unit.Altitude == 0) + return NextActivity; + + if (requireSpace && !Game.IsCellBuildable(self.Location, UnitMovementType.Foot)) + return this; // fail to land if no space + + --unit.Altitude; + return this; } public void Cancel(Actor self) { isCanceled = true; NextActivity = null; } diff --git a/OpenRa.Game/Traits/Activities/ReturnToBase.cs b/OpenRa.Game/Traits/Activities/ReturnToBase.cs index ee7af24ef4..20a746fac4 100644 --- a/OpenRa.Game/Traits/Activities/ReturnToBase.cs +++ b/OpenRa.Game/Traits/Activities/ReturnToBase.cs @@ -8,13 +8,34 @@ namespace OpenRa.Game.Traits.Activities class ReturnToBase : IActivity { public IActivity NextActivity { get; set; } + bool isCanceled; + bool isCalculated; + Actor dest; - readonly float2 w1, w2, w3; /* tangent points to turn circles */ - readonly float2 landPoint; + float2 w1, w2, w3; /* tangent points to turn circles */ + float2 landPoint; - public ReturnToBase(Actor self, float2 landPos) + Actor ChooseAirfield(Actor self) { + // todo: handle reservations + + var airfield = Game.world.Actors + .Where(a => a.Info == Rules.UnitInfo["AFLD"] + && a.Owner == self.Owner) + .FirstOrDefault(); + + if (airfield == null) + throw new NotImplementedException("nowhere to land; what to do?"); + + return airfield; + } + + void Calculate(Actor self) + { + if (dest == null) dest = ChooseAirfield(self); + + var landPos = dest.CenterLocation; var unit = self.traits.Get(); var speed = .2f * Util.GetEffectiveSpeed(self); var approachStart = landPos - new float2(unit.Altitude * speed, 0); @@ -44,22 +65,26 @@ namespace OpenRa.Game.Traits.Activities w2 = c2 + f; w3 = approachStart; landPoint = landPos; + + isCalculated = true; + } + + public ReturnToBase(Actor self, Actor dest) + { + this.dest = dest; } public IActivity Tick(Actor self) { if (isCanceled) return NextActivity; - var unit = self.traits.Get(); - return new Fly(w1) - { - NextActivity = new Fly(w2) - { - NextActivity = new Fly(w3) - { - NextActivity = new Land(landPoint) - } - } - }; + if (!isCalculated) + Calculate(self); + + return Util.SequenceActivities( + new Fly(w1), + new Fly(w2), + new Fly(w3), + new Land(landPoint)); } public void Cancel(Actor self) { isCanceled = true; NextActivity = null; } diff --git a/OpenRa.Game/Traits/AttackHeli.cs b/OpenRa.Game/Traits/AttackHeli.cs new file mode 100644 index 0000000000..093870a0dd --- /dev/null +++ b/OpenRa.Game/Traits/AttackHeli.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using OpenRa.Game.Traits.Activities; + +namespace OpenRa.Game.Traits +{ + class AttackHeli : AttackBase + { + public AttackHeli(Actor self) : base(self) { } + + const int facingTolerance = 20; + public override void Tick(Actor self) + { + base.Tick(self); + + if (target == null) return; + + var unit = self.traits.Get(); + var facingToTarget = Util.GetFacing(target.CenterLocation - self.CenterLocation, unit.Facing); + + if (Math.Abs(facingToTarget - unit.Facing) % 256 < facingTolerance) + DoAttack(self); + } + + protected override void QueueAttack(Actor self, Order order) + { + self.CancelActivity(); + self.QueueActivity(new HeliAttack(order.TargetActor)); + target = order.TargetActor; + // todo: fly home + } + } +} diff --git a/OpenRa.Game/Traits/AttackPlane.cs b/OpenRa.Game/Traits/AttackPlane.cs new file mode 100644 index 0000000000..427096b62b --- /dev/null +++ b/OpenRa.Game/Traits/AttackPlane.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using OpenRa.Game.Traits.Activities; + +namespace OpenRa.Game.Traits +{ + // yet another ugly trait that does two things: + // - plane-specific attack order dispatch + // - forward-facing attack with a tolerance + + class AttackPlane : AttackBase + { + const int facingTolerance = 20; + + public AttackPlane(Actor self) : base(self) { } + + public override void Tick(Actor self) + { + base.Tick(self); + + if (target == null) return; + + var unit = self.traits.Get(); + var facingToTarget = Util.GetFacing(target.CenterLocation - self.CenterLocation, unit.Facing); + + if (Math.Abs(facingToTarget - unit.Facing) % 256 < facingTolerance) + DoAttack(self); + } + + protected override void QueueAttack(Actor self, Order order) + { + self.CancelActivity(); + target = order.TargetActor; + self.QueueActivity(new FlyAttack(order.TargetActor)); + self.QueueActivity(new ReturnToBase(self, null)); + } + } +} diff --git a/OpenRa.Game/Traits/AttackTurreted.cs b/OpenRa.Game/Traits/AttackTurreted.cs index 3d9d40bd9d..256bd25379 100755 --- a/OpenRa.Game/Traits/AttackTurreted.cs +++ b/OpenRa.Game/Traits/AttackTurreted.cs @@ -41,7 +41,7 @@ namespace OpenRa.Game.Traits self.QueueActivity( new Traits.Activities.Follow( order.TargetActor, Math.Max( 0, (int)Rules.WeaponInfo[ weapon ].Range - RangeTolerance ) ) ); - self.traits.Get().target = order.TargetActor; + target = order.TargetActor; } bool buildComplete = false; diff --git a/OpenRa.Game/Traits/Helicopter.cs b/OpenRa.Game/Traits/Helicopter.cs index 400456f832..0c1d5b1a82 100644 --- a/OpenRa.Game/Traits/Helicopter.cs +++ b/OpenRa.Game/Traits/Helicopter.cs @@ -1,19 +1,13 @@ using System; using System.Linq; using OpenRa.Game.GameRules; +using OpenRa.Game.Traits.Activities; namespace OpenRa.Game.Traits { - class Helicopter : ITick, IOrder, IMovement + class Helicopter : IOrder, IMovement { - public int2 targetLocation; - - const int CruiseAltitude = 20; - - public Helicopter(Actor self) - { - targetLocation = self.Location; - } + public Helicopter(Actor self) {} public Order IssueOrder(Actor self, int2 xy, MouseInput mi, Actor underCursor) { @@ -27,61 +21,15 @@ namespace OpenRa.Game.Traits public void ResolveOrder( Actor self, Order order ) { - if( order.OrderString == "Move" ) + if (order.OrderString == "Move") { - targetLocation = order.TargetLocation; - - var attackBase = self.traits.WithInterface().FirstOrDefault(); - if( attackBase != null ) - attackBase.target = null; /* move cancels attack order */ + self.CancelActivity(); + self.QueueActivity(new HeliFly(Util.CenterOfCell(order.TargetLocation))); + self.QueueActivity(new HeliLand(true)); } } - public void Tick(Actor self) - { - var unit = self.traits.Get(); - - if (self.Location != targetLocation) - { - var dist = Util.CenterOfCell(targetLocation) - self.CenterLocation; - var desiredFacing = Util.GetFacing(dist, unit.Facing); - Util.TickFacing(ref unit.Facing, desiredFacing, - self.Info.ROT); - - var rawSpeed = .2f * Util.GetEffectiveSpeed(self); - var angle = (unit.Facing - desiredFacing) / 128f * Math.PI; - var scale = .4f + .6f * (float)Math.Cos(angle); - - if (unit.Altitude > CruiseAltitude / 2) // do some movement. - { - self.CenterLocation += (rawSpeed * scale / dist.Length) * dist; - self.CenterLocation += (1f - scale) * rawSpeed - * float2.FromAngle((float)angle); - self.Location = ((1 / 24f) * self.CenterLocation).ToInt2(); - } - - if (unit.Altitude < CruiseAltitude) - { - ++unit.Altitude; - return; - } - } - else if (unit.Altitude > 0 && - Game.IsCellBuildable( self.Location, UnitMovementType.Foot )) - { - --unit.Altitude; - } - - /* todo: bob slightly when hovering */ - } - public UnitMovementType GetMovementType() - { - return UnitMovementType.Fly; - } - - public bool CanEnterCell(int2 location) - { - return true; // Planes can go anywhere (?) - } + public UnitMovementType GetMovementType() { return UnitMovementType.Fly; } + public bool CanEnterCell(int2 location) { return true; } } } diff --git a/OpenRa.Game/Traits/Plane.cs b/OpenRa.Game/Traits/Plane.cs index 064662ee06..f0accf7995 100644 --- a/OpenRa.Game/Traits/Plane.cs +++ b/OpenRa.Game/Traits/Plane.cs @@ -8,9 +8,7 @@ namespace OpenRa.Game.Traits { class Plane : IOrder, IMovement { - public Plane(Actor self) - { - } + public Plane(Actor self) {} public Order IssueOrder(Actor self, int2 xy, MouseInput mi, Actor underCursor) { @@ -30,13 +28,14 @@ namespace OpenRa.Game.Traits if (order.OrderString == "Move") { self.CancelActivity(); - self.QueueActivity(new Circle(order.TargetLocation)); + self.QueueActivity(new Fly(Util.CenterOfCell(order.TargetLocation))); + self.QueueActivity(new ReturnToBase(self, null)); } if (order.OrderString == "Enter") { self.CancelActivity(); - self.QueueActivity(new ReturnToBase(self, order.TargetActor.CenterLocation)); + self.QueueActivity(new ReturnToBase(self, order.TargetActor)); } } diff --git a/OpenRa.Game/Traits/Production.cs b/OpenRa.Game/Traits/Production.cs index f916f05dc5..c4d1413f39 100755 --- a/OpenRa.Game/Traits/Production.cs +++ b/OpenRa.Game/Traits/Production.cs @@ -33,10 +33,6 @@ namespace OpenRa.Game.Traits var mobile = newUnit.traits.GetOrDefault(); if( mobile != null ) newUnit.QueueActivity( new Activities.Move( rp.rallyPoint, 1 ) ); - - var heli = newUnit.traits.GetOrDefault(); - if (heli != null) - heli.targetLocation = rp.rallyPoint; // TODO: make Activity.Move work for helis. } var bi = self.Info as BuildingInfo; diff --git a/OpenRa.Game/Traits/Util.cs b/OpenRa.Game/Traits/Util.cs index 8809c86d90..fa918b337d 100755 --- a/OpenRa.Game/Traits/Util.cs +++ b/OpenRa.Game/Traits/Util.cs @@ -2,6 +2,7 @@ using System.Linq; using OpenRa.Game.GameRules; using OpenRa.Game.Graphics; +using OpenRa.Game.Traits.Activities; namespace OpenRa.Game.Traits { @@ -135,5 +136,11 @@ namespace OpenRa.Game.Traits .Product(); return mi.Speed * modifier; } + + public static IActivity SequenceActivities(params IActivity[] acts) + { + return acts.Reverse().Aggregate( + (next, a) => { a.NextActivity = next; return a; }); + } } } diff --git a/doc/progress.txt b/doc/progress.txt index aedf15ccfb..8330a3d28b 100644 --- a/doc/progress.txt +++ b/doc/progress.txt @@ -31,17 +31,18 @@ HARV Works ARTY Works Helicopters - - Weapons don't work, - - hover while attacking doesn't work, - Repair/rearm doesn't work TRAN Cargo doesn't work +HELI Weapon offsets wrong +HIND Weapon offsets wrong -Planes - - Edge cases in landing code - - Circle behavior needs to be able to work with a unit - - -YAK Weapons don't work -MIG Weapons don't work +Planes + - AFLD reservations don't work + - Rearm doesn't work (should happen when on AFLD) + - Repair at FIX doesn't work [fix doesn't work?] + - Planes damage themselves (should never happen; these are AG weapons) +YAK Ammo/ROF are funky +MIG Ammo/ROF are funky Ships diff --git a/units.ini b/units.ini old mode 100755 new mode 100644 index 0457488a27..390f224e98 --- a/units.ini +++ b/units.ini @@ -165,13 +165,13 @@ HIND [MIG] Description=Mig Attack Plane BuiltAt=afld -Traits=Unit, Plane, RenderUnit, WithShadow, LimitedAmmo +Traits=Unit, AttackPlane, Plane, RenderUnit, WithShadow, LimitedAmmo InitialFacing=192 LongDesc=Fast Ground-Attack Plane.\n Strong vs Buildings\n Weak vs Infantry, Light Vehicles [YAK] Description=Yak Attack Plane BuiltAt=afld -Traits=Unit, Plane, RenderUnit, WithShadow, LimitedAmmo +Traits=Unit, AttackPlane, Plane, RenderUnit, WithShadow, LimitedAmmo InitialFacing=192 LongDesc=Anti-Tanks & Anti-Infantry Plane.\n Strong vs Infantry, Tanks\n Weak vs Buildings [TRAN] @@ -185,14 +185,14 @@ LongDesc=Fast Infantry Transport Helicopter.\n Unarmed [HELI] Description=Longbow BuiltAt=hpad -Traits=Unit, Helicopter, RenderUnitRotor, WithShadow, LimitedAmmo +Traits=Unit, AttackHeli, Helicopter, RenderUnitRotor, WithShadow, LimitedAmmo PrimaryOffset=0,0,0,-2 InitialFacing=20 LongDesc=Helicopter Gunship with AG Missiles.\n Strong vs Buildings, Tanks\n Weak vs Infantry [HIND] Description=Hind BuiltAt=hpad -Traits=Unit, Helicopter, RenderUnitRotor, WithShadow, LimitedAmmo +Traits=Unit, AttackHeli, Helicopter, RenderUnitRotor, WithShadow, LimitedAmmo InitialFacing=20 LongDesc=Helicopter Gunship with Chainguns.\n Strong vs Infantry, Light Vehicles.\n Weak vs Tanks