diff --git a/OpenRa.Game/Actor.cs b/OpenRa.Game/Actor.cs index 6e5f8987bc..bf32437eaf 100755 --- a/OpenRa.Game/Actor.cs +++ b/OpenRa.Game/Actor.cs @@ -50,7 +50,6 @@ namespace OpenRa.Game public void Tick() { - while (currentActivity != null) { var a = currentActivity; @@ -93,11 +92,8 @@ namespace OpenRa.Game if (!Rules.Map.IsInMap(xy.X, xy.Y)) return null; - // HACK: Get the first unit in the cell - // This will need to be updated for multiple-infantry-in-a-cell - // HACK: this doesn't work for targeting air units either - var underCursor = Game.UnitInfluence.GetUnitsAt( xy ).FirstOrDefault() - ?? Game.BuildingInfluence.GetBuildingAt( xy ); + var loc = mi.Location + Game.viewport.Location; + var underCursor = Game.FindUnits(loc, loc).FirstOrDefault(); if (underCursor != null && !underCursor.Info.Selectable) underCursor = null; @@ -107,19 +103,18 @@ namespace OpenRa.Game .FirstOrDefault( x => x != null ); } - public RectangleF Bounds + public RectangleF GetBounds(bool useAltitude) { - get + var size = SelectedSize; + var loc = CenterLocation - 0.5f * size; + + if (useAltitude) { - var size = SelectedSize; - var loc = CenterLocation - 0.5f * size; var unit = traits.GetOrDefault(); - - if (unit != null) - loc -= new float2(0, unit.Altitude); - - return new RectangleF(loc.X, loc.Y, size.X, size.Y); + if (unit != null) loc -= new float2(0, unit.Altitude); } + + return new RectangleF(loc.X, loc.Y, size.X, size.Y); } public bool IsDead { get { return Health <= 0; } } @@ -150,6 +145,8 @@ namespace OpenRa.Game Game.world.AddFrameEndTask(w => w.Remove(this)); } + if (Health > Info.Strength) + Health = Info.Strength; var newState = GetDamageState(); diff --git a/OpenRa.Game/Combat.cs b/OpenRa.Game/Combat.cs index 9d42af2457..87e30467e2 100644 --- a/OpenRa.Game/Combat.cs +++ b/OpenRa.Game/Combat.cs @@ -39,7 +39,7 @@ namespace OpenRa.Game static float GetMaximumSpread(WeaponInfo weapon, WarheadInfo warhead) { - return (int)(warhead.Spread * Math.Log(weapon.Damage, 2)); + return (int)(warhead.Spread * Math.Log(Math.Abs(weapon.Damage), 2)); } static float GetDamageToInflict(Actor target, int2 loc, WeaponInfo weapon, WarheadInfo warhead) diff --git a/OpenRa.Game/Controller.cs b/OpenRa.Game/Controller.cs index a1c0dc38a8..c51f7f6499 100644 --- a/OpenRa.Game/Controller.cs +++ b/OpenRa.Game/Controller.cs @@ -124,7 +124,7 @@ namespace OpenRa.Game { var mods = GetModifierKeys(); var c = (orderGenerator is UnitOrderGenerator) ? orderGenerator.Order(dragEnd.ToInt2(), - new MouseInput { Button = MouseButton.Right, Modifiers = mods }) + new MouseInput { Location = (Game.CellSize * dragEnd - Game.viewport.Location).ToInt2(), Button = MouseButton.Right, Modifiers = mods }) .Where(o => o.Validate()) .Select(o => CursorForOrderString(o.OrderString, o.Subject, o.TargetLocation)) .FirstOrDefault(a => a != null) : null; diff --git a/OpenRa.Game/Game.cs b/OpenRa.Game/Game.cs index fc88c0922a..7be0cad8fd 100644 --- a/OpenRa.Game/Game.cs +++ b/OpenRa.Game/Game.cs @@ -269,13 +269,19 @@ namespace OpenRa.Game var rect = new RectangleF(min.X, min.Y, max.X - min.X, max.Y - min.Y); return world.Actors - .Where(x => x.Bounds.IntersectsWith(rect)); + .Where(x => x.GetBounds(true).IntersectsWith(rect)); } public static IEnumerable FindUnitsInCircle(float2 a, float r) { - return FindUnits(a - new float2(r, r), a + new float2(r, r)) - .Where(x => (x.CenterLocation - a).LengthSquared < r * r); + var min = a - new float2(r, r); + var max = a + new float2(r, r); + + var rect = new RectangleF(min.X, min.Y, max.X - min.X, max.Y - min.Y); + + var inBox = world.Actors.Where(x => x.GetBounds(false).IntersectsWith(rect)); + + return inBox.Where(x => (x.CenterLocation - a).LengthSquared < r * r); } public static IEnumerable FindTilesInCircle(int2 a, int r) diff --git a/OpenRa.Game/Graphics/WorldRenderer.cs b/OpenRa.Game/Graphics/WorldRenderer.cs index e155bbf7c4..65032fcacb 100644 --- a/OpenRa.Game/Graphics/WorldRenderer.cs +++ b/OpenRa.Game/Graphics/WorldRenderer.cs @@ -111,7 +111,7 @@ namespace OpenRa.Game.Graphics public void DrawSelectionBox(Actor selectedUnit, Color c, bool drawHealthBar) { - var bounds = selectedUnit.Bounds; + var bounds = selectedUnit.GetBounds(true); var xy = new float2(bounds.Left, bounds.Top); var Xy = new float2(bounds.Right, bounds.Top); diff --git a/OpenRa.Game/OpenRa.Game.csproj b/OpenRa.Game/OpenRa.Game.csproj index 0c8a467e2c..8b01dc9cc2 100644 --- a/OpenRa.Game/OpenRa.Game.csproj +++ b/OpenRa.Game/OpenRa.Game.csproj @@ -172,6 +172,7 @@ + diff --git a/OpenRa.Game/Traits/AutoHeal.cs b/OpenRa.Game/Traits/AutoHeal.cs new file mode 100644 index 0000000000..5eb31d11ea --- /dev/null +++ b/OpenRa.Game/Traits/AutoHeal.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OpenRa.Game.Traits +{ + class AutoHeal : ITick + { + public AutoHeal(Actor self) { } + + void AttackTarget(Actor self, Actor target) + { + var attack = self.traits.WithInterface().First(); + if (target != null) + attack.ResolveOrder(self, new Order("Attack", self, target, int2.Zero, null)); + else + self.CancelActivity(); + } + + float GetMaximumRange(Actor self) + { + return new[] { self.Info.Primary, self.Info.Secondary } + .Where(w => w != null) + .Max(w => Rules.WeaponInfo[w].Range); + } + + bool NeedsNewTarget(Actor self) + { + var attack = self.traits.WithInterface().First(); + var range = GetMaximumRange(self); + + if (attack.target == null) + return true; // he's dead. + if ((attack.target.Location - self.Location).LengthSquared > range * range + 2) + return true; // wandered off faster than we could follow + if (attack.target.Health == attack.target.Info.Strength) + return true; // fully healed + + return false; + } + + public void Tick(Actor self) + { + var attack = self.traits.WithInterface().First(); + var range = GetMaximumRange(self); + + if (NeedsNewTarget(self)) + AttackTarget(self, ChooseTarget(self, range)); + } + + Actor ChooseTarget(Actor self, float range) + { + var inRange = Game.FindUnitsInCircle(self.CenterLocation, Game.CellSize * range); + + return inRange + .Where(a => a.Owner == self.Owner) /* todo: one day deal with friendly players */ + .Where(a => Combat.HasAnyValidWeapons(self, a)) + .Where(a => a.Health < a.Info.Strength) + .OrderBy(a => (a.Location - self.Location).LengthSquared) + .FirstOrDefault(); + } + } +} diff --git a/OpenRa.Game/Traits/AutoTarget.cs b/OpenRa.Game/Traits/AutoTarget.cs index 77ad306377..2f84497eb5 100644 --- a/OpenRa.Game/Traits/AutoTarget.cs +++ b/OpenRa.Game/Traits/AutoTarget.cs @@ -16,16 +16,23 @@ namespace OpenRa.Game.Traits attack.ResolveOrder(self, new Order("Attack", self, target, int2.Zero, null)); } + float GetMaximumRange(Actor self) + { + return new[] { self.Info.Primary, self.Info.Secondary } + .Where(w => w != null) + .Max(w => Rules.WeaponInfo[w].Range); + } + public void Tick(Actor self) { if (!self.IsIdle) return; var attack = self.traits.WithInterface().First(); - var range = Rules.WeaponInfo[self.Info.Primary].Range; + var range = GetMaximumRange(self); if (attack.target == null || (attack.target.Location - self.Location).LengthSquared > range * range + 2) - attack.target = ChooseTarget(self, range); + AttackTarget(self, ChooseTarget(self, range)); } Actor ChooseTarget(Actor self, float range) diff --git a/OpenRa.Game/Traits/RenderBuildingWarFactory.cs b/OpenRa.Game/Traits/RenderBuildingWarFactory.cs index 40ccaf25e9..3b34f837d7 100644 --- a/OpenRa.Game/Traits/RenderBuildingWarFactory.cs +++ b/OpenRa.Game/Traits/RenderBuildingWarFactory.cs @@ -35,7 +35,7 @@ namespace OpenRa.Game.Traits { if (doneBuilding) roof.Tick(); - var b = self.Bounds; + var b = self.GetBounds(false); if (isOpen && !Game.UnitInfluence.GetUnitsAt(((1/24f) * self.CenterLocation).ToInt2()).Any()) { isOpen = false; diff --git a/doc/progress.txt b/doc/progress.txt index be4e61b168..2159ddbf1d 100644 --- a/doc/progress.txt +++ b/doc/progress.txt @@ -4,11 +4,11 @@ Ground Units: All Infantry No idle animations E1 Range is wrong. E2 Grenades are too accurate. -E3 AA weapon doesn't work. +E3 Works E4 Flamer origin is wrong E6 Capture action missing, repair action missing E7 C4 action missing -MEDI Heal doesn't work +MEDI Heal targeting is wrong SPY Infiltrate action missing THF Steal action missing C1,C2,Einstein,Kosygin Not implemented @@ -33,7 +33,6 @@ ARTY Works Helicopters - Weapons don't work, - hover while attacking doesn't work, - - render is broken (altitude) -- less broken, but there are Z-order issues. - Repair/rearm doesn't work TRAN Cargo doesn't work diff --git a/rules.ini b/rules.ini index ce3e93ff4e..80f49d6f47 100644 --- a/rules.ini +++ b/rules.ini @@ -2411,7 +2411,7 @@ InfDeath=5 ; special case to only affect infantry (do not use for regular weapons) [Organic] -Spread=0 +Spread=5 Verses=100%,0%,0%,0%,0% InfDeath=0 diff --git a/session.ini b/session.ini index ac2d39b77d..e78029b52a 100644 --- a/session.ini +++ b/session.ini @@ -7,4 +7,5 @@ s0=Multi0,mcv,600,2841,0,Guard,None s1=Multi2,mcv,600,12445,0,Guard,None s2=Multi1,mcv,600,12505,0,Guard,None +;s2=Multi1,e3,600,12505,0,Guard,None s3=Multi3,mcv,600,2910,0,Guard,None \ No newline at end of file diff --git a/units.ini b/units.ini index d13d1df087..9e9cd42ebe 100755 --- a/units.ini +++ b/units.ini @@ -249,7 +249,7 @@ InitialFacing=224 LongDesc=Anti-Air base defense.\n Strong vs Aircraft\n Weak vs Infantry, Tanks [FTUR] Description=Flame Turret -Traits=Building, RenderBuilding, AttackTurreted, AutoTarget +Traits=Turreted, Building, RenderBuilding, AttackTurreted, AutoTarget Dimensions=1,1 Footprint=x SelectionPriority=3 @@ -578,7 +578,7 @@ LongDesc=Elite commando infantry, armed with \ndual pistols and C4.\n Strong vs [MEDI] Description=Medic Voice=MedicVoice -Traits=Unit, Mobile, RenderInfantry, AttackBase, TakeCover, Infantry, AutoTarget +Traits=Unit, Mobile, RenderInfantry, AttackBase, TakeCover, Infantry, AutoHeal LongDesc=Heals nearby infantry.\n Strong vs Nothing\n Weak vs Everything