Bot mcv expansion: 1. Add ResourceMapBotModule. 2. Add McvExpansionManagerBotModule. 3. Give BaseBuilderBotModule better control over refinery. -- allow bot sell refinery too close. -- allow bot sell refinery far away from resource. (requires ResourceMapBotModule) -- allow bot recieve refinery construct requirement. 4. Give BaseBuilderBotModule control of expansion timing. (requires McvExpansionManagerBotModule) 5. Remove unused VehiclesFactoryTypes and useless BarracksTypes (now all use ProductionTypes). 6. Give HarvesterBotModule ability of order harvesters in low resource area to high resource area.
This commit is contained in:
committed by
Gustas Kažukauskas
parent
7a2e25af86
commit
961702d818
308
OpenRA.Mods.Common/Traits/BotModules/ResourceMapBotModule.cs
Normal file
308
OpenRA.Mods.Common/Traits/BotModules/ResourceMapBotModule.cs
Normal file
@@ -0,0 +1,308 @@
|
||||
#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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[TraitLocation(SystemActors.Player)]
|
||||
public class ResourceMapBotModuleInfo : ConditionalTraitInfo, NotBefore<IResourceLayerInfo>
|
||||
{
|
||||
[Desc("Harvestable and valuable resource types.")]
|
||||
public readonly HashSet<string> ValuableResourceTypes = [];
|
||||
|
||||
[Desc("Tells the AI what types are considered resource creator.")]
|
||||
public readonly HashSet<string> ResourceCreatorTypes = [];
|
||||
|
||||
[Desc($"Actor types that are considered refineries for {nameof(HarvesterTypes)}.")]
|
||||
public readonly HashSet<string> RefineryTypes = [];
|
||||
|
||||
[Desc($"Actor types that are considered harvesters for {nameof(ValuableResourceTypes)}.")]
|
||||
public readonly HashSet<string> HarvesterTypes = [];
|
||||
|
||||
[Desc("Actor types that are considered to be the base building for expansion. Other enemy units will also be recorded",
|
||||
"Defence and production building is suggested")]
|
||||
public readonly HashSet<string> EnemyBaseBuildingTypes = [];
|
||||
|
||||
[Desc("Delay (in ticks) for updating the indicies.")]
|
||||
public readonly int UpdateResourceMapInverval = 67;
|
||||
|
||||
[Desc("The size (in cells) of half of the side length of the indice (in square).")]
|
||||
public readonly int ResourceMapStrideRadius = 12;
|
||||
|
||||
public override object Create(ActorInitializer init) { return new ResourceMapBotModule(init.Self, this); }
|
||||
}
|
||||
|
||||
public class ResourceIndice
|
||||
{
|
||||
public int2 IndiceIndex;
|
||||
public CPos IndiceCenter;
|
||||
public int ResourceCellsCount;
|
||||
public CPos ResourceCellsCenter;
|
||||
public CPos[] ResourceCreatorLocs;
|
||||
public int PlayerRefineryCount;
|
||||
public int PlayerHarvetserCount;
|
||||
|
||||
public int EnemyUnitCount;
|
||||
public int EnemyBaseCount;
|
||||
public int FriendlyBaseCount;
|
||||
public int FriendlyUnitCount;
|
||||
|
||||
public ResourceIndice(int2 indiceIndex, CPos indiceCenter)
|
||||
{
|
||||
IndiceIndex = indiceIndex;
|
||||
IndiceCenter = indiceCenter;
|
||||
ResourceCreatorLocs = [];
|
||||
}
|
||||
}
|
||||
|
||||
public class ResourceMapBotModule : IBotTick
|
||||
{
|
||||
readonly World world;
|
||||
readonly Player player;
|
||||
IResourceLayer resourceLayer;
|
||||
public readonly ResourceMapBotModuleInfo Info;
|
||||
|
||||
ResourceIndice[] resourceMapIndices = null;
|
||||
readonly int indiceSideLength;
|
||||
readonly int indiceResourceScanRadius;
|
||||
int resourceMapIndicesColumnCount;
|
||||
int resourceMapIndicesRowCount;
|
||||
|
||||
int updateResourceMapIndex = 0;
|
||||
int updateResourceMapInterval;
|
||||
bool firstTick = true;
|
||||
|
||||
public ResourceMapBotModule(Actor self, ResourceMapBotModuleInfo info)
|
||||
{
|
||||
world = self.World;
|
||||
player = self.Owner;
|
||||
indiceSideLength = info.ResourceMapStrideRadius << 1;
|
||||
Info = info;
|
||||
|
||||
// FindTilesInAnnulus returns cells in a rough circle shape, and resourceMapIndices are divided in square,
|
||||
// so we need a larger range to cover cells in the indice approximately, but avoid takes too much other indices' cells.
|
||||
indiceResourceScanRadius = info.ResourceMapStrideRadius * 12 / 10; // ≈ * (sqrt(2) + 1) / 2
|
||||
}
|
||||
|
||||
void IBotTick.BotTick(IBot bot)
|
||||
{
|
||||
if (firstTick)
|
||||
{
|
||||
resourceLayer = world.WorldActor.TraitOrDefault<IResourceLayer>();
|
||||
updateResourceMapInterval = world.LocalRandom.Next(Info.UpdateResourceMapInverval, Info.UpdateResourceMapInverval << 1);
|
||||
|
||||
if (resourceMapIndices == null && resourceLayer != null)
|
||||
{
|
||||
var map = world.Map;
|
||||
var actualMapWidth = map.Bounds.Width;
|
||||
var actualMapHeight = map.Bounds.Height;
|
||||
var xoffset = map.Bounds.X;
|
||||
var yoffset = map.Bounds.Y;
|
||||
|
||||
resourceMapIndicesColumnCount = (actualMapWidth + indiceSideLength - 1) / indiceSideLength;
|
||||
resourceMapIndicesRowCount = (actualMapHeight + indiceSideLength - 1) / indiceSideLength;
|
||||
|
||||
var overallIndiceWidth = resourceMapIndicesColumnCount * indiceSideLength;
|
||||
var overallIndiceHeight = resourceMapIndicesRowCount * indiceSideLength;
|
||||
|
||||
xoffset -= (overallIndiceWidth - actualMapWidth) >> 1;
|
||||
yoffset -= (overallIndiceHeight - actualMapHeight) >> 1;
|
||||
|
||||
resourceMapIndices = Exts.MakeArray(resourceMapIndicesColumnCount * resourceMapIndicesRowCount,
|
||||
i => new ResourceIndice(new int2(i % resourceMapIndicesColumnCount, i / resourceMapIndicesColumnCount),
|
||||
new MPos(
|
||||
xoffset + i % resourceMapIndicesColumnCount * indiceSideLength + (indiceSideLength >> 1),
|
||||
yoffset + i / resourceMapIndicesColumnCount * indiceSideLength + (indiceSideLength >> 1)).ToCPos(map)));
|
||||
|
||||
for (var i = 0; i < resourceMapIndices.Length; i++)
|
||||
UpdateResourceMap(i);
|
||||
}
|
||||
|
||||
firstTick = false;
|
||||
}
|
||||
|
||||
if (--updateResourceMapInterval <= 0)
|
||||
{
|
||||
updateResourceMapInterval = Info.UpdateResourceMapInverval;
|
||||
UpdateResourceMap(updateResourceMapIndex);
|
||||
updateResourceMapIndex = (updateResourceMapIndex + 1) % resourceMapIndices.Length;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateResourceMap(int index)
|
||||
{
|
||||
if (resourceLayer == null || resourceMapIndices == null || resourceMapIndices.Length == 0)
|
||||
return;
|
||||
|
||||
var indice = resourceMapIndices[index];
|
||||
var sumCellsX = 0;
|
||||
var sumCellsY = 0;
|
||||
|
||||
var resTiles = world.Map.FindTilesInAnnulus(indice.IndiceCenter, 0, indiceResourceScanRadius).Where(c =>
|
||||
{
|
||||
if (!Info.ValuableResourceTypes.Contains(resourceLayer.GetResource(c).Type))
|
||||
return false;
|
||||
|
||||
sumCellsX += c.X;
|
||||
sumCellsY += c.Y;
|
||||
return true;
|
||||
}).ToList();
|
||||
|
||||
var resTilesCount = resTiles.Count;
|
||||
var bestCell = CPos.Zero;
|
||||
if (resTilesCount != 0)
|
||||
{
|
||||
var resAvgCell = new CPos(sumCellsX / resTilesCount, sumCellsY / resTilesCount);
|
||||
bestCell = resTiles[0];
|
||||
var bestDist = (bestCell - resAvgCell).LengthSquared;
|
||||
foreach (var c in resTiles)
|
||||
{
|
||||
var dist = (c - resAvgCell).LengthSquared;
|
||||
if (dist < bestDist)
|
||||
{
|
||||
bestDist = dist;
|
||||
bestCell = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var refineryCount = 0;
|
||||
var harvesterCount = 0;
|
||||
var normalEnemyCount = 0;
|
||||
var highThreatEnemyCount = 0;
|
||||
|
||||
var resourceCreatorLocs = world.FindActorsInCircle(world.Map.CenterOfCell(indice.IndiceCenter), WDist.FromCells(indiceResourceScanRadius))
|
||||
.Where(a =>
|
||||
{
|
||||
if (a.Owner.RelationshipWith(player) == PlayerRelationship.Enemy)
|
||||
{
|
||||
if (Info.EnemyBaseBuildingTypes.Contains(a.Info.Name))
|
||||
highThreatEnemyCount++;
|
||||
else
|
||||
normalEnemyCount++;
|
||||
}
|
||||
else if (a.Owner.RelationshipWith(player) == PlayerRelationship.Ally)
|
||||
{
|
||||
if (Info.EnemyBaseBuildingTypes.Contains(a.Info.Name))
|
||||
indice.FriendlyBaseCount++;
|
||||
else
|
||||
indice.FriendlyUnitCount++;
|
||||
|
||||
if (a.Owner == player)
|
||||
{
|
||||
if (Info.RefineryTypes.Contains(a.Info.Name))
|
||||
refineryCount++;
|
||||
|
||||
if (Info.HarvesterTypes.Contains(a.Info.Name))
|
||||
harvesterCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return Info.ResourceCreatorTypes.Contains(a.Info.Name);
|
||||
}).Select(a => a.Location).ToArray();
|
||||
|
||||
indice.ResourceCellsCount = resTilesCount;
|
||||
indice.ResourceCellsCenter = bestCell;
|
||||
indice.ResourceCreatorLocs = resourceCreatorLocs;
|
||||
indice.PlayerRefineryCount = refineryCount;
|
||||
indice.PlayerHarvetserCount = harvesterCount;
|
||||
indice.EnemyUnitCount = normalEnemyCount;
|
||||
indice.EnemyBaseCount = highThreatEnemyCount;
|
||||
}
|
||||
|
||||
public int GetIndicesLength()
|
||||
{
|
||||
return resourceMapIndices?.Length ?? 0;
|
||||
}
|
||||
|
||||
public int GetIndiceSideLength()
|
||||
{
|
||||
return indiceSideLength;
|
||||
}
|
||||
|
||||
public int GetIndiceColumnCount()
|
||||
{
|
||||
return resourceMapIndicesColumnCount;
|
||||
}
|
||||
|
||||
public int GetIndiceRowCount()
|
||||
{
|
||||
return resourceMapIndicesRowCount;
|
||||
}
|
||||
|
||||
public int GetIndiceScanRadius()
|
||||
{
|
||||
return indiceResourceScanRadius;
|
||||
}
|
||||
|
||||
public (int IndiceCount, int EnemyUnitCount, int EnemyBaseCount) GetNearbyIndicesThreat(int index)
|
||||
{
|
||||
var baseIndice = resourceMapIndices[index];
|
||||
|
||||
var indiceCount = 0;
|
||||
var nearbyEnemyBase = 0;
|
||||
var nearbyEnemyUnit = 0;
|
||||
|
||||
var x = baseIndice.IndiceIndex.X;
|
||||
var y = baseIndice.IndiceIndex.Y;
|
||||
|
||||
var offsets = new int[] { -1, 0, 1 };
|
||||
|
||||
for (var i = 0; i < offsets.Length; i++)
|
||||
{
|
||||
for (var j = 0; j < offsets.Length; j++)
|
||||
{
|
||||
var offsetIndex = x + offsets[i] + (y + offsets[j]) * resourceMapIndicesColumnCount;
|
||||
if (offsetIndex != index && offsetIndex >= 0 && offsetIndex < resourceMapIndices.Length)
|
||||
{
|
||||
var indice = resourceMapIndices[offsetIndex];
|
||||
nearbyEnemyBase += indice.EnemyBaseCount - indice.FriendlyBaseCount;
|
||||
nearbyEnemyUnit += indice.EnemyUnitCount - indice.FriendlyUnitCount;
|
||||
indiceCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (indiceCount, Math.Max(nearbyEnemyUnit, 0), Math.Max(nearbyEnemyBase, 0));
|
||||
}
|
||||
|
||||
public ResourceIndice GetIndice(int i)
|
||||
{
|
||||
if (resourceMapIndices == null || i >= resourceMapIndices.Length)
|
||||
return null;
|
||||
|
||||
return resourceMapIndices[i];
|
||||
}
|
||||
|
||||
public ResourceIndice FindClosestIndiceFromCPos(CPos cpos)
|
||||
{
|
||||
var maxDist = int.MaxValue;
|
||||
var best = 0;
|
||||
|
||||
for (var i = 0; i < resourceMapIndices.Length; i++)
|
||||
{
|
||||
var index = resourceMapIndices[i];
|
||||
var dist = (index.IndiceCenter - cpos).LengthSquared;
|
||||
if (dist < maxDist)
|
||||
{
|
||||
maxDist = dist;
|
||||
best = i;
|
||||
}
|
||||
}
|
||||
|
||||
return GetIndice(best);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user