diff --git a/Barotrauma/BarotraumaClient/ClientCode.shproj.user b/Barotrauma/BarotraumaClient/ClientCode.shproj.user
deleted file mode 100644
index 966b4ffb6..000000000
--- a/Barotrauma/BarotraumaClient/ClientCode.shproj.user
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
- true
-
-
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/AITarget.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/AITarget.cs
index a237a6a1e..bf6555d5d 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/AITarget.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/AITarget.cs
@@ -9,7 +9,7 @@ namespace Barotrauma
public void Draw(SpriteBatch spriteBatch)
{
- if (!ShowAITargets) return;
+ if (!ShowAITargets) { return; }
var pos = new Vector2(WorldPosition.X, -WorldPosition.Y);
if (soundRange > 0.0f)
{
@@ -43,7 +43,7 @@ namespace Barotrauma
}
else
{
- color = Color.WhiteSmoke;
+ //color = Color.WhiteSmoke;
// disable the indicators for structures, because they clutter the debug view
return;
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/EnemyAIController.cs
index d33b41d27..a9ff967ca 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/EnemyAIController.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/EnemyAIController.cs
@@ -17,7 +17,7 @@ namespace Barotrauma
if (State == AIState.Idle && PreviousState == AIState.Attack)
{
var target = _selectedAiTarget ?? _lastAiTarget;
- if (target != null)
+ if (target != null && target.Entity != null)
{
var memory = GetTargetMemory(target);
Vector2 targetPos = memory.Location;
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/HumanAIController.cs
index 726985bb1..fdf426063 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/HumanAIController.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/HumanAIController.cs
@@ -16,11 +16,6 @@ namespace Barotrauma
}*/
}
- partial void SetOrderProjSpecific(Order order, string option)
- {
- GameMain.GameSession.CrewManager.DisplayCharacterOrder(Character, order, option);
- }
-
public override void DebugDraw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch)
{
Vector2 pos = Character.WorldPosition;
@@ -40,7 +35,7 @@ namespace Barotrauma
var currentOrder = ObjectiveManager.CurrentOrder;
if (currentOrder != null)
{
- GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 20), $"ORDER: {currentOrder.DebugTag} ({currentOrder.GetPriority().FormatZeroDecimal()})", Color.White, Color.Black);
+ GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 20), $"ORDER: {currentOrder.DebugTag} ({currentOrder.Priority.FormatZeroDecimal()})", Color.White, Color.Black);
}
else if (ObjectiveManager.WaitTimer > 0)
{
@@ -51,17 +46,17 @@ namespace Barotrauma
{
if (currentOrder == null)
{
- GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 20), $"MAIN OBJECTIVE: {currentObjective.DebugTag} ({currentObjective.GetPriority().FormatZeroDecimal()})", Color.White, Color.Black);
+ GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 20), $"MAIN OBJECTIVE: {currentObjective.DebugTag} ({currentObjective.Priority.FormatZeroDecimal()})", Color.White, Color.Black);
}
- var subObjective = currentObjective.SubObjectives.FirstOrDefault();
+ var subObjective = currentObjective.CurrentSubObjective;
if (subObjective != null)
{
- GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 40), $"SUBOBJECTIVE: {subObjective.DebugTag} ({subObjective.GetPriority().FormatZeroDecimal()})", Color.White, Color.Black);
+ GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 40), $"SUBOBJECTIVE: {subObjective.DebugTag} ({subObjective.Priority.FormatZeroDecimal()})", Color.White, Color.Black);
}
var activeObjective = ObjectiveManager.GetActiveObjective();
if (activeObjective != null)
{
- GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 60), $"ACTIVE OBJECTIVE: {activeObjective.DebugTag} ({activeObjective.GetPriority().FormatZeroDecimal()})", Color.White, Color.Black);
+ GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 60), $"ACTIVE OBJECTIVE: {activeObjective.DebugTag} ({activeObjective.Priority.FormatZeroDecimal()})", Color.White, Color.Black);
}
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs
index a2e3ff53f..bdf4e44c8 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs
@@ -638,8 +638,7 @@ namespace Barotrauma
public void Draw(SpriteBatch spriteBatch, Camera cam)
{
- if (!Enabled) return;
-
+ if (!Enabled) { return; }
AnimController.Draw(spriteBatch, cam);
}
@@ -656,8 +655,6 @@ namespace Barotrauma
if (GameMain.DebugDraw)
{
AnimController.DebugDraw(spriteBatch);
-
- if (aiTarget != null) aiTarget.Draw(spriteBatch);
}
if (GUI.DisableHUD) return;
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs
index 2f2b23ece..d73a9aaef 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs
@@ -92,7 +92,7 @@ namespace Barotrauma
if (!character.IsUnconscious && character.Stun <= 0.0f)
{
- if (character.Info != null && !character.ShouldLockHud())
+ if (character.Info != null && !character.ShouldLockHud() && character.SelectedCharacter == null)
{
bool mouseOnPortrait = HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition) && GUI.MouseOn == null;
if (mouseOnPortrait && PlayerInput.PrimaryMouseButtonClicked())
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/AfflictionHusk.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/AfflictionHusk.cs
index 11f03da17..1ba700e72 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/AfflictionHusk.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/AfflictionHusk.cs
@@ -8,26 +8,25 @@ namespace Barotrauma
{
partial class AfflictionHusk : Affliction
{
- partial void UpdateMessages(float prevStrength, Character character)
+ partial void UpdateMessages()
{
- if (Strength < Prefab.MaxStrength * 0.5f)
+ switch (State)
{
- if (prevStrength % 10.0f > 0.05f && Strength % 10.0f < 0.05f)
- {
+ case InfectionState.Dormant:
GUI.AddMessage(TextManager.Get("HuskDormant"), GUI.Style.Red);
- }
- }
- else if (Strength < Prefab.MaxStrength)
- {
- if (state == InfectionState.Dormant && Character.Controlled == character)
- {
+ break;
+ case InfectionState.Transition:
GUI.AddMessage(TextManager.Get("HuskCantSpeak"), GUI.Style.Red);
- }
- }
- else if (state != InfectionState.Active && Character.Controlled == character)
- {
- GUI.AddMessage(TextManager.GetWithVariable("HuskActivate", "[Attack]", GameMain.Config.KeyBindText(InputType.Attack)),
- GUI.Style.Red);
+ break;
+ case InfectionState.Active:
+ if (character.Params.UseHuskAppendage)
+ {
+ GUI.AddMessage(TextManager.GetWithVariable("HuskActivate", "[Attack]", GameMain.Config.KeyBindText(InputType.Attack)), GUI.Style.Red);
+ }
+ break;
+ case InfectionState.Final:
+ default:
+ break;
}
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs
index 360e81399..ca43fc95b 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs
@@ -132,7 +132,7 @@ namespace Barotrauma
private List heartratePositions;
private float currentHeartrateTime;
private float heartbeatTimer;
- private Texture2D heartrateFade;
+ private static Texture2D heartrateFade;
private readonly HeartratePosition[] heartbeatPattern =
{
@@ -478,7 +478,7 @@ namespace Barotrauma
cprFrame = new GUIFrame(new RectTransform(new Vector2(0.7f, 1.0f), cprLayout.RectTransform), style: "GUIFrameListBox");
- heartrateFade = TextureLoader.FromFile("Content/UI/Health/HeartrateFade.png");
+ heartrateFade ??= TextureLoader.FromFile("Content/UI/Health/HeartrateFade.png");
new GUICustomComponent(new RectTransform(Vector2.One * 0.95f, cprFrame.RectTransform, Anchor.Center), DrawHeartrate, UpdateHeartrate);
@@ -816,9 +816,7 @@ namespace Barotrauma
if (highlightedLimbIndex < 0 && selectedLimbIndex < 0)
{
// If no limb is selected or highlighted, select the one with the most critical afflictions.
- var affliction = GetAllAfflictions(a => a.Prefab.IndicatorLimb != LimbType.None)
- .OrderByDescending(a => a.DamagePerSecond)
- .ThenByDescending(a => a.Strength).FirstOrDefault();
+ var affliction = SortAfflictionsBySeverity(GetAllAfflictions(a => a.Prefab.IndicatorLimb != LimbType.None)).FirstOrDefault();
if (affliction.DamagePerSecond > 0 || affliction.Strength > 0)
{
var limbHealth = GetMatchingLimbHealth(affliction);
@@ -1185,7 +1183,8 @@ namespace Barotrauma
Dictionary treatmentSuitability = new Dictionary();
GetSuitableTreatments(treatmentSuitability, normalize: true, randomization: randomVariance);
- Affliction mostSevereAffliction = afflictions.FirstOrDefault(a => !a.Prefab.IsBuff && !afflictions.Any(a2 => !a2.Prefab.IsBuff && a2.Strength > a.Strength)) ?? afflictions.FirstOrDefault();
+ //Affliction mostSevereAffliction = afflictions.FirstOrDefault(a => !a.Prefab.IsBuff && !afflictions.Any(a2 => !a2.Prefab.IsBuff && a2.Strength > a.Strength)) ?? afflictions.FirstOrDefault();
+ Affliction mostSevereAffliction = SortAfflictionsBySeverity(afflictions).FirstOrDefault();
GUIButton buttonToSelect = null;
foreach (Affliction affliction in afflictions)
@@ -1811,8 +1810,8 @@ namespace Barotrauma
float iconScale = 0.25f * scale;
Vector2 iconPos = highlightArea.Center.ToVector2();
- Affliction mostSevereAffliction = thisAfflictions.FirstOrDefault(a => !a.Prefab.IsBuff && !thisAfflictions.Any(a2 => !a2.Prefab.IsBuff && a2.Strength > a.Strength)) ?? thisAfflictions.FirstOrDefault();
-
+ //Affliction mostSevereAffliction = thisAfflictions.FirstOrDefault(a => !a.Prefab.IsBuff && !thisAfflictions.Any(a2 => !a2.Prefab.IsBuff && a2.Strength > a.Strength)) ?? thisAfflictions.FirstOrDefault();
+ Affliction mostSevereAffliction = SortAfflictionsBySeverity(thisAfflictions).FirstOrDefault();
if (mostSevereAffliction != null) { DrawLimbAfflictionIcon(spriteBatch, mostSevereAffliction, iconScale, ref iconPos); }
if (thisAfflictions.Count() > 1)
@@ -1992,6 +1991,9 @@ namespace Barotrauma
}
}
+ medUIExtra?.Remove();
+ medUIExtra = null;
+
limbIndicatorOverlay?.Remove();
limbIndicatorOverlay = null;
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs
index 021685c5d..0b42757c8 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs
@@ -220,9 +220,18 @@ namespace Barotrauma
}
set
{
- if (HuskSprite != null && value != enableHuskSprite)
+ if (enableHuskSprite == value) { return; }
+ enableHuskSprite = value;
+ if (enableHuskSprite)
{
- if (value)
+ if (HuskSprite == null)
+ {
+ LoadHuskSprite();
+ }
+ }
+ if (HuskSprite != null)
+ {
+ if (enableHuskSprite)
{
List otherWearablesWithHusk = new List() { HuskSprite };
otherWearablesWithHusk.AddRange(OtherWearables);
@@ -235,7 +244,6 @@ namespace Barotrauma
UpdateWearableTypesToHide();
}
}
- enableHuskSprite = value;
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs
index 70b15fe92..8728060a4 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs
@@ -195,6 +195,34 @@ namespace Barotrauma
}
}
+ private static bool IsCommandPermitted(string command, GameClient client)
+ {
+ switch (command)
+ {
+ case "kick":
+ return client.HasPermission(ClientPermissions.Kick);
+ case "ban":
+ case "banip":
+ case "banendpoint":
+ return client.HasPermission(ClientPermissions.Ban);
+ case "unban":
+ case "unbanip":
+ return client.HasPermission(ClientPermissions.Unban);
+ case "netstats":
+ case "help":
+ case "dumpids":
+ case "admin":
+ case "entitylist":
+ case "togglehud":
+ case "toggleupperhud":
+ case "togglecharacternames":
+ case "fpscounter":
+ return true;
+ default:
+ return client.HasConsoleCommandPermission(command);
+ }
+ }
+
public static void DequeueMessages()
{
while (queuedMessages.Count > 0)
@@ -367,6 +395,7 @@ namespace Barotrauma
NewMessage("Steam achievements have been disabled during this play session.", Color.Red);
#endif
}));
+ AssignRelayToServer("enablecheats", true);
commands.Add(new Command("mainmenu|menu", "mainmenu/menu: Go to the main menu.", (string[] args) =>
{
@@ -2113,6 +2142,11 @@ namespace Barotrauma
commands.Add(new Command("flipx", "flipx: mirror the main submarine horizontally", (string[] args) =>
{
+ if (GameMain.NetworkMember != null)
+ {
+ ThrowError("Cannot use the flipx command while playing online.");
+ return;
+ }
Submarine.MainSub?.FlipX();
}, isCheat: true));
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs
index d3e45860b..39494a661 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs
@@ -135,31 +135,33 @@ namespace Barotrauma
}
}
+ Color textColor = textBox.Color;
switch (command)
{
case "r":
case "radio":
- textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Radio];
+ textColor = ChatMessage.MessageColor[(int)ChatMessageType.Radio];
break;
case "d":
case "dead":
- textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Dead];
+ textColor = ChatMessage.MessageColor[(int)ChatMessageType.Dead];
break;
default:
if (Character.Controlled != null && (Character.Controlled.IsDead || Character.Controlled.SpeechImpediment >= 100.0f))
{
- textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Dead];
+ textColor = ChatMessage.MessageColor[(int)ChatMessageType.Dead];
}
else if (command != "") //PMing
{
- textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Private];
+ textColor = ChatMessage.MessageColor[(int)ChatMessageType.Private];
}
else
{
- textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Default];
+ textColor = ChatMessage.MessageColor[(int)ChatMessageType.Default];
}
break;
}
+ textBox.TextColor = textBox.TextBlock.SelectedTextColor = textColor;
return true;
}
@@ -252,21 +254,29 @@ namespace Barotrauma
Visible = false,
CanBeFocused = false
};
- var senderText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), popupMsg.RectTransform, Anchor.TopRight),
- senderName, textColor: senderColor, font: GUI.SmallFont, textAlignment: Alignment.TopRight)
+ var content = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), popupMsg.RectTransform, Anchor.Center));
+ Vector2 senderTextSize = Vector2.Zero;
+ if (!string.IsNullOrEmpty(senderName))
+ {
+ var senderText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform),
+ senderName, textColor: senderColor, style: null, font: GUI.SmallFont)
+ {
+ CanBeFocused = false
+ };
+ senderTextSize = senderText.Font.MeasureString(senderText.WrappedText);
+ senderText.RectTransform.MinSize = new Point(0, senderText.Rect.Height);
+ }
+ var msgPopupText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform),
+ displayedText, textColor: message.Color, font: GUI.SmallFont, textAlignment: Alignment.BottomLeft, style: null, wrap: true)
{
CanBeFocused = false
};
- var msgPopupText = new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.0f), popupMsg.RectTransform, Anchor.TopRight)
- { AbsoluteOffset = new Point(0, senderText.Rect.Height) },
- displayedText, textColor: message.Color, font: GUI.SmallFont, textAlignment: Alignment.TopRight, style: null, wrap: true)
- {
- CanBeFocused = false
- };
- int textWidth = (int)Math.Max(
- msgPopupText.Font.MeasureString(msgPopupText.WrappedText).X,
- senderText.Font.MeasureString(senderText.WrappedText).X);
- popupMsg.RectTransform.Resize(new Point(textWidth + 20, msgPopupText.Rect.Bottom - senderText.Rect.Y), resizeChildren: false);
+ msgPopupText.RectTransform.MinSize = new Point(0, msgPopupText.Rect.Height);
+ Vector2 msgSize = msgPopupText.Font.MeasureString(msgPopupText.WrappedText);
+ int textWidth = (int)Math.Max(msgSize.X + msgPopupText.Padding.X + msgPopupText.Padding.Z, senderTextSize.X) + 10;
+ popupMsg.RectTransform.Resize(new Point((int)(textWidth / content.RectTransform.RelativeSize.X) , (int)((senderTextSize.Y + msgSize.Y) / content.RectTransform.RelativeSize.Y)), resizeChildren: true);
+ popupMsg.RectTransform.IsFixedSize = true;
+ content.Recalculate();
popupMessages.Enqueue(popupMsg);
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/ComponentStyle.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/ComponentStyle.cs
index ec39f671c..f9d0b8092 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GUI/ComponentStyle.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/ComponentStyle.cs
@@ -5,16 +5,6 @@ using System.Xml.Linq;
namespace Barotrauma
{
- public enum TransitionMode
- {
- Linear,
- Smooth,
- Smoother,
- EaseIn,
- EaseOut,
- Exponential
- }
-
public enum SpriteFallBackState
{
None,
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs
index 77bb51dc1..077ac5ec4 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs
@@ -524,23 +524,9 @@ namespace Barotrauma
};
}
- private float GetEasing(TransitionMode easing, float t)
- {
- return easing switch
- {
- TransitionMode.Smooth => MathUtils.SmoothStep(t),
- TransitionMode.Smoother => MathUtils.SmootherStep(t),
- TransitionMode.EaseIn => MathUtils.EaseIn(t),
- TransitionMode.EaseOut => MathUtils.EaseOut(t),
- TransitionMode.Exponential => t * t,
- TransitionMode.Linear => t,
- _ => t,
- };
- }
-
protected Color GetBlendedColor(Color targetColor, ref Color blendedColor)
{
- blendedColor = ColorCrossFadeTime > 0 ? Color.Lerp(blendedColor, targetColor, MathUtils.InverseLerp(ColorCrossFadeTime, 0, GetEasing(ColorTransition, colorFadeTimer))) : targetColor;
+ blendedColor = ColorCrossFadeTime > 0 ? Color.Lerp(blendedColor, targetColor, MathUtils.InverseLerp(ColorCrossFadeTime, 0, ToolBox.GetEasing(ColorTransition, colorFadeTimer))) : targetColor;
return blendedColor;
}
@@ -598,7 +584,7 @@ namespace Barotrauma
foreach (UISprite uiSprite in previousSprites)
{
float alphaMultiplier = SpriteCrossFadeTime > 0 && (uiSprite.CrossFadeOut || currentSprites != null && currentSprites.Any(s => s.CrossFadeIn))
- ? MathUtils.InverseLerp(0, SpriteCrossFadeTime, GetEasing(uiSprite.TransitionMode, spriteFadeTimer)) : 0;
+ ? MathUtils.InverseLerp(0, SpriteCrossFadeTime, ToolBox.GetEasing(uiSprite.TransitionMode, spriteFadeTimer)) : 0;
if (alphaMultiplier > 0)
{
uiSprite.Draw(spriteBatch, rect, previousColor * alphaMultiplier, SpriteEffects);
@@ -612,7 +598,7 @@ namespace Barotrauma
foreach (UISprite uiSprite in currentSprites)
{
float alphaMultiplier = SpriteCrossFadeTime > 0 && (uiSprite.CrossFadeIn || previousSprites != null && previousSprites.Any(s => s.CrossFadeOut))
- ? MathUtils.InverseLerp(SpriteCrossFadeTime, 0, GetEasing(uiSprite.TransitionMode, spriteFadeTimer)) : (_currentColor.A / 255.0f);
+ ? MathUtils.InverseLerp(SpriteCrossFadeTime, 0, ToolBox.GetEasing(uiSprite.TransitionMode, spriteFadeTimer)) : (_currentColor.A / 255.0f);
if (alphaMultiplier > 0)
{
uiSprite.Draw(spriteBatch, rect, _currentColor * alphaMultiplier, SpriteEffects);
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIDropDown.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIDropDown.cs
index 0169c7bf5..f4b41a3e8 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIDropDown.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIDropDown.cs
@@ -95,6 +95,15 @@ namespace Barotrauma
set { button.TextColor = value; }
}
+ public override ScalableFont Font
+ {
+ get { return button?.Font ?? base.Font; }
+ set
+ {
+ if (button != null) { button.Font = value; }
+ }
+ }
+
public void ReceiveTextInput(char inputChar)
{
GUI.KeyboardDispatcher.Subscriber = null;
@@ -168,12 +177,11 @@ namespace Barotrauma
Anchor listAnchor = dropAbove ? Anchor.TopCenter : Anchor.BottomCenter;
Pivot listPivot = dropAbove ? Pivot.BottomCenter : Pivot.TopCenter;
listBox = new GUIListBox(new RectTransform(new Point(Rect.Width, Rect.Height * MathHelper.Clamp(elementCount, 2, 10)), rectT, listAnchor, listPivot)
-
{ IsFixedSize = false }, style: null)
{
- Enabled = !selectMultiple,
- OnSelected = SelectItem
+ Enabled = !selectMultiple
};
+ if (!selectMultiple) { listBox.OnSelected = SelectItem; }
GUI.Style.Apply(listBox, "GUIListBox", this);
GUI.Style.Apply(listBox.ContentBackground, "GUIListBox", this);
@@ -245,7 +253,7 @@ namespace Barotrauma
ToolTip = toolTip
};
- new GUITickBox(new RectTransform(new Point((int)(button.Rect.Height * 0.8f)), frame.RectTransform, anchor: Anchor.CenterLeft), text)
+ new GUITickBox(new RectTransform(new Vector2(1.0f, 0.8f), frame.RectTransform, anchor: Anchor.CenterLeft) { MaxSize = new Point(int.MaxValue, (int)(button.Rect.Height * 0.8f)) }, text)
{
UserData = userData,
ToolTip = toolTip,
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs
index 37fd18873..73daf81f0 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs
@@ -328,11 +328,7 @@ namespace Barotrauma
{
if (text == null) { return; }
- censoredText = "";
- for (int i = 0; i < text.Length; i++)
- {
- censoredText += "\u2022";
- }
+ censoredText = string.IsNullOrEmpty(text) ? "" : new string('\u2022', text.Length);
var rect = Rect;
@@ -446,9 +442,10 @@ namespace Barotrauma
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
if (overflowClipActive)
{
- spriteBatch.End();
Rectangle scissorRect = new Rectangle(rect.X + (int)padding.X, rect.Y, rect.Width - (int)padding.X - (int)padding.Z, rect.Height);
- spriteBatch.GraphicsDevice.ScissorRectangle = scissorRect;
+ if (!scissorRect.Intersects(prevScissorRect)) { return; }
+ spriteBatch.End();
+ spriteBatch.GraphicsDevice.ScissorRectangle = Rectangle.Intersect(prevScissorRect, scissorRect);
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs
index 8d86b74b2..136f5e544 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs
@@ -189,6 +189,11 @@ namespace Barotrauma
Instance = this;
+ if (!Directory.Exists(Content.RootDirectory))
+ {
+ throw new Exception("Content folder not found. If you are trying to compile the game from the source code and own a legal copy of the game, you can copy the Content folder from the game's files to BarotraumaShared/Content.");
+ }
+
Config = new GameSettings();
Md5Hash.LoadCache();
@@ -421,17 +426,27 @@ namespace Barotrauma
GUI.Init(Window, Config.SelectedContentPackages, GraphicsDevice);
DebugConsole.Init();
- CrossThread.RequestExecutionOnMainThread(() =>
+ if (Config.AutoUpdateWorkshopItems)
{
- if (Config.AutoUpdateWorkshopItems)
+ bool waitingForWorkshopUpdates = true;
+ bool result = false;
+ TaskPool.Add(SteamManager.AutoUpdateWorkshopItems(), (task) =>
{
- if (SteamManager.AutoUpdateWorkshopItems())
+ result = task.Result;
+ waitingForWorkshopUpdates = false;
+ });
+
+ while (waitingForWorkshopUpdates) { yield return CoroutineStatus.Running; }
+
+ if (result)
+ {
+ CrossThread.RequestExecutionOnMainThread(() =>
{
ContentPackage.LoadAll();
Config.ReloadContentPackages();
- }
+ });
}
- });
+ }
if (SelectedPackages.None())
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs
index 9e87390d5..b9bdbc7de 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs
@@ -330,17 +330,6 @@ namespace Barotrauma
}
AddCharacterToCrewList(character);
-
- if (character is AICharacter)
- {
- var ai = character.AIController as HumanAIController;
- if (ai == null)
- {
- DebugConsole.ThrowError("Error in crewmanager - attempted to give orders to a character with no HumanAIController");
- return;
- }
- character.SetOrder(ai.CurrentOrder, "", null, false);
- }
}
public void AddCharacterInfo(CharacterInfo characterInfo)
@@ -788,11 +777,11 @@ namespace Barotrauma
characterFrame.SetAsFirstChild();
}
- private void DisplayPreviousCharacterOrder(Character character, GUILayoutGroup characterComponent, OrderInfo currentOrderInfo)
+ private void DisplayPreviousCharacterOrder(Character character, GUILayoutGroup characterComponent, OrderInfo orderInfo)
{
- if (currentOrderInfo.Order == null || currentOrderInfo.Order.Identifier == dismissedOrderPrefab.Identifier) { return; }
+ if (orderInfo.Order == null || orderInfo.Order.Identifier == dismissedOrderPrefab.Identifier) { return; }
- var previousOrderInfo = new OrderInfo(currentOrderInfo);
+ var previousOrderInfo = new OrderInfo(orderInfo);
var prevOrderFrame = new GUIButton(
new RectTransform(
characterComponent.GetChildByUserData("job").RectTransform.RelativeSize,
@@ -835,12 +824,12 @@ namespace Barotrauma
private GUIComponent GetCurrentOrderComponent(GUILayoutGroup characterComponent)
{
- return characterComponent.FindChild(c => c.UserData is OrderInfo orderInfo && orderInfo.ComponentIdentifier == "currentorder");
+ return characterComponent?.FindChild(c => c?.UserData is OrderInfo orderInfo && orderInfo.ComponentIdentifier == "currentorder");
}
private GUIComponent GetPreviousOrderComponent(GUILayoutGroup characterComponent)
{
- return characterComponent.FindChild(c => c.UserData is OrderInfo orderInfo && orderInfo.ComponentIdentifier == "previousorder");
+ return characterComponent?.FindChild(c => c?.UserData is OrderInfo orderInfo && orderInfo.ComponentIdentifier == "previousorder");
}
private struct OrderInfo
@@ -910,7 +899,18 @@ namespace Barotrauma
{
if (!(c.UserData is Character character) || character.IsDead || character.Removed) { continue; }
AddCharacter(character);
- DisplayCharacterOrder(character, character.CurrentOrder, (character.AIController as HumanAIController)?.CurrentOrderOption);
+ if (c.GetChild() is GUILayoutGroup oldLayoutGroup)
+ {
+ if (GetCurrentOrderComponent(oldLayoutGroup)?.UserData is OrderInfo currInfo)
+ {
+ DisplayCharacterOrder(character, currInfo.Order, currInfo.OrderOption);
+ }
+ if (GetPreviousOrderComponent(oldLayoutGroup)?.UserData is OrderInfo prevInfo &&
+ crewList.Content.Children.FirstOrDefault(c => c?.UserData == character)?.GetChild() is GUILayoutGroup newLayoutGroup)
+ {
+ DisplayPreviousCharacterOrder(character, newLayoutGroup, prevInfo);
+ }
+ }
}
}
@@ -944,6 +944,7 @@ namespace Barotrauma
{
Character.Controlled.AIController.ObjectiveManager.WaitTimer = CharacterWaitOnSwitch;
}
+ DisableCommandUI();
Character.Controlled = character;
}
@@ -1176,19 +1177,22 @@ namespace Barotrauma
if (PlayerInput.KeyHit(InputType.RadioChat) && !ChatBox.InputBox.Selected)
{
- ChatBox.InputBox.AddToGUIUpdateList();
- ChatBox.GUIFrame.Flash(Color.YellowGreen, 0.5f);
- if (!ChatBox.ToggleOpen)
+ if (Character.Controlled == null || Character.Controlled.SpeechImpediment < 100)
{
- ChatBox.CloseAfterMessageSent = !ChatBox.ToggleOpen;
- ChatBox.ToggleOpen = true;
- }
+ ChatBox.InputBox.AddToGUIUpdateList();
+ ChatBox.GUIFrame.Flash(Color.YellowGreen, 0.5f);
+ if (!ChatBox.ToggleOpen)
+ {
+ ChatBox.CloseAfterMessageSent = !ChatBox.ToggleOpen;
+ ChatBox.ToggleOpen = true;
+ }
- if (!ChatBox.InputBox.Text.StartsWith(ChatBox.RadioChatString))
- {
- ChatBox.InputBox.Text = ChatBox.RadioChatString;
+ if (!ChatBox.InputBox.Text.StartsWith(ChatBox.RadioChatString))
+ {
+ ChatBox.InputBox.Text = ChatBox.RadioChatString;
+ }
+ ChatBox.InputBox.Select(ChatBox.InputBox.Text.Length);
}
- ChatBox.InputBox.Select(ChatBox.InputBox.Text.Length);
}
}
}
@@ -1293,13 +1297,21 @@ namespace Barotrauma
{
get
{
+#if DEBUG
+ return Character.Controlled == null || Character.Controlled.Info != null && Character.Controlled.SpeechImpediment < 100.0f;
+#else
return Character.Controlled != null && Character.Controlled.SpeechImpediment < 100.0f;
+#endif
}
}
private bool CanSomeoneHearCharacter()
{
+#if DEBUG
+ return true;
+#else
return Character.Controlled != null && characters.Any(c => c != Character.Controlled && c.CanHearCharacter(Character.Controlled));
+#endif
}
private void CreateCommandUI(Character characterContext = null)
@@ -1505,17 +1517,7 @@ namespace Barotrauma
shortcutCenterNode = null;
}
CreateNodes(userData);
- if (returnNode != null && returnNode.Visible)
- {
- var hotkey = optionNodes.Count + 1;
- if (expandNode != null && expandNode.Visible) { hotkey += 1; }
- CreateHotkeyIcon(returnNode.RectTransform, hotkey % 10, true);
- returnNodeHotkey = Keys.D0 + hotkey % 10;
- }
- else
- {
- returnNodeHotkey = Keys.None;
- }
+ CreateReturnNodeHotkey();
return true;
}
@@ -1539,10 +1541,20 @@ namespace Barotrauma
returnNode = null;
}
CreateNodes(userData);
+ CreateReturnNodeHotkey();
+ return true;
+ }
+
+ private void CreateReturnNodeHotkey()
+ {
if (returnNode != null && returnNode.Visible)
{
- var hotkey = optionNodes.Count + 1;
- if (expandNode != null && expandNode.Visible) { hotkey += 1; }
+ var hotkey = 1;
+ if (targetFrame == null || !targetFrame.Visible)
+ {
+ hotkey = optionNodes.Count + 1;
+ if (expandNode != null && expandNode.Visible) { hotkey += 1; }
+ }
CreateHotkeyIcon(returnNode.RectTransform, hotkey % 10, true);
returnNodeHotkey = Keys.D0 + hotkey % 10;
}
@@ -1550,7 +1562,6 @@ namespace Barotrauma
{
returnNodeHotkey = Keys.None;
}
- return true;
}
private void SetCenterNode(GUIButton node)
@@ -2148,7 +2159,11 @@ namespace Barotrauma
ToolTip = character.Info.DisplayName + " (" + character.Info.Job.Name + ")"
};
+#if DEBUG
+ bool canHear = true;
+#else
bool canHear = character.CanHearCharacter(Character.Controlled);
+#endif
if (!canHear)
{
node.CanBeFocused = icon.CanBeFocused = false;
@@ -2271,8 +2286,14 @@ namespace Barotrauma
private Character GetBestCharacterForOrder(Order order)
{
+#if !DEBUG
if (Character.Controlled == null) { return null; }
- return characters.FindAll(c => c != Character.Controlled && c.TeamID == Character.Controlled.TeamID)
+#endif
+ if (order.Category == OrderCategory.Operate && AIObjectiveOperateItem.IsOperatedByAnother(null, order.TargetItemComponent, out Character operatingCharacter))
+ {
+ return operatingCharacter;
+ }
+ return characters.FindAll(c => Character.Controlled == null || (c != Character.Controlled && c.TeamID == Character.Controlled.TeamID))
.OrderByDescending(c => c.CurrentOrder == null || c.CurrentOrder.Identifier == dismissedOrderPrefab.Identifier)
.ThenByDescending(c => order.HasAppropriateJob(c))
.ThenBy(c => c.CurrentOrder?.Weight)
@@ -2281,16 +2302,18 @@ namespace Barotrauma
private List GetCharactersSortedForOrder(Order order)
{
+#if !DEBUG
if (Character.Controlled == null) { return new List(); }
+#endif
if (order.Identifier == "follow")
{
- return characters.FindAll(c => c != Character.Controlled && c.TeamID == Character.Controlled.TeamID)
+ return characters.FindAll(c => Character.Controlled == null || (c != Character.Controlled && c.TeamID == Character.Controlled.TeamID))
.OrderByDescending(c => c.CurrentOrder == null || c.CurrentOrder.Identifier == dismissedOrderPrefab.Identifier)
.ToList();
}
else
{
- return characters.FindAll(c => c.TeamID == Character.Controlled.TeamID)
+ return characters.FindAll(c => Character.Controlled == null || c.TeamID == Character.Controlled.TeamID)
.OrderByDescending(c => c.CurrentOrder == null || c.CurrentOrder.Identifier == dismissedOrderPrefab.Identifier)
.ThenByDescending(c => order.HasAppropriateJob(c))
.ThenBy(c => c.CurrentOrder?.Weight)
@@ -2298,9 +2321,9 @@ namespace Barotrauma
}
}
- #endregion
+#endregion
- #endregion
+#endregion
///
/// Creates a listbox that includes all the characters in the crew, can be used externally (round info menus etc)
@@ -2387,7 +2410,7 @@ namespace Barotrauma
return true;
}
- #region Reports
+#region Reports
///
/// Enables/disables report buttons when needed
@@ -2447,7 +2470,7 @@ namespace Barotrauma
}
}
- #endregion
+#endregion
public void InitSinglePlayerRound()
{
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs
index 833b5693b..06dca83df 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs
@@ -91,7 +91,7 @@ namespace Barotrauma
if (Character.Controlled.Submarine != outpost) { return null; }
//if there's a sub docked to the outpost, we can leave the level
- if (outpost.DockedTo.Count > 0)
+ if (outpost.DockedTo.Any())
{
var dockedSub = outpost.DockedTo.FirstOrDefault();
return dockedSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : dockedSub;
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/CaptainTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/CaptainTutorial.cs
index 711b190ac..89c5fbf6c 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/CaptainTutorial.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/CaptainTutorial.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Linq;
using System.Xml.Linq;
using Barotrauma.Items.Components;
using Barotrauma.Networking;
@@ -221,7 +222,7 @@ namespace Barotrauma.Tutorials
{
//captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f);
yield return new WaitForSeconds(1.0f, false);
- } while (Submarine.MainSub.DockedTo.Count > 0);
+ } while (Submarine.MainSub.DockedTo.Any());
RemoveCompletedObjective(segments[4]);
yield return new WaitForSeconds(2f, false);
TriggerTutorialSegment(5); // Navigate to destination
@@ -245,7 +246,7 @@ namespace Barotrauma.Tutorials
{
//captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f);
yield return new WaitForSeconds(1.0f, false);
- } while (!Submarine.MainSub.AtEndPosition || Submarine.MainSub.DockedTo.Count == 0);
+ } while (!Submarine.MainSub.AtEndPosition || Submarine.MainSub.DockedTo.Any());
RemoveCompletedObjective(segments[6]);
yield return new WaitForSeconds(3f, false);
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.GetWithVariable("Captain.Radio.Complete", "[OUTPOSTNAME]", GameMain.GameSession.EndLocation.Name), ChatMessageType.Radio, null);
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs
index aab3afeef..903f55d0f 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs
@@ -13,6 +13,7 @@ namespace Barotrauma
private GUIFrame infoFrameContent;
public RoundSummary RoundSummary { get; private set; }
+ public static bool IsInfoFrameOpen => GameMain.GameSession?.infoFrame != null;
private bool ToggleInfoFrame()
{
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs
index 8c16da963..1c636b45c 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs
@@ -47,7 +47,7 @@ namespace Barotrauma
{
keyMapping = new KeyOrMouse[Enum.GetNames(typeof(InputType)).Length];
keyMapping[(int)InputType.Run] = new KeyOrMouse(Keys.LeftShift);
- keyMapping[(int)InputType.Attack] = new KeyOrMouse(MouseButton.MiddleMouse);
+ keyMapping[(int)InputType.Attack] = new KeyOrMouse(Keys.R);
keyMapping[(int)InputType.Crouch] = new KeyOrMouse(Keys.LeftControl);
keyMapping[(int)InputType.Grab] = new KeyOrMouse(Keys.G);
keyMapping[(int)InputType.Health] = new KeyOrMouse(Keys.H);
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs
index 10240011e..098dd0abd 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs
@@ -154,7 +154,7 @@ namespace Barotrauma.Items.Components
};
// === POWER WARNING === //
inSufficientPowerWarning = new GUITextBlock(new RectTransform(Vector2.One, activateButton.RectTransform),
- TextManager.Get("FabricatorNoPower"), textColor: GUI.Style.Orange, textAlignment: Alignment.Center, color: Color.Black, style: "OuterGlow")
+ TextManager.Get("FabricatorNoPower"), textColor: GUI.Style.Orange, textAlignment: Alignment.Center, color: Color.Black, style: "OuterGlow", wrap: true)
{
HoverColor = Color.Black,
IgnoreLayoutGroups = true,
@@ -584,15 +584,20 @@ namespace Barotrauma.Items.Components
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
+ FabricatorState newState = (FabricatorState)msg.ReadByte();
+ float newTimeUntilReady = msg.ReadSingle();
int itemIndex = msg.ReadRangedInteger(-1, fabricationRecipes.Count - 1);
UInt16 userID = msg.ReadUInt16();
Character user = Entity.FindEntityByID(userID) as Character;
- if (itemIndex == -1 || user == null)
+ State = newState;
+ timeUntilReady = newTimeUntilReady;
+
+ if (newState == FabricatorState.Stopped || itemIndex == -1 || user == null)
{
CancelFabricating();
}
- else
+ else if (newState == FabricatorState.Active || newState == FabricatorState.Paused)
{
//if already fabricating the selected item, return
if (fabricatedItem != null && fabricationRecipes.IndexOf(fabricatedItem) == itemIndex) { return; }
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs
index ffc0fc194..d1f5e4439 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs
@@ -490,7 +490,7 @@ namespace Barotrauma.Items.Components
disruptedDirections.Clear();
foreach (AITarget t in AITarget.List)
{
- if (t.SoundRange <= 0.0f || !t.Enabled || float.IsNaN(t.SoundRange) || float.IsInfinity(t.SoundRange)) { continue; }
+ if (t.SoundRange <= 0.0f || float.IsNaN(t.SoundRange) || float.IsInfinity(t.SoundRange)) { continue; }
float distSqr = Vector2.DistanceSquared(t.WorldPosition, transducerCenter);
if (distSqr > t.SoundRange * t.SoundRange * 2) { continue; }
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs
index 4254f0534..07bd0f3d9 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs
@@ -665,7 +665,7 @@ namespace Barotrauma.Items.Components
if (Vector2.DistanceSquared(PlayerInput.MousePosition, steerArea.Rect.Center.ToVector2()) < steerRadius * steerRadius)
{
- if (PlayerInput.PrimaryMouseButtonHeld() && !CrewManager.IsCommandInterfaceOpen)
+ if (PlayerInput.PrimaryMouseButtonHeld() && !CrewManager.IsCommandInterfaceOpen && !GameSession.IsInfoFrameOpen)
{
Vector2 displaySubPos = (-sonar.DisplayOffset * sonar.Zoom) / sonar.Range * sonar.DisplayRadius * sonar.Zoom;
displaySubPos.Y = -displaySubPos.Y;
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Power/PowerContainer.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Power/PowerContainer.cs
index c59e2571f..a72a5ed3e 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Power/PowerContainer.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Power/PowerContainer.cs
@@ -106,11 +106,10 @@ namespace Barotrauma.Items.Components
{
if (indicatorSize.X <= 1.0f || indicatorSize.Y <= 1.0f) { return; }
- GUI.DrawRectangle(spriteBatch,
- new Vector2(
- item.DrawPosition.X - item.Sprite.SourceRect.Width / 2 * item.Scale + indicatorPosition.X * item.Scale,
- -item.DrawPosition.Y - item.Sprite.SourceRect.Height / 2 * item.Scale + indicatorPosition.Y * item.Scale),
- indicatorSize * item.Scale, Color.Black, depth: item.SpriteDepth - 0.00001f);
+ Vector2 itemSize = new Vector2(item.Sprite.SourceRect.Width, item.Sprite.SourceRect.Height) * item.Scale;
+ Vector2 indicatorPos = -itemSize / 2 + indicatorPosition * item.Scale;
+ if (item.FlippedX && item.Prefab.CanSpriteFlipX) { indicatorPos.X = -indicatorPos.X - indicatorSize.X * item.Scale; }
+ if (item.FlippedY && item.Prefab.CanSpriteFlipY) { indicatorPos.Y = -indicatorPos.Y - indicatorSize.Y * item.Scale; }
if (charge > 0)
{
@@ -118,25 +117,23 @@ namespace Barotrauma.Items.Components
if (!isHorizontal)
{
GUI.DrawRectangle(spriteBatch,
- new Vector2(
- item.DrawPosition.X - item.Sprite.SourceRect.Width / 2 * item.Scale + indicatorPosition.X * item.Scale + 1,
- -item.DrawPosition.Y - item.Sprite.SourceRect.Height / 2 * item.Scale + indicatorPosition.Y * item.Scale + 1 + ((indicatorSize.Y * item.Scale) * (1.0f - charge / capacity))),
- new Vector2(indicatorSize.X * item.Scale - 2, (indicatorSize.Y * item.Scale - 2) * (charge / capacity)), indicatorColor, true,
- depth: item.SpriteDepth - 0.00001f);
+ new Vector2(item.DrawPosition.X, -item.DrawPosition.Y + ((indicatorSize.Y * item.Scale) * (1.0f - charge / capacity))) + indicatorPos,
+ new Vector2(indicatorSize.X * item.Scale, (indicatorSize.Y * item.Scale) * (charge / capacity)), indicatorColor, true,
+ depth: item.SpriteDepth - 0.00001f);
}
else
{
GUI.DrawRectangle(spriteBatch,
- new Vector2(
- item.DrawPosition.X - item.Sprite.SourceRect.Width / 2 * item.Scale + indicatorPosition.X * item.Scale + 1 ,
- -item.DrawPosition.Y - item.Sprite.SourceRect.Height / 2 * item.Scale + indicatorPosition.Y * item.Scale + 1),
- new Vector2((indicatorSize.X * item.Scale - 2) * (charge / capacity), indicatorSize.Y * item.Scale - 2), indicatorColor, true,
- depth: item.SpriteDepth - 0.00001f);
+ new Vector2(item.DrawPosition.X, -item.DrawPosition.Y) + indicatorPos,
+ new Vector2((indicatorSize.X * item.Scale) * (charge / capacity), indicatorSize.Y * item.Scale), indicatorColor, true,
+ depth: item.SpriteDepth - 0.00001f);
}
}
-
+ GUI.DrawRectangle(spriteBatch,
+ new Vector2(item.DrawPosition.X, -item.DrawPosition.Y) + indicatorPos,
+ indicatorSize * item.Scale, Color.Black, depth: item.SpriteDepth - 0.00001f);
}
-
+
public void ClientWrite(IWriteMessage msg, object[] extraData)
{
msg.WriteRangedInteger((int)(rechargeSpeed / MaxRechargeSpeed * 10), 0, 10);
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs
index c2371bcb7..d61908320 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs
@@ -142,7 +142,7 @@ namespace Barotrauma.Items.Components
private void DrawCharacterInfo(SpriteBatch spriteBatch, Character target, float alpha = 1.0f)
{
- Vector2 hudPos = GameMain.GameScreen.Cam.WorldToScreen(target.WorldPosition);
+ Vector2 hudPos = GameMain.GameScreen.Cam.WorldToScreen(target.DrawPosition);
hudPos += Vector2.UnitX * 50.0f;
List texts = new List();
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/DockingPort.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/DockingPort.cs
index 93249475c..b789b4381 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/DockingPort.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/DockingPort.cs
@@ -152,7 +152,7 @@ namespace Barotrauma.Items.Components
string errorMsg = "Error while reading a docking port network event (Dock method did not create a joint between the ports)." +
" Submarine: " + (item.Submarine?.Name ?? "null") +
", target submarine: " + (DockingTarget.item.Submarine?.Name ?? "null");
- if (item.Submarine?.DockedTo.Contains(DockingTarget.item.Submarine) ?? false)
+ if (item.Submarine?.ConnectedDockingPorts.ContainsKey(DockingTarget.item.Submarine) ?? false)
{
errorMsg += "\nAlready docked.";
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs
index b7b4eb4d5..acfde35ec 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs
@@ -284,6 +284,7 @@ namespace Barotrauma
}
protected static HashSet highlightedSubInventorySlots = new HashSet();
+ private static List subInventorySlotsToDraw = new List();
protected static SlotReference selectedSlot;
@@ -1048,11 +1049,14 @@ namespace Barotrauma
return hoverArea;
}
+
public static void DrawFront(SpriteBatch spriteBatch)
{
- if (GUI.PauseMenuOpen || GUI.SettingsMenuOpen) return;
+ if (GUI.PauseMenuOpen || GUI.SettingsMenuOpen) { return; }
- foreach (var slot in highlightedSubInventorySlots)
+ subInventorySlotsToDraw.Clear();
+ subInventorySlotsToDraw.AddRange(highlightedSubInventorySlots);
+ foreach (var slot in subInventorySlotsToDraw)
{
int slotIndex = Array.IndexOf(slot.ParentInventory.slots, slot.Slot);
if (slotIndex > -1 && slotIndex < slot.ParentInventory.slots.Length)
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs
index d4ded9f01..755769bd2 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs
@@ -333,15 +333,6 @@ namespace Barotrauma
if (GameMain.DebugDraw)
{
- aiTarget?.Draw(spriteBatch);
- var containedItems = ContainedItems;
- if (containedItems != null)
- {
- foreach (Item item in containedItems)
- {
- item.AiTarget?.Draw(spriteBatch);
- }
- }
if (body != null)
{
body.DebugDraw(spriteBatch, Color.White);
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs
index baf8124c6..f7084fce6 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs
@@ -248,8 +248,6 @@ namespace Barotrauma
if (!editing && !GameMain.DebugDraw) return;
- if (aiTarget != null) aiTarget.Draw(spriteBatch);
-
Rectangle drawRect =
Submarine == null ? rect : new Rectangle((int)(Submarine.DrawPosition.X + rect.X), (int)(Submarine.DrawPosition.Y + rect.Y), rect.Width, rect.Height);
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelObjects/LevelObjectPrefab.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelObjects/LevelObjectPrefab.cs
index b961d85be..e2d82ee47 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelObjects/LevelObjectPrefab.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelObjects/LevelObjectPrefab.cs
@@ -4,6 +4,7 @@ using Barotrauma.SpriteDeformations;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Globalization;
+using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
@@ -120,7 +121,7 @@ namespace Barotrauma
SerializableProperty.SerializeProperties(this, element);
- foreach (XElement subElement in element.Elements())
+ foreach (XElement subElement in element.Elements().ToList())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
@@ -139,7 +140,7 @@ namespace Barotrauma
break;
}
}
-
+
foreach (LightSourceParams lightSourceParams in LightSourceParams)
{
var lightElement = new XElement("LightSource");
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/LinkedSubmarine.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/LinkedSubmarine.cs
index 215fd0d52..9cc0bdda8 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Map/LinkedSubmarine.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Map/LinkedSubmarine.cs
@@ -12,10 +12,10 @@ namespace Barotrauma
{
public override void Draw(SpriteBatch spriteBatch, bool editing, bool back = true)
{
- if (!editing || wallVertices == null) return;
+ if (!editing || wallVertices == null) { return; }
- Color color = (IsHighlighted) ? GUI.Style.Orange : GUI.Style.Green;
- if (IsSelected) color = GUI.Style.Red;
+ Color color = IsHighlighted ? GUI.Style.Orange : GUI.Style.Green;
+ if (IsSelected) { color = GUI.Style.Red; }
Vector2 pos = Position;
@@ -37,11 +37,7 @@ namespace Barotrauma
GUI.DrawLine(spriteBatch, pos + Vector2.UnitY * 50.0f, pos - Vector2.UnitY * 50.0f, color, 0.0f, 5);
GUI.DrawLine(spriteBatch, pos + Vector2.UnitX * 50.0f, pos - Vector2.UnitX * 50.0f, color, 0.0f, 5);
- Rectangle drawRect = rect;
- drawRect.Y = -rect.Y;
- GUI.DrawRectangle(spriteBatch, drawRect, GUI.Style.Red, true);
-
- if (!Item.ShowLinks) return;
+ if (!Item.ShowLinks) { return; }
foreach (MapEntity e in linkedTo)
{
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs
index 121bad258..10d8107a2 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs
@@ -350,8 +350,6 @@ namespace Barotrauma
}
}
}
-
- AiTarget?.Draw(spriteBatch);
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs
index c29f8c331..dea60660b 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs
@@ -222,7 +222,7 @@ namespace Barotrauma
GUI.DrawRectangle(spriteBatch, worldBorders, Color.White, false, 0, 5);
- if (sub.subBody.PositionBuffer.Count < 2) continue;
+ if (sub.subBody == null || sub.subBody.PositionBuffer.Count < 2) continue;
Vector2 prevPos = ConvertUnits.ToDisplayUnits(sub.subBody.PositionBuffer[0].Position);
prevPos.Y = -prevPos.Y;
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Client.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Client.cs
index ae9a597bc..7d0273a68 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Client.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Client.cs
@@ -15,6 +15,7 @@ namespace Barotrauma.Networking
public byte ID;
public UInt16 CharacterID;
public bool Muted;
+ public bool InGame;
public bool AllowKicking;
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs
index 47ff63bfb..8e74ab80a 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs
@@ -8,6 +8,7 @@ using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading;
+using System.Threading.Tasks;
namespace Barotrauma.Networking
{
@@ -60,6 +61,19 @@ namespace Barotrauma.Networking
private bool connected;
+ private enum RoundInitStatus
+ {
+ NotStarted,
+ Starting,
+ WaitingForStartGameFinalize,
+ Started,
+ TimedOut,
+ Error,
+ Interrupted
+ }
+
+ private RoundInitStatus roundInitStatus = RoundInitStatus.NotStarted;
+
private byte myID;
private List otherClients;
@@ -148,6 +162,8 @@ namespace Barotrauma.Networking
this.ownerKey = ownerKey;
this.steamP2POwner = steamP2POwner;
+ roundInitStatus = RoundInitStatus.NotStarted;
+
allowReconnect = true;
netStats = new NetStats();
@@ -558,6 +574,13 @@ namespace Barotrauma.Networking
try
{
+ incomingMessagesToProcess.Clear();
+ incomingMessagesToProcess.AddRange(pendingIncomingMessages);
+ foreach (var inc in incomingMessagesToProcess)
+ {
+ ReadDataMessage(inc);
+ }
+ pendingIncomingMessages.Clear();
clientPeer?.Update(deltaTime);
}
catch (Exception e)
@@ -632,11 +655,23 @@ namespace Barotrauma.Networking
}
}
- private CoroutineHandle startGameCoroutine;
+ private readonly List pendingIncomingMessages = new List();
+ private readonly List incomingMessagesToProcess = new List();
private void ReadDataMessage(IReadMessage inc)
{
ServerPacketHeader header = (ServerPacketHeader)inc.ReadByte();
+
+ if (header != ServerPacketHeader.STARTGAMEFINALIZE &&
+ header != ServerPacketHeader.ENDGAME &&
+ roundInitStatus == RoundInitStatus.WaitingForStartGameFinalize)
+ {
+ //rewind the header byte we just read
+ inc.BitPosition -= 8;
+ pendingIncomingMessages.Add(inc);
+ return;
+ }
+
switch (header)
{
case ServerPacketHeader.UPDATE_LOBBY:
@@ -714,7 +749,13 @@ namespace Barotrauma.Networking
}
break;
case ServerPacketHeader.STARTGAME:
- startGameCoroutine = GameMain.Instance.ShowLoading(StartGame(inc), false);
+ GameMain.Instance.ShowLoading(StartGame(inc), false);
+ break;
+ case ServerPacketHeader.STARTGAMEFINALIZE:
+ if (roundInitStatus == RoundInitStatus.WaitingForStartGameFinalize)
+ {
+ ReadStartGameFinalize(inc);
+ }
break;
case ServerPacketHeader.ENDGAME:
string endMessage = inc.ReadString();
@@ -725,6 +766,8 @@ namespace Barotrauma.Networking
GameMain.GameSession.WinningTeam = winningTeam;
GameMain.GameSession.Mission.Completed = true;
}
+
+ roundInitStatus = RoundInitStatus.Interrupted;
CoroutineManager.StartCoroutine(EndGame(endMessage), "EndGame");
break;
case ServerPacketHeader.CAMPAIGN_SETUP_INFO:
@@ -775,7 +818,37 @@ namespace Barotrauma.Networking
break;
}
}
-
+
+ private void ReadStartGameFinalize(IReadMessage inc)
+ {
+ ushort contentToPreloadCount = inc.ReadUInt16();
+ List contentToPreload = new List();
+ for (int i = 0; i < contentToPreloadCount; i++)
+ {
+ ContentType contentType = (ContentType)inc.ReadByte();
+ string filePath = inc.ReadString();
+ contentToPreload.Add(new ContentFile(filePath, contentType));
+ }
+
+ GameMain.GameSession.EventManager.PreloadContent(contentToPreload);
+
+ int levelEqualityCheckVal = inc.ReadInt32();
+
+ if (Level.Loaded.EqualityCheckVal != levelEqualityCheckVal)
+ {
+ string errorMsg = "Level equality check failed. The level generated at your end doesn't match the level generated by the server (seed: " + Level.Loaded.Seed +
+ ", sub: " + Submarine.MainSub.Name + " (" + Submarine.MainSub.MD5Hash.ShortHash + ")" +
+ ", mirrored: " + Level.Loaded.Mirrored + ").";
+ GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + Level.Loaded.Seed, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
+ throw new Exception(errorMsg);
+ }
+
+ GameMain.GameSession.Mission?.ClientReadInitial(inc);
+
+ roundInitStatus = RoundInitStatus.Started;
+ }
+
+
private void OnDisconnect()
{
if (SteamManager.IsInitialized)
@@ -884,6 +957,7 @@ namespace Barotrauma.Networking
}
else
{
+ connected = false;
connectCancelled = true;
string msg = "";
@@ -1108,6 +1182,7 @@ namespace Barotrauma.Networking
if (Character != null) Character.Remove();
HasSpawned = false;
eventErrorWritten = false;
+ GameMain.NetLobbyScreen.StopWaitingForStartRound();
while (CoroutineManager.IsCoroutineRunning("EndGame"))
{
@@ -1126,9 +1201,11 @@ namespace Barotrauma.Networking
EndVoteTickBox.Selected = false;
+ roundInitStatus = RoundInitStatus.Starting;
+
int seed = inc.ReadInt32();
string levelSeed = inc.ReadString();
- int levelEqualityCheckVal = inc.ReadInt32();
+ //int levelEqualityCheckVal = inc.ReadInt32();
float levelDifficulty = inc.ReadSingle();
byte losMode = inc.ReadByte();
@@ -1146,24 +1223,16 @@ namespace Barotrauma.Networking
int missionIndex = inc.ReadInt16();
bool respawnAllowed = inc.ReadBoolean();
- bool loadSecondSub = inc.ReadBoolean();
bool disguisesAllowed = inc.ReadBoolean();
bool rewiringAllowed = inc.ReadBoolean();
bool allowRagdollButton = inc.ReadBoolean();
- ushort contentToPreloadCount = inc.ReadUInt16();
- List contentToPreload = new List();
- for (int i = 0; i < contentToPreloadCount; i++)
- {
- ContentType contentType = (ContentType)inc.ReadByte();
- string filePath = inc.ReadString();
- contentToPreload.Add(new ContentFile(filePath, contentType));
- }
-
serverSettings.ReadMonsterEnabled(inc);
+ bool includesFinalize = inc.ReadBoolean(); inc.ReadPadBits();
+
GameModePreset gameMode = GameModePreset.List.Find(gm => gm.Identifier == modeIdentifier);
MultiPlayerCampaign campaign =
GameMain.NetLobbyScreen.SelectedMode == GameMain.GameSession?.GameMode.Preset && gameMode == GameMain.NetLobbyScreen.SelectedMode ?
@@ -1237,21 +1306,105 @@ namespace Barotrauma.Networking
yield return CoroutineStatus.Failure;
}
+ MissionPrefab missionPrefab = missionIndex < 0 ? null : MissionPrefab.List[missionIndex];
+
GameMain.GameSession = missionIndex < 0 ?
new GameSession(GameMain.NetLobbyScreen.SelectedSub, "", gameMode, MissionType.None) :
- new GameSession(GameMain.NetLobbyScreen.SelectedSub, "", gameMode, MissionPrefab.List[missionIndex]);
- GameMain.GameSession.StartRound(levelSeed, levelDifficulty, loadSecondSub);
+ new GameSession(GameMain.NetLobbyScreen.SelectedSub, "", gameMode, missionPrefab);
+
+ //startRoundTask = Task.Run(async () => { await Task.Yield(); GameMain.GameSession.StartRound(levelSeed, levelDifficulty); });
+ GameMain.GameSession.StartRound(levelSeed, levelDifficulty);
}
else
{
if (GameMain.GameSession?.CrewManager != null) GameMain.GameSession.CrewManager.Reset();
+ /*startRoundTask = Task.Run(async () =>
+ {
+ await Task.Yield();
+ GameMain.GameSession.StartRound(campaign.Map.SelectedConnection.Level,
+ reloadSub: true,
+ mirrorLevel: campaign.Map.CurrentLocation != campaign.Map.SelectedConnection.Locations[0]);
+ });*/
GameMain.GameSession.StartRound(campaign.Map.SelectedConnection.Level,
- reloadSub: true,
- loadSecondSub: false,
- mirrorLevel: campaign.Map.CurrentLocation != campaign.Map.SelectedConnection.Locations[0]);
+ reloadSub: true,
+ mirrorLevel: campaign.Map.CurrentLocation != campaign.Map.SelectedConnection.Locations[0]);
}
- GameMain.GameSession.Mission?.ClientReadInitial(inc);
+ roundInitStatus = RoundInitStatus.WaitingForStartGameFinalize;
+
+ DateTime? timeOut = null;
+ DateTime requestFinalizeTime = DateTime.Now;
+ TimeSpan requestFinalizeInterval = new TimeSpan(0, 0, 2);
+
+ while (true)
+ {
+ try
+ {
+ if (timeOut.HasValue)
+ {
+ if (DateTime.Now > requestFinalizeTime)
+ {
+ IWriteMessage msg = new WriteOnlyMessage();
+ msg.Write((byte)ClientPacketHeader.REQUEST_STARTGAMEFINALIZE);
+ clientPeer.Send(msg, DeliveryMethod.Unreliable);
+ requestFinalizeTime = DateTime.Now + requestFinalizeInterval;
+ }
+ if (DateTime.Now > timeOut)
+ {
+ DebugConsole.ThrowError("Error while starting the round (did not receive STARTGAMEFINALIZE message from the server). Stopping the round...");
+ roundInitStatus = RoundInitStatus.TimedOut;
+ break;
+ }
+ }
+ else
+ {
+ if (includesFinalize)
+ {
+ ReadStartGameFinalize(inc);
+ break;
+ }
+
+ //wait for up to 30 seconds for the server to send the STARTGAMEFINALIZE message
+ timeOut = DateTime.Now + new TimeSpan(0, 0, seconds: 30);
+ }
+
+ if (!connected)
+ {
+ roundInitStatus = RoundInitStatus.Interrupted;
+ break;
+ }
+
+ if (roundInitStatus != RoundInitStatus.WaitingForStartGameFinalize)
+ {
+ break;
+ }
+
+ clientPeer.Update((float)Timing.Step);
+ }
+ catch (Exception e)
+ {
+ DebugConsole.ThrowError("There was an error initializing the round.", e, true);
+ roundInitStatus = RoundInitStatus.Error;
+ break;
+ }
+
+ //waiting for a STARTGAMEFINALIZE message
+ yield return CoroutineStatus.Running;
+ }
+
+ if (roundInitStatus != RoundInitStatus.Started)
+ {
+ if (roundInitStatus != RoundInitStatus.Interrupted)
+ {
+ DebugConsole.ThrowError(roundInitStatus.ToString());
+ CoroutineManager.StartCoroutine(EndGame(""));
+ yield return CoroutineStatus.Failure;
+ }
+ else
+ {
+ yield return CoroutineStatus.Success;
+ }
+ }
if (GameMain.GameSession.Submarine.IsFileCorrupted)
{
@@ -1261,7 +1414,7 @@ namespace Barotrauma.Networking
for (int i = 0; i < Submarine.MainSubs.Length; i++)
{
- if (!loadSecondSub && i > 0) { break; }
+ if (Submarine.MainSubs[i] == null) { break; }
var teamID = i == 0 ? Character.TeamType.Team1 : Character.TeamType.Team2;
Submarine.MainSubs[i].TeamID = teamID;
@@ -1271,23 +1424,10 @@ namespace Barotrauma.Networking
}
}
- if (Level.Loaded.EqualityCheckVal != levelEqualityCheckVal)
- {
- string errorMsg = "Level equality check failed. The level generated at your end doesn't match the level generated by the server (seed: " + Level.Loaded.Seed +
- ", sub: " + Submarine.MainSub.Name + " (" + Submarine.MainSub.MD5Hash.ShortHash + ")" +
- ", mirrored: " + Level.Loaded.Mirrored + ").";
- DebugConsole.ThrowError(errorMsg, createMessageBox: true);
- GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + levelSeed, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
- CoroutineManager.StartCoroutine(EndGame(""));
- yield return CoroutineStatus.Failure;
- }
-
if (respawnAllowed) { respawnManager = new RespawnManager(this, GameMain.NetLobbyScreen.UsingShuttle ? GameMain.NetLobbyScreen.SelectedShuttle : null); }
- GameMain.GameSession.EventManager.PreloadContent(contentToPreload);
-
- ServerSettings.ServerDetailsChanged = true;
gameStarted = true;
+ ServerSettings.ServerDetailsChanged = true;
GameMain.GameScreen.Select();
@@ -1394,6 +1534,7 @@ namespace Barotrauma.Networking
string preferredJob = inc.ReadString();
UInt16 characterID = inc.ReadUInt16();
bool muted = inc.ReadBoolean();
+ bool inGame = inc.ReadBoolean();
bool allowKicking = inc.ReadBoolean();
inc.ReadPadBits();
@@ -1406,6 +1547,7 @@ namespace Barotrauma.Networking
PreferredJob = preferredJob,
CharacterID = characterID,
Muted = muted,
+ InGame = inGame,
AllowKicking = allowKicking
});
}
@@ -1424,6 +1566,7 @@ namespace Barotrauma.Networking
{
SteamID = tc.SteamID,
Muted = tc.Muted,
+ InGame = tc.InGame,
AllowKicking = tc.AllowKicking
};
ConnectedClients.Add(existingClient);
@@ -1433,15 +1576,12 @@ namespace Barotrauma.Networking
existingClient.PreferredJob = tc.PreferredJob;
existingClient.Character = null;
existingClient.Muted = tc.Muted;
+ existingClient.InGame = tc.InGame;
existingClient.AllowKicking = tc.AllowKicking;
GameMain.NetLobbyScreen.SetPlayerNameAndJobPreference(existingClient);
- if (tc.CharacterID > 0)
+ if (Screen.Selected != GameMain.NetLobbyScreen && tc.CharacterID > 0)
{
- existingClient.Character = Entity.FindEntityByID(tc.CharacterID) as Character;
- if (existingClient.Character == null)
- {
- updateClientListId = false;
- }
+ existingClient.CharacterID = tc.CharacterID;
}
if (existingClient.ID == myID)
{
@@ -2440,7 +2580,7 @@ namespace Barotrauma.Networking
if (GUI.KeyboardDispatcher.Subscriber == null)
{
bool chatKeyHit = PlayerInput.KeyHit(InputType.Chat);
- bool radioKeyHit = PlayerInput.KeyHit(InputType.RadioChat);
+ bool radioKeyHit = PlayerInput.KeyHit(InputType.RadioChat) && (Character.Controlled == null || Character.Controlled.SpeechImpediment < 0);
if (chatKeyHit || radioKeyHit)
{
@@ -2498,6 +2638,7 @@ namespace Barotrauma.Networking
{
var transfer = fileReceiver.ActiveTransfers.First();
GameMain.NetLobbyScreen.FileTransferFrame.Visible = true;
+ GameMain.NetLobbyScreen.FileTransferFrame.UserData = transfer;
GameMain.NetLobbyScreen.FileTransferTitle.Text =
ToolBox.LimitString(
TextManager.GetWithVariable("DownloadingFile", "[filename]", transfer.FileName),
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/LidgrenClientPeer.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/LidgrenClientPeer.cs
index d315062c3..41f8f65af 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/LidgrenClientPeer.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/LidgrenClientPeer.cs
@@ -91,6 +91,7 @@ namespace Barotrauma.Networking
return;
}
+ incomingLidgrenMessages.Clear();
netClient.ReadMessages(incomingLidgrenMessages);
foreach (NetIncomingMessage inc in incomingLidgrenMessages)
@@ -107,8 +108,6 @@ namespace Barotrauma.Networking
break;
}
}
-
- incomingLidgrenMessages.Clear();
}
private void HandleDataMessage(NetIncomingMessage inc)
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/SteamManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/SteamManager.cs
index 6b2f569db..2979156d5 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Networking/SteamManager.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/SteamManager.cs
@@ -1314,79 +1314,69 @@ namespace Barotrauma.Steam
return upToDate;
}
- public static bool AutoUpdateWorkshopItems()
+ public static async Task AutoUpdateWorkshopItems()
{
if (!isInitialized) { return false; }
var query = new Steamworks.Ugc.Query(Steamworks.UgcType.All)
.WhereUserSubscribed()
.WithLongDescription();
- //ugcResultPageTasks ??= new List();
- //ugcResultPageTasks.Add();
- CancellationTokenSource cancelTokenSource = new CancellationTokenSource();
- CancellationToken cancelToken = cancelTokenSource.Token;
- Task task = Task.Factory.StartNew(async () =>
- {
- int processedResults = 0; int pageIndex = 1;
- Steamworks.Ugc.ResultPage? resultPage = await query.GetPageAsync(pageIndex);
- while (resultPage.HasValue && resultPage?.ResultCount > 0)
+ DateTime startTime = DateTime.Now;
+ DateTime endTime = startTime + new TimeSpan(0, 0, 30);
+
+ int processedResults = 0; int pageIndex = 1;
+ Steamworks.Ugc.ResultPage? resultPage = await query.GetPageAsync(pageIndex);
+
+ while (resultPage.HasValue && resultPage?.ResultCount > 0)
+ {
+ if (DateTime.Now > endTime)
{
- foreach (var item in resultPage.Value.Entries)
+ return false;
+ }
+ foreach (var item in resultPage.Value.Entries)
+ {
+ try
{
- if (cancelToken.IsCancellationRequested)
- {
- cancelToken.ThrowIfCancellationRequested();
- }
- try
- {
- if (!item.IsInstalled || !CheckWorkshopItemEnabled(item) || CheckWorkshopItemUpToDate(item)) { continue; }
- if (!UpdateWorkshopItem(item, out string errorMsg))
- {
- DebugConsole.ThrowError(errorMsg);
- new GUIMessageBox(
- TextManager.Get("Error"),
- TextManager.GetWithVariables("WorkshopItemUpdateFailed", new string[2] { "[itemname]", "[errormessage]" }, new string[2] { item.Title, errorMsg }));
- }
- else
- {
- //TODO: potential race condition
- new GUIMessageBox("", TextManager.GetWithVariable("WorkshopItemUpdated", "[itemname]", item.Title));
- }
- }
- catch (Exception e)
+ if (!item.IsInstalled || !CheckWorkshopItemEnabled(item) || CheckWorkshopItemUpToDate(item)) { continue; }
+ if (!UpdateWorkshopItem(item, out string errorMsg))
{
+ DebugConsole.ThrowError(errorMsg);
new GUIMessageBox(
TextManager.Get("Error"),
- TextManager.GetWithVariables("WorkshopItemUpdateFailed", new string[2] { "[itemname]", "[errormessage]" }, new string[2] { item.Title, e.Message + ", " + e.TargetSite }));
- GameAnalyticsManager.AddErrorEventOnce(
- "SteamManager.AutoUpdateWorkshopItems:" + e.Message,
- GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
- "Failed to autoupdate workshop item \"" + item.Title + "\". " + e.Message + "\n" + e.StackTrace);
+ TextManager.GetWithVariables("WorkshopItemUpdateFailed", new string[2] { "[itemname]", "[errormessage]" }, new string[2] { item.Title, errorMsg }));
+ }
+ else
+ {
+ //TODO: potential race condition
+ new GUIMessageBox("", TextManager.GetWithVariable("WorkshopItemUpdated", "[itemname]", item.Title));
}
}
-
- processedResults += resultPage.Value.ResultCount;
- pageIndex++;
- if (processedResults < resultPage?.TotalCount)
+ catch (Exception e)
{
- resultPage = await query.GetPageAsync(pageIndex);
- }
- else
- {
- resultPage = null;
+ new GUIMessageBox(
+ TextManager.Get("Error"),
+ TextManager.GetWithVariables("WorkshopItemUpdateFailed", new string[2] { "[itemname]", "[errormessage]" }, new string[2] { item.Title, e.Message + ", " + e.TargetSite }));
+ GameAnalyticsManager.AddErrorEventOnce(
+ "SteamManager.AutoUpdateWorkshopItems:" + e.Message,
+ GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
+ "Failed to autoupdate workshop item \"" + item.Title + "\". " + e.Message + "\n" + e.StackTrace);
}
}
- }, cancelToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
-
- task.Wait(10000);
- if (!task.IsCompleted)
- {
- cancelTokenSource.Cancel();
- task.Wait();
+
+ processedResults += resultPage.Value.ResultCount;
+ pageIndex++;
+ if (processedResults < resultPage?.TotalCount)
+ {
+ resultPage = await query.GetPageAsync(pageIndex);
+ }
+ else
+ {
+ resultPage = null;
+ }
}
-
- return task.Status == TaskStatus.RanToCompletion;
+
+ return true;
}
public static bool UpdateWorkshopItem(Steamworks.Ugc.Item? item, out string errorMsg)
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipClient.cs
index 59db1c580..5eab16fff 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipClient.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipClient.cs
@@ -119,7 +119,7 @@ namespace Barotrauma.Networking
GameMain.NetLobbyScreen?.SetPlayerSpeaking(client);
GameMain.GameSession?.CrewManager?.SetClientSpeaking(client);
- if (client.VoipSound.CurrentAmplitude > 0.1f) //TODO: might need to tweak
+ if ((client.VoipSound.CurrentAmplitude * client.VoipSound.Gain * GameMain.SoundManager.GetCategoryGainMultiplier("voip")) > 0.1f) //TODO: might need to tweak
{
if (client.Character != null && !client.Character.Removed)
{
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Program.cs b/Barotrauma/BarotraumaClient/ClientSource/Program.cs
index ed8e7afa5..ed4a8a0dc 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Program.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Program.cs
@@ -7,6 +7,7 @@ using System.Text;
using GameAnalyticsSDK.Net;
using Barotrauma.Steam;
using System.Diagnostics;
+using System.Runtime.InteropServices;
#if WINDOWS
using SharpDX;
@@ -22,42 +23,56 @@ namespace Barotrauma
///
public static class Program
{
+
+#if LINUX
+ ///
+ /// Sets the required environment variables for the game to initialize Steamworks correctly.
+ ///
+ [DllImport("linux_steam_env", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
+ private static extern void setLinuxEnv();
+#endif
+
///
/// The main entry point for the application.
///
[STAThread]
static void Main(string[] args)
{
- GameMain game = null;
string executableDir = "";
+
#if !DEBUG
+ AppDomain currentDomain = AppDomain.CurrentDomain;
+ currentDomain.UnhandledException += new UnhandledExceptionEventHandler(CrashHandler);
+#endif
+
+#if LINUX
+ setLinuxEnv();
+#endif
+
+ Game = null;
+ executableDir = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location);
+ Directory.SetCurrentDirectory(executableDir);
+ SteamManager.Initialize();
+ Game = new GameMain(args);
+ Game.Run();
+ Game.Dispose();
+ }
+
+ private static GameMain Game;
+
+ private static void CrashHandler(object sender, UnhandledExceptionEventArgs args)
+ {
try
{
-#endif
- executableDir = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location);
- Directory.SetCurrentDirectory(executableDir);
- SteamManager.Initialize();
- game = new GameMain(args);
- game.Run();
- game.Dispose();
-#if !DEBUG
+ Game?.Exit();
+ CrashDump(Game, "crashreport.log", (Exception)args.ExceptionObject);
+ Game?.Dispose();
}
- catch (Exception e)
+ catch
{
- try
- {
- CrashDump(game, Path.Combine(executableDir,"crashreport.log"), e);
- }
- catch (Exception e2)
- {
- CrashMessageBox("Barotrauma seems to have crashed, and failed to generate a crash report: "
- + e2.Message + "\n" + e2.StackTrace.ToString(),
- null);
- }
- game?.Dispose();
+ //exception handler is broken, we have a serious problem here!!
return;
}
-#endif
}
public static void CrashMessageBox(string message, string filePath)
@@ -83,16 +98,9 @@ namespace Barotrauma
string exePath = System.Reflection.Assembly.GetEntryAssembly().Location;
var md5 = System.Security.Cryptography.MD5.Create();
Md5Hash exeHash = null;
- try
+ using (var stream = File.OpenRead(exePath))
{
- using (var stream = File.OpenRead(exePath))
- {
- exeHash = new Md5Hash(stream);
- }
- }
- catch
- {
- //gotta catch them all, we don't want to throw an exception while writing a crash report
+ exeHash = new Md5Hash(stream);
}
StreamWriter sw = new StreamWriter(filePath);
@@ -217,4 +225,4 @@ namespace Barotrauma
}
}
#endif
-}
+ }
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI.cs
index d140b5d4e..a19c776a3 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI.cs
@@ -392,18 +392,19 @@ namespace Barotrauma
{
nameText.Text = Path.GetFileNameWithoutExtension(saveFile);
XDocument doc = SaveUtil.LoadGameSessionDoc(saveFile);
- if (doc.Root.GetChildElement("multiplayercampaign") != null)
- {
- //multiplayer campaign save in the wrong folder -> don't show the save
- saveList.Content.RemoveChild(saveFrame);
- continue;
- }
+
if (doc?.Root == null)
{
DebugConsole.ThrowError("Error loading save file \"" + saveFile + "\". The file may be corrupted.");
nameText.TextColor = GUI.Style.Red;
continue;
}
+ if (doc.Root.GetChildElement("multiplayercampaign") != null)
+ {
+ //multiplayer campaign save in the wrong folder -> don't show the save
+ saveList.Content.RemoveChild(saveFrame);
+ continue;
+ }
subName = doc.Root.GetAttributeString("submarine", "");
saveTime = doc.Root.GetAttributeString("savetime", "");
contentPackageStr = doc.Root.GetAttributeString("selectedcontentpackages", "");
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs
index 1ff4d6a40..fd0284e2f 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs
@@ -3347,6 +3347,7 @@ namespace Barotrauma.CharacterEditor
void CreateCloseButton(SerializableEntityEditor editor, Action onButtonClicked, float size = 1)
{
+ if (editor == null) { return; }
int height = 30;
var parent = new GUIFrame(new RectTransform(new Point(editor.Rect.Width, (int)(height * size * GUI.yScale)), editor.RectTransform, isFixedSize: true), style: null)
{
@@ -3366,6 +3367,7 @@ namespace Barotrauma.CharacterEditor
void CreateAddButtonAtLast(ParamsEditor editor, Action onButtonClicked, string text)
{
+ if (editor == null) { return; }
var parentFrame = new GUIFrame(new RectTransform(new Point(editor.EditorBox.Rect.Width, (int)(50 * GUI.yScale)), editor.EditorBox.Content.RectTransform), style: null, color: ParamsEditor.Color)
{
CanBeFocused = false
@@ -3383,6 +3385,7 @@ namespace Barotrauma.CharacterEditor
void CreateAddButton(SerializableEntityEditor editor, Action onButtonClicked, string text)
{
+ if (editor == null) { return; }
var parent = new GUIFrame(new RectTransform(new Point(editor.Rect.Width, (int)(60 * GUI.yScale)), editor.RectTransform), style: null)
{
CanBeFocused = false
@@ -4386,7 +4389,7 @@ namespace Barotrauma.CharacterEditor
ResetParamsEditor();
}
limb.PullJointWorldAnchorA = ScreenToSim(PlayerInput.MousePosition);
- TryUpdateLimbParam(limb, "pullpos", ConvertUnits.ToDisplayUnits(limb.PullJointLocalAnchorA / limb.Params.Ragdoll.LimbScale));
+ TryUpdateLimbParam(limb, "pullpos", ConvertUnits.ToDisplayUnits(limb.PullJointLocalAnchorA / limb.Params.Scale / limb.Params.Ragdoll.LimbScale));
GUI.DrawLine(spriteBatch, SimToScreen(limb.SimPosition), tformedPullPos, Color.MediumPurple);
});
}
@@ -4469,7 +4472,7 @@ namespace Barotrauma.CharacterEditor
if (joint.BodyA == limb.body.FarseerBody)
{
joint.LocalAnchorA += input;
- Vector2 transformedValue = ConvertUnits.ToDisplayUnits(joint.LocalAnchorA / RagdollParams.JointScale);
+ Vector2 transformedValue = ConvertUnits.ToDisplayUnits(joint.LocalAnchorA / joint.Scale);
TryUpdateJointParam(joint, "limb1anchor", transformedValue);
// Snap all selected joints to the first selected
if (copyJointSettings)
@@ -4484,7 +4487,7 @@ namespace Barotrauma.CharacterEditor
else if (joint.BodyB == limb.body.FarseerBody)
{
joint.LocalAnchorB += input;
- Vector2 transformedValue = ConvertUnits.ToDisplayUnits(joint.LocalAnchorB / RagdollParams.JointScale);
+ Vector2 transformedValue = ConvertUnits.ToDisplayUnits(joint.LocalAnchorB / joint.Scale);
TryUpdateJointParam(joint, "limb2anchor", transformedValue);
// Snap all selected joints to the first selected
if (copyJointSettings)
@@ -4504,12 +4507,12 @@ namespace Barotrauma.CharacterEditor
if (joint.BodyA == limb.body.FarseerBody && otherJoint.BodyA == otherLimb.body.FarseerBody)
{
otherJoint.LocalAnchorA = joint.LocalAnchorA;
- TryUpdateJointParam(otherJoint, "limb1anchor", ConvertUnits.ToDisplayUnits(joint.LocalAnchorA / RagdollParams.JointScale));
+ TryUpdateJointParam(otherJoint, "limb1anchor", ConvertUnits.ToDisplayUnits(joint.LocalAnchorA / joint.Scale));
}
else if (joint.BodyB == limb.body.FarseerBody && otherJoint.BodyB == otherLimb.body.FarseerBody)
{
otherJoint.LocalAnchorB = joint.LocalAnchorB;
- TryUpdateJointParam(otherJoint, "limb2anchor", ConvertUnits.ToDisplayUnits(joint.LocalAnchorB / RagdollParams.JointScale));
+ TryUpdateJointParam(otherJoint, "limb2anchor", ConvertUnits.ToDisplayUnits(joint.LocalAnchorB / joint.Scale));
}
});
}
@@ -4873,10 +4876,10 @@ namespace Barotrauma.CharacterEditor
{
// We want the collider to be slightly smaller than the source rect, because the source rect is usually a bit bigger than the graphic.
float multiplier = 0.85f;
- l.body.SetSize(new Vector2(ConvertUnits.ToSimUnits(width), ConvertUnits.ToSimUnits(height)) * RagdollParams.LimbScale * RagdollParams.TextureScale * multiplier);
- TryUpdateLimbParam(l, "radius", ConvertUnits.ToDisplayUnits(l.body.radius / RagdollParams.LimbScale / RagdollParams.TextureScale));
- TryUpdateLimbParam(l, "width", ConvertUnits.ToDisplayUnits(l.body.width / RagdollParams.LimbScale / RagdollParams.TextureScale));
- TryUpdateLimbParam(l, "height", ConvertUnits.ToDisplayUnits(l.body.height / RagdollParams.LimbScale / RagdollParams.TextureScale));
+ l.body.SetSize(new Vector2(ConvertUnits.ToSimUnits(width), ConvertUnits.ToSimUnits(height)) * l.Scale * RagdollParams.TextureScale * multiplier);
+ TryUpdateLimbParam(l, "radius", ConvertUnits.ToDisplayUnits(l.body.radius / l.Params.Scale / RagdollParams.LimbScale / RagdollParams.TextureScale));
+ TryUpdateLimbParam(l, "width", ConvertUnits.ToDisplayUnits(l.body.width / l.Params.Scale / RagdollParams.LimbScale / RagdollParams.TextureScale));
+ TryUpdateLimbParam(l, "height", ConvertUnits.ToDisplayUnits(l.body.height / l.Params.Scale / RagdollParams.LimbScale / RagdollParams.TextureScale));
}
void RecalculateOrigin(Limb l)
{
@@ -4963,7 +4966,7 @@ namespace Barotrauma.CharacterEditor
{
continue;
}
- Vector2 tformedJointPos = jointPos = jointPos / RagdollParams.JointScale / limb.TextureScale * spriteSheetZoom;
+ Vector2 tformedJointPos = jointPos = jointPos / joint.Scale / limb.TextureScale * spriteSheetZoom;
tformedJointPos.Y = -tformedJointPos.Y;
tformedJointPos.X *= character.AnimController.Dir;
tformedJointPos += limbScreenPos;
@@ -4991,11 +4994,11 @@ namespace Barotrauma.CharacterEditor
Vector2 input = ConvertUnits.ToSimUnits(scaledMouseSpeed);
input.Y = -input.Y;
input.X *= character.AnimController.Dir;
- input *= RagdollParams.JointScale * limb.TextureScale / spriteSheetZoom;
+ input *= joint.Scale * limb.TextureScale / spriteSheetZoom;
if (joint.BodyA == limb.body.FarseerBody)
{
joint.LocalAnchorA += input;
- Vector2 transformedValue = ConvertUnits.ToDisplayUnits(joint.LocalAnchorA / RagdollParams.JointScale);
+ Vector2 transformedValue = ConvertUnits.ToDisplayUnits(joint.LocalAnchorA / joint.Scale);
TryUpdateJointParam(joint, "limb1anchor", transformedValue);
// Snap all selected joints to the first selected
if (copyJointSettings)
@@ -5010,7 +5013,7 @@ namespace Barotrauma.CharacterEditor
else if (joint.BodyB == limb.body.FarseerBody)
{
joint.LocalAnchorB += input;
- Vector2 transformedValue = ConvertUnits.ToDisplayUnits(joint.LocalAnchorB / RagdollParams.JointScale);
+ Vector2 transformedValue = ConvertUnits.ToDisplayUnits(joint.LocalAnchorB / joint.Scale);
TryUpdateJointParam(joint, "limb2anchor", transformedValue);
// Snap all selected joints to the first selected
if (copyJointSettings)
@@ -5029,12 +5032,12 @@ namespace Barotrauma.CharacterEditor
if (joint.BodyA == limb.body.FarseerBody && otherJoint.BodyA == otherLimb.body.FarseerBody)
{
otherJoint.LocalAnchorA = joint.LocalAnchorA;
- TryUpdateJointParam(otherJoint, "limb1anchor", ConvertUnits.ToDisplayUnits(joint.LocalAnchorA / RagdollParams.JointScale));
+ TryUpdateJointParam(otherJoint, "limb1anchor", ConvertUnits.ToDisplayUnits(joint.LocalAnchorA / joint.Scale));
}
else if (joint.BodyB == limb.body.FarseerBody && otherJoint.BodyB == otherLimb.body.FarseerBody)
{
otherJoint.LocalAnchorB = joint.LocalAnchorB;
- TryUpdateJointParam(otherJoint, "limb2anchor", ConvertUnits.ToDisplayUnits(joint.LocalAnchorB / RagdollParams.JointScale));
+ TryUpdateJointParam(otherJoint, "limb2anchor", ConvertUnits.ToDisplayUnits(joint.LocalAnchorB / joint.Scale));
}
});
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs
index 3eeb3be8a..3a0e2d267 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs
@@ -91,7 +91,13 @@ namespace Barotrauma
c.DoVisibilityCheck(cam);
if (c.IsVisible != wasVisible)
{
- c.AnimController.Limbs.ForEach(l => { if (l.LightSource != null) l.LightSource.Enabled = c.IsVisible; });
+ c.AnimController.Limbs.ForEach(l =>
+ {
+ if (l.LightSource != null)
+ {
+ l.LightSource.Enabled = c.IsVisible;
+ }
+ });
}
}
@@ -282,14 +288,23 @@ namespace Barotrauma
}
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.LinearWrap, DepthStencilState.None, null, null, cam.Transform);
- foreach (Character c in Character.CharacterList) c.DrawFront(spriteBatch, cam);
-
- if (Level.Loaded != null) Level.Loaded.DrawFront(spriteBatch, cam);
- if (GameMain.DebugDraw && GameMain.GameSession?.EventManager != null)
+ foreach (Character c in Character.CharacterList)
{
- GameMain.GameSession.EventManager.DebugDraw(spriteBatch);
+ c.DrawFront(spriteBatch, cam);
+ }
+ if (Level.Loaded != null)
+ {
+ Level.Loaded.DrawFront(spriteBatch, cam);
+ }
+ if (GameMain.DebugDraw)
+ {
+ MapEntity.mapEntityList.ForEach(me => me.AiTarget?.Draw(spriteBatch));
+ Character.CharacterList.ForEach(c => c.AiTarget?.Draw(spriteBatch));
+ if (GameMain.GameSession?.EventManager != null)
+ {
+ GameMain.GameSession.EventManager.DebugDraw(spriteBatch);
+ }
}
-
spriteBatch.End();
if (GameMain.LightManager.LosEnabled && GameMain.LightManager.LosMode != LosMode.None && Character.Controlled != null)
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/LevelEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/LevelEditorScreen.cs
index 7ef7322d3..a89bb23c3 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Screens/LevelEditorScreen.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/LevelEditorScreen.cs
@@ -517,8 +517,21 @@ namespace Barotrauma
{
foreach (XElement element in doc.Root.Elements())
{
- if (element.Name.ToString().ToLowerInvariant() != genParams.Name.ToLowerInvariant()) continue;
- SerializableProperty.SerializeProperties(genParams, element, true);
+ XElement levelParamElement = element;
+ if (element.IsOverride())
+ {
+ foreach (XElement subElement in element.Elements())
+ {
+ if (subElement.Name.ToString().Equals(genParams.Name, StringComparison.OrdinalIgnoreCase))
+ {
+ SerializableProperty.SerializeProperties(genParams, subElement, true);
+ }
+ }
+ }
+ else if (element.Name.ToString().Equals(genParams.Name, StringComparison.OrdinalIgnoreCase))
+ {
+ SerializableProperty.SerializeProperties(genParams, element, true);
+ }
break;
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/LobbyScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/LobbyScreen.cs
index 7a3818d74..da10f626d 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Screens/LobbyScreen.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/LobbyScreen.cs
@@ -79,8 +79,7 @@ namespace Barotrauma
private IEnumerable
public static class Program
{
+#if LINUX
+ ///
+ /// Sets the required environment variables for the game to initialize Steamworks correctly.
+ ///
+ [DllImport("linux_steam_env", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
+ private static extern void setLinuxEnv();
+#endif
+
///
/// The main entry point for the application.
///
[STAThread]
static void Main(string[] args)
{
- GameMain game = null;
-
#if !DEBUG
- try
- {
+ AppDomain currentDomain = AppDomain.CurrentDomain;
+ currentDomain.UnhandledException += new UnhandledExceptionEventHandler(CrashHandler);
#endif
- Console.WriteLine("Barotrauma Dedicated Server " + GameMain.Version +
- " (" + AssemblyInfo.GetBuildString() + ", branch " + AssemblyInfo.GetGitBranch() + ", revision " + AssemblyInfo.GetGitRevision() + ")");
-
- game = new GameMain(args);
-
- game.Run();
- if (GameSettings.SendUserStatistics) { GameAnalytics.OnQuit(); }
- SteamManager.ShutDown();
-#if !DEBUG
- }
- catch (Exception e)
- {
- CrashDump(game, "servercrashreport.log", e);
- GameMain.Server?.NotifyCrash();
- }
+#if LINUX
+ setLinuxEnv();
#endif
+ Console.WriteLine("Barotrauma Dedicated Server " + GameMain.Version +
+ " (" + AssemblyInfo.GetBuildString() + ", branch " + AssemblyInfo.GetGitBranch() + ", revision " + AssemblyInfo.GetGitRevision() + ")");
+
+ Game = new GameMain(args);
+
+ Game.Run();
+ if (GameSettings.SendUserStatistics) { GameAnalytics.OnQuit(); }
+ SteamManager.ShutDown();
}
-
- static void CrashDump(GameMain game, string filePath, Exception exception)
+
+ static GameMain Game;
+
+ private static void CrashHandler(object sender, UnhandledExceptionEventArgs args)
{
try
{
- GameMain.Server?.ServerSettings?.SaveSettings();
- GameMain.Server?.ServerSettings?.BanList.Save();
- if (GameMain.Server?.ServerSettings?.KarmaPreset == "custom")
- {
- GameMain.Server?.KarmaManager?.SaveCustomPreset();
- GameMain.Server?.KarmaManager?.Save();
- }
+ Game?.Exit();
+ CrashDump("servercrashreport.log", (Exception)args.ExceptionObject);
+ GameMain.Server?.NotifyCrash();
}
- //gotta catch them all, we don't want to crash while writing a crash report
- catch (Exception e)
+ catch
{
- string errorMsg = "Exception thrown while writing a crash report: " + e.Message + "\n" + e.StackTrace;
- GameAnalyticsManager.AddErrorEventOnce("CrashDump:FailedToSaveSettings", EGAErrorSeverity.Error, errorMsg);
+ //exception handler is broken, we have a serious problem here!!
+ return;
+ }
+ }
+
+ static void CrashDump(string filePath, Exception exception)
+ {
+ GameMain.Server?.ServerSettings?.SaveSettings();
+ GameMain.Server?.ServerSettings?.BanList.Save();
+ if (GameMain.Server?.ServerSettings?.KarmaPreset == "custom")
+ {
+ GameMain.Server?.KarmaManager?.SaveCustomPreset();
+ GameMain.Server?.KarmaManager?.Save();
}
int existingFiles = 0;
@@ -81,9 +91,11 @@ namespace Barotrauma
sb.AppendLine("\n");
sb.AppendLine("Barotrauma seems to have crashed. Sorry for the inconvenience! ");
sb.AppendLine("\n");
- sb.AppendLine("Game version " + GameMain.Version +
- " (" + AssemblyInfo.GetBuildString() + ", branch " + AssemblyInfo.GetGitBranch() + ", revision " + AssemblyInfo.GetGitRevision() + ")");
- sb.AppendLine("Selected content packages: " + (!GameMain.SelectedPackages.Any() ? "None" : string.Join(", ", GameMain.SelectedPackages.Select(c => c.Name))));
+ sb.AppendLine("Game version " + GameMain.Version + " (" + AssemblyInfo.GetBuildString() + ", branch " + AssemblyInfo.GetGitBranch() + ", revision " + AssemblyInfo.GetGitRevision() + ")");
+ if (GameMain.SelectedPackages != null)
+ {
+ sb.AppendLine("Selected content packages: " + (!GameMain.SelectedPackages.Any() ? "None" : string.Join(", ", GameMain.SelectedPackages.Select(c => c.Name))));
+ }
sb.AppendLine("Level seed: " + ((Level.Loaded == null) ? "no level loaded" : Level.Loaded.Seed));
sb.AppendLine("Loaded submarine: " + ((Submarine.MainSub == null) ? "None" : Submarine.MainSub.Name + " (" + Submarine.MainSub.MD5Hash + ")"));
sb.AppendLine("Selected screen: " + (Screen.Selected == null ? "None" : Screen.Selected.ToString()));
diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj
index 53ebc9350..833e3b9a8 100644
--- a/Barotrauma/BarotraumaServer/WindowsServer.csproj
+++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj
@@ -2,11 +2,11 @@
Exe
- netcoreapp3.0
+ netcoreapp3.1
Barotrauma
FakeFish, Undertow Games
Barotrauma Dedicated Server
- 0.9.7.1
+ 0.9.8.0
Copyright © FakeFish 2018-2020
AnyCPU;x64
DedicatedServer
@@ -54,6 +54,7 @@
+
diff --git a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml
index 7a70b5557..7aea02e63 100644
--- a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml
+++ b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml
@@ -1,5 +1,5 @@
-
-
+
+
@@ -55,6 +55,7 @@
+
@@ -73,7 +74,6 @@
-
@@ -106,34 +106,34 @@
-
-
+
+
-
+
-
-
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -141,4 +141,4 @@
-
+
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AITarget.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AITarget.cs
index 4f25f046e..ae2b245fe 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AITarget.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AITarget.cs
@@ -25,7 +25,7 @@ namespace Barotrauma
///
/// How long does it take for the ai target to fade out if not kept alive.
///
- public float FadeOutTime { get; private set; } = 1;
+ public float FadeOutTime { get; private set; } = 2;
public bool Static { get; private set; }
public bool StaticSound { get; private set; }
@@ -92,7 +92,7 @@ namespace Barotrauma
public string SonarLabel;
public string SonarIconIdentifier;
- public bool Enabled = true;
+ public bool Enabled => SoundRange > 0 || SightRange > 0;
public float MinSoundRange, MinSightRange;
public float MaxSoundRange = 100000, MaxSightRange = 100000;
@@ -195,7 +195,7 @@ namespace Barotrauma
public void Update(float deltaTime)
{
- if (!Static && FadeOutTime > 0)
+ if (Enabled && !Static && FadeOutTime > 0)
{
// The aitarget goes silent/invisible if the components don't keep it active
if (!StaticSight)
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs
index 44bfc5ad5..1d77645c4 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs
@@ -89,9 +89,6 @@ namespace Barotrauma
private readonly float memoryFadeTime = 0.5f;
private readonly float avoidTime = 3;
- //Has the character been attacked since the last Update.
- private bool wasAttacked;
-
private float avoidTimer;
public LatchOntoAI LatchOntoAI { get; private set; }
@@ -234,13 +231,6 @@ namespace Barotrauma
public override void Update(float deltaTime)
{
- if (wasAttacked)
- {
- LatchOntoAI?.DeattachFromBody();
- Character.AnimController.ReleaseStuckLimbs();
- wasAttacked = false;
- }
-
if (DisableEnemyAI) { return; }
base.Update(deltaTime);
@@ -305,7 +295,8 @@ namespace Barotrauma
FadeMemories(updateMemoriesInverval);
updateMemoriesTimer = updateMemoriesInverval;
}
- if (Character.HealthPercentage <= FleeHealthThreshold)
+ if (Character.HealthPercentage <= FleeHealthThreshold && SelectedAiTarget != null &&
+ SelectedAiTarget.Entity is Character target && (target.IsPlayer || IsBeingChasedBy(target)))
{
State = AIState.Flee;
wallTarget = null;
@@ -384,9 +375,9 @@ namespace Barotrauma
State = AIState.Idle;
return;
}
- float distance = Vector2.DistanceSquared(WorldPosition, SelectedAiTarget.WorldPosition);
+ float squaredDistance = Vector2.DistanceSquared(WorldPosition, SelectedAiTarget.WorldPosition);
var attackLimb = GetAttackLimb(SelectedAiTarget.WorldPosition);
- if (attackLimb != null && distance <= Math.Pow(attackLimb.attack.Range, 2))
+ if (attackLimb != null && squaredDistance <= Math.Pow(attackLimb.attack.Range, 2))
{
run = true;
if (State == AIState.Avoid)
@@ -402,17 +393,18 @@ namespace Barotrauma
{
bool isBeingChased = IsBeingChased;
float reactDistance = !isBeingChased && selectedTargetingParams != null && selectedTargetingParams.ReactDistance > 0 ? selectedTargetingParams.ReactDistance : GetPerceivingRange(SelectedAiTarget);
- if (distance <= Math.Pow(reactDistance + escapeMargin, 2))
+ if (squaredDistance <= Math.Pow(reactDistance + escapeMargin, 2))
{
float halfReactDistance = reactDistance / 2;
- if (State == AIState.Aggressive || State == AIState.PassiveAggressive && distance < Math.Pow(halfReactDistance, 2))
+ float attackDistance = selectedTargetingParams != null && selectedTargetingParams.AttackDistance > 0 ? selectedTargetingParams.AttackDistance : halfReactDistance;
+ if (State == AIState.Aggressive || State == AIState.PassiveAggressive && squaredDistance < Math.Pow(attackDistance, 2))
{
run = true;
UpdateAttack(deltaTime);
}
else
{
- run = isBeingChased ? true : distance < Math.Pow(halfReactDistance, 2);
+ run = isBeingChased ? true : squaredDistance < Math.Pow(halfReactDistance, 2);
if (escapeMargin <= 0)
{
escapeMargin = halfReactDistance;
@@ -440,13 +432,14 @@ namespace Barotrauma
SwarmBehavior.Refresh();
SwarmBehavior.UpdateSteering(deltaTime);
}
- float speed = Character.AnimController.GetCurrentSpeed(run);
+ float speed = Character.AnimController.GetCurrentSpeed(run && Character.CanRun);
+ steeringManager.Update(speed);
+ Character.AnimController.TargetMovement = Character.ApplyMovementLimits(Steering, State == AIState.Idle && Character.AnimController.InWater ? Steering.Length() : speed);
if (Character.CurrentHull != null && Character.AnimController.InWater)
{
// Halve the swimming speed inside the sub
- speed /= 2;
+ Character.AnimController.TargetMovement *= 0.5f;
}
- steeringManager.Update(speed);
}
#region Idle
@@ -577,7 +570,7 @@ namespace Barotrauma
else if (SelectedAiTarget?.Entity is Character targetCharacter && targetCharacter.CurrentHull == Character.CurrentHull)
{
// Steer away from the target if in the same room
- Vector2 escapeDir = Vector2.Normalize(SelectedAiTarget != null ? WorldPosition - SelectedAiTarget.WorldPosition : Character.GetTargetMovement());
+ Vector2 escapeDir = Vector2.Normalize(SelectedAiTarget != null ? WorldPosition - SelectedAiTarget.WorldPosition : Character.AnimController.TargetMovement);
if (!MathUtils.IsValid(escapeDir)) escapeDir = Vector2.UnitY;
SteeringManager.SteeringManual(deltaTime, escapeDir);
}
@@ -615,7 +608,7 @@ namespace Barotrauma
{
escapeTarget = null;
allGapsSearched = false;
- Vector2 escapeDir = Vector2.Normalize(SelectedAiTarget != null ? WorldPosition - SelectedAiTarget.WorldPosition : Character.GetTargetMovement());
+ Vector2 escapeDir = Vector2.Normalize(SelectedAiTarget != null ? WorldPosition - SelectedAiTarget.WorldPosition : Character.AnimController.TargetMovement);
if (!MathUtils.IsValid(escapeDir)) escapeDir = Vector2.UnitY;
SteeringManager.SteeringManual(deltaTime, escapeDir);
if (Character.CurrentHull == null)
@@ -758,6 +751,10 @@ namespace Barotrauma
bool pursue = false;
if (IsCoolDownRunning)
{
+ if (AttackingLimb.attack.CoolDownTimer >= AttackingLimb.attack.CoolDown + AttackingLimb.attack.CurrentRandomCoolDown - AttackingLimb.attack.AfterAttackDelay)
+ {
+ return;
+ }
switch (AttackingLimb.attack.AfterAttack)
{
case AIBehaviorAfterAttack.Pursue:
@@ -1017,58 +1014,63 @@ namespace Barotrauma
return;
}
- Vector2 offset = Character.SimPosition - steeringLimb.SimPosition;
- // Offset so that we don't overshoot the movement
- Vector2 steerPos = attackSimPos + offset;
-
- if (SteeringManager is IndoorsSteeringManager pathSteering)
+ if (AttackingLimb != null && AttackingLimb.attack.Retreat)
{
- if (pathSteering.CurrentPath != null)
+ UpdateFallBack(attackWorldPos, deltaTime, false);
+ }
+ else
+ {
+ Vector2 offset = Character.SimPosition - steeringLimb.SimPosition;
+ // Offset so that we don't overshoot the movement
+ Vector2 steerPos = attackSimPos + offset;
+ if (SteeringManager is IndoorsSteeringManager pathSteering)
{
- // Attack doors
- if (canAttackSub)
+ if (pathSteering.CurrentPath != null)
{
- // If the target is in the same hull, there shouldn't be any doors blocking the path
- if (targetCharacter == null || targetCharacter.CurrentHull != Character.CurrentHull)
+ // Attack doors
+ if (canAttackSub)
{
- var door = pathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? pathSteering.CurrentPath.NextNode?.ConnectedDoor;
- if (door != null && !door.IsOpen)
+ // 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 (door.Item.AiTarget != null && SelectedAiTarget != door.Item.AiTarget)
+ var door = pathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? pathSteering.CurrentPath.NextNode?.ConnectedDoor;
+ if (door != null && !door.IsOpen)
{
- SelectTarget(door.Item.AiTarget, selectedTargetMemory.Priority);
- return;
+ if (door.Item.AiTarget != null && SelectedAiTarget != door.Item.AiTarget)
+ {
+ SelectTarget(door.Item.AiTarget, selectedTargetMemory.Priority);
+ return;
+ }
}
}
}
- }
- // Steer towards the target if in the same room and swimming
- if ((Character.AnimController.InWater || pursue) && targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull))
- {
- SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(attackSimPos - steeringLimb.SimPosition));
+ // Steer towards the target if in the same room and swimming
+ if ((Character.AnimController.InWater || pursue) && targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull))
+ {
+ SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(attackSimPos - steeringLimb.SimPosition));
+ }
+ else
+ {
+ SteeringManager.SteeringSeek(steerPos, 2);
+ // Switch to Idle when cannot reach the target and if cannot damage the walls
+ if ((!canAttackSub || wallTarget == null) && !pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable)
+ {
+ State = AIState.Idle;
+ return;
+ }
+ }
}
else
{
- SteeringManager.SteeringSeek(steerPos, 2);
- // Switch to Idle when cannot reach the target and if cannot damage the walls
- if ((!canAttackSub || wallTarget == null) && !pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable)
- {
- State = AIState.Idle;
- return;
- }
+ SteeringManager.SteeringSeek(steerPos, 5);
}
}
else
{
- SteeringManager.SteeringSeek(steerPos, 5);
+ SteeringManager.SteeringSeek(steerPos, 10);
+ SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15);
}
}
- else
- {
- SteeringManager.SteeringSeek(steerPos, 10);
- SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15);
- }
-
if (canAttack)
{
if (!UpdateLimbAttack(deltaTime, AttackingLimb, attackSimPos, distance, attackTargetLimb))
@@ -1259,9 +1261,16 @@ namespace Barotrauma
float reactionTime = Rand.Range(0.1f, 0.3f);
updateTargetsTimer = Math.Min(updateTargetsTimer, reactionTime);
- wasAttacked = true;
-
+ bool wasLatched = IsLatchedOnSub;
+ Character.AnimController.ReleaseStuckLimbs();
+ LatchOntoAI?.DeattachFromBody();
if (attacker == null || attacker.AiTarget == null) { return; }
+ if (wasLatched)
+ {
+ avoidTimer = avoidTime * Rand.Range(0.75f, 1.25f);
+ SelectTarget(attacker.AiTarget);
+ return;
+ }
if (State == AIState.Flee)
{
@@ -1272,22 +1281,30 @@ namespace Barotrauma
if (attackResult.Damage > 0.0f)
{
bool canAttack = attacker.Submarine == Character.Submarine && canAttackCharacters || attacker.Submarine != null && canAttackSub;
- if (Character.Params.AI.AttackWhenProvoked)
+ if (Character.Params.AI.AttackWhenProvoked && canAttack)
{
- if (canAttack)
+ if (attacker.IsHusk)
+ {
+ ChangeTargetState("husk", AIState.Attack, 100);
+ }
+ else
{
ChangeTargetState(attacker, AIState.Attack, 100);
}
}
else if (!AIParams.HasTag(attacker.SpeciesName))
{
- if (attacker.AIController is EnemyAIController enemyAI)
+ if (attacker.IsHusk)
+ {
+ ChangeTargetState("husk", canAttack ? AIState.Attack : AIState.Escape, 100);
+ }
+ else if (attacker.AIController is EnemyAIController enemyAI)
{
if (enemyAI.CombatStrength > CombatStrength)
{
if (!AIParams.HasTag("stronger"))
{
- ChangeTargetState(attacker, AIState.Escape, 100);
+ ChangeTargetState(attacker, canAttack ? AIState.Attack : AIState.Escape, 100);
}
}
else if (enemyAI.CombatStrength < CombatStrength)
@@ -1305,7 +1322,7 @@ namespace Barotrauma
}
else
{
- ChangeTargetState(attacker, AIState.Escape, 100);
+ ChangeTargetState(attacker, canAttack ? AIState.Attack : AIState.Escape, 100);
}
}
}
@@ -1515,7 +1532,8 @@ namespace Barotrauma
if (targetCharacter.Submarine != Character.Submarine)
{
// In a different sub or the target is outside when we are inside or vice versa.
- if (State == AIState.Avoid && State == AIState.Escape & State == AIState.Flee)
+ // Make an exception for humans so that creatures cannot avoid/flee from humans that are inside a sub.
+ if (!targetCharacter.IsHuman && State == AIState.Avoid && State == AIState.Escape & State == AIState.Flee)
{
// If we are escaping, let's not ignore the target entirely, because there can be a gaps where we or they can go freely
if (targetCharacter.Submarine != null)
@@ -1550,29 +1568,36 @@ namespace Barotrauma
// Ignore targets that are in the same group (treat them like they were of the same species)
continue;
}
- if (enemy.CombatStrength > CombatStrength)
+ if (targetCharacter.IsHusk && AIParams.HasTag("husk"))
{
- targetingTag = "stronger";
+ targetingTag = "husk";
}
- else if (enemy.CombatStrength < CombatStrength)
+ else
{
- targetingTag = "weaker";
- }
- if (targetingTag == "stronger" && (State == AIState.Avoid || State == AIState.Escape || State == AIState.Flee))
- {
- if (SelectedAiTarget == aiTarget)
+ if (enemy.CombatStrength > CombatStrength)
{
- // Freightened -> hold on to the target
- valueModifier *= 2;
+ targetingTag = "stronger";
}
- if (IsBeingChasedBy(targetCharacter))
+ else if (enemy.CombatStrength < CombatStrength)
{
- valueModifier *= 2;
+ targetingTag = "weaker";
}
- if (Character.CurrentHull != null && !VisibleHulls.Contains(targetCharacter.CurrentHull))
+ if (targetingTag == "stronger" && (State == AIState.Avoid || State == AIState.Escape || State == AIState.Flee))
{
- // Inside but in a different room
- valueModifier /= 2;
+ if (SelectedAiTarget == aiTarget)
+ {
+ // Freightened -> hold on to the target
+ valueModifier *= 2;
+ }
+ if (IsBeingChasedBy(targetCharacter))
+ {
+ valueModifier *= 2;
+ }
+ if (Character.CurrentHull != null && !VisibleHulls.Contains(targetCharacter.CurrentHull))
+ {
+ // Inside but in a different room
+ valueModifier /= 2;
+ }
}
}
}
@@ -1746,6 +1771,9 @@ namespace Barotrauma
if (valueModifier > targetValue)
{
+ // Don't target items that we own.
+ // This is a rare case, and almost entirely related to Humanhusks, so let's check it last to reduce unnecessary checks (although the check shouldn't be expensive)
+ if (aiTarget.Entity is Item i && i.IsOwnedBy(character)) { continue; }
newTarget = aiTarget;
selectedTargetMemory = targetMemory;
targetValue = valueModifier;
@@ -1867,6 +1895,39 @@ namespace Barotrauma
private readonly Dictionary modifiedParams = new Dictionary();
private readonly Dictionary tempParams = new Dictionary();
+ private void ChangeParams(string tag, AIState state, float? priority = null, bool onlyExisting = false)
+ {
+ if (!AIParams.TryGetTarget(tag, out CharacterParams.TargetParams targetParams))
+ {
+ if (!onlyExisting && !tempParams.ContainsKey(tag))
+ {
+ if (AIParams.TryAddNewTarget(tag, state, priority ?? 100, out targetParams))
+ {
+ tempParams.Add(tag, targetParams);
+ }
+ }
+ }
+ if (targetParams != null)
+ {
+ if (priority.HasValue)
+ {
+ targetParams.Priority = priority.Value;
+ }
+ targetParams.State = state;
+ if (!modifiedParams.ContainsKey(tag))
+ {
+ modifiedParams.Add(tag, targetParams);
+ }
+ }
+ }
+
+ private void ChangeTargetState(string tag, AIState state, float? priority = null)
+ {
+ isStateChanged = true;
+ SetStateResetTimer();
+ ChangeParams(tag, state, priority);
+ }
+
///
/// Temporarily changes the predefined state for a target. Eg. Idle -> Attack.
///
@@ -1874,50 +1935,27 @@ namespace Barotrauma
{
isStateChanged = true;
SetStateResetTimer();
- ChangeParams(target.SpeciesName);
- // Target also items, because if we are blind and the target doesn't move, we can only perceive the target when it uses items
- if (state == AIState.Attack || state == AIState.Escape)
+ ChangeParams(target.SpeciesName, state, priority);
+ if (target.IsHuman)
{
- ChangeParams("weapon");
- ChangeParams("tool");
- }
- if (state == AIState.Attack)
- {
- // If the target is shooting from the submarine, we might not perceive it because it doesn't move.
- // --> Target the submarine too.
- if (target.Submarine != null && canAttackSub)
+ // Target also items, because if we are blind and the target doesn't move, we can only perceive the target when it uses items
+ if (state == AIState.Attack || state == AIState.Escape)
{
- ChangeParams("room");
- ChangeParams("wall");
- ChangeParams("door");
+ ChangeParams("weapon", state, priority);
+ ChangeParams("tool", state, priority);
}
- ChangeParams("provocative", onlyExisting: true);
- ChangeParams("light", onlyExisting: true);
- }
-
- void ChangeParams(string tag, bool onlyExisting = false)
- {
- if (!AIParams.TryGetTarget(tag, out CharacterParams.TargetParams targetParams))
+ if (state == AIState.Attack)
{
- if (!onlyExisting && !tempParams.ContainsKey(tag))
+ // If the target is shooting from the submarine, we might not perceive it because it doesn't move.
+ // --> Target the submarine too.
+ if (target.Submarine != null && canAttackSub)
{
- if (AIParams.TryAddNewTarget(tag, state, priority ?? 100, out targetParams))
- {
- tempParams.Add(tag, targetParams);
- }
- }
- }
- if (targetParams != null)
- {
- if (priority.HasValue)
- {
- targetParams.Priority = priority.Value;
- }
- targetParams.State = state;
- if (!modifiedParams.ContainsKey(tag))
- {
- modifiedParams.Add(tag, targetParams);
+ ChangeParams("room", state, priority);
+ ChangeParams("wall", state, priority);
+ ChangeParams("door", state, priority);
}
+ ChangeParams("provocative", state, priority, onlyExisting: true);
+ ChangeParams("light", state, priority, onlyExisting: true);
}
}
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs
index 0d685c7cc..1fc0fa6cb 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs
@@ -170,12 +170,7 @@ namespace Barotrauma
}
}
}
- if (run)
- {
- run = !AnimController.Crouching && !AnimController.IsMovingBackwards;
- }
- float currentSpeed = Character.AnimController.GetCurrentSpeed(run);
- steeringManager.Update(currentSpeed);
+ steeringManager.Update(Character.AnimController.GetCurrentSpeed(run && Character.CanRun));
bool ignorePlatforms = Character.AnimController.TargetMovement.Y < -0.5f &&
(-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X));
@@ -209,17 +204,6 @@ namespace Barotrauma
targetMovement = new Vector2(Character.AnimController.TargetMovement.X, MathHelper.Clamp(Character.AnimController.TargetMovement.Y, -1.0f, 1.0f));
}
- float maxSpeed = Character.ApplyTemporarySpeedLimits(currentSpeed);
- targetMovement.X = MathHelper.Clamp(targetMovement.X, -maxSpeed, maxSpeed);
- targetMovement.Y = MathHelper.Clamp(targetMovement.Y, -maxSpeed, maxSpeed);
-
- //apply speed multiplier if
- // a. it's boosting the movement speed and the character is trying to move fast (= running)
- // b. it's a debuff that decreases movement speed
- float speedMultiplier = Character.SpeedMultiplier;
- if (run || speedMultiplier <= 0.0f) targetMovement *= speedMultiplier;
- Character.ResetSpeedMultiplier(); // Reset, items will set the value before the next update
-
if (Character.AnimController.InWater && targetMovement.LengthSquared() < 0.000001f)
{
bool isAiming = false;
@@ -243,7 +227,7 @@ namespace Barotrauma
}
}
- Character.AnimController.TargetMovement = targetMovement;
+ Character.AnimController.TargetMovement = Character.ApplyMovementLimits(targetMovement, AnimController.GetCurrentSpeed(run));
flipTimer -= deltaTime;
if (flipTimer <= 0.0f)
@@ -323,7 +307,7 @@ namespace Barotrauma
|| ObjectiveManager.IsCurrentObjective()
|| ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(true).Any(o => o.KeepDivingGearOn);
bool removeDivingSuit = !Character.AnimController.HeadInWater && oxygenLow;
- AIObjectiveGoTo gotoObjective = ObjectiveManager.CurrentOrder as AIObjectiveGoTo;
+ AIObjectiveGoTo gotoObjective = ObjectiveManager.GetActiveObjective();
if (!removeDivingSuit)
{
bool targetHasNoSuit = gotoObjective != null && gotoObjective.mimic && !HasDivingSuit(gotoObjective.Target as Character);
@@ -536,14 +520,16 @@ namespace Barotrauma
Hull targetHull = null;
if (Character.CurrentHull != null)
{
+ bool isFighting = ObjectiveManager.HasActiveObjective();
+ bool isFleeing = ObjectiveManager.HasActiveObjective();
foreach (var hull in VisibleHulls)
{
- foreach (Character c in Character.CharacterList)
+ foreach (Character target in Character.CharacterList)
{
- if (c.CurrentHull != hull || !c.Enabled) { continue; }
- if (AIObjectiveFightIntruders.IsValidTarget(c, Character))
+ if (target.CurrentHull != hull || !target.Enabled) { continue; }
+ if (AIObjectiveFightIntruders.IsValidTarget(target, Character))
{
- if (AddTargets(Character, c) && newOrder == null)
+ if (AddTargets(Character, target) && newOrder == null)
{
var orderPrefab = Order.GetPrefab("reportintruders");
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
@@ -560,42 +546,48 @@ namespace Barotrauma
targetHull = hull;
}
}
- foreach (Character c in Character.CharacterList)
+ if (!isFighting)
{
- if (c.CurrentHull != hull) { continue; }
- if (AIObjectiveRescueAll.IsValidTarget(c, Character))
+ foreach (var gap in hull.ConnectedGaps)
{
- if (AddTargets(c, Character) && newOrder == null && !ObjectiveManager.HasActiveObjective())
+ if (AIObjectiveFixLeaks.IsValidTarget(gap, Character))
{
- var orderPrefab = Order.GetPrefab("requestfirstaid");
- newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
- targetHull = hull;
+ if (AddTargets(Character, gap) && newOrder == null && !gap.IsRoomToRoom)
+ {
+ var orderPrefab = Order.GetPrefab("reportbreach");
+ newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
+ targetHull = hull;
+ }
}
}
- }
- foreach (var gap in hull.ConnectedGaps)
- {
- if (AIObjectiveFixLeaks.IsValidTarget(gap, Character))
+ if (!isFleeing)
{
- if (AddTargets(Character, gap) && newOrder == null && !gap.IsRoomToRoom)
+ foreach (Character target in Character.CharacterList)
{
- var orderPrefab = Order.GetPrefab("reportbreach");
- newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
- targetHull = hull;
+ if (target.CurrentHull != hull) { continue; }
+ if (AIObjectiveRescueAll.IsValidTarget(target, Character))
+ {
+ if (AddTargets(Character, target) && newOrder == null && !ObjectiveManager.HasActiveObjective())
+ {
+ var orderPrefab = Order.GetPrefab("requestfirstaid");
+ newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
+ targetHull = hull;
+ }
+ }
}
- }
- }
- foreach (Item item in Item.ItemList)
- {
- if (item.CurrentHull != hull) { continue; }
- if (AIObjectiveRepairItems.IsValidTarget(item, Character))
- {
- if (item.Repairables.All(r => item.ConditionPercentage > r.AIRepairThreshold)) { continue; }
- if (AddTargets(Character, item) && newOrder == null && !ObjectiveManager.HasActiveObjective())
+ foreach (Item item in Item.ItemList)
{
- var orderPrefab = Order.GetPrefab("reportbrokendevices");
- newOrder = new Order(orderPrefab, hull, item.Repairables?.FirstOrDefault(), orderGiver: Character);
- targetHull = hull;
+ if (item.CurrentHull != hull) { continue; }
+ if (AIObjectiveRepairItems.IsValidTarget(item, Character))
+ {
+ if (item.Repairables.All(r => item.ConditionPercentage > r.AIRepairThreshold)) { continue; }
+ if (AddTargets(Character, item) && newOrder == null && !ObjectiveManager.HasActiveObjective())
+ {
+ var orderPrefab = Order.GetPrefab("reportbrokendevices");
+ newOrder = new Order(orderPrefab, hull, item.Repairables?.FirstOrDefault(), orderGiver: Character);
+ targetHull = hull;
+ }
+ }
}
}
}
@@ -650,7 +642,7 @@ namespace Barotrauma
// Should not cancel any existing ai objectives (so that if the character attacked you and then helped, we still would want to retaliate).
return;
}
- if (!attacker.IsRemotePlayer && Character.Controlled != attacker && attacker.AIController != null && attacker.AIController.Enabled)
+ if (attacker.IsBot)
{
// Don't retaliate on damage done by friendly ai, because we know that it's accidental
AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced));
@@ -664,9 +656,8 @@ namespace Barotrauma
}
else
{
- float currentVitality = Character.CharacterHealth.Vitality;
- float dmgPercentage = damage / currentVitality * 100;
- if (dmgPercentage < currentVitality / 10)
+ float dmgPercentage = MathUtils.Percentage(damage, Character.CharacterHealth.Vitality);
+ if (dmgPercentage < 10)
{
// Don't retaliate on minor (accidental) dmg done by characters that are in the same team
AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced));
@@ -711,7 +702,6 @@ namespace Barotrauma
public void SetOrder(Order order, string option, Character orderGiver, bool speak = true)
{
- SetOrderProjSpecific(order, option);
CurrentOrderOption = option;
CurrentOrder = order;
objectiveManager.SetOrder(order, option, orderGiver);
@@ -752,8 +742,6 @@ namespace Barotrauma
}
}
- partial void SetOrderProjSpecific(Order order, string option);
-
public override void SelectTarget(AITarget target)
{
SelectedAiTarget = target;
@@ -806,11 +794,11 @@ namespace Barotrauma
///
public static bool HasDivingMask(Character character, float conditionPercentage = 0) => HasItem(character, "divingmask", "oxygensource", conditionPercentage);
- public static bool HasItem(Character character, string identifier, string containedTag, float conditionPercentage = 0)
+ public static bool HasItem(Character character, string tagOrIdentifier, string containedTag = null, float conditionPercentage = 0)
{
if (character == null) { return false; }
if (character.Inventory == null) { return false; }
- var item = character.Inventory.FindItemByIdentifier(identifier) ?? character.Inventory.FindItemByTag(identifier);
+ var item = character.Inventory.FindItemByIdentifier(tagOrIdentifier) ?? character.Inventory.FindItemByTag(tagOrIdentifier);
return item != null &&
item.ConditionPercentage > conditionPercentage &&
character.HasEquippedItem(item) &&
@@ -934,7 +922,7 @@ namespace Barotrauma
visibleHulls = VisibleHulls;
}
// TODO: should we calculate the visible hulls for each hull? -> could be a bit heavy.
- bool ignoreFire = ObjectiveManager.IsCurrentObjective() || objectiveManager.HasActiveObjective();
+ bool ignoreFire = objectiveManager.HasActiveObjective();
bool ignoreWater = HasDivingSuit(character);
bool ignoreOxygen = ignoreWater || HasDivingMask(character);
bool ignoreEnemies = ObjectiveManager.IsCurrentObjective();
@@ -1022,15 +1010,23 @@ namespace Barotrauma
return false;
}
- public static int CountCrew(Character character, Func predicate = null)
+ public static int CountCrew(Character character, Func predicate = null, bool onlyActive = true, bool onlyBots = false)
{
if (character == null) { return 0; }
int count = 0;
- foreach (var c in Character.CharacterList)
+ foreach (var other in Character.CharacterList)
{
- if (FilterCrewMember(character, c))
+ if (onlyActive && !IsActive(other))
{
- if (predicate == null || predicate(c.AIController as HumanAIController))
+ continue;
+ }
+ if (onlyBots && other.IsPlayer)
+ {
+ continue;
+ }
+ if (FilterCrewMember(character, other))
+ {
+ if (predicate == null || predicate(other.AIController as HumanAIController))
{
count++;
}
@@ -1058,7 +1054,7 @@ namespace Barotrauma
public void DoForEachCrewMember(Action action) => DoForEachCrewMember(Character, action);
public bool IsTrueForAnyCrewMember(Func predicate) => IsTrueForAnyCrewMember(Character, predicate);
public bool IsTrueForAllCrewMembers(Func predicate) => IsTrueForAllCrewMembers(Character, predicate);
- public int CountCrew(Func predicate = null) => CountCrew(Character, predicate);
+ public int CountCrew(Func predicate = null, bool onlyActive = true, bool onlyBots = false) => CountCrew(Character, predicate, onlyActive, onlyBots);
#endregion
}
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs
index e0cd0198f..6628d279f 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs
@@ -134,7 +134,7 @@ namespace Barotrauma
private Vector2 CalculateSteeringSeek(Vector2 target, float weight, Func startNodeFilter = null, Func endNodeFilter = null, Func nodeFilter = null)
{
- bool needsNewPath = character.Params.PathFinderPriority > 0.5f && (currentPath == null || currentPath.Unreachable || currentPath.NextNode == null || Vector2.DistanceSquared(target, currentTarget) > 1);
+ bool needsNewPath = character.Params.PathFinderPriority > 0.5f && (currentPath == null || currentPath.Unreachable || currentPath.Finished || Vector2.DistanceSquared(target, currentTarget) > 1);
//find a new path if one hasn't been found yet or the target is different from the current target
if (needsNewPath || findPathTimer < -1.0f)
{
@@ -308,7 +308,7 @@ namespace Barotrauma
currentPath.SkipToNextNode();
}
}
- else
+ else if (!IsNextLadderSameAsCurrent)
{
Vector2 colliderBottom = character.AnimController.GetColliderBottom();
Vector2 colliderSize = collider.GetSize();
@@ -530,7 +530,6 @@ namespace Barotrauma
if (node.Waypoint != null && node.Waypoint.CurrentHull != null)
{
var hull = node.Waypoint.CurrentHull;
-
if (hull.FireSources.Count > 0)
{
foreach (FireSource fs in hull.FireSources)
@@ -538,9 +537,14 @@ namespace Barotrauma
penalty += fs.Size.X * 10.0f;
}
}
-
- if (character.NeedsAir && hull.WaterVolume / hull.Rect.Width > 100.0f) penalty += 500.0f;
- if (character.PressureProtection < 10.0f && hull.WaterVolume > hull.Volume) penalty += 1000.0f;
+ if (character.NeedsAir && hull.WaterVolume / hull.Rect.Width > 100.0f)
+ {
+ penalty += 500.0f;
+ }
+ if (character.PressureProtection < 10.0f && hull.WaterVolume > hull.Volume)
+ {
+ penalty += 1000.0f;
+ }
}
return penalty;
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/LatchOntoAI.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/LatchOntoAI.cs
index dc4c1b926..68746f340 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/LatchOntoAI.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/LatchOntoAI.cs
@@ -253,7 +253,7 @@ namespace Barotrauma
jointDir = attachLimb.Dir;
- Vector2 transformedLocalAttachPos = localAttachPos * attachLimb.character.AnimController.RagdollParams.LimbScale;
+ Vector2 transformedLocalAttachPos = localAttachPos * attachLimb.Scale * attachLimb.Params.Ragdoll.LimbScale;
if (jointDir < 0.0f) transformedLocalAttachPos.X = -transformedLocalAttachPos.X;
//transformedLocalAttachPos = Vector2.Transform(transformedLocalAttachPos, Matrix.CreateRotationZ(attachLimb.Rotation));
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs
index c71d9043d..bf057ce1a 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs
@@ -32,6 +32,18 @@ namespace Barotrauma
public virtual bool UnequipItems => false;
protected readonly List subObjectives = new List();
+ private float _cumulatedDevotion;
+ protected float CumulatedDevotion
+ {
+ get { return _cumulatedDevotion; }
+ set { _cumulatedDevotion = MathHelper.Clamp(value, 0, MaxDevotion); }
+ }
+
+ protected virtual float MaxDevotion => 10;
+
+ ///
+ /// Final priority value after all calculations.
+ ///
public float Priority { get; set; }
public float PriorityModifier { get; private set; } = 1;
public readonly Character character;
@@ -59,6 +71,7 @@ namespace Barotrauma
///
public virtual bool IsLoop { get; set; }
public IEnumerable SubObjectives => subObjectives;
+ public AIObjective CurrentSubObjective => subObjectives.FirstOrDefault();
private readonly List all = new List();
public IEnumerable GetSubObjectivesRecursive(bool includingSelf = false)
@@ -86,7 +99,7 @@ namespace Barotrauma
public AIObjective GetActiveObjective()
{
- var subObjective = SubObjectives.FirstOrDefault();
+ var subObjective = CurrentSubObjective;
return subObjective == null ? this : subObjective.GetActiveObjective();
}
@@ -157,7 +170,8 @@ namespace Barotrauma
{
if (!AllowSubObjectiveSorting) { return; }
if (subObjectives.None()) { return; }
- subObjectives.Sort((x, y) => y.GetPriority().CompareTo(x.GetPriority()));
+ subObjectives.ForEach(so => so.GetPriority());
+ subObjectives.Sort((x, y) => y.Priority.CompareTo(x.Priority));
if (ConcurrentObjectives)
{
subObjectives.ForEach(so => so.SortSubObjectives());
@@ -168,26 +182,38 @@ namespace Barotrauma
}
}
- public virtual float GetPriority() => Priority * PriorityModifier;
-
- public virtual bool IsDuplicate(T otherObjective) where T : AIObjective => otherObjective.Option == Option;
-
- public virtual void Update(float deltaTime)
+ ///
+ /// Call this only when the priority needs to be recalculated. Use the cached Priority property when you don't need to recalculate.
+ ///
+ public virtual float GetPriority()
{
if (objectiveManager.CurrentOrder == this)
{
Priority = AIObjectiveManager.OrderPriority;
}
- else if (objectiveManager.WaitTimer <= 0)
+ else
{
- 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);
+ Priority = CumulatedDevotion * PriorityModifier;
+ }
+ return Priority;
+ }
+
+ private void UpdateDevotion(float deltaTime)
+ {
+ var currentObjective = objectiveManager.CurrentObjective;
+ if (currentObjective != null && (currentObjective == this || currentObjective.subObjectives.Any(so => so == this)))
+ {
+ CumulatedDevotion += Devotion * PriorityModifier * deltaTime;
+ }
+ }
+
+ public virtual bool IsDuplicate(T otherObjective) where T : AIObjective => otherObjective.Option == Option;
+
+ public virtual void Update(float deltaTime)
+ {
+ if (objectiveManager.CurrentOrder != this && objectiveManager.WaitTimer <= 0)
+ {
+ UpdateDevotion(deltaTime);
}
subObjectives.ForEach(so => so.Update(deltaTime));
}
@@ -264,6 +290,7 @@ namespace Barotrauma
public virtual void OnDeselected()
{
+ CumulatedDevotion = 0;
Deselected?.Invoke();
}
@@ -282,6 +309,7 @@ namespace Barotrauma
isCompleted = false;
hasBeenChecked = false;
_abandon = false;
+ CumulatedDevotion = 0;
}
protected abstract void Act(float deltaTime);
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs
index aff1bf4b7..82b80055c 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs
@@ -100,7 +100,11 @@ namespace Barotrauma
}
}
- public override float GetPriority() => (Enemy != null && (Enemy.Removed || Enemy.IsDead)) ? 0 : Math.Min(100 * PriorityModifier, 100);
+ public override float GetPriority()
+ {
+ Priority = (Enemy != null && (Enemy.Removed || Enemy.IsDead)) ? 0 : Math.Min(100 * PriorityModifier, 100);
+ return Priority;
+ }
public override void Update(float deltaTime)
{
@@ -139,7 +143,7 @@ namespace Barotrauma
}
if (seekAmmunition == null)
{
- if (TryArm() && Enemy != null && !Enemy.Removed)
+ if (Mode != CombatMode.Retreat && TryArm() && Enemy != null && !Enemy.Removed)
{
OperateWeapon(deltaTime);
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveContainItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveContainItem.cs
index 15959d86f..a77455b67 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveContainItem.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveContainItem.cs
@@ -74,15 +74,6 @@ namespace Barotrauma
}
}
- public override float GetPriority()
- {
- if (objectiveManager.CurrentOrder == this)
- {
- return AIObjectiveManager.OrderPriority;
- }
- return 1.0f;
- }
-
private bool CheckItem(Item i) => itemIdentifiers.Any(id => i.Prefab.Identifier == id || i.HasTag(id)) && i.ConditionPercentage > ConditionLevel;
protected override void Act(float deltaTime)
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveDecontainItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveDecontainItem.cs
index 63a86fea7..f13e6e548 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveDecontainItem.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveDecontainItem.cs
@@ -54,15 +54,6 @@ namespace Barotrauma
protected override bool Check() => IsCompleted;
- public override float GetPriority()
- {
- if (objectiveManager.CurrentOrder == this)
- {
- return AIObjectiveManager.OrderPriority;
- }
- return 1.0f;
- }
-
protected override void Act(float deltaTime)
{
Item itemToDecontain = targetItem ?? sourceContainer.Inventory.FindItem(i => itemIdentifiers.Any(id => i.Prefab.Identifier == id || i.HasTag(id)), recursive: false);
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs
index 9dcb5c3a6..8ccdf33c8 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs
@@ -1,5 +1,4 @@
using Barotrauma.Items.Components;
-using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
@@ -29,32 +28,44 @@ namespace Barotrauma
public override float GetPriority()
{
if (!objectiveManager.IsCurrentOrder()
- && Character.CharacterList.Any(c => c.CurrentHull == targetHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { return 0; }
- float yDist = Math.Abs(character.WorldPosition.Y - targetHull.WorldPosition.Y);
- yDist = yDist > 100 ? yDist * 3 : 0;
- float dist = Math.Abs(character.WorldPosition.X - targetHull.WorldPosition.X) + yDist;
- float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, dist));
- if (targetHull == character.CurrentHull)
+ && Character.CharacterList.Any(c => c.CurrentHull == targetHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c)))
{
- distanceFactor = 1;
+ Priority = 0;
}
- float severity = AIObjectiveExtinguishFires.GetFireSeverity(targetHull);
- 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));
+ else
+ {
+ float yDist = Math.Abs(character.WorldPosition.Y - targetHull.WorldPosition.Y);
+ yDist = yDist > 100 ? yDist * 3 : 0;
+ float dist = Math.Abs(character.WorldPosition.X - targetHull.WorldPosition.X) + yDist;
+ float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, dist));
+ if (targetHull == character.CurrentHull)
+ {
+ distanceFactor = 1;
+ }
+ float severity = AIObjectiveExtinguishFires.GetFireSeverity(targetHull);
+ float severityFactor = MathHelper.Lerp(0, 1, severity / 100);
+ float devotion = CumulatedDevotion / 100;
+ Priority = MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + (severityFactor * distanceFactor * PriorityModifier), 0, 1));
+ }
+ return Priority;
}
protected override bool Check() => targetHull.FireSources.None();
+ private float sinTime;
protected override void Act(float deltaTime)
{
- var extinguisherItem = character.Inventory.FindItemByIdentifier("extinguisher") ?? character.Inventory.FindItemByTag("extinguisher");
+ var extinguisherItem = character.Inventory.FindItemByIdentifier("fireextinguisher") ?? character.Inventory.FindItemByTag("fireextinguisher");
if (extinguisherItem == null || extinguisherItem.Condition <= 0.0f || !character.HasEquippedItem(extinguisherItem))
{
TryAddSubObjective(ref getExtinguisherObjective, () =>
{
character.Speak(TextManager.Get("DialogFindExtinguisher"), null, 2.0f, "findextinguisher", 30.0f);
- return new AIObjectiveGetItem(character, "extinguisher", objectiveManager, equip: true);
+ return new AIObjectiveGetItem(character, "fireextinguisher", objectiveManager, equip: true)
+ {
+ // If the item is inside an unsafe hull, decrease the priority
+ GetItemPriority = i => HumanAIController.UnsafeHulls.Contains(i.CurrentHull) ? 0.1f : 1
+ };
});
}
else
@@ -79,8 +90,12 @@ namespace Barotrauma
{
useExtinquisherTimer = 0.0f;
}
+ // Aim
character.CursorPosition = fs.Position;
- if (extinguisher.Item.RequireAimToUse)
+ Vector2 fromCharacterToFireSource = fs.WorldPosition - character.WorldPosition;
+ float dist = fromCharacterToFireSource.Length();
+ character.CursorPosition += VectorExtensions.Forward(extinguisherItem.body.TransformedRotation + (float)Math.Sin(sinTime) / 2, dist / 2);
+ if (extinguisherItem.RequireAimToUse)
{
bool isOperatingButtons = false;
if (SteeringManager == PathSteering)
@@ -95,8 +110,9 @@ namespace Barotrauma
{
character.SetInput(InputType.Aim, false, true);
}
+ sinTime += deltaTime * 10;
}
- character.SetInput(extinguisher.Item.IsShootable ? InputType.Shoot : InputType.Use, false, true);
+ character.SetInput(extinguisherItem.IsShootable ? InputType.Shoot : InputType.Use, false, true);
extinguisher.Use(deltaTime, character);
if (!targetHull.FireSources.Contains(fs))
{
@@ -110,7 +126,7 @@ namespace Barotrauma
if (move)
{
//go to the first firesource
- TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(fs, character, objectiveManager)
+ TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(fs, character, objectiveManager, closeEnough: extinguisher.Range / 2)
{
DialogueIdentifier = "dialogcannotreachfire",
TargetName = fs.Hull.DisplayName
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveExtinguishFires.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveExtinguishFires.cs
index f5142278c..fd6bc49ce 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveExtinguishFires.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveExtinguishFires.cs
@@ -9,7 +9,6 @@ namespace Barotrauma
{
public override string DebugTag => "extinguish fires";
public override bool ForceRun => true;
- public override bool IgnoreUnsafeHulls => true;
public AIObjectiveExtinguishFires(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs
index c41580047..9b851aefb 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs
@@ -9,7 +9,6 @@ namespace Barotrauma
public override string DebugTag => $"find diving gear ({gearTag})";
public override bool ForceRun => true;
public override bool KeepDivingGearOn => true;
- public override bool IgnoreUnsafeHulls => true;
private readonly string gearTag;
private readonly string fallbackTag;
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs
index 1676bffc4..87732b7b0 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs
@@ -26,13 +26,35 @@ namespace Barotrauma
private AIObjectiveGoTo goToObjective;
private AIObjectiveFindDivingGear divingGearObjective;
- public AIObjectiveFindSafety(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
+ public AIObjectiveFindSafety(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
protected override bool Check() => false;
public override bool CanBeCompleted => true;
private bool resetPriority;
+ public override float GetPriority()
+ {
+ if (character.CurrentHull == null)
+ {
+ Priority = objectiveManager.CurrentOrder is AIObjectiveGoTo ? 0 : 100;
+ }
+ else
+ {
+ if (HumanAIController.NeedsDivingGear(character, character.CurrentHull, out _) && !HumanAIController.HasDivingGear(character))
+ {
+ Priority = 100;
+ }
+ Priority = MathHelper.Clamp(Priority, 0, 100);
+ if (divingGearObjective != null && !divingGearObjective.IsCompleted && divingGearObjective.CanBeCompleted)
+ {
+ // Boost the priority while seeking the diving gear
+ Priority = Math.Max(Priority, Math.Min(AIObjectiveManager.OrderPriority + 20, 100));
+ }
+ }
+ return Priority;
+ }
+
public override void Update(float deltaTime)
{
if (resetPriority)
@@ -44,28 +66,19 @@ namespace Barotrauma
if (character.CurrentHull == null)
{
currenthullSafety = 0;
- Priority = objectiveManager.CurrentOrder is AIObjectiveGoTo ? 0 : 100;
- return;
- }
- if (HumanAIController.NeedsDivingGear(character, character.CurrentHull, out _) && !HumanAIController.HasDivingGear(character))
- {
- Priority = 100;
- }
- currenthullSafety = HumanAIController.CurrentHullSafety;
- 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)
- {
- // Boost the priority while seeking the diving gear
- Priority = Math.Max(Priority, Math.Min(AIObjectiveManager.OrderPriority + 20, 100));
+ currenthullSafety = HumanAIController.CurrentHullSafety;
+ if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD)
+ {
+ Priority -= priorityDecrease * deltaTime;
+ }
+ else
+ {
+ float dangerFactor = (100 - currenthullSafety) / 100;
+ Priority += dangerFactor * priorityIncrease * deltaTime;
+ }
}
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFixLeak.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFixLeak.cs
index 9467b940c..4121ccdce 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFixLeak.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFixLeak.cs
@@ -29,16 +29,23 @@ namespace Barotrauma
public override float GetPriority()
{
- if (Leak.Removed || Leak.Open <= 0) { return 0; }
- float xDist = Math.Abs(character.WorldPosition.X - Leak.WorldPosition.X);
- float yDist = Math.Abs(character.WorldPosition.Y - Leak.WorldPosition.Y);
- // Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally).
- // If the target is close, ignore the distance factor alltogether so that we keep fixing the leaks that are nearby.
- float distanceFactor = xDist < 200 && yDist < 100 ? 1 : MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, xDist + yDist * 3.0f));
- float severity = AIObjectiveFixLeaks.GetLeakSeverity(Leak) / 100;
- float max = Math.Min((AIObjectiveManager.OrderPriority - 1), 90);
- float devotion = Math.Min(Priority, 10) / 100;
- return MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + severity * distanceFactor * PriorityModifier, 0, 1));
+ if (Leak.Removed || Leak.Open <= 0)
+ {
+ Priority = 0;
+ }
+ else
+ {
+ float xDist = Math.Abs(character.WorldPosition.X - Leak.WorldPosition.X);
+ float yDist = Math.Abs(character.WorldPosition.Y - Leak.WorldPosition.Y);
+ // Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally).
+ // If the target is close, ignore the distance factor alltogether so that we keep fixing the leaks that are nearby.
+ float distanceFactor = xDist < 200 && yDist < 100 ? 1 : MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, xDist + yDist * 3.0f));
+ float severity = AIObjectiveFixLeaks.GetLeakSeverity(Leak) / 100;
+ float max = Math.Min((AIObjectiveManager.OrderPriority - 1), 90);
+ float devotion = CumulatedDevotion / 100;
+ Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier), 0, 1));
+ }
+ return Priority;
}
protected override void Act(float deltaTime)
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFixLeaks.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFixLeaks.cs
index 5e35d9135..659958632 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFixLeaks.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFixLeaks.cs
@@ -11,7 +11,6 @@ namespace Barotrauma
public override string DebugTag => "fix leaks";
public override bool ForceRun => true;
public override bool KeepDivingGearOn => true;
- public override bool IgnoreUnsafeHulls => true;
public AIObjectiveFixLeaks(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
@@ -36,7 +35,7 @@ namespace Barotrauma
protected override float TargetEvaluation()
{
- int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective());
+ int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective(), onlyBots: true);
int totalLeaks = Targets.Count();
if (totalLeaks == 0) { return 0; }
int secondaryLeaks = Targets.Count(l => l.IsRoomToRoom);
@@ -44,13 +43,13 @@ namespace Barotrauma
bool anyFixers = otherFixers > 0;
if (objectiveManager.CurrentOrder == this)
{
- float ratio = anyFixers ? totalLeaks / otherFixers : 1;
+ float ratio = anyFixers ? totalLeaks / (float)otherFixers : 1;
return Targets.Sum(t => GetLeakSeverity(t)) * ratio;
}
else
{
float ratio = leaks == 0 ? 1 : anyFixers ? leaks / otherFixers : 1;
- if (anyFixers && (ratio <= 1 || otherFixers > 5 || otherFixers / HumanAIController.CountCrew() > 0.75f))
+ if (anyFixers && (ratio <= 1 || otherFixers > 5 || otherFixers / (float)HumanAIController.CountCrew(onlyBots: true) > 0.75f))
{
// Enough fixers
return 0;
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs
index 27950b88d..626c49064 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs
@@ -31,15 +31,6 @@ namespace Barotrauma
public bool AllowToFindDivingGear { get; set; } = true;
- public override float GetPriority()
- {
- if (objectiveManager.CurrentOrder == this)
- {
- return AIObjectiveManager.OrderPriority;
- }
- return 1.0f;
- }
-
public AIObjectiveGetItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, bool equip = true, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier)
{
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs
index f76dbdd71..384c7dade 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs
@@ -51,14 +51,23 @@ namespace Barotrauma
public override float GetPriority()
{
- if (followControlledCharacter && Character.Controlled == null) { return 0.0f; }
- if (Target is Entity e && e.Removed) { return 0.0f; }
- if (IgnoreIfTargetDead && Target is Character character && character.IsDead) { return 0.0f; }
- if (objectiveManager.CurrentOrder == this)
+ if (followControlledCharacter && Character.Controlled == null)
{
- return AIObjectiveManager.OrderPriority;
+ Priority = 0;
}
- return 1.0f;
+ if (Target is Entity e && e.Removed)
+ {
+ Priority = 0;
+ }
+ if (IgnoreIfTargetDead && Target is Character character && character.IsDead)
+ {
+ Priority = 0;
+ }
+ else
+ {
+ return base.GetPriority();
+ }
+ return Priority;
}
public AIObjectiveGoTo(ISpatialEntity target, Character character, AIObjectiveManager objectiveManager, bool repeat = false, bool getDivingGearIfNeeded = true, float priorityModifier = 1, float closeEnough = 0)
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs
index 998691f95..5a62a53bb 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs
@@ -45,20 +45,17 @@ namespace Barotrauma
private float randomUpdateInterval = 5;
public float Random { get; private set; }
- public void SetRandom()
+ public void CalculatePriority()
{
Random = Rand.Range(0.5f, 1.5f);
randomTimer = randomUpdateInterval;
- }
-
- public override float GetPriority()
- {
float max = Math.Min(Math.Min(AIObjectiveManager.RunPriority, AIObjectiveManager.OrderPriority) - 1, 100);
float initiative = character.GetSkillLevel("initiative");
Priority = MathHelper.Lerp(1, max, MathUtils.InverseLerp(100, 0, initiative * Random));
- return Priority;
}
+ public override float GetPriority() => Priority;
+
public override void Update(float deltaTime)
{
if (objectiveManager.CurrentObjective == this)
@@ -69,7 +66,7 @@ namespace Barotrauma
}
else
{
- SetRandom();
+ CalculatePriority();
}
}
}
@@ -182,7 +179,7 @@ namespace Barotrauma
if (!character.IsClimbing)
{
if (SteeringManager != PathSteering || (PathSteering.CurrentPath != null &&
- (PathSteering.CurrentPath.NextNode == null || PathSteering.CurrentPath.Unreachable || PathSteering.CurrentPath.HasOutdoorsNodes)))
+ (PathSteering.CurrentPath.Finished || PathSteering.CurrentPath.Unreachable || PathSteering.CurrentPath.HasOutdoorsNodes)))
{
Wander(deltaTime);
return;
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveLoop.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveLoop.cs
index 61ce5ef6a..8c2a4b340 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveLoop.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveLoop.cs
@@ -45,6 +45,7 @@ namespace Barotrauma
public override bool CanBeCompleted => true;
public override bool AbandonWhenCannotCompleteSubjectives => false;
public override bool AllowSubObjectiveSorting => true;
+ public virtual bool InverseTargetEvaluation => false;
public override bool IsLoop { get => true; set => throw new System.Exception("Trying to set the value for IsLoop from: " + System.Environment.StackTrace); }
@@ -107,21 +108,46 @@ namespace Barotrauma
public override float GetPriority()
{
- if (character.LockHands) { return 0; }
- if (character.Submarine == null) { return 0; }
- if (Targets.None()) { return 0; }
- // Allow the target value to be more than 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)
+ if (character.LockHands || character.Submarine == null || Targets.None())
{
- return AIObjectiveManager.OrderPriority;
+ Priority = 0;
}
- 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);
+ else
+ {
+ // Allow the target value to be more than 100.
+ float targetValue = TargetEvaluation();
+ if (InverseTargetEvaluation)
+ {
+ targetValue = 100 - targetValue;
+ }
+ var currentSubObjective = CurrentSubObjective;
+ if (currentSubObjective != null && currentSubObjective.Priority > targetValue)
+ {
+ // If the priority is higher than the target value, let's just use it.
+ // The priority calculation is more precise, but it takes into account things like distances,
+ // so it's better not to use it if it's lower than the rougher targetValue.
+ targetValue = Priority;
+ }
+ // If the target value is less than 1% of the max value, let's just treat it as zero.
+ if (targetValue < 1)
+ {
+ Priority = 0;
+ }
+ else
+ {
+ if (objectiveManager.CurrentOrder == this)
+ {
+ Priority = AIObjectiveManager.OrderPriority;
+ }
+ else
+ {
+ float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90);
+ float value = MathHelper.Clamp((CumulatedDevotion + (targetValue * PriorityModifier)) / 100, 0, 1);
+ Priority = MathHelper.Lerp(0, max, value);
+ }
+ }
+ }
+ return Priority;
}
protected void UpdateTargets()
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs
index cdf43f230..ff06d4eb2 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs
@@ -41,6 +41,15 @@ namespace Barotrauma
public bool IsActiveObjective() where T : AIObjective => GetActiveObjective() is T;
public AIObjective GetActiveObjective() => CurrentObjective?.GetActiveObjective();
+ ///
+ /// Returns the last active objective of the specific type.
+ ///
+ public T GetActiveObjective() where T : AIObjective => CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).LastOrDefault(so => so is T) as T;
+
+ ///
+ /// Returns all active objectives of the specific type. Creates a new collection -> don't use too frequently.
+ ///
+ public IEnumerable GetActiveObjectives() where T : AIObjective => CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).Where(so => so is T).Select(so => so as T);
public bool HasActiveObjective() where T : AIObjective => CurrentObjective is T || CurrentObjective != null && CurrentObjective.GetSubObjectivesRecursive().Any(so => so is T);
@@ -146,7 +155,7 @@ namespace Barotrauma
{
var previousObjective = CurrentObjective;
var firstObjective = Objectives.FirstOrDefault();
- if (CurrentOrder != null && firstObjective != null && CurrentOrder.GetPriority() > firstObjective.GetPriority())
+ if (CurrentOrder != null && firstObjective != null && CurrentOrder.Priority > firstObjective.Priority)
{
CurrentObjective = CurrentOrder;
}
@@ -158,14 +167,14 @@ namespace Barotrauma
{
previousObjective?.OnDeselected();
CurrentObjective?.OnSelected();
- GetObjective().SetRandom();
+ GetObjective().CalculatePriority();
}
return CurrentObjective;
}
public float GetCurrentPriority()
{
- return CurrentObjective == null ? 0.0f : CurrentObjective.GetPriority();
+ return CurrentObjective == null ? 0.0f : CurrentObjective.Priority;
}
public void UpdateObjectives(float deltaTime)
@@ -203,9 +212,11 @@ namespace Barotrauma
public void SortObjectives()
{
+ CurrentOrder?.GetPriority();
+ Objectives.ForEach(o => o.GetPriority());
if (Objectives.Any())
{
- Objectives.Sort((x, y) => y.GetPriority().CompareTo(x.GetPriority()));
+ Objectives.Sort((x, y) => y.Priority.CompareTo(x.Priority));
}
GetCurrentObjective()?.SortSubObjectives();
}
@@ -297,7 +308,7 @@ namespace Barotrauma
{
IsLoop = true,
// Don't override unless it's an order by a player
- Override = orderGiver != null && (orderGiver == Character.Controlled || orderGiver.IsRemotePlayer)
+ Override = orderGiver != null && orderGiver.IsPlayer
};
break;
default:
@@ -306,7 +317,7 @@ namespace Barotrauma
{
IsLoop = true,
// Don't override unless it's an order by a player
- Override = orderGiver != null && (orderGiver == Character.Controlled || orderGiver.IsRemotePlayer)
+ Override = orderGiver != null && orderGiver.IsPlayer
};
break;
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveOperateItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveOperateItem.cs
index a726858d3..0c00d3e96 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveOperateItem.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveOperateItem.cs
@@ -3,6 +3,7 @@ using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
+using Barotrauma.Extensions;
namespace Barotrauma
{
@@ -31,19 +32,32 @@ namespace Barotrauma
public override float GetPriority()
{
- if (component.Item.ConditionPercentage <= 0) { return 0; }
- if (objectiveManager.CurrentOrder == this)
+ if (component.Item.ConditionPercentage <= 0)
{
- return AIObjectiveManager.OrderPriority;
+ Priority = 0;
}
- if (component.Item.CurrentHull == null) { return 0; }
- if (component.Item.CurrentHull.FireSources.Count > 0) { return 0; }
- if (IsOperatedByAnother(GetTarget())) { return 0; }
- if (Character.CharacterList.Any(c => c.CurrentHull == component.Item.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(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);
+ else
+ {
+ if (objectiveManager.CurrentOrder == this)
+ {
+ Priority = AIObjectiveManager.OrderPriority;
+ }
+ if (component.Item.CurrentHull == null || component.Item.CurrentHull.FireSources.Any() || IsOperatedByAnother(character, GetTarget(), out _))
+ {
+ Priority = 0;
+ }
+ else if (Character.CharacterList.Any(c => c.CurrentHull == component.Item.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c)))
+ {
+ Priority = 0;
+ }
+ else
+ {
+ float value = CumulatedDevotion + (AIObjectiveManager.OrderPriority * PriorityModifier);
+ float max = MathHelper.Min((AIObjectiveManager.OrderPriority - 1), 90);
+ Priority = MathHelper.Clamp(value, 0, max);
+ }
+ }
+ return Priority;
}
public AIObjectiveOperateItem(ItemComponent item, Character character, AIObjectiveManager objectiveManager, string option, bool requireEquip, Entity operateTarget = null, bool useController = false, float priorityModifier = 1)
@@ -62,25 +76,31 @@ namespace Barotrauma
}
}
- private bool IsOperatedByAnother(ItemComponent target)
+ public static bool IsOperatedByAnother(Character character, ItemComponent target, out Character operatingCharacter)
{
+ operatingCharacter = null;
foreach (var c in Character.CharacterList)
{
- if (c == character) { continue; }
- if (!HumanAIController.IsFriendly(c)) { continue; }
+ if (character != null && c == character) { continue; }
+ if (character?.AIController is HumanAIController humanAi && !humanAi.IsFriendly(c)) { continue; }
if (c.SelectedConstruction != target.Item) { continue; }
+ operatingCharacter = c;
// If the other character is player, don't try to operate
if (c.IsRemotePlayer || Character.Controlled == c) { return true; }
- if (c.AIController is HumanAIController humanAi)
+ if (c.AIController is HumanAIController controllingHumanAi)
{
// If the other character is ordered to operate the item, let him do it
- if (humanAi.ObjectiveManager.IsCurrentOrder())
+ if (controllingHumanAi.ObjectiveManager.IsCurrentOrder())
{
return true;
}
else
{
- if (target is Steering)
+ if (character == null)
+ {
+ return true;
+ }
+ else if (target is Steering)
{
// Steering is hard-coded -> cannot use the required skills collection defined in the xml
return character.GetSkillLevel("helm") <= c.GetSkillLevel("helm");
@@ -116,7 +136,7 @@ namespace Barotrauma
return;
}
// Don't allow to operate an item that someone with a better skills already operates, unless this is an order
- if (objectiveManager.CurrentOrder != this && IsOperatedByAnother(target))
+ if (objectiveManager.CurrentOrder != this && IsOperatedByAnother(character, target, out _))
{
// Don't abandon
return;
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectivePumpWater.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectivePumpWater.cs
index 9d5b1058c..0921dc1ff 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectivePumpWater.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectivePumpWater.cs
@@ -12,7 +12,6 @@ namespace Barotrauma
public override string DebugTag => "pump water";
public override bool KeepDivingGearOn => true;
public override bool UnequipItems => true;
- public override bool IgnoreUnsafeHulls => true;
private IEnumerable pumpList;
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItem.cs
index 20a892037..adcaf7124 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItem.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItem.cs
@@ -30,21 +30,28 @@ namespace Barotrauma
{
// TODO: priority list?
// Ignore items that are being repaired by someone else.
- if (Item.Repairables.Any(r => r.CurrentFixer != null && r.CurrentFixer != character)) { return 0; }
- float yDist = Math.Abs(character.WorldPosition.Y - Item.WorldPosition.Y);
- yDist = yDist > 100 ? yDist * 5 : 0;
- float dist = Math.Abs(character.WorldPosition.X - Item.WorldPosition.X) + yDist;
- float distanceFactor = MathHelper.Lerp(1, 0.25f, MathUtils.InverseLerp(0, 5000, dist));
- if (Item.CurrentHull == character.CurrentHull)
+ if (Item.Repairables.Any(r => r.CurrentFixer != null && r.CurrentFixer != character))
{
- distanceFactor = 1;
+ Priority = 0;
}
- float damagePriority = MathHelper.Lerp(1, 0, Item.Condition / Item.MaxCondition);
- float successFactor = MathHelper.Lerp(0, 1, Item.Repairables.Average(r => r.DegreeOfSuccess(character)));
- float isSelected = IsRepairing ? 50 : 0;
- float devotion = (Math.Min(Priority, 10) + isSelected) / 100;
- float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90);
- return MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + damagePriority * distanceFactor * successFactor * PriorityModifier, 0, 1));
+ else
+ {
+ float yDist = Math.Abs(character.WorldPosition.Y - Item.WorldPosition.Y);
+ yDist = yDist > 100 ? yDist * 5 : 0;
+ float dist = Math.Abs(character.WorldPosition.X - Item.WorldPosition.X) + yDist;
+ float distanceFactor = MathHelper.Lerp(1, 0.25f, MathUtils.InverseLerp(0, 5000, dist));
+ if (Item.CurrentHull == character.CurrentHull)
+ {
+ distanceFactor = 1;
+ }
+ float damagePriority = MathHelper.Lerp(1, 0, Item.Condition / Item.MaxCondition);
+ float successFactor = MathHelper.Lerp(0, 1, Item.Repairables.Average(r => r.DegreeOfSuccess(character)));
+ float isSelected = IsRepairing ? 50 : 0;
+ float devotion = (CumulatedDevotion + isSelected) / 100;
+ float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90);
+ Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (damagePriority * distanceFactor * successFactor * PriorityModifier), 0, 1));
+ }
+ return Priority;
}
protected override bool Check()
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItems.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItems.cs
index 28183a43c..f6df24810 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItems.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItems.cs
@@ -81,18 +81,17 @@ namespace Barotrauma
// Don't stop fixing until done
return 100;
}
- int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective());
+ int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective(), onlyBots: true);
int items = Targets.Count;
bool anyFixers = otherFixers > 0;
- float ratio = anyFixers ? items / otherFixers : 1;
- var result = ratio;
+ float ratio = anyFixers ? items / (float)otherFixers : 1;
if (objectiveManager.CurrentOrder == this)
{
return Targets.Sum(t => 100 - t.ConditionPercentage) * ratio;
}
else
{
- if (anyFixers && (ratio <= 1 || otherFixers > 5 || otherFixers / HumanAIController.CountCrew() > 0.75f))
+ if (anyFixers && (ratio <= 1 || otherFixers > 5 || otherFixers / (float)HumanAIController.CountCrew(onlyBots: true) > 0.75f))
{
// Enough fixers
return 0;
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs
index 2a8b6c756..57cea1a9f 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs
@@ -14,7 +14,7 @@ namespace Barotrauma
const float TreatmentDelay = 0.5f;
- const float CloseEnoughToTreat = 150.0f;
+ const float CloseEnoughToTreat = 100.0f;
private readonly Character targetCharacter;
@@ -44,6 +44,15 @@ namespace Barotrauma
Abandon = true;
return;
}
+ if (targetCharacter.SelectedBy != null && targetCharacter.SelectedBy != character)
+ {
+ var otherCharacter = character.SelectedBy;
+ if (otherCharacter != null)
+ {
+ // Someone else is rescuing/holding the target.
+ Abandon = otherCharacter.IsPlayer || character.GetSkillLevel("medical") < otherCharacter.GetSkillLevel("medical");
+ }
+ }
if (targetCharacter != character)
{
@@ -67,7 +76,11 @@ namespace Barotrauma
TargetName = targetCharacter.DisplayName
},
onCompleted: () => RemoveSubObjective(ref goToObjective),
- onAbandon: () => RemoveSubObjective(ref goToObjective));
+ onAbandon: () =>
+ {
+ RemoveSubObjective(ref goToObjective);
+ Abandon = true;
+ });
}
else
{
@@ -86,7 +99,11 @@ namespace Barotrauma
RemoveSubObjective(ref goToObjective);
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(safeHull, character, objectiveManager),
onCompleted: () => RemoveSubObjective(ref goToObjective),
- onAbandon: () => RemoveSubObjective(ref goToObjective));
+ onAbandon: () =>
+ {
+ RemoveSubObjective(ref goToObjective);
+ safeHull = character.CurrentHull;
+ });
}
}
}
@@ -105,7 +122,11 @@ namespace Barotrauma
TargetName = targetCharacter.DisplayName
},
onCompleted: () => RemoveSubObjective(ref goToObjective),
- onAbandon: () => RemoveSubObjective(ref goToObjective));
+ onAbandon: () =>
+ {
+ RemoveSubObjective(ref goToObjective);
+ Abandon = true;
+ });
}
else
{
@@ -127,6 +148,11 @@ namespace Barotrauma
private Dictionary currentTreatmentSuitabilities = new Dictionary();
private void GiveTreatment(float deltaTime)
{
+ if (!targetCharacter.IsPlayer)
+ {
+ // If the target is a bot, don't let it move
+ targetCharacter.AIController.SteeringManager.Reset();
+ }
if (treatmentTimer > 0.0f)
{
treatmentTimer -= deltaTime;
@@ -137,9 +163,8 @@ namespace Barotrauma
//find which treatments are the most suitable to treat the character's current condition
targetCharacter.CharacterHealth.GetSuitableTreatments(currentTreatmentSuitabilities, normalize: false);
- var allAfflictions = GetVitalityReducingAfflictions(targetCharacter).OrderByDescending(a => a.GetVitalityDecrease(targetCharacter.CharacterHealth));
//check if we already have a suitable treatment for any of the afflictions
- foreach (Affliction affliction in allAfflictions)
+ foreach (Affliction affliction in GetSortedAfflictions(targetCharacter))
{
foreach (KeyValuePair treatmentSuitability in affliction.Prefab.TreatmentSuitability)
{
@@ -200,10 +225,12 @@ namespace Barotrauma
onAbandon: () => RemoveSubObjective(ref getItemObjective));
}
}
- character.AnimController.Anim = AnimController.Animation.CPR;
+ if (character != targetCharacter)
+ {
+ character.AnimController.Anim = AnimController.Animation.CPR;
+ }
}
-
private void ApplyTreatment(Affliction affliction, Item item)
{
var targetLimb = targetCharacter.CharacterHealth.GetAfflictionLimb(affliction);
@@ -240,7 +267,7 @@ namespace Barotrauma
Abandon = true;
return false;
}
- bool isCompleted = AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) > AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager);
+ bool isCompleted = AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) >= AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager, character, targetCharacter);
if (isCompleted && targetCharacter != character)
{
character.Speak(TextManager.GetWithVariable("DialogTargetHealed", "[targetname]", targetCharacter.Name),
@@ -253,20 +280,24 @@ namespace Barotrauma
{
if (targetCharacter == null || targetCharacter.CurrentHull == null || targetCharacter.Removed || targetCharacter.IsDead)
{
- return 0;
+ Priority = 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.1f, MathUtils.InverseLerp(0, 5000, dist));
- if (targetCharacter.CurrentHull == character.CurrentHull)
+ else
{
- distanceFactor = 1;
+ // 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.1f, MathUtils.InverseLerp(0, 5000, dist));
+ if (targetCharacter.CurrentHull == character.CurrentHull)
+ {
+ distanceFactor = 1;
+ }
+ float vitalityFactor = 1 - AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) / 100;
+ float devotion = CumulatedDevotion / 100;
+ Priority = MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + (vitalityFactor * distanceFactor * PriorityModifier), 0, 1));
}
- float vitalityFactor = AIObjectiveRescueAll.GetVitalityFactor(targetCharacter);
- float devotion = Math.Min(Priority, 10) / 100;
- return MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + vitalityFactor * distanceFactor, 0, 1));
+ return Priority;
}
- public static IEnumerable GetVitalityReducingAfflictions(Character character) => character.CharacterHealth.GetAllAfflictions(a => a.GetVitalityDecrease(character.CharacterHealth) > 0);
+ public static IEnumerable GetSortedAfflictions(Character character) => CharacterHealth.SortAfflictionsBySeverity(character.CharacterHealth.GetAllAfflictions());
}
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescueAll.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescueAll.cs
index 6cc31471c..d3d9adfed 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescueAll.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescueAll.cs
@@ -8,11 +8,11 @@ namespace Barotrauma
{
public override string DebugTag => "rescue all";
public override bool ForceRun => true;
- public override bool IgnoreUnsafeHulls => true;
+ public override bool InverseTargetEvaluation => true;
private const float vitalityThreshold = 80;
- private const float vitalityThresholdForOrders = 95;
- public static float GetVitalityThreshold(AIObjectiveManager manager)
+ private const float vitalityThresholdForOrders = 100;
+ public static float GetVitalityThreshold(AIObjectiveManager manager, Character character, Character target)
{
if (manager == null)
{
@@ -20,7 +20,7 @@ namespace Barotrauma
}
else
{
- return manager.CurrentOrder is AIObjectiveRescueAll ? vitalityThresholdForOrders : vitalityThreshold;
+ return character == target || manager.CurrentOrder is AIObjectiveRescueAll ? vitalityThresholdForOrders : vitalityThreshold;
}
}
@@ -31,9 +31,50 @@ namespace Barotrauma
protected override IEnumerable GetList() => Character.CharacterList;
- protected override float TargetEvaluation() => Targets.Max(t => GetVitalityFactor(t));
+ protected override float TargetEvaluation()
+ {
+ int otherRescuers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective(), onlyBots: true);
+ int targetCount = Targets.Count;
+ bool anyRescuers = otherRescuers > 0;
+ float ratio = anyRescuers ? targetCount / (float)otherRescuers : 1;
+ if (objectiveManager.CurrentOrder == this)
+ {
+ return Targets.Min(t => GetVitalityFactor(t)) / ratio;
+ }
+ else
+ {
+ float multiplier = 1;
+ if (anyRescuers)
+ {
+ float mySkill = character.GetSkillLevel("medical");
+ int betterRescuers = HumanAIController.CountCrew(c => c != HumanAIController && c.Character.Info.Job.GetSkillLevel("medical") >= mySkill, onlyBots: true);
+ if (targetCount / (float)betterRescuers <= 1)
+ {
+ // Enough rescuers
+ return 100;
+ }
+ else
+ {
+ bool foundOtherMedics = HumanAIController.IsTrueForAnyCrewMember(c => c != HumanAIController && c.Character.Info.Job.Prefab.Identifier == "medicaldoctor");
+ if (foundOtherMedics)
+ {
+ if (character.Info.Job.Prefab.Identifier != "medicaldoctor")
+ {
+ // Double the vitality factor -> less likely to take action
+ multiplier = 2;
+ }
+ }
+ }
+ }
+ return Targets.Min(t => GetVitalityFactor(t)) / ratio * multiplier;
+ }
+ }
- public static float GetVitalityFactor(Character character) => Math.Min(character.HealthPercentage - character.Bleeding - character.Bloodloss - Math.Min(character.Oxygen, 0), 100);
+ public static float GetVitalityFactor(Character character)
+ {
+ float vitality = character.HealthPercentage - character.Bleeding - character.Bloodloss + Math.Min(character.Oxygen, 0);
+ return Math.Clamp(vitality, 0, 100);
+ }
protected override AIObjective ObjectiveConstructor(Character target)
=> new AIObjectiveRescue(character, target, objectiveManager, PriorityModifier);
@@ -47,16 +88,34 @@ namespace Barotrauma
if (!HumanAIController.IsFriendly(character, target)) { return false; }
if (character.AIController is HumanAIController humanAI)
{
- if (GetVitalityFactor(target) > GetVitalityThreshold(humanAI.ObjectiveManager)) { return false; }
+ if (GetVitalityFactor(target) >= GetVitalityThreshold(humanAI.ObjectiveManager, character, target)) { return false; }
+ if (!humanAI.ObjectiveManager.IsCurrentOrder())
+ {
+ // Ignore unsafe hulls, unless ordered
+ if (humanAI.UnsafeHulls.Contains(target.CurrentHull))
+ {
+ return false;
+ }
+ }
}
else
{
- if (GetVitalityFactor(target) > vitalityThreshold) { return false; }
+ if (GetVitalityFactor(target) >= vitalityThreshold) { return false; }
}
if (target.Submarine == null || character.Submarine == null) { return false; }
if (target.Submarine.TeamID != character.Submarine.TeamID) { return false; }
if (target.CurrentHull == null) { return false; }
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(target.CurrentHull, true)) { return false; }
+ if (!target.IsPlayer && HumanAIController.IsActive(target) && target.AIController is HumanAIController targetAI)
+ {
+ // Ignore all concious targets that are currently fighting, fleeing or treating characters
+ if (targetAI.ObjectiveManager.HasActiveObjective() ||
+ targetAI.ObjectiveManager.HasActiveObjective() ||
+ targetAI.ObjectiveManager.HasActiveObjective())
+ {
+ return false;
+ }
+ }
// Don't go into rooms that have enemies
if (Character.CharacterList.Any(c => c.CurrentHull == target.CurrentHull && !HumanAIController.IsFriendly(character, c) && HumanAIController.IsActive(c))) { return false; }
return true;
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs
index ad6603ad1..bcb29ad85 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs
@@ -231,25 +231,12 @@ namespace Barotrauma
}
else
{
- Limb refLimb = GetLimb(LimbType.Head);
- float refAngle;
- if (refLimb == null)
+ float rotation = MathHelper.WrapAngle(Collider.Rotation);
+ rotation = MathHelper.ToDegrees(rotation);
+ if (rotation < 0.0f)
{
- refAngle = CurrentAnimationParams.TorsoAngleInRadians;
- refLimb = GetLimb(LimbType.Torso);
+ rotation += 360;
}
- else
- {
- refAngle = CurrentAnimationParams.HeadAngleInRadians;
- }
-
- float rotation = refLimb.Rotation;
- if (!float.IsNaN(refAngle)) { rotation -= refAngle * Dir; }
-
- rotation = MathHelper.ToDegrees(MathUtils.WrapAngleTwoPi(rotation));
-
- if (rotation < 0.0f) rotation += 360;
-
if (rotation > 20 && rotation < 160)
{
TargetDir = Direction.Left;
@@ -347,9 +334,23 @@ namespace Barotrauma
target.AnimController.Collider.MoveToPos(mouthPos, (float)(Math.Sin(eatTimer) + dragForce));
}
- //pull the character's mouth to the target character (again with a fluctuating force)
- float pullStrength = (float)(Math.Sin(eatTimer) * Math.Max(Math.Sin(eatTimer * 0.5f), 0.0f));
- mouthLimb.body.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f * pullStrength, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
+ if (InWater)
+ {
+ //pull the character's mouth to the target character (again with a fluctuating force)
+ float pullStrength = (float)(Math.Sin(eatTimer) * Math.Max(Math.Sin(eatTimer * 0.5f), 0.0f));
+ mouthLimb.body.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f * pullStrength, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
+ }
+ else
+ {
+ float force = (float)Math.Sin(eatTimer * 100) * mouthLimb.Mass;
+ mouthLimb.body.ApplyLinearImpulse(Vector2.UnitY * force * 2, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
+ mouthLimb.body.ApplyTorque(-force * 50);
+ }
+ var jaw = GetLimb(LimbType.Jaw);
+ if (jaw != null)
+ {
+ jaw.body.ApplyTorque(-(float)Math.Sin(eatTimer * 150) * jaw.Mass * 25);
+ }
character.ApplyStatusEffects(ActionType.OnEating, deltaTime);
@@ -439,7 +440,7 @@ namespace Barotrauma
if (CurrentSwimParams.RotateTowardsMovement)
{
- Collider.SmoothRotate(movementAngle, CurrentSwimParams.SteerTorque);
+ Collider.SmoothRotate(movementAngle, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
if (TorsoAngle.HasValue)
{
Limb torso = GetLimb(LimbType.Torso);
@@ -491,11 +492,11 @@ namespace Barotrauma
}
if (mainLimb.type == LimbType.Head && HeadAngle.HasValue)
{
- Collider.SmoothRotate(HeadAngle.Value * Dir, CurrentSwimParams.SteerTorque);
+ Collider.SmoothRotate(HeadAngle.Value * Dir, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
}
else if (mainLimb.type == LimbType.Torso && TorsoAngle.HasValue)
{
- Collider.SmoothRotate(TorsoAngle.Value * Dir, CurrentSwimParams.SteerTorque);
+ Collider.SmoothRotate(TorsoAngle.Value * Dir, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
}
if (TorsoAngle.HasValue)
{
@@ -515,7 +516,7 @@ namespace Barotrauma
}
var waveLength = Math.Abs(CurrentSwimParams.WaveLength * RagdollParams.JointScale);
- var waveAmplitude = Math.Abs(CurrentSwimParams.WaveAmplitude);
+ var waveAmplitude = Math.Abs(CurrentSwimParams.WaveAmplitude * character.SpeedMultiplier);
if (waveLength > 0 && waveAmplitude > 0)
{
WalkPos -= transformedMovement.Length() / Math.Abs(waveLength);
@@ -524,6 +525,10 @@ namespace Barotrauma
foreach (var limb in Limbs)
{
+ if (Math.Abs(limb.Params.ConstantTorque) > 0)
+ {
+ limb.body.SmoothRotate(movementAngle + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Params.ConstantTorque, wrapAngle: true);
+ }
switch (limb.type)
{
case LimbType.LeftFoot:
@@ -548,7 +553,7 @@ namespace Barotrauma
if (Limbs[i].SteerForce <= 0.0f) { continue; }
if (!Collider.PhysEnabled) { continue; }
Vector2 pullPos = Limbs[i].PullJointWorldAnchorA;
- Limbs[i].body.ApplyForce(movement * Limbs[i].SteerForce * Limbs[i].Mass, pullPos);
+ Limbs[i].body.ApplyForce(movement * Limbs[i].SteerForce * Limbs[i].Mass * Math.Max(character.SpeedMultiplier, 1), pullPos);
}
Vector2 mainLimbDiff = mainLimb.PullJointWorldAnchorB - mainLimb.SimPosition;
@@ -665,6 +670,10 @@ namespace Barotrauma
foreach (Limb limb in Limbs)
{
+ if (Math.Abs(limb.Params.ConstantTorque) > 0)
+ {
+ limb.body.SmoothRotate(movementAngle + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Params.ConstantTorque, wrapAngle: true);
+ }
switch (limb.type)
{
case LimbType.LeftFoot:
@@ -729,7 +738,10 @@ namespace Barotrauma
break;
case LimbType.LeftLeg:
case LimbType.RightLeg:
- if (Math.Abs(CurrentGroundedParams.LegTorque) > 0.001f) limb.body.ApplyTorque(limb.Mass * CurrentGroundedParams.LegTorque * Dir);
+ if (Math.Abs(CurrentGroundedParams.LegTorque) > 0)
+ {
+ limb.body.ApplyTorque(limb.Mass * CurrentGroundedParams.LegTorque * Dir);
+ }
break;
}
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs
index cf3bc553d..296aff27d 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs
@@ -900,7 +900,7 @@ namespace Barotrauma
surfaceLimiter = ConvertUnits.ToDisplayUnits(Collider.SimPosition.Y + 0.4f) - surfacePos;
surfaceLimiter = Math.Max(1.0f, surfaceLimiter);
- if (surfaceLimiter > 50.0f) return;
+ if (surfaceLimiter > 50.0f) { return; }
}
Limb leftHand = GetLimb(LimbType.LeftHand);
@@ -928,8 +928,7 @@ namespace Barotrauma
if (!aiming)
{
float newRotation = MathUtils.VectorToAngle(TargetMovement) - MathHelper.PiOver2;
- Collider.SmoothRotate(newRotation, 5.0f);
- //torso.body.SmoothRotate(newRotation);
+ Collider.SmoothRotate(newRotation, 5.0f * character.SpeedMultiplier);
}
}
else
@@ -942,13 +941,13 @@ namespace Barotrauma
TargetMovement = new Vector2(0.0f, -0.1f);
float newRotation = MathUtils.VectorToAngle(diff);
- Collider.SmoothRotate(newRotation, 5.0f);
+ Collider.SmoothRotate(newRotation, 5.0f * character.SpeedMultiplier);
}
}
torso.body.MoveToPos(Collider.SimPosition + new Vector2((float)Math.Sin(-Collider.Rotation), (float)Math.Cos(-Collider.Rotation)) * 0.4f, 5.0f);
- if (TargetMovement == Vector2.Zero) return;
+ if (TargetMovement == Vector2.Zero) { return; }
movement = MathUtils.SmoothStep(movement, TargetMovement, 0.3f);
@@ -1007,7 +1006,7 @@ namespace Barotrauma
var waist = GetLimb(LimbType.Waist);
footPos = waist == null ? Vector2.Zero : waist.SimPosition - new Vector2((float)Math.Sin(-Collider.Rotation), (float)Math.Cos(-Collider.Rotation)) * (upperLegLength + lowerLegLength);
- Vector2 transformedFootPos = new Vector2((float)Math.Sin(legCyclePos / CurrentSwimParams.LegCycleLength) * CurrentSwimParams.LegMoveAmount * CurrentAnimationParams.CycleSpeed, 0.0f);
+ Vector2 transformedFootPos = new Vector2((float)Math.Sin(legCyclePos / CurrentSwimParams.LegCycleLength / character.SpeedMultiplier) * CurrentSwimParams.LegMoveAmount, 0.0f);
transformedFootPos = Vector2.Transform(transformedFootPos, Matrix.CreateRotationZ(Collider.Rotation));
if (rightFoot != null && !rightFoot.Disabled)
@@ -1061,7 +1060,7 @@ namespace Barotrauma
rightHandPos.X = (Dir == 1.0f) ? Math.Max(0.3f, rightHandPos.X) : Math.Min(-0.3f, rightHandPos.X);
rightHandPos = Vector2.Transform(rightHandPos, rotationMatrix);
- HandIK(rightHand, handPos + rightHandPos, CurrentSwimParams.HandMoveStrength);
+ HandIK(rightHand, handPos + rightHandPos, CurrentSwimParams.HandMoveStrength * character.SpeedMultiplier);
}
if (leftHand != null && !leftHand.Disabled)
@@ -1070,7 +1069,7 @@ namespace Barotrauma
leftHandPos.X = (Dir == 1.0f) ? Math.Max(0.3f, leftHandPos.X) : Math.Min(-0.3f, leftHandPos.X);
leftHandPos = Vector2.Transform(leftHandPos, rotationMatrix);
- HandIK(leftHand, handPos + leftHandPos, CurrentSwimParams.HandMoveStrength);
+ HandIK(leftHand, handPos + leftHandPos, CurrentSwimParams.HandMoveStrength * character.SpeedMultiplier);
}
}
@@ -1929,7 +1928,7 @@ namespace Barotrauma
float sqrDist = Vector2.DistanceSquared(character.WorldPosition, handWorldPos);
if (sqrDist > MathUtils.Pow(ConvertUnits.ToDisplayUnits(upperArmLength + forearmLength), 2))
{
- TargetMovement = Vector2.Normalize(handWorldPos - character.WorldPosition) * GetCurrentSpeed(false);
+ TargetMovement = Vector2.Normalize(handWorldPos - character.WorldPosition) * GetCurrentSpeed(false) * Math.Max(character.SpeedMultiplier, 1);
}
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs
index 6490858b9..519343441 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs
@@ -383,7 +383,7 @@ namespace Barotrauma
}
}
- if (character.IsHusk)
+ if (character.IsHusk && character.Params.UseHuskAppendage)
{
var characterPrefab = CharacterPrefab.FindByFilePath(character.ConfigPath);
if (characterPrefab?.XDocument != null)
@@ -1626,7 +1626,7 @@ namespace Barotrauma
float sin = (float)Math.Sin(mouthLimb.Rotation);
Vector2 bodySize = mouthLimb.body.GetSize();
Vector2 offset = new Vector2(mouthLimb.MouthPos.X * bodySize.X / 2, mouthLimb.MouthPos.Y * bodySize.Y / 2);
- return mouthLimb.SimPosition + new Vector2(offset.X * cos - offset.Y * sin, offset.X * sin + offset.Y * cos) * RagdollParams.LimbScale;
+ return mouthLimb.SimPosition + new Vector2(offset.X * cos - offset.Y * sin, offset.X * sin + offset.Y * cos) * mouthLimb.Scale * RagdollParams.LimbScale;
}
public Vector2 GetColliderBottom()
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs
index a3b3a9ea2..cb323fc2a 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs
@@ -13,11 +13,12 @@ namespace Barotrauma
public enum AttackContext
{
- NotDefined,
+ Any,
Water,
Ground,
Inside,
- Outside
+ Outside,
+ NotDefined
}
public enum AttackTarget
@@ -72,13 +73,13 @@ namespace Barotrauma
partial class Attack : ISerializableEntity
{
- [Serialize(AttackContext.NotDefined, true, description: "The attack will be used only in this context."), Editable]
+ [Serialize(AttackContext.Any, true, description: "The attack will be used only in this context."), Editable]
public AttackContext Context { get; private set; }
[Serialize(AttackTarget.Any, true, description: "Does the attack target only specific targets?"), Editable]
public AttackTarget TargetType { get; private set; }
- [Serialize(LimbType.None, true, description: "If not defined or set to none, the closest limb is used (default)."), Editable]
+ [Serialize(LimbType.None, true, description: "To which limb is the attack aimed at? If not defined or set to none, the closest limb is used (default)."), Editable]
public LimbType TargetLimbType { get; private set; }
[Serialize(HitDetection.Distance, true, description: "Collision detection is more accurate, but it only affects targets that are in contact with the limb."), Editable]
@@ -87,9 +88,15 @@ namespace Barotrauma
[Serialize(AIBehaviorAfterAttack.FallBack, true, description: "The preferred AI behavior after the attack."), Editable]
public AIBehaviorAfterAttack AfterAttack { get; set; }
- [Serialize(false, true, description: "Should the AI try to reverse when aiming with this attack?"), Editable]
+ [Serialize(0f, true, description: "A delay before reacting after performing an attack."), Editable]
+ public float AfterAttackDelay { get; set; }
+
+ [Serialize(false, true, description: "Should the AI try to turn around when aiming with this attack?"), Editable]
public bool Reverse { get; private set; }
+ [Serialize(false, true, description: "Should the AI try to steer away from the target when aiming with this attack? Best combined with PassiveAggressive behavior."), Editable]
+ public bool Retreat { get; private set; }
+
[Serialize(0.0f, true, description: "The min distance from the attack limb to the target before the AI tries to attack."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 2000.0f)]
public float Range { get; set; }
@@ -147,7 +154,19 @@ namespace Barotrauma
[Serialize(0.0f, true, description: "Applied to the attacking limb (or limbs defined using ApplyForceOnLimbs). The direction of the force is towards the target that's being attacked."), Editable(MinValueFloat = -1000.0f, MaxValueFloat = 1000.0f)]
public float Force { get; private set; }
- [Serialize(0.0f, true, description: "Applied to the attacking limb (or limbs defined using ApplyForceOnLimbs)"), Editable(MinValueFloat = -1000.0f, MaxValueFloat = 1000.0f)]
+ [Serialize("0.0, 0.0", true, description: "Applied to the main limb. In world space coordinates(i.e. 0, 1 pushes the character upwards a bit). The attacker's facing direction is taken into account."), Editable]
+ public Vector2 RootForceWorldStart { get; private set; }
+
+ [Serialize("0.0, 0.0", true, description: "Applied to the main limb. In world space coordinates(i.e. 0, 1 pushes the character upwards a bit). The attacker's facing direction is taken into account."), Editable]
+ public Vector2 RootForceWorldMiddle { get; private set; }
+
+ [Serialize("0.0, 0.0", true, description: "Applied to the main limb. In world space coordinates(i.e. 0, 1 pushes the character upwards a bit). The attacker's facing direction is taken into account."), Editable]
+ public Vector2 RootForceWorldEnd { get; private set; }
+
+ [Serialize(TransitionMode.Linear, true, description:""), Editable]
+ public TransitionMode RootTransitionEasing { get; private set; }
+
+ [Serialize(0.0f, true, description: "Applied to the attacking limb (or limbs defined using ApplyForceOnLimbs)"), Editable(MinValueFloat = -10000.0f, MaxValueFloat = 10000.0f)]
public float Torque { get; private set; }
[Serialize(false, true), Editable]
@@ -156,13 +175,13 @@ namespace Barotrauma
[Serialize(0.0f, true, description: "Applied to the target the attack hits. The direction of the impulse is from this limb towards the target (use negative values to pull the target closer)."), Editable(MinValueFloat = -1000.0f, MaxValueFloat = 1000.0f)]
public float TargetImpulse { get; private set; }
- [Serialize("0.0, 0.0", true, description: "Applied to the target, in world space coordinates(i.e. 0, -1 pushes the target downwards)."), Editable]
+ [Serialize("0.0, 0.0", true, description: "Applied to the target, in world space coordinates(i.e. 0, -1 pushes the target downwards). The attacker's facing direction is taken into account."), Editable]
public Vector2 TargetImpulseWorld { get; private set; }
[Serialize(0.0f, true, description: "Applied to the target the attack hits. The direction of the force is from this limb towards the target (use negative values to pull the target closer)."), Editable(-1000.0f, 1000.0f)]
public float TargetForce { get; private set; }
- [Serialize("0.0, 0.0", true, description: "Applied to the target, in world space coordinates(i.e. 0, -1 pushes the target downwards)."), Editable]
+ [Serialize("0.0, 0.0", true, description: "Applied to the target, in world space coordinates(i.e. 0, -1 pushes the target downwards). The attacker's facing direction is taken into account."), Editable]
public Vector2 TargetForceWorld { get; private set; }
[Serialize(0.0f, true, description: "How likely the attack causes target limbs to be severed when the target is dead."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
@@ -419,7 +438,10 @@ namespace Barotrauma
public AttackResult DoDamageToLimb(Character attacker, Limb targetLimb, Vector2 worldPosition, float deltaTime, bool playSound = true)
{
- if (targetLimb == null) return new AttackResult();
+ if (targetLimb == null)
+ {
+ return new AttackResult();
+ }
if (OnlyHumans)
{
@@ -461,6 +483,7 @@ namespace Barotrauma
public float AttackTimer { get; private set; }
public float CoolDownTimer { get; set; }
+ public float CurrentRandomCoolDown { get; private set; }
public float SecondaryCoolDownTimer { get; set; }
public bool IsRunning { get; private set; }
@@ -492,7 +515,8 @@ namespace Barotrauma
public void SetCoolDown()
{
float randomFraction = CoolDown * CoolDownRandomFactor;
- CoolDownTimer = CoolDown + MathHelper.Lerp(-randomFraction, randomFraction, Rand.Value(Rand.RandSync.Server));
+ CurrentRandomCoolDown = MathHelper.Lerp(-randomFraction, randomFraction, Rand.Value(Rand.RandSync.Server));
+ CoolDownTimer = CoolDown + CurrentRandomCoolDown;
randomFraction = SecondaryCoolDown * CoolDownRandomFactor;
SecondaryCoolDownTimer = SecondaryCoolDown + MathHelper.Lerp(-randomFraction, randomFraction, Rand.Value(Rand.RandSync.Server));
}
@@ -501,11 +525,12 @@ namespace Barotrauma
{
CoolDownTimer = 0;
SecondaryCoolDownTimer = 0;
+ CurrentRandomCoolDown = 0;
}
partial void DamageParticles(float deltaTime, Vector2 worldPosition);
- public bool IsValidContext(AttackContext context) => Context == context || Context == AttackContext.NotDefined;
+ public bool IsValidContext(AttackContext context) => Context == context || Context == AttackContext.Any || Context == AttackContext.NotDefined;
public bool IsValidContext(IEnumerable contexts)
{
@@ -559,5 +584,11 @@ namespace Barotrauma
return true;
}
}
+
+ public Vector2 CalculateAttackPhase(TransitionMode easing = TransitionMode.Linear)
+ {
+ float t = AttackTimer / Duration;
+ return MathUtils.Bezier(RootForceWorldStart, RootForceWorldMiddle, RootForceWorldEnd, ToolBox.GetEasing(easing, t));
+ }
}
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs
index c88604bcc..87dff3a0a 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs
@@ -57,6 +57,10 @@ namespace Barotrauma
public Hull CurrentHull = null;
public bool IsRemotePlayer;
+
+ public bool IsPlayer => Controlled == this || IsRemotePlayer;
+ public bool IsBot => !IsPlayer && AIController is HumanAIController humanAI && humanAI.Enabled;
+
public readonly Dictionary Properties;
public Dictionary SerializableProperties
{
@@ -128,6 +132,12 @@ namespace Barotrauma
set => Params.Noise = value;
}
+ public float Visibility
+ {
+ get => Params.Visibility;
+ set => Params.Visibility = value;
+ }
+
public bool IsTraitor;
public string TraitorCurrentObjective = "";
public bool IsHuman => SpeciesName.Equals(CharacterPrefab.HumanSpeciesName, StringComparison.OrdinalIgnoreCase);
@@ -201,7 +211,7 @@ namespace Barotrauma
{
displayName = TextManager.Get($"Character.{SpeciesName}", returnNull: true);
}
- return displayName ?? Name;
+ return string.IsNullOrWhiteSpace(displayName) ? Name : displayName;
}
}
@@ -770,10 +780,14 @@ namespace Barotrauma
{
nonHuskedSpeciesName = AfflictionHusk.GetNonHuskedSpeciesName(speciesName, matchingAffliction);
}
- ragdollParams = IsHumanoid ? RagdollParams.GetDefaultRagdollParams(nonHuskedSpeciesName) : RagdollParams.GetDefaultRagdollParams(nonHuskedSpeciesName) as RagdollParams;
- if (info == null)
+ if (ragdollParams == null)
{
- info = new CharacterInfo(nonHuskedSpeciesName, ragdollParams.FileName);
+ string name = Params.UseHuskAppendage ? nonHuskedSpeciesName : speciesName;
+ ragdollParams = IsHumanoid ? RagdollParams.GetDefaultRagdollParams(name) : RagdollParams.GetDefaultRagdollParams(name) as RagdollParams;
+ }
+ if (Params.HasInfo && info == null)
+ {
+ info = new CharacterInfo(nonHuskedSpeciesName);
}
}
@@ -844,7 +858,7 @@ namespace Barotrauma
Info.HairElement?.Elements("sprite").ForEach(s => head.OtherWearables.Add(new WearableSprite(s, WearableType.Hair)));
#if CLIENT
- head.LoadHuskSprite();
+ head.EnableHuskSprite = Params.Husk;
head.LoadHerpesSprite();
head.UpdateWearableTypesToHide();
#endif
@@ -1010,59 +1024,55 @@ namespace Barotrauma
}
else
{
- if (IsKeyDown(InputType.Left)) targetMovement.X -= 1.0f;
- if (IsKeyDown(InputType.Right)) targetMovement.X += 1.0f;
- if (IsKeyDown(InputType.Up)) targetMovement.Y += 1.0f;
- if (IsKeyDown(InputType.Down)) targetMovement.Y -= 1.0f;
+ if (IsKeyDown(InputType.Left)) { targetMovement.X -= 1.0f; }
+ if (IsKeyDown(InputType.Right)) { targetMovement.X += 1.0f; }
+ if (IsKeyDown(InputType.Up)) { targetMovement.Y += 1.0f; }
+ if (IsKeyDown(InputType.Down)) { targetMovement.Y -= 1.0f; }
}
+ bool run = false;
+ if ((IsKeyDown(InputType.Run) && AnimController.ForceSelectAnimationType == AnimationType.NotDefined) || ForceRun)
+ {
+ run = CanRun;
+ }
+ return ApplyMovementLimits(targetMovement, AnimController.GetCurrentSpeed(run));
+ }
+
+ //can't run if
+ // - dragging someone
+ // - crouching
+ // - moving backwards
+ public bool CanRun => (SelectedCharacter == null || !SelectedCharacter.CanBeDragged) &&
+ (!(AnimController is HumanoidAnimController) || !((HumanoidAnimController)AnimController).Crouching) &&
+ !AnimController.IsMovingBackwards;
+
+ public Vector2 ApplyMovementLimits(Vector2 targetMovement, float currentSpeed)
+ {
//the vertical component is only used for falling through platforms and climbing ladders when not in water,
//so the movement can't be normalized or the Character would walk slower when pressing down/up
if (AnimController.InWater)
{
float length = targetMovement.Length();
- if (length > 0.0f) targetMovement /= length;
+ if (length > 0.0f)
+ {
+ targetMovement /= length;
+ }
}
-
- bool run = false;
- if ((IsKeyDown(InputType.Run) && AnimController.ForceSelectAnimationType == AnimationType.NotDefined) || ForceRun)
- {
- //can't run if
- // - dragging someone
- // - crouching
- // - moving backwards
- run = (SelectedCharacter == null || !SelectedCharacter.CanBeDragged) &&
- (!(AnimController is HumanoidAnimController) || !((HumanoidAnimController)AnimController).Crouching) &&
- !AnimController.IsMovingBackwards;
- }
-
- float currentSpeed = AnimController.GetCurrentSpeed(run);
targetMovement *= currentSpeed;
float maxSpeed = ApplyTemporarySpeedLimits(currentSpeed);
targetMovement.X = MathHelper.Clamp(targetMovement.X, -maxSpeed, maxSpeed);
targetMovement.Y = MathHelper.Clamp(targetMovement.Y, -maxSpeed, maxSpeed);
-
- //apply speed multiplier if
- // a. it's boosting the movement speed and the character is trying to move fast (= running)
- // b. it's a debuff that decreases movement speed
- float speedMultiplier = SpeedMultiplier;
- if (run || speedMultiplier <= 0.0f) targetMovement *= speedMultiplier;
-
- ResetSpeedMultiplier(); // Reset, items will set the value before the next update
-
+ SpeedMultiplier = greatestPositiveSpeedMultiplier - (1f - greatestNegativeSpeedMultiplier);
+ targetMovement *= SpeedMultiplier;
+ // Reset, status effects will set the value before the next update
+ ResetSpeedMultiplier();
return targetMovement;
}
///
/// Can be used to modify the character's speed via StatusEffects
///
- public float SpeedMultiplier
- {
- get
- {
- return greatestPositiveSpeedMultiplier - (1f - greatestNegativeSpeedMultiplier);
- }
- }
+ public float SpeedMultiplier { get; private set; }
public void StackSpeedMultiplier(float val)
{
@@ -1915,7 +1925,7 @@ namespace Barotrauma
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
//disable AI characters that are far away from all clients and the host's character and not controlled by anyone
- if (c == Controlled || c.IsRemotePlayer)
+ if (c.IsPlayer || c.IsBot)
{
c.Enabled = true;
}
@@ -2012,8 +2022,8 @@ namespace Barotrauma
HideFace = false;
- UpdateSightRange();
- UpdateSoundRange();
+ UpdateSightRange(deltaTime);
+ UpdateSoundRange(deltaTime);
if (IsDead) { return; }
@@ -2290,18 +2300,39 @@ namespace Barotrauma
}
}
- private void UpdateSightRange()
+ private readonly float maxAIRange = 10000;
+ private readonly float aiTargetChangeSpeed = 5;
+
+ private void UpdateSightRange(float deltaTime)
{
if (aiTarget == null) { return; }
- float range = (float)Math.Sqrt(Mass) * 250 + AnimController.Collider.LinearVelocity.Length() * 500;
- aiTarget.SightRange = MathHelper.Clamp(range, 0, 10000);
+ float minRange = Math.Clamp((float)Math.Sqrt(Mass) * Visibility, 250, 1000);
+ float massFactor = (float)Math.Sqrt(Mass / 20);
+ float targetRange = Math.Min(minRange + massFactor * AnimController.Collider.LinearVelocity.Length() * 2 * Visibility, maxAIRange);
+ float newRange = MathHelper.SmoothStep(aiTarget.SightRange, targetRange, deltaTime * aiTargetChangeSpeed);
+ if (!float.IsNaN(newRange))
+ {
+ aiTarget.SightRange = newRange;
+ }
}
- private void UpdateSoundRange()
+ private void UpdateSoundRange(float deltaTime)
{
if (aiTarget == null) { return; }
- float range = ((float)Math.Sqrt(Mass) / 3) * (AnimController.TargetMovement.Length() * 2) * Noise;
- aiTarget.SoundRange = MathHelper.Clamp(range, 0, 10000);
+ if (IsDead)
+ {
+ aiTarget.SoundRange = 0;
+ }
+ else
+ {
+ float massFactor = (float)Math.Sqrt(Mass / 10);
+ float targetRange = Math.Min(massFactor * AnimController.Collider.LinearVelocity.Length() * 2 * Noise, maxAIRange);
+ float newRange = MathHelper.SmoothStep(aiTarget.SoundRange, targetRange, deltaTime * aiTargetChangeSpeed);
+ if (!float.IsNaN(newRange))
+ {
+ aiTarget.SoundRange = newRange;
+ }
+ }
}
public bool CanHearCharacter(Character speaker)
@@ -2316,21 +2347,16 @@ namespace Barotrauma
public void SetOrder(Order order, string orderOption, Character orderGiver, bool speak = true)
{
- if (orderGiver != null)
- {
- //set the character order only if the character is close enough to hear the message
- if (!CanHearCharacter(orderGiver)) { return; }
- }
+ //set the character order only if the character is close enough to hear the message
+ if (orderGiver != null && !CanHearCharacter(orderGiver)) { return; }
if (AIController is HumanAIController humanAI)
{
humanAI.SetOrder(order, orderOption, orderGiver, speak);
}
+
#if CLIENT
- else
- {
- GameMain.GameSession?.CrewManager?.DisplayCharacterOrder(this, order, orderOption);
- }
+ GameMain.GameSession?.CrewManager?.DisplayCharacterOrder(this, order, orderOption);
#endif
CurrentOrder = order;
@@ -2469,13 +2495,17 @@ namespace Barotrauma
DamageLimb(worldPosition, targetLimb, attack.Afflictions.Keys, attack.Stun, playSound, attackImpulse, attacker);
if (limbHit == null) { return new AttackResult(); }
-
- limbHit.body?.ApplyLinearImpulse(attack.TargetImpulseWorld + attack.TargetForceWorld * deltaTime, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
+ Vector2 forceWorld = attack.TargetImpulseWorld + attack.TargetForceWorld;
+ if (attacker != null)
+ {
+ forceWorld.X *= attacker.AnimController.Dir;
+ }
+ limbHit.body?.ApplyLinearImpulse(forceWorld * deltaTime, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
var mainLimb = limbHit.character.AnimController.MainLimb;
if (limbHit != mainLimb)
{
// Always add force to mainlimb
- mainLimb.body?.ApplyLinearImpulse(attack.TargetImpulseWorld + attack.TargetForceWorld * deltaTime, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
+ mainLimb.body?.ApplyLinearImpulse(forceWorld * deltaTime, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
}
#if SERVER
if (attacker is Character attackingCharacter && attackingCharacter.AIController == null)
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs
index 23dc3f22d..d8f268ea5 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs
@@ -14,8 +14,13 @@ namespace Barotrauma
public Dictionary SerializableProperties { get; set; }
+ protected float _strength;
[Serialize(0f, true), Editable]
- public float Strength { get; set; }
+ public virtual float Strength
+ {
+ get { return _strength; }
+ set { _strength = value; }
+ }
[Serialize("", true), Editable]
public string Identifier { get; private set; }
@@ -38,7 +43,7 @@ namespace Barotrauma
public Affliction(AfflictionPrefab prefab, float strength)
{
Prefab = prefab;
- Strength = strength;
+ _strength = strength;
Identifier = prefab?.Identifier;
}
@@ -173,11 +178,11 @@ namespace Barotrauma
if (currentEffect.StrengthChange < 0) // Reduce diminishing of buffs if boosted
{
- Strength += currentEffect.StrengthChange * deltaTime * StrengthDiminishMultiplier;
+ _strength += currentEffect.StrengthChange * deltaTime * StrengthDiminishMultiplier;
}
else // Reduce strengthening of afflictions if resistant
{
- Strength += currentEffect.StrengthChange * deltaTime * (1f - characterHealth.GetResistance(Prefab.Identifier));
+ _strength += currentEffect.StrengthChange * deltaTime * (1f - characterHealth.GetResistance(Prefab.Identifier));
}
foreach (StatusEffect statusEffect in currentEffect.StatusEffects)
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs
index 445b65755..cc0509492 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs
@@ -1,7 +1,7 @@
using System.Collections.Generic;
-using System.IO;
using System.Linq;
using System.Xml.Linq;
+using System;
namespace Barotrauma
{
@@ -9,7 +9,7 @@ namespace Barotrauma
{
public enum InfectionState
{
- Dormant, Transition, Active
+ Initial, Dormant, Transition, Active, Final
}
private bool subscribedToDeathEvent;
@@ -17,154 +17,157 @@ namespace Barotrauma
private InfectionState state;
private List huskAppendage;
-
+
+ private Character character;
+
+ private readonly List huskInfection = new List();
+
+ [Serialize(0f, true), Editable]
+ public override float Strength
+ {
+ get { return _strength; }
+ set
+ {
+ // Don't allow to set the strength too high (from outside) to avoid rapid transformation into husk when taking lots of damage from husks.
+ // If the strength is more than the value, this will effectively reset the current strength to the max. That's why we use two steps.
+ float max = _strength > ActiveThreshold ? ActiveThreshold + 1 : DormantThreshold - 1;
+ _strength = Math.Clamp(value, 0, max);
+ }
+ }
+
public InfectionState State
{
get { return state; }
+ private set
+ {
+ if (state == value) { return; }
+ state = value;
+ if (character != null && character == Character.Controlled)
+ {
+ UpdateMessages();
+ }
+ }
}
- public AfflictionHusk(AfflictionPrefab prefab, float strength) :
- base(prefab, strength)
- {
- }
+ private float DormantThreshold => Prefab.MaxStrength * 0.5f;
+ private float ActiveThreshold => Prefab.MaxStrength * 0.75f;
+
+ public AfflictionHusk(AfflictionPrefab prefab, float strength) : base(prefab, strength) { }
public override void Update(CharacterHealth characterHealth, Limb targetLimb, float deltaTime)
{
- float prevStrength = Strength;
base.Update(characterHealth, targetLimb, deltaTime);
-
+ character = characterHealth.Character;
+ if (character == null) { return; }
if (!subscribedToDeathEvent)
{
- characterHealth.Character.OnDeath += CharacterDead;
+ character.OnDeath += CharacterDead;
subscribedToDeathEvent = true;
}
-
- if (characterHealth.Character == Character.Controlled) UpdateMessages(prevStrength, characterHealth.Character);
- if (Strength < Prefab.MaxStrength * 0.5f)
+ if (Strength < DormantThreshold)
{
- UpdateDormantState(deltaTime, characterHealth.Character);
+ DeactivateHusk();
+ State = InfectionState.Dormant;
+ }
+ else if (Strength < ActiveThreshold)
+ {
+ DeactivateHusk();
+ character.SpeechImpediment = 100;
+ State = InfectionState.Transition;
}
else if (Strength < Prefab.MaxStrength)
{
- characterHealth.Character.SpeechImpediment = 100.0f;
- UpdateTransitionState(deltaTime, characterHealth.Character);
+ if (State != InfectionState.Active)
+ {
+ character.SetStun(Rand.Range(2, 4, Rand.RandSync.Server));
+ }
+ State = InfectionState.Active;
+ ActivateHusk();
}
else
{
- characterHealth.Character.SpeechImpediment = 100.0f;
- UpdateActiveState(deltaTime, characterHealth.Character);
+ State = InfectionState.Final;
+ ActivateHusk();
+ ApplyDamage(deltaTime, applyForce: true);
+ character.SetStun(1);
}
}
- partial void UpdateMessages(float prevStrength, Character character);
+ partial void UpdateMessages();
- private void UpdateDormantState(float deltaTime, Character character)
+ private void ApplyDamage(float deltaTime, bool applyForce)
{
- if (state != InfectionState.Dormant)
- {
- DeactivateHusk(character);
- }
-
- state = InfectionState.Dormant;
- }
-
- private void UpdateTransitionState(float deltaTime, Character character)
- {
- if (state != InfectionState.Transition)
- {
- DeactivateHusk(character);
- }
-
- state = InfectionState.Transition;
- }
-
- private void UpdateActiveState(float deltaTime, Character character)
- {
- if (state != InfectionState.Active)
- {
- ActivateHusk(character);
- state = InfectionState.Active;
- }
-
foreach (Limb limb in character.AnimController.Limbs)
{
+ float random = Rand.Value(Rand.RandSync.Server);
+ huskInfection.Clear();
+ huskInfection.Add(AfflictionPrefab.InternalDamage.Instantiate(random * deltaTime / character.AnimController.Limbs.Length));
character.LastDamageSource = null;
- character.DamageLimb(
- limb.WorldPosition, limb,
- new List() { AfflictionPrefab.InternalDamage.Instantiate(0.5f * deltaTime / character.AnimController.Limbs.Length) },
- 0.0f, false, 0.0f);
+ float force = applyForce ? random * 0.1f * limb.Mass : 0;
+ character.DamageLimb(limb.WorldPosition, limb, huskInfection, 0, false, force);
}
}
- public void ActivateHusk(Character character)
+ public void ActivateHusk()
{
- if (huskAppendage == null)
+ if (huskAppendage == null && character.Params.UseHuskAppendage)
{
huskAppendage = AttachHuskAppendage(character, Prefab.Identifier);
- if (huskAppendage != null)
- {
- character.NeedsAir = false;
- character.SetStun(0.5f);
- }
-#if CLIENT
- character.AnimController.GetLimb(LimbType.Head).EnableHuskSprite = true;
-#endif
}
+ character.NeedsAir = false;
+ character.SpeechImpediment = 100;
}
- private void DeactivateHusk(Character character)
+ private void DeactivateHusk()
{
character.NeedsAir = character.Params.MainElement.GetAttributeBool("needsair", false);
if (huskAppendage != null)
{
huskAppendage.ForEach(l => character.AnimController.RemoveLimb(l));
huskAppendage = null;
-#if CLIENT
- character.AnimController.GetLimb(LimbType.Head).EnableHuskSprite = false;
-#endif
}
}
- public void Remove(Character character)
+ public void Remove()
{
- DeactivateHusk(character);
- if (character != null) character.OnDeath -= CharacterDead;
+ if (character == null) { return; }
+ DeactivateHusk();
+ character.OnDeath -= CharacterDead;
subscribedToDeathEvent = false;
}
private void CharacterDead(Character character, CauseOfDeath causeOfDeath)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
- if (Strength < Prefab.MaxStrength * 0.5f || character.Removed) { return; }
+ if (Strength < ActiveThreshold || character.Removed) { return; }
//don't turn the character into a husk if any of its limbs are severed
if (character.AnimController?.LimbJoints != null)
{
foreach (var limbJoint in character.AnimController.LimbJoints)
{
- if (limbJoint.IsSevered) return;
+ if (limbJoint.IsSevered) { return; }
}
}
//create the AI husk in a coroutine to ensure that we don't modify the character list while enumerating it
- CoroutineManager.StartCoroutine(CreateAIHusk(character));
+ CoroutineManager.StartCoroutine(CreateAIHusk());
}
- private IEnumerable CreateAIHusk(Character character)
+ private IEnumerable CreateAIHusk()
{
character.Enabled = false;
Entity.Spawner.AddToRemoveQueue(character);
- string speciesName = GetHuskedSpeciesName(character.SpeciesName, Prefab as AfflictionPrefabHusk);
- CharacterPrefab prefab = CharacterPrefab.FindBySpeciesName(speciesName);
+ string huskedSpeciesName = GetHuskedSpeciesName(character.SpeciesName, Prefab as AfflictionPrefabHusk);
+ CharacterPrefab prefab = CharacterPrefab.FindBySpeciesName(huskedSpeciesName);
if (prefab == null)
{
DebugConsole.ThrowError("Failed to turn character \"" + character.Name + "\" into a husk - husk config file not found.");
yield return CoroutineStatus.Success;
}
-
- var husk = Character.Create(speciesName, character.WorldPosition, character.Info.Name, character.Info, isRemotePlayer: false, hasAi: true, ragdoll: character.AnimController.RagdollParams);
+ var husk = Character.Create(huskedSpeciesName, character.WorldPosition, ToolBox.RandomSeed(8), character.Info, isRemotePlayer: false, hasAi: true);
foreach (Limb limb in husk.AnimController.Limbs)
{
@@ -197,6 +200,11 @@ namespace Barotrauma
husk.Inventory.TryPutItem(character.Inventory.Items[i], i, true, false, null);
}
+ husk.SetStun(5);
+ yield return new WaitForSeconds(5, false);
+#if CLIENT
+ husk.PlaySound(CharacterSound.SoundType.Idle);
+#endif
yield return CoroutineStatus.Success;
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs
index 1b0dd640d..3d0ac4788 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs
@@ -791,8 +791,7 @@ namespace Barotrauma
///
/// A dictionary where the key is the identifier of the item and the value the suitability
/// If true, the suitability values are normalized between 0 and 1. If not, they're arbitrary values defined in the medical item XML, where negative values are unsuitable, and positive ones suitable.
- /// Amount of randomization to apply to the values (0 = the values are accurate, 1 = the values are completely random)
-
+ /// Amount of randomization to apply to the values (0 = the values are accurate, 1 = the values are completely random)
public void GetSuitableTreatments(Dictionary treatmentSuitability, bool normalize, float randomization = 0.0f)
{
//key = item identifier
@@ -873,5 +872,11 @@ namespace Barotrauma
}
partial void RemoveProjSpecific();
+
+ ///
+ /// Automatically filters out buffs.
+ ///
+ public static IEnumerable SortAfflictionsBySeverity(IEnumerable afflictions) =>
+ afflictions.Where(a => !a.Prefab.IsBuff).OrderByDescending(a => a.DamagePerSecond).ThenByDescending(a => a.Strength);
}
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs
index b6ffe5ed0..fa530a09e 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs
@@ -241,7 +241,7 @@ namespace Barotrauma
}
- public static JobPrefab Random(Rand.RandSync sync = Rand.RandSync.Unsynced) => Prefabs.GetRandom(sync);
+ public static JobPrefab Random(Rand.RandSync sync = Rand.RandSync.Unsynced) => Prefabs.GetRandom(p => p.Identifier != "watchman", sync);
public static void LoadAll(IEnumerable files)
{
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs
index 1feb334d1..b990d25d7 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs
@@ -17,7 +17,7 @@ namespace Barotrauma
public enum LimbType
{
None, LeftHand, RightHand, LeftArm, RightArm, LeftForearm, RightForearm,
- LeftLeg, RightLeg, LeftFoot, RightFoot, Head, Torso, Tail, Legs, RightThigh, LeftThigh, Waist
+ LeftLeg, RightLeg, LeftFoot, RightFoot, Head, Torso, Tail, Legs, RightThigh, LeftThigh, Waist, Jaw
};
partial class LimbJoint : RevoluteJoint
@@ -28,6 +28,8 @@ namespace Barotrauma
public readonly Ragdoll ragdoll;
public readonly Limb LimbA, LimbB;
+ public float Scale => Params.Scale * ragdoll.RagdollParams.JointScale;
+
public LimbJoint(Limb limbA, Limb limbB, JointParams jointParams, Ragdoll ragdoll) : this(limbA, limbB, Vector2.One, Vector2.One)
{
Params = jointParams;
@@ -59,15 +61,15 @@ namespace Barotrauma
}
if (ragdoll.IsFlipped)
{
- LocalAnchorA = ConvertUnits.ToSimUnits(new Vector2(-Params.Limb1Anchor.X, Params.Limb1Anchor.Y) * Params.Ragdoll.JointScale);
- LocalAnchorB = ConvertUnits.ToSimUnits(new Vector2(-Params.Limb2Anchor.X, Params.Limb2Anchor.Y) * Params.Ragdoll.JointScale);
+ LocalAnchorA = ConvertUnits.ToSimUnits(new Vector2(-Params.Limb1Anchor.X, Params.Limb1Anchor.Y) * Scale);
+ LocalAnchorB = ConvertUnits.ToSimUnits(new Vector2(-Params.Limb2Anchor.X, Params.Limb2Anchor.Y) * Scale);
UpperLimit = MathHelper.ToRadians(-Params.LowerLimit);
LowerLimit = MathHelper.ToRadians(-Params.UpperLimit);
}
else
{
- LocalAnchorA = ConvertUnits.ToSimUnits(Params.Limb1Anchor * Params.Ragdoll.JointScale);
- LocalAnchorB = ConvertUnits.ToSimUnits(Params.Limb2Anchor * Params.Ragdoll.JointScale);
+ LocalAnchorA = ConvertUnits.ToSimUnits(Params.Limb1Anchor * Scale);
+ LocalAnchorB = ConvertUnits.ToSimUnits(Params.Limb2Anchor * Scale);
UpperLimit = MathHelper.ToRadians(Params.UpperLimit);
LowerLimit = MathHelper.ToRadians(Params.LowerLimit);
}
@@ -125,9 +127,29 @@ namespace Barotrauma
private Direction dir;
public int HealthIndex => Params.HealthIndex;
- public float Scale => Params.Ragdoll.LimbScale;
+ public float Scale => Params.Scale * Params.Ragdoll.LimbScale;
public float AttackPriority => Params.AttackPriority;
- public bool DoesFlip => Params.Flip;
+ public bool DoesFlip
+ {
+ get
+ {
+ if (character.AnimController.CurrentAnimationParams is GroundedMovementParams)
+ {
+ switch (type)
+ {
+ case LimbType.LeftFoot:
+ case LimbType.LeftLeg:
+ case LimbType.LeftThigh:
+ case LimbType.RightFoot:
+ case LimbType.RightLeg:
+ case LimbType.RightThigh:
+ // Legs always has to flip
+ return true;
+ }
+ }
+ return Params.Flip;
+ }
+ }
public float SteerForce => Params.SteerForce;
@@ -654,33 +676,37 @@ namespace Barotrauma
}
Vector2 diff = attackSimPos - SimPosition;
- bool applyForces = (!attack.ApplyForcesOnlyOnce || !wasRunning) && diff.LengthSquared() > 0.00001f;
+ bool applyForces = !attack.ApplyForcesOnlyOnce || !wasRunning;
if (applyForces)
{
-
if (attack.ForceOnLimbIndices != null && attack.ForceOnLimbIndices.Count > 0)
{
foreach (int limbIndex in attack.ForceOnLimbIndices)
{
- if (limbIndex < 0 || limbIndex >= character.AnimController.Limbs.Length) continue;
-
+ if (limbIndex < 0 || limbIndex >= character.AnimController.Limbs.Length) { continue; }
Limb limb = character.AnimController.Limbs[limbIndex];
- limb.body.ApplyTorque(limb.Mass * character.AnimController.Dir * attack.Torque);
+ diff = attackSimPos - limb.SimPosition;
+ if (diff == Vector2.Zero) { continue; }
+ limb.body.ApplyTorque(limb.Mass * character.AnimController.Dir * attack.Torque * limb.Params.AttackForceMultiplier);
Vector2 forcePos = limb.pullJoint == null ? limb.body.SimPosition : limb.pullJoint.WorldAnchorA;
- limb.body.ApplyLinearImpulse(limb.Mass * attack.Force * Vector2.Normalize(attackSimPos - SimPosition), forcePos,
- maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
+ limb.body.ApplyLinearImpulse(limb.Mass * attack.Force * limb.Params.AttackForceMultiplier * Vector2.Normalize(diff), forcePos, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
}
}
- else
+ else if (diff != Vector2.Zero)
{
- body.ApplyTorque(Mass * character.AnimController.Dir * attack.Torque);
+ body.ApplyTorque(Mass * character.AnimController.Dir * attack.Torque * Params.AttackForceMultiplier);
Vector2 forcePos = pullJoint == null ? body.SimPosition : pullJoint.WorldAnchorA;
- body.ApplyLinearImpulse(
- Mass * attack.Force * Vector2.Normalize(attackSimPos - SimPosition),
- forcePos,
- maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
+ body.ApplyLinearImpulse(Mass * attack.Force * Params.AttackForceMultiplier * Vector2.Normalize(diff), forcePos, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
}
}
+ Vector2 forceWorld = attack.CalculateAttackPhase(attack.RootTransitionEasing);
+ forceWorld.X *= character.AnimController.Dir;
+ character.AnimController.MainLimb.body.ApplyLinearImpulse(character.Mass * forceWorld, character.SimPosition, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
+ if (!attack.IsRunning)
+ {
+ // Set the main collider where the body lands after the attack
+ character.AnimController.Collider.SetTransform(character.AnimController.MainLimb.body.SimPosition, rotation: 0);
+ }
return wasHit;
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs
index 8e3333721..04f17291e 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs
@@ -28,9 +28,15 @@ namespace Barotrauma
[Serialize(false, true), Editable]
public bool Humanoid { get; private set; }
+ [Serialize(false, true), Editable]
+ public bool HasInfo { get; private set; }
+
[Serialize(false, true), Editable]
public bool Husk { get; private set; }
+ [Serialize(false, true), Editable]
+ public bool UseHuskAppendage { get; private set; }
+
[Serialize(false, true), Editable]
public bool NeedsAir { get; set; }
@@ -40,6 +46,9 @@ namespace Barotrauma
[Serialize(100f, true, description: "How much noise the character makes when moving?"), Editable(minValue: 0f, maxValue: 1000f)]
public float Noise { get; set; }
+ [Serialize(100f, true, description: "How visible the character is?"), Editable(minValue: 0f, maxValue: 1000f)]
+ public float Visibility { get; set; }
+
[Serialize("blood", true), Editable]
public string BloodDecal { get; private set; }
@@ -450,6 +459,9 @@ namespace Barotrauma
[Serialize(false, true, description: "Does the character try to break inside the sub?"), Editable()]
public bool AggressiveBoarding { get; private set; }
+ [Serialize(true, true, description: "Enforce aggressive behavior if the creature is spawned as a target of a monster mission."), Editable()]
+ public bool EnforceAggressiveBehaviorForMissions { get; private set; }
+
// TODO: latchonto, swarming
public IEnumerable Targets => targets;
@@ -538,6 +550,9 @@ namespace Barotrauma
[Serialize(0f, true, description: "Generic distance that can be used for different purposes depending on the state. Eg. in Avoid state this defines the distance that the character tries to keep to the target. If the distance is 0, it's not used."), Editable(MinValueFloat = 0, ValueStep = 10, DecimalCount = 0)]
public float ReactDistance { get; set; }
+ [Serialize(0f, true, description: "Used for defining the attack distance for PassiveAggressive and Aggressive states. If the distance is 0, it's not used."), Editable(MinValueFloat = 0, ValueStep = 10, DecimalCount = 0)]
+ public float AttackDistance { get; set; }
+
public TargetParams(XElement element, CharacterParams character) : base(element, character) { }
public TargetParams(string tag, AIState state, float priority, CharacterParams character) : base(CreateNewElement(tag, state, priority), character) { }
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs
index f9e20691e..70d687487 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs
@@ -489,6 +489,9 @@ namespace Barotrauma
[Serialize(0.25f, true), Editable]
public float Stiffness { get; set; }
+ [Serialize(1f, true, description: "CAUTION: Not fully implemented. Only use for limb joints that connect non-animated limbs!"), Editable]
+ public float Scale { get; set; }
+
public JointParams(XElement element, RagdollParams ragdoll) : base(element, ragdoll) { }
}
@@ -591,6 +594,18 @@ namespace Barotrauma
[Serialize("", true), Editable]
public string Notes { get; set; }
+ [Serialize(0f, true), Editable]
+ public float ConstantTorque { get; set; }
+
+ [Serialize(0f, true), Editable]
+ public float ConstantAngle { get; set; }
+
+ [Serialize(1f, true), Editable]
+ public float Scale { get; set; }
+
+ [Serialize(1f, true), Editable(DecimalCount = 2, MinValueFloat = 0, MaxValueFloat = 10)]
+ public float AttackForceMultiplier { get; set; }
+
// Non-editable ->
[Serialize(0, true)]
public int HealthIndex { get; set; }
diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentPackage.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentPackage.cs
index 7087bfb5b..84f727001 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/ContentPackage.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/ContentPackage.cs
@@ -460,12 +460,12 @@ namespace Barotrauma
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
var rootElement = doc.Root;
var element = rootElement.IsOverride() ? rootElement.FirstElement() : rootElement;
- var ragdollFolder = RagdollParams.GetFolder(doc, file.Path);
+ var ragdollFolder = RagdollParams.GetFolder(doc, file.Path).CleanUpPathCrossPlatform(true);
if (Directory.Exists(ragdollFolder))
{
Directory.GetFiles(ragdollFolder, "*.xml").ForEach(f => filePaths.Add(f));
}
- var animationFolder = AnimationParams.GetFolder(doc, file.Path);
+ var animationFolder = AnimationParams.GetFolder(doc, file.Path).CleanUpPathCrossPlatform(true);
if (Directory.Exists(animationFolder))
{
Directory.GetFiles(animationFolder, "*.xml").ForEach(f => filePaths.Add(f));
diff --git a/Barotrauma/BarotraumaShared/SharedSource/CoroutineManager.cs b/Barotrauma/BarotraumaShared/SharedSource/CoroutineManager.cs
index 47b732af0..6ef497afd 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/CoroutineManager.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/CoroutineManager.cs
@@ -186,6 +186,7 @@ namespace Barotrauma
#endif
if (handle.Thread == null)
{
+ if (handle.AbortRequested) { return true; }
if (handle.Coroutine.Current != null)
{
WaitForSeconds wfs = handle.Coroutine.Current as WaitForSeconds;
diff --git a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs
index 455ebca33..af7938c46 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs
@@ -1249,6 +1249,13 @@ namespace Barotrauma
}
return;
}
+#if !DEBUG
+ if (!IsCommandPermitted(splitCommand[0].ToLowerInvariant(), GameMain.Client))
+ {
+ ThrowError("You're not permitted to use the command \"" + splitCommand[0].ToLowerInvariant() + "\"!");
+ return;
+ }
+#endif
}
#endif
@@ -1610,8 +1617,26 @@ namespace Barotrauma
}
}
System.Diagnostics.Debug.WriteLine(error);
- NewMessage(error, Color.Red);
#if CLIENT
+ if (listBox == null) { NewMessage(error, Color.Red); return; }
+
+ var textContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.0f), listBox.Content.RectTransform),
+ style: "InnerFrame", color: Color.White)
+ {
+ CanBeFocused = false
+ };
+ var textBlock = new GUITextBlock(new RectTransform(new Point(listBox.Content.Rect.Width - 5, 0), textContainer.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(2, 2) },
+ error, textAlignment: Alignment.TopLeft, font: GUI.SmallFont, wrap: true)
+ {
+ CanBeFocused = false,
+ TextColor = Color.Red
+ };
+ textContainer.RectTransform.NonScaledSize = new Point(textContainer.RectTransform.NonScaledSize.X, textBlock.RectTransform.NonScaledSize.Y + 5);
+ textBlock.SetTextPos();
+
+ listBox.UpdateScrollBarSize();
+ listBox.BarScroll = 1.0f;
+
if (createMessageBox)
{
CoroutineManager.StartCoroutine(CreateMessageBox(error));
@@ -1620,6 +1645,8 @@ namespace Barotrauma
{
isOpen = true;
}
+#else
+ NewMessage(error, Color.Red);
#endif
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs
new file mode 100644
index 000000000..cdcb77796
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Barotrauma
+{
+ public enum TransitionMode
+ {
+ Linear,
+ Smooth,
+ Smoother,
+ EaseIn,
+ EaseOut,
+ Exponential
+ }
+}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CombatMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CombatMission.cs
index 6e5da3bf8..20c2dafd9 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CombatMission.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CombatMission.cs
@@ -44,6 +44,11 @@ namespace Barotrauma
}
}
+ public override int TeamCount
+ {
+ get { return 2; }
+ }
+
public CombatMission(MissionPrefab prefab, Location[] locations)
: base(prefab, locations)
{
@@ -113,7 +118,7 @@ namespace Barotrauma
{
for (int i = 0; i < 2; i++)
{
- if (wifiComponent.Item.Submarine == subs[i] || subs[i].DockedTo.Contains(wifiComponent.Item.Submarine))
+ if (wifiComponent.Item.Submarine == subs[i] || subs[i].ConnectedDockingPorts.ContainsKey(wifiComponent.Item.Submarine))
{
wifiComponent.TeamID = subs[i].TeamID;
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs
index 3386a00ec..7b9ddacd4 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs
@@ -74,6 +74,11 @@ namespace Barotrauma
get { return true; }
}
+ public virtual int TeamCount
+ {
+ get { return 1; }
+ }
+
public virtual IEnumerable SonarPositions
{
get { return Enumerable.Empty(); }
@@ -136,7 +141,7 @@ namespace Barotrauma
}
else
{
- allowedMissions.AddRange(MissionPrefab.List.Where(m => ((int)(missionType & m.type)) != 0));
+ allowedMissions.AddRange(MissionPrefab.List.Where(m => ((int)(missionType & m.Type)) != 0));
}
allowedMissions.RemoveAll(m => isSinglePlayer ? m.MultiplayerOnly : m.SingleplayerOnly);
@@ -168,10 +173,9 @@ namespace Barotrauma
public virtual void Update(float deltaTime) { }
- public virtual bool AssignTeamIDs(List clients)
+ public virtual void AssignTeamIDs(List clients)
{
clients.ForEach(c => c.TeamID = Character.TeamType.Team1);
- return false;
}
protected void ShowMessage(int missionState)
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs
index aa0092485..12e05cc69 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs
@@ -31,7 +31,7 @@ namespace Barotrauma
private readonly ConstructorInfo constructor;
- public readonly MissionType type;
+ public readonly MissionType Type;
public readonly bool MultiplayerOnly, SingleplayerOnly;
@@ -154,18 +154,18 @@ namespace Barotrauma
}
string missionTypeName = element.GetAttributeString("type", "");
- if (!Enum.TryParse(missionTypeName, out type))
+ if (!Enum.TryParse(missionTypeName, out Type))
{
DebugConsole.ThrowError("Error in mission prefab \"" + Name + "\" - \"" + missionTypeName + "\" is not a valid mission type.");
return;
}
- if (type == MissionType.None)
+ if (Type == MissionType.None)
{
DebugConsole.ThrowError("Error in mission prefab \"" + Name + "\" - mission type cannot be none.");
return;
}
- constructor = missionClasses[type].GetConstructor(new[] { typeof(MissionPrefab), typeof(Location[]) });
+ constructor = missionClasses[Type].GetConstructor(new[] { typeof(MissionPrefab), typeof(Location[]) });
InitProjSpecific(element);
}
@@ -189,7 +189,7 @@ namespace Barotrauma
return false;
}
-
+
public Mission Instantiate(Location[] locations)
{
return constructor?.Invoke(new object[] { this, locations }) as Mission;
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs
index 06a19a7c8..88c43fabb 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs
@@ -2,9 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using System;
-using System.Xml.Linq;
-using Barotrauma.Extensions;
-using Barotrauma.Networking;
namespace Barotrauma
{
@@ -113,7 +110,25 @@ namespace Barotrauma
private void InitializeMonsters(IEnumerable monsters)
{
- monsters.ForEach(m => m.Enabled = false);
+ foreach (var monster in monsters)
+ {
+ monster.Enabled = false;
+ if (monster.Params.AI.EnforceAggressiveBehaviorForMissions)
+ {
+ foreach (var targetParam in monster.Params.AI.Targets)
+ {
+ switch (targetParam.State)
+ {
+ case AIState.Avoid:
+ case AIState.Escape:
+ case AIState.Flee:
+ case AIState.PassiveAggressive:
+ targetParam.State = AIState.Attack;
+ break;
+ }
+ }
+ }
+ }
SwarmBehavior.CreateSwarm(monsters.Cast());
foreach (Character monster in monsters)
{
@@ -191,7 +206,10 @@ namespace Barotrauma
completed = true;
}
- public bool IsEliminated(Character enemy) => enemy.Removed || enemy.IsDead || enemy.AIController is EnemyAIController ai && ai.State == AIState.Flee;
-
+ public bool IsEliminated(Character enemy) =>
+ enemy == null ||
+ enemy.Removed ||
+ enemy.IsDead ||
+ enemy.AIController is EnemyAIController ai && ai.State == AIState.Flee;
}
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Extensions/IEnumerableExtensions.cs b/Barotrauma/BarotraumaShared/SharedSource/Extensions/IEnumerableExtensions.cs
index a769afa54..9e129f639 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Extensions/IEnumerableExtensions.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Extensions/IEnumerableExtensions.cs
@@ -90,6 +90,11 @@ namespace Barotrauma.Extensions
return source.Count(predicate) > 1;
}
}
+
+ public static IEnumerable ToEnumerable(this T item)
+ {
+ yield return item;
+ }
// source: https://stackoverflow.com/questions/19237868/get-all-children-to-one-list-recursive-c-sharp
public static IEnumerable SelectManyRecursive(this IEnumerable source, Func> selector)
diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs
index 3964464a1..639dc51d5 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs
@@ -150,14 +150,14 @@ namespace Barotrauma
SaveUtil.LoadGame(SavePath);
}
- public void StartRound(string levelSeed, float? difficulty = null, bool loadSecondSub = false)
+ public void StartRound(string levelSeed, float? difficulty = null)
{
Level randomLevel = Level.CreateRandom(levelSeed, difficulty);
- StartRound(randomLevel, true, loadSecondSub);
+ StartRound(randomLevel, true);
}
- public void StartRound(Level level, bool reloadSub = true, bool loadSecondSub = false, bool mirrorLevel = false)
+ public void StartRound(Level level, bool reloadSub = true, bool mirrorLevel = false)
{
//make sure no status effects have been carried on from the next round
//(they should be stopped in EndRound, this is a safeguard against cases where the round is ended ungracefully)
@@ -177,7 +177,7 @@ namespace Barotrauma
if (reloadSub || Submarine.MainSub != Submarine) { Submarine.Load(true); }
Submarine.MainSub = Submarine;
- if (loadSecondSub)
+ if (GameMode.Mission != null && GameMode.Mission.TeamCount > 1)
{
if (Submarine.MainSubs[1] == null)
{
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs
index d958b9eb3..5d8abdb7f 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs
@@ -173,8 +173,8 @@ namespace Barotrauma.Items.Components
if (!item.linkedTo.Contains(target.item)) item.linkedTo.Add(target.item);
if (!target.item.linkedTo.Contains(item)) target.item.linkedTo.Add(item);
- if (!target.item.Submarine.DockedTo.Contains(item.Submarine)) target.item.Submarine.DockedTo.Add(item.Submarine);
- if (!item.Submarine.DockedTo.Contains(target.item.Submarine)) item.Submarine.DockedTo.Add(target.item.Submarine);
+ if (!target.item.Submarine.DockedTo.Contains(item.Submarine)) target.item.Submarine.ConnectedDockingPorts.Add(item.Submarine, target);
+ if (!item.Submarine.DockedTo.Contains(target.item.Submarine)) item.Submarine.ConnectedDockingPorts.Add(target.item.Submarine, this);
DockingTarget = target;
DockingTarget.DockingTarget = this;
@@ -703,8 +703,8 @@ namespace Barotrauma.Items.Components
ApplyStatusEffects(ActionType.OnSecondaryUse, 1.0f);
- DockingTarget.item.Submarine.DockedTo.Remove(item.Submarine);
- item.Submarine.DockedTo.Remove(DockingTarget.item.Submarine);
+ DockingTarget.item.Submarine.ConnectedDockingPorts.Remove(item.Submarine);
+ item.Submarine.ConnectedDockingPorts.Remove(DockingTarget.item.Submarine);
if (door != null && DockingTarget.door != null)
{
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs
index 7a9b760b7..45d565f4c 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs
@@ -287,24 +287,21 @@ namespace Barotrauma.Items.Components
public override bool Select(Character character)
{
- if (!isBroken)
+ if (isBroken) { return true; }
+ bool hasRequiredItems = HasRequiredItems(character, false);
+ if (HasAccess(character))
{
- bool hasRequiredItems = HasRequiredItems(character, false);
- if (HasAccess(character))
- {
- float originalPickingTime = PickingTime;
- PickingTime = 0;
- ToggleState(ActionType.OnUse, character);
- PickingTime = originalPickingTime;
- }
-#if CLIENT
- else if (hasRequiredItems && character != null && character == Character.Controlled)
- {
- GUI.AddMessage(accessDeniedTxt, GUI.Style.Red);
-
- }
-#endif
+ float originalPickingTime = PickingTime;
+ PickingTime = 0;
+ ToggleState(ActionType.OnUse, character);
+ PickingTime = originalPickingTime;
}
+#if CLIENT
+ else if (hasRequiredItems && character != null && character == Character.Controlled)
+ {
+ GUI.AddMessage(accessDeniedTxt, GUI.Style.Red);
+ }
+#endif
return false;
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs
index 31b47c057..7d7638c33 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs
@@ -578,13 +578,7 @@ namespace Barotrauma.Items.Components
{
character.SetInput(InputType.Aim, false, true);
}
- bool isAiming = false;
- var holdable = item.GetComponent();
- if (holdable != null)
- {
- isAiming = holdable.ControlPose;
- }
- sinTime = isAiming ? sinTime + deltaTime * 5 : 0;
+ sinTime += deltaTime * 5;
}
// Press the trigger only when the tool is approximately facing the target.
Vector2 fromItemToLeak = leak.WorldPosition - item.WorldPosition;
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs
index 73e3baad9..78a17680b 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs
@@ -870,6 +870,7 @@ namespace Barotrauma.Items.Components
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "requireditem":
+ case "requireditems":
RelatedItem newRequiredItem = RelatedItem.Load(subElement, returnEmptyRequirements, item.Name);
if (newRequiredItem == null) continue;
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs
index 7bdd7bfdc..20ad18b85 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs
@@ -22,6 +22,30 @@ namespace Barotrauma.Items.Components
private ItemContainer inputContainer, outputContainer;
+ private enum FabricatorState
+ {
+ Active = 1,
+ Paused = 2,
+ Stopped = 0
+ }
+
+ private FabricatorState state;
+ private FabricatorState State
+ {
+ get
+ {
+ return state;
+ }
+ set
+ {
+ if (state == value) { return; }
+ state = value;
+#if SERVER
+ item.CreateServerEvent(this);
+#endif
+ }
+ }
+
public ItemContainer InputContainer
{
get { return inputContainer; }
@@ -61,6 +85,8 @@ namespace Barotrauma.Items.Components
}
}
+ state = FabricatorState.Stopped;
+
InitProjSpecific();
}
@@ -147,12 +173,15 @@ namespace Barotrauma.Items.Components
currPowerConsumption = powerConsumption;
currPowerConsumption *= MathHelper.Lerp(1.5f, 1.0f, item.Condition / item.MaxCondition);
+ if (GameMain.NetworkMember?.IsServer ?? true)
+ {
+ State = FabricatorState.Active;
+ }
#if SERVER
if (user != null)
{
GameServer.Log(user.LogName + " started fabricating " + selectedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
}
- item.CreateServerEvent(this);
#endif
}
@@ -180,12 +209,15 @@ namespace Barotrauma.Items.Components
inputContainer.Inventory.Locked = false;
outputContainer.Inventory.Locked = false;
+ if (GameMain.NetworkMember?.IsServer ?? true)
+ {
+ State = FabricatorState.Stopped;
+ }
#if SERVER
if (user != null)
{
GameServer.Log(user.LogName + " cancelled the fabrication of " + fabricatedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
}
- item.CreateServerEvent(this);
#endif
}
@@ -199,8 +231,25 @@ namespace Barotrauma.Items.Components
progressState = fabricatedItem == null ? 0.0f : (requiredTime - timeUntilReady) / requiredTime;
- hasPower = Voltage >= MinVoltage;
- if (!hasPower) { return; }
+ if (GameMain.NetworkMember?.IsClient ?? false)
+ {
+ hasPower = State != FabricatorState.Paused;
+ if (!hasPower)
+ {
+ return;
+ }
+ }
+ else
+ {
+ hasPower = Voltage >= MinVoltage;
+
+ if (!hasPower)
+ {
+ State = FabricatorState.Paused;
+ return;
+ }
+ State = FabricatorState.Active;
+ }
var repairable = item.GetComponent();
if (repairable != null)
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs
index 520b0b0a3..e1754b5f8 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs
@@ -37,23 +37,10 @@ namespace Barotrauma.Items.Components
private float currFlow;
public float CurrFlow
{
- get
+ get
{
if (!IsActive) { return 0.0f; }
- return Math.Abs(currFlow);
- }
- }
-
- public override bool IsActive
- {
- get => base.IsActive;
- set
- {
- base.IsActive = value;
- if (!IsActive)
- {
- powerConsumption = 0;
- }
+ return Math.Abs(currFlow);
}
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs
index 27e46c46d..a7cb03f87 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs
@@ -53,7 +53,9 @@ namespace Barotrauma.Items.Components
private PrismaticJoint stickJoint;
private Body stickTarget;
- private Attack attack;
+ private readonly Attack attack;
+
+ private Vector2 launchPos;
public List IgnoredBodies;
@@ -220,6 +222,8 @@ namespace Barotrauma.Items.Components
item.Drop(null);
+ launchPos = item.SimPosition;
+
item.body.Enabled = true;
item.body.ApplyLinearImpulse(impulse, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
@@ -415,7 +419,9 @@ namespace Barotrauma.Items.Components
item.body.SimPosition - ConvertUnits.ToSimUnits(sub.Position) - dir,
item.body.SimPosition - ConvertUnits.ToSimUnits(sub.Position) + dir,
collisionCategory: Physics.CollisionWall);
- if (wallBody?.FixtureList?.First() != null && wallBody.UserData is Structure structure)
+ if (wallBody?.FixtureList?.First() != null && wallBody.UserData is Structure structure &&
+ //ignore the hit if it's behind the position the item was launched from, and the projectile is travelling in the opposite direction
+ Vector2.Dot(item.body.SimPosition - launchPos, dir) > 0)
{
target = wallBody.FixtureList.First();
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs
index 46812f554..a58dc17e3 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs
@@ -79,6 +79,12 @@ namespace Barotrauma.Items.Components
Wire wire = wireItem.GetComponent();
if (wire != null)
{
+ if (Item.ItemList.Any(it => it != item && (it.GetComponent()?.DisconnectedWires.Contains(wire) ?? false)))
+ {
+ if (wire.Item.body != null) { wire.Item.body.Enabled = false; }
+ wire.IsActive = false;
+ wire.UpdateSections();
+ }
DisconnectedWires.Add(wire);
base.IsActive = true;
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs
index 7a150d53f..67c7e815b 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs
@@ -272,13 +272,12 @@ namespace Barotrauma.Items.Components
private void UpdateAITarget(AITarget target)
{
- target.Enabled = IsActive;
if (!IsActive) { return; }
if (target.MaxSightRange <= 0)
{
target.MaxSightRange = Range * 5;
}
- target.SightRange = target.MaxSightRange * lightBrightness;
+ target.SightRange = Math.Max(target.SightRange, target.MaxSightRange * lightBrightness);
}
partial void SetLightSourceState(bool enabled, float brightness);
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs
index 5879e6be8..49370a3a4 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs
@@ -374,8 +374,9 @@ namespace Barotrauma.Items.Components
reload = reloadTime;
projectile.Drop(null);
- projectile.body.Dir = 1.0f;
+ if (projectile.body == null) { return; }
+ projectile.body.Dir = 1.0f;
projectile.body.ResetDynamics();
projectile.body.Enabled = true;
projectile.SetTransform(ConvertUnits.ToSimUnits(new Vector2(item.WorldRect.X + transformedBarrelPos.X, item.WorldRect.Y - transformedBarrelPos.Y)), -rotation);
@@ -591,7 +592,7 @@ namespace Barotrauma.Items.Components
foreach (MapEntity e in item.linkedTo)
{
if (e is Item projectileContainer) { CheckProjectileContainer(projectileContainer, projectiles, returnFirst); }
- if (returnFirst && projectiles.Any()) return projectiles;
+ if (returnFirst && projectiles.Any()) { return projectiles; }
}
return projectiles;
@@ -600,15 +601,15 @@ namespace Barotrauma.Items.Components
private void CheckProjectileContainer(Item projectileContainer, List projectiles, bool returnFirst)
{
var containedItems = projectileContainer.ContainedItems;
- if (containedItems == null) return;
+ if (containedItems == null) { return; }
foreach (Item containedItem in containedItems)
{
var projectileComponent = containedItem.GetComponent();
- if (projectileComponent != null)
+ if (projectileComponent != null && projectileComponent.Item.body != null)
{
projectiles.Add(projectileComponent);
- if (returnFirst) return;
+ if (returnFirst) { return; }
}
else
{
@@ -617,10 +618,10 @@ namespace Barotrauma.Items.Components
foreach (Item subContainedItem in containedItem.ContainedItems)
{
projectileComponent = subContainedItem.GetComponent();
- if (projectileComponent != null)
+ if (projectileComponent != null && projectileComponent.Item.body != null)
{
projectiles.Add(projectileComponent);
- if (returnFirst) return;
+ if (returnFirst) { return; }
}
}
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs
index 2310af049..be96b65b4 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs
@@ -1343,7 +1343,7 @@ namespace Barotrauma
UpdateTransform();
if (CurrentHull == null && body.SimPosition.Y < ConvertUnits.ToSimUnits(Level.MaxEntityDepth))
{
- Spawner.AddToRemoveQueue(this);
+ Spawner?.AddToRemoveQueue(this);
return;
}
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs
index d21bb00a2..a0faf465e 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs
@@ -152,7 +152,7 @@ namespace Barotrauma
public void Save(XElement element)
{
element.Add(
- new XAttribute("identifiers", JoinedIdentifiers),
+ new XAttribute("items", JoinedIdentifiers),
new XAttribute("type", type.ToString()),
new XAttribute("optional", IsOptional),
new XAttribute("ignoreineditor", IgnoreInEditor));
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs
index 412298f03..ae5740cec 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs
@@ -310,6 +310,7 @@ namespace Barotrauma
if (Submarine.MainSub != null)
{
Rectangle dockedSubBorders = Submarine.MainSub.GetDockedBorders();
+ dockedSubBorders.Inflate(dockedSubBorders.Size.ToVector2() * 0.05f);
minWidth = Math.Max(minWidth, Math.Max(dockedSubBorders.Width, dockedSubBorders.Height));
minWidth = Math.Min(minWidth, maxWidth);
}
@@ -1622,7 +1623,7 @@ namespace Barotrauma
EndOutpost = outpost;
if (GameMain.GameSession?.EndLocation != null) { outpost.Name = GameMain.GameSession.EndLocation.Name; }
}
- }
+ }
}
private bool IsModeStartOutpostCompatible()
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Ruins/RuinGenerator.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Ruins/RuinGenerator.cs
index 1d29696e7..2a96202f1 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Ruins/RuinGenerator.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Ruins/RuinGenerator.cs
@@ -721,8 +721,10 @@ namespace Barotrauma.RuinGeneration
//doors create their own gaps, don't create an additional one if there's a door at this
bool doorFound = false;
- foreach (Door door in doors)
+ foreach (Item item in Item.ItemList)
{
+ var door = item.GetComponent();
+ if (door == null) { continue; }
if (Math.Abs(door.Item.WorldPosition.X - gapRect.Value.Center.X) < 5 &&
Math.Abs(door.Item.WorldPosition.Y - gapRect.Value.Center.Y) < 5)
{
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/LinkedSubmarine.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/LinkedSubmarine.cs
index 29606d4ae..339c1423b 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Map/LinkedSubmarine.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Map/LinkedSubmarine.cs
@@ -105,15 +105,20 @@ namespace Barotrauma
{
LinkedSubmarine sl = new LinkedSubmarine(mainSub);
sl.GenerateWallVertices(element);
+ if (sl.wallVertices.Any())
+ {
+ sl.Rect = new Rectangle(
+ (int)sl.wallVertices.Min(v => v.X + position.X),
+ (int)sl.wallVertices.Max(v => v.Y + position.Y),
+ (int)sl.wallVertices.Max(v => v.X + position.X),
+ (int)sl.wallVertices.Min(v => v.Y + position.Y));
- sl.Rect = new Rectangle(
- (int)sl.wallVertices.Min(v => v.X + position.X),
- (int)sl.wallVertices.Max(v => v.Y + position.Y),
- (int)sl.wallVertices.Max(v => v.X + position.X),
- (int)sl.wallVertices.Min(v => v.Y + position.Y));
-
- sl.rect = new Rectangle((int)position.X, (int)position.Y, 1, 1);
-
+ sl.Rect = new Rectangle(sl.rect.X, sl.rect.Y, sl.rect.Width - sl.rect.X, sl.rect.Y - sl.rect.Height);
+ }
+ else
+ {
+ sl.Rect = new Rectangle((int)position.X, (int)position.Y, 10, 10);
+ }
return sl;
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntityPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntityPrefab.cs
index 3d5aade29..b2ff9d02f 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntityPrefab.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntityPrefab.cs
@@ -43,7 +43,6 @@ namespace Barotrauma
protected string originalName;
protected string identifier;
- protected ContentPackage contentPackage;
public Sprite sprite;
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs
index d30787186..c04680ee5 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs
@@ -77,7 +77,18 @@ namespace Barotrauma
private SubmarineBody subBody;
- public readonly List DockedTo;
+ public readonly Dictionary ConnectedDockingPorts;
+ public IEnumerable DockedTo
+ {
+ get
+ {
+ if (ConnectedDockingPorts == null) { yield break; }
+ foreach (Submarine sub in ConnectedDockingPorts.Keys)
+ {
+ yield return sub;
+ }
+ }
+ }
private static Vector2 lastPickedPosition;
private static float lastPickedFraction;
@@ -442,7 +453,7 @@ namespace Barotrauma
}
}
- DockedTo = new List();
+ ConnectedDockingPorts = new Dictionary();
FreeID();
}
@@ -561,27 +572,32 @@ namespace Barotrauma
///
/// Returns a rect that contains the borders of this sub and all subs docked to it
///
- public Rectangle GetDockedBorders()
+ public Rectangle GetDockedBorders(List checkd=null)
{
- Rectangle dockedBorders = Borders;
- dockedBorders.Y -= dockedBorders.Height;
+ if (checkd == null) { checkd = new List(); }
+ checkd.Add(this);
- var connectedSubs = GetConnectedSubs();
+ Rectangle dockedBorders = Borders;
+
+ var connectedSubs = DockedTo.Where(s => !checkd.Contains(s) && !s.IsOutpost).ToList();
foreach (Submarine dockedSub in connectedSubs)
{
- if (dockedSub == this) continue;
+ //use docking ports instead of world position to determine
+ //borders, as world position will not necessarily match where
+ //the subs are supposed to go
+ Vector2? expectedLocation = CalculateDockOffset(this, dockedSub);
+ if (expectedLocation == null) { continue; }
- Vector2 diff = dockedSub.Submarine == this ? dockedSub.WorldPosition : dockedSub.WorldPosition - WorldPosition;
-
- Rectangle dockedSubBorders = dockedSub.Borders;
- dockedSubBorders.Y -= dockedSubBorders.Height;
- dockedSubBorders.Location += MathUtils.ToPoint(diff);
+ Rectangle dockedSubBorders = dockedSub.GetDockedBorders(checkd);
+ dockedSubBorders.Location += MathUtils.ToPoint(expectedLocation.Value);
+ dockedBorders.Y = -dockedBorders.Y;
+ dockedSubBorders.Y = -dockedSubBorders.Y;
dockedBorders = Rectangle.Union(dockedBorders, dockedSubBorders);
+ dockedBorders.Y = -dockedBorders.Y;
}
- dockedBorders.Y += dockedBorders.Height;
return dockedBorders;
}
@@ -1131,25 +1147,37 @@ namespace Barotrauma
prevPosition = position;
}
- public void SetPosition(Vector2 position)
+ public void SetPosition(Vector2 position, List checkd=null)
{
if (!MathUtils.IsValid(position)) return;
-
+
+ if (checkd == null) { checkd = new List(); }
+ if (checkd.Contains(this)) { return; }
+
+ checkd.Add(this);
+
subBody.SetPosition(position);
- foreach (Submarine sub in loaded)
+ foreach (Submarine dockedSub in DockedTo)
{
- if (sub != this && sub.Submarine == this)
- {
- sub.SetPosition(position + sub.WorldPosition);
- sub.Submarine = null;
- }
+ Vector2? expectedLocation = CalculateDockOffset(this, dockedSub);
+ if (expectedLocation == null) { continue; }
+ dockedSub.SetPosition(position + expectedLocation.Value, checkd);
}
//Level.Loaded.SetPosition(-position);
//prevPosition = position;
}
+ public static Vector2? CalculateDockOffset(Submarine sub, Submarine dockedSub)
+ {
+ Item myPort = sub.ConnectedDockingPorts.ContainsKey(dockedSub) ? sub.ConnectedDockingPorts[dockedSub].Item : null;
+ if (myPort == null) { return null; }
+ Item theirPort = dockedSub.ConnectedDockingPorts.ContainsKey(sub) ? dockedSub.ConnectedDockingPorts[sub].Item : null;
+ if (theirPort == null) { return null; }
+ return (myPort.Position - sub.HiddenSubPosition) - (theirPort.Position - dockedSub.HiddenSubPosition);
+ }
+
public void Translate(Vector2 amount)
{
if (amount == Vector2.Zero || !MathUtils.IsValid(amount)) return;
@@ -1733,7 +1761,7 @@ namespace Barotrauma
if (MainSub == this) MainSub = null;
if (MainSubs[1] == this) MainSubs[1] = null;
- DockedTo?.Clear();
+ ConnectedDockingPorts?.Clear();
}
public void Dispose()
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/Client.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/Client.cs
index 98c06bf6d..c46241964 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Networking/Client.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/Client.cs
@@ -21,16 +21,27 @@ namespace Barotrauma.Networking
private Character character;
public Character Character
{
- get { return character; }
+ get
+ {
+ if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && (character?.ID ?? 0) != CharacterID)
+ {
+ Character = Entity.FindEntityByID(CharacterID) as Character;
+ }
+ return character;
+ }
set
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
GameMain.NetworkMember.LastClientListUpdateID++;
+ if (value != null)
+ {
+ CharacterID = value.ID;
+ }
}
else
{
- if (value!=null)
+ if (value != null)
{
DebugConsole.NewMessage(value.Name, Microsoft.Xna.Framework.Color.Yellow);
}
@@ -52,6 +63,8 @@ namespace Barotrauma.Networking
}
}
+ public UInt16 CharacterID;
+
private Vector2 spectate_position;
public Vector2? SpectatePos
{
@@ -98,7 +111,22 @@ namespace Barotrauma.Networking
private set;
}
- public bool InGame;
+ private bool inGame;
+ public bool InGame
+ {
+ get
+ {
+ return inGame;
+ }
+ set
+ {
+ if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
+ {
+ GameMain.NetworkMember.LastClientListUpdateID++;
+ }
+ inGame = value;
+ }
+ }
public bool HasSpawned; //has the client spawned as a character during the current round
private List kickVoters;
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetworkMember.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetworkMember.cs
index ed54d4672..5858d7f55 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetworkMember.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetworkMember.cs
@@ -22,6 +22,8 @@ namespace Barotrauma.Networking
RESPONSE_STARTGAME, //tell the server whether you're ready to start
SERVER_COMMAND, //tell the server to end a round or kick/ban someone (special permissions required)
+ REQUEST_STARTGAMEFINALIZE, //tell the server you're ready to finalize round initialization
+
ERROR //tell the server that an error occurred
}
enum ClientNetObject
@@ -60,6 +62,7 @@ namespace Barotrauma.Networking
QUERY_STARTGAME, //ask the clients whether they're ready to start
STARTGAME, //start a new round
+ STARTGAMEFINALIZE, //finalize round initialization
ENDGAME,
TRAITOR_MESSAGE,
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/SteamManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/SteamManager.cs
index 9a5eada0c..bbfe0948f 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Networking/SteamManager.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/SteamManager.cs
@@ -159,8 +159,9 @@ namespace Barotrauma.Steam
{
if (string.IsNullOrWhiteSpace(str)) { return 0; }
UInt64 retVal;
+ if (str.StartsWith("STEAM64_", StringComparison.InvariantCultureIgnoreCase)) { str = str.Substring(8); }
if (UInt64.TryParse(str, out retVal) && retVal >(1<<52)) { return retVal; }
- if (str.ToUpper().IndexOf("STEAM_") != 0) { return 0; }
+ if (!str.StartsWith("STEAM_", StringComparison.InvariantCultureIgnoreCase)) { return 0; }
string[] split = str.Substring(6).Split(':');
if (split.Length != 3) { return 0; }
@@ -179,7 +180,11 @@ namespace Barotrauma.Steam
UInt64 accountNumber = (uint64 >> 1) & 0x7fffffff;
UInt64 universe = (uint64 >> 56) & 0xff;
- return "STEAM_" + universe.ToString() + ":" + y.ToString() + ":" + accountNumber.ToString();
+ string retVal = "STEAM_" + universe.ToString() + ":" + y.ToString() + ":" + accountNumber.ToString();
+
+ if (SteamIDStringToUInt64(retVal) != uint64) { return "STEAM64_" + uint64.ToString(); }
+
+ return retVal;
}
}
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs b/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs
index 15de71604..974d8e9a1 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs
@@ -250,7 +250,9 @@ namespace Barotrauma
///
/// Takes flipping (Dir) into account.
///
- public float TransformedRotation => Dir < 0 ? Rotation - MathHelper.Pi : Rotation;
+ public float TransformedRotation => TransformRotation(Rotation, Dir);
+
+ public static float TransformRotation(float rot, float dir) => dir < 0 ? rot - MathHelper.Pi : rot;
public Vector2 LinearVelocity
{
@@ -351,9 +353,9 @@ namespace Barotrauma
public PhysicsBody(LimbParams limbParams, Vector2 position)
{
- float radius = ConvertUnits.ToSimUnits(limbParams.Radius) * limbParams.Ragdoll.LimbScale;
- float height = ConvertUnits.ToSimUnits(limbParams.Height) * limbParams.Ragdoll.LimbScale;
- float width = ConvertUnits.ToSimUnits(limbParams.Width) * limbParams.Ragdoll.LimbScale;
+ float radius = ConvertUnits.ToSimUnits(limbParams.Radius) * limbParams.Scale * limbParams.Ragdoll.LimbScale;
+ float height = ConvertUnits.ToSimUnits(limbParams.Height) * limbParams.Scale * limbParams.Ragdoll.LimbScale;
+ float width = ConvertUnits.ToSimUnits(limbParams.Width) * limbParams.Scale * limbParams.Ragdoll.LimbScale;
density = limbParams.Density;
CreateBody(width, height, radius, density);
FarseerBody.BodyType = BodyType.Dynamic;
diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/DelayedEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/DelayedEffect.cs
index cddc9c97d..ae67f66f2 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/DelayedEffect.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/DelayedEffect.cs
@@ -1,17 +1,27 @@
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
+using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
class DelayedListElement
{
- public DelayedEffect Parent;
- public Entity Entity;
- public Vector2? WorldPosition;
- public List Targets;
+ public readonly DelayedEffect Parent;
+ public readonly Entity Entity;
+ public readonly Vector2? WorldPosition;
+ public readonly List Targets;
public float StartTimer;
+
+ public DelayedListElement(DelayedEffect parentEffect, Entity parentEntity, IEnumerable targets, float delay, Vector2? worldPosition)
+ {
+ Parent = parentEffect;
+ Entity = parentEntity;
+ Targets = new List(targets);
+ StartTimer = delay;
+ WorldPosition = worldPosition;
+ }
}
class DelayedEffect : StatusEffect
{
@@ -27,28 +37,18 @@ namespace Barotrauma
public override void Apply(ActionType type, float deltaTime, Entity entity, ISerializableEntity target, Vector2? worldPosition = null)
{
- if (this.type != type || !HasRequiredItems(entity)) return;
- if (!Stackable && DelayList.Any(d => d.Parent == this && d.Targets.FirstOrDefault() == target)) return;
-
- if (targetIdentifiers != null && !IsValidTarget(target)) return;
- if (!HasRequiredConditions(new List() { target })) return;
+ if (this.type != type || !HasRequiredItems(entity)) { return; }
+ if (!Stackable && DelayList.Any(d => d.Parent == this && d.Targets.FirstOrDefault() == target)) { return; }
+ if (targetIdentifiers != null && !IsValidTarget(target)) { return; }
+ if (!HasRequiredConditions(target.ToEnumerable())) { return; }
- DelayedListElement element = new DelayedListElement
- {
- Parent = this,
- StartTimer = delay,
- Entity = entity,
- WorldPosition = worldPosition,
- Targets = new List() { target }
- };
-
- DelayList.Add(element);
+ DelayList.Add(new DelayedListElement(this, entity, target.ToEnumerable(), delay, worldPosition));
}
public override void Apply(ActionType type, float deltaTime, Entity entity, IEnumerable targets, Vector2? worldPosition = null)
{
- if (this.type != type || !HasRequiredItems(entity)) return;
- if (!Stackable && DelayList.Any(d => d.Parent == this && d.Targets.SequenceEqual(targets))) return;
+ if (this.type != type || !HasRequiredItems(entity)) { return; }
+ if (!Stackable && DelayList.Any(d => d.Parent == this && d.Targets.SequenceEqual(targets))) { return; }
currentTargets.Clear();
foreach (ISerializableEntity target in targets)
@@ -61,18 +61,9 @@ namespace Barotrauma
currentTargets.Add(target);
}
- if (!HasRequiredConditions(currentTargets)) return;
+ if (!HasRequiredConditions(currentTargets)) { return; }
- DelayedListElement element = new DelayedListElement
- {
- Parent = this,
- StartTimer = delay,
- Entity = entity,
- WorldPosition = worldPosition,
- Targets = currentTargets
- };
-
- DelayList.Add(element);
+ DelayList.Add(new DelayedListElement(this, entity, currentTargets, delay, worldPosition));
}
public static void Update(float deltaTime)
@@ -88,7 +79,7 @@ namespace Barotrauma
element.StartTimer -= deltaTime;
- if (element.StartTimer > 0.0f) continue;
+ if (element.StartTimer > 0.0f) { continue; }
element.Parent.Apply(1.0f, element.Entity, element.Targets, element.WorldPosition);
DelayList.Remove(element);
diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs
index cee21264e..4f877f611 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs
@@ -1,4 +1,5 @@
-using Barotrauma.Items.Components;
+using Barotrauma.Extensions;
+using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -9,11 +10,27 @@ namespace Barotrauma
{
class DurationListElement
{
- public StatusEffect Parent;
- public Entity Entity;
- public List Targets;
+ public readonly StatusEffect Parent;
+ public readonly Entity Entity;
+ public readonly List Targets;
+ public Character User { get; private set; }
+
public float Timer;
- public Character User;
+
+ public DurationListElement(StatusEffect parentEffect, Entity parentEntity, IEnumerable targets, float duration, Character user)
+ {
+ Parent = parentEffect;
+ Entity = parentEntity;
+ Targets = new List(targets);
+ Timer = duration;
+ User = user;
+ }
+
+ public void Reset(float duration, Character newUser)
+ {
+ Timer = duration;
+ User = newUser;
+ }
}
partial class StatusEffect
@@ -476,10 +493,10 @@ namespace Barotrauma
}
}
- public virtual bool HasRequiredConditions(List targets)
+ public virtual bool HasRequiredConditions(IEnumerable targets)
{
if (!propertyConditionals.Any()) { return true; }
- if (requiredItems.Any() && requiredItems.All(ri => ri.MatchOnEmpty) && targets.Count == 0) { return true; }
+ if (requiredItems.Any() && requiredItems.All(ri => ri.MatchOnEmpty) && targets.Count() == 0) { return true; }
switch (conditionalComparison)
{
case PropertyConditional.Comparison.Or:
@@ -560,33 +577,26 @@ namespace Barotrauma
public virtual void Apply(ActionType type, float deltaTime, Entity entity, ISerializableEntity target, Vector2? worldPosition = null)
{
- if (this.type != type || !HasRequiredItems(entity)) return;
+ if (this.type != type || !HasRequiredItems(entity)) { return; }
- if (targetIdentifiers != null && !IsValidTarget(target)) return;
+ if (targetIdentifiers != null && !IsValidTarget(target)) { return; }
if (duration > 0.0f && !Stackable)
{
//ignore if not stackable and there's already an identical statuseffect
DurationListElement existingEffect = DurationList.Find(d => d.Parent == this && d.Targets.FirstOrDefault() == target);
- if (existingEffect != null)
- {
- existingEffect.Timer = Math.Max(existingEffect.Timer, duration);
- existingEffect.User = user;
- return;
- }
+ existingEffect?.Reset(Math.Max(existingEffect.Timer, duration), user);
+ return;
}
- List targets = new List { target };
-
- if (!HasRequiredConditions(targets)) return;
-
- Apply(deltaTime, entity, targets, worldPosition);
+ if (!HasRequiredConditions(target.ToEnumerable())) { return; }
+ Apply(deltaTime, entity, target.ToEnumerable(), worldPosition);
}
protected readonly List currentTargets = new List();
public virtual void Apply(ActionType type, float deltaTime, Entity entity, IEnumerable targets, Vector2? worldPosition = null)
{
- if (this.type != type) return;
+ if (this.type != type) { return; }
currentTargets.Clear();
foreach (ISerializableEntity target in targets)
@@ -601,7 +611,7 @@ namespace Barotrauma
if (targetIdentifiers != null && currentTargets.Count == 0) { return; }
- if (!HasRequiredItems(entity) || !HasRequiredConditions(currentTargets)) return;
+ if (!HasRequiredItems(entity) || !HasRequiredConditions(currentTargets)) { return; }
if (duration > 0.0f && !Stackable)
{
@@ -609,8 +619,7 @@ namespace Barotrauma
DurationListElement existingEffect = DurationList.Find(d => d.Parent == this && d.Targets.SequenceEqual(currentTargets));
if (existingEffect != null)
{
- existingEffect.Timer = Math.Max(existingEffect.Timer, duration);
- existingEffect.User = user;
+ existingEffect?.Reset(Math.Max(existingEffect.Timer, duration), user);
return;
}
}
@@ -618,7 +627,7 @@ namespace Barotrauma
Apply(deltaTime, entity, currentTargets, worldPosition);
}
- protected void Apply(float deltaTime, Entity entity, List targets, Vector2? worldPosition = null)
+ protected void Apply(float deltaTime, Entity entity, IEnumerable targets, Vector2? worldPosition = null)
{
if (lifeTime > 0)
{
@@ -685,16 +694,7 @@ namespace Barotrauma
if (duration > 0.0f)
{
- DurationListElement element = new DurationListElement
- {
- Parent = this,
- Timer = duration,
- Entity = entity,
- Targets = targets,
- User = user
- };
-
- DurationList.Add(element);
+ DurationList.Add(new DurationListElement(this, entity, targets, duration, user));
}
else
{
@@ -732,7 +732,7 @@ namespace Barotrauma
character.LastDamageSource = entity;
foreach (Limb limb in character.AnimController.Limbs)
{
- limb.character.DamageLimb(position, limb, new List() { multipliedAffliction }, stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: affliction.Source);
+ limb.character.DamageLimb(position, limb, multipliedAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: affliction.Source);
limb.character.TrySeverLimbJoints(limb, SeverLimbsProbability);
//only apply non-limb-specific afflictions to the first limb
if (!affliction.Prefab.LimbSpecific) { break; }
@@ -741,7 +741,7 @@ namespace Barotrauma
else if (target is Limb limb)
{
if (limb.character.Removed || limb.Removed) { continue; }
- limb.character.DamageLimb(position, limb, new List() { multipliedAffliction }, stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: affliction.Source);
+ limb.character.DamageLimb(position, limb, multipliedAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: affliction.Source);
limb.character.TrySeverLimbJoints(limb, SeverLimbsProbability);
}
}
@@ -853,7 +853,7 @@ namespace Barotrauma
ApplyProjSpecific(deltaTime, entity, targets, hull, position);
}
- partial void ApplyProjSpecific(float deltaTime, Entity entity, List targets, Hull currentHull, Vector2 worldPosition);
+ partial void ApplyProjSpecific(float deltaTime, Entity entity, IEnumerable targets, Hull currentHull, Vector2 worldPosition);
private void ApplyToProperty(ISerializableEntity target, SerializableProperty property, object value, float deltaTime)
{
@@ -934,12 +934,12 @@ namespace Barotrauma
if (target is Character character)
{
if (character.Removed) { continue; }
- character.AddDamage(character.WorldPosition, new List() { multipliedAffliction }, stun: 0.0f, playSound: false, attacker: element.User);
+ character.AddDamage(character.WorldPosition, multipliedAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attacker: element.User);
}
else if (target is Limb limb)
{
if (limb.character.Removed || limb.Removed) { continue; }
- limb.character.DamageLimb(limb.WorldPosition, limb, new List() { multipliedAffliction }, stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: element.User);
+ limb.character.DamageLimb(limb.WorldPosition, limb, multipliedAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: element.User);
}
}
diff --git a/Barotrauma/BarotraumaShared/SharedSource/TextManager.cs b/Barotrauma/BarotraumaShared/SharedSource/TextManager.cs
index 23b4288c2..7b96f06e4 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/TextManager.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/TextManager.cs
@@ -206,6 +206,12 @@ namespace Barotrauma
{
lock (mutex)
{
+ if (textPacks == null)
+ {
+ DebugConsole.ThrowError($"Failed to get the text \"{textTag}\" (no text packs loaded).");
+ return textTag;
+ }
+
if (!textPacks.ContainsKey(Language))
{
DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English...");
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs
index 6f31de524..69aaede2d 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs
@@ -118,6 +118,10 @@ namespace Barotrauma
for (int i = 0; i < subDirs.Length; i++)
{
+ if (i == subDirs.Length - 1 && string.IsNullOrEmpty(subDirs[i]))
+ {
+ break;
+ }
string enumPath = string.IsNullOrEmpty(filename) ? "./" : filename;
List filePaths = Directory.GetFileSystemEntries(enumPath).Select(s => Path.GetFileName(s)).ToList();
if (filePaths.Any(s => s.Equals(subDirs[i], StringComparison.Ordinal)))
@@ -571,5 +575,19 @@ namespace Barotrauma
#endif
return path;
}
+
+ public static float GetEasing(TransitionMode easing, float t)
+ {
+ return easing switch
+ {
+ TransitionMode.Smooth => MathUtils.SmoothStep(t),
+ TransitionMode.Smoother => MathUtils.SmootherStep(t),
+ TransitionMode.EaseIn => MathUtils.EaseIn(t),
+ TransitionMode.EaseOut => MathUtils.EaseOut(t),
+ TransitionMode.Exponential => t * t,
+ TransitionMode.Linear => t,
+ _ => t,
+ };
+ }
}
}
diff --git a/Barotrauma/BarotraumaShared/Submarines/Selkie.sub b/Barotrauma/BarotraumaShared/Submarines/Selkie.sub
index 4f355f37f..fa017357e 100644
Binary files a/Barotrauma/BarotraumaShared/Submarines/Selkie.sub and b/Barotrauma/BarotraumaShared/Submarines/Selkie.sub differ
diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt
index 4ab81f579..d812c454b 100644
--- a/Barotrauma/BarotraumaShared/changelog.txt
+++ b/Barotrauma/BarotraumaShared/changelog.txt
@@ -1,3 +1,97 @@
+---------------------------------------------------------------------------------------------------------
+v0.9.8.0
+---------------------------------------------------------------------------------------------------------
+
+Additions and changes:
+- Added husked Crawler.
+- Re-worked Crawler.
+- Moloch: Changed behavior. Added attacks on the tentacles. Fixed some behavior and targeting related issues.
+- Improved eating animations.
+- Rebind monster attack to "R", because the middle mouse button is now reserved for the command interface. Fixes attacking while controlling a husk or some other monster.
+- Calculate the direction from the limbs separately when attacking. Fixes some issues when an attack applies forces on multiple limbs.
+- Added damage protection on Hammerhead Matriarch's and Moloch's skirts/tentacles.
+- Changed the ai target calculations for characters.
+- Adjusted the AI targets for: navterminal, sonar, flashlight, diving suit, underwater scooter, flare, fire extinguisher, and many weapons.
+- Husk infection progress is now a bit different: The thresholds are different, and the damage is taken only in the final phase before turning.
+ The final phase now causes minor twitching in the body.
+- Husk faces are now only visible on fully turned (human) husks.
+- Fixed rapid turning into husk when taking a a lot of damage from husks.
+- Monsters with "flee health threshold" defined now only flee when they are being chased.
+- Adjusted Bonethresher's attacks.
+- Adjusted the AI priorities for Hammerhead Spawn, Husk, Tigerthresher, Moloch, Mudraptor.
+- Mudraptors don't flee anymore when on low health.
+- Improvements and fixes on the medic AI.
+- Add medical autonomous objectives for also other jobs than medics.
+- Improvements and fixes on the extinguish fires behavior.
+- The bots don't anymore report about low priority issues while fighting or fleeing. Also fixes bots always reporting theirself as the target for the required treatments.
+- Movement speed modifiers now adjust the swimming animations and steering forces too.
+- Removed deprecated Molochboss. The boss will be reimplemented later.
+- Forced aggressive behavior for creatures that are spawned as mission targets, make an exception with the Matriarch.
+- Pressing enter is not required after changing values in text fields in the sub editor.
+
+Modding:
+- Husk affliction mechanics now fully support different species.
+- Added "Visibility" parameter for the characters (similar to Noise).
+- Added "Attack Force Multiplier" for all limbs.
+- Implemented per joint and per limb scaling.
+- New LimbType: "Jaw". Used for eating animations.
+- Added "Attack Distance" parameter, which is currently only used for Aggressive and PassiveAggressive behaviors.
+- Added "Constant Angle" and "Constant Torque" for keeping a limb constantly rotated in a desired orientation.
+- Allow to define world forces for the root movement in three phases of the attack: start, middle, end. Set the main collider position to the main limb position when the attack ends to fix the rubberbanding behavior after the attacks.
+- Implemented "After Attack Delay": A property for adding a delay after the attack has hit the target, before starting to update the after attack behavior (e.g. falling back).
+- Renamed "AttackContext.NotDefined" to "AttackContext.Any".
+- Added "Retreat" parameter for attacks. When enabled, the character tries to steer away from the target while attacking it.
+- Accept the plural "requireditems" when overriding items.
+- Replaced "identifiers" with "items" on "RequiredItems" elements. Both strings are handled in code -> no functional difference. Fixed just for clarity.
+
+Bugfixes:
+- Fixes to crashes when autoupdating Workshop items during startup.
+- Fixes to disconnections with the error message "expected old event" when loading a round takes a long time.
+- Fixed crashing if the selected core content package contains errors (missing files, invalid XML files).
+- Fixed splash screen causing a crash on some Mac systems.
+- Fixed corrupted save files causing the game to crash during loading.
+- Fixed a memory leak in the health interface that may have caused crashes on very long game sessions.
+- Fixed clients being able to run the "enablecheats" command client-side without the permission to use the command.
+- Fixed AI inputs not being synced with clients, preventing clients from seeing when the bots aim/shoot/attack.
+- Don't allow using the "flipx" console command while playing online.
+- Fixed portrait area & health bar being clickable (despite being hidden) when grabbing another character.
+- Fixed a case of characters getting stuck facing one direction. Happened when you switched a character when the command interface was enabled.
+- Fixed previous order disappearing from the crew list on resolution change.
+- Fixed text not being colored according to message type (default/radio/pm/dead) in the chat input box.
+- Fixed "collection was modified" exception if drawing a subinventory causes highlighted inventory slots to change.
+- Fixed coilgun/railgun projectiles occasionally hitting the wall next to the turret when launched.
+- Fixed text overlays "vibrating" when using the Health Scanner HUD while the sub is moving.
+- Fixed level editor not saving level generation parameters that are inside an element.
+- Fixed job icons overflowing from the job selection panel when using mods that add more jobs.
+- Fixed clients being unable to cancel file transfers.
+- Fixed StatusEffects with a delay or a duration "transferring" to another target when the same effect is applied on another entity (for example when using the same stabilozine syringe on multiple characters).
+- Fixed particles spawned in StatusEffects only copying the emission angle of the parent item, but not the rotation when CopyEntityAngle is set to true.
+- Fixed terminals not sending any outputs server-side.
+- Fixed bots unequipping diving suits when they shouldn't. Only happened while doing a low prio objective.
+- Fixed retreating bots attacking with adhoc weapons like tools. If the bots retreat, they shouldn't use weapons at all.
+- Fixed the priority calculation for the rescue all objective. Should fix bots refusing to treat seriously injured characters and also fixes the priority of the targets.
+- Fixed occasional rapid flipping (usually noticeable only in debug draw mode) when a bot has no valid path or the path has ended.
+- Fixed bots sometimes not being able to climb ladders because they skip a node too early. Happened especially in Katrull.
+- Fixed some cases where bots fail to release the ladders when they should.
+- Fixed bots occasionally disgarding an autonomous objective with low priority after starting to follow it.
+- Fixed bots ignoring unsafe hulls (= any threats) when they shouldn't and consequently heading into danger.
+- Fixed some monster attacks causing internal damage instead of bitewounds.
+- Fixed Hammerhead Matriarch's head damage modifier ignoring bitewounds.
+- Fixed latched creatures not reacting on being damaged.
+- Fixed monsters flipping constantly while swimming.
+- Fixed Husks not regenerating health properly.
+- Fixed AI targets not being decreased on inactive items.
+- Fixed fire extinguisher definition (xml): the identifier and the tag shouldn't be the same string.
+- Fixed monsters sometimes moving slower than they should when walking inside.
+- Fixed charge indicator not being flipped in horizontally/vertically flipped batteries.
+- Fixed fabricators occasionally getting stuck to the active/inactive state client-side when the fabrication is paused due to insufficient power.
+- Fixed ancient weapon not cutting through holes in walls.
+- Fixed ambient light being brighter in the submarine editor than in-game.
+- Fixed walk speed not being affected by speed reductions.
+- Fixed all enemies ignoring the speed modifiers.
+- Fixed wires appearing as loose items on the floor after saving and reloading if they've been disconnected from both ends.
+- Fixed non-stackable status effects with a duration having no effect.
+
---------------------------------------------------------------------------------------------------------
v0.9.7.1
---------------------------------------------------------------------------------------------------------
diff --git a/Barotrauma/BarotraumaShared/liblinux_steam_env.so b/Barotrauma/BarotraumaShared/liblinux_steam_env.so
new file mode 100644
index 000000000..db4fdec39
Binary files /dev/null and b/Barotrauma/BarotraumaShared/liblinux_steam_env.so differ
diff --git a/Barotrauma/BarotraumaShared/steamclient.so b/Barotrauma/BarotraumaShared/steamclient.so
new file mode 100644
index 000000000..214fabf4d
Binary files /dev/null and b/Barotrauma/BarotraumaShared/steamclient.so differ
diff --git a/Libraries/XNATypes/Rectangle.cs b/Libraries/XNATypes/Rectangle.cs
index f8a73be11..c9c7b77a2 100644
--- a/Libraries/XNATypes/Rectangle.cs
+++ b/Libraries/XNATypes/Rectangle.cs
@@ -375,6 +375,15 @@ namespace Microsoft.Xna.Framework
Height += (int)verticalAmount * 2;
}
+ ///
+ /// Adjusts the edges of this by specified horizontal and vertical amounts.
+ ///
+ /// Value to adjust the edges.
+ public void Inflate(Vector2 amount)
+ {
+ Inflate(amount.X, amount.Y);
+ }
+
///
/// Gets whether or not the other intersects with this rectangle.
///
diff --git a/Libraries/linux_steam_env/linux_steam_env.c b/Libraries/linux_steam_env/linux_steam_env.c
new file mode 100644
index 000000000..149810be8
--- /dev/null
+++ b/Libraries/linux_steam_env/linux_steam_env.c
@@ -0,0 +1,7 @@
+#include
+
+void setLinuxEnv()
+{
+ putenv("SteamAppId=602960");
+ putenv("SteamGameId=602960");
+}
diff --git a/Libraries/webm_mem_playback/webm_mem_playback/Makefile.macos b/Libraries/webm_mem_playback/webm_mem_playback/Makefile.macos
new file mode 100644
index 000000000..f9c8759fa
--- /dev/null
+++ b/Libraries/webm_mem_playback/webm_mem_playback/Makefile.macos
@@ -0,0 +1,15 @@
+CXX=clang++
+CXXFLAGS= -std=c++11 -O3 -fPIC -Wall -I../libvpx_x64_macos -I../libwebm_x64_macos -I../opus/include
+
+
+webm_mem_playback_x64: Exports.o AudioDecoder.o Video.o
+ $(CXX) $(CXXFLAGS) -shared -L../opus_x64_macos/.libs/ -L../libvpx_x64_macos/ -L../libwebm_x64_macos/ -Wl -lopus -lvpx -lwebm -lpthread -Wl -o libwebm_mem_playback_x64.dylib Exports.o AudioDecoder.o Video.o
+
+Exports.o: Exports.cpp Exports.h Video.h
+ $(CXX) $(CXXFLAGS) -c Exports.cpp
+
+Video.o: Video.cpp AudioDecoder.h Video.h
+ $(CXX) $(CXXFLAGS) -c Video.cpp
+
+AudioDecoder.o: AudioDecoder.cpp AudioDecoder.h
+ $(CXX) $(CXXFLAGS) -c AudioDecoder.cpp
diff --git a/WindowsSolution.sln b/WindowsSolution.sln
index eb92b3c70..9c1bdeae7 100644
--- a/WindowsSolution.sln
+++ b/WindowsSolution.sln
@@ -40,6 +40,9 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpFont.NetStandard", "Libraries\SharpFont\Source\SharpFont\SharpFont.NetStandard.csproj", "{6911872D-40EF-400C-B0A1-9985A19ED488}"
EndProject
Global
+ GlobalSection(SharedMSBuildProjectFiles) = preSolution
+ Libraries\GameAnalytics\GA-SDK-MONO-SHARED\GA-SDK-MONO-SHARED.projitems*{95c4d59d-9be4-4278-b4f8-46c0ba1a3916}*SharedItemsImports = 5
+ EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Release|x64 = Release|x64
diff --git a/cleanup_obj.sh b/cleanup_obj.sh
deleted file mode 100644
index 15e419d4a..000000000
--- a/cleanup_obj.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/sh
-
-cd ..
-rm -rf Barotrauma/BarotraumaClient/obj/*
-rm -rf Barotrauma/BarotraumaServer/obj/*
-rm -rf Libraries/Concentus/CSharp/Concentus/obj*
-rm -rf Libraries/Facepunch.Steamworks/obj*
-rm -rf Libraries/Farseer\ Physics\ Engine\ 3.5/obj*
-rm -rf Libraries/Hyper.ComponentModel/obj*
-rm -rf Libraries/Lidgren.Network/obj*
-rm -rf Libraries/MonoGame.Framework/Src/MonoGame.Framework/obj*
-rm -rf Libraries/SharpFont/Source/SharpFont/obj*