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="ActorMap.cs" />
|
||||
<Compile Include="ActorReference.cs" />
|
||||
<Compile Include="DomainIndex.cs" />
|
||||
<Compile Include="Graphics\QuadRenderer.cs" />
|
||||
<Compile Include="PSubVec.cs" />
|
||||
<Compile Include="PSubPos.cs" />
|
||||
|
||||
@@ -111,7 +111,6 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
|
||||
public DomainIndex WorldDomains;
|
||||
internal World(Manifest manifest, Map map, OrderManager orderManager, bool isShellmap)
|
||||
{
|
||||
IsShellmap = isShellmap;
|
||||
@@ -122,9 +121,6 @@ namespace OpenRA
|
||||
TileSet = Rules.TileSets[Map.Tileset];
|
||||
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);
|
||||
|
||||
WorldActor = CreateActor( "World", new TypeDictionary() );
|
||||
|
||||
@@ -44,9 +44,6 @@ namespace OpenRA.Mods.RA.Move
|
||||
{
|
||||
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);
|
||||
if (cached != null)
|
||||
{
|
||||
@@ -58,6 +55,15 @@ namespace OpenRA.Mods.RA.Move
|
||||
|
||||
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(
|
||||
PathSearch.FromPoint(world, mi, self, target, from, true),
|
||||
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
|
||||
var tilesInRange = world.FindTilesInCircle(targetCell, range.Range / 1024 + 1)
|
||||
.Where(t => (t.CenterPosition - target).LengthSquared <= rangeSquared
|
||||
&& mi.CanEnterCell(self.World, self, t, null, true, true)
|
||||
&& !world.WorldDomains.IsCrossDomain(src, t));
|
||||
&& mi.CanEnterCell(self.World, self, t, null, true, true));
|
||||
|
||||
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(
|
||||
|
||||
@@ -457,6 +457,7 @@
|
||||
<Compile Include="Widgets\SlidingContainerWidget.cs" />
|
||||
<Compile Include="Widgets\ResourceBarWidget.cs" />
|
||||
<Compile Include="Widgets\Logic\SimpleTooltipLogic.cs" />
|
||||
<Compile Include="World\DomainIndex.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -158,7 +158,7 @@ Player:
|
||||
silo: 1
|
||||
BuildingFractions:
|
||||
proc: 17%
|
||||
nuke: 10%
|
||||
nuke: 10%
|
||||
pyle: 7%
|
||||
hand: 9%
|
||||
hq: 1%
|
||||
@@ -273,6 +273,7 @@ World:
|
||||
ProductionQueueFromSelection:
|
||||
ProductionTabsWidget: PRODUCTION_TABS
|
||||
BibLayer:
|
||||
DomainIndex:
|
||||
ResourceLayer:
|
||||
ResourceClaimLayer:
|
||||
ResourceType@green-tib:
|
||||
|
||||
@@ -354,6 +354,7 @@ World:
|
||||
BibLayer:
|
||||
BibTypes: bib3x, bib2x
|
||||
BibWidths: 3, 2
|
||||
DomainIndex:
|
||||
ResourceLayer:
|
||||
ResourceClaimLayer:
|
||||
ResourceType@Spice:
|
||||
|
||||
@@ -604,6 +604,7 @@ World:
|
||||
Name: Soviet
|
||||
Race: soviet
|
||||
BibLayer:
|
||||
DomainIndex:
|
||||
ResourceLayer:
|
||||
ResourceClaimLayer:
|
||||
ResourceType@ore:
|
||||
|
||||
Reference in New Issue
Block a user