OMRBotBrainModel.cs
using Sandbox;
using System;
using System.Collections.Generic;
/// <summary>
/// BOT 7 intent vocabulary.
///
/// The active utility controller scores these intents while the tactical and
/// weapon execution layers enforce legality. Keeping the vocabulary explicit
/// lets decisions be inspected,
/// logged and compared instead of disappearing into a chain of unrelated ifs.
/// </summary>
public enum OMRBotIntentKind
{
None,
Engage,
Press,
OpenDistance,
TakeCover,
Reposition,
FlankLeft,
FlankRight,
HoldAmbush,
Suppress,
ReloadSafely,
Search,
Intercept,
Disengage,
RecoverTraversal,
HighNoon
}
/// <summary>
/// Compact description of how much trustworthy contact the bot currently has.
/// This deliberately separates current vision from remembered/inferred evidence.
/// </summary>
public enum OMRBotContactState
{
None,
Reacting,
Visual,
VisualMemory,
Audio,
ThreatDirection,
Belief
}
/// <summary>
/// Readable snapshot of the lightweight BOT 6 opponent model. This is input to
/// future decisions, never a source of hidden live player information.
/// </summary>
public sealed class OMRBotOpponentModelSnapshot
{
public int Samples { get; internal set; }
public float Confidence { get; internal set; }
public float Aggression { get; internal set; } = 0.50f;
public float Mobility { get; internal set; } = 0.50f;
public float AirUse { get; internal set; } = 0.10f;
public float CrouchUse { get; internal set; } = 0.10f;
public float PreferredDistance { get; internal set; } = 0.50f;
public float RoutePredictionConfidence { get; internal set; }
public float RepeekTendency { get; internal set; } = 0.25f;
public float PushTendency { get; internal set; } = 0.50f;
public string Summary { get; internal set; } = "LEARNING";
}
/// <summary>
/// BOT 7 shared blackboard.
///
/// This object contains only information the existing bot already knows or can
/// fairly observe. BOT 7A does not use it to alter behavior yet; it is a stable
/// handoff point between perception/memory and the future utility brain.
/// </summary>
public sealed class OMRBotBlackboard
{
public float UpdatedAt { get; internal set; }
public OMRBotDifficulty Difficulty { get; internal set; }
public OMRBotBehaviorMode BehaviorMode { get; internal set; }
public Vector3 BotPosition { get; internal set; }
public Vector3 BotVelocity { get; internal set; }
public float OwnHealthFraction { get; internal set; } = 1f;
public float OpponentHealthFraction { get; internal set; } = 1f;
public bool KnowsOpponentHealth { get; internal set; }
public OMRBotContactState ContactState { get; internal set; } = OMRBotContactState.None;
public bool HasConfirmedSight { get; internal set; }
public bool HasVisualMemory { get; internal set; }
public bool HasActionableAudio { get; internal set; }
public bool HasRecentThreat { get; internal set; }
public bool HasOpponentBelief { get; internal set; }
public Vector3 OpponentBeliefPosition { get; internal set; }
public float OpponentBeliefConfidence { get; internal set; }
public bool TargetAlive { get; internal set; }
public string TargetName { get; internal set; } = "NONE";
public float TargetDistance { get; internal set; }
public Vector3 ObservedTargetPosition { get; internal set; }
public Vector3 ObservedTargetAimPoint { get; internal set; }
public Vector3 LastKnownPosition { get; internal set; }
public float LastSeenAge { get; internal set; } = -1f;
public Vector3 LastSeenVelocity { get; internal set; }
public Vector3 LastSeenDirection { get; internal set; }
public Vector3 LastHeardPosition { get; internal set; }
public float LastHeardAge { get; internal set; } = -1f;
public float LastHeardConfidence { get; internal set; }
public string LastHeardKind { get; internal set; } = string.Empty;
public Vector3 LastThreatDirection { get; internal set; }
public float ThreatAge { get; internal set; } = -1f;
public bool HasCoverPlan { get; internal set; }
public bool HasFlankPlan { get; internal set; }
public bool HasSearchPlan { get; internal set; }
public bool IsSuppressing { get; internal set; }
public float TacticalAggression { get; internal set; } = 0.50f;
public string LegacyIntent { get; internal set; } = string.Empty;
public OMRBotWeaponDoctrine Weapon { get; internal set; } = OMRBotWeaponDoctrine.Empty;
public OMRBotWeaponDoctrine OpponentWeapon { get; internal set; } = OMRBotWeaponDoctrine.Empty;
public bool KnowsOpponentWeapon { get; internal set; }
public OMRBotMatchupContext Matchup { get; internal set; } = OMRBotMatchupContext.Neutral;
public OMRBotRuleContext Rule { get; internal set; } = OMRBotRuleContext.Standard;
public OMRBotOpponentModelSnapshot OpponentModel { get; } = new();
public void Reset()
{
UpdatedAt = 0f;
Difficulty = OMRBotDifficulty.Normal;
BehaviorMode = OMRBotBehaviorMode.Stationary;
BotPosition = Vector3.Zero;
BotVelocity = Vector3.Zero;
OwnHealthFraction = 1f;
OpponentHealthFraction = 1f;
KnowsOpponentHealth = false;
ContactState = OMRBotContactState.None;
HasConfirmedSight = false;
HasVisualMemory = false;
HasActionableAudio = false;
HasRecentThreat = false;
HasOpponentBelief = false;
OpponentBeliefPosition = Vector3.Zero;
OpponentBeliefConfidence = 0f;
TargetAlive = false;
TargetName = "NONE";
TargetDistance = 0f;
ObservedTargetPosition = Vector3.Zero;
ObservedTargetAimPoint = Vector3.Zero;
LastKnownPosition = Vector3.Zero;
LastSeenAge = -1f;
LastSeenVelocity = Vector3.Zero;
LastSeenDirection = Vector3.Zero;
LastHeardPosition = Vector3.Zero;
LastHeardAge = -1f;
LastHeardConfidence = 0f;
LastHeardKind = string.Empty;
LastThreatDirection = Vector3.Zero;
ThreatAge = -1f;
HasCoverPlan = false;
HasFlankPlan = false;
HasSearchPlan = false;
IsSuppressing = false;
TacticalAggression = 0.50f;
LegacyIntent = string.Empty;
Weapon = OMRBotWeaponDoctrine.Empty;
OpponentWeapon = OMRBotWeaponDoctrine.Empty;
KnowsOpponentWeapon = false;
Matchup = OMRBotMatchupContext.Neutral;
Rule = OMRBotRuleContext.Standard;
OpponentModel.Samples = 0;
OpponentModel.Confidence = 0f;
OpponentModel.Aggression = 0.50f;
OpponentModel.Mobility = 0.50f;
OpponentModel.AirUse = 0.10f;
OpponentModel.CrouchUse = 0.10f;
OpponentModel.PreferredDistance = 0.50f;
OpponentModel.RoutePredictionConfidence = 0f;
OpponentModel.RepeekTendency = 0.25f;
OpponentModel.PushTendency = 0.50f;
OpponentModel.Summary = "LEARNING";
}
}
/// <summary>
/// One utility option populated and selected by the active BOT 7D decision layer.
/// </summary>
public sealed class OMRBotDecisionCandidate
{
public OMRBotIntentKind Intent { get; set; }
public float Score { get; set; }
public bool Legal { get; set; } = true;
public string Reason { get; set; } = string.Empty;
public Vector3 TargetPosition { get; set; }
public bool HasTargetPosition { get; set; }
}
/// <summary>
/// Reusable per-decision scratch frame. It intentionally owns a stable list so
/// later utility evaluation can avoid allocating a new candidate collection every
/// update while remaining easy to inspect in diagnostics.
/// </summary>
public sealed class OMRBotDecisionFrame
{
private readonly List<OMRBotDecisionCandidate> _candidates = new();
private readonly List<OMRBotDecisionCandidate> _candidatePool = new();
public IReadOnlyList<OMRBotDecisionCandidate> Candidates => _candidates;
public OMRBotIntentKind SelectedIntent { get; internal set; } = OMRBotIntentKind.None;
public float SelectedScore { get; internal set; }
public string SelectionReason { get; internal set; } = string.Empty;
public void Clear()
{
_candidates.Clear();
SelectedIntent = OMRBotIntentKind.None;
SelectedScore = 0f;
SelectionReason = string.Empty;
}
// Returned candidates are mutable scratch data; never retain them across Clear().
public OMRBotDecisionCandidate RentCandidate()
{
int index = _candidates.Count;
if ( index >= _candidatePool.Count ) _candidatePool.Add( new OMRBotDecisionCandidate() );
OMRBotDecisionCandidate candidate = _candidatePool[index];
_candidates.Add( candidate );
return candidate;
}
}