Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindThieves.cs
2024-06-18 16:50:02 +03:00

204 lines
8.9 KiB
C#

#nullable enable
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Items.Components;
namespace Barotrauma
{
class AIObjectiveFindThieves : AIObjectiveLoop<Character>
{
public override Identifier Identifier { get; set; } = "find thieves".ToIdentifier();
protected override float IgnoreListClearInterval => 30;
public override bool IgnoreUnsafeHulls => true;
protected override float TargetUpdateTimeMultiplier => 1.0f;
/// <summary>
/// How long the round must have ran before NPCs can start doing inspections
/// (prevents "unfair" inspections you have no chance to react to if you happen to spawn right next to a security NPC with stolen items on you)
/// </summary>
private const float DelayOnRoundStart = 30.0f;
private const float DefaultInspectDistance = 200.0f;
/// <summary>
/// Used when something is stolen and when the guards decide to inspect everyone.
/// </summary>
private const float ExtendedInspectDistance = 400.0f;
/// <summary>
/// Used when the target is tagged as a criminal (= suspective).
/// </summary>
private const float CriminalInspectDistance = 500.0f;
private const float CriminalInspectProbability = 1.0f;
/// <summary>
/// How close the NPC must be to the target to the inspect them? You can use high values to make the NPC
/// systematically go through targets no matter where they are, and low values to check targets they happen to come across.
/// </summary>
private float inspectDistance = DefaultInspectDistance;
private float? overrideInspectProbability;
/// <summary>
/// Chance of inspecting a valid target. The NPC won't try to inspect that target again for <see cref="NormalInspectionInterval"/>
/// regardless if the target is inspected or not.
/// </summary>
public float InspectProbability
{
get
{
if (overrideInspectProbability.HasValue)
{
return overrideInspectProbability.Value;
}
if (GameMain.GameSession?.Campaign is { } campaign)
{
if (campaign.Map?.CurrentLocation?.Reputation is { } reputation)
{
return MathHelper.Lerp(
campaign.Settings.PatdownProbabilityMax,
campaign.Settings.PatdownProbabilityMin,
reputation.NormalizedValue);
}
}
return 0.2f;
}
}
/// <summary>
/// When did the character last inspect whether some other character has stolen items on them?
/// </summary>
private static readonly Dictionary<Character, double> lastInspectionTimes = new Dictionary<Character, double>();
private const float NormalInspectionInterval = 120.0f;
private const float CriminalInspectionInterval = 30.0f;
public AIObjectiveFindThieves(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier) { }
protected override bool IsValidTarget(Character target)
{
if (GameMain.GameSession is not { RoundDuration: > DelayOnRoundStart })
{
return false;
}
if (!IsValidTarget(target, character)) { return false; }
float inspectDist = target.IsCriminal ? CriminalInspectDistance : inspectDistance;
if (Vector2.DistanceSquared(target.WorldPosition, character.WorldPosition) > inspectDist * inspectDist) { return false; }
if (lastInspectionTimes.TryGetValue(target, out double lastInspectionTime))
{
float inspectionInterval = target.IsCriminal ? CriminalInspectionInterval : NormalInspectionInterval;
if (Timing.TotalTime < lastInspectionTime + inspectionInterval)
{
return false;
}
}
return true;
}
protected override IEnumerable<Character> GetList() => Character.CharacterList;
protected override float GetTargetPriority()
{
if (character.IsClimbing)
{
// Don't inspect while climbing, because cannot grab while holding the ladders.
// Can lead to abandoning the objective when we need to climb the ladders to get to the target, but I think that's acceptable.
return 0;
}
return subObjectives.Any() ? 50 : 0;
}
public void InspectEveryone()
{
lastInspectionTimes.Clear();
overrideInspectProbability = 1.0f;
inspectDistance = ExtendedInspectDistance;
}
protected override AIObjective ObjectiveConstructor(Character target)
{
var checkStolenItemsObjective = new AIObjectiveCheckStolenItems(character, target, objectiveManager);
float probabity = target.IsCriminal ? CriminalInspectProbability : InspectProbability;
if (Rand.Range(0.0f, 1.0f, Rand.RandSync.Unsynced) >= probabity)
{
checkStolenItemsObjective.ForceComplete();
lastInspectionTimes[target] = Timing.TotalTime;
}
return checkStolenItemsObjective;
}
private float checkVisibleStolenItemsTimer;
private const float CheckVisibleStolenItemsInterval = 5.0f;
public override void Update(float deltaTime)
{
base.Update(deltaTime);
if (checkVisibleStolenItemsTimer > 0.0f || character.IsClimbing)
{
checkVisibleStolenItemsTimer -= deltaTime;
return;
}
if (character.SelectedSecondaryItem?.GetComponent<Controller>() != null)
{
// Might be e.g. sitting on a chair.
character.SelectedSecondaryItem = null;
}
foreach (var target in Character.CharacterList)
{
if (!IsValidTarget(target, character)) { continue; }
//if we spot someone wearing or holding stolen items, immediately check them (with 100% chance of spotting the stolen items)
if (target.Inventory.AllItems.Any(it => it.Illegitimate && target.HasEquippedItem(it)) &&
character.CanSeeTarget(target, seeThroughWindows: true))
{
AIObjectiveCheckStolenItems? existingObjective =
objectiveManager.GetActiveObjectives<AIObjectiveCheckStolenItems>().FirstOrDefault(o => o.Target == target);
if (existingObjective == null)
{
objectiveManager.AddObjective(new AIObjectiveCheckStolenItems(character, target, objectiveManager));
lastInspectionTimes[target] = Timing.TotalTime;
}
}
}
checkVisibleStolenItemsTimer = CheckVisibleStolenItemsInterval;
}
private bool IsValidTarget(Character target, Character character)
{
if (target == null || target.Removed) { return false; }
if (target.IsIncapacitated) { return false; }
if (target == character) { return false; }
if (target.Submarine == null) { return false; }
if (character.Submarine == null) { return false; }
if (target.CurrentHull == null) { return false; }
if (target.Submarine != character.Submarine) { return false; }
//only player's crew can steal, ignore other teams
if (!target.IsOnPlayerTeam) { return false; }
if (target.IsHandcuffed) { return false; }
// Ignore targets that are climbing, because might need to use ladders to get to them.
if (target.IsClimbing) { return false; }
if (HumanAIController.IsTrueForAnyBotInTheCrew(bot =>
bot != HumanAIController &&
((bot.ObjectiveManager.GetActiveObjective() is AIObjectiveCheckStolenItems checkObj && checkObj.Target == target) ||
(bot.ObjectiveManager.GetActiveObjective() is AIObjectiveCombat combatObj && combatObj.Enemy == target))))
{
// Already being inspected by someone or fighting with someone in our team.
return false;
}
return true;
}
protected override void OnObjectiveCompleted(AIObjective objective, Character target)
{
lastInspectionTimes[target] = Timing.TotalTime;
}
public override void OnDeselected()
{
base.OnDeselected();
character.DeselectCharacter();
}
}
}