237 lines
8.8 KiB
C#
237 lines
8.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using Microsoft.Xna.Framework;
|
|
|
|
namespace Barotrauma
|
|
{
|
|
abstract class AIObjectiveLoop<T> : AIObjective
|
|
{
|
|
public HashSet<T> Targets { get; private set; } = new HashSet<T>();
|
|
public Dictionary<T, AIObjective> Objectives { get; private set; } = new Dictionary<T, AIObjective>();
|
|
protected HashSet<T> ignoreList = new HashSet<T>();
|
|
private float ignoreListTimer;
|
|
protected float targetUpdateTimer;
|
|
protected virtual float TargetUpdateTimeMultiplier { get; } = 1;
|
|
|
|
private float syncTimer;
|
|
private readonly float syncTime = 1;
|
|
|
|
// By default, doesn't clear the list automatically
|
|
protected virtual float IgnoreListClearInterval => 0;
|
|
|
|
public HashSet<T> ReportedTargets { get; private set; } = new HashSet<T>();
|
|
|
|
public bool AddTarget(T target)
|
|
{
|
|
if (character.IsDead) { return false; }
|
|
if (ReportedTargets.Contains(target))
|
|
{
|
|
return false;
|
|
}
|
|
if (Filter(target))
|
|
{
|
|
ReportedTargets.Add(target);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public AIObjectiveLoop(Character character, AIObjectiveManager objectiveManager, float priorityModifier, Identifier option = default)
|
|
: base(character, objectiveManager, priorityModifier, option) { }
|
|
|
|
protected override void Act(float deltaTime) { }
|
|
protected override bool CheckObjectiveSpecific() => false;
|
|
public override bool CanBeCompleted => true;
|
|
public override bool AbandonWhenCannotCompleteSubjectives => false;
|
|
public override bool AllowSubObjectiveSorting => true;
|
|
public override bool AllowWhileHandcuffed => false;
|
|
|
|
public virtual bool InverseTargetEvaluation => false;
|
|
protected virtual bool ResetWhenClearingIgnoreList => true;
|
|
protected virtual bool ForceOrderPriority => true;
|
|
|
|
protected virtual int MaxTargets => int.MaxValue;
|
|
|
|
public override bool IsLoop { get => true; set => throw new Exception("Trying to set the value for IsLoop from: " + System.Environment.StackTrace.CleanupStackTrace()); }
|
|
|
|
public override void Update(float deltaTime)
|
|
{
|
|
base.Update(deltaTime);
|
|
if (IgnoreListClearInterval > 0)
|
|
{
|
|
if (ignoreListTimer > IgnoreListClearInterval)
|
|
{
|
|
if (ResetWhenClearingIgnoreList)
|
|
{
|
|
Reset();
|
|
}
|
|
else
|
|
{
|
|
ignoreList.Clear();
|
|
ignoreListTimer = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ignoreListTimer += deltaTime;
|
|
}
|
|
}
|
|
if (targetUpdateTimer <= 0)
|
|
{
|
|
UpdateTargets();
|
|
}
|
|
else
|
|
{
|
|
targetUpdateTimer -= deltaTime;
|
|
}
|
|
if (syncTimer <= 0)
|
|
{
|
|
syncTimer = Math.Min(syncTime * Rand.Range(0.9f, 1.1f), targetUpdateTimer);
|
|
// Sync objectives, subobjectives and targets
|
|
foreach (var objective in Objectives)
|
|
{
|
|
var target = objective.Key;
|
|
if (!Targets.Contains(target))
|
|
{
|
|
subObjectives.Remove(objective.Value);
|
|
}
|
|
}
|
|
SyncRemovedObjectives(Objectives, GetList());
|
|
}
|
|
else
|
|
{
|
|
syncTimer -= deltaTime;
|
|
}
|
|
}
|
|
|
|
// the timer is set between 1 and 10 seconds, depending on the priority modifier and a random +-25%
|
|
private float CalculateTargetUpdateTimer() => targetUpdateTimer = 1 / MathHelper.Clamp(PriorityModifier * Rand.Range(0.75f, 1.25f), 0.1f, 1) * TargetUpdateTimeMultiplier;
|
|
|
|
public override void Reset()
|
|
{
|
|
base.Reset();
|
|
ignoreList.Clear();
|
|
ignoreListTimer = 0;
|
|
UpdateTargets();
|
|
}
|
|
|
|
protected override float GetPriority()
|
|
{
|
|
if (!IsAllowed)
|
|
{
|
|
HandleNonAllowed();
|
|
return Priority;
|
|
}
|
|
// Allow the target value to be more than 100.
|
|
float targetValue = TargetEvaluation();
|
|
if (InverseTargetEvaluation)
|
|
{
|
|
targetValue = 100 - targetValue;
|
|
}
|
|
var currentSubObjective = CurrentSubObjective;
|
|
if (currentSubObjective != null && currentSubObjective.Priority > targetValue)
|
|
{
|
|
// If the priority is higher than the target value, let's just use it.
|
|
// The priority calculation is more precise, but it takes into account things like distances,
|
|
// so it's better not to use it if it's lower than the rougher targetValue.
|
|
targetValue = currentSubObjective.Priority;
|
|
}
|
|
// If the target value is less than 1% of the max value, let's just treat it as zero.
|
|
if (targetValue < 1)
|
|
{
|
|
Priority = 0;
|
|
}
|
|
else
|
|
{
|
|
if (objectiveManager.IsOrder(this))
|
|
{
|
|
Priority = ForceOrderPriority ? objectiveManager.GetOrderPriority(this) : targetValue;
|
|
}
|
|
else
|
|
{
|
|
float max = AIObjectiveManager.LowestOrderPriority - 1;
|
|
if (this is AIObjectiveRescueAll rescueObjective && rescueObjective.Targets.Contains(character))
|
|
{
|
|
// Allow higher prio
|
|
max = AIObjectiveManager.EmergencyObjectivePriority;
|
|
}
|
|
float value = MathHelper.Clamp((CumulatedDevotion + (targetValue * PriorityModifier)) / 100, 0, 1);
|
|
Priority = MathHelper.Lerp(0, max, value);
|
|
}
|
|
}
|
|
return Priority;
|
|
}
|
|
|
|
protected void UpdateTargets()
|
|
{
|
|
CalculateTargetUpdateTimer();
|
|
Targets.Clear();
|
|
FindTargets();
|
|
CreateObjectives();
|
|
}
|
|
|
|
protected virtual void FindTargets()
|
|
{
|
|
foreach (T target in GetList())
|
|
{
|
|
// The bots always find targets when the objective is an order.
|
|
if (!objectiveManager.IsOrder(this))
|
|
{
|
|
// Battery or pump states cannot currently be reported (not implemented) and therefore we must ignore them -> the bots always know if they require attention.
|
|
bool ignore = this is AIObjectiveChargeBatteries || this is AIObjectivePumpWater || this is AIObjectiveFindThieves;
|
|
if (!ignore && !ReportedTargets.Contains(target)) { continue; }
|
|
}
|
|
if (!Filter(target)) { continue; }
|
|
if (!ignoreList.Contains(target))
|
|
{
|
|
Targets.Add(target);
|
|
if (Targets.Count > MaxTargets)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protected virtual void CreateObjectives()
|
|
{
|
|
foreach (T target in Targets)
|
|
{
|
|
if (ignoreList.Contains(target)) { continue; }
|
|
if (!Objectives.TryGetValue(target, out AIObjective objective))
|
|
{
|
|
objective = ObjectiveConstructor(target);
|
|
Objectives.Add(target, objective);
|
|
if (!subObjectives.Contains(objective))
|
|
{
|
|
subObjectives.Add(objective);
|
|
}
|
|
objective.Completed += () =>
|
|
{
|
|
Objectives.Remove(target);
|
|
OnObjectiveCompleted(objective, target);
|
|
};
|
|
objective.Abandoned += () =>
|
|
{
|
|
Objectives.Remove(target);
|
|
ignoreList.Add(target);
|
|
targetUpdateTimer = Math.Min(0.1f, targetUpdateTimer);
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
protected abstract void OnObjectiveCompleted(AIObjective objective, T target);
|
|
|
|
/// <summary>
|
|
/// List of all possible items of the specified type. Used for filtering the removed objectives.
|
|
/// </summary>
|
|
protected abstract IEnumerable<T> GetList();
|
|
|
|
protected abstract float TargetEvaluation();
|
|
|
|
protected abstract AIObjective ObjectiveConstructor(T target);
|
|
protected abstract bool Filter(T target);
|
|
}
|
|
}
|