Unstable 0.1500.0.0

This commit is contained in:
Markus Isberg
2021-08-26 21:08:21 +09:00
parent 265a2e7ab3
commit 501e02c026
245 changed files with 9775 additions and 2034 deletions
@@ -1,7 +1,8 @@
using Microsoft.Xna.Framework;
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using Barotrauma.Items.Components;
using System.Linq;
namespace Barotrauma
@@ -328,5 +329,8 @@ namespace Barotrauma
protected virtual void OnStateChanged(AIState from, AIState to) { }
protected virtual void OnTargetChanged(AITarget previousTarget, AITarget newTarget) { }
public virtual void ClientRead(IReadMessage msg) { }
public virtual void ServerWrite(IWriteMessage msg) { }
}
}
@@ -1,5 +1,6 @@
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
@@ -37,6 +38,12 @@ namespace Barotrauma
PreviousState = _state;
OnStateChanged(_state, value);
_state = value;
if (_state == AIState.Attack)
{
#if CLIENT
Character.PlaySound(CharacterSound.SoundType.Attack, maxInterval: 3);
#endif
}
}
}
@@ -50,6 +57,8 @@ namespace Barotrauma
private readonly float updateTargetsInterval = 1;
private readonly float updateMemoriesInverval = 1;
private readonly float attackLimbResetInterval = 2;
// Min priority for the memorized targets. The actual value fades gradually, unless kept fresh by selecting the target.
private const float minPriority = 10;
private readonly float avoidLookAheadDistance;
@@ -394,7 +403,7 @@ namespace Barotrauma
public void SelectTarget(AITarget target, float priority)
{
SelectedAiTarget = target;
selectedTargetMemory = GetTargetMemory(target, true);
selectedTargetMemory = GetTargetMemory(target, addIfNotFound: true);
selectedTargetMemory.Priority = priority;
ignoredTargets.Remove(target);
}
@@ -641,7 +650,7 @@ namespace Barotrauma
{
Character c = a.Character;
if (c.IsDead || c.Removed) { return false; }
if (!IsFriendly(Character, c)) { return true; }
if (!Character.IsFriendly(c)) { return true; }
// Only apply the threshold to friendly characters
return a.Damage >= selectedTargetingParams.DamageThreshold;
}
@@ -976,7 +985,7 @@ namespace Barotrauma
Character owner = GetOwner(item);
if (owner != null)
{
if (IsFriendly(Character, owner))
if (Character.IsFriendly(owner))
{
ResetAITarget();
State = AIState.Idle;
@@ -1337,7 +1346,7 @@ namespace Barotrauma
}
else
{
canAttack = Character.CharacterList.All(c => c == Character || !IsFriendly(Character, c) || IsFarEnough(c));
canAttack = Character.CharacterList.All(c => c == Character || !Character.IsFriendly(c) || IsFarEnough(c));
}
if (canAttack)
{
@@ -1356,7 +1365,7 @@ namespace Barotrauma
{
hitTarget = limb.character;
}
if (hitTarget != null && !hitTarget.IsDead && IsFriendly(Character, hitTarget))
if (hitTarget != null && !hitTarget.IsDead && Character.IsFriendly(hitTarget))
{
return true;
}
@@ -1764,7 +1773,7 @@ namespace Barotrauma
Character.AnimController.ReleaseStuckLimbs();
LatchOntoAI?.DeattachFromBody(reset: true, cooldown: 1);
if (attacker == null || attacker.AiTarget == null || attacker.Removed || attacker.IsDead) { return; }
bool isFriendly = IsFriendly(Character, attacker);
bool isFriendly = Character.IsFriendly(attacker);
if (wasLatched)
{
State = AIState.Escape;
@@ -1840,7 +1849,7 @@ namespace Barotrauma
}
}
AITargetMemory targetMemory = GetTargetMemory(attacker.AiTarget, true);
AITargetMemory targetMemory = GetTargetMemory(attacker.AiTarget, addIfNotFound: true);
targetMemory.Priority += GetRelativeDamage(attackResult.Damage, Character.Vitality) * AIParams.AggressionHurt;
// Only allow to react once. Otherwise would attack the target with only a fraction of a cooldown
@@ -1884,16 +1893,15 @@ namespace Barotrauma
private bool UpdateLimbAttack(float deltaTime, Limb attackingLimb, Vector2 attackSimPos, float distance = -1, Limb targetLimb = null)
{
if (SelectedAiTarget?.Entity == null) { return false; }
ActiveAttack = attackingLimb?.attack;
if (attackingLimb?.attack == null) { return false; }
ActiveAttack = attackingLimb.attack;
if (wallTarget != null)
{
// If the selected target is not the wall target, make the wall target the selected target.
var aiTarget = wallTarget.Structure.AiTarget;
if (aiTarget != null && SelectedAiTarget != aiTarget)
{
SelectTarget(aiTarget, GetTargetMemory(SelectedAiTarget, true).Priority);
SelectTarget(aiTarget, GetTargetMemory(SelectedAiTarget, addIfNotFound: true).Priority);
State = AIState.Attack;
}
}
@@ -1902,23 +1910,35 @@ namespace Barotrauma
{
//simulate attack input to get the character to attack client-side
Character.SetInput(InputType.Attack, true, true);
#if SERVER
GameMain.NetworkMember.CreateEntityEvent(Character, new object[]
if (!ActiveAttack.IsRunning)
{
#if SERVER
GameMain.NetworkMember.CreateEntityEvent(Character, new object[]
{
Networking.NetEntityEvent.Type.SetAttackTarget,
attackingLimb,
(damageTarget as Entity)?.ID ?? Entity.NullEntityID,
damageTarget is Character character && targetLimb != null ? Array.IndexOf(character.AnimController.Limbs, targetLimb) : 0,
SimPosition.X,
SimPosition.Y
});
});
#else
Character.PlaySound(CharacterSound.SoundType.Attack, maxInterval: 3);
#endif
}
if (attackingLimb.UpdateAttack(deltaTime, attackSimPos, damageTarget, out AttackResult attackResult, distance, targetLimb))
{
if (damageTarget.Health > 0 && attackResult.Damage > 0)
{
// Managed to hit a living/non-destroyed target. Increase the priority more if the target is low in health -> dies easily/soon
selectedTargetMemory.Priority += GetRelativeDamage(attackResult.Damage, damageTarget.Health) * AIParams.AggressionGreed;
float greed = AIParams.AggressionGreed;
if (!(damageTarget is Character))
{
// Halve the greed for attacking non-characters.
greed /= 2;
}
selectedTargetMemory.Priority += GetRelativeDamage(attackResult.Damage, damageTarget.Health) * greed;
}
else
{
@@ -2125,6 +2145,9 @@ namespace Barotrauma
string targetingTag = null;
if (targetCharacter != null)
{
// ignore if target is tagged to be explicitly ignored (Feign Death)
if (targetCharacter.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI)) { continue; }
if (targetCharacter.IsDead)
{
targetingTag = "dead";
@@ -2139,7 +2162,7 @@ namespace Barotrauma
}
else
{
if (IsFriendly(Character, targetCharacter))
if (Character.IsFriendly(targetCharacter))
{
continue;
}
@@ -2449,7 +2472,7 @@ namespace Barotrauma
{
if (otherCharacter == character) { continue; }
if (otherCharacter.AIController?.SelectedAiTarget != aiTarget) { continue; }
if (!IsFriendly(character, otherCharacter)) { continue; }
if (!character.IsFriendly(otherCharacter)) { continue; }
valueModifier /= 2;
}
}
@@ -2469,7 +2492,7 @@ namespace Barotrauma
// -> just ignore the distance and attack whatever has the highest priority
dist = Math.Max(dist, 100.0f);
AITargetMemory targetMemory = GetTargetMemory(aiTarget, true);
AITargetMemory targetMemory = GetTargetMemory(aiTarget, addIfNotFound: true);
if (Character.CurrentHull != null && Math.Abs(toTarget.Y) > Character.CurrentHull.Size.Y)
{
// Inside the sub, treat objects that are up or down, as they were farther away.
@@ -2527,7 +2550,7 @@ namespace Barotrauma
// Don't target items that we own.
// This is a rare case, and almost entirely related to Humanhusks, so let's check it last to reduce unnecessary checks (although the check shouldn't be expensive)
if (owner == character) { continue; }
if (owner != null && (IsFriendly(Character, owner) || owner.AiTarget != null && ignoredTargets.Contains(owner.AiTarget)))
if (owner != null && (Character.IsFriendly(owner) || owner.AiTarget != null && ignoredTargets.Contains(owner.AiTarget)))
{
continue;
}
@@ -2599,7 +2622,7 @@ namespace Barotrauma
wall = wallTarget?.Structure;
}
// The target is not a wall or it's not the same as we are attached to -> release
bool releaseTarget = wall == null || (!wall.Bodies.Contains(LatchOntoAI.AttachJoints[0].BodyB) && wall.Submarine?.PhysicsBody?.FarseerBody != LatchOntoAI.AttachJoints[0].BodyB);
bool releaseTarget = wall?.Bodies == null || (!wall.Bodies.Contains(LatchOntoAI.AttachJoints[0].BodyB) && wall.Submarine?.PhysicsBody?.FarseerBody != LatchOntoAI.AttachJoints[0].BodyB);
if (!releaseTarget)
{
for (int i = 0; i < wall.Sections.Length; i++)
@@ -2847,10 +2870,15 @@ namespace Barotrauma
{
if (addIfNotFound)
{
memory = new AITargetMemory(target, 10);
memory = new AITargetMemory(target, minPriority);
targetMemories.Add(target, memory);
}
}
if (addIfNotFound)
{
// Keep the memory alive.
memory.Priority = Math.Max(memory.Priority, minPriority);
}
return memory;
}
@@ -3014,7 +3042,7 @@ namespace Barotrauma
{
if (!onlyExisting && !tempParams.ContainsKey(tag))
{
if (AIParams.TryAddNewTarget(tag, state, priority ?? 100, out targetParams))
if (AIParams.TryAddNewTarget(tag, state, priority ?? minPriority, out targetParams))
{
tempParams.Add(tag, targetParams);
}
@@ -3051,6 +3079,7 @@ namespace Barotrauma
ChangeParams(target.SpeciesName, state, priority);
if (target.IsHuman)
{
priority = GetTargetParams("human")?.Priority;
// Target also items, because if we are blind and the target doesn't move, we can only perceive the target when it uses items
if (state == AIState.Attack || state == AIState.Escape)
{
@@ -3061,20 +3090,19 @@ namespace Barotrauma
{
// If the target is shooting from the submarine, we might not perceive it because it doesn't move.
// --> Target the submarine too.
if (target.Submarine != null && (canAttackDoors || canAttackWalls))
if (target.Submarine != null && Character.Submarine == null && (canAttackDoors || canAttackWalls))
{
ChangeParams("room", state, priority);
ChangeParams("room", state, priority * 0.1f);
if (canAttackWalls)
{
ChangeParams("wall", state, priority);
ChangeParams("wall", state, priority * 0.1f);
}
if (canAttackDoors)
{
ChangeParams("door", state, priority);
ChangeParams("door", state, priority * 0.1f);
}
}
ChangeParams("provocative", state, priority, onlyExisting: true);
ChangeParams("light", state, priority, onlyExisting: true);
}
}
}
@@ -3306,7 +3334,17 @@ namespace Barotrauma
return null;
}
public static bool IsFriendly(Character me, Character other) => other.SpeciesName == me.SpeciesName || other.Params.CompareGroup(me.Params.Group);
public override void ServerWrite(IWriteMessage msg)
{
msg.Write((byte)State);
PetBehavior?.ServerWrite(msg);
}
public override void ClientRead(IReadMessage msg)
{
State = (AIState)msg.ReadByte();
PetBehavior?.ClientRead(msg);
}
}
//the "memory" of the Character
@@ -915,10 +915,10 @@ namespace Barotrauma
return false;
}
public static void ReportProblem(Character reporter, Order order)
public static void ReportProblem(Character reporter, Order order, Hull targetHull = null)
{
if (reporter == null || order == null) { return; }
var visibleHulls = new List<Hull>(reporter.GetVisibleHulls());
var visibleHulls = targetHull is null ? new List<Hull>(reporter.GetVisibleHulls()) : new List<Hull> { targetHull };
foreach (var hull in visibleHulls)
{
PropagateHullSafety(reporter, hull);
@@ -1415,7 +1415,7 @@ namespace Barotrauma
if (GameMain.GameSession?.Campaign?.Map?.CurrentLocation != null)
{
var reputationLoss = damageAmount * Reputation.ReputationLossPerWallDamage;
GameMain.GameSession.Campaign.Map.CurrentLocation.Reputation.Value -= reputationLoss;
GameMain.GameSession.Campaign.Map.CurrentLocation.Reputation.AddReputation(-reputationLoss);
}
if (accumulatedDamage <= WarningThreshold) { return; }
@@ -1510,7 +1510,7 @@ namespace Barotrauma
var reputationLoss = MathHelper.Clamp(
(item.Prefab.GetMinPrice() ?? 0) * Reputation.ReputationLossPerStolenItemPrice,
Reputation.MinReputationLossPerStolenItem, Reputation.MaxReputationLossPerStolenItem);
GameMain.GameSession.Campaign.Map.CurrentLocation.Reputation.Value -= reputationLoss;
GameMain.GameSession.Campaign.Map.CurrentLocation.Reputation.AddReputation(-reputationLoss);
}
item.StolenDuringRound = true;
otherCharacter.Speak(TextManager.Get("dialogstealwarning"), null, Rand.Range(0.5f, 1.0f), "thief", 10.0f);
@@ -1971,13 +1971,13 @@ namespace Barotrauma
if (c.Removed) { continue; }
if (c.TeamID != Character.TeamID) { continue; }
if (c.IsIncapacitated) { continue; }
other = c;
if (c.IsPlayer)
{
if (c.SelectedConstruction == target.Item)
{
// If the other character is player, don't try to operate
return true;
other = c;
break;
}
}
else if (c.AIController is HumanAIController operatingAI)
@@ -1991,7 +1991,8 @@ namespace Barotrauma
if (!isOrder && isTargetOrdered)
{
// If the other bot is ordered to operate the item, let him do it, unless we are ordered too
return true;
other = c;
break;
}
else
{
@@ -2012,18 +2013,20 @@ namespace Barotrauma
// Steering is hard-coded -> cannot use the required skills collection defined in the xml
if (Character.GetSkillLevel("helm") <= c.GetSkillLevel("helm"))
{
return true;
other = c;
break;
}
}
else if (target.DegreeOfSuccess(Character) <= target.DegreeOfSuccess(c))
{
return true;
other = c;
break;
}
}
}
}
}
return false;
return other != null;
bool IsOrderedToOperateThis(AIController ai) => ai is HumanAIController humanAI && humanAI.ObjectiveManager.CurrentOrder is AIObjectiveOperateItem operateOrder && operateOrder.Component.Item == target.Item;
}
@@ -161,12 +161,13 @@ namespace Barotrauma
private Vector2 CalculateSteeringSeek(Vector2 target, float weight, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisibility = true)
{
Vector2 targetDiff = target - currentTarget;
if (currentPath != null && currentPath.Nodes.Any())
if (currentPath != null && currentPath.Nodes.Any() && character.Submarine != null)
{
//current path calculated relative to a different sub than where the character is now
//target in a different sub than where the character is now
//take that into account when calculating if the target has moved
Submarine currentPathSub = currentPath?.Nodes.First().Submarine;
if (currentPathSub != character.Submarine && character.Submarine != null)
Submarine currentPathSub = currentPath?.CurrentNode?.Submarine;
if (currentPathSub == character.Submarine) { currentPathSub = currentPath?.Nodes.LastOrDefault()?.Submarine; }
if (currentPathSub != character.Submarine && targetDiff.LengthSquared() > 1 && currentPathSub != null)
{
Vector2 subDiff = character.Submarine.SimPosition - currentPathSub.SimPosition;
targetDiff += subDiff;
@@ -101,11 +101,11 @@ namespace Barotrauma
public enum CombatMode
{
Defensive,
Offensive,
Arrest,
Retreat,
None
Defensive, // Use weapons against the enemy, but try to retreat to a safe place
Offensive, // Engage the enemy and keep attacking it
Arrest, // Try to arrest the enemy without using lethal weapons (stunning + handcuffs)
Retreat, // Run to a safe place without attacking the target
None // Don't use
}
public CombatMode Mode { get; private set; }
@@ -958,14 +958,15 @@ namespace Barotrauma
}
if (reloadTimer > 0) { return; }
if (holdFireCondition != null && holdFireCondition()) { return; }
float sqrDist = Vector2.DistanceSquared(character.Position, Enemy.Position);
sqrDistance = Vector2.DistanceSquared(character.WorldPosition, Enemy.WorldPosition);
distanceTimer = distanceCheckInterval;
if (WeaponComponent is MeleeWeapon meleeWeapon)
{
bool closeEnough = true;
float sqrRange = meleeWeapon.Range * meleeWeapon.Range;
if (character.AnimController.InWater)
{
if (sqrDist > sqrRange)
if (sqrDistance > sqrRange)
{
closeEnough = false;
}
@@ -1003,7 +1004,7 @@ namespace Barotrauma
{
if (WeaponComponent is RepairTool repairTool)
{
if (sqrDist > repairTool.Range * repairTool.Range) { return; }
if (sqrDistance > repairTool.Range * repairTool.Range) { return; }
}
float aimFactor = MathHelper.PiOver2 * (1 - AimAccuracy);
if (VectorExtensions.Angle(VectorExtensions.Forward(Weapon.body.TransformedRotation), Enemy.Position - Weapon.Position) < MathHelper.PiOver4 + aimFactor)
@@ -53,9 +53,13 @@ namespace Barotrauma
distanceFactor = 1;
}
float severity = AIObjectiveExtinguishFires.GetFireSeverity(targetHull);
if (severity > 0.5f && !isOrder)
if (severity > 0.75f && !isOrder &&
targetHull.RoomName != null &&
!targetHull.RoomName.Contains("reactor", StringComparison.OrdinalIgnoreCase) &&
!targetHull.RoomName.Contains("engine", StringComparison.OrdinalIgnoreCase) &&
!targetHull.RoomName.Contains("command", StringComparison.OrdinalIgnoreCase))
{
// Ignore severe fires unless ordered. (Let the fire drain all the oxygen instead).
// Ignore severe fires to prevent casualities unless ordered to extinguish.
Priority = 0;
Abandon = true;
}
@@ -25,7 +25,7 @@ namespace Barotrauma
/// <summary>
/// 0-1 based on the horizontal size of all of the fires in the hull.
/// </summary>
public static float GetFireSeverity(Hull hull) => MathHelper.Lerp(0, 1, MathUtils.InverseLerp(0, Math.Min(hull.Rect.Width, 1000), hull.FireSources.Sum(fs => fs.Size.X)));
public static float GetFireSeverity(Hull hull) => MathHelper.Lerp(0, 1, MathUtils.InverseLerp(0, 500, hull.FireSources.Sum(fs => fs.Size.X)));
protected override IEnumerable<Hull> GetList() => Hull.hullList;
@@ -63,6 +63,7 @@ namespace Barotrauma
if (target.CurrentHull == null) { return false; }
if (HumanAIController.IsFriendly(character, target)) { return false; }
if (!character.Submarine.IsConnectedTo(target.Submarine)) { return false; }
if (target.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI)) { return false; }
return true;
}
}
@@ -57,7 +57,20 @@ namespace Barotrauma
};
},
onAbandon: () => Abandon = true,
onCompleted: () => RemoveSubObjective(ref getDivingGear));
onCompleted: () =>
{
RemoveSubObjective(ref getDivingGear);
if (gearTag == HEAVY_DIVING_GEAR && HumanAIController.HasItem(character, LIGHT_DIVING_GEAR, out IEnumerable<Item> masks, requireEquipped: true))
{
foreach (Item mask in masks)
{
if (mask != targetItem)
{
character.Inventory.TryPutItem(mask, character, CharacterInventory.anySlot);
}
}
}
});
}
else
{
@@ -71,9 +84,13 @@ namespace Barotrauma
{
if (character.IsOnPlayerTeam)
{
if (HumanAIController.HasItem(character, "oxygensource", out _, conditionPercentage: min))
if (HumanAIController.HasItem(character, OXYGEN_SOURCE, out _, conditionPercentage: min))
{
character.Speak(TextManager.Get("dialogswappingoxygentank"), null, 0, "swappingoxygentank", 30.0f);
if (character.Inventory.FindAllItems(i => i.HasTag(OXYGEN_SOURCE) && i.Condition > min).Count == 1)
{
character.Speak(TextManager.Get("dialoglastoxygentank"), null, 0.0f, "dialoglastoxygentank", 30.0f);
}
}
else
{
@@ -105,7 +122,7 @@ namespace Barotrauma
onAbandon: () =>
{
Abandon = true;
if (remainingTanks > 0 && !HumanAIController.HasItem(character, "oxygensource", out _, conditionPercentage: 0.01f))
if (remainingTanks > 0 && !HumanAIController.HasItem(character, OXYGEN_SOURCE, out _, conditionPercentage: 0.01f))
{
character.Speak(TextManager.Get("dialogcantfindtoxygen"), null, 0, "cantfindoxygen", 30.0f);
}
@@ -121,7 +138,7 @@ namespace Barotrauma
int ReportOxygenTankCount()
{
if (character.Submarine != Submarine.MainSub) { return 1; }
int remainingOxygenTanks = Submarine.MainSub.GetItems(false).Count(i => i.HasTag("oxygensource") && i.Condition > 1);
int remainingOxygenTanks = Submarine.MainSub.GetItems(false).Count(i => i.HasTag(OXYGEN_SOURCE) && i.Condition > 1);
if (remainingOxygenTanks == 0)
{
character.Speak(TextManager.Get("DialogOutOfOxygenTanks"), null, 0.0f, "outofoxygentanks", 30.0f);
@@ -136,17 +153,6 @@ namespace Barotrauma
}
}
/// <summary>
/// Returns false only when no inventory can be found from the item.
/// </summary>
public static bool EjectEmptyTanks(Character actor, Item target, out IEnumerable<Item> containedItems)
{
containedItems = target.OwnInventory?.AllItems;
if (containedItems == null) { return false; }
AIController.UnequipEmptyItems(actor, target);
return true;
}
public override void Reset()
{
base.Reset();
@@ -242,9 +242,8 @@ namespace Barotrauma
if (!searchingNewHull)
{
//find all available hulls first
FindTargetHulls();
searchingNewHull = true;
return;
FindTargetHulls();
}
else if (targetHulls.Any())
{
@@ -255,11 +254,10 @@ namespace Barotrauma
var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, errorMsgStr: null, nodeFilter: node =>
{
if (node.Waypoint.CurrentHull == null) { return false; }
// Check that there is no unsafe or forbidden hulls on the way to the target
// Check that there is no unsafe hulls on the way to the target
if (node.Waypoint.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(node.Waypoint.CurrentHull)) { return false; }
if (isCurrentHullAllowed && IsForbidden(node.Waypoint.CurrentHull)) { return false; }
return true;
});
}, endNodeFilter: node => !isCurrentHullAllowed | !IsForbidden(node.Waypoint.CurrentHull));
if (path.Unreachable)
{
//can't go to this room, remove it from the list and try another room
@@ -271,30 +269,19 @@ namespace Barotrauma
SetTargetTimerLow();
return;
}
character.AIController.SelectTarget(currentTarget.AiTarget);
PathSteering.SetPath(path);
SetTargetTimerNormal();
searchingNewHull = false;
}
else
{
// Couldn't find a target for some reason -> reset
// Couldn't find a valid hull
SetTargetTimerHigh();
searchingNewHull = false;
}
if (currentTarget != null)
{
character.AIController.SelectTarget(currentTarget.AiTarget);
string errorMsg = null;
#if DEBUG
bool isRoomNameFound = currentTarget.DisplayName != null;
errorMsg = "(Character " + character.Name + " idling, target " + (isRoomNameFound ? currentTarget.DisplayName : currentTarget.ToString()) + ")";
#endif
var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, errorMsgStr: errorMsg, nodeFilter: node => node.Waypoint.CurrentHull != null);
PathSteering.SetPath(path);
}
SetTargetTimerNormal();
}
newTargetTimer -= deltaTime;
if (!character.IsClimbing && IsSteeringFinished())
{
Wander(deltaTime);
@@ -141,6 +141,7 @@ namespace Barotrauma
public Entity TargetEntity;
public ItemComponent TargetItemComponent;
public readonly bool UseController;
public readonly string[] ControllerTags;
public Controller ConnectedController;
public Character OrderGiver;
@@ -309,6 +310,7 @@ namespace Barotrauma
color = orderElement.GetAttributeColor("color");
FadeOutTime = orderElement.GetAttributeFloat("fadeouttime", 0.0f);
UseController = orderElement.GetAttributeBool("usecontroller", false);
ControllerTags = orderElement.GetAttributeStringArray("controllertags", new string[0]);
TargetAllCharacters = orderElement.GetAttributeBool("targetallcharacters", false);
AppropriateJobs = orderElement.GetAttributeStringArray("appropriatejobs", new string[0]);
Options = orderElement.GetAttributeStringArray("options", new string[0]);
@@ -380,6 +382,7 @@ namespace Barotrauma
SymbolSprite = prefab.SymbolSprite;
Color = prefab.Color;
UseController = prefab.UseController;
ControllerTags = prefab.ControllerTags;
TargetAllCharacters = prefab.TargetAllCharacters;
AppropriateJobs = prefab.AppropriateJobs;
FadeOutTime = prefab.FadeOutTime;
@@ -399,7 +402,7 @@ namespace Barotrauma
{
if (UseController)
{
ConnectedController = targetItem.Item?.FindController();
ConnectedController = targetItem.Item?.FindController(tags: ControllerTags);
if (ConnectedController == null)
{
DebugConsole.AddWarning("AI: Tried to use a controller for operating an item, but couldn't find any.");
@@ -450,19 +453,37 @@ namespace Barotrauma
return false;
}
public string GetChatMessage(string targetCharacterName, string targetRoomName, bool givingOrderToSelf, string orderOption = "")
public string GetChatMessage(string targetCharacterName, string targetRoomName, bool givingOrderToSelf, string orderOption = "", int? priority = null)
{
orderOption ??= "";
string messageTag = (givingOrderToSelf && !TargetAllCharacters ? "OrderDialogSelf." : "OrderDialog.") + Identifier;
if (Identifier != "dismissed" && !string.IsNullOrEmpty(orderOption)) { messageTag += "." + orderOption; }
if (targetCharacterName == null) { targetCharacterName = ""; }
if (targetRoomName == null) { targetRoomName = ""; }
string msg = TextManager.GetWithVariables(messageTag, new string[2] { "[name]", "[roomname]" }, new string[2] { targetCharacterName, targetRoomName }, new bool[2] { false, true }, true);
if (msg == null) { return ""; }
return msg;
priority ??= CharacterInfo.HighestManualOrderPriority;
// If the order has a lesser priority, it means we are rearranging character orders
if (!TargetAllCharacters && priority != CharacterInfo.HighestManualOrderPriority && Identifier != "dismissed")
{
return TextManager.GetWithVariable("rearrangedorders", "[name]", targetCharacterName ?? string.Empty, returnNull: true) ?? string.Empty;
}
string messageTag = $"{(givingOrderToSelf && !TargetAllCharacters ? "OrderDialogSelf" : "OrderDialog")}";
messageTag += $".{Identifier}";
if (!string.IsNullOrEmpty(orderOption))
{
if (Identifier != "dismissed")
{
messageTag += $".{orderOption}";
}
else
{
string[] splitOption = orderOption.Split('.');
if (splitOption.Length > 0)
{
messageTag += $".{splitOption[0]}";
}
}
}
string msg = TextManager.GetWithVariables(messageTag,
new string[2] { "[name]", "[roomname]" },
new string[2] { targetCharacterName ?? string.Empty, targetRoomName ?? string.Empty },
formatCapitals: new bool[2] { false, true },
returnNull: true);
return msg ?? string.Empty;
}
/// <summary>
@@ -505,7 +526,7 @@ namespace Barotrauma
if (item.NonInteractable) { continue; }
if (ItemComponentType != null && item.Components.None(c => c.GetType() == ItemComponentType)) { continue; }
Controller controller = null;
if (UseController && !item.TryFindController(out controller)) { continue; }
if (UseController && !item.TryFindController(out controller, tags: ControllerTags)) { continue; }
if (interactableFor != null && (!item.IsInteractable(interactableFor) || (UseController && !controller.Item.IsInteractable(interactableFor)))) { continue; }
matchingItems.Add(item);
}
@@ -97,64 +97,51 @@ namespace Barotrauma
foreach (WayPoint wp in wayPoints)
{
wp.linkedTo.CollectionChanged += WaypointLinksChanged;
wp.OnLinksChanged += WaypointLinksChanged;
}
IndoorsSteering = indoorsSteering;
}
void WaypointLinksChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
void WaypointLinksChanged(WayPoint wp)
{
if (Submarine.Unloading) { return; }
var waypoints = sender as IEnumerable<MapEntity>;
var node = nodes.Find(n => n.Waypoint == wp);
if (node == null) { return; }
foreach (MapEntity me in waypoints)
for (int i = node.connections.Count - 1; i >= 0; i--)
{
WayPoint wp = me as WayPoint;
if (me == null) { continue; }
var node = nodes.Find(n => n.Waypoint == wp);
if (node == null) { return; }
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
//remove connection if the waypoint isn't connected anymore
if (wp.linkedTo.FirstOrDefault(l => l == node.connections[i].Waypoint) == null)
{
for (int i = node.connections.Count - 1; i >= 0; i--)
{
//remove connection if the waypoint isn't connected anymore
if (wp.linkedTo.FirstOrDefault(l => l == node.connections[i].Waypoint) == null)
{
node.connections.RemoveAt(i);
node.distances.RemoveAt(i);
}
}
node.connections.RemoveAt(i);
node.distances.RemoveAt(i);
}
else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
}
for (int i = 0; i < wp.linkedTo.Count; i++)
{
if (!(wp.linkedTo[i] is WayPoint connected)) { continue; }
//already connected, continue
if (node.connections.Any(n => n.Waypoint == connected)) { continue; }
var matchingNode = nodes.Find(n => n.Waypoint == connected);
if (matchingNode == null)
{
for (int i = 0; i < wp.linkedTo.Count; i++)
{
if (!(wp.linkedTo[i] is WayPoint connected)) { continue; }
//already connected, continue
if (node.connections.Any(n => n.Waypoint == connected)) { continue; }
var matchingNode = nodes.Find(n => n.Waypoint == connected);
if (matchingNode == null)
{
#if DEBUG
DebugConsole.ThrowError("Waypoint connections were changed, no matching path node found in PathFinder");
DebugConsole.ThrowError("Waypoint connections were changed, no matching path node found in PathFinder");
#endif
return;
}
node.connections.Add(matchingNode);
node.distances.Add(Vector2.Distance(node.Position, matchingNode.Position));
}
return;
}
node.connections.Add(matchingNode);
node.distances.Add(Vector2.Distance(node.Position, matchingNode.Position));
}
}
private static readonly List<PathNode> sortedNodes = new List<PathNode>();
private readonly List<PathNode> sortedNodes = new List<PathNode>();
public SteeringPath FindPath(Vector2 start, Vector2 end, Submarine hostSub = null, string errorMsgStr = null, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisibility = true)
{