Fix bug that AI producion pause when there is too many unit in UnitDelays

This commit is contained in:
dnqbob
2023-05-28 21:22:42 +08:00
committed by Gustas
parent 085a4c421b
commit eab0bf8f82

View File

@@ -22,8 +22,9 @@ namespace OpenRA.Mods.Common.Traits
// TODO: Investigate whether this might the (or at least one) reason why bots occasionally get into a state of doing nothing. // TODO: Investigate whether this might the (or at least one) reason why bots occasionally get into a state of doing nothing.
// Reason: If this is less than SquadSize, the bot might get stuck between not producing more units due to this, // Reason: If this is less than SquadSize, the bot might get stuck between not producing more units due to this,
// but also not creating squads since there aren't enough idle units. // but also not creating squads since there aren't enough idle units.
[Desc("Only produce units as long as there are less than this amount of units idling inside the base.")] [Desc("If > 0, only produce units as long as there are less than this amount of units idling inside the base.",
public readonly int IdleBaseUnitsMaximum = 12; "Beware: if it is less than squad size, e.g. the `SquadSize` from `SquadManagerBotModule`, the bot might get stuck as there aren't enough idle units to create squad.")]
public readonly int IdleBaseUnitsMaximum = -1;
[Desc("Production queues AI uses for producing units.")] [Desc("Production queues AI uses for producing units.")]
public readonly string[] UnitQueues = { "Vehicle", "Infantry", "Plane", "Ship", "Aircraft" }; public readonly string[] UnitQueues = { "Vehicle", "Infantry", "Plane", "Ship", "Aircraft" };
@@ -94,17 +95,20 @@ namespace OpenRA.Mods.Common.Traits
queuedBuildRequests.Remove(buildRequest); queuedBuildRequests.Remove(buildRequest);
} }
for (var i = 0; i < Info.UnitQueues.Length; i++) if (Info.IdleBaseUnitsMaximum <= 0 || Info.IdleBaseUnitsMaximum > idleUnitCount)
{ {
if (++currentQueueIndex >= Info.UnitQueues.Length) for (var i = 0; i < Info.UnitQueues.Length; i++)
currentQueueIndex = 0;
if (AIUtils.FindQueues(player, Info.UnitQueues[currentQueueIndex]).Any())
{ {
// PERF: We tick only one type of valid queue at a time if (++currentQueueIndex >= Info.UnitQueues.Length)
// if AI gets enough cash, it can fill all of its queues with enough ticks currentQueueIndex = 0;
BuildUnit(bot, Info.UnitQueues[currentQueueIndex], idleUnitCount < Info.IdleBaseUnitsMaximum);
break; if (AIUtils.FindQueues(player, Info.UnitQueues[currentQueueIndex]).Any())
{
// PERF: We tick only one type of valid queue at a time
// if AI gets enough cash, it can fill all of its queues with enough ticks
BuildRandomUnit(bot, Info.UnitQueues[currentQueueIndex]);
break;
}
} }
} }
} }
@@ -120,36 +124,22 @@ namespace OpenRA.Mods.Common.Traits
return queuedBuildRequests.Count(r => r == requestedActor); return queuedBuildRequests.Count(r => r == requestedActor);
} }
void BuildUnit(IBot bot, string category, bool buildRandom) void BuildRandomUnit(IBot bot, string category)
{ {
if (Info.UnitsToBuild.Count == 0)
return;
// Pick a free queue // Pick a free queue
var queue = AIUtils.FindQueues(player, category).FirstOrDefault(q => !q.AllQueued().Any()); var queue = AIUtils.FindQueues(player, category).FirstOrDefault(q => !q.AllQueued().Any());
if (queue == null) if (queue == null)
return; return;
var unit = buildRandom ? var unit = ChooseRandomUnitToBuild(queue);
ChooseRandomUnitToBuild(queue) :
ChooseUnitToBuild(queue);
if (unit == null) if (unit == null)
return; return;
var name = unit.Name; bot.QueueOrder(Order.StartProduction(queue.Actor, unit.Name, 1));
if (Info.UnitsToBuild != null && !Info.UnitsToBuild.ContainsKey(name))
return;
if (Info.UnitDelays != null &&
Info.UnitDelays.TryGetValue(name, out var delay) &&
delay > world.WorldTick)
return;
if (Info.UnitLimits != null &&
Info.UnitLimits.TryGetValue(name, out var limit) &&
world.Actors.Count(a => a.Owner == player && a.Info.Name == name) >= limit)
return;
bot.QueueOrder(Order.StartProduction(queue.Actor, name, 1));
} }
// In cases where we want to build a specific unit but don't know the queue name (because there's more than one possibility) // In cases where we want to build a specific unit but don't know the queue name (because there's more than one possibility)
@@ -180,30 +170,35 @@ namespace OpenRA.Mods.Common.Traits
ActorInfo ChooseRandomUnitToBuild(ProductionQueue queue) ActorInfo ChooseRandomUnitToBuild(ProductionQueue queue)
{ {
var buildableThings = queue.BuildableItems(); var buildableThings = queue.BuildableItems().Shuffle(world.LocalRandom).ToArray();
var unit = buildableThings.RandomOrDefault(world.LocalRandom); if (buildableThings.Length == 0)
return unit != null && HasAdequateAirUnitReloadBuildings(unit) ? unit : null;
}
ActorInfo ChooseUnitToBuild(ProductionQueue queue)
{
var buildableThings = queue.BuildableItems().Select(b => b.Name).ToHashSet();
if (buildableThings.Count == 0)
return null; return null;
var myUnits = player.World var allUnits = world.Actors.Where(a => a.Owner == player && Info.UnitsToBuild.ContainsKey(a.Info.Name) && !a.IsDead).ToArray();
.ActorsHavingTrait<IPositionable>()
.Where(a => a.Owner == player)
.Select(a => a.Info.Name)
.ToList();
foreach (var unit in Info.UnitsToBuild.Shuffle(world.LocalRandom)) ActorInfo desiredUnit = null;
if (buildableThings.Contains(unit.Key)) var desiredError = int.MaxValue;
if (myUnits.Count(a => a == unit.Key) * 100 < unit.Value * myUnits.Count) foreach (var unit in buildableThings)
if (HasAdequateAirUnitReloadBuildings(world.Map.Rules.Actors[unit.Key])) {
return world.Map.Rules.Actors[unit.Key]; if (!Info.UnitsToBuild.ContainsKey(unit.Name) || (Info.UnitDelays != null && Info.UnitDelays.TryGetValue(unit.Name, out var delay) && delay > world.WorldTick))
continue;
return null; var unitCount = allUnits.Count(a => a.Info.Name == unit.Name);
if (Info.UnitLimits != null && Info.UnitLimits.TryGetValue(unit.Name, out var count) && unitCount >= count)
continue;
var error = allUnits.Length > 0 ? unitCount * 100 / allUnits.Length - Info.UnitsToBuild[unit.Name] : -1;
if (error < 0)
return HasAdequateAirUnitReloadBuildings(unit) ? unit : null;
if (error < desiredError)
{
desiredError = error;
desiredUnit = unit;
}
}
return desiredUnit != null ? (HasAdequateAirUnitReloadBuildings(desiredUnit) ? desiredUnit : null) : null;
} }
// For mods like RA (number of RearmActors must match the number of aircraft) // For mods like RA (number of RearmActors must match the number of aircraft)