Use MobileInfo.GetMovementClass for domain indexing, clearing path for caching and smarter behavior
* Move DomainIndex from being a manual hard-coded hook in World to an IWorldLoaded trait.
This commit is contained in:
@@ -1,106 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
using OpenRA.Effects;
|
|
||||||
using OpenRA.FileFormats;
|
|
||||||
using OpenRA.Graphics;
|
|
||||||
using OpenRA.Network;
|
|
||||||
using OpenRA.Orders;
|
|
||||||
using OpenRA.Support;
|
|
||||||
using OpenRA.Traits;
|
|
||||||
using XRandom = OpenRA.Thirdparty.Random;
|
|
||||||
|
|
||||||
namespace OpenRA
|
|
||||||
{
|
|
||||||
public class DomainIndex
|
|
||||||
{
|
|
||||||
Rectangle bounds;
|
|
||||||
int[,] domains;
|
|
||||||
|
|
||||||
public DomainIndex(World world)
|
|
||||||
{
|
|
||||||
bounds = world.Map.Bounds;
|
|
||||||
domains = new int[(bounds.Width + bounds.X), (bounds.Height + bounds.Y)];
|
|
||||||
|
|
||||||
BuildDomains(world);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetDomainOf(CPos p)
|
|
||||||
{
|
|
||||||
return domains[p.X, p.Y];
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsCrossDomain(CPos p1, CPos p2)
|
|
||||||
{
|
|
||||||
return GetDomainOf(p1) != GetDomainOf(p2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetDomain(CPos p, int domain)
|
|
||||||
{
|
|
||||||
domains[p.X, p.Y] = domain;
|
|
||||||
}
|
|
||||||
|
|
||||||
void BuildDomains(World world)
|
|
||||||
{
|
|
||||||
Map map = world.Map;
|
|
||||||
|
|
||||||
int i = 1;
|
|
||||||
HashSet<CPos> unassigned = new HashSet<CPos>();
|
|
||||||
|
|
||||||
// Fill up our set of yet-unassigned map cells
|
|
||||||
for (int x = map.Bounds.Left; x < bounds.Right; x += 1)
|
|
||||||
{
|
|
||||||
for (int y = bounds.Top; y < bounds.Bottom; y += 1)
|
|
||||||
{
|
|
||||||
unassigned.Add(new CPos(x, y));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (unassigned.Count != 0)
|
|
||||||
{
|
|
||||||
var start = unassigned.First();
|
|
||||||
unassigned.Remove(start);
|
|
||||||
|
|
||||||
// Wander around looking for water transitions
|
|
||||||
bool inWater = WorldUtils.GetTerrainInfo(world, start).IsWater;
|
|
||||||
Queue<CPos> toProcess = new Queue<CPos>();
|
|
||||||
HashSet<CPos> seen = new HashSet<CPos>();
|
|
||||||
toProcess.Enqueue(start);
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
CPos p = toProcess.Dequeue();
|
|
||||||
if (seen.Contains(p)) continue;
|
|
||||||
seen.Add(p);
|
|
||||||
|
|
||||||
TerrainTypeInfo cellInfo = WorldUtils.GetTerrainInfo(world, p);
|
|
||||||
bool isWater = cellInfo.IsWater;
|
|
||||||
|
|
||||||
// Check if we're still in one contiguous domain
|
|
||||||
if (inWater == isWater)
|
|
||||||
{
|
|
||||||
SetDomain(p, i);
|
|
||||||
unassigned.Remove(p);
|
|
||||||
|
|
||||||
// Visit our neighbors, if we haven't already
|
|
||||||
foreach (var d in CVec.directions)
|
|
||||||
{
|
|
||||||
CPos nextPos = p + d;
|
|
||||||
if (nextPos.X >= map.Bounds.Left && nextPos.Y >= map.Bounds.Top &&
|
|
||||||
nextPos.X < map.Bounds.Right && nextPos.Y < map.Bounds.Bottom)
|
|
||||||
{
|
|
||||||
if (!seen.Contains(nextPos)) toProcess.Enqueue(nextPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} while (toProcess.Count != 0);
|
|
||||||
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.Write("debug", "{0}: Found {1} domains", map.Title, i-1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -85,7 +85,6 @@
|
|||||||
<Compile Include="ActorInitializer.cs" />
|
<Compile Include="ActorInitializer.cs" />
|
||||||
<Compile Include="ActorMap.cs" />
|
<Compile Include="ActorMap.cs" />
|
||||||
<Compile Include="ActorReference.cs" />
|
<Compile Include="ActorReference.cs" />
|
||||||
<Compile Include="DomainIndex.cs" />
|
|
||||||
<Compile Include="Graphics\QuadRenderer.cs" />
|
<Compile Include="Graphics\QuadRenderer.cs" />
|
||||||
<Compile Include="PSubVec.cs" />
|
<Compile Include="PSubVec.cs" />
|
||||||
<Compile Include="PSubPos.cs" />
|
<Compile Include="PSubPos.cs" />
|
||||||
|
|||||||
@@ -111,7 +111,6 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public DomainIndex WorldDomains;
|
|
||||||
internal World(Manifest manifest, Map map, OrderManager orderManager, bool isShellmap)
|
internal World(Manifest manifest, Map map, OrderManager orderManager, bool isShellmap)
|
||||||
{
|
{
|
||||||
IsShellmap = isShellmap;
|
IsShellmap = isShellmap;
|
||||||
@@ -122,9 +121,6 @@ namespace OpenRA
|
|||||||
TileSet = Rules.TileSets[Map.Tileset];
|
TileSet = Rules.TileSets[Map.Tileset];
|
||||||
TileSet.LoadTiles();
|
TileSet.LoadTiles();
|
||||||
|
|
||||||
// Identify untraversable regions of the map for faster pathfinding, especially with AI
|
|
||||||
WorldDomains = new DomainIndex(this);
|
|
||||||
|
|
||||||
SharedRandom = new XRandom(orderManager.LobbyInfo.GlobalSettings.RandomSeed);
|
SharedRandom = new XRandom(orderManager.LobbyInfo.GlobalSettings.RandomSeed);
|
||||||
|
|
||||||
WorldActor = CreateActor( "World", new TypeDictionary() );
|
WorldActor = CreateActor( "World", new TypeDictionary() );
|
||||||
|
|||||||
@@ -44,9 +44,6 @@ namespace OpenRA.Mods.RA.Move
|
|||||||
{
|
{
|
||||||
using (new PerfSample("Pathfinder"))
|
using (new PerfSample("Pathfinder"))
|
||||||
{
|
{
|
||||||
// If a water-land transition is required, bail early
|
|
||||||
if (world.WorldDomains.IsCrossDomain(from, target)) return new List<CPos>(0);
|
|
||||||
|
|
||||||
var cached = CachedPaths.FirstOrDefault(p => p.from == from && p.to == target && p.actor == self);
|
var cached = CachedPaths.FirstOrDefault(p => p.from == from && p.to == target && p.actor == self);
|
||||||
if (cached != null)
|
if (cached != null)
|
||||||
{
|
{
|
||||||
@@ -58,6 +55,15 @@ namespace OpenRA.Mods.RA.Move
|
|||||||
|
|
||||||
var mi = self.Info.Traits.Get<MobileInfo>();
|
var mi = self.Info.Traits.Get<MobileInfo>();
|
||||||
|
|
||||||
|
// If a water-land transition is required, bail early
|
||||||
|
var domainIndex = self.World.WorldActor.TraitOrDefault<DomainIndex>();
|
||||||
|
if (domainIndex != null)
|
||||||
|
{
|
||||||
|
var passable = mi.GetMovementClass(world.TileSet);
|
||||||
|
if (!domainIndex.IsPassable(from, target, (uint)passable))
|
||||||
|
return new List<CPos>(0);
|
||||||
|
}
|
||||||
|
|
||||||
var pb = FindBidiPath(
|
var pb = FindBidiPath(
|
||||||
PathSearch.FromPoint(world, mi, self, target, from, true),
|
PathSearch.FromPoint(world, mi, self, target, from, true),
|
||||||
PathSearch.FromPoint(world, mi, self, from, target, true).InReverse()
|
PathSearch.FromPoint(world, mi, self, from, target, true).InReverse()
|
||||||
@@ -87,12 +93,17 @@ namespace OpenRA.Mods.RA.Move
|
|||||||
// This assumes that the SubCell does not change during the path traversal
|
// This assumes that the SubCell does not change during the path traversal
|
||||||
var tilesInRange = world.FindTilesInCircle(targetCell, range.Range / 1024 + 1)
|
var tilesInRange = world.FindTilesInCircle(targetCell, range.Range / 1024 + 1)
|
||||||
.Where(t => (t.CenterPosition - target).LengthSquared <= rangeSquared
|
.Where(t => (t.CenterPosition - target).LengthSquared <= rangeSquared
|
||||||
&& mi.CanEnterCell(self.World, self, t, null, true, true)
|
&& mi.CanEnterCell(self.World, self, t, null, true, true));
|
||||||
&& !world.WorldDomains.IsCrossDomain(src, t));
|
|
||||||
|
|
||||||
if(tilesInRange.Count() == 0)
|
// See if there is any cell within range that does not involve a cross-domain request
|
||||||
|
// Really, we only need to check the circle perimeter, but it's not clear that would be a performance win
|
||||||
|
var domainIndex = self.World.WorldActor.TraitOrDefault<DomainIndex>();
|
||||||
|
if (domainIndex != null)
|
||||||
{
|
{
|
||||||
return new List<CPos>(0);
|
var passable = mi.GetMovementClass(world.TileSet);
|
||||||
|
tilesInRange = new List<CPos>(tilesInRange.Where(t => domainIndex.IsPassable(src, t, (uint)passable)));
|
||||||
|
if (tilesInRange.Count() == 0)
|
||||||
|
return new List<CPos>(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
var path = FindBidiPath(
|
var path = FindBidiPath(
|
||||||
|
|||||||
@@ -457,6 +457,7 @@
|
|||||||
<Compile Include="Widgets\SlidingContainerWidget.cs" />
|
<Compile Include="Widgets\SlidingContainerWidget.cs" />
|
||||||
<Compile Include="Widgets\ResourceBarWidget.cs" />
|
<Compile Include="Widgets\ResourceBarWidget.cs" />
|
||||||
<Compile Include="Widgets\Logic\SimpleTooltipLogic.cs" />
|
<Compile Include="Widgets\Logic\SimpleTooltipLogic.cs" />
|
||||||
|
<Compile Include="World\DomainIndex.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\OpenRA.FileFormats\OpenRA.FileFormats.csproj">
|
<ProjectReference Include="..\OpenRA.FileFormats\OpenRA.FileFormats.csproj">
|
||||||
|
|||||||
146
OpenRA.Mods.RA/World/DomainIndex.cs
Normal file
146
OpenRA.Mods.RA/World/DomainIndex.cs
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
#region Copyright & License Information
|
||||||
|
/*
|
||||||
|
* Copyright 2007-2013 The OpenRA Developers (see AUTHORS)
|
||||||
|
* This file is part of OpenRA, which is free software. It is made
|
||||||
|
* available to you under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation. For more information,
|
||||||
|
* see COPYING.
|
||||||
|
*/
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using OpenRA.Effects;
|
||||||
|
using OpenRA.FileFormats;
|
||||||
|
using OpenRA.Graphics;
|
||||||
|
using OpenRA.Network;
|
||||||
|
using OpenRA.Orders;
|
||||||
|
using OpenRA.Support;
|
||||||
|
using OpenRA.Traits;
|
||||||
|
using OpenRA.Mods.RA.Move;
|
||||||
|
using XRandom = OpenRA.Thirdparty.Random;
|
||||||
|
|
||||||
|
namespace OpenRA.Mods.RA
|
||||||
|
{
|
||||||
|
// Identify untraversable regions of the map for faster pathfinding, especially with AI
|
||||||
|
class DomainIndexInfo : TraitInfo<DomainIndex> {}
|
||||||
|
|
||||||
|
public class DomainIndex : IWorldLoaded
|
||||||
|
{
|
||||||
|
Dictionary<uint, MovementClassDomainIndex> domains;
|
||||||
|
|
||||||
|
public void WorldLoaded(World world)
|
||||||
|
{
|
||||||
|
domains = new Dictionary<uint, MovementClassDomainIndex>();
|
||||||
|
var movementClasses = new HashSet<uint>(
|
||||||
|
Rules.Info.Where(ai => ai.Value.Traits.Contains<MobileInfo>())
|
||||||
|
.Select(ai => (uint)ai.Value.Traits.Get<MobileInfo>().GetMovementClass(world.TileSet)));
|
||||||
|
|
||||||
|
foreach(var mc in movementClasses) domains[mc] = new MovementClassDomainIndex(world, mc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsPassable(CPos p1, CPos p2, uint movementClass)
|
||||||
|
{
|
||||||
|
return domains[movementClass].IsPassable(p1, p2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MovementClassDomainIndex
|
||||||
|
{
|
||||||
|
Rectangle bounds;
|
||||||
|
uint movementClass;
|
||||||
|
int[,] domains;
|
||||||
|
|
||||||
|
public MovementClassDomainIndex(World world, uint movementClass)
|
||||||
|
{
|
||||||
|
this.movementClass = movementClass;
|
||||||
|
bounds = world.Map.Bounds;
|
||||||
|
domains = new int[(bounds.Width + bounds.X), (bounds.Height + bounds.Y)];
|
||||||
|
BuildDomains(world);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetDomainOf(CPos p)
|
||||||
|
{
|
||||||
|
return domains[p.X, p.Y];
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsPassable(CPos p1, CPos p2)
|
||||||
|
{
|
||||||
|
return domains[p1.X, p1.Y] == domains[p2.X, p2.Y];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetDomain(CPos p, int domain)
|
||||||
|
{
|
||||||
|
domains[p.X, p.Y] = domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void BuildDomains(World world)
|
||||||
|
{
|
||||||
|
Map map = world.Map;
|
||||||
|
|
||||||
|
int i = 1;
|
||||||
|
var unassigned = new HashSet<CPos>();
|
||||||
|
|
||||||
|
// Fill up our set of yet-unassigned map cells
|
||||||
|
for (int x = map.Bounds.Left; x < bounds.Right; x += 1)
|
||||||
|
{
|
||||||
|
for (int y = bounds.Top; y < bounds.Bottom; y += 1)
|
||||||
|
{
|
||||||
|
unassigned.Add(new CPos(x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (unassigned.Count != 0)
|
||||||
|
{
|
||||||
|
var start = unassigned.First();
|
||||||
|
unassigned.Remove(start);
|
||||||
|
|
||||||
|
// Wander around looking for water transitions
|
||||||
|
string currentTileType = WorldUtils.GetTerrainType(world, start);
|
||||||
|
int terrainOffset = world.TileSet.Terrain.OrderBy(t => t.Key).ToList().FindIndex(x => x.Key == currentTileType);
|
||||||
|
bool currentPassable = (movementClass & (1 << terrainOffset)) > 0;
|
||||||
|
|
||||||
|
var toProcess = new Queue<CPos>();
|
||||||
|
var seen = new HashSet<CPos>();
|
||||||
|
toProcess.Enqueue(start);
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
CPos p = toProcess.Dequeue();
|
||||||
|
if (seen.Contains(p)) continue;
|
||||||
|
seen.Add(p);
|
||||||
|
|
||||||
|
string candidateTileType = WorldUtils.GetTerrainType(world, p);
|
||||||
|
int candidateTerrainOffset = world.TileSet.Terrain.OrderBy(t => t.Key).ToList().FindIndex(x => x.Key == candidateTileType);
|
||||||
|
bool candidatePassable = (movementClass & (1 << candidateTerrainOffset)) > 0;
|
||||||
|
|
||||||
|
// Check if we're still in one contiguous domain
|
||||||
|
if (currentPassable == candidatePassable)
|
||||||
|
{
|
||||||
|
SetDomain(p, i);
|
||||||
|
unassigned.Remove(p);
|
||||||
|
|
||||||
|
// Visit our neighbors, if we haven't already
|
||||||
|
foreach (var d in CVec.directions)
|
||||||
|
{
|
||||||
|
CPos nextPos = p + d;
|
||||||
|
if (nextPos.X >= map.Bounds.Left && nextPos.Y >= map.Bounds.Top &&
|
||||||
|
nextPos.X < map.Bounds.Right && nextPos.Y < map.Bounds.Bottom)
|
||||||
|
{
|
||||||
|
if (!seen.Contains(nextPos)) toProcess.Enqueue(nextPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (toProcess.Count != 0);
|
||||||
|
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Write("debug", "{0}: Found {1} domains", map.Title, i-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -273,6 +273,7 @@ World:
|
|||||||
ProductionQueueFromSelection:
|
ProductionQueueFromSelection:
|
||||||
ProductionTabsWidget: PRODUCTION_TABS
|
ProductionTabsWidget: PRODUCTION_TABS
|
||||||
BibLayer:
|
BibLayer:
|
||||||
|
DomainIndex:
|
||||||
ResourceLayer:
|
ResourceLayer:
|
||||||
ResourceClaimLayer:
|
ResourceClaimLayer:
|
||||||
ResourceType@green-tib:
|
ResourceType@green-tib:
|
||||||
|
|||||||
@@ -354,6 +354,7 @@ World:
|
|||||||
BibLayer:
|
BibLayer:
|
||||||
BibTypes: bib3x, bib2x
|
BibTypes: bib3x, bib2x
|
||||||
BibWidths: 3, 2
|
BibWidths: 3, 2
|
||||||
|
DomainIndex:
|
||||||
ResourceLayer:
|
ResourceLayer:
|
||||||
ResourceClaimLayer:
|
ResourceClaimLayer:
|
||||||
ResourceType@Spice:
|
ResourceType@Spice:
|
||||||
|
|||||||
@@ -604,6 +604,7 @@ World:
|
|||||||
Name: Soviet
|
Name: Soviet
|
||||||
Race: soviet
|
Race: soviet
|
||||||
BibLayer:
|
BibLayer:
|
||||||
|
DomainIndex:
|
||||||
ResourceLayer:
|
ResourceLayer:
|
||||||
ResourceClaimLayer:
|
ResourceClaimLayer:
|
||||||
ResourceType@ore:
|
ResourceType@ore:
|
||||||
|
|||||||
Reference in New Issue
Block a user