diff --git a/OpenRA.FileFormats/WPos.cs b/OpenRA.FileFormats/WPos.cs index 7800ef8508..32ec012970 100644 --- a/OpenRA.FileFormats/WPos.cs +++ b/OpenRA.FileFormats/WPos.cs @@ -34,6 +34,22 @@ namespace OpenRA public static bool operator ==(WPos me, WPos other) { return (me.X == other.X && me.Y == other.Y && me.Z == other.Z); } public static bool operator !=(WPos me, WPos other) { return !(me == other); } + public static WPos Lerp(WPos a, WPos b, int mul, int div) { return a + (b - a) * mul / div; } + + public static WPos LerpQuadratic(WPos a, WPos b, WAngle pitch, int mul, int div) + { + // Start with a linear lerp between the points + var ret = Lerp(a, b, mul, div); + + if (pitch.Angle == 0) + return ret; + + // Add an additional quadratic variation to height + // Uses fp to avoid integer overflow + var offset = (int)((float)((float)(b - a).Length*pitch.Tan()*mul*(div - mul)) / (float)(1024*div*div)); + return new WPos(ret.X, ret.Y, ret.Z + offset); + } + public static WPos Average(params WPos[] list) { if (list == null || list.Length == 0) diff --git a/OpenRA.FileFormats/WRange.cs b/OpenRA.FileFormats/WRange.cs index 1f4c45ead2..216f8bd77e 100644 --- a/OpenRA.FileFormats/WRange.cs +++ b/OpenRA.FileFormats/WRange.cs @@ -22,6 +22,7 @@ namespace OpenRA public WRange(int r) { Range = r; } public static readonly WRange Zero = new WRange(0); + public static WRange FromCells(int cells) { return new WRange(1024*cells); } public static WRange operator +(WRange a, WRange b) { return new WRange(a.Range + b.Range); } public static WRange operator -(WRange a, WRange b) { return new WRange(a.Range - b.Range); } diff --git a/OpenRA.FileFormats/WVec.cs b/OpenRA.FileFormats/WVec.cs index 4eab6e15d1..7b450d9d57 100644 --- a/OpenRA.FileFormats/WVec.cs +++ b/OpenRA.FileFormats/WVec.cs @@ -53,6 +53,20 @@ namespace OpenRA public static WVec Lerp(WVec a, WVec b, int mul, int div) { return a + (b - a) * mul / div; } + public static WVec LerpQuadratic(WVec a, WVec b, WAngle pitch, int mul, int div) + { + // Start with a linear lerp between the points + var ret = Lerp(a, b, mul, div); + + if (pitch.Angle == 0) + return ret; + + // Add an additional quadratic variation to height + // Uses fp to avoid integer overflow + var offset = (int)((float)((float)(b - a).Length*pitch.Tan()*mul*(div - mul)) / (float)(1024*div*div)); + return new WVec(ret.X, ret.Y, ret.Z + offset); + } + public override int GetHashCode() { return X.GetHashCode() ^ Y.GetHashCode() ^ Z.GetHashCode(); } public override bool Equals(object obj) diff --git a/OpenRA.Game/CPos.cs b/OpenRA.Game/CPos.cs index 02e117c220..82b17d3d8b 100644 --- a/OpenRA.Game/CPos.cs +++ b/OpenRA.Game/CPos.cs @@ -21,8 +21,6 @@ namespace OpenRA public readonly int X, Y; public CPos(int x, int y) { X = x; Y = y; } - public CPos(WPos a) { X = a.X / 1024; Y = a.Y / 1024; } - public static readonly CPos Zero = new CPos(0, 0); public static explicit operator CPos(int2 a) { return new CPos(a.X, a.Y); } @@ -71,4 +69,9 @@ namespace OpenRA public static CPos TopLeftAsCPos(this Rectangle r) { return new CPos(r.Left, r.Top); } public static CPos BottomRightAsCPos(this Rectangle r) { return new CPos(r.Right, r.Bottom); } } -} + + public static class WPosExtensions + { + public static CPos ToCPos(this WPos a) { return new CPos(a.X / 1024, a.Y / 1024); } + } +} \ No newline at end of file diff --git a/OpenRA.Game/GameRules/WeaponInfo.cs b/OpenRA.Game/GameRules/WeaponInfo.cs index a6a65ffd75..2c98c65d8c 100644 --- a/OpenRA.Game/GameRules/WeaponInfo.cs +++ b/OpenRA.Game/GameRules/WeaponInfo.cs @@ -137,5 +137,37 @@ namespace OpenRA.GameRules return ret; } + + public bool IsValidAgainst(Actor a) + { + var targetable = a.TraitOrDefault(); + if (targetable == null || !ValidTargets.Intersect(targetable.TargetTypes).Any()) + return false; + + if (Warheads.All(w => w.EffectivenessAgainst(a) <= 0)) + return false; + + return true; + } + + public bool IsValidAgainst(Target target, World world) + { + if (!target.IsValid) + return false; + + if (target.IsActor) + return IsValidAgainst(target.Actor); + else + { + var cell = target.CenterPosition.ToCPos(); + if (ValidTargets.Contains("Ground") && world.GetTerrainType(cell) != "Water") + return true; + + if (ValidTargets.Contains("Water") && world.GetTerrainType(cell) == "Water") + return true; + + return false; + } + } } } diff --git a/OpenRA.Game/Graphics/ContrailRenderable.cs b/OpenRA.Game/Graphics/ContrailRenderable.cs index 9ef43b1813..a33cedd6b2 100644 --- a/OpenRA.Game/Graphics/ContrailRenderable.cs +++ b/OpenRA.Game/Graphics/ContrailRenderable.cs @@ -60,13 +60,13 @@ namespace OpenRA.Graphics // Start of the first line segment is the tail of the list - don't smooth it. var curPos = trail[idx(next - skip - 1)]; - var curCell = new CPos(curPos); + var curCell = curPos.ToCPos(); var curColor = color; for (var i = 0; i < length - skip - 4; i++) { var j = next - skip - i - 2; var nextPos = WPos.Average(trail[idx(j)], trail[idx(j-1)], trail[idx(j-2)], trail[idx(j-3)]); - var nextCell = new CPos(nextPos); + var nextCell = nextPos.ToCPos(); var nextColor = Exts.ColorLerp(i * 1f / (length - 4), color, Color.Transparent); if (!world.FogObscures(curCell) && !world.FogObscures(nextCell)) diff --git a/OpenRA.Game/Sync.cs b/OpenRA.Game/Sync.cs index 5d29bb85f8..7e41470919 100755 --- a/OpenRA.Game/Sync.cs +++ b/OpenRA.Game/Sync.cs @@ -80,6 +80,31 @@ namespace OpenRA il.EmitCall(OpCodes.Call, ((Func)hash_PSubVec).Method, null); il.Emit(OpCodes.Xor); } + else if (type == typeof(WRange)) + { + il.EmitCall(OpCodes.Call, ((Func)(a => a.GetHashCode())).Method, null); + il.Emit(OpCodes.Xor); + } + else if (type == typeof(WPos)) + { + il.EmitCall(OpCodes.Call, ((Func)(a => a.GetHashCode())).Method, null); + il.Emit(OpCodes.Xor); + } + else if (type == typeof(WVec)) + { + il.EmitCall(OpCodes.Call, ((Func)(a => a.GetHashCode())).Method, null); + il.Emit(OpCodes.Xor); + } + else if (type == typeof(WAngle)) + { + il.EmitCall(OpCodes.Call, ((Func)(a => a.GetHashCode())).Method, null); + il.Emit(OpCodes.Xor); + } + else if (type == typeof(WRot)) + { + il.EmitCall(OpCodes.Call, ((Func)(a => a.GetHashCode())).Method, null); + il.Emit(OpCodes.Xor); + } else if (type == typeof(TypeDictionary)) { il.EmitCall(OpCodes.Call, ((Func)hash_tdict).Method, null); diff --git a/OpenRA.Game/Traits/DrawLineToTarget.cs b/OpenRA.Game/Traits/DrawLineToTarget.cs index 2c72b9489c..a241af6715 100644 --- a/OpenRA.Game/Traits/DrawLineToTarget.cs +++ b/OpenRA.Game/Traits/DrawLineToTarget.cs @@ -52,8 +52,6 @@ namespace OpenRA.Traits public void RenderAfterWorld(WorldRenderer wr) { - //if (self.IsIdle) return; - var force = Game.GetModifierKeys().HasModifier(Modifiers.Alt); if ((lifetime <= 0 || --lifetime <= 0) && !force) return; @@ -61,9 +59,7 @@ namespace OpenRA.Traits if (targets == null || targets.Count == 0) return; - var move = self.TraitOrDefault(); - var origin = (move != null ? self.CenterLocation - new PVecInt(0, move.Altitude) : self.CenterLocation).ToFloat2(); - + var from = wr.ScreenPxPosition(self.CenterPosition); var wlr = Game.Renderer.WorldLineRenderer; foreach (var target in targets) @@ -71,9 +67,10 @@ namespace OpenRA.Traits if (!target.IsValid) continue; - wlr.DrawLine(origin, target.CenterLocation.ToFloat2(), c, c); - DrawTargetMarker(wlr, target.CenterLocation.ToFloat2()); - DrawTargetMarker(wlr, origin); + var to = wr.ScreenPxPosition(target.CenterPosition); + wlr.DrawLine(from, to, c, c); + DrawTargetMarker(wlr, from); + DrawTargetMarker(wlr, to); } } diff --git a/OpenRA.Game/Traits/Selectable.cs b/OpenRA.Game/Traits/Selectable.cs index 9fbaf02b4d..2a8f95f485 100644 --- a/OpenRA.Game/Traits/Selectable.cs +++ b/OpenRA.Game/Traits/Selectable.cs @@ -46,9 +46,9 @@ namespace OpenRA.Traits var Xy = new float2(bounds.Right, bounds.Top); wr.DrawSelectionBox(self, Color.White); - DrawHealthBar(self, xy, Xy); - DrawExtraBars(self, xy, Xy); - DrawUnitPath(self); + DrawHealthBar(wr, self, xy, Xy); + DrawExtraBars(wr, self, xy, Xy); + DrawUnitPath(wr, self); } public void DrawRollover(WorldRenderer wr, Actor self) @@ -61,11 +61,11 @@ namespace OpenRA.Traits var xy = new float2(bounds.Left, bounds.Top); var Xy = new float2(bounds.Right, bounds.Top); - DrawHealthBar(self, xy, Xy); - DrawExtraBars(self, xy, Xy); + DrawHealthBar(wr, self, xy, Xy); + DrawExtraBars(wr, self, xy, Xy); } - void DrawExtraBars(Actor self, float2 xy, float2 Xy) + void DrawExtraBars(WorldRenderer wr, Actor self, float2 xy, float2 Xy) { foreach (var extraBar in self.TraitsImplementing()) { @@ -74,12 +74,12 @@ namespace OpenRA.Traits { xy.Y += 4; Xy.Y += 4; - DrawSelectionBar(self, xy, Xy, extraBar.GetValue(), extraBar.GetColor()); + DrawSelectionBar(wr, self, xy, Xy, extraBar.GetValue(), extraBar.GetColor()); } } } - void DrawSelectionBar(Actor self, float2 xy, float2 Xy, float value, Color barColor) + void DrawSelectionBar(WorldRenderer wr, Actor self, float2 xy, float2 Xy, float value, Color barColor) { if (!self.IsInWorld) return; @@ -102,7 +102,7 @@ namespace OpenRA.Traits wlr.DrawLine(xy + new float2(0, -4), z + new float2(0, -4), barColor2, barColor2); } - void DrawHealthBar(Actor self, float2 xy, float2 Xy) + void DrawHealthBar(WorldRenderer wr, Actor self, float2 xy, float2 Xy) { if (!self.IsInWorld) return; @@ -148,24 +148,21 @@ namespace OpenRA.Traits } } - void DrawUnitPath(Actor self) + void DrawUnitPath(WorldRenderer wr, Actor self) { if (self.World.LocalPlayer == null ||!self.World.LocalPlayer.PlayerActor.Trait().PathDebug) return; var activity = self.GetCurrentActivity(); - var mobile = self.TraitOrDefault(); - if (activity != null && mobile != null) + if (activity != null) { - var alt = new float2(0, -mobile.Altitude); var targets = activity.GetTargets(self); - var start = self.CenterLocation.ToFloat2() + alt; + var start = wr.ScreenPxPosition(self.CenterPosition); var c = Color.Green; var wlr = Game.Renderer.WorldLineRenderer; - foreach (var step in targets.Select(p => p.CenterLocation.ToFloat2())) + foreach (var stp in targets.Where(t => t.IsValid).Select(p => wr.ScreenPxPosition(p.CenterPosition))) { - var stp = step + alt; wlr.DrawLine(stp + new float2(-1, -1), stp + new float2(-1, 1), c, c); wlr.DrawLine(stp + new float2(-1, 1), stp + new float2(1, 1), c, c); wlr.DrawLine(stp + new float2(1, 1), stp + new float2(1, -1), c, c); diff --git a/OpenRA.Game/Traits/Target.cs b/OpenRA.Game/Traits/Target.cs index 33456afd5c..9d0466db0f 100644 --- a/OpenRA.Game/Traits/Target.cs +++ b/OpenRA.Game/Traits/Target.cs @@ -8,17 +8,32 @@ */ #endregion +using System; +using System.Collections.Generic; +using System.Linq; + namespace OpenRA.Traits { - public struct Target // a target: either an actor, or a fixed location. + public struct Target { - public static Target[] NoTargets = {}; + public static readonly Target[] NoTargets = {}; + public static readonly Target None = new Target(); Actor actor; - PPos pos; + WPos pos; bool valid; int generation; + public static Target FromPos(WPos p) { return new Target { pos = p, valid = true }; } + public static Target FromPos(PPos p) { return new Target { pos = p.ToWPos(0), valid = true }; } + public static Target FromCell(CPos c) { return new Target { pos = c.CenterPosition, valid = true }; } + public static Target FromOrder(Order o) + { + return o.TargetActor != null + ? Target.FromActor(o.TargetActor) + : Target.FromCell(o.TargetLocation); + } + public static Target FromActor(Actor a) { return new Target @@ -28,22 +43,56 @@ namespace OpenRA.Traits generation = a.Generation, }; } - public static Target FromPos(PPos p) { return new Target { pos = p, valid = true }; } - public static Target FromCell(CPos c) { return new Target { pos = Util.CenterOfCell(c), valid = true }; } - public static Target FromOrder(Order o) - { - return o.TargetActor != null - ? Target.FromActor(o.TargetActor) - : Target.FromCell(o.TargetLocation); - } - - public static readonly Target None = new Target(); public bool IsValid { get { return valid && (actor == null || (actor.IsInWorld && !actor.IsDead() && actor.Generation == generation)); } } - public PPos PxPosition { get { return IsActor ? actor.Trait().PxPosition : pos; } } + public PPos PxPosition { get { return IsActor ? actor.Trait().PxPosition : PPos.FromWPos(pos); } } public PPos CenterLocation { get { return PxPosition; } } - public Actor Actor { get { return IsActor ? actor : null; } } + + // TODO: This should return true even if the actor is destroyed public bool IsActor { get { return actor != null && !actor.Destroyed; } } + + // Representative position - see Positions for the full set of targetable positions. + public WPos CenterPosition + { + get + { + if (!IsValid) + throw new InvalidOperationException("Attempting to query the position of an invalid Target"); + + return actor != null ? actor.CenterPosition : pos; + } + } + + // Positions available to target for range checks + static readonly WPos[] NoPositions = {}; + public IEnumerable Positions + { + get + { + if (!IsValid) + return NoPositions; + + if (actor == null) + return new []{pos}; + + var targetable = actor.TraitOrDefault(); + if (targetable == null) + return new []{actor.CenterPosition}; + + return targetable.TargetableCells(actor).Select(c => c.CenterPosition); + } + } + + public bool IsInRange(WPos origin, WRange range) + { + if (!IsValid) + return false; + + // Target ranges are calculated in 2D, so ignore height differences + return Positions.Any(t => (t.X - origin.X)*(t.X - origin.X) + + (t.Y - origin.Y)*(t.Y - origin.Y) <= range.Range*range.Range); + } + } } diff --git a/OpenRA.Game/Traits/Util.cs b/OpenRA.Game/Traits/Util.cs index 6b605ab4d1..dc87b9b473 100755 --- a/OpenRA.Game/Traits/Util.cs +++ b/OpenRA.Game/Traits/Util.cs @@ -30,6 +30,11 @@ namespace OpenRA.Traits return ( facing - rot ) & 0xFF; } + public static int GetFacing(WVec d, int currentFacing) + { + return GetFacing(new int2(d.X, d.Y), currentFacing); + } + public static int GetFacing(PVecInt d, int currentFacing) { return GetFacing(d.ToInt2(), currentFacing); @@ -40,7 +45,7 @@ namespace OpenRA.Traits return GetFacing(d.ToInt2(), currentFacing); } - public static int GetFacing( int2 d, int currentFacing ) + public static int GetFacing(int2 d, int currentFacing) { if (d == int2.Zero) return currentFacing; @@ -177,7 +182,7 @@ namespace OpenRA.Traits : new CPos[] {}; if (cells.Length == 0) - cells = new CPos[] { target.CenterLocation.ToCPos() }; + cells = new CPos[] { target.CenterPosition.ToCPos() }; return Util.ExpandFootprint(cells, true); } diff --git a/OpenRA.Game/Traits/World/Shroud.cs b/OpenRA.Game/Traits/World/Shroud.cs index 1e7355b5f7..2b87e4b872 100644 --- a/OpenRA.Game/Traits/World/Shroud.cs +++ b/OpenRA.Game/Traits/World/Shroud.cs @@ -202,7 +202,7 @@ namespace OpenRA.Traits return cells.Select(c => c.First); } - return new[] { a.CenterLocation.ToCPos() }; + return new[] { a.CenterPosition.ToCPos() }; } public void Explore(World world, CPos center, int range) diff --git a/OpenRA.Game/WorldUtils.cs b/OpenRA.Game/WorldUtils.cs index 0c43535154..64c47d5af5 100755 --- a/OpenRA.Game/WorldUtils.cs +++ b/OpenRA.Game/WorldUtils.cs @@ -39,6 +39,11 @@ namespace OpenRA return actors.OrderBy( a => (a.CenterLocation - px).LengthSquared ).FirstOrDefault(); } + public static IEnumerable FindUnitsInCircle(this World world, WPos a, WRange r) + { + return world.FindUnitsInCircle(PPos.FromWPos(a), r.Range * Game.CellSize / 1024); + } + public static IEnumerable FindUnitsInCircle(this World world, PPos a, int r) { using (new PerfSample("FindUnitsInCircle")) diff --git a/OpenRA.Mods.Cnc/Activities/HarvesterDockSequence.cs b/OpenRA.Mods.Cnc/Activities/HarvesterDockSequence.cs index 59999add0e..337438a7fc 100644 --- a/OpenRA.Mods.Cnc/Activities/HarvesterDockSequence.cs +++ b/OpenRA.Mods.Cnc/Activities/HarvesterDockSequence.cs @@ -27,16 +27,15 @@ namespace OpenRA.Mods.Cnc readonly RenderUnit ru; State state; - PPos startDock; - PPos endDock; + WPos startDock, endDock; public HarvesterDockSequence(Actor self, Actor proc) { this.proc = proc; state = State.Turn; harv = self.Trait(); ru = self.Trait(); - startDock = self.Trait().PxPosition; - endDock = proc.Trait().PxPosition + new PVecInt(-15,8); + startDock = self.Trait().PxPosition.ToWPos(0); + endDock = (proc.Trait().PxPosition + new PVecInt(-15,8)).ToWPos(0); } public override Activity Tick(Actor self) diff --git a/OpenRA.Mods.Cnc/Effects/IonCannon.cs b/OpenRA.Mods.Cnc/Effects/IonCannon.cs index 697efece23..c8e8e18e7d 100644 --- a/OpenRA.Mods.Cnc/Effects/IonCannon.cs +++ b/OpenRA.Mods.Cnc/Effects/IonCannon.cs @@ -34,15 +34,13 @@ namespace OpenRA.Mods.Cnc.Effects public IEnumerable Render(WorldRenderer wr) { - yield return new SpriteRenderable(anim.Image, - target.CenterLocation.ToFloat2() - new float2(0, 0.5f*anim.Image.size.Y - Game.CellSize), - wr.Palette("effect"), (int)target.CenterLocation.Y); + yield return new SpriteRenderable(anim.Image, target.CenterPosition, 1, wr.Palette("effect"), 1.0f); } - void Finish( World world ) + void Finish(World world) { world.AddFrameEndTask(w => w.Remove(this)); - Combat.DoExplosion(firedBy, "IonCannon", target.CenterLocation, 0); + Combat.DoExplosion(firedBy, "IonCannon", target.CenterPosition); } } } diff --git a/OpenRA.Mods.RA/AI/HackyAI.cs b/OpenRA.Mods.RA/AI/HackyAI.cs index 0e7219789e..b864e199b0 100644 --- a/OpenRA.Mods.RA/AI/HackyAI.cs +++ b/OpenRA.Mods.RA/AI/HackyAI.cs @@ -518,7 +518,7 @@ namespace OpenRA.Mods.RA.AI if (owner.attackOrFleeFuzzy.CanAttack) { foreach(var u in owner.units) - owner.world.IssueOrder(new Order("AttackMove", u, false) { TargetLocation = owner.Target.CenterLocation.ToCPos() }); + owner.world.IssueOrder(new Order("AttackMove", u, false) { TargetLocation = owner.Target.CenterPosition.ToCPos() }); // We have gathered sufficient units. Attack the nearest enemy unit. owner.fsm.ChangeState(new GroundUnitsAttackMoveState(), true); return; @@ -560,7 +560,7 @@ namespace OpenRA.Mods.RA.AI { owner.world.IssueOrder(new Order("Stop", leader, false)); foreach (var unit in owner.units.Where(a => !ownUnits.Contains(a))) - owner.world.IssueOrder(new Order("AttackMove", unit, false) { TargetLocation = leader.CenterLocation.ToCPos() }); + owner.world.IssueOrder(new Order("AttackMove", unit, false) { TargetLocation = leader.CenterPosition.ToCPos() }); } else { diff --git a/OpenRA.Mods.RA/Activities/Attack.cs b/OpenRA.Mods.RA/Activities/Attack.cs index cae2b93656..28d00a3b68 100755 --- a/OpenRA.Mods.RA/Activities/Attack.cs +++ b/OpenRA.Mods.RA/Activities/Attack.cs @@ -19,7 +19,7 @@ namespace OpenRA.Mods.RA.Activities { protected Target Target; ITargetable targetable; - int Range; + WRange Range; bool AllowMovement; int nextPathTime; @@ -27,7 +27,10 @@ namespace OpenRA.Mods.RA.Activities const int delayBetweenPathingAttempts = 20; const int delaySpread = 5; - public Attack(Target target, int range, bool allowMovement) + public Attack(Target target, WRange range) + : this(target, range, true) {} + + public Attack(Target target, WRange range, bool allowMovement) { Target = target; if (target.IsActor) @@ -37,20 +40,18 @@ namespace OpenRA.Mods.RA.Activities AllowMovement = allowMovement; } - public Attack(Target target, int range) : this(target, range, true) {} - - public override Activity Tick( Actor self ) + public override Activity Tick(Actor self) { var attack = self.Trait(); - - var ret = InnerTick( self, attack ); - attack.IsAttacking = ( ret == this ); + var ret = InnerTick(self, attack); + attack.IsAttacking = (ret == this); return ret; } - protected virtual Activity InnerTick( Actor self, AttackBase attack ) + protected virtual Activity InnerTick(Actor self, AttackBase attack) { - if (IsCanceled) return NextActivity; + if (IsCanceled) + return NextActivity; if (!Target.IsValid) return NextActivity; @@ -61,7 +62,7 @@ namespace OpenRA.Mods.RA.Activities if (targetable != null && !targetable.TargetableBy(Target.Actor, self)) return NextActivity; - if (!Combat.IsInRange(self.CenterLocation, Range, Target)) + if (!Target.IsInRange(self.CenterPosition, Range)) { if (--nextPathTime > 0) return this; @@ -69,13 +70,13 @@ namespace OpenRA.Mods.RA.Activities nextPathTime = self.World.SharedRandom.Next(delayBetweenPathingAttempts - delaySpread, delayBetweenPathingAttempts + delaySpread); - return (AllowMovement) ? Util.SequenceActivities(self.Trait().MoveWithinRange(Target, Range), this) : NextActivity; + return (AllowMovement) ? Util.SequenceActivities(self.Trait().MoveWithinRange(Target, Range.Range / 1024), this) : NextActivity; } - var desiredFacing = Util.GetFacing(Target.CenterLocation - self.CenterLocation, 0); + var desiredFacing = Util.GetFacing(Target.CenterPosition - self.CenterPosition, 0); var facing = self.Trait(); if (facing.Facing != desiredFacing) - return Util.SequenceActivities( new Turn( desiredFacing ), this ); + return Util.SequenceActivities(new Turn(desiredFacing), this); attack.DoAttack(self, Target); return this; diff --git a/OpenRA.Mods.RA/Activities/Enter.cs b/OpenRA.Mods.RA/Activities/Enter.cs index 652e970c63..fbf2243139 100755 --- a/OpenRA.Mods.RA/Activities/Enter.cs +++ b/OpenRA.Mods.RA/Activities/Enter.cs @@ -35,9 +35,9 @@ namespace OpenRA.Mods.RA.Activities // Move to the middle of the target, ignoring impassable tiles var mobile = self.Trait(); - var to = target.CenterLocation; - var from = self.CenterLocation; - var speed = mobile.MovementSpeedForCell(self, self.Location); + var to = target.CenterPosition; + var from = self.CenterPosition; + var speed = mobile.WorldMovementSpeedForCell(self, self.Location); var length = speed > 0 ? (int)((to - from).Length * 3 / speed) : 0; return Util.SequenceActivities( diff --git a/OpenRA.Mods.RA/Activities/Follow.cs b/OpenRA.Mods.RA/Activities/Follow.cs index f806851da1..8e5bae7cd7 100644 --- a/OpenRA.Mods.RA/Activities/Follow.cs +++ b/OpenRA.Mods.RA/Activities/Follow.cs @@ -33,7 +33,7 @@ namespace OpenRA.Mods.RA.Activities if (IsCanceled) return NextActivity; if (!Target.IsValid) return NextActivity; - var inRange = ( Target.CenterLocation.ToCPos() - self.Location ).LengthSquared < Range * Range; + var inRange = ( Target.CenterPosition.ToCPos() - self.Location ).LengthSquared < Range * Range; if( inRange ) return this; if (--nextPathTime > 0) return this; diff --git a/OpenRA.Mods.RA/Activities/Heal.cs b/OpenRA.Mods.RA/Activities/Heal.cs index 0861e56c54..199f89d041 100755 --- a/OpenRA.Mods.RA/Activities/Heal.cs +++ b/OpenRA.Mods.RA/Activities/Heal.cs @@ -17,10 +17,10 @@ namespace OpenRA.Mods.RA.Activities /* non-turreted attack */ public class Heal : Attack { - public Heal(Target target, int range, bool allowMovement) + public Heal(Target target, WRange range, bool allowMovement) : base(target, range, allowMovement) {} - protected override Activity InnerTick( Actor self, AttackBase attack ) + protected override Activity InnerTick(Actor self, AttackBase attack) { if (Target.IsActor && Target.Actor.GetDamageState() == DamageState.Undamaged) return NextActivity; diff --git a/OpenRA.Mods.RA/Activities/LayMines.cs b/OpenRA.Mods.RA/Activities/LayMines.cs index 487b500954..8b426786be 100644 --- a/OpenRA.Mods.RA/Activities/LayMines.cs +++ b/OpenRA.Mods.RA/Activities/LayMines.cs @@ -38,7 +38,7 @@ namespace OpenRA.Mods.RA.Activities return Util.SequenceActivities( new MoveAdjacentTo(Target.FromActor(rearmTarget)), - mobile.MoveTo(rearmTarget.CenterLocation.ToCPos(), rearmTarget), + mobile.MoveTo(rearmTarget.CenterPosition.ToCPos(), rearmTarget), new Rearm(self), new Repair(rearmTarget), this ); diff --git a/OpenRA.Mods.RA/Activities/Leap.cs b/OpenRA.Mods.RA/Activities/Leap.cs index d8f4537386..289e87e379 100644 --- a/OpenRA.Mods.RA/Activities/Leap.cs +++ b/OpenRA.Mods.RA/Activities/Leap.cs @@ -8,7 +8,9 @@ */ #endregion +using System; using System.Linq; +using OpenRA.GameRules; using OpenRA.Mods.RA.Move; using OpenRA.Mods.RA.Render; using OpenRA.Traits; @@ -17,40 +19,54 @@ namespace OpenRA.Mods.RA.Activities { class Leap : Activity { - Target target; - PPos initialLocation; + Mobile mobile; + WeaponInfo weapon; - int moveFraction; - const int delay = 6; + WPos from; + WPos to; + int ticks; + int length; + WAngle angle; - public Leap(Actor self, Target target) + public Leap(Actor self, Actor target, WeaponInfo weapon, WRange speed, WAngle angle) { - this.target = target; - initialLocation = (PPos) self.Trait().PxPosition; + var targetMobile = target.TraitOrDefault(); + if (targetMobile == null) + throw new InvalidOperationException("Leap requires a target actor with the Mobile trait"); - self.Trait().Attacking(self, target); - Sound.Play("dogg5p.aud", self.CenterLocation); + this.weapon = weapon; + this.angle = angle; + mobile = self.Trait(); + mobile.SetLocation(mobile.fromCell, mobile.fromSubCell, targetMobile.fromCell, targetMobile.fromSubCell); + mobile.IsMoving = true; + + from = self.CenterPosition; + var offset = MobileInfo.SubCellOffsets[targetMobile.fromSubCell]; + to = targetMobile.fromCell.CenterPosition + new WVec(offset.X * 1024 / Game.CellSize, offset.Y * 1024 / Game.CellSize, 0); + length = Math.Max((to - from).Length / speed.Range, 1); + + self.Trait().Attacking(self, Target.FromActor(target)); + + if (weapon.Report != null && weapon.Report.Any()) + Sound.Play(weapon.Report.Random(self.World.SharedRandom), self.CenterLocation); } public override Activity Tick(Actor self) { - if( moveFraction == 0 && IsCanceled ) return NextActivity; - if (!target.IsValid) return NextActivity; + if (ticks == 0 && IsCanceled) + return NextActivity; - self.Trait().IsLeaping = true; - var mobile = self.Trait(); - ++moveFraction; - - mobile.PxPosition = PPos.Lerp(initialLocation, target.PxPosition, moveFraction, delay); - - if (moveFraction >= delay) + mobile.AdjustPxPosition(self, PPos.FromWPosHackZ(WPos.LerpQuadratic(from, to, angle, ++ticks, length))); + if (ticks >= length) { - self.TraitsImplementing().FirstOrDefault() - .SetPosition(self, target.CenterLocation.ToCPos()); + mobile.SetLocation(mobile.toCell, mobile.toSubCell, mobile.toCell, mobile.toSubCell); + mobile.FinishedMoving(self); + mobile.IsMoving = false; + + self.World.ActorMap.GetUnitsAt(mobile.toCell, mobile.toSubCell) + .Except(new []{self}).Where(t => weapon.IsValidAgainst(t)) + .Do(t => t.Kill(self)); - if (target.IsActor) - target.Actor.Kill(self); - self.Trait().IsLeaping = false; return NextActivity; } diff --git a/OpenRA.Mods.RA/Activities/MoveAdjacentTo.cs b/OpenRA.Mods.RA/Activities/MoveAdjacentTo.cs index 3615d40485..285312b7b0 100755 --- a/OpenRA.Mods.RA/Activities/MoveAdjacentTo.cs +++ b/OpenRA.Mods.RA/Activities/MoveAdjacentTo.cs @@ -41,7 +41,7 @@ namespace OpenRA.Mods.RA.Activities } ps1.heuristic = PathSearch.DefaultEstimator(mobile.toCell); - var ps2 = PathSearch.FromPoint(self.World, mobile.Info, self, mobile.toCell, target.CenterLocation.ToCPos(), true); + var ps2 = PathSearch.FromPoint(self.World, mobile.Info, self, mobile.toCell, target.CenterPosition.ToCPos(), true); var ret = self.World.WorldActor.Trait().FindBidiPath(ps1, ps2); return Util.SequenceActivities(mobile.MoveTo(() => ret), this); diff --git a/OpenRA.Mods.RA/Activities/UnloadCargo.cs b/OpenRA.Mods.RA/Activities/UnloadCargo.cs index 56968ad59e..e4cba01ee2 100644 --- a/OpenRA.Mods.RA/Activities/UnloadCargo.cs +++ b/OpenRA.Mods.RA/Activities/UnloadCargo.cs @@ -79,23 +79,24 @@ namespace OpenRA.Mods.RA.Activities return this; var actor = cargo.Unload(self); - var exitPx = Util.CenterOfCell(exitTile.Value); - var currentPx = Util.CenterOfCell(self.Location); + var exit = exitTile.Value.CenterPosition; + var current = self.Location.CenterPosition; self.World.AddFrameEndTask(w => { - if (actor.Destroyed) return; + if (actor.Destroyed) + return; var mobile = actor.Trait(); - mobile.Facing = Util.GetFacing( (exitPx - currentPx).ToInt2(), mobile.Facing ); + mobile.Facing = Util.GetFacing(exit - current, mobile.Facing ); mobile.SetPosition(actor, exitTile.Value); - mobile.AdjustPxPosition(actor, currentPx); - var speed = mobile.MovementSpeedForCell(actor, exitTile.Value); - var length = speed > 0 ? ((int)(exitPx - currentPx).Length * 3 / speed) : 0; + mobile.AdjustPxPosition(actor, PPos.FromWPos(current)); + var speed = mobile.WorldMovementSpeedForCell(actor, exitTile.Value); + var length = speed > 0 ? ((int)(exit - current).Length * 3 / speed) : 0; w.Add(actor); actor.CancelActivity(); - actor.QueueActivity(new Drag(currentPx, exitPx, length)); + actor.QueueActivity(new Drag(current, exit, length)); actor.QueueActivity(mobile.MoveTo(exitTile.Value, 0)); var rallyPoint = ChooseRallyPoint(actor).Value; diff --git a/OpenRA.Mods.RA/Air/Aircraft.cs b/OpenRA.Mods.RA/Air/Aircraft.cs index 55958cb82f..0cb4bed6db 100755 --- a/OpenRA.Mods.RA/Air/Aircraft.cs +++ b/OpenRA.Mods.RA/Air/Aircraft.cs @@ -177,7 +177,7 @@ namespace OpenRA.Mods.RA.Air return new Order(order.OrderID, self, queued) { TargetActor = target.Actor }; if (order.OrderID == "Move") - return new Order(order.OrderID, self, queued) { TargetLocation = target.CenterLocation.ToCPos() }; + return new Order(order.OrderID, self, queued) { TargetLocation = target.CenterPosition.ToCPos() }; return null; } diff --git a/OpenRA.Mods.RA/Air/FallsToEarth.cs b/OpenRA.Mods.RA/Air/FallsToEarth.cs index 678b367874..9f62efcc01 100755 --- a/OpenRA.Mods.RA/Air/FallsToEarth.cs +++ b/OpenRA.Mods.RA/Air/FallsToEarth.cs @@ -50,7 +50,7 @@ namespace OpenRA.Mods.RA.Air if (aircraft.Altitude <= 0) { if (info.Explosion != null) - Combat.DoExplosion(self, info.Explosion, self.CenterLocation, 0); + Combat.DoExplosion(self, info.Explosion, self.CenterPosition); self.Destroy(); return null; diff --git a/OpenRA.Mods.RA/Air/Fly.cs b/OpenRA.Mods.RA/Air/Fly.cs index b5a08d103b..09f2ca2d81 100755 --- a/OpenRA.Mods.RA/Air/Fly.cs +++ b/OpenRA.Mods.RA/Air/Fly.cs @@ -35,7 +35,7 @@ namespace OpenRA.Mods.RA.Air var aircraft = self.Trait(); - var desiredFacing = Util.GetFacing(d.ToInt2(), aircraft.Facing); + var desiredFacing = Util.GetFacing(d, aircraft.Facing); if (aircraft.Altitude == cruiseAltitude) aircraft.Facing = Util.TickFacing(aircraft.Facing, desiredFacing, aircraft.ROT); diff --git a/OpenRA.Mods.RA/Air/HeliAttack.cs b/OpenRA.Mods.RA/Air/HeliAttack.cs index 2bad762b79..87e3de5eed 100755 --- a/OpenRA.Mods.RA/Air/HeliAttack.cs +++ b/OpenRA.Mods.RA/Air/HeliAttack.cs @@ -38,12 +38,12 @@ namespace OpenRA.Mods.RA.Air } var attack = self.Trait(); - var dist = target.CenterLocation - self.CenterLocation; + var dist = target.CenterPosition - self.CenterPosition; var desiredFacing = Util.GetFacing(dist, aircraft.Facing); aircraft.Facing = Util.TickFacing(aircraft.Facing, desiredFacing, aircraft.ROT); - if (!Combat.IsInRange(self.CenterLocation, attack.GetMaximumRange(), target)) + if (!target.IsInRange(self.CenterPosition, attack.GetMaximumRange())) aircraft.TickMove(PSubPos.PerPx * aircraft.MovementSpeed, desiredFacing); attack.DoAttack( self, target ); diff --git a/OpenRA.Mods.RA/Air/Land.cs b/OpenRA.Mods.RA/Air/Land.cs index ad028daa55..eb5d41f398 100755 --- a/OpenRA.Mods.RA/Air/Land.cs +++ b/OpenRA.Mods.RA/Air/Land.cs @@ -27,8 +27,8 @@ namespace OpenRA.Mods.RA.Air if (IsCanceled) return NextActivity; - var d = Target.CenterLocation - self.CenterLocation; - if (d.LengthSquared < 50) /* close enough */ + var d = Target.CenterPosition - self.CenterPosition; + if (d.LengthSquared < 256*256) // close enough (1/4 cell) return NextActivity; var aircraft = self.Trait(); diff --git a/OpenRA.Mods.RA/Armament.cs b/OpenRA.Mods.RA/Armament.cs index 74809cfce0..7ec683ac00 100755 --- a/OpenRA.Mods.RA/Armament.cs +++ b/OpenRA.Mods.RA/Armament.cs @@ -107,9 +107,17 @@ namespace OpenRA.Mods.RA if (limitedAmmo != null && !limitedAmmo.HasAmmo()) return; - if (!Combat.IsInRange(self.CenterLocation, Weapon.Range, target)) return; - if (Combat.IsInRange(self.CenterLocation, Weapon.MinRange, target)) return; - if (!IsValidAgainst(self.World, target)) return; + // TODO: Define weapon ranges as WRange + var range = new WRange((int)(1024*Weapon.Range)); + var minRange = new WRange((int)(1024*Weapon.MinRange)); + if (!target.IsInRange(self.CenterPosition, range)) + return; + + if (target.IsInRange(self.CenterPosition, minRange)) + return; + + if (!Weapon.IsValidAgainst(target, self.World)) + return; var barrel = Barrels[Burst % Barrels.Length]; var destMove = target.IsActor ? target.Actor.TraitOrDefault() : null; @@ -164,14 +172,6 @@ namespace OpenRA.Mods.RA } } - public bool IsValidAgainst(World world, Target target) - { - if (target.IsActor) - return Combat.WeaponValidForTarget(Weapon, target.Actor); - else - return Combat.WeaponValidForTarget(Weapon, world, target.CenterLocation.ToCPos()); - } - public bool IsReloading { get { return FireDelay > 0; } } public WVec MuzzleOffset(Actor self, Barrel b) diff --git a/OpenRA.Mods.RA/Attack/AttackBase.cs b/OpenRA.Mods.RA/Attack/AttackBase.cs index a3bd02a1f9..c8263c6dec 100644 --- a/OpenRA.Mods.RA/Attack/AttackBase.cs +++ b/OpenRA.Mods.RA/Attack/AttackBase.cs @@ -117,7 +117,7 @@ namespace OpenRA.Mods.RA if (target.IsActor) return new Order("Attack", self, queued) { TargetActor = target.Actor }; else - return new Order("Attack", self, queued) { TargetLocation = target.CenterLocation.ToCPos() }; + return new Order("Attack", self, queued) { TargetLocation = target.CenterPosition.ToCPos() }; } return null; } @@ -139,10 +139,10 @@ namespace OpenRA.Mods.RA public abstract Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove); - public bool HasAnyValidWeapons(Target t) { return Armaments.Any(a => a.IsValidAgainst(self.World, t)); } - public float GetMaximumRange() { return Armaments.Select(a => a.Weapon.Range).Aggregate(0f, Math.Max); } + public bool HasAnyValidWeapons(Target t) { return Armaments.Any(a => a.Weapon.IsValidAgainst(t, self.World)); } + public WRange GetMaximumRange() { return new WRange((int)(1024*Armaments.Max(a => a.Weapon.Range))); } - public Armament ChooseArmamentForTarget(Target t) { return Armaments.FirstOrDefault(a => a.IsValidAgainst(self.World, t)); } + public Armament ChooseArmamentForTarget(Target t) { return Armaments.FirstOrDefault(a => a.Weapon.IsValidAgainst(t, self.World)); } public void AttackTarget(Target target, bool queued, bool allowMove) { diff --git a/OpenRA.Mods.RA/Attack/AttackFrontal.cs b/OpenRA.Mods.RA/Attack/AttackFrontal.cs index 2f52e23625..4e5abbb442 100644 --- a/OpenRA.Mods.RA/Attack/AttackFrontal.cs +++ b/OpenRA.Mods.RA/Attack/AttackFrontal.cs @@ -28,15 +28,15 @@ namespace OpenRA.Mods.RA public AttackFrontal(Actor self, AttackFrontalInfo info) : base( self ) { this.info = info; } - protected override bool CanAttack( Actor self, Target target ) + protected override bool CanAttack(Actor self, Target target) { - if( !base.CanAttack( self, target ) ) + if (!base.CanAttack(self, target)) return false; var facing = self.Trait().Facing; - var facingToTarget = Util.GetFacing(target.CenterLocation - self.CenterLocation, facing); + var facingToTarget = Util.GetFacing(target.CenterPosition - self.CenterPosition, facing); - if( Math.Abs( facingToTarget - facing ) % 256 > info.FacingTolerance ) + if (Math.Abs(facingToTarget - facing) % 256 > info.FacingTolerance) return false; return true; @@ -44,10 +44,13 @@ namespace OpenRA.Mods.RA public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove) { - var weapon = ChooseArmamentForTarget(newTarget); - if (weapon == null) + var a = ChooseArmamentForTarget(newTarget); + if (a == null) return null; - return new Activities.Attack(newTarget, Math.Max(0, (int)weapon.Weapon.Range), allowMove); + + // TODO: Define weapon ranges as WRange + var range = new WRange(Math.Max(0,(int)(1024*a.Weapon.Range))); + return new Activities.Attack(newTarget, range, allowMove); } } } diff --git a/OpenRA.Mods.RA/Attack/AttackLeap.cs b/OpenRA.Mods.RA/Attack/AttackLeap.cs index b9acc3a097..75683b1673 100644 --- a/OpenRA.Mods.RA/Attack/AttackLeap.cs +++ b/OpenRA.Mods.RA/Attack/AttackLeap.cs @@ -10,6 +10,7 @@ using System; using System.Linq; +using OpenRA.FileFormats; using OpenRA.Mods.RA.Activities; using OpenRA.Traits; @@ -17,38 +18,39 @@ namespace OpenRA.Mods.RA { class AttackLeapInfo : AttackFrontalInfo { + [Desc("Leap speed (in units/tick).")] + public readonly WRange Speed = new WRange(426); + public readonly WAngle Angle = WAngle.FromDegrees(20); + public override object Create(ActorInitializer init) { return new AttackLeap(init.self, this); } } class AttackLeap : AttackFrontal, ISync { - [Sync] internal bool IsLeaping; + AttackLeapInfo info; public AttackLeap(Actor self, AttackLeapInfo info) - : base(self, info) {} + : base(self, info) + { + this.info = info; + } public override void DoAttack(Actor self, Target target) { - if (!CanAttack(self, target)) + if (!CanAttack(self, target) || !target.IsActor) return; var a = ChooseArmamentForTarget(target); if (a == null) return; - if (!Combat.IsInRange(self.CenterLocation, a.Weapon.Range, target)) + // TODO: Define weapon ranges as WRange + var range = new WRange((int)(1024*a.Weapon.Range)); + if (!target.IsInRange(self.CenterPosition, range)) return; self.CancelActivity(); - self.QueueActivity(new Leap(self, target)); - } - - public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove) - { - var a = ChooseArmamentForTarget(newTarget); - if (a == null) - return null; - return new Activities.Attack(newTarget, Math.Max(0, (int)a.Weapon.Range), allowMove); + self.QueueActivity(new Leap(self, target.Actor, a.Weapon, info.Speed, info.Angle)); } } } diff --git a/OpenRA.Mods.RA/Attack/AttackLoyalty.cs b/OpenRA.Mods.RA/Attack/AttackLoyalty.cs index 3559fe52fb..ceb8caab0f 100644 --- a/OpenRA.Mods.RA/Attack/AttackLoyalty.cs +++ b/OpenRA.Mods.RA/Attack/AttackLoyalty.cs @@ -33,7 +33,9 @@ namespace OpenRA.Mods.RA if (arm == null) return; - if (!Combat.IsInRange(self.CenterLocation, arm.Weapon.Range, target)) + // TODO: Define weapon ranges as WRange + var range = new WRange((int)(1024*arm.Weapon.Range)); + if (!target.IsInRange(self.CenterPosition, range)) return; var move = self.TraitOrDefault(); diff --git a/OpenRA.Mods.RA/Attack/AttackMedic.cs b/OpenRA.Mods.RA/Attack/AttackMedic.cs index ec705f25ba..699b2d9cbd 100644 --- a/OpenRA.Mods.RA/Attack/AttackMedic.cs +++ b/OpenRA.Mods.RA/Attack/AttackMedic.cs @@ -29,10 +29,13 @@ namespace OpenRA.Mods.RA public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove) { - var weapon = ChooseArmamentForTarget(newTarget); - if (weapon == null) + var a = ChooseArmamentForTarget(newTarget); + if (a == null) return null; - return new Activities.Heal(newTarget, Math.Max(0, (int)weapon.Weapon.Range), allowMove); + + // TODO: Define weapon ranges as WRange + var range = new WRange(Math.Max(0,(int)(1024*a.Weapon.Range))); + return new Activities.Heal(newTarget, range, allowMove); } } } diff --git a/OpenRA.Mods.RA/AutoHeal.cs b/OpenRA.Mods.RA/AutoHeal.cs index 60b4ca4f40..130f133801 100644 --- a/OpenRA.Mods.RA/AutoHeal.cs +++ b/OpenRA.Mods.RA/AutoHeal.cs @@ -19,10 +19,10 @@ namespace OpenRA.Mods.RA class AutoHeal : INotifyIdle { - public void TickIdle( Actor self ) + public void TickIdle(Actor self) { var attack = self.Trait(); - var inRange = self.World.FindUnitsInCircle(self.CenterLocation, (int)(Game.CellSize * attack.GetMaximumRange())); + var inRange = self.World.FindUnitsInCircle(self.CenterPosition, attack.GetMaximumRange()); var target = inRange .Where(a => a != self && a.AppearsFriendlyTo(self)) @@ -31,8 +31,8 @@ namespace OpenRA.Mods.RA .Where(a => attack.HasAnyValidWeapons(Target.FromActor(a))) .ClosestTo( self.CenterLocation ); - if( target != null ) - self.QueueActivity(attack.GetAttackActivity(self, Target.FromActor( target ), false )); + if (target != null) + self.QueueActivity(attack.GetAttackActivity(self, Target.FromActor(target), false)); } } } diff --git a/OpenRA.Mods.RA/AutoTarget.cs b/OpenRA.Mods.RA/AutoTarget.cs index d53f66fcc3..993ff3f7c3 100644 --- a/OpenRA.Mods.RA/AutoTarget.cs +++ b/OpenRA.Mods.RA/AutoTarget.cs @@ -87,8 +87,9 @@ namespace OpenRA.Mods.RA var target = ScanForTarget(self, null); if (target != null) { - self.SetTargetLine(Target.FromActor(target), Color.Red, false); - attack.AttackTarget(Target.FromActor(target), false, Info.AllowMovement && stance != UnitStance.Defend); + var t = Target.FromActor(target); + self.SetTargetLine(t, Color.Red, false); + attack.AttackTarget(t, false, Info.AllowMovement && stance != UnitStance.Defend); } } @@ -100,10 +101,9 @@ namespace OpenRA.Mods.RA public Actor ScanForTarget(Actor self, Actor currentTarget) { - var range = Info.ScanRadius > 0 ? Info.ScanRadius : attack.GetMaximumRange(); - - if (self.IsIdle || currentTarget == null || !Combat.IsInRange(self.CenterLocation, range, currentTarget)) - if(nextScanTime <= 0) + var range = Info.ScanRadius > 0 ? WRange.FromCells(Info.ScanRadius) : attack.GetMaximumRange(); + if (self.IsIdle || currentTarget == null || !Target.FromActor(currentTarget).IsInRange(self.CenterPosition, range)) + if (nextScanTime <= 0) return ChooseTarget(self, range); return currentTarget; @@ -116,11 +116,10 @@ namespace OpenRA.Mods.RA attack.AttackTarget(Target.FromActor(targetActor), false, Info.AllowMovement && stance != UnitStance.Defend); } - Actor ChooseTarget(Actor self, float range) + Actor ChooseTarget(Actor self, WRange range) { nextScanTime = self.World.SharedRandom.Next(Info.MinimumScanTimeInterval, Info.MaximumScanTimeInterval); - - var inRange = self.World.FindUnitsInCircle(self.CenterLocation, (int)(Game.CellSize * range)); + var inRange = self.World.FindUnitsInCircle(self.CenterPosition, range); if (self.Owner.HasFogVisibility()) { diff --git a/OpenRA.Mods.RA/BelowUnits.cs b/OpenRA.Mods.RA/BelowUnits.cs index 73c6fe4616..9de8e5b48d 100644 --- a/OpenRA.Mods.RA/BelowUnits.cs +++ b/OpenRA.Mods.RA/BelowUnits.cs @@ -29,7 +29,7 @@ namespace OpenRA.Mods.RA { // Offset effective position to the top of the northernmost occupied cell var bi = self.Info.Traits.GetOrDefault(); - offset = (bi != null) ? -FootprintUtils.CenterOffset(bi).Y : -512; + offset = ((bi != null) ? -FootprintUtils.CenterOffset(bi).Y : 0) - 512; } public IEnumerable ModifyRender(Actor self, WorldRenderer wr, IEnumerable r) diff --git a/OpenRA.Mods.RA/Buildings/BaseProvider.cs b/OpenRA.Mods.RA/Buildings/BaseProvider.cs index 0622f5a441..c3c28e8b61 100755 --- a/OpenRA.Mods.RA/Buildings/BaseProvider.cs +++ b/OpenRA.Mods.RA/Buildings/BaseProvider.cs @@ -17,7 +17,7 @@ namespace OpenRA.Mods.RA.Buildings { public class BaseProviderInfo : ITraitInfo { - public readonly float Range = 10; + public readonly int Range = 10; public readonly int Cooldown = 0; public readonly int InitialDelay = 0; diff --git a/OpenRA.Mods.RA/Buildings/Building.cs b/OpenRA.Mods.RA/Buildings/Building.cs index f4904e8b82..057523555e 100755 --- a/OpenRA.Mods.RA/Buildings/Building.cs +++ b/OpenRA.Mods.RA/Buildings/Building.cs @@ -38,23 +38,20 @@ namespace OpenRA.Mods.RA.Buildings public object Create(ActorInitializer init) { return new Building(init, this); } - public PPos CenterLocation(CPos topLeft) + public Actor FindBaseProvider(World world, Player p, CPos topLeft) { - return (PPos)((2 * topLeft.ToInt2() + Dimensions) * Game.CellSize / 2); - } - - bool HasBaseProvider(World world, Player p, CPos topLeft) - { - var center = CenterLocation(topLeft); + var center = topLeft.CenterPosition + FootprintUtils.CenterOffset(this); foreach (var bp in world.ActorsWithTrait()) { if (bp.Actor.Owner.Stances[p] != Stance.Ally || !bp.Trait.Ready()) continue; - if (Combat.IsInRange(center, bp.Trait.Info.Range, bp.Actor.CenterLocation)) - return true; + // Range is counted from the center of the actor, not from each cell. + var target = Target.FromPos(bp.Actor.CenterPosition); + if (target.IsInRange(center, WRange.FromCells(bp.Trait.Info.Range))) + return bp.Actor; } - return false; + return null; } public bool IsCloseEnoughToBase(World world, Player p, string buildingName, CPos topLeft) @@ -62,7 +59,7 @@ namespace OpenRA.Mods.RA.Buildings if (p.PlayerActor.Trait().BuildAnywhere) return true; - if (RequiresBaseProvider && !HasBaseProvider(world, p, topLeft)) + if (RequiresBaseProvider && FindBaseProvider(world, p, topLeft) == null) return false; var buildingMaxBounds = (CVec)Dimensions; @@ -126,7 +123,9 @@ namespace OpenRA.Mods.RA.Buildings occupiedCells = FootprintUtils.UnpathableTiles( self.Info.Name, Info, TopLeft ) .Select(c => Pair.New(c, SubCell.FullCell)).ToArray(); - pxPosition = Info.CenterLocation(topLeft); + + var position = topLeft.CenterPosition + FootprintUtils.CenterOffset(Info); + pxPosition = PPos.FromWPosHackZ(position); } public int GetPowerUsage() diff --git a/OpenRA.Mods.RA/Buildings/FootprintUtils.cs b/OpenRA.Mods.RA/Buildings/FootprintUtils.cs index cddc6ac7c2..ddddcef376 100755 --- a/OpenRA.Mods.RA/Buildings/FootprintUtils.cs +++ b/OpenRA.Mods.RA/Buildings/FootprintUtils.cs @@ -65,7 +65,8 @@ namespace OpenRA.Mods.RA.Buildings public static WVec CenterOffset(BuildingInfo buildingInfo) { var dim = buildingInfo.Dimensions; - return new CVec(dim.X, dim.Y).ToWVec() / 2; + // Offset is measured relative to the center of the cell, so need to subtract an additional half cell. + return new CVec(dim.X, dim.Y).ToWVec() / 2 - new WVec(512, 512, 0); } } } diff --git a/OpenRA.Mods.RA/CarpetBomb.cs b/OpenRA.Mods.RA/CarpetBomb.cs index 7583e27822..03e612d3fe 100644 --- a/OpenRA.Mods.RA/CarpetBomb.cs +++ b/OpenRA.Mods.RA/CarpetBomb.cs @@ -14,25 +14,37 @@ using OpenRA.Traits; namespace OpenRA.Mods.RA { - class CarpetBombInfo : TraitInfo + class CarpetBombInfo : ITraitInfo { [WeaponReference] public readonly string Weapon = null; public readonly int Range = 3; + + public object Create(ActorInitializer init) { return new CarpetBomb(this); } } - class CarpetBomb : ITick, ISync // TODO: maybe integrate this better with the normal weapons system? + // TODO: maybe integrate this better with the normal weapons system? + class CarpetBomb : ITick, ISync { - [Sync] CPos Target; - [Sync] int dropDelay; + CarpetBombInfo info; + Target target; - public void SetTarget(CPos targetCell) { Target = targetCell; } + [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) { - var info = self.Info.Traits.Get(); - - if( !Combat.IsInRange( self.CenterLocation, info.Range, Target.ToPPos() ) ) + if (!target.IsInRange(self.CenterPosition, range)) return; var limitedAmmo = self.TraitOrDefault(); diff --git a/OpenRA.Mods.RA/Combat.cs b/OpenRA.Mods.RA/Combat.cs index 3a3c821639..c65c21837a 100755 --- a/OpenRA.Mods.RA/Combat.cs +++ b/OpenRA.Mods.RA/Combat.cs @@ -45,7 +45,7 @@ namespace OpenRA.Mods.RA if (explosionType != null) world.AddFrameEndTask( - w => w.Add(new Explosion(w, args.dest, explosionType, isWater, args.destAltitude))); + w => w.Add(new Explosion(w, args.dest.ToWPos(args.destAltitude), explosionType))); Sound.Play(GetImpactSound(warhead, isWater), args.dest); @@ -139,12 +139,14 @@ namespace OpenRA.Mods.RA } } - public static void DoExplosion(Actor attacker, string weapontype, PPos pos, int altitude) + public static void DoExplosion(Actor attacker, string weapontype, WPos pos) { + var pxPos = PPos.FromWPos(pos); + var altitude = pos.Z * Game.CellSize / 1024; var args = new ProjectileArgs { - src = pos, - dest = pos, + src = pxPos, + dest = pxPos, srcAltitude = altitude, destAltitude = altitude, firedBy = attacker, @@ -154,7 +156,7 @@ namespace OpenRA.Mods.RA }; if (args.weapon.Report != null && args.weapon.Report.Any()) - Sound.Play(args.weapon.Report.Random(attacker.World.SharedRandom), pos); + Sound.Play(args.weapon.Report.Random(attacker.World.SharedRandom), pxPos); DoImpacts(args); } @@ -176,7 +178,8 @@ namespace OpenRA.Mods.RA static float GetDamageToInflict(Actor target, ProjectileArgs args, WarheadInfo warhead, float modifier) { // don't hit air units with splash from ground explosions, etc - if (!WeaponValidForTarget(args.weapon, target)) return 0f; + if (!args.weapon.IsValidAgainst(target)) + return 0f; var health = target.Info.Traits.GetOrDefault(); if( health == null ) return 0f; @@ -188,48 +191,5 @@ namespace OpenRA.Mods.RA return (float)(rawDamage * multiplier); } - - public static bool WeaponValidForTarget(WeaponInfo weapon, Actor target) - { - var targetable = target.TraitOrDefault(); - if (targetable == null || !weapon.ValidTargets.Intersect(targetable.TargetTypes).Any()) - return false; - - if (weapon.Warheads.All( w => w.EffectivenessAgainst(target) <= 0)) - return false; - - return true; - } - - public static bool WeaponValidForTarget(WeaponInfo weapon, World world, CPos location) - { - if (weapon.ValidTargets.Contains("Ground") && world.GetTerrainType(location) != "Water") return true; - if (weapon.ValidTargets.Contains("Water") && world.GetTerrainType(location) == "Water") return true; - return false; - } - - public static bool IsInRange(PPos attackOrigin, float range, Actor target) - { - var rsq = range * range * Game.CellSize * Game.CellSize; - foreach (var cell in target.Trait().TargetableCells(target)) - if ((attackOrigin - Util.CenterOfCell(cell)).LengthSquared <= rsq) - return true; - return false; - } - - public static bool IsInRange(PPos attackOrigin, float range, PPos targetLocation) - { - var rsq = range * range * Game.CellSize * Game.CellSize; - return (attackOrigin - targetLocation).LengthSquared <= rsq; - } - - public static bool IsInRange(PPos attackOrigin, float range, Target target) - { - if (!target.IsValid) return false; - if (target.IsActor) - return IsInRange(attackOrigin, range, target.Actor); - else - return IsInRange(attackOrigin, range, target.CenterLocation); - } } } diff --git a/OpenRA.Mods.RA/Crates/ExplodeCrateAction.cs b/OpenRA.Mods.RA/Crates/ExplodeCrateAction.cs index 6924c85f8c..2826a21eb9 100644 --- a/OpenRA.Mods.RA/Crates/ExplodeCrateAction.cs +++ b/OpenRA.Mods.RA/Crates/ExplodeCrateAction.cs @@ -27,7 +27,7 @@ namespace OpenRA.Mods.RA public override void Activate(Actor collector) { - Combat.DoExplosion(self, (info as ExplodeCrateActionInfo).Weapon, collector.CenterLocation, 0); + Combat.DoExplosion(self, (info as ExplodeCrateActionInfo).Weapon, collector.CenterPosition); base.Activate(collector); } } diff --git a/OpenRA.Mods.RA/Effects/Explosion.cs b/OpenRA.Mods.RA/Effects/Explosion.cs index 7463e3c2ce..0a8e953e21 100644 --- a/OpenRA.Mods.RA/Effects/Explosion.cs +++ b/OpenRA.Mods.RA/Effects/Explosion.cs @@ -18,26 +18,20 @@ namespace OpenRA.Mods.RA.Effects public class Explosion : IEffect { Animation anim; - PPos pos; - int altitude; + WPos pos; - public Explosion(World world, PPos pixelPos, string style, bool isWater, int altitude) + public Explosion(World world, WPos pos, string style) { - this.pos = pixelPos; - this.altitude = altitude; + this.pos = pos; anim = new Animation("explosion"); - anim.PlayThen(style, - () => world.AddFrameEndTask(w => w.Remove(this))); + anim.PlayThen(style, () => world.AddFrameEndTask(w => w.Remove(this))); } public void Tick( World world ) { anim.Tick(); } public IEnumerable Render(WorldRenderer wr) { - var p = pos.ToInt2() - new int2(0, altitude); - yield return new SpriteRenderable(anim.Image, p, wr.Palette("effect"), p.Y); + yield return new SpriteRenderable(anim.Image, pos, 0, wr.Palette("effect"), 1f); } - - public Player Owner { get { return null; } } } } diff --git a/OpenRA.Mods.RA/Effects/NukeLaunch.cs b/OpenRA.Mods.RA/Effects/NukeLaunch.cs index e0c9657f8f..c228048335 100755 --- a/OpenRA.Mods.RA/Effects/NukeLaunch.cs +++ b/OpenRA.Mods.RA/Effects/NukeLaunch.cs @@ -66,7 +66,7 @@ namespace OpenRA.Mods.RA.Effects void Explode(World world) { world.AddFrameEndTask(w => w.Remove(this)); - Combat.DoExplosion(firedBy.PlayerActor, weapon, PPos.FromWPos(pos), pos.Z * Game.CellSize / 1024); + Combat.DoExplosion(firedBy.PlayerActor, weapon, pos); world.WorldActor.Trait().AddEffect(20, PPos.FromWPos(pos).ToFloat2(), 5); foreach (var a in world.ActorsWithTrait()) diff --git a/OpenRA.Mods.RA/Effects/Parachute.cs b/OpenRA.Mods.RA/Effects/Parachute.cs index 97ac756ce8..bc20073810 100644 --- a/OpenRA.Mods.RA/Effects/Parachute.cs +++ b/OpenRA.Mods.RA/Effects/Parachute.cs @@ -36,7 +36,7 @@ namespace OpenRA.Mods.RA.Effects parachuteOffset = pai.Offset; // Adjust x,y to match the target subcell - cargo.Trait().SetPosition(cargo, new CPos(dropPosition)); + cargo.Trait().SetPosition(cargo, dropPosition.ToCPos()); var cp = cargo.CenterPosition; pos = new WPos(cp.X, cp.Y, dropPosition.Z); } diff --git a/OpenRA.Mods.RA/Explodes.cs b/OpenRA.Mods.RA/Explodes.cs index 8d23da8c3f..96b2bd1b0a 100644 --- a/OpenRA.Mods.RA/Explodes.cs +++ b/OpenRA.Mods.RA/Explodes.cs @@ -42,11 +42,7 @@ namespace OpenRA.Mods.RA var weapon = ChooseWeaponForExplosion(self); if (weapon != null) - { - var move = self.TraitOrDefault(); - var altitude = move != null ? move.Altitude : 0; - Combat.DoExplosion(e.Attacker, weapon, self.CenterLocation, altitude); - } + Combat.DoExplosion(e.Attacker, weapon, self.CenterPosition); } string ChooseWeaponForExplosion(Actor self) diff --git a/OpenRA.Mods.RA/Harvester.cs b/OpenRA.Mods.RA/Harvester.cs index 7a17785dd2..562364f255 100644 --- a/OpenRA.Mods.RA/Harvester.cs +++ b/OpenRA.Mods.RA/Harvester.cs @@ -250,7 +250,7 @@ namespace OpenRA.Mods.RA return new Order(order.OrderID, self, queued) { TargetActor = target.Actor }; if (order.OrderID == "Harvest") - return new Order(order.OrderID, self, queued) { TargetLocation = target.CenterLocation.ToCPos() }; + return new Order(order.OrderID, self, queued) { TargetLocation = target.CenterPosition.ToCPos() }; return null; } diff --git a/OpenRA.Mods.RA/Minelayer.cs b/OpenRA.Mods.RA/Minelayer.cs index da775f83fc..6d638a0e0e 100644 --- a/OpenRA.Mods.RA/Minelayer.cs +++ b/OpenRA.Mods.RA/Minelayer.cs @@ -46,7 +46,7 @@ namespace OpenRA.Mods.RA { if( order is BeginMinefieldOrderTargeter ) { - var start = target.CenterLocation.ToCPos(); + var start = target.CenterPosition.ToCPos(); self.World.OrderGenerator = new MinefieldOrderGenerator( self, start ); return new Order("BeginMinefield", self, false) { TargetLocation = start }; } diff --git a/OpenRA.Mods.RA/Missions/Allies03Script.cs b/OpenRA.Mods.RA/Missions/Allies03Script.cs index 28ee1db87a..1adde71cf4 100644 --- a/OpenRA.Mods.RA/Missions/Allies03Script.cs +++ b/OpenRA.Mods.RA/Missions/Allies03Script.cs @@ -297,7 +297,7 @@ namespace OpenRA.Mods.RA.Missions var enemy = FirstUnshroudedOrDefault(enemies.OrderBy(u => (self.CenterLocation - u.CenterLocation).LengthSquared), world, 10); if (enemy != null) - self.QueueActivity(new AttackMove.AttackMoveActivity(self, new Attack(Target.FromActor(enemy), 3))); + self.QueueActivity(new AttackMove.AttackMoveActivity(self, new Attack(Target.FromActor(enemy), WRange.FromCells(3)))); } void ManageSovietUnits() diff --git a/OpenRA.Mods.RA/Missions/Allies04Script.cs b/OpenRA.Mods.RA/Missions/Allies04Script.cs index a9c32e015a..ba65084906 100644 --- a/OpenRA.Mods.RA/Missions/Allies04Script.cs +++ b/OpenRA.Mods.RA/Missions/Allies04Script.cs @@ -125,7 +125,7 @@ namespace OpenRA.Mods.RA.Missions if (world.FrameNumber == frameInfiltrated + 1500 * 12 && !bridgeTank.IsDead() && bridgeTank.IsInWorld && !bridge.IsDead()) { - bridgeTank.QueueActivity(new Attack(Target.FromPos(bridge.CenterLocation), 4)); + bridgeTank.QueueActivity(new Attack(Target.FromPos(bridge.CenterPosition), WRange.FromCells(4))); attackingBridge = true; } if (attackingBridge && bridge.IsDead()) @@ -152,7 +152,7 @@ namespace OpenRA.Mods.RA.Missions var enemy = enemies.OrderBy(u => (attacker.CenterLocation - u.CenterLocation).LengthSquared).FirstOrDefault(); if (enemy != null) - attacker.QueueActivity(new AttackMove.AttackMoveActivity(attacker, new Attack(Target.FromActor(enemy), 3))); + attacker.QueueActivity(new AttackMove.AttackMoveActivity(attacker, new Attack(Target.FromActor(enemy), WRange.FromCells(3)))); else { attackingTown = false; diff --git a/OpenRA.Mods.RA/Missions/FortLonestarScript.cs b/OpenRA.Mods.RA/Missions/FortLonestarScript.cs index 9e61f389fc..9aebdad818 100644 --- a/OpenRA.Mods.RA/Missions/FortLonestarScript.cs +++ b/OpenRA.Mods.RA/Missions/FortLonestarScript.cs @@ -82,7 +82,7 @@ namespace OpenRA.Mods.RA.Missions var targetEnemy = enemies.OrderBy(u => (self.CenterLocation - u.CenterLocation).LengthSquared).FirstOrDefault(); if (targetEnemy != null) { - self.QueueActivity(new AttackMove.AttackMoveActivity(self, new Attack(Target.FromActor(targetEnemy), 6))); + self.QueueActivity(new AttackMove.AttackMoveActivity(self, new Attack(Target.FromActor(targetEnemy), WRange.FromCells(6)))); } } diff --git a/OpenRA.Mods.RA/Missions/MissionUtils.cs b/OpenRA.Mods.RA/Missions/MissionUtils.cs index 93d9b943af..2d0537c547 100644 --- a/OpenRA.Mods.RA/Missions/MissionUtils.cs +++ b/OpenRA.Mods.RA/Missions/MissionUtils.cs @@ -213,7 +213,7 @@ namespace OpenRA.Mods.RA.Missions var enemy = enemies.OrderBy(u => (self.CenterLocation - u.CenterLocation).LengthSquared).FirstOrDefault(); if (enemy != null) - self.QueueActivity(queued, new AttackMove.AttackMoveActivity(self, new Attack(Target.FromActor(enemy), 3))); + self.QueueActivity(queued, new AttackMove.AttackMoveActivity(self, new Attack(Target.FromActor(enemy), WRange.FromCells(3)))); } } diff --git a/OpenRA.Mods.RA/Missions/MonsterTankMadnessScript.cs b/OpenRA.Mods.RA/Missions/MonsterTankMadnessScript.cs index 58d189afee..622ebae34a 100644 --- a/OpenRA.Mods.RA/Missions/MonsterTankMadnessScript.cs +++ b/OpenRA.Mods.RA/Missions/MonsterTankMadnessScript.cs @@ -258,7 +258,7 @@ namespace OpenRA.Mods.RA.Missions { Sound.Play("reinfor1.aud"); foreach (var unit in units) - world.CreateActor(unit, greece, startEntryPoint.Location, Util.GetFacing(startBridgeEndPoint.CenterLocation - startEntryPoint.CenterLocation, 0)) + world.CreateActor(unit, greece, startEntryPoint.Location, Util.GetFacing(startBridgeEndPoint.CenterPosition - startEntryPoint.CenterPosition, 0)) .QueueActivity(new Move.Move(startMovePoint.Location, 0)); } @@ -266,7 +266,7 @@ namespace OpenRA.Mods.RA.Missions { Sound.Play("reinfor1.aud"); foreach (var unit in units) - world.CreateActor(unit, greece, alliedBaseEntryPoint.Location, Util.GetFacing(alliedBaseMovePoint.CenterLocation - alliedBaseEntryPoint.CenterLocation, 0)) + world.CreateActor(unit, greece, alliedBaseEntryPoint.Location, Util.GetFacing(alliedBaseMovePoint.CenterPosition - alliedBaseEntryPoint.CenterPosition, 0)) .QueueActivity(new Move.Move(alliedBaseMovePoint.Location, 0)); } diff --git a/OpenRA.Mods.RA/Missions/Soviet01ClassicScript.cs b/OpenRA.Mods.RA/Missions/Soviet01ClassicScript.cs index 121aebb274..0d1caddd12 100644 --- a/OpenRA.Mods.RA/Missions/Soviet01ClassicScript.cs +++ b/OpenRA.Mods.RA/Missions/Soviet01ClassicScript.cs @@ -128,7 +128,7 @@ namespace OpenRA.Mods.RA.Missions .Where(a => a.HasTrait() && !a.IsDead()) .OrderBy(a => (startJeep.CenterLocation - a.CenterLocation).LengthSquared) .First(); - Combat.DoExplosion(bridge, "Demolish", bridge.CenterLocation, 0); + Combat.DoExplosion(bridge, "Demolish", bridge.CenterPosition); world.WorldActor.Trait().AddEffect(15, bridge.CenterLocation.ToFloat2(), 6); bridge.Kill(bridge); })); diff --git a/OpenRA.Mods.RA/Missions/Survival01Script.cs b/OpenRA.Mods.RA/Missions/Survival01Script.cs index 3f976702bf..b5b62aa75b 100644 --- a/OpenRA.Mods.RA/Missions/Survival01Script.cs +++ b/OpenRA.Mods.RA/Missions/Survival01Script.cs @@ -214,7 +214,7 @@ namespace OpenRA.Mods.RA.Missions var targetEnemy = enemies.OrderBy(u => (self.CenterLocation - u.CenterLocation).LengthSquared).FirstOrDefault(); if (targetEnemy != null) { - self.QueueActivity(new AttackMove.AttackMoveActivity(self, new Attack(Target.FromActor(targetEnemy), 3))); + self.QueueActivity(new AttackMove.AttackMoveActivity(self, new Attack(Target.FromActor(targetEnemy), WRange.FromCells(3)))); } } diff --git a/OpenRA.Mods.RA/Missions/Survival02Script.cs b/OpenRA.Mods.RA/Missions/Survival02Script.cs index 9036003048..bfb8861f6e 100644 --- a/OpenRA.Mods.RA/Missions/Survival02Script.cs +++ b/OpenRA.Mods.RA/Missions/Survival02Script.cs @@ -139,7 +139,7 @@ namespace OpenRA.Mods.RA.Missions var enemy = FirstUnshroudedOrDefault(enemies.OrderBy(u => (self.CenterLocation - u.CenterLocation).LengthSquared), world, 20); if (enemy != null) - self.QueueActivity(new AttackMove.AttackMoveActivity(self, new Attack(Target.FromActor(enemy), 3))); + self.QueueActivity(new AttackMove.AttackMoveActivity(self, new Attack(Target.FromActor(enemy), WRange.FromCells(3)))); } void SpawnAndAttack(string[] squad, Player owner, CPos location) diff --git a/OpenRA.Mods.RA/Move/Drag.cs b/OpenRA.Mods.RA/Move/Drag.cs index 9379d8c936..307f71b5ee 100755 --- a/OpenRA.Mods.RA/Move/Drag.cs +++ b/OpenRA.Mods.RA/Move/Drag.cs @@ -15,25 +15,25 @@ namespace OpenRA.Mods.RA.Move { public class Drag : Activity { - PPos endLocation; - PPos startLocation; + WPos start, end; int length; int ticks = 0; - public Drag(PPos start, PPos end, int length) + public Drag(WPos start, WPos end, int length) { - startLocation = start; - endLocation = end; + this.start = start; + this.end = end; this.length = length; } public override Activity Tick( Actor self ) { var mobile = self.Trait(); - mobile.PxPosition = length > 1 - ? PPos.Lerp(startLocation, endLocation, ticks, length - 1) - : endLocation; + var pos = length > 1 + ? WPos.Lerp(start, end, ticks, length - 1) + : end; + mobile.PxPosition = PPos.FromWPos(pos); if (++ticks >= length) { mobile.IsMoving = false; @@ -46,7 +46,7 @@ namespace OpenRA.Mods.RA.Move public override IEnumerable GetTargets( Actor self ) { - yield return Target.FromPos(endLocation); + yield return Target.FromPos(end); } // Cannot be cancelled diff --git a/OpenRA.Mods.RA/Move/Mobile.cs b/OpenRA.Mods.RA/Move/Mobile.cs index f380906945..f109367585 100755 --- a/OpenRA.Mods.RA/Move/Mobile.cs +++ b/OpenRA.Mods.RA/Move/Mobile.cs @@ -81,7 +81,7 @@ namespace OpenRA.Mods.RA.Move return passability.ToBits(); } - public readonly Dictionary SubCellOffsets = new Dictionary() + public static readonly Dictionary SubCellOffsets = new Dictionary() { {SubCell.TopLeft, new PVecInt(-7,-6)}, {SubCell.TopRight, new PVecInt(6,-6)}, @@ -170,7 +170,9 @@ namespace OpenRA.Mods.RA.Move public void SetLocation(CPos from, SubCell fromSub, CPos to, SubCell toSub) { - if (fromCell == from && toCell == to) return; + if (fromCell == from && toCell == to && fromSubCell == fromSub && toSubCell == toSub) + return; + RemoveInfluence(); __fromCell = from; __toCell = to; @@ -197,7 +199,7 @@ namespace OpenRA.Mods.RA.Move if (init.Contains()) { this.__fromCell = this.__toCell = init.Get(); - this.PxPosition = Util.CenterOfCell(fromCell) + info.SubCellOffsets[fromSubCell]; + this.PxPosition = Util.CenterOfCell(fromCell) + MobileInfo.SubCellOffsets[fromSubCell]; } this.Facing = init.Contains() ? init.Get() : info.InitialFacing; @@ -207,7 +209,7 @@ namespace OpenRA.Mods.RA.Move public void SetPosition(Actor self, CPos cell) { SetLocation(cell,fromSubCell, cell,fromSubCell); - PxPosition = Util.CenterOfCell(fromCell) + Info.SubCellOffsets[fromSubCell]; + PxPosition = Util.CenterOfCell(fromCell) + MobileInfo.SubCellOffsets[fromSubCell]; FinishedMoving(self); } @@ -232,7 +234,7 @@ namespace OpenRA.Mods.RA.Move if (order is MoveOrderTargeter) { if (Info.OnRails) return null; - return new Order("Move", self, queued) { TargetLocation = target.CenterLocation.ToCPos() }; + return new Order("Move", self, queued) { TargetLocation = target.CenterPosition.ToCPos() }; } return null; } @@ -418,6 +420,11 @@ namespace OpenRA.Mods.RA.Move return (int)(speed / 100); } + public int WorldMovementSpeedForCell(Actor self, CPos cell) + { + return MovementSpeedForCell(self, cell) * 1024 / Game.CellSize; + } + public void AddInfluence() { if (self.IsInWorld) diff --git a/OpenRA.Mods.RA/Move/Move.cs b/OpenRA.Mods.RA/Move/Move.cs index b86f2278cc..5e5cec526b 100755 --- a/OpenRA.Mods.RA/Move/Move.cs +++ b/OpenRA.Mods.RA/Move/Move.cs @@ -58,11 +58,18 @@ namespace OpenRA.Mods.RA.Move this.ignoreBuilding = ignoreBuilding; } + static readonly List NoPath = new List(); public Move(Target target, int range) { - this.getPath = (self,mobile) => self.World.WorldActor.Trait().FindUnitPathToRange( - mobile.toCell, target.CenterLocation.ToCPos(), - range, self); + this.getPath = (self, mobile) => + { + if (!target.IsValid) + return NoPath; + + return self.World.WorldActor.Trait().FindUnitPathToRange( + mobile.toCell, target.CenterPosition.ToCPos(), range, self); + }; + this.destination = null; this.nearEnough = range; } @@ -144,8 +151,8 @@ namespace OpenRA.Mods.RA.Move mobile.SetLocation( mobile.fromCell, mobile.fromSubCell, nextCell.Value.First, nextCell.Value.Second ); var move = new MoveFirstHalf( this, - Util.CenterOfCell( mobile.fromCell ) + mobile.Info.SubCellOffsets[mobile.fromSubCell], - Util.BetweenCells( mobile.fromCell, mobile.toCell ) + (mobile.Info.SubCellOffsets[mobile.fromSubCell] + mobile.Info.SubCellOffsets[mobile.toSubCell] ) / 2, + Util.CenterOfCell( mobile.fromCell ) + MobileInfo.SubCellOffsets[mobile.fromSubCell], + Util.BetweenCells( mobile.fromCell, mobile.toCell ) + (MobileInfo.SubCellOffsets[mobile.fromSubCell] + MobileInfo.SubCellOffsets[mobile.toSubCell] ) / 2, mobile.Facing, mobile.Facing, 0 ); @@ -335,15 +342,15 @@ namespace OpenRA.Mods.RA.Move protected override MovePart OnComplete( Actor self, Mobile mobile, Move parent ) { - var fromSubcellOffset = mobile.Info.SubCellOffsets[mobile.fromSubCell]; - var toSubcellOffset = mobile.Info.SubCellOffsets[mobile.toSubCell]; + var fromSubcellOffset = MobileInfo.SubCellOffsets[mobile.fromSubCell]; + var toSubcellOffset = MobileInfo.SubCellOffsets[mobile.toSubCell]; var nextCell = parent.PopPath( self, mobile ); if( nextCell != null ) { if(IsTurn(mobile, nextCell.Value.First)) { - var nextSubcellOffset = mobile.Info.SubCellOffsets[nextCell.Value.Second]; + var nextSubcellOffset = MobileInfo.SubCellOffsets[nextCell.Value.Second]; var ret = new MoveFirstHalf( move, Util.BetweenCells( mobile.fromCell, mobile.toCell ) + (fromSubcellOffset + toSubcellOffset) / 2, diff --git a/OpenRA.Mods.RA/Orders/PlaceBuildingOrderGenerator.cs b/OpenRA.Mods.RA/Orders/PlaceBuildingOrderGenerator.cs index b561ca2dfd..602c48b42e 100755 --- a/OpenRA.Mods.RA/Orders/PlaceBuildingOrderGenerator.cs +++ b/OpenRA.Mods.RA/Orders/PlaceBuildingOrderGenerator.cs @@ -98,7 +98,7 @@ namespace OpenRA.Mods.RA.Orders initialized = true; } - var offset = (topLeft - CPos.Zero).ToWVec() + FootprintUtils.CenterOffset(BuildingInfo); + var offset = topLeft.CenterPosition + FootprintUtils.CenterOffset(BuildingInfo) - WPos.Zero; foreach (var r in preview) r.WithPos(r.Pos + offset).Render(wr); diff --git a/OpenRA.Mods.RA/Player/BaseAttackNotifier.cs b/OpenRA.Mods.RA/Player/BaseAttackNotifier.cs index a5495f6277..8ce7d9e876 100644 --- a/OpenRA.Mods.RA/Player/BaseAttackNotifier.cs +++ b/OpenRA.Mods.RA/Player/BaseAttackNotifier.cs @@ -44,7 +44,7 @@ namespace OpenRA.Mods.RA if (self.World.FrameNumber - lastAttackTime > info.NotifyInterval * 25) Sound.PlayNotification(self.Owner, "Speech", "BaseAttack", self.Owner.Country.Race); - lastAttackLocation = self.CenterLocation.ToCPos(); + lastAttackLocation = self.CenterPosition.ToCPos(); lastAttackTime = self.World.FrameNumber; } } diff --git a/OpenRA.Mods.RA/Player/HarvesterAttackNotifier.cs b/OpenRA.Mods.RA/Player/HarvesterAttackNotifier.cs index ac4e76df59..f003381ad1 100644 --- a/OpenRA.Mods.RA/Player/HarvesterAttackNotifier.cs +++ b/OpenRA.Mods.RA/Player/HarvesterAttackNotifier.cs @@ -44,7 +44,7 @@ namespace OpenRA.Mods.RA if (self.World.FrameNumber - lastAttackTime > info.NotifyInterval * 25) Sound.PlayNotification(self.Owner, "Speech", "HarvesterAttack", self.Owner.Country.Race); - lastAttackLocation = self.CenterLocation.ToCPos(); + lastAttackLocation = self.CenterPosition.ToCPos(); lastAttackTime = self.World.FrameNumber; } } diff --git a/OpenRA.Mods.RA/Player/PlaceBuilding.cs b/OpenRA.Mods.RA/Player/PlaceBuilding.cs index 607b8f647f..cfa783579b 100755 --- a/OpenRA.Mods.RA/Player/PlaceBuilding.cs +++ b/OpenRA.Mods.RA/Player/PlaceBuilding.cs @@ -83,18 +83,11 @@ namespace OpenRA.Mods.RA if (buildingInfo.RequiresBaseProvider) { - var center = buildingInfo.CenterLocation(order.TargetLocation); - foreach (var bp in w.ActorsWithTrait()) - { - if (bp.Actor.Owner.Stances[self.Owner] != Stance.Ally || !bp.Trait.Ready()) - continue; - - if (Combat.IsInRange(center, bp.Trait.Info.Range, bp.Actor.CenterLocation)) - { - bp.Trait.BeginCooldown(); - break; - } - } + // May be null if the build anywhere cheat is active + // BuildingInfo.IsCloseEnoughToBase has already verified that this is a valid build location + var producer = buildingInfo.FindBaseProvider(w, self.Owner, order.TargetLocation); + if (producer != null) + producer.Trait().BeginCooldown(); } if (GetNumBuildables(self.Owner) > prevItems) diff --git a/OpenRA.Mods.RA/Production.cs b/OpenRA.Mods.RA/Production.cs index ffdbcf8fdb..d8718b08f9 100755 --- a/OpenRA.Mods.RA/Production.cs +++ b/OpenRA.Mods.RA/Production.cs @@ -50,8 +50,8 @@ namespace OpenRA.Mods.RA public void DoProduction(Actor self, ActorInfo producee, ExitInfo exitinfo) { var exit = self.Location + exitinfo.ExitCellVector; - var spawn = self.Trait().PxPosition + exitinfo.SpawnOffsetVector; - var to = Util.CenterOfCell(exit); + var spawn = (self.Trait().PxPosition + exitinfo.SpawnOffsetVector).ToWPos(0); + var to = exit.CenterPosition; var fi = producee.Traits.Get(); var initialFacing = exitinfo.Facing < 0 ? Util.GetFacing(to - spawn, fi.GetInitialFacing()) : exitinfo.Facing; @@ -66,7 +66,7 @@ namespace OpenRA.Mods.RA // TODO: Move this into an *Init // TODO: We should be adjusting the actual position for aircraft, not just visuals. var teleportable = newUnit.Trait(); - teleportable.AdjustPxPosition(newUnit, spawn); + teleportable.AdjustPxPosition(newUnit, PPos.FromWPos(spawn)); // TODO: Generalize this for non-mobile (e.g. aircraft) too // Remember to update the Enter activity too @@ -74,7 +74,7 @@ namespace OpenRA.Mods.RA if (mobile != null) { // Animate the spawn -> exit transition - var speed = mobile.MovementSpeedForCell(newUnit, exit); + var speed = mobile.WorldMovementSpeedForCell(newUnit, exit); var length = speed > 0 ? (int)((to - spawn).Length * 3 / speed) : 0; newUnit.QueueActivity(new Drag(spawn, to, length)); } diff --git a/OpenRA.Mods.RA/RallyPoint.cs b/OpenRA.Mods.RA/RallyPoint.cs index 36a0ff52a3..1db32f3e2b 100755 --- a/OpenRA.Mods.RA/RallyPoint.cs +++ b/OpenRA.Mods.RA/RallyPoint.cs @@ -42,7 +42,7 @@ namespace OpenRA.Mods.RA public Order IssueOrder( Actor self, IOrderTargeter order, Target target, bool queued ) { if( order.OrderID == "SetRallyPoint" ) - return new Order(order.OrderID, self, false) { TargetLocation = target.CenterLocation.ToCPos() }; + return new Order(order.OrderID, self, false) { TargetLocation = target.CenterPosition.ToCPos() }; return null; } diff --git a/OpenRA.Mods.RA/Render/RenderBuildingWarFactory.cs b/OpenRA.Mods.RA/Render/RenderBuildingWarFactory.cs index a766115f47..23e55b5cde 100755 --- a/OpenRA.Mods.RA/Render/RenderBuildingWarFactory.cs +++ b/OpenRA.Mods.RA/Render/RenderBuildingWarFactory.cs @@ -49,10 +49,12 @@ namespace OpenRA.Mods.RA.Render : base(init, info) { roof = new Animation(GetImage(init.self)); - var bi = init.self.Info.Traits.Get(); + + // Additional 512 units move from center -> top of cell + var offset = FootprintUtils.CenterOffset(bi).Y + 512; anims.Add("roof", new AnimationWithOffset(roof, null, - () => !buildComplete, FootprintUtils.CenterOffset(bi).Y)); + () => !buildComplete, offset)); } public void BuildingComplete( Actor self ) diff --git a/OpenRA.Mods.RA/Render/WithBuildingExplosion.cs b/OpenRA.Mods.RA/Render/WithBuildingExplosion.cs index 0a2eb2a6a9..2ca9291cac 100755 --- a/OpenRA.Mods.RA/Render/WithBuildingExplosion.cs +++ b/OpenRA.Mods.RA/Render/WithBuildingExplosion.cs @@ -22,7 +22,7 @@ namespace OpenRA.Mods.RA.Render var bi = self.Info.Traits.Get(); FootprintUtils.UnpathableTiles(self.Info.Name, bi, self.Location).Do( t => self.World.AddFrameEndTask( - w => w.Add(new Explosion(w, Traits.Util.CenterOfCell(t), "building", false, 0)))); + w => w.Add(new Explosion(w, t.CenterPosition, "building")))); } } } diff --git a/OpenRA.Mods.RA/RenderRangeCircle.cs b/OpenRA.Mods.RA/RenderRangeCircle.cs index e0ca291fe7..c931a1627c 100644 --- a/OpenRA.Mods.RA/RenderRangeCircle.cs +++ b/OpenRA.Mods.RA/RenderRangeCircle.cs @@ -47,9 +47,11 @@ namespace OpenRA.Mods.RA if (self.Owner != self.World.LocalPlayer) return; + // Hack: Convert world coords to cells + var pxRange = self.Trait().GetMaximumRange().Range / 1024f; wr.DrawRangeCircleWithContrast( Color.FromArgb(128, Color.Yellow), - self.CenterLocation.ToFloat2(), self.Trait().GetMaximumRange(), + self.CenterLocation.ToFloat2(), pxRange, Color.FromArgb(96, Color.Black), 1); } diff --git a/OpenRA.Mods.RA/Repairable.cs b/OpenRA.Mods.RA/Repairable.cs index 9137a95360..35b1e8bc9c 100644 --- a/OpenRA.Mods.RA/Repairable.cs +++ b/OpenRA.Mods.RA/Repairable.cs @@ -78,7 +78,7 @@ namespace OpenRA.Mods.RA self.CancelActivity(); self.QueueActivity(new MoveAdjacentTo(target)); - self.QueueActivity(mobile.MoveTo(order.TargetActor.CenterLocation.ToCPos(), order.TargetActor)); + self.QueueActivity(mobile.MoveTo(order.TargetActor.CenterPosition.ToCPos(), order.TargetActor)); self.QueueActivity(new Rearm(self)); self.QueueActivity(new Repair(order.TargetActor)); diff --git a/OpenRA.Mods.RA/SmokeTrailWhenDamaged.cs b/OpenRA.Mods.RA/SmokeTrailWhenDamaged.cs index eabdfee99d..4678af3399 100644 --- a/OpenRA.Mods.RA/SmokeTrailWhenDamaged.cs +++ b/OpenRA.Mods.RA/SmokeTrailWhenDamaged.cs @@ -43,7 +43,7 @@ namespace OpenRA.Mods.RA { var position = self.CenterPosition; if (position.Z > 0 && self.GetDamageState() >= info.MinDamage && - !self.World.FogObscures(new CPos(position))) + !self.World.FogObscures(position.ToCPos())) { var offset = info.Offset.Rotate(body.QuantizeOrientation(self, self.Orientation)); var pos = position + body.LocalToWorld(offset); diff --git a/OpenRA.Mods.RA/SupportPowers/SpyPlanePower.cs b/OpenRA.Mods.RA/SupportPowers/SpyPlanePower.cs index 3320fc1d2c..648f1a571a 100755 --- a/OpenRA.Mods.RA/SupportPowers/SpyPlanePower.cs +++ b/OpenRA.Mods.RA/SupportPowers/SpyPlanePower.cs @@ -33,7 +33,7 @@ namespace OpenRA.Mods.RA { new LocationInit( enterCell ), new OwnerInit( self.Owner ), - new FacingInit( Util.GetFacing((order.TargetLocation - enterCell).ToInt2(), 0) ), + new FacingInit( Util.GetFacing(order.TargetLocation - enterCell, 0) ), new AltitudeInit( Rules.Info["u2"].Traits.Get().CruiseAltitude ), }); diff --git a/OpenRA.Mods.RA/TargetableUnit.cs b/OpenRA.Mods.RA/TargetableUnit.cs index 37da70da40..2cda0fa15a 100755 --- a/OpenRA.Mods.RA/TargetableUnit.cs +++ b/OpenRA.Mods.RA/TargetableUnit.cs @@ -54,7 +54,7 @@ namespace OpenRA.Mods.RA public virtual IEnumerable TargetableCells( Actor self ) { - yield return self.CenterLocation.ToCPos(); + yield return self.CenterPosition.ToCPos(); } } } diff --git a/OpenRA.Mods.RA/ThrowsParticle.cs b/OpenRA.Mods.RA/ThrowsParticle.cs index 076000e37f..7f7360d264 100644 --- a/OpenRA.Mods.RA/ThrowsParticle.cs +++ b/OpenRA.Mods.RA/ThrowsParticle.cs @@ -22,14 +22,20 @@ namespace OpenRA.Mods.RA [Desc("Initial position relative to body")] public readonly WVec Offset = WVec.Zero; + [Desc("Minimum distance to throw the particle")] + public readonly WRange MinThrowRange = new WRange(256); + [Desc("Maximum distance to throw the particle")] - public readonly WRange ThrowRange = new WRange(768); + public readonly WRange MaxThrowRange = new WRange(768); - [Desc("Maximum height to throw the particle")] - public readonly WRange ThrowHeight = new WRange(256); + [Desc("Minimum angle to throw the particle")] + public readonly WAngle MinThrowAngle = WAngle.FromDegrees(30); - [Desc("Number of ticks to animate")] - public readonly int Length = 15; + [Desc("Maximum angle to throw the particle")] + public readonly WAngle MaxThrowAngle = WAngle.FromDegrees(60); + + [Desc("Speed to throw the particle (horizontal WPos/tick)")] + public readonly int Velocity = 75; [Desc("Maximum rotation rate")] public readonly float ROT = 15; @@ -39,19 +45,19 @@ namespace OpenRA.Mods.RA class ThrowsParticle : ITick { - ThrowsParticleInfo info; WVec pos; WVec initialPos; WVec finalPos; + WAngle angle; + int tick = 0; + int length; float facing; float rotation; public ThrowsParticle(ActorInitializer init, ThrowsParticleInfo info) { - this.info = info; - var self = init.self; var rs = self.Trait(); var body = self.Trait(); @@ -62,10 +68,12 @@ namespace OpenRA.Mods.RA // Calculate final position var throwRotation = WRot.FromFacing(Game.CosmeticRandom.Next(1024)); - var throwOffset = new WVec((int)(Game.CosmeticRandom.Gauss1D(1)*info.ThrowRange.Range), 0, 0).Rotate(throwRotation); + var throwDistance = Game.CosmeticRandom.Next(info.MinThrowRange.Range, info.MaxThrowRange.Range); initialPos = pos = info.Offset.Rotate(body.QuantizeOrientation(self, WRot.FromFacing(bodyFacing))); - finalPos = initialPos + throwOffset; + finalPos = initialPos + new WVec(throwDistance, 0, 0).Rotate(throwRotation); + angle = new WAngle(Game.CosmeticRandom.Next(info.MinThrowAngle.Angle, info.MaxThrowAngle.Angle)); + length = (finalPos - initialPos).Length / info.Velocity; // Facing rotation rotation = Game.CosmeticRandom.Gauss1D(2) * info.ROT; @@ -77,15 +85,10 @@ namespace OpenRA.Mods.RA public void Tick(Actor self) { - if (tick == info.Length) + if (tick == length) return; - tick++; - // Lerp position horizontally and height along a sinusoid using a cubic ease - var t = (tick*tick*tick / (info.Length*info.Length) - 3*tick*tick / info.Length + 3*tick); - var tp = WVec.Lerp(initialPos, finalPos, t, info.Length); - var th = new WAngle(512*(info.Length - t) / info.Length).Sin()*info.ThrowHeight.Range / 1024; - pos = new WVec(tp.X, tp.Y, th); + pos = WVec.LerpQuadratic(initialPos, finalPos, angle, tick++, length); // Spin the particle facing += rotation; diff --git a/OpenRA.Mods.RA/Turreted.cs b/OpenRA.Mods.RA/Turreted.cs index d863bf90af..68dc809d55 100755 --- a/OpenRA.Mods.RA/Turreted.cs +++ b/OpenRA.Mods.RA/Turreted.cs @@ -71,7 +71,7 @@ namespace OpenRA.Mods.RA public bool FaceTarget(Actor self, Target target) { - desiredFacing = Util.GetFacing(target.CenterLocation - self.CenterLocation, turretFacing); + desiredFacing = Util.GetFacing(target.CenterPosition - self.CenterPosition, turretFacing); return turretFacing == desiredFacing; } diff --git a/mods/cnc/sequences/misc.yaml b/mods/cnc/sequences/misc.yaml index 8556630c36..9f892d6db3 100644 --- a/mods/cnc/sequences/misc.yaml +++ b/mods/cnc/sequences/misc.yaml @@ -223,6 +223,7 @@ ionsfx: idle: Start: 0 Length: * + Offset: 0, -78 bomblet: idle: