diff --git a/OpenRa.Game/Game.cs b/OpenRa.Game/Game.cs index aa05a6babf..dbede28207 100644 --- a/OpenRa.Game/Game.cs +++ b/OpenRa.Game/Game.cs @@ -8,7 +8,7 @@ using IrrKlang; using IjwFramework.Collections; using System; using IjwFramework.Types; -using OpenRa.Game.Traits; +using OpenRa.Game.Traits; using OpenRa.Game.GameRules; namespace OpenRa.Game @@ -22,7 +22,7 @@ namespace OpenRa.Game static TreeCache treeCache; public static TerrainRenderer terrain; public static Viewport viewport; - public static PathFinder pathFinder; + public static PathFinder PathFinder; public static Network network; public static WorldRenderer worldRenderer; public static Controller controller; @@ -32,50 +32,50 @@ namespace OpenRa.Game public static Dictionary players = new Dictionary(); public static Player LocalPlayer { get { return players[localPlayerIndex]; } } - public static BuildingInfluenceMap BuildingInfluence; + public static BuildingInfluenceMap BuildingInfluence; public static UnitInfluenceMap UnitInfluence; - static ISoundEngine soundEngine; - - public static void Initialize(string mapName, Renderer renderer, int2 clientSize, int localPlayer) - { - Rules.LoadRules(mapName); - - for (int i = 0; i < 8; i++) - players.Add(i, new Player(i, string.Format("Multi{0}", i), Race.Soviet)); - - localPlayerIndex = localPlayer; - - var mapFile = new IniFile(FileSystem.Open(mapName)); - map = new Map(mapFile); - FileSystem.Mount(new Package(map.Theater + ".mix")); - - viewport = new Viewport(clientSize, map.Size, renderer); - - terrain = new TerrainRenderer(renderer, map, viewport); - world = new World(); - treeCache = new TreeCache(map); - - foreach (TreeReference treeReference in map.Trees) - world.Add(new Actor(treeReference, treeCache, map.Offset)); - - BuildingInfluence = new BuildingInfluenceMap(8); - UnitInfluence = new UnitInfluenceMap(); - - LoadMapBuildings(mapFile); - LoadMapUnits(mapFile); - - pathFinder = new PathFinder(map, terrain.tileSet); - - network = new Network(); - - controller = new Controller(); - worldRenderer = new WorldRenderer(renderer); - - soundEngine = new ISoundEngine(); - sounds = new Cache(LoadSound); - - PlaySound("intro.aud", false); + static ISoundEngine soundEngine; + + public static void Initialize(string mapName, Renderer renderer, int2 clientSize, int localPlayer) + { + Rules.LoadRules(mapName); + + for (int i = 0; i < 8; i++) + players.Add(i, new Player(i, string.Format("Multi{0}", i), Race.Soviet)); + + localPlayerIndex = localPlayer; + + var mapFile = new IniFile(FileSystem.Open(mapName)); + map = new Map(mapFile); + FileSystem.Mount(new Package(map.Theater + ".mix")); + + viewport = new Viewport(clientSize, map.Size, renderer); + + terrain = new TerrainRenderer(renderer, map, viewport); + world = new World(); + treeCache = new TreeCache(map); + + foreach (TreeReference treeReference in map.Trees) + world.Add(new Actor(treeReference, treeCache, map.Offset)); + + BuildingInfluence = new BuildingInfluenceMap(8); + UnitInfluence = new UnitInfluenceMap(); + + LoadMapBuildings(mapFile); + LoadMapUnits(mapFile); + + PathFinder = new PathFinder(map, terrain.tileSet); + + network = new Network(); + + controller = new Controller(); + worldRenderer = new WorldRenderer(renderer); + + soundEngine = new ISoundEngine(); + sounds = new Cache(LoadSound); + + PlaySound("intro.aud", false); } static void LoadMapBuildings( IniFile mapfile ) @@ -125,7 +125,7 @@ namespace OpenRa.Game public static void Tick() { var stuffFromOtherPlayers = network.Tick(); // todo: actually use the orders! - world.Update(); + world.Update(); UnitInfluence.Tick(); viewport.DrawRegions(); @@ -133,7 +133,7 @@ namespace OpenRa.Game public static bool IsCellBuildable(int2 a, UnitMovementType umt) { - if (BuildingInfluence.GetBuildingAt(a) != null) return false; + if (BuildingInfluence.GetBuildingAt(a) != null) return false; if (UnitInfluence.GetUnitAt(a) != null) return false; a += map.Offset; @@ -158,19 +158,19 @@ namespace OpenRa.Game { return FindUnits(a - new float2(r, r), a + new float2(r, r)) .Where(x => (x.CenterLocation - a).LengthSquared < r * r); - } - - public static IEnumerable FindTilesInCircle(int2 a, int r) - { - var min = a - new int2(r, r); - var max = a + new int2(r, r); - - for (var j = min.Y; j <= max.Y; j++) - for (var i = min.X; i <= max.X; i++) - if (r * r >= (new int2(i, j) - a).LengthSquared) - yield return new int2(i, j); - } - + } + + public static IEnumerable FindTilesInCircle(int2 a, int r) + { + var min = a - new int2(r, r); + var max = a + new int2(r, r); + + for (var j = min.Y; j <= max.Y; j++) + for (var i = min.X; i <= max.X; i++) + if (r * r >= (new int2(i, j) - a).LengthSquared) + yield return new int2(i, j); + } + public static IEnumerable SelectUnitsInBox(float2 a, float2 b) { return FindUnits(a, b).Where(x => x.Owner == LocalPlayer && x.traits.Contains()); @@ -197,52 +197,52 @@ namespace OpenRa.Game public static readonly Pair SovietVoices = Pair.New( new VoicePool("ackno", "affirm1", "noprob", "overout", "ritaway", "roger", "ugotit"), - new VoicePool("await1", "ready", "report1", "yessir1")); - - static int2? FindAdjacentTile(Actor a, UnitMovementType umt) - { - var tiles = Footprint.Tiles(a); - var min = tiles.Aggregate(int2.Min) - new int2(1, 1); - var max = tiles.Aggregate(int2.Max) + new int2(1, 1); - - for (var j = min.Y; j <= max.Y; j++) - for (var i = min.X; i <= max.X; i++) - if (IsCellBuildable(new int2(i, j), umt)) - return new int2(i, j); - - return null; - } - + new VoicePool("await1", "ready", "report1", "yessir1")); + + static int2? FindAdjacentTile(Actor a, UnitMovementType umt) + { + var tiles = Footprint.Tiles(a); + var min = tiles.Aggregate(int2.Min) - new int2(1, 1); + var max = tiles.Aggregate(int2.Max) + new int2(1, 1); + + for (var j = min.Y; j <= max.Y; j++) + for (var i = min.X; i <= max.X; i++) + if (IsCellBuildable(new int2(i, j), umt)) + return new int2(i, j); + + return null; + } + public static void BuildUnit(Player player, string name) - { + { var producerTypes = Rules.UnitInfo[name].BuiltAt; var producer = world.Actors .FirstOrDefault(a => a.unitInfo != null && producerTypes.Contains(a.unitInfo.Name) && a.Owner == player); if (producer == null) - throw new InvalidOperationException("BuildUnit without suitable production structure!"); - - Actor unit; - - if (producerTypes.Contains("spen") || producerTypes.Contains("syrd")) - { - var space = FindAdjacentTile(producer, Rules.UnitInfo[name].WaterBound ? - UnitMovementType.Float : UnitMovementType.Wheel ); /* hackety hack */ - - if (space == null) - throw new NotImplementedException("Nowhere to place this unit."); - - unit = new Actor(name, space.Value, player); - var mobile = unit.traits.Get(); - mobile.facing = SharedRandom.Next(256); - } - else - { - unit = new Actor(name, (1 / 24f * producer.CenterLocation).ToInt2(), player); - var mobile = unit.traits.Get(); - mobile.facing = 128; - mobile.QueueActivity(new Mobile.MoveTo(unit.Location + new int2(0, 3))); + throw new InvalidOperationException("BuildUnit without suitable production structure!"); + + Actor unit; + + if (producerTypes.Contains("spen") || producerTypes.Contains("syrd")) + { + var space = FindAdjacentTile(producer, Rules.UnitInfo[name].WaterBound ? + UnitMovementType.Float : UnitMovementType.Wheel ); /* hackety hack */ + + if (space == null) + throw new NotImplementedException("Nowhere to place this unit."); + + unit = new Actor(name, space.Value, player); + var mobile = unit.traits.Get(); + mobile.facing = SharedRandom.Next(256); + } + else + { + unit = new Actor(name, (1 / 24f * producer.CenterLocation).ToInt2(), player); + var mobile = unit.traits.Get(); + mobile.facing = 128; + mobile.QueueActivity(new Mobile.MoveTo(unit.Location + new int2(0, 3))); } world.AddFrameEndTask(_ => world.Add(unit)); diff --git a/OpenRa.Game/PathFinder.cs b/OpenRa.Game/PathFinder.cs index aa8216ca92..3cbd755102 100644 --- a/OpenRa.Game/PathFinder.cs +++ b/OpenRa.Game/PathFinder.cs @@ -42,6 +42,32 @@ namespace OpenRa.Game return path; } + public List FindPathToPath( int2 from, List path, UnitMovementType umt ) + { + var offset = map.Offset; + var cellInfo = InitCellInfo(); + var queue = new PriorityQueue(); + var estimator = DefaultEstimator( from ); + + var cost = 0.0; + var prev = path[ 0 ] + offset; + for( int i = 0 ; i < path.Count ; i++ ) + { + var sl = path[ i ] + offset; + if( Game.BuildingInfluence.GetBuildingAt( path[ i ] ) == null & Game.UnitInfluence.GetUnitAt( path[ i ] ) == null ) + { + queue.Add( new PathDistance( estimator( sl - offset ), sl ) ); + cellInfo[ sl.X, sl.Y ] = new CellInfo( cost, prev, false ); + } + var d = sl - prev; + cost += ( ( d.X * d.Y != 0 ) ? 1.414213563 : 1.0 ) * passableCost[ (int)umt ][ sl.X, sl.Y ]; + prev = sl; + } + var ret = FindPath( cellInfo, queue, estimator, umt, true ); + ret.Reverse(); + return ret; + } + List FindUnitPath( int2 unitLocation, Func estimator, UnitMovementType umt ) { var startLocation = unitLocation + map.Offset; @@ -50,13 +76,8 @@ namespace OpenRa.Game List FindUnitPath(IEnumerable startLocations, Func estimator, UnitMovementType umt) { - var cellInfo = new CellInfo[128, 128]; var offset = map.Offset; - - for (int x = 0; x < 128; x++) - for (int y = 0; y < 128; y++) - cellInfo[x, y] = new CellInfo(double.PositiveInfinity, new int2(x, y), false); - + var cellInfo = InitCellInfo(); var queue = new PriorityQueue(); foreach (var sl in startLocations) @@ -65,6 +86,13 @@ namespace OpenRa.Game cellInfo[sl.X, sl.Y].MinCost = 0; } + return FindPath( cellInfo, queue, estimator, umt, false ); + } + + List FindPath( CellInfo[ , ] cellInfo, PriorityQueue queue, Func estimator, UnitMovementType umt, bool checkForBlock ) + { + var offset = map.Offset; + while( !queue.Empty ) { PathDistance p = queue.Pop(); @@ -84,6 +112,8 @@ namespace OpenRa.Game continue; if (Game.BuildingInfluence.GetBuildingAt(newHere - offset) != null) continue; + if( checkForBlock && Game.UnitInfluence.GetUnitAt( newHere - offset ) != null ) + continue; double cellCost = ( ( d.X * d.Y != 0 ) ? 1.414213563 : 1.0 ) * passableCost[(int)umt][ newHere.X, newHere.Y ]; double newCost = cellInfo[ here.X, here.Y ].MinCost + cellCost; @@ -102,6 +132,15 @@ namespace OpenRa.Game return new List(); } + static CellInfo[ , ] InitCellInfo() + { + var cellInfo = new CellInfo[ 128, 128 ]; + for( int x = 0 ; x < 128 ; x++ ) + for( int y = 0 ; y < 128 ; y++ ) + cellInfo[ x, y ] = new CellInfo( double.PositiveInfinity, new int2( x, y ), false ); + return cellInfo; + } + List MakePath( CellInfo[ , ] cellInfo, int2 destination, int2 offset ) { List ret = new List(); diff --git a/OpenRa.Game/Traits/Mobile.cs b/OpenRa.Game/Traits/Mobile.cs index 18a45e83e7..280b53717b 100644 --- a/OpenRa.Game/Traits/Mobile.cs +++ b/OpenRa.Game/Traits/Mobile.cs @@ -49,9 +49,9 @@ namespace OpenRa.Game.Traits public Order Order(Actor self, int2 xy, bool lmb, Actor underCursor) { - if( lmb ) return null; - - if( underCursor != null ) + if( lmb ) return null; + + if( underCursor != null ) return null; if (xy != toCell) @@ -121,31 +121,32 @@ namespace OpenRa.Game.Traits public CurrentActivity NextActivity { get; set; } int2? destination; - public List path; + public List path; Func> getPath; MovePart move; public MoveTo( int2 destination ) - { - this.getPath = (self, mobile) => Game.pathFinder.FindUnitPath( - self.Location, destination, + { + this.getPath = (self, mobile) => Game.PathFinder.FindUnitPath( + self.Location, destination, mobile.GetMovementType()); this.destination = destination; - } - - public MoveTo(Actor target, int range) - { - this.getPath = (self, mobile) => Game.pathFinder.FindUnitPathToRange( - self.Location, target.Location, - mobile.GetMovementType(), range); - this.destination = null; + } + + public MoveTo(Actor target, int range) + { + this.getPath = (self, mobile) => Game.PathFinder.FindUnitPathToRange( + self.Location, target.Location, + mobile.GetMovementType(), range); + this.destination = null; } static bool CanEnterCell(int2 c, Actor self) { - var u = Game.UnitInfluence.GetUnitAt(c); - return u == null || u == self; + var u = Game.UnitInfluence.GetUnitAt(c); + var b = Game.BuildingInfluence.GetBuildingAt(c); + return (u == null || u == self) && b == null; } public void Tick( Actor self, Mobile mobile ) @@ -154,46 +155,67 @@ namespace OpenRa.Game.Traits { move.TickMove( self, mobile, this ); return; - } + } if( destination == self.Location ) { mobile.currentActivity = NextActivity; return; - } + } + + if (path == null) path = getPath(self, mobile).TakeWhile(a => a != self.Location).ToList(); + + if (path.Count == 0) + { + destination = mobile.toCell; + return; + } + + destination = path[0]; - if (path == null) path = getPath(self, mobile).TakeWhile(a => a != self.Location).ToList(); - - if (path.Count == 0) + var nextCell = PopPath( self, mobile ); + if( nextCell == null ) + return; + + int2 dir = nextCell.Value - mobile.fromCell; + var firstFacing = Util.GetFacing( dir, mobile.facing ); + if( firstFacing != mobile.facing ) { - destination = mobile.toCell; - return; + mobile.currentActivity = new Turn( firstFacing ) { NextActivity = this }; + path.Add( nextCell.Value ); } else - destination = path[0]; - - var nextCell = path[ path.Count - 1 ]; - int2 dir = nextCell - mobile.fromCell; - var firstFacing = Util.GetFacing( dir, mobile.facing ); - if( firstFacing != mobile.facing ) - mobile.currentActivity = new Turn( firstFacing ) { NextActivity = this }; - else - { - if (!CanEnterCell(nextCell, self)) return; /* todo: repath, sometimes */ - - mobile.toCell = nextCell; - path.RemoveAt( path.Count - 1 ); - - move = new MoveFirstHalf( - CenterOfCell( mobile.fromCell ), - BetweenCells( mobile.fromCell, mobile.toCell ), - mobile.facing, - mobile.facing, - 0 ); - - Game.UnitInfluence.Update(mobile); + { + mobile.toCell = nextCell.Value; + move = new MoveFirstHalf( + CenterOfCell( mobile.fromCell ), + BetweenCells( mobile.fromCell, mobile.toCell ), + mobile.facing, + mobile.facing, + 0 ); + + Game.UnitInfluence.Update( mobile ); } mobile.currentActivity.Tick( self, mobile ); + } + + int2? PopPath( Actor self, Mobile mobile ) + { + if( path.Count == 0 ) return null; + var nextCell = path[ path.Count - 1 ]; + if( !CanEnterCell( nextCell, self ) ) + { + Game.UnitInfluence.Remove( mobile ); + path = Game.PathFinder.FindPathToPath( self.Location, path, mobile.GetMovementType() ) + .TakeWhile( a => a != self.Location ) + .ToList(); + Game.UnitInfluence.Add( mobile ); + if( path.Count == 0 ) + return null; + nextCell = path[ path.Count - 1 ]; + } + path.RemoveAt( path.Count - 1 ); + return nextCell; } static float2 CenterOfCell( int2 loc ) @@ -260,28 +282,25 @@ namespace OpenRa.Game.Traits } protected override MovePart OnComplete( Actor self, Mobile mobile, MoveTo parent ) - { - if( parent.path.Count > 0 ) - { - var nextCell = parent.path[ parent.path.Count - 1 ]; - if( ( nextCell - mobile.toCell ) != ( mobile.toCell - mobile.fromCell ) ) - { - if( CanEnterCell( nextCell, self ) ) - { - parent.path.RemoveAt( parent.path.Count - 1 ); - - var ret = new MoveFirstHalf( - BetweenCells( mobile.fromCell, mobile.toCell ), - BetweenCells( mobile.toCell, nextCell ), - mobile.facing, - Util.GetNearestFacing( mobile.facing, Util.GetFacing( nextCell - mobile.toCell, mobile.facing ) ), - moveFraction - moveFractionTotal ); - mobile.fromCell = mobile.toCell; - mobile.toCell = nextCell; - Game.UnitInfluence.Update( mobile ); - return ret; - } - } + { + var nextCell = parent.PopPath( self, mobile ); + if( nextCell != null ) + { + if( ( nextCell - mobile.toCell ) != ( mobile.toCell - mobile.fromCell ) ) + { + var ret = new MoveFirstHalf( + BetweenCells( mobile.fromCell, mobile.toCell ), + BetweenCells( mobile.toCell, nextCell.Value ), + mobile.facing, + Util.GetNearestFacing( mobile.facing, Util.GetFacing( nextCell.Value - mobile.toCell, mobile.facing ) ), + moveFraction - moveFractionTotal ); + mobile.fromCell = mobile.toCell; + mobile.toCell = nextCell.Value; + Game.UnitInfluence.Update( mobile ); + return ret; + } + else + parent.path.Add( nextCell.Value ); } var ret2 = new MoveSecondHalf( BetweenCells( mobile.fromCell, mobile.toCell ), @@ -314,13 +333,13 @@ namespace OpenRa.Game.Traits path.Clear(); NextActivity = null; } - } - - public IEnumerable GetCurrentPath() - { - var move = currentActivity as MoveTo; - if (move == null || move.path == null) return new int2[] { }; - return Enumerable.Reverse(move.path); - } + } + + public IEnumerable GetCurrentPath() + { + var move = currentActivity as MoveTo; + if (move == null || move.path == null) return new int2[] { }; + return Enumerable.Reverse(move.path); + } } } diff --git a/OpenRa.Game/UnitInfluenceMap.cs b/OpenRa.Game/UnitInfluenceMap.cs index 438906f569..16ffe011fe 100644 --- a/OpenRa.Game/UnitInfluenceMap.cs +++ b/OpenRa.Game/UnitInfluenceMap.cs @@ -25,7 +25,13 @@ namespace OpenRa.Game Update(u); } - public Actor GetUnitAt(int2 a) { return influence[a.X, a.Y]; } + public Actor GetUnitAt( int2 a ) + { + var actor = influence[ a.X, a.Y ]; + if( actor != null && !actor.traits.Get().OccupiedCells().Contains( a ) ) + throw new InvalidOperationException( "UIM: Unit is not in influenced square" ); + return actor; + } public void Add(Mobile a) {