(50c25dbf6) Added a "back" button that closes the credits, minor credits tweaks
This commit is contained in:
@@ -91,7 +91,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
GUI.SmallFont.DrawString(spriteBatch,
|
GUI.SmallFont.DrawString(spriteBatch,
|
||||||
currentNode.ID.ToString(),
|
currentNode.ID.ToString(),
|
||||||
new Vector2(currentNode.DrawPosition.X - 10, -currentNode.DrawPosition.Y - 30),
|
new Vector2(currentNode.DrawPosition.X + 20, -currentNode.DrawPosition.Y - 20),
|
||||||
Color.Red);
|
Color.Red);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using FarseerPhysics;
|
using FarseerPhysics;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
@@ -38,20 +37,16 @@ namespace Barotrauma
|
|||||||
var currentOrder = ObjectiveManager.CurrentOrder;
|
var currentOrder = ObjectiveManager.CurrentOrder;
|
||||||
if (currentOrder != null)
|
if (currentOrder != null)
|
||||||
{
|
{
|
||||||
GUI.DrawString(spriteBatch, pos + textOffset, $"ORDER: {currentOrder.DebugTag} ({currentOrder.GetPriority().FormatZeroDecimal()})", Color.White, Color.Black);
|
GUI.DrawString(spriteBatch, pos + textOffset, $"ORDER: {currentOrder.DebugTag} ({currentOrder.GetPriority(ObjectiveManager).FormatZeroDecimal()})", Color.White, Color.Black);
|
||||||
}
|
|
||||||
else if (ObjectiveManager.WaitTimer > 0)
|
|
||||||
{
|
|
||||||
GUI.DrawString(spriteBatch, pos + textOffset, $"Waiting... {ObjectiveManager.WaitTimer.FormatZeroDecimal()}", Color.White, Color.Black);
|
|
||||||
}
|
}
|
||||||
var currentObjective = ObjectiveManager.CurrentObjective;
|
var currentObjective = ObjectiveManager.CurrentObjective;
|
||||||
if (currentObjective != null)
|
if (currentObjective != null)
|
||||||
{
|
{
|
||||||
GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 20), $"OBJECTIVE: {currentObjective.DebugTag} ({currentObjective.GetPriority().FormatZeroDecimal()})", Color.White, Color.Black);
|
GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 20), $"OBJECTIVE: {currentObjective.DebugTag} ({currentObjective.GetPriority(ObjectiveManager).FormatZeroDecimal()})", Color.White, Color.Black);
|
||||||
var subObjective = currentObjective.SubObjectives.FirstOrDefault();
|
var subObjective = currentObjective.CurrentSubObjective;
|
||||||
if (subObjective != null)
|
if (subObjective != null)
|
||||||
{
|
{
|
||||||
GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 40), $"SUBOBJECTIVE: {subObjective.DebugTag} ({subObjective.GetPriority().FormatZeroDecimal()})", Color.White, Color.Black);
|
GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 40), $"SUBOBJECTIVE: {subObjective.DebugTag} ({subObjective.GetPriority(ObjectiveManager).FormatZeroDecimal()})", Color.White, Color.Black);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,8 +75,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
GUI.SmallFont.DrawString(spriteBatch,
|
GUI.SmallFont.DrawString(spriteBatch,
|
||||||
currentNode.ID.ToString(),
|
currentNode.ID.ToString(),
|
||||||
new Vector2(currentNode.DrawPosition.X - 10, -currentNode.DrawPosition.Y - 30),
|
new Vector2(currentNode.DrawPosition.X + 20, -currentNode.DrawPosition.Y - 20),
|
||||||
Color.Blue);
|
Color.SkyBlue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,6 +152,32 @@ namespace Barotrauma
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (character.MemLocalState.Count > 120) character.MemLocalState.RemoveRange(0, character.MemLocalState.Count - 120);
|
||||||
|
character.MemState.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void ImpactProjSpecific(float impact, Body body)
|
||||||
|
{
|
||||||
|
float volume = MathHelper.Clamp(impact - 3.0f, 0.5f, 1.0f);
|
||||||
|
|
||||||
|
if (body.UserData is Limb limb && character.Stun <= 0f)
|
||||||
|
{
|
||||||
|
if (impact > 3.0f) { PlayImpactSound(limb); }
|
||||||
|
}
|
||||||
|
else if (body.UserData is Limb || body == Collider.FarseerBody)
|
||||||
|
{
|
||||||
|
if (!character.IsRemotePlayer && impact > ImpactTolerance)
|
||||||
|
{
|
||||||
|
SoundPlayer.PlayDamageSound("LimbBlunt", strongestImpact, Collider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Character.Controlled == character)
|
||||||
|
{
|
||||||
|
GameMain.GameScreen.Cam.Shake = Math.Min(Math.Max(strongestImpact, GameMain.GameScreen.Cam.Shake), 3.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (character.MemState.Count < 1) return;
|
if (character.MemState.Count < 1) return;
|
||||||
|
|
||||||
overrideTargetMovement = Vector2.Zero;
|
overrideTargetMovement = Vector2.Zero;
|
||||||
|
|||||||
@@ -18,21 +18,38 @@ namespace Barotrauma
|
|||||||
private Sprite languageSelectionCursor;
|
private Sprite languageSelectionCursor;
|
||||||
private ScalableFont languageSelectionFont;
|
private ScalableFont languageSelectionFont;
|
||||||
|
|
||||||
private Video splashScreen;
|
private Video currSplashScreen;
|
||||||
public Video SplashScreen
|
private DateTime videoStartTime;
|
||||||
|
|
||||||
|
private Queue<Pair<string, Point>> pendingSplashScreens = new Queue<Pair<string, Point>>();
|
||||||
|
/// <summary>
|
||||||
|
/// Pair.first = filepath, Pair.second = resolution
|
||||||
|
/// </summary>
|
||||||
|
public Queue<Pair<string, Point>> PendingSplashScreens
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
lock (loadMutex)
|
lock (loadMutex)
|
||||||
{
|
{
|
||||||
return splashScreen;
|
return pendingSplashScreens;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
lock (loadMutex)
|
lock (loadMutex)
|
||||||
{
|
{
|
||||||
splashScreen = value;
|
pendingSplashScreens = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool PlayingSplashScreen
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (loadMutex)
|
||||||
|
{
|
||||||
|
return currSplashScreen != null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,8 +117,8 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
DrawSplashScreen(spriteBatch);
|
DrawSplashScreen(spriteBatch, graphics);
|
||||||
if (SplashScreen != null && SplashScreen.IsPlaying) return;
|
if (currSplashScreen != null || PendingSplashScreens.Count > 0) { return; }
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -201,46 +218,77 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (languageSelectionFont == null)
|
if (languageSelectionFont == null)
|
||||||
{
|
{
|
||||||
languageSelectionFont = new ScalableFont("Content/Fonts/BebasNeue-Regular.otf", 28, graphicsDevice);
|
languageSelectionFont = new ScalableFont("Content/Fonts/BebasNeue-Regular.otf", (uint)(30 * (GameMain.GraphicsHeight / 1080.0f)), graphicsDevice);
|
||||||
}
|
}
|
||||||
if (languageSelectionCursor == null)
|
if (languageSelectionCursor == null)
|
||||||
{
|
{
|
||||||
languageSelectionCursor = new Sprite("Content/UI/cursor.png", Vector2.Zero);
|
languageSelectionCursor = new Sprite("Content/UI/cursor.png", Vector2.Zero);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector2 textPos = new Vector2(GameMain.GraphicsWidth / 2, GameMain.GraphicsHeight * 0.25f);
|
Vector2 textPos = new Vector2(GameMain.GraphicsWidth / 2, GameMain.GraphicsHeight * 0.3f);
|
||||||
Vector2 textSpacing = new Vector2(0.0f, (GameMain.GraphicsHeight * 0.5f) / TextManager.AvailableLanguages.Count());
|
Vector2 textSpacing = new Vector2(0.0f, (GameMain.GraphicsHeight * 0.5f) / TextManager.AvailableLanguages.Count());
|
||||||
foreach (string language in TextManager.AvailableLanguages)
|
foreach (string language in TextManager.AvailableLanguages)
|
||||||
{
|
{
|
||||||
languageSelectionFont.DrawString(spriteBatch, language, textPos - languageSelectionFont.MeasureString(language) / 2, Color.White * 0.8f);
|
Vector2 textSize = languageSelectionFont.MeasureString(language);
|
||||||
|
bool hover =
|
||||||
|
Math.Abs(PlayerInput.MousePosition.X - textPos.X) < textSize.X / 2 &&
|
||||||
|
Math.Abs(PlayerInput.MousePosition.Y - textPos.Y) < textSpacing.Y / 2;
|
||||||
|
|
||||||
|
//TODO: display the name of the language in the target language?
|
||||||
|
languageSelectionFont.DrawString(spriteBatch, language, textPos - textSize / 2,
|
||||||
|
hover ? Color.White : Color.White * 0.6f);
|
||||||
|
if (hover && PlayerInput.LeftButtonClicked())
|
||||||
|
{
|
||||||
|
GameMain.Config.Language = language;
|
||||||
|
WaitForLanguageSelection = false;
|
||||||
|
}
|
||||||
|
|
||||||
textPos += textSpacing;
|
textPos += textSpacing;
|
||||||
}
|
}
|
||||||
|
|
||||||
languageSelectionCursor.Draw(spriteBatch, PlayerInput.LatestMousePosition);
|
languageSelectionCursor.Draw(spriteBatch, PlayerInput.LatestMousePosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawSplashScreen(SpriteBatch spriteBatch)
|
private void DrawSplashScreen(SpriteBatch spriteBatch, GraphicsDevice graphics)
|
||||||
{
|
{
|
||||||
if (SplashScreen != null)
|
if (currSplashScreen == null && PendingSplashScreens.Count == 0) { return; }
|
||||||
{
|
|
||||||
if (SplashScreen.IsPlaying)
|
|
||||||
{
|
|
||||||
spriteBatch.Begin();
|
|
||||||
spriteBatch.Draw(SplashScreen.GetTexture(), new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), Color.White);
|
|
||||||
spriteBatch.End();
|
|
||||||
|
|
||||||
if (PlayerInput.KeyHit(Keys.Space) || PlayerInput.KeyHit(Keys.Enter) || PlayerInput.LeftButtonDown())
|
if (currSplashScreen == null)
|
||||||
{
|
{
|
||||||
SplashScreen.Dispose(); SplashScreen = null;
|
var newSplashScreen = PendingSplashScreens.Dequeue();
|
||||||
}
|
string fileName = newSplashScreen.First;
|
||||||
}
|
Point resolution = newSplashScreen.Second;
|
||||||
else
|
try
|
||||||
{
|
{
|
||||||
SplashScreen.Dispose(); SplashScreen = null;
|
currSplashScreen = new Video(graphics, GameMain.SoundManager, fileName, (uint)resolution.X, (uint)resolution.Y);
|
||||||
|
videoStartTime = DateTime.Now;
|
||||||
}
|
}
|
||||||
}
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
GameMain.Config.EnableSplashScreen = false;
|
||||||
|
DebugConsole.ThrowError("Playing the splash screen \"" + fileName + "\" failed.", e);
|
||||||
|
PendingSplashScreens.Clear();
|
||||||
|
currSplashScreen = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currSplashScreen.IsPlaying)
|
||||||
|
{
|
||||||
|
spriteBatch.Begin();
|
||||||
|
spriteBatch.Draw(currSplashScreen.GetTexture(), new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), Color.White);
|
||||||
|
spriteBatch.End();
|
||||||
|
|
||||||
|
if (PlayerInput.KeyHit(Keys.Space) || PlayerInput.KeyHit(Keys.Enter) || PlayerInput.LeftButtonDown())
|
||||||
|
{
|
||||||
|
currSplashScreen.Dispose(); currSplashScreen = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (DateTime.Now > videoStartTime + new TimeSpan(0, 0, 0, 0, milliseconds: 500))
|
||||||
|
{
|
||||||
|
currSplashScreen.Dispose(); currSplashScreen = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool drawn;
|
bool drawn;
|
||||||
public IEnumerable<object> DoLoading(IEnumerable<object> loader)
|
public IEnumerable<object> DoLoading(IEnumerable<object> loader)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -270,21 +270,24 @@ namespace Barotrauma
|
|||||||
WaterRenderer.Instance = new WaterRenderer(base.GraphicsDevice, Content);
|
WaterRenderer.Instance = new WaterRenderer(base.GraphicsDevice, Content);
|
||||||
|
|
||||||
loadingScreenOpen = true;
|
loadingScreenOpen = true;
|
||||||
TitleScreen = new LoadingScreen(GraphicsDevice);
|
TitleScreen = new LoadingScreen(GraphicsDevice)
|
||||||
TitleScreen.WaitForLanguageSelection = Config.ShowLanguageSelectionPrompt;
|
{
|
||||||
|
WaitForLanguageSelection = Config.ShowLanguageSelectionPrompt
|
||||||
|
};
|
||||||
|
|
||||||
bool canLoadInSeparateThread = false;
|
bool canLoadInSeparateThread = false;
|
||||||
#if WINDOWS
|
#if WINDOWS
|
||||||
canLoadInSeparateThread = true;
|
canLoadInSeparateThread = true;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
loadingCoroutine = CoroutineManager.StartCoroutine(Load(), "", canLoadInSeparateThread);
|
loadingCoroutine = CoroutineManager.StartCoroutine(Load(canLoadInSeparateThread), "", canLoadInSeparateThread);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitUserStats()
|
private void InitUserStats()
|
||||||
{
|
{
|
||||||
if (GameSettings.ShowUserStatisticsPrompt)
|
if (GameSettings.ShowUserStatisticsPrompt)
|
||||||
{
|
{
|
||||||
|
//TODO: translate
|
||||||
var userStatsPrompt = new GUIMessageBox(
|
var userStatsPrompt = new GUIMessageBox(
|
||||||
"Do you want to help us make Barotrauma better?",
|
"Do you want to help us make Barotrauma better?",
|
||||||
"Do you allow Barotrauma to send usage statistics and error reports to the developers? The data is anonymous, " +
|
"Do you allow Barotrauma to send usage statistics and error reports to the developers? The data is anonymous, " +
|
||||||
@@ -334,16 +337,21 @@ namespace Barotrauma
|
|||||||
SoundManager.SetCategoryGainMultiplier("voip", Config.VoiceChatVolume);
|
SoundManager.SetCategoryGainMultiplier("voip", Config.VoiceChatVolume);
|
||||||
if (Config.EnableSplashScreen)
|
if (Config.EnableSplashScreen)
|
||||||
{
|
{
|
||||||
try
|
var pendingSplashScreens = TitleScreen.PendingSplashScreens;
|
||||||
|
pendingSplashScreens?.Enqueue(new Pair<string, Point>("Content/Splash_UTG.mp4", new Point(1280, 720)));
|
||||||
|
pendingSplashScreens?.Enqueue(new Pair<string, Point>("Content/Splash_FF.mp4", new Point(1280, 720)));
|
||||||
|
pendingSplashScreens?.Enqueue(new Pair<string, Point>("Content/Splash_Daedalic.mp4", new Point(1920, 1080)));
|
||||||
|
}
|
||||||
|
|
||||||
|
//if not loading in a separate thread, wait for the splash screens to finish before continuing the loading
|
||||||
|
//otherwise the videos will look extremely choppy
|
||||||
|
if (!isSeparateThread)
|
||||||
|
{
|
||||||
|
while (TitleScreen.PlayingSplashScreen || TitleScreen.PendingSplashScreens.Count > 0)
|
||||||
{
|
{
|
||||||
(TitleScreen as LoadingScreen).SplashScreen = new Video(base.GraphicsDevice, SoundManager, "Content/splashscreen.mp4", 1280, 720);
|
yield return CoroutineStatus.Running;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
}
|
||||||
{
|
|
||||||
Config.EnableSplashScreen = false;
|
|
||||||
DebugConsole.ThrowError("Playing the splash screen failed.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GUI.Init(Window, Config.SelectedContentPackages, GraphicsDevice);
|
GUI.Init(Window, Config.SelectedContentPackages, GraphicsDevice);
|
||||||
DebugConsole.Init();
|
DebugConsole.Init();
|
||||||
|
|||||||
@@ -74,12 +74,17 @@ namespace Barotrauma
|
|||||||
public CrewManager(XElement element, bool isSinglePlayer)
|
public CrewManager(XElement element, bool isSinglePlayer)
|
||||||
: this(isSinglePlayer)
|
: this(isSinglePlayer)
|
||||||
{
|
{
|
||||||
if (!isSinglePlayer)
|
if (GameMain.Client != null)
|
||||||
{
|
{
|
||||||
DebugConsole.ThrowError("Cannot add messages to single player chat box in multiplayer mode!\n" + Environment.StackTrace);
|
//let the server create random conversations in MP
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (string.IsNullOrEmpty(text)) { return; }
|
List<Character> availableSpeakers = Character.CharacterList.FindAll(c =>
|
||||||
|
c.AIController is HumanAIController &&
|
||||||
|
!c.IsDead &&
|
||||||
|
c.SpeechImpediment <= 100.0f);
|
||||||
|
pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers));
|
||||||
|
}
|
||||||
|
|
||||||
var characterInfo = new CharacterInfo(subElement);
|
var characterInfo = new CharacterInfo(subElement);
|
||||||
characterInfos.Add(characterInfo);
|
characterInfos.Add(characterInfo);
|
||||||
@@ -90,7 +95,6 @@ namespace Barotrauma
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ChatBox.AddMessage(ChatMessage.Create(senderName, text, messageType, sender));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void InitProjectSpecific()
|
partial void InitProjectSpecific()
|
||||||
@@ -195,7 +199,6 @@ namespace Barotrauma
|
|||||||
if (Character.Controlled == null || Character.Controlled.SpeechImpediment >= 100.0f) return false;
|
if (Character.Controlled == null || Character.Controlled.SpeechImpediment >= 100.0f) return false;
|
||||||
SetCharacterOrder(null, order, null, Character.Controlled);
|
SetCharacterOrder(null, order, null, Character.Controlled);
|
||||||
HumanAIController.PropagateHullSafety(Character.Controlled, Character.Controlled.CurrentHull);
|
HumanAIController.PropagateHullSafety(Character.Controlled, Character.Controlled.CurrentHull);
|
||||||
HumanAIController.RefreshTargets(Character.Controlled, order, Character.Controlled.CurrentHull);
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
UserData = order,
|
UserData = order,
|
||||||
@@ -239,27 +242,24 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public IEnumerable<Character> GetCharacters()
|
public IEnumerable<Character> GetCharacters()
|
||||||
{
|
{
|
||||||
if (character?.Inventory == null) return null;
|
if (characterInfos.Contains(characterInfo))
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError("Tried to add the same character info to CrewManager twice.\n" + Environment.StackTrace);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var radioItem = character.Inventory.Items.FirstOrDefault(it => it != null && it.GetComponent<WifiComponent>() != null);
|
characterInfos.Add(characterInfo);
|
||||||
if (radioItem == null) return null;
|
|
||||||
if (requireEquipped && !character.HasEquippedItem(radioItem)) return null;
|
|
||||||
|
|
||||||
return radioItem.GetComponent<WifiComponent>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<CharacterInfo> GetCharacterInfos()
|
public IEnumerable<CharacterInfo> GetCharacterInfos()
|
||||||
{
|
{
|
||||||
if (GameMain.Client != null)
|
if (character == null)
|
||||||
{
|
{
|
||||||
//let the server create random conversations in MP
|
DebugConsole.ThrowError("Tried to remove a null character from CrewManager.\n" + Environment.StackTrace);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
List<Character> availableSpeakers = Character.CharacterList.FindAll(c =>
|
characters.Remove(character);
|
||||||
c.AIController is HumanAIController &&
|
if (removeInfo) characterInfos.Remove(character.Info);
|
||||||
!c.IsDead &&
|
|
||||||
c.SpeechImpediment <= 100.0f);
|
|
||||||
pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddCharacter(Character character)
|
public void AddCharacter(Character character)
|
||||||
@@ -633,9 +633,183 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
characterListBox.BarScroll = roundedPos;
|
characterListBox.BarScroll = roundedPos;
|
||||||
}
|
}
|
||||||
soundIcon.Visible = !muted && !mutedLocally;
|
var characterArea = new GUIButton(new RectTransform(new Point(characterInfoWidth, frame.Rect.Height), frame.RectTransform, Anchor.CenterLeft), style: "GUITextBox")
|
||||||
soundIconDisabled.Visible = muted || mutedLocally;
|
{
|
||||||
soundIconDisabled.ToolTip = TextManager.Get(mutedLocally ? "MutedLocally" : "MutedGlobally");
|
UserData = character,
|
||||||
|
Color = frame.Color,
|
||||||
|
SelectedColor = frame.SelectedColor,
|
||||||
|
HoverColor = frame.HoverColor,
|
||||||
|
ToolTip = characterToolTip
|
||||||
|
};
|
||||||
|
|
||||||
|
var soundIcon = new GUIImage(new RectTransform(new Point((int)(characterArea.Rect.Height * 0.5f)), characterArea.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(5, 0) },
|
||||||
|
"GUISoundIcon")
|
||||||
|
{
|
||||||
|
UserData = "soundicon",
|
||||||
|
CanBeFocused = false,
|
||||||
|
Visible = true
|
||||||
|
};
|
||||||
|
soundIcon.Color = new Color(soundIcon.Color, 0.0f);
|
||||||
|
new GUIImage(new RectTransform(new Point((int)(characterArea.Rect.Height * 0.5f)), characterArea.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(5, 0) },
|
||||||
|
"GUISoundIconDisabled")
|
||||||
|
{
|
||||||
|
UserData = "soundicondisabled",
|
||||||
|
CanBeFocused = true,
|
||||||
|
Visible = false
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isSinglePlayer)
|
||||||
|
{
|
||||||
|
characterArea.OnClicked = CharacterClicked;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
characterArea.CanBeFocused = false;
|
||||||
|
characterArea.CanBeSelected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var characterImage = new GUICustomComponent(new RectTransform(new Point(characterArea.Rect.Height), characterArea.RectTransform, Anchor.CenterLeft),
|
||||||
|
onDraw: (sb, component) => character.Info.DrawIcon(sb, component.Rect.Center.ToVector2(), targetAreaSize: component.Rect.Size.ToVector2()))
|
||||||
|
{
|
||||||
|
CanBeFocused = false,
|
||||||
|
HoverColor = Color.White,
|
||||||
|
SelectedColor = Color.White,
|
||||||
|
ToolTip = characterToolTip
|
||||||
|
};
|
||||||
|
|
||||||
|
var characterName = new GUITextBlock(new RectTransform(new Point(characterArea.Rect.Width - characterImage.Rect.Width - soundIcon.Rect.Width - 10, characterArea.Rect.Height),
|
||||||
|
characterArea.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(soundIcon.Rect.Width + 10, 0) },
|
||||||
|
character.Name, textColor: frame.Color, font: GUI.SmallFont, wrap: true)
|
||||||
|
{
|
||||||
|
Color = frame.Color,
|
||||||
|
HoverColor = Color.Transparent,
|
||||||
|
SelectedColor = Color.Transparent,
|
||||||
|
CanBeFocused = false,
|
||||||
|
ToolTip = characterToolTip,
|
||||||
|
AutoScale = true
|
||||||
|
};
|
||||||
|
|
||||||
|
//---------------- order buttons ----------------
|
||||||
|
|
||||||
|
var orderButtonFrame = new GUILayoutGroup(new RectTransform(new Point(100, frame.Rect.Height), frame.RectTransform)
|
||||||
|
{ AbsoluteOffset = new Point(characterInfoWidth + spacing, 0) },
|
||||||
|
isHorizontal: true, childAnchor: Anchor.CenterLeft)
|
||||||
|
{
|
||||||
|
AbsoluteSpacing = (int)(10 * GUI.Scale),
|
||||||
|
UserData = "orderbuttons",
|
||||||
|
CanBeFocused = false
|
||||||
|
};
|
||||||
|
|
||||||
|
//listbox for holding the orders inappropriate for this character
|
||||||
|
//(so we can easily toggle their visibility)
|
||||||
|
var wrongOrderList = new GUIListBox(new RectTransform(new Point(50, orderButtonFrame.Rect.Height), orderButtonFrame.RectTransform), isHorizontal: true, style: null)
|
||||||
|
{
|
||||||
|
ScrollBarEnabled = false,
|
||||||
|
ScrollBarVisible = false,
|
||||||
|
Enabled = false,
|
||||||
|
Spacing = spacing,
|
||||||
|
ClampMouseRectToParent = false
|
||||||
|
};
|
||||||
|
wrongOrderList.Content.ClampMouseRectToParent = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < orders.Count; i++)
|
||||||
|
{
|
||||||
|
var order = orders[i];
|
||||||
|
if (order.TargetAllCharacters) continue;
|
||||||
|
|
||||||
|
RectTransform btnParent = (i >= correctOrderCount + neutralOrderCount) ?
|
||||||
|
wrongOrderList.Content.RectTransform :
|
||||||
|
orderButtonFrame.RectTransform;
|
||||||
|
|
||||||
|
var btn = new GUIButton(new RectTransform(new Point(iconSize, iconSize), btnParent, Anchor.CenterLeft),
|
||||||
|
style: null)
|
||||||
|
{
|
||||||
|
UserData = order
|
||||||
|
};
|
||||||
|
|
||||||
|
new GUIFrame(new RectTransform(new Vector2(1.5f), btn.RectTransform, Anchor.Center), "OuterGlow")
|
||||||
|
{
|
||||||
|
Color = Color.Lerp(order.Color, frame.Color, 0.5f) * 0.8f,
|
||||||
|
HoverColor = Color.Lerp(order.Color, frame.Color, 0.5f) * 1.0f,
|
||||||
|
PressedColor = Color.Lerp(order.Color, frame.Color, 0.5f) * 0.6f,
|
||||||
|
UserData = "selected",
|
||||||
|
CanBeFocused = false,
|
||||||
|
Visible = false
|
||||||
|
};
|
||||||
|
|
||||||
|
var img = new GUIImage(new RectTransform(Vector2.One, btn.RectTransform), order.Prefab.SymbolSprite);
|
||||||
|
img.Scale = iconSize / (float)img.SourceRect.Width;
|
||||||
|
img.Color = Color.Lerp(order.Color, frame.Color, 0.5f);
|
||||||
|
img.ToolTip = order.Name;
|
||||||
|
img.HoverColor = Color.Lerp(img.Color, Color.White, 0.5f);
|
||||||
|
|
||||||
|
btn.OnClicked += (GUIButton button, object userData) =>
|
||||||
|
{
|
||||||
|
if (Character.Controlled == null || Character.Controlled.SpeechImpediment >= 100.0f) return false;
|
||||||
|
|
||||||
|
if (btn.GetChildByUserData("selected").Visible)
|
||||||
|
{
|
||||||
|
SetCharacterOrder(character, Order.PrefabList.Find(o => o.AITag == "dismissed"), null, Character.Controlled);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (order.ItemComponentType != null || order.ItemIdentifiers.Length > 0 || order.Options.Length > 1)
|
||||||
|
{
|
||||||
|
CreateOrderTargetFrame(button, character, order);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetCharacterOrder(character, order, null, Character.Controlled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
btn.UserData = order;
|
||||||
|
btn.ToolTip = order.Name;
|
||||||
|
|
||||||
|
//divider between different groups of orders
|
||||||
|
if (i == correctOrderCount - 1 || i == correctOrderCount + neutralOrderCount - 1)
|
||||||
|
{
|
||||||
|
//TODO: divider sprite
|
||||||
|
new GUIFrame(new RectTransform(new Point(8, iconSize), orderButtonFrame.RectTransform), style: "GUIButton");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var toggleWrongOrderBtn = new GUIButton(new RectTransform(new Point((int)(30 * GUI.Scale), wrongOrderList.Rect.Height), wrongOrderList.Content.RectTransform),
|
||||||
|
"", style: "UIToggleButton")
|
||||||
|
{
|
||||||
|
UserData = "togglewrongorder",
|
||||||
|
CanBeFocused = false
|
||||||
|
};
|
||||||
|
|
||||||
|
wrongOrderList.RectTransform.NonScaledSize = new Point(
|
||||||
|
wrongOrderList.Content.Children.Sum(c => c.Rect.Width + wrongOrderList.Spacing),
|
||||||
|
wrongOrderList.RectTransform.NonScaledSize.Y);
|
||||||
|
wrongOrderList.RectTransform.SetAsLastChild();
|
||||||
|
|
||||||
|
new GUIFrame(new RectTransform(new Point(
|
||||||
|
wrongOrderList.Rect.Width - toggleWrongOrderBtn.Rect.Width - wrongOrderList.Spacing * 2,
|
||||||
|
wrongOrderList.Rect.Height), wrongOrderList.Content.RectTransform),
|
||||||
|
style: null)
|
||||||
|
{
|
||||||
|
CanBeFocused = false
|
||||||
|
};
|
||||||
|
|
||||||
|
//scale to fit the content
|
||||||
|
orderButtonFrame.RectTransform.NonScaledSize = new Point(
|
||||||
|
orderButtonFrame.Children.Sum(c => c.Rect.Width + orderButtonFrame.AbsoluteSpacing),
|
||||||
|
orderButtonFrame.RectTransform.NonScaledSize.Y);
|
||||||
|
|
||||||
|
frame.RectTransform.NonScaledSize = new Point(
|
||||||
|
characterInfoWidth + spacing + (orderButtonFrame.Rect.Width - wrongOrderList.Rect.Width),
|
||||||
|
frame.RectTransform.NonScaledSize.Y);
|
||||||
|
|
||||||
|
characterListBox.RectTransform.NonScaledSize = new Point(
|
||||||
|
characterListBox.Content.Children.Max(c => c.Rect.Width) + wrongOrderList.Rect.Width,
|
||||||
|
characterListBox.RectTransform.NonScaledSize.Y);
|
||||||
|
characterListBox.Content.RectTransform.NonScaledSize = characterListBox.RectTransform.NonScaledSize;
|
||||||
|
characterListBox.UpdateScrollBarSize();
|
||||||
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<object> KillCharacterAnim(GUIComponent component)
|
private IEnumerable<object> KillCharacterAnim(GUIComponent component)
|
||||||
@@ -779,6 +953,12 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
List<Character> availableSpeakers = Character.CharacterList.FindAll(c =>
|
||||||
|
c.AIController is HumanAIController &&
|
||||||
|
!c.IsDead &&
|
||||||
|
c.SpeechImpediment <= 100.0f);
|
||||||
|
pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers));
|
||||||
|
}
|
||||||
|
|
||||||
character.SetOrder(order, option, orderGiver, speak: orderGiver != character);
|
character.SetOrder(order, option, orderGiver, speak: orderGiver != character);
|
||||||
if (IsSinglePlayer)
|
if (IsSinglePlayer)
|
||||||
@@ -836,19 +1016,23 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//only one target (or an order with no particular targets), just show options
|
|
||||||
else
|
character.SetOrder(order, option, orderGiver, speak: orderGiver != character);
|
||||||
|
if (IsSinglePlayer)
|
||||||
{
|
{
|
||||||
orderTargetFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.2f + order.Options.Length * 0.1f, 0.18f), GUI.Canvas)
|
orderGiver?.Speak(
|
||||||
{ AbsoluteOffset = new Point(orderButton.Rect.Center.X, orderButton.Rect.Bottom) },
|
order.GetChatMessage(character.Name, orderGiver.CurrentHull?.DisplayName, givingOrderToSelf: character == orderGiver, orderOption: option), null);
|
||||||
isHorizontal: true, childAnchor: Anchor.BottomLeft)
|
}
|
||||||
|
else if (orderGiver != null)
|
||||||
|
{
|
||||||
|
OrderChatMessage msg = new OrderChatMessage(order, option, order.TargetItemComponent?.Item, character, orderGiver);
|
||||||
|
if (GameMain.Client != null)
|
||||||
{
|
{
|
||||||
UserData = character,
|
GameMain.Client.SendChatMessage(msg);
|
||||||
Stretch = true
|
}
|
||||||
};
|
}
|
||||||
//line connecting the order button to the option buttons
|
DisplayCharacterOrder(character, order);
|
||||||
//TODO: sprite
|
}
|
||||||
new GUIFrame(new RectTransform(new Vector2(0.5f, 1.0f), orderTargetFrame.RectTransform), style: null);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create the UI panel that's used to select the target and options for a given order
|
/// Create the UI panel that's used to select the target and options for a given order
|
||||||
|
|||||||
@@ -776,37 +776,6 @@ namespace Barotrauma
|
|||||||
ic.DrawHUD(spriteBatch, character);
|
ic.DrawHUD(spriteBatch, character);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
List<ColoredText> texts = new List<ColoredText>();
|
|
||||||
public List<ColoredText> GetHUDTexts(Character character)
|
|
||||||
{
|
|
||||||
texts.Clear();
|
|
||||||
foreach (ItemComponent ic in components)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(ic.DisplayMsg)) continue;
|
|
||||||
if (!ic.CanBePicked && !ic.CanBeSelected) continue;
|
|
||||||
if (ic is Holdable holdable && !holdable.CanBeDeattached()) continue;
|
|
||||||
|
|
||||||
Color color = Color.Gray;
|
|
||||||
bool hasRequiredSkillsAndItems = ic.HasRequiredSkills(character) && ic.HasRequiredItems(character, false);
|
|
||||||
if (hasRequiredSkillsAndItems)
|
|
||||||
{
|
|
||||||
if (ic is Repairable repairable)
|
|
||||||
{
|
|
||||||
if (Condition < repairable.ShowRepairUIThreshold)
|
|
||||||
{
|
|
||||||
color = Color.Cyan;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
color = Color.Cyan;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
texts.Add(new ColoredText(ic.DisplayMsg, color, false));
|
|
||||||
}
|
|
||||||
return texts;
|
return texts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -276,6 +276,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private GUILayoutGroup subPreviewContainer;
|
private GUILayoutGroup subPreviewContainer;
|
||||||
|
|
||||||
|
private GUILayoutGroup subPreviewContainer;
|
||||||
|
|
||||||
private GUIButton loadGameButton;
|
private GUIButton loadGameButton;
|
||||||
|
|
||||||
public Action<Submarine, string, string> StartNewGame;
|
public Action<Submarine, string, string> StartNewGame;
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Graphics;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
@@ -11,7 +8,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
private GUIListBox listBox;
|
private GUIListBox listBox;
|
||||||
|
|
||||||
private float scrollSpeed;
|
private readonly float scrollSpeed;
|
||||||
|
|
||||||
public CreditsPlayer(RectTransform rectT, string configFile) : base(null, rectT)
|
public CreditsPlayer(RectTransform rectT, string configFile) : base(null, rectT)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -354,6 +354,12 @@ namespace Barotrauma
|
|||||||
};
|
};
|
||||||
var creditsContainer = new GUIFrame(new RectTransform(new Vector2(0.75f, 1.5f), menuTabs[(int)Tab.Credits].RectTransform, Anchor.CenterRight), style: "OuterGlow", color: Color.Black * 0.8f);
|
var creditsContainer = new GUIFrame(new RectTransform(new Vector2(0.75f, 1.5f), menuTabs[(int)Tab.Credits].RectTransform, Anchor.CenterRight), style: "OuterGlow", color: Color.Black * 0.8f);
|
||||||
creditsPlayer = new CreditsPlayer(new RectTransform(Vector2.One, creditsContainer.RectTransform), "Content/Texts/Credits.xml");
|
creditsPlayer = new CreditsPlayer(new RectTransform(Vector2.One, creditsContainer.RectTransform), "Content/Texts/Credits.xml");
|
||||||
|
|
||||||
|
new GUIButton(new RectTransform(new Vector2(0.1f, 0.05f), menuTabs[(int)Tab.Credits].RectTransform, Anchor.BottomLeft) { RelativeOffset = new Vector2(0.25f, 0.02f) },
|
||||||
|
TextManager.Get("Back"), style: "GUIButtonLarge")
|
||||||
|
{
|
||||||
|
OnClicked = SelectTab
|
||||||
|
};
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -383,10 +389,9 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private bool SelectTab(GUIButton button, object obj)
|
private bool SelectTab(GUIButton button, object obj)
|
||||||
{
|
{
|
||||||
|
titleText.Visible = true;
|
||||||
if (obj is Tab)
|
if (obj is Tab)
|
||||||
{
|
{
|
||||||
titleText.Visible = true;
|
|
||||||
|
|
||||||
if (GameMain.Config.UnsavedSettings)
|
if (GameMain.Config.UnsavedSettings)
|
||||||
{
|
{
|
||||||
var applyBox = new GUIMessageBox(
|
var applyBox = new GUIMessageBox(
|
||||||
|
|||||||
@@ -36,7 +36,6 @@
|
|||||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\AI\Objectives\AIObjectiveLoop.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\AI\Objectives\AIObjectiveLoop.cs" />
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\AI\Objectives\AIObjectiveManager.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\AI\Objectives\AIObjectiveManager.cs" />
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\AI\Objectives\AIObjectiveOperateItem.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\AI\Objectives\AIObjectiveOperateItem.cs" />
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\AI\Objectives\AIObjectiveFightIntruders.cs" />
|
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\AI\Objectives\AIObjectivePumpWater.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\AI\Objectives\AIObjectivePumpWater.cs" />
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\AI\Objectives\AIObjectiveRepairItem.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\AI\Objectives\AIObjectiveRepairItem.cs" />
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\AI\Objectives\AIObjectiveRepairItems.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\AI\Objectives\AIObjectiveRepairItems.cs" />
|
||||||
|
|||||||
@@ -527,6 +527,15 @@
|
|||||||
<Content Include="$(MSBuildThisFileDirectory)Content\NPCConversations\NpcConversations_TraditionalChinese.xml">
|
<Content Include="$(MSBuildThisFileDirectory)Content\NPCConversations\NpcConversations_TraditionalChinese.xml">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
<Content Include="$(MSBuildThisFileDirectory)Content\Splash_Daedalic.mp4">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Include="$(MSBuildThisFileDirectory)Content\Splash_FF.mp4">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Include="$(MSBuildThisFileDirectory)Content\Splash_UTG.mp4">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
<Content Include="$(MSBuildThisFileDirectory)Content\Texts\BrazilianPortugueseVanilla.xml">
|
<Content Include="$(MSBuildThisFileDirectory)Content\Texts\BrazilianPortugueseVanilla.xml">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
@@ -1631,9 +1640,6 @@
|
|||||||
<Content Include="$(MSBuildThisFileDirectory)Content\Particles\UnderwaterExplosionSheet.png">
|
<Content Include="$(MSBuildThisFileDirectory)Content\Particles\UnderwaterExplosionSheet.png">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
<Content Include="$(MSBuildThisFileDirectory)Content\splashscreen.mp4">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="$(MSBuildThisFileDirectory)Content\Texts\EnglishVanilla.xml">
|
<Content Include="$(MSBuildThisFileDirectory)Content\Texts\EnglishVanilla.xml">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
@@ -3146,9 +3152,6 @@
|
|||||||
<None Include="$(MSBuildThisFileDirectory)Content\Sounds\PickItemFail.ogg">
|
<None Include="$(MSBuildThisFileDirectory)Content\Sounds\PickItemFail.ogg">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
<None Include="$(MSBuildThisFileDirectory)Content\Sounds\StartDrone.ogg">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Include="$(MSBuildThisFileDirectory)Content\Sounds\UI\ChatMsg.ogg">
|
<None Include="$(MSBuildThisFileDirectory)Content\Sounds\UI\ChatMsg.ogg">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ using Microsoft.Xna.Framework;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Barotrauma.Extensions;
|
|
||||||
using Barotrauma.Items.Components;
|
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
@@ -14,16 +12,13 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private AIObjectiveManager objectiveManager;
|
private AIObjectiveManager objectiveManager;
|
||||||
|
|
||||||
private float sortTimer;
|
private float updateObjectiveTimer;
|
||||||
private float crouchRaycastTimer;
|
|
||||||
private float reportTimer;
|
|
||||||
private bool shouldCrouch;
|
private bool shouldCrouch;
|
||||||
|
private float crouchRaycastTimer;
|
||||||
|
const float CrouchRaycastInterval = 1.0f;
|
||||||
|
|
||||||
const float crouchRaycastInterval = 1;
|
public const float HULL_SAFETY_THRESHOLD = 50;
|
||||||
const float reportInterval = 1;
|
|
||||||
const float sortObjectiveInterval = 1;
|
|
||||||
|
|
||||||
public static float HULL_SAFETY_THRESHOLD = 50;
|
|
||||||
|
|
||||||
public HashSet<Hull> UnsafeHulls { get; private set; } = new HashSet<Hull>();
|
public HashSet<Hull> UnsafeHulls { get; private set; } = new HashSet<Hull>();
|
||||||
|
|
||||||
@@ -53,6 +48,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
insideSteering = new IndoorsSteeringManager(this, true, false);
|
insideSteering = new IndoorsSteeringManager(this, true, false);
|
||||||
outsideSteering = new SteeringManager(this);
|
outsideSteering = new SteeringManager(this);
|
||||||
|
|
||||||
objectiveManager = new AIObjectiveManager(c);
|
objectiveManager = new AIObjectiveManager(c);
|
||||||
reportTimer = Rand.Range(0f, reportInterval);
|
reportTimer = Rand.Range(0f, reportInterval);
|
||||||
sortTimer = Rand.Range(0f, sortObjectiveInterval);
|
sortTimer = Rand.Range(0f, sortObjectiveInterval);
|
||||||
@@ -80,37 +76,35 @@ namespace Barotrauma
|
|||||||
Character.ClearInputs();
|
Character.ClearInputs();
|
||||||
|
|
||||||
objectiveManager.UpdateObjectives(deltaTime);
|
objectiveManager.UpdateObjectives(deltaTime);
|
||||||
if (sortTimer > 0.0f)
|
if (updateObjectiveTimer > 0.0f)
|
||||||
{
|
|
||||||
sortTimer -= deltaTime;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
objectiveManager.SortObjectives();
|
|
||||||
sortTimer = sortObjectiveInterval;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reportTimer > 0.0f)
|
|
||||||
{
|
{
|
||||||
reportTimer -= deltaTime;
|
reportTimer -= deltaTime;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (Character.SpeechImpediment < 100.0f)
|
objectiveManager.SortObjectives();
|
||||||
{
|
updateObjectiveTimer = UpdateObjectiveInterval;
|
||||||
ReportProblems();
|
|
||||||
UpdateSpeaking();
|
|
||||||
}
|
|
||||||
reportTimer = reportInterval;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (objectiveManager.CurrentObjective == null) { return; }
|
if (Character.SpeechImpediment < 100.0f)
|
||||||
|
{
|
||||||
|
ReportProblems();
|
||||||
|
UpdateSpeaking();
|
||||||
|
}
|
||||||
|
|
||||||
objectiveManager.DoCurrentObjective(deltaTime);
|
objectiveManager.DoCurrentObjective(deltaTime);
|
||||||
bool run = objectiveManager.CurrentObjective.ForceRun || objectiveManager.GetCurrentPriority() > AIObjectiveManager.RunPriority;
|
|
||||||
|
bool run = objectiveManager.GetCurrentPriority() > AIObjectiveManager.OrderPriority;
|
||||||
if (ObjectiveManager.CurrentObjective is AIObjectiveGoTo goTo && goTo.Target != null)
|
if (ObjectiveManager.CurrentObjective is AIObjectiveGoTo goTo && goTo.Target != null)
|
||||||
{
|
{
|
||||||
run = Vector2.DistanceSquared(Character.SimPosition, goTo.Target.SimPosition) > 3 * 3;
|
if (Vector2.DistanceSquared(Character.SimPosition, goTo.Target.SimPosition) > 3 * 3)
|
||||||
|
{
|
||||||
|
run = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!run)
|
||||||
|
{
|
||||||
|
run = objectiveManager.CurrentObjective.ForceRun;
|
||||||
}
|
}
|
||||||
if (run)
|
if (run)
|
||||||
{
|
{
|
||||||
@@ -167,7 +161,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
bool oxygenLow = Character.OxygenAvailable < CharacterHealth.LowOxygenThreshold;
|
bool oxygenLow = Character.OxygenAvailable < CharacterHealth.LowOxygenThreshold;
|
||||||
bool highPressure = Character.CurrentHull == null || Character.CurrentHull.LethalPressure > 0 && Character.PressureProtection <= 0;
|
bool highPressure = Character.CurrentHull == null || Character.CurrentHull.LethalPressure > 0 && Character.PressureProtection <= 0;
|
||||||
bool shouldKeepTheGearOn = !ObjectiveManager.IsCurrentObjective<AIObjectiveIdle>();
|
bool shouldKeepTheGearOn = objectiveManager.CurrentObjective.KeepDivingGearOn;
|
||||||
|
|
||||||
bool removeDivingSuit = (oxygenLow && !highPressure) || (!shouldKeepTheGearOn && Character.CurrentHull.WaterPercentage < 1 && !Character.IsClimbing && steeringManager == insideSteering && !PathSteering.InStairs);
|
bool removeDivingSuit = (oxygenLow && !highPressure) || (!shouldKeepTheGearOn && Character.CurrentHull.WaterPercentage < 1 && !Character.IsClimbing && steeringManager == insideSteering && !PathSteering.InStairs);
|
||||||
if (removeDivingSuit)
|
if (removeDivingSuit)
|
||||||
@@ -193,7 +187,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!ObjectiveManager.IsCurrentObjective<AIObjectiveExtinguishFires>() && !ObjectiveManager.IsCurrentObjective<AIObjectiveExtinguishFire>())
|
if (!(ObjectiveManager.CurrentOrder is AIObjectiveExtinguishFires) && !(ObjectiveManager.CurrentObjective is AIObjectiveExtinguishFire))
|
||||||
{
|
{
|
||||||
var extinguisherItem = Character.Inventory.FindItemByIdentifier("extinguisher") ?? Character.Inventory.FindItemByTag("extinguisher");
|
var extinguisherItem = Character.Inventory.FindItemByIdentifier("extinguisher") ?? Character.Inventory.FindItemByTag("extinguisher");
|
||||||
if (extinguisherItem != null && Character.HasEquippedItem(extinguisherItem))
|
if (extinguisherItem != null && Character.HasEquippedItem(extinguisherItem))
|
||||||
@@ -202,21 +196,6 @@ namespace Barotrauma
|
|||||||
extinguisherItem.Drop(Character);
|
extinguisherItem.Drop(Character);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach (var item in Character.Inventory.Items)
|
|
||||||
{
|
|
||||||
if (item == null) { continue; }
|
|
||||||
if (ObjectiveManager.CurrentObjective is AIObjectiveIdle)
|
|
||||||
{
|
|
||||||
if (item.AllowedSlots.Contains(InvSlotType.RightHand | InvSlotType.LeftHand) && Character.HasEquippedItem(item))
|
|
||||||
{
|
|
||||||
// Try to put the weapon in an Any slot, and drop it if that fails
|
|
||||||
if (!item.AllowedSlots.Contains(InvSlotType.Any) || !Character.Inventory.TryPutItem(item, Character, new List<InvSlotType>() { InvSlotType.Any }))
|
|
||||||
{
|
|
||||||
item.Drop(Character);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Character.IsKeyDown(InputType.Aim))
|
if (Character.IsKeyDown(InputType.Aim))
|
||||||
{
|
{
|
||||||
@@ -249,70 +228,41 @@ namespace Barotrauma
|
|||||||
Order newOrder = null;
|
Order newOrder = null;
|
||||||
if (Character.CurrentHull != null)
|
if (Character.CurrentHull != null)
|
||||||
{
|
{
|
||||||
if (AIObjectiveExtinguishFires.IsValidTarget(Character.CurrentHull, Character))
|
if (Character.CurrentHull.FireSources.Count > 0)
|
||||||
{
|
{
|
||||||
if (AddTargets<AIObjectiveExtinguishFires, Hull>(Character, Character.CurrentHull))
|
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportfire");
|
||||||
{
|
newOrder = new Order(orderPrefab, Character.CurrentHull, null);
|
||||||
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportfire");
|
|
||||||
newOrder = new Order(orderPrefab, Character.CurrentHull, null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var gap in Character.CurrentHull.ConnectedGaps)
|
if (Character.CurrentHull.ConnectedGaps.Any(g => !g.IsRoomToRoom && g.ConnectedDoor == null && g.Open > 0.0f))
|
||||||
{
|
{
|
||||||
if (AIObjectiveFixLeaks.IsValidTarget(gap, Character))
|
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportbreach");
|
||||||
{
|
newOrder = new Order(orderPrefab, Character.CurrentHull, null);
|
||||||
if (AddTargets<AIObjectiveFixLeaks, Gap>(Character, gap))
|
|
||||||
{
|
|
||||||
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportbreach");
|
|
||||||
newOrder = new Order(orderPrefab, Character.CurrentHull, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (Item item in Item.ItemList)
|
|
||||||
{
|
|
||||||
if (item.CurrentHull != Character.CurrentHull) { continue; }
|
|
||||||
if (AIObjectiveRepairItems.IsValidTarget(item, Character))
|
|
||||||
{
|
|
||||||
if (item.Repairables.All(r => item.Condition > r.ShowRepairUIThreshold)) { continue; }
|
|
||||||
if (AddTargets<AIObjectiveRepairItems, Item>(Character, item))
|
|
||||||
{
|
|
||||||
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportbrokendevices");
|
|
||||||
newOrder = new Order(orderPrefab, Character.CurrentHull, item.Repairables?.FirstOrDefault());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Character c in Character.CharacterList)
|
foreach (Character c in Character.CharacterList)
|
||||||
{
|
{
|
||||||
if (c.CurrentHull != Character.CurrentHull) { continue; }
|
if (c.CurrentHull == Character.CurrentHull && !c.IsDead &&
|
||||||
if (AIObjectiveFightIntruders.IsValidTarget(c, Character))
|
(c.AIController is EnemyAIController || (c.TeamID != Character.TeamID && Character.TeamID != Character.TeamType.FriendlyNPC && c.TeamID != Character.TeamType.FriendlyNPC)))
|
||||||
{
|
{
|
||||||
if (AddTargets<AIObjectiveFightIntruders, Character>(Character, c))
|
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportintruders");
|
||||||
{
|
|
||||||
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportintruders");
|
|
||||||
newOrder = new Order(orderPrefab, Character.CurrentHull, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Character.Bleeding > 1.0f || Character.Vitality < Character.MaxVitality * 0.1f)
|
|
||||||
{
|
|
||||||
|
|
||||||
if (AddTargets<AIObjectiveRescueAll, Character>(Character, Character))
|
|
||||||
{
|
|
||||||
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "requestfirstaid");
|
|
||||||
newOrder = new Order(orderPrefab, Character.CurrentHull, null);
|
newOrder = new Order(orderPrefab, Character.CurrentHull, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Character.CurrentHull != null && (Character.Bleeding > 1.0f || Character.Vitality < Character.MaxVitality * 0.1f))
|
||||||
|
{
|
||||||
|
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "requestfirstaid");
|
||||||
|
newOrder = new Order(orderPrefab, Character.CurrentHull, null);
|
||||||
|
}
|
||||||
|
|
||||||
if (newOrder != null)
|
if (newOrder != null)
|
||||||
{
|
{
|
||||||
if (GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime))
|
if (GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime))
|
||||||
{
|
{
|
||||||
Character.Speak(newOrder.GetChatMessage("", Character.CurrentHull?.DisplayName, givingOrderToSelf: false), ChatMessageType.Order);
|
Character.Speak(
|
||||||
|
newOrder.GetChatMessage("", Character.CurrentHull?.DisplayName, givingOrderToSelf: false), ChatMessageType.Order);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -339,7 +289,6 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
float damage = attackResult.Damage;
|
float damage = attackResult.Damage;
|
||||||
if (damage <= 0) { return; }
|
if (damage <= 0) { return; }
|
||||||
if (ObjectiveManager.CurrentObjective is AIObjectiveFightIntruders) { return; }
|
|
||||||
if (attacker == null || attacker.IsDead || attacker.Removed)
|
if (attacker == null || attacker.IsDead || attacker.Removed)
|
||||||
{
|
{
|
||||||
// Ignore damage from falling etc that we shouldn't react to.
|
// Ignore damage from falling etc that we shouldn't react to.
|
||||||
@@ -387,18 +336,18 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
// Replace the old objective with the new.
|
// Replace the old objective with the new.
|
||||||
ObjectiveManager.Objectives.Remove(combatObjective);
|
ObjectiveManager.Objectives.Remove(combatObjective);
|
||||||
objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode, objectiveManager));
|
objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (delay > 0)
|
if (delay > 0)
|
||||||
{
|
{
|
||||||
objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode, objectiveManager), delay);
|
objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode), delay);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode, objectiveManager));
|
objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -409,10 +358,8 @@ namespace Barotrauma
|
|||||||
CurrentOrderOption = option;
|
CurrentOrderOption = option;
|
||||||
CurrentOrder = order;
|
CurrentOrder = order;
|
||||||
objectiveManager.SetOrder(order, option, orderGiver);
|
objectiveManager.SetOrder(order, option, orderGiver);
|
||||||
if (speak && Character.SpeechImpediment < 100.0f)
|
if (speak && Character.SpeechImpediment < 100.0f) Character.Speak(TextManager.Get("DialogAffirmative"), null, 1.0f);
|
||||||
{
|
|
||||||
Character.Speak(TextManager.Get("DialogAffirmative"), null, 1.0f);
|
|
||||||
}
|
|
||||||
SetOrderProjSpecific(order);
|
SetOrderProjSpecific(order);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -428,7 +375,7 @@ namespace Barotrauma
|
|||||||
crouchRaycastTimer -= deltaTime;
|
crouchRaycastTimer -= deltaTime;
|
||||||
if (crouchRaycastTimer > 0.0f) return;
|
if (crouchRaycastTimer > 0.0f) return;
|
||||||
|
|
||||||
crouchRaycastTimer = crouchRaycastInterval;
|
crouchRaycastTimer = CrouchRaycastInterval;
|
||||||
|
|
||||||
//start the raycast in front of the character in the direction it's heading to
|
//start the raycast in front of the character in the direction it's heading to
|
||||||
Vector2 startPos = Character.SimPosition;
|
Vector2 startPos = Character.SimPosition;
|
||||||
@@ -449,7 +396,7 @@ namespace Barotrauma
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check whether the character has a diving mask in usable condition plus some oxygen.
|
/// Check whether the character has a diving mask in usable condition plus some oxygen.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool HasDivingMask(Character character) => HasItem(character, "diving", "oxygensource");
|
public static bool HasDivingGear(Character character) => HasItem(character, "diving", "oxygensource");
|
||||||
|
|
||||||
public static bool HasItem(Character character, string tag, string containedTag, float conditionPercentage = 0)
|
public static bool HasItem(Character character, string tag, string containedTag, float conditionPercentage = 0)
|
||||||
{
|
{
|
||||||
@@ -469,9 +416,12 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
foreach (var c in Character.CharacterList)
|
foreach (var c in Character.CharacterList)
|
||||||
{
|
{
|
||||||
if (c.AIController is HumanAIController humanAi && humanAi.IsFriendly(character))
|
if (c.TeamID == character.TeamID)
|
||||||
{
|
{
|
||||||
humanAi.RefreshHullSafety(hull);
|
if (c.AIController is HumanAIController humanAi)
|
||||||
|
{
|
||||||
|
humanAi.RefreshHullSafety(hull);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -488,86 +438,13 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RefreshTargets(Character character, Order order, Hull hull)
|
|
||||||
{
|
|
||||||
switch (order.AITag)
|
|
||||||
{
|
|
||||||
case "reportfire":
|
|
||||||
AddTargets<AIObjectiveExtinguishFires, Hull>(character, hull);
|
|
||||||
break;
|
|
||||||
case "reportbreach":
|
|
||||||
foreach (var gap in hull.ConnectedGaps)
|
|
||||||
{
|
|
||||||
if (AIObjectiveFixLeaks.IsValidTarget(gap, character))
|
|
||||||
{
|
|
||||||
AddTargets<AIObjectiveFixLeaks, Gap>(character, gap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "reportbrokendevices":
|
|
||||||
foreach (var item in Item.ItemList)
|
|
||||||
{
|
|
||||||
if (item.CurrentHull != hull) { continue; }
|
|
||||||
if (AIObjectiveRepairItems.IsValidTarget(item, character))
|
|
||||||
{
|
|
||||||
if (item.Repairables.All(r => item.Condition > r.ShowRepairUIThreshold)) { continue; }
|
|
||||||
AddTargets<AIObjectiveRepairItems, Item>(character, item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "reportintruders":
|
|
||||||
foreach (var enemy in Character.CharacterList)
|
|
||||||
{
|
|
||||||
if (enemy.CurrentHull != hull) { continue; }
|
|
||||||
if (AIObjectiveFightIntruders.IsValidTarget(enemy, character))
|
|
||||||
{
|
|
||||||
AddTargets<AIObjectiveFightIntruders, Character>(character, enemy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "requestfirstaid":
|
|
||||||
foreach (var c in Character.CharacterList)
|
|
||||||
{
|
|
||||||
if (c.CurrentHull != hull) { continue; }
|
|
||||||
if (AIObjectiveRescueAll.IsValidTarget(c, character))
|
|
||||||
{
|
|
||||||
AddTargets<AIObjectiveRescueAll, Character>(character, c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
#if DEBUG
|
|
||||||
DebugConsole.ThrowError(order.AITag + " not implemented!");
|
|
||||||
#endif
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool AddTargets<T1, T2>(Character caller, T2 target) where T1 : AIObjectiveLoop<T2>
|
|
||||||
{
|
|
||||||
bool targetAdded = false;
|
|
||||||
foreach (var c in Character.CharacterList)
|
|
||||||
{
|
|
||||||
if (IsFriendly(caller, c) && c.AIController is HumanAIController humanAI)
|
|
||||||
{
|
|
||||||
var objective = humanAI.ObjectiveManager.GetObjective<T1>();
|
|
||||||
if (objective == null) { continue; }
|
|
||||||
if (!targetAdded && objective.AddTarget(target))
|
|
||||||
{
|
|
||||||
targetAdded = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return targetAdded;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float GetHullSafety(Hull hull)
|
public float GetHullSafety(Hull hull)
|
||||||
{
|
{
|
||||||
if (hull == null) { return 0; }
|
if (hull == null) { return 0; }
|
||||||
bool ignoreFire = ObjectiveManager.IsCurrentObjective<AIObjectiveExtinguishFires>() || ObjectiveManager.IsCurrentObjective<AIObjectiveExtinguishFire>();
|
bool ignoreFire = ObjectiveManager.CurrentObjective is AIObjectiveExtinguishFire || ObjectiveManager.CurrentOrder is AIObjectiveExtinguishFires;
|
||||||
bool ignoreWater = HasDivingSuit(Character);
|
bool ignoreWater = HasDivingSuit(Character);
|
||||||
bool ignoreOxygen = ignoreWater || HasDivingMask(Character);
|
bool ignoreOxygen = ignoreWater || HasDivingGear(Character);
|
||||||
bool ignoreEnemies = ObjectiveManager.IsCurrentObjective<AIObjectiveFightIntruders>();
|
bool ignoreEnemies = ObjectiveManager.CurrentObjective is AIObjectiveCombat || ObjectiveManager.CurrentOrder is AIObjectiveCombat;
|
||||||
return GetHullSafety(hull, Character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies);
|
return GetHullSafety(hull, Character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -585,7 +462,7 @@ namespace Barotrauma
|
|||||||
// Even the smallest fire reduces the safety by 50%
|
// Even the smallest fire reduces the safety by 50%
|
||||||
float fire = hull.FireSources.Count * 0.5f + hull.FireSources.Sum(fs => fs.DamageRange) / hull.Size.X;
|
float fire = hull.FireSources.Count * 0.5f + hull.FireSources.Sum(fs => fs.DamageRange) / hull.Size.X;
|
||||||
float fireFactor = ignoreFire ? 1 : MathHelper.Lerp(1, 0, MathHelper.Clamp(fire, 0, 1));
|
float fireFactor = ignoreFire ? 1 : MathHelper.Lerp(1, 0, MathHelper.Clamp(fire, 0, 1));
|
||||||
int enemyCount = Character.CharacterList.Count(e =>
|
int enemyCount = Character.CharacterList.Count(e =>
|
||||||
e.CurrentHull == hull && !e.IsDead && !e.IsUnconscious &&
|
e.CurrentHull == hull && !e.IsDead && !e.IsUnconscious &&
|
||||||
(e.AIController is EnemyAIController || (e.TeamID != character.TeamID && character.TeamID != Character.TeamType.FriendlyNPC && e.TeamID != Character.TeamType.FriendlyNPC)));
|
(e.AIController is EnemyAIController || (e.TeamID != character.TeamID && character.TeamID != Character.TeamType.FriendlyNPC && e.TeamID != Character.TeamType.FriendlyNPC)));
|
||||||
// The hull safety decreases 90% per enemy up to 100% (TODO: test smaller percentages)
|
// The hull safety decreases 90% per enemy up to 100% (TODO: test smaller percentages)
|
||||||
@@ -594,8 +471,7 @@ namespace Barotrauma
|
|||||||
return MathHelper.Clamp(safety * 100, 0, 100);
|
return MathHelper.Clamp(safety * 100, 0, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsFriendly(Character other) => IsFriendly(Character, other);
|
|
||||||
// TODO: If the aliens are quaranteed to be in another team than the player, we wouldn't need to check the species.
|
// TODO: If the aliens are quaranteed to be in another team than the player, we wouldn't need to check the species.
|
||||||
public static bool IsFriendly(Character me, Character other) => other.TeamID == me.TeamID && other.SpeciesName == me.SpeciesName;
|
public bool IsFriendly(Character other) => other.TeamID == Character.TeamID && other.SpeciesName == Character.SpeciesName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -287,8 +287,7 @@ namespace Barotrauma
|
|||||||
character.AnimController.Anim = AnimController.Animation.None;
|
character.AnimController.Anim = AnimController.Animation.None;
|
||||||
character.SelectedConstruction = null;
|
character.SelectedConstruction = null;
|
||||||
}
|
}
|
||||||
float multiplier = MathHelper.Lerp(1, 10, MathHelper.Clamp(collider.LinearVelocity.Length() / 10, 0, 1));
|
if (Vector2.DistanceSquared(pos, currentPath.CurrentNode.SimPosition) < MathUtils.Pow(collider.radius * 3, 2))
|
||||||
if (Vector2.DistanceSquared(pos, currentPath.CurrentNode.SimPosition) < MathUtils.Pow(collider.radius * 2 * multiplier, 2))
|
|
||||||
{
|
{
|
||||||
currentPath.SkipToNextNode();
|
currentPath.SkipToNextNode();
|
||||||
}
|
}
|
||||||
@@ -297,13 +296,13 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
Vector2 colliderBottom = character.AnimController.GetColliderBottom();
|
Vector2 colliderBottom = character.AnimController.GetColliderBottom();
|
||||||
Vector2 colliderSize = collider.GetSize();
|
Vector2 colliderSize = collider.GetSize();
|
||||||
Vector2 velocity = collider.LinearVelocity;
|
|
||||||
// Cannot use the head position, because not all characters have head or it can be below the total height of the character
|
// Cannot use the head position, because not all characters have head or it can be below the total height of the character
|
||||||
float characterHeight = colliderSize.Y + character.AnimController.ColliderHeightFromFloor;
|
float characterHeight = colliderSize.Y + character.AnimController.ColliderHeightFromFloor;
|
||||||
float horizontalDistance = Math.Abs(collider.SimPosition.X - currentPath.CurrentNode.SimPosition.X);
|
float horizontalDistance = Math.Abs(collider.SimPosition.X - currentPath.CurrentNode.SimPosition.X);
|
||||||
bool isAboveFeet = currentPath.CurrentNode.SimPosition.Y > colliderBottom.Y;
|
bool isAboveFeet = currentPath.CurrentNode.SimPosition.Y > colliderBottom.Y;
|
||||||
bool isNotTooHigh = currentPath.CurrentNode.SimPosition.Y < colliderBottom.Y + characterHeight;
|
bool isNotTooHigh = currentPath.CurrentNode.SimPosition.Y < colliderBottom.Y + characterHeight;
|
||||||
if (InStairs)
|
|
||||||
|
if (horizontalDistance < collider.radius * 3 && isAboveFeet && isNotTooHigh)
|
||||||
{
|
{
|
||||||
float multiplierX = MathHelper.Lerp(1, 10, MathHelper.Clamp(Math.Abs(velocity.X) / 10, 0, 1));
|
float multiplierX = MathHelper.Lerp(1, 10, MathHelper.Clamp(Math.Abs(velocity.X) / 10, 0, 1));
|
||||||
float multiplierY = MathHelper.Lerp(1, 10, MathHelper.Clamp(Math.Abs(velocity.Y) / 10, 0, 1));
|
float multiplierY = MathHelper.Lerp(1, 10, MathHelper.Clamp(Math.Abs(velocity.Y) / 10, 0, 1));
|
||||||
@@ -351,8 +350,6 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
for (int i = 0; i < 2; i++)
|
for (int i = 0; i < 2; i++)
|
||||||
{
|
{
|
||||||
WayPoint currentWaypoint = null;
|
|
||||||
WayPoint nextWaypoint = null;
|
|
||||||
Door door = null;
|
Door door = null;
|
||||||
bool shouldBeOpen = false;
|
bool shouldBeOpen = false;
|
||||||
|
|
||||||
@@ -363,19 +360,21 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
WayPoint node = null;
|
||||||
|
WayPoint nextNode = null;
|
||||||
if (i == 0)
|
if (i == 0)
|
||||||
{
|
{
|
||||||
currentWaypoint = currentPath.CurrentNode;
|
node = currentPath.CurrentNode;
|
||||||
nextWaypoint = currentPath.NextNode;
|
nextNode = currentPath.NextNode;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
currentWaypoint = currentPath.PrevNode;
|
node = currentPath.PrevNode;
|
||||||
nextWaypoint = currentPath.CurrentNode;
|
nextNode = currentPath.CurrentNode;
|
||||||
}
|
}
|
||||||
if (currentWaypoint?.ConnectedDoor == null) { continue; }
|
if (node?.ConnectedDoor == null) continue;
|
||||||
|
|
||||||
if (nextWaypoint == null)
|
if (nextNode == null)
|
||||||
{
|
{
|
||||||
//the node we're heading towards is the last one in the path, and at a door
|
//the node we're heading towards is the last one in the path, and at a door
|
||||||
//the door needs to be open for the character to reach the node
|
//the door needs to be open for the character to reach the node
|
||||||
@@ -383,21 +382,21 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
door = currentWaypoint.ConnectedGap.ConnectedDoor;
|
door = node.ConnectedGap.ConnectedDoor;
|
||||||
if (door.LinkedGap.IsHorizontal)
|
if (door.LinkedGap.IsHorizontal)
|
||||||
{
|
{
|
||||||
int currentDir = Math.Sign(nextWaypoint.WorldPosition.X - door.Item.WorldPosition.X);
|
int currentDir = Math.Sign(nextNode.WorldPosition.X - door.Item.WorldPosition.X);
|
||||||
shouldBeOpen = (door.Item.WorldPosition.X - character.WorldPosition.X) * currentDir > -50.0f;
|
shouldBeOpen = (door.Item.WorldPosition.X - character.WorldPosition.X) * currentDir > -50.0f;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int currentDir = Math.Sign(nextWaypoint.WorldPosition.Y - door.Item.WorldPosition.Y);
|
int currentDir = Math.Sign(nextNode.WorldPosition.Y - door.Item.WorldPosition.Y);
|
||||||
shouldBeOpen = (door.Item.WorldPosition.Y - character.WorldPosition.Y) * currentDir > -80.0f;
|
shouldBeOpen = (door.Item.WorldPosition.Y - character.WorldPosition.Y) * currentDir > -80.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (door == null) { return; }
|
if (door == null) return;
|
||||||
|
|
||||||
//toggle the door if it's the previous node and open, or if it's current node and closed
|
//toggle the door if it's the previous node and open, or if it's current node and closed
|
||||||
if (door.IsOpen != shouldBeOpen)
|
if (door.IsOpen != shouldBeOpen)
|
||||||
@@ -423,29 +422,22 @@ namespace Barotrauma
|
|||||||
buttonPressCooldown = ButtonPressInterval;
|
buttonPressCooldown = ButtonPressInterval;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else if (closestButton != null)
|
|
||||||
{
|
closestButton.Item.TryInteract(character, false, true, false);
|
||||||
if (Vector2.DistanceSquared(closestButton.Item.WorldPosition, character.WorldPosition) < MathUtils.Pow(closestButton.Item.InteractDistance * 2, 2))
|
buttonPressCooldown = ButtonPressInterval;
|
||||||
{
|
break;
|
||||||
closestButton.Item.TryInteract(character, false, true, false);
|
|
||||||
buttonPressCooldown = ButtonPressInterval;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Can't reach the button closest to the door.
|
|
||||||
// It's possible that we could reach another buttons.
|
|
||||||
// If this becomes an issue, we could go through them here and check if any of them are reachable
|
|
||||||
// (would have to cache a collection of buttons instead of a single reference in the CanAccess filter method above)
|
|
||||||
currentPath.Unreachable = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (shouldBeOpen)
|
else
|
||||||
{
|
{
|
||||||
currentPath.Unreachable = true;
|
if (!door.HasRequiredItems(character, false) && shouldBeOpen)
|
||||||
return;
|
{
|
||||||
|
currentPath.Unreachable = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
door.Item.TryInteract(character, false, true, true);
|
||||||
|
buttonPressCooldown = ButtonPressInterval;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -453,38 +445,32 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private float? GetNodePenalty(PathNode node, PathNode nextNode)
|
private float? GetNodePenalty(PathNode node, PathNode nextNode)
|
||||||
{
|
{
|
||||||
if (character == null) { return 0.0f; }
|
if (character == null) { return 0.0f; }
|
||||||
|
|
||||||
float penalty = 0.0f;
|
float penalty = 0.0f;
|
||||||
if (nextNode.Waypoint.ConnectedGap != null && nextNode.Waypoint.ConnectedGap.Open < 0.9f)
|
if (nextNode.Waypoint.ConnectedGap != null && nextNode.Waypoint.ConnectedGap.Open < 0.9f)
|
||||||
{
|
{
|
||||||
var door = nextNode.Waypoint.ConnectedDoor;
|
if (nextNode.Waypoint.ConnectedDoor == null)
|
||||||
if (door == null)
|
|
||||||
{
|
{
|
||||||
penalty = 100.0f;
|
penalty = 100.0f;
|
||||||
}
|
}
|
||||||
else
|
else if (!canBreakDoors)
|
||||||
{
|
{
|
||||||
if (!CanAccessDoor(door, button =>
|
//door closed and the character can't open doors -> node can't be traversed
|
||||||
{
|
if (!canOpenDoors || character.LockHands) { return null; }
|
||||||
// Ignore buttons that are on the wrong side of the door
|
|
||||||
if (door.IsHorizontal)
|
var doorButtons = nextNode.Waypoint.ConnectedDoor.Item.GetConnectedComponents<Controller>();
|
||||||
{
|
if (!doorButtons.Any())
|
||||||
if (Math.Sign(button.Item.WorldPosition.Y - door.Item.WorldPosition.Y) != Math.Sign(character.WorldPosition.Y - door.Item.WorldPosition.Y))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (Math.Sign(button.Item.WorldPosition.X - door.Item.WorldPosition.X) != Math.Sign(character.WorldPosition.X - door.Item.WorldPosition.X))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}))
|
|
||||||
{
|
{
|
||||||
return null;
|
if (!nextNode.Waypoint.ConnectedDoor.HasRequiredItems(character, false)) { return null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Controller button in doorButtons)
|
||||||
|
{
|
||||||
|
if (Math.Sign(button.Item.Position.X - nextNode.Waypoint.Position.X) !=
|
||||||
|
Math.Sign(node.Position.X - nextNode.Position.X)) { continue; }
|
||||||
|
|
||||||
|
if (!button.HasRequiredItems(character, false)) { return null; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,21 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public abstract string DebugTag { get; }
|
public abstract string DebugTag { get; }
|
||||||
public virtual bool ForceRun => false;
|
public virtual bool ForceRun => false;
|
||||||
|
public virtual bool KeepDivingGearOn => false;
|
||||||
|
|
||||||
|
protected readonly List<AIObjective> subObjectives = new List<AIObjective>();
|
||||||
|
public float Priority { get; set; }
|
||||||
|
protected readonly Character character;
|
||||||
|
protected string option;
|
||||||
|
protected bool abandon;
|
||||||
|
|
||||||
|
public virtual bool CanBeCompleted => !abandon && subObjectives.All(so => so.CanBeCompleted);
|
||||||
|
public IEnumerable<AIObjective> SubObjectives => subObjectives;
|
||||||
|
public AIObjective CurrentSubObjective { get; private set; }
|
||||||
|
|
||||||
|
protected HumanAIController HumanAIController => character.AIController as HumanAIController;
|
||||||
|
protected IndoorsSteeringManager PathSteering => HumanAIController.PathSteering;
|
||||||
|
protected SteeringManager SteeringManager => HumanAIController.SteeringManager;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Run the main objective with all subobjectives concurrently?
|
/// Run the main objective with all subobjectives concurrently?
|
||||||
@@ -47,7 +62,6 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public AIObjective(Character character, AIObjectiveManager objectiveManager, float priorityModifier, string option = null)
|
public AIObjective(Character character, AIObjectiveManager objectiveManager, float priorityModifier, string option = null)
|
||||||
{
|
{
|
||||||
this.objectiveManager = objectiveManager;
|
|
||||||
this.character = character;
|
this.character = character;
|
||||||
Option = option ?? string.Empty;
|
Option = option ?? string.Empty;
|
||||||
|
|
||||||
@@ -80,6 +94,18 @@ namespace Barotrauma
|
|||||||
#endif
|
#endif
|
||||||
subObjectives.Remove(subObjective);
|
subObjectives.Remove(subObjective);
|
||||||
}
|
}
|
||||||
|
else if (subObjective.ShouldInterruptSubObjective(subObjective))
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
DebugConsole.NewMessage($"Removing subobjective {subObjective.DebugTag} of {DebugTag}, because it is interrupted.");
|
||||||
|
#endif
|
||||||
|
subObjectives.Remove(subObjective);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!subObjectives.Contains(CurrentSubObjective))
|
||||||
|
{
|
||||||
|
CurrentSubObjective = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (AIObjective objective in subObjectives)
|
foreach (AIObjective objective in subObjectives)
|
||||||
@@ -106,40 +132,29 @@ namespace Barotrauma
|
|||||||
subObjectives.Add(objective);
|
subObjectives.Add(objective);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SortSubObjectives()
|
public void SortSubObjectives(AIObjectiveManager objectiveManager)
|
||||||
{
|
{
|
||||||
if (subObjectives.None()) { return; }
|
if (subObjectives.None()) { return; }
|
||||||
subObjectives.Sort((x, y) => y.GetPriority().CompareTo(x.GetPriority()));
|
subObjectives.Sort((x, y) => y.GetPriority(objectiveManager).CompareTo(x.GetPriority(objectiveManager)));
|
||||||
if (ConcurrentObjectives)
|
CurrentSubObjective = SubObjectives.First();
|
||||||
{
|
CurrentSubObjective.SortSubObjectives(objectiveManager);
|
||||||
subObjectives.ForEach(so => so.SortSubObjectives());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
subObjectives.First().SortSubObjectives();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual float GetPriority() => Priority * PriorityModifier;
|
public virtual float GetPriority(AIObjectiveManager objectiveManager) => Priority;
|
||||||
|
|
||||||
public virtual void Update(float deltaTime)
|
public virtual void Update(AIObjectiveManager objectiveManager, float deltaTime)
|
||||||
{
|
{
|
||||||
|
var subObjective = objectiveManager.CurrentObjective?.CurrentSubObjective;
|
||||||
if (objectiveManager.CurrentOrder == this)
|
if (objectiveManager.CurrentOrder == this)
|
||||||
{
|
{
|
||||||
Priority = AIObjectiveManager.OrderPriority;
|
Priority = AIObjectiveManager.OrderPriority;
|
||||||
}
|
}
|
||||||
else if (objectiveManager.WaitTimer <= 0)
|
else if (objectiveManager.CurrentObjective == this || subObjective == this)
|
||||||
{
|
{
|
||||||
if (objectiveManager.CurrentObjective != null)
|
Priority += Devotion * deltaTime;
|
||||||
{
|
|
||||||
if (objectiveManager.CurrentObjective == this || objectiveManager.CurrentObjective.subObjectives.Any(so => so == this))
|
|
||||||
{
|
|
||||||
Priority += Devotion * PriorityModifier * deltaTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Priority = MathHelper.Clamp(Priority, 0, 100);
|
|
||||||
subObjectives.ForEach(so => so.Update(deltaTime));
|
|
||||||
}
|
}
|
||||||
|
Priority = MathHelper.Clamp(Priority, 0, 100);
|
||||||
|
subObjectives.ForEach(so => so.Update(objectiveManager, deltaTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -159,54 +174,13 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
protected virtual bool ShouldInterruptSubObjective(AIObjective subObjective) => false;
|
||||||
/// Checks if the objective already is created and added in subobjectives. If not, creates it.
|
public virtual void OnSelected() { }
|
||||||
/// Handles objectives that cannot be completed. If the objective has been removed form the subobjectives, a null value is assigned to the reference.
|
|
||||||
/// Returns true if the objective was created.
|
|
||||||
/// </summary>
|
|
||||||
protected bool TryAddSubObjective<T>(ref T objective, Func<T> constructor, Action onAbandon = null) where T : AIObjective
|
|
||||||
{
|
|
||||||
if (objective != null)
|
|
||||||
{
|
|
||||||
// Sub objective already found, no need to do anything if it remains in the subobjectives
|
|
||||||
// If the sub objective is removed -> it's either completed or impossible to complete.
|
|
||||||
if (!subObjectives.Contains(objective))
|
|
||||||
{
|
|
||||||
if (!objective.CanBeCompleted)
|
|
||||||
{
|
|
||||||
abandon = true;
|
|
||||||
onAbandon?.Invoke();
|
|
||||||
}
|
|
||||||
objective = null;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
objective = constructor();
|
|
||||||
if (!subObjectives.Contains(objective))
|
|
||||||
{
|
|
||||||
AddSubObjective(objective);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void OnSelected()
|
|
||||||
{
|
|
||||||
// Should we reset steering here?
|
|
||||||
//if (!ConcurrentObjectives)
|
|
||||||
//{
|
|
||||||
// SteeringManager.Reset();
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void Reset() { }
|
public virtual void Reset() { }
|
||||||
|
|
||||||
protected abstract void Act(float deltaTime);
|
protected abstract void Act(float deltaTime);
|
||||||
|
|
||||||
public abstract bool IsCompleted();
|
public abstract bool IsCompleted();
|
||||||
|
|
||||||
public abstract bool IsDuplicate(AIObjective otherObjective);
|
public abstract bool IsDuplicate(AIObjective otherObjective);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-49
@@ -1,19 +1,19 @@
|
|||||||
using Barotrauma.Items.Components;
|
using Barotrauma.Items.Components;
|
||||||
using Barotrauma.Extensions;
|
using Barotrauma.Extensions;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.Xna.Framework;
|
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
class AIObjectiveChargeBatteries : AIObjectiveLoop<PowerContainer>
|
class AIObjectiveChargeBatteries : AIObjectiveLoop<PowerContainer>
|
||||||
{
|
{
|
||||||
public override string DebugTag => "charge batteries";
|
public override string DebugTag => "charge batteries";
|
||||||
private IEnumerable<PowerContainer> batteryList;
|
private readonly IEnumerable<PowerContainer> batteryList;
|
||||||
|
|
||||||
public AIObjectiveChargeBatteries(Character character, AIObjectiveManager objectiveManager, string option, float priorityModifier)
|
public AIObjectiveChargeBatteries(Character character, string option) : base(character, option)
|
||||||
: base(character, objectiveManager, priorityModifier, option) { }
|
{
|
||||||
|
batteryList = Item.ItemList.Select(i => i.GetComponent<PowerContainer>()).Where(b => b != null);
|
||||||
|
}
|
||||||
|
|
||||||
public override bool IsDuplicate(AIObjective otherObjective)
|
public override bool IsDuplicate(AIObjective otherObjective)
|
||||||
{
|
{
|
||||||
@@ -22,57 +22,37 @@ namespace Barotrauma
|
|||||||
|
|
||||||
protected override void FindTargets()
|
protected override void FindTargets()
|
||||||
{
|
{
|
||||||
base.FindTargets();
|
foreach (Item item in Item.ItemList)
|
||||||
if (targets.None() && objectiveManager.CurrentOrder == this)
|
{
|
||||||
|
if (item.Prefab.Identifier != "battery" && !item.HasTag("battery")) { continue; }
|
||||||
|
if (item.Submarine == null) { continue; }
|
||||||
|
if (item.Submarine.TeamID != character.TeamID) { continue; }
|
||||||
|
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { continue; }
|
||||||
|
var battery = item.GetComponent<PowerContainer>();
|
||||||
|
if (battery != null)
|
||||||
|
{
|
||||||
|
if (!ignoreList.Contains(battery))
|
||||||
|
{
|
||||||
|
if (!targets.Contains(battery))
|
||||||
|
{
|
||||||
|
targets.Add(battery);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (targets.None())
|
||||||
{
|
{
|
||||||
character.Speak(TextManager.Get("DialogNoBatteries"), null, 4.0f, "nobatteries", 10.0f);
|
character.Speak(TextManager.Get("DialogNoBatteries"), null, 4.0f, "nobatteries", 10.0f);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool Filter(PowerContainer battery)
|
|
||||||
{
|
|
||||||
if (battery == null) { return false; }
|
|
||||||
var item = battery.Item;
|
|
||||||
if (item.Submarine == null) { return false; }
|
|
||||||
if (item.CurrentHull == null) { return false; }
|
|
||||||
if (item.Submarine.TeamID != character.TeamID) { return false; }
|
|
||||||
if (item.ConditionPercentage <= 0) { return false; }
|
|
||||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { return false; }
|
|
||||||
if (Character.CharacterList.Any(c => c.CurrentHull == item.CurrentHull && !HumanAIController.IsFriendly(c))) { return false; }
|
|
||||||
if (Option == "charge")
|
|
||||||
{
|
|
||||||
if (battery.RechargeRatio >= PowerContainer.aiRechargeTargetRatio - 0.01f) { return false; }
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (battery.RechargeRatio <= 0) { return false; }
|
targets.Sort((x, y) => x.ChargePercentage.CompareTo(y.ChargePercentage));
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override float TargetEvaluation()
|
|
||||||
{
|
|
||||||
if (Option == "charge")
|
|
||||||
{
|
|
||||||
return targets.Max(t => MathHelper.Lerp(100, 0, Math.Abs(PowerContainer.aiRechargeTargetRatio - t.RechargeRatio)));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
|
|
||||||
return targets.Max(t => MathHelper.Lerp(0, 100, t.RechargeRatio));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IEnumerable<PowerContainer> GetList()
|
protected override bool Filter(PowerContainer battery) => true;
|
||||||
{
|
protected override float Average(PowerContainer battery) => 100 - battery.ChargePercentage;
|
||||||
if (batteryList == null)
|
protected override IEnumerable<PowerContainer> GetList() => batteryList;
|
||||||
{
|
protected override AIObjective ObjectiveConstructor(PowerContainer battery) => new AIObjectiveOperateItem(battery, character, Option, false);
|
||||||
batteryList = character.Submarine.GetItems(true).Select(i => i.GetComponent<PowerContainer>()).Where(b => b != null);
|
|
||||||
}
|
|
||||||
return batteryList;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override AIObjective ObjectiveConstructor(PowerContainer battery)
|
|
||||||
=> new AIObjectiveOperateItem(battery, character, objectiveManager, Option, false, priorityModifier: PriorityModifier) { IsLoop = false };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
using Barotrauma.Items.Components;
|
using Barotrauma.Items.Components;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Barotrauma.Extensions;
|
using Barotrauma.Extensions;
|
||||||
using FarseerPhysics;
|
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
class AIObjectiveCombat : AIObjective
|
class AIObjectiveCombat : AIObjective
|
||||||
{
|
{
|
||||||
public override string DebugTag => "combat";
|
public override string DebugTag => "combat";
|
||||||
public bool useCoolDown = true;
|
public override bool KeepDivingGearOn => true;
|
||||||
|
|
||||||
const float CoolDown = 10.0f;
|
const float CoolDown = 10.0f;
|
||||||
|
|
||||||
@@ -25,14 +23,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
_weapon = value;
|
_weapon = value;
|
||||||
_weaponComponent = null;
|
_weaponComponent = null;
|
||||||
if (reloadWeaponObjective != null)
|
reloadWeaponObjective = null;
|
||||||
{
|
|
||||||
if (subObjectives.Contains(reloadWeaponObjective))
|
|
||||||
{
|
|
||||||
subObjectives.Remove(reloadWeaponObjective);
|
|
||||||
}
|
|
||||||
reloadWeaponObjective = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private ItemComponent _weaponComponent;
|
private ItemComponent _weaponComponent;
|
||||||
@@ -50,41 +41,29 @@ namespace Barotrauma
|
|||||||
return _weaponComponent;
|
return _weaponComponent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool ConcurrentObjectives => true;
|
|
||||||
|
|
||||||
private readonly AIObjectiveFindSafety findSafety;
|
|
||||||
private readonly HashSet<RangedWeapon> rangedWeapons = new HashSet<RangedWeapon>();
|
|
||||||
private readonly HashSet<MeleeWeapon> meleeWeapons = new HashSet<MeleeWeapon>();
|
|
||||||
private readonly HashSet<Item> adHocWeapons = new HashSet<Item>();
|
|
||||||
|
|
||||||
private AIObjectiveContainItem reloadWeaponObjective;
|
private AIObjectiveContainItem reloadWeaponObjective;
|
||||||
private AIObjectiveGoTo retreatObjective;
|
|
||||||
private AIObjectiveGoTo followTargetObjective;
|
|
||||||
|
|
||||||
private Hull retreatTarget;
|
private Hull retreatTarget;
|
||||||
|
private AIObjectiveGoTo retreatObjective;
|
||||||
|
private AIObjectiveFindSafety findSafety;
|
||||||
|
|
||||||
private float coolDownTimer;
|
private float coolDownTimer;
|
||||||
|
|
||||||
public enum CombatMode
|
public enum CombatMode
|
||||||
{
|
{
|
||||||
Defensive,
|
Defensive,
|
||||||
Offensive,
|
Offensive, // Not implemented
|
||||||
Retreat
|
Retreat
|
||||||
}
|
}
|
||||||
|
|
||||||
public CombatMode Mode { get; private set; }
|
public CombatMode Mode { get; private set; }
|
||||||
|
|
||||||
public AIObjectiveCombat(Character character, Character enemy, CombatMode mode, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
public AIObjectiveCombat(Character character, Character enemy, CombatMode mode) : base(character, "")
|
||||||
: base(character, objectiveManager, priorityModifier)
|
|
||||||
{
|
{
|
||||||
Enemy = enemy;
|
Enemy = enemy;
|
||||||
coolDownTimer = CoolDown;
|
coolDownTimer = CoolDown;
|
||||||
findSafety = objectiveManager.GetObjective<AIObjectiveFindSafety>();
|
findSafety = HumanAIController.ObjectiveManager.GetObjective<AIObjectiveFindSafety>();
|
||||||
if (findSafety != null)
|
findSafety.Priority = 0;
|
||||||
{
|
findSafety.unreachable.Clear();
|
||||||
findSafety.Priority = 0;
|
|
||||||
findSafety.unreachable.Clear();
|
|
||||||
}
|
|
||||||
Mode = mode;
|
Mode = mode;
|
||||||
if (Enemy == null)
|
if (Enemy == null)
|
||||||
{
|
{
|
||||||
@@ -92,51 +71,14 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override float GetPriority() => (Enemy != null && (Enemy.Removed || Enemy.IsDead)) ? 0 : Math.Min(100 * PriorityModifier, 100);
|
|
||||||
|
|
||||||
public override bool IsDuplicate(AIObjective otherObjective)
|
|
||||||
{
|
|
||||||
if (!(otherObjective is AIObjectiveCombat objective)) return false;
|
|
||||||
return objective.Enemy == Enemy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnSelected() => Weapon = null;
|
|
||||||
|
|
||||||
public override bool IsCompleted()
|
|
||||||
{
|
|
||||||
bool completed = (Enemy != null && (Enemy.Removed || Enemy.IsDead)) || (useCoolDown && coolDownTimer <= 0);
|
|
||||||
if (completed)
|
|
||||||
{
|
|
||||||
if (objectiveManager.CurrentOrder == this && Enemy != null && Enemy.IsDead)
|
|
||||||
{
|
|
||||||
character.Speak(TextManager.Get("DialogTargetDown"), null, 3.0f, "targetdown", 30.0f);
|
|
||||||
}
|
|
||||||
if (Weapon != null)
|
|
||||||
{
|
|
||||||
Unequip();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return completed;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Act(float deltaTime)
|
protected override void Act(float deltaTime)
|
||||||
{
|
{
|
||||||
if (useCoolDown)
|
coolDownTimer -= deltaTime;
|
||||||
{
|
|
||||||
coolDownTimer -= deltaTime;
|
|
||||||
}
|
|
||||||
if (abandon) { return; }
|
if (abandon) { return; }
|
||||||
Arm(deltaTime);
|
|
||||||
Move(deltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Arm(float deltaTime)
|
|
||||||
{
|
|
||||||
switch (Mode)
|
switch (Mode)
|
||||||
{
|
{
|
||||||
case CombatMode.Offensive:
|
|
||||||
case CombatMode.Defensive:
|
case CombatMode.Defensive:
|
||||||
if (Weapon != null && !character.Inventory.Items.Contains(_weapon) || _weaponComponent != null && !_weaponComponent.HasRequiredContainedItems(false))
|
if (Weapon != null && character.Inventory.Items.Contains(_weapon))
|
||||||
{
|
{
|
||||||
Weapon = null;
|
Weapon = null;
|
||||||
}
|
}
|
||||||
@@ -148,65 +90,40 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
Mode = CombatMode.Retreat;
|
Mode = CombatMode.Retreat;
|
||||||
}
|
}
|
||||||
if (Equip())
|
else if (Equip(deltaTime))
|
||||||
{
|
{
|
||||||
if (Reload(deltaTime))
|
if (Reload(deltaTime))
|
||||||
{
|
{
|
||||||
Attack(deltaTime);
|
Attack(deltaTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// When defensive, try to retreat to safety. TODO: in offsensive mode, engage the target
|
||||||
|
Retreat(deltaTime);
|
||||||
break;
|
break;
|
||||||
case CombatMode.Retreat:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Move(float deltaTime)
|
|
||||||
{
|
|
||||||
switch (Mode)
|
|
||||||
{
|
|
||||||
case CombatMode.Offensive:
|
|
||||||
Engage(deltaTime);
|
|
||||||
break;
|
|
||||||
case CombatMode.Defensive:
|
|
||||||
case CombatMode.Retreat:
|
case CombatMode.Retreat:
|
||||||
Retreat(deltaTime);
|
Retreat(deltaTime);
|
||||||
break;
|
break;
|
||||||
|
case CombatMode.Offensive:
|
||||||
default:
|
default:
|
||||||
throw new NotImplementedException();
|
throw new System.NotImplementedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Item GetWeapon()
|
private Item GetWeapon()
|
||||||
{
|
{
|
||||||
rangedWeapons.Clear();
|
|
||||||
meleeWeapons.Clear();
|
|
||||||
adHocWeapons.Clear();
|
|
||||||
Item weapon = null;
|
|
||||||
_weaponComponent = null;
|
_weaponComponent = null;
|
||||||
foreach (var item in character.Inventory.Items)
|
var weapon = character.Inventory.FindItemByTag("weapon");
|
||||||
|
if (weapon == null)
|
||||||
{
|
{
|
||||||
if (item == null) { continue; }
|
foreach (var item in character.Inventory.Items)
|
||||||
foreach (var component in item.Components)
|
|
||||||
{
|
{
|
||||||
if (component is RangedWeapon rw)
|
if (item == null) { continue; }
|
||||||
|
foreach (var component in item.Components)
|
||||||
{
|
{
|
||||||
if (rw.HasRequiredContainedItems(false))
|
if (component is MeleeWeapon || component is RangedWeapon)
|
||||||
{
|
{
|
||||||
rangedWeapons.Add(rw);
|
return item;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else if (component is MeleeWeapon mw)
|
|
||||||
{
|
|
||||||
if (mw.HasRequiredContainedItems(false))
|
|
||||||
{
|
|
||||||
meleeWeapons.Add(mw);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var effects = component.statusEffectLists;
|
var effects = component.statusEffectLists;
|
||||||
if (effects != null)
|
if (effects != null)
|
||||||
{
|
{
|
||||||
@@ -216,10 +133,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (statusEffect.Afflictions.Any())
|
if (statusEffect.Afflictions.Any())
|
||||||
{
|
{
|
||||||
if (component.HasRequiredContainedItems(false))
|
return item;
|
||||||
{
|
|
||||||
adHocWeapons.Add(item);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -227,20 +141,6 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var rangedWeapon = rangedWeapons.OrderByDescending(w => w.CombatPriority).FirstOrDefault();
|
|
||||||
var meleeWeapon = meleeWeapons.OrderByDescending(w => w.CombatPriority).FirstOrDefault();
|
|
||||||
if (rangedWeapon != null)
|
|
||||||
{
|
|
||||||
weapon = rangedWeapon.Item;
|
|
||||||
}
|
|
||||||
else if (meleeWeapon != null)
|
|
||||||
{
|
|
||||||
weapon = meleeWeapon.Item;
|
|
||||||
}
|
|
||||||
if (weapon == null)
|
|
||||||
{
|
|
||||||
weapon = adHocWeapons.GetRandom(Rand.RandSync.Server);
|
|
||||||
}
|
|
||||||
return weapon;
|
return weapon;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,9 +153,10 @@ namespace Barotrauma
|
|||||||
Weapon.Drop(character);
|
Weapon.Drop(character);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool Equip()
|
private bool Equip(float deltaTime)
|
||||||
{
|
{
|
||||||
if (!character.SelectedItems.Contains(Weapon))
|
if (!character.SelectedItems.Contains(Weapon))
|
||||||
{
|
{
|
||||||
@@ -266,7 +167,8 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Mode = CombatMode.Retreat;
|
//couldn't equip the item, escape
|
||||||
|
//Abandon(deltaTime);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -275,52 +177,18 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private void Retreat(float deltaTime)
|
private void Retreat(float deltaTime)
|
||||||
{
|
{
|
||||||
if (followTargetObjective != null)
|
|
||||||
{
|
|
||||||
if (subObjectives.Contains(followTargetObjective))
|
|
||||||
{
|
|
||||||
subObjectives.Remove(followTargetObjective);
|
|
||||||
}
|
|
||||||
followTargetObjective = null;
|
|
||||||
}
|
|
||||||
if (retreatObjective != null && retreatObjective.Target != retreatTarget)
|
|
||||||
{
|
|
||||||
retreatObjective = null;
|
|
||||||
}
|
|
||||||
if (retreatTarget == null || (retreatObjective != null && !retreatObjective.CanBeCompleted))
|
if (retreatTarget == null || (retreatObjective != null && !retreatObjective.CanBeCompleted))
|
||||||
{
|
{
|
||||||
retreatTarget = findSafety.FindBestHull(new List<Hull>() { character.CurrentHull });
|
retreatTarget = findSafety.FindBestHull(new List<Hull>() { character.CurrentHull });
|
||||||
}
|
}
|
||||||
TryAddSubObjective(ref retreatObjective, () => new AIObjectiveGoTo(retreatTarget, character, objectiveManager, false, true));
|
if (retreatTarget != null)
|
||||||
}
|
|
||||||
|
|
||||||
private void Engage(float deltaTime)
|
|
||||||
{
|
|
||||||
retreatTarget = null;
|
|
||||||
if (retreatObjective != null)
|
|
||||||
{
|
{
|
||||||
if (subObjectives.Contains(retreatObjective))
|
if (retreatObjective == null || retreatObjective.Target != retreatTarget)
|
||||||
{
|
{
|
||||||
subObjectives.Remove(retreatObjective);
|
retreatObjective = new AIObjectiveGoTo(retreatTarget, character, false, true);
|
||||||
}
|
}
|
||||||
retreatObjective = null;
|
retreatObjective.TryComplete(deltaTime);
|
||||||
}
|
}
|
||||||
TryAddSubObjective(ref followTargetObjective,
|
|
||||||
constructor: () => new AIObjectiveGoTo(Enemy, character, objectiveManager, repeat: true, getDivingGearIfNeeded: true)
|
|
||||||
{
|
|
||||||
AllowGoingOutside = true,
|
|
||||||
IgnoreIfTargetDead = true,
|
|
||||||
CheckVisibility = true,
|
|
||||||
CloseEnough =
|
|
||||||
WeaponComponent is RangedWeapon ? 3 :
|
|
||||||
WeaponComponent is MeleeWeapon mw ? ConvertUnits.ToSimUnits(mw.Range) :
|
|
||||||
WeaponComponent is RepairTool rt ? ConvertUnits.ToSimUnits(rt.Range) : 0.5f
|
|
||||||
},
|
|
||||||
onAbandon: () =>
|
|
||||||
{
|
|
||||||
SteeringManager.Reset();
|
|
||||||
Mode = CombatMode.Retreat;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool Reload(float deltaTime)
|
private bool Reload(float deltaTime)
|
||||||
@@ -333,47 +201,43 @@ namespace Barotrauma
|
|||||||
Item containedItem = containedItems.FirstOrDefault(it => it.Condition > 0.0f && requiredItem.MatchesItem(it));
|
Item containedItem = containedItems.FirstOrDefault(it => it.Condition > 0.0f && requiredItem.MatchesItem(it));
|
||||||
if (containedItem == null)
|
if (containedItem == null)
|
||||||
{
|
{
|
||||||
TryAddSubObjective(ref reloadWeaponObjective,
|
if (reloadWeaponObjective == null)
|
||||||
constructor: () => new AIObjectiveContainItem(character, requiredItem.Identifiers, Weapon.GetComponent<ItemContainer>(), objectiveManager),
|
{
|
||||||
onAbandon: () =>
|
reloadWeaponObjective = new AIObjectiveContainItem(character, requiredItem.Identifiers, Weapon.GetComponent<ItemContainer>());
|
||||||
{
|
}
|
||||||
SteeringManager.Reset();
|
|
||||||
Mode = CombatMode.Retreat;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return reloadWeaponObjective == null || reloadWeaponObjective.IsCompleted();
|
if (reloadWeaponObjective != null)
|
||||||
|
{
|
||||||
|
if (reloadWeaponObjective.IsCompleted())
|
||||||
|
{
|
||||||
|
reloadWeaponObjective = null;
|
||||||
|
}
|
||||||
|
else if (!reloadWeaponObjective.CanBeCompleted)
|
||||||
|
{
|
||||||
|
Mode = CombatMode.Retreat;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
reloadWeaponObjective.TryComplete(deltaTime);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<FarseerPhysics.Dynamics.Body> myBodies;
|
private IEnumerable<FarseerPhysics.Dynamics.Body> myBodies;
|
||||||
private void Attack(float deltaTime)
|
private void Attack(float deltaTime)
|
||||||
{
|
{
|
||||||
float squaredDistance = Vector2.DistanceSquared(character.Position, Enemy.Position);
|
|
||||||
character.CursorPosition = Enemy.Position;
|
character.CursorPosition = Enemy.Position;
|
||||||
float engageDistance = 500;
|
|
||||||
if (squaredDistance > engageDistance * engageDistance) { return; }
|
|
||||||
bool canSeeTarget = character.CanSeeCharacter(Enemy);
|
|
||||||
if (!canSeeTarget && character.CurrentHull != Enemy.CurrentHull) { return; }
|
|
||||||
if (Weapon.RequireAimToUse)
|
if (Weapon.RequireAimToUse)
|
||||||
{
|
{
|
||||||
bool isOperatingButtons = false;
|
character.SetInput(InputType.Aim, false, true);
|
||||||
if (SteeringManager == PathSteering)
|
|
||||||
{
|
|
||||||
var door = PathSteering.CurrentPath?.CurrentNode?.ConnectedDoor;
|
|
||||||
if (door != null && !door.IsOpen)
|
|
||||||
{
|
|
||||||
isOperatingButtons = door.HasIntegratedButtons || door.Item.GetConnectedComponents<Controller>(true).Any();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!isOperatingButtons && character.SelectedConstruction == null)
|
|
||||||
{
|
|
||||||
character.SetInput(InputType.Aim, false, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (WeaponComponent is MeleeWeapon meleeWeapon)
|
if (WeaponComponent is MeleeWeapon meleeWeapon)
|
||||||
{
|
{
|
||||||
if (squaredDistance <= meleeWeapon.Range * meleeWeapon.Range)
|
if (Vector2.DistanceSquared(character.Position, Enemy.Position) <= meleeWeapon.Range * meleeWeapon.Range)
|
||||||
{
|
{
|
||||||
character.SetInput(InputType.Shoot, false, true);
|
character.SetInput(InputType.Shoot, false, true);
|
||||||
Weapon.Use(deltaTime, character);
|
Weapon.Use(deltaTime, character);
|
||||||
@@ -383,7 +247,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (WeaponComponent is RepairTool repairTool)
|
if (WeaponComponent is RepairTool repairTool)
|
||||||
{
|
{
|
||||||
if (squaredDistance > repairTool.Range * repairTool.Range) { return; }
|
if (Vector2.DistanceSquared(character.Position, Enemy.Position) > repairTool.Range * repairTool.Range) { return; }
|
||||||
}
|
}
|
||||||
if (VectorExtensions.Angle(VectorExtensions.Forward(Weapon.body.TransformedRotation), Enemy.Position - character.Position) < MathHelper.PiOver4)
|
if (VectorExtensions.Angle(VectorExtensions.Forward(Weapon.body.TransformedRotation), Enemy.Position - character.Position) < MathHelper.PiOver4)
|
||||||
{
|
{
|
||||||
@@ -391,8 +255,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
myBodies = character.AnimController.Limbs.Select(l => l.body.FarseerBody);
|
myBodies = character.AnimController.Limbs.Select(l => l.body.FarseerBody);
|
||||||
}
|
}
|
||||||
var collisionCategories = Physics.CollisionCharacter | Physics.CollisionWall;
|
var pickedBody = Submarine.PickBody(character.SimPosition, Enemy.SimPosition, myBodies);
|
||||||
var pickedBody = Submarine.PickBody(character.SimPosition, Enemy.SimPosition, myBodies, collisionCategories);
|
|
||||||
if (pickedBody != null)
|
if (pickedBody != null)
|
||||||
{
|
{
|
||||||
Character target = null;
|
Character target = null;
|
||||||
@@ -414,6 +277,34 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Abandon(float deltaTime)
|
||||||
|
{
|
||||||
|
abandon = true;
|
||||||
|
SteeringManager.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool IsCompleted()
|
||||||
|
{
|
||||||
|
bool completed = (Enemy != null && (Enemy.Removed || Enemy.IsDead)) || coolDownTimer <= 0;
|
||||||
|
if (completed)
|
||||||
|
{
|
||||||
|
if (Weapon != null)
|
||||||
|
{
|
||||||
|
Unequip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return completed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanBeCompleted => !abandon && (reloadWeaponObjective == null || reloadWeaponObjective.CanBeCompleted) && (retreatObjective == null || retreatObjective.CanBeCompleted);
|
||||||
|
public override float GetPriority(AIObjectiveManager objectiveManager) => (Enemy != null && (Enemy.Removed || Enemy.IsDead)) ? 0 : 100;
|
||||||
|
|
||||||
|
public override bool IsDuplicate(AIObjective otherObjective)
|
||||||
|
{
|
||||||
|
if (!(otherObjective is AIObjectiveCombat objective)) return false;
|
||||||
|
return objective.Enemy == Enemy;
|
||||||
|
}
|
||||||
|
|
||||||
//private float CalculateEnemyStrength()
|
//private float CalculateEnemyStrength()
|
||||||
//{
|
//{
|
||||||
// float enemyStrength = 0;
|
// float enemyStrength = 0;
|
||||||
|
|||||||
+43
-24
@@ -9,9 +9,15 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
public override string DebugTag => "contain item";
|
public override string DebugTag => "contain item";
|
||||||
|
|
||||||
|
public int MinContainedAmount = 1;
|
||||||
|
|
||||||
|
//can either be a tag or an identifier
|
||||||
|
private string[] itemIdentifiers;
|
||||||
|
|
||||||
|
private ItemContainer container;
|
||||||
|
|
||||||
public Func<Item, float> GetItemPriority;
|
public Func<Item, float> GetItemPriority;
|
||||||
|
|
||||||
public int MinContainedAmount = 1;
|
|
||||||
public string[] ignoredContainerIdentifiers;
|
public string[] ignoredContainerIdentifiers;
|
||||||
|
|
||||||
//can either be a tag or an identifier
|
//can either be a tag or an identifier
|
||||||
@@ -21,12 +27,14 @@ namespace Barotrauma
|
|||||||
private bool isCompleted;
|
private bool isCompleted;
|
||||||
private AIObjectiveGetItem getItemObjective;
|
private AIObjectiveGetItem getItemObjective;
|
||||||
private AIObjectiveGoTo goToObjective;
|
private AIObjectiveGoTo goToObjective;
|
||||||
|
|
||||||
|
public AIObjectiveContainItem(Character character, string itemIdentifier, ItemContainer container)
|
||||||
|
: this(character, new string[] { itemIdentifier }, container)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public AIObjectiveContainItem(Character character, string itemIdentifier, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
public AIObjectiveContainItem(Character character, string[] itemIdentifiers, ItemContainer container)
|
||||||
: this(character, new string[] { itemIdentifier }, container, objectiveManager, priorityModifier) { }
|
: base (character, "")
|
||||||
|
|
||||||
public AIObjectiveContainItem(Character character, string[] itemIdentifiers, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
|
||||||
: base (character, objectiveManager, priorityModifier)
|
|
||||||
{
|
{
|
||||||
this.itemIdentifiers = itemIdentifiers;
|
this.itemIdentifiers = itemIdentifiers;
|
||||||
for (int i = 0; i < itemIdentifiers.Length; i++)
|
for (int i = 0; i < itemIdentifiers.Length; i++)
|
||||||
@@ -43,10 +51,22 @@ namespace Barotrauma
|
|||||||
int containedItemCount = 0;
|
int containedItemCount = 0;
|
||||||
foreach (Item item in container.Inventory.Items)
|
foreach (Item item in container.Inventory.Items)
|
||||||
{
|
{
|
||||||
if (item != null && itemIdentifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id)))
|
if (item != null && itemIdentifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id))) containedItemCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return containedItemCount >= MinContainedAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanBeCompleted
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (goToObjective != null)
|
||||||
{
|
{
|
||||||
containedItemCount++;
|
containedItemCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return getItemObjective == null || getItemObjective.CanBeCompleted;
|
||||||
}
|
}
|
||||||
return containedItemCount >= MinContainedAmount;
|
return containedItemCount >= MinContainedAmount;
|
||||||
}
|
}
|
||||||
@@ -68,16 +88,17 @@ namespace Barotrauma
|
|||||||
foreach (string identifier in itemIdentifiers)
|
foreach (string identifier in itemIdentifiers)
|
||||||
{
|
{
|
||||||
itemToContain = character.Inventory.FindItemByIdentifier(identifier) ?? character.Inventory.FindItemByTag(identifier);
|
itemToContain = character.Inventory.FindItemByIdentifier(identifier) ?? character.Inventory.FindItemByTag(identifier);
|
||||||
if (itemToContain != null && itemToContain.Condition > 0.0f) { break; }
|
if (itemToContain != null && itemToContain.Condition > 0.0f) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (itemToContain == null)
|
if (itemToContain == null)
|
||||||
{
|
{
|
||||||
TryAddSubObjective(ref getItemObjective, () =>
|
getItemObjective = new AIObjectiveGetItem(character, itemIdentifiers)
|
||||||
new AIObjectiveGetItem(character, itemIdentifiers, objectiveManager)
|
{
|
||||||
{
|
GetItemPriority = GetItemPriority,
|
||||||
GetItemPriority = GetItemPriority,
|
ignoredContainerIdentifiers = ignoredContainerIdentifiers
|
||||||
ignoredContainerIdentifiers = ignoredContainerIdentifiers
|
};
|
||||||
});
|
AddSubObjective(getItemObjective);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (container.Item.ParentInventory == character.Inventory)
|
if (container.Item.ParentInventory == character.Inventory)
|
||||||
@@ -94,8 +115,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (container.Item.CurrentHull != character.CurrentHull ||
|
if (container.Item.CurrentHull != character.CurrentHull || (Vector2.Distance(character.Position, container.Item.Position) > container.Item.InteractDistance && !container.Item.IsInsideTrigger(character.WorldPosition)))
|
||||||
(Vector2.DistanceSquared(character.Position, container.Item.Position) > Math.Pow(container.Item.InteractDistance, 2) && !container.Item.IsInsideTrigger(character.WorldPosition)))
|
|
||||||
{
|
{
|
||||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(container.Item, character, objectiveManager));
|
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(container.Item, character, objectiveManager));
|
||||||
return;
|
return;
|
||||||
@@ -107,15 +127,14 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public override bool IsDuplicate(AIObjective otherObjective)
|
public override bool IsDuplicate(AIObjective otherObjective)
|
||||||
{
|
{
|
||||||
if (!(otherObjective is AIObjectiveContainItem objective)) { return false; }
|
AIObjectiveContainItem objective = otherObjective as AIObjectiveContainItem;
|
||||||
if (objective.container != container) { return false; }
|
if (objective == null) return false;
|
||||||
if (objective.itemIdentifiers.Length != itemIdentifiers.Length) { return false; }
|
if (objective.container != container) return false;
|
||||||
|
if (objective.itemIdentifiers.Length != itemIdentifiers.Length) return false;
|
||||||
|
|
||||||
for (int i = 0; i < itemIdentifiers.Length; i++)
|
for (int i = 0; i < itemIdentifiers.Length; i++)
|
||||||
{
|
{
|
||||||
if (objective.itemIdentifiers[i] != itemIdentifiers[i])
|
if (objective.itemIdentifiers[i] != itemIdentifiers[i]) return false;
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
+53
-21
@@ -8,53 +8,78 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
public override string DebugTag => "decontain item";
|
public override string DebugTag => "decontain item";
|
||||||
|
|
||||||
public Func<Item, float> GetItemPriority;
|
|
||||||
|
|
||||||
//can either be a tag or an identifier
|
//can either be a tag or an identifier
|
||||||
private readonly string[] itemIdentifiers;
|
private string[] itemIdentifiers;
|
||||||
private readonly ItemContainer container;
|
|
||||||
private readonly Item targetItem;
|
private ItemContainer container;
|
||||||
|
|
||||||
private AIObjectiveGoTo goToObjective;
|
|
||||||
private bool isCompleted;
|
private bool isCompleted;
|
||||||
|
|
||||||
public AIObjectiveDecontainItem(Character character, Item targetItem, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
public Func<Item, float> GetItemPriority;
|
||||||
: base(character, objectiveManager, priorityModifier)
|
|
||||||
|
private AIObjectiveGetItem getItemObjective;
|
||||||
|
private AIObjectiveGoTo goToObjective;
|
||||||
|
private Item targetItem;
|
||||||
|
|
||||||
|
public AIObjectiveDecontainItem(Character character, Item targetItem, ItemContainer container)
|
||||||
|
: base(character, "")
|
||||||
{
|
{
|
||||||
this.targetItem = targetItem;
|
this.targetItem = targetItem;
|
||||||
this.container = container;
|
this.container = container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public AIObjectiveDecontainItem(Character character, string itemIdentifier, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
public AIObjectiveDecontainItem(Character character, string itemIdentifier, ItemContainer container)
|
||||||
: this(character, new string[] { itemIdentifier }, container, objectiveManager, priorityModifier) { }
|
: this(character, new string[] { itemIdentifier }, container)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public AIObjectiveDecontainItem(Character character, string[] itemIdentifiers, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
public AIObjectiveDecontainItem(Character character, string[] itemIdentifiers, ItemContainer container)
|
||||||
: base(character, objectiveManager, priorityModifier)
|
: base(character, "")
|
||||||
{
|
{
|
||||||
this.itemIdentifiers = itemIdentifiers;
|
this.itemIdentifiers = itemIdentifiers;
|
||||||
for (int i = 0; i < itemIdentifiers.Length; i++)
|
for (int i = 0; i < itemIdentifiers.Length; i++)
|
||||||
{
|
{
|
||||||
itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant();
|
itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.container = container;
|
this.container = container;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool IsCompleted() => isCompleted;
|
public override bool IsCompleted()
|
||||||
|
{
|
||||||
|
return isCompleted;
|
||||||
|
}
|
||||||
|
|
||||||
public override float GetPriority()
|
public override bool CanBeCompleted
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (goToObjective != null)
|
||||||
|
{
|
||||||
|
return goToObjective.CanBeCompleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getItemObjective == null || getItemObjective.CanBeCompleted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||||
{
|
{
|
||||||
if (objectiveManager.CurrentOrder == this)
|
if (objectiveManager.CurrentOrder == this)
|
||||||
{
|
{
|
||||||
return AIObjectiveManager.OrderPriority;
|
return AIObjectiveManager.OrderPriority;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1.0f;
|
return 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Act(float deltaTime)
|
protected override void Act(float deltaTime)
|
||||||
{
|
{
|
||||||
if (isCompleted) { return; }
|
if (isCompleted) return;
|
||||||
|
|
||||||
Item itemToDecontain = null;
|
Item itemToDecontain = null;
|
||||||
|
|
||||||
//get the item that should be de-contained
|
//get the item that should be de-contained
|
||||||
if (targetItem == null)
|
if (targetItem == null)
|
||||||
{
|
{
|
||||||
@@ -63,7 +88,7 @@ namespace Barotrauma
|
|||||||
foreach (string identifier in itemIdentifiers)
|
foreach (string identifier in itemIdentifiers)
|
||||||
{
|
{
|
||||||
itemToDecontain = container.Inventory.FindItemByIdentifier(identifier) ?? container.Inventory.FindItemByTag(identifier);
|
itemToDecontain = container.Inventory.FindItemByIdentifier(identifier) ?? container.Inventory.FindItemByTag(identifier);
|
||||||
if (itemToDecontain != null) { break; }
|
if (itemToDecontain != null) break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,32 +96,38 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
itemToDecontain = targetItem;
|
itemToDecontain = targetItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (itemToDecontain == null || itemToDecontain.Container != container.Item) // Item not found or already de-contained, consider complete
|
if (itemToDecontain == null || itemToDecontain.Container != container.Item) // Item not found or already de-contained, consider complete
|
||||||
{
|
{
|
||||||
isCompleted = true;
|
isCompleted = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (itemToDecontain.OwnInventory != character.Inventory && itemToDecontain.ParentInventory != character.Inventory)
|
if (itemToDecontain.OwnInventory != character.Inventory && itemToDecontain.ParentInventory != character.Inventory)
|
||||||
{
|
{
|
||||||
if (Vector2.DistanceSquared(character.Position, container.Item.Position) > MathUtils.Pow(container.Item.InteractDistance, 2) && !container.Item.IsInsideTrigger(character.WorldPosition))
|
if (Vector2.Distance(character.Position, container.Item.Position) > container.Item.InteractDistance
|
||||||
|
&& !container.Item.IsInsideTrigger(character.WorldPosition))
|
||||||
{
|
{
|
||||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(container.Item, character, objectiveManager));
|
goToObjective = new AIObjectiveGoTo(container.Item, character);
|
||||||
|
AddSubObjective(goToObjective);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
itemToDecontain.Drop(character);
|
itemToDecontain.Drop(character);
|
||||||
isCompleted = true;
|
isCompleted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool IsDuplicate(AIObjective otherObjective)
|
public override bool IsDuplicate(AIObjective otherObjective)
|
||||||
{
|
{
|
||||||
if (!(otherObjective is AIObjectiveDecontainItem decontainItem)) { return false; }
|
AIObjectiveDecontainItem decontainItem = otherObjective as AIObjectiveDecontainItem;
|
||||||
|
if (decontainItem == null) return false;
|
||||||
if (decontainItem.itemIdentifiers != null && itemIdentifiers != null)
|
if (decontainItem.itemIdentifiers != null && itemIdentifiers != null)
|
||||||
{
|
{
|
||||||
if (decontainItem.itemIdentifiers.Length != itemIdentifiers.Length) { return false; }
|
if (decontainItem.itemIdentifiers.Length != itemIdentifiers.Length) return false;
|
||||||
for (int i = 0; i < decontainItem.itemIdentifiers.Length; i++)
|
for (int i = 0; i < decontainItem.itemIdentifiers.Length; i++)
|
||||||
{
|
{
|
||||||
if (decontainItem.itemIdentifiers[i] != itemIdentifiers[i]) { return false; }
|
if (decontainItem.itemIdentifiers[i] != itemIdentifiers[i]) return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -104,6 +135,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
return decontainItem.targetItem == targetItem;
|
return decontainItem.targetItem == targetItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+86
-79
@@ -3,7 +3,6 @@ using FarseerPhysics;
|
|||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Barotrauma.Extensions;
|
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
@@ -11,117 +10,125 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
public override string DebugTag => "extinguish fire";
|
public override string DebugTag => "extinguish fire";
|
||||||
public override bool ForceRun => true;
|
public override bool ForceRun => true;
|
||||||
public override bool ConcurrentObjectives => true;
|
public override bool KeepDivingGearOn => true;
|
||||||
|
|
||||||
private readonly Hull targetHull;
|
private Hull targetHull;
|
||||||
|
|
||||||
private AIObjectiveGetItem getExtinguisherObjective;
|
private AIObjectiveGetItem getExtinguisherObjective;
|
||||||
|
|
||||||
private AIObjectiveGoTo gotoObjective;
|
private AIObjectiveGoTo gotoObjective;
|
||||||
|
|
||||||
private float useExtinquisherTimer;
|
private float useExtinquisherTimer;
|
||||||
|
|
||||||
public AIObjectiveExtinguishFire(Character character, Hull targetHull, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
public AIObjectiveExtinguishFire(Character character, Hull targetHull) : base(character, "")
|
||||||
: base(character, objectiveManager, priorityModifier)
|
|
||||||
{
|
{
|
||||||
this.targetHull = targetHull;
|
this.targetHull = targetHull;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override float GetPriority()
|
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||||
{
|
{
|
||||||
if (Character.CharacterList.Any(c => c.CurrentHull == targetHull && !HumanAIController.IsFriendly(c))) { return 0; }
|
if (gotoObjective != null && !gotoObjective.CanBeCompleted) { return 0; }
|
||||||
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally)
|
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally)
|
||||||
float dist = Math.Abs(character.WorldPosition.X - targetHull.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - targetHull.WorldPosition.Y) * 2.0f;
|
float dist = Math.Abs(character.WorldPosition.X - targetHull.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - targetHull.WorldPosition.Y) * 2.0f;
|
||||||
float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 10000, dist));
|
float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 10000, dist));
|
||||||
float severity = AIObjectiveExtinguishFires.GetFireSeverity(targetHull);
|
float severityFactor = MathHelper.Lerp(0, 1, MathHelper.Clamp(targetHull.FireSources.Sum(fs => fs.Size.X) / targetHull.Size.X, 0, 1));
|
||||||
float severityFactor = MathHelper.Lerp(0, 1, severity / 100);
|
return MathHelper.Clamp(Priority * severityFactor * distanceFactor, 0, 100);
|
||||||
float devotion = Math.Min(Priority, 10) / 100;
|
|
||||||
return MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + severityFactor * distanceFactor, 0, 1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool IsCompleted() => targetHull.FireSources.None();
|
public override bool IsCompleted()
|
||||||
|
{
|
||||||
|
return targetHull.FireSources.Count == 0;
|
||||||
|
}
|
||||||
|
|
||||||
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveExtinguishFire otherExtinguishFire && otherExtinguishFire.targetHull == targetHull;
|
public override bool IsDuplicate(AIObjective otherObjective)
|
||||||
|
{
|
||||||
|
var otherExtinguishFire = otherObjective as AIObjectiveExtinguishFire;
|
||||||
|
return otherExtinguishFire != null && otherExtinguishFire.targetHull == targetHull;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanBeCompleted
|
||||||
|
{
|
||||||
|
get { return getExtinguisherObjective == null || getExtinguisherObjective.IsCompleted() || getExtinguisherObjective.CanBeCompleted; }
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Act(float deltaTime)
|
protected override void Act(float deltaTime)
|
||||||
{
|
{
|
||||||
var extinguisherItem = character.Inventory.FindItemByIdentifier("extinguisher") ?? character.Inventory.FindItemByTag("extinguisher");
|
var extinguisherItem = character.Inventory.FindItemByIdentifier("extinguisher") ?? character.Inventory.FindItemByTag("extinguisher");
|
||||||
if (extinguisherItem == null || extinguisherItem.Condition <= 0.0f || !character.HasEquippedItem(extinguisherItem))
|
if (extinguisherItem == null || extinguisherItem.Condition <= 0.0f || !character.HasEquippedItem(extinguisherItem))
|
||||||
{
|
{
|
||||||
TryAddSubObjective(ref getExtinguisherObjective, () =>
|
if (getExtinguisherObjective == null)
|
||||||
{
|
{
|
||||||
character.Speak(TextManager.Get("DialogFindExtinguisher"), null, 2.0f, "findextinguisher", 30.0f);
|
character.Speak(TextManager.Get("DialogFindExtinguisher"), null, 2.0f, "findextinguisher", 30.0f);
|
||||||
return new AIObjectiveGetItem(character, "extinguisher", objectiveManager, equip: true);
|
getExtinguisherObjective = new AIObjectiveGetItem(character, "extinguisher", true);
|
||||||
});
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
getExtinguisherObjective.TryComplete(deltaTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
var extinguisher = extinguisherItem.GetComponent<RepairTool>();
|
||||||
|
if (extinguisher == null)
|
||||||
{
|
{
|
||||||
var extinguisher = extinguisherItem.GetComponent<RepairTool>();
|
DebugConsole.ThrowError("AIObjectiveExtinguishFire failed - the item \"" + extinguisherItem + "\" has no RepairTool component but is tagged as an extinguisher");
|
||||||
if (extinguisher == null)
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (FireSource fs in targetHull.FireSources)
|
||||||
|
{
|
||||||
|
bool inRange = fs.IsInDamageRange(character, MathHelper.Clamp(fs.DamageRange * 1.5f, extinguisher.Range * 0.5f, extinguisher.Range));
|
||||||
|
bool move = !inRange;
|
||||||
|
if (inRange || useExtinquisherTimer > 0.0f)
|
||||||
{
|
{
|
||||||
#if DEBUG
|
useExtinquisherTimer += deltaTime;
|
||||||
DebugConsole.ThrowError("AIObjectiveExtinguishFire failed - the item \"" + extinguisherItem + "\" has no RepairTool component but is tagged as an extinguisher");
|
if (useExtinquisherTimer > 2.0f)
|
||||||
#endif
|
|
||||||
abandon = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
foreach (FireSource fs in targetHull.FireSources)
|
|
||||||
{
|
|
||||||
bool inRange = fs.IsInDamageRange(character, MathHelper.Clamp(fs.DamageRange * 1.5f, extinguisher.Range * 0.5f, extinguisher.Range));
|
|
||||||
bool move = !inRange;
|
|
||||||
if (inRange || useExtinquisherTimer > 0.0f)
|
|
||||||
{
|
{
|
||||||
useExtinquisherTimer += deltaTime;
|
useExtinquisherTimer = 0.0f;
|
||||||
if (useExtinquisherTimer > 2.0f)
|
}
|
||||||
|
character.AIController.SteeringManager.Reset();
|
||||||
|
character.CursorPosition = fs.Position;
|
||||||
|
if (extinguisher.Item.RequireAimToUse)
|
||||||
|
{
|
||||||
|
character.SetInput(InputType.Aim, false, true);
|
||||||
|
}
|
||||||
|
Limb sightLimb = null;
|
||||||
|
if (character.Inventory.IsInLimbSlot(extinguisherItem, InvSlotType.RightHand))
|
||||||
|
{
|
||||||
|
sightLimb = character.AnimController.GetLimb(LimbType.RightHand);
|
||||||
|
}
|
||||||
|
else if (character.Inventory.IsInLimbSlot(extinguisherItem, InvSlotType.LeftHand))
|
||||||
|
{
|
||||||
|
sightLimb = character.AnimController.GetLimb(LimbType.LeftHand);
|
||||||
|
}
|
||||||
|
if (!character.CanSeeTarget(fs, sightLimb))
|
||||||
|
{
|
||||||
|
move = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
move = false;
|
||||||
|
extinguisher.Use(deltaTime, character);
|
||||||
|
if (!targetHull.FireSources.Contains(fs))
|
||||||
{
|
{
|
||||||
useExtinquisherTimer = 0.0f;
|
character.Speak(TextManager.Get("DialogPutOutFire").Replace("[roomname]", targetHull.Name), null, 0, "putoutfire", 10.0f);
|
||||||
}
|
|
||||||
character.AIController.SteeringManager.Reset();
|
|
||||||
character.CursorPosition = fs.Position;
|
|
||||||
if (extinguisher.Item.RequireAimToUse)
|
|
||||||
{
|
|
||||||
bool isOperatingButtons = false;
|
|
||||||
if (SteeringManager == PathSteering)
|
|
||||||
{
|
|
||||||
var door = PathSteering.CurrentPath?.CurrentNode?.ConnectedDoor;
|
|
||||||
if (door != null && !door.IsOpen)
|
|
||||||
{
|
|
||||||
isOperatingButtons = door.HasIntegratedButtons || door.Item.GetConnectedComponents<Controller>(true).Any();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!isOperatingButtons)
|
|
||||||
{
|
|
||||||
character.SetInput(InputType.Aim, false, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Limb sightLimb = null;
|
|
||||||
if (character.Inventory.IsInLimbSlot(extinguisherItem, InvSlotType.RightHand))
|
|
||||||
{
|
|
||||||
sightLimb = character.AnimController.GetLimb(LimbType.RightHand);
|
|
||||||
}
|
|
||||||
else if (character.Inventory.IsInLimbSlot(extinguisherItem, InvSlotType.LeftHand))
|
|
||||||
{
|
|
||||||
sightLimb = character.AnimController.GetLimb(LimbType.LeftHand);
|
|
||||||
}
|
|
||||||
if (!character.CanSeeTarget(fs, sightLimb))
|
|
||||||
{
|
|
||||||
move = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
move = false;
|
|
||||||
extinguisher.Use(deltaTime, character);
|
|
||||||
if (!targetHull.FireSources.Contains(fs))
|
|
||||||
{
|
|
||||||
character.Speak(TextManager.Get("DialogPutOutFire").Replace("[roomname]", targetHull.Name), null, 0, "putoutfire", 10.0f);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (move)
|
|
||||||
{
|
|
||||||
//go to the first firesource
|
|
||||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(fs.Position), character, objectiveManager));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
if (move)
|
||||||
|
{
|
||||||
|
//go to the first firesource
|
||||||
|
if (gotoObjective == null || !gotoObjective.CanBeCompleted || gotoObjective.IsCompleted())
|
||||||
|
{
|
||||||
|
gotoObjective = new AIObjectiveGoTo(ConvertUnits.ToSimUnits(fs.Position), character);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gotoObjective.TryComplete(deltaTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+41
-23
@@ -1,44 +1,62 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Barotrauma.Extensions;
|
using Barotrauma.Extensions;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
class AIObjectiveExtinguishFires : AIObjectiveLoop<Hull>
|
class AIObjectiveExtinguishFires : AIObjective
|
||||||
{
|
{
|
||||||
public override string DebugTag => "extinguish fires";
|
public override string DebugTag => "extinguish fires";
|
||||||
public override bool ForceRun => true;
|
public override bool ForceRun => true;
|
||||||
|
public override bool KeepDivingGearOn => true;
|
||||||
|
|
||||||
public AIObjectiveExtinguishFires(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
|
private Dictionary<Hull, AIObjectiveExtinguishFire> extinguishObjectives = new Dictionary<Hull, AIObjectiveExtinguishFire>();
|
||||||
|
|
||||||
protected override void FindTargets()
|
public AIObjectiveExtinguishFires(Character character) : base(character, "") { }
|
||||||
|
|
||||||
|
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||||
{
|
{
|
||||||
base.FindTargets();
|
if (character.Submarine == null) { return 0; }
|
||||||
if (targets.None() && objectiveManager.CurrentOrder == this)
|
int fireCount = character.Submarine.GetHulls(true).Sum(h => h.FireSources.Count);
|
||||||
|
if (objectiveManager.CurrentOrder == this && fireCount > 0)
|
||||||
{
|
{
|
||||||
character.Speak(TextManager.Get("DialogNoFire"), null, 3.0f, "nofire", 30.0f);
|
return AIObjectiveManager.OrderPriority;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return MathHelper.Clamp(fireCount * 20, 0, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool Filter(Hull hull) => IsValidTarget(hull, character);
|
public override bool IsCompleted() => false;
|
||||||
|
public override bool CanBeCompleted => true;
|
||||||
|
|
||||||
protected override float TargetEvaluation() => objectiveManager.CurrentObjective == this ? 100 : targets.Sum(t => GetFireSeverity(t));
|
public override bool IsDuplicate(AIObjective otherObjective)
|
||||||
|
|
||||||
public static float GetFireSeverity(Hull hull) => hull.FireSources.Sum(fs => fs.Size.X);
|
|
||||||
|
|
||||||
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveExtinguishFires;
|
|
||||||
protected override IEnumerable<Hull> GetList() => Hull.hullList;
|
|
||||||
|
|
||||||
protected override AIObjective ObjectiveConstructor(Hull target) => new AIObjectiveExtinguishFire(character, target, objectiveManager, PriorityModifier);
|
|
||||||
|
|
||||||
public static bool IsValidTarget(Hull hull, Character character)
|
|
||||||
{
|
{
|
||||||
if (hull == null) { return false; }
|
return otherObjective is AIObjectiveExtinguishFires;
|
||||||
if (hull.FireSources.None()) { return false; }
|
}
|
||||||
if (hull.Submarine == null) { return false; }
|
|
||||||
if (hull.Submarine.TeamID != character.TeamID) { return false; }
|
protected override void Act(float deltaTime)
|
||||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(hull, true)) { return false; }
|
{
|
||||||
return true;
|
SyncRemovedObjectives(extinguishObjectives, Hull.hullList);
|
||||||
|
if (character.Submarine == null) { return; }
|
||||||
|
foreach (Hull hull in Hull.hullList)
|
||||||
|
{
|
||||||
|
if (hull.FireSources.None()) { continue; }
|
||||||
|
if (hull.Submarine == null) { continue; }
|
||||||
|
if (hull.Submarine.TeamID != character.TeamID) { continue; }
|
||||||
|
// If the character is inside, only take connected hulls into account.
|
||||||
|
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(hull, true)) { continue; }
|
||||||
|
if (!extinguishObjectives.TryGetValue(hull, out AIObjectiveExtinguishFire objective))
|
||||||
|
{
|
||||||
|
objective = new AIObjectiveExtinguishFire(character, hull);
|
||||||
|
extinguishObjectives.Add(hull, objective);
|
||||||
|
AddSubObjective(objective);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (extinguishObjectives.None())
|
||||||
|
{
|
||||||
|
character?.Speak(TextManager.Get("DialogNoFire"), null, 3.0f, "nofire", 30.0f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-28
@@ -1,7 +1,6 @@
|
|||||||
using Barotrauma.Items.Components;
|
using Barotrauma.Items.Components;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Barotrauma.Extensions;
|
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
@@ -10,10 +9,8 @@ namespace Barotrauma
|
|||||||
public override string DebugTag => "find diving gear";
|
public override string DebugTag => "find diving gear";
|
||||||
public override bool ForceRun => true;
|
public override bool ForceRun => true;
|
||||||
|
|
||||||
private readonly string gearTag;
|
private AIObjective subObjective;
|
||||||
|
private string gearTag;
|
||||||
private AIObjectiveGetItem getDivingGear;
|
|
||||||
private AIObjectiveContainItem getOxygen;
|
|
||||||
|
|
||||||
public override bool IsCompleted()
|
public override bool IsCompleted()
|
||||||
{
|
{
|
||||||
@@ -24,16 +21,15 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
var containedItems = character.Inventory.Items[i].ContainedItems;
|
var containedItems = character.Inventory.Items[i].ContainedItems;
|
||||||
if (containedItems == null) { continue; }
|
if (containedItems == null) { continue; }
|
||||||
return containedItems.Any(it => (it.Prefab.Identifier == "oxygentank" || it.HasTag("oxygensource")) && it.Condition > 0.0f);
|
|
||||||
|
var oxygenTank = containedItems.FirstOrDefault(it => (it.Prefab.Identifier == "oxygentank" || it.HasTag("oxygensource")) && it.Condition > 0.0f);
|
||||||
|
if (oxygenTank != null) { return true; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override float GetPriority() => MathHelper.Clamp(100 - character.OxygenAvailable, 0, 100);
|
public AIObjectiveFindDivingGear(Character character, bool needDivingSuit) : base(character, "")
|
||||||
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFindDivingGear;
|
|
||||||
|
|
||||||
public AIObjectiveFindDivingGear(Character character, bool needDivingSuit, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier)
|
|
||||||
{
|
{
|
||||||
gearTag = needDivingSuit ? "divingsuit" : "diving";
|
gearTag = needDivingSuit ? "divingsuit" : "diving";
|
||||||
}
|
}
|
||||||
@@ -46,23 +42,16 @@ namespace Barotrauma
|
|||||||
TryAddSubObjective(ref getDivingGear, () =>
|
TryAddSubObjective(ref getDivingGear, () =>
|
||||||
{
|
{
|
||||||
character.Speak(TextManager.Get("DialogGetDivingGear"), null, 0.0f, "getdivinggear", 30.0f);
|
character.Speak(TextManager.Get("DialogGetDivingGear"), null, 0.0f, "getdivinggear", 30.0f);
|
||||||
return new AIObjectiveGetItem(character, gearTag, objectiveManager, equip: true);
|
subObjective = new AIObjectiveGetItem(character, gearTag, true);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
if (getDivingGear != null) { return; }
|
if (getDivingGear != null) { return; }
|
||||||
var containedItems = item.ContainedItems;
|
var containedItems = item.ContainedItems;
|
||||||
if (containedItems == null)
|
if (containedItems == null)
|
||||||
{
|
{
|
||||||
var containedItems = item.ContainedItems;
|
var containedItems = item.ContainedItems;
|
||||||
if (containedItems == null)
|
if (containedItems == null) { return; }
|
||||||
{
|
//check if there's an oxygen tank in the mask/suit
|
||||||
#if DEBUG
|
|
||||||
DebugConsole.ThrowError("AIObjectiveFindDivingGear failed - the item \"" + item + "\" has no proper inventory");
|
|
||||||
#endif
|
|
||||||
abandon = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Drop empty tanks
|
|
||||||
foreach (Item containedItem in containedItems)
|
foreach (Item containedItem in containedItems)
|
||||||
{
|
{
|
||||||
if (containedItem == null) { continue; }
|
if (containedItem == null) { continue; }
|
||||||
@@ -70,16 +59,26 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
containedItem.Drop(character);
|
containedItem.Drop(character);
|
||||||
}
|
}
|
||||||
}
|
else if (containedItem.Prefab.Identifier == "oxygentank" || containedItem.HasTag("oxygensource"))
|
||||||
if (containedItems.None(it => (it.Prefab.Identifier == "oxygentank" || it.HasTag("oxygensource")) && it.Condition > 0.0f))
|
|
||||||
{
|
|
||||||
TryAddSubObjective(ref getOxygen, () =>
|
|
||||||
{
|
{
|
||||||
character.Speak(TextManager.Get("DialogGetOxygenTank"), null, 0, "getoxygentank", 30.0f);
|
//we've got an oxygen source inside the mask/suit, all good
|
||||||
return new AIObjectiveContainItem(character, new string[] { "oxygentank", "oxygensource" }, item.GetComponent<ItemContainer>(), objectiveManager);
|
return;
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
if (!(subObjective is AIObjectiveContainItem) || subObjective.IsCompleted())
|
||||||
|
{
|
||||||
|
character.Speak(TextManager.Get("DialogGetOxygenTank"), null, 0, "getoxygentank", 30.0f);
|
||||||
|
subObjective = new AIObjectiveContainItem(character, new string[] { "oxygentank", "oxygensource" }, item.GetComponent<ItemContainer>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (subObjective != null)
|
||||||
|
{
|
||||||
|
subObjective.TryComplete(deltaTime);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool CanBeCompleted => subObjective == null || subObjective.CanBeCompleted;
|
||||||
|
public override float GetPriority(AIObjectiveManager objectiveManager) => MathHelper.Clamp(100 - character.OxygenAvailable, 0, 100);
|
||||||
|
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFindDivingGear;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+114
-75
@@ -10,6 +10,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
public override string DebugTag => "find safety";
|
public override string DebugTag => "find safety";
|
||||||
public override bool ForceRun => true;
|
public override bool ForceRun => true;
|
||||||
|
public override bool KeepDivingGearOn => true;
|
||||||
|
|
||||||
// TODO: expose?
|
// TODO: expose?
|
||||||
const float priorityIncrease = 25;
|
const float priorityIncrease = 25;
|
||||||
@@ -17,7 +18,7 @@ namespace Barotrauma
|
|||||||
const float SearchHullInterval = 3.0f;
|
const float SearchHullInterval = 3.0f;
|
||||||
const float clearUnreachableInterval = 30;
|
const float clearUnreachableInterval = 30;
|
||||||
|
|
||||||
public readonly HashSet<Hull> unreachable = new HashSet<Hull>();
|
public readonly List<Hull> unreachable = new List<Hull>();
|
||||||
|
|
||||||
private float currenthullSafety;
|
private float currenthullSafety;
|
||||||
private float unreachableClearTimer;
|
private float unreachableClearTimer;
|
||||||
@@ -26,15 +27,49 @@ namespace Barotrauma
|
|||||||
private AIObjectiveGoTo goToObjective;
|
private AIObjectiveGoTo goToObjective;
|
||||||
private AIObjectiveFindDivingGear divingGearObjective;
|
private AIObjectiveFindDivingGear divingGearObjective;
|
||||||
|
|
||||||
public AIObjectiveFindSafety(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
|
public AIObjectiveFindSafety(Character character) : base(character, "") { }
|
||||||
|
|
||||||
public override bool IsCompleted() => false;
|
public override bool IsCompleted() => false;
|
||||||
public override bool CanBeCompleted => true;
|
public override bool CanBeCompleted => true;
|
||||||
|
|
||||||
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFindSafety;
|
|
||||||
|
|
||||||
public override void Update(float deltaTime)
|
public override void Update(float deltaTime)
|
||||||
{
|
{
|
||||||
|
var currentHull = character.AnimController.CurrentHull;
|
||||||
|
if (HumanAIController.NeedsDivingGear(currentHull) && divingGearObjective == null)
|
||||||
|
{
|
||||||
|
bool needsDivingSuit = currentHull == null || currentHull.WaterPercentage > 90;
|
||||||
|
bool hasEquipment = needsDivingSuit ? HumanAIController.HasDivingSuit(character) : HumanAIController.HasDivingGear(character);
|
||||||
|
if (!hasEquipment)
|
||||||
|
{
|
||||||
|
divingGearObjective = new AIObjectiveFindDivingGear(character, needsDivingSuit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (divingGearObjective != null)
|
||||||
|
{
|
||||||
|
divingGearObjective.TryComplete(deltaTime);
|
||||||
|
if (divingGearObjective.IsCompleted())
|
||||||
|
{
|
||||||
|
divingGearObjective = null;
|
||||||
|
Priority = 0;
|
||||||
|
}
|
||||||
|
else if (divingGearObjective.CanBeCompleted)
|
||||||
|
{
|
||||||
|
// If diving gear objective is active and can be completed, wait for it to complete.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
divingGearObjective = null;
|
||||||
|
// Reduce the timer so that we get a safe hull target faster.
|
||||||
|
searchHullTimer = Math.Min(1, searchHullTimer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currenthullSafety < HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||||
|
{
|
||||||
|
searchHullTimer = Math.Min(1, searchHullTimer);
|
||||||
|
}
|
||||||
|
|
||||||
if (unreachableClearTimer > 0)
|
if (unreachableClearTimer > 0)
|
||||||
{
|
{
|
||||||
unreachableClearTimer -= deltaTime;
|
unreachableClearTimer -= deltaTime;
|
||||||
@@ -44,86 +79,56 @@ namespace Barotrauma
|
|||||||
unreachableClearTimer = clearUnreachableInterval;
|
unreachableClearTimer = clearUnreachableInterval;
|
||||||
unreachable.Clear();
|
unreachable.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (searchHullTimer > 0.0f)
|
||||||
|
{
|
||||||
|
unreachableClearTimer = clearUnreachableInterval;
|
||||||
|
unreachable.Clear();
|
||||||
|
}
|
||||||
if (character.CurrentHull == null)
|
if (character.CurrentHull == null)
|
||||||
{
|
{
|
||||||
currenthullSafety = 0;
|
var bestHull = FindBestHull();
|
||||||
Priority = 100;
|
if (bestHull != null && bestHull != currentHull)
|
||||||
return;
|
{
|
||||||
|
if (goToObjective != null)
|
||||||
|
{
|
||||||
|
if (goToObjective.Target != bestHull)
|
||||||
|
{
|
||||||
|
// If we need diving gear, we should already have it, if possible.
|
||||||
|
goToObjective = new AIObjectiveGoTo(bestHull, character, getDivingGearIfNeeded: false)
|
||||||
|
{
|
||||||
|
AllowGoingOutside = HumanAIController.HasDivingSuit(character)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
goToObjective = new AIObjectiveGoTo(bestHull, character, getDivingGearIfNeeded: false)
|
||||||
|
{
|
||||||
|
AllowGoingOutside = HumanAIController.HasDivingSuit(character)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
searchHullTimer = SearchHullInterval;
|
||||||
}
|
}
|
||||||
if (character.OxygenAvailable < CharacterHealth.LowOxygenThreshold) { Priority = 100; }
|
if (character.OxygenAvailable < CharacterHealth.LowOxygenThreshold) { Priority = 100; }
|
||||||
currenthullSafety = HumanAIController.GetHullSafety(character.CurrentHull);
|
currenthullSafety = HumanAIController.GetHullSafety(character.CurrentHull);
|
||||||
if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD)
|
if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||||
{
|
{
|
||||||
Priority -= priorityDecrease * deltaTime;
|
goToObjective.TryComplete(deltaTime);
|
||||||
}
|
if (!goToObjective.CanBeCompleted)
|
||||||
else
|
|
||||||
{
|
|
||||||
float dangerFactor = (100 - currenthullSafety) / 100;
|
|
||||||
Priority += dangerFactor * priorityIncrease * deltaTime;
|
|
||||||
}
|
|
||||||
Priority = MathHelper.Clamp(Priority, 0, 100);
|
|
||||||
if (divingGearObjective != null && !divingGearObjective.IsCompleted() && divingGearObjective.CanBeCompleted)
|
|
||||||
{
|
|
||||||
Priority = Math.Max(Priority, Math.Min(AIObjectiveManager.OrderPriority + 20, 100));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Act(float deltaTime)
|
|
||||||
{
|
|
||||||
var currentHull = character.AnimController.CurrentHull;
|
|
||||||
bool needsDivingGear = HumanAIController.NeedsDivingGear(currentHull);
|
|
||||||
bool needsDivingSuit = needsDivingGear && (currentHull == null || currentHull.WaterPercentage > 90);
|
|
||||||
bool needsEquipment = false;
|
|
||||||
if (needsDivingSuit)
|
|
||||||
{
|
|
||||||
needsEquipment = !HumanAIController.HasDivingSuit(character);
|
|
||||||
}
|
|
||||||
else if (needsDivingGear)
|
|
||||||
{
|
|
||||||
needsEquipment = !HumanAIController.HasDivingMask(character);
|
|
||||||
}
|
|
||||||
if (needsEquipment)
|
|
||||||
{
|
|
||||||
TryAddSubObjective(ref divingGearObjective,
|
|
||||||
() => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager),
|
|
||||||
onAbandon: () => searchHullTimer = Math.Min(1, searchHullTimer));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (divingGearObjective != null && divingGearObjective.IsCompleted())
|
|
||||||
{
|
{
|
||||||
// Reset the devotion.
|
if (!unreachable.Contains(goToObjective.Target))
|
||||||
Priority = 0;
|
|
||||||
divingGearObjective = null;
|
|
||||||
}
|
|
||||||
if (currenthullSafety < HumanAIController.HULL_SAFETY_THRESHOLD)
|
|
||||||
{
|
|
||||||
searchHullTimer = Math.Min(1, searchHullTimer);
|
|
||||||
}
|
|
||||||
if (searchHullTimer > 0.0f)
|
|
||||||
{
|
|
||||||
searchHullTimer -= deltaTime;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
searchHullTimer = SearchHullInterval;
|
|
||||||
var bestHull = FindBestHull();
|
|
||||||
if (bestHull != null && bestHull != currentHull)
|
|
||||||
{
|
{
|
||||||
if (goToObjective?.Target != bestHull)
|
unreachable.Add(goToObjective.Target as Hull);
|
||||||
{
|
|
||||||
goToObjective = null;
|
|
||||||
}
|
|
||||||
TryAddSubObjective(ref goToObjective, () =>
|
|
||||||
new AIObjectiveGoTo(bestHull, character, objectiveManager, getDivingGearIfNeeded: false)
|
|
||||||
{
|
|
||||||
// If we need diving gear, we should already have it, if possible.
|
|
||||||
AllowGoingOutside = HumanAIController.HasDivingSuit(character)
|
|
||||||
}, onAbandon: () => unreachable.Add(goToObjective.Target as Hull));
|
|
||||||
}
|
}
|
||||||
|
goToObjective = null;
|
||||||
|
HumanAIController.ObjectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
|
||||||
|
//SteeringManager.SteeringWander();
|
||||||
}
|
}
|
||||||
if (goToObjective != null) { return; }
|
}
|
||||||
if (currentHull == null) { return; }
|
else if (currentHull != null)
|
||||||
|
{
|
||||||
//goto objective doesn't exist (a safe hull not found, or a path to a safe hull not found)
|
//goto objective doesn't exist (a safe hull not found, or a path to a safe hull not found)
|
||||||
// -> attempt to manually steer away from hazards
|
// -> attempt to manually steer away from hazards
|
||||||
Vector2 escapeVel = Vector2.Zero;
|
Vector2 escapeVel = Vector2.Zero;
|
||||||
@@ -131,15 +136,17 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
Vector2 dir = character.Position - fireSource.Position;
|
Vector2 dir = character.Position - fireSource.Position;
|
||||||
float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(fireSource.Position, character.Position), 0.1f, 10.0f);
|
float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(fireSource.Position, character.Position), 0.1f, 10.0f);
|
||||||
escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Character enemy in Character.CharacterList)
|
foreach (Character enemy in Character.CharacterList)
|
||||||
{
|
{
|
||||||
//don't run from friendly NPCs
|
//don't run from friendly NPCs
|
||||||
if (enemy.TeamID == Character.TeamType.FriendlyNPC) { continue; }
|
if (enemy.TeamID == Character.TeamType.FriendlyNPC) { continue; }
|
||||||
//friendly NPCs don't run away from anything but characters controlled by EnemyAIController (= monsters)
|
//friendly NPCs don't run away from anything but characters controlled by EnemyAIController (= monsters)
|
||||||
if (character.TeamID == Character.TeamType.FriendlyNPC && !(enemy.AIController is EnemyAIController)) { continue; }
|
if (character.TeamID == Character.TeamType.FriendlyNPC && !(enemy.AIController is EnemyAIController)) { continue; }
|
||||||
if (enemy.CurrentHull == currentHull && !enemy.IsDead && !enemy.IsUnconscious &&
|
|
||||||
|
if (enemy.CurrentHull == currentHull && !enemy.IsDead && !enemy.IsUnconscious &&
|
||||||
(enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID))
|
(enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID))
|
||||||
{
|
{
|
||||||
Vector2 dir = character.Position - enemy.Position;
|
Vector2 dir = character.Position - enemy.Position;
|
||||||
@@ -147,6 +154,7 @@ namespace Barotrauma
|
|||||||
escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (escapeVel != Vector2.Zero)
|
if (escapeVel != Vector2.Zero)
|
||||||
{
|
{
|
||||||
//only move if we haven't reached the edge of the room
|
//only move if we haven't reached the edge of the room
|
||||||
@@ -243,5 +251,36 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
return bestHull;
|
return bestHull;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool IsDuplicate(AIObjective otherObjective)
|
||||||
|
{
|
||||||
|
return (otherObjective is AIObjectiveFindSafety);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(AIObjectiveManager objectiveManager, float deltaTime)
|
||||||
|
{
|
||||||
|
if (character.CurrentHull == null)
|
||||||
|
{
|
||||||
|
currenthullSafety = 0;
|
||||||
|
Priority = 5;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (character.OxygenAvailable < CharacterHealth.LowOxygenThreshold) { Priority = 100; }
|
||||||
|
currenthullSafety = HumanAIController.GetHullSafety(character.CurrentHull);
|
||||||
|
if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||||
|
{
|
||||||
|
Priority -= priorityDecrease * deltaTime;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
float dangerFactor = (100 - currenthullSafety) / 100;
|
||||||
|
Priority += dangerFactor * priorityIncrease * deltaTime;
|
||||||
|
}
|
||||||
|
Priority = MathHelper.Clamp(Priority, 0, 100);
|
||||||
|
if (divingGearObjective != null && !divingGearObjective.IsCompleted() && divingGearObjective.CanBeCompleted)
|
||||||
|
{
|
||||||
|
Priority = Math.Max(Priority, AIObjectiveManager.OrderPriority + 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+90
-306
@@ -3,24 +3,28 @@ using FarseerPhysics;
|
|||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Barotrauma.Extensions;
|
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
class AIObjectiveFixLeak : AIObjective
|
class AIObjectiveFixLeak : AIObjective
|
||||||
{
|
{
|
||||||
public override string DebugTag => "fix leak";
|
public override string DebugTag => "fix leak";
|
||||||
|
|
||||||
|
public override bool KeepDivingGearOn => true;
|
||||||
public override bool ForceRun => true;
|
public override bool ForceRun => true;
|
||||||
|
|
||||||
public Gap Leak { get; private set; }
|
private readonly Gap leak;
|
||||||
|
|
||||||
private AIObjectiveFindDivingGear findDivingGear;
|
private AIObjectiveFindDivingGear findDivingGear;
|
||||||
private AIObjectiveGetItem getWeldingTool;
|
|
||||||
private AIObjectiveContainItem refuelObjective;
|
|
||||||
private AIObjectiveGoTo gotoObjective;
|
private AIObjectiveGoTo gotoObjective;
|
||||||
private AIObjectiveOperateItem operateObjective;
|
private AIObjectiveOperateItem operateObjective;
|
||||||
|
|
||||||
|
public Gap Leak
|
||||||
|
{
|
||||||
|
get { return leak; }
|
||||||
|
}
|
||||||
|
|
||||||
public AIObjectiveFixLeak(Gap leak, Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base (character, objectiveManager, priorityModifier)
|
public AIObjectiveFixLeak(Gap leak, Character character) : base (character, "")
|
||||||
{
|
{
|
||||||
Leak = leak;
|
Leak = leak;
|
||||||
}
|
}
|
||||||
@@ -30,16 +34,17 @@ namespace Barotrauma
|
|||||||
return Leak.Open <= 0.0f || Leak.Removed;
|
return Leak.Open <= 0.0f || Leak.Removed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override float GetPriority()
|
public override bool CanBeCompleted => !abandon && base.CanBeCompleted;
|
||||||
|
|
||||||
|
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||||
{
|
{
|
||||||
if (Leak.Open == 0.0f) { return 0.0f; }
|
if (leak.Open == 0.0f) { return 0.0f; }
|
||||||
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally)
|
|
||||||
float dist = Math.Abs(character.WorldPosition.X - Leak.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - Leak.WorldPosition.Y) * 2.0f;
|
float leakSize = (leak.IsHorizontal ? leak.Rect.Height : leak.Rect.Width) * Math.Max(leak.Open, 0.1f);
|
||||||
float distanceFactor = MathHelper.Lerp(1, 0.25f, MathUtils.InverseLerp(0, 10000, dist));
|
|
||||||
float severity = AIObjectiveFixLeaks.GetLeakSeverity(Leak);
|
float dist = Vector2.DistanceSquared(character.SimPosition, leak.SimPosition);
|
||||||
float max = Math.Min((AIObjectiveManager.OrderPriority - 1), 90);
|
dist = Math.Max(dist / 100.0f, 1.0f);
|
||||||
float devotion = Math.Min(Priority, 10) / 100;
|
return Math.Min(leakSize / dist, 40.0f);
|
||||||
return MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + severity * distanceFactor * PriorityModifier, 0, 1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool IsDuplicate(AIObjective otherObjective)
|
public override bool IsDuplicate(AIObjective otherObjective)
|
||||||
@@ -50,44 +55,59 @@ namespace Barotrauma
|
|||||||
|
|
||||||
protected override void Act(float deltaTime)
|
protected override void Act(float deltaTime)
|
||||||
{
|
{
|
||||||
if (!Leak.IsRoomToRoom)
|
if (!leak.IsRoomToRoom)
|
||||||
{
|
{
|
||||||
if (!HumanAIController.HasDivingSuit(character))
|
if (findDivingGear == null)
|
||||||
{
|
{
|
||||||
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, true, objectiveManager));
|
findDivingGear = new AIObjectiveFindDivingGear(character, true);
|
||||||
|
AddSubObjective(findDivingGear);
|
||||||
|
}
|
||||||
|
else if (!findDivingGear.CanBeCompleted)
|
||||||
|
{
|
||||||
|
abandon = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var weldingTool = character.Inventory.FindItemByTag("weldingtool");
|
var weldingTool = character.Inventory.FindItemByTag("weldingtool");
|
||||||
|
|
||||||
if (weldingTool == null)
|
if (weldingTool == null)
|
||||||
{
|
{
|
||||||
TryAddSubObjective(ref getWeldingTool, () => new AIObjectiveGetItem(character, "weldingtool", objectiveManager, true));
|
AddSubObjective(new AIObjectiveGetItem(character, "weldingtool", true));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var containedItems = weldingTool.ContainedItems;
|
var containedItems = weldingTool.ContainedItems;
|
||||||
if (containedItems == null)
|
if (containedItems == null) return;
|
||||||
|
|
||||||
|
var fuelTank = containedItems.FirstOrDefault(i => i.HasTag("weldingfueltank") && i.Condition > 0.0f);
|
||||||
|
if (fuelTank == null)
|
||||||
{
|
{
|
||||||
#if DEBUG
|
AddSubObjective(new AIObjectiveContainItem(character, "weldingfueltank", weldingTool.GetComponent<ItemContainer>()));
|
||||||
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no proper inventory");
|
|
||||||
#endif
|
|
||||||
abandon = true;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Drop empty tanks
|
}
|
||||||
foreach (Item containedItem in containedItems)
|
|
||||||
|
var repairTool = weldingTool.GetComponent<RepairTool>();
|
||||||
|
if (repairTool == null) { return; }
|
||||||
|
|
||||||
|
Vector2 gapDiff = leak.WorldPosition - character.WorldPosition;
|
||||||
|
|
||||||
|
// TODO: use the collider size/reach?
|
||||||
|
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
|
||||||
|
{
|
||||||
|
HumanAIController.AnimController.Crouching = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
|
||||||
|
bool canReach = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
|
||||||
|
if (canReach)
|
||||||
|
{
|
||||||
|
Limb sightLimb = null;
|
||||||
|
if (character.Inventory.IsInLimbSlot(repairTool.Item, InvSlotType.RightHand))
|
||||||
{
|
{
|
||||||
if (containedItem == null) { continue; }
|
sightLimb = character.AnimController.GetLimb(LimbType.RightHand);
|
||||||
if (containedItem.Condition <= 0.0f)
|
|
||||||
{
|
|
||||||
containedItem.Drop(character);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (containedItems.None(i => i.HasTag("weldingfueltank") && i.Condition > 0.0f))
|
|
||||||
{
|
|
||||||
TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, "weldingfueltank", weldingTool.GetComponent<ItemContainer>(), objectiveManager));
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
else if (character.Inventory.IsInLimbSlot(repairTool.Item, InvSlotType.LeftHand))
|
else if (character.Inventory.IsInLimbSlot(repairTool.Item, InvSlotType.LeftHand))
|
||||||
{
|
{
|
||||||
@@ -95,281 +115,45 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
canReach = character.CanSeeTarget(leak, sightLimb);
|
canReach = character.CanSeeTarget(leak, sightLimb);
|
||||||
}
|
}
|
||||||
var repairTool = weldingTool.GetComponent<RepairTool>();
|
|
||||||
if (repairTool == null)
|
|
||||||
{
|
|
||||||
#if DEBUG
|
|
||||||
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
|
|
||||||
#endif
|
|
||||||
abandon = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition;
|
|
||||||
// TODO: use the collider size/reach?
|
|
||||||
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
|
|
||||||
{
|
|
||||||
HumanAIController.AnimController.Crouching = true;
|
|
||||||
}
|
|
||||||
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
|
|
||||||
bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
|
|
||||||
if (canOperate)
|
|
||||||
{
|
|
||||||
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
|
if (gotoObjective != null)
|
||||||
|
{
|
||||||
|
// Check if the objective is already removed -> completed/impossible
|
||||||
|
if (!subObjectives.Contains(gotoObjective))
|
||||||
|
{
|
||||||
|
if (!gotoObjective.CanBeCompleted)
|
||||||
|
{
|
||||||
|
abandon = true;
|
||||||
|
}
|
||||||
|
gotoObjective = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gotoObjective = new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character)
|
||||||
|
{
|
||||||
|
CloseEnough = reach
|
||||||
|
};
|
||||||
|
if (!subObjectives.Contains(gotoObjective))
|
||||||
|
{
|
||||||
|
AddSubObjective(gotoObjective);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var repairTool = weldingTool.GetComponent<RepairTool>();
|
if (gotoObjective == null || gotoObjective.IsCompleted())
|
||||||
if (repairTool == null)
|
|
||||||
{
|
{
|
||||||
#if DEBUG
|
if (operateObjective == null)
|
||||||
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
|
{
|
||||||
#endif
|
operateObjective = new AIObjectiveOperateItem(repairTool, character, "", true, leak);
|
||||||
abandon = true;
|
AddSubObjective(operateObjective);
|
||||||
return;
|
}
|
||||||
}
|
else if (!subObjectives.Contains(operateObjective))
|
||||||
Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition;
|
{
|
||||||
// TODO: use the collider size/reach?
|
operateObjective = null;
|
||||||
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
|
}
|
||||||
{
|
}
|
||||||
HumanAIController.AnimController.Crouching = true;
|
|
||||||
}
|
|
||||||
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
|
|
||||||
bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
|
|
||||||
if (canOperate)
|
|
||||||
{
|
|
||||||
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
|
|
||||||
}
|
|
||||||
var repairTool = weldingTool.GetComponent<RepairTool>();
|
|
||||||
if (repairTool == null)
|
|
||||||
{
|
|
||||||
#if DEBUG
|
|
||||||
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
|
|
||||||
#endif
|
|
||||||
abandon = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition;
|
|
||||||
// TODO: use the collider size/reach?
|
|
||||||
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
|
|
||||||
{
|
|
||||||
HumanAIController.AnimController.Crouching = true;
|
|
||||||
}
|
|
||||||
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
|
|
||||||
bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
|
|
||||||
if (canOperate)
|
|
||||||
{
|
|
||||||
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
|
|
||||||
}
|
|
||||||
var repairTool = weldingTool.GetComponent<RepairTool>();
|
|
||||||
if (repairTool == null)
|
|
||||||
{
|
|
||||||
#if DEBUG
|
|
||||||
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
|
|
||||||
#endif
|
|
||||||
abandon = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition;
|
|
||||||
// TODO: use the collider size/reach?
|
|
||||||
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
|
|
||||||
{
|
|
||||||
HumanAIController.AnimController.Crouching = true;
|
|
||||||
}
|
|
||||||
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
|
|
||||||
bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
|
|
||||||
if (canOperate)
|
|
||||||
{
|
|
||||||
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
|
|
||||||
}
|
|
||||||
var repairTool = weldingTool.GetComponent<RepairTool>();
|
|
||||||
if (repairTool == null)
|
|
||||||
{
|
|
||||||
#if DEBUG
|
|
||||||
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
|
|
||||||
#endif
|
|
||||||
abandon = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition;
|
|
||||||
// TODO: use the collider size/reach?
|
|
||||||
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
|
|
||||||
{
|
|
||||||
HumanAIController.AnimController.Crouching = true;
|
|
||||||
}
|
|
||||||
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
|
|
||||||
bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
|
|
||||||
if (canOperate)
|
|
||||||
{
|
|
||||||
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
|
|
||||||
}
|
|
||||||
var repairTool = weldingTool.GetComponent<RepairTool>();
|
|
||||||
if (repairTool == null)
|
|
||||||
{
|
|
||||||
#if DEBUG
|
|
||||||
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
|
|
||||||
#endif
|
|
||||||
abandon = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition;
|
|
||||||
// TODO: use the collider size/reach?
|
|
||||||
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
|
|
||||||
{
|
|
||||||
HumanAIController.AnimController.Crouching = true;
|
|
||||||
}
|
|
||||||
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
|
|
||||||
bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
|
|
||||||
if (canOperate)
|
|
||||||
{
|
|
||||||
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
|
|
||||||
}
|
|
||||||
var repairTool = weldingTool.GetComponent<RepairTool>();
|
|
||||||
if (repairTool == null)
|
|
||||||
{
|
|
||||||
#if DEBUG
|
|
||||||
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
|
|
||||||
#endif
|
|
||||||
abandon = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition;
|
|
||||||
// TODO: use the collider size/reach?
|
|
||||||
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
|
|
||||||
{
|
|
||||||
HumanAIController.AnimController.Crouching = true;
|
|
||||||
}
|
|
||||||
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
|
|
||||||
bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
|
|
||||||
if (canOperate)
|
|
||||||
{
|
|
||||||
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
|
|
||||||
}
|
|
||||||
var repairTool = weldingTool.GetComponent<RepairTool>();
|
|
||||||
if (repairTool == null)
|
|
||||||
{
|
|
||||||
#if DEBUG
|
|
||||||
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
|
|
||||||
#endif
|
|
||||||
abandon = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition;
|
|
||||||
// TODO: use the collider size/reach?
|
|
||||||
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
|
|
||||||
{
|
|
||||||
HumanAIController.AnimController.Crouching = true;
|
|
||||||
}
|
|
||||||
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
|
|
||||||
bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
|
|
||||||
if (canOperate)
|
|
||||||
{
|
|
||||||
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
|
|
||||||
}
|
|
||||||
var repairTool = weldingTool.GetComponent<RepairTool>();
|
|
||||||
if (repairTool == null)
|
|
||||||
{
|
|
||||||
#if DEBUG
|
|
||||||
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
|
|
||||||
#endif
|
|
||||||
abandon = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition;
|
|
||||||
// TODO: use the collider size/reach?
|
|
||||||
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
|
|
||||||
{
|
|
||||||
HumanAIController.AnimController.Crouching = true;
|
|
||||||
}
|
|
||||||
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
|
|
||||||
bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
|
|
||||||
if (canOperate)
|
|
||||||
{
|
|
||||||
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
|
|
||||||
}
|
|
||||||
var repairTool = weldingTool.GetComponent<RepairTool>();
|
|
||||||
if (repairTool == null)
|
|
||||||
{
|
|
||||||
#if DEBUG
|
|
||||||
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
|
|
||||||
#endif
|
|
||||||
abandon = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition;
|
|
||||||
// TODO: use the collider size/reach?
|
|
||||||
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
|
|
||||||
{
|
|
||||||
HumanAIController.AnimController.Crouching = true;
|
|
||||||
}
|
|
||||||
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
|
|
||||||
bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
|
|
||||||
if (canOperate)
|
|
||||||
{
|
|
||||||
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
|
|
||||||
}
|
|
||||||
var repairTool = weldingTool.GetComponent<RepairTool>();
|
|
||||||
if (repairTool == null)
|
|
||||||
{
|
|
||||||
#if DEBUG
|
|
||||||
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
|
|
||||||
#endif
|
|
||||||
abandon = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition;
|
|
||||||
// TODO: use the collider size/reach?
|
|
||||||
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
|
|
||||||
{
|
|
||||||
HumanAIController.AnimController.Crouching = true;
|
|
||||||
}
|
|
||||||
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
|
|
||||||
bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
|
|
||||||
if (canOperate)
|
|
||||||
{
|
|
||||||
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Vector2 GetStandPosition()
|
private Vector2 GetStandPosition()
|
||||||
|
|||||||
+27
-22
@@ -9,28 +9,38 @@ namespace Barotrauma
|
|||||||
class AIObjectiveFixLeaks : AIObjectiveLoop<Gap>
|
class AIObjectiveFixLeaks : AIObjectiveLoop<Gap>
|
||||||
{
|
{
|
||||||
public override string DebugTag => "fix leaks";
|
public override string DebugTag => "fix leaks";
|
||||||
|
public override bool KeepDivingGearOn => true;
|
||||||
public override bool ForceRun => true;
|
public override bool ForceRun => true;
|
||||||
|
|
||||||
public AIObjectiveFixLeaks(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
|
public AIObjectiveFixLeaks(Character character) : base (character, "") { }
|
||||||
|
|
||||||
|
protected override void FindTargets()
|
||||||
|
{
|
||||||
|
if (character.Submarine == null) { return 0; }
|
||||||
|
if (targets.None()) { return 0; }
|
||||||
|
if (objectiveManager.CurrentOrder == this)
|
||||||
|
{
|
||||||
|
return AIObjectiveManager.OrderPriority;
|
||||||
|
}
|
||||||
|
return MathHelper.Lerp(0, AIObjectiveManager.OrderPriority, targets.Average(t => Average(t)));
|
||||||
|
}
|
||||||
|
|
||||||
protected override void FindTargets()
|
protected override void FindTargets()
|
||||||
{
|
{
|
||||||
base.FindTargets();
|
base.FindTargets();
|
||||||
if (targets.None() && objectiveManager.CurrentOrder == this)
|
targets.Sort((x, y) => GetGapFixPriority(y).CompareTo(GetGapFixPriority(x)));
|
||||||
{
|
|
||||||
character.Speak(TextManager.Get("DialogNoLeaks"), null, 3.0f, "noleaks", 30.0f);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool Filter(Gap gap) => IsValidTarget(gap, character);
|
protected override bool Filter(Gap gap)
|
||||||
|
|
||||||
public static float GetLeakSeverity(Gap leak)
|
|
||||||
{
|
{
|
||||||
if (leak == null) { return 0; }
|
bool ignore = ignoreList.Contains(gap) || gap.ConnectedWall == null || gap.ConnectedDoor != null || gap.Open <= 0 || gap.linkedTo.All(l => l == null);
|
||||||
float sizeFactor = MathHelper.Lerp(1, 10, MathUtils.InverseLerp(0, 200, (leak.IsHorizontal ? leak.Rect.Width : leak.Rect.Height)));
|
if (!ignore)
|
||||||
float severity = sizeFactor * leak.Open;
|
{
|
||||||
if (!leak.IsRoomToRoom) { severity *= 50; }
|
if (gap.Submarine == null) { ignore = true; }
|
||||||
return MathHelper.Min(severity, 100);
|
else if (gap.Submarine.TeamID != character.TeamID) { ignore = true; }
|
||||||
|
else if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(gap, true)) { ignore = true; }
|
||||||
|
}
|
||||||
|
return ignore;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFixLeaks;
|
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFixLeaks;
|
||||||
@@ -38,14 +48,9 @@ namespace Barotrauma
|
|||||||
protected override IEnumerable<Gap> GetList() => Gap.GapList;
|
protected override IEnumerable<Gap> GetList() => Gap.GapList;
|
||||||
protected override AIObjective ObjectiveConstructor(Gap gap) => new AIObjectiveFixLeak(gap, character, objectiveManager, PriorityModifier);
|
protected override AIObjective ObjectiveConstructor(Gap gap) => new AIObjectiveFixLeak(gap, character, objectiveManager, PriorityModifier);
|
||||||
|
|
||||||
public static bool IsValidTarget(Gap gap, Character character)
|
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFixLeaks;
|
||||||
{
|
protected override float Average(Gap gap) => gap.Open;
|
||||||
if (gap == null) { return false; }
|
protected override IEnumerable<Gap> GetList() => Gap.GapList;
|
||||||
if (gap.ConnectedWall == null || gap.ConnectedDoor != null || gap.Open <= 0 || gap.linkedTo.All(l => l == null)) { return false; }
|
protected override AIObjective ObjectiveConstructor(Gap gap) => new AIObjectiveFixLeak(gap, character);
|
||||||
if (gap.Submarine == null) { return false; }
|
|
||||||
if (gap.Submarine.TeamID != character.TeamID) { return false; }
|
|
||||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(gap, true)) { return false; }
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,6 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
public override string DebugTag => "get item";
|
public override string DebugTag => "get item";
|
||||||
|
|
||||||
private readonly bool equip;
|
|
||||||
private readonly HashSet<Item> ignoredItems = new HashSet<Item>();
|
|
||||||
|
|
||||||
public Func<Item, float> GetItemPriority;
|
public Func<Item, float> GetItemPriority;
|
||||||
|
|
||||||
//can be either tags or identifiers
|
//can be either tags or identifiers
|
||||||
@@ -23,8 +20,14 @@ namespace Barotrauma
|
|||||||
public string[] ignoredContainerIdentifiers;
|
public string[] ignoredContainerIdentifiers;
|
||||||
private AIObjectiveGoTo goToObjective;
|
private AIObjectiveGoTo goToObjective;
|
||||||
private float currItemPriority;
|
private float currItemPriority;
|
||||||
|
private bool equip;
|
||||||
|
|
||||||
public override float GetPriority()
|
private HashSet<Item> ignoredItems = new HashSet<Item>();
|
||||||
|
|
||||||
|
private bool canBeCompleted = true;
|
||||||
|
public override bool CanBeCompleted => canBeCompleted;
|
||||||
|
|
||||||
|
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||||
{
|
{
|
||||||
if (objectiveManager.CurrentOrder == this)
|
if (objectiveManager.CurrentOrder == this)
|
||||||
{
|
{
|
||||||
@@ -33,19 +36,16 @@ namespace Barotrauma
|
|||||||
return 1.0f;
|
return 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AIObjectiveGetItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, bool equip = false, float priorityModifier = 1)
|
public AIObjectiveGetItem(Character character, Item targetItem, bool equip = false) : base(character, "")
|
||||||
: base(character, objectiveManager, priorityModifier)
|
|
||||||
{
|
{
|
||||||
currSearchIndex = -1;
|
currSearchIndex = -1;
|
||||||
this.equip = equip;
|
this.equip = equip;
|
||||||
this.targetItem = targetItem;
|
this.targetItem = targetItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AIObjectiveGetItem(Character character, string itemIdentifier, AIObjectiveManager objectiveManager, bool equip = false, float priorityModifier = 1)
|
public AIObjectiveGetItem(Character character, string itemIdentifier, bool equip = false) : this(character, new string[] { itemIdentifier }, equip) { }
|
||||||
: this(character, new string[] { itemIdentifier }, objectiveManager, equip, priorityModifier) { }
|
|
||||||
|
|
||||||
public AIObjectiveGetItem(Character character, string[] itemIdentifiers, AIObjectiveManager objectiveManager, bool equip = false, float priorityModifier = 1)
|
public AIObjectiveGetItem(Character character, string[] itemIdentifiers, bool equip = false) : base(character, "")
|
||||||
: base(character, objectiveManager, priorityModifier)
|
|
||||||
{
|
{
|
||||||
currSearchIndex = -1;
|
currSearchIndex = -1;
|
||||||
this.equip = equip;
|
this.equip = equip;
|
||||||
@@ -54,15 +54,20 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant();
|
itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckInventory();
|
CheckInventory();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CheckInventory()
|
private void CheckInventory()
|
||||||
{
|
{
|
||||||
if (itemIdentifiers == null) { return; }
|
if (itemIdentifiers == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < character.Inventory.Items.Length; i++)
|
for (int i = 0; i < character.Inventory.Items.Length; i++)
|
||||||
{
|
{
|
||||||
if (character.Inventory.Items[i] == null || character.Inventory.Items[i].Condition <= 0.0f) { continue; }
|
if (character.Inventory.Items[i] == null || character.Inventory.Items[i].Condition <= 0.0f) continue;
|
||||||
if (itemIdentifiers.Any(id => character.Inventory.Items[i].Prefab.Identifier == id || character.Inventory.Items[i].HasTag(id)))
|
if (itemIdentifiers.Any(id => character.Inventory.Items[i].Prefab.Identifier == id || character.Inventory.Items[i].HasTag(id)))
|
||||||
{
|
{
|
||||||
targetItem = character.Inventory.Items[i];
|
targetItem = character.Inventory.Items[i];
|
||||||
@@ -76,7 +81,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
foreach (Item containedItem in containedItems)
|
foreach (Item containedItem in containedItems)
|
||||||
{
|
{
|
||||||
if (containedItem == null || containedItem.Condition <= 0.0f) { continue; }
|
if (containedItem == null || containedItem.Condition <= 0.0f) continue;
|
||||||
if (itemIdentifiers.Any(id => containedItem.Prefab.Identifier == id || containedItem.HasTag(id)))
|
if (itemIdentifiers.Any(id => containedItem.Prefab.Identifier == id || containedItem.HasTag(id)))
|
||||||
{
|
{
|
||||||
targetItem = containedItem;
|
targetItem = containedItem;
|
||||||
@@ -94,11 +99,13 @@ namespace Barotrauma
|
|||||||
FindTargetItem();
|
FindTargetItem();
|
||||||
if (targetItem == null || moveToTarget == null)
|
if (targetItem == null || moveToTarget == null)
|
||||||
{
|
{
|
||||||
objectiveManager.GetObjective<AIObjectiveIdle>()?.Wander(deltaTime);
|
HumanAIController.ObjectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
|
||||||
|
//SteeringManager.SteeringWander();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (moveToTarget.CurrentHull == character.CurrentHull &&
|
if (moveToTarget.CurrentHull == character.CurrentHull &&
|
||||||
Vector2.DistanceSquared(character.Position, moveToTarget.Position) < MathUtils.Pow(targetItem.InteractDistance, 2))
|
Vector2.DistanceSquared(character.Position, moveToTarget.Position) < MathUtils.Pow(targetItem.InteractDistance * 2, 2))
|
||||||
{
|
{
|
||||||
int targetSlot = -1;
|
int targetSlot = -1;
|
||||||
if (equip)
|
if (equip)
|
||||||
@@ -116,7 +123,8 @@ namespace Barotrauma
|
|||||||
for (int i = 0; i < character.Inventory.Items.Length; i++)
|
for (int i = 0; i < character.Inventory.Items.Length; i++)
|
||||||
{
|
{
|
||||||
//slot not needed by the item, continue
|
//slot not needed by the item, continue
|
||||||
if (!slots.HasFlag(character.Inventory.SlotTypes[i])) { continue; }
|
if (!slots.HasFlag(character.Inventory.SlotTypes[i])) continue;
|
||||||
|
|
||||||
targetSlot = i;
|
targetSlot = i;
|
||||||
//slot free, continue
|
//slot free, continue
|
||||||
if (character.Inventory.Items[i] == null) { continue; }
|
if (character.Inventory.Items[i] == null) { continue; }
|
||||||
@@ -128,6 +136,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
targetItem.TryInteract(character, false, true);
|
targetItem.TryInteract(character, false, true);
|
||||||
|
|
||||||
if (targetSlot > -1 && !character.HasEquippedItem(targetItem))
|
if (targetSlot > -1 && !character.HasEquippedItem(targetItem))
|
||||||
{
|
{
|
||||||
character.Inventory.TryPutItem(targetItem, targetSlot, false, false, character);
|
character.Inventory.TryPutItem(targetItem, targetSlot, false, false, character);
|
||||||
@@ -135,20 +144,23 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TryAddSubObjective(ref goToObjective,
|
if (goToObjective == null || moveToTarget != goToObjective.Target)
|
||||||
constructor: () =>
|
{
|
||||||
{
|
//check if we're already looking for a diving gear
|
||||||
//check if we're already looking for a diving gear
|
bool gettingDivingGear = (targetItem != null && targetItem.Prefab.Identifier == "divingsuit" || targetItem.HasTag("diving")) ||
|
||||||
bool gettingDivingGear = (targetItem != null && targetItem.Prefab.Identifier == "divingsuit" || targetItem.HasTag("diving")) ||
|
(itemIdentifiers != null && (itemIdentifiers.Contains("diving") || itemIdentifiers.Contains("divingsuit")));
|
||||||
(itemIdentifiers != null && (itemIdentifiers.Contains("diving") || itemIdentifiers.Contains("divingsuit")));
|
|
||||||
return new AIObjectiveGoTo(moveToTarget, character, objectiveManager, repeat: false, getDivingGearIfNeeded: !gettingDivingGear);
|
//don't attempt to get diving gear to reach the destination if the item we're trying to get is diving gear
|
||||||
},
|
goToObjective = new AIObjectiveGoTo(moveToTarget, character, false, !gettingDivingGear);
|
||||||
onAbandon: () =>
|
}
|
||||||
{
|
|
||||||
targetItem = null;
|
goToObjective.TryComplete(deltaTime);
|
||||||
moveToTarget = null;
|
if (!goToObjective.CanBeCompleted)
|
||||||
ignoredItems.Add(targetItem);
|
{
|
||||||
});
|
targetItem = null;
|
||||||
|
moveToTarget = null;
|
||||||
|
ignoredItems.Add(targetItem);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,10 +176,11 @@ namespace Barotrauma
|
|||||||
#if DEBUG
|
#if DEBUG
|
||||||
DebugConsole.NewMessage($"{character.Name}: Cannot find the item, because neither identifiers nor item is was defined.");
|
DebugConsole.NewMessage($"{character.Name}: Cannot find the item, because neither identifiers nor item is was defined.");
|
||||||
#endif
|
#endif
|
||||||
abandon = true;
|
canBeCompleted = false;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < 10 && currSearchIndex < Item.ItemList.Count - 1; i++)
|
for (int i = 0; i < 10 && currSearchIndex < Item.ItemList.Count - 1; i++)
|
||||||
{
|
{
|
||||||
currSearchIndex++;
|
currSearchIndex++;
|
||||||
@@ -176,18 +189,15 @@ namespace Barotrauma
|
|||||||
if (item.Submarine == null) { continue; }
|
if (item.Submarine == null) { continue; }
|
||||||
else if (item.Submarine.TeamID != character.TeamID) { continue; }
|
else if (item.Submarine.TeamID != character.TeamID) { continue; }
|
||||||
else if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { continue; }
|
else if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { continue; }
|
||||||
|
|
||||||
if (item.CurrentHull == null || item.Condition <= 0.0f) { continue; }
|
if (item.CurrentHull == null || item.Condition <= 0.0f) { continue; }
|
||||||
if (itemIdentifiers.None(id => item.Prefab.Identifier == id || item.HasTag(id))) { continue; }
|
if (itemIdentifiers.None(id => item.Prefab.Identifier == id || item.HasTag(id))) { continue; }
|
||||||
|
|
||||||
if (ignoredContainerIdentifiers != null && item.Container != null)
|
if (ignoredContainerIdentifiers != null && item.Container != null)
|
||||||
{
|
{
|
||||||
if (ignoredContainerIdentifiers.Contains(item.ContainerIdentifier)) { continue; }
|
if (ignoredContainerIdentifiers.Contains(item.ContainerIdentifier)) { continue; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: need to exclude items that already are in the inventory?
|
|
||||||
//// Don't allow to get items in rooms that have a fire (except fire extinguishers) or an enemy inside (except weapons)
|
|
||||||
//if (!itemIdentifiers.Contains("extinguisher") && item.CurrentHull.FireSources.Count > 0 ||
|
|
||||||
// item.GetComponent<MeleeWeapon>() == null && item.GetComponent<RangedWeapon>() == null && Character.CharacterList.Any(c => c.CurrentHull == item.CurrentHull && !HumanAIController.IsFriendly(c))) { continue; }
|
|
||||||
|
|
||||||
//if the item is inside a character's inventory, don't steal it unless the character is dead
|
//if the item is inside a character's inventory, don't steal it unless the character is dead
|
||||||
if (item.ParentInventory is CharacterInventory)
|
if (item.ParentInventory is CharacterInventory)
|
||||||
{
|
{
|
||||||
@@ -209,9 +219,11 @@ namespace Barotrauma
|
|||||||
itemPriority -= Vector2.Distance((rootContainer ?? item).Position, character.Position) * 0.01f;
|
itemPriority -= Vector2.Distance((rootContainer ?? item).Position, character.Position) * 0.01f;
|
||||||
//ignore if the item has a lower priority than the currently selected one
|
//ignore if the item has a lower priority than the currently selected one
|
||||||
if (moveToTarget != null && itemPriority < currItemPriority) { continue; }
|
if (moveToTarget != null && itemPriority < currItemPriority) { continue; }
|
||||||
|
|
||||||
currItemPriority = itemPriority;
|
currItemPriority = itemPriority;
|
||||||
targetItem = item;
|
targetItem = item;
|
||||||
moveToTarget = rootContainer ?? item;
|
moveToTarget = rootContainer ?? item;
|
||||||
|
|
||||||
}
|
}
|
||||||
//if searched through all the items and a target wasn't found, can't be completed
|
//if searched through all the items and a target wasn't found, can't be completed
|
||||||
if (currSearchIndex >= Item.ItemList.Count - 1 && targetItem == null)
|
if (currSearchIndex >= Item.ItemList.Count - 1 && targetItem == null)
|
||||||
@@ -219,21 +231,21 @@ namespace Barotrauma
|
|||||||
#if DEBUG
|
#if DEBUG
|
||||||
DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s): {string.Join(", ", itemIdentifiers)}");
|
DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s): {string.Join(", ", itemIdentifiers)}");
|
||||||
#endif
|
#endif
|
||||||
abandon = true;
|
canBeCompleted = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool IsDuplicate(AIObjective otherObjective)
|
public override bool IsDuplicate(AIObjective otherObjective)
|
||||||
{
|
{
|
||||||
AIObjectiveGetItem getItem = otherObjective as AIObjectiveGetItem;
|
AIObjectiveGetItem getItem = otherObjective as AIObjectiveGetItem;
|
||||||
if (getItem == null) { return false; }
|
if (getItem == null) return false;
|
||||||
if (getItem.equip != equip) { return false; }
|
if (getItem.equip != equip) return false;
|
||||||
if (getItem.itemIdentifiers != null && itemIdentifiers != null)
|
if (getItem.itemIdentifiers != null && itemIdentifiers != null)
|
||||||
{
|
{
|
||||||
if (getItem.itemIdentifiers.Length != itemIdentifiers.Length) { return false; }
|
if (getItem.itemIdentifiers.Length != itemIdentifiers.Length) return false;
|
||||||
for (int i = 0; i < getItem.itemIdentifiers.Length; i++)
|
for (int i = 0; i < getItem.itemIdentifiers.Length; i++)
|
||||||
{
|
{
|
||||||
if (getItem.itemIdentifiers[i] != itemIdentifiers[i]) { return false; }
|
if (getItem.itemIdentifiers[i] != itemIdentifiers[i]) return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -251,10 +263,7 @@ namespace Barotrauma
|
|||||||
foreach (string itemName in itemIdentifiers)
|
foreach (string itemName in itemIdentifiers)
|
||||||
{
|
{
|
||||||
var matchingItem = character.Inventory.FindItemByTag(itemName) ?? character.Inventory.FindItemByIdentifier(itemName);
|
var matchingItem = character.Inventory.FindItemByTag(itemName) ?? character.Inventory.FindItemByIdentifier(itemName);
|
||||||
if (matchingItem != null && (!equip || character.HasEquippedItem(matchingItem)))
|
if (matchingItem != null && (!equip || character.HasEquippedItem(matchingItem))) return true;
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,23 +9,29 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
public override string DebugTag => "go to";
|
public override string DebugTag => "go to";
|
||||||
|
|
||||||
|
private AIObjectiveFindDivingGear findDivingGear;
|
||||||
|
|
||||||
private AIObjectiveFindDivingGear findDivingGear;
|
private AIObjectiveFindDivingGear findDivingGear;
|
||||||
private Vector2 targetPos;
|
private Vector2 targetPos;
|
||||||
private bool repeat;
|
private bool repeat;
|
||||||
|
private bool cannotReach;
|
||||||
|
|
||||||
//how long until the path to the target is declared unreachable
|
//how long until the path to the target is declared unreachable
|
||||||
private float waitUntilPathUnreachable;
|
private float waitUntilPathUnreachable;
|
||||||
private bool getDivingGearIfNeeded;
|
private bool getDivingGearIfNeeded;
|
||||||
|
|
||||||
public float CloseEnough { get; set; } = 0.5f;
|
public float CloseEnough = 0.5f;
|
||||||
public bool IgnoreIfTargetDead { get; set; }
|
|
||||||
public bool AllowGoingOutside { get; set; }
|
|
||||||
public bool CheckVisibility { get; set; }
|
|
||||||
|
|
||||||
public override float GetPriority()
|
public bool IgnoreIfTargetDead;
|
||||||
|
|
||||||
|
public bool AllowGoingOutside = false;
|
||||||
|
|
||||||
|
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||||
{
|
{
|
||||||
if (FollowControlledCharacter && Character.Controlled == null) { return 0.0f; }
|
if (FollowControlledCharacter && Character.Controlled == null) { return 0.0f; }
|
||||||
if (Target != null && Target.Removed) { return 0.0f; }
|
if (Target != null && Target.Removed) { return 0.0f; }
|
||||||
if (IgnoreIfTargetDead && Target is Character character && character.IsDead) { return 0.0f; }
|
if (IgnoreIfTargetDead && Target is Character character && character.IsDead) { return 0.0f; }
|
||||||
|
|
||||||
if (objectiveManager.CurrentOrder == this)
|
if (objectiveManager.CurrentOrder == this)
|
||||||
{
|
{
|
||||||
return AIObjectiveManager.OrderPriority;
|
return AIObjectiveManager.OrderPriority;
|
||||||
@@ -33,6 +39,44 @@ namespace Barotrauma
|
|||||||
return 1.0f;
|
return 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool CanBeCompleted
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
bool canComplete = !cannotReach && !abandon;
|
||||||
|
if (canComplete)
|
||||||
|
{
|
||||||
|
if (FollowControlledCharacter && Character.Controlled == null)
|
||||||
|
{
|
||||||
|
canComplete = false;
|
||||||
|
}
|
||||||
|
else if (Target != null && Target.Removed)
|
||||||
|
{
|
||||||
|
canComplete = false;
|
||||||
|
}
|
||||||
|
else if (!repeat && waitUntilPathUnreachable < 0)
|
||||||
|
{
|
||||||
|
if (SteeringManager == PathSteering && PathSteering.CurrentPath != null)
|
||||||
|
{
|
||||||
|
canComplete = !PathSteering.CurrentPath.Unreachable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!canComplete)
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {(Target != null ? Target.ToString() : TargetPos.ToString())}", Color.Yellow);
|
||||||
|
#endif
|
||||||
|
if (HumanAIController.ObjectiveManager.CurrentOrder != null)
|
||||||
|
{
|
||||||
|
character.Speak(TextManager.Get("DialogCannotReach"), identifier: "cannotreach", minDurationBetweenSimilar: 10.0f);
|
||||||
|
}
|
||||||
|
character.AIController.SteeringManager.Reset();
|
||||||
|
}
|
||||||
|
return canComplete;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Entity Target { get; private set; }
|
public Entity Target { get; private set; }
|
||||||
|
|
||||||
public Vector2 TargetPos => Target != null ? Target.SimPosition : targetPos;
|
public Vector2 TargetPos => Target != null ? Target.SimPosition : targetPos;
|
||||||
@@ -45,7 +89,7 @@ namespace Barotrauma
|
|||||||
this.Target = target;
|
this.Target = target;
|
||||||
this.repeat = repeat;
|
this.repeat = repeat;
|
||||||
|
|
||||||
waitUntilPathUnreachable = 2.0f;
|
waitUntilPathUnreachable = 1.0f;
|
||||||
this.getDivingGearIfNeeded = getDivingGearIfNeeded;
|
this.getDivingGearIfNeeded = getDivingGearIfNeeded;
|
||||||
CalculateCloseEnough();
|
CalculateCloseEnough();
|
||||||
}
|
}
|
||||||
@@ -66,36 +110,27 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (FollowControlledCharacter)
|
if (FollowControlledCharacter)
|
||||||
{
|
{
|
||||||
if (Character.Controlled == null)
|
if (Character.Controlled == null) { return; }
|
||||||
{
|
|
||||||
abandon = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Target = Character.Controlled;
|
Target = Character.Controlled;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Target == character)
|
if (Target == character)
|
||||||
{
|
{
|
||||||
character.AIController.SteeringManager.Reset();
|
character.AIController.SteeringManager.Reset();
|
||||||
abandon = true;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
waitUntilPathUnreachable -= deltaTime;
|
waitUntilPathUnreachable -= deltaTime;
|
||||||
|
|
||||||
if (!character.IsClimbing)
|
if (!character.IsClimbing)
|
||||||
{
|
{
|
||||||
character.SelectedConstruction = null;
|
character.SelectedConstruction = null;
|
||||||
}
|
}
|
||||||
if (Target != null)
|
|
||||||
{
|
if (Target != null) { character.AIController.SelectTarget(Target.AiTarget); }
|
||||||
if (Target.Removed)
|
|
||||||
{
|
|
||||||
abandon = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
character.AIController.SelectTarget(Target.AiTarget);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vector2 currTargetPos = Vector2.Zero;
|
Vector2 currTargetPos = Vector2.Zero;
|
||||||
|
|
||||||
if (Target == null)
|
if (Target == null)
|
||||||
{
|
{
|
||||||
currTargetPos = targetPos;
|
currTargetPos = targetPos;
|
||||||
@@ -110,12 +145,8 @@ namespace Barotrauma
|
|||||||
currTargetPos -= character.Submarine.SimPosition;
|
currTargetPos -= character.Submarine.SimPosition;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bool sightCheck = true;
|
|
||||||
if (CheckVisibility && Target != null)
|
if (Vector2.DistanceSquared(currTargetPos, character.SimPosition) < CloseEnough * CloseEnough)
|
||||||
{
|
|
||||||
sightCheck = Target is Character ch ? character.CanSeeCharacter(ch) : character.CanSeeTarget(Target);
|
|
||||||
}
|
|
||||||
if (sightCheck && Vector2.DistanceSquared(currTargetPos, character.SimPosition) < CloseEnough * CloseEnough)
|
|
||||||
{
|
{
|
||||||
character.AIController.SteeringManager.Reset();
|
character.AIController.SteeringManager.Reset();
|
||||||
character.AnimController.TargetDir = currTargetPos.X > character.SimPosition.X ? Direction.Right : Direction.Left;
|
character.AnimController.TargetDir = currTargetPos.X > character.SimPosition.X ? Direction.Right : Direction.Left;
|
||||||
@@ -123,50 +154,31 @@ namespace Barotrauma
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
bool isInside = character.CurrentHull != null;
|
bool isInside = character.CurrentHull != null;
|
||||||
bool insideSteering = SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.IsPathDirty;
|
bool insideSteering = SteeringManager == PathSteering && PathSteering.CurrentPath != null;
|
||||||
bool targetIsOutside = (Target != null && Target.Submarine == null) || (insideSteering && PathSteering.CurrentPath.HasOutdoorsNodes);
|
bool targetIsOutside = (Target != null && Target.Submarine == null) || (insideSteering && PathSteering.CurrentPath.HasOutdoorsNodes);
|
||||||
if (isInside && targetIsOutside && !AllowGoingOutside)
|
if (isInside && targetIsOutside && !AllowGoingOutside)
|
||||||
{
|
{
|
||||||
abandon = true;
|
cannotReach = true;
|
||||||
}
|
|
||||||
else if (!repeat && waitUntilPathUnreachable < 0)
|
|
||||||
{
|
|
||||||
if (SteeringManager == PathSteering && PathSteering.CurrentPath != null)
|
|
||||||
{
|
|
||||||
abandon = PathSteering.CurrentPath.Unreachable;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (abandon)
|
|
||||||
{
|
|
||||||
#if DEBUG
|
|
||||||
DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {(Target != null ? Target.ToString() : TargetPos.ToString())}", Color.Yellow);
|
|
||||||
#endif
|
|
||||||
if (objectiveManager.CurrentOrder != null)
|
|
||||||
{
|
|
||||||
character.Speak(TextManager.Get("DialogCannotReach"), identifier: "cannotreach", minDurationBetweenSimilar: 10.0f);
|
|
||||||
}
|
|
||||||
character.AIController.SteeringManager.Reset();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
character.AIController.SteeringManager.SteeringSeek(currTargetPos);
|
character.AIController.SteeringManager.SteeringSeek(currTargetPos);
|
||||||
if (getDivingGearIfNeeded)
|
if (getDivingGearIfNeeded)
|
||||||
{
|
{
|
||||||
var targetHull = Target is Hull h ? h : Target is Item i ? i.CurrentHull : Target is Character c ? c.CurrentHull : character.CurrentHull;
|
if (targetIsOutside ||
|
||||||
bool needsDivingGear = HumanAIController.NeedsDivingGear(targetHull);
|
Target is Hull h && HumanAIController.NeedsDivingGear(h) ||
|
||||||
bool needsDivingSuit = needsDivingGear && (targetIsOutside || targetHull.WaterPercentage > 90);
|
Target is Item i && HumanAIController.NeedsDivingGear(i.CurrentHull) ||
|
||||||
bool needsEquipment = false;
|
Target is Character c && HumanAIController.NeedsDivingGear(c.CurrentHull))
|
||||||
if (needsDivingSuit)
|
|
||||||
{
|
{
|
||||||
needsEquipment = !HumanAIController.HasDivingSuit(character);
|
if (findDivingGear == null)
|
||||||
}
|
{
|
||||||
else if (needsDivingGear)
|
findDivingGear = new AIObjectiveFindDivingGear(character, true);
|
||||||
{
|
AddSubObjective(findDivingGear);
|
||||||
needsEquipment = !HumanAIController.HasDivingMask(character);
|
}
|
||||||
}
|
else if (!findDivingGear.CanBeCompleted)
|
||||||
if (needsEquipment)
|
{
|
||||||
{
|
abandon = true;
|
||||||
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager));
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -177,30 +189,40 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (repeat) { return false; }
|
if (repeat) { return false; }
|
||||||
bool completed = false;
|
bool completed = false;
|
||||||
|
|
||||||
if (Target is Item item)
|
if (Target is Item item)
|
||||||
{
|
{
|
||||||
if (item.IsInsideTrigger(character.WorldPosition)) { completed = true; }
|
if (item.IsInsideTrigger(character.WorldPosition)) completed = true;
|
||||||
}
|
}
|
||||||
else if (Target is Character targetCharacter)
|
else if (Target is Character targetCharacter)
|
||||||
{
|
{
|
||||||
if (character.CanInteractWith(targetCharacter)) { completed = true; }
|
if (character.CanInteractWith(targetCharacter)) completed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
completed = completed || Vector2.DistanceSquared(Target != null ? Target.SimPosition : targetPos, character.SimPosition) < CloseEnough * CloseEnough;
|
completed = completed || Vector2.DistanceSquared(Target != null ? Target.SimPosition : targetPos, character.SimPosition) < CloseEnough * CloseEnough;
|
||||||
if (completed) { character.AIController.SteeringManager.Reset(); }
|
|
||||||
|
if (completed) character.AIController.SteeringManager.Reset();
|
||||||
|
|
||||||
return completed;
|
return completed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool IsDuplicate(AIObjective otherObjective)
|
public override bool IsDuplicate(AIObjective otherObjective)
|
||||||
{
|
{
|
||||||
if (!(otherObjective is AIObjectiveGoTo objective)) { return false; }
|
AIObjectiveGoTo objective = otherObjective as AIObjectiveGoTo;
|
||||||
if (objective.Target == Target) { return true; }
|
if (objective == null) return false;
|
||||||
return objective.targetPos == targetPos;
|
|
||||||
}
|
if (objective.Target == Target) return true;
|
||||||
|
|
||||||
private void CalculateCloseEnough()
|
private void CalculateCloseEnough()
|
||||||
{
|
{
|
||||||
float interactionDistance = Target is Item i ? ConvertUnits.ToSimUnits(i.InteractDistance * 0.9f) : 0;
|
float interactionDistance = Target is Item i ? ConvertUnits.ToSimUnits(i.InteractDistance * 0.9f) : 0;
|
||||||
CloseEnough = Math.Max(interactionDistance, CloseEnough);
|
CloseEnough = Math.Max(interactionDistance, CloseEnough);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CalculateCloseEnough()
|
||||||
|
{
|
||||||
|
float interactionDistance = Target is Item i ? ConvertUnits.ToSimUnits(i.InteractDistance) : 0;
|
||||||
|
CloseEnough = Math.Max(interactionDistance, CloseEnough);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,18 +38,6 @@ namespace Barotrauma
|
|||||||
public override bool IsCompleted() => false;
|
public override bool IsCompleted() => false;
|
||||||
public override bool CanBeCompleted => true;
|
public override bool CanBeCompleted => true;
|
||||||
|
|
||||||
public override bool IsLoop { get => true; set => throw new System.Exception("Trying to set the value for IsLoop from: " + System.Environment.StackTrace); }
|
|
||||||
|
|
||||||
private float randomTimer;
|
|
||||||
private float randomUpdateInterval = 5;
|
|
||||||
public float Random { get; private set; }
|
|
||||||
|
|
||||||
public void SetRandom()
|
|
||||||
{
|
|
||||||
Random = Rand.Range(0.5f, 1.5f);
|
|
||||||
randomTimer = randomUpdateInterval;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override float GetPriority()
|
public override float GetPriority()
|
||||||
{
|
{
|
||||||
float max = Math.Min(Math.Min(AIObjectiveManager.RunPriority, AIObjectiveManager.OrderPriority) - 1, 100);
|
float max = Math.Min(Math.Min(AIObjectiveManager.RunPriority, AIObjectiveManager.OrderPriority) - 1, 100);
|
||||||
@@ -83,7 +71,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
protected override void Act(float deltaTime)
|
protected override void Act(float deltaTime)
|
||||||
{
|
{
|
||||||
if (PathSteering == null) { return; }
|
if (PathSteering == null) return;
|
||||||
|
|
||||||
//don't keep dragging others when idling
|
//don't keep dragging others when idling
|
||||||
if (character.SelectedCharacter != null)
|
if (character.SelectedCharacter != null)
|
||||||
@@ -95,10 +83,24 @@ namespace Barotrauma
|
|||||||
character.SelectedConstruction = null;
|
character.SelectedConstruction = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool currentHullForbidden = IsForbidden(character.CurrentHull);
|
||||||
|
if (!currentHullForbidden && !character.AnimController.InWater && !character.IsClimbing && HumanAIController.ObjectiveManager.WaitTimer > 0)
|
||||||
|
{
|
||||||
|
SteeringManager.Reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!character.IsClimbing)
|
||||||
|
{
|
||||||
|
character.SelectedConstruction = null;
|
||||||
|
}
|
||||||
|
|
||||||
bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) ||
|
bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) ||
|
||||||
(PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull)));
|
(PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull)));
|
||||||
|
|
||||||
if (currentTargetIsInvalid || currentTarget == null && IsForbidden(character.CurrentHull))
|
bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) ||
|
||||||
|
(PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull)));
|
||||||
|
|
||||||
|
if (currentTargetIsInvalid || (currentTarget == null && currentHullForbidden))
|
||||||
{
|
{
|
||||||
newTargetTimer = 0;
|
newTargetTimer = 0;
|
||||||
standStillTimer = 0;
|
standStillTimer = 0;
|
||||||
@@ -135,6 +137,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
//choose a random available hull
|
//choose a random available hull
|
||||||
var randomHull = ToolBox.SelectWeightedRandom(targetHulls, hullWeights, Rand.RandSync.Unsynced);
|
var randomHull = ToolBox.SelectWeightedRandom(targetHulls, hullWeights, Rand.RandSync.Unsynced);
|
||||||
|
|
||||||
bool isCurrentHullOK = !HumanAIController.UnsafeHulls.Contains(character.CurrentHull) && !IsForbidden(character.CurrentHull);
|
bool isCurrentHullOK = !HumanAIController.UnsafeHulls.Contains(character.CurrentHull) && !IsForbidden(character.CurrentHull);
|
||||||
if (isCurrentHullOK)
|
if (isCurrentHullOK)
|
||||||
{
|
{
|
||||||
@@ -142,8 +145,7 @@ namespace Barotrauma
|
|||||||
// Only do this when the current hull is ok, because otherwise would block all paths from the current hull to the target hull.
|
// Only do this when the current hull is ok, because otherwise would block all paths from the current hull to the target hull.
|
||||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, randomHull.SimPosition);
|
var path = PathSteering.PathFinder.FindPath(character.SimPosition, randomHull.SimPosition);
|
||||||
if (path.Unreachable ||
|
if (path.Unreachable ||
|
||||||
path.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull) ||
|
path.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull) || IsForbidden(n.CurrentHull)))
|
||||||
IsForbidden(n.CurrentHull)))
|
|
||||||
{
|
{
|
||||||
//can't go to this room, remove it from the list and try another room next frame
|
//can't go to this room, remove it from the list and try another room next frame
|
||||||
int index = targetHulls.IndexOf(randomHull);
|
int index = targetHulls.IndexOf(randomHull);
|
||||||
@@ -266,6 +268,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private void FindTargetHulls()
|
private void FindTargetHulls()
|
||||||
{
|
{
|
||||||
|
bool isCurrentHullOK = !HumanAIController.UnsafeHulls.Contains(character.CurrentHull) && !IsForbidden(character.CurrentHull);
|
||||||
targetHulls.Clear();
|
targetHulls.Clear();
|
||||||
hullWeights.Clear();
|
hullWeights.Clear();
|
||||||
foreach (var hull in Hull.hullList)
|
foreach (var hull in Hull.hullList)
|
||||||
@@ -297,7 +300,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsForbidden(Hull hull)
|
private bool IsForbidden(Hull hull)
|
||||||
{
|
{
|
||||||
if (hull == null) { return true; }
|
if (hull == null) { return true; }
|
||||||
string hullName = hull.RoomName?.ToLowerInvariant();
|
string hullName = hull.RoomName?.ToLowerInvariant();
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
abstract class AIObjectiveLoop<T> : AIObjective
|
abstract class AIObjectiveLoop<T> : AIObjective
|
||||||
{
|
{
|
||||||
protected HashSet<T> targets = new HashSet<T>();
|
protected List<T> targets = new List<T>();
|
||||||
protected Dictionary<T, AIObjective> objectives = new Dictionary<T, AIObjective>();
|
protected Dictionary<T, AIObjective> objectives = new Dictionary<T, AIObjective>();
|
||||||
protected HashSet<T> ignoreList = new HashSet<T>();
|
protected HashSet<T> ignoreList = new HashSet<T>();
|
||||||
private float ignoreListTimer;
|
private float ignoreListTimer;
|
||||||
@@ -15,25 +15,9 @@ namespace Barotrauma
|
|||||||
|
|
||||||
// By default, doesn't clear the list automatically
|
// By default, doesn't clear the list automatically
|
||||||
protected virtual float IgnoreListClearInterval => 0;
|
protected virtual float IgnoreListClearInterval => 0;
|
||||||
|
protected virtual float TargetUpdateInterval => 2;
|
||||||
|
|
||||||
public HashSet<T> ReportedTargets { get; private set; } = new HashSet<T>();
|
public AIObjectiveLoop(Character character, string option) : base(character, option)
|
||||||
|
|
||||||
public bool AddTarget(T target)
|
|
||||||
{
|
|
||||||
if (ReportedTargets.Contains(target))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (Filter(target))
|
|
||||||
{
|
|
||||||
ReportedTargets.Add(target);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AIObjectiveLoop(Character character, AIObjectiveManager objectiveManager, float priorityModifier, string option = null)
|
|
||||||
: base(character, objectiveManager, priorityModifier, option)
|
|
||||||
{
|
{
|
||||||
Reset();
|
Reset();
|
||||||
}
|
}
|
||||||
@@ -42,11 +26,9 @@ namespace Barotrauma
|
|||||||
public override bool IsCompleted() => false;
|
public override bool IsCompleted() => false;
|
||||||
public override bool CanBeCompleted => true;
|
public override bool CanBeCompleted => true;
|
||||||
|
|
||||||
public override bool IsLoop { get => true; set => throw new System.Exception("Trying to set the value for IsLoop from: " + System.Environment.StackTrace); }
|
public override void Update(AIObjectiveManager objectiveManager, float deltaTime)
|
||||||
|
|
||||||
public override void Update(float deltaTime)
|
|
||||||
{
|
{
|
||||||
base.Update(deltaTime);
|
base.Update(objectiveManager, deltaTime);
|
||||||
if (IgnoreListClearInterval > 0)
|
if (IgnoreListClearInterval > 0)
|
||||||
{
|
{
|
||||||
if (ignoreListTimer > IgnoreListClearInterval)
|
if (ignoreListTimer > IgnoreListClearInterval)
|
||||||
@@ -58,13 +40,14 @@ namespace Barotrauma
|
|||||||
ignoreListTimer += deltaTime;
|
ignoreListTimer += deltaTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (targetUpdateTimer < 0)
|
if (targetUpdateTimer >= TargetUpdateInterval)
|
||||||
{
|
{
|
||||||
|
targetUpdateTimer = 0;
|
||||||
UpdateTargets();
|
UpdateTargets();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
targetUpdateTimer -= deltaTime;
|
targetUpdateTimer += deltaTime;
|
||||||
}
|
}
|
||||||
// Sync objectives, subobjectives and targets
|
// Sync objectives, subobjectives and targets
|
||||||
foreach (var objective in objectives)
|
foreach (var objective in objectives)
|
||||||
@@ -73,7 +56,7 @@ namespace Barotrauma
|
|||||||
if (!objective.Value.CanBeCompleted)
|
if (!objective.Value.CanBeCompleted)
|
||||||
{
|
{
|
||||||
ignoreList.Add(target);
|
ignoreList.Add(target);
|
||||||
targetUpdateTimer = 0;
|
targetUpdateTimer = TargetUpdateInterval;
|
||||||
}
|
}
|
||||||
if (!targets.Contains(target))
|
if (!targets.Contains(target))
|
||||||
{
|
{
|
||||||
@@ -87,9 +70,6 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// the timer is set between 1 and 10 seconds, depending on the priority
|
|
||||||
private float SetTargetUpdateTimer() => targetUpdateTimer = 1 / MathHelper.Clamp(PriorityModifier * Rand.Range(0.75f, 1.25f), 0.1f, 1);
|
|
||||||
|
|
||||||
public override void Reset()
|
public override void Reset()
|
||||||
{
|
{
|
||||||
ignoreList.Clear();
|
ignoreList.Clear();
|
||||||
@@ -99,34 +79,19 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public override void OnSelected()
|
public override void OnSelected()
|
||||||
{
|
{
|
||||||
base.OnSelected();
|
Reset();
|
||||||
if (HumanAIController.ObjectiveManager.CurrentOrder == this)
|
|
||||||
{
|
|
||||||
Reset();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override float GetPriority()
|
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||||
{
|
{
|
||||||
if (character.Submarine == null) { return 0; }
|
if (character.Submarine == null) { return 0; }
|
||||||
if (targets.None()) { return 0; }
|
if (targets.None()) { return 0; }
|
||||||
// Allow the target value to be more than 100.
|
float avg = targets.Average(t => Average(t));
|
||||||
float targetValue = TargetEvaluation();
|
return MathHelper.Lerp(0, AIObjectiveManager.OrderPriority + 20, avg / 100);
|
||||||
// If the target value is less than 1% of the max value, let's just treat it as zero.
|
|
||||||
if (targetValue < 1) { return 0; }
|
|
||||||
if (objectiveManager.CurrentOrder == this)
|
|
||||||
{
|
|
||||||
return AIObjectiveManager.OrderPriority;
|
|
||||||
}
|
|
||||||
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90);
|
|
||||||
float devotion = MathHelper.Min(10, Priority);
|
|
||||||
float value = MathHelper.Clamp((devotion + targetValue * PriorityModifier) / 100, 0, 1);
|
|
||||||
return MathHelper.Lerp(0, max, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void UpdateTargets()
|
protected void UpdateTargets()
|
||||||
{
|
{
|
||||||
SetTargetUpdateTimer();
|
|
||||||
targets.Clear();
|
targets.Clear();
|
||||||
FindTargets();
|
FindTargets();
|
||||||
CreateObjectives();
|
CreateObjectives();
|
||||||
@@ -134,19 +99,12 @@ namespace Barotrauma
|
|||||||
|
|
||||||
protected virtual void FindTargets()
|
protected virtual void FindTargets()
|
||||||
{
|
{
|
||||||
foreach (T target in GetList())
|
foreach (T item in GetList())
|
||||||
{
|
{
|
||||||
// The bots always find targets when the objective is an order.
|
if (Filter(item)) { continue; }
|
||||||
if (objectiveManager.CurrentOrder != this)
|
if (!targets.Contains(item))
|
||||||
{
|
{
|
||||||
// Battery or pump states cannot currently be reported (not implemented) and therefore we must ignore them -> the bots always know if they require attention.
|
targets.Add(item);
|
||||||
bool ignore = this is AIObjectiveChargeBatteries || this is AIObjectivePumpWater;
|
|
||||||
if (!ignore && !ReportedTargets.Contains(target)) { continue; }
|
|
||||||
}
|
|
||||||
if (!Filter(target)) { continue; }
|
|
||||||
if (!ignoreList.Contains(target))
|
|
||||||
{
|
|
||||||
targets.Add(target);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -158,7 +116,6 @@ namespace Barotrauma
|
|||||||
if (!objectives.TryGetValue(target, out AIObjective objective))
|
if (!objectives.TryGetValue(target, out AIObjective objective))
|
||||||
{
|
{
|
||||||
objective = ObjectiveConstructor(target);
|
objective = ObjectiveConstructor(target);
|
||||||
objective.Completed += () => ReportedTargets.Remove(target);
|
|
||||||
objectives.Add(target, objective);
|
objectives.Add(target, objective);
|
||||||
AddSubObjective(objective);
|
AddSubObjective(objective);
|
||||||
}
|
}
|
||||||
@@ -169,9 +126,7 @@ namespace Barotrauma
|
|||||||
/// List of all possible items of the specified type. Used for filtering the removed objectives.
|
/// List of all possible items of the specified type. Used for filtering the removed objectives.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected abstract IEnumerable<T> GetList();
|
protected abstract IEnumerable<T> GetList();
|
||||||
|
protected abstract float Average(T target);
|
||||||
protected abstract float TargetEvaluation();
|
|
||||||
|
|
||||||
protected abstract AIObjective ObjectiveConstructor(T target);
|
protected abstract AIObjective ObjectiveConstructor(T target);
|
||||||
protected abstract bool Filter(T target);
|
protected abstract bool Filter(T target);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,29 +9,27 @@ namespace Barotrauma
|
|||||||
class AIObjectiveManager
|
class AIObjectiveManager
|
||||||
{
|
{
|
||||||
// TODO: expose
|
// TODO: expose
|
||||||
public const float OrderPriority = 70;
|
public const float OrderPriority = 50.0f;
|
||||||
public const float RunPriority = 50;
|
|
||||||
// Constantly increases the priority of the selected objective, unless overridden
|
// Constantly increases the priority of the selected objective, unless overridden
|
||||||
public const float baseDevotion = 2;
|
public const float baseDevotion = 2;
|
||||||
|
|
||||||
public List<AIObjective> Objectives { get; private set; } = new List<AIObjective>();
|
public List<AIObjective> Objectives { get; private set; }
|
||||||
|
|
||||||
private Character character;
|
private Character character;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When set above zero, the character will stand still doing nothing until the timer runs out. Does not affect orders.
|
/// When set above zero, the character will stand still doing nothing until the timer runs out. Only affects idling.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public float WaitTimer;
|
public float WaitTimer;
|
||||||
|
|
||||||
public AIObjective CurrentOrder { get; private set; }
|
public AIObjective CurrentOrder { get; private set; }
|
||||||
public AIObjective CurrentObjective { get; private set; }
|
public AIObjective CurrentObjective { get; private set; }
|
||||||
|
|
||||||
public bool IsCurrentObjective<T>() where T : AIObjective => CurrentObjective is T;
|
|
||||||
|
|
||||||
public AIObjectiveManager(Character character)
|
public AIObjectiveManager(Character character)
|
||||||
{
|
{
|
||||||
this.character = character;
|
this.character = character;
|
||||||
CreateAutonomousObjectives();
|
|
||||||
|
Objectives = new List<AIObjective>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddObjective(AIObjective objective)
|
public void AddObjective(AIObjective objective)
|
||||||
@@ -48,30 +46,6 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Dictionary<AIObjective, CoroutineHandle> DelayedObjectives { get; private set; } = new Dictionary<AIObjective, CoroutineHandle>();
|
public Dictionary<AIObjective, CoroutineHandle> DelayedObjectives { get; private set; } = new Dictionary<AIObjective, CoroutineHandle>();
|
||||||
|
|
||||||
public void CreateAutonomousObjectives()
|
|
||||||
{
|
|
||||||
Objectives.Clear();
|
|
||||||
AddObjective(new AIObjectiveFindSafety(character, this), delay: Rand.Value() / 2);
|
|
||||||
AddObjective(new AIObjectiveIdle(character, this), delay: Rand.Value() / 2);
|
|
||||||
int objectiveCount = Objectives.Count;
|
|
||||||
foreach (var automaticOrder in character.Info.Job.Prefab.AutomaticOrders)
|
|
||||||
{
|
|
||||||
var orderPrefab = Order.PrefabList.Find(o => o.AITag == automaticOrder.aiTag);
|
|
||||||
if (orderPrefab == null) { throw new Exception("Could not find a matching prefab by ai tag: " + automaticOrder.aiTag); }
|
|
||||||
// TODO: Similar code is used in CrewManager:815-> DRY
|
|
||||||
var matchingItems = orderPrefab.ItemIdentifiers.Any() ?
|
|
||||||
Item.ItemList.FindAll(it => orderPrefab.ItemIdentifiers.Contains(it.Prefab.Identifier) || it.HasTag(orderPrefab.ItemIdentifiers)) :
|
|
||||||
Item.ItemList.FindAll(it => it.Components.Any(ic => ic.GetType() == orderPrefab.ItemComponentType));
|
|
||||||
matchingItems.RemoveAll(it => it.Submarine != character.Submarine);
|
|
||||||
var item = matchingItems.GetRandom();
|
|
||||||
var order = new Order(orderPrefab, item ?? character.CurrentHull as Entity, item?.Components.FirstOrDefault(ic => ic.GetType() == orderPrefab.ItemComponentType));
|
|
||||||
AddObjective(CreateObjective(order, automaticOrder.option, character, automaticOrder.priorityModifier), delay: Rand.Value() / 2);
|
|
||||||
objectiveCount++;
|
|
||||||
}
|
|
||||||
WaitTimer = Math.Max(WaitTimer, Rand.Range(0.5f, 1f) * objectiveCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddObjective(AIObjective objective, float delay, Action callback = null)
|
public void AddObjective(AIObjective objective, float delay, Action callback = null)
|
||||||
{
|
{
|
||||||
if (DelayedObjectives.TryGetValue(objective, out CoroutineHandle coroutine))
|
if (DelayedObjectives.TryGetValue(objective, out CoroutineHandle coroutine))
|
||||||
@@ -101,7 +75,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
var previousObjective = CurrentObjective;
|
var previousObjective = CurrentObjective;
|
||||||
var firstObjective = Objectives.FirstOrDefault();
|
var firstObjective = Objectives.FirstOrDefault();
|
||||||
if (CurrentOrder != null && firstObjective != null && CurrentOrder.GetPriority() > firstObjective.GetPriority())
|
if (CurrentOrder != null && firstObjective != null && CurrentOrder.GetPriority(this) > firstObjective.GetPriority(this))
|
||||||
{
|
{
|
||||||
CurrentObjective = CurrentOrder;
|
CurrentObjective = CurrentOrder;
|
||||||
}
|
}
|
||||||
@@ -112,24 +86,18 @@ namespace Barotrauma
|
|||||||
if (previousObjective != CurrentObjective)
|
if (previousObjective != CurrentObjective)
|
||||||
{
|
{
|
||||||
CurrentObjective?.OnSelected();
|
CurrentObjective?.OnSelected();
|
||||||
GetObjective<AIObjectiveIdle>()?.SetRandom();
|
|
||||||
}
|
}
|
||||||
return CurrentObjective;
|
return CurrentObjective;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float GetCurrentPriority()
|
public float GetCurrentPriority()
|
||||||
{
|
{
|
||||||
return CurrentObjective == null ? 0.0f : CurrentObjective.GetPriority();
|
return CurrentObjective == null ? 0.0f : CurrentObjective.GetPriority(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateObjectives(float deltaTime)
|
public void UpdateObjectives(float deltaTime)
|
||||||
{
|
{
|
||||||
CurrentOrder?.Update(deltaTime);
|
CurrentOrder?.Update(this, deltaTime);
|
||||||
if (WaitTimer > 0)
|
|
||||||
{
|
|
||||||
WaitTimer -= deltaTime;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < Objectives.Count; i++)
|
for (int i = 0; i < Objectives.Count; i++)
|
||||||
{
|
{
|
||||||
var objective = Objectives[i];
|
var objective = Objectives[i];
|
||||||
@@ -147,9 +115,9 @@ namespace Barotrauma
|
|||||||
#endif
|
#endif
|
||||||
Objectives.Remove(objective);
|
Objectives.Remove(objective);
|
||||||
}
|
}
|
||||||
else if (objective != CurrentOrder)
|
else
|
||||||
{
|
{
|
||||||
objective.Update(deltaTime);
|
objective.Update(this, deltaTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GetCurrentObjective();
|
GetCurrentObjective();
|
||||||
@@ -159,41 +127,15 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (Objectives.Any())
|
if (Objectives.Any())
|
||||||
{
|
{
|
||||||
Objectives.Sort((x, y) => y.GetPriority().CompareTo(x.GetPriority()));
|
Objectives.Sort((x, y) => y.GetPriority(this).CompareTo(x.GetPriority(this)));
|
||||||
}
|
}
|
||||||
CurrentObjective?.SortSubObjectives();
|
CurrentObjective?.SortSubObjectives(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DoCurrentObjective(float deltaTime)
|
public void DoCurrentObjective(float deltaTime)
|
||||||
{
|
{
|
||||||
if (WaitTimer <= 0)
|
if (WaitTimer > 0.0f) { WaitTimer -= deltaTime; }
|
||||||
{
|
CurrentObjective?.TryComplete(deltaTime);
|
||||||
CurrentObjective?.TryComplete(deltaTime);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (CurrentOrder != null)
|
|
||||||
{
|
|
||||||
CurrentOrder.TryComplete(deltaTime);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (character.AIController is HumanAIController humanAI && humanAI.SteeringManager != null)
|
|
||||||
{
|
|
||||||
if (!character.AnimController.InWater &&
|
|
||||||
!character.IsClimbing &&
|
|
||||||
!humanAI.UnsafeHulls.Contains(character.CurrentHull) &&
|
|
||||||
!AIObjectiveIdle.IsForbidden(character.CurrentHull))
|
|
||||||
{
|
|
||||||
humanAI.SteeringManager.Reset();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
CurrentObjective?.TryComplete(deltaTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetOrder(AIObjective objective)
|
public void SetOrder(AIObjective objective)
|
||||||
@@ -203,22 +145,13 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public void SetOrder(Order order, string option, Character orderGiver)
|
public void SetOrder(Order order, string option, Character orderGiver)
|
||||||
{
|
{
|
||||||
CurrentOrder = CreateObjective(order, option, orderGiver);
|
CurrentOrder = null;
|
||||||
if (CurrentOrder == null)
|
if (order == null) return;
|
||||||
{
|
|
||||||
// Recreate objectives, because some of them may be removed, if impossible to complete (e.g. due to path finding)
|
|
||||||
CreateAutonomousObjectives();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public AIObjective CreateObjective(Order order, string option, Character orderGiver, float priorityModifier = 1)
|
|
||||||
{
|
|
||||||
if (order == null) { return null; }
|
|
||||||
AIObjective newObjective;
|
|
||||||
switch (order.AITag.ToLowerInvariant())
|
switch (order.AITag.ToLowerInvariant())
|
||||||
{
|
{
|
||||||
case "follow":
|
case "follow":
|
||||||
newObjective = new AIObjectiveGoTo(orderGiver, character, this, repeat: true, priorityModifier: priorityModifier)
|
CurrentOrder = new AIObjectiveGoTo(orderGiver, character, true)
|
||||||
{
|
{
|
||||||
CloseEnough = 1.5f,
|
CloseEnough = 1.5f,
|
||||||
AllowGoingOutside = true,
|
AllowGoingOutside = true,
|
||||||
@@ -227,41 +160,38 @@ namespace Barotrauma
|
|||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case "wait":
|
case "wait":
|
||||||
newObjective = new AIObjectiveGoTo(character, character, this, repeat: true, priorityModifier: priorityModifier)
|
CurrentOrder = new AIObjectiveGoTo(character, character, true)
|
||||||
{
|
{
|
||||||
AllowGoingOutside = true
|
AllowGoingOutside = true
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case "fixleaks":
|
case "fixleaks":
|
||||||
newObjective = new AIObjectiveFixLeaks(character, this, priorityModifier);
|
CurrentOrder = new AIObjectiveFixLeaks(character);
|
||||||
break;
|
break;
|
||||||
case "chargebatteries":
|
case "chargebatteries":
|
||||||
newObjective = new AIObjectiveChargeBatteries(character, this, option, priorityModifier);
|
CurrentOrder = new AIObjectiveChargeBatteries(character, option);
|
||||||
break;
|
break;
|
||||||
case "rescue":
|
case "rescue":
|
||||||
newObjective = new AIObjectiveRescueAll(character, this, priorityModifier);
|
CurrentOrder = new AIObjectiveRescueAll(character);
|
||||||
break;
|
break;
|
||||||
case "repairsystems":
|
case "repairsystems":
|
||||||
newObjective = new AIObjectiveRepairItems(character, this, priorityModifier) { RequireAdequateSkills = option != "all" };
|
CurrentOrder = new AIObjectiveRepairItems(character) { RequireAdequateSkills = option != "all" };
|
||||||
break;
|
break;
|
||||||
case "pumpwater":
|
case "pumpwater":
|
||||||
newObjective = new AIObjectivePumpWater(character, this, option, priorityModifier: priorityModifier);
|
CurrentOrder = new AIObjectivePumpWater(character, option);
|
||||||
break;
|
break;
|
||||||
case "extinguishfires":
|
case "extinguishfires":
|
||||||
newObjective = new AIObjectiveExtinguishFires(character, this, priorityModifier);
|
CurrentOrder = new AIObjectiveExtinguishFires(character);
|
||||||
break;
|
|
||||||
case "fightintruders":
|
|
||||||
newObjective = new AIObjectiveFightIntruders(character, this, priorityModifier);
|
|
||||||
break;
|
break;
|
||||||
case "steer":
|
case "steer":
|
||||||
var steering = (order?.TargetEntity as Item)?.GetComponent<Steering>();
|
var steering = (order?.TargetEntity as Item)?.GetComponent<Steering>();
|
||||||
if (steering != null) steering.PosToMaintain = steering.Item.Submarine?.WorldPosition;
|
if (steering != null) steering.PosToMaintain = steering.Item.Submarine?.WorldPosition;
|
||||||
if (order.TargetItemComponent == null) { return null; }
|
if (order.TargetItemComponent == null) return;
|
||||||
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option, requireEquip: false, useController: order.UseController, priorityModifier: priorityModifier) { IsLoop = true };
|
CurrentOrder = new AIObjectiveOperateItem(order.TargetItemComponent, character, option, false, null, order.UseController);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (order.TargetItemComponent == null) { return null; }
|
if (order.TargetItemComponent == null) return;
|
||||||
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option, requireEquip: false, useController: order.UseController, priorityModifier: priorityModifier) { IsLoop = true };
|
CurrentOrder = new AIObjectiveOperateItem(order.TargetItemComponent, character, option, false, null, order.UseController);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return newObjective;
|
return newObjective;
|
||||||
|
|||||||
+55
-64
@@ -10,6 +10,8 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
public override string DebugTag => "operate item";
|
public override string DebugTag => "operate item";
|
||||||
|
|
||||||
|
private ItemComponent component, controller;
|
||||||
|
|
||||||
private ItemComponent component, controller;
|
private ItemComponent component, controller;
|
||||||
private Entity operateTarget;
|
private Entity operateTarget;
|
||||||
private bool isCompleted;
|
private bool isCompleted;
|
||||||
@@ -18,25 +20,35 @@ namespace Barotrauma
|
|||||||
private AIObjectiveGoTo goToObjective;
|
private AIObjectiveGoTo goToObjective;
|
||||||
private AIObjectiveGetItem getItemObjective;
|
private AIObjectiveGetItem getItemObjective;
|
||||||
|
|
||||||
public override bool CanBeCompleted => base.CanBeCompleted && (!useController || controller != null);
|
private bool useController;
|
||||||
|
|
||||||
|
private AIObjectiveGoTo gotoObjective;
|
||||||
|
|
||||||
|
public override bool CanBeCompleted
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (gotoObjective != null && !gotoObjective.CanBeCompleted) return false;
|
||||||
|
|
||||||
|
if (useController && controller == null) return false;
|
||||||
|
|
||||||
|
return canBeCompleted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Entity OperateTarget => operateTarget;
|
public Entity OperateTarget => operateTarget;
|
||||||
public ItemComponent Component => component;
|
public ItemComponent Component => component;
|
||||||
|
|
||||||
public override float GetPriority()
|
public ItemComponent Component => component;
|
||||||
|
|
||||||
|
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||||
{
|
{
|
||||||
if (component.Item.ConditionPercentage <= 0) { return 0; }
|
if (gotoObjective != null && !gotoObjective.CanBeCompleted) { return 0; }
|
||||||
if (objectiveManager.CurrentOrder == this)
|
if (objectiveManager.CurrentOrder == this)
|
||||||
{
|
{
|
||||||
return AIObjectiveManager.OrderPriority;
|
return AIObjectiveManager.OrderPriority;
|
||||||
}
|
}
|
||||||
if (component.Item.CurrentHull == null) { return 0; }
|
return base.GetPriority(objectiveManager);
|
||||||
if (component.Item.CurrentHull.FireSources.Count > 0) { return 0; }
|
|
||||||
if (Character.CharacterList.Any(c => c.CurrentHull == component.Item.CurrentHull && !HumanAIController.IsFriendly(c))) { return 0; }
|
|
||||||
float devotion = MathHelper.Min(10, Priority);
|
|
||||||
float value = devotion + AIObjectiveManager.OrderPriority * PriorityModifier;
|
|
||||||
float max = MathHelper.Min((AIObjectiveManager.OrderPriority - 1), 90);
|
|
||||||
return MathHelper.Clamp(value, 0, max);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public AIObjectiveOperateItem(ItemComponent item, Character character, AIObjectiveManager objectiveManager, string option, bool requireEquip, Entity operateTarget = null, bool useController = false, float priorityModifier = 1)
|
public AIObjectiveOperateItem(ItemComponent item, Character character, AIObjectiveManager objectiveManager, string option, bool requireEquip, Entity operateTarget = null, bool useController = false, float priorityModifier = 1)
|
||||||
@@ -46,83 +58,58 @@ namespace Barotrauma
|
|||||||
this.requireEquip = requireEquip;
|
this.requireEquip = requireEquip;
|
||||||
this.operateTarget = operateTarget;
|
this.operateTarget = operateTarget;
|
||||||
this.useController = useController;
|
this.useController = useController;
|
||||||
|
|
||||||
if (useController)
|
if (useController)
|
||||||
{
|
{
|
||||||
//try finding the controller with the simpler non-recursive method first
|
var controllers = component.Item.GetConnectedComponents<Controller>();
|
||||||
controller =
|
if (controllers.Any()) controller = controllers[0];
|
||||||
component.Item.GetConnectedComponents<Controller>().FirstOrDefault() ??
|
|
||||||
component.Item.GetConnectedComponents<Controller>(recursive: true).FirstOrDefault();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canBeCompleted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Act(float deltaTime)
|
protected override void Act(float deltaTime)
|
||||||
{
|
{
|
||||||
ItemComponent target = useController ? controller : component;
|
ItemComponent target = useController ? controller : component;
|
||||||
|
|
||||||
if (useController && controller == null)
|
if (useController && controller == null)
|
||||||
{
|
{
|
||||||
character.Speak(TextManager.Get("DialogCantFindController").Replace("[item]", component.Item.Name), null, 2.0f, "cantfindcontroller", 30.0f);
|
character.Speak(TextManager.Get("DialogCantFindController").Replace("[item]", component.Item.Name), null, 2.0f, "cantfindcontroller", 30.0f);
|
||||||
abandon = true;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (target.CanBeSelected)
|
if (target.CanBeSelected)
|
||||||
{
|
{
|
||||||
bool inSameRoom = character.CurrentHull == target.Item.CurrentHull;
|
bool inSameRoom = character.CurrentHull == target.Item.CurrentHull;
|
||||||
bool withinReach = target.Item.IsInsideTrigger(character.WorldPosition) || Vector2.DistanceSquared(character.Position, target.Item.Position) < MathUtils.Pow(target.Item.InteractDistance, 2);
|
bool withinReach = target.Item.IsInsideTrigger(character.WorldPosition) || Vector2.DistanceSquared(character.Position, target.Item.Position) < MathUtils.Pow(target.Item.InteractDistance, 2);
|
||||||
if (inSameRoom && withinReach)
|
if (inSameRoom && withinReach)
|
||||||
{
|
{
|
||||||
if (character.SelectedConstruction != target.Item)
|
if (character.CurrentHull == target.Item.CurrentHull)
|
||||||
{
|
{
|
||||||
isCompleted = true;
|
if (character.SelectedConstruction != target.Item && target.CanBeSelected)
|
||||||
}
|
{
|
||||||
if (component.AIOperate(deltaTime, character, this))
|
target.Item.TryInteract(character, false, true);
|
||||||
{
|
}
|
||||||
isCompleted = true;
|
if (component.AIOperate(deltaTime, character, this))
|
||||||
}
|
{
|
||||||
if (component.AIOperate(deltaTime, character, this))
|
isCompleted = true;
|
||||||
{
|
}
|
||||||
isCompleted = true;
|
return;
|
||||||
}
|
|
||||||
if (component.AIOperate(deltaTime, character, this))
|
|
||||||
{
|
|
||||||
isCompleted = true;
|
|
||||||
}
|
|
||||||
if (component.AIOperate(deltaTime, character, this))
|
|
||||||
{
|
|
||||||
isCompleted = true;
|
|
||||||
}
|
|
||||||
if (component.AIOperate(deltaTime, character, this))
|
|
||||||
{
|
|
||||||
isCompleted = true;
|
|
||||||
}
|
|
||||||
if (component.AIOperate(deltaTime, character, this))
|
|
||||||
{
|
|
||||||
isCompleted = true;
|
|
||||||
}
|
|
||||||
if (component.AIOperate(deltaTime, character, this))
|
|
||||||
{
|
|
||||||
isCompleted = true;
|
|
||||||
}
|
|
||||||
if (component.AIOperate(deltaTime, character, this))
|
|
||||||
{
|
|
||||||
isCompleted = true;
|
|
||||||
}
|
|
||||||
if (component.AIOperate(deltaTime, character, this))
|
|
||||||
{
|
|
||||||
isCompleted = true;
|
|
||||||
}
|
|
||||||
if (component.AIOperate(deltaTime, character, this))
|
|
||||||
{
|
|
||||||
isCompleted = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
AddSubObjective(gotoObjective = new AIObjectiveGoTo(target.Item, character));
|
||||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(target.Item, character, objectiveManager));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (component.Item.GetComponent<Pickable>() == null)
|
if (component.Item.GetComponent<Pickable>() == null)
|
||||||
|
{
|
||||||
|
//controller/target can't be selected and the item cannot be picked -> objective can't be completed
|
||||||
|
canBeCompleted = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (!character.Inventory.Items.Contains(component.Item))
|
||||||
{
|
{
|
||||||
//controller/target can't be selected and the item cannot be picked -> objective can't be completed
|
//controller/target can't be selected and the item cannot be picked -> objective can't be completed
|
||||||
abandon = true;
|
abandon = true;
|
||||||
@@ -145,9 +132,11 @@ namespace Barotrauma
|
|||||||
#endif
|
#endif
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < character.Inventory.Capacity; i++)
|
for (int i = 0; i < character.Inventory.Capacity; i++)
|
||||||
{
|
{
|
||||||
if (character.Inventory.SlotTypes[i] == InvSlotType.Any || !holdable.AllowedSlots.Any(s => s.HasFlag(character.Inventory.SlotTypes[i])))
|
if (character.Inventory.SlotTypes[i] == InvSlotType.Any ||
|
||||||
|
!holdable.AllowedSlots.Any(s => s.HasFlag(character.Inventory.SlotTypes[i])))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -181,8 +170,10 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public override bool IsDuplicate(AIObjective otherObjective)
|
public override bool IsDuplicate(AIObjective otherObjective)
|
||||||
{
|
{
|
||||||
if (!(otherObjective is AIObjectiveOperateItem operateItem)) { return false; }
|
AIObjectiveOperateItem operateItem = otherObjective as AIObjectiveOperateItem;
|
||||||
return (operateItem.component == component || otherObjective.Option == Option);
|
if (operateItem == null) return false;
|
||||||
|
|
||||||
|
return (operateItem.component == component ||otherObjective.Option == Option);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+47
-53
@@ -1,74 +1,68 @@
|
|||||||
using Barotrauma.Items.Components;
|
using Barotrauma.Items.Components;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.Xna.Framework;
|
|
||||||
using Barotrauma.Extensions;
|
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
class AIObjectivePumpWater : AIObjectiveLoop<Pump>
|
class AIObjectivePumpWater : AIObjectiveLoop<Pump>
|
||||||
{
|
{
|
||||||
public override string DebugTag => "pump water";
|
public override string DebugTag => "pump water";
|
||||||
private IEnumerable<Pump> pumpList;
|
public override bool KeepDivingGearOn => true;
|
||||||
|
private readonly IEnumerable<Pump> pumpList;
|
||||||
|
|
||||||
public AIObjectivePumpWater(Character character, AIObjectiveManager objectiveManager, string option, float priorityModifier = 1)
|
public AIObjectivePumpWater(Character character, string option) : base(character, option)
|
||||||
: base(character, objectiveManager, priorityModifier, option) { }
|
{
|
||||||
|
pumpList = character.Submarine.GetItems(true).Select(i => i.GetComponent<Pump>()).Where(p => p != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||||
|
{
|
||||||
|
if (character.Submarine == null) { return 0; }
|
||||||
|
if (objectiveManager.CurrentOrder == this && targets.Count > 0)
|
||||||
|
{
|
||||||
|
return AIObjectiveManager.OrderPriority;
|
||||||
|
}
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectivePumpWater && otherObjective.Option == Option;
|
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectivePumpWater && otherObjective.Option == Option;
|
||||||
|
|
||||||
|
//availablePumps = allPumps.Where(p => !p.Item.HasTag("ballast") && p.Item.Connections.None(c => c.IsPower && p.Item.GetConnectedComponentsRecursive<Steering>(c).None())).ToList();
|
||||||
protected override void FindTargets()
|
protected override void FindTargets()
|
||||||
{
|
{
|
||||||
if (Option == null) { return; }
|
if (option == null) { return; }
|
||||||
base.FindTargets();
|
foreach (Item item in Item.ItemList)
|
||||||
if (targets.None() && objectiveManager.CurrentOrder == this)
|
|
||||||
{
|
{
|
||||||
character.Speak(TextManager.Get("DialogNoPumps"), null, 3.0f, "nopumps", 30.0f);
|
if (item.HasTag("ballast")) { continue; }
|
||||||
|
if (item.Submarine == null) { continue; }
|
||||||
|
if (item.Submarine.TeamID != character.TeamID) { continue; }
|
||||||
|
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { continue; }
|
||||||
|
var pump = item.GetComponent<Pump>();
|
||||||
|
if (pump != null)
|
||||||
|
{
|
||||||
|
if (!ignoreList.Contains(pump))
|
||||||
|
{
|
||||||
|
if (option == "stoppumping")
|
||||||
|
{
|
||||||
|
if (!pump.IsActive || pump.FlowPercentage == 0.0f) { continue; }
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!pump.Item.InWater) { continue; }
|
||||||
|
if (pump.IsActive && pump.FlowPercentage <= -90.0f) { continue; }
|
||||||
|
}
|
||||||
|
if (!targets.Contains(pump))
|
||||||
|
{
|
||||||
|
targets.Add(pump);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool Filter(Pump pump)
|
protected override bool Filter(Pump pump) => true;
|
||||||
{
|
protected override IEnumerable<Pump> GetList() => pumpList;
|
||||||
if (pump == null) { return false; }
|
protected override AIObjective ObjectiveConstructor(Pump pump) => new AIObjectiveOperateItem(pump, character, Option, false);
|
||||||
if (pump.Item.HasTag("ballast")) { return false; }
|
protected override float Average(Pump target) => 0;
|
||||||
if (pump.Item.Submarine == null) { return false; }
|
|
||||||
if (pump.Item.CurrentHull == null) { return false; }
|
|
||||||
if (pump.Item.Submarine.TeamID != character.TeamID) { return false; }
|
|
||||||
if (pump.Item.ConditionPercentage <= 0) { return false; }
|
|
||||||
if (pump.Item.CurrentHull.FireSources.Count > 0) { return false; }
|
|
||||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(pump.Item, true)) { return false; }
|
|
||||||
if (Character.CharacterList.Any(c => c.CurrentHull == pump.Item.CurrentHull && !HumanAIController.IsFriendly(c))) { return false; }
|
|
||||||
if (Option == "stoppumping")
|
|
||||||
{
|
|
||||||
if (!pump.IsActive || MathUtils.NearlyEqual(pump.FlowPercentage, 0)) { return false; }
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!pump.Item.InWater) { return false; }
|
|
||||||
if (pump.IsActive && pump.FlowPercentage <= -99.9f) { return false; }
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
protected override IEnumerable<Pump> GetList()
|
|
||||||
{
|
|
||||||
if (pumpList == null)
|
|
||||||
{
|
|
||||||
pumpList = character.Submarine.GetItems(true).Select(i => i.GetComponent<Pump>()).Where(p => p != null);
|
|
||||||
}
|
|
||||||
return pumpList;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override AIObjective ObjectiveConstructor(Pump pump) => new AIObjectiveOperateItem(pump, character, objectiveManager, Option, false) { IsLoop = false };
|
|
||||||
protected override float TargetEvaluation()
|
|
||||||
{
|
|
||||||
if (Option == "stoppumping")
|
|
||||||
{
|
|
||||||
return targets.Max(t => MathHelper.Lerp(0, 100, Math.Abs(t.FlowPercentage / 100)));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return targets.Max(t => MathHelper.Lerp(100, 0, Math.Abs(-t.FlowPercentage / 100)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+37
-26
@@ -11,33 +11,37 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
public override string DebugTag => "repair item";
|
public override string DebugTag => "repair item";
|
||||||
|
|
||||||
|
public override bool KeepDivingGearOn => true;
|
||||||
|
|
||||||
public Item Item { get; private set; }
|
public Item Item { get; private set; }
|
||||||
|
|
||||||
private AIObjectiveGoTo goToObjective;
|
private AIObjectiveGoTo goToObjective;
|
||||||
private float previousCondition = -1;
|
|
||||||
private RepairTool repairTool;
|
|
||||||
|
|
||||||
public AIObjectiveRepairItem(Character character, Item item, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier)
|
private float previousCondition = -1;
|
||||||
|
|
||||||
|
public AIObjectiveRepairItem(Character character, Item item) : base(character, "")
|
||||||
{
|
{
|
||||||
Item = item;
|
Item = item;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override float GetPriority()
|
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||||
{
|
{
|
||||||
// TODO: priority list?
|
// TODO: priority list?
|
||||||
|
if (Item.Repairables.None()) { return 0; }
|
||||||
// Ignore items that are being repaired by someone else.
|
// Ignore items that are being repaired by someone else.
|
||||||
if (Item.Repairables.Any(r => r.CurrentFixer != null && r.CurrentFixer != character)) { return 0; }
|
if (Item.Repairables.Any(r => r.CurrentFixer != null && r.CurrentFixer != character)) { return 0; }
|
||||||
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally)
|
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally)
|
||||||
float dist = Math.Abs(character.WorldPosition.X - Item.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - Item.WorldPosition.Y) * 2.0f;
|
float dist = Math.Abs(character.WorldPosition.X - Item.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - Item.WorldPosition.Y) * 2.0f;
|
||||||
float distanceFactor = MathHelper.Lerp(1, 0.5f, MathUtils.InverseLerp(0, 10000, dist));
|
float distanceFactor = MathHelper.Lerp(1, 0.5f, MathUtils.InverseLerp(0, 10000, dist));
|
||||||
float damagePriority = MathHelper.Lerp(1, 0, Item.Condition / Item.MaxCondition);
|
float damagePriority = MathHelper.Lerp(1, 0, (Item.Condition + 10) / Item.MaxCondition);
|
||||||
float successFactor = MathHelper.Lerp(0, 1, Item.Repairables.Average(r => r.DegreeOfSuccess(character)));
|
float successFactor = MathHelper.Lerp(0, 1, Item.Repairables.Average(r => r.DegreeOfSuccess(character)));
|
||||||
float isSelected = character.SelectedConstruction == Item ? 50 : 0;
|
float isSelected = character.SelectedConstruction == Item ? 50 : 0;
|
||||||
float devotion = (Math.Min(Priority, 10) + isSelected) / 100;
|
float baseLevel = Math.Max(Priority + isSelected, 1);
|
||||||
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90);
|
return MathHelper.Clamp(baseLevel * damagePriority * distanceFactor * successFactor, 0, 100);
|
||||||
return MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + damagePriority * distanceFactor * successFactor * PriorityModifier, 0, 1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool CanBeCompleted => !abandon;
|
||||||
|
|
||||||
public override bool IsCompleted()
|
public override bool IsCompleted()
|
||||||
{
|
{
|
||||||
bool isCompleted = Item.IsFullCondition;
|
bool isCompleted = Item.IsFullCondition;
|
||||||
@@ -55,6 +59,15 @@ namespace Barotrauma
|
|||||||
|
|
||||||
protected override void Act(float deltaTime)
|
protected override void Act(float deltaTime)
|
||||||
{
|
{
|
||||||
|
if (goToObjective != null && !subObjectives.Contains(goToObjective))
|
||||||
|
{
|
||||||
|
if (!goToObjective.IsCompleted() && !goToObjective.CanBeCompleted)
|
||||||
|
{
|
||||||
|
abandon = true;
|
||||||
|
character?.Speak(TextManager.Get("DialogCannotRepair").Replace("[itemname]", Item.Name), null, 0.0f, "cannotrepair", 10.0f);
|
||||||
|
}
|
||||||
|
goToObjective = null;
|
||||||
|
}
|
||||||
foreach (Repairable repairable in Item.Repairables)
|
foreach (Repairable repairable in Item.Repairables)
|
||||||
{
|
{
|
||||||
if (!repairable.HasRequiredItems(character, false))
|
if (!repairable.HasRequiredItems(character, false))
|
||||||
@@ -64,14 +77,12 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
foreach (RelatedItem requiredItem in kvp.Value)
|
foreach (RelatedItem requiredItem in kvp.Value)
|
||||||
{
|
{
|
||||||
AddSubObjective(new AIObjectiveGetItem(character, requiredItem.Identifiers, objectiveManager, true));
|
AddSubObjective(new AIObjectiveGetItem(character, requiredItem.Identifiers, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Only continue when the get item sub objectives have been completed.
|
|
||||||
if (subObjectives.Any(so => so is AIObjectiveGetItem)) { return; }
|
|
||||||
if (repairTool == null)
|
if (repairTool == null)
|
||||||
{
|
{
|
||||||
FindRepairTool();
|
FindRepairTool();
|
||||||
@@ -103,31 +114,31 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
// If the current condition is less than the previous condition, we can't complete the task, so let's abandon it. The item is probably deteriorating at a greater speed than we can repair it.
|
// If the current condition is less than the previous condition, we can't complete the task, so let's abandon it. The item is probably deteriorating at a greater speed than we can repair it.
|
||||||
abandon = true;
|
abandon = true;
|
||||||
character?.Speak(TextManager.Get("DialogCannotRepair").Replace("[itemname]", Item.Name), null, 0.0f, "cannotrepair", 10.0f);
|
character?.Speak(TextManager.Get("DialogRepairFailed").Replace("[itemname]", Item.Name), null, 0.0f, "repairfailed", 10.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
repairable.CurrentFixer = abandon && repairable.CurrentFixer == character ? null : character;
|
repairable.CurrentFixer = abandon && repairable.CurrentFixer == character ? null : character;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else if (goToObjective == null || goToObjective.Target != Item)
|
||||||
{
|
{
|
||||||
// If cannot reach the item, approach it.
|
previousCondition = -1;
|
||||||
TryAddSubObjective(ref goToObjective,
|
if (goToObjective != null)
|
||||||
constructor: () =>
|
{
|
||||||
{
|
subObjectives.Remove(goToObjective);
|
||||||
previousCondition = -1;
|
}
|
||||||
var objective = new AIObjectiveGoTo(Item, character, objectiveManager);
|
goToObjective = new AIObjectiveGoTo(Item, character);
|
||||||
if (repairTool != null)
|
if (repairTool != null)
|
||||||
{
|
{
|
||||||
objective.CloseEnough = ConvertUnits.ToSimUnits(repairTool.Range) * 0.75f;
|
//goToObjective.CloseEnough = (HumanAIController.AnimController.ArmLength + ConvertUnits.ToSimUnits(repairTool.Range)) * 0.75f;
|
||||||
}
|
goToObjective.CloseEnough = ConvertUnits.ToSimUnits(repairTool.Range);
|
||||||
return objective;
|
}
|
||||||
},
|
AddSubObjective(goToObjective);
|
||||||
onAbandon: () => character.Speak(TextManager.Get("DialogCannotRepair").Replace("[itemname]", Item.Name), null, 0.0f, "cannotrepair", 10.0f));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private RepairTool repairTool;
|
||||||
private void FindRepairTool()
|
private void FindRepairTool()
|
||||||
{
|
{
|
||||||
foreach (Repairable repairable in Item.Repairables)
|
foreach (Repairable repairable in Item.Repairables)
|
||||||
|
|||||||
+30
-36
@@ -1,5 +1,4 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using Barotrauma.Items.Components;
|
using Barotrauma.Items.Components;
|
||||||
using Barotrauma.Extensions;
|
using Barotrauma.Extensions;
|
||||||
|
|
||||||
@@ -8,25 +7,18 @@ namespace Barotrauma
|
|||||||
class AIObjectiveRepairItems : AIObjectiveLoop<Item>
|
class AIObjectiveRepairItems : AIObjectiveLoop<Item>
|
||||||
{
|
{
|
||||||
public override string DebugTag => "repair items";
|
public override string DebugTag => "repair items";
|
||||||
|
public override bool KeepDivingGearOn => true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Should the character only attempt to fix items they have the skills to fix, or any damaged item
|
/// Should the character only attempt to fix items they have the skills to fix, or any damaged item
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool RequireAdequateSkills;
|
public bool RequireAdequateSkills;
|
||||||
|
|
||||||
public AIObjectiveRepairItems(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
|
public AIObjectiveRepairItems(Character character) : base(character, "") { }
|
||||||
|
|
||||||
|
// TODO: This can allow two active repair items objectives, if RequireAdequateSkills is not at the same value. We don't want that.
|
||||||
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveRepairItems repairItems && repairItems.RequireAdequateSkills == RequireAdequateSkills;
|
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveRepairItems repairItems && repairItems.RequireAdequateSkills == RequireAdequateSkills;
|
||||||
|
|
||||||
protected override void FindTargets()
|
|
||||||
{
|
|
||||||
base.FindTargets();
|
|
||||||
if (targets.None() && objectiveManager.CurrentOrder == this)
|
|
||||||
{
|
|
||||||
character.Speak(TextManager.Get("DialogNoRepairTargets"), null, 3.0f, "norepairtargets", 30.0f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void CreateObjectives()
|
protected override void CreateObjectives()
|
||||||
{
|
{
|
||||||
foreach (var item in targets)
|
foreach (var item in targets)
|
||||||
@@ -46,35 +38,37 @@ namespace Barotrauma
|
|||||||
|
|
||||||
protected override bool Filter(Item item)
|
protected override bool Filter(Item item)
|
||||||
{
|
{
|
||||||
if (!IsValidTarget(item, character)) { return false; }
|
bool ignore = ignoreList.Contains(item) || item.IsFullCondition;
|
||||||
if (item.CurrentHull.FireSources.Count > 0) { return false; }
|
if (!ignore)
|
||||||
// Don't repair items in rooms that have enemies inside.
|
|
||||||
if (Character.CharacterList.Any(c => c.CurrentHull == item.CurrentHull && !HumanAIController.IsFriendly(c))) { return false; }
|
|
||||||
if (!objectives.ContainsKey(item))
|
|
||||||
{
|
{
|
||||||
if (item.Repairables.All(r => item.Condition > r.ShowRepairUIThreshold)) { return false; }
|
if (item.Submarine == null) { ignore = true; }
|
||||||
|
else if (item.Submarine.TeamID != character.TeamID) { ignore = true; }
|
||||||
|
else if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { ignore = true; }
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (item.Repairables.None()) { ignore = true; }
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (Repairable repairable in item.Repairables)
|
||||||
|
{
|
||||||
|
if (!objectives.ContainsKey(item) && item.Condition > repairable.ShowRepairUIThreshold)
|
||||||
|
{
|
||||||
|
ignore = true;
|
||||||
|
}
|
||||||
|
else if (RequireAdequateSkills && !repairable.HasRequiredSkills(character))
|
||||||
|
{
|
||||||
|
ignore = true;
|
||||||
|
}
|
||||||
|
if (ignore) { break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (RequireAdequateSkills)
|
return ignore;
|
||||||
{
|
|
||||||
if (item.Repairables.Any(r => !r.HasRequiredSkills(character))) { return false; }
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override float TargetEvaluation() => targets.Max(t => 100 - t.ConditionPercentage);
|
protected override float Average(Item item) => 100 - item.ConditionPercentage;
|
||||||
protected override IEnumerable<Item> GetList() => Item.ItemList;
|
protected override IEnumerable<Item> GetList() => Item.ItemList;
|
||||||
protected override AIObjective ObjectiveConstructor(Item item) => new AIObjectiveRepairItem(character, item, objectiveManager, PriorityModifier);
|
protected override AIObjective ObjectiveConstructor(Item item) => new AIObjectiveRepairItem(character, item);
|
||||||
|
|
||||||
public static bool IsValidTarget(Item item, Character character)
|
|
||||||
{
|
|
||||||
if (item == null) { return false; }
|
|
||||||
if (item.IsFullCondition) { return false; }
|
|
||||||
if (item.CurrentHull == null) { return false; }
|
|
||||||
if (item.Submarine == null) { return false; }
|
|
||||||
if (item.Submarine.TeamID != character.TeamID) { return false; }
|
|
||||||
if (item.Repairables.None()) { return false; }
|
|
||||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { return false; }
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
public override string DebugTag => "rescue";
|
public override string DebugTag => "rescue";
|
||||||
public override bool ForceRun => true;
|
public override bool ForceRun => true;
|
||||||
|
public override bool KeepDivingGearOn => true;
|
||||||
|
|
||||||
const float TreatmentDelay = 0.5f;
|
const float TreatmentDelay = 0.5f;
|
||||||
|
|
||||||
@@ -20,9 +21,26 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private float treatmentTimer;
|
private float treatmentTimer;
|
||||||
|
|
||||||
public AIObjectiveRescue(Character character, Character targetCharacter, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
public override bool CanBeCompleted
|
||||||
: base(character, objectiveManager, priorityModifier)
|
|
||||||
{
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (targetCharacter.Removed ||
|
||||||
|
targetCharacter.IsDead ||
|
||||||
|
targetCharacter.Vitality / targetCharacter.MaxVitality > AIObjectiveRescueAll.VitalityThreshold)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (goToObjective != null && !goToObjective.CanBeCompleted) { return false; }
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AIObjectiveRescue(Character character, Character targetCharacter)
|
||||||
|
: base(character, "")
|
||||||
|
{
|
||||||
|
Debug.Assert(character != targetCharacter);
|
||||||
this.targetCharacter = targetCharacter;
|
this.targetCharacter = targetCharacter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,28 +96,68 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (subObjectives.Any()) { return; }
|
if (subObjectives.Any()) { return; }
|
||||||
|
|
||||||
|
protected override void Act(float deltaTime)
|
||||||
|
{
|
||||||
|
//target in water -> move to a dry place first
|
||||||
|
if (targetCharacter.AnimController.InWater)
|
||||||
|
{
|
||||||
|
if (character.SelectedCharacter != targetCharacter)
|
||||||
|
{
|
||||||
|
if (!character.CanInteractWith(targetCharacter))
|
||||||
|
{
|
||||||
|
AddSubObjective(goToObjective = new AIObjectiveGoTo(targetCharacter, character));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
character.SelectCharacter(targetCharacter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddSubObjective(new AIObjectiveFindSafety(character));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//target not in water -> we can start applying treatment
|
||||||
if (!character.CanInteractWith(targetCharacter))
|
if (!character.CanInteractWith(targetCharacter))
|
||||||
{
|
{
|
||||||
// Go to the target and select it
|
AddSubObjective(goToObjective = new AIObjectiveGoTo(targetCharacter, character));
|
||||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(targetCharacter, character, objectiveManager));
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// We can start applying treatment
|
if (character.SelectedCharacter == null)
|
||||||
if (character.SelectedCharacter != targetCharacter)
|
|
||||||
{
|
{
|
||||||
character.Speak(TextManager.Get("DialogFoundWoundedTarget")
|
character?.Speak(TextManager.Get("DialogFoundUnconsciousTarget")
|
||||||
.Replace("[targetname]", targetCharacter.Name).Replace("[roomname]", character.CurrentHull.DisplayName),
|
.Replace("[targetname]", targetCharacter.Name).Replace("[roomname]", character.CurrentHull.DisplayName),
|
||||||
null, 1.0f,
|
null, 1.0f,
|
||||||
"foundwoundedtarget" + targetCharacter.Name, 60.0f);
|
"foundunconscioustarget" + targetCharacter.Name, 60.0f);
|
||||||
|
|
||||||
character.SelectCharacter(targetCharacter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
character.SelectCharacter(targetCharacter);
|
||||||
GiveTreatment(deltaTime);
|
GiveTreatment(deltaTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: consider optimizing a bit
|
protected override bool ShouldInterruptSubObjective(AIObjective subObjective)
|
||||||
|
{
|
||||||
|
if (subObjective is AIObjectiveFindSafety)
|
||||||
|
{
|
||||||
|
if (character.SelectedCharacter != targetCharacter) return true;
|
||||||
|
if (character.AnimController.InWater || targetCharacter.AnimController.InWater) return false;
|
||||||
|
|
||||||
|
foreach (Limb limb in targetCharacter.AnimController.Limbs)
|
||||||
|
{
|
||||||
|
if (!Submarine.RectContains(targetCharacter.CurrentHull.WorldRect, limb.WorldPosition)) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !character.AnimController.InWater && !targetCharacter.AnimController.InWater &&
|
||||||
|
HumanAIController.GetHullSafety(character.CurrentHull, character) > HumanAIController.HULL_SAFETY_THRESHOLD;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private void GiveTreatment(float deltaTime)
|
private void GiveTreatment(float deltaTime)
|
||||||
{
|
{
|
||||||
if (treatmentTimer > 0.0f)
|
if (treatmentTimer > 0.0f)
|
||||||
@@ -116,6 +174,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
return Math.Sign(a2.GetVitalityDecrease(targetCharacter.CharacterHealth) - a1.GetVitalityDecrease(targetCharacter.CharacterHealth));
|
return Math.Sign(a2.GetVitalityDecrease(targetCharacter.CharacterHealth) - a1.GetVitalityDecrease(targetCharacter.CharacterHealth));
|
||||||
});
|
});
|
||||||
|
|
||||||
//check if we already have a suitable treatment for any of the afflictions
|
//check if we already have a suitable treatment for any of the afflictions
|
||||||
foreach (Affliction affliction in allAfflictions)
|
foreach (Affliction affliction in allAfflictions)
|
||||||
{
|
{
|
||||||
@@ -130,6 +189,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//didn't have any suitable treatments available, try to find some medical items
|
//didn't have any suitable treatments available, try to find some medical items
|
||||||
HashSet<string> suitableItemIdentifiers = new HashSet<string>();
|
HashSet<string> suitableItemIdentifiers = new HashSet<string>();
|
||||||
foreach (Affliction affliction in allAfflictions)
|
foreach (Affliction affliction in allAfflictions)
|
||||||
@@ -152,8 +212,9 @@ namespace Barotrauma
|
|||||||
itemNameList.Add(itemPrefab.Name);
|
itemNameList.Add(itemPrefab.Name);
|
||||||
}
|
}
|
||||||
//only list the first 4 items
|
//only list the first 4 items
|
||||||
if (itemNameList.Count >= 4) { break; }
|
if (itemNameList.Count >= 4) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (itemNameList.Count > 0)
|
if (itemNameList.Count > 0)
|
||||||
{
|
{
|
||||||
string itemListStr = "";
|
string itemListStr = "";
|
||||||
@@ -165,34 +226,35 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
itemListStr = string.Join(" or ", string.Join(", ", itemNameList.Take(itemNameList.Count - 1)), itemNameList.Last());
|
itemListStr = string.Join(" or ", string.Join(", ", itemNameList.Take(itemNameList.Count - 1)), itemNameList.Last());
|
||||||
}
|
}
|
||||||
character.Speak(TextManager.Get("DialogListRequiredTreatments")
|
character?.Speak(TextManager.Get("DialogListRequiredTreatments")
|
||||||
.Replace("[targetname]", targetCharacter.Name)
|
.Replace("[targetname]", targetCharacter.Name)
|
||||||
.Replace("[treatmentlist]", itemListStr),
|
.Replace("[treatmentlist]", itemListStr),
|
||||||
null, 2.0f, "listrequiredtreatments" + targetCharacter.Name, 60.0f);
|
null, 2.0f, "listrequiredtreatments" + targetCharacter.Name, 60.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
character.DeselectCharacter();
|
character.DeselectCharacter();
|
||||||
AddSubObjective(new AIObjectiveGetItem(character, suitableItemIdentifiers.ToArray(), objectiveManager, equip: true));
|
AddSubObjective(new AIObjectiveGetItem(character, suitableItemIdentifiers.ToArray(), true));
|
||||||
}
|
}
|
||||||
|
|
||||||
character.AnimController.Anim = AnimController.Animation.CPR;
|
character.AnimController.Anim = AnimController.Animation.CPR;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ApplyTreatment(Affliction affliction, Item item)
|
private void ApplyTreatment(Affliction affliction, Item item)
|
||||||
{
|
{
|
||||||
var targetLimb = targetCharacter.CharacterHealth.GetAfflictionLimb(affliction);
|
var targetLimb = targetCharacter.CharacterHealth.GetAfflictionLimb(affliction);
|
||||||
|
|
||||||
bool remove = false;
|
bool remove = false;
|
||||||
foreach (ItemComponent ic in item.Components)
|
foreach (ItemComponent ic in item.Components)
|
||||||
{
|
{
|
||||||
if (!ic.HasRequiredContainedItems(addMessage: false)) { continue; }
|
if (!ic.HasRequiredContainedItems(addMessage: false)) continue;
|
||||||
#if CLIENT
|
#if CLIENT
|
||||||
ic.PlaySound(ActionType.OnUse, character.WorldPosition, character);
|
ic.PlaySound(ActionType.OnUse, character.WorldPosition, character);
|
||||||
#endif
|
#endif
|
||||||
ic.WasUsed = true;
|
ic.WasUsed = true;
|
||||||
ic.ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb);
|
ic.ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb);
|
||||||
if (ic.DeleteOnUse)
|
if (ic.DeleteOnUse) remove = true;
|
||||||
{
|
|
||||||
remove = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (remove)
|
if (remove)
|
||||||
{
|
{
|
||||||
Entity.Spawner?.AddToRemoveQueue(item);
|
Entity.Spawner?.AddToRemoveQueue(item);
|
||||||
@@ -201,35 +263,28 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public override bool IsCompleted()
|
public override bool IsCompleted()
|
||||||
{
|
{
|
||||||
bool isCompleted = targetCharacter.Vitality / targetCharacter.MaxVitality > AIObjectiveRescueAll.VitalityThreshold;
|
bool isCompleted =
|
||||||
|
targetCharacter.Vitality / targetCharacter.MaxVitality > AIObjectiveRescueAll.VitalityThreshold;
|
||||||
|
|
||||||
if (isCompleted)
|
if (isCompleted)
|
||||||
{
|
{
|
||||||
character.Speak(TextManager.Get("DialogTargetHealed").Replace("[targetname]", targetCharacter.Name),
|
character?.Speak(TextManager.Get("DialogTargetHealed").Replace("[targetname]", targetCharacter.Name),
|
||||||
null, 1.0f, "targethealed" + targetCharacter.Name, 60.0f);
|
null, 1.0f, "targethealed" + targetCharacter.Name, 60.0f);
|
||||||
}
|
}
|
||||||
return isCompleted || targetCharacter.Removed || targetCharacter.IsDead;
|
|
||||||
|
return isCompleted || targetCharacter.IsDead;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override float GetPriority()
|
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||||
{
|
{
|
||||||
if (targetCharacter == null) { return 0; }
|
if (targetCharacter.AnimController.CurrentHull == null || targetCharacter.IsDead) { return 0.0f; }
|
||||||
if (targetCharacter.CurrentHull == null || targetCharacter.Removed || targetCharacter.IsDead)
|
|
||||||
{
|
Vector2 diff = targetCharacter.WorldPosition - character.WorldPosition;
|
||||||
abandon = true;
|
float distance = Math.Abs(diff.X) + Math.Abs(diff.Y);
|
||||||
return 0;
|
|
||||||
}
|
float vitalityFactor = (targetCharacter.MaxVitality - targetCharacter.Vitality) / targetCharacter.MaxVitality;
|
||||||
// Don't go into rooms that have enemies
|
|
||||||
if (Character.CharacterList.Any(c => c.CurrentHull == targetCharacter.CurrentHull && !HumanAIController.IsFriendly(c)))
|
return 1000.0f * vitalityFactor / distance;
|
||||||
{
|
|
||||||
abandon = true;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally)
|
|
||||||
float dist = Math.Abs(character.WorldPosition.X - targetCharacter.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - targetCharacter.WorldPosition.Y) * 2.0f;
|
|
||||||
float distanceFactor = MathHelper.Lerp(1, 0.5f, MathUtils.InverseLerp(0, 10000, dist));
|
|
||||||
float vitalityFactor = AIObjectiveRescueAll.GetVitalityFactor(targetCharacter);
|
|
||||||
float devotion = Math.Min(Priority, 10) / 100;
|
|
||||||
return MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + vitalityFactor * distanceFactor, 0, 1));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+37
-13
@@ -4,18 +4,21 @@ using Barotrauma.Extensions;
|
|||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
class AIObjectiveRescueAll : AIObjectiveLoop<Character>
|
class AIObjectiveRescueAll : AIObjective
|
||||||
{
|
{
|
||||||
public override string DebugTag => "rescue all";
|
public override string DebugTag => "rescue all";
|
||||||
public override bool ForceRun => true;
|
|
||||||
|
|
||||||
//only treat characters whose vitality is below this (0.9 = 90% of max vitality)
|
public override bool KeepDivingGearOn => true;
|
||||||
public const float VitalityThreshold = 0.9f;
|
|
||||||
|
//only treat characters whose vitality is below this (0.8 = 80% of max vitality)
|
||||||
|
public const float VitalityThreshold = 0.8f;
|
||||||
|
|
||||||
|
private List<Character> rescueTargets;
|
||||||
|
|
||||||
public AIObjectiveRescueAll(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
public AIObjectiveRescueAll(Character character) : base (character, "")
|
||||||
: base(character, objectiveManager, priorityModifier) { }
|
{
|
||||||
|
rescueTargets = new List<Character>();
|
||||||
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveRescueAll;
|
}
|
||||||
|
|
||||||
protected override void FindTargets()
|
protected override void FindTargets()
|
||||||
{
|
{
|
||||||
@@ -26,13 +29,31 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool Filter(Character target) => IsValidTarget(target, character);
|
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||||
|
{
|
||||||
|
if (character.Submarine == null) { return 0; }
|
||||||
|
GetRescueTargets();
|
||||||
|
if (!rescueTargets.Any()) { return 0.0f; }
|
||||||
|
|
||||||
|
if (objectiveManager.CurrentOrder == this)
|
||||||
|
{
|
||||||
|
return AIObjectiveManager.OrderPriority;
|
||||||
|
}
|
||||||
|
|
||||||
protected override IEnumerable<Character> GetList() => Character.CharacterList;
|
//if there are targets to rescue, the priority is slightly less
|
||||||
|
//than the priority of explicit orders given to the character
|
||||||
|
return AIObjectiveManager.OrderPriority - 5.0f;
|
||||||
|
}
|
||||||
|
|
||||||
protected override AIObjective ObjectiveConstructor(Character target) => new AIObjectiveRescue(character, target, objectiveManager, PriorityModifier);
|
private void GetRescueTargets()
|
||||||
|
{
|
||||||
protected override float TargetEvaluation() => targets.Max(t => GetVitalityFactor(t)) * 100;
|
rescueTargets = Character.CharacterList.FindAll(c =>
|
||||||
|
c.AIController is HumanAIController &&
|
||||||
|
c.TeamID == character.TeamID &&
|
||||||
|
c != character &&
|
||||||
|
!c.IsDead &&
|
||||||
|
c.Vitality / c.MaxVitality < VitalityThreshold);
|
||||||
|
}
|
||||||
|
|
||||||
public static float GetVitalityFactor(Character character) => (character.MaxVitality - character.Vitality) / character.MaxVitality;
|
public static float GetVitalityFactor(Character character) => (character.MaxVitality - character.Vitality) / character.MaxVitality;
|
||||||
|
|
||||||
@@ -48,5 +69,8 @@ namespace Barotrauma
|
|||||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(target.CurrentHull, true)) { return false; }
|
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(target.CurrentHull, true)) { return false; }
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool IsCompleted() => false;
|
||||||
|
public override bool CanBeCompleted => true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,8 @@ namespace Barotrauma
|
|||||||
private Order(XElement orderElement)
|
private Order(XElement orderElement)
|
||||||
{
|
{
|
||||||
AITag = orderElement.GetAttributeString("aitag", "");
|
AITag = orderElement.GetAttributeString("aitag", "");
|
||||||
Name = TextManager.Get("OrderName." + AITag, true) ?? "Name not found";
|
Name = TextManager.Get("OrderName." + AITag, true) ?? orderElement.GetAttributeString("name", "Name not found");
|
||||||
|
DoingText = TextManager.Get("OrderNameDoing." + AITag, true) ?? orderElement.GetAttributeString("doingtext", "");
|
||||||
|
|
||||||
string targetItemType = orderElement.GetAttributeString("targetitemtype", "");
|
string targetItemType = orderElement.GetAttributeString("targetitemtype", "");
|
||||||
if (!string.IsNullOrWhiteSpace(targetItemType))
|
if (!string.IsNullOrWhiteSpace(targetItemType))
|
||||||
@@ -126,6 +127,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
Name = prefab.Name;
|
Name = prefab.Name;
|
||||||
AITag = prefab.AITag;
|
AITag = prefab.AITag;
|
||||||
|
DoingText = prefab.DoingText;
|
||||||
ItemComponentType = prefab.ItemComponentType;
|
ItemComponentType = prefab.ItemComponentType;
|
||||||
Options = prefab.Options;
|
Options = prefab.Options;
|
||||||
SymbolSprite = prefab.SymbolSprite;
|
SymbolSprite = prefab.SymbolSprite;
|
||||||
@@ -140,10 +142,8 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (UseController)
|
if (UseController)
|
||||||
{
|
{
|
||||||
//try finding the controller with the simpler non-recursive method first
|
var controllers = targetItem.Item.GetConnectedComponents<Controller>();
|
||||||
ConnectedController =
|
if (controllers.Count > 0) ConnectedController = controllers[0];
|
||||||
targetItem.Item.GetConnectedComponents<Controller>().FirstOrDefault() ??
|
|
||||||
targetItem.Item.GetConnectedComponents<Controller>(recursive: true).FirstOrDefault();
|
|
||||||
}
|
}
|
||||||
TargetEntity = targetItem.Item;
|
TargetEntity = targetItem.Item;
|
||||||
TargetItemComponent = targetItem;
|
TargetItemComponent = targetItem;
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ namespace Barotrauma
|
|||||||
//}
|
//}
|
||||||
|
|
||||||
var body = Submarine.PickBody(end, node.Waypoint.SimPosition, null,
|
var body = Submarine.PickBody(end, node.Waypoint.SimPosition, null,
|
||||||
Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs );
|
Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs | Physics.CollisionPlatform);
|
||||||
|
|
||||||
if (body != null)
|
if (body != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,10 +24,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
public static List<JobPrefab> List;
|
public static List<JobPrefab> List;
|
||||||
|
|
||||||
public readonly XElement Items;
|
public List<SkillPrefab> Skills;
|
||||||
public readonly List<string> ItemNames = new List<string>();
|
|
||||||
public readonly List<SkillPrefab> Skills = new List<SkillPrefab>();
|
|
||||||
public readonly List<AutonomousObjective> AutomaticOrders = new List<AutonomousObjective>();
|
|
||||||
|
|
||||||
[Serialize("1,1,1,1", false)]
|
[Serialize("1,1,1,1", false)]
|
||||||
public Color UIColor
|
public Color UIColor
|
||||||
@@ -121,6 +118,14 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public XElement ClothingElement { get; private set; }
|
public XElement ClothingElement { get; private set; }
|
||||||
|
|
||||||
|
public JobPrefab(XElement element)
|
||||||
|
{
|
||||||
|
SerializableProperty.DeserializeProperties(this, element);
|
||||||
|
Name = TextManager.Get("JobName." + Identifier);
|
||||||
|
Description = TextManager.Get("JobDescription." + Identifier);
|
||||||
|
|
||||||
|
public XElement ClothingElement { get; private set; }
|
||||||
|
|
||||||
public JobPrefab(XElement element)
|
public JobPrefab(XElement element)
|
||||||
{
|
{
|
||||||
SerializableProperty.DeserializeProperties(this, element);
|
SerializableProperty.DeserializeProperties(this, element);
|
||||||
|
|||||||
@@ -173,8 +173,8 @@ namespace Barotrauma
|
|||||||
var spawnedCharacter = Character.Create(characterInfo, watchmanSpawnpoint.WorldPosition,
|
var spawnedCharacter = Character.Create(characterInfo, watchmanSpawnpoint.WorldPosition,
|
||||||
Level.Loaded.Seed + (outpost == Level.Loaded.StartOutpost ? "start" : "end"));
|
Level.Loaded.Seed + (outpost == Level.Loaded.StartOutpost ? "start" : "end"));
|
||||||
InitializeWatchman(spawnedCharacter);
|
InitializeWatchman(spawnedCharacter);
|
||||||
var objectiveManager = (spawnedCharacter.AIController as HumanAIController)?.ObjectiveManager;
|
(spawnedCharacter.AIController as HumanAIController)?.ObjectiveManager.SetOrder(
|
||||||
objectiveManager?.SetOrder(new AIObjectiveGoTo(watchmanSpawnpoint, spawnedCharacter, objectiveManager, repeat: true, getDivingGearIfNeeded: false));
|
new AIObjectiveGoTo(watchmanSpawnpoint, spawnedCharacter, repeat: true, getDivingGearIfNeeded: false));
|
||||||
if (watchmanJob != null)
|
if (watchmanJob != null)
|
||||||
{
|
{
|
||||||
spawnedCharacter.GiveJobItems();
|
spawnedCharacter.GiveJobItems();
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ namespace Barotrauma.Items.Components
|
|||||||
private bool autoOrientGap;
|
private bool autoOrientGap;
|
||||||
|
|
||||||
private bool isStuck;
|
private bool isStuck;
|
||||||
public bool IsStuck => isStuck;
|
|
||||||
|
|
||||||
private float resetPredictionTimer;
|
private float resetPredictionTimer;
|
||||||
|
|
||||||
private Rectangle doorRect;
|
private Rectangle doorRect;
|
||||||
@@ -229,7 +227,8 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
msg = msg ?? (HasIntegratedButtons ? accessDeniedTxt : cannotOpenText);
|
msg = msg ?? (HasIntegratedButtons ? accessDeniedTxt : cannotOpenText);
|
||||||
}
|
}
|
||||||
return isBroken || base.HasRequiredItems(character, addMessage, msg);
|
if (isBroken) { return true; }
|
||||||
|
return base.HasRequiredItems(character, addMessage, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool Pick(Character picker)
|
public override bool Pick(Character picker)
|
||||||
|
|||||||
@@ -281,7 +281,7 @@ namespace Barotrauma.Items.Components
|
|||||||
float dist = fromItemToLeak.Length();
|
float dist = fromItemToLeak.Length();
|
||||||
|
|
||||||
//too far away -> consider this done and hope the AI is smart enough to move closer
|
//too far away -> consider this done and hope the AI is smart enough to move closer
|
||||||
if (dist > Range * 3.0f) { return true; }
|
if (dist > Range * 5.0f) return true;
|
||||||
|
|
||||||
// TODO: use the collider size?
|
// TODO: use the collider size?
|
||||||
if (!character.AnimController.InWater && character.AnimController is HumanoidAnimController &&
|
if (!character.AnimController.InWater && character.AnimController is HumanoidAnimController &&
|
||||||
@@ -344,19 +344,7 @@ namespace Barotrauma.Items.Components
|
|||||||
character.CursorPosition = leak.Position + VectorExtensions.Forward(Item.body.TransformedRotation + (float)Math.Sin(sinTime), dist);
|
character.CursorPosition = leak.Position + VectorExtensions.Forward(Item.body.TransformedRotation + (float)Math.Sin(sinTime), dist);
|
||||||
if (item.RequireAimToUse)
|
if (item.RequireAimToUse)
|
||||||
{
|
{
|
||||||
bool isOperatingButtons = false;
|
character.SetInput(InputType.Aim, false, true);
|
||||||
if (character.AIController.SteeringManager is IndoorsSteeringManager indoorSteering)
|
|
||||||
{
|
|
||||||
var door = indoorSteering.CurrentPath?.CurrentNode?.ConnectedDoor;
|
|
||||||
if (door != null && !door.IsOpen)
|
|
||||||
{
|
|
||||||
isOperatingButtons = door.HasIntegratedButtons || door.Item.GetConnectedComponents<Controller>(true).Any();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!isOperatingButtons)
|
|
||||||
{
|
|
||||||
character.SetInput(InputType.Aim, false, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Press the trigger only when the tool is approximately facing the target.
|
// Press the trigger only when the tool is approximately facing the target.
|
||||||
var angle = VectorExtensions.Angle(VectorExtensions.Forward(item.body.TransformedRotation), fromItemToLeak);
|
var angle = VectorExtensions.Angle(VectorExtensions.Forward(item.body.TransformedRotation), fromItemToLeak);
|
||||||
@@ -412,8 +400,7 @@ namespace Barotrauma.Items.Components
|
|||||||
object value = property.GetValue(target);
|
object value = property.GetValue(target);
|
||||||
if (value.GetType() == typeof(float))
|
if (value.GetType() == typeof(float))
|
||||||
{
|
{
|
||||||
var progressBar = user.UpdateHUDProgressBar(door, door.Item.WorldPosition, (float)value / 100, Color.DarkGray * 0.5f, Color.White);
|
user.UpdateHUDProgressBar(door, door.Item.WorldPosition, (float)value / 100, Color.DarkGray * 0.5f, Color.White);
|
||||||
if (progressBar != null) { progressBar.Size = new Vector2(60.0f, 20.0f); }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -943,6 +943,25 @@ namespace Barotrauma.Items.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (targetItem.Prefab.DeconstructItems.Any())
|
||||||
|
{
|
||||||
|
inputContainer.Inventory.RemoveItem(targetItem);
|
||||||
|
Entity.Spawner.AddToRemoveQueue(targetItem);
|
||||||
|
MoveInputQueue();
|
||||||
|
PutItemsToLinkedContainer();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (outputContainer.Inventory.Items.All(i => i != null))
|
||||||
|
{
|
||||||
|
targetItem.Drop(dropper: null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
outputContainer.Inventory.TryPutItem(targetItem, user: null, createNetworkEvent: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (targetItem.Prefab.DeconstructItems.Any())
|
if (targetItem.Prefab.DeconstructItems.Any())
|
||||||
{
|
{
|
||||||
inputContainer.Inventory.RemoveItem(targetItem);
|
inputContainer.Inventory.RemoveItem(targetItem);
|
||||||
|
|||||||
@@ -499,7 +499,7 @@ namespace Barotrauma.Items.Components
|
|||||||
//load more fuel if the current maximum output is only 50% of the current load
|
//load more fuel if the current maximum output is only 50% of the current load
|
||||||
if (NeedMoreFuel(minimumOutputRatio: 0.5f))
|
if (NeedMoreFuel(minimumOutputRatio: 0.5f))
|
||||||
{
|
{
|
||||||
var containFuelObjective = new AIObjectiveContainItem(character, new string[] { "fuelrod", "reactorfuel" }, item.GetComponent<ItemContainer>(), objective.objectiveManager)
|
var containFuelObjective = new AIObjectiveContainItem(character, new string[] { "fuelrod", "reactorfuel" }, item.GetComponent<ItemContainer>())
|
||||||
{
|
{
|
||||||
MinContainedAmount = item.ContainedItems.Count(i => i != null && i.Prefab.Identifier == "fuelrod" || i.HasTag("reactorfuel")) + 1,
|
MinContainedAmount = item.ContainedItems.Count(i => i != null && i.Prefab.Identifier == "fuelrod" || i.HasTag("reactorfuel")) + 1,
|
||||||
GetItemPriority = (Item fuelItem) =>
|
GetItemPriority = (Item fuelItem) =>
|
||||||
|
|||||||
@@ -212,33 +212,6 @@ namespace Barotrauma.Items.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vector2? PosToMaintain
|
|
||||||
{
|
|
||||||
get { return posToMaintain; }
|
|
||||||
set { posToMaintain = value; }
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ObstacleDebugInfo
|
|
||||||
{
|
|
||||||
public Vector2 Point1;
|
|
||||||
public Vector2 Point2;
|
|
||||||
|
|
||||||
public Vector2? Intersection;
|
|
||||||
|
|
||||||
public float Dot;
|
|
||||||
|
|
||||||
public Vector2 AvoidStrength;
|
|
||||||
|
|
||||||
public ObstacleDebugInfo(GraphEdge edge, Vector2? intersection, float dot, Vector2 avoidStrength)
|
|
||||||
{
|
|
||||||
Point1 = edge.Point1;
|
|
||||||
Point2 = edge.Point2;
|
|
||||||
Intersection = intersection;
|
|
||||||
Dot = dot;
|
|
||||||
AvoidStrength = avoidStrength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//edge point 1, edge point 2, avoid strength
|
//edge point 1, edge point 2, avoid strength
|
||||||
private List<ObstacleDebugInfo> debugDrawObstacles = new List<ObstacleDebugInfo>();
|
private List<ObstacleDebugInfo> debugDrawObstacles = new List<ObstacleDebugInfo>();
|
||||||
|
|
||||||
|
|||||||
@@ -235,12 +235,12 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
if (string.IsNullOrEmpty(objective.Option) || objective.Option.ToLowerInvariant() == "charge")
|
if (string.IsNullOrEmpty(objective.Option) || objective.Option.ToLowerInvariant() == "charge")
|
||||||
{
|
{
|
||||||
if (Math.Abs(rechargeSpeed - maxRechargeSpeed * aiRechargeTargetRatio) > 0.05f)
|
if (Math.Abs(rechargeSpeed - maxRechargeSpeed * 0.5f) > 0.05f)
|
||||||
{
|
{
|
||||||
#if SERVER
|
#if SERVER
|
||||||
item.CreateServerEvent(this);
|
item.CreateServerEvent(this);
|
||||||
#endif
|
#endif
|
||||||
RechargeSpeed = maxRechargeSpeed * aiRechargeTargetRatio;
|
RechargeSpeed = maxRechargeSpeed * 0.5f;
|
||||||
#if CLIENT
|
#if CLIENT
|
||||||
rechargeSpeedSlider.BarScroll = RechargeSpeed / Math.Max(maxRechargeSpeed, 1.0f);
|
rechargeSpeedSlider.BarScroll = RechargeSpeed / Math.Max(maxRechargeSpeed, 1.0f);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -415,7 +415,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
if (batteryToLoad.RechargeSpeed < batteryToLoad.MaxRechargeSpeed * 0.4f)
|
if (batteryToLoad.RechargeSpeed < batteryToLoad.MaxRechargeSpeed * 0.4f)
|
||||||
{
|
{
|
||||||
objective.AddSubObjective(new AIObjectiveOperateItem(batteryToLoad, character, objective.objectiveManager, option: "", requireEquip: false));
|
objective.AddSubObjective(new AIObjectiveOperateItem(batteryToLoad, character, "", false));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -454,11 +454,11 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
if (container.Inventory.Items[0] != null && container.Inventory.Items[0].Condition <= 0.0f)
|
if (container.Inventory.Items[0] != null && container.Inventory.Items[0].Condition <= 0.0f)
|
||||||
{
|
{
|
||||||
var removeShellObjective = new AIObjectiveDecontainItem(character, container.Inventory.Items[0], container, objective.objectiveManager);
|
var removeShellObjective = new AIObjectiveDecontainItem(character, container.Inventory.Items[0], container);
|
||||||
objective.AddSubObjective(removeShellObjective);
|
objective.AddSubObjective(removeShellObjective);
|
||||||
}
|
}
|
||||||
|
|
||||||
var containShellObjective = new AIObjectiveContainItem(character, container.ContainableItems[0].Identifiers[0], container, objective.objectiveManager);
|
var containShellObjective = new AIObjectiveContainItem(character, container.ContainableItems[0].Identifiers[0], container);
|
||||||
character?.Speak(TextManager.Get("DialogLoadTurret").Replace("[itemname]", item.Name), null, 0.0f, "loadturret", 30.0f);
|
character?.Speak(TextManager.Get("DialogLoadTurret").Replace("[itemname]", item.Name), null, 0.0f, "loadturret", 30.0f);
|
||||||
containShellObjective.MinContainedAmount = usableProjectileCount + 1;
|
containShellObjective.MinContainedAmount = usableProjectileCount + 1;
|
||||||
containShellObjective.ignoredContainerIdentifiers = new string[] { containerItem.prefab.Identifier };
|
containShellObjective.ignoredContainerIdentifiers = new string[] { containerItem.prefab.Identifier };
|
||||||
|
|||||||
@@ -1712,6 +1712,10 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
ApplyStatusEffects(!waterProof && inWater ? ActionType.InWater : ActionType.NotInWater, deltaTime);
|
ApplyStatusEffects(!waterProof && inWater ? ActionType.InWater : ActionType.NotInWater, deltaTime);
|
||||||
}
|
}
|
||||||
|
if (!broken)
|
||||||
|
{
|
||||||
|
ApplyStatusEffects(!waterProof && inWater ? ActionType.InWater : ActionType.NotInWater, deltaTime);
|
||||||
|
}
|
||||||
ApplyStatusEffects(!waterProof && inWater ? ActionType.InWater : ActionType.NotInWater, deltaTime);
|
ApplyStatusEffects(!waterProof && inWater ? ActionType.InWater : ActionType.NotInWater, deltaTime);
|
||||||
|
|
||||||
if (body == null || !body.Enabled || !inWater || ParentInventory != null || Removed) { return; }
|
if (body == null || !body.Enabled || !inWater || ParentInventory != null || Removed) { return; }
|
||||||
@@ -1848,9 +1852,6 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Note: This function generates garbage and might be a bit too heavy to be used once per frame.
|
|
||||||
/// </summary>
|
|
||||||
public List<T> GetConnectedComponents<T>(bool recursive = false) where T : ItemComponent
|
public List<T> GetConnectedComponents<T>(bool recursive = false) where T : ItemComponent
|
||||||
{
|
{
|
||||||
List<T> connectedComponents = new List<T>();
|
List<T> connectedComponents = new List<T>();
|
||||||
@@ -1859,6 +1860,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
HashSet<Connection> alreadySearched = new HashSet<Connection>();
|
HashSet<Connection> alreadySearched = new HashSet<Connection>();
|
||||||
GetConnectedComponentsRecursive(alreadySearched, connectedComponents);
|
GetConnectedComponentsRecursive(alreadySearched, connectedComponents);
|
||||||
|
|
||||||
return connectedComponents;
|
return connectedComponents;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1887,13 +1889,27 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (alreadySearched.Contains(c)) { continue; }
|
if (alreadySearched.Contains(c)) { continue; }
|
||||||
alreadySearched.Add(c);
|
alreadySearched.Add(c);
|
||||||
GetConnectedComponentsRecursive(c, alreadySearched, connectedComponents);
|
|
||||||
|
var recipients = c.Recipients;
|
||||||
|
foreach (Connection recipient in recipients)
|
||||||
|
{
|
||||||
|
if (alreadySearched.Contains(recipient)) { continue; }
|
||||||
|
var component = recipient.Item.GetComponent<T>();
|
||||||
|
if (component != null)
|
||||||
|
{
|
||||||
|
var receiverConnections = wifiReceiver.Item.Connections;
|
||||||
|
if (receiverConnections == null) { continue; }
|
||||||
|
foreach (Connection wifiOutput in receiverConnections)
|
||||||
|
{
|
||||||
|
if ((wifiOutput.IsOutput == recipient.IsOutput) || alreadySearched.Contains(wifiOutput)) { continue; }
|
||||||
|
GetConnectedComponentsRecursive(wifiOutput, alreadySearched, connectedComponents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
recipient.Item.GetConnectedComponentsRecursive<T>(alreadySearched, connectedComponents);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Note: This function generates garbage and might be a bit too heavy to be used once per frame.
|
|
||||||
/// </summary>
|
|
||||||
public List<T> GetConnectedComponentsRecursive<T>(Connection c) where T : ItemComponent
|
public List<T> GetConnectedComponentsRecursive<T>(Connection c) where T : ItemComponent
|
||||||
{
|
{
|
||||||
List<T> connectedComponents = new List<T>();
|
List<T> connectedComponents = new List<T>();
|
||||||
@@ -1923,28 +1939,13 @@ namespace Barotrauma
|
|||||||
foreach (Connection recipient in recipients)
|
foreach (Connection recipient in recipients)
|
||||||
{
|
{
|
||||||
if (alreadySearched.Contains(recipient)) { continue; }
|
if (alreadySearched.Contains(recipient)) { continue; }
|
||||||
|
|
||||||
var component = recipient.Item.GetComponent<T>();
|
var component = recipient.Item.GetComponent<T>();
|
||||||
if (component != null)
|
if (component != null)
|
||||||
{
|
{
|
||||||
connectedComponents.Add(component);
|
connectedComponents.Add(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
//connected to a wifi component -> see which other wifi components it can communicate with
|
|
||||||
var wifiComponent = recipient.Item.GetComponent<WifiComponent>();
|
|
||||||
if (wifiComponent != null && wifiComponent.CanTransmit())
|
|
||||||
{
|
|
||||||
foreach (var wifiReceiver in wifiComponent.GetReceiversInRange())
|
|
||||||
{
|
|
||||||
var receiverConnections = wifiReceiver.Item.Connections;
|
|
||||||
if (receiverConnections == null) { continue; }
|
|
||||||
foreach (Connection wifiOutput in receiverConnections)
|
|
||||||
{
|
|
||||||
if ((wifiOutput.IsOutput == recipient.IsOutput) || alreadySearched.Contains(wifiOutput)) { continue; }
|
|
||||||
GetConnectedComponentsRecursive(wifiOutput, alreadySearched, connectedComponents);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
recipient.Item.GetConnectedComponentsRecursive(recipient, alreadySearched, connectedComponents);
|
recipient.Item.GetConnectedComponentsRecursive(recipient, alreadySearched, connectedComponents);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2247,6 +2248,29 @@ namespace Barotrauma
|
|||||||
if (remove) { Spawner?.AddToRemoveQueue(this); }
|
if (remove) { Spawner?.AddToRemoveQueue(this); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<ColoredText> texts = new List<ColoredText>();
|
||||||
|
public List<ColoredText> GetHUDTexts(Character character)
|
||||||
|
{
|
||||||
|
texts.Clear();
|
||||||
|
foreach (ItemComponent ic in components)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(ic.DisplayMsg)) continue;
|
||||||
|
if (!ic.CanBePicked && !ic.CanBeSelected) continue;
|
||||||
|
if (ic is Holdable holdable && !holdable.CanBeDeattached()) continue;
|
||||||
|
|
||||||
|
Color color = Color.Gray;
|
||||||
|
bool hasRequiredSkillsAndItems = ic.HasRequiredSkills(character) && ic.HasRequiredItems(character, false);
|
||||||
|
if (hasRequiredSkillsAndItems)
|
||||||
|
{
|
||||||
|
color = Color.Cyan;
|
||||||
|
}
|
||||||
|
|
||||||
|
texts.Add(new ColoredText(ic.DisplayMsg, color, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remove) { Spawner?.AddToRemoveQueue(this); }
|
||||||
|
}
|
||||||
|
|
||||||
public bool Combine(Item item)
|
public bool Combine(Item item)
|
||||||
{
|
{
|
||||||
bool isCombined = false;
|
bool isCombined = false;
|
||||||
|
|||||||
@@ -569,6 +569,25 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string DisplayName
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string roomName;
|
||||||
|
[Editable, Serialize("", true, translationTextTag: "RoomName.")]
|
||||||
|
public string RoomName
|
||||||
|
{
|
||||||
|
get { return roomName; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (roomName == value) { return; }
|
||||||
|
roomName = value;
|
||||||
|
DisplayName = TextManager.Get(roomName, returnNull: true) ?? roomName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override Rectangle Rect
|
public override Rectangle Rect
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|||||||
Reference in New Issue
Block a user