(4ca668169) Fixed: Conflict

This commit is contained in:
Joonas Rikkonen
2019-05-16 05:58:48 +03:00
parent 0a67590572
commit cb83984e80
80 changed files with 2036 additions and 1859 deletions
@@ -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 + 20, -currentNode.DrawPosition.Y - 20), new Vector2(currentNode.DrawPosition.X - 10, -currentNode.DrawPosition.Y - 30),
Color.Red); Color.Red);
} }
} }
@@ -1,5 +1,6 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using FarseerPhysics; using FarseerPhysics;
using System.Linq;
namespace Barotrauma namespace Barotrauma
{ {
@@ -37,16 +38,20 @@ 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(ObjectiveManager).FormatZeroDecimal()})", Color.White, Color.Black); GUI.DrawString(spriteBatch, pos + textOffset, $"ORDER: {currentOrder.DebugTag} ({currentOrder.GetPriority().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(ObjectiveManager).FormatZeroDecimal()})", Color.White, Color.Black); GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 20), $"OBJECTIVE: {currentObjective.DebugTag} ({currentObjective.GetPriority().FormatZeroDecimal()})", Color.White, Color.Black);
var subObjective = currentObjective.CurrentSubObjective; var subObjective = currentObjective.SubObjectives.FirstOrDefault();
if (subObjective != null) if (subObjective != null)
{ {
GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 40), $"SUBOBJECTIVE: {subObjective.DebugTag} ({subObjective.GetPriority(ObjectiveManager).FormatZeroDecimal()})", Color.White, Color.Black); GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 40), $"SUBOBJECTIVE: {subObjective.DebugTag} ({subObjective.GetPriority().FormatZeroDecimal()})", Color.White, Color.Black);
} }
} }
} }
@@ -75,8 +80,8 @@ namespace Barotrauma
GUI.SmallFont.DrawString(spriteBatch, GUI.SmallFont.DrawString(spriteBatch,
currentNode.ID.ToString(), currentNode.ID.ToString(),
new Vector2(currentNode.DrawPosition.X + 20, -currentNode.DrawPosition.Y - 20), new Vector2(currentNode.DrawPosition.X - 10, -currentNode.DrawPosition.Y - 30),
Color.SkyBlue); Color.Blue);
} }
} }
} }
@@ -121,90 +121,7 @@ namespace Barotrauma
MainLimb.PullJointWorldAnchorB = Collider.SimPosition; MainLimb.PullJointWorldAnchorB = Collider.SimPosition;
MainLimb.PullJointEnabled = true; MainLimb.PullJointEnabled = true;
} }
character.SelectedConstruction = character.MemState[0].SelectedItem;
} }
if (character.MemState[0].Animation == AnimController.Animation.CPR)
{
character.AnimController.Anim = AnimController.Animation.CPR;
}
else if (character.AnimController.Anim == AnimController.Animation.CPR)
{
character.AnimController.Anim = AnimController.Animation.None;
}
Vector2 newVelocity = Collider.LinearVelocity;
Vector2 newPosition = Collider.SimPosition;
float newRotation = Collider.Rotation;
float newAngularVelocity = Collider.AngularVelocity;
Collider.CorrectPosition(character.MemState, out newPosition, out newVelocity, out newRotation, out newAngularVelocity);
newVelocity = newVelocity.ClampLength(100.0f);
if (!MathUtils.IsValid(newVelocity)) { newVelocity = Vector2.Zero; }
overrideTargetMovement = newVelocity.LengthSquared() > 0.01f ? newVelocity : Vector2.Zero;
Collider.LinearVelocity = newVelocity;
Collider.AngularVelocity = newAngularVelocity;
float distSqrd = Vector2.DistanceSquared(newPosition, Collider.SimPosition);
float errorTolerance = character.AllowInput ? 0.01f : 0.2f;
if (distSqrd > errorTolerance)
{
if (distSqrd > 10.0f || !character.AllowInput)
{
Collider.TargetRotation = newRotation;
SetPosition(newPosition, lerp: distSqrd < 5.0f, ignorePlatforms: false);
}
else
{
Collider.TargetRotation = newRotation;
Collider.TargetPosition = newPosition;
Collider.MoveToTargetPosition(true);
}
}
//unconscious/dead characters can't correct their position using AnimController movement
// -> we need to correct it manually
if (!character.AllowInput)
{
float mainLimbDistSqrd = Vector2.DistanceSquared(MainLimb.PullJointWorldAnchorA, Collider.SimPosition);
float mainLimbErrorTolerance = 0.1f;
//if the main limb is roughly at the correct position and the collider isn't moving (much at least),
//don't attempt to correct the position.
if (mainLimbDistSqrd > mainLimbErrorTolerance || Collider.LinearVelocity.LengthSquared() > 0.05f)
{
MainLimb.PullJointWorldAnchorB = Collider.SimPosition;
MainLimb.PullJointEnabled = true;
}
}
}
character.MemLocalState.Clear();
}
else
{
//remove states with a timestamp (there may still timestamp-based states
//in the list if the controlled character switches from timestamp-based interpolation to ID-based)
character.MemState.RemoveAll(m => m.Timestamp > 0.0f);
for (int i = 0; i < character.MemLocalState.Count; i++)
{
if (character.Submarine == null)
{
//transform in-sub coordinates to outside coordinates
if (character.MemLocalState[i].Position.Y > lowestSubPos)
{
character.MemLocalState[i].TransformInToOutside();
}
}
else if (currentHull?.Submarine != null)
{
//transform outside coordinates to in-sub coordinates
if (character.MemLocalState[i].Position.Y < lowestSubPos)
{
character.MemLocalState[i].TransformOutToInside(currentHull.Submarine);
}
}
} }
character.MemLocalState.Clear(); character.MemLocalState.Clear();
} }
@@ -86,7 +86,7 @@ namespace Barotrauma
if (character.Inventory != null) if (character.Inventory != null)
{ {
if (!character.LockHands && character.Stun < 0.1f && if (!character.LockHands && character.Stun < 0.1f &&
(character.SelectedConstruction == null || character.SelectedConstruction.GetComponent<Controller>() == null)) (character.SelectedConstruction == null || character.SelectedConstruction?.GetComponent<Controller>()?.User != character))
{ {
character.Inventory.Update(deltaTime, cam); character.Inventory.Update(deltaTime, cam);
} }
@@ -321,7 +321,7 @@ namespace Barotrauma
} }
if (character.Inventory != null && !character.LockHands) if (character.Inventory != null && !character.LockHands)
{ {
character.Inventory.Locked = (character.SelectedConstruction != null && character.SelectedConstruction.GetComponent<Controller>() != null); character.Inventory.Locked = (character.SelectedConstruction?.GetComponent<Controller>()?.User == character);
character.Inventory.DrawOwn(spriteBatch); character.Inventory.DrawOwn(spriteBatch);
character.Inventory.CurrentLayout = CharacterHealth.OpenHealthWindow == null && character.SelectedCharacter == null ? character.Inventory.CurrentLayout = CharacterHealth.OpenHealthWindow == null && character.SelectedCharacter == null ?
CharacterInventory.Layout.Default : CharacterInventory.Layout.Default :
@@ -463,7 +463,7 @@ namespace Barotrauma
return; return;
} }
} }
bool forceAfflictionContainerUpdate = false; bool forceAfflictionContainerUpdate = false;
if (updateDisplayedAfflictionsTimer > 0.0f) if (updateDisplayedAfflictionsTimer > 0.0f)
{ {
@@ -505,7 +505,7 @@ namespace Barotrauma
Math.Min(healthShadowSize + deltaTime, healthBar.BarSize) : Math.Min(healthShadowSize + deltaTime, healthBar.BarSize) :
Math.Max(healthShadowSize - deltaTime, healthBar.BarSize); Math.Max(healthShadowSize - deltaTime, healthBar.BarSize);
} }
dropItemArea.Visible = !Character.IsDead; dropItemArea.Visible = !Character.IsDead;
float blurStrength = 0.0f; float blurStrength = 0.0f;
@@ -733,7 +733,9 @@ namespace Barotrauma
Rectangle.Union(HUDLayoutSettings.AfflictionAreaLeft, HUDLayoutSettings.HealthBarAreaLeft) : Rectangle.Union(HUDLayoutSettings.AfflictionAreaLeft, HUDLayoutSettings.HealthBarAreaLeft) :
Rectangle.Union(HUDLayoutSettings.AfflictionAreaRight, HUDLayoutSettings.HealthBarAreaRight); Rectangle.Union(HUDLayoutSettings.AfflictionAreaRight, HUDLayoutSettings.HealthBarAreaRight);
if (Character.AllowInput && UseHealthWindow && hoverArea.Contains(PlayerInput.MousePosition) && Inventory.SelectedSlot == null) if (Character.AllowInput && UseHealthWindow &&
Character.SelectedConstruction?.GetComponent<Controller>()?.User != Character &&
hoverArea.Contains(PlayerInput.MousePosition) && Inventory.SelectedSlot == null)
{ {
healthBar.State = GUIComponent.ComponentState.Hover; healthBar.State = GUIComponent.ComponentState.Hover;
if (PlayerInput.LeftButtonClicked()) if (PlayerInput.LeftButtonClicked())
@@ -56,11 +56,11 @@ namespace Barotrauma
} }
public ScalableFont(XElement element, GraphicsDevice gd = null) public ScalableFont(XElement element, GraphicsDevice gd = null)
: this (element.GetAttributeString("file", ""), (uint)element.GetAttributeInt("size", 14), gd) : this (element.GetAttributeString("file", ""), (uint)element.GetAttributeInt("size", 14), gd, element.GetAttributeBool("dynamicloading", false))
{ {
} }
public ScalableFont(string filename, uint size, GraphicsDevice gd = null) public ScalableFont(string filename, uint size, GraphicsDevice gd = null, bool dynamicLoading = false)
{ {
if (Lib == null) Lib = new Library(); if (Lib == null) Lib = new Library();
this.filename = filename; this.filename = filename;
@@ -310,6 +310,11 @@ namespace Barotrauma
} }
uint charIndex = text[i]; uint charIndex = text[i];
if (DynamicLoading && !texCoords.ContainsKey(charIndex))
{
DynamicRenderAtlas(graphicsDevice, charIndex);
}
if (texCoords.TryGetValue(charIndex, out GlyphData gd) || texCoords.TryGetValue(9633, out gd)) //9633 = white square if (texCoords.TryGetValue(charIndex, out GlyphData gd) || texCoords.TryGetValue(9633, out gd)) //9633 = white square
{ {
if (gd.texIndex >= 0) if (gd.texIndex >= 0)
@@ -344,7 +349,13 @@ namespace Barotrauma
currentPos.Y += baseHeight * 1.8f; currentPos.Y += baseHeight * 1.8f;
continue; continue;
} }
uint charIndex = text[i];
uint charIndex = text[i];
if (DynamicLoading && !texCoords.ContainsKey(charIndex))
{
DynamicRenderAtlas(graphicsDevice, charIndex);
}
if (texCoords.TryGetValue(charIndex, out GlyphData gd) || texCoords.TryGetValue(9633, out gd)) //9633 = white square if (texCoords.TryGetValue(charIndex, out GlyphData gd) || texCoords.TryGetValue(9633, out gd)) //9633 = white square
{ {
if (gd.texIndex >= 0) if (gd.texIndex >= 0)
@@ -376,6 +387,10 @@ namespace Barotrauma
continue; continue;
} }
uint charIndex = text[i]; uint charIndex = text[i];
if (DynamicLoading && !texCoords.ContainsKey(charIndex))
{
DynamicRenderAtlas(graphicsDevice, charIndex);
}
if (texCoords.TryGetValue(charIndex, out GlyphData gd)) if (texCoords.TryGetValue(charIndex, out GlyphData gd))
{ {
currentLineX += gd.advance; currentLineX += gd.advance;
@@ -389,6 +404,10 @@ namespace Barotrauma
{ {
Vector2 retVal = Vector2.Zero; Vector2 retVal = Vector2.Zero;
retVal.Y = baseHeight * 1.8f; retVal.Y = baseHeight * 1.8f;
if (DynamicLoading && !texCoords.ContainsKey(c))
{
DynamicRenderAtlas(graphicsDevice, c);
}
if (texCoords.TryGetValue(c, out GlyphData gd)) if (texCoords.TryGetValue(c, out GlyphData gd))
{ {
retVal.X = gd.advance; retVal.X = gd.advance;
@@ -9,9 +9,11 @@ namespace Barotrauma
public class GUIMessageBox : GUIFrame public class GUIMessageBox : GUIFrame
{ {
public static List<GUIComponent> MessageBoxes = new List<GUIComponent>(); public static List<GUIComponent> MessageBoxes = new List<GUIComponent>();
private static int DefaultWidth
{
get { return Math.Max(400, 400 * (GameMain.GraphicsWidth / 1920)); }
}
public const int DefaultWidth = 400, DefaultHeight = 250;
public List<GUIButton> Buttons { get; private set; } = new List<GUIButton>(); public List<GUIButton> Buttons { get; private set; } = new List<GUIButton>();
//public GUIFrame BackgroundFrame { get; private set; } //public GUIFrame BackgroundFrame { get; private set; }
public GUILayoutGroup Content { get; private set; } public GUILayoutGroup Content { get; private set; }
@@ -21,24 +23,31 @@ namespace Barotrauma
public string Tag { get; private set; } public string Tag { get; private set; }
public static GUIComponent VisibleBox => MessageBoxes.LastOrDefault(); public static GUIComponent VisibleBox => MessageBoxes.LastOrDefault();
public GUIMessageBox(string headerText, string text) public GUIMessageBox(string headerText, string text, Vector2? relativeSize = null, Point? minSize = null)
: this(headerText, text, new string[] {"OK"}, DefaultWidth, 0) : this(headerText, text, new string[] { "OK" }, relativeSize, minSize)
{ {
this.Buttons[0].OnClicked = Close; this.Buttons[0].OnClicked = Close;
} }
public GUIMessageBox(string headerText, string text, int width, int height) public GUIMessageBox(string headerText, string text, string[] buttons, Vector2? relativeSize = null, Point? minSize = null, Alignment textAlignment = Alignment.TopLeft, string tag = "")
: this(headerText, text, new string[] { "OK" }, width, height)
{
this.Buttons[0].OnClicked = Close;
}
// TODO: allow to use a relative size.
public GUIMessageBox(string headerText, string text, string[] buttons, int width = DefaultWidth, int height = 0, Alignment textAlignment = Alignment.TopLeft, string tag = "")
: base(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: "") : base(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: "")
{ {
if (width == DefaultWidth) width = (int)(width * GUI.Scale); //int width = (int)(DefaultWidth * GUI.Scale), height = 0;
int width = DefaultWidth, height = 0;
if (relativeSize.HasValue)
{
width = (int)(GameMain.GraphicsWidth * relativeSize.Value.X);
height = (int)(GameMain.GraphicsHeight * relativeSize.Value.Y);
}
if (minSize.HasValue)
{
width = Math.Max(width, minSize.Value.X);
if (height > 0)
{
height = Math.Max(height, minSize.Value.Y);
}
}
InnerFrame = new GUIFrame(new RectTransform(new Point(width, height), RectTransform, Anchor.Center) { IsFixedSize = false }, style: null); InnerFrame = new GUIFrame(new RectTransform(new Point(width, height), RectTransform, Anchor.Center) { IsFixedSize = false }, style: null);
GUI.Style.Apply(InnerFrame, "", this); GUI.Style.Apply(InnerFrame, "", this);
@@ -51,7 +60,7 @@ namespace Barotrauma
GUI.Style.Apply(Header, "", this); GUI.Style.Apply(Header, "", this);
Header.RectTransform.MinSize = new Point(0, Header.Rect.Height); Header.RectTransform.MinSize = new Point(0, Header.Rect.Height);
if (!string.IsNullOrWhiteSpace(text)) if (height == 0)
{ {
Text = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), Text = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform),
text, textAlignment: textAlignment, wrap: true); text, textAlignment: textAlignment, wrap: true);
@@ -76,7 +85,11 @@ namespace Barotrauma
height += Header.Rect.Height + Content.AbsoluteSpacing; height += Header.Rect.Height + Content.AbsoluteSpacing;
height += (Text == null ? 0 : Text.Rect.Height) + Content.AbsoluteSpacing; height += (Text == null ? 0 : Text.Rect.Height) + Content.AbsoluteSpacing;
height += buttonContainer.Rect.Height; height += buttonContainer.Rect.Height;
if (minSize.HasValue)
{
height = Math.Max(height, minSize.Value.Y);
}
InnerFrame.RectTransform.NonScaledSize = InnerFrame.RectTransform.NonScaledSize =
new Point(InnerFrame.Rect.Width, (int)Math.Max(height / Content.RectTransform.RelativeSize.Y, height + 50)); new Point(InnerFrame.Rect.Width, (int)Math.Max(height / Content.RectTransform.RelativeSize.Y, height + 50));
Content.RectTransform.NonScaledSize = Content.RectTransform.NonScaledSize =
@@ -137,9 +137,10 @@ namespace Barotrauma
private ScalableFont LoadFont(XElement element, GraphicsDevice graphicsDevice) private ScalableFont LoadFont(XElement element, GraphicsDevice graphicsDevice)
{ {
string file = GetFontFilePath(element); string file = GetFontFilePath(element);
uint size = GetFontSize(element); uint size = GetFontSize(element);
return new ScalableFont(file, size, graphicsDevice); bool dynamicLoading = GetFontDynamicLoading(element);
return new ScalableFont(file, size, graphicsDevice, dynamicLoading);
} }
private uint GetFontSize(XElement element) private uint GetFontSize(XElement element)
@@ -170,6 +171,20 @@ namespace Barotrauma
return element.GetAttributeString("file", ""); return element.GetAttributeString("file", "");
} }
private bool GetFontDynamicLoading(XElement element)
{
foreach (XElement subElement in element.Elements())
{
if (subElement.Name.ToString().ToLowerInvariant() != "override") { continue; }
string language = subElement.GetAttributeString("language", "").ToLowerInvariant();
if (GameMain.Config.Language.ToLowerInvariant() == language)
{
return subElement.GetAttributeBool("dynamicloading", false);
}
}
return element.GetAttributeBool("dynamicloading", false);
}
public GUIComponentStyle GetComponentStyle(string name) public GUIComponentStyle GetComponentStyle(string name)
{ {
componentStyles.TryGetValue(name.ToLowerInvariant(), out GUIComponentStyle style); componentStyles.TryGetValue(name.ToLowerInvariant(), out GUIComponentStyle style);
@@ -15,21 +15,41 @@ namespace Barotrauma
private RenderTarget2D renderTarget; private RenderTarget2D renderTarget;
private Video splashScreen; private Sprite languageSelectionCursor;
public Video SplashScreen private ScalableFont languageSelectionFont;
private Video currSplashScreen;
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;
} }
} }
} }
@@ -71,11 +91,17 @@ namespace Barotrauma
} }
public bool WaitForLanguageSelection public bool WaitForLanguageSelection
{
get;
set;
}
public LoadingScreen(GraphicsDevice graphics)
{ {
backgroundTexture = TextureLoader.FromFile("Content/UI/titleBackground.png"); backgroundTexture = TextureLoader.FromFile("Content/UI/titleBackground.png");
renderTarget = new RenderTarget2D(graphics, GameMain.GraphicsWidth, GameMain.GraphicsHeight); renderTarget = new RenderTarget2D(graphics, GameMain.GraphicsWidth, GameMain.GraphicsHeight);
GameMain.Instance.OnResolutionChanged += () => GameMain.Instance.OnResolutionChanged += () =>
{ {
renderTarget?.Dispose(); renderTarget?.Dispose();
renderTarget = new RenderTarget2D(graphics, GameMain.GraphicsWidth, GameMain.GraphicsHeight); renderTarget = new RenderTarget2D(graphics, GameMain.GraphicsWidth, GameMain.GraphicsHeight);
@@ -85,15 +111,14 @@ namespace Barotrauma
selectedTip = TextManager.Get("LoadingScreenTip", true); selectedTip = TextManager.Get("LoadingScreenTip", true);
} }
public void Draw(SpriteBatch spriteBatch, GraphicsDevice graphics, float deltaTime) public void Draw(SpriteBatch spriteBatch, GraphicsDevice graphics, float deltaTime)
{ {
if (GameMain.Config.EnableSplashScreen) if (GameMain.Config.EnableSplashScreen)
{ {
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)
{ {
@@ -101,10 +126,10 @@ namespace Barotrauma
GameMain.Config.EnableSplashScreen = false; GameMain.Config.EnableSplashScreen = false;
} }
} }
var titleStyle = GUI.Style?.GetComponentStyle("TitleText"); var titleStyle = GUI.Style?.GetComponentStyle("TitleText");
Sprite titleSprite = null; Sprite titleSprite = null;
if (titleStyle != null && titleStyle.Sprites.ContainsKey(GUIComponent.ComponentState.None)) if (!WaitForLanguageSelection && titleStyle != null && titleStyle.Sprites.ContainsKey(GUIComponent.ComponentState.None))
{ {
titleSprite = titleStyle.Sprites[GUIComponent.ComponentState.None].First()?.Sprite; titleSprite = titleStyle.Sprites[GUIComponent.ComponentState.None].First()?.Sprite;
} }
@@ -147,8 +172,6 @@ namespace Barotrauma
titleSprite?.Draw(spriteBatch, TitlePosition, Color.White * Math.Min((state - 1.0f) / 5.0f, 1.0f), scale: titleScale); titleSprite?.Draw(spriteBatch, TitlePosition, Color.White * Math.Min((state - 1.0f) / 5.0f, 1.0f), scale: titleScale);
titleSprite?.Draw(spriteBatch, TitlePosition, Color.White * Math.Min((state - 1.0f) / 5.0f, 1.0f), scale: titleScale);
if (WaitForLanguageSelection) if (WaitForLanguageSelection)
{ {
DrawLanguageSelectionPrompt(spriteBatch, graphics); DrawLanguageSelectionPrompt(spriteBatch, graphics);
@@ -191,28 +214,81 @@ namespace Barotrauma
spriteBatch.End(); spriteBatch.End();
} }
private void DrawSplashScreen(SpriteBatch spriteBatch) private void DrawLanguageSelectionPrompt(SpriteBatch spriteBatch, GraphicsDevice graphicsDevice)
{ {
if (SplashScreen != null) if (languageSelectionFont == null)
{ {
if (SplashScreen.IsPlaying) languageSelectionFont = new ScalableFont("Content/Fonts/BebasNeue-Regular.otf", (uint)(30 * (GameMain.GraphicsHeight / 1080.0f)), graphicsDevice);
{ }
spriteBatch.Begin(); if (languageSelectionCursor == null)
spriteBatch.Draw(SplashScreen.GetTexture(), new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), Color.White); {
spriteBatch.End(); languageSelectionCursor = new Sprite("Content/UI/cursor.png", Vector2.Zero);
}
if (PlayerInput.KeyHit(Keys.Space) || PlayerInput.KeyHit(Keys.Enter) || PlayerInput.LeftButtonDown()) Vector2 textPos = new Vector2(GameMain.GraphicsWidth / 2, GameMain.GraphicsHeight * 0.3f);
{ Vector2 textSpacing = new Vector2(0.0f, (GameMain.GraphicsHeight * 0.5f) / TextManager.AvailableLanguages.Count());
SplashScreen.Dispose(); SplashScreen = null; foreach (string language in TextManager.AvailableLanguages)
} {
} Vector2 textSize = languageSelectionFont.MeasureString(language);
else 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())
{ {
SplashScreen.Dispose(); SplashScreen = null; GameMain.Config.Language = language;
WaitForLanguageSelection = false;
} }
}
textPos += textSpacing;
}
languageSelectionCursor.Draw(spriteBatch, PlayerInput.LatestMousePosition);
} }
private void DrawSplashScreen(SpriteBatch spriteBatch, GraphicsDevice graphics)
{
if (currSplashScreen == null && PendingSplashScreens.Count == 0) { return; }
if (currSplashScreen == null)
{
var newSplashScreen = PendingSplashScreens.Dequeue();
string fileName = newSplashScreen.First;
Point resolution = newSplashScreen.Second;
try
{
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)
{ {
+25 -16
View File
@@ -178,7 +178,6 @@ namespace Barotrauma
GUI.KeyboardDispatcher = new EventInput.KeyboardDispatcher(Window); GUI.KeyboardDispatcher = new EventInput.KeyboardDispatcher(Window);
PerformanceCounter = new PerformanceCounter(); PerformanceCounter = new PerformanceCounter();
IsFixedTimeStep = false; IsFixedTimeStep = false;
@@ -271,15 +270,17 @@ 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()
@@ -323,6 +324,11 @@ namespace Barotrauma
DebugConsole.NewMessage("LOADING COROUTINE", Color.Lime); DebugConsole.NewMessage("LOADING COROUTINE", Color.Lime);
} }
while (TitleScreen.WaitForLanguageSelection)
{
yield return CoroutineStatus.Running;
}
SoundManager = new Sounds.SoundManager(); SoundManager = new Sounds.SoundManager();
SoundManager.SetCategoryGainMultiplier("default", Config.SoundVolume); SoundManager.SetCategoryGainMultiplier("default", Config.SoundVolume);
SoundManager.SetCategoryGainMultiplier("ui", Config.SoundVolume); SoundManager.SetCategoryGainMultiplier("ui", Config.SoundVolume);
@@ -331,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();
@@ -371,11 +382,9 @@ namespace Barotrauma
InitUserStats(); InitUserStats();
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
LightManager = new Lights.LightManager(base.GraphicsDevice, Content); LightManager = new Lights.LightManager(base.GraphicsDevice, Content);
WaterRenderer.Instance = new WaterRenderer(base.GraphicsDevice, Content);
TitleScreen.LoadState = 1.0f; TitleScreen.LoadState = 1.0f;
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
@@ -526,7 +535,7 @@ namespace Barotrauma
protected override void UnloadContent() protected override void UnloadContent()
{ {
Video.Close(); Video.Close();
SoundManager.Dispose(); SoundManager?.Dispose();
} }
/// <summary> /// <summary>
@@ -74,17 +74,12 @@ namespace Barotrauma
public CrewManager(XElement element, bool isSinglePlayer) public CrewManager(XElement element, bool isSinglePlayer)
: this(isSinglePlayer) : this(isSinglePlayer)
{ {
if (GameMain.Client != null) if (!isSinglePlayer)
{ {
//let the server create random conversations in MP DebugConsole.ThrowError("Cannot add messages to single player chat box in multiplayer mode!\n" + Environment.StackTrace);
return; return;
} }
List<Character> availableSpeakers = Character.CharacterList.FindAll(c => if (string.IsNullOrEmpty(text)) { return; }
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);
@@ -95,6 +90,7 @@ namespace Barotrauma
break; break;
} }
} }
ChatBox.AddMessage(ChatMessage.Create(senderName, text, messageType, sender));
} }
partial void InitProjectSpecific() partial void InitProjectSpecific()
@@ -199,6 +195,7 @@ 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,
@@ -242,24 +239,27 @@ namespace Barotrauma
public IEnumerable<Character> GetCharacters() public IEnumerable<Character> GetCharacters()
{ {
if (characterInfos.Contains(characterInfo)) if (character?.Inventory == null) return null;
{
DebugConsole.ThrowError("Tried to add the same character info to CrewManager twice.\n" + Environment.StackTrace);
return;
}
characterInfos.Add(characterInfo); var radioItem = character.Inventory.Items.FirstOrDefault(it => it != null && it.GetComponent<WifiComponent>() != null);
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 (character == null) if (GameMain.Client != null)
{ {
DebugConsole.ThrowError("Tried to remove a null character from CrewManager.\n" + Environment.StackTrace); //let the server create random conversations in MP
return; return;
} }
characters.Remove(character); List<Character> availableSpeakers = Character.CharacterList.FindAll(c =>
if (removeInfo) characterInfos.Remove(character.Info); c.AIController is HumanAIController &&
!c.IsDead &&
c.SpeechImpediment <= 100.0f);
pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers));
} }
public void AddCharacter(Character character) public void AddCharacter(Character character)
@@ -633,183 +633,9 @@ namespace Barotrauma
{ {
characterListBox.BarScroll = roundedPos; characterListBox.BarScroll = roundedPos;
} }
var characterArea = new GUIButton(new RectTransform(new Point(characterInfoWidth, frame.Rect.Height), frame.RectTransform, Anchor.CenterLeft), style: "GUITextBox") soundIcon.Visible = !muted && !mutedLocally;
{ soundIconDisabled.Visible = muted || mutedLocally;
UserData = character, soundIconDisabled.ToolTip = TextManager.Get(mutedLocally ? "MutedLocally" : "MutedGlobally");
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)
@@ -953,12 +779,6 @@ 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)
@@ -1016,23 +836,19 @@ namespace Barotrauma
} }
} }
} }
//only one target (or an order with no particular targets), just show options
character.SetOrder(order, option, orderGiver, speak: orderGiver != character); else
if (IsSinglePlayer)
{ {
orderGiver?.Speak( orderTargetFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.2f + order.Options.Length * 0.1f, 0.18f), GUI.Canvas)
order.GetChatMessage(character.Name, orderGiver.CurrentHull?.DisplayName, givingOrderToSelf: character == orderGiver, orderOption: option), null); { AbsoluteOffset = new Point(orderButton.Rect.Center.X, orderButton.Rect.Bottom) },
} isHorizontal: true, childAnchor: Anchor.BottomLeft)
else if (orderGiver != null)
{
OrderChatMessage msg = new OrderChatMessage(order, option, order.TargetItemComponent?.Item, character, orderGiver);
if (GameMain.Client != null)
{ {
GameMain.Client.SendChatMessage(msg); UserData = character,
} Stretch = true
} };
DisplayCharacterOrder(character, order); //line connecting the order button to the option buttons
} //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
@@ -201,7 +201,7 @@ namespace Barotrauma.Tutorials
SetHighlight(captain_statusMonitor, true); SetHighlight(captain_statusMonitor, true);
do do
{ {
captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f); //captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f);
yield return new WaitForSeconds(1.0f); yield return new WaitForSeconds(1.0f);
} while (Submarine.MainSub.DockedTo.Count > 0); } while (Submarine.MainSub.DockedTo.Count > 0);
RemoveCompletedObjective(segments[4]); RemoveCompletedObjective(segments[4]);
@@ -225,7 +225,7 @@ namespace Barotrauma.Tutorials
TriggerTutorialSegment(6); // Docking TriggerTutorialSegment(6); // Docking
do do
{ {
captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f); //captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f);
yield return new WaitForSeconds(1.0f); yield return new WaitForSeconds(1.0f);
} while (!Submarine.MainSub.AtEndPosition || Submarine.MainSub.DockedTo.Count == 0); } while (!Submarine.MainSub.AtEndPosition || Submarine.MainSub.DockedTo.Count == 0);
RemoveCompletedObjective(segments[6]); RemoveCompletedObjective(segments[6]);
@@ -132,7 +132,8 @@ namespace Barotrauma
var languageDD = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.045f), generalLayoutGroup.RectTransform)); var languageDD = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.045f), generalLayoutGroup.RectTransform));
foreach (string language in TextManager.AvailableLanguages) foreach (string language in TextManager.AvailableLanguages)
{ {
languageDD.AddItem(TextManager.Get("Language." + language, returnNull: true) ?? language, language); //TODO: display the name of the language in the target language?
languageDD.AddItem(language, language);
} }
languageDD.SelectItem(TextManager.Language); languageDD.SelectItem(TextManager.Language);
languageDD.OnSelected = (guiComponent, obj) => languageDD.OnSelected = (guiComponent, obj) =>
@@ -78,8 +78,8 @@ namespace Barotrauma.Items.Components
CanBeFocused = false CanBeFocused = false
}; };
var pumpSpeedText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), rightArea.RectTransform) { RelativeOffset = new Vector2(0.25f, 0.0f) }, var pumpSpeedText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), rightArea.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.0f) },
"", textAlignment: Alignment.BottomLeft); "", textAlignment: Alignment.BottomLeft, wrap: true);
string pumpSpeedStr = TextManager.Get("PumpSpeed"); string pumpSpeedStr = TextManager.Get("PumpSpeed");
pumpSpeedText.TextGetter = () => { return pumpSpeedStr + ": " + (int)flowPercentage + " %"; }; pumpSpeedText.TextGetter = () => { return pumpSpeedStr + ": " + (int)flowPercentage + " %"; };
@@ -90,7 +90,7 @@ namespace Barotrauma.Items.Components
}; };
new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), sliderArea.RectTransform), new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), sliderArea.RectTransform),
TextManager.Get("PumpOut"), textAlignment: Alignment.Center); TextManager.Get("PumpOut"), textAlignment: Alignment.Center, wrap: true, font: GUI.SmallFont);
pumpSpeedSlider = new GUIScrollBar(new RectTransform(new Vector2(0.8f, 1.0f), sliderArea.RectTransform), barSize: 0.25f, style: "GUISlider") pumpSpeedSlider = new GUIScrollBar(new RectTransform(new Vector2(0.8f, 1.0f), sliderArea.RectTransform), barSize: 0.25f, style: "GUISlider")
{ {
Step = 0.05f, Step = 0.05f,
@@ -111,7 +111,7 @@ namespace Barotrauma.Items.Components
}; };
new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), sliderArea.RectTransform), new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), sliderArea.RectTransform),
TextManager.Get("PumpIn"), textAlignment: Alignment.Center); TextManager.Get("PumpIn"), textAlignment: Alignment.Center, wrap: true, font: GUI.SmallFont);
} }
public override void OnItemLoaded() public override void OnItemLoaded()
@@ -104,13 +104,13 @@ namespace Barotrauma.Items.Components
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.85f), GuiFrame.RectTransform, Anchor.Center), isHorizontal: true) var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.85f), GuiFrame.RectTransform, Anchor.Center), isHorizontal: true)
{ {
RelativeSpacing = 0.015f, RelativeSpacing = 0.012f,
Stretch = true Stretch = true
}; };
GUIFrame columnLeft = new GUIFrame(new RectTransform(new Vector2(0.2f, 1.0f), paddedFrame.RectTransform), style: null); GUIFrame columnLeft = new GUIFrame(new RectTransform(new Vector2(0.25f, 1.0f), paddedFrame.RectTransform), style: null);
leftHUDColumn = columnLeft; leftHUDColumn = columnLeft;
GUIFrame columnMid = new GUIFrame(new RectTransform(new Vector2(0.5f, 1.0f), paddedFrame.RectTransform), style: null); GUIFrame columnMid = new GUIFrame(new RectTransform(new Vector2(0.45f, 1.0f), paddedFrame.RectTransform), style: null);
GUIFrame columnRight = new GUIFrame(new RectTransform(new Vector2(0.3f, 1.0f), paddedFrame.RectTransform), style: null); GUIFrame columnRight = new GUIFrame(new RectTransform(new Vector2(0.3f, 1.0f), paddedFrame.RectTransform), style: null);
//---------------------------------------------------------- //----------------------------------------------------------
@@ -131,7 +131,7 @@ namespace Barotrauma.Items.Components
}; };
var btnText = warningBtn.GetChild<GUITextBlock>(); var btnText = warningBtn.GetChild<GUITextBlock>();
btnText.Font = GUI.SmallFont; btnText.Font = GUI.Font;
btnText.Wrap = true; btnText.Wrap = true;
btnText.SetTextPos(); btnText.SetTextPos();
warningButtons.Add(warningTexts[i], warningBtn); warningButtons.Add(warningTexts[i], warningBtn);
@@ -776,6 +776,37 @@ 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;
} }
@@ -92,6 +92,7 @@ namespace Barotrauma
if (!string.IsNullOrEmpty(uiLabel) && !subInventory) if (!string.IsNullOrEmpty(uiLabel) && !subInventory)
{ {
uiLabel = ToolBox.WrapText(uiLabel, BackgroundFrame.Width, GUI.Font, 1);
GUI.DrawString(spriteBatch, GUI.DrawString(spriteBatch,
new Vector2((int)(BackgroundFrame.Center.X - GUI.Font.MeasureString(uiLabel).X / 2), (int)BackgroundFrame.Y + 5), new Vector2((int)(BackgroundFrame.Center.X - GUI.Font.MeasureString(uiLabel).X / 2), (int)BackgroundFrame.Y + 5),
uiLabel, Color.White * 0.9f); uiLabel, Color.White * 0.9f);
@@ -25,17 +25,21 @@ namespace Barotrauma
var allLevelObjects = levelObjectManager.GetAllObjects(); var allLevelObjects = levelObjectManager.GetAllObjects();
foreach (var levelObj in allLevelObjects) foreach (var levelObj in allLevelObjects)
{ {
if (levelObj.Prefab.Sprite != null && foreach (Sprite sprite in levelObj.Prefab.Sprites)
!uniqueTextures.Contains(levelObj.Prefab.Sprite.Texture))
{ {
uniqueTextures.Add(levelObj.Prefab.Sprite.Texture); if (!uniqueTextures.Contains(sprite.Texture))
uniqueSprites.Add(levelObj.Prefab.Sprite); {
uniqueTextures.Add(sprite.Texture);
uniqueSprites.Add(sprite);
}
} }
if (levelObj.Prefab.SpecularSprite != null && foreach (Sprite specularSprite in levelObj.Prefab.SpecularSprites)
!uniqueTextures.Contains(levelObj.Prefab.SpecularSprite.Texture))
{ {
uniqueTextures.Add(levelObj.Prefab.SpecularSprite.Texture); if (!uniqueTextures.Contains(specularSprite.Texture))
uniqueSprites.Add(levelObj.Prefab.SpecularSprite); {
uniqueTextures.Add(specularSprite.Texture);
uniqueSprites.Add(specularSprite);
}
} }
} }
@@ -76,8 +76,8 @@ namespace Barotrauma
partial void InitProjSpecific() partial void InitProjSpecific()
{ {
Prefab.Sprite?.EnsureLazyLoaded(); Sprite?.EnsureLazyLoaded();
Prefab.SpecularSprite?.EnsureLazyLoaded(); SpecularSprite?.EnsureLazyLoaded();
Prefab.DeformableSprite?.EnsureLazyLoaded(); Prefab.DeformableSprite?.EnsureLazyLoaded();
CurrentSwingAmount = Prefab.SwingAmountRad; CurrentSwingAmount = Prefab.SwingAmountRad;
@@ -109,7 +109,7 @@ namespace Barotrauma
Vector2 camDiff = new Vector2(obj.Position.X, obj.Position.Y) - cam.WorldViewCenter; Vector2 camDiff = new Vector2(obj.Position.X, obj.Position.Y) - cam.WorldViewCenter;
camDiff.Y = -camDiff.Y; camDiff.Y = -camDiff.Y;
Sprite activeSprite = specular ? obj.ActivePrefab.SpecularSprite : obj.ActivePrefab.Sprite; Sprite activeSprite = specular ? obj.SpecularSprite : obj.Sprite;
activeSprite?.Draw( activeSprite?.Draw(
spriteBatch, spriteBatch,
new Vector2(obj.Position.X, -obj.Position.Y) - camDiff * obj.Position.Z / 10000.0f, new Vector2(obj.Position.X, -obj.Position.Y) - camDiff * obj.Position.Z / 10000.0f,
@@ -491,30 +491,6 @@ namespace Barotrauma
} }
} }
foreach (MapEntity e in MapEntity.mapEntityList)
{
if (Vector2.Distance(e.Position, HiddenSubPosition) > 20000)
{
//move disabled items (wires, items inside containers) inside the sub
if (e is Item item && item.body != null && !item.body.Enabled)
{
item.SetTransform(ConvertUnits.ToSimUnits(HiddenSubPosition), 0.0f);
}
}
}
foreach (MapEntity e in MapEntity.mapEntityList)
{
if (Vector2.Distance(e.Position, HiddenSubPosition) > 20000)
{
//move disabled items (wires, items inside containers) inside the sub
if (e is Item item && item.body != null && !item.body.Enabled)
{
item.SetTransform(ConvertUnits.ToSimUnits(HiddenSubPosition), 0.0f);
}
}
}
foreach (MapEntity e in MapEntity.mapEntityList) foreach (MapEntity e in MapEntity.mapEntityList)
{ {
if (Vector2.Distance(e.Position, HiddenSubPosition) > 20000) if (Vector2.Distance(e.Position, HiddenSubPosition) > 20000)
@@ -1012,7 +1012,7 @@ namespace Barotrauma.Networking
} }
} }
GUIMessageBox msgBox = new GUIMessageBox(TextManager.Get("PermissionsChanged"), msg, GUIMessageBox.DefaultWidth, 0) GUIMessageBox msgBox = new GUIMessageBox(TextManager.Get("PermissionsChanged"), msg)
{ {
UserData = "permissions" UserData = "permissions"
}; };
@@ -1707,7 +1707,7 @@ namespace Barotrauma.Networking
infoButton.UserData = newSub; infoButton.UserData = newSub;
infoButton.OnClicked = (component, userdata) => infoButton.OnClicked = (component, userdata) =>
{ {
((Submarine)userdata).CreatePreviewWindow(new GUIMessageBox("", "", 550, 400)); ((Submarine)userdata).CreatePreviewWindow(new GUIMessageBox("", "", new Vector2(0.25f, 0.25f), new Point(500, 400)));
return true; return true;
}; };
} }
@@ -2401,7 +2401,7 @@ namespace Barotrauma.Networking
{ {
var banReasonPrompt = new GUIMessageBox( var banReasonPrompt = new GUIMessageBox(
TextManager.Get(ban ? "BanReasonPrompt" : "KickReasonPrompt"), TextManager.Get(ban ? "BanReasonPrompt" : "KickReasonPrompt"),
"", new string[] { TextManager.Get("OK"), TextManager.Get("Cancel") }, 400, 300); "", new string[] { TextManager.Get("OK"), TextManager.Get("Cancel") }, new Vector2(0.25f, 0.2f), new Point(400, 200));
var content = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.6f), banReasonPrompt.InnerFrame.RectTransform, Anchor.Center)); var content = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.6f), banReasonPrompt.InnerFrame.RectTransform, Anchor.Center));
var banReasonBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.3f), content.RectTransform)) var banReasonBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.3f), content.RectTransform))
@@ -334,6 +334,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;
@@ -2043,12 +2043,13 @@ namespace Barotrauma
GUI.AddMessage(GetCharacterEditorTranslation("RagdollReset"), Color.WhiteSmoke, font: GUI.Font); GUI.AddMessage(GetCharacterEditorTranslation("RagdollReset"), Color.WhiteSmoke, font: GUI.Font);
return true; return true;
}; };
Vector2 messageBoxRelSize = new Vector2(0.5f, 0.5f);
int messageBoxWidth = GameMain.GraphicsWidth / 2; int messageBoxWidth = GameMain.GraphicsWidth / 2;
int messageBoxHeight = GameMain.GraphicsHeight / 2; int messageBoxHeight = GameMain.GraphicsHeight / 2;
var saveRagdollButton = new GUIButton(new RectTransform(buttonSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("SaveRagdoll")); var saveRagdollButton = new GUIButton(new RectTransform(buttonSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("SaveRagdoll"));
saveRagdollButton.OnClicked += (button, userData) => saveRagdollButton.OnClicked += (button, userData) =>
{ {
var box = new GUIMessageBox(GetCharacterEditorTranslation("SaveRagdoll"), $"{GetCharacterEditorTranslation("ProvideFileName")}: ", new string[] { TextManager.Get("Cancel"), TextManager.Get("Save") }, messageBoxWidth, messageBoxHeight); var box = new GUIMessageBox(GetCharacterEditorTranslation("SaveRagdoll"), $"{GetCharacterEditorTranslation("ProvideFileName")}: ", new string[] { TextManager.Get("Cancel"), TextManager.Get("Save") }, messageBoxRelSize);
var inputField = new GUITextBox(new RectTransform(new Point(box.Content.Rect.Width, 30), box.Content.RectTransform, Anchor.Center), RagdollParams.Name); var inputField = new GUITextBox(new RectTransform(new Point(box.Content.Rect.Width, 30), box.Content.RectTransform, Anchor.Center), RagdollParams.Name);
box.Buttons[0].OnClicked += (b, d) => box.Buttons[0].OnClicked += (b, d) =>
{ {
@@ -2076,7 +2077,7 @@ namespace Barotrauma
var loadRagdollButton = new GUIButton(new RectTransform(buttonSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("LoadRagdoll")); var loadRagdollButton = new GUIButton(new RectTransform(buttonSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("LoadRagdoll"));
loadRagdollButton.OnClicked += (button, userData) => loadRagdollButton.OnClicked += (button, userData) =>
{ {
var loadBox = new GUIMessageBox(GetCharacterEditorTranslation("LoadRagdoll"), "", new string[] { TextManager.Get("Cancel"), TextManager.Get("Load"), TextManager.Get("Delete") }, messageBoxWidth, messageBoxHeight); var loadBox = new GUIMessageBox(GetCharacterEditorTranslation("LoadRagdoll"), "", new string[] { TextManager.Get("Cancel"), TextManager.Get("Load"), TextManager.Get("Delete") }, messageBoxRelSize);
loadBox.Buttons[0].OnClicked += loadBox.Close; loadBox.Buttons[0].OnClicked += loadBox.Close;
var listBox = new GUIListBox(new RectTransform(new Vector2(0.9f, 0.6f), loadBox.Content.RectTransform, Anchor.TopCenter)); var listBox = new GUIListBox(new RectTransform(new Vector2(0.9f, 0.6f), loadBox.Content.RectTransform, Anchor.TopCenter));
var deleteButton = loadBox.Buttons[2]; var deleteButton = loadBox.Buttons[2];
@@ -2122,7 +2123,7 @@ namespace Barotrauma
var msgBox = new GUIMessageBox( var msgBox = new GUIMessageBox(
TextManager.Get("DeleteDialogLabel"), TextManager.Get("DeleteDialogLabel"),
TextManager.Get("DeleteDialogQuestion").Replace("[file]", selectedFile), TextManager.Get("DeleteDialogQuestion").Replace("[file]", selectedFile),
new string[] { TextManager.Get("Yes"), TextManager.Get("Cancel") }, messageBoxWidth - 100, messageBoxHeight - 100); new string[] { TextManager.Get("Yes"), TextManager.Get("Cancel") });
msgBox.Buttons[0].OnClicked += (b, d) => msgBox.Buttons[0].OnClicked += (b, d) =>
{ {
try try
@@ -2163,7 +2164,7 @@ namespace Barotrauma
var saveAnimationButton = new GUIButton(new RectTransform(buttonSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("SaveAnimation")); var saveAnimationButton = new GUIButton(new RectTransform(buttonSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("SaveAnimation"));
saveAnimationButton.OnClicked += (button, userData) => saveAnimationButton.OnClicked += (button, userData) =>
{ {
var box = new GUIMessageBox(GetCharacterEditorTranslation("SaveAnimation"), string.Empty, new string[] { TextManager.Get("Cancel"), TextManager.Get("Save") }, messageBoxWidth, messageBoxHeight); var box = new GUIMessageBox(GetCharacterEditorTranslation("SaveAnimation"), string.Empty, new string[] { TextManager.Get("Cancel"), TextManager.Get("Save") }, messageBoxRelSize);
var textArea = new GUIFrame(new RectTransform(new Vector2(1, 0.1f), box.Content.RectTransform) { MinSize = new Point(350, 30) }, style: null); var textArea = new GUIFrame(new RectTransform(new Vector2(1, 0.1f), box.Content.RectTransform) { MinSize = new Point(350, 30) }, style: null);
var inputLabel = new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), textArea.RectTransform) { MinSize = new Point(250, 30) }, $"{GetCharacterEditorTranslation("ProvideFileName")}: "); var inputLabel = new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), textArea.RectTransform) { MinSize = new Point(250, 30) }, $"{GetCharacterEditorTranslation("ProvideFileName")}: ");
var inputField = new GUITextBox(new RectTransform(new Vector2(0.5f, 1), textArea.RectTransform, Anchor.TopRight) { MinSize = new Point(100, 30) }, CurrentAnimation.Name); var inputField = new GUITextBox(new RectTransform(new Vector2(0.5f, 1), textArea.RectTransform, Anchor.TopRight) { MinSize = new Point(100, 30) }, CurrentAnimation.Name);
@@ -2211,7 +2212,7 @@ namespace Barotrauma
var loadAnimationButton = new GUIButton(new RectTransform(buttonSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("LoadAnimation")); var loadAnimationButton = new GUIButton(new RectTransform(buttonSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("LoadAnimation"));
loadAnimationButton.OnClicked += (button, userData) => loadAnimationButton.OnClicked += (button, userData) =>
{ {
var loadBox = new GUIMessageBox(GetCharacterEditorTranslation("LoadAnimation"), "", new string[] { TextManager.Get("Cancel"), TextManager.Get("Load"), TextManager.Get("Delete") }, messageBoxWidth, messageBoxHeight); var loadBox = new GUIMessageBox(GetCharacterEditorTranslation("LoadAnimation"), "", new string[] { TextManager.Get("Cancel"), TextManager.Get("Load"), TextManager.Get("Delete") }, messageBoxRelSize);
loadBox.Buttons[0].OnClicked += loadBox.Close; loadBox.Buttons[0].OnClicked += loadBox.Close;
var listBox = new GUIListBox(new RectTransform(new Vector2(0.9f, 0.6f), loadBox.Content.RectTransform)); var listBox = new GUIListBox(new RectTransform(new Vector2(0.9f, 0.6f), loadBox.Content.RectTransform));
var deleteButton = loadBox.Buttons[2]; var deleteButton = loadBox.Buttons[2];
@@ -2273,7 +2274,7 @@ namespace Barotrauma
var msgBox = new GUIMessageBox( var msgBox = new GUIMessageBox(
TextManager.Get("DeleteDialogLabel"), TextManager.Get("DeleteDialogLabel"),
TextManager.Get("DeleteDialogQuestion").Replace("[file]", selectedFile), TextManager.Get("DeleteDialogQuestion").Replace("[file]", selectedFile),
new string[] { TextManager.Get("Yes"), TextManager.Get("Cancel") }, messageBoxWidth - 100, messageBoxHeight - 100); new string[] { TextManager.Get("Yes"), TextManager.Get("Cancel") });
msgBox.Buttons[0].OnClicked += (b, d) => msgBox.Buttons[0].OnClicked += (b, d) =>
{ {
try try
@@ -4425,7 +4426,7 @@ namespace Barotrauma
protected override GUIMessageBox Create() protected override GUIMessageBox Create()
{ {
var box = new GUIMessageBox(GetCharacterEditorTranslation("CreateNewCharacter"), string.Empty, new string[] { TextManager.Get("Cancel"), TextManager.Get("Next") }, GameMain.GraphicsWidth / 2, GameMain.GraphicsHeight); var box = new GUIMessageBox(GetCharacterEditorTranslation("CreateNewCharacter"), string.Empty, new string[] { TextManager.Get("Cancel"), TextManager.Get("Next") }, new Vector2(0.5f, 1.0f));
box.Content.ChildAnchor = Anchor.TopCenter; box.Content.ChildAnchor = Anchor.TopCenter;
box.Content.AbsoluteSpacing = 20; box.Content.AbsoluteSpacing = 20;
int elementSize = 30; int elementSize = 30;
@@ -4541,7 +4542,7 @@ namespace Barotrauma
protected override GUIMessageBox Create() protected override GUIMessageBox Create()
{ {
var box = new GUIMessageBox(GetCharacterEditorTranslation("DefineRagdoll"), string.Empty, new string[] { TextManager.Get("Previous"), TextManager.Get("Create") }, GameMain.GraphicsWidth / 2, GameMain.GraphicsHeight); var box = new GUIMessageBox(GetCharacterEditorTranslation("DefineRagdoll"), string.Empty, new string[] { TextManager.Get("Previous"), TextManager.Get("Create") }, new Vector2(0.5f, 1.0f));
box.Content.ChildAnchor = Anchor.TopCenter; box.Content.ChildAnchor = Anchor.TopCenter;
box.Content.AbsoluteSpacing = 20; box.Content.AbsoluteSpacing = 20;
int elementSize = 30; int elementSize = 30;
@@ -4621,7 +4622,7 @@ namespace Barotrauma
{ {
if (htmlBox == null) if (htmlBox == null)
{ {
htmlBox = new GUIMessageBox(GetCharacterEditorTranslation("LoadHTML"), string.Empty, new string[] { TextManager.Get("Close"), TextManager.Get("Load") }, GameMain.GraphicsWidth / 2, GameMain.GraphicsHeight); htmlBox = new GUIMessageBox(GetCharacterEditorTranslation("LoadHTML"), string.Empty, new string[] { TextManager.Get("Close"), TextManager.Get("Load") }, new Vector2(0.5f, 1.0f));
var element = new GUIFrame(new RectTransform(new Vector2(0.8f, 0.05f), htmlBox.Content.RectTransform), style: null, color: Color.Gray * 0.25f); var element = new GUIFrame(new RectTransform(new Vector2(0.8f, 0.05f), htmlBox.Content.RectTransform), style: null, color: Color.Gray * 0.25f);
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1), element.RectTransform), GetCharacterEditorTranslation("HTMLPath")); new GUITextBlock(new RectTransform(new Vector2(0.5f, 1), element.RectTransform), GetCharacterEditorTranslation("HTMLPath"));
var htmlPathElement = new GUITextBox(new RectTransform(new Vector2(0.5f, 1), element.RectTransform, Anchor.TopRight), $"Content/Characters/{Name}/{Name}.html"); var htmlPathElement = new GUITextBox(new RectTransform(new Vector2(0.5f, 1), element.RectTransform, Anchor.TopRight), $"Content/Characters/{Name}/{Name}.html");
@@ -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)
{ {
@@ -174,7 +174,10 @@ namespace Barotrauma
foreach (LevelObjectPrefab levelObjPrefab in LevelObjectPrefab.List) foreach (LevelObjectPrefab levelObjPrefab in LevelObjectPrefab.List)
{ {
levelObjPrefab.Sprite?.EnsureLazyLoaded(); foreach (Sprite sprite in levelObjPrefab.Sprites)
{
sprite?.EnsureLazyLoaded();
}
} }
pointerLightSource = new LightSource(Vector2.Zero, 1000.0f, Color.White, submarine: null); pointerLightSource = new LightSource(Vector2.Zero, 1000.0f, Color.White, submarine: null);
@@ -251,7 +254,7 @@ namespace Barotrauma
CanBeFocused = false, CanBeFocused = false,
}; };
Sprite sprite = levelObjPrefab.Sprite ?? levelObjPrefab.DeformableSprite?.Sprite; Sprite sprite = levelObjPrefab.Sprites.FirstOrDefault() ?? levelObjPrefab.DeformableSprite?.Sprite;
GUIImage img = new GUIImage(new RectTransform(new Point(paddedFrame.Rect.Height, paddedFrame.Rect.Height - textBlock.Rect.Height), GUIImage img = new GUIImage(new RectTransform(new Point(paddedFrame.Rect.Height, paddedFrame.Rect.Height - textBlock.Rect.Height),
paddedFrame.RectTransform, Anchor.TopCenter), sprite, scaleToFit: true) paddedFrame.RectTransform, Anchor.TopCenter), sprite, scaleToFit: true)
{ {
@@ -289,7 +292,7 @@ namespace Barotrauma
editor.AddCustomContent(commonnessContainer, 1); editor.AddCustomContent(commonnessContainer, 1);
} }
Sprite sprite = levelObjectPrefab.Sprite ?? levelObjectPrefab.DeformableSprite?.Sprite; Sprite sprite = levelObjectPrefab.Sprites.FirstOrDefault() ?? levelObjectPrefab.DeformableSprite?.Sprite;
if (sprite != null) if (sprite != null)
{ {
editor.AddCustomContent(new GUIButton(new RectTransform(new Point(editor.Rect.Width / 2, 20)), editor.AddCustomContent(new GUIButton(new RectTransform(new Point(editor.Rect.Width / 2, 20)),
@@ -297,7 +300,6 @@ namespace Barotrauma
{ {
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{ {
GameMain.SpriteEditorScreen.RefreshLists();
editingSprite = sprite; editingSprite = sprite;
GameMain.SpriteEditorScreen.SelectSprite(editingSprite); GameMain.SpriteEditorScreen.SelectSprite(editingSprite);
return true; return true;
@@ -602,7 +604,7 @@ namespace Barotrauma
public GUIMessageBox Create() public GUIMessageBox Create()
{ {
var box = new GUIMessageBox(TextManager.Get("LevelEditorCreateLevelObj"), string.Empty, var box = new GUIMessageBox(TextManager.Get("LevelEditorCreateLevelObj"), string.Empty,
new string[] { TextManager.Get("Cancel"), TextManager.Get("Done") }, GameMain.GraphicsWidth / 2, (int)(GameMain.GraphicsHeight * 0.8f)); new string[] { TextManager.Get("Cancel"), TextManager.Get("Done") }, new Vector2(0.5f, 0.8f));
box.Content.ChildAnchor = Anchor.TopCenter; box.Content.ChildAnchor = Anchor.TopCenter;
box.Content.AbsoluteSpacing = 20; box.Content.AbsoluteSpacing = 20;
@@ -618,8 +620,8 @@ namespace Barotrauma
var texturePathBox = new GUITextBox(new RectTransform(new Point(listBox.Content.Rect.Width, elementSize), listBox.Content.RectTransform)); var texturePathBox = new GUITextBox(new RectTransform(new Point(listBox.Content.Rect.Width, elementSize), listBox.Content.RectTransform));
foreach (LevelObjectPrefab prefab in LevelObjectPrefab.List) foreach (LevelObjectPrefab prefab in LevelObjectPrefab.List)
{ {
if (prefab.Sprite == null) continue; if (prefab.Sprites.FirstOrDefault() == null) continue;
texturePathBox.Text = Path.GetDirectoryName(prefab.Sprite.FilePath); texturePathBox.Text = Path.GetDirectoryName(prefab.Sprites.FirstOrDefault().FilePath);
break; break;
} }
@@ -676,6 +678,8 @@ namespace Barotrauma
doc.WriteTo(writer); doc.WriteTo(writer);
writer.Flush(); writer.Flush();
} }
// Recreate the prefab so that the sprite loads correctly: TODO: consider a better way to do this
newPrefab = new LevelObjectPrefab(newElement);
break; break;
} }
@@ -44,15 +44,15 @@ namespace Barotrauma
{ {
backgroundVignette = new Sprite("Content/UI/MainMenuVignette.png", Vector2.Zero); backgroundVignette = new Sprite("Content/UI/MainMenuVignette.png", Vector2.Zero);
new GUIImage(new RectTransform(new Vector2(0.35f, 0.2f), Frame.RectTransform, Anchor.BottomRight) new GUIImage(new RectTransform(new Vector2(0.4f, 0.25f), Frame.RectTransform, Anchor.BottomRight)
{ RelativeOffset = new Vector2(0.05f, 0.1f), AbsoluteOffset = new Point(-8, -8) }, { RelativeOffset = new Vector2(0.08f, 0.05f), AbsoluteOffset = new Point(-8, -8) },
style: "TitleText") style: "TitleText")
{ {
Color = Color.Black * 0.5f, Color = Color.Black * 0.5f,
CanBeFocused = false CanBeFocused = false
}; };
titleText = new GUIImage(new RectTransform(new Vector2(0.35f, 0.2f), Frame.RectTransform, Anchor.BottomRight) titleText = new GUIImage(new RectTransform(new Vector2(0.4f, 0.25f), Frame.RectTransform, Anchor.BottomRight)
{ RelativeOffset = new Vector2(0.05f, 0.1f) }, { RelativeOffset = new Vector2(0.08f, 0.05f) },
style: "TitleText"); style: "TitleText");
buttonsParent = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 0.85f), parent: Frame.RectTransform, anchor: Anchor.CenterLeft) buttonsParent = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 0.85f), parent: Frame.RectTransform, anchor: Anchor.CenterLeft)
@@ -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(
@@ -1212,7 +1212,7 @@ namespace Barotrauma
}; };
infoButton.OnClicked += (component, userdata) => infoButton.OnClicked += (component, userdata) =>
{ {
((Submarine)userdata).CreatePreviewWindow(new GUIMessageBox("", "", 550, 600)); ((Submarine)userdata).CreatePreviewWindow(new GUIMessageBox("", "", new Vector2(0.25f, 0.25f), new Point(500, 400)));
return true; return true;
}; };
} }
@@ -2005,7 +2005,8 @@ namespace Barotrauma
return false; return false;
} }
var requestFileBox = new GUIMessageBox(TextManager.Get("DownloadSubLabel"), errorMsg, new string[] { TextManager.Get("Yes"), TextManager.Get("No") }, 400, 300) var requestFileBox = new GUIMessageBox(TextManager.Get("DownloadSubLabel"), errorMsg,
new string[] { TextManager.Get("Yes"), TextManager.Get("No") })
{ {
UserData = "request" + subName UserData = "request" + subName
}; };
@@ -193,50 +193,7 @@ namespace Barotrauma
{ {
Sprite sprite = userData as Sprite; Sprite sprite = userData as Sprite;
if (sprite == null) return false; if (sprite == null) return false;
if (selectedSprites.Any(s => s.Texture != selectedTexture)) SelectSprite(sprite);
{
ResetWidgets();
}
if (Widget.EnableMultiSelect)
{
if (selectedSprites.Contains(sprite))
{
selectedSprites.Remove(sprite);
}
else
{
selectedSprites.Add(sprite);
dirtySprites.Add(sprite);
lastSelected = sprite;
}
}
else
{
selectedSprites.Clear();
selectedSprites.Add(sprite);
dirtySprites.Add(sprite);
lastSelected = sprite;
}
if (selectedTexture != sprite.Texture)
{
textureList.Select(sprite.Texture, autoScroll: false);
UpdateScrollBar(textureList);
}
xmlPathText.Text = string.Empty;
foreach (var s in selectedSprites)
{
texturePathText.Text = s.FilePath;
var element = s.SourceElement;
if (element != null)
{
string xmlPath = element.ParseContentPathFromUri();
if (!xmlPathText.Text.Contains(xmlPath))
{
xmlPathText.Text += "\n" + xmlPath;
}
}
}
xmlPathText.TextColor = Color.LightGray;
return true; return true;
} }
}; };
@@ -610,11 +567,56 @@ namespace Barotrauma
public void SelectSprite(Sprite sprite) public void SelectSprite(Sprite sprite)
{ {
ResetWidgets(); if (!loadedSprites.Contains(sprite))
textureList.Select(sprite.Texture); {
ResetZoom(); loadedSprites.Add(sprite);
selectedSprites.Clear(); RefreshLists();
selectedSprites.Add(sprite); }
if (selectedSprites.Any(s => s.Texture != selectedTexture))
{
ResetWidgets();
}
if (Widget.EnableMultiSelect)
{
if (selectedSprites.Contains(sprite))
{
selectedSprites.Remove(sprite);
}
else
{
selectedSprites.Add(sprite);
dirtySprites.Add(sprite);
lastSelected = sprite;
}
}
else
{
selectedSprites.Clear();
selectedSprites.Add(sprite);
dirtySprites.Add(sprite);
lastSelected = sprite;
}
if (selectedTexture != sprite.Texture)
{
textureList.Select(sprite.Texture, autoScroll: false);
UpdateScrollBar(textureList);
}
xmlPathText.Text = string.Empty;
foreach (var s in selectedSprites)
{
texturePathText.Text = s.FilePath;
var element = s.SourceElement;
if (element != null)
{
string xmlPath = element.ParseContentPathFromUri();
if (!xmlPathText.Text.Contains(xmlPath))
{
xmlPathText.Text += "\n" + xmlPath;
}
}
}
xmlPathText.TextColor = Color.LightGray;
} }
public void RefreshLists() public void RefreshLists()
@@ -659,6 +661,7 @@ namespace Barotrauma
public void ResetZoom() public void ResetZoom()
{ {
if (selectedTexture == null) { return; }
var viewArea = GetViewArea; var viewArea = GetViewArea;
float width = viewArea.Width / (float)selectedTexture.Width; float width = viewArea.Width / (float)selectedTexture.Width;
float height = viewArea.Height / (float)selectedTexture.Height; float height = viewArea.Height / (float)selectedTexture.Height;
@@ -906,7 +906,7 @@ namespace Barotrauma
public void CreateTextPicker(string textTag, ISerializableEntity entity, SerializableProperty property, GUITextBox textBox) public void CreateTextPicker(string textTag, ISerializableEntity entity, SerializableProperty property, GUITextBox textBox)
{ {
var msgBox = new GUIMessageBox("", "", new string[] { TextManager.Get("Cancel") }, width: 300, height: 400); var msgBox = new GUIMessageBox("", "", new string[] { TextManager.Get("Cancel") }, new Vector2(0.2f, 0.5f), new Point(300, 400));
msgBox.Buttons[0].OnClicked = msgBox.Close; msgBox.Buttons[0].OnClicked = msgBox.Close;
var textList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.8f), msgBox.Content.RectTransform, Anchor.TopCenter)) var textList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.8f), msgBox.Content.RectTransform, Anchor.TopCenter))
@@ -36,6 +36,7 @@
<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" />
@@ -454,24 +454,12 @@
<Content Include="$(MSBuildThisFileDirectory)Content\Items\Jobgear\Watchman\watchman_gear.xml"> <Content Include="$(MSBuildThisFileDirectory)Content\Items\Jobgear\Watchman\watchman_gear.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Items\Jobgear\Watchman\watchman_legs_female_2.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Items\Jobgear\Watchman\watchman_legs_male_2.png"> <Content Include="$(MSBuildThisFileDirectory)Content\Items\Jobgear\Watchman\watchman_legs_male_2.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Items\Jobgear\Watchman\watchman_legs_male_3.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Items\Jobgear\Watchman\watchman_torso_female_2.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Items\Jobgear\Watchman\watchman_torso_male_2.png"> <Content Include="$(MSBuildThisFileDirectory)Content\Items\Jobgear\Watchman\watchman_torso_male_2.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Items\Jobgear\Watchman\watchman_torso_male_3.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\BackgroundWalls.png"> <Content Include="$(MSBuildThisFileDirectory)Content\Map\BackgroundWalls.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
@@ -505,6 +493,66 @@
<Content Include="$(MSBuildThisFileDirectory)Content\Map\OutpostWall_C.png"> <Content Include="$(MSBuildThisFileDirectory)Content\Map\OutpostWall_C.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\Zones\Ice\Zone4BaseTexture.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\Zones\Ice\Zone4BaseTextureEdge.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\Zones\Ice\Zone4Plants.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\Zones\Ice\Zone4SurfaceCluster.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\Zones\Ice\Zone4SurfaceDetail.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\Zones\ThermalReefs\Zone2BaseTexture.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\Zones\ThermalReefs\Zone2BaseTextureEdge.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\Zones\ThermalReefs\Zone2Plants.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\Zones\ThermalReefs\Zone2SurfaceCluster.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\Zones\ThermalReefs\Zone2SurfaceDetail.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\Zones\Tundra\Zone3BaseTexture.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\Zones\Tundra\Zone3BaseTextureEdge.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\Zones\Tundra\Zone3Plants.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\Zones\Tundra\Zone3SurfaceCluster.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\Zones\Tundra\Zone3SurfaceDetail.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\Zones\Volcanic\Zone1BaseTexture.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\Zones\Volcanic\Zone1BaseTextureEdge.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\Zones\Volcanic\Zone1Plants.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\Zones\Volcanic\Zone1SurfaceCluster.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\Zones\Volcanic\Zone1SurfaceDetail.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\NPCConversations\NpcConversations_BrazilianPortuguese.xml"> <Content Include="$(MSBuildThisFileDirectory)Content\NPCConversations\NpcConversations_BrazilianPortuguese.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
@@ -527,6 +575,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>
@@ -557,6 +614,9 @@
<Content Include="$(MSBuildThisFileDirectory)Content\Tutorials\TutorialVideos\tutorial_deconstruct.mp4"> <Content Include="$(MSBuildThisFileDirectory)Content\Tutorials\TutorialVideos\tutorial_deconstruct.mp4">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Tutorials\TutorialVideos\tutorial_docking.mp4">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Tutorials\TutorialVideos\tutorial_equip.mp4"> <Content Include="$(MSBuildThisFileDirectory)Content\Tutorials\TutorialVideos\tutorial_equip.mp4">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
@@ -1631,9 +1691,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 +3203,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,6 +3,8 @@ 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
{ {
@@ -12,7 +14,28 @@ namespace Barotrauma
private AIObjectiveManager objectiveManager; private AIObjectiveManager objectiveManager;
private float updateObjectiveTimer; private float sortTimer;
private float crouchRaycastTimer;
private float reportTimer;
private bool shouldCrouch;
const float crouchRaycastInterval = 1;
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>();
private SteeringManager outsideSteering, insideSteering;
public IndoorsSteeringManager PathSteering => insideSteering as IndoorsSteeringManager;
public HumanoidAnimController AnimController => Character.AnimController as HumanoidAnimController;
public override AIObjectiveManager ObjectiveManager
{
get { return objectiveManager; }
}
private bool shouldCrouch; private bool shouldCrouch;
private float crouchRaycastTimer; private float crouchRaycastTimer;
@@ -48,7 +71,6 @@ 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);
@@ -76,35 +98,37 @@ namespace Barotrauma
Character.ClearInputs(); Character.ClearInputs();
objectiveManager.UpdateObjectives(deltaTime); objectiveManager.UpdateObjectives(deltaTime);
if (updateObjectiveTimer > 0.0f) if (sortTimer > 0.0f)
{
sortTimer -= deltaTime;
}
else
{
objectiveManager.SortObjectives();
sortTimer = sortObjectiveInterval;
}
if (reportTimer > 0.0f)
{ {
reportTimer -= deltaTime; reportTimer -= deltaTime;
} }
else else
{ {
objectiveManager.SortObjectives(); if (Character.SpeechImpediment < 100.0f)
updateObjectiveTimer = UpdateObjectiveInterval; {
ReportProblems();
UpdateSpeaking();
}
reportTimer = reportInterval;
} }
if (Character.SpeechImpediment < 100.0f) if (objectiveManager.CurrentObjective == null) { return; }
{
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)
{ {
if (Vector2.DistanceSquared(Character.SimPosition, goTo.Target.SimPosition) > 3 * 3) run = Vector2.DistanceSquared(Character.SimPosition, goTo.Target.SimPosition) > 3 * 3;
{
run = true;
}
}
if (!run)
{
run = objectiveManager.CurrentObjective.ForceRun;
} }
if (run) if (run)
{ {
@@ -161,7 +185,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.CurrentObjective.KeepDivingGearOn; bool shouldKeepTheGearOn = !ObjectiveManager.IsCurrentObjective<AIObjectiveIdle>();
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)
@@ -187,7 +211,7 @@ namespace Barotrauma
} }
} }
} }
if (!(ObjectiveManager.CurrentOrder is AIObjectiveExtinguishFires) && !(ObjectiveManager.CurrentObjective is AIObjectiveExtinguishFire)) if (!ObjectiveManager.IsCurrentObjective<AIObjectiveExtinguishFires>() && !ObjectiveManager.IsCurrentObjective<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))
@@ -196,6 +220,21 @@ 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))
{ {
@@ -228,41 +267,70 @@ namespace Barotrauma
Order newOrder = null; Order newOrder = null;
if (Character.CurrentHull != null) if (Character.CurrentHull != null)
{ {
if (Character.CurrentHull.FireSources.Count > 0) if (AIObjectiveExtinguishFires.IsValidTarget(Character.CurrentHull, Character))
{ {
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportfire"); if (AddTargets<AIObjectiveExtinguishFires, Hull>(Character, Character.CurrentHull))
newOrder = new Order(orderPrefab, Character.CurrentHull, null); {
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportfire");
newOrder = new Order(orderPrefab, Character.CurrentHull, null);
}
} }
if (Character.CurrentHull.ConnectedGaps.Any(g => !g.IsRoomToRoom && g.ConnectedDoor == null && g.Open > 0.0f)) foreach (var gap in Character.CurrentHull.ConnectedGaps)
{ {
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportbreach"); if (AIObjectiveFixLeaks.IsValidTarget(gap, Character))
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 && !c.IsDead && if (c.CurrentHull != Character.CurrentHull) { continue; }
(c.AIController is EnemyAIController || (c.TeamID != Character.TeamID && Character.TeamID != Character.TeamType.FriendlyNPC && c.TeamID != Character.TeamType.FriendlyNPC))) if (AIObjectiveFightIntruders.IsValidTarget(c, Character))
{ {
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportintruders"); if (AddTargets<AIObjectiveFightIntruders, Character>(Character, c))
{
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( Character.Speak(newOrder.GetChatMessage("", Character.CurrentHull?.DisplayName, givingOrderToSelf: false), ChatMessageType.Order);
newOrder.GetChatMessage("", Character.CurrentHull?.DisplayName, givingOrderToSelf: false), ChatMessageType.Order);
} }
} }
} }
@@ -289,6 +357,7 @@ 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.
@@ -336,18 +405,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.AddObjective(new AIObjectiveCombat(Character, attacker, mode, objectiveManager));
} }
} }
else else
{ {
if (delay > 0) if (delay > 0)
{ {
objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode), delay); objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode, objectiveManager), delay);
} }
else else
{ {
objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode)); objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode, objectiveManager));
} }
} }
} }
@@ -358,8 +427,10 @@ 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) Character.Speak(TextManager.Get("DialogAffirmative"), null, 1.0f); if (speak && Character.SpeechImpediment < 100.0f)
{
Character.Speak(TextManager.Get("DialogAffirmative"), null, 1.0f);
}
SetOrderProjSpecific(order); SetOrderProjSpecific(order);
} }
@@ -375,7 +446,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;
@@ -396,7 +467,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 HasDivingGear(Character character) => HasItem(character, "diving", "oxygensource"); public static bool HasDivingMask(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)
{ {
@@ -416,12 +487,9 @@ namespace Barotrauma
{ {
foreach (var c in Character.CharacterList) foreach (var c in Character.CharacterList)
{ {
if (c.TeamID == character.TeamID) if (c.AIController is HumanAIController humanAi && humanAi.IsFriendly(character))
{ {
if (c.AIController is HumanAIController humanAi) humanAi.RefreshHullSafety(hull);
{
humanAi.RefreshHullSafety(hull);
}
} }
} }
} }
@@ -438,13 +506,86 @@ 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.CurrentObjective is AIObjectiveExtinguishFire || ObjectiveManager.CurrentOrder is AIObjectiveExtinguishFires; bool ignoreFire = ObjectiveManager.IsCurrentObjective<AIObjectiveExtinguishFires>() || ObjectiveManager.IsCurrentObjective<AIObjectiveExtinguishFire>();
bool ignoreWater = HasDivingSuit(Character); bool ignoreWater = HasDivingSuit(Character);
bool ignoreOxygen = ignoreWater || HasDivingGear(Character); bool ignoreOxygen = ignoreWater || HasDivingMask(Character);
bool ignoreEnemies = ObjectiveManager.CurrentObjective is AIObjectiveCombat || ObjectiveManager.CurrentOrder is AIObjectiveCombat; bool ignoreEnemies = ObjectiveManager.IsCurrentObjective<AIObjectiveFightIntruders>();
return GetHullSafety(hull, Character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies); return GetHullSafety(hull, Character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies);
} }
@@ -462,7 +603,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)
@@ -471,7 +612,8 @@ 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 bool IsFriendly(Character other) => other.TeamID == Character.TeamID && other.SpeciesName == Character.SpeciesName; public static bool IsFriendly(Character me, Character other) => other.TeamID == me.TeamID && other.SpeciesName == me.SpeciesName;
} }
} }
@@ -287,7 +287,8 @@ namespace Barotrauma
character.AnimController.Anim = AnimController.Animation.None; character.AnimController.Anim = AnimController.Animation.None;
character.SelectedConstruction = null; character.SelectedConstruction = null;
} }
if (Vector2.DistanceSquared(pos, currentPath.CurrentNode.SimPosition) < MathUtils.Pow(collider.radius * 3, 2)) 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 * 2 * multiplier, 2))
{ {
currentPath.SkipToNextNode(); currentPath.SkipToNextNode();
} }
@@ -296,13 +297,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));
@@ -350,6 +351,8 @@ 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;
@@ -360,21 +363,19 @@ namespace Barotrauma
} }
else else
{ {
WayPoint node = null;
WayPoint nextNode = null;
if (i == 0) if (i == 0)
{ {
node = currentPath.CurrentNode; currentWaypoint = currentPath.CurrentNode;
nextNode = currentPath.NextNode; nextWaypoint = currentPath.NextNode;
} }
else else
{ {
node = currentPath.PrevNode; currentWaypoint = currentPath.PrevNode;
nextNode = currentPath.CurrentNode; nextWaypoint = currentPath.CurrentNode;
} }
if (node?.ConnectedDoor == null) continue; if (currentWaypoint?.ConnectedDoor == null) { continue; }
if (nextNode == null) if (nextWaypoint == 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
@@ -382,21 +383,21 @@ namespace Barotrauma
} }
else else
{ {
door = node.ConnectedGap.ConnectedDoor; door = currentWaypoint.ConnectedGap.ConnectedDoor;
if (door.LinkedGap.IsHorizontal) if (door.LinkedGap.IsHorizontal)
{ {
int currentDir = Math.Sign(nextNode.WorldPosition.X - door.Item.WorldPosition.X); int currentDir = Math.Sign(nextWaypoint.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(nextNode.WorldPosition.Y - door.Item.WorldPosition.Y); int currentDir = Math.Sign(nextWaypoint.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)
@@ -422,22 +423,29 @@ namespace Barotrauma
buttonPressCooldown = ButtonPressInterval; buttonPressCooldown = ButtonPressInterval;
break; break;
} }
else if (closestButton != null)
closestButton.Item.TryInteract(character, false, true, false);
buttonPressCooldown = ButtonPressInterval;
break;
}
else
{
if (!door.HasRequiredItems(character, false) && shouldBeOpen)
{ {
currentPath.Unreachable = true; if (Vector2.DistanceSquared(closestButton.Item.WorldPosition, character.WorldPosition) < MathUtils.Pow(closestButton.Item.InteractDistance * 2, 2))
return; {
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;
}
} }
}
door.Item.TryInteract(character, false, true, true); else if (shouldBeOpen)
buttonPressCooldown = ButtonPressInterval; {
break; currentPath.Unreachable = true;
return;
} }
} }
} }
@@ -445,32 +453,38 @@ 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)
{ {
if (nextNode.Waypoint.ConnectedDoor == null) var door = nextNode.Waypoint.ConnectedDoor;
if (door == null)
{ {
penalty = 100.0f; penalty = 100.0f;
} }
else if (!canBreakDoors) else
{ {
//door closed and the character can't open doors -> node can't be traversed if (!CanAccessDoor(door, button =>
if (!canOpenDoors || character.LockHands) { return null; } {
// Ignore buttons that are on the wrong side of the door
var doorButtons = nextNode.Waypoint.ConnectedDoor.Item.GetConnectedComponents<Controller>(); if (door.IsHorizontal)
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;
}))
{ {
if (!nextNode.Waypoint.ConnectedDoor.HasRequiredItems(character, false)) { return null; } 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,21 +12,6 @@ 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?
@@ -62,6 +47,7 @@ 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;
@@ -94,18 +80,6 @@ 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)
@@ -132,29 +106,40 @@ namespace Barotrauma
subObjectives.Add(objective); subObjectives.Add(objective);
} }
public void SortSubObjectives(AIObjectiveManager objectiveManager) public void SortSubObjectives()
{ {
if (subObjectives.None()) { return; } if (subObjectives.None()) { return; }
subObjectives.Sort((x, y) => y.GetPriority(objectiveManager).CompareTo(x.GetPriority(objectiveManager))); subObjectives.Sort((x, y) => y.GetPriority().CompareTo(x.GetPriority()));
CurrentSubObjective = SubObjectives.First(); if (ConcurrentObjectives)
CurrentSubObjective.SortSubObjectives(objectiveManager); {
subObjectives.ForEach(so => so.SortSubObjectives());
}
else
{
subObjectives.First().SortSubObjectives();
}
} }
public virtual float GetPriority(AIObjectiveManager objectiveManager) => Priority; public virtual float GetPriority() => Priority * PriorityModifier;
public virtual void Update(AIObjectiveManager objectiveManager, float deltaTime) public virtual void Update(float deltaTime)
{ {
var subObjective = objectiveManager.CurrentObjective?.CurrentSubObjective;
if (objectiveManager.CurrentOrder == this) if (objectiveManager.CurrentOrder == this)
{ {
Priority = AIObjectiveManager.OrderPriority; Priority = AIObjectiveManager.OrderPriority;
} }
else if (objectiveManager.CurrentObjective == this || subObjective == this) else if (objectiveManager.WaitTimer <= 0)
{ {
Priority += Devotion * deltaTime; if (objectiveManager.CurrentObjective != null)
{
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>
@@ -174,13 +159,54 @@ namespace Barotrauma
} }
} }
protected virtual bool ShouldInterruptSubObjective(AIObjective subObjective) => false; /// <summary>
public virtual void OnSelected() { } /// Checks if the objective already is created and added in subobjectives. If not, creates it.
/// 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);
} }
} }
@@ -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 readonly IEnumerable<PowerContainer> batteryList; private IEnumerable<PowerContainer> batteryList;
public AIObjectiveChargeBatteries(Character character, string option) : base(character, option) public AIObjectiveChargeBatteries(Character character, AIObjectiveManager objectiveManager, string option, float priorityModifier)
{ : 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,37 +22,57 @@ namespace Barotrauma
protected override void FindTargets() protected override void FindTargets()
{ {
foreach (Item item in Item.ItemList) base.FindTargets();
{ 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
{ {
targets.Sort((x, y) => x.ChargePercentage.CompareTo(y.ChargePercentage)); if (battery.RechargeRatio <= 0) { return false; }
}
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 bool Filter(PowerContainer battery) => true; protected override IEnumerable<PowerContainer> GetList()
protected override float Average(PowerContainer battery) => 100 - battery.ChargePercentage; {
protected override IEnumerable<PowerContainer> GetList() => batteryList; if (batteryList == null)
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,15 +1,17 @@
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 override bool KeepDivingGearOn => true; public bool useCoolDown = true;
const float CoolDown = 10.0f; const float CoolDown = 10.0f;
@@ -23,7 +25,14 @@ namespace Barotrauma
{ {
_weapon = value; _weapon = value;
_weaponComponent = null; _weaponComponent = null;
reloadWeaponObjective = null; if (reloadWeaponObjective != null)
{
if (subObjectives.Contains(reloadWeaponObjective))
{
subObjectives.Remove(reloadWeaponObjective);
}
reloadWeaponObjective = null;
}
} }
} }
private ItemComponent _weaponComponent; private ItemComponent _weaponComponent;
@@ -41,29 +50,41 @@ namespace Barotrauma
return _weaponComponent; return _weaponComponent;
} }
} }
private AIObjectiveContainItem reloadWeaponObjective;
private Hull retreatTarget;
private AIObjectiveGoTo retreatObjective;
private AIObjectiveFindSafety findSafety;
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 AIObjectiveGoTo retreatObjective;
private AIObjectiveGoTo followTargetObjective;
private Hull retreatTarget;
private float coolDownTimer; private float coolDownTimer;
public enum CombatMode public enum CombatMode
{ {
Defensive, Defensive,
Offensive, // Not implemented Offensive,
Retreat Retreat
} }
public CombatMode Mode { get; private set; } public CombatMode Mode { get; private set; }
public AIObjectiveCombat(Character character, Character enemy, CombatMode mode) : base(character, "") public AIObjectiveCombat(Character character, Character enemy, CombatMode mode, AIObjectiveManager objectiveManager, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier)
{ {
Enemy = enemy; Enemy = enemy;
coolDownTimer = CoolDown; coolDownTimer = CoolDown;
findSafety = HumanAIController.ObjectiveManager.GetObjective<AIObjectiveFindSafety>(); findSafety = objectiveManager.GetObjective<AIObjectiveFindSafety>();
findSafety.Priority = 0; if (findSafety != null)
findSafety.unreachable.Clear(); {
findSafety.Priority = 0;
findSafety.unreachable.Clear();
}
Mode = mode; Mode = mode;
if (Enemy == null) if (Enemy == null)
{ {
@@ -71,14 +92,51 @@ 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)
{ {
coolDownTimer -= deltaTime; if (useCoolDown)
{
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)) if (Weapon != null && !character.Inventory.Items.Contains(_weapon) || _weaponComponent != null && !_weaponComponent.HasRequiredContainedItems(false))
{ {
Weapon = null; Weapon = null;
} }
@@ -90,40 +148,65 @@ namespace Barotrauma
{ {
Mode = CombatMode.Retreat; Mode = CombatMode.Retreat;
} }
else if (Equip(deltaTime)) if (Equip())
{ {
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 System.NotImplementedException(); throw new NotImplementedException();
} }
} }
private Item GetWeapon() private Item GetWeapon()
{ {
rangedWeapons.Clear();
meleeWeapons.Clear();
adHocWeapons.Clear();
Item weapon = null;
_weaponComponent = null; _weaponComponent = null;
var weapon = character.Inventory.FindItemByTag("weapon"); foreach (var item in character.Inventory.Items)
if (weapon == null)
{ {
foreach (var item in character.Inventory.Items) if (item == null) { continue; }
foreach (var component in item.Components)
{ {
if (item == null) { continue; } if (component is RangedWeapon rw)
foreach (var component in item.Components)
{ {
if (component is MeleeWeapon || component is RangedWeapon) if (rw.HasRequiredContainedItems(false))
{ {
return item; rangedWeapons.Add(rw);
} }
}
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)
{ {
@@ -133,7 +216,10 @@ namespace Barotrauma
{ {
if (statusEffect.Afflictions.Any()) if (statusEffect.Afflictions.Any())
{ {
return item; if (component.HasRequiredContainedItems(false))
{
adHocWeapons.Add(item);
}
} }
} }
} }
@@ -141,6 +227,20 @@ 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;
} }
@@ -153,10 +253,9 @@ namespace Barotrauma
Weapon.Drop(character); Weapon.Drop(character);
} }
} }
return true;
} }
private bool Equip(float deltaTime) private bool Equip()
{ {
if (!character.SelectedItems.Contains(Weapon)) if (!character.SelectedItems.Contains(Weapon))
{ {
@@ -167,8 +266,7 @@ namespace Barotrauma
} }
else else
{ {
//couldn't equip the item, escape Mode = CombatMode.Retreat;
//Abandon(deltaTime);
return false; return false;
} }
} }
@@ -177,18 +275,52 @@ 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 });
} }
if (retreatTarget != null) TryAddSubObjective(ref retreatObjective, () => new AIObjectiveGoTo(retreatTarget, character, objectiveManager, false, true));
}
private void Engage(float deltaTime)
{
retreatTarget = null;
if (retreatObjective != null)
{ {
if (retreatObjective == null || retreatObjective.Target != retreatTarget) if (subObjectives.Contains(retreatObjective))
{ {
retreatObjective = new AIObjectiveGoTo(retreatTarget, character, false, true); subObjectives.Remove(retreatObjective);
} }
retreatObjective.TryComplete(deltaTime); retreatObjective = null;
} }
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)
@@ -201,43 +333,47 @@ 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)
{ {
if (reloadWeaponObjective == null) TryAddSubObjective(ref reloadWeaponObjective,
{ constructor: () => new AIObjectiveContainItem(character, requiredItem.Identifiers, Weapon.GetComponent<ItemContainer>(), objectiveManager),
reloadWeaponObjective = new AIObjectiveContainItem(character, requiredItem.Identifiers, Weapon.GetComponent<ItemContainer>()); onAbandon: () =>
} {
SteeringManager.Reset();
Mode = CombatMode.Retreat;
});
} }
} }
} }
if (reloadWeaponObjective != null) return reloadWeaponObjective == null || reloadWeaponObjective.IsCompleted();
{
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)
{ {
character.SetInput(InputType.Aim, false, true); 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.SelectedConstruction == null)
{
character.SetInput(InputType.Aim, false, true);
}
} }
if (WeaponComponent is MeleeWeapon meleeWeapon) if (WeaponComponent is MeleeWeapon meleeWeapon)
{ {
if (Vector2.DistanceSquared(character.Position, Enemy.Position) <= meleeWeapon.Range * meleeWeapon.Range) if (squaredDistance <= meleeWeapon.Range * meleeWeapon.Range)
{ {
character.SetInput(InputType.Shoot, false, true); character.SetInput(InputType.Shoot, false, true);
Weapon.Use(deltaTime, character); Weapon.Use(deltaTime, character);
@@ -247,7 +383,7 @@ namespace Barotrauma
{ {
if (WeaponComponent is RepairTool repairTool) if (WeaponComponent is RepairTool repairTool)
{ {
if (Vector2.DistanceSquared(character.Position, Enemy.Position) > repairTool.Range * repairTool.Range) { return; } if (squaredDistance > 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)
{ {
@@ -255,7 +391,8 @@ namespace Barotrauma
{ {
myBodies = character.AnimController.Limbs.Select(l => l.body.FarseerBody); myBodies = character.AnimController.Limbs.Select(l => l.body.FarseerBody);
} }
var pickedBody = Submarine.PickBody(character.SimPosition, Enemy.SimPosition, myBodies); var collisionCategories = Physics.CollisionCharacter | Physics.CollisionWall;
var pickedBody = Submarine.PickBody(character.SimPosition, Enemy.SimPosition, myBodies, collisionCategories);
if (pickedBody != null) if (pickedBody != null)
{ {
Character target = null; Character target = null;
@@ -277,34 +414,6 @@ 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;
@@ -9,15 +9,9 @@ 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
@@ -27,14 +21,12 @@ 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[] itemIdentifiers, ItemContainer container) public AIObjectiveContainItem(Character character, string itemIdentifier, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1)
: base (character, "") : this(character, new string[] { itemIdentifier }, container, objectiveManager, priorityModifier) { }
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++)
@@ -51,22 +43,10 @@ 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))) containedItemCount++; if (item != null && itemIdentifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id)))
}
return containedItemCount >= MinContainedAmount;
}
public override bool CanBeCompleted
{
get
{
if (goToObjective != null)
{ {
containedItemCount++; containedItemCount++;
} }
return getItemObjective == null || getItemObjective.CanBeCompleted;
} }
return containedItemCount >= MinContainedAmount; return containedItemCount >= MinContainedAmount;
} }
@@ -88,17 +68,16 @@ 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)
{ {
getItemObjective = new AIObjectiveGetItem(character, itemIdentifiers) TryAddSubObjective(ref getItemObjective, () =>
{ new AIObjectiveGetItem(character, itemIdentifiers, objectiveManager)
GetItemPriority = GetItemPriority, {
ignoredContainerIdentifiers = ignoredContainerIdentifiers GetItemPriority = GetItemPriority,
}; ignoredContainerIdentifiers = ignoredContainerIdentifiers
AddSubObjective(getItemObjective); });
return; return;
} }
if (container.Item.ParentInventory == character.Inventory) if (container.Item.ParentInventory == character.Inventory)
@@ -115,7 +94,8 @@ namespace Barotrauma
} }
else else
{ {
if (container.Item.CurrentHull != character.CurrentHull || (Vector2.Distance(character.Position, container.Item.Position) > container.Item.InteractDistance && !container.Item.IsInsideTrigger(character.WorldPosition))) if (container.Item.CurrentHull != character.CurrentHull ||
(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;
@@ -127,14 +107,15 @@ namespace Barotrauma
public override bool IsDuplicate(AIObjective otherObjective) public override bool IsDuplicate(AIObjective otherObjective)
{ {
AIObjectiveContainItem objective = otherObjective as AIObjectiveContainItem; if (!(otherObjective is AIObjectiveContainItem objective)) { return false; }
if (objective == null) return false; if (objective.container != container) { return false; }
if (objective.container != container) return false; if (objective.itemIdentifiers.Length != itemIdentifiers.Length) { 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]) return false; if (objective.itemIdentifiers[i] != itemIdentifiers[i])
{
return false;
}
} }
return true; return true;
} }
@@ -8,78 +8,53 @@ namespace Barotrauma
{ {
public override string DebugTag => "decontain item"; public override string DebugTag => "decontain item";
//can either be a tag or an identifier
private string[] itemIdentifiers;
private ItemContainer container;
private bool isCompleted;
public Func<Item, float> GetItemPriority; public Func<Item, float> GetItemPriority;
private AIObjectiveGetItem getItemObjective; //can either be a tag or an identifier
private AIObjectiveGoTo goToObjective; private readonly string[] itemIdentifiers;
private Item targetItem; private readonly ItemContainer container;
private readonly Item targetItem;
public AIObjectiveDecontainItem(Character character, Item targetItem, ItemContainer container) private AIObjectiveGoTo goToObjective;
: base(character, "") private bool isCompleted;
public AIObjectiveDecontainItem(Character character, Item targetItem, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier)
{ {
this.targetItem = targetItem; this.targetItem = targetItem;
this.container = container; this.container = container;
} }
public AIObjectiveDecontainItem(Character character, string itemIdentifier, ItemContainer container) public AIObjectiveDecontainItem(Character character, string itemIdentifier, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1)
: this(character, new string[] { itemIdentifier }, container) : this(character, new string[] { itemIdentifier }, container, objectiveManager, priorityModifier) { }
{
}
public AIObjectiveDecontainItem(Character character, string[] itemIdentifiers, ItemContainer container) public AIObjectiveDecontainItem(Character character, string[] itemIdentifiers, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1)
: base(character, "") : 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++)
{ {
itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant(); itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant();
} }
this.container = container; this.container = container;
} }
public override bool IsCompleted() public override bool IsCompleted() => isCompleted;
{
return isCompleted;
}
public override bool CanBeCompleted public override float GetPriority()
{
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)
{ {
@@ -88,7 +63,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; }
} }
} }
} }
@@ -96,38 +71,32 @@ 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.Distance(character.Position, container.Item.Position) > container.Item.InteractDistance if (Vector2.DistanceSquared(character.Position, container.Item.Position) > MathUtils.Pow(container.Item.InteractDistance, 2) && !container.Item.IsInsideTrigger(character.WorldPosition))
&& !container.Item.IsInsideTrigger(character.WorldPosition))
{ {
goToObjective = new AIObjectiveGoTo(container.Item, character); TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(container.Item, character, objectiveManager));
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)
{ {
AIObjectiveDecontainItem decontainItem = otherObjective as AIObjectiveDecontainItem; if (!(otherObjective is AIObjectiveDecontainItem decontainItem)) { return false; }
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;
} }
@@ -135,7 +104,6 @@ namespace Barotrauma
{ {
return decontainItem.targetItem == targetItem; return decontainItem.targetItem == targetItem;
} }
return false; return false;
} }
} }
@@ -3,6 +3,7 @@ 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
{ {
@@ -10,125 +11,117 @@ 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 KeepDivingGearOn => true; public override bool ConcurrentObjectives => true;
private Hull targetHull; private readonly 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) : base(character, "") public AIObjectiveExtinguishFire(Character character, Hull targetHull, AIObjectiveManager objectiveManager, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier)
{ {
this.targetHull = targetHull; this.targetHull = targetHull;
} }
public override float GetPriority(AIObjectiveManager objectiveManager) public override float GetPriority()
{ {
if (gotoObjective != null && !gotoObjective.CanBeCompleted) { return 0; } if (Character.CharacterList.Any(c => c.CurrentHull == targetHull && !HumanAIController.IsFriendly(c))) { 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 severityFactor = MathHelper.Lerp(0, 1, MathHelper.Clamp(targetHull.FireSources.Sum(fs => fs.Size.X) / targetHull.Size.X, 0, 1)); float severity = AIObjectiveExtinguishFires.GetFireSeverity(targetHull);
return MathHelper.Clamp(Priority * severityFactor * distanceFactor, 0, 100); float severityFactor = MathHelper.Lerp(0, 1, severity / 100);
float devotion = Math.Min(Priority, 10) / 100;
return MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + severityFactor * distanceFactor, 0, 1));
} }
public override bool IsCompleted() public override bool IsCompleted() => targetHull.FireSources.None();
{
return targetHull.FireSources.Count == 0;
}
public override bool IsDuplicate(AIObjective otherObjective) public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveExtinguishFire otherExtinguishFire && otherExtinguishFire.targetHull == targetHull;
{
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))
{ {
if (getExtinguisherObjective == null) TryAddSubObjective(ref getExtinguisherObjective, () =>
{ {
character.Speak(TextManager.Get("DialogFindExtinguisher"), null, 2.0f, "findextinguisher", 30.0f); character.Speak(TextManager.Get("DialogFindExtinguisher"), null, 2.0f, "findextinguisher", 30.0f);
getExtinguisherObjective = new AIObjectiveGetItem(character, "extinguisher", true); return new AIObjectiveGetItem(character, "extinguisher", objectiveManager, equip: true);
} });
else
{
getExtinguisherObjective.TryComplete(deltaTime);
}
return;
} }
else
var extinguisher = extinguisherItem.GetComponent<RepairTool>();
if (extinguisher == null)
{ {
DebugConsole.ThrowError("AIObjectiveExtinguishFire failed - the item \"" + extinguisherItem + "\" has no RepairTool component but is tagged as an extinguisher"); var extinguisher = extinguisherItem.GetComponent<RepairTool>();
return; if (extinguisher == null)
}
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; #if DEBUG
if (useExtinquisherTimer > 2.0f) DebugConsole.ThrowError("AIObjectiveExtinguishFire failed - the item \"" + extinguisherItem + "\" has no RepairTool component but is tagged as an extinguisher");
#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 = 0.0f; useExtinquisherTimer += deltaTime;
} 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))
{ {
character.Speak(TextManager.Get("DialogPutOutFire").Replace("[roomname]", targetHull.Name), null, 0, "putoutfire", 10.0f); useExtinquisherTimer = 0.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)
if (move)
{
//go to the first firesource
if (gotoObjective == null || !gotoObjective.CanBeCompleted || gotoObjective.IsCompleted())
{ {
gotoObjective = new AIObjectiveGoTo(ConvertUnits.ToSimUnits(fs.Position), character); //go to the first firesource
} TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(fs.Position), character, objectiveManager));
else
{
gotoObjective.TryComplete(deltaTime);
} }
break;
} }
break;
} }
} }
} }
@@ -1,62 +1,44 @@
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 : AIObjective class AIObjectiveExtinguishFires : AIObjectiveLoop<Hull>
{ {
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;
private Dictionary<Hull, AIObjectiveExtinguishFire> extinguishObjectives = new Dictionary<Hull, AIObjectiveExtinguishFire>(); public AIObjectiveExtinguishFires(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
public AIObjectiveExtinguishFires(Character character) : base(character, "") { } protected override void FindTargets()
public override float GetPriority(AIObjectiveManager objectiveManager)
{ {
if (character.Submarine == null) { return 0; } base.FindTargets();
int fireCount = character.Submarine.GetHulls(true).Sum(h => h.FireSources.Count); if (targets.None() && objectiveManager.CurrentOrder == this)
if (objectiveManager.CurrentOrder == this && fireCount > 0)
{ {
return AIObjectiveManager.OrderPriority; character.Speak(TextManager.Get("DialogNoFire"), null, 3.0f, "nofire", 30.0f);
} }
return MathHelper.Clamp(fireCount * 20, 0, 100);
} }
public override bool IsCompleted() => false; protected override bool Filter(Hull hull) => IsValidTarget(hull, character);
public override bool CanBeCompleted => true;
public override bool IsDuplicate(AIObjective otherObjective) protected override float TargetEvaluation() => objectiveManager.CurrentObjective == this ? 100 : targets.Sum(t => GetFireSeverity(t));
{
return otherObjective is AIObjectiveExtinguishFires;
}
protected override void Act(float deltaTime) 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)
{ {
SyncRemovedObjectives(extinguishObjectives, Hull.hullList); if (hull == null) { return false; }
if (character.Submarine == null) { return; } if (hull.FireSources.None()) { return false; }
foreach (Hull hull in Hull.hullList) if (hull.Submarine == null) { return false; }
{ if (hull.Submarine.TeamID != character.TeamID) { return false; }
if (hull.FireSources.None()) { continue; } if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(hull, true)) { return false; }
if (hull.Submarine == null) { continue; } return true;
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);
}
} }
} }
} }
@@ -29,7 +29,7 @@ namespace Barotrauma
protected override IEnumerable<Character> GetList() => Character.CharacterList; protected override IEnumerable<Character> GetList() => Character.CharacterList;
protected override AIObjective ObjectiveConstructor(Character target) => new AIObjectiveCombat(character, target, AIObjectiveCombat.CombatMode.Offensive, objectiveManager, PriorityModifier); protected override AIObjective ObjectiveConstructor(Character target) => new AIObjectiveCombat(character, target, AIObjectiveCombat.CombatMode.Offensive, objectiveManager, PriorityModifier) { useCoolDown = false };
protected override float TargetEvaluation() protected override float TargetEvaluation()
{ {
@@ -1,6 +1,7 @@
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
{ {
@@ -9,8 +10,10 @@ 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 AIObjective subObjective; private readonly string gearTag;
private string gearTag;
private AIObjectiveGetItem getDivingGear;
private AIObjectiveContainItem getOxygen;
public override bool IsCompleted() public override bool IsCompleted()
{ {
@@ -21,15 +24,16 @@ 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 AIObjectiveFindDivingGear(Character character, bool needDivingSuit) : base(character, "") public override float GetPriority() => MathHelper.Clamp(100 - character.OxygenAvailable, 0, 100);
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";
} }
@@ -42,16 +46,23 @@ 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);
subObjective = new AIObjectiveGetItem(character, gearTag, true); return new AIObjectiveGetItem(character, gearTag, objectiveManager, equip: 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) { return; } if (containedItems == null)
//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; }
@@ -59,26 +70,16 @@ 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))
//we've got an oxygen source inside the mask/suit, all good
return;
}
}
if (!(subObjective is AIObjectiveContainItem) || subObjective.IsCompleted())
{ {
character.Speak(TextManager.Get("DialogGetOxygenTank"), null, 0, "getoxygentank", 30.0f); TryAddSubObjective(ref getOxygen, () =>
subObjective = new AIObjectiveContainItem(character, new string[] { "oxygentank", "oxygensource" }, item.GetComponent<ItemContainer>()); {
character.Speak(TextManager.Get("DialogGetOxygenTank"), null, 0, "getoxygentank", 30.0f);
return new AIObjectiveContainItem(character, new string[] { "oxygentank", "oxygensource" }, item.GetComponent<ItemContainer>(), objectiveManager);
});
} }
} }
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;
} }
} }
@@ -10,7 +10,6 @@ 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;
@@ -18,7 +17,7 @@ namespace Barotrauma
const float SearchHullInterval = 3.0f; const float SearchHullInterval = 3.0f;
const float clearUnreachableInterval = 30; const float clearUnreachableInterval = 30;
public readonly List<Hull> unreachable = new List<Hull>(); public readonly HashSet<Hull> unreachable = new HashSet<Hull>();
private float currenthullSafety; private float currenthullSafety;
private float unreachableClearTimer; private float unreachableClearTimer;
@@ -27,49 +26,15 @@ namespace Barotrauma
private AIObjectiveGoTo goToObjective; private AIObjectiveGoTo goToObjective;
private AIObjectiveFindDivingGear divingGearObjective; private AIObjectiveFindDivingGear divingGearObjective;
public AIObjectiveFindSafety(Character character) : base(character, "") { } public AIObjectiveFindSafety(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
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;
@@ -79,56 +44,86 @@ 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)
{ {
var bestHull = FindBestHull(); currenthullSafety = 0;
if (bestHull != null && bestHull != currentHull) Priority = 100;
{ 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)
{ {
goToObjective.TryComplete(deltaTime); Priority -= priorityDecrease * deltaTime;
if (!goToObjective.CanBeCompleted)
{
if (!unreachable.Contains(goToObjective.Target))
{
unreachable.Add(goToObjective.Target as Hull);
}
goToObjective = null;
HumanAIController.ObjectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
//SteeringManager.SteeringWander();
}
} }
else if (currentHull != null) 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.
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)
{
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));
}
}
if (goToObjective != null) { return; }
if (currentHull == null) { return; }
//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;
@@ -136,17 +131,15 @@ 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;
@@ -154,7 +147,6 @@ 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
@@ -251,36 +243,5 @@ 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);
}
}
} }
} }
@@ -3,28 +3,24 @@ 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;
private readonly Gap leak; public Gap Leak { get; private set; }
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) : base (character, "") public AIObjectiveFixLeak(Gap leak, Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base (character, objectiveManager, priorityModifier)
{ {
Leak = leak; Leak = leak;
} }
@@ -34,17 +30,16 @@ namespace Barotrauma
return Leak.Open <= 0.0f || Leak.Removed; return Leak.Open <= 0.0f || Leak.Removed;
} }
public override bool CanBeCompleted => !abandon && base.CanBeCompleted; public override float GetPriority()
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 leakSize = (leak.IsHorizontal ? leak.Rect.Height : leak.Rect.Width) * Math.Max(leak.Open, 0.1f); float dist = Math.Abs(character.WorldPosition.X - Leak.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - Leak.WorldPosition.Y) * 2.0f;
float distanceFactor = MathHelper.Lerp(1, 0.25f, MathUtils.InverseLerp(0, 10000, dist));
float dist = Vector2.DistanceSquared(character.SimPosition, leak.SimPosition); float severity = AIObjectiveFixLeaks.GetLeakSeverity(Leak);
dist = Math.Max(dist / 100.0f, 1.0f); float max = Math.Min((AIObjectiveManager.OrderPriority - 1), 90);
return Math.Min(leakSize / dist, 40.0f); float devotion = Math.Min(Priority, 10) / 100;
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)
@@ -55,59 +50,44 @@ namespace Barotrauma
protected override void Act(float deltaTime) protected override void Act(float deltaTime)
{ {
if (!leak.IsRoomToRoom) if (!Leak.IsRoomToRoom)
{ {
if (findDivingGear == null) if (!HumanAIController.HasDivingSuit(character))
{ {
findDivingGear = new AIObjectiveFindDivingGear(character, true); TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, true, objectiveManager));
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)
{ {
AddSubObjective(new AIObjectiveGetItem(character, "weldingtool", true)); TryAddSubObjective(ref getWeldingTool, () => new AIObjectiveGetItem(character, "weldingtool", objectiveManager, true));
return; return;
} }
else else
{ {
var containedItems = weldingTool.ContainedItems; var containedItems = weldingTool.ContainedItems;
if (containedItems == null) return; if (containedItems == null)
var fuelTank = containedItems.FirstOrDefault(i => i.HasTag("weldingfueltank") && i.Condition > 0.0f);
if (fuelTank == null)
{ {
AddSubObjective(new AIObjectiveContainItem(character, "weldingfueltank", weldingTool.GetComponent<ItemContainer>())); #if DEBUG
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))
{ {
sightLimb = character.AnimController.GetLimb(LimbType.RightHand); if (containedItem == null) { continue; }
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))
{ {
@@ -115,45 +95,31 @@ 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
{ {
if (gotoObjective != null) TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
{
// 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);
}
}
} }
if (gotoObjective == null || gotoObjective.IsCompleted())
{
if (operateObjective == null)
{
operateObjective = new AIObjectiveOperateItem(repairTool, character, "", true, leak);
AddSubObjective(operateObjective);
}
else if (!subObjectives.Contains(operateObjective))
{
operateObjective = null;
}
}
} }
private Vector2 GetStandPosition() private Vector2 GetStandPosition()
@@ -9,38 +9,28 @@ 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) : base (character, "") { } public AIObjectiveFixLeaks(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
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();
targets.Sort((x, y) => GetGapFixPriority(y).CompareTo(GetGapFixPriority(x))); if (targets.None() && objectiveManager.CurrentOrder == this)
{
character.Speak(TextManager.Get("DialogNoLeaks"), null, 3.0f, "noleaks", 30.0f);
}
} }
protected override bool Filter(Gap gap) protected override bool Filter(Gap gap) => IsValidTarget(gap, character);
public static float GetLeakSeverity(Gap leak)
{ {
bool ignore = ignoreList.Contains(gap) || gap.ConnectedWall == null || gap.ConnectedDoor != null || gap.Open <= 0 || gap.linkedTo.All(l => l == null); if (leak == null) { return 0; }
if (!ignore) float sizeFactor = MathHelper.Lerp(1, 10, MathUtils.InverseLerp(0, 200, (leak.IsHorizontal ? leak.Rect.Width : leak.Rect.Height)));
{ float severity = sizeFactor * leak.Open;
if (gap.Submarine == null) { ignore = true; } if (!leak.IsRoomToRoom) { severity *= 50; }
else if (gap.Submarine.TeamID != character.TeamID) { ignore = true; } return MathHelper.Min(severity, 100);
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;
@@ -48,9 +38,14 @@ 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 override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFixLeaks; public static bool IsValidTarget(Gap gap, Character character)
protected override float Average(Gap gap) => gap.Open; {
protected override IEnumerable<Gap> GetList() => Gap.GapList; if (gap == null) { return false; }
protected override AIObjective ObjectiveConstructor(Gap gap) => new AIObjectiveFixLeak(gap, character); if (gap.ConnectedWall == null || gap.ConnectedDoor != null || gap.Open <= 0 || gap.linkedTo.All(l => l == null)) { return false; }
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,6 +11,9 @@ 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
@@ -20,14 +23,8 @@ namespace Barotrauma
public string[] ignoredContainerIdentifiers; public string[] ignoredContainerIdentifiers;
private AIObjectiveGoTo goToObjective; private AIObjectiveGoTo goToObjective;
private float currItemPriority; private float currItemPriority;
private bool equip;
private HashSet<Item> ignoredItems = new HashSet<Item>(); public override float GetPriority()
private bool canBeCompleted = true;
public override bool CanBeCompleted => canBeCompleted;
public override float GetPriority(AIObjectiveManager objectiveManager)
{ {
if (objectiveManager.CurrentOrder == this) if (objectiveManager.CurrentOrder == this)
{ {
@@ -36,16 +33,19 @@ namespace Barotrauma
return 1.0f; return 1.0f;
} }
public AIObjectiveGetItem(Character character, Item targetItem, bool equip = false) : base(character, "") public AIObjectiveGetItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, bool equip = false, float priorityModifier = 1)
: 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, bool equip = false) : this(character, new string[] { itemIdentifier }, equip) { } public AIObjectiveGetItem(Character character, string itemIdentifier, AIObjectiveManager objectiveManager, bool equip = false, float priorityModifier = 1)
: this(character, new string[] { itemIdentifier }, objectiveManager, equip, priorityModifier) { }
public AIObjectiveGetItem(Character character, string[] itemIdentifiers, bool equip = false) : base(character, "") public AIObjectiveGetItem(Character character, string[] itemIdentifiers, AIObjectiveManager objectiveManager, bool equip = false, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier)
{ {
currSearchIndex = -1; currSearchIndex = -1;
this.equip = equip; this.equip = equip;
@@ -54,20 +54,15 @@ namespace Barotrauma
{ {
itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant(); itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant();
} }
CheckInventory(); CheckInventory();
} }
private void CheckInventory() private void CheckInventory()
{ {
if (itemIdentifiers == null) if (itemIdentifiers == null) { return; }
{
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];
@@ -81,7 +76,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;
@@ -99,13 +94,11 @@ namespace Barotrauma
FindTargetItem(); FindTargetItem();
if (targetItem == null || moveToTarget == null) if (targetItem == null || moveToTarget == null)
{ {
HumanAIController.ObjectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime); 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, 2)) Vector2.DistanceSquared(character.Position, moveToTarget.Position) < MathUtils.Pow(targetItem.InteractDistance, 2))
{ {
int targetSlot = -1; int targetSlot = -1;
if (equip) if (equip)
@@ -123,8 +116,7 @@ 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; }
@@ -136,7 +128,6 @@ 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);
@@ -144,23 +135,20 @@ namespace Barotrauma
} }
else else
{ {
if (goToObjective == null || moveToTarget != goToObjective.Target) TryAddSubObjective(ref goToObjective,
{ constructor: () =>
//check if we're already looking for a diving gear {
bool gettingDivingGear = (targetItem != null && targetItem.Prefab.Identifier == "divingsuit" || targetItem.HasTag("diving")) || //check if we're already looking for a diving gear
(itemIdentifiers != null && (itemIdentifiers.Contains("diving") || itemIdentifiers.Contains("divingsuit"))); bool gettingDivingGear = (targetItem != null && targetItem.Prefab.Identifier == "divingsuit" || targetItem.HasTag("diving")) ||
(itemIdentifiers != null && (itemIdentifiers.Contains("diving") || itemIdentifiers.Contains("divingsuit")));
//don't attempt to get diving gear to reach the destination if the item we're trying to get is diving gear return new AIObjectiveGoTo(moveToTarget, character, objectiveManager, repeat: false, getDivingGearIfNeeded: !gettingDivingGear);
goToObjective = new AIObjectiveGoTo(moveToTarget, character, false, !gettingDivingGear); },
} onAbandon: () =>
{
goToObjective.TryComplete(deltaTime); targetItem = null;
if (!goToObjective.CanBeCompleted) moveToTarget = null;
{ ignoredItems.Add(targetItem);
targetItem = null; });
moveToTarget = null;
ignoredItems.Add(targetItem);
}
} }
} }
@@ -176,11 +164,10 @@ 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
canBeCompleted = false; abandon = true;
} }
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++;
@@ -189,15 +176,18 @@ 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)
{ {
@@ -219,11 +209,9 @@ 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)
@@ -231,21 +219,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
canBeCompleted = false; abandon = true;
} }
} }
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;
} }
@@ -263,7 +251,10 @@ 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))) return true; if (matchingItem != null && (!equip || character.HasEquippedItem(matchingItem)))
{
return true;
}
} }
return false; return false;
} }
@@ -9,29 +9,23 @@ 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 = 0.5f; public float CloseEnough { get; set; } = 0.5f;
public bool IgnoreIfTargetDead { get; set; }
public bool AllowGoingOutside { get; set; }
public bool CheckVisibility { get; set; }
public bool IgnoreIfTargetDead; public override float GetPriority()
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;
@@ -39,44 +33,6 @@ 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;
@@ -89,7 +45,7 @@ namespace Barotrauma
this.Target = target; this.Target = target;
this.repeat = repeat; this.repeat = repeat;
waitUntilPathUnreachable = 1.0f; waitUntilPathUnreachable = 2.0f;
this.getDivingGearIfNeeded = getDivingGearIfNeeded; this.getDivingGearIfNeeded = getDivingGearIfNeeded;
CalculateCloseEnough(); CalculateCloseEnough();
} }
@@ -110,27 +66,36 @@ namespace Barotrauma
{ {
if (FollowControlledCharacter) if (FollowControlledCharacter)
{ {
if (Character.Controlled == null) { return; } if (Character.Controlled == null)
{
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;
@@ -145,8 +110,12 @@ namespace Barotrauma
currTargetPos -= character.Submarine.SimPosition; currTargetPos -= character.Submarine.SimPosition;
} }
} }
bool sightCheck = true;
if (Vector2.DistanceSquared(currTargetPos, character.SimPosition) < CloseEnough * CloseEnough) if (CheckVisibility && Target != null)
{
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;
@@ -154,31 +123,50 @@ namespace Barotrauma
else else
{ {
bool isInside = character.CurrentHull != null; bool isInside = character.CurrentHull != null;
bool insideSteering = SteeringManager == PathSteering && PathSteering.CurrentPath != null; bool insideSteering = SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.IsPathDirty;
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)
{ {
cannotReach = true; abandon = 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)
{ {
if (targetIsOutside || var targetHull = Target is Hull h ? h : Target is Item i ? i.CurrentHull : Target is Character c ? c.CurrentHull : character.CurrentHull;
Target is Hull h && HumanAIController.NeedsDivingGear(h) || bool needsDivingGear = HumanAIController.NeedsDivingGear(targetHull);
Target is Item i && HumanAIController.NeedsDivingGear(i.CurrentHull) || bool needsDivingSuit = needsDivingGear && (targetIsOutside || targetHull.WaterPercentage > 90);
Target is Character c && HumanAIController.NeedsDivingGear(c.CurrentHull)) bool needsEquipment = false;
if (needsDivingSuit)
{ {
if (findDivingGear == null) needsEquipment = !HumanAIController.HasDivingSuit(character);
{ }
findDivingGear = new AIObjectiveFindDivingGear(character, true); else if (needsDivingGear)
AddSubObjective(findDivingGear); {
} needsEquipment = !HumanAIController.HasDivingMask(character);
else if (!findDivingGear.CanBeCompleted) }
{ if (needsEquipment)
abandon = true; {
} TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager));
} }
} }
} }
@@ -189,40 +177,30 @@ 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)
{ {
AIObjectiveGoTo objective = otherObjective as AIObjectiveGoTo; if (!(otherObjective is AIObjectiveGoTo objective)) { return false; }
if (objective == null) return false; if (objective.Target == Target) { return true; }
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,6 +38,18 @@ 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);
@@ -71,7 +83,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)
@@ -83,24 +95,10 @@ 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)));
bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) || if (currentTargetIsInvalid || currentTarget == null && IsForbidden(character.CurrentHull))
(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;
@@ -137,7 +135,6 @@ 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)
{ {
@@ -145,7 +142,8 @@ 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) || IsForbidden(n.CurrentHull))) path.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(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);
@@ -268,7 +266,6 @@ 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)
@@ -300,7 +297,7 @@ namespace Barotrauma
} }
} }
private bool IsForbidden(Hull hull) public static 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 List<T> targets = new List<T>(); protected HashSet<T> targets = new HashSet<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,9 +15,25 @@ 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 AIObjectiveLoop(Character character, string option) : base(character, option) public HashSet<T> ReportedTargets { get; private set; } = new HashSet<T>();
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();
} }
@@ -26,9 +42,11 @@ namespace Barotrauma
public override bool IsCompleted() => false; public override bool IsCompleted() => false;
public override bool CanBeCompleted => true; public override bool CanBeCompleted => true;
public override void Update(AIObjectiveManager objectiveManager, float deltaTime) 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(float deltaTime)
{ {
base.Update(objectiveManager, deltaTime); base.Update(deltaTime);
if (IgnoreListClearInterval > 0) if (IgnoreListClearInterval > 0)
{ {
if (ignoreListTimer > IgnoreListClearInterval) if (ignoreListTimer > IgnoreListClearInterval)
@@ -40,14 +58,13 @@ namespace Barotrauma
ignoreListTimer += deltaTime; ignoreListTimer += deltaTime;
} }
} }
if (targetUpdateTimer >= TargetUpdateInterval) if (targetUpdateTimer < 0)
{ {
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)
@@ -56,7 +73,7 @@ namespace Barotrauma
if (!objective.Value.CanBeCompleted) if (!objective.Value.CanBeCompleted)
{ {
ignoreList.Add(target); ignoreList.Add(target);
targetUpdateTimer = TargetUpdateInterval; targetUpdateTimer = 0;
} }
if (!targets.Contains(target)) if (!targets.Contains(target))
{ {
@@ -70,6 +87,9 @@ 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();
@@ -79,19 +99,34 @@ namespace Barotrauma
public override void OnSelected() public override void OnSelected()
{ {
Reset(); base.OnSelected();
if (HumanAIController.ObjectiveManager.CurrentOrder == this)
{
Reset();
}
} }
public override float GetPriority(AIObjectiveManager objectiveManager) public override float GetPriority()
{ {
if (character.Submarine == null) { return 0; } if (character.Submarine == null) { return 0; }
if (targets.None()) { return 0; } if (targets.None()) { return 0; }
float avg = targets.Average(t => Average(t)); // Allow the target value to be more than 100.
return MathHelper.Lerp(0, AIObjectiveManager.OrderPriority + 20, avg / 100); float targetValue = TargetEvaluation();
// 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();
@@ -99,12 +134,19 @@ namespace Barotrauma
protected virtual void FindTargets() protected virtual void FindTargets()
{ {
foreach (T item in GetList()) foreach (T target in GetList())
{ {
if (Filter(item)) { continue; } // The bots always find targets when the objective is an order.
if (!targets.Contains(item)) if (objectiveManager.CurrentOrder != this)
{ {
targets.Add(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.
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);
} }
} }
} }
@@ -116,6 +158,7 @@ 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);
} }
@@ -126,7 +169,9 @@ 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,27 +9,29 @@ namespace Barotrauma
class AIObjectiveManager class AIObjectiveManager
{ {
// TODO: expose // TODO: expose
public const float OrderPriority = 50.0f; public const float OrderPriority = 70;
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; } public List<AIObjective> Objectives { get; private set; } = new List<AIObjective>();
private Character character; private Character character;
/// <summary> /// <summary>
/// When set above zero, the character will stand still doing nothing until the timer runs out. Only affects idling. /// When set above zero, the character will stand still doing nothing until the timer runs out. Does not affect orders.
/// </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)
@@ -46,6 +48,30 @@ 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))
@@ -75,7 +101,7 @@ namespace Barotrauma
{ {
var previousObjective = CurrentObjective; var previousObjective = CurrentObjective;
var firstObjective = Objectives.FirstOrDefault(); var firstObjective = Objectives.FirstOrDefault();
if (CurrentOrder != null && firstObjective != null && CurrentOrder.GetPriority(this) > firstObjective.GetPriority(this)) if (CurrentOrder != null && firstObjective != null && CurrentOrder.GetPriority() > firstObjective.GetPriority())
{ {
CurrentObjective = CurrentOrder; CurrentObjective = CurrentOrder;
} }
@@ -86,18 +112,24 @@ 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(this); return CurrentObjective == null ? 0.0f : CurrentObjective.GetPriority();
} }
public void UpdateObjectives(float deltaTime) public void UpdateObjectives(float deltaTime)
{ {
CurrentOrder?.Update(this, deltaTime); CurrentOrder?.Update(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];
@@ -115,9 +147,9 @@ namespace Barotrauma
#endif #endif
Objectives.Remove(objective); Objectives.Remove(objective);
} }
else else if (objective != CurrentOrder)
{ {
objective.Update(this, deltaTime); objective.Update(deltaTime);
} }
} }
GetCurrentObjective(); GetCurrentObjective();
@@ -127,15 +159,41 @@ namespace Barotrauma
{ {
if (Objectives.Any()) if (Objectives.Any())
{ {
Objectives.Sort((x, y) => y.GetPriority(this).CompareTo(x.GetPriority(this))); Objectives.Sort((x, y) => y.GetPriority().CompareTo(x.GetPriority()));
} }
CurrentObjective?.SortSubObjectives(this); CurrentObjective?.SortSubObjectives();
} }
public void DoCurrentObjective(float deltaTime) public void DoCurrentObjective(float deltaTime)
{ {
if (WaitTimer > 0.0f) { WaitTimer -= deltaTime; } if (WaitTimer <= 0)
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)
@@ -145,13 +203,22 @@ namespace Barotrauma
public void SetOrder(Order order, string option, Character orderGiver) public void SetOrder(Order order, string option, Character orderGiver)
{ {
CurrentOrder = null; CurrentOrder = CreateObjective(order, option, orderGiver);
if (order == null) return; if (CurrentOrder == null)
{
// 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":
CurrentOrder = new AIObjectiveGoTo(orderGiver, character, true) newObjective = new AIObjectiveGoTo(orderGiver, character, this, repeat: true, priorityModifier: priorityModifier)
{ {
CloseEnough = 1.5f, CloseEnough = 1.5f,
AllowGoingOutside = true, AllowGoingOutside = true,
@@ -160,38 +227,41 @@ namespace Barotrauma
}; };
break; break;
case "wait": case "wait":
CurrentOrder = new AIObjectiveGoTo(character, character, true) newObjective = new AIObjectiveGoTo(character, character, this, repeat: true, priorityModifier: priorityModifier)
{ {
AllowGoingOutside = true AllowGoingOutside = true
}; };
break; break;
case "fixleaks": case "fixleaks":
CurrentOrder = new AIObjectiveFixLeaks(character); newObjective = new AIObjectiveFixLeaks(character, this, priorityModifier);
break; break;
case "chargebatteries": case "chargebatteries":
CurrentOrder = new AIObjectiveChargeBatteries(character, option); newObjective = new AIObjectiveChargeBatteries(character, this, option, priorityModifier);
break; break;
case "rescue": case "rescue":
CurrentOrder = new AIObjectiveRescueAll(character); newObjective = new AIObjectiveRescueAll(character, this, priorityModifier);
break; break;
case "repairsystems": case "repairsystems":
CurrentOrder = new AIObjectiveRepairItems(character) { RequireAdequateSkills = option != "all" }; newObjective = new AIObjectiveRepairItems(character, this, priorityModifier) { RequireAdequateSkills = option != "all" };
break; break;
case "pumpwater": case "pumpwater":
CurrentOrder = new AIObjectivePumpWater(character, option); newObjective = new AIObjectivePumpWater(character, this, option, priorityModifier: priorityModifier);
break; break;
case "extinguishfires": case "extinguishfires":
CurrentOrder = new AIObjectiveExtinguishFires(character); newObjective = new AIObjectiveExtinguishFires(character, this, priorityModifier);
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; if (order.TargetItemComponent == null) { return null; }
CurrentOrder = new AIObjectiveOperateItem(order.TargetItemComponent, character, option, false, null, order.UseController); newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option, requireEquip: false, useController: order.UseController, priorityModifier: priorityModifier) { IsLoop = true };
break; break;
default: default:
if (order.TargetItemComponent == null) return; if (order.TargetItemComponent == null) { return null; }
CurrentOrder = new AIObjectiveOperateItem(order.TargetItemComponent, character, option, false, null, order.UseController); newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option, requireEquip: false, useController: order.UseController, priorityModifier: priorityModifier) { IsLoop = true };
break; break;
} }
return newObjective; return newObjective;
@@ -10,8 +10,6 @@ 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;
@@ -20,35 +18,25 @@ namespace Barotrauma
private AIObjectiveGoTo goToObjective; private AIObjectiveGoTo goToObjective;
private AIObjectiveGetItem getItemObjective; private AIObjectiveGetItem getItemObjective;
private bool useController; public override bool CanBeCompleted => base.CanBeCompleted && (!useController || controller != null);
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 ItemComponent Component => component; public override float GetPriority()
public override float GetPriority(AIObjectiveManager objectiveManager)
{ {
if (gotoObjective != null && !gotoObjective.CanBeCompleted) { return 0; } if (component.Item.ConditionPercentage <= 0) { return 0; }
if (objectiveManager.CurrentOrder == this) if (objectiveManager.CurrentOrder == this)
{ {
return AIObjectiveManager.OrderPriority; return AIObjectiveManager.OrderPriority;
} }
return base.GetPriority(objectiveManager); if (component.Item.CurrentHull == null) { return 0; }
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)
@@ -58,34 +46,31 @@ namespace Barotrauma
this.requireEquip = requireEquip; this.requireEquip = requireEquip;
this.operateTarget = operateTarget; this.operateTarget = operateTarget;
this.useController = useController; this.useController = useController;
if (useController) if (useController)
{ {
var controllers = component.Item.GetConnectedComponents<Controller>(); //try finding the controller with the simpler non-recursive method first
if (controllers.Any()) controller = controllers[0]; controller =
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.CurrentHull == target.Item.CurrentHull) if (character.SelectedConstruction != target.Item)
{ {
if (character.SelectedConstruction != target.Item && target.CanBeSelected) if (character.SelectedConstruction != target.Item && target.CanBeSelected)
{ {
@@ -97,19 +82,19 @@ namespace Barotrauma
} }
return; return;
} }
if (component.AIOperate(deltaTime, character, this))
{
isCompleted = true;
}
}
else
{
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(target.Item, character, objectiveManager));
} }
AddSubObjective(gotoObjective = new AIObjectiveGoTo(target.Item, character));
} }
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;
@@ -132,11 +117,9 @@ 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 || if (character.Inventory.SlotTypes[i] == InvSlotType.Any || !holdable.AllowedSlots.Any(s => s.HasFlag(character.Inventory.SlotTypes[i])))
!holdable.AllowedSlots.Any(s => s.HasFlag(character.Inventory.SlotTypes[i])))
{ {
continue; continue;
} }
@@ -170,10 +153,8 @@ namespace Barotrauma
public override bool IsDuplicate(AIObjective otherObjective) public override bool IsDuplicate(AIObjective otherObjective)
{ {
AIObjectiveOperateItem operateItem = otherObjective as AIObjectiveOperateItem; if (!(otherObjective is AIObjectiveOperateItem operateItem)) { return false; }
if (operateItem == null) return false; return (operateItem.component == component || otherObjective.Option == Option);
return (operateItem.component == component ||otherObjective.Option == Option);
} }
} }
} }
@@ -1,68 +1,74 @@
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";
public override bool KeepDivingGearOn => true; private IEnumerable<Pump> pumpList;
private readonly IEnumerable<Pump> pumpList;
public AIObjectivePumpWater(Character character, string option) : base(character, option) public AIObjectivePumpWater(Character character, AIObjectiveManager objectiveManager, string option, float priorityModifier = 1)
{ : 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; }
foreach (Item item in Item.ItemList) base.FindTargets();
if (targets.None() && objectiveManager.CurrentOrder == this)
{ {
if (item.HasTag("ballast")) { continue; } character.Speak(TextManager.Get("DialogNoPumps"), null, 3.0f, "nopumps", 30.0f);
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) => true; protected override bool Filter(Pump pump)
protected override IEnumerable<Pump> GetList() => pumpList; {
protected override AIObjective ObjectiveConstructor(Pump pump) => new AIObjectiveOperateItem(pump, character, Option, false); if (pump == null) { return false; }
protected override float Average(Pump target) => 0; if (pump.Item.HasTag("ballast")) { return false; }
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)));
}
}
} }
} }
@@ -11,37 +11,33 @@ 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 float previousCondition = -1;
private RepairTool repairTool;
public AIObjectiveRepairItem(Character character, Item item) : base(character, "") public AIObjectiveRepairItem(Character character, Item item, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier)
{ {
Item = item; Item = item;
} }
public override float GetPriority(AIObjectiveManager objectiveManager) public override float GetPriority()
{ {
// 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 + 10) / Item.MaxCondition); float damagePriority = MathHelper.Lerp(1, 0, Item.Condition / 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 baseLevel = Math.Max(Priority + isSelected, 1); float devotion = (Math.Min(Priority, 10) + isSelected) / 100;
return MathHelper.Clamp(baseLevel * damagePriority * distanceFactor * successFactor, 0, 100); float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90);
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;
@@ -59,15 +55,6 @@ 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))
@@ -77,12 +64,14 @@ namespace Barotrauma
{ {
foreach (RelatedItem requiredItem in kvp.Value) foreach (RelatedItem requiredItem in kvp.Value)
{ {
AddSubObjective(new AIObjectiveGetItem(character, requiredItem.Identifiers, true)); AddSubObjective(new AIObjectiveGetItem(character, requiredItem.Identifiers, objectiveManager, 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();
@@ -114,31 +103,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("DialogRepairFailed").Replace("[itemname]", Item.Name), null, 0.0f, "repairfailed", 10.0f); character?.Speak(TextManager.Get("DialogCannotRepair").Replace("[itemname]", Item.Name), null, 0.0f, "cannotrepair", 10.0f);
} }
} }
repairable.CurrentFixer = abandon && repairable.CurrentFixer == character ? null : character; repairable.CurrentFixer = abandon && repairable.CurrentFixer == character ? null : character;
break; break;
} }
} }
else if (goToObjective == null || goToObjective.Target != Item) else
{ {
previousCondition = -1; // If cannot reach the item, approach it.
if (goToObjective != null) TryAddSubObjective(ref goToObjective,
{ constructor: () =>
subObjectives.Remove(goToObjective); {
} previousCondition = -1;
goToObjective = new AIObjectiveGoTo(Item, character); var objective = new AIObjectiveGoTo(Item, character, objectiveManager);
if (repairTool != null) if (repairTool != null)
{ {
//goToObjective.CloseEnough = (HumanAIController.AnimController.ArmLength + ConvertUnits.ToSimUnits(repairTool.Range)) * 0.75f; objective.CloseEnough = 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)
@@ -1,4 +1,5 @@
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;
@@ -7,18 +8,25 @@ 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) : base(character, "") { } public AIObjectiveRepairItems(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
// 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)
@@ -38,37 +46,35 @@ namespace Barotrauma
protected override bool Filter(Item item) protected override bool Filter(Item item)
{ {
bool ignore = ignoreList.Contains(item) || item.IsFullCondition; if (!IsValidTarget(item, character)) { return false; }
if (!ignore) if (item.CurrentHull.FireSources.Count > 0) { return false; }
// 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.Submarine == null) { ignore = true; } if (item.Repairables.All(r => item.Condition > r.ShowRepairUIThreshold)) { return false; }
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; }
}
}
}
} }
return ignore; if (RequireAdequateSkills)
{
if (item.Repairables.Any(r => !r.HasRequiredSkills(character))) { return false; }
}
return true;
} }
protected override float Average(Item item) => 100 - item.ConditionPercentage; protected override float TargetEvaluation() => targets.Max(t => 100 - t.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); protected override AIObjective ObjectiveConstructor(Item item) => new AIObjectiveRepairItem(character, item, objectiveManager, PriorityModifier);
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,7 +11,6 @@ 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;
@@ -21,26 +20,9 @@ namespace Barotrauma
private float treatmentTimer; private float treatmentTimer;
public override bool CanBeCompleted public AIObjectiveRescue(Character character, Character targetCharacter, AIObjectiveManager objectiveManager, float priorityModifier = 1)
: 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;
} }
@@ -96,68 +78,28 @@ 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))
{ {
AddSubObjective(goToObjective = new AIObjectiveGoTo(targetCharacter, character)); // Go to the target and select it
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(targetCharacter, character, objectiveManager));
} }
else else
{ {
if (character.SelectedCharacter == null) // We can start applying treatment
if (character.SelectedCharacter != targetCharacter)
{ {
character?.Speak(TextManager.Get("DialogFoundUnconsciousTarget") character.Speak(TextManager.Get("DialogFoundWoundedTarget")
.Replace("[targetname]", targetCharacter.Name).Replace("[roomname]", character.CurrentHull.DisplayName), .Replace("[targetname]", targetCharacter.Name).Replace("[roomname]", character.CurrentHull.DisplayName),
null, 1.0f, null, 1.0f,
"foundunconscioustarget" + targetCharacter.Name, 60.0f); "foundwoundedtarget" + targetCharacter.Name, 60.0f);
}
character.SelectCharacter(targetCharacter); character.SelectCharacter(targetCharacter);
}
GiveTreatment(deltaTime); GiveTreatment(deltaTime);
} }
} }
protected override bool ShouldInterruptSubObjective(AIObjective subObjective) // TODO: consider optimizing a bit
{
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)
@@ -174,7 +116,6 @@ 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)
{ {
@@ -189,7 +130,6 @@ 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)
@@ -212,9 +152,8 @@ 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 = "";
@@ -226,35 +165,34 @@ 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(), true)); AddSubObjective(new AIObjectiveGetItem(character, suitableItemIdentifiers.ToArray(), objectiveManager, equip: 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) remove = true; if (ic.DeleteOnUse)
{
remove = true;
}
} }
if (remove) if (remove)
{ {
Entity.Spawner?.AddToRemoveQueue(item); Entity.Spawner?.AddToRemoveQueue(item);
@@ -263,28 +201,35 @@ namespace Barotrauma
public override bool IsCompleted() public override bool IsCompleted()
{ {
bool isCompleted = bool isCompleted = targetCharacter.Vitality / targetCharacter.MaxVitality > AIObjectiveRescueAll.VitalityThreshold;
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(AIObjectiveManager objectiveManager) public override float GetPriority()
{ {
if (targetCharacter.AnimController.CurrentHull == null || targetCharacter.IsDead) { return 0.0f; } if (targetCharacter == null) { return 0; }
if (targetCharacter.CurrentHull == null || targetCharacter.Removed || targetCharacter.IsDead)
Vector2 diff = targetCharacter.WorldPosition - character.WorldPosition; {
float distance = Math.Abs(diff.X) + Math.Abs(diff.Y); abandon = true;
return 0;
float vitalityFactor = (targetCharacter.MaxVitality - targetCharacter.Vitality) / targetCharacter.MaxVitality; }
// Don't go into rooms that have enemies
return 1000.0f * vitalityFactor / distance; if (Character.CharacterList.Any(c => c.CurrentHull == targetCharacter.CurrentHull && !HumanAIController.IsFriendly(c)))
{
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));
} }
} }
} }
@@ -4,21 +4,18 @@ using Barotrauma.Extensions;
namespace Barotrauma namespace Barotrauma
{ {
class AIObjectiveRescueAll : AIObjective class AIObjectiveRescueAll : AIObjectiveLoop<Character>
{ {
public override string DebugTag => "rescue all"; public override string DebugTag => "rescue all";
public override bool ForceRun => true;
public override bool KeepDivingGearOn => true; //only treat characters whose vitality is below this (0.9 = 90% of max vitality)
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) : base (character, "") public AIObjectiveRescueAll(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1)
{ : 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()
{ {
@@ -29,31 +26,13 @@ namespace Barotrauma
} }
} }
public override float GetPriority(AIObjectiveManager objectiveManager) protected override bool Filter(Character target) => IsValidTarget(target, character);
{
if (character.Submarine == null) { return 0; }
GetRescueTargets();
if (!rescueTargets.Any()) { return 0.0f; }
if (objectiveManager.CurrentOrder == this)
{
return AIObjectiveManager.OrderPriority;
}
//if there are targets to rescue, the priority is slightly less protected override IEnumerable<Character> GetList() => Character.CharacterList;
//than the priority of explicit orders given to the character
return AIObjectiveManager.OrderPriority - 5.0f;
}
private void GetRescueTargets() protected override AIObjective ObjectiveConstructor(Character target) => new AIObjectiveRescue(character, target, objectiveManager, PriorityModifier);
{
rescueTargets = Character.CharacterList.FindAll(c => protected override float TargetEvaluation() => targets.Max(t => GetVitalityFactor(t)) * 100;
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;
@@ -69,8 +48,5 @@ 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,8 +64,7 @@ namespace Barotrauma
private Order(XElement orderElement) private Order(XElement orderElement)
{ {
AITag = orderElement.GetAttributeString("aitag", ""); AITag = orderElement.GetAttributeString("aitag", "");
Name = TextManager.Get("OrderName." + AITag, true) ?? orderElement.GetAttributeString("name", "Name not found"); Name = TextManager.Get("OrderName." + AITag, true) ?? "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))
@@ -127,7 +126,6 @@ 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;
@@ -142,8 +140,10 @@ namespace Barotrauma
{ {
if (UseController) if (UseController)
{ {
var controllers = targetItem.Item.GetConnectedComponents<Controller>(); //try finding the controller with the simpler non-recursive method first
if (controllers.Count > 0) ConnectedController = controllers[0]; ConnectedController =
targetItem.Item.GetConnectedComponents<Controller>().FirstOrDefault() ??
targetItem.Item.GetConnectedComponents<Controller>(recursive: true).FirstOrDefault();
} }
TargetEntity = targetItem.Item; TargetEntity = targetItem.Item;
TargetItemComponent = targetItem; TargetItemComponent = targetItem;
@@ -235,7 +235,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.CollisionPlatform); Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs );
if (body != null) if (body != null)
{ {
@@ -24,7 +24,10 @@ namespace Barotrauma
{ {
public static List<JobPrefab> List; public static List<JobPrefab> List;
public List<SkillPrefab> Skills; public readonly XElement Items;
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
@@ -118,14 +121,6 @@ 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);
(spawnedCharacter.AIController as HumanAIController)?.ObjectiveManager.SetOrder( var objectiveManager = (spawnedCharacter.AIController as HumanAIController)?.ObjectiveManager;
new AIObjectiveGoTo(watchmanSpawnpoint, spawnedCharacter, repeat: true, getDivingGearIfNeeded: false)); objectiveManager?.SetOrder(new AIObjectiveGoTo(watchmanSpawnpoint, spawnedCharacter, objectiveManager, repeat: true, getDivingGearIfNeeded: false));
if (watchmanJob != null) if (watchmanJob != null)
{ {
spawnedCharacter.GiveJobItems(); spawnedCharacter.GiveJobItems();
@@ -279,6 +279,8 @@ namespace Barotrauma
} }
public static bool ShowUserStatisticsPrompt { get; set; } public static bool ShowUserStatisticsPrompt { get; set; }
public bool ShowLanguageSelectionPrompt { get; set; }
public GameSettings() public GameSettings()
{ {
SelectedContentPackages = new HashSet<ContentPackage>(); SelectedContentPackages = new HashSet<ContentPackage>();
@@ -764,6 +766,7 @@ namespace Barotrauma
CheckBindings(!fileFound); CheckBindings(!fileFound);
if (!fileFound) if (!fileFound)
{ {
ShowLanguageSelectionPrompt = true;
SaveNewPlayerConfig(); SaveNewPlayerConfig();
} }
} }
@@ -26,6 +26,8 @@ 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;
@@ -227,8 +229,7 @@ namespace Barotrauma.Items.Components
{ {
msg = msg ?? (HasIntegratedButtons ? accessDeniedTxt : cannotOpenText); msg = msg ?? (HasIntegratedButtons ? accessDeniedTxt : cannotOpenText);
} }
if (isBroken) { return true; } return isBroken || base.HasRequiredItems(character, addMessage, msg);
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 * 5.0f) return true; if (dist > Range * 3.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,7 +344,19 @@ 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)
{ {
character.SetInput(InputType.Aim, false, true); bool isOperatingButtons = false;
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);
@@ -400,7 +412,8 @@ namespace Barotrauma.Items.Components
object value = property.GetValue(target); object value = property.GetValue(target);
if (value.GetType() == typeof(float)) if (value.GetType() == typeof(float))
{ {
user.UpdateHUDProgressBar(door, door.Item.WorldPosition, (float)value / 100, Color.DarkGray * 0.5f, Color.White); var progressBar = 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); }
} }
} }
} }
@@ -43,6 +43,11 @@ namespace Barotrauma.Items.Components
set { userPos = value; } set { userPos = value; }
} }
public Character User
{
get { return user; }
}
public Controller(Item item, XElement element) public Controller(Item item, XElement element)
: base(item, element) : base(item, element)
{ {
@@ -163,7 +168,7 @@ namespace Barotrauma.Items.Components
return false; return false;
} }
item.SendSignal(0, "1", "trigger_out", character); item.SendSignal(0, "1", "trigger_out", user);
ApplyStatusEffects(ActionType.OnUse, 1.0f, activator); ApplyStatusEffects(ActionType.OnUse, 1.0f, activator);
@@ -233,7 +238,7 @@ namespace Barotrauma.Items.Components
private Item GetFocusTarget() private Item GetFocusTarget()
{ {
item.SendSignal(0, MathHelper.ToDegrees(targetRotation).ToString("G", CultureInfo.InvariantCulture), "position_out", character); item.SendSignal(0, MathHelper.ToDegrees(targetRotation).ToString("G", CultureInfo.InvariantCulture), "position_out", user);
for (int i = item.LastSentSignalRecipients.Count - 1; i >= 0; i--) for (int i = item.LastSentSignalRecipients.Count - 1; i >= 0; i--)
{ {
@@ -962,6 +962,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>()) var containFuelObjective = new AIObjectiveContainItem(character, new string[] { "fuelrod", "reactorfuel" }, item.GetComponent<ItemContainer>(), objective.objectiveManager)
{ {
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,6 +212,33 @@ 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 * 0.5f) > 0.05f) if (Math.Abs(rechargeSpeed - maxRechargeSpeed * aiRechargeTargetRatio) > 0.05f)
{ {
#if SERVER #if SERVER
item.CreateServerEvent(this); item.CreateServerEvent(this);
#endif #endif
RechargeSpeed = maxRechargeSpeed * 0.5f; RechargeSpeed = maxRechargeSpeed * aiRechargeTargetRatio;
#if CLIENT #if CLIENT
rechargeSpeedSlider.BarScroll = RechargeSpeed / Math.Max(maxRechargeSpeed, 1.0f); rechargeSpeedSlider.BarScroll = RechargeSpeed / Math.Max(maxRechargeSpeed, 1.0f);
#endif #endif
@@ -152,7 +152,8 @@ namespace Barotrauma.Items.Components
ParentSub = item.CurrentHull?.Submarine, ParentSub = item.CurrentHull?.Submarine,
Position = item.Position, Position = item.Position,
CastShadows = castShadows, CastShadows = castShadows,
IsBackground = drawBehindSubs IsBackground = drawBehindSubs,
SpriteScale = Vector2.One * item.Scale
}; };
#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, "", false)); objective.AddSubObjective(new AIObjectiveOperateItem(batteryToLoad, character, objective.objectiveManager, option: "", requireEquip: 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); var removeShellObjective = new AIObjectiveDecontainItem(character, container.Inventory.Items[0], container, objective.objectiveManager);
objective.AddSubObjective(removeShellObjective); objective.AddSubObjective(removeShellObjective);
} }
var containShellObjective = new AIObjectiveContainItem(character, container.ContainableItems[0].Identifiers[0], container); var containShellObjective = new AIObjectiveContainItem(character, container.ContainableItems[0].Identifiers[0], container, objective.objectiveManager);
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 };
@@ -1828,6 +1828,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; }
@@ -1964,6 +1968,9 @@ 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>();
@@ -1972,7 +1979,6 @@ namespace Barotrauma
{ {
HashSet<Connection> alreadySearched = new HashSet<Connection>(); HashSet<Connection> alreadySearched = new HashSet<Connection>();
GetConnectedComponentsRecursive(alreadySearched, connectedComponents); GetConnectedComponentsRecursive(alreadySearched, connectedComponents);
return connectedComponents; return connectedComponents;
} }
@@ -2001,27 +2007,13 @@ 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>();
@@ -2051,13 +2043,28 @@ 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);
} }
@@ -2360,29 +2367,6 @@ 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;
@@ -493,6 +493,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
@@ -20,6 +20,8 @@ namespace Barotrauma
public float Rotation; public float Rotation;
private int spriteIndex;
public LevelObjectPrefab ActivePrefab; public LevelObjectPrefab ActivePrefab;
public PhysicsBody PhysicsBody public PhysicsBody PhysicsBody
@@ -40,6 +42,15 @@ namespace Barotrauma
set { Triggers.ForEach(t => t.NeedsNetworkSyncing = false); } set { Triggers.ForEach(t => t.NeedsNetworkSyncing = false); }
} }
public Sprite Sprite
{
get { return spriteIndex < 0 || Prefab.Sprites.Count == 0 ? null : Prefab.Sprites[spriteIndex % Prefab.Sprites.Count]; }
}
public Sprite SpecularSprite
{
get { return spriteIndex < 0 || Prefab.SpecularSprites.Count == 0 ? null : Prefab.SpecularSprites[spriteIndex % Prefab.SpecularSprites.Count]; }
}
public LevelObject(LevelObjectPrefab prefab, Vector3 position, float scale, float rotation = 0.0f) public LevelObject(LevelObjectPrefab prefab, Vector3 position, float scale, float rotation = 0.0f)
{ {
Triggers = new List<LevelTrigger>(); Triggers = new List<LevelTrigger>();
@@ -49,6 +60,8 @@ namespace Barotrauma
Scale = scale; Scale = scale;
Rotation = rotation; Rotation = rotation;
spriteIndex = ActivePrefab.Sprites.Any() ? Rand.Int(ActivePrefab.Sprites.Count, Rand.RandSync.Server) : -1;
if (prefab.PhysicsBodyElement != null) if (prefab.PhysicsBodyElement != null)
{ {
PhysicsBody = new PhysicsBody(prefab.PhysicsBodyElement, ConvertUnits.ToSimUnits(new Vector2(position.X, position.Y)), Scale); PhysicsBody = new PhysicsBody(prefab.PhysicsBodyElement, ConvertUnits.ToSimUnits(new Vector2(position.X, position.Y)), Scale);
@@ -166,7 +166,7 @@ namespace Barotrauma
Vector2.Zero, Vector2.Zero, Vector2.Zero, Vector2.Zero Vector2.Zero, Vector2.Zero, Vector2.Zero, Vector2.Zero
}; };
Sprite sprite = newObject.Prefab.Sprite ?? newObject.Prefab.DeformableSprite?.Sprite; Sprite sprite = newObject.Sprite ?? newObject.Prefab.DeformableSprite?.Sprite;
//calculate the positions of the corners of the rotated sprite //calculate the positions of the corners of the rotated sprite
if (sprite != null) if (sprite != null)
@@ -43,18 +43,18 @@ namespace Barotrauma
SeaFloor = 4, SeaFloor = 4,
MainPath = 8 MainPath = 8
} }
public Sprite Sprite
{
get;
private set;
}
public Sprite SpecularSprite public List<Sprite> Sprites
{ {
get; get;
private set; private set;
} } = new List<Sprite>();
public List<Sprite> SpecularSprites
{
get;
private set;
} = new List<Sprite>();
public DeformableSprite DeformableSprite public DeformableSprite DeformableSprite
{ {
@@ -319,7 +319,7 @@ namespace Barotrauma
//use the maximum width of the sprite as the minimum surface width if no value is given //use the maximum width of the sprite as the minimum surface width if no value is given
if (element != null && !element.Attributes("minsurfacewidth").Any()) if (element != null && !element.Attributes("minsurfacewidth").Any())
{ {
if (Sprite != null) MinSurfaceWidth = Sprite.size.X * MaxSize; if (Sprites.Any()) MinSurfaceWidth = Sprites[0].size.X * MaxSize;
if (DeformableSprite != null) MinSurfaceWidth = Math.Max(MinSurfaceWidth, DeformableSprite.Size.X * MaxSize); if (DeformableSprite != null) MinSurfaceWidth = Math.Max(MinSurfaceWidth, DeformableSprite.Size.X * MaxSize);
} }
} }
@@ -331,10 +331,10 @@ namespace Barotrauma
switch (subElement.Name.ToString().ToLowerInvariant()) switch (subElement.Name.ToString().ToLowerInvariant())
{ {
case "sprite": case "sprite":
Sprite = new Sprite(subElement, lazyLoad: true); Sprites.Add( new Sprite(subElement, lazyLoad: true));
break; break;
case "specularsprite": case "specularsprite":
SpecularSprite = new Sprite(subElement, lazyLoad: true); SpecularSprites.Add(new Sprite(subElement, lazyLoad: true));
break; break;
case "deformablesprite": case "deformablesprite":
DeformableSprite = new DeformableSprite(subElement, lazyLoad: true); DeformableSprite = new DeformableSprite(subElement, lazyLoad: true);
@@ -358,9 +358,9 @@ namespace Barotrauma
case "overrideproperties": case "overrideproperties":
var propertyOverride = new LevelObjectPrefab(subElement); var propertyOverride = new LevelObjectPrefab(subElement);
OverrideProperties[OverrideProperties.Count - 1] = propertyOverride; OverrideProperties[OverrideProperties.Count - 1] = propertyOverride;
if (propertyOverride.Sprite == null && propertyOverride.DeformableSprite == null) if (!propertyOverride.Sprites.Any() && propertyOverride.DeformableSprite == null)
{ {
propertyOverride.Sprite = Sprite; propertyOverride.Sprites = Sprites;
propertyOverride.DeformableSprite = DeformableSprite; propertyOverride.DeformableSprite = DeformableSprite;
} }
break; break;
@@ -393,7 +393,9 @@ namespace Barotrauma
{ {
using (FileStream inFile = new FileStream(sCompressedFile, FileMode.Open, FileAccess.Read, FileShare.None)) using (FileStream inFile = new FileStream(sCompressedFile, FileMode.Open, FileAccess.Read, FileShare.None))
using (GZipStream zipStream = new GZipStream(inFile, CompressionMode.Decompress, true)) using (GZipStream zipStream = new GZipStream(inFile, CompressionMode.Decompress, true))
while (DecompressFile(sDir, zipStream, progress)) ; while (DecompressFile(sDir, zipStream, progress)) { };
break;
} }
catch (IOException e) catch (IOException e)
{ {
Binary file not shown.
Binary file not shown.
Binary file not shown.