Release 1.8.6.2 - Calm Before the Storm
This commit is contained in:
@@ -911,6 +911,11 @@ namespace Barotrauma
|
||||
PauseMenu.AddToGUIUpdateList();
|
||||
}
|
||||
|
||||
foreach (var openAccordion in GUIComponent.OpenAccordionPopups)
|
||||
{
|
||||
openAccordion.AddToGUIUpdateList(order: 1);
|
||||
}
|
||||
|
||||
SocialOverlay.Instance?.AddToGuiUpdateList();
|
||||
|
||||
GUIContextMenu.AddActiveToGUIUpdateList();
|
||||
|
||||
@@ -1127,7 +1127,7 @@ namespace Barotrauma
|
||||
component = LoadGUIImage(element, parent);
|
||||
break;
|
||||
case "accordion":
|
||||
return LoadAccordion(element, parent);
|
||||
return LoadAccordion(element, parent, openOnTop: element.GetAttributeBool("openontop", false));
|
||||
case "gridtext":
|
||||
LoadGridText(element, parent);
|
||||
return null;
|
||||
@@ -1147,6 +1147,19 @@ namespace Barotrauma
|
||||
|
||||
component.toolTip = element.GetAttributeString("tooltip", string.Empty);
|
||||
|
||||
GUITextBlock textBlock = component as GUITextBlock ?? (component as GUIButton)?.TextBlock;
|
||||
if (textBlock != null)
|
||||
{
|
||||
if (element.GetAttributeBool("autoscalevertical", false))
|
||||
{
|
||||
textBlock.AutoScaleVertical = true;
|
||||
}
|
||||
if (element.GetAttributeBool("autoscalehorizontal", false))
|
||||
{
|
||||
textBlock.AutoScaleHorizontal = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (element.GetAttributeBool("resizetofitchildren", false))
|
||||
{
|
||||
Vector2 relativeResizeScale = element.GetAttributeVector2("relativeresizescale", Vector2.One);
|
||||
@@ -1209,6 +1222,10 @@ namespace Barotrauma
|
||||
var maxVersion = new Version(attribute.Value);
|
||||
if (GameMain.Version > maxVersion) { return false; }
|
||||
break;
|
||||
case "identifierdismissed":
|
||||
Identifier identifier = element.GetAttributeIdentifier(attribute.Name.ToString(), Identifier.Empty);
|
||||
if (MainMenuScreen.DismissedNotifications.Contains(identifier)) { return false; }
|
||||
break;
|
||||
case "buildconfiguration":
|
||||
switch (attribute.Value.ToString().ToLowerInvariant())
|
||||
{
|
||||
@@ -1286,12 +1303,18 @@ namespace Barotrauma
|
||||
|
||||
private static GUIButton LoadLink(XElement element, RectTransform parent)
|
||||
{
|
||||
Identifier identifier = element.GetAttributeIdentifier("identifier", Identifier.Empty);
|
||||
|
||||
var button = LoadGUIButton(element, parent);
|
||||
string url = element.GetAttributeString("url", "");
|
||||
button.OnClicked = (btn, userdata) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!identifier.IsEmpty)
|
||||
{
|
||||
MainMenuScreen.AddDismissedNotification(identifier);
|
||||
}
|
||||
if (SteamManager.IsInitialized)
|
||||
{
|
||||
SteamManager.OverlayCustomUrl(url);
|
||||
@@ -1348,6 +1371,8 @@ namespace Barotrauma
|
||||
|
||||
private static GUIButton LoadGUIButton(XElement element, RectTransform parent)
|
||||
{
|
||||
Identifier identifier = element.GetAttributeIdentifier("identifier", Identifier.Empty);
|
||||
|
||||
string style = element.GetAttributeString("style", "");
|
||||
if (style == "null") { style = null; }
|
||||
|
||||
@@ -1359,10 +1384,19 @@ namespace Barotrauma
|
||||
element.GetAttributeString("text", "");
|
||||
text = text.Replace(@"\n", "\n");
|
||||
|
||||
return new GUIButton(RectTransform.Load(element, parent),
|
||||
var button = new GUIButton(RectTransform.Load(element, parent),
|
||||
text: text,
|
||||
textAlignment: textAlignment,
|
||||
style: style);
|
||||
button.OnClicked = (btn, userdata) =>
|
||||
{
|
||||
if (!identifier.IsEmpty)
|
||||
{
|
||||
MainMenuScreen.AddDismissedNotification(identifier);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
return button;
|
||||
}
|
||||
|
||||
private static GUIListBox LoadGUIListBox(XElement element, RectTransform parent)
|
||||
@@ -1415,11 +1449,14 @@ namespace Barotrauma
|
||||
{
|
||||
sprite = new Sprite(element);
|
||||
}
|
||||
|
||||
return new GUIImage(RectTransform.Load(element, parent), sprite, scaleToFit: true);
|
||||
var scaleToFit = element.GetAttributeEnum("scaletofit", GUIImage.ScalingMode.ScaleToFitSmallestExtent);
|
||||
return new GUIImage(RectTransform.Load(element, parent), sprite, scaleToFit: scaleToFit);
|
||||
}
|
||||
|
||||
private static GUIButton LoadAccordion(ContentXElement element, RectTransform parent)
|
||||
public static readonly List<GUIComponent> OpenAccordionPopups = new List<GUIComponent>();
|
||||
|
||||
/// <param name="openOnTop">Should the contents of the accordion be forced to open on top of other UI elements?</param>
|
||||
private static GUIButton LoadAccordion(ContentXElement element, RectTransform parent, bool openOnTop)
|
||||
{
|
||||
var button = LoadGUIButton(element, parent);
|
||||
List<GUIComponent> content = new List<GUIComponent>();
|
||||
@@ -1431,6 +1468,7 @@ namespace Barotrauma
|
||||
contentElement.Visible = false;
|
||||
contentElement.IgnoreLayoutGroups = true;
|
||||
content.Add(contentElement);
|
||||
contentElement.UserData = (contentElement.RectTransform.Anchor, contentElement.RectTransform.Pivot);
|
||||
}
|
||||
}
|
||||
button.OnClicked = (btn, userdata) =>
|
||||
@@ -1438,8 +1476,32 @@ namespace Barotrauma
|
||||
bool visible = content.FirstOrDefault()?.Visible ?? true;
|
||||
foreach (GUIComponent contentElement in content)
|
||||
{
|
||||
contentElement.Visible = !visible;
|
||||
contentElement.IgnoreLayoutGroups = !contentElement.Visible;
|
||||
if (openOnTop)
|
||||
{
|
||||
contentElement.rectTransform.Parent = null;
|
||||
//the element is drawn in screen space over anything (no longer a child of the original parent),
|
||||
//we need to calculate the screen space position manually
|
||||
contentElement.rectTransform.SetPosition(Anchor.TopLeft);
|
||||
(Anchor anchor, Pivot pivot) = ((Anchor anchor, Pivot pivot))contentElement.UserData;
|
||||
contentElement.rectTransform.ScreenSpaceOffset =
|
||||
RectTransform.CalculateAnchorPoint(anchor, button.Rect) +
|
||||
RectTransform.CalculatePivotOffset(pivot, contentElement.Rect.Size);
|
||||
contentElement.Visible = true;
|
||||
if (OpenAccordionPopups.Contains(contentElement))
|
||||
{
|
||||
OpenAccordionPopups.Remove(contentElement);
|
||||
}
|
||||
else
|
||||
{
|
||||
OpenAccordionPopups.Clear();
|
||||
OpenAccordionPopups.Add(contentElement);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
contentElement.Visible = !visible;
|
||||
contentElement.IgnoreLayoutGroups = !contentElement.Visible;
|
||||
}
|
||||
}
|
||||
if (button.Parent is GUILayoutGroup layoutGroup)
|
||||
{
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -14,6 +13,24 @@ namespace Barotrauma
|
||||
|
||||
private static bool loadingTextures;
|
||||
|
||||
public enum ScalingMode
|
||||
{
|
||||
/// <summary>
|
||||
/// No automatic scaling, the image is drawn using <see cref="Scale"/>
|
||||
/// </summary>
|
||||
None,
|
||||
/// <summary>
|
||||
/// Automatically scales the image so it fits the smallest extent of the component
|
||||
/// (leaving empty space around the image if its aspect ratio is different than that of the GUIImage)
|
||||
/// </summary>
|
||||
ScaleToFitSmallestExtent,
|
||||
/// <summary>
|
||||
/// Automatically scales the image so it fits the largest extent of the component,
|
||||
/// cutting out the parts that go outside the bounds of the component.
|
||||
/// </summary>
|
||||
ScaleToFitLargestExtent,
|
||||
}
|
||||
|
||||
public static bool LoadingTextures
|
||||
{
|
||||
get
|
||||
@@ -30,7 +47,7 @@ namespace Barotrauma
|
||||
|
||||
private bool crop;
|
||||
|
||||
private readonly bool scaleToFit;
|
||||
private readonly ScalingMode scaleToFit;
|
||||
|
||||
private bool lazyLoaded, loading;
|
||||
|
||||
@@ -89,7 +106,7 @@ namespace Barotrauma
|
||||
sprite = value;
|
||||
sourceRect = value == null ? Rectangle.Empty : value.SourceRect;
|
||||
origin = value == null ? Vector2.Zero : value.size / 2;
|
||||
if (scaleToFit) { RecalculateScale(); }
|
||||
if (scaleToFit != ScalingMode.None) { RecalculateScale(); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,17 +114,27 @@ namespace Barotrauma
|
||||
|
||||
public ComponentState? OverrideState = null;
|
||||
|
||||
public GUIImage(RectTransform rectT, string style, bool scaleToFit = false)
|
||||
public GUIImage(RectTransform rectT, string style, bool scaleToFit)
|
||||
: this(rectT, null, null, scaleToFit ? ScalingMode.ScaleToFitSmallestExtent : ScalingMode.None, style)
|
||||
{
|
||||
}
|
||||
|
||||
public GUIImage(RectTransform rectT, string style, ScalingMode scaleToFit = ScalingMode.None)
|
||||
: this(rectT, null, null, scaleToFit, style)
|
||||
{
|
||||
}
|
||||
|
||||
public GUIImage(RectTransform rectT, Sprite sprite, Rectangle? sourceRect = null, bool scaleToFit = false)
|
||||
public GUIImage(RectTransform rectT, Sprite sprite, bool scaleToFit, Rectangle? sourceRect = null)
|
||||
: this(rectT, sprite, sourceRect, scaleToFit ? ScalingMode.ScaleToFitSmallestExtent : ScalingMode.None, null)
|
||||
{
|
||||
}
|
||||
|
||||
public GUIImage(RectTransform rectT, Sprite sprite, Rectangle? sourceRect = null, ScalingMode scaleToFit = ScalingMode.None)
|
||||
: this(rectT, sprite, sourceRect, scaleToFit, null)
|
||||
{
|
||||
}
|
||||
|
||||
private GUIImage(RectTransform rectT, Sprite sprite, Rectangle? sourceRect, bool scaleToFit, string style) : base(style, rectT)
|
||||
private GUIImage(RectTransform rectT, Sprite sprite, Rectangle? sourceRect, ScalingMode scaleToFit, string style) : base(style, rectT)
|
||||
{
|
||||
this.scaleToFit = scaleToFit;
|
||||
Sprite = sprite;
|
||||
@@ -123,7 +150,7 @@ namespace Barotrauma
|
||||
{
|
||||
color = hoverColor = selectedColor = pressedColor = disabledColor = Color.White;
|
||||
}
|
||||
if (!scaleToFit)
|
||||
if (scaleToFit == ScalingMode.None)
|
||||
{
|
||||
Scale = 1.0f;
|
||||
}
|
||||
@@ -176,9 +203,11 @@ namespace Barotrauma
|
||||
|
||||
Color currentColor = GetColor(State);
|
||||
|
||||
if (BlendState != null)
|
||||
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
|
||||
if (BlendState != null || scaleToFit == ScalingMode.ScaleToFitLargestExtent)
|
||||
{
|
||||
spriteBatch.End();
|
||||
spriteBatch.GraphicsDevice.ScissorRectangle = Rectangle.Intersect(prevScissorRect, Rect);
|
||||
spriteBatch.Begin(blendState: BlendState, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
|
||||
}
|
||||
|
||||
@@ -205,9 +234,10 @@ namespace Barotrauma
|
||||
Scale, SpriteEffects, 0.0f);
|
||||
}
|
||||
|
||||
if (BlendState != null)
|
||||
if (BlendState != null || scaleToFit == ScalingMode.ScaleToFitLargestExtent)
|
||||
{
|
||||
spriteBatch.End();
|
||||
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
|
||||
}
|
||||
}
|
||||
@@ -219,9 +249,18 @@ namespace Barotrauma
|
||||
sourceRect = sprite.SourceRect;
|
||||
}
|
||||
|
||||
Scale = sprite == null || sprite.SourceRect.Width == 0 || sprite.SourceRect.Height == 0 ?
|
||||
1.0f :
|
||||
Math.Min(RectTransform.Rect.Width / (float)sprite.SourceRect.Width, RectTransform.Rect.Height / (float)sprite.SourceRect.Height);
|
||||
if (sprite == null || sprite.SourceRect.Width == 0 || sprite.SourceRect.Height == 0)
|
||||
{
|
||||
Scale = 1.0f;
|
||||
}
|
||||
else if (scaleToFit == ScalingMode.ScaleToFitLargestExtent)
|
||||
{
|
||||
Scale = Math.Max(RectTransform.Rect.Width / (float)sprite.SourceRect.Width, RectTransform.Rect.Height / (float)sprite.SourceRect.Height);
|
||||
}
|
||||
else
|
||||
{
|
||||
Scale = Math.Min(RectTransform.Rect.Width / (float)sprite.SourceRect.Width, RectTransform.Rect.Height / (float)sprite.SourceRect.Height);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> LoadTextureAsync()
|
||||
|
||||
@@ -749,8 +749,8 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
private bool ActiveServiceFull()
|
||||
{
|
||||
return (PendingHires.Count(ci => ci.BotStatus == BotStatus.PendingHireToActiveService) + campaign.CrewManager.GetCharacterInfos().Count())
|
||||
>= CrewManager.MaxCrewSize;
|
||||
int pendingHireCount = PendingHires?.Count(ci => ci.BotStatus == BotStatus.PendingHireToActiveService) ?? 0;
|
||||
return pendingHireCount + campaign.CrewManager.GetCharacterInfos().Count() >= CrewManager.MaxCrewSize;
|
||||
}
|
||||
|
||||
private bool EnoughReputationToHire(CharacterInfo characterInfo)
|
||||
|
||||
@@ -25,8 +25,6 @@ namespace Barotrauma
|
||||
private Video currSplashScreen;
|
||||
private DateTime videoStartTime;
|
||||
|
||||
private bool mirrorBackground;
|
||||
|
||||
public struct PendingSplashScreen
|
||||
{
|
||||
public string Filename;
|
||||
@@ -108,8 +106,7 @@ namespace Barotrauma
|
||||
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, samplerState: GUI.SamplerState);
|
||||
|
||||
GUI.DrawBackgroundSprite(spriteBatch, currentBackgroundTexture, Color.White, drawArea,
|
||||
spriteEffects: mirrorBackground ? SpriteEffects.FlipHorizontally : SpriteEffects.None);
|
||||
GUI.DrawBackgroundSprite(spriteBatch, currentBackgroundTexture, Color.White, drawArea);
|
||||
overlay.Draw(spriteBatch, Vector2.Zero, scale: overlayScale);
|
||||
|
||||
double noiseT = Timing.TotalTime * 0.02f;
|
||||
@@ -386,7 +383,6 @@ namespace Barotrauma
|
||||
{
|
||||
currentBackgroundTexture = missions.Where(m => m.Prefab.HasPortraits).First().Prefab.GetPortrait(Rand.Int(int.MaxValue));
|
||||
}
|
||||
mirrorBackground = Rand.Range(0.0f, 1.0f) < 0.5f;
|
||||
|
||||
while (!drawn)
|
||||
{
|
||||
|
||||
@@ -255,7 +255,7 @@ namespace Barotrauma
|
||||
pageIndicators = new GUIImage[pageCount];
|
||||
for (int i = 0; i < pageCount; i++)
|
||||
{
|
||||
pageIndicators[i] = new GUIImage(new RectTransform(indicatorSize, pageIndicatorHolder.RectTransform) { AbsoluteOffset = new Point(xPos, yPos) }, pageIndicator, null, true);
|
||||
pageIndicators[i] = new GUIImage(new RectTransform(indicatorSize, pageIndicatorHolder.RectTransform) { AbsoluteOffset = new Point(xPos, yPos) }, pageIndicator, scaleToFit: true);
|
||||
xPos += indicatorSize.X + HUDLayoutSettings.Padding;
|
||||
}
|
||||
|
||||
|
||||
@@ -799,8 +799,6 @@ namespace Barotrauma
|
||||
|
||||
customizeTabOpen = !hasUpgradeModules && hasSwappableItems;
|
||||
|
||||
customizeTabOpen = false;
|
||||
|
||||
GUIComponent[] categoryFrames = GetFrames(category);
|
||||
foreach (GUIComponent itemFrame in itemPreviews.Values)
|
||||
{
|
||||
|
||||
@@ -467,7 +467,7 @@ namespace Barotrauma
|
||||
};
|
||||
if (icon != null)
|
||||
{
|
||||
missionIcon = new GUIImage(new RectTransform(new Point(iconSize), content.RectTransform), icon, null, true)
|
||||
missionIcon = new GUIImage(new RectTransform(new Point(iconSize), content.RectTransform), icon, scaleToFit: true)
|
||||
{
|
||||
Color = iconColor,
|
||||
HoverColor = iconColor,
|
||||
|
||||
@@ -86,6 +86,9 @@ namespace Barotrauma.Items.Components
|
||||
[Serialize(true, IsPropertySaveable.No)]
|
||||
public bool ShowAvailableOnlyTickBox { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.No)]
|
||||
public bool ShowCategoryButtons { get; set; }
|
||||
|
||||
public override bool RecreateGUIOnResolutionChange => true;
|
||||
|
||||
protected override void OnResolutionChanged()
|
||||
@@ -120,7 +123,7 @@ namespace Barotrauma.Items.Components
|
||||
itemCategoryButtons.Clear();
|
||||
|
||||
//only create category buttons if there's more than one category in addition to "All"
|
||||
if (itemCategories.Count > 2)
|
||||
if (ShowCategoryButtons && itemCategories.Count > 2)
|
||||
{
|
||||
// === Item category buttons ===
|
||||
var categoryButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.05f, 1.0f), innerArea.RectTransform))
|
||||
@@ -887,7 +890,7 @@ namespace Barotrauma.Items.Components
|
||||
itemIcon.Draw(
|
||||
spriteBatch,
|
||||
slotRect.Center.ToVector2(),
|
||||
color: targetItem.TargetItem.InventoryIconColor * 0.4f,
|
||||
color: Color.Lerp(targetItem.TargetItem.InventoryIconColor, Color.TransparentBlack, 0.5f),
|
||||
scale: Math.Min(slotRect.Width / itemIcon.size.X, slotRect.Height / itemIcon.size.Y) * 0.9f);
|
||||
}
|
||||
}
|
||||
@@ -1259,6 +1262,15 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (!IsActive)
|
||||
{
|
||||
if (outputContainer != null && outputContainer.Inventory.AllItems.Any())
|
||||
{
|
||||
if (outputContainer.Inventory.visualSlots is { } visualSlots && visualSlots.Any() &&
|
||||
visualSlots[0].HighlightTimer <= 0.0f)
|
||||
{
|
||||
visualSlots[0].ShowBorderHighlight(GUIStyle.Green, 0.5f, 0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedItem != null && displayingForCharacter != character)
|
||||
{
|
||||
//reselect to recreate the info based on the new user's skills
|
||||
|
||||
@@ -180,7 +180,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
float playStyleBannerAspectRatio = (float)playStyleBannerSprite.SourceRect.Width / (float)playStyleBannerSprite.SourceRect.Height;
|
||||
playStyleBanner = new GUIImage(new RectTransform(new Vector2(1.0f, 1.0f / playStyleBannerAspectRatio), frame.RectTransform, scaleBasis: ScaleBasis.BothWidth),
|
||||
playStyleBannerSprite, null, true);
|
||||
playStyleBannerSprite, scaleToFit: true);
|
||||
playStyleBannerColor = playStyleBannerSprite.SourceElement.GetAttributeColor("bannercolor", Color.Black);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -23,6 +23,8 @@ namespace Barotrauma
|
||||
{
|
||||
sealed class MainMenuScreen : Screen
|
||||
{
|
||||
public static HashSet<Identifier> DismissedNotifications = new HashSet<Identifier>();
|
||||
|
||||
private enum Tab
|
||||
{
|
||||
NewGame = 0,
|
||||
@@ -588,6 +590,12 @@ namespace Barotrauma
|
||||
SelectTab(Tab.Empty);
|
||||
}
|
||||
|
||||
public static void AddDismissedNotification(Identifier id)
|
||||
{
|
||||
DismissedNotifications.Add(id);
|
||||
GameSettings.SaveCurrentConfig();
|
||||
}
|
||||
|
||||
private void SetMenuTabPositioning()
|
||||
{
|
||||
foreach (GUIFrame menuTab in menuTabs.Values)
|
||||
@@ -616,7 +624,7 @@ namespace Barotrauma
|
||||
};
|
||||
var tutorialPreview = new GUILayoutGroup(new RectTransform(new Vector2(0.6f, 1.0f), tutorialContent.RectTransform)) { RelativeSpacing = 0.05f, Stretch = true };
|
||||
var imageContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), tutorialPreview.RectTransform), style: "InnerFrame");
|
||||
tutorialBanner = new GUIImage(new RectTransform(Vector2.One, imageContainer.RectTransform), style: null, scaleToFit: true);
|
||||
tutorialBanner = new GUIImage(new RectTransform(Vector2.One, imageContainer.RectTransform), style: null, scaleToFit: GUIImage.ScalingMode.ScaleToFitSmallestExtent);
|
||||
|
||||
var infoContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), tutorialPreview.RectTransform), style: "GUIFrameListBox");
|
||||
var infoContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), infoContainer.RectTransform, Anchor.Center), childAnchor: Anchor.TopLeft)
|
||||
@@ -1382,7 +1390,7 @@ namespace Barotrauma
|
||||
var playstyleContainer = new GUIFrame(new RectTransform(new Vector2(1.35f, 0.1f), parent.RectTransform), style: null, color: Color.Black);
|
||||
|
||||
playstyleBanner = new GUIImage(new RectTransform(new Vector2(1.0f, 0.1f), playstyleContainer.RectTransform),
|
||||
GUIStyle.GetComponentStyle($"PlayStyleBanner.{PlayStyle.Serious}").GetSprite(GUIComponent.ComponentState.None), scaleToFit: true)
|
||||
GUIStyle.GetComponentStyle($"PlayStyleBanner.{PlayStyle.Serious}").GetSprite(GUIComponent.ComponentState.None), scaleToFit: GUIImage.ScalingMode.ScaleToFitSmallestExtent)
|
||||
{
|
||||
UserData = PlayStyle.Serious
|
||||
};
|
||||
|
||||
@@ -477,10 +477,20 @@ namespace Barotrauma
|
||||
AbsoluteSpacing = GUI.IntScale(5)
|
||||
};
|
||||
|
||||
Favorite = new GUITickBox(new RectTransform(new Vector2(0.5f, 0.5f), serverInfoContent.RectTransform, Anchor.TopRight, scaleBasis: ScaleBasis.BothHeight),
|
||||
|
||||
var topRightContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 0.5f), serverInfoContent.RectTransform, Anchor.TopRight),
|
||||
isHorizontal: true, childAnchor: Anchor.TopRight)
|
||||
{
|
||||
AbsoluteSpacing = GUI.IntScale(5),
|
||||
CanBeFocused = true
|
||||
};
|
||||
|
||||
SettingsButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), topRightContainer.RectTransform, Anchor.TopRight),
|
||||
TextManager.Get("ServerSettingsButton"), style: "GUIButtonFreeScale");
|
||||
|
||||
Favorite = new GUITickBox(new RectTransform(Vector2.One, topRightContainer.RectTransform, Anchor.TopRight, scaleBasis: ScaleBasis.BothHeight),
|
||||
"", null, "GUIServerListFavoriteTickBox")
|
||||
{
|
||||
IgnoreLayoutGroups = true,
|
||||
Selected = false,
|
||||
ToolTip = TextManager.Get("addtofavorites"),
|
||||
OnSelected = (tickbox) =>
|
||||
@@ -500,8 +510,6 @@ namespace Barotrauma
|
||||
}
|
||||
};
|
||||
|
||||
SettingsButton = new GUIButton(new RectTransform(new Vector2(0.25f, 0.4f), serverInfoContent.RectTransform, Anchor.TopRight),
|
||||
TextManager.Get("ServerSettingsButton"), style: "GUIButtonFreeScale");
|
||||
}
|
||||
|
||||
private void CreateServerMessagePopup(string serverName, string message)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma</Product>
|
||||
<Version>1.8.4.1</Version>
|
||||
<Version>1.8.6.2</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>Barotrauma</AssemblyName>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma</Product>
|
||||
<Version>1.8.4.1</Version>
|
||||
<Version>1.8.6.2</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>Barotrauma</AssemblyName>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma</Product>
|
||||
<Version>1.8.4.1</Version>
|
||||
<Version>1.8.6.2</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>Barotrauma</AssemblyName>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma Dedicated Server</Product>
|
||||
<Version>1.8.4.1</Version>
|
||||
<Version>1.8.6.2</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>DedicatedServer</AssemblyName>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma Dedicated Server</Product>
|
||||
<Version>1.8.4.1</Version>
|
||||
<Version>1.8.6.2</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>DedicatedServer</AssemblyName>
|
||||
|
||||
@@ -1341,12 +1341,13 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
match.BotStatus = pendingToReserveBench[i++] ? BotStatus.PendingHireToReserveBench : BotStatus.PendingHireToActiveService;
|
||||
if (match.BotStatus == BotStatus.PendingHireToActiveService)
|
||||
{
|
||||
//can't add more bots to active service is max has been reached
|
||||
if (pendingHireInfos.Count(ci => ci.BotStatus == BotStatus.PendingHireToActiveService) + CrewManager.GetCharacterInfos().Count() >= CrewManager.MaxCrewSize) { continue; }
|
||||
}
|
||||
|
||||
pendingHireInfos.Add(match);
|
||||
if (pendingHireInfos.Count(ci => ci.BotStatus == BotStatus.PendingHireToActiveService) + CrewManager.GetCharacterInfos().Count() >= CrewManager.MaxCrewSize)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
location.HireManager.PendingHires = pendingHireInfos;
|
||||
}
|
||||
|
||||
@@ -195,7 +195,11 @@ namespace Barotrauma.Networking
|
||||
|
||||
public void BanPlayer(string name, Either<Address, AccountId> addressOrAccountId, string reason, TimeSpan? duration)
|
||||
{
|
||||
if (addressOrAccountId.TryGet(out Address address) && address.IsLocalHost) { return; }
|
||||
if (addressOrAccountId.TryGet(out Address address) && address.IsLocalHost)
|
||||
{
|
||||
DebugConsole.AddWarning($"Cannot ban localhost ({address.StringRepresentation})");
|
||||
return;
|
||||
}
|
||||
|
||||
var existingBan = bannedPlayers.Find(bp => bp.AddressOrAccountId == addressOrAccountId);
|
||||
if (existingBan != null) { bannedPlayers.Remove(existingBan); }
|
||||
|
||||
@@ -200,11 +200,11 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
|
||||
PendingClient? pendingClient = pendingClients.Find(c => c.Connection.NetConnection == inc.SenderConnection);
|
||||
|
||||
if (pendingClient is null)
|
||||
{
|
||||
pendingClient = new PendingClient(new LidgrenConnection(inc.SenderConnection));
|
||||
pendingClients.Add(pendingClient);
|
||||
GameServer.Log($"Incoming connection from {pendingClient.Connection.NetConnection?.RemoteEndPoint?.ToString() ?? "null"}.", ServerLog.MessageType.ServerMessage);
|
||||
}
|
||||
|
||||
inc.SenderConnection.Approve();
|
||||
@@ -218,7 +218,25 @@ namespace Barotrauma.Networking
|
||||
|
||||
IReadMessage inc = lidgrenMsg.ToReadMessage();
|
||||
|
||||
var (_, packetHeader, initialization) = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
|
||||
PeerPacketHeaders peerPacketHeaders = default;
|
||||
try
|
||||
{
|
||||
peerPacketHeaders = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (pendingClient != null)
|
||||
{
|
||||
//pending (= not yet authenticated) client sent malformed data, immediately ban them so they can't use this for spamming
|
||||
GameServer.Log($"Received an invalid connection attempt from {pendingClient.Connection.NetConnection?.RemoteEndPoint?.ToString() ?? "null"}. Banning the IP.", ServerLog.MessageType.DoSProtection);
|
||||
serverSettings.BanList.BanPlayer(name: "Unknown", endpoint: pendingClient.Connection.Endpoint, reason: "Invalid connection attempt", duration: null);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
var (_, packetHeader, initialization) = peerPacketHeaders;
|
||||
|
||||
if (packetHeader.IsConnectionInitializationStep() && pendingClient != null && initialization.HasValue)
|
||||
{
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace Barotrauma.Networking
|
||||
public ConnectionInitialization InitializationStep;
|
||||
public double UpdateTime;
|
||||
public double TimeOut;
|
||||
public int Retries;
|
||||
public int PasswordRetries;
|
||||
public Int32? PasswordSalt;
|
||||
public bool AuthSessionStarted;
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace Barotrauma.Networking
|
||||
OwnerKey = Option.None;
|
||||
Connection = conn;
|
||||
InitializationStep = ConnectionInitialization.AuthInfoAndVersion;
|
||||
Retries = 0;
|
||||
PasswordRetries = 0;
|
||||
PasswordSalt = null;
|
||||
UpdateTime = Timing.TotalTime + Timing.Step * 3.0;
|
||||
TimeOut = NetworkConnection.TimeoutThreshold;
|
||||
@@ -156,8 +156,8 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
else
|
||||
{
|
||||
pendingClient.Retries++;
|
||||
if (serverSettings.BanAfterWrongPassword && pendingClient.Retries > serverSettings.MaxPasswordRetriesBeforeBan)
|
||||
pendingClient.PasswordRetries++;
|
||||
if (serverSettings.BanAfterWrongPassword && pendingClient.PasswordRetries > serverSettings.MaxPasswordRetriesBeforeBan)
|
||||
{
|
||||
const string banMsg = "Failed to enter correct password too many times";
|
||||
BanPendingClient(pendingClient, banMsg, null);
|
||||
@@ -281,7 +281,7 @@ namespace Barotrauma.Networking
|
||||
structToSend = new ServerPeerPasswordPacket
|
||||
{
|
||||
Salt = GetSalt(pendingClient),
|
||||
RetriesLeft = Option<int>.Some(pendingClient.Retries)
|
||||
RetriesLeft = Option<int>.Some(pendingClient.PasswordRetries)
|
||||
};
|
||||
|
||||
static Option<int> GetSalt(PendingClient client)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma Dedicated Server</Product>
|
||||
<Version>1.8.4.1</Version>
|
||||
<Version>1.8.6.2</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>DedicatedServer</AssemblyName>
|
||||
|
||||
@@ -213,7 +213,8 @@ namespace Barotrauma
|
||||
if (targetHull == null) { return false; }
|
||||
if (maxDistance > 0)
|
||||
{
|
||||
if (Vector2.DistanceSquared(Character.WorldPosition, targetWorldPos) > maxDistance * maxDistance) { return false; }
|
||||
// Too far from the gap.
|
||||
if (Vector2.DistanceSquared(Character.WorldPosition, gap.WorldPosition) > maxDistance * maxDistance) { return false; }
|
||||
}
|
||||
if (SteeringManager is IndoorsSteeringManager pathSteering)
|
||||
{
|
||||
|
||||
@@ -1128,9 +1128,9 @@ namespace Barotrauma
|
||||
searchingNewHull = false;
|
||||
}
|
||||
}
|
||||
if (patrolTarget != null && pathSteering.CurrentPath != null && !pathSteering.CurrentPath.Finished && !pathSteering.CurrentPath.Unreachable)
|
||||
if (patrolTarget != null && pathSteering.CurrentPath is { Finished: false, Unreachable: false })
|
||||
{
|
||||
PathSteering.SteeringSeek(Character.GetRelativeSimPosition(patrolTarget), weight: 1, minGapWidth: minGapSize * 1.5f, nodeFilter: n => PatrolNodeFilter(n));
|
||||
pathSteering.SteeringSeek(Character.GetRelativeSimPosition(patrolTarget), weight: 1, minGapWidth: minGapSize * 1.5f, nodeFilter: PatrolNodeFilter);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1570,22 +1570,22 @@ namespace Barotrauma
|
||||
Vector2 toTarget = attackWorldPos - attackLimbPos;
|
||||
Vector2 toTargetOffset = toTarget;
|
||||
// Add a margin when the target is moving away, because otherwise it might be difficult to reach it if the attack takes some time to execute
|
||||
if (wallTarget != null && Character.Submarine == null)
|
||||
if (Character.Submarine == null)
|
||||
{
|
||||
if (wallTarget.Structure.Submarine != null)
|
||||
if (wallTarget != null)
|
||||
{
|
||||
Vector2 margin = CalculateMargin(wallTarget.Structure.Submarine.Velocity);
|
||||
if (wallTarget.Structure.Submarine != null)
|
||||
{
|
||||
Vector2 margin = CalculateMargin(wallTarget.Structure.Submarine.Velocity);
|
||||
toTargetOffset += margin;
|
||||
}
|
||||
}
|
||||
else if (targetCharacter != null)
|
||||
{
|
||||
Vector2 margin = CalculateMargin(targetCharacter.AnimController.Collider.LinearVelocity);
|
||||
toTargetOffset += margin;
|
||||
}
|
||||
}
|
||||
else if (targetCharacter != null)
|
||||
{
|
||||
Vector2 margin = CalculateMargin(targetCharacter.AnimController.Collider.LinearVelocity);
|
||||
toTargetOffset += margin;
|
||||
}
|
||||
else if (SelectedAiTarget.Entity is MapEntity e)
|
||||
{
|
||||
if (e.Submarine != null)
|
||||
else if (SelectedAiTarget.Entity is MapEntity { Submarine: not null } e)
|
||||
{
|
||||
Vector2 margin = CalculateMargin(e.Submarine.Velocity);
|
||||
toTargetOffset += margin;
|
||||
@@ -1666,12 +1666,15 @@ namespace Barotrauma
|
||||
// Crouch if the target is down (only humanoids), so that we can reach it.
|
||||
if (Character.AnimController is HumanoidAnimController humanoidAnimController && distance < AttackLimb.attack.Range * 2)
|
||||
{
|
||||
if (Math.Abs(toTarget.Y) > AttackLimb.attack.Range / 2 && Math.Abs(toTarget.X) <= AttackLimb.attack.Range)
|
||||
if (targetCharacter?.CurrentHull is Hull targetHull && targetHull == Character.CurrentHull && toTarget.Y < 0)
|
||||
{
|
||||
humanoidAnimController.Crouch();
|
||||
if (Math.Abs(toTarget.Y) > AttackLimb.attack.Range / 2 && Math.Abs(toTarget.X) <= AttackLimb.attack.Range)
|
||||
{
|
||||
humanoidAnimController.Crouch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (canAttack)
|
||||
{
|
||||
if (AttackLimb.attack.Ranged)
|
||||
@@ -1706,6 +1709,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (wallTarget == null && Character.CurrentHull != null && targetCharacter != null)
|
||||
{
|
||||
canAttack = Submarine.PickBody(SimPosition, attackSimPos, collisionCategory: Physics.CollisionWall) == null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Limb steeringLimb = canAttack && !AttackLimb.attack.Ranged ? AttackLimb : null;
|
||||
@@ -1720,6 +1727,8 @@ namespace Barotrauma
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
}
|
||||
// Note that this returns null when we don't currently use IndoorsSteeringManager, even if we are able to path!
|
||||
// -> Don't use PathSteering, because that returns the reference even if we currently don't use it.
|
||||
var pathSteering = SteeringManager as IndoorsSteeringManager;
|
||||
if (AttackLimb != null && AttackLimb.attack.Retreat)
|
||||
{
|
||||
@@ -1727,58 +1736,71 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector2 steerPos = attackSimPos;
|
||||
if (!Character.AnimController.SimplePhysicsEnabled)
|
||||
{
|
||||
// Offset so that we don't overshoot the movement
|
||||
Vector2 offset = Character.SimPosition - steeringLimb.SimPosition;
|
||||
steerPos += offset;
|
||||
}
|
||||
if (pathSteering != null)
|
||||
{
|
||||
if (pathSteering.CurrentPath != null)
|
||||
// Attack doors
|
||||
if (canAttackDoors && HasValidPath())
|
||||
{
|
||||
// Attack doors
|
||||
if (canAttackDoors)
|
||||
// If the target is in the same hull, there shouldn't be any doors blocking the path
|
||||
if (targetCharacter == null || targetCharacter.CurrentHull != Character.CurrentHull)
|
||||
{
|
||||
// If the target is in the same hull, there shouldn't be any doors blocking the path
|
||||
if (targetCharacter == null || targetCharacter.CurrentHull != Character.CurrentHull)
|
||||
var door = pathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? pathSteering.CurrentPath.NextNode?.ConnectedDoor;
|
||||
if (door is { CanBeTraversed: false } && (!Character.IsInFriendlySub || !door.HasAccess(Character)))
|
||||
{
|
||||
var door = pathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? pathSteering.CurrentPath.NextNode?.ConnectedDoor;
|
||||
if (door is { CanBeTraversed: false } && (!Character.IsInFriendlySub || !door.HasAccess(Character)))
|
||||
if (door.Item.AiTarget != null && SelectedAiTarget != door.Item.AiTarget)
|
||||
{
|
||||
if (door.Item.AiTarget != null && SelectedAiTarget != door.Item.AiTarget)
|
||||
{
|
||||
SelectTarget(door.Item.AiTarget, currentTargetMemory.Priority);
|
||||
State = AIState.Attack;
|
||||
return;
|
||||
}
|
||||
SelectTarget(door.Item.AiTarget, currentTargetMemory.Priority);
|
||||
State = AIState.Attack;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// When pursuing, we don't want to pursue too close
|
||||
float max = 300;
|
||||
float margin = AttackLimb != null ? Math.Min(AttackLimb.attack.Range * 0.9f, max) : max;
|
||||
if (!canAttack || distance > margin)
|
||||
}
|
||||
// When pursuing, we don't want to pursue too close
|
||||
float max = 300;
|
||||
float margin = AttackLimb != null ? Math.Min(AttackLimb.attack.Range * 0.9f, max) : max;
|
||||
if ((!canAttack || distance > margin) && !IsTryingToSteerThroughGap)
|
||||
{
|
||||
// Steer towards the target if in the same room and swimming
|
||||
// Ruins have walls/pillars inside hulls and therefore we should navigate around them using the path steering.
|
||||
if (Character.CurrentHull != null &&
|
||||
Character.Submarine != null && !Character.Submarine.Info.IsRuin &&
|
||||
(Character.AnimController.InWater || pursue || !Character.AnimController.CanWalk) &&
|
||||
targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull))
|
||||
{
|
||||
// Steer towards the target if in the same room and swimming
|
||||
// Ruins have walls/pillars inside hulls and therefore we should navigate around them using the path steering.
|
||||
if (Character.CurrentHull != null &&
|
||||
Character.Submarine != null && !Character.Submarine.Info.IsRuin &&
|
||||
(Character.AnimController.InWater || pursue || !Character.AnimController.CanWalk) &&
|
||||
targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull))
|
||||
Vector2 myPos = Character.AnimController.SimplePhysicsEnabled ? Character.SimPosition : steeringLimb.SimPosition;
|
||||
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(attackSimPos - myPos));
|
||||
}
|
||||
else
|
||||
{
|
||||
Func<PathNode, bool> nodeFilter = null;
|
||||
float outsideNodePenalty = 0;
|
||||
if (Character.CurrentHull != null && Character.IsInPlayerSub)
|
||||
{
|
||||
Vector2 myPos = Character.AnimController.SimplePhysicsEnabled ? Character.SimPosition : steeringLimb.SimPosition;
|
||||
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(steerPos - myPos));
|
||||
// Prefer not to path back outside, if we are inside player sub.
|
||||
// Fixes monsters sometimes pathing from the airlock to another airlock on the other side of the sub, because the path is technically cheaper than the path through the interiors.
|
||||
outsideNodePenalty = 50;
|
||||
}
|
||||
else
|
||||
{
|
||||
pathSteering.SteeringSeek(steerPos, weight: 2,
|
||||
minGapWidth: minGapSize,
|
||||
startNodeFilter: n => (n.Waypoint.CurrentHull == null) == (Character.CurrentHull == null),
|
||||
checkVisiblity: true);
|
||||
pathSteering.SteeringSeek(Character.GetRelativeSimPosition(SelectedAiTarget.Entity), weight: 2,
|
||||
minGapWidth: minGapSize,
|
||||
startNodeFilter: n => (n.Waypoint.CurrentHull == null) == (Character.CurrentHull == null),
|
||||
nodeFilter: nodeFilter,
|
||||
checkVisiblity: true,
|
||||
outsideNodePenalty: outsideNodePenalty);
|
||||
|
||||
if (!pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable)
|
||||
if (pathSteering.CurrentPath != null)
|
||||
{
|
||||
if (pathSteering.IsPathDirty)
|
||||
{
|
||||
if (Character.CurrentHull is Hull hull && hull.ConnectedGaps.Any(static g => !g.IsRoomToRoom && g.Open >= 1.0f && g.ConnectedDoor != null))
|
||||
{
|
||||
// Reset in the airlock, because otherwise the character may be too slow to change the steering and keep moving outside.
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
// Steer towards the target
|
||||
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(SelectedAiTarget.Entity.WorldPosition - Character.WorldPosition));
|
||||
}
|
||||
else if (pathSteering.CurrentPath.Unreachable)
|
||||
{
|
||||
State = AIState.Idle;
|
||||
IgnoreTarget(SelectedAiTarget);
|
||||
@@ -1787,39 +1809,42 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!IsTryingToSteerThroughGap)
|
||||
}
|
||||
else if (!IsTryingToSteerThroughGap)
|
||||
{
|
||||
if (AttackLimb.attack.Ranged)
|
||||
{
|
||||
if (AttackLimb.attack.Ranged)
|
||||
float dir = Character.AnimController.Dir;
|
||||
if (dir > 0 && attackWorldPos.X > AttackLimb.WorldPosition.X + margin || dir < 0 && attackWorldPos.X < AttackLimb.WorldPosition.X - margin)
|
||||
{
|
||||
float dir = Character.AnimController.Dir;
|
||||
if (dir > 0 && attackWorldPos.X > AttackLimb.WorldPosition.X + margin || dir < 0 && attackWorldPos.X < AttackLimb.WorldPosition.X - margin)
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Too close
|
||||
UpdateFallBack(attackWorldPos, deltaTime, followThrough: false);
|
||||
}
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Close enough
|
||||
SteeringManager.Reset();
|
||||
// Too close
|
||||
UpdateFallBack(attackWorldPos, deltaTime, followThrough: false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(SelectedAiTarget.Entity.WorldPosition - Character.WorldPosition));
|
||||
// Close enough
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pathSteering.SteeringSeek(steerPos, weight: 5, minGapWidth: minGapSize);
|
||||
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(SelectedAiTarget.Entity.WorldPosition - Character.WorldPosition));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector2 steerPos = attackSimPos;
|
||||
if (!Character.AnimController.SimplePhysicsEnabled)
|
||||
{
|
||||
// Offset so that we don't overshoot the movement
|
||||
Vector2 offset = Character.SimPosition - steeringLimb.SimPosition;
|
||||
steerPos += offset;
|
||||
}
|
||||
// Sweeping and circling doesn't work well inside
|
||||
if (Character.CurrentHull == null)
|
||||
{
|
||||
|
||||
@@ -119,10 +119,13 @@ namespace Barotrauma
|
||||
steering += base.DoSteeringSeek(targetSimPos, weight);
|
||||
}
|
||||
|
||||
public void SteeringSeek(Vector2 target, float weight, float minGapWidth = 0, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisiblity = true)
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// <param name="outsideNodePenalty">Additional cost applied to outside nodes. When the character is inside an extra cost of 100 is also automatically added for outside nodes, unless the character is protected from the pressure. Used for example to prevent monsters from preferring outside nodes when they already are inside.</param>
|
||||
public void SteeringSeek(Vector2 target, float weight, float minGapWidth = 0, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisiblity = true, float outsideNodePenalty = 0)
|
||||
{
|
||||
// Have to use a variable here or resetting doesn't work.
|
||||
Vector2 addition = CalculateSteeringSeek(target, weight, minGapWidth, startNodeFilter, endNodeFilter, nodeFilter, checkVisiblity);
|
||||
Vector2 addition = CalculateSteeringSeek(target, weight, minGapWidth, startNodeFilter, endNodeFilter, nodeFilter, checkVisiblity, outsideNodePenalty);
|
||||
steering += addition;
|
||||
}
|
||||
|
||||
@@ -139,9 +142,9 @@ namespace Barotrauma
|
||||
return null;
|
||||
}
|
||||
|
||||
private Vector2 CalculateSteeringSeek(Vector2 target, float weight, float minGapSize = 0, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisibility = true)
|
||||
private Vector2 CalculateSteeringSeek(Vector2 target, float weight, float minGapSize = 0, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisibility = true, float outsideNodePenalty = 0)
|
||||
{
|
||||
bool needsNewPath = currentPath == null || currentPath.Unreachable || currentPath.Finished || currentPath.CurrentNode == null;
|
||||
bool needsNewPath = currentPath == null || currentPath.Unreachable || currentPath.Finished || currentPath.IsAtEndNode || currentPath.CurrentNode == null;
|
||||
if (!needsNewPath && character.Submarine != null && character.Params.PathFinderPriority > 0.5f)
|
||||
{
|
||||
// If the target has moved, we need a new path.
|
||||
@@ -182,7 +185,7 @@ namespace Barotrauma
|
||||
Vector2 currentPos = host.SimPosition;
|
||||
pathFinder.InsideSubmarine = character.Submarine != null && !character.Submarine.Info.IsRuin;
|
||||
pathFinder.ApplyPenaltyToOutsideNodes = character.Submarine != null && !character.IsProtectedFromPressure;
|
||||
var newPath = pathFinder.FindPath(currentPos, target, character.Submarine, "(Character: " + character.Name + ")", minGapSize, startNodeFilter, endNodeFilter, nodeFilter, checkVisibility: checkVisibility);
|
||||
var newPath = pathFinder.FindPath(currentPos, target, character.Submarine, "(Character: " + character.Name + ")", minGapSize, startNodeFilter, endNodeFilter, nodeFilter, checkVisibility: checkVisibility, outsideNodePenalty);
|
||||
bool useNewPath = needsNewPath;
|
||||
if (!useNewPath && currentPath?.CurrentNode != null && newPath.Nodes.Any() && !newPath.Unreachable)
|
||||
{
|
||||
@@ -792,7 +795,7 @@ namespace Barotrauma
|
||||
float? penalty = GetSingleNodePenalty(nextNode);
|
||||
if (penalty == null) { return null; }
|
||||
bool nextNodeAboveWaterLevel = nextNode.Waypoint.CurrentHull != null && nextNode.Waypoint.CurrentHull.Surface < nextNode.Waypoint.Position.Y;
|
||||
if (!character.CanClimb)
|
||||
if (!character.CanClimb && node.Waypoint.Stairs == null && nextNode.Waypoint.Stairs == null)
|
||||
{
|
||||
if (node.Waypoint.Ladders != null && nextNode.Waypoint.Ladders != null && (!nextNode.Waypoint.Ladders.Item.IsInteractable(character) || character.LockHands) ||
|
||||
(nextNode.Position.Y - node.Position.Y > 1.0f && //more than one sim unit to climb up
|
||||
@@ -888,6 +891,7 @@ namespace Barotrauma
|
||||
return penalty;
|
||||
}
|
||||
|
||||
// TODO: Long and complex. Consider refactoring.
|
||||
public static float smallRoomSize = 500;
|
||||
public void Wander(float deltaTime, float wallAvoidDistance = 150, bool stayStillInTightSpace = true)
|
||||
{
|
||||
@@ -915,36 +919,92 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
float leftDist = character.Position.X - currentHull.Rect.X;
|
||||
float rightDist = currentHull.Rect.Right - character.Position.X;
|
||||
if (leftDist < wallAvoidDistance && rightDist < wallAvoidDistance)
|
||||
bool isVerySmallRoom = roomWidth < smallRoomSize;
|
||||
Hull nextRoom = null;
|
||||
if (!stayStillInTightSpace && isVerySmallRoom)
|
||||
{
|
||||
if (Math.Abs(rightDist - leftDist) > wallAvoidDistance / 2)
|
||||
float closestDistance = 0;
|
||||
// Try to steer to the next room
|
||||
foreach (Gap gap in currentHull.ConnectedGaps)
|
||||
{
|
||||
SteeringManual(deltaTime, Vector2.UnitX * Math.Sign(rightDist - leftDist));
|
||||
return;
|
||||
}
|
||||
else if (stayStillInTightSpace)
|
||||
{
|
||||
Reset();
|
||||
return;
|
||||
if (gap.Open < 1.0f) { continue; }
|
||||
float feetPos = ConvertUnits.ToDisplayUnits(character.AnimController.FloorY);
|
||||
float gapTop = gap.Rect.Y;
|
||||
float gapBottom = gap.Rect.Y - gap.Rect.Height;
|
||||
const float margin = 25;
|
||||
if (character.Position.Y > gapTop || feetPos < gapBottom - margin)
|
||||
{
|
||||
// If the character is above or below the gap, they can't walk through it.
|
||||
continue;
|
||||
}
|
||||
Hull room = null;
|
||||
foreach (var entity in gap.linkedTo)
|
||||
{
|
||||
if (entity is not Hull hull) { continue; }
|
||||
if (hull.Submarine != character.Submarine) { continue; }
|
||||
if (hull.Rect.Width < smallRoomSize) { continue; }
|
||||
if (hull != currentHull)
|
||||
{
|
||||
room = hull;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (room == null) { continue; }
|
||||
Vector2 toGap = gap.Position - character.Position;
|
||||
float distance = Math.Abs(toGap.X);
|
||||
if (nextRoom == null || distance < closestDistance)
|
||||
{
|
||||
closestDistance = distance;
|
||||
nextRoom = room;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (leftDist < wallAvoidDistance)
|
||||
if (nextRoom != null)
|
||||
{
|
||||
float speed = (wallAvoidDistance - leftDist) / wallAvoidDistance;
|
||||
SteeringManual(deltaTime, Vector2.UnitX * MathHelper.Clamp(speed, 0.25f, 1));
|
||||
float toNextRoom = nextRoom.Position.X - character.Position.X;
|
||||
SteeringManual(deltaTime, Vector2.UnitX * Math.Sign(toNextRoom));
|
||||
WanderAngle = 0.0f;
|
||||
}
|
||||
else if (rightDist < wallAvoidDistance)
|
||||
{
|
||||
float speed = (wallAvoidDistance - rightDist) / wallAvoidDistance;
|
||||
SteeringManual(deltaTime, -Vector2.UnitX * MathHelper.Clamp(speed, 0.25f, 1));
|
||||
WanderAngle = MathHelper.Pi;
|
||||
}
|
||||
else
|
||||
{
|
||||
wander = true;
|
||||
if (!stayStillInTightSpace && isVerySmallRoom)
|
||||
{
|
||||
// Reset regardless, because moving inside the room would look glitchy.
|
||||
Reset();
|
||||
return;
|
||||
}
|
||||
float leftDist = character.Position.X - currentHull.Rect.X;
|
||||
float rightDist = currentHull.Rect.Right - character.Position.X;
|
||||
if (leftDist < wallAvoidDistance && rightDist < wallAvoidDistance)
|
||||
{
|
||||
float diff = rightDist - leftDist;
|
||||
if (Math.Abs(diff) > wallAvoidDistance / 2)
|
||||
{
|
||||
SteeringManual(deltaTime, Vector2.UnitX * Math.Sign(diff));
|
||||
return;
|
||||
}
|
||||
else if (stayStillInTightSpace)
|
||||
{
|
||||
Reset();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (leftDist < wallAvoidDistance)
|
||||
{
|
||||
float speed = (wallAvoidDistance - leftDist) / wallAvoidDistance;
|
||||
SteeringManual(deltaTime, Vector2.UnitX * MathHelper.Clamp(speed, 0.25f, 1));
|
||||
WanderAngle = 0.0f;
|
||||
}
|
||||
else if (rightDist < wallAvoidDistance)
|
||||
{
|
||||
float speed = (wallAvoidDistance - rightDist) / wallAvoidDistance;
|
||||
SteeringManual(deltaTime, -Vector2.UnitX * MathHelper.Clamp(speed, 0.25f, 1));
|
||||
WanderAngle = MathHelper.Pi;
|
||||
}
|
||||
else
|
||||
{
|
||||
wander = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ namespace Barotrauma
|
||||
|
||||
private readonly List<PathNode> sortedNodes;
|
||||
|
||||
public SteeringPath FindPath(Vector2 start, Vector2 end, Submarine hostSub = null, string errorMsgStr = null, float minGapSize = 0, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisibility = true)
|
||||
public SteeringPath FindPath(Vector2 start, Vector2 end, Submarine hostSub = null, string errorMsgStr = null, float minGapSize = 0, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisibility = true, float outsideNodePenalty = 0)
|
||||
{
|
||||
foreach (PathNode node in nodes)
|
||||
{
|
||||
@@ -325,7 +325,12 @@ namespace Barotrauma
|
||||
#endif
|
||||
return new SteeringPath(true);
|
||||
}
|
||||
return FindPath(startNode, endNode, nodeFilter, errorMsgStr, minGapSize);
|
||||
float outsideNodeCostPenalty = outsideNodePenalty;
|
||||
if (ApplyPenaltyToOutsideNodes)
|
||||
{
|
||||
outsideNodeCostPenalty += 100;
|
||||
}
|
||||
return FindPath(startNode, endNode, nodeFilter, errorMsgStr, minGapSize, outsideNodeCostPenalty);
|
||||
|
||||
bool IsValidStartNode(PathNode node) => IsValidNode(node, (isCharacter, start), startNodeFilter);
|
||||
|
||||
@@ -356,7 +361,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private SteeringPath FindPath(PathNode start, PathNode end, Func<PathNode, bool> filter = null, string errorMsgStr = "", float minGapSize = 0)
|
||||
private SteeringPath FindPath(PathNode start, PathNode end, Func<PathNode, bool> filter = null, string errorMsgStr = "", float minGapSize = 0f, float outsideNodePenalty = 0f)
|
||||
{
|
||||
if (start == end)
|
||||
{
|
||||
@@ -398,50 +403,65 @@ namespace Barotrauma
|
||||
for (int i = 0; i < currNode.connections.Count; i++)
|
||||
{
|
||||
PathNode nextNode = currNode.connections[i];
|
||||
|
||||
//a node that hasn't been searched yet
|
||||
if (nextNode.state == 0)
|
||||
{
|
||||
nextNode.H = Vector2.Distance(nextNode.Position, end.Position);
|
||||
|
||||
float penalty = 0.0f;
|
||||
if (GetNodePenalty != null)
|
||||
switch (nextNode.state)
|
||||
{
|
||||
//a node that hasn't been searched yet
|
||||
case 0:
|
||||
{
|
||||
float? nodePenalty = GetNodePenalty(currNode, nextNode);
|
||||
if (nodePenalty == null)
|
||||
nextNode.H = Vector2.Distance(nextNode.Position, end.Position);
|
||||
float cost = CalculateNodeCost();
|
||||
if (cost < float.PositiveInfinity)
|
||||
{
|
||||
nextNode.state = -1;
|
||||
continue;
|
||||
nextNode.G = cost;
|
||||
nextNode.F = nextNode.G + nextNode.H;
|
||||
nextNode.Parent = currNode;
|
||||
nextNode.state = 1;
|
||||
}
|
||||
penalty = nodePenalty.Value;
|
||||
else
|
||||
{
|
||||
// Set searched and invalid.
|
||||
nextNode.state = -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
//node that has been searched
|
||||
case 1 or -1:
|
||||
{
|
||||
float tempG = CalculateNodeCost();
|
||||
//only use if this new route is better than the
|
||||
//route the node was a part of
|
||||
if (tempG < nextNode.G)
|
||||
{
|
||||
nextNode.G = tempG;
|
||||
nextNode.F = nextNode.G + nextNode.H;
|
||||
nextNode.Parent = currNode;
|
||||
nextNode.state = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
nextNode.G = currNode.G + currNode.distances[i] + penalty;
|
||||
nextNode.F = nextNode.G + nextNode.H;
|
||||
nextNode.Parent = currNode;
|
||||
nextNode.state = 1;
|
||||
}
|
||||
//node that has been searched
|
||||
else if (nextNode.state == 1 || nextNode.state == -1)
|
||||
|
||||
float CalculateNodeCost()
|
||||
{
|
||||
float tempG = currNode.G + currNode.distances[i];
|
||||
|
||||
float penalty = 0f;
|
||||
if (GetNodePenalty != null)
|
||||
{
|
||||
float? nodePenalty = GetNodePenalty(currNode, nextNode);
|
||||
if (nodePenalty == null) { continue; }
|
||||
tempG += nodePenalty.Value;
|
||||
if (nodePenalty.HasValue)
|
||||
{
|
||||
penalty += nodePenalty.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return float.PositiveInfinity;
|
||||
}
|
||||
}
|
||||
|
||||
//only use if this new route is better than the
|
||||
//route the node was a part of
|
||||
if (tempG < nextNode.G)
|
||||
if (currNode.Waypoint.CurrentHull == null)
|
||||
{
|
||||
nextNode.G = tempG;
|
||||
nextNode.F = nextNode.G + nextNode.H;
|
||||
nextNode.Parent = currNode;
|
||||
nextNode.state = 1;
|
||||
penalty += outsideNodePenalty;
|
||||
}
|
||||
return currNode.G + currNode.distances[i] + penalty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2926,13 +2926,13 @@ namespace Barotrauma
|
||||
Entity.Spawner.AddCharacterToSpawnQueue(CharacterPrefab.HumanSpeciesName, spawnPosition, humanPrefab.CreateCharacterInfo(), onSpawn: newCharacter =>
|
||||
{
|
||||
newCharacter.HumanPrefab = humanPrefab;
|
||||
AddToCrew(newCharacter);
|
||||
humanPrefab.GiveItems(newCharacter, newCharacter.Submarine, spawnPoint);
|
||||
humanPrefab.InitializeCharacter(newCharacter);
|
||||
#if SERVER
|
||||
newCharacter.LoadTalents();
|
||||
GameMain.NetworkMember.CreateEntityEvent(newCharacter, new Character.UpdateTalentsEventData());
|
||||
#endif
|
||||
PostSpawnHuman(newCharacter);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2942,10 +2942,10 @@ namespace Barotrauma
|
||||
CharacterInfo characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: job, variant: variant);
|
||||
Entity.Spawner.AddCharacterToSpawnQueue(CharacterPrefab.HumanSpeciesName, spawnPosition, characterInfo, onSpawn: newCharacter =>
|
||||
{
|
||||
AddToCrew(newCharacter);
|
||||
newCharacter.GiveJobItems(isPvPMode: GameMain.GameSession?.GameMode is PvPMode, spawnPoint);
|
||||
newCharacter.GiveIdCardTags(spawnPoint);
|
||||
newCharacter.Info.StartItemsGiven = true;
|
||||
PostSpawnHuman(newCharacter);
|
||||
});
|
||||
}
|
||||
else if (CharacterPrefab.FindBySpeciesName(args[0].ToIdentifier()) is { } prefab)
|
||||
@@ -2953,7 +2953,7 @@ namespace Barotrauma
|
||||
Entity.Spawner.AddCharacterToSpawnQueue(args[0].ToIdentifier(), spawnPosition, prefab.HasCharacterInfo ? new CharacterInfo(prefab.Identifier) : null);
|
||||
}
|
||||
|
||||
void PostSpawnHuman(Character newCharacter)
|
||||
void AddToCrew(Character newCharacter)
|
||||
{
|
||||
newCharacter.TeamID = teamType;
|
||||
if (addToCrew)
|
||||
|
||||
@@ -218,11 +218,11 @@ namespace Barotrauma
|
||||
//the first character in the CustomInteract callback is always the NPC and the 2nd the character who interacted with it
|
||||
//but the TriggerAction can configure the 1st and 2nd entity in either order,
|
||||
//let's make sure we pass the NPC and the interactor in the intended order
|
||||
if (e1 == npc && targets2.Contains(interactor))
|
||||
if (e1 == npc && ParentEvent.GetTargets(Target2Tag).Contains(interactor))
|
||||
{
|
||||
Trigger(npc, interactor);
|
||||
}
|
||||
else if (targets1.Contains(interactor) && e2 == npc)
|
||||
else if (ParentEvent.GetTargets(Target1Tag).Contains(interactor) && e2 == npc)
|
||||
{
|
||||
Trigger(interactor, npc);
|
||||
}
|
||||
|
||||
@@ -250,8 +250,7 @@ namespace Barotrauma
|
||||
}
|
||||
// Overrides the team change set in the base method.
|
||||
var teamId = element.GetAttributeEnum("teamid", requiresRescue ? CharacterTeamType.FriendlyNPC : CharacterTeamType.None);
|
||||
var originalTeam = Level.Loaded.StartOutpost?.TeamID ?? teamId;
|
||||
if (teamId != originalTeam)
|
||||
if (teamId != spawnedCharacter.TeamID)
|
||||
{
|
||||
spawnedCharacter.SetOriginalTeamAndChangeTeam(teamId);
|
||||
}
|
||||
|
||||
@@ -356,7 +356,7 @@ namespace Barotrauma
|
||||
//that allows the NPC to fight intruders and otherwise function in the outpost if the mission is configured to spawn the hostile NPCs in a friendly outpost
|
||||
if (teamId != originalTeam)
|
||||
{
|
||||
spawnedCharacter.SetOriginalTeamAndChangeTeam(teamId);
|
||||
spawnedCharacter.SetOriginalTeamAndChangeTeam(teamId, processImmediately: true);
|
||||
}
|
||||
if (element.GetAttribute("color") != null)
|
||||
{
|
||||
|
||||
@@ -542,6 +542,7 @@ namespace Barotrauma
|
||||
{
|
||||
currentConfig = Config.FromElement(currentConfigDoc.Root ?? throw new NullReferenceException("Config XML element is invalid: document is null."));
|
||||
#if CLIENT
|
||||
MainMenuScreen.DismissedNotifications = currentConfigDoc.Root.GetAttributeIdentifierArray(nameof(MainMenuScreen.DismissedNotifications), defaultValue: Array.Empty<Identifier>()).ToHashSet();
|
||||
ServerListFilters.Init(currentConfigDoc.Root.GetChildElement("serverfilters"));
|
||||
MultiplayerPreferences.Init(
|
||||
currentConfigDoc.Root.GetChildElement("player"),
|
||||
@@ -660,6 +661,8 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
root.Add(new XAttribute(nameof(MainMenuScreen.DismissedNotifications), string.Join(',', MainMenuScreen.DismissedNotifications.Select(n => n.Value))));
|
||||
|
||||
XElement serverFiltersElement = new XElement("serverfilters"); root.Add(serverFiltersElement);
|
||||
ServerListFilters.Instance.SaveTo(serverFiltersElement);
|
||||
|
||||
|
||||
@@ -1,9 +1,56 @@
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
v1.8.6.2
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
- Fixed some icons still scaling incorrectly (e.g. location icons on the campaign map, device icons on the status monitor).
|
||||
- Fixed device previews not appearing in the shipyard menu when you've selected a category with only swappable items but no upgrades (doesn't affect any vanilla content).
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
v1.8.6.1
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
- Fixed an issue that caused the scaling of item icons in the submarine editor (and potentially other places) to be incorrect.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
v1.8.6.0
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
- Fixed certain scripted events breaking when you switch the character (e.g. interaction icons disappearing from some NPCs).
|
||||
- Fixed hostages being hostile to you and the outpost team in jailbreak missions.
|
||||
- Fixed inability to communicate using the headset with characters spawned via the debug console (once again).
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
v1.8.5.0
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
- Updated translations.
|
||||
- Fixed "favorite" checkbox rendering behind the server settings button in the server lobby.
|
||||
- Fixed server not allowing new characters to be hired to the reserve bench if over the maximum crew size.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
v1.8.4.2
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
- Fixed PvP variants of the underwater scooter being sold in campaign stores.
|
||||
- Hid the new sorting and filtering options from vending machines.
|
||||
- Fixes to several AI issues that sometimes made it difficult for monsters to board the sub (e.g. repeatedly moving in and out of the airlock).
|
||||
- Fixes to issues that sometimes caused monsters that are indoors to attempt to attack a target through a wall/floor/ceiling even if they have a valid path to the target.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
v1.8.4.1
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
- Fixed characters sometimes ending up in an invalid state when a player goes back to the server lobby, opts to spectate and rejoins the round: the character wouldn't go unconscious or braindead as it should, but the client would not regain control of it either.
|
||||
- AI characters no longer accidentally hit friendly characters with melee weapons.
|
||||
- Fixed bots not reacting to enemies while following, unless being attacked.
|
||||
- Fixed bots being allowed to operate items in unsafe hulls, meaning they'd attempt to get to a device in a room with e.g. monsters or fires, and then immediately flee, and repeat.
|
||||
- AI fixes to make characters better at dealing with fires.
|
||||
- Fixed bots getting stuck in ladders while following the player and aiming at the enemy.
|
||||
- Fixed bots not facing the enemies (or sometimes glitching while trying to face both the player and the enemy) while following a player.
|
||||
- Fixes to several AI issues that caused issues when bots where trying to find safety.
|
||||
- Fixed bots sometimes changing targets when they should just stick to the current target, causing indecisive behavior.
|
||||
- Made bots crouch by default when using ranged weapons, so that other bots (or players) can shoot past them.
|
||||
- Improved AI behavior in close combat: when a bot has a valid ranged weapon in the inventory, they now try to keep the distance to the target and switch back to the ranged weapon when possible. Previously the bots just used melee, because they can't shoot the targets when they are really close.Improved AI behavior in close combat: when a bot has a valid ranged weapon in the inventory, they now try to keep the distance to the target and switch back to the ranged weapon when possible. Previously the bots just used melee, because they can't shoot the targets when they are really close.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
v1.8.4.0
|
||||
|
||||
Reference in New Issue
Block a user