From 4667679f12f93511666e93b391e958091b50225b Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Tue, 22 Dec 2009 22:37:11 -0800 Subject: [PATCH] Intermediate crushable-behavior checkin; Muliple units per cell in UIM; Crushable TraitInterface; Crushable units are taken into account in pathfinding; Crashes when trying to crush a unit --- OpenRa.Game/Actor.cs | 6 ++- OpenRa.Game/Game.cs | 24 +++++++++++- OpenRa.Game/OpenRa.Game.csproj | 1 + OpenRa.Game/PathFinder.cs | 21 ++--------- OpenRa.Game/PathSearch.cs | 13 ++++--- OpenRa.Game/Traits/Activities/Move.cs | 8 ++-- OpenRa.Game/Traits/Infantry.cs | 34 +++++++++++++++++ OpenRa.Game/Traits/Mobile.cs | 37 ++++++++++++++++++- OpenRa.Game/Traits/Production.cs | 2 +- .../Traits/RenderBuildingWarFactory.cs | 3 +- OpenRa.Game/Traits/TraitsInterfaces.cs | 8 ++++ OpenRa.Game/UiOverlay.cs | 4 +- OpenRa.Game/UnitInfluenceMap.cs | 28 +++++++++----- 13 files changed, 146 insertions(+), 43 deletions(-) create mode 100644 OpenRa.Game/Traits/Infantry.cs diff --git a/OpenRa.Game/Actor.cs b/OpenRa.Game/Actor.cs index ff765c9edb..e91f977190 100755 --- a/OpenRa.Game/Actor.cs +++ b/OpenRa.Game/Actor.cs @@ -85,8 +85,10 @@ namespace OpenRa.Game if (!Rules.Map.IsInMap(xy.X, xy.Y)) return null; - - var underCursor = Game.UnitInfluence.GetUnitAt( xy ) + + // HACK: Get the first unit in the cell + // This will need to be updated for multiple-infantry-in-a-cell + var underCursor = Game.UnitInfluence.GetUnitsAt( xy ).FirstOrDefault() ?? Game.BuildingInfluence.GetBuildingAt( xy ); if (underCursor != null && !underCursor.Info.Selectable) diff --git a/OpenRa.Game/Game.cs b/OpenRa.Game/Game.cs index 6fa6741559..8a18bdeedf 100644 --- a/OpenRa.Game/Game.cs +++ b/OpenRa.Game/Game.cs @@ -210,13 +210,35 @@ namespace OpenRa.Game public static bool IsCellBuildable(int2 a, UnitMovementType umt, Actor toIgnore) { if (BuildingInfluence.GetBuildingAt(a) != null) return false; - if (UnitInfluence.GetUnitAt(a) != null && UnitInfluence.GetUnitAt(a) != toIgnore) return false; + if (UnitInfluence.GetUnitsAt(a).Any(b => b != toIgnore)) return false; return Rules.Map.IsInMap(a.X, a.Y) && TerrainCosts.Cost(umt, Rules.TileSet.GetWalkability(Rules.Map.MapTiles[a.X, a.Y])) < double.PositiveInfinity; } + public static bool IsActorCrushableByActor(Actor a, Actor b) + { + return IsActorCrushableByMovementType(a, b.traits.WithInterface().FirstOrDefault().GetMovementType()); + } + public static bool IsActorCrushableByMovementType(Actor a, UnitMovementType umt) + { + if (a != null) + { + foreach (var crush in a.traits.WithInterface()) + { + if (((crush.IsCrushableByEnemy() && a.Owner != Game.LocalPlayer) || (crush.IsCrushableByFriend() && a.Owner == Game.LocalPlayer)) + && crush.CrushableBy().Contains(umt)) + { + Log.Write("{0} is crushable by MovementType {1}", a.Info.Name, umt); + return true; + } + } + Log.Write("{0} is NOT crushable by MovementType {1}", a.Info.Name, umt); + } + return false; + } + public static bool IsWater(int2 a) { return Rules.Map.IsInMap(a.X, a.Y) && diff --git a/OpenRa.Game/OpenRa.Game.csproj b/OpenRa.Game/OpenRa.Game.csproj index ca5f68d865..9dfa180837 100644 --- a/OpenRa.Game/OpenRa.Game.csproj +++ b/OpenRa.Game/OpenRa.Game.csproj @@ -173,6 +173,7 @@ + diff --git a/OpenRa.Game/PathFinder.cs b/OpenRa.Game/PathFinder.cs index 2a2717c51c..210613a1e6 100644 --- a/OpenRa.Game/PathFinder.cs +++ b/OpenRa.Game/PathFinder.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using OpenRa.FileFormats; using OpenRa.Game.Support; +using OpenRa.Game.Traits; namespace OpenRa.Game { @@ -22,20 +23,6 @@ namespace OpenRa.Game : float.PositiveInfinity; } - bool IsBlocked(int2 from, UnitMovementType umt) - { - for (int v = -1; v < 2; v++) - for (int u = -1; u < 2; u++) - if (u != 0 || v != 0) - { - var p = from + new int2(u, v); - if (passableCost[(int)umt][from.X + u, from.Y + v] < float.PositiveInfinity) - if (Game.BuildingInfluence.CanMoveHere(p) && (Game.UnitInfluence.GetUnitAt(p) == null)) - return false; - } - return true; - } - public List FindUnitPath( int2 from, int2 target, UnitMovementType umt ) { using (new PerfSample("find_unit_path")) @@ -61,13 +48,13 @@ namespace OpenRa.Game return path; } } - + Func AvoidUnitsNear(int2 p, int dist) { return q => p != q && - ((p - q).LengthSquared < dist * dist) && - (Game.UnitInfluence.GetUnitAt(q) != null); + ((p - q).LengthSquared < dist * dist) && + (Game.UnitInfluence.GetUnitsAt(q).Any()); } public List FindPath( PathSearch search ) diff --git a/OpenRa.Game/PathSearch.cs b/OpenRa.Game/PathSearch.cs index cb2bc4dcd2..0290d34056 100755 --- a/OpenRa.Game/PathSearch.cs +++ b/OpenRa.Game/PathSearch.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using IjwFramework.Collections; using OpenRa.Game.Graphics; @@ -35,7 +36,7 @@ namespace OpenRa.Game if (!ignoreTerrain) if (passableCost[(int)umt][p.Location.X, p.Location.Y] == float.PositiveInfinity) return p.Location; - + foreach( int2 d in Util.directions ) { int2 newHere = p.Location + d; @@ -53,13 +54,15 @@ namespace OpenRa.Game if (Rules.Map.IsOverlaySolid(newHere)) continue; } - - if( checkForBlocked && Game.UnitInfluence.GetUnitAt( newHere ) != null ) + + // Replicate real-ra behavior of not being able to enter a cell if there is a mixture of crushable and uncrushable units + if (checkForBlocked && (Game.UnitInfluence.GetUnitsAt(newHere).Any(a => !Game.IsActorCrushableByMovementType(a, umt)))) continue; + if (customBlock != null && customBlock(newHere)) continue; - + var est = heuristic( newHere ); if( est == float.PositiveInfinity ) continue; @@ -75,6 +78,7 @@ namespace OpenRa.Game cellInfo[ newHere.X, newHere.Y ].MinCost = newCost; queue.Add( new PathDistance( newCost + est, newHere ) ); + } return p.Location; } @@ -96,7 +100,6 @@ namespace OpenRa.Game checkForBlocked = checkForBlocked }; search.AddInitialCell( from ); - return search; } diff --git a/OpenRa.Game/Traits/Activities/Move.cs b/OpenRa.Game/Traits/Activities/Move.cs index ef078d05fb..9a655495a1 100755 --- a/OpenRa.Game/Traits/Activities/Move.cs +++ b/OpenRa.Game/Traits/Activities/Move.cs @@ -45,8 +45,10 @@ namespace OpenRa.Game.Traits.Activities static bool CanEnterCell( int2 c, Actor self ) { if (!Game.BuildingInfluence.CanMoveHere(c)) return false; - var u = Game.UnitInfluence.GetUnitAt( c ); - return (u == null || u == self); + + // Cannot enter a cell if any unit inside is uncrushable + // This will need to be updated for multiple-infantry-in-a-cell + return (!Game.UnitInfluence.GetUnitsAt(c).Any(a => a != self && !Game.IsActorCrushableByActor(a, self))); } public IActivity Tick( Actor self ) @@ -68,7 +70,7 @@ namespace OpenRa.Game.Traits.Activities path = getPath( self, mobile ).TakeWhile( a => a != self.Location ).ToList(); SanityCheckPath( mobile ); } - + if( path.Count == 0 ) { destination = mobile.toCell; diff --git a/OpenRa.Game/Traits/Infantry.cs b/OpenRa.Game/Traits/Infantry.cs new file mode 100644 index 0000000000..d1122aa7b2 --- /dev/null +++ b/OpenRa.Game/Traits/Infantry.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OpenRa.Game.Traits +{ + class Infantry : ICrushable + { + public Infantry(Actor self){} + + public bool IsCrushableByFriend() + { + // HACK: should be false + return true; + } + public bool IsCrushableByEnemy() + { + // HACK: should be based off crushable tag + return true; + } + + public void OnCrush(Actor crusher) + { + Sound.Play("squishy2.aud"); + } + + public IEnumerable CrushableBy() + { + yield return UnitMovementType.Track; + yield return UnitMovementType.Wheel; + } + } +} diff --git a/OpenRa.Game/Traits/Mobile.cs b/OpenRa.Game/Traits/Mobile.cs index c25bfae764..dcc29f4ff5 100644 --- a/OpenRa.Game/Traits/Mobile.cs +++ b/OpenRa.Game/Traits/Mobile.cs @@ -80,9 +80,42 @@ namespace OpenRa.Game.Traits } } - public bool CanEnterCell(int2 location) + public bool CanEnterCell(int2 a) { - return Game.IsCellBuildable( location, GetMovementType(), self ); + if (Game.BuildingInfluence.GetBuildingAt(a) != null) return false; + + var actors = Game.UnitInfluence.GetUnitsAt(a); + var crushable = true; + foreach (Actor actor in actors) + { + if (actor == self) continue; + + var c = actor.traits.WithInterface(); + if (c == null) + { + crushable = false; + break; + } + + foreach (var crush in c) + { + // TODO: Unhack this. I can't wrap my head around this right now... + if (!(((crush.IsCrushableByEnemy() && actor.Owner != Game.LocalPlayer) || (crush.IsCrushableByFriend() && actor.Owner == Game.LocalPlayer)) + && crush.CrushableBy().Contains(GetMovementType()))) + { + crushable = false; + Log.Write("{0} is NOT crushable by {1} (mobile)", actor.Info.Name, self.Info.Name); + break; + } + } + Log.Write("{0} is crushable by {1} (mobile)", actor.Info.Name, self.Info.Name); + } + + if (!crushable) return false; + + return Rules.Map.IsInMap(a.X, a.Y) && + TerrainCosts.Cost(GetMovementType(), + Rules.TileSet.GetWalkability(Rules.Map.MapTiles[a.X, a.Y])) < double.PositiveInfinity; } public IEnumerable GetCurrentPath() diff --git a/OpenRa.Game/Traits/Production.cs b/OpenRa.Game/Traits/Production.cs index 4dcfe7fe63..a3cc336c15 100755 --- a/OpenRa.Game/Traits/Production.cs +++ b/OpenRa.Game/Traits/Production.cs @@ -21,7 +21,7 @@ namespace OpenRa.Game.Traits public bool Produce( Actor self, UnitInfo producee ) { var location = CreationLocation( self, producee ); - if( location == null || Game.UnitInfluence.GetUnitAt( location.Value ) != null ) + if( location == null || !Game.UnitInfluence.GetUnitsAt( location.Value ).Any() ) return false; var newUnit = new Actor( producee, location.Value, self.Owner ); diff --git a/OpenRa.Game/Traits/RenderBuildingWarFactory.cs b/OpenRa.Game/Traits/RenderBuildingWarFactory.cs index fe8a527911..40ccaf25e9 100644 --- a/OpenRa.Game/Traits/RenderBuildingWarFactory.cs +++ b/OpenRa.Game/Traits/RenderBuildingWarFactory.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using OpenRa.Game.Graphics; namespace OpenRa.Game.Traits @@ -35,7 +36,7 @@ namespace OpenRa.Game.Traits if (doneBuilding) roof.Tick(); var b = self.Bounds; - if (isOpen && null == Game.UnitInfluence.GetUnitAt(((1/24f) * self.CenterLocation).ToInt2())) + if (isOpen && !Game.UnitInfluence.GetUnitsAt(((1/24f) * self.CenterLocation).ToInt2()).Any()) { isOpen = false; roof.PlayBackwardsThen(prefix + "build-top", () => roof.Play(prefix + "idle-top")); diff --git a/OpenRa.Game/Traits/TraitsInterfaces.cs b/OpenRa.Game/Traits/TraitsInterfaces.cs index cb8903a396..da62f96de9 100644 --- a/OpenRa.Game/Traits/TraitsInterfaces.cs +++ b/OpenRa.Game/Traits/TraitsInterfaces.cs @@ -57,4 +57,12 @@ namespace OpenRa.Game.Traits UnitMovementType GetMovementType(); bool CanEnterCell(int2 location); } + + interface ICrushable + { + bool IsCrushableByFriend(); + bool IsCrushableByEnemy(); + void OnCrush(Actor crusher); + IEnumerableCrushableBy(); + } } diff --git a/OpenRa.Game/UiOverlay.cs b/OpenRa.Game/UiOverlay.cs index 309d44985a..f7abc7dd06 100644 --- a/OpenRa.Game/UiOverlay.cs +++ b/OpenRa.Game/UiOverlay.cs @@ -1,4 +1,6 @@ using System.Drawing; +using System.Collections.Generic; +using System.Linq; using OpenRa.Game.GameRules; using OpenRa.Game.Graphics; @@ -37,7 +39,7 @@ namespace OpenRa.Game if (ShowUnitDebug) for (var j = 0; j < 128; j++) for (var i = 0; i < 128; i++) - if (Game.UnitInfluence.GetUnitAt(new int2(i, j)) != null) + if (Game.UnitInfluence.GetUnitsAt(new int2(i, j)).Any()) spriteRenderer.DrawSprite(unitDebug, Game.CellSize * new float2(i, j), 0); } diff --git a/OpenRa.Game/UnitInfluenceMap.cs b/OpenRa.Game/UnitInfluenceMap.cs index 9fad708018..ee2b6beaea 100644 --- a/OpenRa.Game/UnitInfluenceMap.cs +++ b/OpenRa.Game/UnitInfluenceMap.cs @@ -1,17 +1,22 @@ using System; using System.Diagnostics; using System.Linq; +using System.Collections.Generic; using OpenRa.Game.Traits; namespace OpenRa.Game { class UnitInfluenceMap { - Actor[,] influence = new Actor[128, 128]; + List[,] influence = new List[128, 128]; readonly int2 searchDistance = new int2(2,2); public UnitInfluenceMap() { + for (int i = 0; i < 128; i++) + for (int j = 0; j < 128; j++) + influence[ i, j ] = new List(); + Game.world.ActorRemoved += a => Remove(a, a.traits.WithInterface().FirstOrDefault()); } @@ -25,25 +30,28 @@ namespace OpenRa.Game { for( int y = 0 ; y < 128 ; y++ ) for( int x = 0 ; x < 128 ; x++ ) - if( influence[ x, y ] != null && !influence[ x, y ].traits.WithInterface().First().OccupiedCells().Contains( new int2( x, y ) ) ) - throw new InvalidOperationException( "UIM: Sanity check failed A" ); + if( influence[ x, y ] != null ) + foreach (var a in influence[ x, y ]) + if (!a.traits.WithInterface().First().OccupiedCells().Contains( new int2( x, y ) ) ) + throw new InvalidOperationException( "UIM: Sanity check failed A" ); - foreach( var a in Game.world.Actors ) + foreach( Actor a in Game.world.Actors ) foreach( var ios in a.traits.WithInterface() ) foreach( var cell in ios.OccupiedCells() ) - if( influence[ cell.X, cell.Y ] != a ) - throw new InvalidOperationException( "UIM: Sanity check failed B" ); + if (!influence[cell.X, cell.Y].Contains(a)) + //if( influence[ cell.X, cell.Y ] != a ) + throw new InvalidOperationException( "UIM: Sanity check failed B" ); } [Conditional( "SANITY_CHECKS" )] void SanityCheckAdd( IOccupySpace a ) { foreach( var c in a.OccupiedCells() ) - if( influence[c.X, c.Y] != null ) + if( influence[c.X, c.Y].Any()) throw new InvalidOperationException( "UIM: Sanity check failed (Add)" ); } - public Actor GetUnitAt( int2 a ) + public IEnumerable GetUnitsAt( int2 a ) { return influence[ a.X, a.Y ]; } @@ -52,14 +60,14 @@ namespace OpenRa.Game { SanityCheckAdd( unit ); foreach( var c in unit.OccupiedCells() ) - influence[c.X, c.Y] = self; + influence[c.X, c.Y].Add(self); } public void Remove( Actor self, IOccupySpace unit ) { if (unit != null) foreach (var c in unit.OccupiedCells()) - influence[c.X, c.Y] = null; + influence[c.X, c.Y].Remove(self); } public void Update(Actor self, IOccupySpace unit)