Unstable 1.2.1.0

This commit is contained in:
Markus Isberg
2023-11-10 17:45:19 +02:00
parent 2ea58c58a7
commit 8a2e2ea0ae
268 changed files with 4076 additions and 1843 deletions
@@ -262,7 +262,8 @@ namespace Barotrauma
if (aiElements.Count == 0)
{
DebugConsole.ThrowError("Error in file \"" + c.Params.File + "\" - no AI element found.");
DebugConsole.ThrowError("Error in file \"" + c.Params.File + "\" - no AI element found.",
contentPackage: c.Prefab?.ContentPackage);
outsideSteering = new SteeringManager(this);
insideSteering = new IndoorsSteeringManager(this, false, false);
return;
@@ -330,7 +331,8 @@ namespace Barotrauma
_aiParams = Character.Params.AI;
if (_aiParams == null)
{
DebugConsole.ThrowError($"No AI Params defined for {Character.SpeciesName}. AI disabled.");
DebugConsole.ThrowError($"No AI Params defined for {Character.SpeciesName}. AI disabled.",
contentPackage: Character.Prefab.ContentPackage);
Enabled = false;
_aiParams = new CharacterParams.AIParams(null, Character.Params);
}
@@ -2503,7 +2505,8 @@ namespace Barotrauma
Limb mouthLimb = Character.AnimController.GetLimb(LimbType.Head);
if (mouthLimb == null)
{
DebugConsole.ThrowError("Character \"" + Character.SpeciesName + "\" failed to eat a target (No head limb defined)");
DebugConsole.ThrowError("Character \"" + Character.SpeciesName + "\" failed to eat a target (No head limb defined)",
contentPackage: Character.Prefab.ContentPackage);
State = AIState.Idle;
return;
}
@@ -2540,7 +2543,11 @@ namespace Barotrauma
item.body.LinearVelocity -= velocity * 0.25f;
bool wasBroken = item.Condition <= 0.0f;
item.LastEatenTime = (float)Timing.TotalTimeUnpaused;
item.AddDamage(Character, item.WorldPosition, new Attack(0.0f, 0.0f, 0.0f, 0.0f, 0.02f * Character.Params.EatingSpeed), deltaTime);
item.AddDamage(Character,
item.WorldPosition,
new Attack(0.0f, 0.0f, 0.0f, 0.0f, 0.02f * Character.Params.EatingSpeed),
impulseDirection: Vector2.Zero,
deltaTime);
Character.ApplyStatusEffects(ActionType.OnEating, deltaTime);
if (item.Condition <= 0.0f)
{
@@ -167,10 +167,6 @@ namespace Barotrauma
public HumanAIController(Character c) : base(c)
{
if (!c.IsHuman)
{
throw new Exception($"Tried to create a human ai controller for a non-human: {c.SpeciesName}!");
}
insideSteering = new IndoorsSteeringManager(this, true, false);
outsideSteering = new SteeringManager(this);
objectiveManager = new AIObjectiveManager(c);
@@ -1800,7 +1796,7 @@ namespace Barotrauma
if (!TriggerSecurity(otherHumanAI, combatMode))
{
// Else call the others
foreach (Character security in Character.CharacterList.Where(c => c.TeamID == otherCharacter.TeamID).OrderByDescending(c => Vector2.DistanceSquared(character.WorldPosition, c.WorldPosition)))
foreach (Character security in Character.CharacterList.Where(c => c.TeamID == otherCharacter.TeamID).OrderBy(c => Vector2.DistanceSquared(character.WorldPosition, c.WorldPosition)))
{
if (!TriggerSecurity(security.AIController as HumanAIController, combatMode))
{
@@ -1861,16 +1857,11 @@ namespace Barotrauma
}
if (!someoneSpoke)
{
if (!item.StolenDuringRound &&
Level.Loaded?.Type == LevelData.LevelType.Outpost &&
GameMain.GameSession?.Campaign?.Map?.CurrentLocation != null)
if (!item.StolenDuringRound)
{
var reputationLoss = MathHelper.Clamp(
(item.Prefab.GetMinPrice() ?? 0) * Reputation.ReputationLossPerStolenItemPrice,
Reputation.MinReputationLossPerStolenItem, Reputation.MaxReputationLossPerStolenItem);
GameMain.GameSession.Campaign.Map.CurrentLocation.Reputation?.AddReputation(-reputationLoss);
ApplyStealingReputationLoss(item);
item.StolenDuringRound = true;
}
item.StolenDuringRound = true;
otherCharacter.Speak(TextManager.Get("dialogstealwarning").Value, null, Rand.Range(0.5f, 1.0f), "thief".ToIdentifier(), 10.0f);
someoneSpoke = true;
#if CLIENT
@@ -1881,7 +1872,7 @@ namespace Barotrauma
if (!TriggerSecurity(otherHumanAI))
{
// Else call the others
foreach (Character security in Character.CharacterList.Where(c => c.TeamID == otherCharacter.TeamID).OrderByDescending(c => Vector2.DistanceSquared(thief.WorldPosition, c.WorldPosition)))
foreach (Character security in Character.CharacterList.Where(c => c.TeamID == otherCharacter.TeamID).OrderBy(c => Vector2.DistanceSquared(thief.WorldPosition, c.WorldPosition)))
{
if (TriggerSecurity(security.AIController as HumanAIController))
{
@@ -1902,6 +1893,10 @@ namespace Barotrauma
if (humanAI == null) { return false; }
if (!humanAI.Character.IsSecurity) { return false; }
if (humanAI.ObjectiveManager.IsCurrentObjective<AIObjectiveCombat>()) { return false; }
if (humanAI.ObjectiveManager.GetObjective<AIObjectiveFindThieves>() is { } findThieves)
{
findThieves.InspectEveryone();
}
humanAI.AddCombatObjective(AIObjectiveCombat.CombatMode.Arrest, thief, delay: GetReactionTime(),
abortCondition: obj => thief.Inventory.FindItem(it => it != null && it.StolenDuringRound, true) == null,
onAbort: () =>
@@ -1919,6 +1914,18 @@ namespace Barotrauma
}
}
public static void ApplyStealingReputationLoss(Item item)
{
if (Level.Loaded?.Type == LevelData.LevelType.Outpost &&
GameMain.GameSession?.Campaign?.Map?.CurrentLocation != null)
{
var reputationLoss = MathHelper.Clamp(
(item.Prefab.GetMinPrice() ?? 0) * Reputation.ReputationLossPerStolenItemPrice,
Reputation.MinReputationLossPerStolenItem, Reputation.MaxReputationLossPerStolenItem);
GameMain.GameSession.Campaign.Map.CurrentLocation.Reputation?.AddReputation(-reputationLoss);
}
}
// 0.225 - 0.375
private static float GetReactionTime() => reactionTime * Rand.Range(0.75f, 1.25f);
@@ -197,17 +197,6 @@ namespace Barotrauma
}
}
/// <summary>
/// This method allows multiple subobjectives of same type. Use with caution.
/// </summary>
public void AddSubObjectiveInQueue(AIObjective objective)
{
if (!subObjectives.Contains(objective))
{
subObjectives.Add(objective);
}
}
public void RemoveSubObjective<T>(ref T objective) where T : AIObjective
{
if (objective != null)
@@ -0,0 +1,160 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
class AIObjectiveCheckStolenItems : AIObjective
{
public override Identifier Identifier { get; set; } = "check stolen items".ToIdentifier();
public override bool AllowOutsideSubmarine => false;
public override bool AllowInAnySub => false;
public float FindStolenItemsProbability = 1.0f;
enum State
{
GotoTarget,
Inspect,
Warn,
Done
}
private float inspectDelay;
private float warnDelay;
private State currentState;
public readonly Character TargetCharacter;
private AIObjectiveGoTo? goToObjective;
private readonly List<Item> stolenItems = new List<Item>();
public AIObjectiveCheckStolenItems(Character character, Character targetCharacter, AIObjectiveManager objectiveManager, float priorityModifier = 1) :
base(character, objectiveManager, priorityModifier)
{
TargetCharacter = targetCharacter;
inspectDelay = 5.0f;
warnDelay = 5.0f;
}
public override bool IsLoop
{
get => false;
set => throw new Exception("Trying to set the value for IsLoop from: " + Environment.StackTrace.CleanupStackTrace());
}
protected override bool CheckObjectiveSpecific() => false;
protected override float GetPriority()
{
if (!Abandon && !IsCompleted && objectiveManager.IsOrder(this))
{
Priority = objectiveManager.GetOrderPriority(this);
}
else
{
Priority = AIObjectiveManager.LowestOrderPriority - 1;
}
return Priority;
}
public void ForceComplete()
{
IsCompleted = true;
}
protected override void Act(float deltaTime)
{
switch (currentState)
{
case State.GotoTarget:
TryAddSubObjective(ref goToObjective,
constructor: () =>
{
return new AIObjectiveGoTo(TargetCharacter, character, objectiveManager, repeat: false)
{
SpeakIfFails = false
};
},
onCompleted: () =>
{
RemoveSubObjective(ref goToObjective);
currentState = State.Inspect;
stolenItems.Clear();
TargetCharacter.Inventory.FindAllItems(it => it.SpawnedInCurrentOutpost && !it.AllowStealing, recursive: true, stolenItems);
character.Speak(TextManager.Get("dialogcheckstolenitems").Value);
},
onAbandon: () =>
{
Abandon = true;
});
break;
case State.Inspect:
Inspect(deltaTime);
break;
case State.Warn:
Warn(deltaTime);
break;
}
}
private void Inspect(float deltaTime)
{
if (inspectDelay > 0.0f)
{
character.SelectCharacter(TargetCharacter);
inspectDelay -= deltaTime;
return;
}
if (stolenItems.Any() &&
Rand.Range(0.0f, 1.0f, Rand.RandSync.Unsynced) < FindStolenItemsProbability)
{
character.Speak(TextManager.Get("dialogcheckstolenitems.warn").Value);
currentState = State.Warn;
}
else
{
character.Speak(TextManager.Get("dialogcheckstolenitems.nostolenitems").Value);
currentState = State.Done;
IsCompleted = true;
}
character.DeselectCharacter();
}
private void Warn(float deltaTime)
{
if (warnDelay > 0.0f)
{
warnDelay -= deltaTime;
return;
}
var stolenItemsOnCharacter = stolenItems.Where(it => it.GetRootInventoryOwner() == TargetCharacter);
if (stolenItemsOnCharacter.Any())
{
character.Speak(TextManager.Get("dialogcheckstolenitems.arrest").Value);
HumanAIController.AddCombatObjective(AIObjectiveCombat.CombatMode.Arrest, TargetCharacter);
foreach (var stolenItem in stolenItemsOnCharacter)
{
HumanAIController.ApplyStealingReputationLoss(stolenItem);
}
}
else
{
character.Speak(TextManager.Get("dialogcheckstolenitems.comply").Value);
}
foreach (var item in stolenItems)
{
HumanAIController.ObjectiveManager.AddObjective(new AIObjectiveGetItem(character, item, objectiveManager, equip: false)
{
BasePriority = 10
});
}
currentState = State.Done;
IsCompleted = true;
}
}
}
@@ -0,0 +1,152 @@
#nullable enable
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
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;
const float DefaultInspectDistance = 200.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>
public 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="inspectionInterval"/>
/// 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.MaxStolenItemInspectionProbability,
campaign.Settings.MinStolenItemInspectionProbability,
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 readonly float inspectionInterval = 120.0f;
public AIObjectiveFindThieves(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier) { }
protected override bool Filter(Character target)
{
if (!IsValidTarget(target, character)) { return false; }
if (Vector2.DistanceSquared(target.WorldPosition, character.WorldPosition) > InspectDistance * InspectDistance) { return false; }
if (lastInspectionTimes.TryGetValue(target, out double lastInspectionTime))
{
if (Timing.TotalTime < lastInspectionTime + inspectionInterval)
{
return false;
}
}
return true;
}
protected override IEnumerable<Character> GetList() => Character.CharacterList;
protected override float TargetEvaluation()
{
return subObjectives.Any() ? 50 : 0;
}
public void InspectEveryone()
{
lastInspectionTimes.Clear();
overrideInspectProbability = 1.0f;
InspectDistance = DefaultInspectDistance * 2;
}
protected override AIObjective ObjectiveConstructor(Character target)
{
var checkStolenItemsObjective = new AIObjectiveCheckStolenItems(character, target, objectiveManager);
if (Rand.Range(0.0f, 1.0f, Rand.RandSync.Unsynced) >= InspectProbability)
{
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)
{
checkVisibleStolenItemsTimer -= deltaTime;
return;
}
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.SpawnedInCurrentOutpost && !it.AllowStealing && target.HasEquippedItem(it)) &&
character.CanSeeTarget(target))
{
AIObjectiveCheckStolenItems? existingObjective =
objectiveManager.GetActiveObjectives<AIObjectiveCheckStolenItems>().FirstOrDefault(o => o.TargetCharacter == 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.IsArrested) { return false; }
return true;
}
protected override void OnObjectiveCompleted(AIObjective objective, Character target)
{
lastInspectionTimes[target] = Timing.TotalTime;
}
}
}
@@ -178,7 +178,7 @@ namespace Barotrauma
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;
bool ignore = this is AIObjectiveChargeBatteries || this is AIObjectivePumpWater || this is AIObjectiveFindThieves;
if (!ignore && !ReportedTargets.Contains(target)) { continue; }
}
if (!Filter(target)) { continue; }
@@ -142,6 +142,7 @@ namespace Barotrauma
prevIdleObjective.PreferredOutpostModuleTypes.ForEach(t => newIdleObjective.PreferredOutpostModuleTypes.Add(t));
}
AddObjective(newIdleObjective);
int objectiveCount = Objectives.Count;
foreach (var autonomousObjective in character.Info.Job.Prefab.AutonomousObjectives)
{
@@ -549,6 +550,9 @@ namespace Barotrauma
case "escapehandcuffs":
newObjective = new AIObjectiveEscapeHandcuffs(character, this, priorityModifier: priorityModifier);
break;
case "findthieves":
newObjective = new AIObjectiveFindThieves(character, this, priorityModifier: priorityModifier);
break;
case "prepareforexpedition":
newObjective = new AIObjectivePrepare(character, this, order.GetTargetItems(order.Option), order.RequireItems)
{
@@ -441,7 +441,8 @@ namespace Barotrauma
}
catch (NotImplementedException e)
{
DebugConsole.LogError($"Error creating a new Order instance: unexpected target type \"{targetType}\".\n{e.StackTrace.CleanupStackTrace()}");
DebugConsole.LogError($"Error creating a new Order instance: unexpected target type \"{targetType}\".\n{e.StackTrace.CleanupStackTrace()}",
contentPackage: ContentPackage);
return null;
}
}