RoundManager.BotMatch.cs
using Sandbox;
using System;
using System.Collections.Generic;
using System.Linq;
/// <summary>
/// BOT 7H - full competitive match integration for one host-owned bot.
/// RoundManager remains authoritative for scores, timers, loadouts and rules;
/// this partial only supplies the bot's decisions when the bot owns that choice.
/// </summary>
public sealed partial class RoundManager
{
[Property, Sync( SyncFlags.FromHost ), Group( "Match / Bot" )]
public bool MatchBotEnabled { get; set; }
[Property, Sync( SyncFlags.FromHost ), Group( "Match / Bot" )]
public OMRBotDifficulty MatchBotDifficulty { get; set; } = OMRBotDifficulty.Hard;
[Property, Sync( SyncFlags.FromHost ), Group( "Match / Bot" )]
public string MatchBotName { get; set; } = "ROOK";
[Property, Group( "Match / Bot" ), Range( 0.15f, 2.5f )]
public float MatchBotDecisionDelay { get; set; } = 0.65f;
private RoundPhase _botDecisionPhase = (RoundPhase)(-1);
private int _botDecisionRound = -1;
private float _botDecisionAt = -1f;
public void SetMatchBotDifficulty( OMRBotDifficulty difficulty )
{
if ( !Networking.IsHost )
return;
MatchBotDifficulty = difficulty;
GetBotManager()?.SyncNow();
}
public void SetMatchBotEnabled( bool enabled )
{
if ( !Networking.IsHost )
return;
MatchBotEnabled = enabled;
ResetBotMatchDecisionState();
GetBotManager()?.SyncNow();
}
private void ResetBotMatchDecisionState()
{
_botDecisionPhase = (RoundPhase)(-1);
_botDecisionRound = -1;
_botDecisionAt = -1f;
}
private bool TryHandleBotWinnerDecision( List<PlayerState> players )
{
if ( !MatchBotEnabled || PotOwnerTeam < 0 )
return false;
PlayerState bot = players.FirstOrDefault( x => x.IsBot && x.TeamId == PotOwnerTeam );
if ( bot is null )
return false;
OMRBotController controller = bot.Components.Get<OMRBotController>( true );
if ( controller is null )
return false;
if ( !IsBotDecisionReady( RoundPhase.WinnerDecision ) )
return true;
OMRRoundRuleProfile ruleA = ResolveRoundRule( ModifierOptionA );
OMRRoundRuleProfile ruleB = ResolveRoundRule( ModifierOptionB );
GameObject weaponA = ResolveModifierWeaponPreviewPrefabHost( ModifierOptionA );
GameObject weaponB = ResolveModifierWeaponPreviewPrefabHost( ModifierOptionB );
int opponentTeam = GetOpponentTeam( PotOwnerTeam );
bool bank = controller.DecideBankForMatch(
GetBankedScore( PotOwnerTeam ),
GetBankedScore( opponentTeam ),
CurrentPot,
GetNextRiskPot(),
TargetScore,
ModifierOptionA, ruleA, weaponA,
ModifierOptionB, ruleB, weaponB
);
_botDecisionAt = float.PositiveInfinity;
HandleWinnerDecision( PotOwnerTeam, bank, players );
return true;
}
private bool TryHandleBotModifierDecision( List<PlayerState> players )
{
if ( !MatchBotEnabled || PotOwnerTeam < 0 )
return false;
int chooserTeam = GetOpponentTeam( PotOwnerTeam );
PlayerState bot = players.FirstOrDefault( x => x.IsBot && x.TeamId == chooserTeam );
if ( bot is null )
return false;
OMRBotController controller = bot.Components.Get<OMRBotController>( true );
if ( controller is null )
return false;
if ( !IsBotDecisionReady( RoundPhase.ModifierDecision ) )
return true;
OMRRoundRuleProfile ruleA = ResolveRoundRule( ModifierOptionA );
OMRRoundRuleProfile ruleB = ResolveRoundRule( ModifierOptionB );
GameObject weaponA = ResolveModifierWeaponPreviewPrefabHost( ModifierOptionA );
GameObject weaponB = ResolveModifierWeaponPreviewPrefabHost( ModifierOptionB );
RoundModifier choice = controller.ChooseModifierForMatch(
ModifierOptionA, ruleA, weaponA,
ModifierOptionB, ruleB, weaponB
);
_botDecisionAt = float.PositiveInfinity;
HandleModifierDecision( chooserTeam, choice, players );
return true;
}
private bool IsBotDecisionReady( RoundPhase phase )
{
if ( _botDecisionPhase != phase || _botDecisionRound != RoundNumber )
{
_botDecisionPhase = phase;
_botDecisionRound = RoundNumber;
float delay = MathF.Max( 0.15f, MatchBotDecisionDelay );
delay *= MatchBotDifficulty switch
{
OMRBotDifficulty.Easy => 1.25f,
OMRBotDifficulty.Hard => 0.82f,
_ => 1f
};
_botDecisionAt = Time.Now + delay + Game.Random.Float( 0.05f, 0.22f );
return false;
}
return Time.Now >= _botDecisionAt || ( DecisionEndsAt > 0f && Time.Now >= DecisionEndsAt - 0.10f );
}
private GameObject ResolveModifierWeaponPreviewPrefabHost( RoundModifier modifier )
{
OMRRoundRuleProfile rule = ResolveRoundRule( modifier );
OMRRoundLoadoutDirector loadouts = GetRoundLoadoutDirector();
GameObject preview = loadouts?.PreviewRoundWeapon( rule );
if ( preview is not null && preview.IsValid )
return preview;
return rule?.ResolveFixedWeaponPrefab();
}
}