Files
OpenRA/OpenRA.Mods.Common/MapGenerator/PlayableSpace.cs
2025-04-19 14:06:32 +03:00

148 lines
4.0 KiB
C#

#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* 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, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System.Collections.Generic;
namespace OpenRA.Mods.Common.MapGenerator
{
public static class PlayableSpace
{
public enum Playability
{
/// <summary>Area is unplayable by land/naval units.</summary>
Unplayable = 0,
/// <summary>
/// Area is unplayable by land/naval units, but should count as
/// being "within" a playable region. This usually applies to random
/// rock or river tiles in largely passable templates.
/// </summary>
Partial = 1,
/// <summary>
/// Area is playable by either land or naval units.
/// </summary>
Playable = 2,
}
/// <summary>
/// Additional data for a region containing playable space.
/// The shape of a region is specified separately via a region mask.
/// </summary>
public sealed class Region
{
/// <summary>Area of playable and partially playable space.</summary>
public int Area;
/// <summary>Area of fully playable space.</summary>
public int PlayableArea;
/// <summary>Region ID.</summary>
public int Id;
}
/// <summary>Sentinel indicating a position isn't assigned to a region.</summary>
public const int NullRegion = -1;
/// <summary>
/// <para>
/// Analyses a given map's tiles and ActorPlans and determines the playable space within it.
/// </para>
/// <para>
/// Requires a playabilityMap which specifies whether certain tiles are considered playable
/// or not. Actors are always considered partially playable.
/// </para>
/// <para>
/// RegionMap contains the mapping of map positions to Regions. If a map position is not
/// within a region, the value is NullRegion.
/// </para>
/// </summary>
public static (Region[] Regions, CellLayer<int> RegionMap, CellLayer<Playability> Playable) FindPlayableRegions(
Map map,
List<ActorPlan> actorPlans,
Dictionary<TerrainTile, Playability> playabilityMap)
{
var regions = new List<Region>();
var regionMap = new CellLayer<int>(map);
regionMap.Clear(NullRegion);
var playable = new CellLayer<Playability>(map);
playable.Clear(Playability.Unplayable);
foreach (var mpos in map.AllCells.MapCoords)
if (map.Contains(mpos))
playable[mpos] = playabilityMap[map.Tiles[mpos]];
foreach (var actorPlan in actorPlans)
foreach (var cpos in actorPlan.Footprint().Keys)
if (map.AllCells.Contains(cpos))
{
var mpos = cpos.ToMPos(map);
if (playable[mpos] == Playability.Playable)
playable[mpos] = Playability.Partial;
}
void Fill(Region region, CPos start)
{
void AddToRegion(CPos cpos, bool fullyPlayable)
{
var mpos = cpos.ToMPos(map);
regionMap[mpos] = region.Id;
region.Area++;
if (fullyPlayable)
region.PlayableArea++;
}
bool? Filler(CPos cpos, bool fullyPlayable)
{
var mpos = cpos.ToMPos(map);
if (regionMap[mpos] == NullRegion)
{
if (fullyPlayable && playable[mpos] == Playability.Playable)
{
AddToRegion(cpos, true);
return true;
}
else if (playable[mpos] == Playability.Partial)
{
AddToRegion(cpos, false);
return false;
}
}
return null;
}
CellLayerUtils.FloodFill(
playable,
[(start, true)],
Filler,
Direction.Spread4CVec);
}
foreach (var mpos in map.AllCells.MapCoords)
if (regionMap[mpos] == NullRegion && playable[mpos] == Playability.Playable)
{
var region = new Region()
{
Area = 0,
PlayableArea = 0,
Id = regions.Count,
};
regions.Add(region);
var cpos = mpos.ToCPos(map);
Fill(region, cpos);
}
return (regions.ToArray(), regionMap, playable);
}
}
}