(7ee8dbc11) v0.9.8.0

This commit is contained in:
Joonas Rikkonen
2020-03-31 15:11:41 +03:00
parent 3e99a49383
commit b647059b93
147 changed files with 2299 additions and 1297 deletions
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ShowAllFiles>true</ShowAllFiles>
</PropertyGroup>
</Project>
@@ -9,7 +9,7 @@ namespace Barotrauma
public void Draw(SpriteBatch spriteBatch) public void Draw(SpriteBatch spriteBatch)
{ {
if (!ShowAITargets) return; if (!ShowAITargets) { return; }
var pos = new Vector2(WorldPosition.X, -WorldPosition.Y); var pos = new Vector2(WorldPosition.X, -WorldPosition.Y);
if (soundRange > 0.0f) if (soundRange > 0.0f)
{ {
@@ -43,7 +43,7 @@ namespace Barotrauma
} }
else else
{ {
color = Color.WhiteSmoke; //color = Color.WhiteSmoke;
// disable the indicators for structures, because they clutter the debug view // disable the indicators for structures, because they clutter the debug view
return; return;
} }
@@ -17,7 +17,7 @@ namespace Barotrauma
if (State == AIState.Idle && PreviousState == AIState.Attack) if (State == AIState.Idle && PreviousState == AIState.Attack)
{ {
var target = _selectedAiTarget ?? _lastAiTarget; var target = _selectedAiTarget ?? _lastAiTarget;
if (target != null) if (target != null && target.Entity != null)
{ {
var memory = GetTargetMemory(target); var memory = GetTargetMemory(target);
Vector2 targetPos = memory.Location; Vector2 targetPos = memory.Location;
@@ -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) public override void DebugDraw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch)
{ {
Vector2 pos = Character.WorldPosition; Vector2 pos = Character.WorldPosition;
@@ -40,7 +35,7 @@ namespace Barotrauma
var currentOrder = ObjectiveManager.CurrentOrder; var currentOrder = ObjectiveManager.CurrentOrder;
if (currentOrder != null) 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) else if (ObjectiveManager.WaitTimer > 0)
{ {
@@ -51,17 +46,17 @@ namespace Barotrauma
{ {
if (currentOrder == null) 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) 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(); var activeObjective = ObjectiveManager.GetActiveObjective();
if (activeObjective != null) 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);
} }
} }
} }
@@ -638,8 +638,7 @@ namespace Barotrauma
public void Draw(SpriteBatch spriteBatch, Camera cam) public void Draw(SpriteBatch spriteBatch, Camera cam)
{ {
if (!Enabled) return; if (!Enabled) { return; }
AnimController.Draw(spriteBatch, cam); AnimController.Draw(spriteBatch, cam);
} }
@@ -656,8 +655,6 @@ namespace Barotrauma
if (GameMain.DebugDraw) if (GameMain.DebugDraw)
{ {
AnimController.DebugDraw(spriteBatch); AnimController.DebugDraw(spriteBatch);
if (aiTarget != null) aiTarget.Draw(spriteBatch);
} }
if (GUI.DisableHUD) return; if (GUI.DisableHUD) return;
@@ -92,7 +92,7 @@ namespace Barotrauma
if (!character.IsUnconscious && character.Stun <= 0.0f) 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; bool mouseOnPortrait = HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition) && GUI.MouseOn == null;
if (mouseOnPortrait && PlayerInput.PrimaryMouseButtonClicked()) if (mouseOnPortrait && PlayerInput.PrimaryMouseButtonClicked())
@@ -8,26 +8,25 @@ namespace Barotrauma
{ {
partial class AfflictionHusk : Affliction 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); GUI.AddMessage(TextManager.Get("HuskDormant"), GUI.Style.Red);
} break;
} case InfectionState.Transition:
else if (Strength < Prefab.MaxStrength)
{
if (state == InfectionState.Dormant && Character.Controlled == character)
{
GUI.AddMessage(TextManager.Get("HuskCantSpeak"), GUI.Style.Red); GUI.AddMessage(TextManager.Get("HuskCantSpeak"), GUI.Style.Red);
} break;
} case InfectionState.Active:
else if (state != InfectionState.Active && Character.Controlled == character) if (character.Params.UseHuskAppendage)
{ {
GUI.AddMessage(TextManager.GetWithVariable("HuskActivate", "[Attack]", GameMain.Config.KeyBindText(InputType.Attack)), GUI.AddMessage(TextManager.GetWithVariable("HuskActivate", "[Attack]", GameMain.Config.KeyBindText(InputType.Attack)), GUI.Style.Red);
GUI.Style.Red); }
break;
case InfectionState.Final:
default:
break;
} }
} }
} }
@@ -132,7 +132,7 @@ namespace Barotrauma
private List<HeartratePosition> heartratePositions; private List<HeartratePosition> heartratePositions;
private float currentHeartrateTime; private float currentHeartrateTime;
private float heartbeatTimer; private float heartbeatTimer;
private Texture2D heartrateFade; private static Texture2D heartrateFade;
private readonly HeartratePosition[] heartbeatPattern = 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"); 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); 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 (highlightedLimbIndex < 0 && selectedLimbIndex < 0)
{ {
// If no limb is selected or highlighted, select the one with the most critical afflictions. // If no limb is selected or highlighted, select the one with the most critical afflictions.
var affliction = GetAllAfflictions(a => a.Prefab.IndicatorLimb != LimbType.None) var affliction = SortAfflictionsBySeverity(GetAllAfflictions(a => a.Prefab.IndicatorLimb != LimbType.None)).FirstOrDefault();
.OrderByDescending(a => a.DamagePerSecond)
.ThenByDescending(a => a.Strength).FirstOrDefault();
if (affliction.DamagePerSecond > 0 || affliction.Strength > 0) if (affliction.DamagePerSecond > 0 || affliction.Strength > 0)
{ {
var limbHealth = GetMatchingLimbHealth(affliction); var limbHealth = GetMatchingLimbHealth(affliction);
@@ -1185,7 +1183,8 @@ namespace Barotrauma
Dictionary<string, float> treatmentSuitability = new Dictionary<string, float>(); Dictionary<string, float> treatmentSuitability = new Dictionary<string, float>();
GetSuitableTreatments(treatmentSuitability, normalize: true, randomization: randomVariance); 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; GUIButton buttonToSelect = null;
foreach (Affliction affliction in afflictions) foreach (Affliction affliction in afflictions)
@@ -1811,8 +1810,8 @@ namespace Barotrauma
float iconScale = 0.25f * scale; float iconScale = 0.25f * scale;
Vector2 iconPos = highlightArea.Center.ToVector2(); 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 (mostSevereAffliction != null) { DrawLimbAfflictionIcon(spriteBatch, mostSevereAffliction, iconScale, ref iconPos); }
if (thisAfflictions.Count() > 1) if (thisAfflictions.Count() > 1)
@@ -1992,6 +1991,9 @@ namespace Barotrauma
} }
} }
medUIExtra?.Remove();
medUIExtra = null;
limbIndicatorOverlay?.Remove(); limbIndicatorOverlay?.Remove();
limbIndicatorOverlay = null; limbIndicatorOverlay = null;
} }
@@ -220,9 +220,18 @@ namespace Barotrauma
} }
set 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<WearableSprite> otherWearablesWithHusk = new List<WearableSprite>() { HuskSprite }; List<WearableSprite> otherWearablesWithHusk = new List<WearableSprite>() { HuskSprite };
otherWearablesWithHusk.AddRange(OtherWearables); otherWearablesWithHusk.AddRange(OtherWearables);
@@ -235,7 +244,6 @@ namespace Barotrauma
UpdateWearableTypesToHide(); UpdateWearableTypesToHide();
} }
} }
enableHuskSprite = value;
} }
} }
@@ -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() public static void DequeueMessages()
{ {
while (queuedMessages.Count > 0) while (queuedMessages.Count > 0)
@@ -367,6 +395,7 @@ namespace Barotrauma
NewMessage("Steam achievements have been disabled during this play session.", Color.Red); NewMessage("Steam achievements have been disabled during this play session.", Color.Red);
#endif #endif
})); }));
AssignRelayToServer("enablecheats", true);
commands.Add(new Command("mainmenu|menu", "mainmenu/menu: Go to the main menu.", (string[] args) => 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) => 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(); Submarine.MainSub?.FlipX();
}, isCheat: true)); }, isCheat: true));
@@ -135,31 +135,33 @@ namespace Barotrauma
} }
} }
Color textColor = textBox.Color;
switch (command) switch (command)
{ {
case "r": case "r":
case "radio": case "radio":
textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Radio]; textColor = ChatMessage.MessageColor[(int)ChatMessageType.Radio];
break; break;
case "d": case "d":
case "dead": case "dead":
textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Dead]; textColor = ChatMessage.MessageColor[(int)ChatMessageType.Dead];
break; break;
default: default:
if (Character.Controlled != null && (Character.Controlled.IsDead || Character.Controlled.SpeechImpediment >= 100.0f)) 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 else if (command != "") //PMing
{ {
textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Private]; textColor = ChatMessage.MessageColor[(int)ChatMessageType.Private];
} }
else else
{ {
textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Default]; textColor = ChatMessage.MessageColor[(int)ChatMessageType.Default];
} }
break; break;
} }
textBox.TextColor = textBox.TextBlock.SelectedTextColor = textColor;
return true; return true;
} }
@@ -252,21 +254,29 @@ namespace Barotrauma
Visible = false, Visible = false,
CanBeFocused = false CanBeFocused = false
}; };
var senderText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), popupMsg.RectTransform, Anchor.TopRight), var content = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), popupMsg.RectTransform, Anchor.Center));
senderName, textColor: senderColor, font: GUI.SmallFont, textAlignment: Alignment.TopRight) 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 CanBeFocused = false
}; };
var msgPopupText = new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.0f), popupMsg.RectTransform, Anchor.TopRight) msgPopupText.RectTransform.MinSize = new Point(0, msgPopupText.Rect.Height);
{ AbsoluteOffset = new Point(0, senderText.Rect.Height) }, Vector2 msgSize = msgPopupText.Font.MeasureString(msgPopupText.WrappedText);
displayedText, textColor: message.Color, font: GUI.SmallFont, textAlignment: Alignment.TopRight, style: null, wrap: true) 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);
CanBeFocused = false popupMsg.RectTransform.IsFixedSize = true;
}; content.Recalculate();
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);
popupMessages.Enqueue(popupMsg); popupMessages.Enqueue(popupMsg);
} }
@@ -5,16 +5,6 @@ using System.Xml.Linq;
namespace Barotrauma namespace Barotrauma
{ {
public enum TransitionMode
{
Linear,
Smooth,
Smoother,
EaseIn,
EaseOut,
Exponential
}
public enum SpriteFallBackState public enum SpriteFallBackState
{ {
None, None,
@@ -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) 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; return blendedColor;
} }
@@ -598,7 +584,7 @@ namespace Barotrauma
foreach (UISprite uiSprite in previousSprites) foreach (UISprite uiSprite in previousSprites)
{ {
float alphaMultiplier = SpriteCrossFadeTime > 0 && (uiSprite.CrossFadeOut || currentSprites != null && currentSprites.Any(s => s.CrossFadeIn)) 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) if (alphaMultiplier > 0)
{ {
uiSprite.Draw(spriteBatch, rect, previousColor * alphaMultiplier, SpriteEffects); uiSprite.Draw(spriteBatch, rect, previousColor * alphaMultiplier, SpriteEffects);
@@ -612,7 +598,7 @@ namespace Barotrauma
foreach (UISprite uiSprite in currentSprites) foreach (UISprite uiSprite in currentSprites)
{ {
float alphaMultiplier = SpriteCrossFadeTime > 0 && (uiSprite.CrossFadeIn || previousSprites != null && previousSprites.Any(s => s.CrossFadeOut)) 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) if (alphaMultiplier > 0)
{ {
uiSprite.Draw(spriteBatch, rect, _currentColor * alphaMultiplier, SpriteEffects); uiSprite.Draw(spriteBatch, rect, _currentColor * alphaMultiplier, SpriteEffects);
@@ -95,6 +95,15 @@ namespace Barotrauma
set { button.TextColor = value; } 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) public void ReceiveTextInput(char inputChar)
{ {
GUI.KeyboardDispatcher.Subscriber = null; GUI.KeyboardDispatcher.Subscriber = null;
@@ -168,12 +177,11 @@ namespace Barotrauma
Anchor listAnchor = dropAbove ? Anchor.TopCenter : Anchor.BottomCenter; Anchor listAnchor = dropAbove ? Anchor.TopCenter : Anchor.BottomCenter;
Pivot listPivot = dropAbove ? Pivot.BottomCenter : Pivot.TopCenter; 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) listBox = new GUIListBox(new RectTransform(new Point(Rect.Width, Rect.Height * MathHelper.Clamp(elementCount, 2, 10)), rectT, listAnchor, listPivot)
{ IsFixedSize = false }, style: null) { IsFixedSize = false }, style: null)
{ {
Enabled = !selectMultiple, Enabled = !selectMultiple
OnSelected = SelectItem
}; };
if (!selectMultiple) { listBox.OnSelected = SelectItem; }
GUI.Style.Apply(listBox, "GUIListBox", this); GUI.Style.Apply(listBox, "GUIListBox", this);
GUI.Style.Apply(listBox.ContentBackground, "GUIListBox", this); GUI.Style.Apply(listBox.ContentBackground, "GUIListBox", this);
@@ -245,7 +253,7 @@ namespace Barotrauma
ToolTip = toolTip 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, UserData = userData,
ToolTip = toolTip, ToolTip = toolTip,
@@ -328,11 +328,7 @@ namespace Barotrauma
{ {
if (text == null) { return; } if (text == null) { return; }
censoredText = ""; censoredText = string.IsNullOrEmpty(text) ? "" : new string('\u2022', text.Length);
for (int i = 0; i < text.Length; i++)
{
censoredText += "\u2022";
}
var rect = Rect; var rect = Rect;
@@ -446,9 +442,10 @@ namespace Barotrauma
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
if (overflowClipActive) 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); 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); spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
} }
@@ -189,6 +189,11 @@ namespace Barotrauma
Instance = this; 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(); Config = new GameSettings();
Md5Hash.LoadCache(); Md5Hash.LoadCache();
@@ -421,17 +426,27 @@ namespace Barotrauma
GUI.Init(Window, Config.SelectedContentPackages, GraphicsDevice); GUI.Init(Window, Config.SelectedContentPackages, GraphicsDevice);
DebugConsole.Init(); 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(); ContentPackage.LoadAll();
Config.ReloadContentPackages(); Config.ReloadContentPackages();
} });
} }
}); }
if (SelectedPackages.None()) if (SelectedPackages.None())
@@ -330,17 +330,6 @@ namespace Barotrauma
} }
AddCharacterToCrewList(character); 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) public void AddCharacterInfo(CharacterInfo characterInfo)
@@ -788,11 +777,11 @@ namespace Barotrauma
characterFrame.SetAsFirstChild(); 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( var prevOrderFrame = new GUIButton(
new RectTransform( new RectTransform(
characterComponent.GetChildByUserData("job").RectTransform.RelativeSize, characterComponent.GetChildByUserData("job").RectTransform.RelativeSize,
@@ -835,12 +824,12 @@ namespace Barotrauma
private GUIComponent GetCurrentOrderComponent(GUILayoutGroup characterComponent) 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) 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 private struct OrderInfo
@@ -910,7 +899,18 @@ namespace Barotrauma
{ {
if (!(c.UserData is Character character) || character.IsDead || character.Removed) { continue; } if (!(c.UserData is Character character) || character.IsDead || character.Removed) { continue; }
AddCharacter(character); AddCharacter(character);
DisplayCharacterOrder(character, character.CurrentOrder, (character.AIController as HumanAIController)?.CurrentOrderOption); if (c.GetChild<GUILayoutGroup>() 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<GUILayoutGroup>() is GUILayoutGroup newLayoutGroup)
{
DisplayPreviousCharacterOrder(character, newLayoutGroup, prevInfo);
}
}
} }
} }
@@ -944,6 +944,7 @@ namespace Barotrauma
{ {
Character.Controlled.AIController.ObjectiveManager.WaitTimer = CharacterWaitOnSwitch; Character.Controlled.AIController.ObjectiveManager.WaitTimer = CharacterWaitOnSwitch;
} }
DisableCommandUI();
Character.Controlled = character; Character.Controlled = character;
} }
@@ -1176,19 +1177,22 @@ namespace Barotrauma
if (PlayerInput.KeyHit(InputType.RadioChat) && !ChatBox.InputBox.Selected) if (PlayerInput.KeyHit(InputType.RadioChat) && !ChatBox.InputBox.Selected)
{ {
ChatBox.InputBox.AddToGUIUpdateList(); if (Character.Controlled == null || Character.Controlled.SpeechImpediment < 100)
ChatBox.GUIFrame.Flash(Color.YellowGreen, 0.5f);
if (!ChatBox.ToggleOpen)
{ {
ChatBox.CloseAfterMessageSent = !ChatBox.ToggleOpen; ChatBox.InputBox.AddToGUIUpdateList();
ChatBox.ToggleOpen = true; ChatBox.GUIFrame.Flash(Color.YellowGreen, 0.5f);
} if (!ChatBox.ToggleOpen)
{
ChatBox.CloseAfterMessageSent = !ChatBox.ToggleOpen;
ChatBox.ToggleOpen = true;
}
if (!ChatBox.InputBox.Text.StartsWith(ChatBox.RadioChatString)) if (!ChatBox.InputBox.Text.StartsWith(ChatBox.RadioChatString))
{ {
ChatBox.InputBox.Text = 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 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; return Character.Controlled != null && Character.Controlled.SpeechImpediment < 100.0f;
#endif
} }
} }
private bool CanSomeoneHearCharacter() private bool CanSomeoneHearCharacter()
{ {
#if DEBUG
return true;
#else
return Character.Controlled != null && characters.Any(c => c != Character.Controlled && c.CanHearCharacter(Character.Controlled)); return Character.Controlled != null && characters.Any(c => c != Character.Controlled && c.CanHearCharacter(Character.Controlled));
#endif
} }
private void CreateCommandUI(Character characterContext = null) private void CreateCommandUI(Character characterContext = null)
@@ -1505,17 +1517,7 @@ namespace Barotrauma
shortcutCenterNode = null; shortcutCenterNode = null;
} }
CreateNodes(userData); CreateNodes(userData);
if (returnNode != null && returnNode.Visible) CreateReturnNodeHotkey();
{
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;
}
return true; return true;
} }
@@ -1539,10 +1541,20 @@ namespace Barotrauma
returnNode = null; returnNode = null;
} }
CreateNodes(userData); CreateNodes(userData);
CreateReturnNodeHotkey();
return true;
}
private void CreateReturnNodeHotkey()
{
if (returnNode != null && returnNode.Visible) if (returnNode != null && returnNode.Visible)
{ {
var hotkey = optionNodes.Count + 1; var hotkey = 1;
if (expandNode != null && expandNode.Visible) { hotkey += 1; } if (targetFrame == null || !targetFrame.Visible)
{
hotkey = optionNodes.Count + 1;
if (expandNode != null && expandNode.Visible) { hotkey += 1; }
}
CreateHotkeyIcon(returnNode.RectTransform, hotkey % 10, true); CreateHotkeyIcon(returnNode.RectTransform, hotkey % 10, true);
returnNodeHotkey = Keys.D0 + hotkey % 10; returnNodeHotkey = Keys.D0 + hotkey % 10;
} }
@@ -1550,7 +1562,6 @@ namespace Barotrauma
{ {
returnNodeHotkey = Keys.None; returnNodeHotkey = Keys.None;
} }
return true;
} }
private void SetCenterNode(GUIButton node) private void SetCenterNode(GUIButton node)
@@ -2148,7 +2159,11 @@ namespace Barotrauma
ToolTip = character.Info.DisplayName + " (" + character.Info.Job.Name + ")" ToolTip = character.Info.DisplayName + " (" + character.Info.Job.Name + ")"
}; };
#if DEBUG
bool canHear = true;
#else
bool canHear = character.CanHearCharacter(Character.Controlled); bool canHear = character.CanHearCharacter(Character.Controlled);
#endif
if (!canHear) if (!canHear)
{ {
node.CanBeFocused = icon.CanBeFocused = false; node.CanBeFocused = icon.CanBeFocused = false;
@@ -2271,8 +2286,14 @@ namespace Barotrauma
private Character GetBestCharacterForOrder(Order order) private Character GetBestCharacterForOrder(Order order)
{ {
#if !DEBUG
if (Character.Controlled == null) { return null; } 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) .OrderByDescending(c => c.CurrentOrder == null || c.CurrentOrder.Identifier == dismissedOrderPrefab.Identifier)
.ThenByDescending(c => order.HasAppropriateJob(c)) .ThenByDescending(c => order.HasAppropriateJob(c))
.ThenBy(c => c.CurrentOrder?.Weight) .ThenBy(c => c.CurrentOrder?.Weight)
@@ -2281,16 +2302,18 @@ namespace Barotrauma
private List<Character> GetCharactersSortedForOrder(Order order) private List<Character> GetCharactersSortedForOrder(Order order)
{ {
#if !DEBUG
if (Character.Controlled == null) { return new List<Character>(); } if (Character.Controlled == null) { return new List<Character>(); }
#endif
if (order.Identifier == "follow") 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) .OrderByDescending(c => c.CurrentOrder == null || c.CurrentOrder.Identifier == dismissedOrderPrefab.Identifier)
.ToList(); .ToList();
} }
else 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) .OrderByDescending(c => c.CurrentOrder == null || c.CurrentOrder.Identifier == dismissedOrderPrefab.Identifier)
.ThenByDescending(c => order.HasAppropriateJob(c)) .ThenByDescending(c => order.HasAppropriateJob(c))
.ThenBy(c => c.CurrentOrder?.Weight) .ThenBy(c => c.CurrentOrder?.Weight)
@@ -2298,9 +2321,9 @@ namespace Barotrauma
} }
} }
#endregion #endregion
#endregion #endregion
/// <summary> /// <summary>
/// Creates a listbox that includes all the characters in the crew, can be used externally (round info menus etc) /// 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; return true;
} }
#region Reports #region Reports
/// <summary> /// <summary>
/// Enables/disables report buttons when needed /// Enables/disables report buttons when needed
@@ -2447,7 +2470,7 @@ namespace Barotrauma
} }
} }
#endregion #endregion
public void InitSinglePlayerRound() public void InitSinglePlayerRound()
{ {
@@ -91,7 +91,7 @@ namespace Barotrauma
if (Character.Controlled.Submarine != outpost) { return null; } if (Character.Controlled.Submarine != outpost) { return null; }
//if there's a sub docked to the outpost, we can leave the level //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(); var dockedSub = outpost.DockedTo.FirstOrDefault();
return dockedSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : dockedSub; return dockedSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : dockedSub;
@@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
using Barotrauma.Networking; using Barotrauma.Networking;
@@ -221,7 +222,7 @@ namespace Barotrauma.Tutorials
{ {
//captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f); //captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f);
yield return new WaitForSeconds(1.0f, false); yield return new WaitForSeconds(1.0f, false);
} while (Submarine.MainSub.DockedTo.Count > 0); } while (Submarine.MainSub.DockedTo.Any());
RemoveCompletedObjective(segments[4]); RemoveCompletedObjective(segments[4]);
yield return new WaitForSeconds(2f, false); yield return new WaitForSeconds(2f, false);
TriggerTutorialSegment(5); // Navigate to destination TriggerTutorialSegment(5); // Navigate to destination
@@ -245,7 +246,7 @@ namespace Barotrauma.Tutorials
{ {
//captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f); //captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f);
yield return new WaitForSeconds(1.0f, false); 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]); RemoveCompletedObjective(segments[6]);
yield return new WaitForSeconds(3f, false); yield return new WaitForSeconds(3f, false);
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.GetWithVariable("Captain.Radio.Complete", "[OUTPOSTNAME]", GameMain.GameSession.EndLocation.Name), ChatMessageType.Radio, null); GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.GetWithVariable("Captain.Radio.Complete", "[OUTPOSTNAME]", GameMain.GameSession.EndLocation.Name), ChatMessageType.Radio, null);
@@ -13,6 +13,7 @@ namespace Barotrauma
private GUIFrame infoFrameContent; private GUIFrame infoFrameContent;
public RoundSummary RoundSummary { get; private set; } public RoundSummary RoundSummary { get; private set; }
public static bool IsInfoFrameOpen => GameMain.GameSession?.infoFrame != null;
private bool ToggleInfoFrame() private bool ToggleInfoFrame()
{ {
@@ -47,7 +47,7 @@ namespace Barotrauma
{ {
keyMapping = new KeyOrMouse[Enum.GetNames(typeof(InputType)).Length]; keyMapping = new KeyOrMouse[Enum.GetNames(typeof(InputType)).Length];
keyMapping[(int)InputType.Run] = new KeyOrMouse(Keys.LeftShift); 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.Crouch] = new KeyOrMouse(Keys.LeftControl);
keyMapping[(int)InputType.Grab] = new KeyOrMouse(Keys.G); keyMapping[(int)InputType.Grab] = new KeyOrMouse(Keys.G);
keyMapping[(int)InputType.Health] = new KeyOrMouse(Keys.H); keyMapping[(int)InputType.Health] = new KeyOrMouse(Keys.H);
@@ -154,7 +154,7 @@ namespace Barotrauma.Items.Components
}; };
// === POWER WARNING === // // === POWER WARNING === //
inSufficientPowerWarning = new GUITextBlock(new RectTransform(Vector2.One, activateButton.RectTransform), 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, HoverColor = Color.Black,
IgnoreLayoutGroups = true, IgnoreLayoutGroups = true,
@@ -584,15 +584,20 @@ namespace Barotrauma.Items.Components
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime) 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); int itemIndex = msg.ReadRangedInteger(-1, fabricationRecipes.Count - 1);
UInt16 userID = msg.ReadUInt16(); UInt16 userID = msg.ReadUInt16();
Character user = Entity.FindEntityByID(userID) as Character; 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(); CancelFabricating();
} }
else else if (newState == FabricatorState.Active || newState == FabricatorState.Paused)
{ {
//if already fabricating the selected item, return //if already fabricating the selected item, return
if (fabricatedItem != null && fabricationRecipes.IndexOf(fabricatedItem) == itemIndex) { return; } if (fabricatedItem != null && fabricationRecipes.IndexOf(fabricatedItem) == itemIndex) { return; }
@@ -490,7 +490,7 @@ namespace Barotrauma.Items.Components
disruptedDirections.Clear(); disruptedDirections.Clear();
foreach (AITarget t in AITarget.List) 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); float distSqr = Vector2.DistanceSquared(t.WorldPosition, transducerCenter);
if (distSqr > t.SoundRange * t.SoundRange * 2) { continue; } if (distSqr > t.SoundRange * t.SoundRange * 2) { continue; }
@@ -665,7 +665,7 @@ namespace Barotrauma.Items.Components
if (Vector2.DistanceSquared(PlayerInput.MousePosition, steerArea.Rect.Center.ToVector2()) < steerRadius * steerRadius) 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; Vector2 displaySubPos = (-sonar.DisplayOffset * sonar.Zoom) / sonar.Range * sonar.DisplayRadius * sonar.Zoom;
displaySubPos.Y = -displaySubPos.Y; displaySubPos.Y = -displaySubPos.Y;
@@ -106,11 +106,10 @@ namespace Barotrauma.Items.Components
{ {
if (indicatorSize.X <= 1.0f || indicatorSize.Y <= 1.0f) { return; } if (indicatorSize.X <= 1.0f || indicatorSize.Y <= 1.0f) { return; }
GUI.DrawRectangle(spriteBatch, Vector2 itemSize = new Vector2(item.Sprite.SourceRect.Width, item.Sprite.SourceRect.Height) * item.Scale;
new Vector2( Vector2 indicatorPos = -itemSize / 2 + indicatorPosition * item.Scale;
item.DrawPosition.X - item.Sprite.SourceRect.Width / 2 * item.Scale + indicatorPosition.X * item.Scale, if (item.FlippedX && item.Prefab.CanSpriteFlipX) { indicatorPos.X = -indicatorPos.X - indicatorSize.X * item.Scale; }
-item.DrawPosition.Y - item.Sprite.SourceRect.Height / 2 * item.Scale + indicatorPosition.Y * item.Scale), if (item.FlippedY && item.Prefab.CanSpriteFlipY) { indicatorPos.Y = -indicatorPos.Y - indicatorSize.Y * item.Scale; }
indicatorSize * item.Scale, Color.Black, depth: item.SpriteDepth - 0.00001f);
if (charge > 0) if (charge > 0)
{ {
@@ -118,25 +117,23 @@ namespace Barotrauma.Items.Components
if (!isHorizontal) if (!isHorizontal)
{ {
GUI.DrawRectangle(spriteBatch, GUI.DrawRectangle(spriteBatch,
new Vector2( new Vector2(item.DrawPosition.X, -item.DrawPosition.Y + ((indicatorSize.Y * item.Scale) * (1.0f - charge / capacity))) + indicatorPos,
item.DrawPosition.X - item.Sprite.SourceRect.Width / 2 * item.Scale + indicatorPosition.X * item.Scale + 1, new Vector2(indicatorSize.X * item.Scale, (indicatorSize.Y * item.Scale) * (charge / capacity)), indicatorColor, true,
-item.DrawPosition.Y - item.Sprite.SourceRect.Height / 2 * item.Scale + indicatorPosition.Y * item.Scale + 1 + ((indicatorSize.Y * item.Scale) * (1.0f - charge / capacity))), depth: item.SpriteDepth - 0.00001f);
new Vector2(indicatorSize.X * item.Scale - 2, (indicatorSize.Y * item.Scale - 2) * (charge / capacity)), indicatorColor, true,
depth: item.SpriteDepth - 0.00001f);
} }
else else
{ {
GUI.DrawRectangle(spriteBatch, GUI.DrawRectangle(spriteBatch,
new Vector2( new Vector2(item.DrawPosition.X, -item.DrawPosition.Y) + indicatorPos,
item.DrawPosition.X - item.Sprite.SourceRect.Width / 2 * item.Scale + indicatorPosition.X * item.Scale + 1 , new Vector2((indicatorSize.X * item.Scale) * (charge / capacity), indicatorSize.Y * item.Scale), indicatorColor, true,
-item.DrawPosition.Y - item.Sprite.SourceRect.Height / 2 * item.Scale + indicatorPosition.Y * item.Scale + 1), depth: item.SpriteDepth - 0.00001f);
new Vector2((indicatorSize.X * item.Scale - 2) * (charge / capacity), indicatorSize.Y * item.Scale - 2), 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) public void ClientWrite(IWriteMessage msg, object[] extraData)
{ {
msg.WriteRangedInteger((int)(rechargeSpeed / MaxRechargeSpeed * 10), 0, 10); msg.WriteRangedInteger((int)(rechargeSpeed / MaxRechargeSpeed * 10), 0, 10);
@@ -142,7 +142,7 @@ namespace Barotrauma.Items.Components
private void DrawCharacterInfo(SpriteBatch spriteBatch, Character target, float alpha = 1.0f) 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; hudPos += Vector2.UnitX * 50.0f;
List<string> texts = new List<string>(); List<string> texts = new List<string>();
@@ -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)." + 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") + " Submarine: " + (item.Submarine?.Name ?? "null") +
", target submarine: " + (DockingTarget.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."; errorMsg += "\nAlready docked.";
} }
@@ -284,6 +284,7 @@ namespace Barotrauma
} }
protected static HashSet<SlotReference> highlightedSubInventorySlots = new HashSet<SlotReference>(); protected static HashSet<SlotReference> highlightedSubInventorySlots = new HashSet<SlotReference>();
private static List<SlotReference> subInventorySlotsToDraw = new List<SlotReference>();
protected static SlotReference selectedSlot; protected static SlotReference selectedSlot;
@@ -1048,11 +1049,14 @@ namespace Barotrauma
return hoverArea; return hoverArea;
} }
public static void DrawFront(SpriteBatch spriteBatch) 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); int slotIndex = Array.IndexOf(slot.ParentInventory.slots, slot.Slot);
if (slotIndex > -1 && slotIndex < slot.ParentInventory.slots.Length) if (slotIndex > -1 && slotIndex < slot.ParentInventory.slots.Length)
@@ -333,15 +333,6 @@ namespace Barotrauma
if (GameMain.DebugDraw) if (GameMain.DebugDraw)
{ {
aiTarget?.Draw(spriteBatch);
var containedItems = ContainedItems;
if (containedItems != null)
{
foreach (Item item in containedItems)
{
item.AiTarget?.Draw(spriteBatch);
}
}
if (body != null) if (body != null)
{ {
body.DebugDraw(spriteBatch, Color.White); body.DebugDraw(spriteBatch, Color.White);
@@ -248,8 +248,6 @@ namespace Barotrauma
if (!editing && !GameMain.DebugDraw) return; if (!editing && !GameMain.DebugDraw) return;
if (aiTarget != null) aiTarget.Draw(spriteBatch);
Rectangle drawRect = Rectangle drawRect =
Submarine == null ? rect : new Rectangle((int)(Submarine.DrawPosition.X + rect.X), (int)(Submarine.DrawPosition.Y + rect.Y), rect.Width, rect.Height); Submarine == null ? rect : new Rectangle((int)(Submarine.DrawPosition.X + rect.X), (int)(Submarine.DrawPosition.Y + rect.Y), rect.Width, rect.Height);
@@ -4,6 +4,7 @@ using Barotrauma.SpriteDeformations;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
namespace Barotrauma namespace Barotrauma
@@ -120,7 +121,7 @@ namespace Barotrauma
SerializableProperty.SerializeProperties(this, element); SerializableProperty.SerializeProperties(this, element);
foreach (XElement subElement in element.Elements()) foreach (XElement subElement in element.Elements().ToList())
{ {
switch (subElement.Name.ToString().ToLowerInvariant()) switch (subElement.Name.ToString().ToLowerInvariant())
{ {
@@ -139,7 +140,7 @@ namespace Barotrauma
break; break;
} }
} }
foreach (LightSourceParams lightSourceParams in LightSourceParams) foreach (LightSourceParams lightSourceParams in LightSourceParams)
{ {
var lightElement = new XElement("LightSource"); var lightElement = new XElement("LightSource");
@@ -12,10 +12,10 @@ namespace Barotrauma
{ {
public override void Draw(SpriteBatch spriteBatch, bool editing, bool back = true) 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; Color color = IsHighlighted ? GUI.Style.Orange : GUI.Style.Green;
if (IsSelected) color = GUI.Style.Red; if (IsSelected) { color = GUI.Style.Red; }
Vector2 pos = Position; 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.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); GUI.DrawLine(spriteBatch, pos + Vector2.UnitX * 50.0f, pos - Vector2.UnitX * 50.0f, color, 0.0f, 5);
Rectangle drawRect = rect; if (!Item.ShowLinks) { return; }
drawRect.Y = -rect.Y;
GUI.DrawRectangle(spriteBatch, drawRect, GUI.Style.Red, true);
if (!Item.ShowLinks) return;
foreach (MapEntity e in linkedTo) foreach (MapEntity e in linkedTo)
{ {
@@ -350,8 +350,6 @@ namespace Barotrauma
} }
} }
} }
AiTarget?.Draw(spriteBatch);
} }
} }
@@ -222,7 +222,7 @@ namespace Barotrauma
GUI.DrawRectangle(spriteBatch, worldBorders, Color.White, false, 0, 5); 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); Vector2 prevPos = ConvertUnits.ToDisplayUnits(sub.subBody.PositionBuffer[0].Position);
prevPos.Y = -prevPos.Y; prevPos.Y = -prevPos.Y;
@@ -15,6 +15,7 @@ namespace Barotrauma.Networking
public byte ID; public byte ID;
public UInt16 CharacterID; public UInt16 CharacterID;
public bool Muted; public bool Muted;
public bool InGame;
public bool AllowKicking; public bool AllowKicking;
} }
@@ -8,6 +8,7 @@ using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
namespace Barotrauma.Networking namespace Barotrauma.Networking
{ {
@@ -60,6 +61,19 @@ namespace Barotrauma.Networking
private bool connected; private bool connected;
private enum RoundInitStatus
{
NotStarted,
Starting,
WaitingForStartGameFinalize,
Started,
TimedOut,
Error,
Interrupted
}
private RoundInitStatus roundInitStatus = RoundInitStatus.NotStarted;
private byte myID; private byte myID;
private List<Client> otherClients; private List<Client> otherClients;
@@ -148,6 +162,8 @@ namespace Barotrauma.Networking
this.ownerKey = ownerKey; this.ownerKey = ownerKey;
this.steamP2POwner = steamP2POwner; this.steamP2POwner = steamP2POwner;
roundInitStatus = RoundInitStatus.NotStarted;
allowReconnect = true; allowReconnect = true;
netStats = new NetStats(); netStats = new NetStats();
@@ -558,6 +574,13 @@ namespace Barotrauma.Networking
try try
{ {
incomingMessagesToProcess.Clear();
incomingMessagesToProcess.AddRange(pendingIncomingMessages);
foreach (var inc in incomingMessagesToProcess)
{
ReadDataMessage(inc);
}
pendingIncomingMessages.Clear();
clientPeer?.Update(deltaTime); clientPeer?.Update(deltaTime);
} }
catch (Exception e) catch (Exception e)
@@ -632,11 +655,23 @@ namespace Barotrauma.Networking
} }
} }
private CoroutineHandle startGameCoroutine; private readonly List<IReadMessage> pendingIncomingMessages = new List<IReadMessage>();
private readonly List<IReadMessage> incomingMessagesToProcess = new List<IReadMessage>();
private void ReadDataMessage(IReadMessage inc) private void ReadDataMessage(IReadMessage inc)
{ {
ServerPacketHeader header = (ServerPacketHeader)inc.ReadByte(); 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) switch (header)
{ {
case ServerPacketHeader.UPDATE_LOBBY: case ServerPacketHeader.UPDATE_LOBBY:
@@ -714,7 +749,13 @@ namespace Barotrauma.Networking
} }
break; break;
case ServerPacketHeader.STARTGAME: 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; break;
case ServerPacketHeader.ENDGAME: case ServerPacketHeader.ENDGAME:
string endMessage = inc.ReadString(); string endMessage = inc.ReadString();
@@ -725,6 +766,8 @@ namespace Barotrauma.Networking
GameMain.GameSession.WinningTeam = winningTeam; GameMain.GameSession.WinningTeam = winningTeam;
GameMain.GameSession.Mission.Completed = true; GameMain.GameSession.Mission.Completed = true;
} }
roundInitStatus = RoundInitStatus.Interrupted;
CoroutineManager.StartCoroutine(EndGame(endMessage), "EndGame"); CoroutineManager.StartCoroutine(EndGame(endMessage), "EndGame");
break; break;
case ServerPacketHeader.CAMPAIGN_SETUP_INFO: case ServerPacketHeader.CAMPAIGN_SETUP_INFO:
@@ -775,7 +818,37 @@ namespace Barotrauma.Networking
break; break;
} }
} }
private void ReadStartGameFinalize(IReadMessage inc)
{
ushort contentToPreloadCount = inc.ReadUInt16();
List<ContentFile> contentToPreload = new List<ContentFile>();
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() private void OnDisconnect()
{ {
if (SteamManager.IsInitialized) if (SteamManager.IsInitialized)
@@ -884,6 +957,7 @@ namespace Barotrauma.Networking
} }
else else
{ {
connected = false;
connectCancelled = true; connectCancelled = true;
string msg = ""; string msg = "";
@@ -1108,6 +1182,7 @@ namespace Barotrauma.Networking
if (Character != null) Character.Remove(); if (Character != null) Character.Remove();
HasSpawned = false; HasSpawned = false;
eventErrorWritten = false; eventErrorWritten = false;
GameMain.NetLobbyScreen.StopWaitingForStartRound();
while (CoroutineManager.IsCoroutineRunning("EndGame")) while (CoroutineManager.IsCoroutineRunning("EndGame"))
{ {
@@ -1126,9 +1201,11 @@ namespace Barotrauma.Networking
EndVoteTickBox.Selected = false; EndVoteTickBox.Selected = false;
roundInitStatus = RoundInitStatus.Starting;
int seed = inc.ReadInt32(); int seed = inc.ReadInt32();
string levelSeed = inc.ReadString(); string levelSeed = inc.ReadString();
int levelEqualityCheckVal = inc.ReadInt32(); //int levelEqualityCheckVal = inc.ReadInt32();
float levelDifficulty = inc.ReadSingle(); float levelDifficulty = inc.ReadSingle();
byte losMode = inc.ReadByte(); byte losMode = inc.ReadByte();
@@ -1146,24 +1223,16 @@ namespace Barotrauma.Networking
int missionIndex = inc.ReadInt16(); int missionIndex = inc.ReadInt16();
bool respawnAllowed = inc.ReadBoolean(); bool respawnAllowed = inc.ReadBoolean();
bool loadSecondSub = inc.ReadBoolean();
bool disguisesAllowed = inc.ReadBoolean(); bool disguisesAllowed = inc.ReadBoolean();
bool rewiringAllowed = inc.ReadBoolean(); bool rewiringAllowed = inc.ReadBoolean();
bool allowRagdollButton = inc.ReadBoolean(); bool allowRagdollButton = inc.ReadBoolean();
ushort contentToPreloadCount = inc.ReadUInt16();
List<ContentFile> contentToPreload = new List<ContentFile>();
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); serverSettings.ReadMonsterEnabled(inc);
bool includesFinalize = inc.ReadBoolean(); inc.ReadPadBits();
GameModePreset gameMode = GameModePreset.List.Find(gm => gm.Identifier == modeIdentifier); GameModePreset gameMode = GameModePreset.List.Find(gm => gm.Identifier == modeIdentifier);
MultiPlayerCampaign campaign = MultiPlayerCampaign campaign =
GameMain.NetLobbyScreen.SelectedMode == GameMain.GameSession?.GameMode.Preset && gameMode == GameMain.NetLobbyScreen.SelectedMode ? GameMain.NetLobbyScreen.SelectedMode == GameMain.GameSession?.GameMode.Preset && gameMode == GameMain.NetLobbyScreen.SelectedMode ?
@@ -1237,21 +1306,105 @@ namespace Barotrauma.Networking
yield return CoroutineStatus.Failure; yield return CoroutineStatus.Failure;
} }
MissionPrefab missionPrefab = missionIndex < 0 ? null : MissionPrefab.List[missionIndex];
GameMain.GameSession = missionIndex < 0 ? GameMain.GameSession = missionIndex < 0 ?
new GameSession(GameMain.NetLobbyScreen.SelectedSub, "", gameMode, MissionType.None) : new GameSession(GameMain.NetLobbyScreen.SelectedSub, "", gameMode, MissionType.None) :
new GameSession(GameMain.NetLobbyScreen.SelectedSub, "", gameMode, MissionPrefab.List[missionIndex]); new GameSession(GameMain.NetLobbyScreen.SelectedSub, "", gameMode, missionPrefab);
GameMain.GameSession.StartRound(levelSeed, levelDifficulty, loadSecondSub);
//startRoundTask = Task.Run(async () => { await Task.Yield(); GameMain.GameSession.StartRound(levelSeed, levelDifficulty); });
GameMain.GameSession.StartRound(levelSeed, levelDifficulty);
} }
else else
{ {
if (GameMain.GameSession?.CrewManager != null) GameMain.GameSession.CrewManager.Reset(); 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, GameMain.GameSession.StartRound(campaign.Map.SelectedConnection.Level,
reloadSub: true, reloadSub: true,
loadSecondSub: false, mirrorLevel: campaign.Map.CurrentLocation != campaign.Map.SelectedConnection.Locations[0]);
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) if (GameMain.GameSession.Submarine.IsFileCorrupted)
{ {
@@ -1261,7 +1414,7 @@ namespace Barotrauma.Networking
for (int i = 0; i < Submarine.MainSubs.Length; i++) 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; var teamID = i == 0 ? Character.TeamType.Team1 : Character.TeamType.Team2;
Submarine.MainSubs[i].TeamID = teamID; 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); } if (respawnAllowed) { respawnManager = new RespawnManager(this, GameMain.NetLobbyScreen.UsingShuttle ? GameMain.NetLobbyScreen.SelectedShuttle : null); }
GameMain.GameSession.EventManager.PreloadContent(contentToPreload);
ServerSettings.ServerDetailsChanged = true;
gameStarted = true; gameStarted = true;
ServerSettings.ServerDetailsChanged = true;
GameMain.GameScreen.Select(); GameMain.GameScreen.Select();
@@ -1394,6 +1534,7 @@ namespace Barotrauma.Networking
string preferredJob = inc.ReadString(); string preferredJob = inc.ReadString();
UInt16 characterID = inc.ReadUInt16(); UInt16 characterID = inc.ReadUInt16();
bool muted = inc.ReadBoolean(); bool muted = inc.ReadBoolean();
bool inGame = inc.ReadBoolean();
bool allowKicking = inc.ReadBoolean(); bool allowKicking = inc.ReadBoolean();
inc.ReadPadBits(); inc.ReadPadBits();
@@ -1406,6 +1547,7 @@ namespace Barotrauma.Networking
PreferredJob = preferredJob, PreferredJob = preferredJob,
CharacterID = characterID, CharacterID = characterID,
Muted = muted, Muted = muted,
InGame = inGame,
AllowKicking = allowKicking AllowKicking = allowKicking
}); });
} }
@@ -1424,6 +1566,7 @@ namespace Barotrauma.Networking
{ {
SteamID = tc.SteamID, SteamID = tc.SteamID,
Muted = tc.Muted, Muted = tc.Muted,
InGame = tc.InGame,
AllowKicking = tc.AllowKicking AllowKicking = tc.AllowKicking
}; };
ConnectedClients.Add(existingClient); ConnectedClients.Add(existingClient);
@@ -1433,15 +1576,12 @@ namespace Barotrauma.Networking
existingClient.PreferredJob = tc.PreferredJob; existingClient.PreferredJob = tc.PreferredJob;
existingClient.Character = null; existingClient.Character = null;
existingClient.Muted = tc.Muted; existingClient.Muted = tc.Muted;
existingClient.InGame = tc.InGame;
existingClient.AllowKicking = tc.AllowKicking; existingClient.AllowKicking = tc.AllowKicking;
GameMain.NetLobbyScreen.SetPlayerNameAndJobPreference(existingClient); GameMain.NetLobbyScreen.SetPlayerNameAndJobPreference(existingClient);
if (tc.CharacterID > 0) if (Screen.Selected != GameMain.NetLobbyScreen && tc.CharacterID > 0)
{ {
existingClient.Character = Entity.FindEntityByID(tc.CharacterID) as Character; existingClient.CharacterID = tc.CharacterID;
if (existingClient.Character == null)
{
updateClientListId = false;
}
} }
if (existingClient.ID == myID) if (existingClient.ID == myID)
{ {
@@ -2440,7 +2580,7 @@ namespace Barotrauma.Networking
if (GUI.KeyboardDispatcher.Subscriber == null) if (GUI.KeyboardDispatcher.Subscriber == null)
{ {
bool chatKeyHit = PlayerInput.KeyHit(InputType.Chat); 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) if (chatKeyHit || radioKeyHit)
{ {
@@ -2498,6 +2638,7 @@ namespace Barotrauma.Networking
{ {
var transfer = fileReceiver.ActiveTransfers.First(); var transfer = fileReceiver.ActiveTransfers.First();
GameMain.NetLobbyScreen.FileTransferFrame.Visible = true; GameMain.NetLobbyScreen.FileTransferFrame.Visible = true;
GameMain.NetLobbyScreen.FileTransferFrame.UserData = transfer;
GameMain.NetLobbyScreen.FileTransferTitle.Text = GameMain.NetLobbyScreen.FileTransferTitle.Text =
ToolBox.LimitString( ToolBox.LimitString(
TextManager.GetWithVariable("DownloadingFile", "[filename]", transfer.FileName), TextManager.GetWithVariable("DownloadingFile", "[filename]", transfer.FileName),
@@ -91,6 +91,7 @@ namespace Barotrauma.Networking
return; return;
} }
incomingLidgrenMessages.Clear();
netClient.ReadMessages(incomingLidgrenMessages); netClient.ReadMessages(incomingLidgrenMessages);
foreach (NetIncomingMessage inc in incomingLidgrenMessages) foreach (NetIncomingMessage inc in incomingLidgrenMessages)
@@ -107,8 +108,6 @@ namespace Barotrauma.Networking
break; break;
} }
} }
incomingLidgrenMessages.Clear();
} }
private void HandleDataMessage(NetIncomingMessage inc) private void HandleDataMessage(NetIncomingMessage inc)
@@ -1314,79 +1314,69 @@ namespace Barotrauma.Steam
return upToDate; return upToDate;
} }
public static bool AutoUpdateWorkshopItems() public static async Task<bool> AutoUpdateWorkshopItems()
{ {
if (!isInitialized) { return false; } if (!isInitialized) { return false; }
var query = new Steamworks.Ugc.Query(Steamworks.UgcType.All) var query = new Steamworks.Ugc.Query(Steamworks.UgcType.All)
.WhereUserSubscribed() .WhereUserSubscribed()
.WithLongDescription(); .WithLongDescription();
//ugcResultPageTasks ??= new List<Task>();
//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) if (!item.IsInstalled || !CheckWorkshopItemEnabled(item) || CheckWorkshopItemUpToDate(item)) { continue; }
{ if (!UpdateWorkshopItem(item, out string errorMsg))
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)
{ {
DebugConsole.ThrowError(errorMsg);
new GUIMessageBox( new GUIMessageBox(
TextManager.Get("Error"), TextManager.Get("Error"),
TextManager.GetWithVariables("WorkshopItemUpdateFailed", new string[2] { "[itemname]", "[errormessage]" }, new string[2] { item.Title, e.Message + ", " + e.TargetSite })); TextManager.GetWithVariables("WorkshopItemUpdateFailed", new string[2] { "[itemname]", "[errormessage]" }, new string[2] { item.Title, errorMsg }));
GameAnalyticsManager.AddErrorEventOnce( }
"SteamManager.AutoUpdateWorkshopItems:" + e.Message, else
GameAnalyticsSDK.Net.EGAErrorSeverity.Error, {
"Failed to autoupdate workshop item \"" + item.Title + "\". " + e.Message + "\n" + e.StackTrace); //TODO: potential race condition
new GUIMessageBox("", TextManager.GetWithVariable("WorkshopItemUpdated", "[itemname]", item.Title));
} }
} }
catch (Exception e)
processedResults += resultPage.Value.ResultCount;
pageIndex++;
if (processedResults < resultPage?.TotalCount)
{ {
resultPage = await query.GetPageAsync(pageIndex); new GUIMessageBox(
} TextManager.Get("Error"),
else TextManager.GetWithVariables("WorkshopItemUpdateFailed", new string[2] { "[itemname]", "[errormessage]" }, new string[2] { item.Title, e.Message + ", " + e.TargetSite }));
{ GameAnalyticsManager.AddErrorEventOnce(
resultPage = null; "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);
processedResults += resultPage.Value.ResultCount;
task.Wait(10000); pageIndex++;
if (!task.IsCompleted) if (processedResults < resultPage?.TotalCount)
{ {
cancelTokenSource.Cancel(); resultPage = await query.GetPageAsync(pageIndex);
task.Wait(); }
else
{
resultPage = null;
}
} }
return task.Status == TaskStatus.RanToCompletion; return true;
} }
public static bool UpdateWorkshopItem(Steamworks.Ugc.Item? item, out string errorMsg) public static bool UpdateWorkshopItem(Steamworks.Ugc.Item? item, out string errorMsg)
@@ -119,7 +119,7 @@ namespace Barotrauma.Networking
GameMain.NetLobbyScreen?.SetPlayerSpeaking(client); GameMain.NetLobbyScreen?.SetPlayerSpeaking(client);
GameMain.GameSession?.CrewManager?.SetClientSpeaking(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) if (client.Character != null && !client.Character.Removed)
{ {
@@ -7,6 +7,7 @@ using System.Text;
using GameAnalyticsSDK.Net; using GameAnalyticsSDK.Net;
using Barotrauma.Steam; using Barotrauma.Steam;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.InteropServices;
#if WINDOWS #if WINDOWS
using SharpDX; using SharpDX;
@@ -22,42 +23,56 @@ namespace Barotrauma
/// </summary> /// </summary>
public static class Program public static class Program
{ {
#if LINUX
/// <summary>
/// Sets the required environment variables for the game to initialize Steamworks correctly.
/// </summary>
[DllImport("linux_steam_env", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
private static extern void setLinuxEnv();
#endif
/// <summary> /// <summary>
/// The main entry point for the application. /// The main entry point for the application.
/// </summary> /// </summary>
[STAThread] [STAThread]
static void Main(string[] args) static void Main(string[] args)
{ {
GameMain game = null;
string executableDir = ""; string executableDir = "";
#if !DEBUG #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 try
{ {
#endif Game?.Exit();
executableDir = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location); CrashDump(Game, "crashreport.log", (Exception)args.ExceptionObject);
Directory.SetCurrentDirectory(executableDir); Game?.Dispose();
SteamManager.Initialize();
game = new GameMain(args);
game.Run();
game.Dispose();
#if !DEBUG
} }
catch (Exception e) catch
{ {
try //exception handler is broken, we have a serious problem here!!
{
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();
return; return;
} }
#endif
} }
public static void CrashMessageBox(string message, string filePath) public static void CrashMessageBox(string message, string filePath)
@@ -83,16 +98,9 @@ namespace Barotrauma
string exePath = System.Reflection.Assembly.GetEntryAssembly().Location; string exePath = System.Reflection.Assembly.GetEntryAssembly().Location;
var md5 = System.Security.Cryptography.MD5.Create(); var md5 = System.Security.Cryptography.MD5.Create();
Md5Hash exeHash = null; Md5Hash exeHash = null;
try using (var stream = File.OpenRead(exePath))
{ {
using (var stream = File.OpenRead(exePath)) exeHash = new Md5Hash(stream);
{
exeHash = new Md5Hash(stream);
}
}
catch
{
//gotta catch them all, we don't want to throw an exception while writing a crash report
} }
StreamWriter sw = new StreamWriter(filePath); StreamWriter sw = new StreamWriter(filePath);
@@ -217,4 +225,4 @@ namespace Barotrauma
} }
} }
#endif #endif
} }
@@ -392,18 +392,19 @@ namespace Barotrauma
{ {
nameText.Text = Path.GetFileNameWithoutExtension(saveFile); nameText.Text = Path.GetFileNameWithoutExtension(saveFile);
XDocument doc = SaveUtil.LoadGameSessionDoc(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) if (doc?.Root == null)
{ {
DebugConsole.ThrowError("Error loading save file \"" + saveFile + "\". The file may be corrupted."); DebugConsole.ThrowError("Error loading save file \"" + saveFile + "\". The file may be corrupted.");
nameText.TextColor = GUI.Style.Red; nameText.TextColor = GUI.Style.Red;
continue; 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", ""); subName = doc.Root.GetAttributeString("submarine", "");
saveTime = doc.Root.GetAttributeString("savetime", ""); saveTime = doc.Root.GetAttributeString("savetime", "");
contentPackageStr = doc.Root.GetAttributeString("selectedcontentpackages", ""); contentPackageStr = doc.Root.GetAttributeString("selectedcontentpackages", "");
@@ -3347,6 +3347,7 @@ namespace Barotrauma.CharacterEditor
void CreateCloseButton(SerializableEntityEditor editor, Action onButtonClicked, float size = 1) void CreateCloseButton(SerializableEntityEditor editor, Action onButtonClicked, float size = 1)
{ {
if (editor == null) { return; }
int height = 30; 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) 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) 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) 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 CanBeFocused = false
@@ -3383,6 +3385,7 @@ namespace Barotrauma.CharacterEditor
void CreateAddButton(SerializableEntityEditor editor, Action onButtonClicked, string text) 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) var parent = new GUIFrame(new RectTransform(new Point(editor.Rect.Width, (int)(60 * GUI.yScale)), editor.RectTransform), style: null)
{ {
CanBeFocused = false CanBeFocused = false
@@ -4386,7 +4389,7 @@ namespace Barotrauma.CharacterEditor
ResetParamsEditor(); ResetParamsEditor();
} }
limb.PullJointWorldAnchorA = ScreenToSim(PlayerInput.MousePosition); 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); GUI.DrawLine(spriteBatch, SimToScreen(limb.SimPosition), tformedPullPos, Color.MediumPurple);
}); });
} }
@@ -4469,7 +4472,7 @@ namespace Barotrauma.CharacterEditor
if (joint.BodyA == limb.body.FarseerBody) if (joint.BodyA == limb.body.FarseerBody)
{ {
joint.LocalAnchorA += input; joint.LocalAnchorA += input;
Vector2 transformedValue = ConvertUnits.ToDisplayUnits(joint.LocalAnchorA / RagdollParams.JointScale); Vector2 transformedValue = ConvertUnits.ToDisplayUnits(joint.LocalAnchorA / joint.Scale);
TryUpdateJointParam(joint, "limb1anchor", transformedValue); TryUpdateJointParam(joint, "limb1anchor", transformedValue);
// Snap all selected joints to the first selected // Snap all selected joints to the first selected
if (copyJointSettings) if (copyJointSettings)
@@ -4484,7 +4487,7 @@ namespace Barotrauma.CharacterEditor
else if (joint.BodyB == limb.body.FarseerBody) else if (joint.BodyB == limb.body.FarseerBody)
{ {
joint.LocalAnchorB += input; joint.LocalAnchorB += input;
Vector2 transformedValue = ConvertUnits.ToDisplayUnits(joint.LocalAnchorB / RagdollParams.JointScale); Vector2 transformedValue = ConvertUnits.ToDisplayUnits(joint.LocalAnchorB / joint.Scale);
TryUpdateJointParam(joint, "limb2anchor", transformedValue); TryUpdateJointParam(joint, "limb2anchor", transformedValue);
// Snap all selected joints to the first selected // Snap all selected joints to the first selected
if (copyJointSettings) if (copyJointSettings)
@@ -4504,12 +4507,12 @@ namespace Barotrauma.CharacterEditor
if (joint.BodyA == limb.body.FarseerBody && otherJoint.BodyA == otherLimb.body.FarseerBody) if (joint.BodyA == limb.body.FarseerBody && otherJoint.BodyA == otherLimb.body.FarseerBody)
{ {
otherJoint.LocalAnchorA = joint.LocalAnchorA; 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) else if (joint.BodyB == limb.body.FarseerBody && otherJoint.BodyB == otherLimb.body.FarseerBody)
{ {
otherJoint.LocalAnchorB = joint.LocalAnchorB; 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. // 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; float multiplier = 0.85f;
l.body.SetSize(new Vector2(ConvertUnits.ToSimUnits(width), ConvertUnits.ToSimUnits(height)) * RagdollParams.LimbScale * RagdollParams.TextureScale * multiplier); l.body.SetSize(new Vector2(ConvertUnits.ToSimUnits(width), ConvertUnits.ToSimUnits(height)) * l.Scale * RagdollParams.TextureScale * multiplier);
TryUpdateLimbParam(l, "radius", ConvertUnits.ToDisplayUnits(l.body.radius / RagdollParams.LimbScale / RagdollParams.TextureScale)); TryUpdateLimbParam(l, "radius", ConvertUnits.ToDisplayUnits(l.body.radius / l.Params.Scale / RagdollParams.LimbScale / RagdollParams.TextureScale));
TryUpdateLimbParam(l, "width", ConvertUnits.ToDisplayUnits(l.body.width / 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 / RagdollParams.LimbScale / RagdollParams.TextureScale)); TryUpdateLimbParam(l, "height", ConvertUnits.ToDisplayUnits(l.body.height / l.Params.Scale / RagdollParams.LimbScale / RagdollParams.TextureScale));
} }
void RecalculateOrigin(Limb l) void RecalculateOrigin(Limb l)
{ {
@@ -4963,7 +4966,7 @@ namespace Barotrauma.CharacterEditor
{ {
continue; continue;
} }
Vector2 tformedJointPos = jointPos = jointPos / RagdollParams.JointScale / limb.TextureScale * spriteSheetZoom; Vector2 tformedJointPos = jointPos = jointPos / joint.Scale / limb.TextureScale * spriteSheetZoom;
tformedJointPos.Y = -tformedJointPos.Y; tformedJointPos.Y = -tformedJointPos.Y;
tformedJointPos.X *= character.AnimController.Dir; tformedJointPos.X *= character.AnimController.Dir;
tformedJointPos += limbScreenPos; tformedJointPos += limbScreenPos;
@@ -4991,11 +4994,11 @@ namespace Barotrauma.CharacterEditor
Vector2 input = ConvertUnits.ToSimUnits(scaledMouseSpeed); Vector2 input = ConvertUnits.ToSimUnits(scaledMouseSpeed);
input.Y = -input.Y; input.Y = -input.Y;
input.X *= character.AnimController.Dir; input.X *= character.AnimController.Dir;
input *= RagdollParams.JointScale * limb.TextureScale / spriteSheetZoom; input *= joint.Scale * limb.TextureScale / spriteSheetZoom;
if (joint.BodyA == limb.body.FarseerBody) if (joint.BodyA == limb.body.FarseerBody)
{ {
joint.LocalAnchorA += input; joint.LocalAnchorA += input;
Vector2 transformedValue = ConvertUnits.ToDisplayUnits(joint.LocalAnchorA / RagdollParams.JointScale); Vector2 transformedValue = ConvertUnits.ToDisplayUnits(joint.LocalAnchorA / joint.Scale);
TryUpdateJointParam(joint, "limb1anchor", transformedValue); TryUpdateJointParam(joint, "limb1anchor", transformedValue);
// Snap all selected joints to the first selected // Snap all selected joints to the first selected
if (copyJointSettings) if (copyJointSettings)
@@ -5010,7 +5013,7 @@ namespace Barotrauma.CharacterEditor
else if (joint.BodyB == limb.body.FarseerBody) else if (joint.BodyB == limb.body.FarseerBody)
{ {
joint.LocalAnchorB += input; joint.LocalAnchorB += input;
Vector2 transformedValue = ConvertUnits.ToDisplayUnits(joint.LocalAnchorB / RagdollParams.JointScale); Vector2 transformedValue = ConvertUnits.ToDisplayUnits(joint.LocalAnchorB / joint.Scale);
TryUpdateJointParam(joint, "limb2anchor", transformedValue); TryUpdateJointParam(joint, "limb2anchor", transformedValue);
// Snap all selected joints to the first selected // Snap all selected joints to the first selected
if (copyJointSettings) if (copyJointSettings)
@@ -5029,12 +5032,12 @@ namespace Barotrauma.CharacterEditor
if (joint.BodyA == limb.body.FarseerBody && otherJoint.BodyA == otherLimb.body.FarseerBody) if (joint.BodyA == limb.body.FarseerBody && otherJoint.BodyA == otherLimb.body.FarseerBody)
{ {
otherJoint.LocalAnchorA = joint.LocalAnchorA; 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) else if (joint.BodyB == limb.body.FarseerBody && otherJoint.BodyB == otherLimb.body.FarseerBody)
{ {
otherJoint.LocalAnchorB = joint.LocalAnchorB; otherJoint.LocalAnchorB = joint.LocalAnchorB;
TryUpdateJointParam(otherJoint, "limb2anchor", ConvertUnits.ToDisplayUnits(joint.LocalAnchorB / RagdollParams.JointScale)); TryUpdateJointParam(otherJoint, "limb2anchor", ConvertUnits.ToDisplayUnits(joint.LocalAnchorB / joint.Scale));
} }
}); });
} }
@@ -91,7 +91,13 @@ namespace Barotrauma
c.DoVisibilityCheck(cam); c.DoVisibilityCheck(cam);
if (c.IsVisible != wasVisible) 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); spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.LinearWrap, DepthStencilState.None, null, null, cam.Transform);
foreach (Character c in Character.CharacterList) c.DrawFront(spriteBatch, cam); foreach (Character c in Character.CharacterList)
if (Level.Loaded != null) Level.Loaded.DrawFront(spriteBatch, cam);
if (GameMain.DebugDraw && GameMain.GameSession?.EventManager != null)
{ {
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(); spriteBatch.End();
if (GameMain.LightManager.LosEnabled && GameMain.LightManager.LosMode != LosMode.None && Character.Controlled != null) if (GameMain.LightManager.LosEnabled && GameMain.LightManager.LosMode != LosMode.None && Character.Controlled != null)
@@ -517,8 +517,21 @@ namespace Barotrauma
{ {
foreach (XElement element in doc.Root.Elements()) foreach (XElement element in doc.Root.Elements())
{ {
if (element.Name.ToString().ToLowerInvariant() != genParams.Name.ToLowerInvariant()) continue; XElement levelParamElement = element;
SerializableProperty.SerializeProperties(genParams, element, true); 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; break;
} }
} }
@@ -79,8 +79,7 @@ namespace Barotrauma
private IEnumerable<object> LoadRound() private IEnumerable<object> LoadRound()
{ {
GameMain.GameSession.StartRound(campaignUI.SelectedLevel, GameMain.GameSession.StartRound(campaignUI.SelectedLevel,
reloadSub: true, reloadSub: true,
loadSecondSub: false,
mirrorLevel: GameMain.GameSession.Map.CurrentLocation != GameMain.GameSession.Map.SelectedConnection.Locations[0]); mirrorLevel: GameMain.GameSession.Map.CurrentLocation != GameMain.GameSession.Map.SelectedConnection.Locations[0]);
GameMain.GameScreen.Select(); GameMain.GameScreen.Select();
@@ -443,7 +443,7 @@ namespace Barotrauma
{ {
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{ {
if (!(userdata is FileReceiver.FileTransferIn transfer)) { return false; } if (!(FileTransferFrame.UserData is FileReceiver.FileTransferIn transfer)) { return false; }
GameMain.Client?.CancelFileTransfer(transfer); GameMain.Client?.CancelFileTransfer(transfer);
GameMain.Client.FileReceiver.StopTransfer(transfer); GameMain.Client.FileReceiver.StopTransfer(transfer);
return true; return true;
@@ -658,7 +658,7 @@ namespace Barotrauma
OnClicked = (btn, obj) => OnClicked = (btn, obj) =>
{ {
GameMain.Client.RequestStartRound(); GameMain.Client.RequestStartRound();
CoroutineManager.StartCoroutine(WaitForStartRound(StartButton, allowCancel: true), "WaitForStartRound"); CoroutineManager.StartCoroutine(WaitForStartRound(StartButton, allowCancel: false), "WaitForStartRound");
return true; return true;
} }
}; };
@@ -1147,6 +1147,22 @@ namespace Barotrauma
clientDisabledElements.AddRange(botSpawnModeButtons); clientDisabledElements.AddRange(botSpawnModeButtons);
} }
public void StopWaitingForStartRound()
{
CoroutineManager.StopCoroutines("WaitForStartRound");
GUIMessageBox.CloseAll();
if (StartButton != null)
{
StartButton.Enabled = true;
}
if (campaignUI?.StartButton != null)
{
campaignUI.StartButton.Enabled = true;
}
GUI.ClearCursorWait();
}
public IEnumerable<object> WaitForStartRound(GUIButton startButton, bool allowCancel) public IEnumerable<object> WaitForStartRound(GUIButton startButton, bool allowCancel)
{ {
GUI.SetCursorWaiting(); GUI.SetCursorWaiting();
@@ -1173,7 +1189,8 @@ namespace Barotrauma
} }
DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, 10); DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, 10);
while (Selected == GameMain.NetLobbyScreen && DateTime.Now < timeOut) while (Selected == GameMain.NetLobbyScreen &&
DateTime.Now < timeOut)
{ {
msgBox.Header.Text = headerText + new string('.', ((int)Timing.TotalTime % 3 + 1)); msgBox.Header.Text = headerText + new string('.', ((int)Timing.TotalTime % 3 + 1));
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
@@ -1322,6 +1339,8 @@ namespace Barotrauma
if (GameMain.Client == null) return; if (GameMain.Client == null) return;
spectateButton.Visible = true; spectateButton.Visible = true;
spectateButton.Enabled = true; spectateButton.Enabled = true;
StartButton.Visible = false;
} }
public void SetCampaignCharacterInfo(CharacterInfo newCampaignCharacterInfo) public void SetCampaignCharacterInfo(CharacterInfo newCampaignCharacterInfo)
@@ -2745,11 +2764,11 @@ namespace Barotrauma
availableJobs = availableJobs.ToList(); availableJobs = availableJobs.ToList();
int itemsInRow = 1; int itemsInRow = 0;
foreach (var jobPrefab in availableJobs) foreach (var jobPrefab in availableJobs)
{ {
if (itemsInRow >= 4) if (itemsInRow >= 3)
{ {
row = new GUILayoutGroup(new RectTransform(Vector2.One, rows.RectTransform), true); row = new GUILayoutGroup(new RectTransform(Vector2.One, rows.RectTransform), true);
itemsInRow = 0; itemsInRow = 0;
@@ -741,6 +741,11 @@ namespace Barotrauma
{ {
base.Select(); base.Select();
GameMain.LightManager.AmbientLight =
Level.Loaded?.GenerationParams?.AmbientLightColor ??
LevelGenerationParams.LevelParams?.FirstOrDefault()?.AmbientLightColor ??
new Color(20, 20, 20, 255);
UpdateEntityList(); UpdateEntityList();
string name = (Submarine.MainSub == null) ? TextManager.Get("unspecifiedsubfilename") : Submarine.MainSub.Name; string name = (Submarine.MainSub == null) ? TextManager.Get("unspecifiedsubfilename") : Submarine.MainSub.Name;
@@ -1624,7 +1629,10 @@ namespace Barotrauma
foreach (Item item in Item.ItemList) foreach (Item item in Item.ItemList)
{ {
var lightComponent = item.GetComponent<LightComponent>(); var lightComponent = item.GetComponent<LightComponent>();
if (lightComponent != null) lightComponent.Light.Enabled = item.ParentInventory == null; if (lightComponent != null)
{
lightComponent.Light.Enabled = item.ParentInventory == null;
}
} }
if (selectedSub.GameVersion < new Version("0.8.9.0")) if (selectedSub.GameVersion < new Version("0.8.9.0"))
@@ -1790,6 +1798,7 @@ namespace Barotrauma
} }
MapEntity.DeselectAll(); MapEntity.DeselectAll();
MapEntity.FilteredSelectedList.Clear();
} }
private void RemoveDummyCharacter() private void RemoveDummyCharacter()
@@ -569,15 +569,13 @@ namespace Barotrauma
Font = GUI.SmallFont, Font = GUI.SmallFont,
Text = value, Text = value,
OverflowClip = true, OverflowClip = true,
OnEnterPressed = (textBox, text) => };
propertyBox.OnDeselected += (textBox, keys) =>
{
if (property.TrySetValue(entity, textBox.Text))
{ {
if (property.TrySetValue(entity, text)) TrySendNetworkUpdate(entity, property);
{ textBox.Text = (string)property.GetValue(entity);
TrySendNetworkUpdate(entity, property);
textBox.Text = (string)property.GetValue(entity);
textBox.Deselect();
}
return true;
} }
}; };
if (translationTextTag != null) if (translationTextTag != null)
@@ -52,7 +52,7 @@ namespace Barotrauma
} }
} }
partial void ApplyProjSpecific(float deltaTime, Entity entity, List<ISerializableEntity> targets, Hull hull, Vector2 worldPosition) partial void ApplyProjSpecific(float deltaTime, Entity entity, IEnumerable<ISerializableEntity> targets, Hull hull, Vector2 worldPosition)
{ {
if (entity == null) { return; } if (entity == null) { return; }
@@ -112,17 +112,19 @@ namespace Barotrauma
foreach (ParticleEmitter emitter in particleEmitters) foreach (ParticleEmitter emitter in particleEmitters)
{ {
float angle = 0.0f; float angle = 0.0f;
float particleRotation = 0.0f;
if (emitter.Prefab.CopyEntityAngle) if (emitter.Prefab.CopyEntityAngle)
{ {
if (entity is Item item && item.body != null) if (entity is Item item && item.body != null)
{ {
angle = item.body.Rotation + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi); angle = item.body.Rotation + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
particleRotation = -item.body.Rotation;
if (item.body.Dir < 0.0f) { particleRotation += MathHelper.Pi; }
} }
} }
emitter.Emit(deltaTime, worldPosition, hull, angle); emitter.Emit(deltaTime, worldPosition, hull, angle: angle, particleRotation: particleRotation);
} }
} }
static partial void UpdateAllProjSpecific(float deltaTime) static partial void UpdateAllProjSpecific(float deltaTime)
@@ -2,11 +2,11 @@
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.9.7.1</Version> <Version>0.9.8.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
+2 -2
View File
@@ -2,11 +2,11 @@
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.9.7.1</Version> <Version>0.9.8.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
@@ -2,16 +2,17 @@
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.9.7.1</Version> <Version>0.9.8.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon> <ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
<Configurations>Debug;Release;Unstable</Configurations> <Configurations>Debug;Release;Unstable</Configurations>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@@ -0,0 +1 @@
./DedicatedServer
@@ -2,11 +2,11 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.9.7.1</Version> <Version>0.9.8.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
@@ -54,6 +54,7 @@
<Content Include="..\BarotraumaShared\**\*" CopyToOutputDirectory="PreserveNewest" /> <Content Include="..\BarotraumaShared\**\*" CopyToOutputDirectory="PreserveNewest" />
<Content Remove="..\BarotraumaShared\**\*.cs" /> <Content Remove="..\BarotraumaShared\**\*.cs" />
<Compile Include="..\BarotraumaShared\**\*.cs" /> <Compile Include="..\BarotraumaShared\**\*.cs" />
<Content Include="DedicatedServer.exe" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(Configuration)'!='Debug'"> <ItemGroup Condition="'$(Configuration)'!='Debug'">
+3 -2
View File
@@ -2,11 +2,11 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.9.7.1</Version> <Version>0.9.8.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
@@ -61,6 +61,7 @@
<Compile Include="..\BarotraumaShared\**\*.cs" /> <Compile Include="..\BarotraumaShared\**\*.cs" />
<Content Remove="..\BarotraumaShared\libsteam_api64.dylib" /> <Content Remove="..\BarotraumaShared\libsteam_api64.dylib" />
<Content Remove="..\BarotraumaShared\libsteam_api64.so" /> <Content Remove="..\BarotraumaShared\libsteam_api64.so" />
<Content Remove="DedicatedServer.exe" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(Configuration)'!='Debug'"> <ItemGroup Condition="'$(Configuration)'!='Debug'">
@@ -52,7 +52,7 @@ namespace Barotrauma
if (memInput.Count > 0) if (memInput.Count > 0)
{ {
prevDequeuedInput = dequeuedInput; prevDequeuedInput = dequeuedInput;
dequeuedInput = memInput[memInput.Count - 1].states; dequeuedInput = memInput[memInput.Count - 1].states & InputNetFlags.Ragdoll;
memInput.RemoveAt(memInput.Count - 1); memInput.RemoveAt(memInput.Count - 1);
} }
} }
@@ -333,6 +333,14 @@ namespace Barotrauma
attack = dequeuedInput.HasFlag(InputNetFlags.Attack); attack = dequeuedInput.HasFlag(InputNetFlags.Attack);
shoot = dequeuedInput.HasFlag(InputNetFlags.Shoot); shoot = dequeuedInput.HasFlag(InputNetFlags.Shoot);
} }
else if (keys != null)
{
aiming = keys[(int)InputType.Aim].GetHeldQueue;
use = keys[(int)InputType.Use].GetHeldQueue;
attack = keys[(int)InputType.Attack].GetHeldQueue;
shoot = keys[(int)InputType.Shoot].GetHeldQueue;
networkUpdateSent = true;
}
tempBuffer.Write(aiming); tempBuffer.Write(aiming);
tempBuffer.Write(shoot); tempBuffer.Write(shoot);
@@ -21,7 +21,7 @@ namespace Barotrauma
} }
} }
public override bool AssignTeamIDs(List<Client> clients) public override void AssignTeamIDs(List<Client> clients)
{ {
List<Client> randList = new List<Client>(clients); List<Client> randList = new List<Client>(clients);
for (int i = 0; i < randList.Count; i++) for (int i = 0; i < randList.Count; i++)
@@ -44,7 +44,6 @@ namespace Barotrauma
randList[i].TeamID = Character.TeamType.Team2; randList[i].TeamID = Character.TeamType.Team2;
} }
} }
return true;
} }
public override void Update(float deltaTime) public override void Update(float deltaTime)
@@ -33,6 +33,8 @@ namespace Barotrauma.Items.Components
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null) public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{ {
msg.Write((byte)State);
msg.Write(timeUntilReady);
int itemIndex = fabricatedItem == null ? -1 : fabricationRecipes.IndexOf(fabricatedItem); int itemIndex = fabricatedItem == null ? -1 : fabricationRecipes.IndexOf(fabricatedItem);
msg.WriteRangedInteger(itemIndex, -1, fabricationRecipes.Count - 1); msg.WriteRangedInteger(itemIndex, -1, fabricationRecipes.Count - 1);
UInt16 userID = fabricatedItem == null || user == null ? (UInt16)0 : user.ID; UInt16 userID = fabricatedItem == null || user == null ? (UInt16)0 : user.ID;
@@ -17,9 +17,10 @@ namespace Barotrauma.Items.Components
GameServer.Log(c.Character.LogName + " entered \"" + newOutputValue + "\" on " + item.Name, GameServer.Log(c.Character.LogName + " entered \"" + newOutputValue + "\" on " + item.Name,
ServerLog.MessageType.ItemInteraction); ServerLog.MessageType.ItemInteraction);
OutputValue = newOutputValue; OutputValue = newOutputValue;
item.SendSignal(0, newOutputValue, "signal_out", null);
item.CreateServerEvent(this);
} }
item.CreateServerEvent(this);
} }
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null) public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
@@ -8,6 +8,11 @@ namespace Barotrauma
{ {
partial class Item : MapEntity, IDamageable, ISerializableEntity, IServerSerializable, IClientSerializable partial class Item : MapEntity, IDamageable, ISerializableEntity, IServerSerializable, IClientSerializable
{ {
public override Sprite Sprite
{
get { return prefab?.sprite; }
}
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null) public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{ {
string errorMsg = ""; string errorMsg = "";
@@ -627,16 +627,21 @@ namespace Barotrauma.Networking
//game already started -> send start message immediately //game already started -> send start message immediately
if (gameStarted) if (gameStarted)
{ {
SendStartMessage(roundStartSeed, Submarine.MainSub, GameMain.GameSession.GameMode.Preset, connectedClient); SendStartMessage(roundStartSeed, GameMain.GameSession.Level.Seed, GameMain.GameSession, connectedClient, true);
} }
} }
break; break;
case ClientPacketHeader.REQUEST_STARTGAMEFINALIZE:
if (gameStarted && connectedClient != null)
{
SendRoundStartFinalize(connectedClient);
}
break;
case ClientPacketHeader.UPDATE_LOBBY: case ClientPacketHeader.UPDATE_LOBBY:
ClientReadLobby(inc); ClientReadLobby(inc);
break; break;
case ClientPacketHeader.UPDATE_INGAME: case ClientPacketHeader.UPDATE_INGAME:
if (!gameStarted) return; if (!gameStarted) { return; }
ClientReadIngame(inc); ClientReadIngame(inc);
break; break;
case ClientPacketHeader.CAMPAIGN_SETUP_INFO: case ClientPacketHeader.CAMPAIGN_SETUP_INFO:
@@ -1480,6 +1485,7 @@ namespace Barotrauma.Networking
outmsg.Write(client.Character == null || !gameStarted ? (client.PreferredJob ?? "") : ""); outmsg.Write(client.Character == null || !gameStarted ? (client.PreferredJob ?? "") : "");
outmsg.Write(client.Character == null || !gameStarted ? (ushort)0 : client.Character.ID); outmsg.Write(client.Character == null || !gameStarted ? (ushort)0 : client.Character.ID);
outmsg.Write(client.Muted); outmsg.Write(client.Muted);
outmsg.Write(client.InGame);
outmsg.Write(client.Connection != OwnerConnection); //is kicking the player allowed outmsg.Write(client.Connection != OwnerConnection); //is kicking the player allowed
outmsg.WritePadBits(); outmsg.WritePadBits();
} }
@@ -1773,10 +1779,10 @@ namespace Barotrauma.Networking
//always allow the server owner to spectate even if it's disallowed in server settings //always allow the server owner to spectate even if it's disallowed in server settings
playingClients.RemoveAll(c => c.Connection == OwnerConnection && c.SpectateOnly); playingClients.RemoveAll(c => c.Connection == OwnerConnection && c.SpectateOnly);
if (GameMain.GameSession.GameMode.Mission != null && if (GameMain.GameSession.GameMode.Mission != null)
GameMain.GameSession.GameMode.Mission.AssignTeamIDs(playingClients))
{ {
teamCount = 2; GameMain.GameSession.GameMode.Mission.AssignTeamIDs(playingClients);
teamCount = GameMain.GameSession.GameMode.Mission.TeamCount;
} }
else else
{ {
@@ -1796,9 +1802,10 @@ namespace Barotrauma.Networking
campaign.Map.SelectRandomLocation(preferUndiscovered: true); campaign.Map.SelectRandomLocation(preferUndiscovered: true);
} }
SendStartMessage(roundStartSeed, campaign.Map.SelectedConnection.Level.Seed, GameMain.GameSession, connectedClients, false);
GameMain.GameSession.StartRound(campaign.Map.SelectedConnection.Level, GameMain.GameSession.StartRound(campaign.Map.SelectedConnection.Level,
reloadSub: true, reloadSub: true,
loadSecondSub: teamCount > 1,
mirrorLevel: campaign.Map.CurrentLocation != campaign.Map.SelectedConnection.Locations[0]); mirrorLevel: campaign.Map.CurrentLocation != campaign.Map.SelectedConnection.Locations[0]);
campaign.AssignClientCharacterInfos(connectedClients); campaign.AssignClientCharacterInfos(connectedClients);
@@ -1808,7 +1815,9 @@ namespace Barotrauma.Networking
} }
else else
{ {
GameMain.GameSession.StartRound(GameMain.NetLobbyScreen.LevelSeed, serverSettings.SelectedLevelDifficulty, teamCount > 1); SendStartMessage(roundStartSeed, GameMain.NetLobbyScreen.LevelSeed, GameMain.GameSession, connectedClients, false);
GameMain.GameSession.StartRound(GameMain.NetLobbyScreen.LevelSeed, serverSettings.SelectedLevelDifficulty);
Log("Game mode: " + selectedMode.Name, ServerLog.MessageType.ServerMessage); Log("Game mode: " + selectedMode.Name, ServerLog.MessageType.ServerMessage);
Log("Submarine: " + selectedSub.Name, ServerLog.MessageType.ServerMessage); Log("Submarine: " + selectedSub.Name, ServerLog.MessageType.ServerMessage);
Log("Level seed: " + GameMain.NetLobbyScreen.LevelSeed, ServerLog.MessageType.ServerMessage); Log("Level seed: " + GameMain.NetLobbyScreen.LevelSeed, ServerLog.MessageType.ServerMessage);
@@ -1946,8 +1955,6 @@ namespace Barotrauma.Networking
GameAnalyticsManager.AddDesignEvent("Traitors:" + (TraitorManager == null ? "Disabled" : "Enabled")); GameAnalyticsManager.AddDesignEvent("Traitors:" + (TraitorManager == null ? "Disabled" : "Enabled"));
SendStartMessage(roundStartSeed, Submarine.MainSub, GameMain.GameSession.GameMode.Preset, connectedClients);
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
GameMain.GameScreen.Select(); GameMain.GameScreen.Select();
@@ -1965,35 +1972,34 @@ namespace Barotrauma.Networking
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
private void SendStartMessage(int seed, Submarine selectedSub, GameModePreset selectedMode, List<Client> clients) private void SendStartMessage(int seed, string levelSeed, GameSession gameSession, List<Client> clients, bool includesFinalize)
{ {
foreach (Client client in clients) foreach (Client client in clients)
{ {
SendStartMessage(seed, selectedSub, selectedMode, client); SendStartMessage(seed, levelSeed, gameSession, client, includesFinalize);
} }
} }
private void SendStartMessage(int seed, Submarine selectedSub, GameModePreset selectedMode, Client client) private void SendStartMessage(int seed, string levelSeed, GameSession gameSession, Client client, bool includesFinalize)
{ {
IWriteMessage msg = new WriteOnlyMessage(); IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ServerPacketHeader.STARTGAME); msg.Write((byte)ServerPacketHeader.STARTGAME);
msg.Write(seed); msg.Write(seed);
msg.Write(GameMain.GameSession.Level.Seed); msg.Write(levelSeed);
msg.Write(GameMain.GameSession.Level.EqualityCheckVal);
msg.Write(serverSettings.SelectedLevelDifficulty); msg.Write(serverSettings.SelectedLevelDifficulty);
msg.Write((byte)GameMain.Config.LosMode); msg.Write((byte)GameMain.Config.LosMode);
msg.Write((byte)GameMain.NetLobbyScreen.MissionType); msg.Write((byte)GameMain.NetLobbyScreen.MissionType);
msg.Write(selectedSub.Name); msg.Write(gameSession.Submarine.Name);
msg.Write(selectedSub.MD5Hash.Hash); msg.Write(gameSession.Submarine.MD5Hash.Hash);
msg.Write(serverSettings.UseRespawnShuttle); msg.Write(serverSettings.UseRespawnShuttle);
msg.Write(GameMain.NetLobbyScreen.SelectedShuttle.Name); msg.Write(GameMain.NetLobbyScreen.SelectedShuttle.Name);
msg.Write(GameMain.NetLobbyScreen.SelectedShuttle.MD5Hash.Hash); msg.Write(GameMain.NetLobbyScreen.SelectedShuttle.MD5Hash.Hash);
msg.Write(selectedMode.Identifier); msg.Write(gameSession.GameMode.Preset.Identifier);
msg.Write((short)(GameMain.GameSession.GameMode?.Mission == null ? msg.Write((short)(GameMain.GameSession.GameMode?.Mission == null ?
-1 : MissionPrefab.List.IndexOf(GameMain.GameSession.GameMode.Mission.Prefab))); -1 : MissionPrefab.List.IndexOf(GameMain.GameSession.GameMode.Mission.Prefab)));
@@ -2002,13 +2008,33 @@ namespace Barotrauma.Networking
MissionMode missionMode = GameMain.GameSession.GameMode as MissionMode; MissionMode missionMode = GameMain.GameSession.GameMode as MissionMode;
bool missionAllowRespawn = campaign == null && (missionMode?.Mission == null || missionMode.Mission.AllowRespawn); bool missionAllowRespawn = campaign == null && (missionMode?.Mission == null || missionMode.Mission.AllowRespawn);
msg.Write(serverSettings.AllowRespawn && missionAllowRespawn); msg.Write(serverSettings.AllowRespawn && missionAllowRespawn);
msg.Write(Submarine.MainSubs[1] != null); //loadSecondSub
msg.Write(serverSettings.AllowDisguises); msg.Write(serverSettings.AllowDisguises);
msg.Write(serverSettings.AllowRewiring); msg.Write(serverSettings.AllowRewiring);
msg.Write(serverSettings.AllowRagdollButton); msg.Write(serverSettings.AllowRagdollButton);
serverSettings.WriteMonsterEnabled(msg);
msg.Write(includesFinalize); msg.WritePadBits();
if (includesFinalize)
{
WriteRoundStartFinalize(msg, client);
}
serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable);
}
private void SendRoundStartFinalize(Client client)
{
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ServerPacketHeader.STARTGAMEFINALIZE);
WriteRoundStartFinalize(msg, client);
serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable);
}
private void WriteRoundStartFinalize(IWriteMessage msg, Client client)
{
//tell the client what content files they should preload //tell the client what content files they should preload
var contentToPreload = GameMain.GameSession.EventManager.GetFilesToPreload(); var contentToPreload = GameMain.GameSession.EventManager.GetFilesToPreload();
msg.Write((ushort)contentToPreload.Count()); msg.Write((ushort)contentToPreload.Count());
@@ -2017,12 +2043,8 @@ namespace Barotrauma.Networking
msg.Write((byte)contentFile.Type); msg.Write((byte)contentFile.Type);
msg.Write(contentFile.Path); msg.Write(contentFile.Path);
} }
msg.Write(GameMain.GameSession.Level.EqualityCheckVal);
serverSettings.WriteMonsterEnabled(msg);
GameMain.GameSession.Mission?.ServerWriteInitial(msg, client); GameMain.GameSession.Mission?.ServerWriteInitial(msg, client);
serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable);
} }
public void EndGame() public void EndGame()
@@ -7,6 +7,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Runtime.InteropServices;
#endregion #endregion
@@ -17,53 +18,62 @@ namespace Barotrauma
/// </summary> /// </summary>
public static class Program public static class Program
{ {
#if LINUX
/// <summary>
/// Sets the required environment variables for the game to initialize Steamworks correctly.
/// </summary>
[DllImport("linux_steam_env", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
private static extern void setLinuxEnv();
#endif
/// <summary> /// <summary>
/// The main entry point for the application. /// The main entry point for the application.
/// </summary> /// </summary>
[STAThread] [STAThread]
static void Main(string[] args) static void Main(string[] args)
{ {
GameMain game = null;
#if !DEBUG #if !DEBUG
try AppDomain currentDomain = AppDomain.CurrentDomain;
{ currentDomain.UnhandledException += new UnhandledExceptionEventHandler(CrashHandler);
#endif #endif
Console.WriteLine("Barotrauma Dedicated Server " + GameMain.Version + #if LINUX
" (" + AssemblyInfo.GetBuildString() + ", branch " + AssemblyInfo.GetGitBranch() + ", revision " + AssemblyInfo.GetGitRevision() + ")"); setLinuxEnv();
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();
}
#endif #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 try
{ {
GameMain.Server?.ServerSettings?.SaveSettings(); Game?.Exit();
GameMain.Server?.ServerSettings?.BanList.Save(); CrashDump("servercrashreport.log", (Exception)args.ExceptionObject);
if (GameMain.Server?.ServerSettings?.KarmaPreset == "custom") GameMain.Server?.NotifyCrash();
{
GameMain.Server?.KarmaManager?.SaveCustomPreset();
GameMain.Server?.KarmaManager?.Save();
}
} }
//gotta catch them all, we don't want to crash while writing a crash report catch
catch (Exception e)
{ {
string errorMsg = "Exception thrown while writing a crash report: " + e.Message + "\n" + e.StackTrace; //exception handler is broken, we have a serious problem here!!
GameAnalyticsManager.AddErrorEventOnce("CrashDump:FailedToSaveSettings", EGAErrorSeverity.Error, errorMsg); 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; int existingFiles = 0;
@@ -81,9 +91,11 @@ namespace Barotrauma
sb.AppendLine("\n"); sb.AppendLine("\n");
sb.AppendLine("Barotrauma seems to have crashed. Sorry for the inconvenience! "); sb.AppendLine("Barotrauma seems to have crashed. Sorry for the inconvenience! ");
sb.AppendLine("\n"); sb.AppendLine("\n");
sb.AppendLine("Game version " + GameMain.Version + sb.AppendLine("Game version " + GameMain.Version + " (" + AssemblyInfo.GetBuildString() + ", branch " + AssemblyInfo.GetGitBranch() + ", revision " + AssemblyInfo.GetGitRevision() + ")");
" (" + 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("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("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("Loaded submarine: " + ((Submarine.MainSub == null) ? "None" : Submarine.MainSub.Name + " (" + Submarine.MainSub.MD5Hash + ")"));
sb.AppendLine("Selected screen: " + (Screen.Selected == null ? "None" : Screen.Selected.ToString())); sb.AppendLine("Selected screen: " + (Screen.Selected == null ? "None" : Screen.Selected.ToString()));
@@ -2,11 +2,11 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.9.7.1</Version> <Version>0.9.8.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
@@ -54,6 +54,7 @@
<Content Include="..\BarotraumaShared\**\*" CopyToOutputDirectory="PreserveNewest" /> <Content Include="..\BarotraumaShared\**\*" CopyToOutputDirectory="PreserveNewest" />
<Content Remove="..\BarotraumaShared\**\*.cs" /> <Content Remove="..\BarotraumaShared\**\*.cs" />
<Compile Include="..\BarotraumaShared\**\*.cs" /> <Compile Include="..\BarotraumaShared\**\*.cs" />
<Content Remove="DedicatedServer.exe" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(Configuration)'!='Debug'"> <ItemGroup Condition="'$(Configuration)'!='Debug'">
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<contentpackage name="Vanilla 0.9" path="Data/ContentPackages/Vanilla 0.9" corepackage="true" hideinworkshopmenu="true" gameversion="0.9.0.0"> <contentpackage name="Vanilla 0.9" path="Data/ContentPackages/Vanilla 0.9.xml" corepackage="true" gameversion="0.9.0.0">
<Item file="Content/Items/idcard.xml" /> <Item file="Content/Items/idcard.xml" />
<Item file="Content/Items/Alien/alienitems.xml" /> <Item file="Content/Items/Alien/alienitems.xml" />
<Item file="Content/Items/Button/button.xml" /> <Item file="Content/Items/Button/button.xml" />
@@ -55,6 +55,7 @@
<Character file="Content/Characters/Charybdis/Charybdis.xml" /> <Character file="Content/Characters/Charybdis/Charybdis.xml" />
<Character file="Content/Characters/Coelanth/Coelanth.xml" /> <Character file="Content/Characters/Coelanth/Coelanth.xml" />
<Character file="Content/Characters/Crawler/Crawler.xml" /> <Character file="Content/Characters/Crawler/Crawler.xml" />
<Character file="Content/Characters/Crawlerhusk/Crawlerhusk.xml" />
<Character file="Content/Characters/Endworm/Endworm.xml" /> <Character file="Content/Characters/Endworm/Endworm.xml" />
<Character file="Content/Characters/Fractalguardian/Fractalguardian.xml" /> <Character file="Content/Characters/Fractalguardian/Fractalguardian.xml" />
<Character file="Content/Characters/Fractalguardian2/Fractalguardian2.xml" /> <Character file="Content/Characters/Fractalguardian2/Fractalguardian2.xml" />
@@ -73,7 +74,6 @@
<Character file="Content/Characters/Mudraptor/Mudraptor.xml" /> <Character file="Content/Characters/Mudraptor/Mudraptor.xml" />
<Character file="Content/Characters/Mantis/Mantis.xml" /> <Character file="Content/Characters/Mantis/Mantis.xml" />
<Character file="Content/Characters/Moloch/Moloch.xml" /> <Character file="Content/Characters/Moloch/Moloch.xml" />
<Character file="Content/Characters/Molochboss/Molochboss.xml" />
<Character file="Content/Characters/Watcher/Watcher.xml" /> <Character file="Content/Characters/Watcher/Watcher.xml" />
<Character file="Content/Characters/Tigerthresher/Tigerthresher.xml" /> <Character file="Content/Characters/Tigerthresher/Tigerthresher.xml" />
<Character file="Content/Characters/Bonethresher/Bonethresher.xml" /> <Character file="Content/Characters/Bonethresher/Bonethresher.xml" />
@@ -106,34 +106,34 @@
<Text file="Content/Texts/Japanese/JapaneseVanilla.xml" /> <Text file="Content/Texts/Japanese/JapaneseVanilla.xml" />
<Text file="Content/Texts/Turkish/TurkishVanilla.xml" /> <Text file="Content/Texts/Turkish/TurkishVanilla.xml" />
<Text file="Content/Texts/Korean/KoreanVanilla.xml" /> <Text file="Content/Texts/Korean/KoreanVanilla.xml" />
<UIStyle file="Content/UI/style.xml"/> <UIStyle file="Content/UI/style.xml" />
<Afflictions file="Content/Afflictions.xml"/> <Afflictions file="Content/Afflictions.xml" />
<Structure file="Content/Map/StructurePrefabs.xml" /> <Structure file="Content/Map/StructurePrefabs.xml" />
<RuinConfig file="Content/Map/RuinConfig.xml" /> <RuinConfig file="Content/Map/RuinConfig.xml" />
<BackgroundCreaturePrefabs file="Content/BackgroundCreatures/BackgroundCreaturePrefabs.xml"/> <BackgroundCreaturePrefabs file="Content/BackgroundCreatures/BackgroundCreaturePrefabs.xml" />
<LevelObjectPrefabs file="Content/LevelObjects/LevelObjectPrefabs.xml" /> <LevelObjectPrefabs file="Content/LevelObjects/LevelObjectPrefabs.xml" />
<Particles file="Content/Particles/ParticlePrefabs.xml"/> <Particles file="Content/Particles/ParticlePrefabs.xml" />
<Decals file="Content/Particles/DecalPrefabs.xml"/> <Decals file="Content/Particles/DecalPrefabs.xml" />
<RandomEvents file="Content/randomevents.xml" /> <RandomEvents file="Content/randomevents.xml" />
<EventManagerSettings file="Content/EventManagerSettings.xml" /> <EventManagerSettings file="Content/EventManagerSettings.xml" />
<LocationTypes file="Content/Map/locationTypes.xml" /> <LocationTypes file="Content/Map/locationTypes.xml" />
<MapGenerationParameters file="Content/Map/MapGenerationParameters.xml" /> <MapGenerationParameters file="Content/Map/MapGenerationParameters.xml" />
<LevelGenerationParameters file="Content/Map/LevelGenerationParameters.xml" /> <LevelGenerationParameters file="Content/Map/LevelGenerationParameters.xml" />
<Missions file="Content/Missions.xml" /> <Missions file="Content/Missions.xml" />
<TraitorMissions file="Content/TraitorMissions.xml" /> <TraitorMissions file="Content/TraitorMissions.xml" />
<NPCConversations file="Content/NPCConversations/English/NpcConversations_English.xml"/> <NPCConversations file="Content/NPCConversations/English/NpcConversations_English.xml" />
<NPCConversations file="Content/NPCConversations/German/NpcConversations_German.xml"/> <NPCConversations file="Content/NPCConversations/German/NpcConversations_German.xml" />
<NPCConversations file="Content/NPCConversations/French/NpcConversations_French.xml"/> <NPCConversations file="Content/NPCConversations/French/NpcConversations_French.xml" />
<NPCConversations file="Content/NPCConversations/Russian/NpcConversations_Russian.xml"/> <NPCConversations file="Content/NPCConversations/Russian/NpcConversations_Russian.xml" />
<NPCConversations file="Content/NPCConversations/Polish/NpcConversations_Polish.xml"/> <NPCConversations file="Content/NPCConversations/Polish/NpcConversations_Polish.xml" />
<NPCConversations file="Content/NPCConversations/BrazilianPortuguese/NpcConversations_BrazilianPortuguese.xml"/> <NPCConversations file="Content/NPCConversations/BrazilianPortuguese/NpcConversations_BrazilianPortuguese.xml" />
<NPCConversations file="Content/NPCConversations/CastilianSpanish/NpcConversations_CastilianSpanish.xml"/> <NPCConversations file="Content/NPCConversations/CastilianSpanish/NpcConversations_CastilianSpanish.xml" />
<NPCConversations file="Content/NPCConversations/LatinamericanSpanish/NpcConversations_LatinamericanSpanish.xml"/> <NPCConversations file="Content/NPCConversations/LatinamericanSpanish/NpcConversations_LatinamericanSpanish.xml" />
<NPCConversations file="Content/NPCConversations/SimplifiedChinese/NpcConversations_SimplifiedChinese.xml"/> <NPCConversations file="Content/NPCConversations/SimplifiedChinese/NpcConversations_SimplifiedChinese.xml" />
<NPCConversations file="Content/NPCConversations/TraditionalChinese/NpcConversations_TraditionalChinese.xml"/> <NPCConversations file="Content/NPCConversations/TraditionalChinese/NpcConversations_TraditionalChinese.xml" />
<NPCConversations file="Content/NPCConversations/Japanese/NpcConversations_Japanese.xml"/> <NPCConversations file="Content/NPCConversations/Japanese/NpcConversations_Japanese.xml" />
<NPCConversations file="Content/NPCConversations/Turkish/NpcConversations_Turkish.xml"/> <NPCConversations file="Content/NPCConversations/Turkish/NpcConversations_Turkish.xml" />
<NPCConversations file="Content/NPCConversations/Korean/NpcConversations_Korean.xml"/> <NPCConversations file="Content/NPCConversations/Korean/NpcConversations_Korean.xml" />
<Jobs file="Content/Jobs.xml" /> <Jobs file="Content/Jobs.xml" />
<Orders file="Content/Orders.xml" /> <Orders file="Content/Orders.xml" />
<Sounds file="Content/Sounds/sounds.xml" /> <Sounds file="Content/Sounds/sounds.xml" />
@@ -141,4 +141,4 @@
<SkillSettings file="Content/SkillSettings.xml" /> <SkillSettings file="Content/SkillSettings.xml" />
<Executable file="Barotrauma" /> <Executable file="Barotrauma" />
<ServerExecutable file="DedicatedServer" /> <ServerExecutable file="DedicatedServer" />
</contentpackage> </contentpackage>
@@ -25,7 +25,7 @@ namespace Barotrauma
/// <summary> /// <summary>
/// How long does it take for the ai target to fade out if not kept alive. /// How long does it take for the ai target to fade out if not kept alive.
/// </summary> /// </summary>
public float FadeOutTime { get; private set; } = 1; public float FadeOutTime { get; private set; } = 2;
public bool Static { get; private set; } public bool Static { get; private set; }
public bool StaticSound { get; private set; } public bool StaticSound { get; private set; }
@@ -92,7 +92,7 @@ namespace Barotrauma
public string SonarLabel; public string SonarLabel;
public string SonarIconIdentifier; public string SonarIconIdentifier;
public bool Enabled = true; public bool Enabled => SoundRange > 0 || SightRange > 0;
public float MinSoundRange, MinSightRange; public float MinSoundRange, MinSightRange;
public float MaxSoundRange = 100000, MaxSightRange = 100000; public float MaxSoundRange = 100000, MaxSightRange = 100000;
@@ -195,7 +195,7 @@ namespace Barotrauma
public void Update(float deltaTime) 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 // The aitarget goes silent/invisible if the components don't keep it active
if (!StaticSight) if (!StaticSight)
@@ -89,9 +89,6 @@ namespace Barotrauma
private readonly float memoryFadeTime = 0.5f; private readonly float memoryFadeTime = 0.5f;
private readonly float avoidTime = 3; private readonly float avoidTime = 3;
//Has the character been attacked since the last Update.
private bool wasAttacked;
private float avoidTimer; private float avoidTimer;
public LatchOntoAI LatchOntoAI { get; private set; } public LatchOntoAI LatchOntoAI { get; private set; }
@@ -234,13 +231,6 @@ namespace Barotrauma
public override void Update(float deltaTime) public override void Update(float deltaTime)
{ {
if (wasAttacked)
{
LatchOntoAI?.DeattachFromBody();
Character.AnimController.ReleaseStuckLimbs();
wasAttacked = false;
}
if (DisableEnemyAI) { return; } if (DisableEnemyAI) { return; }
base.Update(deltaTime); base.Update(deltaTime);
@@ -305,7 +295,8 @@ namespace Barotrauma
FadeMemories(updateMemoriesInverval); FadeMemories(updateMemoriesInverval);
updateMemoriesTimer = 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; State = AIState.Flee;
wallTarget = null; wallTarget = null;
@@ -384,9 +375,9 @@ namespace Barotrauma
State = AIState.Idle; State = AIState.Idle;
return; return;
} }
float distance = Vector2.DistanceSquared(WorldPosition, SelectedAiTarget.WorldPosition); float squaredDistance = Vector2.DistanceSquared(WorldPosition, SelectedAiTarget.WorldPosition);
var attackLimb = GetAttackLimb(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; run = true;
if (State == AIState.Avoid) if (State == AIState.Avoid)
@@ -402,17 +393,18 @@ namespace Barotrauma
{ {
bool isBeingChased = IsBeingChased; bool isBeingChased = IsBeingChased;
float reactDistance = !isBeingChased && selectedTargetingParams != null && selectedTargetingParams.ReactDistance > 0 ? selectedTargetingParams.ReactDistance : GetPerceivingRange(SelectedAiTarget); 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; 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; run = true;
UpdateAttack(deltaTime); UpdateAttack(deltaTime);
} }
else else
{ {
run = isBeingChased ? true : distance < Math.Pow(halfReactDistance, 2); run = isBeingChased ? true : squaredDistance < Math.Pow(halfReactDistance, 2);
if (escapeMargin <= 0) if (escapeMargin <= 0)
{ {
escapeMargin = halfReactDistance; escapeMargin = halfReactDistance;
@@ -440,13 +432,14 @@ namespace Barotrauma
SwarmBehavior.Refresh(); SwarmBehavior.Refresh();
SwarmBehavior.UpdateSteering(deltaTime); 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) if (Character.CurrentHull != null && Character.AnimController.InWater)
{ {
// Halve the swimming speed inside the sub // Halve the swimming speed inside the sub
speed /= 2; Character.AnimController.TargetMovement *= 0.5f;
} }
steeringManager.Update(speed);
} }
#region Idle #region Idle
@@ -577,7 +570,7 @@ namespace Barotrauma
else if (SelectedAiTarget?.Entity is Character targetCharacter && targetCharacter.CurrentHull == Character.CurrentHull) else if (SelectedAiTarget?.Entity is Character targetCharacter && targetCharacter.CurrentHull == Character.CurrentHull)
{ {
// Steer away from the target if in the same room // 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; if (!MathUtils.IsValid(escapeDir)) escapeDir = Vector2.UnitY;
SteeringManager.SteeringManual(deltaTime, escapeDir); SteeringManager.SteeringManual(deltaTime, escapeDir);
} }
@@ -615,7 +608,7 @@ namespace Barotrauma
{ {
escapeTarget = null; escapeTarget = null;
allGapsSearched = false; 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; if (!MathUtils.IsValid(escapeDir)) escapeDir = Vector2.UnitY;
SteeringManager.SteeringManual(deltaTime, escapeDir); SteeringManager.SteeringManual(deltaTime, escapeDir);
if (Character.CurrentHull == null) if (Character.CurrentHull == null)
@@ -758,6 +751,10 @@ namespace Barotrauma
bool pursue = false; bool pursue = false;
if (IsCoolDownRunning) if (IsCoolDownRunning)
{ {
if (AttackingLimb.attack.CoolDownTimer >= AttackingLimb.attack.CoolDown + AttackingLimb.attack.CurrentRandomCoolDown - AttackingLimb.attack.AfterAttackDelay)
{
return;
}
switch (AttackingLimb.attack.AfterAttack) switch (AttackingLimb.attack.AfterAttack)
{ {
case AIBehaviorAfterAttack.Pursue: case AIBehaviorAfterAttack.Pursue:
@@ -1017,58 +1014,63 @@ namespace Barotrauma
return; return;
} }
Vector2 offset = Character.SimPosition - steeringLimb.SimPosition; if (AttackingLimb != null && AttackingLimb.attack.Retreat)
// Offset so that we don't overshoot the movement
Vector2 steerPos = attackSimPos + offset;
if (SteeringManager is IndoorsSteeringManager pathSteering)
{ {
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 (pathSteering.CurrentPath != null)
if (canAttackSub)
{ {
// If the target is in the same hull, there shouldn't be any doors blocking the path // Attack doors
if (targetCharacter == null || targetCharacter.CurrentHull != Character.CurrentHull) if (canAttackSub)
{ {
var door = pathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? pathSteering.CurrentPath.NextNode?.ConnectedDoor; // If the target is in the same hull, there shouldn't be any doors blocking the path
if (door != null && !door.IsOpen) 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); if (door.Item.AiTarget != null && SelectedAiTarget != door.Item.AiTarget)
return; {
SelectTarget(door.Item.AiTarget, selectedTargetMemory.Priority);
return;
}
} }
} }
} }
} // Steer towards the target if in the same room and swimming
// Steer towards the target if in the same room and swimming if ((Character.AnimController.InWater || pursue) && targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull))
if ((Character.AnimController.InWater || pursue) && targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull)) {
{ SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(attackSimPos - steeringLimb.SimPosition));
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 else
{ {
SteeringManager.SteeringSeek(steerPos, 2); SteeringManager.SteeringSeek(steerPos, 5);
// 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 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 (canAttack)
{ {
if (!UpdateLimbAttack(deltaTime, AttackingLimb, attackSimPos, distance, attackTargetLimb)) if (!UpdateLimbAttack(deltaTime, AttackingLimb, attackSimPos, distance, attackTargetLimb))
@@ -1259,9 +1261,16 @@ namespace Barotrauma
float reactionTime = Rand.Range(0.1f, 0.3f); float reactionTime = Rand.Range(0.1f, 0.3f);
updateTargetsTimer = Math.Min(updateTargetsTimer, reactionTime); updateTargetsTimer = Math.Min(updateTargetsTimer, reactionTime);
wasAttacked = true; bool wasLatched = IsLatchedOnSub;
Character.AnimController.ReleaseStuckLimbs();
LatchOntoAI?.DeattachFromBody();
if (attacker == null || attacker.AiTarget == null) { return; } 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) if (State == AIState.Flee)
{ {
@@ -1272,22 +1281,30 @@ namespace Barotrauma
if (attackResult.Damage > 0.0f) if (attackResult.Damage > 0.0f)
{ {
bool canAttack = attacker.Submarine == Character.Submarine && canAttackCharacters || attacker.Submarine != null && canAttackSub; 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); ChangeTargetState(attacker, AIState.Attack, 100);
} }
} }
else if (!AIParams.HasTag(attacker.SpeciesName)) 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 (enemyAI.CombatStrength > CombatStrength)
{ {
if (!AIParams.HasTag("stronger")) if (!AIParams.HasTag("stronger"))
{ {
ChangeTargetState(attacker, AIState.Escape, 100); ChangeTargetState(attacker, canAttack ? AIState.Attack : AIState.Escape, 100);
} }
} }
else if (enemyAI.CombatStrength < CombatStrength) else if (enemyAI.CombatStrength < CombatStrength)
@@ -1305,7 +1322,7 @@ namespace Barotrauma
} }
else 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) if (targetCharacter.Submarine != Character.Submarine)
{ {
// In a different sub or the target is outside when we are inside or vice versa. // 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 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) 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) // Ignore targets that are in the same group (treat them like they were of the same species)
continue; continue;
} }
if (enemy.CombatStrength > CombatStrength) if (targetCharacter.IsHusk && AIParams.HasTag("husk"))
{ {
targetingTag = "stronger"; targetingTag = "husk";
} }
else if (enemy.CombatStrength < CombatStrength) else
{ {
targetingTag = "weaker"; if (enemy.CombatStrength > CombatStrength)
}
if (targetingTag == "stronger" && (State == AIState.Avoid || State == AIState.Escape || State == AIState.Flee))
{
if (SelectedAiTarget == aiTarget)
{ {
// Freightened -> hold on to the target targetingTag = "stronger";
valueModifier *= 2;
} }
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 if (SelectedAiTarget == aiTarget)
valueModifier /= 2; {
// 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) 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; newTarget = aiTarget;
selectedTargetMemory = targetMemory; selectedTargetMemory = targetMemory;
targetValue = valueModifier; targetValue = valueModifier;
@@ -1867,6 +1895,39 @@ namespace Barotrauma
private readonly Dictionary<string, CharacterParams.TargetParams> modifiedParams = new Dictionary<string, CharacterParams.TargetParams>(); private readonly Dictionary<string, CharacterParams.TargetParams> modifiedParams = new Dictionary<string, CharacterParams.TargetParams>();
private readonly Dictionary<string, CharacterParams.TargetParams> tempParams = new Dictionary<string, CharacterParams.TargetParams>(); private readonly Dictionary<string, CharacterParams.TargetParams> tempParams = new Dictionary<string, CharacterParams.TargetParams>();
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);
}
/// <summary> /// <summary>
/// Temporarily changes the predefined state for a target. Eg. Idle -> Attack. /// Temporarily changes the predefined state for a target. Eg. Idle -> Attack.
/// </summary> /// </summary>
@@ -1874,50 +1935,27 @@ namespace Barotrauma
{ {
isStateChanged = true; isStateChanged = true;
SetStateResetTimer(); SetStateResetTimer();
ChangeParams(target.SpeciesName); ChangeParams(target.SpeciesName, state, priority);
// Target also items, because if we are blind and the target doesn't move, we can only perceive the target when it uses items if (target.IsHuman)
if (state == AIState.Attack || state == AIState.Escape)
{ {
ChangeParams("weapon"); // Target also items, because if we are blind and the target doesn't move, we can only perceive the target when it uses items
ChangeParams("tool"); if (state == AIState.Attack || state == AIState.Escape)
}
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)
{ {
ChangeParams("room"); ChangeParams("weapon", state, priority);
ChangeParams("wall"); ChangeParams("tool", state, priority);
ChangeParams("door");
} }
ChangeParams("provocative", onlyExisting: true); if (state == AIState.Attack)
ChangeParams("light", onlyExisting: true);
}
void ChangeParams(string tag, bool onlyExisting = false)
{
if (!AIParams.TryGetTarget(tag, out CharacterParams.TargetParams targetParams))
{ {
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)) ChangeParams("room", state, priority);
{ ChangeParams("wall", state, priority);
tempParams.Add(tag, targetParams); ChangeParams("door", state, priority);
}
}
}
if (targetParams != null)
{
if (priority.HasValue)
{
targetParams.Priority = priority.Value;
}
targetParams.State = state;
if (!modifiedParams.ContainsKey(tag))
{
modifiedParams.Add(tag, targetParams);
} }
ChangeParams("provocative", state, priority, onlyExisting: true);
ChangeParams("light", state, priority, onlyExisting: true);
} }
} }
} }
@@ -170,12 +170,7 @@ namespace Barotrauma
} }
} }
} }
if (run) steeringManager.Update(Character.AnimController.GetCurrentSpeed(run && Character.CanRun));
{
run = !AnimController.Crouching && !AnimController.IsMovingBackwards;
}
float currentSpeed = Character.AnimController.GetCurrentSpeed(run);
steeringManager.Update(currentSpeed);
bool ignorePlatforms = Character.AnimController.TargetMovement.Y < -0.5f && bool ignorePlatforms = Character.AnimController.TargetMovement.Y < -0.5f &&
(-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X)); (-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)); 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) if (Character.AnimController.InWater && targetMovement.LengthSquared() < 0.000001f)
{ {
bool isAiming = false; bool isAiming = false;
@@ -243,7 +227,7 @@ namespace Barotrauma
} }
} }
Character.AnimController.TargetMovement = targetMovement; Character.AnimController.TargetMovement = Character.ApplyMovementLimits(targetMovement, AnimController.GetCurrentSpeed(run));
flipTimer -= deltaTime; flipTimer -= deltaTime;
if (flipTimer <= 0.0f) if (flipTimer <= 0.0f)
@@ -323,7 +307,7 @@ namespace Barotrauma
|| ObjectiveManager.IsCurrentObjective<AIObjectiveFindSafety>() || ObjectiveManager.IsCurrentObjective<AIObjectiveFindSafety>()
|| ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(true).Any(o => o.KeepDivingGearOn); || ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(true).Any(o => o.KeepDivingGearOn);
bool removeDivingSuit = !Character.AnimController.HeadInWater && oxygenLow; bool removeDivingSuit = !Character.AnimController.HeadInWater && oxygenLow;
AIObjectiveGoTo gotoObjective = ObjectiveManager.CurrentOrder as AIObjectiveGoTo; AIObjectiveGoTo gotoObjective = ObjectiveManager.GetActiveObjective<AIObjectiveGoTo>();
if (!removeDivingSuit) if (!removeDivingSuit)
{ {
bool targetHasNoSuit = gotoObjective != null && gotoObjective.mimic && !HasDivingSuit(gotoObjective.Target as Character); bool targetHasNoSuit = gotoObjective != null && gotoObjective.mimic && !HasDivingSuit(gotoObjective.Target as Character);
@@ -536,14 +520,16 @@ namespace Barotrauma
Hull targetHull = null; Hull targetHull = null;
if (Character.CurrentHull != null) if (Character.CurrentHull != null)
{ {
bool isFighting = ObjectiveManager.HasActiveObjective<AIObjectiveCombat>();
bool isFleeing = ObjectiveManager.HasActiveObjective<AIObjectiveFindSafety>();
foreach (var hull in VisibleHulls) 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 (target.CurrentHull != hull || !target.Enabled) { continue; }
if (AIObjectiveFightIntruders.IsValidTarget(c, Character)) if (AIObjectiveFightIntruders.IsValidTarget(target, Character))
{ {
if (AddTargets<AIObjectiveFightIntruders, Character>(Character, c) && newOrder == null) if (AddTargets<AIObjectiveFightIntruders, Character>(Character, target) && newOrder == null)
{ {
var orderPrefab = Order.GetPrefab("reportintruders"); var orderPrefab = Order.GetPrefab("reportintruders");
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character); newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
@@ -560,42 +546,48 @@ namespace Barotrauma
targetHull = hull; targetHull = hull;
} }
} }
foreach (Character c in Character.CharacterList) if (!isFighting)
{ {
if (c.CurrentHull != hull) { continue; } foreach (var gap in hull.ConnectedGaps)
if (AIObjectiveRescueAll.IsValidTarget(c, Character))
{ {
if (AddTargets<AIObjectiveRescueAll, Character>(c, Character) && newOrder == null && !ObjectiveManager.HasActiveObjective<AIObjectiveRescue>()) if (AIObjectiveFixLeaks.IsValidTarget(gap, Character))
{ {
var orderPrefab = Order.GetPrefab("requestfirstaid"); if (AddTargets<AIObjectiveFixLeaks, Gap>(Character, gap) && newOrder == null && !gap.IsRoomToRoom)
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character); {
targetHull = hull; var orderPrefab = Order.GetPrefab("reportbreach");
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
targetHull = hull;
}
} }
} }
} if (!isFleeing)
foreach (var gap in hull.ConnectedGaps)
{
if (AIObjectiveFixLeaks.IsValidTarget(gap, Character))
{ {
if (AddTargets<AIObjectiveFixLeaks, Gap>(Character, gap) && newOrder == null && !gap.IsRoomToRoom) foreach (Character target in Character.CharacterList)
{ {
var orderPrefab = Order.GetPrefab("reportbreach"); if (target.CurrentHull != hull) { continue; }
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character); if (AIObjectiveRescueAll.IsValidTarget(target, Character))
targetHull = hull; {
if (AddTargets<AIObjectiveRescueAll, Character>(Character, target) && newOrder == null && !ObjectiveManager.HasActiveObjective<AIObjectiveRescue>())
{
var orderPrefab = Order.GetPrefab("requestfirstaid");
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
targetHull = hull;
}
}
} }
} foreach (Item item in Item.ItemList)
}
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<AIObjectiveRepairItems, Item>(Character, item) && newOrder == null && !ObjectiveManager.HasActiveObjective<AIObjectiveRepairItem>())
{ {
var orderPrefab = Order.GetPrefab("reportbrokendevices"); if (item.CurrentHull != hull) { continue; }
newOrder = new Order(orderPrefab, hull, item.Repairables?.FirstOrDefault(), orderGiver: Character); if (AIObjectiveRepairItems.IsValidTarget(item, Character))
targetHull = hull; {
if (item.Repairables.All(r => item.ConditionPercentage > r.AIRepairThreshold)) { continue; }
if (AddTargets<AIObjectiveRepairItems, Item>(Character, item) && newOrder == null && !ObjectiveManager.HasActiveObjective<AIObjectiveRepairItem>())
{
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). // Should not cancel any existing ai objectives (so that if the character attacked you and then helped, we still would want to retaliate).
return; 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 // 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)); AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced));
@@ -664,9 +656,8 @@ namespace Barotrauma
} }
else else
{ {
float currentVitality = Character.CharacterHealth.Vitality; float dmgPercentage = MathUtils.Percentage(damage, Character.CharacterHealth.Vitality);
float dmgPercentage = damage / currentVitality * 100; if (dmgPercentage < 10)
if (dmgPercentage < currentVitality / 10)
{ {
// Don't retaliate on minor (accidental) dmg done by characters that are in the same team // 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)); 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) public void SetOrder(Order order, string option, Character orderGiver, bool speak = true)
{ {
SetOrderProjSpecific(order, option);
CurrentOrderOption = option; CurrentOrderOption = option;
CurrentOrder = order; CurrentOrder = order;
objectiveManager.SetOrder(order, option, orderGiver); objectiveManager.SetOrder(order, option, orderGiver);
@@ -752,8 +742,6 @@ namespace Barotrauma
} }
} }
partial void SetOrderProjSpecific(Order order, string option);
public override void SelectTarget(AITarget target) public override void SelectTarget(AITarget target)
{ {
SelectedAiTarget = target; SelectedAiTarget = target;
@@ -806,11 +794,11 @@ namespace Barotrauma
/// </summary> /// </summary>
public static bool HasDivingMask(Character character, float conditionPercentage = 0) => HasItem(character, "divingmask", "oxygensource", conditionPercentage); 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 == null) { return false; }
if (character.Inventory == 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 && return item != null &&
item.ConditionPercentage > conditionPercentage && item.ConditionPercentage > conditionPercentage &&
character.HasEquippedItem(item) && character.HasEquippedItem(item) &&
@@ -934,7 +922,7 @@ namespace Barotrauma
visibleHulls = VisibleHulls; visibleHulls = VisibleHulls;
} }
// TODO: should we calculate the visible hulls for each hull? -> could be a bit heavy. // TODO: should we calculate the visible hulls for each hull? -> could be a bit heavy.
bool ignoreFire = ObjectiveManager.IsCurrentObjective<AIObjectiveExtinguishFires>() || objectiveManager.HasActiveObjective<AIObjectiveExtinguishFire>(); bool ignoreFire = objectiveManager.HasActiveObjective<AIObjectiveExtinguishFire>();
bool ignoreWater = HasDivingSuit(character); bool ignoreWater = HasDivingSuit(character);
bool ignoreOxygen = ignoreWater || HasDivingMask(character); bool ignoreOxygen = ignoreWater || HasDivingMask(character);
bool ignoreEnemies = ObjectiveManager.IsCurrentObjective<AIObjectiveFightIntruders>(); bool ignoreEnemies = ObjectiveManager.IsCurrentObjective<AIObjectiveFightIntruders>();
@@ -1022,15 +1010,23 @@ namespace Barotrauma
return false; return false;
} }
public static int CountCrew(Character character, Func<HumanAIController, bool> predicate = null) public static int CountCrew(Character character, Func<HumanAIController, bool> predicate = null, bool onlyActive = true, bool onlyBots = false)
{ {
if (character == null) { return 0; } if (character == null) { return 0; }
int count = 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++; count++;
} }
@@ -1058,7 +1054,7 @@ namespace Barotrauma
public void DoForEachCrewMember(Action<HumanAIController> action) => DoForEachCrewMember(Character, action); public void DoForEachCrewMember(Action<HumanAIController> action) => DoForEachCrewMember(Character, action);
public bool IsTrueForAnyCrewMember(Func<HumanAIController, bool> predicate) => IsTrueForAnyCrewMember(Character, predicate); public bool IsTrueForAnyCrewMember(Func<HumanAIController, bool> predicate) => IsTrueForAnyCrewMember(Character, predicate);
public bool IsTrueForAllCrewMembers(Func<HumanAIController, bool> predicate) => IsTrueForAllCrewMembers(Character, predicate); public bool IsTrueForAllCrewMembers(Func<HumanAIController, bool> predicate) => IsTrueForAllCrewMembers(Character, predicate);
public int CountCrew(Func<HumanAIController, bool> predicate = null) => CountCrew(Character, predicate); public int CountCrew(Func<HumanAIController, bool> predicate = null, bool onlyActive = true, bool onlyBots = false) => CountCrew(Character, predicate, onlyActive, onlyBots);
#endregion #endregion
} }
} }
@@ -134,7 +134,7 @@ namespace Barotrauma
private Vector2 CalculateSteeringSeek(Vector2 target, float weight, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null) private Vector2 CalculateSteeringSeek(Vector2 target, float weight, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> 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 //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) if (needsNewPath || findPathTimer < -1.0f)
{ {
@@ -308,7 +308,7 @@ namespace Barotrauma
currentPath.SkipToNextNode(); currentPath.SkipToNextNode();
} }
} }
else else if (!IsNextLadderSameAsCurrent)
{ {
Vector2 colliderBottom = character.AnimController.GetColliderBottom(); Vector2 colliderBottom = character.AnimController.GetColliderBottom();
Vector2 colliderSize = collider.GetSize(); Vector2 colliderSize = collider.GetSize();
@@ -530,7 +530,6 @@ namespace Barotrauma
if (node.Waypoint != null && node.Waypoint.CurrentHull != null) if (node.Waypoint != null && node.Waypoint.CurrentHull != null)
{ {
var hull = node.Waypoint.CurrentHull; var hull = node.Waypoint.CurrentHull;
if (hull.FireSources.Count > 0) if (hull.FireSources.Count > 0)
{ {
foreach (FireSource fs in hull.FireSources) foreach (FireSource fs in hull.FireSources)
@@ -538,9 +537,14 @@ namespace Barotrauma
penalty += fs.Size.X * 10.0f; penalty += fs.Size.X * 10.0f;
} }
} }
if (character.NeedsAir && hull.WaterVolume / hull.Rect.Width > 100.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; penalty += 500.0f;
}
if (character.PressureProtection < 10.0f && hull.WaterVolume > hull.Volume)
{
penalty += 1000.0f;
}
} }
return penalty; return penalty;
@@ -253,7 +253,7 @@ namespace Barotrauma
jointDir = attachLimb.Dir; 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; if (jointDir < 0.0f) transformedLocalAttachPos.X = -transformedLocalAttachPos.X;
//transformedLocalAttachPos = Vector2.Transform(transformedLocalAttachPos, Matrix.CreateRotationZ(attachLimb.Rotation)); //transformedLocalAttachPos = Vector2.Transform(transformedLocalAttachPos, Matrix.CreateRotationZ(attachLimb.Rotation));
@@ -32,6 +32,18 @@ namespace Barotrauma
public virtual bool UnequipItems => false; public virtual bool UnequipItems => false;
protected readonly List<AIObjective> subObjectives = new List<AIObjective>(); protected readonly List<AIObjective> subObjectives = new List<AIObjective>();
private float _cumulatedDevotion;
protected float CumulatedDevotion
{
get { return _cumulatedDevotion; }
set { _cumulatedDevotion = MathHelper.Clamp(value, 0, MaxDevotion); }
}
protected virtual float MaxDevotion => 10;
/// <summary>
/// Final priority value after all calculations.
/// </summary>
public float Priority { get; set; } public float Priority { get; set; }
public float PriorityModifier { get; private set; } = 1; public float PriorityModifier { get; private set; } = 1;
public readonly Character character; public readonly Character character;
@@ -59,6 +71,7 @@ namespace Barotrauma
/// </summary> /// </summary>
public virtual bool IsLoop { get; set; } public virtual bool IsLoop { get; set; }
public IEnumerable<AIObjective> SubObjectives => subObjectives; public IEnumerable<AIObjective> SubObjectives => subObjectives;
public AIObjective CurrentSubObjective => subObjectives.FirstOrDefault();
private readonly List<AIObjective> all = new List<AIObjective>(); private readonly List<AIObjective> all = new List<AIObjective>();
public IEnumerable<AIObjective> GetSubObjectivesRecursive(bool includingSelf = false) public IEnumerable<AIObjective> GetSubObjectivesRecursive(bool includingSelf = false)
@@ -86,7 +99,7 @@ namespace Barotrauma
public AIObjective GetActiveObjective() public AIObjective GetActiveObjective()
{ {
var subObjective = SubObjectives.FirstOrDefault(); var subObjective = CurrentSubObjective;
return subObjective == null ? this : subObjective.GetActiveObjective(); return subObjective == null ? this : subObjective.GetActiveObjective();
} }
@@ -157,7 +170,8 @@ namespace Barotrauma
{ {
if (!AllowSubObjectiveSorting) { return; } if (!AllowSubObjectiveSorting) { return; }
if (subObjectives.None()) { 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) if (ConcurrentObjectives)
{ {
subObjectives.ForEach(so => so.SortSubObjectives()); subObjectives.ForEach(so => so.SortSubObjectives());
@@ -168,26 +182,38 @@ namespace Barotrauma
} }
} }
public virtual float GetPriority() => Priority * PriorityModifier; /// <summary>
/// Call this only when the priority needs to be recalculated. Use the cached Priority property when you don't need to recalculate.
public virtual bool IsDuplicate<T>(T otherObjective) where T : AIObjective => otherObjective.Option == Option; /// </summary>
public virtual float GetPriority()
public virtual void Update(float deltaTime)
{ {
if (objectiveManager.CurrentOrder == this) if (objectiveManager.CurrentOrder == this)
{ {
Priority = AIObjectiveManager.OrderPriority; Priority = AIObjectiveManager.OrderPriority;
} }
else if (objectiveManager.WaitTimer <= 0) else
{ {
if (objectiveManager.CurrentObjective != null) Priority = CumulatedDevotion * PriorityModifier;
{ }
if (objectiveManager.CurrentObjective == this || objectiveManager.CurrentObjective.subObjectives.Any(so => so == this)) return Priority;
{ }
Priority += Devotion * PriorityModifier * deltaTime;
} private void UpdateDevotion(float deltaTime)
} {
Priority = MathHelper.Clamp(Priority, 0, 100); var currentObjective = objectiveManager.CurrentObjective;
if (currentObjective != null && (currentObjective == this || currentObjective.subObjectives.Any(so => so == this)))
{
CumulatedDevotion += Devotion * PriorityModifier * deltaTime;
}
}
public virtual bool IsDuplicate<T>(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)); subObjectives.ForEach(so => so.Update(deltaTime));
} }
@@ -264,6 +290,7 @@ namespace Barotrauma
public virtual void OnDeselected() public virtual void OnDeselected()
{ {
CumulatedDevotion = 0;
Deselected?.Invoke(); Deselected?.Invoke();
} }
@@ -282,6 +309,7 @@ namespace Barotrauma
isCompleted = false; isCompleted = false;
hasBeenChecked = false; hasBeenChecked = false;
_abandon = false; _abandon = false;
CumulatedDevotion = 0;
} }
protected abstract void Act(float deltaTime); protected abstract void Act(float deltaTime);
@@ -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) public override void Update(float deltaTime)
{ {
@@ -139,7 +143,7 @@ namespace Barotrauma
} }
if (seekAmmunition == null) if (seekAmmunition == null)
{ {
if (TryArm() && Enemy != null && !Enemy.Removed) if (Mode != CombatMode.Retreat && TryArm() && Enemy != null && !Enemy.Removed)
{ {
OperateWeapon(deltaTime); OperateWeapon(deltaTime);
} }
@@ -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; private bool CheckItem(Item i) => itemIdentifiers.Any(id => i.Prefab.Identifier == id || i.HasTag(id)) && i.ConditionPercentage > ConditionLevel;
protected override void Act(float deltaTime) protected override void Act(float deltaTime)
@@ -54,15 +54,6 @@ namespace Barotrauma
protected override bool Check() => IsCompleted; 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) 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); Item itemToDecontain = targetItem ?? sourceContainer.Inventory.FindItem(i => itemIdentifiers.Any(id => i.Prefab.Identifier == id || i.HasTag(id)), recursive: false);
@@ -1,5 +1,4 @@
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
using FarseerPhysics;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Linq; using System.Linq;
@@ -29,32 +28,44 @@ namespace Barotrauma
public override float GetPriority() public override float GetPriority()
{ {
if (!objectiveManager.IsCurrentOrder<AIObjectiveExtinguishFires>() if (!objectiveManager.IsCurrentOrder<AIObjectiveExtinguishFires>()
&& Character.CharacterList.Any(c => c.CurrentHull == targetHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { return 0; } && Character.CharacterList.Any(c => c.CurrentHull == targetHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c)))
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; Priority = 0;
} }
float severity = AIObjectiveExtinguishFires.GetFireSeverity(targetHull); else
float severityFactor = MathHelper.Lerp(0, 1, severity / 100); {
float devotion = Math.Min(Priority, 10) / 100; float yDist = Math.Abs(character.WorldPosition.Y - targetHull.WorldPosition.Y);
return MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + severityFactor * distanceFactor, 0, 1)); 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(); protected override bool Check() => targetHull.FireSources.None();
private float sinTime;
protected override void Act(float deltaTime) protected override void Act(float deltaTime)
{ {
var extinguisherItem = character.Inventory.FindItemByIdentifier("extinguisher") ?? character.Inventory.FindItemByTag("extinguisher"); var extinguisherItem = character.Inventory.FindItemByIdentifier("fireextinguisher") ?? character.Inventory.FindItemByTag("fireextinguisher");
if (extinguisherItem == null || extinguisherItem.Condition <= 0.0f || !character.HasEquippedItem(extinguisherItem)) if (extinguisherItem == null || extinguisherItem.Condition <= 0.0f || !character.HasEquippedItem(extinguisherItem))
{ {
TryAddSubObjective(ref getExtinguisherObjective, () => TryAddSubObjective(ref getExtinguisherObjective, () =>
{ {
character.Speak(TextManager.Get("DialogFindExtinguisher"), null, 2.0f, "findextinguisher", 30.0f); character.Speak(TextManager.Get("DialogFindExtinguisher"), null, 2.0f, "findextinguisher", 30.0f);
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 else
@@ -79,8 +90,12 @@ namespace Barotrauma
{ {
useExtinquisherTimer = 0.0f; useExtinquisherTimer = 0.0f;
} }
// Aim
character.CursorPosition = fs.Position; 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; bool isOperatingButtons = false;
if (SteeringManager == PathSteering) if (SteeringManager == PathSteering)
@@ -95,8 +110,9 @@ namespace Barotrauma
{ {
character.SetInput(InputType.Aim, false, true); 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); extinguisher.Use(deltaTime, character);
if (!targetHull.FireSources.Contains(fs)) if (!targetHull.FireSources.Contains(fs))
{ {
@@ -110,7 +126,7 @@ namespace Barotrauma
if (move) if (move)
{ {
//go to the first firesource //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", DialogueIdentifier = "dialogcannotreachfire",
TargetName = fs.Hull.DisplayName TargetName = fs.Hull.DisplayName
@@ -9,7 +9,6 @@ namespace Barotrauma
{ {
public override string DebugTag => "extinguish fires"; public override string DebugTag => "extinguish fires";
public override bool ForceRun => true; public override bool ForceRun => true;
public override bool IgnoreUnsafeHulls => true;
public AIObjectiveExtinguishFires(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { } public AIObjectiveExtinguishFires(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
@@ -9,7 +9,6 @@ namespace Barotrauma
public override string DebugTag => $"find diving gear ({gearTag})"; public override string DebugTag => $"find diving gear ({gearTag})";
public override bool ForceRun => true; public override bool ForceRun => true;
public override bool KeepDivingGearOn => true; public override bool KeepDivingGearOn => true;
public override bool IgnoreUnsafeHulls => true;
private readonly string gearTag; private readonly string gearTag;
private readonly string fallbackTag; private readonly string fallbackTag;
@@ -26,13 +26,35 @@ namespace Barotrauma
private AIObjectiveGoTo goToObjective; private AIObjectiveGoTo goToObjective;
private AIObjectiveFindDivingGear divingGearObjective; private AIObjectiveFindDivingGear divingGearObjective;
public AIObjectiveFindSafety(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { } public AIObjectiveFindSafety(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
protected override bool Check() => false; protected override bool Check() => false;
public override bool CanBeCompleted => true; public override bool CanBeCompleted => true;
private bool resetPriority; 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) public override void Update(float deltaTime)
{ {
if (resetPriority) if (resetPriority)
@@ -44,28 +66,19 @@ namespace Barotrauma
if (character.CurrentHull == null) if (character.CurrentHull == null)
{ {
currenthullSafety = 0; 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 else
{ {
float dangerFactor = (100 - currenthullSafety) / 100; currenthullSafety = HumanAIController.CurrentHullSafety;
Priority += dangerFactor * priorityIncrease * deltaTime; if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD)
} {
Priority = MathHelper.Clamp(Priority, 0, 100); Priority -= priorityDecrease * deltaTime;
if (divingGearObjective != null && !divingGearObjective.IsCompleted && divingGearObjective.CanBeCompleted) }
{ else
// Boost the priority while seeking the diving gear {
Priority = Math.Max(Priority, Math.Min(AIObjectiveManager.OrderPriority + 20, 100)); float dangerFactor = (100 - currenthullSafety) / 100;
Priority += dangerFactor * priorityIncrease * deltaTime;
}
} }
} }
@@ -29,16 +29,23 @@ namespace Barotrauma
public override float GetPriority() public override float GetPriority()
{ {
if (Leak.Removed || Leak.Open <= 0) { return 0; } if (Leak.Removed || Leak.Open <= 0)
float xDist = Math.Abs(character.WorldPosition.X - Leak.WorldPosition.X); {
float yDist = Math.Abs(character.WorldPosition.Y - Leak.WorldPosition.Y); Priority = 0;
// 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. else
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 xDist = Math.Abs(character.WorldPosition.X - Leak.WorldPosition.X);
float max = Math.Min((AIObjectiveManager.OrderPriority - 1), 90); float yDist = Math.Abs(character.WorldPosition.Y - Leak.WorldPosition.Y);
float devotion = Math.Min(Priority, 10) / 100; // Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally).
return MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + severity * distanceFactor * PriorityModifier, 0, 1)); // 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) protected override void Act(float deltaTime)
@@ -11,7 +11,6 @@ namespace Barotrauma
public override string DebugTag => "fix leaks"; public override string DebugTag => "fix leaks";
public override bool ForceRun => true; public override bool ForceRun => true;
public override bool KeepDivingGearOn => true; public override bool KeepDivingGearOn => true;
public override bool IgnoreUnsafeHulls => true;
public AIObjectiveFixLeaks(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { } public AIObjectiveFixLeaks(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
@@ -36,7 +35,7 @@ namespace Barotrauma
protected override float TargetEvaluation() protected override float TargetEvaluation()
{ {
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveFixLeaks>()); int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveFixLeaks>(), onlyBots: true);
int totalLeaks = Targets.Count(); int totalLeaks = Targets.Count();
if (totalLeaks == 0) { return 0; } if (totalLeaks == 0) { return 0; }
int secondaryLeaks = Targets.Count(l => l.IsRoomToRoom); int secondaryLeaks = Targets.Count(l => l.IsRoomToRoom);
@@ -44,13 +43,13 @@ namespace Barotrauma
bool anyFixers = otherFixers > 0; bool anyFixers = otherFixers > 0;
if (objectiveManager.CurrentOrder == this) if (objectiveManager.CurrentOrder == this)
{ {
float ratio = anyFixers ? totalLeaks / otherFixers : 1; float ratio = anyFixers ? totalLeaks / (float)otherFixers : 1;
return Targets.Sum(t => GetLeakSeverity(t)) * ratio; return Targets.Sum(t => GetLeakSeverity(t)) * ratio;
} }
else else
{ {
float ratio = leaks == 0 ? 1 : anyFixers ? leaks / otherFixers : 1; 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 // Enough fixers
return 0; return 0;
@@ -31,15 +31,6 @@ namespace Barotrauma
public bool AllowToFindDivingGear { get; set; } = true; 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) public AIObjectiveGetItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, bool equip = true, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier) : base(character, objectiveManager, priorityModifier)
{ {
@@ -51,14 +51,23 @@ namespace Barotrauma
public override float GetPriority() public override float GetPriority()
{ {
if (followControlledCharacter && Character.Controlled == null) { return 0.0f; } if (followControlledCharacter && Character.Controlled == null)
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)
{ {
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) public AIObjectiveGoTo(ISpatialEntity target, Character character, AIObjectiveManager objectiveManager, bool repeat = false, bool getDivingGearIfNeeded = true, float priorityModifier = 1, float closeEnough = 0)
@@ -45,20 +45,17 @@ namespace Barotrauma
private float randomUpdateInterval = 5; private float randomUpdateInterval = 5;
public float Random { get; private set; } public float Random { get; private set; }
public void SetRandom() public void CalculatePriority()
{ {
Random = Rand.Range(0.5f, 1.5f); Random = Rand.Range(0.5f, 1.5f);
randomTimer = randomUpdateInterval; randomTimer = randomUpdateInterval;
}
public override float GetPriority()
{
float max = Math.Min(Math.Min(AIObjectiveManager.RunPriority, AIObjectiveManager.OrderPriority) - 1, 100); float max = Math.Min(Math.Min(AIObjectiveManager.RunPriority, AIObjectiveManager.OrderPriority) - 1, 100);
float initiative = character.GetSkillLevel("initiative"); float initiative = character.GetSkillLevel("initiative");
Priority = MathHelper.Lerp(1, max, MathUtils.InverseLerp(100, 0, initiative * Random)); Priority = MathHelper.Lerp(1, max, MathUtils.InverseLerp(100, 0, initiative * Random));
return Priority;
} }
public override float GetPriority() => Priority;
public override void Update(float deltaTime) public override void Update(float deltaTime)
{ {
if (objectiveManager.CurrentObjective == this) if (objectiveManager.CurrentObjective == this)
@@ -69,7 +66,7 @@ namespace Barotrauma
} }
else else
{ {
SetRandom(); CalculatePriority();
} }
} }
} }
@@ -182,7 +179,7 @@ namespace Barotrauma
if (!character.IsClimbing) if (!character.IsClimbing)
{ {
if (SteeringManager != PathSteering || (PathSteering.CurrentPath != null && 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); Wander(deltaTime);
return; return;
@@ -45,6 +45,7 @@ namespace Barotrauma
public override bool CanBeCompleted => true; public override bool CanBeCompleted => true;
public override bool AbandonWhenCannotCompleteSubjectives => false; public override bool AbandonWhenCannotCompleteSubjectives => false;
public override bool AllowSubObjectiveSorting => true; 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); } 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() public override float GetPriority()
{ {
if (character.LockHands) { return 0; } if (character.LockHands || character.Submarine == null || Targets.None())
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)
{ {
return AIObjectiveManager.OrderPriority; Priority = 0;
} }
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90); else
float devotion = MathHelper.Min(10, Priority); {
float value = MathHelper.Clamp((devotion + targetValue * PriorityModifier) / 100, 0, 1); // Allow the target value to be more than 100.
return MathHelper.Lerp(0, max, value); 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() protected void UpdateTargets()
@@ -41,6 +41,15 @@ namespace Barotrauma
public bool IsActiveObjective<T>() where T : AIObjective => GetActiveObjective() is T; public bool IsActiveObjective<T>() where T : AIObjective => GetActiveObjective() is T;
public AIObjective GetActiveObjective() => CurrentObjective?.GetActiveObjective(); public AIObjective GetActiveObjective() => CurrentObjective?.GetActiveObjective();
/// <summary>
/// Returns the last active objective of the specific type.
/// </summary>
public T GetActiveObjective<T>() where T : AIObjective => CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).LastOrDefault(so => so is T) as T;
/// <summary>
/// Returns all active objectives of the specific type. Creates a new collection -> don't use too frequently.
/// </summary>
public IEnumerable<T> GetActiveObjectives<T>() where T : AIObjective => CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).Where(so => so is T).Select(so => so as T);
public bool HasActiveObjective<T>() where T : AIObjective => CurrentObjective is T || CurrentObjective != null && CurrentObjective.GetSubObjectivesRecursive().Any(so => so is T); public bool HasActiveObjective<T>() 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 previousObjective = CurrentObjective;
var firstObjective = Objectives.FirstOrDefault(); var firstObjective = Objectives.FirstOrDefault();
if (CurrentOrder != null && firstObjective != null && CurrentOrder.GetPriority() > firstObjective.GetPriority()) if (CurrentOrder != null && firstObjective != null && CurrentOrder.Priority > firstObjective.Priority)
{ {
CurrentObjective = CurrentOrder; CurrentObjective = CurrentOrder;
} }
@@ -158,14 +167,14 @@ namespace Barotrauma
{ {
previousObjective?.OnDeselected(); previousObjective?.OnDeselected();
CurrentObjective?.OnSelected(); CurrentObjective?.OnSelected();
GetObjective<AIObjectiveIdle>().SetRandom(); GetObjective<AIObjectiveIdle>().CalculatePriority();
} }
return CurrentObjective; return CurrentObjective;
} }
public float GetCurrentPriority() public float GetCurrentPriority()
{ {
return CurrentObjective == null ? 0.0f : CurrentObjective.GetPriority(); return CurrentObjective == null ? 0.0f : CurrentObjective.Priority;
} }
public void UpdateObjectives(float deltaTime) public void UpdateObjectives(float deltaTime)
@@ -203,9 +212,11 @@ namespace Barotrauma
public void SortObjectives() public void SortObjectives()
{ {
CurrentOrder?.GetPriority();
Objectives.ForEach(o => o.GetPriority());
if (Objectives.Any()) if (Objectives.Any())
{ {
Objectives.Sort((x, y) => y.GetPriority().CompareTo(x.GetPriority())); Objectives.Sort((x, y) => y.Priority.CompareTo(x.Priority));
} }
GetCurrentObjective()?.SortSubObjectives(); GetCurrentObjective()?.SortSubObjectives();
} }
@@ -297,7 +308,7 @@ namespace Barotrauma
{ {
IsLoop = true, IsLoop = true,
// Don't override unless it's an order by a player // 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; break;
default: default:
@@ -306,7 +317,7 @@ namespace Barotrauma
{ {
IsLoop = true, IsLoop = true,
// Don't override unless it's an order by a player // 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; break;
} }
@@ -3,6 +3,7 @@ using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma namespace Barotrauma
{ {
@@ -31,19 +32,32 @@ namespace Barotrauma
public override float GetPriority() public override float GetPriority()
{ {
if (component.Item.ConditionPercentage <= 0) { return 0; } if (component.Item.ConditionPercentage <= 0)
if (objectiveManager.CurrentOrder == this)
{ {
return AIObjectiveManager.OrderPriority; Priority = 0;
} }
if (component.Item.CurrentHull == null) { return 0; } else
if (component.Item.CurrentHull.FireSources.Count > 0) { return 0; } {
if (IsOperatedByAnother(GetTarget())) { return 0; } if (objectiveManager.CurrentOrder == this)
if (Character.CharacterList.Any(c => c.CurrentHull == component.Item.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { return 0; } {
float devotion = MathHelper.Min(10, Priority); Priority = AIObjectiveManager.OrderPriority;
float value = devotion + AIObjectiveManager.OrderPriority * PriorityModifier; }
float max = MathHelper.Min((AIObjectiveManager.OrderPriority - 1), 90); if (component.Item.CurrentHull == null || component.Item.CurrentHull.FireSources.Any() || IsOperatedByAnother(character, GetTarget(), out _))
return MathHelper.Clamp(value, 0, max); {
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) 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) foreach (var c in Character.CharacterList)
{ {
if (c == character) { continue; } if (character != null && c == character) { continue; }
if (!HumanAIController.IsFriendly(c)) { continue; } if (character?.AIController is HumanAIController humanAi && !humanAi.IsFriendly(c)) { continue; }
if (c.SelectedConstruction != target.Item) { continue; } if (c.SelectedConstruction != target.Item) { continue; }
operatingCharacter = c;
// If the other character is player, don't try to operate // If the other character is player, don't try to operate
if (c.IsRemotePlayer || Character.Controlled == c) { return true; } 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 the other character is ordered to operate the item, let him do it
if (humanAi.ObjectiveManager.IsCurrentOrder<AIObjectiveOperateItem>()) if (controllingHumanAi.ObjectiveManager.IsCurrentOrder<AIObjectiveOperateItem>())
{ {
return true; return true;
} }
else 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 // Steering is hard-coded -> cannot use the required skills collection defined in the xml
return character.GetSkillLevel("helm") <= c.GetSkillLevel("helm"); return character.GetSkillLevel("helm") <= c.GetSkillLevel("helm");
@@ -116,7 +136,7 @@ namespace Barotrauma
return; return;
} }
// Don't allow to operate an item that someone with a better skills already operates, unless this is an order // 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 // Don't abandon
return; return;
@@ -12,7 +12,6 @@ namespace Barotrauma
public override string DebugTag => "pump water"; public override string DebugTag => "pump water";
public override bool KeepDivingGearOn => true; public override bool KeepDivingGearOn => true;
public override bool UnequipItems => true; public override bool UnequipItems => true;
public override bool IgnoreUnsafeHulls => true;
private IEnumerable<Pump> pumpList; private IEnumerable<Pump> pumpList;
@@ -30,21 +30,28 @@ namespace Barotrauma
{ {
// TODO: priority list? // TODO: priority list?
// Ignore items that are being repaired by someone else. // Ignore items that are being repaired by someone else.
if (Item.Repairables.Any(r => r.CurrentFixer != null && r.CurrentFixer != character)) { return 0; } if (Item.Repairables.Any(r => r.CurrentFixer != null && r.CurrentFixer != character))
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; Priority = 0;
} }
float damagePriority = MathHelper.Lerp(1, 0, Item.Condition / Item.MaxCondition); else
float successFactor = MathHelper.Lerp(0, 1, Item.Repairables.Average(r => r.DegreeOfSuccess(character))); {
float isSelected = IsRepairing ? 50 : 0; float yDist = Math.Abs(character.WorldPosition.Y - Item.WorldPosition.Y);
float devotion = (Math.Min(Priority, 10) + isSelected) / 100; yDist = yDist > 100 ? yDist * 5 : 0;
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90); float dist = Math.Abs(character.WorldPosition.X - Item.WorldPosition.X) + yDist;
return MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + damagePriority * distanceFactor * successFactor * PriorityModifier, 0, 1)); 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() protected override bool Check()
@@ -81,18 +81,17 @@ namespace Barotrauma
// Don't stop fixing until done // Don't stop fixing until done
return 100; return 100;
} }
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveRepairItems>()); int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveRepairItems>(), onlyBots: true);
int items = Targets.Count; int items = Targets.Count;
bool anyFixers = otherFixers > 0; bool anyFixers = otherFixers > 0;
float ratio = anyFixers ? items / otherFixers : 1; float ratio = anyFixers ? items / (float)otherFixers : 1;
var result = ratio;
if (objectiveManager.CurrentOrder == this) if (objectiveManager.CurrentOrder == this)
{ {
return Targets.Sum(t => 100 - t.ConditionPercentage) * ratio; return Targets.Sum(t => 100 - t.ConditionPercentage) * ratio;
} }
else 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 // Enough fixers
return 0; return 0;
@@ -14,7 +14,7 @@ namespace Barotrauma
const float TreatmentDelay = 0.5f; const float TreatmentDelay = 0.5f;
const float CloseEnoughToTreat = 150.0f; const float CloseEnoughToTreat = 100.0f;
private readonly Character targetCharacter; private readonly Character targetCharacter;
@@ -44,6 +44,15 @@ namespace Barotrauma
Abandon = true; Abandon = true;
return; 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) if (targetCharacter != character)
{ {
@@ -67,7 +76,11 @@ namespace Barotrauma
TargetName = targetCharacter.DisplayName TargetName = targetCharacter.DisplayName
}, },
onCompleted: () => RemoveSubObjective(ref goToObjective), onCompleted: () => RemoveSubObjective(ref goToObjective),
onAbandon: () => RemoveSubObjective(ref goToObjective)); onAbandon: () =>
{
RemoveSubObjective(ref goToObjective);
Abandon = true;
});
} }
else else
{ {
@@ -86,7 +99,11 @@ namespace Barotrauma
RemoveSubObjective(ref goToObjective); RemoveSubObjective(ref goToObjective);
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(safeHull, character, objectiveManager), TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(safeHull, character, objectiveManager),
onCompleted: () => RemoveSubObjective(ref goToObjective), onCompleted: () => RemoveSubObjective(ref goToObjective),
onAbandon: () => RemoveSubObjective(ref goToObjective)); onAbandon: () =>
{
RemoveSubObjective(ref goToObjective);
safeHull = character.CurrentHull;
});
} }
} }
} }
@@ -105,7 +122,11 @@ namespace Barotrauma
TargetName = targetCharacter.DisplayName TargetName = targetCharacter.DisplayName
}, },
onCompleted: () => RemoveSubObjective(ref goToObjective), onCompleted: () => RemoveSubObjective(ref goToObjective),
onAbandon: () => RemoveSubObjective(ref goToObjective)); onAbandon: () =>
{
RemoveSubObjective(ref goToObjective);
Abandon = true;
});
} }
else else
{ {
@@ -127,6 +148,11 @@ namespace Barotrauma
private Dictionary<string, float> currentTreatmentSuitabilities = new Dictionary<string, float>(); private Dictionary<string, float> currentTreatmentSuitabilities = new Dictionary<string, float>();
private void GiveTreatment(float deltaTime) 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) if (treatmentTimer > 0.0f)
{ {
treatmentTimer -= deltaTime; treatmentTimer -= deltaTime;
@@ -137,9 +163,8 @@ namespace Barotrauma
//find which treatments are the most suitable to treat the character's current condition //find which treatments are the most suitable to treat the character's current condition
targetCharacter.CharacterHealth.GetSuitableTreatments(currentTreatmentSuitabilities, normalize: false); 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 //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<string, float> treatmentSuitability in affliction.Prefab.TreatmentSuitability) foreach (KeyValuePair<string, float> treatmentSuitability in affliction.Prefab.TreatmentSuitability)
{ {
@@ -200,10 +225,12 @@ namespace Barotrauma
onAbandon: () => RemoveSubObjective(ref getItemObjective)); 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) private void ApplyTreatment(Affliction affliction, Item item)
{ {
var targetLimb = targetCharacter.CharacterHealth.GetAfflictionLimb(affliction); var targetLimb = targetCharacter.CharacterHealth.GetAfflictionLimb(affliction);
@@ -240,7 +267,7 @@ namespace Barotrauma
Abandon = true; Abandon = true;
return false; return false;
} }
bool isCompleted = AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) > AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager); bool isCompleted = AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) >= AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager, character, targetCharacter);
if (isCompleted && targetCharacter != character) if (isCompleted && targetCharacter != character)
{ {
character.Speak(TextManager.GetWithVariable("DialogTargetHealed", "[targetname]", targetCharacter.Name), character.Speak(TextManager.GetWithVariable("DialogTargetHealed", "[targetname]", targetCharacter.Name),
@@ -253,20 +280,24 @@ namespace Barotrauma
{ {
if (targetCharacter == null || targetCharacter.CurrentHull == null || targetCharacter.Removed || targetCharacter.IsDead) 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) else
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; // 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); return Priority;
float devotion = Math.Min(Priority, 10) / 100;
return MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + vitalityFactor * distanceFactor, 0, 1));
} }
public static IEnumerable<Affliction> GetVitalityReducingAfflictions(Character character) => character.CharacterHealth.GetAllAfflictions(a => a.GetVitalityDecrease(character.CharacterHealth) > 0); public static IEnumerable<Affliction> GetSortedAfflictions(Character character) => CharacterHealth.SortAfflictionsBySeverity(character.CharacterHealth.GetAllAfflictions());
} }
} }
@@ -8,11 +8,11 @@ namespace Barotrauma
{ {
public override string DebugTag => "rescue all"; public override string DebugTag => "rescue all";
public override bool ForceRun => true; public override bool ForceRun => true;
public override bool IgnoreUnsafeHulls => true; public override bool InverseTargetEvaluation => true;
private const float vitalityThreshold = 80; private const float vitalityThreshold = 80;
private const float vitalityThresholdForOrders = 95; private const float vitalityThresholdForOrders = 100;
public static float GetVitalityThreshold(AIObjectiveManager manager) public static float GetVitalityThreshold(AIObjectiveManager manager, Character character, Character target)
{ {
if (manager == null) if (manager == null)
{ {
@@ -20,7 +20,7 @@ namespace Barotrauma
} }
else 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<Character> GetList() => Character.CharacterList; protected override IEnumerable<Character> 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<AIObjectiveRescueAll>(), 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) protected override AIObjective ObjectiveConstructor(Character target)
=> new AIObjectiveRescue(character, target, objectiveManager, PriorityModifier); => new AIObjectiveRescue(character, target, objectiveManager, PriorityModifier);
@@ -47,16 +88,34 @@ namespace Barotrauma
if (!HumanAIController.IsFriendly(character, target)) { return false; } if (!HumanAIController.IsFriendly(character, target)) { return false; }
if (character.AIController is HumanAIController humanAI) 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<AIObjectiveRescueAll>())
{
// Ignore unsafe hulls, unless ordered
if (humanAI.UnsafeHulls.Contains(target.CurrentHull))
{
return false;
}
}
} }
else 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 == null || character.Submarine == null) { return false; }
if (target.Submarine.TeamID != character.Submarine.TeamID) { return false; } if (target.Submarine.TeamID != character.Submarine.TeamID) { return false; }
if (target.CurrentHull == null) { return false; } if (target.CurrentHull == null) { return false; }
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(target.CurrentHull, true)) { 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<AIObjectiveCombat>() ||
targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveFindSafety>() ||
targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveRescue>())
{
return false;
}
}
// Don't go into rooms that have enemies // 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; } if (Character.CharacterList.Any(c => c.CurrentHull == target.CurrentHull && !HumanAIController.IsFriendly(character, c) && HumanAIController.IsActive(c))) { return false; }
return true; return true;
@@ -231,25 +231,12 @@ namespace Barotrauma
} }
else else
{ {
Limb refLimb = GetLimb(LimbType.Head); float rotation = MathHelper.WrapAngle(Collider.Rotation);
float refAngle; rotation = MathHelper.ToDegrees(rotation);
if (refLimb == null) if (rotation < 0.0f)
{ {
refAngle = CurrentAnimationParams.TorsoAngleInRadians; rotation += 360;
refLimb = GetLimb(LimbType.Torso);
} }
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) if (rotation > 20 && rotation < 160)
{ {
TargetDir = Direction.Left; TargetDir = Direction.Left;
@@ -347,9 +334,23 @@ namespace Barotrauma
target.AnimController.Collider.MoveToPos(mouthPos, (float)(Math.Sin(eatTimer) + dragForce)); target.AnimController.Collider.MoveToPos(mouthPos, (float)(Math.Sin(eatTimer) + dragForce));
} }
//pull the character's mouth to the target character (again with a fluctuating force) if (InWater)
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); //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); character.ApplyStatusEffects(ActionType.OnEating, deltaTime);
@@ -439,7 +440,7 @@ namespace Barotrauma
if (CurrentSwimParams.RotateTowardsMovement) if (CurrentSwimParams.RotateTowardsMovement)
{ {
Collider.SmoothRotate(movementAngle, CurrentSwimParams.SteerTorque); Collider.SmoothRotate(movementAngle, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
if (TorsoAngle.HasValue) if (TorsoAngle.HasValue)
{ {
Limb torso = GetLimb(LimbType.Torso); Limb torso = GetLimb(LimbType.Torso);
@@ -491,11 +492,11 @@ namespace Barotrauma
} }
if (mainLimb.type == LimbType.Head && HeadAngle.HasValue) 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) 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) if (TorsoAngle.HasValue)
{ {
@@ -515,7 +516,7 @@ namespace Barotrauma
} }
var waveLength = Math.Abs(CurrentSwimParams.WaveLength * RagdollParams.JointScale); 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) if (waveLength > 0 && waveAmplitude > 0)
{ {
WalkPos -= transformedMovement.Length() / Math.Abs(waveLength); WalkPos -= transformedMovement.Length() / Math.Abs(waveLength);
@@ -524,6 +525,10 @@ namespace Barotrauma
foreach (var limb in Limbs) 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) switch (limb.type)
{ {
case LimbType.LeftFoot: case LimbType.LeftFoot:
@@ -548,7 +553,7 @@ namespace Barotrauma
if (Limbs[i].SteerForce <= 0.0f) { continue; } if (Limbs[i].SteerForce <= 0.0f) { continue; }
if (!Collider.PhysEnabled) { continue; } if (!Collider.PhysEnabled) { continue; }
Vector2 pullPos = Limbs[i].PullJointWorldAnchorA; 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; Vector2 mainLimbDiff = mainLimb.PullJointWorldAnchorB - mainLimb.SimPosition;
@@ -665,6 +670,10 @@ namespace Barotrauma
foreach (Limb limb in Limbs) 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) switch (limb.type)
{ {
case LimbType.LeftFoot: case LimbType.LeftFoot:
@@ -729,7 +738,10 @@ namespace Barotrauma
break; break;
case LimbType.LeftLeg: case LimbType.LeftLeg:
case LimbType.RightLeg: 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; break;
} }
} }
@@ -900,7 +900,7 @@ namespace Barotrauma
surfaceLimiter = ConvertUnits.ToDisplayUnits(Collider.SimPosition.Y + 0.4f) - surfacePos; surfaceLimiter = ConvertUnits.ToDisplayUnits(Collider.SimPosition.Y + 0.4f) - surfacePos;
surfaceLimiter = Math.Max(1.0f, surfaceLimiter); surfaceLimiter = Math.Max(1.0f, surfaceLimiter);
if (surfaceLimiter > 50.0f) return; if (surfaceLimiter > 50.0f) { return; }
} }
Limb leftHand = GetLimb(LimbType.LeftHand); Limb leftHand = GetLimb(LimbType.LeftHand);
@@ -928,8 +928,7 @@ namespace Barotrauma
if (!aiming) if (!aiming)
{ {
float newRotation = MathUtils.VectorToAngle(TargetMovement) - MathHelper.PiOver2; float newRotation = MathUtils.VectorToAngle(TargetMovement) - MathHelper.PiOver2;
Collider.SmoothRotate(newRotation, 5.0f); Collider.SmoothRotate(newRotation, 5.0f * character.SpeedMultiplier);
//torso.body.SmoothRotate(newRotation);
} }
} }
else else
@@ -942,13 +941,13 @@ namespace Barotrauma
TargetMovement = new Vector2(0.0f, -0.1f); TargetMovement = new Vector2(0.0f, -0.1f);
float newRotation = MathUtils.VectorToAngle(diff); 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); 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); movement = MathUtils.SmoothStep(movement, TargetMovement, 0.3f);
@@ -1007,7 +1006,7 @@ namespace Barotrauma
var waist = GetLimb(LimbType.Waist); 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); 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)); transformedFootPos = Vector2.Transform(transformedFootPos, Matrix.CreateRotationZ(Collider.Rotation));
if (rightFoot != null && !rightFoot.Disabled) 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.X = (Dir == 1.0f) ? Math.Max(0.3f, rightHandPos.X) : Math.Min(-0.3f, rightHandPos.X);
rightHandPos = Vector2.Transform(rightHandPos, rotationMatrix); rightHandPos = Vector2.Transform(rightHandPos, rotationMatrix);
HandIK(rightHand, handPos + rightHandPos, CurrentSwimParams.HandMoveStrength); HandIK(rightHand, handPos + rightHandPos, CurrentSwimParams.HandMoveStrength * character.SpeedMultiplier);
} }
if (leftHand != null && !leftHand.Disabled) 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.X = (Dir == 1.0f) ? Math.Max(0.3f, leftHandPos.X) : Math.Min(-0.3f, leftHandPos.X);
leftHandPos = Vector2.Transform(leftHandPos, rotationMatrix); 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); float sqrDist = Vector2.DistanceSquared(character.WorldPosition, handWorldPos);
if (sqrDist > MathUtils.Pow(ConvertUnits.ToDisplayUnits(upperArmLength + forearmLength), 2)) 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);
} }
} }
@@ -383,7 +383,7 @@ namespace Barotrauma
} }
} }
if (character.IsHusk) if (character.IsHusk && character.Params.UseHuskAppendage)
{ {
var characterPrefab = CharacterPrefab.FindByFilePath(character.ConfigPath); var characterPrefab = CharacterPrefab.FindByFilePath(character.ConfigPath);
if (characterPrefab?.XDocument != null) if (characterPrefab?.XDocument != null)
@@ -1626,7 +1626,7 @@ namespace Barotrauma
float sin = (float)Math.Sin(mouthLimb.Rotation); float sin = (float)Math.Sin(mouthLimb.Rotation);
Vector2 bodySize = mouthLimb.body.GetSize(); Vector2 bodySize = mouthLimb.body.GetSize();
Vector2 offset = new Vector2(mouthLimb.MouthPos.X * bodySize.X / 2, mouthLimb.MouthPos.Y * bodySize.Y / 2); 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() public Vector2 GetColliderBottom()
@@ -13,11 +13,12 @@ namespace Barotrauma
public enum AttackContext public enum AttackContext
{ {
NotDefined, Any,
Water, Water,
Ground, Ground,
Inside, Inside,
Outside Outside,
NotDefined
} }
public enum AttackTarget public enum AttackTarget
@@ -72,13 +73,13 @@ namespace Barotrauma
partial class Attack : ISerializableEntity 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; } public AttackContext Context { get; private set; }
[Serialize(AttackTarget.Any, true, description: "Does the attack target only specific targets?"), Editable] [Serialize(AttackTarget.Any, true, description: "Does the attack target only specific targets?"), Editable]
public AttackTarget TargetType { get; private set; } 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; } 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] [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] [Serialize(AIBehaviorAfterAttack.FallBack, true, description: "The preferred AI behavior after the attack."), Editable]
public AIBehaviorAfterAttack AfterAttack { get; set; } 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; } 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)] [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; } 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)] [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; } 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; } public float Torque { get; private set; }
[Serialize(false, true), Editable] [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)] [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; } 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; } 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)] [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; } 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; } 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)] [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) 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) if (OnlyHumans)
{ {
@@ -461,6 +483,7 @@ namespace Barotrauma
public float AttackTimer { get; private set; } public float AttackTimer { get; private set; }
public float CoolDownTimer { get; set; } public float CoolDownTimer { get; set; }
public float CurrentRandomCoolDown { get; private set; }
public float SecondaryCoolDownTimer { get; set; } public float SecondaryCoolDownTimer { get; set; }
public bool IsRunning { get; private set; } public bool IsRunning { get; private set; }
@@ -492,7 +515,8 @@ namespace Barotrauma
public void SetCoolDown() public void SetCoolDown()
{ {
float randomFraction = CoolDown * CoolDownRandomFactor; 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; randomFraction = SecondaryCoolDown * CoolDownRandomFactor;
SecondaryCoolDownTimer = SecondaryCoolDown + MathHelper.Lerp(-randomFraction, randomFraction, Rand.Value(Rand.RandSync.Server)); SecondaryCoolDownTimer = SecondaryCoolDown + MathHelper.Lerp(-randomFraction, randomFraction, Rand.Value(Rand.RandSync.Server));
} }
@@ -501,11 +525,12 @@ namespace Barotrauma
{ {
CoolDownTimer = 0; CoolDownTimer = 0;
SecondaryCoolDownTimer = 0; SecondaryCoolDownTimer = 0;
CurrentRandomCoolDown = 0;
} }
partial void DamageParticles(float deltaTime, Vector2 worldPosition); 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<AttackContext> contexts) public bool IsValidContext(IEnumerable<AttackContext> contexts)
{ {
@@ -559,5 +584,11 @@ namespace Barotrauma
return true; return true;
} }
} }
public Vector2 CalculateAttackPhase(TransitionMode easing = TransitionMode.Linear)
{
float t = AttackTimer / Duration;
return MathUtils.Bezier(RootForceWorldStart, RootForceWorldMiddle, RootForceWorldEnd, ToolBox.GetEasing(easing, t));
}
} }
} }
@@ -57,6 +57,10 @@ namespace Barotrauma
public Hull CurrentHull = null; public Hull CurrentHull = null;
public bool IsRemotePlayer; public bool IsRemotePlayer;
public bool IsPlayer => Controlled == this || IsRemotePlayer;
public bool IsBot => !IsPlayer && AIController is HumanAIController humanAI && humanAI.Enabled;
public readonly Dictionary<string, SerializableProperty> Properties; public readonly Dictionary<string, SerializableProperty> Properties;
public Dictionary<string, SerializableProperty> SerializableProperties public Dictionary<string, SerializableProperty> SerializableProperties
{ {
@@ -128,6 +132,12 @@ namespace Barotrauma
set => Params.Noise = value; set => Params.Noise = value;
} }
public float Visibility
{
get => Params.Visibility;
set => Params.Visibility = value;
}
public bool IsTraitor; public bool IsTraitor;
public string TraitorCurrentObjective = ""; public string TraitorCurrentObjective = "";
public bool IsHuman => SpeciesName.Equals(CharacterPrefab.HumanSpeciesName, StringComparison.OrdinalIgnoreCase); public bool IsHuman => SpeciesName.Equals(CharacterPrefab.HumanSpeciesName, StringComparison.OrdinalIgnoreCase);
@@ -201,7 +211,7 @@ namespace Barotrauma
{ {
displayName = TextManager.Get($"Character.{SpeciesName}", returnNull: true); 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); nonHuskedSpeciesName = AfflictionHusk.GetNonHuskedSpeciesName(speciesName, matchingAffliction);
} }
ragdollParams = IsHumanoid ? RagdollParams.GetDefaultRagdollParams<HumanRagdollParams>(nonHuskedSpeciesName) : RagdollParams.GetDefaultRagdollParams<FishRagdollParams>(nonHuskedSpeciesName) as RagdollParams; if (ragdollParams == null)
if (info == null)
{ {
info = new CharacterInfo(nonHuskedSpeciesName, ragdollParams.FileName); string name = Params.UseHuskAppendage ? nonHuskedSpeciesName : speciesName;
ragdollParams = IsHumanoid ? RagdollParams.GetDefaultRagdollParams<HumanRagdollParams>(name) : RagdollParams.GetDefaultRagdollParams<FishRagdollParams>(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))); Info.HairElement?.Elements("sprite").ForEach(s => head.OtherWearables.Add(new WearableSprite(s, WearableType.Hair)));
#if CLIENT #if CLIENT
head.LoadHuskSprite(); head.EnableHuskSprite = Params.Husk;
head.LoadHerpesSprite(); head.LoadHerpesSprite();
head.UpdateWearableTypesToHide(); head.UpdateWearableTypesToHide();
#endif #endif
@@ -1010,59 +1024,55 @@ namespace Barotrauma
} }
else else
{ {
if (IsKeyDown(InputType.Left)) targetMovement.X -= 1.0f; if (IsKeyDown(InputType.Left)) { targetMovement.X -= 1.0f; }
if (IsKeyDown(InputType.Right)) targetMovement.X += 1.0f; if (IsKeyDown(InputType.Right)) { targetMovement.X += 1.0f; }
if (IsKeyDown(InputType.Up)) targetMovement.Y += 1.0f; if (IsKeyDown(InputType.Up)) { targetMovement.Y += 1.0f; }
if (IsKeyDown(InputType.Down)) 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, //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 //so the movement can't be normalized or the Character would walk slower when pressing down/up
if (AnimController.InWater) if (AnimController.InWater)
{ {
float length = targetMovement.Length(); 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; targetMovement *= currentSpeed;
float maxSpeed = ApplyTemporarySpeedLimits(currentSpeed); float maxSpeed = ApplyTemporarySpeedLimits(currentSpeed);
targetMovement.X = MathHelper.Clamp(targetMovement.X, -maxSpeed, maxSpeed); targetMovement.X = MathHelper.Clamp(targetMovement.X, -maxSpeed, maxSpeed);
targetMovement.Y = MathHelper.Clamp(targetMovement.Y, -maxSpeed, maxSpeed); targetMovement.Y = MathHelper.Clamp(targetMovement.Y, -maxSpeed, maxSpeed);
SpeedMultiplier = greatestPositiveSpeedMultiplier - (1f - greatestNegativeSpeedMultiplier);
//apply speed multiplier if targetMovement *= SpeedMultiplier;
// a. it's boosting the movement speed and the character is trying to move fast (= running) // Reset, status effects will set the value before the next update
// b. it's a debuff that decreases movement speed ResetSpeedMultiplier();
float speedMultiplier = SpeedMultiplier;
if (run || speedMultiplier <= 0.0f) targetMovement *= speedMultiplier;
ResetSpeedMultiplier(); // Reset, items will set the value before the next update
return targetMovement; return targetMovement;
} }
/// <summary> /// <summary>
/// Can be used to modify the character's speed via StatusEffects /// Can be used to modify the character's speed via StatusEffects
/// </summary> /// </summary>
public float SpeedMultiplier public float SpeedMultiplier { get; private set; }
{
get
{
return greatestPositiveSpeedMultiplier - (1f - greatestNegativeSpeedMultiplier);
}
}
public void StackSpeedMultiplier(float val) public void StackSpeedMultiplier(float val)
{ {
@@ -1915,7 +1925,7 @@ namespace Barotrauma
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) 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 //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; c.Enabled = true;
} }
@@ -2012,8 +2022,8 @@ namespace Barotrauma
HideFace = false; HideFace = false;
UpdateSightRange(); UpdateSightRange(deltaTime);
UpdateSoundRange(); UpdateSoundRange(deltaTime);
if (IsDead) { return; } 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; } if (aiTarget == null) { return; }
float range = (float)Math.Sqrt(Mass) * 250 + AnimController.Collider.LinearVelocity.Length() * 500; float minRange = Math.Clamp((float)Math.Sqrt(Mass) * Visibility, 250, 1000);
aiTarget.SightRange = MathHelper.Clamp(range, 0, 10000); 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; } if (aiTarget == null) { return; }
float range = ((float)Math.Sqrt(Mass) / 3) * (AnimController.TargetMovement.Length() * 2) * Noise; if (IsDead)
aiTarget.SoundRange = MathHelper.Clamp(range, 0, 10000); {
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) public bool CanHearCharacter(Character speaker)
@@ -2316,21 +2347,16 @@ namespace Barotrauma
public void SetOrder(Order order, string orderOption, Character orderGiver, bool speak = true) 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 (orderGiver != null && !CanHearCharacter(orderGiver)) { return; }
//set the character order only if the character is close enough to hear the message
if (!CanHearCharacter(orderGiver)) { return; }
}
if (AIController is HumanAIController humanAI) if (AIController is HumanAIController humanAI)
{ {
humanAI.SetOrder(order, orderOption, orderGiver, speak); humanAI.SetOrder(order, orderOption, orderGiver, speak);
} }
#if CLIENT #if CLIENT
else GameMain.GameSession?.CrewManager?.DisplayCharacterOrder(this, order, orderOption);
{
GameMain.GameSession?.CrewManager?.DisplayCharacterOrder(this, order, orderOption);
}
#endif #endif
CurrentOrder = order; CurrentOrder = order;
@@ -2469,13 +2495,17 @@ namespace Barotrauma
DamageLimb(worldPosition, targetLimb, attack.Afflictions.Keys, attack.Stun, playSound, attackImpulse, attacker); DamageLimb(worldPosition, targetLimb, attack.Afflictions.Keys, attack.Stun, playSound, attackImpulse, attacker);
if (limbHit == null) { return new AttackResult(); } if (limbHit == null) { return new AttackResult(); }
Vector2 forceWorld = attack.TargetImpulseWorld + attack.TargetForceWorld;
limbHit.body?.ApplyLinearImpulse(attack.TargetImpulseWorld + attack.TargetForceWorld * deltaTime, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); if (attacker != null)
{
forceWorld.X *= attacker.AnimController.Dir;
}
limbHit.body?.ApplyLinearImpulse(forceWorld * deltaTime, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
var mainLimb = limbHit.character.AnimController.MainLimb; var mainLimb = limbHit.character.AnimController.MainLimb;
if (limbHit != mainLimb) if (limbHit != mainLimb)
{ {
// Always add force to 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 SERVER
if (attacker is Character attackingCharacter && attackingCharacter.AIController == null) if (attacker is Character attackingCharacter && attackingCharacter.AIController == null)
@@ -14,8 +14,13 @@ namespace Barotrauma
public Dictionary<string, SerializableProperty> SerializableProperties { get; set; } public Dictionary<string, SerializableProperty> SerializableProperties { get; set; }
protected float _strength;
[Serialize(0f, true), Editable] [Serialize(0f, true), Editable]
public float Strength { get; set; } public virtual float Strength
{
get { return _strength; }
set { _strength = value; }
}
[Serialize("", true), Editable] [Serialize("", true), Editable]
public string Identifier { get; private set; } public string Identifier { get; private set; }
@@ -38,7 +43,7 @@ namespace Barotrauma
public Affliction(AfflictionPrefab prefab, float strength) public Affliction(AfflictionPrefab prefab, float strength)
{ {
Prefab = prefab; Prefab = prefab;
Strength = strength; _strength = strength;
Identifier = prefab?.Identifier; Identifier = prefab?.Identifier;
} }
@@ -173,11 +178,11 @@ namespace Barotrauma
if (currentEffect.StrengthChange < 0) // Reduce diminishing of buffs if boosted 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 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) foreach (StatusEffect statusEffect in currentEffect.StatusEffects)
@@ -1,7 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
using System;
namespace Barotrauma namespace Barotrauma
{ {
@@ -9,7 +9,7 @@ namespace Barotrauma
{ {
public enum InfectionState public enum InfectionState
{ {
Dormant, Transition, Active Initial, Dormant, Transition, Active, Final
} }
private bool subscribedToDeathEvent; private bool subscribedToDeathEvent;
@@ -17,154 +17,157 @@ namespace Barotrauma
private InfectionState state; private InfectionState state;
private List<Limb> huskAppendage; private List<Limb> huskAppendage;
private Character character;
private readonly List<Affliction> huskInfection = new List<Affliction>();
[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 public InfectionState State
{ {
get { return 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) : private float DormantThreshold => Prefab.MaxStrength * 0.5f;
base(prefab, strength) 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) public override void Update(CharacterHealth characterHealth, Limb targetLimb, float deltaTime)
{ {
float prevStrength = Strength;
base.Update(characterHealth, targetLimb, deltaTime); base.Update(characterHealth, targetLimb, deltaTime);
character = characterHealth.Character;
if (character == null) { return; }
if (!subscribedToDeathEvent) if (!subscribedToDeathEvent)
{ {
characterHealth.Character.OnDeath += CharacterDead; character.OnDeath += CharacterDead;
subscribedToDeathEvent = true; subscribedToDeathEvent = true;
} }
if (Strength < DormantThreshold)
if (characterHealth.Character == Character.Controlled) UpdateMessages(prevStrength, characterHealth.Character);
if (Strength < Prefab.MaxStrength * 0.5f)
{ {
UpdateDormantState(deltaTime, characterHealth.Character); DeactivateHusk();
State = InfectionState.Dormant;
}
else if (Strength < ActiveThreshold)
{
DeactivateHusk();
character.SpeechImpediment = 100;
State = InfectionState.Transition;
} }
else if (Strength < Prefab.MaxStrength) else if (Strength < Prefab.MaxStrength)
{ {
characterHealth.Character.SpeechImpediment = 100.0f; if (State != InfectionState.Active)
UpdateTransitionState(deltaTime, characterHealth.Character); {
character.SetStun(Rand.Range(2, 4, Rand.RandSync.Server));
}
State = InfectionState.Active;
ActivateHusk();
} }
else else
{ {
characterHealth.Character.SpeechImpediment = 100.0f; State = InfectionState.Final;
UpdateActiveState(deltaTime, characterHealth.Character); 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) 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.LastDamageSource = null;
character.DamageLimb( float force = applyForce ? random * 0.1f * limb.Mass : 0;
limb.WorldPosition, limb, character.DamageLimb(limb.WorldPosition, limb, huskInfection, 0, false, force);
new List<Affliction>() { AfflictionPrefab.InternalDamage.Instantiate(0.5f * deltaTime / character.AnimController.Limbs.Length) },
0.0f, false, 0.0f);
} }
} }
public void ActivateHusk(Character character) public void ActivateHusk()
{ {
if (huskAppendage == null) if (huskAppendage == null && character.Params.UseHuskAppendage)
{ {
huskAppendage = AttachHuskAppendage(character, Prefab.Identifier); 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); character.NeedsAir = character.Params.MainElement.GetAttributeBool("needsair", false);
if (huskAppendage != null) if (huskAppendage != null)
{ {
huskAppendage.ForEach(l => character.AnimController.RemoveLimb(l)); huskAppendage.ForEach(l => character.AnimController.RemoveLimb(l));
huskAppendage = null; 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) { return; }
if (character != null) character.OnDeath -= CharacterDead; DeactivateHusk();
character.OnDeath -= CharacterDead;
subscribedToDeathEvent = false; subscribedToDeathEvent = false;
} }
private void CharacterDead(Character character, CauseOfDeath causeOfDeath) private void CharacterDead(Character character, CauseOfDeath causeOfDeath)
{ {
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } 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 //don't turn the character into a husk if any of its limbs are severed
if (character.AnimController?.LimbJoints != null) if (character.AnimController?.LimbJoints != null)
{ {
foreach (var limbJoint in character.AnimController.LimbJoints) 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 //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<object> CreateAIHusk(Character character) private IEnumerable<object> CreateAIHusk()
{ {
character.Enabled = false; character.Enabled = false;
Entity.Spawner.AddToRemoveQueue(character); Entity.Spawner.AddToRemoveQueue(character);
string speciesName = GetHuskedSpeciesName(character.SpeciesName, Prefab as AfflictionPrefabHusk); string huskedSpeciesName = GetHuskedSpeciesName(character.SpeciesName, Prefab as AfflictionPrefabHusk);
CharacterPrefab prefab = CharacterPrefab.FindBySpeciesName(speciesName); CharacterPrefab prefab = CharacterPrefab.FindBySpeciesName(huskedSpeciesName);
if (prefab == null) if (prefab == null)
{ {
DebugConsole.ThrowError("Failed to turn character \"" + character.Name + "\" into a husk - husk config file not found."); DebugConsole.ThrowError("Failed to turn character \"" + character.Name + "\" into a husk - husk config file not found.");
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
var husk = Character.Create(huskedSpeciesName, character.WorldPosition, ToolBox.RandomSeed(8), character.Info, isRemotePlayer: false, hasAi: true);
var husk = Character.Create(speciesName, character.WorldPosition, character.Info.Name, character.Info, isRemotePlayer: false, hasAi: true, ragdoll: character.AnimController.RagdollParams);
foreach (Limb limb in husk.AnimController.Limbs) 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.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; yield return CoroutineStatus.Success;
} }
@@ -791,8 +791,7 @@ namespace Barotrauma
/// </summary> /// </summary>
/// <param name="treatmentSuitability">A dictionary where the key is the identifier of the item and the value the suitability</param> /// <param name="treatmentSuitability">A dictionary where the key is the identifier of the item and the value the suitability</param>
/// <param name="normalize">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.</param> /// <param name="normalize">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.</param>
/// <param name="randomization">Amount of randomization to apply to the values (0 = the values are accurate, 1 = the values are completely random)</param> /// <param name="randomization">Amount of randomization to apply to the values (0 = the values are accurate, 1 = the values are completely random)</param>
public void GetSuitableTreatments(Dictionary<string, float> treatmentSuitability, bool normalize, float randomization = 0.0f) public void GetSuitableTreatments(Dictionary<string, float> treatmentSuitability, bool normalize, float randomization = 0.0f)
{ {
//key = item identifier //key = item identifier
@@ -873,5 +872,11 @@ namespace Barotrauma
} }
partial void RemoveProjSpecific(); partial void RemoveProjSpecific();
/// <summary>
/// Automatically filters out buffs.
/// </summary>
public static IEnumerable<Affliction> SortAfflictionsBySeverity(IEnumerable<Affliction> afflictions) =>
afflictions.Where(a => !a.Prefab.IsBuff).OrderByDescending(a => a.DamagePerSecond).ThenByDescending(a => a.Strength);
} }
} }
@@ -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<ContentFile> files) public static void LoadAll(IEnumerable<ContentFile> files)
{ {

Some files were not shown because too many files have changed in this diff Show More