Files
OpenRA/OpenRA.Mods.Common/Traits/Player/ModularBot.cs
2024-10-04 15:11:27 +03:00

123 lines
3.3 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;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Support;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[Desc("Bot that uses BotModules.")]
[TraitLocation(SystemActors.Player)]
public sealed class ModularBotInfo : TraitInfo, IBotInfo
{
[FieldLoader.Require]
[Desc("Internal id for this bot.")]
public readonly string Type = null;
[FluentReference]
[Desc("Human-readable name this bot uses.")]
public readonly string Name = null;
[Desc("Minimum portion of pending orders to issue each tick (e.g. 5 issues at least 1/5th of all pending orders). " +
"Excess orders remain queued for subsequent ticks.")]
public readonly int MinOrderQuotientPerTick = 5;
string IBotInfo.Type => Type;
string IBotInfo.Name => Name;
public override object Create(ActorInitializer init) { return new ModularBot(this, init); }
}
public sealed class ModularBot : ITick, IBot, INotifyDamage
{
public bool IsEnabled;
readonly ModularBotInfo info;
readonly World world;
readonly Queue<Order> orders = new();
Player player;
IBotTick[] tickModules;
IBotRespondToAttack[] attackResponseModules;
IBotInfo IBot.Info => info;
Player IBot.Player => player;
public ModularBot(ModularBotInfo info, ActorInitializer init)
{
this.info = info;
world = init.World;
}
// Called by the host's player creation code
public void Activate(Player p)
{
// Bot logic is not allowed to affect world state, and can only act by issuing orders
// These orders are recorded in the replay, so bots shouldn't be enabled during replays
if (p.World.IsReplay)
return;
IsEnabled = true;
player = p;
tickModules = p.PlayerActor.TraitsImplementing<IBotTick>().ToArray();
attackResponseModules = p.PlayerActor.TraitsImplementing<IBotRespondToAttack>().ToArray();
foreach (var ibe in p.PlayerActor.TraitsImplementing<IBotEnabled>())
ibe.BotEnabled(this);
}
void IBot.QueueOrder(Order order)
{
orders.Enqueue(order);
}
void ITick.Tick(Actor self)
{
if (!IsEnabled || self.World.IsLoadingGameSave)
return;
using (new PerfSample("bot_tick"))
{
Sync.RunUnsynced(Game.Settings.Debug.SyncCheckBotModuleCode, world, () =>
{
foreach (var t in tickModules)
if (t.IsTraitEnabled())
t.BotTick(this);
});
}
var ordersToIssueThisTick = Math.Min((orders.Count + info.MinOrderQuotientPerTick - 1) / info.MinOrderQuotientPerTick, orders.Count);
for (var i = 0; i < ordersToIssueThisTick; i++)
world.IssueOrder(orders.Dequeue());
}
void INotifyDamage.Damaged(Actor self, AttackInfo e)
{
if (!IsEnabled || self.World.IsLoadingGameSave)
return;
using (new PerfSample("bot_attack_response"))
{
Sync.RunUnsynced(Game.Settings.Debug.SyncCheckBotModuleCode, world, () =>
{
foreach (var t in attackResponseModules)
if (t.IsTraitEnabled())
t.RespondToAttack(this, self, e);
});
}
}
}
}