Skip to content

Commit

Permalink
Fix bug that AI producion pause when there is too many unit in UnitDe…
Browse files Browse the repository at this point in the history
…lays
  • Loading branch information
dnqbob authored and PunkPun committed Sep 9, 2023
1 parent 085a4c4 commit eab0bf8
Showing 1 changed file with 46 additions and 51 deletions.
97 changes: 46 additions & 51 deletions OpenRA.Mods.Common/Traits/BotModules/UnitBuilderBotModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ public class UnitBuilderBotModuleInfo : ConditionalTraitInfo
// 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,
// 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.")]
public readonly int IdleBaseUnitsMaximum = 12;
[Desc("If > 0, only produce units as long as there are less than this amount of units idling inside the base.",
"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.")]
public readonly string[] UnitQueues = { "Vehicle", "Infantry", "Plane", "Ship", "Aircraft" };
Expand Down Expand Up @@ -94,17 +95,20 @@ void IBotTick.BotTick(IBot bot)
queuedBuildRequests.Remove(buildRequest);
}

for (var i = 0; i < Info.UnitQueues.Length; i++)
if (Info.IdleBaseUnitsMaximum <= 0 || Info.IdleBaseUnitsMaximum > idleUnitCount)
{
if (++currentQueueIndex >= Info.UnitQueues.Length)
currentQueueIndex = 0;

if (AIUtils.FindQueues(player, Info.UnitQueues[currentQueueIndex]).Any())
for (var i = 0; i < Info.UnitQueues.Length; i++)
{
// 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
BuildUnit(bot, Info.UnitQueues[currentQueueIndex], idleUnitCount < Info.IdleBaseUnitsMaximum);
break;
if (++currentQueueIndex >= Info.UnitQueues.Length)
currentQueueIndex = 0;

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;
}
}
}
}
Expand All @@ -120,36 +124,22 @@ int IBotRequestUnitProduction.RequestedProductionCount(IBot bot, string requeste
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
var queue = AIUtils.FindQueues(player, category).FirstOrDefault(q => !q.AllQueued().Any());
if (queue == null)
return;

var unit = buildRandom ?
ChooseRandomUnitToBuild(queue) :
ChooseUnitToBuild(queue);
var unit = ChooseRandomUnitToBuild(queue);

if (unit == null)
return;

var name = unit.Name;

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));
bot.QueueOrder(Order.StartProduction(queue.Actor, unit.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)
Expand Down Expand Up @@ -180,30 +170,35 @@ void BuildUnit(IBot bot, string name)

ActorInfo ChooseRandomUnitToBuild(ProductionQueue queue)
{
var buildableThings = queue.BuildableItems();
var unit = buildableThings.RandomOrDefault(world.LocalRandom);
return unit != null && HasAdequateAirUnitReloadBuildings(unit) ? unit : null;
}

ActorInfo ChooseUnitToBuild(ProductionQueue queue)
{
var buildableThings = queue.BuildableItems().Select(b => b.Name).ToHashSet();
if (buildableThings.Count == 0)
var buildableThings = queue.BuildableItems().Shuffle(world.LocalRandom).ToArray();
if (buildableThings.Length == 0)
return null;

var myUnits = player.World
.ActorsHavingTrait<IPositionable>()
.Where(a => a.Owner == player)
.Select(a => a.Info.Name)
.ToList();
var allUnits = world.Actors.Where(a => a.Owner == player && Info.UnitsToBuild.ContainsKey(a.Info.Name) && !a.IsDead).ToArray();

ActorInfo desiredUnit = null;
var desiredError = int.MaxValue;
foreach (var unit in buildableThings)
{
if (!Info.UnitsToBuild.ContainsKey(unit.Name) || (Info.UnitDelays != null && Info.UnitDelays.TryGetValue(unit.Name, out var delay) && delay > world.WorldTick))
continue;

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;

foreach (var unit in Info.UnitsToBuild.Shuffle(world.LocalRandom))
if (buildableThings.Contains(unit.Key))
if (myUnits.Count(a => a == unit.Key) * 100 < unit.Value * myUnits.Count)
if (HasAdequateAirUnitReloadBuildings(world.Map.Rules.Actors[unit.Key]))
return world.Map.Rules.Actors[unit.Key];
if (error < desiredError)
{
desiredError = error;
desiredUnit = unit;
}
}

return null;
return desiredUnit != null ? (HasAdequateAirUnitReloadBuildings(desiredUnit) ? desiredUnit : null) : null;
}

// For mods like RA (number of RearmActors must match the number of aircraft)
Expand Down

0 comments on commit eab0bf8

Please sign in to comment.